@oh-my-pi/pi-coding-agent 15.13.1 → 15.13.2

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 (71) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/cli.js +957 -214
  3. package/dist/types/config/model-registry.d.ts +1 -0
  4. package/dist/types/config/models-config-schema.d.ts +3 -0
  5. package/dist/types/config/models-config.d.ts +3 -0
  6. package/dist/types/config/settings-schema.d.ts +66 -0
  7. package/dist/types/edit/hashline/block-resolver.d.ts +1 -1
  8. package/dist/types/edit/index.d.ts +2 -0
  9. package/dist/types/modes/components/welcome.d.ts +1 -0
  10. package/dist/types/modes/controllers/input-controller.d.ts +4 -4
  11. package/dist/types/modes/rpc/rpc-types.d.ts +2 -1
  12. package/dist/types/sdk.d.ts +3 -0
  13. package/dist/types/session/session-dump-format.d.ts +2 -1
  14. package/dist/types/system-prompt.d.ts +11 -0
  15. package/dist/types/tools/ask.d.ts +2 -0
  16. package/dist/types/tools/ast-edit.d.ts +2 -0
  17. package/dist/types/tools/ast-grep.d.ts +2 -0
  18. package/dist/types/tools/browser.d.ts +2 -0
  19. package/dist/types/tools/debug.d.ts +2 -0
  20. package/dist/types/tools/eval.d.ts +2 -0
  21. package/dist/types/tools/find.d.ts +2 -0
  22. package/dist/types/tools/inspect-image.d.ts +2 -1
  23. package/dist/types/tools/irc.d.ts +2 -0
  24. package/dist/types/tools/ssh.d.ts +2 -0
  25. package/dist/types/tools/todo.d.ts +2 -0
  26. package/dist/types/tui/tree-list.d.ts +1 -0
  27. package/package.json +12 -12
  28. package/src/config/model-registry.ts +10 -0
  29. package/src/config/models-config-schema.ts +2 -0
  30. package/src/config/models-config.ts +1 -0
  31. package/src/config/settings-schema.ts +53 -0
  32. package/src/edit/hashline/block-resolver.ts +1 -1
  33. package/src/edit/hashline/execute.ts +1 -6
  34. package/src/edit/index.ts +48 -0
  35. package/src/eval/__tests__/js-context-manager.test.ts +41 -1
  36. package/src/eval/js/context-manager.ts +92 -26
  37. package/src/eval/js/worker-core.ts +1 -1
  38. package/src/internal-urls/docs-index.generated.ts +9 -2
  39. package/src/modes/components/welcome.ts +14 -4
  40. package/src/modes/controllers/input-controller.ts +21 -38
  41. package/src/modes/rpc/rpc-mode.ts +1 -0
  42. package/src/modes/rpc/rpc-types.ts +2 -2
  43. package/src/prompts/system/system-prompt.md +17 -21
  44. package/src/prompts/tools/ask.md +0 -8
  45. package/src/prompts/tools/ast-edit.md +0 -15
  46. package/src/prompts/tools/ast-grep.md +0 -13
  47. package/src/prompts/tools/browser.md +0 -21
  48. package/src/prompts/tools/debug.md +0 -13
  49. package/src/prompts/tools/eval.md +0 -9
  50. package/src/prompts/tools/find.md +0 -13
  51. package/src/prompts/tools/inspect-image.md +0 -9
  52. package/src/prompts/tools/irc.md +0 -15
  53. package/src/prompts/tools/patch.md +0 -13
  54. package/src/prompts/tools/ssh.md +0 -9
  55. package/src/prompts/tools/todo.md +1 -19
  56. package/src/sdk.ts +19 -0
  57. package/src/session/agent-session.ts +125 -19
  58. package/src/session/session-dump-format.ts +10 -31
  59. package/src/system-prompt.ts +31 -0
  60. package/src/tools/ask.ts +41 -0
  61. package/src/tools/ast-edit.ts +46 -0
  62. package/src/tools/ast-grep.ts +24 -0
  63. package/src/tools/browser.ts +52 -0
  64. package/src/tools/debug.ts +17 -0
  65. package/src/tools/eval.ts +20 -1
  66. package/src/tools/find.ts +24 -0
  67. package/src/tools/inspect-image.ts +27 -1
  68. package/src/tools/irc.ts +41 -0
  69. package/src/tools/ssh.ts +16 -0
  70. package/src/tools/todo.ts +82 -3
  71. package/src/tui/tree-list.ts +68 -19
@@ -70,8 +70,7 @@ export interface LspServerInfo {
70
70
  export class WelcomeComponent implements Component {
71
71
  #animStart: number | null = null;
72
72
  #animTimer: ReturnType<typeof setInterval> | null = null;
73
- /** Tip chosen once per instance so re-renders (intro, LSP updates) don't shuffle it. */
74
- readonly #tip: string | undefined = TIPS.length > 0 ? TIPS[Math.floor(Math.random() * TIPS.length)] : undefined;
73
+ #selectedTip: string | undefined;
75
74
  // Render cache: the welcome box is the first transcript-area component, so
76
75
  // returning a stable array reference keeps the whole frame prefix stable.
77
76
  // Bypassed while the intro animation runs (every frame differs).
@@ -85,6 +84,16 @@ export class WelcomeComponent implements Component {
85
84
  private recentSessions: RecentSession[] = [],
86
85
  private lspServers: LspServerInfo[] = [],
87
86
  ) {}
87
+ get tip(): string | undefined {
88
+ if (this.#selectedTip === undefined) {
89
+ if (theme.getSymbolPreset() === "unicode" && Math.random() < 0.1) {
90
+ this.#selectedTip = "Please use nerdfont 😭.";
91
+ } else {
92
+ this.#selectedTip = TIPS.length > 0 ? TIPS[Math.floor(Math.random() * TIPS.length)] : "";
93
+ }
94
+ }
95
+ return this.#selectedTip || undefined;
96
+ }
88
97
 
89
98
  invalidate(): void {
90
99
  this.#cachedWidth = -1;
@@ -316,8 +325,9 @@ export class WelcomeComponent implements Component {
316
325
  * when no tip is available or the box is too narrow to be useful.
317
326
  */
318
327
  #renderTip(boxWidth: number): string[] {
319
- if (!this.#tip) return [];
320
- return renderWelcomeTip(this.#tip, boxWidth);
328
+ const tip = this.tip;
329
+ if (!tip) return [];
330
+ return renderWelcomeTip(tip, boxWidth);
321
331
  }
322
332
 
323
333
  /** Center text within a given width */
@@ -44,26 +44,9 @@ function hasPasteText(value: unknown): value is PasteTarget {
44
44
  return typeof value === "object" && value !== null && typeof (value as PasteTarget).pasteText === "function";
45
45
  }
46
46
 
47
- /** Wrap pasted text in a fenced code block, using a backtick fence longer than any run of
48
- * backticks already in the content so an embedded fence cannot terminate the block early. */
49
- function wrapPasteInCodeBlock(content: string): string {
50
- let longestRun = 0;
51
- let run = 0;
52
- for (let i = 0; i < content.length; i++) {
53
- if (content.charCodeAt(i) === 96 /* backtick */) {
54
- run++;
55
- if (run > longestRun) longestRun = run;
56
- } else {
57
- run = 0;
58
- }
59
- }
60
- const fence = "`".repeat(Math.max(3, longestRun + 1));
61
- return `${fence}\n${content}\n${fence}`;
62
- }
63
-
64
- /** Wrap pasted text in `<pasted_text>` tags so the model treats it as one quoted block. */
65
- function wrapPasteInXml(content: string): string {
66
- return `<pasted_text>\n${content}\n</pasted_text>`;
47
+ /** Wrap pasted text in `<attachment>` tags so the model treats it as one quoted block. */
48
+ function wrapPasteInAttachmentBlock(content: string): string {
49
+ return `<attachment>\n${content}\n</attachment>`;
67
50
  }
68
51
 
69
52
  const TINY_TITLE_PROGRESS_DONE_TTL_MS = 3_000;
@@ -99,8 +82,8 @@ export class InputController {
99
82
  // (>= LEFT_DOUBLE_TAP_MAX_GAP_MS) starts a fresh sequence. See
100
83
  // #detectLeftDoubleTap.
101
84
  #leftTapCount = 0;
102
- // Sequential index for `local://attachment-N` references created by the large-paste "attach as
103
- // file" action. Seeded from 0 and bumped past any existing attachment files in #attachPasteAsFile.
85
+ // Sequential index for `local://attachment-N` references created by the large-paste local-file
86
+ // action. Seeded from 0 and bumped past any existing attachment files in #attachPasteAsFile.
104
87
  #attachmentCounter = 0;
105
88
 
106
89
  #showTinyTitleDownloadProgress(modelKey: string): void {
@@ -1282,24 +1265,24 @@ export class InputController {
1282
1265
  }
1283
1266
 
1284
1267
  /**
1285
- * Present the large-paste menu and apply the chosen action: wrap in a code block or in XML tags
1286
- * (both collapse to a `[Paste]` marker that expands on submit), or save the text to a file and
1287
- * reference its path so the agent can `read` it on demand. Cancelling (Esc) falls back to the
1288
- * default inline paste marker, so the pasted content is never lost.
1268
+ * Present the large-paste menu and apply the chosen action: wrap in `<attachment>` tags (collapsed
1269
+ * to a `[Paste]` marker that expands on submit), save the text to a file and reference its path so
1270
+ * the agent can `read` it on demand, or paste inline. Cancelling (Esc) falls back to the default
1271
+ * inline paste marker, so the pasted content is never lost.
1289
1272
  */
1290
1273
  async presentLargePasteMenu(text: string, lineCount: number): Promise<void> {
1291
- const CODE_BLOCK = "Wrap in a code block";
1292
- const XML = "Wrap in XML tags";
1293
- const FILE = "Attach as a file";
1274
+ const WRAPPED_BLOCK = "Attach as a wrapped block";
1275
+ const LOCAL_FILE = "Attach as local file";
1276
+ const INLINE = "Paste inline";
1294
1277
 
1295
1278
  let choice: string | undefined;
1296
1279
  try {
1297
1280
  choice = await this.ctx.showHookSelector(
1298
1281
  `Pasted ${lineCount} lines`,
1299
1282
  [
1300
- { label: CODE_BLOCK, description: "Fence the text in a ``` block, collapsed to a marker" },
1301
- { label: XML, description: "Wrap the text in <pasted_text> tags, collapsed to a marker" },
1302
- { label: FILE, description: "Save the text to a file and reference its path" },
1283
+ { label: WRAPPED_BLOCK, description: "Wrap the text in <attachment> tags, collapsed to a marker" },
1284
+ { label: LOCAL_FILE, description: "Save the text to a local://attachment file" },
1285
+ { label: INLINE, description: "Collapse the text to an inline paste marker" },
1303
1286
  ],
1304
1287
  { helpText: "Esc to paste inline" },
1305
1288
  );
@@ -1309,15 +1292,15 @@ export class InputController {
1309
1292
  }
1310
1293
 
1311
1294
  switch (choice) {
1312
- case CODE_BLOCK:
1313
- this.ctx.editor.insertPaste(wrapPasteInCodeBlock(text));
1314
- break;
1315
- case XML:
1316
- this.ctx.editor.insertPaste(wrapPasteInXml(text));
1295
+ case WRAPPED_BLOCK:
1296
+ this.ctx.editor.insertPaste(wrapPasteInAttachmentBlock(text));
1317
1297
  break;
1318
- case FILE:
1298
+ case LOCAL_FILE:
1319
1299
  await this.#attachPasteAsFile(text, lineCount);
1320
1300
  break;
1301
+ case INLINE:
1302
+ this.ctx.editor.insertPaste(text);
1303
+ break;
1321
1304
  default:
1322
1305
  // Esc / cancel: keep the original behavior — collapse to an inline paste marker.
1323
1306
  this.ctx.editor.insertPaste(text);
@@ -796,6 +796,7 @@ export async function runRpcMode(
796
796
  name: tool.name,
797
797
  description: tool.description,
798
798
  parameters: isZodSchema(tool.parameters) ? zodToWireSchema(tool.parameters) : tool.parameters,
799
+ examples: tool.examples,
799
800
  })),
800
801
  contextUsage: session.getContextUsage(),
801
802
  };
@@ -6,7 +6,7 @@
6
6
  */
7
7
  import type { AgentMessage, AgentToolResult, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
8
8
  import type { CompactionResult } from "@oh-my-pi/pi-agent-core/compaction";
9
- import type { Effort, ImageContent, Model } from "@oh-my-pi/pi-ai";
9
+ import type { Effort, ImageContent, Model, ToolExample } from "@oh-my-pi/pi-ai";
10
10
  import type { BashResult } from "../../exec/bash-executor";
11
11
  import type { ContextUsage } from "../../extensibility/extensions/types";
12
12
  import type { AgentSessionEvent, SessionStats } from "../../session/agent-session";
@@ -107,7 +107,7 @@ export interface RpcSessionState {
107
107
  todoPhases: TodoPhase[];
108
108
  /** For session dump / export (plain-text parity with /dump). */
109
109
  systemPrompt?: string[];
110
- dumpTools?: Array<{ name: string; description: string; parameters: unknown }>;
110
+ dumpTools?: Array<{ name: string; description: string; parameters: unknown; examples?: readonly ToolExample[] }>;
111
111
  /** Current context window usage. Null tokens/percent when unknown (e.g. right after compaction). */
112
112
  contextUsage?: ContextUsage;
113
113
  }
@@ -24,27 +24,6 @@ Use tools whenever they materially improve correctness, completeness, or groundi
24
24
  - SHOULD parallelize calls when possible.
25
25
  {{#has tools "task"}}- User says `parallel`/`parallelize` → MUST use `{{toolRefs.task}}` subagents; parallel tool calls alone do not satisfy.{{/has}}
26
26
 
27
- {{#if toolInfo.length}}
28
- # Inventory
29
- {{#if mcpDiscoveryMode}}
30
- <discovery-notice>
31
- {{#if hasMCPDiscoveryServers}}Discoverable MCP servers in this session: {{#list mcpDiscoveryServerSummaries join=", "}}{{this}}{{/list}}.{{/if}}
32
- If the task may involve external systems, SaaS APIs, chat, tickets, databases, deployments, or other non-local integrations, you SHOULD call `{{toolRefs.search_tool_bm25}}` before concluding no such tool exists.
33
- </discovery-notice>
34
- {{/if}}
35
- {{#if repeatToolDescriptions}}
36
- {{#each toolInfo}}
37
- <tool name={{name}}>
38
- {{description}}
39
- </tool>
40
- {{/each}}
41
- {{else}}
42
- {{#each toolInfo}}
43
- - {{#if label}}{{label}}: `{{name}}`{{else}}`{{name}}`{{/if}}
44
- {{/each}}
45
- {{/if}}
46
- {{/if}}
47
-
48
27
  # I/O
49
28
  - For tools taking `path` or path-like fields, prefer relative paths.
50
29
  {{#if intentTracing}}- Most tools have a `{{intentField}}` parameter. Fill it with a concise intent in present participle form, 2-6 words, no period, capitalized.{{/if}}
@@ -115,6 +94,23 @@ Delegation is preferred here. Once the design is settled, you SHOULD fan substan
115
94
  {{/has}}
116
95
  {{/if}}
117
96
 
97
+ {{#if toolInfo.length}}
98
+ # Inventory
99
+ {{#if mcpDiscoveryMode}}
100
+ <discovery-notice>
101
+ {{#if hasMCPDiscoveryServers}}Discoverable MCP servers in this session: {{#list mcpDiscoveryServerSummaries join=", "}}{{this}}{{/list}}.{{/if}}
102
+ If the task may involve external systems, SaaS APIs, chat, tickets, databases, deployments, or other non-local integrations, you SHOULD call `{{toolRefs.search_tool_bm25}}` before concluding no such tool exists.
103
+ </discovery-notice>
104
+ {{/if}}
105
+ {{#if toolListMode}}
106
+ {{#each toolInfo}}
107
+ - {{#if label}}{{label}}: `{{name}}`{{else}}`{{name}}`{{/if}}
108
+ {{/each}}
109
+ {{else}}
110
+ {{toolInventory}}
111
+ {{/if}}
112
+ {{/if}}
113
+
118
114
  ENV
119
115
  ===================================
120
116
 
@@ -20,11 +20,3 @@ Asks user when you need clarification or input during task execution.
20
20
  - **If multiple choices are acceptable**, pick the most conservative/standard option and proceed; state the choice.
21
21
  - **Do NOT include "Other" option** — UI automatically adds "Other (type your own)" to every question.
22
22
  </critical>
23
-
24
- <examples>
25
- # Single question
26
- questions: [{"id": "auth_method", "question": "Which authentication method should this API use?", "options": [{"label": "JWT", "description": "Bearer tokens for stateless API clients."}, {"label": "OAuth2", "description": "Delegated authorization with external identity providers."}, {"label": "Session cookies", "description": "Browser-first authentication backed by server-side sessions."}], "recommended": 0}]
27
-
28
- # Multiple questions
29
- questions: [{"id": "storage_type", "question": "Which storage backend?", "options": [{"label": "SQLite"}, {"label": "PostgreSQL"}]}, {"id": "auth_method", "question": "Which auth method?", "options": [{"label": "JWT"}, {"label": "Session cookies"}]}]
30
- </examples>
@@ -18,21 +18,6 @@ Performs structural AST-aware rewrites via native ast-grep.
18
18
  - Parse issues when files cannot be processed
19
19
  </output>
20
20
 
21
- <examples>
22
- # Rename a call site across TypeScript files
23
- `{"ops":[{"pat":"oldApi($$$ARGS)","out":"newApi($$$ARGS)"}],"paths":["src/**/*.ts"]}`
24
- # Delete matching calls
25
- `{"ops":[{"pat":"console.log($$$ARGS)","out":""}],"paths":["src/**/*.ts"]}`
26
- # Rewrite import source path
27
- `{"ops":[{"pat":"import { $$$IMPORTS } from \"old-package\"","out":"import { $$$IMPORTS } from \"new-package\""}],"paths":["src/**/*.ts"]}`
28
- # Modernize to optional chaining (same metavariable enforces identity)
29
- `{"ops":[{"pat":"$A && $A()","out":"$A?.()"}],"paths":["src/**/*.ts"]}`
30
- # Swap two arguments using captures
31
- `{"ops":[{"pat":"assertEqual($A, $B)","out":"assertEqual($B, $A)"}],"paths":["tests/**/*.ts"]}`
32
- # Python — convert print calls to logging
33
- `{"ops":[{"pat":"print($$$ARGS)","out":"logger.info($$$ARGS)"}],"paths":["src/**/*.py"]}`
34
- </examples>
35
-
36
21
  <critical>
37
22
  - Parse issues mean the rewrite is malformed or mis-scoped — fix the pattern before assuming a clean no-op
38
23
  - For one-off local text edits, you SHOULD prefer the Edit tool
@@ -22,19 +22,6 @@ Performs structural code search using AST matching via native ast-grep.
22
22
  - Summary counts (`totalMatches`, `filesWithMatches`, `filesSearched`) and parse issues when present
23
23
  </output>
24
24
 
25
- <examples>
26
- # Search TypeScript files under src
27
- `{"pat":"console.log($$$)","paths":["src/**/*.ts"]}`
28
- # Named imports from a specific package
29
- `{"pat":"import { $$$IMPORTS } from \"react\"","paths":["src/**/*.ts"]}`
30
- # Arrow functions assigned to a const
31
- `{"pat":"const $NAME = ($$$ARGS) => $BODY","paths":["src/utils/**/*.ts"]}`
32
- # Method call on any object, ignoring method name with `$_`
33
- `{"pat":"logger.$_($$$ARGS)","paths":["src/**/*.ts"]}`
34
- # Loosest existence check for a symbol in one file
35
- `{"pat":"processItems","paths":["src/worker.ts"]}`
36
- </examples>
37
-
38
25
  <critical>
39
26
  - AVOID repo-root scans — narrow `paths` first
40
27
  - Parse issues are query failure, not evidence of absence: repair the pattern or tighten `paths` before concluding "no matches"
@@ -37,27 +37,6 @@ Drives real Chromium tab; full puppeteer access via JS execution.
37
37
  - `code` runs with full Node access. Treat as your code, not sandboxed code.
38
38
  </critical>
39
39
 
40
- <examples>
41
- # Open a tab and read structured page data
42
- `{"action":"open","name":"docs","url":"https://example.com"}`
43
- `{"action":"run","name":"docs","code":"const obs = await tab.observe(); display(obs); return obs.elements.length;"}`
44
-
45
- # Click an observed element by id
46
- `{"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();"}`
47
-
48
- # Fill and submit a form via selectors
49
- `{"action":"run","name":"docs","code":"await tab.fill('input[name=email]', 'me@example.com'); await tab.click('text/Continue');"}`
50
-
51
- # Screenshot to look at the page — no save path
52
- `{"action":"run","name":"docs","code":"await tab.screenshot();"}`
53
-
54
- # Attach to an existing Electron app
55
- `{"action":"open","name":"cursor","app":{"path":"/Applications/Cursor.app/Contents/MacOS/Cursor"}}`
56
-
57
- # Close every tab and kill spawned-app processes
58
- `{"action":"close","all":true,"kill":true}`
59
- </examples>
60
-
61
40
  <output>
62
41
  Per call: `display(value)` outputs (text/images), then the JSON-stringified return value of `code`. `run` always produces at least a status line.
63
42
  </output>
@@ -19,16 +19,3 @@ Use for launching or attaching debuggers, setting breakpoints, stepping through
19
19
  - `program` must be an executable file or debug target, not a directory or interpreter name that resolves to a workspace directory.
20
20
  - Python debugging requires `debugpy`; install with `pip install debugpy` if the adapter is unavailable.
21
21
  </caution>
22
-
23
- <examples>
24
- # Launch and inspect hang
25
- 1. `debug(action: "launch", program: "./my_app")`
26
- 2. `debug(action: "set_breakpoint", file: "src/main.c", line: 42)`
27
- 3. `debug(action: "continue")`
28
- 4. If the program appears hung: `debug(action: "pause")`
29
- 5. Inspect state with `threads`, `stack_trace`, `scopes`, and `variables`
30
- # Launch a Python script with debugpy
31
- `debug(action: "launch", adapter: "debugpy", program: "scripts/job.py", args: ["--flag"])`
32
- # Raw debugger command through repl
33
- `debug(action: "evaluate", expression: "info registers", context: "repl")`
34
- </examples>
@@ -58,12 +58,3 @@ budget → per-turn token budget
58
58
  {{#if py}}`budget.total` (ceiling or None), `budget.spent()`, `budget.remaining()` (math.inf when no ceiling), `budget.hard` (bool).{{/if}}{{#if js}}`await budget.total()` (ceiling or null), `await budget.spent()`, `await budget.remaining()` (Infinity when no ceiling), `await budget.hard()`.{{/if}} Ceiling comes from a `+Nk` directive (advisory) or `+Nk!`/Goal Mode (hard — `agent()` refuses to spawn past it); otherwise None/null, spend still tracked across the turn.
59
59
  ```
60
60
  </prelude>
61
-
62
- <example>
63
- {
64
- "cells": [
65
- { "language": "py", "title": "imports", "timeout": 10, "code": "import json\nfrom pathlib import Path" },
66
- { "language": "py", "title": "load config", "code": "data = json.loads(read('package.json'))\ndisplay(data)" }
67
- ]
68
- }
69
- </example>
@@ -14,19 +14,6 @@ Finds files and directories using fast pattern matching that works with any code
14
14
  Matching file and directory paths sorted by modification time (most recent first), grouped by directory to reduce token usage. Each group starts with `# <dir>/` followed by basenames (one per line); directory entries get a trailing `/`. Root-level entries have no header. Truncated at 200 entries or 50KB.
15
15
  </output>
16
16
 
17
- <examples>
18
- # Find files
19
- `{"paths": ["src/**/*.ts"]}`
20
- # Multiple targets — separate array elements
21
- `{"paths": ["src/**/*.ts", "test/**/*.ts"]}`
22
- # Find gitignored files like .env
23
- `{"paths": [".env*"], "gitignore": false}`
24
- # Find directories matching a name (returns both files and dirs; directories are suffixed with `/`)
25
- `{"paths": ["**/tests"]}`
26
- # Long-running search on a slow volume
27
- `{"paths": ["/Volumes/Storage/**/*.py"], "timeout": 30}`
28
- </examples>
29
-
30
17
  <avoid>
31
18
  For open-ended searches requiring multiple rounds of globbing and searching, you MUST use Task tool instead.
32
19
  </avoid>
@@ -11,15 +11,6 @@ Inspects an image file with a vision-capable model and returns compact text anal
11
11
  - Use this tool over `read` when the goal is image analysis
12
12
  </instruction>
13
13
 
14
- <examples>
15
- # OCR with strict formatting
16
- `{"path":"screenshots/error.png","question":"Extract all visible text verbatim. Return as bullet list in reading order."}`
17
- # Screenshot debugging
18
- `{"path":"screenshots/settings.png","question":"Identify the likely cause of the disabled Save button. Return: (1) observations, (2) likely cause, (3) confidence."}`
19
- # Scene/object question
20
- `{"path":"photos/shelf.jpg","question":"List all clearly visible product labels and their shelf positions (top/middle/bottom). If unreadable, say unreadable."}`
21
- </examples>
22
-
23
14
  <output>
24
15
  - Returns text-only analysis from the vision model
25
16
  - No image content blocks are returned in tool output
@@ -40,18 +40,3 @@ Applies to sending and replying.
40
40
  - `inbox`: pending messages, oldest first.
41
41
  - `list`: peers with status, unread count, parent, last activity.
42
42
  </output>
43
-
44
- <examples>
45
- # List peers
46
- `{"op": "list"}`
47
- # Fire-and-forget DM — same send wakes idle/parked peers
48
- `{"op": "send", "to": "AuthLoader", "message": "Still touching src/server/auth.ts? I need to add a 401 path."}`
49
- # Round-trip when you cannot proceed without the answer
50
- `{"op": "send", "to": "Main", "message": "JWT or session cookies for the auth flow?", "await": true}`
51
- # Block until a specific peer answers
52
- `{"op": "wait", "from": "AuthLoader", "timeoutMs": 60000}`
53
- # Drain pending messages
54
- `{"op": "inbox"}`
55
- # Broadcast to live peers (no replies expected)
56
- `{"op": "send", "to": "all", "message": "About to refactor src/server/middleware/*. Anyone already in there?"}`
57
- </examples>
@@ -50,19 +50,6 @@ Returns success/failure; on failure, error message indicates:
50
50
  - NEVER use edit to fix indentation, whitespace, or reformat code. Formatting is a single command run once at the end (`bun fmt`, `cargo fmt`, `prettier --write`, etc.) — not N individual edits. If you see inconsistent indentation after an edit, leave it; the formatter will fix all of it in one pass.
51
51
  </critical>
52
52
 
53
- <examples>
54
- # Create
55
- `edit {"path":"hello.txt","edits":[{"op":"create","diff":"Hello\n"}]}`
56
- # Update
57
- `edit {"path":"src/app.py","edits":[{"op":"update","diff":"@@ def greet():\n def greet():\n-print('Hi')\n+print('Hello')\n"}]}`
58
- # Rename
59
- `edit {"path":"src/app.py","edits":[{"op":"update","rename":"src/main.py","diff":"@@\n …\n"}]}`
60
- # Delete
61
- `edit {"path":"obsolete.txt","edits":[{"op":"delete"}]}`
62
- # Multiple entries
63
- All entries in one call apply to the top-level `path`; use separate calls for different files.
64
- </examples>
65
-
66
53
  <avoid>
67
54
  - Generic anchors: `import`, `export`, `describe`, `function`, `const`
68
55
  - Repeating same addition in multiple hunks (duplicate blocks)
@@ -20,12 +20,3 @@ Runs commands on remote hosts.
20
20
  <critical>
21
21
  You MUST verify the shell type from "Available hosts" and use matching commands.
22
22
  </critical>
23
-
24
- <examples>
25
- # List files: Linux
26
- Host: server1 (10.0.0.1) | linux/bash. Command: `ls -la /home/user`
27
- # Show running processes: Windows cmd
28
- Host: winbox (192.168.1.5) | windows/cmd. Command: `tasklist /v`
29
- # Get system info: macOS
30
- Host: macbook (10.0.0.20) | macos/zsh. Command: `uname -a && sw_vers`
31
- </examples>
@@ -9,6 +9,7 @@ Allowed `op` values are only `init`, `start`, `done`, `drop`, `rm`, `append`, an
9
9
  |`op`|Required fields|Effect|
10
10
  |---|---|---|
11
11
  |`init`|`list: [{phase, items: string[]}]`|Initialize the full list (replaces any existing list)|
12
+ |`init`|`items: string[]`|Flattened single-phase init|
12
13
  |`start`|`task`|Mark in progress|
13
14
  |`done`|`task` or `phase`|Mark completed|
14
15
  |`drop`|`task` or `phase`|Mark abandoned|
@@ -33,25 +34,6 @@ Allowed `op` values are only `init`, `start`, `done`, `drop`, `rm`, `append`, an
33
34
  - User provides a set of tasks to complete
34
35
  - New instructions arrive mid-task — capture before proceeding
35
36
 
36
- <examples>
37
- # Initial setup (multi-phase)
38
- `{"ops":[{"op":"init","list":[{"phase":"Foundation","items":["Scaffold crate","Wire workspace"]},{"phase":"Auth","items":["Port credential store","Wire OAuth providers"]},{"phase":"Verification","items":["Run cargo test"]}]}]}`
39
- # View current state (read-only)
40
- `{"ops":[{"op":"view"}]}`
41
- # Initial setup (single phase)
42
- `{"ops":[{"op":"init","list":[{"phase":"Implementation","items":["Apply fix","Run tests"]}]}]}`
43
- # Complete one task
44
- `{"ops":[{"op":"done","task":"Wire workspace"}]}`
45
- # Complete a whole phase
46
- `{"ops":[{"op":"done","phase":"Auth"}]}`
47
- # Remove all tasks
48
- `{"ops":[{"op":"rm"}]}`
49
- # Drop one task
50
- `{"ops":[{"op":"drop","task":"Run cargo test"}]}`
51
- # Append tasks to a phase
52
- `{"ops":[{"op":"append","phase":"Auth","items":["Handle retries","Run tests"]}]}`
53
- </examples>
54
-
55
37
  <critical>
56
38
  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:
57
39
  - You MUST `init` the list with EVERY item as its own task before doing the work.
package/src/sdk.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  type SimpleStreamOptions,
17
17
  streamSimple,
18
18
  } from "@oh-my-pi/pi-ai";
19
+ import type { ToolCallSyntax } from "@oh-my-pi/pi-ai/grammar";
19
20
  import {
20
21
  getOpenAICodexTransportDetails,
21
22
  prewarmOpenAICodexResponses,
@@ -550,6 +551,17 @@ export interface CreateAgentSessionResult {
550
551
  eventBus: EventBus;
551
552
  }
552
553
 
554
+ export type ToolCallFormat = "auto" | "native" | ToolCallSyntax;
555
+
556
+ export function resolveToolCallSyntax(
557
+ format: ToolCallFormat,
558
+ model: Pick<Model, "supportsTools"> | undefined,
559
+ ): ToolCallSyntax | undefined {
560
+ if (format === "native") return undefined;
561
+ if (format === "auto") return model?.supportsTools === false ? "glm" : undefined;
562
+ return format;
563
+ }
564
+
553
565
  // Re-exports
554
566
 
555
567
  export type { PromptTemplate } from "./config/prompt-templates";
@@ -2149,6 +2161,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2149
2161
  }
2150
2162
  appendPrompt = parts.join("\n\n");
2151
2163
  }
2164
+ // Owned/in-band tool syntax (non-native) repeats the catalog as `# Tool:`
2165
+ // sections; native tool calling lets the compact name list suffice.
2166
+ const nativeTools =
2167
+ resolveToolCallSyntax(settings.get("tools.format"), agent?.state.model ?? model) === undefined;
2152
2168
  const defaultPrompt = await buildSystemPromptInternal({
2153
2169
  cwd,
2154
2170
  skills,
@@ -2160,6 +2176,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2160
2176
  skillsSettings: settings.getGroup("skills"),
2161
2177
  appendSystemPrompt: appendPrompt,
2162
2178
  repeatToolDescriptions,
2179
+ nativeTools,
2163
2180
  intentField,
2164
2181
  mcpDiscoveryMode: hasDiscoverableTools,
2165
2182
  mcpDiscoveryServerSummaries: discoverableToolSummary.servers.map(formatDiscoverableToolServerSummary),
@@ -2489,6 +2506,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
2489
2506
  return result;
2490
2507
  },
2491
2508
  intentTracing: !!intentField,
2509
+ toolCallSyntax: resolveToolCallSyntax(settings.get("tools.format"), model),
2510
+ abortOnFabricatedToolResult: settings.get("tools.abortOnFabricatedResult"),
2492
2511
  getToolChoice: () => session?.nextToolChoice(),
2493
2512
  telemetry: options.telemetry,
2494
2513
  appendOnlyContext: model