@oh-my-pi/pi-coding-agent 15.8.3 → 15.9.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 (172) hide show
  1. package/CHANGELOG.md +113 -31
  2. package/dist/types/capability/skill.d.ts +7 -0
  3. package/dist/types/cli/update-cli.d.ts +15 -1
  4. package/dist/types/config/append-only-context-mode.d.ts +8 -0
  5. package/dist/types/config/model-registry.d.ts +3 -0
  6. package/dist/types/config/models-config-schema.d.ts +15 -0
  7. package/dist/types/config/settings-schema.d.ts +38 -24
  8. package/dist/types/debug/protocol-probe.d.ts +38 -0
  9. package/dist/types/debug/terminal-info.d.ts +34 -0
  10. package/dist/types/exa/mcp-client.d.ts +2 -1
  11. package/dist/types/export/html/template.generated.d.ts +1 -1
  12. package/dist/types/extensibility/custom-tools/types.d.ts +1 -1
  13. package/dist/types/extensibility/extensions/types.d.ts +11 -0
  14. package/dist/types/extensibility/shared-events.d.ts +1 -1
  15. package/dist/types/index.d.ts +1 -0
  16. package/dist/types/mcp/json-rpc.d.ts +6 -1
  17. package/dist/types/mcp/tool-bridge.d.ts +4 -0
  18. package/dist/types/mcp/transports/stdio.d.ts +16 -7
  19. package/dist/types/mnemopi/state.d.ts +2 -2
  20. package/dist/types/modes/components/agent-dashboard.d.ts +1 -0
  21. package/dist/types/modes/components/assistant-message.d.ts +3 -2
  22. package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -0
  23. package/dist/types/modes/components/hook-selector.d.ts +11 -0
  24. package/dist/types/modes/components/plugin-settings.d.ts +40 -8
  25. package/dist/types/modes/components/session-selector.d.ts +8 -3
  26. package/dist/types/modes/components/settings-selector.d.ts +1 -1
  27. package/dist/types/modes/components/todo-reminder.d.ts +1 -1
  28. package/dist/types/modes/components/transcript-container.d.ts +36 -0
  29. package/dist/types/modes/interactive-mode.d.ts +2 -1
  30. package/dist/types/modes/rpc/rpc-types.d.ts +1 -1
  31. package/dist/types/modes/theme/theme.d.ts +20 -1
  32. package/dist/types/modes/utils/keybinding-matchers.d.ts +4 -0
  33. package/dist/types/plan-mode/plan-handoff.d.ts +20 -0
  34. package/dist/types/session/agent-session.d.ts +5 -2
  35. package/dist/types/session/history-storage.d.ts +3 -4
  36. package/dist/types/session/indexed-session-storage.d.ts +59 -0
  37. package/dist/types/session/messages.d.ts +1 -0
  38. package/dist/types/session/redis-session-storage.d.ts +12 -85
  39. package/dist/types/session/session-manager.d.ts +21 -0
  40. package/dist/types/session/session-storage.d.ts +5 -7
  41. package/dist/types/session/sql-session-storage.d.ts +16 -85
  42. package/dist/types/slash-commands/types.d.ts +17 -4
  43. package/dist/types/task/executor.d.ts +9 -0
  44. package/dist/types/task/index.d.ts +3 -1
  45. package/dist/types/telemetry-export.d.ts +19 -0
  46. package/dist/types/tiny/compiled-runtime.d.ts +35 -0
  47. package/dist/types/tiny/text.d.ts +17 -0
  48. package/dist/types/tools/ask.d.ts +1 -0
  49. package/dist/types/tools/index.d.ts +4 -2
  50. package/dist/types/tools/path-utils.d.ts +1 -1
  51. package/dist/types/tools/search.d.ts +2 -2
  52. package/dist/types/tools/{todo-write.d.ts → todo.d.ts} +18 -18
  53. package/dist/types/utils/session-color.d.ts +7 -2
  54. package/dist/types/web/search/index.d.ts +1 -1
  55. package/dist/types/web/search/provider.d.ts +2 -2
  56. package/dist/types/web/search/providers/base.d.ts +14 -0
  57. package/dist/types/web/search/providers/exa.d.ts +9 -0
  58. package/dist/types/web/search/providers/perplexity.d.ts +2 -2
  59. package/dist/types/web/search/types.d.ts +66 -1
  60. package/package.json +15 -9
  61. package/scripts/build-binary.ts +12 -0
  62. package/src/capability/skill.ts +7 -0
  63. package/src/cli/args.ts +1 -1
  64. package/src/cli/session-picker.ts +1 -0
  65. package/src/cli/update-cli.ts +54 -2
  66. package/src/commands/completions.ts +1 -1
  67. package/src/config/append-only-context-mode.ts +37 -0
  68. package/src/config/models-config-schema.ts +1 -0
  69. package/src/config/settings-schema.ts +24 -57
  70. package/src/debug/index.ts +67 -1
  71. package/src/debug/protocol-probe.ts +267 -0
  72. package/src/debug/terminal-info.ts +127 -0
  73. package/src/exa/mcp-client.ts +11 -5
  74. package/src/export/html/template.generated.ts +1 -1
  75. package/src/export/html/template.js +3 -3
  76. package/src/extensibility/custom-tools/types.ts +1 -1
  77. package/src/extensibility/extensions/types.ts +11 -0
  78. package/src/extensibility/shared-events.ts +1 -1
  79. package/src/extensibility/skills.ts +3 -3
  80. package/src/index.ts +1 -0
  81. package/src/internal-urls/docs-index.generated.ts +7 -7
  82. package/src/main.ts +16 -2
  83. package/src/mcp/json-rpc.ts +8 -0
  84. package/src/mcp/render.ts +3 -0
  85. package/src/mcp/tool-bridge.ts +10 -2
  86. package/src/mcp/transports/http.ts +33 -16
  87. package/src/mcp/transports/stdio.ts +37 -12
  88. package/src/mnemopi/state.ts +4 -4
  89. package/src/modes/acp/acp-agent.ts +168 -3
  90. package/src/modes/acp/acp-event-mapper.ts +7 -7
  91. package/src/modes/components/agent-dashboard.ts +103 -31
  92. package/src/modes/components/assistant-message.ts +3 -2
  93. package/src/modes/components/extensions/extension-dashboard.ts +56 -10
  94. package/src/modes/components/history-search.ts +128 -14
  95. package/src/modes/components/hook-selector.ts +149 -14
  96. package/src/modes/components/plugin-settings.ts +270 -36
  97. package/src/modes/components/session-selector.ts +81 -19
  98. package/src/modes/components/settings-selector.ts +1 -1
  99. package/src/modes/components/status-line/segments.ts +2 -1
  100. package/src/modes/components/status-line.ts +1 -1
  101. package/src/modes/components/tips.txt +6 -2
  102. package/src/modes/components/todo-reminder.ts +1 -1
  103. package/src/modes/components/tool-execution.ts +9 -4
  104. package/src/modes/components/transcript-container.ts +109 -0
  105. package/src/modes/controllers/command-controller.ts +4 -3
  106. package/src/modes/controllers/event-controller.ts +12 -7
  107. package/src/modes/controllers/extension-ui-controller.ts +3 -0
  108. package/src/modes/controllers/input-controller.ts +10 -5
  109. package/src/modes/controllers/selector-controller.ts +30 -19
  110. package/src/modes/controllers/todo-command-controller.ts +1 -1
  111. package/src/modes/interactive-mode.ts +56 -9
  112. package/src/modes/print-mode.ts +5 -0
  113. package/src/modes/rpc/rpc-types.ts +1 -1
  114. package/src/modes/theme/theme.ts +48 -8
  115. package/src/modes/utils/keybinding-matchers.ts +10 -0
  116. package/src/modes/utils/ui-helpers.ts +1 -0
  117. package/src/plan-mode/plan-handoff.ts +37 -0
  118. package/src/priority.json +4 -0
  119. package/src/prompts/goals/goal-continuation.md +1 -1
  120. package/src/prompts/steering/user-interjection.md +10 -0
  121. package/src/prompts/system/agent-creation-architect.md +1 -26
  122. package/src/prompts/system/eager-todo.md +3 -3
  123. package/src/prompts/system/orchestrate-notice.md +5 -5
  124. package/src/prompts/system/plan-mode-approved.md +14 -17
  125. package/src/prompts/system/plan-mode-reference.md +3 -6
  126. package/src/prompts/system/subagent-system-prompt.md +11 -0
  127. package/src/prompts/system/system-prompt.md +143 -145
  128. package/src/prompts/system/title-system.md +3 -2
  129. package/src/prompts/system/workflow-notice.md +1 -1
  130. package/src/prompts/tools/browser.md +33 -30
  131. package/src/prompts/tools/render-mermaid.md +2 -2
  132. package/src/prompts/tools/search.md +1 -1
  133. package/src/prompts/tools/task.md +3 -1
  134. package/src/prompts/tools/{todo-write.md → todo.md} +5 -5
  135. package/src/sdk.ts +6 -21
  136. package/src/session/agent-session.ts +44 -21
  137. package/src/session/history-storage.ts +11 -18
  138. package/src/session/indexed-session-storage.ts +430 -0
  139. package/src/session/messages.ts +80 -0
  140. package/src/session/redis-session-storage.ts +66 -377
  141. package/src/session/session-manager.ts +109 -23
  142. package/src/session/session-storage.ts +148 -68
  143. package/src/session/sql-session-storage.ts +131 -382
  144. package/src/slash-commands/helpers/todo.ts +2 -2
  145. package/src/slash-commands/types.ts +27 -10
  146. package/src/task/executor.ts +9 -1
  147. package/src/task/index.ts +51 -1
  148. package/src/telemetry-export.ts +126 -0
  149. package/src/tiny/compiled-runtime.ts +179 -0
  150. package/src/tiny/text.ts +112 -1
  151. package/src/tiny/worker.ts +24 -2
  152. package/src/tools/ask.ts +133 -87
  153. package/src/tools/fetch.ts +17 -4
  154. package/src/tools/find.ts +2 -2
  155. package/src/tools/index.ts +6 -4
  156. package/src/tools/memory-recall.ts +1 -1
  157. package/src/tools/memory-reflect.ts +1 -1
  158. package/src/tools/path-utils.ts +16 -7
  159. package/src/tools/renderers.ts +2 -2
  160. package/src/tools/search.ts +6 -3
  161. package/src/tools/ssh.ts +26 -10
  162. package/src/tools/{todo-write.ts → todo.ts} +32 -35
  163. package/src/tools/write.ts +14 -2
  164. package/src/tui/status-line.ts +15 -4
  165. package/src/utils/session-color.ts +39 -14
  166. package/src/utils/title-generator.ts +9 -2
  167. package/src/web/search/index.ts +4 -2
  168. package/src/web/search/provider.ts +19 -35
  169. package/src/web/search/providers/base.ts +17 -0
  170. package/src/web/search/providers/exa.ts +111 -7
  171. package/src/web/search/providers/perplexity.ts +8 -4
  172. package/src/web/search/types.ts +74 -32
@@ -1,2 +1,3 @@
1
- Generate a 3-6 word title for a coding session from the user's first message. Capture the main task or topic.
2
- Output ONLY the title. No quotes or trailing punctuation.
1
+ Need generate 3-6 word title from first message; capture main task
2
+ Output title only; no quotes no punctuation
3
+ If message has no concrete task yet (greeting, small talk, vague), output exactly: none
@@ -62,7 +62,7 @@ Scale to the ask: "find any bugs" → a few finders, single-vote verify. "thorou
62
62
  </patterns>
63
63
 
64
64
  <execution>
65
- - Decompose the surface first; capture it in `todo_write` when it spans phases.
65
+ - Decompose the surface first; capture it in `todo` when it spans phases.
66
66
  - Prefer `schema=` for any agent whose output you branch on.
67
67
  - After a fan-out returns, YOU own correctness: read the artifacts, run the gate, verify before acting. Subagents do the legwork; they don't get the last word.
68
68
  - Keep going until the task is closed — a returned fan-out is a step, not a stopping point.
@@ -1,41 +1,41 @@
1
- Drives a real Chromium tab with full puppeteer access via JS execution.
1
+ Drives real Chromium tab; full puppeteer access via JS execution.
2
2
 
3
3
  <instruction>
4
- - For static web content (articles, docs, issues/PRs, JSON, PDFs, feeds), prefer the `read` tool with a URL — reader-mode text without spinning up a browser. Use this tool when you need JS execution, authentication, or interactive actions.
4
+ - For static web content (articles, docs, issues/PRs, JSON, PDFs, feeds), prefer `read` tool with URL — reader-mode text without spinning up browser. Use this tool when Need JS execution, authentication, or interactive actions.
5
5
  - Three actions only:
6
- - `open` — acquire (or reuse) a named tab. `name` defaults to `"main"`. Optional `url` navigates after the tab is ready. Optional `viewport` sets dimensions. Optional `dialogs: "accept" | "dismiss"` auto-handles `alert`/`confirm`/`beforeunload` so navigation/clicks don't hang (default: leave dialogs unhandled — page hangs until caller wires `page.on('dialog', …)`).
7
- - `close` — release a tab by `name`, or every tab with `all: true`. For spawned-app browsers, set `kill: true` to terminate the process tree (default leaves it running).
8
- - `run` — execute JS against an existing tab. `code` is the body of an async function with `page`, `browser`, `tab`, `display`, `assert`, `wait` in scope. The function's return value is JSON-stringified into the tool result; multiple `display(value)` calls accumulate text/images.
6
+ - `open` — acquire or reuse named tab. `name` defaults `"main"`. Optional `url` navigates after tab ready. Optional `viewport` sets dimensions. Optional `dialogs: "accept" | "dismiss"` auto-handles `alert`/`confirm`/`beforeunload` so navigation/clicks don't hang (default: leave dialogs unhandled — page hangs until caller wires `page.on('dialog', …)`).
7
+ - `close` — release tab by `name`, or every tab with `all: true`. For spawned-app browsers, set `kill: true` to terminate process tree (default leaves running).
8
+ - `run` — execute JS against existing tab. `code` is body of async function with `page`, `browser`, `tab`, `display`, `assert`, `wait` in scope. Function's return value JSON-stringified into tool result; multiple `display(value)` calls accumulate text/images.
9
9
  - Tabs survive across `run` calls and across in-process subagents. Open once, reuse many times.
10
- - Browser kinds, selected by the `app` field on `open`:
10
+ - Browser kinds, selected by `app` field on `open`:
11
11
  - default (no `app`) → headless Chromium with stealth patches.
12
- - `app.path` → spawn an absolute binary (Electron/CDP). If a running instance already exposes a CDP port, it is reused; otherwise stale instances are killed and a fresh one is spawned. No stealth patches — never tamper with a real desktop app.
13
- - `app.cdp_url` → connect to an existing CDP endpoint (e.g. `http://127.0.0.1:9222`).
14
- - `app.target` (with `path`/`cdp_url`) — substring matched against url+title to pick a BrowserWindow when the app exposes several.
15
- - Inside `run`, `tab` exposes high-level helpers; reach for `page` (raw puppeteer Page) when you need anything they don't cover.
16
- - `tab.goto(url, { waitUntil? })` — clears the element cache and navigates.
17
- - `tab.observe({ includeAll?, viewportOnly? })` — accessibility snapshot. Returns `{ url, title, viewport, scroll, elements: [{ id, role, name, value, states, … }] }`. Element ids are stable until the next observe/goto.
18
- - `tab.id(n)` — resolves an element id from the most recent observe to a real `ElementHandle` you can `.click()`, `.type()`, etc.
12
+ - `app.path` → spawn absolute binary (Electron/CDP). If running instance already exposes CDP port, reused; otherwise stale instances killed, fresh one spawned. No stealth patches — NEVER tamper with real desktop app.
13
+ - `app.cdp_url` → connect to existing CDP endpoint (e.g. `http://127.0.0.1:9222`).
14
+ - `app.target` (with `path`/`cdp_url`) — substring matched against url+title to pick BrowserWindow when app exposes several.
15
+ - Inside `run`, `tab` exposes high-level helpers; reach for `page` (raw puppeteer Page) when Need anything they don't cover.
16
+ - `tab.goto(url, { waitUntil? })` — clears element cache and navigates.
17
+ - `tab.observe({ includeAll?, viewportOnly? })` — accessibility snapshot. Returns `{ url, title, viewport, scroll, elements: [{ id, role, name, value, states, … }] }`. Element ids stable until next observe/goto.
18
+ - `tab.id(n)` — resolves element id from most recent observe to real `ElementHandle` you can `.click()`, `.type()`, etc.
19
19
  - `tab.click(selector)` / `tab.type(selector, text)` / `tab.fill(selector, value)` / `tab.press(key, { selector? })` / `tab.scroll(dx, dy)` — selector-based actions.
20
- - `tab.waitFor(selector)` — waits until the selector is attached, returns the resolved `ElementHandle` for chaining (e.g. `const btn = await tab.waitFor('text/Submit'); await btn.click();`).
21
- - `tab.drag(from, to)` — drag from one point to another. Each endpoint is either a selector string (drag center-to-center) or a `{ x, y }` viewport-coordinate point (e.g. for canvases, sliders).
22
- - `tab.scrollIntoView(selector)` — scroll the matching element to the center of the viewport (use before clicking off-screen elements).
23
- - `tab.select(selector, …values)` — set the selected option(s) on a `<select>`. Returns the values that ended up selected. `tab.fill` NEVER works for selects.
24
- - `tab.uploadFile(selector, …filePaths)` — attach files to an `<input type="file">`. Paths resolve relative to cwd.
25
- - `tab.waitForUrl(pattern, { timeout? })` — pattern is a substring or `RegExp`. Polls `location.href` so it works for SPA pushState navigations, not just real navigations. Returns the matched URL.
26
- - `tab.waitForResponse(pattern, { timeout? })` — pattern is a substring, `RegExp`, or `(response) => boolean`. Returns the raw puppeteer `HTTPResponse` (call `.text()` / `.json()` / `.status()` / `.headers()` on it).
27
- - `tab.evaluate(fn, …args)` — sugar for `page.evaluate` with the abort signal already wired. Use this instead of dropping to `page.evaluate` for ad-hoc DOM reads.
28
- - `tab.screenshot({ selector?, fullPage?, save?, silent? })` — auto-attaches the image to the tool output unless `silent: true`. Saves full-res to `save` (or `browser.screenshotDir` setting) and a downscaled copy to the model.
20
+ - `tab.waitFor(selector)` — waits until selector attached, returns resolved `ElementHandle` for chaining (e.g. `const btn = await tab.waitFor('text/Submit'); await btn.click();`).
21
+ - `tab.drag(from, to)` — drag from one point to another. Each endpoint either selector string (drag center-to-center) or `{ x, y }` viewport-coordinate point (for canvases, sliders).
22
+ - `tab.scrollIntoView(selector)` — scroll matching element to center of viewport (use before clicking off-screen elements).
23
+ - `tab.select(selector, …values)` — set selected option(s) on `<select>`. Returns values that ended up selected. `tab.fill` NEVER works for selects.
24
+ - `tab.uploadFile(selector, …filePaths)` — attach files to `<input type="file">`. Paths resolve relative to cwd.
25
+ - `tab.waitForUrl(pattern, { timeout? })` — pattern substring or `RegExp`. Polls `location.href` so works for SPA pushState navigations, not just real navigations. Returns matched URL.
26
+ - `tab.waitForResponse(pattern, { timeout? })` — pattern substring, `RegExp`, or `(response) => boolean`. Returns raw puppeteer `HTTPResponse` (call `.text()` / `.json()` / `.status()` / `.headers()` on it).
27
+ - `tab.evaluate(fn, …args)` — sugar for `page.evaluate` with abort signal already wired. Use this instead of dropping to `page.evaluate` for ad-hoc DOM reads.
28
+ - `tab.screenshot({ selector?, fullPage?, save?, silent? })` — captures screenshot and **auto-attaches to tool output for you to view** (unless `silent: true`). `save` is **strictly optional**: OMIT when you just want to look at page — downscaled image shown regardless, full-res capture written to temp file automatically. Pass `save` (a path) ONLY when deliberately need to keep full-res copy on disk for later use; `browser.screenshotDir` does same for every shot. NEVER invent `save` path for throwaway/temporal screenshot.
29
29
  - `tab.extract(format = "markdown")` — Readability-extracted page content.
30
- - Selectors accept CSS as well as puppeteer query handlers: `aria/Sign in`, `text/Continue`, `xpath/…`, `pierce/…`. Playwright-style `p-aria/[name="…"]`, `p-text/…`, etc. are normalized.
31
- - Default to `tab.observe()` over `tab.screenshot()` for understanding page state. Screenshot only when visual appearance matters.
30
+ - Selectors accept CSS plus puppeteer query handlers: `aria/Sign in`, `text/Continue`, `xpath/…`, `pierce/…`. Playwright-style `p-aria/[name="…"]`, `p-text/…` normalized.
31
+ - Default `tab.observe()` over `tab.screenshot()` for page state. Screenshot only when visual appearance matters.
32
32
  </instruction>
33
33
 
34
34
  <critical>
35
- - You MUST call `open` before `run`. `run` does not implicitly create a tab.
36
- - You NEVER screenshot just to "see what's on the page" — `tab.observe()` returns structured data with element ids you can act on immediately.
37
- - After a `tab.goto()` or any navigation, prior element ids from `tab.observe()` are invalidated. Re-observe before referencing them.
38
- - `code` runs with full Node access. Treat it as your code, not sandboxed code.
35
+ - MUST call `open` before `run`. `run` does not implicitly create tab.
36
+ - NEVER screenshot just to "see what's on page" — `tab.observe()` returns structured data with element ids you can act on immediately.
37
+ - After `tab.goto()` or any navigation, prior element ids from `tab.observe()` invalidated. Re-observe before referencing them.
38
+ - `code` runs with full Node access. Treat as your code, not sandboxed code.
39
39
  </critical>
40
40
 
41
41
  <examples>
@@ -46,7 +46,10 @@ Drives a real Chromium tab with full puppeteer access via JS execution.
46
46
  # Click an observed element by id
47
47
  `{"action":"run","name":"docs","code":"const obs = await tab.observe(); const link = obs.elements.find(e => e.role === 'link' && e.name === 'Sign in'); assert(link, 'Sign in link missing'); await (await tab.id(link.id)).click();"}`
48
48
 
49
- # Save a full-page screenshot to disk
49
+ # Take a transient screenshot just to look at the page — NO save path needed; the image is shown to you
50
+ `{"action":"run","name":"docs","code":"await tab.screenshot();"}`
51
+
52
+ # Persist a full-page screenshot to disk (only when you deliberately need to keep the file)
50
53
  `{"action":"run","name":"docs","code":"await tab.screenshot({ fullPage: true, save: 'screenshot.png' });"}`
51
54
 
52
55
  # Fill and submit a form via selectors
@@ -66,5 +69,5 @@ Drives a real Chromium tab with full puppeteer access via JS execution.
66
69
  </examples>
67
70
 
68
71
  <output>
69
- - Per call: any `display(value)` outputs (text/images) followed by the JSON-stringified return value of the `code` function. `run` always produces at least a status line.
72
+ - Per call: any `display(value)` outputs (text/images) followed by JSON-stringified return value of `code` function. `run` always produces at least status line.
70
73
  </output>
@@ -5,5 +5,5 @@ Parameters:
5
5
  - `config` (optional): JSON render configuration (spacing and layout options).
6
6
  Behavior:
7
7
  - Returns ASCII diagram text.
8
- - Saves full output to `artifact://<id>` when storage is available.
9
- - Returns error when Mermaid input is invalid or rendering fails.
8
+ - Saves full output to `artifact://<id>` when storage available.
9
+ - Returns error when Mermaid input invalid or rendering fails.
@@ -2,7 +2,7 @@ Searches files using powerful regex matching.
2
2
 
3
3
  <instruction>
4
4
  - Supports Rust regex syntax (RE2-style — no lookaround or backreferences). Use line anchors or post-filters instead of (?!…)/(?<!…)
5
- - `paths` is required and accepts either one string or an array of files, directories, globs, or internal URLs
5
+ - `paths` accepts either one string or an array of files, directories, globs, or internal URLs. Optional: when omitted or empty it searches the workspace root (`.`). Prefer scoping to specific paths when you know them.
6
6
  - For multiple targets, pass an array with one target per element: `["src", "tests"]`.
7
7
  - Cross-line patterns are detected from literal `\n` or escaped `\\n` in `pattern`
8
8
  </instruction>
@@ -38,6 +38,8 @@ Subagents have no conversation history. Every fact, file path, and direction the
38
38
  - Pass large payloads via `local://<path>` URIs, not inline. {{#if contextEnabled}} (other than the context){{/if}}
39
39
  {{#if contextEnabled}}- Put shared constraints in `context` once; do not duplicate across assignments.{{/if}}
40
40
  - Prefer agents that investigate **and** edit in one pass; only spin a read-only discovery step when affected files are genuinely unknown.
41
+ - **Read-only agents**: Agents tagged READ-ONLY (e.g. `explore`) have no edit/write/command tools. NEVER hand them an assignment that requires changing files or running commands — they cannot do it and the turn is wasted. Use them to investigate and report back; do the edits yourself or delegate to a writing agent (`task`, `oracle`, `designer`).
42
+ - **No reasoning offload**: NEVER offload reasoning, analysis, design, or decision-making to `quick_task` or `explore` — they run minimal-effort / small models for mechanical lookups and data collection only. Keep judgment and synthesis in your own context; delegate hard thinking to `task`, `plan`, or `oracle`.
41
43
  </rules>
42
44
 
43
45
  <parallelization>
@@ -71,7 +73,7 @@ Parallel when tasks touch disjoint files or are independent refactors/tests.
71
73
  Agent spawning is disabled for this context.
72
74
  {{else}}
73
75
  {{#list agents join="\n"}}
74
- # {{name}}
76
+ # {{name}}{{#if readOnly}} — READ-ONLY (no edit/write/exec tools){{/if}}
75
77
  {{description}}
76
78
  {{/list}}
77
79
  {{/if}}
@@ -50,9 +50,9 @@ Allowed `op` values are only `init`, `start`, `done`, `drop`, `rm`, `append`, an
50
50
  </examples>
51
51
 
52
52
  <critical>
53
- When the user hands you a multi-step plan — a phased todo, a numbered or bulleted checklist, or "N bugs/items/tasks" to work through:
54
- - You MUST `init` the list with EVERY item as its own task before doing the work.
55
- - Enumerate all of them;
56
- - NEVER summarize the plan into fewer tasks, sample "the important ones", drop items, or rely on memory to track the rest.
53
+ When the user hands you a multi-step plan — a phased todo, a numbered or bulleted checklist, or "N bugs/items/tasks" to work through:
54
+ - You MUST `init` the list with EVERY item as its own task before doing the work.
55
+ - Enumerate all of them;
56
+ - NEVER summarize the plan into fewer tasks, sample "the important ones", drop items, or rely on memory to track the rest.
57
57
  The entire point is to remember every one.
58
- </critical>
58
+ </critical>
package/src/sdk.ts CHANGED
@@ -39,6 +39,7 @@ import { createAutoresearchExtension } from "./autoresearch";
39
39
  import { loadCapability } from "./capability";
40
40
  import { type Rule, ruleCapability, setActiveRules } from "./capability/rule";
41
41
  import { bucketRules } from "./capability/rule-buckets";
42
+ import { shouldEnableAppendOnlyContext } from "./config/append-only-context-mode";
42
43
  import { ModelRegistry } from "./config/model-registry";
43
44
  import {
44
45
  formatModelString,
@@ -101,7 +102,7 @@ import {
101
102
  import { AgentSession } from "./session/agent-session";
102
103
  import { resolveAuthBrokerConfig } from "./session/auth-broker-config";
103
104
  import { AuthBrokerClient, AuthStorage, RemoteAuthCredentialStore } from "./session/auth-storage";
104
- import { type CustomMessage, convertToLlm } from "./session/messages";
105
+ import { type CustomMessage, convertToLlm, wrapSteeringForModel } from "./session/messages";
105
106
  import { getRestorableSessionModels, SessionManager } from "./session/session-manager";
106
107
  import { closeAllConnections } from "./ssh/connection-manager";
107
108
  import { unmountAll } from "./ssh/sshfs-mount";
@@ -646,24 +647,6 @@ function registerPythonCleanup(): void {
646
647
  postmortem.register("python-cleanup", disposeAllKernelSessions);
647
648
  }
648
649
 
649
- /**
650
- * Resolve whether to enable append-only context mode based on the setting and provider.
651
- *
652
- * - `"on"` → always enable
653
- * - `"off"` → never enable
654
- * - `"auto"` → enable for DeepSeek (prefix-caching provider)
655
- */
656
- function resolveAppendOnlyMode(setting: "auto" | "on" | "off" | undefined, provider: string): boolean {
657
- switch (setting ?? "auto") {
658
- case "on":
659
- return true;
660
- case "off":
661
- return false;
662
- default:
663
- return provider === "deepseek";
664
- }
665
- }
666
-
667
650
  function customToolToDefinition(tool: CustomTool): ToolDefinition {
668
651
  const definition: ToolDefinition & { [TOOL_DEFINITION_MARKER]: true } = {
669
652
  name: tool.name,
@@ -1259,6 +1242,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1259
1242
  getModelString: () => (hasExplicitModel && model ? formatModelString(model) : undefined),
1260
1243
  getActiveModelString,
1261
1244
  getPlanModeState: () => session?.getPlanModeState(),
1245
+ getPlanReferencePath: () => session?.getPlanReferencePath() ?? "local://PLAN.md",
1262
1246
  getGoalModeState: () => session?.getGoalModeState(),
1263
1247
  getGoalRuntime: () => session?.goalRuntime,
1264
1248
  getUsageStatistics: () => sessionManager.getUsageStatistics(),
@@ -1904,7 +1888,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1904
1888
  return obfuscateMessages(obfuscator, converted);
1905
1889
  };
1906
1890
  const transformContext = async (messages: AgentMessage[], _signal?: AbortSignal) => {
1907
- return await extensionRunner.emitContext(messages);
1891
+ const withContext = await extensionRunner.emitContext(messages);
1892
+ return wrapSteeringForModel(withContext);
1908
1893
  };
1909
1894
  const onPayload = async (payload: unknown, _model?: Model) => {
1910
1895
  return await extensionRunner.emitBeforeProviderRequest(payload);
@@ -2027,7 +2012,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2027
2012
  getToolChoice: () => session?.nextToolChoice(),
2028
2013
  telemetry: options.telemetry,
2029
2014
  appendOnlyContext: model
2030
- ? resolveAppendOnlyMode(settings.get("provider.appendOnlyContext"), model.provider)
2015
+ ? shouldEnableAppendOnlyContext(settings.get("provider.appendOnlyContext"), model)
2031
2016
  ? new AppendOnlyContextManager()
2032
2017
  : undefined
2033
2018
  : undefined,
@@ -100,6 +100,7 @@ import { type AsyncJob, type AsyncJobDeliveryState, AsyncJobManager } from "../a
100
100
  import { classifyDifficulty } from "../auto-thinking/classifier";
101
101
  import { reset as resetCapabilities } from "../capability";
102
102
  import type { Rule } from "../capability/rule";
103
+ import { shouldEnableAppendOnlyContext } from "../config/append-only-context-mode";
103
104
  import { MODEL_ROLE_IDS, type ModelRegistry } from "../config/model-registry";
104
105
  import {
105
106
  extractExplicitThinkingSelector,
@@ -203,7 +204,7 @@ import type { CheckpointState } from "../tools/checkpoint";
203
204
  import { outputMeta } from "../tools/output-meta";
204
205
  import { normalizeLocalScheme, resolveToCwd } from "../tools/path-utils";
205
206
  import { isAutoQaEnabled } from "../tools/report-tool-issue";
206
- import { getLatestTodoPhasesFromEntries, type TodoItem, type TodoPhase } from "../tools/todo-write";
207
+ import { getLatestTodoPhasesFromEntries, type TodoItem, type TodoPhase } from "../tools/todo";
207
208
  import { ToolAbortError, ToolError } from "../tools/tool-errors";
208
209
  import { clampTimeout } from "../tools/tool-timeouts";
209
210
  import { parseCommandArgs } from "../utils/command-args";
@@ -232,7 +233,7 @@ import type {
232
233
  SessionContext,
233
234
  SessionManager,
234
235
  } from "./session-manager";
235
- import { getLatestCompactionEntry, getRestorableSessionModels } from "./session-manager";
236
+ import { EPHEMERAL_MODEL_CHANGE_ROLE, getLatestCompactionEntry, getRestorableSessionModels } from "./session-manager";
236
237
  import type { ShakeMode, ShakeResult } from "./shake-types";
237
238
  import { ToolChoiceQueue } from "./tool-choice-queue";
238
239
  import { YieldQueue } from "./yield-queue";
@@ -1293,6 +1294,12 @@ export class AgentSession {
1293
1294
  this.#standingResolveHandler = handler ?? undefined;
1294
1295
  }
1295
1296
 
1297
+ #sessionSwitchReconciler: (() => Promise<void>) | undefined;
1298
+
1299
+ setSessionSwitchReconciler(reconciler: (() => Promise<void>) | null): void {
1300
+ this.#sessionSwitchReconciler = reconciler ?? undefined;
1301
+ }
1302
+
1296
1303
  /** Provider-scoped mutable state store for transport/session caches. */
1297
1304
  get providerSessionState(): Map<string, ProviderSessionState> {
1298
1305
  return this.#providerSessionState;
@@ -1807,21 +1814,21 @@ export class AgentSession {
1807
1814
  if (toolName === "edit" && details?.path) {
1808
1815
  this.#invalidateFileCacheForPath(details.path);
1809
1816
  }
1810
- if (toolName === "todo_write" && !isError && Array.isArray(details?.phases)) {
1817
+ if (toolName === "todo" && !isError && Array.isArray(details?.phases)) {
1811
1818
  this.setTodoPhases(details.phases);
1812
1819
  }
1813
- if (toolName === "todo_write" && isError) {
1820
+ if (toolName === "todo" && isError) {
1814
1821
  const errorText = content?.find(part => part.type === "text")?.text;
1815
1822
  const reminderText = [
1816
1823
  "<system-reminder>",
1817
- "todo_write failed, so todo progress is not visible to the user.",
1818
- errorText ? `Failure: ${errorText}` : "Failure: todo_write returned an error.",
1819
- "Fix the todo payload and call todo_write again before continuing.",
1824
+ "todo failed, so todo progress is not visible to the user.",
1825
+ errorText ? `Failure: ${errorText}` : "Failure: todo returned an error.",
1826
+ "Fix the todo payload and call todo again before continuing.",
1820
1827
  "</system-reminder>",
1821
1828
  ].join("\n");
1822
1829
  await this.sendCustomMessage(
1823
1830
  {
1824
- customType: "todo-write-error-reminder",
1831
+ customType: "todo-error-reminder",
1825
1832
  content: reminderText,
1826
1833
  display: false,
1827
1834
  details: { toolName, errorText },
@@ -4639,6 +4646,7 @@ export class AgentSession {
4639
4646
  this.agent.steer({
4640
4647
  role: "user",
4641
4648
  content,
4649
+ steering: true,
4642
4650
  attribution: "user",
4643
4651
  timestamp: Date.now(),
4644
4652
  });
@@ -4994,7 +5002,7 @@ export class AgentSession {
4994
5002
  // splice mutated canonical `#todoPhases` between tool calls, so the model
4995
5003
  // observed phase totals shrinking ("5 → 4") after marking tasks done. The
4996
5004
  // `tasks.todoClearDelay` setting is now inert; completed tasks survive
4997
- // until the next explicit `todo_write` call removes them via `rm`/`drop`.
5005
+ // until the next explicit `todo` call removes them via `rm`/`drop`.
4998
5006
 
4999
5007
  /**
5000
5008
  * Abort current operation and wait for agent to become idle.
@@ -5225,7 +5233,11 @@ export class AgentSession {
5225
5233
  * Validates API key, saves to session log but NOT to settings.
5226
5234
  * @throws Error if no API key available for the model
5227
5235
  */
5228
- async setModelTemporary(model: Model, thinkingLevel?: ThinkingLevel): Promise<void> {
5236
+ async setModelTemporary(
5237
+ model: Model,
5238
+ thinkingLevel?: ThinkingLevel,
5239
+ options?: { ephemeral?: boolean },
5240
+ ): Promise<void> {
5229
5241
  const previousEditMode = this.#resolveActiveEditMode();
5230
5242
  const apiKey = await this.#modelRegistry.getApiKey(model, this.sessionId);
5231
5243
  if (!apiKey) {
@@ -5234,7 +5246,10 @@ export class AgentSession {
5234
5246
 
5235
5247
  this.#clearActiveRetryFallback();
5236
5248
  this.#setModelWithProviderSessionReset(model);
5237
- this.sessionManager.appendModelChange(`${model.provider}/${model.id}`, "temporary");
5249
+ this.sessionManager.appendModelChange(
5250
+ `${model.provider}/${model.id}`,
5251
+ options?.ephemeral ? EPHEMERAL_MODEL_CHANGE_ROLE : "temporary",
5252
+ );
5238
5253
  this.settings.getStorage()?.recordModelUsage(`${model.provider}/${model.id}`);
5239
5254
 
5240
5255
  // Apply explicit thinking level if given; otherwise prefer the model's
@@ -6553,16 +6568,16 @@ export class AgentSession {
6553
6568
  return undefined;
6554
6569
  }
6555
6570
 
6556
- if (!this.#toolRegistry.has("todo_write")) {
6557
- logger.warn("Eager todo enforcement skipped because todo_write is unavailable", {
6571
+ if (!this.#toolRegistry.has("todo")) {
6572
+ logger.warn("Eager todo enforcement skipped because todo is unavailable", {
6558
6573
  activeToolNames: this.agent.state.tools.map(tool => tool.name),
6559
6574
  });
6560
6575
  return undefined;
6561
6576
  }
6562
6577
 
6563
- const todoWriteToolChoice = buildNamedToolChoice("todo_write", this.model);
6564
- if (!todoWriteToolChoice) {
6565
- logger.warn("Eager todo enforcement skipped because the current model does not support forcing todo_write", {
6578
+ const todoToolChoice = buildNamedToolChoice("todo", this.model);
6579
+ if (!todoToolChoice) {
6580
+ logger.warn("Eager todo enforcement skipped because the current model does not support forcing todo", {
6566
6581
  modelApi: this.model?.api,
6567
6582
  modelId: this.model?.id,
6568
6583
  });
@@ -6580,7 +6595,7 @@ export class AgentSession {
6580
6595
  attribution: "agent",
6581
6596
  timestamp: Date.now(),
6582
6597
  },
6583
- toolChoice: todoWriteToolChoice,
6598
+ toolChoice: todoToolChoice,
6584
6599
  };
6585
6600
  }
6586
6601
  /**
@@ -6682,7 +6697,7 @@ export class AgentSession {
6682
6697
  if (!targetModel) return false;
6683
6698
 
6684
6699
  try {
6685
- await this.setModelTemporary(targetModel);
6700
+ await this.setModelTemporary(targetModel, undefined, { ephemeral: true });
6686
6701
  logger.debug("Context promotion switched model on overflow", {
6687
6702
  from: `${currentModel.provider}/${currentModel.id}`,
6688
6703
  to: `${targetModel.provider}/${targetModel.id}`,
@@ -6735,8 +6750,8 @@ export class AgentSession {
6735
6750
  */
6736
6751
  #syncAppendOnlyContext(model: Model | null | undefined): void {
6737
6752
  const setting = this.settings.get("provider.appendOnlyContext") ?? "auto";
6753
+ const enable = shouldEnableAppendOnlyContext(setting, model);
6738
6754
  const providerId = model?.provider;
6739
- const enable = setting === "on" || (setting === "auto" && providerId === "deepseek");
6740
6755
  const prev = this.#lastAppendOnlyResolution;
6741
6756
  if (prev && prev.enable === enable && prev.providerId === providerId) return;
6742
6757
  this.#lastAppendOnlyResolution = { enable, providerId };
@@ -7848,7 +7863,7 @@ export class AgentSession {
7848
7863
  const nextThinkingLevel = selector.thinkingLevel ?? currentThinkingLevel;
7849
7864
 
7850
7865
  this.#setModelWithProviderSessionReset(candidate);
7851
- this.sessionManager.appendModelChange(`${candidate.provider}/${candidate.id}`, "temporary");
7866
+ this.sessionManager.appendModelChange(`${candidate.provider}/${candidate.id}`, EPHEMERAL_MODEL_CHANGE_ROLE);
7852
7867
  this.settings.getStorage()?.recordModelUsage(`${candidate.provider}/${candidate.id}`);
7853
7868
  this.setThinkingLevel(nextThinkingLevel);
7854
7869
  if (!this.#activeRetryFallback) {
@@ -7921,7 +7936,7 @@ export class AgentSession {
7921
7936
  const thinkingToApply =
7922
7937
  currentThinkingLevel === lastAppliedFallbackThinkingLevel ? originalThinkingLevel : currentThinkingLevel;
7923
7938
  this.#setModelWithProviderSessionReset(primaryModel);
7924
- this.sessionManager.appendModelChange(`${primaryModel.provider}/${primaryModel.id}`, "temporary");
7939
+ this.sessionManager.appendModelChange(`${primaryModel.provider}/${primaryModel.id}`, EPHEMERAL_MODEL_CHANGE_ROLE);
7925
7940
  this.settings.getStorage()?.recordModelUsage(`${primaryModel.provider}/${primaryModel.id}`);
7926
7941
  this.setThinkingLevel(thinkingToApply);
7927
7942
  this.#clearActiveRetryFallback();
@@ -8948,6 +8963,14 @@ export class AgentSession {
8948
8963
  this.#resetMnemopiConversationTrackingIfMnemopi();
8949
8964
  }
8950
8965
  this.#reconnectToAgent();
8966
+ try {
8967
+ await this.#sessionSwitchReconciler?.();
8968
+ } catch (error) {
8969
+ logger.warn("Failed to reconcile session mode after switch", {
8970
+ targetSessionFile: sessionPath,
8971
+ error: String(error),
8972
+ });
8973
+ }
8951
8974
  return true;
8952
8975
  } catch (error) {
8953
8976
  this.sessionManager.restoreState(previousSessionState);
@@ -209,10 +209,6 @@ CREATE TRIGGER IF NOT EXISTS history_ai AFTER INSERT ON history BEGIN
209
209
  logger.debug("HistoryStorage FTS query failed, using substring only", { error: String(error) });
210
210
  }
211
211
 
212
- if (ftsRows.length >= safeLimit) {
213
- return ftsRows.map(row => this.#toEntry(row));
214
- }
215
-
216
212
  // 2. Substring fallback (token-AND LIKE). Catches infix matches FTS5's
217
213
  // prefix-only wildcard cannot reach (e.g. "mit" -> "commit"). Bounded
218
214
  // by safeLimit, ordered by recency - no full-table load into JS.
@@ -227,27 +223,24 @@ CREATE TRIGGER IF NOT EXISTS history_ai AFTER INSERT ON history BEGIN
227
223
  return subRows.map(row => this.#toEntry(row));
228
224
  }
229
225
 
230
- const seen = new Set<number>();
231
- const merged: HistoryEntry[] = [];
226
+ const rowsById = new Map<number, HistoryRow>();
232
227
  for (const row of ftsRows) {
233
- if (seen.has(row.id)) continue;
234
- seen.add(row.id);
235
- merged.push(this.#toEntry(row));
228
+ rowsById.set(row.id, row);
236
229
  }
237
230
  for (const row of subRows) {
238
- if (merged.length >= safeLimit) break;
239
- if (seen.has(row.id)) continue;
240
- seen.add(row.id);
241
- merged.push(this.#toEntry(row));
231
+ if (!rowsById.has(row.id)) rowsById.set(row.id, row);
242
232
  }
243
- return merged;
233
+
234
+ return [...rowsById.values()]
235
+ .sort((a, b) => b.created_at - a.created_at || b.id - a.id)
236
+ .slice(0, safeLimit)
237
+ .map(row => this.#toEntry(row));
244
238
  }
245
239
 
246
240
  /**
247
- * IDs of the sessions whose stored prompts match `query`, ordered by match
248
- * relevance (most relevant/recent first) and de-duplicated. Prompts with no
249
- * recorded session are skipped. Used to augment session ranking in the
250
- * resume picker with prompts that the 4KB session-list prefix never sees.
241
+ * IDs of the sessions whose stored prompts match `query`, ordered by prompt
242
+ * recency and de-duplicated. Used to augment session ranking in the resume
243
+ * picker with prompts that the 4KB session-list prefix never sees.
251
244
  */
252
245
  matchingSessionIds(query: string, limit = 500): string[] {
253
246
  const seen = new Set<string>();