@oh-my-pi/pi-coding-agent 15.13.2 → 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 +62 -0
- package/dist/cli.js +587 -499
- 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 +75 -5
- package/dist/types/eval/js/context-manager.d.ts +15 -0
- 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 +4 -1
- package/dist/types/modes/types.d.ts +9 -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/unexpected-stop-classifier.d.ts +13 -0
- package/dist/types/session/yield-queue.d.ts +2 -0
- package/dist/types/stt/asr-client.d.ts +1 -1
- package/dist/types/tiny/title-client.d.ts +1 -1
- package/dist/types/tools/job.d.ts +1 -0
- package/dist/types/tools/path-utils.d.ts +1 -0
- package/dist/types/tools/report-tool-issue.d.ts +0 -1
- package/dist/types/tts/tts-client.d.ts +1 -1
- package/dist/types/utils/thinking-display.d.ts +1 -17
- 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/cli.ts +25 -12
- package/src/config/model-registry.ts +6 -2
- package/src/config/model-roles.ts +13 -1
- package/src/config/settings-schema.ts +67 -5
- package/src/eval/__tests__/agent-bridge.test.ts +106 -46
- package/src/eval/__tests__/js-context-manager.test.ts +12 -2
- package/src/eval/js/context-manager.ts +40 -3
- package/src/eval/js/worker-entry.ts +7 -0
- package/src/export/html/template.js +18 -22
- package/src/internal-urls/docs-index.generated.ts +8 -5
- package/src/main.ts +19 -5
- package/src/modes/acp/acp-agent.ts +2 -2
- package/src/modes/acp/acp-event-mapper.ts +2 -2
- package/src/modes/components/advisor-message.ts +99 -0
- package/src/modes/components/agent-hub.ts +38 -7
- package/src/modes/components/assistant-message.ts +110 -15
- package/src/modes/components/snapcompact-shape-preview-doc.md +2 -2
- package/src/modes/components/snapcompact-shape-preview.ts +2 -2
- package/src/modes/components/status-line/segments.ts +20 -7
- package/src/modes/components/tree-selector.ts +3 -2
- package/src/modes/controllers/command-controller.ts +69 -2
- package/src/modes/controllers/event-controller.ts +3 -3
- package/src/modes/controllers/input-controller.ts +7 -1
- package/src/modes/controllers/streaming-reveal.ts +4 -4
- package/src/modes/interactive-mode.ts +14 -2
- package/src/modes/types.ts +9 -1
- package/src/modes/utils/ui-helpers.ts +12 -3
- package/src/prompts/advisor/advise-tool.md +1 -0
- package/src/prompts/advisor/system.md +31 -0
- package/src/prompts/agents/oracle.md +0 -1
- package/src/prompts/agents/reviewer.md +0 -1
- package/src/prompts/system/unexpected-stop-classifier.md +17 -0
- package/src/prompts/system/unexpected-stop-retry.md +4 -0
- package/src/sdk.ts +52 -13
- package/src/session/agent-session.ts +722 -21
- package/src/session/session-dump-format.ts +15 -142
- package/src/session/session-history-format.ts +30 -11
- package/src/session/unexpected-stop-classifier.ts +129 -0
- package/src/session/yield-queue.ts +5 -1
- package/src/slash-commands/builtin-registry.ts +102 -4
- package/src/stt/asr-client.ts +1 -1
- package/src/system-prompt.ts +1 -1
- package/src/tiny/title-client.ts +1 -1
- package/src/tools/browser/tab-supervisor.ts +1 -1
- package/src/tools/browser/tab-worker-entry.ts +12 -4
- package/src/tools/job.ts +1 -0
- package/src/tools/path-utils.ts +33 -2
- package/src/tools/report-tool-issue.ts +2 -7
- package/src/tts/tts-client.ts +1 -1
- package/src/utils/thinking-display.ts +8 -34
- package/src/web/scrapers/docs-rs.ts +2 -3
|
@@ -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) {
|
|
@@ -22,7 +22,7 @@ import type { AgentSessionEvent } from "../../session/agent-session";
|
|
|
22
22
|
import { isSilentAbort, readQueueChipText, resolveAbortLabel } from "../../session/messages";
|
|
23
23
|
import type { ResolveToolDetails } from "../../tools/resolve";
|
|
24
24
|
import { vocalizer } from "../../tts/vocalizer";
|
|
25
|
-
import {
|
|
25
|
+
import { canonicalizeMessage } from "../../utils/thinking-display";
|
|
26
26
|
import { interruptHint } from "../shared";
|
|
27
27
|
import { StreamingRevealController } from "./streaming-reveal";
|
|
28
28
|
import { ToolArgsRevealController } from "./tool-args-reveal";
|
|
@@ -480,8 +480,8 @@ export class EventController {
|
|
|
480
480
|
|
|
481
481
|
const visibleBlockCount = this.ctx.streamingMessage.content.filter(
|
|
482
482
|
content =>
|
|
483
|
-
(content.type === "text" && content.text
|
|
484
|
-
(content.type === "thinking" &&
|
|
483
|
+
(content.type === "text" && canonicalizeMessage(content.text)) ||
|
|
484
|
+
(content.type === "thinking" && canonicalizeMessage(content.thinking)),
|
|
485
485
|
).length;
|
|
486
486
|
if (visibleBlockCount > this.#lastVisibleBlockCount) {
|
|
487
487
|
this.#resetReadGroup();
|
|
@@ -689,11 +689,17 @@ export class InputController {
|
|
|
689
689
|
this.ctx.pendingImages = [];
|
|
690
690
|
this.ctx.pendingImageLinks = [];
|
|
691
691
|
|
|
692
|
-
// Render user message immediately, then let session events catch up
|
|
692
|
+
// Render user message immediately, then let session events catch up.
|
|
693
|
+
// Tag the submission as "steer": this is a normal Enter the controller
|
|
694
|
+
// believed was idle, but a background turn can start in the gap before
|
|
695
|
+
// `submitInteractiveInput` dispatches it. Steering matches the
|
|
696
|
+
// streaming-branch Enter (above) and keeps the message from throwing
|
|
697
|
+
// AgentBusyError on that race.
|
|
693
698
|
const submission = this.ctx.startPendingSubmission({
|
|
694
699
|
text,
|
|
695
700
|
images,
|
|
696
701
|
imageLinks: inputImageLinks,
|
|
702
|
+
streamingBehavior: "steer",
|
|
697
703
|
});
|
|
698
704
|
|
|
699
705
|
this.ctx.onInputCallback(submission);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { getSegmenter } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { LRUCache } from "lru-cache/raw";
|
|
4
|
-
import {
|
|
4
|
+
import { canonicalizeMessage } from "../../utils/thinking-display";
|
|
5
5
|
import type { AssistantMessageComponent } from "../components/assistant-message";
|
|
6
6
|
|
|
7
7
|
export const STREAMING_REVEAL_FRAME_MS = 1000 / 30;
|
|
@@ -88,7 +88,7 @@ export function visibleUnits(message: AssistantMessage, hideThinking: boolean):
|
|
|
88
88
|
for (const block of message.content) {
|
|
89
89
|
if (block.type === "text") {
|
|
90
90
|
total += countGraphemes(block.text);
|
|
91
|
-
} else if (block.type === "thinking" && !hideThinking &&
|
|
91
|
+
} else if (block.type === "thinking" && !hideThinking && canonicalizeMessage(block.thinking)) {
|
|
92
92
|
total += countGraphemes(block.thinking);
|
|
93
93
|
}
|
|
94
94
|
}
|
|
@@ -129,7 +129,7 @@ export function buildDisplayMessage(
|
|
|
129
129
|
const units = countOf(i, block.text);
|
|
130
130
|
content.push(revealTextBlock(block, remaining, units));
|
|
131
131
|
remaining = Math.max(0, remaining - units);
|
|
132
|
-
} else if (block.type === "thinking" && !hideThinking &&
|
|
132
|
+
} else if (block.type === "thinking" && !hideThinking && canonicalizeMessage(block.thinking)) {
|
|
133
133
|
const units = countOf(i, block.thinking);
|
|
134
134
|
content.push(revealThinkingBlock(block, remaining, units));
|
|
135
135
|
remaining = Math.max(0, remaining - units);
|
|
@@ -231,7 +231,7 @@ export class StreamingRevealController {
|
|
|
231
231
|
const block = message.content[i]!;
|
|
232
232
|
if (block.type === "text") {
|
|
233
233
|
total += this.#unitCounter.count(i, block.text);
|
|
234
|
-
} else if (block.type === "thinking" && !this.#hideThinkingBlock &&
|
|
234
|
+
} else if (block.type === "thinking" && !this.#hideThinkingBlock && canonicalizeMessage(block.thinking)) {
|
|
235
235
|
total += this.#unitCounter.count(i, block.thinking);
|
|
236
236
|
}
|
|
237
237
|
}
|
|
@@ -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
|
);
|
|
@@ -1123,6 +1125,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1123
1125
|
imageLinks?: (string | undefined)[];
|
|
1124
1126
|
customType?: string;
|
|
1125
1127
|
display?: boolean;
|
|
1128
|
+
streamingBehavior?: "steer" | "followUp";
|
|
1126
1129
|
}): SubmittedUserInput {
|
|
1127
1130
|
const submission: SubmittedUserInput = {
|
|
1128
1131
|
text: input.text,
|
|
@@ -1130,6 +1133,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1130
1133
|
imageLinks: input.imageLinks,
|
|
1131
1134
|
customType: input.customType,
|
|
1132
1135
|
display: input.display,
|
|
1136
|
+
streamingBehavior: input.streamingBehavior,
|
|
1133
1137
|
cancelled: false,
|
|
1134
1138
|
started: false,
|
|
1135
1139
|
};
|
|
@@ -3331,8 +3335,12 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3331
3335
|
return this.#commandController.handleExportCommand(text);
|
|
3332
3336
|
}
|
|
3333
3337
|
|
|
3334
|
-
handleDumpCommand() {
|
|
3335
|
-
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);
|
|
3336
3344
|
}
|
|
3337
3345
|
|
|
3338
3346
|
handleDebugTranscriptCommand(): Promise<void> {
|
|
@@ -3351,6 +3359,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3351
3359
|
return this.#commandController.handleSessionCommand();
|
|
3352
3360
|
}
|
|
3353
3361
|
|
|
3362
|
+
handleAdvisorStatusCommand(): Promise<void> {
|
|
3363
|
+
return this.#commandController.handleAdvisorStatusCommand();
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3354
3366
|
handleJobsCommand(): Promise<void> {
|
|
3355
3367
|
return this.#commandController.handleJobsCommand();
|
|
3356
3368
|
}
|
package/src/modes/types.ts
CHANGED
|
@@ -54,6 +54,11 @@ export type SubmittedUserInput = {
|
|
|
54
54
|
* turn. Used by the `c`/`.` continue shortcut. */
|
|
55
55
|
synthetic?: boolean;
|
|
56
56
|
display?: boolean;
|
|
57
|
+
/** Queue intent if the session is (or becomes) busy when this submission is
|
|
58
|
+
* dispatched: "steer" (interrupt the active turn) or "followUp" (process after
|
|
59
|
+
* it). Normal user Enter carries "steer" to match the streaming-branch Enter;
|
|
60
|
+
* background/continuation submits omit it and default to "followUp". */
|
|
61
|
+
streamingBehavior?: "steer" | "followUp";
|
|
57
62
|
cancelled: boolean;
|
|
58
63
|
started: boolean;
|
|
59
64
|
};
|
|
@@ -222,6 +227,7 @@ export interface InteractiveModeContext {
|
|
|
222
227
|
imageLinks?: (string | undefined)[];
|
|
223
228
|
customType?: string;
|
|
224
229
|
display?: boolean;
|
|
230
|
+
streamingBehavior?: "steer" | "followUp";
|
|
225
231
|
}): SubmittedUserInput;
|
|
226
232
|
cancelPendingSubmission(): boolean;
|
|
227
233
|
markPendingSubmissionStarted(input: SubmittedUserInput): boolean;
|
|
@@ -264,13 +270,15 @@ export interface InteractiveModeContext {
|
|
|
264
270
|
handleShareCommand(): Promise<void>;
|
|
265
271
|
handleTodoCommand(args: string): Promise<void>;
|
|
266
272
|
handleSessionCommand(): Promise<void>;
|
|
273
|
+
handleAdvisorStatusCommand(): Promise<void>;
|
|
267
274
|
handleJobsCommand(): Promise<void>;
|
|
268
275
|
handleUsageCommand(reports?: UsageReport[] | null): Promise<void>;
|
|
269
276
|
handleChangelogCommand(showFull?: boolean): Promise<void>;
|
|
270
277
|
handleHotkeysCommand(): void;
|
|
271
278
|
handleToolsCommand(): void;
|
|
272
279
|
handleContextCommand(): void;
|
|
273
|
-
handleDumpCommand(): void;
|
|
280
|
+
handleDumpCommand(isRaw?: boolean): void;
|
|
281
|
+
handleAdvisorDumpCommand(isRaw?: boolean): void;
|
|
274
282
|
handleDebugTranscriptCommand(): Promise<void>;
|
|
275
283
|
handleClearCommand(): Promise<void>;
|
|
276
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";
|
|
@@ -45,7 +47,7 @@ import {
|
|
|
45
47
|
import type { SessionContext } from "../../session/session-context";
|
|
46
48
|
import { createIrcMessageCard } from "../../tools/irc";
|
|
47
49
|
import { formatBytes, formatDuration } from "../../tools/render-utils";
|
|
48
|
-
import {
|
|
50
|
+
import { canonicalizeMessage } from "../../utils/thinking-display";
|
|
49
51
|
|
|
50
52
|
type TextBlock = { type: "text"; text: string };
|
|
51
53
|
interface RenderInitialMessagesOptions {
|
|
@@ -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;
|
|
@@ -399,8 +408,8 @@ export class UiHelpers {
|
|
|
399
408
|
const assistantComponent = lastChild instanceof AssistantMessageComponent ? lastChild : undefined;
|
|
400
409
|
const hasVisibleAssistantContent = message.content.some(
|
|
401
410
|
content =>
|
|
402
|
-
(content.type === "text" && content.text
|
|
403
|
-
(content.type === "thinking" &&
|
|
411
|
+
(content.type === "text" && canonicalizeMessage(content.text)) ||
|
|
412
|
+
(content.type === "thinking" && canonicalizeMessage(content.thinking)),
|
|
404
413
|
);
|
|
405
414
|
if (hasVisibleAssistantContent) {
|
|
406
415
|
// Rebuild reconstructs immutable history; seal (not finalize) so the
|
|
@@ -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.
|
|
@@ -4,7 +4,6 @@ description: Wise senior engineer to consult or delegate work to — debugging,
|
|
|
4
4
|
spawns: explore
|
|
5
5
|
model: pi/slow
|
|
6
6
|
thinking-level: xhigh
|
|
7
|
-
blocking: true
|
|
8
7
|
---
|
|
9
8
|
|
|
10
9
|
You are the wise guy on the team — a senior engineer with deep judgment that other agents consult when they are stuck, uncertain, or need a second opinion. You also take direct delegation: if the caller hands you work, you do it, including reads, writes, edits, and running commands.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
You are checking whether an assistant message is an unexpected stop. A message is an unexpected stop if the assistant says it will take an action, continue working, or call a tool, but then ends without actually doing so.
|
|
2
|
+
|
|
3
|
+
Examples of unexpected stops:
|
|
4
|
+
- "I should do the same for the JS eval worker. Doing that now."
|
|
5
|
+
- "Let me run the tests next."
|
|
6
|
+
- "I'll fix that now."
|
|
7
|
+
- "Should I do that for you?"
|
|
8
|
+
|
|
9
|
+
Not an unexpected stop:
|
|
10
|
+
- "I've completed the task."
|
|
11
|
+
- "Is there anything else I can help with?"
|
|
12
|
+
- "The fix is done and tests pass."
|
|
13
|
+
|
|
14
|
+
Message:
|
|
15
|
+
{{message}}
|
|
16
|
+
|
|
17
|
+
Answer with a single word: YES if this is an unexpected stop, NO otherwise.
|
package/src/sdk.ts
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
type SimpleStreamOptions,
|
|
17
17
|
streamSimple,
|
|
18
18
|
} from "@oh-my-pi/pi-ai";
|
|
19
|
-
import type {
|
|
19
|
+
import type { Dialect } from "@oh-my-pi/pi-ai/dialect";
|
|
20
20
|
import {
|
|
21
21
|
getOpenAICodexTransportDetails,
|
|
22
22
|
prewarmOpenAICodexResponses,
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
prompt,
|
|
36
36
|
Snowflake,
|
|
37
37
|
} from "@oh-my-pi/pi-utils";
|
|
38
|
+
import { ADVISOR_READONLY_TOOL_NAMES, discoverWatchdogFiles } from "./advisor";
|
|
38
39
|
import { type AsyncJob, AsyncJobManager } from "./async";
|
|
39
40
|
import { AutoLearnController, buildAutoLearnInstructions } from "./autolearn/controller";
|
|
40
41
|
import { loadCapability } from "./capability";
|
|
@@ -551,12 +552,12 @@ export interface CreateAgentSessionResult {
|
|
|
551
552
|
eventBus: EventBus;
|
|
552
553
|
}
|
|
553
554
|
|
|
554
|
-
export type
|
|
555
|
+
export type DialectFormat = "auto" | "native" | Dialect;
|
|
555
556
|
|
|
556
|
-
export function
|
|
557
|
-
format:
|
|
557
|
+
export function resolveDialect(
|
|
558
|
+
format: DialectFormat,
|
|
558
559
|
model: Pick<Model, "supportsTools"> | undefined,
|
|
559
|
-
):
|
|
560
|
+
): Dialect | undefined {
|
|
560
561
|
if (format === "native") return undefined;
|
|
561
562
|
if (format === "auto") return model?.supportsTools === false ? "glm" : undefined;
|
|
562
563
|
return format;
|
|
@@ -1141,6 +1142,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1141
1142
|
? Promise.resolve(options.contextFiles)
|
|
1142
1143
|
: logger.time("discoverContextFiles", discoverContextFiles, cwd, agentDir);
|
|
1143
1144
|
contextFilesPromise.catch(() => {});
|
|
1145
|
+
const watchdogFilesPromise = logger.time("discoverWatchdogFiles", () => discoverWatchdogFiles(cwd, agentDir));
|
|
1146
|
+
watchdogFilesPromise.catch(() => {});
|
|
1144
1147
|
const promptTemplatesPromise = options.promptTemplates
|
|
1145
1148
|
? Promise.resolve(options.promptTemplates)
|
|
1146
1149
|
: logger.time("discoverPromptTemplates", discoverPromptTemplates, cwd, agentDir);
|
|
@@ -1370,9 +1373,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1370
1373
|
}
|
|
1371
1374
|
return result;
|
|
1372
1375
|
};
|
|
1373
|
-
const [contextFiles, resolvedWorkspaceTree] = await Promise.all([
|
|
1376
|
+
const [contextFiles, resolvedWorkspaceTree, watchdogFiles] = await Promise.all([
|
|
1374
1377
|
contextFilesPromise,
|
|
1375
1378
|
raceWithDeadline("buildWorkspaceTree", workspaceTreePromise),
|
|
1379
|
+
watchdogFilesPromise,
|
|
1376
1380
|
]);
|
|
1377
1381
|
|
|
1378
1382
|
let agent: Agent;
|
|
@@ -1608,8 +1612,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1608
1612
|
let startDeferredMCPDiscovery:
|
|
1609
1613
|
| ((liveSession: AgentSession, activation: DeferredMCPActivation) => void)
|
|
1610
1614
|
| undefined;
|
|
1615
|
+
const startupQuiet = settings.get("startup.quiet");
|
|
1611
1616
|
const onMCPConnecting = (serverNames: string[]) => {
|
|
1612
|
-
if (!options.hasUI || serverNames.length === 0) return;
|
|
1617
|
+
if (!options.hasUI || startupQuiet || serverNames.length === 0) return;
|
|
1613
1618
|
eventBus.emit(MCP_CONNECTING_EVENT_CHANNEL, { serverNames } satisfies McpConnectingEvent);
|
|
1614
1619
|
};
|
|
1615
1620
|
const mcpDiscoverOptions = {
|
|
@@ -2161,10 +2166,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2161
2166
|
}
|
|
2162
2167
|
appendPrompt = parts.join("\n\n");
|
|
2163
2168
|
}
|
|
2164
|
-
// Owned/in-band tool
|
|
2169
|
+
// Owned/in-band tool dialect (non-native) repeats the catalog as `# Tool:`
|
|
2165
2170
|
// sections; native tool calling lets the compact name list suffice.
|
|
2166
|
-
const nativeTools =
|
|
2167
|
-
resolveToolCallSyntax(settings.get("tools.format"), agent?.state.model ?? model) === undefined;
|
|
2171
|
+
const nativeTools = resolveDialect(settings.get("tools.format"), agent?.state.model ?? model) === undefined;
|
|
2168
2172
|
const defaultPrompt = await buildSystemPromptInternal({
|
|
2169
2173
|
cwd,
|
|
2170
2174
|
skills,
|
|
@@ -2506,7 +2510,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2506
2510
|
return result;
|
|
2507
2511
|
},
|
|
2508
2512
|
intentTracing: !!intentField,
|
|
2509
|
-
|
|
2513
|
+
dialect: resolveDialect(settings.get("tools.format"), model),
|
|
2510
2514
|
abortOnFabricatedToolResult: settings.get("tools.abortOnFabricatedResult"),
|
|
2511
2515
|
getToolChoice: () => session?.nextToolChoice(),
|
|
2512
2516
|
telemetry: options.telemetry,
|
|
@@ -2537,7 +2541,41 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2537
2541
|
}
|
|
2538
2542
|
}
|
|
2539
2543
|
|
|
2544
|
+
// Hard-isolated read-only toolset for the advisor (built unconditionally so
|
|
2545
|
+
// it can be toggled at runtime). Fresh ReadTool/SearchTool/FindTool bound to a
|
|
2546
|
+
// DISTINCT ToolSession so the advisor's investigative reads never touch the
|
|
2547
|
+
// primary's snapshot, seen-lines, conflict, or summary caches (all keyed on
|
|
2548
|
+
// session identity). `cwd` stays dynamic; edit/yield capabilities are off.
|
|
2549
|
+
const advisorToolSession: ToolSession = {
|
|
2550
|
+
...toolSession,
|
|
2551
|
+
get cwd() {
|
|
2552
|
+
return sessionManager.getCwd();
|
|
2553
|
+
},
|
|
2554
|
+
hasEditTool: false,
|
|
2555
|
+
requireYieldTool: false,
|
|
2556
|
+
conflictHistory: undefined,
|
|
2557
|
+
fileSnapshotStore: undefined,
|
|
2558
|
+
getSessionId: () => {
|
|
2559
|
+
const id = sessionManager.getSessionId?.();
|
|
2560
|
+
return id ? `${id}-advisor` : null;
|
|
2561
|
+
},
|
|
2562
|
+
getAgentId: () => "advisor",
|
|
2563
|
+
};
|
|
2564
|
+
const built = await Promise.all(
|
|
2565
|
+
[...ADVISOR_READONLY_TOOL_NAMES].map(name =>
|
|
2566
|
+
BUILTIN_TOOLS[name as keyof typeof BUILTIN_TOOLS](advisorToolSession),
|
|
2567
|
+
),
|
|
2568
|
+
);
|
|
2569
|
+
const advisorReadOnlyTools: Tool[] = built
|
|
2570
|
+
.filter((tool): tool is Tool => tool != null)
|
|
2571
|
+
.map(wrapToolWithMetaNotice);
|
|
2572
|
+
|
|
2573
|
+
let advisorWatchdogPrompt: string | undefined;
|
|
2574
|
+
if (watchdogFiles && watchdogFiles.length > 0) {
|
|
2575
|
+
advisorWatchdogPrompt = watchdogFiles.join("\n\n");
|
|
2576
|
+
}
|
|
2540
2577
|
session = new AgentSession({
|
|
2578
|
+
advisorWatchdogPrompt,
|
|
2541
2579
|
agent,
|
|
2542
2580
|
thinkingLevel: autoThinking ? AUTO_THINKING : effectiveThinkingLevel,
|
|
2543
2581
|
sessionManager,
|
|
@@ -2592,6 +2630,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2592
2630
|
agentKind,
|
|
2593
2631
|
providerSessionId: options.providerSessionId,
|
|
2594
2632
|
parentEvalSessionId: options.parentEvalSessionId,
|
|
2633
|
+
advisorReadOnlyTools,
|
|
2595
2634
|
});
|
|
2596
2635
|
hasSession = true;
|
|
2597
2636
|
if (asyncJobManager) {
|
|
@@ -2696,7 +2735,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2696
2735
|
type: "completed",
|
|
2697
2736
|
servers: result.servers,
|
|
2698
2737
|
};
|
|
2699
|
-
eventBus.emit(LSP_STARTUP_EVENT_CHANNEL, event);
|
|
2738
|
+
if (!startupQuiet) eventBus.emit(LSP_STARTUP_EVENT_CHANNEL, event);
|
|
2700
2739
|
} catch (error) {
|
|
2701
2740
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2702
2741
|
logger.warn("LSP server warmup failed", { cwd, error: errorMessage });
|
|
@@ -2708,7 +2747,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2708
2747
|
type: "failed",
|
|
2709
2748
|
error: errorMessage,
|
|
2710
2749
|
};
|
|
2711
|
-
eventBus.emit(LSP_STARTUP_EVENT_CHANNEL, event);
|
|
2750
|
+
if (!startupQuiet) eventBus.emit(LSP_STARTUP_EVENT_CHANNEL, event);
|
|
2712
2751
|
}
|
|
2713
2752
|
})();
|
|
2714
2753
|
}
|