@kinetica/admin-agent 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -13
- package/dist/admin-agent.js +2173 -1973
- package/package.json +1 -1
package/dist/admin-agent.js
CHANGED
|
@@ -111,1069 +111,831 @@ function printBanner(model) {
|
|
|
111
111
|
const version = getVersion();
|
|
112
112
|
const subtitle = `admin-agent ${import_picocolors.default.dim(`v${version}`)}`;
|
|
113
113
|
const header = model ? `${subtitle}
|
|
114
|
-
${import_picocolors.default.dim(`
|
|
114
|
+
${import_picocolors.default.dim(`Model: ${model}`)}` : subtitle;
|
|
115
115
|
process.stderr.write("\n\n" + gradientize(LOGO) + "\n\n" + header + "\n");
|
|
116
116
|
return subtitle;
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
// src/
|
|
120
|
-
var
|
|
119
|
+
// src/cli/select-model.ts
|
|
120
|
+
var import_prompts5 = require("@inquirer/prompts");
|
|
121
121
|
|
|
122
|
-
// src/
|
|
123
|
-
var
|
|
122
|
+
// src/agent/run-agent.ts
|
|
123
|
+
var import_claude_agent_sdk4 = require("@anthropic-ai/claude-agent-sdk");
|
|
124
|
+
var import_prompts4 = require("@inquirer/prompts");
|
|
125
|
+
var import_picocolors8 = __toESM(require("picocolors"));
|
|
124
126
|
|
|
125
|
-
// src/
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
try {
|
|
129
|
-
const platform = process.platform;
|
|
130
|
-
const { command, args } = platform === "darwin" ? { command: "open", args: [url] } : platform === "win32" ? { command: "cmd", args: ["/c", "start", "", url] } : { command: "xdg-open", args: [url] };
|
|
131
|
-
const child = (0, import_child_process.spawn)(command, args, { detached: true, stdio: "ignore" });
|
|
132
|
-
child.unref();
|
|
133
|
-
return true;
|
|
134
|
-
} catch {
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
127
|
+
// src/agent/diagnostic-sql.ts
|
|
128
|
+
function has(columns, name) {
|
|
129
|
+
return columns.includes(name);
|
|
137
130
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
131
|
+
var FALLBACK_QUERY_HISTORY_SQL = `-- Slow queries in the last hour
|
|
132
|
+
SELECT query_id, user_name, query_text, start_time, stop_time,
|
|
133
|
+
TIMESTAMPDIFF(SECOND, start_time, stop_time) AS elapsed_sec
|
|
134
|
+
FROM ki_catalog.ki_query_history
|
|
135
|
+
WHERE start_time > NOW() - INTERVAL '1' HOUR
|
|
136
|
+
ORDER BY elapsed_sec DESC
|
|
137
|
+
LIMIT 20;`;
|
|
138
|
+
var FALLBACK_ACTIVE_QUERIES_SQL = `-- Currently active queries
|
|
139
|
+
SELECT query_id, user_name, query_text, start_time, execution_status
|
|
140
|
+
FROM ki_catalog.ki_query_active_all
|
|
141
|
+
ORDER BY start_time ASC;`;
|
|
142
|
+
var FALLBACK_TIERED_OBJECTS_SQL = `-- Objects in disk tier (potential memory pressure)
|
|
143
|
+
-- NOTE: id is a string (e.g. @schema@oid[col][0]), NOT a numeric OID. Filter by table: WHERE id LIKE '%table_name%'
|
|
144
|
+
-- For per-table tier placement, prefer kinetica_resource_objects with table_names filter.
|
|
145
|
+
SELECT id, tier, size, source_rank, owner_resource_group
|
|
146
|
+
FROM ki_catalog.ki_tiered_objects
|
|
147
|
+
WHERE tier != 'VRAM'
|
|
148
|
+
ORDER BY size DESC
|
|
149
|
+
LIMIT 20;`;
|
|
150
|
+
var FALLBACK_OBJ_STAT_SQL = `-- Table sizes and row counts
|
|
151
|
+
SELECT object_name, total_bytes, row_count
|
|
152
|
+
FROM ki_catalog.ki_obj_stat
|
|
153
|
+
ORDER BY total_bytes DESC
|
|
154
|
+
LIMIT 30;`;
|
|
155
|
+
var FALLBACK_COLUMNS_SQL = `-- Structural column metadata (use kinetica_show_table for Kinetica-native types)
|
|
156
|
+
SELECT c.table_name, c.column_name, c.column_position
|
|
157
|
+
FROM ki_catalog.ki_columns c
|
|
158
|
+
WHERE c.table_name = '<TABLE_NAME>'
|
|
159
|
+
ORDER BY c.column_position;`;
|
|
160
|
+
var FALLBACK_DATATYPES_SQL = `-- Resolve column_type_oid to human-readable type name
|
|
161
|
+
SELECT oid, name, sql_typename
|
|
162
|
+
FROM ki_catalog.ki_datatypes
|
|
163
|
+
ORDER BY oid;`;
|
|
164
|
+
var FALLBACK_QUERY_SPAN_METRICS_SQL = `-- Query span metrics for a specific query
|
|
165
|
+
SELECT query_id, span_id, parent_span_id, operator, sql_step,
|
|
166
|
+
metric_data, start_time, stop_time, source_rank
|
|
167
|
+
FROM ki_catalog.ki_query_span_metrics_all
|
|
168
|
+
WHERE query_id = '<QUERY_ID>'
|
|
169
|
+
ORDER BY start_time;`;
|
|
170
|
+
var FALLBACK_QUERY_WORKERS_SQL = `-- Active query workers (non-idle)
|
|
171
|
+
SELECT job_id, worker_id, type, status, elapsed_time_ms, source_rank
|
|
172
|
+
FROM ki_catalog.ki_query_workers
|
|
173
|
+
WHERE status != 'IDLE'
|
|
174
|
+
ORDER BY elapsed_time_ms DESC;`;
|
|
175
|
+
var FALLBACK_OBJECTS_SQL = `-- Object registry and metadata
|
|
176
|
+
SELECT oid, object_name, schema_name, type_id, persistence, obj_kind,
|
|
177
|
+
creation_time, last_read_time, read_count, write_count
|
|
178
|
+
FROM ki_catalog.ki_objects
|
|
179
|
+
ORDER BY last_read_time DESC
|
|
180
|
+
LIMIT 30;`;
|
|
181
|
+
var FALLBACK_PARTITIONS_SQL = `-- Partition sizes and tier distribution
|
|
182
|
+
SELECT oid, object_name, schema_name, rank_num, partition_type,
|
|
183
|
+
partition_id, num_rows, actual_bytes, tier
|
|
184
|
+
FROM ki_catalog.ki_partitions
|
|
185
|
+
ORDER BY actual_bytes DESC
|
|
186
|
+
LIMIT 30;`;
|
|
187
|
+
var FALLBACK_INDEXES_SQL = `-- Index definitions
|
|
188
|
+
SELECT oid, object_name, schema_name, index_type, index_columns
|
|
189
|
+
FROM ki_catalog.ki_indexes
|
|
190
|
+
ORDER BY object_name;`;
|
|
191
|
+
var FALLBACK_PERIODIC_OBJECTS_SQL = `-- Periodic refresh schedules
|
|
192
|
+
SELECT oid, object_name, schema_name, last_refresh_time,
|
|
193
|
+
next_refresh_time, additional_info
|
|
194
|
+
FROM ki_catalog.ki_periodic_objects
|
|
195
|
+
ORDER BY next_refresh_time;`;
|
|
196
|
+
var FALLBACK_USERS_AND_ROLES_SQL = `-- Users and roles
|
|
197
|
+
SELECT oid, name, can_login, is_superuser, resource_group
|
|
198
|
+
FROM ki_catalog.ki_users_and_roles
|
|
199
|
+
ORDER BY name;`;
|
|
200
|
+
var FALLBACK_OBJECT_PERMISSIONS_SQL = `-- Object permissions
|
|
201
|
+
SELECT role_name, permission_type, object_type, object_name, with_grant_option
|
|
202
|
+
FROM ki_catalog.ki_object_permissions
|
|
203
|
+
ORDER BY object_name, role_name;`;
|
|
204
|
+
var FALLBACK_DEPEND_SQL = `-- Object dependency graph
|
|
205
|
+
SELECT src_obj_oid, src_obj_kind, dep_obj_oid, dep_obj_kind, mv_oid, dep_kind
|
|
206
|
+
FROM ki_catalog.ki_depend;`;
|
|
207
|
+
var FALLBACK_LOAD_HISTORY_SQL = `-- Recent data load history
|
|
208
|
+
SELECT table_oid, datasource_oid, user_name, load_kind,
|
|
209
|
+
start_time, end_time, rows_inserted, event_message
|
|
210
|
+
FROM ki_catalog.ki_load_history
|
|
211
|
+
WHERE start_time > NOW() - INTERVAL '1' HOUR
|
|
212
|
+
ORDER BY start_time DESC
|
|
213
|
+
LIMIT 20;`;
|
|
214
|
+
var FALLBACK_BACKUP_HISTORY_SQL = `-- Backup history
|
|
215
|
+
SELECT backup_name, operation, status, start_time, end_time,
|
|
216
|
+
num_files, num_bytes, num_records
|
|
217
|
+
FROM ki_catalog.ki_backup_history
|
|
218
|
+
ORDER BY start_time DESC
|
|
219
|
+
LIMIT 20;`;
|
|
220
|
+
var FALLBACK_KAFKA_LAG_INFO_SQL = `-- Kafka consumer lag
|
|
221
|
+
SELECT datasource_oid, table_oid, schema_name, table_name,
|
|
222
|
+
partition_id, highest_offset, last_committed_offset
|
|
223
|
+
FROM ki_catalog.ki_kafka_lag_info
|
|
224
|
+
ORDER BY datasource_oid, partition_id;`;
|
|
225
|
+
function buildQueryHistorySql(columns) {
|
|
226
|
+
const select2 = columns.join(", ");
|
|
227
|
+
const elapsed = has(columns, "start_time") && has(columns, "stop_time") ? ",\n TIMESTAMPDIFF(SECOND, start_time, stop_time) AS elapsed_sec" : "";
|
|
228
|
+
const where = has(columns, "start_time") ? "\nWHERE start_time > NOW() - INTERVAL '1' HOUR" : "";
|
|
229
|
+
const orderBy = has(columns, "start_time") && has(columns, "stop_time") ? (
|
|
230
|
+
// Kinetica does not support timestamp arithmetic in ORDER BY; use the elapsed_sec alias instead
|
|
231
|
+
"\nORDER BY elapsed_sec DESC"
|
|
232
|
+
) : "";
|
|
233
|
+
return `-- Slow queries in the last hour
|
|
234
|
+
SELECT ${select2}${elapsed}
|
|
235
|
+
FROM ki_catalog.ki_query_history${where}${orderBy}
|
|
236
|
+
LIMIT 20;`;
|
|
169
237
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
authQuery.accountInfo(),
|
|
177
|
-
new Promise(
|
|
178
|
-
(_, reject) => setTimeout(() => reject(new Error("Probe timed out")), PROBE_TIMEOUT_MS)
|
|
179
|
-
)
|
|
180
|
-
]);
|
|
181
|
-
if (info.email || info.apiKeySource) {
|
|
182
|
-
return info;
|
|
183
|
-
}
|
|
184
|
-
return null;
|
|
185
|
-
} catch {
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
238
|
+
function buildActiveQueriesSql(columns) {
|
|
239
|
+
const select2 = columns.join(", ");
|
|
240
|
+
const orderBy = has(columns, "start_time") ? "\nORDER BY start_time ASC" : "";
|
|
241
|
+
return `-- Currently active queries
|
|
242
|
+
SELECT ${select2}
|
|
243
|
+
FROM ki_catalog.ki_query_active_all${orderBy};`;
|
|
188
244
|
}
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
}
|
|
199
|
-
const env = options.forceLogin ? (() => {
|
|
200
|
-
const { ANTHROPIC_API_KEY: _stripped, ...rest } = process.env;
|
|
201
|
-
return { ...rest, CLAUDE_AGENT_SDK_CLIENT_APP: "admin-agent" };
|
|
202
|
-
})() : { ...process.env, CLAUDE_AGENT_SDK_CLIENT_APP: "admin-agent" };
|
|
203
|
-
const abortController = new AbortController();
|
|
204
|
-
async function* hangingPrompt() {
|
|
205
|
-
await new Promise(() => {
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
const authQuery = (0, import_claude_agent_sdk.query)({
|
|
209
|
-
prompt: hangingPrompt(),
|
|
210
|
-
options: {
|
|
211
|
-
persistSession: false,
|
|
212
|
-
abortController,
|
|
213
|
-
env,
|
|
214
|
-
...options.loginMethod ? { forceLoginMethod: options.loginMethod } : {},
|
|
215
|
-
...options.loginOrgUUID ? { forceLoginOrgUUID: options.loginOrgUUID } : {}
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
try {
|
|
219
|
-
if (!options.forceLogin) {
|
|
220
|
-
const cached = await probeCachedCredentials(authQuery);
|
|
221
|
-
if (cached) {
|
|
222
|
-
return { method: "oauth", email: cached.email };
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
return await resolveAuthentication(authQuery, {
|
|
226
|
-
forceLogin: options.forceLogin,
|
|
227
|
-
loginWithClaudeAi: (options.loginMethod ?? "claudeai") === "claudeai",
|
|
228
|
-
hasApiKey
|
|
229
|
-
});
|
|
230
|
-
} finally {
|
|
231
|
-
abortController.abort();
|
|
232
|
-
await authQuery.return().catch(() => {
|
|
233
|
-
});
|
|
234
|
-
}
|
|
245
|
+
function buildTieredObjectsSql(columns) {
|
|
246
|
+
const select2 = columns.join(", ");
|
|
247
|
+
const where = has(columns, "tier") ? "\nWHERE tier != 'VRAM'" : "";
|
|
248
|
+
const orderBy = has(columns, "size") ? "\nORDER BY size DESC" : "";
|
|
249
|
+
const idNote = has(columns, "id") ? "\n-- NOTE: id is a string (e.g. @schema@oid[col][0]), NOT a numeric OID. Filter by table: WHERE id LIKE '%table_name%'" : "";
|
|
250
|
+
return `-- Objects in disk tier (potential memory pressure)${idNote}
|
|
251
|
+
SELECT ${select2}
|
|
252
|
+
FROM ki_catalog.ki_tiered_objects${where}${orderBy}
|
|
253
|
+
LIMIT 20;`;
|
|
235
254
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
function resolveSdkCliPath() {
|
|
244
|
-
const require_ = (0, import_node_module.createRequire)(__filename);
|
|
245
|
-
return import_node_path.default.join(import_node_path.default.dirname(require_.resolve("@anthropic-ai/claude-agent-sdk")), "cli.js");
|
|
255
|
+
function buildObjStatSql(columns) {
|
|
256
|
+
const select2 = columns.join(", ");
|
|
257
|
+
const orderBy = has(columns, "total_bytes") ? "\nORDER BY total_bytes DESC" : "";
|
|
258
|
+
return `-- Table sizes and row counts
|
|
259
|
+
SELECT ${select2}
|
|
260
|
+
FROM ki_catalog.ki_obj_stat${orderBy}
|
|
261
|
+
LIMIT 30;`;
|
|
246
262
|
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
var import_picocolors3 = __toESM(require("picocolors"));
|
|
269
|
-
function parseEnvContent(content) {
|
|
270
|
-
const entries = [];
|
|
271
|
-
for (const line of content.split("\n")) {
|
|
272
|
-
const trimmed = line.trim();
|
|
273
|
-
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
274
|
-
const match = /^([A-Za-z_]\w*)=(.*)/.exec(trimmed);
|
|
275
|
-
if (!match) continue;
|
|
276
|
-
const key = match[1];
|
|
277
|
-
let value = match[2];
|
|
278
|
-
if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
|
|
279
|
-
value = value.slice(1, -1).replace(/\\(["\\])/g, "$1");
|
|
280
|
-
} else if (value.startsWith("'") && value.endsWith("'") && value.length >= 2) {
|
|
281
|
-
value = value.slice(1, -1);
|
|
282
|
-
}
|
|
283
|
-
entries.push([key, value]);
|
|
284
|
-
}
|
|
285
|
-
return new Map(entries);
|
|
263
|
+
var STRUCTURAL_COLUMNS = /* @__PURE__ */ new Set([
|
|
264
|
+
"table_name",
|
|
265
|
+
"column_name",
|
|
266
|
+
"column_position",
|
|
267
|
+
"is_nullable",
|
|
268
|
+
"is_shard_key",
|
|
269
|
+
"is_primary_key",
|
|
270
|
+
"is_dict_encoded",
|
|
271
|
+
"default_value",
|
|
272
|
+
"bytes_on_disk_uncompressed",
|
|
273
|
+
"bytes_on_disk_compressed"
|
|
274
|
+
]);
|
|
275
|
+
function buildColumnsSql(columns) {
|
|
276
|
+
const filtered = columns.filter((c) => STRUCTURAL_COLUMNS.has(c));
|
|
277
|
+
const selectCols = filtered.length > 0 ? filtered : columns;
|
|
278
|
+
const select2 = selectCols.map((c) => `c.${c}`).join(", ");
|
|
279
|
+
const where = has(selectCols, "table_name") ? "\nWHERE c.table_name = '<TABLE_NAME>'" : "";
|
|
280
|
+
const orderBy = has(selectCols, "column_position") ? "\nORDER BY c.column_position" : has(selectCols, "column_name") ? "\nORDER BY c.column_name" : "";
|
|
281
|
+
return `-- Structural column metadata (use kinetica_show_table for Kinetica-native types)
|
|
282
|
+
SELECT ${select2}
|
|
283
|
+
FROM ki_catalog.ki_columns c${where}${orderBy};`;
|
|
286
284
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
);
|
|
294
|
-
}
|
|
295
|
-
if (!REQUIRES_QUOTING.test(value)) {
|
|
296
|
-
return value;
|
|
297
|
-
}
|
|
298
|
-
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
299
|
-
return `"${escaped}"`;
|
|
285
|
+
function buildDatatypesSql(columns) {
|
|
286
|
+
const select2 = columns.join(", ");
|
|
287
|
+
const orderBy = has(columns, "oid") ? "\nORDER BY oid" : "";
|
|
288
|
+
return `-- Resolve column_type_oid to human-readable type name
|
|
289
|
+
SELECT ${select2}
|
|
290
|
+
FROM ki_catalog.ki_datatypes${orderBy};`;
|
|
300
291
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
KINETICA_PASS=
|
|
309
|
-
`;
|
|
310
|
-
function buildEnvContent(url, user, existingContent) {
|
|
311
|
-
const safeUrl = escapeEnvValue(url);
|
|
312
|
-
const safeUser = escapeEnvValue(user);
|
|
313
|
-
if (!existingContent?.trim()) {
|
|
314
|
-
return ENV_TEMPLATE.replace("{url}", safeUrl).replace("{user}", safeUser);
|
|
315
|
-
}
|
|
316
|
-
const lines = existingContent.split("\n");
|
|
317
|
-
let urlReplaced = false;
|
|
318
|
-
let userReplaced = false;
|
|
319
|
-
const updated = lines.map((line) => {
|
|
320
|
-
if (/^KINETICA_URL=/.exec(line)) {
|
|
321
|
-
urlReplaced = true;
|
|
322
|
-
return `KINETICA_URL=${safeUrl}`;
|
|
323
|
-
}
|
|
324
|
-
if (/^KINETICA_USER=/.exec(line)) {
|
|
325
|
-
userReplaced = true;
|
|
326
|
-
return `KINETICA_USER=${safeUser}`;
|
|
327
|
-
}
|
|
328
|
-
return line;
|
|
329
|
-
});
|
|
330
|
-
if (!urlReplaced) updated.push(`KINETICA_URL=${safeUrl}`);
|
|
331
|
-
if (!userReplaced) updated.push(`KINETICA_USER=${safeUser}`);
|
|
332
|
-
return updated.join("\n");
|
|
292
|
+
function buildQuerySpanMetricsSql(columns) {
|
|
293
|
+
const select2 = columns.join(", ");
|
|
294
|
+
const where = has(columns, "query_id") ? "\nWHERE query_id = '<QUERY_ID>'" : "";
|
|
295
|
+
const orderBy = has(columns, "start_time") ? "\nORDER BY start_time" : "";
|
|
296
|
+
return `-- Query span metrics for a specific query
|
|
297
|
+
SELECT ${select2}
|
|
298
|
+
FROM ki_catalog.ki_query_span_metrics_all${where}${orderBy};`;
|
|
333
299
|
}
|
|
334
|
-
function
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
env[key] = value;
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
} catch {
|
|
345
|
-
}
|
|
300
|
+
function buildQueryWorkersSql(columns) {
|
|
301
|
+
const select2 = columns.join(", ");
|
|
302
|
+
const where = has(columns, "status") ? "\nWHERE status != 'IDLE'" : "";
|
|
303
|
+
const orderBy = has(columns, "elapsed_time_ms") ? "\nORDER BY elapsed_time_ms DESC" : "";
|
|
304
|
+
return `-- Active query workers (non-idle)
|
|
305
|
+
SELECT ${select2}
|
|
306
|
+
FROM ki_catalog.ki_query_workers${where}${orderBy};`;
|
|
346
307
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
if (!shouldSave) return;
|
|
355
|
-
const filePath = (0, import_path2.join)(dir ?? process.cwd(), ".env");
|
|
356
|
-
let existing;
|
|
357
|
-
try {
|
|
358
|
-
existing = await (0, import_promises.readFile)(filePath, "utf8");
|
|
359
|
-
} catch {
|
|
360
|
-
}
|
|
361
|
-
const content = buildEnvContent(url, user, existing);
|
|
362
|
-
await (0, import_promises.writeFile)(filePath, content, "utf8");
|
|
363
|
-
console.error(import_picocolors3.default.dim("Saved to .env"));
|
|
364
|
-
} catch (err) {
|
|
365
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
366
|
-
console.error(import_picocolors3.default.yellow(`Could not save .env file: ${message}`));
|
|
367
|
-
}
|
|
308
|
+
function buildObjectsSql(columns) {
|
|
309
|
+
const select2 = columns.join(", ");
|
|
310
|
+
const orderBy = has(columns, "last_read_time") ? "\nORDER BY last_read_time DESC" : has(columns, "creation_time") ? "\nORDER BY creation_time DESC" : "";
|
|
311
|
+
return `-- Object registry and metadata
|
|
312
|
+
SELECT ${select2}
|
|
313
|
+
FROM ki_catalog.ki_objects${orderBy}
|
|
314
|
+
LIMIT 30;`;
|
|
368
315
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
var import_picocolors4 = __toESM(require("picocolors"));
|
|
377
|
-
async function collectCredentials() {
|
|
378
|
-
const prompted = /* @__PURE__ */ new Set();
|
|
379
|
-
const envUrl = process.env.KINETICA_URL;
|
|
380
|
-
const envUser = process.env.KINETICA_USER;
|
|
381
|
-
if (envUrl && envUser && process.stdin.isTTY) {
|
|
382
|
-
console.error(import_picocolors4.default.dim(`Saved connection: ${envUrl} (${envUser})`));
|
|
383
|
-
const useSaved = await (0, import_prompts2.confirm)({
|
|
384
|
-
message: "Use saved connection?",
|
|
385
|
-
default: true
|
|
386
|
-
});
|
|
387
|
-
if (!useSaved) {
|
|
388
|
-
prompted.add("url");
|
|
389
|
-
prompted.add("user");
|
|
390
|
-
const url2 = await (0, import_prompts2.input)({ message: "Kinetica endpoint URL:" });
|
|
391
|
-
const user2 = await (0, import_prompts2.input)({ message: "Admin username:" });
|
|
392
|
-
const pass2 = await (0, import_prompts2.password)({ message: "Admin password:", mask: "*" });
|
|
393
|
-
return { credentials: { url: url2, user: user2, pass: pass2 }, prompted };
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
const url = envUrl ?? (prompted.add("url"), await (0, import_prompts2.input)({ message: "Kinetica endpoint URL:" }));
|
|
397
|
-
const user = envUser ?? (prompted.add("user"), await (0, import_prompts2.input)({ message: "Admin username:" }));
|
|
398
|
-
const pass = process.env.KINETICA_PASS ?? await (0, import_prompts2.password)({ message: "Admin password:", mask: "*" });
|
|
399
|
-
return { credentials: { url, user, pass }, prompted };
|
|
316
|
+
function buildPartitionsSql(columns) {
|
|
317
|
+
const select2 = columns.join(", ");
|
|
318
|
+
const orderBy = has(columns, "actual_bytes") ? "\nORDER BY actual_bytes DESC" : "";
|
|
319
|
+
return `-- Partition sizes and tier distribution
|
|
320
|
+
SELECT ${select2}
|
|
321
|
+
FROM ki_catalog.ki_partitions${orderBy}
|
|
322
|
+
LIMIT 30;`;
|
|
400
323
|
}
|
|
401
|
-
|
|
402
|
-
const
|
|
403
|
-
const
|
|
404
|
-
return
|
|
324
|
+
function buildIndexesSql(columns) {
|
|
325
|
+
const select2 = columns.join(", ");
|
|
326
|
+
const orderBy = has(columns, "object_name") ? "\nORDER BY object_name" : "";
|
|
327
|
+
return `-- Index definitions
|
|
328
|
+
SELECT ${select2}
|
|
329
|
+
FROM ki_catalog.ki_indexes${orderBy};`;
|
|
405
330
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
return parsed.origin;
|
|
331
|
+
function buildPeriodicObjectsSql(columns) {
|
|
332
|
+
const select2 = columns.join(", ");
|
|
333
|
+
const orderBy = has(columns, "next_refresh_time") ? "\nORDER BY next_refresh_time" : "";
|
|
334
|
+
return `-- Periodic refresh schedules
|
|
335
|
+
SELECT ${select2}
|
|
336
|
+
FROM ki_catalog.ki_periodic_objects${orderBy};`;
|
|
413
337
|
}
|
|
414
|
-
function
|
|
415
|
-
const
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
return fetch(fullUrl, {
|
|
421
|
-
method: "POST",
|
|
422
|
-
headers: {
|
|
423
|
-
Authorization: authHeader,
|
|
424
|
-
"Content-Type": "application/json"
|
|
425
|
-
},
|
|
426
|
-
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
427
|
-
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
428
|
-
});
|
|
429
|
-
};
|
|
430
|
-
return {
|
|
431
|
-
baseUrl: url,
|
|
432
|
-
makeRequest: (endpoint, body) => doFetch(`${url}${endpoint}`, body),
|
|
433
|
-
makeRequestToPort: (port, endpoint, body) => doFetch(`${replacePort(url, port)}${endpoint}`, body)
|
|
434
|
-
};
|
|
338
|
+
function buildUsersAndRolesSql(columns) {
|
|
339
|
+
const select2 = columns.join(", ");
|
|
340
|
+
const orderBy = has(columns, "name") ? "\nORDER BY name" : "";
|
|
341
|
+
return `-- Users and roles
|
|
342
|
+
SELECT ${select2}
|
|
343
|
+
FROM ki_catalog.ki_users_and_roles${orderBy};`;
|
|
435
344
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
var ANY_PROTOCOL_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
443
|
-
function hasProtocol(input5) {
|
|
444
|
-
return HTTP_PROTOCOL_RE.test(input5);
|
|
345
|
+
function buildObjectPermissionsSql(columns) {
|
|
346
|
+
const select2 = columns.join(", ");
|
|
347
|
+
const orderBy = has(columns, "object_name") && has(columns, "role_name") ? "\nORDER BY object_name, role_name" : has(columns, "object_name") ? "\nORDER BY object_name" : "";
|
|
348
|
+
return `-- Object permissions
|
|
349
|
+
SELECT ${select2}
|
|
350
|
+
FROM ki_catalog.ki_object_permissions${orderBy};`;
|
|
445
351
|
}
|
|
446
|
-
function
|
|
447
|
-
|
|
352
|
+
function buildDependSql(columns) {
|
|
353
|
+
const select2 = columns.join(", ");
|
|
354
|
+
return `-- Object dependency graph
|
|
355
|
+
SELECT ${select2}
|
|
356
|
+
FROM ki_catalog.ki_depend;`;
|
|
448
357
|
}
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
return false;
|
|
458
|
-
}
|
|
358
|
+
function buildLoadHistorySql(columns) {
|
|
359
|
+
const select2 = columns.join(", ");
|
|
360
|
+
const where = has(columns, "start_time") ? "\nWHERE start_time > NOW() - INTERVAL '1' HOUR" : "";
|
|
361
|
+
const orderBy = has(columns, "start_time") ? "\nORDER BY start_time DESC" : "";
|
|
362
|
+
return `-- Recent data load history
|
|
363
|
+
SELECT ${select2}
|
|
364
|
+
FROM ki_catalog.ki_load_history${where}${orderBy}
|
|
365
|
+
LIMIT 20;`;
|
|
459
366
|
}
|
|
460
|
-
function
|
|
461
|
-
|
|
367
|
+
function buildBackupHistorySql(columns) {
|
|
368
|
+
const select2 = columns.join(", ");
|
|
369
|
+
const orderBy = has(columns, "start_time") ? "\nORDER BY start_time DESC" : "";
|
|
370
|
+
return `-- Backup history
|
|
371
|
+
SELECT ${select2}
|
|
372
|
+
FROM ki_catalog.ki_backup_history${orderBy}
|
|
373
|
+
LIMIT 20;`;
|
|
462
374
|
}
|
|
463
|
-
function
|
|
464
|
-
|
|
375
|
+
function buildKafkaLagInfoSql(columns) {
|
|
376
|
+
const select2 = columns.join(", ");
|
|
377
|
+
const orderBy = has(columns, "datasource_oid") && has(columns, "partition_id") ? "\nORDER BY datasource_oid, partition_id" : has(columns, "datasource_oid") ? "\nORDER BY datasource_oid" : "";
|
|
378
|
+
return `-- Kafka consumer lag
|
|
379
|
+
SELECT ${select2}
|
|
380
|
+
FROM ki_catalog.ki_kafka_lag_info${orderBy};`;
|
|
465
381
|
}
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
}
|
|
486
|
-
|
|
382
|
+
var BUILDER_REGISTRY = [
|
|
383
|
+
// Query History and Performance
|
|
384
|
+
{
|
|
385
|
+
table: "ki_query_history",
|
|
386
|
+
section: "Query History and Performance",
|
|
387
|
+
build: buildQueryHistorySql,
|
|
388
|
+
fallback: FALLBACK_QUERY_HISTORY_SQL
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
table: "ki_query_active_all",
|
|
392
|
+
section: "Query History and Performance",
|
|
393
|
+
build: buildActiveQueriesSql,
|
|
394
|
+
fallback: FALLBACK_ACTIVE_QUERIES_SQL
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
table: "ki_query_span_metrics_all",
|
|
398
|
+
section: "Query History and Performance",
|
|
399
|
+
build: buildQuerySpanMetricsSql,
|
|
400
|
+
fallback: FALLBACK_QUERY_SPAN_METRICS_SQL
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
table: "ki_query_workers",
|
|
404
|
+
section: "Query History and Performance",
|
|
405
|
+
build: buildQueryWorkersSql,
|
|
406
|
+
fallback: FALLBACK_QUERY_WORKERS_SQL
|
|
407
|
+
},
|
|
408
|
+
// Memory and Storage Tiers
|
|
409
|
+
{
|
|
410
|
+
table: "ki_tiered_objects",
|
|
411
|
+
section: "Memory and Storage Tiers",
|
|
412
|
+
build: buildTieredObjectsSql,
|
|
413
|
+
fallback: FALLBACK_TIERED_OBJECTS_SQL
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
table: "ki_obj_stat",
|
|
417
|
+
section: "Memory and Storage Tiers",
|
|
418
|
+
build: buildObjStatSql,
|
|
419
|
+
fallback: FALLBACK_OBJ_STAT_SQL
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
table: "ki_partitions",
|
|
423
|
+
section: "Memory and Storage Tiers",
|
|
424
|
+
build: buildPartitionsSql,
|
|
425
|
+
fallback: FALLBACK_PARTITIONS_SQL
|
|
426
|
+
},
|
|
427
|
+
// Object Registry and Metadata
|
|
428
|
+
{
|
|
429
|
+
table: "ki_objects",
|
|
430
|
+
section: "Object Registry and Metadata",
|
|
431
|
+
build: buildObjectsSql,
|
|
432
|
+
fallback: FALLBACK_OBJECTS_SQL
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
table: "ki_indexes",
|
|
436
|
+
section: "Object Registry and Metadata",
|
|
437
|
+
build: buildIndexesSql,
|
|
438
|
+
fallback: FALLBACK_INDEXES_SQL
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
table: "ki_periodic_objects",
|
|
442
|
+
section: "Object Registry and Metadata",
|
|
443
|
+
build: buildPeriodicObjectsSql,
|
|
444
|
+
fallback: FALLBACK_PERIODIC_OBJECTS_SQL
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
table: "ki_depend",
|
|
448
|
+
section: "Object Registry and Metadata",
|
|
449
|
+
build: buildDependSql,
|
|
450
|
+
fallback: FALLBACK_DEPEND_SQL
|
|
451
|
+
},
|
|
452
|
+
// Security and Access Control
|
|
453
|
+
{
|
|
454
|
+
table: "ki_users_and_roles",
|
|
455
|
+
section: "Security and Access Control",
|
|
456
|
+
build: buildUsersAndRolesSql,
|
|
457
|
+
fallback: FALLBACK_USERS_AND_ROLES_SQL
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
table: "ki_object_permissions",
|
|
461
|
+
section: "Security and Access Control",
|
|
462
|
+
build: buildObjectPermissionsSql,
|
|
463
|
+
fallback: FALLBACK_OBJECT_PERMISSIONS_SQL
|
|
464
|
+
},
|
|
465
|
+
// Data Ingestion and Operations
|
|
466
|
+
{
|
|
467
|
+
table: "ki_load_history",
|
|
468
|
+
section: "Data Ingestion and Operations",
|
|
469
|
+
build: buildLoadHistorySql,
|
|
470
|
+
fallback: FALLBACK_LOAD_HISTORY_SQL
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
table: "ki_backup_history",
|
|
474
|
+
section: "Data Ingestion and Operations",
|
|
475
|
+
build: buildBackupHistorySql,
|
|
476
|
+
fallback: FALLBACK_BACKUP_HISTORY_SQL
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
table: "ki_kafka_lag_info",
|
|
480
|
+
section: "Data Ingestion and Operations",
|
|
481
|
+
build: buildKafkaLagInfoSql,
|
|
482
|
+
fallback: FALLBACK_KAFKA_LAG_INFO_SQL
|
|
483
|
+
},
|
|
484
|
+
// Schema Inspection
|
|
485
|
+
{
|
|
486
|
+
table: "ki_columns",
|
|
487
|
+
section: "Schema Inspection",
|
|
488
|
+
build: buildColumnsSql,
|
|
489
|
+
fallback: FALLBACK_COLUMNS_SQL
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
table: "ki_datatypes",
|
|
493
|
+
section: "Schema Inspection",
|
|
494
|
+
build: buildDatatypesSql,
|
|
495
|
+
fallback: FALLBACK_DATATYPES_SQL
|
|
487
496
|
}
|
|
497
|
+
];
|
|
498
|
+
|
|
499
|
+
// src/tools/index.ts
|
|
500
|
+
var import_claude_agent_sdk2 = require("@anthropic-ai/claude-agent-sdk");
|
|
501
|
+
var import_zod16 = require("zod");
|
|
502
|
+
var import_picocolors4 = __toESM(require("picocolors"));
|
|
503
|
+
|
|
504
|
+
// src/approval/registry.ts
|
|
505
|
+
var DEFAULT_READ_ONLY_TOOLS = /* @__PURE__ */ new Set();
|
|
506
|
+
function createRegistry(tools = DEFAULT_READ_ONLY_TOOLS) {
|
|
507
|
+
return {
|
|
508
|
+
isReadOnlyTool: (toolName) => tools.has(toolName),
|
|
509
|
+
// Returns a NEW registry — never mutates the current one
|
|
510
|
+
registerReadOnlyTool: (toolName) => createRegistry(/* @__PURE__ */ new Set([...tools, toolName])),
|
|
511
|
+
tools
|
|
512
|
+
};
|
|
488
513
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
if (
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const scheme = normalized.split("://")[0];
|
|
500
|
-
return {
|
|
501
|
-
ok: false,
|
|
502
|
-
error: `Unsupported protocol: ${scheme}. Use http:// or https://`
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
const httpsUrl = `https://${normalized}`;
|
|
506
|
-
const httpUrl = `http://${normalized}`;
|
|
507
|
-
console.error(import_picocolors5.default.dim("Detecting protocol..."));
|
|
508
|
-
if (await probeProtocol(httpsUrl)) {
|
|
509
|
-
return { ok: true, url: httpsUrl };
|
|
510
|
-
}
|
|
511
|
-
if (isHttpsOnly()) {
|
|
512
|
-
return {
|
|
513
|
-
ok: false,
|
|
514
|
-
error: `HTTPS probe to ${httpsUrl} failed and KINETICA_HTTPS_ONLY=1; refusing to fall back to plaintext HTTP.`
|
|
515
|
-
};
|
|
516
|
-
}
|
|
517
|
-
if (!await probeProtocol(httpUrl)) {
|
|
518
|
-
return {
|
|
519
|
-
ok: false,
|
|
520
|
-
error: `Could not connect to ${normalized} via https:// or http://`
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
if (!isInteractive()) {
|
|
524
|
-
return {
|
|
525
|
-
ok: false,
|
|
526
|
-
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.`
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
const approved = await confirmHttpFallback(normalized);
|
|
530
|
-
if (!approved) {
|
|
531
|
-
return {
|
|
532
|
-
ok: false,
|
|
533
|
-
error: "User declined plaintext HTTP fallback"
|
|
534
|
-
};
|
|
514
|
+
var defaultRegistry = createRegistry();
|
|
515
|
+
var isReadOnlyTool = defaultRegistry.isReadOnlyTool;
|
|
516
|
+
var READ_ONLY_TOOLS = defaultRegistry.tools;
|
|
517
|
+
|
|
518
|
+
// src/output/stringify.ts
|
|
519
|
+
function stringifyValue(v) {
|
|
520
|
+
if (v === void 0 || v === null) return "";
|
|
521
|
+
if (typeof v === "string") return v;
|
|
522
|
+
if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint") {
|
|
523
|
+
return v.toString();
|
|
535
524
|
}
|
|
536
|
-
|
|
525
|
+
if (typeof v === "object") return JSON.stringify(v);
|
|
526
|
+
if (typeof v === "symbol") return v.toString();
|
|
527
|
+
return "";
|
|
537
528
|
}
|
|
538
529
|
|
|
539
|
-
// src/
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
function extractVersion(responseBody) {
|
|
544
|
-
try {
|
|
545
|
-
const outer = JSON.parse(responseBody);
|
|
546
|
-
if (typeof outer.data_str !== "string") return void 0;
|
|
547
|
-
const inner = JSON.parse(outer.data_str);
|
|
548
|
-
const systemStr = inner.status_map?.system;
|
|
549
|
-
if (typeof systemStr !== "string") return void 0;
|
|
550
|
-
const system = JSON.parse(systemStr);
|
|
551
|
-
return typeof system.version === "string" ? system.version : void 0;
|
|
552
|
-
} catch {
|
|
553
|
-
return void 0;
|
|
530
|
+
// src/output/format.ts
|
|
531
|
+
function formatOutput(json) {
|
|
532
|
+
if (json === null || json === void 0) {
|
|
533
|
+
return "(empty)";
|
|
554
534
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
return
|
|
560
|
-
} catch {
|
|
561
|
-
return void 0;
|
|
535
|
+
if (Array.isArray(json)) {
|
|
536
|
+
return formatArray(json);
|
|
537
|
+
}
|
|
538
|
+
if (typeof json === "object") {
|
|
539
|
+
return formatObject(json);
|
|
562
540
|
}
|
|
541
|
+
return stringifyValue(json);
|
|
563
542
|
}
|
|
564
|
-
|
|
565
|
-
if (
|
|
566
|
-
return
|
|
543
|
+
function formatArray(arr) {
|
|
544
|
+
if (arr.length === 0) {
|
|
545
|
+
return "(no results)";
|
|
567
546
|
}
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
const body = await response.text();
|
|
572
|
-
return { ok: true, version: extractVersionFromHostManager(body) };
|
|
573
|
-
} catch {
|
|
574
|
-
return { ok: false };
|
|
547
|
+
const first = arr[0];
|
|
548
|
+
if (typeof first === "object" && first !== null && !Array.isArray(first)) {
|
|
549
|
+
return formatTableArray(arr);
|
|
575
550
|
}
|
|
551
|
+
return arr.map(stringifyValue).join("\n");
|
|
576
552
|
}
|
|
577
|
-
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
553
|
+
function formatTableArray(rows) {
|
|
554
|
+
const headers = Object.keys(rows[0]);
|
|
555
|
+
const cells = rows.map((row) => headers.map((h) => stringifyValue(row[h])));
|
|
556
|
+
const colWidths = headers.map(
|
|
557
|
+
(h, i) => Math.max(h.length, ...cells.map((row) => row[i].length), 3)
|
|
558
|
+
);
|
|
559
|
+
const pad = (text, col) => text.padEnd(colWidths[col]);
|
|
560
|
+
const headerRow = `| ${headers.map((h, i) => pad(h, i)).join(" | ")} |`;
|
|
561
|
+
const separatorRow = `| ${colWidths.map((w) => "-".repeat(w)).join(" | ")} |`;
|
|
562
|
+
const dataRows = cells.map((row) => `| ${row.map((cell, i) => pad(cell, i)).join(" | ")} |`);
|
|
563
|
+
return [headerRow, separatorRow, ...dataRows].join("\n");
|
|
564
|
+
}
|
|
565
|
+
function formatObject(obj) {
|
|
566
|
+
return Object.entries(obj).map(([key, value]) => {
|
|
567
|
+
if (typeof value === "object" && value !== null) {
|
|
568
|
+
return `**${key}:**
|
|
569
|
+
${formatOutput(value)}`;
|
|
570
|
+
}
|
|
571
|
+
return `**${key}:** ${stringifyValue(value)}`;
|
|
572
|
+
}).join("\n");
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// src/output/format-tool-name.ts
|
|
576
|
+
function formatToolName(toolName) {
|
|
577
|
+
const stripped = toolName.replace(/^mcp__[^_]+__/, "").replace(/^kinetica_/, "");
|
|
578
|
+
return stripped.replace(/_/g, " ");
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// src/types/index.ts
|
|
582
|
+
var DEFAULT_TRUNCATION = {
|
|
583
|
+
headLines: 150,
|
|
584
|
+
tailLines: 50
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
// src/output/truncate.ts
|
|
588
|
+
function truncateOutput(text, options = DEFAULT_TRUNCATION) {
|
|
589
|
+
if (text === "") return "";
|
|
590
|
+
const lines = text.split("\n");
|
|
591
|
+
const { headLines, tailLines } = options;
|
|
592
|
+
const threshold = headLines + tailLines;
|
|
593
|
+
if (lines.length <= threshold) {
|
|
594
|
+
return text;
|
|
582
595
|
}
|
|
583
|
-
const
|
|
584
|
-
|
|
596
|
+
const truncatedCount = lines.length - headLines - tailLines;
|
|
597
|
+
const head = lines.slice(0, headLines);
|
|
598
|
+
const tail = lines.slice(lines.length - tailLines);
|
|
599
|
+
return [...head, "", `[... ${truncatedCount} lines truncated ...]`, "", ...tail].join("\n");
|
|
585
600
|
}
|
|
586
|
-
|
|
587
|
-
|
|
601
|
+
|
|
602
|
+
// src/output/reshape.ts
|
|
603
|
+
function safeString(v) {
|
|
604
|
+
if (typeof v === "object" && v !== null) {
|
|
605
|
+
return JSON.stringify(v);
|
|
606
|
+
}
|
|
607
|
+
return String(v);
|
|
588
608
|
}
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
609
|
+
function flatObjectToRows(obj, keyLabel = "key", valueLabel = "value") {
|
|
610
|
+
return Object.entries(obj).map(([k, v]) => ({
|
|
611
|
+
[keyLabel]: k,
|
|
612
|
+
[valueLabel]: safeString(v)
|
|
613
|
+
}));
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// src/tools/rest/parse-data-str.ts
|
|
617
|
+
function parseDataStr(outerDataStr, raw) {
|
|
618
|
+
if (typeof outerDataStr !== "string") {
|
|
619
|
+
return { ok: true, data: void 0 };
|
|
595
620
|
}
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
621
|
+
try {
|
|
622
|
+
return { ok: true, data: JSON.parse(outerDataStr) };
|
|
623
|
+
} catch (err) {
|
|
624
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
625
|
+
return { ok: false, status: 200, error: `data_str parse error: ${message}`, raw };
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// src/tools/rest/health.ts
|
|
630
|
+
async function healthCheck(session2) {
|
|
631
|
+
let response;
|
|
632
|
+
let rawText;
|
|
633
|
+
try {
|
|
634
|
+
response = await session2.makeRequest("/show/system/status", {});
|
|
635
|
+
rawText = await response.text();
|
|
636
|
+
} catch (err) {
|
|
637
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
638
|
+
return {
|
|
639
|
+
ok: false,
|
|
640
|
+
status: 0,
|
|
641
|
+
error: message,
|
|
642
|
+
raw: ""
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
if (!response.ok) {
|
|
646
|
+
return {
|
|
647
|
+
ok: false,
|
|
648
|
+
status: response.status,
|
|
649
|
+
error: `HTTP ${response.status}`,
|
|
650
|
+
raw: rawText
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
let parsed;
|
|
654
|
+
try {
|
|
655
|
+
parsed = JSON.parse(rawText);
|
|
656
|
+
} catch (err) {
|
|
657
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
658
|
+
return {
|
|
659
|
+
ok: false,
|
|
660
|
+
status: 200,
|
|
661
|
+
error: `JSON parse error: ${message}`,
|
|
662
|
+
raw: rawText
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
const inner = parseDataStr(parsed.data_str, rawText);
|
|
666
|
+
if (!inner.ok) return inner;
|
|
667
|
+
const statusMap = inner.data?.status_map ?? {};
|
|
668
|
+
return {
|
|
669
|
+
ok: true,
|
|
670
|
+
data: flatObjectToRows(statusMap, "component", "status")
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// src/tools/rest/decode-nested-json.ts
|
|
675
|
+
function decodeNestedJsonStrings(obj) {
|
|
676
|
+
return Object.fromEntries(
|
|
677
|
+
Object.entries(obj).map(([k, v]) => {
|
|
678
|
+
if (typeof v !== "string") return [k, v];
|
|
679
|
+
try {
|
|
680
|
+
const parsed = JSON.parse(v);
|
|
681
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
682
|
+
return [k, decodeNestedJsonStrings(parsed)];
|
|
629
683
|
}
|
|
630
|
-
|
|
631
|
-
|
|
684
|
+
return [k, parsed];
|
|
685
|
+
} catch {
|
|
686
|
+
return [k, v];
|
|
632
687
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
await offerSaveCredentials(resolvedUrl, currentUser);
|
|
644
|
-
}
|
|
645
|
-
return { session: session2, kineticaVersion: hmResult.version, degraded: true };
|
|
646
|
-
}
|
|
647
|
-
console.error(import_picocolors6.default.red("Host manager also unreachable. Exiting."));
|
|
648
|
-
process.exit(1);
|
|
688
|
+
})
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// src/tools/rest/flatten-rank-stats.ts
|
|
693
|
+
function findTier(tiers, prefix) {
|
|
694
|
+
for (const [name, data] of Object.entries(tiers)) {
|
|
695
|
+
if (name === prefix || name.startsWith(`${prefix}.`) || name.startsWith(`${prefix}_`) || name.startsWith(prefix) && /^\d/.test(name.slice(prefix.length))) {
|
|
696
|
+
if (typeof data === "object" && data !== null && !Array.isArray(data)) {
|
|
697
|
+
return data;
|
|
649
698
|
}
|
|
650
699
|
}
|
|
651
700
|
}
|
|
652
|
-
|
|
701
|
+
return void 0;
|
|
653
702
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
// src/agent/diagnostic-sql.ts
|
|
661
|
-
function has(columns, name) {
|
|
662
|
-
return columns.includes(name);
|
|
703
|
+
function overallField(tier, field) {
|
|
704
|
+
if (!tier) return "";
|
|
705
|
+
const overall = tier.overall;
|
|
706
|
+
if (typeof overall !== "object" || overall === null) return "";
|
|
707
|
+
const value = overall[field];
|
|
708
|
+
return stringifyValue(value);
|
|
663
709
|
}
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
LIMIT 20;`;
|
|
671
|
-
var FALLBACK_ACTIVE_QUERIES_SQL = `-- Currently active queries
|
|
672
|
-
SELECT query_id, user_name, query_text, start_time, execution_status
|
|
673
|
-
FROM ki_catalog.ki_query_active_all
|
|
674
|
-
ORDER BY start_time ASC;`;
|
|
675
|
-
var FALLBACK_TIERED_OBJECTS_SQL = `-- Objects in disk tier (potential memory pressure)
|
|
676
|
-
-- NOTE: id is a string (e.g. @schema@oid[col][0]), NOT a numeric OID. Filter by table: WHERE id LIKE '%table_name%'
|
|
677
|
-
-- For per-table tier placement, prefer kinetica_resource_objects with table_names filter.
|
|
678
|
-
SELECT id, tier, size, source_rank, owner_resource_group
|
|
679
|
-
FROM ki_catalog.ki_tiered_objects
|
|
680
|
-
WHERE tier != 'VRAM'
|
|
681
|
-
ORDER BY size DESC
|
|
682
|
-
LIMIT 20;`;
|
|
683
|
-
var FALLBACK_OBJ_STAT_SQL = `-- Table sizes and row counts
|
|
684
|
-
SELECT object_name, total_bytes, row_count
|
|
685
|
-
FROM ki_catalog.ki_obj_stat
|
|
686
|
-
ORDER BY total_bytes DESC
|
|
687
|
-
LIMIT 30;`;
|
|
688
|
-
var FALLBACK_COLUMNS_SQL = `-- Structural column metadata (use kinetica_show_table for Kinetica-native types)
|
|
689
|
-
SELECT c.table_name, c.column_name, c.column_position
|
|
690
|
-
FROM ki_catalog.ki_columns c
|
|
691
|
-
WHERE c.table_name = '<TABLE_NAME>'
|
|
692
|
-
ORDER BY c.column_position;`;
|
|
693
|
-
var FALLBACK_DATATYPES_SQL = `-- Resolve column_type_oid to human-readable type name
|
|
694
|
-
SELECT oid, name, sql_typename
|
|
695
|
-
FROM ki_catalog.ki_datatypes
|
|
696
|
-
ORDER BY oid;`;
|
|
697
|
-
var FALLBACK_QUERY_SPAN_METRICS_SQL = `-- Query span metrics for a specific query
|
|
698
|
-
SELECT query_id, span_id, parent_span_id, operator, sql_step,
|
|
699
|
-
metric_data, start_time, stop_time, source_rank
|
|
700
|
-
FROM ki_catalog.ki_query_span_metrics_all
|
|
701
|
-
WHERE query_id = '<QUERY_ID>'
|
|
702
|
-
ORDER BY start_time;`;
|
|
703
|
-
var FALLBACK_QUERY_WORKERS_SQL = `-- Active query workers (non-idle)
|
|
704
|
-
SELECT job_id, worker_id, type, status, elapsed_time_ms, source_rank
|
|
705
|
-
FROM ki_catalog.ki_query_workers
|
|
706
|
-
WHERE status != 'IDLE'
|
|
707
|
-
ORDER BY elapsed_time_ms DESC;`;
|
|
708
|
-
var FALLBACK_OBJECTS_SQL = `-- Object registry and metadata
|
|
709
|
-
SELECT oid, object_name, schema_name, type_id, persistence, obj_kind,
|
|
710
|
-
creation_time, last_read_time, read_count, write_count
|
|
711
|
-
FROM ki_catalog.ki_objects
|
|
712
|
-
ORDER BY last_read_time DESC
|
|
713
|
-
LIMIT 30;`;
|
|
714
|
-
var FALLBACK_PARTITIONS_SQL = `-- Partition sizes and tier distribution
|
|
715
|
-
SELECT oid, object_name, schema_name, rank_num, partition_type,
|
|
716
|
-
partition_id, num_rows, actual_bytes, tier
|
|
717
|
-
FROM ki_catalog.ki_partitions
|
|
718
|
-
ORDER BY actual_bytes DESC
|
|
719
|
-
LIMIT 30;`;
|
|
720
|
-
var FALLBACK_INDEXES_SQL = `-- Index definitions
|
|
721
|
-
SELECT oid, object_name, schema_name, index_type, index_columns
|
|
722
|
-
FROM ki_catalog.ki_indexes
|
|
723
|
-
ORDER BY object_name;`;
|
|
724
|
-
var FALLBACK_PERIODIC_OBJECTS_SQL = `-- Periodic refresh schedules
|
|
725
|
-
SELECT oid, object_name, schema_name, last_refresh_time,
|
|
726
|
-
next_refresh_time, additional_info
|
|
727
|
-
FROM ki_catalog.ki_periodic_objects
|
|
728
|
-
ORDER BY next_refresh_time;`;
|
|
729
|
-
var FALLBACK_USERS_AND_ROLES_SQL = `-- Users and roles
|
|
730
|
-
SELECT oid, name, can_login, is_superuser, resource_group
|
|
731
|
-
FROM ki_catalog.ki_users_and_roles
|
|
732
|
-
ORDER BY name;`;
|
|
733
|
-
var FALLBACK_OBJECT_PERMISSIONS_SQL = `-- Object permissions
|
|
734
|
-
SELECT role_name, permission_type, object_type, object_name, with_grant_option
|
|
735
|
-
FROM ki_catalog.ki_object_permissions
|
|
736
|
-
ORDER BY object_name, role_name;`;
|
|
737
|
-
var FALLBACK_DEPEND_SQL = `-- Object dependency graph
|
|
738
|
-
SELECT src_obj_oid, src_obj_kind, dep_obj_oid, dep_obj_kind, mv_oid, dep_kind
|
|
739
|
-
FROM ki_catalog.ki_depend;`;
|
|
740
|
-
var FALLBACK_LOAD_HISTORY_SQL = `-- Recent data load history
|
|
741
|
-
SELECT table_oid, datasource_oid, user_name, load_kind,
|
|
742
|
-
start_time, end_time, rows_inserted, event_message
|
|
743
|
-
FROM ki_catalog.ki_load_history
|
|
744
|
-
WHERE start_time > NOW() - INTERVAL '1' HOUR
|
|
745
|
-
ORDER BY start_time DESC
|
|
746
|
-
LIMIT 20;`;
|
|
747
|
-
var FALLBACK_BACKUP_HISTORY_SQL = `-- Backup history
|
|
748
|
-
SELECT backup_name, operation, status, start_time, end_time,
|
|
749
|
-
num_files, num_bytes, num_records
|
|
750
|
-
FROM ki_catalog.ki_backup_history
|
|
751
|
-
ORDER BY start_time DESC
|
|
752
|
-
LIMIT 20;`;
|
|
753
|
-
var FALLBACK_KAFKA_LAG_INFO_SQL = `-- Kafka consumer lag
|
|
754
|
-
SELECT datasource_oid, table_oid, schema_name, table_name,
|
|
755
|
-
partition_id, highest_offset, last_committed_offset
|
|
756
|
-
FROM ki_catalog.ki_kafka_lag_info
|
|
757
|
-
ORDER BY datasource_oid, partition_id;`;
|
|
758
|
-
function buildQueryHistorySql(columns) {
|
|
759
|
-
const select = columns.join(", ");
|
|
760
|
-
const elapsed = has(columns, "start_time") && has(columns, "stop_time") ? ",\n TIMESTAMPDIFF(SECOND, start_time, stop_time) AS elapsed_sec" : "";
|
|
761
|
-
const where = has(columns, "start_time") ? "\nWHERE start_time > NOW() - INTERVAL '1' HOUR" : "";
|
|
762
|
-
const orderBy = has(columns, "start_time") && has(columns, "stop_time") ? (
|
|
763
|
-
// Kinetica does not support timestamp arithmetic in ORDER BY; use the elapsed_sec alias instead
|
|
764
|
-
"\nORDER BY elapsed_sec DESC"
|
|
765
|
-
) : "";
|
|
766
|
-
return `-- Slow queries in the last hour
|
|
767
|
-
SELECT ${select}${elapsed}
|
|
768
|
-
FROM ki_catalog.ki_query_history${where}${orderBy}
|
|
769
|
-
LIMIT 20;`;
|
|
770
|
-
}
|
|
771
|
-
function buildActiveQueriesSql(columns) {
|
|
772
|
-
const select = columns.join(", ");
|
|
773
|
-
const orderBy = has(columns, "start_time") ? "\nORDER BY start_time ASC" : "";
|
|
774
|
-
return `-- Currently active queries
|
|
775
|
-
SELECT ${select}
|
|
776
|
-
FROM ki_catalog.ki_query_active_all${orderBy};`;
|
|
777
|
-
}
|
|
778
|
-
function buildTieredObjectsSql(columns) {
|
|
779
|
-
const select = columns.join(", ");
|
|
780
|
-
const where = has(columns, "tier") ? "\nWHERE tier != 'VRAM'" : "";
|
|
781
|
-
const orderBy = has(columns, "size") ? "\nORDER BY size DESC" : "";
|
|
782
|
-
const idNote = has(columns, "id") ? "\n-- NOTE: id is a string (e.g. @schema@oid[col][0]), NOT a numeric OID. Filter by table: WHERE id LIKE '%table_name%'" : "";
|
|
783
|
-
return `-- Objects in disk tier (potential memory pressure)${idNote}
|
|
784
|
-
SELECT ${select}
|
|
785
|
-
FROM ki_catalog.ki_tiered_objects${where}${orderBy}
|
|
786
|
-
LIMIT 20;`;
|
|
787
|
-
}
|
|
788
|
-
function buildObjStatSql(columns) {
|
|
789
|
-
const select = columns.join(", ");
|
|
790
|
-
const orderBy = has(columns, "total_bytes") ? "\nORDER BY total_bytes DESC" : "";
|
|
791
|
-
return `-- Table sizes and row counts
|
|
792
|
-
SELECT ${select}
|
|
793
|
-
FROM ki_catalog.ki_obj_stat${orderBy}
|
|
794
|
-
LIMIT 30;`;
|
|
795
|
-
}
|
|
796
|
-
var STRUCTURAL_COLUMNS = /* @__PURE__ */ new Set([
|
|
797
|
-
"table_name",
|
|
798
|
-
"column_name",
|
|
799
|
-
"column_position",
|
|
800
|
-
"is_nullable",
|
|
801
|
-
"is_shard_key",
|
|
802
|
-
"is_primary_key",
|
|
803
|
-
"is_dict_encoded",
|
|
804
|
-
"default_value",
|
|
805
|
-
"bytes_on_disk_uncompressed",
|
|
806
|
-
"bytes_on_disk_compressed"
|
|
807
|
-
]);
|
|
808
|
-
function buildColumnsSql(columns) {
|
|
809
|
-
const filtered = columns.filter((c) => STRUCTURAL_COLUMNS.has(c));
|
|
810
|
-
const selectCols = filtered.length > 0 ? filtered : columns;
|
|
811
|
-
const select = selectCols.map((c) => `c.${c}`).join(", ");
|
|
812
|
-
const where = has(selectCols, "table_name") ? "\nWHERE c.table_name = '<TABLE_NAME>'" : "";
|
|
813
|
-
const orderBy = has(selectCols, "column_position") ? "\nORDER BY c.column_position" : has(selectCols, "column_name") ? "\nORDER BY c.column_name" : "";
|
|
814
|
-
return `-- Structural column metadata (use kinetica_show_table for Kinetica-native types)
|
|
815
|
-
SELECT ${select}
|
|
816
|
-
FROM ki_catalog.ki_columns c${where}${orderBy};`;
|
|
817
|
-
}
|
|
818
|
-
function buildDatatypesSql(columns) {
|
|
819
|
-
const select = columns.join(", ");
|
|
820
|
-
const orderBy = has(columns, "oid") ? "\nORDER BY oid" : "";
|
|
821
|
-
return `-- Resolve column_type_oid to human-readable type name
|
|
822
|
-
SELECT ${select}
|
|
823
|
-
FROM ki_catalog.ki_datatypes${orderBy};`;
|
|
710
|
+
function computePercent(used, limit) {
|
|
711
|
+
if (!used || !limit) return "";
|
|
712
|
+
const usedN = Number(used);
|
|
713
|
+
const limitN = Number(limit);
|
|
714
|
+
if (!Number.isFinite(usedN) || !Number.isFinite(limitN) || limitN === 0) return "";
|
|
715
|
+
return `${(usedN / limitN * 100).toFixed(1)}%`;
|
|
824
716
|
}
|
|
825
|
-
function
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
717
|
+
function flattenRanksSummary(decoded) {
|
|
718
|
+
const ranksRaw = typeof decoded.ranks === "object" && decoded.ranks !== null ? decoded.ranks : decoded;
|
|
719
|
+
return Object.entries(ranksRaw).filter(([, v]) => typeof v === "object" && v !== null && !Array.isArray(v)).map(([rankId, rankData]) => {
|
|
720
|
+
const rank = rankData;
|
|
721
|
+
const tiers = typeof rank.tiers === "object" && rank.tiers !== null ? rank.tiers : {};
|
|
722
|
+
const ramUsed = overallField(findTier(tiers, "RAM"), "used");
|
|
723
|
+
const ramLimit = overallField(findTier(tiers, "RAM"), "limit");
|
|
724
|
+
return {
|
|
725
|
+
rank: rankId,
|
|
726
|
+
ram_used: ramUsed,
|
|
727
|
+
ram_limit: ramLimit,
|
|
728
|
+
ram_percent: computePercent(ramUsed, ramLimit),
|
|
729
|
+
persist_used: overallField(findTier(tiers, "PERSIST"), "used"),
|
|
730
|
+
disk_used: overallField(findTier(tiers, "DISK"), "used"),
|
|
731
|
+
vram_used: overallField(findTier(tiers, "VRAM"), "used")
|
|
732
|
+
};
|
|
733
|
+
});
|
|
832
734
|
}
|
|
833
|
-
function
|
|
834
|
-
const
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
735
|
+
function flattenRankDetail(rankData) {
|
|
736
|
+
const tiersRaw = typeof rankData.tiers === "object" && rankData.tiers !== null ? rankData.tiers : {};
|
|
737
|
+
const tiers = Object.entries(tiersRaw).filter(([, v]) => typeof v === "object" && v !== null && !Array.isArray(v)).map(([tierName, tierData]) => {
|
|
738
|
+
const tier = tierData;
|
|
739
|
+
const overall = typeof tier.overall === "object" && tier.overall !== null ? tier.overall : {};
|
|
740
|
+
const stats = typeof tier.stats === "object" && tier.stats !== null ? tier.stats : {};
|
|
741
|
+
const row = { tier: tierName };
|
|
742
|
+
for (const [k, v] of Object.entries(overall)) {
|
|
743
|
+
row[k] = stringifyValue(v);
|
|
744
|
+
}
|
|
745
|
+
for (const [k, v] of Object.entries(stats)) {
|
|
746
|
+
row[k] = stringifyValue(v);
|
|
747
|
+
}
|
|
748
|
+
return row;
|
|
749
|
+
});
|
|
750
|
+
const rgRaw = typeof rankData.resource_groups === "object" && rankData.resource_groups !== null ? rankData.resource_groups : {};
|
|
751
|
+
const resource_groups = Object.entries(rgRaw).filter(([, v]) => typeof v === "object" && v !== null && !Array.isArray(v)).map(([rgName, rgData]) => {
|
|
752
|
+
const rg = rgData;
|
|
753
|
+
const row = { name: rgName };
|
|
754
|
+
for (const [k, v] of Object.entries(rg)) {
|
|
755
|
+
row[k] = stringifyValue(v);
|
|
756
|
+
}
|
|
757
|
+
return row;
|
|
758
|
+
});
|
|
759
|
+
return { tiers, resource_groups };
|
|
840
760
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
761
|
+
|
|
762
|
+
// src/tools/rest/metrics.ts
|
|
763
|
+
async function getMetrics(session2, nodeId) {
|
|
764
|
+
let response;
|
|
765
|
+
let rawText;
|
|
766
|
+
try {
|
|
767
|
+
response = await session2.makeRequest("/show/resource/statistics", {
|
|
768
|
+
options: {}
|
|
769
|
+
});
|
|
770
|
+
rawText = await response.text();
|
|
771
|
+
} catch (err) {
|
|
772
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
773
|
+
return { ok: false, status: 0, error: message, raw: "" };
|
|
774
|
+
}
|
|
775
|
+
if (!response.ok) {
|
|
776
|
+
return {
|
|
777
|
+
ok: false,
|
|
778
|
+
status: response.status,
|
|
779
|
+
error: `HTTP ${response.status}`,
|
|
780
|
+
raw: rawText
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
let parsed;
|
|
784
|
+
try {
|
|
785
|
+
parsed = JSON.parse(rawText);
|
|
786
|
+
} catch (err) {
|
|
787
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
788
|
+
return {
|
|
789
|
+
ok: false,
|
|
790
|
+
status: 200,
|
|
791
|
+
error: `JSON parse error: ${message}`,
|
|
792
|
+
raw: rawText
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
const inner = parseDataStr(parsed.data_str, rawText);
|
|
796
|
+
if (!inner.ok) return inner;
|
|
797
|
+
const statisticsMap = inner.data?.statistics_map ?? {};
|
|
798
|
+
const decoded = decodeNestedJsonStrings(statisticsMap);
|
|
799
|
+
const rows = flattenRanksSummary(decoded);
|
|
800
|
+
if (nodeId !== void 0) {
|
|
801
|
+
return { ok: true, data: rows, note: `Filtered for node: ${nodeId}` };
|
|
802
|
+
}
|
|
803
|
+
return { ok: true, data: rows };
|
|
848
804
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
805
|
+
|
|
806
|
+
// src/tools/rest/summarize-shards.ts
|
|
807
|
+
var EMPTY_SUMMARY = {
|
|
808
|
+
shard_array_version: void 0,
|
|
809
|
+
total_shards: 0,
|
|
810
|
+
rank_count: 0,
|
|
811
|
+
distribution: [],
|
|
812
|
+
balanced: true
|
|
813
|
+
};
|
|
814
|
+
function summarizeShards(raw) {
|
|
815
|
+
if (raw === null || raw === void 0 || typeof raw !== "object") {
|
|
816
|
+
return EMPTY_SUMMARY;
|
|
817
|
+
}
|
|
818
|
+
const obj = raw;
|
|
819
|
+
const version = typeof obj.shard_array_version === "number" ? obj.shard_array_version : void 0;
|
|
820
|
+
const shardMap = obj.shard_map;
|
|
821
|
+
if (shardMap === null || shardMap === void 0 || typeof shardMap !== "object" || Array.isArray(shardMap)) {
|
|
822
|
+
return { ...EMPTY_SUMMARY, shard_array_version: version };
|
|
823
|
+
}
|
|
824
|
+
const entries = Object.values(shardMap);
|
|
825
|
+
if (entries.length === 0) {
|
|
826
|
+
return { ...EMPTY_SUMMARY, shard_array_version: version };
|
|
827
|
+
}
|
|
828
|
+
const counts = entries.reduce((acc, rank) => {
|
|
829
|
+
const map = new Map(acc);
|
|
830
|
+
map.set(rank, (acc.get(rank) ?? 0) + 1);
|
|
831
|
+
return map;
|
|
832
|
+
}, /* @__PURE__ */ new Map());
|
|
833
|
+
const totalShards = entries.length;
|
|
834
|
+
const distribution = [...counts.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([rank, count]) => ({
|
|
835
|
+
rank,
|
|
836
|
+
shard_count: count,
|
|
837
|
+
percent: `${(count / totalShards * 100).toFixed(1)}%`
|
|
838
|
+
}));
|
|
839
|
+
const shardCounts = distribution.map((d) => d.shard_count);
|
|
840
|
+
const balanced = Math.max(...shardCounts) - Math.min(...shardCounts) <= 1;
|
|
841
|
+
return {
|
|
842
|
+
shard_array_version: version,
|
|
843
|
+
total_shards: totalShards,
|
|
844
|
+
rank_count: counts.size,
|
|
845
|
+
distribution,
|
|
846
|
+
balanced
|
|
847
|
+
};
|
|
856
848
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
}
|
|
864
|
-
function
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
}
|
|
908
|
-
|
|
909
|
-
const select = columns.join(", ");
|
|
910
|
-
const orderBy = has(columns, "datasource_oid") && has(columns, "partition_id") ? "\nORDER BY datasource_oid, partition_id" : has(columns, "datasource_oid") ? "\nORDER BY datasource_oid" : "";
|
|
911
|
-
return `-- Kafka consumer lag
|
|
912
|
-
SELECT ${select}
|
|
913
|
-
FROM ki_catalog.ki_kafka_lag_info${orderBy};`;
|
|
849
|
+
|
|
850
|
+
// src/tools/rest/system-properties.ts
|
|
851
|
+
var import_zod = require("zod");
|
|
852
|
+
var GetSystemPropertiesSchema = import_zod.z.object({
|
|
853
|
+
category: import_zod.z.string().optional(),
|
|
854
|
+
key_pattern: import_zod.z.string().optional()
|
|
855
|
+
});
|
|
856
|
+
async function getSystemProperties(session2, input5) {
|
|
857
|
+
try {
|
|
858
|
+
const response = await session2.makeRequest("/show/system/properties", {
|
|
859
|
+
options: {}
|
|
860
|
+
});
|
|
861
|
+
if (!response.ok) {
|
|
862
|
+
const raw2 = await response.text();
|
|
863
|
+
return {
|
|
864
|
+
ok: false,
|
|
865
|
+
status: response.status,
|
|
866
|
+
error: `HTTP ${response.status}`,
|
|
867
|
+
raw: raw2
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
const raw = await response.text();
|
|
871
|
+
let parsed;
|
|
872
|
+
try {
|
|
873
|
+
parsed = JSON.parse(raw);
|
|
874
|
+
} catch (parseError) {
|
|
875
|
+
const message = parseError instanceof Error ? parseError.message : String(parseError);
|
|
876
|
+
return {
|
|
877
|
+
ok: false,
|
|
878
|
+
status: 200,
|
|
879
|
+
error: `JSON parse error: ${message}`,
|
|
880
|
+
raw
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
const inner = parseDataStr(parsed.data_str, raw);
|
|
884
|
+
if (!inner.ok) return inner;
|
|
885
|
+
const propertyMap = inner.data?.property_map ?? {};
|
|
886
|
+
const filteredMap = applyFilters(propertyMap, input5);
|
|
887
|
+
return {
|
|
888
|
+
ok: true,
|
|
889
|
+
data: flatObjectToRows(filteredMap, "property", "value"),
|
|
890
|
+
rowCount: Object.keys(filteredMap).length
|
|
891
|
+
};
|
|
892
|
+
} catch (error) {
|
|
893
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
894
|
+
return {
|
|
895
|
+
ok: false,
|
|
896
|
+
status: 0,
|
|
897
|
+
error: message,
|
|
898
|
+
raw: ""
|
|
899
|
+
};
|
|
900
|
+
}
|
|
914
901
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
{
|
|
918
|
-
|
|
919
|
-
section: "Query History and Performance",
|
|
920
|
-
build: buildQueryHistorySql,
|
|
921
|
-
fallback: FALLBACK_QUERY_HISTORY_SQL
|
|
922
|
-
},
|
|
923
|
-
{
|
|
924
|
-
table: "ki_query_active_all",
|
|
925
|
-
section: "Query History and Performance",
|
|
926
|
-
build: buildActiveQueriesSql,
|
|
927
|
-
fallback: FALLBACK_ACTIVE_QUERIES_SQL
|
|
928
|
-
},
|
|
929
|
-
{
|
|
930
|
-
table: "ki_query_span_metrics_all",
|
|
931
|
-
section: "Query History and Performance",
|
|
932
|
-
build: buildQuerySpanMetricsSql,
|
|
933
|
-
fallback: FALLBACK_QUERY_SPAN_METRICS_SQL
|
|
934
|
-
},
|
|
935
|
-
{
|
|
936
|
-
table: "ki_query_workers",
|
|
937
|
-
section: "Query History and Performance",
|
|
938
|
-
build: buildQueryWorkersSql,
|
|
939
|
-
fallback: FALLBACK_QUERY_WORKERS_SQL
|
|
940
|
-
},
|
|
941
|
-
// Memory and Storage Tiers
|
|
942
|
-
{
|
|
943
|
-
table: "ki_tiered_objects",
|
|
944
|
-
section: "Memory and Storage Tiers",
|
|
945
|
-
build: buildTieredObjectsSql,
|
|
946
|
-
fallback: FALLBACK_TIERED_OBJECTS_SQL
|
|
947
|
-
},
|
|
948
|
-
{
|
|
949
|
-
table: "ki_obj_stat",
|
|
950
|
-
section: "Memory and Storage Tiers",
|
|
951
|
-
build: buildObjStatSql,
|
|
952
|
-
fallback: FALLBACK_OBJ_STAT_SQL
|
|
953
|
-
},
|
|
954
|
-
{
|
|
955
|
-
table: "ki_partitions",
|
|
956
|
-
section: "Memory and Storage Tiers",
|
|
957
|
-
build: buildPartitionsSql,
|
|
958
|
-
fallback: FALLBACK_PARTITIONS_SQL
|
|
959
|
-
},
|
|
960
|
-
// Object Registry and Metadata
|
|
961
|
-
{
|
|
962
|
-
table: "ki_objects",
|
|
963
|
-
section: "Object Registry and Metadata",
|
|
964
|
-
build: buildObjectsSql,
|
|
965
|
-
fallback: FALLBACK_OBJECTS_SQL
|
|
966
|
-
},
|
|
967
|
-
{
|
|
968
|
-
table: "ki_indexes",
|
|
969
|
-
section: "Object Registry and Metadata",
|
|
970
|
-
build: buildIndexesSql,
|
|
971
|
-
fallback: FALLBACK_INDEXES_SQL
|
|
972
|
-
},
|
|
973
|
-
{
|
|
974
|
-
table: "ki_periodic_objects",
|
|
975
|
-
section: "Object Registry and Metadata",
|
|
976
|
-
build: buildPeriodicObjectsSql,
|
|
977
|
-
fallback: FALLBACK_PERIODIC_OBJECTS_SQL
|
|
978
|
-
},
|
|
979
|
-
{
|
|
980
|
-
table: "ki_depend",
|
|
981
|
-
section: "Object Registry and Metadata",
|
|
982
|
-
build: buildDependSql,
|
|
983
|
-
fallback: FALLBACK_DEPEND_SQL
|
|
984
|
-
},
|
|
985
|
-
// Security and Access Control
|
|
986
|
-
{
|
|
987
|
-
table: "ki_users_and_roles",
|
|
988
|
-
section: "Security and Access Control",
|
|
989
|
-
build: buildUsersAndRolesSql,
|
|
990
|
-
fallback: FALLBACK_USERS_AND_ROLES_SQL
|
|
991
|
-
},
|
|
992
|
-
{
|
|
993
|
-
table: "ki_object_permissions",
|
|
994
|
-
section: "Security and Access Control",
|
|
995
|
-
build: buildObjectPermissionsSql,
|
|
996
|
-
fallback: FALLBACK_OBJECT_PERMISSIONS_SQL
|
|
997
|
-
},
|
|
998
|
-
// Data Ingestion and Operations
|
|
999
|
-
{
|
|
1000
|
-
table: "ki_load_history",
|
|
1001
|
-
section: "Data Ingestion and Operations",
|
|
1002
|
-
build: buildLoadHistorySql,
|
|
1003
|
-
fallback: FALLBACK_LOAD_HISTORY_SQL
|
|
1004
|
-
},
|
|
1005
|
-
{
|
|
1006
|
-
table: "ki_backup_history",
|
|
1007
|
-
section: "Data Ingestion and Operations",
|
|
1008
|
-
build: buildBackupHistorySql,
|
|
1009
|
-
fallback: FALLBACK_BACKUP_HISTORY_SQL
|
|
1010
|
-
},
|
|
1011
|
-
{
|
|
1012
|
-
table: "ki_kafka_lag_info",
|
|
1013
|
-
section: "Data Ingestion and Operations",
|
|
1014
|
-
build: buildKafkaLagInfoSql,
|
|
1015
|
-
fallback: FALLBACK_KAFKA_LAG_INFO_SQL
|
|
1016
|
-
},
|
|
1017
|
-
// Schema Inspection
|
|
1018
|
-
{
|
|
1019
|
-
table: "ki_columns",
|
|
1020
|
-
section: "Schema Inspection",
|
|
1021
|
-
build: buildColumnsSql,
|
|
1022
|
-
fallback: FALLBACK_COLUMNS_SQL
|
|
1023
|
-
},
|
|
1024
|
-
{
|
|
1025
|
-
table: "ki_datatypes",
|
|
1026
|
-
section: "Schema Inspection",
|
|
1027
|
-
build: buildDatatypesSql,
|
|
1028
|
-
fallback: FALLBACK_DATATYPES_SQL
|
|
902
|
+
function applyFilters(propertyMap, input5) {
|
|
903
|
+
const { category, key_pattern } = input5;
|
|
904
|
+
if (category === void 0 && key_pattern === void 0) {
|
|
905
|
+
return propertyMap;
|
|
1029
906
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
var DEFAULT_READ_ONLY_TOOLS = /* @__PURE__ */ new Set();
|
|
1039
|
-
function createRegistry(tools = DEFAULT_READ_ONLY_TOOLS) {
|
|
1040
|
-
return {
|
|
1041
|
-
isReadOnlyTool: (toolName) => tools.has(toolName),
|
|
1042
|
-
// Returns a NEW registry — never mutates the current one
|
|
1043
|
-
registerReadOnlyTool: (toolName) => createRegistry(/* @__PURE__ */ new Set([...tools, toolName])),
|
|
1044
|
-
tools
|
|
1045
|
-
};
|
|
907
|
+
const patternLower = key_pattern?.toLowerCase();
|
|
908
|
+
return Object.fromEntries(
|
|
909
|
+
Object.entries(propertyMap).filter(([key]) => {
|
|
910
|
+
const matchesCategory = category === void 0 || key.startsWith(category);
|
|
911
|
+
const matchesPattern = patternLower === void 0 || key.toLowerCase().includes(patternLower);
|
|
912
|
+
return matchesCategory && matchesPattern;
|
|
913
|
+
})
|
|
914
|
+
);
|
|
1046
915
|
}
|
|
1047
|
-
var defaultRegistry = createRegistry();
|
|
1048
|
-
var isReadOnlyTool = defaultRegistry.isReadOnlyTool;
|
|
1049
|
-
var READ_ONLY_TOOLS = defaultRegistry.tools;
|
|
1050
916
|
|
|
1051
|
-
// src/
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
if (
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
if (
|
|
1059
|
-
|
|
1060
|
-
return
|
|
917
|
+
// src/tools/rest/discover-hm-port.ts
|
|
918
|
+
var DEFAULT_HM_PORT = 9300;
|
|
919
|
+
async function discoverHmPort(session2) {
|
|
920
|
+
const result = await getSystemProperties(session2, { key_pattern: "hm_http_port" });
|
|
921
|
+
if (!result.ok) return DEFAULT_HM_PORT;
|
|
922
|
+
const rows = result.data;
|
|
923
|
+
const entry = rows.find((r) => r.property?.includes("hm_http_port"));
|
|
924
|
+
if (!entry?.value) return DEFAULT_HM_PORT;
|
|
925
|
+
const port = parseInt(entry.value, 10);
|
|
926
|
+
return Number.isFinite(port) ? port : DEFAULT_HM_PORT;
|
|
1061
927
|
}
|
|
1062
928
|
|
|
1063
|
-
// src/
|
|
1064
|
-
function
|
|
1065
|
-
if (json === null || json === void 0) {
|
|
1066
|
-
return "(empty)";
|
|
1067
|
-
}
|
|
1068
|
-
if (Array.isArray(json)) {
|
|
1069
|
-
return formatArray(json);
|
|
1070
|
-
}
|
|
1071
|
-
if (typeof json === "object") {
|
|
1072
|
-
return formatObject(json);
|
|
1073
|
-
}
|
|
1074
|
-
return stringifyValue(json);
|
|
1075
|
-
}
|
|
1076
|
-
function formatArray(arr) {
|
|
1077
|
-
if (arr.length === 0) {
|
|
1078
|
-
return "(no results)";
|
|
1079
|
-
}
|
|
1080
|
-
const first = arr[0];
|
|
1081
|
-
if (typeof first === "object" && first !== null && !Array.isArray(first)) {
|
|
1082
|
-
return formatTableArray(arr);
|
|
1083
|
-
}
|
|
1084
|
-
return arr.map(stringifyValue).join("\n");
|
|
1085
|
-
}
|
|
1086
|
-
function formatTableArray(rows) {
|
|
1087
|
-
const headers = Object.keys(rows[0]);
|
|
1088
|
-
const cells = rows.map((row) => headers.map((h) => stringifyValue(row[h])));
|
|
1089
|
-
const colWidths = headers.map(
|
|
1090
|
-
(h, i) => Math.max(h.length, ...cells.map((row) => row[i].length), 3)
|
|
1091
|
-
);
|
|
1092
|
-
const pad = (text, col) => text.padEnd(colWidths[col]);
|
|
1093
|
-
const headerRow = `| ${headers.map((h, i) => pad(h, i)).join(" | ")} |`;
|
|
1094
|
-
const separatorRow = `| ${colWidths.map((w) => "-".repeat(w)).join(" | ")} |`;
|
|
1095
|
-
const dataRows = cells.map((row) => `| ${row.map((cell, i) => pad(cell, i)).join(" | ")} |`);
|
|
1096
|
-
return [headerRow, separatorRow, ...dataRows].join("\n");
|
|
1097
|
-
}
|
|
1098
|
-
function formatObject(obj) {
|
|
1099
|
-
return Object.entries(obj).map(([key, value]) => {
|
|
1100
|
-
if (typeof value === "object" && value !== null) {
|
|
1101
|
-
return `**${key}:**
|
|
1102
|
-
${formatOutput(value)}`;
|
|
1103
|
-
}
|
|
1104
|
-
return `**${key}:** ${stringifyValue(value)}`;
|
|
1105
|
-
}).join("\n");
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
// src/output/format-tool-name.ts
|
|
1109
|
-
function formatToolName(toolName) {
|
|
1110
|
-
const stripped = toolName.replace(/^mcp__[^_]+__/, "").replace(/^kinetica_/, "");
|
|
1111
|
-
return stripped.replace(/_/g, " ");
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
// src/types/index.ts
|
|
1115
|
-
var DEFAULT_TRUNCATION = {
|
|
1116
|
-
headLines: 150,
|
|
1117
|
-
tailLines: 50
|
|
1118
|
-
};
|
|
1119
|
-
|
|
1120
|
-
// src/output/truncate.ts
|
|
1121
|
-
function truncateOutput(text, options = DEFAULT_TRUNCATION) {
|
|
1122
|
-
if (text === "") return "";
|
|
1123
|
-
const lines = text.split("\n");
|
|
1124
|
-
const { headLines, tailLines } = options;
|
|
1125
|
-
const threshold = headLines + tailLines;
|
|
1126
|
-
if (lines.length <= threshold) {
|
|
1127
|
-
return text;
|
|
1128
|
-
}
|
|
1129
|
-
const truncatedCount = lines.length - headLines - tailLines;
|
|
1130
|
-
const head = lines.slice(0, headLines);
|
|
1131
|
-
const tail = lines.slice(lines.length - tailLines);
|
|
1132
|
-
return [...head, "", `[... ${truncatedCount} lines truncated ...]`, "", ...tail].join("\n");
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
// src/output/reshape.ts
|
|
1136
|
-
function safeString(v) {
|
|
1137
|
-
if (typeof v === "object" && v !== null) {
|
|
1138
|
-
return JSON.stringify(v);
|
|
1139
|
-
}
|
|
1140
|
-
return String(v);
|
|
1141
|
-
}
|
|
1142
|
-
function flatObjectToRows(obj, keyLabel = "key", valueLabel = "value") {
|
|
1143
|
-
return Object.entries(obj).map(([k, v]) => ({
|
|
1144
|
-
[keyLabel]: k,
|
|
1145
|
-
[valueLabel]: safeString(v)
|
|
1146
|
-
}));
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
// src/tools/rest/parse-data-str.ts
|
|
1150
|
-
function parseDataStr(outerDataStr, raw) {
|
|
1151
|
-
if (typeof outerDataStr !== "string") {
|
|
1152
|
-
return { ok: true, data: void 0 };
|
|
1153
|
-
}
|
|
1154
|
-
try {
|
|
1155
|
-
return { ok: true, data: JSON.parse(outerDataStr) };
|
|
1156
|
-
} catch (err) {
|
|
1157
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1158
|
-
return { ok: false, status: 200, error: `data_str parse error: ${message}`, raw };
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
// src/tools/rest/health.ts
|
|
1163
|
-
async function healthCheck(session2) {
|
|
929
|
+
// src/tools/rest/cluster.ts
|
|
930
|
+
async function fetchJson(session2, endpoint, body) {
|
|
1164
931
|
let response;
|
|
1165
932
|
let rawText;
|
|
1166
933
|
try {
|
|
1167
|
-
response = await session2.makeRequest(
|
|
934
|
+
response = await session2.makeRequest(endpoint, body);
|
|
1168
935
|
rawText = await response.text();
|
|
1169
936
|
} catch (err) {
|
|
1170
937
|
const message = err instanceof Error ? err.message : String(err);
|
|
1171
|
-
return {
|
|
1172
|
-
ok: false,
|
|
1173
|
-
status: 0,
|
|
1174
|
-
error: message,
|
|
1175
|
-
raw: ""
|
|
1176
|
-
};
|
|
938
|
+
return { ok: false, status: 0, error: message, raw: "" };
|
|
1177
939
|
}
|
|
1178
940
|
if (!response.ok) {
|
|
1179
941
|
return {
|
|
@@ -1183,9 +945,9 @@ async function healthCheck(session2) {
|
|
|
1183
945
|
raw: rawText
|
|
1184
946
|
};
|
|
1185
947
|
}
|
|
1186
|
-
let parsed;
|
|
1187
948
|
try {
|
|
1188
|
-
parsed = JSON.parse(rawText);
|
|
949
|
+
const parsed = JSON.parse(rawText);
|
|
950
|
+
return { ok: true, data: parsed };
|
|
1189
951
|
} catch (err) {
|
|
1190
952
|
const message = err instanceof Error ? err.message : String(err);
|
|
1191
953
|
return {
|
|
@@ -1195,111 +957,20 @@ async function healthCheck(session2) {
|
|
|
1195
957
|
raw: rawText
|
|
1196
958
|
};
|
|
1197
959
|
}
|
|
1198
|
-
const inner = parseDataStr(parsed.data_str, rawText);
|
|
1199
|
-
if (!inner.ok) return inner;
|
|
1200
|
-
const statusMap = inner.data?.status_map ?? {};
|
|
1201
|
-
return {
|
|
1202
|
-
ok: true,
|
|
1203
|
-
data: flatObjectToRows(statusMap, "component", "status")
|
|
1204
|
-
};
|
|
1205
|
-
}
|
|
1206
|
-
|
|
1207
|
-
// src/tools/rest/decode-nested-json.ts
|
|
1208
|
-
function decodeNestedJsonStrings(obj) {
|
|
1209
|
-
return Object.fromEntries(
|
|
1210
|
-
Object.entries(obj).map(([k, v]) => {
|
|
1211
|
-
if (typeof v !== "string") return [k, v];
|
|
1212
|
-
try {
|
|
1213
|
-
const parsed = JSON.parse(v);
|
|
1214
|
-
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
1215
|
-
return [k, decodeNestedJsonStrings(parsed)];
|
|
1216
|
-
}
|
|
1217
|
-
return [k, parsed];
|
|
1218
|
-
} catch {
|
|
1219
|
-
return [k, v];
|
|
1220
|
-
}
|
|
1221
|
-
})
|
|
1222
|
-
);
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
// src/tools/rest/flatten-rank-stats.ts
|
|
1226
|
-
function findTier(tiers, prefix) {
|
|
1227
|
-
for (const [name, data] of Object.entries(tiers)) {
|
|
1228
|
-
if (name === prefix || name.startsWith(`${prefix}.`) || name.startsWith(`${prefix}_`) || name.startsWith(prefix) && /^\d/.test(name.slice(prefix.length))) {
|
|
1229
|
-
if (typeof data === "object" && data !== null && !Array.isArray(data)) {
|
|
1230
|
-
return data;
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
}
|
|
1234
|
-
return void 0;
|
|
1235
|
-
}
|
|
1236
|
-
function overallField(tier, field) {
|
|
1237
|
-
if (!tier) return "";
|
|
1238
|
-
const overall = tier.overall;
|
|
1239
|
-
if (typeof overall !== "object" || overall === null) return "";
|
|
1240
|
-
const value = overall[field];
|
|
1241
|
-
return stringifyValue(value);
|
|
1242
|
-
}
|
|
1243
|
-
function computePercent(used, limit) {
|
|
1244
|
-
if (!used || !limit) return "";
|
|
1245
|
-
const usedN = Number(used);
|
|
1246
|
-
const limitN = Number(limit);
|
|
1247
|
-
if (!Number.isFinite(usedN) || !Number.isFinite(limitN) || limitN === 0) return "";
|
|
1248
|
-
return `${(usedN / limitN * 100).toFixed(1)}%`;
|
|
1249
960
|
}
|
|
1250
|
-
function
|
|
1251
|
-
|
|
1252
|
-
return Object.entries(ranksRaw).filter(([, v]) => typeof v === "object" && v !== null && !Array.isArray(v)).map(([rankId, rankData]) => {
|
|
1253
|
-
const rank = rankData;
|
|
1254
|
-
const tiers = typeof rank.tiers === "object" && rank.tiers !== null ? rank.tiers : {};
|
|
1255
|
-
const ramUsed = overallField(findTier(tiers, "RAM"), "used");
|
|
1256
|
-
const ramLimit = overallField(findTier(tiers, "RAM"), "limit");
|
|
961
|
+
async function fetchJsonOnPort(session2, port, endpoint, body) {
|
|
962
|
+
if (!session2.makeRequestToPort) {
|
|
1257
963
|
return {
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
persist_used: overallField(findTier(tiers, "PERSIST"), "used"),
|
|
1263
|
-
disk_used: overallField(findTier(tiers, "DISK"), "used"),
|
|
1264
|
-
vram_used: overallField(findTier(tiers, "VRAM"), "used")
|
|
964
|
+
ok: false,
|
|
965
|
+
status: 0,
|
|
966
|
+
error: "makeRequestToPort not available on this session",
|
|
967
|
+
raw: ""
|
|
1265
968
|
};
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
function flattenRankDetail(rankData) {
|
|
1269
|
-
const tiersRaw = typeof rankData.tiers === "object" && rankData.tiers !== null ? rankData.tiers : {};
|
|
1270
|
-
const tiers = Object.entries(tiersRaw).filter(([, v]) => typeof v === "object" && v !== null && !Array.isArray(v)).map(([tierName, tierData]) => {
|
|
1271
|
-
const tier = tierData;
|
|
1272
|
-
const overall = typeof tier.overall === "object" && tier.overall !== null ? tier.overall : {};
|
|
1273
|
-
const stats = typeof tier.stats === "object" && tier.stats !== null ? tier.stats : {};
|
|
1274
|
-
const row = { tier: tierName };
|
|
1275
|
-
for (const [k, v] of Object.entries(overall)) {
|
|
1276
|
-
row[k] = stringifyValue(v);
|
|
1277
|
-
}
|
|
1278
|
-
for (const [k, v] of Object.entries(stats)) {
|
|
1279
|
-
row[k] = stringifyValue(v);
|
|
1280
|
-
}
|
|
1281
|
-
return row;
|
|
1282
|
-
});
|
|
1283
|
-
const rgRaw = typeof rankData.resource_groups === "object" && rankData.resource_groups !== null ? rankData.resource_groups : {};
|
|
1284
|
-
const resource_groups = Object.entries(rgRaw).filter(([, v]) => typeof v === "object" && v !== null && !Array.isArray(v)).map(([rgName, rgData]) => {
|
|
1285
|
-
const rg = rgData;
|
|
1286
|
-
const row = { name: rgName };
|
|
1287
|
-
for (const [k, v] of Object.entries(rg)) {
|
|
1288
|
-
row[k] = stringifyValue(v);
|
|
1289
|
-
}
|
|
1290
|
-
return row;
|
|
1291
|
-
});
|
|
1292
|
-
return { tiers, resource_groups };
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
// src/tools/rest/metrics.ts
|
|
1296
|
-
async function getMetrics(session2, nodeId) {
|
|
969
|
+
}
|
|
1297
970
|
let response;
|
|
1298
971
|
let rawText;
|
|
1299
972
|
try {
|
|
1300
|
-
response = await session2.
|
|
1301
|
-
options: {}
|
|
1302
|
-
});
|
|
973
|
+
response = await session2.makeRequestToPort(port, endpoint, body);
|
|
1303
974
|
rawText = await response.text();
|
|
1304
975
|
} catch (err) {
|
|
1305
976
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -1313,213 +984,9 @@ async function getMetrics(session2, nodeId) {
|
|
|
1313
984
|
raw: rawText
|
|
1314
985
|
};
|
|
1315
986
|
}
|
|
1316
|
-
let parsed;
|
|
1317
987
|
try {
|
|
1318
|
-
parsed = JSON.parse(rawText);
|
|
1319
|
-
|
|
1320
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1321
|
-
return {
|
|
1322
|
-
ok: false,
|
|
1323
|
-
status: 200,
|
|
1324
|
-
error: `JSON parse error: ${message}`,
|
|
1325
|
-
raw: rawText
|
|
1326
|
-
};
|
|
1327
|
-
}
|
|
1328
|
-
const inner = parseDataStr(parsed.data_str, rawText);
|
|
1329
|
-
if (!inner.ok) return inner;
|
|
1330
|
-
const statisticsMap = inner.data?.statistics_map ?? {};
|
|
1331
|
-
const decoded = decodeNestedJsonStrings(statisticsMap);
|
|
1332
|
-
const rows = flattenRanksSummary(decoded);
|
|
1333
|
-
if (nodeId !== void 0) {
|
|
1334
|
-
return { ok: true, data: rows, note: `Filtered for node: ${nodeId}` };
|
|
1335
|
-
}
|
|
1336
|
-
return { ok: true, data: rows };
|
|
1337
|
-
}
|
|
1338
|
-
|
|
1339
|
-
// src/tools/rest/summarize-shards.ts
|
|
1340
|
-
var EMPTY_SUMMARY = {
|
|
1341
|
-
shard_array_version: void 0,
|
|
1342
|
-
total_shards: 0,
|
|
1343
|
-
rank_count: 0,
|
|
1344
|
-
distribution: [],
|
|
1345
|
-
balanced: true
|
|
1346
|
-
};
|
|
1347
|
-
function summarizeShards(raw) {
|
|
1348
|
-
if (raw === null || raw === void 0 || typeof raw !== "object") {
|
|
1349
|
-
return EMPTY_SUMMARY;
|
|
1350
|
-
}
|
|
1351
|
-
const obj = raw;
|
|
1352
|
-
const version = typeof obj.shard_array_version === "number" ? obj.shard_array_version : void 0;
|
|
1353
|
-
const shardMap = obj.shard_map;
|
|
1354
|
-
if (shardMap === null || shardMap === void 0 || typeof shardMap !== "object" || Array.isArray(shardMap)) {
|
|
1355
|
-
return { ...EMPTY_SUMMARY, shard_array_version: version };
|
|
1356
|
-
}
|
|
1357
|
-
const entries = Object.values(shardMap);
|
|
1358
|
-
if (entries.length === 0) {
|
|
1359
|
-
return { ...EMPTY_SUMMARY, shard_array_version: version };
|
|
1360
|
-
}
|
|
1361
|
-
const counts = entries.reduce((acc, rank) => {
|
|
1362
|
-
const map = new Map(acc);
|
|
1363
|
-
map.set(rank, (acc.get(rank) ?? 0) + 1);
|
|
1364
|
-
return map;
|
|
1365
|
-
}, /* @__PURE__ */ new Map());
|
|
1366
|
-
const totalShards = entries.length;
|
|
1367
|
-
const distribution = [...counts.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([rank, count]) => ({
|
|
1368
|
-
rank,
|
|
1369
|
-
shard_count: count,
|
|
1370
|
-
percent: `${(count / totalShards * 100).toFixed(1)}%`
|
|
1371
|
-
}));
|
|
1372
|
-
const shardCounts = distribution.map((d) => d.shard_count);
|
|
1373
|
-
const balanced = Math.max(...shardCounts) - Math.min(...shardCounts) <= 1;
|
|
1374
|
-
return {
|
|
1375
|
-
shard_array_version: version,
|
|
1376
|
-
total_shards: totalShards,
|
|
1377
|
-
rank_count: counts.size,
|
|
1378
|
-
distribution,
|
|
1379
|
-
balanced
|
|
1380
|
-
};
|
|
1381
|
-
}
|
|
1382
|
-
|
|
1383
|
-
// src/tools/rest/system-properties.ts
|
|
1384
|
-
var import_zod = require("zod");
|
|
1385
|
-
var GetSystemPropertiesSchema = import_zod.z.object({
|
|
1386
|
-
category: import_zod.z.string().optional(),
|
|
1387
|
-
key_pattern: import_zod.z.string().optional()
|
|
1388
|
-
});
|
|
1389
|
-
async function getSystemProperties(session2, input5) {
|
|
1390
|
-
try {
|
|
1391
|
-
const response = await session2.makeRequest("/show/system/properties", {
|
|
1392
|
-
options: {}
|
|
1393
|
-
});
|
|
1394
|
-
if (!response.ok) {
|
|
1395
|
-
const raw2 = await response.text();
|
|
1396
|
-
return {
|
|
1397
|
-
ok: false,
|
|
1398
|
-
status: response.status,
|
|
1399
|
-
error: `HTTP ${response.status}`,
|
|
1400
|
-
raw: raw2
|
|
1401
|
-
};
|
|
1402
|
-
}
|
|
1403
|
-
const raw = await response.text();
|
|
1404
|
-
let parsed;
|
|
1405
|
-
try {
|
|
1406
|
-
parsed = JSON.parse(raw);
|
|
1407
|
-
} catch (parseError) {
|
|
1408
|
-
const message = parseError instanceof Error ? parseError.message : String(parseError);
|
|
1409
|
-
return {
|
|
1410
|
-
ok: false,
|
|
1411
|
-
status: 200,
|
|
1412
|
-
error: `JSON parse error: ${message}`,
|
|
1413
|
-
raw
|
|
1414
|
-
};
|
|
1415
|
-
}
|
|
1416
|
-
const inner = parseDataStr(parsed.data_str, raw);
|
|
1417
|
-
if (!inner.ok) return inner;
|
|
1418
|
-
const propertyMap = inner.data?.property_map ?? {};
|
|
1419
|
-
const filteredMap = applyFilters(propertyMap, input5);
|
|
1420
|
-
return {
|
|
1421
|
-
ok: true,
|
|
1422
|
-
data: flatObjectToRows(filteredMap, "property", "value"),
|
|
1423
|
-
rowCount: Object.keys(filteredMap).length
|
|
1424
|
-
};
|
|
1425
|
-
} catch (error) {
|
|
1426
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1427
|
-
return {
|
|
1428
|
-
ok: false,
|
|
1429
|
-
status: 0,
|
|
1430
|
-
error: message,
|
|
1431
|
-
raw: ""
|
|
1432
|
-
};
|
|
1433
|
-
}
|
|
1434
|
-
}
|
|
1435
|
-
function applyFilters(propertyMap, input5) {
|
|
1436
|
-
const { category, key_pattern } = input5;
|
|
1437
|
-
if (category === void 0 && key_pattern === void 0) {
|
|
1438
|
-
return propertyMap;
|
|
1439
|
-
}
|
|
1440
|
-
const patternLower = key_pattern?.toLowerCase();
|
|
1441
|
-
return Object.fromEntries(
|
|
1442
|
-
Object.entries(propertyMap).filter(([key]) => {
|
|
1443
|
-
const matchesCategory = category === void 0 || key.startsWith(category);
|
|
1444
|
-
const matchesPattern = patternLower === void 0 || key.toLowerCase().includes(patternLower);
|
|
1445
|
-
return matchesCategory && matchesPattern;
|
|
1446
|
-
})
|
|
1447
|
-
);
|
|
1448
|
-
}
|
|
1449
|
-
|
|
1450
|
-
// src/tools/rest/discover-hm-port.ts
|
|
1451
|
-
var DEFAULT_HM_PORT2 = 9300;
|
|
1452
|
-
async function discoverHmPort(session2) {
|
|
1453
|
-
const result = await getSystemProperties(session2, { key_pattern: "hm_http_port" });
|
|
1454
|
-
if (!result.ok) return DEFAULT_HM_PORT2;
|
|
1455
|
-
const rows = result.data;
|
|
1456
|
-
const entry = rows.find((r) => r.property?.includes("hm_http_port"));
|
|
1457
|
-
if (!entry?.value) return DEFAULT_HM_PORT2;
|
|
1458
|
-
const port = parseInt(entry.value, 10);
|
|
1459
|
-
return Number.isFinite(port) ? port : DEFAULT_HM_PORT2;
|
|
1460
|
-
}
|
|
1461
|
-
|
|
1462
|
-
// src/tools/rest/cluster.ts
|
|
1463
|
-
async function fetchJson(session2, endpoint, body) {
|
|
1464
|
-
let response;
|
|
1465
|
-
let rawText;
|
|
1466
|
-
try {
|
|
1467
|
-
response = await session2.makeRequest(endpoint, body);
|
|
1468
|
-
rawText = await response.text();
|
|
1469
|
-
} catch (err) {
|
|
1470
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1471
|
-
return { ok: false, status: 0, error: message, raw: "" };
|
|
1472
|
-
}
|
|
1473
|
-
if (!response.ok) {
|
|
1474
|
-
return {
|
|
1475
|
-
ok: false,
|
|
1476
|
-
status: response.status,
|
|
1477
|
-
error: `HTTP ${response.status}`,
|
|
1478
|
-
raw: rawText
|
|
1479
|
-
};
|
|
1480
|
-
}
|
|
1481
|
-
try {
|
|
1482
|
-
const parsed = JSON.parse(rawText);
|
|
1483
|
-
return { ok: true, data: parsed };
|
|
1484
|
-
} catch (err) {
|
|
1485
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1486
|
-
return {
|
|
1487
|
-
ok: false,
|
|
1488
|
-
status: 200,
|
|
1489
|
-
error: `JSON parse error: ${message}`,
|
|
1490
|
-
raw: rawText
|
|
1491
|
-
};
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
async function fetchJsonOnPort(session2, port, endpoint, body) {
|
|
1495
|
-
if (!session2.makeRequestToPort) {
|
|
1496
|
-
return {
|
|
1497
|
-
ok: false,
|
|
1498
|
-
status: 0,
|
|
1499
|
-
error: "makeRequestToPort not available on this session",
|
|
1500
|
-
raw: ""
|
|
1501
|
-
};
|
|
1502
|
-
}
|
|
1503
|
-
let response;
|
|
1504
|
-
let rawText;
|
|
1505
|
-
try {
|
|
1506
|
-
response = await session2.makeRequestToPort(port, endpoint, body);
|
|
1507
|
-
rawText = await response.text();
|
|
1508
|
-
} catch (err) {
|
|
1509
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1510
|
-
return { ok: false, status: 0, error: message, raw: "" };
|
|
1511
|
-
}
|
|
1512
|
-
if (!response.ok) {
|
|
1513
|
-
return {
|
|
1514
|
-
ok: false,
|
|
1515
|
-
status: response.status,
|
|
1516
|
-
error: `HTTP ${response.status}`,
|
|
1517
|
-
raw: rawText
|
|
1518
|
-
};
|
|
1519
|
-
}
|
|
1520
|
-
try {
|
|
1521
|
-
const parsed = JSON.parse(rawText);
|
|
1522
|
-
return { ok: true, data: parsed };
|
|
988
|
+
const parsed = JSON.parse(rawText);
|
|
989
|
+
return { ok: true, data: parsed };
|
|
1523
990
|
} catch (err) {
|
|
1524
991
|
const message = err instanceof Error ? err.message : String(err);
|
|
1525
992
|
return {
|
|
@@ -3055,28 +2522,28 @@ async function alterConfiguration(session2, input5) {
|
|
|
3055
2522
|
|
|
3056
2523
|
// src/tools/mutation/alter-table-columns.ts
|
|
3057
2524
|
var import_zod15 = require("zod");
|
|
3058
|
-
var
|
|
3059
|
-
var
|
|
3060
|
-
var
|
|
2525
|
+
var import_prompts2 = require("@inquirer/prompts");
|
|
2526
|
+
var import_picocolors3 = __toESM(require("picocolors"));
|
|
2527
|
+
var import_claude_agent_sdk = require("@anthropic-ai/claude-agent-sdk");
|
|
3061
2528
|
|
|
3062
2529
|
// src/approval/checklist.ts
|
|
3063
|
-
var
|
|
3064
|
-
var
|
|
3065
|
-
var DIVIDER =
|
|
2530
|
+
var import_prompts = require("@inquirer/prompts");
|
|
2531
|
+
var import_picocolors2 = __toESM(require("picocolors"));
|
|
2532
|
+
var DIVIDER = import_picocolors2.default.dim("\u2500".repeat(60));
|
|
3066
2533
|
function renderChecklist(header, summary, items) {
|
|
3067
2534
|
const lines = [
|
|
3068
2535
|
"",
|
|
3069
2536
|
DIVIDER,
|
|
3070
|
-
` ${
|
|
2537
|
+
` ${import_picocolors2.default.bold(import_picocolors2.default.yellow(header))}`,
|
|
3071
2538
|
"",
|
|
3072
|
-
` ${
|
|
2539
|
+
` ${import_picocolors2.default.dim("Summary:")} ${summary}`,
|
|
3073
2540
|
"",
|
|
3074
|
-
` ${
|
|
2541
|
+
` ${import_picocolors2.default.bold(`${items.length} proposed column change(s):`)}`,
|
|
3075
2542
|
""
|
|
3076
2543
|
];
|
|
3077
2544
|
for (let i = 0; i < items.length; i++) {
|
|
3078
2545
|
const item = items[i];
|
|
3079
|
-
lines.push(` ${
|
|
2546
|
+
lines.push(` ${import_picocolors2.default.bold(`${i + 1}.`)} ${item.label}`, ` ${import_picocolors2.default.dim(item.description)}`);
|
|
3080
2547
|
}
|
|
3081
2548
|
lines.push("", DIVIDER, "");
|
|
3082
2549
|
return lines.join("\n");
|
|
@@ -3085,7 +2552,7 @@ async function showChecklist(header, summary, items) {
|
|
|
3085
2552
|
const panel = renderChecklist(header, summary, items);
|
|
3086
2553
|
process.stderr.write(panel);
|
|
3087
2554
|
try {
|
|
3088
|
-
const selected = await (0,
|
|
2555
|
+
const selected = await (0, import_prompts.checkbox)({
|
|
3089
2556
|
message: "Select columns to alter (space to toggle, enter to confirm):",
|
|
3090
2557
|
choices: items.map((item, i) => ({
|
|
3091
2558
|
value: i,
|
|
@@ -3120,12 +2587,12 @@ function buildAlterTableSql(tableName, columns) {
|
|
|
3120
2587
|
return `ALTER TABLE ${tableName}
|
|
3121
2588
|
${clauses.join(",\n")}`;
|
|
3122
2589
|
}
|
|
3123
|
-
var SQL_DIVIDER =
|
|
2590
|
+
var SQL_DIVIDER = import_picocolors3.default.dim("\u2500".repeat(60));
|
|
3124
2591
|
async function confirmSqlExecution(sql) {
|
|
3125
2592
|
const panel = [
|
|
3126
2593
|
"",
|
|
3127
2594
|
SQL_DIVIDER,
|
|
3128
|
-
` ${
|
|
2595
|
+
` ${import_picocolors3.default.bold(import_picocolors3.default.yellow("Generated SQL:"))}`,
|
|
3129
2596
|
"",
|
|
3130
2597
|
sql.split("\n").map((line) => ` ${line}`).join("\n"),
|
|
3131
2598
|
"",
|
|
@@ -3134,7 +2601,7 @@ async function confirmSqlExecution(sql) {
|
|
|
3134
2601
|
].join("\n");
|
|
3135
2602
|
process.stderr.write(panel);
|
|
3136
2603
|
try {
|
|
3137
|
-
const response = await (0,
|
|
2604
|
+
const response = await (0, import_prompts2.input)({ message: "Execute? (y/n):" });
|
|
3138
2605
|
return response.trim().toLowerCase() === "y";
|
|
3139
2606
|
} catch {
|
|
3140
2607
|
return false;
|
|
@@ -3200,7 +2667,7 @@ async function alterTableColumns(session2, toolInput) {
|
|
|
3200
2667
|
};
|
|
3201
2668
|
}
|
|
3202
2669
|
function makeAlterTableColumnsTool(session2, deps) {
|
|
3203
|
-
return (0,
|
|
2670
|
+
return (0, import_claude_agent_sdk.tool)(
|
|
3204
2671
|
"kinetica_alter_table_columns",
|
|
3205
2672
|
"Batch multiple column type/property changes on a SINGLE table into one efficient ALTER TABLE statement. Use when recommending 2+ column changes on the same table (e.g., adding DICT encoding to multiple columns, adding TEXT_SEARCH, changing column types). Each column change requires: column_name, new_definition (full type definition with properties and nullability), and description (human-readable reason). The operator selects which columns to alter via interactive checklist, then confirms the generated SQL. For a single column change, use kinetica_execute_mutation_sql directly. Kinetica ALTER TABLE syntax requires repeating the FULL column definition \u2014 properties go INSIDE parentheses: VARCHAR(50, DICT), not VARCHAR(50) DICT.",
|
|
3206
2673
|
AlterTableColumnsSchema.shape,
|
|
@@ -3313,19 +2780,19 @@ function applyOutputPipeline(result) {
|
|
|
3313
2780
|
return truncateOutput(formatOutput(payload));
|
|
3314
2781
|
}
|
|
3315
2782
|
function logMutationAudit(toolName, result, input5) {
|
|
3316
|
-
const statusLabel = result.ok ?
|
|
2783
|
+
const statusLabel = result.ok ? import_picocolors4.default.bold(import_picocolors4.default.green("EXECUTED")) : import_picocolors4.default.bold(import_picocolors4.default.red("FAILED"));
|
|
3317
2784
|
const redacted = redactAuditInput(input5);
|
|
3318
2785
|
const inputSummary = Object.entries(redacted).map(([k, v]) => `${k}=${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
3319
2786
|
const displayName = formatToolName(toolName);
|
|
3320
2787
|
process.stderr.write(
|
|
3321
|
-
` ${
|
|
3322
|
-
${
|
|
2788
|
+
` ${import_picocolors4.default.dim("MUTATION")} ${statusLabel} ${displayName}
|
|
2789
|
+
${import_picocolors4.default.dim(inputSummary)}
|
|
3323
2790
|
|
|
3324
2791
|
`
|
|
3325
2792
|
);
|
|
3326
2793
|
}
|
|
3327
2794
|
function makeAlterSystemPropertiesTool(session2) {
|
|
3328
|
-
return (0,
|
|
2795
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3329
2796
|
"kinetica_alter_system_properties",
|
|
3330
2797
|
"Apply runtime configuration changes to Kinetica via /alter/system/properties. Accepts a map of property name to new value. Captures current values before applying and verifies changes after. Only the 43 documented properties are accepted \u2014 requests with unsupported property names are rejected before any API call. Blocked for safety: ai_api_key, external_files_directory. Common properties (7.2.x): subtask_concurrency_limit, request_timeout, max_get_records_size, concurrent_kernel_execution, max_concurrent_kernels, tcs_per_tom, tps_per_tom, chunk_size, enable_audit, egress_parquet_compression, background_worker_threads. All property names require 'conf.' prefix in /show/system/properties but NOT in /alter/system/properties.",
|
|
3331
2798
|
AlterSystemPropertiesSchema.shape,
|
|
@@ -3339,7 +2806,7 @@ function makeAlterSystemPropertiesTool(session2) {
|
|
|
3339
2806
|
);
|
|
3340
2807
|
}
|
|
3341
2808
|
function makeExecuteMutationSqlTool(session2) {
|
|
3342
|
-
return (0,
|
|
2809
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3343
2810
|
"kinetica_execute_mutation_sql",
|
|
3344
2811
|
"Execute a SQL mutation statement on Kinetica (CREATE INDEX, ALTER TABLE, ALTER SYSTEM SET, REFRESH MATERIALIZED VIEW, etc.). ANALYZE TABLE is NOT supported by Kinetica \u2014 do not call it. DROP, TRUNCATE, DELETE, and UPDATE are always rejected \u2014 even when wrapped in a CTE (WITH ... DELETE/UPDATE). Requires user approval before execution.",
|
|
3345
2812
|
ExecuteMutationSqlSchema.shape,
|
|
@@ -3353,7 +2820,7 @@ function makeExecuteMutationSqlTool(session2) {
|
|
|
3353
2820
|
);
|
|
3354
2821
|
}
|
|
3355
2822
|
function makeAdminRebalanceTool(session2) {
|
|
3356
|
-
return (0,
|
|
2823
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3357
2824
|
"kinetica_admin_rebalance",
|
|
3358
2825
|
"Trigger shard data rebalancing across Kinetica cluster ranks via /admin/rebalance. Options: rebalance_sharded_data, rebalance_unsharded_data, table_includes, table_excludes, aggressiveness (1-5, capped for safety), compact_after_rebalance, compact_only. Captures before/after shard distribution for verification. WARNING: rebalance causes delayed query responses while running \u2014 use low aggressiveness (1-3) during production hours. NOTE: On single-worker-rank clusters, /admin/rebalance returns 'Database must be offline' because there is only one data rank \u2014 rebalance is only meaningful with 2+ worker ranks.",
|
|
3359
2826
|
AdminRebalanceSchema.shape,
|
|
@@ -3367,7 +2834,7 @@ function makeAdminRebalanceTool(session2) {
|
|
|
3367
2834
|
);
|
|
3368
2835
|
}
|
|
3369
2836
|
function makeAlterConfigurationTool(session2) {
|
|
3370
|
-
return (0,
|
|
2837
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3371
2838
|
"kinetica_alter_configuration",
|
|
3372
2839
|
"Replace the full gpudb.conf configuration on the Kinetica host manager via /admin/alter/configuration (port 9300). Requires the complete config_string content \u2014 the entire file is replaced. Captures before/after config summaries for verification. WARNING: This replaces the ENTIRE configuration file. Always read the current config via kinetica_show_configuration first, make targeted changes to specific lines, and submit the full modified content. Never compose a config from scratch. Requires host manager connectivity and user approval.",
|
|
3373
2840
|
AlterConfigurationSchema.shape,
|
|
@@ -3381,7 +2848,7 @@ function makeAlterConfigurationTool(session2) {
|
|
|
3381
2848
|
);
|
|
3382
2849
|
}
|
|
3383
2850
|
function makeHealthCheckTool(session2) {
|
|
3384
|
-
return (0,
|
|
2851
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3385
2852
|
"kinetica_health_check",
|
|
3386
2853
|
"Query Kinetica system health status via /show/system/status. Returns 11 components as rows (component, status): system (cluster status, leader, offline), ranks (per-rank status/mode/accepting_jobs/read_only), hosts (hostname, memory, GPU IDs, sub-service statuses), http_server (connections current/refused, thread capacity), ha_cluster_info/ha_status, graph, text, migrations, triggers, symbols. NOTE: Each status value is a JSON-encoded string \u2014 parse mentally to extract nested fields. Healthy baseline: system.status='running', all ranks rank_status='running' + rank_mode='run', hosts.status='running', http_server.connections.refused=0.",
|
|
3387
2854
|
{},
|
|
@@ -3393,7 +2860,7 @@ function makeHealthCheckTool(session2) {
|
|
|
3393
2860
|
);
|
|
3394
2861
|
}
|
|
3395
2862
|
function makeGetMetricsTool(session2) {
|
|
3396
|
-
return (0,
|
|
2863
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3397
2864
|
"kinetica_get_metrics",
|
|
3398
2865
|
"Retrieve per-rank storage tier metrics from /show/resource/statistics. Returns rows with: rank, ram_used, ram_limit, ram_percent (computed as 'X.Y%' e.g. '9.6%'), persist_used, disk_used, vram_used (all string values). Rank 0 is the head/coordinator node with minimal RAM (~794MB limit) and empty persist/disk/vram fields. Worker ranks (1+) hold the actual data with ~5.6GB RAM limit. Empty string means tier not configured; '0' means configured but unused. Healthy baseline: ram_percent under 80%. Optional node_id to annotate which node was requested.",
|
|
3399
2866
|
{ node_id: import_zod16.z.string().optional() },
|
|
@@ -3405,7 +2872,7 @@ function makeGetMetricsTool(session2) {
|
|
|
3405
2872
|
);
|
|
3406
2873
|
}
|
|
3407
2874
|
function makeClusterStatusTool(session2) {
|
|
3408
|
-
return (0,
|
|
2875
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3409
2876
|
"kinetica_cluster_status",
|
|
3410
2877
|
"Get full cluster overview via 4 sub-calls: (1) /admin/show/cluster/operations \u2014 in_progress flag, percent_complete, rebalance/add/remove status; (2) /admin/show/shards \u2014 summarized as shard distribution per rank (total_shards, rank_count, per-rank shard_count/percent, balanced flag, shard_array_version); (3) /admin/show/alerts on host manager port \u2014 recent alerts (gracefully degrades if unavailable); (4) /admin/show/jobs \u2014 active async jobs as {job_id, status, endpoint} objects. Healthy baseline: operations.in_progress=false, shards.balanced=true, empty alerts/jobs arrays.",
|
|
3411
2878
|
{},
|
|
@@ -3417,7 +2884,7 @@ function makeClusterStatusTool(session2) {
|
|
|
3417
2884
|
);
|
|
3418
2885
|
}
|
|
3419
2886
|
function makeNodeDetailsTool(session2) {
|
|
3420
|
-
return (0,
|
|
2887
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3421
2888
|
"kinetica_node_details",
|
|
3422
2889
|
"Get per-rank resource statistics from /show/resource/statistics (same endpoint as kinetica_get_metrics). Without node_id: returns summary rows for all ranks. With node_id: returns detailed tier + resource-group breakdown for that rank. Per-tier fields: limit, used, free, percent_used, num_evictable_objs, num_unevictable_objs, plus stats: evictions, pins, unpins, watermark_cycles, allocs, reallocs, deallocs (RAM/VRAM tiers) or reads, writes, deletes (PERSIST/DISK tiers). Per-resource-group fields: name, thread_running_count, data (bytes). Rank 0 is the head node \u2014 only RAM tier (no PERSIST/DISK/VRAM), no resource groups.",
|
|
3423
2890
|
{ node_id: import_zod16.z.string().optional() },
|
|
@@ -3429,7 +2896,7 @@ function makeNodeDetailsTool(session2) {
|
|
|
3429
2896
|
);
|
|
3430
2897
|
}
|
|
3431
2898
|
function makeGetLogsTool(session2) {
|
|
3432
|
-
return (0,
|
|
2899
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3433
2900
|
"kinetica_get_logs",
|
|
3434
2901
|
"Retrieve application logs via /admin/show/logs. Sources: kinetica, rank, syslog, gadmin, reveal, workbench. Severity: DEBUG|INFO|WARN|ERROR|FATAL. Time: duration (1h, 30m) or start_time+end_time. WARNING: This endpoint is NOT available on Kinetica 7.2.x \u2014 it always returns a stub response. Use kinetica_execute_sql to query ki_catalog.ki_query_history (for query errors) or ki_catalog.ki_query_span_metrics_all (for operation-level events) instead.",
|
|
3435
2902
|
GetLogsSchema.shape,
|
|
@@ -3442,7 +2909,7 @@ function makeGetLogsTool(session2) {
|
|
|
3442
2909
|
);
|
|
3443
2910
|
}
|
|
3444
2911
|
function makeShowConfigurationTool(session2) {
|
|
3445
|
-
return (0,
|
|
2912
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3446
2913
|
"kinetica_show_configuration",
|
|
3447
2914
|
"Retrieve the full gpudb.conf configuration file from the Kinetica host manager via /admin/show/configuration (port 9300). Returns the raw config_string in INI format with all sections and comments. Use this to inspect the complete server configuration for drift detection, misconfiguration diagnosis, or before proposing config changes via kinetica_alter_configuration. Requires host manager connectivity.",
|
|
3448
2915
|
ShowConfigurationSchema.shape,
|
|
@@ -3455,7 +2922,7 @@ function makeShowConfigurationTool(session2) {
|
|
|
3455
2922
|
);
|
|
3456
2923
|
}
|
|
3457
2924
|
function makeGetSystemPropertiesTool(session2) {
|
|
3458
|
-
return (0,
|
|
2925
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3459
2926
|
"kinetica_get_system_properties",
|
|
3460
2927
|
"Read Kinetica system configuration properties from /show/system/properties. Returns 260+ property rows as {property, value} pairs (all values are strings). Filter by category prefix (e.g., 'conf.tier', 'conf.sql', 'version') or key_pattern substring. Key property groups: conf.tier.* (RAM limits, watermarks, tier strategy), conf.sql.* (parallel_execution, planner timeout), conf.enable_* (authorization, HA, ML, text_search), version.* (gpudb_core_version, compute_engine). Omit both filters to get the full property_map.",
|
|
3461
2928
|
GetSystemPropertiesSchema.shape,
|
|
@@ -3468,7 +2935,7 @@ function makeGetSystemPropertiesTool(session2) {
|
|
|
3468
2935
|
);
|
|
3469
2936
|
}
|
|
3470
2937
|
function makeExecuteSqlTool(session2, catalogSchemas) {
|
|
3471
|
-
return (0,
|
|
2938
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3472
2939
|
"kinetica_execute_sql",
|
|
3473
2940
|
"Execute a read-only SQL query (SELECT, WITH, or EXPLAIN) against Kinetica. Key system tables: ki_catalog.ki_query_history (slow queries \u2014 columns: job_id, query_id, user_name, endpoint, execution_status, error_message, query_text, start_time, stop_time, sql_step_count, refresh_id, resource_group, source_ip), ki_catalog.ki_query_active_all (running queries \u2014 columns: job_id, query_id, user_name, resource_group, source_ip, endpoint, execution_status, error_message, start_time, query_text, sql_step_count, refresh_id, is_mh, is_perpetual, is_cancellable, is_using_timeout, source_rank), ki_catalog.ki_tiered_objects (per-object tier placement \u2014 size, id (string like @schema@oid[col][0] \u2014 NOT a numeric OID, do not join with ki_objects.oid), priority, tier, evictable, locked, pin_count, ram_evictions, persist_evictions, owner_resource_group, source_rank, outer_object; for per-table tier data prefer kinetica_resource_objects with table_names filter), ki_catalog.ki_obj_stat (table sizes \u2014 oid, schema_name, object_name, row_count, bytes_per_row, total_bytes), ki_catalog.ki_columns (column metadata), ki_catalog.ki_objects (object registry with obj_kind R=table/V=view). WARNING: ki_catalog.ki_tables and ki_catalog.ki_version do NOT exist in Kinetica 7.2.x \u2014 use ki_objects and /show/system/status instead.",
|
|
3474
2941
|
ExecuteSqlSchema.shape,
|
|
@@ -3485,7 +2952,7 @@ function makeExecuteSqlTool(session2, catalogSchemas) {
|
|
|
3485
2952
|
);
|
|
3486
2953
|
}
|
|
3487
2954
|
function makeExplainQueryTool(session2) {
|
|
3488
|
-
return (0,
|
|
2955
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3489
2956
|
"kinetica_explain_query",
|
|
3490
2957
|
"Get the execution plan for a SQL statement. Pass the SELECT statement without the EXPLAIN keyword \u2014 it will be added automatically. Returns rows with columns: ID (step number), ENDPOINT (internal REST endpoint used, e.g., /get/records/bycolumn), INPUT_TABLES, OUTPUT_TABLE, DEPENDENCIES (step IDs this depends on; -1 means none). Use to understand which internal operations a query triggers and verify index usage.",
|
|
3491
2958
|
ExplainQuerySchema.shape,
|
|
@@ -3497,7 +2964,7 @@ function makeExplainQueryTool(session2) {
|
|
|
3497
2964
|
);
|
|
3498
2965
|
}
|
|
3499
2966
|
function makeSystemTimingTool(session2) {
|
|
3500
|
-
return (0,
|
|
2967
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3501
2968
|
"kinetica_system_timing",
|
|
3502
2969
|
"Show endpoint response timing statistics from /show/system/timing. Returns the last ~100 API calls as {endpoint, time_in_ms, job_id} rows with sub-millisecond precision. job_id='0' means synchronous execution at head node; non-zero means async. Typical baselines: /show/system/properties <4ms, /show/security <1ms, /execute/sql 14-1300ms, /admin/verifydb 500-4500ms. Use to identify slow API endpoints or confirm whether a specific call was abnormally slow.",
|
|
3503
2970
|
{},
|
|
@@ -3509,7 +2976,7 @@ function makeSystemTimingTool(session2) {
|
|
|
3509
2976
|
);
|
|
3510
2977
|
}
|
|
3511
2978
|
function makeResourceGroupsTool(session2) {
|
|
3512
|
-
return (0,
|
|
2979
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3513
2980
|
"kinetica_resource_groups",
|
|
3514
2981
|
"List resource groups and their configuration from /show/resourcegroups. Returns {groups, rank_usage, info}. Groups include: name, RAM.max_memory, VRAM.GPU0.max_memory, max_cpu_concurrency, max_data, max_scheduling_priority (100=system, 50=default), max_tier_priority. Value '9223372036854775807' (Long.MAX_VALUE) means unlimited. Default groups are kinetica_system_resource_group (priority 100) and kinetica_default_resource_group (priority 50). rank_usage maps rank IDs to JSON-encoded per-group usage (thread_running_count, data bytes, RAM.used, VRAM.GPU0.used). Only worker ranks appear in rank_usage \u2014 rank 0 (head) has no entry. Set show_tier_usage=true for rank-level breakdown.",
|
|
3515
2982
|
ResourceGroupsSchema.shape,
|
|
@@ -3522,7 +2989,7 @@ function makeResourceGroupsTool(session2) {
|
|
|
3522
2989
|
);
|
|
3523
2990
|
}
|
|
3524
2991
|
function makeVerifyDbTool(session2) {
|
|
3525
|
-
return (0,
|
|
2992
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3526
2993
|
"kinetica_verify_db",
|
|
3527
2994
|
"Run a read-only database integrity verification via /admin/verifydb. Checks for null values, persistence issues, and rank0 consistency. Always runs in concurrent_safe mode. Returns {verified_ok: boolean, error_list: [], orphaned_tables_total_size: number}. Healthy: verified_ok=true, empty error_list, orphaned_tables_total_size of -1 (not checked) or 0. WARNING: This is the slowest diagnostic tool \u2014 typically takes 500ms-4500ms. Use sparingly and only when data integrity issues are suspected.",
|
|
3528
2995
|
VerifyDbSchema.shape,
|
|
@@ -3535,7 +3002,7 @@ function makeVerifyDbTool(session2) {
|
|
|
3535
3002
|
);
|
|
3536
3003
|
}
|
|
3537
3004
|
function makeShowSecurityTool(session2) {
|
|
3538
|
-
return (0,
|
|
3005
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3539
3006
|
"kinetica_show_security",
|
|
3540
3007
|
"Show security configuration from /show/security. Returns {types, roles, permissions, resource_groups, info}. When enable_authorization=false (check permissions[''].enable_authorization), types/roles/resource_groups are empty objects \u2014 the tool provides minimal data. When authorization is enabled: types maps usernames to 'internal_user'/'external_user', roles maps role names to member arrays, permissions maps users to permission arrays, resource_groups maps users to group names. Use to audit access control or diagnose authorization failures.",
|
|
3541
3008
|
ShowSecuritySchema.shape,
|
|
@@ -3548,7 +3015,7 @@ function makeShowSecurityTool(session2) {
|
|
|
3548
3015
|
);
|
|
3549
3016
|
}
|
|
3550
3017
|
function makeShowTableTool(session2) {
|
|
3551
|
-
return (0,
|
|
3018
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3552
3019
|
"kinetica_show_table",
|
|
3553
3020
|
"Show table metadata from /show/table. When a specific table_name is provided: returns table metadata with Kinetica-native column types, per-column properties (DICT, TEXT_SEARCH, COMPRESS, etc.), and index definitions from ki_catalog.ki_indexes (index_type, index_columns) \u2014 this is the preferred method for full table inspection. When table_name is omitted or empty: returns schema-level (collection) entries with sizes, but the processed output may be empty \u2014 use kinetica_execute_sql with 'SELECT * FROM ki_catalog.ki_objects WHERE obj_kind = ''R'' ORDER BY schema_name' for reliable table listing instead.",
|
|
3554
3021
|
ShowTableSchema.shape,
|
|
@@ -3561,7 +3028,7 @@ function makeShowTableTool(session2) {
|
|
|
3561
3028
|
);
|
|
3562
3029
|
}
|
|
3563
3030
|
function makeResourceObjectsTool(session2) {
|
|
3564
|
-
return (0,
|
|
3031
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3565
3032
|
"kinetica_resource_objects",
|
|
3566
3033
|
`Show per-rank resource tier usage from /show/resource/objects. Returns {rank_objects: {rank_id: JSON_string_with_objects_array}, info}. Each object has: id (naming convention: @table@oid[column][chunk] for data, AttrIndex[...] for indexes, PKIndex_... for PK hashes), size (bytes), priority (1=system, 5=user, 9=temp), tier ('RAM' or 'PERSIST'), evictable (boolean), locked (boolean), pin_count, ram_evictions, persist_evictions, owner_resource_group. The rank_objects JSON is {"objects": [...]} \u2014 an array nested under an 'objects' key. Only worker ranks have data \u2014 rank 0 (head) has no resource objects. Healthy: zero evictions, zero pin_count at rest.`,
|
|
3567
3034
|
ResourceObjectsSchema.shape,
|
|
@@ -3574,7 +3041,7 @@ function makeResourceObjectsTool(session2) {
|
|
|
3574
3041
|
);
|
|
3575
3042
|
}
|
|
3576
3043
|
function makeHostManagerStatusTool(session2) {
|
|
3577
|
-
return (0,
|
|
3044
|
+
return (0, import_claude_agent_sdk2.tool)(
|
|
3578
3045
|
"kinetica_host_manager_status",
|
|
3579
3046
|
"Query the Kinetica host manager root endpoint (port 9300) for cluster-wide status. Returns a flat key-value map including: version, hostname, system_mode ('run'/'stop'), system_status ('running'/'stopped'), system_idle_time (seconds), cluster_leader (IP), cluster_operation ('none'/'rebalance'/etc.), license_type/status/expiration, per-host and per-rank mode/status/pid, and service statuses (ml, httpd, query_planner, reveal, stats, graph, text). Healthy baseline: system_mode='run', system_status='running', all rankN_status='running', license_status='ok'. Does NOT require Kinetica DB authentication \u2014 queries the host manager service directly.",
|
|
3580
3047
|
{},
|
|
@@ -3726,17 +3193,17 @@ function buildEvidenceChecklist() {
|
|
|
3726
3193
|
|
|
3727
3194
|
// src/agent/report-template.ts
|
|
3728
3195
|
var import_node_fs2 = require("fs");
|
|
3729
|
-
var
|
|
3196
|
+
var import_node_path2 = require("path");
|
|
3730
3197
|
|
|
3731
3198
|
// src/agent/load-playbooks.ts
|
|
3732
|
-
var
|
|
3733
|
-
var
|
|
3199
|
+
var import_promises = require("fs/promises");
|
|
3200
|
+
var import_node_path = require("path");
|
|
3734
3201
|
var import_node_fs = require("fs");
|
|
3735
3202
|
function findPackageRoot(startDir) {
|
|
3736
3203
|
let dir = startDir;
|
|
3737
|
-
while (dir !== (0,
|
|
3738
|
-
if ((0, import_node_fs.existsSync)((0,
|
|
3739
|
-
dir = (0,
|
|
3204
|
+
while (dir !== (0, import_node_path.dirname)(dir)) {
|
|
3205
|
+
if ((0, import_node_fs.existsSync)((0, import_node_path.join)(dir, "package.json"))) return dir;
|
|
3206
|
+
dir = (0, import_node_path.dirname)(dir);
|
|
3740
3207
|
}
|
|
3741
3208
|
return startDir;
|
|
3742
3209
|
}
|
|
@@ -3769,13 +3236,13 @@ function extractBody(raw) {
|
|
|
3769
3236
|
}
|
|
3770
3237
|
async function loadPlaybooks(playbooksDir) {
|
|
3771
3238
|
try {
|
|
3772
|
-
const dir = playbooksDir ?? (0,
|
|
3239
|
+
const dir = playbooksDir ?? (0, import_node_path.join)(findPackageRoot(__dirname), "knowledge", "playbooks");
|
|
3773
3240
|
if (!(0, import_node_fs.existsSync)(dir)) return [];
|
|
3774
|
-
const files = await (0,
|
|
3241
|
+
const files = await (0, import_promises.readdir)(dir);
|
|
3775
3242
|
const mdFiles = files.filter((f) => f.endsWith(".md")).sort();
|
|
3776
3243
|
const playbooks = [];
|
|
3777
3244
|
for (const file of mdFiles) {
|
|
3778
|
-
const raw = await (0,
|
|
3245
|
+
const raw = await (0, import_promises.readFile)((0, import_node_path.join)(dir, file), "utf-8");
|
|
3779
3246
|
const frontmatter = parseFrontmatter(raw);
|
|
3780
3247
|
if (!frontmatter) continue;
|
|
3781
3248
|
playbooks.push({
|
|
@@ -3794,7 +3261,7 @@ async function loadPlaybooks(playbooksDir) {
|
|
|
3794
3261
|
function loadReportTemplateSync() {
|
|
3795
3262
|
try {
|
|
3796
3263
|
const root = findPackageRoot(__dirname);
|
|
3797
|
-
const path2 = (0,
|
|
3264
|
+
const path2 = (0, import_node_path2.join)(root, "knowledge", "templates", "report.md");
|
|
3798
3265
|
return (0, import_node_fs2.readFileSync)(path2, "utf-8");
|
|
3799
3266
|
} catch (err) {
|
|
3800
3267
|
console.warn(`[report-template] failed to load knowledge/templates/report.md: ${String(err)}`);
|
|
@@ -4083,6 +3550,13 @@ Monitor your context window usage during long investigations:
|
|
|
4083
3550
|
- If you detect that context is getting full (many rounds, many large tool responses), warn the operator: "The session context is getting long. Consider starting a fresh session after this report to maintain investigation quality. Your reports are saved to disk."
|
|
4084
3551
|
- Do NOT continue investigating when context is too full \u2014 write the report with evidence gathered so far.
|
|
4085
3552
|
|
|
3553
|
+
## Budget & Length Awareness
|
|
3554
|
+
|
|
3555
|
+
The session has a per-session budget guard that can end the run before you finish \u2014 and the operator may see a warning that you are "approaching the budget guard". To make sure a diagnostic always survives an early cutoff:
|
|
3556
|
+
- During a long or expensive investigation (many rounds or many large tool responses), proactively call \`save_report\` with \`partial: true\` to checkpoint your findings so far. A partial report is far better than none.
|
|
3557
|
+
- If the operator warns you that the budget guard is approaching, STOP gathering new evidence: immediately save a \`partial: true\` report with the evidence you have, state your best current hypothesis, and wind down the turn.
|
|
3558
|
+
- Treat the guard as a normal limit, not an error \u2014 never apologize for it; just preserve the work.
|
|
3559
|
+
|
|
4086
3560
|
---
|
|
4087
3561
|
|
|
4088
3562
|
## Output Formatting
|
|
@@ -4165,18 +3639,18 @@ async function discoverCatalogSchemas(session2) {
|
|
|
4165
3639
|
}
|
|
4166
3640
|
|
|
4167
3641
|
// src/agent/load-references.ts
|
|
4168
|
-
var
|
|
4169
|
-
var
|
|
3642
|
+
var import_promises2 = require("fs/promises");
|
|
3643
|
+
var import_node_path3 = require("path");
|
|
4170
3644
|
var import_node_fs3 = require("fs");
|
|
4171
3645
|
async function loadReferences(refsDir) {
|
|
4172
3646
|
try {
|
|
4173
|
-
const dir = refsDir ?? (0,
|
|
3647
|
+
const dir = refsDir ?? (0, import_node_path3.join)(findPackageRoot(__dirname), "knowledge", "references");
|
|
4174
3648
|
if (!(0, import_node_fs3.existsSync)(dir)) return [];
|
|
4175
|
-
const files = await (0,
|
|
3649
|
+
const files = await (0, import_promises2.readdir)(dir);
|
|
4176
3650
|
const mdFiles = files.filter((f) => f.endsWith(".md")).sort();
|
|
4177
3651
|
const references = [];
|
|
4178
3652
|
for (const file of mdFiles) {
|
|
4179
|
-
const raw = await (0,
|
|
3653
|
+
const raw = await (0, import_promises2.readFile)((0, import_node_path3.join)(dir, file), "utf-8");
|
|
4180
3654
|
const frontmatter = parseFrontmatter(raw);
|
|
4181
3655
|
if (!frontmatter) continue;
|
|
4182
3656
|
references.push({
|
|
@@ -4187,669 +3661,1367 @@ async function loadReferences(refsDir) {
|
|
|
4187
3661
|
filename: file
|
|
4188
3662
|
});
|
|
4189
3663
|
}
|
|
4190
|
-
return references;
|
|
4191
|
-
} catch {
|
|
4192
|
-
return [];
|
|
3664
|
+
return references;
|
|
3665
|
+
} catch {
|
|
3666
|
+
return [];
|
|
3667
|
+
}
|
|
3668
|
+
}
|
|
3669
|
+
|
|
3670
|
+
// src/agent/prompt-budget.ts
|
|
3671
|
+
var CHARS_PER_TOKEN = 4;
|
|
3672
|
+
var DEFAULT_PROMPT_BUDGET_TOKENS = 2e4;
|
|
3673
|
+
function estimateTokens(text) {
|
|
3674
|
+
if (!text) return 0;
|
|
3675
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
3676
|
+
}
|
|
3677
|
+
function checkPromptBudget(prompt, opts) {
|
|
3678
|
+
const threshold = opts?.warnAtTokens ?? DEFAULT_PROMPT_BUDGET_TOKENS;
|
|
3679
|
+
const tokens = estimateTokens(prompt);
|
|
3680
|
+
return {
|
|
3681
|
+
tokens,
|
|
3682
|
+
chars: prompt ? prompt.length : 0,
|
|
3683
|
+
threshold,
|
|
3684
|
+
overBudget: tokens > threshold
|
|
3685
|
+
};
|
|
3686
|
+
}
|
|
3687
|
+
|
|
3688
|
+
// src/agent/session-budget.ts
|
|
3689
|
+
var DEFAULT_MAX_BUDGET_USD = 5;
|
|
3690
|
+
var DEFAULT_WARN_FRACTION = 0.8;
|
|
3691
|
+
var BUDGET_ENV_VAR = "ADMIN_AGENT_MAX_BUDGET";
|
|
3692
|
+
var MODEL_PRICING = {
|
|
3693
|
+
sonnet: { inputPerMTok: 3, outputPerMTok: 15, cacheReadPerMTok: 0.3, cacheCreationPerMTok: 3.75 },
|
|
3694
|
+
haiku: { inputPerMTok: 1, outputPerMTok: 5, cacheReadPerMTok: 0.1, cacheCreationPerMTok: 1.25 },
|
|
3695
|
+
opus: { inputPerMTok: 15, outputPerMTok: 75, cacheReadPerMTok: 1.5, cacheCreationPerMTok: 18.75 }
|
|
3696
|
+
};
|
|
3697
|
+
function fromSdkUsage(raw) {
|
|
3698
|
+
const u = raw ?? {};
|
|
3699
|
+
return {
|
|
3700
|
+
inputTokens: u.input_tokens,
|
|
3701
|
+
outputTokens: u.output_tokens,
|
|
3702
|
+
cacheReadInputTokens: u.cache_read_input_tokens,
|
|
3703
|
+
cacheCreationInputTokens: u.cache_creation_input_tokens
|
|
3704
|
+
};
|
|
3705
|
+
}
|
|
3706
|
+
function safeCount(value) {
|
|
3707
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0;
|
|
3708
|
+
}
|
|
3709
|
+
function isValidBudget(value) {
|
|
3710
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
3711
|
+
}
|
|
3712
|
+
function estimateTurnCostUsd(usage, model) {
|
|
3713
|
+
if (!usage) return 0;
|
|
3714
|
+
const price = MODEL_PRICING[model];
|
|
3715
|
+
const input5 = safeCount(usage.inputTokens);
|
|
3716
|
+
const output = safeCount(usage.outputTokens);
|
|
3717
|
+
const cacheRead = safeCount(usage.cacheReadInputTokens);
|
|
3718
|
+
const cacheCreation = safeCount(usage.cacheCreationInputTokens);
|
|
3719
|
+
return (input5 * price.inputPerMTok + output * price.outputPerMTok + cacheRead * price.cacheReadPerMTok + cacheCreation * price.cacheCreationPerMTok) / 1e6;
|
|
3720
|
+
}
|
|
3721
|
+
function resolveMaxBudgetUsd(flagValue, env = process.env) {
|
|
3722
|
+
if (isValidBudget(flagValue)) return flagValue;
|
|
3723
|
+
const raw = env[BUDGET_ENV_VAR];
|
|
3724
|
+
if (raw !== void 0 && raw !== "") {
|
|
3725
|
+
const parsed = Number(raw);
|
|
3726
|
+
if (isValidBudget(parsed)) return parsed;
|
|
3727
|
+
}
|
|
3728
|
+
return DEFAULT_MAX_BUDGET_USD;
|
|
3729
|
+
}
|
|
3730
|
+
function createBudgetTracker(opts) {
|
|
3731
|
+
const warnFraction = opts.warnFraction ?? DEFAULT_WARN_FRACTION;
|
|
3732
|
+
const warnAt = opts.maxUsd * warnFraction;
|
|
3733
|
+
let spent = 0;
|
|
3734
|
+
let warned = false;
|
|
3735
|
+
return {
|
|
3736
|
+
add(usage, model) {
|
|
3737
|
+
spent += estimateTurnCostUsd(usage, model);
|
|
3738
|
+
},
|
|
3739
|
+
spentUsd() {
|
|
3740
|
+
return spent;
|
|
3741
|
+
},
|
|
3742
|
+
shouldWarn() {
|
|
3743
|
+
return !warned && spent > warnAt;
|
|
3744
|
+
},
|
|
3745
|
+
markWarned() {
|
|
3746
|
+
warned = true;
|
|
3747
|
+
}
|
|
3748
|
+
};
|
|
3749
|
+
}
|
|
3750
|
+
|
|
3751
|
+
// src/report/save-report.ts
|
|
3752
|
+
var import_promises3 = require("fs/promises");
|
|
3753
|
+
var import_node_path4 = require("path");
|
|
3754
|
+
var import_claude_agent_sdk3 = require("@anthropic-ai/claude-agent-sdk");
|
|
3755
|
+
var import_zod17 = require("zod");
|
|
3756
|
+
var PARTIAL_MARKER = "(PARTIAL -- investigation interrupted)\n\n";
|
|
3757
|
+
function formatTimestamp(date) {
|
|
3758
|
+
const year = date.getUTCFullYear();
|
|
3759
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
3760
|
+
const day = String(date.getUTCDate()).padStart(2, "0");
|
|
3761
|
+
const hours = String(date.getUTCHours()).padStart(2, "0");
|
|
3762
|
+
const minutes = String(date.getUTCMinutes()).padStart(2, "0");
|
|
3763
|
+
const seconds = String(date.getUTCSeconds()).padStart(2, "0");
|
|
3764
|
+
return `${year}-${month}-${day}-${hours}${minutes}${seconds}`;
|
|
3765
|
+
}
|
|
3766
|
+
function makeSaveReportTool() {
|
|
3767
|
+
return (0, import_claude_agent_sdk3.tool)(
|
|
3768
|
+
"save_report",
|
|
3769
|
+
"Save a diagnostic report to disk. Automatically scrubs credentials, creates a timestamped filename in reports/, and auto-creates the directory. Use at the end of each investigation or when interrupted.",
|
|
3770
|
+
{
|
|
3771
|
+
content: import_zod17.z.string().describe("The full markdown diagnostic report content"),
|
|
3772
|
+
partial: import_zod17.z.boolean().optional().describe(
|
|
3773
|
+
"Set to true if the investigation was interrupted (e.g., Ctrl+C). Prepends a PARTIAL marker to the report."
|
|
3774
|
+
)
|
|
3775
|
+
},
|
|
3776
|
+
async (args) => {
|
|
3777
|
+
const rawContent = args.partial ? `${PARTIAL_MARKER}${args.content}` : args.content;
|
|
3778
|
+
const scrubbed = scrubCredentials(rawContent);
|
|
3779
|
+
const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
|
|
3780
|
+
const filename = `kinetica-diag-${timestamp}.md`;
|
|
3781
|
+
const dir = (0, import_node_path4.resolve)(process.cwd(), "reports");
|
|
3782
|
+
await (0, import_promises3.mkdir)(dir, { recursive: true });
|
|
3783
|
+
const filepath = (0, import_node_path4.join)(dir, filename);
|
|
3784
|
+
await (0, import_promises3.writeFile)(filepath, scrubbed, "utf-8");
|
|
3785
|
+
return {
|
|
3786
|
+
content: [{ type: "text", text: `Report saved: ${filepath}` }]
|
|
3787
|
+
};
|
|
3788
|
+
},
|
|
3789
|
+
{ annotations: { readOnly: true } }
|
|
3790
|
+
);
|
|
3791
|
+
}
|
|
3792
|
+
|
|
3793
|
+
// src/approval/gate.ts
|
|
3794
|
+
var import_prompts3 = require("@inquirer/prompts");
|
|
3795
|
+
|
|
3796
|
+
// src/approval/display.ts
|
|
3797
|
+
var import_picocolors5 = __toESM(require("picocolors"));
|
|
3798
|
+
var IMPACT_FALLBACK = "Impact unknown \u2014 review parameters carefully";
|
|
3799
|
+
var DIVIDER2 = import_picocolors5.default.dim("\u2500".repeat(50));
|
|
3800
|
+
var LABEL_WIDTH = 8;
|
|
3801
|
+
function formatLabel(label) {
|
|
3802
|
+
return ` ${label.padEnd(LABEL_WIDTH)}: `;
|
|
3803
|
+
}
|
|
3804
|
+
function renderApprovalPanel(toolName, toolInput, impact, beforeAfter, reasoningSummary) {
|
|
3805
|
+
const header = import_picocolors5.default.bold(import_picocolors5.default.yellow(" Mutation Approval Required"));
|
|
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");
|
|
3831
|
+
}
|
|
3832
|
+
|
|
3833
|
+
// src/approval/gate.ts
|
|
3834
|
+
var DENY_MESSAGE = "User denied this mutation. Skip and continue with the investigation.";
|
|
3835
|
+
var REASONING_FALLBACK = "Reasoning not available. Review the action details above before proceeding.";
|
|
3836
|
+
function createApprovalGate(isReadOnly) {
|
|
3837
|
+
return async (toolName, toolInput, options) => {
|
|
3838
|
+
if (isReadOnly(toolName)) {
|
|
3839
|
+
return {
|
|
3840
|
+
behavior: "allow",
|
|
3841
|
+
updatedInput: toolInput,
|
|
3842
|
+
toolUseID: options.toolUseID
|
|
3843
|
+
};
|
|
3844
|
+
}
|
|
3845
|
+
const impact = options.decisionReason;
|
|
3846
|
+
const panel = renderApprovalPanel(toolName, toolInput, impact);
|
|
3847
|
+
console.error(panel);
|
|
3848
|
+
while (true) {
|
|
3849
|
+
try {
|
|
3850
|
+
const raw = await (0, import_prompts3.input)({ message: "Proceed? (y/n/explain):" }, { signal: options.signal });
|
|
3851
|
+
const normalized = raw.trim().toLowerCase();
|
|
3852
|
+
if (normalized === "y") {
|
|
3853
|
+
process.stderr.write("\n");
|
|
3854
|
+
return {
|
|
3855
|
+
behavior: "allow",
|
|
3856
|
+
updatedInput: toolInput,
|
|
3857
|
+
toolUseID: options.toolUseID
|
|
3858
|
+
};
|
|
3859
|
+
}
|
|
3860
|
+
if (normalized === "n") {
|
|
3861
|
+
process.stderr.write("\n");
|
|
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
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
};
|
|
3889
|
+
}
|
|
3890
|
+
|
|
3891
|
+
// src/agent/turn-gate.ts
|
|
3892
|
+
function createTurnGate() {
|
|
3893
|
+
let resolve2 = () => {
|
|
3894
|
+
};
|
|
3895
|
+
let promise = new Promise((r) => {
|
|
3896
|
+
resolve2 = r;
|
|
3897
|
+
});
|
|
3898
|
+
return Object.freeze({
|
|
3899
|
+
wait: () => promise,
|
|
3900
|
+
open: () => {
|
|
3901
|
+
resolve2();
|
|
3902
|
+
},
|
|
3903
|
+
close: () => {
|
|
3904
|
+
promise = new Promise((r) => {
|
|
3905
|
+
resolve2 = r;
|
|
3906
|
+
});
|
|
3907
|
+
}
|
|
3908
|
+
});
|
|
3909
|
+
}
|
|
3910
|
+
|
|
3911
|
+
// src/output/render-markdown.ts
|
|
3912
|
+
var import_picocolors6 = __toESM(require("picocolors"));
|
|
3913
|
+
var BOLD_RE = /\*\*(.+?)\*\*/g;
|
|
3914
|
+
var HEADING_RE = /^(#{1,6})\s+(.+)$/;
|
|
3915
|
+
function renderMarkdownLine(line) {
|
|
3916
|
+
const headingMatch = HEADING_RE.exec(line);
|
|
3917
|
+
if (headingMatch) {
|
|
3918
|
+
return import_picocolors6.default.bold(headingMatch[2]);
|
|
3919
|
+
}
|
|
3920
|
+
if (line.includes("**")) {
|
|
3921
|
+
return line.replace(BOLD_RE, (_, text) => import_picocolors6.default.bold(text));
|
|
3922
|
+
}
|
|
3923
|
+
return line;
|
|
3924
|
+
}
|
|
3925
|
+
|
|
3926
|
+
// src/output/reformat-tables.ts
|
|
3927
|
+
var SEPARATOR_CELL_RE = /^:?-+:?$/;
|
|
3928
|
+
var BOLD_MARKERS_RE = /\*\*(.+?)\*\*/g;
|
|
3929
|
+
function visualWidth(text) {
|
|
3930
|
+
return text.replace(BOLD_MARKERS_RE, "$1").length;
|
|
3931
|
+
}
|
|
3932
|
+
function isSeparatorCell(cell) {
|
|
3933
|
+
return SEPARATOR_CELL_RE.test(cell);
|
|
3934
|
+
}
|
|
3935
|
+
function isSeparatorRow(cells) {
|
|
3936
|
+
return cells.length > 0 && cells.every(isSeparatorCell);
|
|
3937
|
+
}
|
|
3938
|
+
function parseCells(line) {
|
|
3939
|
+
return line.split("|").slice(1, -1).map((c) => c.trim());
|
|
3940
|
+
}
|
|
3941
|
+
function reformatTableBlock(lines) {
|
|
3942
|
+
const parsed = lines.map(parseCells);
|
|
3943
|
+
const colCount = Math.max(...parsed.map((row) => row.length));
|
|
3944
|
+
const normalised = parsed.map((row) => {
|
|
3945
|
+
const padded = [...row];
|
|
3946
|
+
while (padded.length < colCount) {
|
|
3947
|
+
padded.push("");
|
|
3948
|
+
}
|
|
3949
|
+
return padded;
|
|
3950
|
+
});
|
|
3951
|
+
const colWidths = Array.from(
|
|
3952
|
+
{ length: colCount },
|
|
3953
|
+
(_, col) => Math.max(
|
|
3954
|
+
3,
|
|
3955
|
+
...normalised.filter((row) => !isSeparatorRow(row)).map((row) => visualWidth(row[col]))
|
|
3956
|
+
)
|
|
3957
|
+
);
|
|
3958
|
+
const borderRow = `+${colWidths.map((w) => "-".repeat(w + 2)).join("+")}+`;
|
|
3959
|
+
const bodyRows = normalised.map((row) => {
|
|
3960
|
+
if (isSeparatorRow(row)) {
|
|
3961
|
+
return borderRow;
|
|
3962
|
+
}
|
|
3963
|
+
const cells = row.map((cell, col) => {
|
|
3964
|
+
const rendered = renderMarkdownLine(cell);
|
|
3965
|
+
const pad = colWidths[col] - visualWidth(cell);
|
|
3966
|
+
return rendered + " ".repeat(Math.max(0, pad));
|
|
3967
|
+
});
|
|
3968
|
+
return `| ${cells.join(" | ")} |`;
|
|
3969
|
+
});
|
|
3970
|
+
return [borderRow, ...bodyRows, borderRow];
|
|
3971
|
+
}
|
|
3972
|
+
|
|
3973
|
+
// src/output/streaming-table-aligner.ts
|
|
3974
|
+
var TABLE_LINE_RE = /^\|.*\|$/;
|
|
3975
|
+
function createStreamingTableAligner() {
|
|
3976
|
+
let lineBuffer = "";
|
|
3977
|
+
let tableLines = [];
|
|
3978
|
+
function flushTable() {
|
|
3979
|
+
if (tableLines.length === 0) return "";
|
|
3980
|
+
const aligned = reformatTableBlock(tableLines);
|
|
3981
|
+
tableLines = [];
|
|
3982
|
+
return aligned.join("\n") + "\n";
|
|
3983
|
+
}
|
|
3984
|
+
function push(text) {
|
|
3985
|
+
if (!text) return "";
|
|
3986
|
+
const combined = lineBuffer + text;
|
|
3987
|
+
const segments = combined.split("\n");
|
|
3988
|
+
lineBuffer = segments[segments.length - 1];
|
|
3989
|
+
const completeLines = segments.slice(0, -1);
|
|
3990
|
+
let output = "";
|
|
3991
|
+
for (const line of completeLines) {
|
|
3992
|
+
const trimmed = line.trim();
|
|
3993
|
+
if (TABLE_LINE_RE.test(trimmed)) {
|
|
3994
|
+
tableLines.push(trimmed);
|
|
3995
|
+
} else {
|
|
3996
|
+
output += flushTable();
|
|
3997
|
+
output += renderMarkdownLine(line) + "\n";
|
|
3998
|
+
}
|
|
3999
|
+
}
|
|
4000
|
+
return output;
|
|
4001
|
+
}
|
|
4002
|
+
function flush() {
|
|
4003
|
+
let output = flushTable();
|
|
4004
|
+
if (lineBuffer) {
|
|
4005
|
+
output += renderMarkdownLine(lineBuffer);
|
|
4006
|
+
lineBuffer = "";
|
|
4007
|
+
}
|
|
4008
|
+
return output;
|
|
4009
|
+
}
|
|
4010
|
+
return Object.freeze({ push, flush });
|
|
4011
|
+
}
|
|
4012
|
+
|
|
4013
|
+
// src/output/spinner.ts
|
|
4014
|
+
var import_picocolors7 = __toESM(require("picocolors"));
|
|
4015
|
+
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4016
|
+
var FRAME_INTERVAL_MS = 80;
|
|
4017
|
+
var DEFAULT_LABEL = "Thinking";
|
|
4018
|
+
function createSpinner() {
|
|
4019
|
+
let timer = null;
|
|
4020
|
+
let frameIndex = 0;
|
|
4021
|
+
const start = (label = DEFAULT_LABEL) => {
|
|
4022
|
+
if (timer !== null) return;
|
|
4023
|
+
frameIndex = 0;
|
|
4024
|
+
timer = setInterval(() => {
|
|
4025
|
+
const frame = FRAMES[frameIndex % FRAMES.length];
|
|
4026
|
+
process.stderr.write(`\r${import_picocolors7.default.dim(`${frame} ${label}...`)}`);
|
|
4027
|
+
frameIndex += 1;
|
|
4028
|
+
}, FRAME_INTERVAL_MS);
|
|
4029
|
+
timer.unref();
|
|
4030
|
+
};
|
|
4031
|
+
const stop = () => {
|
|
4032
|
+
if (timer === null) return;
|
|
4033
|
+
clearInterval(timer);
|
|
4034
|
+
timer = null;
|
|
4035
|
+
process.stderr.write("\r\x1B[K");
|
|
4036
|
+
};
|
|
4037
|
+
const isRunning = () => timer !== null;
|
|
4038
|
+
return Object.freeze({ start, stop, isRunning });
|
|
4039
|
+
}
|
|
4040
|
+
|
|
4041
|
+
// src/agent/run-agent.ts
|
|
4042
|
+
var MCP_SERVER_NAME = "kinetica-diagnostics";
|
|
4043
|
+
var SAVE_REPORT_TOOL_NAME = `mcp__${MCP_SERVER_NAME}__save_report`;
|
|
4044
|
+
function contentCallsSaveReport(content) {
|
|
4045
|
+
if (!Array.isArray(content)) return false;
|
|
4046
|
+
return content.some((block) => {
|
|
4047
|
+
if (typeof block !== "object" || block === null) return false;
|
|
4048
|
+
const { type, name } = block;
|
|
4049
|
+
return type === "tool_use" && name === SAVE_REPORT_TOOL_NAME;
|
|
4050
|
+
});
|
|
4051
|
+
}
|
|
4052
|
+
function formatCostSuffix(costUsd) {
|
|
4053
|
+
return costUsd !== void 0 && costUsd > 0 ? ` Cost: $${costUsd.toFixed(4)}.` : "";
|
|
4054
|
+
}
|
|
4055
|
+
function formatMetricsLine(turns, durationMs, durationApiMs, costUsd) {
|
|
4056
|
+
const durationSec = Math.round(durationMs / 1e3);
|
|
4057
|
+
const apiPct = durationMs > 0 ? Math.round(durationApiMs / durationMs * 100) : 0;
|
|
4058
|
+
return `Turns: ${turns}. Duration: ${durationSec}s (${apiPct}% API).${formatCostSuffix(costUsd)}`;
|
|
4059
|
+
}
|
|
4060
|
+
var EXIT_COMMANDS = /* @__PURE__ */ new Set(["exit", "quit", "end", "q"]);
|
|
4061
|
+
var SUPPORTED_MODELS = ["sonnet", "haiku", "opus"];
|
|
4062
|
+
var DEFAULT_AGENT_MODEL = "sonnet";
|
|
4063
|
+
var ALLOWED_TOOL_NAMES = [
|
|
4064
|
+
...DIAGNOSTIC_TOOL_NAMES.map((name) => `mcp__${MCP_SERVER_NAME}__${name}`),
|
|
4065
|
+
SAVE_REPORT_TOOL_NAME,
|
|
4066
|
+
`mcp__${MCP_SERVER_NAME}__${ALTER_TABLE_COLUMNS_TOOL_NAME}`
|
|
4067
|
+
];
|
|
4068
|
+
var DISALLOWED_TOOLS = ["Bash", "Edit", "Write", "MultiEdit"];
|
|
4069
|
+
var ERROR_LABELS = {
|
|
4070
|
+
authentication_failed: "Authentication failed \u2014 check your API key or re-run with --login",
|
|
4071
|
+
billing_error: "Billing error \u2014 check your Anthropic account",
|
|
4072
|
+
rate_limit: "Rate limit exceeded",
|
|
4073
|
+
server_error: "Anthropic API server error",
|
|
4074
|
+
invalid_request: "Invalid API request",
|
|
4075
|
+
max_output_tokens: "Response exceeded maximum output length",
|
|
4076
|
+
unknown: "Unknown API error"
|
|
4077
|
+
};
|
|
4078
|
+
function isExitCommand(text) {
|
|
4079
|
+
return EXIT_COMMANDS.has(text.trim().toLowerCase());
|
|
4080
|
+
}
|
|
4081
|
+
function makeUserMessage(content) {
|
|
4082
|
+
return {
|
|
4083
|
+
type: "user",
|
|
4084
|
+
message: { role: "user", content },
|
|
4085
|
+
parent_tool_use_id: null,
|
|
4086
|
+
session_id: ""
|
|
4087
|
+
};
|
|
4088
|
+
}
|
|
4089
|
+
async function* makeInteractivePrompt(abortController, turnGate, spinner) {
|
|
4090
|
+
while (!abortController.signal.aborted) {
|
|
4091
|
+
try {
|
|
4092
|
+
process.stderr.write("\n");
|
|
4093
|
+
const issue = await (0, import_prompts4.input)({ message: "Describe the issue to investigate:" });
|
|
4094
|
+
process.stderr.write("\n");
|
|
4095
|
+
const trimmed = issue.trim();
|
|
4096
|
+
if (!trimmed) continue;
|
|
4097
|
+
if (isExitCommand(trimmed)) return;
|
|
4098
|
+
spinner.start();
|
|
4099
|
+
yield makeUserMessage(trimmed);
|
|
4100
|
+
break;
|
|
4101
|
+
} catch {
|
|
4102
|
+
return;
|
|
4103
|
+
}
|
|
4104
|
+
}
|
|
4105
|
+
while (!abortController.signal.aborted) {
|
|
4106
|
+
try {
|
|
4107
|
+
await turnGate.wait();
|
|
4108
|
+
if (abortController.signal.aborted) break;
|
|
4109
|
+
process.stderr.write("\n");
|
|
4110
|
+
const response = await (0, import_prompts4.input)({ message: "You:" });
|
|
4111
|
+
process.stderr.write("\n");
|
|
4112
|
+
const trimmed = response.trim();
|
|
4113
|
+
if (!trimmed) continue;
|
|
4114
|
+
if (isExitCommand(trimmed)) return;
|
|
4115
|
+
turnGate.close();
|
|
4116
|
+
spinner.start();
|
|
4117
|
+
yield makeUserMessage(trimmed);
|
|
4118
|
+
} catch {
|
|
4119
|
+
return;
|
|
4120
|
+
}
|
|
4121
|
+
}
|
|
4122
|
+
}
|
|
4123
|
+
async function displayDegradedStatus(session2) {
|
|
4124
|
+
const [statusResult, alertsResult] = await Promise.all([
|
|
4125
|
+
hostManagerStatus(session2),
|
|
4126
|
+
hostManagerAlerts(session2)
|
|
4127
|
+
]);
|
|
4128
|
+
process.stderr.write(import_picocolors8.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
|
+
if (statusResult.ok) {
|
|
4130
|
+
const rows = statusResult.data;
|
|
4131
|
+
const maxKeyLen = rows.reduce((max, r) => Math.max(max, r.key.length), 0);
|
|
4132
|
+
for (const row of rows) {
|
|
4133
|
+
process.stderr.write(` ${import_picocolors8.default.dim(row.key.padEnd(maxKeyLen))} ${row.value}
|
|
4134
|
+
`);
|
|
4135
|
+
}
|
|
4136
|
+
} else {
|
|
4137
|
+
process.stderr.write(` ${import_picocolors8.default.red(`Error: ${statusResult.error}`)}
|
|
4138
|
+
`);
|
|
4139
|
+
}
|
|
4140
|
+
process.stderr.write("\n");
|
|
4141
|
+
process.stderr.write(import_picocolors8.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
|
+
if (alertsResult.ok) {
|
|
4143
|
+
const alerts = alertsResult.data;
|
|
4144
|
+
if (alerts.length === 0) {
|
|
4145
|
+
process.stderr.write(` ${import_picocolors8.default.dim("No recent alerts.")}
|
|
4146
|
+
`);
|
|
4147
|
+
} else {
|
|
4148
|
+
for (const alert of alerts) {
|
|
4149
|
+
process.stderr.write(` ${import_picocolors8.default.dim(alert.timestamp)} ${alert.type} ${alert.params}
|
|
4150
|
+
`);
|
|
4151
|
+
}
|
|
4152
|
+
}
|
|
4153
|
+
} else {
|
|
4154
|
+
process.stderr.write(` ${import_picocolors8.default.dim(`Unavailable: ${alertsResult.error}`)}
|
|
4155
|
+
`);
|
|
4156
|
+
}
|
|
4157
|
+
process.stderr.write("\n");
|
|
4158
|
+
}
|
|
4159
|
+
async function runAgent(session2, kineticaVersion, degraded, model, runOptions) {
|
|
4160
|
+
const authMethod = runOptions?.authMethod ?? "api_key";
|
|
4161
|
+
const dollarCapped = authMethod === "api_key";
|
|
4162
|
+
const resolvedBudgetUsd = runOptions?.maxBudgetUsd ?? DEFAULT_MAX_BUDGET_USD;
|
|
4163
|
+
const [catalogSchemas, playbooks, references] = await Promise.all([
|
|
4164
|
+
degraded ? Promise.resolve(void 0) : discoverCatalogSchemas(session2),
|
|
4165
|
+
loadPlaybooks(),
|
|
4166
|
+
loadReferences()
|
|
4167
|
+
]);
|
|
4168
|
+
const systemPrompt = buildSystemPrompt(
|
|
4169
|
+
kineticaVersion,
|
|
4170
|
+
catalogSchemas,
|
|
4171
|
+
playbooks,
|
|
4172
|
+
references,
|
|
4173
|
+
degraded
|
|
4174
|
+
);
|
|
4175
|
+
const budget = checkPromptBudget(systemPrompt);
|
|
4176
|
+
if (process.env.DEBUG) {
|
|
4177
|
+
process.stderr.write(
|
|
4178
|
+
import_picocolors8.default.dim(`System prompt: ~${budget.tokens} tokens (${budget.chars} chars)
|
|
4179
|
+
`)
|
|
4180
|
+
);
|
|
4181
|
+
}
|
|
4182
|
+
if (budget.overBudget) {
|
|
4183
|
+
process.stderr.write(
|
|
4184
|
+
import_picocolors8.default.yellow(
|
|
4185
|
+
`\u26A0 system prompt is ~${budget.tokens} tokens (threshold ${budget.threshold}) \u2014 knowledge corpus is getting expensive; consider keyword-based playbook selection.
|
|
4186
|
+
`
|
|
4187
|
+
)
|
|
4188
|
+
);
|
|
4189
|
+
}
|
|
4190
|
+
const diagnosticTools = makeDiagnosticTools(session2, catalogSchemas);
|
|
4191
|
+
const mutationTools = makeMutationTools(session2);
|
|
4192
|
+
const saveReportTool = makeSaveReportTool();
|
|
4193
|
+
const alterTableColumnsTool = makeAlterTableColumnsToolWithDeps(session2);
|
|
4194
|
+
const server = (0, import_claude_agent_sdk4.createSdkMcpServer)({
|
|
4195
|
+
name: MCP_SERVER_NAME,
|
|
4196
|
+
version: "1.0.0",
|
|
4197
|
+
tools: [...diagnosticTools, ...mutationTools, saveReportTool, alterTableColumnsTool]
|
|
4198
|
+
});
|
|
4199
|
+
const spinner = createSpinner();
|
|
4200
|
+
const registry = createDiagnosticRegistry();
|
|
4201
|
+
const approvalGate = createApprovalGate(registry.isReadOnlyTool);
|
|
4202
|
+
const canUseTool = async (toolName, toolInput, options2) => {
|
|
4203
|
+
spinner.stop();
|
|
4204
|
+
return approvalGate(toolName, toolInput, options2);
|
|
4205
|
+
};
|
|
4206
|
+
const abortController = new AbortController();
|
|
4207
|
+
const effectiveModel = model ?? DEFAULT_AGENT_MODEL;
|
|
4208
|
+
const options = {
|
|
4209
|
+
mcpServers: { [MCP_SERVER_NAME]: server },
|
|
4210
|
+
allowedTools: ALLOWED_TOOL_NAMES,
|
|
4211
|
+
disallowedTools: [...DISALLOWED_TOOLS],
|
|
4212
|
+
canUseTool,
|
|
4213
|
+
systemPrompt,
|
|
4214
|
+
model: effectiveModel,
|
|
4215
|
+
fallbackModel: "haiku",
|
|
4216
|
+
thinking: { type: "adaptive" },
|
|
4217
|
+
maxTurns: 100,
|
|
4218
|
+
// Only impose a dollar cap for per-token billing. For OAuth subscription users
|
|
4219
|
+
// the SDK would otherwise cut them off at a notional dollar figure they never pay;
|
|
4220
|
+
// omitting it leaves the turn limit (maxTurns) as their guard.
|
|
4221
|
+
...dollarCapped ? { maxBudgetUsd: resolvedBudgetUsd } : {},
|
|
4222
|
+
persistSession: false,
|
|
4223
|
+
includePartialMessages: true,
|
|
4224
|
+
abortController,
|
|
4225
|
+
env: { ...process.env, CLAUDE_AGENT_SDK_CLIENT_APP: "admin-agent" }
|
|
4226
|
+
};
|
|
4227
|
+
const guardLine = dollarCapped ? import_picocolors8.default.dim(`Budget guard: $${resolvedBudgetUsd.toFixed(2)} (raise with --max-budget)
|
|
4228
|
+
`) : import_picocolors8.default.dim("Budget guard: subscription (Pro/Max) \u2014 turn-limited\n");
|
|
4229
|
+
if (degraded) {
|
|
4230
|
+
process.stderr.write("\nKinetica Diagnostic Session Ready (DEGRADED MODE)\n");
|
|
4231
|
+
process.stderr.write(
|
|
4232
|
+
"DB engine (port 9191) is unreachable. Only host manager tools are available.\n\n"
|
|
4233
|
+
);
|
|
4234
|
+
await displayDegradedStatus(session2);
|
|
4235
|
+
} else {
|
|
4236
|
+
process.stderr.write("\nKinetica Diagnostic Session Ready\n");
|
|
4237
|
+
}
|
|
4238
|
+
process.stderr.write(guardLine);
|
|
4239
|
+
process.stderr.write("Type 'exit' to end the session.\n\n");
|
|
4240
|
+
const turnGate = createTurnGate();
|
|
4241
|
+
const agentQuery = (0, import_claude_agent_sdk4.query)({
|
|
4242
|
+
prompt: makeInteractivePrompt(abortController, turnGate, spinner),
|
|
4243
|
+
options
|
|
4244
|
+
});
|
|
4245
|
+
process.once("SIGINT", () => {
|
|
4246
|
+
spinner.stop();
|
|
4247
|
+
process.stderr.write("\nInterrupted \u2014 aborting investigation...\n");
|
|
4248
|
+
abortController.abort();
|
|
4249
|
+
agentQuery.close();
|
|
4250
|
+
});
|
|
4251
|
+
let numTurns = 0;
|
|
4252
|
+
let totalCostUsd = 0;
|
|
4253
|
+
let durationMs = 0;
|
|
4254
|
+
let durationApiMs = 0;
|
|
4255
|
+
let cacheReadTokens = 0;
|
|
4256
|
+
let cacheCreationTokens = 0;
|
|
4257
|
+
let lastStreamCharWasNewline = true;
|
|
4258
|
+
const tableAligner = createStreamingTableAligner();
|
|
4259
|
+
let hadNonAbortError = false;
|
|
4260
|
+
let reportSavedThisRun = false;
|
|
4261
|
+
let invBase = { turns: 0, duration: 0, api: 0, cost: 0 };
|
|
4262
|
+
const budgetTracker = dollarCapped ? createBudgetTracker({ maxUsd: resolvedBudgetUsd }) : void 0;
|
|
4263
|
+
try {
|
|
4264
|
+
for await (const message of agentQuery) {
|
|
4265
|
+
if (message.type === "stream_event") {
|
|
4266
|
+
const { event: evt } = message;
|
|
4267
|
+
if (evt.type === "content_block_delta" && evt.delta.type === "text_delta") {
|
|
4268
|
+
const text = evt.delta.text ?? "";
|
|
4269
|
+
if (text) {
|
|
4270
|
+
spinner.stop();
|
|
4271
|
+
const output = tableAligner.push(text);
|
|
4272
|
+
if (output) {
|
|
4273
|
+
process.stderr.write(output);
|
|
4274
|
+
lastStreamCharWasNewline = output.endsWith("\n");
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4277
|
+
}
|
|
4278
|
+
} else if (message.type === "assistant") {
|
|
4279
|
+
const assistantMsg = message;
|
|
4280
|
+
const remaining = tableAligner.flush();
|
|
4281
|
+
if (remaining) {
|
|
4282
|
+
process.stderr.write(remaining);
|
|
4283
|
+
lastStreamCharWasNewline = remaining.endsWith("\n");
|
|
4284
|
+
}
|
|
4285
|
+
if (!lastStreamCharWasNewline) {
|
|
4286
|
+
process.stderr.write("\n");
|
|
4287
|
+
lastStreamCharWasNewline = true;
|
|
4288
|
+
}
|
|
4289
|
+
if (budgetTracker) {
|
|
4290
|
+
budgetTracker.add(fromSdkUsage(assistantMsg.message.usage), effectiveModel);
|
|
4291
|
+
if (budgetTracker.shouldWarn()) {
|
|
4292
|
+
spinner.stop();
|
|
4293
|
+
process.stderr.write(
|
|
4294
|
+
import_picocolors8.default.yellow(
|
|
4295
|
+
`
|
|
4296
|
+
\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
|
+
`
|
|
4298
|
+
)
|
|
4299
|
+
);
|
|
4300
|
+
budgetTracker.markWarned();
|
|
4301
|
+
}
|
|
4302
|
+
}
|
|
4303
|
+
if (contentCallsSaveReport(assistantMsg.message.content)) {
|
|
4304
|
+
reportSavedThisRun = true;
|
|
4305
|
+
}
|
|
4306
|
+
if (assistantMsg.message.stop_reason === "end_turn") {
|
|
4307
|
+
spinner.stop();
|
|
4308
|
+
turnGate.open();
|
|
4309
|
+
} else if (assistantMsg.message.stop_reason === "tool_use") {
|
|
4310
|
+
spinner.start("Investigating");
|
|
4311
|
+
} else {
|
|
4312
|
+
spinner.stop();
|
|
4313
|
+
}
|
|
4314
|
+
if (assistantMsg.error) {
|
|
4315
|
+
spinner.stop();
|
|
4316
|
+
const label = ERROR_LABELS[assistantMsg.error] ?? assistantMsg.error;
|
|
4317
|
+
process.stderr.write(import_picocolors8.default.yellow(`
|
|
4318
|
+
API error: ${label}
|
|
4319
|
+
`));
|
|
4320
|
+
turnGate.open();
|
|
4321
|
+
}
|
|
4322
|
+
} else if (message.type === "result") {
|
|
4323
|
+
spinner.stop();
|
|
4324
|
+
const resultMsg = message;
|
|
4325
|
+
numTurns = resultMsg.num_turns;
|
|
4326
|
+
totalCostUsd = resultMsg.total_cost_usd;
|
|
4327
|
+
durationMs = resultMsg.duration_ms;
|
|
4328
|
+
durationApiMs = resultMsg.duration_api_ms;
|
|
4329
|
+
const usages = Object.values(resultMsg.modelUsage ?? {});
|
|
4330
|
+
cacheReadTokens = usages.reduce((sum, u) => sum + (u.cacheReadInputTokens ?? 0), 0);
|
|
4331
|
+
cacheCreationTokens = usages.reduce((sum, u) => sum + (u.cacheCreationInputTokens ?? 0), 0);
|
|
4332
|
+
if (resultMsg.subtype === "error_max_turns") {
|
|
4333
|
+
process.stderr.write(
|
|
4334
|
+
import_picocolors8.default.yellow(
|
|
4335
|
+
`
|
|
4336
|
+
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
|
+
`
|
|
4338
|
+
)
|
|
4339
|
+
);
|
|
4340
|
+
} else if (resultMsg.subtype === "error_max_budget_usd") {
|
|
4341
|
+
const spentStr = totalCostUsd > 0 ? ` ($${totalCostUsd.toFixed(2)} spent)` : "";
|
|
4342
|
+
process.stderr.write(
|
|
4343
|
+
import_picocolors8.default.yellow(
|
|
4344
|
+
`
|
|
4345
|
+
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
|
+
`
|
|
4347
|
+
)
|
|
4348
|
+
);
|
|
4349
|
+
} else if (resultMsg.subtype === "error_during_execution") {
|
|
4350
|
+
process.stderr.write(
|
|
4351
|
+
"\nExecution error \u2014 the agent encountered an unrecoverable failure.\n"
|
|
4352
|
+
);
|
|
4353
|
+
} else if (resultMsg.subtype !== "success") {
|
|
4354
|
+
process.stderr.write(`
|
|
4355
|
+
Agent session ended with error: ${resultMsg.subtype}
|
|
4356
|
+
`);
|
|
4357
|
+
}
|
|
4358
|
+
if (resultMsg.permission_denials.length > 0) {
|
|
4359
|
+
const denied = resultMsg.permission_denials.map((d) => d.tool_name).join(", ");
|
|
4360
|
+
process.stderr.write(`
|
|
4361
|
+
Permission denials: ${denied}
|
|
4362
|
+
`);
|
|
4363
|
+
}
|
|
4364
|
+
if (reportSavedThisRun) {
|
|
4365
|
+
const line = formatMetricsLine(
|
|
4366
|
+
numTurns - invBase.turns,
|
|
4367
|
+
durationMs - invBase.duration,
|
|
4368
|
+
durationApiMs - invBase.api,
|
|
4369
|
+
dollarCapped ? totalCostUsd - invBase.cost : void 0
|
|
4370
|
+
);
|
|
4371
|
+
process.stderr.write(`
|
|
4372
|
+
Investigation complete \u2014 ${line}
|
|
4373
|
+
`);
|
|
4374
|
+
invBase = {
|
|
4375
|
+
turns: numTurns,
|
|
4376
|
+
duration: durationMs,
|
|
4377
|
+
api: durationApiMs,
|
|
4378
|
+
cost: totalCostUsd
|
|
4379
|
+
};
|
|
4380
|
+
reportSavedThisRun = false;
|
|
4381
|
+
}
|
|
4382
|
+
turnGate.open();
|
|
4383
|
+
} else if (message.type === "system") {
|
|
4384
|
+
const sysMsg = message;
|
|
4385
|
+
if (sysMsg.subtype === "init") {
|
|
4386
|
+
const initMsg = message;
|
|
4387
|
+
const failed = (initMsg.mcp_servers ?? []).filter(
|
|
4388
|
+
(s) => s.name === MCP_SERVER_NAME && s.status !== "connected"
|
|
4389
|
+
);
|
|
4390
|
+
for (const s of failed) {
|
|
4391
|
+
process.stderr.write(
|
|
4392
|
+
`
|
|
4393
|
+
Warning: MCP server "${s.name}" failed to connect (${s.status})
|
|
4394
|
+
`
|
|
4395
|
+
);
|
|
4396
|
+
}
|
|
4397
|
+
} else if (sysMsg.subtype === "api_retry") {
|
|
4398
|
+
const retryMsg = message;
|
|
4399
|
+
const statusStr = retryMsg.error_status !== null ? ` (HTTP ${retryMsg.error_status})` : "";
|
|
4400
|
+
const delaySec = Math.round(retryMsg.retry_delay_ms / 1e3);
|
|
4401
|
+
process.stderr.write(
|
|
4402
|
+
import_picocolors8.default.yellow(
|
|
4403
|
+
`
|
|
4404
|
+
API error${statusStr}. Retrying (attempt ${retryMsg.attempt}/${retryMsg.max_retries}) in ${delaySec}s...
|
|
4405
|
+
`
|
|
4406
|
+
)
|
|
4407
|
+
);
|
|
4408
|
+
} else if (sysMsg.subtype === "compact_boundary") {
|
|
4409
|
+
const compactMsg = message;
|
|
4410
|
+
const preTokens = compactMsg.compact_metadata.pre_tokens;
|
|
4411
|
+
process.stderr.write(
|
|
4412
|
+
`
|
|
4413
|
+
[Context compressed (${preTokens} tokens before compaction) \u2014 investigation continues]
|
|
4414
|
+
`
|
|
4415
|
+
);
|
|
4416
|
+
}
|
|
4417
|
+
} else if (message.type === "rate_limit_event") {
|
|
4418
|
+
const rateMsg = message;
|
|
4419
|
+
const { status, resetsAt } = rateMsg.rate_limit_info;
|
|
4420
|
+
if (status === "rejected") {
|
|
4421
|
+
const resetStr = resetsAt ? ` Resets at ${new Date(resetsAt * 1e3).toISOString()}.` : "";
|
|
4422
|
+
process.stderr.write(`
|
|
4423
|
+
Rate limited \u2014 requests rejected.${resetStr}
|
|
4424
|
+
`);
|
|
4425
|
+
} else if (status === "allowed_warning") {
|
|
4426
|
+
process.stderr.write("\nApproaching rate limit \u2014 investigation may slow.\n");
|
|
4427
|
+
}
|
|
4428
|
+
} else if (message.type === "control_request") {
|
|
4429
|
+
const controlMsg = message;
|
|
4430
|
+
if (controlMsg.request.subtype === "claude_authenticate") {
|
|
4431
|
+
process.stderr.write(import_picocolors8.default.yellow("\nRe-authentication requested by SDK...\n"));
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
} catch (error) {
|
|
4436
|
+
spinner.stop();
|
|
4437
|
+
if (error instanceof import_claude_agent_sdk4.AbortError) {
|
|
4438
|
+
hadNonAbortError = false;
|
|
4439
|
+
} else {
|
|
4440
|
+
hadNonAbortError = true;
|
|
4441
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4442
|
+
process.stderr.write(import_picocolors8.default.red(`
|
|
4443
|
+
Agent error: ${message}
|
|
4444
|
+
`));
|
|
4445
|
+
}
|
|
4446
|
+
} finally {
|
|
4447
|
+
spinner.stop();
|
|
4448
|
+
const remaining = tableAligner.flush();
|
|
4449
|
+
if (remaining) {
|
|
4450
|
+
process.stderr.write(remaining);
|
|
4451
|
+
}
|
|
4452
|
+
turnGate.open();
|
|
4453
|
+
const sessionCost = dollarCapped ? totalCostUsd : void 0;
|
|
4454
|
+
if (process.env.DEBUG && (cacheReadTokens > 0 || cacheCreationTokens > 0)) {
|
|
4455
|
+
process.stderr.write(
|
|
4456
|
+
import_picocolors8.default.dim(
|
|
4457
|
+
`Cache: ${cacheReadTokens} read / ${cacheCreationTokens} created input tokens (read > 0 confirms the system prompt is served from cache)
|
|
4458
|
+
`
|
|
4459
|
+
)
|
|
4460
|
+
);
|
|
4461
|
+
}
|
|
4462
|
+
if (hadNonAbortError) {
|
|
4463
|
+
process.stderr.write(
|
|
4464
|
+
`
|
|
4465
|
+
Session ended due to error. Turns: ${numTurns}.${formatCostSuffix(sessionCost)}
|
|
4466
|
+
`
|
|
4467
|
+
);
|
|
4468
|
+
} else {
|
|
4469
|
+
const line = formatMetricsLine(numTurns, durationMs, durationApiMs, sessionCost);
|
|
4470
|
+
process.stderr.write(`
|
|
4471
|
+
Session ended. ${line}
|
|
4472
|
+
`);
|
|
4473
|
+
}
|
|
4193
4474
|
}
|
|
4194
4475
|
}
|
|
4195
4476
|
|
|
4196
|
-
// src/
|
|
4197
|
-
var
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
chars: prompt ? prompt.length : 0,
|
|
4209
|
-
threshold,
|
|
4210
|
-
overBudget: tokens > threshold
|
|
4211
|
-
};
|
|
4477
|
+
// src/cli/select-model.ts
|
|
4478
|
+
var MODEL_LABELS = {
|
|
4479
|
+
sonnet: "Sonnet \u2014 balanced, best general coding (default)",
|
|
4480
|
+
haiku: "Haiku \u2014 fastest & cheapest, lighter reasoning",
|
|
4481
|
+
opus: "Opus \u2014 deepest reasoning, slower & pricier"
|
|
4482
|
+
};
|
|
4483
|
+
async function selectModel() {
|
|
4484
|
+
return (0, import_prompts5.select)({
|
|
4485
|
+
message: "Select model for this session:",
|
|
4486
|
+
default: DEFAULT_AGENT_MODEL,
|
|
4487
|
+
choices: SUPPORTED_MODELS.map((value) => ({ value, name: MODEL_LABELS[value] }))
|
|
4488
|
+
});
|
|
4212
4489
|
}
|
|
4213
4490
|
|
|
4214
|
-
// src/
|
|
4215
|
-
var
|
|
4216
|
-
var import_node_path5 = require("path");
|
|
4217
|
-
var import_claude_agent_sdk4 = require("@anthropic-ai/claude-agent-sdk");
|
|
4218
|
-
var import_zod17 = require("zod");
|
|
4219
|
-
var PARTIAL_MARKER = "(PARTIAL -- investigation interrupted)\n\n";
|
|
4220
|
-
function formatTimestamp(date) {
|
|
4221
|
-
const year = date.getUTCFullYear();
|
|
4222
|
-
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
4223
|
-
const day = String(date.getUTCDate()).padStart(2, "0");
|
|
4224
|
-
const hours = String(date.getUTCHours()).padStart(2, "0");
|
|
4225
|
-
const minutes = String(date.getUTCMinutes()).padStart(2, "0");
|
|
4226
|
-
const seconds = String(date.getUTCSeconds()).padStart(2, "0");
|
|
4227
|
-
return `${year}-${month}-${day}-${hours}${minutes}${seconds}`;
|
|
4228
|
-
}
|
|
4229
|
-
function makeSaveReportTool() {
|
|
4230
|
-
return (0, import_claude_agent_sdk4.tool)(
|
|
4231
|
-
"save_report",
|
|
4232
|
-
"Save a diagnostic report to disk. Automatically scrubs credentials, creates a timestamped filename in reports/, and auto-creates the directory. Use at the end of each investigation or when interrupted.",
|
|
4233
|
-
{
|
|
4234
|
-
content: import_zod17.z.string().describe("The full markdown diagnostic report content"),
|
|
4235
|
-
partial: import_zod17.z.boolean().optional().describe(
|
|
4236
|
-
"Set to true if the investigation was interrupted (e.g., Ctrl+C). Prepends a PARTIAL marker to the report."
|
|
4237
|
-
)
|
|
4238
|
-
},
|
|
4239
|
-
async (args) => {
|
|
4240
|
-
const rawContent = args.partial ? `${PARTIAL_MARKER}${args.content}` : args.content;
|
|
4241
|
-
const scrubbed = scrubCredentials(rawContent);
|
|
4242
|
-
const timestamp = formatTimestamp(/* @__PURE__ */ new Date());
|
|
4243
|
-
const filename = `kinetica-diag-${timestamp}.md`;
|
|
4244
|
-
const dir = (0, import_node_path5.resolve)(process.cwd(), "reports");
|
|
4245
|
-
await (0, import_promises4.mkdir)(dir, { recursive: true });
|
|
4246
|
-
const filepath = (0, import_node_path5.join)(dir, filename);
|
|
4247
|
-
await (0, import_promises4.writeFile)(filepath, scrubbed, "utf-8");
|
|
4248
|
-
return {
|
|
4249
|
-
content: [{ type: "text", text: `Report saved: ${filepath}` }]
|
|
4250
|
-
};
|
|
4251
|
-
},
|
|
4252
|
-
{ annotations: { readOnly: true } }
|
|
4253
|
-
);
|
|
4254
|
-
}
|
|
4491
|
+
// src/auth/preflight.ts
|
|
4492
|
+
var import_claude_agent_sdk5 = require("@anthropic-ai/claude-agent-sdk");
|
|
4255
4493
|
|
|
4256
|
-
// src/
|
|
4257
|
-
var
|
|
4494
|
+
// src/auth/oauth-flow.ts
|
|
4495
|
+
var import_picocolors9 = __toESM(require("picocolors"));
|
|
4258
4496
|
|
|
4259
|
-
// src/
|
|
4260
|
-
var
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4497
|
+
// src/auth/open-browser.ts
|
|
4498
|
+
var import_child_process = require("child_process");
|
|
4499
|
+
function openBrowser(url) {
|
|
4500
|
+
try {
|
|
4501
|
+
const platform = process.platform;
|
|
4502
|
+
const { command, args } = platform === "darwin" ? { command: "open", args: [url] } : platform === "win32" ? { command: "cmd", args: ["/c", "start", "", url] } : { command: "xdg-open", args: [url] };
|
|
4503
|
+
const child = (0, import_child_process.spawn)(command, args, { detached: true, stdio: "ignore" });
|
|
4504
|
+
child.unref();
|
|
4505
|
+
return true;
|
|
4506
|
+
} catch {
|
|
4507
|
+
return false;
|
|
4508
|
+
}
|
|
4266
4509
|
}
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
const formatted = typeof value === "string" ? value : JSON.stringify(value, null, 2);
|
|
4273
|
-
return ` ${import_picocolors10.default.dim(key)}: ${formatted}`;
|
|
4274
|
-
}).join("\n");
|
|
4275
|
-
const impactLine = `${formatLabel("Impact")}${impact ?? IMPACT_FALLBACK}`;
|
|
4276
|
-
const prompt = import_picocolors10.default.dim(
|
|
4277
|
-
`${formatLabel("Respond")}y (proceed) | n (abort) | explain (show reasoning)`
|
|
4278
|
-
);
|
|
4279
|
-
const hasBeforeAfter = beforeAfter !== void 0 && beforeAfter.length > 0;
|
|
4280
|
-
const beforeAfterSection = hasBeforeAfter ? beforeAfter.map(
|
|
4281
|
-
(entry) => ` ${import_picocolors10.default.dim(entry.key)}: ${entry.current} ${import_picocolors10.default.yellow("->")} ${entry.proposed}`
|
|
4282
|
-
).join("\n") : null;
|
|
4283
|
-
const hasReasoning = reasoningSummary !== void 0 && reasoningSummary.length > 0;
|
|
4284
|
-
const reasoningSection = hasReasoning ? `${formatLabel("Reason")}${reasoningSummary}` : null;
|
|
4285
|
-
const sections = ["", DIVIDER2, header, "", action, paramSection, ""];
|
|
4286
|
-
if (beforeAfterSection !== null) {
|
|
4287
|
-
sections.push(beforeAfterSection, "");
|
|
4510
|
+
|
|
4511
|
+
// src/auth/oauth-flow.ts
|
|
4512
|
+
async function resolveAuthentication(agentQuery, options) {
|
|
4513
|
+
if (options.hasApiKey && !options.forceLogin) {
|
|
4514
|
+
return { method: "api_key" };
|
|
4288
4515
|
}
|
|
4289
|
-
|
|
4290
|
-
|
|
4516
|
+
const query3 = agentQuery;
|
|
4517
|
+
try {
|
|
4518
|
+
const { manualUrl, automaticUrl } = await query3.claudeAuthenticate(options.loginWithClaudeAi);
|
|
4519
|
+
const opened = openBrowser(automaticUrl);
|
|
4520
|
+
if (opened) {
|
|
4521
|
+
process.stderr.write(import_picocolors9.default.dim("Browser opened for login. Waiting for authentication...\n"));
|
|
4522
|
+
} else {
|
|
4523
|
+
process.stderr.write(`
|
|
4524
|
+
Open this URL in your browser to log in:
|
|
4525
|
+
${import_picocolors9.default.bold(manualUrl)}
|
|
4526
|
+
|
|
4527
|
+
`);
|
|
4528
|
+
process.stderr.write(import_picocolors9.default.dim("Waiting for browser login to complete...\n"));
|
|
4529
|
+
}
|
|
4530
|
+
await query3.claudeOAuthWaitForCompletion();
|
|
4531
|
+
return { method: "oauth" };
|
|
4532
|
+
} catch (err) {
|
|
4533
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4534
|
+
process.stderr.write(
|
|
4535
|
+
import_picocolors9.default.yellow(`
|
|
4536
|
+
Warning: OAuth login failed (${message}). SDK may retry automatically.
|
|
4537
|
+
`)
|
|
4538
|
+
);
|
|
4539
|
+
return { method: "oauth" };
|
|
4291
4540
|
}
|
|
4292
|
-
sections.push(impactLine, "", prompt, DIVIDER2, "");
|
|
4293
|
-
return sections.join("\n");
|
|
4294
4541
|
}
|
|
4295
4542
|
|
|
4296
|
-
// src/
|
|
4297
|
-
var
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4543
|
+
// src/auth/preflight.ts
|
|
4544
|
+
var PROBE_TIMEOUT_MS = 1e4;
|
|
4545
|
+
async function probeCachedCredentials(authQuery) {
|
|
4546
|
+
try {
|
|
4547
|
+
const info = await Promise.race([
|
|
4548
|
+
authQuery.accountInfo(),
|
|
4549
|
+
new Promise(
|
|
4550
|
+
(_, reject) => setTimeout(() => reject(new Error("Probe timed out")), PROBE_TIMEOUT_MS)
|
|
4551
|
+
)
|
|
4552
|
+
]);
|
|
4553
|
+
if (info.email || info.apiKeySource) {
|
|
4554
|
+
return info;
|
|
4307
4555
|
}
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4312
|
-
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
toolUseID: options.toolUseID
|
|
4348
|
-
};
|
|
4556
|
+
return null;
|
|
4557
|
+
} catch {
|
|
4558
|
+
return null;
|
|
4559
|
+
}
|
|
4560
|
+
}
|
|
4561
|
+
async function authenticateAnthropic(options) {
|
|
4562
|
+
const hasApiKey = Boolean(process.env.ANTHROPIC_API_KEY);
|
|
4563
|
+
if (hasApiKey && !options.forceLogin) {
|
|
4564
|
+
return { method: "api_key" };
|
|
4565
|
+
}
|
|
4566
|
+
if (!process.stdin.isTTY) {
|
|
4567
|
+
throw new Error(
|
|
4568
|
+
"No ANTHROPIC_API_KEY set and terminal is non-interactive. Set ANTHROPIC_API_KEY or run in an interactive terminal with --login."
|
|
4569
|
+
);
|
|
4570
|
+
}
|
|
4571
|
+
const env = options.forceLogin ? (() => {
|
|
4572
|
+
const { ANTHROPIC_API_KEY: _stripped, ...rest } = process.env;
|
|
4573
|
+
return { ...rest, CLAUDE_AGENT_SDK_CLIENT_APP: "admin-agent" };
|
|
4574
|
+
})() : { ...process.env, CLAUDE_AGENT_SDK_CLIENT_APP: "admin-agent" };
|
|
4575
|
+
const abortController = new AbortController();
|
|
4576
|
+
async function* hangingPrompt() {
|
|
4577
|
+
await new Promise(() => {
|
|
4578
|
+
});
|
|
4579
|
+
}
|
|
4580
|
+
const authQuery = (0, import_claude_agent_sdk5.query)({
|
|
4581
|
+
prompt: hangingPrompt(),
|
|
4582
|
+
options: {
|
|
4583
|
+
persistSession: false,
|
|
4584
|
+
abortController,
|
|
4585
|
+
env,
|
|
4586
|
+
...options.loginMethod ? { forceLoginMethod: options.loginMethod } : {},
|
|
4587
|
+
...options.loginOrgUUID ? { forceLoginOrgUUID: options.loginOrgUUID } : {}
|
|
4588
|
+
}
|
|
4589
|
+
});
|
|
4590
|
+
try {
|
|
4591
|
+
if (!options.forceLogin) {
|
|
4592
|
+
const cached = await probeCachedCredentials(authQuery);
|
|
4593
|
+
if (cached) {
|
|
4594
|
+
return { method: "oauth", email: cached.email };
|
|
4349
4595
|
}
|
|
4350
4596
|
}
|
|
4351
|
-
|
|
4597
|
+
return await resolveAuthentication(authQuery, {
|
|
4598
|
+
forceLogin: options.forceLogin,
|
|
4599
|
+
loginWithClaudeAi: (options.loginMethod ?? "claudeai") === "claudeai",
|
|
4600
|
+
hasApiKey
|
|
4601
|
+
});
|
|
4602
|
+
} finally {
|
|
4603
|
+
abortController.abort();
|
|
4604
|
+
await authQuery.return().catch(() => {
|
|
4605
|
+
});
|
|
4606
|
+
}
|
|
4607
|
+
}
|
|
4608
|
+
|
|
4609
|
+
// src/auth/logout.ts
|
|
4610
|
+
var import_child_process2 = require("child_process");
|
|
4611
|
+
var import_node_module = require("module");
|
|
4612
|
+
var import_node_path5 = __toESM(require("path"));
|
|
4613
|
+
var import_util = require("util");
|
|
4614
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
|
|
4615
|
+
function resolveSdkCliPath() {
|
|
4616
|
+
const require_ = (0, import_node_module.createRequire)(__filename);
|
|
4617
|
+
return import_node_path5.default.join(import_node_path5.default.dirname(require_.resolve("@anthropic-ai/claude-agent-sdk")), "cli.js");
|
|
4618
|
+
}
|
|
4619
|
+
async function logout() {
|
|
4620
|
+
try {
|
|
4621
|
+
const sdkCliPath = resolveSdkCliPath();
|
|
4622
|
+
const { stdout, stderr } = await execFileAsync(process.execPath, [
|
|
4623
|
+
sdkCliPath,
|
|
4624
|
+
"auth",
|
|
4625
|
+
"logout"
|
|
4626
|
+
]);
|
|
4627
|
+
const output = (stdout || stderr || "").trim();
|
|
4628
|
+
return { success: true, message: output || "Logged out successfully." };
|
|
4629
|
+
} catch (err) {
|
|
4630
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4631
|
+
return { success: false, message: `Logout failed: ${message}` };
|
|
4632
|
+
}
|
|
4352
4633
|
}
|
|
4353
4634
|
|
|
4354
|
-
// src/
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4635
|
+
// src/session/env-file.ts
|
|
4636
|
+
var import_fs2 = require("fs");
|
|
4637
|
+
var import_promises4 = require("fs/promises");
|
|
4638
|
+
var import_path2 = require("path");
|
|
4639
|
+
var import_prompts6 = require("@inquirer/prompts");
|
|
4640
|
+
var import_picocolors10 = __toESM(require("picocolors"));
|
|
4641
|
+
function parseEnvContent(content) {
|
|
4642
|
+
const entries = [];
|
|
4643
|
+
for (const line of content.split("\n")) {
|
|
4644
|
+
const trimmed = line.trim();
|
|
4645
|
+
if (trimmed === "" || trimmed.startsWith("#")) continue;
|
|
4646
|
+
const match = /^([A-Za-z_]\w*)=(.*)/.exec(trimmed);
|
|
4647
|
+
if (!match) continue;
|
|
4648
|
+
const key = match[1];
|
|
4649
|
+
let value = match[2];
|
|
4650
|
+
if (value.startsWith('"') && value.endsWith('"') && value.length >= 2) {
|
|
4651
|
+
value = value.slice(1, -1).replace(/\\(["\\])/g, "$1");
|
|
4652
|
+
} else if (value.startsWith("'") && value.endsWith("'") && value.length >= 2) {
|
|
4653
|
+
value = value.slice(1, -1);
|
|
4370
4654
|
}
|
|
4371
|
-
|
|
4655
|
+
entries.push([key, value]);
|
|
4656
|
+
}
|
|
4657
|
+
return new Map(entries);
|
|
4372
4658
|
}
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4380
|
-
if (headingMatch) {
|
|
4381
|
-
return import_picocolors11.default.bold(headingMatch[2]);
|
|
4659
|
+
var FORBIDDEN_VALUE_CHARS = /[\n\r\0]/;
|
|
4660
|
+
var REQUIRES_QUOTING = /[\s"'#]/;
|
|
4661
|
+
function escapeEnvValue(value) {
|
|
4662
|
+
if (FORBIDDEN_VALUE_CHARS.test(value)) {
|
|
4663
|
+
throw new Error(
|
|
4664
|
+
"Env value contains a forbidden character (newline or null byte); refusing to write"
|
|
4665
|
+
);
|
|
4382
4666
|
}
|
|
4383
|
-
if (
|
|
4384
|
-
return
|
|
4667
|
+
if (!REQUIRES_QUOTING.test(value)) {
|
|
4668
|
+
return value;
|
|
4385
4669
|
}
|
|
4386
|
-
|
|
4670
|
+
const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
4671
|
+
return `"${escaped}"`;
|
|
4387
4672
|
}
|
|
4673
|
+
var ENV_TEMPLATE = `# Anthropic API key (optional \u2014 if not set, OAuth login via browser is used)
|
|
4674
|
+
ANTHROPIC_API_KEY=
|
|
4388
4675
|
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
function
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
}
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
const padded = [...row];
|
|
4409
|
-
while (padded.length < colCount) {
|
|
4410
|
-
padded.push("");
|
|
4676
|
+
# Kinetica connection details (prompted interactively if not set)
|
|
4677
|
+
# If password is omitted, the agent will prompt for it at startup
|
|
4678
|
+
KINETICA_URL={url}
|
|
4679
|
+
KINETICA_USER={user}
|
|
4680
|
+
KINETICA_PASS=
|
|
4681
|
+
`;
|
|
4682
|
+
function buildEnvContent(url, user, existingContent) {
|
|
4683
|
+
const safeUrl = escapeEnvValue(url);
|
|
4684
|
+
const safeUser = escapeEnvValue(user);
|
|
4685
|
+
if (!existingContent?.trim()) {
|
|
4686
|
+
return ENV_TEMPLATE.replace("{url}", safeUrl).replace("{user}", safeUser);
|
|
4687
|
+
}
|
|
4688
|
+
const lines = existingContent.split("\n");
|
|
4689
|
+
let urlReplaced = false;
|
|
4690
|
+
let userReplaced = false;
|
|
4691
|
+
const updated = lines.map((line) => {
|
|
4692
|
+
if (/^KINETICA_URL=/.exec(line)) {
|
|
4693
|
+
urlReplaced = true;
|
|
4694
|
+
return `KINETICA_URL=${safeUrl}`;
|
|
4411
4695
|
}
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
{ length: colCount },
|
|
4416
|
-
(_, col) => Math.max(
|
|
4417
|
-
3,
|
|
4418
|
-
...normalised.filter((row) => !isSeparatorRow(row)).map((row) => visualWidth(row[col]))
|
|
4419
|
-
)
|
|
4420
|
-
);
|
|
4421
|
-
const borderRow = `+${colWidths.map((w) => "-".repeat(w + 2)).join("+")}+`;
|
|
4422
|
-
const bodyRows = normalised.map((row) => {
|
|
4423
|
-
if (isSeparatorRow(row)) {
|
|
4424
|
-
return borderRow;
|
|
4696
|
+
if (/^KINETICA_USER=/.exec(line)) {
|
|
4697
|
+
userReplaced = true;
|
|
4698
|
+
return `KINETICA_USER=${safeUser}`;
|
|
4425
4699
|
}
|
|
4426
|
-
|
|
4427
|
-
const rendered = renderMarkdownLine(cell);
|
|
4428
|
-
const pad = colWidths[col] - visualWidth(cell);
|
|
4429
|
-
return rendered + " ".repeat(Math.max(0, pad));
|
|
4430
|
-
});
|
|
4431
|
-
return `| ${cells.join(" | ")} |`;
|
|
4700
|
+
return line;
|
|
4432
4701
|
});
|
|
4433
|
-
|
|
4702
|
+
if (!urlReplaced) updated.push(`KINETICA_URL=${safeUrl}`);
|
|
4703
|
+
if (!userReplaced) updated.push(`KINETICA_USER=${safeUser}`);
|
|
4704
|
+
return updated.join("\n");
|
|
4434
4705
|
}
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
const aligned = reformatTableBlock(tableLines);
|
|
4444
|
-
tableLines = [];
|
|
4445
|
-
return aligned.join("\n") + "\n";
|
|
4446
|
-
}
|
|
4447
|
-
function push(text) {
|
|
4448
|
-
if (!text) return "";
|
|
4449
|
-
const combined = lineBuffer + text;
|
|
4450
|
-
const segments = combined.split("\n");
|
|
4451
|
-
lineBuffer = segments[segments.length - 1];
|
|
4452
|
-
const completeLines = segments.slice(0, -1);
|
|
4453
|
-
let output = "";
|
|
4454
|
-
for (const line of completeLines) {
|
|
4455
|
-
const trimmed = line.trim();
|
|
4456
|
-
if (TABLE_LINE_RE.test(trimmed)) {
|
|
4457
|
-
tableLines.push(trimmed);
|
|
4458
|
-
} else {
|
|
4459
|
-
output += flushTable();
|
|
4460
|
-
output += renderMarkdownLine(line) + "\n";
|
|
4706
|
+
function loadEnvFile(dir, env = process.env) {
|
|
4707
|
+
try {
|
|
4708
|
+
const filePath = (0, import_path2.join)(dir ?? process.cwd(), ".env");
|
|
4709
|
+
const content = (0, import_fs2.readFileSync)(filePath, "utf8");
|
|
4710
|
+
const parsed = parseEnvContent(content);
|
|
4711
|
+
for (const [key, value] of parsed) {
|
|
4712
|
+
if (env[key] === void 0 && value !== "") {
|
|
4713
|
+
env[key] = value;
|
|
4461
4714
|
}
|
|
4462
4715
|
}
|
|
4463
|
-
|
|
4464
|
-
}
|
|
4465
|
-
function flush() {
|
|
4466
|
-
let output = flushTable();
|
|
4467
|
-
if (lineBuffer) {
|
|
4468
|
-
output += renderMarkdownLine(lineBuffer);
|
|
4469
|
-
lineBuffer = "";
|
|
4470
|
-
}
|
|
4471
|
-
return output;
|
|
4716
|
+
} catch {
|
|
4472
4717
|
}
|
|
4473
|
-
return Object.freeze({ push, flush });
|
|
4474
|
-
}
|
|
4475
|
-
|
|
4476
|
-
// src/output/spinner.ts
|
|
4477
|
-
var import_picocolors12 = __toESM(require("picocolors"));
|
|
4478
|
-
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4479
|
-
var FRAME_INTERVAL_MS = 80;
|
|
4480
|
-
var DEFAULT_LABEL = "Thinking";
|
|
4481
|
-
function createSpinner() {
|
|
4482
|
-
let timer = null;
|
|
4483
|
-
let frameIndex = 0;
|
|
4484
|
-
const start = (label = DEFAULT_LABEL) => {
|
|
4485
|
-
if (timer !== null) return;
|
|
4486
|
-
frameIndex = 0;
|
|
4487
|
-
timer = setInterval(() => {
|
|
4488
|
-
const frame = FRAMES[frameIndex % FRAMES.length];
|
|
4489
|
-
process.stderr.write(`\r${import_picocolors12.default.dim(`${frame} ${label}...`)}`);
|
|
4490
|
-
frameIndex += 1;
|
|
4491
|
-
}, FRAME_INTERVAL_MS);
|
|
4492
|
-
timer.unref();
|
|
4493
|
-
};
|
|
4494
|
-
const stop = () => {
|
|
4495
|
-
if (timer === null) return;
|
|
4496
|
-
clearInterval(timer);
|
|
4497
|
-
timer = null;
|
|
4498
|
-
process.stderr.write("\r\x1B[K");
|
|
4499
|
-
};
|
|
4500
|
-
const isRunning = () => timer !== null;
|
|
4501
|
-
return Object.freeze({ start, stop, isRunning });
|
|
4502
|
-
}
|
|
4503
|
-
|
|
4504
|
-
// src/agent/run-agent.ts
|
|
4505
|
-
var MCP_SERVER_NAME = "kinetica-diagnostics";
|
|
4506
|
-
var EXIT_COMMANDS = /* @__PURE__ */ new Set(["exit", "quit", "end", "q"]);
|
|
4507
|
-
var SUPPORTED_MODELS = ["sonnet", "haiku", "opus"];
|
|
4508
|
-
var DEFAULT_AGENT_MODEL = "sonnet";
|
|
4509
|
-
var DEFAULT_MAX_BUDGET_USD = 5;
|
|
4510
|
-
var ALLOWED_TOOL_NAMES = [
|
|
4511
|
-
...DIAGNOSTIC_TOOL_NAMES.map((name) => `mcp__${MCP_SERVER_NAME}__${name}`),
|
|
4512
|
-
`mcp__${MCP_SERVER_NAME}__save_report`,
|
|
4513
|
-
`mcp__${MCP_SERVER_NAME}__${ALTER_TABLE_COLUMNS_TOOL_NAME}`
|
|
4514
|
-
];
|
|
4515
|
-
var DISALLOWED_TOOLS = ["Bash", "Edit", "Write", "MultiEdit"];
|
|
4516
|
-
var ERROR_LABELS = {
|
|
4517
|
-
authentication_failed: "Authentication failed \u2014 check your API key or re-run with --login",
|
|
4518
|
-
billing_error: "Billing error \u2014 check your Anthropic account",
|
|
4519
|
-
rate_limit: "Rate limit exceeded",
|
|
4520
|
-
server_error: "Anthropic API server error",
|
|
4521
|
-
invalid_request: "Invalid API request",
|
|
4522
|
-
max_output_tokens: "Response exceeded maximum output length",
|
|
4523
|
-
unknown: "Unknown API error"
|
|
4524
|
-
};
|
|
4525
|
-
function isExitCommand(text) {
|
|
4526
|
-
return EXIT_COMMANDS.has(text.trim().toLowerCase());
|
|
4527
|
-
}
|
|
4528
|
-
function makeUserMessage(content) {
|
|
4529
|
-
return {
|
|
4530
|
-
type: "user",
|
|
4531
|
-
message: { role: "user", content },
|
|
4532
|
-
parent_tool_use_id: null,
|
|
4533
|
-
session_id: ""
|
|
4534
|
-
};
|
|
4535
4718
|
}
|
|
4536
|
-
async function
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
yield makeUserMessage(trimmed);
|
|
4547
|
-
break;
|
|
4548
|
-
} catch {
|
|
4549
|
-
return;
|
|
4550
|
-
}
|
|
4551
|
-
}
|
|
4552
|
-
while (!abortController.signal.aborted) {
|
|
4719
|
+
async function offerSaveCredentials(url, user, dir) {
|
|
4720
|
+
if (!process.stdin.isTTY) return;
|
|
4721
|
+
try {
|
|
4722
|
+
const shouldSave = await (0, import_prompts6.confirm)({
|
|
4723
|
+
message: "Save KINETICA_URL and KINETICA_USER to .env? (password is never saved)",
|
|
4724
|
+
default: true
|
|
4725
|
+
});
|
|
4726
|
+
if (!shouldSave) return;
|
|
4727
|
+
const filePath = (0, import_path2.join)(dir ?? process.cwd(), ".env");
|
|
4728
|
+
let existing;
|
|
4553
4729
|
try {
|
|
4554
|
-
await
|
|
4555
|
-
if (abortController.signal.aborted) break;
|
|
4556
|
-
process.stderr.write("\n");
|
|
4557
|
-
const response = await (0, import_prompts8.input)({ message: "You:" });
|
|
4558
|
-
process.stderr.write("\n");
|
|
4559
|
-
const trimmed = response.trim();
|
|
4560
|
-
if (!trimmed) continue;
|
|
4561
|
-
if (isExitCommand(trimmed)) return;
|
|
4562
|
-
turnGate.close();
|
|
4563
|
-
spinner.start();
|
|
4564
|
-
yield makeUserMessage(trimmed);
|
|
4730
|
+
existing = await (0, import_promises4.readFile)(filePath, "utf8");
|
|
4565
4731
|
} catch {
|
|
4566
|
-
return;
|
|
4567
4732
|
}
|
|
4733
|
+
const content = buildEnvContent(url, user, existing);
|
|
4734
|
+
await (0, import_promises4.writeFile)(filePath, content, "utf8");
|
|
4735
|
+
console.error(import_picocolors10.default.dim("Saved to .env"));
|
|
4736
|
+
} catch (err) {
|
|
4737
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4738
|
+
console.error(import_picocolors10.default.yellow(`Could not save .env file: ${message}`));
|
|
4568
4739
|
}
|
|
4569
4740
|
}
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4741
|
+
|
|
4742
|
+
// src/session/verify.ts
|
|
4743
|
+
var import_picocolors13 = __toESM(require("picocolors"));
|
|
4744
|
+
var import_prompts9 = require("@inquirer/prompts");
|
|
4745
|
+
|
|
4746
|
+
// src/session/collect.ts
|
|
4747
|
+
var import_prompts7 = require("@inquirer/prompts");
|
|
4748
|
+
var import_picocolors11 = __toESM(require("picocolors"));
|
|
4749
|
+
async function collectCredentials() {
|
|
4750
|
+
const prompted = /* @__PURE__ */ new Set();
|
|
4751
|
+
const envUrl = process.env.KINETICA_URL;
|
|
4752
|
+
const envUser = process.env.KINETICA_USER;
|
|
4753
|
+
if (envUrl && envUser && process.stdin.isTTY) {
|
|
4754
|
+
console.error(import_picocolors11.default.dim(`Saved connection: ${envUrl} (${envUser})`));
|
|
4755
|
+
const useSaved = await (0, import_prompts7.confirm)({
|
|
4756
|
+
message: "Use saved connection?",
|
|
4757
|
+
default: true
|
|
4758
|
+
});
|
|
4759
|
+
if (!useSaved) {
|
|
4760
|
+
prompted.add("url");
|
|
4761
|
+
prompted.add("user");
|
|
4762
|
+
const url2 = await (0, import_prompts7.input)({ message: "Kinetica endpoint URL:" });
|
|
4763
|
+
const user2 = await (0, import_prompts7.input)({ message: "Admin username:" });
|
|
4764
|
+
const pass2 = await (0, import_prompts7.password)({ message: "Admin password:", mask: "*" });
|
|
4765
|
+
return { credentials: { url: url2, user: user2, pass: pass2 }, prompted };
|
|
4582
4766
|
}
|
|
4583
|
-
} else {
|
|
4584
|
-
process.stderr.write(` ${import_picocolors13.default.red(`Error: ${statusResult.error}`)}
|
|
4585
|
-
`);
|
|
4586
4767
|
}
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4768
|
+
const url = envUrl ?? (prompted.add("url"), await (0, import_prompts7.input)({ message: "Kinetica endpoint URL:" }));
|
|
4769
|
+
const user = envUser ?? (prompted.add("user"), await (0, import_prompts7.input)({ message: "Admin username:" }));
|
|
4770
|
+
const pass = process.env.KINETICA_PASS ?? await (0, import_prompts7.password)({ message: "Admin password:", mask: "*" });
|
|
4771
|
+
return { credentials: { url, user, pass }, prompted };
|
|
4772
|
+
}
|
|
4773
|
+
async function repromptCredentials() {
|
|
4774
|
+
const user = await (0, import_prompts7.input)({ message: "Admin username:" });
|
|
4775
|
+
const pass = await (0, import_prompts7.password)({ message: "Admin password:", mask: "*" });
|
|
4776
|
+
return { user, pass };
|
|
4777
|
+
}
|
|
4778
|
+
|
|
4779
|
+
// src/session/KineticaSession.ts
|
|
4780
|
+
var REQUEST_TIMEOUT_MS = 3e4;
|
|
4781
|
+
function replacePort(baseUrl, port) {
|
|
4782
|
+
const parsed = new URL(baseUrl);
|
|
4783
|
+
parsed.port = String(port);
|
|
4784
|
+
return parsed.origin;
|
|
4785
|
+
}
|
|
4786
|
+
function createSession(url, user, pass) {
|
|
4787
|
+
const authHeader = "Basic " + Buffer.from(`${user}:${pass}`).toString("base64");
|
|
4788
|
+
const doFetch = async (fullUrl, body) => {
|
|
4789
|
+
if (process.env.DEBUG) {
|
|
4790
|
+
console.error(`[DEBUG] POST ${fullUrl}`);
|
|
4599
4791
|
}
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4792
|
+
return fetch(fullUrl, {
|
|
4793
|
+
method: "POST",
|
|
4794
|
+
headers: {
|
|
4795
|
+
Authorization: authHeader,
|
|
4796
|
+
"Content-Type": "application/json"
|
|
4797
|
+
},
|
|
4798
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
4799
|
+
signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
|
|
4800
|
+
});
|
|
4801
|
+
};
|
|
4802
|
+
return {
|
|
4803
|
+
baseUrl: url,
|
|
4804
|
+
makeRequest: (endpoint, body) => doFetch(`${url}${endpoint}`, body),
|
|
4805
|
+
makeRequestToPort: (port, endpoint, body) => doFetch(`${replacePort(url, port)}${endpoint}`, body)
|
|
4806
|
+
};
|
|
4605
4807
|
}
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
);
|
|
4808
|
+
|
|
4809
|
+
// src/session/resolve-url.ts
|
|
4810
|
+
var import_picocolors12 = __toESM(require("picocolors"));
|
|
4811
|
+
var import_prompts8 = require("@inquirer/prompts");
|
|
4812
|
+
var PROBE_TIMEOUT_MS2 = 3e3;
|
|
4813
|
+
var HTTP_PROTOCOL_RE = /^https?:\/\//i;
|
|
4814
|
+
var ANY_PROTOCOL_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
4815
|
+
function hasProtocol(input5) {
|
|
4816
|
+
return HTTP_PROTOCOL_RE.test(input5);
|
|
4817
|
+
}
|
|
4818
|
+
function stripTrailingSlashes(url) {
|
|
4819
|
+
return url.replace(/\/+$/, "");
|
|
4820
|
+
}
|
|
4821
|
+
async function probeProtocol(url) {
|
|
4822
|
+
try {
|
|
4823
|
+
await fetch(url, {
|
|
4824
|
+
method: "HEAD",
|
|
4825
|
+
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS2)
|
|
4826
|
+
});
|
|
4827
|
+
return true;
|
|
4828
|
+
} catch {
|
|
4829
|
+
return false;
|
|
4625
4830
|
}
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4831
|
+
}
|
|
4832
|
+
function isHttpsOnly() {
|
|
4833
|
+
return process.env.KINETICA_HTTPS_ONLY === "1";
|
|
4834
|
+
}
|
|
4835
|
+
function isInteractive() {
|
|
4836
|
+
return Boolean(process.stdin.isTTY);
|
|
4837
|
+
}
|
|
4838
|
+
async function confirmHttpFallback(host) {
|
|
4839
|
+
process.stderr.write(
|
|
4840
|
+
"\n" + import_picocolors12.default.red(
|
|
4841
|
+
import_picocolors12.default.bold(
|
|
4842
|
+
` WARNING: HTTPS unavailable at ${host}.
|
|
4843
|
+
Falling back to plaintext HTTP will transmit your Kinetica credentials in the clear.
|
|
4630
4844
|
`
|
|
4631
4845
|
)
|
|
4632
|
-
)
|
|
4846
|
+
) + import_picocolors12.default.dim(
|
|
4847
|
+
` Set KINETICA_HTTPS_ONLY=1 to refuse this fallback automatically, or pass an explicit http:// prefix to silence this prompt.
|
|
4848
|
+
|
|
4849
|
+
`
|
|
4850
|
+
)
|
|
4851
|
+
);
|
|
4852
|
+
try {
|
|
4853
|
+
return await (0, import_prompts8.confirm)({
|
|
4854
|
+
message: "Continue over plaintext HTTP?",
|
|
4855
|
+
default: false
|
|
4856
|
+
});
|
|
4857
|
+
} catch {
|
|
4858
|
+
return false;
|
|
4633
4859
|
}
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
const
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
const
|
|
4652
|
-
const
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4860
|
+
}
|
|
4861
|
+
async function resolveUrl(input5) {
|
|
4862
|
+
const trimmed = input5.trim();
|
|
4863
|
+
if (trimmed === "") {
|
|
4864
|
+
return { ok: false, error: "URL is empty" };
|
|
4865
|
+
}
|
|
4866
|
+
const normalized = stripTrailingSlashes(trimmed);
|
|
4867
|
+
if (hasProtocol(normalized)) {
|
|
4868
|
+
return { ok: true, url: normalized };
|
|
4869
|
+
}
|
|
4870
|
+
if (ANY_PROTOCOL_RE.test(normalized)) {
|
|
4871
|
+
const scheme = normalized.split("://")[0];
|
|
4872
|
+
return {
|
|
4873
|
+
ok: false,
|
|
4874
|
+
error: `Unsupported protocol: ${scheme}. Use http:// or https://`
|
|
4875
|
+
};
|
|
4876
|
+
}
|
|
4877
|
+
const httpsUrl = `https://${normalized}`;
|
|
4878
|
+
const httpUrl = `http://${normalized}`;
|
|
4879
|
+
console.error(import_picocolors12.default.dim("Detecting protocol..."));
|
|
4880
|
+
if (await probeProtocol(httpsUrl)) {
|
|
4881
|
+
return { ok: true, url: httpsUrl };
|
|
4882
|
+
}
|
|
4883
|
+
if (isHttpsOnly()) {
|
|
4884
|
+
return {
|
|
4885
|
+
ok: false,
|
|
4886
|
+
error: `HTTPS probe to ${httpsUrl} failed and KINETICA_HTTPS_ONLY=1; refusing to fall back to plaintext HTTP.`
|
|
4887
|
+
};
|
|
4888
|
+
}
|
|
4889
|
+
if (!await probeProtocol(httpUrl)) {
|
|
4890
|
+
return {
|
|
4891
|
+
ok: false,
|
|
4892
|
+
error: `Could not connect to ${normalized} via https:// or http://`
|
|
4893
|
+
};
|
|
4894
|
+
}
|
|
4895
|
+
if (!isInteractive()) {
|
|
4896
|
+
return {
|
|
4897
|
+
ok: false,
|
|
4898
|
+
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.`
|
|
4899
|
+
};
|
|
4900
|
+
}
|
|
4901
|
+
const approved = await confirmHttpFallback(normalized);
|
|
4902
|
+
if (!approved) {
|
|
4903
|
+
return {
|
|
4904
|
+
ok: false,
|
|
4905
|
+
error: "User declined plaintext HTTP fallback"
|
|
4906
|
+
};
|
|
4907
|
+
}
|
|
4908
|
+
return { ok: true, url: httpUrl };
|
|
4909
|
+
}
|
|
4910
|
+
|
|
4911
|
+
// src/session/verify.ts
|
|
4912
|
+
var MAX_RETRIES = 3;
|
|
4913
|
+
var MAX_REPROMPTS = 2;
|
|
4914
|
+
var DEFAULT_HM_PORT2 = 9300;
|
|
4915
|
+
function extractVersion(responseBody) {
|
|
4916
|
+
try {
|
|
4917
|
+
const outer = JSON.parse(responseBody);
|
|
4918
|
+
if (typeof outer.data_str !== "string") return void 0;
|
|
4919
|
+
const inner = JSON.parse(outer.data_str);
|
|
4920
|
+
const systemStr = inner.status_map?.system;
|
|
4921
|
+
if (typeof systemStr !== "string") return void 0;
|
|
4922
|
+
const system = JSON.parse(systemStr);
|
|
4923
|
+
return typeof system.version === "string" ? system.version : void 0;
|
|
4924
|
+
} catch {
|
|
4925
|
+
return void 0;
|
|
4678
4926
|
}
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
prompt: makeInteractivePrompt(abortController, turnGate, spinner),
|
|
4682
|
-
options
|
|
4683
|
-
});
|
|
4684
|
-
process.once("SIGINT", () => {
|
|
4685
|
-
spinner.stop();
|
|
4686
|
-
process.stderr.write("\nInterrupted \u2014 aborting investigation...\n");
|
|
4687
|
-
abortController.abort();
|
|
4688
|
-
agentQuery.close();
|
|
4689
|
-
});
|
|
4690
|
-
let numTurns = 0;
|
|
4691
|
-
let totalCostUsd = 0;
|
|
4692
|
-
let durationMs = 0;
|
|
4693
|
-
let durationApiMs = 0;
|
|
4694
|
-
let lastStreamCharWasNewline = true;
|
|
4695
|
-
const tableAligner = createStreamingTableAligner();
|
|
4696
|
-
let hadNonAbortError = false;
|
|
4927
|
+
}
|
|
4928
|
+
function extractVersionFromHostManager(responseBody) {
|
|
4697
4929
|
try {
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4930
|
+
const parsed = JSON.parse(responseBody);
|
|
4931
|
+
return typeof parsed.version === "string" ? parsed.version : void 0;
|
|
4932
|
+
} catch {
|
|
4933
|
+
return void 0;
|
|
4934
|
+
}
|
|
4935
|
+
}
|
|
4936
|
+
async function probeHostManager(session2) {
|
|
4937
|
+
if (!session2.makeRequestToPort) {
|
|
4938
|
+
return { ok: false };
|
|
4939
|
+
}
|
|
4940
|
+
try {
|
|
4941
|
+
const response = await session2.makeRequestToPort(DEFAULT_HM_PORT2, "/", void 0);
|
|
4942
|
+
if (!response.ok) return { ok: false };
|
|
4943
|
+
const body = await response.text();
|
|
4944
|
+
return { ok: true, version: extractVersionFromHostManager(body) };
|
|
4945
|
+
} catch {
|
|
4946
|
+
return { ok: false };
|
|
4947
|
+
}
|
|
4948
|
+
}
|
|
4949
|
+
async function verifyConnectivity(session2) {
|
|
4950
|
+
const response = await session2.makeRequest("/show/system/status", {});
|
|
4951
|
+
if (!response.ok) {
|
|
4952
|
+
const body2 = await response.text();
|
|
4953
|
+
throw new Error(`HTTP ${response.status}: ${body2}`);
|
|
4954
|
+
}
|
|
4955
|
+
const body = await response.text();
|
|
4956
|
+
return extractVersion(body);
|
|
4957
|
+
}
|
|
4958
|
+
function isCredentialError(errorMessage) {
|
|
4959
|
+
return errorMessage.startsWith("HTTP 401") || errorMessage.startsWith("HTTP 403");
|
|
4960
|
+
}
|
|
4961
|
+
async function connectWithRetry() {
|
|
4962
|
+
const { credentials, prompted } = await collectCredentials();
|
|
4963
|
+
const resolved = await resolveUrl(credentials.url);
|
|
4964
|
+
if (!resolved.ok) {
|
|
4965
|
+
console.error(import_picocolors13.default.red(resolved.error));
|
|
4966
|
+
process.exit(1);
|
|
4967
|
+
}
|
|
4968
|
+
const resolvedUrl = resolved.url;
|
|
4969
|
+
let currentUser = credentials.user;
|
|
4970
|
+
let currentPass = credentials.pass;
|
|
4971
|
+
let wasReprompted = false;
|
|
4972
|
+
let repromptCount = 0;
|
|
4973
|
+
let session2 = createSession(resolvedUrl, currentUser, currentPass);
|
|
4974
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
4975
|
+
try {
|
|
4976
|
+
const kineticaVersion = await verifyConnectivity(session2);
|
|
4977
|
+
console.error(import_picocolors13.default.green("Connected to Kinetica successfully."));
|
|
4978
|
+
if (prompted.size > 0 || wasReprompted) {
|
|
4979
|
+
await offerSaveCredentials(resolvedUrl, currentUser);
|
|
4980
|
+
}
|
|
4981
|
+
return { session: session2, kineticaVersion, degraded: false };
|
|
4982
|
+
} catch (err) {
|
|
4983
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4984
|
+
console.error(import_picocolors13.default.red(`Connection failed (attempt ${attempt}/${MAX_RETRIES}): ${msg}`));
|
|
4985
|
+
if (isCredentialError(msg)) {
|
|
4986
|
+
if (process.stdin.isTTY && repromptCount < MAX_REPROMPTS) {
|
|
4987
|
+
const shouldRetry = await (0, import_prompts9.confirm)({
|
|
4988
|
+
message: "Credentials may be incorrect. Re-enter?",
|
|
4989
|
+
default: true
|
|
4990
|
+
});
|
|
4991
|
+
if (shouldRetry) {
|
|
4992
|
+
const fresh = await repromptCredentials();
|
|
4993
|
+
currentUser = fresh.user;
|
|
4994
|
+
currentPass = fresh.pass;
|
|
4995
|
+
wasReprompted = true;
|
|
4996
|
+
repromptCount++;
|
|
4997
|
+
session2 = createSession(resolvedUrl, currentUser, currentPass);
|
|
4998
|
+
attempt = 0;
|
|
4999
|
+
continue;
|
|
4710
5000
|
}
|
|
4711
5001
|
}
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
process.stderr.write("\n");
|
|
4721
|
-
lastStreamCharWasNewline = true;
|
|
4722
|
-
}
|
|
4723
|
-
if (assistantMsg.message.stop_reason === "end_turn") {
|
|
4724
|
-
spinner.stop();
|
|
4725
|
-
turnGate.open();
|
|
4726
|
-
} else if (assistantMsg.message.stop_reason === "tool_use") {
|
|
4727
|
-
spinner.start("Investigating");
|
|
4728
|
-
} else {
|
|
4729
|
-
spinner.stop();
|
|
4730
|
-
}
|
|
4731
|
-
if (assistantMsg.error) {
|
|
4732
|
-
spinner.stop();
|
|
4733
|
-
const label = ERROR_LABELS[assistantMsg.error] ?? assistantMsg.error;
|
|
4734
|
-
process.stderr.write(import_picocolors13.default.yellow(`
|
|
4735
|
-
API error: ${label}
|
|
4736
|
-
`));
|
|
4737
|
-
turnGate.open();
|
|
4738
|
-
}
|
|
4739
|
-
} else if (message.type === "result") {
|
|
4740
|
-
spinner.stop();
|
|
4741
|
-
const resultMsg = message;
|
|
4742
|
-
numTurns = resultMsg.num_turns;
|
|
4743
|
-
totalCostUsd = resultMsg.total_cost_usd;
|
|
4744
|
-
durationMs = resultMsg.duration_ms;
|
|
4745
|
-
durationApiMs = resultMsg.duration_api_ms;
|
|
4746
|
-
if (resultMsg.subtype === "error_max_turns") {
|
|
4747
|
-
process.stderr.write(
|
|
4748
|
-
"\nInvestigation hit turn limit. Partial report may be available.\n"
|
|
4749
|
-
);
|
|
4750
|
-
} else if (resultMsg.subtype === "error_max_budget_usd") {
|
|
4751
|
-
process.stderr.write("\nBudget limit reached.\n");
|
|
4752
|
-
} else if (resultMsg.subtype === "error_during_execution") {
|
|
4753
|
-
process.stderr.write(
|
|
4754
|
-
"\nExecution error \u2014 the agent encountered an unrecoverable failure.\n"
|
|
4755
|
-
);
|
|
4756
|
-
} else if (resultMsg.subtype !== "success") {
|
|
4757
|
-
process.stderr.write(`
|
|
4758
|
-
Agent session ended with error: ${resultMsg.subtype}
|
|
4759
|
-
`);
|
|
4760
|
-
}
|
|
4761
|
-
if (resultMsg.permission_denials.length > 0) {
|
|
4762
|
-
const denied = resultMsg.permission_denials.map((d) => d.tool_name).join(", ");
|
|
4763
|
-
process.stderr.write(`
|
|
4764
|
-
Permission denials: ${denied}
|
|
4765
|
-
`);
|
|
4766
|
-
}
|
|
4767
|
-
turnGate.open();
|
|
4768
|
-
} else if (message.type === "system") {
|
|
4769
|
-
const sysMsg = message;
|
|
4770
|
-
if (sysMsg.subtype === "init") {
|
|
4771
|
-
const initMsg = message;
|
|
4772
|
-
const failed = (initMsg.mcp_servers ?? []).filter(
|
|
4773
|
-
(s) => s.name === MCP_SERVER_NAME && s.status !== "connected"
|
|
4774
|
-
);
|
|
4775
|
-
for (const s of failed) {
|
|
4776
|
-
process.stderr.write(
|
|
4777
|
-
`
|
|
4778
|
-
Warning: MCP server "${s.name}" failed to connect (${s.status})
|
|
4779
|
-
`
|
|
4780
|
-
);
|
|
4781
|
-
}
|
|
4782
|
-
} else if (sysMsg.subtype === "api_retry") {
|
|
4783
|
-
const retryMsg = message;
|
|
4784
|
-
const statusStr = retryMsg.error_status !== null ? ` (HTTP ${retryMsg.error_status})` : "";
|
|
4785
|
-
const delaySec = Math.round(retryMsg.retry_delay_ms / 1e3);
|
|
4786
|
-
process.stderr.write(
|
|
5002
|
+
console.error(import_picocolors13.default.red("Authentication failed. Exiting."));
|
|
5003
|
+
process.exit(1);
|
|
5004
|
+
}
|
|
5005
|
+
if (attempt === MAX_RETRIES) {
|
|
5006
|
+
console.error(import_picocolors13.default.yellow("DB engine unreachable. Probing host manager on port 9300..."));
|
|
5007
|
+
const hmResult = await probeHostManager(session2);
|
|
5008
|
+
if (hmResult.ok) {
|
|
5009
|
+
console.error(
|
|
4787
5010
|
import_picocolors13.default.yellow(
|
|
4788
|
-
|
|
4789
|
-
API error${statusStr}. Retrying (attempt ${retryMsg.attempt}/${retryMsg.max_retries}) in ${delaySec}s...
|
|
4790
|
-
`
|
|
5011
|
+
"Connected in DEGRADED MODE (host manager only). Most diagnostic tools will be unavailable."
|
|
4791
5012
|
)
|
|
4792
5013
|
);
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
`
|
|
4798
|
-
[Context compressed (${preTokens} tokens before compaction) \u2014 investigation continues]
|
|
4799
|
-
`
|
|
4800
|
-
);
|
|
4801
|
-
}
|
|
4802
|
-
} else if (message.type === "rate_limit_event") {
|
|
4803
|
-
const rateMsg = message;
|
|
4804
|
-
const { status, resetsAt } = rateMsg.rate_limit_info;
|
|
4805
|
-
if (status === "rejected") {
|
|
4806
|
-
const resetStr = resetsAt ? ` Resets at ${new Date(resetsAt * 1e3).toISOString()}.` : "";
|
|
4807
|
-
process.stderr.write(`
|
|
4808
|
-
Rate limited \u2014 requests rejected.${resetStr}
|
|
4809
|
-
`);
|
|
4810
|
-
} else if (status === "allowed_warning") {
|
|
4811
|
-
process.stderr.write("\nApproaching rate limit \u2014 investigation may slow.\n");
|
|
4812
|
-
}
|
|
4813
|
-
} else if (message.type === "control_request") {
|
|
4814
|
-
const controlMsg = message;
|
|
4815
|
-
if (controlMsg.request.subtype === "claude_authenticate") {
|
|
4816
|
-
process.stderr.write(import_picocolors13.default.yellow("\nRe-authentication requested by SDK...\n"));
|
|
5014
|
+
if (prompted.size > 0 || wasReprompted) {
|
|
5015
|
+
await offerSaveCredentials(resolvedUrl, currentUser);
|
|
5016
|
+
}
|
|
5017
|
+
return { session: session2, kineticaVersion: hmResult.version, degraded: true };
|
|
4817
5018
|
}
|
|
5019
|
+
console.error(import_picocolors13.default.red("Host manager also unreachable. Exiting."));
|
|
5020
|
+
process.exit(1);
|
|
4818
5021
|
}
|
|
4819
5022
|
}
|
|
4820
|
-
} catch (error) {
|
|
4821
|
-
spinner.stop();
|
|
4822
|
-
if (error instanceof import_claude_agent_sdk5.AbortError) {
|
|
4823
|
-
hadNonAbortError = false;
|
|
4824
|
-
} else {
|
|
4825
|
-
hadNonAbortError = true;
|
|
4826
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
4827
|
-
process.stderr.write(import_picocolors13.default.red(`
|
|
4828
|
-
Agent error: ${message}
|
|
4829
|
-
`));
|
|
4830
|
-
}
|
|
4831
|
-
} finally {
|
|
4832
|
-
spinner.stop();
|
|
4833
|
-
const remaining = tableAligner.flush();
|
|
4834
|
-
if (remaining) {
|
|
4835
|
-
process.stderr.write(remaining);
|
|
4836
|
-
}
|
|
4837
|
-
turnGate.open();
|
|
4838
|
-
const durationSec = Math.round(durationMs / 1e3);
|
|
4839
|
-
const apiPct = durationMs > 0 ? Math.round(durationApiMs / durationMs * 100) : 0;
|
|
4840
|
-
const costStr = totalCostUsd > 0 ? ` Cost: $${totalCostUsd.toFixed(4)}.` : "";
|
|
4841
|
-
if (hadNonAbortError) {
|
|
4842
|
-
process.stderr.write(`
|
|
4843
|
-
Session ended due to error. Turns: ${numTurns}.${costStr}
|
|
4844
|
-
`);
|
|
4845
|
-
} else {
|
|
4846
|
-
process.stderr.write(
|
|
4847
|
-
`
|
|
4848
|
-
Session ended. Turns: ${numTurns}. Duration: ${durationSec}s (${apiPct}% API).${costStr}
|
|
4849
|
-
`
|
|
4850
|
-
);
|
|
4851
|
-
}
|
|
4852
5023
|
}
|
|
5024
|
+
throw new Error("unreachable");
|
|
4853
5025
|
}
|
|
4854
5026
|
|
|
4855
5027
|
// src/cli/index.ts
|
|
@@ -4874,12 +5046,14 @@ function printHelp() {
|
|
|
4874
5046
|
" --login-org=UUID Target organization UUID for OAuth",
|
|
4875
5047
|
" --logout Log out from Anthropic account and exit",
|
|
4876
5048
|
` --model=NAME Override agent model (${SUPPORTED_MODELS.join(" | ")}); default: sonnet`,
|
|
5049
|
+
" --max-budget=USD Per-session budget cap in USD (API-key billing only); default: 5.00",
|
|
4877
5050
|
"",
|
|
4878
5051
|
" Environment variables:",
|
|
4879
|
-
" ANTHROPIC_API_KEY
|
|
4880
|
-
"
|
|
4881
|
-
"
|
|
4882
|
-
"
|
|
5052
|
+
" ANTHROPIC_API_KEY Anthropic API key (if not set, OAuth login via browser is used)",
|
|
5053
|
+
" ADMIN_AGENT_MAX_BUDGET Per-session budget cap in USD (overridden by --max-budget)",
|
|
5054
|
+
" KINETICA_URL Kinetica endpoint URL",
|
|
5055
|
+
" KINETICA_USER Admin username",
|
|
5056
|
+
" KINETICA_PASS Admin password",
|
|
4883
5057
|
""
|
|
4884
5058
|
];
|
|
4885
5059
|
process.stdout.write(lines.join("\n") + "\n");
|
|
@@ -4924,9 +5098,31 @@ async function main() {
|
|
|
4924
5098
|
return;
|
|
4925
5099
|
}
|
|
4926
5100
|
}
|
|
5101
|
+
const budgetArg = args.find((a) => a.startsWith("--max-budget="));
|
|
5102
|
+
const budgetValue = budgetArg?.split("=")[1];
|
|
5103
|
+
let maxBudgetFlag;
|
|
5104
|
+
if (budgetValue !== void 0) {
|
|
5105
|
+
const parsed = Number(budgetValue);
|
|
5106
|
+
if (!isValidBudget(parsed)) {
|
|
5107
|
+
process.stderr.write(
|
|
5108
|
+
import_picocolors14.default.red(
|
|
5109
|
+
`Error: invalid --max-budget value "${budgetValue}". Use a positive number, e.g. --max-budget=10
|
|
5110
|
+
`
|
|
5111
|
+
)
|
|
5112
|
+
);
|
|
5113
|
+
process.exitCode = 1;
|
|
5114
|
+
return;
|
|
5115
|
+
}
|
|
5116
|
+
maxBudgetFlag = parsed;
|
|
5117
|
+
}
|
|
4927
5118
|
loadEnvFile();
|
|
5119
|
+
printBanner();
|
|
5120
|
+
if (model === void 0 && process.stdin.isTTY) {
|
|
5121
|
+
model = await selectModel();
|
|
5122
|
+
}
|
|
4928
5123
|
const effectiveModel = model ?? DEFAULT_AGENT_MODEL;
|
|
4929
|
-
|
|
5124
|
+
process.stderr.write(import_picocolors14.default.dim(`Model: ${effectiveModel}
|
|
5125
|
+
`));
|
|
4930
5126
|
const authResult = await authenticateAnthropic({ forceLogin, loginMethod, loginOrgUUID });
|
|
4931
5127
|
if (authResult.method === "oauth") {
|
|
4932
5128
|
const acctInfo = authResult.email ? ` (${authResult.email})` : "";
|
|
@@ -4935,9 +5131,13 @@ async function main() {
|
|
|
4935
5131
|
} else {
|
|
4936
5132
|
process.stderr.write(import_picocolors14.default.dim("Authenticated via API key\n"));
|
|
4937
5133
|
}
|
|
5134
|
+
const maxBudgetUsd = resolveMaxBudgetUsd(maxBudgetFlag);
|
|
4938
5135
|
const { session: connectedSession, kineticaVersion, degraded } = await connectWithRetry();
|
|
4939
5136
|
session = connectedSession;
|
|
4940
|
-
await runAgent(session, kineticaVersion, degraded, model
|
|
5137
|
+
await runAgent(session, kineticaVersion, degraded, model, {
|
|
5138
|
+
authMethod: authResult.method,
|
|
5139
|
+
maxBudgetUsd
|
|
5140
|
+
});
|
|
4941
5141
|
}
|
|
4942
5142
|
function getSession() {
|
|
4943
5143
|
return session;
|