@oh-my-pi/pi-coding-agent 15.9.67 → 15.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +136 -0
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/cli/gallery-cli.d.ts +43 -0
- package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
- package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
- package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
- package/dist/types/cli/gallery-screenshot.d.ts +35 -0
- package/dist/types/commands/gallery.d.ts +47 -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 +6 -1
- package/dist/types/config/model-id-affixes.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +25 -2
- package/dist/types/config/settings-schema.d.ts +41 -6
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
- package/dist/types/lsp/types.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/main.d.ts +3 -2
- package/dist/types/memory-backend/index.d.ts +2 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +1 -1
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +5 -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/tool-execution.d.ts +18 -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 +0 -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/selector-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/index.d.ts +5 -4
- package/dist/types/modes/interactive-mode.d.ts +16 -6
- package/dist/types/modes/setup-version.d.ts +11 -0
- package/dist/types/modes/setup-wizard/index.d.ts +2 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +19 -6
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- 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 +3 -1
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +14 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/telemetry-export.d.ts +1 -1
- 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-render.d.ts +1 -8
- package/dist/types/tools/fetch.d.ts +15 -7
- package/dist/types/tools/find.d.ts +8 -4
- 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/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +13 -9
- package/dist/types/tools/renderers.d.ts +16 -2
- package/dist/types/tools/search.d.ts +5 -1
- 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 +5 -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/scrapers/github.d.ts +22 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- package/dist/types/web/search/providers/perplexity.d.ts +8 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +9 -9
- package/scripts/dev-launch +42 -0
- package/scripts/dev-launch-preload.ts +19 -0
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/args.ts +2 -2
- package/src/cli/dry-balance-cli.ts +52 -17
- package/src/cli/gallery-cli.ts +226 -0
- package/src/cli/gallery-fixtures/agentic.ts +292 -0
- package/src/cli/gallery-fixtures/codeintel.ts +188 -0
- package/src/cli/gallery-fixtures/edit.ts +194 -0
- package/src/cli/gallery-fixtures/fs.ts +153 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +49 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +250 -0
- package/src/cli/gallery-fixtures/search.ts +213 -0
- package/src/cli/gallery-fixtures/shell.ts +167 -0
- package/src/cli/gallery-fixtures/types.ts +41 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli-commands.ts +1 -0
- package/src/commands/gallery.ts +52 -0
- package/src/commands/launch.ts +1 -1
- 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 +33 -9
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/keybindings.ts +15 -6
- package/src/config/model-equivalence.ts +35 -12
- package/src/config/model-id-affixes.ts +39 -22
- package/src/config/model-registry.ts +41 -18
- package/src/config/settings-schema.ts +28 -5
- package/src/config/settings.ts +31 -2
- package/src/dap/client.ts +14 -16
- 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 +40 -54
- package/src/edit/renderer.ts +111 -119
- package/src/eval/__tests__/agent-bridge.test.ts +75 -32
- package/src/eval/__tests__/llm-bridge.test.ts +90 -31
- package/src/eval/agent-bridge.ts +34 -7
- package/src/eval/llm-bridge.ts +8 -3
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/plugins/doctor.ts +0 -1
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/goals/tools/goal-tool.ts +37 -27
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/lsp/client.ts +104 -55
- package/src/lsp/types.ts +10 -0
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +53 -56
- package/src/memories/index.ts +12 -5
- package/src/memory-backend/index.ts +13 -1
- package/src/memory-backend/resolve.ts +3 -5
- package/src/memory-backend/types.ts +1 -1
- 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 +33 -1
- 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/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 +3 -5
- package/src/modes/components/tips.txt +1 -0
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +115 -90
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +1 -2
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +70 -57
- package/src/modes/controllers/event-controller.ts +41 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +135 -122
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +25 -27
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/index.ts +5 -4
- package/src/modes/interactive-mode.ts +171 -82
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +3 -2
- package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +19 -8
- package/src/modes/utils/context-usage.ts +10 -6
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +44 -46
- 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/sdk.ts +32 -60
- package/src/session/agent-session.ts +89 -13
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +13 -5
- package/src/slash-commands/builtin-registry.ts +37 -10
- package/src/slash-commands/helpers/usage-report.ts +2 -0
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +25 -4
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +212 -148
- package/src/telemetry-export.ts +25 -7
- 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 +50 -6
- package/src/tools/debug.ts +20 -8
- package/src/tools/eval-backends.ts +6 -17
- package/src/tools/eval-render.ts +21 -18
- package/src/tools/eval.ts +5 -4
- package/src/tools/fetch.ts +391 -91
- package/src/tools/find.ts +44 -30
- package/src/tools/gh-renderer.ts +81 -42
- 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 +8 -1
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/plan-mode-guard.ts +21 -39
- package/src/tools/read.ts +23 -16
- package/src/tools/render-utils.ts +38 -40
- package/src/tools/renderers.ts +16 -1
- package/src/tools/report-tool-issue.ts +1 -1
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +189 -95
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +138 -59
- package/src/tools/write.ts +100 -60
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +9 -1
- package/src/utils/enhanced-paste.ts +202 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/scrapers/github.ts +255 -3
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/anthropic.ts +25 -19
- 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/perplexity.ts +199 -51
- 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/web/search/render.ts +39 -54
- package/src/web/search/types.ts +5 -1
- package/dist/types/eval/__tests__/shared-executors.test.d.ts +0 -1
- package/src/eval/__tests__/shared-executors.test.ts +0 -609
package/src/tools/todo.ts
CHANGED
|
@@ -9,8 +9,8 @@ import type { Theme } from "../modes/theme/theme";
|
|
|
9
9
|
import todoDescription from "../prompts/tools/todo.md" with { type: "text" };
|
|
10
10
|
import type { ToolSession } from "../sdk";
|
|
11
11
|
import type { SessionEntry } from "../session/session-manager";
|
|
12
|
-
import { renderStatusLine, renderTreeList } from "../tui";
|
|
13
|
-
import { PREVIEW_LIMITS } from "./render-utils";
|
|
12
|
+
import { framedBlock, renderStatusLine, renderTreeList } from "../tui";
|
|
13
|
+
import { formatErrorDetail, PREVIEW_LIMITS } from "./render-utils";
|
|
14
14
|
|
|
15
15
|
// =============================================================================
|
|
16
16
|
// Types
|
|
@@ -755,19 +755,17 @@ function formatTodoLine(
|
|
|
755
755
|
}
|
|
756
756
|
}
|
|
757
757
|
|
|
758
|
-
function renderNoteAttachments(phases: TodoPhase[], uiTheme: Theme): string[] {
|
|
758
|
+
function renderNoteAttachments(phases: TodoPhase[], uiTheme: Theme, indent: string): string[] {
|
|
759
759
|
const lines: string[] = [];
|
|
760
760
|
for (const phase of phases) {
|
|
761
761
|
for (const task of phase.tasks) {
|
|
762
762
|
if (task.status !== "in_progress" || !task.notes || task.notes.length === 0) continue;
|
|
763
|
-
const bar = uiTheme.fg("dim", uiTheme.tree.vertical);
|
|
764
|
-
const title = uiTheme.fg("dim", chalk.italic(`§ notes — ${task.content}`));
|
|
765
763
|
lines.push("");
|
|
766
|
-
lines.push(
|
|
764
|
+
lines.push(`${indent}${uiTheme.fg("dim", chalk.italic(`§ notes — ${task.content}`))}`);
|
|
767
765
|
for (let j = 0; j < task.notes.length; j++) {
|
|
768
|
-
if (j > 0) lines.push(
|
|
766
|
+
if (j > 0) lines.push("");
|
|
769
767
|
for (const noteLine of task.notes[j].split("\n")) {
|
|
770
|
-
lines.push(
|
|
768
|
+
lines.push(`${indent} ${uiTheme.fg("dim", noteLine)}`);
|
|
771
769
|
}
|
|
772
770
|
}
|
|
773
771
|
}
|
|
@@ -775,25 +773,102 @@ function renderNoteAttachments(phases: TodoPhase[], uiTheme: Theme): string[] {
|
|
|
775
773
|
return lines;
|
|
776
774
|
}
|
|
777
775
|
|
|
776
|
+
/**
|
|
777
|
+
* Phases the latest update touched, plus the active (in_progress) phase.
|
|
778
|
+
* Returns `null` when there is no usable signal, meaning "render every phase
|
|
779
|
+
* fully" — this preserves the legacy view and the manual-expand path.
|
|
780
|
+
*/
|
|
781
|
+
function computeTouchedPhases(
|
|
782
|
+
args: TodoRenderArgs | undefined,
|
|
783
|
+
phases: TodoPhase[],
|
|
784
|
+
completedTasks: TodoCompletionTransition[],
|
|
785
|
+
): Set<string> | null {
|
|
786
|
+
const touched = new Set<string>();
|
|
787
|
+
// The phase holding the in_progress task is where attention sits after the
|
|
788
|
+
// auto-promotion that follows every completion.
|
|
789
|
+
for (const phase of phases) {
|
|
790
|
+
if (phase.tasks.some(task => task.status === "in_progress")) touched.add(phase.name);
|
|
791
|
+
}
|
|
792
|
+
// Phases with a task that just transitioned to completed in this update.
|
|
793
|
+
for (const transition of completedTasks) touched.add(transition.phase);
|
|
794
|
+
// Phases explicitly named by the ops that ran. `init` replaces the whole
|
|
795
|
+
// list, so the entire plan is fresh and every phase counts as touched.
|
|
796
|
+
const ops = Array.isArray(args?.ops) ? args.ops : [];
|
|
797
|
+
for (const op of ops) {
|
|
798
|
+
if (!op || typeof op !== "object") continue;
|
|
799
|
+
if (op.op === "init") {
|
|
800
|
+
for (const phase of phases) touched.add(phase.name);
|
|
801
|
+
break;
|
|
802
|
+
}
|
|
803
|
+
if (typeof op.phase === "string" && op.phase) {
|
|
804
|
+
const named = phases.find(phase => phase.name === op.phase);
|
|
805
|
+
if (named) touched.add(named.name);
|
|
806
|
+
}
|
|
807
|
+
if (typeof op.task === "string" && op.task) {
|
|
808
|
+
const located = findTaskByContent(phases, op.task);
|
|
809
|
+
if (located) touched.add(located.phase.name);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
return touched.size > 0 ? touched : null;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/** One-line summary for a collapsed (untouched) phase: dim header + progress. */
|
|
816
|
+
function formatPhaseSummary(phase: TodoPhase, oneBasedIndex: number, uiTheme: Theme): string {
|
|
817
|
+
const total = phase.tasks.length;
|
|
818
|
+
const done = phase.tasks.filter(task => task.status === "completed").length;
|
|
819
|
+
const name = uiTheme.fg("dim", chalk.bold(formatPhaseDisplayName(phase.name, oneBasedIndex)));
|
|
820
|
+
return `${name}${uiTheme.fg("dim", ` ${done}/${total}`)}`;
|
|
821
|
+
}
|
|
822
|
+
|
|
778
823
|
export const todoToolRenderer = {
|
|
779
|
-
renderCall(args: TodoRenderArgs,
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
const
|
|
788
|
-
|
|
824
|
+
renderCall(args: TodoRenderArgs, options: RenderResultOptions, uiTheme: Theme): Component {
|
|
825
|
+
// `args` here is the raw partially-parsed JSON from the streaming
|
|
826
|
+
// tool-call delta and may not satisfy `TodoRenderArgs` at runtime:
|
|
827
|
+
// `parseStreamingJson` can hand back `{ ops: "[" }` mid-delta, or
|
|
828
|
+
// entries that are `null` / strings before fields stream. Guard
|
|
829
|
+
// against non-array `ops` and non-object entries so a malformed
|
|
830
|
+
// delta never breaks the TUI render loop (#2005).
|
|
831
|
+
const opsList = Array.isArray(args?.ops) ? args.ops : [];
|
|
832
|
+
const ops =
|
|
833
|
+
opsList.length === 0
|
|
834
|
+
? ["update"]
|
|
835
|
+
: opsList.map(entry => {
|
|
836
|
+
const e = entry && typeof entry === "object" ? entry : ({} as NonNullable<typeof entry>);
|
|
837
|
+
const parts = [e.op ?? "update"];
|
|
838
|
+
if (e.task) parts.push(e.task);
|
|
839
|
+
if (e.phase) parts.push(e.phase);
|
|
840
|
+
if (Array.isArray(e.items) && e.items.length) {
|
|
841
|
+
parts.push(`${e.items.length} item${e.items.length === 1 ? "" : "s"}`);
|
|
842
|
+
}
|
|
843
|
+
return parts.join(" ");
|
|
844
|
+
});
|
|
845
|
+
// No body worth boxing while the call streams — a lone status line reads
|
|
846
|
+
// cleaner than an empty frame. The container renders it without chrome.
|
|
847
|
+
const header = renderStatusLine(
|
|
848
|
+
{ icon: "pending", spinnerFrame: options?.spinnerFrame, title: "Todo", meta: ops },
|
|
849
|
+
uiTheme,
|
|
850
|
+
);
|
|
851
|
+
return new Text(header, 0, 0);
|
|
789
852
|
},
|
|
790
853
|
|
|
791
854
|
renderResult(
|
|
792
|
-
result: { content: Array<{ type: string; text?: string }>; details?: TodoToolDetails },
|
|
855
|
+
result: { content: Array<{ type: string; text?: string }>; details?: TodoToolDetails; isError?: boolean },
|
|
793
856
|
options: RenderResultOptions,
|
|
794
857
|
uiTheme: Theme,
|
|
795
|
-
|
|
858
|
+
args?: TodoRenderArgs,
|
|
796
859
|
): Component {
|
|
860
|
+
if (result.isError) {
|
|
861
|
+
const errorText = result.content?.find(content => content.type === "text")?.text ?? "Todo operation failed";
|
|
862
|
+
const header = renderStatusLine({ icon: "error", title: "Todo" }, uiTheme);
|
|
863
|
+
return framedBlock(uiTheme, width => ({
|
|
864
|
+
header,
|
|
865
|
+
sections: [{ lines: formatErrorDetail(errorText, uiTheme).split("\n") }],
|
|
866
|
+
state: "error",
|
|
867
|
+
borderColor: "error",
|
|
868
|
+
width,
|
|
869
|
+
}));
|
|
870
|
+
}
|
|
871
|
+
|
|
797
872
|
const phases = (result.details?.phases ?? []).filter(phase => phase.tasks.length > 0);
|
|
798
873
|
const completedTasks = result.details?.completedTasks ?? [];
|
|
799
874
|
const completionKeysByPhase = new Map<string, Set<string>>();
|
|
@@ -809,48 +884,52 @@ export const todoToolRenderer = {
|
|
|
809
884
|
const header = renderStatusLine({ icon: "success", title: "Todo", meta: [`${allTasks.length} tasks`] }, uiTheme);
|
|
810
885
|
if (allTasks.length === 0) {
|
|
811
886
|
const fallback = result.content?.find(content => content.type === "text")?.text ?? "No todos";
|
|
812
|
-
return new Text(`${header}\n${uiTheme.fg("dim", fallback)}`, 0, 0);
|
|
887
|
+
return new Text(`${header}\n ${uiTheme.fg("dim", fallback)}`, 0, 0);
|
|
813
888
|
}
|
|
814
889
|
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
const phase = phases[p];
|
|
830
|
-
if (phases.length > 1) {
|
|
831
|
-
lines.push(uiTheme.fg("accent", chalk.bold(` ${formatPhaseDisplayName(phase.name, p + 1)}`)));
|
|
832
|
-
}
|
|
833
|
-
const completionKeys = completionKeysByPhase.get(phase.name) ?? EMPTY_COMPLETION_KEYS;
|
|
834
|
-
const treeLines = renderTreeList(
|
|
835
|
-
{
|
|
836
|
-
items: phase.tasks,
|
|
837
|
-
expanded,
|
|
838
|
-
maxCollapsed: PREVIEW_LIMITS.COLLAPSED_ITEMS,
|
|
839
|
-
itemType: "todo",
|
|
840
|
-
renderItem: todo => formatTodoLine(todo, uiTheme, "", completionKeys, spinnerFrame),
|
|
841
|
-
},
|
|
842
|
-
uiTheme,
|
|
843
|
-
);
|
|
844
|
-
for (const line of treeLines) {
|
|
845
|
-
lines.push(` ${line}`);
|
|
846
|
-
}
|
|
890
|
+
return framedBlock(uiTheme, width => {
|
|
891
|
+
const { expanded, spinnerFrame } = options;
|
|
892
|
+
const multiPhase = phases.length > 1;
|
|
893
|
+
const indent = multiPhase ? " " : "";
|
|
894
|
+
// Collapse phases this update didn't touch down to a one-line summary so
|
|
895
|
+
// a single task flip doesn't redraw every phase's full task list. The
|
|
896
|
+
// manual expand toggle (and the no-signal fallback) still shows all.
|
|
897
|
+
const touched = expanded || !multiPhase ? null : computeTouchedPhases(args, phases, completedTasks);
|
|
898
|
+
const bodyLines: string[] = [];
|
|
899
|
+
for (let p = 0; p < phases.length; p++) {
|
|
900
|
+
const phase = phases[p];
|
|
901
|
+
if (touched && !touched.has(phase.name)) {
|
|
902
|
+
bodyLines.push(formatPhaseSummary(phase, p + 1, uiTheme));
|
|
903
|
+
continue;
|
|
847
904
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
905
|
+
if (multiPhase) {
|
|
906
|
+
bodyLines.push(uiTheme.fg("accent", chalk.bold(formatPhaseDisplayName(phase.name, p + 1))));
|
|
907
|
+
}
|
|
908
|
+
const completionKeys = completionKeysByPhase.get(phase.name) ?? EMPTY_COMPLETION_KEYS;
|
|
909
|
+
const treeLines = renderTreeList(
|
|
910
|
+
{
|
|
911
|
+
items: phase.tasks,
|
|
912
|
+
expanded,
|
|
913
|
+
maxCollapsed: PREVIEW_LIMITS.COLLAPSED_ITEMS,
|
|
914
|
+
itemType: "todo",
|
|
915
|
+
renderItem: todo => formatTodoLine(todo, uiTheme, "", completionKeys, spinnerFrame),
|
|
916
|
+
},
|
|
917
|
+
uiTheme,
|
|
918
|
+
);
|
|
919
|
+
for (const line of treeLines) {
|
|
920
|
+
bodyLines.push(`${indent}${line}`);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
bodyLines.push(...renderNoteAttachments(phases, uiTheme, indent));
|
|
924
|
+
while (bodyLines.length > 0 && bodyLines[0].trim() === "") bodyLines.shift();
|
|
925
|
+
return {
|
|
926
|
+
header,
|
|
927
|
+
sections: bodyLines.length > 0 ? [{ lines: bodyLines }] : [],
|
|
928
|
+
state: options.isPartial ? "pending" : "success",
|
|
929
|
+
borderColor: "borderMuted",
|
|
930
|
+
width,
|
|
931
|
+
};
|
|
932
|
+
});
|
|
854
933
|
},
|
|
855
934
|
mergeCallAndResult: true,
|
|
856
935
|
};
|
package/src/tools/write.ts
CHANGED
|
@@ -5,7 +5,6 @@ import * as path from "node:path";
|
|
|
5
5
|
import { formatHashlineHeader, stripHashlinePrefixes } from "@oh-my-pi/hashline";
|
|
6
6
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
7
7
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
8
|
-
import { Text } from "@oh-my-pi/pi-tui";
|
|
9
8
|
import { isEnoent, isRecord, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
10
9
|
import * as z from "zod/v4";
|
|
11
10
|
|
|
@@ -19,7 +18,7 @@ import { getDiagnosticsLedger } from "../lsp/diagnostics-ledger";
|
|
|
19
18
|
import { getLanguageFromPath, highlightCode, type Theme } from "../modes/theme/theme";
|
|
20
19
|
import writeDescription from "../prompts/tools/write.md" with { type: "text" };
|
|
21
20
|
import type { ToolSession } from "../sdk";
|
|
22
|
-
import {
|
|
21
|
+
import { fileHyperlink, framedBlock, renderStatusLine } from "../tui";
|
|
23
22
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
24
23
|
import { truncateForPrompt } from "./approval";
|
|
25
24
|
import { parseArchivePathCandidates } from "./archive-reader";
|
|
@@ -37,10 +36,9 @@ import { formatPathRelativeToCwd, isInternalUrlPath } from "./path-utils";
|
|
|
37
36
|
import { enforcePlanModeWrite, resolvePlanPath } from "./plan-mode-guard";
|
|
38
37
|
import {
|
|
39
38
|
formatDiagnostics,
|
|
39
|
+
formatErrorDetail,
|
|
40
40
|
formatExpandHint,
|
|
41
41
|
formatMoreItems,
|
|
42
|
-
formatStatusIcon,
|
|
43
|
-
formatTitle,
|
|
44
42
|
getLspBatchRequest,
|
|
45
43
|
replaceTabs,
|
|
46
44
|
shortenPath,
|
|
@@ -79,6 +77,9 @@ export interface WriteToolDetails {
|
|
|
79
77
|
meta?: OutputMeta;
|
|
80
78
|
/** Set when the file was auto-chmod'd because content begins with a `#!` shebang. */
|
|
81
79
|
madeExecutable?: boolean;
|
|
80
|
+
/** Absolute filesystem path the write resolved to. Used by the renderer to wrap
|
|
81
|
+
* the (possibly cwd-relative) header path in an OSC 8 `file://` hyperlink. */
|
|
82
|
+
resolvedPath?: string;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
/**
|
|
@@ -418,7 +419,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
418
419
|
}`;
|
|
419
420
|
return {
|
|
420
421
|
content: [{ type: "text", text: `Successfully wrote ${content.length} bytes to ${outputPath}` }],
|
|
421
|
-
details: {},
|
|
422
|
+
details: { resolvedPath: resolvedArchivePath.absolutePath },
|
|
422
423
|
};
|
|
423
424
|
}
|
|
424
425
|
|
|
@@ -534,7 +535,10 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
534
535
|
}
|
|
535
536
|
|
|
536
537
|
invalidateFsScanAfterWrite(resolvedSqlitePath.absolutePath);
|
|
537
|
-
return toolResult<WriteToolDetails>({
|
|
538
|
+
return toolResult<WriteToolDetails>({ resolvedPath: resolvedSqlitePath.absolutePath })
|
|
539
|
+
.text(resultText)
|
|
540
|
+
.sourcePath(resolvedSqlitePath.absolutePath)
|
|
541
|
+
.done();
|
|
538
542
|
} catch (error) {
|
|
539
543
|
if (isEnoent(error)) {
|
|
540
544
|
throw new ToolError(`SQLite database '${displayPath}' not found`);
|
|
@@ -595,12 +599,13 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
595
599
|
if (!diagnostics) {
|
|
596
600
|
return {
|
|
597
601
|
content: [{ type: "text", text: resultText }],
|
|
598
|
-
details: {},
|
|
602
|
+
details: { resolvedPath: absolutePath },
|
|
599
603
|
};
|
|
600
604
|
}
|
|
601
605
|
return {
|
|
602
606
|
content: [{ type: "text", text: resultText }],
|
|
603
607
|
details: {
|
|
608
|
+
resolvedPath: absolutePath,
|
|
604
609
|
diagnostics,
|
|
605
610
|
meta: outputMeta()
|
|
606
611
|
.diagnostics(diagnostics.summary, diagnostics.messages ?? [])
|
|
@@ -873,7 +878,10 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
873
878
|
if (stripped) {
|
|
874
879
|
resultText += `\nNote: auto-stripped hashline display prefixes from content before writing.`;
|
|
875
880
|
}
|
|
876
|
-
return {
|
|
881
|
+
return {
|
|
882
|
+
content: [{ type: "text", text: resultText }],
|
|
883
|
+
details: { resolvedPath: absolutePath },
|
|
884
|
+
};
|
|
877
885
|
}
|
|
878
886
|
|
|
879
887
|
const diagnostics = await this.#writethrough(absolutePath, cleanContent, signal, undefined, batchRequest);
|
|
@@ -890,13 +898,14 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
890
898
|
if (!diagnostics) {
|
|
891
899
|
return {
|
|
892
900
|
content: [{ type: "text", text: resultText }],
|
|
893
|
-
details: { madeExecutable: madeExecutable || undefined },
|
|
901
|
+
details: { resolvedPath: absolutePath, madeExecutable: madeExecutable || undefined },
|
|
894
902
|
};
|
|
895
903
|
}
|
|
896
904
|
|
|
897
905
|
return {
|
|
898
906
|
content: [{ type: "text", text: resultText }],
|
|
899
907
|
details: {
|
|
908
|
+
resolvedPath: absolutePath,
|
|
900
909
|
diagnostics,
|
|
901
910
|
madeExecutable: madeExecutable || undefined,
|
|
902
911
|
meta: outputMeta()
|
|
@@ -960,9 +969,9 @@ function formatStreamingContent(
|
|
|
960
969
|
}
|
|
961
970
|
for (let i = 0; i < highlighted.length; i++) {
|
|
962
971
|
const lineNum = startIndex + i + 1;
|
|
963
|
-
const gutter = uiTheme.fg("dim", `${String(lineNum).padStart(lineNumberWidth, " ")}
|
|
972
|
+
const gutter = uiTheme.fg("dim", `${String(lineNum).padStart(lineNumberWidth, " ")} `);
|
|
964
973
|
const body = replaceTabs(highlighted[i] ?? "");
|
|
965
|
-
text +=
|
|
974
|
+
text += `${gutter}${body}\n`;
|
|
966
975
|
}
|
|
967
976
|
text += uiTheme.fg("dim", `… (streaming)`);
|
|
968
977
|
return text;
|
|
@@ -986,9 +995,9 @@ function renderContentPreview(
|
|
|
986
995
|
let text = "\n\n";
|
|
987
996
|
for (let i = 0; i < highlighted.length; i++) {
|
|
988
997
|
const lineNum = i + 1;
|
|
989
|
-
const gutter = uiTheme.fg("dim", `${String(lineNum).padStart(lineNumberWidth, " ")}
|
|
998
|
+
const gutter = uiTheme.fg("dim", `${String(lineNum).padStart(lineNumberWidth, " ")} `);
|
|
990
999
|
const body = replaceTabs(highlighted[i] ?? "");
|
|
991
|
-
text +=
|
|
1000
|
+
text += `${gutter}${body}\n`;
|
|
992
1001
|
}
|
|
993
1002
|
if (!expanded && hidden > 0) {
|
|
994
1003
|
const hint = formatExpandHint(uiTheme, expanded, hidden > 0);
|
|
@@ -1005,23 +1014,44 @@ export const writeToolRenderer = {
|
|
|
1005
1014
|
const lang = getLanguageFromPath(rawPath) ?? "text";
|
|
1006
1015
|
const langIcon = uiTheme.fg("muted", uiTheme.getLangIcon(lang));
|
|
1007
1016
|
const pathDisplay = filePath ? uiTheme.fg("accent", filePath) : uiTheme.fg("toolOutput", "…");
|
|
1008
|
-
const
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1017
|
+
const header = renderStatusLine(
|
|
1018
|
+
{
|
|
1019
|
+
icon: "pending",
|
|
1020
|
+
spinnerFrame: options?.spinnerFrame,
|
|
1021
|
+
title: "Write",
|
|
1022
|
+
description: `${langIcon} ${pathDisplay}`,
|
|
1023
|
+
},
|
|
1024
|
+
uiTheme,
|
|
1025
|
+
);
|
|
1026
|
+
return framedBlock(uiTheme, width => {
|
|
1027
|
+
const body = args.content
|
|
1028
|
+
? formatStreamingContent(args.content, Boolean(options?.expanded), lang, uiTheme)
|
|
1029
|
+
: "";
|
|
1030
|
+
const bodyLines = body ? body.split("\n") : [];
|
|
1031
|
+
while (bodyLines.length > 0 && bodyLines[0].trim() === "") bodyLines.shift();
|
|
1032
|
+
return {
|
|
1033
|
+
header,
|
|
1034
|
+
sections: bodyLines.length > 0 ? [{ lines: bodyLines }] : [],
|
|
1035
|
+
state: "pending",
|
|
1036
|
+
borderColor: "borderMuted",
|
|
1037
|
+
width,
|
|
1038
|
+
};
|
|
1039
|
+
});
|
|
1040
|
+
},
|
|
1019
1041
|
|
|
1020
|
-
|
|
1042
|
+
// Only the expanded (Ctrl+O) preview is append-only: it renders the whole
|
|
1043
|
+
// content top-anchored, so streamed chunks only append rows at the bottom.
|
|
1044
|
+
// The collapsed preview slides a bounded tail window (`formatStreamingContent`
|
|
1045
|
+
// with `WRITE_STREAMING_PREVIEW_LINES`) whose visible rows re-layout as the
|
|
1046
|
+
// window moves — not append-only, but it never overflows the viewport, so its
|
|
1047
|
+
// head is never at risk of being dropped regardless. `write` has no partial
|
|
1048
|
+
// result (content streams as args), so `result` is ignored here.
|
|
1049
|
+
isStreamingPreviewAppendOnly(args: WriteRenderArgs, options: RenderResultOptions, _result?: unknown): boolean {
|
|
1050
|
+
return Boolean(options?.expanded && args.content);
|
|
1021
1051
|
},
|
|
1022
1052
|
|
|
1023
1053
|
renderResult(
|
|
1024
|
-
result: { content: Array<{ type: string; text?: string }>; details?: WriteToolDetails },
|
|
1054
|
+
result: { content: Array<{ type: string; text?: string }>; details?: WriteToolDetails; isError?: boolean },
|
|
1025
1055
|
options: RenderResultOptions,
|
|
1026
1056
|
uiTheme: Theme,
|
|
1027
1057
|
args?: WriteRenderArgs,
|
|
@@ -1031,14 +1061,33 @@ export const writeToolRenderer = {
|
|
|
1031
1061
|
const fileContent = args?.content || "";
|
|
1032
1062
|
const lang = getLanguageFromPath(rawPath);
|
|
1033
1063
|
const langIcon = uiTheme.fg("muted", uiTheme.getLangIcon(lang));
|
|
1034
|
-
|
|
1064
|
+
// The header shows the cwd-relative path but links to the absolute path the
|
|
1065
|
+
// write resolved to (args.path may be relative, which would yield a broken
|
|
1066
|
+
// `file://` URI). Falls back to plain text when the result lacks a path.
|
|
1067
|
+
const linkTarget = result.details?.resolvedPath;
|
|
1068
|
+
const styledPath = filePath ? uiTheme.fg("accent", filePath) : uiTheme.fg("toolOutput", "…");
|
|
1069
|
+
const pathDisplay = filePath && linkTarget ? fileHyperlink(linkTarget, styledPath) : styledPath;
|
|
1070
|
+
|
|
1071
|
+
if (result.isError) {
|
|
1072
|
+
const errorText = result.content?.find(c => c.type === "text")?.text ?? "";
|
|
1073
|
+
const header = renderStatusLine(
|
|
1074
|
+
{ icon: "error", title: "Write", description: `${langIcon} ${pathDisplay}` },
|
|
1075
|
+
uiTheme,
|
|
1076
|
+
);
|
|
1077
|
+
return framedBlock(uiTheme, width => ({
|
|
1078
|
+
header,
|
|
1079
|
+
sections: [{ lines: formatErrorDetail(errorText, uiTheme).split("\n") }],
|
|
1080
|
+
state: "error",
|
|
1081
|
+
borderColor: "error",
|
|
1082
|
+
width,
|
|
1083
|
+
}));
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1035
1086
|
const lineCount = countLines(fileContent);
|
|
1036
1087
|
const lineSuffix = formatLineCountSuffix(lineCount, uiTheme);
|
|
1037
1088
|
const execSuffix = result.details?.madeExecutable
|
|
1038
1089
|
? `${uiTheme.fg("dim", " · ")}${uiTheme.fg("success", "made executable!")}`
|
|
1039
1090
|
: "";
|
|
1040
|
-
|
|
1041
|
-
// Build header with status icon
|
|
1042
1091
|
const header = renderStatusLine(
|
|
1043
1092
|
{
|
|
1044
1093
|
icon: "success",
|
|
@@ -1049,38 +1098,29 @@ export const writeToolRenderer = {
|
|
|
1049
1098
|
);
|
|
1050
1099
|
const diagnostics = result.details?.diagnostics;
|
|
1051
1100
|
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
const
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
if (diagnostics) {
|
|
1064
|
-
const diagText = formatDiagnostics(diagnostics, expanded, uiTheme, fp =>
|
|
1065
|
-
uiTheme.getLangIcon(getLanguageFromPath(fp)),
|
|
1066
|
-
);
|
|
1067
|
-
if (diagText.trim()) {
|
|
1068
|
-
const diagLines = diagText.split("\n");
|
|
1069
|
-
const firstNonEmpty = diagLines.findIndex(line => line.trim());
|
|
1070
|
-
if (firstNonEmpty >= 0) {
|
|
1071
|
-
text += `\n${diagLines.slice(firstNonEmpty).join("\n")}`;
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1101
|
+
return framedBlock(uiTheme, width => {
|
|
1102
|
+
const { expanded } = options;
|
|
1103
|
+
let body = renderContentPreview(fileContent, expanded, lang, uiTheme);
|
|
1104
|
+
if (diagnostics) {
|
|
1105
|
+
const diagText = formatDiagnostics(diagnostics, expanded, uiTheme, fp =>
|
|
1106
|
+
uiTheme.getLangIcon(getLanguageFromPath(fp)),
|
|
1107
|
+
);
|
|
1108
|
+
if (diagText.trim()) {
|
|
1109
|
+
const diagLines = diagText.split("\n");
|
|
1110
|
+
const firstNonEmpty = diagLines.findIndex(line => line.trim());
|
|
1111
|
+
if (firstNonEmpty >= 0) body += `\n${diagLines.slice(firstNonEmpty).join("\n")}`;
|
|
1074
1112
|
}
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1113
|
+
}
|
|
1114
|
+
const bodyLines = body.split("\n");
|
|
1115
|
+
while (bodyLines.length > 0 && bodyLines[0].trim() === "") bodyLines.shift();
|
|
1116
|
+
return {
|
|
1117
|
+
header,
|
|
1118
|
+
sections: bodyLines.length > 0 ? [{ lines: bodyLines }] : [],
|
|
1119
|
+
state: "success",
|
|
1120
|
+
borderColor: "borderMuted",
|
|
1121
|
+
width,
|
|
1122
|
+
};
|
|
1123
|
+
});
|
|
1084
1124
|
},
|
|
1085
1125
|
mergeCallAndResult: true,
|
|
1086
1126
|
};
|