@oh-my-pi/pi-coding-agent 15.10.0 → 15.10.2
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 +142 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/cli/startup-cwd.d.ts +2 -0
- package/dist/types/commands/launch.d.ts +3 -0
- package/dist/types/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +2 -2
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/config/api-key-resolver.d.ts +34 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-provider-priority.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +17 -1
- package/dist/types/config/model-resolver.d.ts +4 -1
- package/dist/types/config/settings-schema.d.ts +9 -0
- package/dist/types/config/settings.d.ts +7 -2
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/debug/report-bundle.d.ts +3 -0
- package/dist/types/edit/file-snapshot-store.d.ts +18 -10
- package/dist/types/eval/py/__tests__/prelude.test.d.ts +1 -0
- package/dist/types/extensibility/extensions/types.d.ts +4 -1
- package/dist/types/lsp/client.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/main.d.ts +3 -9
- package/dist/types/mcp/tool-bridge.d.ts +2 -0
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +4 -1
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/status-line.d.ts +2 -0
- package/dist/types/modes/components/transcript-container.d.ts +11 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +17 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +16 -5
- package/dist/types/modes/magic-keywords.d.ts +1 -1
- package/dist/types/modes/markdown-prose.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +21 -5
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/modes/workflow.d.ts +3 -3
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/auth-storage.d.ts +1 -1
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +8 -3
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +17 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ast-edit.d.ts +3 -0
- package/dist/types/tools/ast-grep.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/eval.d.ts +8 -0
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/gh-cache-invalidation.d.ts +6 -0
- package/dist/types/tools/github-cache.d.ts +12 -0
- package/dist/types/tools/grouped-file-output.d.ts +95 -12
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +5 -9
- package/dist/types/tools/search.d.ts +6 -2
- package/dist/types/tools/sqlite-reader.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +3 -2
- package/dist/types/tools/write.d.ts +3 -0
- package/dist/types/tools/yield.d.ts +8 -0
- package/dist/types/tui/output-block.d.ts +16 -4
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- package/package.json +9 -9
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/args.ts +3 -1
- package/src/cli/dry-balance-cli.ts +54 -21
- package/src/cli/gallery-cli.ts +4 -1
- package/src/cli/gallery-fixtures/misc.ts +29 -0
- package/src/cli/startup-cwd.ts +68 -0
- package/src/commands/launch.ts +3 -0
- package/src/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +2 -2
- package/src/commit/changelog/generate.ts +2 -2
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +36 -11
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/model-provider-priority.ts +55 -0
- package/src/config/model-registry.ts +29 -24
- package/src/config/model-resolver.ts +39 -7
- package/src/config/settings-schema.ts +10 -0
- package/src/config/settings.ts +106 -43
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +1 -0
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +47 -53
- package/src/debug/raw-sse-buffer.ts +7 -4
- package/src/debug/report-bundle.ts +9 -0
- package/src/edit/file-snapshot-store.ts +33 -1
- package/src/edit/hashline/filesystem.ts +2 -1
- package/src/edit/renderer.ts +82 -78
- package/src/eval/__tests__/llm-bridge.test.ts +110 -31
- package/src/eval/js/context-manager.ts +32 -15
- package/src/eval/llm-bridge.ts +22 -6
- package/src/eval/py/__tests__/prelude.test.ts +19 -0
- package/src/eval/py/executor.ts +23 -11
- package/src/eval/py/prelude.py +1 -1
- package/src/extensibility/extensions/types.ts +10 -1
- package/src/goals/tools/goal-tool.ts +36 -26
- package/src/internal-urls/docs-index.generated.ts +8 -8
- package/src/lsp/client.ts +23 -11
- package/src/lsp/config.ts +11 -1
- package/src/lsp/index.ts +61 -9
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +100 -72
- package/src/mcp/tool-bridge.ts +2 -0
- package/src/memories/index.ts +14 -7
- package/src/mnemopi/backend.ts +5 -1
- package/src/modes/acp/acp-agent.ts +33 -26
- package/src/modes/components/assistant-message.ts +2 -9
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/copy-selector.ts +1 -44
- package/src/modes/components/custom-editor.ts +164 -109
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/model-selector.ts +59 -13
- package/src/modes/components/oauth-selector.ts +33 -7
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +799 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/read-tool-group.ts +20 -4
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/status-line.ts +19 -4
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +68 -88
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +2 -3
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +57 -55
- package/src/modes/controllers/event-controller.ts +67 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +170 -126
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +23 -25
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/interactive-mode.ts +274 -112
- package/src/modes/magic-keywords.ts +1 -1
- package/src/modes/markdown-prose.ts +1 -1
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/shimmer.ts +20 -9
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +21 -7
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/modes/workflow.ts +10 -10
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/plan-mode-active.md +67 -58
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/bash.md +9 -0
- package/src/prompts/tools/browser.md +1 -1
- package/src/prompts/tools/eval.md +2 -1
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +37 -46
- package/src/session/agent-session.ts +119 -18
- package/src/session/auth-storage.ts +2 -0
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +109 -28
- package/src/slash-commands/builtin-registry.ts +36 -9
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +76 -38
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +211 -147
- package/src/tools/archive-reader.ts +64 -0
- package/src/tools/ask.ts +119 -164
- package/src/tools/ast-edit.ts +98 -71
- package/src/tools/ast-grep.ts +37 -43
- package/src/tools/bash.ts +57 -6
- package/src/tools/browser/tab-supervisor.ts +13 -1
- package/src/tools/browser/tab-worker.ts +33 -4
- package/src/tools/debug.ts +20 -8
- package/src/tools/eval.ts +13 -2
- package/src/tools/fetch.ts +297 -7
- package/src/tools/find.ts +51 -30
- package/src/tools/gh-cache-invalidation.ts +200 -0
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/github-cache.ts +25 -0
- package/src/tools/grouped-file-output.ts +272 -48
- package/src/tools/image-gen.ts +150 -103
- package/src/tools/inspect-image-renderer.ts +63 -41
- package/src/tools/inspect-image.ts +10 -3
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/path-utils.ts +28 -2
- package/src/tools/plan-mode-guard.ts +66 -39
- package/src/tools/read.ts +48 -28
- package/src/tools/render-utils.ts +21 -37
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +118 -81
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +118 -52
- package/src/tools/write.ts +83 -64
- package/src/tools/yield.ts +10 -1
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +11 -3
- package/src/utils/enhanced-paste.ts +230 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/codex.ts +37 -8
- package/src/web/search/providers/exa.ts +11 -3
- package/src/web/search/providers/kimi.ts +28 -17
- package/src/web/search/providers/parallel.ts +35 -24
- package/src/web/search/providers/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
package/src/debug/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from "@oh-my-pi/pi-tui";
|
|
20
20
|
import { getSessionsDir } from "@oh-my-pi/pi-utils";
|
|
21
21
|
import { DynamicBorder } from "../modes/components/dynamic-border";
|
|
22
|
+
import { TranscriptBlock } from "../modes/components/transcript-container";
|
|
22
23
|
import { getSelectListTheme, getSymbolTheme, theme } from "../modes/theme/theme";
|
|
23
24
|
import type { InteractiveModeContext } from "../modes/types";
|
|
24
25
|
import { formatBytes } from "../tools/render-utils";
|
|
@@ -150,13 +151,13 @@ export class DebugSelectorComponent extends Container {
|
|
|
150
151
|
}
|
|
151
152
|
|
|
152
153
|
// Show message and wait for keypress
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
154
|
+
const block = new TranscriptBlock();
|
|
155
|
+
block.addChild(new Text(theme.fg("accent", `${theme.status.info} CPU profiling started`), 1, 0));
|
|
156
|
+
block.addChild(new Spacer(1));
|
|
157
|
+
block.addChild(
|
|
157
158
|
new Text(theme.fg("muted", "Reproduce the performance issue, then press Enter to stop profiling."), 1, 0),
|
|
158
159
|
);
|
|
159
|
-
this.ctx.
|
|
160
|
+
this.ctx.present(block);
|
|
160
161
|
|
|
161
162
|
// Wait for Enter keypress
|
|
162
163
|
const { promise, resolve } = Promise.withResolvers<void>();
|
|
@@ -194,6 +195,7 @@ export class DebugSelectorComponent extends Container {
|
|
|
194
195
|
const result = await createReportBundle({
|
|
195
196
|
sessionFile: this.ctx.sessionManager.getSessionFile(),
|
|
196
197
|
settings: this.#getResolvedSettings(),
|
|
198
|
+
rawSseText: this.#getRawSseText(),
|
|
197
199
|
cpuProfile,
|
|
198
200
|
workProfile,
|
|
199
201
|
});
|
|
@@ -201,19 +203,16 @@ export class DebugSelectorComponent extends Container {
|
|
|
201
203
|
loader.stop();
|
|
202
204
|
this.ctx.statusContainer.clear();
|
|
203
205
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
);
|
|
208
|
-
this.ctx.
|
|
209
|
-
this.ctx.chatContainer.addChild(new Text(theme.fg("dim", `Files: ${result.files.length}`), 1, 0));
|
|
206
|
+
const block = new TranscriptBlock();
|
|
207
|
+
block.addChild(new Text(theme.fg("success", `${theme.status.success} Performance report saved`), 1, 0));
|
|
208
|
+
block.addChild(new Text(theme.fg("dim", formatFileHyperlink(result.path)), 1, 0));
|
|
209
|
+
block.addChild(new Text(theme.fg("dim", `Files: ${result.files.length}`), 1, 0));
|
|
210
|
+
this.ctx.present(block);
|
|
210
211
|
} catch (err) {
|
|
211
212
|
loader.stop();
|
|
212
213
|
this.ctx.statusContainer.clear();
|
|
213
214
|
this.ctx.showError(`Failed to create report: ${err instanceof Error ? err.message : String(err)}`);
|
|
214
215
|
}
|
|
215
|
-
|
|
216
|
-
this.ctx.ui.requestRender();
|
|
217
216
|
}
|
|
218
217
|
|
|
219
218
|
async #handleWorkReport(): Promise<void> {
|
|
@@ -231,15 +230,13 @@ export class DebugSelectorComponent extends Container {
|
|
|
231
230
|
|
|
232
231
|
openPath(tmpPath);
|
|
233
232
|
|
|
234
|
-
this.ctx.
|
|
235
|
-
|
|
233
|
+
this.ctx.present([
|
|
234
|
+
new Spacer(1),
|
|
236
235
|
new Text(theme.fg("dim", `Opened flamegraph (${workProfile.sampleCount} samples)`), 1, 0),
|
|
237
|
-
);
|
|
236
|
+
]);
|
|
238
237
|
} catch (err) {
|
|
239
238
|
this.ctx.showError(`Failed to open profile: ${err instanceof Error ? err.message : String(err)}`);
|
|
240
239
|
}
|
|
241
|
-
|
|
242
|
-
this.ctx.ui.requestRender();
|
|
243
240
|
}
|
|
244
241
|
|
|
245
242
|
async #handleDumpReport(): Promise<void> {
|
|
@@ -257,24 +254,22 @@ export class DebugSelectorComponent extends Container {
|
|
|
257
254
|
const result = await createReportBundle({
|
|
258
255
|
sessionFile: this.ctx.sessionManager.getSessionFile(),
|
|
259
256
|
settings: this.#getResolvedSettings(),
|
|
257
|
+
rawSseText: this.#getRawSseText(),
|
|
260
258
|
});
|
|
261
259
|
|
|
262
260
|
loader.stop();
|
|
263
261
|
this.ctx.statusContainer.clear();
|
|
264
262
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
);
|
|
269
|
-
this.ctx.
|
|
270
|
-
this.ctx.chatContainer.addChild(new Text(theme.fg("dim", `Files: ${result.files.length}`), 1, 0));
|
|
263
|
+
const block = new TranscriptBlock();
|
|
264
|
+
block.addChild(new Text(theme.fg("success", `${theme.status.success} Report bundle saved`), 1, 0));
|
|
265
|
+
block.addChild(new Text(theme.fg("dim", formatFileHyperlink(result.path)), 1, 0));
|
|
266
|
+
block.addChild(new Text(theme.fg("dim", `Files: ${result.files.length}`), 1, 0));
|
|
267
|
+
this.ctx.present(block);
|
|
271
268
|
} catch (err) {
|
|
272
269
|
loader.stop();
|
|
273
270
|
this.ctx.statusContainer.clear();
|
|
274
271
|
this.ctx.showError(`Failed to create report: ${err instanceof Error ? err.message : String(err)}`);
|
|
275
272
|
}
|
|
276
|
-
|
|
277
|
-
this.ctx.ui.requestRender();
|
|
278
273
|
}
|
|
279
274
|
|
|
280
275
|
async #handleMemoryReport(): Promise<void> {
|
|
@@ -295,25 +290,23 @@ export class DebugSelectorComponent extends Container {
|
|
|
295
290
|
const result = await createReportBundle({
|
|
296
291
|
sessionFile: this.ctx.sessionManager.getSessionFile(),
|
|
297
292
|
settings: this.#getResolvedSettings(),
|
|
293
|
+
rawSseText: this.#getRawSseText(),
|
|
298
294
|
heapSnapshot,
|
|
299
295
|
});
|
|
300
296
|
|
|
301
297
|
loader.stop();
|
|
302
298
|
this.ctx.statusContainer.clear();
|
|
303
299
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
);
|
|
308
|
-
this.ctx.
|
|
309
|
-
this.ctx.chatContainer.addChild(new Text(theme.fg("dim", `Files: ${result.files.length}`), 1, 0));
|
|
300
|
+
const block = new TranscriptBlock();
|
|
301
|
+
block.addChild(new Text(theme.fg("success", `${theme.status.success} Memory report saved`), 1, 0));
|
|
302
|
+
block.addChild(new Text(theme.fg("dim", formatFileHyperlink(result.path)), 1, 0));
|
|
303
|
+
block.addChild(new Text(theme.fg("dim", `Files: ${result.files.length}`), 1, 0));
|
|
304
|
+
this.ctx.present(block);
|
|
310
305
|
} catch (err) {
|
|
311
306
|
loader.stop();
|
|
312
307
|
this.ctx.statusContainer.clear();
|
|
313
308
|
this.ctx.showError(`Failed to create report: ${err instanceof Error ? err.message : String(err)}`);
|
|
314
309
|
}
|
|
315
|
-
|
|
316
|
-
this.ctx.ui.requestRender();
|
|
317
310
|
}
|
|
318
311
|
|
|
319
312
|
async #handleViewLogs(): Promise<void> {
|
|
@@ -365,15 +358,14 @@ export class DebugSelectorComponent extends Container {
|
|
|
365
358
|
const info = await collectSystemInfo();
|
|
366
359
|
const formatted = formatSystemInfo(info);
|
|
367
360
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
361
|
+
const block = new TranscriptBlock();
|
|
362
|
+
block.addChild(new DynamicBorder());
|
|
363
|
+
block.addChild(new Text(formatted, 1, 0));
|
|
364
|
+
block.addChild(new DynamicBorder());
|
|
365
|
+
this.ctx.present(block);
|
|
372
366
|
} catch (err) {
|
|
373
367
|
this.ctx.showError(`Failed to collect system info: ${err instanceof Error ? err.message : String(err)}`);
|
|
374
368
|
}
|
|
375
|
-
|
|
376
|
-
this.ctx.ui.requestRender();
|
|
377
369
|
}
|
|
378
370
|
|
|
379
371
|
async #handleViewTerminalState(): Promise<void> {
|
|
@@ -384,11 +376,11 @@ export class DebugSelectorComponent extends Container {
|
|
|
384
376
|
});
|
|
385
377
|
const formatted = formatTerminalState(info);
|
|
386
378
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
this.ctx.
|
|
379
|
+
const block = new TranscriptBlock();
|
|
380
|
+
block.addChild(new DynamicBorder());
|
|
381
|
+
block.addChild(new Text(formatted, 1, 0));
|
|
382
|
+
block.addChild(new DynamicBorder());
|
|
383
|
+
this.ctx.present(block);
|
|
392
384
|
}
|
|
393
385
|
|
|
394
386
|
async #handleViewProtocols(): Promise<void> {
|
|
@@ -407,15 +399,14 @@ export class DebugSelectorComponent extends Container {
|
|
|
407
399
|
TERMINAL.sendNotification(notification);
|
|
408
400
|
}
|
|
409
401
|
|
|
410
|
-
this.ctx.
|
|
411
|
-
|
|
402
|
+
this.ctx.present([
|
|
403
|
+
new Spacer(1),
|
|
412
404
|
new ProtocolProbeComponent({
|
|
413
405
|
image: buildSampleImage(),
|
|
414
406
|
imageBudget: this.ctx.ui.imageBudget,
|
|
415
407
|
notificationSuppressed: suppressed,
|
|
416
408
|
}),
|
|
417
|
-
);
|
|
418
|
-
this.ctx.ui.requestRender();
|
|
409
|
+
]);
|
|
419
410
|
}
|
|
420
411
|
|
|
421
412
|
async #handleTranscriptExport(): Promise<void> {
|
|
@@ -487,21 +478,24 @@ export class DebugSelectorComponent extends Container {
|
|
|
487
478
|
loader.stop();
|
|
488
479
|
this.ctx.statusContainer.clear();
|
|
489
480
|
|
|
490
|
-
this.ctx.
|
|
491
|
-
|
|
481
|
+
this.ctx.present([
|
|
482
|
+
new Spacer(1),
|
|
492
483
|
new Text(
|
|
493
484
|
theme.fg("success", `${theme.status.success} Cleared ${result.removed} artifact directories`),
|
|
494
485
|
1,
|
|
495
486
|
0,
|
|
496
487
|
),
|
|
497
|
-
);
|
|
488
|
+
]);
|
|
498
489
|
} catch (err) {
|
|
499
490
|
loader.stop();
|
|
500
491
|
this.ctx.statusContainer.clear();
|
|
501
492
|
this.ctx.showError(`Failed to clear cache: ${err instanceof Error ? err.message : String(err)}`);
|
|
502
493
|
}
|
|
494
|
+
}
|
|
503
495
|
|
|
504
|
-
|
|
496
|
+
#getRawSseText(): string | undefined {
|
|
497
|
+
const rawSseText = resolveRawSseDebugBuffer(this.ctx.session).toRawText();
|
|
498
|
+
return rawSseText.trim().length > 0 ? rawSseText : undefined;
|
|
505
499
|
}
|
|
506
500
|
|
|
507
501
|
#getResolvedSettings(): Record<string, unknown> {
|
|
@@ -152,9 +152,9 @@ export class RawSseDebugBuffer {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
// Ownership contract for `event.raw`:
|
|
155
|
-
// The caller (
|
|
156
|
-
//
|
|
157
|
-
//
|
|
155
|
+
// The caller (`notifyRawSseEvent` in `packages/ai/src/utils/sse-debug.ts`)
|
|
156
|
+
// hands us a freshly-allocated `string[]` per event and never retains,
|
|
157
|
+
// mutates, or re-dispatches it.
|
|
158
158
|
// That lets `trimRawLines` keep the array by reference instead of
|
|
159
159
|
// cloning on every chunk — a measurable savings on the streaming hot
|
|
160
160
|
// path. If a future observer-chain mutates the array, restore the
|
|
@@ -192,7 +192,10 @@ export class RawSseDebugBuffer {
|
|
|
192
192
|
toRawText(): string {
|
|
193
193
|
// Reads the live array directly: `rawRecordText` only computes a string
|
|
194
194
|
// from each record, so no caller-visible mutation is possible.
|
|
195
|
-
|
|
195
|
+
const body = this.#records.map(rawRecordText).join("\n");
|
|
196
|
+
if (this.#droppedRecords === 0) return body;
|
|
197
|
+
const dropped = `: omp-debug-dropped records=${this.#droppedRecords} chars=${this.#droppedChars}\n\n`;
|
|
198
|
+
return body.length > 0 ? `${dropped}${body}` : dropped;
|
|
196
199
|
}
|
|
197
200
|
|
|
198
201
|
#append(record: RawSseDebugRecord, chars: number): void {
|
|
@@ -45,6 +45,8 @@ export interface ReportBundleOptions {
|
|
|
45
45
|
heapSnapshot?: HeapSnapshot;
|
|
46
46
|
/** Work profile (for work scheduling reports) */
|
|
47
47
|
workProfile?: WorkProfile;
|
|
48
|
+
/** Raw provider SSE diagnostics captured by the session buffer */
|
|
49
|
+
rawSseText?: string;
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
export interface ReportBundleResult {
|
|
@@ -70,6 +72,7 @@ export interface DebugLogSource {
|
|
|
70
72
|
* - env.json: Sanitized environment variables
|
|
71
73
|
* - config.json: Resolved settings
|
|
72
74
|
* - profile.cpuprofile: CPU profile (performance report only)
|
|
75
|
+
* - raw-sse.txt: Recent raw provider SSE diagnostics (when captured)
|
|
73
76
|
* - profile.md: Markdown CPU profile (performance report only)
|
|
74
77
|
* - heap.heapsnapshot: Heap snapshot (memory report only)
|
|
75
78
|
* - work.folded: Work profile folded stacks (work report only)
|
|
@@ -109,6 +112,12 @@ export async function createReportBundle(options: ReportBundleOptions): Promise<
|
|
|
109
112
|
files.push("logs.txt");
|
|
110
113
|
}
|
|
111
114
|
|
|
115
|
+
// Recent raw provider SSE diagnostics
|
|
116
|
+
if (options.rawSseText && options.rawSseText.trim().length > 0) {
|
|
117
|
+
data["raw-sse.txt"] = options.rawSseText;
|
|
118
|
+
files.push("raw-sse.txt");
|
|
119
|
+
}
|
|
120
|
+
|
|
112
121
|
// Session file
|
|
113
122
|
if (options.sessionFile) {
|
|
114
123
|
try {
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
* from `@oh-my-pi/hashline`; the only coding-agent-specific concern here
|
|
9
9
|
* is wiring it onto the per-session owner object.
|
|
10
10
|
*/
|
|
11
|
+
import * as fs from "node:fs";
|
|
12
|
+
import * as path from "node:path";
|
|
11
13
|
import { InMemorySnapshotStore } from "@oh-my-pi/hashline";
|
|
12
14
|
import { normalizeToLF } from "./normalize";
|
|
13
15
|
|
|
@@ -33,6 +35,36 @@ export function getFileSnapshotStore(session: FileSnapshotStoreOwner): InMemoryS
|
|
|
33
35
|
return session.fileSnapshotStore;
|
|
34
36
|
}
|
|
35
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Canonicalize an absolute path into the stable key the snapshot store uses.
|
|
40
|
+
*
|
|
41
|
+
* Different code paths reach the snapshot store via different path forms:
|
|
42
|
+
* `read local://foo.md` records under the file's `fs.realpath` (the local
|
|
43
|
+
* protocol handler resolves symlinks); a subsequent `edit` may address the
|
|
44
|
+
* same artifact via `local://foo.md`, whose resolver does NOT realpath, or
|
|
45
|
+
* via the absolute path returned in the `[path#tag]` header. macOS adds the
|
|
46
|
+
* same hazard at the working-tree level (`/tmp/...` vs `/private/tmp/...`).
|
|
47
|
+
* Collapsing every key through `realpath` makes those forms fuse onto one
|
|
48
|
+
* snapshot entry, so a freshly-minted tag is never rejected as stale just
|
|
49
|
+
* because the lookup spelled the same file differently.
|
|
50
|
+
*
|
|
51
|
+
* Non-existent paths (new-file writes) fall back to a realpath of the parent
|
|
52
|
+
* directory + basename, then to the input. This keeps creates and updates on
|
|
53
|
+
* the same canonical key.
|
|
54
|
+
*/
|
|
55
|
+
export function canonicalSnapshotKey(absolutePath: string): string {
|
|
56
|
+
try {
|
|
57
|
+
return fs.realpathSync.native(absolutePath);
|
|
58
|
+
} catch {
|
|
59
|
+
try {
|
|
60
|
+
const parent = fs.realpathSync.native(path.dirname(absolutePath));
|
|
61
|
+
return path.join(parent, path.basename(absolutePath));
|
|
62
|
+
} catch {
|
|
63
|
+
return absolutePath;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
36
68
|
/**
|
|
37
69
|
* Read the full text of `absolutePath` (within {@link SNAPSHOT_MAX_BYTES}),
|
|
38
70
|
* record it as a version snapshot, and return its content-hash tag. Returns
|
|
@@ -52,7 +84,7 @@ export async function recordFileSnapshot(
|
|
|
52
84
|
const file = Bun.file(absolutePath);
|
|
53
85
|
if (file.size > SNAPSHOT_MAX_BYTES) return undefined;
|
|
54
86
|
const normalized = normalizeToLF(await file.text());
|
|
55
|
-
return getFileSnapshotStore(session).record(absolutePath, normalized);
|
|
87
|
+
return getFileSnapshotStore(session).record(canonicalSnapshotKey(absolutePath), normalized);
|
|
56
88
|
} catch {
|
|
57
89
|
return undefined;
|
|
58
90
|
}
|
|
@@ -23,6 +23,7 @@ import type { ToolSession } from "../../tools";
|
|
|
23
23
|
import { assertEditableFileContent } from "../../tools/auto-generated-guard";
|
|
24
24
|
import { invalidateFsScanAfterWrite } from "../../tools/fs-cache-invalidation";
|
|
25
25
|
import { enforcePlanModeWrite, resolvePlanPath } from "../../tools/plan-mode-guard";
|
|
26
|
+
import { canonicalSnapshotKey } from "../file-snapshot-store";
|
|
26
27
|
import { readEditFileText, serializeEditFileText } from "../read-file";
|
|
27
28
|
import type { LspBatchRequest } from "../renderer";
|
|
28
29
|
|
|
@@ -81,7 +82,7 @@ export class HashlineFilesystem extends Filesystem {
|
|
|
81
82
|
}
|
|
82
83
|
|
|
83
84
|
canonicalPath(relativePath: string): string {
|
|
84
|
-
return this.resolveAbsolute(relativePath);
|
|
85
|
+
return canonicalSnapshotKey(this.resolveAbsolute(relativePath));
|
|
85
86
|
}
|
|
86
87
|
|
|
87
88
|
async readText(relativePath: string): Promise<string> {
|
package/src/edit/renderer.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { HL_FILE_PREFIX, HL_FILE_SUFFIX } from "@oh-my-pi/hashline";
|
|
6
6
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
7
|
-
import {
|
|
7
|
+
import { visibleWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
|
|
8
8
|
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
9
9
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
10
10
|
import type { FileDiagnosticsResult } from "../lsp";
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
formatDiffStats,
|
|
17
17
|
formatExpandHint,
|
|
18
18
|
formatStatusIcon,
|
|
19
|
-
formatTitle,
|
|
20
19
|
getDiffStats,
|
|
21
20
|
getLspBatchRequest,
|
|
22
21
|
type LspBatchRequest,
|
|
@@ -25,7 +24,7 @@ import {
|
|
|
25
24
|
shortenPath,
|
|
26
25
|
truncateDiffByHunk,
|
|
27
26
|
} from "../tools/render-utils";
|
|
28
|
-
import { fileHyperlink, Hasher, type RenderCache, renderStatusLine, truncateToWidth } from "../tui";
|
|
27
|
+
import { fileHyperlink, framedBlock, Hasher, type RenderCache, renderStatusLine, truncateToWidth } from "../tui";
|
|
29
28
|
import type { EditMode } from "../utils/edit-mode";
|
|
30
29
|
import type { DiffError, DiffResult } from "./diff";
|
|
31
30
|
import { type ApplyPatchEntry, expandApplyPatchToEntries, expandApplyPatchToPreviewEntries } from "./modes/apply-patch";
|
|
@@ -186,10 +185,14 @@ function getOperationTitle(op: Operation | undefined): string {
|
|
|
186
185
|
function formatEditPathDisplay(
|
|
187
186
|
rawPath: string,
|
|
188
187
|
uiTheme: Theme,
|
|
189
|
-
options?: { rename?: string; firstChangedLine?: number },
|
|
188
|
+
options?: { rename?: string; firstChangedLine?: number; linkPath?: string; renameLinkPath?: string },
|
|
190
189
|
): string {
|
|
190
|
+
// `rawPath`/`rename` are shown (cwd-relative) but the OSC 8 link targets the
|
|
191
|
+
// absolute path when known — a relative `rawPath` would yield a `file:///rel`
|
|
192
|
+
// URI that resolves against filesystem root instead of cwd.
|
|
193
|
+
const linkTarget = options?.linkPath || rawPath;
|
|
191
194
|
let pathDisplay = rawPath
|
|
192
|
-
? fileHyperlink(
|
|
195
|
+
? fileHyperlink(linkTarget, uiTheme.fg("accent", shortenPath(rawPath)))
|
|
193
196
|
: uiTheme.fg("toolOutput", "…");
|
|
194
197
|
|
|
195
198
|
if (options?.firstChangedLine) {
|
|
@@ -197,7 +200,8 @@ function formatEditPathDisplay(
|
|
|
197
200
|
}
|
|
198
201
|
|
|
199
202
|
if (options?.rename) {
|
|
200
|
-
|
|
203
|
+
const renameTarget = options.renameLinkPath || options.rename;
|
|
204
|
+
pathDisplay += ` ${uiTheme.fg("dim", "→")} ${fileHyperlink(renameTarget, uiTheme.fg("accent", shortenPath(options.rename)))}`;
|
|
201
205
|
}
|
|
202
206
|
|
|
203
207
|
return pathDisplay;
|
|
@@ -206,7 +210,7 @@ function formatEditPathDisplay(
|
|
|
206
210
|
function formatEditDescription(
|
|
207
211
|
rawPath: string,
|
|
208
212
|
uiTheme: Theme,
|
|
209
|
-
options?: { rename?: string; firstChangedLine?: number },
|
|
213
|
+
options?: { rename?: string; firstChangedLine?: number; linkPath?: string; renameLinkPath?: string },
|
|
210
214
|
): { language: string; description: string } {
|
|
211
215
|
const language = getLanguageFromPath(rawPath) ?? "text";
|
|
212
216
|
const icon = uiTheme.fg("muted", uiTheme.getLangIcon(language));
|
|
@@ -459,23 +463,31 @@ export const editToolRenderer = {
|
|
|
459
463
|
const rename = editArgs.rename || firstEdit?.rename || firstEdit?.move || firstApplyPatchEntry?.rename;
|
|
460
464
|
const op = editArgs.op || firstEdit?.op || firstApplyPatchEntry?.op;
|
|
461
465
|
const { description } = formatEditDescription(rawPath, uiTheme, { rename });
|
|
462
|
-
const spinner =
|
|
463
|
-
options?.spinnerFrame !== undefined ? formatStatusIcon("running", uiTheme, options.spinnerFrame) : "";
|
|
464
|
-
let text = `${formatTitle(getOperationTitle(op), uiTheme)} ${spinner ? `${spinner} ` : ""}${description}`;
|
|
465
|
-
// Show file count hint for multi-file edits
|
|
466
466
|
let fileCount = hashlineInputSummary?.entries.length ?? applyPatchSummary?.entries.length ?? 0;
|
|
467
467
|
if (Array.isArray(editArgs.edits)) {
|
|
468
468
|
fileCount = countEditFiles(editArgs.edits);
|
|
469
469
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
470
|
+
return framedBlock(uiTheme, width => {
|
|
471
|
+
let header = renderStatusLine(
|
|
472
|
+
{ icon: "pending", spinnerFrame: options?.spinnerFrame, title: getOperationTitle(op), description },
|
|
473
|
+
uiTheme,
|
|
474
|
+
);
|
|
475
|
+
if (fileCount > 1) header += uiTheme.fg("dim", ` (+${fileCount - 1} more)`);
|
|
476
|
+
let body = getCallPreview(editArgs, rawPath, uiTheme, renderContext, options.expanded);
|
|
477
|
+
if (applyPatchSummary?.error) {
|
|
478
|
+
body += `\n${uiTheme.fg("error", truncateToWidth(replaceTabs(applyPatchSummary.error, rawPath), Math.max(1, width - 2)))}`;
|
|
479
|
+
}
|
|
480
|
+
const bodyLines = body ? body.split("\n") : [];
|
|
481
|
+
while (bodyLines.length > 0 && bodyLines[0].trim() === "") bodyLines.shift();
|
|
482
|
+
return {
|
|
483
|
+
header,
|
|
484
|
+
sections: bodyLines.length > 0 ? [{ lines: bodyLines }] : [],
|
|
485
|
+
state: applyPatchSummary?.error ? "error" : "pending",
|
|
486
|
+
borderColor: applyPatchSummary?.error ? "error" : "borderMuted",
|
|
487
|
+
width,
|
|
488
|
+
contentPaddingLeft: 0,
|
|
489
|
+
};
|
|
490
|
+
});
|
|
479
491
|
},
|
|
480
492
|
|
|
481
493
|
renderResult(
|
|
@@ -525,65 +537,57 @@ function renderSingleFileResult(
|
|
|
525
537
|
(result.content?.find(c => c.type === "text")?.text ?? "")
|
|
526
538
|
: "";
|
|
527
539
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
540
|
+
return framedBlock(uiTheme, width => {
|
|
541
|
+
const { expanded, renderContext } = options;
|
|
542
|
+
const editDiffPreview = renderContext?.editDiffPreview;
|
|
543
|
+
const renderDiffFn = renderContext?.renderDiff ?? ((t: string) => t);
|
|
544
|
+
|
|
545
|
+
const firstChangedLine =
|
|
546
|
+
(editDiffPreview && "firstChangedLine" in editDiffPreview ? editDiffPreview.firstChangedLine : undefined) ||
|
|
547
|
+
(details && !isError ? details.firstChangedLine : undefined);
|
|
548
|
+
const linkPath = details && "path" in details ? details.path : undefined;
|
|
549
|
+
const { description } = formatEditDescription(rawPath, uiTheme, { rename, firstChangedLine, linkPath });
|
|
550
|
+
|
|
551
|
+
// Change stats ride inline on the header bar next to the path.
|
|
552
|
+
const previewDiff = editDiffPreview && !("error" in editDiffPreview) ? editDiffPreview.diff : undefined;
|
|
553
|
+
const headerDiff = isError ? undefined : details?.diff || previewDiff;
|
|
554
|
+
const statsSuffix = headerDiff ? formatDiffStatsSuffix(headerDiff, uiTheme) : "";
|
|
555
|
+
const header =
|
|
556
|
+
renderStatusLine({ icon: isError ? "error" : "success", title: getOperationTitle(op), description }, uiTheme) +
|
|
557
|
+
statsSuffix;
|
|
558
|
+
|
|
559
|
+
let body = "";
|
|
560
|
+
if (isError) {
|
|
561
|
+
if (errorText) body = uiTheme.fg("error", replaceTabs(errorText, rawPath));
|
|
562
|
+
} else if (details?.diff) {
|
|
563
|
+
body = renderDiffSection(details.diff, rawPath, expanded, uiTheme, renderDiffFn);
|
|
564
|
+
} else if (editDiffPreview) {
|
|
565
|
+
if ("error" in editDiffPreview) body = uiTheme.fg("error", replaceTabs(editDiffPreview.error, rawPath));
|
|
566
|
+
else if (editDiffPreview.diff)
|
|
567
|
+
body = renderDiffSection(editDiffPreview.diff, rawPath, expanded, uiTheme, renderDiffFn);
|
|
568
|
+
}
|
|
569
|
+
if (details?.diagnostics) {
|
|
570
|
+
body += formatDiagnostics(details.diagnostics, expanded, uiTheme, (fp: string) =>
|
|
571
|
+
uiTheme.getLangIcon(getLanguageFromPath(fp)),
|
|
555
572
|
);
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
if (isError) {
|
|
559
|
-
if (errorText) {
|
|
560
|
-
text += `\n\n${uiTheme.fg("error", replaceTabs(errorText, rawPath))}`;
|
|
561
|
-
}
|
|
562
|
-
} else if (details?.diff) {
|
|
563
|
-
text += renderDiffSection(details.diff, rawPath, expanded, uiTheme, renderDiffFn);
|
|
564
|
-
} else if (editDiffPreview) {
|
|
565
|
-
if ("error" in editDiffPreview) {
|
|
566
|
-
text += `\n\n${uiTheme.fg("error", replaceTabs(editDiffPreview.error, rawPath))}`;
|
|
567
|
-
} else if (editDiffPreview.diff) {
|
|
568
|
-
text += renderDiffSection(editDiffPreview.diff, rawPath, expanded, uiTheme, renderDiffFn);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
if (details?.diagnostics) {
|
|
573
|
-
text += formatDiagnostics(details.diagnostics, expanded, uiTheme, (fp: string) =>
|
|
574
|
-
uiTheme.getLangIcon(getLanguageFromPath(fp)),
|
|
575
|
-
);
|
|
576
|
-
}
|
|
573
|
+
}
|
|
577
574
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
575
|
+
// Diff lines self-wrap with a continuation gutter; pre-wrap to the frame's
|
|
576
|
+
// inner width so renderOutputBlock's generic wrap is a no-op. Edit frames
|
|
577
|
+
// use a flush left border because code-frame gutters already provide padding.
|
|
578
|
+
const innerWidth = Math.max(1, width - 2);
|
|
579
|
+
const bodyLines = body.length > 0 ? body.split("\n").flatMap(line => wrapEditRendererLine(line, innerWidth)) : [];
|
|
580
|
+
while (bodyLines.length > 0 && bodyLines[0].trim() === "") bodyLines.shift();
|
|
581
|
+
|
|
582
|
+
return {
|
|
583
|
+
header,
|
|
584
|
+
sections: bodyLines.length > 0 ? [{ lines: bodyLines }] : [],
|
|
585
|
+
state: isError ? "error" : options.isPartial ? "pending" : "success",
|
|
586
|
+
borderColor: isError ? "error" : "borderMuted",
|
|
587
|
+
width,
|
|
588
|
+
contentPaddingLeft: 0,
|
|
589
|
+
};
|
|
590
|
+
});
|
|
587
591
|
}
|
|
588
592
|
|
|
589
593
|
function renderMultiFileResult(
|
|
@@ -638,7 +642,7 @@ function renderMultiFileResult(
|
|
|
638
642
|
},
|
|
639
643
|
invalidate() {
|
|
640
644
|
cached = undefined;
|
|
641
|
-
for (const c of fileComponents) c.invalidate();
|
|
645
|
+
for (const c of fileComponents) c.invalidate?.();
|
|
642
646
|
},
|
|
643
647
|
};
|
|
644
648
|
}
|