my-pi 0.0.12 → 0.0.13
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 +1 -1
- package/dist/{api-CMc8zzEG.js → api-CWEizv2k.js} +2014 -2014
- package/dist/api-CWEizv2k.js.map +1 -0
- package/dist/api.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/extensions/{otel.test.ts → telemetry.test.ts} +1 -1
- package/dist/api-CMc8zzEG.js.map +0 -1
- /package/src/extensions/{otel.ts → telemetry.ts} +0 -0
|
@@ -2279,2197 +2279,2197 @@ async function mcp(pi) {
|
|
|
2279
2279
|
});
|
|
2280
2280
|
}
|
|
2281
2281
|
//#endregion
|
|
2282
|
-
//#region src/extensions/
|
|
2283
|
-
const
|
|
2284
|
-
|
|
2285
|
-
|
|
2282
|
+
//#region src/extensions/prompt-presets.ts
|
|
2283
|
+
const PRESET_STATE_TYPE = "prompt-preset-state";
|
|
2284
|
+
const ENABLED$1 = "[x]";
|
|
2285
|
+
const DISABLED$1 = "[ ]";
|
|
2286
|
+
const SELECTED = "(x)";
|
|
2287
|
+
const UNSELECTED = "( )";
|
|
2288
|
+
const NONE_BASE_ID = "__base_none__";
|
|
2289
|
+
const DEFAULT_PROMPT_PRESETS = {
|
|
2290
|
+
terse: {
|
|
2291
|
+
kind: "base",
|
|
2292
|
+
description: "Short, direct, no fluff",
|
|
2293
|
+
instructions: "Be concise and direct. Default to the shortest response that fully solves the user's request. No purple prose, no filler, no repetitive caveats. Prefer a short paragraph or a few bullets. Only include extra detail when it materially affects the decision, implementation, or next step."
|
|
2294
|
+
},
|
|
2295
|
+
standard: {
|
|
2296
|
+
kind: "base",
|
|
2297
|
+
description: "Clear and concise with key context",
|
|
2298
|
+
instructions: "Be clear, direct, and concise. Include only the reasoning and implementation details that matter. Avoid filler, grandstanding, and ornamental language. Use bullets when they improve scanability."
|
|
2299
|
+
},
|
|
2300
|
+
detailed: {
|
|
2301
|
+
kind: "base",
|
|
2302
|
+
description: "More explanation when nuance matters",
|
|
2303
|
+
instructions: "Be thorough when the task is complex or tradeoffs matter, but stay practical. Explain only the details that help the user decide, verify, or implement. Avoid purple prose and unnecessary scene-setting."
|
|
2304
|
+
},
|
|
2305
|
+
"no-purple-prose": {
|
|
2306
|
+
kind: "layer",
|
|
2307
|
+
description: "Strip out ornamental language",
|
|
2308
|
+
instructions: "Do not use purple prose, flourish, motivational filler, or theatrical transitions. Prefer plain language and concrete statements."
|
|
2309
|
+
},
|
|
2310
|
+
bullets: {
|
|
2311
|
+
kind: "layer",
|
|
2312
|
+
description: "Prefer short bullets when useful",
|
|
2313
|
+
instructions: "When presenting options, findings, or steps, prefer short bullet lists over long paragraphs."
|
|
2314
|
+
},
|
|
2315
|
+
"clarify-first": {
|
|
2316
|
+
kind: "layer",
|
|
2317
|
+
description: "Ask brief clarifying questions when requirements are ambiguous",
|
|
2318
|
+
instructions: "If the request is materially ambiguous, ask the minimum clarifying question(s) needed before proceeding. Do not ask unnecessary questions."
|
|
2319
|
+
},
|
|
2320
|
+
"include-risks": {
|
|
2321
|
+
kind: "layer",
|
|
2322
|
+
description: "Call out notable risks or tradeoffs",
|
|
2323
|
+
instructions: "When making a recommendation or implementation plan, briefly mention the key risk, tradeoff, or caveat if one materially matters."
|
|
2324
|
+
}
|
|
2286
2325
|
};
|
|
2287
|
-
function
|
|
2288
|
-
|
|
2326
|
+
function normalize_prompt_presets(input) {
|
|
2327
|
+
if (!input || typeof input !== "object") return {};
|
|
2328
|
+
const normalized = {};
|
|
2329
|
+
for (const [raw_name, raw_value] of Object.entries(input)) {
|
|
2330
|
+
const name = raw_name.trim();
|
|
2331
|
+
if (!name) continue;
|
|
2332
|
+
if (typeof raw_value === "string") {
|
|
2333
|
+
normalized[name] = {
|
|
2334
|
+
kind: "base",
|
|
2335
|
+
instructions: raw_value
|
|
2336
|
+
};
|
|
2337
|
+
continue;
|
|
2338
|
+
}
|
|
2339
|
+
if (!raw_value || typeof raw_value !== "object") continue;
|
|
2340
|
+
const candidate = raw_value;
|
|
2341
|
+
if (typeof candidate.instructions !== "string") continue;
|
|
2342
|
+
normalized[name] = {
|
|
2343
|
+
instructions: candidate.instructions,
|
|
2344
|
+
...candidate.kind === "layer" ? { kind: "layer" } : {},
|
|
2345
|
+
...typeof candidate.description === "string" ? { description: candidate.description } : {}
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2348
|
+
return normalized;
|
|
2289
2349
|
}
|
|
2290
|
-
function
|
|
2291
|
-
return
|
|
2350
|
+
function to_loaded_prompt_presets(presets, source) {
|
|
2351
|
+
return Object.fromEntries(Object.entries(presets).map(([name, preset]) => [name, {
|
|
2352
|
+
name,
|
|
2353
|
+
kind: preset.kind === "layer" ? "layer" : "base",
|
|
2354
|
+
source,
|
|
2355
|
+
...preset
|
|
2356
|
+
}]));
|
|
2292
2357
|
}
|
|
2293
|
-
function
|
|
2294
|
-
|
|
2295
|
-
return resolve(cwd, override_path);
|
|
2358
|
+
function get_global_presets_path() {
|
|
2359
|
+
return join(getAgentDir(), "presets.json");
|
|
2296
2360
|
}
|
|
2297
|
-
function
|
|
2298
|
-
|
|
2299
|
-
|
|
2361
|
+
function get_project_presets_path(cwd) {
|
|
2362
|
+
return join(cwd, ".pi", "presets.json");
|
|
2363
|
+
}
|
|
2364
|
+
function get_persisted_prompt_state_path() {
|
|
2365
|
+
return join(getAgentDir(), "prompt-preset-state.json");
|
|
2366
|
+
}
|
|
2367
|
+
function read_prompt_presets_file(path) {
|
|
2368
|
+
if (!existsSync(path)) return {};
|
|
2300
2369
|
try {
|
|
2301
|
-
|
|
2302
|
-
return {
|
|
2303
|
-
version: typeof parsed.version === "number" ? parsed.version : DEFAULT_CONFIG$1.version,
|
|
2304
|
-
enabled: typeof parsed.enabled === "boolean" ? parsed.enabled : DEFAULT_CONFIG$1.enabled
|
|
2305
|
-
};
|
|
2370
|
+
return normalize_prompt_presets(JSON.parse(readFileSync(path, "utf-8")));
|
|
2306
2371
|
} catch {
|
|
2307
|
-
return {
|
|
2372
|
+
return {};
|
|
2308
2373
|
}
|
|
2309
2374
|
}
|
|
2310
|
-
function
|
|
2311
|
-
|
|
2375
|
+
function load_prompt_presets(cwd) {
|
|
2376
|
+
return Object.assign({}, to_loaded_prompt_presets(DEFAULT_PROMPT_PRESETS, "builtin"), to_loaded_prompt_presets(read_prompt_presets_file(get_global_presets_path()), "user"), to_loaded_prompt_presets(read_prompt_presets_file(get_project_presets_path(cwd)), "project"));
|
|
2377
|
+
}
|
|
2378
|
+
function sort_prompt_presets(presets) {
|
|
2379
|
+
return Object.fromEntries(Object.entries(presets).sort(([a], [b]) => a.localeCompare(b)));
|
|
2380
|
+
}
|
|
2381
|
+
function save_project_prompt_presets(cwd, presets) {
|
|
2382
|
+
const path = get_project_presets_path(cwd);
|
|
2312
2383
|
const dir = dirname(path);
|
|
2313
2384
|
if (!existsSync(dir)) mkdirSync(dir, {
|
|
2314
2385
|
recursive: true,
|
|
2315
2386
|
mode: 448
|
|
2316
2387
|
});
|
|
2317
2388
|
const tmp = `${path}.tmp-${Date.now()}`;
|
|
2318
|
-
writeFileSync(tmp, JSON.stringify(
|
|
2389
|
+
writeFileSync(tmp, JSON.stringify(sort_prompt_presets(presets), null, " ") + "\n", { mode: 384 });
|
|
2319
2390
|
renameSync(tmp, path);
|
|
2391
|
+
return path;
|
|
2320
2392
|
}
|
|
2321
|
-
function
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
}
|
|
2341
|
-
function get_eval_metadata() {
|
|
2393
|
+
function remove_project_prompt_preset(cwd, name) {
|
|
2394
|
+
const path = get_project_presets_path(cwd);
|
|
2395
|
+
const project_presets = read_prompt_presets_file(path);
|
|
2396
|
+
if (!(name in project_presets)) return {
|
|
2397
|
+
removed: false,
|
|
2398
|
+
path,
|
|
2399
|
+
remaining: Object.keys(project_presets).length
|
|
2400
|
+
};
|
|
2401
|
+
delete project_presets[name];
|
|
2402
|
+
const remaining = Object.keys(project_presets).length;
|
|
2403
|
+
if (remaining === 0) {
|
|
2404
|
+
if (existsSync(path)) unlinkSync(path);
|
|
2405
|
+
return {
|
|
2406
|
+
removed: true,
|
|
2407
|
+
path,
|
|
2408
|
+
remaining
|
|
2409
|
+
};
|
|
2410
|
+
}
|
|
2411
|
+
save_project_prompt_presets(cwd, project_presets);
|
|
2342
2412
|
return {
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
suite: process.env.MY_PI_EVAL_SUITE ?? null
|
|
2413
|
+
removed: true,
|
|
2414
|
+
path,
|
|
2415
|
+
remaining
|
|
2347
2416
|
};
|
|
2348
2417
|
}
|
|
2349
|
-
function
|
|
2350
|
-
if (!
|
|
2351
|
-
|
|
2352
|
-
id: null
|
|
2353
|
-
};
|
|
2418
|
+
function normalize_prompt_preset_state(input) {
|
|
2419
|
+
if (!input || typeof input !== "object") return void 0;
|
|
2420
|
+
const candidate = input;
|
|
2354
2421
|
return {
|
|
2355
|
-
|
|
2356
|
-
|
|
2422
|
+
base_name: typeof candidate.base_name === "string" && candidate.base_name.trim() ? candidate.base_name.trim() : null,
|
|
2423
|
+
layer_names: Array.isArray(candidate.layer_names) ? [...new Set(candidate.layer_names.filter((value) => typeof value === "string" && value.trim().length > 0).map((value) => value.trim()))].sort() : []
|
|
2357
2424
|
};
|
|
2358
2425
|
}
|
|
2359
|
-
function
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2426
|
+
function read_persisted_prompt_states(path = get_persisted_prompt_state_path()) {
|
|
2427
|
+
if (!existsSync(path)) return {
|
|
2428
|
+
version: 1,
|
|
2429
|
+
projects: {}
|
|
2430
|
+
};
|
|
2364
2431
|
try {
|
|
2365
|
-
|
|
2432
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
2433
|
+
const raw_projects = parsed.projects && typeof parsed.projects === "object" ? parsed.projects : {};
|
|
2434
|
+
const projects = {};
|
|
2435
|
+
for (const [cwd, value] of Object.entries(raw_projects)) {
|
|
2436
|
+
const normalized = normalize_prompt_preset_state(value);
|
|
2437
|
+
if (!normalized) continue;
|
|
2438
|
+
projects[cwd] = normalized;
|
|
2439
|
+
}
|
|
2440
|
+
return {
|
|
2441
|
+
version: typeof parsed.version === "number" ? parsed.version : 1,
|
|
2442
|
+
projects
|
|
2443
|
+
};
|
|
2366
2444
|
} catch {
|
|
2367
|
-
return
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
}
|
|
2445
|
+
return {
|
|
2446
|
+
version: 1,
|
|
2447
|
+
projects: {}
|
|
2448
|
+
};
|
|
2371
2449
|
}
|
|
2372
2450
|
}
|
|
2373
|
-
function
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
if (Array.isArray(value)) return {
|
|
2382
|
-
type: "array",
|
|
2383
|
-
length: value.length,
|
|
2384
|
-
items: depth >= 1 ? void 0 : value.slice(0, 5).map((item) => summarize_value(item, depth + 1))
|
|
2451
|
+
function load_persisted_prompt_state(cwd, path = get_persisted_prompt_state_path()) {
|
|
2452
|
+
return read_persisted_prompt_states(path).projects[cwd];
|
|
2453
|
+
}
|
|
2454
|
+
function save_persisted_prompt_state(cwd, state, path = get_persisted_prompt_state_path()) {
|
|
2455
|
+
const persisted = read_persisted_prompt_states(path);
|
|
2456
|
+
persisted.projects[cwd] = normalize_prompt_preset_state(state) ?? {
|
|
2457
|
+
base_name: null,
|
|
2458
|
+
layer_names: []
|
|
2385
2459
|
};
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
}
|
|
2399
|
-
return summary;
|
|
2400
|
-
}
|
|
2401
|
-
return { type: typeof value };
|
|
2460
|
+
const dir = dirname(path);
|
|
2461
|
+
if (!existsSync(dir)) mkdirSync(dir, {
|
|
2462
|
+
recursive: true,
|
|
2463
|
+
mode: 448
|
|
2464
|
+
});
|
|
2465
|
+
const tmp = `${path}.tmp-${Date.now()}`;
|
|
2466
|
+
writeFileSync(tmp, JSON.stringify({
|
|
2467
|
+
version: 1,
|
|
2468
|
+
projects: Object.fromEntries(Object.entries(persisted.projects).sort(([a], [b]) => a.localeCompare(b)))
|
|
2469
|
+
}, null, " ") + "\n", { mode: 384 });
|
|
2470
|
+
renameSync(tmp, path);
|
|
2471
|
+
return path;
|
|
2402
2472
|
}
|
|
2403
|
-
function
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
tool: tool_name,
|
|
2409
|
-
timeout: input.timeout ?? null,
|
|
2410
|
-
command: summarize_value(input.command)
|
|
2411
|
-
});
|
|
2412
|
-
case "read":
|
|
2413
|
-
case "write":
|
|
2414
|
-
case "edit": return safe_json_stringify({
|
|
2415
|
-
tool: tool_name,
|
|
2416
|
-
path: typeof input.path === "string" ? input.path : null,
|
|
2417
|
-
offset: typeof input.offset === "number" ? input.offset : null,
|
|
2418
|
-
limit: typeof input.limit === "number" ? input.limit : null,
|
|
2419
|
-
content: summarize_value(input.content),
|
|
2420
|
-
edits: summarize_value(input.edits)
|
|
2421
|
-
});
|
|
2422
|
-
default: return safe_json_stringify({
|
|
2423
|
-
tool: tool_name,
|
|
2424
|
-
summary: summarize_value(args)
|
|
2425
|
-
});
|
|
2473
|
+
function get_last_preset_state(ctx) {
|
|
2474
|
+
const entries = ctx.sessionManager.getEntries();
|
|
2475
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
2476
|
+
const entry = entries[i];
|
|
2477
|
+
if (entry.type === "custom" && entry.customType === PRESET_STATE_TYPE && entry.data) return entry.data;
|
|
2426
2478
|
}
|
|
2427
2479
|
}
|
|
2428
|
-
function
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
return safe_json_stringify({
|
|
2433
|
-
keys: Object.keys(headers).slice(0, 20),
|
|
2434
|
-
count: Object.keys(headers).length
|
|
2435
|
-
});
|
|
2436
|
-
}
|
|
2437
|
-
function summarize_provider_payload(payload) {
|
|
2438
|
-
return safe_json_stringify(summarize_value(payload));
|
|
2439
|
-
}
|
|
2440
|
-
function get_stop_reason(message) {
|
|
2441
|
-
if (!message || typeof message !== "object") return null;
|
|
2442
|
-
const stop_reason = message.stopReason;
|
|
2443
|
-
return typeof stop_reason === "string" ? stop_reason : null;
|
|
2444
|
-
}
|
|
2445
|
-
function get_error_message(message) {
|
|
2446
|
-
if (!message || typeof message !== "object") return null;
|
|
2447
|
-
const error_message = message.errorMessage;
|
|
2448
|
-
return typeof error_message === "string" ? error_message : null;
|
|
2449
|
-
}
|
|
2450
|
-
function infer_run_outcome(event) {
|
|
2451
|
-
const last_assistant = event.messages.filter((message) => message.role === "assistant").at(-1);
|
|
2452
|
-
const stop_reason = get_stop_reason(last_assistant);
|
|
2453
|
-
if (stop_reason === "error") return {
|
|
2454
|
-
success: false,
|
|
2455
|
-
error_message: get_error_message(last_assistant) ?? "agent error"
|
|
2456
|
-
};
|
|
2457
|
-
if (stop_reason === "aborted") return {
|
|
2458
|
-
success: false,
|
|
2459
|
-
error_message: get_error_message(last_assistant) ?? "agent aborted"
|
|
2460
|
-
};
|
|
2461
|
-
return {
|
|
2462
|
-
success: true,
|
|
2463
|
-
error_message: null
|
|
2464
|
-
};
|
|
2465
|
-
}
|
|
2466
|
-
function format_telemetry_status(options) {
|
|
2467
|
-
const override_label = options.override === void 0 ? "none" : options.override ? "--telemetry" : "--no-telemetry";
|
|
2468
|
-
return [
|
|
2469
|
-
`telemetry ${options.effective_enabled ? "enabled" : "disabled"} now`,
|
|
2470
|
-
`default ${options.saved_enabled ? "enabled" : "disabled"}`,
|
|
2471
|
-
`override ${override_label}`,
|
|
2472
|
-
`db ${options.db_path}`
|
|
2473
|
-
].join("\n");
|
|
2474
|
-
}
|
|
2475
|
-
function format_bytes(bytes) {
|
|
2476
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
2477
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
2478
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
2479
|
-
}
|
|
2480
|
-
function format_timestamp(timestamp) {
|
|
2481
|
-
return new Date(timestamp).toISOString();
|
|
2480
|
+
function sets_equal$1(a, b) {
|
|
2481
|
+
if (a.size !== b.size) return false;
|
|
2482
|
+
for (const value of a) if (!b.has(value)) return false;
|
|
2483
|
+
return true;
|
|
2482
2484
|
}
|
|
2483
|
-
function
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2485
|
+
function get_prompt_source_label(source) {
|
|
2486
|
+
switch (source) {
|
|
2487
|
+
case "builtin": return "built-in";
|
|
2488
|
+
case "user": return "user";
|
|
2489
|
+
case "project": return "project";
|
|
2490
|
+
}
|
|
2488
2491
|
}
|
|
2489
|
-
function
|
|
2490
|
-
|
|
2491
|
-
if (value === false) return "failure";
|
|
2492
|
-
return "unknown";
|
|
2492
|
+
function list_base_presets(presets) {
|
|
2493
|
+
return Object.values(presets).filter((preset) => preset.kind === "base").sort((a, b) => a.name.localeCompare(b.name));
|
|
2493
2494
|
}
|
|
2494
|
-
function
|
|
2495
|
-
return (
|
|
2496
|
-
if (token.startsWith("\"") && token.endsWith("\"") || token.startsWith("'") && token.endsWith("'")) return token.slice(1, -1);
|
|
2497
|
-
return token;
|
|
2498
|
-
});
|
|
2495
|
+
function list_layer_presets(presets) {
|
|
2496
|
+
return Object.values(presets).filter((preset) => preset.kind === "layer").sort((a, b) => a.name.localeCompare(b.name));
|
|
2499
2497
|
}
|
|
2500
|
-
function
|
|
2501
|
-
const
|
|
2502
|
-
const
|
|
2503
|
-
|
|
2504
|
-
|
|
2505
|
-
|
|
2506
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
else errors.push(`Unexpected argument: ${token}`);
|
|
2511
|
-
continue;
|
|
2512
|
-
}
|
|
2513
|
-
const key = token.slice(0, equals_index);
|
|
2514
|
-
const value = token.slice(equals_index + 1);
|
|
2515
|
-
switch (key) {
|
|
2516
|
-
case "eval_run_id":
|
|
2517
|
-
case "run":
|
|
2518
|
-
filters.eval_run_id = value;
|
|
2519
|
-
break;
|
|
2520
|
-
case "eval_case_id":
|
|
2521
|
-
case "case":
|
|
2522
|
-
filters.eval_case_id = value;
|
|
2523
|
-
break;
|
|
2524
|
-
case "eval_suite":
|
|
2525
|
-
case "suite":
|
|
2526
|
-
filters.eval_suite = value;
|
|
2527
|
-
break;
|
|
2528
|
-
case "success":
|
|
2529
|
-
if (value === "true") filters.success = true;
|
|
2530
|
-
else if (value === "false") filters.success = false;
|
|
2531
|
-
else if (value === "null") filters.success = null;
|
|
2532
|
-
else errors.push(`Invalid success value: ${value}. Use true, false, or null`);
|
|
2533
|
-
break;
|
|
2534
|
-
case "limit": {
|
|
2535
|
-
const parsed = Number.parseInt(value, 10);
|
|
2536
|
-
if (!Number.isFinite(parsed) || parsed <= 0) errors.push(`Invalid limit value: ${value}. Use a positive integer`);
|
|
2537
|
-
else filters.limit = parsed;
|
|
2538
|
-
break;
|
|
2539
|
-
}
|
|
2540
|
-
default: errors.push(`Unknown filter: ${key}`);
|
|
2498
|
+
function format_summary(active_base_name, active_layers, presets) {
|
|
2499
|
+
const lines = [`Base: ${active_base_name ?? "(none)"}`];
|
|
2500
|
+
const layer_names = [...active_layers].sort();
|
|
2501
|
+
if (layer_names.length === 0) lines.push("Layers: (none)");
|
|
2502
|
+
else {
|
|
2503
|
+
lines.push("Layers:");
|
|
2504
|
+
for (const name of layer_names) {
|
|
2505
|
+
const preset = presets[name];
|
|
2506
|
+
const description = preset?.description ? ` — ${preset.description}` : "";
|
|
2507
|
+
lines.push(`- ${name}${description}`);
|
|
2541
2508
|
}
|
|
2542
2509
|
}
|
|
2543
|
-
|
|
2544
|
-
return {
|
|
2545
|
-
subcommand,
|
|
2546
|
-
export_path,
|
|
2547
|
-
filters,
|
|
2548
|
-
errors
|
|
2549
|
-
};
|
|
2510
|
+
return lines.join("\n");
|
|
2550
2511
|
}
|
|
2551
|
-
function
|
|
2512
|
+
function format_active_details(active_base_name, active_layers, presets) {
|
|
2552
2513
|
const parts = [];
|
|
2553
|
-
if (
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2514
|
+
if (active_base_name) {
|
|
2515
|
+
const base = presets[active_base_name];
|
|
2516
|
+
if (base) {
|
|
2517
|
+
parts.push(`Base: ${base.name}`);
|
|
2518
|
+
if (base.description) parts.push(`Description: ${base.description}`);
|
|
2519
|
+
parts.push(`Source: ${get_prompt_source_label(base.source)}`);
|
|
2520
|
+
parts.push("", base.instructions.trim());
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
const layer_names = [...active_layers].sort();
|
|
2524
|
+
if (layer_names.length > 0) {
|
|
2525
|
+
if (parts.length > 0) parts.push("", "---", "");
|
|
2526
|
+
parts.push("Layers:");
|
|
2527
|
+
for (const name of layer_names) {
|
|
2528
|
+
const layer = presets[name];
|
|
2529
|
+
if (!layer) continue;
|
|
2530
|
+
parts.push(`- ${layer.name} (${get_prompt_source_label(layer.source)})`);
|
|
2531
|
+
if (layer.description) parts.push(` ${layer.description}`);
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
return parts.join("\n") || "No preset or layers active";
|
|
2559
2535
|
}
|
|
2560
|
-
function
|
|
2561
|
-
return
|
|
2562
|
-
|
|
2563
|
-
`schema v${options.stats.schema_version}`,
|
|
2564
|
-
`runs ${options.stats.runs}`,
|
|
2565
|
-
`turns ${options.stats.turns}`,
|
|
2566
|
-
`tool_calls ${options.stats.tool_calls}`,
|
|
2567
|
-
`provider_requests ${options.stats.provider_requests}`,
|
|
2568
|
-
`db_bytes ${format_bytes(options.stats.db_bytes)}`,
|
|
2569
|
-
`wal_bytes ${format_bytes(options.stats.wal_bytes)}`,
|
|
2570
|
-
`total_bytes ${format_bytes(options.stats.total_bytes)}`
|
|
2571
|
-
].join("\n");
|
|
2536
|
+
function get_footer_prompt_status(active_base_name, active_layers) {
|
|
2537
|
+
if (!active_base_name && active_layers.size === 0) return;
|
|
2538
|
+
return `prompt:${active_base_name ?? "none"}${active_layers.size > 0 ? ` +${active_layers.size}` : ""}`;
|
|
2572
2539
|
}
|
|
2573
|
-
function
|
|
2574
|
-
|
|
2575
|
-
`db ${options.db_path}`,
|
|
2576
|
-
`filters ${format_filter_summary(options.filters)}`,
|
|
2577
|
-
"no matching runs"
|
|
2578
|
-
].join("\n");
|
|
2579
|
-
return [
|
|
2580
|
-
`db ${options.db_path}`,
|
|
2581
|
-
`filters ${format_filter_summary(options.filters)}`,
|
|
2582
|
-
...options.runs.map((run) => [
|
|
2583
|
-
`${format_timestamp(run.started_at)} ${run.id}`,
|
|
2584
|
-
`status=${format_success(run.success)}`,
|
|
2585
|
-
`duration=${format_duration(run.duration_ms)}`,
|
|
2586
|
-
`turns=${run.turn_count}`,
|
|
2587
|
-
`tools=${run.tool_call_count}`,
|
|
2588
|
-
`tool_errors=${run.tool_error_count}`,
|
|
2589
|
-
`provider_requests=${run.provider_request_count}`,
|
|
2590
|
-
run.eval_run_id ? `eval_run_id=${run.eval_run_id}` : null,
|
|
2591
|
-
run.eval_case_id ? `eval_case_id=${run.eval_case_id}` : null,
|
|
2592
|
-
run.eval_suite ? `eval_suite=${run.eval_suite}` : null
|
|
2593
|
-
].filter(Boolean).join(" "))
|
|
2594
|
-
].join("\n");
|
|
2540
|
+
function sanitize_status_text(text) {
|
|
2541
|
+
return text.replace(/[\r\n\t]/g, " ").replace(/ +/g, " ").trim();
|
|
2595
2542
|
}
|
|
2596
|
-
function
|
|
2597
|
-
|
|
2543
|
+
function format_token_count(count) {
|
|
2544
|
+
if (count < 1e3) return count.toString();
|
|
2545
|
+
if (count < 1e4) return `${(count / 1e3).toFixed(1)}k`;
|
|
2546
|
+
if (count < 1e6) return `${Math.round(count / 1e3)}k`;
|
|
2547
|
+
if (count < 1e7) return `${(count / 1e6).toFixed(1)}M`;
|
|
2548
|
+
return `${Math.round(count / 1e6)}M`;
|
|
2598
2549
|
}
|
|
2599
|
-
|
|
2600
|
-
const
|
|
2601
|
-
|
|
2550
|
+
function get_current_thinking_level(ctx) {
|
|
2551
|
+
const entries = ctx.sessionManager.getEntries();
|
|
2552
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
2553
|
+
const entry = entries[i];
|
|
2554
|
+
if (entry.type === "thinking_level_change" && typeof entry.thinkingLevel === "string") return entry.thinkingLevel;
|
|
2555
|
+
}
|
|
2556
|
+
return ctx.model?.reasoning ? "high" : "off";
|
|
2602
2557
|
}
|
|
2603
|
-
function
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2558
|
+
function render_footer_lines(ctx, theme, footer_data, width, active_base_name, active_layers) {
|
|
2559
|
+
let total_input = 0;
|
|
2560
|
+
let total_output = 0;
|
|
2561
|
+
let total_cache_read = 0;
|
|
2562
|
+
let total_cache_write = 0;
|
|
2563
|
+
let total_cost = 0;
|
|
2564
|
+
for (const entry of ctx.sessionManager.getEntries()) if (entry.type === "message" && entry.message.role === "assistant") {
|
|
2565
|
+
total_input += entry.message.usage.input;
|
|
2566
|
+
total_output += entry.message.usage.output;
|
|
2567
|
+
total_cache_read += entry.message.usage.cacheRead;
|
|
2568
|
+
total_cache_write += entry.message.usage.cacheWrite;
|
|
2569
|
+
total_cost += entry.message.usage.cost.total;
|
|
2570
|
+
}
|
|
2571
|
+
const context_usage = ctx.getContextUsage();
|
|
2572
|
+
const context_window = context_usage?.contextWindow ?? ctx.model?.contextWindow ?? 0;
|
|
2573
|
+
const context_percent_value = context_usage?.percent ?? 0;
|
|
2574
|
+
const context_percent = context_usage?.percent !== null ? context_percent_value.toFixed(1) : "?";
|
|
2575
|
+
let pwd = ctx.cwd;
|
|
2576
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
2577
|
+
if (home && pwd.startsWith(home)) pwd = `~${pwd.slice(home.length)}`;
|
|
2578
|
+
const branch = footer_data.getGitBranch();
|
|
2579
|
+
if (branch) pwd = `${pwd} (${branch})`;
|
|
2580
|
+
const session_name = ctx.sessionManager.getSessionName();
|
|
2581
|
+
if (session_name) pwd = `${pwd} • ${session_name}`;
|
|
2582
|
+
const stats_parts = [];
|
|
2583
|
+
if (total_input) stats_parts.push(`↑${format_token_count(total_input)}`);
|
|
2584
|
+
if (total_output) stats_parts.push(`↓${format_token_count(total_output)}`);
|
|
2585
|
+
if (total_cache_read) stats_parts.push(`R${format_token_count(total_cache_read)}`);
|
|
2586
|
+
if (total_cache_write) stats_parts.push(`W${format_token_count(total_cache_write)}`);
|
|
2587
|
+
const using_subscription = ctx.model ? ctx.modelRegistry.isUsingOAuth(ctx.model) : false;
|
|
2588
|
+
if (total_cost || using_subscription) stats_parts.push(`$${total_cost.toFixed(3)}${using_subscription ? " (sub)" : ""}`);
|
|
2589
|
+
const context_percent_display = context_percent === "?" ? `?/${format_token_count(context_window)}` : `${context_percent}%/${format_token_count(context_window)}`;
|
|
2590
|
+
let context_percent_str = context_percent_display;
|
|
2591
|
+
if (context_percent_value > 90) context_percent_str = theme.fg("error", context_percent_display);
|
|
2592
|
+
else if (context_percent_value > 70) context_percent_str = theme.fg("warning", context_percent_display);
|
|
2593
|
+
stats_parts.push(context_percent_str);
|
|
2594
|
+
let stats_left = stats_parts.join(" ");
|
|
2595
|
+
let stats_left_width = visibleWidth(stats_left);
|
|
2596
|
+
if (stats_left_width > width) {
|
|
2597
|
+
stats_left = truncateToWidth(stats_left, width, "...");
|
|
2598
|
+
stats_left_width = visibleWidth(stats_left);
|
|
2599
|
+
}
|
|
2600
|
+
const model_name = ctx.model?.id || "no-model";
|
|
2601
|
+
const thinking_level = get_current_thinking_level(ctx);
|
|
2602
|
+
let right_side_without_provider = model_name;
|
|
2603
|
+
if (ctx.model?.reasoning) right_side_without_provider = thinking_level === "off" ? `${model_name} • thinking off` : `${model_name} • ${thinking_level}`;
|
|
2604
|
+
let right_side = right_side_without_provider;
|
|
2605
|
+
if (footer_data.getAvailableProviderCount() > 1 && ctx.model) {
|
|
2606
|
+
right_side = `(${ctx.model.provider}) ${right_side_without_provider}`;
|
|
2607
|
+
if (stats_left_width + 2 + visibleWidth(right_side) > width) right_side = right_side_without_provider;
|
|
2608
|
+
}
|
|
2609
|
+
const right_side_width = visibleWidth(right_side);
|
|
2610
|
+
const total_needed = stats_left_width + 2 + right_side_width;
|
|
2611
|
+
let stats_line;
|
|
2612
|
+
if (total_needed <= width) {
|
|
2613
|
+
const padding = " ".repeat(width - stats_left_width - right_side_width);
|
|
2614
|
+
stats_line = stats_left + padding + right_side;
|
|
2615
|
+
} else {
|
|
2616
|
+
const available_for_right = width - stats_left_width - 2;
|
|
2617
|
+
if (available_for_right > 0) {
|
|
2618
|
+
const truncated_right = truncateToWidth(right_side, available_for_right, "");
|
|
2619
|
+
const truncated_right_width = visibleWidth(truncated_right);
|
|
2620
|
+
const padding = " ".repeat(Math.max(0, width - stats_left_width - truncated_right_width));
|
|
2621
|
+
stats_line = stats_left + padding + truncated_right;
|
|
2622
|
+
} else stats_line = stats_left;
|
|
2623
|
+
}
|
|
2624
|
+
const dim_stats_left = theme.fg("dim", stats_left);
|
|
2625
|
+
const remainder = stats_line.slice(stats_left.length);
|
|
2626
|
+
const dim_remainder = theme.fg("dim", remainder);
|
|
2627
|
+
const lines = [truncateToWidth(theme.fg("dim", pwd), width, theme.fg("dim", "...")), dim_stats_left + dim_remainder];
|
|
2628
|
+
const prompt_status = get_footer_prompt_status(active_base_name, active_layers);
|
|
2629
|
+
if (prompt_status) {
|
|
2630
|
+
const themed_status = theme.fg("dim", prompt_status);
|
|
2631
|
+
const status_width = visibleWidth(themed_status);
|
|
2632
|
+
const aligned_status = status_width >= width ? truncateToWidth(themed_status, width, theme.fg("dim", "...")) : `${" ".repeat(width - status_width)}${themed_status}`;
|
|
2633
|
+
lines.push(aligned_status);
|
|
2634
|
+
}
|
|
2635
|
+
const other_statuses = Array.from(footer_data.getExtensionStatuses().entries()).filter(([key]) => key !== "preset").sort(([a], [b]) => a.localeCompare(b)).map(([, text]) => sanitize_status_text(text));
|
|
2636
|
+
if (other_statuses.length > 0) lines.push(truncateToWidth(other_statuses.join(" "), width, theme.fg("dim", "...")));
|
|
2637
|
+
return lines;
|
|
2638
|
+
}
|
|
2639
|
+
function set_status(ctx, active_base_name, active_layers) {
|
|
2640
|
+
ctx.ui.setStatus("preset", void 0);
|
|
2641
|
+
if (!ctx.hasUI) return;
|
|
2642
|
+
ctx.ui.setFooter((tui, theme, footer_data) => {
|
|
2643
|
+
return {
|
|
2644
|
+
dispose: footer_data.onBranchChange(() => tui.requestRender()),
|
|
2645
|
+
invalidate() {},
|
|
2646
|
+
render(width) {
|
|
2647
|
+
return render_footer_lines(ctx, theme, footer_data, width, active_base_name, active_layers);
|
|
2648
|
+
}
|
|
2615
2649
|
};
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
|
|
2630
|
-
|
|
2650
|
+
});
|
|
2651
|
+
}
|
|
2652
|
+
function persist_state(pi, ctx, active_base_name, active_layers) {
|
|
2653
|
+
const state = {
|
|
2654
|
+
base_name: active_base_name ?? null,
|
|
2655
|
+
layer_names: [...active_layers].sort()
|
|
2656
|
+
};
|
|
2657
|
+
pi.appendEntry(PRESET_STATE_TYPE, state);
|
|
2658
|
+
save_persisted_prompt_state(ctx.cwd, state);
|
|
2659
|
+
}
|
|
2660
|
+
function normalize_active_state(presets, active_base_name, active_layers) {
|
|
2661
|
+
return {
|
|
2662
|
+
active_base_name: active_base_name && presets[active_base_name]?.kind === "base" ? active_base_name : void 0,
|
|
2663
|
+
active_layers: new Set([...active_layers].filter((name) => presets[name]?.kind === "layer"))
|
|
2664
|
+
};
|
|
2665
|
+
}
|
|
2666
|
+
function parse_preset_flag(flag) {
|
|
2667
|
+
return flag.split(",").map((item) => item.trim()).filter(Boolean);
|
|
2668
|
+
}
|
|
2669
|
+
function is_subcommand(command) {
|
|
2670
|
+
return [
|
|
2671
|
+
"list",
|
|
2672
|
+
"show",
|
|
2673
|
+
"clear",
|
|
2674
|
+
"edit",
|
|
2675
|
+
"delete",
|
|
2676
|
+
"reset",
|
|
2677
|
+
"reload",
|
|
2678
|
+
"base",
|
|
2679
|
+
"enable",
|
|
2680
|
+
"disable",
|
|
2681
|
+
"toggle"
|
|
2682
|
+
].includes(command);
|
|
2683
|
+
}
|
|
2684
|
+
async function prompt_presets(pi) {
|
|
2685
|
+
let presets = {};
|
|
2686
|
+
let active_base_name;
|
|
2687
|
+
let active_layers = /* @__PURE__ */ new Set();
|
|
2688
|
+
function get_base(name) {
|
|
2689
|
+
return name ? presets[name] : void 0;
|
|
2690
|
+
}
|
|
2691
|
+
function get_layer(name) {
|
|
2692
|
+
const preset = presets[name];
|
|
2693
|
+
return preset?.kind === "layer" ? preset : void 0;
|
|
2694
|
+
}
|
|
2695
|
+
function commit_state(ctx, next_base_name, next_layers, options) {
|
|
2696
|
+
active_base_name = next_base_name;
|
|
2697
|
+
active_layers = new Set(next_layers);
|
|
2698
|
+
set_status(ctx, active_base_name, active_layers);
|
|
2699
|
+
if (options?.persist !== false) persist_state(pi, ctx, active_base_name, active_layers);
|
|
2700
|
+
if (options?.notify) ctx.ui.notify(options.notify, "info");
|
|
2701
|
+
}
|
|
2702
|
+
function activate_base(name, ctx, options) {
|
|
2703
|
+
if (!name) {
|
|
2704
|
+
commit_state(ctx, void 0, active_layers, {
|
|
2705
|
+
persist: options?.persist,
|
|
2706
|
+
notify: "Base preset cleared"
|
|
2631
2707
|
});
|
|
2632
|
-
|
|
2633
|
-
active_turns.clear();
|
|
2634
|
-
provider_request_ids.length = 0;
|
|
2708
|
+
return true;
|
|
2635
2709
|
}
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2710
|
+
const preset = get_base(name);
|
|
2711
|
+
if (!preset) {
|
|
2712
|
+
ctx.ui.notify(`Unknown base preset: ${name}`, "warning");
|
|
2713
|
+
return false;
|
|
2640
2714
|
}
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2715
|
+
commit_state(ctx, preset.name, active_layers, {
|
|
2716
|
+
persist: options?.persist,
|
|
2717
|
+
notify: `Base preset "${preset.name}" activated`
|
|
2718
|
+
});
|
|
2719
|
+
return true;
|
|
2720
|
+
}
|
|
2721
|
+
function set_layer_enabled(name, enabled, ctx, options) {
|
|
2722
|
+
const preset = get_layer(name);
|
|
2723
|
+
if (!preset) {
|
|
2724
|
+
ctx.ui.notify(`Unknown prompt layer: ${name}`, "warning");
|
|
2725
|
+
return false;
|
|
2644
2726
|
}
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
return;
|
|
2673
|
-
}
|
|
2674
|
-
if (subcommand === "stats") {
|
|
2675
|
-
if (!existsSync(db_path)) {
|
|
2676
|
-
command_message(ctx, `No telemetry database at ${db_path}`);
|
|
2677
|
-
return;
|
|
2678
|
-
}
|
|
2679
|
-
const stats_store = store ?? await load_store(db_path);
|
|
2680
|
-
const should_close_after = stats_store !== store;
|
|
2681
|
-
try {
|
|
2682
|
-
command_message(ctx, format_telemetry_stats({
|
|
2683
|
-
db_path,
|
|
2684
|
-
stats: stats_store.get_stats()
|
|
2685
|
-
}));
|
|
2686
|
-
} finally {
|
|
2687
|
-
if (should_close_after) stats_store.close();
|
|
2688
|
-
}
|
|
2689
|
-
return;
|
|
2690
|
-
}
|
|
2691
|
-
if (subcommand === "query" || subcommand === "export") {
|
|
2692
|
-
if (!existsSync(db_path)) {
|
|
2693
|
-
command_message(ctx, `No telemetry database at ${db_path}`);
|
|
2694
|
-
return;
|
|
2695
|
-
}
|
|
2696
|
-
const query_store = store ?? await load_store(db_path);
|
|
2697
|
-
const should_close_after = query_store !== store;
|
|
2698
|
-
try {
|
|
2699
|
-
const runs = query_store.query_runs(parsed.filters);
|
|
2700
|
-
if (subcommand === "query") {
|
|
2701
|
-
command_message(ctx, format_telemetry_query_results({
|
|
2702
|
-
db_path,
|
|
2703
|
-
filters: parsed.filters,
|
|
2704
|
-
runs
|
|
2705
|
-
}));
|
|
2706
|
-
return;
|
|
2707
|
-
}
|
|
2708
|
-
const export_path = resolve(cwd, parsed.export_path ?? get_default_telemetry_export_path(cwd));
|
|
2709
|
-
mkdirSync(dirname(export_path), { recursive: true });
|
|
2710
|
-
writeFileSync(export_path, JSON.stringify({
|
|
2711
|
-
exported_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2712
|
-
db_path,
|
|
2713
|
-
schema_version: query_store.get_stats().schema_version,
|
|
2714
|
-
filters: parsed.filters,
|
|
2715
|
-
runs
|
|
2716
|
-
}, null, 2), "utf-8");
|
|
2717
|
-
command_message(ctx, `Exported ${runs.length} telemetry run${runs.length === 1 ? "" : "s"} to ${export_path}`);
|
|
2718
|
-
return;
|
|
2719
|
-
} finally {
|
|
2720
|
-
if (should_close_after) query_store.close();
|
|
2721
|
-
}
|
|
2722
|
-
}
|
|
2723
|
-
if (subcommand === "path") {
|
|
2724
|
-
command_message(ctx, db_path);
|
|
2725
|
-
return;
|
|
2726
|
-
}
|
|
2727
|
-
const next_enabled = subcommand === "on";
|
|
2728
|
-
config = {
|
|
2729
|
-
...config,
|
|
2730
|
-
enabled: next_enabled
|
|
2731
|
-
};
|
|
2732
|
-
save_telemetry_config(config);
|
|
2733
|
-
if (options.enabled !== void 0) {
|
|
2734
|
-
command_message(ctx, [`Saved default telemetry ${next_enabled ? "enabled" : "disabled"}.`, `Current process still uses ${options.enabled ? "--telemetry" : "--no-telemetry"}.`].join(" "));
|
|
2735
|
-
return;
|
|
2736
|
-
}
|
|
2737
|
-
effective_enabled = next_enabled;
|
|
2738
|
-
if (effective_enabled) {
|
|
2739
|
-
await ensure_store();
|
|
2740
|
-
command_message(ctx, `Telemetry enabled. Writing to ${db_path}`);
|
|
2741
|
-
return;
|
|
2742
|
-
}
|
|
2743
|
-
finish_active_run_on_disable("telemetry disabled");
|
|
2744
|
-
close_store();
|
|
2745
|
-
command_message(ctx, "Telemetry disabled.");
|
|
2727
|
+
const next_layers = new Set(active_layers);
|
|
2728
|
+
if (enabled) next_layers.add(preset.name);
|
|
2729
|
+
else next_layers.delete(preset.name);
|
|
2730
|
+
commit_state(ctx, active_base_name, next_layers, {
|
|
2731
|
+
persist: options?.persist,
|
|
2732
|
+
notify: enabled ? `Layer "${preset.name}" enabled` : `Layer "${preset.name}" disabled`
|
|
2733
|
+
});
|
|
2734
|
+
return true;
|
|
2735
|
+
}
|
|
2736
|
+
function toggle_layer(name, ctx, options) {
|
|
2737
|
+
return set_layer_enabled(name, !active_layers.has(name), ctx, options);
|
|
2738
|
+
}
|
|
2739
|
+
async function edit_preset(name, ctx) {
|
|
2740
|
+
const existing = presets[name];
|
|
2741
|
+
const kind_choice = await ctx.ui.select("Preset kind", [existing?.kind === "layer" ? "layer (current)" : "base (current)", existing?.kind === "layer" ? "base" : "layer"]);
|
|
2742
|
+
if (!kind_choice) return;
|
|
2743
|
+
const kind = kind_choice.startsWith("layer") ? "layer" : "base";
|
|
2744
|
+
const description = await ctx.ui.input(`Description for ${name}`, existing?.description ?? "");
|
|
2745
|
+
if (description === void 0) return;
|
|
2746
|
+
const instructions = await ctx.ui.editor(`Edit ${kind} preset: ${name}`, existing?.instructions ?? "");
|
|
2747
|
+
if (instructions === void 0) return;
|
|
2748
|
+
save_project_prompt_presets(ctx.cwd, {
|
|
2749
|
+
...read_prompt_presets_file(get_project_presets_path(ctx.cwd)),
|
|
2750
|
+
[name]: {
|
|
2751
|
+
kind,
|
|
2752
|
+
instructions,
|
|
2753
|
+
...description.trim() ? { description: description.trim() } : {}
|
|
2746
2754
|
}
|
|
2747
2755
|
});
|
|
2748
|
-
|
|
2749
|
-
|
|
2756
|
+
presets = load_prompt_presets(ctx.cwd);
|
|
2757
|
+
const normalized = normalize_active_state(presets, active_base_name, active_layers);
|
|
2758
|
+
active_base_name = normalized.active_base_name;
|
|
2759
|
+
active_layers = normalized.active_layers;
|
|
2760
|
+
if (kind === "base") activate_base(name, ctx);
|
|
2761
|
+
else set_layer_enabled(name, true, ctx);
|
|
2762
|
+
ctx.ui.notify(`Saved preset "${name}" to ${get_project_presets_path(ctx.cwd)}`, "info");
|
|
2763
|
+
}
|
|
2764
|
+
function remove_custom_preset(name, ctx, mode) {
|
|
2765
|
+
const result = remove_project_prompt_preset(ctx.cwd, name);
|
|
2766
|
+
if (!result.removed) {
|
|
2767
|
+
ctx.ui.notify(`No project-local preset named "${name}" to ${mode}`, "warning");
|
|
2768
|
+
return;
|
|
2769
|
+
}
|
|
2770
|
+
presets = load_prompt_presets(ctx.cwd);
|
|
2771
|
+
const normalized = normalize_active_state(presets, active_base_name, active_layers);
|
|
2772
|
+
active_base_name = normalized.active_base_name;
|
|
2773
|
+
active_layers = normalized.active_layers;
|
|
2774
|
+
set_status(ctx, active_base_name, active_layers);
|
|
2775
|
+
persist_state(pi, ctx, active_base_name, active_layers);
|
|
2776
|
+
const fallback = presets[name];
|
|
2777
|
+
if (mode === "reset" && fallback) {
|
|
2778
|
+
ctx.ui.notify(`Reset "${name}" to ${get_prompt_source_label(fallback.source)} preset`, "info");
|
|
2779
|
+
return;
|
|
2780
|
+
}
|
|
2781
|
+
ctx.ui.notify(result.remaining === 0 ? `Removed "${name}" and deleted ${result.path}` : `Removed "${name}" from ${result.path}`, "info");
|
|
2782
|
+
}
|
|
2783
|
+
async function show_manager(ctx) {
|
|
2784
|
+
const base_presets = list_base_presets(presets);
|
|
2785
|
+
const layer_presets = list_layer_presets(presets);
|
|
2786
|
+
if (base_presets.length === 0 && layer_presets.length === 0) {
|
|
2787
|
+
ctx.ui.notify("No prompt presets available", "warning");
|
|
2788
|
+
return;
|
|
2789
|
+
}
|
|
2790
|
+
const initial_base = active_base_name;
|
|
2791
|
+
const initial_layers = new Set(active_layers);
|
|
2792
|
+
let selected_base = active_base_name;
|
|
2793
|
+
const enabled_layers = new Set(active_layers);
|
|
2794
|
+
const items = [];
|
|
2795
|
+
const base_ids = /* @__PURE__ */ new Set();
|
|
2796
|
+
const layer_ids = /* @__PURE__ */ new Set();
|
|
2797
|
+
items.push({
|
|
2798
|
+
id: "__header_base__",
|
|
2799
|
+
label: `── Base presets (${base_presets.length + 1}) ──`,
|
|
2800
|
+
description: "",
|
|
2801
|
+
currentValue: ""
|
|
2750
2802
|
});
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
active_store.insert_run({
|
|
2758
|
-
id: run_id,
|
|
2759
|
-
session_file: get_session_file(ctx),
|
|
2760
|
-
cwd: ctx.cwd,
|
|
2761
|
-
started_at: now(),
|
|
2762
|
-
model_provider: model_identity.provider,
|
|
2763
|
-
model_id: model_identity.id,
|
|
2764
|
-
eval_run_id: eval_metadata.run_id,
|
|
2765
|
-
eval_case_id: eval_metadata.case_id,
|
|
2766
|
-
eval_attempt: eval_metadata.attempt,
|
|
2767
|
-
eval_suite: eval_metadata.suite
|
|
2768
|
-
});
|
|
2769
|
-
active_run = { id: run_id };
|
|
2770
|
-
active_turns.clear();
|
|
2771
|
-
provider_request_ids.length = 0;
|
|
2803
|
+
items.push({
|
|
2804
|
+
id: NONE_BASE_ID,
|
|
2805
|
+
label: "(none)",
|
|
2806
|
+
description: "No active base preset",
|
|
2807
|
+
currentValue: UNSELECTED,
|
|
2808
|
+
values: [SELECTED, UNSELECTED]
|
|
2772
2809
|
});
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2810
|
+
base_ids.add(NONE_BASE_ID);
|
|
2811
|
+
for (const preset of base_presets) {
|
|
2812
|
+
items.push({
|
|
2813
|
+
id: preset.name,
|
|
2814
|
+
label: preset.name,
|
|
2815
|
+
description: [`${get_prompt_source_label(preset.source)} • ${preset.description ?? "base preset"}`].join("\n"),
|
|
2816
|
+
currentValue: UNSELECTED,
|
|
2817
|
+
values: [SELECTED, UNSELECTED]
|
|
2781
2818
|
});
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2819
|
+
base_ids.add(preset.name);
|
|
2820
|
+
}
|
|
2821
|
+
items.push({
|
|
2822
|
+
id: "__header_layers__",
|
|
2823
|
+
label: `── Prompt layers (${layer_presets.length}) ──`,
|
|
2824
|
+
description: "",
|
|
2825
|
+
currentValue: ""
|
|
2785
2826
|
});
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2790
|
-
|
|
2791
|
-
|
|
2792
|
-
|
|
2793
|
-
turn_index: event.turnIndex,
|
|
2794
|
-
started_at: event.timestamp
|
|
2827
|
+
for (const preset of layer_presets) {
|
|
2828
|
+
items.push({
|
|
2829
|
+
id: preset.name,
|
|
2830
|
+
label: preset.name,
|
|
2831
|
+
description: [`${get_prompt_source_label(preset.source)} • ${preset.description ?? "layer"}`].join("\n"),
|
|
2832
|
+
currentValue: DISABLED$1,
|
|
2833
|
+
values: [ENABLED$1, DISABLED$1]
|
|
2795
2834
|
});
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2799
|
-
if (!
|
|
2800
|
-
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
|
|
2835
|
+
layer_ids.add(preset.name);
|
|
2836
|
+
}
|
|
2837
|
+
function sync_values() {
|
|
2838
|
+
for (const item of items) if (base_ids.has(item.id)) item.currentValue = item.id === NONE_BASE_ID && !selected_base || item.id === selected_base ? SELECTED : UNSELECTED;
|
|
2839
|
+
else if (layer_ids.has(item.id)) item.currentValue = enabled_layers.has(item.id) ? ENABLED$1 : DISABLED$1;
|
|
2840
|
+
}
|
|
2841
|
+
sync_values();
|
|
2842
|
+
await ctx.ui.custom((tui, theme, _kb, done) => {
|
|
2843
|
+
const list = new SettingsList(items, Math.min(Math.max(items.length + 4, 8), 24), {
|
|
2844
|
+
cursor: theme.fg("accent", "›"),
|
|
2845
|
+
label: (text, selected) => {
|
|
2846
|
+
if (text.startsWith("──") && text.endsWith("──")) return theme.fg("dim", theme.bold(text));
|
|
2847
|
+
return selected ? theme.fg("accent", text) : text;
|
|
2848
|
+
},
|
|
2849
|
+
value: (text, selected) => {
|
|
2850
|
+
const color = text === ENABLED$1 || text === SELECTED ? "success" : "dim";
|
|
2851
|
+
const rendered = theme.fg(color, text);
|
|
2852
|
+
return selected ? theme.bold(theme.fg("accent", rendered)) : rendered;
|
|
2853
|
+
},
|
|
2854
|
+
description: (text) => theme.fg("muted", text),
|
|
2855
|
+
hint: (text) => theme.fg("dim", text)
|
|
2856
|
+
}, (id, new_value) => {
|
|
2857
|
+
if (id.startsWith("__header_")) return;
|
|
2858
|
+
if (base_ids.has(id)) {
|
|
2859
|
+
selected_base = new_value === SELECTED && id !== NONE_BASE_ID ? id : void 0;
|
|
2860
|
+
sync_values();
|
|
2861
|
+
return;
|
|
2862
|
+
}
|
|
2863
|
+
if (layer_ids.has(id)) {
|
|
2864
|
+
if (new_value === ENABLED$1) enabled_layers.add(id);
|
|
2865
|
+
else enabled_layers.delete(id);
|
|
2866
|
+
sync_values();
|
|
2867
|
+
}
|
|
2868
|
+
}, () => done(void 0), { enableSearch: true });
|
|
2869
|
+
const container = new Container();
|
|
2870
|
+
container.addChild({
|
|
2871
|
+
render: () => [
|
|
2872
|
+
theme.fg("accent", theme.bold("Prompt presets")),
|
|
2873
|
+
theme.fg("muted", `base: ${selected_base ?? "(none)"} • ${enabled_layers.size} layer(s) enabled`),
|
|
2874
|
+
""
|
|
2875
|
+
],
|
|
2876
|
+
invalidate: () => {}
|
|
2805
2877
|
});
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2813
|
-
run_id: active_run.id,
|
|
2814
|
-
turn_id: current_turn?.id ?? null,
|
|
2815
|
-
tool_name: event.toolName,
|
|
2816
|
-
started_at: now(),
|
|
2817
|
-
args_summary_json: summarize_tool_args(event.toolName, event.args)
|
|
2878
|
+
container.addChild({
|
|
2879
|
+
render(width) {
|
|
2880
|
+
return list.render(width);
|
|
2881
|
+
},
|
|
2882
|
+
invalidate() {
|
|
2883
|
+
list.invalidate();
|
|
2884
|
+
}
|
|
2818
2885
|
});
|
|
2886
|
+
container.addChild(new Text(theme.fg("dim", "search filters • enter toggles • esc close"), 0, 1));
|
|
2887
|
+
return {
|
|
2888
|
+
render(width) {
|
|
2889
|
+
return container.render(width);
|
|
2890
|
+
},
|
|
2891
|
+
invalidate() {
|
|
2892
|
+
container.invalidate();
|
|
2893
|
+
},
|
|
2894
|
+
handleInput(data) {
|
|
2895
|
+
list.handleInput(data);
|
|
2896
|
+
tui.requestRender();
|
|
2897
|
+
}
|
|
2898
|
+
};
|
|
2819
2899
|
});
|
|
2820
|
-
|
|
2821
|
-
if (!store || !active_run) return;
|
|
2822
|
-
store.note_tool_update(event.toolCallId);
|
|
2823
|
-
});
|
|
2824
|
-
pi.on("tool_execution_end", async (event) => {
|
|
2825
|
-
if (!store || !active_run) return;
|
|
2826
|
-
store.finish_tool_call({
|
|
2827
|
-
tool_call_id: event.toolCallId,
|
|
2828
|
-
ended_at: now(),
|
|
2829
|
-
is_error: event.isError,
|
|
2830
|
-
result_summary_json: summarize_tool_result(event.result),
|
|
2831
|
-
error_message: event.isError && event.result != null ? safe_json_stringify(summarize_value(event.result)) : null
|
|
2832
|
-
});
|
|
2833
|
-
});
|
|
2834
|
-
pi.on("before_provider_request", async (event) => {
|
|
2835
|
-
if (!store || !active_run) return;
|
|
2836
|
-
const request_id = randomUUID();
|
|
2837
|
-
const current_turn = [...active_turns.values()].at(-1);
|
|
2838
|
-
store.insert_provider_request({
|
|
2839
|
-
id: request_id,
|
|
2840
|
-
run_id: active_run.id,
|
|
2841
|
-
turn_id: current_turn?.id ?? null,
|
|
2842
|
-
started_at: now(),
|
|
2843
|
-
payload_summary_json: summarize_provider_payload(event.payload)
|
|
2844
|
-
});
|
|
2845
|
-
provider_request_ids.push(request_id);
|
|
2846
|
-
});
|
|
2847
|
-
pi.on("after_provider_response", async (event) => {
|
|
2848
|
-
if (!store || !active_run) return;
|
|
2849
|
-
const request_id = provider_request_ids.shift();
|
|
2850
|
-
if (!request_id) return;
|
|
2851
|
-
store.finish_provider_request({
|
|
2852
|
-
id: request_id,
|
|
2853
|
-
ended_at: now(),
|
|
2854
|
-
status_code: event.status,
|
|
2855
|
-
headers_json: summarize_headers(event.headers)
|
|
2856
|
-
});
|
|
2857
|
-
});
|
|
2858
|
-
pi.on("session_shutdown", async () => {
|
|
2859
|
-
if (store && active_run) store.finish_run({
|
|
2860
|
-
id: active_run.id,
|
|
2861
|
-
ended_at: now(),
|
|
2862
|
-
success: null,
|
|
2863
|
-
error_message: "session shutdown"
|
|
2864
|
-
});
|
|
2865
|
-
close_store();
|
|
2866
|
-
active_run = null;
|
|
2867
|
-
active_turns.clear();
|
|
2868
|
-
provider_request_ids.length = 0;
|
|
2869
|
-
});
|
|
2870
|
-
};
|
|
2871
|
-
}
|
|
2872
|
-
create_telemetry_extension();
|
|
2873
|
-
//#endregion
|
|
2874
|
-
//#region src/extensions/prompt-presets.ts
|
|
2875
|
-
const PRESET_STATE_TYPE = "prompt-preset-state";
|
|
2876
|
-
const ENABLED$1 = "[x]";
|
|
2877
|
-
const DISABLED$1 = "[ ]";
|
|
2878
|
-
const SELECTED = "(x)";
|
|
2879
|
-
const UNSELECTED = "( )";
|
|
2880
|
-
const NONE_BASE_ID = "__base_none__";
|
|
2881
|
-
const DEFAULT_PROMPT_PRESETS = {
|
|
2882
|
-
terse: {
|
|
2883
|
-
kind: "base",
|
|
2884
|
-
description: "Short, direct, no fluff",
|
|
2885
|
-
instructions: "Be concise and direct. Default to the shortest response that fully solves the user's request. No purple prose, no filler, no repetitive caveats. Prefer a short paragraph or a few bullets. Only include extra detail when it materially affects the decision, implementation, or next step."
|
|
2886
|
-
},
|
|
2887
|
-
standard: {
|
|
2888
|
-
kind: "base",
|
|
2889
|
-
description: "Clear and concise with key context",
|
|
2890
|
-
instructions: "Be clear, direct, and concise. Include only the reasoning and implementation details that matter. Avoid filler, grandstanding, and ornamental language. Use bullets when they improve scanability."
|
|
2891
|
-
},
|
|
2892
|
-
detailed: {
|
|
2893
|
-
kind: "base",
|
|
2894
|
-
description: "More explanation when nuance matters",
|
|
2895
|
-
instructions: "Be thorough when the task is complex or tradeoffs matter, but stay practical. Explain only the details that help the user decide, verify, or implement. Avoid purple prose and unnecessary scene-setting."
|
|
2896
|
-
},
|
|
2897
|
-
"no-purple-prose": {
|
|
2898
|
-
kind: "layer",
|
|
2899
|
-
description: "Strip out ornamental language",
|
|
2900
|
-
instructions: "Do not use purple prose, flourish, motivational filler, or theatrical transitions. Prefer plain language and concrete statements."
|
|
2901
|
-
},
|
|
2902
|
-
bullets: {
|
|
2903
|
-
kind: "layer",
|
|
2904
|
-
description: "Prefer short bullets when useful",
|
|
2905
|
-
instructions: "When presenting options, findings, or steps, prefer short bullet lists over long paragraphs."
|
|
2906
|
-
},
|
|
2907
|
-
"clarify-first": {
|
|
2908
|
-
kind: "layer",
|
|
2909
|
-
description: "Ask brief clarifying questions when requirements are ambiguous",
|
|
2910
|
-
instructions: "If the request is materially ambiguous, ask the minimum clarifying question(s) needed before proceeding. Do not ask unnecessary questions."
|
|
2911
|
-
},
|
|
2912
|
-
"include-risks": {
|
|
2913
|
-
kind: "layer",
|
|
2914
|
-
description: "Call out notable risks or tradeoffs",
|
|
2915
|
-
instructions: "When making a recommendation or implementation plan, briefly mention the key risk, tradeoff, or caveat if one materially matters."
|
|
2900
|
+
if (selected_base !== initial_base || !sets_equal$1(initial_layers, enabled_layers)) commit_state(ctx, selected_base, enabled_layers, { notify: "Updated prompt preset selection" });
|
|
2916
2901
|
}
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
|
|
2902
|
+
pi.registerFlag("preset", {
|
|
2903
|
+
description: "Activate prompt config on startup. Accepts a base preset or comma-separated preset/layer names.",
|
|
2904
|
+
type: "string"
|
|
2905
|
+
});
|
|
2906
|
+
pi.registerCommand("preset", {
|
|
2907
|
+
description: "Manage base prompt presets and prompt layers",
|
|
2908
|
+
getArgumentCompletions: (prefix) => {
|
|
2909
|
+
const trimmed = prefix.trim();
|
|
2910
|
+
const parts = trimmed ? trimmed.split(/\s+/) : [];
|
|
2911
|
+
const base_names = list_base_presets(presets).map((preset) => preset.name);
|
|
2912
|
+
const layer_names = list_layer_presets(presets).map((preset) => preset.name);
|
|
2913
|
+
const all_names = [...base_names, ...layer_names];
|
|
2914
|
+
if (parts.length <= 1) {
|
|
2915
|
+
const query = parts[0] ?? "";
|
|
2916
|
+
return [...[
|
|
2917
|
+
"list",
|
|
2918
|
+
"show",
|
|
2919
|
+
"clear",
|
|
2920
|
+
"edit",
|
|
2921
|
+
"delete",
|
|
2922
|
+
"reset",
|
|
2923
|
+
"reload",
|
|
2924
|
+
"base",
|
|
2925
|
+
"enable",
|
|
2926
|
+
"disable",
|
|
2927
|
+
"toggle"
|
|
2928
|
+
].filter((item) => item.startsWith(query)).map((item) => ({
|
|
2929
|
+
value: item,
|
|
2930
|
+
label: item
|
|
2931
|
+
})), ...all_names.filter((item) => item.startsWith(query)).map((item) => ({
|
|
2932
|
+
value: item,
|
|
2933
|
+
label: item
|
|
2934
|
+
}))];
|
|
2935
|
+
}
|
|
2936
|
+
const command = parts[0];
|
|
2937
|
+
const query = parts.slice(1).join(" ");
|
|
2938
|
+
if (command === "base") return base_names.filter((item) => item.startsWith(query)).map((item) => ({
|
|
2939
|
+
value: `base ${item}`,
|
|
2940
|
+
label: item
|
|
2941
|
+
}));
|
|
2942
|
+
if ([
|
|
2943
|
+
"enable",
|
|
2944
|
+
"disable",
|
|
2945
|
+
"toggle"
|
|
2946
|
+
].includes(command)) return layer_names.filter((item) => item.startsWith(query)).map((item) => ({
|
|
2947
|
+
value: `${command} ${item}`,
|
|
2948
|
+
label: item
|
|
2949
|
+
}));
|
|
2950
|
+
if (command === "edit") return all_names.filter((item) => item.startsWith(query)).map((item) => ({
|
|
2951
|
+
value: `edit ${item}`,
|
|
2952
|
+
label: item
|
|
2953
|
+
}));
|
|
2954
|
+
if (["delete", "reset"].includes(command)) return all_names.filter((item) => item.startsWith(query)).map((item) => ({
|
|
2955
|
+
value: `${command} ${item}`,
|
|
2956
|
+
label: item
|
|
2957
|
+
}));
|
|
2958
|
+
return null;
|
|
2959
|
+
},
|
|
2960
|
+
handler: async (args, ctx) => {
|
|
2961
|
+
const trimmed = args.trim();
|
|
2962
|
+
if (!trimmed) {
|
|
2963
|
+
if (ctx.hasUI) {
|
|
2964
|
+
await show_manager(ctx);
|
|
2965
|
+
return;
|
|
2966
|
+
}
|
|
2967
|
+
ctx.ui.notify(format_summary(active_base_name, active_layers, presets), "info");
|
|
2968
|
+
return;
|
|
2969
|
+
}
|
|
2970
|
+
const [first, ...rest] = trimmed.split(/\s+/);
|
|
2971
|
+
const arg = rest.join(" ").trim();
|
|
2972
|
+
switch (first) {
|
|
2973
|
+
case "list":
|
|
2974
|
+
ctx.ui.notify(format_summary(active_base_name, active_layers, presets), "info");
|
|
2975
|
+
return;
|
|
2976
|
+
case "show":
|
|
2977
|
+
ctx.ui.notify(format_active_details(active_base_name, active_layers, presets), "info");
|
|
2978
|
+
return;
|
|
2979
|
+
case "clear":
|
|
2980
|
+
commit_state(ctx, void 0, /* @__PURE__ */ new Set(), { notify: "Cleared base preset and prompt layers" });
|
|
2981
|
+
return;
|
|
2982
|
+
case "reload": {
|
|
2983
|
+
presets = load_prompt_presets(ctx.cwd);
|
|
2984
|
+
const normalized = normalize_active_state(presets, active_base_name, active_layers);
|
|
2985
|
+
active_base_name = normalized.active_base_name;
|
|
2986
|
+
active_layers = normalized.active_layers;
|
|
2987
|
+
set_status(ctx, active_base_name, active_layers);
|
|
2988
|
+
ctx.ui.notify("Reloaded prompt presets", "info");
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
case "base":
|
|
2992
|
+
if (!arg) {
|
|
2993
|
+
ctx.ui.notify("Usage: /preset base <name>", "warning");
|
|
2994
|
+
return;
|
|
2995
|
+
}
|
|
2996
|
+
activate_base(arg, ctx);
|
|
2997
|
+
return;
|
|
2998
|
+
case "enable":
|
|
2999
|
+
if (!arg) {
|
|
3000
|
+
ctx.ui.notify("Usage: /preset enable <layer>", "warning");
|
|
3001
|
+
return;
|
|
3002
|
+
}
|
|
3003
|
+
set_layer_enabled(arg, true, ctx);
|
|
3004
|
+
return;
|
|
3005
|
+
case "disable":
|
|
3006
|
+
if (!arg) {
|
|
3007
|
+
ctx.ui.notify("Usage: /preset disable <layer>", "warning");
|
|
3008
|
+
return;
|
|
3009
|
+
}
|
|
3010
|
+
set_layer_enabled(arg, false, ctx);
|
|
3011
|
+
return;
|
|
3012
|
+
case "toggle":
|
|
3013
|
+
if (!arg) {
|
|
3014
|
+
ctx.ui.notify("Usage: /preset toggle <layer>", "warning");
|
|
3015
|
+
return;
|
|
3016
|
+
}
|
|
3017
|
+
toggle_layer(arg, ctx);
|
|
3018
|
+
return;
|
|
3019
|
+
case "edit":
|
|
3020
|
+
if (!arg) {
|
|
3021
|
+
ctx.ui.notify("Usage: /preset edit <name>", "warning");
|
|
3022
|
+
return;
|
|
3023
|
+
}
|
|
3024
|
+
await edit_preset(arg, ctx);
|
|
3025
|
+
return;
|
|
3026
|
+
case "delete":
|
|
3027
|
+
if (!arg) {
|
|
3028
|
+
ctx.ui.notify("Usage: /preset delete <name>", "warning");
|
|
3029
|
+
return;
|
|
3030
|
+
}
|
|
3031
|
+
remove_custom_preset(arg, ctx, "delete");
|
|
3032
|
+
return;
|
|
3033
|
+
case "reset":
|
|
3034
|
+
if (!arg) {
|
|
3035
|
+
ctx.ui.notify("Usage: /preset reset <name>", "warning");
|
|
3036
|
+
return;
|
|
3037
|
+
}
|
|
3038
|
+
remove_custom_preset(arg, ctx, "reset");
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
if (is_subcommand(first)) {
|
|
3042
|
+
ctx.ui.notify(`Unsupported preset command: ${first}`, "warning");
|
|
3043
|
+
return;
|
|
3044
|
+
}
|
|
3045
|
+
const preset = presets[trimmed];
|
|
3046
|
+
if (!preset) {
|
|
3047
|
+
ctx.ui.notify(`Unknown preset or layer: ${trimmed}`, "warning");
|
|
3048
|
+
return;
|
|
3049
|
+
}
|
|
3050
|
+
if (preset.kind === "base") activate_base(preset.name, ctx);
|
|
3051
|
+
else toggle_layer(preset.name, ctx);
|
|
2930
3052
|
}
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
|
|
2946
|
-
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
}
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
3053
|
+
});
|
|
3054
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
3055
|
+
presets = load_prompt_presets(ctx.cwd);
|
|
3056
|
+
active_base_name = void 0;
|
|
3057
|
+
active_layers = /* @__PURE__ */ new Set();
|
|
3058
|
+
const preset_flag = pi.getFlag("preset");
|
|
3059
|
+
if (typeof preset_flag === "string" && preset_flag.trim()) {
|
|
3060
|
+
for (const name of parse_preset_flag(preset_flag)) {
|
|
3061
|
+
const preset = presets[name];
|
|
3062
|
+
if (!preset) continue;
|
|
3063
|
+
if (preset.kind === "base") active_base_name = name;
|
|
3064
|
+
else active_layers.add(name);
|
|
3065
|
+
}
|
|
3066
|
+
const normalized = normalize_active_state(presets, active_base_name, active_layers);
|
|
3067
|
+
active_base_name = normalized.active_base_name;
|
|
3068
|
+
active_layers = normalized.active_layers;
|
|
3069
|
+
set_status(ctx, active_base_name, active_layers);
|
|
3070
|
+
return;
|
|
3071
|
+
}
|
|
3072
|
+
const restored = get_last_preset_state(ctx) ?? load_persisted_prompt_state(ctx.cwd);
|
|
3073
|
+
if (restored) {
|
|
3074
|
+
active_base_name = restored.base_name ?? void 0;
|
|
3075
|
+
active_layers = new Set(restored.layer_names ?? []);
|
|
3076
|
+
}
|
|
3077
|
+
const normalized = normalize_active_state(presets, active_base_name, active_layers);
|
|
3078
|
+
active_base_name = normalized.active_base_name;
|
|
3079
|
+
active_layers = normalized.active_layers;
|
|
3080
|
+
set_status(ctx, active_base_name, active_layers);
|
|
3081
|
+
});
|
|
3082
|
+
pi.on("before_agent_start", async (event) => {
|
|
3083
|
+
const blocks = [];
|
|
3084
|
+
const base = get_base(active_base_name);
|
|
3085
|
+
if (base?.instructions.trim()) blocks.push(`## Active Base Prompt: ${base.name}\n${base.instructions.trim()}`);
|
|
3086
|
+
const layer_blocks = [...active_layers].sort().map((name) => presets[name]).filter((preset) => Boolean(preset?.instructions.trim())).map((preset) => `### ${preset.name}\n${preset.instructions.trim()}`);
|
|
3087
|
+
if (layer_blocks.length > 0) blocks.push(`## Active Prompt Layers\n\n${layer_blocks.join("\n\n")}`);
|
|
3088
|
+
if (blocks.length === 0) return;
|
|
3089
|
+
return { systemPrompt: `${event.systemPrompt}\n\n${blocks.join("\n\n")}` };
|
|
3090
|
+
});
|
|
3091
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
3092
|
+
ctx.ui.setStatus("preset", void 0);
|
|
3093
|
+
ctx.ui.setFooter(void 0);
|
|
3094
|
+
});
|
|
2958
3095
|
}
|
|
2959
|
-
|
|
2960
|
-
|
|
3096
|
+
//#endregion
|
|
3097
|
+
//#region src/extensions/recall.ts
|
|
3098
|
+
const DEFAULT_DB_PATH = join(process.env.HOME, ".pi", "pirecall.db");
|
|
3099
|
+
function sync_recall_db_in_background() {
|
|
3100
|
+
if (!existsSync(DEFAULT_DB_PATH)) return;
|
|
2961
3101
|
try {
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
}
|
|
2967
|
-
|
|
2968
|
-
return Object.assign({}, to_loaded_prompt_presets(DEFAULT_PROMPT_PRESETS, "builtin"), to_loaded_prompt_presets(read_prompt_presets_file(get_global_presets_path()), "user"), to_loaded_prompt_presets(read_prompt_presets_file(get_project_presets_path(cwd)), "project"));
|
|
2969
|
-
}
|
|
2970
|
-
function sort_prompt_presets(presets) {
|
|
2971
|
-
return Object.fromEntries(Object.entries(presets).sort(([a], [b]) => a.localeCompare(b)));
|
|
3102
|
+
spawn("npx", [
|
|
3103
|
+
"pirecall",
|
|
3104
|
+
"sync",
|
|
3105
|
+
"--json"
|
|
3106
|
+
], { stdio: "ignore" }).unref();
|
|
3107
|
+
} catch {}
|
|
2972
3108
|
}
|
|
2973
|
-
function
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
3109
|
+
async function recall(pi) {
|
|
3110
|
+
pi.on("session_start", async () => {
|
|
3111
|
+
sync_recall_db_in_background();
|
|
3112
|
+
});
|
|
3113
|
+
pi.on("before_agent_start", async (event) => {
|
|
3114
|
+
return { systemPrompt: event.systemPrompt + `
|
|
3115
|
+
|
|
3116
|
+
## Session Recall
|
|
3117
|
+
|
|
3118
|
+
You have access to past Pi session history via \`npx pirecall\`. Use it when:
|
|
3119
|
+
- The user references prior work ("what did we do", "last time", "remember when")
|
|
3120
|
+
- You need context from a previous session about this project
|
|
3121
|
+
- You want to avoid repeating work already done
|
|
3122
|
+
|
|
3123
|
+
Quick reference:
|
|
3124
|
+
- \`npx pirecall recall "<query>" --json\` — LLM-optimised context retrieval with surrounding messages
|
|
3125
|
+
- \`npx pirecall search "<query>" --json\` — full-text search (supports FTS5: AND, OR, NOT, "phrase", prefix*)
|
|
3126
|
+
- \`npx pirecall search "<query>" --json --project my-pi\` — filter by project
|
|
3127
|
+
- \`npx pirecall search "<query>" --json --after 2026-04-10\` — filter by date
|
|
3128
|
+
- \`npx pirecall sessions --json\` — list recent sessions
|
|
3129
|
+
- \`npx pirecall stats --json\` — database statistics
|
|
3130
|
+
|
|
3131
|
+
Always pass \`--json\` for structured output.` };
|
|
2979
3132
|
});
|
|
2980
|
-
const tmp = `${path}.tmp-${Date.now()}`;
|
|
2981
|
-
writeFileSync(tmp, JSON.stringify(sort_prompt_presets(presets), null, " ") + "\n", { mode: 384 });
|
|
2982
|
-
renameSync(tmp, path);
|
|
2983
|
-
return path;
|
|
2984
|
-
}
|
|
2985
|
-
function remove_project_prompt_preset(cwd, name) {
|
|
2986
|
-
const path = get_project_presets_path(cwd);
|
|
2987
|
-
const project_presets = read_prompt_presets_file(path);
|
|
2988
|
-
if (!(name in project_presets)) return {
|
|
2989
|
-
removed: false,
|
|
2990
|
-
path,
|
|
2991
|
-
remaining: Object.keys(project_presets).length
|
|
2992
|
-
};
|
|
2993
|
-
delete project_presets[name];
|
|
2994
|
-
const remaining = Object.keys(project_presets).length;
|
|
2995
|
-
if (remaining === 0) {
|
|
2996
|
-
if (existsSync(path)) unlinkSync(path);
|
|
2997
|
-
return {
|
|
2998
|
-
removed: true,
|
|
2999
|
-
path,
|
|
3000
|
-
remaining
|
|
3001
|
-
};
|
|
3002
|
-
}
|
|
3003
|
-
save_project_prompt_presets(cwd, project_presets);
|
|
3004
|
-
return {
|
|
3005
|
-
removed: true,
|
|
3006
|
-
path,
|
|
3007
|
-
remaining
|
|
3008
|
-
};
|
|
3009
3133
|
}
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3134
|
+
//#endregion
|
|
3135
|
+
//#region src/skills/config.ts
|
|
3136
|
+
const DEFAULT_CONFIG$1 = {
|
|
3137
|
+
version: 1,
|
|
3138
|
+
enabled: {},
|
|
3139
|
+
defaults: "all-disabled"
|
|
3140
|
+
};
|
|
3141
|
+
function get_config_path() {
|
|
3142
|
+
return join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "my-pi", "skills.json");
|
|
3017
3143
|
}
|
|
3018
|
-
function
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
projects: {}
|
|
3022
|
-
};
|
|
3144
|
+
function load_skills_config() {
|
|
3145
|
+
const path = get_config_path();
|
|
3146
|
+
if (!existsSync(path)) return { ...DEFAULT_CONFIG$1 };
|
|
3023
3147
|
try {
|
|
3024
|
-
const
|
|
3025
|
-
const
|
|
3026
|
-
const projects = {};
|
|
3027
|
-
for (const [cwd, value] of Object.entries(raw_projects)) {
|
|
3028
|
-
const normalized = normalize_prompt_preset_state(value);
|
|
3029
|
-
if (!normalized) continue;
|
|
3030
|
-
projects[cwd] = normalized;
|
|
3031
|
-
}
|
|
3148
|
+
const raw = readFileSync(path, "utf-8");
|
|
3149
|
+
const parsed = JSON.parse(raw);
|
|
3032
3150
|
return {
|
|
3033
|
-
version:
|
|
3034
|
-
|
|
3151
|
+
version: parsed.version ?? 1,
|
|
3152
|
+
enabled: parsed.enabled ?? {},
|
|
3153
|
+
defaults: parsed.defaults ?? "all-enabled"
|
|
3035
3154
|
};
|
|
3036
3155
|
} catch {
|
|
3037
|
-
return {
|
|
3038
|
-
version: 1,
|
|
3039
|
-
projects: {}
|
|
3040
|
-
};
|
|
3156
|
+
return { ...DEFAULT_CONFIG$1 };
|
|
3041
3157
|
}
|
|
3042
3158
|
}
|
|
3043
|
-
function
|
|
3044
|
-
|
|
3045
|
-
}
|
|
3046
|
-
function save_persisted_prompt_state(cwd, state, path = get_persisted_prompt_state_path()) {
|
|
3047
|
-
const persisted = read_persisted_prompt_states(path);
|
|
3048
|
-
persisted.projects[cwd] = normalize_prompt_preset_state(state) ?? {
|
|
3049
|
-
base_name: null,
|
|
3050
|
-
layer_names: []
|
|
3051
|
-
};
|
|
3159
|
+
function save_skills_config(config) {
|
|
3160
|
+
const path = get_config_path();
|
|
3052
3161
|
const dir = dirname(path);
|
|
3053
3162
|
if (!existsSync(dir)) mkdirSync(dir, {
|
|
3054
3163
|
recursive: true,
|
|
3055
3164
|
mode: 448
|
|
3056
3165
|
});
|
|
3057
3166
|
const tmp = `${path}.tmp-${Date.now()}`;
|
|
3058
|
-
writeFileSync(tmp, JSON.stringify({
|
|
3059
|
-
version: 1,
|
|
3060
|
-
projects: Object.fromEntries(Object.entries(persisted.projects).sort(([a], [b]) => a.localeCompare(b)))
|
|
3061
|
-
}, null, " ") + "\n", { mode: 384 });
|
|
3167
|
+
writeFileSync(tmp, JSON.stringify(config, null, " ") + "\n", { mode: 384 });
|
|
3062
3168
|
renameSync(tmp, path);
|
|
3063
|
-
return path;
|
|
3064
3169
|
}
|
|
3065
|
-
function
|
|
3066
|
-
|
|
3067
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
3068
|
-
const entry = entries[i];
|
|
3069
|
-
if (entry.type === "custom" && entry.customType === PRESET_STATE_TYPE && entry.data) return entry.data;
|
|
3070
|
-
}
|
|
3170
|
+
function make_skill_key(name, source) {
|
|
3171
|
+
return `${name}@${source}`;
|
|
3071
3172
|
}
|
|
3072
|
-
function
|
|
3073
|
-
if (
|
|
3074
|
-
|
|
3075
|
-
return true;
|
|
3173
|
+
function is_skill_enabled(config, key) {
|
|
3174
|
+
if (key in config.enabled) return config.enabled[key];
|
|
3175
|
+
return config.defaults === "all-enabled";
|
|
3076
3176
|
}
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3177
|
+
//#endregion
|
|
3178
|
+
//#region src/skills/scanner.ts
|
|
3179
|
+
const IMPORT_METADATA_FILE = ".my-pi-source.json";
|
|
3180
|
+
function read_installed_plugins() {
|
|
3181
|
+
const path = join(homedir(), ".claude", "plugins", "installed_plugins.json");
|
|
3182
|
+
if (!existsSync(path)) return null;
|
|
3183
|
+
try {
|
|
3184
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
3185
|
+
} catch {
|
|
3186
|
+
return null;
|
|
3082
3187
|
}
|
|
3083
3188
|
}
|
|
3084
|
-
function
|
|
3085
|
-
|
|
3189
|
+
function parse_skill_md(skill_path) {
|
|
3190
|
+
try {
|
|
3191
|
+
const { frontmatter } = parseFrontmatter(readFileSync(skill_path, "utf-8"));
|
|
3192
|
+
const description = frontmatter?.description;
|
|
3193
|
+
if (!description) return null;
|
|
3194
|
+
return {
|
|
3195
|
+
name: frontmatter?.name || basename(dirname(skill_path)),
|
|
3196
|
+
description: description.trim()
|
|
3197
|
+
};
|
|
3198
|
+
} catch {
|
|
3199
|
+
return null;
|
|
3200
|
+
}
|
|
3086
3201
|
}
|
|
3087
|
-
function
|
|
3088
|
-
|
|
3202
|
+
function read_import_metadata(base_dir) {
|
|
3203
|
+
const metadata_path = join(base_dir, IMPORT_METADATA_FILE);
|
|
3204
|
+
if (!existsSync(metadata_path)) return void 0;
|
|
3205
|
+
try {
|
|
3206
|
+
return JSON.parse(readFileSync(metadata_path, "utf-8"));
|
|
3207
|
+
} catch {
|
|
3208
|
+
return;
|
|
3209
|
+
}
|
|
3089
3210
|
}
|
|
3090
|
-
function
|
|
3091
|
-
|
|
3092
|
-
const
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3211
|
+
function scan_dir_for_skills(dir, options) {
|
|
3212
|
+
if (!existsSync(dir)) return [];
|
|
3213
|
+
const results = [];
|
|
3214
|
+
const direct = join(dir, "SKILL.md");
|
|
3215
|
+
if ((options.include_direct_root_skill ?? true) && existsSync(direct)) {
|
|
3216
|
+
const parsed = parse_skill_md(direct);
|
|
3217
|
+
if (parsed) results.push({
|
|
3218
|
+
...parsed,
|
|
3219
|
+
skillPath: direct,
|
|
3220
|
+
baseDir: dir,
|
|
3221
|
+
source: options.source,
|
|
3222
|
+
kind: options.kind,
|
|
3223
|
+
plugin: options.plugin,
|
|
3224
|
+
import_meta: options.kind === "managed" ? read_import_metadata(dir) : void 0
|
|
3225
|
+
});
|
|
3226
|
+
return results;
|
|
3227
|
+
}
|
|
3228
|
+
try {
|
|
3229
|
+
const matches = globSync("*/SKILL.md", { cwd: dir });
|
|
3230
|
+
for (const match of matches) {
|
|
3231
|
+
const full_path = resolve(dir, match);
|
|
3232
|
+
const parsed = parse_skill_md(full_path);
|
|
3233
|
+
if (parsed) {
|
|
3234
|
+
const base_dir = dirname(full_path);
|
|
3235
|
+
results.push({
|
|
3236
|
+
...parsed,
|
|
3237
|
+
skillPath: full_path,
|
|
3238
|
+
baseDir: base_dir,
|
|
3239
|
+
source: options.source,
|
|
3240
|
+
kind: options.kind,
|
|
3241
|
+
plugin: options.plugin,
|
|
3242
|
+
import_meta: options.kind === "managed" ? read_import_metadata(base_dir) : void 0
|
|
3243
|
+
});
|
|
3244
|
+
}
|
|
3100
3245
|
}
|
|
3246
|
+
} catch {}
|
|
3247
|
+
return results;
|
|
3248
|
+
}
|
|
3249
|
+
function dedupe_by_skill_path(skills) {
|
|
3250
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3251
|
+
const deduped = [];
|
|
3252
|
+
for (const skill of skills) {
|
|
3253
|
+
if (seen.has(skill.skillPath)) continue;
|
|
3254
|
+
seen.add(skill.skillPath);
|
|
3255
|
+
deduped.push(skill);
|
|
3101
3256
|
}
|
|
3102
|
-
return
|
|
3257
|
+
return deduped;
|
|
3103
3258
|
}
|
|
3104
|
-
function
|
|
3105
|
-
const
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3259
|
+
function scan_managed_skills() {
|
|
3260
|
+
const skills = [];
|
|
3261
|
+
for (const skill of scan_dir_for_skills(join(homedir(), ".claude", "skills"), {
|
|
3262
|
+
source: "user-local",
|
|
3263
|
+
kind: "managed"
|
|
3264
|
+
})) skills.push(skill);
|
|
3265
|
+
for (const skill of scan_dir_for_skills(join(homedir(), ".pi", "agent", "skills"), {
|
|
3266
|
+
source: "pi-native",
|
|
3267
|
+
kind: "managed",
|
|
3268
|
+
include_direct_root_skill: false
|
|
3269
|
+
})) skills.push(skill);
|
|
3270
|
+
return dedupe_by_skill_path(skills);
|
|
3271
|
+
}
|
|
3272
|
+
function scan_importable_skills() {
|
|
3273
|
+
const skills = [];
|
|
3274
|
+
const plugins = read_installed_plugins();
|
|
3275
|
+
if (!plugins?.plugins) return skills;
|
|
3276
|
+
for (const [plugin_id, entries] of Object.entries(plugins.plugins)) {
|
|
3277
|
+
const entry = entries[0];
|
|
3278
|
+
if (!entry?.installPath || !existsSync(entry.installPath)) continue;
|
|
3279
|
+
const source = `plugin:${plugin_id}`;
|
|
3280
|
+
const plugin = {
|
|
3281
|
+
pluginId: plugin_id,
|
|
3282
|
+
installPath: entry.installPath,
|
|
3283
|
+
version: entry.version,
|
|
3284
|
+
gitCommitSha: entry.gitCommitSha
|
|
3285
|
+
};
|
|
3286
|
+
for (const skill of scan_dir_for_skills(join(entry.installPath, "skills"), {
|
|
3287
|
+
source,
|
|
3288
|
+
kind: "external",
|
|
3289
|
+
plugin
|
|
3290
|
+
})) skills.push(skill);
|
|
3291
|
+
for (const skill of scan_dir_for_skills(join(entry.installPath, ".pi", "skills"), {
|
|
3292
|
+
source,
|
|
3293
|
+
kind: "external",
|
|
3294
|
+
plugin
|
|
3295
|
+
})) skills.push(skill);
|
|
3296
|
+
const direct_root_skill = join(entry.installPath, "SKILL.md");
|
|
3297
|
+
if (existsSync(direct_root_skill)) {
|
|
3298
|
+
const parsed = parse_skill_md(direct_root_skill);
|
|
3299
|
+
if (parsed) skills.push({
|
|
3300
|
+
...parsed,
|
|
3301
|
+
skillPath: direct_root_skill,
|
|
3302
|
+
baseDir: entry.installPath,
|
|
3303
|
+
source,
|
|
3304
|
+
kind: "external",
|
|
3305
|
+
plugin
|
|
3306
|
+
});
|
|
3113
3307
|
}
|
|
3114
3308
|
}
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3309
|
+
return dedupe_by_skill_path(skills);
|
|
3310
|
+
}
|
|
3311
|
+
//#endregion
|
|
3312
|
+
//#region src/skills/importer.ts
|
|
3313
|
+
const IMPORT_METADATA_VERSION = 1;
|
|
3314
|
+
function get_managed_skills_dir() {
|
|
3315
|
+
return join(homedir(), ".pi", "agent", "skills");
|
|
3316
|
+
}
|
|
3317
|
+
function ensure_dir(path) {
|
|
3318
|
+
mkdirSync(path, {
|
|
3319
|
+
recursive: true,
|
|
3320
|
+
mode: 448
|
|
3321
|
+
});
|
|
3322
|
+
}
|
|
3323
|
+
function list_files_recursively(dir) {
|
|
3324
|
+
const files = [];
|
|
3325
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
3326
|
+
const full_path = join(dir, entry.name);
|
|
3327
|
+
if (entry.name === ".my-pi-source.json") continue;
|
|
3328
|
+
if (entry.isDirectory()) {
|
|
3329
|
+
files.push(...list_files_recursively(full_path));
|
|
3330
|
+
continue;
|
|
3124
3331
|
}
|
|
3332
|
+
if (entry.isFile()) files.push(full_path);
|
|
3333
|
+
}
|
|
3334
|
+
return files.sort((a, b) => a.localeCompare(b));
|
|
3335
|
+
}
|
|
3336
|
+
function hash_directory(dir) {
|
|
3337
|
+
const hash = createHash("sha256");
|
|
3338
|
+
for (const file of list_files_recursively(dir)) {
|
|
3339
|
+
hash.update(relative(dir, file));
|
|
3340
|
+
hash.update("\0");
|
|
3341
|
+
hash.update(readFileSync(file));
|
|
3342
|
+
hash.update("\0");
|
|
3125
3343
|
}
|
|
3126
|
-
return
|
|
3344
|
+
return hash.digest("hex");
|
|
3127
3345
|
}
|
|
3128
|
-
function
|
|
3129
|
-
|
|
3130
|
-
|
|
3346
|
+
function read_metadata(base_dir) {
|
|
3347
|
+
const path = join(base_dir, IMPORT_METADATA_FILE);
|
|
3348
|
+
if (!existsSync(path)) return void 0;
|
|
3349
|
+
try {
|
|
3350
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
3351
|
+
} catch {
|
|
3352
|
+
return;
|
|
3353
|
+
}
|
|
3131
3354
|
}
|
|
3132
|
-
function
|
|
3133
|
-
|
|
3355
|
+
function write_metadata(base_dir, metadata) {
|
|
3356
|
+
writeFileSync(join(base_dir, IMPORT_METADATA_FILE), JSON.stringify(metadata, null, " ") + "\n", { mode: 384 });
|
|
3134
3357
|
}
|
|
3135
|
-
function
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3358
|
+
function replace_directory(source_dir, dest_dir) {
|
|
3359
|
+
const parent_dir = dirname(dest_dir);
|
|
3360
|
+
ensure_dir(parent_dir);
|
|
3361
|
+
const tmp_dir = join(parent_dir, `.${resolve(dest_dir).split("/").pop()}.tmp-${Date.now()}`);
|
|
3362
|
+
rmSync(tmp_dir, {
|
|
3363
|
+
recursive: true,
|
|
3364
|
+
force: true
|
|
3365
|
+
});
|
|
3366
|
+
cpSync(source_dir, tmp_dir, {
|
|
3367
|
+
recursive: true,
|
|
3368
|
+
preserveTimestamps: true,
|
|
3369
|
+
verbatimSymlinks: false
|
|
3370
|
+
});
|
|
3371
|
+
rmSync(dest_dir, {
|
|
3372
|
+
recursive: true,
|
|
3373
|
+
force: true
|
|
3374
|
+
});
|
|
3375
|
+
cpSync(tmp_dir, dest_dir, {
|
|
3376
|
+
recursive: true,
|
|
3377
|
+
preserveTimestamps: true,
|
|
3378
|
+
verbatimSymlinks: false
|
|
3379
|
+
});
|
|
3380
|
+
rmSync(tmp_dir, {
|
|
3381
|
+
recursive: true,
|
|
3382
|
+
force: true
|
|
3383
|
+
});
|
|
3141
3384
|
}
|
|
3142
|
-
function
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3385
|
+
function import_external_skill(skill) {
|
|
3386
|
+
if (skill.kind !== "external") throw new Error(`Skill ${skill.name} is not importable`);
|
|
3387
|
+
const managed_root = get_managed_skills_dir();
|
|
3388
|
+
ensure_dir(managed_root);
|
|
3389
|
+
const skill_dir = join(managed_root, skill.name);
|
|
3390
|
+
if (existsSync(skill_dir)) {
|
|
3391
|
+
if (!statSync(skill_dir).isDirectory()) throw new Error(`${skill_dir} exists and is not a directory`);
|
|
3392
|
+
if (!read_metadata(skill_dir)) throw new Error(`Refusing to overwrite existing unmanaged skill at ${skill_dir}`);
|
|
3147
3393
|
}
|
|
3148
|
-
|
|
3394
|
+
replace_directory(skill.baseDir, skill_dir);
|
|
3395
|
+
const upstream_hash = hash_directory(skill.baseDir);
|
|
3396
|
+
const imported_hash = hash_directory(skill_dir);
|
|
3397
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3398
|
+
const metadata = {
|
|
3399
|
+
version: IMPORT_METADATA_VERSION,
|
|
3400
|
+
source: skill.source,
|
|
3401
|
+
upstream_skill_path: skill.skillPath,
|
|
3402
|
+
upstream_base_dir: skill.baseDir,
|
|
3403
|
+
upstream_install_path: skill.plugin?.installPath,
|
|
3404
|
+
upstream_version: skill.plugin?.version,
|
|
3405
|
+
upstream_git_commit_sha: skill.plugin?.gitCommitSha,
|
|
3406
|
+
imported_at: now,
|
|
3407
|
+
last_synced_at: now,
|
|
3408
|
+
imported_hash,
|
|
3409
|
+
upstream_hash
|
|
3410
|
+
};
|
|
3411
|
+
write_metadata(skill_dir, metadata);
|
|
3412
|
+
return {
|
|
3413
|
+
skillDir: skill_dir,
|
|
3414
|
+
metadata
|
|
3415
|
+
};
|
|
3149
3416
|
}
|
|
3150
|
-
function
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
const
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
if (
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3417
|
+
function sync_imported_skill(skill) {
|
|
3418
|
+
if (skill.kind !== "managed" || !skill.import_meta) throw new Error(`Skill ${skill.name} is not managed by my-pi sync`);
|
|
3419
|
+
const metadata = skill.import_meta;
|
|
3420
|
+
if (!existsSync(metadata.upstream_base_dir)) throw new Error(`Upstream source no longer exists: ${metadata.upstream_base_dir}`);
|
|
3421
|
+
if (hash_directory(skill.baseDir) !== metadata.imported_hash) throw new Error(`Refusing to sync ${skill.name}; local changes detected in ${skill.baseDir}`);
|
|
3422
|
+
const upstream_hash = hash_directory(metadata.upstream_base_dir);
|
|
3423
|
+
if (upstream_hash === metadata.upstream_hash) return {
|
|
3424
|
+
skillDir: skill.baseDir,
|
|
3425
|
+
metadata,
|
|
3426
|
+
changed: false
|
|
3427
|
+
};
|
|
3428
|
+
replace_directory(metadata.upstream_base_dir, skill.baseDir);
|
|
3429
|
+
const imported_hash = hash_directory(skill.baseDir);
|
|
3430
|
+
const updated = {
|
|
3431
|
+
...metadata,
|
|
3432
|
+
last_synced_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3433
|
+
imported_hash,
|
|
3434
|
+
upstream_hash
|
|
3435
|
+
};
|
|
3436
|
+
write_metadata(skill.baseDir, updated);
|
|
3437
|
+
return {
|
|
3438
|
+
skillDir: skill.baseDir,
|
|
3439
|
+
metadata: updated,
|
|
3440
|
+
changed: true
|
|
3441
|
+
};
|
|
3442
|
+
}
|
|
3443
|
+
//#endregion
|
|
3444
|
+
//#region src/skills/manager.ts
|
|
3445
|
+
function resolve_skill_key(skill) {
|
|
3446
|
+
return make_skill_key(skill.name, skill.source);
|
|
3447
|
+
}
|
|
3448
|
+
function match_skill_by_key_or_name(skills, key_or_name) {
|
|
3449
|
+
const exact_key = skills.find((skill) => resolve_skill_key(skill) === key_or_name);
|
|
3450
|
+
if (exact_key) return exact_key;
|
|
3451
|
+
const by_name = skills.filter((skill) => skill.name === key_or_name);
|
|
3452
|
+
if (by_name.length === 1) return by_name[0];
|
|
3453
|
+
if (by_name.length > 1) throw new Error(`Multiple skills named ${key_or_name}. Use an exact key instead.`);
|
|
3454
|
+
throw new Error(`Unknown skill: ${key_or_name}`);
|
|
3455
|
+
}
|
|
3456
|
+
function create_skills_manager() {
|
|
3457
|
+
let config = load_skills_config();
|
|
3458
|
+
let managed_cache = null;
|
|
3459
|
+
let importable_cache = null;
|
|
3460
|
+
function get_managed() {
|
|
3461
|
+
if (!managed_cache) managed_cache = scan_managed_skills();
|
|
3462
|
+
return managed_cache;
|
|
3191
3463
|
}
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
if (ctx.model?.reasoning) right_side_without_provider = thinking_level === "off" ? `${model_name} • thinking off` : `${model_name} • ${thinking_level}`;
|
|
3196
|
-
let right_side = right_side_without_provider;
|
|
3197
|
-
if (footer_data.getAvailableProviderCount() > 1 && ctx.model) {
|
|
3198
|
-
right_side = `(${ctx.model.provider}) ${right_side_without_provider}`;
|
|
3199
|
-
if (stats_left_width + 2 + visibleWidth(right_side) > width) right_side = right_side_without_provider;
|
|
3464
|
+
function get_importable() {
|
|
3465
|
+
if (!importable_cache) importable_cache = scan_importable_skills();
|
|
3466
|
+
return importable_cache;
|
|
3200
3467
|
}
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
const available_for_right = width - stats_left_width - 2;
|
|
3209
|
-
if (available_for_right > 0) {
|
|
3210
|
-
const truncated_right = truncateToWidth(right_side, available_for_right, "");
|
|
3211
|
-
const truncated_right_width = visibleWidth(truncated_right);
|
|
3212
|
-
const padding = " ".repeat(Math.max(0, width - stats_left_width - truncated_right_width));
|
|
3213
|
-
stats_line = stats_left + padding + truncated_right;
|
|
3214
|
-
} else stats_line = stats_left;
|
|
3468
|
+
function to_managed(skill) {
|
|
3469
|
+
const key = resolve_skill_key(skill);
|
|
3470
|
+
return {
|
|
3471
|
+
...skill,
|
|
3472
|
+
key,
|
|
3473
|
+
enabled: skill.kind === "managed" ? is_skill_enabled(config, key) : false
|
|
3474
|
+
};
|
|
3215
3475
|
}
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
const dim_remainder = theme.fg("dim", remainder);
|
|
3219
|
-
const lines = [truncateToWidth(theme.fg("dim", pwd), width, theme.fg("dim", "...")), dim_stats_left + dim_remainder];
|
|
3220
|
-
const prompt_status = get_footer_prompt_status(active_base_name, active_layers);
|
|
3221
|
-
if (prompt_status) {
|
|
3222
|
-
const themed_status = theme.fg("dim", prompt_status);
|
|
3223
|
-
const status_width = visibleWidth(themed_status);
|
|
3224
|
-
const aligned_status = status_width >= width ? truncateToWidth(themed_status, width, theme.fg("dim", "...")) : `${" ".repeat(width - status_width)}${themed_status}`;
|
|
3225
|
-
lines.push(aligned_status);
|
|
3476
|
+
function get_enabled_managed_skills() {
|
|
3477
|
+
return get_managed().filter((skill) => is_skill_enabled(config, resolve_skill_key(skill))).map(to_managed);
|
|
3226
3478
|
}
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3479
|
+
return {
|
|
3480
|
+
discover() {
|
|
3481
|
+
return get_managed().map(to_managed);
|
|
3482
|
+
},
|
|
3483
|
+
discover_importable() {
|
|
3484
|
+
return get_importable().map(to_managed);
|
|
3485
|
+
},
|
|
3486
|
+
is_enabled_by_skill(name, filePath) {
|
|
3487
|
+
const discovered = get_managed();
|
|
3488
|
+
const match = discovered.find((s) => s.skillPath === filePath);
|
|
3489
|
+
if (match) return is_skill_enabled(config, resolve_skill_key(match));
|
|
3490
|
+
const by_name = discovered.find((s) => s.name === name);
|
|
3491
|
+
if (by_name) return is_skill_enabled(config, resolve_skill_key(by_name));
|
|
3492
|
+
return true;
|
|
3493
|
+
},
|
|
3494
|
+
get_enabled_skill_paths() {
|
|
3495
|
+
return get_enabled_managed_skills().map((skill) => skill.baseDir);
|
|
3496
|
+
},
|
|
3497
|
+
enable(key) {
|
|
3498
|
+
config.enabled[key] = true;
|
|
3499
|
+
save_skills_config(config);
|
|
3500
|
+
return true;
|
|
3501
|
+
},
|
|
3502
|
+
disable(key) {
|
|
3503
|
+
config.enabled[key] = false;
|
|
3504
|
+
save_skills_config(config);
|
|
3505
|
+
return false;
|
|
3506
|
+
},
|
|
3507
|
+
toggle(key) {
|
|
3508
|
+
const current = is_skill_enabled(config, key);
|
|
3509
|
+
config.enabled[key] = !current;
|
|
3510
|
+
save_skills_config(config);
|
|
3511
|
+
return !current;
|
|
3512
|
+
},
|
|
3513
|
+
search(query) {
|
|
3514
|
+
const q = query.toLowerCase();
|
|
3515
|
+
return this.discover().filter((s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.source.toLowerCase().includes(q));
|
|
3516
|
+
},
|
|
3517
|
+
search_importable(query) {
|
|
3518
|
+
const q = query.toLowerCase();
|
|
3519
|
+
return this.discover_importable().filter((s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.source.toLowerCase().includes(q));
|
|
3520
|
+
},
|
|
3521
|
+
set_defaults(policy) {
|
|
3522
|
+
config.defaults = policy;
|
|
3523
|
+
save_skills_config(config);
|
|
3524
|
+
},
|
|
3525
|
+
import_skill(key_or_name) {
|
|
3526
|
+
const skill = match_skill_by_key_or_name(get_importable(), key_or_name);
|
|
3527
|
+
const result = import_external_skill(skill);
|
|
3528
|
+
const managed_key = make_skill_key(skill.name, "pi-native");
|
|
3529
|
+
config.enabled[managed_key] = true;
|
|
3530
|
+
save_skills_config(config);
|
|
3531
|
+
this.refresh();
|
|
3532
|
+
return {
|
|
3533
|
+
...result,
|
|
3534
|
+
key: managed_key
|
|
3535
|
+
};
|
|
3536
|
+
},
|
|
3537
|
+
sync_skill(key_or_name) {
|
|
3538
|
+
const skill = match_skill_by_key_or_name(get_managed(), key_or_name);
|
|
3539
|
+
const result = sync_imported_skill(skill);
|
|
3540
|
+
this.refresh();
|
|
3541
|
+
return {
|
|
3542
|
+
...result,
|
|
3543
|
+
key: resolve_skill_key(skill)
|
|
3544
|
+
};
|
|
3545
|
+
},
|
|
3546
|
+
refresh() {
|
|
3547
|
+
managed_cache = null;
|
|
3548
|
+
importable_cache = null;
|
|
3549
|
+
config = load_skills_config();
|
|
3550
|
+
}
|
|
3551
|
+
};
|
|
3230
3552
|
}
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3240
|
-
|
|
3241
|
-
|
|
3553
|
+
//#endregion
|
|
3554
|
+
//#region src/extensions/skills.ts
|
|
3555
|
+
const ENABLED = "[x]";
|
|
3556
|
+
const DISABLED = "[ ]";
|
|
3557
|
+
const SYNC = "[~]";
|
|
3558
|
+
const IMPORTED_LABEL = "[=]";
|
|
3559
|
+
function sort_skills(skills) {
|
|
3560
|
+
return [...skills].sort((a, b) => {
|
|
3561
|
+
const by_name = a.name.localeCompare(b.name);
|
|
3562
|
+
if (by_name !== 0) return by_name;
|
|
3563
|
+
const by_source = a.source.localeCompare(b.source);
|
|
3564
|
+
if (by_source !== 0) return by_source;
|
|
3565
|
+
return a.key.localeCompare(b.key);
|
|
3242
3566
|
});
|
|
3243
3567
|
}
|
|
3244
|
-
function
|
|
3245
|
-
const
|
|
3246
|
-
|
|
3247
|
-
|
|
3568
|
+
function find_matching_imported_skill(managed_skills, skill) {
|
|
3569
|
+
const exact_match = managed_skills.find((candidate) => candidate.import_meta?.source === skill.source && (candidate.import_meta.upstream_skill_path === skill.skillPath || candidate.import_meta.upstream_base_dir === skill.baseDir));
|
|
3570
|
+
if (exact_match) return exact_match;
|
|
3571
|
+
return managed_skills.find((candidate) => candidate.import_meta?.source === skill.source && candidate.name === skill.name);
|
|
3572
|
+
}
|
|
3573
|
+
function get_importable_state(managed_skills, skill) {
|
|
3574
|
+
const imported = find_matching_imported_skill(managed_skills, skill);
|
|
3575
|
+
if (imported?.import_meta) {
|
|
3576
|
+
const version_changed = Boolean(skill.plugin?.version && imported.import_meta.upstream_version && skill.plugin.version !== imported.import_meta.upstream_version);
|
|
3577
|
+
const sha_changed = Boolean(skill.plugin?.gitCommitSha && imported.import_meta.upstream_git_commit_sha && skill.plugin.gitCommitSha !== imported.import_meta.upstream_git_commit_sha);
|
|
3578
|
+
if (version_changed || sha_changed) return {
|
|
3579
|
+
label: "sync",
|
|
3580
|
+
detail: "Press Enter to sync the imported copy and reload",
|
|
3581
|
+
action: "sync"
|
|
3582
|
+
};
|
|
3583
|
+
return {
|
|
3584
|
+
label: "imported",
|
|
3585
|
+
detail: `Already imported to ${imported.baseDir}`,
|
|
3586
|
+
action: null
|
|
3587
|
+
};
|
|
3588
|
+
}
|
|
3589
|
+
const managed_conflict = managed_skills.find((candidate) => candidate.name === skill.name);
|
|
3590
|
+
if (managed_conflict) return {
|
|
3591
|
+
label: "managed",
|
|
3592
|
+
detail: `Already managed at ${managed_conflict.baseDir}`,
|
|
3593
|
+
action: null
|
|
3594
|
+
};
|
|
3595
|
+
return {
|
|
3596
|
+
label: "import",
|
|
3597
|
+
detail: "Press Enter to import into pi-native skills and reload",
|
|
3598
|
+
action: "import"
|
|
3248
3599
|
};
|
|
3249
|
-
pi.appendEntry(PRESET_STATE_TYPE, state);
|
|
3250
|
-
save_persisted_prompt_state(ctx.cwd, state);
|
|
3251
3600
|
}
|
|
3252
|
-
function
|
|
3601
|
+
function to_setting_item(skill) {
|
|
3602
|
+
const detail_lines = [
|
|
3603
|
+
`${skill.source} • ${skill.key}`,
|
|
3604
|
+
skill.description,
|
|
3605
|
+
skill.baseDir
|
|
3606
|
+
];
|
|
3607
|
+
if (skill.import_meta?.upstream_version) detail_lines.push(`upstream: ${skill.import_meta.upstream_version}${skill.import_meta.upstream_git_commit_sha ? ` • ${skill.import_meta.upstream_git_commit_sha.slice(0, 12)}` : ""}`);
|
|
3253
3608
|
return {
|
|
3254
|
-
|
|
3255
|
-
|
|
3609
|
+
id: skill.key,
|
|
3610
|
+
label: skill.name,
|
|
3611
|
+
description: detail_lines.join("\n"),
|
|
3612
|
+
currentValue: skill.enabled ? ENABLED : DISABLED,
|
|
3613
|
+
values: [ENABLED, DISABLED]
|
|
3256
3614
|
};
|
|
3257
3615
|
}
|
|
3258
|
-
function
|
|
3259
|
-
|
|
3616
|
+
function to_importable_setting_item(managed_skills, skill) {
|
|
3617
|
+
const state = get_importable_state(managed_skills, skill);
|
|
3618
|
+
const detail_lines = [
|
|
3619
|
+
`${skill.source} • ${skill.key}`,
|
|
3620
|
+
skill.description,
|
|
3621
|
+
skill.baseDir
|
|
3622
|
+
];
|
|
3623
|
+
if (skill.plugin?.version) detail_lines.push(`plugin: ${skill.plugin.version}${skill.plugin.gitCommitSha ? ` • ${skill.plugin.gitCommitSha.slice(0, 12)}` : ""}`);
|
|
3624
|
+
if (state.action === "import") return {
|
|
3625
|
+
id: skill.key,
|
|
3626
|
+
label: skill.name,
|
|
3627
|
+
description: detail_lines.join("\n"),
|
|
3628
|
+
currentValue: DISABLED,
|
|
3629
|
+
values: [ENABLED, DISABLED]
|
|
3630
|
+
};
|
|
3631
|
+
if (state.action === "sync") {
|
|
3632
|
+
detail_lines.push("enter to sync");
|
|
3633
|
+
return {
|
|
3634
|
+
id: skill.key,
|
|
3635
|
+
label: skill.name,
|
|
3636
|
+
description: detail_lines.join("\n"),
|
|
3637
|
+
currentValue: SYNC,
|
|
3638
|
+
values: [SYNC]
|
|
3639
|
+
};
|
|
3640
|
+
}
|
|
3641
|
+
detail_lines.push(state.detail);
|
|
3642
|
+
return {
|
|
3643
|
+
id: skill.key,
|
|
3644
|
+
label: skill.name,
|
|
3645
|
+
description: detail_lines.join("\n"),
|
|
3646
|
+
currentValue: IMPORTED_LABEL
|
|
3647
|
+
};
|
|
3260
3648
|
}
|
|
3261
|
-
function
|
|
3262
|
-
return
|
|
3263
|
-
|
|
3264
|
-
|
|
3265
|
-
"clear",
|
|
3266
|
-
"edit",
|
|
3267
|
-
"delete",
|
|
3268
|
-
"reset",
|
|
3269
|
-
"reload",
|
|
3270
|
-
"base",
|
|
3271
|
-
"enable",
|
|
3272
|
-
"disable",
|
|
3273
|
-
"toggle"
|
|
3274
|
-
].includes(command);
|
|
3649
|
+
function sets_equal(a, b) {
|
|
3650
|
+
if (a.size !== b.size) return false;
|
|
3651
|
+
for (const value of a) if (!b.has(value)) return false;
|
|
3652
|
+
return true;
|
|
3275
3653
|
}
|
|
3276
|
-
async function
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
notify: "Base preset cleared"
|
|
3299
|
-
});
|
|
3300
|
-
return true;
|
|
3301
|
-
}
|
|
3302
|
-
const preset = get_base(name);
|
|
3303
|
-
if (!preset) {
|
|
3304
|
-
ctx.ui.notify(`Unknown base preset: ${name}`, "warning");
|
|
3305
|
-
return false;
|
|
3306
|
-
}
|
|
3307
|
-
commit_state(ctx, preset.name, active_layers, {
|
|
3308
|
-
persist: options?.persist,
|
|
3309
|
-
notify: `Base preset "${preset.name}" activated`
|
|
3310
|
-
});
|
|
3311
|
-
return true;
|
|
3312
|
-
}
|
|
3313
|
-
function set_layer_enabled(name, enabled, ctx, options) {
|
|
3314
|
-
const preset = get_layer(name);
|
|
3315
|
-
if (!preset) {
|
|
3316
|
-
ctx.ui.notify(`Unknown prompt layer: ${name}`, "warning");
|
|
3317
|
-
return false;
|
|
3318
|
-
}
|
|
3319
|
-
const next_layers = new Set(active_layers);
|
|
3320
|
-
if (enabled) next_layers.add(preset.name);
|
|
3321
|
-
else next_layers.delete(preset.name);
|
|
3322
|
-
commit_state(ctx, active_base_name, next_layers, {
|
|
3323
|
-
persist: options?.persist,
|
|
3324
|
-
notify: enabled ? `Layer "${preset.name}" enabled` : `Layer "${preset.name}" disabled`
|
|
3325
|
-
});
|
|
3326
|
-
return true;
|
|
3327
|
-
}
|
|
3328
|
-
function toggle_layer(name, ctx, options) {
|
|
3329
|
-
return set_layer_enabled(name, !active_layers.has(name), ctx, options);
|
|
3330
|
-
}
|
|
3331
|
-
async function edit_preset(name, ctx) {
|
|
3332
|
-
const existing = presets[name];
|
|
3333
|
-
const kind_choice = await ctx.ui.select("Preset kind", [existing?.kind === "layer" ? "layer (current)" : "base (current)", existing?.kind === "layer" ? "base" : "layer"]);
|
|
3334
|
-
if (!kind_choice) return;
|
|
3335
|
-
const kind = kind_choice.startsWith("layer") ? "layer" : "base";
|
|
3336
|
-
const description = await ctx.ui.input(`Description for ${name}`, existing?.description ?? "");
|
|
3337
|
-
if (description === void 0) return;
|
|
3338
|
-
const instructions = await ctx.ui.editor(`Edit ${kind} preset: ${name}`, existing?.instructions ?? "");
|
|
3339
|
-
if (instructions === void 0) return;
|
|
3340
|
-
save_project_prompt_presets(ctx.cwd, {
|
|
3341
|
-
...read_prompt_presets_file(get_project_presets_path(ctx.cwd)),
|
|
3342
|
-
[name]: {
|
|
3343
|
-
kind,
|
|
3344
|
-
instructions,
|
|
3345
|
-
...description.trim() ? { description: description.trim() } : {}
|
|
3654
|
+
async function skills(pi) {
|
|
3655
|
+
const mgr = create_skills_manager();
|
|
3656
|
+
const subs = [
|
|
3657
|
+
"import",
|
|
3658
|
+
"sync",
|
|
3659
|
+
"refresh",
|
|
3660
|
+
"defaults"
|
|
3661
|
+
];
|
|
3662
|
+
pi.registerCommand("skills", {
|
|
3663
|
+
description: "Manage pi-native skills and import external skills",
|
|
3664
|
+
getArgumentCompletions: (prefix) => {
|
|
3665
|
+
const parts = prefix.trim().split(/\s+/);
|
|
3666
|
+
if (parts.length <= 1) return subs.filter((s) => s.startsWith(parts[0] || "")).map((s) => ({
|
|
3667
|
+
value: s,
|
|
3668
|
+
label: s
|
|
3669
|
+
}));
|
|
3670
|
+
if (parts[0] === "import") {
|
|
3671
|
+
const q = parts.slice(1).join(" ").toLowerCase();
|
|
3672
|
+
return sort_skills(mgr.discover_importable()).filter((s) => s.key.toLowerCase().includes(q) || s.name.toLowerCase().includes(q)).slice(0, 20).map((s) => ({
|
|
3673
|
+
value: `${parts[0]} ${s.key}`,
|
|
3674
|
+
label: s.key
|
|
3675
|
+
}));
|
|
3346
3676
|
}
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
const normalized = normalize_active_state(presets, active_base_name, active_layers);
|
|
3364
|
-
active_base_name = normalized.active_base_name;
|
|
3365
|
-
active_layers = normalized.active_layers;
|
|
3366
|
-
set_status(ctx, active_base_name, active_layers);
|
|
3367
|
-
persist_state(pi, ctx, active_base_name, active_layers);
|
|
3368
|
-
const fallback = presets[name];
|
|
3369
|
-
if (mode === "reset" && fallback) {
|
|
3370
|
-
ctx.ui.notify(`Reset "${name}" to ${get_prompt_source_label(fallback.source)} preset`, "info");
|
|
3371
|
-
return;
|
|
3372
|
-
}
|
|
3373
|
-
ctx.ui.notify(result.remaining === 0 ? `Removed "${name}" and deleted ${result.path}` : `Removed "${name}" from ${result.path}`, "info");
|
|
3374
|
-
}
|
|
3375
|
-
async function show_manager(ctx) {
|
|
3376
|
-
const base_presets = list_base_presets(presets);
|
|
3377
|
-
const layer_presets = list_layer_presets(presets);
|
|
3378
|
-
if (base_presets.length === 0 && layer_presets.length === 0) {
|
|
3379
|
-
ctx.ui.notify("No prompt presets available", "warning");
|
|
3380
|
-
return;
|
|
3381
|
-
}
|
|
3382
|
-
const initial_base = active_base_name;
|
|
3383
|
-
const initial_layers = new Set(active_layers);
|
|
3384
|
-
let selected_base = active_base_name;
|
|
3385
|
-
const enabled_layers = new Set(active_layers);
|
|
3386
|
-
const items = [];
|
|
3387
|
-
const base_ids = /* @__PURE__ */ new Set();
|
|
3388
|
-
const layer_ids = /* @__PURE__ */ new Set();
|
|
3389
|
-
items.push({
|
|
3390
|
-
id: "__header_base__",
|
|
3391
|
-
label: `── Base presets (${base_presets.length + 1}) ──`,
|
|
3392
|
-
description: "",
|
|
3393
|
-
currentValue: ""
|
|
3394
|
-
});
|
|
3395
|
-
items.push({
|
|
3396
|
-
id: NONE_BASE_ID,
|
|
3397
|
-
label: "(none)",
|
|
3398
|
-
description: "No active base preset",
|
|
3399
|
-
currentValue: UNSELECTED,
|
|
3400
|
-
values: [SELECTED, UNSELECTED]
|
|
3401
|
-
});
|
|
3402
|
-
base_ids.add(NONE_BASE_ID);
|
|
3403
|
-
for (const preset of base_presets) {
|
|
3404
|
-
items.push({
|
|
3405
|
-
id: preset.name,
|
|
3406
|
-
label: preset.name,
|
|
3407
|
-
description: [`${get_prompt_source_label(preset.source)} • ${preset.description ?? "base preset"}`].join("\n"),
|
|
3408
|
-
currentValue: UNSELECTED,
|
|
3409
|
-
values: [SELECTED, UNSELECTED]
|
|
3410
|
-
});
|
|
3411
|
-
base_ids.add(preset.name);
|
|
3412
|
-
}
|
|
3413
|
-
items.push({
|
|
3414
|
-
id: "__header_layers__",
|
|
3415
|
-
label: `── Prompt layers (${layer_presets.length}) ──`,
|
|
3416
|
-
description: "",
|
|
3417
|
-
currentValue: ""
|
|
3418
|
-
});
|
|
3419
|
-
for (const preset of layer_presets) {
|
|
3420
|
-
items.push({
|
|
3421
|
-
id: preset.name,
|
|
3422
|
-
label: preset.name,
|
|
3423
|
-
description: [`${get_prompt_source_label(preset.source)} • ${preset.description ?? "layer"}`].join("\n"),
|
|
3424
|
-
currentValue: DISABLED$1,
|
|
3425
|
-
values: [ENABLED$1, DISABLED$1]
|
|
3426
|
-
});
|
|
3427
|
-
layer_ids.add(preset.name);
|
|
3428
|
-
}
|
|
3429
|
-
function sync_values() {
|
|
3430
|
-
for (const item of items) if (base_ids.has(item.id)) item.currentValue = item.id === NONE_BASE_ID && !selected_base || item.id === selected_base ? SELECTED : UNSELECTED;
|
|
3431
|
-
else if (layer_ids.has(item.id)) item.currentValue = enabled_layers.has(item.id) ? ENABLED$1 : DISABLED$1;
|
|
3432
|
-
}
|
|
3433
|
-
sync_values();
|
|
3434
|
-
await ctx.ui.custom((tui, theme, _kb, done) => {
|
|
3435
|
-
const list = new SettingsList(items, Math.min(Math.max(items.length + 4, 8), 24), {
|
|
3436
|
-
cursor: theme.fg("accent", "›"),
|
|
3437
|
-
label: (text, selected) => {
|
|
3438
|
-
if (text.startsWith("──") && text.endsWith("──")) return theme.fg("dim", theme.bold(text));
|
|
3439
|
-
return selected ? theme.fg("accent", text) : text;
|
|
3440
|
-
},
|
|
3441
|
-
value: (text, selected) => {
|
|
3442
|
-
const color = text === ENABLED$1 || text === SELECTED ? "success" : "dim";
|
|
3443
|
-
const rendered = theme.fg(color, text);
|
|
3444
|
-
return selected ? theme.bold(theme.fg("accent", rendered)) : rendered;
|
|
3445
|
-
},
|
|
3446
|
-
description: (text) => theme.fg("muted", text),
|
|
3447
|
-
hint: (text) => theme.fg("dim", text)
|
|
3448
|
-
}, (id, new_value) => {
|
|
3449
|
-
if (id.startsWith("__header_")) return;
|
|
3450
|
-
if (base_ids.has(id)) {
|
|
3451
|
-
selected_base = new_value === SELECTED && id !== NONE_BASE_ID ? id : void 0;
|
|
3452
|
-
sync_values();
|
|
3677
|
+
if (parts[0] === "sync") {
|
|
3678
|
+
const q = parts.slice(1).join(" ").toLowerCase();
|
|
3679
|
+
return sort_skills(mgr.discover().filter((skill) => Boolean(skill.import_meta))).filter((s) => s.key.toLowerCase().includes(q) || s.name.toLowerCase().includes(q)).slice(0, 20).map((s) => ({
|
|
3680
|
+
value: `${parts[0]} ${s.key}`,
|
|
3681
|
+
label: s.key
|
|
3682
|
+
}));
|
|
3683
|
+
}
|
|
3684
|
+
return null;
|
|
3685
|
+
},
|
|
3686
|
+
handler: async (args, ctx) => {
|
|
3687
|
+
const trimmed = args.trim();
|
|
3688
|
+
if (!trimmed && ctx.hasUI) {
|
|
3689
|
+
const discovered = sort_skills(mgr.discover());
|
|
3690
|
+
const importable = sort_skills(mgr.discover_importable());
|
|
3691
|
+
if (discovered.length === 0 && importable.length === 0) {
|
|
3692
|
+
ctx.ui.notify("No managed or importable skills found");
|
|
3453
3693
|
return;
|
|
3454
3694
|
}
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
|
|
3695
|
+
const initial_enabled = new Set(discovered.filter((skill) => skill.enabled).map((skill) => skill.key));
|
|
3696
|
+
const current_enabled = new Set(initial_enabled);
|
|
3697
|
+
const queued_imports = /* @__PURE__ */ new Set();
|
|
3698
|
+
let reload_notice = null;
|
|
3699
|
+
const managed_items = discovered.map(to_setting_item);
|
|
3700
|
+
const importable_items = importable.map((skill) => to_importable_setting_item(discovered, skill));
|
|
3701
|
+
const all_items = [];
|
|
3702
|
+
if (managed_items.length > 0) {
|
|
3703
|
+
all_items.push({
|
|
3704
|
+
id: "__header_managed__",
|
|
3705
|
+
label: `── Managed (${managed_items.length}) ──`,
|
|
3706
|
+
description: "",
|
|
3707
|
+
currentValue: ""
|
|
3708
|
+
});
|
|
3709
|
+
all_items.push(...managed_items);
|
|
3459
3710
|
}
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
invalidate: () => {}
|
|
3469
|
-
});
|
|
3470
|
-
container.addChild({
|
|
3471
|
-
render(width) {
|
|
3472
|
-
return list.render(width);
|
|
3473
|
-
},
|
|
3474
|
-
invalidate() {
|
|
3475
|
-
list.invalidate();
|
|
3711
|
+
if (importable_items.length > 0) {
|
|
3712
|
+
all_items.push({
|
|
3713
|
+
id: "__header_importable__",
|
|
3714
|
+
label: `── Importable (${importable_items.length}) ──`,
|
|
3715
|
+
description: "",
|
|
3716
|
+
currentValue: ""
|
|
3717
|
+
});
|
|
3718
|
+
all_items.push(...importable_items);
|
|
3476
3719
|
}
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3720
|
+
const managed_keys = new Set(discovered.map((s) => s.key));
|
|
3721
|
+
const importable_map = new Map(importable.map((s) => [s.key, s]));
|
|
3722
|
+
await ctx.ui.custom((tui, theme, _kb, done) => {
|
|
3723
|
+
const list = new SettingsList(all_items, Math.min(Math.max(all_items.length + 4, 8), 22), {
|
|
3724
|
+
cursor: theme.fg("accent", "›"),
|
|
3725
|
+
label: (text, selected) => {
|
|
3726
|
+
if (text.startsWith("──") && text.endsWith("──")) return theme.fg("dim", theme.bold(text));
|
|
3727
|
+
return selected ? theme.fg("accent", text) : text;
|
|
3728
|
+
},
|
|
3729
|
+
value: (text, selected) => {
|
|
3730
|
+
const color = text === ENABLED ? "success" : text === SYNC ? "warning" : text === IMPORTED_LABEL ? "success" : "dim";
|
|
3731
|
+
const rendered = theme.fg(color, text);
|
|
3732
|
+
return selected ? theme.bold(theme.fg("accent", rendered)) : rendered;
|
|
3733
|
+
},
|
|
3734
|
+
description: (text) => theme.fg("muted", text),
|
|
3735
|
+
hint: (text) => theme.fg("dim", text)
|
|
3736
|
+
}, (id, new_value) => {
|
|
3737
|
+
if (id.startsWith("__header_")) return;
|
|
3738
|
+
if (managed_keys.has(id)) {
|
|
3739
|
+
if (new_value === ENABLED) {
|
|
3740
|
+
current_enabled.add(id);
|
|
3741
|
+
mgr.enable(id);
|
|
3742
|
+
} else {
|
|
3743
|
+
current_enabled.delete(id);
|
|
3744
|
+
mgr.disable(id);
|
|
3745
|
+
}
|
|
3746
|
+
return;
|
|
3747
|
+
}
|
|
3748
|
+
const import_skill = importable_map.get(id);
|
|
3749
|
+
if (!import_skill) return;
|
|
3750
|
+
const state = get_importable_state(discovered, import_skill);
|
|
3751
|
+
if (state.action === "import") {
|
|
3752
|
+
if (new_value === ENABLED) queued_imports.add(id);
|
|
3753
|
+
else queued_imports.delete(id);
|
|
3754
|
+
return;
|
|
3755
|
+
}
|
|
3756
|
+
if (state.action === "sync") {
|
|
3757
|
+
const imported_skill = find_matching_imported_skill(discovered, import_skill);
|
|
3758
|
+
if (!imported_skill) {
|
|
3759
|
+
ctx.ui.notify(`Imported copy for ${import_skill.name} was not found`, "warning");
|
|
3760
|
+
return;
|
|
3761
|
+
}
|
|
3762
|
+
try {
|
|
3763
|
+
if (mgr.sync_skill(imported_skill.key).changed) {
|
|
3764
|
+
reload_notice = `Synced ${import_skill.name}. Reloading...`;
|
|
3765
|
+
done(void 0);
|
|
3766
|
+
} else ctx.ui.notify(`${import_skill.name} is already up to date.`, "info");
|
|
3767
|
+
} catch (error) {
|
|
3768
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "warning");
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
}, () => done(void 0), { enableSearch: true });
|
|
3772
|
+
const container = new Container();
|
|
3773
|
+
container.addChild({
|
|
3774
|
+
render: () => {
|
|
3775
|
+
const enabled = current_enabled.size;
|
|
3776
|
+
const disabled = discovered.length - enabled;
|
|
3777
|
+
const queued = queued_imports.size;
|
|
3778
|
+
const parts = [`${enabled} enabled`, `${disabled} disabled`];
|
|
3779
|
+
if (importable.length > 0) parts.push(`${importable.length} importable`);
|
|
3780
|
+
if (queued > 0) parts.push(`${queued} queued for import`);
|
|
3781
|
+
return [
|
|
3782
|
+
theme.fg("accent", theme.bold("Skills")),
|
|
3783
|
+
theme.fg("muted", parts.join(" • ")),
|
|
3784
|
+
""
|
|
3785
|
+
];
|
|
3786
|
+
},
|
|
3787
|
+
invalidate: () => {}
|
|
3788
|
+
});
|
|
3789
|
+
container.addChild({
|
|
3790
|
+
render(width) {
|
|
3791
|
+
return list.render(width);
|
|
3792
|
+
},
|
|
3793
|
+
invalidate() {
|
|
3794
|
+
list.invalidate();
|
|
3795
|
+
}
|
|
3796
|
+
});
|
|
3797
|
+
container.addChild(new Text(theme.fg("dim", "search filters • enter toggles • esc close"), 0, 1));
|
|
3798
|
+
return {
|
|
3799
|
+
render(width) {
|
|
3800
|
+
return container.render(width);
|
|
3801
|
+
},
|
|
3802
|
+
invalidate() {
|
|
3803
|
+
container.invalidate();
|
|
3804
|
+
},
|
|
3805
|
+
handleInput(data) {
|
|
3806
|
+
list.handleInput(data);
|
|
3807
|
+
tui.requestRender();
|
|
3808
|
+
}
|
|
3809
|
+
};
|
|
3810
|
+
});
|
|
3811
|
+
if (queued_imports.size > 0) {
|
|
3812
|
+
const imported_names = [];
|
|
3813
|
+
for (const key of queued_imports) try {
|
|
3814
|
+
mgr.import_skill(key);
|
|
3815
|
+
imported_names.push(key);
|
|
3816
|
+
} catch (error) {
|
|
3817
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "warning");
|
|
3818
|
+
}
|
|
3819
|
+
if (imported_names.length > 0) reload_notice = `Imported ${imported_names.length} skill(s). Reloading...`;
|
|
3489
3820
|
}
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
}
|
|
3494
|
-
pi.registerFlag("preset", {
|
|
3495
|
-
description: "Activate prompt config on startup. Accepts a base preset or comma-separated preset/layer names.",
|
|
3496
|
-
type: "string"
|
|
3497
|
-
});
|
|
3498
|
-
pi.registerCommand("preset", {
|
|
3499
|
-
description: "Manage base prompt presets and prompt layers",
|
|
3500
|
-
getArgumentCompletions: (prefix) => {
|
|
3501
|
-
const trimmed = prefix.trim();
|
|
3502
|
-
const parts = trimmed ? trimmed.split(/\s+/) : [];
|
|
3503
|
-
const base_names = list_base_presets(presets).map((preset) => preset.name);
|
|
3504
|
-
const layer_names = list_layer_presets(presets).map((preset) => preset.name);
|
|
3505
|
-
const all_names = [...base_names, ...layer_names];
|
|
3506
|
-
if (parts.length <= 1) {
|
|
3507
|
-
const query = parts[0] ?? "";
|
|
3508
|
-
return [...[
|
|
3509
|
-
"list",
|
|
3510
|
-
"show",
|
|
3511
|
-
"clear",
|
|
3512
|
-
"edit",
|
|
3513
|
-
"delete",
|
|
3514
|
-
"reset",
|
|
3515
|
-
"reload",
|
|
3516
|
-
"base",
|
|
3517
|
-
"enable",
|
|
3518
|
-
"disable",
|
|
3519
|
-
"toggle"
|
|
3520
|
-
].filter((item) => item.startsWith(query)).map((item) => ({
|
|
3521
|
-
value: item,
|
|
3522
|
-
label: item
|
|
3523
|
-
})), ...all_names.filter((item) => item.startsWith(query)).map((item) => ({
|
|
3524
|
-
value: item,
|
|
3525
|
-
label: item
|
|
3526
|
-
}))];
|
|
3527
|
-
}
|
|
3528
|
-
const command = parts[0];
|
|
3529
|
-
const query = parts.slice(1).join(" ");
|
|
3530
|
-
if (command === "base") return base_names.filter((item) => item.startsWith(query)).map((item) => ({
|
|
3531
|
-
value: `base ${item}`,
|
|
3532
|
-
label: item
|
|
3533
|
-
}));
|
|
3534
|
-
if ([
|
|
3535
|
-
"enable",
|
|
3536
|
-
"disable",
|
|
3537
|
-
"toggle"
|
|
3538
|
-
].includes(command)) return layer_names.filter((item) => item.startsWith(query)).map((item) => ({
|
|
3539
|
-
value: `${command} ${item}`,
|
|
3540
|
-
label: item
|
|
3541
|
-
}));
|
|
3542
|
-
if (command === "edit") return all_names.filter((item) => item.startsWith(query)).map((item) => ({
|
|
3543
|
-
value: `edit ${item}`,
|
|
3544
|
-
label: item
|
|
3545
|
-
}));
|
|
3546
|
-
if (["delete", "reset"].includes(command)) return all_names.filter((item) => item.startsWith(query)).map((item) => ({
|
|
3547
|
-
value: `${command} ${item}`,
|
|
3548
|
-
label: item
|
|
3549
|
-
}));
|
|
3550
|
-
return null;
|
|
3551
|
-
},
|
|
3552
|
-
handler: async (args, ctx) => {
|
|
3553
|
-
const trimmed = args.trim();
|
|
3554
|
-
if (!trimmed) {
|
|
3555
|
-
if (ctx.hasUI) {
|
|
3556
|
-
await show_manager(ctx);
|
|
3821
|
+
if (reload_notice) {
|
|
3822
|
+
ctx.ui.notify(reload_notice, "info");
|
|
3823
|
+
await ctx.reload();
|
|
3557
3824
|
return;
|
|
3558
3825
|
}
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
const [first, ...rest] = trimmed.split(/\s+/);
|
|
3563
|
-
const arg = rest.join(" ").trim();
|
|
3564
|
-
switch (first) {
|
|
3565
|
-
case "list":
|
|
3566
|
-
ctx.ui.notify(format_summary(active_base_name, active_layers, presets), "info");
|
|
3567
|
-
return;
|
|
3568
|
-
case "show":
|
|
3569
|
-
ctx.ui.notify(format_active_details(active_base_name, active_layers, presets), "info");
|
|
3570
|
-
return;
|
|
3571
|
-
case "clear":
|
|
3572
|
-
commit_state(ctx, void 0, /* @__PURE__ */ new Set(), { notify: "Cleared base preset and prompt layers" });
|
|
3573
|
-
return;
|
|
3574
|
-
case "reload": {
|
|
3575
|
-
presets = load_prompt_presets(ctx.cwd);
|
|
3576
|
-
const normalized = normalize_active_state(presets, active_base_name, active_layers);
|
|
3577
|
-
active_base_name = normalized.active_base_name;
|
|
3578
|
-
active_layers = normalized.active_layers;
|
|
3579
|
-
set_status(ctx, active_base_name, active_layers);
|
|
3580
|
-
ctx.ui.notify("Reloaded prompt presets", "info");
|
|
3826
|
+
if (!sets_equal(initial_enabled, current_enabled)) {
|
|
3827
|
+
ctx.ui.notify("Reloading to apply updated skills...", "info");
|
|
3828
|
+
await ctx.reload();
|
|
3581
3829
|
return;
|
|
3582
3830
|
}
|
|
3583
|
-
case "base":
|
|
3584
|
-
if (!arg) {
|
|
3585
|
-
ctx.ui.notify("Usage: /preset base <name>", "warning");
|
|
3586
|
-
return;
|
|
3587
|
-
}
|
|
3588
|
-
activate_base(arg, ctx);
|
|
3589
|
-
return;
|
|
3590
|
-
case "enable":
|
|
3591
|
-
if (!arg) {
|
|
3592
|
-
ctx.ui.notify("Usage: /preset enable <layer>", "warning");
|
|
3593
|
-
return;
|
|
3594
|
-
}
|
|
3595
|
-
set_layer_enabled(arg, true, ctx);
|
|
3596
|
-
return;
|
|
3597
|
-
case "disable":
|
|
3598
|
-
if (!arg) {
|
|
3599
|
-
ctx.ui.notify("Usage: /preset disable <layer>", "warning");
|
|
3600
|
-
return;
|
|
3601
|
-
}
|
|
3602
|
-
set_layer_enabled(arg, false, ctx);
|
|
3603
|
-
return;
|
|
3604
|
-
case "toggle":
|
|
3605
|
-
if (!arg) {
|
|
3606
|
-
ctx.ui.notify("Usage: /preset toggle <layer>", "warning");
|
|
3607
|
-
return;
|
|
3608
|
-
}
|
|
3609
|
-
toggle_layer(arg, ctx);
|
|
3610
|
-
return;
|
|
3611
|
-
case "edit":
|
|
3612
|
-
if (!arg) {
|
|
3613
|
-
ctx.ui.notify("Usage: /preset edit <name>", "warning");
|
|
3614
|
-
return;
|
|
3615
|
-
}
|
|
3616
|
-
await edit_preset(arg, ctx);
|
|
3617
|
-
return;
|
|
3618
|
-
case "delete":
|
|
3619
|
-
if (!arg) {
|
|
3620
|
-
ctx.ui.notify("Usage: /preset delete <name>", "warning");
|
|
3621
|
-
return;
|
|
3622
|
-
}
|
|
3623
|
-
remove_custom_preset(arg, ctx, "delete");
|
|
3624
|
-
return;
|
|
3625
|
-
case "reset":
|
|
3626
|
-
if (!arg) {
|
|
3627
|
-
ctx.ui.notify("Usage: /preset reset <name>", "warning");
|
|
3628
|
-
return;
|
|
3629
|
-
}
|
|
3630
|
-
remove_custom_preset(arg, ctx, "reset");
|
|
3631
|
-
return;
|
|
3632
|
-
}
|
|
3633
|
-
if (is_subcommand(first)) {
|
|
3634
|
-
ctx.ui.notify(`Unsupported preset command: ${first}`, "warning");
|
|
3635
|
-
return;
|
|
3636
|
-
}
|
|
3637
|
-
const preset = presets[trimmed];
|
|
3638
|
-
if (!preset) {
|
|
3639
|
-
ctx.ui.notify(`Unknown preset or layer: ${trimmed}`, "warning");
|
|
3640
3831
|
return;
|
|
3641
3832
|
}
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
}
|
|
3688
|
-
//#endregion
|
|
3689
|
-
//#region src/extensions/recall.ts
|
|
3690
|
-
const DEFAULT_DB_PATH = join(process.env.HOME, ".pi", "pirecall.db");
|
|
3691
|
-
function sync_recall_db_in_background() {
|
|
3692
|
-
if (!existsSync(DEFAULT_DB_PATH)) return;
|
|
3693
|
-
try {
|
|
3694
|
-
spawn("npx", [
|
|
3695
|
-
"pirecall",
|
|
3696
|
-
"sync",
|
|
3697
|
-
"--json"
|
|
3698
|
-
], { stdio: "ignore" }).unref();
|
|
3699
|
-
} catch {}
|
|
3700
|
-
}
|
|
3701
|
-
async function recall(pi) {
|
|
3702
|
-
pi.on("session_start", async () => {
|
|
3703
|
-
sync_recall_db_in_background();
|
|
3704
|
-
});
|
|
3705
|
-
pi.on("before_agent_start", async (event) => {
|
|
3706
|
-
return { systemPrompt: event.systemPrompt + `
|
|
3707
|
-
|
|
3708
|
-
## Session Recall
|
|
3709
|
-
|
|
3710
|
-
You have access to past Pi session history via \`npx pirecall\`. Use it when:
|
|
3711
|
-
- The user references prior work ("what did we do", "last time", "remember when")
|
|
3712
|
-
- You need context from a previous session about this project
|
|
3713
|
-
- You want to avoid repeating work already done
|
|
3714
|
-
|
|
3715
|
-
Quick reference:
|
|
3716
|
-
- \`npx pirecall recall "<query>" --json\` — LLM-optimised context retrieval with surrounding messages
|
|
3717
|
-
- \`npx pirecall search "<query>" --json\` — full-text search (supports FTS5: AND, OR, NOT, "phrase", prefix*)
|
|
3718
|
-
- \`npx pirecall search "<query>" --json --project my-pi\` — filter by project
|
|
3719
|
-
- \`npx pirecall search "<query>" --json --after 2026-04-10\` — filter by date
|
|
3720
|
-
- \`npx pirecall sessions --json\` — list recent sessions
|
|
3721
|
-
- \`npx pirecall stats --json\` — database statistics
|
|
3722
|
-
|
|
3723
|
-
Always pass \`--json\` for structured output.` };
|
|
3833
|
+
const [sub, ...rest] = (trimmed || "list").split(/\s+/);
|
|
3834
|
+
const arg = rest.join(" ");
|
|
3835
|
+
switch (sub) {
|
|
3836
|
+
case "import":
|
|
3837
|
+
if (!arg) {
|
|
3838
|
+
ctx.ui.notify("Usage: /skills import <key|name>", "warning");
|
|
3839
|
+
return;
|
|
3840
|
+
}
|
|
3841
|
+
try {
|
|
3842
|
+
const result = mgr.import_skill(arg);
|
|
3843
|
+
ctx.ui.notify(`Imported ${arg} to ${result.skillDir}. Reloading...`, "info");
|
|
3844
|
+
await ctx.reload();
|
|
3845
|
+
return;
|
|
3846
|
+
} catch (error) {
|
|
3847
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "warning");
|
|
3848
|
+
return;
|
|
3849
|
+
}
|
|
3850
|
+
case "sync":
|
|
3851
|
+
if (!arg) {
|
|
3852
|
+
ctx.ui.notify("Usage: /skills sync <key|name>", "warning");
|
|
3853
|
+
return;
|
|
3854
|
+
}
|
|
3855
|
+
try {
|
|
3856
|
+
const result = mgr.sync_skill(arg);
|
|
3857
|
+
ctx.ui.notify(result.changed ? `Synced ${arg}. Reloading...` : `${arg} is already up to date.`, "info");
|
|
3858
|
+
if (result.changed) await ctx.reload();
|
|
3859
|
+
return;
|
|
3860
|
+
} catch (error) {
|
|
3861
|
+
ctx.ui.notify(error instanceof Error ? error.message : String(error), "warning");
|
|
3862
|
+
return;
|
|
3863
|
+
}
|
|
3864
|
+
case "refresh":
|
|
3865
|
+
mgr.refresh();
|
|
3866
|
+
ctx.ui.notify(`Rescanned: ${mgr.discover().length} managed skills, ${mgr.discover_importable().length} importable skills found`);
|
|
3867
|
+
break;
|
|
3868
|
+
case "defaults":
|
|
3869
|
+
if (arg !== "all-enabled" && arg !== "all-disabled") {
|
|
3870
|
+
ctx.ui.notify("Usage: /skills defaults <all-enabled|all-disabled>", "warning");
|
|
3871
|
+
return;
|
|
3872
|
+
}
|
|
3873
|
+
mgr.set_defaults(arg);
|
|
3874
|
+
ctx.ui.notify(`Default policy: ${arg}`);
|
|
3875
|
+
break;
|
|
3876
|
+
default: ctx.ui.notify(`Unknown: ${sub}. Use: ${subs.join(", ")}`, "warning");
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3724
3879
|
});
|
|
3725
3880
|
}
|
|
3726
3881
|
//#endregion
|
|
3727
|
-
//#region src/
|
|
3882
|
+
//#region src/extensions/telemetry-config.ts
|
|
3728
3883
|
const DEFAULT_CONFIG = {
|
|
3729
3884
|
version: 1,
|
|
3730
|
-
enabled:
|
|
3731
|
-
defaults: "all-disabled"
|
|
3885
|
+
enabled: false
|
|
3732
3886
|
};
|
|
3733
|
-
function
|
|
3734
|
-
return join(
|
|
3735
|
-
}
|
|
3736
|
-
function load_skills_config() {
|
|
3737
|
-
const path = get_config_path();
|
|
3738
|
-
if (!existsSync(path)) return { ...DEFAULT_CONFIG };
|
|
3739
|
-
try {
|
|
3740
|
-
const raw = readFileSync(path, "utf-8");
|
|
3741
|
-
const parsed = JSON.parse(raw);
|
|
3742
|
-
return {
|
|
3743
|
-
version: parsed.version ?? 1,
|
|
3744
|
-
enabled: parsed.enabled ?? {},
|
|
3745
|
-
defaults: parsed.defaults ?? "all-enabled"
|
|
3746
|
-
};
|
|
3747
|
-
} catch {
|
|
3748
|
-
return { ...DEFAULT_CONFIG };
|
|
3749
|
-
}
|
|
3750
|
-
}
|
|
3751
|
-
function save_skills_config(config) {
|
|
3752
|
-
const path = get_config_path();
|
|
3753
|
-
const dir = dirname(path);
|
|
3754
|
-
if (!existsSync(dir)) mkdirSync(dir, {
|
|
3755
|
-
recursive: true,
|
|
3756
|
-
mode: 448
|
|
3757
|
-
});
|
|
3758
|
-
const tmp = `${path}.tmp-${Date.now()}`;
|
|
3759
|
-
writeFileSync(tmp, JSON.stringify(config, null, " ") + "\n", { mode: 384 });
|
|
3760
|
-
renameSync(tmp, path);
|
|
3761
|
-
}
|
|
3762
|
-
function make_skill_key(name, source) {
|
|
3763
|
-
return `${name}@${source}`;
|
|
3764
|
-
}
|
|
3765
|
-
function is_skill_enabled(config, key) {
|
|
3766
|
-
if (key in config.enabled) return config.enabled[key];
|
|
3767
|
-
return config.defaults === "all-enabled";
|
|
3768
|
-
}
|
|
3769
|
-
//#endregion
|
|
3770
|
-
//#region src/skills/scanner.ts
|
|
3771
|
-
const IMPORT_METADATA_FILE = ".my-pi-source.json";
|
|
3772
|
-
function read_installed_plugins() {
|
|
3773
|
-
const path = join(homedir(), ".claude", "plugins", "installed_plugins.json");
|
|
3774
|
-
if (!existsSync(path)) return null;
|
|
3775
|
-
try {
|
|
3776
|
-
return JSON.parse(readFileSync(path, "utf-8"));
|
|
3777
|
-
} catch {
|
|
3778
|
-
return null;
|
|
3779
|
-
}
|
|
3780
|
-
}
|
|
3781
|
-
function parse_skill_md(skill_path) {
|
|
3782
|
-
try {
|
|
3783
|
-
const { frontmatter } = parseFrontmatter(readFileSync(skill_path, "utf-8"));
|
|
3784
|
-
const description = frontmatter?.description;
|
|
3785
|
-
if (!description) return null;
|
|
3786
|
-
return {
|
|
3787
|
-
name: frontmatter?.name || basename(dirname(skill_path)),
|
|
3788
|
-
description: description.trim()
|
|
3789
|
-
};
|
|
3790
|
-
} catch {
|
|
3791
|
-
return null;
|
|
3792
|
-
}
|
|
3793
|
-
}
|
|
3794
|
-
function read_import_metadata(base_dir) {
|
|
3795
|
-
const metadata_path = join(base_dir, IMPORT_METADATA_FILE);
|
|
3796
|
-
if (!existsSync(metadata_path)) return void 0;
|
|
3797
|
-
try {
|
|
3798
|
-
return JSON.parse(readFileSync(metadata_path, "utf-8"));
|
|
3799
|
-
} catch {
|
|
3800
|
-
return;
|
|
3801
|
-
}
|
|
3802
|
-
}
|
|
3803
|
-
function scan_dir_for_skills(dir, options) {
|
|
3804
|
-
if (!existsSync(dir)) return [];
|
|
3805
|
-
const results = [];
|
|
3806
|
-
const direct = join(dir, "SKILL.md");
|
|
3807
|
-
if ((options.include_direct_root_skill ?? true) && existsSync(direct)) {
|
|
3808
|
-
const parsed = parse_skill_md(direct);
|
|
3809
|
-
if (parsed) results.push({
|
|
3810
|
-
...parsed,
|
|
3811
|
-
skillPath: direct,
|
|
3812
|
-
baseDir: dir,
|
|
3813
|
-
source: options.source,
|
|
3814
|
-
kind: options.kind,
|
|
3815
|
-
plugin: options.plugin,
|
|
3816
|
-
import_meta: options.kind === "managed" ? read_import_metadata(dir) : void 0
|
|
3817
|
-
});
|
|
3818
|
-
return results;
|
|
3819
|
-
}
|
|
3820
|
-
try {
|
|
3821
|
-
const matches = globSync("*/SKILL.md", { cwd: dir });
|
|
3822
|
-
for (const match of matches) {
|
|
3823
|
-
const full_path = resolve(dir, match);
|
|
3824
|
-
const parsed = parse_skill_md(full_path);
|
|
3825
|
-
if (parsed) {
|
|
3826
|
-
const base_dir = dirname(full_path);
|
|
3827
|
-
results.push({
|
|
3828
|
-
...parsed,
|
|
3829
|
-
skillPath: full_path,
|
|
3830
|
-
baseDir: base_dir,
|
|
3831
|
-
source: options.source,
|
|
3832
|
-
kind: options.kind,
|
|
3833
|
-
plugin: options.plugin,
|
|
3834
|
-
import_meta: options.kind === "managed" ? read_import_metadata(base_dir) : void 0
|
|
3835
|
-
});
|
|
3836
|
-
}
|
|
3837
|
-
}
|
|
3838
|
-
} catch {}
|
|
3839
|
-
return results;
|
|
3840
|
-
}
|
|
3841
|
-
function dedupe_by_skill_path(skills) {
|
|
3842
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3843
|
-
const deduped = [];
|
|
3844
|
-
for (const skill of skills) {
|
|
3845
|
-
if (seen.has(skill.skillPath)) continue;
|
|
3846
|
-
seen.add(skill.skillPath);
|
|
3847
|
-
deduped.push(skill);
|
|
3848
|
-
}
|
|
3849
|
-
return deduped;
|
|
3850
|
-
}
|
|
3851
|
-
function scan_managed_skills() {
|
|
3852
|
-
const skills = [];
|
|
3853
|
-
for (const skill of scan_dir_for_skills(join(homedir(), ".claude", "skills"), {
|
|
3854
|
-
source: "user-local",
|
|
3855
|
-
kind: "managed"
|
|
3856
|
-
})) skills.push(skill);
|
|
3857
|
-
for (const skill of scan_dir_for_skills(join(homedir(), ".pi", "agent", "skills"), {
|
|
3858
|
-
source: "pi-native",
|
|
3859
|
-
kind: "managed",
|
|
3860
|
-
include_direct_root_skill: false
|
|
3861
|
-
})) skills.push(skill);
|
|
3862
|
-
return dedupe_by_skill_path(skills);
|
|
3863
|
-
}
|
|
3864
|
-
function scan_importable_skills() {
|
|
3865
|
-
const skills = [];
|
|
3866
|
-
const plugins = read_installed_plugins();
|
|
3867
|
-
if (!plugins?.plugins) return skills;
|
|
3868
|
-
for (const [plugin_id, entries] of Object.entries(plugins.plugins)) {
|
|
3869
|
-
const entry = entries[0];
|
|
3870
|
-
if (!entry?.installPath || !existsSync(entry.installPath)) continue;
|
|
3871
|
-
const source = `plugin:${plugin_id}`;
|
|
3872
|
-
const plugin = {
|
|
3873
|
-
pluginId: plugin_id,
|
|
3874
|
-
installPath: entry.installPath,
|
|
3875
|
-
version: entry.version,
|
|
3876
|
-
gitCommitSha: entry.gitCommitSha
|
|
3877
|
-
};
|
|
3878
|
-
for (const skill of scan_dir_for_skills(join(entry.installPath, "skills"), {
|
|
3879
|
-
source,
|
|
3880
|
-
kind: "external",
|
|
3881
|
-
plugin
|
|
3882
|
-
})) skills.push(skill);
|
|
3883
|
-
for (const skill of scan_dir_for_skills(join(entry.installPath, ".pi", "skills"), {
|
|
3884
|
-
source,
|
|
3885
|
-
kind: "external",
|
|
3886
|
-
plugin
|
|
3887
|
-
})) skills.push(skill);
|
|
3888
|
-
const direct_root_skill = join(entry.installPath, "SKILL.md");
|
|
3889
|
-
if (existsSync(direct_root_skill)) {
|
|
3890
|
-
const parsed = parse_skill_md(direct_root_skill);
|
|
3891
|
-
if (parsed) skills.push({
|
|
3892
|
-
...parsed,
|
|
3893
|
-
skillPath: direct_root_skill,
|
|
3894
|
-
baseDir: entry.installPath,
|
|
3895
|
-
source,
|
|
3896
|
-
kind: "external",
|
|
3897
|
-
plugin
|
|
3898
|
-
});
|
|
3899
|
-
}
|
|
3900
|
-
}
|
|
3901
|
-
return dedupe_by_skill_path(skills);
|
|
3902
|
-
}
|
|
3903
|
-
//#endregion
|
|
3904
|
-
//#region src/skills/importer.ts
|
|
3905
|
-
const IMPORT_METADATA_VERSION = 1;
|
|
3906
|
-
function get_managed_skills_dir() {
|
|
3907
|
-
return join(homedir(), ".pi", "agent", "skills");
|
|
3908
|
-
}
|
|
3909
|
-
function ensure_dir(path) {
|
|
3910
|
-
mkdirSync(path, {
|
|
3911
|
-
recursive: true,
|
|
3912
|
-
mode: 448
|
|
3913
|
-
});
|
|
3914
|
-
}
|
|
3915
|
-
function list_files_recursively(dir) {
|
|
3916
|
-
const files = [];
|
|
3917
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
3918
|
-
const full_path = join(dir, entry.name);
|
|
3919
|
-
if (entry.name === ".my-pi-source.json") continue;
|
|
3920
|
-
if (entry.isDirectory()) {
|
|
3921
|
-
files.push(...list_files_recursively(full_path));
|
|
3922
|
-
continue;
|
|
3923
|
-
}
|
|
3924
|
-
if (entry.isFile()) files.push(full_path);
|
|
3925
|
-
}
|
|
3926
|
-
return files.sort((a, b) => a.localeCompare(b));
|
|
3927
|
-
}
|
|
3928
|
-
function hash_directory(dir) {
|
|
3929
|
-
const hash = createHash("sha256");
|
|
3930
|
-
for (const file of list_files_recursively(dir)) {
|
|
3931
|
-
hash.update(relative(dir, file));
|
|
3932
|
-
hash.update("\0");
|
|
3933
|
-
hash.update(readFileSync(file));
|
|
3934
|
-
hash.update("\0");
|
|
3935
|
-
}
|
|
3936
|
-
return hash.digest("hex");
|
|
3887
|
+
function get_telemetry_config_path() {
|
|
3888
|
+
return join(getAgentDir(), "telemetry.json");
|
|
3937
3889
|
}
|
|
3938
|
-
function
|
|
3939
|
-
|
|
3940
|
-
|
|
3890
|
+
function get_default_telemetry_db_path() {
|
|
3891
|
+
return join(getAgentDir(), "telemetry.db");
|
|
3892
|
+
}
|
|
3893
|
+
function resolve_telemetry_db_path(cwd, override_path) {
|
|
3894
|
+
if (!override_path) return get_default_telemetry_db_path();
|
|
3895
|
+
return resolve(cwd, override_path);
|
|
3896
|
+
}
|
|
3897
|
+
function load_telemetry_config() {
|
|
3898
|
+
const path = get_telemetry_config_path();
|
|
3899
|
+
if (!existsSync(path)) return { ...DEFAULT_CONFIG };
|
|
3941
3900
|
try {
|
|
3942
|
-
|
|
3901
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
3902
|
+
return {
|
|
3903
|
+
version: typeof parsed.version === "number" ? parsed.version : DEFAULT_CONFIG.version,
|
|
3904
|
+
enabled: typeof parsed.enabled === "boolean" ? parsed.enabled : DEFAULT_CONFIG.enabled
|
|
3905
|
+
};
|
|
3943
3906
|
} catch {
|
|
3944
|
-
return;
|
|
3907
|
+
return { ...DEFAULT_CONFIG };
|
|
3945
3908
|
}
|
|
3946
3909
|
}
|
|
3947
|
-
function
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
const parent_dir = dirname(dest_dir);
|
|
3952
|
-
ensure_dir(parent_dir);
|
|
3953
|
-
const tmp_dir = join(parent_dir, `.${resolve(dest_dir).split("/").pop()}.tmp-${Date.now()}`);
|
|
3954
|
-
rmSync(tmp_dir, {
|
|
3955
|
-
recursive: true,
|
|
3956
|
-
force: true
|
|
3957
|
-
});
|
|
3958
|
-
cpSync(source_dir, tmp_dir, {
|
|
3959
|
-
recursive: true,
|
|
3960
|
-
preserveTimestamps: true,
|
|
3961
|
-
verbatimSymlinks: false
|
|
3962
|
-
});
|
|
3963
|
-
rmSync(dest_dir, {
|
|
3964
|
-
recursive: true,
|
|
3965
|
-
force: true
|
|
3966
|
-
});
|
|
3967
|
-
cpSync(tmp_dir, dest_dir, {
|
|
3968
|
-
recursive: true,
|
|
3969
|
-
preserveTimestamps: true,
|
|
3970
|
-
verbatimSymlinks: false
|
|
3971
|
-
});
|
|
3972
|
-
rmSync(tmp_dir, {
|
|
3910
|
+
function save_telemetry_config(config) {
|
|
3911
|
+
const path = get_telemetry_config_path();
|
|
3912
|
+
const dir = dirname(path);
|
|
3913
|
+
if (!existsSync(dir)) mkdirSync(dir, {
|
|
3973
3914
|
recursive: true,
|
|
3974
|
-
|
|
3915
|
+
mode: 448
|
|
3975
3916
|
});
|
|
3917
|
+
const tmp = `${path}.tmp-${Date.now()}`;
|
|
3918
|
+
writeFileSync(tmp, JSON.stringify(config, null, " ") + "\n", { mode: 384 });
|
|
3919
|
+
renameSync(tmp, path);
|
|
3976
3920
|
}
|
|
3977
|
-
function
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
imported_at: now,
|
|
3999
|
-
last_synced_at: now,
|
|
4000
|
-
imported_hash,
|
|
4001
|
-
upstream_hash
|
|
4002
|
-
};
|
|
4003
|
-
write_metadata(skill_dir, metadata);
|
|
3921
|
+
function resolve_telemetry_enabled(config = load_telemetry_config(), override) {
|
|
3922
|
+
return override ?? config.enabled;
|
|
3923
|
+
}
|
|
3924
|
+
//#endregion
|
|
3925
|
+
//#region src/extensions/telemetry.ts
|
|
3926
|
+
const COMMANDS = [
|
|
3927
|
+
"status",
|
|
3928
|
+
"stats",
|
|
3929
|
+
"query",
|
|
3930
|
+
"export",
|
|
3931
|
+
"on",
|
|
3932
|
+
"off",
|
|
3933
|
+
"path"
|
|
3934
|
+
];
|
|
3935
|
+
const DEFAULT_QUERY_LIMIT = 20;
|
|
3936
|
+
function parse_int(value) {
|
|
3937
|
+
if (!value) return null;
|
|
3938
|
+
const parsed = Number.parseInt(value, 10);
|
|
3939
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
3940
|
+
}
|
|
3941
|
+
function get_eval_metadata() {
|
|
4004
3942
|
return {
|
|
4005
|
-
|
|
4006
|
-
|
|
3943
|
+
run_id: process.env.MY_PI_EVAL_RUN_ID ?? null,
|
|
3944
|
+
case_id: process.env.MY_PI_EVAL_CASE_ID ?? null,
|
|
3945
|
+
attempt: parse_int(process.env.MY_PI_EVAL_ATTEMPT),
|
|
3946
|
+
suite: process.env.MY_PI_EVAL_SUITE ?? null
|
|
4007
3947
|
};
|
|
4008
3948
|
}
|
|
4009
|
-
function
|
|
4010
|
-
if (
|
|
4011
|
-
|
|
4012
|
-
|
|
4013
|
-
if (hash_directory(skill.baseDir) !== metadata.imported_hash) throw new Error(`Refusing to sync ${skill.name}; local changes detected in ${skill.baseDir}`);
|
|
4014
|
-
const upstream_hash = hash_directory(metadata.upstream_base_dir);
|
|
4015
|
-
if (upstream_hash === metadata.upstream_hash) return {
|
|
4016
|
-
skillDir: skill.baseDir,
|
|
4017
|
-
metadata,
|
|
4018
|
-
changed: false
|
|
4019
|
-
};
|
|
4020
|
-
replace_directory(metadata.upstream_base_dir, skill.baseDir);
|
|
4021
|
-
const imported_hash = hash_directory(skill.baseDir);
|
|
4022
|
-
const updated = {
|
|
4023
|
-
...metadata,
|
|
4024
|
-
last_synced_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4025
|
-
imported_hash,
|
|
4026
|
-
upstream_hash
|
|
3949
|
+
function get_model_identity(model) {
|
|
3950
|
+
if (!model) return {
|
|
3951
|
+
provider: null,
|
|
3952
|
+
id: null
|
|
4027
3953
|
};
|
|
4028
|
-
write_metadata(skill.baseDir, updated);
|
|
4029
3954
|
return {
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
changed: true
|
|
3955
|
+
provider: typeof model.provider === "string" ? model.provider : null,
|
|
3956
|
+
id: typeof model.id === "string" ? model.id : null
|
|
4033
3957
|
};
|
|
4034
3958
|
}
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
function resolve_skill_key(skill) {
|
|
4038
|
-
return make_skill_key(skill.name, skill.source);
|
|
4039
|
-
}
|
|
4040
|
-
function match_skill_by_key_or_name(skills, key_or_name) {
|
|
4041
|
-
const exact_key = skills.find((skill) => resolve_skill_key(skill) === key_or_name);
|
|
4042
|
-
if (exact_key) return exact_key;
|
|
4043
|
-
const by_name = skills.filter((skill) => skill.name === key_or_name);
|
|
4044
|
-
if (by_name.length === 1) return by_name[0];
|
|
4045
|
-
if (by_name.length > 1) throw new Error(`Multiple skills named ${key_or_name}. Use an exact key instead.`);
|
|
4046
|
-
throw new Error(`Unknown skill: ${key_or_name}`);
|
|
3959
|
+
function get_session_file(ctx) {
|
|
3960
|
+
return ctx.sessionManager.getSessionFile?.() ?? null;
|
|
4047
3961
|
}
|
|
4048
|
-
function
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
if (!importable_cache) importable_cache = scan_importable_skills();
|
|
4058
|
-
return importable_cache;
|
|
4059
|
-
}
|
|
4060
|
-
function to_managed(skill) {
|
|
4061
|
-
const key = resolve_skill_key(skill);
|
|
4062
|
-
return {
|
|
4063
|
-
...skill,
|
|
4064
|
-
key,
|
|
4065
|
-
enabled: skill.kind === "managed" ? is_skill_enabled(config, key) : false
|
|
4066
|
-
};
|
|
4067
|
-
}
|
|
4068
|
-
function get_enabled_managed_skills() {
|
|
4069
|
-
return get_managed().filter((skill) => is_skill_enabled(config, resolve_skill_key(skill))).map(to_managed);
|
|
3962
|
+
function safe_json_stringify(value) {
|
|
3963
|
+
if (value === void 0) return null;
|
|
3964
|
+
try {
|
|
3965
|
+
return JSON.stringify(value);
|
|
3966
|
+
} catch {
|
|
3967
|
+
return JSON.stringify({
|
|
3968
|
+
type: typeof value,
|
|
3969
|
+
unserializable: true
|
|
3970
|
+
});
|
|
4070
3971
|
}
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
return true;
|
|
4085
|
-
},
|
|
4086
|
-
get_enabled_skill_paths() {
|
|
4087
|
-
return get_enabled_managed_skills().map((skill) => skill.baseDir);
|
|
4088
|
-
},
|
|
4089
|
-
enable(key) {
|
|
4090
|
-
config.enabled[key] = true;
|
|
4091
|
-
save_skills_config(config);
|
|
4092
|
-
return true;
|
|
4093
|
-
},
|
|
4094
|
-
disable(key) {
|
|
4095
|
-
config.enabled[key] = false;
|
|
4096
|
-
save_skills_config(config);
|
|
4097
|
-
return false;
|
|
4098
|
-
},
|
|
4099
|
-
toggle(key) {
|
|
4100
|
-
const current = is_skill_enabled(config, key);
|
|
4101
|
-
config.enabled[key] = !current;
|
|
4102
|
-
save_skills_config(config);
|
|
4103
|
-
return !current;
|
|
4104
|
-
},
|
|
4105
|
-
search(query) {
|
|
4106
|
-
const q = query.toLowerCase();
|
|
4107
|
-
return this.discover().filter((s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.source.toLowerCase().includes(q));
|
|
4108
|
-
},
|
|
4109
|
-
search_importable(query) {
|
|
4110
|
-
const q = query.toLowerCase();
|
|
4111
|
-
return this.discover_importable().filter((s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q) || s.source.toLowerCase().includes(q));
|
|
4112
|
-
},
|
|
4113
|
-
set_defaults(policy) {
|
|
4114
|
-
config.defaults = policy;
|
|
4115
|
-
save_skills_config(config);
|
|
4116
|
-
},
|
|
4117
|
-
import_skill(key_or_name) {
|
|
4118
|
-
const skill = match_skill_by_key_or_name(get_importable(), key_or_name);
|
|
4119
|
-
const result = import_external_skill(skill);
|
|
4120
|
-
const managed_key = make_skill_key(skill.name, "pi-native");
|
|
4121
|
-
config.enabled[managed_key] = true;
|
|
4122
|
-
save_skills_config(config);
|
|
4123
|
-
this.refresh();
|
|
4124
|
-
return {
|
|
4125
|
-
...result,
|
|
4126
|
-
key: managed_key
|
|
4127
|
-
};
|
|
4128
|
-
},
|
|
4129
|
-
sync_skill(key_or_name) {
|
|
4130
|
-
const skill = match_skill_by_key_or_name(get_managed(), key_or_name);
|
|
4131
|
-
const result = sync_imported_skill(skill);
|
|
4132
|
-
this.refresh();
|
|
4133
|
-
return {
|
|
4134
|
-
...result,
|
|
4135
|
-
key: resolve_skill_key(skill)
|
|
4136
|
-
};
|
|
4137
|
-
},
|
|
4138
|
-
refresh() {
|
|
4139
|
-
managed_cache = null;
|
|
4140
|
-
importable_cache = null;
|
|
4141
|
-
config = load_skills_config();
|
|
4142
|
-
}
|
|
3972
|
+
}
|
|
3973
|
+
function summarize_value(value, depth = 0) {
|
|
3974
|
+
if (value == null) return null;
|
|
3975
|
+
if (typeof value === "string") return {
|
|
3976
|
+
type: "string",
|
|
3977
|
+
bytes: Buffer.byteLength(value, "utf-8"),
|
|
3978
|
+
lines: value === "" ? 0 : value.split(/\r?\n/).length
|
|
3979
|
+
};
|
|
3980
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return value;
|
|
3981
|
+
if (Array.isArray(value)) return {
|
|
3982
|
+
type: "array",
|
|
3983
|
+
length: value.length,
|
|
3984
|
+
items: depth >= 1 ? void 0 : value.slice(0, 5).map((item) => summarize_value(item, depth + 1))
|
|
4143
3985
|
};
|
|
3986
|
+
if (typeof value === "object") {
|
|
3987
|
+
const entries = Object.entries(value);
|
|
3988
|
+
const summary = {
|
|
3989
|
+
type: "object",
|
|
3990
|
+
keys: entries.map(([key]) => key).slice(0, 20)
|
|
3991
|
+
};
|
|
3992
|
+
if (depth < 1) for (const [key, child] of entries.slice(0, 10)) {
|
|
3993
|
+
if (key === "oldText" || key === "newText" || key === "content" || key === "text") {
|
|
3994
|
+
summary[`${key}_summary`] = summarize_value(child, depth + 1);
|
|
3995
|
+
continue;
|
|
3996
|
+
}
|
|
3997
|
+
summary[key] = summarize_value(child, depth + 1);
|
|
3998
|
+
}
|
|
3999
|
+
return summary;
|
|
4000
|
+
}
|
|
4001
|
+
return { type: typeof value };
|
|
4144
4002
|
}
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
const
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4003
|
+
function summarize_tool_args(tool_name, args) {
|
|
4004
|
+
if (!args || typeof args !== "object") return safe_json_stringify(summarize_value(args));
|
|
4005
|
+
const input = args;
|
|
4006
|
+
switch (tool_name) {
|
|
4007
|
+
case "bash": return safe_json_stringify({
|
|
4008
|
+
tool: tool_name,
|
|
4009
|
+
timeout: input.timeout ?? null,
|
|
4010
|
+
command: summarize_value(input.command)
|
|
4011
|
+
});
|
|
4012
|
+
case "read":
|
|
4013
|
+
case "write":
|
|
4014
|
+
case "edit": return safe_json_stringify({
|
|
4015
|
+
tool: tool_name,
|
|
4016
|
+
path: typeof input.path === "string" ? input.path : null,
|
|
4017
|
+
offset: typeof input.offset === "number" ? input.offset : null,
|
|
4018
|
+
limit: typeof input.limit === "number" ? input.limit : null,
|
|
4019
|
+
content: summarize_value(input.content),
|
|
4020
|
+
edits: summarize_value(input.edits)
|
|
4021
|
+
});
|
|
4022
|
+
default: return safe_json_stringify({
|
|
4023
|
+
tool: tool_name,
|
|
4024
|
+
summary: summarize_value(args)
|
|
4025
|
+
});
|
|
4026
|
+
}
|
|
4027
|
+
}
|
|
4028
|
+
function summarize_tool_result(result) {
|
|
4029
|
+
return safe_json_stringify(summarize_value(result));
|
|
4030
|
+
}
|
|
4031
|
+
function summarize_headers(headers) {
|
|
4032
|
+
return safe_json_stringify({
|
|
4033
|
+
keys: Object.keys(headers).slice(0, 20),
|
|
4034
|
+
count: Object.keys(headers).length
|
|
4158
4035
|
});
|
|
4159
4036
|
}
|
|
4160
|
-
function
|
|
4161
|
-
|
|
4162
|
-
if (exact_match) return exact_match;
|
|
4163
|
-
return managed_skills.find((candidate) => candidate.import_meta?.source === skill.source && candidate.name === skill.name);
|
|
4037
|
+
function summarize_provider_payload(payload) {
|
|
4038
|
+
return safe_json_stringify(summarize_value(payload));
|
|
4164
4039
|
}
|
|
4165
|
-
function
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
if (
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
action: null
|
|
4040
|
+
function get_stop_reason(message) {
|
|
4041
|
+
if (!message || typeof message !== "object") return null;
|
|
4042
|
+
const stop_reason = message.stopReason;
|
|
4043
|
+
return typeof stop_reason === "string" ? stop_reason : null;
|
|
4044
|
+
}
|
|
4045
|
+
function get_error_message(message) {
|
|
4046
|
+
if (!message || typeof message !== "object") return null;
|
|
4047
|
+
const error_message = message.errorMessage;
|
|
4048
|
+
return typeof error_message === "string" ? error_message : null;
|
|
4049
|
+
}
|
|
4050
|
+
function infer_run_outcome(event) {
|
|
4051
|
+
const last_assistant = event.messages.filter((message) => message.role === "assistant").at(-1);
|
|
4052
|
+
const stop_reason = get_stop_reason(last_assistant);
|
|
4053
|
+
if (stop_reason === "error") return {
|
|
4054
|
+
success: false,
|
|
4055
|
+
error_message: get_error_message(last_assistant) ?? "agent error"
|
|
4056
|
+
};
|
|
4057
|
+
if (stop_reason === "aborted") return {
|
|
4058
|
+
success: false,
|
|
4059
|
+
error_message: get_error_message(last_assistant) ?? "agent aborted"
|
|
4186
4060
|
};
|
|
4187
4061
|
return {
|
|
4188
|
-
|
|
4189
|
-
|
|
4190
|
-
action: "import"
|
|
4062
|
+
success: true,
|
|
4063
|
+
error_message: null
|
|
4191
4064
|
};
|
|
4192
4065
|
}
|
|
4193
|
-
function
|
|
4194
|
-
const
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4066
|
+
function format_telemetry_status(options) {
|
|
4067
|
+
const override_label = options.override === void 0 ? "none" : options.override ? "--telemetry" : "--no-telemetry";
|
|
4068
|
+
return [
|
|
4069
|
+
`telemetry ${options.effective_enabled ? "enabled" : "disabled"} now`,
|
|
4070
|
+
`default ${options.saved_enabled ? "enabled" : "disabled"}`,
|
|
4071
|
+
`override ${override_label}`,
|
|
4072
|
+
`db ${options.db_path}`
|
|
4073
|
+
].join("\n");
|
|
4074
|
+
}
|
|
4075
|
+
function format_bytes(bytes) {
|
|
4076
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
4077
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
4078
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
4079
|
+
}
|
|
4080
|
+
function format_timestamp(timestamp) {
|
|
4081
|
+
return new Date(timestamp).toISOString();
|
|
4082
|
+
}
|
|
4083
|
+
function format_duration(duration_ms) {
|
|
4084
|
+
if (duration_ms === null) return "open";
|
|
4085
|
+
if (duration_ms < 1e3) return `${duration_ms}ms`;
|
|
4086
|
+
if (duration_ms < 6e4) return `${(duration_ms / 1e3).toFixed(1)}s`;
|
|
4087
|
+
return `${(duration_ms / 6e4).toFixed(1)}m`;
|
|
4088
|
+
}
|
|
4089
|
+
function format_success(value) {
|
|
4090
|
+
if (value === true) return "success";
|
|
4091
|
+
if (value === false) return "failure";
|
|
4092
|
+
return "unknown";
|
|
4093
|
+
}
|
|
4094
|
+
function tokenize_command_args(input) {
|
|
4095
|
+
return (input.match(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\S+/g) ?? []).map((token) => {
|
|
4096
|
+
if (token.startsWith("\"") && token.endsWith("\"") || token.startsWith("'") && token.endsWith("'")) return token.slice(1, -1);
|
|
4097
|
+
return token;
|
|
4098
|
+
});
|
|
4099
|
+
}
|
|
4100
|
+
function parse_telemetry_command(input) {
|
|
4101
|
+
const tokens = tokenize_command_args(input.trim());
|
|
4102
|
+
const subcommand = tokens[0] ?? "status";
|
|
4103
|
+
const filters = {};
|
|
4104
|
+
let export_path = null;
|
|
4105
|
+
const errors = [];
|
|
4106
|
+
for (const token of tokens.slice(1)) {
|
|
4107
|
+
const equals_index = token.indexOf("=");
|
|
4108
|
+
if (equals_index === -1) {
|
|
4109
|
+
if (subcommand === "export" && export_path === null) export_path = token;
|
|
4110
|
+
else errors.push(`Unexpected argument: ${token}`);
|
|
4111
|
+
continue;
|
|
4112
|
+
}
|
|
4113
|
+
const key = token.slice(0, equals_index);
|
|
4114
|
+
const value = token.slice(equals_index + 1);
|
|
4115
|
+
switch (key) {
|
|
4116
|
+
case "eval_run_id":
|
|
4117
|
+
case "run":
|
|
4118
|
+
filters.eval_run_id = value;
|
|
4119
|
+
break;
|
|
4120
|
+
case "eval_case_id":
|
|
4121
|
+
case "case":
|
|
4122
|
+
filters.eval_case_id = value;
|
|
4123
|
+
break;
|
|
4124
|
+
case "eval_suite":
|
|
4125
|
+
case "suite":
|
|
4126
|
+
filters.eval_suite = value;
|
|
4127
|
+
break;
|
|
4128
|
+
case "success":
|
|
4129
|
+
if (value === "true") filters.success = true;
|
|
4130
|
+
else if (value === "false") filters.success = false;
|
|
4131
|
+
else if (value === "null") filters.success = null;
|
|
4132
|
+
else errors.push(`Invalid success value: ${value}. Use true, false, or null`);
|
|
4133
|
+
break;
|
|
4134
|
+
case "limit": {
|
|
4135
|
+
const parsed = Number.parseInt(value, 10);
|
|
4136
|
+
if (!Number.isFinite(parsed) || parsed <= 0) errors.push(`Invalid limit value: ${value}. Use a positive integer`);
|
|
4137
|
+
else filters.limit = parsed;
|
|
4138
|
+
break;
|
|
4139
|
+
}
|
|
4140
|
+
default: errors.push(`Unknown filter: ${key}`);
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
if (subcommand === "query" && filters.limit === void 0) filters.limit = DEFAULT_QUERY_LIMIT;
|
|
4200
4144
|
return {
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
values: [ENABLED, DISABLED]
|
|
4145
|
+
subcommand,
|
|
4146
|
+
export_path,
|
|
4147
|
+
filters,
|
|
4148
|
+
errors
|
|
4206
4149
|
};
|
|
4207
4150
|
}
|
|
4208
|
-
function
|
|
4209
|
-
const
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4151
|
+
function format_filter_summary(filters) {
|
|
4152
|
+
const parts = [];
|
|
4153
|
+
if (filters.eval_run_id !== void 0) parts.push(`eval_run_id=${filters.eval_run_id}`);
|
|
4154
|
+
if (filters.eval_case_id !== void 0) parts.push(`eval_case_id=${filters.eval_case_id}`);
|
|
4155
|
+
if (filters.eval_suite !== void 0) parts.push(`eval_suite=${filters.eval_suite}`);
|
|
4156
|
+
if (filters.success !== void 0) parts.push(`success=${String(filters.success)}`);
|
|
4157
|
+
if (filters.limit !== void 0) parts.push(`limit=${filters.limit}`);
|
|
4158
|
+
return parts.length > 0 ? parts.join(" ") : "none";
|
|
4159
|
+
}
|
|
4160
|
+
function format_telemetry_stats(options) {
|
|
4161
|
+
return [
|
|
4162
|
+
`db ${options.db_path}`,
|
|
4163
|
+
`schema v${options.stats.schema_version}`,
|
|
4164
|
+
`runs ${options.stats.runs}`,
|
|
4165
|
+
`turns ${options.stats.turns}`,
|
|
4166
|
+
`tool_calls ${options.stats.tool_calls}`,
|
|
4167
|
+
`provider_requests ${options.stats.provider_requests}`,
|
|
4168
|
+
`db_bytes ${format_bytes(options.stats.db_bytes)}`,
|
|
4169
|
+
`wal_bytes ${format_bytes(options.stats.wal_bytes)}`,
|
|
4170
|
+
`total_bytes ${format_bytes(options.stats.total_bytes)}`
|
|
4171
|
+
].join("\n");
|
|
4172
|
+
}
|
|
4173
|
+
function format_telemetry_query_results(options) {
|
|
4174
|
+
if (options.runs.length === 0) return [
|
|
4175
|
+
`db ${options.db_path}`,
|
|
4176
|
+
`filters ${format_filter_summary(options.filters)}`,
|
|
4177
|
+
"no matching runs"
|
|
4178
|
+
].join("\n");
|
|
4179
|
+
return [
|
|
4180
|
+
`db ${options.db_path}`,
|
|
4181
|
+
`filters ${format_filter_summary(options.filters)}`,
|
|
4182
|
+
...options.runs.map((run) => [
|
|
4183
|
+
`${format_timestamp(run.started_at)} ${run.id}`,
|
|
4184
|
+
`status=${format_success(run.success)}`,
|
|
4185
|
+
`duration=${format_duration(run.duration_ms)}`,
|
|
4186
|
+
`turns=${run.turn_count}`,
|
|
4187
|
+
`tools=${run.tool_call_count}`,
|
|
4188
|
+
`tool_errors=${run.tool_error_count}`,
|
|
4189
|
+
`provider_requests=${run.provider_request_count}`,
|
|
4190
|
+
run.eval_run_id ? `eval_run_id=${run.eval_run_id}` : null,
|
|
4191
|
+
run.eval_case_id ? `eval_case_id=${run.eval_case_id}` : null,
|
|
4192
|
+
run.eval_suite ? `eval_suite=${run.eval_suite}` : null
|
|
4193
|
+
].filter(Boolean).join(" "))
|
|
4194
|
+
].join("\n");
|
|
4195
|
+
}
|
|
4196
|
+
function get_default_telemetry_export_path(cwd) {
|
|
4197
|
+
return resolve(cwd, `telemetry-export-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}.json`);
|
|
4240
4198
|
}
|
|
4241
|
-
function
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
return true;
|
|
4199
|
+
async function default_load_store(db_path) {
|
|
4200
|
+
const { TelemetryDatabase } = await import("./telemetry-db-BnenoOSj.js");
|
|
4201
|
+
return TelemetryDatabase.open(db_path);
|
|
4245
4202
|
}
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
if (!
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
const
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
if (importable_items.length > 0) {
|
|
4304
|
-
all_items.push({
|
|
4305
|
-
id: "__header_importable__",
|
|
4306
|
-
label: `── Importable (${importable_items.length}) ──`,
|
|
4307
|
-
description: "",
|
|
4308
|
-
currentValue: ""
|
|
4309
|
-
});
|
|
4310
|
-
all_items.push(...importable_items);
|
|
4311
|
-
}
|
|
4312
|
-
const managed_keys = new Set(discovered.map((s) => s.key));
|
|
4313
|
-
const importable_map = new Map(importable.map((s) => [s.key, s]));
|
|
4314
|
-
await ctx.ui.custom((tui, theme, _kb, done) => {
|
|
4315
|
-
const list = new SettingsList(all_items, Math.min(Math.max(all_items.length + 4, 8), 22), {
|
|
4316
|
-
cursor: theme.fg("accent", "›"),
|
|
4317
|
-
label: (text, selected) => {
|
|
4318
|
-
if (text.startsWith("──") && text.endsWith("──")) return theme.fg("dim", theme.bold(text));
|
|
4319
|
-
return selected ? theme.fg("accent", text) : text;
|
|
4320
|
-
},
|
|
4321
|
-
value: (text, selected) => {
|
|
4322
|
-
const color = text === ENABLED ? "success" : text === SYNC ? "warning" : text === IMPORTED_LABEL ? "success" : "dim";
|
|
4323
|
-
const rendered = theme.fg(color, text);
|
|
4324
|
-
return selected ? theme.bold(theme.fg("accent", rendered)) : rendered;
|
|
4325
|
-
},
|
|
4326
|
-
description: (text) => theme.fg("muted", text),
|
|
4327
|
-
hint: (text) => theme.fg("dim", text)
|
|
4328
|
-
}, (id, new_value) => {
|
|
4329
|
-
if (id.startsWith("__header_")) return;
|
|
4330
|
-
if (managed_keys.has(id)) {
|
|
4331
|
-
if (new_value === ENABLED) {
|
|
4332
|
-
current_enabled.add(id);
|
|
4333
|
-
mgr.enable(id);
|
|
4334
|
-
} else {
|
|
4335
|
-
current_enabled.delete(id);
|
|
4336
|
-
mgr.disable(id);
|
|
4337
|
-
}
|
|
4338
|
-
return;
|
|
4339
|
-
}
|
|
4340
|
-
const import_skill = importable_map.get(id);
|
|
4341
|
-
if (!import_skill) return;
|
|
4342
|
-
const state = get_importable_state(discovered, import_skill);
|
|
4343
|
-
if (state.action === "import") {
|
|
4344
|
-
if (new_value === ENABLED) queued_imports.add(id);
|
|
4345
|
-
else queued_imports.delete(id);
|
|
4346
|
-
return;
|
|
4347
|
-
}
|
|
4348
|
-
if (state.action === "sync") {
|
|
4349
|
-
const imported_skill = find_matching_imported_skill(discovered, import_skill);
|
|
4350
|
-
if (!imported_skill) {
|
|
4351
|
-
ctx.ui.notify(`Imported copy for ${import_skill.name} was not found`, "warning");
|
|
4352
|
-
return;
|
|
4353
|
-
}
|
|
4354
|
-
try {
|
|
4355
|
-
if (mgr.sync_skill(imported_skill.key).changed) {
|
|
4356
|
-
reload_notice = `Synced ${import_skill.name}. Reloading...`;
|
|
4357
|
-
done(void 0);
|
|
4358
|
-
} else ctx.ui.notify(`${import_skill.name} is already up to date.`, "info");
|
|
4359
|
-
} catch (error) {
|
|
4360
|
-
ctx.ui.notify(error instanceof Error ? error.message : String(error), "warning");
|
|
4361
|
-
}
|
|
4362
|
-
}
|
|
4363
|
-
}, () => done(void 0), { enableSearch: true });
|
|
4364
|
-
const container = new Container();
|
|
4365
|
-
container.addChild({
|
|
4366
|
-
render: () => {
|
|
4367
|
-
const enabled = current_enabled.size;
|
|
4368
|
-
const disabled = discovered.length - enabled;
|
|
4369
|
-
const queued = queued_imports.size;
|
|
4370
|
-
const parts = [`${enabled} enabled`, `${disabled} disabled`];
|
|
4371
|
-
if (importable.length > 0) parts.push(`${importable.length} importable`);
|
|
4372
|
-
if (queued > 0) parts.push(`${queued} queued for import`);
|
|
4373
|
-
return [
|
|
4374
|
-
theme.fg("accent", theme.bold("Skills")),
|
|
4375
|
-
theme.fg("muted", parts.join(" • ")),
|
|
4376
|
-
""
|
|
4377
|
-
];
|
|
4378
|
-
},
|
|
4379
|
-
invalidate: () => {}
|
|
4380
|
-
});
|
|
4381
|
-
container.addChild({
|
|
4382
|
-
render(width) {
|
|
4383
|
-
return list.render(width);
|
|
4384
|
-
},
|
|
4385
|
-
invalidate() {
|
|
4386
|
-
list.invalidate();
|
|
4387
|
-
}
|
|
4388
|
-
});
|
|
4389
|
-
container.addChild(new Text(theme.fg("dim", "search filters • enter toggles • esc close"), 0, 1));
|
|
4390
|
-
return {
|
|
4391
|
-
render(width) {
|
|
4392
|
-
return container.render(width);
|
|
4393
|
-
},
|
|
4394
|
-
invalidate() {
|
|
4395
|
-
container.invalidate();
|
|
4396
|
-
},
|
|
4397
|
-
handleInput(data) {
|
|
4398
|
-
list.handleInput(data);
|
|
4399
|
-
tui.requestRender();
|
|
4400
|
-
}
|
|
4401
|
-
};
|
|
4402
|
-
});
|
|
4403
|
-
if (queued_imports.size > 0) {
|
|
4404
|
-
const imported_names = [];
|
|
4405
|
-
for (const key of queued_imports) try {
|
|
4406
|
-
mgr.import_skill(key);
|
|
4407
|
-
imported_names.push(key);
|
|
4408
|
-
} catch (error) {
|
|
4409
|
-
ctx.ui.notify(error instanceof Error ? error.message : String(error), "warning");
|
|
4410
|
-
}
|
|
4411
|
-
if (imported_names.length > 0) reload_notice = `Imported ${imported_names.length} skill(s). Reloading...`;
|
|
4203
|
+
function create_telemetry_extension(options = {}) {
|
|
4204
|
+
return async function telemetry(pi) {
|
|
4205
|
+
const now = options.now ?? (() => Date.now());
|
|
4206
|
+
const load_store = options.load_store ?? default_load_store;
|
|
4207
|
+
const cwd = options.cwd ?? process.cwd();
|
|
4208
|
+
const db_path = resolve_telemetry_db_path(cwd, options.db_path);
|
|
4209
|
+
let config = load_telemetry_config();
|
|
4210
|
+
let store = null;
|
|
4211
|
+
let effective_enabled = resolve_telemetry_enabled(config, options.enabled);
|
|
4212
|
+
let current_model = {
|
|
4213
|
+
provider: null,
|
|
4214
|
+
id: null
|
|
4215
|
+
};
|
|
4216
|
+
let active_run = null;
|
|
4217
|
+
const active_turns = /* @__PURE__ */ new Map();
|
|
4218
|
+
const provider_request_ids = [];
|
|
4219
|
+
async function ensure_store() {
|
|
4220
|
+
if (!effective_enabled) return null;
|
|
4221
|
+
if (!store) store = await load_store(db_path);
|
|
4222
|
+
return store;
|
|
4223
|
+
}
|
|
4224
|
+
function finish_active_run_on_disable(reason) {
|
|
4225
|
+
if (!store || !active_run) return;
|
|
4226
|
+
store.finish_run({
|
|
4227
|
+
id: active_run.id,
|
|
4228
|
+
ended_at: now(),
|
|
4229
|
+
success: null,
|
|
4230
|
+
error_message: reason
|
|
4231
|
+
});
|
|
4232
|
+
active_run = null;
|
|
4233
|
+
active_turns.clear();
|
|
4234
|
+
provider_request_ids.length = 0;
|
|
4235
|
+
}
|
|
4236
|
+
function close_store() {
|
|
4237
|
+
if (!store) return;
|
|
4238
|
+
store.close();
|
|
4239
|
+
store = null;
|
|
4240
|
+
}
|
|
4241
|
+
function command_message(ctx, message) {
|
|
4242
|
+
if (ctx.hasUI) ctx.ui.notify(message);
|
|
4243
|
+
else console.error(message);
|
|
4244
|
+
}
|
|
4245
|
+
pi.registerCommand("telemetry", {
|
|
4246
|
+
description: "Manage local SQLite telemetry for evals and debugging",
|
|
4247
|
+
getArgumentCompletions: (prefix) => {
|
|
4248
|
+
const first_token = prefix.trim().split(/\s+/, 1)[0] ?? "";
|
|
4249
|
+
return COMMANDS.filter((command) => command.startsWith(first_token)).map((command) => ({
|
|
4250
|
+
value: command,
|
|
4251
|
+
label: command
|
|
4252
|
+
}));
|
|
4253
|
+
},
|
|
4254
|
+
handler: async (args, ctx) => {
|
|
4255
|
+
const parsed = parse_telemetry_command(args);
|
|
4256
|
+
const subcommand = parsed.subcommand;
|
|
4257
|
+
if (!COMMANDS.includes(subcommand)) {
|
|
4258
|
+
command_message(ctx, `Unknown telemetry command: ${subcommand}. Use: ${COMMANDS.join(", ")}`);
|
|
4259
|
+
return;
|
|
4412
4260
|
}
|
|
4413
|
-
if (
|
|
4414
|
-
ctx.
|
|
4415
|
-
await ctx.reload();
|
|
4261
|
+
if (parsed.errors.length > 0) {
|
|
4262
|
+
command_message(ctx, parsed.errors.join("\n"));
|
|
4416
4263
|
return;
|
|
4417
4264
|
}
|
|
4418
|
-
if (
|
|
4419
|
-
ctx
|
|
4420
|
-
|
|
4265
|
+
if (subcommand === "status") {
|
|
4266
|
+
command_message(ctx, format_telemetry_status({
|
|
4267
|
+
saved_enabled: config.enabled,
|
|
4268
|
+
effective_enabled,
|
|
4269
|
+
override: options.enabled,
|
|
4270
|
+
db_path
|
|
4271
|
+
}));
|
|
4421
4272
|
return;
|
|
4422
4273
|
}
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4426
|
-
const arg = rest.join(" ");
|
|
4427
|
-
switch (sub) {
|
|
4428
|
-
case "import":
|
|
4429
|
-
if (!arg) {
|
|
4430
|
-
ctx.ui.notify("Usage: /skills import <key|name>", "warning");
|
|
4274
|
+
if (subcommand === "stats") {
|
|
4275
|
+
if (!existsSync(db_path)) {
|
|
4276
|
+
command_message(ctx, `No telemetry database at ${db_path}`);
|
|
4431
4277
|
return;
|
|
4432
4278
|
}
|
|
4279
|
+
const stats_store = store ?? await load_store(db_path);
|
|
4280
|
+
const should_close_after = stats_store !== store;
|
|
4433
4281
|
try {
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4438
|
-
}
|
|
4439
|
-
|
|
4440
|
-
return;
|
|
4282
|
+
command_message(ctx, format_telemetry_stats({
|
|
4283
|
+
db_path,
|
|
4284
|
+
stats: stats_store.get_stats()
|
|
4285
|
+
}));
|
|
4286
|
+
} finally {
|
|
4287
|
+
if (should_close_after) stats_store.close();
|
|
4441
4288
|
}
|
|
4442
|
-
|
|
4443
|
-
|
|
4444
|
-
|
|
4289
|
+
return;
|
|
4290
|
+
}
|
|
4291
|
+
if (subcommand === "query" || subcommand === "export") {
|
|
4292
|
+
if (!existsSync(db_path)) {
|
|
4293
|
+
command_message(ctx, `No telemetry database at ${db_path}`);
|
|
4445
4294
|
return;
|
|
4446
4295
|
}
|
|
4296
|
+
const query_store = store ?? await load_store(db_path);
|
|
4297
|
+
const should_close_after = query_store !== store;
|
|
4447
4298
|
try {
|
|
4448
|
-
const
|
|
4449
|
-
|
|
4450
|
-
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4299
|
+
const runs = query_store.query_runs(parsed.filters);
|
|
4300
|
+
if (subcommand === "query") {
|
|
4301
|
+
command_message(ctx, format_telemetry_query_results({
|
|
4302
|
+
db_path,
|
|
4303
|
+
filters: parsed.filters,
|
|
4304
|
+
runs
|
|
4305
|
+
}));
|
|
4306
|
+
return;
|
|
4307
|
+
}
|
|
4308
|
+
const export_path = resolve(cwd, parsed.export_path ?? get_default_telemetry_export_path(cwd));
|
|
4309
|
+
mkdirSync(dirname(export_path), { recursive: true });
|
|
4310
|
+
writeFileSync(export_path, JSON.stringify({
|
|
4311
|
+
exported_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4312
|
+
db_path,
|
|
4313
|
+
schema_version: query_store.get_stats().schema_version,
|
|
4314
|
+
filters: parsed.filters,
|
|
4315
|
+
runs
|
|
4316
|
+
}, null, 2), "utf-8");
|
|
4317
|
+
command_message(ctx, `Exported ${runs.length} telemetry run${runs.length === 1 ? "" : "s"} to ${export_path}`);
|
|
4463
4318
|
return;
|
|
4319
|
+
} finally {
|
|
4320
|
+
if (should_close_after) query_store.close();
|
|
4464
4321
|
}
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4322
|
+
}
|
|
4323
|
+
if (subcommand === "path") {
|
|
4324
|
+
command_message(ctx, db_path);
|
|
4325
|
+
return;
|
|
4326
|
+
}
|
|
4327
|
+
const next_enabled = subcommand === "on";
|
|
4328
|
+
config = {
|
|
4329
|
+
...config,
|
|
4330
|
+
enabled: next_enabled
|
|
4331
|
+
};
|
|
4332
|
+
save_telemetry_config(config);
|
|
4333
|
+
if (options.enabled !== void 0) {
|
|
4334
|
+
command_message(ctx, [`Saved default telemetry ${next_enabled ? "enabled" : "disabled"}.`, `Current process still uses ${options.enabled ? "--telemetry" : "--no-telemetry"}.`].join(" "));
|
|
4335
|
+
return;
|
|
4336
|
+
}
|
|
4337
|
+
effective_enabled = next_enabled;
|
|
4338
|
+
if (effective_enabled) {
|
|
4339
|
+
await ensure_store();
|
|
4340
|
+
command_message(ctx, `Telemetry enabled. Writing to ${db_path}`);
|
|
4341
|
+
return;
|
|
4342
|
+
}
|
|
4343
|
+
finish_active_run_on_disable("telemetry disabled");
|
|
4344
|
+
close_store();
|
|
4345
|
+
command_message(ctx, "Telemetry disabled.");
|
|
4469
4346
|
}
|
|
4470
|
-
}
|
|
4471
|
-
|
|
4347
|
+
});
|
|
4348
|
+
pi.on("model_select", async (event) => {
|
|
4349
|
+
current_model = get_model_identity(event.model);
|
|
4350
|
+
});
|
|
4351
|
+
pi.on("agent_start", async (_event, ctx) => {
|
|
4352
|
+
const active_store = await ensure_store();
|
|
4353
|
+
if (!active_store) return;
|
|
4354
|
+
const run_id = randomUUID();
|
|
4355
|
+
const eval_metadata = get_eval_metadata();
|
|
4356
|
+
const model_identity = ctx.model ? get_model_identity(ctx.model) : current_model;
|
|
4357
|
+
active_store.insert_run({
|
|
4358
|
+
id: run_id,
|
|
4359
|
+
session_file: get_session_file(ctx),
|
|
4360
|
+
cwd: ctx.cwd,
|
|
4361
|
+
started_at: now(),
|
|
4362
|
+
model_provider: model_identity.provider,
|
|
4363
|
+
model_id: model_identity.id,
|
|
4364
|
+
eval_run_id: eval_metadata.run_id,
|
|
4365
|
+
eval_case_id: eval_metadata.case_id,
|
|
4366
|
+
eval_attempt: eval_metadata.attempt,
|
|
4367
|
+
eval_suite: eval_metadata.suite
|
|
4368
|
+
});
|
|
4369
|
+
active_run = { id: run_id };
|
|
4370
|
+
active_turns.clear();
|
|
4371
|
+
provider_request_ids.length = 0;
|
|
4372
|
+
});
|
|
4373
|
+
pi.on("agent_end", async (event) => {
|
|
4374
|
+
if (!store || !active_run) return;
|
|
4375
|
+
const outcome = infer_run_outcome(event);
|
|
4376
|
+
store.finish_run({
|
|
4377
|
+
id: active_run.id,
|
|
4378
|
+
ended_at: now(),
|
|
4379
|
+
success: outcome.success,
|
|
4380
|
+
error_message: outcome.error_message
|
|
4381
|
+
});
|
|
4382
|
+
active_run = null;
|
|
4383
|
+
active_turns.clear();
|
|
4384
|
+
provider_request_ids.length = 0;
|
|
4385
|
+
});
|
|
4386
|
+
pi.on("turn_start", async (event) => {
|
|
4387
|
+
if (!store || !active_run) return;
|
|
4388
|
+
const turn_id = `${active_run.id}:turn:${event.turnIndex}`;
|
|
4389
|
+
active_turns.set(event.turnIndex, { id: turn_id });
|
|
4390
|
+
store.insert_turn({
|
|
4391
|
+
id: turn_id,
|
|
4392
|
+
run_id: active_run.id,
|
|
4393
|
+
turn_index: event.turnIndex,
|
|
4394
|
+
started_at: event.timestamp
|
|
4395
|
+
});
|
|
4396
|
+
});
|
|
4397
|
+
pi.on("turn_end", async (event) => {
|
|
4398
|
+
const active_turn = active_turns.get(event.turnIndex);
|
|
4399
|
+
if (!store || !active_turn) return;
|
|
4400
|
+
store.finish_turn({
|
|
4401
|
+
id: active_turn.id,
|
|
4402
|
+
ended_at: now(),
|
|
4403
|
+
tool_result_count: event.toolResults.length,
|
|
4404
|
+
stop_reason: get_stop_reason(event.message)
|
|
4405
|
+
});
|
|
4406
|
+
active_turns.delete(event.turnIndex);
|
|
4407
|
+
});
|
|
4408
|
+
pi.on("tool_execution_start", async (event) => {
|
|
4409
|
+
if (!store || !active_run) return;
|
|
4410
|
+
const current_turn = [...active_turns.values()].at(-1);
|
|
4411
|
+
store.insert_tool_call({
|
|
4412
|
+
tool_call_id: event.toolCallId,
|
|
4413
|
+
run_id: active_run.id,
|
|
4414
|
+
turn_id: current_turn?.id ?? null,
|
|
4415
|
+
tool_name: event.toolName,
|
|
4416
|
+
started_at: now(),
|
|
4417
|
+
args_summary_json: summarize_tool_args(event.toolName, event.args)
|
|
4418
|
+
});
|
|
4419
|
+
});
|
|
4420
|
+
pi.on("tool_execution_update", async (event) => {
|
|
4421
|
+
if (!store || !active_run) return;
|
|
4422
|
+
store.note_tool_update(event.toolCallId);
|
|
4423
|
+
});
|
|
4424
|
+
pi.on("tool_execution_end", async (event) => {
|
|
4425
|
+
if (!store || !active_run) return;
|
|
4426
|
+
store.finish_tool_call({
|
|
4427
|
+
tool_call_id: event.toolCallId,
|
|
4428
|
+
ended_at: now(),
|
|
4429
|
+
is_error: event.isError,
|
|
4430
|
+
result_summary_json: summarize_tool_result(event.result),
|
|
4431
|
+
error_message: event.isError && event.result != null ? safe_json_stringify(summarize_value(event.result)) : null
|
|
4432
|
+
});
|
|
4433
|
+
});
|
|
4434
|
+
pi.on("before_provider_request", async (event) => {
|
|
4435
|
+
if (!store || !active_run) return;
|
|
4436
|
+
const request_id = randomUUID();
|
|
4437
|
+
const current_turn = [...active_turns.values()].at(-1);
|
|
4438
|
+
store.insert_provider_request({
|
|
4439
|
+
id: request_id,
|
|
4440
|
+
run_id: active_run.id,
|
|
4441
|
+
turn_id: current_turn?.id ?? null,
|
|
4442
|
+
started_at: now(),
|
|
4443
|
+
payload_summary_json: summarize_provider_payload(event.payload)
|
|
4444
|
+
});
|
|
4445
|
+
provider_request_ids.push(request_id);
|
|
4446
|
+
});
|
|
4447
|
+
pi.on("after_provider_response", async (event) => {
|
|
4448
|
+
if (!store || !active_run) return;
|
|
4449
|
+
const request_id = provider_request_ids.shift();
|
|
4450
|
+
if (!request_id) return;
|
|
4451
|
+
store.finish_provider_request({
|
|
4452
|
+
id: request_id,
|
|
4453
|
+
ended_at: now(),
|
|
4454
|
+
status_code: event.status,
|
|
4455
|
+
headers_json: summarize_headers(event.headers)
|
|
4456
|
+
});
|
|
4457
|
+
});
|
|
4458
|
+
pi.on("session_shutdown", async () => {
|
|
4459
|
+
if (store && active_run) store.finish_run({
|
|
4460
|
+
id: active_run.id,
|
|
4461
|
+
ended_at: now(),
|
|
4462
|
+
success: null,
|
|
4463
|
+
error_message: "session shutdown"
|
|
4464
|
+
});
|
|
4465
|
+
close_store();
|
|
4466
|
+
active_run = null;
|
|
4467
|
+
active_turns.clear();
|
|
4468
|
+
provider_request_ids.length = 0;
|
|
4469
|
+
});
|
|
4470
|
+
};
|
|
4472
4471
|
}
|
|
4472
|
+
create_telemetry_extension();
|
|
4473
4473
|
//#endregion
|
|
4474
4474
|
//#region src/api.ts
|
|
4475
4475
|
const BUILTIN_EXTENSION_FACTORIES = {
|
|
@@ -4588,4 +4588,4 @@ async function create_my_pi(options = {}) {
|
|
|
4588
4588
|
//#endregion
|
|
4589
4589
|
export { create_my_pi as n, runPrintMode$1 as r, InteractiveMode$1 as t };
|
|
4590
4590
|
|
|
4591
|
-
//# sourceMappingURL=api-
|
|
4591
|
+
//# sourceMappingURL=api-CWEizv2k.js.map
|