@oh-my-pi/pi-coding-agent 16.0.11 → 16.1.1
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 +36 -0
- package/dist/cli.js +3166 -3202
- package/dist/types/config/settings-schema.d.ts +40 -39
- package/dist/types/lsp/types.d.ts +5 -3
- package/dist/types/modes/components/__tests__/skill-message.test.d.ts +1 -0
- package/dist/types/modes/components/assistant-message.d.ts +8 -0
- package/dist/types/modes/components/cache-invalidation-marker.d.ts +39 -0
- package/dist/types/modes/components/compaction-summary-message.d.ts +14 -1
- package/dist/types/modes/components/index.d.ts +0 -1
- package/dist/types/modes/components/message-frame.d.ts +6 -4
- package/dist/types/modes/interactive-mode.d.ts +2 -1
- package/dist/types/modes/theme/theme.d.ts +7 -1
- package/dist/types/modes/types.d.ts +7 -1
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +20 -1
- package/dist/types/session/session-context.d.ts +7 -0
- package/dist/types/session/session-dump-format.d.ts +1 -0
- package/dist/types/session/tool-choice-queue.d.ts +14 -0
- package/dist/types/system-prompt.d.ts +3 -3
- package/dist/types/tools/index.d.ts +4 -0
- package/dist/types/tools/resolve.d.ts +15 -5
- package/package.json +12 -12
- package/src/config/settings-schema.ts +48 -39
- package/src/config/settings.ts +40 -0
- package/src/debug/log-viewer.ts +4 -4
- package/src/debug/raw-sse.ts +4 -4
- package/src/edit/renderer.ts +2 -2
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/lsp/client.ts +9 -9
- package/src/lsp/render.ts +7 -7
- package/src/lsp/types.ts +6 -3
- package/src/modes/components/__tests__/skill-message.test.ts +92 -0
- package/src/modes/components/agent-dashboard.ts +1 -1
- package/src/modes/components/assistant-message.ts +21 -0
- package/src/modes/components/cache-invalidation-marker.ts +94 -0
- package/src/modes/components/chat-transcript-builder.ts +16 -2
- package/src/modes/components/compaction-summary-message.ts +29 -1
- package/src/modes/components/custom-message.ts +4 -1
- package/src/modes/components/dynamic-border.ts +1 -1
- package/src/modes/components/extensions/extension-dashboard.ts +1 -1
- package/src/modes/components/extensions/inspector-panel.ts +5 -5
- package/src/modes/components/hook-selector.ts +2 -2
- package/src/modes/components/index.ts +0 -1
- package/src/modes/components/message-frame.ts +10 -6
- package/src/modes/components/model-selector.ts +2 -2
- package/src/modes/components/overlay-box.ts +10 -9
- package/src/modes/components/settings-defs.ts +7 -0
- package/src/modes/components/skill-message.ts +39 -19
- package/src/modes/components/tiny-title-download-progress.ts +1 -1
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/event-controller.ts +14 -0
- package/src/modes/controllers/selector-controller.ts +7 -0
- package/src/modes/interactive-mode.ts +9 -1
- package/src/modes/theme/theme.ts +14 -0
- package/src/modes/types.ts +7 -1
- package/src/modes/utils/ui-helpers.ts +20 -2
- package/src/prompts/steering/user-interjection.md +3 -4
- package/src/sdk.ts +8 -6
- package/src/session/agent-session.ts +96 -23
- package/src/session/messages.ts +7 -9
- package/src/session/session-context.ts +54 -7
- package/src/session/session-dump-format.ts +3 -1
- package/src/session/snapcompact-inline.ts +2 -2
- package/src/session/tool-choice-queue.ts +59 -0
- package/src/system-prompt.ts +10 -9
- package/src/tools/bash-interactive.ts +4 -4
- package/src/tools/index.ts +4 -0
- package/src/tools/resolve.ts +66 -41
- package/src/tui/output-block.ts +9 -9
- package/dist/types/modes/components/branch-summary-message.d.ts +0 -13
- package/src/modes/components/branch-summary-message.ts +0 -46
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared box-drawing chrome for fullscreen overlays (the `/copy` picker, the
|
|
3
|
-
* plan-review overlay, …). Every helper paints with `theme.
|
|
4
|
-
* the `border`/`accent` theme
|
|
3
|
+
* plan-review overlay, …). Every helper paints with `theme.boxRound` glyphs
|
|
4
|
+
* (rounded corners, sharp tee/cross junctions) and the `border`/`accent` theme
|
|
5
|
+
* colors so all outlined overlays read identically.
|
|
5
6
|
*/
|
|
6
7
|
import { padding, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
7
8
|
import { theme } from "../theme/theme";
|
|
@@ -23,7 +24,7 @@ function paint(s: string): string {
|
|
|
23
24
|
|
|
24
25
|
/** Top border with an optional accent-colored title inset into the rule. */
|
|
25
26
|
export function topBorder(width: number, title: string): string {
|
|
26
|
-
const box = theme.
|
|
27
|
+
const box = theme.boxRound;
|
|
27
28
|
const inner = Math.max(0, width - 2);
|
|
28
29
|
if (!title) return paint(box.topLeft + box.horizontal.repeat(inner) + box.topRight);
|
|
29
30
|
const shown = truncateToWidth(` ${title} `, Math.max(0, inner - 2));
|
|
@@ -37,18 +38,18 @@ export function topBorder(width: number, title: string): string {
|
|
|
37
38
|
|
|
38
39
|
/** A horizontal rule with left/right tees, splitting overlay sections. */
|
|
39
40
|
export function divider(width: number): string {
|
|
40
|
-
const box = theme.
|
|
41
|
+
const box = theme.boxRound;
|
|
41
42
|
return paint(box.teeRight + box.horizontal.repeat(Math.max(0, width - 2)) + box.teeLeft);
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export function bottomBorder(width: number): string {
|
|
45
|
-
const box = theme.
|
|
46
|
+
const box = theme.boxRound;
|
|
46
47
|
return paint(box.bottomLeft + box.horizontal.repeat(Math.max(0, width - 2)) + box.bottomRight);
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
/** Wrap pre-styled content in vertical borders with single-column insets. */
|
|
50
51
|
export function row(content: string, width: number): string {
|
|
51
|
-
const box = theme.
|
|
52
|
+
const box = theme.boxRound;
|
|
52
53
|
return `${paint(box.vertical)} ${fit(content, Math.max(0, width - 4))} ${paint(box.vertical)}`;
|
|
53
54
|
}
|
|
54
55
|
|
|
@@ -70,7 +71,7 @@ export function splitBodyWidth(width: number, sidebarWidth: number): number {
|
|
|
70
71
|
|
|
71
72
|
/** Top border carrying the title, split by a `┬` over the column divider. */
|
|
72
73
|
export function topBorderSplit(width: number, title: string, sidebarWidth: number): string {
|
|
73
|
-
const box = theme.
|
|
74
|
+
const box = theme.boxRound;
|
|
74
75
|
const dividerCol = splitDividerCol(sidebarWidth);
|
|
75
76
|
const leftLen = Math.max(0, dividerCol - 1);
|
|
76
77
|
const rightLen = Math.max(0, width - 2 - dividerCol);
|
|
@@ -90,7 +91,7 @@ export function topBorderSplit(width: number, title: string, sidebarWidth: numbe
|
|
|
90
91
|
|
|
91
92
|
/** Section rule that closes the sidebar column with a `┴` over the divider. */
|
|
92
93
|
export function dividerSplit(width: number, sidebarWidth: number): string {
|
|
93
|
-
const box = theme.
|
|
94
|
+
const box = theme.boxRound;
|
|
94
95
|
const dividerCol = splitDividerCol(sidebarWidth);
|
|
95
96
|
const leftLen = Math.max(0, dividerCol - 1);
|
|
96
97
|
const rightLen = Math.max(0, width - 2 - dividerCol);
|
|
@@ -101,7 +102,7 @@ export function dividerSplit(width: number, sidebarWidth: number): string {
|
|
|
101
102
|
|
|
102
103
|
/** A two-column content row: `│ sidebar │ body │`, each inset by one column. */
|
|
103
104
|
export function splitRow(sidebar: string, body: string, width: number, sidebarWidth: number): string {
|
|
104
|
-
const box = theme.
|
|
105
|
+
const box = theme.boxRound;
|
|
105
106
|
const bodyWidth = splitBodyWidth(width, sidebarWidth);
|
|
106
107
|
const bar = paint(box.vertical);
|
|
107
108
|
return `${bar} ${fit(sidebar, sidebarWidth)} ${bar} ${fit(body, bodyWidth)} ${bar}`;
|
|
@@ -76,6 +76,13 @@ export type SettingDef = BooleanSettingDef | EnumSettingDef | SubmenuSettingDef
|
|
|
76
76
|
|
|
77
77
|
const CONDITIONS: Record<string, () => boolean> = {
|
|
78
78
|
hasImageProtocol: () => !!TERMINAL.imageProtocol,
|
|
79
|
+
advisorEnabled: () => {
|
|
80
|
+
try {
|
|
81
|
+
return Settings.instance.get("advisor.enabled") === true;
|
|
82
|
+
} catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
},
|
|
79
86
|
hindsightActive: () => {
|
|
80
87
|
try {
|
|
81
88
|
return Settings.instance.get("memory.backend") === "hindsight";
|
|
@@ -3,6 +3,8 @@ import type { Component } from "@oh-my-pi/pi-tui";
|
|
|
3
3
|
import { Box, Container, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
|
5
5
|
import type { CustomMessage, SkillPromptDetails } from "../../session/messages";
|
|
6
|
+
import { shortenPath } from "../../tools/render-utils";
|
|
7
|
+
import { fileHyperlink } from "../../tui";
|
|
6
8
|
|
|
7
9
|
export class SkillMessageComponent extends Container {
|
|
8
10
|
#box: Box;
|
|
@@ -38,25 +40,26 @@ export class SkillMessageComponent extends Container {
|
|
|
38
40
|
this.removeChild(this.#box);
|
|
39
41
|
this.addChild(this.#box);
|
|
40
42
|
this.#box.clear();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
this.#box.addChild(new Text(label, 0, 0));
|
|
44
|
-
this.#box.addChild(new Spacer(1));
|
|
43
|
+
// Re-read symbols every rebuild so a runtime theme/preset switch refreshes the outline.
|
|
44
|
+
this.#box.setBorder({ chars: theme.boxRound, color: t => theme.fg("borderMuted", t) });
|
|
45
45
|
|
|
46
46
|
const details = this.message.details;
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
);
|
|
47
|
+
const name = details?.name?.trim() || "unknown";
|
|
48
|
+
// Collapse args to one line: a stray newline/tab in user-supplied args would split the header.
|
|
49
|
+
const args = details?.args?.replace(/\s+/g, " ").trim() ?? "";
|
|
50
|
+
|
|
51
|
+
// Header: icon-tag + skill name, with the invocation args trailing dimmed.
|
|
52
|
+
const tag = theme.fg("customMessageLabel", theme.bold(`${theme.icon.extensionSkill} skill`));
|
|
53
|
+
let header = `${tag} ${theme.fg("customMessageText", theme.bold(name))}`;
|
|
54
|
+
if (args) {
|
|
55
|
+
header += ` ${theme.fg("dim", args)}`;
|
|
56
|
+
}
|
|
57
|
+
this.#box.addChild(new Text(header, 0, 0));
|
|
58
|
+
|
|
59
|
+
const meta = this.#metaLine(details);
|
|
60
|
+
if (meta) {
|
|
61
|
+
this.#box.addChild(new Text(meta, 0, 0));
|
|
62
|
+
}
|
|
60
63
|
|
|
61
64
|
if (!this.#expanded) {
|
|
62
65
|
return;
|
|
@@ -68,8 +71,7 @@ export class SkillMessageComponent extends Container {
|
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
this.#box.addChild(new Spacer(1));
|
|
71
|
-
|
|
72
|
-
this.#box.addChild(new Text(promptHeader, 0, 0));
|
|
74
|
+
this.#box.addChild(new Text(theme.fg("muted", "prompt"), 0, 0));
|
|
73
75
|
this.#box.addChild(new Spacer(1));
|
|
74
76
|
|
|
75
77
|
this.#contentComponent = new Markdown(text, 0, 0, getMarkdownTheme(), {
|
|
@@ -78,6 +80,24 @@ export class SkillMessageComponent extends Container {
|
|
|
78
80
|
this.#box.addChild(this.#contentComponent);
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
/** Sub-line under the header: home-shortened (clickable) accent path · muted prompt size. */
|
|
84
|
+
#metaLine(details: SkillPromptDetails | undefined): string | undefined {
|
|
85
|
+
const parts: string[] = [];
|
|
86
|
+
|
|
87
|
+
const filePath = details?.path;
|
|
88
|
+
if (filePath) {
|
|
89
|
+
parts.push(fileHyperlink(filePath, theme.fg("accent", shortenPath(filePath)), { line: 1 }));
|
|
90
|
+
}
|
|
91
|
+
if (typeof details?.lineCount === "number") {
|
|
92
|
+
parts.push(theme.fg("muted", `${details.lineCount} ${details.lineCount === 1 ? "line" : "lines"}`));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (parts.length === 0) {
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
return ` ${parts.join(theme.fg("muted", theme.sep.dot))}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
81
101
|
#extractText(): string {
|
|
82
102
|
if (typeof this.message.content === "string") {
|
|
83
103
|
return this.message.content;
|
|
@@ -74,7 +74,7 @@ export class TinyTitleDownloadProgressComponent implements Component {
|
|
|
74
74
|
render(width: number): readonly string[] {
|
|
75
75
|
width = Math.max(1, width);
|
|
76
76
|
const spec = getTinyTitleModelSpec(this.#modelKey);
|
|
77
|
-
const border = theme.fg("border", theme.
|
|
77
|
+
const border = theme.fg("border", theme.boxRound.horizontal.repeat(width));
|
|
78
78
|
const status = statusLabel(this.#event);
|
|
79
79
|
const file = currentFile(this.#event);
|
|
80
80
|
const pct =
|
|
@@ -308,7 +308,7 @@ export class WelcomeComponent implements Component {
|
|
|
308
308
|
}
|
|
309
309
|
// Bottom border
|
|
310
310
|
if (showRightColumn) {
|
|
311
|
-
lines.push(bl + h.repeat(leftCol) + theme.fg("dim", theme.
|
|
311
|
+
lines.push(bl + h.repeat(leftCol) + theme.fg("dim", theme.boxRound.teeUp) + h.repeat(rightCol) + br);
|
|
312
312
|
} else {
|
|
313
313
|
lines.push(bl + h.repeat(leftCol) + br);
|
|
314
314
|
}
|
|
@@ -5,6 +5,7 @@ import { extractTextContent } from "../../commit/utils";
|
|
|
5
5
|
import { settings } from "../../config/settings";
|
|
6
6
|
import { getFileSnapshotStore } from "../../edit/file-snapshot-store";
|
|
7
7
|
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
|
8
|
+
import { detectCacheInvalidation } from "../../modes/components/cache-invalidation-marker";
|
|
8
9
|
import {
|
|
9
10
|
ReadToolGroupComponent,
|
|
10
11
|
readArgsHaveTarget,
|
|
@@ -659,6 +660,16 @@ export class EventController {
|
|
|
659
660
|
// waiting poll cannot be displaced anymore — freeze it in place.
|
|
660
661
|
this.#resolveDisplaceablePoll();
|
|
661
662
|
}
|
|
663
|
+
// Surface a prompt-cache invalidation: if the previous turn cached a
|
|
664
|
+
// meaningful prefix and this request read none of it back, flag the turn.
|
|
665
|
+
const usage = event.message.usage;
|
|
666
|
+
if (usage.cacheRead + usage.cacheWrite + usage.input > 0) {
|
|
667
|
+
if (settings.get("display.cacheMissMarker")) {
|
|
668
|
+
const invalidation = detectCacheInvalidation(this.ctx.lastAssistantUsage, usage);
|
|
669
|
+
if (invalidation) this.ctx.streamingComponent.setCacheInvalidation(invalidation);
|
|
670
|
+
}
|
|
671
|
+
this.ctx.lastAssistantUsage = usage;
|
|
672
|
+
}
|
|
662
673
|
this.#lastAssistantComponent = this.ctx.streamingComponent;
|
|
663
674
|
this.#lastAssistantComponent.markTranscriptBlockFinalized();
|
|
664
675
|
if (settings.get("display.showTokenUsage")) {
|
|
@@ -969,12 +980,14 @@ export class EventController {
|
|
|
969
980
|
}
|
|
970
981
|
this.ctx.showWarning(event.errorMessage);
|
|
971
982
|
} else if (!event.skipped) {
|
|
983
|
+
this.ctx.lastAssistantUsage = undefined;
|
|
972
984
|
this.ctx.rebuildChatFromMessages();
|
|
973
985
|
this.ctx.statusLine.invalidate();
|
|
974
986
|
this.ctx.updateEditorTopBorder();
|
|
975
987
|
this.ctx.showStatus("Auto-shake completed");
|
|
976
988
|
}
|
|
977
989
|
} else if (event.result) {
|
|
990
|
+
this.ctx.lastAssistantUsage = undefined;
|
|
978
991
|
this.ctx.rebuildChatFromMessages();
|
|
979
992
|
this.ctx.statusLine.invalidate();
|
|
980
993
|
this.ctx.updateEditorTopBorder();
|
|
@@ -982,6 +995,7 @@ export class EventController {
|
|
|
982
995
|
this.ctx.showWarning(event.errorMessage);
|
|
983
996
|
} else if (isHandoffAction) {
|
|
984
997
|
this.ctx.chatContainer.clear();
|
|
998
|
+
this.ctx.lastAssistantUsage = undefined;
|
|
985
999
|
this.ctx.rebuildChatFromMessages();
|
|
986
1000
|
this.ctx.statusLine.invalidate();
|
|
987
1001
|
this.ctx.updateEditorTopBorder();
|
|
@@ -327,6 +327,13 @@ export class SelectorController {
|
|
|
327
327
|
// InputController.toggleThinkingBlockVisibility).
|
|
328
328
|
this.ctx.ui.resetDisplay();
|
|
329
329
|
break;
|
|
330
|
+
case "display.cacheMissMarker":
|
|
331
|
+
// Rebuild re-runs the usage-based detection under the new setting so
|
|
332
|
+
// markers appear/disappear; full reset retires any already committed
|
|
333
|
+
// to native scrollback (mirrors hideThinking).
|
|
334
|
+
this.ctx.rebuildChatFromMessages();
|
|
335
|
+
this.ctx.ui.resetDisplay();
|
|
336
|
+
break;
|
|
330
337
|
case "tui.tight":
|
|
331
338
|
setTuiTight(value as boolean);
|
|
332
339
|
this.ctx.ui.invalidate();
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
ThinkingLevel,
|
|
13
13
|
} from "@oh-my-pi/pi-agent-core";
|
|
14
14
|
import type { CompactionOutcome } from "@oh-my-pi/pi-agent-core/compaction";
|
|
15
|
-
import type { AssistantMessage, ImageContent, Message, Model, UsageReport } from "@oh-my-pi/pi-ai";
|
|
15
|
+
import type { AssistantMessage, ImageContent, Message, Model, Usage, UsageReport } from "@oh-my-pi/pi-ai";
|
|
16
16
|
import { modelsAreEqual } from "@oh-my-pi/pi-catalog/models";
|
|
17
17
|
import type {
|
|
18
18
|
Component,
|
|
@@ -412,6 +412,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
412
412
|
isPythonMode = false;
|
|
413
413
|
streamingComponent: AssistantMessageComponent | undefined = undefined;
|
|
414
414
|
streamingMessage: AssistantMessage | undefined = undefined;
|
|
415
|
+
lastAssistantUsage: Usage | undefined = undefined;
|
|
415
416
|
loadingAnimation: Loader | undefined = undefined;
|
|
416
417
|
autoCompactionLoader: Loader | undefined = undefined;
|
|
417
418
|
retryLoader: Loader | undefined = undefined;
|
|
@@ -512,6 +513,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
512
513
|
this.compactionQueuedMessages = [];
|
|
513
514
|
this.streamingComponent = undefined;
|
|
514
515
|
this.streamingMessage = undefined;
|
|
516
|
+
this.lastAssistantUsage = undefined;
|
|
515
517
|
this.pendingTools.clear();
|
|
516
518
|
}
|
|
517
519
|
readonly #uiHelpers: UiHelpers;
|
|
@@ -1858,6 +1860,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1858
1860
|
this.#planModePreviousTools = previousTools;
|
|
1859
1861
|
this.planModePlanFilePath = planFilePath;
|
|
1860
1862
|
this.planModeEnabled = true;
|
|
1863
|
+
// Suppress cache-miss marker on the next turn: plan mode changes the system
|
|
1864
|
+
// prompt, which predictably invalidates the cache.
|
|
1865
|
+
this.lastAssistantUsage = undefined;
|
|
1861
1866
|
|
|
1862
1867
|
await this.session.setActiveToolsByName(uniquePlanTools);
|
|
1863
1868
|
this.session.setPlanModeState({
|
|
@@ -1975,6 +1980,9 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1975
1980
|
this.session.setStandingResolveHandler?.(null);
|
|
1976
1981
|
this.session.setPlanModeState(undefined);
|
|
1977
1982
|
this.planModeEnabled = false;
|
|
1983
|
+
// Suppress cache-miss marker on the next turn: plan exit changes the system
|
|
1984
|
+
// prompt, which predictably invalidates the cache.
|
|
1985
|
+
this.lastAssistantUsage = undefined;
|
|
1978
1986
|
this.planModePaused = options?.paused ?? false;
|
|
1979
1987
|
this.planModePlanFilePath = undefined;
|
|
1980
1988
|
this.#planModePreviousTools = undefined;
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -111,6 +111,7 @@ export type SymbolKey =
|
|
|
111
111
|
| "icon.agents"
|
|
112
112
|
| "icon.job"
|
|
113
113
|
| "icon.cache"
|
|
114
|
+
| "icon.cacheMiss"
|
|
114
115
|
| "icon.input"
|
|
115
116
|
| "icon.output"
|
|
116
117
|
| "icon.host"
|
|
@@ -310,6 +311,7 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
310
311
|
"icon.agents": "👥",
|
|
311
312
|
"icon.job": "⚙",
|
|
312
313
|
"icon.cache": "💾",
|
|
314
|
+
"icon.cacheMiss": "⊘",
|
|
313
315
|
"icon.input": "⤵",
|
|
314
316
|
"icon.output": "⤴",
|
|
315
317
|
"icon.host": "🖥",
|
|
@@ -579,6 +581,8 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
579
581
|
"icon.job": "\uf013",
|
|
580
582
|
// pick: | alt:
|
|
581
583
|
"icon.cache": "\uf1c0",
|
|
584
|
+
// pick: (fa-ban) | alt: ⊘
|
|
585
|
+
"icon.cacheMiss": "\uf05e",
|
|
582
586
|
// pick: | alt: →
|
|
583
587
|
"icon.input": "\uf090",
|
|
584
588
|
// pick: | alt: →
|
|
@@ -810,6 +814,7 @@ const ASCII_SYMBOLS: SymbolMap = {
|
|
|
810
814
|
"icon.agents": "AG",
|
|
811
815
|
"icon.job": "bg",
|
|
812
816
|
"icon.cache": "cache",
|
|
817
|
+
"icon.cacheMiss": "!",
|
|
813
818
|
"icon.input": "in:",
|
|
814
819
|
"icon.output": "out:",
|
|
815
820
|
"icon.host": "host",
|
|
@@ -1711,6 +1716,14 @@ export class Theme {
|
|
|
1711
1716
|
bottomRight: this.#symbols["boxRound.bottomRight"],
|
|
1712
1717
|
horizontal: this.#symbols["boxRound.horizontal"],
|
|
1713
1718
|
vertical: this.#symbols["boxRound.vertical"],
|
|
1719
|
+
// Junctions have no rounded Unicode variant, so a rounded box reuses the
|
|
1720
|
+
// sharp tee/cross glyphs. Sourcing them from the boxSharp.* tokens keeps a
|
|
1721
|
+
// theme's `boxSharp.tee*` overrides effective for rounded-box dividers.
|
|
1722
|
+
cross: this.#symbols["boxSharp.cross"],
|
|
1723
|
+
teeDown: this.#symbols["boxSharp.teeDown"],
|
|
1724
|
+
teeUp: this.#symbols["boxSharp.teeUp"],
|
|
1725
|
+
teeRight: this.#symbols["boxSharp.teeRight"],
|
|
1726
|
+
teeLeft: this.#symbols["boxSharp.teeLeft"],
|
|
1714
1727
|
};
|
|
1715
1728
|
}
|
|
1716
1729
|
|
|
@@ -1770,6 +1783,7 @@ export class Theme {
|
|
|
1770
1783
|
agents: this.#symbols["icon.agents"],
|
|
1771
1784
|
job: this.#symbols["icon.job"],
|
|
1772
1785
|
cache: this.#symbols["icon.cache"],
|
|
1786
|
+
cacheMiss: this.#symbols["icon.cacheMiss"],
|
|
1773
1787
|
input: this.#symbols["icon.input"],
|
|
1774
1788
|
output: this.#symbols["icon.output"],
|
|
1775
1789
|
host: this.#symbols["icon.host"],
|
package/src/modes/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { CompactionOutcome } from "@oh-my-pi/pi-agent-core/compaction";
|
|
3
|
-
import type { AssistantMessage, ImageContent, Message, UsageReport } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import type { AssistantMessage, ImageContent, Message, Usage, UsageReport } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import type { Component, Container, EditorTheme, Loader, Spacer, Text, TUI } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import type { CollabGuestLink } from "../collab/guest";
|
|
6
6
|
import type { CollabHost } from "../collab/host";
|
|
@@ -159,6 +159,12 @@ export interface InteractiveModeContext {
|
|
|
159
159
|
isPythonMode: boolean;
|
|
160
160
|
streamingComponent: AssistantMessageComponent | undefined;
|
|
161
161
|
streamingMessage: AssistantMessage | undefined;
|
|
162
|
+
/**
|
|
163
|
+
* Usage of the most recently rendered assistant turn, used to detect a
|
|
164
|
+
* prompt-cache invalidation on the next turn (cache footprint collapse).
|
|
165
|
+
* Reseeded by `renderSessionContext` on every rebuild/session switch.
|
|
166
|
+
*/
|
|
167
|
+
lastAssistantUsage: Usage | undefined;
|
|
162
168
|
loadingAnimation: Loader | undefined;
|
|
163
169
|
autoCompactionLoader: Loader | undefined;
|
|
164
170
|
retryLoader: Loader | undefined;
|
|
@@ -9,9 +9,10 @@ import { createAdvisorMessageCard } from "../../modes/components/advisor-message
|
|
|
9
9
|
import { AssistantMessageComponent } from "../../modes/components/assistant-message";
|
|
10
10
|
import { createBackgroundTanDispatchBlock } from "../../modes/components/background-tan-message";
|
|
11
11
|
import { BashExecutionComponent } from "../../modes/components/bash-execution";
|
|
12
|
-
import {
|
|
12
|
+
import { detectCacheInvalidation } from "../../modes/components/cache-invalidation-marker";
|
|
13
13
|
import { CollabPromptMessageComponent } from "../../modes/components/collab-prompt-message";
|
|
14
14
|
import {
|
|
15
|
+
BranchSummaryMessageComponent,
|
|
15
16
|
CompactionSummaryMessageComponent,
|
|
16
17
|
createHandoffSummaryMessageComponent,
|
|
17
18
|
} from "../../modes/components/compaction-summary-message";
|
|
@@ -358,6 +359,9 @@ export class UiHelpers {
|
|
|
358
359
|
): void {
|
|
359
360
|
// Preserved: message_start handler owns this lifecycle (see #783)
|
|
360
361
|
this.ctx.pendingTools.clear();
|
|
362
|
+
// Reseed the cache-invalidation baseline: this rebuild re-derives every
|
|
363
|
+
// turn's marker from usage, and the last turn becomes the live baseline.
|
|
364
|
+
this.ctx.lastAssistantUsage = undefined;
|
|
361
365
|
|
|
362
366
|
if (options.updateFooter) {
|
|
363
367
|
this.ctx.statusLine.invalidate();
|
|
@@ -399,13 +403,27 @@ export class UiHelpers {
|
|
|
399
403
|
// updateResult armed.
|
|
400
404
|
previous.seal();
|
|
401
405
|
};
|
|
402
|
-
|
|
406
|
+
const messages = sessionContext.messages;
|
|
407
|
+
const count = messages.length;
|
|
408
|
+
for (let i = 0; i < count; i++) {
|
|
409
|
+
const message = messages[i]!;
|
|
403
410
|
if (message.role !== "toolResult") flushPendingUsage();
|
|
404
411
|
// Assistant messages need special handling for tool calls
|
|
405
412
|
if (message.role === "assistant") {
|
|
406
413
|
this.ctx.addMessageToChat(message);
|
|
407
414
|
const lastChild = this.ctx.chatContainer.children[this.ctx.chatContainer.children.length - 1];
|
|
408
415
|
const assistantComponent = lastChild instanceof AssistantMessageComponent ? lastChild : undefined;
|
|
416
|
+
if (assistantComponent) {
|
|
417
|
+
const usage = message.usage;
|
|
418
|
+
const explained = sessionContext.cacheMissExplainedAt?.[i] ?? false;
|
|
419
|
+
if (this.ctx.settings.get("display.cacheMissMarker") && !explained) {
|
|
420
|
+
const invalidation = detectCacheInvalidation(this.ctx.lastAssistantUsage, usage);
|
|
421
|
+
if (invalidation) assistantComponent.setCacheInvalidation(invalidation);
|
|
422
|
+
}
|
|
423
|
+
if (usage.cacheRead + usage.cacheWrite + usage.input > 0) {
|
|
424
|
+
this.ctx.lastAssistantUsage = usage;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
409
427
|
const hasVisibleAssistantContent = message.content.some(
|
|
410
428
|
content =>
|
|
411
429
|
(content.type === "text" && canonicalizeMessage(content.text)) ||
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<user_interjection>
|
|
2
|
-
The user sent this message while you were working
|
|
3
|
-
priority and supersedes
|
|
4
|
-
|
|
5
|
-
now.
|
|
2
|
+
The user sent this message as an interjection while you were working. It takes
|
|
3
|
+
priority and supersedes earlier instructions wherever they conflict — re-read it
|
|
4
|
+
and make sure your current work reflects their intent.
|
|
6
5
|
|
|
7
6
|
<message>
|
|
8
7
|
{{message}}
|
package/src/sdk.ts
CHANGED
|
@@ -838,7 +838,7 @@ export interface BuildSystemPromptOptions {
|
|
|
838
838
|
contextFiles?: Array<{ path: string; content: string }>;
|
|
839
839
|
cwd?: string;
|
|
840
840
|
appendPrompt?: string;
|
|
841
|
-
|
|
841
|
+
inlineToolDescriptors?: boolean;
|
|
842
842
|
}
|
|
843
843
|
|
|
844
844
|
/**
|
|
@@ -853,7 +853,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
853
853
|
skills: options.skills,
|
|
854
854
|
contextFiles: options.contextFiles,
|
|
855
855
|
appendSystemPrompt: options.appendPrompt,
|
|
856
|
-
|
|
856
|
+
inlineToolDescriptors: options.inlineToolDescriptors,
|
|
857
857
|
});
|
|
858
858
|
}
|
|
859
859
|
|
|
@@ -2130,7 +2130,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2130
2130
|
emitEvent: event => cursorEventEmitter?.(event),
|
|
2131
2131
|
});
|
|
2132
2132
|
|
|
2133
|
-
const
|
|
2133
|
+
const inlineToolDescriptors = settings.get("inlineToolDescriptors");
|
|
2134
2134
|
const eagerTasks = settings.get("task.eager") !== "default";
|
|
2135
2135
|
const eagerTasksAlways = settings.get("task.eager") === "always";
|
|
2136
2136
|
const intentField = $flag("PI_INTENT_TRACING", settings.get("tools.intentTracing")) ? INTENT_FIELD : undefined;
|
|
@@ -2198,7 +2198,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2198
2198
|
}
|
|
2199
2199
|
appendPrompt = parts.join("\n\n");
|
|
2200
2200
|
}
|
|
2201
|
-
// Owned/in-band tool
|
|
2201
|
+
// Owned/in-band tool dialects (non-native) require the catalog as `# Tool:`
|
|
2202
2202
|
// sections; native tool calling lets the compact name list suffice.
|
|
2203
2203
|
const nativeTools = resolveDialect(settings.get("tools.format"), agent?.state.model ?? model) === undefined;
|
|
2204
2204
|
const defaultPrompt = await buildSystemPromptInternal({
|
|
@@ -2211,7 +2211,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2211
2211
|
alwaysApplyRules,
|
|
2212
2212
|
skillsSettings: settings.getGroup("skills"),
|
|
2213
2213
|
appendSystemPrompt: appendPrompt,
|
|
2214
|
-
|
|
2214
|
+
inlineToolDescriptors,
|
|
2215
2215
|
nativeTools,
|
|
2216
2216
|
intentField,
|
|
2217
2217
|
mcpDiscoveryMode: hasDiscoverableTools,
|
|
@@ -2536,9 +2536,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2536
2536
|
return result;
|
|
2537
2537
|
},
|
|
2538
2538
|
intentTracing: !!intentField,
|
|
2539
|
+
pruneToolDescriptions: inlineToolDescriptors,
|
|
2539
2540
|
dialect: resolveDialect(settings.get("tools.format"), model),
|
|
2540
2541
|
abortOnFabricatedToolResult: settings.get("tools.abortOnFabricatedResult"),
|
|
2541
|
-
getToolChoice: () => session?.
|
|
2542
|
+
getToolChoice: () => session?.nextToolChoiceDirective(),
|
|
2542
2543
|
telemetry: options.telemetry,
|
|
2543
2544
|
appendOnlyContext: model
|
|
2544
2545
|
? shouldEnableAppendOnlyContext(settings.get("provider.appendOnlyContext"), model)
|
|
@@ -2606,6 +2607,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2606
2607
|
session = new AgentSession({
|
|
2607
2608
|
advisorWatchdogPrompt,
|
|
2608
2609
|
agent,
|
|
2610
|
+
pruneToolDescriptions: inlineToolDescriptors,
|
|
2609
2611
|
thinkingLevel: autoThinking ? AUTO_THINKING : effectiveThinkingLevel,
|
|
2610
2612
|
sessionManager,
|
|
2611
2613
|
settings,
|