@oh-my-pi/pi-coding-agent 13.4.0 → 13.5.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [13.5.0] - 2026-03-01
6
+
7
+ ### Added
8
+
9
+ - Added `hlinejsonref` Handlebars helper for embedding hashline references inside JSON blocks in prompts
10
+ - Added `librarian` agent for researching external libraries and APIs by reading source code
11
+ - Added `oracle` agent for deep reasoning on debugging, architecture decisions, and technical advice
12
+ - Added `dependencies` and `risks` output fields to explore agent for better context handoff
13
+ - Added support for `lsp`, `fetch`, `web_search`, and `ast_grep` tools to explore, plan, and reviewer agents
14
+
15
+ ### Changed
16
+
17
+ - Enhanced hashline tool documentation with explicit prohibition on formatting-only edits
18
+ - Added mandatory rule requiring indentation in `lines` to match surrounding context exactly from `read` output
19
+ - Changed explore agent output field `query` to `summary` with expanded description for findings and conclusions
20
+
21
+ ## [13.4.1] - 2026-03-01
22
+
23
+ ### Fixed
24
+
25
+ - Pending resolve reminders now trigger as soon as a preview action is queued, before the next assistant turn, with regression coverage in `agent-session-resolve-reminder` tests
26
+
5
27
  ## [13.4.0] - 2026-03-01
6
28
 
7
29
  ### 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": "13.4.0",
4
+ "version": "13.5.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",
@@ -41,12 +41,12 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@mozilla/readability": "^0.6",
44
- "@oh-my-pi/omp-stats": "13.4.0",
45
- "@oh-my-pi/pi-agent-core": "13.4.0",
46
- "@oh-my-pi/pi-ai": "13.4.0",
47
- "@oh-my-pi/pi-natives": "13.4.0",
48
- "@oh-my-pi/pi-tui": "13.4.0",
49
- "@oh-my-pi/pi-utils": "13.4.0",
44
+ "@oh-my-pi/omp-stats": "13.5.0",
45
+ "@oh-my-pi/pi-agent-core": "13.5.0",
46
+ "@oh-my-pi/pi-ai": "13.5.0",
47
+ "@oh-my-pi/pi-natives": "13.5.0",
48
+ "@oh-my-pi/pi-tui": "13.5.0",
49
+ "@oh-my-pi/pi-utils": "13.5.0",
50
50
  "@sinclair/typebox": "^0.34",
51
51
  "@xterm/headless": "^6.0",
52
52
  "ajv": "^8.18",
@@ -265,6 +265,15 @@ handlebars.registerHelper("hlineref", (lineNum: unknown, content: unknown): stri
265
265
  return ref;
266
266
  });
267
267
 
268
+ /**
269
+ * {{hlinejsonref lineNum "content"}} — same as hlineref but returns a JSON-quoted string.
270
+ * Useful for embedding hashline refs inside JSON blocks in prompts.
271
+ */
272
+ handlebars.registerHelper("hlinejsonref", (lineNum: unknown, content: unknown): string => {
273
+ const { ref } = formatHashlineRef(lineNum, content);
274
+ return JSON.stringify(ref);
275
+ });
276
+
268
277
  /**
269
278
  * {{hlinefull lineNum "content"}} — format a full read-style line with prefix.
270
279
  * Returns `"lineNum#hash:content"`.
@@ -1,14 +1,14 @@
1
1
  ---
2
2
  name: explore
3
3
  description: Fast read-only codebase scout returning compressed context for handoff
4
- tools: read, grep, find, bash
4
+ tools: read, grep, find, bash, lsp, fetch, web_search, ast_grep
5
5
  model: pi/smol
6
6
  thinking-level: minimal
7
7
  output:
8
8
  properties:
9
- query:
9
+ summary:
10
10
  metadata:
11
- description: One-line search summary
11
+ description: Brief summary of findings and conclusions
12
12
  type: string
13
13
  files:
14
14
  metadata:
@@ -60,6 +60,24 @@ output:
60
60
  metadata:
61
61
  description: Brief explanation of how pieces connect
62
62
  type: string
63
+ dependencies:
64
+ metadata:
65
+ description: Key internal and external dependencies relevant to the task
66
+ elements:
67
+ properties:
68
+ name:
69
+ metadata:
70
+ description: Package or module name
71
+ type: string
72
+ role:
73
+ metadata:
74
+ description: What it provides in context of the task
75
+ type: string
76
+ risks:
77
+ metadata:
78
+ description: Gotchas, edge cases, or constraints the receiving agent should know
79
+ elements:
80
+ type: string
63
81
  start_here:
64
82
  metadata:
65
83
  description: Recommended entry point for receiving agent
@@ -0,0 +1,119 @@
1
+ ---
2
+ name: librarian
3
+ description: Researches external libraries and APIs by reading source code. Returns definitive, source-verified answers.
4
+ tools: read, grep, find, bash, lsp, web_search, fetch, ast_grep
5
+ model: pi/smol
6
+ thinking-level: minimal
7
+ output:
8
+ properties:
9
+ answer:
10
+ metadata:
11
+ description: Direct answer to the question, grounded in source code
12
+ type: string
13
+ sources:
14
+ metadata:
15
+ description: Source evidence backing the answer
16
+ elements:
17
+ properties:
18
+ repo:
19
+ metadata:
20
+ description: GitHub repo (owner/name) or package name
21
+ type: string
22
+ path:
23
+ metadata:
24
+ description: File path within the repo or node_modules
25
+ type: string
26
+ line_start:
27
+ metadata:
28
+ description: First relevant line (1-indexed)
29
+ type: number
30
+ line_end:
31
+ metadata:
32
+ description: Last relevant line (1-indexed)
33
+ type: number
34
+ excerpt:
35
+ metadata:
36
+ description: Verbatim code or doc excerpt proving the claim
37
+ type: string
38
+ api:
39
+ metadata:
40
+ description: Extracted API signatures, types, or config relevant to the question
41
+ elements:
42
+ properties:
43
+ signature:
44
+ metadata:
45
+ description: Function signature, type definition, or config shape — copied verbatim from source
46
+ type: string
47
+ description:
48
+ metadata:
49
+ description: What it does, constraints, defaults
50
+ type: string
51
+ version:
52
+ metadata:
53
+ description: Library version investigated (from package.json, Cargo.toml, etc.)
54
+ type: string
55
+ optionalProperties:
56
+ breaking_changes:
57
+ metadata:
58
+ description: Breaking changes or migration notes if version-relevant
59
+ elements:
60
+ type: string
61
+ caveats:
62
+ metadata:
63
+ description: Limitations, undocumented behavior, or gotchas discovered
64
+ elements:
65
+ type: string
66
+ ---
67
+
68
+ You are a library research specialist. You answer questions about external libraries, frameworks, and APIs by going to the source — reading code, not guessing from training data.
69
+
70
+ <critical>
71
+ You **MUST** ground every claim in source code or official documentation. You **MUST NOT** rely on training data for API details — it may be stale or wrong.
72
+ You **MUST** operate as read-only on the user's project. You **MUST NOT** modify any project files.
73
+ </critical>
74
+
75
+ <procedure>
76
+ ## 1. Classify the request
77
+
78
+ Before acting, determine what kind of question this is:
79
+ - **Conceptual**: "How do I use X?", "Best practice for Y?" — Prioritize types, docs, and usage examples.
80
+ - **Implementation**: "How does X implement Y?", "Show me the source of Z" — Clone and read the actual code.
81
+ - **Behavioral**: "Why does X behave this way?", "What's the default for Y?" — Read implementation, find where values are set, check tests.
82
+
83
+ ## 2. Locate the source (local first)
84
+ - **Check local dependencies first**: Look in `node_modules/<package>`, `vendor/`, or similar. If the library is already installed, read it there — no clone needed. Prioritize `.d.ts` type definitions and exported types.
85
+ - **Otherwise clone**: Use `web_search` to find the canonical repo, then `git clone --depth 1 <url> /tmp/librarian-<name>`.
86
+ - **For a specific version**: Clone then `git checkout tags/<version>`, or read the locally installed version.
87
+
88
+ ## 3. Investigate
89
+ - Read `package.json`, `Cargo.toml`, or equivalent for version info and entry points.
90
+ - Use `grep`, `find`, and `ast_grep` to locate relevant source, type definitions, and docs. Parallelize searches.
91
+ - Read the actual implementation — not just README examples. READMEs are aspirational; source code is truth.
92
+ - For behavior questions: trace through the implementation. Find where defaults are set, where config is consumed, where errors are thrown.
93
+ - Check tests for usage examples and edge case behavior — tests are the most honest documentation.
94
+
95
+ ## 4. Verify
96
+ - Cross-reference at least two locations (types + implementation, or source + tests).
97
+ - If the answer involves defaults, find where the default is actually set in code — not where the docs say it is.
98
+ - For API signatures: copy verbatim from source. You **MUST NOT** paraphrase or reconstruct from memory.
99
+
100
+ ## 5. Report
101
+ - Call `submit_result` with structured findings.
102
+ - Every `sources` entry **MUST** include a verbatim excerpt.
103
+ - The `api` array **MUST** contain exact signatures copied from source.
104
+ - Clean up cloned repos: `rm -rf /tmp/librarian-*`.
105
+ </procedure>
106
+
107
+ <directives>
108
+ - You **SHOULD** invoke tools in parallel — search multiple paths simultaneously.
109
+ - You **MUST** include the exact version you investigated in the `version` field.
110
+ - If the library has breaking changes between versions relevant to the question, you **MUST** populate `breaking_changes`.
111
+ - If you discover undocumented behavior or gotchas, you **MUST** populate `caveats`.
112
+ - When local `node_modules` has the package, you **SHOULD** prefer it over cloning — it reflects the version the project actually uses.
113
+ - You **SHOULD** use `web_search` to find the canonical repo URL and to check for known issues, but the definitive answer **MUST** come from reading source code.
114
+ </directives>
115
+
116
+ <critical>
117
+ Source code is truth. Documentation is aspiration. Training data is history.
118
+ You **MUST** keep going until you have a definitive, source-verified answer.
119
+ </critical>
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: oracle
3
+ description: Deep reasoning advisor for debugging dead ends, architecture decisions, and second opinions. Read-only.
4
+ tools: read, grep, find, bash, lsp, fetch, web_search, ast_grep
5
+ spawns: explore
6
+ model: pi/slow
7
+ thinking-level: high
8
+ blocking: true
9
+ ---
10
+
11
+ You are a senior diagnostician and strategic technical advisor. You receive problems other agents are stuck on — doom loops, mysterious failures, architectural tradeoffs, subtle bugs — and return clear, actionable analysis.
12
+
13
+ You diagnose, explain, and recommend. You do not implement. Others act on your findings.
14
+
15
+ <critical>
16
+ You **MUST** operate as read-only. You **MUST NOT** write, edit, or modify files, nor execute any state-changing commands.
17
+ </critical>
18
+
19
+ <directives>
20
+ - You **MUST** reason from first principles. The caller already tried the obvious.
21
+ - You **MUST** use tools to verify claims. You **MUST NOT** speculate about code behavior — read it.
22
+ - You **MUST** identify root causes, not symptoms. If the caller says "X is broken", determine *why* X is broken.
23
+ - You **MUST** surface hidden assumptions — in the code, in the caller's framing, in the environment.
24
+ - You **SHOULD** consider at least two hypotheses before converging on one.
25
+ - You **SHOULD** invoke tools in parallel when investigating multiple hypotheses.
26
+ - When the problem is architectural, you **MUST** weigh tradeoffs explicitly: what does each option cost, what does it buy, what does it foreclose.
27
+ </directives>
28
+
29
+ <decision-framework>
30
+ Apply pragmatic minimalism:
31
+ - **Bias toward simplicity**: The right solution is the least complex one that fulfills actual requirements. Resist hypothetical future needs.
32
+ - **Leverage what exists**: Favor modifications to current code and established patterns over introducing new components. New dependencies or infrastructure require explicit justification.
33
+ - **One clear path**: Present a single primary recommendation. Mention alternatives only when they offer substantially different tradeoffs worth considering.
34
+ - **Match depth to complexity**: Quick questions get quick answers. Reserve thorough analysis for genuinely complex problems.
35
+ - **Signal the investment**: Tag recommendations with estimated effort — Quick (<1h), Short (1-4h), Medium (1-2d), Large (3d+).
36
+ </decision-framework>
37
+
38
+ <procedure>
39
+ 1. Read the problem statement carefully. Identify what was already tried and why it failed.
40
+ 2. Form 2-3 hypotheses for the root cause.
41
+ 3. Use tools to gather evidence — read relevant code, trace data flow, check types, grep for related patterns. Parallelize independent reads.
42
+ 4. Eliminate hypotheses based on evidence. Narrow to the most likely cause.
43
+ 5. If the problem is a decision (not a bug), lay out options with concrete tradeoffs.
44
+ 6. Deliver a clear verdict with supporting evidence.
45
+ </procedure>
46
+
47
+ <output>
48
+ Structure your response in tiers:
49
+
50
+ **Always include:**
51
+ - **Diagnosis**: What is actually wrong, or what the real tradeoff is. 2-3 sentences.
52
+ - **Evidence**: Specific file paths, line numbers, code excerpts that support your conclusion.
53
+ - **Recommendation**: What to do about it — concrete, actionable, with enough detail that an implementing agent can act without re-investigating. Numbered steps, each 1-2 sentences.
54
+
55
+ **Include when relevant:**
56
+ - **Caveats**: Anything you are not confident about. Uncertainty **MUST** be stated, not hidden.
57
+ - **Risks**: Edge cases, failure modes, or mitigation strategies.
58
+
59
+ **Only when genuinely applicable:**
60
+ - **Escalation triggers**: Conditions that would justify a more complex solution.
61
+ - **Alternative sketch**: High-level outline of an alternative path (not a full design).
62
+
63
+ You **MUST NOT** pad with meta-commentary. Dense and useful beats long and thorough.
64
+ </output>
65
+
66
+ <scope-discipline>
67
+ - Recommend ONLY what was asked. No unsolicited improvements.
68
+ - If you notice other issues, list at most 2 as "Optional future considerations" at the end.
69
+ - You **MUST NOT** expand the problem surface beyond the original request.
70
+ - Exhaust provided context before reaching for tools. External lookups fill genuine gaps, not curiosity.
71
+ </scope-discipline>
72
+
73
+ <critical>
74
+ You **MUST** keep going until you have a clear answer or have exhausted available evidence.
75
+ Before finalizing: re-scan for unstated assumptions, verify claims are grounded in code not invented, check for overly strong language not justified by evidence.
76
+ This matters. The caller is stuck. Get it right.
77
+ </critical>
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: plan
3
3
  description: Software architect for complex multi-file architectural decisions. NOT for simple tasks, single-file changes, or tasks completable in <5 tool calls.
4
- tools: read, grep, find, bash
4
+ tools: read, grep, find, bash, lsp, fetch, web_search, ast_grep
5
5
  spawns: explore
6
6
  model: pi/plan, pi/slow
7
7
  thinking-level: high
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: reviewer
3
3
  description: "Code review specialist for quality/security analysis"
4
- tools: read, grep, find, bash, report_finding
4
+ tools: read, grep, find, bash, lsp, fetch, web_search, ast_grep, report_finding
5
5
  spawns: explore, task
6
6
  model: pi/slow
7
7
  thinking-level: high
@@ -4,22 +4,20 @@ Performs structural AST-aware rewrites via native ast-grep.
4
4
  - Use for codemods and structural rewrites where plain text replace is unsafe
5
5
  - Narrow scope with `path` before replacing (`path` accepts files, directories, or glob patterns)
6
6
  - Default to language-scoped rewrites in mixed repositories: set `lang` and keep `path` narrow
7
- - Always returns a preview; after reviewing, call `resolve` with `action: "apply"` or `action: "discard"`
8
7
  - Treat parse issues as a scoping signal: tighten `path`/`lang` before retrying
9
8
  - Metavariables captured in each rewrite pattern (`$A`, `$$$ARGS`) are substituted into that entry's rewrite template
10
9
  - Each matched rewrite is a 1:1 structural substitution; you cannot split one capture into multiple nodes or merge multiple captures into one node
11
10
  </instruction>
12
11
 
13
12
  <output>
14
- - Returns replacement summary, per-file replacement counts, and change previews
15
- - Reports whether changes were applied or only previewed
13
+ - Returns replacement summary, per-file replacement counts, and change diffs
16
14
  - Includes parse issues when files cannot be processed
17
15
  </output>
18
16
 
19
17
  <examples>
20
- - Rename a call site across a directory, preview first:
18
+ - Rename a call site across a directory:
21
19
  `{"ops":[{"pat":"oldApi($$$ARGS)","out":"newApi($$$ARGS)"}],"lang":"typescript","path":"src/"}`
22
- - Multi-op codemod preview before resolving:
20
+ - Multi-op codemod:
23
21
  `{"ops":[{"pat":"require($A)","out":"import $A"},{"pat":"module.exports = $E","out":"export default $E"}],"lang":"javascript","path":"src/"}`
24
22
  - Swap two arguments using captures:
25
23
  `{"ops":[{"pat":"assertEqual($A, $B)","out":"assertEqual($B, $A)"}],"lang":"typescript","path":"tests/"}`
@@ -28,6 +26,5 @@ Performs structural AST-aware rewrites via native ast-grep.
28
26
  <critical>
29
27
  - `ops` **MUST** contain at least one concrete `{ pat, out }` entry
30
28
  - If the path pattern spans multiple languages, set `lang` explicitly for deterministic rewrites
31
- - Review preview output, then use the `resolve` tool to apply or discard (with a reason)
32
29
  - For one-off local text edits, prefer the Edit tool instead of AST edit
33
30
  </critical>
@@ -6,6 +6,10 @@ Applies precise file edits using `LINE#ID` tags from `read` output.
6
6
  3. You **MUST** submit one `edit` call per file with all operations, think your changes through before submitting.
7
7
  </workflow>
8
8
 
9
+ <prohibited>
10
+ You **MUST NOT** use this tool for formatting-only edits: reindenting, realigning, brace-style changes, whitespace normalization, or line-length wrapping. Any edit whose diff is purely whitespace is a formatting operation — run the appropriate formatter for the project instead.
11
+ </prohibited>
12
+
9
13
  <operations>
10
14
  Every edit has `op`, `pos`, and `lines`. Range replaces also have `end`. Both `pos` and `end` use `"N#ID"` format (e.g. `"23#XY"`).
11
15
  **`pos`** — the anchor line. Meaning depends on `op`:
@@ -43,6 +47,8 @@ Every edit has `op`, `pos`, and `lines`. Range replaces also have `end`. Both `p
43
47
  3. **Range end tag (inclusive):** `end` is inclusive and **MUST** point to the final line being replaced.
44
48
  - If `lines` includes a closing boundary token (`}`, `]`, `)`, `);`, `},`), `end` **MUST** include the original boundary line.
45
49
  - You **MUST NOT** set `end` to an interior line and then re-add the boundary token in `lines`; that duplicates the next surviving line.
50
+ - To remove a line while keeping its neighbors, **delete** it (`lines: null`). You **MUST NOT** replace it with the content of an adjacent line — that line still exists and will be duplicated.
51
+ 4. **Match surrounding indentation:** Leading whitespace in `lines` **MUST** be copied verbatim from adjacent lines in the `read` output. Do not infer or reconstruct indentation from memory — count the actual leading spaces on the lines immediately above and below the insertion or replacement point.
46
52
  </rules>
47
53
 
48
54
  <recovery>
@@ -59,7 +65,7 @@ Every edit has `op`, `pos`, and `lines`. Range replaces also have `end`. Both `p
59
65
  path: "…",
60
66
  edits: [{
61
67
  op: "replace",
62
- pos: "{{hlineref 23 " const timeout: number = 5000;"}}",
68
+ pos: {{hlinejsonref 23 " const timeout: number = 5000;"}},
63
69
  lines: [" const timeout: number = 30_000;"]
64
70
  }]
65
71
  }
@@ -73,7 +79,7 @@ Single line — `lines: null` deletes entirely:
73
79
  path: "…",
74
80
  edits: [{
75
81
  op: "replace",
76
- pos: "{{hlineref 7 "// @ts-ignore"}}",
82
+ pos: {{hlinejsonref 7 "// @ts-ignore"}},
77
83
  lines: null
78
84
  }]
79
85
  }
@@ -84,8 +90,8 @@ Range — add `end`:
84
90
  path: "…",
85
91
  edits: [{
86
92
  op: "replace",
87
- pos: "{{hlineref 80 " // TODO: remove after migration"}}",
88
- end: "{{hlineref 83 " }"}}",
93
+ pos: {{hlinejsonref 80 " // TODO: remove after migration"}},
94
+ end: {{hlinejsonref 83 " }"}},
89
95
  lines: null
90
96
  }]
91
97
  }
@@ -101,7 +107,7 @@ Range — add `end`:
101
107
  path: "…",
102
108
  edits: [{
103
109
  op: "replace",
104
- pos: "{{hlineref 14 " placeholder: \"DO NOT SHIP\","}}",
110
+ pos: {{hlinejsonref 14 " placeholder: \"DO NOT SHIP\","}},
105
111
  lines: [""]
106
112
  }]
107
113
  }
@@ -120,8 +126,8 @@ Range — add `end`:
120
126
  path: "…",
121
127
  edits: [{
122
128
  op: "replace",
123
- pos: "{{hlineref 60 " } catch (err) {"}}",
124
- end: "{{hlineref 63 " }"}}",
129
+ pos: {{hlinejsonref 60 " } catch (err) {"}},
130
+ end: {{hlinejsonref 63 " }"}},
125
131
  lines: [
126
132
  " } catch (err) {",
127
133
  " if (isEnoent(err)) return null;",
@@ -146,8 +152,8 @@ Bad — `end` stops before `}` while `lines` already includes `}`:
146
152
  path: "…",
147
153
  edits: [{
148
154
  op: "replace",
149
- pos: "{{hlineref 70 "if (ok) {"}}",
150
- end: "{{hlineref 71 " run();"}}",
155
+ pos: {{hlinejsonref 70 "if (ok) {"}},
156
+ end: {{hlinejsonref 71 " run();"}},
151
157
  lines: [
152
158
  "if (ok) {",
153
159
  " runSafe();",
@@ -162,8 +168,8 @@ Good — include original `}` in the replaced range when replacement keeps `}`:
162
168
  path: "…",
163
169
  edits: [{
164
170
  op: "replace",
165
- pos: "{{hlineref 70 "if (ok) {"}}",
166
- end: "{{hlineref 72 "}"}}",
171
+ pos: {{hlinejsonref 70 "if (ok) {"}},
172
+ end: {{hlinejsonref 72 "}"}},
167
173
  lines: [
168
174
  "if (ok) {",
169
175
  " runSafe();",
@@ -190,7 +196,7 @@ Also apply the same rule to `);`, `],`, and `},` closers: if replacement include
190
196
  path: "…",
191
197
  edits: [{
192
198
  op: "prepend",
193
- pos: "{{hlineref 48 "function y() {"}}",
199
+ pos: {{hlinejsonref 48 "function y() {"}},
194
200
  lines: [
195
201
  "function z() {",
196
202
  " runZ();",
@@ -230,7 +236,7 @@ Good — anchors to structural line:
230
236
  path: "…",
231
237
  edits: [{
232
238
  op: "prepend",
233
- pos: "{{hlineref 103 "export function serialize(data: unknown): string {"}}",
239
+ pos: {{hlinejsonref 103 "export function serialize(data: unknown): string {"}},
234
240
  lines: [
235
241
  "function validate(data: unknown): boolean {",
236
242
  " return data != null && typeof data === \"object\";",
@@ -242,10 +248,51 @@ Good — anchors to structural line:
242
248
  ```
243
249
  </example>
244
250
 
251
+ <example name="indentation must match context">
252
+ Leading whitespace in `lines` **MUST** be copied from the `read` output, not reconstructed from memory. Check the actual indent of neighboring lines.
253
+ ```ts
254
+ {{hlinefull 10 "class Foo {"}}
255
+ {{hlinefull 11 " bar() {"}}
256
+ {{hlinefull 12 " return 1;"}}
257
+ {{hlinefull 13 " }"}}
258
+ {{hlinefull 14 "}"}}
259
+ ```
260
+ Bad — indent guessed as 4 spaces instead of 2 (as seen on lines 11–13):
261
+ ```
262
+ {
263
+ path: "…",
264
+ edits: [{
265
+ op: "prepend",
266
+ pos: {{hlinejsonref 14 "}"}},
267
+ lines: [
268
+ " baz() {",
269
+ " return 2;",
270
+ " }"
271
+ ]
272
+ }]
273
+ }
274
+ ```
275
+ Good — indent matches the 2-space style visible on adjacent lines:
276
+ ```
277
+ {
278
+ path: "…",
279
+ edits: [{
280
+ op: "prepend",
281
+ pos: {{hlinejsonref 14 "}"}},
282
+ lines: [
283
+ " baz() {",
284
+ " return 2;",
285
+ " }"
286
+ ]
287
+ }]
288
+ }
289
+ ```
290
+ </example>
291
+
245
292
  <critical>
246
293
  - Edit payload: `{ path, edits[] }`. Each entry: `op`, `lines`, optional `pos`/`end`. No extra keys.
247
294
  - Every tag **MUST** be copied exactly from fresh tool result as `N#ID`.
248
295
  - You **MUST** re-read after each edit call before issuing another on same file.
249
- - Formatting is a batch operation. You **MUST** never use this tool for formatting.
296
+ - Formatting is a batch operation. You **MUST NOT** use this tool to reformat, reindent, or adjust whitespace — run the project's formatter instead. If the only change is whitespace, it is formatting; do not touch it.
250
297
  - `lines` entries **MUST** be literal file content with real space indentation. (`\\t` in JSON inserts a literal backslash-t into the file, not a tab.)
251
298
  </critical>
@@ -291,6 +291,7 @@ export class AgentSession {
291
291
 
292
292
  // Event subscription state
293
293
  #unsubscribeAgent?: () => void;
294
+ #unsubscribePendingActionPush?: () => void;
294
295
  #eventListeners: AgentSessionEventListener[] = [];
295
296
 
296
297
  /** Tracks pending steering messages for UI display. Removed when delivered. */
@@ -397,6 +398,21 @@ export class AgentSession {
397
398
  this.#obfuscator = config.obfuscator;
398
399
  this.agent.providerSessionState = this.#providerSessionState;
399
400
  this.#pendingActionStore = config.pendingActionStore;
401
+ this.#unsubscribePendingActionPush = this.#pendingActionStore?.subscribePush(action => {
402
+ const reminderText = [
403
+ "<system-reminder>",
404
+ "This is a preview. Call the `resolve` tool to apply or discard these changes.",
405
+ "</system-reminder>",
406
+ ].join("\n");
407
+ this.agent.steer({
408
+ role: "custom",
409
+ customType: "resolve-reminder",
410
+ content: reminderText,
411
+ display: false,
412
+ details: { toolName: action.sourceToolName },
413
+ timestamp: Date.now(),
414
+ });
415
+ });
400
416
  this.#syncTodoPhasesFromBranch();
401
417
 
402
418
  // Always subscribe to agent events for internal handling
@@ -688,22 +704,6 @@ export class AgentSession {
688
704
  { deliverAs: "nextTurn" },
689
705
  );
690
706
  }
691
- if (!isError && this.#pendingActionStore?.hasPending) {
692
- const reminderText = [
693
- "<system-reminder>",
694
- "This is a preview. Call the `resolve` tool to apply or discard these changes.",
695
- "</system-reminder>",
696
- ].join("\n");
697
- await this.sendCustomMessage(
698
- {
699
- customType: "resolve-reminder",
700
- content: reminderText,
701
- display: false,
702
- details: { toolName },
703
- },
704
- { deliverAs: "nextTurn" },
705
- );
706
- }
707
707
  }
708
708
  }
709
709
 
@@ -1443,6 +1443,8 @@ export class AgentSession {
1443
1443
  state.close();
1444
1444
  }
1445
1445
  this.#providerSessionState.clear();
1446
+ this.#unsubscribePendingActionPush?.();
1447
+ this.#unsubscribePendingActionPush = undefined;
1446
1448
  this.#disconnectFromAgent();
1447
1449
  this.#eventListeners = [];
1448
1450
  }
@@ -9,6 +9,8 @@ import designerMd from "../prompts/agents/designer.md" with { type: "text" };
9
9
  import exploreMd from "../prompts/agents/explore.md" with { type: "text" };
10
10
  // Embed agent markdown files at build time
11
11
  import agentFrontmatterTemplate from "../prompts/agents/frontmatter.md" with { type: "text" };
12
+ import librarianMd from "../prompts/agents/librarian.md" with { type: "text" };
13
+ import oracleMd from "../prompts/agents/oracle.md" with { type: "text" };
12
14
  import planMd from "../prompts/agents/plan.md" with { type: "text" };
13
15
  import reviewerMd from "../prompts/agents/reviewer.md" with { type: "text" };
14
16
  import taskMd from "../prompts/agents/task.md" with { type: "text" };
@@ -42,6 +44,8 @@ const EMBEDDED_AGENT_DEFS: EmbeddedAgentDef[] = [
42
44
  { fileName: "plan.md", template: planMd },
43
45
  { fileName: "designer.md", template: designerMd },
44
46
  { fileName: "reviewer.md", template: reviewerMd },
47
+ { fileName: "oracle.md", template: oracleMd },
48
+ { fileName: "librarian.md", template: librarianMd },
45
49
  {
46
50
  fileName: "task.md",
47
51
  frontmatter: {
@@ -10,9 +10,14 @@ export interface PendingAction {
10
10
 
11
11
  export class PendingActionStore {
12
12
  #actions: PendingAction[] = [];
13
+ #pushListeners = new Set<(action: PendingAction, count: number) => void>();
13
14
 
14
15
  push(action: PendingAction): void {
15
16
  this.#actions.push(action);
17
+ const count = this.#actions.length;
18
+ for (const listener of this.#pushListeners) {
19
+ listener(action, count);
20
+ }
16
21
  }
17
22
 
18
23
  peek(): PendingAction | null {
@@ -23,10 +28,21 @@ export class PendingActionStore {
23
28
  return this.#actions.pop() ?? null;
24
29
  }
25
30
 
31
+ subscribePush(listener: (action: PendingAction, count: number) => void): () => void {
32
+ this.#pushListeners.add(listener);
33
+ return () => {
34
+ this.#pushListeners.delete(listener);
35
+ };
36
+ }
37
+
26
38
  clear(): void {
27
39
  this.#actions = [];
28
40
  }
29
41
 
42
+ get count(): number {
43
+ return this.#actions.length;
44
+ }
45
+
30
46
  get hasPending(): boolean {
31
47
  return this.#actions.length > 0;
32
48
  }