@oh-my-pi/pi-coding-agent 17.2.9 → 17.2.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 (281) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/dist/{CHANGELOG-8yy8h3bc.md → CHANGELOG-d9xenpn9.md} +83 -0
  3. package/dist/cli.js +10208 -12596
  4. package/dist/types/capability/mcp.d.ts +11 -0
  5. package/dist/types/capability/skill.d.ts +6 -0
  6. package/dist/types/cli/args.d.ts +1 -0
  7. package/dist/types/cli/command-help.d.ts +3 -0
  8. package/dist/types/commands/share.d.ts +25 -0
  9. package/dist/types/commit/agentic/index.d.ts +3 -1
  10. package/dist/types/commit/execute.d.ts +32 -0
  11. package/dist/types/commit/index.d.ts +2 -1
  12. package/dist/types/commit/pipeline.d.ts +7 -1
  13. package/dist/types/config/model-registry.d.ts +4 -0
  14. package/dist/types/config/model-resolver.d.ts +13 -0
  15. package/dist/types/config/settings-schema.d.ts +1 -34
  16. package/dist/types/discovery/agent-plugin-format.d.ts +143 -0
  17. package/dist/types/discovery/agent-plugins.d.ts +1 -0
  18. package/dist/types/discovery/contained-path.d.ts +34 -0
  19. package/dist/types/discovery/index.d.ts +1 -0
  20. package/dist/types/extensibility/custom-commands/types.d.ts +5 -5
  21. package/dist/types/extensibility/custom-tools/types.d.ts +3 -3
  22. package/dist/types/extensibility/extensions/loader.d.ts +9 -2
  23. package/dist/types/extensibility/extensions/types.d.ts +45 -8
  24. package/dist/types/extensibility/hooks/types.d.ts +5 -5
  25. package/dist/types/extensibility/shared-events.d.ts +3 -2
  26. package/dist/types/extensibility/skills.d.ts +5 -0
  27. package/dist/types/index.d.ts +2 -2
  28. package/dist/types/main.d.ts +1 -1
  29. package/dist/types/mcp/transports/header-policy.d.ts +45 -0
  30. package/dist/types/mcp/types.d.ts +15 -0
  31. package/dist/types/modes/acp/acp-agent.d.ts +1 -1
  32. package/dist/types/modes/acp/acp-client-bridge.d.ts +1 -1
  33. package/dist/types/modes/acp/acp-event-mapper.d.ts +1 -1
  34. package/dist/types/modes/acp/acp-mode.d.ts +1 -1
  35. package/dist/types/modes/components/agent-hub-projection.d.ts +38 -0
  36. package/dist/types/modes/components/agent-hub-renderer.d.ts +41 -0
  37. package/dist/types/modes/components/agent-hub.d.ts +14 -5
  38. package/dist/types/modes/components/agent-transcript-viewer.d.ts +2 -0
  39. package/dist/types/modes/components/chat-transcript-builder.d.ts +2 -0
  40. package/dist/types/modes/components/custom-editor.d.ts +1 -2
  41. package/dist/types/modes/components/model-browser.d.ts +9 -1
  42. package/dist/types/modes/components/status-line/types.d.ts +2 -0
  43. package/dist/types/modes/components/tool-execution.d.ts +2 -0
  44. package/dist/types/modes/interactive-mode.d.ts +8 -1
  45. package/dist/types/modes/utils/transcript-render-helpers.d.ts +3 -2
  46. package/dist/types/registry/agent-registry.d.ts +47 -0
  47. package/dist/types/registry/persisted-agents.d.ts +3 -1
  48. package/dist/types/secrets/index.d.ts +23 -1
  49. package/dist/types/session/agent-session-events.d.ts +2 -2
  50. package/dist/types/session/agent-session.d.ts +4 -2
  51. package/dist/types/session/messages.d.ts +1 -0
  52. package/dist/types/session/prewalk.d.ts +3 -1
  53. package/dist/types/session/session-entries.d.ts +10 -0
  54. package/dist/types/session/session-loader.d.ts +11 -1
  55. package/dist/types/session/session-manager.d.ts +9 -1
  56. package/dist/types/session/session-tools.d.ts +18 -1
  57. package/dist/types/session/turn-recovery.d.ts +7 -2
  58. package/dist/types/slash-commands/acp-builtins.d.ts +1 -1
  59. package/dist/types/slash-commands/available-commands.d.ts +1 -1
  60. package/dist/types/task/executor.d.ts +6 -0
  61. package/dist/types/task/index.d.ts +4 -3
  62. package/dist/types/task/read-only-policy.d.ts +3 -0
  63. package/dist/types/task/structured-subagent.d.ts +2 -0
  64. package/dist/types/task/types.d.ts +4 -0
  65. package/dist/types/tools/bash.d.ts +3 -3
  66. package/dist/types/tools/browser/launch.d.ts +2 -1
  67. package/dist/types/tools/browser/tab-worker.d.ts +2 -2
  68. package/dist/types/tools/path-utils.d.ts +1 -0
  69. package/dist/types/tools/read.d.ts +1 -5
  70. package/dist/types/tools/run-scope.d.ts +27 -1
  71. package/dist/types/tools/shell-tokenize.d.ts +16 -0
  72. package/dist/types/tools/terminal-output.d.ts +1 -1
  73. package/dist/types/utils/turndown.d.ts +1 -1
  74. package/dist/types/vibe/runtime.d.ts +2 -3
  75. package/dist/types/web/scrapers/readthedocs.d.ts +0 -3
  76. package/dist/types/web/search/providers/browser-headers.d.ts +2 -3
  77. package/examples/custom-tools/README.md +1 -1
  78. package/examples/extensions/README.md +1 -1
  79. package/examples/extensions/api-demo.ts +5 -6
  80. package/examples/extensions/chalk-logger.ts +1 -1
  81. package/examples/extensions/hello.ts +2 -2
  82. package/examples/extensions/reload-runtime.ts +1 -1
  83. package/examples/extensions/tools.ts +2 -2
  84. package/examples/extensions/with-deps/index.ts +1 -1
  85. package/examples/sdk/06-extensions.ts +1 -1
  86. package/package.json +15 -31
  87. package/scripts/bundle-dist.ts +1 -11
  88. package/scripts/legacy-pi-virtual-module.ts +4 -1
  89. package/src/advisor/runtime.ts +54 -0
  90. package/src/capability/mcp.ts +11 -0
  91. package/src/capability/skill.ts +6 -0
  92. package/src/cli/agents-cli.ts +1 -1
  93. package/src/cli/args.ts +24 -2
  94. package/src/cli/auth-broker-cli.ts +1 -1
  95. package/src/cli/auth-gateway-cli.ts +1 -1
  96. package/src/cli/bench-cli.ts +1 -1
  97. package/src/cli/claude-trace-cli.ts +1 -1
  98. package/src/cli/command-help.ts +4 -0
  99. package/src/cli/config-cli.ts +1 -1
  100. package/src/cli/dry-balance-cli.ts +1 -1
  101. package/src/cli/file-processor.ts +1 -1
  102. package/src/cli/flag-tables.ts +4 -0
  103. package/src/cli/gallery-cli.ts +1 -1
  104. package/src/cli/grep-cli.ts +1 -1
  105. package/src/cli/grievances-cli.ts +1 -1
  106. package/src/cli/help-extra.ts +1 -1
  107. package/src/cli/models-cli.ts +1 -1
  108. package/src/cli/plugin-cli.ts +1 -1
  109. package/src/cli/read-cli.ts +1 -1
  110. package/src/cli/setup-cli.ts +1 -1
  111. package/src/cli/shell-cli.ts +1 -1
  112. package/src/cli/ssh-cli.ts +1 -1
  113. package/src/cli/stats-cli.ts +1 -1
  114. package/src/cli/tiny-models-cli.ts +1 -1
  115. package/src/cli/ttsr-cli.ts +1 -1
  116. package/src/cli/update-cli.ts +1 -1
  117. package/src/cli/usage-cli.ts +1 -1
  118. package/src/cli/web-search-cli.ts +1 -1
  119. package/src/cli/worktree-cli.ts +1 -1
  120. package/src/cli-commands.ts +5 -0
  121. package/src/commands/commit.ts +16 -3
  122. package/src/commands/say.ts +1 -1
  123. package/src/commands/share.ts +71 -0
  124. package/src/commands/token.ts +1 -1
  125. package/src/commit/agentic/agent.ts +1 -1
  126. package/src/commit/agentic/index.ts +39 -21
  127. package/src/commit/cli.ts +1 -1
  128. package/src/commit/execute.ts +56 -0
  129. package/src/commit/index.ts +2 -1
  130. package/src/commit/pipeline.ts +20 -7
  131. package/src/config/model-discovery.ts +1 -1
  132. package/src/config/model-registry.ts +21 -1
  133. package/src/config/model-resolver.ts +54 -7
  134. package/src/config/settings-schema.ts +2 -33
  135. package/src/config/settings.ts +46 -0
  136. package/src/discovery/agent-plugin-format.ts +551 -0
  137. package/src/discovery/agent-plugins.ts +341 -0
  138. package/src/discovery/agents.ts +4 -2
  139. package/src/discovery/builtin-rules/ts-no-inline-cast-access.md +5 -4
  140. package/src/discovery/builtin-rules/ts-no-tiny-functions.md +1 -1
  141. package/src/discovery/claude-plugins.ts +21 -5
  142. package/src/discovery/contained-path.ts +78 -0
  143. package/src/discovery/helpers.ts +23 -9
  144. package/src/discovery/index.ts +1 -0
  145. package/src/discovery/omp-plugins.ts +20 -7
  146. package/src/eval/py/runner.py +38 -1
  147. package/src/exec/non-interactive-env.ts +1 -0
  148. package/src/extensibility/custom-commands/loader.ts +4 -4
  149. package/src/extensibility/custom-commands/types.ts +5 -5
  150. package/src/extensibility/custom-tools/loader.ts +4 -4
  151. package/src/extensibility/custom-tools/types.ts +3 -3
  152. package/src/extensibility/extensions/loader.ts +85 -20
  153. package/src/extensibility/extensions/runner.ts +6 -0
  154. package/src/extensibility/extensions/types.ts +51 -8
  155. package/src/extensibility/hooks/loader.ts +4 -5
  156. package/src/extensibility/hooks/types.ts +5 -5
  157. package/src/extensibility/plugins/legacy-pi-compat.ts +53 -0
  158. package/src/extensibility/plugins/marketplace/manager.ts +2 -1
  159. package/src/extensibility/shared-events.ts +3 -2
  160. package/src/extensibility/skills.ts +9 -0
  161. package/src/index.ts +2 -2
  162. package/src/internal-urls/memory-protocol.ts +5 -1
  163. package/src/internal-urls/skill-protocol.ts +14 -0
  164. package/src/internal-urls/vault-protocol.ts +2 -2
  165. package/src/launch/client.ts +11 -1
  166. package/src/launch/protocol.ts +7 -2
  167. package/src/launch/terminal-output.ts +1 -1
  168. package/src/main.ts +88 -23
  169. package/src/markit/converters/docx.ts +1 -1
  170. package/src/markit/converters/epub.ts +1 -1
  171. package/src/markit/converters/pptx.ts +1 -1
  172. package/src/markit/converters/xlsx.ts +1 -1
  173. package/src/mcp/config.ts +3 -0
  174. package/src/mcp/manager.ts +12 -9
  175. package/src/mcp/transports/header-policy.ts +95 -0
  176. package/src/mcp/transports/http.ts +35 -52
  177. package/src/mcp/transports/sse.ts +20 -27
  178. package/src/mcp/types.ts +15 -0
  179. package/src/modes/acp/acp-agent.ts +20 -9
  180. package/src/modes/acp/acp-client-bridge.ts +1 -1
  181. package/src/modes/acp/acp-event-mapper.ts +1 -1
  182. package/src/modes/acp/acp-mode.ts +1 -1
  183. package/src/modes/components/agent-dashboard.ts +2 -0
  184. package/src/modes/components/agent-hub-projection.ts +248 -0
  185. package/src/modes/components/agent-hub-renderer.ts +194 -0
  186. package/src/modes/components/agent-hub.ts +674 -207
  187. package/src/modes/components/agent-transcript-viewer.ts +3 -0
  188. package/src/modes/components/assistant-message.ts +1 -1
  189. package/src/modes/components/chat-transcript-builder.ts +3 -0
  190. package/src/modes/components/custom-editor.ts +0 -9
  191. package/src/modes/components/extensions/inspector-panel.ts +11 -6
  192. package/src/modes/components/footer.ts +1 -3
  193. package/src/modes/components/model-browser.ts +11 -3
  194. package/src/modes/components/model-hub.ts +4 -1
  195. package/src/modes/components/status-line/component.ts +1 -0
  196. package/src/modes/components/status-line/segments.ts +10 -7
  197. package/src/modes/components/status-line/types.ts +2 -0
  198. package/src/modes/components/tool-execution.ts +13 -16
  199. package/src/modes/components/tree-selector.ts +62 -3
  200. package/src/modes/controllers/command-controller.ts +4 -1
  201. package/src/modes/controllers/event-controller.ts +44 -20
  202. package/src/modes/controllers/extension-ui-controller.ts +2 -2
  203. package/src/modes/controllers/input-controller.ts +20 -2
  204. package/src/modes/controllers/mcp-command-controller.ts +81 -20
  205. package/src/modes/controllers/selector-controller.ts +41 -49
  206. package/src/modes/controllers/streaming-reveal.ts +1 -1
  207. package/src/modes/interactive-mode.ts +13 -2
  208. package/src/modes/rpc/rpc-mode.ts +2 -2
  209. package/src/modes/runtime-init.ts +1 -1
  210. package/src/modes/theme/theme.ts +2 -2
  211. package/src/modes/utils/transcript-render-helpers.ts +4 -2
  212. package/src/modes/utils/ui-helpers.ts +1 -0
  213. package/src/prompts/tools/computer.md +1 -1
  214. package/src/registry/agent-lifecycle.ts +14 -7
  215. package/src/registry/agent-registry.ts +65 -2
  216. package/src/registry/persisted-agents.ts +341 -16
  217. package/src/sdk.ts +6 -49
  218. package/src/secrets/index.ts +52 -1
  219. package/src/session/agent-session-events.ts +2 -2
  220. package/src/session/agent-session.ts +54 -16
  221. package/src/session/messages.ts +1 -0
  222. package/src/session/prewalk.ts +57 -17
  223. package/src/session/session-context.ts +3 -5
  224. package/src/session/session-entries.ts +10 -0
  225. package/src/session/session-handoff.ts +5 -1
  226. package/src/session/session-listing.ts +1 -1
  227. package/src/session/session-loader.ts +74 -8
  228. package/src/session/session-manager.ts +22 -2
  229. package/src/session/session-paths.ts +57 -9
  230. package/src/session/session-tools.ts +65 -4
  231. package/src/session/todo-tracker.ts +11 -1
  232. package/src/session/turn-recovery.ts +96 -74
  233. package/src/slash-commands/acp-builtins.ts +1 -1
  234. package/src/slash-commands/available-commands.ts +1 -1
  235. package/src/slash-commands/builtin-registry.ts +12 -8
  236. package/src/task/executor.ts +70 -20
  237. package/src/task/index.ts +30 -34
  238. package/src/task/isolation-runner.ts +24 -11
  239. package/src/task/persisted-revive.ts +12 -5
  240. package/src/task/read-only-policy.ts +27 -0
  241. package/src/task/structured-subagent.ts +14 -4
  242. package/src/task/types.ts +4 -0
  243. package/src/tools/auto-generated-guard.ts +1 -1
  244. package/src/tools/bash-interactive.ts +4 -4
  245. package/src/tools/bash-skill-urls.ts +15 -0
  246. package/src/tools/bash.ts +16 -17
  247. package/src/tools/browser/cmux/cmux-tab.ts +66 -18
  248. package/src/tools/browser/launch.ts +41 -12
  249. package/src/tools/browser/readable.ts +9 -9
  250. package/src/tools/browser/tab-supervisor.ts +12 -3
  251. package/src/tools/browser/tab-worker-entry.ts +1 -1
  252. package/src/tools/browser/tab-worker.ts +101 -12
  253. package/src/tools/debug.ts +1 -1
  254. package/src/tools/fetch.ts +1 -1
  255. package/src/tools/jtd-to-json-schema.ts +123 -21
  256. package/src/tools/path-utils.ts +9 -2
  257. package/src/tools/read.ts +21 -9
  258. package/src/tools/run-scope.ts +290 -4
  259. package/src/tools/shell-tokenize.ts +98 -0
  260. package/src/tools/terminal-output.ts +1 -1
  261. package/src/tools/todo.ts +1 -1
  262. package/src/utils/clipboard.ts +10 -2
  263. package/src/utils/external-editor.ts +4 -2
  264. package/src/utils/jj.ts +1 -1
  265. package/src/utils/turndown.ts +1 -2
  266. package/src/vibe/runtime.ts +5 -5
  267. package/src/web/scrapers/arxiv.ts +1 -1
  268. package/src/web/scrapers/go-pkg.ts +1 -1
  269. package/src/web/scrapers/iacr.ts +1 -1
  270. package/src/web/scrapers/readthedocs.ts +2 -1
  271. package/src/web/scrapers/twitter.ts +2 -2
  272. package/src/web/scrapers/types.ts +1 -1
  273. package/src/web/scrapers/wikipedia.ts +1 -1
  274. package/src/web/search/providers/browser-headers.ts +12 -39
  275. package/src/web/search/providers/codex.ts +3 -26
  276. package/src/web/search/providers/ecosia.ts +1 -1
  277. package/src/web/search/providers/exa.ts +1 -1
  278. package/src/web/search/providers/google.ts +1 -1
  279. package/src/web/search/providers/mojeek.ts +1 -1
  280. package/src/web/search/providers/startpage.ts +1 -1
  281. package/src/markit/converters/mammoth.d.ts +0 -24
@@ -44,6 +44,7 @@ import type { SessionOAuthAccountList } from "../session/agent-session-types";
44
44
  import { COMPACT_MODES, parseCompactArgs } from "../session/compact-modes";
45
45
  import { resolveResumableSession } from "../session/session-listing";
46
46
  import { formatShakeSummary, type ShakeMode } from "../session/shake-types";
47
+ import { refreshAgentDiscovery } from "../task";
47
48
  import type { ComputerTool } from "../tools/computer";
48
49
  import { computerExposureMode } from "../tools/computer/exposure";
49
50
  import { expandTilde, resolveToCwd } from "../tools/path-utils";
@@ -762,10 +763,12 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
762
763
  if (!runtime.session.modelRegistry.hasConfiguredAuth(resolved.model)) {
763
764
  return usage(`No API key for ${resolved.model.provider}/${resolved.model.id}`, runtime);
764
765
  }
765
- runtime.session.armPrewalk(resolved.model, resolved.thinkingLevel);
766
- await runtime.output(
767
- `Prewalk on: switching to ${resolved.model.provider}/${resolved.model.id} at the next edit/write (todo-gated).`,
768
- );
766
+ const armed = runtime.session.armPrewalk(resolved.model, resolved.thinkingLevel);
767
+ if (armed) {
768
+ await runtime.output(
769
+ `Prewalk on: switching to ${resolved.model.provider}/${resolved.model.id} at the next edit/write (todo-gated).`,
770
+ );
771
+ }
769
772
  return commandConsumed();
770
773
  },
771
774
  },
@@ -3067,14 +3070,15 @@ export const BUILTIN_SLASH_COMMANDS_INTERNAL: ReadonlyArray<SlashCommandSpec> =
3067
3070
 
3068
3071
  /**
3069
3072
  * Reload the interactive session's plugin runtime: invalidate fs/plugin-root
3070
- * caches, rediscover skills and file slash commands, reset the capability
3071
- * cache, and reconnect MCP servers (rebinding the session's MCP tools). Shared
3072
- * by `/reload-plugins`'s TUI handler and the `handle`-adapter's `reloadPlugins`
3073
- * hook so both honor the command's documented MCP reload scope (#7189).
3073
+ * caches, rediscover skills, file slash commands, and task agents, reset the
3074
+ * capability cache, and reconnect MCP servers (rebinding the session's MCP
3075
+ * tools). Shared by `/reload-plugins`'s TUI handler and the `handle`-adapter's
3076
+ * `reloadPlugins` hook so both honor the command's documented reload scope.
3074
3077
  */
3075
3078
  async function reloadTuiPluginState(ctx: InteractiveModeContext): Promise<void> {
3076
3079
  const projectPath = await resolveActiveProjectRegistryPath(ctx.sessionManager.getCwd());
3077
3080
  clearPluginRootsAndCaches(projectPath ? [projectPath] : undefined);
3081
+ await refreshAgentDiscovery(ctx.sessionManager.getCwd());
3078
3082
  await ctx.refreshSkillState();
3079
3083
  await ctx.refreshSlashCommandState();
3080
3084
  resetCapabilities();
@@ -17,6 +17,7 @@ import {
17
17
  formatModelStringWithRouting,
18
18
  resolveAgentPrewalkPattern,
19
19
  resolveConfiguredModelPatterns,
20
+ resolveExplicitModelRole,
20
21
  resolveModelOverride,
21
22
  resolveModelOverrideWithAuthFallback,
22
23
  } from "../config/model-resolver";
@@ -59,6 +60,7 @@ import { buildNamedToolChoice } from "../utils/tool-choice";
59
60
  import type { WorkspaceTree } from "../workspace-tree";
60
61
  import { generateTaskLabel } from "./label";
61
62
  import { resolveAgentPrewalkDefault } from "./prewalk";
63
+ import { isReadOnlyAgent } from "./read-only-policy";
62
64
  import { subprocessToolRegistry } from "./subprocess-tool-registry";
63
65
  import {
64
66
  type AgentDefinition,
@@ -183,11 +185,26 @@ function resolveSubagentRetryFallbackCandidates(
183
185
  return candidates;
184
186
  }
185
187
 
186
- function resolveSubagentDefaultRetryFallbackChain(
188
+ /**
189
+ * Chain a single-model subagent inherits when its own model patterns supply no
190
+ * fallbacks of their own. The child is pinned to a `subagent:<id>` role whose
191
+ * chain shadows every configured role chain (see
192
+ * {@link installSubagentRetryFallbackChain}), so a role-alias request (`@smol`)
193
+ * MUST inherit that role's chain — otherwise the pin silently re-routes the
194
+ * child onto the `default` role's chain. Explicit model selectors keep
195
+ * inheriting `default`: they carry no role identity, and a role that happens to
196
+ * be assigned the same model must not capture the child's fallback routing.
197
+ */
198
+ function resolveSubagentInheritedRetryFallbackChain(
187
199
  settings: Settings,
188
200
  modelRegistry: ModelRegistry,
201
+ modelPatterns: string[],
189
202
  ): string[] | undefined {
190
- const fallbackChain = settings.get("retry.fallbackChains")?.default;
203
+ const configuredChains = settings.get("retry.fallbackChains");
204
+ const role = resolveExplicitModelRole(modelPatterns, settings);
205
+ // An explicitly emptied role chain means "no fallbacks", not "inherit
206
+ // default" — mirrors expandDefaultRetryFallbackChains.
207
+ const fallbackChain = (role !== undefined ? configuredChains?.[role] : undefined) ?? configuredChains?.default;
191
208
  if (
192
209
  !Array.isArray(fallbackChain) ||
193
210
  fallbackChain.length === 0 ||
@@ -206,11 +223,11 @@ function installSubagentRetryFallbackChain(args: {
206
223
  settings: Settings;
207
224
  id: string;
208
225
  candidates: SubagentRetryFallbackCandidate[];
209
- defaultFallbackChain: string[] | undefined;
226
+ inheritedFallbackChain: string[] | undefined;
210
227
  model: Model<Api> | undefined;
211
228
  authFallbackUsed: boolean;
212
229
  }): string | undefined {
213
- const { settings, id, candidates, defaultFallbackChain, model, authFallbackUsed } = args;
230
+ const { settings, id, candidates, inheritedFallbackChain, model, authFallbackUsed } = args;
214
231
  if (!model || authFallbackUsed || candidates.length === 0) return undefined;
215
232
 
216
233
  const selectedIndex = candidates.findIndex(
@@ -219,8 +236,8 @@ function installSubagentRetryFallbackChain(args: {
219
236
  if (selectedIndex < 0) return undefined;
220
237
  const fallbackSelectors = candidates.slice(selectedIndex + 1).map(candidate => candidate.selector);
221
238
  const existingFallbackChains = settings.get("retry.fallbackChains");
222
- // A single explicit model may reuse a configured default chain, but never an implicit parent fallback.
223
- const fallbackChain = fallbackSelectors.length > 0 ? fallbackSelectors : defaultFallbackChain;
239
+ // A single configured model may reuse its role's (or the default) configured chain, but never an implicit parent fallback.
240
+ const fallbackChain = fallbackSelectors.length > 0 ? fallbackSelectors : inheritedFallbackChain;
224
241
  if (
225
242
  !Array.isArray(fallbackChain) ||
226
243
  fallbackChain.length === 0 ||
@@ -344,6 +361,8 @@ export interface ExecutorOptions {
344
361
  */
345
362
  detached?: boolean;
346
363
  modelOverride?: string | string[];
364
+ /** Explicit pre-expansion model role alias selected for this run. */
365
+ modelRole?: string;
347
366
  /**
348
367
  * Active model selector of the parent session, used as an auth-aware fallback
349
368
  * if the resolved subagent model has no working credentials. See #985.
@@ -890,6 +909,8 @@ interface RunMonitorArgs {
890
909
  /** Parent settings for tiny-model label generation. */
891
910
  settings?: Settings;
892
911
  modelOverride?: string | string[];
912
+ /** Explicit pre-expansion model role alias selected for this run. */
913
+ modelRole?: string;
893
914
  signal?: AbortSignal;
894
915
  onProgress?: (progress: AgentProgress) => void;
895
916
  eventBus?: EventBus;
@@ -1005,6 +1026,7 @@ function createSubagentRunMonitor(args: RunMonitorArgs): SubagentRunMonitor {
1005
1026
  cost: 0,
1006
1027
  durationMs: 0,
1007
1028
  modelOverride: args.modelOverride,
1029
+ modelRole: args.modelRole,
1008
1030
  };
1009
1031
 
1010
1032
  const outputChunks: string[] = [];
@@ -2055,6 +2077,8 @@ interface FinalizeRunArgs {
2055
2077
  task: string;
2056
2078
  assignment?: string;
2057
2079
  modelOverride?: string | string[];
2080
+ /** Explicit pre-expansion model role alias selected for this run. */
2081
+ modelRole?: string;
2058
2082
  outputSchema?: unknown;
2059
2083
  outputSchemaMode?: StructuredSubagentSchemaMode;
2060
2084
  outputSchemaSource?: StructuredSubagentSchemaSource;
@@ -2074,7 +2098,7 @@ interface FinalizeRunArgs {
2074
2098
  * event.
2075
2099
  */
2076
2100
  async function finalizeRunResult(args: FinalizeRunArgs): Promise<SingleResult> {
2077
- const { monitor, done, index, id, agent, task, assignment, signal, modelOverride } = args;
2101
+ const { monitor, done, index, id, agent, task, assignment, signal, modelOverride, modelRole } = args;
2078
2102
  const progress = monitor.progress;
2079
2103
  let exitCode = done.exitCode;
2080
2104
  let stderr = done.error ?? "";
@@ -2201,6 +2225,7 @@ async function finalizeRunResult(args: FinalizeRunArgs): Promise<SingleResult> {
2201
2225
  contextTokens: progress.contextTokens,
2202
2226
  contextWindow: progress.contextWindow,
2203
2227
  modelOverride,
2228
+ modelRole,
2204
2229
  resolvedModel: progress.resolvedModel,
2205
2230
  resolvedModelIsFallback: progress.resolvedModelIsFallback,
2206
2231
  error: exitCode !== 0 && stderr ? stderr : undefined,
@@ -2222,6 +2247,8 @@ export interface IrcWakeTurnMonitorOptions {
2222
2247
  agent: AgentDefinition;
2223
2248
  description?: string;
2224
2249
  modelOverride?: string | string[];
2250
+ /** Explicit pre-expansion model role alias selected for this run. */
2251
+ modelRole?: string;
2225
2252
  eventBus?: EventBus;
2226
2253
  parentToolCallId?: string;
2227
2254
  /** Fallback session file when the registry ref carries none. */
@@ -2266,6 +2293,7 @@ export function attachIrcWakeTurnMonitor(session: AgentSession, options: IrcWake
2266
2293
  task: ircTask,
2267
2294
  description: options.description,
2268
2295
  modelOverride: options.modelOverride,
2296
+ modelRole: options.modelRole,
2269
2297
  eventBus: options.eventBus,
2270
2298
  parentToolCallId: options.parentToolCallId,
2271
2299
  detached: true,
@@ -2323,6 +2351,7 @@ export function attachIrcWakeTurnMonitor(session: AgentSession, options: IrcWake
2323
2351
  agent,
2324
2352
  task: ircTask,
2325
2353
  modelOverride: options.modelOverride,
2354
+ modelRole: options.modelRole,
2326
2355
  outputSchema: options.outputSchema,
2327
2356
  outputSchemaMode: options.outputSchemaMode,
2328
2357
  outputSchemaSource: options.outputSchemaSource,
@@ -2391,14 +2420,20 @@ export async function finalizeSubagentLifecycle(args: {
2391
2420
  args.abortKind === "budget" && args.keepAlive && !args.isolated && args.reviveSession !== null;
2392
2421
  if (args.aborted && !resumableAbort) {
2393
2422
  if (ref && ownsRef) {
2394
- // Terminal hard kill: mark `aborted` and detach the session before
2395
- // disposing so the ref satisfies the AgentRef invariant (session null
2396
- // when aborted) ensureLive/hub focus must treat it as terminal, never
2397
- // route into the disposed session.
2398
- registry.setStatus(args.id, "aborted", ref);
2399
- registry.detachSession(args.id, ref);
2423
+ // Route hard kills through the lifecycle owner so the terminal
2424
+ // decision is durable and a restart cannot rediscover the transcript
2425
+ // as a revivable parked agent.
2426
+ try {
2427
+ await AgentLifecycleManager.global().release(args.id, ref, { tombstone: true });
2428
+ } catch (error) {
2429
+ logger.warn("runSubagent: failed to persist kill tombstone", { id: args.id, error: String(error) });
2430
+ registry.setStatus(args.id, "aborted", ref);
2431
+ registry.detachSession(args.id, ref);
2432
+ await disposeSession();
2433
+ }
2434
+ } else {
2435
+ await disposeSession();
2400
2436
  }
2401
- await disposeSession();
2402
2437
  return;
2403
2438
  }
2404
2439
 
@@ -2447,6 +2482,8 @@ export interface FollowUpTurnOptions {
2447
2482
  message: string;
2448
2483
  index?: number;
2449
2484
  description?: string;
2485
+ /** Explicit pre-expansion model role alias retained from the original run. */
2486
+ modelRole?: string;
2450
2487
  /** Structured-output state retained from the original invocation. */
2451
2488
  outputSchema?: unknown;
2452
2489
  outputSchemaMode?: StructuredSubagentSchemaMode;
@@ -2486,6 +2523,7 @@ export async function runSubagentFollowUpTurn(options: FollowUpTurnOptions): Pro
2486
2523
  agent,
2487
2524
  task: message,
2488
2525
  description: options.description,
2526
+ modelRole: options.modelRole,
2489
2527
  signal,
2490
2528
  onProgress: options.onProgress,
2491
2529
  eventBus: options.eventBus,
@@ -2535,6 +2573,7 @@ export async function runSubagentFollowUpTurn(options: FollowUpTurnOptions): Pro
2535
2573
  id,
2536
2574
  agent,
2537
2575
  task: message,
2576
+ modelRole: options.modelRole,
2538
2577
  outputSchema: options.outputSchema,
2539
2578
  outputSchemaMode: options.outputSchemaMode,
2540
2579
  outputSchemaSource: options.outputSchemaSource,
@@ -2561,6 +2600,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
2561
2600
  id,
2562
2601
  worktree,
2563
2602
  modelOverride,
2603
+ modelRole,
2564
2604
  thinkingLevel,
2565
2605
  outputSchema,
2566
2606
  enableLsp,
@@ -2590,6 +2630,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
2590
2630
  tokens: 0,
2591
2631
  requests: 0,
2592
2632
  modelOverride,
2633
+ modelRole,
2593
2634
  error: "Cancelled before start",
2594
2635
  aborted: true,
2595
2636
  abortReason: "Cancelled before start",
@@ -2680,6 +2721,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
2680
2721
  modelRegistry: options.modelRegistry,
2681
2722
  settings,
2682
2723
  modelOverride,
2724
+ modelRole,
2683
2725
  signal,
2684
2726
  onProgress,
2685
2727
  eventBus: options.eventBus,
@@ -2713,6 +2755,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
2713
2755
  agent,
2714
2756
  description: options.description,
2715
2757
  modelOverride,
2758
+ modelRole,
2716
2759
  eventBus: options.eventBus,
2717
2760
  parentToolCallId: options.parentToolCallId,
2718
2761
  sessionFile: subtaskSessionFile,
@@ -2790,9 +2833,9 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
2790
2833
  checkAbort();
2791
2834
 
2792
2835
  const configuredModelPatterns = resolveConfiguredModelPatterns(modelPatterns, settings);
2793
- const defaultRetryFallbackChain =
2836
+ const inheritedRetryFallbackChain =
2794
2837
  configuredModelPatterns.length === 1
2795
- ? resolveSubagentDefaultRetryFallbackChain(subagentSettings, modelRegistry)
2838
+ ? resolveSubagentInheritedRetryFallbackChain(subagentSettings, modelRegistry, modelPatterns)
2796
2839
  : undefined;
2797
2840
  const {
2798
2841
  model,
@@ -2827,7 +2870,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
2827
2870
  settings: subagentSettings,
2828
2871
  id,
2829
2872
  candidates: resolveSubagentRetryFallbackCandidates(modelPatterns, modelRegistry, subagentSettings),
2830
- defaultFallbackChain: defaultRetryFallbackChain,
2873
+ inheritedFallbackChain: inheritedRetryFallbackChain,
2831
2874
  model,
2832
2875
  authFallbackUsed,
2833
2876
  });
@@ -2970,7 +3013,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
2970
3013
  modelPatternFallbackRole:
2971
3014
  model || modelOverride === undefined ? undefined : `${SUBAGENT_RETRY_FALLBACK_ROLE_PREFIX}${id}`,
2972
3015
  modelPatternDefaultFallbackChain:
2973
- model || modelOverride === undefined ? undefined : defaultRetryFallbackChain,
3016
+ model || modelOverride === undefined ? undefined : inheritedRetryFallbackChain,
2974
3017
  thinkingLevel: effectiveThinkingLevel,
2975
3018
  thinkingLevelCeiling: spawnEffortCeiling,
2976
3019
  toolNames,
@@ -3098,6 +3141,10 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
3098
3141
  systemPrompt: session.agent.state.systemPrompt.join("\n\n"),
3099
3142
  task,
3100
3143
  tools: session.getActiveToolNames(),
3144
+ agent: agent.name,
3145
+ modelRole: modelRole ?? resolveExplicitModelRole(modelOverride ?? agent.model, subagentSettings),
3146
+ resolvedModel: progress.resolvedModel,
3147
+ readOnly: isReadOnlyAgent(agent),
3101
3148
  spawns: spawnsEnv,
3102
3149
  readSummarize: agent.readSummarize,
3103
3150
  outputSchema,
@@ -3147,7 +3194,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
3147
3194
  session.sessionManager.appendLabelChange(targetId, label);
3148
3195
  },
3149
3196
  getActiveTools: () => session.getEnabledToolNames(),
3150
- getAllTools: () => session.getAllToolNames(),
3197
+ getAllTools: () => session.getAllToolInfos(),
3151
3198
  setActiveTools: (toolNames: string[]) =>
3152
3199
  session.setActiveToolsByName(toolNames.filter(name => !isParentOwnedTool(name))),
3153
3200
  getCommands: () => getSessionSlashCommands(session),
@@ -3358,7 +3405,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
3358
3405
  const done = await runSubagent();
3359
3406
  monitor.finish();
3360
3407
 
3361
- return finalizeRunResult({
3408
+ const result = await finalizeRunResult({
3362
3409
  monitor,
3363
3410
  done,
3364
3411
  index,
@@ -3367,6 +3414,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
3367
3414
  task,
3368
3415
  assignment,
3369
3416
  modelOverride,
3417
+ modelRole,
3370
3418
  outputSchema,
3371
3419
  outputSchemaMode: options.outputSchemaMode,
3372
3420
  outputSchemaSource: options.outputSchemaSource,
@@ -3378,4 +3426,6 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
3378
3426
  sessionFile: subtaskSessionFile,
3379
3427
  startTime,
3380
3428
  });
3429
+ AgentRegistry.global().setHistory(id, { outputPath: result.outputPath });
3430
+ return result;
3381
3431
  }
package/src/task/index.ts CHANGED
@@ -27,6 +27,7 @@ import { TASK_EFFORTS, type TaskEffort } from "../thinking";
27
27
  import { truncateForPrompt } from "../tools/approval";
28
28
  import { isIrcEnabled } from "../tools/hub";
29
29
  import { formatBytes, formatDuration } from "../tools/render-utils";
30
+ import { isReadOnlyAgent } from "./read-only-policy";
30
31
  import { isScoutSpawnable, resolveSpawnPolicy } from "./spawn-policy";
31
32
  import {
32
33
  type AgentDefinition,
@@ -102,6 +103,7 @@ export { loadBundledAgents as BUNDLED_AGENTS } from "./agents";
102
103
  export { discoverCommands, expandCommand, getCommand } from "./commands";
103
104
  export { discoverAgents, getAgent } from "./discovery";
104
105
  export { AgentOutputManager } from "./output-manager";
106
+ export * from "./read-only-policy";
105
107
  export type {
106
108
  AgentDefinition,
107
109
  AgentProgress,
@@ -119,32 +121,6 @@ export {
119
121
  taskSchema,
120
122
  } from "./types";
121
123
 
122
- // Built-in tools whose approval tier is "read" (see tool classes' `approval`).
123
- // An agent is read-only iff its declared tools are a non-empty subset of this set.
124
- // Fail-safe: any unknown tool makes the agent not read-only.
125
- export const READ_ONLY_TOOL_NAMES: ReadonlySet<string> = new Set([
126
- "read",
127
- "grep",
128
- "glob",
129
- "web_search",
130
- "ast_grep",
131
- "yield",
132
- "hub",
133
- "ask",
134
- "todo",
135
- "recall",
136
- "reflect",
137
- "retain",
138
- "memory_edit",
139
- "inspect_image",
140
- "checkpoint",
141
- "rewind",
142
- ]);
143
-
144
- export function isReadOnlyAgent(agent: AgentDefinition): boolean {
145
- return !!agent.tools?.length && agent.tools.every(tool => READ_ONLY_TOOL_NAMES.has(tool));
146
- }
147
-
148
124
  /**
149
125
  * Preview text for a child result. Falls back to "(no output)" — annotated
150
126
  * with the request count when the child actually did work, so the parent can
@@ -465,16 +441,19 @@ export function composeSpawnAdvisory(args: {
465
441
  class TaskJobError extends Error {}
466
442
 
467
443
  /**
468
- * Process-level memo for create-time agent discovery, keyed by resolved cwd.
444
+ * Process-level create-time discovery memo and published reload snapshots,
445
+ * keyed by resolved cwd.
469
446
  *
470
447
  * `TaskTool.create` runs for every (sub)agent session in this process and the
471
448
  * walk-up + plugin-registry scan in `discoverAgents` is identical for a given
472
- * cwd, so repeat creations reuse the first scan. Execution-time discovery
473
- * (`#runSpawn`) intentionally stays fresh. The memo also tracks the live
474
- * `discoverAgents` binding: test spies swap that binding, which invalidates
475
- * the memo automatically.
449
+ * cwd, so repeat creations reuse the first scan. Explicit plugin reloads
450
+ * replace the matching snapshot so already-created tools advertise the latest
451
+ * definitions. Execution-time discovery (`#runSpawn`) intentionally stays
452
+ * fresh. The memo also tracks the live `discoverAgents` binding: test spies
453
+ * swap that binding, which invalidates both caches automatically.
476
454
  */
477
455
  const discoveryMemo = new Map<string, Promise<DiscoveryResult>>();
456
+ const discoverySnapshots = new Map<string, AgentDefinition[]>();
478
457
  let discoveryMemoFn: typeof discoverAgents | undefined;
479
458
 
480
459
  function discoverAgentsForCreate(cwd: string): Promise<DiscoveryResult> {
@@ -482,6 +461,7 @@ function discoverAgentsForCreate(cwd: string): Promise<DiscoveryResult> {
482
461
  if (discoveryMemoFn !== fn) {
483
462
  discoveryMemoFn = fn;
484
463
  discoveryMemo.clear();
464
+ discoverySnapshots.clear();
485
465
  }
486
466
  const key = path.resolve(cwd);
487
467
  let pending = discoveryMemo.get(key);
@@ -495,6 +475,17 @@ function discoverAgentsForCreate(cwd: string): Promise<DiscoveryResult> {
495
475
  return pending;
496
476
  }
497
477
 
478
+ /** Rescan one cwd and publish its definitions to existing and future task tools. */
479
+ export async function refreshAgentDiscovery(cwd: string): Promise<void> {
480
+ const key = path.resolve(cwd);
481
+ discoveryMemo.delete(key);
482
+ const pending = discoverAgentsForCreate(cwd);
483
+ const { agents } = await pending;
484
+ if (discoveryMemo.get(key) === pending) {
485
+ discoverySnapshots.set(key, agents);
486
+ }
487
+ }
488
+
498
489
  // ═══════════════════════════════════════════════════════════════════════════
499
490
  // Tool Class
500
491
  // ═══════════════════════════════════════════════════════════════════════════
@@ -611,7 +602,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
611
602
  const planMode = this.session.getPlanModeState?.()?.enabled === true;
612
603
  const isolationMode = this.session.settings.get("task.isolation.mode");
613
604
  return renderDescription({
614
- agents: this.#discoveredAgents,
605
+ agents: discoverySnapshots.get(path.resolve(this.session.cwd)) ?? this.#discoveredAgents,
615
606
  isolationEnabled: !planMode && isolationMode !== "none",
616
607
  applyIsolatedChanges: this.session.settings.get("task.isolation.apply"),
617
608
  disabledAgents,
@@ -860,6 +851,7 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
860
851
  id: agentId,
861
852
  agent: agentType,
862
853
  agentSource,
854
+ modelRole: policy.modelRole,
863
855
  status: "pending",
864
856
  task: renderSubagentUserPrompt(assignment),
865
857
  assignment,
@@ -1143,8 +1135,10 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1143
1135
  // polling row reflects the resolved model, reasoning level,
1144
1136
  // and running counters without reverting the "running"
1145
1137
  // status back to the subagent's initial "pending" snapshot.
1138
+ progress.modelRole = nextProgress.modelRole ?? progress.modelRole;
1146
1139
  progress.resolvedModel = nextProgress.resolvedModel;
1147
- progress.resolvedModelIsFallback = nextProgress.resolvedModelIsFallback;
1140
+ progress.resolvedModelIsFallback =
1141
+ nextProgress.resolvedModelIsFallback ?? progress.resolvedModelIsFallback;
1148
1142
  progress.tokens = nextProgress.tokens;
1149
1143
  progress.requests = nextProgress.requests;
1150
1144
  progress.contextTokens = nextProgress.contextTokens;
@@ -1187,9 +1181,11 @@ export class TaskTool implements AgentTool<TaskToolSchemaInstance, TaskToolDetai
1187
1181
  progress.extractedToolData = singleResult?.extractedToolData;
1188
1182
  progress.retryFailure = singleResult?.retryFailure;
1189
1183
  progress.retryState = undefined;
1184
+ progress.modelRole = singleResult?.modelRole ?? progress.modelRole;
1190
1185
  if (singleResult?.resolvedModel) {
1191
1186
  progress.resolvedModel = singleResult.resolvedModel;
1192
- progress.resolvedModelIsFallback = singleResult.resolvedModelIsFallback;
1187
+ progress.resolvedModelIsFallback =
1188
+ singleResult.resolvedModelIsFallback ?? progress.resolvedModelIsFallback;
1193
1189
  } else {
1194
1190
  delete progress.resolvedModel;
1195
1191
  delete progress.resolvedModelIsFallback;
@@ -20,6 +20,7 @@
20
20
  */
21
21
  import * as path from "node:path";
22
22
  import type * as natives from "@oh-my-pi/pi-natives";
23
+ import { AgentRegistry } from "../registry/agent-registry";
23
24
  import type { ToolSession } from "../tools";
24
25
  import { generateCommitMessage } from "../utils/commit-message-generator";
25
26
  import * as git from "../utils/git";
@@ -44,6 +45,15 @@ import {
44
45
 
45
46
  type IsoBackendKind = natives.IsoBackendKind;
46
47
 
48
+ function rememberAgentArtifacts(result: SingleResult): SingleResult {
49
+ AgentRegistry.global().setHistory(result.id, {
50
+ outputPath: result.outputPath,
51
+ patchPath: result.patchPath,
52
+ branchName: result.branchName,
53
+ });
54
+ return result;
55
+ }
56
+
47
57
  /** Resolved repo + baseline used by every isolated spawn in a single call. */
48
58
  export interface IsolationContext {
49
59
  repoRoot: string;
@@ -172,14 +182,14 @@ export async function runIsolatedSubprocess(opts: IsolatedRunOptions): Promise<S
172
182
  opts.description,
173
183
  opts.buildCommitMessage?.(),
174
184
  );
175
- return {
185
+ return rememberAgentArtifacts({
176
186
  ...result,
177
187
  branchName: commitResult?.branchName,
178
188
  branchBaseSha: commitResult?.baseSha,
179
189
  nestedPatches: commitResult?.nestedPatches,
180
- };
190
+ });
181
191
  } catch (mergeErr) {
182
- // Agent succeeded but branch commit failed — clean up stale branch
192
+ // Agent succeeded but branch commit failed — clean up stale branch.
183
193
  const branchName = `omp/task/${opts.agentId}`;
184
194
  await git.branch.tryDelete(opts.context.repoRoot, branchName);
185
195
  const msg = mergeErr instanceof Error ? mergeErr.message : String(mergeErr);
@@ -190,34 +200,37 @@ export async function runIsolatedSubprocess(opts: IsolatedRunOptions): Promise<S
190
200
  opts.artifactsDir,
191
201
  opts.agentId,
192
202
  );
193
- return {
203
+ return rememberAgentArtifacts({
194
204
  ...result,
195
205
  patchPath: patchResult.patchPath,
196
206
  nestedPatches: patchResult.nestedPatches,
197
207
  error: `Merge failed: ${msg}`,
198
- };
208
+ });
199
209
  } catch (patchErr) {
200
210
  const patchMsg = patchErr instanceof Error ? patchErr.message : String(patchErr);
201
- return { ...result, error: `Merge failed: ${msg}; patch capture failed: ${patchMsg}` };
211
+ return rememberAgentArtifacts({
212
+ ...result,
213
+ error: `Merge failed: ${msg}; patch capture failed: ${patchMsg}`,
214
+ });
202
215
  }
203
216
  }
204
217
  }
205
218
  if (result.exitCode === 0) {
206
219
  try {
207
220
  const patchResult = await writeIsolationPatch(isolationDir, taskBaseline, opts.artifactsDir, opts.agentId);
208
- return {
221
+ return rememberAgentArtifacts({
209
222
  ...result,
210
223
  patchPath: patchResult.patchPath,
211
224
  nestedPatches: patchResult.nestedPatches,
212
- };
225
+ });
213
226
  } catch (patchErr) {
214
227
  const msg = patchErr instanceof Error ? patchErr.message : String(patchErr);
215
- return { ...result, error: `Patch capture failed: ${msg}` };
228
+ return rememberAgentArtifacts({ ...result, error: `Patch capture failed: ${msg}` });
216
229
  }
217
230
  }
218
- return result;
231
+ return rememberAgentArtifacts(result);
219
232
  } catch (err) {
220
- return opts.buildFailureResult(err);
233
+ return rememberAgentArtifacts(opts.buildFailureResult(err));
221
234
  } finally {
222
235
  if (handle) {
223
236
  const isolationHandle = handle;
@@ -1,6 +1,6 @@
1
1
  import * as fs from "node:fs/promises";
2
-
3
2
  import type { ModelRegistry } from "../config/model-registry";
3
+ import { formatModelRoleAlias } from "../config/model-roles";
4
4
  import type { Settings } from "../config/settings";
5
5
  import { MCPManager } from "../mcp/manager";
6
6
  import type { PersistedSubagentReviverFactory } from "../registry/agent-lifecycle";
@@ -79,6 +79,14 @@ export function createPersistedSubagentReviverFactory(
79
79
  taskDepth++;
80
80
  parentId = registry.get(parentId)?.parentId;
81
81
  }
82
+ const subagentSettings = createSubagentSettings(
83
+ ctx.settings,
84
+ init.readSummarize === false ? { "read.summarize.enabled": false } : undefined,
85
+ );
86
+ const persistedModelPattern =
87
+ init.modelRole && init.modelRole !== "default"
88
+ ? [formatModelRoleAlias(init.modelRole), ...(init.resolvedModel ? [init.resolvedModel] : [])]
89
+ : init.resolvedModel;
82
90
  return async expectedRef => {
83
91
  // Re-open fresh on every revive: park closes the writer, so this takes
84
92
  // the single-writer lock cleanly and restores the full message history.
@@ -96,10 +104,9 @@ export function createPersistedSubagentReviverFactory(
96
104
  cwd: ctx.session.sessionManager.getCwd(),
97
105
  authStorage: ctx.authStorage,
98
106
  modelRegistry: ctx.modelRegistry,
99
- settings: createSubagentSettings(
100
- ctx.settings,
101
- init.readSummarize === false ? { "read.summarize.enabled": false } : undefined,
102
- ),
107
+ ...(persistedModelPattern ? { modelPattern: persistedModelPattern } : {}),
108
+ modelPatternAuthFallback: init.resolvedModel,
109
+ settings: subagentSettings,
103
110
  sessionManager: reopened,
104
111
  agentId: ref.id,
105
112
  agentDisplayName: ref.displayName,
@@ -0,0 +1,27 @@
1
+ import type { AgentDefinition } from "./types";
2
+
3
+ // Built-in tools whose approval tier is "read" (see tool classes' `approval`).
4
+ // An agent is read-only iff its declared tools are a non-empty subset of this set.
5
+ // Fail-safe: any unknown tool makes the agent not read-only.
6
+ export const READ_ONLY_TOOL_NAMES: ReadonlySet<string> = new Set([
7
+ "read",
8
+ "grep",
9
+ "glob",
10
+ "web_search",
11
+ "ast_grep",
12
+ "yield",
13
+ "hub",
14
+ "ask",
15
+ "todo",
16
+ "recall",
17
+ "reflect",
18
+ "retain",
19
+ "memory_edit",
20
+ "inspect_image",
21
+ "checkpoint",
22
+ "rewind",
23
+ ]);
24
+
25
+ export function isReadOnlyAgent(agent: AgentDefinition): boolean {
26
+ return !!agent.tools?.length && agent.tools.every(tool => READ_ONLY_TOOL_NAMES.has(tool));
27
+ }