@oh-my-pi/pi-coding-agent 14.2.1 → 14.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 +143 -1
- package/package.json +19 -19
- package/src/autoresearch/prompt.md +1 -1
- package/src/cli/args.ts +10 -1
- package/src/cli/shell-cli.ts +15 -3
- package/src/commit/agentic/prompts/analyze-file.md +1 -1
- package/src/config/model-registry.ts +67 -15
- package/src/config/prompt-templates.ts +5 -5
- package/src/config/settings-schema.ts +63 -4
- package/src/cursor.ts +3 -8
- package/src/debug/system-info.ts +6 -2
- package/src/discovery/claude.ts +58 -36
- package/src/discovery/helpers.ts +3 -3
- package/src/discovery/opencode.ts +20 -2
- package/src/edit/diff.ts +50 -47
- package/src/edit/index.ts +87 -57
- package/src/edit/line-hash.ts +735 -19
- package/src/edit/modes/apply-patch.ts +0 -9
- package/src/edit/modes/atom.ts +658 -0
- package/src/edit/modes/chunk.ts +144 -78
- package/src/edit/modes/hashline.ts +223 -146
- package/src/edit/modes/patch.ts +5 -9
- package/src/edit/modes/replace.ts +6 -11
- package/src/edit/renderer.ts +112 -143
- package/src/edit/streaming.ts +385 -0
- package/src/exec/bash-executor.ts +58 -5
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +4 -12
- package/src/extensibility/custom-tools/types.ts +2 -0
- package/src/extensibility/custom-tools/wrapper.ts +2 -1
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/internal-urls/pi-protocol.ts +0 -2
- package/src/lsp/client.ts +8 -1
- package/src/lsp/defaults.json +2 -1
- package/src/lsp/index.ts +1 -1
- package/src/mcp/render.ts +1 -8
- package/src/modes/acp/acp-agent.ts +76 -2
- package/src/modes/components/assistant-message.ts +5 -34
- package/src/modes/components/diff.ts +23 -14
- package/src/modes/components/footer.ts +21 -16
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/settings-defs.ts +6 -1
- package/src/modes/components/todo-reminder.ts +1 -8
- package/src/modes/components/tool-execution.ts +112 -105
- package/src/modes/controllers/input-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +0 -2
- package/src/modes/print-mode.ts +8 -0
- package/src/modes/theme/mermaid-cache.ts +13 -52
- package/src/modes/theme/theme.ts +2 -2
- package/src/prompts/agents/librarian.md +1 -1
- package/src/prompts/agents/reviewer.md +4 -4
- package/src/prompts/ci-green-request.md +1 -1
- package/src/prompts/review-request.md +1 -1
- package/src/prompts/system/subagent-system-prompt.md +3 -3
- package/src/prompts/system/subagent-yield-reminder.md +11 -0
- package/src/prompts/system/system-prompt.md +4 -1
- package/src/prompts/tools/ask.md +3 -2
- package/src/prompts/tools/ast-edit.md +15 -19
- package/src/prompts/tools/ast-grep.md +18 -24
- package/src/prompts/tools/atom.md +96 -0
- package/src/prompts/tools/browser.md +1 -0
- package/src/prompts/tools/chunk-edit.md +58 -179
- package/src/prompts/tools/debug.md +4 -5
- package/src/prompts/tools/exit-plan-mode.md +4 -5
- package/src/prompts/tools/find.md +4 -8
- package/src/prompts/tools/github.md +18 -0
- package/src/prompts/tools/grep.md +8 -8
- package/src/prompts/tools/hashline.md +22 -89
- package/src/prompts/tools/{gemini-image.md → image-gen.md} +1 -1
- package/src/prompts/tools/inspect-image.md +6 -6
- package/src/prompts/tools/lsp.md +6 -0
- package/src/prompts/tools/patch.md +12 -19
- package/src/prompts/tools/python.md +3 -2
- package/src/prompts/tools/read-chunk.md +46 -8
- package/src/prompts/tools/read.md +9 -6
- package/src/prompts/tools/ssh.md +8 -17
- package/src/prompts/tools/todo-write.md +54 -41
- package/src/sdk.ts +22 -14
- package/src/session/agent-session.ts +61 -22
- package/src/session/session-manager.ts +228 -57
- package/src/session/streaming-output.ts +11 -0
- package/src/system-prompt.ts +7 -2
- package/src/task/executor.ts +44 -48
- package/src/task/render.ts +11 -13
- package/src/tools/ask.ts +7 -7
- package/src/tools/ast-edit.ts +45 -41
- package/src/tools/ast-grep.ts +77 -85
- package/src/tools/bash.ts +21 -9
- package/src/tools/browser.ts +32 -30
- package/src/tools/calculator.ts +4 -4
- package/src/tools/cancel-job.ts +1 -1
- package/src/tools/checkpoint.ts +2 -2
- package/src/tools/debug.ts +41 -37
- package/src/tools/exit-plan-mode.ts +1 -1
- package/src/tools/find.ts +4 -4
- package/src/tools/gh-renderer.ts +12 -4
- package/src/tools/gh.ts +514 -712
- package/src/tools/grep.ts +115 -130
- package/src/tools/{gemini-image.ts → image-gen.ts} +459 -60
- package/src/tools/index.ts +14 -32
- package/src/tools/inspect-image.ts +3 -3
- package/src/tools/json-tree.ts +114 -114
- package/src/tools/match-line-format.ts +9 -8
- package/src/tools/notebook.ts +8 -7
- package/src/tools/poll-tool.ts +2 -1
- package/src/tools/python.ts +9 -23
- package/src/tools/read.ts +32 -21
- package/src/tools/render-mermaid.ts +1 -1
- package/src/tools/render-utils.ts +18 -0
- package/src/tools/renderers.ts +2 -2
- package/src/tools/report-tool-issue.ts +3 -2
- package/src/tools/resolve.ts +1 -1
- package/src/tools/review.ts +12 -10
- package/src/tools/search-tool-bm25.ts +2 -4
- package/src/tools/sqlite-reader.ts +116 -3
- package/src/tools/ssh.ts +4 -4
- package/src/tools/todo-write.ts +172 -147
- package/src/tools/vim.ts +14 -15
- package/src/tools/write.ts +4 -4
- package/src/tools/{submit-result.ts → yield.ts} +11 -13
- package/src/utils/edit-mode.ts +2 -1
- package/src/utils/file-display-mode.ts +10 -5
- package/src/utils/git.ts +9 -5
- package/src/utils/shell-snapshot.ts +2 -3
- package/src/vim/render.ts +4 -4
- package/src/web/search/providers/codex.ts +129 -6
- package/src/prompts/system/subagent-submit-reminder.md +0 -11
- package/src/prompts/tools/gh-issue-view.md +0 -11
- package/src/prompts/tools/gh-pr-checkout.md +0 -12
- package/src/prompts/tools/gh-pr-diff.md +0 -12
- package/src/prompts/tools/gh-pr-push.md +0 -11
- package/src/prompts/tools/gh-pr-view.md +0 -11
- package/src/prompts/tools/gh-repo-view.md +0 -11
- package/src/prompts/tools/gh-run-watch.md +0 -12
- package/src/prompts/tools/gh-search-issues.md +0 -11
- package/src/prompts/tools/gh-search-prs.md +0 -11
|
@@ -14,14 +14,7 @@ import {
|
|
|
14
14
|
type TUI,
|
|
15
15
|
} from "@oh-my-pi/pi-tui";
|
|
16
16
|
import { getProjectDir, logger } from "@oh-my-pi/pi-utils";
|
|
17
|
-
import {
|
|
18
|
-
computeEditDiff,
|
|
19
|
-
computeHashlineDiff,
|
|
20
|
-
computePatchDiff,
|
|
21
|
-
type DiffError,
|
|
22
|
-
type DiffResult,
|
|
23
|
-
expandApplyPatchToEntries,
|
|
24
|
-
} from "../../edit";
|
|
17
|
+
import { EDIT_MODE_STRATEGIES, type EditMode, type PerFileDiffPreview } from "../../edit";
|
|
25
18
|
import type { Theme } from "../../modes/theme/theme";
|
|
26
19
|
import { theme } from "../../modes/theme/theme";
|
|
27
20
|
import { BASH_DEFAULT_PREVIEW_LINES } from "../../tools/bash";
|
|
@@ -34,7 +27,6 @@ import {
|
|
|
34
27
|
JSON_TREE_SCALAR_LEN_COLLAPSED,
|
|
35
28
|
JSON_TREE_SCALAR_LEN_EXPANDED,
|
|
36
29
|
renderJsonTreeLines,
|
|
37
|
-
stripInternalArgs,
|
|
38
30
|
} from "../../tools/json-tree";
|
|
39
31
|
import { PYTHON_DEFAULT_PREVIEW_LINES } from "../../tools/python";
|
|
40
32
|
import { formatExpandHint, replaceTabs, resolveImageOptions, truncateToWidth } from "../../tools/render-utils";
|
|
@@ -65,6 +57,12 @@ function isEditLikeToolName(toolName: string): boolean {
|
|
|
65
57
|
return toolName === "edit" || toolName === "apply_patch";
|
|
66
58
|
}
|
|
67
59
|
|
|
60
|
+
function resolveEditModeForTool(toolName: string, tool: AgentTool | undefined): EditMode | undefined {
|
|
61
|
+
if (toolName === "apply_patch") return "apply_patch";
|
|
62
|
+
if (toolName !== "edit") return undefined;
|
|
63
|
+
return (tool as { mode?: EditMode } | undefined)?.mode;
|
|
64
|
+
}
|
|
65
|
+
|
|
68
66
|
export interface ToolExecutionOptions {
|
|
69
67
|
showImages?: boolean; // default: true (only used if terminal supports images)
|
|
70
68
|
editFuzzyThreshold?: number;
|
|
@@ -111,9 +109,12 @@ export class ToolExecutionComponent extends Container {
|
|
|
111
109
|
isError?: boolean;
|
|
112
110
|
details?: any;
|
|
113
111
|
};
|
|
114
|
-
//
|
|
115
|
-
#
|
|
116
|
-
#
|
|
112
|
+
// Edit preview state (single-file for legacy modes, multi-file for chunk)
|
|
113
|
+
#editMode?: EditMode;
|
|
114
|
+
#editDiffPreview?: PerFileDiffPreview[];
|
|
115
|
+
#editDiffScheduleTimer?: NodeJS.Timeout;
|
|
116
|
+
#editDiffAbort?: AbortController;
|
|
117
|
+
#editDiffLastArgsKey?: string;
|
|
117
118
|
// Cached converted images for Kitty protocol (which requires PNG), keyed by index
|
|
118
119
|
#convertedImages: Map<number, { data: string; mimeType: string }> = new Map();
|
|
119
120
|
// Spinner animation for partial task results
|
|
@@ -166,116 +167,98 @@ export class ToolExecutionComponent extends Container {
|
|
|
166
167
|
this.addChild(this.#contentText);
|
|
167
168
|
}
|
|
168
169
|
|
|
170
|
+
this.#editMode = resolveEditModeForTool(toolName, tool);
|
|
171
|
+
|
|
169
172
|
this.#updateDisplay();
|
|
173
|
+
this.#schedulePreviewDiff(0);
|
|
170
174
|
}
|
|
171
175
|
|
|
172
176
|
updateArgs(args: any, _toolCallId?: string): void {
|
|
173
177
|
this.#args = cloneToolArgs(args);
|
|
174
178
|
this.#updateSpinnerAnimation();
|
|
179
|
+
this.#schedulePreviewDiff();
|
|
175
180
|
this.#updateDisplay();
|
|
176
181
|
}
|
|
177
182
|
|
|
178
183
|
/**
|
|
179
184
|
* Signal that args are complete (tool is about to execute).
|
|
180
|
-
* This triggers diff computation for edit-like tools.
|
|
185
|
+
* This triggers an immediate final diff computation for edit-like tools.
|
|
181
186
|
*/
|
|
182
187
|
setArgsComplete(_toolCallId?: string): void {
|
|
183
188
|
this.#argsComplete = true;
|
|
184
189
|
this.#updateSpinnerAnimation();
|
|
185
|
-
this.#
|
|
190
|
+
this.#schedulePreviewDiff(0);
|
|
186
191
|
}
|
|
187
192
|
|
|
188
193
|
/**
|
|
189
|
-
*
|
|
190
|
-
*
|
|
194
|
+
* Schedule a debounced compute of the streaming edit-diff preview.
|
|
195
|
+
* `delayMs === 0` runs immediately (used on construction and on
|
|
196
|
+
* `setArgsComplete`). All other calls coalesce to a trailing-edge timer.
|
|
191
197
|
*/
|
|
192
|
-
#
|
|
193
|
-
if (!
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const input = this.#args.input;
|
|
202
|
-
const argsKey = JSON.stringify({ input });
|
|
203
|
-
if (this.#editDiffArgsKey === argsKey) return;
|
|
204
|
-
this.#editDiffArgsKey = argsKey;
|
|
205
|
-
|
|
206
|
-
try {
|
|
207
|
-
const first = expandApplyPatchToEntries({ input })[0];
|
|
208
|
-
if (!first?.path) return;
|
|
209
|
-
computePatchDiff({ ...first, op: first.op ?? "update" }, this.#cwd, {
|
|
210
|
-
fuzzyThreshold: this.#editFuzzyThreshold,
|
|
211
|
-
allowFuzzy: this.#editAllowFuzzy,
|
|
212
|
-
}).then(result => {
|
|
213
|
-
if (this.#editDiffArgsKey === argsKey) {
|
|
214
|
-
this.#editDiffPreview = result;
|
|
215
|
-
this.#updateDisplay();
|
|
216
|
-
this.#ui.requestRender();
|
|
217
|
-
}
|
|
218
|
-
});
|
|
219
|
-
} catch (err) {
|
|
220
|
-
this.#editDiffPreview = { error: err instanceof Error ? err.message : String(err) };
|
|
221
|
-
this.#updateDisplay();
|
|
222
|
-
this.#ui.requestRender();
|
|
223
|
-
}
|
|
198
|
+
#schedulePreviewDiff(delayMs = 80): void {
|
|
199
|
+
if (!this.#editMode) return;
|
|
200
|
+
if (this.#editDiffScheduleTimer) {
|
|
201
|
+
clearTimeout(this.#editDiffScheduleTimer);
|
|
202
|
+
this.#editDiffScheduleTimer = undefined;
|
|
203
|
+
}
|
|
204
|
+
if (delayMs === 0) {
|
|
205
|
+
void this.#runPreviewDiff();
|
|
224
206
|
return;
|
|
225
207
|
}
|
|
208
|
+
this.#editDiffScheduleTimer = setTimeout(() => {
|
|
209
|
+
this.#editDiffScheduleTimer = undefined;
|
|
210
|
+
void this.#runPreviewDiff();
|
|
211
|
+
}, delayMs);
|
|
212
|
+
}
|
|
226
213
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
if (
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
214
|
+
async #runPreviewDiff(): Promise<void> {
|
|
215
|
+
const editMode = this.#editMode;
|
|
216
|
+
if (!editMode) return;
|
|
217
|
+
const strategy = EDIT_MODE_STRATEGIES[editMode];
|
|
218
|
+
if (!strategy) return;
|
|
219
|
+
|
|
220
|
+
const args = this.#args;
|
|
221
|
+
if (args == null || typeof args !== "object") return;
|
|
222
|
+
|
|
223
|
+
const partialJson = (args as { __partialJson?: string }).__partialJson;
|
|
224
|
+
let effectiveArgs: unknown;
|
|
225
|
+
try {
|
|
226
|
+
effectiveArgs = strategy.extractCompleteEdits(args, partialJson);
|
|
227
|
+
} catch {
|
|
228
|
+
effectiveArgs = args;
|
|
229
|
+
}
|
|
239
230
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
231
|
+
// Coalesce duplicate computes for identical args.
|
|
232
|
+
let argsKey: string;
|
|
233
|
+
try {
|
|
234
|
+
argsKey = JSON.stringify(effectiveArgs);
|
|
235
|
+
} catch {
|
|
236
|
+
argsKey = String(Date.now());
|
|
237
|
+
}
|
|
238
|
+
if (argsKey === this.#editDiffLastArgsKey) return;
|
|
239
|
+
this.#editDiffLastArgsKey = argsKey;
|
|
247
240
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
241
|
+
this.#editDiffAbort?.abort();
|
|
242
|
+
const controller = new AbortController();
|
|
243
|
+
this.#editDiffAbort = controller;
|
|
251
244
|
|
|
252
|
-
|
|
245
|
+
try {
|
|
246
|
+
const previews = await strategy.computeDiffPreview(effectiveArgs, {
|
|
247
|
+
cwd: this.#cwd,
|
|
248
|
+
signal: controller.signal,
|
|
253
249
|
fuzzyThreshold: this.#editFuzzyThreshold,
|
|
254
250
|
allowFuzzy: this.#editAllowFuzzy,
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
this.#editDiffArgsKey = argsKey;
|
|
266
|
-
|
|
267
|
-
computeHashlineDiff({ path, edits: fileEdits, move }, this.#cwd).then(result =>
|
|
268
|
-
this.#applyEditDiffResult(argsKey, result),
|
|
269
|
-
);
|
|
251
|
+
});
|
|
252
|
+
if (controller.signal.aborted) return;
|
|
253
|
+
if (previews) {
|
|
254
|
+
this.#editDiffPreview = previews;
|
|
255
|
+
this.#updateDisplay();
|
|
256
|
+
this.#ui.requestRender();
|
|
257
|
+
}
|
|
258
|
+
} catch (err) {
|
|
259
|
+
if (controller.signal.aborted) return;
|
|
260
|
+
logger.warn("Edit preview diff failed", { tool: this.#toolName, error: String(err) });
|
|
270
261
|
}
|
|
271
|
-
// Chunk mode edits don't have a pre-execution diff preview
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
#applyEditDiffResult(argsKey: string, result: DiffResult | DiffError): void {
|
|
275
|
-
if (this.#editDiffArgsKey !== argsKey) return;
|
|
276
|
-
this.#editDiffPreview = result;
|
|
277
|
-
this.#updateDisplay();
|
|
278
|
-
this.#ui.requestRender();
|
|
279
262
|
}
|
|
280
263
|
|
|
281
264
|
updateResult(
|
|
@@ -378,6 +361,12 @@ export class ToolExecutionComponent extends Container {
|
|
|
378
361
|
this.#spinnerInterval = undefined;
|
|
379
362
|
this.#spinnerFrame = undefined;
|
|
380
363
|
}
|
|
364
|
+
if (this.#editDiffScheduleTimer) {
|
|
365
|
+
clearTimeout(this.#editDiffScheduleTimer);
|
|
366
|
+
this.#editDiffScheduleTimer = undefined;
|
|
367
|
+
}
|
|
368
|
+
this.#editDiffAbort?.abort();
|
|
369
|
+
this.#editDiffAbort = undefined;
|
|
381
370
|
}
|
|
382
371
|
|
|
383
372
|
setExpanded(expanded: boolean): void {
|
|
@@ -549,6 +538,9 @@ export class ToolExecutionComponent extends Container {
|
|
|
549
538
|
this.#contentBox.setBgFn(renderer.inline ? undefined : bgFn);
|
|
550
539
|
this.#contentBox.clear();
|
|
551
540
|
|
|
541
|
+
const renderContext = this.#buildRenderContext();
|
|
542
|
+
this.#renderState.renderContext = renderContext;
|
|
543
|
+
|
|
552
544
|
const shouldRenderCall = !this.#result || !renderer.mergeCallAndResult;
|
|
553
545
|
if (shouldRenderCall) {
|
|
554
546
|
// Render call component
|
|
@@ -567,10 +559,6 @@ export class ToolExecutionComponent extends Container {
|
|
|
567
559
|
// Render result component if we have a result
|
|
568
560
|
if (this.#result) {
|
|
569
561
|
try {
|
|
570
|
-
// Build render context for tools that need extra state
|
|
571
|
-
const renderContext = this.#buildRenderContext();
|
|
572
|
-
this.#renderState.renderContext = renderContext;
|
|
573
|
-
|
|
574
562
|
const resultComponent = renderer.renderResult(
|
|
575
563
|
{
|
|
576
564
|
content: this.#result.content as any,
|
|
@@ -646,10 +634,20 @@ export class ToolExecutionComponent extends Container {
|
|
|
646
634
|
if (!isEditLikeToolName(this.#toolName)) {
|
|
647
635
|
return this.#args;
|
|
648
636
|
}
|
|
649
|
-
|
|
637
|
+
const previews = this.#editDiffPreview;
|
|
638
|
+
if (!previews || previews.length === 0) {
|
|
650
639
|
return this.#args;
|
|
651
640
|
}
|
|
652
|
-
|
|
641
|
+
// Single-file previews feed the existing `previewDiff` channel consumed
|
|
642
|
+
// by `formatStreamingDiff` in the renderer. Multi-file previews are
|
|
643
|
+
// piped via `renderContext.perFileDiffPreview`, so the args we hand to
|
|
644
|
+
// `renderCall` only need the first file's diff to preserve prior
|
|
645
|
+
// single-file behavior.
|
|
646
|
+
const first = previews[0];
|
|
647
|
+
if (!first?.diff) {
|
|
648
|
+
return this.#args;
|
|
649
|
+
}
|
|
650
|
+
return { ...(this.#args as Record<string, unknown>), previewDiff: first.diff };
|
|
653
651
|
}
|
|
654
652
|
|
|
655
653
|
/**
|
|
@@ -680,8 +678,19 @@ export class ToolExecutionComponent extends Container {
|
|
|
680
678
|
context.previewLines = PYTHON_DEFAULT_PREVIEW_LINES;
|
|
681
679
|
context.timeout = normalizeTimeoutSeconds(this.#args?.timeout, 600);
|
|
682
680
|
} else if (isEditLikeToolName(this.#toolName)) {
|
|
683
|
-
|
|
684
|
-
|
|
681
|
+
context.editMode = this.#editMode;
|
|
682
|
+
const previews = this.#editDiffPreview;
|
|
683
|
+
if (previews && previews.length > 0) {
|
|
684
|
+
const first = previews[0];
|
|
685
|
+
if (first?.diff || first?.error) {
|
|
686
|
+
context.editDiffPreview = first.error
|
|
687
|
+
? { error: first.error }
|
|
688
|
+
: { diff: first.diff ?? "", firstChangedLine: first.firstChangedLine };
|
|
689
|
+
}
|
|
690
|
+
if (previews.length > 1) {
|
|
691
|
+
context.perFileDiffPreview = previews;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
685
694
|
context.renderDiff = renderDiff;
|
|
686
695
|
}
|
|
687
696
|
|
|
@@ -733,9 +742,7 @@ export class ToolExecutionComponent extends Container {
|
|
|
733
742
|
lines.push("");
|
|
734
743
|
lines.push(theme.fg("dim", "Args"));
|
|
735
744
|
const tree = renderJsonTreeLines(
|
|
736
|
-
this.#args
|
|
737
|
-
? stripInternalArgs(this.#args as Record<string, unknown>)
|
|
738
|
-
: this.#args,
|
|
745
|
+
this.#args,
|
|
739
746
|
theme,
|
|
740
747
|
JSON_TREE_MAX_DEPTH_EXPANDED,
|
|
741
748
|
JSON_TREE_MAX_LINES_EXPANDED,
|
|
@@ -715,7 +715,7 @@ export class InputController {
|
|
|
715
715
|
return;
|
|
716
716
|
}
|
|
717
717
|
|
|
718
|
-
const currentText = this.ctx.editor.getText();
|
|
718
|
+
const currentText = this.ctx.editor.getExpandedText?.() ?? this.ctx.editor.getText();
|
|
719
719
|
|
|
720
720
|
let ttyHandle: fs.FileHandle | null = null;
|
|
721
721
|
try {
|
|
@@ -362,7 +362,7 @@ export class SelectorController {
|
|
|
362
362
|
}
|
|
363
363
|
break;
|
|
364
364
|
case "providers.image":
|
|
365
|
-
if (value === "auto" || value === "gemini" || value === "openrouter") {
|
|
365
|
+
if (value === "auto" || value === "openai" || value === "gemini" || value === "openrouter") {
|
|
366
366
|
setPreferredImageProvider(value);
|
|
367
367
|
}
|
|
368
368
|
break;
|
|
@@ -63,7 +63,6 @@ import { SelectorController } from "./controllers/selector-controller";
|
|
|
63
63
|
import { SSHCommandController } from "./controllers/ssh-command-controller";
|
|
64
64
|
import { OAuthManualInputManager } from "./oauth-manual-input";
|
|
65
65
|
import { SessionObserverRegistry } from "./session-observer-registry";
|
|
66
|
-
import { setMermaidRenderCallback } from "./theme/mermaid-cache";
|
|
67
66
|
import type { Theme } from "./theme/theme";
|
|
68
67
|
import {
|
|
69
68
|
getEditorTheme,
|
|
@@ -220,7 +219,6 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
220
219
|
|
|
221
220
|
this.ui = new TUI(new ProcessTerminal(), settings.get("showHardwareCursor"));
|
|
222
221
|
this.ui.setClearOnShrink(settings.get("clearOnShrink"));
|
|
223
|
-
setMermaidRenderCallback(() => this.ui.requestRender());
|
|
224
222
|
this.chatContainer = new Container();
|
|
225
223
|
this.pendingMessagesContainer = new Container();
|
|
226
224
|
this.statusContainer = new Container();
|
package/src/modes/print-mode.ts
CHANGED
|
@@ -161,6 +161,14 @@ export async function runPrintMode(session: AgentSession, options: PrintModeOpti
|
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
+
if (
|
|
165
|
+
assistantMsg.errorMessage &&
|
|
166
|
+
assistantMsg.stopReason !== "error" &&
|
|
167
|
+
assistantMsg.stopReason !== "aborted"
|
|
168
|
+
) {
|
|
169
|
+
process.stderr.write(`${sanitizeText(assistantMsg.errorMessage)}\n`);
|
|
170
|
+
}
|
|
171
|
+
|
|
164
172
|
// Output text content
|
|
165
173
|
for (const content of assistantMsg.content) {
|
|
166
174
|
if (content.type === "text") {
|
|
@@ -1,63 +1,24 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { renderMermaidAsciiSafe } from "@oh-my-pi/pi-utils";
|
|
2
2
|
|
|
3
|
-
const cache = new Map<
|
|
3
|
+
const cache = new Map<string, string | null>();
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Set callback to trigger TUI re-render when mermaid ASCII renders become available.
|
|
9
|
-
*/
|
|
10
|
-
export function setMermaidRenderCallback(callback: (() => void) | null): void {
|
|
11
|
-
onRenderNeeded = callback;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Get a pre-rendered mermaid ASCII diagram by hash.
|
|
16
|
-
* Returns null if not cached or rendering failed.
|
|
17
|
-
*/
|
|
18
|
-
export function getMermaidAscii(hash: bigint | number): string | null {
|
|
19
|
-
return cache.get(hash) ?? null;
|
|
5
|
+
function normalizeMermaidSource(source: string): string {
|
|
6
|
+
return source.replace(/\r\n?/g, "\n").trim();
|
|
20
7
|
}
|
|
21
8
|
|
|
22
9
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
10
|
+
* Resolve mermaid ASCII from fenced block source text.
|
|
11
|
+
* Returns null when rendering fails, while memoizing failures to avoid repeated work.
|
|
25
12
|
*/
|
|
26
|
-
export function
|
|
27
|
-
const
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
let hasNew = false;
|
|
31
|
-
|
|
32
|
-
for (const { source, hash } of blocks) {
|
|
33
|
-
if (cache.has(hash)) continue;
|
|
34
|
-
|
|
35
|
-
const ascii = renderMermaidAsciiSafe(source);
|
|
36
|
-
if (ascii) {
|
|
37
|
-
cache.set(hash, ascii);
|
|
38
|
-
hasNew = true;
|
|
39
|
-
} else {
|
|
40
|
-
cache.set(hash, null);
|
|
41
|
-
}
|
|
13
|
+
export function resolveMermaidAscii(source: string): string | null {
|
|
14
|
+
const normalizedSource = normalizeMermaidSource(source);
|
|
15
|
+
if (cache.has(normalizedSource)) {
|
|
16
|
+
return cache.get(normalizedSource) ?? null;
|
|
42
17
|
}
|
|
43
18
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
} catch (error) {
|
|
48
|
-
logger.warn("Mermaid render callback failed", {
|
|
49
|
-
error: error instanceof Error ? error.message : String(error),
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Check if markdown contains mermaid blocks that aren't cached yet.
|
|
57
|
-
*/
|
|
58
|
-
export function hasPendingMermaid(markdown: string): boolean {
|
|
59
|
-
const blocks = extractMermaidBlocks(markdown);
|
|
60
|
-
return blocks.some(({ hash }) => !cache.has(hash));
|
|
19
|
+
const ascii = normalizedSource ? renderMermaidAsciiSafe(normalizedSource) : null;
|
|
20
|
+
cache.set(normalizedSource, ascii);
|
|
21
|
+
return ascii;
|
|
61
22
|
}
|
|
62
23
|
|
|
63
24
|
/**
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -18,7 +18,7 @@ import chalk from "chalk";
|
|
|
18
18
|
import darkThemeJson from "./dark.json" with { type: "json" };
|
|
19
19
|
import { defaultThemes } from "./defaults";
|
|
20
20
|
import lightThemeJson from "./light.json" with { type: "json" };
|
|
21
|
-
import {
|
|
21
|
+
import { resolveMermaidAscii } from "./mermaid-cache";
|
|
22
22
|
|
|
23
23
|
export { getLanguageFromPath } from "../../utils/lang-from-path";
|
|
24
24
|
|
|
@@ -2339,7 +2339,7 @@ export function getMarkdownTheme(): MarkdownTheme {
|
|
|
2339
2339
|
underline: (text: string) => theme.underline(text),
|
|
2340
2340
|
strikethrough: (text: string) => chalk.strikethrough(text),
|
|
2341
2341
|
symbols: getSymbolTheme(),
|
|
2342
|
-
|
|
2342
|
+
resolveMermaidAscii,
|
|
2343
2343
|
highlightCode: (code: string, lang?: string): string[] => {
|
|
2344
2344
|
const validLang = lang && nativeSupportsLanguage(lang) ? lang : undefined;
|
|
2345
2345
|
try {
|
|
@@ -98,7 +98,7 @@ Before acting, determine what kind of question this is:
|
|
|
98
98
|
- For API signatures: copy verbatim from source. You **MUST NOT** paraphrase or reconstruct from memory.
|
|
99
99
|
|
|
100
100
|
## 5. Report
|
|
101
|
-
- Call `
|
|
101
|
+
- Call `yield` with structured findings.
|
|
102
102
|
- Every `sources` entry **MUST** include a verbatim excerpt.
|
|
103
103
|
- The `api` array **MUST** contain exact signatures copied from source.
|
|
104
104
|
- Clean up cloned repos: `rm -rf /tmp/librarian-*`.
|
|
@@ -44,7 +44,7 @@ output:
|
|
|
44
44
|
type: number
|
|
45
45
|
file_path:
|
|
46
46
|
metadata:
|
|
47
|
-
description:
|
|
47
|
+
description: Path to affected file
|
|
48
48
|
type: string
|
|
49
49
|
line_start:
|
|
50
50
|
metadata:
|
|
@@ -63,7 +63,7 @@ Your goal is to identify bugs the author would want fixed before merge.
|
|
|
63
63
|
1. Run `git diff` (or `gh pr diff <number>`) to view patch
|
|
64
64
|
2. Read modified files for full context
|
|
65
65
|
3. Call `report_finding` per issue
|
|
66
|
-
4. Call `
|
|
66
|
+
4. Call `yield` with verdict
|
|
67
67
|
|
|
68
68
|
Bash is read-only: `git diff`, `git log`, `git show`, `gh pr diff`. You **MUST NOT** make file edits or trigger builds.
|
|
69
69
|
</procedure>
|
|
@@ -108,10 +108,10 @@ Each `report_finding` requires:
|
|
|
108
108
|
- `body`: One paragraph
|
|
109
109
|
- `priority`: 0-3
|
|
110
110
|
- `confidence`: 0.0-1.0
|
|
111
|
-
- `file_path`:
|
|
111
|
+
- `file_path`: Path to affected file
|
|
112
112
|
- `line_start`, `line_end`: Range ≤10 lines, must overlap diff
|
|
113
113
|
|
|
114
|
-
Final `
|
|
114
|
+
Final `yield` call (payload under `result.data`):
|
|
115
115
|
- `result.data.overall_correctness`: "correct" (no bugs/blockers) or "incorrect"
|
|
116
116
|
- `result.data.explanation`: Plain text, 1-3 sentences summarizing verdict. Don't repeat findings (captured via `report_finding`).
|
|
117
117
|
- `result.data.confidence`: 0.0-1.0
|
|
@@ -4,7 +4,7 @@ Do not stop after a single fix attempt.
|
|
|
4
4
|
</critical>
|
|
5
5
|
|
|
6
6
|
<instruction>
|
|
7
|
-
- Prefer `
|
|
7
|
+
- Prefer the `github` tool with `op: run_watch` and no other arguments if that tool is available.
|
|
8
8
|
- Otherwise use `gh` cli.
|
|
9
9
|
- Use the workflow runs for the current HEAD commit as the source of truth after each push.
|
|
10
10
|
</instruction>
|
|
@@ -40,7 +40,7 @@ Reviewer **MUST**:
|
|
|
40
40
|
2. {{#if skipDiff}}**MUST** run `git diff`/`git show` for assigned files{{else}}**MUST** use diff hunks below (**MUST NOT** re-run git diff){{/if}}
|
|
41
41
|
3. **MAY** read full file context as needed via `read`
|
|
42
42
|
4. Call `report_finding` per issue
|
|
43
|
-
5. Call `
|
|
43
|
+
5. Call `yield` with verdict when done
|
|
44
44
|
|
|
45
45
|
{{#if skipDiff}}
|
|
46
46
|
### Diff Previews
|
|
@@ -15,9 +15,9 @@ If you need additional information, you can find your conversation with the user
|
|
|
15
15
|
{{/if}}
|
|
16
16
|
|
|
17
17
|
{{SECTION_SEPARATOR "Closure"}}
|
|
18
|
-
No TODO tracking, no progress updates. Execute, call `
|
|
18
|
+
No TODO tracking, no progress updates. Execute, call `yield`, done.
|
|
19
19
|
|
|
20
|
-
When finished, you **MUST** call `
|
|
20
|
+
When finished, you **MUST** call `yield` exactly once. This is like writing to a ticket, provide what is required, and close it.
|
|
21
21
|
|
|
22
22
|
This is your only way to return a result. You **MUST NOT** put JSON in plain text, and you **MUST NOT** substitute a text summary for the structured `result.data` parameter.
|
|
23
23
|
|
|
@@ -29,7 +29,7 @@ Your result **MUST** match this TypeScript interface:
|
|
|
29
29
|
{{/if}}
|
|
30
30
|
|
|
31
31
|
{{SECTION_SEPARATOR "Giving Up"}}
|
|
32
|
-
Giving up is a last resort. If truly blocked, you **MUST** call `
|
|
32
|
+
Giving up is a last resort. If truly blocked, you **MUST** call `yield` exactly once with `result.error` describing what you tried and the exact blocker.
|
|
33
33
|
You **MUST NOT** give up due to uncertainty, missing information obtainable via tools or repo context, or needing a design decision you can derive yourself.
|
|
34
34
|
|
|
35
35
|
You **MUST** keep going until this ticket is closed. This matters.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<system-reminder>
|
|
2
|
+
You stopped without calling yield. This is reminder {{retryCount}} of {{maxRetries}}.
|
|
3
|
+
|
|
4
|
+
You **MUST** call yield as your only action now. Choose one:
|
|
5
|
+
- If task is complete: call yield with your result in `result.data`
|
|
6
|
+
- If task failed: call yield with `result.error` describing what happened
|
|
7
|
+
|
|
8
|
+
You **MUST NOT** give up if you can still complete the task through exploration (using available tools or repo context). If you submit an error, you **MUST** include what you tried and the exact blocker.
|
|
9
|
+
|
|
10
|
+
You **MUST NOT** output text without a tool call. You **MUST** call yield to finish.
|
|
11
|
+
</system-reminder>
|
|
@@ -187,13 +187,16 @@ You **MUST NOT** use Python or Bash when a specialized tool exists.
|
|
|
187
187
|
{{/ifAny}}
|
|
188
188
|
|
|
189
189
|
{{#ifAny (includes tools "read") (includes tools "write") (includes tools "grep") (includes tools "find") (includes tools "edit")}}
|
|
190
|
-
{{#has tools "read"}}- Use `read`, not `cat
|
|
190
|
+
{{#has tools "read"}}- Use `read`, not `cat`.{{/has}}
|
|
191
191
|
{{#has tools "write"}}- Use `write`, not shell redirection.{{/has}}
|
|
192
192
|
{{#has tools "grep"}}- Use `grep`, not shell regex search.{{/has}}
|
|
193
193
|
{{#has tools "find"}}- Use `find`, not shell file globbing.{{/has}}
|
|
194
194
|
{{#has tools "edit"}}- Use `edit` for surgical text changes, not `sed`.{{/has}}
|
|
195
195
|
{{/ifAny}}
|
|
196
196
|
|
|
197
|
+
### Paths
|
|
198
|
+
- For tools that take a `path` (or path-like field), prefer cwd-relative paths for files inside the cwd. Use absolute paths only when targeting files outside the cwd or when expanding `~`.
|
|
199
|
+
|
|
197
200
|
{{#has tools "lsp"}}
|
|
198
201
|
### LSP guidance
|
|
199
202
|
Use semantic tools for semantic questions:
|
package/src/prompts/tools/ask.md
CHANGED
|
@@ -21,8 +21,9 @@ Asks user when you need clarification or input during task execution.
|
|
|
21
21
|
- **Do NOT include "Other" option** — UI automatically adds "Other (type your own)" to every question.
|
|
22
22
|
</critical>
|
|
23
23
|
|
|
24
|
-
<
|
|
24
|
+
<examples>
|
|
25
|
+
# Single question
|
|
25
26
|
question: "Which authentication method should this API use?"
|
|
26
27
|
options: [{"label": "JWT"}, {"label": "OAuth2"}, {"label": "Session cookies"}]
|
|
27
28
|
recommended: 0
|
|
28
|
-
</
|
|
29
|
+
</examples>
|