@oh-my-pi/pi-coding-agent 15.13.3 → 16.0.0
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 +40 -0
- package/dist/cli.js +506 -443
- package/dist/types/advisor/__tests__/advisor.test.d.ts +1 -0
- package/dist/types/advisor/advise-tool.d.ts +58 -0
- package/dist/types/advisor/index.d.ts +3 -0
- package/dist/types/advisor/runtime.d.ts +52 -0
- package/dist/types/advisor/watchdog.d.ts +5 -0
- package/dist/types/config/model-roles.d.ts +1 -1
- package/dist/types/config/settings-schema.d.ts +44 -5
- package/dist/types/modes/components/advisor-message.d.ts +9 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -0
- package/dist/types/modes/controllers/command-controller.d.ts +3 -1
- package/dist/types/modes/interactive-mode.d.ts +3 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/sdk.d.ts +3 -3
- package/dist/types/session/agent-session.d.ts +71 -2
- package/dist/types/session/session-history-format.d.ts +4 -0
- package/dist/types/session/yield-queue.d.ts +2 -0
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/report-tool-issue.d.ts +0 -1
- package/package.json +13 -13
- package/src/advisor/__tests__/advisor.test.ts +586 -0
- package/src/advisor/advise-tool.ts +87 -0
- package/src/advisor/index.ts +3 -0
- package/src/advisor/runtime.ts +248 -0
- package/src/advisor/watchdog.ts +83 -0
- package/src/config/model-roles.ts +13 -1
- package/src/config/settings-schema.ts +42 -5
- package/src/internal-urls/docs-index.generated.ts +6 -5
- package/src/main.ts +4 -0
- package/src/modes/components/advisor-message.ts +99 -0
- package/src/modes/components/agent-hub.ts +7 -0
- package/src/modes/components/assistant-message.ts +86 -0
- package/src/modes/components/status-line/segments.ts +20 -7
- package/src/modes/controllers/command-controller.ts +69 -2
- package/src/modes/interactive-mode.ts +12 -2
- package/src/modes/types.ts +3 -1
- package/src/modes/utils/ui-helpers.ts +9 -0
- package/src/prompts/advisor/advise-tool.md +1 -0
- package/src/prompts/advisor/system.md +31 -0
- package/src/sdk.ts +52 -13
- package/src/session/agent-session.ts +560 -13
- package/src/session/session-dump-format.ts +15 -131
- package/src/session/session-history-format.ts +30 -11
- package/src/session/yield-queue.ts +5 -1
- package/src/slash-commands/builtin-registry.ts +102 -4
- package/src/system-prompt.ts +1 -1
- package/src/tools/path-utils.ts +33 -2
- package/src/tools/report-tool-issue.ts +2 -7
- package/src/web/scrapers/docs-rs.ts +2 -3
package/src/main.ts
CHANGED
|
@@ -140,6 +140,10 @@ const HOST_DEFAULTED_SETTING_PATHS: SettingPath[] = [
|
|
|
140
140
|
// memory should opt in explicitly through their own settings layer.
|
|
141
141
|
"memory.backend",
|
|
142
142
|
"memories.enabled",
|
|
143
|
+
// Advisor is interactive-session assistance. Protocol hosts opt in explicitly
|
|
144
|
+
// instead of inheriting a user's globally-enabled local preference.
|
|
145
|
+
"advisor.enabled",
|
|
146
|
+
"advisor.subagents",
|
|
143
147
|
];
|
|
144
148
|
|
|
145
149
|
const RPC_BACKGROUND_DEFAULTED_SETTING_PATHS: SettingPath[] = [
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { type Component, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
2
|
+
import type { AdvisorMessageDetails, AdvisorSeverity } from "../../advisor";
|
|
3
|
+
import {
|
|
4
|
+
createCachedComponent,
|
|
5
|
+
formatBadge,
|
|
6
|
+
replaceTabs,
|
|
7
|
+
type ToolUIColor,
|
|
8
|
+
wrapTextWithAnsi,
|
|
9
|
+
} from "../../tools/render-utils";
|
|
10
|
+
import { Ellipsis, renderStatusLine, truncateToWidth } from "../../tui";
|
|
11
|
+
import type { Theme } from "../theme/theme";
|
|
12
|
+
|
|
13
|
+
const COLLAPSED_NOTES = 3;
|
|
14
|
+
const NOTE_LINE_WIDTH = 110;
|
|
15
|
+
|
|
16
|
+
function wrapVarying(text: string, w1: number, w2: number): string[] {
|
|
17
|
+
if (text.length === 0) return [];
|
|
18
|
+
const firstWrap = wrapTextWithAnsi(text, w1);
|
|
19
|
+
if (firstWrap.length <= 1) {
|
|
20
|
+
return firstWrap;
|
|
21
|
+
}
|
|
22
|
+
const firstLine = firstWrap[0];
|
|
23
|
+
const idx = text.indexOf(firstLine);
|
|
24
|
+
if (idx === -1) {
|
|
25
|
+
return wrapTextWithAnsi(text, w2);
|
|
26
|
+
}
|
|
27
|
+
const remainder = text.slice(idx + firstLine.length).trimStart();
|
|
28
|
+
const restWrap = wrapTextWithAnsi(remainder, w2);
|
|
29
|
+
return [firstLine, ...restWrap];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function severityColor(severity: AdvisorSeverity | undefined): ToolUIColor {
|
|
33
|
+
switch (severity) {
|
|
34
|
+
case "blocker":
|
|
35
|
+
return "error";
|
|
36
|
+
case "concern":
|
|
37
|
+
return "warning";
|
|
38
|
+
default:
|
|
39
|
+
return "muted";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Display-only transcript card for advisor notes injected into the primary
|
|
45
|
+
* session. Mirrors the IRC card's glyph + quote-border conventions so passive
|
|
46
|
+
* advice reads as a distinct, non-interrupting aside rather than a user turn.
|
|
47
|
+
*/
|
|
48
|
+
export function createAdvisorMessageCard(
|
|
49
|
+
details: AdvisorMessageDetails | undefined,
|
|
50
|
+
getExpanded: () => boolean,
|
|
51
|
+
uiTheme: Theme,
|
|
52
|
+
): Component {
|
|
53
|
+
const notes = details?.notes ?? [];
|
|
54
|
+
const blockers = notes.filter(note => note.severity === "blocker").length;
|
|
55
|
+
const meta: string[] = [`${notes.length} ${notes.length === 1 ? "note" : "notes"}`];
|
|
56
|
+
if (blockers > 0) meta.push(uiTheme.fg("error", `${blockers} blocker${blockers === 1 ? "" : "s"}`));
|
|
57
|
+
|
|
58
|
+
return createCachedComponent(
|
|
59
|
+
getExpanded,
|
|
60
|
+
(width, expanded) => {
|
|
61
|
+
const glyph = uiTheme.styledSymbol("status.info", "accent");
|
|
62
|
+
const lines = [renderStatusLine({ iconOverride: glyph, title: "Advisor", meta }, uiTheme)];
|
|
63
|
+
const quote = uiTheme.fg("dim", uiTheme.md.quoteBorder);
|
|
64
|
+
const shown = expanded ? notes : notes.slice(0, COLLAPSED_NOTES);
|
|
65
|
+
for (const entry of shown) {
|
|
66
|
+
const badge = entry.severity
|
|
67
|
+
? `${formatBadge(entry.severity, severityColor(entry.severity), uiTheme)} `
|
|
68
|
+
: "";
|
|
69
|
+
const quotePrefix = ` ${quote} `;
|
|
70
|
+
const quoteWidth = visibleWidth(quotePrefix);
|
|
71
|
+
const badgeWidth = visibleWidth(badge);
|
|
72
|
+
const w1 = Math.max(10, Math.min(NOTE_LINE_WIDTH, width) - quoteWidth - badgeWidth);
|
|
73
|
+
const w2 = Math.max(10, Math.min(NOTE_LINE_WIDTH, width) - quoteWidth);
|
|
74
|
+
|
|
75
|
+
const paragraphs = entry.note.split("\n").filter(p => p.trim());
|
|
76
|
+
const bodyLines: string[] = [];
|
|
77
|
+
for (let i = 0; i < paragraphs.length; i++) {
|
|
78
|
+
const p = paragraphs[i];
|
|
79
|
+
if (i === 0) {
|
|
80
|
+
bodyLines.push(...wrapVarying(p, w1, w2));
|
|
81
|
+
} else {
|
|
82
|
+
bodyLines.push(...wrapTextWithAnsi(p, w2));
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
bodyLines.forEach((line, index) => {
|
|
87
|
+
const prefix = index === 0 ? badge : "";
|
|
88
|
+
lines.push(` ${quote} ${prefix}${uiTheme.fg("toolOutput", replaceTabs(line))}`);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
const hidden = notes.length - shown.length;
|
|
92
|
+
if (hidden > 0) {
|
|
93
|
+
lines.push(` ${quote} ${uiTheme.fg("dim", `… +${hidden} more ${hidden === 1 ? "note" : "notes"}`)}`);
|
|
94
|
+
}
|
|
95
|
+
return lines.map(line => truncateToWidth(line, width, Ellipsis.Unicode));
|
|
96
|
+
},
|
|
97
|
+
{ paddingX: 1 },
|
|
98
|
+
);
|
|
99
|
+
}
|
|
@@ -19,6 +19,7 @@ import type { AgentMessage, AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
|
19
19
|
import type { Usage } from "@oh-my-pi/pi-ai";
|
|
20
20
|
import { Container, Editor, matchesKey, ScrollView, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
21
21
|
import { formatAge, formatBytes, formatDuration, formatNumber, getProjectDir, logger } from "@oh-my-pi/pi-utils";
|
|
22
|
+
import type { AdvisorMessageDetails } from "../../advisor";
|
|
22
23
|
import { COLLAB_PROMPT_MESSAGE_TYPE, type CollabPromptDetails } from "../../collab/protocol";
|
|
23
24
|
import type { KeyId } from "../../config/keybindings";
|
|
24
25
|
import { settings } from "../../config/settings";
|
|
@@ -45,6 +46,7 @@ import { canonicalizeMessage } from "../../utils/thinking-display";
|
|
|
45
46
|
import type { ObservableSession, SessionObserverRegistry } from "../session-observer-registry";
|
|
46
47
|
import { getEditorTheme, theme } from "../theme/theme";
|
|
47
48
|
import { matchesSelectDown, matchesSelectUp } from "../utils/keybinding-matchers";
|
|
49
|
+
import { createAdvisorMessageCard } from "./advisor-message";
|
|
48
50
|
import { AssistantMessageComponent } from "./assistant-message";
|
|
49
51
|
import { createBackgroundTanDispatchBlock } from "./background-tan-message";
|
|
50
52
|
import { BashExecutionComponent } from "./bash-execution";
|
|
@@ -1241,6 +1243,11 @@ export class AgentHubOverlayComponent extends Container {
|
|
|
1241
1243
|
this.#chatLog.addChild(card);
|
|
1242
1244
|
return;
|
|
1243
1245
|
}
|
|
1246
|
+
if (message.customType === "advisor") {
|
|
1247
|
+
const details = (message as CustomMessage<AdvisorMessageDetails>).details;
|
|
1248
|
+
this.#chatLog.addChild(createAdvisorMessageCard(details, () => this.#chatExpanded, theme));
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1244
1251
|
if (message.customType === BACKGROUND_TAN_DISPATCH_MESSAGE_TYPE) {
|
|
1245
1252
|
this.#chatLog.addChild(createBackgroundTanDispatchBlock(message as CustomMessage<unknown>));
|
|
1246
1253
|
return;
|
|
@@ -15,6 +15,15 @@ import { canonicalizeMessage } from "../../utils/thinking-display";
|
|
|
15
15
|
*/
|
|
16
16
|
const MAX_TRANSCRIPT_ERROR_LINES = 8;
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Frames for the streaming "thinking" pulse rendered in place of a hidden
|
|
20
|
+
* thinking block while the model is still producing it. A single fixed-width
|
|
21
|
+
* glyph that rises ▁▃▄▃ so the indicator animates without shifting the line.
|
|
22
|
+
* Advanced every {@link THINKING_DOTS_FRAME_MS}.
|
|
23
|
+
*/
|
|
24
|
+
const THINKING_DOTS_FRAMES = ["▁", "▃", "▄", "▃"] as const;
|
|
25
|
+
const THINKING_DOTS_FRAME_MS = 320;
|
|
26
|
+
|
|
18
27
|
/**
|
|
19
28
|
* Component that renders a complete assistant message
|
|
20
29
|
*/
|
|
@@ -50,6 +59,11 @@ export class AssistantMessageComponent extends Container {
|
|
|
50
59
|
#fastPathItems:
|
|
51
60
|
| Array<{ md: Markdown; contentIndex: number; blockType: "text" | "thinking"; lastText: string }>
|
|
52
61
|
| undefined;
|
|
62
|
+
/** Live "thinking" pulse shown in place of a hidden thinking block while it
|
|
63
|
+
* streams; undefined when not animating. Driven by {@link #thinkingDotsTimer}. */
|
|
64
|
+
#thinkingDots: Text | undefined;
|
|
65
|
+
#thinkingDotsTimer: NodeJS.Timeout | undefined;
|
|
66
|
+
#thinkingDotsFrame = 0;
|
|
53
67
|
|
|
54
68
|
constructor(
|
|
55
69
|
message?: AssistantMessage,
|
|
@@ -87,6 +101,60 @@ export class AssistantMessageComponent extends Container {
|
|
|
87
101
|
this.hideThinkingBlock = hide;
|
|
88
102
|
}
|
|
89
103
|
|
|
104
|
+
override dispose(): void {
|
|
105
|
+
this.#stopThinkingAnimation();
|
|
106
|
+
super.dispose();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Whether to render the animated "thinking" pulse in place of the suppressed
|
|
111
|
+
* reasoning: only while this block is still streaming (not yet finalized — the
|
|
112
|
+
* in-flight message always carries `stopReason: "stop"`, so finalization is the
|
|
113
|
+
* only reliable live signal), thinking is hidden, no tool call has started, and
|
|
114
|
+
* the active tail block is a thinking block (the model is reasoning right now).
|
|
115
|
+
* Once text starts, a tool call streams, or the block is sealed, the pulse ends.
|
|
116
|
+
*/
|
|
117
|
+
#shouldAnimateThinking(message: AssistantMessage): boolean {
|
|
118
|
+
if (!this.hideThinkingBlock || this.#transcriptBlockFinalized) return false;
|
|
119
|
+
let tail: "text" | "thinking" | undefined;
|
|
120
|
+
for (const content of message.content) {
|
|
121
|
+
if (content.type === "toolCall") return false;
|
|
122
|
+
if (content.type === "text" && canonicalizeMessage(content.text)) tail = "text";
|
|
123
|
+
else if (content.type === "thinking" && canonicalizeMessage(content.thinking)) tail = "thinking";
|
|
124
|
+
}
|
|
125
|
+
return tail === "thinking";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#thinkingDotsLabel(): string {
|
|
129
|
+
const glyph = THINKING_DOTS_FRAMES[this.#thinkingDotsFrame % THINKING_DOTS_FRAMES.length] ?? "…";
|
|
130
|
+
return theme.fg("thinkingText", glyph);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
#startThinkingAnimation(): void {
|
|
134
|
+
if (this.#thinkingDotsTimer) return;
|
|
135
|
+
this.#thinkingDotsTimer = setInterval(() => this.#advanceThinkingDots(), THINKING_DOTS_FRAME_MS);
|
|
136
|
+
this.#thinkingDotsTimer.unref?.();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
#advanceThinkingDots(): void {
|
|
140
|
+
if (!this.#thinkingDots) {
|
|
141
|
+
this.#stopThinkingAnimation();
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
this.#thinkingDotsFrame = (this.#thinkingDotsFrame + 1) % THINKING_DOTS_FRAMES.length;
|
|
145
|
+
if (this.#thinkingDots.setText(this.#thinkingDotsLabel())) {
|
|
146
|
+
this.onImageUpdate?.();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
#stopThinkingAnimation(): void {
|
|
151
|
+
if (this.#thinkingDotsTimer) {
|
|
152
|
+
clearInterval(this.#thinkingDotsTimer);
|
|
153
|
+
this.#thinkingDotsTimer = undefined;
|
|
154
|
+
}
|
|
155
|
+
this.#thinkingDotsFrame = 0;
|
|
156
|
+
}
|
|
157
|
+
|
|
90
158
|
/**
|
|
91
159
|
* Toggle suppression of the inline `Error: …` line while the same error is
|
|
92
160
|
* pinned in the banner above the editor. Re-renders so the change is visible.
|
|
@@ -109,6 +177,14 @@ export class AssistantMessageComponent extends Container {
|
|
|
109
177
|
|
|
110
178
|
markTranscriptBlockFinalized(): void {
|
|
111
179
|
this.#transcriptBlockFinalized = true;
|
|
180
|
+
this.#stopThinkingAnimation();
|
|
181
|
+
// If the live pulse was on screen when the block sealed, drop the fast path
|
|
182
|
+
// and rebuild so the placeholder is removed — finalized blocks never animate.
|
|
183
|
+
if (this.#thinkingDots) {
|
|
184
|
+
this.#fastPathKey = undefined;
|
|
185
|
+
this.#fastPathItems = undefined;
|
|
186
|
+
if (this.#lastMessage) this.updateContent(this.#lastMessage, { transient: this.#lastUpdateTransient });
|
|
187
|
+
}
|
|
112
188
|
}
|
|
113
189
|
|
|
114
190
|
/**
|
|
@@ -326,6 +402,7 @@ export class AssistantMessageComponent extends Container {
|
|
|
326
402
|
|
|
327
403
|
// Clear content container
|
|
328
404
|
this.#contentContainer.clear();
|
|
405
|
+
this.#thinkingDots = undefined;
|
|
329
406
|
|
|
330
407
|
// Determine if we should capture Markdown instances for next fast path
|
|
331
408
|
const shouldCapture = this.#canFastPath(message);
|
|
@@ -382,6 +459,15 @@ export class AssistantMessageComponent extends Container {
|
|
|
382
459
|
}
|
|
383
460
|
}
|
|
384
461
|
|
|
462
|
+
if (this.#shouldAnimateThinking(message)) {
|
|
463
|
+
if (hasVisibleContent) this.#contentContainer.addChild(new Spacer(1));
|
|
464
|
+
this.#thinkingDots = new Text(this.#thinkingDotsLabel(), 1, 0);
|
|
465
|
+
this.#contentContainer.addChild(this.#thinkingDots);
|
|
466
|
+
this.#startThinkingAnimation();
|
|
467
|
+
} else {
|
|
468
|
+
this.#stopThinkingAnimation();
|
|
469
|
+
}
|
|
470
|
+
|
|
385
471
|
this.#renderToolImages();
|
|
386
472
|
// Check if aborted - show after partial content
|
|
387
473
|
// But only if there are no tool calls (tool execution components will show the error)
|
|
@@ -86,32 +86,45 @@ const modelSegment: StatusLineSegment = {
|
|
|
86
86
|
modelName = modelName.slice(7);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
89
|
+
// Fast-mode icon and thinking-level suffix trail the model name and are
|
|
90
|
+
// colored together with it as `statusLineModel`. The advisor "++" badge
|
|
91
|
+
// sits between the name and that tail in `accent`, so it reads as a
|
|
92
|
+
// distinct marker. theme.fg resets only the fg, so the spans are
|
|
93
|
+
// concatenated (not nested) to keep each color intact.
|
|
94
|
+
let tail = "";
|
|
91
95
|
if (ctx.session.isFastModeActive() && theme.icon.fast) {
|
|
92
|
-
|
|
96
|
+
tail += ` ${theme.icon.fast}`;
|
|
93
97
|
}
|
|
94
98
|
|
|
95
|
-
// Add thinking level with dot separator
|
|
96
99
|
if (opts.showThinkingLevel !== false && state.model?.thinking) {
|
|
97
100
|
if (ctx.session.isAutoThinking) {
|
|
98
101
|
// Pending (no turn classified yet / classifying) shows a symbol-theme
|
|
99
102
|
// question-box marker; once resolved it shows `<level>`.
|
|
100
103
|
const resolved = ctx.session.autoResolvedThinkingLevel();
|
|
101
104
|
const resolvedText = resolved ? (theme.thinking[resolved as keyof typeof theme.thinking] ?? resolved) : "";
|
|
102
|
-
|
|
105
|
+
tail += `${theme.sep.dot}${resolved ? resolvedText : `${theme.thinking.autoPending} auto`}`;
|
|
103
106
|
} else {
|
|
104
107
|
const level = state.thinkingLevel ?? ThinkingLevel.Off;
|
|
105
108
|
if (level !== ThinkingLevel.Off) {
|
|
106
109
|
const thinkingText = theme.thinking[level as keyof typeof theme.thinking];
|
|
107
110
|
if (thinkingText) {
|
|
108
|
-
|
|
111
|
+
tail += `${theme.sep.dot}${thinkingText}`;
|
|
109
112
|
}
|
|
110
113
|
}
|
|
111
114
|
}
|
|
112
115
|
}
|
|
113
116
|
|
|
114
|
-
|
|
117
|
+
// `statusLineModel` is aliased to `accent` in many themes, so the badge
|
|
118
|
+
// uses `success` to stay visibly distinct from the model name color.
|
|
119
|
+
let content = theme.fg("statusLineModel", withIcon(theme.icon.model, modelName));
|
|
120
|
+
if (ctx.session.isAdvisorActive()) {
|
|
121
|
+
content += theme.fg("success", "++");
|
|
122
|
+
}
|
|
123
|
+
if (tail) {
|
|
124
|
+
content += theme.fg("statusLineModel", tail);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { content, visible: true };
|
|
115
128
|
},
|
|
116
129
|
};
|
|
117
130
|
|
|
@@ -84,9 +84,9 @@ export class CommandController {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
handleDumpCommand() {
|
|
87
|
+
handleDumpCommand(isRaw = false) {
|
|
88
88
|
try {
|
|
89
|
-
const formatted = this.ctx.session.formatSessionAsText();
|
|
89
|
+
const formatted = this.ctx.session.formatSessionAsText({ compact: !isRaw });
|
|
90
90
|
if (!formatted) {
|
|
91
91
|
this.ctx.showError("No messages to dump yet.");
|
|
92
92
|
return;
|
|
@@ -98,6 +98,26 @@ export class CommandController {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
handleAdvisorDumpCommand(isRaw = false) {
|
|
102
|
+
try {
|
|
103
|
+
const advisorHistory = this.ctx.session.formatAdvisorHistoryAsText({ compact: !isRaw });
|
|
104
|
+
if (advisorHistory === null) {
|
|
105
|
+
this.ctx.showError("Advisor is not active for this session.");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
if (!advisorHistory) {
|
|
109
|
+
this.ctx.showError("Advisor has no history yet.");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
copyToClipboard(advisorHistory);
|
|
113
|
+
this.ctx.showStatus("Advisor history copied to clipboard");
|
|
114
|
+
} catch (error: unknown) {
|
|
115
|
+
this.ctx.showError(
|
|
116
|
+
`Failed to copy advisor history: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
101
121
|
async handleDebugTranscriptCommand(): Promise<void> {
|
|
102
122
|
try {
|
|
103
123
|
const width = Math.max(1, this.ctx.ui.terminal.columns);
|
|
@@ -305,6 +325,53 @@ export class CommandController {
|
|
|
305
325
|
this.ctx.present([new Spacer(1), new Text(info, 1, 0)]);
|
|
306
326
|
}
|
|
307
327
|
|
|
328
|
+
async handleAdvisorStatusCommand(): Promise<void> {
|
|
329
|
+
const stats = this.ctx.session.getAdvisorStats();
|
|
330
|
+
if (!stats.active) {
|
|
331
|
+
this.ctx.present([
|
|
332
|
+
new Spacer(1),
|
|
333
|
+
new Text(
|
|
334
|
+
stats.configured
|
|
335
|
+
? "Advisor setting is enabled, but no model is assigned to the 'advisor' role."
|
|
336
|
+
: "Advisor is disabled.",
|
|
337
|
+
1,
|
|
338
|
+
0,
|
|
339
|
+
),
|
|
340
|
+
]);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
const model = stats.model!;
|
|
344
|
+
let info = `${theme.bold("Advisor Status")}\n\n`;
|
|
345
|
+
info += `${theme.bold("Provider")}\n`;
|
|
346
|
+
info += `${theme.fg("dim", "Model:")} ${model.provider}/${model.id}\n`;
|
|
347
|
+
info += `\n${theme.bold("Messages")}\n`;
|
|
348
|
+
info += `${theme.fg("dim", "User:")} ${stats.messages.user.toLocaleString()}\n`;
|
|
349
|
+
info += `${theme.fg("dim", "Assistant:")} ${stats.messages.assistant.toLocaleString()}\n`;
|
|
350
|
+
info += `${theme.fg("dim", "Total:")} ${stats.messages.total.toLocaleString()}\n`;
|
|
351
|
+
info += `\n${theme.bold("Context")}\n`;
|
|
352
|
+
if (stats.contextWindow > 0) {
|
|
353
|
+
const percent = Math.round((stats.contextTokens / stats.contextWindow) * 100);
|
|
354
|
+
info += `${theme.fg("dim", "Tokens:")} ${stats.contextTokens.toLocaleString()} / ${stats.contextWindow.toLocaleString()} (${percent}%)\n`;
|
|
355
|
+
} else {
|
|
356
|
+
info += `${theme.fg("dim", "Tokens:")} ${stats.contextTokens.toLocaleString()}\n`;
|
|
357
|
+
}
|
|
358
|
+
info += `\n${theme.bold("Spend")}\n`;
|
|
359
|
+
info += `${theme.fg("dim", "Input:")} ${stats.tokens.input.toLocaleString()}\n`;
|
|
360
|
+
info += `${theme.fg("dim", "Output:")} ${stats.tokens.output.toLocaleString()}\n`;
|
|
361
|
+
if (stats.tokens.cacheRead > 0) {
|
|
362
|
+
info += `${theme.fg("dim", "Cache Read:")} ${stats.tokens.cacheRead.toLocaleString()}\n`;
|
|
363
|
+
}
|
|
364
|
+
if (stats.tokens.cacheWrite > 0) {
|
|
365
|
+
info += `${theme.fg("dim", "Cache Write:")} ${stats.tokens.cacheWrite.toLocaleString()}\n`;
|
|
366
|
+
}
|
|
367
|
+
info += `${theme.fg("dim", "Total:")} ${stats.tokens.total.toLocaleString()}\n`;
|
|
368
|
+
if (stats.cost > 0) {
|
|
369
|
+
info += `\n${theme.bold("Cost")}\n`;
|
|
370
|
+
info += `${theme.fg("dim", "Total:")} $${stats.cost.toFixed(4)}\n`;
|
|
371
|
+
}
|
|
372
|
+
this.ctx.present([new Spacer(1), new Text(info, 1, 0)]);
|
|
373
|
+
}
|
|
374
|
+
|
|
308
375
|
async handleJobsCommand(): Promise<void> {
|
|
309
376
|
const snapshot = this.ctx.session.getAsyncJobSnapshot({ recentLimit: 5 });
|
|
310
377
|
if (!snapshot) {
|
|
@@ -542,6 +542,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
542
542
|
if (eventBus) {
|
|
543
543
|
this.#eventBusUnsubscribers.push(
|
|
544
544
|
eventBus.on(LSP_STARTUP_EVENT_CHANNEL, data => {
|
|
545
|
+
if (this.settings.get("startup.quiet")) return;
|
|
545
546
|
this.#handleLspStartupEvent(data as LspStartupEvent);
|
|
546
547
|
}),
|
|
547
548
|
);
|
|
@@ -551,6 +552,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
551
552
|
logger.warn("Ignoring malformed mcp:connecting event", { data });
|
|
552
553
|
return;
|
|
553
554
|
}
|
|
555
|
+
if (this.settings.get("startup.quiet")) return;
|
|
554
556
|
this.showStatus(formatMCPConnectingMessage(data.serverNames));
|
|
555
557
|
}),
|
|
556
558
|
);
|
|
@@ -3333,8 +3335,12 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3333
3335
|
return this.#commandController.handleExportCommand(text);
|
|
3334
3336
|
}
|
|
3335
3337
|
|
|
3336
|
-
handleDumpCommand() {
|
|
3337
|
-
return this.#commandController.handleDumpCommand();
|
|
3338
|
+
handleDumpCommand(isRaw?: boolean) {
|
|
3339
|
+
return this.#commandController.handleDumpCommand(isRaw);
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
handleAdvisorDumpCommand(isRaw?: boolean) {
|
|
3343
|
+
return this.#commandController.handleAdvisorDumpCommand(isRaw);
|
|
3338
3344
|
}
|
|
3339
3345
|
|
|
3340
3346
|
handleDebugTranscriptCommand(): Promise<void> {
|
|
@@ -3353,6 +3359,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3353
3359
|
return this.#commandController.handleSessionCommand();
|
|
3354
3360
|
}
|
|
3355
3361
|
|
|
3362
|
+
handleAdvisorStatusCommand(): Promise<void> {
|
|
3363
|
+
return this.#commandController.handleAdvisorStatusCommand();
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3356
3366
|
handleJobsCommand(): Promise<void> {
|
|
3357
3367
|
return this.#commandController.handleJobsCommand();
|
|
3358
3368
|
}
|
package/src/modes/types.ts
CHANGED
|
@@ -270,13 +270,15 @@ export interface InteractiveModeContext {
|
|
|
270
270
|
handleShareCommand(): Promise<void>;
|
|
271
271
|
handleTodoCommand(args: string): Promise<void>;
|
|
272
272
|
handleSessionCommand(): Promise<void>;
|
|
273
|
+
handleAdvisorStatusCommand(): Promise<void>;
|
|
273
274
|
handleJobsCommand(): Promise<void>;
|
|
274
275
|
handleUsageCommand(reports?: UsageReport[] | null): Promise<void>;
|
|
275
276
|
handleChangelogCommand(showFull?: boolean): Promise<void>;
|
|
276
277
|
handleHotkeysCommand(): void;
|
|
277
278
|
handleToolsCommand(): void;
|
|
278
279
|
handleContextCommand(): void;
|
|
279
|
-
handleDumpCommand(): void;
|
|
280
|
+
handleDumpCommand(isRaw?: boolean): void;
|
|
281
|
+
handleAdvisorDumpCommand(isRaw?: boolean): void;
|
|
280
282
|
handleDebugTranscriptCommand(): Promise<void>;
|
|
281
283
|
handleClearCommand(): Promise<void>;
|
|
282
284
|
handleFreshCommand(): Promise<void>;
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { AssistantMessage, ImageContent, Message, Usage } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { type Component, Spacer, Text, TruncatedText } from "@oh-my-pi/pi-tui";
|
|
4
|
+
import type { AdvisorMessageDetails } from "../../advisor";
|
|
4
5
|
import { COLLAB_PROMPT_MESSAGE_TYPE, type CollabPromptDetails } from "../../collab/protocol";
|
|
5
6
|
import { settings } from "../../config/settings";
|
|
6
7
|
import { getFileSnapshotStore } from "../../edit/file-snapshot-store";
|
|
8
|
+
import { createAdvisorMessageCard } from "../../modes/components/advisor-message";
|
|
7
9
|
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
|
8
10
|
import { createBackgroundTanDispatchBlock } from "../../modes/components/background-tan-message";
|
|
9
11
|
import { BashExecutionComponent } from "../../modes/components/bash-execution";
|
|
@@ -240,6 +242,13 @@ export class UiHelpers {
|
|
|
240
242
|
this.ctx.chatContainer.addChild(card);
|
|
241
243
|
return [card];
|
|
242
244
|
}
|
|
245
|
+
if (message.customType === "advisor") {
|
|
246
|
+
const details = (message as CustomMessage<AdvisorMessageDetails>).details;
|
|
247
|
+
this.ctx.chatContainer.addChild(
|
|
248
|
+
createAdvisorMessageCard(details, () => this.ctx.toolOutputExpanded, theme),
|
|
249
|
+
);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
243
252
|
if (message.customType === BACKGROUND_TAN_DISPATCH_MESSAGE_TYPE) {
|
|
244
253
|
this.ctx.chatContainer.addChild(createBackgroundTanDispatchBlock(message as CustomMessage<unknown>));
|
|
245
254
|
break;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Send one concrete, terse piece of advice to the agent you are watching. Use sparingly; stay silent when nothing matters.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<system-conventions>
|
|
2
|
+
RFC 2119 applies to MUST, REQUIRED, SHOULD, RECOMMENDED, MAY, OPTIONAL. `NEVER` and `AVOID` are aliases for `MUST NOT` and `SHOULD NOT`.
|
|
3
|
+
You can explore the workspace; budget is 2–3 tool calls per advise (exception: critical bugs warrant deeper verification before raising a blocker).
|
|
4
|
+
</system-conventions>
|
|
5
|
+
|
|
6
|
+
You bring a different angle.
|
|
7
|
+
The agent might not have thought about an edge case, spotted a hallucinated API, or realized a simpler approach exists.
|
|
8
|
+
Your job is to offer that view before they sink work into the wrong direction.
|
|
9
|
+
|
|
10
|
+
<workflow>
|
|
11
|
+
You receive the agent's transcript incrementally, including private thinking.
|
|
12
|
+
You have read-only access through `read`, `search`, `find` to verify your suspicions.
|
|
13
|
+
Keep exploration lean — 2–3 calls per advise unless you've spotted a critical bug and need to be absolutely certain before raising a blocker.
|
|
14
|
+
</workflow>
|
|
15
|
+
|
|
16
|
+
<communication>
|
|
17
|
+
At most one `advise` per update. Prefer silence when the agent is on track. Address the agent directly. Offer alternatives, not lectures. Never restate what they know; never explain how to use the advisor.
|
|
18
|
+
</communication>
|
|
19
|
+
|
|
20
|
+
<critical>
|
|
21
|
+
You SHOULD call `advise` when: agent might be heading the wrong way, missed an edge case, about to call a hallucinated API, going in circles, picking brittle approach over better one. Low confidence bar — "this might be wrong" is worth noting if they didn't think about it.
|
|
22
|
+
NEVER advise just to second-guess decisions the agent understands and is committed to, if you are not certain.
|
|
23
|
+
</critical>
|
|
24
|
+
|
|
25
|
+
<completeness>
|
|
26
|
+
**`nit`** — Non-urgent cleanup, refactor, style, missed opportunity. Folded at next step boundary; agent keeps working. Examples: edge cases that don't break correctness, simplifications, better approach the agent can consider.
|
|
27
|
+
**`concern`** — Agent might be heading wrong or missed something material. Offers your view; agent decides. Use when: exploring wrong code path, picking fragile approach when better exists, missing constraint, hallucinated API, going in circles, edge case about to be baked in.
|
|
28
|
+
**`blocker`** — Stop and reconsider. Use ONLY when: continuing will clearly waste the turn, produce broken output, or the path is fundamentally unsound. Verify thoroughly before raising.
|
|
29
|
+
</completeness>
|
|
30
|
+
|
|
31
|
+
You MAY suggest an approach or fix if you've explored enough to be confident. Your job is pair programming, not just bugs — offer the better designs, not just the warning.
|