@astralyn/pi 1.4.9 → 1.4.11

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 (230) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/dist/bundle/chunks/{chunk-BAGHIP5J.js → chunk-NUQMVNKQ.js} +3 -3
  3. package/dist/bundle/chunks/{chunk-3PKXUKTB.js → chunk-PUU43PDV.js} +121 -136
  4. package/dist/bundle/chunks/{chunk-F66AY3WR.js → chunk-TH3QDDPD.js} +1 -1
  5. package/dist/bundle/chunks/{execute-P22VMM2H.js → execute-VLAIP26K.js} +1 -1
  6. package/dist/bundle/chunks/{virtual-modules-VKMBQTR2.js → virtual-modules-63XNTSVN.js} +1 -1
  7. package/dist/bundle/cli.js +1 -1
  8. package/dist/bundle/index.js +1 -1
  9. package/dist/bundle/rpc-entry.js +1 -1
  10. package/dist/core/agent-session.d.ts +34 -0
  11. package/dist/core/agent-session.d.ts.map +1 -1
  12. package/dist/core/agent-session.js +99 -5
  13. package/dist/core/agent-session.js.map +1 -1
  14. package/dist/core/extensions/index.d.ts +1 -1
  15. package/dist/core/extensions/index.d.ts.map +1 -1
  16. package/dist/core/extensions/index.js.map +1 -1
  17. package/dist/core/extensions/loader.d.ts.map +1 -1
  18. package/dist/core/extensions/loader.js +14 -7
  19. package/dist/core/extensions/loader.js.map +1 -1
  20. package/dist/core/isolated-session.d.ts +7 -2
  21. package/dist/core/isolated-session.d.ts.map +1 -1
  22. package/dist/core/isolated-session.js +7 -3
  23. package/dist/core/isolated-session.js.map +1 -1
  24. package/dist/core/session-manager.d.ts +17 -1
  25. package/dist/core/session-manager.d.ts.map +1 -1
  26. package/dist/core/session-manager.js +29 -0
  27. package/dist/core/session-manager.js.map +1 -1
  28. package/dist/core/tasks/delivery.d.ts +31 -12
  29. package/dist/core/tasks/delivery.d.ts.map +1 -1
  30. package/dist/core/tasks/delivery.js +97 -36
  31. package/dist/core/tasks/delivery.js.map +1 -1
  32. package/dist/core/tasks/execution.d.ts +27 -14
  33. package/dist/core/tasks/execution.d.ts.map +1 -1
  34. package/dist/core/tasks/execution.js +90 -86
  35. package/dist/core/tasks/execution.js.map +1 -1
  36. package/dist/core/tasks/limits.d.ts +7 -11
  37. package/dist/core/tasks/limits.d.ts.map +1 -1
  38. package/dist/core/tasks/limits.js +8 -16
  39. package/dist/core/tasks/limits.js.map +1 -1
  40. package/dist/core/tasks/output.d.ts +9 -1
  41. package/dist/core/tasks/output.d.ts.map +1 -1
  42. package/dist/core/tasks/output.js +23 -10
  43. package/dist/core/tasks/output.js.map +1 -1
  44. package/dist/core/tasks/presentation.d.ts +1 -1
  45. package/dist/core/tasks/presentation.d.ts.map +1 -1
  46. package/dist/core/tasks/presentation.js +6 -4
  47. package/dist/core/tasks/presentation.js.map +1 -1
  48. package/dist/core/tasks/record.d.ts +41 -29
  49. package/dist/core/tasks/record.d.ts.map +1 -1
  50. package/dist/core/tasks/record.js +82 -37
  51. package/dist/core/tasks/record.js.map +1 -1
  52. package/dist/core/tasks/reminder.js +2 -2
  53. package/dist/core/tasks/reminder.js.map +1 -1
  54. package/dist/core/tasks/runtime.d.ts +15 -23
  55. package/dist/core/tasks/runtime.d.ts.map +1 -1
  56. package/dist/core/tasks/runtime.js +50 -178
  57. package/dist/core/tasks/runtime.js.map +1 -1
  58. package/dist/core/tasks/scheduler.d.ts +3 -1
  59. package/dist/core/tasks/scheduler.d.ts.map +1 -1
  60. package/dist/core/tasks/scheduler.js +16 -12
  61. package/dist/core/tasks/scheduler.js.map +1 -1
  62. package/dist/core/tasks/session.d.ts +24 -6
  63. package/dist/core/tasks/session.d.ts.map +1 -1
  64. package/dist/core/tasks/session.js +70 -22
  65. package/dist/core/tasks/session.js.map +1 -1
  66. package/dist/core/tasks/store.d.ts +17 -4
  67. package/dist/core/tasks/store.d.ts.map +1 -1
  68. package/dist/core/tasks/store.js +45 -48
  69. package/dist/core/tasks/store.js.map +1 -1
  70. package/dist/core/tasks/types.d.ts +64 -93
  71. package/dist/core/tasks/types.d.ts.map +1 -1
  72. package/dist/core/tasks/types.js +7 -0
  73. package/dist/core/tasks/types.js.map +1 -1
  74. package/dist/core/tools/shell-execution.d.ts.map +1 -1
  75. package/dist/core/tools/shell-execution.js +2 -1
  76. package/dist/core/tools/shell-execution.js.map +1 -1
  77. package/dist/core/tools/shell-tool.d.ts.map +1 -1
  78. package/dist/core/tools/shell-tool.js +20 -13
  79. package/dist/core/tools/shell-tool.js.map +1 -1
  80. package/dist/extensions/agent/activity-lines.d.ts +10 -0
  81. package/dist/extensions/agent/activity-lines.d.ts.map +1 -0
  82. package/dist/extensions/agent/activity-lines.js +76 -0
  83. package/dist/extensions/agent/activity-lines.js.map +1 -0
  84. package/dist/extensions/agent/activity.d.ts +25 -4
  85. package/dist/extensions/agent/activity.d.ts.map +1 -1
  86. package/dist/extensions/agent/activity.js +76 -21
  87. package/dist/extensions/agent/activity.js.map +1 -1
  88. package/dist/extensions/agent/artifacts.d.ts +15 -0
  89. package/dist/extensions/agent/artifacts.d.ts.map +1 -0
  90. package/dist/extensions/agent/artifacts.js +44 -0
  91. package/dist/extensions/agent/artifacts.js.map +1 -0
  92. package/dist/extensions/agent/progress.d.ts +30 -4
  93. package/dist/extensions/agent/progress.d.ts.map +1 -1
  94. package/dist/extensions/agent/progress.js +84 -11
  95. package/dist/extensions/agent/progress.js.map +1 -1
  96. package/dist/extensions/agent/render.d.ts.map +1 -1
  97. package/dist/extensions/agent/render.js +8 -24
  98. package/dist/extensions/agent/render.js.map +1 -1
  99. package/dist/extensions/agent/report.d.ts +11 -3
  100. package/dist/extensions/agent/report.d.ts.map +1 -1
  101. package/dist/extensions/agent/report.js +32 -7
  102. package/dist/extensions/agent/report.js.map +1 -1
  103. package/dist/extensions/agent/tool.d.ts.map +1 -1
  104. package/dist/extensions/agent/tool.js +20 -5
  105. package/dist/extensions/agent/tool.js.map +1 -1
  106. package/dist/extensions/agent/types.d.ts +2 -0
  107. package/dist/extensions/agent/types.d.ts.map +1 -1
  108. package/dist/extensions/agent/types.js.map +1 -1
  109. package/dist/extensions/agent/worker.d.ts +7 -0
  110. package/dist/extensions/agent/worker.d.ts.map +1 -1
  111. package/dist/extensions/agent/worker.js +9 -3
  112. package/dist/extensions/agent/worker.js.map +1 -1
  113. package/dist/extensions/cached-compaction/summary.d.ts +1 -1
  114. package/dist/extensions/cached-compaction/summary.d.ts.map +1 -1
  115. package/dist/extensions/cached-compaction/summary.js +13 -16
  116. package/dist/extensions/cached-compaction/summary.js.map +1 -1
  117. package/dist/extensions/search/index.d.ts.map +1 -1
  118. package/dist/extensions/search/index.js +6 -7
  119. package/dist/extensions/search/index.js.map +1 -1
  120. package/dist/extensions/tasks/tool/actions.d.ts +1 -2
  121. package/dist/extensions/tasks/tool/actions.d.ts.map +1 -1
  122. package/dist/extensions/tasks/tool/actions.js +12 -80
  123. package/dist/extensions/tasks/tool/actions.js.map +1 -1
  124. package/dist/extensions/tasks/tool/constants.d.ts +1 -3
  125. package/dist/extensions/tasks/tool/constants.d.ts.map +1 -1
  126. package/dist/extensions/tasks/tool/constants.js +1 -3
  127. package/dist/extensions/tasks/tool/constants.js.map +1 -1
  128. package/dist/extensions/tasks/tool/schema.d.ts +2 -3
  129. package/dist/extensions/tasks/tool/schema.d.ts.map +1 -1
  130. package/dist/extensions/tasks/tool/schema.js +10 -18
  131. package/dist/extensions/tasks/tool/schema.js.map +1 -1
  132. package/dist/extensions/tasks/tool/types.d.ts +1 -17
  133. package/dist/extensions/tasks/tool/types.d.ts.map +1 -1
  134. package/dist/extensions/tasks/tool/types.js.map +1 -1
  135. package/dist/extensions/tasks/tool.d.ts +1 -2
  136. package/dist/extensions/tasks/tool.d.ts.map +1 -1
  137. package/dist/extensions/tasks/tool.js +5 -6
  138. package/dist/extensions/tasks/tool.js.map +1 -1
  139. package/dist/extensions/tasks/ui/completion-render.d.ts.map +1 -1
  140. package/dist/extensions/tasks/ui/completion-render.js +2 -2
  141. package/dist/extensions/tasks/ui/completion-render.js.map +1 -1
  142. package/dist/extensions/tasks/ui/footer.d.ts +37 -0
  143. package/dist/extensions/tasks/ui/footer.d.ts.map +1 -0
  144. package/dist/extensions/tasks/ui/footer.js +129 -0
  145. package/dist/extensions/tasks/ui/footer.js.map +1 -0
  146. package/dist/extensions/tasks/ui/frame.d.ts +36 -0
  147. package/dist/extensions/tasks/ui/frame.d.ts.map +1 -0
  148. package/dist/extensions/tasks/ui/frame.js +33 -0
  149. package/dist/extensions/tasks/ui/frame.js.map +1 -0
  150. package/dist/extensions/tasks/ui/help.d.ts +6 -0
  151. package/dist/extensions/tasks/ui/help.d.ts.map +1 -0
  152. package/dist/extensions/tasks/ui/help.js +53 -0
  153. package/dist/extensions/tasks/ui/help.js.map +1 -0
  154. package/dist/extensions/tasks/ui/inspector.d.ts +4 -3
  155. package/dist/extensions/tasks/ui/inspector.d.ts.map +1 -1
  156. package/dist/extensions/tasks/ui/inspector.js +25 -6
  157. package/dist/extensions/tasks/ui/inspector.js.map +1 -1
  158. package/dist/extensions/tasks/ui/layout.d.ts +15 -18
  159. package/dist/extensions/tasks/ui/layout.d.ts.map +1 -1
  160. package/dist/extensions/tasks/ui/layout.js +162 -255
  161. package/dist/extensions/tasks/ui/layout.js.map +1 -1
  162. package/dist/extensions/tasks/ui/list.d.ts +3 -1
  163. package/dist/extensions/tasks/ui/list.d.ts.map +1 -1
  164. package/dist/extensions/tasks/ui/list.js +19 -14
  165. package/dist/extensions/tasks/ui/list.js.map +1 -1
  166. package/dist/extensions/tasks/ui/manager.d.ts +17 -5
  167. package/dist/extensions/tasks/ui/manager.d.ts.map +1 -1
  168. package/dist/extensions/tasks/ui/manager.js +147 -90
  169. package/dist/extensions/tasks/ui/manager.js.map +1 -1
  170. package/dist/extensions/tasks/ui/model.d.ts +32 -5
  171. package/dist/extensions/tasks/ui/model.d.ts.map +1 -1
  172. package/dist/extensions/tasks/ui/model.js +7 -7
  173. package/dist/extensions/tasks/ui/model.js.map +1 -1
  174. package/dist/extensions/tasks/ui/panes.d.ts +40 -0
  175. package/dist/extensions/tasks/ui/panes.d.ts.map +1 -0
  176. package/dist/extensions/tasks/ui/panes.js +144 -0
  177. package/dist/extensions/tasks/ui/panes.js.map +1 -0
  178. package/dist/extensions/tasks/ui/render.d.ts +2 -16
  179. package/dist/extensions/tasks/ui/render.d.ts.map +1 -1
  180. package/dist/extensions/tasks/ui/render.js +13 -85
  181. package/dist/extensions/tasks/ui/render.js.map +1 -1
  182. package/dist/extensions/tasks/ui/viewport.d.ts +12 -4
  183. package/dist/extensions/tasks/ui/viewport.d.ts.map +1 -1
  184. package/dist/extensions/tasks/ui/viewport.js +23 -7
  185. package/dist/extensions/tasks/ui/viewport.js.map +1 -1
  186. package/dist/extensions/tasks/ui/views.d.ts +12 -6
  187. package/dist/extensions/tasks/ui/views.d.ts.map +1 -1
  188. package/dist/extensions/tasks/ui/views.js +39 -32
  189. package/dist/extensions/tasks/ui/views.js.map +1 -1
  190. package/dist/index.d.ts +1 -1
  191. package/dist/index.d.ts.map +1 -1
  192. package/dist/index.js.map +1 -1
  193. package/dist/modes/interactive/components/session-selector.d.ts +11 -1
  194. package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
  195. package/dist/modes/interactive/components/session-selector.js +43 -17
  196. package/dist/modes/interactive/components/session-selector.js.map +1 -1
  197. package/dist/modes/print-mode.d.ts.map +1 -1
  198. package/dist/modes/print-mode.js +38 -2
  199. package/dist/modes/print-mode.js.map +1 -1
  200. package/dist/modes/rpc/rpc-client.d.ts +11 -0
  201. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  202. package/dist/modes/rpc/rpc-client.js +48 -0
  203. package/dist/modes/rpc/rpc-client.js.map +1 -1
  204. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  205. package/dist/modes/rpc/rpc-mode.js +2 -0
  206. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  207. package/dist/modes/rpc/rpc-types.d.ts +2 -0
  208. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  209. package/dist/modes/rpc/rpc-types.js.map +1 -1
  210. package/docs/bundled/extensions/agent.md +39 -7
  211. package/docs/bundled/extensions/statusline.md +1 -1
  212. package/docs/bundled/tasks.md +28 -37
  213. package/docs/bundled/tool-presentation.md +1 -1
  214. package/docs/extensions.md +15 -13
  215. package/docs/json.md +3 -0
  216. package/docs/models-configuration.md +1 -1
  217. package/docs/models.md +7 -85
  218. package/docs/rpc.md +14 -0
  219. package/docs/sdk.md +12 -2
  220. package/docs/session-format.md +11 -5
  221. package/docs/sessions.md +1 -1
  222. package/docs/usage.md +2 -0
  223. package/package.json +3 -4
  224. package/dist/modes/interactive/tool-view/tool-execution.d.ts +0 -14
  225. package/dist/modes/interactive/tool-view/tool-execution.d.ts.map +0 -1
  226. package/dist/modes/interactive/tool-view/tool-execution.js +0 -13
  227. package/dist/modes/interactive/tool-view/tool-execution.js.map +0 -1
  228. package/docs/images/doom-extension.png +0 -0
  229. package/docs/images/exy.png +0 -0
  230. package/npm-shrinkwrap.json +0 -4207
@@ -10,7 +10,7 @@ Start Bash through the native `bash` tool (the optional native Windows `powershe
10
10
  { "command": "npm run build", "timeout": 120, "background": true }
11
11
  ```
12
12
 
13
- Omitting `background` keeps the normal foreground wait. A background submission returns an execution reference, not a successful final outcome. Up to eight background executions run at once; a ninth background submission is rejected without starting. Foreground commands are never limited by background work. `bg create` has been removed; its old stored results and background notifications show their saved text in transcripts.
13
+ Omitting `background` keeps the normal foreground wait. A background submission returns an execution reference, not a successful final outcome; an executor that already has its final result when it accepts answers the caller instead. A task is `background` only once its caller has been released: a submission, or a Ctrl+B request made while the executor is still preparing, waits for the executor to accept, and a task answered inline stays foreground, is saved as a foreground record, and never appears in `tasks list`. Up to eight background executions run at once, counting any still waiting for that acceptance; a ninth background submission is rejected without starting. Foreground commands are never limited by background work. `bg create` has been removed; its old stored results and background notifications show their saved text in transcripts.
14
14
 
15
15
  In interactive mode, **Ctrl+B** moves eligible foreground managed tasks to the background, oldest first, while background slots remain; when every slot is taken it says so instead. Once a foreground execution has run for ten seconds and can still move, the statusline shows `Ctrl+B to run in background`, even when no background tasks exist. The hint is hidden while every background slot is taken, background execution is disabled, or no eligible foreground execution remains. It uses the configured shortcut and disappears if that shortcut is disabled. The shortcut works even when `/tasks` owns focus. The same execution continues: no cancellation, restart or timeout reset. The handoff result names the path the work took, because the model cannot see it: a command submitted with `background: true` reports a handoff from an empty log, while one moved mid-run reports that its execution continued unchanged, that the log already holds output, and that a supplied timeout is still measured from command startup. It does not detach ordinary file tools, or user `!` shell commands.
16
16
 
@@ -18,6 +18,8 @@ When nothing is eligible, the key is not consumed: it falls through to other bin
18
18
 
19
19
  Configure `app.tasks.detach` in `keybindings.json` to change or disable the shortcut. The default `tui.editor.cursorLeft` is now `left`, freeing Ctrl+B. To restore Emacs cursor behavior, disable or rebind detach before assigning Ctrl+B to cursor-left.
20
20
 
21
+ Background work, submitted or detached, is admitted only while the `tasks` tool is active: without it the model could start work it cannot read or stop. If the bundled `builtin:tasks` extension is disabled or the tool is deactivated, `background: true` is rejected with a message saying that no work was started, the detach key and statusline hint disappear, and foreground execution is unaffected. Work admitted earlier keeps running and its completion is still delivered.
22
+
21
23
  Hosts can prohibit background execution with `backgroundAllowed: false`. This restriction survives runtime replacement and does not depend on task kind; foreground execution remains available.
22
24
 
23
25
  ## Management tool
@@ -27,31 +29,18 @@ Hosts can prohibit background execution with `backgroundAllowed: false`. This re
27
29
  | Action | Parameters | Behavior |
28
30
  |---|---|---|
29
31
  | `list` | — | Lists active and up to five retained finished records, at most 100 rows; foreground executions are omitted with a count (they deliver inline in the transcript) |
30
- | `read` | `taskId`, optional `mode`, `bytes` | Reads bounded model-readable output/report while running or after completion; running Agents return a one-line status without Tool calls |
31
- | `wait` | optional `taskId`, `waitMs`, `sinceBytes` | Waits for one task, or for any task running at call time when `taskId` is omitted |
32
+ | `read` | `taskId`, optional `mode`, `bytes`, `sinceBytes` | Reads bounded model-readable output/report while running or after completion; running Agents return a one-line status without Tool calls |
32
33
  | `kill` | `taskId` | Requests cancellation of the selected task |
33
34
 
34
- Use the execution ID returned by the native tool or `tasks list`. An unknown or stale `taskId` fails with the session's current tasks listed inline (active first, then recent finishes), so the next call can use a real ID without a separate list call; an ambiguous prefix lists exactly the tasks it matched. Only `wait` makes `taskId` optional: omitting it waits for the first terminal result among background tasks that were running when the call started, and returns `no_tasks` immediately when that cohort is empty. Tasks started during the wait are not part of the cohort.
35
-
36
- ```json
37
- { "action": "read", "taskId": "<execution-id>", "mode": "tail", "bytes": 8192 }
38
- ```
39
-
40
- Reads default to an 8KB tail. `bytes` clamps between 256 bytes and 48KB; `mode` can be `head` or `tail`. A Bash output path is included when available. Tasks without a log return their bounded published result. Executors can separately attach human-only display text: `/tasks` shows it, while `tasks read`, settled waits, and completion context exclude it. Agent Tool calls use this display channel, so no read-view parameter is needed. Every read and settled wait reports its byte range and where to continue, for example `bytes 92160–100352 of 100352 · next sinceBytes 100352`. Use a returned offset with that task's ID; waitAny does not accept a cross-task offset.
35
+ There is no `wait` action. A background completion is delivered to the model automatically and starts a new turn if the previous one has ended, so the model continues independent work or ends its turn instead of blocking or polling.
41
36
 
42
- ```json
43
- { "action": "wait", "taskId": "<execution-id>", "waitMs": 20000, "sinceBytes": 4096 }
44
- ```
45
-
46
- Wait for any task already running at call time by omitting the ID:
37
+ The tool is the only way the model can follow background work, which is why background needs it (see [Start or detach work](#start-or-detach-work)). Use the execution ID returned by the native tool or `tasks list`. An unknown or stale `taskId` fails with the session's current tasks listed inline (active first, then recent finishes), so the next call can use a real ID without a separate list call; an ambiguous prefix lists exactly the tasks it matched.
47
38
 
48
39
  ```json
49
- { "action": "wait", "waitMs": 20000 }
40
+ { "action": "read", "taskId": "<execution-id>", "bytes": 8192 }
50
41
  ```
51
42
 
52
- `waitMs` defaults to 20 seconds and clamps between 1 second and 10 minutes. A task that reaches a terminal state produces a `settled` outcome; expiry produces `timed_out` and leaves execution running. New steering input produces `interrupted` and ends only the wait, so the new input can proceed without cancelling the task; follow-up input does not interrupt it. A background completion steered into the run can interrupt a wait on another task the same way.
53
-
54
- Wait output is limited to 32KB. With `taskId`, it can start after `sinceBytes`; when more than 32KB was written after that offset, the wait returns the newest 32KB and reports how many bytes it skipped. `sinceBytes` is rejected without `taskId` because offsets belong to one task. Interrupted waits do not read output. A settled waitAny reads the winning task and lists initial cohort members still running. Continue independent work instead of sleep-polling or repeatedly reading; wait when the next step genuinely depends on a result.
43
+ Reads default to 8KB. Without `mode`, a report such as an Agent's is read from its start and a log such as a command's from its end; `mode` can be `head` or `tail` and always wins. `details.mode`, and with it the Transcript row, names the end actually read. `bytes` clamps between 256 bytes and 48KB. A Bash output path is included when available. Tasks without a log return their bounded published result. Executors can separately attach a human-only display: `/tasks` shows it in place of the result, while `tasks read` and completion context always use the result. Only a completion's own display survives settlement. Agent Tool calls use this display channel, so no read-view parameter is needed. Every read reports its byte range and where to continue, for example `bytes 92160–100352 of 100352 · next sinceBytes 100352`. Use a returned offset with that task's ID: a head read continues forward from it, while a tail read keeps the newest unread window. Offsets count raw source bytes, so undecodable UTF-8 cannot change the continuation position.
55
44
 
56
45
  ```json
57
46
  { "action": "kill", "taskId": "<execution-id>" }
@@ -59,19 +48,21 @@ Wait output is limited to 32KB. With `taskId`, it can start after `sinceBytes`;
59
48
 
60
49
  A cancellation request is not proof that execution has stopped. The task can remain `stopping` during cleanup. Read its later terminal status for the outcome. Partial, failed, cancelled and timeout outcomes are not displayed as successful completion.
61
50
 
62
- All model-facing management responses, including listings and error messages, are bounded to 50KB and 2,000 lines overall. Reads and settled waits report byte ranges and the next `sinceBytes`; waitAny accepts no cross-task input offset, so use the winning task's ID with its returned offset. Repeated reads do not add usage or restart execution. Completion delivery and usage accounting belong to the host, not panel refreshes.
51
+ All model-facing management responses, including listings and error messages, are bounded to 50KB and 2,000 lines overall. Repeated reads do not add usage or restart execution. Completion delivery and usage accounting belong to the host, not panel refreshes.
63
52
 
64
53
  ## `/tasks` panel
65
54
 
66
- The fullscreen panel has one scrollable list. **Active** groups queued, running and stopping work; **Finished** groups all retained results on the selected branch, including foreground shell results, newest first. Empty groups are hidden. Each task is one row: its status glyph, its source label such as Bash or Agent in an aligned dim column, the title, and right-aligned metadata. The metadata shows elapsed time, the status name for every status except running, completed and failed, whose glyphs are unambiguous, and an ID suffix when two titles are identical. Foreground/background mode is available in Details. Durations omit zero components and separate remaining units (`8s`, `1m`, `1m 5s`, `1h 5m`). The background action is offered only when the selected task can move.
55
+ The fullscreen panel has one scrollable list. **Active** groups queued, running and stopping work; **Finished** groups all retained results on the selected branch, including foreground shell results, newest first. Empty groups are hidden. Each task is one row: its status glyph, its source label such as Bash or Agent in an aligned dim column, the title, and right-aligned metadata. The metadata shows elapsed time, the status name for every status except running, completed and failed, whose glyphs are unambiguous, and an ID suffix when two titles are identical. The list's top border names the panel and counts what is shown (`Tasks · 2 active · 5 finished`); its bottom border shows the visible range (`1-12 of 40`) only when the list overflows. While a stop question is open, the target row's metadata reads `stop?` in the warning color. Durations omit zero components and separate remaining units (`8s`, `1m`, `1m 5s`, `1h 5m`). The background action is offered only when the selected task can move.
67
56
 
68
57
  Selection follows the task when it finishes; new work does not steal focus or replace browsed output. The list preserves the selected row's screen position where space permits. Reopening selects the newest task started since the panel last closed; when nothing new started, it keeps the remembered selection. The right-hand tab is remembered as well, while the runtime remains available. Runtime replacement resets these preferences, and branch navigation removes results outside the visible branch. History remains bounded by the retention limits below.
69
58
 
70
- At 100 columns or more, the task list sits beside an inspector. The inspector's first row names the selected task — `Bash · Running · 12s` — with the **Output** and **Details** tabs at its right. **Output** shows the result directly; **Details** starts with the source, task ID and status, then holds what the executor declared — its input such as the full command or prompt, and facts such as the directory, model or exit code — plus the log location. Each task and tab keeps its reading position for the lifetime of the panel. Tab switches between the two panes, preserving the right-hand tab. Left/Right switch Output/Details only when the inspector has focus.
59
+ At 100 columns or more, the task list sits beside an inspector; the list takes 40% of the width, between 34 and 60 columns. Each pane's title is in its top border: the list's `Tasks`, and the inspector's **Output** and **Details** tabs. The inspector's first row is the selected task's title in bold and its second is a meta line, `Bash · Running · Background · 12s`, with the status in its outcome color; a rule joined to the pane's border separates this header from the tab's content. **Output** shows the result directly; **Details** starts with the task ID, then holds what the executor declared — its input such as the full command or prompt (inline when it is one line, as a block when it spans several), facts such as the directory, model or exit code — and the log location. Long values wrap inside their own column, with continuation lines hanging under the value, and unbroken tokens such as paths break at the column width. Each task and tab keeps its reading position for the lifetime of the panel. Tab switches between the two panes, preserving the right-hand tab. Left/Right switch Output/Details only when the inspector has focus.
60
+
61
+ The focused pane has a highlighted border. Task selection uses a persistent row background, and the selected right-hand tab uses an underline; neither changes when focus moves. When the system theme has no background colors, the selected row uses inverse text. There are no focus arrows or extra title colors. Narrow terminals show one pane at a time at full width, with the same Tab navigation, no focus highlight, and the title and meta rows in the inspector. In a narrow terminal the inspector is a drill-down page: Escape returns to the list, and Escape in the list closes the panel; in the wide layout Escape closes the panel from either pane. The footer names the action as `Esc back` or `Esc close`. The minimum supported size is 60 columns by 14 rows; below it, a centered dialog asks for a larger window and covers the screen, because the panel keeps input focus while open.
71
62
 
72
- The focused pane has a highlighted border. Task selection uses a persistent row background, and the selected right-hand tab uses an underline; neither changes when focus moves. When the system theme has no background colors, the selected row uses inverse text. There are no focus arrows or extra title colors. Narrow terminals show one pane at full width with the same Tab navigation and no focus highlight. The inspector includes task context when the list is hidden. Escape closes the panel from either pane. The minimum supported size is 60 columns by 14 rows; below it, a centered dialog asks for a larger window and covers the screen, because the panel keeps input focus while open.
63
+ The footer shows context hints on the left and pins the Escape action to the right edge. When the row is too narrow, the lowest-priority hints go first: pane and tab switching, then the arrow hint, open, find, stop and background, with `? keys` kept longest. A message appears there only when an action did not do what was asked, as a warning (`!`) for a no-op or refusal such as a reached background limit or a task that already finished, or as an error (`×`) for an unexpected failure. It replaces the hints and clears on the next key or click, or after about four seconds. A successful stop or background request shows no message: the row and the header already read `Stopping` or the new mode.
73
64
 
74
- Every executor is presented the same way, from data it declares: a label such as Bash or Agent, an input, labelled facts, and whether its output is a log or a report. Logs show live plain-text output; other executors show their published result plus any human-only display suffix, updated as soon as the executor publishes rather than on the log poll. A completed report, such as an Agent's, uses the native Markdown renderer while its retained activity remains literal text. Agent Output combines its status line or report with compact Tool calls, written as in the transcript: `✓ read README.md · 4ms`. A task without an input shows its title in Details. Missing logs fall back to the saved result. Task status, failure reasons and output-read errors remain distinct.
65
+ Every executor is presented the same way, from data it declares: a label such as Bash or Agent, an input, labelled facts, and whether its output is a log or a report. Only a log streams from its file, with a reading state (following, browsing or finished) and a follow control. Any other output shows what its executor published, updated as soon as it publishes rather than on the log poll: while the task runs, the lines its executor renders for the panel (styled and fitted to the pane, such as a running Agent's colored tool calls with an elapsed time), otherwise its human-readable display when it has one, otherwise the result that models read. A report is never read from an attached file, so a report saved in full still shows its published beginning, with the file listed in Details as **Full report**. A completed report uses the native Markdown renderer. Output follows the tail of a log and of a live report; a settled report opens at its first line, and keeps the position you scrolled to. When a report overflows, its scroll range appears in the pane's bottom border. A running Agent shows its compact tool calls, written as in the transcript (`✓ read README.md · 4ms`), and a finished one shows its final report. A task without an input shows its title in Details. Missing logs fall back to the saved result. Task status, failure reasons and output-read errors remain distinct.
75
66
 
76
67
  | Default key | Action |
77
68
  |---|---|
@@ -79,35 +70,35 @@ Every executor is presented the same way, from data it declares: a label such as
79
70
  | Left / Right | Output / Details, only while the inspector has focus |
80
71
  | Tab / Shift+Tab | Switch between list and inspector, preserving the current tab |
81
72
  | Up / Down | Select a task or scroll the focused region |
82
- | Enter | Open the selected task: focus the inspector, or show it full width in a narrow terminal |
73
+ | Enter | Open the selected task: focus the inspector, which fills the width in a narrow terminal |
83
74
  | Page Up / Page Down | Page the focused list or region |
84
75
  | Home / End | Top / bottom of the list or bounded preview |
85
76
  | `f` | Follow the latest output, or load the final output after completion, while Output has focus |
86
77
  | `b` | Move only the selected eligible foreground task to the background |
87
- | `k`, then Enter | Request cancellation of the selected whole task. The footer asks `Stop Bash "npm test"?` while the list and inspector stay visible; Escape cancels. Confirmation has no time limit and closes if its target finishes |
78
+ | `k`, then Enter | Request cancellation of the selected whole task. The footer asks `Stop Bash "npm test"?` while the list and inspector stay visible and the target row reads `stop?`; Escape cancels. Confirmation has no time limit and closes if its target finishes |
88
79
  | `?` | Show configurable controls, grouped into Navigate, View and Act; Up/Down scroll the help |
89
- | Escape | Close the current search, help or confirmation first; otherwise close the panel |
80
+ | Escape | Close the current search, help or confirmation first; then, in a narrow terminal, return from the inspector to the list; otherwise close the panel |
90
81
  | Ctrl+B | Detach **all** eligible foreground executions through the host |
91
82
 
92
- Click a pane to focus it, a task row to select it, or a tab label to display it. The mouse wheel scrolls the pane under the pointer without changing keyboard focus or task selection. Footer hints are clickable as well, including while transient feedback owns that row: the click dismisses it and performs the hint's action. While a stop question owns the footer, only its stop and cancel actions respond. Search, help and confirmation consume their own input without activating the panel behind them.
83
+ Click a pane to focus it, a task row to select it, or a tab label to display it. The mouse wheel scrolls the pane under the pointer without changing keyboard focus or task selection. Footer hints and the Escape action are clickable as well; a click while a message is showing dismisses it. While a stop question owns the footer, only its stop and cancel actions respond. Each mode answers only to its own targets: search to its candidates and cancel, help to its footer, so a click never activates the panel behind them.
93
84
 
94
- Shell output initially follows the tail, marked **Live** next to the output. Scrolling up enters **Browsing**, which holds a bounded output snapshot while status continues updating. In Output, press `f`, End, or scroll down to the bottom to resume following. If the task finishes while browsing, the panel offers to load its final output. Closing or scrolling never pauses, restarts or cancels execution.
85
+ Shell output initially follows the tail; the inspector's bottom border shows **Live**. Scrolling up enters **Browsing · f follow**, which holds a bounded output snapshot while status continues updating, and the label itself is clickable. In Output, press `f`, End, or scroll down to the bottom to resume following. If the task finishes while browsing, the border offers `Finished · f load final output`. Only a log has this reading state; a published result is always the current one. The Details bottom border shows its scroll range only when Details overflow. Closing or scrolling never pauses, restarts or cancels execution.
95
86
 
96
- The panel polls only selected tail output once per second, up to 48 KiB per read; navigation and settlement can request an immediate refresh. Progress updates and animation do not add log reads. A truncated tail shows its preview and total byte counts; ordinary complete output omits these statistics. Empty output and expired logs have explicit messages; an unavailable log falls back to the saved result when available. Selecting a task retains its result and log without delaying completion notifications. Closing releases subscriptions, the retention lease and timers.
87
+ The panel polls only the selected attached log's tail once per second, up to 48 KiB per read; navigation and settlement can request an immediate refresh. Progress updates and animation do not add log reads. A truncated tail shows its preview and total byte counts; ordinary complete output omits these statistics. Empty output and expired logs have explicit messages; an unavailable log falls back to the saved result when available. Selecting a task retains its result and log without delaying completion notifications. Closing releases subscriptions, the retention lease and timers.
97
88
 
98
89
  Controls use the `app.tasks.*` bindings: `search`, `previousTab`, `nextTab`, `nextFocus`, `previousFocus`, `top`, `bottom`, `follow`, `kill`, `confirmStop`, `detachSelected`, `detach` and `help`. List paging uses `tui.select.pageUp` / `pageDown`; Details and Output paging use `tui.editor.pageUp` / `pageDown`. All are configurable. The former view and direct-focus bindings have been removed.
99
90
 
100
91
  The `/hotkeys` table lists the task bindings next to the editor ones, and the panel's `?` help lists every control, so both detach scopes are discoverable without reading these docs.
101
92
 
102
- The statusline counts only queued, running and stopping background tasks: `1 background task · /tasks to view` or `2 background tasks · /tasks to view`. Foreground tasks and retained results do not contribute to the count; the summary disappears when no active background tasks remain. Finished results remain available in `/tasks`. The independent detach hint can appear alone or after the summary: `1 background task · /tasks to view · Ctrl+B to run in background`. The count uses normal text, `/tasks` and the shortcut use the accent color, and descriptions and separators are muted. The model-facing `tasks list` keeps its background-only scope and foreground omission count.
93
+ The statusline counts only queued, running and stopping background tasks: `1 background task · /tasks to view` or `2 background tasks · /tasks to view`. Foreground tasks and retained results do not contribute to the count; the summary disappears when no active background tasks remain. Finished results remain available in `/tasks`. The independent detach hint can appear alone or after the summary: `1 background task · /tasks to view · Ctrl+B to run in background`. The count uses normal text, `/tasks` and the shortcut use the accent color, and descriptions and separators are muted. The model-facing `tasks list` keeps its background-only scope and foreground omission count. A task whose detach is waiting for its executor to accept still shows as foreground, and is not counted, until the executor accepts.
103
94
 
104
95
  ## Completion notifications
105
96
 
106
97
  Interactive `task-completion` messages have a compact collapsed summary: the executor's label, outcome, duration, short ID, and the task title. Failures retain a short reason; log paths, output and worker reports stay out of the collapsed view. In fullscreen mode, left-click the notification to expand or collapse that message independently. The blank spacer above it is not a click target. The configured tool-output expansion binding (default Ctrl+O) remains available for toggling output expansion across the transcript.
107
98
 
108
- Expanded notifications separate the input under its own label, such as **Command** or **Prompt** (or **Task** without one), **Details** with the declared facts such as directory and exit code, **Result** or **Error**, a bounded plain-text **Output** preview, **Log** and the full execution ID. A log's saved output keeps its end, where the outcome is; a report's keeps its beginning, where the conclusion is. Both carry an explicit truncation flag. Screen line limits are independent of source truncation. These are previews of the saved bounded completion.
99
+ Expanded notifications separate the input under its own label, such as **Command** or **Prompt** (or **Task** without one), **Details** with the declared facts such as directory and exit code, **Result** or **Error**, a bounded plain-text **Output** preview, **Log** and the full execution ID. A log's saved output keeps its end, where the outcome is; a report's keeps its beginning, where the conclusion is. Both carry an explicit truncation flag. Screen line limits are independent of source truncation. The card ends with the task ID; it carries no separate closing line. A failed shell task's card states its error once: the output holds only what the command printed.
109
100
 
110
- Rendering reads the self-contained terminal snapshot in message `details`, so `/reload`, history eviction, expired logs and restart do not require a live task lookup. The session generates model-facing `content` from the same facts; `details` never enters model context or usage accounting. Replay details retain up to 40 KiB of output. The automatic model-facing notification includes only an 8 KiB tail of a log, plus the log path and truncation guidance for `tasks read`; a report arrives from its beginning, whole when it fits the 48 KiB budget, as its foreground result would. Commands containing `Output:` and literal truncation notices remain ordinary data. See the [snapshot contract](../session-format.md#background-records).
101
+ Rendering reads the self-contained terminal snapshot in message `details`, so `/reload`, history eviction, expired logs and restart do not require a live task lookup. The session generates model-facing `content` from the same facts; `details` never enters model context or usage accounting. Replay details retain up to 40 KiB of output. A shell task's result holds the command's output only; its failure is the separate error, which the notification states once on its `Error:` line instead of repeating it at the end of the output. The automatic model-facing notification includes only an 8 KiB tail of a log, plus the log path and truncation guidance for `tasks read`; a report arrives from its beginning, whole when it fits the 48 KiB budget, as its foreground result would. Commands containing `Output:` and literal truncation notices remain ordinary data. See the [snapshot contract](../session-format.md#background-records).
111
102
 
112
103
  Only the current structured completion format (version 2) is specialized. Older completions, including version-1 shell and report completions, and malformed details receive a bounded plain **Details** view of their saved message; no text reconstruction or migration is performed. Legacy `background-task` notifications show as plain messages. HTML export and `/tree` selector labels continue to display the saved message content.
113
104
 
@@ -117,7 +108,7 @@ Background execution belongs to the current session runtime, not a daemon. Paren
117
108
 
118
109
  The panel is an observer, not a cleanup engine. Admission, bounded history/output retention, completion delivery and headless exit policy are enforced by the core service and hosting mode.
119
110
 
120
- Interactive mode enables Background. Built-in print, JSON, and RPC modes and ordinary SDK sessions leave it disabled and reject `background: true`; normal foreground execution remains available. An SDK embedding can explicitly enable it via `session.bindExtensions({ tasksEnabled: true })`, but must own cancellation, bounded draining, result-driven turns, and shutdown. See [SDK Background execution](../sdk.md#background-execution).
111
+ Interactive mode and built-in print, JSON, and RPC modes enable Background. Ordinary SDK sessions leave it disabled and reject `background: true`; normal foreground execution remains available. An SDK embedding can explicitly enable it via `session.bindExtensions({ tasksEnabled: true })`, but must own cancellation, bounded draining, result-driven turns, and shutdown. See [SDK Background execution](../sdk.md#background-execution).
121
112
 
122
113
  Managed shell output is collected continuously from startup, including before detach. Background shell output is capped at 20 MiB; crossing the cap fails and stops the command. Foreground output retains its existing uncapped log behavior: detaching a command already over the background budget stops it without deleting the prior bytes. No timeout is supplied by default, and a supplied timeout remains measured from command startup, in seconds, across detach.
123
114
 
@@ -125,9 +116,9 @@ Managed logs are ephemeral: they are retained with the runtime record and cleane
125
116
 
126
117
  Each settlement appends one version-3 `task-record` custom entry holding the terminal snapshot and its usage. A foreground snapshot omits its output, which the transcript already saves in the tool result, and restoration reads it back from there. The runtime restores version-3 terminal history from the selected branch for programmatic reads and the panel's Finished group, selecting the newest records independently for each history. Older `task-result` records are left in the session file and are not migrated or restored; usage from released sessions still counts. Live execution never resumes after restart, and restoration does not replay accounting or completion events. Restored records drop their log path: logs never outlive the runtime that wrote them. See [session format](../session-format.md#background-records).
127
118
 
128
- Delivered history outside the selected branch can be released and restored on return, so it does not fill the new branch's history budget. Undelivered completions stay protected while their branch is hidden. Pending delivery, pins and active reads use a separate allowance within the total runtime retention cap; restoration respects the same cap.
119
+ Settled history outside the selected branch can be released and restored on return, so it does not fill the new branch's history budget. An undelivered completion is a retention lease held by the session's delivery: its record and output stay protected, even while its branch is hidden, until the model has the completion, a direct SDK consumer acknowledges it, or its runtime closes or is replaced; completions still pending when a runtime is replaced are not delivered by its successor. A bare runtime outside a session holds no such lease, so its settled records are ordinary history. Leases, pins and active reads use a separate allowance within the total runtime retention cap; restoration respects the same cap.
129
120
 
130
- Completion delivery follows the same queueing path as interactive input. While the agent is running, a completion is steered into that run, so it lands right after the current tool batch — exactly like a message the user types mid-run, and behind any already queued steering. An idle session starts a completion turn instead. User preflight (input hooks, model and authentication checks, compaction) still holds delivery, so a user message is always the first thing a new run sees. One bounded completion is in flight at a time. Each execution produces one completion. A terminal `tasks wait` coordinates with automatic delivery only after its tool result is persisted: while `tasks wait` prepares a result for persistence, that task is not announced, and aborting during output reading does not lose the pending completion. A settled waitAny applies the same receipt to its winning task and releases the rest of the cohort for ordinary delivery. Direct SDK waits remain observational until explicitly acknowledged. Plain reads and panel selection retain output without delaying notifications. Progress and repeated reads do not inject messages or add usage. `agent_settled` still describes the main agent, not the end of all background executions.
121
+ Completion delivery follows the same queueing path as interactive input. While the agent is running, a completion is steered into that run, so it lands right after the current tool batch — exactly like a message the user types mid-run, and behind any already queued steering. An idle session starts a completion turn instead. User preflight (input hooks, model and authentication checks, compaction) still holds delivery, so a user message is always the first thing a new run sees. One bounded completion is in flight at a time. Each execution produces one completion. A direct SDK consumer that handles a result outside model history acknowledges it with `session.acknowledgeTaskDelivery(id)`. Plain reads and panel selection retain output without delaying notifications. Progress and repeated reads do not inject messages or add usage. `agent_settled` still describes the main agent, not the end of all background executions; `session_quiescent` and `session.waitForQuiescence()` mark the point where no task is unsettled and no completion turn remains. Print and JSON mode wait for that point before exiting.
131
122
 
132
123
  After a successful compaction, the next main-agent request includes one bounded, request-local reminder listing background tasks still active at that moment. It warns against duplicate work without creating a turn or entering session history. A successful assistant response consumes it; failed or aborted requests leave it eligible for the next attempt. If no tasks are active, the reminder is cleared instead of being carried forward to unrelated future work.
133
124
 
@@ -135,6 +126,6 @@ Failed completion delivery leaves the terminal result available for inspection r
135
126
 
136
127
  Queued extension `nextTurn` context accompanies completion turns and stays queued until each message is persisted. A failed or partially persisted turn therefore retries only the context that has not been saved.
137
128
 
138
- If an executor ignores cancellation and settles after bounded cleanup has retired its runtime or branch, the old session quarantines its bounded result and reported usage: the latest 32 records remain in memory, and persisted sessions also append `<session-file>.tasks-late.jsonl`. These audit records are excluded from active totals and are not automatically reconciled. A completed cleanup grace period is not proof that an uncooperative executor stopped.
129
+ If an executor ignores cancellation and settles after bounded cleanup has retired its runtime or branch, the old session quarantines its bounded result and reported usage: the latest 32 records remain in memory, and persisted sessions also append `<session-file>.tasks-late.jsonl`, which `/resume` deletion removes with the session. These audit records are excluded from active totals and are not automatically reconciled. A completed cleanup grace period is not proof that an uncooperative executor stopped.
139
130
 
140
131
  The previous extension-owned interactive-prompt stall watchdog was not ported. Pi no longer automatically flags a prompt-looking shell tail as `waiting for input` or sends stall remediation notifications. Use non-interactive commands and inspect/stop stalled work manually; legacy stall notifications show as plain saved messages.
@@ -39,7 +39,7 @@ The `Ctrl+B` background hint is not part of a tool row. Once a foreground manage
39
39
 
40
40
  The look is defined in one place, `src/modes/interactive/tool-view/style.ts`: the marker and rail glyphs, their colors, the spacing between blocks, and which tools fold their result. `FramedComponent` in the same file hangs a component's lines off the marker or the rail and derives the gutter width from the glyphs.
41
41
 
42
- The upstream `components/tool-execution.ts` owns one call's lifecycle: pending, success and error states, expansion, image placement and Kitty conversion, and renderer disposal. The bundled `tool-presentation` extension attaches a TUI-only shell through the existing tool renderer resolver; `tool-view/shell.ts` maps the visual policy to that shell. The old `tool-view/tool-execution.ts` path is only a compatibility adapter for direct source imports, not a second lifecycle implementation.
42
+ The upstream `components/tool-execution.ts` owns one call's lifecycle: pending, success and error states, expansion, image placement and Kitty conversion, and renderer disposal. The bundled `tool-presentation` extension attaches a TUI-only shell through the existing tool renderer resolver; `tool-view/shell.ts` maps the visual policy to that shell. Direct constructors can attach the same shell with `withToolShell()` from `tool-view/shell.ts`; there is no second lifecycle implementation.
43
43
 
44
44
  `tool-view/chat.ts` owns the chat container that measures tool blocks for spacing and disposes rows when the chat clears. It reads each row's gap policy: upstream rows keep one blank line, while the distribution shell uses the compact policy above. Extension tools and built-in tools use the same shell unless they explicitly own a self shell.
45
45
 
@@ -1257,14 +1257,13 @@ Session-bound `TasksContext` supervises native and extension-owned execution. Fo
1257
1257
 
1258
1258
  | Method | Contract |
1259
1259
  |---|---|
1260
- | `execute<T>(execution)` | Returns `{ kind: "result", result, status, error? }` or `{ kind: "background", task }` |
1260
+ | `execute<T>(execution)` | Returns `{ kind: "result", result, status, error? }` or `{ kind: "background", task, reason }`, where `reason` is `"submitted"` when the caller asked for `background` and `"detached"` when it was moved while the work ran |
1261
1261
  | `list()` / `get(id)` | Bounded, read-only (frozen) retained snapshots; IDs support unique prefixes |
1262
- | `read(id, options?)` | Bounded head, tail, or byte-offset model-readable output; excludes human-only display text |
1263
- | `wait(id, options?)` | One observation of one task. `options`: `timeoutMs`, `signal`, `toolCallId`, and `read: { bytes?, sinceBytes? }`. Returns `{ outcome, waitedMs, task, read? }` where `outcome` is `settled`, `timed_out`, `interrupted` (host steering), or `closed` (host shut down; state unconfirmed). Timeout, cancellation, or interruption ends the wait, not execution |
1264
- | `waitAny(options?)` | Observes the first task running at call time to settle, with `timeoutMs`, `signal`, `toolCallId`, and `read: { bytes? }`. Returns `{ outcome, waitedMs, task?, read?, running }`; `outcome` adds `no_tasks` for an empty cohort. Later tasks are excluded |
1262
+ | `presentOutput(id, width, theme)` | The running task's live Output lines from its executor's `present` (below), or `undefined` for an unknown, settled or presenter-less task, or when the presenter throws. Hosts that render task Output, such as `/tasks`, call it on every repaint and use the default view when it returns nothing |
1263
+ | `read(id, options?)` | Bounded head, tail, or byte-offset model-readable output; excludes human-only display text. `options`: `mode`, `bytes`, `sinceBytes`. Without `mode`, a `report` is read from its start and a `log` from its end. With `sinceBytes`, `head` continues forward from that offset and `tail` keeps the newest window at or after it. Returned ranges and `endByte` use raw source bytes |
1265
1264
  | `kill(id)` | Requests cancellation; `stopping` is not a terminal status |
1266
- | `detach(id)` / `detachForeground()` | Hands off one task, or eligible foreground tasks oldest first while background slots remain, without restarting them; throws when every background slot is taken |
1267
- | `canDetach(id)` | Reports whether an execution can move now, including host policy and available background slots; does not change the execution |
1265
+ | `detach(id)` / `detachForeground()` | Hands off one task, or eligible foreground tasks oldest first while background slots remain, without restarting them; throws when every background slot is taken. Returns `false` / `0` while the host cannot supervise background work (see below) |
1266
+ | `canDetach(id)` | Reports whether an execution can move now, including host policy, the `tasks` tool being active, and available background slots; does not change the execution |
1268
1267
  | `subscribe(listener)` | Observes task state and progress |
1269
1268
  | `retain(id)` | Returns an idempotent release function protecting retained data, without delaying notifications |
1270
1269
 
@@ -1273,21 +1272,24 @@ Session-bound `TasksContext` supervises native and extension-owned execution. Fo
1273
1272
  | Field | Contract |
1274
1273
  |---|---|
1275
1274
  | `label` | Human source name for the panel and completion card, such as `Bash` or `Agent`; defaults to `kind` |
1276
- | `output` | `"log"` (default) or `"report"`. A log's outcome is at its end, so a background completion gives the model an 8 KiB tail. A report leads with its conclusion, so it arrives from its beginning, whole when it fits the 48 KiB context budget, as its foreground result would; a completed report renders as Markdown in `/tasks` |
1275
+ | `output` | `"log"` (default) or `"report"`. A log's outcome is at its end, so a background completion gives the model an 8 KiB tail, and `read()` returns its end unless `mode` says otherwise. A report leads with its conclusion, so it arrives from its beginning, whole when it fits the 48 KiB context budget, as its foreground result would, and `read()` returns its start unless `mode` says otherwise; a completed report renders as Markdown in `/tasks` |
1277
1276
  | `input` | `{ label, text, truncated, language? }`: what the task was asked to do, such as a command or briefing. Shown in Details and the expanded completion card, highlighted when `language` is set |
1278
1277
  | `facts` | Up to 16 labelled `[label, value]` Details rows, such as `["Directory", cwd]` |
1279
- | `concurrency` | `{ key, limit }`: at most `limit` executions sharing `key` run at once. Later ones wait as `queued` tasks and `run` starts when a slot frees |
1278
+ | `concurrency` | `{ key, limit }`: at most `limit` executions sharing `key` run at once. Lanes belong to one `kind`, so another executor can reuse the key. Later ones wait as `queued` tasks and `run` starts when a slot frees |
1280
1279
  | `background`, `signal`, `onUpdate` | Requested handoff, parent cancellation, and foreground progress forwarding |
1280
+ | `present` | `(width, theme)` returning `string[]` or `undefined`: renders the running task's Output in `/tasks` with its own styling, such as colored columns or an elapsed time. Return lines already fitted to `width` (the panel clips any that are wider), or `undefined` for the default view. It runs synchronously on every repaint, so keep it cheap, and it must not return untrusted text without removing terminal sequences. It exists in memory for this execution only: never persisted, not part of any `TaskSnapshot`, dropped at settlement and absent after a restore. A throwing presenter is ignored. `display` stays the plain fallback for other consumers |
1281
1281
 
1282
1282
  `kind` is an executor-owned source label matching `[a-z][a-z0-9-]{0,63}`; the runtime never dispatches on it. Presentation is declared data: the panel and completion card render `label`, `input`, `facts`, and `output` the same way for every executor. Input and facts restore with terminal task history; facts also appear in the completion notification. At most eight background executions, queued ones included, are retained as active at once; foreground executions are not limited by them. Foreground and background results have separate history quotas.
1283
1283
 
1284
- A caller can be handed off to the background while its task is queued, before `run` starts, or after `run` calls `accept()`. A queued task becomes `running` when its `run` starts; cancelling it settles it as `cancelled` without starting it.
1284
+ A caller can be handed off to the background while its task is queued, before `run` starts, or after `run` calls `accept()`. A release requested while `run` is still preparing (a `background: true` submission, or a user detach) waits for `accept()`; if `run` settles first, the caller gets the result inline and the task stays `foreground`. `mode` is therefore `background` exactly when the caller was released with a background outcome. A queued task becomes `running` when its `run` starts; cancelling it settles it as `cancelled` without starting it.
1285
1285
 
1286
- `run` returns `TaskCompletion<T>`: `{ result, status?, display?, error?, facts?, usage? }`. The original tool result is returned to a foreground caller; task history keeps only its bounded text. Optional `display` is bounded human-only Output suffix text, excluded from `tasks read`, settled waits, and completion context. Terminal `facts` replace launch facts with the same label, as the shell does with `["Exit", "42"]`. Explicit terminal statuses are `completed`, `partial`, `failed`, `cancelled`, and `timeout`. The host settles usage once, preferring `usage`, then `result.usage`, then the latest published usage; managed foreground results omit usage to avoid billing it twice.
1286
+ Background work is admitted, submitted or detached, only while the host can supervise it: the session's `tasks` management tool must be active, because the model could otherwise start work it cannot read or stop. A rejected submission starts nothing and its error says so; work admitted earlier keeps running and delivering if the tool is deactivated later.
1287
1287
 
1288
- `TaskControl<T>` exposes `id`, `signal`, `mode`, `onModeChange(listener)`, `accept()`, `publish(update)`, `attachOutput(path, cleanup?)`, and `cancel()`. `publish({ result, display?, usage? })` replaces the bounded model-readable result and optionally the human-only Output suffix and cumulative usage; `/tasks` combines result and display, while model reads and completion notifications use only the result. A task with an attached output file is read from that file instead. Call `accept()` after whole-invocation preflight, before allowing a background handoff. Use the control's signal throughout execution. Parent-call cancellation is disconnected after handoff, while runtime shutdown still cancels the task. Mode subscriptions concern only this task; executors do not need the global registry to observe their own handoff or cancel themselves.
1288
+ `run` returns `TaskCompletion<T>`: `{ result, status?, display?, error?, facts?, usage? }`. The original tool result is returned to a foreground caller; task history keeps only its bounded text. Optional `display` is the human-only view that `/tasks` shows in place of the result; `tasks read` and completion context always use `result`. A terminal snapshot carries a display only if this completion supplies one: the last progress display does not survive settlement. Terminal `facts` replace launch facts with the same label, as the shell does with `["Exit", "42"]`. Explicit terminal statuses are `completed`, `partial`, `failed`, `cancelled`, and `timeout`. The host settles usage once, preferring `usage`, then `result.usage`, then the latest published usage; managed foreground results omit usage to avoid billing it twice.
1289
1289
 
1290
- Completion messages store `{ version: 3, task }` in `details`, a bounded terminal `TaskSnapshot`; only their bounded `content` enters model context. Built-in `tasks wait` binds delivery to the actual tool call, and the host confirms it after successful persistence; a settled waitAny binds only its winning task. Reads and waits without a `toolCallId`, including `waitAny`, do not acknowledge delivery, and arbitrary tool-result detail fields cannot acknowledge a task.
1290
+ `TaskControl<T>` exposes `id`, `signal`, `mode`, `onModeChange(listener)`, `accept()`, `publish(update)`, `attachOutput(path, cleanup?)`, and `cancel()`. `publish({ result, display?, usage? })` replaces the bounded model-readable result and optionally the live human-only display and cumulative usage; while it runs `/tasks` shows the display in place of the result, while model reads and completion notifications use only the result. A task with an attached output file is read from that file instead. Call `accept()` after whole-invocation preflight, before allowing a background handoff; `onModeChange` listeners run when the mode becomes `background`. Use the control's signal throughout execution. Parent-call cancellation is disconnected after handoff, while runtime shutdown still cancels the task. Mode subscriptions concern only this task; executors do not need the global registry to observe their own handoff or cancel themselves.
1291
+
1292
+ Completion messages store `{ version: 3, task }` in `details`, a bounded terminal `TaskSnapshot`; only their bounded `content` enters model context. Reads do not acknowledge delivery, and arbitrary tool-result detail fields cannot acknowledge a task. An undelivered completion is a retention lease the session holds on its task: the record and its output stay available until the model has it, and a runtime embedded without a session never holds one, so its settled records are ordinary bounded history.
1291
1293
 
1292
1294
  ```typescript
1293
1295
  const outcome = await ctx.tasks.execute({
@@ -1309,7 +1311,7 @@ if (outcome.kind === "result") return outcome.result;
1309
1311
  return { content: [{ type: "text", text: `Task ${outcome.task.id} continues in the background.` }], details: undefined };
1310
1312
  ```
1311
1313
 
1312
- When Pi attaches the runtime to its durable session lifecycle, `ctx.tasks.session` exposes `stoppedByTreeNavigation(targetId)`, which lists running tasks that navigating to an entry would cancel. Standalone runtimes omit it. Wait receipts and steering interruption are not extension concerns: the runtime applies them inside `wait()` and `waitAny()`. In a session host, passing the tool call's `toolCallId` keeps a settled background task out of automatic notifications from before the wait until that tool result is persisted, so the persisted result acknowledges it exactly once; without a `toolCallId`, or in a standalone runtime, a wait is purely observational.
1314
+ When Pi attaches the runtime to its durable session lifecycle, `ctx.tasks.session` exposes `stoppedByTreeNavigation(targetId)`, which lists running tasks that navigating to an entry would cancel. Standalone runtimes omit it.
1313
1315
 
1314
1316
  `TaskRuntime`, `TaskRuntimeOptions`, and the public `Task*`/`TasksContext`/`TasksSessionContext` types are exported from `@astralyn/pi`. Captured capabilities close with their runtime; obtain the replacement from the new session context after reload or session replacement.
1315
1317
 
package/docs/json.md CHANGED
@@ -33,6 +33,7 @@ type AgentSessionEvent =
33
33
  | Exclude<AgentEvent, { type: "agent_end" }>
34
34
  | { type: "agent_end"; messages: AgentMessage[]; willRetry: boolean }
35
35
  | { type: "agent_settled"; aborted: boolean }
36
+ | { type: "session_quiescent" }
36
37
  | { type: "queue_update"; steering: readonly string[]; followUp: readonly string[] }
37
38
  | { type: "compaction_start"; reason: "manual" | "threshold" | "overflow" }
38
39
  | { type: "entry_appended"; entry: SessionEntry }
@@ -49,6 +50,8 @@ type AgentSessionEvent =
49
50
  ```
50
51
  ```
51
52
 
53
+ `session_quiescent` means nothing more will happen without new input: no run, compaction, retry, or branch summary, no unsettled Background task, and no Background completion waiting to start a turn. It is emitted once each time the session reaches that state, after the `agent_settled` of the last run. In `--mode json` the stream ends with it: print mode waits for quiescence after every prompt, so the turns that Background completions start are part of the stream, and a line on stderr names the tasks it is waiting for.
54
+
52
55
  `queue_update` emits the full pending steering and follow-up queues whenever they change. `compaction_start` and `compaction_end` cover both manual and automatic compaction.
53
56
 
54
57
  Base events retained by `AgentSessionEvent` come from `AgentEvent` in [`@earendil-works/pi-agent-core`](https://www.npmjs.com/package/@earendil-works/pi-agent-core); its `agent_end` member is replaced above:
@@ -88,7 +88,7 @@ Each `modelOverrides` key is the ID of a built-in or extension model. Unknown ID
88
88
 
89
89
  ## Input limits
90
90
 
91
- See [Describe model input and caching](models.md#describe-model-input-and-caching).
91
+ See [Image Input Limits](models.md#image-input-limits).
92
92
 
93
93
  | Field | Type | Default | Description |
94
94
  |---|---|---|---|
package/docs/models.md CHANGED
@@ -153,18 +153,7 @@ At provider level, `api` is the default for entries in that provider's `models`
153
153
 
154
154
  Each key in `providers` is the provider ID used by `/model`, `--provider`, saved credentials, and model references.
155
155
 
156
- | Field | Description |
157
- | --- | --- |
158
- | `name` | Optional display name for the provider. Defaults to its ID. |
159
- | `baseUrl` | Default endpoint for this provider's models. A new provider normally needs this for its first custom model; a model may override it. |
160
- | `api` | Default [API tag](#api-tags) for entries in `models`. |
161
- | `apiKey` | Optional fallback API-key value; see [Value Resolution](#value-resolution) and [credential resolution](providers.md#resolution-order). |
162
- | `oauth` | Dynamic OAuth provider type. `"radius"` is the only supported value and requires a Radius gateway `baseUrl`; see [Providers: Radius](providers.md#radius). |
163
- | `headers` | Request headers for every model on the provider; see [Custom Headers](#custom-headers). |
164
- | `authHeader` | Adds `Authorization: Bearer <resolved apiKey>` after resolving authentication. |
165
- | `compat` | Compatibility defaults merged into the provider's models; see [Compatibility Options](#compatibility-options). |
166
- | `models` | Custom model definitions. On a built-in provider they are added or replaced by `id`; other catalog models remain. |
167
- | `modelOverrides` | Partial changes for matching final models, including built-in and extension-registered models; see [Per-model Overrides](#per-model-overrides). |
156
+ See [Provider fields](models-configuration.md#providers) for the complete field reference and defaults. `oauth: "radius"` selects the Radius OAuth flow and requires a gateway `baseUrl`; see [Providers: Radius](providers.md#radius).
168
157
 
169
158
  A custom model needs an effective `baseUrl` and `api`. For a new provider, put both on the provider (the usual approach) or on its first model. On a built-in provider, a model that replaces an existing ID can fall back to that catalog model's endpoint and API. `contextWindow` and `maxTokens`, when supplied in a `models` entry, must be positive.
170
159
 
@@ -236,24 +225,7 @@ Provider headers apply to every request. A configured model header overrides the
236
225
 
237
226
  A `models` entry supplies a complete custom model definition with useful defaults. A model matching a built-in ID replaces that catalog entry rather than partially modifying it; use [`modelOverrides`](#per-model-overrides) for partial changes.
238
227
 
239
- | Field | Required | Default | Description |
240
- | --- | --- | --- | --- |
241
- | `id` | Yes | — | Model identifier sent to the API. |
242
- | `name` | No | `id` | Human-readable label. It is searchable by `--model` patterns and shown as model detail text. |
243
- | `api` | No | Provider `api` | [API tag](#api-tags) for this model. |
244
- | `baseUrl` | No | Provider `baseUrl` | Endpoint override for this model. |
245
- | `reasoning` | No | `false` | Whether the model supports Pi thinking levels. |
246
- | `thinkingLevelMap` | No | omitted | Maps Pi thinking levels to provider values and declares unsupported levels; see below. |
247
- | `input` | No | `["text"]` | Supported input kinds: `text` and `image`. |
248
- | `inputLimits` | No | omitted | Request limits and image preprocessing for this model; see [Image Input Limits](#image-input-limits). |
249
- | `contextWindow` | No | `128000` | Context window in tokens. |
250
- | `maxTokens` | No | `16384` | Maximum generated tokens. |
251
- | `samplingParamsByThinkingLevel` | No | omitted | Per-level sampling overrides; see [Configure sampling by thinking level](#configure-sampling-by-thinking-level). |
252
- | `samplingParams` | No | omitted | Free-form sampling parameters merged verbatim into every request body; see [Sampling Parameters](#sampling-parameters). |
253
- | `cost` | No | all rates `0` | Per-million-token rates and optional request-wide price tiers. |
254
- | `promptCache` | No | omitted | Best-effort prompt cache lifetime in seconds per retention tier; see [Prompt Cache Lifetimes](#prompt-cache-lifetimes). |
255
- | `headers` | No | omitted | Model-specific request headers; see [Custom Headers](#custom-headers). |
256
- | `compat` | No | provider `compat` | Compatibility refinements merged with provider defaults. |
228
+ See [Model fields](models-configuration.md#models) for the complete field reference, including defaults and nested cost and input-limit shapes.
257
229
 
258
230
  `/model`, `--list-models`, and the footer show a model's `id`; `name` does not replace it. The model selector and `--model` pattern matching also search `name`.
259
231
 
@@ -506,20 +478,7 @@ Set `compat` on a provider for defaults or on a `models`/`modelOverrides` entry
506
478
 
507
479
  For `api: "anthropic-messages"`, use these options for Anthropic-compatible endpoints:
508
480
 
509
- | Field | Description |
510
- | --- | --- |
511
- | `supportsEagerToolInputStreaming` | Accepts per-tool `eager_input_streaming`. Default: `true`. When `false`, Pi omits it and sends the legacy `fine-grained-tool-streaming-2025-05-14` beta header for tool-enabled requests. |
512
- | `supportsLongCacheRetention` | Accepts `cache_control.ttl: "1h"` for long cache retention. Default: `true`. |
513
- | `sendSessionAffinityHeaders` | Sends `x-session-affinity` from the session ID when caching is enabled. Default: `false`; built-in model metadata can enable it for providers that need it. |
514
- | `supportsCacheControlOnTools` | Accepts Anthropic `cache_control` on tool definitions. Default: `true`. |
515
- | `supportsTemperature` | Accepts non-default `temperature`. Default: `true`; set `false` for endpoints such as Claude Opus 4.7+ that reject it. |
516
- | `forceAdaptiveThinking` | Uses adaptive thinking (`thinking.type: "adaptive"` and `output_config.effort`) regardless of model ID. Default: `false`. |
517
- | `allowEmptySignature` | Replays an empty thinking signature as `signature: ""` instead of converting the thinking block to text. Default: `false`. |
518
- | `supportsStrictTools` | Accepts strict JSON-schema tool definitions. Default: `false`; capable built-in Anthropic models set it in their metadata. |
519
- | `supportsMidConvoEffort` | Accepts effort-only system messages and thinking binding controls. Default: `false`. |
520
- | `supportsMidConvoSystemMessages` | Accepts system-role messages inside the conversation. When `false`, later system messages fold into the top-level system prompt. Default: `false`. |
521
- | `supportsMidConvoToolChanges` | Accepts mid-conversation `tool_addition` and `tool_removal` blocks. Requires `supportsMidConvoSystemMessages`. Default: `false`. |
522
- | `allowedFallbackModels` | Models Anthropic accepts in `fallbacks` for server-side refusal fallback, with local pricing metadata for returned fallback responses. Omit `fallbacks` when empty. |
481
+ See [Anthropic Messages compatibility fields](models-configuration.md#anthropic-messages) for the complete field reference and defaults.
523
482
 
524
483
  For example, a proxy that lacks eager streaming but supports long cache retention and adaptive thinking can use:
525
484
 
@@ -554,36 +513,9 @@ For example, a proxy that lacks eager streaming but supports long cache retentio
554
513
 
555
514
  For `api: "openai-completions"`, Pi auto-detects many defaults from the provider ID and base URL. Set an option only when the endpoint differs from that behavior.
556
515
 
557
- | Field | Description |
558
- | --- | --- |
559
- | `supportsStore` | Accepts `store: false`. Default: URL-detected. |
560
- | `supportsDeveloperRole` | Uses `developer` rather than `system` for reasoning models. Default: URL-detected. |
561
- | `supportsReasoningEffort` | Accepts `reasoning_effort` when the selected thinking format uses it. Default: URL-detected. |
562
- | `supportsUsageInStreaming` | Accepts `stream_options: { "include_usage": true }`. Default: `true`. |
563
- | `supportsFinishReason` | Whether streamed responses include `finish_reason`. When `false`, Pi infers `stop` or `toolUse` when the stream ends. Default: `true`. |
564
- | `maxTokensField` | Selects `max_completion_tokens` or `max_tokens`. Default: URL-detected. |
565
- | `requiresToolResultName` | Requires `name` on tool-result messages. Default: URL-detected. |
566
- | `requiresAssistantAfterToolResult` | Requires an assistant message before a user message following tool results. Default: URL-detected. |
567
- | `requiresThinkingAsText` | Replays thinking blocks as text delimited by `<thinking>`. Default: URL-detected. |
568
- | `requiresReasoningContentOnAssistantMessages` | Includes empty `reasoning_content` on replayed assistant messages when reasoning is enabled. Default: URL-detected. |
569
- | `thinkingFormat` | One of `openai`, `openrouter`, `deepseek`, `together`, `zai`, `qwen`, `chat-template`, `qwen-chat-template`, `string-thinking`, or `ant-ling`. Default: `openai` unless URL detection selects a known provider format. |
570
- | `chatTemplateKwargs` | `chat_template_kwargs` for `thinkingFormat: "chat-template"`. Values may be strings, numbers, booleans, `null`, or `{ "$var": "thinking.enabled" | "thinking.effort" | "thinking.budget", "omitWhenOff"?: true }`. |
571
- | `chatTemplateArgs` | `chat_template_args` for `thinkingFormat: "baseten"`. Values may be strings, numbers, booleans, `null`, or `{ "$var": "thinking.enabled" | "thinking.effort" | "thinking.budget", "omitWhenOff"?: true }`. |
572
- | `thinkingTokenBudgetField` | Top-level request field used to cap reasoning tokens from `thinkingBudgets`, clamped so at least 1024 tokens remain for the answer. Use `thinking_token_budget` for vLLM, `thinking_budget` for Qwen/DashScope/SGLang, or `thinking_budget_tokens` for llama.cpp. Off by default. |
573
- | `supportsThinkingTokenBudget` | Alias for `thinkingTokenBudgetField: "thinking_token_budget"` (vLLM). Prefer `thinkingTokenBudgetField`. Default: `false`. |
574
- | `zaiToolStream` | Sends top-level `tool_stream: true` when tools are present. Default: `false`; use only for Z.AI-compatible endpoints that require it. |
575
- | `cacheControlFormat` | `anthropic` applies Anthropic-style `cache_control` markers to the system prompt, last tool definition, and final text content when caching is enabled. |
576
- | `sendSessionAffinityHeaders` | Sends session-affinity headers from the session ID when caching is enabled. Default: `false`. |
577
- | `sessionAffinityFormat` | `openai` sends `session_id`, `x-client-request-id`, and `x-session-affinity`; `openai-nosession` omits `session_id`; `openrouter` sends `x-session-id`. It does not change `prompt_cache_key`. Default: URL-detected. |
578
- | `supportsStrictMode` | Accepts strict JSON-schema function tools. Default: URL-detected. |
579
- | `supportsOpenAIGrammarTools` | Emits OpenAI Lark/regex grammar tools. When `false`, grammar-constrained tools fall back to normal function tools. Default: `false`. |
580
- | `supportsMidConvoSystemMessages` | Accepts system or developer messages after the conversation has started. When `false`, later system messages fold into the leading system message. Default: `false`. |
581
- | `supportsMidConvoToolAdditions` | Accepts tools introduced by mid-conversation system messages. Requires `supportsMidConvoSystemMessages`. Default: `false`. |
582
- | `supportsLongCacheRetention` | Accepts long prompt-cache retention: `prompt_cache_retention: "24h"`, or `cache_control.ttl: "1h"` with `cacheControlFormat: "anthropic"`. Default: `true` except URL-detected incompatible endpoints. |
583
- | `openRouterRouting` | OpenRouter routing preferences sent unchanged in the request `provider` field. |
584
- | `vercelGatewayRouting` | Vercel AI Gateway routing preferences (`only`, `order`) sent as `providerOptions.gateway`. |
585
-
586
- Thinking formats use these request shapes: `openai` sends `reasoning_effort`; `openrouter` sends `reasoning: { effort }`; `deepseek` sends `thinking: { type }` and, when enabled, `reasoning_effort`; `together` sends `reasoning: { enabled }` and optionally `reasoning_effort`; `zai` sends `thinking: { type }`; `qwen` sends `enable_thinking`; `qwen-chat-template` sends `chat_template_kwargs.enable_thinking` and `preserve_thinking`; `string-thinking` sends top-level `thinking` as a string; and `ant-ling` sends `reasoning: { effort }` only for a non-null mapped effort. `chat-template` sends the configured `chatTemplateKwargs`.
516
+ See [OpenAI Chat Completions compatibility fields](models-configuration.md#openai-completions) for the complete field reference and defaults.
517
+
518
+ Thinking formats use these request shapes: `openai` sends `reasoning_effort`; `openrouter` sends `reasoning: { effort }`; `deepseek` sends `thinking: { type }` and, when enabled, `reasoning_effort`; `together` sends `reasoning: { enabled }` and optionally `reasoning_effort`; `baseten` sends the configured `chatTemplateArgs` and optionally `reasoning_effort`; `zai` sends `thinking: { type }`; `qwen` sends `enable_thinking`; `qwen-chat-template` sends `chat_template_kwargs.enable_thinking` and `preserve_thinking`; `string-thinking` sends top-level `thinking` as a string; and `ant-ling` sends `reasoning: { effort }` only for a non-null mapped effort. `chat-template` sends the configured `chatTemplateKwargs`.
587
519
 
588
520
  `thinkingTokenBudgetField` is independent of `thinkingFormat`. Do not enable it on the generated Qwen catalog: those models already send `reasoning_effort`, and DashScope rejects `thinking_budget` together with `reasoning_effort`.
589
521
 
@@ -686,17 +618,7 @@ Vercel AI Gateway example:
686
618
 
687
619
  ### OpenAI Responses
688
620
 
689
- For `api: "openai-responses"`, the compatible fields are `supportsDeveloperRole`, `sessionAffinityFormat`, `supportsLongCacheRetention`, `supportsStrictMode`, `supportsOpenAIGrammarTools`, `supportsToolSearch`, and `supportsExplicitPromptCacheMode`.
690
-
691
- | Field | Description |
692
- | --- | --- |
693
- | `supportsDeveloperRole` | Uses `developer` rather than `system` for reasoning models. Default: `true`. |
694
- | `sessionAffinityFormat` | `openai` sends `session_id` and `x-client-request-id`; `openai-nosession` omits `session_id`; `openrouter` sends `x-session-id`. It does not change `prompt_cache_key`. |
695
- | `supportsLongCacheRetention` | Accepts long cache retention: `prompt_cache_options.ttl: "30m"` when `supportsExplicitPromptCacheMode` is enabled (including GPT-5.6+ Responses models), otherwise `prompt_cache_retention: "24h"`. Default: `true`. |
696
- | `supportsStrictMode` | Accepts strict JSON-schema function tools. Default: `false`. |
697
- | `supportsOpenAIGrammarTools` | Emits OpenAI Lark/regex grammar tools. Default: `false`. |
698
- | `supportsToolSearch` | Supports client-executed deferred tool search. Default: `false`. |
699
- | `supportsExplicitPromptCacheMode` | Accepts `prompt_cache_options: { mode: "explicit" }` when cache retention is `none`, which disables implicit prompt caching; long cache retention uses `{ ttl: "30m" }` when `supportsLongCacheRetention` is also enabled. Default: `false`. |
621
+ For `api: "openai-responses"`, see [Responses compatibility fields](models-configuration.md#openai-responses) for the complete field reference and defaults.
700
622
 
701
623
  The public Pi AI model type also exposes the Responses compatibility shape for `azure-openai-responses` and `openai-codex-responses`. The Azure transport uses `supportsDeveloperRole`, `supportsStrictMode`, and `supportsOpenAIGrammarTools`; the Codex transport uses `supportsStrictMode`, `supportsOpenAIGrammarTools`, and `supportsToolSearch`. Other Responses options do not alter those transports.
702
624
 
package/docs/rpc.md CHANGED
@@ -201,6 +201,7 @@ Response:
201
201
  "thinkingLevel": "medium",
202
202
  "isStreaming": false,
203
203
  "isCompacting": false,
204
+ "isQuiescent": true,
204
205
  "steeringMode": "all",
205
206
  "followUpMode": "one-at-a-time",
206
207
  "sessionFile": "/path/to/session.jsonl",
@@ -213,6 +214,8 @@ Response:
213
214
  }
214
215
  ```
215
216
 
217
+ `isQuiescent` is true when no run, compaction, retry, unsettled Background task, or pending Background completion turn remains (see [session_quiescent](#session_quiescent)).
218
+
216
219
  The `model` field is a full [Model](#model) object when one is selected, otherwise it is omitted. The `sessionFile` and `sessionName` fields are also omitted when unavailable; `sessionName` is the display name set via `set_session_name`.
217
220
 
218
221
  #### get_messages
@@ -895,6 +898,7 @@ Events are streamed to stdout as JSON lines during agent operation. Events do no
895
898
  | `agent_start` | Agent begins processing |
896
899
  | `agent_end` | One low-level agent run completes (may still be followed by retry, compaction, or queued continuations) |
897
900
  | `agent_settled` | Agent run is fully settled; no automatic retry, compaction retry, or queued continuation remains |
901
+ | `session_quiescent` | Nothing more will happen without new input, including Background tasks and the turns their completions start |
898
902
  | `turn_start` | New turn begins |
899
903
  | `turn_end` | Turn completes (includes assistant message and tool results) |
900
904
  | `message_start` | Message begins |
@@ -947,6 +951,16 @@ Emitted after the full session-level run settles. At this point Pi will not cont
947
951
  {"type": "agent_settled", "aborted": false}
948
952
  ```
949
953
 
954
+ ### session_quiescent
955
+
956
+ Emitted when nothing more will happen without new input: no run, compaction, retry, or branch summary is active, no Background task is unsettled, and no Background completion is waiting to start a turn. A Background completion starts a new run after `agent_settled`, so a client that needs "all work done" waits for this event instead. It is emitted once per transition into that state, after the `agent_settled` of the last run. A completion whose delivery failed does not count, because it is retried only on the next input.
957
+
958
+ ```json
959
+ {"type": "session_quiescent"}
960
+ ```
961
+
962
+ `RpcClient.waitForQuiescence(timeout?)` resolves on this event, or at once when `get_state` reports `isQuiescent`; `collectEventsUntilQuiescent(timeout?)` collects events until it. `waitForIdle()` and `collectEvents()` still resolve on the first `agent_settled`. Closing stdin shuts the process down immediately, without waiting for Background work.
963
+
950
964
  ### turn_start / turn_end
951
965
 
952
966
  A turn consists of one assistant response plus any resulting tool calls and results.
package/docs/sdk.md CHANGED
@@ -71,6 +71,10 @@ interface AgentSession {
71
71
  // Send a prompt and wait for completion
72
72
  prompt(text: string, options?: PromptOptions): Promise<void>;
73
73
 
74
+ // Resolve once no run, compaction, retry or Background work remains, including completion turns
75
+ waitForQuiescence(signal?: AbortSignal): Promise<void>;
76
+ readonly isQuiescent: boolean;
77
+
74
78
  // Queue messages, optionally with image attachments
75
79
  steer(text: string, images?: ImageContent[]): Promise<void>;
76
80
  followUp(text: string, images?: ImageContent[]): Promise<void>;
@@ -246,16 +250,19 @@ Both `steer()` and `followUp()` expand file-based prompt templates but error on
246
250
 
247
251
  ### Background execution
248
252
 
249
- Background is a session-owned capability, not a daemon or a flag on `createAgentSession()`. Interactive mode enables it. Ordinary SDK sessions and the built-in print, JSON, and RPC hosts leave it disabled and reject native shell or extension calls with `background: true`. Normal foreground tools still work, including standalone `createBashTool()` / `createPowerShellTool()` calls without a session context.
253
+ Background is a session-owned capability, not a daemon or a flag on `createAgentSession()`. Interactive mode and the built-in print, JSON, and RPC hosts enable it. Ordinary SDK sessions leave it disabled and reject native shell or extension calls with `background: true`. Normal foreground tools still work, including standalone `createBashTool()` / `createPowerShellTool()` calls without a session context.
250
254
 
251
255
  An embedding can opt in with `await session.bindExtensions({ tasksEnabled: true })`. Bind before prompting; this also binds extensions and emits their session-start lifecycle. When using `AgentSessionRuntime`, rebind the replacement session through the runtime's rebind callback. Setting `mode` or providing a UI alone does not enable Background.
252
256
 
257
+ Background work is admitted only while the `tasks` management tool is active in the session, so the model that starts work can always read and stop it. The bundled `builtin:tasks` extension provides that tool; a session that disables the extension or deactivates the tool rejects `background: true` and every detach path (the error says no work was started), while work already admitted keeps running and delivering.
258
+
253
259
  The host must choose:
254
260
 
255
261
  - **Lifetime and cancellation:** a settled main-agent turn is not the end of background work. `session.abort()` cancels foreground-owned work, not detached work. Use `session.tasks.kill(id)` for a whole execution, or `await session.tasks.shutdown(graceMs?)` to close admission, cancel all owned work and drain within a bounded grace period (default 2 seconds, maximum 60 seconds). A non-cooperating executor can outlive that grace period.
256
262
  - **Result-driven turns and cost:** automatic completions join the host's prompt queue, so a running turn receives them as steering and an idle session starts a completion turn. Use the nestable `session.pauseTaskNotifications()` to defer delivery; its returned release function is idempotent. Pausing does not stop execution or its costs. Budget worker activity and completion-triggered turns explicitly.
257
- - **Observation and delivery:** `session.tasks` exposes the [public supervision methods](extensions.md#ctxtasks). `read()`, `wait()`, and `waitAny()` observe; a terminal snapshot alone does not acknowledge model delivery. `waitAny()` covers only tasks running at call time and reports `no_tasks` for an empty cohort. Both return a typed `outcome` (`settled`, `timed_out`, `interrupted`, or `closed`), and steering interrupts only waits, never execution. The built-in `tasks wait` tool passes its tool-call ID as `toolCallId`, so the runtime holds the observed result back from notifications and the host confirms it only after persisting that tool result, even if extensions rewrite its details. A direct SDK waiter that consumes the result outside model history omits `toolCallId` and can explicitly call `session.tasks.markDelivered(task.id)` after successful consumption. A timeout or cancelled wait does not stop the execution. Failed notification delivery leaves the result available for inspection; after fixing the host failure, call `session.retryTaskNotifications()` explicitly. Pi does not retry a failed delivery forever on a timer.
263
+ - **Observation and delivery:** `session.tasks` exposes the [public supervision methods](extensions.md#ctxtasks). `list()`, `get()` and `read()` observe; a terminal snapshot alone does not acknowledge model delivery, and there is no blocking wait: subscribe with `session.tasks.subscribe()` or use `waitForQuiescence()` below. A direct SDK consumer that handles a result outside model history can call `session.acknowledgeTaskDelivery(task.id)` after successful consumption, which stops the session from notifying the model about it (an unknown or already delivered ID is ignored). An undelivered completion keeps its task and output retained until it is delivered or acknowledged, or its runtime closes. Failed notification delivery leaves the result available for inspection; after fixing the host failure, call `session.retryTaskNotifications()` explicitly. Pi does not retry a failed delivery forever on a timer.
258
264
  - **Shutdown:** `await runtime.dispose()` handles background shutdown and extension lifecycle when using the runtime API. For a directly owned session, drain Background, abort the main agent, then dispose; synchronous `session.dispose()` alone is not an awaited drain.
265
+ - **Waiting for all work:** `prompt()` resolves when its own run settles, before any Background completion turn. `await session.waitForQuiescence(signal?)` resolves once the session is quiescent: no run, compaction, retry or branch summary, no unsettled Background task, and no completion left to deliver (the `session_quiescent` event marks the same moment, once per transition). It uses no timers and rejects when `signal` aborts or the session is disposed. A completion whose delivery failed does not count, since it retries only on the next input; a Background task that never ends keeps the wait open, so pass a signal when that matters.
259
266
 
260
267
  A deliberately single-turn embedding can suppress completion-triggered turns and cancel leftover work on exit:
261
268
 
@@ -378,6 +385,9 @@ session.subscribe((event) => {
378
385
  // Main agent, retries, compaction, and queued continuations have settled
379
386
  // Background executions may still be active
380
387
  break;
388
+ case "session_quiescent":
389
+ // Also no unsettled Background task and no completion turn still to come
390
+ break;
381
391
 
382
392
  // Turn lifecycle (one LLM response + tool calls)
383
393
  case "turn_start":