@oh-my-pi/pi-coding-agent 13.1.2 → 13.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 (235) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/package.json +7 -7
  3. package/scripts/format-prompts.ts +33 -14
  4. package/src/async/job-manager.ts +43 -1
  5. package/src/capability/index.ts +1 -2
  6. package/src/capability/tool.ts +1 -1
  7. package/src/cli/args.ts +1 -2
  8. package/src/cli/config-cli.ts +1 -1
  9. package/src/cli/file-processor.ts +1 -2
  10. package/src/cli/grep-cli.ts +1 -1
  11. package/src/cli/jupyter-cli.ts +1 -1
  12. package/src/cli/plugin-cli.ts +1 -1
  13. package/src/cli/setup-cli.ts +1 -1
  14. package/src/cli/shell-cli.ts +1 -1
  15. package/src/cli/ssh-cli.ts +1 -1
  16. package/src/cli/stats-cli.ts +1 -2
  17. package/src/cli/update-cli.ts +1 -2
  18. package/src/cli/web-search-cli.ts +1 -1
  19. package/src/cli.ts +1 -1
  20. package/src/commands/launch.ts +2 -1
  21. package/src/commit/agentic/agent.ts +2 -1
  22. package/src/commit/agentic/index.ts +1 -2
  23. package/src/commit/agentic/prompts/system.md +3 -3
  24. package/src/commit/agentic/tools/propose-changelog.ts +30 -19
  25. package/src/commit/changelog/generate.ts +16 -6
  26. package/src/commit/changelog/index.ts +2 -1
  27. package/src/commit/pipeline.ts +1 -2
  28. package/src/commit/prompts/reduce-system.md +1 -1
  29. package/src/commit/types.ts +10 -1
  30. package/src/config/keybindings.ts +1 -2
  31. package/src/config/model-registry.ts +1 -1
  32. package/src/config/prompt-templates.ts +14 -2
  33. package/src/config/settings-schema.ts +10 -0
  34. package/src/config/settings.ts +25 -2
  35. package/src/config.ts +1 -2
  36. package/src/debug/index.ts +1 -1
  37. package/src/debug/report-bundle.ts +1 -2
  38. package/src/debug/system-info.ts +1 -2
  39. package/src/discovery/agents.ts +2 -2
  40. package/src/discovery/builtin.ts +24 -14
  41. package/src/discovery/claude-plugins.ts +3 -2
  42. package/src/discovery/claude.ts +9 -9
  43. package/src/discovery/codex.ts +3 -3
  44. package/src/discovery/cursor.ts +5 -4
  45. package/src/discovery/gemini.ts +5 -5
  46. package/src/discovery/helpers.ts +47 -69
  47. package/src/discovery/mcp-json.ts +3 -3
  48. package/src/discovery/opencode.ts +7 -8
  49. package/src/discovery/ssh.ts +3 -3
  50. package/src/discovery/vscode.ts +3 -2
  51. package/src/discovery/windsurf.ts +3 -2
  52. package/src/exa/company.ts +1 -1
  53. package/src/exa/factory.ts +1 -6
  54. package/src/exa/linkedin.ts +1 -1
  55. package/src/exa/mcp-client.ts +19 -8
  56. package/src/exa/search.ts +2 -2
  57. package/src/exa/types.ts +3 -3
  58. package/src/exec/bash-executor.ts +2 -1
  59. package/src/exec/non-interactive-env.ts +43 -0
  60. package/src/export/custom-share.ts +1 -1
  61. package/src/export/html/index.ts +1 -2
  62. package/src/extensibility/custom-commands/loader.ts +1 -2
  63. package/src/extensibility/plugins/installer.ts +1 -2
  64. package/src/extensibility/plugins/loader.ts +1 -2
  65. package/src/extensibility/plugins/manager.ts +3 -2
  66. package/src/extensibility/skills.ts +59 -115
  67. package/src/index.ts +1 -3
  68. package/src/internal-urls/docs-index.generated.ts +1 -1
  69. package/src/ipy/executor.ts +1 -2
  70. package/src/ipy/gateway-coordinator.ts +1 -2
  71. package/src/ipy/modules.ts +1 -1
  72. package/src/ipy/runtime.ts +1 -3
  73. package/src/main.ts +1 -2
  74. package/src/mcp/config.ts +1 -1
  75. package/src/mcp/transports/stdio.ts +1 -2
  76. package/src/memories/index.ts +1 -2
  77. package/src/modes/components/diff.ts +49 -19
  78. package/src/modes/components/extensions/extension-dashboard.ts +1 -1
  79. package/src/modes/components/extensions/inspector-panel.ts +8 -2
  80. package/src/modes/components/footer.ts +1 -2
  81. package/src/modes/components/status-line/segments.ts +1 -2
  82. package/src/modes/components/tool-execution.ts +3 -10
  83. package/src/modes/components/welcome.ts +1 -1
  84. package/src/modes/controllers/command-controller.ts +1 -2
  85. package/src/modes/controllers/mcp-command-controller.ts +1 -1
  86. package/src/modes/controllers/selector-controller.ts +1 -1
  87. package/src/modes/controllers/ssh-command-controller.ts +1 -1
  88. package/src/modes/interactive-mode.ts +2 -3
  89. package/src/modes/shared.ts +1 -2
  90. package/src/modes/theme/theme.ts +1 -2
  91. package/src/patch/index.ts +1 -25
  92. package/src/prompts/agents/designer.md +7 -10
  93. package/src/prompts/agents/explore.md +15 -23
  94. package/src/prompts/agents/init.md +23 -23
  95. package/src/prompts/agents/plan.md +14 -77
  96. package/src/prompts/agents/reviewer.md +6 -5
  97. package/src/prompts/agents/task.md +13 -11
  98. package/src/prompts/compaction/branch-summary.md +3 -3
  99. package/src/prompts/compaction/compaction-short-summary.md +7 -7
  100. package/src/prompts/compaction/compaction-summary-context.md +1 -1
  101. package/src/prompts/compaction/compaction-summary.md +5 -5
  102. package/src/prompts/compaction/compaction-turn-prefix.md +3 -3
  103. package/src/prompts/compaction/compaction-update-summary.md +11 -11
  104. package/src/prompts/memories/consolidation.md +5 -5
  105. package/src/prompts/memories/read-path.md +6 -6
  106. package/src/prompts/memories/stage_one_input.md +1 -1
  107. package/src/prompts/memories/stage_one_system.md +5 -5
  108. package/src/prompts/review-request.md +4 -4
  109. package/src/prompts/system/agent-creation-architect.md +17 -17
  110. package/src/prompts/system/agent-creation-user.md +2 -2
  111. package/src/prompts/system/custom-system-prompt.md +4 -4
  112. package/src/prompts/system/plan-mode-active.md +20 -20
  113. package/src/prompts/system/plan-mode-approved.md +7 -7
  114. package/src/prompts/system/plan-mode-reference.md +2 -2
  115. package/src/prompts/system/plan-mode-subagent.md +8 -8
  116. package/src/prompts/system/subagent-submit-reminder.md +5 -5
  117. package/src/prompts/system/subagent-system-prompt.md +29 -22
  118. package/src/prompts/system/subagent-user-prompt.md +7 -3
  119. package/src/prompts/system/summarization-system.md +1 -1
  120. package/src/prompts/system/system-prompt.md +201 -226
  121. package/src/prompts/system/title-system.md +2 -2
  122. package/src/prompts/system/ttsr-interrupt.md +1 -1
  123. package/src/prompts/system/web-search.md +16 -16
  124. package/src/prompts/tools/ask.md +1 -3
  125. package/src/prompts/tools/await.md +2 -4
  126. package/src/prompts/tools/bash.md +5 -7
  127. package/src/prompts/tools/browser.md +4 -6
  128. package/src/prompts/tools/calculator.md +1 -3
  129. package/src/prompts/tools/cancel-job.md +2 -4
  130. package/src/prompts/tools/exit-plan-mode.md +7 -7
  131. package/src/prompts/tools/fetch.md +0 -2
  132. package/src/prompts/tools/find.md +3 -5
  133. package/src/prompts/tools/gemini-image.md +6 -22
  134. package/src/prompts/tools/grep.md +4 -6
  135. package/src/prompts/tools/hashline.md +12 -15
  136. package/src/prompts/tools/lsp.md +1 -3
  137. package/src/prompts/tools/patch.md +7 -9
  138. package/src/prompts/tools/python.md +10 -14
  139. package/src/prompts/tools/read.md +0 -2
  140. package/src/prompts/tools/replace.md +5 -7
  141. package/src/prompts/tools/ssh.md +3 -5
  142. package/src/prompts/tools/task.md +6 -8
  143. package/src/prompts/tools/todo-write.md +7 -9
  144. package/src/prompts/tools/web-search.md +3 -5
  145. package/src/prompts/tools/write.md +3 -5
  146. package/src/sdk.ts +1 -2
  147. package/src/session/agent-session.ts +10 -26
  148. package/src/session/agent-storage.ts +1 -2
  149. package/src/session/history-storage.ts +1 -2
  150. package/src/session/session-manager.ts +10 -2
  151. package/src/ssh/connection-manager.ts +11 -2
  152. package/src/ssh/sshfs-mount.ts +7 -1
  153. package/src/system-prompt.ts +25 -103
  154. package/src/task/agents.ts +1 -1
  155. package/src/task/worktree.ts +1 -2
  156. package/src/tools/ask.ts +0 -1
  157. package/src/tools/await-tool.ts +6 -3
  158. package/src/tools/bash-interactive.ts +2 -45
  159. package/src/tools/bash.ts +5 -5
  160. package/src/tools/browser.ts +1 -2
  161. package/src/tools/gemini-image.ts +8 -28
  162. package/src/tools/json-tree.ts +2 -1
  163. package/src/tools/python.ts +1 -1
  164. package/src/tools/read.ts +1 -2
  165. package/src/tools/render-utils.ts +5 -2
  166. package/src/tools/todo-write.ts +11 -9
  167. package/src/utils/tools-manager.ts +1 -2
  168. package/src/web/scrapers/artifacthub.ts +2 -1
  169. package/src/web/scrapers/aur.ts +2 -1
  170. package/src/web/scrapers/biorxiv.ts +2 -1
  171. package/src/web/scrapers/bluesky.ts +2 -1
  172. package/src/web/scrapers/chocolatey.ts +2 -1
  173. package/src/web/scrapers/cisa-kev.ts +2 -1
  174. package/src/web/scrapers/clojars.ts +2 -1
  175. package/src/web/scrapers/coingecko.ts +2 -1
  176. package/src/web/scrapers/crates-io.ts +2 -1
  177. package/src/web/scrapers/crossref.ts +2 -1
  178. package/src/web/scrapers/discogs.ts +3 -1
  179. package/src/web/scrapers/discourse.ts +2 -1
  180. package/src/web/scrapers/dockerhub.ts +2 -1
  181. package/src/web/scrapers/fdroid.ts +2 -1
  182. package/src/web/scrapers/firefox-addons.ts +2 -1
  183. package/src/web/scrapers/flathub.ts +2 -1
  184. package/src/web/scrapers/gitlab.ts +1 -1
  185. package/src/web/scrapers/go-pkg.ts +2 -1
  186. package/src/web/scrapers/hackage.ts +2 -1
  187. package/src/web/scrapers/hackernews.ts +2 -1
  188. package/src/web/scrapers/hex.ts +2 -1
  189. package/src/web/scrapers/huggingface.ts +2 -1
  190. package/src/web/scrapers/jetbrains-marketplace.ts +2 -1
  191. package/src/web/scrapers/lemmy.ts +2 -1
  192. package/src/web/scrapers/lobsters.ts +2 -1
  193. package/src/web/scrapers/mastodon.ts +2 -1
  194. package/src/web/scrapers/maven.ts +2 -1
  195. package/src/web/scrapers/mdn.ts +2 -1
  196. package/src/web/scrapers/metacpan.ts +2 -1
  197. package/src/web/scrapers/musicbrainz.ts +3 -1
  198. package/src/web/scrapers/npm.ts +2 -1
  199. package/src/web/scrapers/nuget.ts +2 -1
  200. package/src/web/scrapers/nvd.ts +2 -1
  201. package/src/web/scrapers/ollama.ts +2 -1
  202. package/src/web/scrapers/open-vsx.ts +2 -1
  203. package/src/web/scrapers/opencorporates.ts +2 -1
  204. package/src/web/scrapers/openlibrary.ts +2 -1
  205. package/src/web/scrapers/orcid.ts +3 -1
  206. package/src/web/scrapers/osv.ts +2 -1
  207. package/src/web/scrapers/packagist.ts +2 -1
  208. package/src/web/scrapers/pub-dev.ts +2 -1
  209. package/src/web/scrapers/pubmed.ts +2 -1
  210. package/src/web/scrapers/pypi.ts +2 -1
  211. package/src/web/scrapers/rawg.ts +2 -8
  212. package/src/web/scrapers/reddit.ts +2 -1
  213. package/src/web/scrapers/repology.ts +2 -1
  214. package/src/web/scrapers/rfc.ts +2 -1
  215. package/src/web/scrapers/rubygems.ts +2 -1
  216. package/src/web/scrapers/searchcode.ts +2 -1
  217. package/src/web/scrapers/sec-edgar.ts +2 -1
  218. package/src/web/scrapers/semantic-scholar.ts +2 -1
  219. package/src/web/scrapers/snapcraft.ts +2 -1
  220. package/src/web/scrapers/sourcegraph.ts +2 -1
  221. package/src/web/scrapers/spdx.ts +2 -1
  222. package/src/web/scrapers/stackoverflow.ts +2 -1
  223. package/src/web/scrapers/terraform.ts +2 -1
  224. package/src/web/scrapers/types.ts +0 -11
  225. package/src/web/scrapers/vimeo.ts +2 -1
  226. package/src/web/scrapers/vscode-marketplace.ts +2 -1
  227. package/src/web/scrapers/w3c.ts +2 -1
  228. package/src/web/scrapers/wikidata.ts +2 -1
  229. package/src/web/search/index.ts +10 -14
  230. package/src/web/search/provider.ts +2 -2
  231. package/src/web/search/providers/codex.ts +1 -2
  232. package/src/web/search/providers/exa.ts +1 -6
  233. package/src/web/search/providers/gemini.ts +1 -1
  234. package/src/web/search/providers/perplexity.ts +1 -2
  235. package/src/web/search/providers/utils.ts +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,37 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [13.2.1] - 2026-02-24
6
+
7
+ ### Fixed
8
+ - Fixed changelog tools to enforce category-specific arrays and reuse the shared category list for generation
9
+ - Non-interactive environment variables (pager, editor, prompt suppression) were not applied to non-PTY bash execution, causing commands to potentially block on pagers or prompts
10
+
11
+ ### Changed
12
+
13
+ - Extracted non-interactive environment config from `bash-interactive.ts` into shared `non-interactive-env.ts` module, applied consistently to all bash execution paths
14
+
15
+ ## [13.2.0] - 2026-02-23
16
+ ### Breaking Changes
17
+
18
+ - Made `description` field required in CustomTool interface
19
+
20
+ ### Changed
21
+
22
+ - Reorganized imports from `@oh-my-pi/pi-utils/dirs` to consolidate with main `@oh-my-pi/pi-utils` exports for cleaner dependency management
23
+ - Renamed `loadSkillsFromDir` to `scanSkillsFromDir` with updated interface for improved clarity on skill discovery behavior
24
+ - Moved `tryParseJson` utility from local scrapers module to `@oh-my-pi/pi-utils` for centralized JSON parsing
25
+ - Simplified patch module exports by consolidating type re-exports with `export * from './types'`
26
+ - Removed `emitCustomToolSessionEvent` method from AgentSession for streamlined session lifecycle management
27
+ - Changed skill discovery from recursive to non-recursive (one level deep only) for improved performance and clarity
28
+ - Simplified skill loading logic by removing recursive directory traversal and consolidating ignore rule handling
29
+
30
+ ### Removed
31
+
32
+ - Removed `parseJSON` helper function from discovery module (replaced by `tryParseJson` from pi-utils)
33
+ - Removed backwards compatibility comment from `AskToolDetails.question` field
34
+ - Removed unused SSH resource cleanup functions `closeAllConnections` and `unmountAll` from session imports
35
+
5
36
  ## [13.1.2] - 2026-02-23
6
37
  ### Breaking Changes
7
38
 
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": "13.1.2",
4
+ "version": "13.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",
@@ -41,12 +41,12 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@mozilla/readability": "^0.6",
44
- "@oh-my-pi/omp-stats": "13.1.2",
45
- "@oh-my-pi/pi-agent-core": "13.1.2",
46
- "@oh-my-pi/pi-ai": "13.1.2",
47
- "@oh-my-pi/pi-natives": "13.1.2",
48
- "@oh-my-pi/pi-tui": "13.1.2",
49
- "@oh-my-pi/pi-utils": "13.1.2",
44
+ "@oh-my-pi/omp-stats": "13.2.1",
45
+ "@oh-my-pi/pi-agent-core": "13.2.1",
46
+ "@oh-my-pi/pi-ai": "13.2.1",
47
+ "@oh-my-pi/pi-natives": "13.2.1",
48
+ "@oh-my-pi/pi-tui": "13.2.1",
49
+ "@oh-my-pi/pi-utils": "13.2.1",
50
50
  "@sinclair/typebox": "^0.34",
51
51
  "@xterm/headless": "^6.0",
52
52
  "ajv": "^8.18",
@@ -11,6 +11,7 @@
11
11
  * 6. Collapse 2+ blank lines to single blank line
12
12
  * 7. Trim trailing whitespace (preserve indentation)
13
13
  * 8. No trailing newline at EOF
14
+ * 9. Bold RFC 2119 keywords (MUST, SHOULD, MAY, etc.) in prompt content
14
15
  */
15
16
  import { Glob } from "bun";
16
17
 
@@ -37,6 +38,23 @@ const TABLE_ROW = /^\|.*\|$/;
37
38
  // Table separator (|---|---|)
38
39
  const TABLE_SEP = /^\|[-:\s|]+\|$/;
39
40
 
41
+ /** RFC 2119 keywords used in prompts. */
42
+ const RFC2119_KEYWORDS = /\b(?:MUST NOT|SHOULD NOT|SHALL NOT|RECOMMENDED|REQUIRED|OPTIONAL|SHOULD|SHALL|MUST|MAY)\b/g;
43
+
44
+ function boldRfc2119Keywords(line: string): string {
45
+ return line.replace(RFC2119_KEYWORDS, (match, offset, source) => {
46
+ const isAlreadyBold =
47
+ source[offset - 2] === "*" &&
48
+ source[offset - 1] === "*" &&
49
+ source[offset + match.length] === "*" &&
50
+ source[offset + match.length + 1] === "*";
51
+ if (isAlreadyBold) {
52
+ return match;
53
+ }
54
+ return `**${match}**`;
55
+ });
56
+ }
57
+
40
58
  /** Compact a table row by trimming cell padding */
41
59
  function compactTableRow(line: string): string {
42
60
  // Split by |, trim each cell, rejoin
@@ -62,12 +80,6 @@ function compactTableSep(line: string): string {
62
80
  }
63
81
 
64
82
  function formatPrompt(content: string): string {
65
- // Replace common ascii ellipsis and arrow patterns with their unicode equivalents
66
- content = content
67
- .replace(/\.{3}/g, "…")
68
- .replace(/->/g, "→")
69
- .replace(/<-/g, "←")
70
- .replace(/<->/g, "↔");
71
83
  const lines = content.split("\n");
72
84
  const result: string[] = [];
73
85
  let inCodeBlock = false;
@@ -75,9 +87,9 @@ function formatPrompt(content: string): string {
75
87
  const topLevelTags: string[] = [];
76
88
 
77
89
  for (let i = 0; i < lines.length; i++) {
78
- let line = lines[i];
90
+ let line = lines[i].trimEnd();
79
91
 
80
- const trimmed = line.trim();
92
+ const trimmed = line.trimStart();
81
93
 
82
94
  // Track code blocks - don't modify inside them
83
95
  if (CODE_FENCE.test(trimmed)) {
@@ -91,9 +103,18 @@ function formatPrompt(content: string): string {
91
103
  continue;
92
104
  }
93
105
 
106
+ // Replace common ascii ellipsis and arrow patterns with their unicode equivalents
107
+ line = line
108
+ .replace(/\.{3}/g, "…")
109
+ .replace(/->/g, "→")
110
+ .replace(/<-/g, "←")
111
+ .replace(/<->/g, "↔")
112
+ .replace(/!=/g, "≠")
113
+ .replace(/<=/g, "≤")
114
+ .replace(/>=/g, "≥");
115
+
94
116
  // Track top-level XML opening tags for depth-aware indent stripping
95
- const isOpeningXml =
96
- OPENING_XML.test(trimmed) && !trimmed.endsWith("/>");
117
+ const isOpeningXml = OPENING_XML.test(trimmed) && !trimmed.endsWith("/>");
97
118
  if (isOpeningXml && line.length === trimmed.length) {
98
119
  // Opening tag at column 0 — track as top-level
99
120
  const match = OPENING_XML.exec(trimmed);
@@ -104,10 +125,7 @@ function formatPrompt(content: string): string {
104
125
  const closingMatch = CLOSING_XML.exec(trimmed);
105
126
  if (closingMatch) {
106
127
  const tagName = closingMatch[1];
107
- if (
108
- topLevelTags.length > 0 &&
109
- topLevelTags[topLevelTags.length - 1] === tagName
110
- ) {
128
+ if (topLevelTags.length > 0 && topLevelTags[topLevelTags.length - 1] === tagName) {
111
129
  // Closing tag matches a top-level opener — strip indent
112
130
  line = trimmed;
113
131
  topLevelTags.pop();
@@ -126,6 +144,7 @@ function formatPrompt(content: string): string {
126
144
  // Trim trailing whitespace (preserve leading for non-closing-tags)
127
145
  line = line.trimEnd();
128
146
  }
147
+ line = boldRfc2119Keywords(line);
129
148
 
130
149
  const isBlank = trimmed === "";
131
150
 
@@ -47,6 +47,7 @@ export interface AsyncJobRegisterOptions {
47
47
  export class AsyncJobManager {
48
48
  readonly #jobs = new Map<string, AsyncJob>();
49
49
  readonly #deliveries: AsyncJobDelivery[] = [];
50
+ readonly #suppressedDeliveries = new Set<string>();
50
51
  readonly #evictionTimers = new Map<string, NodeJS.Timeout>();
51
52
  readonly #onJobComplete: AsyncJobManagerOptions["onJobComplete"];
52
53
  readonly #maxRunningJobs: number;
@@ -81,6 +82,7 @@ export class AsyncJobManager {
81
82
  }
82
83
 
83
84
  const id = this.#resolveJobId(options?.id);
85
+ this.#suppressedDeliveries.delete(id);
84
86
  const abortController = new AbortController();
85
87
  const startTime = Date.now();
86
88
 
@@ -182,6 +184,23 @@ export class AsyncJobManager {
182
184
  return this.#deliveries.length > 0;
183
185
  }
184
186
 
187
+ acknowledgeDeliveries(jobIds: string[]): number {
188
+ const uniqueJobIds = Array.from(new Set(jobIds.map(id => id.trim()).filter(id => id.length > 0)));
189
+ if (uniqueJobIds.length === 0) return 0;
190
+
191
+ for (const jobId of uniqueJobIds) {
192
+ this.#suppressedDeliveries.add(jobId);
193
+ }
194
+
195
+ const before = this.#deliveries.length;
196
+ this.#deliveries.splice(
197
+ 0,
198
+ this.#deliveries.length,
199
+ ...this.#deliveries.filter(delivery => !this.#suppressedDeliveries.has(delivery.jobId)),
200
+ );
201
+ return before - this.#deliveries.length;
202
+ }
203
+
185
204
  cancelAll(): void {
186
205
  for (const job of this.getRunningJobs()) {
187
206
  job.status = "cancelled";
@@ -234,6 +253,7 @@ export class AsyncJobManager {
234
253
  this.#clearEvictionTimers();
235
254
  this.#jobs.clear();
236
255
  this.#deliveries.length = 0;
256
+ this.#suppressedDeliveries.clear();
237
257
  return drained;
238
258
  }
239
259
 
@@ -257,6 +277,7 @@ export class AsyncJobManager {
257
277
  #scheduleEviction(jobId: string): void {
258
278
  if (this.#retentionMs <= 0) {
259
279
  this.#jobs.delete(jobId);
280
+ this.#suppressedDeliveries.delete(jobId);
260
281
  return;
261
282
  }
262
283
  const existing = this.#evictionTimers.get(jobId);
@@ -266,6 +287,7 @@ export class AsyncJobManager {
266
287
  const timer = setTimeout(() => {
267
288
  this.#evictionTimers.delete(jobId);
268
289
  this.#jobs.delete(jobId);
290
+ this.#suppressedDeliveries.delete(jobId);
269
291
  }, this.#retentionMs);
270
292
  timer.unref();
271
293
  this.#evictionTimers.set(jobId, timer);
@@ -278,7 +300,14 @@ export class AsyncJobManager {
278
300
  this.#evictionTimers.clear();
279
301
  }
280
302
 
303
+ #isDeliverySuppressed(jobId: string): boolean {
304
+ return this.#suppressedDeliveries.has(jobId);
305
+ }
306
+
281
307
  #enqueueDelivery(jobId: string, text: string): void {
308
+ if (this.#isDeliverySuppressed(jobId)) {
309
+ return;
310
+ }
282
311
  this.#deliveries.push({
283
312
  jobId,
284
313
  text,
@@ -308,10 +337,21 @@ export class AsyncJobManager {
308
337
  async #runDeliveryLoop(): Promise<void> {
309
338
  while (this.#deliveries.length > 0) {
310
339
  const delivery = this.#deliveries[0];
340
+ if (this.#isDeliverySuppressed(delivery.jobId)) {
341
+ this.#deliveries.shift();
342
+ continue;
343
+ }
311
344
  const waitMs = delivery.nextAttemptAt - Date.now();
312
345
  if (waitMs > 0) {
313
346
  await Bun.sleep(waitMs);
314
347
  }
348
+ if (this.#deliveries[0] !== delivery) {
349
+ continue;
350
+ }
351
+ if (this.#isDeliverySuppressed(delivery.jobId)) {
352
+ this.#deliveries.shift();
353
+ continue;
354
+ }
315
355
 
316
356
  try {
317
357
  await this.#onJobComplete(delivery.jobId, delivery.text, this.#jobs.get(delivery.jobId));
@@ -321,7 +361,9 @@ export class AsyncJobManager {
321
361
  delivery.lastError = error instanceof Error ? error.message : String(error);
322
362
  delivery.nextAttemptAt = Date.now() + this.#getRetryDelay(delivery.attempt);
323
363
  this.#deliveries.shift();
324
- this.#deliveries.push(delivery);
364
+ if (!this.#isDeliverySuppressed(delivery.jobId)) {
365
+ this.#deliveries.push(delivery);
366
+ }
325
367
  logger.warn("Async job completion delivery failed", {
326
368
  jobId: delivery.jobId,
327
369
  attempt: delivery.attempt,
@@ -8,8 +8,7 @@
8
8
  */
9
9
  import * as os from "node:os";
10
10
  import * as path from "node:path";
11
- import { logger } from "@oh-my-pi/pi-utils";
12
- import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
11
+ import { getProjectDir, logger } from "@oh-my-pi/pi-utils";
13
12
 
14
13
  import type { Settings } from "../config/settings";
15
14
  import { clearCache as clearFsCache, cacheStats as fsCacheStats, invalidate as invalidateFs } from "./fs";
@@ -15,7 +15,7 @@ export interface CustomTool {
15
15
  /** Absolute path to tool definition file */
16
16
  path: string;
17
17
  /** Tool description */
18
- description?: string;
18
+ description: string;
19
19
  /** Tool implementation (script path or inline) */
20
20
  implementation?: string;
21
21
  /** Source level */
package/src/cli/args.ts CHANGED
@@ -2,8 +2,7 @@
2
2
  * CLI argument parsing and help display
3
3
  */
4
4
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
5
- import { logger } from "@oh-my-pi/pi-utils";
6
- import { APP_NAME, CONFIG_DIR_NAME } from "@oh-my-pi/pi-utils/dirs";
5
+ import { APP_NAME, CONFIG_DIR_NAME, logger } from "@oh-my-pi/pi-utils";
7
6
  import chalk from "chalk";
8
7
  import { BUILTIN_TOOLS } from "../tools";
9
8
 
@@ -5,7 +5,7 @@
5
5
  * Uses settings-defs as the source of truth for available settings.
6
6
  */
7
7
 
8
- import { APP_NAME, getAgentDir } from "@oh-my-pi/pi-utils/dirs";
8
+ import { APP_NAME, getAgentDir } from "@oh-my-pi/pi-utils";
9
9
  import chalk from "chalk";
10
10
  import {
11
11
  getDefault,
@@ -4,8 +4,7 @@
4
4
  import * as fs from "node:fs";
5
5
  import * as path from "node:path";
6
6
  import type { ImageContent } from "@oh-my-pi/pi-ai";
7
- import { isEnoent } from "@oh-my-pi/pi-utils";
8
- import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
7
+ import { getProjectDir, isEnoent } from "@oh-my-pi/pi-utils";
9
8
  import chalk from "chalk";
10
9
  import { resolveReadPath } from "../tools/path-utils";
11
10
  import { formatBytes } from "../tools/render-utils";
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import * as path from "node:path";
7
7
  import { grep } from "@oh-my-pi/pi-natives";
8
- import { APP_NAME } from "@oh-my-pi/pi-utils/dirs";
8
+ import { APP_NAME } from "@oh-my-pi/pi-utils";
9
9
  import chalk from "chalk";
10
10
 
11
11
  export interface GrepCommandArgs {
@@ -4,7 +4,7 @@
4
4
  * Handles `omp jupyter` subcommand for managing the shared Python gateway.
5
5
  */
6
6
 
7
- import { APP_NAME } from "@oh-my-pi/pi-utils/dirs";
7
+ import { APP_NAME } from "@oh-my-pi/pi-utils";
8
8
  import chalk from "chalk";
9
9
  import { getGatewayStatus, shutdownSharedGateway } from "../ipy/gateway-coordinator";
10
10
 
@@ -4,7 +4,7 @@
4
4
  * Handles `omp plugin <command>` subcommands for plugin lifecycle management.
5
5
  */
6
6
 
7
- import { APP_NAME } from "@oh-my-pi/pi-utils/dirs";
7
+ import { APP_NAME } from "@oh-my-pi/pi-utils";
8
8
  import chalk from "chalk";
9
9
  import { PluginManager, parseSettingValue, validateSetting } from "../extensibility/plugins";
10
10
  import { theme } from "../modes/theme/theme";
@@ -4,7 +4,7 @@
4
4
  * Handles `omp setup <component>` to install dependencies for optional features.
5
5
  */
6
6
  import * as path from "node:path";
7
- import { APP_NAME, getPythonEnvDir } from "@oh-my-pi/pi-utils/dirs";
7
+ import { APP_NAME, getPythonEnvDir } from "@oh-my-pi/pi-utils";
8
8
  import { $ } from "bun";
9
9
  import chalk from "chalk";
10
10
  import { theme } from "../modes/theme/theme";
@@ -6,7 +6,7 @@
6
6
  import * as path from "node:path";
7
7
  import { createInterface } from "node:readline/promises";
8
8
  import { Shell } from "@oh-my-pi/pi-natives";
9
- import { APP_NAME, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
9
+ import { APP_NAME, getProjectDir } from "@oh-my-pi/pi-utils";
10
10
  import chalk from "chalk";
11
11
  import { Settings } from "../config/settings";
12
12
  import { getOrCreateSnapshot } from "../utils/shell-snapshot";
@@ -4,7 +4,7 @@
4
4
  * Handles `omp ssh <command>` subcommands for SSH host configuration management.
5
5
  */
6
6
 
7
- import { getSSHConfigPath } from "@oh-my-pi/pi-utils/dirs";
7
+ import { getSSHConfigPath } from "@oh-my-pi/pi-utils";
8
8
  import chalk from "chalk";
9
9
  import { addSSHHost, readSSHConfigFile, removeSSHHost, type SSHHostConfig } from "../ssh/config-writer";
10
10
 
@@ -4,8 +4,7 @@
4
4
  * Handles `omp stats` subcommand for viewing AI usage statistics.
5
5
  */
6
6
 
7
- import { formatDuration, formatNumber, formatPercent } from "@oh-my-pi/pi-utils";
8
- import { APP_NAME } from "@oh-my-pi/pi-utils/dirs";
7
+ import { APP_NAME, formatDuration, formatNumber, formatPercent } from "@oh-my-pi/pi-utils";
9
8
  import chalk from "chalk";
10
9
  import { openPath } from "../utils/open";
11
10
 
@@ -7,8 +7,7 @@
7
7
  import { execSync, spawnSync } from "node:child_process";
8
8
  import * as fs from "node:fs";
9
9
  import { pipeline } from "node:stream/promises";
10
- import { isEnoent } from "@oh-my-pi/pi-utils";
11
- import { APP_NAME, VERSION } from "@oh-my-pi/pi-utils/dirs";
10
+ import { APP_NAME, isEnoent, VERSION } from "@oh-my-pi/pi-utils";
12
11
  import chalk from "chalk";
13
12
  import { theme } from "../modes/theme/theme";
14
13
 
@@ -4,7 +4,7 @@
4
4
  * Handles `omp q`/`omp web-search` subcommands for testing web search providers.
5
5
  */
6
6
 
7
- import { APP_NAME } from "@oh-my-pi/pi-utils/dirs";
7
+ import { APP_NAME } from "@oh-my-pi/pi-utils";
8
8
  import chalk from "chalk";
9
9
  import { initTheme, theme } from "../modes/theme/theme";
10
10
  import { runSearchQuery, type SearchParams } from "../web/search/index";
package/src/cli.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env bun
2
+ import { APP_NAME, VERSION } from "@oh-my-pi/pi-utils";
2
3
  /**
3
4
  * CLI entry point — registers all commands explicitly and delegates to the
4
5
  * lightweight CLI runner from pi-utils.
5
6
  */
6
7
  import { type CommandEntry, run } from "@oh-my-pi/pi-utils/cli";
7
- import { APP_NAME, VERSION } from "@oh-my-pi/pi-utils/dirs";
8
8
 
9
9
  // Detect known Bun errata that cause TUI crashes (e.g. Bun.stringWidth mishandling OSC sequences).
10
10
  if (Bun.stringWidth("\x1b[0m\x1b]8;;\x07") !== 0) {
@@ -1,8 +1,9 @@
1
1
  /**
2
2
  * Root command for the coding agent CLI.
3
3
  */
4
+
5
+ import { APP_NAME } from "@oh-my-pi/pi-utils";
4
6
  import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
5
- import { APP_NAME } from "@oh-my-pi/pi-utils/dirs";
6
7
  import { parseArgs } from "../cli/args";
7
8
  import { runRootCommand } from "../main";
8
9
 
@@ -1,3 +1,4 @@
1
+ import { INTENT_FIELD } from "@oh-my-pi/pi-agent-core";
1
2
  import type { Api, Model } from "@oh-my-pi/pi-ai";
2
3
  import { Markdown } from "@oh-my-pi/pi-tui";
3
4
  import chalk from "chalk";
@@ -245,7 +246,7 @@ function formatToolArgs(args?: Record<string, unknown>): string[] {
245
246
  }
246
247
  };
247
248
  for (const [key, value] of Object.entries(args)) {
248
- if (key === "agent__intent") continue;
249
+ if (key === INTENT_FIELD) continue;
249
250
  visit(value, key);
250
251
  }
251
252
  return lines;
@@ -1,7 +1,6 @@
1
1
  import * as path from "node:path";
2
2
  import { createInterface } from "node:readline/promises";
3
- import { $env, isEnoent } from "@oh-my-pi/pi-utils";
4
- import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
3
+ import { $env, getProjectDir, isEnoent } from "@oh-my-pi/pi-utils";
5
4
  import { applyChangelogProposals } from "../../commit/changelog";
6
5
  import { detectChangelogBoundaries } from "../../commit/changelog/detect";
7
6
  import { parseUnreleasedSection } from "../../commit/changelog/parse";
@@ -13,11 +13,11 @@ Workflow rules:
13
13
  6. Do not use read.
14
14
 
15
15
  Commit requirements:
16
- - Summary line: past-tense verb, <= 72 chars, no trailing period.
16
+ - Summary line: past-tense verb, 72 chars, no trailing period.
17
17
  - Avoid filler words: comprehensive, various, several, improved, enhanced, better.
18
18
  - Avoid meta phrases: "this commit", "this change", "updated code", "modified files".
19
19
  - Scope: lowercase, max two segments; only letters, digits, hyphens, underscores.
20
- - Detail lines optional (0-6). Each sentence ending in period, <= 120 chars.
20
+ - Detail lines optional (0-6). Each sentence ending in period, 120 chars.
21
21
 
22
22
  Conventional commit types:
23
23
  {{types_description}}
@@ -34,5 +34,5 @@ Tool guidance:
34
34
 
35
35
  ## Changelog Requirements
36
36
 
37
- If changelog targets provided, you MUST call `propose_changelog` before finishing.
37
+ If changelog targets provided, you **MUST** call `propose_changelog` before finishing.
38
38
  If you propose split commit plan, include changelog target files in relevant commit changes.
@@ -1,16 +1,25 @@
1
- import { Type } from "@sinclair/typebox";
1
+ import { type TSchema, Type } from "@sinclair/typebox";
2
2
  import type { CommitAgentState } from "../../../commit/agentic/state";
3
- import type { ChangelogCategory } from "../../../commit/types";
3
+ import { CHANGELOG_CATEGORIES, type ChangelogCategory } from "../../../commit/types";
4
4
  import type { CustomTool } from "../../../extensibility/custom-tools/types";
5
5
 
6
+ const changelogEntryProperties = CHANGELOG_CATEGORIES.reduce<Record<ChangelogCategory, TSchema>>(
7
+ (acc, category) => {
8
+ acc[category] = Type.Optional(Type.Array(Type.String()));
9
+ return acc;
10
+ },
11
+ {} as Record<ChangelogCategory, TSchema>,
12
+ );
13
+
14
+ const changelogEntriesSchema = Type.Object(changelogEntryProperties);
15
+ const changelogDeletionsSchema = Type.Object(changelogEntryProperties, {
16
+ description: "Entries to remove from existing changelog sections (case-insensitive match)",
17
+ });
18
+
6
19
  const changelogEntrySchema = Type.Object({
7
20
  path: Type.String(),
8
- entries: Type.Record(Type.String(), Type.Array(Type.String())),
9
- deletions: Type.Optional(
10
- Type.Record(Type.String(), Type.Array(Type.String()), {
11
- description: "Entries to remove from existing changelog sections (case-insensitive match)",
12
- }),
13
- ),
21
+ entries: changelogEntriesSchema,
22
+ deletions: Type.Optional(changelogDeletionsSchema),
14
23
  });
15
24
 
16
25
  const proposeChangelogSchema = Type.Object({
@@ -23,15 +32,7 @@ interface ChangelogResponse {
23
32
  warnings: string[];
24
33
  }
25
34
 
26
- const allowedCategories = new Set<ChangelogCategory>([
27
- "Breaking Changes",
28
- "Added",
29
- "Changed",
30
- "Deprecated",
31
- "Removed",
32
- "Fixed",
33
- "Security",
34
- ]);
35
+ const allowedCategories = new Set<ChangelogCategory>(CHANGELOG_CATEGORIES);
35
36
 
36
37
  export function createProposeChangelogTool(
37
38
  state: CommitAgentState,
@@ -50,11 +51,16 @@ export function createProposeChangelogTool(
50
51
 
51
52
  const normalized = params.entries.map(entry => {
52
53
  const cleaned: Record<string, string[]> = {};
53
- for (const [category, values] of Object.entries(entry.entries ?? {})) {
54
+ const entries = entry.entries as Record<string, string[]>;
55
+ for (const [category, values] of Object.entries(entries)) {
54
56
  if (!allowedCategories.has(category as ChangelogCategory)) {
55
57
  errors.push(`Unknown changelog category for ${entry.path}: ${category}`);
56
58
  continue;
57
59
  }
60
+ if (!Array.isArray(values)) {
61
+ errors.push(`Invalid changelog entries for ${entry.path}: ${category}`);
62
+ continue;
63
+ }
58
64
  const items = values.map(value => value.trim().replace(/\.$/, "")).filter(value => value.length > 0);
59
65
  if (items.length > 0) {
60
66
  cleaned[category] = Array.from(new Set(items));
@@ -64,11 +70,16 @@ export function createProposeChangelogTool(
64
70
  let cleanedDeletions: Record<string, string[]> | undefined;
65
71
  if (entry.deletions) {
66
72
  cleanedDeletions = {};
67
- for (const [category, values] of Object.entries(entry.deletions)) {
73
+ const deletions = entry.deletions as Record<string, string[]>;
74
+ for (const [category, values] of Object.entries(deletions)) {
68
75
  if (!allowedCategories.has(category as ChangelogCategory)) {
69
76
  errors.push(`Unknown deletion category for ${entry.path}: ${category}`);
70
77
  continue;
71
78
  }
79
+ if (!Array.isArray(values)) {
80
+ errors.push(`Invalid deletion entries for ${entry.path}: ${category}`);
81
+ continue;
82
+ }
72
83
  const items = values.map(value => value.trim()).filter(value => value.length > 0);
73
84
  if (items.length > 0) {
74
85
  cleanedDeletions[category] = Array.from(new Set(items));
@@ -1,17 +1,27 @@
1
1
  import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
2
2
  import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
3
- import { Type } from "@sinclair/typebox";
3
+ import { type TSchema, Type } from "@sinclair/typebox";
4
4
  import changelogSystemPrompt from "../../commit/prompts/changelog-system.md" with { type: "text" };
5
5
  import changelogUserPrompt from "../../commit/prompts/changelog-user.md" with { type: "text" };
6
- import type { ChangelogGenerationResult } from "../../commit/types";
6
+ import { CHANGELOG_CATEGORIES, type ChangelogCategory, type ChangelogGenerationResult } from "../../commit/types";
7
7
  import { renderPromptTemplate } from "../../config/prompt-templates";
8
8
  import { extractTextContent, extractToolCall, parseJsonPayload } from "../utils";
9
9
 
10
- const ChangelogTool = {
10
+ const changelogEntryProperties = CHANGELOG_CATEGORIES.reduce<Record<ChangelogCategory, TSchema>>(
11
+ (acc, category) => {
12
+ acc[category] = Type.Optional(Type.Array(Type.String()));
13
+ return acc;
14
+ },
15
+ {} as Record<ChangelogCategory, TSchema>,
16
+ );
17
+
18
+ const changelogEntriesSchema = Type.Object(changelogEntryProperties);
19
+
20
+ export const changelogTool = {
11
21
  name: "create_changelog_entries",
12
22
  description: "Generate changelog entries grouped by Keep a Changelog categories.",
13
23
  parameters: Type.Object({
14
- entries: Type.Record(Type.String(), Type.Array(Type.String())),
24
+ entries: changelogEntriesSchema,
15
25
  }),
16
26
  };
17
27
 
@@ -46,7 +56,7 @@ export async function generateChangelogEntries({
46
56
  {
47
57
  systemPrompt: renderPromptTemplate(changelogSystemPrompt),
48
58
  messages: [{ role: "user", content: prompt, timestamp: Date.now() }],
49
- tools: [ChangelogTool],
59
+ tools: [changelogTool],
50
60
  },
51
61
  { apiKey, maxTokens: 1200 },
52
62
  );
@@ -58,7 +68,7 @@ export async function generateChangelogEntries({
58
68
  function parseChangelogResponse(message: AssistantMessage): ChangelogGenerationResult {
59
69
  const toolCall = extractToolCall(message, "create_changelog_entries");
60
70
  if (toolCall) {
61
- const parsed = validateToolCall([ChangelogTool], toolCall) as ChangelogGenerationResult;
71
+ const parsed = validateToolCall([changelogTool], toolCall) as ChangelogGenerationResult;
62
72
  return { entries: parsed.entries ?? {} };
63
73
  }
64
74
 
@@ -2,11 +2,12 @@ import * as path from "node:path";
2
2
  import type { Api, Model } from "@oh-my-pi/pi-ai";
3
3
  import { logger } from "@oh-my-pi/pi-utils";
4
4
  import type { ControlledGit } from "../../commit/git";
5
+ import { CHANGELOG_CATEGORIES } from "../../commit/types";
5
6
  import { detectChangelogBoundaries } from "./detect";
6
7
  import { generateChangelogEntries } from "./generate";
7
8
  import { parseUnreleasedSection } from "./parse";
8
9
 
9
- const CHANGELOG_SECTIONS = ["Breaking Changes", "Added", "Changed", "Deprecated", "Removed", "Fixed", "Security"];
10
+ const CHANGELOG_SECTIONS = CHANGELOG_CATEGORIES;
10
11
 
11
12
  const DEFAULT_MAX_DIFF_CHARS = 120_000;
12
13
 
@@ -1,7 +1,6 @@
1
1
  import * as path from "node:path";
2
2
  import type { Api, Model } from "@oh-my-pi/pi-ai";
3
- import { logger } from "@oh-my-pi/pi-utils";
4
- import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
3
+ import { getProjectDir, logger } from "@oh-my-pi/pi-utils";
5
4
  import { ModelRegistry } from "../config/model-registry";
6
5
  import { renderPromptTemplate } from "../config/prompt-templates";
7
6
  import { Settings } from "../config/settings";