apex-code 0.5.1 → 0.6.0

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 (86) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +41 -2
  3. package/dist/core/agent-session.d.ts +37 -0
  4. package/dist/core/agent-session.d.ts.map +1 -1
  5. package/dist/core/agent-session.js +264 -60
  6. package/dist/core/agent-session.js.map +1 -1
  7. package/dist/core/default-tool-names.d.ts +7 -0
  8. package/dist/core/default-tool-names.d.ts.map +1 -0
  9. package/dist/core/default-tool-names.js +14 -0
  10. package/dist/core/default-tool-names.js.map +1 -0
  11. package/dist/core/extensions/runner.d.ts.map +1 -1
  12. package/dist/core/extensions/runner.js +2 -0
  13. package/dist/core/extensions/runner.js.map +1 -1
  14. package/dist/core/extensions/types.d.ts +3 -0
  15. package/dist/core/extensions/types.d.ts.map +1 -1
  16. package/dist/core/extensions/types.js.map +1 -1
  17. package/dist/core/keybindings.d.ts +11 -1
  18. package/dist/core/keybindings.d.ts.map +1 -1
  19. package/dist/core/keybindings.js +6 -1
  20. package/dist/core/keybindings.js.map +1 -1
  21. package/dist/core/permissions/startup.d.ts +1 -1
  22. package/dist/core/permissions/startup.d.ts.map +1 -1
  23. package/dist/core/permissions/startup.js.map +1 -1
  24. package/dist/core/sdk.d.ts.map +1 -1
  25. package/dist/core/sdk.js +13 -9
  26. package/dist/core/sdk.js.map +1 -1
  27. package/dist/core/settings-manager.d.ts +4 -0
  28. package/dist/core/settings-manager.d.ts.map +1 -1
  29. package/dist/core/settings-manager.js +13 -0
  30. package/dist/core/settings-manager.js.map +1 -1
  31. package/dist/core/slash-commands.d.ts.map +1 -1
  32. package/dist/core/slash-commands.js +1 -0
  33. package/dist/core/slash-commands.js.map +1 -1
  34. package/dist/core/tools/contract.d.ts +1 -0
  35. package/dist/core/tools/contract.d.ts.map +1 -1
  36. package/dist/core/tools/contract.js.map +1 -1
  37. package/dist/core/tools/index.d.ts +1 -1
  38. package/dist/core/tools/index.d.ts.map +1 -1
  39. package/dist/core/tools/index.js.map +1 -1
  40. package/dist/core/tools/plan-present.d.ts +13 -12
  41. package/dist/core/tools/plan-present.d.ts.map +1 -1
  42. package/dist/core/tools/plan-present.js +32 -19
  43. package/dist/core/tools/plan-present.js.map +1 -1
  44. package/dist/core/tools/renderers/index.d.ts +1 -1
  45. package/dist/core/tools/renderers/index.d.ts.map +1 -1
  46. package/dist/core/tools/renderers/index.js +2 -0
  47. package/dist/core/tools/renderers/index.js.map +1 -1
  48. package/dist/core/tools/renderers/todo-write.d.ts +7 -0
  49. package/dist/core/tools/renderers/todo-write.d.ts.map +1 -0
  50. package/dist/core/tools/renderers/todo-write.js +58 -0
  51. package/dist/core/tools/renderers/todo-write.js.map +1 -0
  52. package/dist/index.d.ts +2 -2
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +1 -1
  55. package/dist/index.js.map +1 -1
  56. package/dist/modes/interactive/components/first-use-hints.d.ts +1 -1
  57. package/dist/modes/interactive/components/first-use-hints.d.ts.map +1 -1
  58. package/dist/modes/interactive/components/first-use-hints.js +1 -0
  59. package/dist/modes/interactive/components/first-use-hints.js.map +1 -1
  60. package/dist/modes/interactive/components/footer.d.ts +3 -1
  61. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  62. package/dist/modes/interactive/components/footer.js +25 -11
  63. package/dist/modes/interactive/components/footer.js.map +1 -1
  64. package/dist/modes/interactive/components/settings-selector.d.ts +5 -0
  65. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  66. package/dist/modes/interactive/components/settings-selector.js +20 -1
  67. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  68. package/dist/modes/interactive/components/task-panel.d.ts +27 -0
  69. package/dist/modes/interactive/components/task-panel.d.ts.map +1 -0
  70. package/dist/modes/interactive/components/task-panel.js +92 -0
  71. package/dist/modes/interactive/components/task-panel.js.map +1 -0
  72. package/dist/modes/interactive/components/turn-summary.d.ts +6 -0
  73. package/dist/modes/interactive/components/turn-summary.d.ts.map +1 -0
  74. package/dist/modes/interactive/components/turn-summary.js +12 -0
  75. package/dist/modes/interactive/components/turn-summary.js.map +1 -0
  76. package/dist/modes/interactive/interactive-mode.d.ts +9 -0
  77. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  78. package/dist/modes/interactive/interactive-mode.js +162 -11
  79. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  80. package/dist/package-manager-cli.d.ts.map +1 -1
  81. package/dist/package-manager-cli.js +31 -72
  82. package/dist/package-manager-cli.js.map +1 -1
  83. package/docs/keybindings.md +19 -1
  84. package/docs/settings.md +8 -0
  85. package/npm-shrinkwrap.json +5 -5
  86. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"keybindings.js","sourceRoot":"","sources":["../../src/core/keybindings.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Keybinding,\n\ttype KeybindingDefinitions,\n\ttype KeybindingsConfig,\n\ttype KeyId,\n\tTUI_KEYBINDINGS,\n\tKeybindingsManager as TuiKeybindingsManager,\n} from \"@earendil-works/pi-tui\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.ts\";\nimport { stripBom } from \"../utils/text.ts\";\n\nexport interface AppKeybindings {\n\t\"app.interrupt\": true;\n\t\"app.clear\": true;\n\t\"app.exit\": true;\n\t\"app.suspend\": true;\n\t\"app.thinking.cycle\": true;\n\t\"app.thinking.save\": true;\n\t\"app.model.cycleForward\": true;\n\t\"app.model.cycleBackward\": true;\n\t\"app.model.select\": true;\n\t\"app.tools.expand\": true;\n\t\"app.thinking.toggle\": true;\n\t\"app.session.toggleNamedFilter\": true;\n\t\"app.editor.external\": true;\n\t\"app.message.copy\": true;\n\t\"app.message.followUp\": true;\n\t\"app.message.dequeue\": true;\n\t\"app.clipboard.pasteImage\": true;\n\t\"app.session.new\": true;\n\t\"app.session.tree\": true;\n\t\"app.session.fork\": true;\n\t\"app.session.resume\": true;\n\t\"app.tree.foldOrUp\": true;\n\t\"app.tree.unfoldOrDown\": true;\n\t\"app.tree.editLabel\": true;\n\t\"app.tree.toggleLabelTimestamp\": true;\n\t\"app.session.togglePath\": true;\n\t\"app.session.toggleSort\": true;\n\t\"app.session.rename\": true;\n\t\"app.session.delete\": true;\n\t\"app.session.deleteNoninvasive\": true;\n\t\"app.models.save\": true;\n\t\"app.models.enableAll\": true;\n\t\"app.models.clearAll\": true;\n\t\"app.models.toggleProvider\": true;\n\t\"app.models.reorderUp\": true;\n\t\"app.models.reorderDown\": true;\n\t\"app.tree.filter.default\": true;\n\t\"app.tree.filter.noTools\": true;\n\t\"app.tree.filter.userOnly\": true;\n\t\"app.tree.filter.labeledOnly\": true;\n\t\"app.tree.filter.all\": true;\n\t\"app.tree.filter.cycleForward\": true;\n\t\"app.tree.filter.cycleBackward\": true;\n}\n\nexport type AppKeybinding = keyof AppKeybindings;\n\nexport function useWindowsKeybindings(\n\tplatform: NodeJS.Platform = process.platform,\n\tenv: NodeJS.ProcessEnv = process.env,\n): boolean {\n\treturn platform === \"win32\" || (platform === \"linux\" && Boolean(env.WSL_DISTRO_NAME || env.WSL_INTEROP));\n}\n\ndeclare module \"@earendil-works/pi-tui\" {\n\tinterface Keybindings extends AppKeybindings {}\n}\n\nconst windowsKeybindings = useWindowsKeybindings();\n\nexport const KEYBINDINGS = {\n\t...TUI_KEYBINDINGS,\n\t\"tui.editor.undo\": {\n\t\t...TUI_KEYBINDINGS[\"tui.editor.undo\"],\n\t\tdefaultKeys: process.platform === \"win32\" ? \"ctrl+z\" : windowsKeybindings ? \"alt+z\" : \"ctrl+-\",\n\t},\n\t\"tui.altScreen.previousPrompt\": {\n\t\t...TUI_KEYBINDINGS[\"tui.altScreen.previousPrompt\"],\n\t\tdefaultKeys: windowsKeybindings ? \"ctrl+up\" : [\"ctrl+shift+up\", \"ctrl+up\"],\n\t},\n\t\"tui.altScreen.nextPrompt\": {\n\t\t...TUI_KEYBINDINGS[\"tui.altScreen.nextPrompt\"],\n\t\tdefaultKeys: windowsKeybindings ? \"ctrl+down\" : [\"ctrl+shift+down\", \"ctrl+down\"],\n\t},\n\t\"tui.altScreen.search\": {\n\t\t...TUI_KEYBINDINGS[\"tui.altScreen.search\"],\n\t\tdefaultKeys: windowsKeybindings ? \"ctrl+f\" : \"ctrl+shift+f\",\n\t},\n\t\"app.interrupt\": { defaultKeys: \"escape\", description: \"Cancel or abort\" },\n\t\"app.clear\": { defaultKeys: \"ctrl+c\", description: \"Clear editor\" },\n\t\"app.exit\": { defaultKeys: \"ctrl+d\", description: \"Exit when editor is empty\" },\n\t\"app.suspend\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? [] : \"ctrl+z\",\n\t\tdescription: \"Suspend to background\",\n\t},\n\t\"app.thinking.cycle\": {\n\t\tdefaultKeys: \"shift+tab\",\n\t\tdescription: \"Cycle thinking level\",\n\t},\n\t\"app.thinking.save\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Save thinking level\",\n\t},\n\t\"app.model.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Cycle to next model\",\n\t},\n\t\"app.model.cycleBackward\": {\n\t\tdefaultKeys: windowsKeybindings ? \"alt+p\" : \"shift+ctrl+p\",\n\t\tdescription: \"Cycle to previous model\",\n\t},\n\t\"app.model.select\": { defaultKeys: \"ctrl+l\", description: \"Open model selector\" },\n\t\"app.tools.expand\": { defaultKeys: \"ctrl+o\", description: \"Cycle conversation detail\" },\n\t\"app.thinking.toggle\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Toggle thinking blocks\",\n\t},\n\t\"app.session.toggleNamedFilter\": {\n\t\tdefaultKeys: \"ctrl+n\",\n\t\tdescription: \"Toggle named session filter\",\n\t},\n\t\"app.editor.external\": {\n\t\tdefaultKeys: \"ctrl+g\",\n\t\tdescription: \"Open external editor\",\n\t},\n\t\"app.message.copy\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Copy selection or last assistant message\",\n\t},\n\t\"app.message.followUp\": {\n\t\tdefaultKeys: windowsKeybindings ? \"ctrl+q\" : \"alt+enter\",\n\t\tdescription: \"Queue follow-up message\",\n\t},\n\t\"app.message.dequeue\": {\n\t\tdefaultKeys: windowsKeybindings ? \"alt+q\" : \"alt+up\",\n\t\tdescription: \"Restore queued messages\",\n\t},\n\t\"app.clipboard.pasteImage\": {\n\t\tdefaultKeys: windowsKeybindings ? \"alt+v\" : \"ctrl+v\",\n\t\tdescription: \"Paste image from clipboard (text fallback)\",\n\t},\n\t\"app.session.new\": { defaultKeys: [], description: \"Start a new session\" },\n\t\"app.session.tree\": { defaultKeys: [], description: \"Open session tree\" },\n\t\"app.session.fork\": { defaultKeys: [], description: \"Fork current session\" },\n\t\"app.session.resume\": { defaultKeys: [], description: \"Resume a session\" },\n\t\"app.tree.foldOrUp\": {\n\t\tdefaultKeys: process.platform === \"darwin\" ? [\"alt+left\", \"ctrl+left\"] : [\"ctrl+left\", \"alt+left\"],\n\t\tdescription: \"Fold tree branch or move up\",\n\t},\n\t\"app.tree.unfoldOrDown\": {\n\t\tdefaultKeys: process.platform === \"darwin\" ? [\"alt+right\", \"ctrl+right\"] : [\"ctrl+right\", \"alt+right\"],\n\t\tdescription: \"Unfold tree branch or move down\",\n\t},\n\t\"app.tree.editLabel\": {\n\t\tdefaultKeys: \"shift+l\",\n\t\tdescription: \"Edit tree label\",\n\t},\n\t\"app.tree.toggleLabelTimestamp\": {\n\t\tdefaultKeys: \"shift+t\",\n\t\tdescription: \"Toggle tree label timestamps\",\n\t},\n\t\"app.session.togglePath\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle session path display\",\n\t},\n\t\"app.session.toggleSort\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Toggle session sort mode\",\n\t},\n\t\"app.session.rename\": {\n\t\tdefaultKeys: \"ctrl+r\",\n\t\tdescription: \"Rename session\",\n\t},\n\t\"app.session.delete\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Delete session\",\n\t},\n\t\"app.session.deleteNoninvasive\": {\n\t\tdefaultKeys: \"ctrl+backspace\",\n\t\tdescription: \"Delete session when query is empty\",\n\t},\n\t\"app.models.save\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Save model selection\",\n\t},\n\t\"app.models.enableAll\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Enable all models\",\n\t},\n\t\"app.models.clearAll\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Clear all models\",\n\t},\n\t\"app.models.toggleProvider\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle all models for provider\",\n\t},\n\t\"app.models.reorderUp\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Move model up in order\",\n\t},\n\t\"app.models.reorderDown\": {\n\t\tdefaultKeys: \"alt+down\",\n\t\tdescription: \"Move model down in order\",\n\t},\n\t\"app.tree.filter.default\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Tree filter: default view\",\n\t},\n\t\"app.tree.filter.noTools\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Tree filter: hide tool results\",\n\t},\n\t\"app.tree.filter.userOnly\": {\n\t\tdefaultKeys: \"ctrl+u\",\n\t\tdescription: \"Tree filter: user messages only\",\n\t},\n\t\"app.tree.filter.labeledOnly\": {\n\t\tdefaultKeys: \"ctrl+l\",\n\t\tdescription: \"Tree filter: labeled entries only\",\n\t},\n\t\"app.tree.filter.all\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Tree filter: show all entries\",\n\t},\n\t\"app.tree.filter.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+o\",\n\t\tdescription: \"Tree filter: cycle forward\",\n\t},\n\t\"app.tree.filter.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+o\",\n\t\tdescription: \"Tree filter: cycle backward\",\n\t},\n} as const satisfies KeybindingDefinitions;\n\nconst KEYBINDING_NAME_MIGRATIONS = {\n\tcursorUp: \"tui.editor.cursorUp\",\n\tcursorDown: \"tui.editor.cursorDown\",\n\tcursorLeft: \"tui.editor.cursorLeft\",\n\tcursorRight: \"tui.editor.cursorRight\",\n\tcursorWordLeft: \"tui.editor.cursorWordLeft\",\n\tcursorWordRight: \"tui.editor.cursorWordRight\",\n\tcursorLineStart: \"tui.editor.cursorLineStart\",\n\tcursorLineEnd: \"tui.editor.cursorLineEnd\",\n\tjumpForward: \"tui.editor.jumpForward\",\n\tjumpBackward: \"tui.editor.jumpBackward\",\n\tpageUp: \"tui.editor.pageUp\",\n\tpageDown: \"tui.editor.pageDown\",\n\tdeleteCharBackward: \"tui.editor.deleteCharBackward\",\n\tdeleteCharForward: \"tui.editor.deleteCharForward\",\n\tdeleteWordBackward: \"tui.editor.deleteWordBackward\",\n\tdeleteWordForward: \"tui.editor.deleteWordForward\",\n\tdeleteToLineStart: \"tui.editor.deleteToLineStart\",\n\tdeleteToLineEnd: \"tui.editor.deleteToLineEnd\",\n\tyank: \"tui.editor.yank\",\n\tyankPop: \"tui.editor.yankPop\",\n\tundo: \"tui.editor.undo\",\n\tnewLine: \"tui.input.newLine\",\n\tsubmit: \"tui.input.submit\",\n\ttab: \"tui.input.tab\",\n\tcopy: \"tui.input.copy\",\n\tselectUp: \"tui.select.up\",\n\tselectDown: \"tui.select.down\",\n\tselectPageUp: \"tui.select.pageUp\",\n\tselectPageDown: \"tui.select.pageDown\",\n\tselectConfirm: \"tui.select.confirm\",\n\tselectCancel: \"tui.select.cancel\",\n\tinterrupt: \"app.interrupt\",\n\tclear: \"app.clear\",\n\texit: \"app.exit\",\n\tsuspend: \"app.suspend\",\n\tcycleThinkingLevel: \"app.thinking.cycle\",\n\tcycleModelForward: \"app.model.cycleForward\",\n\tcycleModelBackward: \"app.model.cycleBackward\",\n\tselectModel: \"app.model.select\",\n\texpandTools: \"app.tools.expand\",\n\ttoggleThinking: \"app.thinking.toggle\",\n\ttoggleSessionNamedFilter: \"app.session.toggleNamedFilter\",\n\texternalEditor: \"app.editor.external\",\n\tfollowUp: \"app.message.followUp\",\n\tdequeue: \"app.message.dequeue\",\n\tpasteImage: \"app.clipboard.pasteImage\",\n\tnewSession: \"app.session.new\",\n\ttree: \"app.session.tree\",\n\tfork: \"app.session.fork\",\n\tresume: \"app.session.resume\",\n\ttreeFoldOrUp: \"app.tree.foldOrUp\",\n\ttreeUnfoldOrDown: \"app.tree.unfoldOrDown\",\n\ttreeEditLabel: \"app.tree.editLabel\",\n\ttreeToggleLabelTimestamp: \"app.tree.toggleLabelTimestamp\",\n\ttoggleSessionPath: \"app.session.togglePath\",\n\ttoggleSessionSort: \"app.session.toggleSort\",\n\trenameSession: \"app.session.rename\",\n\tdeleteSession: \"app.session.delete\",\n\tdeleteSessionNoninvasive: \"app.session.deleteNoninvasive\",\n} as const satisfies Record<string, Keybinding>;\n\nfunction isLegacyKeybindingName(key: string): key is keyof typeof KEYBINDING_NAME_MIGRATIONS {\n\treturn key in KEYBINDING_NAME_MIGRATIONS;\n}\n\nfunction toKeybindingsConfig(value: Record<string, unknown>): KeybindingsConfig {\n\tconst config: KeybindingsConfig = {};\n\tfor (const [key, binding] of Object.entries(value)) {\n\t\tif (typeof binding === \"string\") {\n\t\t\tconfig[key] = binding as KeyId;\n\t\t\tcontinue;\n\t\t}\n\t\tif (Array.isArray(binding) && binding.every((entry) => typeof entry === \"string\")) {\n\t\t\tconfig[key] = binding as KeyId[];\n\t\t}\n\t}\n\treturn config;\n}\n\nexport function migrateKeybindingsConfig(rawConfig: Record<string, unknown>): {\n\tconfig: Record<string, unknown>;\n\tmigrated: boolean;\n} {\n\tconst config: Record<string, unknown> = {};\n\tlet migrated = false;\n\n\tfor (const [key, value] of Object.entries(rawConfig)) {\n\t\tconst nextKey = isLegacyKeybindingName(key) ? KEYBINDING_NAME_MIGRATIONS[key] : key;\n\t\tif (nextKey !== key) {\n\t\t\tmigrated = true;\n\t\t}\n\t\tif (key !== nextKey && Object.hasOwn(rawConfig, nextKey)) {\n\t\t\tmigrated = true;\n\t\t\tcontinue;\n\t\t}\n\t\tconfig[nextKey] = value;\n\t}\n\n\treturn { config: orderKeybindingsConfig(config), migrated };\n}\n\nfunction orderKeybindingsConfig(config: Record<string, unknown>): Record<string, unknown> {\n\tconst ordered: Record<string, unknown> = {};\n\tfor (const keybinding of Object.keys(KEYBINDINGS)) {\n\t\tif (Object.hasOwn(config, keybinding)) {\n\t\t\tordered[keybinding] = config[keybinding];\n\t\t}\n\t}\n\n\tconst extras = Object.keys(config)\n\t\t.filter((key) => !Object.hasOwn(ordered, key))\n\t\t.sort();\n\tfor (const key of extras) {\n\t\tordered[key] = config[key];\n\t}\n\n\treturn ordered;\n}\n\nfunction loadRawConfig(path: string): Record<string, unknown> | undefined {\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst parsed = JSON.parse(stripBom(readFileSync(path, \"utf-8\"))) as unknown;\n\t\tif (typeof parsed !== \"object\" || parsed === null) return undefined;\n\t\treturn parsed as Record<string, unknown>;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport class KeybindingsManager extends TuiKeybindingsManager {\n\tprivate configPath: string | undefined;\n\n\tconstructor(userBindings: KeybindingsConfig = {}, configPath?: string) {\n\t\tsuper(KEYBINDINGS, userBindings);\n\t\tthis.configPath = configPath;\n\t}\n\n\tstatic create(agentDir: string = getAgentDir()): KeybindingsManager {\n\t\tconst configPath = join(agentDir, \"keybindings.json\");\n\t\tconst userBindings = KeybindingsManager.loadFromFile(configPath);\n\t\treturn new KeybindingsManager(userBindings, configPath);\n\t}\n\n\treload(): void {\n\t\tif (!this.configPath) return;\n\t\tthis.setUserBindings(KeybindingsManager.loadFromFile(this.configPath));\n\t}\n\n\tgetEffectiveConfig(): KeybindingsConfig {\n\t\treturn this.getResolvedBindings();\n\t}\n\n\tprivate static loadFromFile(path: string): KeybindingsConfig {\n\t\tconst rawConfig = loadRawConfig(path);\n\t\tif (!rawConfig) return {};\n\t\treturn toKeybindingsConfig(migrateKeybindingsConfig(rawConfig).config);\n\t}\n}\n\nexport type { Keybinding, KeybindingsConfig, KeyId };\n"]}
1
+ {"version":3,"file":"keybindings.js","sourceRoot":"","sources":["../../src/core/keybindings.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype Keybinding,\n\ttype KeybindingDefinitions,\n\ttype KeybindingsConfig,\n\ttype KeyId,\n\tTUI_KEYBINDINGS,\n\tKeybindingsManager as TuiKeybindingsManager,\n} from \"@earendil-works/pi-tui\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { getAgentDir } from \"../config.ts\";\nimport { stripBom } from \"../utils/text.ts\";\n\nexport interface AppKeybindings {\n\t\"app.interrupt\": true;\n\t\"app.clear\": true;\n\t\"app.exit\": true;\n\t\"app.suspend\": true;\n\t\"app.thinking.cycle\": true;\n\t\"app.permissionMode.cycle\": true;\n\t\"app.thinking.save\": true;\n\t\"app.model.cycleForward\": true;\n\t\"app.model.cycleBackward\": true;\n\t\"app.model.select\": true;\n\t\"app.tools.expand\": true;\n\t\"app.thinking.toggle\": true;\n\t\"app.tasks.toggle\": true;\n\t\"app.session.toggleNamedFilter\": true;\n\t\"app.editor.external\": true;\n\t\"app.message.copy\": true;\n\t\"app.message.followUp\": true;\n\t\"app.message.dequeue\": true;\n\t\"app.clipboard.pasteImage\": true;\n\t\"app.session.new\": true;\n\t\"app.session.tree\": true;\n\t\"app.session.fork\": true;\n\t\"app.session.resume\": true;\n\t\"app.tree.foldOrUp\": true;\n\t\"app.tree.unfoldOrDown\": true;\n\t\"app.tree.editLabel\": true;\n\t\"app.tree.toggleLabelTimestamp\": true;\n\t\"app.session.togglePath\": true;\n\t\"app.session.toggleSort\": true;\n\t\"app.session.rename\": true;\n\t\"app.session.delete\": true;\n\t\"app.session.deleteNoninvasive\": true;\n\t\"app.models.save\": true;\n\t\"app.models.enableAll\": true;\n\t\"app.models.clearAll\": true;\n\t\"app.models.toggleProvider\": true;\n\t\"app.models.reorderUp\": true;\n\t\"app.models.reorderDown\": true;\n\t\"app.tree.filter.default\": true;\n\t\"app.tree.filter.noTools\": true;\n\t\"app.tree.filter.userOnly\": true;\n\t\"app.tree.filter.labeledOnly\": true;\n\t\"app.tree.filter.all\": true;\n\t\"app.tree.filter.cycleForward\": true;\n\t\"app.tree.filter.cycleBackward\": true;\n}\n\nexport type AppKeybinding = keyof AppKeybindings;\n\nexport function useWindowsKeybindings(\n\tplatform: NodeJS.Platform = process.platform,\n\tenv: NodeJS.ProcessEnv = process.env,\n): boolean {\n\treturn platform === \"win32\" || (platform === \"linux\" && Boolean(env.WSL_DISTRO_NAME || env.WSL_INTEROP));\n}\n\ndeclare module \"@earendil-works/pi-tui\" {\n\tinterface Keybindings extends AppKeybindings {}\n}\n\nconst windowsKeybindings = useWindowsKeybindings();\n\nexport const KEYBINDINGS = {\n\t...TUI_KEYBINDINGS,\n\t\"tui.editor.undo\": {\n\t\t...TUI_KEYBINDINGS[\"tui.editor.undo\"],\n\t\tdefaultKeys: process.platform === \"win32\" ? \"ctrl+z\" : windowsKeybindings ? \"alt+z\" : \"ctrl+-\",\n\t},\n\t\"tui.altScreen.previousPrompt\": {\n\t\t...TUI_KEYBINDINGS[\"tui.altScreen.previousPrompt\"],\n\t\tdefaultKeys: windowsKeybindings ? \"ctrl+up\" : [\"ctrl+shift+up\", \"ctrl+up\"],\n\t},\n\t\"tui.altScreen.nextPrompt\": {\n\t\t...TUI_KEYBINDINGS[\"tui.altScreen.nextPrompt\"],\n\t\tdefaultKeys: windowsKeybindings ? \"ctrl+down\" : [\"ctrl+shift+down\", \"ctrl+down\"],\n\t},\n\t\"tui.altScreen.search\": {\n\t\t...TUI_KEYBINDINGS[\"tui.altScreen.search\"],\n\t\tdefaultKeys: windowsKeybindings ? \"ctrl+f\" : \"ctrl+shift+f\",\n\t},\n\t\"app.interrupt\": { defaultKeys: \"escape\", description: \"Cancel or abort\" },\n\t\"app.clear\": { defaultKeys: \"ctrl+c\", description: \"Clear editor\" },\n\t\"app.exit\": { defaultKeys: \"ctrl+d\", description: \"Exit when editor is empty\" },\n\t\"app.suspend\": {\n\t\tdefaultKeys: process.platform === \"win32\" ? [] : \"ctrl+z\",\n\t\tdescription: \"Suspend to background\",\n\t},\n\t\"app.thinking.cycle\": {\n\t\tdefaultKeys: \"alt+t\",\n\t\tdescription: \"Cycle thinking level\",\n\t},\n\t\"app.permissionMode.cycle\": {\n\t\tdefaultKeys: \"shift+tab\",\n\t\tdescription: \"Cycle permission mode for this session\",\n\t},\n\t\"app.thinking.save\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Save thinking level\",\n\t},\n\t\"app.model.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Cycle to next model\",\n\t},\n\t\"app.model.cycleBackward\": {\n\t\tdefaultKeys: windowsKeybindings ? \"alt+p\" : \"shift+ctrl+p\",\n\t\tdescription: \"Cycle to previous model\",\n\t},\n\t\"app.model.select\": { defaultKeys: \"ctrl+l\", description: \"Open model selector\" },\n\t\"app.tools.expand\": { defaultKeys: \"ctrl+o\", description: \"Cycle conversation detail\" },\n\t\"app.tasks.toggle\": { defaultKeys: \"alt+j\", description: \"Expand or collapse task panel\" },\n\t\"app.thinking.toggle\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Toggle thinking blocks\",\n\t},\n\t\"app.session.toggleNamedFilter\": {\n\t\tdefaultKeys: \"ctrl+n\",\n\t\tdescription: \"Toggle named session filter\",\n\t},\n\t\"app.editor.external\": {\n\t\tdefaultKeys: \"ctrl+g\",\n\t\tdescription: \"Open external editor\",\n\t},\n\t\"app.message.copy\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Copy selection or last assistant message\",\n\t},\n\t\"app.message.followUp\": {\n\t\tdefaultKeys: windowsKeybindings ? \"ctrl+q\" : \"alt+enter\",\n\t\tdescription: \"Queue follow-up message\",\n\t},\n\t\"app.message.dequeue\": {\n\t\tdefaultKeys: windowsKeybindings ? \"alt+q\" : \"alt+up\",\n\t\tdescription: \"Restore queued messages\",\n\t},\n\t\"app.clipboard.pasteImage\": {\n\t\tdefaultKeys: windowsKeybindings ? \"alt+v\" : \"ctrl+v\",\n\t\tdescription: \"Paste image from clipboard (text fallback)\",\n\t},\n\t\"app.session.new\": { defaultKeys: [], description: \"Start a new session\" },\n\t\"app.session.tree\": { defaultKeys: [], description: \"Open session tree\" },\n\t\"app.session.fork\": { defaultKeys: [], description: \"Fork current session\" },\n\t\"app.session.resume\": { defaultKeys: [], description: \"Resume a session\" },\n\t\"app.tree.foldOrUp\": {\n\t\tdefaultKeys: process.platform === \"darwin\" ? [\"alt+left\", \"ctrl+left\"] : [\"ctrl+left\", \"alt+left\"],\n\t\tdescription: \"Fold tree branch or move up\",\n\t},\n\t\"app.tree.unfoldOrDown\": {\n\t\tdefaultKeys: process.platform === \"darwin\" ? [\"alt+right\", \"ctrl+right\"] : [\"ctrl+right\", \"alt+right\"],\n\t\tdescription: \"Unfold tree branch or move down\",\n\t},\n\t\"app.tree.editLabel\": {\n\t\tdefaultKeys: \"shift+l\",\n\t\tdescription: \"Edit tree label\",\n\t},\n\t\"app.tree.toggleLabelTimestamp\": {\n\t\tdefaultKeys: \"shift+t\",\n\t\tdescription: \"Toggle tree label timestamps\",\n\t},\n\t\"app.session.togglePath\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle session path display\",\n\t},\n\t\"app.session.toggleSort\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Toggle session sort mode\",\n\t},\n\t\"app.session.rename\": {\n\t\tdefaultKeys: \"ctrl+r\",\n\t\tdescription: \"Rename session\",\n\t},\n\t\"app.session.delete\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Delete session\",\n\t},\n\t\"app.session.deleteNoninvasive\": {\n\t\tdefaultKeys: \"ctrl+backspace\",\n\t\tdescription: \"Delete session when query is empty\",\n\t},\n\t\"app.models.save\": {\n\t\tdefaultKeys: \"ctrl+s\",\n\t\tdescription: \"Save model selection\",\n\t},\n\t\"app.models.enableAll\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Enable all models\",\n\t},\n\t\"app.models.clearAll\": {\n\t\tdefaultKeys: \"ctrl+x\",\n\t\tdescription: \"Clear all models\",\n\t},\n\t\"app.models.toggleProvider\": {\n\t\tdefaultKeys: \"ctrl+p\",\n\t\tdescription: \"Toggle all models for provider\",\n\t},\n\t\"app.models.reorderUp\": {\n\t\tdefaultKeys: \"alt+up\",\n\t\tdescription: \"Move model up in order\",\n\t},\n\t\"app.models.reorderDown\": {\n\t\tdefaultKeys: \"alt+down\",\n\t\tdescription: \"Move model down in order\",\n\t},\n\t\"app.tree.filter.default\": {\n\t\tdefaultKeys: \"ctrl+d\",\n\t\tdescription: \"Tree filter: default view\",\n\t},\n\t\"app.tree.filter.noTools\": {\n\t\tdefaultKeys: \"ctrl+t\",\n\t\tdescription: \"Tree filter: hide tool results\",\n\t},\n\t\"app.tree.filter.userOnly\": {\n\t\tdefaultKeys: \"ctrl+u\",\n\t\tdescription: \"Tree filter: user messages only\",\n\t},\n\t\"app.tree.filter.labeledOnly\": {\n\t\tdefaultKeys: \"ctrl+l\",\n\t\tdescription: \"Tree filter: labeled entries only\",\n\t},\n\t\"app.tree.filter.all\": {\n\t\tdefaultKeys: \"ctrl+a\",\n\t\tdescription: \"Tree filter: show all entries\",\n\t},\n\t\"app.tree.filter.cycleForward\": {\n\t\tdefaultKeys: \"ctrl+o\",\n\t\tdescription: \"Tree filter: cycle forward\",\n\t},\n\t\"app.tree.filter.cycleBackward\": {\n\t\tdefaultKeys: \"shift+ctrl+o\",\n\t\tdescription: \"Tree filter: cycle backward\",\n\t},\n} as const satisfies KeybindingDefinitions;\n\nconst KEYBINDING_NAME_MIGRATIONS = {\n\tcursorUp: \"tui.editor.cursorUp\",\n\tcursorDown: \"tui.editor.cursorDown\",\n\tcursorLeft: \"tui.editor.cursorLeft\",\n\tcursorRight: \"tui.editor.cursorRight\",\n\tcursorWordLeft: \"tui.editor.cursorWordLeft\",\n\tcursorWordRight: \"tui.editor.cursorWordRight\",\n\tcursorLineStart: \"tui.editor.cursorLineStart\",\n\tcursorLineEnd: \"tui.editor.cursorLineEnd\",\n\tjumpForward: \"tui.editor.jumpForward\",\n\tjumpBackward: \"tui.editor.jumpBackward\",\n\tpageUp: \"tui.editor.pageUp\",\n\tpageDown: \"tui.editor.pageDown\",\n\tdeleteCharBackward: \"tui.editor.deleteCharBackward\",\n\tdeleteCharForward: \"tui.editor.deleteCharForward\",\n\tdeleteWordBackward: \"tui.editor.deleteWordBackward\",\n\tdeleteWordForward: \"tui.editor.deleteWordForward\",\n\tdeleteToLineStart: \"tui.editor.deleteToLineStart\",\n\tdeleteToLineEnd: \"tui.editor.deleteToLineEnd\",\n\tyank: \"tui.editor.yank\",\n\tyankPop: \"tui.editor.yankPop\",\n\tundo: \"tui.editor.undo\",\n\tnewLine: \"tui.input.newLine\",\n\tsubmit: \"tui.input.submit\",\n\ttab: \"tui.input.tab\",\n\tcopy: \"tui.input.copy\",\n\tselectUp: \"tui.select.up\",\n\tselectDown: \"tui.select.down\",\n\tselectPageUp: \"tui.select.pageUp\",\n\tselectPageDown: \"tui.select.pageDown\",\n\tselectConfirm: \"tui.select.confirm\",\n\tselectCancel: \"tui.select.cancel\",\n\tinterrupt: \"app.interrupt\",\n\tclear: \"app.clear\",\n\texit: \"app.exit\",\n\tsuspend: \"app.suspend\",\n\tcycleThinkingLevel: \"app.thinking.cycle\",\n\tcycleModelForward: \"app.model.cycleForward\",\n\tcycleModelBackward: \"app.model.cycleBackward\",\n\tselectModel: \"app.model.select\",\n\texpandTools: \"app.tools.expand\",\n\ttoggleThinking: \"app.thinking.toggle\",\n\ttoggleSessionNamedFilter: \"app.session.toggleNamedFilter\",\n\texternalEditor: \"app.editor.external\",\n\tfollowUp: \"app.message.followUp\",\n\tdequeue: \"app.message.dequeue\",\n\tpasteImage: \"app.clipboard.pasteImage\",\n\tnewSession: \"app.session.new\",\n\ttree: \"app.session.tree\",\n\tfork: \"app.session.fork\",\n\tresume: \"app.session.resume\",\n\ttreeFoldOrUp: \"app.tree.foldOrUp\",\n\ttreeUnfoldOrDown: \"app.tree.unfoldOrDown\",\n\ttreeEditLabel: \"app.tree.editLabel\",\n\ttreeToggleLabelTimestamp: \"app.tree.toggleLabelTimestamp\",\n\ttoggleSessionPath: \"app.session.togglePath\",\n\ttoggleSessionSort: \"app.session.toggleSort\",\n\trenameSession: \"app.session.rename\",\n\tdeleteSession: \"app.session.delete\",\n\tdeleteSessionNoninvasive: \"app.session.deleteNoninvasive\",\n} as const satisfies Record<string, Keybinding>;\n\nfunction isLegacyKeybindingName(key: string): key is keyof typeof KEYBINDING_NAME_MIGRATIONS {\n\treturn key in KEYBINDING_NAME_MIGRATIONS;\n}\n\nfunction toKeybindingsConfig(value: Record<string, unknown>): KeybindingsConfig {\n\tconst config: KeybindingsConfig = {};\n\tfor (const [key, binding] of Object.entries(value)) {\n\t\tif (typeof binding === \"string\") {\n\t\t\tconfig[key] = binding as KeyId;\n\t\t\tcontinue;\n\t\t}\n\t\tif (Array.isArray(binding) && binding.every((entry) => typeof entry === \"string\")) {\n\t\t\tconfig[key] = binding as KeyId[];\n\t\t}\n\t}\n\treturn config;\n}\n\nexport function migrateKeybindingsConfig(rawConfig: Record<string, unknown>): {\n\tconfig: Record<string, unknown>;\n\tmigrated: boolean;\n} {\n\tconst config: Record<string, unknown> = {};\n\tlet migrated = false;\n\n\tfor (const [key, value] of Object.entries(rawConfig)) {\n\t\tconst nextKey = isLegacyKeybindingName(key) ? KEYBINDING_NAME_MIGRATIONS[key] : key;\n\t\tif (nextKey !== key) {\n\t\t\tmigrated = true;\n\t\t}\n\t\tif (key !== nextKey && Object.hasOwn(rawConfig, nextKey)) {\n\t\t\tmigrated = true;\n\t\t\tcontinue;\n\t\t}\n\t\tconfig[nextKey] = value;\n\t}\n\n\treturn { config: orderKeybindingsConfig(config), migrated };\n}\n\nfunction orderKeybindingsConfig(config: Record<string, unknown>): Record<string, unknown> {\n\tconst ordered: Record<string, unknown> = {};\n\tfor (const keybinding of Object.keys(KEYBINDINGS)) {\n\t\tif (Object.hasOwn(config, keybinding)) {\n\t\t\tordered[keybinding] = config[keybinding];\n\t\t}\n\t}\n\n\tconst extras = Object.keys(config)\n\t\t.filter((key) => !Object.hasOwn(ordered, key))\n\t\t.sort();\n\tfor (const key of extras) {\n\t\tordered[key] = config[key];\n\t}\n\n\treturn ordered;\n}\n\nfunction loadRawConfig(path: string): Record<string, unknown> | undefined {\n\tif (!existsSync(path)) return undefined;\n\ttry {\n\t\tconst parsed = JSON.parse(stripBom(readFileSync(path, \"utf-8\"))) as unknown;\n\t\tif (typeof parsed !== \"object\" || parsed === null) return undefined;\n\t\treturn parsed as Record<string, unknown>;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nexport class KeybindingsManager extends TuiKeybindingsManager {\n\tprivate configPath: string | undefined;\n\n\tconstructor(userBindings: KeybindingsConfig = {}, configPath?: string) {\n\t\tsuper(KEYBINDINGS, userBindings);\n\t\tthis.configPath = configPath;\n\t}\n\n\tstatic create(agentDir: string = getAgentDir()): KeybindingsManager {\n\t\tconst configPath = join(agentDir, \"keybindings.json\");\n\t\tconst userBindings = KeybindingsManager.loadFromFile(configPath);\n\t\treturn new KeybindingsManager(userBindings, configPath);\n\t}\n\n\treload(): void {\n\t\tif (!this.configPath) return;\n\t\tthis.setUserBindings(KeybindingsManager.loadFromFile(this.configPath));\n\t}\n\n\tgetEffectiveConfig(): KeybindingsConfig {\n\t\treturn this.getResolvedBindings();\n\t}\n\n\tprivate static loadFromFile(path: string): KeybindingsConfig {\n\t\tconst rawConfig = loadRawConfig(path);\n\t\tif (!rawConfig) return {};\n\t\treturn toKeybindingsConfig(migrateKeybindingsConfig(rawConfig).config);\n\t}\n}\n\nexport type { Keybinding, KeybindingsConfig, KeyId };\n"]}
@@ -25,7 +25,7 @@ export declare function resolvePermissionModeForStartup(options: {
25
25
  requestedMode: PermissionMode | undefined;
26
26
  }): PermissionModeStartupResult;
27
27
  /** Where an effective mode came from. `"default"` means nothing set one. */
28
- export type PermissionModeOrigin = "flag" | WritablePermissionSource | "default";
28
+ export type PermissionModeOrigin = "interactive" | "flag" | WritablePermissionSource | "default";
29
29
  export interface EffectiveModeResolution {
30
30
  mode: PermissionMode;
31
31
  origin: PermissionModeOrigin;
@@ -1 +1 @@
1
- {"version":3,"file":"startup.d.ts","sourceRoot":"","sources":["../../../src/core/permissions/startup.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAoB,KAAK,cAAc,EAAE,KAAK,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAElG,MAAM,MAAM,2BAA2B,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9G;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE;IACxD,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,cAAc,GAAG,SAAS,CAAC;CAC1C,GAAG,2BAA2B,CAW9B;AAYD,4EAA4E;AAC5E,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,wBAAwB,GAAG,SAAS,CAAC;AAEjF,MAAM,WAAW,uBAAuB;IACvC,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,oBAAoB,CAAC;CAC7B;AAED;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAC7C,QAAQ,EAAE,cAAc,GAAG,SAAS,EACpC,aAAa,EAAE,WAAW,CAAC,wBAAwB,EAAE,cAAc,CAAC,GAClE,uBAAuB,CAUzB;AAED,wBAAgB,oBAAoB,CACnC,QAAQ,EAAE,cAAc,GAAG,SAAS,EACpC,aAAa,EAAE,WAAW,CAAC,wBAAwB,EAAE,cAAc,CAAC,GAClE,cAAc,CAEhB","sourcesContent":["/**\n * Startup-time permission mode resolution (ADR 0004). A misconfigured\n * non-interactive run should fail fast, at startup, with an actionable message —\n * not deny every tool call mid-run in a way that looks like a bug (see the ADR's\n * \"ask fails closed, and a session that cannot ask refuses to start\").\n */\n\nimport { PERMISSION_MODES, type PermissionMode, type WritablePermissionSource } from \"./store.ts\";\n\nexport type PermissionModeStartupResult = { ok: true; mode: PermissionMode } | { ok: false; message: string };\n\n/**\n * `requestedMode` is assumed already validated against `PERMISSION_MODES` (args.ts\n * rejects an unknown value at parse time) — this only decides whether a session may\n * start *without* one. Interactive sessions may: an `ask` there has a human to\n * resolve it. Non-interactive sessions may not: with no responder, `ask` fails\n * closed to `deny`, and discovering that per-call, mid-run, is the exact bad\n * failure mode this check exists to avoid.\n */\nexport function resolvePermissionModeForStartup(options: {\n\tinteractive: boolean;\n\trequestedMode: PermissionMode | undefined;\n}): PermissionModeStartupResult {\n\tif (options.requestedMode !== undefined) {\n\t\treturn { ok: true, mode: options.requestedMode };\n\t}\n\tif (!options.interactive) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: `Non-interactive sessions require an explicit --permission-mode. Valid modes: ${PERMISSION_MODES.join(\", \")}.`,\n\t\t};\n\t}\n\treturn { ok: true, mode: \"default\" };\n}\n\n/** Sources a mode can come from, in precedence order — mirrors PERMISSION_SOURCES (rules.ts), restricted to the sources that can plausibly set one. */\nconst MODE_SOURCE_ORDER: readonly [\"flag\", ...WritablePermissionSource[]] = [\n\t\"flag\",\n\t\"local\",\n\t\"project\",\n\t\"user\",\n\t\"command\",\n\t\"session\",\n];\n\n/** Where an effective mode came from. `\"default\"` means nothing set one. */\nexport type PermissionModeOrigin = \"flag\" | WritablePermissionSource | \"default\";\n\nexport interface EffectiveModeResolution {\n\tmode: PermissionMode;\n\torigin: PermissionModeOrigin;\n}\n\n/**\n * The `--permission-mode` flag outranks every persisted mode, including a project\n * rule a team committed — a deliberate per-run operator decision should not be\n * silently overridden by checked-in config (ADR 0004).\n *\n * Returns the origin alongside the mode so a caller that offers to *change* the\n * mode can tell whether its write would actually take effect. A settings toggle\n * writing `user` while `local` or the flag outranks it is a silent no-op, which is\n * the one failure a UI here must not have.\n */\nexport function resolveEffectiveModeWithOrigin(\n\tflagMode: PermissionMode | undefined,\n\tmodesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>,\n): EffectiveModeResolution {\n\tfor (const source of MODE_SOURCE_ORDER) {\n\t\tif (source === \"flag\") {\n\t\t\tif (flagMode !== undefined) return { mode: flagMode, origin: \"flag\" };\n\t\t\tcontinue;\n\t\t}\n\t\tconst mode = modesBySource.get(source);\n\t\tif (mode !== undefined) return { mode, origin: source };\n\t}\n\treturn { mode: \"default\", origin: \"default\" };\n}\n\nexport function resolveEffectiveMode(\n\tflagMode: PermissionMode | undefined,\n\tmodesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>,\n): PermissionMode {\n\treturn resolveEffectiveModeWithOrigin(flagMode, modesBySource).mode;\n}\n"]}
1
+ {"version":3,"file":"startup.d.ts","sourceRoot":"","sources":["../../../src/core/permissions/startup.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAoB,KAAK,cAAc,EAAE,KAAK,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAElG,MAAM,MAAM,2BAA2B,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,cAAc,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE9G;;;;;;;GAOG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE;IACxD,WAAW,EAAE,OAAO,CAAC;IACrB,aAAa,EAAE,cAAc,GAAG,SAAS,CAAC;CAC1C,GAAG,2BAA2B,CAW9B;AAYD,4EAA4E;AAC5E,MAAM,MAAM,oBAAoB,GAAG,aAAa,GAAG,MAAM,GAAG,wBAAwB,GAAG,SAAS,CAAC;AAEjG,MAAM,WAAW,uBAAuB;IACvC,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,EAAE,oBAAoB,CAAC;CAC7B;AAED;;;;;;;;;GASG;AACH,wBAAgB,8BAA8B,CAC7C,QAAQ,EAAE,cAAc,GAAG,SAAS,EACpC,aAAa,EAAE,WAAW,CAAC,wBAAwB,EAAE,cAAc,CAAC,GAClE,uBAAuB,CAUzB;AAED,wBAAgB,oBAAoB,CACnC,QAAQ,EAAE,cAAc,GAAG,SAAS,EACpC,aAAa,EAAE,WAAW,CAAC,wBAAwB,EAAE,cAAc,CAAC,GAClE,cAAc,CAEhB","sourcesContent":["/**\n * Startup-time permission mode resolution (ADR 0004). A misconfigured\n * non-interactive run should fail fast, at startup, with an actionable message —\n * not deny every tool call mid-run in a way that looks like a bug (see the ADR's\n * \"ask fails closed, and a session that cannot ask refuses to start\").\n */\n\nimport { PERMISSION_MODES, type PermissionMode, type WritablePermissionSource } from \"./store.ts\";\n\nexport type PermissionModeStartupResult = { ok: true; mode: PermissionMode } | { ok: false; message: string };\n\n/**\n * `requestedMode` is assumed already validated against `PERMISSION_MODES` (args.ts\n * rejects an unknown value at parse time) — this only decides whether a session may\n * start *without* one. Interactive sessions may: an `ask` there has a human to\n * resolve it. Non-interactive sessions may not: with no responder, `ask` fails\n * closed to `deny`, and discovering that per-call, mid-run, is the exact bad\n * failure mode this check exists to avoid.\n */\nexport function resolvePermissionModeForStartup(options: {\n\tinteractive: boolean;\n\trequestedMode: PermissionMode | undefined;\n}): PermissionModeStartupResult {\n\tif (options.requestedMode !== undefined) {\n\t\treturn { ok: true, mode: options.requestedMode };\n\t}\n\tif (!options.interactive) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: `Non-interactive sessions require an explicit --permission-mode. Valid modes: ${PERMISSION_MODES.join(\", \")}.`,\n\t\t};\n\t}\n\treturn { ok: true, mode: \"default\" };\n}\n\n/** Sources a mode can come from, in precedence order — mirrors PERMISSION_SOURCES (rules.ts), restricted to the sources that can plausibly set one. */\nconst MODE_SOURCE_ORDER: readonly [\"flag\", ...WritablePermissionSource[]] = [\n\t\"flag\",\n\t\"local\",\n\t\"project\",\n\t\"user\",\n\t\"command\",\n\t\"session\",\n];\n\n/** Where an effective mode came from. `\"default\"` means nothing set one. */\nexport type PermissionModeOrigin = \"interactive\" | \"flag\" | WritablePermissionSource | \"default\";\n\nexport interface EffectiveModeResolution {\n\tmode: PermissionMode;\n\torigin: PermissionModeOrigin;\n}\n\n/**\n * The `--permission-mode` flag outranks every persisted mode, including a project\n * rule a team committed — a deliberate per-run operator decision should not be\n * silently overridden by checked-in config (ADR 0004).\n *\n * Returns the origin alongside the mode so a caller that offers to *change* the\n * mode can tell whether its write would actually take effect. A settings toggle\n * writing `user` while `local` or the flag outranks it is a silent no-op, which is\n * the one failure a UI here must not have.\n */\nexport function resolveEffectiveModeWithOrigin(\n\tflagMode: PermissionMode | undefined,\n\tmodesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>,\n): EffectiveModeResolution {\n\tfor (const source of MODE_SOURCE_ORDER) {\n\t\tif (source === \"flag\") {\n\t\t\tif (flagMode !== undefined) return { mode: flagMode, origin: \"flag\" };\n\t\t\tcontinue;\n\t\t}\n\t\tconst mode = modesBySource.get(source);\n\t\tif (mode !== undefined) return { mode, origin: source };\n\t}\n\treturn { mode: \"default\", origin: \"default\" };\n}\n\nexport function resolveEffectiveMode(\n\tflagMode: PermissionMode | undefined,\n\tmodesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>,\n): PermissionMode {\n\treturn resolveEffectiveModeWithOrigin(flagMode, modesBySource).mode;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"startup.js","sourceRoot":"","sources":["../../../src/core/permissions/startup.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,gBAAgB,EAAsD,MAAM,YAAY,CAAC;AAIlG;;;;;;;GAOG;AACH,MAAM,UAAU,+BAA+B,CAAC,OAG/C,EAA+B;IAC/B,IAAI,OAAO,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QACzC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC;IAClD,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QAC1B,OAAO;YACN,EAAE,EAAE,KAAK;YACT,OAAO,EAAE,gFAAgF,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;SACvH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAAA,CACrC;AAED,yJAAuJ;AACvJ,MAAM,iBAAiB,GAAqD;IAC3E,MAAM;IACN,OAAO;IACP,SAAS;IACT,MAAM;IACN,SAAS;IACT,SAAS;CACT,CAAC;AAUF;;;;;;;;;GASG;AACH,MAAM,UAAU,8BAA8B,CAC7C,QAAoC,EACpC,aAAoE,EAC1C;IAC1B,KAAK,MAAM,MAAM,IAAI,iBAAiB,EAAE,CAAC;QACxC,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACvB,IAAI,QAAQ,KAAK,SAAS;gBAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;YACtE,SAAS;QACV,CAAC;QACD,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACzD,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AAAA,CAC9C;AAED,MAAM,UAAU,oBAAoB,CACnC,QAAoC,EACpC,aAAoE,EACnD;IACjB,OAAO,8BAA8B,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,IAAI,CAAC;AAAA,CACpE","sourcesContent":["/**\n * Startup-time permission mode resolution (ADR 0004). A misconfigured\n * non-interactive run should fail fast, at startup, with an actionable message —\n * not deny every tool call mid-run in a way that looks like a bug (see the ADR's\n * \"ask fails closed, and a session that cannot ask refuses to start\").\n */\n\nimport { PERMISSION_MODES, type PermissionMode, type WritablePermissionSource } from \"./store.ts\";\n\nexport type PermissionModeStartupResult = { ok: true; mode: PermissionMode } | { ok: false; message: string };\n\n/**\n * `requestedMode` is assumed already validated against `PERMISSION_MODES` (args.ts\n * rejects an unknown value at parse time) — this only decides whether a session may\n * start *without* one. Interactive sessions may: an `ask` there has a human to\n * resolve it. Non-interactive sessions may not: with no responder, `ask` fails\n * closed to `deny`, and discovering that per-call, mid-run, is the exact bad\n * failure mode this check exists to avoid.\n */\nexport function resolvePermissionModeForStartup(options: {\n\tinteractive: boolean;\n\trequestedMode: PermissionMode | undefined;\n}): PermissionModeStartupResult {\n\tif (options.requestedMode !== undefined) {\n\t\treturn { ok: true, mode: options.requestedMode };\n\t}\n\tif (!options.interactive) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: `Non-interactive sessions require an explicit --permission-mode. Valid modes: ${PERMISSION_MODES.join(\", \")}.`,\n\t\t};\n\t}\n\treturn { ok: true, mode: \"default\" };\n}\n\n/** Sources a mode can come from, in precedence order — mirrors PERMISSION_SOURCES (rules.ts), restricted to the sources that can plausibly set one. */\nconst MODE_SOURCE_ORDER: readonly [\"flag\", ...WritablePermissionSource[]] = [\n\t\"flag\",\n\t\"local\",\n\t\"project\",\n\t\"user\",\n\t\"command\",\n\t\"session\",\n];\n\n/** Where an effective mode came from. `\"default\"` means nothing set one. */\nexport type PermissionModeOrigin = \"flag\" | WritablePermissionSource | \"default\";\n\nexport interface EffectiveModeResolution {\n\tmode: PermissionMode;\n\torigin: PermissionModeOrigin;\n}\n\n/**\n * The `--permission-mode` flag outranks every persisted mode, including a project\n * rule a team committed — a deliberate per-run operator decision should not be\n * silently overridden by checked-in config (ADR 0004).\n *\n * Returns the origin alongside the mode so a caller that offers to *change* the\n * mode can tell whether its write would actually take effect. A settings toggle\n * writing `user` while `local` or the flag outranks it is a silent no-op, which is\n * the one failure a UI here must not have.\n */\nexport function resolveEffectiveModeWithOrigin(\n\tflagMode: PermissionMode | undefined,\n\tmodesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>,\n): EffectiveModeResolution {\n\tfor (const source of MODE_SOURCE_ORDER) {\n\t\tif (source === \"flag\") {\n\t\t\tif (flagMode !== undefined) return { mode: flagMode, origin: \"flag\" };\n\t\t\tcontinue;\n\t\t}\n\t\tconst mode = modesBySource.get(source);\n\t\tif (mode !== undefined) return { mode, origin: source };\n\t}\n\treturn { mode: \"default\", origin: \"default\" };\n}\n\nexport function resolveEffectiveMode(\n\tflagMode: PermissionMode | undefined,\n\tmodesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>,\n): PermissionMode {\n\treturn resolveEffectiveModeWithOrigin(flagMode, modesBySource).mode;\n}\n"]}
1
+ {"version":3,"file":"startup.js","sourceRoot":"","sources":["../../../src/core/permissions/startup.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,gBAAgB,EAAsD,MAAM,YAAY,CAAC;AAIlG;;;;;;;GAOG;AACH,MAAM,UAAU,+BAA+B,CAAC,OAG/C,EAA+B;IAC/B,IAAI,OAAO,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QACzC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,aAAa,EAAE,CAAC;IAClD,CAAC;IACD,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QAC1B,OAAO;YACN,EAAE,EAAE,KAAK;YACT,OAAO,EAAE,gFAAgF,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;SACvH,CAAC;IACH,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAAA,CACrC;AAED,yJAAuJ;AACvJ,MAAM,iBAAiB,GAAqD;IAC3E,MAAM;IACN,OAAO;IACP,SAAS;IACT,MAAM;IACN,SAAS;IACT,SAAS;CACT,CAAC;AAUF;;;;;;;;;GASG;AACH,MAAM,UAAU,8BAA8B,CAC7C,QAAoC,EACpC,aAAoE,EAC1C;IAC1B,KAAK,MAAM,MAAM,IAAI,iBAAiB,EAAE,CAAC;QACxC,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACvB,IAAI,QAAQ,KAAK,SAAS;gBAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;YACtE,SAAS;QACV,CAAC;QACD,MAAM,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACvC,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACzD,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AAAA,CAC9C;AAED,MAAM,UAAU,oBAAoB,CACnC,QAAoC,EACpC,aAAoE,EACnD;IACjB,OAAO,8BAA8B,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,IAAI,CAAC;AAAA,CACpE","sourcesContent":["/**\n * Startup-time permission mode resolution (ADR 0004). A misconfigured\n * non-interactive run should fail fast, at startup, with an actionable message —\n * not deny every tool call mid-run in a way that looks like a bug (see the ADR's\n * \"ask fails closed, and a session that cannot ask refuses to start\").\n */\n\nimport { PERMISSION_MODES, type PermissionMode, type WritablePermissionSource } from \"./store.ts\";\n\nexport type PermissionModeStartupResult = { ok: true; mode: PermissionMode } | { ok: false; message: string };\n\n/**\n * `requestedMode` is assumed already validated against `PERMISSION_MODES` (args.ts\n * rejects an unknown value at parse time) — this only decides whether a session may\n * start *without* one. Interactive sessions may: an `ask` there has a human to\n * resolve it. Non-interactive sessions may not: with no responder, `ask` fails\n * closed to `deny`, and discovering that per-call, mid-run, is the exact bad\n * failure mode this check exists to avoid.\n */\nexport function resolvePermissionModeForStartup(options: {\n\tinteractive: boolean;\n\trequestedMode: PermissionMode | undefined;\n}): PermissionModeStartupResult {\n\tif (options.requestedMode !== undefined) {\n\t\treturn { ok: true, mode: options.requestedMode };\n\t}\n\tif (!options.interactive) {\n\t\treturn {\n\t\t\tok: false,\n\t\t\tmessage: `Non-interactive sessions require an explicit --permission-mode. Valid modes: ${PERMISSION_MODES.join(\", \")}.`,\n\t\t};\n\t}\n\treturn { ok: true, mode: \"default\" };\n}\n\n/** Sources a mode can come from, in precedence order — mirrors PERMISSION_SOURCES (rules.ts), restricted to the sources that can plausibly set one. */\nconst MODE_SOURCE_ORDER: readonly [\"flag\", ...WritablePermissionSource[]] = [\n\t\"flag\",\n\t\"local\",\n\t\"project\",\n\t\"user\",\n\t\"command\",\n\t\"session\",\n];\n\n/** Where an effective mode came from. `\"default\"` means nothing set one. */\nexport type PermissionModeOrigin = \"interactive\" | \"flag\" | WritablePermissionSource | \"default\";\n\nexport interface EffectiveModeResolution {\n\tmode: PermissionMode;\n\torigin: PermissionModeOrigin;\n}\n\n/**\n * The `--permission-mode` flag outranks every persisted mode, including a project\n * rule a team committed — a deliberate per-run operator decision should not be\n * silently overridden by checked-in config (ADR 0004).\n *\n * Returns the origin alongside the mode so a caller that offers to *change* the\n * mode can tell whether its write would actually take effect. A settings toggle\n * writing `user` while `local` or the flag outranks it is a silent no-op, which is\n * the one failure a UI here must not have.\n */\nexport function resolveEffectiveModeWithOrigin(\n\tflagMode: PermissionMode | undefined,\n\tmodesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>,\n): EffectiveModeResolution {\n\tfor (const source of MODE_SOURCE_ORDER) {\n\t\tif (source === \"flag\") {\n\t\t\tif (flagMode !== undefined) return { mode: flagMode, origin: \"flag\" };\n\t\t\tcontinue;\n\t\t}\n\t\tconst mode = modesBySource.get(source);\n\t\tif (mode !== undefined) return { mode, origin: source };\n\t}\n\treturn { mode: \"default\", origin: \"default\" };\n}\n\nexport function resolveEffectiveMode(\n\tflagMode: PermissionMode | undefined,\n\tmodesBySource: ReadonlyMap<WritablePermissionSource, PermissionMode>,\n): PermissionMode {\n\treturn resolveEffectiveModeWithOrigin(flagMode, modesBySource).mode;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/core/sdk.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from \"node:path\";\nimport type { CredentialStore, ModelsSimpleStreamOptions } from \"@earendil-works/pi-ai\";\nimport { clampThinkingLevel, type Message, type Model, streamSimple } from \"@earendil-works/pi-ai/compat\";\nimport {\n\tAgent,\n\ttype AgentMessage,\n\ttype AgentRunBudgetController,\n\ttype AgentStopReason,\n\tcreateRunBudgetController,\n\tsetDefaultStreamFn,\n\ttype ThinkingLevel,\n} from \"apex-code-agent-core\";\nimport { getAgentDir } from \"../config.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { AgentSession, type AgentSessionConfig, type AggregateBudgetUsageSnapshot } from \"./agent-session.ts\";\nimport { formatNoModelsAvailableMessage } from \"./auth-guidance.ts\";\nimport { AuthStorage } from \"./auth-storage.ts\";\nimport { CacheWarmer } from \"./cache-warmer.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport { createAgentDefinitionResolver } from \"./delegation/agents.ts\";\nimport {\n\ttype AgentDefinitionResolver,\n\ttype ChildRunPolicySnapshot,\n\tChildRunRegistry,\n\ttype ChildTurnResult,\n\ttype DelegationRuntimeOptions,\n} from \"./delegation/runtime.ts\";\nimport type { ExtensionRunner, LoadExtensionsResult, SessionStartEvent, ToolDefinition } from \"./extensions/index.ts\";\nimport { loadHookRuntime } from \"./hooks/loader.ts\";\nimport type { HookRuntime } from \"./hooks/types.ts\";\nimport { createSessionMcpConnector } from \"./mcp/connector.ts\";\nimport { createMcpRuntime } from \"./mcp/runtime.ts\";\nimport { convertToLlm } from \"./messages.ts\";\nimport { findInitialModel } from \"./model-resolver.ts\";\nimport { ModelRuntime } from \"./model-runtime.ts\";\nimport { DerivedPermissionRuleStore } from \"./permissions/store.ts\";\nimport { mergeProviderAttributionHeaders } from \"./provider-attribution.ts\";\nimport type { ResourceLoader } from \"./resource-loader.ts\";\nimport { DefaultResourceLoader } from \"./resource-loader.ts\";\nimport { getDefaultSessionDir, SessionManager } from \"./session-manager.ts\";\nimport { type ResolvedRunBudget, SettingsManager } from \"./settings-manager.ts\";\nimport { time } from \"./timings.ts\";\nimport { type BackgroundShellRegistry, createBackgroundShellRegistry } from \"./tools/background-shell.ts\";\nimport type { ApexToolDefinition, Capability, EvidenceSink } from \"./tools/contract.ts\";\nimport {\n\tcreateAllToolDefinitions,\n\tcreateBashTool,\n\tcreateCodingTools,\n\tcreateDelegateToolDefinition,\n\tcreateEditTool,\n\tcreateFindTool,\n\tcreateGrepTool,\n\tcreateLsTool,\n\tcreatePowerShellTool,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateWriteTool,\n\ttype ToolName,\n\twithFileMutationQueue,\n} from \"./tools/index.ts\";\nimport { GitWorktreeWorkspaceOwner } from \"./workspace/git-worktree-owner.ts\";\n\n// Preserve the pre-0.81 fallback for extensions that construct Agent instances\n// or invoke low-level agent loops without supplying streamFn. Agent core remains\n// provider-agnostic and does not import pi-ai/compat itself.\nsetDefaultStreamFn(streamSimple);\n\nexport interface CreateAgentSessionOptions {\n\tchildRunRegistry?: AgentSessionConfig[\"childRunRegistry\"];\n\t/** Working directory for project-local discovery. Default: process.cwd() */\n\tcwd?: string;\n\t/** Global config directory. Default: ~/.apex-code/agent */\n\tagentDir?: string;\n\n\t/** Canonical model/auth runtime. Defaults to a runtime using agentDir/auth.json and models.json. */\n\tmodelRuntime?: ModelRuntime;\n\n\t/** Model to use. Default: from settings, else first available */\n\tmodel?: Model<any>;\n\t/** Thinking level. Default: from settings, else 'medium' (clamped to model capabilities) */\n\tthinkingLevel?: ThinkingLevel;\n\t/** Models available for cycling (Ctrl+P in interactive mode) */\n\tscopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;\n\n\t/**\n\t * Optional default tool suppression mode when no explicit allowlist is provided.\n\t *\n\t * - \"all\": start with no tools enabled\n\t * - \"builtin\": disable the default built-in tools (read, bash, edit, write)\n\t * but keep extension/custom tools enabled\n\t */\n\tnoTools?: \"all\" | \"builtin\";\n\t/**\n\t * Optional allowlist of tool names.\n\t *\n\t * When omitted, pi uses the `defaultTools` setting for the initial built-in\n\t * selection when configured. Otherwise it enables the default built-in tools\n\t * (read, bash, edit, write). Extension/custom tools remain enabled unless\n\t * `noTools` changes that default. When provided, only the listed tool names are\n\t * enabled.\n\t */\n\ttools?: string[];\n\t/** Optional denylist of tool names to disable. Applies after `tools` when both are provided. */\n\texcludeTools?: string[];\n\t/** Custom tools to register (in addition to built-in tools). */\n\tcustomTools?: ToolDefinition[];\n\n\t/** Resource loader. When omitted, DefaultResourceLoader is used. */\n\tresourceLoader?: ResourceLoader;\n\n\t/** Session manager. Default: SessionManager.create(cwd) */\n\tsessionManager?: SessionManager;\n\n\t/** Settings manager. Default: SettingsManager.create(cwd, agentDir) */\n\tsettingsManager?: SettingsManager;\n\t/**\n\t * Per-run budget policy override (spec 2026-09-09-run-and-child-session-architecture.md,\n\t * \"Shared budgets\"). When supplied, it replaces the settings read for this\n\t * session's Agent construction; `settingsManager.getRunBudget()` stays the\n\t * default. Per-run only: every session -- parent and child alike -- counts\n\t * its own runs against this policy. It never creates a shared ceiling.\n\t */\n\trunBudget?: ResolvedRunBudget;\n\t/**\n\t * The root aggregate budget for this session's whole delegation tree (spec\n\t * 2026-09-09, \"Shared budgets\"). Explicit opt-in: it is never derived from\n\t * `runBudget`. When set, this session creates ONE aggregate controller and\n\t * (a) composes it under its own Agent's controller, so the parent's own runs\n\t * consume the aggregate while the local per-prompt scope is unchanged, and\n\t * (b) hands THE SAME controller instance to every delegated child regardless\n\t * of depth, so the whole tree -- grandchildren included -- shares one\n\t * ceiling. Read it through `session.aggregateBudgetUsage()`. Absent: no\n\t * shared ceiling anywhere -- every session keeps local-only budgets (the\n\t * per-run policy is still inherited).\n\t */\n\taggregateBudget?: ResolvedRunBudget;\n\t/**\n\t * Maximum simultaneously-active delegated children this session's delegation\n\t * runtime admits (spec 2026-09-09, \"Shared budgets\"). Checked at child\n\t * admission, BEFORE any child session is built; over the limit the admission\n\t * throws an actionable error naming the limit. An admitted run holds one\n\t * slot until terminal settlement (completed/failed/interrupted), close, or\n\t * launch failure. `undefined` (default) is unlimited. Only meaningful when\n\t * delegation is configured.\n\t */\n\tmaxConcurrentChildren?: number;\n\t/**\n\t * Budget controller scope for this session's Agent. \"prompt\" (default;\n\t * upstream semantics) starts a fresh controller at every prompt();\n\t * \"session\" creates one controller at the first prompt/continue and reuses\n\t * it, so a resumed or follow-up turn continues the same budget. Delegated\n\t * children are built with \"session\" so their budget survives sendInput /\n\t * follow-up / resume turns (spec 2026-09-09, \"Shared budgets\").\n\t */\n\tbudgetScope?: \"prompt\" | \"session\";\n\t/**\n\t * A shared budget controller composed under this session's own controller\n\t * (`createCompositeBudgetController`): every gate allows only if both\n\t * allow, and only accepted attempts record to both. The caller owns\n\t * the controller's lifetime; this session never resets it. This is how a\n\t * delegated child receives its root's aggregate controller (spec 2026-09-09,\n\t * \"Shared budgets\") -- sessions that are not delegated children normally\n\t * pass `aggregateBudget` instead and leave this unset.\n\t */\n\tsharedBudgetController?: AgentRunBudgetController;\n\t/** Session start event metadata for extension runtime startup. */\n\tsessionStartEvent?: SessionStartEvent;\n\t/** Authorization configuration for every registered tool call. */\n\tpermissionGate?: AgentSessionConfig[\"permissionGate\"];\n\t/** Optional headless permission bridge (ACP, spec 2026-08-31-acp-adapter.md): consulted before the interactive TUI responder. */\n\tpermissionResponderFactory?: AgentSessionConfig[\"permissionResponderFactory\"];\n\t/** Pre-assembled declarative hooks. Absent by default; `createAgentSession` assembles them from the settings manager when not supplied. */\n\thookRuntime?: HookRuntime;\n\t/** Background-shell registry. Absent by default; `createAgentSession` creates one and disposes it with the session. */\n\tbackgroundShellRegistry?: BackgroundShellRegistry;\n\t/** Optional destination for source-level evidence; defaults to the durable session sink. */\n\tevidenceSink?: EvidenceSink;\n\t/** Optional post-mutation diagnostics injected into `edit`/`write` (LSP.4). */\n\tdiagnosticsOperations?: AgentSessionConfig[\"diagnosticsOperations\"];\n\t/** Optional `lsp` navigation tool transport (LSP.5); registers and activates `lsp` when present. */\n\tlspOperations?: AgentSessionConfig[\"lspOperations\"];\n\twebSearchOperations?: AgentSessionConfig[\"webSearchOperations\"];\n\t/**\n\t * Delegation entry point (roadmap Phase 5, ADR 0008). When set, `delegate`\n\t * executes real child sessions through `resolveAgent`, with the child's\n\t * authority derived from this session's live active tools and permission\n\t * store -- never reconstructed. When omitted, `delegate` stays inert (no agent\n\t * types resolve). Agent discovery is injected here as a caller-supplied\n\t * resolver rather than built in, until Phase 5 task 5.5 lands real\n\t * markdown/frontmatter discovery. Requires `permissionGate` to be set too: a\n\t * child's authority has nothing to derive from otherwise.\n\t */\n\tdelegation?: { resolveAgent?: AgentDefinitionResolver };\n\t/**\n\t * Credential store the MCP OAuth connector uses (spec 2026-09-01-mcp-oauth).\n\t * Default: the host AuthStorage, the same fallback the model runtime applies.\n\t */\n\tmcpCredentials?: CredentialStore;\n}\n\n/** Result from createAgentSession */\nexport interface CreateAgentSessionResult {\n\t/** The created session */\n\tsession: AgentSession;\n\t/** Extensions result (for UI context setup in interactive mode) */\n\textensionsResult: LoadExtensionsResult;\n\t/**\n\t * The session's delegation runtime options when delegation is configured\n\t * (undefined otherwise). Exposing the one runtime the `delegate` tool uses\n\t * lets external lifecycle callers (tests) launch\n\t * children through the same authority derivation instead of re-deriving it.\n\t */\n\tdelegationRuntime?: DelegationRuntimeOptions;\n\t/** Warning if session was restored with a different model than saved */\n\tmodelFallbackMessage?: string;\n}\n\n// Re-exports\n\nexport * from \"./agent-session-runtime.ts\";\nexport type {\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n\tExtensionFactory,\n\tInlineExtension,\n\tSlashCommandInfo,\n\tSlashCommandSource,\n\tToolDefinition,\n} from \"./extensions/index.ts\";\nexport type { PromptTemplate } from \"./prompt-templates.ts\";\nexport type { Skill } from \"./skills.ts\";\nexport type { Tool } from \"./tools/index.ts\";\n// Tool factories (for custom cwd)\nexport {\n\tcreateBashTool,\n\tcreateCodingTools,\n\tcreateEditTool,\n\tcreateFindTool,\n\tcreateGrepTool,\n\tcreateLsTool,\n\tcreatePowerShellTool,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateWriteTool,\n\twithFileMutationQueue,\n};\n\n// Helper Functions\n\nfunction getDefaultAgentDir(): string {\n\treturn getAgentDir();\n}\n\n/**\n * Create an AgentSession with the specified options.\n *\n * @example\n * ```typescript\n * // Minimal - uses defaults\n * const { session } = await createAgentSession();\n *\n * // With explicit model\n * import { getModel } from '@earendil-works/pi-ai';\n * const { session } = await createAgentSession({\n * model: getModel('anthropic', 'claude-opus-4-5'),\n * thinkingLevel: 'high',\n * });\n *\n * // Continue previous session\n * const { session, modelFallbackMessage } = await createAgentSession({\n * continueSession: true,\n * });\n *\n * // Full control\n * const loader = new DefaultResourceLoader({\n * cwd: process.cwd(),\n * agentDir: getAgentDir(),\n * settingsManager: SettingsManager.create(),\n * });\n * await loader.reload();\n * const { session } = await createAgentSession({\n * model: myModel,\n * tools: [\"read\", \"bash\"],\n * resourceLoader: loader,\n * sessionManager: SessionManager.inMemory(),\n * });\n * ```\n */\n/** The most recent assistant text in a completed session -- what a delegated child returns to its parent as its result. */\nfunction extractFinalAssistantText(session: AgentSession): string {\n\tconst messages = session.agent.state.messages;\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst message = messages[i];\n\t\tif (message.role !== \"assistant\") continue;\n\t\tconst text = message.content\n\t\t\t.filter(\n\t\t\t\t(content): content is Extract<AgentMessage, { role: \"assistant\" }>[\"content\"][number] & { type: \"text\" } =>\n\t\t\t\t\tcontent.type === \"text\",\n\t\t\t)\n\t\t\t.map((content) => content.text)\n\t\t\t.join(\"\\n\");\n\t\tif (text) return text;\n\t}\n\treturn \"\";\n}\n\nexport async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {\n\tconst cwd = resolvePath(options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd());\n\tconst agentDir = options.agentDir ? resolvePath(options.agentDir) : getDefaultAgentDir();\n\tlet resourceLoader = options.resourceLoader;\n\n\tconst authPath = options.agentDir ? join(agentDir, \"auth.json\") : undefined;\n\tconst modelsPath = options.agentDir ? join(agentDir, \"models.json\") : undefined;\n\tconst modelRuntime = options.modelRuntime ?? (await ModelRuntime.create({ authPath, modelsPath }));\n\n\t// A caller wanting project scope supplies a manager built with the decision. ADR 0034.\n\tconst settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir, { projectTrusted: false });\n\tconst sessionManager = options.sessionManager ?? SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));\n\tconst backgroundShellRegistry = options.backgroundShellRegistry ?? createBackgroundShellRegistry();\n\t// One resolved per-run budget policy: the caller's override when supplied,\n\t// else the settings policy. Children built below inherit this resolved value\n\t// so a child run carries the same per-run policy as its parent without\n\t// re-reading settings (spec 2026-09-09, \"Shared budgets\"). Per-run only: it\n\t// never creates a shared ceiling.\n\tconst runBudget = options.runBudget ?? settingsManager.getRunBudget();\n\t// The root aggregate budget (spec 2026-09-09, \"Shared budgets\"): created\n\t// ONLY when the caller explicitly configures `aggregateBudget` -- never\n\t// derived from the per-run policy. One controller per root tree: the\n\t// parent's own Agent consumes it (composed under its per-prompt controller),\n\t// and buildChildSession forwards THE SAME instance to every child regardless\n\t// of depth, so a grandchild's consumption lands on the root ledger. Its wall\n\t// time starts here, at root session construction, and no descendant can\n\t// reset it: descendants only consume.\n\tconst aggregateBudgetController = options.aggregateBudget\n\t\t? createRunBudgetController(options.aggregateBudget)\n\t\t: undefined;\n\t// The shared ceiling this session consumes and hands to its delegated\n\t// children: the root aggregate when configured, else a caller-supplied\n\t// controller (which is how a delegated child receives its root's aggregate).\n\t// With neither there is NO shared controller at all: every session keeps\n\t// local-only budgets under its inherited per-run policy.\n\tconst sharedBudgetController = aggregateBudgetController ?? options.sharedBudgetController;\n\t// The tree-facing usage snapshot: the aggregate controller's counters cover\n\t// every accepted provider request, tool call, and maintenance request of the\n\t// parent's own runs and of every descendant, so one read reports the tree.\n\tconst aggregateBudgetUsage = sharedBudgetController\n\t\t? (): AggregateBudgetUsageSnapshot => {\n\t\t\t\tconst usage = sharedBudgetController.usage();\n\t\t\t\treturn {\n\t\t\t\t\tproviderRequests: usage.providerRequests,\n\t\t\t\t\ttoolCalls: usage.toolCalls,\n\t\t\t\t\tmaintenanceRequests: usage.maintenanceRequests,\n\t\t\t\t\tstartedAtMs: usage.startedAt,\n\t\t\t\t};\n\t\t\t}\n\t\t: undefined;\n\n\tif (!resourceLoader) {\n\t\tresourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });\n\t\tawait resourceLoader.reload();\n\t\ttime(\"resourceLoader.reload\");\n\t}\n\n\t// Check if session has existing data to restore\n\tconst existingSession = sessionManager.buildSessionContext();\n\tconst hasExistingSession = existingSession.messages.length > 0;\n\tconst hasThinkingEntry = sessionManager.getBranch().some((entry) => entry.type === \"thinking_level_change\");\n\n\tlet model = options.model;\n\tlet modelFallbackMessage: string | undefined;\n\n\t// If session has data, try to restore model from it\n\tif (!model && hasExistingSession && existingSession.model) {\n\t\tconst restoredModel = modelRuntime.getModel(existingSession.model.provider, existingSession.model.modelId);\n\t\tif (restoredModel && modelRuntime.hasConfiguredAuth(restoredModel.provider)) {\n\t\t\tmodel = restoredModel;\n\t\t}\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`;\n\t\t}\n\t}\n\n\t// If still no model, use findInitialModel (checks settings default, then provider defaults)\n\tif (!model) {\n\t\tconst result = await findInitialModel({\n\t\t\tscopedModels: [],\n\t\t\tisContinuing: hasExistingSession,\n\t\t\tdefaultProvider: settingsManager.getDefaultProvider(),\n\t\t\tdefaultModelId: settingsManager.getDefaultModel(),\n\t\t\tdefaultThinkingLevel: settingsManager.getDefaultThinkingLevel(),\n\t\t\tmodelThinkingLevels: settingsManager.getAllModelThinkingLevels(),\n\t\t\tmodelRuntime,\n\t\t});\n\t\tmodel = result.model;\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = formatNoModelsAvailableMessage();\n\t\t} else if (modelFallbackMessage) {\n\t\t\tmodelFallbackMessage += `. Using ${model.provider}/${model.id}`;\n\t\t}\n\t}\n\n\tlet thinkingLevel = options.thinkingLevel;\n\n\t// If session has data, restore thinking level from it\n\tif (thinkingLevel === undefined && hasExistingSession) {\n\t\tthinkingLevel = hasThinkingEntry\n\t\t\t? (existingSession.thinkingLevel as ThinkingLevel)\n\t\t\t: (settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL);\n\t}\n\n\t// Fall back to per-model override, then global default\n\tif (thinkingLevel === undefined && model) {\n\t\tconst perModel = settingsManager.getModelThinkingLevel(model.provider, model.id);\n\t\tif (perModel) {\n\t\t\tthinkingLevel = perModel;\n\t\t}\n\t}\n\tif (thinkingLevel === undefined) {\n\t\tthinkingLevel = settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;\n\t}\n\n\t// Clamp to model capabilities\n\tif (!model) {\n\t\tthinkingLevel = \"off\";\n\t} else {\n\t\tthinkingLevel = clampThinkingLevel(model, thinkingLevel) as ThinkingLevel;\n\t}\n\n\t// OAuth-capable MCP servers resolve tokens through the session's own credential\n\t// seam: direct and lock-serialized, fail-closed when no store applies\n\t// (spec 2026-09-01-mcp-oauth). Servers without `auth: \"oauth\"` never touch it.\n\tconst mcpRuntime = createMcpRuntime(\n\t\tcwd,\n\t\tcreateSessionMcpConnector({\n\t\t\tcredentials: options.mcpCredentials ?? AuthStorage.create(authPath),\n\t\t}),\n\t\t{ projectTrusted: settingsManager.isProjectTrusted(), agentDir },\n\t);\n\n\t// `web_search`, `lsp`, and `mcp` join the core four only when configured. All stay\n\t// registered either way, so an explicit `--tools` selection still reaches them and an\n\t// unconfigured call still explains itself. Activating an unconfigured tool would put a\n\t// name in the prompt that can only ever fail.\n\tconst defaultActiveToolNames: string[] = [\n\t\t...([\"read\", \"bash\", \"edit\", \"write\"] satisfies ToolName[]),\n\t\t...(options.lspOperations ? [\"lsp\"] : []),\n\t\t...(options.webSearchOperations ? ([\"web_search\"] satisfies ToolName[]) : []),\n\t\t...(mcpRuntime ? [\"mcp\"] : []),\n\t];\n\tconst configuredDefaultToolNames = settingsManager.getDefaultTools();\n\tconst allowedToolNames = options.tools ?? (options.noTools === \"all\" ? [] : undefined);\n\tconst excludedToolNames = options.excludeTools;\n\tconst excludedToolNameSet = excludedToolNames ? new Set(excludedToolNames) : undefined;\n\tconst initialActiveToolNames = (\n\t\toptions.tools ?? (options.noTools ? [] : (configuredDefaultToolNames ?? defaultActiveToolNames))\n\t).filter((name) => !excludedToolNameSet?.has(name));\n\n\t// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)\n\tconst convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {\n\t\tconst converted = convertToLlm(messages);\n\t\t// Check setting dynamically so mid-session changes take effect\n\t\tif (!settingsManager.getBlockImages()) {\n\t\t\treturn converted;\n\t\t}\n\t\t// Filter out ImageContent from all messages, replacing with text placeholder\n\t\treturn converted.map((msg) => {\n\t\t\tif (msg.role === \"user\" || msg.role === \"toolResult\") {\n\t\t\t\tconst content = msg.content;\n\t\t\t\tif (Array.isArray(content)) {\n\t\t\t\t\tconst hasImages = content.some((c) => c.type === \"image\");\n\t\t\t\t\tif (hasImages) {\n\t\t\t\t\t\tconst filteredContent = content\n\t\t\t\t\t\t\t.map((c) =>\n\t\t\t\t\t\t\t\tc.type === \"image\" ? { type: \"text\" as const, text: \"Image reading is disabled.\" } : c,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.filter(\n\t\t\t\t\t\t\t\t(c, i, arr) =>\n\t\t\t\t\t\t\t\t\t// Dedupe consecutive \"Image reading is disabled.\" texts\n\t\t\t\t\t\t\t\t\t!(\n\t\t\t\t\t\t\t\t\t\tc.type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\tc.text === \"Image reading is disabled.\" &&\n\t\t\t\t\t\t\t\t\t\ti > 0 &&\n\t\t\t\t\t\t\t\t\t\tarr[i - 1].type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\t(arr[i - 1] as { type: \"text\"; text: string }).text === \"Image reading is disabled.\"\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\treturn { ...msg, content: filteredContent };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn msg;\n\t\t});\n\t};\n\n\tconst extensionRunnerRef: { current?: ExtensionRunner } = {};\n\tconst cacheWarmer = new CacheWarmer(\n\t\tmodelRuntime,\n\t\tsessionManager,\n\t\t() => settingsManager.getCacheWarmingMode(),\n\t\tasync (event) => extensionRunnerRef.current?.emitCacheWarmingDecision(event) ?? event.action,\n\t);\n\tconst buildRequestOptions = (\n\t\trequestModel: Model<any>,\n\t\toptions: ModelsSimpleStreamOptions = {},\n\t): ModelsSimpleStreamOptions => {\n\t\tconst providerRetrySettings = settingsManager.getProviderRetrySettings();\n\t\tconst httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();\n\t\tconst effectiveTimeoutMs = httpIdleTimeoutMs === 0 ? 2147483647 : httpIdleTimeoutMs;\n\t\tconst headerRunner = extensionRunnerRef.current;\n\t\treturn {\n\t\t\t...options,\n\t\t\ttimeoutMs: options.timeoutMs ?? providerRetrySettings.timeoutMs ?? effectiveTimeoutMs,\n\t\t\twebsocketConnectTimeoutMs: options.websocketConnectTimeoutMs ?? settingsManager.getWebSocketConnectTimeoutMs(),\n\t\t\tmaxRetries: options.maxRetries ?? providerRetrySettings.maxRetries,\n\t\t\tmaxRetryDelayMs: options.maxRetryDelayMs ?? providerRetrySettings.maxRetryDelayMs,\n\t\t\ttransformHeaders: async (requestHeaders) => {\n\t\t\t\tconst headers = mergeProviderAttributionHeaders(\n\t\t\t\t\trequestModel,\n\t\t\t\t\tsettingsManager,\n\t\t\t\t\toptions.sessionId,\n\t\t\t\t\trequestHeaders,\n\t\t\t\t);\n\t\t\t\treturn headerRunner?.hasHandlers(\"before_provider_headers\")\n\t\t\t\t\t? headerRunner.emitBeforeProviderHeaders(headers ?? {})\n\t\t\t\t\t: (headers ?? {});\n\t\t\t},\n\t\t};\n\t};\n\tconst cacheContextIsCurrent = (requestModel: Model<any>) => {\n\t\tconst messages = agent.state.messages;\n\t\treturn () => {\n\t\t\tconst currentModel = agent.state.model;\n\t\t\tconst currentMessages = agent.state.messages;\n\t\t\treturn (\n\t\t\t\tcurrentModel.provider === requestModel.provider &&\n\t\t\t\tcurrentModel.id === requestModel.id &&\n\t\t\t\tmessages.length <= currentMessages.length &&\n\t\t\t\tmessages.every((message, index) => currentMessages[index] === message)\n\t\t\t);\n\t\t};\n\t};\n\tconst transformProviderPayload = async (payload: unknown) => {\n\t\tconst runner = extensionRunnerRef.current;\n\t\tif (!runner?.hasHandlers(\"before_provider_request\")) return payload;\n\t\treturn runner.emitBeforeProviderRequest(payload);\n\t};\n\tconst handleProviderResponse: NonNullable<ModelsSimpleStreamOptions[\"onResponse\"]> = async (response) => {\n\t\tconst runner = extensionRunnerRef.current;\n\t\tif (!runner?.hasHandlers(\"after_provider_response\")) return;\n\t\tawait runner.emit({\n\t\t\ttype: \"after_provider_response\",\n\t\t\tstatus: response.status,\n\t\t\theaders: response.headers,\n\t\t});\n\t};\n\n\tconst agent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt: \"\",\n\t\t\tmodel,\n\t\t\tthinkingLevel,\n\t\t\ttools: [],\n\t\t\tmessages: existingSession.messages,\n\t\t},\n\t\t// The resolved budget policy from above (override or settings); every\n\t\t// execution mode (interactive, print, JSON, RPC, ACP, SDK) runs through\n\t\t// this session, so none counts on its own\n\t\t// (spec 2026-09-01-tool-reliability-and-execution-budgets.md).\n\t\trunBudget,\n\t\tbudgetScope: options.budgetScope ?? \"prompt\",\n\t\t// The root aggregate when configured (the parent's own runs consume it,\n\t\t// per-prompt local scope unchanged), else a caller-supplied shared\n\t\t// controller, else nothing (spec 2026-09-09, \"Shared budgets\").\n\t\tsharedBudgetController,\n\t\tconvertToLlm: convertToLlmWithBlockImages,\n\t\tstreamFn: async (model, context, options) => {\n\t\t\tconst requestOptions = buildRequestOptions(model, options);\n\t\t\t// Compaction and summaries use their own routing ids; only session requests\n\t\t\t// replace the cache entry, so warming restarts from them. Keep warming while\n\t\t\t// the current transcript still extends the request's prefix. Agent state may\n\t\t\t// shallow-copy the messages array or refresh the model object without changing\n\t\t\t// the provider request, so top-level object identity is not a valid cache key.\n\t\t\tif (options?.sessionId === sessionManager.getSessionId()) {\n\t\t\t\tcacheWarmer.start({ model, context, options: requestOptions }, cacheContextIsCurrent(model));\n\t\t\t}\n\t\t\treturn modelRuntime.streamSimple(model, context, requestOptions);\n\t\t},\n\t\tonPayload: transformProviderPayload,\n\t\tonResponse: handleProviderResponse,\n\t\tsessionId: sessionManager.getSessionId(),\n\t\ttransformContext: async (messages) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner) return messages;\n\t\t\treturn runner.emitContext(messages);\n\t\t},\n\t\tsteeringMode: settingsManager.getSteeringMode(),\n\t\tfollowUpMode: settingsManager.getFollowUpMode(),\n\t\ttransport: settingsManager.getTransport(),\n\t\tthinkingBudgets: settingsManager.getThinkingBudgets(),\n\t\tmaxRetryDelayMs: settingsManager.getProviderRetrySettings().maxRetryDelayMs,\n\t});\n\n\t// Restore missing settings metadata for older sessions.\n\tif (hasExistingSession) {\n\t\tif (!hasThinkingEntry) {\n\t\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t\t}\n\t} else {\n\t\t// Save initial model and thinking level for new sessions so they can be restored on resume\n\t\tif (model) {\n\t\t\tsessionManager.appendModelChange(model.provider, model.id);\n\t\t}\n\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t}\n\n\t// Forward ref, mirroring extensionRunnerRef above: the delegation runtime's\n\t// closures are needed to build `customTools` before `session` exists, but they\n\t// must read the *live* session (active tools can change after construction),\n\t// not a snapshot frozen at this point.\n\tconst parentSessionRef: { current?: AgentSession } = {};\n\tconst childRunRegistry = options.childRunRegistry ?? new ChildRunRegistry();\n\tconst customTools: ToolDefinition[] = options.customTools ? [...options.customTools] : [];\n\t// A configured permission gate supplies the authority a child must derive, so\n\t// discovery is enabled by default in real sessions. Callers that need fixture\n\t// definitions may still provide an explicit resolver.\n\tconst delegation = options.delegation ?? (options.permissionGate ? {} : undefined);\n\tlet delegationRuntime: DelegationRuntimeOptions | undefined;\n\tif (delegation) {\n\t\tconst parentPermissionGate = options.permissionGate;\n\t\tconst resolveAgent =\n\t\t\tdelegation.resolveAgent ??\n\t\t\tcreateAgentDefinitionResolver({\n\t\t\t\tcwd,\n\t\t\t\tagentDir,\n\t\t\t\tisProjectTrusted: () => settingsManager.isProjectTrusted(),\n\t\t\t});\n\t\tlet worktreeOwner: GitWorktreeWorkspaceOwner | undefined;\n\t\t// One read of the delegation bound for this runtime; the policy snapshot\n\t\t// below reports the same value it enforces instead of re-reading settings.\n\t\tconst delegationMaxDepth = settingsManager.getDelegationMaxDepth();\n\t\tdelegationRuntime = {\n\t\t\tchildRunRegistry,\n\t\t\tresolveAgent,\n\t\t\t// Concurrency cap (spec 2026-09-09, \"Shared budgets\"): the registry's\n\t\t\t// admission path reads it from the runtime options. Undefined is\n\t\t\t// unlimited.\n\t\t\tmaxConcurrentChildren: options.maxConcurrentChildren,\n\t\t\tgetParentCapabilities: () => {\n\t\t\t\tconst capabilities = new Set<Capability>();\n\t\t\t\tfor (const name of parentSessionRef.current?.getActiveToolNames() ?? []) {\n\t\t\t\t\tconst contract = (\n\t\t\t\t\t\tparentSessionRef.current?.getToolDefinition(name) as Partial<ApexToolDefinition> | undefined\n\t\t\t\t\t)?.contract;\n\t\t\t\t\tif (contract) for (const capability of contract.capabilities) capabilities.add(capability);\n\t\t\t\t}\n\t\t\t\treturn capabilities;\n\t\t\t},\n\t\t\tgetToolCapabilities: (toolName) => {\n\t\t\t\t// Prefer the parent's own live registry first (it also covers\n\t\t\t\t// extension/custom tools an agent definition might name), but fall back\n\t\t\t\t// to the canonical built-in registry: \"what capability does tool X\n\t\t\t\t// need\" is a property of the tool itself, independent of whether the\n\t\t\t\t// parent's own session restricted it out of its active/allowed set via\n\t\t\t\t// `tools`/`excludeTools` -- that restriction affects the parent's own\n\t\t\t\t// *authority* (getParentCapabilities, above), not what a named tool is.\n\t\t\t\tconst fromParent = (\n\t\t\t\t\tparentSessionRef.current?.getToolDefinition(toolName) as Partial<ApexToolDefinition> | undefined\n\t\t\t\t)?.contract?.capabilities;\n\t\t\t\tif (fromParent) return fromParent;\n\t\t\t\treturn (createAllToolDefinitions(cwd)[toolName as ToolName] as ApexToolDefinition | undefined)?.contract\n\t\t\t\t\t?.capabilities;\n\t\t\t},\n\t\t\t// Read fresh each call, not captured once: depth is a live property of\n\t\t\t// the (possibly delegated) session this createAgentSession call is\n\t\t\t// building, via the same sessionManager this closure already captures.\n\t\t\tgetDelegationDepth: () => sessionManager.getDelegationDepth(),\n\t\t\tmaxDelegationDepth: delegationMaxDepth,\n\t\t\tgetParentSessionDir: () => sessionManager.getSessionDir(),\n\t\t\t// Child-run records carry the parent session id so durable records and\n\t\t\t// protocol payloads tie each child to this session's own identity.\n\t\t\tgetParentSessionId: () => sessionManager.getSessionId(),\n\t\t\t// Worktree isolation (spec 2026-09-09, \"Parallel work and ownership\"):\n\t\t\t// the production workspace owner, constructed on the first worktree-\n\t\t\t// isolated delegation only. Shared-read delegations never read this\n\t\t\t// property (the runtime consults the owner only for isolation\n\t\t\t// \"worktree\"), so they never construct an owner and never invoke git.\n\t\t\tget workspaceOwner() {\n\t\t\t\tif (worktreeOwner === undefined) {\n\t\t\t\t\tworktreeOwner = new GitWorktreeWorkspaceOwner(cwd);\n\t\t\t\t}\n\t\t\t\treturn worktreeOwner;\n\t\t\t},\n\t\t\tbuildChildSession: async ({\n\t\t\t\tdefinition,\n\t\t\t\ttoolNames,\n\t\t\t\tcapabilities,\n\t\t\t\tdepth,\n\t\t\t\tsessionId,\n\t\t\t\tartifactDir,\n\t\t\t\tworkspace,\n\t\t\t\treattachSessionPath,\n\t\t\t\ttimeoutMs,\n\t\t\t}) => {\n\t\t\t\tif (!parentPermissionGate) {\n\t\t\t\t\tthrow new Error(\"delegate requires a permission gate configured on the parent session (ADR 0008).\");\n\t\t\t\t}\n\t\t\t\tconst childModel =\n\t\t\t\t\t(definition.model && modelRuntime.getAvailableSnapshot().find((m) => m.id === definition.model)) ||\n\t\t\t\t\tmodel;\n\t\t\t\tif (!childModel) {\n\t\t\t\t\tthrow new Error(`Cannot delegate to \"${definition.name}\": no model is available for the child session.`);\n\t\t\t\t}\n\t\t\t\t// The derived policy snapshot (spec 2026-09-09, \"Derive, do not\n\t\t\t\t// reconstruct\"): the exact values this construction used -- the\n\t\t\t\t// ceiling-checked tool allowlist, the admitted capability set from\n\t\t\t\t// the runtime's single admission projection (never a second\n\t\t\t\t// classification, ADR 0010), the delegation bound it enforces, the\n\t\t\t\t// child's model, and the budget wiring every child is built with\n\t\t\t\t// (session-scoped own controller; aggregate only when a shared ceiling\n\t\t\t\t// was configured at the root). It rides on the returned handle so the\n\t\t\t\t// registry persists it on the child-run record.\n\t\t\t\tconst policy: ChildRunPolicySnapshot = {\n\t\t\t\t\ttools: [...toolNames],\n\t\t\t\t\tcapabilities: [...capabilities].sort(),\n\t\t\t\t\tmaxDelegationDepth: delegationMaxDepth,\n\t\t\t\t\tmodel: childModel.id,\n\t\t\t\t\tbudgetScope: \"session\",\n\t\t\t\t\taggregateBudget: sharedBudgetController !== undefined,\n\t\t\t\t};\n\t\t\t\t// An isolated child runs inside the workspace owner's prepared root\n\t\t\t\t// (its linked worktree); a shared child keeps this session's cwd.\n\t\t\t\tconst childCwd = workspace?.root ?? cwd;\n\t\t\t\t// The one reattachment seam (restart reconstruction): when the request\n\t\t\t\t// carries the existing child session path, that transcript is OPENED\n\t\t\t\t// rather than created; identity (header id, parentSession, depth) comes\n\t\t\t\t// from the file's own header. Every other input below is derived\n\t\t\t\t// exactly as for a fresh delegation -- permission store, model, tools,\n\t\t\t\t// budget -- so there is no second construction path.\n\t\t\t\tconst childSessionManager = reattachSessionPath\n\t\t\t\t\t? SessionManager.open(reattachSessionPath, artifactDir)\n\t\t\t\t\t: artifactDir\n\t\t\t\t\t\t? SessionManager.create(childCwd, artifactDir, {\n\t\t\t\t\t\t\t\tid: sessionId,\n\t\t\t\t\t\t\t\tparentSession: sessionManager.getSessionId(),\n\t\t\t\t\t\t\t\tdelegationDepth: depth,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t: SessionManager.inMemory(childCwd, {\n\t\t\t\t\t\t\t\tparentSession: sessionManager.getSessionId(),\n\t\t\t\t\t\t\t\tdelegationDepth: depth,\n\t\t\t\t\t\t\t});\n\t\t\t\tconst derivedStore = new DerivedPermissionRuleStore({ parent: parentPermissionGate.store });\n\t\t\t\tconst { session: childSession } = await createAgentSession({\n\t\t\t\t\tcwd: childCwd,\n\t\t\t\t\tagentDir,\n\t\t\t\t\tmodel: childModel,\n\t\t\t\t\tmodelRuntime,\n\t\t\t\t\tsettingsManager,\n\t\t\t\t\t// The parent's already-resolved per-run budget (override or settings),\n\t\t\t\t\t// not a fresh settings read: every child run carries the same per-run\n\t\t\t\t\t// policy as its parent (spec 2026-09-09, \"Shared budgets\").\n\t\t\t\t\t//\n\t\t\t\t\t// Session scope + the root aggregate: a resumed or follow-up child\n\t\t\t\t\t// turn continues the same child budget (one controller per child\n\t\t\t\t\t// Agent, created at its first turn), and every gate also answers\n\t\t\t\t\t// to the tree's shared ceiling -- accepted consumption records to\n\t\t\t\t\t// both the child's own controller and the aggregate. THE SAME\n\t\t\t\t\t// aggregate instance reaches every descendant regardless of\n\t\t\t\t\t// depth (a grandchild's parent forwards it onward), so there is\n\t\t\t\t\t// exactly ONE aggregate per root tree and no child can reset it\n\t\t\t\t\t// by resuming or spawning children (spec 2026-09-09, \"Shared\n\t\t\t\t\t// budgets\"). Without an aggregate there is no shared controller:\n\t\t\t\t\t// the child keeps local-only budgets under the inherited policy.\n\t\t\t\t\t//\n\t\t\t\t\t// A launch timeout (spec 2026-09-09, timeouts) overrides the\n\t\t\t\t\t// child's OWN wall-time limit with the requested timeoutMs, so the\n\t\t\t\t\t// existing AgentRunBudget wall-time gate enforces it mid-run; the\n\t\t\t\t\t// parent policy's other limits still apply unchanged.\n\t\t\t\t\trunBudget: timeoutMs === undefined ? runBudget : { ...runBudget, maxWallTimeMs: timeoutMs },\n\t\t\t\t\tbudgetScope: \"session\",\n\t\t\t\t\tsharedBudgetController,\n\t\t\t\t\t// The same per-parent admission cap applies to a child's own\n\t\t\t\t\t// delegations (each runtime counts its direct children).\n\t\t\t\t\tmaxConcurrentChildren: options.maxConcurrentChildren,\n\t\t\t\t\tsessionManager: childSessionManager,\n\t\t\t\t\tresourceLoader,\n\t\t\t\t\ttools: toolNames,\n\t\t\t\t\t// No responder: a child's `ask` fails closed rather than prompting the\n\t\t\t\t\t// human for a call the human did not make (ADR 0008, \"Who answers a\n\t\t\t\t\t// child's ask\").\n\t\t\t\t\tpermissionGate: { store: derivedStore, getMode: parentPermissionGate.getMode },\n\t\t\t\t\t// A delegated child shares the parent's cwd-bound LSP pool (spec, \"no\n\t\t\t\t\t// multi-root LSP connection and no sharing across cwd-bound service\n\t\t\t\t\t// lifetimes\" -- delegated children are the one sharing exception).\n\t\t\t\t\tdiagnosticsOperations: options.diagnosticsOperations,\n\t\t\t\t\tlspOperations: options.lspOperations,\n\t\t\t\t\t// Shared for the same reason as the LSP pool: the backend was resolved\n\t\t\t\t\t// once (possibly by running a shell command for the key) and a child\n\t\t\t\t\t// that re-resolved nothing would hold a `web_search` that only throws.\n\t\t\t\t\twebSearchOperations: options.webSearchOperations,\n\t\t\t\t\t// `checkpointSettings` is deliberately *not* forwarded. A child shares the\n\t\t\t\t\t// parent's cwd, so it would write refs under its own session id into the\n\t\t\t\t\t// same repository and interleave them with the parent's. The parent's\n\t\t\t\t\t// per-turn capture already covers everything a child does inside that turn.\n\n\t\t\t\t\t// Forwarded so a child that itself holds `delegate` can delegate again\n\t\t\t\t\t// (bounded by the depth guard above, read fresh from its own session\n\t\t\t\t\t// header on its next createAgentSession call) -- otherwise recursion\n\t\t\t\t\t// would be silently capped at one level regardless of maxDelegationDepth.\n\t\t\t\t\tdelegation,\n\t\t\t\t});\n\t\t\t\tawait childSession.bindExtensions({});\n\t\t\t\tlet status: import(\"./delegation/runtime.ts\").ChildSessionStatus = \"idle\";\n\t\t\t\tlet runPromise: Promise<void> | undefined;\n\t\t\t\tlet lastTurnResult: ChildTurnResult | undefined;\n\t\t\t\t// The captured structured terminal outcome of the run in flight\n\t\t\t\t// (`agent_end`'s stopReason). Budget refusals emit agent_end without\n\t\t\t\t// an assistant message, so this is the only way the settlement below\n\t\t\t\t// can see and name them.\n\t\t\t\tlet runStopReason: AgentStopReason | undefined;\n\t\t\t\tconst ensureOpen = () => {\n\t\t\t\t\tif (status === \"closed\") throw new Error(\"Child session is closed.\");\n\t\t\t\t};\n\t\t\t\tconst start = (input: string): Promise<void> => {\n\t\t\t\t\tensureOpen();\n\t\t\t\t\tif (runPromise) throw new Error(\"Child session is already running.\");\n\t\t\t\t\tstatus = \"running\";\n\t\t\t\t\trunStopReason = undefined;\n\t\t\t\t\t// Capture the run's structured terminal outcome: a budget refusal\n\t\t\t\t\t// emits agent_end without producing an assistant message, so the\n\t\t\t\t\t// message-based checks below cannot see it.\n\t\t\t\t\tconst unsubscribeStop = childSession.subscribe((event) => {\n\t\t\t\t\t\tif (event.type === \"agent_end\") runStopReason = event.stopReason;\n\t\t\t\t\t});\n\t\t\t\t\tconst pending = childSession\n\t\t\t\t\t\t.prompt(input)\n\t\t\t\t\t\t.then(\n\t\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t\tunsubscribeStop();\n\t\t\t\t\t\t\t\tif (status === \"closed\") {\n\t\t\t\t\t\t\t\t\tlastTurnResult = { outcome: \"failed\", output: \"\" };\n\t\t\t\t\t\t\t\t\tthrow new Error(\"Child session is closed.\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (status === \"interrupted\") {\n\t\t\t\t\t\t\t\t\tlastTurnResult = { outcome: \"interrupted\", output: extractFinalAssistantText(childSession) };\n\t\t\t\t\t\t\t\t\tthrow new Error(\"Child session is interrupted.\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst lastAssistant = [...childSession.agent.state.messages]\n\t\t\t\t\t\t\t\t\t.reverse()\n\t\t\t\t\t\t\t\t\t.find((message) => message.role === \"assistant\");\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\tlastAssistant?.role === \"assistant\" &&\n\t\t\t\t\t\t\t\t\t(lastAssistant.stopReason === \"aborted\" || lastAssistant.stopReason === \"error\")\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\tstatus = \"interrupted\";\n\t\t\t\t\t\t\t\t\tlastTurnResult = {\n\t\t\t\t\t\t\t\t\t\toutcome: lastAssistant.stopReason === \"aborted\" ? \"interrupted\" : \"failed\",\n\t\t\t\t\t\t\t\t\t\toutput: lastAssistant.errorMessage || extractFinalAssistantText(childSession),\n\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\tthrow new Error(lastAssistant.errorMessage || \"Child session was interrupted.\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tstatus = \"idle\";\n\t\t\t\t\t\t\t\tlastTurnResult = { outcome: \"completed\", output: extractFinalAssistantText(childSession) };\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t(error) => {\n\t\t\t\t\t\t\t\tunsubscribeStop();\n\t\t\t\t\t\t\t\tif (status !== \"closed\") status = \"interrupted\";\n\t\t\t\t\t\t\t\tlastTurnResult =\n\t\t\t\t\t\t\t\t\tstatus === \"closed\"\n\t\t\t\t\t\t\t\t\t\t? { outcome: \"failed\", output: \"\" }\n\t\t\t\t\t\t\t\t\t\t: { outcome: \"interrupted\", output: extractFinalAssistantText(childSession) };\n\t\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.finally(() => {\n\t\t\t\t\t\t\trunPromise = undefined;\n\t\t\t\t\t\t});\n\t\t\t\t\trunPromise = pending;\n\t\t\t\t\treturn pending;\n\t\t\t\t};\n\t\t\t\tconst sendInput = (input: string) => {\n\t\t\t\t\tensureOpen();\n\t\t\t\t\tif (runPromise) {\n\t\t\t\t\t\tif (status !== \"running\") throw new Error(\"Child session is still stopping.\");\n\t\t\t\t\t\t// A follow-up queued onto a live run settles through that run's\n\t\t\t\t\t\t// own loop; only turns that settle here pass through the gate.\n\t\t\t\t\t\treturn childSession.followUp(input);\n\t\t\t\t\t}\n\t\t\t\t\treturn start(input).then(() => requireVerifiedTurn());\n\t\t\t\t};\n\t\t\t\tconst close = () => {\n\t\t\t\t\tif (status === \"closed\") return;\n\t\t\t\t\tstatus = \"closed\";\n\t\t\t\t\tchildSession.dispose();\n\t\t\t\t};\n\t\t\t\t// Verification gate (spec 2026-09-09, \"Verification at completion\"):\n\t\t\t\t// a child with configured verification policies may not settle a turn\n\t\t\t\t// as successful unless the session's canonical tracker reports\n\t\t\t\t// \"verified\". The tracker is the only verifier; no child-local one is\n\t\t\t\t// introduced. With the \"explicit\" boundary the gate runs\n\t\t\t\t// requestVerification once here; with the \"post-turn\" boundary the\n\t\t\t\t// session already ran verification at its own turn boundary, so the\n\t\t\t\t// gate only reads the status. Any other completion status --\n\t\t\t\t// failed/unavailable/interrupted/continued-unverified -- fails the\n\t\t\t\t// turn with the status named in the error.\n\t\t\t\tconst requireVerifiedTurn = async (): Promise<void> => {\n\t\t\t\t\tconst policyIds = childSession.verificationPolicyIds();\n\t\t\t\t\tif (policyIds.length === 0) return;\n\t\t\t\t\tconst verificationStatus =\n\t\t\t\t\t\tchildSession.verificationBoundary() === \"explicit\"\n\t\t\t\t\t\t\t? await childSession.requestVerification()\n\t\t\t\t\t\t\t: childSession.verificationStatus();\n\t\t\t\t\tif (verificationStatus === \"verified\") return;\n\t\t\t\t\tconst message = `Child verification failed: ${verificationStatus} (policies: ${policyIds.join(\", \")}).`;\n\t\t\t\t\tlastTurnResult = { outcome: \"failed\", output: message };\n\t\t\t\t\tthrow new Error(message);\n\t\t\t\t};\n\t\t\t\treturn {\n\t\t\t\t\tget status() {\n\t\t\t\t\t\treturn status;\n\t\t\t\t\t},\n\t\t\t\t\tlatestResult: () => lastTurnResult,\n\t\t\t\t\tusage: () => childSession.agent.budgetUsage(),\n\t\t\t\t\t// The usage-rollup seam (spec 2026-09-09): the registry reads the\n\t\t\t\t\t// child's own transcript file when one exists, and falls back to\n\t\t\t\t\t// these in-memory entries only for a child that never persisted a\n\t\t\t\t\t// transcript. Read-only; the child's SessionManager stays the owner.\n\t\t\t\t\tsessionEntries: () => childSessionManager.getEntries(),\n\t\t\t\t\tasync run(task: string) {\n\t\t\t\t\t\tconst prompt = definition.systemPrompt\n\t\t\t\t\t\t\t? `${definition.systemPrompt}\\n\\nTask: ${task}`\n\t\t\t\t\t\t\t: `Task: ${task}`;\n\t\t\t\t\t\tawait start(prompt);\n\t\t\t\t\t\tif (status === \"closed\" || status === \"interrupted\") throw new Error(`Child session is ${status}.`);\n\t\t\t\t\t\t// The launch timeout's settlement error path (spec 2026-09-09,\n\t\t\t\t\t\t// timeouts): a run refused at the child's own wall-time gate never\n\t\t\t\t\t\t// started meaningful work, so the delegation settles failed with\n\t\t\t\t\t\t// the exhausted limit named, reusing the budget's existing\n\t\t\t\t\t\t// exhaustedLimit vocabulary. Other budget refusals keep their\n\t\t\t\t\t\t// existing semantics (the refusal is named in the child transcript\n\t\t\t\t\t\t// and the prior result stands).\n\t\t\t\t\t\tif (runStopReason?.kind === \"budget-exhausted\" && runStopReason.limit === \"wall-time\") {\n\t\t\t\t\t\t\tconst message = `Child run stopped before completing: the run's ${runStopReason.limit} budget was exhausted.`;\n\t\t\t\t\t\t\tlastTurnResult = { outcome: \"failed\", output: message };\n\t\t\t\t\t\t\tthrow new Error(message);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Delegated work cannot claim success when the child has a\n\t\t\t\t\t\t// required verification boundary and verification did not pass.\n\t\t\t\t\t\t// Use the session's canonical tracker rather than introducing\n\t\t\t\t\t\t// a second child-only verifier.\n\t\t\t\t\t\tawait requireVerifiedTurn();\n\t\t\t\t\t\treturn { output: extractFinalAssistantText(childSession) };\n\t\t\t\t\t},\n\t\t\t\t\twait: async () => {\n\t\t\t\t\t\twhile (runPromise) await runPromise;\n\t\t\t\t\t},\n\t\t\t\t\tinterrupt: () => {\n\t\t\t\t\t\tensureOpen();\n\t\t\t\t\t\tif (status === \"running\") {\n\t\t\t\t\t\t\tstatus = \"interrupted\";\n\t\t\t\t\t\t\tvoid childSession.abort().catch(() => undefined);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tclose,\n\t\t\t\t\tsendInput,\n\t\t\t\t\tfollowUp: sendInput,\n\t\t\t\t\tdispose: close,\n\t\t\t\t\t// Construction linkage (spec 2026-09-09, policy/artifact\n\t\t\t\t\t// linkage): the registry relays these onto the persisted child_run\n\t\t\t\t\t// record and every status/wait payload derived from it.\n\t\t\t\t\tpolicy,\n\t\t\t\t};\n\t\t\t},\n\t\t};\n\t\tcustomTools.push(createDelegateToolDefinition(delegationRuntime) as ToolDefinition<any, any>);\n\t\t// Historical resume (restart reconstruction) reattaches a persisted child\n\t\t// through THIS runtime's buildChildSession seam. Wiring it here -- not only\n\t\t// at the first delegation -- is what makes resume work on a freshly\n\t\t// reopened session before any new delegation has run.\n\t\tchildRunRegistry.setRuntimeOptions(delegationRuntime);\n\t}\n\n\tconst session = new AgentSession({\n\t\tchildRunRegistry,\n\t\tdelegationRuntime,\n\t\tagent,\n\t\t// Root aggregate usage (spec 2026-09-09, \"Shared budgets\"): undefined\n\t\t// when the tree has no aggregate budget.\n\t\taggregateBudgetUsage,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tcwd,\n\t\tscopedModels: options.scopedModels,\n\t\tresourceLoader,\n\t\tcustomTools,\n\t\tmodelRuntime,\n\t\tcacheWarmer,\n\t\tinitialActiveToolNames,\n\t\tallowedToolNames,\n\t\texcludedToolNames,\n\t\textensionRunnerRef,\n\t\tsessionStartEvent: options.sessionStartEvent,\n\t\tpermissionGate: options.permissionGate,\n\t\tpermissionResponderFactory: options.permissionResponderFactory,\n\t\tevidenceSink: options.evidenceSink,\n\t\tdiagnosticsOperations: options.diagnosticsOperations,\n\t\tlspOperations: options.lspOperations,\n\t\twebSearchOperations: options.webSearchOperations,\n\t\tmcpRuntime,\n\t\tcheckpointSettings: settingsManager.getCheckpointSettings(),\n\t\t// Declarative hooks (spec 2026-08-31-declarative-hooks): assembled from the\n\t\t// merged settings -- project-scope entries only appear once trusted. A\n\t\t// malformed hooks key throws here, failing closed at wiring. Deliberately\n\t\t// not forwarded to delegation children (see the spec's boundary note).\n\t\thookRuntime: options.hookRuntime ?? loadHookRuntime(settingsManager.getHookSettings()),\n\t\tbackgroundShellRegistry,\n\t});\n\tparentSessionRef.current = session;\n\t// Eager servers connect here rather than on first use, so their tools are in the\n\t// cache before the model's first search. Detached: a slow or broken server delays\n\t// nothing, and `warmEagerServers` already swallows its own failures.\n\tvoid mcpRuntime?.warm();\n\tconst extensionsResult = resourceLoader.getExtensions();\n\n\treturn {\n\t\tsession,\n\t\textensionsResult,\n\t\tdelegationRuntime,\n\t\tmodelFallbackMessage,\n\t};\n}\n"]}
1
+ {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/core/sdk.ts"],"names":[],"mappings":"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","sourcesContent":["import { join } from \"node:path\";\nimport type { CredentialStore, ModelsSimpleStreamOptions } from \"@earendil-works/pi-ai\";\nimport { clampThinkingLevel, type Message, type Model, streamSimple } from \"@earendil-works/pi-ai/compat\";\nimport {\n\tAgent,\n\ttype AgentMessage,\n\ttype AgentRunBudgetController,\n\ttype AgentStopReason,\n\tcreateRunBudgetController,\n\tsetDefaultStreamFn,\n\ttype ThinkingLevel,\n} from \"apex-code-agent-core\";\nimport { getAgentDir } from \"../config.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { AgentSession, type AgentSessionConfig, type AggregateBudgetUsageSnapshot } from \"./agent-session.ts\";\nimport { formatNoModelsAvailableMessage } from \"./auth-guidance.ts\";\nimport { AuthStorage } from \"./auth-storage.ts\";\nimport { CacheWarmer } from \"./cache-warmer.ts\";\nimport { configuredDefaultToolNames } from \"./default-tool-names.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport { createAgentDefinitionResolver } from \"./delegation/agents.ts\";\nimport {\n\ttype AgentDefinitionResolver,\n\ttype ChildRunPolicySnapshot,\n\tChildRunRegistry,\n\ttype ChildTurnResult,\n\ttype DelegationRuntimeOptions,\n} from \"./delegation/runtime.ts\";\nimport type { ExtensionRunner, LoadExtensionsResult, SessionStartEvent, ToolDefinition } from \"./extensions/index.ts\";\nimport { loadHookRuntime } from \"./hooks/loader.ts\";\nimport type { HookRuntime } from \"./hooks/types.ts\";\nimport { createSessionMcpConnector } from \"./mcp/connector.ts\";\nimport { createMcpRuntime } from \"./mcp/runtime.ts\";\nimport { convertToLlm } from \"./messages.ts\";\nimport { findInitialModel } from \"./model-resolver.ts\";\nimport { ModelRuntime } from \"./model-runtime.ts\";\nimport { DerivedPermissionRuleStore } from \"./permissions/store.ts\";\nimport { mergeProviderAttributionHeaders } from \"./provider-attribution.ts\";\nimport type { ResourceLoader } from \"./resource-loader.ts\";\nimport { DefaultResourceLoader } from \"./resource-loader.ts\";\nimport { getDefaultSessionDir, SessionManager } from \"./session-manager.ts\";\nimport { type ResolvedRunBudget, SettingsManager } from \"./settings-manager.ts\";\nimport { time } from \"./timings.ts\";\nimport { type BackgroundShellRegistry, createBackgroundShellRegistry } from \"./tools/background-shell.ts\";\nimport type { ApexToolDefinition, Capability, EvidenceSink } from \"./tools/contract.ts\";\nimport {\n\tcreateAllToolDefinitions,\n\tcreateBashTool,\n\tcreateCodingTools,\n\tcreateDelegateToolDefinition,\n\tcreateEditTool,\n\tcreateFindTool,\n\tcreateGrepTool,\n\tcreateLsTool,\n\tcreatePowerShellTool,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateWriteTool,\n\ttype ToolName,\n\twithFileMutationQueue,\n} from \"./tools/index.ts\";\nimport { GitWorktreeWorkspaceOwner } from \"./workspace/git-worktree-owner.ts\";\n\n// Preserve the pre-0.81 fallback for extensions that construct Agent instances\n// or invoke low-level agent loops without supplying streamFn. Agent core remains\n// provider-agnostic and does not import pi-ai/compat itself.\nsetDefaultStreamFn(streamSimple);\n\nexport interface CreateAgentSessionOptions {\n\tchildRunRegistry?: AgentSessionConfig[\"childRunRegistry\"];\n\t/** Working directory for project-local discovery. Default: process.cwd() */\n\tcwd?: string;\n\t/** Global config directory. Default: ~/.apex-code/agent */\n\tagentDir?: string;\n\n\t/** Canonical model/auth runtime. Defaults to a runtime using agentDir/auth.json and models.json. */\n\tmodelRuntime?: ModelRuntime;\n\n\t/** Model to use. Default: from settings, else first available */\n\tmodel?: Model<any>;\n\t/** Thinking level. Default: from settings, else 'medium' (clamped to model capabilities) */\n\tthinkingLevel?: ThinkingLevel;\n\t/** Models available for cycling (Ctrl+P in interactive mode) */\n\tscopedModels?: Array<{ model: Model<any>; thinkingLevel?: ThinkingLevel }>;\n\n\t/**\n\t * Optional default tool suppression mode when no explicit allowlist is provided.\n\t *\n\t * - \"all\": start with no tools enabled\n\t * - \"builtin\": disable the default built-in tools (read, bash, edit, write)\n\t * but keep extension/custom tools enabled\n\t */\n\tnoTools?: \"all\" | \"builtin\";\n\t/**\n\t * Optional allowlist of tool names.\n\t *\n\t * When omitted, pi uses the `defaultTools` setting for the initial built-in\n\t * selection when configured. Otherwise it enables the default built-in tools\n\t * (read, bash, edit, write). Extension/custom tools remain enabled unless\n\t * `noTools` changes that default. When provided, only the listed tool names are\n\t * enabled.\n\t */\n\ttools?: string[];\n\t/** Optional denylist of tool names to disable. Applies after `tools` when both are provided. */\n\texcludeTools?: string[];\n\t/** Custom tools to register (in addition to built-in tools). */\n\tcustomTools?: ToolDefinition[];\n\n\t/** Resource loader. When omitted, DefaultResourceLoader is used. */\n\tresourceLoader?: ResourceLoader;\n\n\t/** Session manager. Default: SessionManager.create(cwd) */\n\tsessionManager?: SessionManager;\n\n\t/** Settings manager. Default: SettingsManager.create(cwd, agentDir) */\n\tsettingsManager?: SettingsManager;\n\t/**\n\t * Per-run budget policy override (spec 2026-09-09-run-and-child-session-architecture.md,\n\t * \"Shared budgets\"). When supplied, it replaces the settings read for this\n\t * session's Agent construction; `settingsManager.getRunBudget()` stays the\n\t * default. Per-run only: every session -- parent and child alike -- counts\n\t * its own runs against this policy. It never creates a shared ceiling.\n\t */\n\trunBudget?: ResolvedRunBudget;\n\t/**\n\t * The root aggregate budget for this session's whole delegation tree (spec\n\t * 2026-09-09, \"Shared budgets\"). Explicit opt-in: it is never derived from\n\t * `runBudget`. When set, this session creates ONE aggregate controller and\n\t * (a) composes it under its own Agent's controller, so the parent's own runs\n\t * consume the aggregate while the local per-prompt scope is unchanged, and\n\t * (b) hands THE SAME controller instance to every delegated child regardless\n\t * of depth, so the whole tree -- grandchildren included -- shares one\n\t * ceiling. Read it through `session.aggregateBudgetUsage()`. Absent: no\n\t * shared ceiling anywhere -- every session keeps local-only budgets (the\n\t * per-run policy is still inherited).\n\t */\n\taggregateBudget?: ResolvedRunBudget;\n\t/**\n\t * Maximum simultaneously-active delegated children this session's delegation\n\t * runtime admits (spec 2026-09-09, \"Shared budgets\"). Checked at child\n\t * admission, BEFORE any child session is built; over the limit the admission\n\t * throws an actionable error naming the limit. An admitted run holds one\n\t * slot until terminal settlement (completed/failed/interrupted), close, or\n\t * launch failure. `undefined` (default) is unlimited. Only meaningful when\n\t * delegation is configured.\n\t */\n\tmaxConcurrentChildren?: number;\n\t/**\n\t * Budget controller scope for this session's Agent. \"prompt\" (default;\n\t * upstream semantics) starts a fresh controller at every prompt();\n\t * \"session\" creates one controller at the first prompt/continue and reuses\n\t * it, so a resumed or follow-up turn continues the same budget. Delegated\n\t * children are built with \"session\" so their budget survives sendInput /\n\t * follow-up / resume turns (spec 2026-09-09, \"Shared budgets\").\n\t */\n\tbudgetScope?: \"prompt\" | \"session\";\n\t/**\n\t * A shared budget controller composed under this session's own controller\n\t * (`createCompositeBudgetController`): every gate allows only if both\n\t * allow, and only accepted attempts record to both. The caller owns\n\t * the controller's lifetime; this session never resets it. This is how a\n\t * delegated child receives its root's aggregate controller (spec 2026-09-09,\n\t * \"Shared budgets\") -- sessions that are not delegated children normally\n\t * pass `aggregateBudget` instead and leave this unset.\n\t */\n\tsharedBudgetController?: AgentRunBudgetController;\n\t/** Session start event metadata for extension runtime startup. */\n\tsessionStartEvent?: SessionStartEvent;\n\t/** Authorization configuration for every registered tool call. */\n\tpermissionGate?: AgentSessionConfig[\"permissionGate\"];\n\t/** Optional headless permission bridge (ACP, spec 2026-08-31-acp-adapter.md): consulted before the interactive TUI responder. */\n\tpermissionResponderFactory?: AgentSessionConfig[\"permissionResponderFactory\"];\n\t/** Pre-assembled declarative hooks. Absent by default; `createAgentSession` assembles them from the settings manager when not supplied. */\n\thookRuntime?: HookRuntime;\n\t/** Background-shell registry. Absent by default; `createAgentSession` creates one and disposes it with the session. */\n\tbackgroundShellRegistry?: BackgroundShellRegistry;\n\t/** Optional destination for source-level evidence; defaults to the durable session sink. */\n\tevidenceSink?: EvidenceSink;\n\t/** Optional post-mutation diagnostics injected into `edit`/`write` (LSP.4). */\n\tdiagnosticsOperations?: AgentSessionConfig[\"diagnosticsOperations\"];\n\t/** Optional `lsp` navigation tool transport (LSP.5); registers and activates `lsp` when present. */\n\tlspOperations?: AgentSessionConfig[\"lspOperations\"];\n\twebSearchOperations?: AgentSessionConfig[\"webSearchOperations\"];\n\t/**\n\t * Delegation entry point (roadmap Phase 5, ADR 0008). When set, `delegate`\n\t * executes real child sessions through `resolveAgent`, with the child's\n\t * authority derived from this session's live active tools and permission\n\t * store -- never reconstructed. When omitted, `delegate` stays inert (no agent\n\t * types resolve). Agent discovery is injected here as a caller-supplied\n\t * resolver rather than built in, until Phase 5 task 5.5 lands real\n\t * markdown/frontmatter discovery. Requires `permissionGate` to be set too: a\n\t * child's authority has nothing to derive from otherwise.\n\t */\n\tdelegation?: { resolveAgent?: AgentDefinitionResolver };\n\t/**\n\t * Credential store the MCP OAuth connector uses (spec 2026-09-01-mcp-oauth).\n\t * Default: the host AuthStorage, the same fallback the model runtime applies.\n\t */\n\tmcpCredentials?: CredentialStore;\n}\n\n/** Result from createAgentSession */\nexport interface CreateAgentSessionResult {\n\t/** The created session */\n\tsession: AgentSession;\n\t/** Extensions result (for UI context setup in interactive mode) */\n\textensionsResult: LoadExtensionsResult;\n\t/**\n\t * The session's delegation runtime options when delegation is configured\n\t * (undefined otherwise). Exposing the one runtime the `delegate` tool uses\n\t * lets external lifecycle callers (tests) launch\n\t * children through the same authority derivation instead of re-deriving it.\n\t */\n\tdelegationRuntime?: DelegationRuntimeOptions;\n\t/** Warning if session was restored with a different model than saved */\n\tmodelFallbackMessage?: string;\n}\n\n// Re-exports\n\nexport * from \"./agent-session-runtime.ts\";\nexport type {\n\tExtensionAPI,\n\tExtensionCommandContext,\n\tExtensionContext,\n\tExtensionFactory,\n\tInlineExtension,\n\tSlashCommandInfo,\n\tSlashCommandSource,\n\tToolDefinition,\n} from \"./extensions/index.ts\";\nexport type { PromptTemplate } from \"./prompt-templates.ts\";\nexport type { Skill } from \"./skills.ts\";\nexport type { Tool } from \"./tools/index.ts\";\n// Tool factories (for custom cwd)\nexport {\n\tcreateBashTool,\n\tcreateCodingTools,\n\tcreateEditTool,\n\tcreateFindTool,\n\tcreateGrepTool,\n\tcreateLsTool,\n\tcreatePowerShellTool,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateWriteTool,\n\twithFileMutationQueue,\n};\n\n// Helper Functions\n\nfunction getDefaultAgentDir(): string {\n\treturn getAgentDir();\n}\n\n/**\n * Create an AgentSession with the specified options.\n *\n * @example\n * ```typescript\n * // Minimal - uses defaults\n * const { session } = await createAgentSession();\n *\n * // With explicit model\n * import { getModel } from '@earendil-works/pi-ai';\n * const { session } = await createAgentSession({\n * model: getModel('anthropic', 'claude-opus-4-5'),\n * thinkingLevel: 'high',\n * });\n *\n * // Continue previous session\n * const { session, modelFallbackMessage } = await createAgentSession({\n * continueSession: true,\n * });\n *\n * // Full control\n * const loader = new DefaultResourceLoader({\n * cwd: process.cwd(),\n * agentDir: getAgentDir(),\n * settingsManager: SettingsManager.create(),\n * });\n * await loader.reload();\n * const { session } = await createAgentSession({\n * model: myModel,\n * tools: [\"read\", \"bash\"],\n * resourceLoader: loader,\n * sessionManager: SessionManager.inMemory(),\n * });\n * ```\n */\n/** The most recent assistant text in a completed session -- what a delegated child returns to its parent as its result. */\nfunction extractFinalAssistantText(session: AgentSession): string {\n\tconst messages = session.agent.state.messages;\n\tfor (let i = messages.length - 1; i >= 0; i--) {\n\t\tconst message = messages[i];\n\t\tif (message.role !== \"assistant\") continue;\n\t\tconst text = message.content\n\t\t\t.filter(\n\t\t\t\t(content): content is Extract<AgentMessage, { role: \"assistant\" }>[\"content\"][number] & { type: \"text\" } =>\n\t\t\t\t\tcontent.type === \"text\",\n\t\t\t)\n\t\t\t.map((content) => content.text)\n\t\t\t.join(\"\\n\");\n\t\tif (text) return text;\n\t}\n\treturn \"\";\n}\n\nexport async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {\n\tconst cwd = resolvePath(options.cwd ?? options.sessionManager?.getCwd() ?? process.cwd());\n\tconst agentDir = options.agentDir ? resolvePath(options.agentDir) : getDefaultAgentDir();\n\tlet resourceLoader = options.resourceLoader;\n\n\tconst authPath = options.agentDir ? join(agentDir, \"auth.json\") : undefined;\n\tconst modelsPath = options.agentDir ? join(agentDir, \"models.json\") : undefined;\n\tconst modelRuntime = options.modelRuntime ?? (await ModelRuntime.create({ authPath, modelsPath }));\n\n\t// A caller wanting project scope supplies a manager built with the decision. ADR 0034.\n\tconst settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir, { projectTrusted: false });\n\tconst sessionManager = options.sessionManager ?? SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));\n\tconst backgroundShellRegistry = options.backgroundShellRegistry ?? createBackgroundShellRegistry();\n\t// One resolved per-run budget policy: the caller's override when supplied,\n\t// else the settings policy. Children built below inherit this resolved value\n\t// so a child run carries the same per-run policy as its parent without\n\t// re-reading settings (spec 2026-09-09, \"Shared budgets\"). Per-run only: it\n\t// never creates a shared ceiling.\n\tconst runBudget = options.runBudget ?? settingsManager.getRunBudget();\n\t// The root aggregate budget (spec 2026-09-09, \"Shared budgets\"): created\n\t// ONLY when the caller explicitly configures `aggregateBudget` -- never\n\t// derived from the per-run policy. One controller per root tree: the\n\t// parent's own Agent consumes it (composed under its per-prompt controller),\n\t// and buildChildSession forwards THE SAME instance to every child regardless\n\t// of depth, so a grandchild's consumption lands on the root ledger. Its wall\n\t// time starts here, at root session construction, and no descendant can\n\t// reset it: descendants only consume.\n\tconst aggregateBudgetController = options.aggregateBudget\n\t\t? createRunBudgetController(options.aggregateBudget)\n\t\t: undefined;\n\t// The shared ceiling this session consumes and hands to its delegated\n\t// children: the root aggregate when configured, else a caller-supplied\n\t// controller (which is how a delegated child receives its root's aggregate).\n\t// With neither there is NO shared controller at all: every session keeps\n\t// local-only budgets under its inherited per-run policy.\n\tconst sharedBudgetController = aggregateBudgetController ?? options.sharedBudgetController;\n\t// The tree-facing usage snapshot: the aggregate controller's counters cover\n\t// every accepted provider request, tool call, and maintenance request of the\n\t// parent's own runs and of every descendant, so one read reports the tree.\n\tconst aggregateBudgetUsage = sharedBudgetController\n\t\t? (): AggregateBudgetUsageSnapshot => {\n\t\t\t\tconst usage = sharedBudgetController.usage();\n\t\t\t\treturn {\n\t\t\t\t\tproviderRequests: usage.providerRequests,\n\t\t\t\t\ttoolCalls: usage.toolCalls,\n\t\t\t\t\tmaintenanceRequests: usage.maintenanceRequests,\n\t\t\t\t\tstartedAtMs: usage.startedAt,\n\t\t\t\t};\n\t\t\t}\n\t\t: undefined;\n\n\tif (!resourceLoader) {\n\t\tresourceLoader = new DefaultResourceLoader({ cwd, agentDir, settingsManager });\n\t\tawait resourceLoader.reload();\n\t\ttime(\"resourceLoader.reload\");\n\t}\n\n\t// Check if session has existing data to restore\n\tconst existingSession = sessionManager.buildSessionContext();\n\tconst hasExistingSession = existingSession.messages.length > 0;\n\tconst hasThinkingEntry = sessionManager.getBranch().some((entry) => entry.type === \"thinking_level_change\");\n\n\tlet model = options.model;\n\tlet modelFallbackMessage: string | undefined;\n\n\t// If session has data, try to restore model from it\n\tif (!model && hasExistingSession && existingSession.model) {\n\t\tconst restoredModel = modelRuntime.getModel(existingSession.model.provider, existingSession.model.modelId);\n\t\tif (restoredModel && modelRuntime.hasConfiguredAuth(restoredModel.provider)) {\n\t\t\tmodel = restoredModel;\n\t\t}\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = `Could not restore model ${existingSession.model.provider}/${existingSession.model.modelId}`;\n\t\t}\n\t}\n\n\t// If still no model, use findInitialModel (checks settings default, then provider defaults)\n\tif (!model) {\n\t\tconst result = await findInitialModel({\n\t\t\tscopedModels: [],\n\t\t\tisContinuing: hasExistingSession,\n\t\t\tdefaultProvider: settingsManager.getDefaultProvider(),\n\t\t\tdefaultModelId: settingsManager.getDefaultModel(),\n\t\t\tdefaultThinkingLevel: settingsManager.getDefaultThinkingLevel(),\n\t\t\tmodelThinkingLevels: settingsManager.getAllModelThinkingLevels(),\n\t\t\tmodelRuntime,\n\t\t});\n\t\tmodel = result.model;\n\t\tif (!model) {\n\t\t\tmodelFallbackMessage = formatNoModelsAvailableMessage();\n\t\t} else if (modelFallbackMessage) {\n\t\t\tmodelFallbackMessage += `. Using ${model.provider}/${model.id}`;\n\t\t}\n\t}\n\n\tlet thinkingLevel = options.thinkingLevel;\n\n\t// If session has data, restore thinking level from it\n\tif (thinkingLevel === undefined && hasExistingSession) {\n\t\tthinkingLevel = hasThinkingEntry\n\t\t\t? (existingSession.thinkingLevel as ThinkingLevel)\n\t\t\t: (settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL);\n\t}\n\n\t// Fall back to per-model override, then global default\n\tif (thinkingLevel === undefined && model) {\n\t\tconst perModel = settingsManager.getModelThinkingLevel(model.provider, model.id);\n\t\tif (perModel) {\n\t\t\tthinkingLevel = perModel;\n\t\t}\n\t}\n\tif (thinkingLevel === undefined) {\n\t\tthinkingLevel = settingsManager.getDefaultThinkingLevel() ?? DEFAULT_THINKING_LEVEL;\n\t}\n\n\t// Clamp to model capabilities\n\tif (!model) {\n\t\tthinkingLevel = \"off\";\n\t} else {\n\t\tthinkingLevel = clampThinkingLevel(model, thinkingLevel) as ThinkingLevel;\n\t}\n\n\t// OAuth-capable MCP servers resolve tokens through the session's own credential\n\t// seam: direct and lock-serialized, fail-closed when no store applies\n\t// (spec 2026-09-01-mcp-oauth). Servers without `auth: \"oauth\"` never touch it.\n\tconst mcpRuntime = createMcpRuntime(\n\t\tcwd,\n\t\tcreateSessionMcpConnector({\n\t\t\tcredentials: options.mcpCredentials ?? AuthStorage.create(authPath),\n\t\t}),\n\t\t{ projectTrusted: settingsManager.isProjectTrusted(), agentDir },\n\t);\n\n\t// `web_search`, `lsp`, and `mcp` join the core four only when configured. All stay\n\t// registered either way, so an explicit `--tools` selection still reaches them and an\n\t// unconfigured call still explains itself. Activating an unconfigured tool would put a\n\t// name in the prompt that can only ever fail.\n\tconst defaultActiveToolNames = configuredDefaultToolNames({\n\t\tlsp: Boolean(options.lspOperations),\n\t\twebSearch: Boolean(options.webSearchOperations),\n\t\tmcp: Boolean(mcpRuntime),\n\t});\n\tconst savedDefaultToolNames = settingsManager.getDefaultTools();\n\tconst allowedToolNames = options.tools ?? (options.noTools === \"all\" ? [] : undefined);\n\tconst excludedToolNames = options.excludeTools;\n\tconst excludedToolNameSet = excludedToolNames ? new Set(excludedToolNames) : undefined;\n\tconst initialActiveToolNames = (\n\t\toptions.tools ?? (options.noTools ? [] : (savedDefaultToolNames ?? defaultActiveToolNames))\n\t).filter((name) => !excludedToolNameSet?.has(name));\n\n\t// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)\n\tconst convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {\n\t\tconst converted = convertToLlm(messages);\n\t\t// Check setting dynamically so mid-session changes take effect\n\t\tif (!settingsManager.getBlockImages()) {\n\t\t\treturn converted;\n\t\t}\n\t\t// Filter out ImageContent from all messages, replacing with text placeholder\n\t\treturn converted.map((msg) => {\n\t\t\tif (msg.role === \"user\" || msg.role === \"toolResult\") {\n\t\t\t\tconst content = msg.content;\n\t\t\t\tif (Array.isArray(content)) {\n\t\t\t\t\tconst hasImages = content.some((c) => c.type === \"image\");\n\t\t\t\t\tif (hasImages) {\n\t\t\t\t\t\tconst filteredContent = content\n\t\t\t\t\t\t\t.map((c) =>\n\t\t\t\t\t\t\t\tc.type === \"image\" ? { type: \"text\" as const, text: \"Image reading is disabled.\" } : c,\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.filter(\n\t\t\t\t\t\t\t\t(c, i, arr) =>\n\t\t\t\t\t\t\t\t\t// Dedupe consecutive \"Image reading is disabled.\" texts\n\t\t\t\t\t\t\t\t\t!(\n\t\t\t\t\t\t\t\t\t\tc.type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\tc.text === \"Image reading is disabled.\" &&\n\t\t\t\t\t\t\t\t\t\ti > 0 &&\n\t\t\t\t\t\t\t\t\t\tarr[i - 1].type === \"text\" &&\n\t\t\t\t\t\t\t\t\t\t(arr[i - 1] as { type: \"text\"; text: string }).text === \"Image reading is disabled.\"\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\treturn { ...msg, content: filteredContent };\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn msg;\n\t\t});\n\t};\n\n\tconst extensionRunnerRef: { current?: ExtensionRunner } = {};\n\tconst cacheWarmer = new CacheWarmer(\n\t\tmodelRuntime,\n\t\tsessionManager,\n\t\t() => settingsManager.getCacheWarmingMode(),\n\t\tasync (event) => extensionRunnerRef.current?.emitCacheWarmingDecision(event) ?? event.action,\n\t);\n\tconst buildRequestOptions = (\n\t\trequestModel: Model<any>,\n\t\toptions: ModelsSimpleStreamOptions = {},\n\t): ModelsSimpleStreamOptions => {\n\t\tconst providerRetrySettings = settingsManager.getProviderRetrySettings();\n\t\tconst httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();\n\t\tconst effectiveTimeoutMs = httpIdleTimeoutMs === 0 ? 2147483647 : httpIdleTimeoutMs;\n\t\tconst headerRunner = extensionRunnerRef.current;\n\t\treturn {\n\t\t\t...options,\n\t\t\ttimeoutMs: options.timeoutMs ?? providerRetrySettings.timeoutMs ?? effectiveTimeoutMs,\n\t\t\twebsocketConnectTimeoutMs: options.websocketConnectTimeoutMs ?? settingsManager.getWebSocketConnectTimeoutMs(),\n\t\t\tmaxRetries: options.maxRetries ?? providerRetrySettings.maxRetries,\n\t\t\tmaxRetryDelayMs: options.maxRetryDelayMs ?? providerRetrySettings.maxRetryDelayMs,\n\t\t\ttransformHeaders: async (requestHeaders) => {\n\t\t\t\tconst headers = mergeProviderAttributionHeaders(\n\t\t\t\t\trequestModel,\n\t\t\t\t\tsettingsManager,\n\t\t\t\t\toptions.sessionId,\n\t\t\t\t\trequestHeaders,\n\t\t\t\t);\n\t\t\t\treturn headerRunner?.hasHandlers(\"before_provider_headers\")\n\t\t\t\t\t? headerRunner.emitBeforeProviderHeaders(headers ?? {})\n\t\t\t\t\t: (headers ?? {});\n\t\t\t},\n\t\t};\n\t};\n\tconst cacheContextIsCurrent = (requestModel: Model<any>) => {\n\t\tconst messages = agent.state.messages;\n\t\treturn () => {\n\t\t\tconst currentModel = agent.state.model;\n\t\t\tconst currentMessages = agent.state.messages;\n\t\t\treturn (\n\t\t\t\tcurrentModel.provider === requestModel.provider &&\n\t\t\t\tcurrentModel.id === requestModel.id &&\n\t\t\t\tmessages.length <= currentMessages.length &&\n\t\t\t\tmessages.every((message, index) => currentMessages[index] === message)\n\t\t\t);\n\t\t};\n\t};\n\tconst transformProviderPayload = async (payload: unknown) => {\n\t\tconst runner = extensionRunnerRef.current;\n\t\tif (!runner?.hasHandlers(\"before_provider_request\")) return payload;\n\t\treturn runner.emitBeforeProviderRequest(payload);\n\t};\n\tconst handleProviderResponse: NonNullable<ModelsSimpleStreamOptions[\"onResponse\"]> = async (response) => {\n\t\tconst runner = extensionRunnerRef.current;\n\t\tif (!runner?.hasHandlers(\"after_provider_response\")) return;\n\t\tawait runner.emit({\n\t\t\ttype: \"after_provider_response\",\n\t\t\tstatus: response.status,\n\t\t\theaders: response.headers,\n\t\t});\n\t};\n\n\tconst agent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt: \"\",\n\t\t\tmodel,\n\t\t\tthinkingLevel,\n\t\t\ttools: [],\n\t\t\tmessages: existingSession.messages,\n\t\t},\n\t\t// The resolved budget policy from above (override or settings); every\n\t\t// execution mode (interactive, print, JSON, RPC, ACP, SDK) runs through\n\t\t// this session, so none counts on its own\n\t\t// (spec 2026-09-01-tool-reliability-and-execution-budgets.md).\n\t\trunBudget,\n\t\tbudgetScope: options.budgetScope ?? \"prompt\",\n\t\t// The root aggregate when configured (the parent's own runs consume it,\n\t\t// per-prompt local scope unchanged), else a caller-supplied shared\n\t\t// controller, else nothing (spec 2026-09-09, \"Shared budgets\").\n\t\tsharedBudgetController,\n\t\tconvertToLlm: convertToLlmWithBlockImages,\n\t\tstreamFn: async (model, context, options) => {\n\t\t\tconst requestOptions = buildRequestOptions(model, options);\n\t\t\t// Compaction and summaries use their own routing ids; only session requests\n\t\t\t// replace the cache entry, so warming restarts from them. Keep warming while\n\t\t\t// the current transcript still extends the request's prefix. Agent state may\n\t\t\t// shallow-copy the messages array or refresh the model object without changing\n\t\t\t// the provider request, so top-level object identity is not a valid cache key.\n\t\t\tif (options?.sessionId === sessionManager.getSessionId()) {\n\t\t\t\tcacheWarmer.start({ model, context, options: requestOptions }, cacheContextIsCurrent(model));\n\t\t\t}\n\t\t\treturn modelRuntime.streamSimple(model, context, requestOptions);\n\t\t},\n\t\tonPayload: transformProviderPayload,\n\t\tonResponse: handleProviderResponse,\n\t\tsessionId: sessionManager.getSessionId(),\n\t\ttransformContext: async (messages) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner) return messages;\n\t\t\treturn runner.emitContext(messages);\n\t\t},\n\t\tsteeringMode: settingsManager.getSteeringMode(),\n\t\tfollowUpMode: settingsManager.getFollowUpMode(),\n\t\ttransport: settingsManager.getTransport(),\n\t\tthinkingBudgets: settingsManager.getThinkingBudgets(),\n\t\tmaxRetryDelayMs: settingsManager.getProviderRetrySettings().maxRetryDelayMs,\n\t});\n\n\t// Restore missing settings metadata for older sessions.\n\tif (hasExistingSession) {\n\t\tif (!hasThinkingEntry) {\n\t\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t\t}\n\t} else {\n\t\t// Save initial model and thinking level for new sessions so they can be restored on resume\n\t\tif (model) {\n\t\t\tsessionManager.appendModelChange(model.provider, model.id);\n\t\t}\n\t\tsessionManager.appendThinkingLevelChange(thinkingLevel);\n\t}\n\n\t// Forward ref, mirroring extensionRunnerRef above: the delegation runtime's\n\t// closures are needed to build `customTools` before `session` exists, but they\n\t// must read the *live* session (active tools can change after construction),\n\t// not a snapshot frozen at this point.\n\tconst parentSessionRef: { current?: AgentSession } = {};\n\tconst childRunRegistry = options.childRunRegistry ?? new ChildRunRegistry();\n\tconst customTools: ToolDefinition[] = options.customTools ? [...options.customTools] : [];\n\t// A configured permission gate supplies the authority a child must derive, so\n\t// discovery is enabled by default in real sessions. Callers that need fixture\n\t// definitions may still provide an explicit resolver.\n\tconst delegation = options.delegation ?? (options.permissionGate ? {} : undefined);\n\tlet delegationRuntime: DelegationRuntimeOptions | undefined;\n\tif (delegation) {\n\t\tconst parentPermissionGate = options.permissionGate;\n\t\tconst resolveAgent =\n\t\t\tdelegation.resolveAgent ??\n\t\t\tcreateAgentDefinitionResolver({\n\t\t\t\tcwd,\n\t\t\t\tagentDir,\n\t\t\t\tisProjectTrusted: () => settingsManager.isProjectTrusted(),\n\t\t\t});\n\t\tlet worktreeOwner: GitWorktreeWorkspaceOwner | undefined;\n\t\t// One read of the delegation bound for this runtime; the policy snapshot\n\t\t// below reports the same value it enforces instead of re-reading settings.\n\t\tconst delegationMaxDepth = settingsManager.getDelegationMaxDepth();\n\t\tdelegationRuntime = {\n\t\t\tchildRunRegistry,\n\t\t\tresolveAgent,\n\t\t\t// Concurrency cap (spec 2026-09-09, \"Shared budgets\"): the registry's\n\t\t\t// admission path reads it from the runtime options. Undefined is\n\t\t\t// unlimited.\n\t\t\tmaxConcurrentChildren: options.maxConcurrentChildren,\n\t\t\tgetParentCapabilities: () => {\n\t\t\t\tconst capabilities = new Set<Capability>();\n\t\t\t\tfor (const name of parentSessionRef.current?.getActiveToolNames() ?? []) {\n\t\t\t\t\tconst contract = (\n\t\t\t\t\t\tparentSessionRef.current?.getToolDefinition(name) as Partial<ApexToolDefinition> | undefined\n\t\t\t\t\t)?.contract;\n\t\t\t\t\tif (contract) for (const capability of contract.capabilities) capabilities.add(capability);\n\t\t\t\t}\n\t\t\t\treturn capabilities;\n\t\t\t},\n\t\t\tgetToolCapabilities: (toolName) => {\n\t\t\t\t// Prefer the parent's own live registry first (it also covers\n\t\t\t\t// extension/custom tools an agent definition might name), but fall back\n\t\t\t\t// to the canonical built-in registry: \"what capability does tool X\n\t\t\t\t// need\" is a property of the tool itself, independent of whether the\n\t\t\t\t// parent's own session restricted it out of its active/allowed set via\n\t\t\t\t// `tools`/`excludeTools` -- that restriction affects the parent's own\n\t\t\t\t// *authority* (getParentCapabilities, above), not what a named tool is.\n\t\t\t\tconst fromParent = (\n\t\t\t\t\tparentSessionRef.current?.getToolDefinition(toolName) as Partial<ApexToolDefinition> | undefined\n\t\t\t\t)?.contract?.capabilities;\n\t\t\t\tif (fromParent) return fromParent;\n\t\t\t\treturn (createAllToolDefinitions(cwd)[toolName as ToolName] as ApexToolDefinition | undefined)?.contract\n\t\t\t\t\t?.capabilities;\n\t\t\t},\n\t\t\t// Read fresh each call, not captured once: depth is a live property of\n\t\t\t// the (possibly delegated) session this createAgentSession call is\n\t\t\t// building, via the same sessionManager this closure already captures.\n\t\t\tgetDelegationDepth: () => sessionManager.getDelegationDepth(),\n\t\t\tmaxDelegationDepth: delegationMaxDepth,\n\t\t\tgetParentSessionDir: () => sessionManager.getSessionDir(),\n\t\t\t// Child-run records carry the parent session id so durable records and\n\t\t\t// protocol payloads tie each child to this session's own identity.\n\t\t\tgetParentSessionId: () => sessionManager.getSessionId(),\n\t\t\t// Worktree isolation (spec 2026-09-09, \"Parallel work and ownership\"):\n\t\t\t// the production workspace owner, constructed on the first worktree-\n\t\t\t// isolated delegation only. Shared-read delegations never read this\n\t\t\t// property (the runtime consults the owner only for isolation\n\t\t\t// \"worktree\"), so they never construct an owner and never invoke git.\n\t\t\tget workspaceOwner() {\n\t\t\t\tif (worktreeOwner === undefined) {\n\t\t\t\t\tworktreeOwner = new GitWorktreeWorkspaceOwner(cwd);\n\t\t\t\t}\n\t\t\t\treturn worktreeOwner;\n\t\t\t},\n\t\t\tbuildChildSession: async ({\n\t\t\t\tdefinition,\n\t\t\t\ttoolNames,\n\t\t\t\tcapabilities,\n\t\t\t\tdepth,\n\t\t\t\tsessionId,\n\t\t\t\tartifactDir,\n\t\t\t\tworkspace,\n\t\t\t\treattachSessionPath,\n\t\t\t\ttimeoutMs,\n\t\t\t}) => {\n\t\t\t\tif (!parentPermissionGate) {\n\t\t\t\t\tthrow new Error(\"delegate requires a permission gate configured on the parent session (ADR 0008).\");\n\t\t\t\t}\n\t\t\t\tconst childModel =\n\t\t\t\t\t(definition.model && modelRuntime.getAvailableSnapshot().find((m) => m.id === definition.model)) ||\n\t\t\t\t\tmodel;\n\t\t\t\tif (!childModel) {\n\t\t\t\t\tthrow new Error(`Cannot delegate to \"${definition.name}\": no model is available for the child session.`);\n\t\t\t\t}\n\t\t\t\t// The derived policy snapshot (spec 2026-09-09, \"Derive, do not\n\t\t\t\t// reconstruct\"): the exact values this construction used -- the\n\t\t\t\t// ceiling-checked tool allowlist, the admitted capability set from\n\t\t\t\t// the runtime's single admission projection (never a second\n\t\t\t\t// classification, ADR 0010), the delegation bound it enforces, the\n\t\t\t\t// child's model, and the budget wiring every child is built with\n\t\t\t\t// (session-scoped own controller; aggregate only when a shared ceiling\n\t\t\t\t// was configured at the root). It rides on the returned handle so the\n\t\t\t\t// registry persists it on the child-run record.\n\t\t\t\tconst policy: ChildRunPolicySnapshot = {\n\t\t\t\t\ttools: [...toolNames],\n\t\t\t\t\tcapabilities: [...capabilities].sort(),\n\t\t\t\t\tmaxDelegationDepth: delegationMaxDepth,\n\t\t\t\t\tmodel: childModel.id,\n\t\t\t\t\tbudgetScope: \"session\",\n\t\t\t\t\taggregateBudget: sharedBudgetController !== undefined,\n\t\t\t\t};\n\t\t\t\t// An isolated child runs inside the workspace owner's prepared root\n\t\t\t\t// (its linked worktree); a shared child keeps this session's cwd.\n\t\t\t\tconst childCwd = workspace?.root ?? cwd;\n\t\t\t\t// The one reattachment seam (restart reconstruction): when the request\n\t\t\t\t// carries the existing child session path, that transcript is OPENED\n\t\t\t\t// rather than created; identity (header id, parentSession, depth) comes\n\t\t\t\t// from the file's own header. Every other input below is derived\n\t\t\t\t// exactly as for a fresh delegation -- permission store, model, tools,\n\t\t\t\t// budget -- so there is no second construction path.\n\t\t\t\tconst childSessionManager = reattachSessionPath\n\t\t\t\t\t? SessionManager.open(reattachSessionPath, artifactDir)\n\t\t\t\t\t: artifactDir\n\t\t\t\t\t\t? SessionManager.create(childCwd, artifactDir, {\n\t\t\t\t\t\t\t\tid: sessionId,\n\t\t\t\t\t\t\t\tparentSession: sessionManager.getSessionId(),\n\t\t\t\t\t\t\t\tdelegationDepth: depth,\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t: SessionManager.inMemory(childCwd, {\n\t\t\t\t\t\t\t\tparentSession: sessionManager.getSessionId(),\n\t\t\t\t\t\t\t\tdelegationDepth: depth,\n\t\t\t\t\t\t\t});\n\t\t\t\tconst derivedStore = new DerivedPermissionRuleStore({ parent: parentPermissionGate.store });\n\t\t\t\tconst { session: childSession } = await createAgentSession({\n\t\t\t\t\tcwd: childCwd,\n\t\t\t\t\tagentDir,\n\t\t\t\t\tmodel: childModel,\n\t\t\t\t\tmodelRuntime,\n\t\t\t\t\tsettingsManager,\n\t\t\t\t\t// The parent's already-resolved per-run budget (override or settings),\n\t\t\t\t\t// not a fresh settings read: every child run carries the same per-run\n\t\t\t\t\t// policy as its parent (spec 2026-09-09, \"Shared budgets\").\n\t\t\t\t\t//\n\t\t\t\t\t// Session scope + the root aggregate: a resumed or follow-up child\n\t\t\t\t\t// turn continues the same child budget (one controller per child\n\t\t\t\t\t// Agent, created at its first turn), and every gate also answers\n\t\t\t\t\t// to the tree's shared ceiling -- accepted consumption records to\n\t\t\t\t\t// both the child's own controller and the aggregate. THE SAME\n\t\t\t\t\t// aggregate instance reaches every descendant regardless of\n\t\t\t\t\t// depth (a grandchild's parent forwards it onward), so there is\n\t\t\t\t\t// exactly ONE aggregate per root tree and no child can reset it\n\t\t\t\t\t// by resuming or spawning children (spec 2026-09-09, \"Shared\n\t\t\t\t\t// budgets\"). Without an aggregate there is no shared controller:\n\t\t\t\t\t// the child keeps local-only budgets under the inherited policy.\n\t\t\t\t\t//\n\t\t\t\t\t// A launch timeout (spec 2026-09-09, timeouts) overrides the\n\t\t\t\t\t// child's OWN wall-time limit with the requested timeoutMs, so the\n\t\t\t\t\t// existing AgentRunBudget wall-time gate enforces it mid-run; the\n\t\t\t\t\t// parent policy's other limits still apply unchanged.\n\t\t\t\t\trunBudget: timeoutMs === undefined ? runBudget : { ...runBudget, maxWallTimeMs: timeoutMs },\n\t\t\t\t\tbudgetScope: \"session\",\n\t\t\t\t\tsharedBudgetController,\n\t\t\t\t\t// The same per-parent admission cap applies to a child's own\n\t\t\t\t\t// delegations (each runtime counts its direct children).\n\t\t\t\t\tmaxConcurrentChildren: options.maxConcurrentChildren,\n\t\t\t\t\tsessionManager: childSessionManager,\n\t\t\t\t\tresourceLoader,\n\t\t\t\t\ttools: toolNames,\n\t\t\t\t\t// No responder: a child's `ask` fails closed rather than prompting the\n\t\t\t\t\t// human for a call the human did not make (ADR 0008, \"Who answers a\n\t\t\t\t\t// child's ask\").\n\t\t\t\t\tpermissionGate: {\n\t\t\t\t\t\tstore: derivedStore,\n\t\t\t\t\t\tgetMode: () =>\n\t\t\t\t\t\t\tparentSessionRef.current?.getInteractivePermissionMode() ?? parentPermissionGate.getMode(),\n\t\t\t\t\t},\n\t\t\t\t\t// A delegated child shares the parent's cwd-bound LSP pool (spec, \"no\n\t\t\t\t\t// multi-root LSP connection and no sharing across cwd-bound service\n\t\t\t\t\t// lifetimes\" -- delegated children are the one sharing exception).\n\t\t\t\t\tdiagnosticsOperations: options.diagnosticsOperations,\n\t\t\t\t\tlspOperations: options.lspOperations,\n\t\t\t\t\t// Shared for the same reason as the LSP pool: the backend was resolved\n\t\t\t\t\t// once (possibly by running a shell command for the key) and a child\n\t\t\t\t\t// that re-resolved nothing would hold a `web_search` that only throws.\n\t\t\t\t\twebSearchOperations: options.webSearchOperations,\n\t\t\t\t\t// `checkpointSettings` is deliberately *not* forwarded. A child shares the\n\t\t\t\t\t// parent's cwd, so it would write refs under its own session id into the\n\t\t\t\t\t// same repository and interleave them with the parent's. The parent's\n\t\t\t\t\t// per-turn capture already covers everything a child does inside that turn.\n\n\t\t\t\t\t// Forwarded so a child that itself holds `delegate` can delegate again\n\t\t\t\t\t// (bounded by the depth guard above, read fresh from its own session\n\t\t\t\t\t// header on its next createAgentSession call) -- otherwise recursion\n\t\t\t\t\t// would be silently capped at one level regardless of maxDelegationDepth.\n\t\t\t\t\tdelegation,\n\t\t\t\t});\n\t\t\t\tawait childSession.bindExtensions({});\n\t\t\t\tlet status: import(\"./delegation/runtime.ts\").ChildSessionStatus = \"idle\";\n\t\t\t\tlet runPromise: Promise<void> | undefined;\n\t\t\t\tlet lastTurnResult: ChildTurnResult | undefined;\n\t\t\t\t// The captured structured terminal outcome of the run in flight\n\t\t\t\t// (`agent_end`'s stopReason). Budget refusals emit agent_end without\n\t\t\t\t// an assistant message, so this is the only way the settlement below\n\t\t\t\t// can see and name them.\n\t\t\t\tlet runStopReason: AgentStopReason | undefined;\n\t\t\t\tconst ensureOpen = () => {\n\t\t\t\t\tif (status === \"closed\") throw new Error(\"Child session is closed.\");\n\t\t\t\t};\n\t\t\t\tconst start = (input: string): Promise<void> => {\n\t\t\t\t\tensureOpen();\n\t\t\t\t\tif (runPromise) throw new Error(\"Child session is already running.\");\n\t\t\t\t\tstatus = \"running\";\n\t\t\t\t\trunStopReason = undefined;\n\t\t\t\t\t// Capture the run's structured terminal outcome: a budget refusal\n\t\t\t\t\t// emits agent_end without producing an assistant message, so the\n\t\t\t\t\t// message-based checks below cannot see it.\n\t\t\t\t\tconst unsubscribeStop = childSession.subscribe((event) => {\n\t\t\t\t\t\tif (event.type === \"agent_end\") runStopReason = event.stopReason;\n\t\t\t\t\t});\n\t\t\t\t\tconst pending = childSession\n\t\t\t\t\t\t.prompt(input)\n\t\t\t\t\t\t.then(\n\t\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t\tunsubscribeStop();\n\t\t\t\t\t\t\t\tif (status === \"closed\") {\n\t\t\t\t\t\t\t\t\tlastTurnResult = { outcome: \"failed\", output: \"\" };\n\t\t\t\t\t\t\t\t\tthrow new Error(\"Child session is closed.\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (status === \"interrupted\") {\n\t\t\t\t\t\t\t\t\tlastTurnResult = { outcome: \"interrupted\", output: extractFinalAssistantText(childSession) };\n\t\t\t\t\t\t\t\t\tthrow new Error(\"Child session is interrupted.\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tconst lastAssistant = [...childSession.agent.state.messages]\n\t\t\t\t\t\t\t\t\t.reverse()\n\t\t\t\t\t\t\t\t\t.find((message) => message.role === \"assistant\");\n\t\t\t\t\t\t\t\tif (\n\t\t\t\t\t\t\t\t\tlastAssistant?.role === \"assistant\" &&\n\t\t\t\t\t\t\t\t\t(lastAssistant.stopReason === \"aborted\" || lastAssistant.stopReason === \"error\")\n\t\t\t\t\t\t\t\t) {\n\t\t\t\t\t\t\t\t\tstatus = \"interrupted\";\n\t\t\t\t\t\t\t\t\tlastTurnResult = {\n\t\t\t\t\t\t\t\t\t\toutcome: lastAssistant.stopReason === \"aborted\" ? \"interrupted\" : \"failed\",\n\t\t\t\t\t\t\t\t\t\toutput: lastAssistant.errorMessage || extractFinalAssistantText(childSession),\n\t\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\t\tthrow new Error(lastAssistant.errorMessage || \"Child session was interrupted.\");\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tstatus = \"idle\";\n\t\t\t\t\t\t\t\tlastTurnResult = { outcome: \"completed\", output: extractFinalAssistantText(childSession) };\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t(error) => {\n\t\t\t\t\t\t\t\tunsubscribeStop();\n\t\t\t\t\t\t\t\tif (status !== \"closed\") status = \"interrupted\";\n\t\t\t\t\t\t\t\tlastTurnResult =\n\t\t\t\t\t\t\t\t\tstatus === \"closed\"\n\t\t\t\t\t\t\t\t\t\t? { outcome: \"failed\", output: \"\" }\n\t\t\t\t\t\t\t\t\t\t: { outcome: \"interrupted\", output: extractFinalAssistantText(childSession) };\n\t\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.finally(() => {\n\t\t\t\t\t\t\trunPromise = undefined;\n\t\t\t\t\t\t});\n\t\t\t\t\trunPromise = pending;\n\t\t\t\t\treturn pending;\n\t\t\t\t};\n\t\t\t\tconst sendInput = (input: string) => {\n\t\t\t\t\tensureOpen();\n\t\t\t\t\tif (runPromise) {\n\t\t\t\t\t\tif (status !== \"running\") throw new Error(\"Child session is still stopping.\");\n\t\t\t\t\t\t// A follow-up queued onto a live run settles through that run's\n\t\t\t\t\t\t// own loop; only turns that settle here pass through the gate.\n\t\t\t\t\t\treturn childSession.followUp(input);\n\t\t\t\t\t}\n\t\t\t\t\treturn start(input).then(() => requireVerifiedTurn());\n\t\t\t\t};\n\t\t\t\tconst close = () => {\n\t\t\t\t\tif (status === \"closed\") return;\n\t\t\t\t\tstatus = \"closed\";\n\t\t\t\t\tchildSession.dispose();\n\t\t\t\t};\n\t\t\t\t// Verification gate (spec 2026-09-09, \"Verification at completion\"):\n\t\t\t\t// a child with configured verification policies may not settle a turn\n\t\t\t\t// as successful unless the session's canonical tracker reports\n\t\t\t\t// \"verified\". The tracker is the only verifier; no child-local one is\n\t\t\t\t// introduced. With the \"explicit\" boundary the gate runs\n\t\t\t\t// requestVerification once here; with the \"post-turn\" boundary the\n\t\t\t\t// session already ran verification at its own turn boundary, so the\n\t\t\t\t// gate only reads the status. Any other completion status --\n\t\t\t\t// failed/unavailable/interrupted/continued-unverified -- fails the\n\t\t\t\t// turn with the status named in the error.\n\t\t\t\tconst requireVerifiedTurn = async (): Promise<void> => {\n\t\t\t\t\tconst policyIds = childSession.verificationPolicyIds();\n\t\t\t\t\tif (policyIds.length === 0) return;\n\t\t\t\t\tconst verificationStatus =\n\t\t\t\t\t\tchildSession.verificationBoundary() === \"explicit\"\n\t\t\t\t\t\t\t? await childSession.requestVerification()\n\t\t\t\t\t\t\t: childSession.verificationStatus();\n\t\t\t\t\tif (verificationStatus === \"verified\") return;\n\t\t\t\t\tconst message = `Child verification failed: ${verificationStatus} (policies: ${policyIds.join(\", \")}).`;\n\t\t\t\t\tlastTurnResult = { outcome: \"failed\", output: message };\n\t\t\t\t\tthrow new Error(message);\n\t\t\t\t};\n\t\t\t\treturn {\n\t\t\t\t\tget status() {\n\t\t\t\t\t\treturn status;\n\t\t\t\t\t},\n\t\t\t\t\tlatestResult: () => lastTurnResult,\n\t\t\t\t\tusage: () => childSession.agent.budgetUsage(),\n\t\t\t\t\t// The usage-rollup seam (spec 2026-09-09): the registry reads the\n\t\t\t\t\t// child's own transcript file when one exists, and falls back to\n\t\t\t\t\t// these in-memory entries only for a child that never persisted a\n\t\t\t\t\t// transcript. Read-only; the child's SessionManager stays the owner.\n\t\t\t\t\tsessionEntries: () => childSessionManager.getEntries(),\n\t\t\t\t\tasync run(task: string) {\n\t\t\t\t\t\tconst prompt = definition.systemPrompt\n\t\t\t\t\t\t\t? `${definition.systemPrompt}\\n\\nTask: ${task}`\n\t\t\t\t\t\t\t: `Task: ${task}`;\n\t\t\t\t\t\tawait start(prompt);\n\t\t\t\t\t\tif (status === \"closed\" || status === \"interrupted\") throw new Error(`Child session is ${status}.`);\n\t\t\t\t\t\t// The launch timeout's settlement error path (spec 2026-09-09,\n\t\t\t\t\t\t// timeouts): a run refused at the child's own wall-time gate never\n\t\t\t\t\t\t// started meaningful work, so the delegation settles failed with\n\t\t\t\t\t\t// the exhausted limit named, reusing the budget's existing\n\t\t\t\t\t\t// exhaustedLimit vocabulary. Other budget refusals keep their\n\t\t\t\t\t\t// existing semantics (the refusal is named in the child transcript\n\t\t\t\t\t\t// and the prior result stands).\n\t\t\t\t\t\tif (runStopReason?.kind === \"budget-exhausted\" && runStopReason.limit === \"wall-time\") {\n\t\t\t\t\t\t\tconst message = `Child run stopped before completing: the run's ${runStopReason.limit} budget was exhausted.`;\n\t\t\t\t\t\t\tlastTurnResult = { outcome: \"failed\", output: message };\n\t\t\t\t\t\t\tthrow new Error(message);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Delegated work cannot claim success when the child has a\n\t\t\t\t\t\t// required verification boundary and verification did not pass.\n\t\t\t\t\t\t// Use the session's canonical tracker rather than introducing\n\t\t\t\t\t\t// a second child-only verifier.\n\t\t\t\t\t\tawait requireVerifiedTurn();\n\t\t\t\t\t\treturn { output: extractFinalAssistantText(childSession) };\n\t\t\t\t\t},\n\t\t\t\t\twait: async () => {\n\t\t\t\t\t\twhile (runPromise) await runPromise;\n\t\t\t\t\t},\n\t\t\t\t\tinterrupt: () => {\n\t\t\t\t\t\tensureOpen();\n\t\t\t\t\t\tif (status === \"running\") {\n\t\t\t\t\t\t\tstatus = \"interrupted\";\n\t\t\t\t\t\t\tvoid childSession.abort().catch(() => undefined);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tclose,\n\t\t\t\t\tsendInput,\n\t\t\t\t\tfollowUp: sendInput,\n\t\t\t\t\tdispose: close,\n\t\t\t\t\t// Construction linkage (spec 2026-09-09, policy/artifact\n\t\t\t\t\t// linkage): the registry relays these onto the persisted child_run\n\t\t\t\t\t// record and every status/wait payload derived from it.\n\t\t\t\t\tpolicy,\n\t\t\t\t};\n\t\t\t},\n\t\t};\n\t\tcustomTools.push(createDelegateToolDefinition(delegationRuntime) as ToolDefinition<any, any>);\n\t\t// Historical resume (restart reconstruction) reattaches a persisted child\n\t\t// through THIS runtime's buildChildSession seam. Wiring it here -- not only\n\t\t// at the first delegation -- is what makes resume work on a freshly\n\t\t// reopened session before any new delegation has run.\n\t\tchildRunRegistry.setRuntimeOptions(delegationRuntime);\n\t}\n\n\tconst session = new AgentSession({\n\t\tchildRunRegistry,\n\t\tdelegationRuntime,\n\t\tagent,\n\t\t// Root aggregate usage (spec 2026-09-09, \"Shared budgets\"): undefined\n\t\t// when the tree has no aggregate budget.\n\t\taggregateBudgetUsage,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tcwd,\n\t\tscopedModels: options.scopedModels,\n\t\tresourceLoader,\n\t\tcustomTools,\n\t\tmodelRuntime,\n\t\tcacheWarmer,\n\t\tinitialActiveToolNames,\n\t\timplicitDefaultToolNames: defaultActiveToolNames,\n\t\tallowedToolNames,\n\t\texcludedToolNames,\n\t\textensionRunnerRef,\n\t\tsessionStartEvent: options.sessionStartEvent,\n\t\tpermissionGate: options.permissionGate,\n\t\tpermissionResponderFactory: options.permissionResponderFactory,\n\t\tevidenceSink: options.evidenceSink,\n\t\tdiagnosticsOperations: options.diagnosticsOperations,\n\t\tlspOperations: options.lspOperations,\n\t\twebSearchOperations: options.webSearchOperations,\n\t\tmcpRuntime,\n\t\tcheckpointSettings: settingsManager.getCheckpointSettings(),\n\t\t// Declarative hooks (spec 2026-08-31-declarative-hooks): assembled from the\n\t\t// merged settings -- project-scope entries only appear once trusted. A\n\t\t// malformed hooks key throws here, failing closed at wiring. Deliberately\n\t\t// not forwarded to delegation children (see the spec's boundary note).\n\t\thookRuntime: options.hookRuntime ?? loadHookRuntime(settingsManager.getHookSettings()),\n\t\tbackgroundShellRegistry,\n\t});\n\tparentSessionRef.current = session;\n\t// Eager servers connect here rather than on first use, so their tools are in the\n\t// cache before the model's first search. Detached: a slow or broken server delays\n\t// nothing, and `warmEagerServers` already swallows its own failures.\n\tvoid mcpRuntime?.warm();\n\tconst extensionsResult = resourceLoader.getExtensions();\n\n\treturn {\n\t\tsession,\n\t\textensionsResult,\n\t\tdelegationRuntime,\n\t\tmodelFallbackMessage,\n\t};\n}\n"]}
package/dist/core/sdk.js CHANGED
@@ -7,6 +7,7 @@ import { AgentSession } from "./agent-session.js";
7
7
  import { formatNoModelsAvailableMessage } from "./auth-guidance.js";
8
8
  import { AuthStorage } from "./auth-storage.js";
9
9
  import { CacheWarmer } from "./cache-warmer.js";
10
+ import { configuredDefaultToolNames } from "./default-tool-names.js";
10
11
  import { DEFAULT_THINKING_LEVEL } from "./defaults.js";
11
12
  import { createAgentDefinitionResolver } from "./delegation/agents.js";
12
13
  import { ChildRunRegistry, } from "./delegation/runtime.js";
@@ -210,17 +211,16 @@ export async function createAgentSession(options = {}) {
210
211
  // registered either way, so an explicit `--tools` selection still reaches them and an
211
212
  // unconfigured call still explains itself. Activating an unconfigured tool would put a
212
213
  // name in the prompt that can only ever fail.
213
- const defaultActiveToolNames = [
214
- ...["read", "bash", "edit", "write"],
215
- ...(options.lspOperations ? ["lsp"] : []),
216
- ...(options.webSearchOperations ? ["web_search"] : []),
217
- ...(mcpRuntime ? ["mcp"] : []),
218
- ];
219
- const configuredDefaultToolNames = settingsManager.getDefaultTools();
214
+ const defaultActiveToolNames = configuredDefaultToolNames({
215
+ lsp: Boolean(options.lspOperations),
216
+ webSearch: Boolean(options.webSearchOperations),
217
+ mcp: Boolean(mcpRuntime),
218
+ });
219
+ const savedDefaultToolNames = settingsManager.getDefaultTools();
220
220
  const allowedToolNames = options.tools ?? (options.noTools === "all" ? [] : undefined);
221
221
  const excludedToolNames = options.excludeTools;
222
222
  const excludedToolNameSet = excludedToolNames ? new Set(excludedToolNames) : undefined;
223
- const initialActiveToolNames = (options.tools ?? (options.noTools ? [] : (configuredDefaultToolNames ?? defaultActiveToolNames))).filter((name) => !excludedToolNameSet?.has(name));
223
+ const initialActiveToolNames = (options.tools ?? (options.noTools ? [] : (savedDefaultToolNames ?? defaultActiveToolNames))).filter((name) => !excludedToolNameSet?.has(name));
224
224
  // Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)
225
225
  const convertToLlmWithBlockImages = (messages) => {
226
226
  const converted = convertToLlm(messages);
@@ -519,7 +519,10 @@ export async function createAgentSession(options = {}) {
519
519
  // No responder: a child's `ask` fails closed rather than prompting the
520
520
  // human for a call the human did not make (ADR 0008, "Who answers a
521
521
  // child's ask").
522
- permissionGate: { store: derivedStore, getMode: parentPermissionGate.getMode },
522
+ permissionGate: {
523
+ store: derivedStore,
524
+ getMode: () => parentSessionRef.current?.getInteractivePermissionMode() ?? parentPermissionGate.getMode(),
525
+ },
523
526
  // A delegated child shares the parent's cwd-bound LSP pool (spec, "no
524
527
  // multi-root LSP connection and no sharing across cwd-bound service
525
528
  // lifetimes" -- delegated children are the one sharing exception).
@@ -729,6 +732,7 @@ export async function createAgentSession(options = {}) {
729
732
  modelRuntime,
730
733
  cacheWarmer,
731
734
  initialActiveToolNames,
735
+ implicitDefaultToolNames: defaultActiveToolNames,
732
736
  allowedToolNames,
733
737
  excludedToolNames,
734
738
  extensionRunnerRef,