@oh-my-pi/pi-coding-agent 3.3.1337 → 3.5.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 +32 -0
- 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 +27 -0
- package/src/core/session-manager.ts +60 -4
- package/src/core/settings-manager.ts +101 -0
- package/src/core/system-prompt.ts +15 -0
- package/src/core/title-generator.ts +28 -6
- package/src/core/tools/index.ts +6 -0
- package/src/core/tools/rulebook.ts +124 -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/extensions/extension-dashboard.ts +297 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +477 -0
- package/src/modes/interactive/components/extensions/index.ts +9 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
- package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
- package/src/modes/interactive/components/extensions/types.ts +191 -0
- package/src/modes/interactive/components/footer.ts +15 -1
- package/src/modes/interactive/components/settings-defs.ts +31 -31
- package/src/modes/interactive/components/settings-selector.ts +0 -1
- package/src/modes/interactive/components/ttsr-notification.ts +82 -0
- package/src/modes/interactive/interactive-mode.ts +54 -314
- package/src/modes/print-mode.ts +34 -0
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
import { basename } from "node:path";
|
|
10
9
|
import type { AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
11
10
|
import type { AssistantMessage, ImageContent, Message, OAuthProvider } from "@oh-my-pi/pi-ai";
|
|
12
11
|
import type { SlashCommand } from "@oh-my-pi/pi-tui";
|
|
@@ -24,28 +23,15 @@ import {
|
|
|
24
23
|
TUI,
|
|
25
24
|
visibleWidth,
|
|
26
25
|
} from "@oh-my-pi/pi-tui";
|
|
27
|
-
import { contextFileCapability } from "../../capability/context-file";
|
|
28
|
-
import { instructionCapability } from "../../capability/instruction";
|
|
29
|
-
import { promptCapability } from "../../capability/prompt";
|
|
30
|
-
import { ruleCapability } from "../../capability/rule";
|
|
31
26
|
import { getAuthPath, getDebugLogPath } from "../../config";
|
|
32
27
|
import type { AgentSession, AgentSessionEvent } from "../../core/agent-session";
|
|
33
28
|
import type { CustomToolSessionEvent, LoadedCustomTool } from "../../core/custom-tools/index";
|
|
34
29
|
import type { HookUIContext } from "../../core/hooks/index";
|
|
35
30
|
import { createCompactionSummaryMessage } from "../../core/messages";
|
|
36
31
|
import { getRecentSessions, type SessionContext, SessionManager } from "../../core/session-manager";
|
|
37
|
-
import { loadSkills } from "../../core/skills";
|
|
38
32
|
import { generateSessionTitle, setTerminalTitle } from "../../core/title-generator";
|
|
39
33
|
import type { TruncationResult } from "../../core/tools/truncate";
|
|
40
|
-
import {
|
|
41
|
-
type ContextFile,
|
|
42
|
-
disableProvider,
|
|
43
|
-
enableProvider,
|
|
44
|
-
type Instruction,
|
|
45
|
-
loadSync,
|
|
46
|
-
type Prompt,
|
|
47
|
-
type Rule,
|
|
48
|
-
} from "../../discovery";
|
|
34
|
+
import { disableProvider, enableProvider } from "../../discovery";
|
|
49
35
|
import { getChangelogPath, parseChangelog } from "../../utils/changelog";
|
|
50
36
|
import { copyToClipboard, readImageFromClipboard } from "../../utils/clipboard";
|
|
51
37
|
import { ArminComponent } from "./components/armin";
|
|
@@ -56,6 +42,7 @@ import { BranchSummaryMessageComponent } from "./components/branch-summary-messa
|
|
|
56
42
|
import { CompactionSummaryMessageComponent } from "./components/compaction-summary-message";
|
|
57
43
|
import { CustomEditor } from "./components/custom-editor";
|
|
58
44
|
import { DynamicBorder } from "./components/dynamic-border";
|
|
45
|
+
import { ExtensionDashboard } from "./components/extensions";
|
|
59
46
|
import { FooterComponent } from "./components/footer";
|
|
60
47
|
import { HookEditorComponent } from "./components/hook-editor";
|
|
61
48
|
import { HookInputComponent } from "./components/hook-input";
|
|
@@ -67,6 +54,7 @@ import { SessionSelectorComponent } from "./components/session-selector";
|
|
|
67
54
|
import { SettingsSelectorComponent } from "./components/settings-selector";
|
|
68
55
|
import { ToolExecutionComponent } from "./components/tool-execution";
|
|
69
56
|
import { TreeSelectorComponent } from "./components/tree-selector";
|
|
57
|
+
import { TtsrNotificationComponent } from "./components/ttsr-notification";
|
|
70
58
|
import { UserMessageComponent } from "./components/user-message";
|
|
71
59
|
import { UserMessageSelectorComponent } from "./components/user-message-selector";
|
|
72
60
|
import { WelcomeComponent } from "./components/welcome";
|
|
@@ -202,7 +190,8 @@ export class InteractiveMode {
|
|
|
202
190
|
{ name: "share", description: "Share session as a secret GitHub gist" },
|
|
203
191
|
{ name: "copy", description: "Copy last agent message to clipboard" },
|
|
204
192
|
{ name: "session", description: "Show session info and stats" },
|
|
205
|
-
{ name: "
|
|
193
|
+
{ name: "extensions", description: "Open Extension Control Center dashboard" },
|
|
194
|
+
{ name: "status", description: "Alias for /extensions" },
|
|
206
195
|
{ name: "changelog", description: "Show changelog entries" },
|
|
207
196
|
{ name: "hotkeys", description: "Show all keyboard shortcuts" },
|
|
208
197
|
{ name: "branch", description: "Create a new branch from a previous message" },
|
|
@@ -412,6 +401,7 @@ export class InteractiveMode {
|
|
|
412
401
|
this.streamingComponent = undefined;
|
|
413
402
|
this.streamingMessage = undefined;
|
|
414
403
|
this.pendingTools.clear();
|
|
404
|
+
this.titleGenerationAttempted = false;
|
|
415
405
|
|
|
416
406
|
this.chatContainer.addChild(new Spacer(1));
|
|
417
407
|
this.chatContainer.addChild(new Text(`${theme.fg("accent", "✓ New session started")}`, 1, 1));
|
|
@@ -793,8 +783,8 @@ export class InteractiveMode {
|
|
|
793
783
|
this.editor.setText("");
|
|
794
784
|
return;
|
|
795
785
|
}
|
|
796
|
-
if (text === "/status") {
|
|
797
|
-
this.
|
|
786
|
+
if (text === "/extensions" || text === "/status") {
|
|
787
|
+
this.showExtensionsDashboard();
|
|
798
788
|
this.editor.setText("");
|
|
799
789
|
return;
|
|
800
790
|
}
|
|
@@ -1007,18 +997,28 @@ export class InteractiveMode {
|
|
|
1007
997
|
if (event.message.role === "user") break;
|
|
1008
998
|
if (this.streamingComponent && event.message.role === "assistant") {
|
|
1009
999
|
this.streamingMessage = event.message;
|
|
1010
|
-
|
|
1000
|
+
// Don't show "Aborted" text for TTSR aborts - we'll show a nicer message
|
|
1001
|
+
if (this.session.isTtsrAbortPending && this.streamingMessage.stopReason === "aborted") {
|
|
1002
|
+
// TTSR abort - suppress the "Aborted" rendering in the component
|
|
1003
|
+
const msgWithoutAbort = { ...this.streamingMessage, stopReason: "stop" as const };
|
|
1004
|
+
this.streamingComponent.updateContent(msgWithoutAbort);
|
|
1005
|
+
} else {
|
|
1006
|
+
this.streamingComponent.updateContent(this.streamingMessage);
|
|
1007
|
+
}
|
|
1011
1008
|
|
|
1012
1009
|
if (this.streamingMessage.stopReason === "aborted" || this.streamingMessage.stopReason === "error") {
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1010
|
+
// Skip error handling for TTSR aborts
|
|
1011
|
+
if (!this.session.isTtsrAbortPending) {
|
|
1012
|
+
const errorMessage =
|
|
1013
|
+
this.streamingMessage.stopReason === "aborted"
|
|
1014
|
+
? "Operation aborted"
|
|
1015
|
+
: this.streamingMessage.errorMessage || "Error";
|
|
1016
|
+
for (const [, component] of this.pendingTools.entries()) {
|
|
1017
|
+
component.updateResult({
|
|
1018
|
+
content: [{ type: "text", text: errorMessage }],
|
|
1019
|
+
isError: true,
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
1022
|
}
|
|
1023
1023
|
this.pendingTools.clear();
|
|
1024
1024
|
} else {
|
|
@@ -1188,6 +1188,15 @@ export class InteractiveMode {
|
|
|
1188
1188
|
this.ui.requestRender();
|
|
1189
1189
|
break;
|
|
1190
1190
|
}
|
|
1191
|
+
|
|
1192
|
+
case "ttsr_triggered": {
|
|
1193
|
+
// Show a fancy notification when TTSR rules are triggered
|
|
1194
|
+
const component = new TtsrNotificationComponent(event.rules);
|
|
1195
|
+
component.setExpanded(this.toolOutputExpanded);
|
|
1196
|
+
this.chatContainer.addChild(component);
|
|
1197
|
+
this.ui.requestRender();
|
|
1198
|
+
break;
|
|
1199
|
+
}
|
|
1191
1200
|
}
|
|
1192
1201
|
}
|
|
1193
1202
|
|
|
@@ -1654,11 +1663,11 @@ export class InteractiveMode {
|
|
|
1654
1663
|
.then((title) => {
|
|
1655
1664
|
if (title) {
|
|
1656
1665
|
this.sessionManager.setSessionTitle(title);
|
|
1657
|
-
setTerminalTitle(`
|
|
1666
|
+
setTerminalTitle(`omp: ${title}`);
|
|
1658
1667
|
}
|
|
1659
1668
|
})
|
|
1660
1669
|
.catch(() => {
|
|
1661
|
-
//
|
|
1670
|
+
// Errors logged via logger in title-generator
|
|
1662
1671
|
});
|
|
1663
1672
|
}
|
|
1664
1673
|
|
|
@@ -1736,6 +1745,21 @@ export class InteractiveMode {
|
|
|
1736
1745
|
});
|
|
1737
1746
|
}
|
|
1738
1747
|
|
|
1748
|
+
/**
|
|
1749
|
+
* Show the Extension Control Center dashboard.
|
|
1750
|
+
* Replaces /status with a unified view of all providers and extensions.
|
|
1751
|
+
*/
|
|
1752
|
+
private showExtensionsDashboard(): void {
|
|
1753
|
+
this.showSelector((done) => {
|
|
1754
|
+
const dashboard = new ExtensionDashboard(process.cwd(), this.settingsManager);
|
|
1755
|
+
dashboard.onClose = () => {
|
|
1756
|
+
done();
|
|
1757
|
+
this.ui.requestRender();
|
|
1758
|
+
};
|
|
1759
|
+
return { component: dashboard, focus: dashboard };
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
|
|
1739
1763
|
/**
|
|
1740
1764
|
* Handle setting changes from the settings selector.
|
|
1741
1765
|
* Most settings are saved directly via SettingsManager in the definitions.
|
|
@@ -2390,290 +2414,6 @@ export class InteractiveMode {
|
|
|
2390
2414
|
this.ui.requestRender();
|
|
2391
2415
|
}
|
|
2392
2416
|
|
|
2393
|
-
private handleStatusCommand(): void {
|
|
2394
|
-
type StatusSource =
|
|
2395
|
-
| { provider: string; level: string }
|
|
2396
|
-
| { mcpServer: string; provider?: string }
|
|
2397
|
-
| "builtin"
|
|
2398
|
-
| "unknown";
|
|
2399
|
-
|
|
2400
|
-
type StatusLine = {
|
|
2401
|
-
name: string;
|
|
2402
|
-
sourceText: string;
|
|
2403
|
-
nameWithSource: string;
|
|
2404
|
-
desc?: string;
|
|
2405
|
-
};
|
|
2406
|
-
|
|
2407
|
-
type LineSection = {
|
|
2408
|
-
title: string;
|
|
2409
|
-
lines: StatusLine[];
|
|
2410
|
-
};
|
|
2411
|
-
|
|
2412
|
-
type Section = { kind: "lines"; section: LineSection } | { kind: "text"; text: string };
|
|
2413
|
-
|
|
2414
|
-
const capitalize = (value: string): string => value.charAt(0).toUpperCase() + value.slice(1);
|
|
2415
|
-
|
|
2416
|
-
const resolveSourceText = (source: StatusSource): string => {
|
|
2417
|
-
if (source === "builtin") return "builtin";
|
|
2418
|
-
if (source === "unknown") return "unknown";
|
|
2419
|
-
if ("mcpServer" in source) {
|
|
2420
|
-
if (!source.provider) return `mcp:${source.mcpServer}`;
|
|
2421
|
-
return `${source.mcpServer} via ${source.provider}`;
|
|
2422
|
-
}
|
|
2423
|
-
const levelLabel = capitalize(source.level);
|
|
2424
|
-
return `via ${source.provider} (${levelLabel})`;
|
|
2425
|
-
};
|
|
2426
|
-
|
|
2427
|
-
const renderSourceText = (text: string): string => text.replace(/\bvia\b/, theme.italic("via"));
|
|
2428
|
-
|
|
2429
|
-
const truncateText = (text: string, maxWidth: number): string => {
|
|
2430
|
-
const textWidth = visibleWidth(text);
|
|
2431
|
-
if (textWidth <= maxWidth) return text;
|
|
2432
|
-
if (maxWidth <= 3) {
|
|
2433
|
-
let acc = "";
|
|
2434
|
-
let width = 0;
|
|
2435
|
-
for (const char of text) {
|
|
2436
|
-
const charWidth = visibleWidth(char);
|
|
2437
|
-
if (width + charWidth > maxWidth) break;
|
|
2438
|
-
width += charWidth;
|
|
2439
|
-
acc += char;
|
|
2440
|
-
}
|
|
2441
|
-
return acc;
|
|
2442
|
-
}
|
|
2443
|
-
const targetWidth = maxWidth - 3;
|
|
2444
|
-
let acc = "";
|
|
2445
|
-
let width = 0;
|
|
2446
|
-
for (const char of text) {
|
|
2447
|
-
const charWidth = visibleWidth(char);
|
|
2448
|
-
if (width + charWidth > targetWidth) break;
|
|
2449
|
-
width += charWidth;
|
|
2450
|
-
acc += char;
|
|
2451
|
-
}
|
|
2452
|
-
return `${acc}...`;
|
|
2453
|
-
};
|
|
2454
|
-
|
|
2455
|
-
const buildLineSection = <T>(
|
|
2456
|
-
title: string,
|
|
2457
|
-
items: readonly T[],
|
|
2458
|
-
getName: (item: T) => string,
|
|
2459
|
-
getDesc: (item: T) => string | undefined,
|
|
2460
|
-
getSource: (item: T) => StatusSource,
|
|
2461
|
-
): LineSection | null => {
|
|
2462
|
-
if (items.length === 0) return null;
|
|
2463
|
-
|
|
2464
|
-
const lines = items.map((item) => {
|
|
2465
|
-
const name = getName(item);
|
|
2466
|
-
const desc = getDesc(item)?.trim();
|
|
2467
|
-
const sourceText = resolveSourceText(getSource(item));
|
|
2468
|
-
const nameWithSource = sourceText ? `${name} ${sourceText}` : name;
|
|
2469
|
-
return { name, sourceText, nameWithSource, desc };
|
|
2470
|
-
});
|
|
2471
|
-
|
|
2472
|
-
return { title, lines };
|
|
2473
|
-
};
|
|
2474
|
-
|
|
2475
|
-
const renderLineSection = (section: LineSection, maxNameWidth: number): string => {
|
|
2476
|
-
const formattedLines = section.lines.map((line) => {
|
|
2477
|
-
let nameText = line.name;
|
|
2478
|
-
let sourceText = line.sourceText;
|
|
2479
|
-
|
|
2480
|
-
if (sourceText) {
|
|
2481
|
-
const maxSourceWidth = Math.max(0, maxNameWidth - 2);
|
|
2482
|
-
sourceText = truncateText(sourceText, maxSourceWidth);
|
|
2483
|
-
}
|
|
2484
|
-
const sourceWidth = sourceText ? visibleWidth(sourceText) : 0;
|
|
2485
|
-
const availableForName = sourceText ? Math.max(1, maxNameWidth - sourceWidth - 1) : maxNameWidth;
|
|
2486
|
-
nameText = truncateText(nameText, availableForName);
|
|
2487
|
-
|
|
2488
|
-
const nameWithSourcePlain = sourceText ? `${nameText} ${sourceText}` : nameText;
|
|
2489
|
-
const sourceRendered = sourceText ? renderSourceText(sourceText) : "";
|
|
2490
|
-
const nameRendered = sourceText ? `${theme.bold(nameText)} ${sourceRendered}` : theme.bold(nameText);
|
|
2491
|
-
const pad = Math.max(0, maxNameWidth - visibleWidth(nameWithSourcePlain));
|
|
2492
|
-
const desc = line.desc;
|
|
2493
|
-
const descPart = desc
|
|
2494
|
-
? ` ${theme.fg("dim", desc.slice(0, 50) + (desc.length > 50 ? "..." : ""))}`
|
|
2495
|
-
: "";
|
|
2496
|
-
return ` ${nameRendered}${" ".repeat(pad)}${descPart}`;
|
|
2497
|
-
});
|
|
2498
|
-
|
|
2499
|
-
return `${theme.bold(theme.fg("accent", section.title))}\n${formattedLines.join("\n")}`;
|
|
2500
|
-
};
|
|
2501
|
-
|
|
2502
|
-
const sections: Section[] = [];
|
|
2503
|
-
const pushLineSection = <T>(
|
|
2504
|
-
title: string,
|
|
2505
|
-
items: readonly T[],
|
|
2506
|
-
getName: (item: T) => string,
|
|
2507
|
-
getDesc: (item: T) => string | undefined,
|
|
2508
|
-
getSource: (item: T) => StatusSource,
|
|
2509
|
-
): void => {
|
|
2510
|
-
const section = buildLineSection(title, items, getName, getDesc, getSource);
|
|
2511
|
-
if (section) {
|
|
2512
|
-
sections.push({ kind: "lines", section });
|
|
2513
|
-
}
|
|
2514
|
-
};
|
|
2515
|
-
|
|
2516
|
-
// Loaded context files
|
|
2517
|
-
const contextFilesResult = loadSync(contextFileCapability.id, { cwd: process.cwd() });
|
|
2518
|
-
const contextFiles = contextFilesResult.items as ContextFile[];
|
|
2519
|
-
pushLineSection(
|
|
2520
|
-
"Context Files",
|
|
2521
|
-
contextFiles,
|
|
2522
|
-
(f) => basename(f.path),
|
|
2523
|
-
() => undefined,
|
|
2524
|
-
(f) => ({ provider: f._source.providerName, level: f.level }),
|
|
2525
|
-
);
|
|
2526
|
-
|
|
2527
|
-
// Loaded skills
|
|
2528
|
-
const skillsSettings = this.session.skillsSettings;
|
|
2529
|
-
if (skillsSettings?.enabled !== false) {
|
|
2530
|
-
const { skills, warnings: skillWarnings } = loadSkills(skillsSettings ?? {});
|
|
2531
|
-
pushLineSection(
|
|
2532
|
-
"Skills",
|
|
2533
|
-
skills,
|
|
2534
|
-
(s) => s.name,
|
|
2535
|
-
(s) => s.description,
|
|
2536
|
-
(s) => (s._source ? { provider: s._source.providerName, level: s._source.level } : "unknown"),
|
|
2537
|
-
);
|
|
2538
|
-
if (skillWarnings.length > 0) {
|
|
2539
|
-
sections.push(
|
|
2540
|
-
{
|
|
2541
|
-
kind: "text",
|
|
2542
|
-
text:
|
|
2543
|
-
theme.bold(theme.fg("warning", "Skill Warnings")) +
|
|
2544
|
-
"\n" +
|
|
2545
|
-
skillWarnings.map((w) => theme.fg("warning", ` ${w.skillPath}: ${w.message}`)).join("\n"),
|
|
2546
|
-
},
|
|
2547
|
-
);
|
|
2548
|
-
}
|
|
2549
|
-
}
|
|
2550
|
-
|
|
2551
|
-
// Loaded rules
|
|
2552
|
-
const rulesResult = loadSync<Rule>(ruleCapability.id, { cwd: process.cwd() });
|
|
2553
|
-
pushLineSection(
|
|
2554
|
-
"Rules",
|
|
2555
|
-
rulesResult.items,
|
|
2556
|
-
(r) => r.name,
|
|
2557
|
-
(r) => r.description,
|
|
2558
|
-
(r) => ({ provider: r._source.providerName, level: r._source.level }),
|
|
2559
|
-
);
|
|
2560
|
-
|
|
2561
|
-
// Loaded prompts
|
|
2562
|
-
const promptsResult = loadSync<Prompt>(promptCapability.id, { cwd: process.cwd() });
|
|
2563
|
-
pushLineSection(
|
|
2564
|
-
"Prompts",
|
|
2565
|
-
promptsResult.items,
|
|
2566
|
-
(p) => p.name,
|
|
2567
|
-
() => undefined,
|
|
2568
|
-
(p) => ({ provider: p._source.providerName, level: p._source.level }),
|
|
2569
|
-
);
|
|
2570
|
-
|
|
2571
|
-
// Loaded instructions
|
|
2572
|
-
const instructionsResult = loadSync<Instruction>(instructionCapability.id, { cwd: process.cwd() });
|
|
2573
|
-
pushLineSection(
|
|
2574
|
-
"Instructions",
|
|
2575
|
-
instructionsResult.items,
|
|
2576
|
-
(i) => i.name,
|
|
2577
|
-
(i) => (i.applyTo ? `applies to: ${i.applyTo}` : undefined),
|
|
2578
|
-
(i) => ({ provider: i._source.providerName, level: i._source.level }),
|
|
2579
|
-
);
|
|
2580
|
-
|
|
2581
|
-
// Loaded custom tools - split MCP from non-MCP
|
|
2582
|
-
if (this.customTools.size > 0) {
|
|
2583
|
-
const allTools = Array.from(this.customTools.values());
|
|
2584
|
-
const mcpTools = allTools.filter((ct) => ct.path.startsWith("mcp:"));
|
|
2585
|
-
const customTools = allTools.filter((ct) => !ct.path.startsWith("mcp:"));
|
|
2586
|
-
|
|
2587
|
-
// MCP Tools section
|
|
2588
|
-
if (mcpTools.length > 0) {
|
|
2589
|
-
pushLineSection(
|
|
2590
|
-
"MCP Tools",
|
|
2591
|
-
mcpTools,
|
|
2592
|
-
(ct) => ct.tool.label || ct.tool.name,
|
|
2593
|
-
() => undefined,
|
|
2594
|
-
(ct) => {
|
|
2595
|
-
const match = ct.path.match(/^mcp:(.+?) via (.+)$/);
|
|
2596
|
-
if (match) {
|
|
2597
|
-
const [, serverName, providerName] = match;
|
|
2598
|
-
return { mcpServer: serverName, provider: providerName };
|
|
2599
|
-
}
|
|
2600
|
-
return ct.path.startsWith("mcp:") ? { mcpServer: ct.path.slice(4) } : "unknown";
|
|
2601
|
-
},
|
|
2602
|
-
);
|
|
2603
|
-
}
|
|
2604
|
-
|
|
2605
|
-
// Custom Tools section
|
|
2606
|
-
if (customTools.length > 0) {
|
|
2607
|
-
pushLineSection(
|
|
2608
|
-
"Custom Tools",
|
|
2609
|
-
customTools,
|
|
2610
|
-
(ct) => ct.tool.label || ct.tool.name,
|
|
2611
|
-
(ct) => ct.tool.description,
|
|
2612
|
-
(ct) => {
|
|
2613
|
-
if (ct.source?.provider === "builtin") return "builtin";
|
|
2614
|
-
if (ct.path === "<exa>") return "builtin";
|
|
2615
|
-
return ct.source ? { provider: ct.source.providerName, level: ct.source.level } : "unknown";
|
|
2616
|
-
},
|
|
2617
|
-
);
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
|
|
2621
|
-
// Loaded slash commands (file-based)
|
|
2622
|
-
const fileCommands = this.session.fileCommands;
|
|
2623
|
-
pushLineSection(
|
|
2624
|
-
"Slash Commands",
|
|
2625
|
-
fileCommands,
|
|
2626
|
-
(cmd) => `/${cmd.name}`,
|
|
2627
|
-
(cmd) => cmd.description,
|
|
2628
|
-
(cmd) => (cmd._source ? { provider: cmd._source.providerName, level: cmd._source.level } : "unknown"),
|
|
2629
|
-
);
|
|
2630
|
-
|
|
2631
|
-
// Loaded hooks
|
|
2632
|
-
const hookRunner = this.session.hookRunner;
|
|
2633
|
-
if (hookRunner) {
|
|
2634
|
-
const hookPaths = hookRunner.getHookPaths();
|
|
2635
|
-
if (hookPaths.length > 0) {
|
|
2636
|
-
sections.push(
|
|
2637
|
-
{
|
|
2638
|
-
kind: "text",
|
|
2639
|
-
text:
|
|
2640
|
-
`${theme.bold(theme.fg("accent", "Hooks"))}\n` +
|
|
2641
|
-
hookPaths.map((p) => ` ${theme.bold(basename(p))} ${theme.fg("dim", "hook")}`).join("\n"),
|
|
2642
|
-
},
|
|
2643
|
-
);
|
|
2644
|
-
}
|
|
2645
|
-
}
|
|
2646
|
-
|
|
2647
|
-
const lineSections = sections.filter((section): section is { kind: "lines"; section: LineSection } => {
|
|
2648
|
-
return section.kind === "lines";
|
|
2649
|
-
});
|
|
2650
|
-
const allLines = lineSections.flatMap((section) => section.section.lines);
|
|
2651
|
-
const maxNameWidth = allLines.length
|
|
2652
|
-
? Math.min(60, Math.max(...allLines.map((line) => visibleWidth(line.nameWithSource))))
|
|
2653
|
-
: 0;
|
|
2654
|
-
const renderedSections = sections
|
|
2655
|
-
.map((section) =>
|
|
2656
|
-
section.kind === "lines" ? renderLineSection(section.section, maxNameWidth) : section.text,
|
|
2657
|
-
)
|
|
2658
|
-
.filter((section) => section.length > 0);
|
|
2659
|
-
|
|
2660
|
-
if (renderedSections.length === 0) {
|
|
2661
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
2662
|
-
this.chatContainer.addChild(new Text(theme.fg("muted", "No extensions loaded."), 1, 0));
|
|
2663
|
-
} else {
|
|
2664
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
2665
|
-
this.chatContainer.addChild(new DynamicBorder());
|
|
2666
|
-
this.chatContainer.addChild(new Text(theme.bold(theme.fg("accent", "Loaded Extensions")), 1, 0));
|
|
2667
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
2668
|
-
for (const section of renderedSections) {
|
|
2669
|
-
this.chatContainer.addChild(new Text(section, 1, 0));
|
|
2670
|
-
this.chatContainer.addChild(new Spacer(1));
|
|
2671
|
-
}
|
|
2672
|
-
this.chatContainer.addChild(new DynamicBorder());
|
|
2673
|
-
}
|
|
2674
|
-
this.ui.requestRender();
|
|
2675
|
-
}
|
|
2676
|
-
|
|
2677
2417
|
private async handleClearCommand(): Promise<void> {
|
|
2678
2418
|
// Stop loading animation
|
|
2679
2419
|
if (this.loadingAnimation) {
|
package/src/modes/print-mode.ts
CHANGED
|
@@ -7,9 +7,35 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { AssistantMessage, ImageContent } from "@oh-my-pi/pi-ai";
|
|
10
|
+
import { APP_NAME, VERSION } from "../config";
|
|
10
11
|
import type { AgentSession } from "../core/agent-session";
|
|
11
12
|
import { logger } from "../core/logger";
|
|
12
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Print session header to stderr (text mode only).
|
|
16
|
+
*/
|
|
17
|
+
function printHeader(session: AgentSession): void {
|
|
18
|
+
const model = session.model;
|
|
19
|
+
const lines = [
|
|
20
|
+
`${APP_NAME} v${VERSION}`,
|
|
21
|
+
"--------",
|
|
22
|
+
`workdir: ${process.cwd()}`,
|
|
23
|
+
`model: ${model?.id ?? "unknown"}`,
|
|
24
|
+
`provider: ${model?.provider ?? "unknown"}`,
|
|
25
|
+
`thinking: ${session.thinkingLevel}`,
|
|
26
|
+
`session: ${session.sessionId}`,
|
|
27
|
+
"--------",
|
|
28
|
+
];
|
|
29
|
+
console.error(lines.join("\n"));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Print session footer to stderr (text mode only).
|
|
34
|
+
*/
|
|
35
|
+
function printFooter(): void {
|
|
36
|
+
console.error("--------");
|
|
37
|
+
}
|
|
38
|
+
|
|
13
39
|
/**
|
|
14
40
|
* Run in print (single-shot) mode.
|
|
15
41
|
* Sends prompts to the agent and outputs the result.
|
|
@@ -27,6 +53,11 @@ export async function runPrintMode(
|
|
|
27
53
|
initialMessage?: string,
|
|
28
54
|
initialImages?: ImageContent[],
|
|
29
55
|
): Promise<void> {
|
|
56
|
+
// Print header to stderr (text mode only)
|
|
57
|
+
if (mode === "text") {
|
|
58
|
+
printHeader(session);
|
|
59
|
+
}
|
|
60
|
+
|
|
30
61
|
// Hook runner already has no-op UI context by default (set in main.ts)
|
|
31
62
|
// Set up hooks for print mode (no UI)
|
|
32
63
|
const hookRunner = session.hookRunner;
|
|
@@ -116,6 +147,9 @@ export async function runPrintMode(
|
|
|
116
147
|
}
|
|
117
148
|
}
|
|
118
149
|
}
|
|
150
|
+
|
|
151
|
+
// Print footer to stderr
|
|
152
|
+
printFooter();
|
|
119
153
|
}
|
|
120
154
|
|
|
121
155
|
// Ensure stdout is fully flushed before returning
|