@oh-my-pi/pi-coding-agent 14.1.2 → 14.2.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.
Files changed (130) hide show
  1. package/CHANGELOG.md +47 -2
  2. package/package.json +8 -8
  3. package/scripts/build-binary.ts +61 -0
  4. package/src/autoresearch/helpers.ts +10 -0
  5. package/src/autoresearch/index.ts +1 -11
  6. package/src/autoresearch/tools/init-experiment.ts +1 -10
  7. package/src/autoresearch/tools/log-experiment.ts +1 -11
  8. package/src/autoresearch/tools/run-experiment.ts +1 -10
  9. package/src/bun-imports.d.ts +6 -0
  10. package/src/cli/plugin-cli.ts +23 -45
  11. package/src/commit/agentic/tools/propose-commit.ts +1 -14
  12. package/src/commit/agentic/tools/split-commit.ts +1 -15
  13. package/src/commit/utils.ts +15 -1
  14. package/src/config/model-registry.ts +3 -3
  15. package/src/config/prompt-templates.ts +4 -12
  16. package/src/config/settings-schema.ts +27 -2
  17. package/src/config/settings.ts +1 -1
  18. package/src/dap/session.ts +8 -2
  19. package/src/discovery/claude-plugins.ts +61 -6
  20. package/src/discovery/codex.ts +2 -15
  21. package/src/discovery/gemini.ts +2 -15
  22. package/src/discovery/helpers.ts +40 -1
  23. package/src/discovery/opencode.ts +2 -15
  24. package/src/edit/apply-patch/index.ts +87 -0
  25. package/src/edit/apply-patch/parser.ts +174 -0
  26. package/src/edit/diff.ts +3 -14
  27. package/src/edit/index.ts +67 -3
  28. package/src/edit/modes/apply-patch.lark +19 -0
  29. package/src/edit/modes/apply-patch.ts +63 -0
  30. package/src/edit/modes/chunk.ts +6 -2
  31. package/src/edit/modes/hashline.ts +3 -3
  32. package/src/edit/modes/replace.ts +2 -13
  33. package/src/edit/read-file.ts +18 -0
  34. package/src/edit/renderer.ts +61 -33
  35. package/src/extensibility/extensions/compact-handler.ts +40 -0
  36. package/src/extensibility/extensions/runner.ts +11 -29
  37. package/src/extensibility/utils.ts +7 -1
  38. package/src/internal-urls/docs-index.generated.ts +9 -2
  39. package/src/lsp/client.ts +14 -5
  40. package/src/lsp/index.ts +53 -10
  41. package/src/lsp/render.ts +14 -2
  42. package/src/lsp/types.ts +2 -0
  43. package/src/main.ts +1 -0
  44. package/src/mcp/manager.ts +29 -48
  45. package/src/memories/index.ts +7 -1
  46. package/src/modes/acp/acp-agent.ts +3 -16
  47. package/src/modes/components/model-selector.ts +15 -24
  48. package/src/modes/components/plugin-settings.ts +16 -5
  49. package/src/modes/components/read-tool-group.ts +92 -9
  50. package/src/modes/components/settings-defs.ts +18 -0
  51. package/src/modes/components/settings-selector.ts +2 -6
  52. package/src/modes/components/tool-execution.ts +61 -28
  53. package/src/modes/controllers/event-controller.ts +3 -1
  54. package/src/modes/controllers/extension-ui-controller.ts +99 -150
  55. package/src/modes/controllers/selector-controller.ts +3 -12
  56. package/src/modes/interactive-mode.ts +4 -2
  57. package/src/modes/print-mode.ts +4 -22
  58. package/src/modes/rpc/rpc-mode.ts +18 -38
  59. package/src/modes/shared.ts +10 -1
  60. package/src/modes/utils/ui-helpers.ts +6 -2
  61. package/src/plan-mode/approved-plan.ts +5 -4
  62. package/src/prompts/system/subagent-system-prompt.md +4 -4
  63. package/src/prompts/system/subagent-user-prompt.md +2 -2
  64. package/src/prompts/system/system-prompt.md +208 -243
  65. package/src/prompts/tools/apply-patch.md +67 -0
  66. package/src/prompts/tools/ast-edit.md +18 -23
  67. package/src/prompts/tools/ast-grep.md +25 -32
  68. package/src/prompts/tools/bash.md +11 -23
  69. package/src/prompts/tools/debug.md +8 -22
  70. package/src/prompts/tools/find.md +0 -4
  71. package/src/prompts/tools/grep.md +3 -5
  72. package/src/prompts/tools/hashline.md +16 -10
  73. package/src/prompts/tools/python.md +10 -14
  74. package/src/prompts/tools/read.md +17 -24
  75. package/src/prompts/tools/task.md +57 -21
  76. package/src/prompts/tools/todo-write.md +45 -67
  77. package/src/session/agent-session.ts +4 -4
  78. package/src/session/session-manager.ts +15 -7
  79. package/src/session/streaming-output.ts +24 -0
  80. package/src/slash-commands/builtin-registry.ts +3 -14
  81. package/src/task/executor.ts +13 -34
  82. package/src/task/index.ts +82 -18
  83. package/src/task/simple-mode.ts +27 -0
  84. package/src/task/template.ts +17 -3
  85. package/src/task/types.ts +77 -30
  86. package/src/tools/ask.ts +2 -4
  87. package/src/tools/ast-edit.ts +41 -17
  88. package/src/tools/ast-grep.ts +8 -27
  89. package/src/tools/bash-skill-urls.ts +9 -7
  90. package/src/tools/bash.ts +66 -24
  91. package/src/tools/browser.ts +1 -1
  92. package/src/tools/fetch.ts +1 -14
  93. package/src/tools/file-recorder.ts +35 -0
  94. package/src/tools/find.ts +25 -29
  95. package/src/tools/gh-format.ts +12 -0
  96. package/src/tools/gh-renderer.ts +1 -8
  97. package/src/tools/gh.ts +6 -13
  98. package/src/tools/grep.ts +103 -59
  99. package/src/tools/jtd-to-json-schema.ts +16 -0
  100. package/src/tools/match-line-format.ts +20 -0
  101. package/src/tools/path-utils.ts +61 -5
  102. package/src/tools/plan-mode-guard.ts +6 -5
  103. package/src/tools/python.ts +1 -1
  104. package/src/tools/read.ts +1 -1
  105. package/src/tools/render-utils.ts +38 -6
  106. package/src/tools/renderers.ts +1 -0
  107. package/src/tools/resolve.ts +12 -3
  108. package/src/tools/ssh.ts +3 -11
  109. package/src/tools/submit-result.ts +1 -13
  110. package/src/tools/todo-write.ts +137 -103
  111. package/src/tools/vim.ts +1 -1
  112. package/src/tools/write.ts +2 -23
  113. package/src/tui/code-cell.ts +12 -7
  114. package/src/utils/edit-mode.ts +3 -2
  115. package/src/utils/git.ts +1 -1
  116. package/src/vim/engine.ts +41 -58
  117. package/src/web/scrapers/crates-io.ts +1 -14
  118. package/src/web/scrapers/types.ts +13 -0
  119. package/src/web/search/providers/base.ts +13 -0
  120. package/src/web/search/providers/brave.ts +2 -5
  121. package/src/web/search/providers/codex.ts +20 -24
  122. package/src/web/search/providers/gemini.ts +39 -1
  123. package/src/web/search/providers/jina.ts +2 -5
  124. package/src/web/search/providers/kagi.ts +3 -8
  125. package/src/web/search/providers/kimi.ts +3 -7
  126. package/src/web/search/providers/parallel.ts +3 -8
  127. package/src/web/search/providers/synthetic.ts +3 -7
  128. package/src/web/search/providers/tavily.ts +15 -11
  129. package/src/web/search/providers/utils.ts +36 -0
  130. package/src/web/search/providers/zai.ts +3 -7
package/CHANGELOG.md CHANGED
@@ -2,6 +2,52 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [14.2.0] - 2026-04-23
6
+
7
+ ### Added
8
+
9
+ - Added an `apply_patch` edit mode that accepts Codex `*** Begin Patch` envelopes, shares patch-mode execution and diagnostics, and renders streaming per-file diffs in the TUI.
10
+
11
+ ### Changed
12
+
13
+ - Changed Spark models to default to `apply_patch` edit mode instead of `replace`.
14
+ - Tightened the contract for `SearchParams.recency` in `web/search/providers/base.ts`: providers MUST interpret recency as a pure time filter and MUST NOT use it as an implicit signal to change topic scope, content domain, or ranking strategy.
15
+ - Inline read tool previews are now optional via `read.toolResultPreview` and default to off
16
+
17
+ ### Fixed
18
+
19
+ - Fixed `apply_patch` streaming previews to avoid showing the missing `*** End Patch` parse error while the patch body is still arriving.
20
+ - Fixed diagnostics rendering to replace tabs before TUI output, preventing compiler messages from breaking tree alignment.
21
+ - Fixed compiled `omp` binaries to ignore project-local `bunfig.toml` and `.env` autoloading at startup, preventing unrelated project config from crashing or preloading code into the CLI
22
+ - Fixed edit tool diff and replace operations to report missing-file failures as `File not found: <path>` errors instead of raw filesystem ENOENT errors
23
+ - Fixed `local://` URL path leak on Linux where `//` collapsing to `/` produced `local:/path` forms that bypassed the internal protocol handler and leaked as filesystem paths, breaking plan mode file resolution
24
+ - Fixed Darwin compiled binaries failing to start under Bun 1.3.12 by ad-hoc signing local and release binary builds after applying Bun's no-codesign workaround ([#754](https://github.com/can1357/oh-my-pi/issues/754))
25
+ - Fixed Tavily web search silently returning off-topic news articles when `--recency` was set. The provider was unconditionally coupling `topic: "news"` to recency, which scoped Tavily's index to news publications and excluded documentation, release notes, GitHub, and all non-news technical content. Technical queries with `--recency` now return the correct corpus.
26
+ - Fixed status-line sanitization to strip OSC, DCS, PM, APC, and 8-bit CSI escape sequences instead of leaving payload fragments in the UI
27
+ - Fixed inline read tool previews to avoid rendering duplicate summary rows above the same code cell
28
+
29
+ ## [14.1.3] - 2026-04-17
30
+
31
+ ### Breaking Changes
32
+
33
+ - Replaced the legacy `todo_write` `ops`-based API (`replace`, `update`, `add_task`, and `remove_task`) with direct top-level fields, requiring migration of any callers using the old request shape
34
+ - Removed in-place updates to existing task `content`, `details`, and `notes` via `todo_write`; note changes now append through `add_notes`
35
+ - Phased task definitions in `todo_write` now reject `notes` on initial creation, so notes must be added later with `add_notes`
36
+
37
+ ### Added
38
+
39
+ - Added `complete`, `start`, `abandon`, `remove`, `add_notes`, and `add_tasks` parameters to `todo_write` so callers can complete, jump to, drop, and annotate tasks without op wrappers
40
+ - Added direct `add_phase` support as a top-level argument for inserting a new phase in `todo_write`
41
+ - Added `task.simple` with `default`, `schema-free`, and `independent` modes so the task tool can disable task-call `schema` and shared `context` inputs while preserving agent-defined and inherited subagent schemas
42
+
43
+ ### Changed
44
+
45
+ - Changed `add_tasks` to insert tasks by phase name or ID and allow multiple tasks to be added in one call
46
+
47
+ ### Fixed
48
+
49
+ - Fixed task calls in `schema-free` and `independent` modes to return clear mode-specific errors when disallowed `context` or `schema` inputs are provided
50
+ - Fixed newly generated session IDs to use UUIDv7 for new, forked, and branched sessions while preserving resumed session IDs
5
51
  ## [14.1.1] - 2026-04-14
6
52
 
7
53
  ### Breaking Changes
@@ -183,7 +229,6 @@
183
229
 
184
230
  - Fixed typo in system prompt: 'backwards compatibiltity' → 'backwards compatibility'
185
231
 
186
-
187
232
  ## [14.0.3] - 2026-04-09
188
233
 
189
234
  ### Fixed
@@ -7082,4 +7127,4 @@ Initial public release.
7082
7127
  - Git branch display in footer
7083
7128
  - Message queueing during streaming responses
7084
7129
  - OAuth integration for Gmail and Google Calendar access
7085
- - HTML export with syntax highlighting and collapsible sections
7130
+ - HTML export with syntax highlighting and collapsible sections
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.1.2",
4
+ "version": "14.2.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",
@@ -31,7 +31,7 @@
31
31
  "omp": "src/cli.ts"
32
32
  },
33
33
  "scripts": {
34
- "build": "bun --cwd=../stats scripts/generate-client-bundle.ts --generate && bun --cwd=../natives run embed:native && bun build --compile --define PI_COMPILED=true --external mupdf --root ../.. ./src/cli.ts --outfile dist/omp && bun --cwd=../natives run embed:native --reset && bun --cwd=../stats scripts/generate-client-bundle.ts --reset",
34
+ "build": "bun scripts/build-binary.ts",
35
35
  "check": "biome check . && bun run check:types",
36
36
  "check:types": "tsgo -p tsconfig.json --noEmit",
37
37
  "lint": "biome lint .",
@@ -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.1.2",
50
- "@oh-my-pi/pi-agent-core": "14.1.2",
51
- "@oh-my-pi/pi-ai": "14.1.2",
52
- "@oh-my-pi/pi-natives": "14.1.2",
53
- "@oh-my-pi/pi-tui": "14.1.2",
54
- "@oh-my-pi/pi-utils": "14.1.2",
49
+ "@oh-my-pi/omp-stats": "14.2.1",
50
+ "@oh-my-pi/pi-agent-core": "14.2.1",
51
+ "@oh-my-pi/pi-ai": "14.2.1",
52
+ "@oh-my-pi/pi-natives": "14.2.1",
53
+ "@oh-my-pi/pi-tui": "14.2.1",
54
+ "@oh-my-pi/pi-utils": "14.2.1",
55
55
  "@sinclair/typebox": "^0.34",
56
56
  "@xterm/headless": "^6.0",
57
57
  "ajv": "^8.18",
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import * as path from "node:path";
4
+
5
+ const packageDir = path.join(import.meta.dir, "..");
6
+ const outputPath = path.join(packageDir, "dist", "omp");
7
+
8
+ function shouldAdhocSignDarwinBinary(): boolean {
9
+ return process.platform === "darwin";
10
+ }
11
+
12
+ async function runCommand(command: string[], env: NodeJS.ProcessEnv = Bun.env): Promise<void> {
13
+ const proc = Bun.spawn(command, {
14
+ cwd: packageDir,
15
+ env,
16
+ stdout: "inherit",
17
+ stderr: "inherit",
18
+ });
19
+ const exitCode = await proc.exited;
20
+ if (exitCode !== 0) {
21
+ throw new Error(`Command failed with exit code ${exitCode}: ${command.join(" ")}`);
22
+ }
23
+ }
24
+
25
+ async function main(): Promise<void> {
26
+ await runCommand(["bun", "--cwd=../stats", "scripts/generate-client-bundle.ts", "--generate"]);
27
+ try {
28
+ await runCommand(["bun", "--cwd=../natives", "run", "embed:native"]);
29
+ try {
30
+ const buildEnv = shouldAdhocSignDarwinBinary() ? { ...Bun.env, BUN_NO_CODESIGN_MACHO_BINARY: "1" } : Bun.env;
31
+ await runCommand(
32
+ [
33
+ "bun",
34
+ "build",
35
+ "--compile",
36
+ "--define",
37
+ "PI_COMPILED=true",
38
+ "--external",
39
+ "mupdf",
40
+ "--root",
41
+ "../..",
42
+ "./src/cli.ts",
43
+ "--outfile",
44
+ "dist/omp",
45
+ ],
46
+ buildEnv,
47
+ );
48
+
49
+ // Bun 1.3.12 emits a truncated Mach-O signature on darwin builds.
50
+ if (shouldAdhocSignDarwinBinary()) {
51
+ await runCommand(["codesign", "--force", "--sign", "-", outputPath]);
52
+ }
53
+ } finally {
54
+ await runCommand(["bun", "--cwd=../natives", "run", "embed:native", "--reset"]);
55
+ }
56
+ } finally {
57
+ await runCommand(["bun", "--cwd=../stats", "scripts/generate-client-bundle.ts", "--reset"]);
58
+ }
59
+ }
60
+
61
+ await main();
@@ -505,3 +505,13 @@ function clonePendingAsiValue(value: unknown): ASIValue | undefined {
505
505
  }
506
506
  return undefined;
507
507
  }
508
+
509
+ export function collectLoggedRunNumbers(results: readonly { runNumber: number | null }[]): Set<number> {
510
+ const runNumbers = new Set<number>();
511
+ for (const result of results) {
512
+ if (result.runNumber !== null) {
513
+ runNumbers.add(result.runNumber);
514
+ }
515
+ }
516
+ return runNumbers;
517
+ }
@@ -8,6 +8,7 @@ import { pathMatchesContractPath } from "./contract";
8
8
  import { createDashboardController } from "./dashboard";
9
9
  import { ensureAutoresearchBranch } from "./git";
10
10
  import {
11
+ collectLoggedRunNumbers,
11
12
  formatNum,
12
13
  isAutoresearchCommittableFile,
13
14
  isAutoresearchLocalStatePath,
@@ -485,17 +486,6 @@ function findBestResult(runtime: AutoresearchRuntime): ExperimentResult | null {
485
486
  }
486
487
  return best;
487
488
  }
488
-
489
- function collectLoggedRunNumbers(results: ExperimentResult[]): Set<number> {
490
- const runNumbers = new Set<number>();
491
- for (const result of results) {
492
- if (result.runNumber !== null) {
493
- runNumbers.add(result.runNumber);
494
- }
495
- }
496
- return runNumbers;
497
- }
498
-
499
489
  function summaryToChecks(summary: PendingRunSummary | null): ChecksResult | null {
500
490
  if (!summary || summary.checksPass === null) {
501
491
  return null;
@@ -15,6 +15,7 @@ import {
15
15
  } from "../contract";
16
16
  import {
17
17
  abandonUnloggedAutoresearchRuns,
18
+ collectLoggedRunNumbers,
18
19
  isAutoresearchShCommand,
19
20
  readMaxExperiments,
20
21
  readPendingRunSummary,
@@ -383,13 +384,3 @@ export function createInitExperimentTool(
383
384
  function renderInitCall(name: string, theme: Theme): string {
384
385
  return `${theme.fg("toolTitle", theme.bold("init_experiment"))} ${theme.fg("accent", truncateToWidth(replaceTabs(name), 100))}`;
385
386
  }
386
-
387
- function collectLoggedRunNumbers(results: ExperimentState["results"]): Set<number> {
388
- const runNumbers = new Set<number>();
389
- for (const result of results) {
390
- if (result.runNumber !== null) {
391
- runNumbers.add(result.runNumber);
392
- }
393
- }
394
- return runNumbers;
395
- }
@@ -12,6 +12,7 @@ import { applyAutoresearchContractToExperimentState } from "../apply-contract-to
12
12
  import { loadAutoresearchScriptSnapshot, pathMatchesContractPath, readAutoresearchContract } from "../contract";
13
13
  import { computeRunModifiedPaths, getCurrentAutoresearchBranch, parseWorkDirDirtyPathsWithStatus } from "../git";
14
14
  import {
15
+ collectLoggedRunNumbers,
15
16
  formatNum,
16
17
  inferMetricUnitFromName,
17
18
  isAutoresearchCommittableFile,
@@ -472,17 +473,6 @@ function persistRun(workDir: string, experiment: ExperimentResult): void {
472
473
  const jsonlPath = path.join(workDir, "autoresearch.jsonl");
473
474
  fs.appendFileSync(jsonlPath, `${JSON.stringify(entry)}\n`);
474
475
  }
475
-
476
- function collectLoggedRunNumbers(results: ExperimentResult[]): Set<number> {
477
- const runNumbers = new Set<number>();
478
- for (const result of results) {
479
- if (result.runNumber !== null) {
480
- runNumbers.add(result.runNumber);
481
- }
482
- }
483
- return runNumbers;
484
- }
485
-
486
476
  function validateObservedStatus(
487
477
  status: ExperimentResult["status"],
488
478
  pendingRun: { checksPass: boolean | null; passed: boolean },
@@ -11,6 +11,7 @@ import { replaceTabs, shortenPath, truncateToWidth } from "../../tools/render-ut
11
11
  import * as git from "../../utils/git";
12
12
  import { parseWorkDirDirtyPaths } from "../git";
13
13
  import {
14
+ collectLoggedRunNumbers,
14
15
  EXPERIMENT_MAX_BYTES,
15
16
  EXPERIMENT_MAX_LINES,
16
17
  formatElapsed,
@@ -666,13 +667,3 @@ function isProgressDetails(value: unknown): value is RunExperimentProgressDetail
666
667
  if (typeof value !== "object" || value === null) return false;
667
668
  return "phase" in value && value.phase === "running";
668
669
  }
669
-
670
- function collectLoggedRunNumbers(results: Array<{ runNumber: number | null }>): Set<number> {
671
- const runNumbers = new Set<number>();
672
- for (const result of results) {
673
- if (result.runNumber !== null) {
674
- runNumbers.add(result.runNumber);
675
- }
676
- }
677
- return runNumbers;
678
- }
@@ -20,3 +20,9 @@ declare module "*.py" {
20
20
  const content: string;
21
21
  export default content;
22
22
  }
23
+
24
+ // Lark grammar files imported as text
25
+ declare module "*.lark" {
26
+ const content: string;
27
+ export default content;
28
+ }
@@ -831,42 +831,7 @@ async function handleEnable(
831
831
  plugins: string[],
832
832
  flags: { json?: boolean; scope?: "user" | "project" },
833
833
  ): Promise<void> {
834
- if (plugins.length === 0) {
835
- console.error(chalk.red(`Usage: ${APP_NAME} plugin enable <plugin> ...`));
836
- process.exit(1);
837
- }
838
-
839
- const mktMgr = await makeMarketplaceManager();
840
- const installedPlugins = new Set((await mktMgr.listInstalledPlugins()).map(p => p.id));
841
-
842
- for (const name of plugins) {
843
- if (installedPlugins.has(name)) {
844
- try {
845
- await mktMgr.setPluginEnabled(name, true, flags.scope);
846
- if (flags.json) {
847
- console.log(JSON.stringify({ enabled: name }));
848
- } else {
849
- console.log(chalk.green(`${theme.status.success} Enabled ${name}`));
850
- }
851
- } catch (err) {
852
- console.error(chalk.red(`${theme.status.error} Failed to enable ${name}: ${err}`));
853
- process.exit(1);
854
- }
855
- continue;
856
- }
857
-
858
- try {
859
- await manager.setEnabled(name, true);
860
- if (flags.json) {
861
- console.log(JSON.stringify({ enabled: name }));
862
- } else {
863
- console.log(chalk.green(`${theme.status.success} Enabled ${name}`));
864
- }
865
- } catch (err) {
866
- console.error(chalk.red(`${theme.status.error} Failed to enable ${name}: ${err}`));
867
- process.exit(1);
868
- }
869
- }
834
+ return handleSetEnabled(manager, plugins, flags, true);
870
835
  }
871
836
 
872
837
  async function handleDisable(
@@ -874,8 +839,21 @@ async function handleDisable(
874
839
  plugins: string[],
875
840
  flags: { json?: boolean; scope?: "user" | "project" },
876
841
  ): Promise<void> {
842
+ return handleSetEnabled(manager, plugins, flags, false);
843
+ }
844
+
845
+ async function handleSetEnabled(
846
+ manager: PluginManager,
847
+ plugins: string[],
848
+ flags: { json?: boolean; scope?: "user" | "project" },
849
+ enabled: boolean,
850
+ ): Promise<void> {
851
+ const action = enabled ? "enable" : "disable";
852
+ const pastTense = enabled ? "Enabled" : "Disabled";
853
+ const jsonKey = enabled ? "enabled" : "disabled";
854
+
877
855
  if (plugins.length === 0) {
878
- console.error(chalk.red(`Usage: ${APP_NAME} plugin disable <plugin> ...`));
856
+ console.error(chalk.red(`Usage: ${APP_NAME} plugin ${action} <plugin> ...`));
879
857
  process.exit(1);
880
858
  }
881
859
 
@@ -885,28 +863,28 @@ async function handleDisable(
885
863
  for (const name of plugins) {
886
864
  if (installedPlugins.has(name)) {
887
865
  try {
888
- await mktMgr.setPluginEnabled(name, false, flags.scope);
866
+ await mktMgr.setPluginEnabled(name, enabled, flags.scope);
889
867
  if (flags.json) {
890
- console.log(JSON.stringify({ disabled: name }));
868
+ console.log(JSON.stringify({ [jsonKey]: name }));
891
869
  } else {
892
- console.log(chalk.green(`${theme.status.success} Disabled ${name}`));
870
+ console.log(chalk.green(`${theme.status.success} ${pastTense} ${name}`));
893
871
  }
894
872
  } catch (err) {
895
- console.error(chalk.red(`${theme.status.error} Failed to disable ${name}: ${err}`));
873
+ console.error(chalk.red(`${theme.status.error} Failed to ${action} ${name}: ${err}`));
896
874
  process.exit(1);
897
875
  }
898
876
  continue;
899
877
  }
900
878
 
901
879
  try {
902
- await manager.setEnabled(name, false);
880
+ await manager.setEnabled(name, enabled);
903
881
  if (flags.json) {
904
- console.log(JSON.stringify({ disabled: name }));
882
+ console.log(JSON.stringify({ [jsonKey]: name }));
905
883
  } else {
906
- console.log(chalk.green(`${theme.status.success} Disabled ${name}`));
884
+ console.log(chalk.green(`${theme.status.success} ${pastTense} ${name}`));
907
885
  }
908
886
  } catch (err) {
909
- console.error(chalk.red(`${theme.status.error} Failed to disable ${name}: ${err}`));
887
+ console.error(chalk.red(`${theme.status.error} Failed to ${action} ${name}: ${err}`));
910
888
  process.exit(1);
911
889
  }
912
890
  }
@@ -10,6 +10,7 @@ import {
10
10
  } from "../../../commit/agentic/validation";
11
11
  import { validateAnalysis } from "../../../commit/analysis/validation";
12
12
  import type { CommitType, ConventionalAnalysis, ConventionalDetail } from "../../../commit/types";
13
+ import { normalizeDetails } from "../../../commit/utils";
13
14
  import type { CustomTool } from "../../../extensibility/custom-tools/types";
14
15
  import * as git from "../../../utils/git";
15
16
  import { commitTypeSchema, detailSchema } from "./schemas.js";
@@ -35,20 +36,6 @@ interface ProposalResponse {
35
36
  };
36
37
  }
37
38
 
38
- function normalizeDetails(
39
- details: Array<{
40
- text: string;
41
- changelog_category?: ConventionalDetail["changelogCategory"];
42
- user_visible?: boolean;
43
- }>,
44
- ): ConventionalDetail[] {
45
- return details.map(detail => ({
46
- text: detail.text.trim(),
47
- changelogCategory: detail.user_visible ? detail.changelog_category : undefined,
48
- userVisible: detail.user_visible ?? false,
49
- }));
50
- }
51
-
52
39
  export function createProposeCommitTool(cwd: string, state: CommitAgentState): CustomTool<typeof proposeCommitSchema> {
53
40
  return {
54
41
  name: "propose_commit",
@@ -10,7 +10,7 @@ import {
10
10
  validateTypeConsistency,
11
11
  } from "../../../commit/agentic/validation";
12
12
  import { validateScope } from "../../../commit/analysis/validation";
13
- import type { ConventionalDetail } from "../../../commit/types";
13
+ import { normalizeDetails } from "../../../commit/utils";
14
14
  import type { CustomTool } from "../../../extensibility/custom-tools/types";
15
15
  import * as git from "../../../utils/git";
16
16
  import { commitTypeSchema, detailSchema } from "./schemas.js";
@@ -49,20 +49,6 @@ interface SplitCommitResponse {
49
49
  proposal?: SplitCommitPlan;
50
50
  }
51
51
 
52
- function normalizeDetails(
53
- details: Array<{
54
- text: string;
55
- changelog_category?: ConventionalDetail["changelogCategory"];
56
- user_visible?: boolean;
57
- }>,
58
- ): ConventionalDetail[] {
59
- return details.map(detail => ({
60
- text: detail.text.trim(),
61
- changelogCategory: detail.user_visible ? detail.changelog_category : undefined,
62
- userVisible: detail.user_visible ?? false,
63
- }));
64
- }
65
-
66
52
  export function createSplitCommitTool(
67
53
  cwd: string,
68
54
  state: CommitAgentState,
@@ -1,5 +1,5 @@
1
1
  import type { AssistantMessage, ToolCall } from "@oh-my-pi/pi-ai";
2
- import type { ChangelogCategory, ConventionalAnalysis } from "./types";
2
+ import type { ChangelogCategory, ConventionalAnalysis, ConventionalDetail } from "./types";
3
3
 
4
4
  export function extractToolCall(message: AssistantMessage, name: string): ToolCall | undefined {
5
5
  return message.content.find(content => content.type === "toolCall" && content.name === name) as ToolCall | undefined;
@@ -42,3 +42,17 @@ export function normalizeAnalysis(parsed: {
42
42
  issueRefs: parsed.issue_refs ?? [],
43
43
  };
44
44
  }
45
+
46
+ export function normalizeDetails(
47
+ details: Array<{
48
+ text: string;
49
+ changelog_category?: ConventionalDetail["changelogCategory"];
50
+ user_visible?: boolean;
51
+ }>,
52
+ ): ConventionalDetail[] {
53
+ return details.map(detail => ({
54
+ text: detail.text.trim(),
55
+ changelogCategory: detail.user_visible ? detail.changelog_category : undefined,
56
+ userVisible: detail.user_visible ?? false,
57
+ }));
58
+ }
@@ -1886,7 +1886,7 @@ export class ModelRegistry {
1886
1886
  * Get API key for a model.
1887
1887
  */
1888
1888
  async getApiKey(model: Model<Api>, sessionId?: string): Promise<string | undefined> {
1889
- if (this.#keylessProviders.has(model.provider)) {
1889
+ if (this.#keylessProviders.has(model.provider) && !this.authStorage.hasAuth(model.provider)) {
1890
1890
  return kNoAuth;
1891
1891
  }
1892
1892
  return this.authStorage.getApiKey(model.provider, sessionId, { baseUrl: model.baseUrl, modelId: model.id });
@@ -1896,14 +1896,14 @@ export class ModelRegistry {
1896
1896
  * Get API key for a provider (e.g., "openai").
1897
1897
  */
1898
1898
  async getApiKeyForProvider(provider: string, sessionId?: string, baseUrl?: string): Promise<string | undefined> {
1899
- if (this.#keylessProviders.has(provider)) {
1899
+ if (this.#keylessProviders.has(provider) && !this.authStorage.hasAuth(provider)) {
1900
1900
  return kNoAuth;
1901
1901
  }
1902
1902
  return this.authStorage.getApiKey(provider, sessionId, { baseUrl });
1903
1903
  }
1904
1904
 
1905
1905
  async #peekApiKeyForProvider(provider: string): Promise<string | undefined> {
1906
- if (this.#keylessProviders.has(provider)) {
1906
+ if (this.#keylessProviders.has(provider) && !this.authStorage.hasAuth(provider)) {
1907
1907
  return kNoAuth;
1908
1908
  }
1909
1909
  return this.authStorage.peekApiKey(provider);
@@ -31,18 +31,10 @@ prompt.registerHelper("jtdToTypeScript", (schema: unknown): string => {
31
31
  }
32
32
  });
33
33
 
34
- /**
35
- * Renders a section separator:
36
- *
37
- * ═══════════════════════════════
38
- * Name
39
- * ═══════════════════════════════
40
- */
41
- export function sectionSeparator(name: string): string {
42
- return `\n\n═══════════${name}═══════════\n`;
43
- }
44
-
45
- prompt.registerHelper("SECTION_SEPERATOR", (name: unknown): string => sectionSeparator(String(name)));
34
+ // `sectionSeparator` + SECTION_SEPARATOR helper live in pi-utils/prompt so every
35
+ // template consumer gets them registered without a coupling back to this module.
36
+ // Re-exported here for call sites that already reference the coding-agent path.
37
+ export { sectionSeparator } from "@oh-my-pi/pi-utils/prompt";
46
38
 
47
39
  function formatHashlineRef(lineNum: unknown, content: unknown): { num: number; text: string; ref: string } {
48
40
  const num = typeof lineNum === "number" ? lineNum : Number.parseInt(String(lineNum), 10);
@@ -1,4 +1,5 @@
1
1
  import { THINKING_EFFORTS } from "@oh-my-pi/pi-ai";
2
+ import { TASK_SIMPLE_MODES } from "../task/simple-mode";
2
3
 
3
4
  /** Unified settings schema - single source of truth for all settings.
4
5
  * Unified settings schema - single source of truth for all settings.
@@ -885,6 +886,8 @@ export const SETTINGS_SCHEMA = {
885
886
 
886
887
  "memories.rolloutPayloadPercent": { type: "number", default: 0.7 },
887
888
 
889
+ "memories.phase1InputTokenLimit": { type: "number", default: 4000 },
890
+
888
891
  "memories.fallbackTokenLimit": { type: "number", default: 16000 },
889
892
 
890
893
  "memories.summaryInjectionTokenLimit": { type: "number", default: 5000 },
@@ -952,12 +955,12 @@ export const SETTINGS_SCHEMA = {
952
955
  // Edit tool
953
956
  "edit.mode": {
954
957
  type: "enum",
955
- values: ["replace", "patch", "hashline", "chunk", "vim"] as const,
958
+ values: ["replace", "patch", "hashline", "chunk", "vim", "apply_patch"] as const,
956
959
  default: "hashline",
957
960
  ui: {
958
961
  tab: "editing",
959
962
  label: "Edit Mode",
960
- description: "Select the edit tool variant (replace, patch, hashline, chunk, or vim)",
963
+ description: "Select the edit tool variant (replace, patch, hashline, chunk, vim, or apply_patch)",
961
964
  },
962
965
  },
963
966
 
@@ -1033,6 +1036,16 @@ export const SETTINGS_SCHEMA = {
1033
1036
  },
1034
1037
  },
1035
1038
 
1039
+ "read.toolResultPreview": {
1040
+ type: "boolean",
1041
+ default: false,
1042
+ ui: {
1043
+ tab: "editing",
1044
+ label: "Inline Read Previews",
1045
+ description: "Render read tool results inline in the transcript instead of summary rows",
1046
+ },
1047
+ },
1048
+
1036
1049
  "read.prosechunks": {
1037
1050
  type: "boolean",
1038
1051
  default: false,
@@ -1506,6 +1519,18 @@ export const SETTINGS_SCHEMA = {
1506
1519
  },
1507
1520
  },
1508
1521
 
1522
+ "task.simple": {
1523
+ type: "enum",
1524
+ values: TASK_SIMPLE_MODES,
1525
+ default: "default",
1526
+ ui: {
1527
+ tab: "tasks",
1528
+ label: "Task Input Mode",
1529
+ description: "How much shared structure the task tool accepts (default, schema-free, or independent)",
1530
+ submenu: true,
1531
+ },
1532
+ },
1533
+
1509
1534
  "task.maxConcurrency": {
1510
1535
  type: "number",
1511
1536
  default: 32,
@@ -326,7 +326,7 @@ export class Settings {
326
326
 
327
327
  /**
328
328
  * Get the edit variant for a specific model.
329
- * Returns "patch", "replace", "hashline", "chunk", "vim", or null (use global default).
329
+ * Returns "patch", "replace", "hashline", "chunk", "vim", "apply_patch", or null (use global default).
330
330
  */
331
331
  getEditVariantForModel(model: string | undefined): EditMode | null {
332
332
  if (!model) return null;
@@ -1075,11 +1075,17 @@ export class DapSessionManager {
1075
1075
  * MUST be called before the command that triggers the event.
1076
1076
  */
1077
1077
  #prepareStopOutcome(session: DapSession, signal?: AbortSignal, timeoutMs: number = 30_000): Promise<unknown> {
1078
- return Promise.race([
1078
+ const promises = [
1079
1079
  session.client.waitForEvent("stopped", undefined, signal, timeoutMs),
1080
1080
  session.client.waitForEvent("terminated", undefined, signal, timeoutMs),
1081
1081
  session.client.waitForEvent("exited", undefined, signal, timeoutMs),
1082
- ]);
1082
+ ];
1083
+ // Promise.race leaves the losing waiters pending; their timeouts would
1084
+ // otherwise surface as unhandled rejections once they fire.
1085
+ for (const p of promises) {
1086
+ p.catch(() => {});
1087
+ }
1088
+ return Promise.race(promises);
1083
1089
  }
1084
1090
 
1085
1091
  /**