@oh-my-pi/pi-coding-agent 14.3.0 → 14.4.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 (120) hide show
  1. package/CHANGELOG.md +98 -1
  2. package/package.json +7 -7
  3. package/src/autoresearch/prompt.md +1 -1
  4. package/src/commit/agentic/prompts/analyze-file.md +1 -1
  5. package/src/config/model-registry.ts +67 -15
  6. package/src/config/prompt-templates.ts +5 -5
  7. package/src/config/settings-schema.ts +4 -4
  8. package/src/cursor.ts +3 -8
  9. package/src/discovery/helpers.ts +3 -3
  10. package/src/edit/diff.ts +50 -47
  11. package/src/edit/index.ts +86 -57
  12. package/src/edit/line-hash.ts +743 -24
  13. package/src/edit/modes/apply-patch.ts +0 -9
  14. package/src/edit/modes/atom.ts +893 -0
  15. package/src/edit/modes/chunk.ts +14 -24
  16. package/src/edit/modes/hashline.ts +193 -146
  17. package/src/edit/modes/patch.ts +5 -9
  18. package/src/edit/modes/replace.ts +6 -11
  19. package/src/edit/renderer.ts +14 -10
  20. package/src/edit/streaming.ts +50 -16
  21. package/src/exec/bash-executor.ts +2 -4
  22. package/src/export/html/template.generated.ts +1 -1
  23. package/src/export/html/template.js +4 -12
  24. package/src/extensibility/custom-tools/types.ts +2 -0
  25. package/src/extensibility/custom-tools/wrapper.ts +2 -1
  26. package/src/internal-urls/docs-index.generated.ts +2 -2
  27. package/src/lsp/defaults.json +142 -652
  28. package/src/lsp/index.ts +1 -1
  29. package/src/mcp/render.ts +1 -8
  30. package/src/modes/components/assistant-message.ts +4 -0
  31. package/src/modes/components/diff.ts +23 -14
  32. package/src/modes/components/footer.ts +21 -16
  33. package/src/modes/components/session-selector.ts +3 -3
  34. package/src/modes/components/settings-defs.ts +6 -1
  35. package/src/modes/components/todo-reminder.ts +1 -8
  36. package/src/modes/components/tool-execution.ts +1 -4
  37. package/src/modes/controllers/selector-controller.ts +1 -1
  38. package/src/modes/print-mode.ts +8 -0
  39. package/src/prompts/agents/librarian.md +1 -1
  40. package/src/prompts/agents/reviewer.md +4 -4
  41. package/src/prompts/ci-green-request.md +1 -1
  42. package/src/prompts/review-request.md +1 -1
  43. package/src/prompts/system/subagent-system-prompt.md +3 -3
  44. package/src/prompts/system/subagent-yield-reminder.md +11 -0
  45. package/src/prompts/system/system-prompt.md +3 -0
  46. package/src/prompts/tools/ask.md +3 -2
  47. package/src/prompts/tools/ast-edit.md +16 -20
  48. package/src/prompts/tools/ast-grep.md +19 -24
  49. package/src/prompts/tools/atom.md +87 -0
  50. package/src/prompts/tools/chunk-edit.md +37 -161
  51. package/src/prompts/tools/debug.md +4 -5
  52. package/src/prompts/tools/exit-plan-mode.md +4 -5
  53. package/src/prompts/tools/find.md +4 -8
  54. package/src/prompts/tools/github.md +18 -0
  55. package/src/prompts/tools/grep.md +4 -5
  56. package/src/prompts/tools/hashline.md +22 -89
  57. package/src/prompts/tools/{gemini-image.md → image-gen.md} +1 -1
  58. package/src/prompts/tools/inspect-image.md +6 -6
  59. package/src/prompts/tools/lsp.md +1 -1
  60. package/src/prompts/tools/patch.md +12 -19
  61. package/src/prompts/tools/python.md +3 -2
  62. package/src/prompts/tools/read-chunk.md +2 -3
  63. package/src/prompts/tools/read.md +2 -2
  64. package/src/prompts/tools/ssh.md +8 -17
  65. package/src/prompts/tools/todo-write.md +54 -41
  66. package/src/sdk.ts +14 -9
  67. package/src/session/agent-session.ts +25 -2
  68. package/src/session/session-manager.ts +4 -1
  69. package/src/task/executor.ts +43 -48
  70. package/src/task/render.ts +11 -13
  71. package/src/tools/ask.ts +7 -7
  72. package/src/tools/ast-edit.ts +45 -41
  73. package/src/tools/ast-grep.ts +77 -85
  74. package/src/tools/bash.ts +8 -9
  75. package/src/tools/browser.ts +32 -30
  76. package/src/tools/calculator.ts +4 -4
  77. package/src/tools/cancel-job.ts +1 -1
  78. package/src/tools/checkpoint.ts +2 -2
  79. package/src/tools/debug.ts +41 -37
  80. package/src/tools/exit-plan-mode.ts +1 -1
  81. package/src/tools/find.ts +4 -4
  82. package/src/tools/gh-renderer.ts +12 -4
  83. package/src/tools/gh.ts +509 -697
  84. package/src/tools/grep.ts +116 -131
  85. package/src/tools/{gemini-image.ts → image-gen.ts} +459 -60
  86. package/src/tools/index.ts +14 -32
  87. package/src/tools/inspect-image.ts +3 -3
  88. package/src/tools/json-tree.ts +114 -114
  89. package/src/tools/match-line-format.ts +8 -7
  90. package/src/tools/notebook.ts +8 -7
  91. package/src/tools/poll-tool.ts +2 -1
  92. package/src/tools/python.ts +9 -23
  93. package/src/tools/read.ts +32 -25
  94. package/src/tools/render-mermaid.ts +1 -1
  95. package/src/tools/render-utils.ts +18 -0
  96. package/src/tools/renderers.ts +2 -2
  97. package/src/tools/report-tool-issue.ts +3 -2
  98. package/src/tools/resolve.ts +1 -1
  99. package/src/tools/review.ts +12 -10
  100. package/src/tools/search-tool-bm25.ts +2 -4
  101. package/src/tools/ssh.ts +4 -4
  102. package/src/tools/todo-write.ts +172 -147
  103. package/src/tools/vim.ts +14 -15
  104. package/src/tools/write.ts +4 -4
  105. package/src/tools/{submit-result.ts → yield.ts} +11 -13
  106. package/src/utils/edit-mode.ts +2 -1
  107. package/src/utils/file-display-mode.ts +10 -5
  108. package/src/utils/git.ts +9 -5
  109. package/src/utils/shell-snapshot.ts +2 -3
  110. package/src/vim/render.ts +4 -4
  111. package/src/prompts/system/subagent-submit-reminder.md +0 -11
  112. package/src/prompts/tools/gh-issue-view.md +0 -11
  113. package/src/prompts/tools/gh-pr-checkout.md +0 -12
  114. package/src/prompts/tools/gh-pr-diff.md +0 -12
  115. package/src/prompts/tools/gh-pr-push.md +0 -12
  116. package/src/prompts/tools/gh-pr-view.md +0 -11
  117. package/src/prompts/tools/gh-repo-view.md +0 -11
  118. package/src/prompts/tools/gh-run-watch.md +0 -12
  119. package/src/prompts/tools/gh-search-issues.md +0 -11
  120. package/src/prompts/tools/gh-search-prs.md +0 -11
@@ -50,25 +50,18 @@ 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
- <example name="create">
54
- edit {"edits":[{"path":"hello.txt","op":"create","diff":"Hello\n"}]}
55
- </example>
56
-
57
- <example name="update">
58
- edit {"edits":[{"path":"src/app.py","op":"update","diff":"@@ def greet():\n def greet():\n-print('Hi')\n+print('Hello')\n"}]}
59
- </example>
60
-
61
- <example name="rename">
62
- edit {"edits":[{"path":"src/app.py","op":"update","rename":"src/main.py","diff":"@@\n …\n"}]}
63
- </example>
64
-
65
- <example name="delete">
66
- edit {"edits":[{"path":"obsolete.txt","op":"delete"}]}
67
- </example>
68
-
69
- <example name="multi-file">
70
- edit {"edits":[{"path":"src/types.ts","op":"update","diff":"@@\n-old\n+new\n"},{"path":"src/index.ts","op":"update","diff":"@@\n-old\n+new\n"}]}
71
- </example>
53
+ <examples>
54
+ # Create
55
+ `edit {"edits":[{"path":"hello.txt","op":"create","diff":"Hello\n"}]}`
56
+ # Update
57
+ `edit {"edits":[{"path":"src/app.py","op":"update","diff":"@@ def greet():\n def greet():\n-print('Hi')\n+print('Hello')\n"}]}`
58
+ # Rename
59
+ `edit {"edits":[{"path":"src/app.py","op":"update","rename":"src/main.py","diff":"@@\n …\n"}]}`
60
+ # Delete
61
+ `edit {"edits":[{"path":"obsolete.txt","op":"delete"}]}`
62
+ # Multi-file
63
+ `edit {"edits":[{"path":"src/types.ts","op":"update","diff":"@@\n-old\n+new\n"},{"path":"src/index.ts","op":"update","diff":"@@\n-old\n+new\n"}]}`
64
+ </examples>
72
65
 
73
66
  <avoid>
74
67
  - Generic anchors: `import`, `export`, `describe`, `function`, `const`
@@ -44,7 +44,8 @@ User sees output like Jupyter notebook; rich displays render fully:
44
44
  - You **MUST** use `run()` for shell commands; you **MUST NOT** use raw `subprocess`
45
45
  </critical>
46
46
 
47
- <example name="multiple small cells">
47
+ <examples>
48
+ # Multiple small cells
48
49
  ```python
49
50
  cells: [
50
51
  {"title": "imports", "code": "import json\nfrom pathlib import Path"},
@@ -53,4 +54,4 @@ cells: [
53
54
  {"title": "use helper", "code": "configs = [parse_config(p) for p in Path('.').glob('*.json')]"}
54
55
  ]
55
56
  ```
56
- </example>
57
+ </examples>
@@ -2,7 +2,6 @@ Reads files using syntax-aware chunks. Also inspects directories, archives, SQLi
2
2
 
3
3
  <instruction>
4
4
  The chunk-aware `read` variant returns AST-scoped chunks with current checksum IDs for structural editing, and otherwise behaves like `open` for non-code content.
5
-
6
5
  - You **MUST** parallelize calls when exploring related files
7
6
  - For URLs, `read` fetches the page and returns clean extracted text/markdown by default (reader-mode). It handles HTML pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom, JSON endpoints, PDFs, etc. You **SHOULD** reach for `read` — not a browser/puppeteer tool — for fetching and inspecting web content.
8
7
 
@@ -17,7 +16,7 @@ The chunk-aware `read` variant returns AST-scoped chunks with current checksum I
17
16
  |---|---|
18
17
  |*(omitted)*|Read full file as chunks (up to {{DEFAULT_LIMIT}} lines)|
19
18
  |`class_Foo`|Read a specific chunk|
20
- |`class_Foo.fn_bar#ABCD~`|Read a chunk region (body `~` / head `^`) by ID|
19
+ |`class_Foo.fn_bar#thth~`|Read a chunk region (body `~` / head `^`) by ID|
21
20
  |`?`|List all chunk paths with IDs|
22
21
  |`L50`|Read from line 50 onward (shorthand for L50 to EOF)|
23
22
  |`L50-L120`|Read lines 50 through 120|
@@ -27,7 +26,7 @@ The chunk-aware `read` variant returns AST-scoped chunks with current checksum I
27
26
  Max {{DEFAULT_MAX_LINES}} lines per call.
28
27
 
29
28
  # Chunks
30
- Each anchor `@full.chunk.path#CCCC` (with `-` prefixes for nesting depth) in the output identifies a chunk. Use `full.chunk.path#CCCC` as-is to read truncated chunks.
29
+ Each anchor `@full.chunk.path#thth` (with `-` prefixes for nesting depth) in the output identifies a chunk. Use `full.chunk.path#thth` as-is to read truncated chunks.
31
30
  If you need a canonical target list, run `read(path="file", sel="?")`. That listing shows chunk paths with IDs and is the safest structural discovery mode. Summary lines in this listing are orientation hints; follow a selector with `read(path="file", sel="chunk#ID")` or use `raw` when you need exact source.
32
31
  Line numbers in the gutter are absolute file line numbers.
33
32
 
@@ -18,13 +18,13 @@ The `read` tool is multi-purpose and more capable than it looks — inspects fil
18
18
  |`L50`|Read from line 50 onward (shorthand for L50 to EOF)|
19
19
  |`L50-L120`|Read lines 50 through 120|
20
20
  |`L20-L20`|Read exactly one line|
21
- |`raw`|Raw content without transformations (for URLs: untouched HTML)|
21
+ |`raw`|Skip line-numbering / hashline / chunking; return file content as plain text. For URLs: untouched HTML.|
22
22
 
23
23
  Max {{DEFAULT_MAX_LINES}} lines per call.
24
24
 
25
25
  # Filesystem
26
26
  {{#if IS_HASHLINE_MODE}}
27
- - Reading from FS returns lines prefixed with anchors: `41#ZZ:def alpha():`
27
+ - Reading from FS returns lines prefixed with anchors: `41th|def alpha():` (line number, 2-letter ID, pipe, then content)
28
28
  {{else}}
29
29
  {{#if IS_LINE_NUMBER_MODE}}
30
30
  - Reading from FS returns lines prefixed with line numbers: `41:def alpha():`
@@ -25,20 +25,11 @@ You **MUST** build commands from the reference below
25
25
  You **MUST** verify the shell type from "Available hosts" and use matching commands.
26
26
  </critical>
27
27
 
28
- <example name="linux">
29
- Task: List /home/user files on "server1"
30
- Host: server1 (10.0.0.1) | linux/bash
31
- Command: `ls -la /home/user`
32
- </example>
33
-
34
- <example name="windows-cmd">
35
- Task: Show running processes on "winbox"
36
- Host: winbox (192.168.1.5) | windows/cmd
37
- Command: `tasklist /v`
38
- </example>
39
-
40
- <example name="macos">
41
- Task: Get system info on "macbook"
42
- Host: macbook (10.0.0.20) | macos/zsh
43
- Command: `uname -a && sw_vers`
44
- </example>
28
+ <examples>
29
+ # List files: Linux
30
+ Host: server1 (10.0.0.1) | linux/bash. Command: `ls -la /home/user`
31
+ # Show running processes: Windows cmd
32
+ Host: winbox (192.168.1.5) | windows/cmd. Command: `tasklist /v`
33
+ # Get system info: macOS
34
+ Host: macbook (10.0.0.20) | macos/zsh. Command: `uname -a && sw_vers`
35
+ </examples>
@@ -1,28 +1,53 @@
1
- Manages a phased task list. Each field is a verb set the ones you need in a single call.
1
+ Manages a phased task list through an `ops` array of flat operations.
2
2
  The next pending task is auto-promoted to `in_progress` after completing the current one.
3
3
 
4
4
  <protocol>
5
- ## Fields
5
+ ## Shape
6
+
7
+ Pass an object with an `ops` array:
8
+
9
+ ```ts
10
+ {
11
+ ops: [
12
+ { op: "replace", phases: [...] },
13
+ { op: "start", task: "task-3" },
14
+ { op: "done", phase: "Implementation" },
15
+ { op: "rm" },
16
+ { op: "drop", task: "task-9" },
17
+ { op: "append", phase: "Implementation", items: [{ id: "task-10", label: "Run tests" }] },
18
+ ],
19
+ }
20
+ ```
21
+
22
+ ## Operation fields
6
23
 
7
24
  |Field|Type|When to use|
8
25
  |---|---|---|
9
- |`phases`|Phase[]|Initial setup, or full restructure when the plan changes significantly|
10
- |`complete`|string[]|Mark tasks done|
11
- |`start`|string|Jump to a specific task out of order|
12
- |`abandon`|string[]|Drop tasks intentionally|
13
- |`remove`|string[]|Remove tasks that are no longer relevant|
14
- |`add_notes`|{id, notes}[]|Append runtime observations to tasks|
15
- |`add_tasks`|{phase, content, details?}[]|Add tasks to a phase (by name or ID)|
16
- |`add_phase`|{name, tasks?}|Add a new phase of work discovered mid-task|
26
+ |`op`|string|Required. One of `replace`, `start`, `done`, `rm`, `drop`, `append`|
27
+ |`task`|string|Task id for `start`, or a task target for `done` / `rm` / `drop`|
28
+ |`phase`|string|Phase target for `done` / `rm` / `drop`, or append destination for `append`|
29
+ |`items`|{id, label}[]|Required for `append`. If the phase does not exist, it is created at the end|
30
+ |`phases`|Phase[]|Only for `replace`. Keeps initial phased setup available for harness bootstrap and full restructures|
31
+
32
+ ## Semantics
33
+ - `start`: requires `task`; sets that task to `in_progress`
34
+ - `done`: marks one task, one phase, or all tasks completed
35
+ - `rm`: removes one task, one phase's tasks, or all tasks
36
+ - `drop`: marks one task, one phase, or all tasks abandoned
37
+ - `append`: appends `items` to `phase`; creates the phase if missing
38
+ - `replace`: replaces the full todo list
39
+
40
+ If `done`, `rm`, or `drop` omits both `task` and `phase`, it applies to all tasks.
17
41
 
18
42
  ## Task Anatomy
19
- - `content`: Short label (5-10 words). What is being done, not how.
20
- - `details`: File paths, implementation steps, edge cases. Shown only when the task is active.
43
+ - `label`: Short label (5-10 words). What is being done, not how.
44
+ - `replace` task `content` should stay short and specific.
21
45
 
22
46
  ## Rules
23
- - Mark tasks completed immediately after finishing — never defer
24
- - Complete phases in order — do not skip ahead while earlier ones are pending
25
- - On blockers: add a new task describing the blocker
47
+ - Mark tasks done immediately after finishing — never defer.
48
+ - Complete phases in order — do not skip ahead while earlier ones are pending.
49
+ - On blockers, append a new task to the active phase.
50
+ - Keep ids stable once introduced.
26
51
  </protocol>
27
52
 
28
53
  <conditions>
@@ -33,32 +58,20 @@ Create a todo list when:
33
58
  4. New instructions arrive mid-task — capture before proceeding
34
59
  </conditions>
35
60
 
36
- <example name="initial-setup">
37
- {phases: [
38
- {name: "Investigation", tasks: [{content: "Read source"}, {content: "Map callsites"}]},
39
- {name: "Implementation", tasks: [{content: "Apply fix", details: "Update parser.ts to handle edge case in line 42"}, {content: "Run tests"}]}
40
- ]}
41
- </example>
42
-
43
- <example name="complete">
44
- {complete: ["task-2", "task-3"]}
45
- </example>
46
-
47
- <example name="add-notes">
48
- {add_notes: [{id: "task-3", notes: "Found edge case in parser — needs null check"}]}
49
- </example>
50
-
51
- <example name="add-task">
52
- {add_tasks: [{phase: "Implementation", content: "Handle retries", details: "Cap exponential backoff in retry.ts"}]}
53
- </example>
54
-
55
- <example name="add-phase">
56
- {add_phase: {name: "Cleanup", tasks: [{content: "Remove dead code"}]}}
57
- </example>
58
-
59
- <example name="combined">
60
- {complete: ["task-2"], add_notes: [{id: "task-3", notes: "Needs extra validation"}]}
61
- </example>
61
+ <examples>
62
+ # Initial setup
63
+ `{"ops":[{"op":"replace","phases":[{"name":"Investigation","tasks":[{"content":"Read source"},{"content":"Map callsites"}]},{"name":"Implementation","tasks":[{"content":"Apply fix"},{"content":"Run tests"}]}]}]}`
64
+ # Complete one task
65
+ `{"ops":[{"op":"done","task":"task-2"}]}`
66
+ # Complete a whole phase
67
+ `{"ops":[{"op":"done","phase":"Implementation"}]}`
68
+ # Remove all tasks
69
+ `{"ops":[{"op":"rm"}]}`
70
+ # Drop one task
71
+ `{"ops":[{"op":"drop","task":"task-7"}]}`
72
+ # Append tasks to a phase
73
+ `{"ops":[{"op":"append","phase":"Implementation","items":[{"id":"task-8","label":"Handle retries"},{"id":"task-9","label":"Run tests"}]}]}`
74
+ </examples>
62
75
 
63
76
  <avoid>
64
77
  - Single-step tasks — act directly
package/src/sdk.ts CHANGED
@@ -128,7 +128,7 @@ import {
128
128
  warmupLspServers,
129
129
  } from "./tools";
130
130
  import { ToolContextStore } from "./tools/context";
131
- import { getGeminiImageTools } from "./tools/gemini-image";
131
+ import { getImageGenTools } from "./tools/image-gen";
132
132
  import { wrapToolWithMetaNotice } from "./tools/output-meta";
133
133
  import { queueResolveHandler } from "./tools/resolve";
134
134
  import { EventBus } from "./utils/event-bus";
@@ -209,8 +209,8 @@ export interface CreateAgentSessionOptions {
209
209
 
210
210
  /** Output schema for structured completion (subagents) */
211
211
  outputSchema?: unknown;
212
- /** Whether to include the submit_result tool by default */
213
- requireSubmitResultTool?: boolean;
212
+ /** Whether to include the yield tool by default */
213
+ requireYieldTool?: boolean;
214
214
  /** Task recursion depth (for subagent sessions). Default: 0 */
215
215
  taskDepth?: number;
216
216
  /** Parent task ID prefix for nested artifact naming (e.g., "6-Extensions") */
@@ -673,7 +673,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
673
673
  }
674
674
 
675
675
  const imageProvider = settings.get("providers.image");
676
- if (imageProvider === "auto" || imageProvider === "gemini" || imageProvider === "openrouter") {
676
+ if (
677
+ imageProvider === "auto" ||
678
+ imageProvider === "openai" ||
679
+ imageProvider === "gemini" ||
680
+ imageProvider === "openrouter"
681
+ ) {
677
682
  setPreferredImageProvider(imageProvider);
678
683
  }
679
684
 
@@ -916,7 +921,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
916
921
  skills,
917
922
  eventBus,
918
923
  outputSchema: options.outputSchema,
919
- requireSubmitResultTool: options.requireSubmitResultTool,
924
+ requireYieldTool: options.requireYieldTool,
920
925
  taskDepth: options.taskDepth ?? 0,
921
926
  getSessionFile: () => sessionManager.getSessionFile() ?? null,
922
927
  getPythonKernelOwnerId: () => pythonKernelOwnerId,
@@ -1047,10 +1052,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1047
1052
  }
1048
1053
  toolSession.mcpManager = mcpManager;
1049
1054
 
1050
- // Add Gemini image tools if GEMINI_API_KEY (or GOOGLE_API_KEY) is available
1051
- const geminiImageTools = await logger.time("getGeminiImageTools", getGeminiImageTools);
1052
- if (geminiImageTools.length > 0) {
1053
- customTools.push(...(geminiImageTools as unknown as CustomTool[]));
1055
+ // Add image tools when the active model or configured image providers can generate images.
1056
+ const imageGenTools = await logger.time("getImageGenTools", () => getImageGenTools(modelRegistry, model));
1057
+ if (imageGenTools.length > 0) {
1058
+ customTools.push(...(imageGenTools as unknown as CustomTool[]));
1054
1059
  }
1055
1060
 
1056
1061
  // Add web search tools
@@ -525,6 +525,7 @@ export class AgentSession {
525
525
  #obfuscator: SecretObfuscator | undefined;
526
526
  #checkpointState: CheckpointState | undefined = undefined;
527
527
  #pendingRewindReport: string | undefined = undefined;
528
+ #lastSuccessfulYieldToolCallId: string | undefined = undefined;
528
529
  #promptGeneration = 0;
529
530
  #providerSessionState = new Map<string, ProviderSessionState>();
530
531
 
@@ -789,6 +790,9 @@ export class AgentSession {
789
790
  this.#toolChoiceQueue.resolve();
790
791
  }
791
792
  }
793
+ if (event.type === "tool_execution_end" && event.toolName === "yield" && !event.isError) {
794
+ this.#lastSuccessfulYieldToolCallId = event.toolCallId;
795
+ }
792
796
  if (event.type === "turn_end" && this.#pendingRewindReport) {
793
797
  const report = this.#pendingRewindReport;
794
798
  this.#pendingRewindReport = undefined;
@@ -1026,7 +1030,10 @@ export class AgentSession {
1026
1030
  .find((message): message is AssistantMessage => message.role === "assistant");
1027
1031
  const msg = this.#lastAssistantMessage ?? fallbackAssistant;
1028
1032
  this.#lastAssistantMessage = undefined;
1029
- if (!msg) return;
1033
+ if (!msg) {
1034
+ this.#lastSuccessfulYieldToolCallId = undefined;
1035
+ return;
1036
+ }
1030
1037
 
1031
1038
  // Invalidate GitHub Copilot credentials on auth failure so stale tokens
1032
1039
  // aren't reused on the next request
@@ -1040,8 +1047,15 @@ export class AgentSession {
1040
1047
 
1041
1048
  if (this.#skipPostTurnMaintenanceAssistantTimestamp === msg.timestamp) {
1042
1049
  this.#skipPostTurnMaintenanceAssistantTimestamp = undefined;
1050
+ this.#lastSuccessfulYieldToolCallId = undefined;
1051
+ return;
1052
+ }
1053
+
1054
+ if (this.#assistantEndedWithSuccessfulYield(msg)) {
1055
+ this.#lastSuccessfulYieldToolCallId = undefined;
1043
1056
  return;
1044
1057
  }
1058
+ this.#lastSuccessfulYieldToolCallId = undefined;
1045
1059
 
1046
1060
  // Check for retryable errors first (overloaded, rate limit, server errors)
1047
1061
  if (this.#isRetryableError(msg)) {
@@ -3227,7 +3241,6 @@ export class AgentSession {
3227
3241
  id: task.id,
3228
3242
  content: task.content,
3229
3243
  status: task.status,
3230
- notes: task.notes,
3231
3244
  })),
3232
3245
  }));
3233
3246
  }
@@ -4230,6 +4243,16 @@ export class AgentSession {
4230
4243
  }
4231
4244
  }
4232
4245
  }
4246
+ #assistantEndedWithSuccessfulYield(assistantMessage: AssistantMessage): boolean {
4247
+ const toolCallId = this.#lastSuccessfulYieldToolCallId;
4248
+ if (!toolCallId) return false;
4249
+ const lastToolCall = assistantMessage.content
4250
+ .slice()
4251
+ .reverse()
4252
+ .find((content): content is ToolCall => content.type === "toolCall");
4253
+ return lastToolCall?.name === "yield" && lastToolCall.id === toolCallId;
4254
+ }
4255
+
4233
4256
  #enforceRewindBeforeYield(): boolean {
4234
4257
  if (!this.#checkpointState || this.#pendingRewindReport) {
4235
4258
  return false;
@@ -259,6 +259,8 @@ export interface SessionInfo {
259
259
  created: Date;
260
260
  modified: Date;
261
261
  messageCount: number;
262
+ /** File size in bytes on disk; used for compact list rendering. */
263
+ size: number;
262
264
  firstMessage: string;
263
265
  allMessagesText: string;
264
266
  }
@@ -1264,7 +1266,7 @@ function extractTextFromContent(content: Message["content"]): string {
1264
1266
  .join(" ");
1265
1267
  }
1266
1268
 
1267
- const SESSION_LIST_PREFIX_BYTES = 1024;
1269
+ const SESSION_LIST_PREFIX_BYTES = 4096;
1268
1270
  const SESSION_LIST_PARALLEL_THRESHOLD = 64;
1269
1271
  const SESSION_LIST_MAX_WORKERS = 16;
1270
1272
  const sessionListPrefixDecoder = new TextDecoder("utf-8", { fatal: false });
@@ -1466,6 +1468,7 @@ async function collectSessionFromFile(
1466
1468
  created: new Date(header.timestamp ?? ""),
1467
1469
  modified: stats.mtime,
1468
1470
  messageCount,
1471
+ size: stats.size,
1469
1472
  firstMessage: firstMessage || "(no messages)",
1470
1473
  allMessagesText: allMessages.length > 0 ? allMessages.join(" ") : firstMessage,
1471
1474
  };
@@ -18,8 +18,8 @@ import { runExtensionCompact, runExtensionSetModel } from "../extensibility/exte
18
18
  import type { Skill } from "../extensibility/skills";
19
19
  import { callTool } from "../mcp/client";
20
20
  import type { MCPManager } from "../mcp/manager";
21
- import submitReminderTemplate from "../prompts/system/subagent-submit-reminder.md" with { type: "text" };
22
21
  import subagentSystemPromptTemplate from "../prompts/system/subagent-system-prompt.md" with { type: "text" };
22
+ import submitReminderTemplate from "../prompts/system/subagent-yield-reminder.md" with { type: "text" };
23
23
  import { createAgentSession, discoverAuthStorage } from "../sdk";
24
24
  import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
25
25
  import type { AuthStorage } from "../session/auth-storage";
@@ -223,7 +223,7 @@ function resolveFallbackCompletion(rawOutput: string, outputSchema: unknown): {
223
223
  return { data: candidate };
224
224
  }
225
225
 
226
- export interface SubmitResultItem {
226
+ export interface YieldItem {
227
227
  data?: unknown;
228
228
  status?: "success" | "aborted";
229
229
  error?: string;
@@ -235,7 +235,7 @@ interface FinalizeSubprocessOutputArgs {
235
235
  stderr: string;
236
236
  doneAborted: boolean;
237
237
  signalAborted: boolean;
238
- submitResultItems?: SubmitResultItem[];
238
+ yieldItems?: YieldItem[];
239
239
  reportFindings?: ReviewFinding[];
240
240
  outputSchema: unknown;
241
241
  }
@@ -244,44 +244,42 @@ interface FinalizeSubprocessOutputResult {
244
244
  rawOutput: string;
245
245
  exitCode: number;
246
246
  stderr: string;
247
- abortedViaSubmitResult: boolean;
248
- hasSubmitResult: boolean;
247
+ abortedViaYield: boolean;
248
+ hasYield: boolean;
249
249
  }
250
250
 
251
- export const SUBAGENT_WARNING_NULL_SUBMIT_RESULT = "SYSTEM WARNING: Subagent called submit_result with null data.";
252
- export const SUBAGENT_WARNING_MISSING_SUBMIT_RESULT =
253
- "SYSTEM WARNING: Subagent exited without calling submit_result tool after 3 reminders.";
251
+ export const SUBAGENT_WARNING_NULL_YIELD = "SYSTEM WARNING: Subagent called yield with null data.";
252
+ export const SUBAGENT_WARNING_MISSING_YIELD =
253
+ "SYSTEM WARNING: Subagent exited without calling yield tool after 3 reminders.";
254
254
 
255
255
  export function finalizeSubprocessOutput(args: FinalizeSubprocessOutputArgs): FinalizeSubprocessOutputResult {
256
256
  let { rawOutput, exitCode, stderr } = args;
257
- const { submitResultItems, reportFindings, doneAborted, signalAborted, outputSchema } = args;
258
- let abortedViaSubmitResult = false;
259
- const hasSubmitResult = Array.isArray(submitResultItems) && submitResultItems.length > 0;
260
-
261
- if (hasSubmitResult) {
262
- const lastSubmitResult = submitResultItems[submitResultItems.length - 1];
263
- if (lastSubmitResult?.status === "aborted") {
264
- abortedViaSubmitResult = true;
257
+ const { yieldItems, reportFindings, doneAborted, signalAborted, outputSchema } = args;
258
+ let abortedViaYield = false;
259
+ const hasYield = Array.isArray(yieldItems) && yieldItems.length > 0;
260
+
261
+ if (hasYield) {
262
+ const lastYield = yieldItems[yieldItems.length - 1];
263
+ if (lastYield?.status === "aborted") {
264
+ abortedViaYield = true;
265
265
  exitCode = 0;
266
- stderr = lastSubmitResult.error || "Subagent aborted task";
266
+ stderr = lastYield.error || "Subagent aborted task";
267
267
  try {
268
- rawOutput = JSON.stringify({ aborted: true, error: lastSubmitResult.error }, null, 2);
268
+ rawOutput = JSON.stringify({ aborted: true, error: lastYield.error }, null, 2);
269
269
  } catch {
270
- rawOutput = `{"aborted":true,"error":"${lastSubmitResult.error || "Unknown error"}"}`;
270
+ rawOutput = `{"aborted":true,"error":"${lastYield.error || "Unknown error"}"}`;
271
271
  }
272
272
  } else {
273
- const submitData = lastSubmitResult?.data;
273
+ const submitData = lastYield?.data;
274
274
  if (submitData === null || submitData === undefined) {
275
- rawOutput = rawOutput
276
- ? `${SUBAGENT_WARNING_NULL_SUBMIT_RESULT}\n\n${rawOutput}`
277
- : SUBAGENT_WARNING_NULL_SUBMIT_RESULT;
275
+ rawOutput = rawOutput ? `${SUBAGENT_WARNING_NULL_YIELD}\n\n${rawOutput}` : SUBAGENT_WARNING_NULL_YIELD;
278
276
  } else {
279
277
  const completeData = normalizeCompleteData(submitData, reportFindings);
280
278
  try {
281
279
  rawOutput = JSON.stringify(completeData, null, 2) ?? "null";
282
280
  } catch (err) {
283
281
  const errorMessage = err instanceof Error ? err.message : String(err);
284
- rawOutput = `{"error":"Failed to serialize submit_result data: ${errorMessage}"}`;
282
+ rawOutput = `{"error":"Failed to serialize yield data: ${errorMessage}"}`;
285
283
  }
286
284
  exitCode = 0;
287
285
  stderr = "";
@@ -307,17 +305,15 @@ export function finalizeSubprocessOutput(args: FinalizeSubprocessOutputArgs): Fi
307
305
  stderr = "";
308
306
  } else if (exitCode === 0) {
309
307
  const hasRawOutput = rawOutput.trim().length > 0;
310
- rawOutput = rawOutput
311
- ? `${SUBAGENT_WARNING_MISSING_SUBMIT_RESULT}\n\n${rawOutput}`
312
- : SUBAGENT_WARNING_MISSING_SUBMIT_RESULT;
308
+ rawOutput = rawOutput ? `${SUBAGENT_WARNING_MISSING_YIELD}\n\n${rawOutput}` : SUBAGENT_WARNING_MISSING_YIELD;
313
309
  if (hasOutputSchema || !hasRawOutput) {
314
310
  exitCode = 1;
315
- stderr = SUBAGENT_WARNING_MISSING_SUBMIT_RESULT;
311
+ stderr = SUBAGENT_WARNING_MISSING_YIELD;
316
312
  }
317
313
  }
318
314
  }
319
315
 
320
- return { rawOutput, exitCode, stderr, abortedViaSubmitResult, hasSubmitResult };
316
+ return { rawOutput, exitCode, stderr, abortedViaYield, hasYield };
321
317
  }
322
318
 
323
319
  /**
@@ -564,7 +560,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
564
560
  const abortSignal = abortController.signal;
565
561
  let activeSession: AgentSession | null = null;
566
562
  let unsubscribe: (() => void) | null = null;
567
- let submitResultCalled = false;
563
+ let yieldCalled = false;
568
564
 
569
565
  // Accumulate usage incrementally from message_end events (no memory for streaming events)
570
566
  const accumulatedUsage = {
@@ -789,8 +785,8 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
789
785
  existing.push(data);
790
786
  }
791
787
  progress.extractedToolData[event.toolName] = existing;
792
- if (event.toolName === "submit_result") {
793
- submitResultCalled = true;
788
+ if (event.toolName === "yield") {
789
+ yieldCalled = true;
794
790
  }
795
791
  }
796
792
  }
@@ -955,7 +951,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
955
951
  thinkingLevel: effectiveThinkingLevel,
956
952
  toolNames,
957
953
  outputSchema,
958
- requireSubmitResultTool: true,
954
+ requireYieldTool: true,
959
955
  contextFiles: options.contextFiles,
960
956
  skills: options.skills,
961
957
  promptTemplates: options.promptTemplates,
@@ -1070,7 +1066,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1070
1066
  await extensionRunner.emit({ type: "session_start" });
1071
1067
  }
1072
1068
 
1073
- const MAX_SUBMIT_RESULT_RETRIES = 3;
1069
+ const MAX_YIELD_RETRIES = 3;
1074
1070
  unsubscribe = session.subscribe(event => {
1075
1071
  if (isAgentEvent(event)) {
1076
1072
  try {
@@ -1087,15 +1083,15 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1087
1083
  await session.prompt(task, { attribution: "agent" });
1088
1084
  await session.waitForIdle();
1089
1085
 
1090
- const reminderToolChoice = buildNamedToolChoice("submit_result", session.model);
1086
+ const reminderToolChoice = buildNamedToolChoice("yield", session.model);
1091
1087
 
1092
1088
  let retryCount = 0;
1093
- while (!submitResultCalled && retryCount < MAX_SUBMIT_RESULT_RETRIES && !abortSignal.aborted) {
1089
+ while (!yieldCalled && retryCount < MAX_YIELD_RETRIES && !abortSignal.aborted) {
1094
1090
  try {
1095
1091
  retryCount++;
1096
1092
  const reminder = prompt.render(submitReminderTemplate, {
1097
1093
  retryCount,
1098
- maxRetries: MAX_SUBMIT_RESULT_RETRIES,
1094
+ maxRetries: MAX_YIELD_RETRIES,
1099
1095
  });
1100
1096
 
1101
1097
  await session.prompt(reminder, {
@@ -1111,7 +1107,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1111
1107
  }
1112
1108
 
1113
1109
  await session.waitForIdle();
1114
- if (!submitResultCalled && !abortSignal.aborted) {
1110
+ if (!yieldCalled && !abortSignal.aborted) {
1115
1111
  exitCode = 0;
1116
1112
  }
1117
1113
 
@@ -1186,7 +1182,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1186
1182
 
1187
1183
  // Use final output if available, otherwise accumulated output
1188
1184
  let rawOutput = finalOutputChunks.length > 0 ? finalOutputChunks.join("") : outputChunks.join("");
1189
- const submitResultItems = progress.extractedToolData?.submit_result as SubmitResultItem[] | undefined;
1185
+ const yieldItems = progress.extractedToolData?.yield as YieldItem[] | undefined;
1190
1186
  const reportFindings = progress.extractedToolData?.report_finding as ReviewFinding[] | undefined;
1191
1187
  const finalized = finalizeSubprocessOutput({
1192
1188
  rawOutput,
@@ -1194,17 +1190,16 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1194
1190
  stderr,
1195
1191
  doneAborted: Boolean(done.aborted),
1196
1192
  signalAborted: Boolean(signal?.aborted),
1197
- submitResultItems,
1193
+ yieldItems,
1198
1194
  reportFindings,
1199
1195
  outputSchema,
1200
1196
  });
1201
1197
  rawOutput = finalized.rawOutput;
1202
1198
  exitCode = finalized.exitCode;
1203
1199
  stderr = finalized.stderr;
1204
- const lastSubmitResult = submitResultItems?.[submitResultItems.length - 1];
1205
- const submitResultAbortReason =
1206
- lastSubmitResult?.status === "aborted" ? lastSubmitResult.error || "Subagent aborted task" : undefined;
1207
- const { abortedViaSubmitResult, hasSubmitResult } = finalized;
1200
+ const lastYield = yieldItems?.[yieldItems.length - 1];
1201
+ const yieldAbortReason = lastYield?.status === "aborted" ? lastYield.error || "Subagent aborted task" : undefined;
1202
+ const { abortedViaYield, hasYield } = finalized;
1208
1203
  const { content: truncatedOutput, truncated } = truncateTail(rawOutput, {
1209
1204
  maxBytes: MAX_OUTPUT_BYTES,
1210
1205
  maxLines: MAX_OUTPUT_LINES,
@@ -1228,16 +1223,16 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
1228
1223
  }
1229
1224
 
1230
1225
  // Update final progress
1231
- const wasAborted = abortedViaSubmitResult || (!hasSubmitResult && (done.aborted || signal?.aborted || false));
1226
+ const wasAborted = abortedViaYield || (!hasYield && (done.aborted || signal?.aborted || false));
1232
1227
  const finalAbortReason = wasAborted
1233
- ? abortedViaSubmitResult
1234
- ? submitResultAbortReason
1228
+ ? abortedViaYield
1229
+ ? yieldAbortReason
1235
1230
  : (done.abortReason ?? (signal?.aborted ? resolveSignalAbortReason() : "Subagent aborted task"))
1236
1231
  : undefined;
1237
1232
  progress.status = wasAborted ? "aborted" : exitCode === 0 ? "completed" : "failed";
1238
1233
  scheduleProgress(true);
1239
1234
 
1240
- // Emit lifecycle end event after finalization so submit_result status is reflected
1235
+ // Emit lifecycle end event after finalization so yield status is reflected
1241
1236
  if (options.eventBus) {
1242
1237
  options.eventBus.emit(TASK_SUBAGENT_LIFECYCLE_CHANNEL, {
1243
1238
  id,