@oh-my-pi/pi-coding-agent 11.2.2 → 11.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +100 -0
- package/examples/extensions/plan-mode.ts +1 -1
- package/examples/hooks/qna.ts +1 -1
- package/examples/hooks/status-line.ts +1 -1
- package/examples/sdk/11-sessions.ts +1 -1
- package/package.json +10 -10
- package/src/cli/args.ts +9 -6
- package/src/cli/update-cli.ts +2 -2
- package/src/commands/index/index.ts +2 -5
- package/src/commit/agentic/agent.ts +1 -1
- package/src/commit/changelog/index.ts +2 -2
- package/src/config/keybindings.ts +16 -1
- package/src/config/model-registry.ts +25 -20
- package/src/config/model-resolver.ts +8 -8
- package/src/config/resolve-config-value.ts +92 -0
- package/src/config/settings-schema.ts +9 -0
- package/src/config.ts +14 -1
- package/src/export/html/template.css +7 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +33 -16
- package/src/extensibility/custom-commands/bundled/review/index.ts +1 -1
- package/src/extensibility/extensions/index.ts +18 -0
- package/src/extensibility/extensions/loader.ts +15 -0
- package/src/extensibility/extensions/runner.ts +78 -1
- package/src/extensibility/extensions/types.ts +131 -5
- package/src/extensibility/extensions/wrapper.ts +1 -1
- package/src/extensibility/plugins/git-url.ts +270 -0
- package/src/extensibility/plugins/index.ts +2 -0
- package/src/extensibility/slash-commands.ts +45 -0
- package/src/index.ts +7 -0
- package/src/lsp/render.ts +50 -43
- package/src/lsp/utils.ts +2 -2
- package/src/main.ts +11 -10
- package/src/mcp/transports/stdio.ts +9 -35
- package/src/modes/components/custom-message.ts +0 -8
- package/src/modes/components/diff.ts +1 -7
- package/src/modes/components/footer.ts +4 -4
- package/src/modes/components/model-selector.ts +4 -0
- package/src/modes/components/todo-display.ts +13 -3
- package/src/modes/components/tool-execution.ts +30 -16
- package/src/modes/components/tree-selector.ts +50 -19
- package/src/modes/controllers/event-controller.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +34 -2
- package/src/modes/controllers/input-controller.ts +47 -33
- package/src/modes/controllers/selector-controller.ts +10 -15
- package/src/modes/interactive-mode.ts +50 -38
- package/src/modes/print-mode.ts +6 -0
- package/src/modes/rpc/rpc-client.ts +25 -40
- package/src/modes/rpc/rpc-mode.ts +36 -31
- package/src/modes/rpc/rpc-types.ts +2 -2
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +3 -1
- package/src/patch/applicator.ts +2 -3
- package/src/patch/fuzzy.ts +1 -1
- package/src/patch/shared.ts +74 -61
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/tools/task.md +6 -0
- package/src/sdk.ts +15 -11
- package/src/session/agent-session.ts +72 -23
- package/src/session/auth-storage.ts +2 -1
- package/src/session/blob-store.ts +105 -0
- package/src/session/session-manager.ts +112 -75
- package/src/session/session-storage.ts +1 -1
- package/src/task/executor.ts +19 -9
- package/src/task/render.ts +80 -58
- package/src/tools/ask.ts +28 -5
- package/src/tools/bash.ts +47 -39
- package/src/tools/browser.ts +248 -26
- package/src/tools/calculator.ts +42 -23
- package/src/tools/fetch.ts +33 -16
- package/src/tools/find.ts +57 -22
- package/src/tools/grep.ts +54 -25
- package/src/tools/index.ts +5 -5
- package/src/tools/notebook.ts +19 -6
- package/src/tools/path-utils.ts +26 -1
- package/src/tools/python.ts +20 -14
- package/src/tools/read.ts +22 -9
- package/src/tools/render-utils.ts +5 -45
- package/src/tools/ssh.ts +59 -53
- package/src/tools/submit-result.ts +2 -2
- package/src/tools/todo-write.ts +32 -14
- package/src/tools/truncate.ts +1 -1
- package/src/tools/write.ts +39 -24
- package/src/tui/output-block.ts +61 -3
- package/src/tui/tree-list.ts +4 -4
- package/src/tui/utils.ts +71 -1
- package/src/utils/frontmatter.ts +1 -1
- package/src/utils/mime.ts +1 -1
- package/src/utils/title-generator.ts +1 -1
- package/src/utils/tools-manager.ts +18 -2
- package/src/web/scrapers/osv.ts +4 -1
- package/src/web/scrapers/utils.ts +8 -6
- package/src/web/scrapers/youtube.ts +1 -1
- package/src/web/search/index.ts +1 -1
- package/src/web/search/render.ts +96 -90
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,106 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [11.3.0] - 2026-02-06
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added resumption hint printed to stderr on session exit showing command to resume the session (e.g., `Resume this session with claude --resume <session-id>`)
|
|
10
|
+
- New `BlobStore` class for content-addressed storage of large binary data (images) externalized from session files
|
|
11
|
+
- New `getBlobsDir()` function to get path to blob store directory
|
|
12
|
+
- Support for externalizing large images to blob store during session persistence, reducing JSONL file size
|
|
13
|
+
- New blob reference format (`blob:sha256:<hash>`) for tracking externalized image data in sessions
|
|
14
|
+
- Exported `ModeChangeEntry` type for tracking agent mode transitions
|
|
15
|
+
- Support for restoring plan mode state when resuming sessions
|
|
16
|
+
- New `appendModeChange()` method in SessionManager to record mode transitions
|
|
17
|
+
- New `mode` and `modeData` fields in SessionContext to track active agent mode
|
|
18
|
+
- Support for `PI_PACKAGE_DIR` environment variable to override package directory (useful for Nix/Guix store paths)
|
|
19
|
+
- New keybindings for session management: `toggleSessionNamedFilter` (Ctrl+N), `newSession`, `tree`, `fork`, and `resume` actions
|
|
20
|
+
- Support for shell command execution in configuration values (API keys, headers) using `!` prefix, with result caching
|
|
21
|
+
- New `clearOnShrink` display setting to control whether empty rows are cleared when content shrinks
|
|
22
|
+
- New `SlashCommandInfo`, `SlashCommandLocation`, and `SlashCommandSource` types for extension slash command discovery
|
|
23
|
+
- New `getCommands()` method in ExtensionAPI to retrieve available slash commands
|
|
24
|
+
- New `switchSession()` action in ExtensionCommandContext to switch between sessions
|
|
25
|
+
- New `SwitchSessionHandler` type for extension session switching handlers
|
|
26
|
+
- New `getSystemPrompt()` method in ExtensionUIContext to access current system prompt
|
|
27
|
+
- New `getToolsExpanded()` and `setToolsExpanded()` methods in ExtensionUIContext for tool output expansion control
|
|
28
|
+
- New `WriteToolCallEvent` type for write tool call events
|
|
29
|
+
- New `isToolCallEventType()` type guard for tool call events
|
|
30
|
+
- Support for image content in RPC `steer` and `followUp` commands
|
|
31
|
+
- New `GitSource` type and `parseGitUrl()` function for parsing git URLs in plugin system
|
|
32
|
+
- Tool input types exported: `BashToolInput`, `FindToolInput`, `GrepToolInput`, `ReadToolInput`, `WriteToolInput`
|
|
33
|
+
- Support for `@` prefix normalization in file paths (strips leading `@` character)
|
|
34
|
+
- New `parentSessionPath` field in SessionInfo to track forked session origins
|
|
35
|
+
- Skill file relative path resolution against skill directory in system prompt
|
|
36
|
+
- Support for Termux/Android package installation guidance for missing tools
|
|
37
|
+
- Support for puppeteer query handlers (aria/, text/, xpath/, pierce/) in selector parameters across all browser actions
|
|
38
|
+
- Automatic normalization of legacy p- prefixed selectors (p-aria/, p-text/, p-xpath/, p-pierce/) to modern puppeteer query handler syntax
|
|
39
|
+
- Improved click action with intelligent element selection that prioritizes visible, actionable candidates and retries until timeout
|
|
40
|
+
- Enhanced actionability checking for click operations, validating visibility, pointer events, opacity, viewport intersection, and element occlusion
|
|
41
|
+
|
|
42
|
+
### Changed
|
|
43
|
+
|
|
44
|
+
- Modified `--resume` flag to accept optional session ID or path (e.g., `--resume abc123` or `--resume /path/to/session.jsonl`), with session picker shown when no value provided
|
|
45
|
+
- Consolidated `--session` flag as an alias for `--resume` with value for improved CLI consistency
|
|
46
|
+
- Removed read tool grouping reset logic that was breaking grouping when text or thinking blocks appeared between tool calls
|
|
47
|
+
- Image persistence now externalizes images ≥1KB to content-addressed blob store instead of compressing inline
|
|
48
|
+
- Session loading now automatically resolves blob references back to base64 image data
|
|
49
|
+
- Session forking now resolves blob references in copied entries to ensure data integrity
|
|
50
|
+
- Screenshot tool now automatically compresses images for API content using the same resizing logic as pasted images, reducing payload size while maintaining quality
|
|
51
|
+
- Improved text truncation across tool renderers to respect terminal width constraints and prevent output overflow
|
|
52
|
+
- Enhanced render caching to include width parameter for accurate cache invalidation when terminal width changes
|
|
53
|
+
- HTML export filter now treats `mode_change` entries as settings entries alongside model changes and thinking level changes
|
|
54
|
+
- Replaced ellipsis string (`...`) with Unicode ellipsis character (`…`) throughout UI text and truncation logic for improved typography
|
|
55
|
+
- Improved render performance by introducing caching for tool output blocks and search results to avoid redundant text width and padding computations
|
|
56
|
+
- Enhanced read tool grouping to reset when non-tool content (text/thinking blocks) appears between read calls, preventing unintended coalescing
|
|
57
|
+
- Improved string preview formatting in scalar values to show line counts and truncation indicators for multi-line strings
|
|
58
|
+
- Refactored tool execution component to use shared mutable render state for spinner frames and expansion state, reducing closure overhead
|
|
59
|
+
- Enhanced error handling in tool renderers with logging for renderer failures instead of silent fallbacks
|
|
60
|
+
- Made shell command execution in configuration values asynchronous to prevent blocking the TUI
|
|
61
|
+
- Improved `@` prefix normalization to only strip leading `@` for well-known path syntaxes (absolute paths, home directory, internal URL shorthands) to avoid mangling literal paths
|
|
62
|
+
- Enhanced git URL parsing to strip credentials from repository URLs and validate URL-encoded hash fragments
|
|
63
|
+
- Improved null data handling in task submission to preserve agent output when `submit_result` is called with null/undefined data, enabling fallback text extraction instead of discarding output
|
|
64
|
+
- Updated default model IDs across providers: Claude Sonnet 4.5 → Claude Opus 4.6, Gemini 2.5 Pro → Gemini 3 Pro variants, and others
|
|
65
|
+
- Made model definition fields optional with sensible defaults for local models (Ollama, LM Studio, etc.)
|
|
66
|
+
- Modified custom tool execute signature to reorder parameters: `(toolCallId, params, signal, onUpdate, ctx)` instead of `(toolCallId, params, onUpdate, ctx, signal)`
|
|
67
|
+
- Changed `--version` and `--list-models` flags to exit with `process.exit(0)` instead of returning
|
|
68
|
+
- Improved `--export` flag to exit with `process.exit(0)` on success
|
|
69
|
+
- Enhanced tree selector to preserve last selected ID across filter changes
|
|
70
|
+
- Modified tree navigation to use real leaf ID instead of skipping metadata entries
|
|
71
|
+
- Improved footer path truncation logic to prevent invalid truncation at boundary
|
|
72
|
+
- Enhanced model selector to display selected model name when no matches found
|
|
73
|
+
- Improved RPC client `steer()` and `followUp()` methods to accept optional image content
|
|
74
|
+
- Updated extension loader to check for explicit extension entries in root directory before discovering subdirectories
|
|
75
|
+
- Removed line limiting in custom message component when collapsed
|
|
76
|
+
- Improved API key resolution to support shell command execution via `resolveConfigValue()`
|
|
77
|
+
- Enhanced session branching to preserve parent session path reference
|
|
78
|
+
- Updated selector parameter descriptions to document support for CSS selectors and puppeteer query handlers
|
|
79
|
+
- Modified viewport handling in headless mode to respect custom viewport parameters while disabling viewport in headed mode for better window management
|
|
80
|
+
- Improved click action to use specialized text query handler logic with retry mechanism for better reliability with dynamic content
|
|
81
|
+
|
|
82
|
+
### Fixed
|
|
83
|
+
|
|
84
|
+
- Fixed background color stability in output blocks when inner content contains SGR reset sequences, preventing background color from being cleared mid-line
|
|
85
|
+
- Fixed spurious ellipsis appended to output lines that were already padded to terminal width by trimming trailing spaces before truncation check
|
|
86
|
+
- Fixed config file parsing to properly handle missing files instead of treating them as errors
|
|
87
|
+
- Fixed truncation indicator in truncate tool to use ellipsis character (…) instead of verbose '[truncated]' suffix
|
|
88
|
+
- Fixed concurrent shell command execution by de-duplicating in-flight requests for the same command
|
|
89
|
+
- Fixed git URL parsing to properly handle URL-encoded characters in hash fragments and reject invalid encodings
|
|
90
|
+
- Fixed task executor to properly handle agents calling `submit_result` with null data by treating it as missing and attempting to extract output from conversation text rather than silently failing
|
|
91
|
+
- Fixed HTML export template to safely handle invalid argument types in tool rendering
|
|
92
|
+
- Fixed path shortening in HTML export to handle non-string paths
|
|
93
|
+
- Fixed custom message rendering to properly display full content without artificial line limits
|
|
94
|
+
- Fixed tree navigation to only restore editor text when editor is empty
|
|
95
|
+
- Fixed session creation to properly track parent session when forking
|
|
96
|
+
- Fixed thinking level initialization to only append change entry for new sessions without existing thinking entries
|
|
97
|
+
- Fixed tool expansion state management to properly propagate through UI context
|
|
98
|
+
- Fixed click action to properly handle text/ query handlers with timeout and retry logic instead of failing immediately
|
|
99
|
+
- Fixed viewport application to only apply when in headless mode or when explicitly requested, preventing conflicts in headed browser mode
|
|
100
|
+
|
|
101
|
+
### Security
|
|
102
|
+
|
|
103
|
+
- Added support for shell command execution in configuration values with caching to enable secure credential resolution patterns
|
|
104
|
+
|
|
5
105
|
## [11.2.1] - 2026-02-05
|
|
6
106
|
|
|
7
107
|
### Fixed
|
package/examples/hooks/qna.ts
CHANGED
|
@@ -71,7 +71,7 @@ export default function (pi: HookAPI) {
|
|
|
71
71
|
|
|
72
72
|
// Run extraction with loader UI
|
|
73
73
|
const result = await ctx.ui.custom<string | null>((tui, theme, done) => {
|
|
74
|
-
const loader = new BorderedLoader(tui, theme, `Extracting questions using ${ctx.model!.id}
|
|
74
|
+
const loader = new BorderedLoader(tui, theme, `Extracting questions using ${ctx.model!.id}…`);
|
|
75
75
|
loader.onAbort = () => done(null);
|
|
76
76
|
|
|
77
77
|
// Do the work
|
|
@@ -18,7 +18,7 @@ export default function (pi: HookAPI) {
|
|
|
18
18
|
turnCount++;
|
|
19
19
|
const theme = ctx.ui.theme;
|
|
20
20
|
const spinner = theme.fg("accent", "●");
|
|
21
|
-
const text = theme.fg("dim", ` Turn ${turnCount}
|
|
21
|
+
const text = theme.fg("dim", ` Turn ${turnCount}…`);
|
|
22
22
|
ctx.ui.setStatus("status-demo", spinner + text);
|
|
23
23
|
});
|
|
24
24
|
|
|
@@ -28,7 +28,7 @@ console.log("Continued session:", continued.sessionFile);
|
|
|
28
28
|
const sessions = SessionManager.list(process.cwd());
|
|
29
29
|
console.log(`\nFound ${sessions.length} sessions:`);
|
|
30
30
|
for (const info of sessions.slice(0, 3)) {
|
|
31
|
-
console.log(` ${info.id.slice(0, 8)}
|
|
31
|
+
console.log(` ${info.id.slice(0, 8)}… - "${info.firstMessage.slice(0, 30)}…"`);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
if (sessions.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.3.0",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -87,15 +87,15 @@
|
|
|
87
87
|
"test": "bun test"
|
|
88
88
|
},
|
|
89
89
|
"dependencies": {
|
|
90
|
-
"@oclif/core": "^4.5.6",
|
|
91
|
-
"@oclif/plugin-autocomplete": "^3.2.23",
|
|
92
90
|
"@mozilla/readability": "0.6.0",
|
|
93
|
-
"@
|
|
94
|
-
"@
|
|
95
|
-
"@oh-my-pi/
|
|
96
|
-
"@oh-my-pi/pi-
|
|
97
|
-
"@oh-my-pi/pi-
|
|
98
|
-
"@oh-my-pi/pi-
|
|
91
|
+
"@oclif/core": "^4.8.0",
|
|
92
|
+
"@oclif/plugin-autocomplete": "^3.2.40",
|
|
93
|
+
"@oh-my-pi/omp-stats": "11.3.0",
|
|
94
|
+
"@oh-my-pi/pi-agent-core": "11.3.0",
|
|
95
|
+
"@oh-my-pi/pi-ai": "11.3.0",
|
|
96
|
+
"@oh-my-pi/pi-natives": "11.3.0",
|
|
97
|
+
"@oh-my-pi/pi-tui": "11.3.0",
|
|
98
|
+
"@oh-my-pi/pi-utils": "11.3.0",
|
|
99
99
|
"@sinclair/typebox": "^0.34.48",
|
|
100
100
|
"ajv": "^8.17.1",
|
|
101
101
|
"chalk": "^5.6.2",
|
|
@@ -107,7 +107,7 @@
|
|
|
107
107
|
"jsdom": "28.0.0",
|
|
108
108
|
"marked": "^17.0.1",
|
|
109
109
|
"node-html-parser": "^7.0.2",
|
|
110
|
-
"puppeteer": "^24.
|
|
110
|
+
"puppeteer": "^24.37.1",
|
|
111
111
|
"smol-toml": "^1.6.0",
|
|
112
112
|
"zod": "^4.3.6"
|
|
113
113
|
},
|
package/src/cli/args.ts
CHANGED
|
@@ -21,12 +21,11 @@ export interface Args {
|
|
|
21
21
|
appendSystemPrompt?: string;
|
|
22
22
|
thinking?: ThinkingLevel;
|
|
23
23
|
continue?: boolean;
|
|
24
|
-
resume?:
|
|
24
|
+
resume?: string | true;
|
|
25
25
|
help?: boolean;
|
|
26
26
|
version?: boolean;
|
|
27
27
|
mode?: Mode;
|
|
28
28
|
noSession?: boolean;
|
|
29
|
-
session?: string;
|
|
30
29
|
sessionDir?: string;
|
|
31
30
|
models?: string[];
|
|
32
31
|
tools?: string[];
|
|
@@ -76,8 +75,13 @@ export function parseArgs(args: string[], extensionFlags?: Map<string, { type: "
|
|
|
76
75
|
}
|
|
77
76
|
} else if (arg === "--continue" || arg === "-c") {
|
|
78
77
|
result.continue = true;
|
|
79
|
-
} else if (arg === "--resume" || arg === "-r") {
|
|
80
|
-
|
|
78
|
+
} else if (arg === "--resume" || arg === "-r" || arg === "--session") {
|
|
79
|
+
const next = args[i + 1];
|
|
80
|
+
if (next && !next.startsWith("-")) {
|
|
81
|
+
result.resume = args[++i];
|
|
82
|
+
} else {
|
|
83
|
+
result.resume = true;
|
|
84
|
+
}
|
|
81
85
|
} else if (arg === "--provider" && i + 1 < args.length) {
|
|
82
86
|
result.provider = args[++i];
|
|
83
87
|
} else if (arg === "--model" && i + 1 < args.length) {
|
|
@@ -96,8 +100,6 @@ export function parseArgs(args: string[], extensionFlags?: Map<string, { type: "
|
|
|
96
100
|
result.appendSystemPrompt = args[++i];
|
|
97
101
|
} else if (arg === "--no-session") {
|
|
98
102
|
result.noSession = true;
|
|
99
|
-
} else if (arg === "--session" && i + 1 < args.length) {
|
|
100
|
-
result.session = args[++i];
|
|
101
103
|
} else if (arg === "--session-dir" && i + 1 < args.length) {
|
|
102
104
|
result.sessionDir = args[++i];
|
|
103
105
|
} else if (arg === "--models" && i + 1 < args.length) {
|
|
@@ -214,6 +216,7 @@ export function getExtraHelpText(): string {
|
|
|
214
216
|
|
|
215
217
|
${chalk.dim("# Configuration")}
|
|
216
218
|
PI_CODING_AGENT_DIR - Session storage directory (default: ~/${CONFIG_DIR_NAME}/agent)
|
|
219
|
+
PI_PACKAGE_DIR - Override package directory (for Nix/Guix store paths)
|
|
217
220
|
PI_SMOL_MODEL - Override smol/fast model (see --smol)
|
|
218
221
|
PI_SLOW_MODEL - Override slow/reasoning model (see --slow)
|
|
219
222
|
PI_PLAN_MODEL - Override planning model (see --plan)
|
package/src/cli/update-cli.ts
CHANGED
|
@@ -190,7 +190,7 @@ async function updateViaBinary(release: ReleaseInfo): Promise<void> {
|
|
|
190
190
|
const nativePath = path.join(execDir, nativeAddonName);
|
|
191
191
|
const nativeTempPath = `${nativePath}.new`;
|
|
192
192
|
|
|
193
|
-
console.log(chalk.dim(`Downloading ${binaryName}
|
|
193
|
+
console.log(chalk.dim(`Downloading ${binaryName}…`));
|
|
194
194
|
|
|
195
195
|
// Download to temp file
|
|
196
196
|
const response = await fetch(asset.url, { redirect: "follow" });
|
|
@@ -202,7 +202,7 @@ async function updateViaBinary(release: ReleaseInfo): Promise<void> {
|
|
|
202
202
|
await pipeline(response.body, fileStream);
|
|
203
203
|
|
|
204
204
|
// Download native addon
|
|
205
|
-
console.log(chalk.dim(`Downloading ${nativeAddonName}
|
|
205
|
+
console.log(chalk.dim(`Downloading ${nativeAddonName}…`));
|
|
206
206
|
|
|
207
207
|
const nativeResponse = await fetch(nativeAsset.url, { redirect: "follow" });
|
|
208
208
|
if (!nativeResponse.ok || !nativeResponse.body) {
|
|
@@ -58,12 +58,9 @@ export default class Index extends Command {
|
|
|
58
58
|
char: "c",
|
|
59
59
|
description: "Continue previous session",
|
|
60
60
|
}),
|
|
61
|
-
resume: Flags.
|
|
61
|
+
resume: Flags.string({
|
|
62
62
|
char: "r",
|
|
63
|
-
description: "
|
|
64
|
-
}),
|
|
65
|
-
session: Flags.string({
|
|
66
|
-
description: "Use specific session file",
|
|
63
|
+
description: "Resume a session (by ID prefix, path, or picker if omitted)",
|
|
67
64
|
}),
|
|
68
65
|
"session-dir": Flags.string({
|
|
69
66
|
description: "Directory for session storage and lookup",
|
|
@@ -54,7 +54,7 @@ export async function runChangelogFlow({
|
|
|
54
54
|
|
|
55
55
|
const updated: string[] = [];
|
|
56
56
|
for (const boundary of boundaries) {
|
|
57
|
-
onProgress?.(`Generating entries for ${boundary.changelogPath}
|
|
57
|
+
onProgress?.(`Generating entries for ${boundary.changelogPath}…`);
|
|
58
58
|
const diff = await git.getDiffForFiles(boundary.files, true);
|
|
59
59
|
if (!diff.trim()) continue;
|
|
60
60
|
const stat = await git.getStatForFiles(boundary.files, true);
|
|
@@ -108,7 +108,7 @@ export async function applyChangelogProposals({
|
|
|
108
108
|
(!proposal.deletions || Object.keys(proposal.deletions).length === 0)
|
|
109
109
|
)
|
|
110
110
|
continue;
|
|
111
|
-
onProgress?.(`Applying entries for ${proposal.path}
|
|
111
|
+
onProgress?.(`Applying entries for ${proposal.path}…`);
|
|
112
112
|
const exists = fs.existsSync(proposal.path);
|
|
113
113
|
if (!exists) {
|
|
114
114
|
logger.warn("commit changelog path missing", { path: proposal.path });
|
|
@@ -26,11 +26,16 @@ export type AppAction =
|
|
|
26
26
|
| "togglePlanMode"
|
|
27
27
|
| "expandTools"
|
|
28
28
|
| "toggleThinking"
|
|
29
|
+
| "toggleSessionNamedFilter"
|
|
29
30
|
| "externalEditor"
|
|
30
31
|
| "historySearch"
|
|
31
32
|
| "followUp"
|
|
32
33
|
| "dequeue"
|
|
33
|
-
| "pasteImage"
|
|
34
|
+
| "pasteImage"
|
|
35
|
+
| "newSession"
|
|
36
|
+
| "tree"
|
|
37
|
+
| "fork"
|
|
38
|
+
| "resume";
|
|
34
39
|
|
|
35
40
|
/**
|
|
36
41
|
* All configurable actions.
|
|
@@ -60,10 +65,15 @@ export const DEFAULT_APP_KEYBINDINGS: Record<AppAction, KeyId | KeyId[]> = {
|
|
|
60
65
|
historySearch: "ctrl+r",
|
|
61
66
|
expandTools: "ctrl+o",
|
|
62
67
|
toggleThinking: "ctrl+t",
|
|
68
|
+
toggleSessionNamedFilter: "ctrl+n",
|
|
63
69
|
externalEditor: "ctrl+g",
|
|
64
70
|
followUp: "ctrl+enter",
|
|
65
71
|
dequeue: "alt+up",
|
|
66
72
|
pasteImage: "ctrl+v",
|
|
73
|
+
newSession: [],
|
|
74
|
+
tree: [],
|
|
75
|
+
fork: [],
|
|
76
|
+
resume: [],
|
|
67
77
|
};
|
|
68
78
|
|
|
69
79
|
/**
|
|
@@ -88,10 +98,15 @@ const APP_ACTIONS: AppAction[] = [
|
|
|
88
98
|
"historySearch",
|
|
89
99
|
"expandTools",
|
|
90
100
|
"toggleThinking",
|
|
101
|
+
"toggleSessionNamedFilter",
|
|
91
102
|
"externalEditor",
|
|
92
103
|
"followUp",
|
|
93
104
|
"dequeue",
|
|
94
105
|
"pasteImage",
|
|
106
|
+
"newSession",
|
|
107
|
+
"tree",
|
|
108
|
+
"fork",
|
|
109
|
+
"resume",
|
|
95
110
|
];
|
|
96
111
|
|
|
97
112
|
function isAppAction(action: string): action is AppAction {
|
|
@@ -54,9 +54,10 @@ const OpenAICompatSchema = Type.Object({
|
|
|
54
54
|
});
|
|
55
55
|
|
|
56
56
|
// Schema for custom model definition
|
|
57
|
+
// Most fields are optional with sensible defaults for local models (Ollama, LM Studio, etc.)
|
|
57
58
|
const ModelDefinitionSchema = Type.Object({
|
|
58
59
|
id: Type.String({ minLength: 1 }),
|
|
59
|
-
name: Type.String({ minLength: 1 }),
|
|
60
|
+
name: Type.Optional(Type.String({ minLength: 1 })),
|
|
60
61
|
api: Type.Optional(
|
|
61
62
|
Type.Union([
|
|
62
63
|
Type.Literal("openai-completions"),
|
|
@@ -68,16 +69,18 @@ const ModelDefinitionSchema = Type.Object({
|
|
|
68
69
|
Type.Literal("google-vertex"),
|
|
69
70
|
]),
|
|
70
71
|
),
|
|
71
|
-
reasoning: Type.Boolean(),
|
|
72
|
-
input: Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")])),
|
|
73
|
-
cost: Type.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
72
|
+
reasoning: Type.Optional(Type.Boolean()),
|
|
73
|
+
input: Type.Optional(Type.Array(Type.Union([Type.Literal("text"), Type.Literal("image")]))),
|
|
74
|
+
cost: Type.Optional(
|
|
75
|
+
Type.Object({
|
|
76
|
+
input: Type.Number(),
|
|
77
|
+
output: Type.Number(),
|
|
78
|
+
cacheRead: Type.Number(),
|
|
79
|
+
cacheWrite: Type.Number(),
|
|
80
|
+
}),
|
|
81
|
+
),
|
|
82
|
+
contextWindow: Type.Optional(Type.Number()),
|
|
83
|
+
maxTokens: Type.Optional(Type.Number()),
|
|
81
84
|
headers: Type.Optional(Type.Record(Type.String(), Type.String())),
|
|
82
85
|
compat: Type.Optional(OpenAICompatSchema),
|
|
83
86
|
});
|
|
@@ -141,10 +144,10 @@ export const ModelsConfigFile = new ConfigFile<ModelsConfig>("models", ModelsCon
|
|
|
141
144
|
}
|
|
142
145
|
|
|
143
146
|
if (!modelDef.id) throw new Error(`Provider ${providerName}: model missing "id"`);
|
|
144
|
-
|
|
145
|
-
if (modelDef.contextWindow <= 0)
|
|
147
|
+
// Validate contextWindow/maxTokens only if provided (they have defaults)
|
|
148
|
+
if (modelDef.contextWindow !== undefined && modelDef.contextWindow <= 0)
|
|
146
149
|
throw new Error(`Provider ${providerName}, model ${modelDef.id}: invalid contextWindow`);
|
|
147
|
-
if (modelDef.maxTokens <= 0)
|
|
150
|
+
if (modelDef.maxTokens !== undefined && modelDef.maxTokens <= 0)
|
|
148
151
|
throw new Error(`Provider ${providerName}, model ${modelDef.id}: invalid maxTokens`);
|
|
149
152
|
}
|
|
150
153
|
}
|
|
@@ -377,17 +380,19 @@ export class ModelRegistry {
|
|
|
377
380
|
}
|
|
378
381
|
|
|
379
382
|
// baseUrl is validated to exist for providers with models
|
|
383
|
+
// Apply defaults for optional fields
|
|
384
|
+
const defaultCost = { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 };
|
|
380
385
|
models.push({
|
|
381
386
|
id: modelDef.id,
|
|
382
|
-
name: modelDef.name,
|
|
387
|
+
name: modelDef.name ?? modelDef.id,
|
|
383
388
|
api: api as Api,
|
|
384
389
|
provider: providerName,
|
|
385
390
|
baseUrl: providerConfig.baseUrl!,
|
|
386
|
-
reasoning: modelDef.reasoning,
|
|
387
|
-
input: modelDef.input as ("text" | "image")[],
|
|
388
|
-
cost: modelDef.cost,
|
|
389
|
-
contextWindow: modelDef.contextWindow,
|
|
390
|
-
maxTokens: modelDef.maxTokens,
|
|
391
|
+
reasoning: modelDef.reasoning ?? false,
|
|
392
|
+
input: (modelDef.input ?? ["text"]) as ("text" | "image")[],
|
|
393
|
+
cost: modelDef.cost ?? defaultCost,
|
|
394
|
+
contextWindow: modelDef.contextWindow ?? 128000,
|
|
395
|
+
maxTokens: modelDef.maxTokens ?? 16384,
|
|
391
396
|
headers,
|
|
392
397
|
compat: modelDef.compat,
|
|
393
398
|
} as Model<Api>);
|
|
@@ -11,25 +11,25 @@ import type { Settings } from "./settings";
|
|
|
11
11
|
|
|
12
12
|
/** Default model IDs for each known provider */
|
|
13
13
|
export const defaultModelPerProvider: Record<KnownProvider, string> = {
|
|
14
|
-
"amazon-bedrock": "us.anthropic.claude-
|
|
15
|
-
anthropic: "claude-
|
|
14
|
+
"amazon-bedrock": "us.anthropic.claude-opus-4-6-v1",
|
|
15
|
+
anthropic: "claude-opus-4-6",
|
|
16
16
|
openai: "gpt-5.1-codex",
|
|
17
|
-
"openai-codex": "codex
|
|
17
|
+
"openai-codex": "gpt-5.3-codex",
|
|
18
18
|
google: "gemini-2.5-pro",
|
|
19
19
|
"google-gemini-cli": "gemini-2.5-pro",
|
|
20
20
|
"google-antigravity": "gemini-3-pro-high",
|
|
21
|
-
"google-vertex": "gemini-
|
|
21
|
+
"google-vertex": "gemini-3-pro-preview",
|
|
22
22
|
"github-copilot": "gpt-4o",
|
|
23
|
-
cursor: "claude-
|
|
23
|
+
cursor: "claude-opus-4-6",
|
|
24
24
|
openrouter: "openai/gpt-5.1-codex",
|
|
25
|
-
"vercel-ai-gateway": "claude-
|
|
25
|
+
"vercel-ai-gateway": "anthropic/claude-opus-4-6",
|
|
26
26
|
xai: "grok-4-fast-non-reasoning",
|
|
27
27
|
groq: "openai/gpt-oss-120b",
|
|
28
28
|
cerebras: "zai-glm-4.6",
|
|
29
29
|
zai: "glm-4.6",
|
|
30
30
|
mistral: "devstral-medium-latest",
|
|
31
|
-
minimax: "MiniMax-M2",
|
|
32
|
-
opencode: "claude-opus-4-
|
|
31
|
+
minimax: "MiniMax-M2.1",
|
|
32
|
+
opencode: "claude-opus-4-6",
|
|
33
33
|
"kimi-code": "kimi-k2.5",
|
|
34
34
|
};
|
|
35
35
|
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve configuration values that may be shell commands, environment variables, or literals.
|
|
3
|
+
*
|
|
4
|
+
* Note: command execution is async to avoid blocking the TUI.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { executeShell } from "@oh-my-pi/pi-natives";
|
|
8
|
+
|
|
9
|
+
/** Cache for successful shell command results (persists for process lifetime). */
|
|
10
|
+
const commandResultCache = new Map<string, string>();
|
|
11
|
+
|
|
12
|
+
/** De-duplicates concurrent executions for the same command. */
|
|
13
|
+
const commandInFlight = new Map<string, Promise<string | undefined>>();
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a config value (API key, header value, etc.) to an actual value.
|
|
17
|
+
* - If starts with "!", executes the rest as a shell command and uses stdout (cached)
|
|
18
|
+
* - Otherwise checks environment variable first, then treats as literal (not cached)
|
|
19
|
+
*/
|
|
20
|
+
export async function resolveConfigValue(config: string): Promise<string | undefined> {
|
|
21
|
+
if (config.startsWith("!")) {
|
|
22
|
+
return await executeCommand(config);
|
|
23
|
+
}
|
|
24
|
+
const envValue = process.env[config];
|
|
25
|
+
return envValue || config;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function executeCommand(commandConfig: string): Promise<string | undefined> {
|
|
29
|
+
const cached = commandResultCache.get(commandConfig);
|
|
30
|
+
if (cached !== undefined) {
|
|
31
|
+
return cached;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const existing = commandInFlight.get(commandConfig);
|
|
35
|
+
if (existing) {
|
|
36
|
+
return await existing;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const command = commandConfig.slice(1);
|
|
40
|
+
const promise = runShellCommand(command, 10_000)
|
|
41
|
+
.then(result => {
|
|
42
|
+
if (result !== undefined) {
|
|
43
|
+
commandResultCache.set(commandConfig, result);
|
|
44
|
+
}
|
|
45
|
+
return result;
|
|
46
|
+
})
|
|
47
|
+
.finally(() => {
|
|
48
|
+
commandInFlight.delete(commandConfig);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
commandInFlight.set(commandConfig, promise);
|
|
52
|
+
return await promise;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function runShellCommand(command: string, timeoutMs: number): Promise<string | undefined> {
|
|
56
|
+
try {
|
|
57
|
+
let output = "";
|
|
58
|
+
const result = await executeShell({ command, timeoutMs }, chunk => {
|
|
59
|
+
output += chunk;
|
|
60
|
+
});
|
|
61
|
+
if (result.timedOut || result.exitCode !== 0) {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const trimmed = output.trim();
|
|
65
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
66
|
+
} catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Resolve all header values using the same resolution logic as API keys.
|
|
73
|
+
*/
|
|
74
|
+
export async function resolveHeaders(
|
|
75
|
+
headers: Record<string, string> | undefined,
|
|
76
|
+
): Promise<Record<string, string> | undefined> {
|
|
77
|
+
if (!headers) return undefined;
|
|
78
|
+
const resolved: Record<string, string> = {};
|
|
79
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
80
|
+
const resolvedValue = await resolveConfigValue(value);
|
|
81
|
+
if (resolvedValue) {
|
|
82
|
+
resolved[key] = resolvedValue;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return Object.keys(resolved).length > 0 ? resolved : undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Clear the config value command cache. Exported for testing. */
|
|
89
|
+
export function clearConfigValueCache(): void {
|
|
90
|
+
commandResultCache.clear();
|
|
91
|
+
commandInFlight.clear();
|
|
92
|
+
}
|
|
@@ -242,6 +242,15 @@ export const SETTINGS_SCHEMA = {
|
|
|
242
242
|
default: true, // will be computed based on platform if undefined
|
|
243
243
|
ui: { tab: "display", label: "Hardware cursor", description: "Show terminal cursor for IME support" },
|
|
244
244
|
},
|
|
245
|
+
clearOnShrink: {
|
|
246
|
+
type: "boolean",
|
|
247
|
+
default: false,
|
|
248
|
+
ui: {
|
|
249
|
+
tab: "display",
|
|
250
|
+
label: "Clear on shrink",
|
|
251
|
+
description: "Clear empty rows when content shrinks (may cause flicker)",
|
|
252
|
+
},
|
|
253
|
+
},
|
|
245
254
|
extensions: { type: "array", default: [] as string[] },
|
|
246
255
|
enabledModels: { type: "array", default: [] as string[] },
|
|
247
256
|
disabledProviders: { type: "array", default: [] as string[] },
|
package/src/config.ts
CHANGED
|
@@ -35,6 +35,14 @@ const priorityList = [
|
|
|
35
35
|
* Walk up from import.meta.dir until we find package.json, or fall back to cwd.
|
|
36
36
|
*/
|
|
37
37
|
export function getPackageDir(): string {
|
|
38
|
+
// Allow override via environment variable (useful for Nix/Guix where store paths tokenize poorly)
|
|
39
|
+
const envDir = process.env.PI_PACKAGE_DIR;
|
|
40
|
+
if (envDir) {
|
|
41
|
+
if (envDir === "~") return os.homedir();
|
|
42
|
+
if (envDir.startsWith("~/")) return os.homedir() + envDir.slice(1);
|
|
43
|
+
return envDir;
|
|
44
|
+
}
|
|
45
|
+
|
|
38
46
|
let dir = import.meta.dir;
|
|
39
47
|
while (dir !== path.dirname(dir)) {
|
|
40
48
|
if (fs.existsSync(path.join(dir, "package.json"))) {
|
|
@@ -218,7 +226,7 @@ export class ConfigFile<T> implements IConfigFile<T> {
|
|
|
218
226
|
}
|
|
219
227
|
return this.#storeCache({ value: parsed, status: "ok" });
|
|
220
228
|
} catch (error) {
|
|
221
|
-
if (
|
|
229
|
+
if (isEnoent(error)) {
|
|
222
230
|
return this.#storeCache({ status: "not-found" });
|
|
223
231
|
}
|
|
224
232
|
logger.warn("Failed to parse config file", { path: this.path(), error });
|
|
@@ -280,6 +288,11 @@ export function getPromptsDir(): string {
|
|
|
280
288
|
return path.join(getAgentDir(), "prompts");
|
|
281
289
|
}
|
|
282
290
|
|
|
291
|
+
/** Get path to content-addressed blob store directory */
|
|
292
|
+
export function getBlobsDir(): string {
|
|
293
|
+
return path.join(getAgentDir(), "blobs");
|
|
294
|
+
}
|
|
295
|
+
|
|
283
296
|
/** Get path to sessions directory */
|
|
284
297
|
export function getSessionsDir(): string {
|
|
285
298
|
return path.join(getAgentDir(), "sessions");
|
|
@@ -473,6 +473,10 @@
|
|
|
473
473
|
display: block;
|
|
474
474
|
}
|
|
475
475
|
|
|
476
|
+
.ansi-line {
|
|
477
|
+
white-space: pre-wrap;
|
|
478
|
+
}
|
|
479
|
+
|
|
476
480
|
.tool-images {
|
|
477
481
|
}
|
|
478
482
|
|
|
@@ -666,6 +670,9 @@
|
|
|
666
670
|
color: var(--error);
|
|
667
671
|
padding: 0 var(--line-height);
|
|
668
672
|
}
|
|
673
|
+
.tool-error {
|
|
674
|
+
color: var(--error);
|
|
675
|
+
}
|
|
669
676
|
|
|
670
677
|
/* Images */
|
|
671
678
|
.message-images {
|