@oh-my-pi/pi-coding-agent 14.0.5 → 14.1.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 +120 -0
- package/package.json +8 -8
- package/src/async/index.ts +1 -0
- package/src/async/job-manager.ts +43 -10
- package/src/async/support.ts +5 -0
- package/src/cli/list-models.ts +96 -57
- package/src/commit/agentic/tools/analyze-file.ts +1 -2
- package/src/commit/model-selection.ts +16 -13
- package/src/config/mcp-schema.json +1 -1
- package/src/config/model-equivalence.ts +675 -0
- package/src/config/model-registry.ts +242 -45
- package/src/config/model-resolver.ts +282 -65
- package/src/config/settings-schema.ts +27 -3
- package/src/config/settings.ts +1 -1
- package/src/cursor.ts +64 -23
- package/src/edit/index.ts +254 -89
- package/src/edit/modes/chunk.ts +336 -57
- package/src/edit/modes/hashline.ts +51 -26
- package/src/edit/modes/patch.ts +16 -10
- package/src/edit/modes/replace.ts +15 -7
- package/src/edit/renderer.ts +248 -94
- package/src/export/html/template.css +82 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +614 -97
- package/src/extensibility/custom-tools/types.ts +0 -3
- package/src/extensibility/extensions/loader.ts +16 -0
- package/src/extensibility/extensions/runner.ts +2 -7
- package/src/extensibility/extensions/types.ts +8 -4
- package/src/internal-urls/docs-index.generated.ts +4 -4
- package/src/internal-urls/jobs-protocol.ts +2 -1
- package/src/ipy/executor.ts +447 -52
- package/src/ipy/kernel.ts +39 -13
- package/src/lsp/client.ts +55 -1
- package/src/lsp/index.ts +8 -0
- package/src/lsp/types.ts +6 -0
- package/src/main.ts +6 -2
- package/src/memories/index.ts +7 -6
- package/src/modes/acp/acp-agent.ts +4 -1
- package/src/modes/components/bash-execution.ts +16 -4
- package/src/modes/components/model-selector.ts +221 -64
- package/src/modes/components/status-line/presets.ts +17 -6
- package/src/modes/components/status-line/segments.ts +15 -0
- package/src/modes/components/status-line-segment-editor.ts +1 -0
- package/src/modes/components/status-line.ts +7 -1
- package/src/modes/components/tool-execution.ts +145 -75
- package/src/modes/controllers/command-controller.ts +42 -1
- package/src/modes/controllers/event-controller.ts +4 -1
- package/src/modes/controllers/extension-ui-controller.ts +28 -5
- package/src/modes/controllers/input-controller.ts +9 -3
- package/src/modes/controllers/selector-controller.ts +17 -6
- package/src/modes/interactive-mode.ts +19 -3
- package/src/modes/print-mode.ts +13 -4
- package/src/modes/prompt-action-autocomplete.ts +3 -5
- package/src/modes/rpc/rpc-mode.ts +8 -2
- package/src/modes/shared.ts +2 -2
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/prompts/system/system-prompt.md +5 -1
- package/src/prompts/tools/bash.md +16 -1
- package/src/prompts/tools/cancel-job.md +1 -1
- package/src/prompts/tools/chunk-edit.md +191 -163
- package/src/prompts/tools/hashline.md +11 -11
- package/src/prompts/tools/patch.md +10 -5
- package/src/prompts/tools/{await.md → poll.md} +1 -1
- package/src/prompts/tools/read-chunk.md +12 -3
- package/src/prompts/tools/read.md +9 -0
- package/src/prompts/tools/task.md +2 -2
- package/src/prompts/tools/vim.md +98 -0
- package/src/prompts/tools/write.md +1 -0
- package/src/sdk.ts +758 -725
- package/src/session/agent-session.ts +187 -40
- package/src/session/session-manager.ts +50 -4
- package/src/slash-commands/builtin-registry.ts +17 -0
- package/src/task/executor.ts +9 -5
- package/src/task/index.ts +3 -5
- package/src/task/types.ts +2 -2
- package/src/tools/bash.ts +240 -57
- package/src/tools/cancel-job.ts +2 -1
- package/src/tools/find.ts +5 -2
- package/src/tools/grep.ts +77 -8
- package/src/tools/index.ts +48 -19
- package/src/tools/inspect-image.ts +1 -1
- package/src/tools/{await-tool.ts → poll-tool.ts} +38 -31
- package/src/tools/python.ts +293 -278
- package/src/tools/read.ts +218 -1
- package/src/tools/sqlite-reader.ts +623 -0
- package/src/tools/submit-result.ts +5 -2
- package/src/tools/todo-write.ts +8 -2
- package/src/tools/vim.ts +966 -0
- package/src/tools/write.ts +187 -1
- package/src/utils/commit-message-generator.ts +1 -0
- package/src/utils/edit-mode.ts +2 -1
- package/src/utils/git.ts +24 -1
- package/src/utils/session-color.ts +55 -0
- package/src/utils/title-generator.ts +16 -7
- package/src/vim/buffer.ts +309 -0
- package/src/vim/commands.ts +382 -0
- package/src/vim/engine.ts +2426 -0
- package/src/vim/parser.ts +151 -0
- package/src/vim/render.ts +252 -0
- package/src/vim/types.ts +197 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,121 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [14.1.1] - 2026-04-14
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- Removed the standalone `vim` tool from built-in tool lists, so vim-style editing is now invoked through `edit` in `vim` mode
|
|
10
|
+
- Removed the `searchDb` field from session and extension tool contexts, so custom tools and extensions no longer receive a shared native search DB handle from `ToolSession`, `CustomToolContext`, `ExtensionContext`, and `CreateAgentSessionOptions`
|
|
11
|
+
- Changed the `vim` tool API to require either `open: "path"` or `kbd: [...]` per call and removed direct `line`/`col` cursor parameters from `open`, so callers must position the cursor via key sequences after opening
|
|
12
|
+
- Changed the `edit` schemas for patch, replace, hashline, and chunk modes from top-level request fields to `edits` array entries, requiring path/mode details on each edit and breaking callers that send legacy top-level `path`, `old_text`, `new_text`, `op`, `move`, or `delete` payloads
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- Added Vim ex aliases `:del`, `:ya`, `:co`, and `:mo` as shorthand for existing delete, yank, copy, and move commands
|
|
17
|
+
- Added support for additional Vim ex command aliases `:write`/`write!`, `:edit`/`edit!`, and `:update`/`:up` in command parsing
|
|
18
|
+
- Added support for vim `:global` and `:vglobal`/`/` variants as `:g/pattern/d` and `:v/pattern/d` parsing and execution
|
|
19
|
+
- Added support for extra Vim operations by treating `x`, `X`, `s`, `S`, `C`, and `D` as delete/change operator aliases
|
|
20
|
+
- Added support for new Vim motions `gE`/`ge`, `g_`, `g*`, `g#`, and `|`
|
|
21
|
+
- Added support for `C-f` and `C-b` page motions in vim mode
|
|
22
|
+
- Added `C-u` and `C-o` in vim insert mode to clear to line start and execute a one-off normal-mode command before returning to insert
|
|
23
|
+
- Added insert-mode visual operators `J`, `u`, `U`, `p`, and `P` to join lines, convert case, and replace the selected region with register content
|
|
24
|
+
- Added normal-mode line motions `+`, `-`, and `_` to move to line offsets at the first non-blank character
|
|
25
|
+
- Added `*` and `#` normal-mode commands to search forward or backward for the word under the cursor
|
|
26
|
+
- Added `gJ` to join a line range, `gv` to restore the last visual selection, and `ZZ`/`ZQ` shortcuts for save-and-exit or exit-without-save in vim mode
|
|
27
|
+
- Added paragraph text object `p` for `ip`/`ap`-style paragraph selection
|
|
28
|
+
- Added support for Vim ex line-address forms like `.`, `$`, `+N`/`-N`, destination addresses such as `:t$`, and ranged `:global` commands
|
|
29
|
+
- Added Vim ex `:join`/`:j` and `:join!`/`:j!` support to join addressed lines with or without whitespace normalization
|
|
30
|
+
- Added a warning when chunk edits write to the `~` selector with body lines that appear over-indented, instructing users to start top-level body text at column 0
|
|
31
|
+
- Added validation feedback for suspect indentation in chunk-mode `~` body writes so users can align content with the tool's automatic base indentation
|
|
32
|
+
- Added support for multi-file `edit` calls across replace, patch, hashline, and chunk modes by grouping `edits` entries by file path and returning combined per-file results
|
|
33
|
+
- Added per-edit `path` support in chunk entries so each operation can target explicit files when submitting mixed edits in a single request
|
|
34
|
+
- Added support for `computeHashlineDiff` to accept hashline edits with `loc` and `content` payloads without requiring pre-resolved `op` fields
|
|
35
|
+
- Added `/rename <title>` slash command to set an explicit session name, updating the session header and terminal tab title ([#658](https://github.com/can1357/oh-my-pi/issues/658))
|
|
36
|
+
- Added `session_name` status line segment: displays the session name in the status bar right side with a stable hash-derived accent color unique to each name; shown in all presets when a name is set
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- Changed vim path normalization to accept colon-prefixed `path` values instead of rejecting them as Vim commands
|
|
41
|
+
- Changed default `providers.openaiWebsockets` setting to `off` when unset, so OpenAI websocket transport is now disabled unless explicitly enabled
|
|
42
|
+
- Changed Vim ex `:update`/`:up` execution to skip writing unchanged buffers and report buffer unchanged status
|
|
43
|
+
- Changed Vim page-scroll commands `C-f`, `C-b`, `C-u`, and `C-d` to move in viewport-height based increments instead of fixed constants
|
|
44
|
+
- Changed `z` command behavior so `zt`, `zb`, and `z.` now align cursor movement to first non-blank in the line
|
|
45
|
+
- Changed `:g`/`:v` global command handling to process matching lines safely by working in reverse order and preserving file structure
|
|
46
|
+
- Changed vim tab breadcrumb rendering from ` → ` to `→` in the editor view
|
|
47
|
+
- Changed custom tool and task execution contexts to no longer expose a shared `searchDb` accessor, removing direct access to native grep/glob/fuzzyFind search backends from extension callbacks
|
|
48
|
+
- Changed the `task` tool `schema` field to require JSON-encoded JTD schema text instead of a schema object, matching prompt guidance and task-subagent invocation
|
|
49
|
+
- Changed chunk edit payloads to encode selectors as `path: "file:selector"` and updated chunk tool guidance and examples to match
|
|
50
|
+
- Updated `edit` call/result rendering to show per-file diff sections and append a `(+N more)` hint when edits target multiple files
|
|
51
|
+
- Grouped chunk-mode `grep` results by directory, file, and chunk so directory searches now render as hierarchical sections (`#`/`##`) with per-chunk anchor lines
|
|
52
|
+
- Updated chunk-mode `grep` output to include match lines under their containing chunk entries with consistent line-number alignment based on file length
|
|
53
|
+
- Changed eager todo enforcement to only apply on the first user message of a conversation, skipping subsequent user turns that may correct, clarify, or redirect the prior task
|
|
54
|
+
|
|
55
|
+
### Removed
|
|
56
|
+
|
|
57
|
+
- Removed live in-progress Vim tool previews during streaming call execution, so the TUI now shows only the last completed file viewport until the call finishes
|
|
58
|
+
|
|
59
|
+
### Fixed
|
|
60
|
+
|
|
61
|
+
- Fixed vim-mode multi-step line edits by auto-reordering ascending line-positioned commands to descending order before execution
|
|
62
|
+
- Fixed Vim viewport rendering to display the inline highlighted cursor character and keep long cursor lines centered around the cursor in tool previews
|
|
63
|
+
- Fixed Vim `:global` command defaults to handle only supported subcommands and report unsupported ones explicitly
|
|
64
|
+
- Fixed Vim ex execution so parsed `:update`, `:yank`, and `:put` commands now run instead of falling through
|
|
65
|
+
- Fixed vim tool rendering so streamed calls preview the live target viewport and large insert payloads update incrementally instead of popping in all at once
|
|
66
|
+
- Fixed session event delivery so streaming `message_update`/tool-call previews reach the TUI immediately instead of waiting for extension handlers to finish
|
|
67
|
+
- Fixed HTML session export rendering so background-job wait calls render as `poll` instead of stale `await`, while still recognizing legacy exported sessions
|
|
68
|
+
- Fixed OpenRouter model resolution to accept dated routed selectors such as `openrouter/z-ai/glm-4.7-20251222:nitro`, inheriting metadata from the base catalog model when the exact variant is not listed yet
|
|
69
|
+
- Fixed pre-execution edit preview routing so replace/patch/hashline mode diffs are computed from the new structured edit entries
|
|
70
|
+
- Adjusted chunk/hashline/prompt guidance and validation to align with the refactored per-entry schema
|
|
71
|
+
- Fixed chunk streaming output detection to verify chunk edits with `chunkToolEditSchema`, preventing non-chunk edit payloads from being rendered as chunk diffs
|
|
72
|
+
- Fixed tool execution output to return the original `toolResult` text content from tools instead of sanitizing it before sending completion messages
|
|
73
|
+
- Fixed session accent rendering in the status line and editor to reset only foreground color (`\x1b[39m`) so applying a session color no longer clears other ANSI styles
|
|
74
|
+
- Session name sanitization: strip C0/C1 control characters (including ANSI ESC) from session names at storage time and in status line rendering, preventing escape sequence injection into TUI output
|
|
75
|
+
- Auto-generated session titles no longer overwrite a name set via `/rename`: `setSessionName` now tracks whether the name was set by the user or auto-generated and silently ignores auto titles once a user name is in place; terminal title follows the same guard
|
|
76
|
+
- Session accent border color now applied on session resume and after auto-title generation, not only after an explicit `/rename`
|
|
77
|
+
- Fixed retained Python kernel ownership so `AgentSession.dispose()` only shuts down kernels owned by that session, including warmup-created kernels
|
|
78
|
+
|
|
79
|
+
## [14.1.0] - 2026-04-11
|
|
80
|
+
### Added
|
|
81
|
+
|
|
82
|
+
- Added richer tool rendering details in session export HTML, including metadata badges, argument formatting, and todo task tree styling for exported tool and workflow messages
|
|
83
|
+
- Added a persistent `js` tool backed by `node:vm`, with cross-session `highway` KV/pubsub, tool calls from inside JS cells, and `$` / `$$` interactive JavaScript execution
|
|
84
|
+
- Added SQLite database read support to the `read` tool for `.sqlite`, `.sqlite3`, `.db`, and `.db3` files with table listing, schema + sample output, row lookup, paginated query filtering, and read-only `q=SELECT` mode
|
|
85
|
+
- Added SQLite mutation support to the `write` tool so `db.sqlite:table` inserts JSON5 rows and `db.sqlite:table:key` updates or deletes rows via row key
|
|
86
|
+
- Added rendering of usage report entries for accounts with no usage limits, including account label and optional plan type with a `-- no limits` indicator
|
|
87
|
+
- Updated account label resolution to fall back to email or accountId so unlabeled unlimited-plan accounts display a meaningful name
|
|
88
|
+
- Added canonical model equivalence and provider coalescing across `models.yml`, `enabledModels`, `--models`, `/model`, and `--list-models`
|
|
89
|
+
- Added `equivalence` overrides/exclusions to `models.yml` and `modelProviderOrder` to `config.yml` for global canonical-provider preference
|
|
90
|
+
|
|
91
|
+
### Changed
|
|
92
|
+
|
|
93
|
+
- Enabled `await` and `cancel_job` to be available when `bash.autoBackground.enabled` is set, so auto-backgrounded bash jobs can be awaited or cancelled without enabling `async.enabled`
|
|
94
|
+
- Updated bash auto-background behavior so short commands returned inline output when they completed before the configured threshold, while longer runs moved to background jobs automatically
|
|
95
|
+
- Replaced the LLM-callable Python execution path with JavaScript execution in the shared VM context, including updated renderers, prompts, session messages, and extension events
|
|
96
|
+
- Updated interactive and CLI model listings/selectors to work with canonical model ids while resolving them to concrete provider variants for actual execution
|
|
97
|
+
- Updated role assignment persistence so selected model settings now store the selector used by users, including thinking-level suffixes, while runtime continues to run against the resolved concrete provider model
|
|
98
|
+
- Updated model scope resolution to expand exact canonical model ids into all matching provider variants when filtering supported model sets
|
|
99
|
+
- Changed the agent to avoid giving time estimates or task-duration predictions in user responses, focusing on required work instead
|
|
100
|
+
- Changed generated code guidance to avoid speculative abstractions and extra compatibility scaffolding, favoring direct implementations that match current needs
|
|
101
|
+
- Changed model role resolution so roles can store either canonical model ids or explicit `provider/model` selectors while sessions continue to record the concrete model actually used
|
|
102
|
+
- Updated bash execution to optionally auto-background long-running commands through the existing background-job pipeline, with dedicated settings for enabling the behavior and adjusting the delay
|
|
103
|
+
|
|
104
|
+
### Fixed
|
|
105
|
+
|
|
106
|
+
- Fixed session export rendering so JavaScript execution messages now use `jsExecution` labels and content instead of `pythonExecution`, matching current tool behavior
|
|
107
|
+
- Fixed JavaScript cell execution to auto-display returned values once and preserve persistent VM bindings across calls until reset
|
|
108
|
+
- Fixed `.db`/`.db3` reads to verify SQLite file headers and fall back to normal file reading when the extension matches but the content is not a SQLite database
|
|
109
|
+
- Fixed SQLite selector parsing and resolution to correctly route requests to database operations at the file-extension boundary instead of misrouting through plain file/archive handlers
|
|
110
|
+
- Fixed unsupported or unsafe selectors by rejecting missing tables, composite primary keys for row lookups, unknown query parameters, and row operations on non-existent tables
|
|
111
|
+
- Fixed model resolution for commit message generation, title generation, memory consolidation, and image inspection when role strings use canonical ids instead of raw provider/model values
|
|
112
|
+
- Fixed default-model updates so previously configured thinking levels were preserved when reassigning a role
|
|
113
|
+
- Fixed model scope and selection handling in CLI/session startup paths that previously failed to resolve aliases consistently across features
|
|
114
|
+
- Fixed short-lived git subprocesses to disable `core.fsmonitor` and `core.untrackedCache`, avoiding unnecessary repository watchers and cache work during agent git operations
|
|
115
|
+
|
|
116
|
+
### Security
|
|
117
|
+
|
|
118
|
+
- Blocked destructive SQL execution in read-mode SQLite access by using read-only connections and rejecting bound-parameter raw SQL
|
|
119
|
+
|
|
5
120
|
## [14.0.5] - 2026-04-11
|
|
6
121
|
### Added
|
|
7
122
|
|
|
@@ -44,6 +159,10 @@
|
|
|
44
159
|
- Fixed stale diagnostics being reused after unrelated file publishes by clearing cached diagnostics before refreshing file state
|
|
45
160
|
- Fixed Codex search to use streamed answer text when final answer is an image placeholder or empty
|
|
46
161
|
|
|
162
|
+
### Fixed
|
|
163
|
+
|
|
164
|
+
- Fixed MCP config docs and schema to use `~/.omp/agent/mcp.json` for user-scoped OMP-native MCP config while keeping project config at `<cwd>/.omp/mcp.json`
|
|
165
|
+
|
|
47
166
|
## [14.0.4] - 2026-04-10
|
|
48
167
|
### Added
|
|
49
168
|
|
|
@@ -64,6 +183,7 @@
|
|
|
64
183
|
|
|
65
184
|
- Fixed typo in system prompt: 'backwards compatibiltity' → 'backwards compatibility'
|
|
66
185
|
|
|
186
|
+
|
|
67
187
|
## [14.0.3] - 2026-04-09
|
|
68
188
|
|
|
69
189
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "14.
|
|
4
|
+
"version": "14.1.1",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -46,19 +46,19 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@agentclientprotocol/sdk": "0.16.1",
|
|
48
48
|
"@mozilla/readability": "^0.6",
|
|
49
|
-
"@oh-my-pi/omp-stats": "
|
|
50
|
-
"@oh-my-pi/pi-agent-core": "
|
|
51
|
-
"@oh-my-pi/pi-ai": "
|
|
52
|
-
"@oh-my-pi/pi-natives": "
|
|
53
|
-
"@oh-my-pi/pi-tui": "
|
|
54
|
-
"@oh-my-pi/pi-utils": "
|
|
49
|
+
"@oh-my-pi/omp-stats": "workspace:*",
|
|
50
|
+
"@oh-my-pi/pi-agent-core": "workspace:*",
|
|
51
|
+
"@oh-my-pi/pi-ai": "workspace:*",
|
|
52
|
+
"@oh-my-pi/pi-natives": "workspace:*",
|
|
53
|
+
"@oh-my-pi/pi-tui": "workspace:*",
|
|
54
|
+
"@oh-my-pi/pi-utils": "workspace:*",
|
|
55
55
|
"@sinclair/typebox": "^0.34",
|
|
56
56
|
"@xterm/headless": "^6.0",
|
|
57
57
|
"ajv": "^8.18",
|
|
58
58
|
"chalk": "^5.6",
|
|
59
59
|
"diff": "^8.0",
|
|
60
60
|
"fflate": "0.8.2",
|
|
61
|
-
"handlebars": "^4.7",
|
|
61
|
+
"handlebars": "^4.7.9",
|
|
62
62
|
"linkedom": "^0.18",
|
|
63
63
|
"lru-cache": "11.3.1",
|
|
64
64
|
"markit-ai": "0.5.0",
|
package/src/async/index.ts
CHANGED
package/src/async/job-manager.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { logger
|
|
1
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
2
2
|
|
|
3
3
|
const DELIVERY_RETRY_BASE_MS = 500;
|
|
4
4
|
const DELIVERY_RETRY_MAX_MS = 30_000;
|
|
@@ -48,6 +48,7 @@ export class AsyncJobManager {
|
|
|
48
48
|
readonly #jobs = new Map<string, AsyncJob>();
|
|
49
49
|
readonly #deliveries: AsyncJobDelivery[] = [];
|
|
50
50
|
readonly #suppressedDeliveries = new Set<string>();
|
|
51
|
+
readonly #watchedJobs = new Set<string>();
|
|
51
52
|
readonly #evictionTimers = new Map<string, NodeJS.Timeout>();
|
|
52
53
|
readonly #onJobComplete: AsyncJobManagerOptions["onJobComplete"];
|
|
53
54
|
readonly #maxRunningJobs: number;
|
|
@@ -184,6 +185,25 @@ export class AsyncJobManager {
|
|
|
184
185
|
return this.#deliveries.length > 0;
|
|
185
186
|
}
|
|
186
187
|
|
|
188
|
+
watchJobs(jobIds: string[]): number {
|
|
189
|
+
const uniqueJobIds = Array.from(new Set(jobIds.map(id => id.trim()).filter(id => id.length > 0)));
|
|
190
|
+
for (const jobId of uniqueJobIds) {
|
|
191
|
+
this.#watchedJobs.add(jobId);
|
|
192
|
+
}
|
|
193
|
+
return uniqueJobIds.length;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
unwatchJobs(jobIds: string[]): number {
|
|
197
|
+
const uniqueJobIds = Array.from(new Set(jobIds.map(id => id.trim()).filter(id => id.length > 0)));
|
|
198
|
+
let removed = 0;
|
|
199
|
+
for (const jobId of uniqueJobIds) {
|
|
200
|
+
if (this.#watchedJobs.delete(jobId)) {
|
|
201
|
+
removed += 1;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return removed;
|
|
205
|
+
}
|
|
206
|
+
|
|
187
207
|
acknowledgeDeliveries(jobIds: string[]): number {
|
|
188
208
|
const uniqueJobIds = Array.from(new Set(jobIds.map(id => id.trim()).filter(id => id.length > 0)));
|
|
189
209
|
if (uniqueJobIds.length === 0) return 0;
|
|
@@ -196,7 +216,7 @@ export class AsyncJobManager {
|
|
|
196
216
|
this.#deliveries.splice(
|
|
197
217
|
0,
|
|
198
218
|
this.#deliveries.length,
|
|
199
|
-
...this.#deliveries.filter(delivery => !this
|
|
219
|
+
...this.#deliveries.filter(delivery => !this.isDeliverySuppressed(delivery.jobId)),
|
|
200
220
|
);
|
|
201
221
|
return before - this.#deliveries.length;
|
|
202
222
|
}
|
|
@@ -254,12 +274,21 @@ export class AsyncJobManager {
|
|
|
254
274
|
this.#jobs.clear();
|
|
255
275
|
this.#deliveries.length = 0;
|
|
256
276
|
this.#suppressedDeliveries.clear();
|
|
277
|
+
this.#watchedJobs.clear();
|
|
257
278
|
return drained;
|
|
258
279
|
}
|
|
259
280
|
|
|
260
281
|
#resolveJobId(preferredId?: string): string {
|
|
261
|
-
|
|
262
|
-
|
|
282
|
+
preferredId = preferredId?.trim();
|
|
283
|
+
if (!preferredId) {
|
|
284
|
+
let candidate = 1;
|
|
285
|
+
while (true) {
|
|
286
|
+
const id = `bg_${candidate}`;
|
|
287
|
+
if (!this.#jobs.has(id)) {
|
|
288
|
+
return id;
|
|
289
|
+
}
|
|
290
|
+
candidate += 1;
|
|
291
|
+
}
|
|
263
292
|
}
|
|
264
293
|
|
|
265
294
|
const base = preferredId.trim();
|
|
@@ -278,6 +307,7 @@ export class AsyncJobManager {
|
|
|
278
307
|
if (this.#retentionMs <= 0) {
|
|
279
308
|
this.#jobs.delete(jobId);
|
|
280
309
|
this.#suppressedDeliveries.delete(jobId);
|
|
310
|
+
this.#watchedJobs.delete(jobId);
|
|
281
311
|
return;
|
|
282
312
|
}
|
|
283
313
|
const existing = this.#evictionTimers.get(jobId);
|
|
@@ -288,6 +318,7 @@ export class AsyncJobManager {
|
|
|
288
318
|
this.#evictionTimers.delete(jobId);
|
|
289
319
|
this.#jobs.delete(jobId);
|
|
290
320
|
this.#suppressedDeliveries.delete(jobId);
|
|
321
|
+
this.#watchedJobs.delete(jobId);
|
|
291
322
|
}, this.#retentionMs);
|
|
292
323
|
timer.unref();
|
|
293
324
|
this.#evictionTimers.set(jobId, timer);
|
|
@@ -300,12 +331,13 @@ export class AsyncJobManager {
|
|
|
300
331
|
this.#evictionTimers.clear();
|
|
301
332
|
}
|
|
302
333
|
|
|
303
|
-
|
|
304
|
-
return this.#suppressedDeliveries.has(jobId);
|
|
334
|
+
isDeliverySuppressed(jobId: string): boolean {
|
|
335
|
+
return this.#suppressedDeliveries.has(jobId) || this.#watchedJobs.has(jobId);
|
|
305
336
|
}
|
|
306
337
|
|
|
307
338
|
#enqueueDelivery(jobId: string, text: string): void {
|
|
308
|
-
if
|
|
339
|
+
// Skip delivery if already acknowledged
|
|
340
|
+
if (this.isDeliverySuppressed(jobId)) {
|
|
309
341
|
return;
|
|
310
342
|
}
|
|
311
343
|
this.#deliveries.push({
|
|
@@ -337,7 +369,7 @@ export class AsyncJobManager {
|
|
|
337
369
|
async #runDeliveryLoop(): Promise<void> {
|
|
338
370
|
while (this.#deliveries.length > 0) {
|
|
339
371
|
const delivery = this.#deliveries[0];
|
|
340
|
-
if (this
|
|
372
|
+
if (this.isDeliverySuppressed(delivery.jobId)) {
|
|
341
373
|
this.#deliveries.shift();
|
|
342
374
|
continue;
|
|
343
375
|
}
|
|
@@ -348,7 +380,8 @@ export class AsyncJobManager {
|
|
|
348
380
|
if (this.#deliveries[0] !== delivery) {
|
|
349
381
|
continue;
|
|
350
382
|
}
|
|
351
|
-
|
|
383
|
+
// Check again after sleep
|
|
384
|
+
if (this.isDeliverySuppressed(delivery.jobId)) {
|
|
352
385
|
this.#deliveries.shift();
|
|
353
386
|
continue;
|
|
354
387
|
}
|
|
@@ -361,7 +394,7 @@ export class AsyncJobManager {
|
|
|
361
394
|
delivery.lastError = error instanceof Error ? error.message : String(error);
|
|
362
395
|
delivery.nextAttemptAt = Date.now() + this.#getRetryDelay(delivery.attempt);
|
|
363
396
|
this.#deliveries.shift();
|
|
364
|
-
if (!this
|
|
397
|
+
if (!this.isDeliverySuppressed(delivery.jobId)) {
|
|
365
398
|
this.#deliveries.push(delivery);
|
|
366
399
|
}
|
|
367
400
|
logger.warn("Async job completion delivery failed", {
|
package/src/cli/list-models.ts
CHANGED
|
@@ -6,6 +6,45 @@ import { formatNumber } from "@oh-my-pi/pi-utils";
|
|
|
6
6
|
import type { ModelRegistry } from "../config/model-registry";
|
|
7
7
|
import { fuzzyFilter } from "../utils/fuzzy";
|
|
8
8
|
|
|
9
|
+
interface ProviderRow {
|
|
10
|
+
provider: string;
|
|
11
|
+
model: string;
|
|
12
|
+
context: string;
|
|
13
|
+
maxOut: string;
|
|
14
|
+
thinking: string;
|
|
15
|
+
images: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface CanonicalRow {
|
|
19
|
+
canonical: string;
|
|
20
|
+
selected: string;
|
|
21
|
+
variants: string;
|
|
22
|
+
context: string;
|
|
23
|
+
maxOut: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function writeLine(line = ""): void {
|
|
27
|
+
process.stdout.write(`${line}\n`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function renderTable<T extends Record<string, string>>(rows: T[], headers: T): void {
|
|
31
|
+
const widths = Object.fromEntries(
|
|
32
|
+
Object.keys(headers).map(key => [key, Math.max(headers[key]!.length, ...rows.map(row => row[key]!.length))]),
|
|
33
|
+
) as Record<keyof T, number>;
|
|
34
|
+
|
|
35
|
+
const headerLine = Object.keys(headers)
|
|
36
|
+
.map(key => headers[key as keyof T]!.padEnd(widths[key as keyof T]))
|
|
37
|
+
.join(" ");
|
|
38
|
+
writeLine(headerLine);
|
|
39
|
+
|
|
40
|
+
for (const row of rows) {
|
|
41
|
+
const line = Object.keys(headers)
|
|
42
|
+
.map(key => row[key as keyof T]!.padEnd(widths[key as keyof T]))
|
|
43
|
+
.join(" ");
|
|
44
|
+
writeLine(line);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
9
48
|
/**
|
|
10
49
|
* List available models, optionally filtered by search pattern
|
|
11
50
|
*/
|
|
@@ -13,77 +52,77 @@ export async function listModels(modelRegistry: ModelRegistry, searchPattern?: s
|
|
|
13
52
|
const models = modelRegistry.getAvailable();
|
|
14
53
|
|
|
15
54
|
if (models.length === 0) {
|
|
16
|
-
|
|
55
|
+
writeLine("No models available. Set API keys in environment variables.");
|
|
17
56
|
return;
|
|
18
57
|
}
|
|
19
58
|
|
|
20
|
-
// Apply fuzzy filter if search pattern provided
|
|
21
59
|
let filteredModels: Model<Api>[] = models;
|
|
22
60
|
if (searchPattern) {
|
|
23
|
-
filteredModels = fuzzyFilter(models, searchPattern,
|
|
61
|
+
filteredModels = fuzzyFilter(models, searchPattern, model => `${model.provider} ${model.id}`);
|
|
24
62
|
}
|
|
25
63
|
|
|
26
|
-
|
|
27
|
-
|
|
64
|
+
const filteredCanonical = modelRegistry
|
|
65
|
+
.getCanonicalModels({ availableOnly: true, candidates: filteredModels })
|
|
66
|
+
.map(record => {
|
|
67
|
+
const selected = modelRegistry.resolveCanonicalModel(record.id, {
|
|
68
|
+
availableOnly: true,
|
|
69
|
+
candidates: filteredModels,
|
|
70
|
+
});
|
|
71
|
+
if (!selected) return undefined;
|
|
72
|
+
return {
|
|
73
|
+
canonical: record.id,
|
|
74
|
+
selected: `${selected.provider}/${selected.id}`,
|
|
75
|
+
variants: String(record.variants.length),
|
|
76
|
+
context: formatNumber(selected.contextWindow),
|
|
77
|
+
maxOut: formatNumber(selected.maxTokens),
|
|
78
|
+
} satisfies CanonicalRow;
|
|
79
|
+
})
|
|
80
|
+
.filter((row): row is CanonicalRow => row !== undefined)
|
|
81
|
+
.sort((left, right) => left.canonical.localeCompare(right.canonical));
|
|
82
|
+
|
|
83
|
+
if (filteredModels.length === 0 && filteredCanonical.length === 0) {
|
|
84
|
+
writeLine(`No models matching "${searchPattern}"`);
|
|
28
85
|
return;
|
|
29
86
|
}
|
|
30
87
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const providerCmp = a.provider.localeCompare(b.provider);
|
|
88
|
+
filteredModels.sort((left, right) => {
|
|
89
|
+
const providerCmp = left.provider.localeCompare(right.provider);
|
|
34
90
|
if (providerCmp !== 0) return providerCmp;
|
|
35
|
-
return
|
|
91
|
+
return left.id.localeCompare(right.id);
|
|
36
92
|
});
|
|
37
93
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}));
|
|
94
|
+
const providerRows = filteredModels.map(model => ({
|
|
95
|
+
provider: model.provider,
|
|
96
|
+
model: model.id,
|
|
97
|
+
context: formatNumber(model.contextWindow),
|
|
98
|
+
maxOut: formatNumber(model.maxTokens),
|
|
99
|
+
thinking: model.thinking ? getSupportedEfforts(model).join(",") : model.reasoning ? "yes" : "-",
|
|
100
|
+
images: model.input.includes("image") ? "yes" : "no",
|
|
101
|
+
})) satisfies ProviderRow[];
|
|
47
102
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
maxOut: Math.max(headers.maxOut.length, ...rows.map(r => r.maxOut.length)),
|
|
62
|
-
thinking: Math.max(headers.thinking.length, ...rows.map(r => r.thinking.length)),
|
|
63
|
-
images: Math.max(headers.images.length, ...rows.map(r => r.images.length)),
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
// Print header
|
|
67
|
-
const headerLine = [
|
|
68
|
-
headers.provider.padEnd(widths.provider),
|
|
69
|
-
headers.model.padEnd(widths.model),
|
|
70
|
-
headers.context.padEnd(widths.context),
|
|
71
|
-
headers.maxOut.padEnd(widths.maxOut),
|
|
72
|
-
headers.thinking.padEnd(widths.thinking),
|
|
73
|
-
headers.images.padEnd(widths.images),
|
|
74
|
-
].join(" ");
|
|
75
|
-
console.log(headerLine);
|
|
103
|
+
if (filteredCanonical.length > 0) {
|
|
104
|
+
writeLine("Canonical models");
|
|
105
|
+
renderTable(filteredCanonical, {
|
|
106
|
+
canonical: "canonical",
|
|
107
|
+
selected: "selected",
|
|
108
|
+
variants: "variants",
|
|
109
|
+
context: "context",
|
|
110
|
+
maxOut: "max-out",
|
|
111
|
+
});
|
|
112
|
+
if (providerRows.length > 0) {
|
|
113
|
+
writeLine();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
76
116
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
console.log(line);
|
|
117
|
+
if (providerRows.length > 0) {
|
|
118
|
+
writeLine("Provider models");
|
|
119
|
+
renderTable(providerRows, {
|
|
120
|
+
provider: "provider",
|
|
121
|
+
model: "model",
|
|
122
|
+
context: "context",
|
|
123
|
+
maxOut: "max-out",
|
|
124
|
+
thinking: "thinking",
|
|
125
|
+
images: "images",
|
|
126
|
+
});
|
|
88
127
|
}
|
|
89
128
|
}
|
|
@@ -43,7 +43,6 @@ function buildToolSession(
|
|
|
43
43
|
settings: options.settings,
|
|
44
44
|
authStorage: options.authStorage,
|
|
45
45
|
modelRegistry: options.modelRegistry,
|
|
46
|
-
searchDb: ctx.searchDb,
|
|
47
46
|
};
|
|
48
47
|
}
|
|
49
48
|
|
|
@@ -79,7 +78,7 @@ export function createAnalyzeFileTool(options: {
|
|
|
79
78
|
});
|
|
80
79
|
const taskParams: TaskParams = {
|
|
81
80
|
agent: "quick_task",
|
|
82
|
-
schema: analyzeFileOutputSchema,
|
|
81
|
+
schema: JSON.stringify(analyzeFileOutputSchema),
|
|
83
82
|
tasks,
|
|
84
83
|
};
|
|
85
84
|
return taskTool.execute(toolCallId, taskParams, signal, onUpdate);
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { MODEL_ROLE_IDS } from "../config/model-registry";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
type ModelLookupRegistry,
|
|
6
|
+
parseModelPattern,
|
|
7
|
+
resolveModelRoleValue,
|
|
8
|
+
resolveRoleSelection,
|
|
9
|
+
} from "../config/model-resolver";
|
|
5
10
|
import type { Settings } from "../config/settings";
|
|
6
11
|
import MODEL_PRIO from "../priority.json" with { type: "json" };
|
|
7
12
|
|
|
@@ -11,19 +16,20 @@ export interface ResolvedCommitModel {
|
|
|
11
16
|
thinkingLevel?: ThinkingLevel;
|
|
12
17
|
}
|
|
13
18
|
|
|
19
|
+
type CommitModelRegistry = ModelLookupRegistry & {
|
|
20
|
+
getApiKey: (model: Model<Api>) => Promise<string | undefined>;
|
|
21
|
+
};
|
|
22
|
+
|
|
14
23
|
export async function resolvePrimaryModel(
|
|
15
24
|
override: string | undefined,
|
|
16
25
|
settings: Settings,
|
|
17
|
-
modelRegistry:
|
|
18
|
-
getAvailable: () => Model<Api>[];
|
|
19
|
-
getApiKey: (model: Model<Api>) => Promise<string | undefined>;
|
|
20
|
-
},
|
|
26
|
+
modelRegistry: CommitModelRegistry,
|
|
21
27
|
): Promise<ResolvedCommitModel> {
|
|
22
28
|
const available = modelRegistry.getAvailable();
|
|
23
29
|
const matchPreferences = { usageOrder: settings.getStorage()?.getModelUsageOrder() };
|
|
24
30
|
const resolved = override
|
|
25
|
-
? resolveModelRoleValue(override, available, { settings, matchPreferences })
|
|
26
|
-
: resolveRoleSelection(["commit", "smol", ...MODEL_ROLE_IDS], settings, available);
|
|
31
|
+
? resolveModelRoleValue(override, available, { settings, matchPreferences, modelRegistry })
|
|
32
|
+
: resolveRoleSelection(["commit", "smol", ...MODEL_ROLE_IDS], settings, available, modelRegistry);
|
|
27
33
|
const model = resolved?.model;
|
|
28
34
|
if (!model) {
|
|
29
35
|
throw new Error("No model available for commit generation");
|
|
@@ -37,15 +43,12 @@ export async function resolvePrimaryModel(
|
|
|
37
43
|
|
|
38
44
|
export async function resolveSmolModel(
|
|
39
45
|
settings: Settings,
|
|
40
|
-
modelRegistry:
|
|
41
|
-
getAvailable: () => Model<Api>[];
|
|
42
|
-
getApiKey: (model: Model<Api>) => Promise<string | undefined>;
|
|
43
|
-
},
|
|
46
|
+
modelRegistry: CommitModelRegistry,
|
|
44
47
|
fallbackModel: Model<Api>,
|
|
45
48
|
fallbackApiKey: string,
|
|
46
49
|
): Promise<ResolvedCommitModel> {
|
|
47
50
|
const available = modelRegistry.getAvailable();
|
|
48
|
-
const resolvedSmol = resolveRoleSelection(["smol"], settings, available);
|
|
51
|
+
const resolvedSmol = resolveRoleSelection(["smol"], settings, available, modelRegistry);
|
|
49
52
|
if (resolvedSmol?.model) {
|
|
50
53
|
const apiKey = await modelRegistry.getApiKey(resolvedSmol.model);
|
|
51
54
|
if (apiKey) return { model: resolvedSmol.model, apiKey, thinkingLevel: resolvedSmol.thinkingLevel };
|
|
@@ -53,7 +56,7 @@ export async function resolveSmolModel(
|
|
|
53
56
|
|
|
54
57
|
const matchPreferences = { usageOrder: settings.getStorage()?.getModelUsageOrder() };
|
|
55
58
|
for (const pattern of MODEL_PRIO.smol) {
|
|
56
|
-
const candidate = parseModelPattern(pattern, available, matchPreferences).model;
|
|
59
|
+
const candidate = parseModelPattern(pattern, available, matchPreferences, { modelRegistry }).model;
|
|
57
60
|
if (!candidate) continue;
|
|
58
61
|
const apiKey = await modelRegistry.getApiKey(candidate);
|
|
59
62
|
if (apiKey) return { model: candidate, apiKey };
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"$id": "https://raw.githubusercontent.com/can1357/oh-my-pi/main/packages/coding-agent/src/config/mcp-schema.json",
|
|
4
4
|
"title": "OMP MCP configuration",
|
|
5
|
-
"description": "Schema for mcp.json, .mcp.json, .omp/mcp.json, and ~/.omp/mcp.json used by the OMP coding agent.",
|
|
5
|
+
"description": "Schema for mcp.json, .mcp.json, .omp/mcp.json, and ~/.omp/agent/mcp.json used by the OMP coding agent.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"properties": {
|