@oh-my-pi/pi-coding-agent 16.3.12 → 16.3.14
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 +25 -0
- package/dist/cli.js +3252 -3254
- package/dist/types/config/keybindings.d.ts +9 -4
- package/dist/types/config/settings.d.ts +1 -1
- package/dist/types/extensibility/extensions/types.d.ts +11 -2
- package/dist/types/mnemopi/state.d.ts +7 -3
- package/dist/types/modes/acp/acp-event-mapper.d.ts +1 -0
- package/dist/types/modes/components/read-tool-group.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +3 -1
- package/dist/types/modes/rpc/rpc-client.d.ts +11 -5
- package/dist/types/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/session/agent-session.d.ts +3 -4
- package/dist/types/tools/read.d.ts +1 -0
- package/dist/types/tools/renderers.d.ts +12 -5
- package/dist/types/tools/ssh.d.ts +4 -1
- package/dist/types/tools/write.d.ts +1 -0
- package/dist/types/utils/thinking-display.d.ts +5 -0
- package/package.json +12 -12
- package/src/config/keybindings.ts +62 -10
- package/src/config/model-registry.ts +85 -20
- package/src/config/settings.ts +48 -21
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/extensions/types.ts +13 -2
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/mnemopi/state.ts +19 -5
- package/src/modes/acp/acp-agent.ts +69 -8
- package/src/modes/acp/acp-event-mapper.ts +1 -1
- package/src/modes/components/read-tool-group.ts +5 -1
- package/src/modes/components/tool-execution.ts +28 -24
- package/src/modes/components/transcript-container.ts +33 -0
- package/src/modes/controllers/extension-ui-controller.test.ts +16 -0
- package/src/modes/controllers/extension-ui-controller.ts +1 -0
- package/src/modes/controllers/input-controller.ts +5 -55
- package/src/modes/interactive-mode.ts +43 -2
- package/src/modes/rpc/rpc-client.ts +42 -13
- package/src/modes/rpc/rpc-mode.ts +21 -19
- package/src/modes/types.ts +3 -0
- package/src/prompts/agents/plan.md +0 -1
- package/src/prompts/agents/reviewer.md +0 -1
- package/src/prompts/system/workflow-notice.md +5 -5
- package/src/prompts/tools/grep.md +2 -1
- package/src/prompts/tools/memory-edit.md +2 -0
- package/src/session/agent-session.ts +8 -5
- package/src/tools/grep.ts +58 -13
- package/src/tools/image-gen.ts +1 -1
- package/src/tools/memory-edit.ts +3 -1
- package/src/tools/read.ts +33 -13
- package/src/tools/renderers.ts +13 -5
- package/src/tools/ssh.ts +10 -3
- package/src/tools/tts.ts +1 -1
- package/src/tools/write.ts +26 -0
- package/src/utils/thinking-display.ts +74 -28
package/src/tools/write.ts
CHANGED
|
@@ -985,6 +985,24 @@ function countLines(text: string): number {
|
|
|
985
985
|
return text.split("\n").length;
|
|
986
986
|
}
|
|
987
987
|
|
|
988
|
+
/** Bounded newline scan: whether `text` spans more than `maxLines` lines.
|
|
989
|
+
* Runs on every live compose (the repaint predicate below), so it must not
|
|
990
|
+
* materialize the split the way `countLines` does. */
|
|
991
|
+
function exceedsLineCount(text: string, maxLines: number): boolean {
|
|
992
|
+
if (!text) return false;
|
|
993
|
+
let lines = 1;
|
|
994
|
+
for (let index = text.indexOf("\n"); index !== -1; index = text.indexOf("\n", index + 1)) {
|
|
995
|
+
if (++lines > maxLines) return true;
|
|
996
|
+
}
|
|
997
|
+
return false;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
function writeContentOf(args: unknown): string {
|
|
1001
|
+
if (args == null || typeof args !== "object" || !("content" in args)) return "";
|
|
1002
|
+
const content = args.content;
|
|
1003
|
+
return typeof content === "string" ? content : "";
|
|
1004
|
+
}
|
|
1005
|
+
|
|
988
1006
|
function formatLineCountSuffix(lineCount: number, uiTheme: Theme): string {
|
|
989
1007
|
if (lineCount <= 0) return "";
|
|
990
1008
|
return uiTheme.fg("dim", ` · ${lineCount} line${lineCount === 1 ? "" : "s"}`);
|
|
@@ -1218,4 +1236,12 @@ export const writeToolRenderer = {
|
|
|
1218
1236
|
});
|
|
1219
1237
|
},
|
|
1220
1238
|
mergeCallAndResult: true,
|
|
1239
|
+
// The collapsed pending preview follows the streaming edge with a tail
|
|
1240
|
+
// window once the content outgrows it (`… (N earlier lines)` + last rows);
|
|
1241
|
+
// the first partial result re-anchors the frame to the top of the file, so
|
|
1242
|
+
// tail rows already committed to viewport/native scrollback would survive
|
|
1243
|
+
// as stale content above the new frame without a full replay. Expanded and
|
|
1244
|
+
// short previews stay top-anchored and skip the (scrollback-wiping) reset.
|
|
1245
|
+
forceFirstResultViewportRepaint: (args: unknown, options: RenderResultOptions) =>
|
|
1246
|
+
!options.expanded && exceedsLineCount(writeContentOf(args), WRITE_STREAMING_PREVIEW_LINES),
|
|
1221
1247
|
};
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
|
|
3
|
-
// Single-
|
|
4
|
-
// the same growing thinking text is formatted up to three times (reveal
|
|
5
|
-
// reveal slice, component render); this collapses them to one
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// one active thinking block and never regresses (a miss recomputes
|
|
9
|
-
// before).
|
|
10
|
-
let
|
|
11
|
-
let
|
|
3
|
+
// Single-slot-per-mode memo for formatThinkingForDisplay. During a streaming
|
|
4
|
+
// tick the same growing thinking text is formatted up to three times (reveal
|
|
5
|
+
// count, reveal slice, component render); this collapses them to one
|
|
6
|
+
// computation. Prose and raw modes produce different output for the same text,
|
|
7
|
+
// so each mode keeps its own slot. One entry per mode is enough for the common
|
|
8
|
+
// case of one active thinking block and never regresses (a miss recomputes
|
|
9
|
+
// exactly as before).
|
|
10
|
+
let proseCacheKey = "";
|
|
11
|
+
let proseCacheValue = "";
|
|
12
|
+
let rawCacheKey = "";
|
|
13
|
+
let rawCacheValue = "";
|
|
12
14
|
|
|
13
15
|
export function canonicalizeMessage(text: string | null | undefined): string {
|
|
14
16
|
if (!text) return "";
|
|
@@ -22,9 +24,35 @@ export function canonicalizeMessage(text: string | null | undefined): string {
|
|
|
22
24
|
return "";
|
|
23
25
|
}
|
|
24
26
|
|
|
27
|
+
// gpt-5.x reasoning summaries pad every summary part with an empty HTML
|
|
28
|
+
// comment (`**Headline**\n\n<!-- -->`), streamed as a `<!--` delta followed by
|
|
29
|
+
// ` -->`. Comments with actual content are left untouched.
|
|
30
|
+
const EMPTY_COMMENT_RE = /^<!--\s*-->$/;
|
|
31
|
+
const OPEN_COMMENT_RE = /^<!--\s*$/;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Whether `line` is reasoning-summary comment noise: an empty HTML comment,
|
|
35
|
+
* or its still-unterminated `<!--` prefix on the last line while streaming.
|
|
36
|
+
*/
|
|
37
|
+
function isCommentNoise(line: string, isLastLine: boolean): boolean {
|
|
38
|
+
const trimmed = line.trim();
|
|
39
|
+
return EMPTY_COMMENT_RE.test(trimmed) || (isLastLine && OPEN_COMMENT_RE.test(trimmed));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Thinking text prepared for display. Both modes drop empty `<!-- -->`
|
|
44
|
+
* sentinel lines outside code fences (see {@link isCommentNoise}); prose-only
|
|
45
|
+
* mode additionally elides fenced code down to a trailing ellipsis.
|
|
46
|
+
*/
|
|
25
47
|
export function formatThinkingForDisplay(text: string, proseOnly: boolean): string {
|
|
26
|
-
if (!
|
|
27
|
-
|
|
48
|
+
if (!text) return text;
|
|
49
|
+
const hasComment = text.includes("<!--");
|
|
50
|
+
if (proseOnly) {
|
|
51
|
+
if (text === proseCacheKey) return proseCacheValue;
|
|
52
|
+
} else {
|
|
53
|
+
if (!hasComment) return text;
|
|
54
|
+
if (text === rawCacheKey) return rawCacheValue;
|
|
55
|
+
}
|
|
28
56
|
|
|
29
57
|
const lines = text.split("\n");
|
|
30
58
|
const resultLines: string[] = [];
|
|
@@ -56,22 +84,31 @@ export function formatThinkingForDisplay(text: string, proseOnly: boolean): stri
|
|
|
56
84
|
|
|
57
85
|
for (let i = 0; i < lines.length; i++) {
|
|
58
86
|
const line = lines[i]!;
|
|
59
|
-
const open = FENCE.exec(line);
|
|
60
87
|
|
|
61
88
|
if (inFence) {
|
|
89
|
+
const close = FENCE.exec(line);
|
|
62
90
|
// A closing fence is the same char, at least as long, with nothing else on the line.
|
|
63
91
|
if (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
line.slice(
|
|
92
|
+
close &&
|
|
93
|
+
close[2]![0] === fenceChar &&
|
|
94
|
+
close[2]!.length >= fenceLen &&
|
|
95
|
+
line.slice(close[1]!.length + close[2]!.length).trim() === ""
|
|
68
96
|
) {
|
|
69
97
|
inFence = false;
|
|
70
98
|
fenceChar = "";
|
|
71
99
|
fenceLen = 0;
|
|
72
100
|
}
|
|
73
|
-
//
|
|
74
|
-
|
|
101
|
+
// Prose mode skips all fence lines; raw mode keeps them verbatim
|
|
102
|
+
// (comment markers inside fences are code, not noise).
|
|
103
|
+
if (!proseOnly) resultLines.push(line);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Drop the whole line so `**Headline**\n\n<!-- -->` leaves no blank tail.
|
|
108
|
+
if (hasComment && isCommentNoise(line, i === lines.length - 1)) continue;
|
|
109
|
+
|
|
110
|
+
const open = FENCE.exec(line);
|
|
111
|
+
if (open) {
|
|
75
112
|
const marker = open[2]!;
|
|
76
113
|
const ch = marker[0]!;
|
|
77
114
|
// A backtick fence's info string may not contain a backtick.
|
|
@@ -79,18 +116,25 @@ export function formatThinkingForDisplay(text: string, proseOnly: boolean): stri
|
|
|
79
116
|
inFence = true;
|
|
80
117
|
fenceChar = ch;
|
|
81
118
|
fenceLen = marker.length;
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
119
|
+
if (proseOnly) {
|
|
120
|
+
appendEllipsis();
|
|
121
|
+
} else {
|
|
122
|
+
resultLines.push(line);
|
|
123
|
+
}
|
|
124
|
+
continue;
|
|
85
125
|
}
|
|
86
|
-
} else {
|
|
87
|
-
resultLines.push(line);
|
|
88
126
|
}
|
|
127
|
+
resultLines.push(line);
|
|
89
128
|
}
|
|
90
129
|
|
|
91
130
|
const formatted = resultLines.join("\n");
|
|
92
|
-
|
|
93
|
-
|
|
131
|
+
if (proseOnly) {
|
|
132
|
+
proseCacheKey = text;
|
|
133
|
+
proseCacheValue = formatted;
|
|
134
|
+
} else {
|
|
135
|
+
rawCacheKey = text;
|
|
136
|
+
rawCacheValue = formatted;
|
|
137
|
+
}
|
|
94
138
|
return formatted;
|
|
95
139
|
}
|
|
96
140
|
|
|
@@ -99,9 +143,11 @@ export function hasDisplayableThinking(
|
|
|
99
143
|
text: string | null | undefined,
|
|
100
144
|
formattedText: string | null | undefined,
|
|
101
145
|
): boolean {
|
|
102
|
-
if (!text) return false;
|
|
103
|
-
|
|
104
|
-
|
|
146
|
+
if (!text || !formattedText) return false;
|
|
147
|
+
// Visibility keys off the formatted text: a block whose raw text is only
|
|
148
|
+
// comment noise (`<!-- -->\n`) formats to whitespace and stays hidden. The
|
|
149
|
+
// raw canonicalize check still hides dot/ellipsis-only placeholder blocks.
|
|
150
|
+
return formattedText.trim().length > 0 && canonicalizeMessage(text).length > 0;
|
|
105
151
|
}
|
|
106
152
|
|
|
107
153
|
/** Whether an assistant message contains thinking content the TUI can reveal. */
|