@oh-my-pi/pi-coding-agent 14.9.9 → 15.0.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 +123 -0
- package/examples/extensions/plan-mode.ts +0 -1
- package/package.json +9 -9
- package/scripts/build-binary.ts +5 -0
- package/scripts/format-prompts.ts +1 -1
- package/src/autoresearch/helpers.ts +17 -0
- package/src/autoresearch/tools/log-experiment.ts +9 -17
- package/src/autoresearch/tools/run-experiment.ts +2 -17
- package/src/capability/skill.ts +7 -0
- package/src/cli/args.ts +2 -2
- package/src/cli/list-models.ts +1 -1
- package/src/cli/shell-cli.ts +3 -13
- package/src/cli/update-cli.ts +1 -1
- package/src/cli.ts +11 -29
- package/src/commands/acp.ts +24 -0
- package/src/commands/launch.ts +6 -4
- package/src/commit/agentic/prompts/system.md +1 -1
- package/src/commit/agentic/tools/propose-changelog.ts +8 -1
- package/src/commit/analysis/conventional.ts +8 -66
- package/src/commit/map-reduce/reduce-phase.ts +6 -65
- package/src/commit/pipeline.ts +2 -2
- package/src/commit/shared-llm.ts +89 -0
- package/src/config/config-file.ts +210 -0
- package/src/config/model-equivalence.ts +8 -11
- package/src/config/model-registry.ts +13 -2
- package/src/config/model-resolver.ts +31 -4
- package/src/config/settings-schema.ts +102 -1
- package/src/config/settings.ts +1 -1
- package/src/config.ts +3 -219
- package/src/edit/index.ts +22 -1
- package/src/edit/modes/patch.ts +10 -0
- package/src/edit/modes/replace.ts +3 -0
- package/src/edit/renderer.ts +17 -1
- package/src/eval/js/context-manager.ts +1 -1
- package/src/eval/js/executor.ts +3 -0
- package/src/eval/js/shared/rewrite-imports.ts +122 -50
- package/src/eval/js/shared/runtime.ts +31 -4
- package/src/eval/js/tool-bridge.ts +43 -21
- package/src/eval/py/executor.ts +5 -0
- package/src/exa/factory.ts +2 -2
- package/src/exa/mcp-client.ts +74 -1
- package/src/exec/bash-executor.ts +5 -1
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +0 -11
- package/src/extensibility/extensions/runner.ts +55 -2
- package/src/extensibility/extensions/types.ts +98 -221
- package/src/extensibility/hooks/types.ts +89 -314
- package/src/extensibility/shared-events.ts +343 -0
- package/src/extensibility/skills.ts +42 -1
- package/src/goals/index.ts +3 -0
- package/src/goals/runtime.ts +500 -0
- package/src/goals/state.ts +37 -0
- package/src/goals/tools/goal-tool.ts +237 -0
- package/src/hashline/anchors.ts +2 -2
- package/src/hindsight/mental-models.ts +1 -1
- package/src/internal-urls/agent-protocol.ts +1 -20
- package/src/internal-urls/artifact-protocol.ts +1 -19
- package/src/internal-urls/docs-index.generated.ts +9 -10
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/issue-pr-protocol.ts +577 -0
- package/src/internal-urls/registry-helpers.ts +25 -0
- package/src/internal-urls/router.ts +6 -3
- package/src/internal-urls/types.ts +22 -1
- package/src/main.ts +24 -11
- package/src/mcp/oauth-flow.ts +20 -0
- package/src/modes/acp/acp-agent.ts +412 -71
- package/src/modes/acp/acp-client-bridge.ts +152 -0
- package/src/modes/acp/acp-event-mapper.ts +180 -15
- package/src/modes/acp/terminal-auth.ts +37 -0
- package/src/modes/components/assistant-message.ts +14 -8
- package/src/modes/components/bash-execution.ts +24 -63
- package/src/modes/components/custom-message.ts +14 -40
- package/src/modes/components/eval-execution.ts +27 -57
- package/src/modes/components/execution-shared.ts +102 -0
- package/src/modes/components/hook-message.ts +17 -49
- package/src/modes/components/mcp-add-wizard.ts +26 -5
- package/src/modes/components/message-frame.ts +88 -0
- package/src/modes/components/model-selector.ts +1 -1
- package/src/modes/components/read-tool-group.ts +29 -1
- package/src/modes/components/session-observer-overlay.ts +6 -2
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/status-line/segments.ts +55 -4
- package/src/modes/components/status-line/types.ts +4 -0
- package/src/modes/components/status-line.ts +28 -10
- package/src/modes/components/tool-execution.ts +7 -8
- package/src/modes/controllers/command-controller-shared.ts +108 -0
- package/src/modes/controllers/command-controller.ts +27 -10
- package/src/modes/controllers/event-controller.ts +60 -18
- package/src/modes/controllers/extension-ui-controller.ts +8 -2
- package/src/modes/controllers/input-controller.ts +85 -39
- package/src/modes/controllers/mcp-command-controller.ts +56 -61
- package/src/modes/controllers/ssh-command-controller.ts +18 -57
- package/src/modes/interactive-mode.ts +675 -39
- package/src/modes/print-mode.ts +16 -86
- package/src/modes/rpc/rpc-mode.ts +30 -88
- package/src/modes/runtime-init.ts +115 -0
- package/src/modes/theme/defaults/dark-poimandres.json +2 -0
- package/src/modes/theme/defaults/light-poimandres.json +2 -0
- package/src/modes/theme/theme.ts +18 -6
- package/src/modes/types.ts +20 -5
- package/src/modes/utils/context-usage.ts +13 -13
- package/src/modes/utils/ui-helpers.ts +25 -6
- package/src/plan-mode/approved-plan.ts +35 -1
- package/src/prompts/agents/designer.md +5 -5
- package/src/prompts/agents/explore.md +7 -7
- package/src/prompts/agents/init.md +9 -9
- package/src/prompts/agents/librarian.md +14 -14
- package/src/prompts/agents/plan.md +4 -4
- package/src/prompts/agents/reviewer.md +5 -5
- package/src/prompts/agents/task.md +10 -10
- package/src/prompts/commands/orchestrate.md +2 -2
- package/src/prompts/compaction/branch-summary.md +3 -3
- package/src/prompts/compaction/compaction-short-summary.md +7 -7
- package/src/prompts/compaction/compaction-summary-context.md +1 -1
- package/src/prompts/compaction/compaction-summary.md +5 -5
- package/src/prompts/compaction/compaction-turn-prefix.md +3 -3
- package/src/prompts/compaction/compaction-update-summary.md +11 -11
- package/src/prompts/goals/goal-budget-limit.md +16 -0
- package/src/prompts/goals/goal-continuation.md +28 -0
- package/src/prompts/goals/goal-mode-active.md +23 -0
- package/src/prompts/memories/consolidation.md +2 -2
- package/src/prompts/memories/read-path.md +1 -1
- package/src/prompts/memories/stage_one_input.md +1 -1
- package/src/prompts/memories/stage_one_system.md +5 -5
- package/src/prompts/review-request.md +4 -4
- package/src/prompts/system/agent-creation-architect.md +17 -17
- package/src/prompts/system/agent-creation-user.md +2 -2
- package/src/prompts/system/commit-message-system.md +2 -2
- package/src/prompts/system/custom-system-prompt.md +2 -2
- package/src/prompts/system/eager-todo.md +6 -6
- package/src/prompts/system/handoff-document.md +1 -1
- package/src/prompts/system/plan-mode-active.md +25 -24
- package/src/prompts/system/plan-mode-approved.md +4 -4
- package/src/prompts/system/plan-mode-compact-instructions.md +16 -0
- package/src/prompts/system/plan-mode-reference.md +2 -2
- package/src/prompts/system/plan-mode-subagent.md +8 -8
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +3 -3
- package/src/prompts/system/project-prompt.md +4 -4
- package/src/prompts/system/subagent-system-prompt.md +7 -7
- package/src/prompts/system/subagent-yield-reminder.md +4 -4
- package/src/prompts/system/system-prompt.md +72 -71
- package/src/prompts/system/ttsr-interrupt.md +1 -1
- package/src/prompts/tools/apply-patch.md +1 -1
- package/src/prompts/tools/ast-edit.md +3 -3
- package/src/prompts/tools/ast-grep.md +3 -3
- package/src/prompts/tools/bash.md +6 -0
- package/src/prompts/tools/browser.md +3 -3
- package/src/prompts/tools/checkpoint.md +3 -3
- package/src/prompts/tools/find.md +3 -3
- package/src/prompts/tools/github.md +2 -5
- package/src/prompts/tools/goal.md +13 -0
- package/src/prompts/tools/hashline.md +104 -116
- package/src/prompts/tools/image-gen.md +3 -3
- package/src/prompts/tools/irc.md +1 -1
- package/src/prompts/tools/lsp.md +2 -2
- package/src/prompts/tools/patch.md +6 -6
- package/src/prompts/tools/read.md +8 -7
- package/src/prompts/tools/replace.md +5 -5
- package/src/prompts/tools/resolve.md +6 -5
- package/src/prompts/tools/retain.md +1 -1
- package/src/prompts/tools/rewind.md +2 -2
- package/src/prompts/tools/search.md +2 -2
- package/src/prompts/tools/ssh.md +2 -2
- package/src/prompts/tools/task.md +12 -6
- package/src/prompts/tools/web-search.md +2 -2
- package/src/prompts/tools/write.md +3 -3
- package/src/sdk.ts +81 -17
- package/src/session/agent-session.ts +656 -125
- package/src/session/blob-store.ts +36 -3
- package/src/session/client-bridge.ts +81 -0
- package/src/session/compaction/errors.ts +31 -0
- package/src/session/compaction/index.ts +1 -0
- package/src/session/messages.ts +67 -2
- package/src/session/session-manager.ts +131 -12
- package/src/session/session-storage.ts +33 -15
- package/src/session/streaming-output.ts +309 -13
- package/src/slash-commands/acp-builtins.ts +46 -0
- package/src/slash-commands/builtin-registry.ts +717 -116
- package/src/slash-commands/helpers/context-report.ts +39 -0
- package/src/slash-commands/helpers/format.ts +23 -0
- package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
- package/src/slash-commands/helpers/mcp.ts +532 -0
- package/src/slash-commands/helpers/parse.ts +85 -0
- package/src/slash-commands/helpers/ssh.ts +193 -0
- package/src/slash-commands/helpers/todo.ts +279 -0
- package/src/slash-commands/helpers/usage-report.ts +91 -0
- package/src/slash-commands/types.ts +126 -0
- package/src/ssh/ssh-executor.ts +5 -0
- package/src/system-prompt.ts +4 -2
- package/src/task/executor.ts +27 -10
- package/src/task/index.ts +20 -1
- package/src/task/render.ts +27 -18
- package/src/task/types.ts +4 -0
- package/src/tools/ast-edit.ts +21 -120
- package/src/tools/ast-grep.ts +21 -119
- package/src/tools/bash-interactive.ts +9 -1
- package/src/tools/bash.ts +203 -6
- package/src/tools/browser/attach.ts +3 -3
- package/src/tools/browser/launch.ts +81 -18
- package/src/tools/browser/registry.ts +1 -5
- package/src/tools/browser/tab-supervisor.ts +51 -14
- package/src/tools/conflict-detect.ts +21 -10
- package/src/tools/eval.ts +3 -1
- package/src/tools/fetch.ts +15 -4
- package/src/tools/find.ts +39 -39
- package/src/tools/gh-renderer.ts +0 -12
- package/src/tools/gh.ts +689 -182
- package/src/tools/github-cache.ts +548 -0
- package/src/tools/index.ts +25 -11
- package/src/tools/inspect-image.ts +3 -10
- package/src/tools/output-meta.ts +176 -37
- package/src/tools/path-utils.ts +125 -2
- package/src/tools/read.ts +605 -239
- package/src/tools/render-utils.ts +92 -0
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resolve.ts +72 -44
- package/src/tools/search.ts +120 -186
- package/src/tools/write.ts +67 -10
- package/src/tui/code-cell.ts +70 -2
- package/src/utils/file-mentions.ts +1 -1
- package/src/utils/image-loading.ts +7 -3
- package/src/utils/image-resize.ts +32 -43
- package/src/vim/parser.ts +0 -17
- package/src/vim/render.ts +1 -1
- package/src/vim/types.ts +1 -1
- package/src/web/search/providers/gemini.ts +35 -95
- package/src/prompts/tools/exit-plan-mode.md +0 -6
- package/src/tools/exit-plan-mode.ts +0 -97
- package/src/utils/fuzzy.ts +0 -108
- package/src/utils/image-convert.ts +0 -27
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { Settings } from "../config/settings";
|
|
2
|
+
import type { InteractiveModeContext } from "../modes/types";
|
|
3
|
+
import type { AgentSession } from "../session/agent-session";
|
|
4
|
+
import type { SessionManager } from "../session/session-manager";
|
|
5
|
+
|
|
6
|
+
/** Declarative subcommand definition for commands like /mcp. */
|
|
7
|
+
export interface SubcommandDef {
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
/** Usage hint shown as dim ghost text, e.g. "<name> [--scope project|user]". */
|
|
11
|
+
usage?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Declarative builtin slash command metadata used by autocomplete and help UI. */
|
|
15
|
+
export interface BuiltinSlashCommand {
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
/** Subcommands for dropdown completion (e.g. /mcp add, /mcp list). */
|
|
19
|
+
subcommands?: SubcommandDef[];
|
|
20
|
+
/** Static inline hint when command takes a simple argument (no subcommands). */
|
|
21
|
+
inlineHint?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Parsed slash-command text after stripping the leading "/". */
|
|
25
|
+
export interface ParsedSlashCommand {
|
|
26
|
+
name: string;
|
|
27
|
+
args: string;
|
|
28
|
+
text: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Result returned by a slash-command handler.
|
|
33
|
+
*
|
|
34
|
+
* - `void` / `undefined` — command was handled and consumed; no further input.
|
|
35
|
+
* - `{ consumed: true }` — explicit equivalent of the above (ACP shape).
|
|
36
|
+
* - `{ prompt: string }` — command handled, pass `prompt` through as the new
|
|
37
|
+
* user input (e.g. `/force <tool> <prompt>` keeps `<prompt>` as the message).
|
|
38
|
+
*/
|
|
39
|
+
export type SlashCommandResult = undefined | { consumed: true } | { prompt: string };
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Runtime visible to slash-command handlers that run in text/ACP mode.
|
|
43
|
+
*
|
|
44
|
+
* Both the TUI dispatcher (when invoking a `handle` via its adapter) and the
|
|
45
|
+
* ACP dispatcher pass this shape. Implementations MUST NOT depend on TUI-only
|
|
46
|
+
* state (editor, selectors, status line).
|
|
47
|
+
*/
|
|
48
|
+
export interface SlashCommandRuntime {
|
|
49
|
+
session: AgentSession;
|
|
50
|
+
sessionManager: SessionManager;
|
|
51
|
+
settings: Settings;
|
|
52
|
+
cwd: string;
|
|
53
|
+
/** Emit text to the operator. TUI maps to `ctx.showStatus`, ACP to `sessionUpdate`. */
|
|
54
|
+
output: (text: string) => Promise<void> | void;
|
|
55
|
+
/** Re-advertise the available command list (no-op outside ACP). */
|
|
56
|
+
refreshCommands: () => Promise<void> | void;
|
|
57
|
+
/**
|
|
58
|
+
* Reload plugin state (caches, slash command registry, project registries)
|
|
59
|
+
* and re-emit available commands. Used by `/reload-plugins`, `/move`, and
|
|
60
|
+
* `/marketplace`/`/plugins` mutations so the session sees a consistent view
|
|
61
|
+
* after plugin or project-scope changes.
|
|
62
|
+
*/
|
|
63
|
+
reloadPlugins: () => Promise<void>;
|
|
64
|
+
notifyTitleChanged?: () => Promise<void> | void;
|
|
65
|
+
notifyConfigChanged?: () => Promise<void> | void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Runtime visible to TUI-only handlers (`handleTui`). Carries the interactive
|
|
70
|
+
* mode context plus the background-detach hook. Intentionally narrower than
|
|
71
|
+
* `SlashCommandRuntime` so existing callers can keep building it from just
|
|
72
|
+
* `{ ctx, handleBackgroundCommand }`; when the TUI dispatcher needs to invoke
|
|
73
|
+
* a `handle` (no `handleTui` override), it synthesizes a `SlashCommandRuntime`
|
|
74
|
+
* from `ctx`.
|
|
75
|
+
*/
|
|
76
|
+
export interface TuiSlashCommandRuntime {
|
|
77
|
+
ctx: InteractiveModeContext;
|
|
78
|
+
handleBackgroundCommand: () => void;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Unified slash-command spec consumed by both TUI and ACP dispatchers. */
|
|
82
|
+
export interface SlashCommandSpec extends BuiltinSlashCommand {
|
|
83
|
+
aliases?: string[];
|
|
84
|
+
/** When false, the dispatcher refuses to handle invocations that include arguments. */
|
|
85
|
+
allowArgs?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* ACP-specific override for `description`. Used by `ACP_BUILTIN_SLASH_COMMANDS`
|
|
88
|
+
* when building `available_commands_update` payloads so the client receives
|
|
89
|
+
* mode-appropriate copy (e.g. `/dump` advertises "Return full transcript as
|
|
90
|
+
* plain text" in ACP rather than the TUI's clipboard-centric copy).
|
|
91
|
+
*/
|
|
92
|
+
acpDescription?: string;
|
|
93
|
+
/**
|
|
94
|
+
* ACP-specific override for the advertised input hint. `subcommands`-only
|
|
95
|
+
* specs that historically advertised `<subcommand>` / `[on|off|status]` /
|
|
96
|
+
* `info|delete` to ACP clients carry the hint here so the unification does
|
|
97
|
+
* not silently drop it from `available_commands_update`.
|
|
98
|
+
*/
|
|
99
|
+
acpInputHint?: string;
|
|
100
|
+
/**
|
|
101
|
+
* Text/ACP-mode handler. The same body is invoked from the ACP dispatcher
|
|
102
|
+
* and, via the TUI adapter, when no `handleTui` override is provided.
|
|
103
|
+
*/
|
|
104
|
+
handle?: (
|
|
105
|
+
command: ParsedSlashCommand,
|
|
106
|
+
runtime: SlashCommandRuntime,
|
|
107
|
+
) => Promise<SlashCommandResult> | SlashCommandResult;
|
|
108
|
+
/**
|
|
109
|
+
* TUI-only handler that supersedes `handle` when both are present. Use for
|
|
110
|
+
* selectors, wizards, dashboards, and anything else that requires
|
|
111
|
+
* `InteractiveModeContext`.
|
|
112
|
+
*/
|
|
113
|
+
handleTui?: (
|
|
114
|
+
command: ParsedSlashCommand,
|
|
115
|
+
runtime: TuiSlashCommandRuntime,
|
|
116
|
+
) => Promise<SlashCommandResult> | SlashCommandResult;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @deprecated Use `SlashCommandRuntime` directly. Retained as an alias so
|
|
121
|
+
* downstream code that imported the ACP-specific name keeps compiling.
|
|
122
|
+
*/
|
|
123
|
+
export type AcpBuiltinCommandRuntime = SlashCommandRuntime;
|
|
124
|
+
|
|
125
|
+
/** Result returned by `executeAcpBuiltinSlashCommand`. */
|
|
126
|
+
export type AcpBuiltinSlashCommandResult = false | { consumed: true } | { prompt: string };
|
package/src/ssh/ssh-executor.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { logger, ptree } from "@oh-my-pi/pi-utils";
|
|
2
|
+
import { Settings } from "../config/settings";
|
|
2
3
|
import { OutputSink } from "../session/streaming-output";
|
|
4
|
+
import { resolveOutputMaxColumns, resolveOutputSinkHeadBytes } from "../tools/output-meta";
|
|
3
5
|
import { buildRemoteCommand, ensureConnection, ensureHostInfo, type SSHConnectionTarget } from "./connection-manager";
|
|
4
6
|
import { hasSshfs, mountRemote } from "./sshfs-mount";
|
|
5
7
|
|
|
@@ -83,10 +85,13 @@ export async function executeSSH(
|
|
|
83
85
|
stderr: "full",
|
|
84
86
|
});
|
|
85
87
|
|
|
88
|
+
const settings = await Settings.init();
|
|
86
89
|
const sink = new OutputSink({
|
|
87
90
|
onChunk: options?.onChunk,
|
|
88
91
|
artifactPath: options?.artifactPath,
|
|
89
92
|
artifactId: options?.artifactId,
|
|
93
|
+
headBytes: resolveOutputSinkHeadBytes(settings),
|
|
94
|
+
maxColumns: resolveOutputMaxColumns(settings),
|
|
90
95
|
});
|
|
91
96
|
|
|
92
97
|
const streams = [child.stdout.pipeTo(sink.createInput())];
|
package/src/system-prompt.ts
CHANGED
|
@@ -530,9 +530,11 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
530
530
|
description: tools?.get(name)?.description ?? "",
|
|
531
531
|
}));
|
|
532
532
|
|
|
533
|
-
// Filter skills
|
|
533
|
+
// Filter skills for the rendered system prompt:
|
|
534
|
+
// - require the `read` tool so the model can actually fetch skill content;
|
|
535
|
+
// - drop skills with frontmatter `hide: true` (still loadable via skill:// and /skill:<name>).
|
|
534
536
|
const hasRead = tools?.has("read");
|
|
535
|
-
const filteredSkills = hasRead ? skills : [];
|
|
537
|
+
const filteredSkills = hasRead ? skills.filter(skill => skill.hide !== true) : [];
|
|
536
538
|
|
|
537
539
|
const effectiveSystemPromptCustomization = dedupePromptSource(systemPromptCustomization, [
|
|
538
540
|
resolvedCustomPrompt,
|
package/src/task/executor.ts
CHANGED
|
@@ -379,21 +379,29 @@ function firstNumberField(record: Record<string, unknown>, keys: string[]): numb
|
|
|
379
379
|
}
|
|
380
380
|
|
|
381
381
|
/**
|
|
382
|
-
*
|
|
382
|
+
* Tokens for progress display: input + output + cacheWrite per turn.
|
|
383
|
+
*
|
|
384
|
+
* Deliberately excludes cacheRead. With prompt caching, cacheRead in each turn
|
|
385
|
+
* equals the full cached context (potentially hundreds of KB), so summing it
|
|
386
|
+
* across all turns produces a cumulative total that is N×context_size — far
|
|
387
|
+
* larger than the context window and misleading as a "work done" metric.
|
|
388
|
+
* cacheWrite is kept because each byte is written once, not repeated per turn.
|
|
389
|
+
* The cost segment handles billing; dedicated cache_read/cache_write segments
|
|
390
|
+
* handle cache-specific monitoring.
|
|
383
391
|
*/
|
|
384
392
|
function getUsageTokens(usage: unknown): number {
|
|
385
393
|
if (!usage || typeof usage !== "object") return 0;
|
|
386
394
|
const record = usage as Record<string, unknown>;
|
|
387
395
|
|
|
388
|
-
const totalTokens = firstNumberField(record, ["totalTokens", "total_tokens"]);
|
|
389
|
-
if (totalTokens !== undefined && totalTokens > 0) return totalTokens;
|
|
390
|
-
|
|
391
396
|
const input = firstNumberField(record, ["input", "input_tokens", "inputTokens"]) ?? 0;
|
|
392
397
|
const output = firstNumberField(record, ["output", "output_tokens", "outputTokens"]) ?? 0;
|
|
393
|
-
const cacheRead = firstNumberField(record, ["cacheRead", "cache_read", "cacheReadTokens"]) ?? 0;
|
|
394
398
|
const cacheWrite = firstNumberField(record, ["cacheWrite", "cache_write", "cacheWriteTokens"]) ?? 0;
|
|
395
|
-
|
|
396
|
-
|
|
399
|
+
const computed = input + output + cacheWrite;
|
|
400
|
+
if (computed > 0) return computed;
|
|
401
|
+
// Fallback for providers that only surface a pre-summed total without individual
|
|
402
|
+
// field breakdown. This total includes cacheRead, but returning it is still better
|
|
403
|
+
// than silently showing 0 for those providers.
|
|
404
|
+
return firstNumberField(record, ["totalTokens", "total_tokens"]) ?? 0;
|
|
397
405
|
}
|
|
398
406
|
|
|
399
407
|
/**
|
|
@@ -497,6 +505,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
497
505
|
recentOutput: [],
|
|
498
506
|
toolCount: 0,
|
|
499
507
|
tokens: 0,
|
|
508
|
+
cost: 0,
|
|
500
509
|
durationMs: 0,
|
|
501
510
|
modelOverride,
|
|
502
511
|
};
|
|
@@ -892,6 +901,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
892
901
|
accumulatedUsage.cost.cacheRead += getNumberField(costRecord, "cacheRead") ?? 0;
|
|
893
902
|
accumulatedUsage.cost.cacheWrite += getNumberField(costRecord, "cacheWrite") ?? 0;
|
|
894
903
|
accumulatedUsage.cost.total += getNumberField(costRecord, "total") ?? 0;
|
|
904
|
+
progress.cost = accumulatedUsage.cost.total;
|
|
895
905
|
}
|
|
896
906
|
}
|
|
897
907
|
// Accumulate tokens for progress display
|
|
@@ -947,10 +957,17 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
947
957
|
|
|
948
958
|
try {
|
|
949
959
|
checkAbort();
|
|
950
|
-
|
|
951
|
-
checkAbort();
|
|
960
|
+
// Pin authStorage to modelRegistry.authStorage — mirrors the createAgentSession invariant.
|
|
952
961
|
const registryFromParent = options.modelRegistry !== undefined;
|
|
953
|
-
const modelRegistry =
|
|
962
|
+
const modelRegistry =
|
|
963
|
+
options.modelRegistry ?? new ModelRegistry(options.authStorage ?? (await discoverAuthStorage()));
|
|
964
|
+
const authStorage = modelRegistry.authStorage;
|
|
965
|
+
if (options.authStorage && options.authStorage !== authStorage) {
|
|
966
|
+
throw new Error(
|
|
967
|
+
"options.authStorage and options.modelRegistry.authStorage must be the same instance when both are provided",
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
checkAbort();
|
|
954
971
|
if (!registryFromParent) {
|
|
955
972
|
await modelRegistry.refresh();
|
|
956
973
|
} else {
|
package/src/task/index.ts
CHANGED
|
@@ -306,6 +306,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
306
306
|
recentOutput: [],
|
|
307
307
|
toolCount: 0,
|
|
308
308
|
tokens: 0,
|
|
309
|
+
cost: 0,
|
|
309
310
|
durationMs: 0,
|
|
310
311
|
});
|
|
311
312
|
}
|
|
@@ -390,6 +391,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
390
391
|
: "failed";
|
|
391
392
|
progress.durationMs = singleResult?.durationMs ?? Math.max(0, Date.now() - startedAt);
|
|
392
393
|
progress.tokens = singleResult?.tokens ?? 0;
|
|
394
|
+
progress.cost = singleResult?.usage?.cost.total ?? 0;
|
|
393
395
|
progress.extractedToolData = singleResult?.extractedToolData;
|
|
394
396
|
}
|
|
395
397
|
completedJobs += 1;
|
|
@@ -483,11 +485,27 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
483
485
|
? ` Failed to schedule ${failedSchedules.length} task${failedSchedules.length === 1 ? "" : "s"}.`
|
|
484
486
|
: "";
|
|
485
487
|
|
|
488
|
+
const ircEnabled = this.session.settings.get("irc.enabled") === true;
|
|
489
|
+
const taskIdByItemId = new Map<string, string>();
|
|
490
|
+
for (let i = 0; i < taskItems.length; i++) {
|
|
491
|
+
taskIdByItemId.set(taskItems[i].id, uniqueIds[i]);
|
|
492
|
+
}
|
|
493
|
+
const startedListing = startedJobs
|
|
494
|
+
.map(({ taskId }) => {
|
|
495
|
+
const id = taskIdByItemId.get(taskId) ?? taskId;
|
|
496
|
+
const desc = progressByTaskId.get(taskId)?.description;
|
|
497
|
+
return desc ? `- \`${id}\` — ${desc}` : `- \`${id}\``;
|
|
498
|
+
})
|
|
499
|
+
.join("\n");
|
|
500
|
+
const coordinationHint = ircEnabled
|
|
501
|
+
? ` DM these ids via \`irc\` to coordinate while they run; reach for \`job\` only to inspect (\`list\`), wait (\`poll\`), or cancel a stuck task.`
|
|
502
|
+
: ` Use \`job\` to inspect (\`list\`), wait (\`poll\`), or cancel a stuck task by id.`;
|
|
503
|
+
|
|
486
504
|
return {
|
|
487
505
|
content: [
|
|
488
506
|
{
|
|
489
507
|
type: "text",
|
|
490
|
-
text: `Started ${startedJobs.length} background task job${startedJobs.length === 1 ? "" : "s"} using ${params.agent}.${scheduleFailureSummary} Results will be delivered when complete
|
|
508
|
+
text: `Started ${startedJobs.length} background task job${startedJobs.length === 1 ? "" : "s"} using ${params.agent}.${scheduleFailureSummary} Results will be delivered when complete.\n${startedListing}\n${coordinationHint}`,
|
|
491
509
|
},
|
|
492
510
|
],
|
|
493
511
|
details: {
|
|
@@ -815,6 +833,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
815
833
|
recentOutput: [],
|
|
816
834
|
toolCount: 0,
|
|
817
835
|
tokens: 0,
|
|
836
|
+
cost: 0,
|
|
818
837
|
durationMs: 0,
|
|
819
838
|
modelOverride,
|
|
820
839
|
description: taskItem.description,
|
package/src/task/render.ts
CHANGED
|
@@ -50,6 +50,24 @@ function getStatusIcon(status: AgentProgress["status"], theme: Theme, spinnerFra
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/** Append tool-count, token, and cost stats to a status line string. */
|
|
54
|
+
function appendAgentStats(
|
|
55
|
+
line: string,
|
|
56
|
+
opts: { toolCount?: number; tokens: number; cost: number },
|
|
57
|
+
theme: Theme,
|
|
58
|
+
): string {
|
|
59
|
+
if (opts.toolCount) {
|
|
60
|
+
line += `${theme.sep.dot}${theme.fg("dim", `${opts.toolCount} tools`)}`;
|
|
61
|
+
}
|
|
62
|
+
if (opts.tokens > 0) {
|
|
63
|
+
line += `${theme.sep.dot}${theme.fg("dim", `${formatNumber(opts.tokens)} tokens`)}`;
|
|
64
|
+
}
|
|
65
|
+
if (opts.cost > 0) {
|
|
66
|
+
line += `${theme.sep.dot}${theme.fg("statusLineCost", `$${opts.cost.toFixed(2)}`)}`;
|
|
67
|
+
}
|
|
68
|
+
return line;
|
|
69
|
+
}
|
|
70
|
+
|
|
53
71
|
function formatFindingSummary(findings: ReportFindingDetails[], theme: Theme): string {
|
|
54
72
|
if (findings.length === 0) return theme.fg("dim", "Findings: none");
|
|
55
73
|
|
|
@@ -526,19 +544,9 @@ function renderAgentProgress(
|
|
|
526
544
|
const taskPreview = truncateToWidth(progress.assignment ?? progress.task, 40);
|
|
527
545
|
statusLine += ` ${theme.fg("muted", taskPreview)}`;
|
|
528
546
|
}
|
|
529
|
-
|
|
530
|
-
statusLine += `${theme.sep.dot}${theme.fg("dim", `${progress.toolCount} tools`)}`;
|
|
531
|
-
}
|
|
532
|
-
if (progress.tokens > 0) {
|
|
533
|
-
statusLine += `${theme.sep.dot}${theme.fg("dim", `${formatNumber(progress.tokens)} tokens`)}`;
|
|
534
|
-
}
|
|
547
|
+
statusLine = appendAgentStats(statusLine, progress, theme);
|
|
535
548
|
} else if (progress.status === "completed") {
|
|
536
|
-
|
|
537
|
-
statusLine += `${theme.sep.dot}${theme.fg("dim", `${progress.toolCount} tools`)}`;
|
|
538
|
-
}
|
|
539
|
-
if (progress.tokens > 0) {
|
|
540
|
-
statusLine += `${theme.sep.dot}${theme.fg("dim", `${formatNumber(progress.tokens)} tokens`)}`;
|
|
541
|
-
}
|
|
549
|
+
statusLine = appendAgentStats(statusLine, progress, theme);
|
|
542
550
|
}
|
|
543
551
|
|
|
544
552
|
lines.push(statusLine);
|
|
@@ -661,8 +669,10 @@ function renderReviewResult(
|
|
|
661
669
|
lines.push(`${continuePrefix} ${theme.fg("dim", replaceTabs(line))}`);
|
|
662
670
|
}
|
|
663
671
|
} else {
|
|
664
|
-
// Preview: first sentence or ~100 chars
|
|
665
|
-
const
|
|
672
|
+
// Preview: first sentence or ~100 chars (flatten tabs/newlines first)
|
|
673
|
+
const flat = replaceTabs(summary.explanation).replace(/[\r\n]+/g, " ");
|
|
674
|
+
const firstSentence = flat.split(/[.!?]/)[0].trim();
|
|
675
|
+
const preview = truncateToWidth(`${firstSentence}.`, 100);
|
|
666
676
|
lines.push(`${continuePrefix}${theme.fg("dim", preview)}`);
|
|
667
677
|
}
|
|
668
678
|
}
|
|
@@ -701,7 +711,8 @@ function renderFindings(
|
|
|
701
711
|
const findingContinue = isLastFinding ? " " : `${theme.tree.vertical} `;
|
|
702
712
|
|
|
703
713
|
const { color } = getPriorityInfo(finding.priority);
|
|
704
|
-
const
|
|
714
|
+
const rawTitle = finding.title?.replace(/^\[P\d\]\s*/, "") ?? "Untitled";
|
|
715
|
+
const titleText = replaceTabs(rawTitle).replace(/[\r\n]+/g, " ");
|
|
705
716
|
const loc = `${path.basename(finding.file_path || "<unknown>")}:${finding.line_start}`;
|
|
706
717
|
|
|
707
718
|
lines.push(
|
|
@@ -765,9 +776,7 @@ function renderAgentResult(result: SingleResult, isLast: boolean, expanded: bool
|
|
|
765
776
|
iconColor,
|
|
766
777
|
theme,
|
|
767
778
|
)}`;
|
|
768
|
-
|
|
769
|
-
statusLine += `${theme.sep.dot}${theme.fg("dim", `${formatNumber(result.tokens)} tokens`)}`;
|
|
770
|
-
}
|
|
779
|
+
statusLine = appendAgentStats(statusLine, { tokens: result.tokens, cost: result.usage?.cost.total ?? 0 }, theme);
|
|
771
780
|
statusLine += `${theme.sep.dot}${theme.fg("dim", formatDuration(result.durationMs))}`;
|
|
772
781
|
|
|
773
782
|
if (result.truncated) {
|
package/src/task/types.ts
CHANGED
|
@@ -217,7 +217,10 @@ export interface AgentProgress {
|
|
|
217
217
|
recentTools: Array<{ tool: string; args: string; endMs: number }>;
|
|
218
218
|
recentOutput: string[];
|
|
219
219
|
toolCount: number;
|
|
220
|
+
/** Cumulative input + output + cacheWrite tokens across all turns. Excludes cacheRead (re-reads cached context every turn, making cumulative sum misleading). */
|
|
220
221
|
tokens: number;
|
|
222
|
+
/** Cumulative billing cost in USD, accumulated incrementally from message_end events. */
|
|
223
|
+
cost: number;
|
|
221
224
|
durationMs: number;
|
|
222
225
|
modelOverride?: string | string[];
|
|
223
226
|
/** Data extracted by registered subprocess tool handlers (keyed by tool name) */
|
|
@@ -239,6 +242,7 @@ export interface SingleResult {
|
|
|
239
242
|
stderr: string;
|
|
240
243
|
truncated: boolean;
|
|
241
244
|
durationMs: number;
|
|
245
|
+
/** Cumulative input + output + cacheWrite tokens across all turns. Excludes cacheRead (re-reads cached context every turn, making cumulative sum misleading). */
|
|
242
246
|
tokens: number;
|
|
243
247
|
modelOverride?: string | string[];
|
|
244
248
|
error?: string;
|
package/src/tools/ast-edit.ts
CHANGED
|
@@ -7,33 +7,27 @@ import { $envpos, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
|
7
7
|
import { type Static, Type } from "@sinclair/typebox";
|
|
8
8
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
9
9
|
import { computeLineHash, HL_BODY_SEP } from "../hashline/hash";
|
|
10
|
-
import { InternalUrlRouter } from "../internal-urls";
|
|
11
10
|
import type { Theme } from "../modes/theme/theme";
|
|
12
11
|
import astEditDescription from "../prompts/tools/ast-edit.md" with { type: "text" };
|
|
13
|
-
import { Ellipsis,
|
|
12
|
+
import { Ellipsis, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
|
|
14
13
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
15
14
|
import type { ToolSession } from ".";
|
|
16
15
|
import { createFileRecorder, formatResultPath } from "./file-recorder";
|
|
17
16
|
import { formatGroupedFiles } from "./grouped-file-output";
|
|
18
17
|
import type { OutputMeta } from "./output-meta";
|
|
18
|
+
import { resolveToolSearchScope } from "./path-utils";
|
|
19
19
|
import {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
normalizePathLikeInput,
|
|
23
|
-
parseSearchPath,
|
|
24
|
-
partitionExistingPaths,
|
|
25
|
-
resolveExplicitSearchPaths,
|
|
26
|
-
resolveToCwd,
|
|
27
|
-
} from "./path-utils";
|
|
28
|
-
import {
|
|
20
|
+
appendParseErrorsBulletList,
|
|
21
|
+
createCachedComponent,
|
|
29
22
|
dedupeParseErrors,
|
|
30
23
|
formatCodeFrameLine,
|
|
31
24
|
formatCount,
|
|
32
25
|
formatEmptyMessage,
|
|
33
26
|
formatErrorMessage,
|
|
34
27
|
formatParseErrors,
|
|
35
|
-
|
|
28
|
+
formatParseErrorsCountLabel,
|
|
36
29
|
PREVIEW_LIMITS,
|
|
30
|
+
splitGroupsByBlankLine,
|
|
37
31
|
} from "./render-utils";
|
|
38
32
|
import { queueResolveHandler } from "./resolve";
|
|
39
33
|
import { ToolError } from "./tool-errors";
|
|
@@ -205,63 +199,12 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
205
199
|
const normalizedRewrites = Object.fromEntries(ops);
|
|
206
200
|
const maxFiles = $envpos("PI_MAX_AST_FILES", 1000);
|
|
207
201
|
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
if (rawPaths.some(rawPath => rawPath.length === 0)) {
|
|
215
|
-
throw new ToolError("`paths` must contain non-empty paths or globs");
|
|
216
|
-
}
|
|
217
|
-
const internalRouter = InternalUrlRouter.instance();
|
|
218
|
-
const resolvedPathInputs: string[] = [];
|
|
219
|
-
for (const rawPath of rawPaths) {
|
|
220
|
-
if (!internalRouter.canHandle(rawPath)) {
|
|
221
|
-
resolvedPathInputs.push(rawPath);
|
|
222
|
-
continue;
|
|
223
|
-
}
|
|
224
|
-
if (hasGlobPathChars(rawPath)) {
|
|
225
|
-
throw new ToolError(`Glob patterns are not supported for internal URLs: ${rawPath}`);
|
|
226
|
-
}
|
|
227
|
-
const resource = await internalRouter.resolve(rawPath);
|
|
228
|
-
if (!resource.sourcePath) {
|
|
229
|
-
throw new ToolError(`Cannot rewrite internal URL without backing file: ${rawPath}`);
|
|
230
|
-
}
|
|
231
|
-
resolvedPathInputs.push(resource.sourcePath);
|
|
232
|
-
}
|
|
233
|
-
let effectivePathInputs = resolvedPathInputs;
|
|
234
|
-
if (resolvedPathInputs.length > 1) {
|
|
235
|
-
const partition = await partitionExistingPaths(resolvedPathInputs, this.session.cwd, parseSearchPath);
|
|
236
|
-
if (partition.valid.length === 0) {
|
|
237
|
-
throw new ToolError(`Path not found: ${partition.missing.join(", ")}`);
|
|
238
|
-
}
|
|
239
|
-
effectivePathInputs = partition.valid;
|
|
240
|
-
}
|
|
241
|
-
if (effectivePathInputs.length === 1) {
|
|
242
|
-
const parsedPath = parseSearchPath(effectivePathInputs[0] ?? ".");
|
|
243
|
-
searchPath = resolveToCwd(parsedPath.basePath, this.session.cwd);
|
|
244
|
-
globFilter = parsedPath.glob;
|
|
245
|
-
scopePath = formatScopePath(searchPath);
|
|
246
|
-
} else {
|
|
247
|
-
const multiSearchPath = await resolveExplicitSearchPaths(effectivePathInputs, this.session.cwd, globFilter);
|
|
248
|
-
if (!multiSearchPath) {
|
|
249
|
-
throw new ToolError("`paths` must contain at least one path or glob");
|
|
250
|
-
}
|
|
251
|
-
searchPath = multiSearchPath.basePath;
|
|
252
|
-
globFilter = multiSearchPath.targets ? undefined : multiSearchPath.glob;
|
|
253
|
-
multiTargets = multiSearchPath.targets;
|
|
254
|
-
scopePath = multiSearchPath.scopePath;
|
|
255
|
-
}
|
|
256
|
-
const resolvedSearchPath = searchPath;
|
|
257
|
-
scopePath = scopePath ?? formatScopePath(resolvedSearchPath);
|
|
258
|
-
let isDirectory: boolean;
|
|
259
|
-
try {
|
|
260
|
-
const stat = await Bun.file(resolvedSearchPath).stat();
|
|
261
|
-
isDirectory = stat.isDirectory();
|
|
262
|
-
} catch {
|
|
263
|
-
throw new ToolError(`Path not found: ${scopePath}`);
|
|
264
|
-
}
|
|
202
|
+
const scope = await resolveToolSearchScope({
|
|
203
|
+
rawPaths: params.paths,
|
|
204
|
+
cwd: this.session.cwd,
|
|
205
|
+
internalUrlAction: "rewrite",
|
|
206
|
+
});
|
|
207
|
+
const { searchPath: resolvedSearchPath, scopePath, isDirectory, multiTargets, globFilter } = scope;
|
|
265
208
|
|
|
266
209
|
const result = await runAstEditOnce(multiTargets, resolvedSearchPath, globFilter, {
|
|
267
210
|
rewrites: normalizedRewrites,
|
|
@@ -502,15 +445,7 @@ export const astEditToolRenderer = {
|
|
|
502
445
|
if (filesSearched > 0) meta.push(`searched ${filesSearched}`);
|
|
503
446
|
const header = renderStatusLine({ icon: "warning", title: "AST Edit", description, meta }, uiTheme);
|
|
504
447
|
const lines = [header, formatEmptyMessage("No replacements made", uiTheme)];
|
|
505
|
-
|
|
506
|
-
const capped = details.parseErrors.slice(0, PARSE_ERRORS_LIMIT);
|
|
507
|
-
for (const err of capped) {
|
|
508
|
-
lines.push(uiTheme.fg("warning", ` - ${err}`));
|
|
509
|
-
}
|
|
510
|
-
if (details.parseErrors.length > PARSE_ERRORS_LIMIT) {
|
|
511
|
-
lines.push(uiTheme.fg("dim", ` … ${details.parseErrors.length - PARSE_ERRORS_LIMIT} more`));
|
|
512
|
-
}
|
|
513
|
-
}
|
|
448
|
+
appendParseErrorsBulletList(lines, details?.parseErrors, uiTheme);
|
|
514
449
|
return new Text(lines.join("\n"), 0, 0);
|
|
515
450
|
}
|
|
516
451
|
|
|
@@ -523,28 +458,7 @@ export const astEditToolRenderer = {
|
|
|
523
458
|
const description = rewriteCount === 1 ? args?.ops?.[0]?.pat : undefined;
|
|
524
459
|
|
|
525
460
|
const textContent = result.details?.displayContent ?? result.content?.find(c => c.type === "text")?.text ?? "";
|
|
526
|
-
const
|
|
527
|
-
const hasSeparators = rawLines.some(line => line.trim().length === 0);
|
|
528
|
-
const allGroups: string[][] = [];
|
|
529
|
-
if (hasSeparators) {
|
|
530
|
-
let current: string[] = [];
|
|
531
|
-
for (const line of rawLines) {
|
|
532
|
-
if (line.trim().length === 0) {
|
|
533
|
-
if (current.length > 0) {
|
|
534
|
-
allGroups.push(current);
|
|
535
|
-
current = [];
|
|
536
|
-
}
|
|
537
|
-
continue;
|
|
538
|
-
}
|
|
539
|
-
current.push(line);
|
|
540
|
-
}
|
|
541
|
-
if (current.length > 0) allGroups.push(current);
|
|
542
|
-
} else {
|
|
543
|
-
const nonEmpty = rawLines.filter(line => line.trim().length > 0);
|
|
544
|
-
if (nonEmpty.length > 0) {
|
|
545
|
-
allGroups.push(nonEmpty);
|
|
546
|
-
}
|
|
547
|
-
}
|
|
461
|
+
const allGroups = splitGroupsByBlankLine(textContent.split("\n"));
|
|
548
462
|
const changeGroups = allGroups.filter(
|
|
549
463
|
group => !group[0]?.startsWith("Safety cap reached") && !group[0]?.startsWith("Parse issues:"),
|
|
550
464
|
);
|
|
@@ -560,23 +474,15 @@ export const astEditToolRenderer = {
|
|
|
560
474
|
extraLines.push(uiTheme.fg("warning", "limit reached; narrow path"));
|
|
561
475
|
}
|
|
562
476
|
if (details?.parseErrors?.length) {
|
|
563
|
-
|
|
564
|
-
const label =
|
|
565
|
-
total > PARSE_ERRORS_LIMIT
|
|
566
|
-
? `${PARSE_ERRORS_LIMIT} / ${total} parse issues`
|
|
567
|
-
: `${total} parse issue${total !== 1 ? "s" : ""}`;
|
|
568
|
-
extraLines.push(uiTheme.fg("warning", label));
|
|
477
|
+
extraLines.push(uiTheme.fg("warning", formatParseErrorsCountLabel(details.parseErrors)));
|
|
569
478
|
}
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
const { expanded } = options;
|
|
574
|
-
const key = new Hasher().bool(expanded).u32(width).digest();
|
|
575
|
-
if (cached?.key === key) return cached.lines;
|
|
479
|
+
return createCachedComponent(
|
|
480
|
+
() => options.expanded,
|
|
481
|
+
width => {
|
|
576
482
|
const changeLines = renderTreeList(
|
|
577
483
|
{
|
|
578
484
|
items: changeGroups,
|
|
579
|
-
expanded,
|
|
485
|
+
expanded: options.expanded,
|
|
580
486
|
maxCollapsed: changeGroups.length,
|
|
581
487
|
maxCollapsedLines: COLLAPSED_CHANGE_LIMIT,
|
|
582
488
|
itemType: "change",
|
|
@@ -591,14 +497,9 @@ export const astEditToolRenderer = {
|
|
|
591
497
|
},
|
|
592
498
|
uiTheme,
|
|
593
499
|
);
|
|
594
|
-
|
|
595
|
-
cached = { key, lines: rendered };
|
|
596
|
-
return rendered;
|
|
500
|
+
return [header, ...changeLines, ...extraLines].map(l => truncateToWidth(l, width, Ellipsis.Omit));
|
|
597
501
|
},
|
|
598
|
-
|
|
599
|
-
cached = undefined;
|
|
600
|
-
},
|
|
601
|
-
};
|
|
502
|
+
);
|
|
602
503
|
},
|
|
603
504
|
mergeCallAndResult: true,
|
|
604
505
|
};
|