@oh-my-pi/pi-coding-agent 14.0.5 → 14.1.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/package.json +7 -7
  3. package/src/async/index.ts +1 -0
  4. package/src/async/support.ts +5 -0
  5. package/src/cli/list-models.ts +96 -57
  6. package/src/commit/model-selection.ts +16 -13
  7. package/src/config/model-equivalence.ts +674 -0
  8. package/src/config/model-registry.ts +179 -11
  9. package/src/config/model-resolver.ts +171 -50
  10. package/src/config/settings-schema.ts +23 -0
  11. package/src/export/html/template.css +82 -0
  12. package/src/export/html/template.generated.ts +1 -1
  13. package/src/export/html/template.js +612 -97
  14. package/src/internal-urls/docs-index.generated.ts +1 -1
  15. package/src/internal-urls/jobs-protocol.ts +2 -1
  16. package/src/lsp/client.ts +1 -1
  17. package/src/main.ts +6 -1
  18. package/src/memories/index.ts +7 -6
  19. package/src/modes/components/model-selector.ts +221 -64
  20. package/src/modes/controllers/command-controller.ts +18 -0
  21. package/src/modes/controllers/selector-controller.ts +13 -5
  22. package/src/prompts/system/system-prompt.md +5 -1
  23. package/src/prompts/tools/bash.md +15 -0
  24. package/src/prompts/tools/cancel-job.md +1 -1
  25. package/src/prompts/tools/read-chunk.md +9 -0
  26. package/src/prompts/tools/read.md +9 -0
  27. package/src/prompts/tools/write.md +1 -0
  28. package/src/sdk.ts +7 -4
  29. package/src/session/agent-session.ts +23 -6
  30. package/src/task/executor.ts +5 -1
  31. package/src/tools/await-tool.ts +2 -1
  32. package/src/tools/bash.ts +221 -56
  33. package/src/tools/cancel-job.ts +2 -1
  34. package/src/tools/inspect-image.ts +1 -1
  35. package/src/tools/read.ts +218 -1
  36. package/src/tools/sqlite-reader.ts +623 -0
  37. package/src/tools/write.ts +187 -1
  38. package/src/utils/commit-message-generator.ts +1 -0
  39. package/src/utils/git.ts +24 -1
  40. package/src/utils/title-generator.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,47 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [14.1.0] - 2026-04-11
6
+ ### Added
7
+
8
+ - 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
9
+ - 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
10
+ - 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
11
+ - 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
12
+ - Added rendering of usage report entries for accounts with no usage limits, including account label and optional plan type with a `-- no limits` indicator
13
+ - Updated account label resolution to fall back to email or accountId so unlabeled unlimited-plan accounts display a meaningful name
14
+ - Added canonical model equivalence and provider coalescing across `models.yml`, `enabledModels`, `--models`, `/model`, and `--list-models`
15
+ - Added `equivalence` overrides/exclusions to `models.yml` and `modelProviderOrder` to `config.yml` for global canonical-provider preference
16
+
17
+ ### Changed
18
+
19
+ - 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`
20
+ - 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
21
+ - Replaced the LLM-callable Python execution path with JavaScript execution in the shared VM context, including updated renderers, prompts, session messages, and extension events
22
+ - Updated interactive and CLI model listings/selectors to work with canonical model ids while resolving them to concrete provider variants for actual execution
23
+ - 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
24
+ - Updated model scope resolution to expand exact canonical model ids into all matching provider variants when filtering supported model sets
25
+ - Changed the agent to avoid giving time estimates or task-duration predictions in user responses, focusing on required work instead
26
+ - Changed generated code guidance to avoid speculative abstractions and extra compatibility scaffolding, favoring direct implementations that match current needs
27
+ - 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
28
+ - 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
29
+
30
+ ### Fixed
31
+
32
+ - Fixed session export rendering so JavaScript execution messages now use `jsExecution` labels and content instead of `pythonExecution`, matching current tool behavior
33
+ - Fixed JavaScript cell execution to auto-display returned values once and preserve persistent VM bindings across calls until reset
34
+ - 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
35
+ - 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
36
+ - 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
37
+ - 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
38
+ - Fixed default-model updates so previously configured thinking levels were preserved when reassigning a role
39
+ - Fixed model scope and selection handling in CLI/session startup paths that previously failed to resolve aliases consistently across features
40
+ - Fixed short-lived git subprocesses to disable `core.fsmonitor` and `core.untrackedCache`, avoiding unnecessary repository watchers and cache work during agent git operations
41
+
42
+ ### Security
43
+
44
+ - Blocked destructive SQL execution in read-mode SQLite access by using read-only connections and rejecting bound-parameter raw SQL
45
+
5
46
  ## [14.0.5] - 2026-04-11
6
47
  ### Added
7
48
 
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.0.5",
4
+ "version": "14.1.0",
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,12 +46,12 @@
46
46
  "dependencies": {
47
47
  "@agentclientprotocol/sdk": "0.16.1",
48
48
  "@mozilla/readability": "^0.6",
49
- "@oh-my-pi/omp-stats": "14.0.5",
50
- "@oh-my-pi/pi-agent-core": "14.0.5",
51
- "@oh-my-pi/pi-ai": "14.0.5",
52
- "@oh-my-pi/pi-natives": "14.0.5",
53
- "@oh-my-pi/pi-tui": "14.0.5",
54
- "@oh-my-pi/pi-utils": "14.0.5",
49
+ "@oh-my-pi/omp-stats": "14.1.0",
50
+ "@oh-my-pi/pi-agent-core": "14.1.0",
51
+ "@oh-my-pi/pi-ai": "14.1.0",
52
+ "@oh-my-pi/pi-natives": "14.1.0",
53
+ "@oh-my-pi/pi-tui": "14.1.0",
54
+ "@oh-my-pi/pi-utils": "14.1.0",
55
55
  "@sinclair/typebox": "^0.34",
56
56
  "@xterm/headless": "^6.0",
57
57
  "ajv": "^8.18",
@@ -1 +1,2 @@
1
1
  export * from "./job-manager";
2
+ export * from "./support";
@@ -0,0 +1,5 @@
1
+ import type { Settings } from "../config/settings";
2
+
3
+ export function isBackgroundJobSupportEnabled(settings: Pick<Settings, "get">): boolean {
4
+ return settings.get("async.enabled") || settings.get("bash.autoBackground.enabled");
5
+ }
@@ -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
- console.log("No models available. Set API keys in environment variables.");
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, m => `${m.provider} ${m.id}`);
61
+ filteredModels = fuzzyFilter(models, searchPattern, model => `${model.provider} ${model.id}`);
24
62
  }
25
63
 
26
- if (filteredModels.length === 0) {
27
- console.log(`No models matching "${searchPattern}"`);
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
- // Sort by provider, then by model id
32
- filteredModels.sort((a, b) => {
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 a.id.localeCompare(b.id);
91
+ return left.id.localeCompare(right.id);
36
92
  });
37
93
 
38
- // Calculate column widths
39
- const rows = filteredModels.map(m => ({
40
- provider: m.provider,
41
- model: m.id,
42
- context: formatNumber(m.contextWindow),
43
- maxOut: formatNumber(m.maxTokens),
44
- thinking: m.thinking ? getSupportedEfforts(m).join(",") : m.reasoning ? "yes" : "-",
45
- images: m.input.includes("image") ? "yes" : "no",
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
- const headers = {
49
- provider: "provider",
50
- model: "model",
51
- context: "context",
52
- maxOut: "max-out",
53
- thinking: "thinking",
54
- images: "images",
55
- };
56
-
57
- const widths = {
58
- provider: Math.max(headers.provider.length, ...rows.map(r => r.provider.length)),
59
- model: Math.max(headers.model.length, ...rows.map(r => r.model.length)),
60
- context: Math.max(headers.context.length, ...rows.map(r => r.context.length)),
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
- // Print rows
78
- for (const row of rows) {
79
- const line = [
80
- row.provider.padEnd(widths.provider),
81
- row.model.padEnd(widths.model),
82
- row.context.padEnd(widths.context),
83
- row.maxOut.padEnd(widths.maxOut),
84
- row.thinking.padEnd(widths.thinking),
85
- row.images.padEnd(widths.images),
86
- ].join(" ");
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
  }
@@ -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 { parseModelPattern, resolveModelRoleValue, resolveRoleSelection } from "../config/model-resolver";
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 };