apex-code 0.0.1-alpha.8 → 0.0.4
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.
- package/CHANGELOG.md +231 -0
- package/README.md +9 -8
- package/dist/cli/args.d.ts +26 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +72 -10
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/file-processor.d.ts.map +1 -1
- package/dist/cli/file-processor.js +2 -1
- package/dist/cli/file-processor.js.map +1 -1
- package/dist/cli/mcp-command.d.ts +9 -0
- package/dist/cli/mcp-command.d.ts.map +1 -0
- package/dist/cli/mcp-command.js +53 -0
- package/dist/cli/mcp-command.js.map +1 -0
- package/dist/cli/project-trust.d.ts.map +1 -1
- package/dist/cli/project-trust.js +3 -1
- package/dist/cli/project-trust.js.map +1 -1
- package/dist/cli/startup-ui.d.ts.map +1 -1
- package/dist/cli/startup-ui.js +2 -1
- package/dist/cli/startup-ui.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +94 -5
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +3 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +45 -18
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +3 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session.d.ts +183 -15
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +728 -117
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-storage.d.ts.map +1 -1
- package/dist/core/auth-storage.js +6 -7
- package/dist/core/auth-storage.js.map +1 -1
- package/dist/core/bash-executor.d.ts.map +1 -1
- package/dist/core/bash-executor.js +7 -0
- package/dist/core/bash-executor.js.map +1 -1
- package/dist/core/checkpoints/git-checkpoints.d.ts +39 -0
- package/dist/core/checkpoints/git-checkpoints.d.ts.map +1 -0
- package/dist/core/checkpoints/git-checkpoints.js +251 -0
- package/dist/core/checkpoints/git-checkpoints.js.map +1 -0
- package/dist/core/checkpoints/session-checkpoints.d.ts +23 -0
- package/dist/core/checkpoints/session-checkpoints.d.ts.map +1 -0
- package/dist/core/checkpoints/session-checkpoints.js +36 -0
- package/dist/core/checkpoints/session-checkpoints.js.map +1 -0
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +7 -3
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +9 -3
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +59 -36
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/defaults.d.ts +1 -0
- package/dist/core/defaults.d.ts.map +1 -1
- package/dist/core/defaults.js +9 -0
- package/dist/core/defaults.js.map +1 -1
- package/dist/core/delegation/runtime.d.ts +6 -5
- package/dist/core/delegation/runtime.d.ts.map +1 -1
- package/dist/core/delegation/runtime.js +4 -3
- package/dist/core/delegation/runtime.js.map +1 -1
- package/dist/core/durable-state/sqlite.d.ts.map +1 -1
- package/dist/core/durable-state/sqlite.js +26 -2
- package/dist/core/durable-state/sqlite.js.map +1 -1
- package/dist/core/exec.d.ts.map +1 -1
- package/dist/core/exec.js +5 -2
- package/dist/core/exec.js.map +1 -1
- package/dist/core/extensions/index.d.ts +2 -2
- package/dist/core/extensions/index.d.ts.map +1 -1
- package/dist/core/extensions/index.js +1 -1
- package/dist/core/extensions/index.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +123 -50
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/runner.d.ts +5 -0
- package/dist/core/extensions/runner.d.ts.map +1 -1
- package/dist/core/extensions/runner.js +45 -1
- package/dist/core/extensions/runner.js.map +1 -1
- package/dist/core/extensions/source-runtime.d.ts +14 -0
- package/dist/core/extensions/source-runtime.d.ts.map +1 -0
- package/dist/core/extensions/source-runtime.js +56 -0
- package/dist/core/extensions/source-runtime.js.map +1 -0
- package/dist/core/extensions/types.d.ts +60 -11
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js +3 -0
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/footer-data-provider.d.ts +1 -1
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +1 -1
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/formatter-lifecycle.d.ts +69 -0
- package/dist/core/formatter-lifecycle.d.ts.map +1 -0
- package/dist/core/formatter-lifecycle.js +360 -0
- package/dist/core/formatter-lifecycle.js.map +1 -0
- package/dist/core/hooks/command-handler.d.ts +19 -0
- package/dist/core/hooks/command-handler.d.ts.map +1 -0
- package/dist/core/hooks/command-handler.js +112 -0
- package/dist/core/hooks/command-handler.js.map +1 -0
- package/dist/core/hooks/http-handler.d.ts +10 -0
- package/dist/core/hooks/http-handler.d.ts.map +1 -0
- package/dist/core/hooks/http-handler.js +36 -0
- package/dist/core/hooks/http-handler.js.map +1 -0
- package/dist/core/hooks/loader.d.ts +29 -0
- package/dist/core/hooks/loader.d.ts.map +1 -0
- package/dist/core/hooks/loader.js +139 -0
- package/dist/core/hooks/loader.js.map +1 -0
- package/dist/core/hooks/runtime.d.ts +13 -0
- package/dist/core/hooks/runtime.d.ts.map +1 -0
- package/dist/core/hooks/runtime.js +63 -0
- package/dist/core/hooks/runtime.js.map +1 -0
- package/dist/core/hooks/types.d.ts +83 -0
- package/dist/core/hooks/types.d.ts.map +1 -0
- package/dist/core/hooks/types.js +24 -0
- package/dist/core/hooks/types.js.map +1 -0
- package/dist/core/keybindings.d.ts +20 -19
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +26 -5
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/mcp/config.d.ts +16 -0
- package/dist/core/mcp/config.d.ts.map +1 -0
- package/dist/core/mcp/config.js +166 -0
- package/dist/core/mcp/config.js.map +1 -0
- package/dist/core/mcp/connector.d.ts +35 -0
- package/dist/core/mcp/connector.d.ts.map +1 -0
- package/dist/core/mcp/connector.js +105 -0
- package/dist/core/mcp/connector.js.map +1 -0
- package/dist/core/mcp/contract.d.ts +19 -0
- package/dist/core/mcp/contract.d.ts.map +1 -0
- package/dist/core/mcp/contract.js +89 -0
- package/dist/core/mcp/contract.js.map +1 -0
- package/dist/core/mcp/mcp-tool.d.ts +32 -0
- package/dist/core/mcp/mcp-tool.d.ts.map +1 -0
- package/dist/core/mcp/mcp-tool.js +171 -0
- package/dist/core/mcp/mcp-tool.js.map +1 -0
- package/dist/core/mcp/metadata-cache.d.ts +29 -0
- package/dist/core/mcp/metadata-cache.d.ts.map +1 -0
- package/dist/core/mcp/metadata-cache.js +99 -0
- package/dist/core/mcp/metadata-cache.js.map +1 -0
- package/dist/core/mcp/oauth/authorize.d.ts +21 -0
- package/dist/core/mcp/oauth/authorize.d.ts.map +1 -0
- package/dist/core/mcp/oauth/authorize.js +32 -0
- package/dist/core/mcp/oauth/authorize.js.map +1 -0
- package/dist/core/mcp/oauth/discover.d.ts +27 -0
- package/dist/core/mcp/oauth/discover.d.ts.map +1 -0
- package/dist/core/mcp/oauth/discover.js +116 -0
- package/dist/core/mcp/oauth/discover.js.map +1 -0
- package/dist/core/mcp/oauth/flow.d.ts +31 -0
- package/dist/core/mcp/oauth/flow.d.ts.map +1 -0
- package/dist/core/mcp/oauth/flow.js +175 -0
- package/dist/core/mcp/oauth/flow.js.map +1 -0
- package/dist/core/mcp/oauth/mcp-token.d.ts +51 -0
- package/dist/core/mcp/oauth/mcp-token.d.ts.map +1 -0
- package/dist/core/mcp/oauth/mcp-token.js +129 -0
- package/dist/core/mcp/oauth/mcp-token.js.map +1 -0
- package/dist/core/mcp/oauth/pkce.d.ts +12 -0
- package/dist/core/mcp/oauth/pkce.d.ts.map +1 -0
- package/dist/core/mcp/oauth/pkce.js +11 -0
- package/dist/core/mcp/oauth/pkce.js.map +1 -0
- package/dist/core/mcp/oauth/register.d.ts +16 -0
- package/dist/core/mcp/oauth/register.d.ts.map +1 -0
- package/dist/core/mcp/oauth/register.js +42 -0
- package/dist/core/mcp/oauth/register.js.map +1 -0
- package/dist/core/mcp/oauth/token-client.d.ts +33 -0
- package/dist/core/mcp/oauth/token-client.d.ts.map +1 -0
- package/dist/core/mcp/oauth/token-client.js +65 -0
- package/dist/core/mcp/oauth/token-client.js.map +1 -0
- package/dist/core/mcp/runtime.d.ts +20 -0
- package/dist/core/mcp/runtime.d.ts.map +1 -0
- package/dist/core/mcp/runtime.js +37 -0
- package/dist/core/mcp/runtime.js.map +1 -0
- package/dist/core/mcp/schema.d.ts +22 -0
- package/dist/core/mcp/schema.d.ts.map +1 -0
- package/dist/core/mcp/schema.js +20 -0
- package/dist/core/mcp/schema.js.map +1 -0
- package/dist/core/mcp/server-manager.d.ts +43 -0
- package/dist/core/mcp/server-manager.d.ts.map +1 -0
- package/dist/core/mcp/server-manager.js +142 -0
- package/dist/core/mcp/server-manager.js.map +1 -0
- package/dist/core/mcp/types.d.ts +100 -0
- package/dist/core/mcp/types.d.ts.map +1 -0
- package/dist/core/mcp/types.js +8 -0
- package/dist/core/mcp/types.js.map +1 -0
- package/dist/core/model-config.d.ts +5 -0
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +3 -1
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-resolver.d.ts +1 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +14 -8
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/core/models-store.d.ts.map +1 -1
- package/dist/core/models-store.js +2 -1
- package/dist/core/models-store.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +22 -14
- package/dist/core/package-manager.js.map +1 -1
- package/dist/core/permissions/gate.d.ts.map +1 -1
- package/dist/core/permissions/gate.js +45 -4
- package/dist/core/permissions/gate.js.map +1 -1
- package/dist/core/permissions/operations.d.ts +20 -0
- package/dist/core/permissions/operations.d.ts.map +1 -0
- package/dist/core/permissions/operations.js +9 -0
- package/dist/core/permissions/operations.js.map +1 -0
- package/dist/core/permissions/policy-command.d.ts +42 -0
- package/dist/core/permissions/policy-command.d.ts.map +1 -0
- package/dist/core/permissions/policy-command.js +44 -0
- package/dist/core/permissions/policy-command.js.map +1 -0
- package/dist/core/permissions/responder.d.ts +49 -2
- package/dist/core/permissions/responder.d.ts.map +1 -1
- package/dist/core/permissions/responder.js +20 -8
- package/dist/core/permissions/responder.js.map +1 -1
- package/dist/core/permissions/rules.d.ts.map +1 -1
- package/dist/core/permissions/rules.js +11 -1
- package/dist/core/permissions/rules.js.map +1 -1
- package/dist/core/permissions/store.d.ts +18 -0
- package/dist/core/permissions/store.d.ts.map +1 -1
- package/dist/core/permissions/store.js +89 -8
- package/dist/core/permissions/store.js.map +1 -1
- package/dist/core/pi-manifest.d.ts.map +1 -1
- package/dist/core/pi-manifest.js +2 -1
- package/dist/core/pi-manifest.js.map +1 -1
- package/dist/core/policy-executor.d.ts +47 -0
- package/dist/core/policy-executor.d.ts.map +1 -0
- package/dist/core/policy-executor.js +228 -0
- package/dist/core/policy-executor.js.map +1 -0
- package/dist/core/policy-loader.d.ts +72 -0
- package/dist/core/policy-loader.d.ts.map +1 -0
- package/dist/core/policy-loader.js +195 -0
- package/dist/core/policy-loader.js.map +1 -0
- package/dist/core/project-trust.d.ts +1 -1
- package/dist/core/project-trust.d.ts.map +1 -1
- package/dist/core/project-trust.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +2 -1
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/resource-loader.d.ts.map +1 -1
- package/dist/core/resource-loader.js +3 -2
- package/dist/core/resource-loader.js.map +1 -1
- package/dist/core/sandbox/bwrap-arguments.d.ts +41 -0
- package/dist/core/sandbox/bwrap-arguments.d.ts.map +1 -0
- package/dist/core/sandbox/bwrap-arguments.js +136 -0
- package/dist/core/sandbox/bwrap-arguments.js.map +1 -0
- package/dist/core/sandbox/child-entry.d.ts.map +1 -1
- package/dist/core/sandbox/child-entry.js +3 -1
- package/dist/core/sandbox/child-entry.js.map +1 -1
- package/dist/core/sandbox/cli-launch.d.ts +84 -4
- package/dist/core/sandbox/cli-launch.d.ts.map +1 -1
- package/dist/core/sandbox/cli-launch.js +115 -16
- package/dist/core/sandbox/cli-launch.js.map +1 -1
- package/dist/core/sandbox/cli-supervisor.d.ts +2 -0
- package/dist/core/sandbox/cli-supervisor.d.ts.map +1 -1
- package/dist/core/sandbox/cli-supervisor.js +50 -2
- package/dist/core/sandbox/cli-supervisor.js.map +1 -1
- package/dist/core/sandbox/full-access.d.ts +28 -0
- package/dist/core/sandbox/full-access.d.ts.map +1 -0
- package/dist/core/sandbox/full-access.js +63 -0
- package/dist/core/sandbox/full-access.js.map +1 -0
- package/dist/core/sandbox/git-identity.d.ts +57 -0
- package/dist/core/sandbox/git-identity.d.ts.map +1 -0
- package/dist/core/sandbox/git-identity.js +78 -0
- package/dist/core/sandbox/git-identity.js.map +1 -0
- package/dist/core/sandbox/host-approval.d.ts +42 -0
- package/dist/core/sandbox/host-approval.d.ts.map +1 -0
- package/dist/core/sandbox/host-approval.js +99 -0
- package/dist/core/sandbox/host-approval.js.map +1 -0
- package/dist/core/sandbox/linux-backend.d.ts +14 -0
- package/dist/core/sandbox/linux-backend.d.ts.map +1 -1
- package/dist/core/sandbox/linux-backend.js +158 -80
- package/dist/core/sandbox/linux-backend.js.map +1 -1
- package/dist/core/sandbox/macos-backend.d.ts +5 -0
- package/dist/core/sandbox/macos-backend.d.ts.map +1 -1
- package/dist/core/sandbox/macos-backend.js +164 -5
- package/dist/core/sandbox/macos-backend.js.map +1 -1
- package/dist/core/sandbox/network-proxy.d.ts +19 -0
- package/dist/core/sandbox/network-proxy.d.ts.map +1 -1
- package/dist/core/sandbox/network-proxy.js +93 -19
- package/dist/core/sandbox/network-proxy.js.map +1 -1
- package/dist/core/sandbox/policy.d.ts +8 -0
- package/dist/core/sandbox/policy.d.ts.map +1 -1
- package/dist/core/sandbox/policy.js +29 -12
- package/dist/core/sandbox/policy.js.map +1 -1
- package/dist/core/sandbox/profiles.d.ts +25 -0
- package/dist/core/sandbox/profiles.d.ts.map +1 -0
- package/dist/core/sandbox/profiles.js +23 -0
- package/dist/core/sandbox/profiles.js.map +1 -0
- package/dist/core/sandbox/rpc/command-client.d.ts +28 -0
- package/dist/core/sandbox/rpc/command-client.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/command-client.js +86 -0
- package/dist/core/sandbox/rpc/command-client.js.map +1 -0
- package/dist/core/sandbox/rpc/command-proxy.d.ts +48 -0
- package/dist/core/sandbox/rpc/command-proxy.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/command-proxy.js +120 -0
- package/dist/core/sandbox/rpc/command-proxy.js.map +1 -0
- package/dist/core/sandbox/rpc/credential-proxy.d.ts.map +1 -1
- package/dist/core/sandbox/rpc/credential-proxy.js +2 -96
- package/dist/core/sandbox/rpc/credential-proxy.js.map +1 -1
- package/dist/core/sandbox/rpc/framing.d.ts +35 -0
- package/dist/core/sandbox/rpc/framing.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/framing.js +114 -0
- package/dist/core/sandbox/rpc/framing.js.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-helper.d.ts +47 -0
- package/dist/core/sandbox/rpc/git-credential-helper.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-helper.js +222 -0
- package/dist/core/sandbox/rpc/git-credential-helper.js.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.d.ts +66 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.d.ts.map +1 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.js +209 -0
- package/dist/core/sandbox/rpc/git-credential-proxy.js.map +1 -0
- package/dist/core/sandbox/supervisor-temp.d.ts +18 -0
- package/dist/core/sandbox/supervisor-temp.d.ts.map +1 -0
- package/dist/core/sandbox/supervisor-temp.js +21 -0
- package/dist/core/sandbox/supervisor-temp.js.map +1 -0
- package/dist/core/sandbox/supervisor.d.ts +12 -1
- package/dist/core/sandbox/supervisor.d.ts.map +1 -1
- package/dist/core/sandbox/supervisor.js +15 -1
- package/dist/core/sandbox/supervisor.js.map +1 -1
- package/dist/core/sandbox/terminal-handoff.d.ts +68 -0
- package/dist/core/sandbox/terminal-handoff.d.ts.map +1 -0
- package/dist/core/sandbox/terminal-handoff.js +221 -0
- package/dist/core/sandbox/terminal-handoff.js.map +1 -0
- package/dist/core/sandbox/terminal-size.d.ts +13 -0
- package/dist/core/sandbox/terminal-size.d.ts.map +1 -1
- package/dist/core/sandbox/terminal-size.js +23 -2
- package/dist/core/sandbox/terminal-size.js.map +1 -1
- package/dist/core/sdk.d.ts +24 -2
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +63 -4
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-export.d.ts +4 -0
- package/dist/core/session-export.d.ts.map +1 -0
- package/dist/core/session-export.js +32 -0
- package/dist/core/session-export.js.map +1 -0
- package/dist/core/session-manager.d.ts +13 -1
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +34 -6
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-diagnostics.d.ts +9 -0
- package/dist/core/settings-diagnostics.d.ts.map +1 -0
- package/dist/core/settings-diagnostics.js +21 -0
- package/dist/core/settings-diagnostics.js.map +1 -0
- package/dist/core/settings-manager.d.ts +172 -1
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +160 -11
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/skills.d.ts.map +1 -1
- package/dist/core/skills.js +51 -33
- package/dist/core/skills.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +4 -1
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +8 -2
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/core/tools/background-shell.d.ts +39 -0
- package/dist/core/tools/background-shell.d.ts.map +1 -0
- package/dist/core/tools/background-shell.js +81 -0
- package/dist/core/tools/background-shell.js.map +1 -0
- package/dist/core/tools/bash-command-segments.d.ts.map +1 -1
- package/dist/core/tools/bash-command-segments.js +2 -0
- package/dist/core/tools/bash-command-segments.js.map +1 -1
- package/dist/core/tools/bash.d.ts +44 -16
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +264 -32
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/contract-snapshot.d.ts +58 -0
- package/dist/core/tools/contract-snapshot.d.ts.map +1 -0
- package/dist/core/tools/contract-snapshot.js +51 -0
- package/dist/core/tools/contract-snapshot.js.map +1 -0
- package/dist/core/tools/contract.d.ts +53 -5
- package/dist/core/tools/contract.d.ts.map +1 -1
- package/dist/core/tools/contract.js +22 -2
- package/dist/core/tools/contract.js.map +1 -1
- package/dist/core/tools/delegate.d.ts +6 -4
- package/dist/core/tools/delegate.d.ts.map +1 -1
- package/dist/core/tools/delegate.js +21 -11
- package/dist/core/tools/delegate.js.map +1 -1
- package/dist/core/tools/edit-diff.d.ts +48 -5
- package/dist/core/tools/edit-diff.d.ts.map +1 -1
- package/dist/core/tools/edit-diff.js +217 -8
- package/dist/core/tools/edit-diff.js.map +1 -1
- package/dist/core/tools/edit.d.ts +8 -0
- package/dist/core/tools/edit.d.ts.map +1 -1
- package/dist/core/tools/edit.js +72 -10
- package/dist/core/tools/edit.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +5 -2
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +5 -2
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/index.d.ts +14 -2
- package/dist/core/tools/index.d.ts.map +1 -1
- package/dist/core/tools/index.js +12 -0
- package/dist/core/tools/index.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +5 -2
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/lsp.d.ts +19 -1
- package/dist/core/tools/lsp.d.ts.map +1 -1
- package/dist/core/tools/lsp.js +163 -10
- package/dist/core/tools/lsp.js.map +1 -1
- package/dist/core/tools/path-permission.d.ts +3 -0
- package/dist/core/tools/path-permission.d.ts.map +1 -1
- package/dist/core/tools/path-permission.js +16 -4
- package/dist/core/tools/path-permission.js.map +1 -1
- package/dist/core/tools/path-utils.d.ts +27 -2
- package/dist/core/tools/path-utils.d.ts.map +1 -1
- package/dist/core/tools/path-utils.js +221 -4
- package/dist/core/tools/path-utils.js.map +1 -1
- package/dist/core/tools/powershell.d.ts +16 -0
- package/dist/core/tools/powershell.d.ts.map +1 -0
- package/dist/core/tools/powershell.js +39 -0
- package/dist/core/tools/powershell.js.map +1 -0
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +20 -11
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/test.d.ts +54 -9
- package/dist/core/tools/test.d.ts.map +1 -1
- package/dist/core/tools/test.js +239 -19
- package/dist/core/tools/test.js.map +1 -1
- package/dist/core/tools/write.d.ts +7 -0
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +64 -9
- package/dist/core/tools/write.js.map +1 -1
- package/dist/core/trust-manager.d.ts.map +1 -1
- package/dist/core/trust-manager.js +2 -1
- package/dist/core/trust-manager.js.map +1 -1
- package/dist/core/verification-lifecycle.d.ts +77 -0
- package/dist/core/verification-lifecycle.d.ts.map +1 -0
- package/dist/core/verification-lifecycle.js +146 -0
- package/dist/core/verification-lifecycle.js.map +1 -0
- package/dist/core/workspace/artifacts.d.ts +45 -0
- package/dist/core/workspace/artifacts.d.ts.map +1 -0
- package/dist/core/workspace/artifacts.js +165 -0
- package/dist/core/workspace/artifacts.js.map +1 -0
- package/dist/core/workspace/comparison.d.ts +27 -0
- package/dist/core/workspace/comparison.d.ts.map +1 -0
- package/dist/core/workspace/comparison.js +94 -0
- package/dist/core/workspace/comparison.js.map +1 -0
- package/dist/core/workspace/git-observer.d.ts +48 -0
- package/dist/core/workspace/git-observer.d.ts.map +1 -0
- package/dist/core/workspace/git-observer.js +346 -0
- package/dist/core/workspace/git-observer.js.map +1 -0
- package/dist/core/workspace/projection.d.ts +3 -0
- package/dist/core/workspace/projection.d.ts.map +1 -0
- package/dist/core/workspace/projection.js +56 -0
- package/dist/core/workspace/projection.js.map +1 -0
- package/dist/core/workspace/state.d.ts +133 -0
- package/dist/core/workspace/state.d.ts.map +1 -0
- package/dist/core/workspace/state.js +96 -0
- package/dist/core/workspace/state.js.map +1 -0
- package/dist/extensions/llama/client.d.ts +6 -0
- package/dist/extensions/llama/client.d.ts.map +1 -1
- package/dist/extensions/llama/client.js +7 -0
- package/dist/extensions/llama/client.js.map +1 -1
- package/dist/extensions/llama/index.d.ts.map +1 -1
- package/dist/extensions/llama/index.js +2 -0
- package/dist/extensions/llama/index.js.map +1 -1
- package/dist/extensions/llama/provider.d.ts +3 -1
- package/dist/extensions/llama/provider.d.ts.map +1 -1
- package/dist/extensions/llama/provider.js +29 -4
- package/dist/extensions/llama/provider.js.map +1 -1
- package/dist/index.d.ts +8 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -4
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts +6 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +58 -23
- package/dist/main.js.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/migrations.js +4 -3
- package/dist/migrations.js.map +1 -1
- package/dist/modes/acp/server.d.ts +55 -0
- package/dist/modes/acp/server.d.ts.map +1 -0
- package/dist/modes/acp/server.js +244 -0
- package/dist/modes/acp/server.js.map +1 -0
- package/dist/modes/acp/translate.d.ts +37 -0
- package/dist/modes/acp/translate.d.ts.map +1 -0
- package/dist/modes/acp/translate.js +50 -0
- package/dist/modes/acp/translate.js.map +1 -0
- package/dist/modes/index.d.ts +1 -0
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +1 -0
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +29 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +182 -23
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/bash-execution.d.ts +7 -0
- package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/bash-execution.js +20 -0
- package/dist/modes/interactive/components/bash-execution.js.map +1 -1
- package/dist/modes/interactive/components/custom-editor.d.ts +9 -4
- package/dist/modes/interactive/components/custom-editor.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-editor.js +20 -9
- package/dist/modes/interactive/components/custom-editor.js.map +1 -1
- package/dist/modes/interactive/components/extension-selector.d.ts +7 -1
- package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/extension-selector.js +7 -3
- package/dist/modes/interactive/components/extension-selector.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts +19 -0
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +70 -10
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/components/model-selector.d.ts +10 -3
- package/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/model-selector.js +56 -14
- package/dist/modes/interactive/components/model-selector.js.map +1 -1
- package/dist/modes/interactive/components/permission-preview.d.ts +15 -0
- package/dist/modes/interactive/components/permission-preview.d.ts.map +1 -0
- package/dist/modes/interactive/components/permission-preview.js +33 -0
- package/dist/modes/interactive/components/permission-preview.js.map +1 -0
- package/dist/modes/interactive/components/session-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector.js +2 -2
- package/dist/modes/interactive/components/session-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +9 -2
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +128 -56
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/components/settings-submenu.d.ts +72 -0
- package/dist/modes/interactive/components/settings-submenu.d.ts.map +1 -0
- package/dist/modes/interactive/components/settings-submenu.js +165 -0
- package/dist/modes/interactive/components/settings-submenu.js.map +1 -0
- package/dist/modes/interactive/components/thinking-selector.d.ts +15 -3
- package/dist/modes/interactive/components/thinking-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/thinking-selector.js +74 -17
- package/dist/modes/interactive/components/thinking-selector.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +23 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +64 -10
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/external-editor.d.ts.map +1 -1
- package/dist/modes/interactive/external-editor.js +2 -1
- package/dist/modes/interactive/external-editor.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +45 -0
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +395 -43
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts +12 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +20 -9
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/modes/json-event.d.ts +16 -13
- package/dist/modes/json-event.d.ts.map +1 -1
- package/dist/modes/json-event.js +20 -6
- package/dist/modes/json-event.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +27 -12
- package/dist/modes/print-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +7 -0
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +7 -0
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +3 -0
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +12 -0
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/dist/package-manager-cli.d.ts +1 -0
- package/dist/package-manager-cli.d.ts.map +1 -1
- package/dist/package-manager-cli.js +196 -10
- package/dist/package-manager-cli.js.map +1 -1
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +2 -1
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/management-http.d.ts +5 -3
- package/dist/utils/management-http.d.ts.map +1 -1
- package/dist/utils/management-http.js +14 -10
- package/dist/utils/management-http.js.map +1 -1
- package/dist/utils/shell.d.ts +3 -0
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +23 -9
- package/dist/utils/shell.js.map +1 -1
- package/dist/utils/syntax-highlight.d.ts +1 -0
- package/dist/utils/syntax-highlight.d.ts.map +1 -1
- package/dist/utils/syntax-highlight.js +60 -1
- package/dist/utils/syntax-highlight.js.map +1 -1
- package/dist/utils/text.d.ts +8 -0
- package/dist/utils/text.d.ts.map +1 -0
- package/dist/utils/text.js +9 -0
- package/dist/utils/text.js.map +1 -0
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +6 -4
- package/dist/utils/version-check.js.map +1 -1
- package/docs/compaction.md +19 -0
- package/docs/custom-provider.md +12 -9
- package/docs/environment-variables.md +1 -1
- package/docs/extensions.md +35 -7
- package/docs/json.md +7 -2
- package/docs/keybindings.md +9 -9
- package/docs/llama-cpp.md +2 -0
- package/docs/models.md +8 -2
- package/docs/packages.md +2 -2
- package/docs/quickstart.md +1 -1
- package/docs/rpc.md +32 -3
- package/docs/sdk.md +32 -2
- package/docs/settings.md +23 -7
- package/docs/skills.md +4 -3
- package/docs/terminal-setup.md +23 -7
- package/docs/usage.md +10 -5
- package/docs/windows.md +24 -2
- package/examples/extensions/border-status-editor.ts +1 -1
- package/examples/extensions/built-in-tool-renderer.ts +8 -4
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/git-checkpoint.ts +24 -36
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/minimal-mode.ts +2 -2
- package/examples/extensions/notify.ts +3 -1
- package/examples/extensions/permission-gate.ts +1 -1
- package/examples/extensions/plan-mode/index.ts +1 -1
- package/examples/extensions/rpc-demo.ts +1 -1
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/subagent/README.md +1 -1
- package/examples/extensions/subagent/index.ts +6 -1
- package/examples/extensions/titlebar-spinner.ts +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/rpc-extension-ui.ts +1 -1
- package/examples/sdk/06-extensions.ts +1 -1
- package/examples/sdk/README.md +1 -1
- package/npm-shrinkwrap.json +439 -405
- package/package.json +17 -17
- package/dist/server/create-harness.d.ts +0 -26
- package/dist/server/create-harness.d.ts.map +0 -1
- package/dist/server/create-harness.js +0 -107
- package/dist/server/create-harness.js.map +0 -1
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { closeSync, constants, openSync, readFileSync, rmSync, watch } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { writeWithoutFollowingLinks } from "./terminal-size.js";
|
|
4
|
+
/**
|
|
5
|
+
* Lends the terminal from the sandboxed child back to the supervisor for a prompt.
|
|
6
|
+
*
|
|
7
|
+
* The supervisor has to draw the escalation prompt itself. Per ADR 0023 an approval
|
|
8
|
+
* asserted from inside the boundary is indistinguishable from one forged by the code the
|
|
9
|
+
* boundary exists to contain, so the human's answer must be read by the side that owns the
|
|
10
|
+
* decision. The child cannot read it in any case: it runs under `bwrap --new-session` and
|
|
11
|
+
* has no controlling terminal, which `terminal-size.ts` documents for the same reason.
|
|
12
|
+
*
|
|
13
|
+
* The transport is the one `terminal-size.ts` already established -- a file in the
|
|
14
|
+
* workspace, which is bind-mounted into the child, watched on the other side. Signals are
|
|
15
|
+
* unavailable because bwrap sits between the processes as PID 1 and forwards nothing.
|
|
16
|
+
*
|
|
17
|
+
* Two files rather than one, because the supervisor must know the child has actually let
|
|
18
|
+
* go of stdin before it reads an answer. The child inherits stdin and its TUI holds it in
|
|
19
|
+
* raw mode; prompting while both sides read would lose keystrokes to whichever won.
|
|
20
|
+
*
|
|
21
|
+
* The child is asked, never obeyed. A child that never acknowledges delays the prompt by
|
|
22
|
+
* the timeout and no longer, because a contained process that could withhold
|
|
23
|
+
* acknowledgement indefinitely would be able to veto the human's decision -- which is the
|
|
24
|
+
* authority failure ADR 0023 forbids. An unread prompt is a legibility failure, and that
|
|
25
|
+
* is the direction this is allowed to fail in.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Env var naming the directory holding the supervisor's command file.
|
|
29
|
+
*
|
|
30
|
+
* Supervisor-private where the platform backend allocates one (PS.3): the child reads the
|
|
31
|
+
* suspend/resume latch there and can never write it. The acknowledgement travels the other
|
|
32
|
+
* way and therefore has its own variable below.
|
|
33
|
+
*/
|
|
34
|
+
export const TERMINAL_HANDOFF_PATH_VARIABLE = "APEX_TERMINAL_HANDOFF_PATH";
|
|
35
|
+
/**
|
|
36
|
+
* Env var naming the child-writable acknowledgement file.
|
|
37
|
+
*
|
|
38
|
+
* Absent means "the file next to the command file", which is the single-directory
|
|
39
|
+
* arrangement the macOS backend still uses. The two locations are kept explicit rather
|
|
40
|
+
* than derived because the platforms genuinely differ: Linux can put the command file on a
|
|
41
|
+
* host path the child sees read-only, and macOS has no `/home` tmpfs to separate them with.
|
|
42
|
+
*/
|
|
43
|
+
export const TERMINAL_HANDOFF_ACK_PATH_VARIABLE = "APEX_TERMINAL_HANDOFF_ACK_PATH";
|
|
44
|
+
const STATE_FILE = "terminal-handoff";
|
|
45
|
+
const ACKNOWLEDGEMENT_FILE = "terminal-handoff-ack";
|
|
46
|
+
const DEFAULT_ACKNOWLEDGEMENT_TIMEOUT_MS = 1_000;
|
|
47
|
+
/**
|
|
48
|
+
* Read without following a symlink at the final path component; undefined when refused.
|
|
49
|
+
*
|
|
50
|
+
* The acknowledgement is the one half of the handoff the contained side writes, so a link
|
|
51
|
+
* planted at its path would otherwise let the child choose which host file the supervisor
|
|
52
|
+
* reads. Its content is not authority (ADR 0023), but the read itself is the supervisor's.
|
|
53
|
+
*/
|
|
54
|
+
function readWithoutFollowingLinks(path) {
|
|
55
|
+
let descriptor;
|
|
56
|
+
try {
|
|
57
|
+
descriptor = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
return readFileSync(descriptor, "utf8");
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
closeSync(descriptor);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* How often each side re-reads the state file, independently of the watcher.
|
|
74
|
+
*
|
|
75
|
+
* `fs.watch` is a thin wrapper over whatever the platform provides, and the platforms do
|
|
76
|
+
* not agree. macOS CI delivered a resume more than a second after it was written, and
|
|
77
|
+
* intermittently: the event arrives, late enough that a TUI would sit visibly frozen
|
|
78
|
+
* after the human had already answered. The watcher still does the work in the common
|
|
79
|
+
* case; this only bounds how wrong it can be.
|
|
80
|
+
*/
|
|
81
|
+
const POLL_INTERVAL_MS = 100;
|
|
82
|
+
function parseState(contents) {
|
|
83
|
+
const value = contents.trim();
|
|
84
|
+
return value === "suspend" || value === "resume" ? value : undefined;
|
|
85
|
+
}
|
|
86
|
+
/** Supervisor side. `directory` holds the command file; see the env vars above. */
|
|
87
|
+
export function createTerminalHandoff(directory, options) {
|
|
88
|
+
const statePath = join(directory, STATE_FILE);
|
|
89
|
+
const acknowledgementPath = options?.acknowledgementPath ?? join(directory, ACKNOWLEDGEMENT_FILE);
|
|
90
|
+
const acknowledgementDirectory = dirname(acknowledgementPath);
|
|
91
|
+
// Floored at two poll intervals. The state file is a latch the child samples, so a
|
|
92
|
+
// supervisor that gives up before the child can sample writes `resume` over its own
|
|
93
|
+
// `suspend` and the child observes neither: it reads one value equal to what it
|
|
94
|
+
// already held. Two intervals guarantee a sampling opportunity even when the
|
|
95
|
+
// watcher never fires, which is the macOS case POLL_INTERVAL_MS exists for.
|
|
96
|
+
const timeout = Math.max(options?.acknowledgementTimeoutMs ?? DEFAULT_ACKNOWLEDGEMENT_TIMEOUT_MS, POLL_INTERVAL_MS * 2);
|
|
97
|
+
// Every borrow chains onto the last, which is what keeps two escalations that arrive
|
|
98
|
+
// together from drawing over each other.
|
|
99
|
+
let queue = Promise.resolve();
|
|
100
|
+
function write(state) {
|
|
101
|
+
try {
|
|
102
|
+
writeWithoutFollowingLinks(statePath, `${state}\n`);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// A handoff we cannot request still leaves the prompt readable often enough to
|
|
106
|
+
// be worth attempting; it is never worth failing the escalation over.
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function waitForAcknowledgement() {
|
|
110
|
+
return new Promise((resolve) => {
|
|
111
|
+
let watcher;
|
|
112
|
+
let timer;
|
|
113
|
+
let poll;
|
|
114
|
+
const finish = () => {
|
|
115
|
+
if (timer)
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
if (poll)
|
|
118
|
+
clearInterval(poll);
|
|
119
|
+
watcher?.close();
|
|
120
|
+
resolve();
|
|
121
|
+
};
|
|
122
|
+
const check = () => {
|
|
123
|
+
// No-follow: the acknowledgement path is the one half of the handoff the child
|
|
124
|
+
// may write, so a link planted there would otherwise let it choose which host
|
|
125
|
+
// file the supervisor reads.
|
|
126
|
+
if (readWithoutFollowingLinks(acknowledgementPath)?.trim() === "suspended")
|
|
127
|
+
finish();
|
|
128
|
+
};
|
|
129
|
+
timer = setTimeout(finish, timeout);
|
|
130
|
+
try {
|
|
131
|
+
watcher = watch(acknowledgementDirectory, check);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Without a watcher the poll below still delivers, just less promptly.
|
|
135
|
+
}
|
|
136
|
+
poll = setInterval(check, POLL_INTERVAL_MS);
|
|
137
|
+
poll.unref();
|
|
138
|
+
check();
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
async function run(prompt) {
|
|
142
|
+
try {
|
|
143
|
+
rmSync(acknowledgementPath, { force: true });
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
// A stale acknowledgement is handled by the content check, not by its absence.
|
|
147
|
+
}
|
|
148
|
+
write("suspend");
|
|
149
|
+
await waitForAcknowledgement();
|
|
150
|
+
try {
|
|
151
|
+
return await prompt();
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
write("resume");
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return {
|
|
158
|
+
borrowTerminal(prompt) {
|
|
159
|
+
const next = queue.then(() => run(prompt), () => run(prompt));
|
|
160
|
+
// Swallowed here only so one rejected borrow does not poison the queue for the
|
|
161
|
+
// next; the caller still receives the rejection through `next`.
|
|
162
|
+
queue = next.then(() => undefined, () => undefined);
|
|
163
|
+
return next;
|
|
164
|
+
},
|
|
165
|
+
stop() {
|
|
166
|
+
write("resume");
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
/** Child side. Acknowledges a suspend once it has actually stopped drawing and reading. */
|
|
171
|
+
export function observeTerminalHandoff(directory, hooks, options) {
|
|
172
|
+
const statePath = join(directory, STATE_FILE);
|
|
173
|
+
const acknowledgementPath = options?.acknowledgementPath ?? join(directory, ACKNOWLEDGEMENT_FILE);
|
|
174
|
+
let current = "resume";
|
|
175
|
+
let applying = Promise.resolve();
|
|
176
|
+
const apply = () => {
|
|
177
|
+
let state;
|
|
178
|
+
try {
|
|
179
|
+
state = parseState(readFileSync(statePath, "utf8"));
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (!state || state === current)
|
|
185
|
+
return;
|
|
186
|
+
current = state;
|
|
187
|
+
applying = applying.then(async () => {
|
|
188
|
+
if (state === "suspend") {
|
|
189
|
+
await hooks.suspend();
|
|
190
|
+
try {
|
|
191
|
+
writeWithoutFollowingLinks(acknowledgementPath, "suspended\n");
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// The supervisor's timeout covers an acknowledgement we cannot write.
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
await hooks.resume();
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
};
|
|
202
|
+
apply();
|
|
203
|
+
let watcher;
|
|
204
|
+
try {
|
|
205
|
+
watcher = watch(directory, apply);
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// Without a watcher the poll below still delivers, just less promptly.
|
|
209
|
+
}
|
|
210
|
+
const poll = setInterval(apply, POLL_INTERVAL_MS);
|
|
211
|
+
// Never hold the process open for this. The child exits on its own schedule and a
|
|
212
|
+
// handoff that stops being observed at shutdown has nothing left to deliver.
|
|
213
|
+
poll.unref();
|
|
214
|
+
return {
|
|
215
|
+
stop: () => {
|
|
216
|
+
watcher?.close();
|
|
217
|
+
clearInterval(poll);
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=terminal-handoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"terminal-handoff.js","sourceRoot":"","sources":["../../../src/core/sandbox/terminal-handoff.ts"],"names":[],"mappings":"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","sourcesContent":["import { closeSync, constants, type FSWatcher, openSync, readFileSync, rmSync, watch } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { writeWithoutFollowingLinks } from \"./terminal-size.ts\";\n\n/**\n * Lends the terminal from the sandboxed child back to the supervisor for a prompt.\n *\n * The supervisor has to draw the escalation prompt itself. Per ADR 0023 an approval\n * asserted from inside the boundary is indistinguishable from one forged by the code the\n * boundary exists to contain, so the human's answer must be read by the side that owns the\n * decision. The child cannot read it in any case: it runs under `bwrap --new-session` and\n * has no controlling terminal, which `terminal-size.ts` documents for the same reason.\n *\n * The transport is the one `terminal-size.ts` already established -- a file in the\n * workspace, which is bind-mounted into the child, watched on the other side. Signals are\n * unavailable because bwrap sits between the processes as PID 1 and forwards nothing.\n *\n * Two files rather than one, because the supervisor must know the child has actually let\n * go of stdin before it reads an answer. The child inherits stdin and its TUI holds it in\n * raw mode; prompting while both sides read would lose keystrokes to whichever won.\n *\n * The child is asked, never obeyed. A child that never acknowledges delays the prompt by\n * the timeout and no longer, because a contained process that could withhold\n * acknowledgement indefinitely would be able to veto the human's decision -- which is the\n * authority failure ADR 0023 forbids. An unread prompt is a legibility failure, and that\n * is the direction this is allowed to fail in.\n */\n\n/**\n * Env var naming the directory holding the supervisor's command file.\n *\n * Supervisor-private where the platform backend allocates one (PS.3): the child reads the\n * suspend/resume latch there and can never write it. The acknowledgement travels the other\n * way and therefore has its own variable below.\n */\nexport const TERMINAL_HANDOFF_PATH_VARIABLE = \"APEX_TERMINAL_HANDOFF_PATH\";\n\n/**\n * Env var naming the child-writable acknowledgement file.\n *\n * Absent means \"the file next to the command file\", which is the single-directory\n * arrangement the macOS backend still uses. The two locations are kept explicit rather\n * than derived because the platforms genuinely differ: Linux can put the command file on a\n * host path the child sees read-only, and macOS has no `/home` tmpfs to separate them with.\n */\nexport const TERMINAL_HANDOFF_ACK_PATH_VARIABLE = \"APEX_TERMINAL_HANDOFF_ACK_PATH\";\n\nconst STATE_FILE = \"terminal-handoff\";\nconst ACKNOWLEDGEMENT_FILE = \"terminal-handoff-ack\";\nconst DEFAULT_ACKNOWLEDGEMENT_TIMEOUT_MS = 1_000;\n\n/**\n * Read without following a symlink at the final path component; undefined when refused.\n *\n * The acknowledgement is the one half of the handoff the contained side writes, so a link\n * planted at its path would otherwise let the child choose which host file the supervisor\n * reads. Its content is not authority (ADR 0023), but the read itself is the supervisor's.\n */\nfunction readWithoutFollowingLinks(path: string): string | undefined {\n\tlet descriptor: number;\n\ttry {\n\t\tdescriptor = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);\n\t} catch {\n\t\treturn undefined;\n\t}\n\ttry {\n\t\treturn readFileSync(descriptor, \"utf8\");\n\t} catch {\n\t\treturn undefined;\n\t} finally {\n\t\tcloseSync(descriptor);\n\t}\n}\n\n/**\n * How often each side re-reads the state file, independently of the watcher.\n *\n * `fs.watch` is a thin wrapper over whatever the platform provides, and the platforms do\n * not agree. macOS CI delivered a resume more than a second after it was written, and\n * intermittently: the event arrives, late enough that a TUI would sit visibly frozen\n * after the human had already answered. The watcher still does the work in the common\n * case; this only bounds how wrong it can be.\n */\nconst POLL_INTERVAL_MS = 100;\n\ntype HandoffState = \"suspend\" | \"resume\";\n\nfunction parseState(contents: string): HandoffState | undefined {\n\tconst value = contents.trim();\n\treturn value === \"suspend\" || value === \"resume\" ? value : undefined;\n}\n\nexport interface TerminalHandoff {\n\t/**\n\t * Suspend the child, run `prompt` with the terminal to itself, then resume the child.\n\t * Overlapping calls are serialised, so two prompts never share the terminal.\n\t */\n\tborrowTerminal<T>(prompt: () => Promise<T>): Promise<T>;\n\tstop(): void;\n}\n\nexport interface TerminalHandoffOptions {\n\tacknowledgementTimeoutMs?: number;\n\t/**\n\t * Where the child writes its acknowledgement. Defaults to the file beside the command\n\t * file. Split from it when the command file lives somewhere the child cannot write.\n\t */\n\tacknowledgementPath?: string;\n}\n\n/** Supervisor side. `directory` holds the command file; see the env vars above. */\nexport function createTerminalHandoff(directory: string, options?: TerminalHandoffOptions): TerminalHandoff {\n\tconst statePath = join(directory, STATE_FILE);\n\tconst acknowledgementPath = options?.acknowledgementPath ?? join(directory, ACKNOWLEDGEMENT_FILE);\n\tconst acknowledgementDirectory = dirname(acknowledgementPath);\n\t// Floored at two poll intervals. The state file is a latch the child samples, so a\n\t// supervisor that gives up before the child can sample writes `resume` over its own\n\t// `suspend` and the child observes neither: it reads one value equal to what it\n\t// already held. Two intervals guarantee a sampling opportunity even when the\n\t// watcher never fires, which is the macOS case POLL_INTERVAL_MS exists for.\n\tconst timeout = Math.max(\n\t\toptions?.acknowledgementTimeoutMs ?? DEFAULT_ACKNOWLEDGEMENT_TIMEOUT_MS,\n\t\tPOLL_INTERVAL_MS * 2,\n\t);\n\t// Every borrow chains onto the last, which is what keeps two escalations that arrive\n\t// together from drawing over each other.\n\tlet queue: Promise<unknown> = Promise.resolve();\n\n\tfunction write(state: HandoffState): void {\n\t\ttry {\n\t\t\twriteWithoutFollowingLinks(statePath, `${state}\\n`);\n\t\t} catch {\n\t\t\t// A handoff we cannot request still leaves the prompt readable often enough to\n\t\t\t// be worth attempting; it is never worth failing the escalation over.\n\t\t}\n\t}\n\n\tfunction waitForAcknowledgement(): Promise<void> {\n\t\treturn new Promise((resolve) => {\n\t\t\tlet watcher: FSWatcher | undefined;\n\t\t\tlet timer: NodeJS.Timeout | undefined;\n\t\t\tlet poll: NodeJS.Timeout | undefined;\n\t\t\tconst finish = () => {\n\t\t\t\tif (timer) clearTimeout(timer);\n\t\t\t\tif (poll) clearInterval(poll);\n\t\t\t\twatcher?.close();\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tconst check = () => {\n\t\t\t\t// No-follow: the acknowledgement path is the one half of the handoff the child\n\t\t\t\t// may write, so a link planted there would otherwise let it choose which host\n\t\t\t\t// file the supervisor reads.\n\t\t\t\tif (readWithoutFollowingLinks(acknowledgementPath)?.trim() === \"suspended\") finish();\n\t\t\t};\n\t\t\ttimer = setTimeout(finish, timeout);\n\t\t\ttry {\n\t\t\t\twatcher = watch(acknowledgementDirectory, check);\n\t\t\t} catch {\n\t\t\t\t// Without a watcher the poll below still delivers, just less promptly.\n\t\t\t}\n\t\t\tpoll = setInterval(check, POLL_INTERVAL_MS);\n\t\t\tpoll.unref();\n\t\t\tcheck();\n\t\t});\n\t}\n\n\tasync function run<T>(prompt: () => Promise<T>): Promise<T> {\n\t\ttry {\n\t\t\trmSync(acknowledgementPath, { force: true });\n\t\t} catch {\n\t\t\t// A stale acknowledgement is handled by the content check, not by its absence.\n\t\t}\n\t\twrite(\"suspend\");\n\t\tawait waitForAcknowledgement();\n\t\ttry {\n\t\t\treturn await prompt();\n\t\t} finally {\n\t\t\twrite(\"resume\");\n\t\t}\n\t}\n\n\treturn {\n\t\tborrowTerminal<T>(prompt: () => Promise<T>): Promise<T> {\n\t\t\tconst next = queue.then(\n\t\t\t\t() => run(prompt),\n\t\t\t\t() => run(prompt),\n\t\t\t);\n\t\t\t// Swallowed here only so one rejected borrow does not poison the queue for the\n\t\t\t// next; the caller still receives the rejection through `next`.\n\t\t\tqueue = next.then(\n\t\t\t\t() => undefined,\n\t\t\t\t() => undefined,\n\t\t\t);\n\t\t\treturn next;\n\t\t},\n\t\tstop(): void {\n\t\t\twrite(\"resume\");\n\t\t},\n\t};\n}\n\n/** Child side. Acknowledges a suspend once it has actually stopped drawing and reading. */\nexport function observeTerminalHandoff(\n\tdirectory: string,\n\thooks: { suspend: () => void | Promise<void>; resume: () => void | Promise<void> },\n\toptions?: { acknowledgementPath?: string },\n): { stop: () => void } {\n\tconst statePath = join(directory, STATE_FILE);\n\tconst acknowledgementPath = options?.acknowledgementPath ?? join(directory, ACKNOWLEDGEMENT_FILE);\n\tlet current: HandoffState = \"resume\";\n\tlet applying: Promise<void> = Promise.resolve();\n\n\tconst apply = () => {\n\t\tlet state: HandoffState | undefined;\n\t\ttry {\n\t\t\tstate = parseState(readFileSync(statePath, \"utf8\"));\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tif (!state || state === current) return;\n\t\tcurrent = state;\n\t\tapplying = applying.then(async () => {\n\t\t\tif (state === \"suspend\") {\n\t\t\t\tawait hooks.suspend();\n\t\t\t\ttry {\n\t\t\t\t\twriteWithoutFollowingLinks(acknowledgementPath, \"suspended\\n\");\n\t\t\t\t} catch {\n\t\t\t\t\t// The supervisor's timeout covers an acknowledgement we cannot write.\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tawait hooks.resume();\n\t\t\t}\n\t\t});\n\t};\n\n\tapply();\n\n\tlet watcher: FSWatcher | undefined;\n\ttry {\n\t\twatcher = watch(directory, apply);\n\t} catch {\n\t\t// Without a watcher the poll below still delivers, just less promptly.\n\t}\n\tconst poll = setInterval(apply, POLL_INTERVAL_MS);\n\t// Never hold the process open for this. The child exits on its own schedule and a\n\t// handoff that stops being observed at shutdown has nothing left to deliver.\n\tpoll.unref();\n\n\treturn {\n\t\tstop: () => {\n\t\t\twatcher?.close();\n\t\t\tclearInterval(poll);\n\t\t},\n\t};\n}\n"]}
|
|
@@ -15,6 +15,19 @@
|
|
|
15
15
|
*/
|
|
16
16
|
/** Env var naming the file both sides agree on. */
|
|
17
17
|
export declare const TERMINAL_SIZE_PATH_VARIABLE = "APEX_TERMINAL_SIZE_PATH";
|
|
18
|
+
/**
|
|
19
|
+
* Write without following a symlink at the final path component.
|
|
20
|
+
*
|
|
21
|
+
* Shared with `terminal-handoff.ts`, which took its transport from this file and inherits
|
|
22
|
+
* the same exposure: every file here is written by the supervisor, on the host, at a path
|
|
23
|
+
* the contained side may be able to create an entry at. A link planted there would
|
|
24
|
+
* redirect a host write the child could not otherwise make -- the audit demonstrated this
|
|
25
|
+
* against the workspace state directory. `O_NOFOLLOW` refuses instead.
|
|
26
|
+
*
|
|
27
|
+
* Node exposes no `openat`, so this guards the final component only; the containing
|
|
28
|
+
* directory must be one the child cannot write for the guarantee to be complete.
|
|
29
|
+
*/
|
|
30
|
+
export declare function writeWithoutFollowingLinks(path: string, contents: string): void;
|
|
18
31
|
export interface TerminalSize {
|
|
19
32
|
columns: number;
|
|
20
33
|
rows: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal-size.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/terminal-size.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AAEH,mDAAmD;AACnD,eAAO,MAAM,2BAA2B,4BAA4B,CAAC;AAErE,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACb;AAMD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAK5E;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,WAA4B,GAAG,MAAM,IAAI,CAmBzG;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,MAAM,CAAC,WAA4B,GACzC;IAAE,IAAI,EAAE,MAAM,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAyC3C","sourcesContent":["import { type FSWatcher, readFileSync, watch, writeFileSync } from \"node:fs\";\n\n/**\n * Carries the host terminal's size across the sandbox boundary.\n *\n * The sandboxed child runs under `bwrap --new-session`, so it has no\n * controlling terminal: the kernel never sends it SIGWINCH, and the window size\n * its stdout reports is frozen at whatever it was when the sandbox was created.\n * Everything downstream reads `process.stdout.columns`, so a resize left the TUI\n * rendering to a width the terminal no longer had.\n *\n * The supervisor owns a real TTY and does get the resize event. It publishes the\n * new size to a file inside the workspace, which is already bind-mounted into\n * the sandbox; the child watches that file and republishes it as a `resize` on\n * its own stdout. Signals are deliberately not used as the transport: bwrap sits\n * between the two processes as PID 1 and does not forward SIGWINCH.\n */\n\n/** Env var naming the file both sides agree on. */\nexport const TERMINAL_SIZE_PATH_VARIABLE = \"APEX_TERMINAL_SIZE_PATH\";\n\nexport interface TerminalSize {\n\tcolumns: number;\n\trows: number;\n}\n\nfunction formatSize(size: TerminalSize): string {\n\treturn `${size.columns} ${size.rows}\\n`;\n}\n\nexport function parseTerminalSize(contents: string): TerminalSize | undefined {\n\tconst [columns, rows] = contents.trim().split(/\\s+/).map(Number);\n\tif (!Number.isInteger(columns) || !Number.isInteger(rows)) return undefined;\n\tif (columns <= 0 || rows <= 0) return undefined;\n\treturn { columns, rows };\n}\n\n/**\n * Host side. Publishes the current size, then republishes on every resize.\n * Returns a stop function; the caller must run it on every launch exit path.\n */\nexport function publishTerminalSize(path: string, stdout: NodeJS.WriteStream = process.stdout): () => void {\n\tif (!stdout.isTTY) return () => {};\n\n\tconst write = () => {\n\t\tconst columns = stdout.columns;\n\t\tconst rows = stdout.rows;\n\t\tif (!Number.isInteger(columns) || !Number.isInteger(rows)) return;\n\t\ttry {\n\t\t\
|
|
1
|
+
{"version":3,"file":"terminal-size.d.ts","sourceRoot":"","sources":["../../../src/core/sandbox/terminal-size.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AAEH,mDAAmD;AACnD,eAAO,MAAM,2BAA2B,4BAA4B,CAAC;AAErE;;;;;;;;;;;GAWG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAW/E;AAED,MAAM,WAAW,YAAY;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACb;AAMD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAK5E;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,MAAM,CAAC,WAA4B,GAAG,MAAM,IAAI,CAmBzG;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAChC,IAAI,EAAE,MAAM,EACZ,MAAM,GAAE,MAAM,CAAC,WAA4B,GACzC;IAAE,IAAI,EAAE,MAAM,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAyC3C","sourcesContent":["import { closeSync, constants, type FSWatcher, openSync, readFileSync, watch, writeFileSync } from \"node:fs\";\n\n/**\n * Carries the host terminal's size across the sandbox boundary.\n *\n * The sandboxed child runs under `bwrap --new-session`, so it has no\n * controlling terminal: the kernel never sends it SIGWINCH, and the window size\n * its stdout reports is frozen at whatever it was when the sandbox was created.\n * Everything downstream reads `process.stdout.columns`, so a resize left the TUI\n * rendering to a width the terminal no longer had.\n *\n * The supervisor owns a real TTY and does get the resize event. It publishes the\n * new size to a file inside the workspace, which is already bind-mounted into\n * the sandbox; the child watches that file and republishes it as a `resize` on\n * its own stdout. Signals are deliberately not used as the transport: bwrap sits\n * between the two processes as PID 1 and does not forward SIGWINCH.\n */\n\n/** Env var naming the file both sides agree on. */\nexport const TERMINAL_SIZE_PATH_VARIABLE = \"APEX_TERMINAL_SIZE_PATH\";\n\n/**\n * Write without following a symlink at the final path component.\n *\n * Shared with `terminal-handoff.ts`, which took its transport from this file and inherits\n * the same exposure: every file here is written by the supervisor, on the host, at a path\n * the contained side may be able to create an entry at. A link planted there would\n * redirect a host write the child could not otherwise make -- the audit demonstrated this\n * against the workspace state directory. `O_NOFOLLOW` refuses instead.\n *\n * Node exposes no `openat`, so this guards the final component only; the containing\n * directory must be one the child cannot write for the guarantee to be complete.\n */\nexport function writeWithoutFollowingLinks(path: string, contents: string): void {\n\tconst descriptor = openSync(\n\t\tpath,\n\t\tconstants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW,\n\t\t0o600,\n\t);\n\ttry {\n\t\twriteFileSync(descriptor, contents);\n\t} finally {\n\t\tcloseSync(descriptor);\n\t}\n}\n\nexport interface TerminalSize {\n\tcolumns: number;\n\trows: number;\n}\n\nfunction formatSize(size: TerminalSize): string {\n\treturn `${size.columns} ${size.rows}\\n`;\n}\n\nexport function parseTerminalSize(contents: string): TerminalSize | undefined {\n\tconst [columns, rows] = contents.trim().split(/\\s+/).map(Number);\n\tif (!Number.isInteger(columns) || !Number.isInteger(rows)) return undefined;\n\tif (columns <= 0 || rows <= 0) return undefined;\n\treturn { columns, rows };\n}\n\n/**\n * Host side. Publishes the current size, then republishes on every resize.\n * Returns a stop function; the caller must run it on every launch exit path.\n */\nexport function publishTerminalSize(path: string, stdout: NodeJS.WriteStream = process.stdout): () => void {\n\tif (!stdout.isTTY) return () => {};\n\n\tconst write = () => {\n\t\tconst columns = stdout.columns;\n\t\tconst rows = stdout.rows;\n\t\tif (!Number.isInteger(columns) || !Number.isInteger(rows)) return;\n\t\ttry {\n\t\t\twriteWithoutFollowingLinks(path, formatSize({ columns, rows }));\n\t\t} catch {\n\t\t\t// A size we cannot publish is not worth failing a launch over.\n\t\t}\n\t};\n\n\twrite();\n\tstdout.on(\"resize\", write);\n\treturn () => {\n\t\tstdout.off(\"resize\", write);\n\t};\n}\n\n/**\n * Child side. Applies the published size to `process.stdout` and keeps applying\n * it as the file changes.\n *\n * `columns` and `rows` are redefined as getters because Node recomputes them\n * from the sandbox's own console on SIGWINCH, which is exactly the stale value\n * this exists to override. The setters swallow those writes rather than throwing.\n */\nexport function applyTerminalSize(\n\tpath: string,\n\tstdout: NodeJS.WriteStream = process.stdout,\n): { stop: () => void; refresh: () => void } {\n\tlet current: TerminalSize | undefined;\n\n\tconst refresh = () => {\n\t\tlet size: TerminalSize | undefined;\n\t\ttry {\n\t\t\tsize = parseTerminalSize(readFileSync(path, \"utf8\"));\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tif (!size) return;\n\t\tif (current && current.columns === size.columns && current.rows === size.rows) return;\n\t\tcurrent = size;\n\t\tObject.defineProperty(stdout, \"columns\", {\n\t\t\tconfigurable: true,\n\t\t\tget: () => current?.columns,\n\t\t\tset: () => {},\n\t\t});\n\t\tObject.defineProperty(stdout, \"rows\", {\n\t\t\tconfigurable: true,\n\t\t\tget: () => current?.rows,\n\t\t\tset: () => {},\n\t\t});\n\t\tstdout.emit(\"resize\");\n\t};\n\n\trefresh();\n\n\tlet watcher: FSWatcher | undefined;\n\ttry {\n\t\twatcher = watch(path, refresh);\n\t} catch {\n\t\t// Without a watcher the initial size still applies; only live resize is lost.\n\t}\n\n\treturn {\n\t\trefresh,\n\t\tstop: () => {\n\t\t\twatcher?.close();\n\t\t},\n\t};\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFileSync, watch, writeFileSync } from "node:fs";
|
|
1
|
+
import { closeSync, constants, openSync, readFileSync, watch, writeFileSync } from "node:fs";
|
|
2
2
|
/**
|
|
3
3
|
* Carries the host terminal's size across the sandbox boundary.
|
|
4
4
|
*
|
|
@@ -16,6 +16,27 @@ import { readFileSync, watch, writeFileSync } from "node:fs";
|
|
|
16
16
|
*/
|
|
17
17
|
/** Env var naming the file both sides agree on. */
|
|
18
18
|
export const TERMINAL_SIZE_PATH_VARIABLE = "APEX_TERMINAL_SIZE_PATH";
|
|
19
|
+
/**
|
|
20
|
+
* Write without following a symlink at the final path component.
|
|
21
|
+
*
|
|
22
|
+
* Shared with `terminal-handoff.ts`, which took its transport from this file and inherits
|
|
23
|
+
* the same exposure: every file here is written by the supervisor, on the host, at a path
|
|
24
|
+
* the contained side may be able to create an entry at. A link planted there would
|
|
25
|
+
* redirect a host write the child could not otherwise make -- the audit demonstrated this
|
|
26
|
+
* against the workspace state directory. `O_NOFOLLOW` refuses instead.
|
|
27
|
+
*
|
|
28
|
+
* Node exposes no `openat`, so this guards the final component only; the containing
|
|
29
|
+
* directory must be one the child cannot write for the guarantee to be complete.
|
|
30
|
+
*/
|
|
31
|
+
export function writeWithoutFollowingLinks(path, contents) {
|
|
32
|
+
const descriptor = openSync(path, constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW, 0o600);
|
|
33
|
+
try {
|
|
34
|
+
writeFileSync(descriptor, contents);
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
closeSync(descriptor);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
19
40
|
function formatSize(size) {
|
|
20
41
|
return `${size.columns} ${size.rows}\n`;
|
|
21
42
|
}
|
|
@@ -40,7 +61,7 @@ export function publishTerminalSize(path, stdout = process.stdout) {
|
|
|
40
61
|
if (!Number.isInteger(columns) || !Number.isInteger(rows))
|
|
41
62
|
return;
|
|
42
63
|
try {
|
|
43
|
-
|
|
64
|
+
writeWithoutFollowingLinks(path, formatSize({ columns, rows }));
|
|
44
65
|
}
|
|
45
66
|
catch {
|
|
46
67
|
// A size we cannot publish is not worth failing a launch over.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"terminal-size.js","sourceRoot":"","sources":["../../../src/core/sandbox/terminal-size.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"terminal-size.js","sourceRoot":"","sources":["../../../src/core/sandbox/terminal-size.ts"],"names":[],"mappings":"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","sourcesContent":["import { closeSync, constants, type FSWatcher, openSync, readFileSync, watch, writeFileSync } from \"node:fs\";\n\n/**\n * Carries the host terminal's size across the sandbox boundary.\n *\n * The sandboxed child runs under `bwrap --new-session`, so it has no\n * controlling terminal: the kernel never sends it SIGWINCH, and the window size\n * its stdout reports is frozen at whatever it was when the sandbox was created.\n * Everything downstream reads `process.stdout.columns`, so a resize left the TUI\n * rendering to a width the terminal no longer had.\n *\n * The supervisor owns a real TTY and does get the resize event. It publishes the\n * new size to a file inside the workspace, which is already bind-mounted into\n * the sandbox; the child watches that file and republishes it as a `resize` on\n * its own stdout. Signals are deliberately not used as the transport: bwrap sits\n * between the two processes as PID 1 and does not forward SIGWINCH.\n */\n\n/** Env var naming the file both sides agree on. */\nexport const TERMINAL_SIZE_PATH_VARIABLE = \"APEX_TERMINAL_SIZE_PATH\";\n\n/**\n * Write without following a symlink at the final path component.\n *\n * Shared with `terminal-handoff.ts`, which took its transport from this file and inherits\n * the same exposure: every file here is written by the supervisor, on the host, at a path\n * the contained side may be able to create an entry at. A link planted there would\n * redirect a host write the child could not otherwise make -- the audit demonstrated this\n * against the workspace state directory. `O_NOFOLLOW` refuses instead.\n *\n * Node exposes no `openat`, so this guards the final component only; the containing\n * directory must be one the child cannot write for the guarantee to be complete.\n */\nexport function writeWithoutFollowingLinks(path: string, contents: string): void {\n\tconst descriptor = openSync(\n\t\tpath,\n\t\tconstants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC | constants.O_NOFOLLOW,\n\t\t0o600,\n\t);\n\ttry {\n\t\twriteFileSync(descriptor, contents);\n\t} finally {\n\t\tcloseSync(descriptor);\n\t}\n}\n\nexport interface TerminalSize {\n\tcolumns: number;\n\trows: number;\n}\n\nfunction formatSize(size: TerminalSize): string {\n\treturn `${size.columns} ${size.rows}\\n`;\n}\n\nexport function parseTerminalSize(contents: string): TerminalSize | undefined {\n\tconst [columns, rows] = contents.trim().split(/\\s+/).map(Number);\n\tif (!Number.isInteger(columns) || !Number.isInteger(rows)) return undefined;\n\tif (columns <= 0 || rows <= 0) return undefined;\n\treturn { columns, rows };\n}\n\n/**\n * Host side. Publishes the current size, then republishes on every resize.\n * Returns a stop function; the caller must run it on every launch exit path.\n */\nexport function publishTerminalSize(path: string, stdout: NodeJS.WriteStream = process.stdout): () => void {\n\tif (!stdout.isTTY) return () => {};\n\n\tconst write = () => {\n\t\tconst columns = stdout.columns;\n\t\tconst rows = stdout.rows;\n\t\tif (!Number.isInteger(columns) || !Number.isInteger(rows)) return;\n\t\ttry {\n\t\t\twriteWithoutFollowingLinks(path, formatSize({ columns, rows }));\n\t\t} catch {\n\t\t\t// A size we cannot publish is not worth failing a launch over.\n\t\t}\n\t};\n\n\twrite();\n\tstdout.on(\"resize\", write);\n\treturn () => {\n\t\tstdout.off(\"resize\", write);\n\t};\n}\n\n/**\n * Child side. Applies the published size to `process.stdout` and keeps applying\n * it as the file changes.\n *\n * `columns` and `rows` are redefined as getters because Node recomputes them\n * from the sandbox's own console on SIGWINCH, which is exactly the stale value\n * this exists to override. The setters swallow those writes rather than throwing.\n */\nexport function applyTerminalSize(\n\tpath: string,\n\tstdout: NodeJS.WriteStream = process.stdout,\n): { stop: () => void; refresh: () => void } {\n\tlet current: TerminalSize | undefined;\n\n\tconst refresh = () => {\n\t\tlet size: TerminalSize | undefined;\n\t\ttry {\n\t\t\tsize = parseTerminalSize(readFileSync(path, \"utf8\"));\n\t\t} catch {\n\t\t\treturn;\n\t\t}\n\t\tif (!size) return;\n\t\tif (current && current.columns === size.columns && current.rows === size.rows) return;\n\t\tcurrent = size;\n\t\tObject.defineProperty(stdout, \"columns\", {\n\t\t\tconfigurable: true,\n\t\t\tget: () => current?.columns,\n\t\t\tset: () => {},\n\t\t});\n\t\tObject.defineProperty(stdout, \"rows\", {\n\t\t\tconfigurable: true,\n\t\t\tget: () => current?.rows,\n\t\t\tset: () => {},\n\t\t});\n\t\tstdout.emit(\"resize\");\n\t};\n\n\trefresh();\n\n\tlet watcher: FSWatcher | undefined;\n\ttry {\n\t\twatcher = watch(path, refresh);\n\t} catch {\n\t\t// Without a watcher the initial size still applies; only live resize is lost.\n\t}\n\n\treturn {\n\t\trefresh,\n\t\tstop: () => {\n\t\t\twatcher?.close();\n\t\t},\n\t};\n}\n"]}
|
package/dist/core/sdk.d.ts
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import type { CredentialStore } from "@earendil-works/pi-ai";
|
|
1
2
|
import { type Model } from "@earendil-works/pi-ai/compat";
|
|
2
3
|
import { type ThinkingLevel } from "apex-code-agent-core";
|
|
3
4
|
import { AgentSession, type AgentSessionConfig } from "./agent-session.ts";
|
|
4
5
|
import type { AgentDefinitionResolver } from "./delegation/runtime.ts";
|
|
5
6
|
import type { LoadExtensionsResult, SessionStartEvent, ToolDefinition } from "./extensions/index.ts";
|
|
7
|
+
import type { HookRuntime } from "./hooks/types.ts";
|
|
6
8
|
import { ModelRuntime } from "./model-runtime.ts";
|
|
7
9
|
import type { ResourceLoader } from "./resource-loader.ts";
|
|
8
10
|
import { SessionManager } from "./session-manager.ts";
|
|
9
11
|
import { SettingsManager } from "./settings-manager.ts";
|
|
12
|
+
import { type BackgroundShellRegistry } from "./tools/background-shell.ts";
|
|
10
13
|
import type { EvidenceSink } from "./tools/contract.ts";
|
|
11
|
-
import { createBashTool, createCodingTools, createEditTool, createFindTool, createGrepTool, createLsTool, createReadOnlyTools, createReadTool, createWriteTool, withFileMutationQueue } from "./tools/index.ts";
|
|
14
|
+
import { createBashTool, createCodingTools, createEditTool, createFindTool, createGrepTool, createLsTool, createPowerShellTool, createReadOnlyTools, createReadTool, createWriteTool, withFileMutationQueue } from "./tools/index.ts";
|
|
12
15
|
export interface CreateAgentSessionOptions {
|
|
13
16
|
/** Working directory for project-local discovery. Default: process.cwd() */
|
|
14
17
|
cwd?: string;
|
|
@@ -57,6 +60,12 @@ export interface CreateAgentSessionOptions {
|
|
|
57
60
|
sessionStartEvent?: SessionStartEvent;
|
|
58
61
|
/** Authorization configuration for every registered tool call. */
|
|
59
62
|
permissionGate?: AgentSessionConfig["permissionGate"];
|
|
63
|
+
/** Optional headless permission bridge (ACP, spec 2026-08-31-acp-adapter.md): consulted before the interactive TUI responder. */
|
|
64
|
+
permissionResponderFactory?: AgentSessionConfig["permissionResponderFactory"];
|
|
65
|
+
/** Pre-assembled declarative hooks. Absent by default; `createAgentSession` assembles them from the settings manager when not supplied. */
|
|
66
|
+
hookRuntime?: HookRuntime;
|
|
67
|
+
/** Background-shell registry. Absent by default; `createAgentSession` creates one and disposes it with the session. */
|
|
68
|
+
backgroundShellRegistry?: BackgroundShellRegistry;
|
|
60
69
|
/** Optional destination for source-level evidence; defaults to the durable session sink. */
|
|
61
70
|
evidenceSink?: EvidenceSink;
|
|
62
71
|
/** Optional post-mutation diagnostics injected into `edit`/`write` (LSP.4). */
|
|
@@ -77,7 +86,16 @@ export interface CreateAgentSessionOptions {
|
|
|
77
86
|
delegation?: {
|
|
78
87
|
resolveAgent?: AgentDefinitionResolver;
|
|
79
88
|
};
|
|
89
|
+
/**
|
|
90
|
+
* Credential store the MCP OAuth connector uses (spec 2026-09-01-mcp-oauth).
|
|
91
|
+
* Default: the sandbox channel store in a sandboxed child, else the host
|
|
92
|
+
* AuthStorage — the same fallback chain the model runtime applies.
|
|
93
|
+
*/
|
|
94
|
+
mcpCredentials?: CredentialStore;
|
|
95
|
+
/** The caller's assertion about OS sandbox containment. */
|
|
96
|
+
sandbox?: SdkSandboxContract;
|
|
80
97
|
}
|
|
98
|
+
export type SdkSandboxContract = "required" | "external" | "none";
|
|
81
99
|
/** Result from createAgentSession */
|
|
82
100
|
export interface CreateAgentSessionResult {
|
|
83
101
|
/** The created session */
|
|
@@ -86,12 +104,16 @@ export interface CreateAgentSessionResult {
|
|
|
86
104
|
extensionsResult: LoadExtensionsResult;
|
|
87
105
|
/** Warning if session was restored with a different model than saved */
|
|
88
106
|
modelFallbackMessage?: string;
|
|
107
|
+
sandboxContract: SdkSandboxContract;
|
|
108
|
+
sandboxDiagnostic?: string;
|
|
89
109
|
}
|
|
110
|
+
export declare const SDK_SANDBOX_ENFORCEMENT_MARKER_VARIABLE = "APEX_CODE_SANDBOX_ENFORCED";
|
|
111
|
+
export declare const SDK_SANDBOX_ENFORCEMENT_MARKER_VALUE = "1";
|
|
90
112
|
export * from "./agent-session-runtime.ts";
|
|
91
113
|
export type { ExtensionAPI, ExtensionCommandContext, ExtensionContext, ExtensionFactory, InlineExtension, SlashCommandInfo, SlashCommandSource, ToolDefinition, } from "./extensions/index.ts";
|
|
92
114
|
export type { PromptTemplate } from "./prompt-templates.ts";
|
|
93
115
|
export type { Skill } from "./skills.ts";
|
|
94
116
|
export type { Tool } from "./tools/index.ts";
|
|
95
|
-
export { withFileMutationQueue, createCodingTools, createReadOnlyTools, createReadTool, createBashTool, createEditTool, createWriteTool, createGrepTool, createFindTool, createLsTool, };
|
|
117
|
+
export { withFileMutationQueue, createCodingTools, createReadOnlyTools, createReadTool, createBashTool, createEditTool, createWriteTool, createGrepTool, createFindTool, createLsTool, createPowerShellTool, };
|
|
96
118
|
export declare function createAgentSession(options?: CreateAgentSessionOptions): Promise<CreateAgentSessionResult>;
|
|
97
119
|
//# sourceMappingURL=sdk.d.ts.map
|
package/dist/core/sdk.d.ts.map
CHANGED
|
@@ -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 { clampThinkingLevel, type Message, type Model, streamSimple } from \"@earendil-works/pi-ai/compat\";\nimport { Agent, type AgentMessage, setDefaultStreamFn, type ThinkingLevel } from \"apex-code-agent-core\";\nimport { getAgentDir } from \"../config.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { AgentSession, type AgentSessionConfig } from \"./agent-session.ts\";\nimport { formatNoModelsAvailableMessage } from \"./auth-guidance.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport { createAgentDefinitionResolver } from \"./delegation/agents.ts\";\nimport type { AgentDefinitionResolver, DelegationRuntimeOptions } from \"./delegation/runtime.ts\";\nimport type { ExtensionRunner, LoadExtensionsResult, SessionStartEvent, ToolDefinition } from \"./extensions/index.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 { SettingsManager } from \"./settings-manager.ts\";\nimport { time } from \"./timings.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\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateWriteTool,\n\ttype ToolName,\n\twithFileMutationQueue,\n} from \"./tools/index.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\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/** 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 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}\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/** 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\nexport {\n\twithFileMutationQueue,\n\t// Tool factories (for custom cwd)\n\tcreateCodingTools,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateBashTool,\n\tcreateEditTool,\n\tcreateWriteTool,\n\tcreateGrepTool,\n\tcreateFindTool,\n\tcreateLsTool,\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\tconst settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);\n\tconst sessionManager = options.sessionManager ?? SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));\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\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 settings default\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// `web_search` and `lsp` join the core four only when configured. Both 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];\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\tlet agent: Agent;\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\n\tagent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt: \"\",\n\t\t\tmodel,\n\t\t\tthinkingLevel,\n\t\t\ttools: [],\n\t\t},\n\t\tconvertToLlm: convertToLlmWithBlockImages,\n\t\tstreamFn: async (model, context, options) => {\n\t\t\tconst providerRetrySettings = settingsManager.getProviderRetrySettings();\n\t\t\tconst httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();\n\t\t\t// SDKs treat timeout=0 as 0ms (immediate timeout), not \"no timeout\".\n\t\t\t// Use max int32 to effectively disable the timeout.\n\t\t\tconst effectiveTimeoutMs = httpIdleTimeoutMs === 0 ? 2147483647 : httpIdleTimeoutMs;\n\t\t\tconst timeoutMs = options?.timeoutMs ?? providerRetrySettings.timeoutMs ?? effectiveTimeoutMs;\n\t\t\tconst websocketConnectTimeoutMs =\n\t\t\t\toptions?.websocketConnectTimeoutMs ?? settingsManager.getWebSocketConnectTimeoutMs();\n\t\t\tconst headerRunner = extensionRunnerRef.current;\n\t\t\treturn modelRuntime.streamSimple(model, context, {\n\t\t\t\t...options,\n\t\t\t\ttimeoutMs,\n\t\t\t\twebsocketConnectTimeoutMs,\n\t\t\t\tmaxRetries: options?.maxRetries ?? providerRetrySettings.maxRetries,\n\t\t\t\tmaxRetryDelayMs: options?.maxRetryDelayMs ?? providerRetrySettings.maxRetryDelayMs,\n\t\t\t\ttransformHeaders: async (requestHeaders) => {\n\t\t\t\t\tconst headers = mergeProviderAttributionHeaders(\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tsettingsManager,\n\t\t\t\t\t\toptions?.sessionId,\n\t\t\t\t\t\trequestHeaders,\n\t\t\t\t\t);\n\t\t\t\t\treturn headerRunner?.hasHandlers(\"before_provider_headers\")\n\t\t\t\t\t\t? headerRunner.emitBeforeProviderHeaders(headers ?? {})\n\t\t\t\t\t\t: (headers ?? {});\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t\tonPayload: async (payload, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"before_provider_request\")) {\n\t\t\t\treturn payload;\n\t\t\t}\n\t\t\treturn runner.emitBeforeProviderRequest(payload);\n\t\t},\n\t\tonResponse: async (response, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"after_provider_response\")) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait runner.emit({\n\t\t\t\ttype: \"after_provider_response\",\n\t\t\t\tstatus: response.status,\n\t\t\t\theaders: response.headers,\n\t\t\t});\n\t\t},\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 messages if session has existing data\n\tif (hasExistingSession) {\n\t\tagent.state.messages = existingSession.messages;\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 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\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\tconst delegationRuntime: DelegationRuntimeOptions = {\n\t\t\tresolveAgent,\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: settingsManager.getDelegationMaxDepth(),\n\t\t\tgetParentSessionDir: () => sessionManager.getSessionDir(),\n\t\t\tbuildChildSession: async ({ definition, toolNames, depth, sessionId, artifactDir }) => {\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\tconst childSessionManager = artifactDir\n\t\t\t\t\t? SessionManager.create(cwd, artifactDir, {\n\t\t\t\t\t\t\tid: sessionId,\n\t\t\t\t\t\t\tparentSession: sessionManager.getSessionId(),\n\t\t\t\t\t\t\tdelegationDepth: depth,\n\t\t\t\t\t\t})\n\t\t\t\t\t: SessionManager.inMemory(cwd, {\n\t\t\t\t\t\t\tparentSession: sessionManager.getSessionId(),\n\t\t\t\t\t\t\tdelegationDepth: depth,\n\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,\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\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// 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\treturn {\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 childSession.prompt(prompt);\n\t\t\t\t\t\treturn { output: extractFinalAssistantText(childSession) };\n\t\t\t\t\t},\n\t\t\t\t\tdispose() {\n\t\t\t\t\t\tchildSession.dispose();\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t},\n\t\t};\n\t\tcustomTools.push(createDelegateToolDefinition(delegationRuntime) as ToolDefinition<any, any>);\n\t}\n\n\tconst session = new AgentSession({\n\t\tagent,\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\tinitialActiveToolNames,\n\t\tallowedToolNames,\n\t\texcludedToolNames,\n\t\textensionRunnerRef,\n\t\tsessionStartEvent: options.sessionStartEvent,\n\t\tpermissionGate: options.permissionGate,\n\t\tevidenceSink: options.evidenceSink,\n\t\tdiagnosticsOperations: options.diagnosticsOperations,\n\t\tlspOperations: options.lspOperations,\n\t\twebSearchOperations: options.webSearchOperations,\n\t});\n\tparentSessionRef.current = session;\n\tconst extensionsResult = resourceLoader.getExtensions();\n\n\treturn {\n\t\tsession,\n\t\textensionsResult,\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 } from \"@earendil-works/pi-ai\";\nimport { clampThinkingLevel, type Message, type Model, streamSimple } from \"@earendil-works/pi-ai/compat\";\nimport { Agent, type AgentMessage, setDefaultStreamFn, type ThinkingLevel } from \"apex-code-agent-core\";\nimport { getAgentDir } from \"../config.ts\";\nimport { resolvePath } from \"../utils/paths.ts\";\nimport { AgentSession, type AgentSessionConfig } from \"./agent-session.ts\";\nimport { formatNoModelsAvailableMessage } from \"./auth-guidance.ts\";\nimport { AuthStorage } from \"./auth-storage.ts\";\nimport { DEFAULT_THINKING_LEVEL } from \"./defaults.ts\";\nimport { createAgentDefinitionResolver } from \"./delegation/agents.ts\";\nimport type { AgentDefinitionResolver, DelegationRuntimeOptions } 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 { SANDBOX_ENFORCEMENT_MARKER_VALUE, SANDBOX_ENFORCEMENT_MARKER_VARIABLE } from \"./sandbox/cli-launch.ts\";\nimport { createSandboxCredentialStore } from \"./sandbox/rpc/credential-client.ts\";\nimport { getDefaultSessionDir, SessionManager } from \"./session-manager.ts\";\nimport { 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\";\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\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/** 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 sandbox channel store in a sandboxed child, else the host\n\t * AuthStorage — the same fallback chain the model runtime applies.\n\t */\n\tmcpCredentials?: CredentialStore;\n\t/** The caller's assertion about OS sandbox containment. */\n\tsandbox?: SdkSandboxContract;\n}\n\nexport type SdkSandboxContract = \"required\" | \"external\" | \"none\";\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/** Warning if session was restored with a different model than saved */\n\tmodelFallbackMessage?: string;\n\tsandboxContract: SdkSandboxContract;\n\tsandboxDiagnostic?: string;\n}\n\nexport const SDK_SANDBOX_ENFORCEMENT_MARKER_VARIABLE = SANDBOX_ENFORCEMENT_MARKER_VARIABLE;\nexport const SDK_SANDBOX_ENFORCEMENT_MARKER_VALUE = SANDBOX_ENFORCEMENT_MARKER_VALUE;\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\nexport {\n\twithFileMutationQueue,\n\t// Tool factories (for custom cwd)\n\tcreateCodingTools,\n\tcreateReadOnlyTools,\n\tcreateReadTool,\n\tcreateBashTool,\n\tcreateEditTool,\n\tcreateWriteTool,\n\tcreateGrepTool,\n\tcreateFindTool,\n\tcreateLsTool,\n\tcreatePowerShellTool,\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 sandboxContract = options.sandbox ?? \"none\";\n\tconst markerPresent = process.env[SDK_SANDBOX_ENFORCEMENT_MARKER_VARIABLE] === SDK_SANDBOX_ENFORCEMENT_MARKER_VALUE;\n\tif (sandboxContract === \"required\" && !markerPresent) {\n\t\tthrow new Error(\"SDK sandbox contract requires OS containment, but no enforcing supervisor marker is present.\");\n\t}\n\tconst sandboxDiagnostic =\n\t\tsandboxContract === \"external\"\n\t\t\t? \"Sandbox containment is external to the SDK; the SDK cannot verify OS containment.\"\n\t\t\t: sandboxContract === \"none\"\n\t\t\t\t? \"No OS containment is asserted by the SDK sandbox contract.\"\n\t\t\t\t: undefined;\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\tconst settingsManager = options.settingsManager ?? SettingsManager.create(cwd, agentDir);\n\tconst sessionManager = options.sessionManager ?? SessionManager.create(cwd, getDefaultSessionDir(cwd, agentDir));\n\tconst backgroundShellRegistry = options.backgroundShellRegistry ?? createBackgroundShellRegistry();\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 on the host, supervisor-mediated in a sandboxed\n\t// child, fail-closed when neither applies (spec 2026-09-01-mcp-oauth). Servers\n\t// without `auth: \"oauth\"` never touch it.\n\tconst mcpRuntime = createMcpRuntime(\n\t\tcwd,\n\t\tcreateSessionMcpConnector({\n\t\t\tcredentials: options.mcpCredentials ?? createSandboxCredentialStore() ?? AuthStorage.create(authPath),\n\t\t}),\n\t\t{ projectTrusted: settingsManager.isProjectTrusted() },\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\tlet agent: Agent;\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\n\tagent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt: \"\",\n\t\t\tmodel,\n\t\t\tthinkingLevel,\n\t\t\ttools: [],\n\t\t},\n\t\t// One normalized budget policy from settings; every execution mode (interactive,\n\t\t// print, JSON, RPC, ACP, SDK) runs through this session, so none counts on its\n\t\t// own (spec 2026-09-01-tool-reliability-and-execution-budgets.md).\n\t\trunBudget: settingsManager.getRunBudget(),\n\t\tconvertToLlm: convertToLlmWithBlockImages,\n\t\tstreamFn: async (model, context, options) => {\n\t\t\tconst providerRetrySettings = settingsManager.getProviderRetrySettings();\n\t\t\tconst httpIdleTimeoutMs = settingsManager.getHttpIdleTimeoutMs();\n\t\t\t// SDKs treat timeout=0 as 0ms (immediate timeout), not \"no timeout\".\n\t\t\t// Use max int32 to effectively disable the timeout.\n\t\t\tconst effectiveTimeoutMs = httpIdleTimeoutMs === 0 ? 2147483647 : httpIdleTimeoutMs;\n\t\t\tconst timeoutMs = options?.timeoutMs ?? providerRetrySettings.timeoutMs ?? effectiveTimeoutMs;\n\t\t\tconst websocketConnectTimeoutMs =\n\t\t\t\toptions?.websocketConnectTimeoutMs ?? settingsManager.getWebSocketConnectTimeoutMs();\n\t\t\tconst headerRunner = extensionRunnerRef.current;\n\t\t\treturn modelRuntime.streamSimple(model, context, {\n\t\t\t\t...options,\n\t\t\t\ttimeoutMs,\n\t\t\t\twebsocketConnectTimeoutMs,\n\t\t\t\tmaxRetries: options?.maxRetries ?? providerRetrySettings.maxRetries,\n\t\t\t\tmaxRetryDelayMs: options?.maxRetryDelayMs ?? providerRetrySettings.maxRetryDelayMs,\n\t\t\t\ttransformHeaders: async (requestHeaders) => {\n\t\t\t\t\tconst headers = mergeProviderAttributionHeaders(\n\t\t\t\t\t\tmodel,\n\t\t\t\t\t\tsettingsManager,\n\t\t\t\t\t\toptions?.sessionId,\n\t\t\t\t\t\trequestHeaders,\n\t\t\t\t\t);\n\t\t\t\t\treturn headerRunner?.hasHandlers(\"before_provider_headers\")\n\t\t\t\t\t\t? headerRunner.emitBeforeProviderHeaders(headers ?? {})\n\t\t\t\t\t\t: (headers ?? {});\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t\tonPayload: async (payload, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"before_provider_request\")) {\n\t\t\t\treturn payload;\n\t\t\t}\n\t\t\treturn runner.emitBeforeProviderRequest(payload);\n\t\t},\n\t\tonResponse: async (response, _model) => {\n\t\t\tconst runner = extensionRunnerRef.current;\n\t\t\tif (!runner?.hasHandlers(\"after_provider_response\")) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait runner.emit({\n\t\t\t\ttype: \"after_provider_response\",\n\t\t\t\tstatus: response.status,\n\t\t\t\theaders: response.headers,\n\t\t\t});\n\t\t},\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 messages if session has existing data\n\tif (hasExistingSession) {\n\t\tagent.state.messages = existingSession.messages;\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 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\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\tconst delegationRuntime: DelegationRuntimeOptions = {\n\t\t\tresolveAgent,\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: settingsManager.getDelegationMaxDepth(),\n\t\t\tgetParentSessionDir: () => sessionManager.getSessionDir(),\n\t\t\tbuildChildSession: async ({ definition, toolNames, depth, sessionId, artifactDir }) => {\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\tconst childSessionManager = artifactDir\n\t\t\t\t\t? SessionManager.create(cwd, artifactDir, {\n\t\t\t\t\t\t\tid: sessionId,\n\t\t\t\t\t\t\tparentSession: sessionManager.getSessionId(),\n\t\t\t\t\t\t\tdelegationDepth: depth,\n\t\t\t\t\t\t})\n\t\t\t\t\t: SessionManager.inMemory(cwd, {\n\t\t\t\t\t\t\tparentSession: sessionManager.getSessionId(),\n\t\t\t\t\t\t\tdelegationDepth: depth,\n\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,\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\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\tsandbox: sandboxContract,\n\t\t\t\t});\n\t\t\t\tawait childSession.bindExtensions({});\n\t\t\t\treturn {\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 childSession.prompt(prompt);\n\t\t\t\t\t\treturn { output: extractFinalAssistantText(childSession) };\n\t\t\t\t\t},\n\t\t\t\t\tdispose() {\n\t\t\t\t\t\tchildSession.dispose();\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t},\n\t\t};\n\t\tcustomTools.push(createDelegateToolDefinition(delegationRuntime) as ToolDefinition<any, any>);\n\t}\n\n\tconst session = new AgentSession({\n\t\tagent,\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\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\tmodelFallbackMessage,\n\t\tsandboxContract,\n\t\tsandboxDiagnostic,\n\t};\n}\n"]}
|