@oh-my-pi/pi-coding-agent 11.3.0 → 11.4.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 +23 -3
- package/package.json +7 -7
- package/src/modes/components/diff.ts +40 -6
- package/src/patch/applicator.ts +104 -1
- package/src/patch/shared.ts +5 -4
- package/src/prompts/system/plan-mode-active.md +6 -6
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/tools/ask.md +2 -2
- package/src/prompts/tools/gemini-image.md +2 -2
- package/src/prompts/tools/lsp.md +2 -2
- package/src/prompts/tools/patch.md +1 -1
- package/src/prompts/tools/python.md +3 -3
- package/src/prompts/tools/task.md +1 -1
- package/src/prompts/tools/todo-write.md +2 -2
- package/src/prompts/tools/web-search.md +2 -2
- package/src/prompts/tools/write.md +2 -5
- package/src/session/agent-session.ts +21 -12
- package/src/tools/bash.ts +5 -3
- package/src/tools/write.ts +3 -2
- package/src/tui/code-cell.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [11.4.1] - 2026-02-06
|
|
6
|
+
### Fixed
|
|
7
|
+
|
|
8
|
+
- Fixed tab character display in error messages and bash tool output by properly replacing tabs with spaces
|
|
9
|
+
|
|
10
|
+
## [11.4.0] - 2026-02-06
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Visualize leading whitespace (indentation) in diff output with dim glyphs—tabs display as `→` and spaces as `·` for improved readability
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Fixed patch applicator to correctly handle context-only hunks (pure context lines between @@ markers) without altering indentation in tab-indented files
|
|
19
|
+
- Fixed indentation conversion logic to infer tab width from space-to-tab patterns using linear regression (ax+b model) when pattern uses spaces and actual file uses tabs
|
|
20
|
+
- Fixed tab character rendering in tool output previews and code cell displays, ensuring tabs are properly converted to spaces for consistent terminal display
|
|
21
|
+
- Fixed `newSession()` to properly await session manager operations, ensuring new session is fully initialized before returning
|
|
22
|
+
- Fixed session formatting to use XML structure for tools and tool invocations instead of YAML, improving compatibility with structured output parsing
|
|
23
|
+
|
|
5
24
|
## [11.3.0] - 2026-02-06
|
|
6
25
|
|
|
7
26
|
### Added
|
|
@@ -109,6 +128,7 @@
|
|
|
109
128
|
- Fixed CLI invocation with flags only (e.g. `pi --model=codex`) to route to the default command instead of erroring
|
|
110
129
|
|
|
111
130
|
## [11.2.0] - 2026-02-05
|
|
131
|
+
|
|
112
132
|
### Added
|
|
113
133
|
|
|
114
134
|
- Added `omp commit` command to generate commit messages and update changelogs with `--push`, `--dry-run`, `--no-changelog`, and model override flags
|
|
@@ -1080,7 +1100,7 @@
|
|
|
1080
1100
|
- Updated all tools to use structured metadata instead of inline notices for truncation, limits, and diagnostics
|
|
1081
1101
|
- Replaced manual error formatting with ToolError.render() and standardized error handling
|
|
1082
1102
|
- Enhanced bash and python executors to save full output as artifacts when truncated
|
|
1083
|
-
- Improved abort signal handling across
|
|
1103
|
+
- Improved abort signal handling across <caution>ith consistent ToolAbortError
|
|
1084
1104
|
- Renamed task parameter from `vars` to `args` throughout task tool interface and updated template rendering to support built-in `{{id}}` and `{{description}}` placeholders
|
|
1085
1105
|
- Simplified todo-write tool by removing active_form parameter, using single content field for task descriptions
|
|
1086
1106
|
- Updated system prompt structure with `<important>` and `<avoid>` tags, clearer critical sections, and standardized whitespace handling
|
|
@@ -1808,8 +1828,8 @@
|
|
|
1808
1828
|
|
|
1809
1829
|
- Fixed editor border rendering glitch after canceling slash command autocomplete
|
|
1810
1830
|
- Fixed login/logout credential path message to reference agent.db
|
|
1811
|
-
|
|
1812
|
-
|
|
1831
|
+
- Removed legacy auth.json file—credentials are stored exclusively in agent.db
|
|
1832
|
+
- Removed legacy auth.json file—credentials are stored exclusively in agent.db
|
|
1813
1833
|
|
|
1814
1834
|
## [4.2.0] - 2026-01-10
|
|
1815
1835
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.4.1",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -90,12 +90,12 @@
|
|
|
90
90
|
"@mozilla/readability": "0.6.0",
|
|
91
91
|
"@oclif/core": "^4.8.0",
|
|
92
92
|
"@oclif/plugin-autocomplete": "^3.2.40",
|
|
93
|
-
"@oh-my-pi/omp-stats": "11.
|
|
94
|
-
"@oh-my-pi/pi-agent-core": "11.
|
|
95
|
-
"@oh-my-pi/pi-ai": "11.
|
|
96
|
-
"@oh-my-pi/pi-natives": "11.
|
|
97
|
-
"@oh-my-pi/pi-tui": "11.
|
|
98
|
-
"@oh-my-pi/pi-utils": "11.
|
|
93
|
+
"@oh-my-pi/omp-stats": "11.4.1",
|
|
94
|
+
"@oh-my-pi/pi-agent-core": "11.4.1",
|
|
95
|
+
"@oh-my-pi/pi-ai": "11.4.1",
|
|
96
|
+
"@oh-my-pi/pi-natives": "11.4.1",
|
|
97
|
+
"@oh-my-pi/pi-tui": "11.4.1",
|
|
98
|
+
"@oh-my-pi/pi-utils": "11.4.1",
|
|
99
99
|
"@sinclair/typebox": "^0.34.48",
|
|
100
100
|
"ajv": "^8.17.1",
|
|
101
101
|
"chalk": "^5.6.2",
|
|
@@ -2,6 +2,38 @@ import * as Diff from "diff";
|
|
|
2
2
|
import { theme } from "../../modes/theme/theme";
|
|
3
3
|
import { replaceTabs } from "../../tools/render-utils";
|
|
4
4
|
|
|
5
|
+
/** SGR dim on / normal intensity — additive, preserves fg/bg colors. */
|
|
6
|
+
const DIM = "\x1b[2m";
|
|
7
|
+
const DIM_OFF = "\x1b[22m";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Visualize leading whitespace (indentation) with dim glyphs.
|
|
11
|
+
* Tabs become ` → ` and spaces become `·`. Only affects whitespace
|
|
12
|
+
* before the first non-whitespace character; remaining tabs in code
|
|
13
|
+
* content are replaced with spaces (like replaceTabs).
|
|
14
|
+
*/
|
|
15
|
+
function visualizeIndent(text: string): string {
|
|
16
|
+
const match = text.match(/^([ \t]+)/);
|
|
17
|
+
if (!match) return replaceTabs(text);
|
|
18
|
+
const indent = match[1];
|
|
19
|
+
const rest = text.slice(indent.length);
|
|
20
|
+
// Normalize: collapse 3-space groups (tab-width) into tab arrows,
|
|
21
|
+
// then handle remaining tabs and lone spaces.
|
|
22
|
+
const normalized = indent.replaceAll("\t", " ");
|
|
23
|
+
let visible = "";
|
|
24
|
+
let pos = 0;
|
|
25
|
+
while (pos < normalized.length) {
|
|
26
|
+
if (pos + 3 <= normalized.length && normalized.slice(pos, pos + 3) === " ") {
|
|
27
|
+
visible += `${DIM} → ${DIM_OFF}`;
|
|
28
|
+
pos += 3;
|
|
29
|
+
} else {
|
|
30
|
+
visible += `${DIM}·${DIM_OFF}`;
|
|
31
|
+
pos++;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return `${visible}${replaceTabs(rest)}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
5
37
|
/**
|
|
6
38
|
* Parse diff line to extract prefix, line number, and content.
|
|
7
39
|
* Format: "+123 content" or "-123 content" or " 123 content" or " ..."
|
|
@@ -122,24 +154,26 @@ export function renderDiff(diffText: string, _options: RenderDiffOptions = {}):
|
|
|
122
154
|
replaceTabs(added.content),
|
|
123
155
|
);
|
|
124
156
|
|
|
125
|
-
result.push(theme.fg("toolDiffRemoved", formatLine("-", removed.lineNum, removedLine)));
|
|
126
|
-
result.push(theme.fg("toolDiffAdded", formatLine("+", added.lineNum, addedLine)));
|
|
157
|
+
result.push(theme.fg("toolDiffRemoved", formatLine("-", removed.lineNum, visualizeIndent(removedLine))));
|
|
158
|
+
result.push(theme.fg("toolDiffAdded", formatLine("+", added.lineNum, visualizeIndent(addedLine))));
|
|
127
159
|
} else {
|
|
128
160
|
// Show all removed lines first, then all added lines
|
|
129
161
|
for (const removed of removedLines) {
|
|
130
|
-
result.push(
|
|
162
|
+
result.push(
|
|
163
|
+
theme.fg("toolDiffRemoved", formatLine("-", removed.lineNum, visualizeIndent(removed.content))),
|
|
164
|
+
);
|
|
131
165
|
}
|
|
132
166
|
for (const added of addedLines) {
|
|
133
|
-
result.push(theme.fg("toolDiffAdded", formatLine("+", added.lineNum,
|
|
167
|
+
result.push(theme.fg("toolDiffAdded", formatLine("+", added.lineNum, visualizeIndent(added.content))));
|
|
134
168
|
}
|
|
135
169
|
}
|
|
136
170
|
} else if (parsed.prefix === "+") {
|
|
137
171
|
// Standalone added line
|
|
138
|
-
result.push(theme.fg("toolDiffAdded", formatLine("+", parsed.lineNum,
|
|
172
|
+
result.push(theme.fg("toolDiffAdded", formatLine("+", parsed.lineNum, visualizeIndent(parsed.content))));
|
|
139
173
|
i++;
|
|
140
174
|
} else {
|
|
141
175
|
// Context line
|
|
142
|
-
result.push(theme.fg("toolDiffContext", formatLine(" ", parsed.lineNum,
|
|
176
|
+
result.push(theme.fg("toolDiffContext", formatLine(" ", parsed.lineNum, visualizeIndent(parsed.content))));
|
|
143
177
|
i++;
|
|
144
178
|
}
|
|
145
179
|
}
|
package/src/patch/applicator.ts
CHANGED
|
@@ -126,6 +126,8 @@ function adjustLinesIndentation(patternLines: string[], actualLines: string[], n
|
|
|
126
126
|
|
|
127
127
|
let patternTabOnly = true;
|
|
128
128
|
let actualSpaceOnly = true;
|
|
129
|
+
let patternSpaceOnly = true;
|
|
130
|
+
let actualTabOnly = true;
|
|
129
131
|
let patternMixed = false;
|
|
130
132
|
let actualMixed = false;
|
|
131
133
|
|
|
@@ -133,6 +135,7 @@ function adjustLinesIndentation(patternLines: string[], actualLines: string[], n
|
|
|
133
135
|
if (line.trim().length === 0) continue;
|
|
134
136
|
const ws = getLeadingWhitespace(line);
|
|
135
137
|
if (ws.includes(" ")) patternTabOnly = false;
|
|
138
|
+
if (ws.includes("\t")) patternSpaceOnly = false;
|
|
136
139
|
if (ws.includes(" ") && ws.includes("\t")) patternMixed = true;
|
|
137
140
|
}
|
|
138
141
|
|
|
@@ -140,6 +143,7 @@ function adjustLinesIndentation(patternLines: string[], actualLines: string[], n
|
|
|
140
143
|
if (line.trim().length === 0) continue;
|
|
141
144
|
const ws = getLeadingWhitespace(line);
|
|
142
145
|
if (ws.includes("\t")) actualSpaceOnly = false;
|
|
146
|
+
if (ws.includes(" ")) actualTabOnly = false;
|
|
143
147
|
if (ws.includes(" ") && ws.includes("\t")) actualMixed = true;
|
|
144
148
|
}
|
|
145
149
|
|
|
@@ -173,6 +177,88 @@ function adjustLinesIndentation(patternLines: string[], actualLines: string[], n
|
|
|
173
177
|
}
|
|
174
178
|
}
|
|
175
179
|
|
|
180
|
+
// Reverse: pattern uses spaces, actual uses tabs — infer spaces = tabs * width + offset
|
|
181
|
+
// Collect (tabs, spaces) pairs from matched lines to solve for the model's tab rendering.
|
|
182
|
+
// With one data point: spaces = tabs * width (offset=0).
|
|
183
|
+
// With two+: solve ax + b via pairs with distinct tab counts.
|
|
184
|
+
if (!patternMixed && !actualMixed && patternSpaceOnly && actualTabOnly) {
|
|
185
|
+
const samples = new Map<number, number>(); // tabs -> spaces
|
|
186
|
+
const lineCount = Math.min(patternLines.length, actualLines.length);
|
|
187
|
+
let consistent = true;
|
|
188
|
+
for (let i = 0; i < lineCount; i++) {
|
|
189
|
+
const patternLine = patternLines[i];
|
|
190
|
+
const actualLine = actualLines[i];
|
|
191
|
+
if (patternLine.trim().length === 0 || actualLine.trim().length === 0) continue;
|
|
192
|
+
const spaces = countLeadingWhitespace(patternLine);
|
|
193
|
+
const tabs = countLeadingWhitespace(actualLine);
|
|
194
|
+
if (tabs === 0) continue;
|
|
195
|
+
const existing = samples.get(tabs);
|
|
196
|
+
if (existing !== undefined && existing !== spaces) {
|
|
197
|
+
consistent = false;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
samples.set(tabs, spaces);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (consistent && samples.size > 0) {
|
|
204
|
+
let tabWidth: number | undefined;
|
|
205
|
+
let offset = 0;
|
|
206
|
+
|
|
207
|
+
if (samples.size === 1) {
|
|
208
|
+
// One level: assume offset=0, width = spaces / tabs
|
|
209
|
+
const [[tabs, spaces]] = samples;
|
|
210
|
+
if (spaces % tabs === 0) {
|
|
211
|
+
tabWidth = spaces / tabs;
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
// Two+ levels: solve via any two distinct pairs
|
|
215
|
+
// spaces = tabs * width + offset => width = (s2 - s1) / (t2 - t1)
|
|
216
|
+
const entries = [...samples.entries()];
|
|
217
|
+
const [t1, s1] = entries[0];
|
|
218
|
+
const [t2, s2] = entries[1];
|
|
219
|
+
if (t1 !== t2) {
|
|
220
|
+
const w = (s2 - s1) / (t2 - t1);
|
|
221
|
+
if (w > 0 && Number.isInteger(w)) {
|
|
222
|
+
const b = s1 - t1 * w;
|
|
223
|
+
// Validate all samples against this model
|
|
224
|
+
let valid = true;
|
|
225
|
+
for (const [t, s] of samples) {
|
|
226
|
+
if (t * w + b !== s) {
|
|
227
|
+
valid = false;
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (valid) {
|
|
232
|
+
tabWidth = w;
|
|
233
|
+
offset = b;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (tabWidth !== undefined && tabWidth > 0) {
|
|
240
|
+
const converted = newLines.map(line => {
|
|
241
|
+
if (line.trim().length === 0) return line;
|
|
242
|
+
const ws = countLeadingWhitespace(line);
|
|
243
|
+
if (ws === 0) return line;
|
|
244
|
+
// Reverse: tabs = (spaces - offset) / width
|
|
245
|
+
const adjusted = ws - offset;
|
|
246
|
+
if (adjusted >= 0 && adjusted % tabWidth! === 0) {
|
|
247
|
+
return "\t".repeat(adjusted / tabWidth!) + line.slice(ws);
|
|
248
|
+
}
|
|
249
|
+
// Partial tab — keep remainder as spaces
|
|
250
|
+
const tabCount = Math.floor(adjusted / tabWidth!);
|
|
251
|
+
const remainder = adjusted - tabCount * tabWidth!;
|
|
252
|
+
if (tabCount >= 0) {
|
|
253
|
+
return "\t".repeat(tabCount) + " ".repeat(remainder) + line.slice(ws);
|
|
254
|
+
}
|
|
255
|
+
return line;
|
|
256
|
+
});
|
|
257
|
+
return converted;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
176
262
|
// Build a map from trimmed content to actual lines (by content, not position)
|
|
177
263
|
// This handles fuzzy matches where pattern and actual may not be positionally aligned
|
|
178
264
|
const contentToActualLines = new Map<string, string[]>();
|
|
@@ -1118,8 +1204,25 @@ function computeReplacements(
|
|
|
1118
1204
|
|
|
1119
1205
|
// Adjust indentation if needed (handles fuzzy matches where indentation differs)
|
|
1120
1206
|
const actualMatchedLines = originalLines.slice(found, found + pattern.length);
|
|
1121
|
-
const adjustedNewLines = adjustLinesIndentation(pattern, actualMatchedLines, newSlice);
|
|
1122
1207
|
|
|
1208
|
+
// Skip pure-context hunks (no +/- lines — oldLines === newLines).
|
|
1209
|
+
// They serve only to advance lineIndex for subsequent hunks.
|
|
1210
|
+
let isNoOp = pattern.length === newSlice.length;
|
|
1211
|
+
if (isNoOp) {
|
|
1212
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
1213
|
+
if (pattern[i] !== newSlice[i]) {
|
|
1214
|
+
isNoOp = false;
|
|
1215
|
+
break;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
if (isNoOp) {
|
|
1221
|
+
lineIndex = found + pattern.length;
|
|
1222
|
+
continue;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
const adjustedNewLines = adjustLinesIndentation(pattern, actualMatchedLines, newSlice);
|
|
1123
1226
|
replacements.push({ startIndex: found, oldLen: pattern.length, newLines: adjustedNewLines });
|
|
1124
1227
|
lineIndex = found + pattern.length;
|
|
1125
1228
|
}
|
package/src/patch/shared.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
formatStatusIcon,
|
|
15
15
|
getDiffStats,
|
|
16
16
|
PREVIEW_LIMITS,
|
|
17
|
+
replaceTabs,
|
|
17
18
|
shortenPath,
|
|
18
19
|
ToolUIKit,
|
|
19
20
|
truncateDiffByHunk,
|
|
@@ -181,7 +182,7 @@ export const editToolRenderer = {
|
|
|
181
182
|
const maxLines = 6;
|
|
182
183
|
text += "\n\n";
|
|
183
184
|
for (const line of previewLines.slice(0, maxLines)) {
|
|
184
|
-
text += `${uiTheme.fg("toolOutput", ui.truncate(line, 80))}\n`;
|
|
185
|
+
text += `${uiTheme.fg("toolOutput", ui.truncate(replaceTabs(line), 80))}\n`;
|
|
185
186
|
}
|
|
186
187
|
if (previewLines.length > maxLines) {
|
|
187
188
|
text += uiTheme.fg("dim", `… ${previewLines.length - maxLines} more lines`);
|
|
@@ -191,7 +192,7 @@ export const editToolRenderer = {
|
|
|
191
192
|
const maxLines = 6;
|
|
192
193
|
text += "\n\n";
|
|
193
194
|
for (const line of previewLines.slice(0, maxLines)) {
|
|
194
|
-
text += `${uiTheme.fg("toolOutput", ui.truncate(line, 80))}\n`;
|
|
195
|
+
text += `${uiTheme.fg("toolOutput", ui.truncate(replaceTabs(line), 80))}\n`;
|
|
195
196
|
}
|
|
196
197
|
if (previewLines.length > maxLines) {
|
|
197
198
|
text += uiTheme.fg("dim", `… ${previewLines.length - maxLines} more lines`);
|
|
@@ -264,13 +265,13 @@ export const editToolRenderer = {
|
|
|
264
265
|
|
|
265
266
|
if (result.isError) {
|
|
266
267
|
if (errorText) {
|
|
267
|
-
text += `\n\n${uiTheme.fg("error", errorText)}`;
|
|
268
|
+
text += `\n\n${uiTheme.fg("error", replaceTabs(errorText))}`;
|
|
268
269
|
}
|
|
269
270
|
} else if (result.details?.diff) {
|
|
270
271
|
text += renderDiffSection(result.details.diff, rawPath, expanded, uiTheme, ui, renderDiffFn);
|
|
271
272
|
} else if (editDiffPreview) {
|
|
272
273
|
if ("error" in editDiffPreview) {
|
|
273
|
-
text += `\n\n${uiTheme.fg("error", editDiffPreview.error)}`;
|
|
274
|
+
text += `\n\n${uiTheme.fg("error", replaceTabs(editDiffPreview.error))}`;
|
|
274
275
|
} else if (editDiffPreview.diff) {
|
|
275
276
|
text += renderDiffSection(editDiffPreview.diff, rawPath, expanded, uiTheme, ui, renderDiffFn);
|
|
276
277
|
}
|
|
@@ -19,9 +19,9 @@ Create plan at `{{planFilePath}}`.
|
|
|
19
19
|
|
|
20
20
|
Use `{{editToolName}}` incremental updates; `{{writeToolName}}` only create/full replace.
|
|
21
21
|
|
|
22
|
-
<
|
|
22
|
+
<caution>
|
|
23
23
|
Plan execution runs in fresh context (session cleared). Make plan file self-contained: include requirements, decisions, key findings, remaining todos needed to continue without prior session history.
|
|
24
|
-
</
|
|
24
|
+
</caution>
|
|
25
25
|
|
|
26
26
|
{{#if reentry}}
|
|
27
27
|
## Re-entry
|
|
@@ -56,7 +56,7 @@ Use `{{editToolName}}` update plan file as you learn; don't wait until end.
|
|
|
56
56
|
- Smaller task → fewer or no questions
|
|
57
57
|
</procedure>
|
|
58
58
|
|
|
59
|
-
<
|
|
59
|
+
<caution>
|
|
60
60
|
### Plan Structure
|
|
61
61
|
|
|
62
62
|
Use clear markdown headers; include:
|
|
@@ -65,7 +65,7 @@ Use clear markdown headers; include:
|
|
|
65
65
|
- Verification: how to test end-to-end
|
|
66
66
|
|
|
67
67
|
Concise enough to scan. Detailed enough to execute.
|
|
68
|
-
</
|
|
68
|
+
</caution>
|
|
69
69
|
|
|
70
70
|
{{else}}
|
|
71
71
|
## Planning Workflow
|
|
@@ -87,9 +87,9 @@ Update `{{planFilePath}}` (`{{editToolName}}` changes, `{{writeToolName}}` only
|
|
|
87
87
|
- Verification section
|
|
88
88
|
</procedure>
|
|
89
89
|
|
|
90
|
-
<
|
|
90
|
+
<caution>
|
|
91
91
|
Ask questions throughout. Don't make large assumptions about user intent.
|
|
92
|
-
</
|
|
92
|
+
</caution>
|
|
93
93
|
{{/if}}
|
|
94
94
|
|
|
95
95
|
<directives>
|
|
@@ -4,7 +4,7 @@ XML tags prompt: system-level instructions, not suggestions.
|
|
|
4
4
|
Tag hierarchy (enforcement):
|
|
5
5
|
- `<critical>` — Inviolable; noncompliance = system failure.
|
|
6
6
|
- `<prohibited>` — Forbidden; actions cause harm.
|
|
7
|
-
- `<
|
|
7
|
+
- `<caution>` — High priority; important to follow.
|
|
8
8
|
- `<instruction>` — Operating rules; follow precisely.
|
|
9
9
|
- `<conditions>` — When rules apply; check before acting.
|
|
10
10
|
- `<avoid>` — Anti-patterns; prefer alternatives.
|
package/src/prompts/tools/ask.md
CHANGED
|
@@ -16,9 +16,9 @@ Ask user when you need clarification or input during task execution.
|
|
|
16
16
|
Returns selected option(s) as text. For multi-part questions, returns map of question IDs to selected values.
|
|
17
17
|
</output>
|
|
18
18
|
|
|
19
|
-
<
|
|
19
|
+
<caution>
|
|
20
20
|
- Provide 2-5 concise, distinct options
|
|
21
|
-
</
|
|
21
|
+
</caution>
|
|
22
22
|
|
|
23
23
|
<critical>
|
|
24
24
|
**Default to action. Do NOT ask unless you are genuinely blocked and user preference is required to avoid a wrong outcome.**
|
|
@@ -14,10 +14,10 @@ When using multiple `input_images`, describe each image's role in `subject` or `
|
|
|
14
14
|
Returns generated image saved to disk. Response includes file path where image was written.
|
|
15
15
|
</output>
|
|
16
16
|
|
|
17
|
-
<
|
|
17
|
+
<caution>
|
|
18
18
|
- For photoreal: add "ultra-detailed, realistic, natural skin texture" to style
|
|
19
19
|
- For posters/cards: use 9:16 aspect ratio with negative space for text placement
|
|
20
20
|
- For iteration: use `changes` for targeted adjustments rather than regenerating from scratch
|
|
21
21
|
- For text: add "sharp, legible, correctly spelled" for important text; keep text short
|
|
22
22
|
- For diagrams: include "scientifically accurate" in style and provide facts explicitly
|
|
23
|
-
</
|
|
23
|
+
</caution>
|
package/src/prompts/tools/lsp.md
CHANGED
|
@@ -22,7 +22,7 @@ Interact with Language Server Protocol servers for code intelligence.
|
|
|
22
22
|
- `reload`: Confirmation of server restart
|
|
23
23
|
</output>
|
|
24
24
|
|
|
25
|
-
<
|
|
25
|
+
<caution>
|
|
26
26
|
- Requires running LSP server for target language
|
|
27
27
|
- Some operations require file to be saved to disk
|
|
28
|
-
</
|
|
28
|
+
</caution>
|
|
@@ -48,7 +48,7 @@ Returns success/failure; on failure, error message indicates:
|
|
|
48
48
|
- Never use anchors as comments (no line numbers, location labels, placeholders like `@@ @@`)
|
|
49
49
|
- Do not place new lines outside intended block
|
|
50
50
|
- If edit fails or breaks structure, re-read file and produce new patch from current content—do not retry same diff
|
|
51
|
-
-
|
|
51
|
+
- **NEVER** use edit to fix indentation or reformat code—run the project's formatter instead
|
|
52
52
|
</critical>
|
|
53
53
|
|
|
54
54
|
<example name="create">
|
|
@@ -41,13 +41,13 @@ User sees output like Jupyter notebook; rich displays render fully:
|
|
|
41
41
|
- `display(HTML(...))` → rendered HTML
|
|
42
42
|
- `display(Markdown(...))` → formatted markdown
|
|
43
43
|
- `plt.show()` → inline figures
|
|
44
|
-
**You will see object repr** (e.g., `<IPython.core.display.JSON object>`). Trust `display()`; do not assume user sees only repr.
|
|
44
|
+
**You will see object repr** (e.g., `<IPython.core.display.JSON object>`). Trust `display()`; do not assume user sees only repr.
|
|
45
45
|
</output>
|
|
46
46
|
|
|
47
|
-
<
|
|
47
|
+
<caution>
|
|
48
48
|
- Per-call mode uses fresh kernel each call
|
|
49
49
|
- Use `reset: true` to clear state when session mode active
|
|
50
|
-
</
|
|
50
|
+
</caution>
|
|
51
51
|
|
|
52
52
|
<critical>
|
|
53
53
|
- Use `run()` for shell commands; never raw `subprocess`
|
|
@@ -123,7 +123,7 @@ If tempted to write above, expand using templates.
|
|
|
123
123
|
Structured agents (`explore`, `reviewer`) have built-in output schemas. Describing a different output format in `context`/`assignment` without overriding via `schema` creates a mismatch — the agent can't reconcile your prose instructions with its schema and submits null data. Always use `schema` for output structure, or pick an agent whose built-in schema matches your needs.
|
|
124
124
|
**Test/lint commands in parallel tasks** — edit wars:
|
|
125
125
|
Parallel agents share working tree. If two agents run `bun check` or `bun test` concurrently, they see each other's half-finished edits, "fix" phantom errors, loop. **Never tell parallel tasks run project-wide build/test/lint commands.** Each task edits, stops. Caller verifies after all tasks complete.
|
|
126
|
-
**If you can
|
|
126
|
+
**If you can't specify scope yet**, create **Discovery task** first: enumerate files, find callsites, list candidates. Then fan out with explicit paths.
|
|
127
127
|
|
|
128
128
|
### Delegate intent, not keystrokes
|
|
129
129
|
|
|
@@ -36,9 +36,9 @@ Use proactively:
|
|
|
36
36
|
Returns confirmation todo list updated.
|
|
37
37
|
</output>
|
|
38
38
|
|
|
39
|
-
<
|
|
39
|
+
<caution>
|
|
40
40
|
When in doubt, use this.
|
|
41
|
-
</
|
|
41
|
+
</caution>
|
|
42
42
|
|
|
43
43
|
<example name="use-dark-mode">
|
|
44
44
|
User: Add dark mode toggle to settings. Run tests when done.
|
|
@@ -14,6 +14,6 @@ Returns search results formatted as blocks with:
|
|
|
14
14
|
- Provider-dependent structure based on selected backend
|
|
15
15
|
</output>
|
|
16
16
|
|
|
17
|
-
<
|
|
17
|
+
<caution>
|
|
18
18
|
Searches are performed automatically within a single API call—no pagination or follow-up requests needed.
|
|
19
|
-
</
|
|
19
|
+
</caution>
|
|
@@ -14,8 +14,5 @@ Confirmation of file creation/write with path. When LSP available, content may b
|
|
|
14
14
|
<critical>
|
|
15
15
|
- Prefer Edit tool for modifying existing files (more precise, preserves formatting)
|
|
16
16
|
- Create documentation files (*.md, README) only when explicitly requested
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
<important>
|
|
20
|
-
- Include emojis only when explicitly requested
|
|
21
|
-
</important>
|
|
17
|
+
- No emojis unless requested
|
|
18
|
+
</critical>
|
|
@@ -29,7 +29,6 @@ import type {
|
|
|
29
29
|
} from "@oh-my-pi/pi-ai";
|
|
30
30
|
import { isContextOverflow, modelsAreEqual, supportsXhigh } from "@oh-my-pi/pi-ai";
|
|
31
31
|
import { abortableSleep, isEnoent, logger } from "@oh-my-pi/pi-utils";
|
|
32
|
-
import { YAML } from "bun";
|
|
33
32
|
import type { Rule } from "../capability/rule";
|
|
34
33
|
import { getAgentDbPath } from "../config";
|
|
35
34
|
import { MODEL_ROLE_IDS, type ModelRegistry, type ModelRole } from "../config/model-registry";
|
|
@@ -1740,7 +1739,7 @@ export class AgentSession {
|
|
|
1740
1739
|
await this.abort();
|
|
1741
1740
|
this.agent.reset();
|
|
1742
1741
|
await this.sessionManager.flush();
|
|
1743
|
-
this.sessionManager.newSession(options);
|
|
1742
|
+
await this.sessionManager.newSession(options);
|
|
1744
1743
|
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
1745
1744
|
this._steeringMessages = [];
|
|
1746
1745
|
this._followUpMessages = [];
|
|
@@ -2443,7 +2442,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
2443
2442
|
|
|
2444
2443
|
// Start a new session
|
|
2445
2444
|
await this.sessionManager.flush();
|
|
2446
|
-
this.sessionManager.newSession();
|
|
2445
|
+
await this.sessionManager.newSession();
|
|
2447
2446
|
this.agent.reset();
|
|
2448
2447
|
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
2449
2448
|
this._steeringMessages = [];
|
|
@@ -3453,7 +3452,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3453
3452
|
await this.sessionManager.flush();
|
|
3454
3453
|
|
|
3455
3454
|
if (!selectedEntry.parentId) {
|
|
3456
|
-
this.sessionManager.newSession({ parentSession: previousSessionFile });
|
|
3455
|
+
await this.sessionManager.newSession({ parentSession: previousSessionFile });
|
|
3457
3456
|
} else {
|
|
3458
3457
|
this.sessionManager.createBranchedSession(selectedEntry.parentId);
|
|
3459
3458
|
}
|
|
@@ -3875,6 +3874,16 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3875
3874
|
formatSessionAsText(): string {
|
|
3876
3875
|
const lines: string[] = [];
|
|
3877
3876
|
|
|
3877
|
+
/** Serialize an object as XML parameter elements, one per key. */
|
|
3878
|
+
function formatArgsAsXml(args: Record<string, unknown>, indent = "\t"): string {
|
|
3879
|
+
const parts: string[] = [];
|
|
3880
|
+
for (const [key, value] of Object.entries(args)) {
|
|
3881
|
+
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
3882
|
+
parts.push(`${indent}<parameter name="${key}">${text}</parameter>`);
|
|
3883
|
+
}
|
|
3884
|
+
return parts.join("\n");
|
|
3885
|
+
}
|
|
3886
|
+
|
|
3878
3887
|
// Include system prompt at the beginning
|
|
3879
3888
|
const systemPrompt = this.agent.state.systemPrompt;
|
|
3880
3889
|
if (systemPrompt) {
|
|
@@ -3914,12 +3923,11 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3914
3923
|
if (tools.length > 0) {
|
|
3915
3924
|
lines.push("## Available Tools\n");
|
|
3916
3925
|
for (const tool of tools) {
|
|
3917
|
-
lines.push(
|
|
3926
|
+
lines.push(`<tool name="${tool.name}">`);
|
|
3918
3927
|
lines.push(tool.description);
|
|
3919
|
-
lines.push("\n```yaml");
|
|
3920
3928
|
const parametersClean = stripTypeBoxFields(tool.parameters);
|
|
3921
|
-
lines.push(
|
|
3922
|
-
lines.push("
|
|
3929
|
+
lines.push(`\nParameters:\n${formatArgsAsXml(parametersClean as Record<string, unknown>)}`);
|
|
3930
|
+
lines.push("<" + "/tool>\n");
|
|
3923
3931
|
}
|
|
3924
3932
|
lines.push("\n");
|
|
3925
3933
|
}
|
|
@@ -3951,10 +3959,11 @@ Be thorough - include exact file paths, function names, error messages, and tech
|
|
|
3951
3959
|
lines.push(c.thinking);
|
|
3952
3960
|
lines.push("</thinking>\n");
|
|
3953
3961
|
} else if (c.type === "toolCall") {
|
|
3954
|
-
lines.push(
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3962
|
+
lines.push(`<invoke name="${c.name}">`);
|
|
3963
|
+
if (c.arguments && typeof c.arguments === "object") {
|
|
3964
|
+
lines.push(formatArgsAsXml(c.arguments as Record<string, unknown>));
|
|
3965
|
+
}
|
|
3966
|
+
lines.push("<" + "/invoke>\n");
|
|
3958
3967
|
}
|
|
3959
3968
|
}
|
|
3960
3969
|
lines.push("");
|
package/src/tools/bash.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { applyHeadTail, normalizeBashCommand } from "./bash-normalize";
|
|
|
17
17
|
import type { OutputMeta } from "./output-meta";
|
|
18
18
|
import { allocateOutputArtifact, createTailBuffer } from "./output-utils";
|
|
19
19
|
import { resolveToCwd } from "./path-utils";
|
|
20
|
-
import { formatBytes, wrapBrackets } from "./render-utils";
|
|
20
|
+
import { formatBytes, replaceTabs, wrapBrackets } from "./render-utils";
|
|
21
21
|
import { ToolError } from "./tool-errors";
|
|
22
22
|
import { toolResult } from "./tool-result";
|
|
23
23
|
import { DEFAULT_MAX_BYTES } from "./truncate";
|
|
@@ -271,11 +271,13 @@ export const bashToolRenderer = {
|
|
|
271
271
|
const hasOutput = displayOutput.trim().length > 0;
|
|
272
272
|
if (hasOutput) {
|
|
273
273
|
if (expanded) {
|
|
274
|
-
outputLines.push(
|
|
274
|
+
outputLines.push(
|
|
275
|
+
...displayOutput.split("\n").map(line => uiTheme.fg("toolOutput", replaceTabs(line))),
|
|
276
|
+
);
|
|
275
277
|
} else {
|
|
276
278
|
const styledOutput = displayOutput
|
|
277
279
|
.split("\n")
|
|
278
|
-
.map(line => uiTheme.fg("toolOutput", line))
|
|
280
|
+
.map(line => uiTheme.fg("toolOutput", replaceTabs(line)))
|
|
279
281
|
.join("\n");
|
|
280
282
|
const textContent = styledOutput;
|
|
281
283
|
const result = truncateToVisualLines(textContent, previewLines, width);
|
package/src/tools/write.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
formatExpandHint,
|
|
24
24
|
formatMoreItems,
|
|
25
25
|
formatStatusIcon,
|
|
26
|
+
replaceTabs,
|
|
26
27
|
shortenPath,
|
|
27
28
|
ToolUIKit,
|
|
28
29
|
} from "./render-utils";
|
|
@@ -160,7 +161,7 @@ function formatStreamingContent(content: string, uiTheme: Theme, ui: ToolUIKit):
|
|
|
160
161
|
text += uiTheme.fg("dim", `… (${hidden} earlier lines)\n`);
|
|
161
162
|
}
|
|
162
163
|
for (const line of displayLines) {
|
|
163
|
-
text += `${uiTheme.fg("toolOutput", ui.truncate(line, 80))}\n`;
|
|
164
|
+
text += `${uiTheme.fg("toolOutput", ui.truncate(replaceTabs(line), 80))}\n`;
|
|
164
165
|
}
|
|
165
166
|
text += uiTheme.fg("dim", `… (streaming)`);
|
|
166
167
|
return text;
|
|
@@ -175,7 +176,7 @@ function renderContentPreview(content: string, expanded: boolean, uiTheme: Theme
|
|
|
175
176
|
|
|
176
177
|
let text = "\n\n";
|
|
177
178
|
for (const line of displayLines) {
|
|
178
|
-
text += `${uiTheme.fg("toolOutput", ui.truncate(line, 80))}\n`;
|
|
179
|
+
text += `${uiTheme.fg("toolOutput", ui.truncate(replaceTabs(line), 80))}\n`;
|
|
179
180
|
}
|
|
180
181
|
if (!expanded && hidden > 0) {
|
|
181
182
|
const hint = formatExpandHint(uiTheme, expanded, hidden > 0);
|
package/src/tui/code-cell.ts
CHANGED
|
@@ -90,7 +90,7 @@ export function renderCodeCell(options: CodeCellOptions, theme: Theme): string[]
|
|
|
90
90
|
const maxLines = expanded ? rawLines.length : Math.min(rawLines.length, outputMaxLines);
|
|
91
91
|
const displayLines = rawLines
|
|
92
92
|
.slice(0, maxLines)
|
|
93
|
-
.map(line => (line.includes("\x1b[") ? line : theme.fg("toolOutput", line)));
|
|
93
|
+
.map(line => (line.includes("\x1b[") ? replaceTabs(line) : theme.fg("toolOutput", replaceTabs(line))));
|
|
94
94
|
outputLines.push(...displayLines);
|
|
95
95
|
const remaining = rawLines.length - maxLines;
|
|
96
96
|
if (remaining > 0) {
|