@oh-my-pi/pi-coding-agent 15.4.1 → 15.4.3

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 CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.4.3] - 2026-05-26
6
+
7
+ ### Fixed
8
+
9
+ - Fixed Google Vertex cached project discovery replacing the bundled fallback catalog so `/models` does not keep showing outdated Gemini entries after authoritative Vertex discovery ([#1412](https://github.com/can1357/oh-my-pi/issues/1412)).
10
+
11
+ ## [15.4.2] - 2026-05-26
12
+
13
+ ### Fixed
14
+
15
+ - Fixed plan-mode subagents being unable to terminate because `yield` was registered but missing from the active tool set when `requireYieldTool` was combined with an explicit `toolNames` list ([#1408](https://github.com/can1357/oh-my-pi/issues/1408))
16
+
5
17
  ## [15.4.1] - 2026-05-26
6
18
 
7
19
  ### Breaking Changes
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": "15.4.1",
4
+ "version": "15.4.3",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -47,12 +47,12 @@
47
47
  "@agentclientprotocol/sdk": "0.21.0",
48
48
  "@babel/parser": "^7.29.3",
49
49
  "@mozilla/readability": "^0.6.0",
50
- "@oh-my-pi/omp-stats": "15.4.1",
51
- "@oh-my-pi/pi-agent-core": "15.4.1",
52
- "@oh-my-pi/pi-ai": "15.4.1",
53
- "@oh-my-pi/pi-natives": "15.4.1",
54
- "@oh-my-pi/pi-tui": "15.4.1",
55
- "@oh-my-pi/pi-utils": "15.4.1",
50
+ "@oh-my-pi/omp-stats": "15.4.3",
51
+ "@oh-my-pi/pi-agent-core": "15.4.3",
52
+ "@oh-my-pi/pi-ai": "15.4.3",
53
+ "@oh-my-pi/pi-natives": "15.4.3",
54
+ "@oh-my-pi/pi-tui": "15.4.3",
55
+ "@oh-my-pi/pi-utils": "15.4.3",
56
56
  "@puppeteer/browsers": "^2.13.0",
57
57
  "@types/turndown": "5.0.6",
58
58
  "@xterm/headless": "^6.0.0",
@@ -291,6 +291,28 @@ export function mergeDiscoveredModel<TApi extends Api>(
291
291
  return model;
292
292
  }
293
293
 
294
+ function isAuthoritativeProjectCatalogModel(model: Model<Api>): boolean {
295
+ return (
296
+ model.provider === "google-vertex" &&
297
+ model.api === "openai-completions" &&
298
+ model.baseUrl.includes("/endpoints/openapi")
299
+ );
300
+ }
301
+
302
+ function providersWithAuthoritativeProjectCatalog(models: readonly Model<Api>[]): Set<string> {
303
+ const providers = new Set<string>();
304
+ for (const model of models) {
305
+ if (isAuthoritativeProjectCatalogModel(model)) {
306
+ providers.add(model.provider);
307
+ }
308
+ }
309
+ return providers;
310
+ }
311
+
312
+ function dropProviderModels(models: readonly Model<Api>[], providers: ReadonlySet<string>): Model<Api>[] {
313
+ return models.filter(model => !providers.has(model.provider));
314
+ }
315
+
294
316
  interface DiscoveryProviderConfig {
295
317
  provider: string;
296
318
  api: Api;
@@ -877,9 +899,13 @@ export class ModelRegistry {
877
899
  this.#equivalenceConfig = equivalence;
878
900
 
879
901
  this.#addImplicitDiscoverableProviders(configuredProviders);
880
- const builtInModels = this.#applyHardcodedModelPolicies(this.#loadBuiltInModels(overrides));
902
+ let builtInModels = this.#applyHardcodedModelPolicies(this.#loadBuiltInModels(overrides));
881
903
  const cachedStandardModels = this.#applyHardcodedModelPolicies(this.#loadCachedStandardProviderModels());
882
904
  const cachedDiscoveries = this.#applyHardcodedModelPolicies(this.#loadCachedDiscoverableModels());
905
+ const cachedAuthoritativeProviders = providersWithAuthoritativeProjectCatalog(cachedStandardModels);
906
+ if (cachedAuthoritativeProviders.size > 0) {
907
+ builtInModels = dropProviderModels(builtInModels, cachedAuthoritativeProviders);
908
+ }
883
909
  const resolvedDefaults = this.#mergeResolvedModels(
884
910
  this.#mergeResolvedModels(builtInModels, cachedStandardModels),
885
911
  cachedDiscoveries,
@@ -1229,7 +1255,10 @@ export class ModelRegistry {
1229
1255
  ),
1230
1256
  ),
1231
1257
  );
1232
- const resolved = this.#mergeResolvedModels(this.#models, discoveredModels);
1258
+ const authoritativeProviders = providersWithAuthoritativeProjectCatalog(discoveredModels);
1259
+ const baseModels =
1260
+ authoritativeProviders.size > 0 ? dropProviderModels(this.#models, authoritativeProviders) : this.#models;
1261
+ const resolved = this.#mergeResolvedModels(baseModels, discoveredModels);
1233
1262
  const withConfigModels = this.#mergeCustomModels(resolved, this.#customModelOverlays);
1234
1263
  // Merge runtime extension models so they survive online discovery completion
1235
1264
  const combined = this.#mergeCustomModels(withConfigModels, this.#runtimeModelOverlays);
@@ -13,7 +13,7 @@ import {
13
13
  TabBar,
14
14
  Text,
15
15
  } from "@oh-my-pi/pi-tui";
16
- import { type SettingPath, settings } from "../../config/settings";
16
+ import { getDefault, type SettingPath, settings } from "../../config/settings";
17
17
  import type {
18
18
  SettingTab,
19
19
  StatusLinePreset,
@@ -294,6 +294,7 @@ export class SettingsSelectorComponent extends Container {
294
294
  }
295
295
 
296
296
  const currentValue = this.#getCurrentValue(def);
297
+ const changed = this.#isChanged(def, currentValue);
297
298
 
298
299
  switch (def.type) {
299
300
  case "boolean":
@@ -303,6 +304,7 @@ export class SettingsSelectorComponent extends Container {
303
304
  description: def.description,
304
305
  currentValue: currentValue ? "true" : "false",
305
306
  values: ["true", "false"],
307
+ changed,
306
308
  };
307
309
 
308
310
  case "enum":
@@ -312,6 +314,7 @@ export class SettingsSelectorComponent extends Container {
312
314
  description: def.description,
313
315
  currentValue: currentValue as string,
314
316
  values: [...def.values],
317
+ changed,
315
318
  };
316
319
 
317
320
  case "submenu":
@@ -321,6 +324,7 @@ export class SettingsSelectorComponent extends Container {
321
324
  description: def.description,
322
325
  currentValue: this.#getSubmenuCurrentValue(def.path, currentValue),
323
326
  submenu: (cv, done) => this.#createSubmenu(def, cv, done),
327
+ changed,
324
328
  };
325
329
 
326
330
  case "text":
@@ -330,6 +334,7 @@ export class SettingsSelectorComponent extends Container {
330
334
  description: def.description,
331
335
  currentValue: (currentValue as string) ?? "",
332
336
  submenu: (cv, done) => this.#createTextInput(def, cv, done),
337
+ changed,
333
338
  };
334
339
  }
335
340
  }
@@ -341,6 +346,10 @@ export class SettingsSelectorComponent extends Container {
341
346
  return settings.get(def.path);
342
347
  }
343
348
 
349
+ #isChanged(def: SettingDef, currentValue: unknown): boolean {
350
+ return !Object.is(currentValue, getDefault(def.path));
351
+ }
352
+
344
353
  #getSubmenuCurrentValue(path: SettingPath, value: unknown): string {
345
354
  const rawValue = String(value ?? "");
346
355
  if (path === "compaction.thresholdPercent" && (rawValue === "-1" || rawValue === "")) {
@@ -2404,8 +2404,10 @@ export function getEditorTheme(): EditorTheme {
2404
2404
 
2405
2405
  export function getSettingsListTheme(): import("@oh-my-pi/pi-tui").SettingsListTheme {
2406
2406
  return {
2407
- label: (text: string, selected: boolean) => (selected ? theme.fg("accent", text) : text),
2408
- value: (text: string, selected: boolean) => (selected ? theme.fg("accent", text) : theme.fg("muted", text)),
2407
+ label: (text: string, selected: boolean, changed: boolean) =>
2408
+ changed ? theme.fg("statusLineGitDirty", text) : selected ? theme.fg("accent", text) : text,
2409
+ value: (text: string, selected: boolean, changed: boolean) =>
2410
+ selected ? theme.fg("accent", text) : changed ? theme.fg("statusLineGitDirty", text) : theme.fg("muted", text),
2409
2411
  description: (text: string) => theme.fg("dim", text),
2410
2412
  cursor: theme.fg("accent", `${theme.nav.cursor} `),
2411
2413
  hint: (text: string) => theme.fg("dim", text),
package/src/sdk.ts CHANGED
@@ -1658,9 +1658,22 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1658
1658
  };
1659
1659
 
1660
1660
  const toolNamesFromRegistry = Array.from(toolRegistry.keys());
1661
- const requestedToolNames =
1662
- (options.toolNames ? [...new Set(options.toolNames.map(name => name.toLowerCase()))] : undefined) ??
1663
- toolNamesFromRegistry;
1661
+ const explicitlyRequestedToolNames = options.toolNames
1662
+ ? [...new Set(options.toolNames.map(name => name.toLowerCase()))]
1663
+ : undefined;
1664
+ // When `requireYieldTool` is set, the subagent's prompts and idle-reminders demand a
1665
+ // `yield` call to terminate. The tool registry already includes `yield` (see
1666
+ // `createTools`), but an explicit `toolNames` list would otherwise drop it from the
1667
+ // active set — leaving the model unable to satisfy the contract. Mirror the same
1668
+ // invariant `parseAgentFields` enforces on frontmatter `tools`.
1669
+ if (
1670
+ options.requireYieldTool === true &&
1671
+ explicitlyRequestedToolNames &&
1672
+ !explicitlyRequestedToolNames.includes("yield")
1673
+ ) {
1674
+ explicitlyRequestedToolNames.push("yield");
1675
+ }
1676
+ const requestedToolNames = explicitlyRequestedToolNames ?? toolNamesFromRegistry;
1664
1677
  const normalizedRequested = requestedToolNames.filter(name => toolRegistry.has(name));
1665
1678
  const requestedToolNameSet = new Set(normalizedRequested);
1666
1679
  // Effective discovery mode: tools.discoveryMode takes precedence; mcp.discoveryMode is back-compat alias.
package/src/tools/gh.ts CHANGED
@@ -1748,9 +1748,13 @@ async function fetchRunsForCommit(
1748
1748
  cwd: string,
1749
1749
  repo: string,
1750
1750
  headSha: string,
1751
- branch: string | undefined,
1752
1751
  signal?: AbortSignal,
1753
1752
  ): Promise<GhRunSnapshot[]> {
1753
+ // Filter only by `head_sha`. The SHA uniquely identifies the commit, so
1754
+ // adding the GitHub `branch=` filter would wrongly exclude workflow runs
1755
+ // whose `head_branch` is not the local checkout — e.g. tag-push triggered
1756
+ // release workflows (`head_branch=v1.2.3`) or PR-triggered runs
1757
+ // (`head_branch=<pr head>`). See coding-agent issue tracker for details.
1754
1758
  const response = await git.github.json<GhActionsRunListResponse>(
1755
1759
  cwd,
1756
1760
  [
@@ -1762,7 +1766,6 @@ async function fetchRunsForCommit(
1762
1766
  `head_sha=${headSha}`,
1763
1767
  "-F",
1764
1768
  `per_page=${RUN_JOBS_PAGE_SIZE}`,
1765
- ...(branch ? ["-F", `branch=${branch}`] : []),
1766
1769
  ],
1767
1770
  signal,
1768
1771
  { repoProvided: true },
@@ -3406,7 +3409,7 @@ async function executeRunWatch(
3406
3409
  throwIfAborted(signal);
3407
3410
  pollCount += 1;
3408
3411
 
3409
- let runs = await fetchRunsForCommit(session.cwd, repo, headSha, branch, signal);
3412
+ let runs = await fetchRunsForCommit(session.cwd, repo, headSha, signal);
3410
3413
  const details = buildCommitRunWatchDetails(repo, headSha, branch, runs, {
3411
3414
  state: "watching",
3412
3415
  pollCount,
@@ -3434,7 +3437,7 @@ async function executeRunWatch(
3434
3437
  }),
3435
3438
  });
3436
3439
  await scheduler.wait(graceSeconds * 1000, { signal });
3437
- runs = await fetchRunsForCommit(session.cwd, repo, headSha, branch, signal);
3440
+ runs = await fetchRunsForCommit(session.cwd, repo, headSha, signal);
3438
3441
  }
3439
3442
 
3440
3443
  const failedJobLogs = await fetchFailedJobLogs(