@oh-my-pi/pi-coding-agent 3.1.1337 → 3.4.1337
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/CHANGELOG.md +19 -0
- package/docs/extension-loading.md +2 -2
- package/docs/sdk.md +2 -2
- package/package.json +4 -4
- package/src/capability/rule.ts +4 -0
- package/src/core/agent-session.ts +92 -1
- package/src/core/sdk.ts +14 -0
- package/src/core/session-manager.ts +60 -4
- package/src/core/settings-manager.ts +68 -0
- package/src/core/ttsr.ts +211 -0
- package/src/discovery/builtin.ts +1 -0
- package/src/discovery/cline.ts +2 -0
- package/src/discovery/cursor.ts +2 -0
- package/src/discovery/windsurf.ts +3 -0
- package/src/modes/interactive/components/footer.ts +15 -1
- package/src/modes/interactive/components/settings-defs.ts +29 -0
- package/src/modes/interactive/components/ttsr-notification.ts +82 -0
- package/src/modes/interactive/interactive-mode.ts +161 -130
- package/src/modes/print-mode.ts +2 -2
|
@@ -67,6 +67,7 @@ import { SessionSelectorComponent } from "./components/session-selector";
|
|
|
67
67
|
import { SettingsSelectorComponent } from "./components/settings-selector";
|
|
68
68
|
import { ToolExecutionComponent } from "./components/tool-execution";
|
|
69
69
|
import { TreeSelectorComponent } from "./components/tree-selector";
|
|
70
|
+
import { TtsrNotificationComponent } from "./components/ttsr-notification";
|
|
70
71
|
import { UserMessageComponent } from "./components/user-message";
|
|
71
72
|
import { UserMessageSelectorComponent } from "./components/user-message-selector";
|
|
72
73
|
import { WelcomeComponent } from "./components/welcome";
|
|
@@ -1007,18 +1008,28 @@ export class InteractiveMode {
|
|
|
1007
1008
|
if (event.message.role === "user") break;
|
|
1008
1009
|
if (this.streamingComponent && event.message.role === "assistant") {
|
|
1009
1010
|
this.streamingMessage = event.message;
|
|
1010
|
-
|
|
1011
|
+
// Don't show "Aborted" text for TTSR aborts - we'll show a nicer message
|
|
1012
|
+
if (this.session.isTtsrAbortPending && this.streamingMessage.stopReason === "aborted") {
|
|
1013
|
+
// TTSR abort - suppress the "Aborted" rendering in the component
|
|
1014
|
+
const msgWithoutAbort = { ...this.streamingMessage, stopReason: "stop" as const };
|
|
1015
|
+
this.streamingComponent.updateContent(msgWithoutAbort);
|
|
1016
|
+
} else {
|
|
1017
|
+
this.streamingComponent.updateContent(this.streamingMessage);
|
|
1018
|
+
}
|
|
1011
1019
|
|
|
1012
1020
|
if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1021
|
+
// Skip error handling for TTSR aborts
|
|
1022
|
+
if (!this.session.isTtsrAbortPending) {
|
|
1023
|
+
const errorMessage =
|
|
1024
|
+
this.streamingMessage.stopReason === "aborted"
|
|
1025
|
+
? "Operation aborted"
|
|
1026
|
+
: this.streamingMessage.errorMessage || "Error";
|
|
1027
|
+
for (const [, component] of this.pendingTools.entries()) {
|
|
1028
|
+
component.updateResult({
|
|
1029
|
+
content: [{ type: "text", text: errorMessage }],
|
|
1030
|
+
isError: true,
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1022
1033
|
}
|
|
1023
1034
|
this.pendingTools.clear();
|
|
1024
1035
|
} else {
|
|
@@ -1188,6 +1199,15 @@ export class InteractiveMode {
|
|
|
1188
1199
|
this.ui.requestRender();
|
|
1189
1200
|
break;
|
|
1190
1201
|
}
|
|
1202
|
+
|
|
1203
|
+
case "ttsr_triggered": {
|
|
1204
|
+
// Show a fancy notification when TTSR rules are triggered
|
|
1205
|
+
const component = new TtsrNotificationComponent(event.rules);
|
|
1206
|
+
component.setExpanded(this.toolOutputExpanded);
|
|
1207
|
+
this.chatContainer.addChild(component);
|
|
1208
|
+
this.ui.requestRender();
|
|
1209
|
+
break;
|
|
1210
|
+
}
|
|
1191
1211
|
}
|
|
1192
1212
|
}
|
|
1193
1213
|
|
|
@@ -1617,7 +1637,7 @@ export class InteractiveMode {
|
|
|
1617
1637
|
theme.bold(theme.fg("warning", "Update Available")) +
|
|
1618
1638
|
"\n" +
|
|
1619
1639
|
theme.fg("muted", `New version ${newVersion} is available. Run: `) +
|
|
1620
|
-
theme.fg("accent", "
|
|
1640
|
+
theme.fg("accent", "omp update"),
|
|
1621
1641
|
1,
|
|
1622
1642
|
0,
|
|
1623
1643
|
),
|
|
@@ -2391,14 +2411,26 @@ export class InteractiveMode {
|
|
|
2391
2411
|
}
|
|
2392
2412
|
|
|
2393
2413
|
private handleStatusCommand(): void {
|
|
2394
|
-
const sections: string[] = [];
|
|
2395
|
-
|
|
2396
2414
|
type StatusSource =
|
|
2397
2415
|
| { provider: string; level: string }
|
|
2398
2416
|
| { mcpServer: string; provider?: string }
|
|
2399
2417
|
| "builtin"
|
|
2400
2418
|
| "unknown";
|
|
2401
2419
|
|
|
2420
|
+
type StatusLine = {
|
|
2421
|
+
name: string;
|
|
2422
|
+
sourceText: string;
|
|
2423
|
+
nameWithSource: string;
|
|
2424
|
+
desc?: string;
|
|
2425
|
+
};
|
|
2426
|
+
|
|
2427
|
+
type LineSection = {
|
|
2428
|
+
title: string;
|
|
2429
|
+
lines: StatusLine[];
|
|
2430
|
+
};
|
|
2431
|
+
|
|
2432
|
+
type Section = { kind: "lines"; section: LineSection } | { kind: "text"; text: string };
|
|
2433
|
+
|
|
2402
2434
|
const capitalize = (value: string): string => value.charAt(0).toUpperCase() + value.slice(1);
|
|
2403
2435
|
|
|
2404
2436
|
const resolveSourceText = (source: StatusSource): string => {
|
|
@@ -2440,29 +2472,28 @@ export class InteractiveMode {
|
|
|
2440
2472
|
return `${acc}...`;
|
|
2441
2473
|
};
|
|
2442
2474
|
|
|
2443
|
-
|
|
2444
|
-
const formatSection = <T>(
|
|
2475
|
+
const buildLineSection = <T>(
|
|
2445
2476
|
title: string,
|
|
2446
2477
|
items: readonly T[],
|
|
2447
2478
|
getName: (item: T) => string,
|
|
2448
2479
|
getDesc: (item: T) => string | undefined,
|
|
2449
2480
|
getSource: (item: T) => StatusSource,
|
|
2450
|
-
):
|
|
2451
|
-
if (items.length === 0) return
|
|
2481
|
+
): LineSection | null => {
|
|
2482
|
+
if (items.length === 0) return null;
|
|
2452
2483
|
|
|
2453
|
-
const
|
|
2484
|
+
const lines = items.map((item) => {
|
|
2454
2485
|
const name = getName(item);
|
|
2455
|
-
const desc = getDesc(item);
|
|
2486
|
+
const desc = getDesc(item)?.trim();
|
|
2456
2487
|
const sourceText = resolveSourceText(getSource(item));
|
|
2457
2488
|
const nameWithSource = sourceText ? `${name} ${sourceText}` : name;
|
|
2458
2489
|
return { name, sourceText, nameWithSource, desc };
|
|
2459
2490
|
});
|
|
2460
2491
|
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
const formattedLines =
|
|
2492
|
+
return { title, lines };
|
|
2493
|
+
};
|
|
2494
|
+
|
|
2495
|
+
const renderLineSection = (section: LineSection, maxNameWidth: number): string => {
|
|
2496
|
+
const formattedLines = section.lines.map((line) => {
|
|
2466
2497
|
let nameText = line.name;
|
|
2467
2498
|
let sourceText = line.sourceText;
|
|
2468
2499
|
|
|
@@ -2471,103 +2502,97 @@ export class InteractiveMode {
|
|
|
2471
2502
|
sourceText = truncateText(sourceText, maxSourceWidth);
|
|
2472
2503
|
}
|
|
2473
2504
|
const sourceWidth = sourceText ? visibleWidth(sourceText) : 0;
|
|
2474
|
-
const availableForName = sourceText
|
|
2475
|
-
? Math.max(1, maxNameWidth - sourceWidth - 1)
|
|
2476
|
-
: maxNameWidth;
|
|
2505
|
+
const availableForName = sourceText ? Math.max(1, maxNameWidth - sourceWidth - 1) : maxNameWidth;
|
|
2477
2506
|
nameText = truncateText(nameText, availableForName);
|
|
2478
2507
|
|
|
2479
2508
|
const nameWithSourcePlain = sourceText ? `${nameText} ${sourceText}` : nameText;
|
|
2480
2509
|
const sourceRendered = sourceText ? renderSourceText(sourceText) : "";
|
|
2481
2510
|
const nameRendered = sourceText ? `${theme.bold(nameText)} ${sourceRendered}` : theme.bold(nameText);
|
|
2482
2511
|
const pad = Math.max(0, maxNameWidth - visibleWidth(nameWithSourcePlain));
|
|
2483
|
-
const desc = line.desc
|
|
2512
|
+
const desc = line.desc;
|
|
2484
2513
|
const descPart = desc ? ` ${theme.fg("dim", desc.slice(0, 50) + (desc.length > 50 ? "..." : ""))}` : "";
|
|
2485
2514
|
return ` ${nameRendered}${" ".repeat(pad)}${descPart}`;
|
|
2486
2515
|
});
|
|
2487
2516
|
|
|
2488
|
-
return `${theme.bold(theme.fg("accent", title))}\n${formattedLines.join("\n")}`;
|
|
2517
|
+
return `${theme.bold(theme.fg("accent", section.title))}\n${formattedLines.join("\n")}`;
|
|
2518
|
+
};
|
|
2519
|
+
|
|
2520
|
+
const sections: Section[] = [];
|
|
2521
|
+
const pushLineSection = <T>(
|
|
2522
|
+
title: string,
|
|
2523
|
+
items: readonly T[],
|
|
2524
|
+
getName: (item: T) => string,
|
|
2525
|
+
getDesc: (item: T) => string | undefined,
|
|
2526
|
+
getSource: (item: T) => StatusSource,
|
|
2527
|
+
): void => {
|
|
2528
|
+
const section = buildLineSection(title, items, getName, getDesc, getSource);
|
|
2529
|
+
if (section) {
|
|
2530
|
+
sections.push({ kind: "lines", section });
|
|
2531
|
+
}
|
|
2489
2532
|
};
|
|
2490
2533
|
|
|
2491
2534
|
// Loaded context files
|
|
2492
2535
|
const contextFilesResult = loadSync(contextFileCapability.id, { cwd: process.cwd() });
|
|
2493
2536
|
const contextFiles = contextFilesResult.items as ContextFile[];
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
(f) => ({ provider: f._source.providerName, level: f.level }),
|
|
2502
|
-
),
|
|
2503
|
-
);
|
|
2504
|
-
}
|
|
2537
|
+
pushLineSection(
|
|
2538
|
+
"Context Files",
|
|
2539
|
+
contextFiles,
|
|
2540
|
+
(f) => basename(f.path),
|
|
2541
|
+
() => undefined,
|
|
2542
|
+
(f) => ({ provider: f._source.providerName, level: f.level }),
|
|
2543
|
+
);
|
|
2505
2544
|
|
|
2506
2545
|
// Loaded skills
|
|
2507
2546
|
const skillsSettings = this.session.skillsSettings;
|
|
2508
2547
|
if (skillsSettings?.enabled !== false) {
|
|
2509
2548
|
const { skills, warnings: skillWarnings } = loadSkills(skillsSettings ?? {});
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
(s) => (s._source ? { provider: s._source.providerName, level: s._source.level } : "unknown"),
|
|
2518
|
-
),
|
|
2519
|
-
);
|
|
2520
|
-
}
|
|
2549
|
+
pushLineSection(
|
|
2550
|
+
"Skills",
|
|
2551
|
+
skills,
|
|
2552
|
+
(s) => s.name,
|
|
2553
|
+
(s) => s.description,
|
|
2554
|
+
(s) => (s._source ? { provider: s._source.providerName, level: s._source.level } : "unknown"),
|
|
2555
|
+
);
|
|
2521
2556
|
if (skillWarnings.length > 0) {
|
|
2522
|
-
sections.push(
|
|
2523
|
-
|
|
2557
|
+
sections.push({
|
|
2558
|
+
kind: "text",
|
|
2559
|
+
text:
|
|
2560
|
+
theme.bold(theme.fg("warning", "Skill Warnings")) +
|
|
2524
2561
|
"\n" +
|
|
2525
2562
|
skillWarnings.map((w) => theme.fg("warning", ` ${w.skillPath}: ${w.message}`)).join("\n"),
|
|
2526
|
-
);
|
|
2563
|
+
});
|
|
2527
2564
|
}
|
|
2528
2565
|
}
|
|
2529
2566
|
|
|
2530
2567
|
// Loaded rules
|
|
2531
2568
|
const rulesResult = loadSync<Rule>(ruleCapability.id, { cwd: process.cwd() });
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
(r) => ({ provider: r._source.providerName, level: r._source.level }),
|
|
2540
|
-
),
|
|
2541
|
-
);
|
|
2542
|
-
}
|
|
2569
|
+
pushLineSection(
|
|
2570
|
+
"Rules",
|
|
2571
|
+
rulesResult.items,
|
|
2572
|
+
(r) => r.name,
|
|
2573
|
+
(r) => r.description,
|
|
2574
|
+
(r) => ({ provider: r._source.providerName, level: r._source.level }),
|
|
2575
|
+
);
|
|
2543
2576
|
|
|
2544
2577
|
// Loaded prompts
|
|
2545
2578
|
const promptsResult = loadSync<Prompt>(promptCapability.id, { cwd: process.cwd() });
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
(p) => ({ provider: p._source.providerName, level: p._source.level }),
|
|
2554
|
-
),
|
|
2555
|
-
);
|
|
2556
|
-
}
|
|
2579
|
+
pushLineSection(
|
|
2580
|
+
"Prompts",
|
|
2581
|
+
promptsResult.items,
|
|
2582
|
+
(p) => p.name,
|
|
2583
|
+
() => undefined,
|
|
2584
|
+
(p) => ({ provider: p._source.providerName, level: p._source.level }),
|
|
2585
|
+
);
|
|
2557
2586
|
|
|
2558
2587
|
// Loaded instructions
|
|
2559
2588
|
const instructionsResult = loadSync<Instruction>(instructionCapability.id, { cwd: process.cwd() });
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
(i) => ({ provider: i._source.providerName, level: i._source.level }),
|
|
2568
|
-
),
|
|
2569
|
-
);
|
|
2570
|
-
}
|
|
2589
|
+
pushLineSection(
|
|
2590
|
+
"Instructions",
|
|
2591
|
+
instructionsResult.items,
|
|
2592
|
+
(i) => i.name,
|
|
2593
|
+
(i) => (i.applyTo ? `applies to: ${i.applyTo}` : undefined),
|
|
2594
|
+
(i) => ({ provider: i._source.providerName, level: i._source.level }),
|
|
2595
|
+
);
|
|
2571
2596
|
|
|
2572
2597
|
// Loaded custom tools - split MCP from non-MCP
|
|
2573
2598
|
if (this.customTools.size > 0) {
|
|
@@ -2577,68 +2602,74 @@ export class InteractiveMode {
|
|
|
2577
2602
|
|
|
2578
2603
|
// MCP Tools section
|
|
2579
2604
|
if (mcpTools.length > 0) {
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
(
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
},
|
|
2594
|
-
),
|
|
2605
|
+
pushLineSection(
|
|
2606
|
+
"MCP Tools",
|
|
2607
|
+
mcpTools,
|
|
2608
|
+
(ct) => ct.tool.label || ct.tool.name,
|
|
2609
|
+
() => undefined,
|
|
2610
|
+
(ct) => {
|
|
2611
|
+
const match = ct.path.match(/^mcp:(.+?) via (.+)$/);
|
|
2612
|
+
if (match) {
|
|
2613
|
+
const [, serverName, providerName] = match;
|
|
2614
|
+
return { mcpServer: serverName, provider: providerName };
|
|
2615
|
+
}
|
|
2616
|
+
return ct.path.startsWith("mcp:") ? { mcpServer: ct.path.slice(4) } : "unknown";
|
|
2617
|
+
},
|
|
2595
2618
|
);
|
|
2596
2619
|
}
|
|
2597
2620
|
|
|
2598
2621
|
// Custom Tools section
|
|
2599
2622
|
if (customTools.length > 0) {
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
(ct)
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
},
|
|
2611
|
-
),
|
|
2623
|
+
pushLineSection(
|
|
2624
|
+
"Custom Tools",
|
|
2625
|
+
customTools,
|
|
2626
|
+
(ct) => ct.tool.label || ct.tool.name,
|
|
2627
|
+
(ct) => ct.tool.description,
|
|
2628
|
+
(ct) => {
|
|
2629
|
+
if (ct.source?.provider === "builtin") return "builtin";
|
|
2630
|
+
if (ct.path === "<exa>") return "builtin";
|
|
2631
|
+
return ct.source ? { provider: ct.source.providerName, level: ct.source.level } : "unknown";
|
|
2632
|
+
},
|
|
2612
2633
|
);
|
|
2613
2634
|
}
|
|
2614
2635
|
}
|
|
2615
2636
|
|
|
2616
2637
|
// Loaded slash commands (file-based)
|
|
2617
2638
|
const fileCommands = this.session.fileCommands;
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
(cmd) => (cmd._source ? { provider: cmd._source.providerName, level: cmd._source.level } : "unknown"),
|
|
2626
|
-
),
|
|
2627
|
-
);
|
|
2628
|
-
}
|
|
2639
|
+
pushLineSection(
|
|
2640
|
+
"Slash Commands",
|
|
2641
|
+
fileCommands,
|
|
2642
|
+
(cmd) => `/${cmd.name}`,
|
|
2643
|
+
(cmd) => cmd.description,
|
|
2644
|
+
(cmd) => (cmd._source ? { provider: cmd._source.providerName, level: cmd._source.level } : "unknown"),
|
|
2645
|
+
);
|
|
2629
2646
|
|
|
2630
2647
|
// Loaded hooks
|
|
2631
2648
|
const hookRunner = this.session.hookRunner;
|
|
2632
2649
|
if (hookRunner) {
|
|
2633
2650
|
const hookPaths = hookRunner.getHookPaths();
|
|
2634
2651
|
if (hookPaths.length > 0) {
|
|
2635
|
-
sections.push(
|
|
2636
|
-
|
|
2637
|
-
|
|
2652
|
+
sections.push({
|
|
2653
|
+
kind: "text",
|
|
2654
|
+
text:
|
|
2655
|
+
`${theme.bold(theme.fg("accent", "Hooks"))}\n` +
|
|
2656
|
+
hookPaths.map((p) => ` ${theme.bold(basename(p))} ${theme.fg("dim", "hook")}`).join("\n"),
|
|
2657
|
+
});
|
|
2638
2658
|
}
|
|
2639
2659
|
}
|
|
2640
2660
|
|
|
2641
|
-
|
|
2661
|
+
const lineSections = sections.filter((section): section is { kind: "lines"; section: LineSection } => {
|
|
2662
|
+
return section.kind === "lines";
|
|
2663
|
+
});
|
|
2664
|
+
const allLines = lineSections.flatMap((section) => section.section.lines);
|
|
2665
|
+
const maxNameWidth = allLines.length
|
|
2666
|
+
? Math.min(60, Math.max(...allLines.map((line) => visibleWidth(line.nameWithSource))))
|
|
2667
|
+
: 0;
|
|
2668
|
+
const renderedSections = sections
|
|
2669
|
+
.map((section) => (section.kind === "lines" ? renderLineSection(section.section, maxNameWidth) : section.text))
|
|
2670
|
+
.filter((section) => section.length > 0);
|
|
2671
|
+
|
|
2672
|
+
if (renderedSections.length === 0) {
|
|
2642
2673
|
this.chatContainer.addChild(new Spacer(1));
|
|
2643
2674
|
this.chatContainer.addChild(new Text(theme.fg("muted", "No extensions loaded."), 1, 0));
|
|
2644
2675
|
} else {
|
|
@@ -2646,7 +2677,7 @@ export class InteractiveMode {
|
|
|
2646
2677
|
this.chatContainer.addChild(new DynamicBorder());
|
|
2647
2678
|
this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "Loaded Extensions")), 1, 0));
|
|
2648
2679
|
this.chatContainer.addChild(new Spacer(1));
|
|
2649
|
-
for (const section of
|
|
2680
|
+
for (const section of renderedSections) {
|
|
2650
2681
|
this.chatContainer.addChild(new Text(section, 1, 0));
|
|
2651
2682
|
this.chatContainer.addChild(new Spacer(1));
|
|
2652
2683
|
}
|
package/src/modes/print-mode.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Print mode (single-shot): Send prompts, output result, exit.
|
|
3
3
|
*
|
|
4
4
|
* Used for:
|
|
5
|
-
* - `
|
|
6
|
-
* - `
|
|
5
|
+
* - `omp -p "prompt"` - text output
|
|
6
|
+
* - `omp --mode json "prompt"` - JSON event stream
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { AssistantMessage, ImageContent } from "@oh-my-pi/pi-ai";
|