@oh-my-pi/pi-coding-agent 11.2.3 → 11.4.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 +119 -4
- package/examples/extensions/plan-mode.ts +1 -1
- package/examples/hooks/qna.ts +1 -1
- package/examples/hooks/status-line.ts +1 -1
- package/examples/sdk/11-sessions.ts +1 -1
- package/package.json +8 -8
- package/src/cli/args.ts +9 -6
- package/src/cli/update-cli.ts +2 -2
- package/src/commands/index/index.ts +2 -5
- package/src/commit/agentic/agent.ts +1 -1
- package/src/commit/changelog/index.ts +2 -2
- package/src/config/keybindings.ts +16 -1
- package/src/config/model-registry.ts +25 -20
- package/src/config/model-resolver.ts +8 -8
- package/src/config/resolve-config-value.ts +92 -0
- package/src/config/settings-schema.ts +9 -0
- package/src/config.ts +14 -1
- package/src/export/html/template.css +7 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +33 -16
- package/src/extensibility/custom-commands/bundled/review/index.ts +1 -1
- package/src/extensibility/extensions/index.ts +18 -0
- package/src/extensibility/extensions/loader.ts +15 -0
- package/src/extensibility/extensions/runner.ts +78 -1
- package/src/extensibility/extensions/types.ts +131 -5
- package/src/extensibility/extensions/wrapper.ts +1 -1
- package/src/extensibility/plugins/git-url.ts +270 -0
- package/src/extensibility/plugins/index.ts +2 -0
- package/src/extensibility/slash-commands.ts +45 -0
- package/src/index.ts +7 -0
- package/src/lsp/render.ts +50 -43
- package/src/lsp/utils.ts +2 -2
- package/src/main.ts +11 -10
- package/src/mcp/transports/stdio.ts +3 -5
- package/src/modes/components/custom-message.ts +0 -8
- package/src/modes/components/diff.ts +41 -13
- package/src/modes/components/footer.ts +4 -4
- package/src/modes/components/model-selector.ts +4 -0
- package/src/modes/components/todo-display.ts +13 -3
- package/src/modes/components/tool-execution.ts +30 -16
- package/src/modes/components/tree-selector.ts +50 -19
- package/src/modes/controllers/event-controller.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +34 -2
- package/src/modes/controllers/input-controller.ts +47 -33
- package/src/modes/controllers/selector-controller.ts +10 -15
- package/src/modes/interactive-mode.ts +50 -38
- package/src/modes/print-mode.ts +6 -0
- package/src/modes/rpc/rpc-client.ts +4 -4
- package/src/modes/rpc/rpc-mode.ts +17 -2
- package/src/modes/rpc/rpc-types.ts +2 -2
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +3 -1
- package/src/patch/applicator.ts +106 -4
- package/src/patch/fuzzy.ts +1 -1
- package/src/patch/shared.ts +77 -63
- package/src/prompts/system/plan-mode-active.md +6 -6
- package/src/prompts/system/system-prompt.md +2 -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 +7 -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/sdk.ts +15 -11
- package/src/session/agent-session.ts +92 -34
- package/src/session/auth-storage.ts +2 -1
- package/src/session/blob-store.ts +105 -0
- package/src/session/session-manager.ts +107 -44
- package/src/task/executor.ts +19 -9
- package/src/task/render.ts +80 -58
- package/src/tools/ask.ts +28 -5
- package/src/tools/bash.ts +47 -39
- package/src/tools/browser.ts +248 -26
- package/src/tools/calculator.ts +42 -23
- package/src/tools/fetch.ts +33 -16
- package/src/tools/find.ts +57 -22
- package/src/tools/grep.ts +54 -25
- package/src/tools/index.ts +5 -5
- package/src/tools/notebook.ts +19 -6
- package/src/tools/path-utils.ts +26 -1
- package/src/tools/python.ts +20 -14
- package/src/tools/read.ts +21 -8
- package/src/tools/render-utils.ts +5 -45
- package/src/tools/ssh.ts +59 -53
- package/src/tools/submit-result.ts +2 -2
- package/src/tools/todo-write.ts +32 -14
- package/src/tools/truncate.ts +1 -1
- package/src/tools/write.ts +42 -26
- package/src/tui/code-cell.ts +1 -1
- package/src/tui/output-block.ts +61 -3
- package/src/tui/tree-list.ts +4 -4
- package/src/tui/utils.ts +71 -1
- package/src/utils/frontmatter.ts +1 -1
- package/src/utils/title-generator.ts +1 -1
- package/src/utils/tools-manager.ts +18 -2
- package/src/web/scrapers/osv.ts +4 -1
- package/src/web/scrapers/youtube.ts +1 -1
- package/src/web/search/index.ts +1 -1
- package/src/web/search/render.ts +96 -90
|
@@ -286,6 +286,15 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
286
286
|
return Promise.resolve({ success: false, error: "Theme switching not supported in RPC mode" });
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
+
getToolsExpanded() {
|
|
290
|
+
// Tool expansion not supported in RPC mode - no TUI
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
setToolsExpanded(_expanded: boolean) {
|
|
295
|
+
// Tool expansion not supported in RPC mode - no TUI
|
|
296
|
+
}
|
|
297
|
+
|
|
289
298
|
setEditorComponent(): void {
|
|
290
299
|
// Custom editor components not supported in RPC mode
|
|
291
300
|
}
|
|
@@ -316,6 +325,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
316
325
|
getActiveTools: () => session.getActiveToolNames(),
|
|
317
326
|
getAllTools: () => session.getAllToolNames(),
|
|
318
327
|
setActiveTools: (toolNames: string[]) => session.setActiveToolsByName(toolNames),
|
|
328
|
+
getCommands: () => [],
|
|
319
329
|
setModel: async model => {
|
|
320
330
|
const key = await session.modelRegistry.getApiKey(model);
|
|
321
331
|
if (!key) return false;
|
|
@@ -335,6 +345,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
335
345
|
shutdownState.requested = true;
|
|
336
346
|
},
|
|
337
347
|
getContextUsage: () => session.getContextUsage(),
|
|
348
|
+
getSystemPrompt: () => session.systemPrompt,
|
|
338
349
|
compact: async instructionsOrOptions => {
|
|
339
350
|
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
340
351
|
const options =
|
|
@@ -364,6 +375,10 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
364
375
|
const result = await session.navigateTree(targetId, { summarize: options?.summarize });
|
|
365
376
|
return { cancelled: result.cancelled };
|
|
366
377
|
},
|
|
378
|
+
switchSession: async sessionPath => {
|
|
379
|
+
const success = await session.switchSession(sessionPath);
|
|
380
|
+
return { cancelled: !success };
|
|
381
|
+
},
|
|
367
382
|
compact: async instructionsOrOptions => {
|
|
368
383
|
const instructions = typeof instructionsOrOptions === "string" ? instructionsOrOptions : undefined;
|
|
369
384
|
const options =
|
|
@@ -412,12 +427,12 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
412
427
|
}
|
|
413
428
|
|
|
414
429
|
case "steer": {
|
|
415
|
-
await session.steer(command.message);
|
|
430
|
+
await session.steer(command.message, command.images);
|
|
416
431
|
return success(id, "steer");
|
|
417
432
|
}
|
|
418
433
|
|
|
419
434
|
case "follow_up": {
|
|
420
|
-
await session.followUp(command.message);
|
|
435
|
+
await session.followUp(command.message, command.images);
|
|
421
436
|
return success(id, "follow_up");
|
|
422
437
|
}
|
|
423
438
|
|
|
@@ -17,8 +17,8 @@ import type { CompactionResult } from "../../session/compaction";
|
|
|
17
17
|
export type RpcCommand =
|
|
18
18
|
// Prompting
|
|
19
19
|
| { id?: string; type: "prompt"; message: string; images?: ImageContent[]; streamingBehavior?: "steer" | "followUp" }
|
|
20
|
-
| { id?: string; type: "steer"; message: string }
|
|
21
|
-
| { id?: string; type: "follow_up"; message: string }
|
|
20
|
+
| { id?: string; type: "steer"; message: string; images?: ImageContent[] }
|
|
21
|
+
| { id?: string; type: "follow_up"; message: string; images?: ImageContent[] }
|
|
22
22
|
| { id?: string; type: "abort" }
|
|
23
23
|
| { id?: string; type: "new_session"; parentSession?: string }
|
|
24
24
|
|
package/src/modes/types.ts
CHANGED
|
@@ -176,6 +176,7 @@ export interface InteractiveModeContext {
|
|
|
176
176
|
cycleThinkingLevel(): void;
|
|
177
177
|
cycleRoleModel(options?: { temporary?: boolean }): Promise<void>;
|
|
178
178
|
toggleToolOutputExpansion(): void;
|
|
179
|
+
setToolsExpanded(expanded: boolean): void;
|
|
179
180
|
toggleThinkingBlockVisibility(): void;
|
|
180
181
|
openExternalEditor(): void;
|
|
181
182
|
registerExtensionShortcuts(): void;
|
|
@@ -203,7 +203,9 @@ export class UiHelpers {
|
|
|
203
203
|
|
|
204
204
|
// Render tool call components
|
|
205
205
|
for (const content of message.content) {
|
|
206
|
-
if (content.type !== "toolCall")
|
|
206
|
+
if (content.type !== "toolCall") {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
207
209
|
|
|
208
210
|
if (content.name === "read") {
|
|
209
211
|
if (!readGroup) {
|
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[]>();
|
|
@@ -434,8 +520,7 @@ function formatSequenceMatchPreview(lines: string[], startIdx: number): string {
|
|
|
434
520
|
return previewLines
|
|
435
521
|
.map((line, i) => {
|
|
436
522
|
const num = start + i + 1;
|
|
437
|
-
const truncated =
|
|
438
|
-
line.length > MATCH_PREVIEW_MAX_LEN ? `${line.slice(0, MATCH_PREVIEW_MAX_LEN - 3)}...` : line;
|
|
523
|
+
const truncated = line.length > MATCH_PREVIEW_MAX_LEN ? `${line.slice(0, MATCH_PREVIEW_MAX_LEN - 1)}…` : line;
|
|
439
524
|
return ` ${num} | ${truncated}`;
|
|
440
525
|
})
|
|
441
526
|
.join("\n");
|
|
@@ -1103,7 +1188,7 @@ function computeReplacements(
|
|
|
1103
1188
|
return lines
|
|
1104
1189
|
.map((line, i) => {
|
|
1105
1190
|
const num = start + i + 1;
|
|
1106
|
-
const truncated = line.length > maxLineLength ? `${line.slice(0, maxLineLength -
|
|
1191
|
+
const truncated = line.length > maxLineLength ? `${line.slice(0, maxLineLength - 1)}…` : line;
|
|
1107
1192
|
return ` ${num} | ${truncated}`;
|
|
1108
1193
|
})
|
|
1109
1194
|
.join("\n");
|
|
@@ -1119,8 +1204,25 @@ function computeReplacements(
|
|
|
1119
1204
|
|
|
1120
1205
|
// Adjust indentation if needed (handles fuzzy matches where indentation differs)
|
|
1121
1206
|
const actualMatchedLines = originalLines.slice(found, found + pattern.length);
|
|
1122
|
-
const adjustedNewLines = adjustLinesIndentation(pattern, actualMatchedLines, newSlice);
|
|
1123
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);
|
|
1124
1226
|
replacements.push({ startIndex: found, oldLen: pattern.length, newLines: adjustedNewLines });
|
|
1125
1227
|
lineIndex = found + pattern.length;
|
|
1126
1228
|
}
|
package/src/patch/fuzzy.ts
CHANGED
|
@@ -241,7 +241,7 @@ export function findMatch(
|
|
|
241
241
|
const preview = previewLines
|
|
242
242
|
.map((line, idx) => {
|
|
243
243
|
const num = start + idx + 1;
|
|
244
|
-
return ` ${num} | ${line.length > OCCURRENCE_PREVIEW_MAX_LEN ? `${line.slice(0, OCCURRENCE_PREVIEW_MAX_LEN -
|
|
244
|
+
return ` ${num} | ${line.length > OCCURRENCE_PREVIEW_MAX_LEN ? `${line.slice(0, OCCURRENCE_PREVIEW_MAX_LEN - 1)}…` : line}`;
|
|
245
245
|
})
|
|
246
246
|
.join("\n");
|
|
247
247
|
occurrencePreviews.push(preview);
|
package/src/patch/shared.ts
CHANGED
|
@@ -13,12 +13,14 @@ import {
|
|
|
13
13
|
formatExpandHint,
|
|
14
14
|
formatStatusIcon,
|
|
15
15
|
getDiffStats,
|
|
16
|
+
PREVIEW_LIMITS,
|
|
17
|
+
replaceTabs,
|
|
16
18
|
shortenPath,
|
|
17
19
|
ToolUIKit,
|
|
18
20
|
truncateDiffByHunk,
|
|
19
21
|
} from "../tools/render-utils";
|
|
20
22
|
import type { RenderCallOptions } from "../tools/renderers";
|
|
21
|
-
import { renderStatusLine } from "../tui";
|
|
23
|
+
import { Ellipsis, Hasher, type RenderCache, renderStatusLine, truncateToWidth } from "../tui";
|
|
22
24
|
import type { DiffError, DiffResult, Operation } from "./types";
|
|
23
25
|
|
|
24
26
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -85,8 +87,6 @@ export interface EditRenderContext {
|
|
|
85
87
|
renderDiff?: (diffText: string, options?: { filePath?: string }) => string;
|
|
86
88
|
}
|
|
87
89
|
|
|
88
|
-
const EDIT_DIFF_PREVIEW_HUNKS = 2;
|
|
89
|
-
const EDIT_DIFF_PREVIEW_LINES = 24;
|
|
90
90
|
const EDIT_STREAMING_PREVIEW_LINES = 12;
|
|
91
91
|
|
|
92
92
|
function countLines(text: string): number {
|
|
@@ -140,7 +140,7 @@ function renderDiffSection(
|
|
|
140
140
|
hiddenLines,
|
|
141
141
|
} = expanded
|
|
142
142
|
? { text: diff, hiddenHunks: 0, hiddenLines: 0 }
|
|
143
|
-
: truncateDiffByHunk(diff,
|
|
143
|
+
: truncateDiffByHunk(diff, PREVIEW_LIMITS.DIFF_COLLAPSED_HUNKS, PREVIEW_LIMITS.DIFF_COLLAPSED_LINES);
|
|
144
144
|
|
|
145
145
|
text += `\n\n${renderDiffFn(truncatedDiff, { filePath: rawPath })}`;
|
|
146
146
|
if (!expanded && (hiddenHunks > 0 || hiddenLines > 0)) {
|
|
@@ -182,7 +182,7 @@ export const editToolRenderer = {
|
|
|
182
182
|
const maxLines = 6;
|
|
183
183
|
text += "\n\n";
|
|
184
184
|
for (const line of previewLines.slice(0, maxLines)) {
|
|
185
|
-
text += `${uiTheme.fg("toolOutput", ui.truncate(line, 80))}\n`;
|
|
185
|
+
text += `${uiTheme.fg("toolOutput", ui.truncate(replaceTabs(line), 80))}\n`;
|
|
186
186
|
}
|
|
187
187
|
if (previewLines.length > maxLines) {
|
|
188
188
|
text += uiTheme.fg("dim", `… ${previewLines.length - maxLines} more lines`);
|
|
@@ -192,7 +192,7 @@ export const editToolRenderer = {
|
|
|
192
192
|
const maxLines = 6;
|
|
193
193
|
text += "\n\n";
|
|
194
194
|
for (const line of previewLines.slice(0, maxLines)) {
|
|
195
|
-
text += `${uiTheme.fg("toolOutput", ui.truncate(line, 80))}\n`;
|
|
195
|
+
text += `${uiTheme.fg("toolOutput", ui.truncate(replaceTabs(line), 80))}\n`;
|
|
196
196
|
}
|
|
197
197
|
if (previewLines.length > maxLines) {
|
|
198
198
|
text += uiTheme.fg("dim", `… ${previewLines.length - maxLines} more lines`);
|
|
@@ -209,75 +209,89 @@ export const editToolRenderer = {
|
|
|
209
209
|
args?: EditRenderArgs,
|
|
210
210
|
): Component {
|
|
211
211
|
const ui = new ToolUIKit(uiTheme);
|
|
212
|
-
const { expanded, renderContext } = options;
|
|
213
212
|
const rawPath = args?.file_path || args?.path || "";
|
|
214
213
|
const filePath = shortenPath(rawPath);
|
|
215
214
|
const editLanguage = getLanguageFromPath(rawPath) ?? "text";
|
|
216
215
|
const editIcon = uiTheme.fg("muted", uiTheme.getLangIcon(editLanguage));
|
|
217
|
-
const editDiffPreview = renderContext?.editDiffPreview;
|
|
218
|
-
const renderDiffFn = renderContext?.renderDiff ?? ((t: string) => t);
|
|
219
216
|
|
|
220
|
-
// Get op and rename from args or details
|
|
221
217
|
const op = args?.op || result.details?.op;
|
|
222
218
|
const rename = args?.rename || result.details?.rename;
|
|
219
|
+
const opTitle = op === "create" ? "Create" : op === "delete" ? "Delete" : "Edit";
|
|
223
220
|
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
if (firstChangedLine) {
|
|
230
|
-
pathDisplay += uiTheme.fg("warning", `:${firstChangedLine}`);
|
|
231
|
-
}
|
|
221
|
+
// Pre-compute metadata line (static across renders)
|
|
222
|
+
const metadataLine =
|
|
223
|
+
op !== "delete"
|
|
224
|
+
? `\n${formatMetadataLine(countLines(args?.newText ?? args?.oldText ?? args?.diff ?? args?.patch ?? ""), editLanguage, uiTheme)}`
|
|
225
|
+
: "";
|
|
232
226
|
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
pathDisplay += ` ${uiTheme.fg("dim", "→")} ${uiTheme.fg("accent", shortenPath(rename))}`;
|
|
236
|
-
}
|
|
227
|
+
// Pre-compute error text (static)
|
|
228
|
+
const errorText = result.isError ? (result.content?.find(c => c.type === "text")?.text ?? "") : "";
|
|
237
229
|
|
|
238
|
-
|
|
239
|
-
const opTitle = op === "create" ? "Create" : op === "delete" ? "Delete" : "Edit";
|
|
240
|
-
const header = renderStatusLine(
|
|
241
|
-
{
|
|
242
|
-
icon: result.isError ? "error" : "success",
|
|
243
|
-
title: opTitle,
|
|
244
|
-
description: `${editIcon} ${pathDisplay}`,
|
|
245
|
-
},
|
|
246
|
-
uiTheme,
|
|
247
|
-
);
|
|
248
|
-
let text = header;
|
|
249
|
-
|
|
250
|
-
// Skip metadata line for delete operations
|
|
251
|
-
if (op !== "delete") {
|
|
252
|
-
const editLineCount = countLines(args?.newText ?? args?.oldText ?? args?.diff ?? args?.patch ?? "");
|
|
253
|
-
text += `\n${formatMetadataLine(editLineCount, editLanguage, uiTheme)}`;
|
|
254
|
-
}
|
|
230
|
+
let cached: RenderCache | undefined;
|
|
255
231
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
// Prefer actual diff after execution
|
|
264
|
-
text += renderDiffSection(result.details.diff, rawPath, expanded, uiTheme, ui, renderDiffFn);
|
|
265
|
-
} else if (editDiffPreview) {
|
|
266
|
-
// Use cached diff preview when no actual diff is available
|
|
267
|
-
if ("error" in editDiffPreview) {
|
|
268
|
-
text += `\n\n${uiTheme.fg("error", editDiffPreview.error)}`;
|
|
269
|
-
} else if (editDiffPreview.diff) {
|
|
270
|
-
text += renderDiffSection(editDiffPreview.diff, rawPath, expanded, uiTheme, ui, renderDiffFn);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
232
|
+
return {
|
|
233
|
+
render(width) {
|
|
234
|
+
const { expanded, renderContext } = options;
|
|
235
|
+
const editDiffPreview = renderContext?.editDiffPreview;
|
|
236
|
+
const renderDiffFn = renderContext?.renderDiff ?? ((t: string) => t);
|
|
237
|
+
const key = new Hasher().bool(expanded).u32(width).digest();
|
|
238
|
+
if (cached?.key === key) return cached.lines;
|
|
273
239
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
240
|
+
// Build path display with line number
|
|
241
|
+
let pathDisplay = filePath ? uiTheme.fg("accent", filePath) : uiTheme.fg("toolOutput", "…");
|
|
242
|
+
const firstChangedLine =
|
|
243
|
+
(editDiffPreview && "firstChangedLine" in editDiffPreview
|
|
244
|
+
? editDiffPreview.firstChangedLine
|
|
245
|
+
: undefined) || (result.details && !result.isError ? result.details.firstChangedLine : undefined);
|
|
246
|
+
if (firstChangedLine) {
|
|
247
|
+
pathDisplay += uiTheme.fg("warning", `:${firstChangedLine}`);
|
|
248
|
+
}
|
|
280
249
|
|
|
281
|
-
|
|
250
|
+
// Add arrow for rename operations
|
|
251
|
+
if (rename) {
|
|
252
|
+
pathDisplay += ` ${uiTheme.fg("dim", "→")} ${uiTheme.fg("accent", shortenPath(rename))}`;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const header = renderStatusLine(
|
|
256
|
+
{
|
|
257
|
+
icon: result.isError ? "error" : "success",
|
|
258
|
+
title: opTitle,
|
|
259
|
+
description: `${editIcon} ${pathDisplay}`,
|
|
260
|
+
},
|
|
261
|
+
uiTheme,
|
|
262
|
+
);
|
|
263
|
+
let text = header;
|
|
264
|
+
text += metadataLine;
|
|
265
|
+
|
|
266
|
+
if (result.isError) {
|
|
267
|
+
if (errorText) {
|
|
268
|
+
text += `\n\n${uiTheme.fg("error", errorText)}`;
|
|
269
|
+
}
|
|
270
|
+
} else if (result.details?.diff) {
|
|
271
|
+
text += renderDiffSection(result.details.diff, rawPath, expanded, uiTheme, ui, renderDiffFn);
|
|
272
|
+
} else if (editDiffPreview) {
|
|
273
|
+
if ("error" in editDiffPreview) {
|
|
274
|
+
text += `\n\n${uiTheme.fg("error", editDiffPreview.error)}`;
|
|
275
|
+
} else if (editDiffPreview.diff) {
|
|
276
|
+
text += renderDiffSection(editDiffPreview.diff, rawPath, expanded, uiTheme, ui, renderDiffFn);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Show LSP diagnostics if available
|
|
281
|
+
if (result.details?.diagnostics) {
|
|
282
|
+
text += ui.formatDiagnostics(result.details.diagnostics, expanded, (fp: string) =>
|
|
283
|
+
uiTheme.getLangIcon(getLanguageFromPath(fp)),
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const lines =
|
|
288
|
+
width > 0 ? text.split("\n").map(line => truncateToWidth(line, width, Ellipsis.Omit)) : text.split("\n");
|
|
289
|
+
cached = { key, lines };
|
|
290
|
+
return lines;
|
|
291
|
+
},
|
|
292
|
+
invalidate() {
|
|
293
|
+
cached = undefined;
|
|
294
|
+
},
|
|
295
|
+
};
|
|
282
296
|
},
|
|
283
297
|
};
|
|
@@ -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.
|
|
@@ -221,6 +221,7 @@ Main branch: {{git.mainBranch}}
|
|
|
221
221
|
{{#if skills.length}}
|
|
222
222
|
<skills>
|
|
223
223
|
Scan descriptions vs domain. Skill covers output? Read `skill://<name>` first.
|
|
224
|
+
When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.
|
|
224
225
|
|
|
225
226
|
{{#list skills join="\n"}}
|
|
226
227
|
<skill name="{{name}}">
|
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`
|
|
@@ -70,6 +70,10 @@ Run in isolated git worktree; returns patches. Use when tasks edit overlapping f
|
|
|
70
70
|
### `schema` (optional — recommended for structured output)
|
|
71
71
|
|
|
72
72
|
JTD schema defining expected response structure. Use typed properties. If you care about parsing result, define here — **never describe output format in `context` or `assignment`**.
|
|
73
|
+
|
|
74
|
+
<caution>
|
|
75
|
+
**Schema vs agent mismatch causes null output.** Agents with `output="structured"` (e.g., `explore`) have a built-in schema. If you also pass `schema`, yours takes precedence — but if you describe output format in `context`/`assignment` instead, the agent's built-in schema wins. The agent gets confused trying to fit your requested format into its schema shape and submits `null`. Either: (1) use `schema` to override the built-in one, (2) use `task` agent which has no built-in schema, or (3) match your instructions to the agent's expected output shape.
|
|
76
|
+
</caution>
|
|
73
77
|
---
|
|
74
78
|
|
|
75
79
|
## Writing an assignment
|
|
@@ -115,9 +119,11 @@ Use structure every assignment:
|
|
|
115
119
|
- "No WASM."
|
|
116
120
|
|
|
117
121
|
If tempted to write above, expand using templates.
|
|
122
|
+
**Output format in prose instead of `schema`** — agent returns null:
|
|
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.
|
|
118
124
|
**Test/lint commands in parallel tasks** — edit wars:
|
|
119
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.
|
|
120
|
-
**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.
|
|
121
127
|
|
|
122
128
|
### Delegate intent, not keystrokes
|
|
123
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>
|