@f5-sales-demo/xcsh 19.51.2
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 +7073 -0
- package/README.md +14 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +109 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/todo/index.ts +206 -0
- package/examples/extensions/README.md +143 -0
- package/examples/extensions/api-demo.ts +89 -0
- package/examples/extensions/chalk-logger.ts +25 -0
- package/examples/extensions/hello.ts +32 -0
- package/examples/extensions/pirate.ts +43 -0
- package/examples/extensions/plan-mode.ts +551 -0
- package/examples/extensions/reload-runtime.ts +37 -0
- package/examples/extensions/todo.ts +296 -0
- package/examples/extensions/tools.ts +145 -0
- package/examples/extensions/with-deps/index.ts +36 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +16 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +48 -0
- package/examples/hooks/confirm-destructive.ts +58 -0
- package/examples/hooks/custom-compaction.ts +115 -0
- package/examples/hooks/dirty-repo-guard.ts +51 -0
- package/examples/hooks/file-trigger.ts +40 -0
- package/examples/hooks/git-checkpoint.ts +52 -0
- package/examples/hooks/handoff.ts +149 -0
- package/examples/hooks/permission-gate.ts +33 -0
- package/examples/hooks/protected-paths.ts +29 -0
- package/examples/hooks/qna.ts +118 -0
- package/examples/hooks/status-line.ts +39 -0
- package/examples/sdk/01-minimal.ts +21 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +43 -0
- package/examples/sdk/04-skills.ts +43 -0
- package/examples/sdk/06-extensions.ts +80 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +35 -0
- package/examples/sdk/08-prompt-templates.ts +41 -0
- package/examples/sdk/08-slash-commands.ts +46 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
- package/examples/sdk/11-sessions.ts +47 -0
- package/examples/sdk/README.md +172 -0
- package/package.json +539 -0
- package/scripts/build-info/resolvers.ts +51 -0
- package/scripts/capture-ax-fixture.ts +84 -0
- package/scripts/capture-login-fixture.ts +30 -0
- package/scripts/check-workflow-field-coverage.ts +101 -0
- package/scripts/extension-uat-harness.ts +340 -0
- package/scripts/format-prompts.ts +68 -0
- package/scripts/generate-api-spec-index.ts +580 -0
- package/scripts/generate-branding-index.ts +81 -0
- package/scripts/generate-build-info.ts +106 -0
- package/scripts/generate-console-catalog.ts +88 -0
- package/scripts/generate-console-field-metadata.ts +74 -0
- package/scripts/generate-docs-index.ts +40 -0
- package/scripts/generate-template.ts +32 -0
- package/scripts/generate-terraform-index.ts +81 -0
- package/scripts/mermaid-gallery.ts +132 -0
- package/scripts/uat-matrix-modalities.ts +451 -0
- package/scripts/uat-matrix-report.ts +175 -0
- package/scripts/uat-matrix.ts +353 -0
- package/src/async/index.ts +2 -0
- package/src/async/job-manager.ts +416 -0
- package/src/async/support.ts +5 -0
- package/src/autoresearch/apply-contract-to-state.ts +1 -0
- package/src/autoresearch/command-resume.md +17 -0
- package/src/autoresearch/contract.ts +205 -0
- package/src/autoresearch/dashboard.ts +374 -0
- package/src/autoresearch/git.ts +191 -0
- package/src/autoresearch/helpers.ts +328 -0
- package/src/autoresearch/index.ts +402 -0
- package/src/autoresearch/prompt.md +236 -0
- package/src/autoresearch/resume-message.md +16 -0
- package/src/autoresearch/state.ts +303 -0
- package/src/autoresearch/tools/init-experiment.ts +400 -0
- package/src/autoresearch/tools/log-experiment.ts +793 -0
- package/src/autoresearch/tools/run-experiment.ts +679 -0
- package/src/autoresearch/types.ts +114 -0
- package/src/browser/acquire.ts +249 -0
- package/src/browser/actions.ts +126 -0
- package/src/browser/auth.ts +176 -0
- package/src/browser/ax.ts +134 -0
- package/src/browser/cdp-core.ts +45 -0
- package/src/browser/cdp-page-actions.ts +51 -0
- package/src/browser/chrome-locate.ts +66 -0
- package/src/browser/dom-context.ts +89 -0
- package/src/browser/dt-context.ts +100 -0
- package/src/browser/extension-bridge.ts +182 -0
- package/src/browser/extension-page-actions.ts +397 -0
- package/src/browser/extension-provider.ts +373 -0
- package/src/browser/index.ts +17 -0
- package/src/browser/input-commit.ts +74 -0
- package/src/browser/native-messaging.ts +20 -0
- package/src/browser/page-actions.ts +18 -0
- package/src/browser/provider.ts +155 -0
- package/src/browser/resolver.ts +126 -0
- package/src/browser/selector.ts +40 -0
- package/src/bun-imports.d.ts +22 -0
- package/src/capability/context-file.ts +43 -0
- package/src/capability/extension-module.ts +34 -0
- package/src/capability/extension.ts +47 -0
- package/src/capability/fs.ts +107 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +438 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +74 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule.ts +224 -0
- package/src/capability/settings.ts +34 -0
- package/src/capability/skill.ts +50 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/ssh.ts +41 -0
- package/src/capability/system-prompt.ts +34 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +168 -0
- package/src/cli/agents-cli.ts +138 -0
- package/src/cli/args.ts +286 -0
- package/src/cli/chrome-cli.ts +88 -0
- package/src/cli/classify-install-target.ts +50 -0
- package/src/cli/commands/init-xdg.ts +27 -0
- package/src/cli/config-cli.ts +418 -0
- package/src/cli/file-processor.ts +121 -0
- package/src/cli/grep-cli.ts +160 -0
- package/src/cli/grievances-cli.ts +78 -0
- package/src/cli/initial-message.ts +58 -0
- package/src/cli/jupyter-cli.ts +106 -0
- package/src/cli/list-models.ts +128 -0
- package/src/cli/plugin-cli.ts +964 -0
- package/src/cli/read-cli.ts +68 -0
- package/src/cli/session-picker.ts +52 -0
- package/src/cli/setup-cli.ts +479 -0
- package/src/cli/shell-cli.ts +174 -0
- package/src/cli/ssh-cli.ts +179 -0
- package/src/cli/stats-cli.ts +182 -0
- package/src/cli/update-cli.ts +446 -0
- package/src/cli/web-search-cli.ts +203 -0
- package/src/cli.ts +123 -0
- package/src/commands/agents.ts +57 -0
- package/src/commands/chrome-host.ts +71 -0
- package/src/commands/chrome.ts +28 -0
- package/src/commands/commit.ts +36 -0
- package/src/commands/config.ts +51 -0
- package/src/commands/grep.ts +48 -0
- package/src/commands/grievances.ts +20 -0
- package/src/commands/jupyter.ts +32 -0
- package/src/commands/launch.ts +144 -0
- package/src/commands/plugin.ts +78 -0
- package/src/commands/read.ts +33 -0
- package/src/commands/setup.ts +42 -0
- package/src/commands/shell.ts +29 -0
- package/src/commands/ssh.ts +60 -0
- package/src/commands/stats.ts +29 -0
- package/src/commands/update.ts +21 -0
- package/src/commands/web-search.ts +46 -0
- package/src/commit/agentic/agent.ts +314 -0
- package/src/commit/agentic/fallback.ts +96 -0
- package/src/commit/agentic/index.ts +354 -0
- package/src/commit/agentic/prompts/analyze-file.md +22 -0
- package/src/commit/agentic/prompts/session-user.md +25 -0
- package/src/commit/agentic/prompts/split-confirm.md +1 -0
- package/src/commit/agentic/prompts/system.md +38 -0
- package/src/commit/agentic/state.ts +60 -0
- package/src/commit/agentic/tools/analyze-file.ts +127 -0
- package/src/commit/agentic/tools/git-file-diff.ts +191 -0
- package/src/commit/agentic/tools/git-hunk.ts +50 -0
- package/src/commit/agentic/tools/git-overview.ts +81 -0
- package/src/commit/agentic/tools/index.ts +54 -0
- package/src/commit/agentic/tools/propose-changelog.ts +139 -0
- package/src/commit/agentic/tools/propose-commit.ts +122 -0
- package/src/commit/agentic/tools/recent-commits.ts +81 -0
- package/src/commit/agentic/tools/schemas.ts +31 -0
- package/src/commit/agentic/tools/split-commit.ts +251 -0
- package/src/commit/agentic/topo-sort.ts +44 -0
- package/src/commit/agentic/trivial.ts +51 -0
- package/src/commit/agentic/validation.ts +183 -0
- package/src/commit/analysis/conventional.ts +122 -0
- package/src/commit/analysis/index.ts +4 -0
- package/src/commit/analysis/scope.ts +242 -0
- package/src/commit/analysis/summary.ts +105 -0
- package/src/commit/analysis/validation.ts +66 -0
- package/src/commit/changelog/detect.ts +40 -0
- package/src/commit/changelog/generate.ts +101 -0
- package/src/commit/changelog/index.ts +234 -0
- package/src/commit/changelog/parse.ts +44 -0
- package/src/commit/cli.ts +85 -0
- package/src/commit/git/diff.ts +148 -0
- package/src/commit/index.ts +5 -0
- package/src/commit/map-reduce/index.ts +69 -0
- package/src/commit/map-reduce/map-phase.ts +193 -0
- package/src/commit/map-reduce/reduce-phase.ts +108 -0
- package/src/commit/map-reduce/utils.ts +9 -0
- package/src/commit/message.ts +11 -0
- package/src/commit/model-selection.ts +66 -0
- package/src/commit/pipeline.ts +242 -0
- package/src/commit/prompts/analysis-system.md +148 -0
- package/src/commit/prompts/analysis-user.md +38 -0
- package/src/commit/prompts/changelog-system.md +50 -0
- package/src/commit/prompts/changelog-user.md +18 -0
- package/src/commit/prompts/file-observer-system.md +24 -0
- package/src/commit/prompts/file-observer-user.md +8 -0
- package/src/commit/prompts/reduce-system.md +50 -0
- package/src/commit/prompts/reduce-user.md +17 -0
- package/src/commit/prompts/summary-retry.md +3 -0
- package/src/commit/prompts/summary-system.md +38 -0
- package/src/commit/prompts/summary-user.md +13 -0
- package/src/commit/prompts/types-description.md +2 -0
- package/src/commit/types.ts +118 -0
- package/src/commit/utils/exclusions.ts +42 -0
- package/src/commit/utils.ts +44 -0
- package/src/config/auto-config.ts +698 -0
- package/src/config/context-schema.json +115 -0
- package/src/config/file-lock.ts +121 -0
- package/src/config/keybindings.ts +493 -0
- package/src/config/mcp-schema.json +230 -0
- package/src/config/model-equivalence.ts +675 -0
- package/src/config/model-registry.ts +2265 -0
- package/src/config/model-resolver.ts +1289 -0
- package/src/config/prompt-templates.ts +271 -0
- package/src/config/resolve-config-value.ts +110 -0
- package/src/config/settings-schema.ts +2155 -0
- package/src/config/settings.ts +730 -0
- package/src/config.ts +418 -0
- package/src/cursor.ts +323 -0
- package/src/dap/client.ts +674 -0
- package/src/dap/config.ts +150 -0
- package/src/dap/defaults.json +211 -0
- package/src/dap/index.ts +4 -0
- package/src/dap/session.ts +1255 -0
- package/src/dap/types.ts +600 -0
- package/src/debug/index.ts +440 -0
- package/src/debug/log-formatting.ts +58 -0
- package/src/debug/log-viewer.ts +908 -0
- package/src/debug/profiler.ts +158 -0
- package/src/debug/report-bundle.ts +365 -0
- package/src/debug/system-info.ts +107 -0
- package/src/discovery/agents-md.ts +67 -0
- package/src/discovery/agents.ts +219 -0
- package/src/discovery/builtin.ts +851 -0
- package/src/discovery/claude-plugins.ts +297 -0
- package/src/discovery/claude.ts +543 -0
- package/src/discovery/cline.ts +83 -0
- package/src/discovery/codex.ts +535 -0
- package/src/discovery/cursor.ts +220 -0
- package/src/discovery/gemini.ts +396 -0
- package/src/discovery/github.ts +118 -0
- package/src/discovery/helpers.ts +934 -0
- package/src/discovery/index.ts +79 -0
- package/src/discovery/language.ts +46 -0
- package/src/discovery/mcp-json.ts +171 -0
- package/src/discovery/opencode.ts +393 -0
- package/src/discovery/plugin-dir-roots.ts +28 -0
- package/src/discovery/ssh.ts +153 -0
- package/src/discovery/substitute-plugin-root.ts +29 -0
- package/src/discovery/vscode.ts +105 -0
- package/src/discovery/windsurf.ts +147 -0
- package/src/edit/diff.ts +818 -0
- package/src/edit/index.ts +481 -0
- package/src/edit/line-hash.ts +67 -0
- package/src/edit/modes/chunk.ts +762 -0
- package/src/edit/modes/hashline.ts +1366 -0
- package/src/edit/modes/patch.ts +1791 -0
- package/src/edit/modes/replace.ts +1115 -0
- package/src/edit/normalize.ts +375 -0
- package/src/edit/renderer.ts +627 -0
- package/src/exa/factory.ts +61 -0
- package/src/exa/index.ts +27 -0
- package/src/exa/mcp-client.ts +293 -0
- package/src/exa/render.ts +232 -0
- package/src/exa/researcher.ts +47 -0
- package/src/exa/search.ts +86 -0
- package/src/exa/types.ts +166 -0
- package/src/exa/websets.ts +247 -0
- package/src/exec/bash-executor.ts +332 -0
- package/src/exec/exec.ts +53 -0
- package/src/exec/idle-timeout-watchdog.ts +126 -0
- package/src/exec/non-interactive-env.ts +48 -0
- package/src/export/custom-share.ts +65 -0
- package/src/export/html/index.ts +161 -0
- package/src/export/html/template.css +1001 -0
- package/src/export/html/template.generated.ts +2 -0
- package/src/export/html/template.html +46 -0
- package/src/export/html/template.js +1950 -0
- package/src/export/html/template.macro.ts +24 -0
- package/src/export/html/vendor/highlight.min.js +1213 -0
- package/src/export/html/vendor/marked.min.js +6 -0
- package/src/export/ttsr.ts +434 -0
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +25 -0
- package/src/extensibility/custom-commands/bundled/review/index.ts +456 -0
- package/src/extensibility/custom-commands/index.ts +2 -0
- package/src/extensibility/custom-commands/loader.ts +236 -0
- package/src/extensibility/custom-commands/types.ts +111 -0
- package/src/extensibility/custom-tools/index.ts +7 -0
- package/src/extensibility/custom-tools/loader.ts +238 -0
- package/src/extensibility/custom-tools/types.ts +251 -0
- package/src/extensibility/custom-tools/wrapper.ts +47 -0
- package/src/extensibility/extensions/index.ts +15 -0
- package/src/extensibility/extensions/loader.ts +572 -0
- package/src/extensibility/extensions/runner.ts +848 -0
- package/src/extensibility/extensions/types.ts +1394 -0
- package/src/extensibility/extensions/wrapper.ts +191 -0
- package/src/extensibility/hooks/index.ts +5 -0
- package/src/extensibility/hooks/loader.ts +255 -0
- package/src/extensibility/hooks/runner.ts +425 -0
- package/src/extensibility/hooks/tool-wrapper.ts +108 -0
- package/src/extensibility/hooks/types.ts +826 -0
- package/src/extensibility/plugins/doctor.ts +66 -0
- package/src/extensibility/plugins/git-url.ts +281 -0
- package/src/extensibility/plugins/index.ts +9 -0
- package/src/extensibility/plugins/installer.ts +192 -0
- package/src/extensibility/plugins/loader.ts +267 -0
- package/src/extensibility/plugins/manager.ts +731 -0
- package/src/extensibility/plugins/marketplace/cache.ts +136 -0
- package/src/extensibility/plugins/marketplace/fetcher.ts +318 -0
- package/src/extensibility/plugins/marketplace/index.ts +6 -0
- package/src/extensibility/plugins/marketplace/manager.ts +781 -0
- package/src/extensibility/plugins/marketplace/prerequisites.ts +255 -0
- package/src/extensibility/plugins/marketplace/registry.ts +196 -0
- package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
- package/src/extensibility/plugins/marketplace/types.ts +201 -0
- package/src/extensibility/plugins/parser.ts +105 -0
- package/src/extensibility/plugins/types.ts +194 -0
- package/src/extensibility/skills.ts +269 -0
- package/src/extensibility/slash-commands.ts +246 -0
- package/src/extensibility/tool-proxy.ts +25 -0
- package/src/extensibility/utils.ts +38 -0
- package/src/index.ts +61 -0
- package/src/internal-urls/agent-protocol.ts +136 -0
- package/src/internal-urls/api-catalog-resolve.ts +495 -0
- package/src/internal-urls/api-catalog-types.ts +94 -0
- package/src/internal-urls/api-spec-resolve.ts +1036 -0
- package/src/internal-urls/api-spec-types.ts +239 -0
- package/src/internal-urls/artifact-protocol.ts +97 -0
- package/src/internal-urls/branding-index.generated.ts +88 -0
- package/src/internal-urls/build-info-runtime.ts +302 -0
- package/src/internal-urls/build-info.generated.ts +33 -0
- package/src/internal-urls/computer-profile.ts +692 -0
- package/src/internal-urls/console-catalog-types.ts +21 -0
- package/src/internal-urls/console-catalog.generated.ts +532 -0
- package/src/internal-urls/console-field-metadata-types.ts +59 -0
- package/src/internal-urls/console-field-metadata.generated.ts +4841 -0
- package/src/internal-urls/console-resolve.ts +178 -0
- package/src/internal-urls/docs-index.generated.ts +698 -0
- package/src/internal-urls/extension-api.md +147 -0
- package/src/internal-urls/index.ts +46 -0
- package/src/internal-urls/jobs-protocol.ts +119 -0
- package/src/internal-urls/json-query.ts +126 -0
- package/src/internal-urls/local-protocol.ts +206 -0
- package/src/internal-urls/mcp-protocol.ts +156 -0
- package/src/internal-urls/memory-protocol.ts +133 -0
- package/src/internal-urls/parse.ts +72 -0
- package/src/internal-urls/profile-collectors.ts +106 -0
- package/src/internal-urls/router.ts +55 -0
- package/src/internal-urls/rule-protocol.ts +55 -0
- package/src/internal-urls/skill-protocol.ts +111 -0
- package/src/internal-urls/terraform-index.generated.ts +1852 -0
- package/src/internal-urls/terraform-resolve.ts +184 -0
- package/src/internal-urls/terraform-types.ts +45 -0
- package/src/internal-urls/types.ts +52 -0
- package/src/internal-urls/user-profile.ts +363 -0
- package/src/internal-urls/xcsh-protocol.ts +644 -0
- package/src/ipy/cancellation.ts +28 -0
- package/src/ipy/executor.ts +1155 -0
- package/src/ipy/gateway-coordinator.ts +424 -0
- package/src/ipy/kernel.ts +1091 -0
- package/src/ipy/modules.ts +144 -0
- package/src/ipy/prelude.py +849 -0
- package/src/ipy/prelude.ts +3 -0
- package/src/ipy/runtime.ts +221 -0
- package/src/locales/ar.json +512 -0
- package/src/locales/de.json +512 -0
- package/src/locales/en.json +549 -0
- package/src/locales/es.json +512 -0
- package/src/locales/fr.json +512 -0
- package/src/locales/hi.json +512 -0
- package/src/locales/index.ts +29 -0
- package/src/locales/it.json +512 -0
- package/src/locales/ja.json +512 -0
- package/src/locales/ko.json +512 -0
- package/src/locales/pt-br.json +512 -0
- package/src/locales/th.json +512 -0
- package/src/locales/zh-cn.json +512 -0
- package/src/locales/zh-tw.json +512 -0
- package/src/lsp/client.ts +941 -0
- package/src/lsp/clients/biome-client.ts +202 -0
- package/src/lsp/clients/index.ts +50 -0
- package/src/lsp/clients/lsp-linter-client.ts +93 -0
- package/src/lsp/clients/swiftlint-client.ts +120 -0
- package/src/lsp/config.ts +418 -0
- package/src/lsp/defaults.json +999 -0
- package/src/lsp/edits.ts +109 -0
- package/src/lsp/index.ts +1746 -0
- package/src/lsp/lspmux.ts +233 -0
- package/src/lsp/render.ts +692 -0
- package/src/lsp/startup-events.ts +13 -0
- package/src/lsp/types.ts +444 -0
- package/src/lsp/utils.ts +691 -0
- package/src/main.ts +944 -0
- package/src/mcp/client.ts +482 -0
- package/src/mcp/config-writer.ts +225 -0
- package/src/mcp/config.ts +365 -0
- package/src/mcp/discoverable-tool-metadata.ts +202 -0
- package/src/mcp/index.ts +29 -0
- package/src/mcp/json-rpc.ts +84 -0
- package/src/mcp/loader.ts +124 -0
- package/src/mcp/manager.ts +1152 -0
- package/src/mcp/oauth-discovery.ts +349 -0
- package/src/mcp/oauth-flow.ts +387 -0
- package/src/mcp/render.ts +157 -0
- package/src/mcp/smithery-auth.ts +104 -0
- package/src/mcp/smithery-connect.ts +145 -0
- package/src/mcp/smithery-registry.ts +477 -0
- package/src/mcp/tool-bridge.ts +417 -0
- package/src/mcp/tool-cache.ts +117 -0
- package/src/mcp/transports/http.ts +475 -0
- package/src/mcp/transports/index.ts +6 -0
- package/src/mcp/transports/stdio.ts +325 -0
- package/src/mcp/types.ts +423 -0
- package/src/memories/index.ts +1115 -0
- package/src/memories/storage.ts +577 -0
- package/src/modes/acp/acp-agent.ts +1361 -0
- package/src/modes/acp/acp-event-mapper.ts +538 -0
- package/src/modes/acp/acp-mode.ts +15 -0
- package/src/modes/acp/index.ts +2 -0
- package/src/modes/components/agent-dashboard.ts +1124 -0
- package/src/modes/components/assistant-message.ts +213 -0
- package/src/modes/components/bash-execution.ts +345 -0
- package/src/modes/components/bordered-loader.ts +41 -0
- package/src/modes/components/branch-summary-message.ts +45 -0
- package/src/modes/components/btw-panel.ts +103 -0
- package/src/modes/components/compaction-summary-message.ts +51 -0
- package/src/modes/components/context-add-wizard.ts +534 -0
- package/src/modes/components/countdown-timer.ts +75 -0
- package/src/modes/components/custom-editor.ts +238 -0
- package/src/modes/components/custom-message.ts +91 -0
- package/src/modes/components/diff.ts +210 -0
- package/src/modes/components/dynamic-border.ts +25 -0
- package/src/modes/components/extensions/extension-dashboard.ts +326 -0
- package/src/modes/components/extensions/extension-list.ts +492 -0
- package/src/modes/components/extensions/index.ts +9 -0
- package/src/modes/components/extensions/inspector-panel.ts +317 -0
- package/src/modes/components/extensions/state-manager.ts +587 -0
- package/src/modes/components/extensions/types.ts +191 -0
- package/src/modes/components/footer.ts +262 -0
- package/src/modes/components/gutter-block.ts +315 -0
- package/src/modes/components/history-search.ts +158 -0
- package/src/modes/components/hook-editor.ts +151 -0
- package/src/modes/components/hook-input.ts +81 -0
- package/src/modes/components/hook-message.ts +100 -0
- package/src/modes/components/hook-selector.ts +192 -0
- package/src/modes/components/index.ts +36 -0
- package/src/modes/components/keybinding-hints.ts +65 -0
- package/src/modes/components/login-dialog.ts +164 -0
- package/src/modes/components/mcp-add-wizard.ts +1314 -0
- package/src/modes/components/model-selector.ts +946 -0
- package/src/modes/components/oauth-selector.ts +211 -0
- package/src/modes/components/plugin-selector.ts +95 -0
- package/src/modes/components/plugin-settings.ts +477 -0
- package/src/modes/components/plugins/index.ts +5 -0
- package/src/modes/components/plugins/plugin-dashboard.ts +523 -0
- package/src/modes/components/plugins/plugin-inspector-pane.ts +84 -0
- package/src/modes/components/plugins/plugin-list-pane.ts +109 -0
- package/src/modes/components/plugins/state-manager.ts +229 -0
- package/src/modes/components/plugins/types.ts +51 -0
- package/src/modes/components/plugins/utils.ts +6 -0
- package/src/modes/components/python-execution.ts +244 -0
- package/src/modes/components/queue-mode-selector.ts +56 -0
- package/src/modes/components/read-tool-group.ts +137 -0
- package/src/modes/components/session-observer-overlay.ts +483 -0
- package/src/modes/components/session-selector.ts +343 -0
- package/src/modes/components/settings-defs.ts +568 -0
- package/src/modes/components/settings-selector.ts +641 -0
- package/src/modes/components/show-images-selector.ts +45 -0
- package/src/modes/components/skill-message.ts +90 -0
- package/src/modes/components/status-line/context-gradient.ts +64 -0
- package/src/modes/components/status-line/git-utils.ts +42 -0
- package/src/modes/components/status-line/hex-ansi.ts +29 -0
- package/src/modes/components/status-line/index.ts +4 -0
- package/src/modes/components/status-line/presets.ts +174 -0
- package/src/modes/components/status-line/segments.ts +494 -0
- package/src/modes/components/status-line/separators.ts +55 -0
- package/src/modes/components/status-line/token-rate.ts +66 -0
- package/src/modes/components/status-line/types.ts +96 -0
- package/src/modes/components/status-line-segment-editor.ts +361 -0
- package/src/modes/components/status-line.ts +729 -0
- package/src/modes/components/theme-selector.ts +63 -0
- package/src/modes/components/thinking-selector.ts +52 -0
- package/src/modes/components/todo-reminder.ts +54 -0
- package/src/modes/components/tool-execution.ts +781 -0
- package/src/modes/components/tree-selector.ts +908 -0
- package/src/modes/components/ttsr-notification.ts +80 -0
- package/src/modes/components/user-message-selector.ts +141 -0
- package/src/modes/components/user-message.ts +100 -0
- package/src/modes/components/visual-truncate.ts +63 -0
- package/src/modes/components/welcome-checks.ts +313 -0
- package/src/modes/components/welcome.ts +270 -0
- package/src/modes/controllers/btw-controller.ts +193 -0
- package/src/modes/controllers/chord-routing.ts +37 -0
- package/src/modes/controllers/command-controller.ts +1293 -0
- package/src/modes/controllers/context-command-controller.ts +61 -0
- package/src/modes/controllers/event-controller.ts +743 -0
- package/src/modes/controllers/extension-ui-controller.ts +983 -0
- package/src/modes/controllers/input-controller.ts +877 -0
- package/src/modes/controllers/mcp-command-controller.ts +1940 -0
- package/src/modes/controllers/selector-controller.ts +1292 -0
- package/src/modes/controllers/ssh-command-controller.ts +421 -0
- package/src/modes/index.ts +34 -0
- package/src/modes/interactive-mode.ts +1781 -0
- package/src/modes/oauth-manual-input.ts +42 -0
- package/src/modes/print-mode.ts +196 -0
- package/src/modes/prompt-action-autocomplete.ts +231 -0
- package/src/modes/rpc/host-tools.ts +186 -0
- package/src/modes/rpc/rpc-client.ts +762 -0
- package/src/modes/rpc/rpc-mode.ts +877 -0
- package/src/modes/rpc/rpc-types.ts +322 -0
- package/src/modes/session-observer-registry.ts +146 -0
- package/src/modes/shared.ts +30 -0
- package/src/modes/theme/defaults/index.ts +7 -0
- package/src/modes/theme/defaults/xcsh-dark.json +113 -0
- package/src/modes/theme/defaults/xcsh-light.json +115 -0
- package/src/modes/theme/mermaid-cache.ts +151 -0
- package/src/modes/theme/mermaid-palette.ts +39 -0
- package/src/modes/theme/theme-schema.json +553 -0
- package/src/modes/theme/theme.ts +2563 -0
- package/src/modes/types.ts +273 -0
- package/src/modes/utils/hotkeys-markdown.ts +60 -0
- package/src/modes/utils/keybinding-matchers.ts +30 -0
- package/src/modes/utils/read-group-outcome-aggregator.ts +55 -0
- package/src/modes/utils/sanitize-error-message.ts +60 -0
- package/src/modes/utils/tools-markdown.ts +28 -0
- package/src/modes/utils/ui-helpers.ts +734 -0
- package/src/plan-mode/approved-plan.ts +55 -0
- package/src/plan-mode/state.ts +6 -0
- package/src/priority.json +37 -0
- package/src/prompts/agents/designer.md +67 -0
- package/src/prompts/agents/explore.md +61 -0
- package/src/prompts/agents/frontmatter.md +10 -0
- package/src/prompts/agents/init.md +36 -0
- package/src/prompts/agents/librarian.md +121 -0
- package/src/prompts/agents/plan.md +49 -0
- package/src/prompts/agents/reviewer.md +127 -0
- package/src/prompts/agents/task.md +16 -0
- package/src/prompts/ci-green-request.md +35 -0
- package/src/prompts/compaction/branch-summary-context.md +5 -0
- package/src/prompts/compaction/branch-summary-preamble.md +2 -0
- package/src/prompts/compaction/branch-summary.md +30 -0
- package/src/prompts/compaction/compaction-short-summary.md +9 -0
- package/src/prompts/compaction/compaction-summary-context.md +5 -0
- package/src/prompts/compaction/compaction-summary.md +38 -0
- package/src/prompts/compaction/compaction-turn-prefix.md +17 -0
- package/src/prompts/compaction/compaction-update-summary.md +45 -0
- package/src/prompts/memories/consolidation.md +30 -0
- package/src/prompts/memories/read-path.md +11 -0
- package/src/prompts/memories/stage_one_input.md +6 -0
- package/src/prompts/memories/stage_one_system.md +21 -0
- package/src/prompts/review-request.md +70 -0
- package/src/prompts/system/agent-creation-architect.md +65 -0
- package/src/prompts/system/agent-creation-user.md +6 -0
- package/src/prompts/system/auto-handoff-threshold-focus.md +1 -0
- package/src/prompts/system/btw-user.md +8 -0
- package/src/prompts/system/commit-message-system.md +2 -0
- package/src/prompts/system/custom-system-prompt.md +82 -0
- package/src/prompts/system/eager-todo.md +13 -0
- package/src/prompts/system/file-operations.md +10 -0
- package/src/prompts/system/handoff-document.md +48 -0
- package/src/prompts/system/plan-mode-active.md +114 -0
- package/src/prompts/system/plan-mode-approved.md +23 -0
- package/src/prompts/system/plan-mode-reference.md +14 -0
- package/src/prompts/system/plan-mode-subagent.md +34 -0
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
- package/src/prompts/system/subagent-submit-reminder.md +7 -0
- package/src/prompts/system/subagent-system-prompt.md +36 -0
- package/src/prompts/system/subagent-user-prompt.md +12 -0
- package/src/prompts/system/summarization-system.md +3 -0
- package/src/prompts/system/system-prompt.md +698 -0
- package/src/prompts/system/title-system.md +2 -0
- package/src/prompts/system/ttsr-interrupt.md +7 -0
- package/src/prompts/system/web-search.md +28 -0
- package/src/prompts/tools/ask.md +28 -0
- package/src/prompts/tools/ast-edit.md +48 -0
- package/src/prompts/tools/ast-grep.md +54 -0
- package/src/prompts/tools/async-result.md +5 -0
- package/src/prompts/tools/bash.md +67 -0
- package/src/prompts/tools/browser.md +26 -0
- package/src/prompts/tools/calculator.md +10 -0
- package/src/prompts/tools/cancel-job.md +5 -0
- package/src/prompts/tools/catalog-workflow-runner.md +13 -0
- package/src/prompts/tools/checkpoint.md +16 -0
- package/src/prompts/tools/chunk-edit.md +279 -0
- package/src/prompts/tools/debug.md +43 -0
- package/src/prompts/tools/display-image.md +23 -0
- package/src/prompts/tools/exit-plan-mode.md +41 -0
- package/src/prompts/tools/find.md +26 -0
- package/src/prompts/tools/gemini-image.md +7 -0
- package/src/prompts/tools/grep.md +29 -0
- package/src/prompts/tools/hashline.md +131 -0
- package/src/prompts/tools/inspect-image-system.md +20 -0
- package/src/prompts/tools/inspect-image.md +32 -0
- package/src/prompts/tools/lsp.md +33 -0
- package/src/prompts/tools/patch.md +77 -0
- package/src/prompts/tools/poll.md +5 -0
- package/src/prompts/tools/python.md +60 -0
- package/src/prompts/tools/read-chunk.md +35 -0
- package/src/prompts/tools/read.md +63 -0
- package/src/prompts/tools/render-mermaid.md +9 -0
- package/src/prompts/tools/replace.md +36 -0
- package/src/prompts/tools/resolve.md +8 -0
- package/src/prompts/tools/rewind.md +13 -0
- package/src/prompts/tools/search-tool-bm25.md +34 -0
- package/src/prompts/tools/ssh.md +44 -0
- package/src/prompts/tools/task-summary.md +28 -0
- package/src/prompts/tools/task.md +131 -0
- package/src/prompts/tools/todo-write.md +89 -0
- package/src/prompts/tools/vim.md +98 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +14 -0
- package/src/prompts/tools/xcsh-api.md +283 -0
- package/src/resource-management/index.ts +42 -0
- package/src/sdk.ts +2123 -0
- package/src/secrets/index.ts +146 -0
- package/src/secrets/obfuscator.ts +290 -0
- package/src/secrets/regex.ts +21 -0
- package/src/services/context-env.ts +102 -0
- package/src/services/xcsh-api-client.ts +230 -0
- package/src/services/xcsh-context-command.ts +803 -0
- package/src/services/xcsh-context-display.ts +49 -0
- package/src/services/xcsh-context-indicators.ts +17 -0
- package/src/services/xcsh-context-segment.ts +39 -0
- package/src/services/xcsh-context.ts +1410 -0
- package/src/services/xcsh-env.ts +107 -0
- package/src/services/xcsh-knowledge.ts +177 -0
- package/src/services/xcsh-table.ts +164 -0
- package/src/session/agent-session.ts +6690 -0
- package/src/session/agent-storage.ts +451 -0
- package/src/session/artifacts.ts +132 -0
- package/src/session/auth-storage.ts +17 -0
- package/src/session/blob-store.ts +135 -0
- package/src/session/compaction/branch-summarization.ts +322 -0
- package/src/session/compaction/compaction.ts +1388 -0
- package/src/session/compaction/index.ts +7 -0
- package/src/session/compaction/pruning.ts +91 -0
- package/src/session/compaction/utils.ts +184 -0
- package/src/session/history-storage.ts +253 -0
- package/src/session/messages.ts +597 -0
- package/src/session/session-dump-format.ts +203 -0
- package/src/session/session-manager.ts +2816 -0
- package/src/session/session-storage.ts +370 -0
- package/src/session/streaming-output.ts +770 -0
- package/src/session/tool-choice-queue.ts +213 -0
- package/src/slash-commands/builtin-registry.ts +1614 -0
- package/src/slash-commands/export-command.ts +178 -0
- package/src/slash-commands/marketplace-install-parser.ts +99 -0
- package/src/slash-commands/resource-commands.ts +250 -0
- package/src/ssh/config-writer.ts +183 -0
- package/src/ssh/connection-manager.ts +445 -0
- package/src/ssh/ssh-executor.ts +131 -0
- package/src/ssh/sshfs-mount.ts +137 -0
- package/src/ssh/utils.ts +8 -0
- package/src/stt/downloader.ts +71 -0
- package/src/stt/index.ts +3 -0
- package/src/stt/recorder.ts +351 -0
- package/src/stt/setup.ts +52 -0
- package/src/stt/stt-controller.ts +160 -0
- package/src/stt/transcribe.py +70 -0
- package/src/stt/transcriber.ts +91 -0
- package/src/system-prompt.ts +688 -0
- package/src/task/agents.ts +166 -0
- package/src/task/commands.ts +131 -0
- package/src/task/discovery.ts +126 -0
- package/src/task/executor.ts +1324 -0
- package/src/task/index.ts +1197 -0
- package/src/task/isolation-backend.ts +72 -0
- package/src/task/name-generator.ts +1577 -0
- package/src/task/output-manager.ts +107 -0
- package/src/task/parallel.ts +116 -0
- package/src/task/render.ts +1035 -0
- package/src/task/subprocess-tool-registry.ts +88 -0
- package/src/task/template.ts +33 -0
- package/src/task/types.ts +230 -0
- package/src/task/worktree.ts +580 -0
- package/src/task/xcsh-command.ts +26 -0
- package/src/thinking.ts +87 -0
- package/src/tools/action-renderer.ts +198 -0
- package/src/tools/archive-reader.ts +315 -0
- package/src/tools/ask.ts +791 -0
- package/src/tools/ast-edit.ts +492 -0
- package/src/tools/ast-grep.ts +453 -0
- package/src/tools/auto-generated-guard.ts +305 -0
- package/src/tools/bash-interactive.ts +383 -0
- package/src/tools/bash-interceptor.ts +67 -0
- package/src/tools/bash-normalize.ts +107 -0
- package/src/tools/bash-skill-urls.ts +246 -0
- package/src/tools/bash.ts +878 -0
- package/src/tools/browser-renderer.ts +129 -0
- package/src/tools/browser.ts +1366 -0
- package/src/tools/calculator.ts +543 -0
- package/src/tools/cancel-job.ts +100 -0
- package/src/tools/catalog-workflow-runner.ts +718 -0
- package/src/tools/checkpoint.ts +133 -0
- package/src/tools/context.ts +39 -0
- package/src/tools/debug.ts +1004 -0
- package/src/tools/display-image-renderer.ts +96 -0
- package/src/tools/display-image.ts +109 -0
- package/src/tools/exit-plan-mode.ts +101 -0
- package/src/tools/fetch.ts +1462 -0
- package/src/tools/find.ts +437 -0
- package/src/tools/fs-cache-invalidation.ts +33 -0
- package/src/tools/gemini-image.ts +1004 -0
- package/src/tools/grep.ts +705 -0
- package/src/tools/index.ts +442 -0
- package/src/tools/inspect-image-renderer.ts +101 -0
- package/src/tools/inspect-image.ts +176 -0
- package/src/tools/json-tree.ts +266 -0
- package/src/tools/jtd-to-json-schema.ts +199 -0
- package/src/tools/jtd-to-typescript.ts +136 -0
- package/src/tools/jtd-utils.ts +102 -0
- package/src/tools/list-limit.ts +40 -0
- package/src/tools/mermaid-renderer.ts +127 -0
- package/src/tools/notebook.ts +288 -0
- package/src/tools/output-meta.ts +588 -0
- package/src/tools/path-utils.ts +560 -0
- package/src/tools/plan-mode-guard.ts +41 -0
- package/src/tools/poll-tool.ts +177 -0
- package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
- package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
- package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
- package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
- package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
- package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
- package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
- package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
- package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
- package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
- package/src/tools/puppeteer/10_stealth_plugins.txt +206 -0
- package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
- package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
- package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
- package/src/tools/python.ts +1184 -0
- package/src/tools/read.ts +1591 -0
- package/src/tools/render-mermaid.ts +93 -0
- package/src/tools/render-utils.ts +677 -0
- package/src/tools/renderers.ts +82 -0
- package/src/tools/report-tool-issue.ts +80 -0
- package/src/tools/resolve.ts +196 -0
- package/src/tools/review.ts +238 -0
- package/src/tools/search-tool-bm25.ts +288 -0
- package/src/tools/sqlite-reader.ts +623 -0
- package/src/tools/ssh.ts +322 -0
- package/src/tools/submit-result.ts +229 -0
- package/src/tools/todo-render.ts +33 -0
- package/src/tools/todo-write.ts +440 -0
- package/src/tools/tool-errors.ts +62 -0
- package/src/tools/tool-result.ts +93 -0
- package/src/tools/tool-timeouts.ts +30 -0
- package/src/tools/vim.ts +981 -0
- package/src/tools/write.ts +671 -0
- package/src/tools/xcsh-api-renderer.ts +402 -0
- package/src/tools/xcsh-api.ts +887 -0
- package/src/tui/code-cell.ts +101 -0
- package/src/tui/file-list.ts +47 -0
- package/src/tui/index.ts +11 -0
- package/src/tui/output-block.ts +175 -0
- package/src/tui/status-line.ts +39 -0
- package/src/tui/tree-list.ts +84 -0
- package/src/tui/types.ts +15 -0
- package/src/tui/utils.ts +103 -0
- package/src/utils/changelog.ts +98 -0
- package/src/utils/clipboard.ts +80 -0
- package/src/utils/command-args.ts +76 -0
- package/src/utils/commit-message-generator.ts +137 -0
- package/src/utils/edit-mode.ts +50 -0
- package/src/utils/event-bus.ts +33 -0
- package/src/utils/external-editor.ts +65 -0
- package/src/utils/file-display-mode.ts +37 -0
- package/src/utils/file-mentions.ts +376 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/git.ts +1495 -0
- package/src/utils/gitstatus.ts +140 -0
- package/src/utils/image-convert.ts +27 -0
- package/src/utils/image-loading.ts +98 -0
- package/src/utils/image-passthrough.ts +118 -0
- package/src/utils/image-resize.ts +272 -0
- package/src/utils/image-viewer.ts +17 -0
- package/src/utils/lang-from-path.ts +239 -0
- package/src/utils/markit.ts +81 -0
- package/src/utils/open.ts +20 -0
- package/src/utils/session-color.ts +55 -0
- package/src/utils/shell-snapshot.ts +188 -0
- package/src/utils/title-generator.ts +193 -0
- package/src/utils/tool-choice.ts +28 -0
- package/src/utils/tools-manager.ts +356 -0
- package/src/vim/buffer.ts +309 -0
- package/src/vim/commands.ts +382 -0
- package/src/vim/engine.ts +2426 -0
- package/src/vim/parser.ts +151 -0
- package/src/vim/render.ts +252 -0
- package/src/vim/types.ts +197 -0
- package/src/web/kagi.ts +174 -0
- package/src/web/parallel.ts +346 -0
- package/src/web/scrapers/artifacthub.ts +207 -0
- package/src/web/scrapers/arxiv.ts +83 -0
- package/src/web/scrapers/aur.ts +162 -0
- package/src/web/scrapers/biorxiv.ts +133 -0
- package/src/web/scrapers/bluesky.ts +262 -0
- package/src/web/scrapers/brew.ts +172 -0
- package/src/web/scrapers/cheatsh.ts +68 -0
- package/src/web/scrapers/chocolatey.ts +196 -0
- package/src/web/scrapers/choosealicense.ts +95 -0
- package/src/web/scrapers/cisa-kev.ts +87 -0
- package/src/web/scrapers/clojars.ts +154 -0
- package/src/web/scrapers/coingecko.ts +177 -0
- package/src/web/scrapers/crates-io.ts +110 -0
- package/src/web/scrapers/crossref.ts +136 -0
- package/src/web/scrapers/devto.ts +147 -0
- package/src/web/scrapers/discogs.ts +306 -0
- package/src/web/scrapers/discourse.ts +197 -0
- package/src/web/scrapers/dockerhub.ts +138 -0
- package/src/web/scrapers/docs-rs.ts +653 -0
- package/src/web/scrapers/fdroid.ts +134 -0
- package/src/web/scrapers/firefox-addons.ts +191 -0
- package/src/web/scrapers/flathub.ts +223 -0
- package/src/web/scrapers/github-gist.ts +58 -0
- package/src/web/scrapers/github.ts +452 -0
- package/src/web/scrapers/gitlab.ts +401 -0
- package/src/web/scrapers/go-pkg.ts +266 -0
- package/src/web/scrapers/hackage.ts +140 -0
- package/src/web/scrapers/hackernews.ts +189 -0
- package/src/web/scrapers/hex.ts +105 -0
- package/src/web/scrapers/huggingface.ts +321 -0
- package/src/web/scrapers/iacr.ts +89 -0
- package/src/web/scrapers/index.ts +252 -0
- package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
- package/src/web/scrapers/lemmy.ts +203 -0
- package/src/web/scrapers/lobsters.ts +175 -0
- package/src/web/scrapers/mastodon.ts +292 -0
- package/src/web/scrapers/maven.ts +138 -0
- package/src/web/scrapers/mdn.ts +169 -0
- package/src/web/scrapers/metacpan.ts +222 -0
- package/src/web/scrapers/musicbrainz.ts +250 -0
- package/src/web/scrapers/npm.ts +98 -0
- package/src/web/scrapers/nuget.ts +183 -0
- package/src/web/scrapers/nvd.ts +222 -0
- package/src/web/scrapers/ollama.ts +239 -0
- package/src/web/scrapers/open-vsx.ts +106 -0
- package/src/web/scrapers/opencorporates.ts +292 -0
- package/src/web/scrapers/openlibrary.ts +336 -0
- package/src/web/scrapers/orcid.ts +286 -0
- package/src/web/scrapers/osv.ts +176 -0
- package/src/web/scrapers/packagist.ts +160 -0
- package/src/web/scrapers/pub-dev.ts +143 -0
- package/src/web/scrapers/pubmed.ts +211 -0
- package/src/web/scrapers/pypi.ts +112 -0
- package/src/web/scrapers/rawg.ts +110 -0
- package/src/web/scrapers/readthedocs.ts +120 -0
- package/src/web/scrapers/reddit.ts +95 -0
- package/src/web/scrapers/repology.ts +251 -0
- package/src/web/scrapers/rfc.ts +201 -0
- package/src/web/scrapers/rubygems.ts +103 -0
- package/src/web/scrapers/searchcode.ts +190 -0
- package/src/web/scrapers/sec-edgar.ts +261 -0
- package/src/web/scrapers/semantic-scholar.ts +171 -0
- package/src/web/scrapers/snapcraft.ts +187 -0
- package/src/web/scrapers/sourcegraph.ts +336 -0
- package/src/web/scrapers/spdx.ts +108 -0
- package/src/web/scrapers/spotify.ts +198 -0
- package/src/web/scrapers/stackoverflow.ts +120 -0
- package/src/web/scrapers/terraform.ts +277 -0
- package/src/web/scrapers/tldr.ts +47 -0
- package/src/web/scrapers/twitter.ts +93 -0
- package/src/web/scrapers/types.ts +267 -0
- package/src/web/scrapers/utils.ts +109 -0
- package/src/web/scrapers/vimeo.ts +133 -0
- package/src/web/scrapers/vscode-marketplace.ts +187 -0
- package/src/web/scrapers/w3c.ts +156 -0
- package/src/web/scrapers/wikidata.ts +344 -0
- package/src/web/scrapers/wikipedia.ts +84 -0
- package/src/web/scrapers/youtube.ts +313 -0
- package/src/web/search/errors.ts +120 -0
- package/src/web/search/index.ts +346 -0
- package/src/web/search/params.ts +129 -0
- package/src/web/search/provider.ts +97 -0
- package/src/web/search/providers/anthropic.ts +339 -0
- package/src/web/search/providers/base.ts +35 -0
- package/src/web/search/providers/brave.ts +150 -0
- package/src/web/search/providers/codex.ts +402 -0
- package/src/web/search/providers/exa.ts +264 -0
- package/src/web/search/providers/firecrawl.ts +115 -0
- package/src/web/search/providers/gemini.ts +566 -0
- package/src/web/search/providers/jina.ts +99 -0
- package/src/web/search/providers/kagi.ts +70 -0
- package/src/web/search/providers/kimi.ts +157 -0
- package/src/web/search/providers/parallel.ts +63 -0
- package/src/web/search/providers/perplexity.ts +546 -0
- package/src/web/search/providers/synthetic.ts +113 -0
- package/src/web/search/providers/tavily.ts +162 -0
- package/src/web/search/providers/utils.ts +36 -0
- package/src/web/search/providers/zai.ts +311 -0
- package/src/web/search/render.ts +348 -0
- package/src/web/search/types.ts +438 -0
- package/src/web/search/utils.ts +17 -0
package/src/sdk.ts
ADDED
|
@@ -0,0 +1,2123 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Agent,
|
|
3
|
+
type AgentEvent,
|
|
4
|
+
type AgentMessage,
|
|
5
|
+
type AgentTool,
|
|
6
|
+
INTENT_FIELD,
|
|
7
|
+
type ThinkingLevel,
|
|
8
|
+
} from "@f5xc-salesdemos/pi-agent-core";
|
|
9
|
+
import type { Message, Model } from "@f5xc-salesdemos/pi-ai";
|
|
10
|
+
import {
|
|
11
|
+
getOpenAICodexTransportDetails,
|
|
12
|
+
prewarmOpenAICodexResponses,
|
|
13
|
+
} from "@f5xc-salesdemos/pi-ai/providers/openai-codex-responses";
|
|
14
|
+
import type { Component } from "@f5xc-salesdemos/pi-tui";
|
|
15
|
+
import {
|
|
16
|
+
$env,
|
|
17
|
+
$flag,
|
|
18
|
+
getAgentDbPath,
|
|
19
|
+
getAgentDir,
|
|
20
|
+
getLocale,
|
|
21
|
+
getLocaleDisplayName,
|
|
22
|
+
getProjectDir,
|
|
23
|
+
logger,
|
|
24
|
+
postmortem,
|
|
25
|
+
prompt,
|
|
26
|
+
Snowflake,
|
|
27
|
+
} from "@f5xc-salesdemos/pi-utils";
|
|
28
|
+
import { AsyncJobManager, isBackgroundJobSupportEnabled } from "./async";
|
|
29
|
+
import { createAutoresearchExtension } from "./autoresearch";
|
|
30
|
+
import { loadCapability } from "./capability";
|
|
31
|
+
import { type Rule, ruleCapability } from "./capability/rule";
|
|
32
|
+
import { hasLiteLLMEnv } from "./config/auto-config";
|
|
33
|
+
import { ModelRegistry } from "./config/model-registry";
|
|
34
|
+
import { formatModelString, parseModelPattern, parseModelString, resolveModelRoleValue } from "./config/model-resolver";
|
|
35
|
+
import { loadPromptTemplates as loadPromptTemplatesInternal, type PromptTemplate } from "./config/prompt-templates";
|
|
36
|
+
import { Settings, type SkillsSettings } from "./config/settings";
|
|
37
|
+
import { CursorExecHandlers } from "./cursor";
|
|
38
|
+
import "./discovery";
|
|
39
|
+
import { resolveConfigValue } from "./config/resolve-config-value";
|
|
40
|
+
import { initializeWithSettings } from "./discovery";
|
|
41
|
+
import { TtsrManager } from "./export/ttsr";
|
|
42
|
+
import {
|
|
43
|
+
type CustomCommandsLoadResult,
|
|
44
|
+
type LoadedCustomCommand,
|
|
45
|
+
loadCustomCommands as loadCustomCommandsInternal,
|
|
46
|
+
} from "./extensibility/custom-commands";
|
|
47
|
+
import { discoverAndLoadCustomTools } from "./extensibility/custom-tools";
|
|
48
|
+
import type { CustomTool, CustomToolContext, CustomToolSessionEvent } from "./extensibility/custom-tools/types";
|
|
49
|
+
import { CustomToolAdapter } from "./extensibility/custom-tools/wrapper";
|
|
50
|
+
import {
|
|
51
|
+
discoverAndLoadExtensions,
|
|
52
|
+
type ExtensionContext,
|
|
53
|
+
type ExtensionFactory,
|
|
54
|
+
ExtensionRunner,
|
|
55
|
+
ExtensionToolWrapper,
|
|
56
|
+
type ExtensionUIContext,
|
|
57
|
+
type LoadExtensionsResult,
|
|
58
|
+
loadExtensionFromFactory,
|
|
59
|
+
loadExtensions,
|
|
60
|
+
type ToolDefinition,
|
|
61
|
+
wrapRegisteredTools,
|
|
62
|
+
} from "./extensibility/extensions";
|
|
63
|
+
import { loadSkills as loadSkillsInternal, type Skill, type SkillWarning } from "./extensibility/skills";
|
|
64
|
+
import { type FileSlashCommand, loadSlashCommands as loadSlashCommandsInternal } from "./extensibility/slash-commands";
|
|
65
|
+
import {
|
|
66
|
+
AgentProtocolHandler,
|
|
67
|
+
ArtifactProtocolHandler,
|
|
68
|
+
InternalDocsProtocolHandler,
|
|
69
|
+
InternalUrlRouter,
|
|
70
|
+
JobsProtocolHandler,
|
|
71
|
+
LocalProtocolHandler,
|
|
72
|
+
McpProtocolHandler,
|
|
73
|
+
MemoryProtocolHandler,
|
|
74
|
+
RuleProtocolHandler,
|
|
75
|
+
SkillProtocolHandler,
|
|
76
|
+
} from "./internal-urls";
|
|
77
|
+
import { buildComputerHint, loadComputerProfile } from "./internal-urls/computer-profile";
|
|
78
|
+
import { loadProfile, type UserProfile } from "./internal-urls/user-profile";
|
|
79
|
+
import { disposeAllKernelSessions, disposeKernelSessionsByOwner } from "./ipy/executor";
|
|
80
|
+
import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "./lsp/startup-events";
|
|
81
|
+
import { discoverAndLoadMCPTools, type MCPManager, type MCPToolsLoadResult } from "./mcp";
|
|
82
|
+
import {
|
|
83
|
+
collectDiscoverableMCPTools,
|
|
84
|
+
formatDiscoverableMCPToolServerSummary,
|
|
85
|
+
selectDiscoverableMCPToolNamesByServer,
|
|
86
|
+
summarizeDiscoverableMCPTools,
|
|
87
|
+
} from "./mcp/discoverable-tool-metadata";
|
|
88
|
+
import { buildMemoryToolDeveloperInstructions, getMemoryRoot, startMemoryStartupTask } from "./memories";
|
|
89
|
+
import asyncResultTemplate from "./prompts/tools/async-result.md" with { type: "text" };
|
|
90
|
+
import {
|
|
91
|
+
collectEnvSecrets,
|
|
92
|
+
deobfuscateSessionContext,
|
|
93
|
+
loadSecrets,
|
|
94
|
+
obfuscateMessages,
|
|
95
|
+
SECRET_ENV_PATTERNS,
|
|
96
|
+
type SecretEntry,
|
|
97
|
+
SecretObfuscator,
|
|
98
|
+
} from "./secrets";
|
|
99
|
+
import { createContextEnv } from "./services/context-env";
|
|
100
|
+
import { AgentSession } from "./session/agent-session";
|
|
101
|
+
import { AuthStorage } from "./session/auth-storage";
|
|
102
|
+
import { convertToLlm } from "./session/messages";
|
|
103
|
+
import { SessionManager } from "./session/session-manager";
|
|
104
|
+
import { closeAllConnections } from "./ssh/connection-manager";
|
|
105
|
+
import { unmountAll } from "./ssh/sshfs-mount";
|
|
106
|
+
import {
|
|
107
|
+
buildSystemPrompt as buildSystemPromptInternal,
|
|
108
|
+
buildSystemPromptToolMetadata,
|
|
109
|
+
loadProjectContextFiles as loadContextFilesInternal,
|
|
110
|
+
} from "./system-prompt";
|
|
111
|
+
import { AgentOutputManager } from "./task/output-manager";
|
|
112
|
+
import { parseThinkingLevel, resolveThinkingLevelForModel, toReasoningEffort } from "./thinking";
|
|
113
|
+
import {
|
|
114
|
+
BashTool,
|
|
115
|
+
BUILTIN_TOOLS,
|
|
116
|
+
createTools,
|
|
117
|
+
discoverStartupLspServers,
|
|
118
|
+
EditTool,
|
|
119
|
+
FindTool,
|
|
120
|
+
GrepTool,
|
|
121
|
+
getSearchTools,
|
|
122
|
+
HIDDEN_TOOLS,
|
|
123
|
+
isSearchProviderPreference,
|
|
124
|
+
type LspStartupServerInfo,
|
|
125
|
+
loadSshTool,
|
|
126
|
+
PythonTool,
|
|
127
|
+
ReadTool,
|
|
128
|
+
ResolveTool,
|
|
129
|
+
renderSearchToolBm25Description,
|
|
130
|
+
setPreferredImageProvider,
|
|
131
|
+
setPreferredSearchProvider,
|
|
132
|
+
type Tool,
|
|
133
|
+
type ToolSession,
|
|
134
|
+
WriteTool,
|
|
135
|
+
warmupLspServers,
|
|
136
|
+
} from "./tools";
|
|
137
|
+
import { ToolContextStore } from "./tools/context";
|
|
138
|
+
import { getGeminiImageTools } from "./tools/gemini-image";
|
|
139
|
+
import { wrapToolWithMetaNotice } from "./tools/output-meta";
|
|
140
|
+
import { queueResolveHandler } from "./tools/resolve";
|
|
141
|
+
import { EventBus } from "./utils/event-bus";
|
|
142
|
+
import { buildNamedToolChoice } from "./utils/tool-choice";
|
|
143
|
+
|
|
144
|
+
// Types
|
|
145
|
+
export interface CreateAgentSessionOptions {
|
|
146
|
+
/** Working directory for project-local discovery. Default: getProjectDir() */
|
|
147
|
+
cwd?: string;
|
|
148
|
+
/** Global config directory. Default: ~/.omp/agent */
|
|
149
|
+
agentDir?: string;
|
|
150
|
+
/** Spawns to allow. Default: "*" */
|
|
151
|
+
spawns?: string;
|
|
152
|
+
|
|
153
|
+
/** Auth storage for credentials. Default: discoverAuthStorage(agentDir) */
|
|
154
|
+
authStorage?: AuthStorage;
|
|
155
|
+
/** Model registry. Default: discoverModels(authStorage, agentDir) */
|
|
156
|
+
modelRegistry?: ModelRegistry;
|
|
157
|
+
|
|
158
|
+
/** Model to use. Default: from settings, else first available */
|
|
159
|
+
model?: Model;
|
|
160
|
+
/** Raw model pattern string (e.g. from --model CLI flag) to resolve after extensions load.
|
|
161
|
+
* Used when model lookup is deferred because extension-provided models aren't registered yet. */
|
|
162
|
+
modelPattern?: string;
|
|
163
|
+
/** Thinking selector. Default: from settings, else unset */
|
|
164
|
+
thinkingLevel?: ThinkingLevel;
|
|
165
|
+
/** Models available for cycling (Ctrl+P in interactive mode) */
|
|
166
|
+
scopedModels?: Array<{ model: Model; thinkingLevel?: ThinkingLevel }>;
|
|
167
|
+
|
|
168
|
+
/** System prompt. String replaces default, function receives default and returns final. */
|
|
169
|
+
systemPrompt?: string | ((defaultPrompt: string) => string);
|
|
170
|
+
/** Optional provider-facing session identifier for prompt caches and sticky auth selection.
|
|
171
|
+
* Keeps persisted session files isolated while reusing provider-side caches. */
|
|
172
|
+
providerSessionId?: string;
|
|
173
|
+
|
|
174
|
+
/** Custom tools to register (in addition to built-in tools). Accepts both CustomTool and ToolDefinition. */
|
|
175
|
+
customTools?: (CustomTool | ToolDefinition)[];
|
|
176
|
+
/** Inline extensions (merged with discovery). */
|
|
177
|
+
extensions?: ExtensionFactory[];
|
|
178
|
+
/** Additional extension paths to load (merged with discovery). */
|
|
179
|
+
additionalExtensionPaths?: string[];
|
|
180
|
+
/** Disable extension discovery (explicit paths still load). */
|
|
181
|
+
disableExtensionDiscovery?: boolean;
|
|
182
|
+
/**
|
|
183
|
+
* Pre-loaded extensions (skips file discovery).
|
|
184
|
+
* @internal Used by CLI when extensions are loaded early to parse custom flags.
|
|
185
|
+
*/
|
|
186
|
+
preloadedExtensions?: LoadExtensionsResult;
|
|
187
|
+
|
|
188
|
+
/** Shared event bus for tool/extension communication. Default: creates new bus. */
|
|
189
|
+
eventBus?: EventBus;
|
|
190
|
+
|
|
191
|
+
/** Skills. Default: discovered from multiple locations */
|
|
192
|
+
skills?: Skill[];
|
|
193
|
+
/** Rules. Default: discovered from multiple locations */
|
|
194
|
+
rules?: Rule[];
|
|
195
|
+
/** Context files (XCSH.md content). Default: discovered walking up from cwd */
|
|
196
|
+
contextFiles?: Array<{ path: string; content: string }>;
|
|
197
|
+
/** Prompt templates. Default: discovered from cwd/.omp/prompts/ + agentDir/prompts/ */
|
|
198
|
+
promptTemplates?: PromptTemplate[];
|
|
199
|
+
/** File-based slash commands. Default: discovered from commands/ directories */
|
|
200
|
+
slashCommands?: FileSlashCommand[];
|
|
201
|
+
|
|
202
|
+
/** Enable MCP server discovery from .mcp.json files. Default: true */
|
|
203
|
+
enableMCP?: boolean;
|
|
204
|
+
|
|
205
|
+
/** Enable LSP integration (tool, formatting, diagnostics, warmup). Default: true */
|
|
206
|
+
enableLsp?: boolean;
|
|
207
|
+
/** Skip Python kernel availability check and prelude warmup */
|
|
208
|
+
skipPythonPreflight?: boolean;
|
|
209
|
+
/** Force Python prelude warmup even when test env would normally skip it */
|
|
210
|
+
forcePythonWarmup?: boolean;
|
|
211
|
+
|
|
212
|
+
/** Tool names explicitly requested (enables disabled-by-default tools) */
|
|
213
|
+
toolNames?: string[];
|
|
214
|
+
|
|
215
|
+
/** Output schema for structured completion (subagents) */
|
|
216
|
+
outputSchema?: unknown;
|
|
217
|
+
/** Whether to include the submit_result tool by default */
|
|
218
|
+
requireSubmitResultTool?: boolean;
|
|
219
|
+
/** Task recursion depth (for subagent sessions). Default: 0 */
|
|
220
|
+
taskDepth?: number;
|
|
221
|
+
/** Parent task ID prefix for nested artifact naming (e.g., "6-Extensions") */
|
|
222
|
+
parentTaskPrefix?: string;
|
|
223
|
+
|
|
224
|
+
/** Session manager. Default: session stored under the configured agentDir sessions root */
|
|
225
|
+
sessionManager?: SessionManager;
|
|
226
|
+
|
|
227
|
+
/** Settings instance. Default: Settings.init({ cwd, agentDir }) */
|
|
228
|
+
settings?: Settings;
|
|
229
|
+
|
|
230
|
+
/** Whether UI is available (enables interactive tools like ask). Default: false */
|
|
231
|
+
hasUI?: boolean;
|
|
232
|
+
|
|
233
|
+
/** Opaque fuzzy-search database handle (fork-specific). */
|
|
234
|
+
searchDb?: unknown;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Result from createAgentSession */
|
|
238
|
+
export interface CreateAgentSessionResult {
|
|
239
|
+
/** The created session */
|
|
240
|
+
session: AgentSession;
|
|
241
|
+
/** Extensions result (loaded extensions + runtime) */
|
|
242
|
+
extensionsResult: LoadExtensionsResult;
|
|
243
|
+
/** Update tool UI context (interactive mode) */
|
|
244
|
+
setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;
|
|
245
|
+
/** MCP manager for server lifecycle management (undefined if MCP disabled) */
|
|
246
|
+
mcpManager?: MCPManager;
|
|
247
|
+
/** Warning if session was restored with a different model than saved */
|
|
248
|
+
modelFallbackMessage?: string;
|
|
249
|
+
/** LSP servers detected for startup; warmup may continue in the background */
|
|
250
|
+
lspServers?: LspStartupServerInfo[];
|
|
251
|
+
/** Shared event bus for tool/extension communication */
|
|
252
|
+
eventBus: EventBus;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Re-exports
|
|
256
|
+
|
|
257
|
+
export type { PromptTemplate } from "./config/prompt-templates";
|
|
258
|
+
export { Settings, type SkillsSettings } from "./config/settings";
|
|
259
|
+
export type { CustomCommand, CustomCommandFactory } from "./extensibility/custom-commands/types";
|
|
260
|
+
export type { CustomTool, CustomToolFactory } from "./extensibility/custom-tools/types";
|
|
261
|
+
export type * from "./extensibility/extensions";
|
|
262
|
+
export type { Skill } from "./extensibility/skills";
|
|
263
|
+
export type { FileSlashCommand } from "./extensibility/slash-commands";
|
|
264
|
+
export type { MCPManager, MCPServerConfig, MCPServerConnection, MCPToolsLoadResult } from "./mcp";
|
|
265
|
+
export type { Tool } from "./tools";
|
|
266
|
+
|
|
267
|
+
export {
|
|
268
|
+
// Individual tool classes (for custom usage)
|
|
269
|
+
BashTool,
|
|
270
|
+
// Tool classes and factories
|
|
271
|
+
BUILTIN_TOOLS,
|
|
272
|
+
createTools,
|
|
273
|
+
EditTool,
|
|
274
|
+
FindTool,
|
|
275
|
+
GrepTool,
|
|
276
|
+
HIDDEN_TOOLS,
|
|
277
|
+
loadSshTool,
|
|
278
|
+
PythonTool,
|
|
279
|
+
ReadTool,
|
|
280
|
+
ResolveTool,
|
|
281
|
+
type ToolSession,
|
|
282
|
+
WriteTool,
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// Helper Functions
|
|
286
|
+
|
|
287
|
+
function getDefaultAgentDir(): string {
|
|
288
|
+
return getAgentDir();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Discovery Functions
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Create an AuthStorage instance with fallback support.
|
|
295
|
+
* Reads from primary path first, then falls back to legacy paths (.pi, .codex).
|
|
296
|
+
*/
|
|
297
|
+
export async function discoverAuthStorage(agentDir: string = getDefaultAgentDir()): Promise<AuthStorage> {
|
|
298
|
+
const dbPath = getAgentDbPath(agentDir);
|
|
299
|
+
logger.debug("discoverAuthStorage", { agentDir, dbPath });
|
|
300
|
+
|
|
301
|
+
const storage = await AuthStorage.create(dbPath, { configValueResolver: resolveConfigValue });
|
|
302
|
+
await storage.reload();
|
|
303
|
+
return storage;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Discover extensions from cwd.
|
|
308
|
+
*/
|
|
309
|
+
export async function discoverExtensions(cwd?: string): Promise<LoadExtensionsResult> {
|
|
310
|
+
const resolvedCwd = cwd ?? getProjectDir();
|
|
311
|
+
|
|
312
|
+
return discoverAndLoadExtensions([], resolvedCwd);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Discover skills from cwd and agentDir.
|
|
317
|
+
*/
|
|
318
|
+
export async function discoverSkills(
|
|
319
|
+
cwd?: string,
|
|
320
|
+
_agentDir?: string,
|
|
321
|
+
settings?: SkillsSettings,
|
|
322
|
+
): Promise<{ skills: Skill[]; warnings: SkillWarning[] }> {
|
|
323
|
+
return await loadSkillsInternal({
|
|
324
|
+
...settings,
|
|
325
|
+
cwd: cwd ?? getProjectDir(),
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Discover context files (XCSH.md) walking up from cwd.
|
|
331
|
+
* Returns files sorted by depth (farther from cwd first, so closer files appear last/more prominent).
|
|
332
|
+
*/
|
|
333
|
+
export async function discoverContextFiles(
|
|
334
|
+
cwd?: string,
|
|
335
|
+
_agentDir?: string,
|
|
336
|
+
): Promise<Array<{ path: string; content: string; depth?: number }>> {
|
|
337
|
+
return await loadContextFilesInternal({
|
|
338
|
+
cwd: cwd ?? getProjectDir(),
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Discover prompt templates from cwd and agentDir.
|
|
344
|
+
*/
|
|
345
|
+
export async function discoverPromptTemplates(cwd?: string, agentDir?: string): Promise<PromptTemplate[]> {
|
|
346
|
+
return await loadPromptTemplatesInternal({
|
|
347
|
+
cwd: cwd ?? getProjectDir(),
|
|
348
|
+
agentDir: agentDir ?? getDefaultAgentDir(),
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Discover file-based slash commands from commands/ directories.
|
|
354
|
+
*/
|
|
355
|
+
export async function discoverSlashCommands(cwd?: string): Promise<FileSlashCommand[]> {
|
|
356
|
+
return loadSlashCommandsInternal({ cwd: cwd ?? getProjectDir() });
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Discover custom commands (TypeScript slash commands) from cwd and agentDir.
|
|
361
|
+
*/
|
|
362
|
+
export async function discoverCustomTSCommands(cwd?: string, agentDir?: string): Promise<CustomCommandsLoadResult> {
|
|
363
|
+
const resolvedCwd = cwd ?? getProjectDir();
|
|
364
|
+
const resolvedAgentDir = agentDir ?? getDefaultAgentDir();
|
|
365
|
+
|
|
366
|
+
return loadCustomCommandsInternal({
|
|
367
|
+
cwd: resolvedCwd,
|
|
368
|
+
agentDir: resolvedAgentDir,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Discover MCP servers from .mcp.json files.
|
|
374
|
+
* Returns the manager and loaded tools.
|
|
375
|
+
*/
|
|
376
|
+
export async function discoverMCPServers(cwd?: string): Promise<MCPToolsLoadResult> {
|
|
377
|
+
const resolvedCwd = cwd ?? getProjectDir();
|
|
378
|
+
return discoverAndLoadMCPTools(resolvedCwd);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// API Key Helpers
|
|
382
|
+
|
|
383
|
+
// System Prompt
|
|
384
|
+
|
|
385
|
+
export interface BuildSystemPromptOptions {
|
|
386
|
+
tools?: Tool[];
|
|
387
|
+
skills?: Skill[];
|
|
388
|
+
contextFiles?: Array<{ path: string; content: string }>;
|
|
389
|
+
cwd?: string;
|
|
390
|
+
appendPrompt?: string;
|
|
391
|
+
repeatToolDescriptions?: boolean;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Build the default system prompt.
|
|
396
|
+
*/
|
|
397
|
+
export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}): Promise<string> {
|
|
398
|
+
return await buildSystemPromptInternal({
|
|
399
|
+
cwd: options.cwd,
|
|
400
|
+
skills: options.skills,
|
|
401
|
+
contextFiles: options.contextFiles,
|
|
402
|
+
appendSystemPrompt: options.appendPrompt,
|
|
403
|
+
repeatToolDescriptions: options.repeatToolDescriptions,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Internal Helpers
|
|
408
|
+
|
|
409
|
+
function createCustomToolContext(ctx: ExtensionContext): CustomToolContext {
|
|
410
|
+
return {
|
|
411
|
+
sessionManager: ctx.sessionManager,
|
|
412
|
+
modelRegistry: ctx.modelRegistry,
|
|
413
|
+
model: ctx.model,
|
|
414
|
+
isIdle: ctx.isIdle,
|
|
415
|
+
hasQueuedMessages: ctx.hasPendingMessages,
|
|
416
|
+
abort: ctx.abort,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function isCustomTool(tool: CustomTool | ToolDefinition): tool is CustomTool {
|
|
421
|
+
// To distinguish, we mark converted tools with a hidden symbol property.
|
|
422
|
+
// If the tool doesn't have this marker, it's a CustomTool that needs conversion.
|
|
423
|
+
return !(tool as any).__isToolDefinition;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const TOOL_DEFINITION_MARKER = Symbol("__isToolDefinition");
|
|
427
|
+
|
|
428
|
+
let sshCleanupRegistered = false;
|
|
429
|
+
|
|
430
|
+
async function cleanupSshResources(): Promise<void> {
|
|
431
|
+
const results = await Promise.allSettled([closeAllConnections(), unmountAll()]);
|
|
432
|
+
for (const result of results) {
|
|
433
|
+
if (result.status === "rejected") {
|
|
434
|
+
logger.warn("SSH cleanup failed", { error: String(result.reason) });
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function registerSshCleanup(): void {
|
|
440
|
+
if (sshCleanupRegistered) return;
|
|
441
|
+
sshCleanupRegistered = true;
|
|
442
|
+
postmortem.register("ssh-cleanup", cleanupSshResources);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
let pythonCleanupRegistered = false;
|
|
446
|
+
|
|
447
|
+
function registerPythonCleanup(): void {
|
|
448
|
+
if (pythonCleanupRegistered) return;
|
|
449
|
+
pythonCleanupRegistered = true;
|
|
450
|
+
postmortem.register("python-cleanup", disposeAllKernelSessions);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
function customToolToDefinition(tool: CustomTool): ToolDefinition {
|
|
454
|
+
const definition: ToolDefinition & { [TOOL_DEFINITION_MARKER]: true } = {
|
|
455
|
+
name: tool.name,
|
|
456
|
+
label: tool.label,
|
|
457
|
+
description: tool.description,
|
|
458
|
+
parameters: tool.parameters,
|
|
459
|
+
hidden: tool.hidden,
|
|
460
|
+
deferrable: tool.deferrable,
|
|
461
|
+
mcpServerName: tool.mcpServerName,
|
|
462
|
+
mcpToolName: tool.mcpToolName,
|
|
463
|
+
execute: (toolCallId, params, signal, onUpdate, ctx) =>
|
|
464
|
+
tool.execute(toolCallId, params, onUpdate, createCustomToolContext(ctx), signal),
|
|
465
|
+
onSession: tool.onSession ? (event, ctx) => tool.onSession?.(event, createCustomToolContext(ctx)) : undefined,
|
|
466
|
+
renderCall: tool.renderCall,
|
|
467
|
+
renderResult: tool.renderResult
|
|
468
|
+
? (result, options, theme): Component => {
|
|
469
|
+
const component = tool.renderResult?.(
|
|
470
|
+
result,
|
|
471
|
+
{ expanded: options.expanded, isPartial: options.isPartial, spinnerFrame: options.spinnerFrame },
|
|
472
|
+
theme,
|
|
473
|
+
);
|
|
474
|
+
// Return empty component if undefined to match Component type requirement
|
|
475
|
+
return component ?? ({ render: () => [] } as unknown as Component);
|
|
476
|
+
}
|
|
477
|
+
: undefined,
|
|
478
|
+
[TOOL_DEFINITION_MARKER]: true,
|
|
479
|
+
};
|
|
480
|
+
return definition;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
|
|
484
|
+
return api => {
|
|
485
|
+
for (const tool of tools) {
|
|
486
|
+
api.registerTool(customToolToDefinition(tool));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const runOnSession = async (event: CustomToolSessionEvent, ctx: ExtensionContext) => {
|
|
490
|
+
for (const tool of tools) {
|
|
491
|
+
if (!tool.onSession) continue;
|
|
492
|
+
try {
|
|
493
|
+
await tool.onSession(event, createCustomToolContext(ctx));
|
|
494
|
+
} catch (err) {
|
|
495
|
+
logger.warn("Custom tool onSession error", { tool: tool.name, error: String(err) });
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
api.on("session_start", async (_event, ctx) =>
|
|
501
|
+
runOnSession({ reason: "start", previousSessionFile: undefined }, ctx),
|
|
502
|
+
);
|
|
503
|
+
api.on("session_switch", async (event, ctx) =>
|
|
504
|
+
runOnSession({ reason: "switch", previousSessionFile: event.previousSessionFile }, ctx),
|
|
505
|
+
);
|
|
506
|
+
api.on("session_branch", async (event, ctx) =>
|
|
507
|
+
runOnSession({ reason: "branch", previousSessionFile: event.previousSessionFile }, ctx),
|
|
508
|
+
);
|
|
509
|
+
api.on("session_tree", async (_event, ctx) =>
|
|
510
|
+
runOnSession({ reason: "tree", previousSessionFile: undefined }, ctx),
|
|
511
|
+
);
|
|
512
|
+
api.on("session_shutdown", async (_event, ctx) =>
|
|
513
|
+
runOnSession({ reason: "shutdown", previousSessionFile: undefined }, ctx),
|
|
514
|
+
);
|
|
515
|
+
api.on("auto_compaction_start", async (event, ctx) =>
|
|
516
|
+
runOnSession({ reason: "auto_compaction_start", trigger: event.reason, action: event.action }, ctx),
|
|
517
|
+
);
|
|
518
|
+
api.on("auto_compaction_end", async (event, ctx) =>
|
|
519
|
+
runOnSession(
|
|
520
|
+
{
|
|
521
|
+
reason: "auto_compaction_end",
|
|
522
|
+
action: event.action,
|
|
523
|
+
result: event.result,
|
|
524
|
+
aborted: event.aborted,
|
|
525
|
+
willRetry: event.willRetry,
|
|
526
|
+
errorMessage: event.errorMessage,
|
|
527
|
+
},
|
|
528
|
+
ctx,
|
|
529
|
+
),
|
|
530
|
+
);
|
|
531
|
+
api.on("auto_retry_start", async (event, ctx) =>
|
|
532
|
+
runOnSession(
|
|
533
|
+
{
|
|
534
|
+
reason: "auto_retry_start",
|
|
535
|
+
attempt: event.attempt,
|
|
536
|
+
maxAttempts: event.maxAttempts,
|
|
537
|
+
delayMs: event.delayMs,
|
|
538
|
+
errorMessage: event.errorMessage,
|
|
539
|
+
},
|
|
540
|
+
ctx,
|
|
541
|
+
),
|
|
542
|
+
);
|
|
543
|
+
api.on("auto_retry_end", async (event, ctx) =>
|
|
544
|
+
runOnSession(
|
|
545
|
+
{
|
|
546
|
+
reason: "auto_retry_end",
|
|
547
|
+
success: event.success,
|
|
548
|
+
attempt: event.attempt,
|
|
549
|
+
finalError: event.finalError,
|
|
550
|
+
},
|
|
551
|
+
ctx,
|
|
552
|
+
),
|
|
553
|
+
);
|
|
554
|
+
api.on("ttsr_triggered", async (event, ctx) =>
|
|
555
|
+
runOnSession({ reason: "ttsr_triggered", rules: event.rules }, ctx),
|
|
556
|
+
);
|
|
557
|
+
api.on("todo_reminder", async (event, ctx) =>
|
|
558
|
+
runOnSession(
|
|
559
|
+
{
|
|
560
|
+
reason: "todo_reminder",
|
|
561
|
+
todos: event.todos,
|
|
562
|
+
attempt: event.attempt,
|
|
563
|
+
maxAttempts: event.maxAttempts,
|
|
564
|
+
},
|
|
565
|
+
ctx,
|
|
566
|
+
),
|
|
567
|
+
);
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// Factory
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Build LoadedCustomCommand entries for all MCP prompts across connected servers.
|
|
575
|
+
* These are re-created whenever prompts change (setOnPromptsChanged callback).
|
|
576
|
+
*/
|
|
577
|
+
function buildMCPPromptCommands(manager: MCPManager): LoadedCustomCommand[] {
|
|
578
|
+
const commands: LoadedCustomCommand[] = [];
|
|
579
|
+
for (const serverName of manager.getConnectedServers()) {
|
|
580
|
+
const prompts = manager.getServerPrompts(serverName);
|
|
581
|
+
if (!prompts?.length) continue;
|
|
582
|
+
for (const prompt of prompts) {
|
|
583
|
+
const commandName = `${serverName}:${prompt.name}`;
|
|
584
|
+
commands.push({
|
|
585
|
+
path: `mcp:${commandName}`,
|
|
586
|
+
resolvedPath: `mcp:${commandName}`,
|
|
587
|
+
source: "bundled",
|
|
588
|
+
command: {
|
|
589
|
+
name: commandName,
|
|
590
|
+
description: prompt.description ?? `MCP prompt from ${serverName}`,
|
|
591
|
+
async execute(args: string[]) {
|
|
592
|
+
const promptArgs: Record<string, string> = {};
|
|
593
|
+
for (const arg of args) {
|
|
594
|
+
const eqIdx = arg.indexOf("=");
|
|
595
|
+
if (eqIdx > 0) {
|
|
596
|
+
promptArgs[arg.slice(0, eqIdx)] = arg.slice(eqIdx + 1);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
const result = await manager.executePrompt(serverName, prompt.name, promptArgs);
|
|
600
|
+
if (!result) return "";
|
|
601
|
+
const parts: string[] = [];
|
|
602
|
+
for (const msg of result.messages) {
|
|
603
|
+
const contentItems = Array.isArray(msg.content) ? msg.content : [msg.content];
|
|
604
|
+
for (const item of contentItems) {
|
|
605
|
+
if (item.type === "text") {
|
|
606
|
+
parts.push(item.text);
|
|
607
|
+
} else if (item.type === "resource") {
|
|
608
|
+
const resource = item.resource;
|
|
609
|
+
if (resource.text) parts.push(resource.text);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return parts.join("\n\n");
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return commands;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Create an AgentSession with the specified options.
|
|
623
|
+
*
|
|
624
|
+
* @example
|
|
625
|
+
* ```typescript
|
|
626
|
+
* // Minimal - uses defaults
|
|
627
|
+
* const { session } = await createAgentSession();
|
|
628
|
+
*
|
|
629
|
+
* // With explicit model
|
|
630
|
+
* import { getModel } from '@f5xc-salesdemos/pi-ai';
|
|
631
|
+
* const { session } = await createAgentSession({
|
|
632
|
+
* model: getModel('anthropic', 'claude-opus-4-5'),
|
|
633
|
+
* thinkingLevel: 'high',
|
|
634
|
+
* });
|
|
635
|
+
*
|
|
636
|
+
* // Continue previous session
|
|
637
|
+
* const { session, modelFallbackMessage } = await createAgentSession({
|
|
638
|
+
* continueSession: true,
|
|
639
|
+
* });
|
|
640
|
+
*
|
|
641
|
+
* // Full control
|
|
642
|
+
* const { session } = await createAgentSession({
|
|
643
|
+
* model: myModel,
|
|
644
|
+
* getApiKey: async () => Bun.env.MY_KEY,
|
|
645
|
+
* systemPrompt: 'You are helpful.',
|
|
646
|
+
* tools: codingTools({ cwd: getProjectDir() }),
|
|
647
|
+
* skills: [],
|
|
648
|
+
* sessionManager: SessionManager.inMemory(),
|
|
649
|
+
* });
|
|
650
|
+
* ```
|
|
651
|
+
*/
|
|
652
|
+
export async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {
|
|
653
|
+
const cwd = options.cwd ?? getProjectDir();
|
|
654
|
+
const agentDir = options.agentDir ?? getDefaultAgentDir();
|
|
655
|
+
const eventBus = options.eventBus ?? new EventBus();
|
|
656
|
+
|
|
657
|
+
registerSshCleanup();
|
|
658
|
+
registerPythonCleanup();
|
|
659
|
+
|
|
660
|
+
// Use provided or create AuthStorage and ModelRegistry
|
|
661
|
+
const authStorage = options.authStorage ?? (await logger.time("discoverModels", discoverAuthStorage, agentDir));
|
|
662
|
+
const modelRegistry = options.modelRegistry ?? new ModelRegistry(authStorage);
|
|
663
|
+
|
|
664
|
+
const settings = options.settings ?? (await logger.time("settings", Settings.init, { cwd, agentDir }));
|
|
665
|
+
logger.time("initializeWithSettings");
|
|
666
|
+
initializeWithSettings(settings);
|
|
667
|
+
if (!options.modelRegistry) {
|
|
668
|
+
modelRegistry.refreshInBackground();
|
|
669
|
+
}
|
|
670
|
+
const skillsSettings = settings.getGroup("skills");
|
|
671
|
+
const disabledExtensionIds = settings.get("disabledExtensions") ?? [];
|
|
672
|
+
const discoveredSkillsPromise =
|
|
673
|
+
options.skills === undefined
|
|
674
|
+
? discoverSkills(cwd, agentDir, { ...skillsSettings, disabledExtensions: disabledExtensionIds })
|
|
675
|
+
: undefined;
|
|
676
|
+
|
|
677
|
+
// Initialize provider preferences from settings
|
|
678
|
+
const webSearchProvider = settings.get("providers.webSearch");
|
|
679
|
+
if (typeof webSearchProvider === "string" && isSearchProviderPreference(webSearchProvider)) {
|
|
680
|
+
setPreferredSearchProvider(webSearchProvider);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const imageProvider = settings.get("providers.image");
|
|
684
|
+
if (imageProvider === "auto" || imageProvider === "gemini" || imageProvider === "openrouter") {
|
|
685
|
+
setPreferredImageProvider(imageProvider);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
const sessionManager =
|
|
689
|
+
options.sessionManager ??
|
|
690
|
+
logger.time("sessionManager", () =>
|
|
691
|
+
SessionManager.create(cwd, SessionManager.getDefaultSessionDir(cwd, agentDir)),
|
|
692
|
+
);
|
|
693
|
+
const providerSessionId = options.providerSessionId ?? sessionManager.getSessionId();
|
|
694
|
+
const modelApiKeyAvailability = new Map<string, boolean>();
|
|
695
|
+
const getModelAvailabilityKey = (candidate: Model): string =>
|
|
696
|
+
`${candidate.provider}\u0000${candidate.baseUrl ?? ""}`;
|
|
697
|
+
const hasModelApiKey = async (candidate: Model): Promise<boolean> => {
|
|
698
|
+
const availabilityKey = getModelAvailabilityKey(candidate);
|
|
699
|
+
const cached = modelApiKeyAvailability.get(availabilityKey);
|
|
700
|
+
if (cached !== undefined) {
|
|
701
|
+
return cached;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
const hasKey = !!(await modelRegistry.getApiKey(candidate, providerSessionId));
|
|
705
|
+
modelApiKeyAvailability.set(availabilityKey, hasKey);
|
|
706
|
+
return hasKey;
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
// Load and create secret obfuscator early so resumed session state and prompt warnings
|
|
710
|
+
// reflect actual loaded secrets, not just the setting toggle.
|
|
711
|
+
// Env-based secrets are always collected (hardcoded masking — no opt-in required).
|
|
712
|
+
// File-based secrets (secrets.yml) remain opt-in behind the secrets.enabled setting.
|
|
713
|
+
let obfuscator: SecretObfuscator | undefined;
|
|
714
|
+
{
|
|
715
|
+
// Collect context-sensitive values (context loads before session in main.ts).
|
|
716
|
+
let contextSensitiveValues: string[] | undefined;
|
|
717
|
+
try {
|
|
718
|
+
const { ContextService } = await import("./services/xcsh-context");
|
|
719
|
+
contextSensitiveValues = ContextService.getSensitiveContextValues();
|
|
720
|
+
} catch {
|
|
721
|
+
// ContextService not initialized — skip (SDK consumers, tests, etc.)
|
|
722
|
+
}
|
|
723
|
+
// Scan both process.env AND bash.environment (context-injected values)
|
|
724
|
+
// for env vars matching sensitive name patterns.
|
|
725
|
+
const bashEnv = (settings.get("bash.environment") ?? {}) as Record<string, string>;
|
|
726
|
+
const envEntries = collectEnvSecrets({
|
|
727
|
+
additionalEnv: bashEnv,
|
|
728
|
+
additionalValues: contextSensitiveValues,
|
|
729
|
+
});
|
|
730
|
+
let fileEntries: SecretEntry[] = [];
|
|
731
|
+
if (settings.get("secrets.enabled")) {
|
|
732
|
+
fileEntries = await logger.time("loadSecrets", loadSecrets, cwd, agentDir);
|
|
733
|
+
}
|
|
734
|
+
// File entries MUST come first to preserve placeholder index stability
|
|
735
|
+
// for resumed sessions that persisted #HASH# tokens from secrets.yml.
|
|
736
|
+
const allEntries = [...fileEntries, ...envEntries];
|
|
737
|
+
if (allEntries.length > 0) {
|
|
738
|
+
obfuscator = new SecretObfuscator(allEntries);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
const secretsEnabled = obfuscator?.hasSecrets() === true;
|
|
742
|
+
|
|
743
|
+
// Capture ContextService reference for sync consumers (e.g., InternalDocsProtocolHandler's
|
|
744
|
+
// getContextStatus getter below). Null when ContextService isn't available (SDK consumers, tests).
|
|
745
|
+
let contextServiceRef: typeof import("./services/xcsh-context").ContextService | null = null;
|
|
746
|
+
let knowledgeServiceRef: typeof import("./services/xcsh-knowledge").KnowledgeService | null = null;
|
|
747
|
+
|
|
748
|
+
// Capture ContextService reference for sync consumers (rebuildSystemPrompt context resolution,
|
|
749
|
+
// InternalDocsProtocolHandler getContextStatus). The context-change listener itself is
|
|
750
|
+
// registered later, atomically with its addDisposeHook cleanup, AFTER session construction
|
|
751
|
+
// succeeds — registering it here would leak on createAgentSession failures.
|
|
752
|
+
try {
|
|
753
|
+
const { ContextService } = await import("./services/xcsh-context");
|
|
754
|
+
contextServiceRef = ContextService;
|
|
755
|
+
} catch {
|
|
756
|
+
// ContextService not available (SDK consumers, tests). Skip.
|
|
757
|
+
}
|
|
758
|
+
try {
|
|
759
|
+
const { KnowledgeService } = await import("./services/xcsh-knowledge");
|
|
760
|
+
const { getXCSHConfigDir } = await import("@f5xc-salesdemos/pi-utils");
|
|
761
|
+
knowledgeServiceRef = KnowledgeService;
|
|
762
|
+
if (!KnowledgeService._hasInstance()) {
|
|
763
|
+
KnowledgeService.init(getXCSHConfigDir());
|
|
764
|
+
KnowledgeService.instance.loadCache();
|
|
765
|
+
}
|
|
766
|
+
} catch {
|
|
767
|
+
// KnowledgeService not available — skip.
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Check if session has existing data to restore
|
|
771
|
+
const existingSession = logger.time("loadSessionContext", () =>
|
|
772
|
+
deobfuscateSessionContext(sessionManager.buildSessionContext(), obfuscator),
|
|
773
|
+
);
|
|
774
|
+
const existingBranch = logger.time("getSessionBranch", () => sessionManager.getBranch());
|
|
775
|
+
const hasExistingSession = existingBranch.length > 0;
|
|
776
|
+
const hasThinkingEntry = existingBranch.some(entry => entry.type === "thinking_level_change");
|
|
777
|
+
const hasServiceTierEntry = existingBranch.some(entry => entry.type === "service_tier_change");
|
|
778
|
+
|
|
779
|
+
const hasExplicitModel = options.model !== undefined || options.modelPattern !== undefined;
|
|
780
|
+
const modelMatchPreferences = {
|
|
781
|
+
usageOrder: settings.getStorage()?.getModelUsageOrder(),
|
|
782
|
+
};
|
|
783
|
+
// When LiteLLM is configured and no model cache exists yet (first run),
|
|
784
|
+
// await the background refresh so model discovery from the proxy completes
|
|
785
|
+
// before we select a default model. Bounded by the 3s probe timeout.
|
|
786
|
+
if (!options.modelRegistry && hasLiteLLMEnv() && modelRegistry.hasUncachedDiscoverableProviders()) {
|
|
787
|
+
await logger.time("awaitLiteLLMDiscovery", () => modelRegistry.awaitBackgroundRefresh());
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const defaultRoleSpec = logger.time("resolveDefaultModelRole", () =>
|
|
791
|
+
resolveModelRoleValue(settings.getModelRole("default"), modelRegistry.getAvailable(), {
|
|
792
|
+
settings,
|
|
793
|
+
matchPreferences: modelMatchPreferences,
|
|
794
|
+
modelRegistry,
|
|
795
|
+
}),
|
|
796
|
+
);
|
|
797
|
+
let model = options.model;
|
|
798
|
+
let modelFallbackMessage: string | undefined;
|
|
799
|
+
// If session has data, try to restore model from it.
|
|
800
|
+
// Skip restore when an explicit model was requested.
|
|
801
|
+
const defaultModelStr = existingSession.models.default;
|
|
802
|
+
if (!hasExplicitModel && !model && hasExistingSession && defaultModelStr) {
|
|
803
|
+
await logger.time("restoreSessionModel", async () => {
|
|
804
|
+
const parsedModel = parseModelString(defaultModelStr);
|
|
805
|
+
if (parsedModel) {
|
|
806
|
+
const restoredModel = modelRegistry.find(parsedModel.provider, parsedModel.id);
|
|
807
|
+
if (restoredModel && (await hasModelApiKey(restoredModel))) {
|
|
808
|
+
model = restoredModel;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (!model) {
|
|
812
|
+
modelFallbackMessage = `Could not restore model ${defaultModelStr}`;
|
|
813
|
+
}
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// If still no model, try settings default.
|
|
818
|
+
// Skip settings fallback when an explicit model was requested.
|
|
819
|
+
if (!hasExplicitModel && !model && defaultRoleSpec.model) {
|
|
820
|
+
const settingsDefaultModel = defaultRoleSpec.model;
|
|
821
|
+
logger.time("resolveSettingsDefaultModel", () => {
|
|
822
|
+
// defaultRoleSpec.model already comes from modelRegistry.getAvailable(),
|
|
823
|
+
// so re-validating auth here just repeats the expensive lookup path.
|
|
824
|
+
model = settingsDefaultModel;
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
const taskDepth = options.taskDepth ?? 0;
|
|
829
|
+
|
|
830
|
+
let thinkingLevel = options.thinkingLevel;
|
|
831
|
+
|
|
832
|
+
// If session has data and includes a thinking entry, restore it
|
|
833
|
+
if (thinkingLevel === undefined && hasExistingSession && hasThinkingEntry) {
|
|
834
|
+
thinkingLevel = parseThinkingLevel(existingSession.thinkingLevel);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (thinkingLevel === undefined && !hasExplicitModel && !hasThinkingEntry && defaultRoleSpec.explicitThinkingLevel) {
|
|
838
|
+
thinkingLevel = defaultRoleSpec.thinkingLevel;
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// Fall back to settings default
|
|
842
|
+
if (thinkingLevel === undefined) {
|
|
843
|
+
thinkingLevel = settings.get("defaultThinkingLevel");
|
|
844
|
+
}
|
|
845
|
+
if (model) {
|
|
846
|
+
const resolvedModel = model;
|
|
847
|
+
thinkingLevel = logger.time("resolveThinkingLevelForModel", () =>
|
|
848
|
+
resolveThinkingLevelForModel(resolvedModel, thinkingLevel),
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
let skills: Skill[];
|
|
853
|
+
let skillWarnings: SkillWarning[];
|
|
854
|
+
if (options.skills !== undefined) {
|
|
855
|
+
skills = options.skills;
|
|
856
|
+
skillWarnings = [];
|
|
857
|
+
} else {
|
|
858
|
+
const discovered = await logger.time(
|
|
859
|
+
"discoverSkills",
|
|
860
|
+
() => discoveredSkillsPromise ?? Promise.resolve({ skills: [], warnings: [] }),
|
|
861
|
+
);
|
|
862
|
+
skills = discovered.skills;
|
|
863
|
+
skillWarnings = discovered.warnings;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
// Discover rules and bucket them in one pass to avoid repeated scans over large rule sets.
|
|
867
|
+
const { ttsrManager, rulebookRules, alwaysApplyRules } = await logger.time("discoverTtsrRules", async () => {
|
|
868
|
+
const ttsrSettings = settings.getGroup("ttsr");
|
|
869
|
+
const ttsrManager = new TtsrManager(ttsrSettings);
|
|
870
|
+
const rulesResult =
|
|
871
|
+
options.rules !== undefined
|
|
872
|
+
? { items: options.rules, warnings: undefined }
|
|
873
|
+
: await loadCapability<Rule>(ruleCapability.id, { cwd });
|
|
874
|
+
const rulebookRules: Rule[] = [];
|
|
875
|
+
const alwaysApplyRules: Rule[] = [];
|
|
876
|
+
for (const rule of rulesResult.items) {
|
|
877
|
+
const isTtsrRule = rule.condition && rule.condition.length > 0 ? ttsrManager.addRule(rule) : false;
|
|
878
|
+
if (isTtsrRule) {
|
|
879
|
+
continue;
|
|
880
|
+
}
|
|
881
|
+
if (rule.alwaysApply === true) {
|
|
882
|
+
alwaysApplyRules.push(rule);
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
if (rule.description) {
|
|
886
|
+
rulebookRules.push(rule);
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
if (existingSession.injectedTtsrRules.length > 0) {
|
|
890
|
+
ttsrManager.restoreInjected(existingSession.injectedTtsrRules);
|
|
891
|
+
}
|
|
892
|
+
return { ttsrManager, rulebookRules, alwaysApplyRules };
|
|
893
|
+
});
|
|
894
|
+
|
|
895
|
+
const contextFiles = await logger.time(
|
|
896
|
+
"discoverContextFiles",
|
|
897
|
+
async () => options.contextFiles ?? (await discoverContextFiles(cwd, agentDir)),
|
|
898
|
+
);
|
|
899
|
+
|
|
900
|
+
let agent: Agent;
|
|
901
|
+
let session!: AgentSession;
|
|
902
|
+
let hasSession = false;
|
|
903
|
+
// LSP is opt-in: a language server initialized against a very large workspace
|
|
904
|
+
// (e.g. terraform-ls on a multi-repo tree) can stream unbounded output that is
|
|
905
|
+
// buffered without limit and exhausts process memory (OOM). `lsp.enabled` is
|
|
906
|
+
// the master switch — it gates the startup warmup and write/edit diagnostics.
|
|
907
|
+
const enableLsp = options.enableLsp ?? settings.get("lsp.enabled") ?? false;
|
|
908
|
+
const backgroundJobsEnabled = isBackgroundJobSupportEnabled(settings);
|
|
909
|
+
const asyncMaxJobs = Math.min(100, Math.max(1, settings.get("async.maxJobs") ?? 100));
|
|
910
|
+
const ASYNC_INLINE_RESULT_MAX_CHARS = 12_000;
|
|
911
|
+
const ASYNC_PREVIEW_MAX_CHARS = 4_000;
|
|
912
|
+
const formatAsyncResultForFollowUp = async (result: string): Promise<string> => {
|
|
913
|
+
if (result.length <= ASYNC_INLINE_RESULT_MAX_CHARS) {
|
|
914
|
+
return result;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const preview = `${result.slice(0, ASYNC_PREVIEW_MAX_CHARS)}\n\n[Output truncated. Showing first ${ASYNC_PREVIEW_MAX_CHARS.toLocaleString()} characters.]`;
|
|
918
|
+
try {
|
|
919
|
+
const { path: artifactPath, id: artifactId } = await sessionManager.allocateArtifactPath("async");
|
|
920
|
+
if (artifactPath && artifactId) {
|
|
921
|
+
await Bun.write(artifactPath, result);
|
|
922
|
+
return `${preview}\nFull output: artifact://${artifactId}`;
|
|
923
|
+
}
|
|
924
|
+
} catch (error) {
|
|
925
|
+
logger.warn("Failed to persist async follow-up artifact", {
|
|
926
|
+
error: error instanceof Error ? error.message : String(error),
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
return preview;
|
|
931
|
+
};
|
|
932
|
+
const asyncJobManager = backgroundJobsEnabled
|
|
933
|
+
? new AsyncJobManager({
|
|
934
|
+
maxRunningJobs: asyncMaxJobs,
|
|
935
|
+
onJobComplete: async (jobId, result, job) => {
|
|
936
|
+
if (!session || asyncJobManager!.isDeliverySuppressed(jobId)) return;
|
|
937
|
+
const formattedResult = await formatAsyncResultForFollowUp(result);
|
|
938
|
+
if (asyncJobManager!.isDeliverySuppressed(jobId)) return;
|
|
939
|
+
|
|
940
|
+
const message = prompt.render(asyncResultTemplate, { jobId, result: formattedResult });
|
|
941
|
+
const durationMs = job ? Math.max(0, Date.now() - job.startTime) : undefined;
|
|
942
|
+
await session.sendCustomMessage(
|
|
943
|
+
{
|
|
944
|
+
customType: "async-result",
|
|
945
|
+
content: message,
|
|
946
|
+
display: true,
|
|
947
|
+
attribution: "agent",
|
|
948
|
+
details: {
|
|
949
|
+
jobId,
|
|
950
|
+
type: job?.type,
|
|
951
|
+
label: job?.label,
|
|
952
|
+
durationMs,
|
|
953
|
+
},
|
|
954
|
+
},
|
|
955
|
+
{ deliverAs: "followUp", triggerTurn: true },
|
|
956
|
+
);
|
|
957
|
+
},
|
|
958
|
+
})
|
|
959
|
+
: undefined;
|
|
960
|
+
|
|
961
|
+
const pythonKernelOwnerId = `agent-session:${Snowflake.next()}`;
|
|
962
|
+
|
|
963
|
+
try {
|
|
964
|
+
const getActiveModelString = (): string | undefined => {
|
|
965
|
+
const activeModel = agent?.state.model;
|
|
966
|
+
if (activeModel) return formatModelString(activeModel);
|
|
967
|
+
if (model) return formatModelString(model);
|
|
968
|
+
return undefined;
|
|
969
|
+
};
|
|
970
|
+
const toolSession: ToolSession = {
|
|
971
|
+
cwd,
|
|
972
|
+
hasUI: options.hasUI ?? false,
|
|
973
|
+
enableLsp,
|
|
974
|
+
get hasEditTool() {
|
|
975
|
+
const requestedToolNames = options.toolNames
|
|
976
|
+
? [...new Set(options.toolNames.map(name => name.toLowerCase()))]
|
|
977
|
+
: undefined;
|
|
978
|
+
return !requestedToolNames || requestedToolNames.includes("edit");
|
|
979
|
+
},
|
|
980
|
+
skipPythonPreflight: options.skipPythonPreflight,
|
|
981
|
+
forcePythonWarmup: options.forcePythonWarmup,
|
|
982
|
+
contextFiles,
|
|
983
|
+
skills,
|
|
984
|
+
eventBus,
|
|
985
|
+
outputSchema: options.outputSchema,
|
|
986
|
+
requireSubmitResultTool: options.requireSubmitResultTool,
|
|
987
|
+
taskDepth: options.taskDepth ?? 0,
|
|
988
|
+
getSessionFile: () => sessionManager.getSessionFile() ?? null,
|
|
989
|
+
getPythonKernelOwnerId: () => pythonKernelOwnerId,
|
|
990
|
+
assertPythonExecutionAllowed: () => session?.assertPythonExecutionAllowed(),
|
|
991
|
+
trackPythonExecution: (execution, abortController) =>
|
|
992
|
+
session ? session.trackPythonExecution(execution, abortController) : execution,
|
|
993
|
+
getSessionId: () => sessionManager.getSessionId?.() ?? null,
|
|
994
|
+
getSessionSpawns: () => options.spawns ?? "*",
|
|
995
|
+
getModelString: () => (hasExplicitModel && model ? formatModelString(model) : undefined),
|
|
996
|
+
getActiveModelString,
|
|
997
|
+
getPlanModeState: () => session.getPlanModeState(),
|
|
998
|
+
getCompactContext: () => session.formatCompactContext(),
|
|
999
|
+
getTodoPhases: () => session.getTodoPhases(),
|
|
1000
|
+
setTodoPhases: phases => session.setTodoPhases(phases),
|
|
1001
|
+
isMCPDiscoveryEnabled: () => session.isMCPDiscoveryEnabled(),
|
|
1002
|
+
getDiscoverableMCPTools: () => session.getDiscoverableMCPTools(),
|
|
1003
|
+
getDiscoverableMCPSearchIndex: () => session.getDiscoverableMCPSearchIndex(),
|
|
1004
|
+
getSelectedMCPToolNames: () => session.getSelectedMCPToolNames(),
|
|
1005
|
+
activateDiscoveredMCPTools: toolNames => session.activateDiscoveredMCPTools(toolNames),
|
|
1006
|
+
getCheckpointState: () => session.getCheckpointState(),
|
|
1007
|
+
setCheckpointState: state => session.setCheckpointState(state ?? undefined),
|
|
1008
|
+
getToolChoiceQueue: () => session.toolChoiceQueue,
|
|
1009
|
+
buildToolChoice: name => {
|
|
1010
|
+
const m = session.model;
|
|
1011
|
+
return m ? buildNamedToolChoice(name, m) : undefined;
|
|
1012
|
+
},
|
|
1013
|
+
steer: msg =>
|
|
1014
|
+
session.agent.steer({
|
|
1015
|
+
role: "custom",
|
|
1016
|
+
customType: msg.customType,
|
|
1017
|
+
content: msg.content,
|
|
1018
|
+
display: false,
|
|
1019
|
+
details: msg.details,
|
|
1020
|
+
attribution: "agent",
|
|
1021
|
+
timestamp: Date.now(),
|
|
1022
|
+
}),
|
|
1023
|
+
peekQueueInvoker: () => session.peekQueueInvoker(),
|
|
1024
|
+
allocateOutputArtifact: async toolType => {
|
|
1025
|
+
try {
|
|
1026
|
+
return await sessionManager.allocateArtifactPath(toolType);
|
|
1027
|
+
} catch {
|
|
1028
|
+
return {};
|
|
1029
|
+
}
|
|
1030
|
+
},
|
|
1031
|
+
settings,
|
|
1032
|
+
authStorage,
|
|
1033
|
+
modelRegistry,
|
|
1034
|
+
asyncJobManager,
|
|
1035
|
+
};
|
|
1036
|
+
|
|
1037
|
+
// Initialize internal URL router for internal protocols (agent://, artifact://, memory://, skill://, rule://, mcp://, local://)
|
|
1038
|
+
const internalRouter = new InternalUrlRouter();
|
|
1039
|
+
const getArtifactsDir = () => sessionManager.getArtifactsDir();
|
|
1040
|
+
internalRouter.register(new AgentProtocolHandler({ getArtifactsDir }));
|
|
1041
|
+
internalRouter.register(new ArtifactProtocolHandler({ getArtifactsDir }));
|
|
1042
|
+
internalRouter.register(
|
|
1043
|
+
new MemoryProtocolHandler({
|
|
1044
|
+
getMemoryRoot: () => getMemoryRoot(agentDir, settings.getCwd()),
|
|
1045
|
+
}),
|
|
1046
|
+
);
|
|
1047
|
+
internalRouter.register(
|
|
1048
|
+
new LocalProtocolHandler({
|
|
1049
|
+
getArtifactsDir,
|
|
1050
|
+
getSessionId: () => sessionManager.getSessionId(),
|
|
1051
|
+
}),
|
|
1052
|
+
);
|
|
1053
|
+
internalRouter.register(
|
|
1054
|
+
new SkillProtocolHandler({
|
|
1055
|
+
getSkills: () => skills,
|
|
1056
|
+
}),
|
|
1057
|
+
);
|
|
1058
|
+
internalRouter.register(
|
|
1059
|
+
new RuleProtocolHandler({
|
|
1060
|
+
getRules: () => [...rulebookRules, ...alwaysApplyRules],
|
|
1061
|
+
}),
|
|
1062
|
+
);
|
|
1063
|
+
internalRouter.register(
|
|
1064
|
+
new InternalDocsProtocolHandler({
|
|
1065
|
+
getContextStatus: () => {
|
|
1066
|
+
try {
|
|
1067
|
+
return contextServiceRef?.instance?.getStatus() ?? null;
|
|
1068
|
+
} catch {
|
|
1069
|
+
// ContextService.instance throws if not initialized; ignore.
|
|
1070
|
+
return null;
|
|
1071
|
+
}
|
|
1072
|
+
},
|
|
1073
|
+
}),
|
|
1074
|
+
);
|
|
1075
|
+
internalRouter.register(new JobsProtocolHandler({ getAsyncJobManager: () => asyncJobManager }));
|
|
1076
|
+
internalRouter.register(new McpProtocolHandler({ getMcpManager: () => mcpManager }));
|
|
1077
|
+
toolSession.internalRouter = internalRouter;
|
|
1078
|
+
toolSession.getArtifactsDir = getArtifactsDir;
|
|
1079
|
+
toolSession.agentOutputManager = new AgentOutputManager(
|
|
1080
|
+
getArtifactsDir,
|
|
1081
|
+
options.parentTaskPrefix ? { parentPrefix: options.parentTaskPrefix } : undefined,
|
|
1082
|
+
);
|
|
1083
|
+
|
|
1084
|
+
// Create built-in tools (already wrapped with meta notice formatting)
|
|
1085
|
+
const builtinTools = await logger.time("createAllTools", createTools, toolSession, options.toolNames);
|
|
1086
|
+
|
|
1087
|
+
// Discover MCP tools from .mcp.json files
|
|
1088
|
+
let mcpManager: MCPManager | undefined;
|
|
1089
|
+
const enableMCP = options.enableMCP ?? true;
|
|
1090
|
+
const customTools: CustomTool[] = [];
|
|
1091
|
+
if (enableMCP) {
|
|
1092
|
+
const mcpResult = await logger.time("discoverAndLoadMCPTools", discoverAndLoadMCPTools, cwd, {
|
|
1093
|
+
onConnecting: serverNames => {
|
|
1094
|
+
if (serverNames.length > 0) {
|
|
1095
|
+
logger.debug("Connecting to MCP servers", { servers: serverNames });
|
|
1096
|
+
}
|
|
1097
|
+
},
|
|
1098
|
+
enableProjectConfig: settings.get("mcp.enableProjectConfig") ?? true,
|
|
1099
|
+
// Always filter Exa - we have native integration
|
|
1100
|
+
filterExa: true,
|
|
1101
|
+
// Filter browser MCP servers when builtin browser tool is active
|
|
1102
|
+
filterBrowser: settings.get("browser.enabled") ?? false,
|
|
1103
|
+
cacheStorage: settings.getStorage(),
|
|
1104
|
+
authStorage,
|
|
1105
|
+
});
|
|
1106
|
+
mcpManager = mcpResult.manager;
|
|
1107
|
+
toolSession.mcpManager = mcpManager;
|
|
1108
|
+
|
|
1109
|
+
if (settings.get("mcp.notifications")) {
|
|
1110
|
+
mcpManager.setNotificationsEnabled(true);
|
|
1111
|
+
}
|
|
1112
|
+
// If we extracted Exa API keys from MCP configs and EXA_API_KEY isn't set, use the first one
|
|
1113
|
+
if (mcpResult.exaApiKeys.length > 0 && !$env.EXA_API_KEY) {
|
|
1114
|
+
Bun.env.EXA_API_KEY = mcpResult.exaApiKeys[0];
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// Log MCP errors
|
|
1118
|
+
for (const { path, error } of mcpResult.errors) {
|
|
1119
|
+
logger.error("MCP tool load failed", { path, error });
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
if (mcpResult.tools.length > 0) {
|
|
1123
|
+
// MCP tools are LoadedCustomTool, extract the tool property
|
|
1124
|
+
customTools.push(...mcpResult.tools.map(loaded => loaded.tool));
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
// Add Gemini image tools if GEMINI_API_KEY (or GOOGLE_API_KEY) is available
|
|
1129
|
+
const geminiImageTools = await logger.time("getGeminiImageTools", getGeminiImageTools);
|
|
1130
|
+
if (geminiImageTools.length > 0) {
|
|
1131
|
+
customTools.push(...(geminiImageTools as unknown as CustomTool[]));
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
// Add web search tools
|
|
1135
|
+
if (options.toolNames?.includes("web_search")) {
|
|
1136
|
+
customTools.push(...getSearchTools());
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// Discover and load custom tools from .omp/tools/, .xcsh/tools/, etc.
|
|
1140
|
+
const builtInToolNames = builtinTools.map(t => t.name);
|
|
1141
|
+
const discoveredCustomTools = await logger.time(
|
|
1142
|
+
"discoverAndLoadCustomTools",
|
|
1143
|
+
discoverAndLoadCustomTools,
|
|
1144
|
+
[],
|
|
1145
|
+
cwd,
|
|
1146
|
+
builtInToolNames,
|
|
1147
|
+
action => queueResolveHandler(toolSession, action),
|
|
1148
|
+
);
|
|
1149
|
+
for (const { path, error } of discoveredCustomTools.errors) {
|
|
1150
|
+
logger.error("Custom tool load failed", { path, error });
|
|
1151
|
+
}
|
|
1152
|
+
if (discoveredCustomTools.tools.length > 0) {
|
|
1153
|
+
customTools.push(...discoveredCustomTools.tools.map(loaded => loaded.tool));
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
const inlineExtensions: ExtensionFactory[] = options.extensions ? [...options.extensions] : [];
|
|
1157
|
+
inlineExtensions.push(createAutoresearchExtension);
|
|
1158
|
+
if (customTools.length > 0) {
|
|
1159
|
+
inlineExtensions.push(createCustomToolsExtension(customTools));
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Load extensions (discovers from standard locations + configured paths)
|
|
1163
|
+
let extensionsResult: LoadExtensionsResult;
|
|
1164
|
+
if (options.disableExtensionDiscovery) {
|
|
1165
|
+
const configuredPaths = options.additionalExtensionPaths ?? [];
|
|
1166
|
+
extensionsResult = await logger.time("loadExtensions", loadExtensions, configuredPaths, cwd, eventBus);
|
|
1167
|
+
for (const { path, error } of extensionsResult.errors) {
|
|
1168
|
+
logger.error("Failed to load extension", { path, error });
|
|
1169
|
+
}
|
|
1170
|
+
} else if (options.preloadedExtensions) {
|
|
1171
|
+
extensionsResult = options.preloadedExtensions;
|
|
1172
|
+
} else {
|
|
1173
|
+
// Merge CLI extension paths with settings extension paths
|
|
1174
|
+
const configuredPaths = [...(options.additionalExtensionPaths ?? []), ...(settings.get("extensions") ?? [])];
|
|
1175
|
+
const disabledExtensionIds = settings.get("disabledExtensions") ?? [];
|
|
1176
|
+
extensionsResult = await logger.time(
|
|
1177
|
+
"discoverAndLoadExtensions",
|
|
1178
|
+
discoverAndLoadExtensions,
|
|
1179
|
+
configuredPaths,
|
|
1180
|
+
cwd,
|
|
1181
|
+
eventBus,
|
|
1182
|
+
disabledExtensionIds,
|
|
1183
|
+
);
|
|
1184
|
+
for (const { path, error } of extensionsResult.errors) {
|
|
1185
|
+
logger.error("Failed to load extension", { path, error });
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// Load inline extensions from factories
|
|
1190
|
+
if (inlineExtensions.length > 0) {
|
|
1191
|
+
for (let i = 0; i < inlineExtensions.length; i++) {
|
|
1192
|
+
const factory = inlineExtensions[i];
|
|
1193
|
+
const loaded = await loadExtensionFromFactory(
|
|
1194
|
+
factory,
|
|
1195
|
+
cwd,
|
|
1196
|
+
eventBus,
|
|
1197
|
+
extensionsResult.runtime,
|
|
1198
|
+
`<inline-${i}>`,
|
|
1199
|
+
);
|
|
1200
|
+
extensionsResult.extensions.push(loaded);
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
// Process provider registrations queued during extension loading.
|
|
1205
|
+
// This must happen before the runner is created so that models registered by
|
|
1206
|
+
// extensions are available for model selection on session resume / fallback.
|
|
1207
|
+
const activeExtensionSources = extensionsResult.extensions.map(extension => extension.path);
|
|
1208
|
+
modelRegistry.syncExtensionSources(activeExtensionSources);
|
|
1209
|
+
for (const sourceId of new Set(activeExtensionSources)) {
|
|
1210
|
+
modelRegistry.clearSourceRegistrations(sourceId);
|
|
1211
|
+
}
|
|
1212
|
+
if (extensionsResult.runtime.pendingProviderRegistrations.length > 0) {
|
|
1213
|
+
for (const { name, config, sourceId } of extensionsResult.runtime.pendingProviderRegistrations) {
|
|
1214
|
+
modelRegistry.registerProvider(name, config, sourceId);
|
|
1215
|
+
}
|
|
1216
|
+
extensionsResult.runtime.pendingProviderRegistrations = [];
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
// Resolve deferred --model pattern now that extension models are registered.
|
|
1220
|
+
if (!model && options.modelPattern) {
|
|
1221
|
+
const availableModels = modelRegistry.getAll();
|
|
1222
|
+
const matchPreferences = {
|
|
1223
|
+
usageOrder: settings.getStorage()?.getModelUsageOrder(),
|
|
1224
|
+
};
|
|
1225
|
+
const { model: resolved } = parseModelPattern(options.modelPattern, availableModels, matchPreferences, {
|
|
1226
|
+
modelRegistry,
|
|
1227
|
+
});
|
|
1228
|
+
if (resolved) {
|
|
1229
|
+
model = resolved;
|
|
1230
|
+
modelFallbackMessage = undefined;
|
|
1231
|
+
} else {
|
|
1232
|
+
modelFallbackMessage = `Model "${options.modelPattern}" not found`;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
// Fall back to first available model with a valid API key.
|
|
1237
|
+
// Skip fallback if the user explicitly requested a model via --model that wasn't found.
|
|
1238
|
+
if (!model && !options.modelPattern) {
|
|
1239
|
+
const allModels = modelRegistry.getAll();
|
|
1240
|
+
for (const candidate of allModels) {
|
|
1241
|
+
if (await hasModelApiKey(candidate)) {
|
|
1242
|
+
model = candidate;
|
|
1243
|
+
break;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
if (model) {
|
|
1247
|
+
if (modelFallbackMessage) {
|
|
1248
|
+
modelFallbackMessage += `. Using ${model.provider}/${model.id}`;
|
|
1249
|
+
}
|
|
1250
|
+
} else {
|
|
1251
|
+
modelFallbackMessage =
|
|
1252
|
+
"No models available. Use /login or set an API key environment variable. Then use /model to select a model.";
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// Discover custom commands (TypeScript slash commands)
|
|
1257
|
+
const customCommandsResult: CustomCommandsLoadResult = options.disableExtensionDiscovery
|
|
1258
|
+
? { commands: [], errors: [] }
|
|
1259
|
+
: await logger.time("discoverCustomCommands", loadCustomCommandsInternal, { cwd, agentDir });
|
|
1260
|
+
if (!options.disableExtensionDiscovery) {
|
|
1261
|
+
for (const { path, error } of customCommandsResult.errors) {
|
|
1262
|
+
logger.error("Failed to load custom command", { path, error });
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
let extensionRunner: ExtensionRunner | undefined;
|
|
1267
|
+
if (extensionsResult.extensions.length > 0) {
|
|
1268
|
+
extensionRunner = new ExtensionRunner(
|
|
1269
|
+
extensionsResult.extensions,
|
|
1270
|
+
extensionsResult.runtime,
|
|
1271
|
+
cwd,
|
|
1272
|
+
sessionManager,
|
|
1273
|
+
modelRegistry,
|
|
1274
|
+
);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
const getSessionContext = () => ({
|
|
1278
|
+
sessionManager,
|
|
1279
|
+
modelRegistry,
|
|
1280
|
+
model: agent.state.model,
|
|
1281
|
+
isIdle: () => !session.isStreaming,
|
|
1282
|
+
hasQueuedMessages: () => session.queuedMessageCount > 0,
|
|
1283
|
+
abort: () => {
|
|
1284
|
+
session.abort();
|
|
1285
|
+
},
|
|
1286
|
+
settings,
|
|
1287
|
+
});
|
|
1288
|
+
const toolContextStore = new ToolContextStore(getSessionContext);
|
|
1289
|
+
|
|
1290
|
+
const registeredTools = extensionRunner?.getAllRegisteredTools() ?? [];
|
|
1291
|
+
let wrappedExtensionTools: Tool[];
|
|
1292
|
+
|
|
1293
|
+
if (extensionRunner) {
|
|
1294
|
+
// With extension runner: convert CustomTools to ToolDefinitions and wrap all together
|
|
1295
|
+
const allCustomTools = [
|
|
1296
|
+
...registeredTools,
|
|
1297
|
+
...(options.customTools?.map(tool => {
|
|
1298
|
+
const definition = isCustomTool(tool) ? customToolToDefinition(tool) : tool;
|
|
1299
|
+
return { definition, extensionPath: "<sdk>" };
|
|
1300
|
+
}) ?? []),
|
|
1301
|
+
];
|
|
1302
|
+
wrappedExtensionTools = wrapRegisteredTools(allCustomTools, extensionRunner);
|
|
1303
|
+
} else {
|
|
1304
|
+
// Without extension runner: wrap CustomTools directly with CustomToolAdapter
|
|
1305
|
+
// ToolDefinition items require ExtensionContext and cannot be used without a runner
|
|
1306
|
+
const customToolContext = (): CustomToolContext => ({
|
|
1307
|
+
sessionManager,
|
|
1308
|
+
modelRegistry,
|
|
1309
|
+
model: agent?.state.model,
|
|
1310
|
+
isIdle: () => !session?.isStreaming,
|
|
1311
|
+
hasQueuedMessages: () => (session?.queuedMessageCount ?? 0) > 0,
|
|
1312
|
+
abort: () => session?.abort(),
|
|
1313
|
+
settings,
|
|
1314
|
+
});
|
|
1315
|
+
wrappedExtensionTools = (options.customTools ?? [])
|
|
1316
|
+
.filter(isCustomTool)
|
|
1317
|
+
.map(tool => CustomToolAdapter.wrap(tool, customToolContext));
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
// All built-in tools are active (conditional tools like git/ask return null from factory if disabled)
|
|
1321
|
+
const toolRegistry = new Map<string, Tool>();
|
|
1322
|
+
for (const tool of builtinTools) {
|
|
1323
|
+
toolRegistry.set(tool.name, tool);
|
|
1324
|
+
}
|
|
1325
|
+
for (const tool of wrappedExtensionTools) {
|
|
1326
|
+
toolRegistry.set(tool.name, tool);
|
|
1327
|
+
}
|
|
1328
|
+
if (extensionRunner) {
|
|
1329
|
+
for (const tool of toolRegistry.values()) {
|
|
1330
|
+
toolRegistry.set(tool.name, new ExtensionToolWrapper(tool, extensionRunner));
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
if (model?.provider === "cursor") {
|
|
1334
|
+
toolRegistry.delete("edit");
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
const hasDeferrableTools = Array.from(toolRegistry.values()).some(tool => tool.deferrable === true);
|
|
1338
|
+
if (!hasDeferrableTools) {
|
|
1339
|
+
toolRegistry.delete("resolve");
|
|
1340
|
+
} else if (!toolRegistry.has("resolve")) {
|
|
1341
|
+
const resolveTool = await logger.time("createTools:resolve:session", HIDDEN_TOOLS.resolve, toolSession);
|
|
1342
|
+
if (resolveTool) {
|
|
1343
|
+
toolRegistry.set(resolveTool.name, wrapToolWithMetaNotice(resolveTool));
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
let cursorEventEmitter: ((event: AgentEvent) => void) | undefined;
|
|
1348
|
+
const cursorExecHandlers = new CursorExecHandlers({
|
|
1349
|
+
cwd,
|
|
1350
|
+
tools: toolRegistry,
|
|
1351
|
+
getToolContext: () => toolContextStore.getContext(),
|
|
1352
|
+
emitEvent: event => cursorEventEmitter?.(event),
|
|
1353
|
+
});
|
|
1354
|
+
|
|
1355
|
+
const repeatToolDescriptions = settings.get("repeatToolDescriptions");
|
|
1356
|
+
const eagerTasks = settings.get("task.eager");
|
|
1357
|
+
const intentField = settings.get("tools.intentTracing") || $flag("PI_INTENT_TRACING") ? INTENT_FIELD : undefined;
|
|
1358
|
+
const rebuildSystemPrompt = async (toolNames: string[], tools: Map<string, AgentTool>): Promise<string> => {
|
|
1359
|
+
toolContextStore.setToolNames(toolNames);
|
|
1360
|
+
const discoverableMCPTools = mcpDiscoveryEnabled ? collectDiscoverableMCPTools(tools.values()) : [];
|
|
1361
|
+
const discoverableMCPSummary = summarizeDiscoverableMCPTools(discoverableMCPTools);
|
|
1362
|
+
const hasDiscoverableMCPTools =
|
|
1363
|
+
mcpDiscoveryEnabled && toolNames.includes("search_tool_bm25") && discoverableMCPTools.length > 0;
|
|
1364
|
+
const promptTools = buildSystemPromptToolMetadata(tools, {
|
|
1365
|
+
search_tool_bm25: { description: renderSearchToolBm25Description(discoverableMCPTools) },
|
|
1366
|
+
});
|
|
1367
|
+
const memoryInstructions = await buildMemoryToolDeveloperInstructions(agentDir, settings);
|
|
1368
|
+
|
|
1369
|
+
// Resolve F5 XC context for the prompt. Read fresh each rebuild so tool-triggered
|
|
1370
|
+
// rebuilds reflect the most recent /context activate. Mid-session context changes without a
|
|
1371
|
+
// tool change are handled via custom_message injection in the onContextChange listener.
|
|
1372
|
+
let contextForPrompt:
|
|
1373
|
+
| {
|
|
1374
|
+
tenant: string;
|
|
1375
|
+
namespace: string;
|
|
1376
|
+
credentialSource: string;
|
|
1377
|
+
authStatus: string;
|
|
1378
|
+
apiUrl?: string;
|
|
1379
|
+
envVars?: Record<string, string>;
|
|
1380
|
+
}
|
|
1381
|
+
| undefined;
|
|
1382
|
+
try {
|
|
1383
|
+
const status = contextServiceRef?.instance?.getStatus();
|
|
1384
|
+
// The LLM needs to anchor on tenant + namespace regardless of whether credentials
|
|
1385
|
+
// come from a named context or from XCSH_API_URL/XCSH_API_TOKEN env vars. For the
|
|
1386
|
+
// env-only path, activeContextName is null but activeContextTenant (derived from
|
|
1387
|
+
// XCSH_API_URL) is still set and credentialSource is "environment". Guard on
|
|
1388
|
+
// tenant, not name, so env-backed deployments also get the prompt anchor.
|
|
1389
|
+
if (status?.isConfigured && status.activeContextTenant) {
|
|
1390
|
+
contextForPrompt = {
|
|
1391
|
+
tenant: status.activeContextTenant,
|
|
1392
|
+
namespace: status.activeContextNamespace ?? "default",
|
|
1393
|
+
credentialSource: status.credentialSource,
|
|
1394
|
+
authStatus: status.authStatus,
|
|
1395
|
+
apiUrl: status.activeContextUrl ?? undefined,
|
|
1396
|
+
};
|
|
1397
|
+
const sensitiveKeys = contextServiceRef?.instance?.getActiveSensitiveKeys();
|
|
1398
|
+
const ctxEnv = createContextEnv(settings, sensitiveKeys?.size ? { sensitiveKeys } : undefined);
|
|
1399
|
+
const envVars = ctxEnv.getNonSensitiveVars();
|
|
1400
|
+
if (Object.keys(envVars).length > 0) {
|
|
1401
|
+
contextForPrompt.envVars = envVars;
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
} catch {
|
|
1405
|
+
// ContextService not available or not initialized — leave contextForPrompt undefined.
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
let knowledgeTopics: string | undefined;
|
|
1409
|
+
try {
|
|
1410
|
+
if (knowledgeServiceRef) {
|
|
1411
|
+
const svc = knowledgeServiceRef.instance;
|
|
1412
|
+
let cached = svc.getIndex();
|
|
1413
|
+
if (!cached) {
|
|
1414
|
+
await svc.getOrRefreshIndex();
|
|
1415
|
+
cached = svc.getIndex();
|
|
1416
|
+
} else {
|
|
1417
|
+
void svc.getOrRefreshIndex();
|
|
1418
|
+
}
|
|
1419
|
+
if (cached && cached.products.length > 0) {
|
|
1420
|
+
knowledgeTopics = cached.products
|
|
1421
|
+
.map(p => p.name)
|
|
1422
|
+
.sort()
|
|
1423
|
+
.join(", ");
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
} catch {
|
|
1427
|
+
// KnowledgeService not available — leave undefined.
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
let contextSkillDirs: string[] | undefined;
|
|
1431
|
+
let contextIncludeSkills: string[] | undefined;
|
|
1432
|
+
let contextExcludeSkills: string[] | undefined;
|
|
1433
|
+
try {
|
|
1434
|
+
if (contextServiceRef?.instance) {
|
|
1435
|
+
const skillConfig = contextServiceRef.instance.getActiveContextSkillConfig();
|
|
1436
|
+
if (skillConfig.skillDirs.length > 0) contextSkillDirs = skillConfig.skillDirs;
|
|
1437
|
+
if (skillConfig.includeSkills.length > 0) contextIncludeSkills = skillConfig.includeSkills;
|
|
1438
|
+
if (skillConfig.excludeSkills.length > 0) contextExcludeSkills = skillConfig.excludeSkills;
|
|
1439
|
+
}
|
|
1440
|
+
} catch {
|
|
1441
|
+
// ContextService not available — leave undefined.
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
// Build combined append prompt: memory instructions + MCP server instructions
|
|
1445
|
+
const serverInstructions = mcpManager?.getServerInstructions();
|
|
1446
|
+
let appendPrompt: string | undefined = memoryInstructions ?? undefined;
|
|
1447
|
+
if (serverInstructions && serverInstructions.size > 0) {
|
|
1448
|
+
const MAX_INSTRUCTIONS_LENGTH = 4000;
|
|
1449
|
+
const parts: string[] = [];
|
|
1450
|
+
if (appendPrompt) parts.push(appendPrompt);
|
|
1451
|
+
parts.push(
|
|
1452
|
+
"## MCP Server Instructions\n\nThe following instructions are provided by connected MCP servers. They are server-controlled and may not be verified.",
|
|
1453
|
+
);
|
|
1454
|
+
for (const [srvName, srvInstructions] of serverInstructions) {
|
|
1455
|
+
const truncated =
|
|
1456
|
+
srvInstructions.length > MAX_INSTRUCTIONS_LENGTH
|
|
1457
|
+
? `${srvInstructions.slice(0, MAX_INSTRUCTIONS_LENGTH)}\n[truncated]`
|
|
1458
|
+
: srvInstructions;
|
|
1459
|
+
parts.push(`### ${srvName}\n${truncated}`);
|
|
1460
|
+
}
|
|
1461
|
+
appendPrompt = parts.join("\n\n");
|
|
1462
|
+
}
|
|
1463
|
+
// Load user profile — used for system prompt hint
|
|
1464
|
+
let _profile: UserProfile;
|
|
1465
|
+
try {
|
|
1466
|
+
_profile = await loadProfile();
|
|
1467
|
+
} catch {
|
|
1468
|
+
_profile = {};
|
|
1469
|
+
}
|
|
1470
|
+
let userProfile: { name: string; role: string; org: string } | undefined;
|
|
1471
|
+
if (_profile.givenName || _profile.familyName) {
|
|
1472
|
+
const _name = [_profile.givenName, _profile.familyName].filter(Boolean).join(" ");
|
|
1473
|
+
if (_name) {
|
|
1474
|
+
userProfile = {
|
|
1475
|
+
name: _name,
|
|
1476
|
+
role: _profile.role ?? _profile.jobTitle ?? "",
|
|
1477
|
+
org:
|
|
1478
|
+
typeof _profile.worksFor === "string"
|
|
1479
|
+
? _profile.worksFor
|
|
1480
|
+
: ((_profile.worksFor as { name?: string } | undefined)?.name ?? ""),
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
// Load compact computer profile hint for system prompt
|
|
1486
|
+
let computerProfile:
|
|
1487
|
+
| {
|
|
1488
|
+
ramGB: number;
|
|
1489
|
+
cpu: string;
|
|
1490
|
+
os: string;
|
|
1491
|
+
cores?: number;
|
|
1492
|
+
shell?: string;
|
|
1493
|
+
diskFree?: string;
|
|
1494
|
+
model?: string;
|
|
1495
|
+
}
|
|
1496
|
+
| undefined;
|
|
1497
|
+
try {
|
|
1498
|
+
const _computerProfile = await loadComputerProfile();
|
|
1499
|
+
computerProfile = buildComputerHint(_computerProfile) ?? undefined;
|
|
1500
|
+
} catch {
|
|
1501
|
+
// No computer profile — hint block omitted
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
const currentLocale = getLocale();
|
|
1505
|
+
const localeName = currentLocale !== "en" ? getLocaleDisplayName(currentLocale) : undefined;
|
|
1506
|
+
const localeForPrompt = localeName ? { code: currentLocale, name: localeName } : undefined;
|
|
1507
|
+
|
|
1508
|
+
const defaultPrompt = await buildSystemPromptInternal({
|
|
1509
|
+
cwd,
|
|
1510
|
+
skills,
|
|
1511
|
+
contextFiles,
|
|
1512
|
+
tools: promptTools,
|
|
1513
|
+
toolNames,
|
|
1514
|
+
rules: rulebookRules,
|
|
1515
|
+
alwaysApplyRules,
|
|
1516
|
+
skillsSettings: settings.getGroup("skills"),
|
|
1517
|
+
appendSystemPrompt: appendPrompt,
|
|
1518
|
+
repeatToolDescriptions,
|
|
1519
|
+
intentField,
|
|
1520
|
+
mcpDiscoveryMode: hasDiscoverableMCPTools,
|
|
1521
|
+
mcpDiscoveryServerSummaries: discoverableMCPSummary.servers.map(formatDiscoverableMCPToolServerSummary),
|
|
1522
|
+
eagerTasks,
|
|
1523
|
+
secretsEnabled,
|
|
1524
|
+
context: contextForPrompt,
|
|
1525
|
+
locale: localeForPrompt,
|
|
1526
|
+
userProfile,
|
|
1527
|
+
computerProfile,
|
|
1528
|
+
knowledgeTopics,
|
|
1529
|
+
contextSkillDirs,
|
|
1530
|
+
contextIncludeSkills,
|
|
1531
|
+
contextExcludeSkills,
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
if (options.systemPrompt === undefined) {
|
|
1535
|
+
return defaultPrompt;
|
|
1536
|
+
}
|
|
1537
|
+
if (typeof options.systemPrompt === "string") {
|
|
1538
|
+
return await buildSystemPromptInternal({
|
|
1539
|
+
cwd,
|
|
1540
|
+
skills,
|
|
1541
|
+
contextFiles,
|
|
1542
|
+
tools: promptTools,
|
|
1543
|
+
toolNames,
|
|
1544
|
+
rules: rulebookRules,
|
|
1545
|
+
alwaysApplyRules,
|
|
1546
|
+
skillsSettings: settings.getGroup("skills"),
|
|
1547
|
+
customPrompt: options.systemPrompt,
|
|
1548
|
+
appendSystemPrompt: appendPrompt,
|
|
1549
|
+
repeatToolDescriptions,
|
|
1550
|
+
intentField,
|
|
1551
|
+
mcpDiscoveryMode: hasDiscoverableMCPTools,
|
|
1552
|
+
mcpDiscoveryServerSummaries: discoverableMCPSummary.servers.map(formatDiscoverableMCPToolServerSummary),
|
|
1553
|
+
eagerTasks,
|
|
1554
|
+
secretsEnabled,
|
|
1555
|
+
context: contextForPrompt,
|
|
1556
|
+
locale: localeForPrompt,
|
|
1557
|
+
userProfile,
|
|
1558
|
+
computerProfile,
|
|
1559
|
+
knowledgeTopics,
|
|
1560
|
+
contextSkillDirs,
|
|
1561
|
+
contextIncludeSkills,
|
|
1562
|
+
contextExcludeSkills,
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
return options.systemPrompt(defaultPrompt);
|
|
1566
|
+
};
|
|
1567
|
+
|
|
1568
|
+
const toolNamesFromRegistry = Array.from(toolRegistry.keys());
|
|
1569
|
+
const requestedToolNames =
|
|
1570
|
+
(options.toolNames ? [...new Set(options.toolNames.map(name => name.toLowerCase()))] : undefined) ??
|
|
1571
|
+
toolNamesFromRegistry;
|
|
1572
|
+
const normalizedRequested = requestedToolNames.filter(name => toolRegistry.has(name));
|
|
1573
|
+
const includeExitPlanMode = requestedToolNames.includes("exit_plan_mode");
|
|
1574
|
+
const mcpDiscoveryEnabled = settings.get("mcp.discoveryMode") ?? false;
|
|
1575
|
+
const defaultInactiveToolNames = new Set(
|
|
1576
|
+
registeredTools.filter(tool => tool.definition.defaultInactive).map(tool => tool.definition.name),
|
|
1577
|
+
);
|
|
1578
|
+
const requestedActiveToolNames = includeExitPlanMode
|
|
1579
|
+
? normalizedRequested
|
|
1580
|
+
: normalizedRequested.filter(name => name !== "exit_plan_mode");
|
|
1581
|
+
const initialRequestedActiveToolNames = options.toolNames
|
|
1582
|
+
? requestedActiveToolNames
|
|
1583
|
+
: requestedActiveToolNames.filter(name => !defaultInactiveToolNames.has(name));
|
|
1584
|
+
const explicitlyRequestedMCPToolNames = options.toolNames
|
|
1585
|
+
? requestedActiveToolNames.filter(name => name.startsWith("mcp_"))
|
|
1586
|
+
: [];
|
|
1587
|
+
const discoveryDefaultServers = new Set(
|
|
1588
|
+
(settings.get("mcp.discoveryDefaultServers") ?? []).map(serverName => serverName.trim()).filter(Boolean),
|
|
1589
|
+
);
|
|
1590
|
+
const discoveryDefaultServerToolNames = mcpDiscoveryEnabled
|
|
1591
|
+
? selectDiscoverableMCPToolNamesByServer(
|
|
1592
|
+
collectDiscoverableMCPTools(toolRegistry.values()),
|
|
1593
|
+
discoveryDefaultServers,
|
|
1594
|
+
)
|
|
1595
|
+
: [];
|
|
1596
|
+
let initialSelectedMCPToolNames: string[] = [];
|
|
1597
|
+
let defaultSelectedMCPToolNames: string[] = [];
|
|
1598
|
+
let initialToolNames = [...initialRequestedActiveToolNames];
|
|
1599
|
+
if (mcpDiscoveryEnabled) {
|
|
1600
|
+
const restoredSelectedMCPToolNames = existingSession.selectedMCPToolNames.filter(name =>
|
|
1601
|
+
toolRegistry.has(name),
|
|
1602
|
+
);
|
|
1603
|
+
defaultSelectedMCPToolNames = [
|
|
1604
|
+
...new Set([...discoveryDefaultServerToolNames, ...explicitlyRequestedMCPToolNames]),
|
|
1605
|
+
];
|
|
1606
|
+
initialSelectedMCPToolNames = existingSession.hasPersistedMCPToolSelection
|
|
1607
|
+
? restoredSelectedMCPToolNames
|
|
1608
|
+
: [...new Set([...restoredSelectedMCPToolNames, ...defaultSelectedMCPToolNames])];
|
|
1609
|
+
initialToolNames = [
|
|
1610
|
+
...new Set([
|
|
1611
|
+
...initialRequestedActiveToolNames.filter(name => !name.startsWith("mcp_")),
|
|
1612
|
+
...initialSelectedMCPToolNames,
|
|
1613
|
+
]),
|
|
1614
|
+
];
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
// Custom tools and extension-registered tools are always included regardless of toolNames filter
|
|
1618
|
+
const alwaysInclude: string[] = [
|
|
1619
|
+
...(options.customTools?.map(t => (isCustomTool(t) ? t.name : t.name)) ?? []),
|
|
1620
|
+
...registeredTools.filter(t => !t.definition.defaultInactive).map(t => t.definition.name),
|
|
1621
|
+
];
|
|
1622
|
+
for (const name of alwaysInclude) {
|
|
1623
|
+
if (mcpDiscoveryEnabled && name.startsWith("mcp_")) {
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
if (toolRegistry.has(name) && !initialToolNames.includes(name)) {
|
|
1627
|
+
initialToolNames.push(name);
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
const systemPrompt = await logger.time("buildSystemPrompt", rebuildSystemPrompt, initialToolNames, toolRegistry);
|
|
1632
|
+
|
|
1633
|
+
const promptTemplates =
|
|
1634
|
+
options.promptTemplates ??
|
|
1635
|
+
(await logger.time("discoverPromptTemplates", discoverPromptTemplates, cwd, agentDir));
|
|
1636
|
+
toolSession.promptTemplates = promptTemplates;
|
|
1637
|
+
|
|
1638
|
+
const slashCommands =
|
|
1639
|
+
options.slashCommands ?? (await logger.time("discoverSlashCommands", discoverSlashCommands, cwd));
|
|
1640
|
+
|
|
1641
|
+
// Create convertToLlm wrapper that filters images if blockImages is enabled (defense-in-depth)
|
|
1642
|
+
const convertToLlmWithBlockImages = (messages: AgentMessage[]): Message[] => {
|
|
1643
|
+
const converted = convertToLlm(messages);
|
|
1644
|
+
// Check setting dynamically so mid-session changes take effect
|
|
1645
|
+
if (!settings.get("images.blockImages")) {
|
|
1646
|
+
return converted;
|
|
1647
|
+
}
|
|
1648
|
+
// Filter out ImageContent from all messages, replacing with text placeholder
|
|
1649
|
+
return converted.map(msg => {
|
|
1650
|
+
if (msg.role === "user" || msg.role === "toolResult") {
|
|
1651
|
+
const content = msg.content;
|
|
1652
|
+
if (Array.isArray(content)) {
|
|
1653
|
+
const hasImages = content.some(c => c.type === "image");
|
|
1654
|
+
if (hasImages) {
|
|
1655
|
+
const filteredContent = content
|
|
1656
|
+
.map(c =>
|
|
1657
|
+
c.type === "image" ? { type: "text" as const, text: "Image reading is disabled." } : c,
|
|
1658
|
+
)
|
|
1659
|
+
.filter((c, i, arr) => {
|
|
1660
|
+
// Dedupe consecutive "Image reading is disabled." texts
|
|
1661
|
+
if (!(c.type === "text" && c.text === "Image reading is disabled." && i > 0)) return true;
|
|
1662
|
+
const prev = arr[i - 1];
|
|
1663
|
+
return !(prev.type === "text" && prev.text === "Image reading is disabled.");
|
|
1664
|
+
});
|
|
1665
|
+
return { ...msg, content: filteredContent };
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
return msg;
|
|
1670
|
+
});
|
|
1671
|
+
};
|
|
1672
|
+
|
|
1673
|
+
// Final convertToLlm: chain block-images filter with secret obfuscation
|
|
1674
|
+
const convertToLlmFinal = (messages: AgentMessage[]): Message[] => {
|
|
1675
|
+
const converted = convertToLlmWithBlockImages(messages);
|
|
1676
|
+
if (!obfuscator?.hasSecrets()) return converted;
|
|
1677
|
+
return obfuscateMessages(obfuscator, converted);
|
|
1678
|
+
};
|
|
1679
|
+
const transformContext = extensionRunner
|
|
1680
|
+
? async (messages: AgentMessage[], _signal?: AbortSignal) => {
|
|
1681
|
+
return await extensionRunner.emitContext(messages);
|
|
1682
|
+
}
|
|
1683
|
+
: undefined;
|
|
1684
|
+
const onPayload = extensionRunner
|
|
1685
|
+
? async (payload: unknown, _model?: Model) => {
|
|
1686
|
+
return await extensionRunner.emitBeforeProviderRequest(payload);
|
|
1687
|
+
}
|
|
1688
|
+
: undefined;
|
|
1689
|
+
|
|
1690
|
+
const setToolUIContext = (uiContext: ExtensionUIContext, hasUI: boolean) => {
|
|
1691
|
+
toolContextStore.setUIContext(uiContext, hasUI);
|
|
1692
|
+
};
|
|
1693
|
+
|
|
1694
|
+
const initialTools = initialToolNames
|
|
1695
|
+
.map(name => toolRegistry.get(name))
|
|
1696
|
+
.filter((tool): tool is AgentTool => tool !== undefined);
|
|
1697
|
+
|
|
1698
|
+
const openaiWebsocketSetting = settings.get("providers.openaiWebsockets") ?? "off";
|
|
1699
|
+
const preferOpenAICodexWebsockets =
|
|
1700
|
+
openaiWebsocketSetting === "on" ? true : openaiWebsocketSetting === "off" ? false : undefined;
|
|
1701
|
+
const serviceTierSetting = settings.get("serviceTier");
|
|
1702
|
+
|
|
1703
|
+
const initialServiceTier = hasServiceTierEntry
|
|
1704
|
+
? existingSession.serviceTier
|
|
1705
|
+
: serviceTierSetting === "none"
|
|
1706
|
+
? undefined
|
|
1707
|
+
: serviceTierSetting;
|
|
1708
|
+
|
|
1709
|
+
agent = new Agent({
|
|
1710
|
+
initialState: {
|
|
1711
|
+
systemPrompt,
|
|
1712
|
+
model,
|
|
1713
|
+
thinkingLevel: toReasoningEffort(thinkingLevel),
|
|
1714
|
+
tools: initialTools,
|
|
1715
|
+
},
|
|
1716
|
+
convertToLlm: convertToLlmFinal,
|
|
1717
|
+
onPayload,
|
|
1718
|
+
sessionId: providerSessionId,
|
|
1719
|
+
transformContext,
|
|
1720
|
+
steeringMode: settings.get("steeringMode") ?? "one-at-a-time",
|
|
1721
|
+
followUpMode: settings.get("followUpMode") ?? "one-at-a-time",
|
|
1722
|
+
interruptMode: settings.get("interruptMode") ?? "wait",
|
|
1723
|
+
thinkingBudgets: settings.getGroup("thinkingBudgets"),
|
|
1724
|
+
temperature: settings.get("temperature") >= 0 ? settings.get("temperature") : undefined,
|
|
1725
|
+
topP: settings.get("topP") >= 0 ? settings.get("topP") : undefined,
|
|
1726
|
+
topK: settings.get("topK") >= 0 ? settings.get("topK") : undefined,
|
|
1727
|
+
minP: settings.get("minP") >= 0 ? settings.get("minP") : undefined,
|
|
1728
|
+
presencePenalty: settings.get("presencePenalty") >= 0 ? settings.get("presencePenalty") : undefined,
|
|
1729
|
+
repetitionPenalty: settings.get("repetitionPenalty") >= 0 ? settings.get("repetitionPenalty") : undefined,
|
|
1730
|
+
serviceTier: initialServiceTier,
|
|
1731
|
+
kimiApiFormat: settings.get("providers.kimiApiFormat") ?? "anthropic",
|
|
1732
|
+
preferWebsockets: preferOpenAICodexWebsockets,
|
|
1733
|
+
getToolContext: tc => toolContextStore.getContext(tc),
|
|
1734
|
+
getApiKey: async provider => {
|
|
1735
|
+
// Use the provider-facing session id for sticky credential selection so cache keys
|
|
1736
|
+
// and provider auth affinity stay aligned across fresh benchmark sessions.
|
|
1737
|
+
const key = await modelRegistry.getApiKeyForProvider(provider, providerSessionId);
|
|
1738
|
+
if (!key) {
|
|
1739
|
+
throw new Error(`No API key found for provider "${provider}"`);
|
|
1740
|
+
}
|
|
1741
|
+
return key;
|
|
1742
|
+
},
|
|
1743
|
+
cursorExecHandlers,
|
|
1744
|
+
transformToolCallArguments: (args, _toolName) => {
|
|
1745
|
+
let result = args;
|
|
1746
|
+
const maxTimeout = settings.get("tools.maxTimeout");
|
|
1747
|
+
if (maxTimeout > 0 && typeof result.timeout === "number") {
|
|
1748
|
+
result = { ...result, timeout: Math.min(result.timeout, maxTimeout) };
|
|
1749
|
+
}
|
|
1750
|
+
if (obfuscator?.hasSecrets()) {
|
|
1751
|
+
result = obfuscator.deobfuscateObject(result);
|
|
1752
|
+
}
|
|
1753
|
+
return result;
|
|
1754
|
+
},
|
|
1755
|
+
intentTracing: !!intentField,
|
|
1756
|
+
getToolChoice: () => session?.nextToolChoice(),
|
|
1757
|
+
});
|
|
1758
|
+
|
|
1759
|
+
cursorEventEmitter = event => agent.emitExternalEvent(event);
|
|
1760
|
+
|
|
1761
|
+
// Restore messages if session has existing data
|
|
1762
|
+
if (hasExistingSession) {
|
|
1763
|
+
agent.replaceMessages(existingSession.messages);
|
|
1764
|
+
} else {
|
|
1765
|
+
// Save initial model and thinking level for new sessions so they can be restored on resume
|
|
1766
|
+
if (model) {
|
|
1767
|
+
sessionManager.appendModelChange(`${model.provider}/${model.id}`);
|
|
1768
|
+
}
|
|
1769
|
+
sessionManager.appendThinkingLevelChange(thinkingLevel);
|
|
1770
|
+
// Save active context (if any) so resumed sessions know their platform context.
|
|
1771
|
+
try {
|
|
1772
|
+
const { ContextService } = await import("./services/xcsh-context");
|
|
1773
|
+
const status = ContextService.instance?.getStatus();
|
|
1774
|
+
if (status?.isConfigured && status.activeContextName && status.activeContextTenant) {
|
|
1775
|
+
sessionManager.appendContextChange(
|
|
1776
|
+
status.activeContextName,
|
|
1777
|
+
status.activeContextTenant,
|
|
1778
|
+
status.activeContextNamespace ?? "default",
|
|
1779
|
+
);
|
|
1780
|
+
}
|
|
1781
|
+
} catch {
|
|
1782
|
+
// ContextService not available (SDK consumers, tests). Skip.
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
session = new AgentSession({
|
|
1787
|
+
agent,
|
|
1788
|
+
thinkingLevel,
|
|
1789
|
+
sessionManager,
|
|
1790
|
+
settings,
|
|
1791
|
+
pythonKernelOwnerId,
|
|
1792
|
+
scopedModels: options.scopedModels,
|
|
1793
|
+
promptTemplates,
|
|
1794
|
+
slashCommands,
|
|
1795
|
+
extensionRunner,
|
|
1796
|
+
customCommands: customCommandsResult.commands,
|
|
1797
|
+
skills,
|
|
1798
|
+
skillWarnings,
|
|
1799
|
+
skillsSettings: settings.getGroup("skills"),
|
|
1800
|
+
modelRegistry,
|
|
1801
|
+
toolRegistry,
|
|
1802
|
+
transformContext,
|
|
1803
|
+
onPayload,
|
|
1804
|
+
convertToLlm: convertToLlmFinal,
|
|
1805
|
+
rebuildSystemPrompt,
|
|
1806
|
+
mcpDiscoveryEnabled,
|
|
1807
|
+
initialSelectedMCPToolNames,
|
|
1808
|
+
defaultSelectedMCPToolNames,
|
|
1809
|
+
persistInitialMCPToolSelection: !hasExistingSession,
|
|
1810
|
+
defaultSelectedMCPServerNames: [...discoveryDefaultServers],
|
|
1811
|
+
ttsrManager,
|
|
1812
|
+
obfuscator,
|
|
1813
|
+
asyncJobManager,
|
|
1814
|
+
});
|
|
1815
|
+
hasSession = true;
|
|
1816
|
+
|
|
1817
|
+
// Register the context-change listener now that the session exists. Registering here
|
|
1818
|
+
// (atomically with addDisposeHook below) means a failed createAgentSession leaves no
|
|
1819
|
+
// leaked listener, and the listener closes over a fully-constructed session.
|
|
1820
|
+
//
|
|
1821
|
+
// The listener fires on every #applyToSettings call in ContextService (activate,
|
|
1822
|
+
// setNamespace, setEnvVars, unsetEnvVars, loadActive). It serves two roles:
|
|
1823
|
+
// 1. Refresh the secret obfuscator when context env/token changes.
|
|
1824
|
+
// 2. Notify the LLM when context the prompt block mirrors (name OR namespace)
|
|
1825
|
+
// changes — settings-only events (env var mutations) do not emit the notice.
|
|
1826
|
+
//
|
|
1827
|
+
// Wrapped in try so that tests and SDK consumers that never initialize ContextService
|
|
1828
|
+
// don't trip on .instance throwing. In those paths we skip listener registration
|
|
1829
|
+
// entirely — there's no context to track.
|
|
1830
|
+
try {
|
|
1831
|
+
if (!contextServiceRef) throw new Error("no ContextService");
|
|
1832
|
+
const service = contextServiceRef;
|
|
1833
|
+
|
|
1834
|
+
// Track both name and namespace — they both appear in the system-prompt anchor block
|
|
1835
|
+
// and both affect F5 XC operation targeting, so either changing is an LLM-visible event.
|
|
1836
|
+
// Seed from current state so re-activating the same context, or firing for env-only
|
|
1837
|
+
// mutations, does not produce a spurious "Context switched" directive.
|
|
1838
|
+
const seedStatus = service.instance.getStatus();
|
|
1839
|
+
let lastEmittedContext: { name: string; namespace: string } | undefined =
|
|
1840
|
+
seedStatus.activeContextName && seedStatus.activeContextTenant
|
|
1841
|
+
? {
|
|
1842
|
+
name: seedStatus.activeContextName,
|
|
1843
|
+
namespace: seedStatus.activeContextNamespace ?? "default",
|
|
1844
|
+
}
|
|
1845
|
+
: undefined;
|
|
1846
|
+
|
|
1847
|
+
const listener: (ctx: import("./services/xcsh-context").XCSHContext) => void = ctx => {
|
|
1848
|
+
// Role 1: obfuscator refresh on credential change.
|
|
1849
|
+
const newValues: string[] = [ctx.apiToken];
|
|
1850
|
+
if (ctx.sensitiveKeys && ctx.env) {
|
|
1851
|
+
for (const key of ctx.sensitiveKeys) {
|
|
1852
|
+
const v = ctx.env[key];
|
|
1853
|
+
if (v) newValues.push(v);
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
const bashEnv = (settings.get("bash.environment") ?? {}) as Record<string, string>;
|
|
1857
|
+
for (const [name, value] of Object.entries(bashEnv)) {
|
|
1858
|
+
if (value && SECRET_ENV_PATTERNS.test(name)) newValues.push(value);
|
|
1859
|
+
}
|
|
1860
|
+
if (obfuscator) {
|
|
1861
|
+
obfuscator.addPlainSecrets(newValues);
|
|
1862
|
+
} else {
|
|
1863
|
+
// Obfuscator was undefined at session start (no secrets detected).
|
|
1864
|
+
// Create one now so late context activations are still masked.
|
|
1865
|
+
const entries: SecretEntry[] = newValues
|
|
1866
|
+
.filter(v => v.length > 0)
|
|
1867
|
+
.map(v => ({ type: "plain" as const, content: v, mode: "obfuscate" as const }));
|
|
1868
|
+
if (entries.length > 0) {
|
|
1869
|
+
obfuscator = new SecretObfuscator(entries);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
|
|
1873
|
+
// Role 2: notify the LLM when name OR namespace changes. Read
|
|
1874
|
+
// ContextService.getStatus() (not the callback arg) to honor the XCSH_NAMESPACE
|
|
1875
|
+
// env override consistently with session-start emission.
|
|
1876
|
+
try {
|
|
1877
|
+
const currentStatus = service.instance?.getStatus();
|
|
1878
|
+
const currentName = currentStatus?.activeContextName ?? undefined;
|
|
1879
|
+
const currentTenant = currentStatus?.activeContextTenant ?? undefined;
|
|
1880
|
+
if (!currentName || !currentTenant) return;
|
|
1881
|
+
const currentNamespace = currentStatus.activeContextNamespace ?? "default";
|
|
1882
|
+
|
|
1883
|
+
const changed =
|
|
1884
|
+
!lastEmittedContext ||
|
|
1885
|
+
currentName !== lastEmittedContext.name ||
|
|
1886
|
+
currentNamespace !== lastEmittedContext.namespace;
|
|
1887
|
+
if (!changed) return;
|
|
1888
|
+
|
|
1889
|
+
// Append the context_change entry for replay/resume state reconstruction.
|
|
1890
|
+
sessionManager.appendContextChange(currentName, currentTenant, currentNamespace);
|
|
1891
|
+
|
|
1892
|
+
// Push a custom_message into BOTH agent.state.messages AND the session log so
|
|
1893
|
+
// the LLM sees the directive on its next turn. sendCustomMessage handles
|
|
1894
|
+
// streaming vs non-streaming correctly (queues via steer/followUp/nextTurn
|
|
1895
|
+
// when a turn is in flight).
|
|
1896
|
+
const nameChanged = !lastEmittedContext || currentName !== lastEmittedContext.name;
|
|
1897
|
+
const prefix = nameChanged
|
|
1898
|
+
? `[Context switched to ${currentName}]`
|
|
1899
|
+
: `[F5 XC namespace changed to ${currentNamespace}]`;
|
|
1900
|
+
void session.sendCustomMessage({
|
|
1901
|
+
customType: "context_change_notice",
|
|
1902
|
+
content:
|
|
1903
|
+
`${prefix} Tenant: ${currentTenant}, ` +
|
|
1904
|
+
`namespace: ${currentNamespace}. Target this tenant and namespace ` +
|
|
1905
|
+
`for subsequent F5 XC operations.`,
|
|
1906
|
+
display: true,
|
|
1907
|
+
attribution: "agent",
|
|
1908
|
+
});
|
|
1909
|
+
lastEmittedContext = { name: currentName, namespace: currentNamespace };
|
|
1910
|
+
void session.refreshBaseSystemPrompt();
|
|
1911
|
+
// Role 3: background-validate credentials after context switch so the
|
|
1912
|
+
// LLM knows whether the new context is usable. Fire-and-forget — do
|
|
1913
|
+
// not block the context change notification.
|
|
1914
|
+
void (async () => {
|
|
1915
|
+
try {
|
|
1916
|
+
const { status: authStatus, latencyMs } = await service.instance.validateToken({
|
|
1917
|
+
timeoutMs: 5000,
|
|
1918
|
+
});
|
|
1919
|
+
const qualifier =
|
|
1920
|
+
authStatus === "connected"
|
|
1921
|
+
? `connected${latencyMs ? ` (${latencyMs}ms)` : ""}`
|
|
1922
|
+
: authStatus === "auth_error"
|
|
1923
|
+
? "credential error -- token may be invalid or expired"
|
|
1924
|
+
: "unreachable -- network error or tenant offline";
|
|
1925
|
+
void session.sendCustomMessage({
|
|
1926
|
+
customType: "context_validation_result",
|
|
1927
|
+
content: `[Auth status: ${authStatus}] Credentials for ${currentTenant}: ${qualifier}.`,
|
|
1928
|
+
display: true,
|
|
1929
|
+
attribution: "agent",
|
|
1930
|
+
});
|
|
1931
|
+
} catch {
|
|
1932
|
+
// Validation failed (e.g., no credentials configured) -- skip silently.
|
|
1933
|
+
}
|
|
1934
|
+
})();
|
|
1935
|
+
} catch {
|
|
1936
|
+
// ContextService.instance throws if not initialized; skip.
|
|
1937
|
+
}
|
|
1938
|
+
};
|
|
1939
|
+
|
|
1940
|
+
service.onContextChange(listener);
|
|
1941
|
+
session.addDisposeHook(() => service.offContextChange(listener));
|
|
1942
|
+
const authListener = (prev: string, current: string) => {
|
|
1943
|
+
if (prev === current) return;
|
|
1944
|
+
const isDegradation = current === "auth_error" || current === "offline";
|
|
1945
|
+
if (!isDegradation && prev === "unknown") return;
|
|
1946
|
+
const content = isDegradation
|
|
1947
|
+
? `[Auth status: ${prev} → ${current}] F5 XC credentials may be stale. Run /context validate to check.`
|
|
1948
|
+
: `[Auth status: ${current}] F5 XC credentials are valid again.`;
|
|
1949
|
+
void session.sendCustomMessage({
|
|
1950
|
+
customType: "auth_status_change",
|
|
1951
|
+
content,
|
|
1952
|
+
display: true,
|
|
1953
|
+
attribution: "agent",
|
|
1954
|
+
});
|
|
1955
|
+
};
|
|
1956
|
+
service.onAuthStatusChange(authListener);
|
|
1957
|
+
session.addDisposeHook(() => service.offAuthStatusChange(authListener));
|
|
1958
|
+
const tokenHealthListener = (_prev: string, current: string) => {
|
|
1959
|
+
if (current === "expiring") {
|
|
1960
|
+
void session.sendCustomMessage({
|
|
1961
|
+
customType: "token_health_change",
|
|
1962
|
+
content: "[Token expiring] F5 XC API token expires soon. Run /context create to rotate.",
|
|
1963
|
+
display: true,
|
|
1964
|
+
attribution: "agent",
|
|
1965
|
+
});
|
|
1966
|
+
} else if (current === "expired") {
|
|
1967
|
+
void session.sendCustomMessage({
|
|
1968
|
+
customType: "token_health_change",
|
|
1969
|
+
content: "[Token expired] F5 XC API token has expired. Run /context create to replace it.",
|
|
1970
|
+
display: true,
|
|
1971
|
+
attribution: "agent",
|
|
1972
|
+
});
|
|
1973
|
+
}
|
|
1974
|
+
};
|
|
1975
|
+
service.onTokenHealthChange(tokenHealthListener);
|
|
1976
|
+
session.addDisposeHook(() => service.offTokenHealthChange(tokenHealthListener));
|
|
1977
|
+
} catch {
|
|
1978
|
+
// ContextService not initialized — skip listener registration entirely.
|
|
1979
|
+
// Tests and SDK consumers that don't use contexts won't reach this branch.
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
if (model?.api === "openai-codex-responses") {
|
|
1983
|
+
const codexModel = model;
|
|
1984
|
+
const codexTransport = getOpenAICodexTransportDetails(codexModel, {
|
|
1985
|
+
sessionId: providerSessionId,
|
|
1986
|
+
baseUrl: codexModel.baseUrl,
|
|
1987
|
+
preferWebsockets: preferOpenAICodexWebsockets,
|
|
1988
|
+
providerSessionState: session.providerSessionState,
|
|
1989
|
+
});
|
|
1990
|
+
if (codexTransport.websocketPreferred) {
|
|
1991
|
+
void (async () => {
|
|
1992
|
+
try {
|
|
1993
|
+
const codexPrewarmApiKey = await modelRegistry.getApiKey(codexModel, providerSessionId);
|
|
1994
|
+
if (!codexPrewarmApiKey) return;
|
|
1995
|
+
await logger.time("prewarmOpenAICodexResponses", prewarmOpenAICodexResponses, codexModel, {
|
|
1996
|
+
apiKey: codexPrewarmApiKey,
|
|
1997
|
+
sessionId: providerSessionId,
|
|
1998
|
+
preferWebsockets: preferOpenAICodexWebsockets,
|
|
1999
|
+
providerSessionState: session.providerSessionState,
|
|
2000
|
+
});
|
|
2001
|
+
} catch (error) {
|
|
2002
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2003
|
+
logger.debug("Codex websocket prewarm failed", {
|
|
2004
|
+
error: errorMessage,
|
|
2005
|
+
provider: codexModel.provider,
|
|
2006
|
+
model: codexModel.id,
|
|
2007
|
+
});
|
|
2008
|
+
}
|
|
2009
|
+
})();
|
|
2010
|
+
}
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
// Start LSP warmup in the background so startup does not block on language server initialization.
|
|
2014
|
+
let lspServers: CreateAgentSessionResult["lspServers"];
|
|
2015
|
+
if (enableLsp && settings.get("lsp.diagnosticsOnWrite")) {
|
|
2016
|
+
lspServers = discoverStartupLspServers(cwd);
|
|
2017
|
+
if (lspServers.length > 0) {
|
|
2018
|
+
void (async () => {
|
|
2019
|
+
try {
|
|
2020
|
+
const result = await logger.time("warmupLspServers", warmupLspServers, cwd);
|
|
2021
|
+
const serversByName = new Map(result.servers.map(server => [server.name, server] as const));
|
|
2022
|
+
for (const server of lspServers ?? []) {
|
|
2023
|
+
const next = serversByName.get(server.name);
|
|
2024
|
+
if (!next) continue;
|
|
2025
|
+
server.status = next.status;
|
|
2026
|
+
server.fileTypes = next.fileTypes;
|
|
2027
|
+
server.error = next.error;
|
|
2028
|
+
}
|
|
2029
|
+
const event: LspStartupEvent = {
|
|
2030
|
+
type: "completed",
|
|
2031
|
+
servers: result.servers,
|
|
2032
|
+
};
|
|
2033
|
+
eventBus.emit(LSP_STARTUP_EVENT_CHANNEL, event);
|
|
2034
|
+
} catch (error) {
|
|
2035
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2036
|
+
logger.warn("LSP server warmup failed", { cwd, error: errorMessage });
|
|
2037
|
+
for (const server of lspServers ?? []) {
|
|
2038
|
+
server.status = "error";
|
|
2039
|
+
server.error = errorMessage;
|
|
2040
|
+
}
|
|
2041
|
+
const event: LspStartupEvent = {
|
|
2042
|
+
type: "failed",
|
|
2043
|
+
error: errorMessage,
|
|
2044
|
+
};
|
|
2045
|
+
eventBus.emit(LSP_STARTUP_EVENT_CHANNEL, event);
|
|
2046
|
+
}
|
|
2047
|
+
})();
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
|
|
2051
|
+
logger.time("startMemoryStartupTask", () =>
|
|
2052
|
+
startMemoryStartupTask({
|
|
2053
|
+
session,
|
|
2054
|
+
settings,
|
|
2055
|
+
modelRegistry,
|
|
2056
|
+
agentDir,
|
|
2057
|
+
taskDepth,
|
|
2058
|
+
}),
|
|
2059
|
+
);
|
|
2060
|
+
|
|
2061
|
+
// Wire MCP manager callbacks to session for reactive tool updates
|
|
2062
|
+
if (mcpManager) {
|
|
2063
|
+
mcpManager.setOnToolsChanged(tools => {
|
|
2064
|
+
void session.refreshMCPTools(tools);
|
|
2065
|
+
});
|
|
2066
|
+
// Wire prompt refresh → rebuild MCP prompt slash commands
|
|
2067
|
+
mcpManager.setOnPromptsChanged(serverName => {
|
|
2068
|
+
const promptCommands = buildMCPPromptCommands(mcpManager);
|
|
2069
|
+
session.setMCPPromptCommands(promptCommands);
|
|
2070
|
+
logger.debug("MCP prompt commands refreshed", { path: `mcp:${serverName}` });
|
|
2071
|
+
});
|
|
2072
|
+
const notificationDebounceTimers = new Map<string, Timer>();
|
|
2073
|
+
const clearDebounceTimers = () => {
|
|
2074
|
+
for (const timer of notificationDebounceTimers.values()) clearTimeout(timer);
|
|
2075
|
+
notificationDebounceTimers.clear();
|
|
2076
|
+
};
|
|
2077
|
+
postmortem.register("mcp-notification-cleanup", clearDebounceTimers);
|
|
2078
|
+
mcpManager.setOnResourcesChanged((serverName, uri) => {
|
|
2079
|
+
logger.debug("MCP resources changed", { path: `mcp:${serverName}`, uri });
|
|
2080
|
+
if (!settings.get("mcp.notifications")) return;
|
|
2081
|
+
const debounceMs = settings.get("mcp.notificationDebounceMs");
|
|
2082
|
+
const key = `${serverName}:${uri}`;
|
|
2083
|
+
const existing = notificationDebounceTimers.get(key);
|
|
2084
|
+
if (existing) clearTimeout(existing);
|
|
2085
|
+
notificationDebounceTimers.set(
|
|
2086
|
+
key,
|
|
2087
|
+
setTimeout(() => {
|
|
2088
|
+
notificationDebounceTimers.delete(key);
|
|
2089
|
+
// Re-check: user may have disabled notifications during the debounce window
|
|
2090
|
+
if (!settings.get("mcp.notifications")) return;
|
|
2091
|
+
void session.followUp(
|
|
2092
|
+
`[MCP notification] Server "${serverName}" reports resource \`${uri}\` was updated. Use read(path="mcp://${uri}") to inspect if relevant.`,
|
|
2093
|
+
);
|
|
2094
|
+
}, debounceMs),
|
|
2095
|
+
);
|
|
2096
|
+
});
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
logger.time("createAgentSession:return");
|
|
2100
|
+
return {
|
|
2101
|
+
session,
|
|
2102
|
+
extensionsResult,
|
|
2103
|
+
setToolUIContext,
|
|
2104
|
+
mcpManager,
|
|
2105
|
+
modelFallbackMessage,
|
|
2106
|
+
lspServers,
|
|
2107
|
+
eventBus,
|
|
2108
|
+
};
|
|
2109
|
+
} catch (error) {
|
|
2110
|
+
try {
|
|
2111
|
+
if (hasSession) {
|
|
2112
|
+
await session.dispose();
|
|
2113
|
+
} else {
|
|
2114
|
+
await disposeKernelSessionsByOwner(pythonKernelOwnerId);
|
|
2115
|
+
}
|
|
2116
|
+
} catch (cleanupError) {
|
|
2117
|
+
logger.warn("Failed to clean up createAgentSession resources after startup error", {
|
|
2118
|
+
error: cleanupError instanceof Error ? cleanupError.message : String(cleanupError),
|
|
2119
|
+
});
|
|
2120
|
+
}
|
|
2121
|
+
throw error;
|
|
2122
|
+
}
|
|
2123
|
+
}
|