// Auto-generated by scripts/generate-docs-index.ts - DO NOT EDIT

export const EMBEDDED_DOC_FILENAMES: readonly string[] = ["ar/configuration/blob-artifact-architecture.md","ar/configuration/config-usage.md","ar/configuration/environment-variables.md","ar/configuration/fs-scan-cache-architecture.md","ar/configuration/hooks.md","ar/configuration/porting-from-pi-mono.md","ar/configuration/rpc.md","ar/configuration/sdk.md","ar/configuration/secrets.md","ar/extensions/extension-loading.md","ar/extensions/extensions.md","ar/extensions/gemini-manifest-extensions.md","ar/extensions/marketplace.md","ar/extensions/plugin-manager-installer-plumbing.md","ar/extensions/rulebook-matching-pipeline.md","ar/extensions/skills.md","ar/index.md","ar/mcp/mcp-config.md","ar/mcp/mcp-protocol-transports.md","ar/mcp/mcp-runtime-lifecycle.md","ar/mcp/mcp-server-tool-authoring.md","ar/natives/natives-addon-loader-runtime.md","ar/natives/natives-architecture.md","ar/natives/natives-binding-contract.md","ar/natives/natives-build-release-debugging.md","ar/natives/natives-media-system-utils.md","ar/natives/natives-rust-task-cancellation.md","ar/natives/natives-shell-pty-process.md","ar/natives/natives-text-search-pipeline.md","ar/natives/porting-to-natives.md","ar/providers/models.md","ar/providers/provider-streaming-internals.md","ar/providers/python-repl.md","ar/runtime-tools/bash-tool-runtime.md","ar/runtime-tools/context-command.md","ar/runtime-tools/custom-tools.md","ar/runtime-tools/notebook-tool-runtime.md","ar/runtime-tools/resolve-tool-runtime.md","ar/runtime-tools/slash-command-internals.md","ar/runtime-tools/task-agent-discovery.md","ar/sessions/compaction.md","ar/sessions/handoff-generation-pipeline.md","ar/sessions/memory.md","ar/sessions/non-compaction-retry-policy.md","ar/sessions/session-operations-export-share-fork-resume.md","ar/sessions/session-switching-and-recent-listing.md","ar/sessions/session-tree-plan.md","ar/sessions/session.md","ar/sessions/ttsr-injection-lifecycle.md","ar/tui/theme.md","ar/tui/tree.md","ar/tui/tui-runtime-internals.md","ar/tui/tui.md","de/configuration/blob-artifact-architecture.md","de/configuration/config-usage.md","de/configuration/environment-variables.md","de/configuration/fs-scan-cache-architecture.md","de/configuration/hooks.md","de/configuration/porting-from-pi-mono.md","de/configuration/rpc.md","de/configuration/sdk.md","de/configuration/secrets.md","de/extensions/extension-loading.md","de/extensions/extensions.md","de/extensions/gemini-manifest-extensions.md","de/extensions/marketplace.md","de/extensions/plugin-manager-installer-plumbing.md","de/extensions/rulebook-matching-pipeline.md","de/extensions/skills.md","de/index.md","de/mcp/mcp-config.md","de/mcp/mcp-protocol-transports.md","de/mcp/mcp-runtime-lifecycle.md","de/mcp/mcp-server-tool-authoring.md","de/natives/natives-addon-loader-runtime.md","de/natives/natives-architecture.md","de/natives/natives-binding-contract.md","de/natives/natives-build-release-debugging.md","de/natives/natives-media-system-utils.md","de/natives/natives-rust-task-cancellation.md","de/natives/natives-shell-pty-process.md","de/natives/natives-text-search-pipeline.md","de/natives/porting-to-natives.md","de/providers/models.md","de/providers/provider-streaming-internals.md","de/providers/python-repl.md","de/runtime-tools/bash-tool-runtime.md","de/runtime-tools/context-command.md","de/runtime-tools/custom-tools.md","de/runtime-tools/notebook-tool-runtime.md","de/runtime-tools/resolve-tool-runtime.md","de/runtime-tools/slash-command-internals.md","de/runtime-tools/task-agent-discovery.md","de/sessions/compaction.md","de/sessions/handoff-generation-pipeline.md","de/sessions/memory.md","de/sessions/non-compaction-retry-policy.md","de/sessions/session-operations-export-share-fork-resume.md","de/sessions/session-switching-and-recent-listing.md","de/sessions/session-tree-plan.md","de/sessions/session.md","de/sessions/ttsr-injection-lifecycle.md","de/tui/theme.md","de/tui/tree.md","de/tui/tui-runtime-internals.md","de/tui/tui.md","en/configuration/blob-artifact-architecture.md","en/configuration/config-usage.md","en/configuration/environment-variables.md","en/configuration/fs-scan-cache-architecture.md","en/configuration/hooks.md","en/configuration/porting-from-pi-mono.md","en/configuration/rpc.md","en/configuration/sdk.md","en/configuration/secrets.md","en/extensions/extension-loading.md","en/extensions/extensions.md","en/extensions/gemini-manifest-extensions.md","en/extensions/marketplace.md","en/extensions/plugin-manager-installer-plumbing.md","en/extensions/rulebook-matching-pipeline.md","en/extensions/skills.md","en/index.md","en/mcp/mcp-config.md","en/mcp/mcp-protocol-transports.md","en/mcp/mcp-runtime-lifecycle.md","en/mcp/mcp-server-tool-authoring.md","en/natives/natives-addon-loader-runtime.md","en/natives/natives-architecture.md","en/natives/natives-binding-contract.md","en/natives/natives-build-release-debugging.md","en/natives/natives-media-system-utils.md","en/natives/natives-rust-task-cancellation.md","en/natives/natives-shell-pty-process.md","en/natives/natives-text-search-pipeline.md","en/natives/porting-to-natives.md","en/providers/models.md","en/providers/provider-streaming-internals.md","en/providers/python-repl.md","en/runtime-tools/bash-tool-runtime.md","en/runtime-tools/context-command.md","en/runtime-tools/custom-tools.md","en/runtime-tools/notebook-tool-runtime.md","en/runtime-tools/resolve-tool-runtime.md","en/runtime-tools/slash-command-internals.md","en/runtime-tools/task-agent-discovery.md","en/sessions/compaction.md","en/sessions/handoff-generation-pipeline.md","en/sessions/memory.md","en/sessions/non-compaction-retry-policy.md","en/sessions/session-operations-export-share-fork-resume.md","en/sessions/session-switching-and-recent-listing.md","en/sessions/session-tree-plan.md","en/sessions/session.md","en/sessions/ttsr-injection-lifecycle.md","en/tui/theme.md","en/tui/tree.md","en/tui/tui-runtime-internals.md","en/tui/tui.md","es/configuration/blob-artifact-architecture.md","es/configuration/config-usage.md","es/configuration/environment-variables.md","es/configuration/fs-scan-cache-architecture.md","es/configuration/hooks.md","es/configuration/porting-from-pi-mono.md","es/configuration/rpc.md","es/configuration/sdk.md","es/configuration/secrets.md","es/extensions/extension-loading.md","es/extensions/extensions.md","es/extensions/gemini-manifest-extensions.md","es/extensions/marketplace.md","es/extensions/plugin-manager-installer-plumbing.md","es/extensions/rulebook-matching-pipeline.md","es/extensions/skills.md","es/index.md","es/mcp/mcp-config.md","es/mcp/mcp-protocol-transports.md","es/mcp/mcp-runtime-lifecycle.md","es/mcp/mcp-server-tool-authoring.md","es/natives/natives-addon-loader-runtime.md","es/natives/natives-architecture.md","es/natives/natives-binding-contract.md","es/natives/natives-build-release-debugging.md","es/natives/natives-media-system-utils.md","es/natives/natives-rust-task-cancellation.md","es/natives/natives-shell-pty-process.md","es/natives/natives-text-search-pipeline.md","es/natives/porting-to-natives.md","es/providers/models.md","es/providers/provider-streaming-internals.md","es/providers/python-repl.md","es/runtime-tools/bash-tool-runtime.md","es/runtime-tools/context-command.md","es/runtime-tools/custom-tools.md","es/runtime-tools/notebook-tool-runtime.md","es/runtime-tools/resolve-tool-runtime.md","es/runtime-tools/slash-command-internals.md","es/runtime-tools/task-agent-discovery.md","es/sessions/compaction.md","es/sessions/handoff-generation-pipeline.md","es/sessions/memory.md","es/sessions/non-compaction-retry-policy.md","es/sessions/session-operations-export-share-fork-resume.md","es/sessions/session-switching-and-recent-listing.md","es/sessions/session-tree-plan.md","es/sessions/session.md","es/sessions/ttsr-injection-lifecycle.md","es/tui/theme.md","es/tui/tree.md","es/tui/tui-runtime-internals.md","es/tui/tui.md","fr/configuration/blob-artifact-architecture.md","fr/configuration/config-usage.md","fr/configuration/environment-variables.md","fr/configuration/fs-scan-cache-architecture.md","fr/configuration/hooks.md","fr/configuration/porting-from-pi-mono.md","fr/configuration/rpc.md","fr/configuration/sdk.md","fr/configuration/secrets.md","fr/extensions/extension-loading.md","fr/extensions/extensions.md","fr/extensions/gemini-manifest-extensions.md","fr/extensions/marketplace.md","fr/extensions/plugin-manager-installer-plumbing.md","fr/extensions/rulebook-matching-pipeline.md","fr/extensions/skills.md","fr/index.md","fr/mcp/mcp-config.md","fr/mcp/mcp-protocol-transports.md","fr/mcp/mcp-runtime-lifecycle.md","fr/mcp/mcp-server-tool-authoring.md","fr/natives/natives-addon-loader-runtime.md","fr/natives/natives-architecture.md","fr/natives/natives-binding-contract.md","fr/natives/natives-build-release-debugging.md","fr/natives/natives-media-system-utils.md","fr/natives/natives-rust-task-cancellation.md","fr/natives/natives-shell-pty-process.md","fr/natives/natives-text-search-pipeline.md","fr/natives/porting-to-natives.md","fr/providers/models.md","fr/providers/provider-streaming-internals.md","fr/providers/python-repl.md","fr/runtime-tools/bash-tool-runtime.md","fr/runtime-tools/context-command.md","fr/runtime-tools/custom-tools.md","fr/runtime-tools/notebook-tool-runtime.md","fr/runtime-tools/resolve-tool-runtime.md","fr/runtime-tools/slash-command-internals.md","fr/runtime-tools/task-agent-discovery.md","fr/sessions/compaction.md","fr/sessions/handoff-generation-pipeline.md","fr/sessions/memory.md","fr/sessions/non-compaction-retry-policy.md","fr/sessions/session-operations-export-share-fork-resume.md","fr/sessions/session-switching-and-recent-listing.md","fr/sessions/session-tree-plan.md","fr/sessions/session.md","fr/sessions/ttsr-injection-lifecycle.md","fr/tui/theme.md","fr/tui/tree.md","fr/tui/tui-runtime-internals.md","fr/tui/tui.md","hi/configuration/blob-artifact-architecture.md","hi/configuration/config-usage.md","hi/configuration/environment-variables.md","hi/configuration/fs-scan-cache-architecture.md","hi/configuration/hooks.md","hi/configuration/porting-from-pi-mono.md","hi/configuration/rpc.md","hi/configuration/sdk.md","hi/configuration/secrets.md","hi/extensions/extension-loading.md","hi/extensions/extensions.md","hi/extensions/gemini-manifest-extensions.md","hi/extensions/marketplace.md","hi/extensions/plugin-manager-installer-plumbing.md","hi/extensions/rulebook-matching-pipeline.md","hi/extensions/skills.md","hi/index.md","hi/mcp/mcp-config.md","hi/mcp/mcp-protocol-transports.md","hi/mcp/mcp-runtime-lifecycle.md","hi/mcp/mcp-server-tool-authoring.md","hi/natives/natives-addon-loader-runtime.md","hi/natives/natives-architecture.md","hi/natives/natives-binding-contract.md","hi/natives/natives-build-release-debugging.md","hi/natives/natives-media-system-utils.md","hi/natives/natives-rust-task-cancellation.md","hi/natives/natives-shell-pty-process.md","hi/natives/natives-text-search-pipeline.md","hi/natives/porting-to-natives.md","hi/providers/models.md","hi/providers/provider-streaming-internals.md","hi/providers/python-repl.md","hi/runtime-tools/bash-tool-runtime.md","hi/runtime-tools/context-command.md","hi/runtime-tools/custom-tools.md","hi/runtime-tools/notebook-tool-runtime.md","hi/runtime-tools/resolve-tool-runtime.md","hi/runtime-tools/slash-command-internals.md","hi/runtime-tools/task-agent-discovery.md","hi/sessions/compaction.md","hi/sessions/handoff-generation-pipeline.md","hi/sessions/memory.md","hi/sessions/non-compaction-retry-policy.md","hi/sessions/session-operations-export-share-fork-resume.md","hi/sessions/session-switching-and-recent-listing.md","hi/sessions/session-tree-plan.md","hi/sessions/session.md","hi/sessions/ttsr-injection-lifecycle.md","hi/tui/theme.md","hi/tui/tree.md","hi/tui/tui-runtime-internals.md","hi/tui/tui.md","it/configuration/blob-artifact-architecture.md","it/configuration/config-usage.md","it/configuration/environment-variables.md","it/configuration/fs-scan-cache-architecture.md","it/configuration/hooks.md","it/configuration/porting-from-pi-mono.md","it/configuration/rpc.md","it/configuration/sdk.md","it/configuration/secrets.md","it/extensions/extension-loading.md","it/extensions/extensions.md","it/extensions/gemini-manifest-extensions.md","it/extensions/marketplace.md","it/extensions/plugin-manager-installer-plumbing.md","it/extensions/rulebook-matching-pipeline.md","it/extensions/skills.md","it/index.md","it/mcp/mcp-config.md","it/mcp/mcp-protocol-transports.md","it/mcp/mcp-runtime-lifecycle.md","it/mcp/mcp-server-tool-authoring.md","it/natives/natives-addon-loader-runtime.md","it/natives/natives-architecture.md","it/natives/natives-binding-contract.md","it/natives/natives-build-release-debugging.md","it/natives/natives-media-system-utils.md","it/natives/natives-rust-task-cancellation.md","it/natives/natives-shell-pty-process.md","it/natives/natives-text-search-pipeline.md","it/natives/porting-to-natives.md","it/providers/models.md","it/providers/provider-streaming-internals.md","it/providers/python-repl.md","it/runtime-tools/bash-tool-runtime.md","it/runtime-tools/context-command.md","it/runtime-tools/custom-tools.md","it/runtime-tools/notebook-tool-runtime.md","it/runtime-tools/resolve-tool-runtime.md","it/runtime-tools/slash-command-internals.md","it/runtime-tools/task-agent-discovery.md","it/sessions/compaction.md","it/sessions/handoff-generation-pipeline.md","it/sessions/memory.md","it/sessions/non-compaction-retry-policy.md","it/sessions/session-operations-export-share-fork-resume.md","it/sessions/session-switching-and-recent-listing.md","it/sessions/session-tree-plan.md","it/sessions/session.md","it/sessions/ttsr-injection-lifecycle.md","it/tui/theme.md","it/tui/tree.md","it/tui/tui-runtime-internals.md","it/tui/tui.md","ja/configuration/blob-artifact-architecture.md","ja/configuration/config-usage.md","ja/configuration/environment-variables.md","ja/configuration/fs-scan-cache-architecture.md","ja/configuration/hooks.md","ja/configuration/porting-from-pi-mono.md","ja/configuration/rpc.md","ja/configuration/sdk.md","ja/configuration/secrets.md","ja/extensions/extension-loading.md","ja/extensions/extensions.md","ja/extensions/gemini-manifest-extensions.md","ja/extensions/marketplace.md","ja/extensions/plugin-manager-installer-plumbing.md","ja/extensions/rulebook-matching-pipeline.md","ja/extensions/skills.md","ja/index.md","ja/mcp/mcp-config.md","ja/mcp/mcp-protocol-transports.md","ja/mcp/mcp-runtime-lifecycle.md","ja/mcp/mcp-server-tool-authoring.md","ja/natives/natives-addon-loader-runtime.md","ja/natives/natives-architecture.md","ja/natives/natives-binding-contract.md","ja/natives/natives-build-release-debugging.md","ja/natives/natives-media-system-utils.md","ja/natives/natives-rust-task-cancellation.md","ja/natives/natives-shell-pty-process.md","ja/natives/natives-text-search-pipeline.md","ja/natives/porting-to-natives.md","ja/providers/models.md","ja/providers/provider-streaming-internals.md","ja/providers/python-repl.md","ja/runtime-tools/bash-tool-runtime.md","ja/runtime-tools/context-command.md","ja/runtime-tools/custom-tools.md","ja/runtime-tools/notebook-tool-runtime.md","ja/runtime-tools/resolve-tool-runtime.md","ja/runtime-tools/slash-command-internals.md","ja/runtime-tools/task-agent-discovery.md","ja/sessions/compaction.md","ja/sessions/handoff-generation-pipeline.md","ja/sessions/memory.md","ja/sessions/non-compaction-retry-policy.md","ja/sessions/session-operations-export-share-fork-resume.md","ja/sessions/session-switching-and-recent-listing.md","ja/sessions/session-tree-plan.md","ja/sessions/session.md","ja/sessions/ttsr-injection-lifecycle.md","ja/tui/theme.md","ja/tui/tree.md","ja/tui/tui-runtime-internals.md","ja/tui/tui.md","ko/configuration/blob-artifact-architecture.md","ko/configuration/config-usage.md","ko/configuration/environment-variables.md","ko/configuration/fs-scan-cache-architecture.md","ko/configuration/hooks.md","ko/configuration/porting-from-pi-mono.md","ko/configuration/rpc.md","ko/configuration/sdk.md","ko/configuration/secrets.md","ko/extensions/extension-loading.md","ko/extensions/extensions.md","ko/extensions/gemini-manifest-extensions.md","ko/extensions/marketplace.md","ko/extensions/plugin-manager-installer-plumbing.md","ko/extensions/rulebook-matching-pipeline.md","ko/extensions/skills.md","ko/index.md","ko/mcp/mcp-config.md","ko/mcp/mcp-protocol-transports.md","ko/mcp/mcp-runtime-lifecycle.md","ko/mcp/mcp-server-tool-authoring.md","ko/natives/natives-addon-loader-runtime.md","ko/natives/natives-architecture.md","ko/natives/natives-binding-contract.md","ko/natives/natives-build-release-debugging.md","ko/natives/natives-media-system-utils.md","ko/natives/natives-rust-task-cancellation.md","ko/natives/natives-shell-pty-process.md","ko/natives/natives-text-search-pipeline.md","ko/natives/porting-to-natives.md","ko/providers/models.md","ko/providers/provider-streaming-internals.md","ko/providers/python-repl.md","ko/runtime-tools/bash-tool-runtime.md","ko/runtime-tools/context-command.md","ko/runtime-tools/custom-tools.md","ko/runtime-tools/notebook-tool-runtime.md","ko/runtime-tools/resolve-tool-runtime.md","ko/runtime-tools/slash-command-internals.md","ko/runtime-tools/task-agent-discovery.md","ko/sessions/compaction.md","ko/sessions/handoff-generation-pipeline.md","ko/sessions/memory.md","ko/sessions/non-compaction-retry-policy.md","ko/sessions/session-operations-export-share-fork-resume.md","ko/sessions/session-switching-and-recent-listing.md","ko/sessions/session-tree-plan.md","ko/sessions/session.md","ko/sessions/ttsr-injection-lifecycle.md","ko/tui/theme.md","ko/tui/tree.md","ko/tui/tui-runtime-internals.md","ko/tui/tui.md","pt-br/configuration/blob-artifact-architecture.md","pt-br/configuration/config-usage.md","pt-br/configuration/environment-variables.md","pt-br/configuration/fs-scan-cache-architecture.md","pt-br/configuration/hooks.md","pt-br/configuration/porting-from-pi-mono.md","pt-br/configuration/rpc.md","pt-br/configuration/sdk.md","pt-br/configuration/secrets.md","pt-br/extensions/extension-loading.md","pt-br/extensions/extensions.md","pt-br/extensions/gemini-manifest-extensions.md","pt-br/extensions/marketplace.md","pt-br/extensions/plugin-manager-installer-plumbing.md","pt-br/extensions/rulebook-matching-pipeline.md","pt-br/extensions/skills.md","pt-br/index.md","pt-br/mcp/mcp-config.md","pt-br/mcp/mcp-protocol-transports.md","pt-br/mcp/mcp-runtime-lifecycle.md","pt-br/mcp/mcp-server-tool-authoring.md","pt-br/natives/natives-addon-loader-runtime.md","pt-br/natives/natives-architecture.md","pt-br/natives/natives-binding-contract.md","pt-br/natives/natives-build-release-debugging.md","pt-br/natives/natives-media-system-utils.md","pt-br/natives/natives-rust-task-cancellation.md","pt-br/natives/natives-shell-pty-process.md","pt-br/natives/natives-text-search-pipeline.md","pt-br/natives/porting-to-natives.md","pt-br/providers/models.md","pt-br/providers/provider-streaming-internals.md","pt-br/providers/python-repl.md","pt-br/runtime-tools/bash-tool-runtime.md","pt-br/runtime-tools/context-command.md","pt-br/runtime-tools/custom-tools.md","pt-br/runtime-tools/notebook-tool-runtime.md","pt-br/runtime-tools/resolve-tool-runtime.md","pt-br/runtime-tools/slash-command-internals.md","pt-br/runtime-tools/task-agent-discovery.md","pt-br/sessions/compaction.md","pt-br/sessions/handoff-generation-pipeline.md","pt-br/sessions/memory.md","pt-br/sessions/non-compaction-retry-policy.md","pt-br/sessions/session-operations-export-share-fork-resume.md","pt-br/sessions/session-switching-and-recent-listing.md","pt-br/sessions/session-tree-plan.md","pt-br/sessions/session.md","pt-br/sessions/ttsr-injection-lifecycle.md","pt-br/tui/theme.md","pt-br/tui/tree.md","pt-br/tui/tui-runtime-internals.md","pt-br/tui/tui.md","th/configuration/blob-artifact-architecture.md","th/configuration/config-usage.md","th/configuration/environment-variables.md","th/configuration/fs-scan-cache-architecture.md","th/configuration/hooks.md","th/configuration/porting-from-pi-mono.md","th/configuration/rpc.md","th/configuration/sdk.md","th/configuration/secrets.md","th/extensions/extension-loading.md","th/extensions/extensions.md","th/extensions/gemini-manifest-extensions.md","th/extensions/marketplace.md","th/extensions/plugin-manager-installer-plumbing.md","th/extensions/rulebook-matching-pipeline.md","th/extensions/skills.md","th/index.md","th/mcp/mcp-config.md","th/mcp/mcp-protocol-transports.md","th/mcp/mcp-runtime-lifecycle.md","th/mcp/mcp-server-tool-authoring.md","th/natives/natives-addon-loader-runtime.md","th/natives/natives-architecture.md","th/natives/natives-binding-contract.md","th/natives/natives-build-release-debugging.md","th/natives/natives-media-system-utils.md","th/natives/natives-rust-task-cancellation.md","th/natives/natives-shell-pty-process.md","th/natives/natives-text-search-pipeline.md","th/natives/porting-to-natives.md","th/providers/models.md","th/providers/provider-streaming-internals.md","th/providers/python-repl.md","th/runtime-tools/bash-tool-runtime.md","th/runtime-tools/context-command.md","th/runtime-tools/custom-tools.md","th/runtime-tools/notebook-tool-runtime.md","th/runtime-tools/resolve-tool-runtime.md","th/runtime-tools/slash-command-internals.md","th/runtime-tools/task-agent-discovery.md","th/sessions/compaction.md","th/sessions/handoff-generation-pipeline.md","th/sessions/memory.md","th/sessions/non-compaction-retry-policy.md","th/sessions/session-operations-export-share-fork-resume.md","th/sessions/session-switching-and-recent-listing.md","th/sessions/session-tree-plan.md","th/sessions/session.md","th/sessions/ttsr-injection-lifecycle.md","th/tui/theme.md","th/tui/tree.md","th/tui/tui-runtime-internals.md","th/tui/tui.md","zh-cn/configuration/blob-artifact-architecture.md","zh-cn/configuration/config-usage.md","zh-cn/configuration/environment-variables.md","zh-cn/configuration/fs-scan-cache-architecture.md","zh-cn/configuration/hooks.md","zh-cn/configuration/porting-from-pi-mono.md","zh-cn/configuration/rpc.md","zh-cn/configuration/sdk.md","zh-cn/configuration/secrets.md","zh-cn/extensions/extension-loading.md","zh-cn/extensions/extensions.md","zh-cn/extensions/gemini-manifest-extensions.md","zh-cn/extensions/marketplace.md","zh-cn/extensions/plugin-manager-installer-plumbing.md","zh-cn/extensions/rulebook-matching-pipeline.md","zh-cn/extensions/skills.md","zh-cn/index.md","zh-cn/mcp/mcp-config.md","zh-cn/mcp/mcp-protocol-transports.md","zh-cn/mcp/mcp-runtime-lifecycle.md","zh-cn/mcp/mcp-server-tool-authoring.md","zh-cn/natives/natives-addon-loader-runtime.md","zh-cn/natives/natives-architecture.md","zh-cn/natives/natives-binding-contract.md","zh-cn/natives/natives-build-release-debugging.md","zh-cn/natives/natives-media-system-utils.md","zh-cn/natives/natives-rust-task-cancellation.md","zh-cn/natives/natives-shell-pty-process.md","zh-cn/natives/natives-text-search-pipeline.md","zh-cn/natives/porting-to-natives.md","zh-cn/providers/models.md","zh-cn/providers/provider-streaming-internals.md","zh-cn/providers/python-repl.md","zh-cn/runtime-tools/bash-tool-runtime.md","zh-cn/runtime-tools/context-command.md","zh-cn/runtime-tools/custom-tools.md","zh-cn/runtime-tools/notebook-tool-runtime.md","zh-cn/runtime-tools/resolve-tool-runtime.md","zh-cn/runtime-tools/slash-command-internals.md","zh-cn/runtime-tools/task-agent-discovery.md","zh-cn/sessions/compaction.md","zh-cn/sessions/handoff-generation-pipeline.md","zh-cn/sessions/memory.md","zh-cn/sessions/non-compaction-retry-policy.md","zh-cn/sessions/session-operations-export-share-fork-resume.md","zh-cn/sessions/session-switching-and-recent-listing.md","zh-cn/sessions/session-tree-plan.md","zh-cn/sessions/session.md","zh-cn/sessions/ttsr-injection-lifecycle.md","zh-cn/tui/theme.md","zh-cn/tui/tree.md","zh-cn/tui/tui-runtime-internals.md","zh-cn/tui/tui.md","zh-tw/configuration/blob-artifact-architecture.md","zh-tw/configuration/config-usage.md","zh-tw/configuration/environment-variables.md","zh-tw/configuration/fs-scan-cache-architecture.md","zh-tw/configuration/hooks.md","zh-tw/configuration/porting-from-pi-mono.md","zh-tw/configuration/rpc.md","zh-tw/configuration/sdk.md","zh-tw/configuration/secrets.md","zh-tw/extensions/extension-loading.md","zh-tw/extensions/extensions.md","zh-tw/extensions/gemini-manifest-extensions.md","zh-tw/extensions/marketplace.md","zh-tw/extensions/plugin-manager-installer-plumbing.md","zh-tw/extensions/rulebook-matching-pipeline.md","zh-tw/extensions/skills.md","zh-tw/index.md","zh-tw/mcp/mcp-config.md","zh-tw/mcp/mcp-protocol-transports.md","zh-tw/mcp/mcp-runtime-lifecycle.md","zh-tw/mcp/mcp-server-tool-authoring.md","zh-tw/natives/natives-addon-loader-runtime.md","zh-tw/natives/natives-architecture.md","zh-tw/natives/natives-binding-contract.md","zh-tw/natives/natives-build-release-debugging.md","zh-tw/natives/natives-media-system-utils.md","zh-tw/natives/natives-rust-task-cancellation.md","zh-tw/natives/natives-shell-pty-process.md","zh-tw/natives/natives-text-search-pipeline.md","zh-tw/natives/porting-to-natives.md","zh-tw/providers/models.md","zh-tw/providers/provider-streaming-internals.md","zh-tw/providers/python-repl.md","zh-tw/runtime-tools/bash-tool-runtime.md","zh-tw/runtime-tools/context-command.md","zh-tw/runtime-tools/custom-tools.md","zh-tw/runtime-tools/notebook-tool-runtime.md","zh-tw/runtime-tools/resolve-tool-runtime.md","zh-tw/runtime-tools/slash-command-internals.md","zh-tw/runtime-tools/task-agent-discovery.md","zh-tw/sessions/compaction.md","zh-tw/sessions/handoff-generation-pipeline.md","zh-tw/sessions/memory.md","zh-tw/sessions/non-compaction-retry-policy.md","zh-tw/sessions/session-operations-export-share-fork-resume.md","zh-tw/sessions/session-switching-and-recent-listing.md","zh-tw/sessions/session-tree-plan.md","zh-tw/sessions/session.md","zh-tw/sessions/ttsr-injection-lifecycle.md","zh-tw/tui/theme.md","zh-tw/tui/tree.md","zh-tw/tui/tui-runtime-internals.md","zh-tw/tui/tui.md"];

export const EMBEDDED_DOCS: Readonly<Record<string, string>> = {
	"ar/configuration/blob-artifact-architecture.md": "---\ntitle: بنية تخزين البيانات الثنائية والقطع الأثرية\ndescription: >-\n  مخزن البيانات الثنائية القابل للعنونة بالمحتوى وسجل القطع الأثرية لوسائط\n  الجلسة، ولقطات الشاشة، ومخرجات الأدوات.\nsidebar:\n  order: 7\n  label: تخزين البيانات الثنائية والقطع الأثرية\ni18n:\n  sourceHash: 70d255f48d5b\n  translator: machine\n---\n\n# بنية تخزين البيانات الثنائية والقطع الأثرية\n\nيصف هذا المستند كيفية قيام وكيل الترميز بتخزين الحمولات الكبيرة/الثنائية خارج جلسة JSONL، وكيفية استمرار مخرجات الأدوات المقتطعة، وكيفية تحليل عناوين URL الداخلية (`artifact://`، `agent://`) إلى البيانات المخزنة.\n\n## سبب وجود نظامَي تخزين\n\nيستخدم وقت التشغيل آليتَين مختلفتَين للاستمرارية لأشكال بيانات مختلفة:\n\n- **البيانات الثنائية المعنونة بالمحتوى** (`blob:sha256:<hash>`): تخزين عام موجّه للثنائيات يُستخدم لاستخراج حمولات base64 للصور الكبيرة من إدخالات الجلسة المستمرة.\n- **القطع الأثرية محددة النطاق بالجلسة** (ملفات تحت `<sessionFile-without-.jsonl>/`): ملفات نصية لكل جلسة تُستخدم للمخرجات الكاملة للأدوات ومخرجات الوكلاء الفرعيين.\n\nهما مفصولان عمداً:\n\n- يُحسّن تخزين البيانات الثنائية إلغاء التكرار والمراجع الثابتة بواسطة تجزئة المحتوى،\n- يُحسّن تخزين القطع الأثرية أدوات الجلسة للإلحاق فقط واسترجاع المستخدم/الأداة بواسطة معرفات محلية.\n\n## حدود التخزين والتخطيط على القرص\n\n## حد مخزن البيانات الثنائية (عام)\n\nينشئ `SessionManager` الكائن `BlobStore(getBlobsDir())`، لذا تقع ملفات البيانات الثنائية في دليل بيانات ثنائية مشترك عام (وليس في مجلد جلسة).\n\nتسمية ملفات البيانات الثنائية:\n\n- مسار الملف: `<blobsDir>/<sha256-hex>`\n- بدون امتداد\n- سلسلة المرجع المخزنة في الإدخالات: `blob:sha256:<sha256-hex>`\n\nالآثار المترتبة:\n\n- يتحلل نفس المحتوى الثنائي عبر الجلسات إلى نفس التجزئة/المسار،\n- الكتابات تكون مكافئة على مستوى المحتوى،\n- يمكن أن تتجاوز البيانات الثنائية أي ملف جلسة فردي.\n\n## حد القطع الأثرية (محلي للجلسة)\n\nيشتق `ArtifactManager` دليل القطع الأثرية من مسار ملف الجلسة:\n\n- ملف الجلسة: `.../<timestamp>_<sessionId>.jsonl`\n- دليل القطع الأثرية: `.../<timestamp>_<sessionId>/` (احذف `.jsonl`)\n\nتشترك أنواع القطع الأثرية في هذا الدليل:\n\n- ملفات مخرجات الأدوات المقتطعة: `<numericId>.<toolType>.log` (لـ `artifact://`)\n- ملفات مخرجات الوكيل الفرعي: `<outputId>.md` (لـ `agent://`)\n\n## مخططات تخصيص المعرف والاسم\n\n## معرفات البيانات الثنائية: تجزئة المحتوى\n\nتحسب `BlobStore.put()` SHA-256 على البايتات الثنائية الخام وتُعيد:\n\n- `hash`: ملخص سداسي عشري،\n- `path`: `<blobsDir>/<hash>`،\n- `ref`: `blob:sha256:<hash>`.\n\nلا يُستخدم عداد محلي للجلسة.\n\n## معرفات القطع الأثرية: عدد صحيح تصاعدي محلي للجلسة\n\nيفحص `ArtifactManager` ملفات القطع الأثرية `*.log` الموجودة عند الاستخدام الأول لإيجاد الحد الأقصى للمعرف الرقمي الموجود ويضبط `nextId = max + 1`.\n\nسلوك التخصيص:\n\n- تنسيق الملف: `{id}.{toolType}.log`\n- المعرفات هي سلاسل متسلسلة (`\"0\"`، `\"1\"`، ...)\n- لا يُكتب فوق القطع الأثرية الموجودة عند الاستئناف لأن الفحص يحدث قبل التخصيص.\n\nإذا كان دليل القطع الأثرية مفقوداً، يُعيد الفحص قائمة فارغة ويبدأ التخصيص من `0`.\n\n## معرفات مخرجات الوكيل (`agent://`)\n\nيُخصص `AgentOutputManager` المعرفات لمخرجات الوكيل الفرعي بصيغة `<index>-<requestedId>` (مدمجة اختيارياً تحت بادئة الأصل، مثل `0-Parent.1-Child`). يفحص ملفات `.md` الموجودة عند التهيئة لمواصلة الفهرس التالي عند الاستئناف.\n\n## تدفق بيانات الاستمرارية\n\n## 1) مسار إعادة كتابة استمرارية إدخال الجلسة\n\nقبل كتابة إدخالات الجلسة (`#rewriteFile` / الاستمرارية التزايدية)، يستدعي `SessionManager` الدالة `prepareEntryForPersistence()` (عبر `truncateForPersistence`).\n\nالسلوكيات الرئيسية:\n\n1. **اقتطاع السلاسل الكبيرة**: تُقطع السلاسل الضخمة ويُضاف إليها اللاحقة `\"[Session persistence truncated large content]\"`.\n2. **تجريد الحقول العابرة**: تُحذف `partialJson` و`jsonlEvents` من الإدخالات المستمرة.\n3. **استخراج الصور إلى بيانات ثنائية**:\n   - يُطبق فقط على كتل الصور في مصفوفات `content`،\n   - فقط عندما لا تكون `data` مرجع بيانات ثنائية بالفعل،\n   - فقط عندما يبلغ طول base64 الحد الأدنى (`BLOB_EXTERNALIZE_THRESHOLD = 1024`)،\n   - يستبدل base64 المضمّن بـ `blob:sha256:<hash>`.\n\nيُبقي هذا جلسة JSONL مضغوطة مع الحفاظ على إمكانية الاسترداد.\n\n## 2) مسار إعادة ترطيب تحميل الجلسة\n\nعند فتح جلسة (`setSessionFile`)، وبعد عمليات الترحيل، يُشغّل `SessionManager` الدالة `resolveBlobRefsInEntries()`.\n\nلكل كتلة صورة رسالة/رسالة مخصصة تحتوي على `blob:sha256:<hash>`:\n\n- يقرأ بايتات البيانات الثنائية من مخزن البيانات الثنائية،\n- يحوّل البايتات مجدداً إلى base64،\n- يعدّل الإدخال في الذاكرة لتضمين base64 لمستهلكي وقت التشغيل.\n\nإذا كانت البيانات الثنائية مفقودة:\n\n- تسجّل `resolveImageData()` تحذيراً،\n- تُعيد سلسلة المرجع الأصلية دون تغيير،\n- يستمر التحميل (بدون عطل قسري).\n\n## 3) مسار تسرب/اقتطاع مخرجات الأداة\n\nيُشغّل `OutputSink` المخرجات المتدفقة في bash/python/ssh والمنفذين المرتبطين.\n\nالسلوك:\n\n1. يُعقّم كل جزء ويُلحق بمخزن الذيل في الذاكرة.\n2. عندما تتجاوز البايتات في الذاكرة عتبة التسرب (`DEFAULT_MAX_BYTES`، 50KB)، يُعلّم الحوض المخرجات على أنها مقتطعة.\n3. إذا كان مسار قطعة أثرية متاحاً، يفتح الحوض كاتب ملفات ويكتب:\n   - المحتوى المؤقت الموجود مرة واحدة،\n   - جميع الأجزاء اللاحقة.\n4. يُقلَّص المخزن في الذاكرة دائماً إلى نافذة الذيل للعرض.\n5. تُعيد `dump()` ملخصاً يتضمن `artifactId` فقط عند إنشاء حوض الملفات بنجاح.\n\nالتأثير العملي:\n\n- يُظهر واجهة المستخدم/إعادة أداة الذيل المقتطع،\n- يُحفظ المخرج الكامل في ملف قطعة أثرية ويُشار إليه بـ `artifact://<id>`.\n\nإذا فشل إنشاء حوض الملفات (خطأ إدخال/إخراج، مسار مفقود، إلخ)، يرجع الحوض بصمت إلى الاقتطاع في الذاكرة فقط؛ ولا يُستمر في حفظ المخرج الكامل.\n\n## نموذج وصول URL\n\n## مراجع `blob:`\n\n`blob:sha256:<hash>` هو مرجع استمرارية داخل حمولات إدخال الجلسة، وليس مخطط URL داخلي يعالجه الموجّه. يتم التحليل بواسطة `SessionManager` أثناء تحميل الجلسة.\n\n## `artifact://<id>`\n\nيعالجه `ArtifactProtocolHandler`:\n\n- يتطلب دليل قطع أثرية جلسة نشطة،\n- يجب أن يكون المعرف رقمياً،\n- يُحلَّل بمطابقة بادئة اسم الملف `<id>.`،\n- يُعيد نصاً خاماً (`text/plain`) من ملف `.log` المطابق،\n- عند الغياب، يتضمن الخطأ قائمة بمعرفات القطع الأثرية المتاحة.\n\nسلوك الدليل المفقود:\n\n- إذا كان دليل القطع الأثرية غير موجود، يُطلق استثناء `No artifacts directory found`.\n\n## `agent://<id>`\n\nيعالجه `AgentProtocolHandler` عبر `<artifactsDir>/<id>.md`:\n\n- يُعيد النموذج البسيط نص markdown،\n- يُجري نموذجا `/path` أو `?q=` استخراج JSON،\n- لا يمكن الجمع بين استخراج المسار والاستعلام،\n- إذا طُلب الاستخراج، يجب أن يُحلَّل محتوى الملف بصيغة JSON.\n\nسلوك الدليل المفقود:\n\n- يُطلق استثناء `No artifacts directory found`.\n\nسلوك المخرج المفقود:\n\n- يُطلق استثناء `Not found: <id>` مع المعرفات المتاحة من ملفات `.md` الموجودة.\n\nتكامل أداة القراءة:\n\n- تدعم `read` ترقيم الصفحات بالإزاحة/الحد لقراءات URL الداخلية غير الاستخراجية،\n- ترفض `offset/limit` عند استخدام استخراج `agent://`.\n\n## دلالات الاستئناف والتفرع والنقل\n\n## الاستئناف\n\n- يفحص `ArtifactManager` ملفات `{id}.*.log` الموجودة عند أول تخصيص ويواصل الترقيم.\n- يفحص `AgentOutputManager` معرفات مخرجات `.md` الموجودة ويواصل الترقيم.\n- يُعيد `SessionManager` ترطيب مراجع البيانات الثنائية إلى base64 عند التحميل.\n\n## التفرع\n\nيُنشئ `SessionManager.fork()` ملف جلسة جديد بمعرف جلسة جديد وارتباط `parentSession`، ثم يُعيد مسارات الملفات القديمة/الجديدة. تُعالج نسخة القطع الأثرية بواسطة `AgentSession.fork()`:\n\n- يحاول النسخ التكراري لدليل القطع الأثرية القديم إلى دليل القطع الأثرية الجديد،\n- يُتحمّل الدليل القديم المفقود،\n- تُسجَّل أخطاء النسخ غير ENOENT كتحذيرات ويكتمل التفرع على أي حال.\n\nآثار المعرفات بعد التفرع:\n\n- إذا نجح النسخ، تواصل عدادات القطع الأثرية في الجلسة الجديدة بعد الحد الأقصى للمعرف المنسوخ،\n- إذا فشل النسخ/تجاوزه، تبدأ معرفات قطع أثرية الجلسة الجديدة من `0`.\n\nآثار البيانات الثنائية بعد التفرع:\n\n- البيانات الثنائية عامة ومعنونة بالمحتوى، لذا لا يلزم نسخ دليل البيانات الثنائية.\n\n## الانتقال إلى مسار عمل جديد\n\nيُعيد `SessionManager.moveTo()` تسمية ملف الجلسة ودليل القطع الأثرية إلى دليل الجلسة الافتراضي الجديد، مع منطق التراجع إذا فشلت خطوة لاحقة. يُحافظ هذا على هوية القطع الأثرية مع إعادة توطين نطاق الجلسة.\n\n## معالجة الأعطال ومسارات الرجوع\n\n| الحالة | السلوك |\n| --- | --- |\n| ملف البيانات الثنائية مفقود أثناء إعادة الترطيب | التحذير والاحتفاظ بسلسلة المرجع `blob:sha256:` في الذاكرة |\n| قراءة البيانات الثنائية ENOENT عبر `BlobStore.get` | تُعيد `null` |\n| دليل القطع الأثرية مفقود (`ArtifactManager.listFiles`) | تُعيد قائمة فارغة (يمكن بدء التخصيص من جديد) |\n| دليل القطع الأثرية مفقود (`artifact://` / `agent://`) | يُطلق استثناء صريح `No artifacts directory found` |\n| معرف القطعة الأثرية غير موجود | يُطلق استثناء مع قائمة بالمعرفات المتاحة |\n| فشل تهيئة كاتب القطعة الأثرية في OutputSink | يواصل مع الاقتطاع بالذيل فقط (بدون قطعة أثرية للمخرج الكامل) |\n| لا يوجد ملف جلسة (بعض مسارات المهام) | تعود أداة المهمة إلى دليل قطع أثرية مؤقت لمخرجات الوكيل الفرعي |\n\n## استخراج البيانات الثنائية مقابل قطع الأثرية للمخرج النصي\n\n- **استخراج البيانات الثنائية** يُخصص لحمولات الصور الثنائية داخل محتوى إدخال الجلسة المستمرة؛ يستبدل base64 المضمّن في JSONL بمراجع محتوى ثابتة.\n- **القطع الأثرية** ملفات نصية عادية لمخرجات التنفيذ ومخرجات الوكيل الفرعي؛ يمكن عنونتها بواسطة معرفات محلية للجلسة عبر عناوين URL الداخلية.\n\nيتقاطع النظامان بشكل غير مباشر فقط (كلاهما يُقلل من تضخم جلسة JSONL) لكن لهما هوية وعمر ومسارات استرداد مختلفة.\n\n## ملفات التنفيذ\n\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts) — تنسيق مرجع البيانات الثنائية، والتجزئة، والوضع/الأخذ، ومساعدات الاستخراج/التحليل.\n- [`src/session/artifacts.ts`](../../packages/coding-agent/src/session/artifacts.ts) — نموذج دليل قطع أثرية الجلسة وتخصيص معرف القطعة الأثرية الرقمي.\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — سلوك اقتطاع `OutputSink`/التسرب إلى الملف وبيانات الملخص.\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts) — تحويلات الاستمرارية، وإعادة ترطيب البيانات الثنائية عند التحميل، وتفاعلات تفرع/نقل الجلسة.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — نسخ دليل القطع الأثرية أثناء التفرع التفاعلي.\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — تمهيد مدير قطع أثرية الأداة وتخصيص مسار القطعة الأثرية لكل أداة.\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — محلّل `artifact://`.\n- [`src/internal-urls/agent-protocol.ts`](../../packages/coding-agent/src/internal-urls/agent-protocol.ts) — محلّل `agent://` + استخراج JSON.\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — توصيل موجّه URL الداخلي ومحلّل دليل القطع الأثرية.\n- [`src/task/output-manager.ts`](../../packages/coding-agent/src/task/output-manager.ts) — تخصيص معرف مخرجات الوكيل محدد النطاق بالجلسة لـ `agent://`.\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — كتابة قطع أثرية مخرجات الوكيل الفرعي (`<id>.md`) والرجوع إلى دليل القطع الأثرية المؤقت.\n",
	"ar/configuration/config-usage.md": "---\ntitle: اكتشاف التكوين وحلّه\ndescription: >-\n  كيف يكتشف xcsh التكوين ويحلّه ويُطبّق طبقاته من جذور المشروع والمستخدم\n  والمؤسسة.\nsidebar:\n  order: 1\n  label: التكوين\ni18n:\n  sourceHash: e38bd9792499\n  translator: machine\n---\n\n# اكتشاف التكوين وحلّه\n\nيصف هذا المستند كيف يحلّ وكيل البرمجة (coding-agent) التكوين حالياً: أي الجذور يتم فحصها، وكيف تعمل الأسبقية، وكيف يُستهلك التكوين المحلول من قِبَل الإعدادات والمهارات والخطّافات (hooks) والأدوات والإضافات.\n\n## النطاق\n\nالتنفيذ الأساسي:\n\n- `src/config.ts`\n- `src/config/settings.ts`\n- `src/config/settings-schema.ts`\n- `src/discovery/builtin.ts`\n- `src/discovery/helpers.ts`\n\nنقاط التكامل الرئيسية:\n\n- `src/capability/index.ts`\n- `src/discovery/index.ts`\n- `src/extensibility/skills.ts`\n- `src/extensibility/hooks/loader.ts`\n- `src/extensibility/custom-tools/loader.ts`\n- `src/extensibility/extensions/loader.ts`\n\n---\n\n## تدفق الحلّ (مرئي)\n\n```text\n         Config roots (ordered)\n┌───────────────────────────────────────┐\n│ 1) ~/.xcsh/agent + <cwd>/.xcsh          │\n│ 2) ~/.claude   + <cwd>/.claude        │\n│ 3) ~/.codex    + <cwd>/.codex         │\n│ 4) ~/.gemini   + <cwd>/.gemini        │\n└───────────────────────────────────────┘\n                    │\n                    ▼\n        config.ts helper resolution\n  (getConfigDirs/findConfigFile/findNearest...)\n                    │\n                    ▼\n       capability providers enumerate items\n (native, claude, codex, gemini, agents, etc.)\n                    │\n                    ▼\n      priority sort + per-capability dedup\n                    │\n                    ▼\n          subsystem-specific consumption\n   (settings, skills, hooks, tools, extensions)\n```\n\n## 1) جذور التكوين وترتيب المصادر\n\n## الجذور القانونية\n\nيُعرّف `src/config.ts` قائمة أسبقية مصادر ثابتة:\n\n1. `.xcsh` (أصلي)\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nالقواعد على مستوى المستخدم:\n\n- `~/.xcsh/agent`\n- `~/.claude`\n- `~/.codex`\n- `~/.gemini`\n\nالقواعد على مستوى المشروع:\n\n- `<cwd>/.xcsh`\n- `<cwd>/.claude`\n- `<cwd>/.codex`\n- `<cwd>/.gemini`\n\n`CONFIG_DIR_NAME` هو `.xcsh` (`packages/utils/src/dirs.ts`).\n\n## قيد مهم\n\nالمساعدات العامة في `src/config.ts` **لا** تتضمن `.pi` في ترتيب اكتشاف المصادر.\n\n---\n\n## 2) مساعدات الاكتشاف الأساسية (`src/config.ts`)\n\n## `getConfigDirs(subpath, options)`\n\nتُرجع مُدخلات مرتبة:\n\n- المُدخلات على مستوى المستخدم أولاً (حسب أسبقية المصدر)\n- ثم المُدخلات على مستوى المشروع (حسب نفس أسبقية المصدر)\n\nالخيارات:\n\n- `user` (الافتراضي `true`)\n- `project` (الافتراضي `true`)\n- `cwd` (الافتراضي `getProjectDir()`)\n- `existingOnly` (الافتراضي `false`)\n\nتُستخدم هذه الواجهة البرمجية لعمليات البحث عن التكوين المبنية على المجلدات (الأوامر، الخطّافات، الأدوات، الوكلاء، إلخ).\n\n## `findConfigFile(subpath, options)` / `findConfigFileWithMeta(...)`\n\nتبحث عن أول ملف موجود عبر القواعد المرتبة، وتُرجع أول تطابق (المسار فقط أو المسار + البيانات الوصفية).\n\n## `findAllNearestProjectConfigDirs(subpath, cwd)`\n\nتتنقل عبر المجلدات الأب صعوداً وتُرجع **أقرب مجلد موجود لكل قاعدة مصدر** (`.xcsh`، `.claude`، `.codex`، `.gemini`)، ثم تُرتّب النتائج حسب أسبقية المصدر.\n\nاستخدم هذه الدالة عندما يجب أن يُورَث تكوين المشروع من المجلدات الأب (سلوك المستودعات المتعددة/مساحات العمل المتداخلة).\n\n---\n\n## 3) غلاف ملف التكوين (`ConfigFile<T>` في `src/config.ts`)\n\n`ConfigFile<T>` هو المُحمّل المُتحقق من المخطط لملفات التكوين المفردة.\n\nالصيغ المدعومة:\n\n- `.yml` / `.yaml`\n- `.json` / `.jsonc`\n\nالسلوك:\n\n- يتحقق من البيانات المُحلّلة باستخدام AJV مقابل مخطط TypeBox المُقدّم.\n- يُخبّئ نتيجة التحميل حتى استدعاء `invalidate()`.\n- يُرجع نتيجة ثلاثية الحالة عبر `tryLoad()`:\n  - `ok`\n  - `not-found`\n  - `error` (`ConfigError` مع سياق المخطط/التحليل)\n\nترحيل النسخ القديمة لا يزال مدعوماً:\n\n- إذا كان المسار المستهدف `.yml`/`.yaml`، يتم ترحيل ملف `.json` مجاور تلقائياً مرة واحدة (`migrateJsonToYml`).\n\n---\n\n## 4) نموذج حلّ الإعدادات (`src/config/settings.ts`)\n\nنموذج الإعدادات في وقت التشغيل مُطبّق بطبقات:\n\n1. الإعدادات العامة: `~/.xcsh/agent/config.yml`\n2. إعدادات المشروع: مُكتشفة عبر قدرة الإعدادات (`settings.json` من المزوّدين)\n3. التجاوزات في وقت التشغيل: في الذاكرة، غير مستمرة\n4. القيم الافتراضية للمخطط: من `SETTINGS_SCHEMA`\n\nمسار القراءة الفعّال:\n\n`defaults <- global <- project <- overrides`\n\nسلوك الكتابة:\n\n- `settings.set(...)` يكتب إلى الطبقة **العامة** (`config.yml`) ويُدرج عملية حفظ في الخلفية.\n- إعدادات المشروع هي للقراءة فقط من اكتشاف القدرات.\n\n## سلوك الترحيل لا يزال نشطاً\n\nعند بدء التشغيل، إذا كان `config.yml` مفقوداً:\n\n1. الترحيل من `~/.xcsh/agent/settings.json` (يُعاد تسميته إلى `.bak` عند النجاح)\n2. الدمج مع إعدادات قاعدة البيانات القديمة من `agent.db`\n3. كتابة النتيجة المدمجة إلى `config.yml`\n\nعمليات ترحيل على مستوى الحقول في `#migrateRawSettings`:\n\n- `queueMode` -> `steeringMode`\n- `ask.timeout` بالمللي ثانية -> بالثواني عندما تبدو القيمة القديمة كمللي ثانية (`> 1000`)\n- البنية القديمة المسطحة `theme: \"...\"` -> بنية `theme.dark/theme.light`\n\n---\n\n## 5) تكامل القدرات/الاكتشاف\n\nمعظم تدفقات تحميل التكوين غير الأساسية تمر عبر سجل القدرات (`src/capability/index.ts` + `src/discovery/index.ts`).\n\n## ترتيب المزوّدين\n\nيتم ترتيب المزوّدين حسب الأولوية الرقمية (الأعلى أولاً). أمثلة على الأولويات:\n\n- OMP الأصلي (`builtin.ts`): `100`\n- Claude: `80`\n- Codex / agents / Claude marketplace: `70`\n- Gemini: `60`\n\n```text\nProvider precedence (higher wins)\n\nnative (.xcsh)          priority 100\nclaude                 priority  80\ncodex / agents / ...   priority  70\ngemini                 priority  60\n```\n\n## دلالات إزالة التكرار\n\nتُعرّف القدرات `key(item)`:\n\n- نفس المفتاح => العنصر الأول يفوز (العنصر ذو الأولوية الأعلى/المُحمّل أولاً)\n- لا مفتاح (`undefined`) => لا إزالة تكرار، يتم الاحتفاظ بجميع العناصر\n\nالمفاتيح ذات الصلة:\n\n- المهارات: `name`\n- الأدوات: `name`\n- الخطّافات: `${type}:${tool}:${name}`\n- وحدات الإضافات: `name`\n- الإضافات: `name`\n- الإعدادات: لا إزالة تكرار (يتم الاحتفاظ بجميع العناصر)\n\n---\n\n## 6) سلوك المزوّد الأصلي `.xcsh` (`src/discovery/builtin.ts`)\n\nالمزوّد الأصلي (`id: native`) يقرأ من:\n\n- المشروع: `<cwd>/.xcsh/...`\n- المستخدم: `~/.xcsh/agent/...`\n\n### قاعدة قبول المجلد\n\n`builtin.ts` يتضمن جذر التكوين فقط إذا كان المجلد موجوداً **وغير فارغ** (`ifNonEmptyDir`).\n\n### التحميل الخاص بالنطاق\n\n- المهارات: `skills/*/SKILL.md`\n- أوامر الشرطة المائلة: `commands/*.md`\n- القواعد: `rules/*.{md,mdc}`\n- المطالبات: `prompts/*.md`\n- التعليمات: `instructions/*.md`\n- الخطّافات: `hooks/pre/*`، `hooks/post/*`\n- الأدوات: `tools/*.json|*.md` و `tools/<name>/index.ts`\n- وحدات الإضافات: مُكتشفة تحت `extensions/` (+ مصفوفة نصوص قديمة `settings.json.extensions`)\n- الإضافات: `extensions/<name>/gemini-extension.json`\n- قدرة الإعدادات: `settings.json`\n\n### فارق دقيق في البحث عن أقرب مشروع\n\nبالنسبة لـ `SYSTEM.md` و `XCSH.md`، يستخدم المزوّد الأصلي بحث أقرب مجلد `.xcsh` أب في المشروع (تصاعدي) لكنه لا يزال يشترط أن يكون مجلد `.xcsh` غير فارغ.\n\n---\n\n## 7) كيف تستهلك الأنظمة الفرعية الرئيسية التكوين\n\n## نظام الإعدادات الفرعي\n\n- `Settings.init()` يُحمّل `config.yml` العام + عناصر قدرة `settings.json` المُكتشفة على مستوى المشروع.\n- يتم دمج عناصر القدرات التي تحمل `level === \"project\"` فقط في طبقة المشروع.\n\n## نظام المهارات الفرعي\n\n- `extensibility/skills.ts` يُحمّل عبر `loadCapability(skillCapability.id, { cwd })`.\n- يُطبّق مفاتيح تبديل المصادر والمرشحات (`ignoredSkills`، `includeSkills`، مجلدات مخصصة).\n- مفاتيح التبديل ذات الأسماء القديمة لا تزال موجودة (`skills.enablePiUser`، `skills.enablePiProject`) لكنها تتحكم في المزوّد الأصلي (`provider === \"native\"`).\n\n## نظام الخطّافات الفرعي\n\n- `discoverAndLoadHooks()` يحلّ مسارات الخطّافات من قدرة الخطّافات + المسارات المُعدّة صراحةً.\n- ثم يُحمّل الوحدات عبر استيراد Bun.\n\n## نظام الأدوات الفرعي\n\n- `discoverAndLoadCustomTools()` يحلّ مسارات الأدوات من قدرة الأدوات + مسارات أدوات الإضافات + المسارات المُعدّة صراحةً.\n- ملفات الأدوات التصريحية `.md/.json` هي بيانات وصفية فقط؛ التحميل التنفيذي يتوقع وحدات شفرة.\n\n## نظام الإضافات الفرعي\n\n- `discoverAndLoadExtensions()` يحلّ وحدات الإضافات من قدرة وحدة الإضافات بالإضافة إلى المسارات الصريحة.\n- التنفيذ الحالي يحتفظ عمداً فقط بعناصر القدرات التي تحمل `_source.provider === \"native\"` قبل التحميل.\n\n---\n\n## 8) قواعد الأسبقية التي يمكن الاعتماد عليها\n\nاستخدم هذا النموذج الذهني:\n\n1. ترتيب مجلد المصدر من `config.ts` يُحدد ترتيب المسارات المرشحة.\n2. أولوية مزوّد القدرة تُحدد الأسبقية عبر المزوّدين.\n3. إزالة تكرار مفتاح القدرة تُحدد سلوك التعارض (الأول يفوز للقدرات ذات المفاتيح).\n4. منطق الدمج الخاص بالنظام الفرعي يمكن أن يُغيّر الأسبقية الفعّالة بشكل إضافي (خاصةً الإعدادات).\n\n### تحذير خاص بالإعدادات\n\nعناصر قدرة الإعدادات لا يتم إزالة تكرارها؛ `Settings.#loadProjectSettings()` يُجري دمجاً عميقاً لعناصر المشروع بالترتيب المُرجَع. لأن الدمج يُطبّق قيم العنصر الأحدث فوق القيم الأقدم، فإن سلوك التجاوز الفعّال يعتمد على ترتيب إصدار المزوّد، وليس فقط على دلالات مفتاح القدرة.\n\n---\n\n## 9) سلوكيات التوافق/القديمة لا تزال موجودة\n\n- ترحيل JSON -> YAML في `ConfigFile` للملفات المستهدفة بصيغة YAML.\n- ترحيل الإعدادات من `settings.json` و `agent.db` إلى `config.yml`.\n- عمليات ترحيل مفاتيح الإعدادات (`queueMode`، `ask.timeout`، `theme` المسطح).\n- توافق بيان الإضافات: المُحمّل يقبل كلاً من أقسام بيان `package.json.xcsh` و `package.json.pi`.\n- أسماء الإعدادات القديمة `skills.enablePiUser` / `skills.enablePiProject` لا تزال بوابات نشطة لمصدر المهارات الأصلي.\n\nإذا تمت إزالة مسارات التوافق هذه من الشفرة، حدّث هذا المستند فوراً؛ عدة سلوكيات في وقت التشغيل لا تزال تعتمد عليها اليوم.\n",
	"ar/configuration/environment-variables.md": "---\ntitle: متغيرات البيئة\ndescription: >-\n  Runtime environment variable reference for xcsh configuration and behavior\n  control.\nsidebar:\n  order: 2\n  label: متغيرات البيئة\ni18n:\n  sourceHash: e2890f963c02\n  translator: machine\n---\n\n# متغيرات البيئة (مرجع وقت التشغيل الحالي)\n\nهذا المرجع مُستمد من مسارات الشيفرة البرمجية الحالية في:\n\n- `packages/coding-agent/src/**`\n- `packages/ai/src/**` (حل المزود/المصادقة المُستخدم من قِبل coding-agent)\n- `packages/utils/src/**` و `packages/tui/src/**` حيث تؤثر تلك المتغيرات مباشرة على وقت تشغيل coding-agent\n\nيوثّق هذا المرجع السلوك النشط فقط.\n\n## نموذج الحل والأسبقية\n\nتستخدم معظم عمليات البحث في وقت التشغيل `$env` من `@f5-sales-demo/pi-utils` (`packages/utils/src/env.ts`).\n\nترتيب تحميل `$env`:\n\n1. بيئة العملية الحالية (`Bun.env`)\n2. ملف `.env` الخاص بالمشروع (`$PWD/.env`) للمفاتيح غير المُعيَّنة مسبقاً\n3. ملف `.env` الخاص بالمجلد الرئيسي (`~/.env`) للمفاتيح غير المُعيَّنة مسبقاً\n\nقاعدة إضافية في ملفات `.env`: يتم نسخ مفاتيح `XCSH_*` إلى مفاتيح `PI_*` أثناء التحليل.\n\n---\n\n## 1) مصادقة النموذج/المزود\n\nيتم استهلاك هذه المتغيرات عبر `getEnvApiKey()` (`packages/ai/src/stream.ts`) ما لم يُذكر خلاف ذلك.\n\n### بيانات اعتماد المزود الأساسية\n\n| المتغير                        | الاستخدام | مطلوب عند                                                 | ملاحظات / الأسبقية                                                                                  |\n|---------------------------------|---|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|\n| `ANTHROPIC_OAUTH_TOKEN`         | مصادقة Anthropic API | استخدام Anthropic مع مصادقة رمز OAuth                         | يأخذ الأسبقية على `ANTHROPIC_API_KEY` لحل مصادقة المزود                              |\n| `ANTHROPIC_API_KEY`             | مصادقة Anthropic API | استخدام Anthropic بدون رمز OAuth                           | البديل الاحتياطي بعد `ANTHROPIC_OAUTH_TOKEN`                                                              |\n| `ANTHROPIC_FOUNDRY_API_KEY`     | Anthropic عبر Azure Foundry / بوابة المؤسسات | تفعيل `CLAUDE_CODE_USE_FOUNDRY`                             | يأخذ الأسبقية على `ANTHROPIC_OAUTH_TOKEN` و `ANTHROPIC_API_KEY` عند تفعيل وضع Foundry  |\n| `OPENAI_API_KEY`                | مصادقة OpenAI | استخدام مزودي عائلة OpenAI بدون وسيط apiKey صريح | يُستخدم من قِبل مزودي OpenAI Completions/Responses                                                      |\n| `GEMINI_API_KEY`                | مصادقة Google Gemini | استخدام نماذج مزود `google`                                | المفتاح الأساسي لتعيين مزود Gemini                                                             |\n| `GOOGLE_API_KEY`                | مصادقة احتياطية لأداة صور Gemini | استخدام أداة `gemini_image` بدون `GEMINI_API_KEY`            | يُستخدم من قِبل المسار الاحتياطي لأداة صور coding-agent                                                       |\n| `GROQ_API_KEY`                  | مصادقة Groq | استخدام نماذج Groq                                             |                                                                                                     |\n| `CEREBRAS_API_KEY`              | مصادقة Cerebras | استخدام نماذج Cerebras                                         |                                                                                                     |\n| `TOGETHER_API_KEY`              | مصادقة Together | استخدام مزود `together`                                     |                                                                                                     |\n| `HUGGINGFACE_HUB_TOKEN`         | مصادقة Hugging Face | استخدام مزود `huggingface`                                  | متغير بيئة رمز Hugging Face الأساسي                                                                  |\n| `HF_TOKEN`                      | مصادقة Hugging Face | استخدام مزود `huggingface`                                  | البديل الاحتياطي عندما يكون `HUGGINGFACE_HUB_TOKEN` غير مُعيَّن                                                      |\n| `SYNTHETIC_API_KEY`             | مصادقة Synthetic | استخدام نماذج Synthetic                                        |                                                                                                     |\n| `NVIDIA_API_KEY`                | مصادقة NVIDIA | استخدام مزود `nvidia`                                       |                                                                                                     |\n| `NANO_GPT_API_KEY`              | مصادقة NanoGPT | استخدام مزود `nanogpt`                                      |                                                                                                     |\n| `VENICE_API_KEY`                | مصادقة Venice | استخدام مزود `venice`                                       |                                                                                                     |\n| `LITELLM_API_KEY`               | مصادقة LiteLLM | استخدام مزود `litellm`                                      | مفتاح وكيل LiteLLM المتوافق مع OpenAI. عند تعيينه مع `LITELLM_BASE_URL`، يُمكّن التكوين التلقائي لـ `models.yml` |\n| `LM_STUDIO_API_KEY`             | مصادقة LM Studio (اختياري) | استخدام مزود `lm-studio` مع مضيفين مُصادَق عليهم           | عادةً يعمل LM Studio المحلي بدون مصادقة؛ أي رمز غير فارغ يعمل عند الحاجة لمفتاح         |\n| `OLLAMA_API_KEY`                | مصادقة Ollama (اختياري) | استخدام مزود `ollama` مع مضيفين مُصادَق عليهم              | عادةً يعمل Ollama المحلي بدون مصادقة؛ أي رمز غير فارغ يعمل عند الحاجة لمفتاح            |\n| `LLAMA_CPP_API_KEY`             | مصادقة Ollama (اختياري) | استخدام `llama-server` مع معامل `--api-key`              | عادةً يعمل llama.cpp المحلي بدون مصادقة؛ أي رمز غير فارغ يعمل عند تكوين مفتاح       |\n| `XIAOMI_API_KEY`                | مصادقة Xiaomi MiMo | استخدام مزود `xiaomi`                                       |                                                                                                     |\n| `MOONSHOT_API_KEY`              | مصادقة Moonshot | استخدام مزود `moonshot`                                     |                                                                                                     |\n| `XAI_API_KEY`                   | مصادقة xAI | استخدام نماذج xAI                                              |                                                                                                     |\n| `OPENROUTER_API_KEY`            | مصادقة OpenRouter | استخدام نماذج OpenRouter                                       | يُستخدم أيضاً من قِبل أداة الصور عندما يكون المزود المفضل/التلقائي هو OpenRouter                                  |\n| `MISTRAL_API_KEY`               | مصادقة Mistral | استخدام نماذج Mistral                                          |                                                                                                     |\n| `ZAI_API_KEY`                   | مصادقة z.ai | استخدام نماذج z.ai                                             | يُستخدم أيضاً من قِبل مزود بحث الويب z.ai                                                               |\n| `MINIMAX_API_KEY`               | مصادقة MiniMax | استخدام مزود `minimax`                                      |                                                                                                     |\n| `MINIMAX_CODE_API_KEY`          | مصادقة MiniMax Code | استخدام مزود `minimax-code`                                 |                                                                                                     |\n| `MINIMAX_CODE_CN_API_KEY`       | مصادقة MiniMax Code CN | استخدام مزود `minimax-code-cn`                              |                                                                                                     |\n| `OPENCODE_API_KEY`              | مصادقة OpenCode | استخدام نماذج OpenCode                                         |                                                                                                     |\n| `QIANFAN_API_KEY`               | مصادقة Qianfan | استخدام مزود `qianfan`                                      |                                                                                                     |\n| `QWEN_OAUTH_TOKEN`              | مصادقة Qwen Portal | استخدام `qwen-portal` مع رمز OAuth                          | يأخذ الأسبقية على `QWEN_PORTAL_API_KEY`                                                         |\n| `QWEN_PORTAL_API_KEY`           | مصادقة Qwen Portal | استخدام `qwen-portal` مع مفتاح API                              | البديل الاحتياطي بعد `QWEN_OAUTH_TOKEN`                                                                   |\n| `ZENMUX_API_KEY`                | مصادقة ZenMux | استخدام مزود `zenmux`                                       | يُستخدم لمسارات ZenMux المتوافقة مع OpenAI وAnthropic                                              |\n| `VLLM_API_KEY`                  | مصادقة/اكتشاف vLLM | استخدام مزود `vllm` (خوادم محلية متوافقة مع OpenAI)       | أي قيمة غير فارغة تعمل للخوادم المحلية بدون مصادقة                                                 |\n| `CURSOR_ACCESS_TOKEN`           | مصادقة مزود Cursor | استخدام مزود Cursor                                         |                                                                                                     |\n| `AI_GATEWAY_API_KEY`            | مصادقة Vercel AI Gateway | استخدام مزود `vercel-ai-gateway`                            |                                                                                                     |\n| `CLOUDFLARE_AI_GATEWAY_API_KEY` | مصادقة Cloudflare AI Gateway | استخدام مزود `cloudflare-ai-gateway`                        | يجب تكوين عنوان URL الأساسي كـ `https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic` |\n\n### سلاسل رموز GitHub/Copilot\n\n| المتغير | الاستخدام | السلسلة |\n|---|---|---|\n| `COPILOT_GITHUB_TOKEN` | مصادقة مزود GitHub Copilot | `COPILOT_GITHUB_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN` |\n| `GH_TOKEN` | بديل Copilot الاحتياطي؛ مصادقة GitHub API في أداة استخراج الويب | في أداة استخراج الويب: `GITHUB_TOKEN` → `GH_TOKEN` |\n| `GITHUB_TOKEN` | بديل Copilot الاحتياطي؛ مصادقة GitHub API في أداة استخراج الويب | في أداة استخراج الويب: يُفحص قبل `GH_TOKEN` |\n\n---\n\n## 2) تكوين وقت التشغيل الخاص بالمزود\n\n### بوابة Anthropic Foundry (Azure / وكيل المؤسسات)\n\nعند تفعيل `CLAUDE_CODE_USE_FOUNDRY`، تتحول طلبات Anthropic إلى وضع Foundry:\n\n- يُحل عنوان URL الأساسي من `FOUNDRY_BASE_URL` (يبقى البديل الاحتياطي هو عنوان URL الأساسي للنموذج/الافتراضي إذا لم يُعيَّن).\n- يصبح حل مفتاح API لمزود `anthropic`:\n  `ANTHROPIC_FOUNDRY_API_KEY` → `ANTHROPIC_OAUTH_TOKEN` → `ANTHROPIC_API_KEY`.\n- يتم تحليل `ANTHROPIC_CUSTOM_HEADERS` كأزواج `key: value` مفصولة بفاصلة/سطر جديد ودمجها في ترويسات الطلب.\n- يمكن حقن مواد TLS للعميل/الخادم من قيم البيئة:\n  `NODE_EXTRA_CA_CERTS`، `CLAUDE_CODE_CLIENT_CERT`، `CLAUDE_CODE_CLIENT_KEY`.\n  كل منها يقبل إما:\n  - مسار نظام ملفات لمحتوى PEM، أو\n  - PEM مضمّن (بما في ذلك تسلسلات `\\n` المُهرَّبة).\n\n| المتغير | نوع القيمة | السلوك |\n|---|---|---|\n| `CLAUDE_CODE_USE_FOUNDRY` | سلسلة شبيهة بالقيمة المنطقية (`1`، `true`، `yes`، `on`) | يُفعّل وضع Foundry لمزود Anthropic |\n| `FOUNDRY_BASE_URL` | سلسلة URL | عنوان URL الأساسي لنقطة نهاية Anthropic في وضع Foundry |\n| `ANTHROPIC_FOUNDRY_API_KEY` | سلسلة رمز | يُستخدم لـ `Authorization: Bearer <token>` |\n| `ANTHROPIC_CUSTOM_HEADERS` | سلسلة قائمة ترويسات | ترويسات إضافية؛ التنسيق `header-a: value, header-b: value` أو مفصولة بأسطر جديدة |\n| `NODE_EXTRA_CA_CERTS` | مسار PEM أو PEM مضمّن | سلسلة CA إضافية للتحقق من شهادة الخادم |\n| `CLAUDE_CODE_CLIENT_CERT` | مسار PEM أو PEM مضمّن | شهادة عميل mTLS |\n| `CLAUDE_CODE_CLIENT_KEY` | مسار PEM أو PEM مضمّن | مفتاح خاص لعميل mTLS (يجب أن يُقرن بالشهادة) |\n\n### Amazon Bedrock\n\n| المتغير | الافتراضي / السلوك |\n|---|---|\n| `AWS_REGION` | مصدر المنطقة الأساسي |\n| `AWS_DEFAULT_REGION` | البديل الاحتياطي إذا لم يُعيَّن `AWS_REGION` |\n| `AWS_PROFILE` | يُفعّل مسار مصادقة الملف الشخصي المُسمى |\n| `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` | يُفعّل مسار مصادقة مفتاح IAM |\n| `AWS_BEARER_TOKEN_BEDROCK` | يُفعّل مسار مصادقة رمز الحامل |\n| `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` / `AWS_CONTAINER_CREDENTIALS_FULL_URI` | يُفعّل مسار بيانات اعتماد مهمة ECS |\n| `AWS_WEB_IDENTITY_TOKEN_FILE` + `AWS_ROLE_ARN` | يُفعّل مسار مصادقة هوية الويب |\n| `AWS_BEDROCK_SKIP_AUTH` | إذا كان `1`، يحقن بيانات اعتماد وهمية (سيناريوهات الوكيل/بدون مصادقة) |\n| `AWS_BEDROCK_FORCE_HTTP1` | إذا كان `1`، يفرض معالج طلبات Node HTTP/1 |\n\nالبديل الاحتياطي للمنطقة في شيفرة المزود: `options.region` → `AWS_REGION` → `AWS_DEFAULT_REGION` → `us-east-1`.\n\n### Azure OpenAI Responses\n\n| المتغير | الافتراضي / السلوك |\n|---|---|\n| `AZURE_OPENAI_API_KEY` | مطلوب ما لم يُمرر مفتاح API كخيار |\n| `AZURE_OPENAI_API_VERSION` | الافتراضي `v1` |\n| `AZURE_OPENAI_BASE_URL` | تجاوز مباشر لعنوان URL الأساسي |\n| `AZURE_OPENAI_RESOURCE_NAME` | يُستخدم لبناء عنوان URL الأساسي: `https://<resource>.openai.azure.com/openai/v1` |\n| `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` | سلسلة تعيين اختيارية: `modelId=deploymentName,model2=deployment2` |\n\nحل عنوان URL الأساسي: خيار `azureBaseUrl` → بيئة `AZURE_OPENAI_BASE_URL` → اسم المورد من الخيار/البيئة → `model.baseUrl`.\n\n### Google Vertex AI\n\n| المتغير | مطلوب؟ | ملاحظات |\n|---|---|---|\n| `GOOGLE_CLOUD_PROJECT` | نعم (ما لم يُمرر في الخيارات) | البديل الاحتياطي: `GCLOUD_PROJECT` |\n| `GCLOUD_PROJECT` | بديل احتياطي | يُستخدم كمصدر بديل لمعرّف المشروع |\n| `GOOGLE_CLOUD_LOCATION` | نعم (ما لم يُمرر في الخيارات) | لا يوجد افتراضي في المزود |\n| `GOOGLE_APPLICATION_CREDENTIALS` | مشروط | إذا عُيِّن، يجب أن يكون الملف موجوداً؛ وإلا يُفحص مسار ADC الاحتياطي (`~/.config/gcloud/application_default_credentials.json`) |\n\n### Kimi\n\n| المتغير | الافتراضي / السلوك |\n|---|---|\n| `KIMI_CODE_OAUTH_HOST` | تجاوز مضيف OAuth الأساسي |\n| `KIMI_OAUTH_HOST` | تجاوز مضيف OAuth الاحتياطي |\n| `KIMI_CODE_BASE_URL` | يتجاوز عنوان URL الأساسي لنقطة نهاية استخدام Kimi (`usage/kimi.ts`) |\n\nسلسلة مضيف OAuth: `KIMI_CODE_OAUTH_HOST` → `KIMI_OAUTH_HOST` → `https://auth.kimi.com`.\n\n### توافق Antigravity/صور Gemini\n\n| المتغير | الافتراضي / السلوك |\n|---|---|\n| `PI_AI_ANTIGRAVITY_VERSION` | يتجاوز علامة إصدار وكيل المستخدم Antigravity في مزود Gemini CLI |\n\n### استجابات OpenAI Codex (عناصر التحكم بالميزات/التصحيح)\n\n| المتغير | السلوك |\n|---|---|\n| `PI_CODEX_DEBUG` | `1`/`true` يُفعّل تسجيل تصحيح مزود Codex |\n| `PI_CODEX_WEBSOCKET` | `1`/`true` يُفعّل تفضيل نقل websocket |\n| `PI_CODEX_WEBSOCKET_V2` | `1`/`true` يُفعّل مسار websocket v2 |\n| `PI_CODEX_WEBSOCKET_IDLE_TIMEOUT_MS` | تجاوز عدد صحيح موجب (الافتراضي 300000) |\n| `PI_CODEX_WEBSOCKET_RETRY_BUDGET` | تجاوز عدد صحيح غير سالب (الافتراضي 5) |\n| `PI_CODEX_WEBSOCKET_RETRY_DELAY_MS` | تجاوز قاعدة التراجع الأسي كعدد صحيح موجب (الافتراضي 500) |\n\n### تصحيح مزود Cursor\n\n| المتغير | السلوك |\n|---|---|\n| `DEBUG_CURSOR` | يُفعّل سجلات تصحيح المزود؛ `2`/`verbose` لمقتطفات حمولة مفصلة |\n| `DEBUG_CURSOR_LOG` | مسار ملف اختياري لإخراج سجل تصحيح JSONL |\n\n### مفتاح توافق ذاكرة التخزين المؤقت للموجهات\n\n| المتغير | السلوك |\n|---|---|\n| `PI_CACHE_RETENTION` | إذا كان `long`، يُفعّل الاحتفاظ الطويل حيث يكون مدعوماً (`anthropic`، `openai-responses`، حل احتفاظ Bedrock) |\n\n---\n\n## 3) النظام الفرعي لبحث الويب\n\n### بيانات اعتماد مزود البحث\n\n| المتغير | يُستخدم من قِبل |\n|---|---|\n| `EXA_API_KEY` | مزود بحث Exa وأدوات Exa MCP |\n| `BRAVE_API_KEY` | مزود بحث Brave |\n| `PERPLEXITY_API_KEY` | مزود بحث Perplexity في وضع مفتاح API |\n| `TAVILY_API_KEY` | مزود بحث Tavily |\n| `ZAI_API_KEY` | مزود بحث z.ai (يفحص أيضاً OAuth المُخزَّن في `agent.db`) |\n| `OPENAI_API_KEY` / Codex OAuth في DB | توفر/مصادقة مزود بحث Codex |\n\n### سلسلة مصادقة بحث الويب من Anthropic\n\nيحل `packages/coding-agent/src/web/search/auth.ts` بيانات اعتماد بحث الويب من Anthropic بهذا الترتيب:\n\n1. `ANTHROPIC_SEARCH_API_KEY` (+ اختيارياً `ANTHROPIC_SEARCH_BASE_URL`)\n2. إدخال مزود `models.json` مع `api: \"anthropic-messages\"`\n3. بيانات اعتماد Anthropic OAuth من `agent.db` (يجب ألا تنتهي صلاحيتها خلال مهلة 5 دقائق)\n4. بديل Anthropic العام الاحتياطي: مفتاح المزود (`ANTHROPIC_FOUNDRY_API_KEY`/`ANTHROPIC_OAUTH_TOKEN`/`ANTHROPIC_API_KEY`) + اختيارياً `ANTHROPIC_BASE_URL` (`FOUNDRY_BASE_URL` عند تفعيل وضع Foundry)\n\nالمتغيرات ذات الصلة:\n\n| المتغير | الافتراضي / السلوك |\n|---|---|\n| `ANTHROPIC_SEARCH_API_KEY` | مفتاح بحث صريح بأعلى أسبقية |\n| `ANTHROPIC_SEARCH_BASE_URL` | الافتراضي `https://api.anthropic.com` عند حذفه |\n| `ANTHROPIC_SEARCH_MODEL` | الافتراضي `claude-haiku-4-5` |\n| `ANTHROPIC_BASE_URL` | عنوان URL أساسي عام احتياطي لمسار المصادقة من المستوى 4 |\n\n### علامة سلوك تدفق Perplexity OAuth\n\n| المتغير | السلوك |\n|---|---|\n| `PI_AUTH_NO_BORROW` | إذا عُيِّن، يُعطّل مسار استعارة رمز التطبيق الأصلي في macOS في تدفق تسجيل دخول Perplexity |\n\n---\n\n## 4) أدوات Python ووقت تشغيل النواة\n\n| المتغير | الافتراضي / السلوك |\n|---|---|\n| `PI_PY` | تجاوز وضع أداة Python: `0`/`bash`=`bash-only`، `1`/`py`=`ipy-only`، `mix`/`both`=`both`؛ يتم تجاهل القيم غير الصالحة |\n| `PI_PYTHON_SKIP_CHECK` | إذا كان `1`، يتخطى فحوصات توفر نواة Python/فحوصات الإحماء |\n| `PI_PYTHON_GATEWAY_URL` | إذا عُيِّن، يستخدم بوابة نواة خارجية بدلاً من البوابة المشتركة المحلية |\n| `PI_PYTHON_GATEWAY_TOKEN` | رمز مصادقة اختياري للبوابة الخارجية (`Authorization: token <value>`) |\n| `PI_PYTHON_IPC_TRACE` | إذا كان `1`، يُفعّل مسار تتبع IPC منخفض المستوى في وحدة النواة |\n| `VIRTUAL_ENV` | مسار بيئة افتراضية بأعلى أسبقية لحل وقت تشغيل Python |\n\nسلوك شرطي إضافي:\n\n- إذا كان `BUN_ENV=test` أو `NODE_ENV=test`، تُعامل فحوصات توفر Python على أنها ناجحة ويُتخطى الإحماء.\n- تصفية بيئة Python ترفض مفاتيح API الشائعة وتسمح بمتغيرات الأساس الآمنة + بادئات `LC_`، `XDG_`، `PI_`.\n\n---\n\n## 5) مفاتيح تبديل سلوك الوكيل/وقت التشغيل\n\n| المتغير                   | الافتراضي / السلوك                                                                           |\n|----------------------------|----------------------------------------------------------------------------------------------|\n| `PI_SMOL_MODEL`            | تجاوز مؤقت لدور النموذج `smol` (خيار CLI `--smol` يأخذ الأسبقية)                     |\n| `PI_SLOW_MODEL`            | تجاوز مؤقت لدور النموذج `slow` (خيار CLI `--slow` يأخذ الأسبقية)                     |\n| `PI_PLAN_MODEL`            | تجاوز مؤقت لدور النموذج `plan` (خيار CLI `--plan` يأخذ الأسبقية)                     |\n| `PI_NO_TITLE`              | إذا عُيِّن (أي قيمة غير فارغة)، يُعطّل التوليد التلقائي لعنوان الجلسة عند أول رسالة مستخدم   |\n| `NULL_PROMPT`              | إذا كان `true`، يُعيد منشئ موجه النظام سلسلة فارغة                                        |\n| `PI_BLOCKED_AGENT`         | يحظر نوع وكيل فرعي محدد في أداة المهام                                                 |\n| `PI_SUBPROCESS_CMD`        | يتجاوز أمر إنشاء الوكيل الفرعي (تجاوز حل `xcsh` / `xcsh.cmd`)                       |\n| `PI_TASK_MAX_OUTPUT_BYTES` | أقصى عدد بايتات مُلتقطة لكل وكيل فرعي (الافتراضي `500000`)                                    |\n| `PI_TASK_MAX_OUTPUT_LINES` | أقصى عدد أسطر مُلتقطة لكل وكيل فرعي (الافتراضي `5000`)                                      |\n| `PI_TIMING`                | إذا كان `1`، يُفعّل سجلات أدوات توقيت بدء التشغيل/الأدوات                                     |\n| `PI_DEBUG_STARTUP`         | يُفعّل طباعات تصحيح مرحلة بدء التشغيل إلى stderr في مسارات بدء تشغيل متعددة                       |\n| `PI_PACKAGE_DIR`           | يتجاوز حل دليل أصول الحزمة الأساسي (بحث مسار المستندات/الأمثلة/سجل التغييرات)            |\n| `PI_DISABLE_LSPMUX`        | إذا كان `1`، يُعطّل اكتشاف/تكامل lspmux ويفرض إنشاء خادم LSP مباشر          |\n| `LITELLM_BASE_URL`         | عنوان URL الأساسي لوكيل LiteLLM. عند تعيينه مع `LITELLM_API_KEY`، يُشغّل التوليد التلقائي لـ `models.yml` عند أول تشغيل والإصلاح الذاتي عند كل بدء تشغيل |\n| `LM_STUDIO_BASE_URL`       | تجاوز عنوان URL الأساسي الافتراضي الضمني لاكتشاف LM Studio (`http://127.0.0.1:1234/v1` إذا لم يُعيَّن) |\n| `OLLAMA_BASE_URL`          | تجاوز عنوان URL الأساسي الافتراضي الضمني لاكتشاف Ollama (`http://127.0.0.1:11434` إذا لم يُعيَّن)      |\n| `LLAMA_CPP_BASE_URL`       | تجاوز عنوان URL الأساسي الافتراضي الضمني لاكتشاف Llama.cpp (`http://127.0.0.1:8080` إذا لم يُعيَّن)    |\n| `PI_EDIT_VARIANT`          | إذا كان `hashline`، يفرض وضع عرض القراءة/البحث بنمط hashline عند توفر أداة التحرير               |\n| `PI_NO_PTY`                | إذا كان `1`، يُعطّل مسار PTY التفاعلي لأداة bash                                          |\n\nيُعيَّن `PI_NO_PTY` أيضاً داخلياً عند استخدام خيار CLI `--no-pty`.\n\n---\n\n## 6) مسارات جذر التخزين والتكوين\n\nتُستهلك هذه المتغيرات عبر `@f5-sales-demo/pi-utils/dirs` وتؤثر على مكان تخزين coding-agent للبيانات.\n\n| المتغير | الافتراضي / السلوك |\n|---|---|\n| `PI_CONFIG_DIR` | اسم دليل جذر التكوين تحت المجلد الرئيسي (الافتراضي `.xcsh`) |\n| `PI_CODING_AGENT_DIR` | تجاوز كامل لدليل الوكيل (الافتراضي `~/<PI_CONFIG_DIR or .xcsh>/agent`) |\n| `PWD` | يُستخدم عند مطابقة دليل العمل الحالي الكنسي في مساعدات المسارات |\n\n---\n\n## 7) بيئة تنفيذ الصدفة/الأدوات\n\n(من `packages/utils/src/procmgr.ts` وتكامل أداة bash في coding-agent.)\n\n| المتغير | السلوك |\n|---|---|\n| `PI_BASH_NO_CI` | يمنع الحقن التلقائي لـ `CI=true` في بيئة الصدفة المُنشأة |\n| `CLAUDE_BASH_NO_CI` | اسم مستعار قديم احتياطي لـ `PI_BASH_NO_CI` |\n| `PI_BASH_NO_LOGIN` | مخصص لتعطيل وضع صدفة تسجيل الدخول |\n| `CLAUDE_BASH_NO_LOGIN` | اسم مستعار قديم احتياطي لـ `PI_BASH_NO_LOGIN` |\n| `PI_SHELL_PREFIX` | غلاف بادئة أمر اختياري |\n| `CLAUDE_CODE_SHELL_PREFIX` | اسم مستعار قديم احتياطي لـ `PI_SHELL_PREFIX` |\n| `VISUAL` | أمر المحرر الخارجي المفضل |\n| `EDITOR` | أمر المحرر الخارجي الاحتياطي |\n\nملاحظة حول التنفيذ الحالي: يتم قراءة `PI_BASH_NO_LOGIN`/`CLAUDE_BASH_NO_LOGIN`، لكن `getShellArgs()` الحالية تُعيد `['-l','-c']` في كلا الفرعين (بدون تأثير فعلي حالياً).\n\n---\n\n## 8) واجهة المستخدم/السمة/اكتشاف الجلسة (بيئة مُكتشفة تلقائياً)\n\nتُقرأ هذه المتغيرات كإشارات وقت تشغيل؛ وعادةً يتم تعيينها من قِبل الطرفية/نظام التشغيل بدلاً من التكوين اليدوي.\n\n| المتغير | الاستخدام |\n|---|---|\n| `COLORTERM`، `TERM`، `WT_SESSION` | اكتشاف قدرة الألوان (وضع ألوان السمة) |\n| `COLORFGBG` | اكتشاف تلقائي لخلفية الطرفية فاتحة/داكنة |\n| `TERM_PROGRAM`، `TERM_PROGRAM_VERSION`، `TERMINAL_EMULATOR` | هوية الطرفية في موجه/سياق النظام |\n| `KDE_FULL_SESSION`، `XDG_CURRENT_DESKTOP`، `DESKTOP_SESSION`، `XDG_SESSION_DESKTOP`، `GDMSESSION`، `WINDOWMANAGER` | اكتشاف سطح المكتب/مدير النوافذ في موجه/سياق النظام |\n| `KITTY_WINDOW_ID`، `TMUX_PANE`، `TERM_SESSION_ID`، `WT_SESSION` | معرّفات تتبع جلسة مستقرة لكل طرفية |\n| `SHELL`، `ComSpec`، `TERM_PROGRAM`، `TERM` | تشخيصات معلومات النظام |\n| `APPDATA`، `XDG_CONFIG_HOME` | حل مسار تكوين lspmux |\n| `HOME` | اختصار المسار في واجهة أوامر MCP |\n\n---\n\n## 9) علامات المُحمّل الأصلي/التصحيح\n\n| المتغير | السلوك |\n|---|---|\n| `PI_DEV` | يُفعّل تشخيصات تحميل الإضافة الأصلية المُفصّلة في `packages/natives` |\n\n## 10) علامات وقت تشغيل TUI (حزمة مشتركة، تؤثر على تجربة المستخدم في coding-agent)\n\n| المتغير | السلوك |\n|---|---|\n| `PI_NOTIFICATIONS` | `off` / `0` / `false` تمنع إشعارات سطح المكتب |\n| `PI_TUI_WRITE_LOG` | إذا عُيِّن، يُسجّل كتابات TUI في ملف |\n| `PI_HARDWARE_CURSOR` | إذا كان `1`، يُفعّل وضع مؤشر الأجهزة |\n| `PI_CLEAR_ON_SHRINK` | إذا كان `1`، يمسح الصفوف الفارغة عند تقلّص المحتوى |\n| `PI_DEBUG_REDRAW` | إذا كان `1`، يُفعّل تسجيل تصحيح إعادة الرسم |\n| `PI_TUI_DEBUG` | إذا كان `1`، يُفعّل مسار تفريغ تصحيح TUI العميق |\n\n---\n\n## 11) عناصر التحكم بتوليد الالتزامات\n\n| المتغير | السلوك |\n|---|---|\n| `PI_COMMIT_TEST_FALLBACK` | إذا كان `true` (غير حساس لحالة الأحرف)، يفرض مسار توليد الالتزام الاحتياطي |\n| `PI_COMMIT_NO_FALLBACK` | إذا كان `true`، يُعطّل البديل الاحتياطي عندما لا يُعيد الوكيل أي اقتراح |\n| `PI_COMMIT_MAP_REDUCE` | إذا كان `false`، يُعطّل مسار تحليل الالتزام بنمط map-reduce |\n| `DEBUG` | إذا عُيِّن، تُطبع تتبعات مكدس أخطاء وكيل الالتزام |\n\n---\n\n## المتغيرات ذات الحساسية الأمنية\n\nتعامل مع هذه المتغيرات كأسرار؛ لا تُسجّلها أو تُرسلها إلى المستودع:\n\n- مفاتيح المزود/API وبيانات اعتماد OAuth/رمز الحامل (جميع `*_API_KEY`، `*_TOKEN`، رموز الوصول/التحديث لـ OAuth)\n- بيانات اعتماد السحابة (`AWS_*`، مسار `GOOGLE_APPLICATION_CREDENTIALS` قد يكشف مواد حساب الخدمة)\n- متغيرات مصادقة البحث/المزود (`EXA_API_KEY`، `BRAVE_API_KEY`، `PERPLEXITY_API_KEY`، مفاتيح بحث Anthropic)\n- مواد mTLS لـ Foundry (`CLAUDE_CODE_CLIENT_CERT`، `CLAUDE_CODE_CLIENT_KEY`، `NODE_EXTRA_CA_CERTS` عندما يشير إلى حزم CA خاصة)\n\nيقوم وقت تشغيل Python أيضاً بتجريد العديد من متغيرات المفاتيح الشائعة صراحةً قبل إنشاء عمليات نواة فرعية (`packages/coding-agent/src/ipy/runtime.ts`).\n",
	"ar/configuration/fs-scan-cache-architecture.md": "---\ntitle: بنية ذاكرة التخزين المؤقت لمسح نظام الملفات\ndescription: >-\n  عقد ذاكرة التخزين المؤقت لمسح نظام الملفات لاكتشاف الملفات بسرعة مع دلالات\n  stale-while-revalidate.\nsidebar:\n  order: 8\n  label: ذاكرة التخزين المؤقت لمسح نظام الملفات\ni18n:\n  sourceHash: 2a2bde1726ac\n  translator: machine\n---\n\n# عقد بنية ذاكرة التخزين المؤقت لمسح نظام الملفات\n\nيحدد هذا المستند العقد الحالي لذاكرة التخزين المؤقت المشتركة لمسح نظام الملفات المُنفَّذة بلغة Rust (`crates/pi-natives/src/fs_cache.rs`) والمُستهلَكة من قِبل واجهات برمجة التطبيقات الأصلية للاكتشاف/البحث المكشوفة لـ `packages/coding-agent`.\n\n## ما هي هذه الذاكرة المؤقتة\n\nتخزن الذاكرة المؤقتة قوائم إدخالات المسح الكامل للمجلدات (`GlobMatch[]`) مفهرسة حسب نطاق المسح وسياسة الاجتياز، ثم تسمح للعمليات عالية المستوى (تصفية glob، والتسجيل الضبابي، واختيار ملفات grep) بالعمل على تلك الإدخالات المخزنة مؤقتاً.\n\nالأهداف الرئيسية:\n\n- تجنب عمليات المشي المتكررة في نظام الملفات لاستدعاءات الاكتشاف/البحث المتكررة\n- الحفاظ على الاتساق بين `glob` و`fuzzyFind` و`grep` عندما يتشاركون نفس سياسة المسح\n- السماح باسترداد صريح للبيانات المنتهية الصلاحية للنتائج الفارغة والإبطال الصريح بعد تعديلات الملفات\n\n## الملكية والواجهة العامة\n\n- تنفيذ الذاكرة المؤقتة والسياسة: `crates/pi-natives/src/fs_cache.rs`\n- المستهلكون الأصليون:\n  - `crates/pi-natives/src/glob.rs`\n  - `crates/pi-natives/src/fd.rs` (`fuzzyFind`)\n  - `crates/pi-natives/src/grep.rs`\n- ربط/تصدير JS:\n  - `packages/natives/src/glob/index.ts` (`invalidateFsScanCache`)\n  - `packages/natives/src/glob/types.ts`\n  - `packages/natives/src/grep/types.ts`\n- مساعدات إبطال التعديلات في coding-agent:\n  - `packages/coding-agent/src/tools/fs-cache-invalidation.ts`\n\n## تقسيم مفتاح الذاكرة المؤقتة (عقد ثابت)\n\nكل إدخال مفهرس بواسطة:\n\n- مسار المجلد `root` المُعياري\n- القيمة المنطقية `include_hidden`\n- القيمة المنطقية `use_gitignore`\n\nالآثار المترتبة:\n\n- عمليات المسح المخفية وغير المخفية **لا** تتشارك الإدخالات.\n- عمليات المسح التي تحترم gitignore وتلك التي تعطل التجاهل **لا** تتشارك الإدخالات.\n- يجب على المستهلكين تمرير دلالات مستقرة لسلوك hidden/gitignore؛ تغيير أي من العلمين ينشئ قسم ذاكرة مؤقتة مختلف.\n\nتضمين `node_modules` **ليس** في مفتاح الذاكرة المؤقتة. تخزن الذاكرة المؤقتة الإدخالات مع تضمين `node_modules`؛ ويتم تطبيق التصفية لكل مستهلك بعد الاسترجاع.\n\n## سلوك جمع المسح\n\nيستخدم ملء الذاكرة المؤقتة ماشياً حتمياً (`ignore::WalkBuilder`) مُكوَّناً بواسطة `include_hidden` و`use_gitignore`:\n\n- `follow_links(false)`\n- مرتب حسب مسار الملف\n- يتم تخطي `.git` دائماً\n- يتم جمع `node_modules` دائماً في وقت مسح الذاكرة المؤقتة (ويتم تصفيته اختيارياً لاحقاً)\n- يتم التقاط نوع ملف الإدخال + `mtime` عبر `symlink_metadata`\n\nيتم حل جذور البحث بواسطة `resolve_search_path`:\n\n- يتم حل المسارات النسبية مقابل مجلد العمل الحالي (cwd)\n- يجب أن يكون الهدف مجلداً موجوداً\n- يتم تعيير الجذر عند الإمكان\n\n## سياسة الحداثة والإخلاء\n\nالسياسة العامة (قابلة للتجاوز عبر متغيرات البيئة):\n\n- `FS_SCAN_CACHE_TTL_MS` (الافتراضي `1000`)\n- `FS_SCAN_EMPTY_RECHECK_MS` (الافتراضي `200`)\n- `FS_SCAN_CACHE_MAX_ENTRIES` (الافتراضي `16`)\n\nالسلوك:\n\n- `get_or_scan(...)`\n  - إذا كان TTL يساوي `0`: تجاوز الذاكرة المؤقتة بالكامل، مسح جديد دائماً (`cache_age_ms = 0`)\n  - عند إصابة الذاكرة المؤقتة ضمن TTL: إرجاع الإدخالات المخزنة + `cache_age_ms` غير صفري\n  - عند إصابة منتهية الصلاحية: إخلاء المفتاح، إعادة المسح، تخزين إدخال جديد\n- يتم فرض الحد الأقصى للإدخالات عبر إخلاء الأقدم أولاً حسب `created_at`\n\n## إعادة الفحص السريع للنتائج الفارغة (منفصل عن الإصابات العادية)\n\nإصابة الذاكرة المؤقتة العادية:\n\n- إصابة الذاكرة المؤقتة ضمن TTL تُرجع الإدخالات المخزنة ولا تفعل شيئاً آخر.\n\nإعادة الفحص السريع للنتائج الفارغة:\n\n- هذه سياسة **من جانب المُستدعي** باستخدام `ScanResult.cache_age_ms`\n- إذا كانت النتيجة المصفاة/المستعلمة فارغة وعمر المسح المخزن مؤقتاً يبلغ على الأقل `empty_recheck_ms()`، يقوم المُستدعي بعملية `force_rescan(...)` واحدة ويعيد المحاولة\n- يهدف إلى تقليل النتائج السلبية المنتهية الصلاحية عند إضافة ملفات مؤخراً لكن الذاكرة المؤقتة لا تزال ضمن TTL\n\nالمستهلكون الحاليون:\n\n- `glob`: يعيد الفحص عندما تكون المطابقات المصفاة فارغة ويتجاوز عمر المسح الحد الأدنى\n- `fuzzyFind` (`fd.rs`): يعيد الفحص فقط عندما يكون الاستعلام غير فارغ والمطابقات المُسجَّلة فارغة\n- `grep`: يعيد الفحص عندما تكون قائمة الملفات المرشحة المختارة فارغة\n\n## الإعدادات الافتراضية للمستهلكين واستخدام الذاكرة المؤقتة\n\nالذاكرة المؤقتة اختيارية في جميع واجهات برمجة التطبيقات المكشوفة (`cache?: boolean`، الافتراضي `false`).\n\nالإعدادات الافتراضية الحالية في واجهات برمجة التطبيقات الأصلية:\n\n- `glob`: `hidden=false`، `gitignore=true`، `cache=false`\n- `fuzzyFind`: `hidden=false`، `gitignore=true`، `cache=false`\n- `grep`: `hidden=true`، `cache=false`، ومسح الذاكرة المؤقتة يستخدم دائماً `use_gitignore=true`\n\nمُستدعو coding-agent حالياً:\n\n- اكتشاف مرشحي الإشارات عالي الحجم يُفعّل الذاكرة المؤقتة:\n  - `packages/coding-agent/src/utils/file-mentions.ts`\n  - الملف الشخصي: `hidden=true`، `gitignore=true`، `includeNodeModules=true`، `cache=true`\n- تكامل `grep` على مستوى الأداة يُعطّل حالياً ذاكرة المسح المؤقتة (`cache: false`):\n  - `packages/coding-agent/src/tools/grep.ts`\n\n## عقد الإبطال\n\nنقطة دخول الإبطال الأصلية:\n\n- `invalidateFsScanCache(path?: string)`\n  - مع `path`: إزالة إدخالات الذاكرة المؤقتة التي يكون جذرها بادئة للمسار المستهدف\n  - بدون path: مسح جميع إدخالات ذاكرة المسح المؤقتة\n\nتفاصيل معالجة المسار:\n\n- يتم حل مسارات الإبطال النسبية مقابل cwd\n- يحاول الإبطال التعيير\n- إذا كان الهدف غير موجود (مثل الحذف)، يتم تعيير المجلد الأب كبديل وإعادة إرفاق اسم الملف عند الإمكان\n- هذا يحافظ على سلوك الإبطال لعمليات الإنشاء/الحذف/إعادة التسمية حيث قد لا يكون أحد الطرفين موجوداً\n\n## مسؤوليات تدفق التعديلات في coding-agent\n\nيجب أن يُبطل كود coding-agent بعد تعديلات نظام الملفات الناجحة.\n\nالمساعدات المركزية:\n\n- `invalidateFsScanAfterWrite(path)`\n- `invalidateFsScanAfterDelete(path)`\n- `invalidateFsScanAfterRename(oldPath, newPath)` (يُبطل كلا الطرفين عندما تختلف المسارات)\n\nمواقع استدعاء أدوات التعديل الحالية:\n\n- `packages/coding-agent/src/tools/write.ts`\n- `packages/coding-agent/src/patch/index.ts` (تدفقات hashline/patch/replace)\n\nالقاعدة: إذا عدّل تدفق ما محتوى أو موقع نظام الملفات وتجاوز هذه المساعدات، فإن أخطاء انتهاء صلاحية الذاكرة المؤقتة متوقعة.\n\n## إضافة مستهلك جديد للذاكرة المؤقتة بأمان\n\nعند تقديم استخدام الذاكرة المؤقتة في مسار ماسح/بحث جديد:\n\n1. **استخدم مدخلات سياسة مسح مستقرة**\n   - قرر دلالات hidden/gitignore أولاً\n   - مررها بشكل متسق إلى `get_or_scan`/`force_rescan` بحيث تكون أقسام الذاكرة المؤقتة مقصودة\n\n2. **تعامل مع بيانات الذاكرة المؤقتة على أنها مُصفاة مسبقاً فقط حسب سياسة الاجتياز**\n   - طبّق التصفية الخاصة بالأداة (أنماط glob، مرشحات النوع، قواعد node_modules) بعد الاسترجاع\n   - لا تفترض أبداً أن الإدخالات المخزنة مؤقتاً تعكس بالفعل مرشحاتك عالية المستوى\n\n3. **نفّذ إعادة الفحص السريع للنتائج الفارغة فقط لمخاطر النتائج السلبية المنتهية الصلاحية**\n   - استخدم `scan.cache_age_ms >= empty_recheck_ms()`\n   - أعد المحاولة مرة واحدة مع `force_rescan(..., store=true, ...)`\n   - أبقِ هذا المسار منفصلاً عن منطق إصابة الذاكرة المؤقتة العادية\n\n4. **احترم وضع عدم التخزين المؤقت صراحةً**\n   - عندما يُعطّل المُستدعي الذاكرة المؤقتة، استدعِ `force_rescan(..., store=false, ...)`\n   - لا تملأ الذاكرة المؤقتة المشتركة في مسار طلب بدون تخزين مؤقت\n\n5. **اربط إبطال التعديلات لأي مسار كتابة جديد**\n   - بعد نجاح الكتابة/التحرير/الحذف/إعادة التسمية، استدعِ مساعد إبطال coding-agent\n   - لإعادة التسمية/النقل، أبطل كلاً من المسار القديم والجديد\n\n6. **لا تُضف مقابض TTL لكل استدعاء**\n   - العقد الحالي هو سياسة عامة فقط (مُكوَّنة عبر البيئة)، بدون تجاوز TTL لكل طلب\n\n## الحدود المعروفة\n\n- نطاق الذاكرة المؤقتة محلي للعملية في الذاكرة (`DashMap`)، ولا يُحفظ عبر إعادة تشغيل العملية.\n- تخزن الذاكرة المؤقتة إدخالات المسح، وليس نتائج الأداة النهائية.\n- يتشارك `glob`/`fuzzyFind`/`grep` إدخالات المسح فقط عندما تتطابق أبعاد المفتاح (`root`، `hidden`، `gitignore`).\n- يُستبعد `.git` دائماً في وقت جمع المسح بغض النظر عن خيارات المُستدعي.\n",
	"ar/configuration/hooks.md": "---\ntitle: الخطافات (Hooks)\ndescription: نظام الخطافات للأتمتة قبل/بعد الأحداث في دورة حياة عميل الترميز.\nsidebar:\n  order: 4\n  label: الخطافات (Hooks)\ni18n:\n  sourceHash: cdbec10bc405\n  translator: machine\n---\n\n# الخطافات (Hooks)\n\nيصف هذا المستند **كود نظام الخطافات الحالي** في `src/extensibility/hooks/*`.\n\n## الحالة الراهنة في وقت التشغيل\n\nلا يزال حزمة الخطافات (`src/extensibility/hooks/`) مُصدَّرة وقابلة للاستخدام كسطح API، غير أن وقت تشغيل واجهة سطر الأوامر الافتراضي يُهيّئ الآن مسار **مشغّل الامتدادات**. في تدفق بدء التشغيل الحالي:\n\n- يُعامَل `--hook` كاسم مستعار لـ `--extension` (يتم دمج مسارات واجهة سطر الأوامر في `additionalExtensionPaths`)\n- تُغلَّف الأدوات بواسطة `ExtensionToolWrapper`، لا `HookToolWrapper`\n- تمر تحويلات السياق وانبعاثات دورة الحياة عبر `ExtensionRunner`\n\nلذلك يوثّق هذا الملف تطبيق نظام الخطافات نفسه (الأنواع/المحمّل/المشغّل/الغلاف)، بما في ذلك السلوك القديم والقيود.\n\n## الملفات الرئيسية\n\n- `src/extensibility/hooks/types.ts` — سياق الخطاف، وأنواع الأحداث، وعقود النتائج\n- `src/extensibility/hooks/loader.ts` — تحميل الوحدات وجسر اكتشاف الخطافات\n- `src/extensibility/hooks/runner.ts` — إرسال الأحداث، والبحث عن الأوامر، وإشارة الأخطاء\n- `src/extensibility/hooks/tool-wrapper.ts` — غلاف اعتراض الأدوات قبل/بعد التنفيذ\n- `src/extensibility/hooks/index.ts` — الصادرات/إعادة الصادرات\n\n## ما هي وحدة الخطاف\n\nيجب أن تصدّر وحدة الخطاف تصديرًا افتراضيًا لمصنع:\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function hook(pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName === \"bash\" && String(event.input.command ?? \"\").includes(\"rm -rf\")) {\n   return { block: true, reason: \"blocked by policy\" };\n  }\n });\n}\n```\n\nيمكن للمصنع:\n\n- تسجيل معالجات الأحداث باستخدام `pi.on(...)`\n- إرسال رسائل مخصصة دائمة باستخدام `pi.sendMessage(...)`\n- استمرار الحالة غير المرتبطة بنموذج اللغة الكبير باستخدام `pi.appendEntry(...)`\n- تسجيل أوامر الشرطة المائلة عبر `pi.registerCommand(...)`\n- تسجيل عارضات رسائل مخصصة عبر `pi.registerMessageRenderer(...)`\n- تشغيل أوامر الصدفة عبر `pi.exec(...)`\n\n## الاكتشاف والتحميل\n\nتقوم `discoverAndLoadHooks(configuredPaths, cwd)` بما يلي:\n\n1. تحميل الخطافات المكتشفة من سجل القدرات (`loadCapability(\"hooks\")`)\n2. إلحاق المسارات المُهيَّأة صراحةً (مع إزالة التكرار وفق المسار المطلق)\n3. استدعاء `loadHooks(allPaths, cwd)`\n\nتستورد `loadHooks` بعد ذلك كل مسار وتتوقع وجود دالة `default`.\n\n### تحليل المسارات\n\nيحلّ `loader.ts` مسارات الخطافات على النحو التالي:\n\n- المسار المطلق: يُستخدم كما هو\n- مسار `~`: يُوسَّع\n- المسار النسبي: يُحلّ بالنسبة إلى `cwd`\n\n### عدم التطابق مع النظام القديم\n\nلا تزال موفرو الاكتشاف لـ `hookCapability` تُنمذج ملفات خطافات الصدفة الأسلوب قبل/بعد (مثلاً `.claude/hooks/pre/*`، `.xcsh/.../hooks/pre/*`).\n\nيستخدم محمّل الخطافات هنا استيراد الوحدة الديناميكي ويتطلب وجود مصنع خطاف JS/TS افتراضي. إذا كان مسار الخطاف المكتشف غير قابل للاستيراد كوحدة، يفشل التحميل ويُبلَّغ عنه في `LoadHooksResult.errors`.\n\n## أسطح الأحداث\n\nأحداث الخطافات مكتوبة بشكل صارم في `types.ts`.\n\n### أحداث الجلسة\n\n- `session_start`\n- `session_before_switch` → يمكن أن يُرجع `{ cancel?: boolean }`\n- `session_switch`\n- `session_before_branch` → يمكن أن يُرجع `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_branch`\n- `session_before_compact` → يمكن أن يُرجع `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session.compacting` → يمكن أن يُرجع `{ context?: string[]; prompt?: string; preserveData?: Record<string, unknown> }`\n- `session_compact`\n- `session_before_tree` → يمكن أن يُرجع `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n- `session_tree`\n- `session_shutdown`\n\n### أحداث العامل/السياق\n\n- `context` → يمكن أن يُرجع `{ messages?: Message[] }`\n- `before_agent_start` → يمكن أن يُرجع `{ message?: { customType; content; display; details } }`\n- `agent_start`\n- `agent_end`\n- `turn_start`\n- `turn_end`\n- `auto_compaction_start`\n- `auto_compaction_end`\n- `auto_retry_start`\n- `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### أحداث الأدوات (نموذج قبل/بعد)\n\n- `tool_call` (قبل التنفيذ) → يمكن أن يُرجع `{ block?: boolean; reason?: string }`\n- `tool_result` (بعد التنفيذ) → يمكن أن يُرجع `{ content?; details?; isError? }`\n\nهذا هو النموذج الأساسي للاعتراض قبل/بعد في نظام الخطافات.\n\n```text\nتدفق اعتراض خطاف الأداة\n\nمعالجات tool_call\n   │\n   ├─ أي { block: true }؟ ── نعم ──> رمي استثناء (الأداة محظورة)\n   │\n   └─ لا\n      │\n      ▼\n   تنفيذ الأداة الأساسية\n      │\n      ├─ نجاح ──> يمكن لمعالجات tool_result تجاوز { content, details }\n      │\n      └─ خطأ   ──> إصدار tool_result(isError=true) ثم إعادة رمي الخطأ الأصلي\n```\n\n## نموذج التنفيذ ودلالات التحويل\n\n### 1) قبل التنفيذ: `tool_call`\n\nتُصدر `HookToolWrapper.execute()` حدث `tool_call` قبل تنفيذ الأداة.\n\n- إذا أرجع أي معالج `{ block: true }`، يتوقف التنفيذ\n- إذا رمى المعالج استثناءً، يفشل الغلاف بأمان ويحظر التنفيذ\n- يصبح `reason` المُرجَع نص الخطأ المرمي\n\n### 2) تنفيذ الأداة\n\nتُنفَّذ الأداة الأساسية بشكل طبيعي إذا لم تُحظر.\n\n### 3) بعد التنفيذ: `tool_result`\n\nبعد النجاح، يُصدر الغلاف حدث `tool_result` مع:\n\n- `toolName`، `toolCallId`، `input`\n- `content`\n- `details`\n- `isError: false`\n\nإذا أرجع المعالج تجاوزات:\n\n- يمكن لـ `content` استبدال محتوى النتيجة\n- يمكن لـ `details` استبدال تفاصيل النتيجة\n\nعند فشل الأداة، يُصدر الغلاف حدث `tool_result` مع `isError: true` ومحتوى نص الخطأ، ثم يُعيد رمي الخطأ الأصلي.\n\n### ما يمكن للخطافات تحويله\n\n- سياق نموذج اللغة الكبير لاستدعاء واحد عبر `context` (سلسلة استبدال `messages`)\n- محتوى/تفاصيل مخرجات الأداة عند نجاح استدعاءات الأداة (مسار `tool_result`)\n- الرسالة المُحقونة قبل بدء العامل عبر `before_agent_start`\n- سلوك الإلغاء/الضغط المخصص/الشجرة عبر `session_before_*` و`session.compacting`\n\n### ما لا يمكن للخطافات تحويله في هذا التطبيق\n\n- معاملات إدخال الأداة الخام في مكانها (الحظر/السماح فقط على `tool_call`)\n- استمرار التنفيذ بعد أخطاء الأداة المرمية (مسار الخطأ يُعيد الرمي)\n- حالة النجاح/الخطأ النهائية في سلوك الغلاف (النوع المُرجَع `isError` لا يُطبَّق بواسطة `HookToolWrapper`)\n\n## الترتيب وسلوك التعارض\n\n### ترتيب مستوى الاكتشاف\n\nيُرتَّب موفرو القدرات حسب الأولوية (الأعلى أولاً). إزالة التكرار تكون بمفتاح القدرة، الأول يفوز.\n\nبالنسبة لـ `hooks`، مفتاح القدرة هو `${type}:${tool}:${name}`. التكرارات المُظلَّلة من الموفرين ذوي الأولوية الأدنى مُعلَّمة ومستبعدة من القائمة المكتشفة الفعّالة.\n\n### ترتيب التحميل\n\nتبني `discoverAndLoadHooks` قائمة `allPaths` مسطحة، مُزالة التكرار بالمسار المطلق المحلول، ثم تكرر `loadHooks` بذلك الترتيب.\nيعتمد ترتيب الملفات داخل كل دليل مكتشف على مخرجات `readdir`؛ لا يُجري محمّل الخطافات ترتيبًا إضافيًا.\n\n### ترتيب المعالجات في وقت التشغيل\n\nداخل `HookRunner`، الترتيب محدد بتسلسل التسجيل:\n\n1. ترتيب مصفوفة الخطافات\n2. ترتيب تسجيل المعالجات لكل خطاف/حدث\n\nسلوك التعارض حسب نوع الحدث:\n\n- `tool_call`: آخر نتيجة مُرجَعة تفوز ما لم يحظر أحد المعالجات؛ أول حظر يُقصر الدائرة\n- `tool_result`: آخر تجاوز مُرجَع يفوز (بدون تقصير الدائرة)\n- `context`: متسلسل؛ كل معالج يستقبل مخرجات رسائل المعالج السابق\n- `before_agent_start`: أول رسالة مُرجَعة تُحفظ؛ الرسائل اللاحقة تُتجاهل\n- `session_before_*`: يُتتبَّع آخر نتيجة مُرجَعة؛ `cancel: true` يُقصر الدائرة فورًا\n- `session.compacting`: آخر نتيجة مُرجَعة تفوز\n\nتعارضات الأوامر/العارضات:\n\n- يُرجع `getCommand(name)` أول تطابق عبر الخطافات (الأول المحمَّل يفوز)\n- يُرجع `getMessageRenderer(customType)` أول تطابق\n- يُرجع `getRegisteredCommands()` جميع الأوامر (بدون إزالة تكرار)\n\n## تفاعلات واجهة المستخدم (`HookContext.ui`)\n\nيتضمن `HookUIContext`:\n\n- `select`، `confirm`، `input`، `editor`\n- `notify`\n- `setStatus`\n- `custom`\n- `setEditorText`، `getEditorText`\n- مُحصِّل `theme`\n\nيشير `ctx.hasUI` إلى ما إذا كانت واجهة المستخدم التفاعلية متاحة.\n\nعند التشغيل بدون واجهة مستخدم، يكون سلوك السياق الافتراضي عديم التأثير:\n\n- تُرجع `select/input/editor` القيمة `undefined`\n- تُرجع `confirm` القيمة `false`\n- `notify` و`setStatus` و`setEditorText` لا تؤثر على شيء\n- تُرجع `getEditorText` القيمة `\"\"`\n\n### سلوك سطر الحالة\n\nنص حالة الخطاف المُعيَّن عبر `ctx.ui.setStatus(key, text)`:\n\n- يُخزَّن لكل مفتاح\n- يُرتَّب حسب اسم المفتاح\n- يُعقَّم (`\\r`، `\\n`، `\\t` → مسافات؛ تُطوى المسافات المتكررة)\n- يُدمج ويُقتطع بحسب العرض للعرض\n\n## انتشار الأخطاء والرجوع\n\n### وقت التحميل\n\n- وحدة غير صالحة أو تصدير افتراضي مفقود → يُلتقط في `LoadHooksResult.errors`\n- يستمر التحميل للخطافات الأخرى\n\n### وقت الحدث\n\nتلتقط `HookRunner.emit(...)` أخطاء المعالجات لمعظم الأحداث وتُصدر `HookError` للمستمعين (`hookPath`، `event`، `error`)، ثم تستمر.\n\n`emitToolCall(...)` أكثر صرامة: لا تُبتلع أخطاء المعالجات هنا؛ بل تنتشر إلى المُستدعي. في `HookToolWrapper`، يحظر هذا استدعاء الأداة (الفشل الآمن).\n\n## أمثلة API واقعية\n\n### حظر أوامر bash غير الآمنة\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName !== \"bash\") return;\n  const cmd = String(event.input.command ?? \"\");\n  if (!cmd.includes(\"rm -rf\")) return;\n\n  if (!ctx.hasUI) return { block: true, reason: \"rm -rf blocked (no UI)\" };\n  const ok = await ctx.ui.confirm(\"Dangerous command\", `Allow: ${cmd}`);\n  if (!ok) return { block: true, reason: \"user denied command\" };\n });\n}\n```\n\n### تنقيح مخرجات الأداة بعد التنفيذ\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_result\", async event => {\n  if (event.toolName !== \"read\" || event.isError) return;\n\n  const redacted = event.content.map(chunk => {\n   if (chunk.type !== \"text\") return chunk;\n   return { ...chunk, text: chunk.text.replaceAll(/API_KEY=\\S+/g, \"API_KEY=[REDACTED]\") };\n  });\n\n  return { content: redacted };\n });\n}\n```\n\n### تعديل سياق النموذج لكل استدعاء نموذج اللغة الكبير\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"context\", async event => {\n  const filtered = event.messages.filter(msg => !(msg.role === \"custom\" && msg.customType === \"debug-only\"));\n  return { messages: filtered };\n });\n}\n```\n\n### تسجيل أمر الشرطة المائلة مع أساليب سياق آمنة للأوامر\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.registerCommand(\"handoff\", {\n  description: \"Create a new session with setup message\",\n  handler: async (_args, ctx) => {\n   await ctx.waitForIdle();\n   await ctx.newSession({\n    parentSession: ctx.sessionManager.getSessionFile(),\n    setup: async sm => {\n     sm.appendMessage({\n      role: \"user\",\n      content: [{ type: \"text\", text: \"Continue from prior session summary.\" }],\n      timestamp: Date.now(),\n     });\n    },\n   });\n  },\n });\n}\n```\n\n## سطح التصدير\n\nيُصدر `src/extensibility/hooks/index.ts`:\n\n- واجهات برمجة التحميل (`discoverAndLoadHooks`، `loadHooks`)\n- المشغّل والغلاف (`HookRunner`، `HookToolWrapper`)\n- جميع أنواع الخطافات\n- إعادة تصدير `execCommand`\n\nويُعيد جذر الحزمة (`src/index.ts`) تصدير **أنواع** الخطافات كسطح توافق قديم.\n",
	"ar/configuration/porting-from-pi-mono.md": "---\ntitle: 'النقل من pi-mono: دليل عملي للدمج'\ndescription: دليل عملي لترحيل الكود من مستودع pi-mono الأحادي إلى قاعدة كود xcsh.\nsidebar:\n  order: 9\n  label: النقل من pi-mono\ni18n:\n  sourceHash: fd4e8c09303d\n  translator: machine\n---\n\n# النقل من pi-mono: دليل عملي للدمج\n\nهذا الدليل عبارة عن قائمة تحقق قابلة للتكرار لنقل التغييرات من pi-mono إلى هذا المستودع.\nاستخدمه لأي عملية دمج: ملف واحد، أو فرع ميزة، أو مزامنة إصدار كاملة.\n\n## نقطة المزامنة الأخيرة\n\n**الالتزام:** `b21b42d032919de2f2e6920a76fa9a37c3920c0a`\n**التاريخ:** 2026-03-22\n\nقم بتحديث هذا القسم بعد كل مزامنة؛ لا تعد استخدام النطاق السابق.\n\nعند بدء مزامنة جديدة، قم بتوليد الرقع من هذا الالتزام فصاعداً:\n\n```bash\ngit format-patch b21b42d032919de2f2e6920a76fa9a37c3920c0a..HEAD --stdout > changes.patch\n```\n\n## 0) حدد النطاق\n\n- حدد المرجع الأصلي (التزام، وسم، أو طلب سحب).\n- اسرد الحزم أو المجلدات التي تخطط للعمل عليها.\n- قرر أي الميزات ضمن النطاق وأيها يتم تخطيها عمداً.\n\n## 1) انقل الكود بأمان\n\n- فضّل فرقاً نظيفاً ومركّزاً بدلاً من النسخ الشامل.\n- تجنب نسخ المخرجات المبنية أو الملفات المولّدة.\n- إذا أضاف المصدر الأصلي ملفات جديدة، أضفها صراحةً وراجع محتوياتها.\n\n## 2) طابق اتفاقيات امتدادات الاستيراد\n\nمعظم ملفات TypeScript التنفيذية تحذف `.js` في الاستيرادات الداخلية، لكن بعض نقاط الدخول للاختبارات/المعايير تحتفظ بـ `.js` لتوافق ESM\nفي وقت التشغيل. اتبع النمط الموجود في الحزمة المحلية؛ لا تقم بإزالة الامتدادات بشكل شامل.\n\n- في ملفات التشغيل في `packages/coding-agent`، احتفظ بالاستيرادات الداخلية بدون امتداد ما لم تستورد أصولاً غير TS.\n- في `packages/tui/test` و `packages/natives/bench`، احتفظ بـ `.js` حيث تستخدمها الملفات المحيطة بالفعل.\n- احتفظ بامتدادات الملفات الحقيقية عندما تتطلبها الأدوات (مثل `.json`، `.css`، تضمينات نصوص `.md`).\n- مثال: `import { x } from \"./foo.js\";` → `import { x } from \"./foo\";` (فقط عندما تكون اتفاقية الحزمة بدون امتداد).\n\n## 3) استبدل نطاقات الاستيراد\n\nيستخدم المصدر الأصلي نطاقات حزم مختلفة. استبدلها بشكل متسق.\n\n- استبدل النطاقات القديمة بالنطاق المحلي المستخدم هنا.\n- أمثلة (عدّلها لتتوافق مع الحزم الفعلية التي تنقلها):\n  - `@mariozechner/pi-coding-agent` → `@f5-sales-demo/xcsh`\n  - `@mariozechner/pi-agent-core` → `@f5-sales-demo/pi-agent-core`\n  - `@mariozechner/pi-tui` → `@f5-sales-demo/pi-tui`\n  - `@mariozechner/pi-ai` → `@f5-sales-demo/pi-ai`\n\n## 4) استخدم واجهات Bun حيث تتفوق على Node\n\nنحن نعمل على Bun. استبدل واجهات Node فقط عندما يوفر Bun بديلاً أفضل.\n\n**قم بالاستبدال:**\n\n- تشغيل العمليات: `child_process.spawn` → Bun Shell `$` للأوامر البسيطة، `Bun.spawn`/`Bun.spawnSync` للتدفق أو العمل طويل الأمد\n- إدخال/إخراج الملفات: `fs.readFileSync` → `Bun.file().text()` / `Bun.write()`\n- عملاء HTTP: `node-fetch`، `axios` → `fetch` الأصلي\n- تجزئة التشفير: `node:crypto` → Web Crypto أو `Bun.hash`\n- SQLite: `better-sqlite3` → `bun:sqlite`\n- تحميل المتغيرات البيئية: `dotenv` → يحمّل Bun ملف `.env` تلقائياً\n\n**لا تستبدل (هذه تعمل بشكل جيد في Bun):**\n\n- `os.homedir()` — لا تستبدلها بـ `Bun.env.HOME` أو `Bun.env.HOME` أو القيمة الحرفية `\"~\"`\n- `os.tmpdir()` — لا تستبدلها بـ `Bun.env.TMPDIR || \"/tmp\"` أو مسارات مشفرة\n- `fs.mkdtempSync()` — لا تستبدلها ببناء المسار يدوياً\n- `path.join()`، `path.resolve()`، إلخ — هذه جيدة كما هي\n\n**نمط الاستيراد:** استخدم بادئة `node:` مع استيرادات مساحة الأسماء فقط (لا استيرادات مسماة من `node:fs` أو `node:path`).\n\n**اتفاقيات Bun الإضافية:**\n\n- فضّل Bun Shell `$` للأوامر القصيرة غير المتدفقة؛ استخدم `Bun.spawn` فقط عندما تحتاج إلى إدخال/إخراج متدفق أو التحكم في العملية.\n- استخدم `Bun.file()`/`Bun.write()` للملفات و `node:fs/promises` للمجلدات.\n- تجنب فحوصات `Bun.file().exists()`؛ استخدم معالجة `isEnoent` في try/catch.\n- فضّل `Bun.sleep(ms)` على أغلفة `setTimeout`.\n\n**خطأ:**\n\n```typescript\n// BROKEN: env vars may be undefined, \"~\" is not expanded\nconst home = Bun.env.HOME || \"~\";\nconst tmp = Bun.env.TMPDIR || \"/tmp\";\n```\n\n**صحيح:**\n\n```typescript\nimport * as os from \"node:os\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst configDir = path.join(os.homedir(), \".config\", \"myapp\");\nconst tempDir = fs.mkdtempSync(path.join(os.tmpdir(), \"myapp-\"));\n```\n\n## 5) فضّل تضمينات Bun (بدون نسخ)\n\nلا تنسخ أصول وقت التشغيل أو ملفات المورّدين في وقت البناء.\n\n- إذا كان المصدر الأصلي ينسخ الأصول إلى مجلد dist، استبدلها بتضمينات متوافقة مع Bun.\n- الموجّهات هي ملفات `.md` ثابتة؛ استخدم استيرادات نصوص Bun (`with { type: \"text\" }`) و Handlebars بدلاً من سلاسل الموجّهات المضمنة.\n- استخدم `import.meta.dir` + `Bun.file` لتحميل الموارد المجاورة غير النصية.\n- احتفظ بالأصول في المستودع ودع المجمّع يضمّنها.\n- تخلص من سكربتات النسخ ما لم يطلبها المستخدم صراحةً.\n- إذا كان المصدر الأصلي يقرأ ملفاً احتياطياً مجمّعاً في وقت التشغيل، استبدل قراءات نظام الملفات باستيراد تضمين نصي من Bun.\n  - مثال (احتياطي تعليمات Codex):\n    - `const FALLBACK_PROMPT_PATH = join(import.meta.dir, \"codex-instructions.md\");` -> تمت إزالته\n    - `import FALLBACK_INSTRUCTIONS from \"./codex-instructions.md\" with { type: \"text\" };`\n    - استخدم `return FALLBACK_INSTRUCTIONS;` بدلاً من `readFileSync(FALLBACK_PROMPT_PATH, \"utf8\")`\n\n## 6) انقل `package.json` بعناية\n\nتعامل مع `package.json` كعقد. ادمج بشكل مقصود.\n\n- احتفظ بـ `name` و `version` و `type` و `exports` و `bin` الحاليين ما لم يتطلب النقل تغييرات.\n- استبدل سكربتات npm/node ببدائل Bun (مثل `bun check`، `bun test`).\n- تأكد من أن التبعيات تستخدم النطاق الصحيح.\n- لا تخفّض إصدارات التبعيات لإصلاح أخطاء الأنواع؛ قم بالترقية بدلاً من ذلك.\n- تحقق من روابط حزم مساحة العمل و `peerDependencies`.\n\n## 7) وحّد أسلوب الكود والأدوات\n\n- حافظ على اتفاقيات التنسيق الحالية.\n- لا تُدخل `any` ما لم يكن مطلوباً.\n- تجنب الاستيرادات الديناميكية واستيرادات الأنواع المضمنة؛ استخدم استيرادات المستوى الأعلى فقط.\n- لا تبنِ الموجّهات في الكود أبداً؛ الموجّهات هي ملفات `.md` ثابتة يتم عرضها باستخدام Handlebars.\n- في coding-agent، لا تستخدم `console.log`/`console.warn`/`console.error` أبداً؛ استخدم `logger` من `@f5-sales-demo/pi-utils`.\n- استخدم `Promise.withResolvers()` بدلاً من `new Promise((resolve, reject) => ...)`.\n- **لا تستخدم الكلمات المفتاحية `private`/`protected`/`public` على حقول الفئة أو الطرق.** استخدم حقول ES الخاصة `#` للتغليف؛ اترك الأعضاء القابلة للوصول بدون كلمة مفتاحية. الاستثناء الوحيد هو خصائص معاملات المُنشئ (`constructor(private readonly x: T)`)، حيث تكون الكلمة المفتاحية مطلوبة بواسطة TypeScript. عند نقل كود المصدر الأصلي الذي يستخدم `private foo` أو `protected bar`، حوّلها إلى `#foo` (خاص) أو `bar` بدون كلمة مفتاحية (قابل للوصول).\n- فضّل المساعدات والأدوات الموجودة على الكود الجديد المخصص.\n- حافظ على تغييرات البنية التحتية الموجهة لـ Bun التي تم إجراؤها بالفعل في هذا المستودع:\n  - وقت التشغيل هو Bun (لا نقاط دخول Node).\n  - مدير الحزم هو Bun (لا ملفات قفل npm).\n  - واجهات Node الثقيلة (`child_process`، `readline`) مستبدلة ببدائل Bun.\n  - واجهات Node الخفيفة (`os.homedir`، `os.tmpdir`، `fs.mkdtempSync`، `path.*`) محفوظة.\n  - سطور shebang لـ CLI تستخدم `bun` (ليس `node`، ليس `tsx`).\n  - الحزم تستخدم ملفات المصدر مباشرة (لا خطوة بناء TypeScript).\n  - سير عمل CI يشغّل Bun للتثبيت/الفحص/الاختبار.\n\n## 8) أزل طبقات التوافق القديمة\n\nما لم يُطلب ذلك، أزل وسائط التوافق الأصلية.\n\n- احذف الواجهات القديمة التي تم استبدالها.\n- حدّث جميع مواقع الاستدعاء للواجهة الجديدة مباشرة.\n- لا تحتفظ بنسخ `*_v2` أو نسخ متوازية.\n\n## 9) حدّث التوثيق والمراجع\n\n- استبدل روابط مستودع pi-mono حيثما كان مناسباً.\n- حدّث الأمثلة لاستخدام Bun ونطاقات الحزم الصحيحة.\n- تأكد من أن تعليمات README لا تزال تتوافق مع سلوك المستودع الحالي.\n\n## 10) تحقق من صحة النقل\n\nشغّل الفحوصات القياسية بعد التغييرات:\n\n- `bun check`\n\nإذا كان المستودع يحتوي بالفعل على فحوصات فاشلة غير مرتبطة بتغييراتك، أشر إلى ذلك.\nالاختبارات تستخدم مشغّل Bun (ليس Vitest)، لكن شغّل `bun test` فقط عند الطلب صراحةً.\n\n## 11) احمِ الميزات المحسّنة (قائمة فخ الانحدار)\n\nإذا كنت قد حسّنت السلوك محلياً بالفعل، تعامل مع تلك التحسينات على أنها **غير قابلة للتفاوض**. قبل النقل، دوّن\nالتحسينات وأضف فحوصات صريحة حتى لا تضيع في الدمج.\n\n- **جمّد السلوك المتوقع**: أضف ملاحظة قصيرة \"قبل/بعد\" لكل تحسين (المدخلات، المخرجات،\n  القيم الافتراضية، الحالات الحدية). هذا يمنع التراجع الصامت.\n- **خريطة القديم → الجديد للواجهات**: إذا أعاد المصدر الأصلي تسمية المفاهيم (hooks → extensions، custom tools → tools، إلخ)،\n  تأكد من أن كل نقطة دخول قديمة لا تزال متصلة. علم أو تصدير واحد مفقود يعني وظائف مفقودة.\n- **تحقق من التصديرات**: تحقق من `exports` في `package.json`، والأنواع العامة، وملفات barrel. عمليات النقل الأصلية غالباً\n  تنسى إعادة تصدير الإضافات المحلية.\n- **غطِّ المسارات غير السعيدة**: إذا أصلحت معالجة الأخطاء، أو المهل الزمنية، أو منطق الاحتياط، أضف اختباراً أو\n  على الأقل قائمة تحقق يدوية تمارس تلك المسارات.\n- **تحقق من الافتراضيات وترتيب دمج التكوين**: التحسينات غالباً تعيش في الافتراضيات. تأكد من أن الافتراضيات الجديدة\n  لم تتراجع (مثل أسبقية التكوين الجديدة، الميزات المعطلة، قوائم الأدوات).\n- **دقق سلوك البيئة/الصدفة**: إذا أصلحت التنفيذ أو العزل، تحقق من أن المسار الجديد لا يزال يستخدم بيئتك\n  المنقّحة ولا يعيد تقديم تجاوزات الأسماء المستعارة/الدوال.\n- **أعد تشغيل عينات مستهدفة**: احتفظ بمجموعة أدنى من الأمثلة \"المعروفة بأنها جيدة\" وشغّلها بعد النقل\n  (أعلام CLI، تسجيل الإضافات، تنفيذ الأدوات).\n\n## 12) اكتشف وتعامل مع الكود المُعاد هيكلته\n\nقبل نقل ملف، تحقق مما إذا كان المصدر الأصلي قد أعاد هيكلته بشكل كبير:\n\n```bash\n# Compare the file you're about to port against what you have locally\ngit diff HEAD upstream/main -- path/to/file.ts\n```\n\nإذا أظهر الفرق أن الملف تمت **إعادة هيكلته** (ليس مجرد ترقيع):\n\n- تجريدات جديدة، مفاهيم مُعاد تسميتها، وحدات مدمجة، تدفق بيانات متغير\n\nفيجب عليك **قراءة التنفيذ الجديد بالكامل** قبل النقل. الدمج الأعمى للكود المُعاد هيكلته يفقد الوظائف لأن:\n\nملاحظة: تم مؤخراً تقسيم الوضع التفاعلي إلى controllers/utils/types. عند نقل التغييرات المرتبطة بأثر رجعي، انقل التحديثات إلى الملفات الفردية التي أنشأناها وتأكد من بقاء توصيل `interactive-mode.ts` متزامناً.\n\n1. **الافتراضيات تتغير بصمت** - متغير جديد `defaultFoo = [a, b]` قد يستبدل `getAllFoo()` القديم الذي كان يعيد `[a, b, c, d, e]`.\n\n2. **خيارات الواجهة تُسقط** - عندما تندمج الأنظمة (مثل `hooks` + `customTools` → `extensions`)، قد لا تتصل الخيارات القديمة بالتنفيذ الجديد.\n\n3. **مسارات الكود تصبح قديمة** - مفهوم مُعاد تسميته (مثل `hookMessage` → `custom`) يحتاج تحديثات في كل عبارة switch، وحارس نوع، ومعالج—ليس فقط التعريف.\n\n4. **السياق/القدرات تتقلص** - الواجهات القديمة ربما كشفت `{ logger, typebox, pi }` التي نسيت الواجهات الجديدة تضمينها.\n\n### عملية النقل الدلالي\n\nعندما يعيد المصدر الأصلي هيكلة وحدة:\n\n1. **اقرأ التنفيذ القديم** - افهم ماذا كان يفعل، ما الخيارات التي قبلها، ما الذي كشفه.\n\n2. **اقرأ التنفيذ الجديد** - افهم التجريدات الجديدة وكيف تتوافق مع السلوك القديم.\n\n3. **تحقق من تكافؤ الميزات** - لكل قدرة في الكود القديم، تأكد من أن الكود الجديد يحافظ عليها أو يزيلها صراحةً.\n\n4. **ابحث عن المتبقيات** - ابحث عن الأسماء/المفاهيم القديمة التي ربما فاتت في عبارات switch، والمعالجات، ومكونات الواجهة.\n\n5. **اختبر الحدود** - أعلام CLI، خيارات SDK، معالجات الأحداث، القيم الافتراضية—هذه هي حيث تختبئ الانحدارات.\n\n### فحوصات سريعة\n\n```bash\n# Find all uses of an old concept that may need updating\nrg \"oldConceptName\" --type ts\n\n# Compare default values between versions\ngit show upstream/main:path/to/file.ts | rg \"default|DEFAULT\"\n\n# Check if all enum/union values have handlers\nrg \"case \\\"\" path/to/file.ts\n```\n\n## 13) قائمة تحقق التدقيق السريع\n\nاستخدم هذه كمرور نهائي قبل الانتهاء:\n\n- [ ] امتدادات الاستيراد تتبع اتفاقية الحزمة المحلية (لا إزالة شاملة لـ `.js`)\n- [ ] لا واجهات Node-فقط في الكود الجديد/المنقول\n- [ ] جميع نطاقات الحزم محدّثة\n- [ ] سكربتات `package.json` تستخدم Bun\n- [ ] الموجّهات هي استيرادات نصوص `.md` (لا سلاسل موجّهات مضمنة)\n- [ ] لا `console.*` في coding-agent (استخدم `logger`)\n- [ ] الأصول تُحمّل عبر أنماط تضمين Bun (لا سكربتات نسخ)\n- [ ] الاختبارات أو الفحوصات تعمل (أو مذكورة صراحةً كمحظورة)\n- [ ] لا انحدارات في الوظائف (انظر الأقسام 11-12)\n\n## 14) صيغة رسالة الالتزام\n\nعند التزام نقل بأثر رجعي، اتبع صيغة المستودع `<type>(scope): <past-tense description>` واحتفظ بنطاق\nالالتزامات في العنوان.\n\n```\nfix(coding-agent): backported pi-mono changes (<from>..<to>)\n\npackages/<package>:\n- <type>: <description>\n- <type>: <description> (#<issue> by @<contributor>)\n\npackages/<other-package>:\n- <type>: <description>\n```\n\n**مثال:**\n\n```\nfix(coding-agent): backported pi-mono changes (9f3eef65f..52532c7c0)\n\npackages/ai:\n- fix: handle \"sensitive\" stop reason from Anthropic API\n- fix: normalize tool call IDs with special characters for Responses API\n- fix: add overflow detection for Bedrock, MiniMax, Kimi providers\n- fix: 429 status is rate limiting, not context overflow\n\npackages/tui:\n- fix: refactored autocomplete state tracking\n- fix: file autocomplete should not trigger on empty text\n- fix: configurable autocomplete max visible items\n- fix: improved table column width calculation with word-aware wrapping\n\npackages/coding-agent:\n- fix: preserve external config.yml edits on save (#1046 by @nicobailonMD)\n- fix: resolve macOS NFD and curly quote variants in file paths\n```\n\n**القواعد:**\n\n- جمّع التغييرات حسب الحزمة\n- استخدم أنواع الالتزام التقليدية (`fix`، `feat`، `refactor`، `perf`، `docs`)\n- ضمّن أرقام المشاكل/طلبات السحب الأصلية ونسبة المساهمين للمساهمات الخارجية\n- نطاق الالتزامات في العنوان يساعد في تتبع نقاط المزامنة\n\n## 15) الاختلافات المقصودة\n\nيحتوي فرعنا على قرارات معمارية تختلف عن المصدر الأصلي. **لا تنقل هذه الأنماط الأصلية:**\n\n### بنية واجهة المستخدم\n\n| المصدر الأصلي                                    | فرعنا                                                  | السبب                                                                |\n| ------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------- |\n| فئة `FooterDataProvider`                  | `StatusLineComponent`                                     | سطر حالة أبسط ومتكامل                                       |\n| `ctx.ui.setHeader()` / `ctx.ui.setFooter()` | وسيط فارغ في الأوضاع غير TUI                                     | مُنفّذ في TUI، بدون عملية في غيره                                   |\n| `ctx.ui.setEditorComponent()`               | وسيط فارغ في الأوضاع غير TUI                                     | مُنفّذ في TUI، بدون عملية في غيره                                   |\n| كائن خيارات `InteractiveModeOptions`     | معاملات مُنشئ موضعية (نوع الخيارات لا يزال مُصدّراً) | حافظ على توقيع المُنشئ؛ حدّث النوع عندما يضيف المصدر الأصلي حقولاً |\n\n### تسمية المكونات\n\n| المصدر الأصلي                     | فرعنا                |\n| ---------------------------- | ----------------------- |\n| `extension-input.ts`         | `hook-input.ts`         |\n| `extension-selector.ts`      | `hook-selector.ts`      |\n| `ExtensionInputComponent`    | `HookInputComponent`    |\n| `ExtensionSelectorComponent` | `HookSelectorComponent` |\n\n### تسمية الواجهات\n\n| المصدر الأصلي                                 | فرعنا                                 | ملاحظات                                     |\n| ---------------------------------------- | ---------------------------------------- | ----------------------------------------- |\n| `sessionManager.appendSessionInfo(name)` | `sessionManager.setSessionName(name)`    | نستخدم `sessionName` في كل مكان           |\n| `sessionManager.getSessionName()`        | `sessionManager.getSessionName()`        | نفسه (وحّدنا ليتوافق مع RPC الأصلي) |\n| `agent.sessionName` / `setSessionName()` | `agent.sessionName` / `setSessionName()` | نفسه                                      |\n\n### دمج الملفات\n\n| المصدر الأصلي                                           | فرعنا                                | السبب                                  |\n| -------------------------------------------------- | --------------------------------------- | --------------------------------------- |\n| `clipboard.ts` + `clipboard-image.ts` (ملفات أدوات) | وحدة الحافظة في `@f5-sales-demo/pi-natives` | مدمجة في تنفيذ N-API الأصلي |\n\n### إطار الاختبار\n\n| المصدر الأصلي                  | فرعنا                      |\n| ------------------------- | ----------------------------- |\n| `vitest` مع `vi.mock()` | `bun:test` مع `vi` من bun |\n| تأكيدات `node:test`    | مطابقات `expect()`           |\n\n### بنية الأدوات\n\n| المصدر الأصلي                            | فرعنا                                                          | ملاحظات                                                     |\n| ----------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------- |\n| `createTool(cwd: string, options?)` | `createTools(session: ToolSession)` عبر سجل `BUILTIN_TOOLS`  | مصانع الأدوات تقبل `ToolSession` ويمكن أن تعيد `null` |\n| واجهات `*Operations` لكل أداة   | واجهات كل أداة تبقى (`FindOperations`، `GrepOperations`)   | تُستخدم لتجاوزات SSH/البعيد                             |\n| `fs/promises` في Node.js في كل مكان    | `Bun.file()`/`Bun.write()` للملفات؛ `node:fs/promises` للمجلدات | فضّل واجهات Bun عندما تبسّط                        |\n\n### تخزين المصادقة\n\n| المصدر الأصلي                        | فرعنا                                    | ملاحظات                                        |\n| ------------------------------- | ------------------------------------------- | -------------------------------------------- |\n| `proper-lockfile` + `auth.json` | `agent.db` (bun:sqlite)                     | بيانات الاعتماد مخزنة حصرياً في `agent.db` |\n| بيانات اعتماد واحدة لكل مزوّد  | بيانات اعتماد متعددة مع اختيار دوري | منطق تقارب الجلسة والتراجع محفوظ |\n\n### الإضافات\n\n| المصدر الأصلي                      | فرعنا                                   |\n| ----------------------------- | ------------------------------------------ |\n| `jiti` لتحميل TypeScript | `import()` الأصلي في Bun                      |\n| حقل `pkg.pi` في البيان       | `pkg.xcsh ?? pkg.pi` (فضّل مساحة أسمائنا) |\n\n### تخطَّ هذه الميزات الأصلية\n\nعند النقل، **تخطَّ** هذه الملفات/الميزات بالكامل:\n\n- `footer-data-provider.ts` — نستخدم StatusLineComponent\n- `clipboard-image.ts` — الحافظة في وحدة N-API في `@f5-sales-demo/pi-natives`\n- ملفات سير عمل GitHub — لدينا CI خاص بنا\n- `models.generated.ts` — مولّد تلقائياً، أعد توليده محلياً (كـ models.json بدلاً من ذلك)\n\n### ميزات أضفناها (احفظها)\n\nهذه موجودة في فرعنا ولكن ليست في المصدر الأصلي. **لا تكتب فوقها أبداً:**\n\n- `StatusLineComponent` في الوضع التفاعلي\n- مصادقة متعددة بيانات الاعتماد مع تقارب الجلسة\n- نظام الاكتشاف القائم على القدرات (`defineCapability`، `registerProvider`، `loadCapability`، `skillCapability`، إلخ)\n- تكاملات MCP/Exa/SSH\n- الكتابة المباشرة عبر LSP للتنسيق عند الحفظ\n- اعتراض Bash (`checkBashInterception`)\n- اقتراحات المسارات الضبابية في أداة القراءة\n",
	"ar/configuration/rpc.md": "---\ntitle: مرجع بروتوكول RPC\ndescription: مرجع بروتوكول JSON-RPC للاتصال بين العمليات بين مكونات xcsh.\nsidebar:\n  order: 5\n  label: بروتوكول RPC\ni18n:\n  sourceHash: b4a3ddaf08ab\n  translator: machine\n---\n\n# مرجع بروتوكول RPC\n\nيُشغّل وضع RPC وكيل البرمجة كبروتوكول JSON محدد بأسطر جديدة عبر stdio.\n\n- **stdin**: الأوامر (`RpcCommand`) واستجابات واجهة المستخدم للإضافات\n- **stdout**: استجابات الأوامر (`RpcResponse`)، أحداث الجلسة/الوكيل، طلبات واجهة المستخدم للإضافات\n\nالتنفيذ الأساسي:\n\n- `src/modes/rpc/rpc-mode.ts`\n- `src/modes/rpc/rpc-types.ts`\n- `src/session/agent-session.ts`\n- `packages/agent/src/agent.ts`\n- `packages/agent/src/agent-loop.ts`\n\n## بدء التشغيل\n\n```bash\nxcsh --mode rpc [regular CLI options]\n```\n\nملاحظات حول السلوك:\n\n- يتم رفض وسائط CLI من نوع `@file` في وضع RPC.\n- يُعطّل وضع RPC توليد عنوان الجلسة التلقائي افتراضيًا لتجنب استدعاء إضافي للنموذج.\n- يُعيد وضع RPC تعيين إعدادات `todo.*` و `task.*` و `async.*` المؤثرة على سير العمل إلى قيمها الافتراضية المدمجة بدلاً من وراثة تخصيصات المستخدم.\n- تقرأ العملية stdin كـ JSONL (`readJsonl(Bun.stdin.stream())`).\n- عند إغلاق stdin، تنتهي العملية برمز خروج `0`.\n- تُكتب الاستجابات/الأحداث ككائن JSON واحد لكل سطر.\n\n## النقل والتأطير\n\nكل إطار هو كائن JSON واحد متبوع بـ `\\n`.\n\nلا يوجد غلاف إضافي يتجاوز شكل الكائن نفسه.\n\n### فئات الإطارات الصادرة (stdout)\n\n1. `RpcResponse` (`{ type: \"response\", ... }`)\n2. كائنات `AgentSessionEvent` (`agent_start`، `message_update`، إلخ.)\n3. `RpcExtensionUIRequest` (`{ type: \"extension_ui_request\", ... }`)\n4. أخطاء الإضافات (`{ type: \"extension_error\", extensionPath, event, error }`)\n\n### فئات الإطارات الواردة (stdin)\n\n1. `RpcCommand`\n2. `RpcExtensionUIResponse` (`{ type: \"extension_ui_response\", ... }`)\n\n## ربط الطلب/الاستجابة\n\nتقبل جميع الأوامر `id?: string` اختياري.\n\n- إذا تم توفيره، تُعيد استجابات الأوامر العادية نفس `id`.\n- يعتمد `RpcClient` على هذا لحل الطلبات المعلقة.\n\nسلوك حدي مهم من وقت التشغيل:\n\n- تُصدر استجابات الأوامر غير المعروفة بـ `id: undefined` (حتى لو كان للطلب `id`).\n- استثناءات التحليل/المعالج في حلقة الإدخال تُصدر `command: \"parse\"` بـ `id: undefined`.\n- يُعيد `prompt` و `abort_and_prompt` نجاحًا فوريًا، ثم قد يُصدران استجابة خطأ لاحقة بنفس **الـ id** إذا فشلت جدولة الطلب غير المتزامنة.\n\n## مخطط الأوامر (المرجعي)\n\n`RpcCommand` مُعرّف في `src/modes/rpc/rpc-types.ts`:\n\n### الطلبات\n\n- `{ id?, type: \"prompt\", message: string, images?: ImageContent[], streamingBehavior?: \"steer\" | \"followUp\" }`\n- `{ id?, type: \"steer\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"follow_up\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"abort\" }`\n- `{ id?, type: \"abort_and_prompt\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"new_session\", parentSession?: string }`\n\n### الحالة\n\n- `{ id?, type: \"get_state\" }`\n- `{ id?, type: \"set_todos\", phases: TodoPhase[] }`\n- `{ id?, type: \"set_host_tools\", tools: RpcHostToolDefinition[] }`\n\n### النموذج\n\n- `{ id?, type: \"set_model\", provider: string, modelId: string }`\n- `{ id?, type: \"cycle_model\" }`\n- `{ id?, type: \"get_available_models\" }`\n\n### التفكير\n\n- `{ id?, type: \"set_thinking_level\", level: ThinkingLevel }`\n- `{ id?, type: \"cycle_thinking_level\" }`\n\n### أوضاع قائمة الانتظار\n\n- `{ id?, type: \"set_steering_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_follow_up_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_interrupt_mode\", mode: \"immediate\" | \"wait\" }`\n\n### الضغط\n\n- `{ id?, type: \"compact\", customInstructions?: string }`\n- `{ id?, type: \"set_auto_compaction\", enabled: boolean }`\n\n### إعادة المحاولة\n\n- `{ id?, type: \"set_auto_retry\", enabled: boolean }`\n- `{ id?, type: \"abort_retry\" }`\n\n### Bash\n\n- `{ id?, type: \"bash\", command: string }`\n- `{ id?, type: \"abort_bash\" }`\n\n### الجلسة\n\n- `{ id?, type: \"get_session_stats\" }`\n- `{ id?, type: \"export_html\", outputPath?: string }`\n- `{ id?, type: \"switch_session\", sessionPath: string }`\n- `{ id?, type: \"branch\", entryId: string }`\n- `{ id?, type: \"get_branch_messages\" }`\n- `{ id?, type: \"get_last_assistant_text\" }`\n- `{ id?, type: \"set_session_name\", name: string }`\n\n### الرسائل\n\n- `{ id?, type: \"get_messages\" }`\n\n## مخطط الاستجابة\n\nتستخدم جميع نتائج الأوامر `RpcResponse`:\n\n- نجاح: `{ id?, type: \"response\", command: <command>, success: true, data?: ... }`\n- فشل: `{ id?, type: \"response\", command: string, success: false, error: string }`\n\nحمولات البيانات خاصة بكل أمر ومُعرّفة في `rpc-types.ts`.\n\n### حمولة `get_state`\n\n```json\n{\n  \"model\": { \"provider\": \"...\", \"id\": \"...\" },\n  \"thinkingLevel\": \"off|minimal|low|medium|high|xhigh\",\n  \"isStreaming\": false,\n  \"isCompacting\": false,\n  \"steeringMode\": \"all|one-at-a-time\",\n  \"followUpMode\": \"all|one-at-a-time\",\n  \"interruptMode\": \"immediate|wait\",\n  \"sessionFile\": \"...\",\n  \"sessionId\": \"...\",\n  \"sessionName\": \"...\",\n  \"autoCompactionEnabled\": true,\n  \"messageCount\": 0,\n  \"queuedMessageCount\": 0,\n  \"todoPhases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Todos\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the tool surface\",\n          \"status\": \"in_progress\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n### حمولة `set_todos`\n\nتستبدل حالة المهام في الذاكرة للجلسة الحالية وتُعيد قائمة المراحل المُعيّرة:\n\n```json\n{\n  \"id\": \"req_2\",\n  \"type\": \"set_todos\",\n  \"phases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Evaluation\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the read tool surface\",\n          \"status\": \"in_progress\"\n        },\n        {\n          \"id\": \"task-2\",\n          \"content\": \"Exercise edit operations\",\n          \"status\": \"pending\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nهذا مفيد للمضيفين الذين يريدون تهيئة خطة مسبقة قبل أول طلب.\n\n### حمولة `set_host_tools`\n\nتستبدل المجموعة الحالية من الأدوات المملوكة للمضيف التي يمكن لخادم RPC\nاستدعاؤها مرة أخرى عبر stdio:\n\n```json\n{\n  \"id\": \"req_3\",\n  \"type\": \"set_host_tools\",\n  \"tools\": [\n    {\n      \"name\": \"echo_host\",\n      \"label\": \"Echo Host\",\n      \"description\": \"Echo a value from the embedding host\",\n      \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"message\": { \"type\": \"string\" }\n        },\n        \"required\": [\"message\"],\n        \"additionalProperties\": false\n      }\n    }\n  ]\n}\n```\n\nحمولة الاستجابة هي:\n\n```json\n{\n  \"toolNames\": [\"echo_host\"]\n}\n```\n\nتُضاف هذه الأدوات إلى سجل أدوات الجلسة النشطة قبل استدعاء النموذج التالي.\nإعادة إرسال `set_host_tools` تستبدل المجموعة السابقة المملوكة للمضيف.\n\n## مخطط تدفق الأحداث\n\nيُمرر وضع RPC كائنات `AgentSessionEvent` من `AgentSession.subscribe(...)`.\n\nأنواع الأحداث الشائعة:\n\n- `agent_start`، `agent_end`\n- `turn_start`، `turn_end`\n- `message_start`، `message_update`، `message_end`\n- `tool_execution_start`، `tool_execution_update`، `tool_execution_end`\n- `auto_compaction_start`، `auto_compaction_end`\n- `auto_retry_start`، `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n- `todo_auto_clear`\n\nتُصدر أخطاء مُشغّل الإضافات بشكل منفصل كـ:\n\n```json\n{ \"type\": \"extension_error\", \"extensionPath\": \"...\", \"event\": \"...\", \"error\": \"...\" }\n```\n\nيتضمن `message_update` فروق البث في `assistantMessageEvent` (فروق النص/التفكير/استدعاء الأدوات).\n\n## التزامن والترتيب في الطلبات/قائمة الانتظار\n\nهذا هو أهم سلوك تشغيلي.\n\n### الإقرار الفوري مقابل الاكتمال\n\nيتم **الإقرار بـ** `prompt` و `abort_and_prompt` **فوريًا**:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n```\n\nهذا يعني:\n\n- قبول الأمر ≠ اكتمال التشغيل\n- يُلاحظ الاكتمال النهائي عبر `agent_end`\n\n### أثناء البث\n\nيتطلب `AgentSession.prompt()` تحديد `streamingBehavior` أثناء البث النشط:\n\n- `\"steer\"` => رسالة توجيه مُدرجة في قائمة الانتظار (مسار المقاطعة)\n- `\"followUp\"` => رسالة متابعة مُدرجة في قائمة الانتظار (مسار ما بعد الدور)\n\nإذا لم يُحدد أثناء البث، يفشل الطلب.\n\n### القيم الافتراضية لقائمة الانتظار\n\nمن مخطط إعدادات وكيل البرمجة (`packages/coding-agent/src/config/settings-schema.ts`):\n\n- `steeringMode`: `\"one-at-a-time\"`\n- `followUpMode`: `\"one-at-a-time\"`\n- `interruptMode`: `\"wait\"`\n\n### دلالات الأوضاع\n\n- `set_steering_mode` / `set_follow_up_mode`\n  - `\"one-at-a-time\"`: إخراج رسالة واحدة من قائمة الانتظار لكل دور\n  - `\"all\"`: إخراج قائمة الانتظار بالكامل دفعة واحدة\n- `set_interrupt_mode`\n  - `\"immediate\"`: يتحقق تنفيذ الأدوات من التوجيه بين استدعاءات الأدوات؛ يمكن للتوجيه المعلق إلغاء استدعاءات الأدوات المتبقية في الدور\n  - `\"wait\"`: تأجيل التوجيه حتى اكتمال الدور\n\n## بروتوكول واجهة المستخدم الفرعي للإضافات\n\nتستخدم الإضافات في وضع RPC إطارات طلب/استجابة لواجهة المستخدم.\n\n### الطلب الصادر\n\nطرق `RpcExtensionUIRequest` (`type: \"extension_ui_request\"`):\n\n- `select`، `confirm`، `input`، `editor`\n- `notify`، `setStatus`، `setWidget`، `setTitle`، `set_editor_text`\n\nملاحظة وقت التشغيل:\n\n- يتم تعطيل توليد عنوان الجلسة التلقائي في وضع RPC، كما يتم قمع طلبات واجهة المستخدم\n  `setTitle` افتراضيًا لأن معظم المضيفين لا يملكون سطحًا ذا معنى لعنوان\n  المحطة الطرفية. عيّن `PI_RPC_EMIT_TITLE=1` لإعادة تفعيل حدث واجهة المستخدم فقط.\n\nمثال:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"123\", \"method\": \"confirm\", \"title\": \"Confirm\", \"message\": \"Continue?\", \"timeout\": 30000 }\n```\n\n### الاستجابة الواردة\n\n`RpcExtensionUIResponse` (`type: \"extension_ui_response\"`):\n\n- `{ type: \"extension_ui_response\", id: string, value: string }`\n- `{ type: \"extension_ui_response\", id: string, confirmed: boolean }`\n- `{ type: \"extension_ui_response\", id: string, cancelled: true }`\n\nإذا كان لمربع الحوار مهلة زمنية، يحل وضع RPC إلى قيمة افتراضية عند انتهاء المهلة/الإلغاء.\n\n## بروتوكول أدوات المضيف الفرعي\n\nيمكن لمضيفي RPC كشف أدوات مخصصة للوكيل عن طريق إرسال `set_host_tools`، ثم\nخدمة طلبات التنفيذ عبر نفس النقل.\n\n### الطلب الصادر\n\nعندما يريد الوكيل من المضيف تنفيذ إحدى تلك الأدوات، يُصدر وضع RPC:\n\n```json\n{\n  \"type\": \"host_tool_call\",\n  \"id\": \"host_1\",\n  \"toolCallId\": \"toolu_123\",\n  \"toolName\": \"echo_host\",\n  \"arguments\": { \"message\": \"hello\" }\n}\n```\n\nإذا تم إلغاء تنفيذ الأداة لاحقًا، يُصدر وضع RPC:\n\n```json\n{\n  \"type\": \"host_tool_cancel\",\n  \"id\": \"host_cancel_1\",\n  \"targetId\": \"host_1\"\n}\n```\n\n### التحديثات الواردة والاكتمال\n\nيمكن للمضيفين اختياريًا بث التقدم:\n\n```json\n{\n  \"type\": \"host_tool_update\",\n  \"id\": \"host_1\",\n  \"partialResult\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"working\" }]\n  }\n}\n```\n\nيستخدم الاكتمال:\n\n```json\n{\n  \"type\": \"host_tool_result\",\n  \"id\": \"host_1\",\n  \"result\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"done\" }]\n  }\n}\n```\n\nعيّن `isError: true` على `host_tool_result` لإظهار المحتوى المُرجع كخطأ أداة.\n\n## نموذج الأخطاء وقابلية الاسترداد\n\n### فشل مستوى الأوامر\n\nحالات الفشل تكون `success: false` مع `error` كنص.\n\n```json\n{ \"id\": \"req_2\", \"type\": \"response\", \"command\": \"set_model\", \"success\": false, \"error\": \"Model not found: provider/model\" }\n```\n\n### توقعات قابلية الاسترداد\n\n- معظم حالات فشل الأوامر قابلة للاسترداد؛ تبقى العملية قيد التشغيل.\n- JSONL مشوّه / استثناءات حلقة التحليل تُصدر استجابة خطأ `parse` وتستمر في قراءة الأسطر التالية.\n- يتم رفض `set_session_name` الفارغ (`Session name cannot be empty`).\n- يتم تجاهل استجابات واجهة المستخدم للإضافات ذات `id` غير معروف.\n- شروط إنهاء العملية هي إغلاق stdin أو إيقاف تشغيل صريح تُفعّله الإضافة.\n\n## تدفقات الأوامر المختصرة\n\n### 1) طلب وبث\n\nstdin:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"prompt\", \"message\": \"Summarize this repo\" }\n```\n\nتسلسل stdout (نموذجي):\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n{ \"type\": \"agent_start\" }\n{ \"type\": \"message_update\", \"assistantMessageEvent\": { \"type\": \"text_delta\", \"delta\": \"...\" }, \"message\": { \"role\": \"assistant\", \"content\": [] } }\n{ \"type\": \"agent_end\", \"messages\": [] }\n```\n\n### 2) طلب أثناء البث مع سياسة قائمة انتظار صريحة\n\nstdin:\n\n```json\n{ \"id\": \"req_2\", \"type\": \"prompt\", \"message\": \"Also include risks\", \"streamingBehavior\": \"followUp\" }\n```\n\n### 3) فحص وضبط سلوك قائمة الانتظار\n\nstdin:\n\n```json\n{ \"id\": \"q1\", \"type\": \"get_state\" }\n{ \"id\": \"q2\", \"type\": \"set_steering_mode\", \"mode\": \"all\" }\n{ \"id\": \"q3\", \"type\": \"set_interrupt_mode\", \"mode\": \"wait\" }\n```\n\n### 4) دورة واجهة المستخدم للإضافة ذهابًا وإيابًا\n\nstdout:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"ui_7\", \"method\": \"input\", \"title\": \"Branch name\", \"placeholder\": \"feature/...\" }\n```\n\nstdin:\n\n```json\n{ \"type\": \"extension_ui_response\", \"id\": \"ui_7\", \"value\": \"feature/rpc-host\" }\n```\n\n## ملاحظات حول مساعد `RpcClient`\n\n`src/modes/rpc/rpc-client.ts` هو غلاف تسهيلي، وليس تعريف البروتوكول.\n\nخصائص المساعد الحالية:\n\n- يُشغّل `bun <cliPath> --mode rpc`\n- يربط الاستجابات بمعرّفات `req_<n>` المُولّدة\n- يُوزّع فقط أنواع `AgentEvent` المعروفة إلى المستمعين\n- يدعم أدوات المضيف المخصصة عبر `setCustomTools()` والمعالجة التلقائية لـ `host_tool_call` / `host_tool_cancel`\n- **لا** يوفر طرقًا مساعدة لكل أمر بروتوكول (على سبيل المثال، `set_interrupt_mode` و `set_session_name` موجودان في أنواع البروتوكول لكن غير مُغلّفين كطرق مخصصة)\n\nاستخدم إطارات البروتوكول الخام إذا كنت بحاجة إلى تغطية كاملة للسطح.\n",
	"ar/configuration/sdk.md": "---\ntitle: SDK\ndescription: >-\n  مجموعة أدوات تطوير البرامج (SDK) لبناء وكلاء مخصصين وتكاملات على رأس وقت تشغيل\n  وكيل الترميز xcsh.\nsidebar:\n  order: 6\n  label: SDK\ni18n:\n  sourceHash: 80f3a4374241\n  translator: machine\n---\n\n# SDK\n\nSDK هو سطح التكامل داخل العملية الواحدة لـ `@f5-sales-demo/xcsh`.\nاستخدمه عندما تريد وصولاً مباشراً إلى حالة الوكيل، وبث الأحداث، وتوصيل الأدوات، والتحكم في الجلسة من عملية Bun/Node الخاصة بك.\n\nإذا كنت بحاجة إلى عزل متعدد اللغات/العمليات، فاستخدم وضع RPC بدلاً من ذلك.\n\n## التثبيت\n\n```bash\nbun add @f5-sales-demo/xcsh\n```\n\n## نقاط الدخول\n\nيُصدِّر `@f5-sales-demo/xcsh` واجهات برمجة SDK من جذر الحزمة (وأيضاً عبر `@f5-sales-demo/xcsh/sdk`).\n\nالصادرات الأساسية للمضمِّنين:\n\n- `createAgentSession`\n- `SessionManager`\n- `Settings`\n- `AuthStorage`\n- `ModelRegistry`\n- `discoverAuthStorage`\n- مساعدات الاكتشاف (`discoverExtensions`, `discoverSkills`, `discoverContextFiles`, `discoverPromptTemplates`, `discoverSlashCommands`, `discoverCustomTSCommands`, `discoverMCPServers`)\n- سطح مصنع الأدوات (`createTools`, `BUILTIN_TOOLS`, فئات الأدوات)\n\n## البداية السريعة (افتراضيات الاكتشاف التلقائي)\n\n```ts\nimport { createAgentSession } from \"@f5-sales-demo/xcsh\";\n\nconst { session, modelFallbackMessage } = await createAgentSession();\n\nif (modelFallbackMessage) {\n process.stderr.write(`${modelFallbackMessage}\\n`);\n}\n\nconst unsubscribe = session.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Summarize this repository in 3 bullets.\");\nunsubscribe();\nawait session.dispose();\n```\n\n## ما الذي تكتشفه `createAgentSession()` بشكل افتراضي\n\nتتبع `createAgentSession()` مبدأ \"قدِّم للتجاوز، أو أهمل للاكتشاف\".\n\nإذا أُهمل، فإنها تحلّ:\n\n- `cwd`: `getProjectDir()`\n- `agentDir`: `~/.xcsh/agent` (عبر `getAgentDir()`)\n- `authStorage`: `discoverAuthStorage(agentDir)`\n- `modelRegistry`: `new ModelRegistry(authStorage)` + `await refresh()`\n- `settings`: `await Settings.init({ cwd, agentDir })`\n- `sessionManager`: `SessionManager.create(cwd)` (مدعوم بالملفات)\n- المهارات/ملفات السياق/قوالب الطلبات/أوامر الشرطة المائلة/الإضافات/الأوامر المخصصة بـ TS\n- الأدوات المدمجة عبر `createTools(...)`\n- أدوات MCP (مفعّلة بشكل افتراضي)\n- تكامل LSP (مفعّل بشكل افتراضي)\n\n### المدخلات الإلزامية مقابل الاختيارية\n\nعادةً ما تحتاج إلى توفير ما تريد التحكم فيه فقط:\n\n- **يجب توفيره**: لا شيء لجلسة بحد أدنى\n- **يُوفَّر عادةً بشكل صريح** في المضمِّنين:\n    - `sessionManager` (إذا كنت بحاجة إلى ذاكرة مؤقتة أو موقع مخصص)\n    - `authStorage` + `modelRegistry` (إذا كنت تتحكم في دورة حياة بيانات الاعتماد/النماذج)\n    - `model` أو `modelPattern` (إذا كان اختيار النموذج الحتمي مهماً)\n    - `settings` (إذا كنت بحاجة إلى إعداد معزول/اختباري)\n\n## سلوك مدير الجلسة (مستمر مقابل في الذاكرة)\n\nتستخدم `AgentSession` دائماً `SessionManager`؛ ويعتمد السلوك على المصنع الذي تستخدمه.\n\n### مدعوم بالملفات (الافتراضي)\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.create(process.cwd()),\n});\n\nconsole.log(session.sessionFile); // absolute .jsonl path\n```\n\n- يحفظ المحادثة/الرسائل/دلتا الحالة في ملفات الجلسة.\n- يدعم سير عمل الاستئناف/الفتح/الإدراج/التفريع.\n- `session.sessionFile` محدَّد.\n\n### في الذاكرة\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.inMemory(),\n});\n\nconsole.log(session.sessionFile); // undefined\n```\n\n- لا استمرارية على نظام الملفات.\n- مفيد للاختبارات، والعمال المؤقتين، والوكلاء ذوي النطاق المحدود بالطلب.\n- تعمل أساليب الجلسة بشكل طبيعي، لكن السلوكيات الخاصة بالاستمرارية (مسارات استئناف/تفريع الملفات) تكون محدودة طبيعياً.\n\n### مساعدات الاستئناف/الفتح/الإدراج\n\n```ts\nimport { SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst recent = await SessionManager.continueRecent(process.cwd());\nconst listed = await SessionManager.list(process.cwd());\nconst opened = listed[0] ? await SessionManager.open(listed[0].path) : null;\n```\n\n## توصيل النماذج والمصادقة\n\nتستخدم `createAgentSession()` كلاً من `ModelRegistry` و`AuthStorage` لاختيار النماذج وحل مفاتيح API.\n\n### التوصيل الصريح\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst available = modelRegistry.getAvailable();\nif (available.length === 0) throw new Error(\"No authenticated models available\");\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n model: available[0],\n thinkingLevel: \"medium\",\n sessionManager: SessionManager.inMemory(),\n});\n```\n\n### ترتيب الاختيار عند إهمال `model`\n\nعندما لا يُقدَّم `model`/`modelPattern` صريح:\n\n1. استعادة النموذج من الجلسة الموجودة (إذا كانت قابلة للاستعادة والمفتاح متاح)\n2. دور النموذج الافتراضي في الإعدادات (`default`)\n3. أول نموذج متاح بمصادقة صالحة\n\nإذا فشلت الاستعادة، يشرح `modelFallbackMessage` الخيار البديل.\n\n### أولوية المصادقة\n\nيحلّ `AuthStorage.getApiKey(...)` بهذا الترتيب:\n\n1. التجاوز في وقت التشغيل (`setRuntimeApiKey`)\n2. بيانات الاعتماد المخزنة في `agent.db`\n3. متغيرات بيئة الموفر\n4. حلّ المزود المخصص الاحتياطي (إذا كان مهيَّأً)\n\n## نموذج الاشتراك في الأحداث\n\nاشترك باستخدام `session.subscribe(listener)`؛ فتُعيد دالة إلغاء الاشتراك.\n\n```ts\nconst unsubscribe = session.subscribe(event => {\n switch (event.type) {\n  case \"agent_start\":\n  case \"turn_start\":\n  case \"tool_execution_start\":\n   break;\n  case \"message_update\":\n   if (event.assistantMessageEvent.type === \"text_delta\") {\n    process.stdout.write(event.assistantMessageEvent.delta);\n   }\n   break;\n }\n});\n```\n\nيتضمن `AgentSessionEvent` الأحداث الأساسية `AgentEvent` بالإضافة إلى أحداث مستوى الجلسة:\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n## دورة حياة الطلب\n\n`session.prompt(text, options?)` هي نقطة الدخول الأساسية.\n\nالسلوك:\n\n1. توسيع اختياري للأوامر/القوالب (أوامر `/`، والأوامر المخصصة، وأوامر الشرطة المائلة في الملفات، وقوالب الطلبات)\n2. إذا كان يبث حالياً:\n    - يتطلب `streamingBehavior: \"steer\" | \"followUp\"`\n    - يضع في قائمة انتظار بدلاً من التخلي عن العمل\n3. إذا كان خاملاً:\n    - يتحقق من النموذج ومفتاح API\n    - يُلحق رسالة المستخدم\n    - يبدأ دور الوكيل\n\nواجهات برمجة التطبيقات ذات الصلة:\n\n- `sendUserMessage(content, { deliverAs? })`\n- `steer(text, images?)`\n- `followUp(text, images?)`\n- `sendCustomMessage({ customType, content, ... }, { deliverAs?, triggerTurn? })`\n- `abort()`\n\n## الأدوات وتكامل الإضافات\n\n### المدمجات والتصفية\n\n- تأتي المدمجات من `createTools(...)` و`BUILTIN_TOOLS`.\n- يعمل `toolNames` كقائمة سماح للمدمجات.\n- لا تزال الأدوات المخصصة (`customTools`) والأدوات المسجَّلة عبر الإضافات مُدرجة.\n- الأدوات المخفية (مثل `submit_result`) تستوجب الاشتراك الصريح ما لم تطلبها الخيارات.\n\n```ts\nconst { session } = await createAgentSession({\n toolNames: [\"read\", \"grep\", \"find\", \"write\"],\n requireSubmitResultTool: true,\n});\n```\n\n### الإضافات\n\n- `extensions`: مصفوفة `ExtensionFactory[]` مضمَّنة\n- `additionalExtensionPaths`: تحميل ملفات إضافات إضافية\n- `disableExtensionDiscovery`: تعطيل فحص الإضافات التلقائي\n- `preloadedExtensions`: إعادة استخدام مجموعة الإضافات المحمَّلة مسبقاً\n\n### تغييرات مجموعة الأدوات في وقت التشغيل\n\nتدعم `AgentSession` تحديثات التفعيل في وقت التشغيل:\n\n- `getActiveToolNames()`\n- `getAllToolNames()`\n- `setActiveToolsByName(names)`\n- `refreshMCPTools(mcpTools)`\n\nيُعاد بناء موجّه النظام ليعكس تغييرات الأدوات النشطة.\n\n## مساعدات الاكتشاف\n\nاستخدم هذه المساعدات عندما تريد تحكماً جزئياً دون إعادة إنشاء منطق الاكتشاف الداخلي:\n\n- `discoverAuthStorage(agentDir?)`\n- `discoverExtensions(cwd?)`\n- `discoverSkills(cwd?, _agentDir?, settings?)`\n- `discoverContextFiles(cwd?, _agentDir?)`\n- `discoverPromptTemplates(cwd?, agentDir?)`\n- `discoverSlashCommands(cwd?)`\n- `discoverCustomTSCommands(cwd?, agentDir?)`\n- `discoverMCPServers(cwd?)`\n- `buildSystemPrompt(options?)`\n\n## خيارات موجَّهة للوكلاء الفرعيين\n\nلمستهلكي SDK الذين يبنون منظِّمين (مشابهاً لتدفق منفِّذ المهام):\n\n- `outputSchema`: يمرر توقع المخرجات المنظَّمة إلى سياق الأداة\n- `requireSubmitResultTool`: يفرض تضمين أداة `submit_result`\n- `taskDepth`: سياق عمق التكرار لجلسات المهام المتداخلة\n- `parentTaskPrefix`: بادئة تسمية الأدوات لمخرجات المهام المتداخلة\n\nهذه خيارات اختيارية لتضمين الوكيل الفردي الاعتيادي.\n\n## قيمة إرجاع `createAgentSession()`\n\n```ts\ntype CreateAgentSessionResult = {\n session: AgentSession;\n extensionsResult: LoadExtensionsResult;\n setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;\n mcpManager?: MCPManager;\n modelFallbackMessage?: string;\n lspServers?: Array<{ name: string; status: \"ready\" | \"error\"; fileTypes: string[]; error?: string }>;\n};\n```\n\nاستخدم `setToolUIContext(...)` فقط إذا كان مضمِّنك يوفر إمكانيات واجهة مستخدم ينبغي للأدوات/الإضافات الاستدعاء إليها.\n\n## مثال تضمين محكوم بحد أدنى\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n Settings,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst settings = Settings.isolated({\n \"compaction.enabled\": true,\n \"retry.enabled\": true,\n});\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n settings,\n sessionManager: SessionManager.inMemory(),\n toolNames: [\"read\", \"grep\", \"find\", \"edit\", \"write\"],\n enableMCP: false,\n enableLsp: true,\n});\n\nsession.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Find all TODO comments in this repo and propose fixes.\");\nawait session.dispose();\n```\n",
	"ar/configuration/secrets.md": "---\ntitle: إخفاء الأسرار\ndescription: خط أنابيب إخفاء الأسرار الذي يحجب القيم الحساسة من سجلات الجلسات والمخرجات.\nsidebar:\n  order: 3\n  label: الأسرار\ni18n:\n  sourceHash: 1d9dc101c614\n  translator: machine\n---\n\n# إخفاء الأسرار\n\nيمنع إرسال القيم الحساسة (مفاتيح API، والرموز المميزة، وكلمات المرور) إلى موفري LLM. عند التفعيل، يتم استبدال الأسرار بعناصر نائبة حتمية قبل مغادرتها للعملية، واستعادتها في وسائط استدعاء الأدوات التي يُعيدها النموذج.\n\n## التفعيل\n\nمُفعَّل افتراضيًا. يمكن التبديل عبر واجهة المستخدم `/settings` أو مباشرةً في `config.yml`:\n\n```yaml\nsecrets:\n  enabled: false\n```\n\n## كيفية عمله\n\n1. عند بدء الجلسة، يتم جمع الأسرار من مصدرين:\n   - **متغيرات البيئة** المطابقة لأنماط الأسرار الشائعة (`*_KEY`، و`*_SECRET`، و`*_TOKEN`، و`*_PASSWORD`، وغيرها) ذات القيم التي تبلغ 8 أحرف أو أكثر\n   - **ملفات `secrets.yml`** (انظر أدناه)\n\n2. يتم في الرسائل الصادرة إلى LLM استبدال جميع قيم الأسرار بعناصر نائبة مثل `<<$env:S0>>`، و`<<$env:S1>>`، وغيرها.\n\n3. يتم التنقل العميق في وسائط استدعاء الأدوات التي يُعيدها النموذج، واستعادة العناصر النائبة إلى قيمها الأصلية قبل التنفيذ.\n\nيتحكم وضعان في ما يحدث لكل سر:\n\n| الوضع | السلوك | قابل للعكس |\n|---|---|---|\n| `obfuscate` (افتراضي) | يُستبدل بعنصر نائب مفهرس `<<$env:SN>>` | نعم (تُستعاد قيمته الأصلية في وسائط الأداة) |\n| `replace` | يُستبدل بسلسلة نصية حتمية بالطول ذاته | لا (أحادي الاتجاه) |\n\n## secrets.yml\n\nحدِّد إدخالات الأسرار المخصصة في YAML. يتم فحص موقعين:\n\n| المستوى | المسار | الغرض |\n|---|---|---|\n| عام | `~/.xcsh/agent/secrets.yml` | الأسرار عبر جميع المشاريع |\n| مشروع | `<cwd>/.xcsh/secrets.yml` | الأسرار الخاصة بالمشروع |\n\nتتجاوز إدخالات المشروع الإدخالات العامة ذات `content` المتطابق.\n\n### المخطط\n\nكل إدخال في المصفوفة يحتوي على هذه الحقول:\n\n| الحقل | النوع | مطلوب | الوصف |\n|---|---|---|---|\n| `type` | `\"plain\"` أو `\"regex\"` | نعم | استراتيجية المطابقة |\n| `content` | سلسلة نصية | نعم | قيمة السر (plain) أو نمط regex (regex) |\n| `mode` | `\"obfuscate\"` أو `\"replace\"` | لا | الافتراضي: `\"obfuscate\"` |\n| `replacement` | سلسلة نصية | لا | استبدال مخصص (وضع replace فقط) |\n| `flags` | سلسلة نصية | لا | أعلام regex (نوع regex فقط) |\n\n### أمثلة\n\n#### الأسرار النصية العادية\n\n```yaml\n# إخفاء مفتاح API محدد (الوضع الافتراضي)\n- type: plain\n  content: sk-proj-abc123def456\n\n# استبدال كلمة مرور قاعدة البيانات بسلسلة ثابتة\n- type: plain\n  content: hunter2\n  mode: replace\n  replacement: \"********\"\n```\n\n#### أسرار Regex\n\n```yaml\n# إخفاء أي مفتاح بأسلوب AWS\n- type: regex\n  content: \"AKIA[0-9A-Z]{16}\"\n\n# مطابقة غير حساسة لحالة الأحرف مع أعلام صريحة\n- type: regex\n  content: \"api[_-]?key\\\\s*=\\\\s*\\\\w+\"\n  flags: \"i\"\n\n# صيغة regex الحرفية (النمط والأعلام في سلسلة واحدة)\n- type: regex\n  content: \"/bearer\\\\s+[a-zA-Z0-9._~+\\\\/=-]+/i\"\n```\n\nتُجري إدخالات Regex المسح دائمًا على نطاق عام (يتم تطبيق العلم `g` تلقائيًا). تُدعم صيغة regex الحرفية `/pattern/flags` كبديل لحقلي `content` و`flags` المنفصلين. يتم التعامل مع الشرطات المائلة المُهرَّبة داخل النمط (`\\\\/`) بصورة صحيحة.\n\n#### وضع الاستبدال مع Regex\n\n```yaml\n# استبدال أحادي الاتجاه لسلاسل الاتصال (غير قابل للعكس)\n- type: regex\n  content: \"postgres://[^\\\\s]+\"\n  mode: replace\n  replacement: \"postgres://***\"\n```\n\n## التفاعل مع اكتشاف متغيرات البيئة\n\nيتم جمع متغيرات البيئة دائمًا أولًا. تُلحَق الإدخالات المُعرَّفة في الملفات بعدها، لذا يمكن للإدخالات الملفية تغطية الأسرار التي لا توجد في متغيرات البيئة (ملفات التكوين، والقيم المُضمَّنة، وغيرها). إذا ظهرت القيمة ذاتها في كليهما، فإن وضع إدخال الملف يأخذ الأولوية.\n\n## الملفات الرئيسية\n\n- `src/secrets/index.ts` -- التحميل والدمج وجمع متغيرات البيئة\n- `src/secrets/obfuscator.ts` -- فئة `SecretObfuscator`، وتوليد العناصر النائبة، وإخفاء الرسائل\n- `src/secrets/regex.ts` -- تحليل regex الحرفية وتجميعها\n- `src/config/settings-schema.ts` -- تعريف إعداد `secrets.enabled`\n",
	"ar/extensions/extension-loading.md": "---\ntitle: تحميل الامتدادات (وحدات TypeScript/JavaScript)\ndescription: >-\n  خط أنابيب تحميل وحدات TypeScript وJavaScript للامتدادات مع الدقة والتحقق\n  والتخزين المؤقت.\nsidebar:\n  order: 2\n  label: تحميل الامتدادات\ni18n:\n  sourceHash: a8cea231c660\n  translator: machine\n---\n\n# تحميل الامتدادات (وحدات TypeScript/JavaScript)\n\nتتناول هذه الوثيقة كيفية اكتشاف وكيل البرمجة وتحميل **وحدات الامتدادات** (`.ts`/`.js`) عند بدء التشغيل.\n\n**لا تتناول** امتدادات بيان `gemini-extension.json` (موثقة بشكل منفصل).\n\n## ما الذي يقوم به هذا النظام الفرعي\n\nيقوم تحميل الامتدادات ببناء قائمة بملفات إدخال الوحدات، واستيراد كل وحدة باستخدام Bun، وتنفيذ المصنع الخاص بها، ويُعيد:\n\n- تعريفات الامتدادات المحملة\n- أخطاء التحميل لكل مسار (دون إيقاف عملية التحميل بأكملها)\n- كائن وقت تشغيل الامتداد المشترك الذي يستخدمه لاحقًا `ExtensionRunner`\n\n## ملفات التنفيذ الأساسية\n\n- `src/extensibility/extensions/loader.ts` — اكتشاف المسار + الاستيراد/التنفيذ\n- `src/extensibility/extensions/index.ts` — الصادرات العامة\n- `src/extensibility/extensions/runner.ts` — تنفيذ وقت التشغيل/الأحداث بعد التحميل\n- `src/discovery/builtin.ts` — موفر الاكتشاف التلقائي الأصلي لوحدات الامتدادات\n- `src/config/settings.ts` — تحميل إعدادات `extensions` / `disabledExtensions` المدمجة\n\n---\n\n## مدخلات تحميل الامتدادات\n\n### 1) وحدات الامتدادات الأصلية المكتشفة تلقائيًا\n\nتبدأ `discoverAndLoadExtensions()` بطلب العناصر ذات قدرة `extension-module` من موفري الاكتشاف، ثم تحتفظ بعناصر موفر `native` فقط.\n\nالمواقع الأصلية الفعلية:\n\n- المشروع: `<cwd>/.xcsh/extensions`\n- المستخدم: `~/.xcsh/agent/extensions`\n\nتأتي جذور المسارات من الموفر الأصلي (`SOURCE_PATHS.native`).\n\nملاحظات:\n\n- يعتمد الاكتشاف التلقائي الأصلي حاليًا على `.xcsh`.\n- لا يزال يُقبل `.pi` القديم في مفاتيح بيان `package.json` (`pi.extensions`)، لكنه لا يُستخدم كجذر أصلي هنا.\n\n### 2) المسارات المهيأة صراحةً\n\nبعد الاكتشاف التلقائي، تُضاف المسارات المهيأة ويتم حلها.\n\nمصادر المسارات المهيأة في مسار بدء تشغيل الجلسة الرئيسي (`sdk.ts`):\n\n1. المسارات المقدمة عبر CLI (`--extension/-e`، كما يُعامَل `--hook` أيضًا كمسار امتداد)\n2. مصفوفة `extensions` في الإعدادات (الإعدادات العامة والمشروع المدمجة)\n\nملف الإعدادات العامة:\n\n- `~/.xcsh/agent/config.yml` (أو دليل وكيل مخصص عبر `PI_CODING_AGENT_DIR`)\n\nملف إعدادات المشروع:\n\n- `<cwd>/.xcsh/settings.json`\n\nأمثلة:\n\n```yaml\n# ~/.xcsh/agent/config.yml\nextensions:\n  - ~/my-exts/safety.ts\n  - ./local/ext-pack\n```\n\n```json\n{\n  \"extensions\": [\"./.xcsh/extensions/my-extra\"]\n}\n```\n\n---\n\n## عناصر التحكم في التفعيل/التعطيل\n\n### تعطيل الاكتشاف\n\n- CLI: `--no-extensions`\n- خيار SDK: `disableExtensionDiscovery`\n\nتقسيم السلوك:\n\n- SDK: عند تعيين `disableExtensionDiscovery=true`، لا يزال يحمّل `additionalExtensionPaths` عبر `loadExtensions()`.\n- بناء مسار CLI (`main.ts`) يمسح مسارات امتداد CLI حاليًا عند تعيين `--no-extensions`، لذا لا يتم إعادة توجيه `-e/--hook` الصريح في هذا الوضع.\n\n### تعطيل وحدات امتداد محددة\n\nيُصفي إعداد `disabledExtensions` بناءً على تنسيق معرّف الامتداد:\n\n- `extension-module:<derivedName>`\n\nيعتمد `derivedName` على مسار الإدخال (`getExtensionNameFromPath`)، على سبيل المثال:\n\n- `/x/foo.ts` -> `foo`\n- `/x/bar/index.ts` -> `bar`\n\nمثال:\n\n```yaml\ndisabledExtensions:\n  - extension-module:foo\n```\n\n---\n\n## دقة المسار والإدخال\n\n### تطبيع المسار\n\nبالنسبة للمسارات المهيأة:\n\n1. تطبيع المسافات Unicode\n2. توسيع `~`\n3. إذا كان نسبيًا، يُحل بالنسبة إلى `cwd` الحالي\n\n### إذا كان المسار المهيأ ملفًا\n\nيُستخدم مباشرةً كمرشح لإدخال الوحدة.\n\n### إذا كان المسار المهيأ دليلًا\n\nترتيب الدقة:\n\n1. `package.json` في ذلك الدليل مع `xcsh.extensions` (أو `pi.extensions` القديم) -> استخدام الإدخالات المُعلنة\n2. `index.ts`\n3. `index.js`\n4. وإلا فحص مستوى واحد بحثًا عن إدخالات الامتداد:\n   - `*.ts` / `*.js` المباشرة\n   - `index.ts` / `index.js` في دليل فرعي\n   - `package.json` في دليل فرعي مع `xcsh.extensions` / `pi.extensions`\n\nالقواعد والقيود:\n\n- لا يوجد اكتشاف متكرر يتجاوز مستوى دليل فرعي واحد\n- تُحل إدخالات بيان `extensions` المُعلنة بالنسبة لدليل الحزمة ذلك\n- تُدرج الإدخالات المُعلنة فقط إذا كان الملف موجودًا/الوصول مسموحًا به\n- في أزواج `*/index.{ts,js}`، يُفضَّل TypeScript على JavaScript\n- تُعامَل الروابط الرمزية كملفات/أدلة مؤهلة\n\n### يختلف سلوك التجاهل حسب المصدر\n\n- يستخدم الاكتشاف التلقائي الأصلي (`discoverExtensionModulePaths` في مساعدات الاكتشاف) glob أصلي مع `gitignore: true` و`hidden: false`.\n- لا يطبّق فحص الدليل المهيأ الصريح في `loader.ts` قواعد `readdir` **ولا** يُطبق تصفية gitignore.\n\n---\n\n## ترتيب التحميل والأسبقية\n\nتبني `discoverAndLoadExtensions()` قائمة واحدة مرتبة ثم تستدعي `loadExtensions()`.\n\nالترتيب:\n\n1. الوحدات المكتشفة تلقائيًا الأصلية\n2. المسارات المهيأة الصريحة (بالترتيب المقدم)\n\nفي `sdk.ts`، الترتيب المهيأ هو:\n\n1. المسارات الإضافية لـ CLI\n2. إعدادات `extensions`\n\nإلغاء التكرار:\n\n- يعتمد على المسار المطلق\n- أول مسار مرئي يفوز\n- تُتجاهل التكرارات اللاحقة\n\nالاستنتاج: إذا كان نفس مسار الوحدة مكتشفًا تلقائيًا ومهيأًا صراحةً، فيتم تحميله مرة واحدة في الموضع الأول (مرحلة الاكتشاف التلقائي).\n\n---\n\n## استيراد الوحدة وعقد المصنع\n\nيتم تحميل كل مسار مرشح باستخدام الاستيراد الديناميكي:\n\n- `await import(resolvedPath)`\n- المصنع هو `module.default ?? module`\n- يجب أن يكون المصنع دالة (`ExtensionFactory`)\n\nإذا لم يكن التصدير دالة، يفشل ذلك المسار بخطأ منظم وتستمر عملية التحميل.\n\n---\n\n## معالجة الفشل والعزل\n\n### أثناء التحميل\n\nيتم التقاط حالات الفشل لكل مسار امتداد كـ `{ path, error }` ولا تمنع تحميل المسارات الأخرى.\n\nالحالات الشائعة:\n\n- فشل الاستيراد / ملف مفقود\n- تصدير مصنع غير صالح (غير دالة)\n- استثناء مُلقى أثناء تنفيذ المصنع\n\n### نموذج عزل وقت التشغيل\n\n- الامتدادات **غير معزولة في صندوق رمل** (نفس العملية/وقت التشغيل).\n- تتشارك في `EventBus` واحد وكائن `ExtensionRuntime` واحد.\n- أثناء التحميل، تُلقي أساليب إجراء وقت التشغيل عمدًا `ExtensionRuntimeNotInitializedError`؛ يحدث ربط الإجراءات لاحقًا في `ExtensionRunner.initialize()`.\n\n### بعد التحميل\n\nعند تشغيل الأحداث عبر `ExtensionRunner`، يتم التقاط استثناءات المعالج وإصدارها كأخطاء امتداد بدلًا من إسقاط حلقة التشغيل.\n\n---\n\n## أمثلة تخطيط المستخدم/المشروع الدنيا\n\n### على مستوى المستخدم\n\n```text\n~/.xcsh/agent/\n  config.yml\n  extensions/\n    guardrails.ts\n    audit/\n      index.ts\n```\n\n### على مستوى المشروع\n\n```text\n<repo>/\n  .xcsh/\n    settings.json\n    extensions/\n      checks/\n        package.json\n      lint-gates.ts\n```\n\n`checks/package.json`:\n\n```json\n{\n  \"xcsh\": {\n    \"extensions\": [\"./src/check-a.ts\", \"./src/check-b.js\"]\n  }\n}\n```\n\nلا يزال يُقبل مفتاح البيان القديم:\n\n```json\n{\n  \"pi\": {\n    \"extensions\": [\"./index.ts\"]\n  }\n}\n```\n",
	"ar/extensions/extensions.md": "---\ntitle: الإضافات\ndescription: >-\n  نظرة عامة على وقت تشغيل الإضافات تشمل الأنواع ودورة حياة المُشغِّل والتسجيل\n  والاكتشاف.\nsidebar:\n  order: 1\n  label: نظرة عامة\ni18n:\n  sourceHash: 14cc16dbd98b\n  translator: machine\n---\n\n# الإضافات\n\nالدليل الأساسي لتأليف إضافات وقت التشغيل في `packages/coding-agent`.\n\nتغطي هذه الوثيقة وقت تشغيل الإضافات الحالي في:\n\n- `src/extensibility/extensions/types.ts`\n- `src/extensibility/extensions/runner.ts`\n- `src/extensibility/extensions/wrapper.ts`\n- `src/extensibility/extensions/index.ts`\n- `src/modes/controllers/extension-ui-controller.ts`\n\nللاطلاع على مسارات الاكتشاف وقواعد التحميل من نظام الملفات، راجع `docs/extension-loading.md`.\n\n## ما هي الإضافة\n\nالإضافة هي وحدة TS/JS تُصدِّر مصنعًا افتراضيًا:\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nexport default function myExtension(pi: ExtensionAPI) {\n // register handlers/tools/commands/renderers\n}\n```\n\nيمكن للإضافات الجمع بين كل ما يلي في وحدة واحدة:\n\n- معالجات الأحداث (`pi.on(...)`)\n- الأدوات القابلة للاستدعاء بواسطة LLM (`pi.registerTool(...)`)\n- أوامر الشرطة المائلة (`pi.registerCommand(...)`)\n- اختصارات لوحة المفاتيح والأعلام\n- عرض الرسائل المخصص\n- واجهات برمجية لحقن الجلسات/الرسائل (`sendMessage`، `sendUserMessage`، `appendEntry`)\n\n## نموذج وقت التشغيل\n\n1. يتم استيراد الإضافات وتشغيل دوال المصنع الخاصة بها.\n2. خلال مرحلة التحميل هذه، تكون طرق التسجيل صالحة؛ أما طرق الإجراءات في وقت التشغيل فلم تُهيَّأ بعد.\n3. تقوم `ExtensionRunner.initialize(...)` بربط الإجراءات/السياقات الحية للوضع النشط.\n4. تُرسَل أحداث دورة حياة الجلسة/الوكيل/الأداة إلى المعالجات.\n5. يتم تغليف كل تنفيذ للأدوات باعتراض الإضافات (`tool_call` / `tool_result`).\n\n```text\nExtension lifecycle (simplified)\n\nload paths\n   │\n   ▼\nimport module + run factory (registration only)\n   │\n   ▼\nExtensionRunner.initialize(mode/session/tool registry)\n   │\n   ├─ emit session/agent events to handlers\n   ├─ wrap tool execution (tool_call/tool_result)\n   └─ expose runtime actions (sendMessage, setActiveTools, ...)\n```\n\nقيد مهم من `loader.ts`:\n\n- استدعاء طرق الإجراءات مثل `pi.sendMessage()` أثناء تحميل الإضافة يُلقي `ExtensionRuntimeNotInitializedError`\n- قم بالتسجيل أولاً؛ ونفِّذ سلوك وقت التشغيل من الأحداث/الأوامر/الأدوات\n\n## البداية السريعة\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\nimport { Type } from \"@sinclair/typebox\";\n\nexport default function (pi: ExtensionAPI) {\n pi.setLabel(\"Safety + Utilities\");\n\n pi.on(\"session_start\", async (_event, ctx) => {\n  ctx.ui.notify(`Extension loaded in ${ctx.cwd}`, \"info\");\n });\n\n pi.on(\"tool_call\", async (event) => {\n  if (event.toolName === \"bash\" && event.input.command?.includes(\"rm -rf\")) {\n   return { block: true, reason: \"Blocked by extension policy\" };\n  }\n });\n\n pi.registerTool({\n  name: \"hello_extension\",\n  label: \"Hello Extension\",\n  description: \"Return a greeting\",\n  parameters: Type.Object({ name: Type.String() }),\n  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {\n   return {\n    content: [{ type: \"text\", text: `Hello, ${params.name}` }],\n    details: { greeted: params.name },\n   };\n  },\n });\n\n pi.registerCommand(\"hello-ext\", {\n  description: \"Show queue state\",\n  handler: async (_args, ctx) => {\n   ctx.ui.notify(`pending=${ctx.hasPendingMessages()}`, \"info\");\n  },\n });\n}\n```\n\n## أسطح واجهة برمجة الإضافات\n\n## 1) التسجيل والإجراءات (`ExtensionAPI`)\n\nالطرق الأساسية:\n\n- `on(event, handler)`\n- `registerTool`، `registerCommand`، `registerShortcut`، `registerFlag`\n- `registerMessageRenderer`\n- `sendMessage`، `sendUserMessage`، `appendEntry`\n- `getActiveTools`، `getAllTools`، `setActiveTools`\n- `getSessionName`، `setSessionName`\n- `setModel`، `getThinkingLevel`، `setThinkingLevel`\n- `registerProvider`\n- `events` (ناقل أحداث مشترك)\n\nفي الوضع التفاعلي، تعمل معالجات `input` قبل الفحص الافتراضي للعنوان التلقائي للرسالة الأولى. يمكن للإضافات التي تستدعي `await pi.setSessionName(...)` من `input` تعيين اسم الجلسة الدائم ومنع تشغيل العنوان المُولَّد تلقائيًا الافتراضي لتلك الجلسة.\n\nكما يتاح أيضاً:\n\n- `pi.logger`\n- `pi.typebox`\n- `pi.pi` (صادرات الحزمة)\n\n### دلالات توصيل الرسائل\n\nتدعم `pi.sendMessage(message, options)`:\n\n- `deliverAs: \"steer\"` (الافتراضي) — يقاطع التشغيل الحالي\n- `deliverAs: \"followUp\"` — مُدرَج في الطابور للتشغيل بعد التشغيل الحالي\n- `deliverAs: \"nextTurn\"` — مُخزَّن ويُحقَن في موجه المستخدم التالي\n- `triggerTurn: true` — يبدأ دورة عند الخمول (`nextTurn` يتجاهل هذا)\n\nتمر `pi.sendUserMessage(content, { deliverAs })` دائمًا عبر تدفق الموجه؛ أثناء البث تُدرَج في الطابور كتوجيه/متابعة.\n\n## 2) سياق المعالج (`ExtensionContext`)\n\nتستقبل المعالجات وأداة `execute` السياق `ctx` الذي يحتوي على:\n\n- `ui`\n- `hasUI`\n- `cwd`\n- `sessionManager` (للقراءة فقط)\n- `modelRegistry`، `model`\n- `getContextUsage()`\n- `compact(...)`\n- `isIdle()`، `hasPendingMessages()`، `abort()`\n- `shutdown()`\n- `getSystemPrompt()`\n\n## 3) سياق الأمر (`ExtensionCommandContext`)\n\nتحصل معالجات الأوامر إضافةً إلى ذلك على:\n\n- `waitForIdle()`\n- `newSession(...)`\n- `switchSession(...)`\n- `branch(entryId)`\n- `navigateTree(targetId, { summarize })`\n- `reload()`\n\nاستخدم سياق الأمر لتدفقات التحكم في الجلسات؛ هذه الطرق مفصولة عمدًا عن معالجات الأحداث العامة.\n\n## سطح الأحداث (الأسماء والسلوك الحالي)\n\nاتحادات الأحداث الأساسية وأنواع الحمولة موجودة في `types.ts`.\n\n### دورة حياة الجلسة\n\n- `session_start`\n- `session_before_switch` / `session_switch`\n- `session_before_branch` / `session_branch`\n- `session_before_compact` / `session.compacting` / `session_compact`\n- `session_before_tree` / `session_tree`\n- `session_shutdown`\n\nالأحداث المسبقة القابلة للإلغاء:\n\n- `session_before_switch` → `{ cancel?: boolean }`\n- `session_before_branch` → `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_before_compact` → `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session_before_tree` → `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n\n### دورة حياة الموجه والدورة\n\n- `input`\n- `before_agent_start`\n- `context`\n- `agent_start` / `agent_end`\n- `turn_start` / `turn_end`\n- `message_start` / `message_update` / `message_end`\n\n### دورة حياة الأداة\n\n- `tool_call` (ما قبل التنفيذ، يمكن الحجب)\n- `tool_result` (ما بعد التنفيذ، يمكن تعديل المحتوى/التفاصيل/isError)\n- `tool_execution_start` / `tool_execution_update` / `tool_execution_end` (قابلية المراقبة)\n\n`tool_result` ذو أسلوب وسيط: تعمل المعالجات بترتيب الإضافات وكل منها يرى التعديلات السابقة.\n\n### إشارات الموثوقية/وقت التشغيل\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### اعتراض أوامر المستخدم\n\n- `user_bash` (تجاوز بـ `{ result }`)\n- `user_python` (تجاوز بـ `{ result }`)\n\n### `resources_discover`\n\n`resources_discover` موجود في أنواع الإضافات و`ExtensionRunner`.\nملاحظة وقت التشغيل الحالي: `ExtensionRunner.emitResourcesDiscover(...)` مُنفَّذ، لكن لا توجد نقاط استدعاء `AgentSession` تستدعيه في قاعدة الكود الحالية.\n\n## تفاصيل تأليف الأدوات\n\nتستخدم `registerTool` الـ `ToolDefinition` من `types.ts`.\n\nتوقيع `execute` الحالي:\n\n```ts\nexecute(\n toolCallId,\n params,\n signal,\n onUpdate,\n ctx,\n): Promise<AgentToolResult>\n```\n\nالقالب:\n\n```ts\npi.registerTool({\n name: \"my_tool\",\n label: \"My Tool\",\n description: \"...\",\n parameters: Type.Object({}),\n async execute(_id, _params, signal, onUpdate, ctx) {\n  if (signal?.aborted) {\n   return { content: [{ type: \"text\", text: \"Cancelled\" }] };\n  }\n  onUpdate?.({ content: [{ type: \"text\", text: \"Working...\" }] });\n  return { content: [{ type: \"text\", text: \"Done\" }], details: {} };\n },\n onSession(event, ctx) {\n  // reason: start|switch|branch|tree|shutdown\n },\n renderCall(args, theme) {\n  // optional TUI render\n },\n renderResult(result, options, theme, args) {\n  // optional TUI render\n },\n});\n```\n\nيعترض `tool_call`/`tool_result` جميع الأدوات بمجرد تغليف السجل في `sdk.ts`، بما في ذلك الأدوات المدمجة وأدوات الإضافات/المخصصة.\n\n## نقاط تكامل واجهة المستخدم\n\nيُنفِّذ `ctx.ui` واجهة `ExtensionUIContext`. يختلف الدعم حسب الوضع.\n\n### الوضع التفاعلي (`extension-ui-controller.ts`)\n\nالمدعوم:\n\n- مربعات الحوار: `select`، `confirm`، `input`، `editor`\n- الإشعارات/الحالة/نص المحرر/إدخال الطرفية/التراكبات المخصصة\n- سرد السمات/تحميلها بالاسم (`setTheme` يدعم أسماء السلاسل)\n- تبديل توسيع الأدوات\n\nالطرق التي لا تُنفِّذ شيئًا حاليًا في هذا المتحكم:\n\n- `setFooter`\n- `setHeader`\n- `setEditorComponent`\n\nلاحظ أيضًا: يتوجه `setWidget` حاليًا إلى نص سطر الحالة عبر `setHookWidget(...)`.\n\n### وضع RPC (`rpc-mode.ts`)\n\nيعتمد `ctx.ui` على أحداث `extension_ui_request` الخاصة بـ RPC:\n\n- طرق مربعات الحوار (`select`، `confirm`، `input`، `editor`) تُرسَل ذهابًا وإيابًا إلى استجابات العميل\n- الطرق التي ترسل وتنسى تُطلق طلبات (`notify`، `setStatus`، `setWidget` لمصفوفات السلاسل، `setTitle`، `setEditorText`)\n\nغير مدعوم/لا يُنفِّذ شيئًا في تنفيذ RPC:\n\n- `onTerminalInput`\n- `custom`\n- `setFooter`، `setHeader`، `setEditorComponent`\n- `setWorkingMessage`\n- تبديل السمات/تحميلها (`setTheme` يُعيد فشلاً)\n- عناصر التحكم في توسيع الأدوات غير فعّالة\n\n### مسارات الطباعة/بدون رأس/الوكيل الفرعي\n\nعند عدم تزويد سياق واجهة المستخدم لتهيئة المُشغِّل، يكون `ctx.hasUI` بقيمة `false` وتكون الطرق عديمة التأثير أو تُعيد قيمة افتراضية.\n\n### وضع تفاعلي في الخلفية\n\nيُثبِّت وضع الخلفية كائن سياق واجهة مستخدم غير تفاعلي. في التنفيذ الحالي، قد يظل `ctx.hasUI` بقيمة `true` بينما تُعيد مربعات الحوار التفاعلية قيمًا افتراضية/لا تُنفِّذ شيئًا.\n\n## أنماط الجلسة والحالة\n\nللحفاظ الدائم على حالة الإضافة:\n\n1. احفظ بـ `pi.appendEntry(customType, data)`.\n2. أعد بناء الحالة من `ctx.sessionManager.getBranch()` عند `session_start`، `session_branch`، `session_tree`.\n3. احتفظ بـ `details` نتيجة الأداة منظَّمة عندما يجب أن تكون الحالة مرئية/قابلة للإعادة من تاريخ نتائج الأداة.\n\nمثال على نمط إعادة البناء:\n\n```ts\npi.on(\"session_start\", async (_event, ctx) => {\n let latest;\n for (const entry of ctx.sessionManager.getBranch()) {\n  if (entry.type === \"custom\" && entry.customType === \"my-state\") {\n   latest = entry.data;\n  }\n }\n // restore from latest\n});\n```\n\n## نقاط توسعة العرض\n\n## عارض الرسائل المخصص\n\n```ts\npi.registerMessageRenderer(\"my-type\", (message, { expanded }, theme) => {\n // return pi-tui Component\n});\n```\n\nيُستخدم في العرض التفاعلي عند عرض الرسائل المخصصة.\n\n## عارض استدعاء/نتيجة الأداة\n\nقدِّم `renderCall` / `renderResult` على تعريفات `registerTool` لتصوير مخصص للأداة في TUI.\n\n## القيود والمزالق\n\n- إجراءات وقت التشغيل غير متاحة أثناء تحميل الإضافة.\n- أخطاء `tool_call` تحجب التنفيذ (الفشل مغلق).\n- تعارضات أسماء الأوامر مع الأوامر المدمجة يتم تخطيها مع تشخيصات.\n- الاختصارات المحجوزة يتم تجاهلها (`ctrl+c`، `ctrl+d`، `ctrl+z`، `ctrl+k`، `ctrl+p`، `ctrl+l`، `ctrl+o`، `ctrl+t`، `ctrl+g`، `shift+tab`، `shift+ctrl+p`، `alt+enter`، `escape`، `enter`).\n- عامِل `ctx.reload()` باعتباره نهائيًا لإطار معالج الأمر الحالي.\n\n## الإضافات مقابل الخطافات مقابل الأدوات المخصصة\n\nاستخدم السطح المناسب:\n\n- **الإضافات** (`src/extensibility/extensions/*`): نظام موحد (أحداث + أدوات + أوامر + عارضات + تسجيل الموفر).\n- **الخطافات** (`src/extensibility/hooks/*`): واجهة برمجية للأحداث إرثية منفصلة.\n- **الأدوات المخصصة** (`src/extensibility/custom-tools/*`): وحدات تركز على الأدوات؛ عند تحميلها جنبًا إلى جنب مع الإضافات يتم تكييفها وتمريرها عبر أغلفة اعتراض الإضافات.\n\nإذا كنت بحاجة إلى حزمة واحدة تمتلك السياسة والأدوات وتجربة مستخدم الأوامر والعرض معًا، فاستخدم الإضافات.\n",
	"ar/extensions/gemini-manifest-extensions.md": "---\ntitle: امتدادات Gemini Manifest\ndescription: تنسيق امتداد Gemini manifest لتوافق المهارات والوكلاء عبر المنصات.\nsidebar:\n  order: 7\n  label: Gemini manifest\ni18n:\n  sourceHash: 7134165a5f6d\n  translator: machine\n---\n\n# امتدادات Gemini Manifest (`gemini-extension.json`)\n\nتتناول هذه الوثيقة كيفية اكتشاف وكيل البرمجة وتحليل امتدادات Gemini manifest (`gemini-extension.json`) في قدرة `extensions`.\n\n**لا** تتناول هذه الوثيقة تحميل وحدات امتداد TypeScript/JavaScript (`extensions/*.ts`، `index.ts`، `package.json xcsh.extensions`)، وهو موثق في `extension-loading.md`.\n\n## ملفات التنفيذ\n\n- [`../src/discovery/gemini.ts`](../../packages/coding-agent/src/discovery/gemini.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/capability/extension.ts`](../../packages/coding-agent/src/capability/extension.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/loader.ts`](../../packages/coding-agent/src/extensibility/extensions/loader.ts)\n\n---\n\n## ما الذي يتم اكتشافه\n\nيسجّل موفر Gemini (المعرّف `id: gemini`، الأولوية `60`) محمّل `extensions` يقوم بفحص جذرين ثابتين:\n\n- المستخدم: `~/.gemini/extensions`\n- المشروع: `<cwd>/.gemini/extensions`\n\nيتم حل المسارات مباشرةً من `ctx.home` و`ctx.cwd` عبر `getUserPath()` / `getProjectPath()`.\n\nقاعدة النطاق المهمة: البحث في المشروع يقتصر على **الدليل الحالي فقط**، ولا يتجوّل في الدلائل الأصل.\n\n---\n\n## قواعد فحص الدليل\n\nلكل جذر (`~/.gemini/extensions` و`<cwd>/.gemini/extensions`)، يقوم الاكتشاف بما يلي:\n\n1. `readDirEntries(root)`\n2. الاحتفاظ بالدلائل الفرعية المباشرة فقط (`entry.isDirectory()`)\n3. لكل دليل فرعي `<name>`، محاولة قراءة ما يلي تحديدًا:\n   - `<root>/<name>/gemini-extension.json`\n\nلا يوجد فحص متعمّق يتجاوز مستوى دليل واحد.\n\n### الدلائل المخفية\n\nلا يستثني اكتشاف Gemini manifest الدلائل التي تبدأ بنقطة. إذا كان ثمة دليل فرعي مخفي يحتوي على `gemini-extension.json`، فإنه يُؤخذ في الاعتبار.\n\n### الملفات المفقودة أو غير القابلة للقراءة\n\nإذا كان `gemini-extension.json` مفقودًا أو غير قابل للقراءة، يتم تخطي ذلك الدليل بصمت دون أي تحذير.\n\n---\n\n## شكل الـ manifest (كما هو منفَّذ)\n\nيُعرّف نوع القدرة شكل الـ manifest على النحو التالي:\n\n```ts\ninterface ExtensionManifest {\n name?: string;\n description?: string;\n mcpServers?: Record<string, Omit<MCPServer, \"name\" | \"_source\">>;\n tools?: unknown[];\n context?: unknown;\n}\n```\n\nالسلوك في وقت الاكتشاف متساهل عن قصد:\n\n- يُشترط نجاح تحليل JSON.\n- لا يوجد تحقق من مخطط البيانات في وقت التشغيل لأنواع الحقول/محتوياتها باستثناء بنية JSON.\n- يتم تخزين الكائن المُحلَّل بوصفه `manifest` على عنصر القدرة.\n\n### تطبيع الاسم\n\nيُعيَّن `Extension.name` إلى:\n\n1. `manifest.name` إذا لم يكن `null`/`undefined`\n2. وإلا اسم دليل الامتداد\n\nلا يُطبَّق أي إلزام بنوع السلسلة النصية هنا.\n\n---\n\n## التجسيد في عناصر القدرة\n\nيُنشئ كل manifest محلَّل صحيح عنصر قدرة `Extension` واحدًا:\n\n```ts\n{\n name: manifest.name ?? <directory-name>,\n path: <extension-directory>,\n manifest: <parsed-json>,\n level: \"user\" | \"project\",\n _source: {\n  provider: \"gemini\",\n  providerName: \"Gemini CLI\" // مُرفق من قِبل سجل القدرات\n  path: <absolute-manifest-path>,\n  level: \"user\" | \"project\"\n }\n}\n```\n\nملاحظات:\n\n- يُطبَّع `_source.path` إلى مسار مطلق بواسطة `createSourceMeta()`.\n- التحقق من القدرة على مستوى السجل لـ `extensions` يفحص فقط وجود `name` و`path`.\n- لا يتم التحقق من المحتويات الداخلية لـ manifest (`mcpServers`، `tools`، `context`) أثناء الاكتشاف.\n\n---\n\n## معالجة الأخطاء ودلالات التحذيرات\n\n### ما يُصدر تحذيرًا\n\n- JSON غير صحيح في ملف manifest:\n  - تنسيق التحذير: `Invalid JSON in <manifestPath>`\n\n### ما لا يُصدر تحذيرًا (تخطي صامت)\n\n- دليل `extensions` مفقود\n- الدليل الفرعي لا يحتوي على `gemini-extension.json`\n- ملف manifest غير قابل للقراءة\n- JSON في الـ manifest صحيح نحويًا لكنه غريب أو غير مكتمل دلاليًا\n\nهذا يعني أن الصحة الجزئية مقبولة: فشل JSON النحوي فقط هو ما يُصدر تحذيرًا.\n\n---\n\n## الأسبقية وإزالة التكرار مع المصادر الأخرى\n\nيتم تجميع قدرة `extensions` عبر الموفرين من قِبل سجل القدرات.\n\nالموفرون الحاليون لهذه القدرة:\n\n- `native` (`packages/coding-agent/src/discovery/builtin.ts`) الأولوية `100`\n- `gemini` (`packages/coding-agent/src/discovery/gemini.ts`) الأولوية `60`\n\nمفتاح إزالة التكرار هو `ext.name` (`extensionCapability.key = ext => ext.name`).\n\n### الأسبقية عبر الموفرين\n\nيفوز الموفر ذو الأولوية الأعلى عند تكرار أسماء الامتدادات.\n\n- إذا أصدر كل من `native` و`gemini` امتدادًا باسم `foo`، يُحتفظ بعنصر native.\n- يظل التكرار ذو الأولوية الأدنى محتفظًا به في `result.all` فقط مع `_shadowed = true`.\n\n### تأثيرات الترتيب داخل الموفر\n\nنظرًا لأن إزالة التكرار تعمل على مبدأ \"الأول يفوز\"، فإن ترتيب العناصر داخل الموفر يُهم.\n\n- يُلحق محمّل Gemini **المستخدم أولًا**، ثم **المشروع**.\n- لذلك، في حالة تكرار الأسماء بين `~/.gemini/extensions` و`<cwd>/.gemini/extensions`، يُحتفظ بعنصر المستخدم ويُطغى على عنصر المشروع.\n\nفي المقابل، يبني الموفر native ترتيب دليل الإعدادات بشكل مختلف (`project` ثم `user` في `getConfigDirs()`)، لذا يكون الطغيان داخل الموفر native في الاتجاه المعاكس.\n\n---\n\n## ملخص سلوك المستخدم مقابل المشروع\n\nبالنسبة لـ Gemini manifests تحديدًا:\n\n- يتم فحص كلا الجذرين (المستخدم والمشروع) في كل عملية تحميل.\n- جذر المشروع مثبَّت على `<cwd>/.gemini/extensions` (بدون اجتياز الدلائل الأصل).\n- تكرار الأسماء داخل مصدر Gemini يُحل لصالح المستخدم أولًا.\n- تكرار الأسماء مع الموفرين ذوي الأولوية الأعلى (ولا سيما native) يخسر بحكم الأولوية.\n\n---\n\n## الحدود الفاصلة: بيانات وصف الاكتشاف مقابل تحميل الامتداد في وقت التشغيل\n\nيُغذّي اكتشاف `gemini-extension.json` حاليًا بيانات وصف القدرة (عناصر `Extension`). **لا** يقوم مباشرةً بتحميل وحدات امتداد TS/JS قابلة للتشغيل.\n\nيستخدم تحميل الوحدات في وقت التشغيل (`discoverAndLoadExtensions()` / `loadExtensions()`) مسارات `extension-modules` الصريحة، ويقوم حاليًا بتصفية الوحدات المكتشفة تلقائيًا للموفر `native` فقط.\n\nالانعكاس العملي:\n\n- امتدادات Gemini manifest قابلة للاكتشاف بوصفها سجلات قدرات.\n- لا يتم تنفيذها بذاتها بوصفها وحدات امتداد في وقت التشغيل من قِبل خط أنابيب محمّل الامتدادات.\n\nهذا الفصل مقصود في التنفيذ الحالي ويُفسّر سبب إمكانية تباين اكتشاف الـ manifest وتحميل الوحدات القابلة للتنفيذ.\n",
	"ar/extensions/marketplace.md": "---\ntitle: نظام سوق الإضافات\ndescription: نظام سوق الإضافات لاكتشاف وتثبيت وإدارة مجموعات الإضافات المنسّقة.\nsidebar:\n  order: 4\n  label: السوق\ni18n:\n  sourceHash: 71d9f8f93a81\n  translator: machine\n---\n\n# نظام سوق الإضافات\n\nيتيح لك نظام السوق اكتشاف وتثبيت وإدارة الإضافات من كتالوجات مستضافة على Git. وهو متوافق مع تنسيق سجل إضافات Claude Code.\n\n## البداية السريعة\n\n```\n/marketplace add anthropics/f5-sales-demo-marketplace\n/marketplace install wordpress.com@f5-sales-demo-marketplace\n```\n\nأو اكتب `/marketplace` بدون أي معاملات لفتح متصفح الإضافات التفاعلي.\n\n## المفاهيم\n\n**السوق** هو مستودع Git (أو مجلد محلي) يحتوي على ملف كتالوج في المسار `.xcsh-plugin/marketplace.json`. يسرد الكتالوج الإضافات المتاحة مع مصادرها وأوصافها وبياناتها الوصفية.\n\n**الإضافة** هي مجلد يحتوي على مهارات أو أوامر أو خطافات (hooks) أو خوادم MCP أو خوادم LSP. يتم تعريف الإضافات بصيغة `name@marketplace` (مثل `code-review@f5-sales-demo-marketplace`).\n\n**النطاقات**: يمكن تثبيت الإضافات على نطاقين:\n\n- **المستخدم** (الافتراضي) -- متاحة في جميع المشاريع، تُخزَّن في `~/.xcsh/plugins/installed_plugins.json`\n- **المشروع** -- متاحة فقط في المشروع الحالي، تُخزَّن في `.xcsh/installed_plugins.json`\n\nتثبيتات نطاق المشروع تتجاوز تثبيتات نطاق المستخدم للإضافة نفسها.\n\n## الأوامر\n\n### الوضع التفاعلي\n\n| الأمر | التأثير |\n|---|---|\n| `/marketplace` | فتح متصفح الإضافات التفاعلي (تثبيت) |\n\n### إدارة السوق\n\n| الأمر | التأثير |\n|---|---|\n| `/marketplace add <source>` | إضافة مصدر سوق |\n| `/marketplace remove <name>` | إزالة سوق |\n| `/marketplace update [name]` | إعادة جلب الكتالوج(ات)؛ احذف الاسم لتحديث الكل |\n| `/marketplace list` | عرض الأسواق المُعدّة |\n\n### عمليات الإضافات\n\n| الأمر | التأثير |\n|---|---|\n| `/marketplace discover [marketplace]` | تصفح الإضافات المتاحة |\n| `/marketplace install [--force] [--scope user\\|project] name@marketplace` | تثبيت إضافة |\n| `/marketplace uninstall [--scope user\\|project] name@marketplace` | إلغاء تثبيت إضافة |\n| `/marketplace installed` | عرض الإضافات المثبتة من السوق |\n| `/marketplace upgrade [--scope user\\|project] [name@marketplace]` | ترقية إضافة واحدة أو جميع الإضافات |\n\n### المكافئات في سطر الأوامر\n\nنفس العمليات متاحة من سطر الأوامر:\n\n```\nxcsh plugin marketplace add <source>\nxcsh plugin marketplace remove <name>\nxcsh plugin marketplace update [name]\nxcsh plugin marketplace list\nxcsh plugin discover [marketplace]\nxcsh plugin install --scope project name@marketplace\n```\n\n## مصادر السوق\n\nعند تشغيل `/marketplace add <source>`، يصنّف النظام المصدر:\n\n| صيغة المصدر | النوع | مثال |\n|---|---|---|\n| `owner/repo` | اختصار GitHub | `anthropics/f5-sales-demo-marketplace` |\n| `https://...*.json` | رابط كتالوج مباشر | `https://example.com/marketplace.json` |\n| `https://...*.git` أو `git@...` | مستودع Git | `https://github.com/org/repo.git` |\n| `./path` أو `~/path` أو `/path` | مجلد محلي | `./my-marketplace` |\n\nيقوم النظام باستنساخ المستودع (أو قراءة المجلد المحلي)، وتحديد موقع `.xcsh-plugin/marketplace.json`، والتحقق من صحته، وتخزين الكتالوج محلياً في ذاكرة التخزين المؤقت.\n\n## صيغة الكتالوج (marketplace.json)\n\nيوجد كتالوج السوق في `.xcsh-plugin/marketplace.json` في جذر المستودع:\n\n```json\n{\n  \"$schema\": \"https://anthropic.com/claude-code/marketplace.schema.json\",\n  \"name\": \"my-marketplace\",\n  \"owner\": {\n    \"name\": \"Your Name\",\n    \"email\": \"you@example.com\"\n  },\n  \"description\": \"A collection of plugins\",\n  \"plugins\": [\n    {\n      \"name\": \"my-plugin\",\n      \"description\": \"What this plugin does\",\n      \"source\": \"./plugins/my-plugin\",\n      \"category\": \"development\",\n      \"homepage\": \"https://github.com/you/my-plugin\"\n    }\n  ]\n}\n```\n\n### الحقول المطلوبة\n\n| الحقل | الوصف |\n|---|---|\n| `name` | اسم السوق. أحرف صغيرة أبجدية رقمية، شرطات، ونقاط. يجب أن يبدأ وينتهي بحرف أبجدي رقمي. بحد أقصى 64 حرفاً. |\n| `owner.name` | اسم مالك السوق |\n| `plugins` | مصفوفة من إدخالات الإضافات |\n\n### حقول إدخال الإضافة\n\n| الحقل | مطلوب | الوصف |\n|---|---|---|\n| `name` | نعم | اسم الإضافة (نفس قواعد اسم السوق) |\n| `source` | نعم | مكان العثور على الإضافة (انظر أدناه) |\n| `description` | لا | وصف مختصر |\n| `version` | لا | سلسلة الإصدار |\n| `author` | لا | `{ name, email? }` |\n| `homepage` | لا | رابط URL |\n| `category` | لا | سلسلة التصنيف (مثل `development`، `productivity`، `security`) |\n| `tags` | لا | مصفوفة من الوسوم النصية |\n| `strict` | لا | قيمة منطقية |\n| `commands` | لا | أوامر الشرطة المائلة المُقدَّمة |\n| `agents` | لا | الوكلاء المُقدَّمون |\n| `hooks` | لا | تعريفات الخطافات |\n| `mcpServers` | لا | تعريفات خوادم MCP |\n| `lspServers` | لا | تعريفات خوادم LSP |\n\n### صيغ مصدر الإضافة\n\nيدعم حقل `source` عدة صيغ:\n\n**مسار نسبي** (داخل مستودع السوق):\n\n```json\n\"source\": \"./plugins/my-plugin\"\n```\n\n**رابط مستودع Git**:\n\n```json\n\"source\": {\n  \"source\": \"url\",\n  \"url\": \"https://github.com/org/repo.git\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**اختصار GitHub**:\n\n```json\n\"source\": {\n  \"source\": \"github\",\n  \"repo\": \"org/repo\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**مجلد فرعي في Git** (مستودع أحادي):\n\n```json\n\"source\": {\n  \"source\": \"git-subdir\",\n  \"url\": \"https://github.com/org/monorepo.git\",\n  \"path\": \"plugins/my-plugin\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**حزمة npm**:\n\n```json\n\"source\": {\n  \"source\": \"npm\",\n  \"package\": \"@scope/my-plugin\",\n  \"version\": \"1.0.0\"\n}\n```\n\n## التخطيط على القرص\n\n```\n~/.xcsh/\n  config/\n    marketplaces.json          # سجل الأسواق المُضافة\n  plugins/\n    installed_plugins.json     # الإضافات المثبتة على نطاق المستخدم\n    cache/\n      marketplaces/            # كتالوجات السوق المخزنة مؤقتاً\n      plugins/                 # مجلدات الإضافات المخزنة مؤقتاً\n\n<project>/.xcsh/\n  installed_plugins.json       # الإضافات المثبتة على نطاق المشروع\n```\n\n## قواعد التسمية\n\nيجب أن تكون أسماء الأسواق والإضافات:\n\n- تبدأ وتنتهي بحرف صغير أو رقم\n- تحتوي فقط على أحرف صغيرة وأرقام وشرطات ونقاط\n- بحد أقصى 64 حرفاً\n\nيجب ألا تتجاوز معرّفات الإضافات (`name@marketplace`) 128 حرفاً إجمالاً.\n\nأمثلة صحيحة: `my-plugin`، `code-review`، `wordpress.com`، `ai-firstify`\nأمثلة غير صحيحة: `-bad`، `bad-`، `.bad`، `Bad`، `under_score`\n",
	"ar/extensions/plugin-manager-installer-plumbing.md": "---\ntitle: مدير الإضافات وأنابيب التثبيت\ndescription: >-\n  الجوانب الداخلية لمدير الإضافات تشمل التثبيت والتحقق وحل التبعيات وإدارة دورة\n  الحياة.\nsidebar:\n  order: 5\n  label: مدير الإضافات\ni18n:\n  sourceHash: 9c33e5a2c22a\n  translator: machine\n---\n\n# مدير الإضافات وأنابيب التثبيت\n\nيصف هذا المستند كيفية تأثير عمليات `xcsh plugin` على حالة الإضافات على القرص، وكيف تُصبح الإضافات المثبّتة قدرات وقت تشغيل (الأدوات حالياً، مع توفر حل مسارات الخطافات/الأوامر).\n\n## النطاق والمعمارية\n\nيوجد تطبيقان لإدارة الإضافات في قاعدة الكود:\n\n1. **المسار النشط المستخدم بواسطة أوامر CLI**: `PluginManager` (`src/extensibility/plugins/manager.ts`)\n2. **وحدة المساعد القديمة**: دوال المثبّت (`src/extensibility/plugins/installer.ts`)\n\nيمر تنفيذ الأمر `xcsh plugin ...` عبر `PluginManager`.\n\nلا يزال `installer.ts` يوثّق فحوصات الأمان المهمة وسلوك نظام الملفات، لكنه ليس المسار المستخدم بواسطة `src/commands/plugin.ts` + `src/cli/plugin-cli.ts`.\n\n## دورة الحياة: من استدعاء CLI إلى التوفر في وقت التشغيل\n\n```text\nxcsh plugin <action> ...\n  -> src/commands/plugin.ts\n  -> runPluginCommand(...) in src/cli/plugin-cli.ts\n  -> PluginManager method (install/list/uninstall/link/...) \n  -> mutate ~/.xcsh/plugins/{package.json,node_modules,xcsh-plugins.lock.json}\n  -> runtime discovery: discoverAndLoadCustomTools(...)\n  -> getAllPluginToolPaths(cwd)\n  -> custom tool loader imports tool modules\n```\n\n### نقاط دخول الأوامر\n\n- يُعرّف `src/commands/plugin.ts` الأمر والأعلام ويُحيل إلى `runPluginCommand`.\n- يُعيّن `src/cli/plugin-cli.ts` الأوامر الفرعية إلى أساليب `PluginManager`:\n  - `install`، `uninstall`، `list`، `link`، `doctor`، `features`، `config`، `enable`، `disable`\n- لا يوجد إجراء `update` صريح؛ يتم التحديث عبر إعادة تشغيل `install` بمواصفة حزمة/إصدار جديدة.\n\n## النموذج على القرص\n\nتقع حالة الإضافات العامة تحت `~/.xcsh/plugins`:\n\n- `package.json` — بيان التبعيات المستخدم بواسطة `bun install`/`bun uninstall`\n- `node_modules/` — حزم الإضافات المثبّتة أو الروابط الرمزية\n- `xcsh-plugins.lock.json` — حالة وقت التشغيل:\n  - تفعيل/تعطيل لكل إضافة\n  - مجموعة الميزات المحددة لكل إضافة\n  - إعدادات الإضافة المُحفوظة\n\nتقع التجاوزات المحلية للمشروع في:\n\n- `<cwd>/.xcsh/plugin-overrides.json`\n\nالتجاوزات للقراءة فقط من منظور المدير/المُحمّل (لا يوجد مسار كتابة هنا)، ويمكنها تعطيل الإضافات أو تجاوز الميزات/الإعدادات لهذا المشروع.\n\n## تحليل مواصفة الإضافة وتفسير البيانات الوصفية\n\n## قواعد نحو مواصفة التثبيت\n\nيدعم `parsePluginSpec` (`parser.ts`):\n\n- `pkg` -> `features: null` (السلوك الافتراضي)\n- `pkg[*]` -> تفعيل جميع ميزات البيان\n- `pkg[]` -> عدم تفعيل أي ميزات اختيارية\n- `pkg[a,b]` -> تفعيل الميزات المسمّاة\n- `@scope/pkg@1.2.3[feat]` -> حزمة محددة النطاق والإصدار مع اختيار ميزات صريح\n\nيُزيل `extractPackageName` لاحقة الإصدار للبحث عن المسار على القرص بعد التثبيت.\n\n## مصدر البيان والحقول المطلوبة\n\nيُحلَّل البيان على النحو التالي:\n\n1. `package.json.xcsh`\n2. احتياطي `package.json.pi`\n3. احتياطي `{ version: package.version }`\n\nالانعكاسات:\n\n- لا يوجد تحقق صارم من المخطط في المدير/المُحمّل.\n- الحزمة التي تفتقر إلى `xcsh`/`pi` لا تزال قابلة للتثبيت والإدراج.\n- يتخطى تحميل الإضافة في وقت التشغيل (`getEnabledPlugins`) الحزم التي تفتقر إلى بيان `xcsh`/`pi`.\n- يُستبدل `manifest.version` دائماً من `version` الحزمة.\n\nفشل تحليل JSON في `package.json` يُعدّ فشلاً صعباً عند القراءة؛ قد يفشل شكل البيان المشوّه لاحقاً فقط عند استهلاك حقول محددة.\n\n## تدفق التثبيت/التحديث (`PluginManager.install`)\n\n1. تحليل صيغة أقواس الميزات من مواصفة التثبيت.\n2. التحقق من اسم الحزمة بواسطة regex + قائمة رفض أحرف shell الخاصة.\n3. التأكد من وجود `package.json` للإضافة (`xcsh-plugins`، خريطة التبعيات الخاصة).\n4. تشغيل `bun install <packageSpec>` في `~/.xcsh/plugins`.\n5. قراءة `node_modules/<name>/package.json` للحزمة المثبّتة.\n6. حل البيان وحساب `enabledFeatures`:\n   - `[*]`: جميع الميزات المعلنة (أو `null` إذا لم تكن هناك خريطة ميزات)\n   - `[a,b]`: التحقق من وجود كل ميزة في خريطة ميزات البيان\n   - `[]`: قائمة ميزات فارغة\n   - مواصفة مجردة: `null` (استخدام سياسة الافتراضيات لاحقاً في المُحمّل)\n7. تحديث أو إدراج حالة وقت تشغيل ملف القفل: `{ version, enabledFeatures, enabled: true }`.\n\n### دلالات التحديث\n\nنظراً لأن التحديث مدفوع بالتثبيت:\n\n- `xcsh plugin install pkg@newVersion` يُحدّث التبعية وإصدار ملف القفل.\n- تُحفظ الإعدادات الحالية؛ يُستبدل إدخال الحالة للإصدار/الميزات/التفعيل.\n- لا توجد منطق \"التحقق من التحديثات\" أو الترحيل التحويلي.\n\n## تدفق الإزالة (`PluginManager.uninstall`)\n\n1. التحقق من اسم الحزمة.\n2. تشغيل `bun uninstall <name>` في مجلد الإضافة.\n3. إزالة حالة وقت تشغيل الإضافة من ملف القفل:\n   - `config.plugins[name]`\n   - `config.settings[name]`\n\nإذا فشل أمر إلغاء التثبيت، لا تتغير حالة وقت التشغيل.\n\n## تدفق الإدراج (`PluginManager.list`)\n\n1. قراءة خريطة تبعيات الإضافة من `~/.xcsh/plugins/package.json`.\n2. تحميل تهيئة وقت تشغيل ملف القفل (الملف غير موجود -> افتراضيات فارغة).\n3. تحميل تجاوزات المشروع (`<cwd>/.xcsh/plugin-overrides.json`، أخطاء التحليل/القراءة -> كائن فارغ مع تحذير).\n4. لكل تبعية تحتوي على `package.json` قابل للحل:\n   - بناء سجل `InstalledPlugin`\n   - دمج حالة الميزة/التفعيل:\n     - الأساس من ملف القفل (أو الافتراضيات)\n     - يمكن لتجاوزات المشروع استبدال اختيار الميزات\n     - قائمة `disabled` الخاصة بالمشروع تُخفي الإضافة كمعطّلة\n\nهذه هي الحالة الفعّالة المستخدمة بواسطة مخرجات حالة CLI وعمليات الإعدادات/الميزات.\n\n## تدفق الربط (`PluginManager.link`)\n\nيدعم `link` تطوير الإضافات المحلية عبر إنشاء رابط رمزي لحزمة محلية في `~/.xcsh/plugins/node_modules/<pkg.name>`.\n\nالسلوك:\n\n1. حل `localPath` بالنسبة لـ cwd المدير.\n2. طلب `package.json` المحلي وحقل `name`.\n3. التأكد من وجود مجلدات الإضافة.\n4. لأسماء النطاق، إنشاء مجلد النطاق.\n5. إزالة المسار الموجود في موقع الرابط المستهدف.\n6. إنشاء الرابط الرمزي.\n7. إضافة إدخال ملف القفل لوقت التشغيل ممكّناً بالميزات الافتراضية (`null`).\n\nتنبيه: لا يُطبّق `PluginManager.link` الحالي فحص حدود مسار `cwd` الموجود في `installer.ts` القديم (`normalizedPath.startsWith(normalizedCwd)`)، لذا تقع مسؤولية الثقة على عاتق المستدعي.\n\n## التحميل في وقت التشغيل: من الإضافة المثبّتة إلى القدرات القابلة للاستدعاء\n\n## بوابة الاكتشاف\n\nيقرأ `getEnabledPlugins(cwd)` (`plugins/loader.ts`):\n\n- بيان تبعيات الإضافة (`package.json`)\n- حالة وقت تشغيل ملف القفل\n- تجاوزات المشروع عبر `getConfigDirPaths(\"plugin-overrides.json\", { user: false, cwd })`\n\nالتصفية:\n\n- تخطي إذا لم يوجد `package.json` للإضافة\n- تخطي إذا كان البيان (`xcsh`/`pi`) غائباً\n- تخطي إذا كان معطّلاً عالمياً في ملف القفل\n- تخطي إذا كان معطّلاً على مستوى المشروع\n\n## حل مسارات القدرات\n\nلكل إضافة ممكّنة:\n\n- `resolvePluginToolPaths(plugin)`\n- `resolvePluginHookPaths(plugin)`\n- `resolvePluginCommandPaths(plugin)`\n\nيتضمن كل محلِّل إدخالات أساسية بالإضافة إلى إدخالات الميزات:\n\n- قائمة ميزات صريحة -> الميزات المحددة فقط\n- `enabledFeatures === null` -> تفعيل الميزات المميّزة بـ `default: true`\n\nيتم تخطي الملفات المفقودة بصمت (حارس `existsSync`).\n\n## اختلافات التوصيل الحالي في وقت التشغيل\n\n- **الأدوات مُوصَّلة في وقت التشغيل اليوم** عبر `discoverAndLoadCustomTools` (`custom-tools/loader.ts`)، التي تستدعي `getAllPluginToolPaths(cwd)`.\n- تُزال تكرارات المسارات بالمسار المطلق المحلول في اكتشاف الأدوات المخصصة (مجموعة `seen`، يفوز المسار الأول).\n- **محلِّلات الخطافات/الأوامر موجودة** ومُصدَّرة، لكن هذا المسار البرمجي لا يوصلها حالياً بسجل وقت تشغيل بنفس الطريقة التي تُوصَّل بها الأدوات.\n\n## تفاصيل إدارة القفل/الحالة\n\nيُخزّن `PluginManager` التهيئة في وقت التشغيل مؤقتاً في الذاكرة لكل مثيل (`#runtimeConfig`) ويُحمّلها بتكاسل مرة واحدة.\n\nسلوك التحميل:\n\n- ملف القفل غير موجود -> `{ plugins: {}, settings: {} }`\n- فشل قراءة/تحليل ملف القفل -> تحذير + نفس الافتراضيات الفارغة\n\nسلوك الحفظ:\n\n- يكتب JSON كامل لملف القفل مُنسَّقاً عند كل تعديل\n\nلا يوجد قفل متعدد العمليات أو استراتيجية دمج؛ يمكن للكتّاب المتزامنين الكتابة فوق بعضهم.\n\n## فحوصات الأمان وحدود الثقة\n\n## التحقق من المدخلات/الحزم\n\nيُطبّق مسار المدير النشط التحقق من اسم الحزمة:\n\n- regex للمواصفات المحددة/غير المحددة النطاق (اختيارياً مع الإصدار)\n- قائمة رفض صريحة لأحرف shell الخاصة (`[;&|`$(){}[]<>\\\\]`)\n\nيُقيّد هذا مخاطر حقن الأوامر عند استدعاء `bun install/uninstall`.\n\n## حدود ثقة نظام الملفات\n\n- يُنفَّذ كود الإضافة داخل العملية عند استيراد وحدات الأدوات المخصصة؛ لا يوجد عزل.\n- يُوصَّل المسارات النسبية للبيان بمجلد حزمة الإضافة ويُتحقق من وجودها فقط.\n- الحزمة نفسها تُعدّ كوداً موثوقاً به بعد التثبيت.\n\n## فحوصات المثبّت القديم فقط\n\nيتضمن `installer.ts` فحوصات إضافية عند وقت الربط غير موجودة في `PluginManager.link`:\n\n- يجب أن يُحلَّل المسار المحلي داخل cwd المشروع\n- حراس إضافية لاجتياز اسم الحزمة/المسار لتسمية الهدف الرمزي\n\nنظراً لأن CLI يستخدم `PluginManager`، فإن هذه الحراس الأكثر صرامة للربط غير موجودة حالياً على المسار الرئيسي.\n\n## سلوك الفشل والنجاح الجزئي والتراجع\n\nمدير الإضافات ليس تحويلياً.\n\n| مرحلة العملية | سلوك الفشل | التراجع |\n| --- | --- | --- |\n| `bun install` يفشل | يتوقف التثبيت مع stderr | غير متاح (لا كتابة للحالة بعد) |\n| نجاح التثبيت، ثم فشل التحقق من البيان/الميزات | يفشل الأمر | لا تراجع بإلغاء التثبيت؛ قد تبقى التبعية في `node_modules`/`package.json` |\n| نجاح التثبيت، ثم فشل كتابة ملف القفل | يفشل الأمر | لا تراجع للحزمة المثبّتة |\n| نجاح `bun uninstall`، فشل كتابة ملف القفل | يفشل الأمر | الحزمة مُزالة، قد تبقى حالة وقت تشغيل قديمة |\n| `link` يُزيل الهدف القديم ثم يفشل إنشاء الرابط الرمزي | يفشل الأمر | لا استعادة للرابط/المجلد السابق |\n\nتشغيلياً، يمكن لـ `doctor --fix` إصلاح بعض الانحرافات (`bun install`، تنظيف التهيئة اليتيمة، تنظيف الميزات غير الصالحة)، لكنه مجهود بذل ما في الوسع.\n\n## ملخص سلوك البيان المشوّه/المفقود\n\n- الحقل `xcsh`/`pi` مفقود:\n  - التثبيت/الإدراج: مسموح به (بيان أدنى)\n  - اكتشاف الإضافات الممكّنة في وقت التشغيل: يُتخطى كغير إضافة\n- ميزة مفقودة مشار إليها بمواصفة التثبيت أو `features --set/--enable`: خطأ صعب مع قائمة الميزات المتاحة\n- `plugin-overrides.json` غير صالح: يُتجاهل مع الرجوع إلى `{}` في كلا مسارات المدير والمُحمّل\n- مسارات الملفات للأدوات/الخطافات/الأوامر المشار إليها في البيان غير موجودة: يُتجاهل بصمت أثناء توسيع المحلِّل؛ يُشار إليها كأخطاء فقط بواسطة `doctor`\n\n## اختلافات الوضع والأولوية\n\n- `--dry-run` (التثبيت): يُعيد نتيجة تثبيت اصطناعية، لا كتابة لنظام الملفات/الشبكة/الحالة.\n- `--json`: تنسيق المخرجات فقط، لا تغيير في السلوك.\n- تجاوزات المشروع تأخذ دائماً الأولوية على ملف القفل العام لعرض الميزات/الإعدادات.\n- التفعيل الفعّال هو `runtimeEnabled && !projectDisabled`.\n\n## ملفات التطبيق\n\n- [`src/commands/plugin.ts`](../../packages/coding-agent/src/commands/plugin.ts) — إعلان أمر CLI وتعيين الأعلام\n- [`src/cli/plugin-cli.ts`](../../packages/coding-agent/src/cli/plugin-cli.ts) — توزيع الإجراءات، معالجات الأوامر الموجهة للمستخدم\n- [`src/extensibility/plugins/manager.ts`](../../packages/coding-agent/src/extensibility/plugins/manager.ts) — تطبيق التثبيت/الإزالة/الإدراج/الربط/الحالة/doctor النشط\n- [`src/extensibility/plugins/installer.ts`](../../packages/coding-agent/src/extensibility/plugins/installer.ts) — مساعدات المثبّت القديمة وفحوصات أمان الربط الإضافية\n- [`src/extensibility/plugins/loader.ts`](../../packages/coding-agent/src/extensibility/plugins/loader.ts) — اكتشاف الإضافات الممكّنة وحل مسارات الأدوات/الخطافات/الأوامر\n- [`src/extensibility/plugins/parser.ts`](../../packages/coding-agent/src/extensibility/plugins/parser.ts) — مساعدات تحليل مواصفة التثبيت واسم الحزمة\n- [`src/extensibility/plugins/types.ts`](../../packages/coding-agent/src/extensibility/plugins/types.ts) — عقود نوع البيان/وقت التشغيل/التجاوز\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts) — التوصيل في وقت التشغيل لوحدات الأدوات المقدَّمة من الإضافات\n",
	"ar/extensions/rulebook-matching-pipeline.md": "---\ntitle: خط أنابيب مطابقة كتاب القواعد\ndescription: >-\n  خط أنابيب مطابقة كتاب القواعد لاختيار مجموعات التعليمات الخاصة بالسياق\n  وتطبيقها على جلسات الوكيل.\nsidebar:\n  order: 6\n  label: مطابقة كتاب القواعد\ni18n:\n  sourceHash: a16a9c565053\n  translator: machine\n---\n\n# خط أنابيب مطابقة كتاب القواعد\n\nيصف هذا المستند كيفية اكتشاف وكيل البرمجة للقواعد من تنسيقات الإعداد المدعومة، وتطبيعها في شكل `Rule` موحّد، وحل تعارضات الأسبقية، وتقسيم الناتج إلى:\n\n- **قواعد كتاب القواعد** (متاحة للنموذج عبر موجّه النظام + عناوين URL من النوع `rule://`)\n- **قواعد TTSR** (قواعد مقاطعة تدفق السفر عبر الزمن)\n\nيعكس هذا المستند التنفيذ الحالي، بما في ذلك الدلالات الجزئية والبيانات الوصفية التي تُحلَّل ولكن لا تُطبَّق.\n\n## ملفات التنفيذ\n\n- [`../src/capability/rule.ts`](../../packages/coding-agent/src/capability/rule.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/discovery/index.ts`](../../packages/coding-agent/src/discovery/index.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/cursor.ts`](../../packages/coding-agent/src/discovery/cursor.ts)\n- [`../src/discovery/windsurf.ts`](../../packages/coding-agent/src/discovery/windsurf.ts)\n- [`../src/discovery/cline.ts`](../../packages/coding-agent/src/discovery/cline.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/system-prompt.ts`](../../packages/coding-agent/src/system-prompt.ts)\n- [`../src/internal-urls/rule-protocol.ts`](../../packages/coding-agent/src/internal-urls/rule-protocol.ts)\n- [`../src/utils/frontmatter.ts`](../../packages/coding-agent/src/utils/frontmatter.ts)\n\n## 1. الشكل القانوني للقاعدة\n\nتُطبِّع جميع الموفِّرين ملفات المصدر إلى `Rule`:\n\n```ts\ninterface Rule {\n  name: string;\n  path: string;\n  content: string;\n  globs?: string[];\n  alwaysApply?: boolean;\n  description?: string;\n  ttsrTrigger?: string;\n  _source: SourceMeta;\n}\n```\n\nهوية القدرة هي `rule.name` (`ruleCapability.key = rule => rule.name`).\n\nالنتيجة: الأسبقية وإزالة التكرار تعتمدان على **الاسم فقط**. يُعتبر ملفان مختلفان يحملان الاسم `name` ذاته قاعدةً منطقية واحدة.\n\n## 2. مصادر الاكتشاف والتطبيع\n\nيُسجِّل `src/discovery/index.ts` الموفِّرين تلقائيًا. بالنسبة لـ `rules`، الموفِّرون الحاليون هم:\n\n- `native` (الأولوية `100`)\n- `cursor` (الأولوية `50`)\n- `windsurf` (الأولوية `50`)\n- `cline` (الأولوية `40`)\n\n### الموفِّر الأصلي (`builtin.ts`)\n\nيُحمِّل قواعد `.xcsh` من:\n\n- المشروع: `<cwd>/.xcsh/rules/*.{md,mdc}`\n- المستخدم: `~/.xcsh/agent/rules/*.{md,mdc}`\n\nالتطبيع:\n\n- `name` = اسم الملف بدون `.md`/`.mdc`\n- يُحلَّل الـ frontmatter عبر `parseFrontmatter`\n- `content` = الجسم (بعد إزالة frontmatter)\n- يُعيَّن كلٌّ من `globs` و`alwaysApply` و`description` و`ttsr_trigger` مباشرةً\n\nتحفظ مهم: يُصنَّف `globs` كـ `string[] | undefined` دون تصفية للعناصر في هذا الموفِّر.\n\n### موفِّر Cursor (`cursor.ts`)\n\nيُحمِّل من:\n\n- المستخدم: `~/.cursor/rules/*.{mdc,md}`\n- المشروع: `<cwd>/.cursor/rules/*.{mdc,md}`\n\nالتطبيع (`transformMDCRule`):\n\n- `description`: يُحتفظ به فقط إذا كان من نوع string\n- `alwaysApply`: يُحتفظ بـ `true` فقط (`false` يصبح `undefined`)\n- `globs`: يقبل مصفوفة (عناصر string فقط) أو string مفردة\n- `ttsr_trigger`: string فقط\n- `name` من اسم الملف بدون الامتداد\n\n### موفِّر Windsurf (`windsurf.ts`)\n\nيُحمِّل من:\n\n- المستخدم: `~/.codeium/windsurf/memories/global_rules.md` (اسم القاعدة الثابت `global_rules`)\n- المشروع: `<cwd>/.windsurf/rules/*.md`\n\nالتطبيع:\n\n- `globs`: مصفوفة-من-string أو string مفردة\n- `alwaysApply` و`description` مُصنَّفان من frontmatter\n- `ttsr_trigger`: string فقط\n- `name` من اسم الملف لقواعد المشروع\n\n### موفِّر Cline (`cline.ts`)\n\nيبحث من `cwd` صعودًا عن أقرب `.clinerules`:\n\n- إذا كان مجلدًا: يُحمِّل `*.md` بداخله\n- إذا كان ملفًا: يُحمِّل ملفًا واحدًا كقاعدة باسم `clinerules`\n\nالتطبيع:\n\n- `globs`: مصفوفة-من-string أو string مفردة\n- `alwaysApply`: فقط إذا كان من نوع boolean\n- `description`: string فقط\n- `ttsr_trigger`: string فقط\n\n## 3. سلوك تحليل frontmatter والغموض\n\nتستخدم جميع الموفِّرين `parseFrontmatter` (`utils/frontmatter.ts`) بهذه الدلالات:\n\n1. يُحلَّل frontmatter فقط عندما يبدأ المحتوى بـ `---` ويحتوي على إغلاق `\\n---`.\n2. يُقطَّع الجسم بعد استخراج frontmatter.\n3. إذا فشل تحليل YAML:\n   - يُسجَّل تحذير،\n   - يعود المحلِّل إلى تحليل سطري بسيط للنوع `key: value` (`^(\\w+):\\s*(.*)$`).\n\nعواقب الغموض:\n\n- لا يدعم المحلِّل الاحتياطي المصفوفاتِ أو الكائناتِ المتداخلة أو قواعد الاقتباس أو المفاتيح ذات الواصلات.\n- تصبح قيم المحلِّل الاحتياطي strings (مثلاً `alwaysApply: true` تصبح string `\"true\"`)، لذا قد تتجاهل الموفِّرون الذين يشترطون أنواع boolean/string البياناتِ الوصفية.\n- يعمل `ttsr_trigger` في المحلِّل الاحتياطي (مفتاح بالشرطة السفلية)؛ أما المفاتيح مثل `thinking-level` فلا تعمل.\n- تُحمَّل الملفات التي لا تحتوي على frontmatter صالح كقواعد ببيانات وصفية فارغة وجسم المحتوى كاملاً.\n\n## 4. أسبقية الموفِّر وإزالة التكرار\n\nيدمج `loadCapability(\"rules\")` (`capability/index.ts`) مخرجات الموفِّرين ثم يُزيل التكرار حسب `rule.name`.\n\n### نموذج الأسبقية\n\n- يُرتَّب الموفِّرون تنازليًا حسب الأولوية.\n- الأولوية المتساوية تُحافظ على ترتيب التسجيل (`cursor` قبل `windsurf` من `discovery/index.ts`).\n- إزالة التكرار بمبدأ الفوز الأول: يُحتفظ بأول اسم قاعدة يُصادَف؛ العناصر اللاحقة بالاسم ذاته تُوسَم بـ `_shadowed` في `all` وتُستبعَد من `items`.\n\nترتيب موفِّري القواعد الفعلي حاليًا هو:\n\n1. `native` (100)\n2. `cursor` (50)\n3. `windsurf` (50)\n4. `cline` (40)\n\n### تحفظ ترتيب الموفِّر الداخلي\n\nداخل الموفِّر، يأتي ترتيب العناصر من نتائج glob في `loadFilesFromDir` إضافةً إلى ترتيب الإلحاق الصريح. هذا الترتيب حتمي بما يكفي للاستخدام العادي لكنه ليس مُرتَّبًا صراحةً في الكود.\n\nالفوارق الجديرة بالملاحظة في ترتيب المصدر:\n\n- يُلحق `native` مجلدات الإعداد الخاصة بالمشروع ثم المستخدم.\n- يُلحق `cursor` نتائج المستخدم ثم المشروع.\n- يُلحق `windsurf` المستخدم `global_rules` أولاً ثم قواعد المشروع.\n- يُحمِّل `cline` مصدر `.clinerules` الأقرب فقط.\n\n## 5. التقسيم إلى حاويات Rulebook وAlways-Apply وTTSR\n\nبعد اكتشاف القواعد في `createAgentSession` (`sdk.ts`):\n\n1. تُفحَص جميع القواعد المكتشفة.\n2. تُسجَّل القواعد التي تحتوي على `condition` (مفتاح frontmatter؛ يُقبَل `ttsr_trigger` / `ttsrTrigger` كبديل احتياطي) في `TtsrManager`.\n3. تُبنى قائمة `rulebookRules` منفصلة بهذا المحدِّد:\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && !rule.alwaysApply && !!rule.description\n```\n\n4. تُبنى قائمة `alwaysApplyRules`:\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && rule.alwaysApply === true\n```\n\n### سلوك الحاويات\n\n- **حاوية TTSR**: أي قاعدة تحتوي على `condition` (لا يُشترط وجود description). تأخذ الأولوية على الحاويات الأخرى.\n- **حاوية Always-apply**: `alwaysApply === true`، ليست TTSR. يُحقَن محتواها الكامل في موجّه النظام. قابلة للوصول عبر `rule://`.\n- **حاوية Rulebook**: يجب أن تحتوي على description، وألا تكون TTSR، وألا تكون `alwaysApply`. تُدرج في موجّه النظام بالاسم والوصف؛ يُقرأ محتواها عند الطلب عبر `rule://`.\n- القاعدة التي تحتوي على `condition` و`alwaysApply` معًا تنتقل إلى TTSR فقط (TTSR لها الأولوية).\n- القاعدة التي تحتوي على `alwaysApply` و`description` معًا تنتقل إلى always-apply فقط (لا إلى rulebook).\n\n## 6. كيف تؤثر البيانات الوصفية على أسطح وقت التشغيل\n\n### `description`\n\n- مطلوبة للإدراج في كتاب القواعد.\n- تُعرض في كتلة `<rules>` بموجّه النظام.\n- غياب description يعني أن القاعدة غير متاحة عبر `rule://` وغير مدرجة في قواعد موجّه النظام.\n\n### `globs`\n\n- تُنقَل عبر `Rule`.\n- تُعرض كإدخالات `<glob>...</glob>` في كتلة قواعد موجّه النظام.\n- مكشوفة في حالة واجهة مستخدم القواعد (قائمة وضع `extensions`).\n- **غير مطبَّقة للمطابقة التلقائية في هذا الخط.** لا يوجد مطابق glob في وقت التشغيل يختار القواعد حسب الملف الحالي أو هدف الأداة.\n\n### `alwaysApply`\n\n- مُحلَّل ومحتفَظ به من قِبَل الموفِّرين.\n- مستخدَم في عرض واجهة المستخدم (تسمية المشغِّل `\"always\"` في مدير حالة الإضافات).\n- مستخدَم كشرط استبعاد من `rulebookRules`.\n- **يُحقَن محتوى القاعدة الكامل تلقائيًا في موجّه النظام** (قبل قسم قواعد كتاب القواعد).\n- القاعدة قابلة للعنونة أيضًا عبر `rule://<name>` لإعادة القراءة.\n\n### `ttsr_trigger`\n\n- يُعيَّن إلى `rule.ttsrTrigger`.\n- إذا كان موجودًا، تُوجَّه القاعدة إلى مدير TTSR، لا إلى كتاب القواعد.\n\n## 7. مسار الإدراج في موجّه النظام\n\nيستقبل `buildSystemPromptInternal` كلاً من `rules` (كتاب القواعد) و`alwaysApplyRules`.\n\nتُعرض قواعد always-apply أولاً، مع حقن محتواها الخام مباشرةً في الموجّه.\n\nتُعرض قواعد كتاب القواعد في قسم `# Rules` مع:\n\n- `Read rule://<name> when working in matching domain`\n- `name` و`description` لكل قاعدة، وقائمة `<glob>` الاختيارية\n\nهذا إرشادي/سياقي: ينص نص الموجّه على طلب النموذج قراءة القواعد القابلة للتطبيق، لكن الكود لا يُطبِّق قابلية تطبيق glob.\n\n## 8. سلوك عنوان URL الداخلي `rule://`\n\nيُسجَّل `RuleProtocolHandler` بـ:\n\n```ts\nnew RuleProtocolHandler({ getRules: () => [...rulebookRules, ...alwaysApplyRules] })\n```\n\nالاستنتاجات:\n\n- يُحلَّل `rule://<name>` مقابل كلٍّ من **rulebookRules** و**alwaysApplyRules**.\n- قواعد TTSR الحصرية والقواعد التي لا تحتوي على description ولا `alwaysApply` غير قابلة للعنونة عبر `rule://`.\n- التحليل يعتمد على المطابقة الدقيقة للاسم.\n- الأسماء غير المعروفة تُرجع خطأً يسرد أسماء القواعد المتاحة.\n- المحتوى المُرجَع هو `rule.content` الخام (مع إزالة frontmatter)، من نوع المحتوى `text/markdown`.\n\n## 9. الدلالات الجزئية / غير المطبَّقة المعروفة\n\n1. تذكر أوصاف الموفِّرين ملفات قديمة (`.cursorrules`، `.windsurfrules`)، لكن مسارات كود التحميل الحالية لا تقرأ تلك الملفات فعليًا.\n2. بيانات `globs` الوصفية مكشوفة للموجّه/واجهة المستخدم لكنها غير مطبَّقة بواسطة منطق اختيار القواعد.\n3. اختيار القواعد لـ `rule://` يشمل قواعد rulebook وalways-apply، لكن لا يشمل قواعد TTSR الحصرية.\n4. تُنتَج تحذيرات الاكتشاف (`loadCapability(\"rules\").warnings`) لكن `createAgentSession` لا تعرضها/تسجِّلها حاليًا في هذا المسار.\n",
	"ar/extensions/skills.md": "---\ntitle: المهارات\ndescription: نظام المهارات لتسجيل القدرات المتخصصة واكتشافها واستدعائها في وكيل البرمجة.\nsidebar:\n  order: 3\n  label: المهارات\ni18n:\n  sourceHash: 3e062cc13851\n  translator: machine\n---\n\n# المهارات\n\nالمهارات هي حزم قدرات مدعومة بالملفات، يتم اكتشافها عند بدء التشغيل وعرضها على النموذج بوصفها:\n\n- بيانات وصفية خفيفة الوزن في موجه النظام (الاسم + الوصف)\n- محتوى عند الطلب عبر `read skill://...`\n- أوامر تفاعلية اختيارية `/skill:<name>`\n\nيتناول هذا المستند سلوك وقت التشغيل الحالي في `src/extensibility/skills.ts` و`src/discovery/builtin.ts` و`src/internal-urls/skill-protocol.ts` و`src/discovery/agents-md.ts`.\n\n## ما هي المهارة في قاعدة الشيفرة هذه\n\nتُمثَّل المهارة المكتشفة على النحو التالي:\n\n- `name`\n- `description`\n- `filePath` (مسار `SKILL.md`)\n- `baseDir` (دليل المهارة)\n- بيانات وصفية للمصدر (`provider`، `level`، المسار)\n\nيتطلب وقت التشغيل فقط `name` و`path` للتحقق من الصحة. عملياً، تعتمد جودة المطابقة على أن يكون `description` ذا معنى.\n\n## التخطيط المطلوب وتوقعات SKILL.md\n\n### تخطيط الدليل\n\nبالنسبة للاكتشاف القائم على الموفر (الموفرون الأصليون/Claude/Codex/Agents/plugin)، تُكتشف المهارات على **مستوى واحد تحت `skills/`**:\n\n- `<skills-root>/<skill-name>/SKILL.md`\n\nلا يكتشف محملو الموفرين الأنماط المتداخلة مثل `<skills-root>/group/<skill>/SKILL.md`.\n\nبالنسبة لـ `skills.customDirectories`، يستخدم الفحص نفس التخطيط غير المتكرر (`*/SKILL.md`).\n\n```text\nProvider-discovered layout (non-recursive under skills/):\n\n<root>/skills/\n  ├─ postgres/\n  │   └─ SKILL.md      ✅ discovered\n  ├─ pdf/\n  │   └─ SKILL.md      ✅ discovered\n  └─ team/\n      └─ internal/\n          └─ SKILL.md  ❌ not discovered by provider loaders\n\nCustom-directory scanning is also non-recursive, so nested paths are ignored unless you point `customDirectories` at that nested parent.\n```\n\n### بيانات frontmatter الخاصة بـ `SKILL.md`\n\nحقول frontmatter المدعومة في نوع المهارة:\n\n- `name?: string`\n- `description?: string`\n- `globs?: string[]`\n- `alwaysApply?: boolean`\n- تُحفظ المفاتيح الإضافية كبيانات وصفية غير معروفة\n\nسلوك وقت التشغيل الحالي:\n\n- يُعيَّن `name` افتراضياً إلى اسم دليل المهارة\n- يُطلب `description` من أجل:\n  - اكتشاف مهارات موفر `.xcsh` الأصلي (`requireDescription: true`)\n  - عمليات فحص `skills.customDirectories` عبر `scanSkillsFromDir` في `src/discovery/helpers.ts` (غير متكررة)\n- يمكن للموفرين غير الأصليين تحميل المهارات دون وصف\n\n## خط أنابيب الاكتشاف\n\nتُنفذ `discoverSkills()` في `src/extensibility/skills.ts` مرحلتين:\n\n1. **موفرو القدرات** عبر `loadCapability(\"skills\")`\n2. **الأدلة المخصصة** عبر `scanSkillsFromDir(..., { requireDescription: true })` (تعداد دليل بمستوى واحد)\n\nإذا كان `skills.enabled` يساوي `false`، يُعيد الاكتشاف مهارات بدون عناصر.\n\n### موفرو المهارات المدمجون والأولوية\n\nيُرتب الموفرون حسب الأولوية أولاً (الأعلى يفوز)، ثم ترتيب التسجيل عند التعادل.\n\nموفرو المهارات المسجلون حالياً:\n\n1. `native` (الأولوية 100) — مهارات المستخدم/المشروع `.xcsh` عبر `src/discovery/builtin.ts`\n2. `claude` (الأولوية 80)\n3. مجموعة الأولوية 70 (بترتيب التسجيل):\n   - `claude-plugins`\n   - `agents`\n   - `codex`\n\nمفتاح إزالة التكرار هو اسم المهارة. يفوز العنصر الأول بالاسم المعطى.\n\n### مبدلات المصدر والتصفية\n\nتطبق `discoverSkills()` عناصر التحكم التالية:\n\n- مبدلات المصدر: `enableCodexUser`، `enableClaudeUser`، `enableClaudeProject`، `enablePiUser`، `enablePiProject`\n- مرشحات glob على اسم المهارة:\n  - `ignoredSkills` (استبعاد)\n  - `includeSkills` (قائمة السماح للتضمين؛ فارغة تعني تضمين الكل)\n\nترتيب التصفية هو:\n\n1. المصدر مُمكَّن\n2. غير مستبعد\n3. مُدرج (إذا كانت قائمة التضمين موجودة)\n\nبالنسبة للموفرين غير codex/claude/native (على سبيل المثال `agents`، `claude-plugins`)، يعود التمكين حالياً إلى: مُمكَّن إذا كان **أي** مبدل مصدر مدمج مُمكَّناً.\n\n### التعامل مع التصادمات والتكرار\n\n- إزالة تكرار القدرات تحتفظ بالفعل بأول مهارة لكل اسم (موفر الأولوية الأعلى)\n- تقوم `extensibility/skills.ts` بالإضافة إلى ذلك بـ:\n  - إزالة تكرار الملفات المتطابقة بواسطة `realpath` (آمن للروابط الرمزية)\n  - إصدار تحذيرات التصادم عندما يتعارض اسم مهارة لاحقة\n  - الاحتفاظ بواجهة برمجية مريحة `discoverSkillsFromDir({ dir, source })` كمحوّل رفيع فوق `scanSkillsFromDir`\n- تُدمج مهارات الأدلة المخصصة بعد مهارات الموفر وتتبع نفس سلوك التصادم\n\n## سلوك الاستخدام في وقت التشغيل\n\n### التعرض في موجه النظام\n\nيستخدم بناء موجه النظام (`src/system-prompt.ts`) المهارات المكتشفة على النحو التالي:\n\n- إذا كانت أداة `read` متاحة:\n  - تضمين قائمة المهارات المكتشفة في الموجه\n- وإلا:\n  - حذف القائمة المكتشفة\n\nتتلقى الوكلاء الفرعية لأداة المهمة قائمة المهارات المكتشفة/المقدمة للجلسة عبر إنشاء الجلسة الاعتيادي؛ لا يوجد تثبيت مهارة لكل مهمة بصورة منفردة.\n\n### أوامر `/skill:<name>` التفاعلية\n\nإذا كان `skills.enableSkillCommands` يساوي true، يُسجّل الوضع التفاعلي أمر شريطة واحداً لكل مهارة مكتشفة.\n\nسلوك `/skill:<name> [args]`:\n\n- يقرأ ملف المهارة مباشرة من `filePath`\n- يُزيل frontmatter\n- يُحقن نص المهارة كرسالة مخصصة للمتابعة\n- يُضيف بيانات وصفية (`Skill: <path>`، `User: <args>` اختيارية)\n\n## سلوك رابط `skill://`\n\nيدعم `src/internal-urls/skill-protocol.ts`:\n\n- `skill://<name>` ← يُحلَّل إلى `SKILL.md` لتلك المهارة\n- `skill://<name>/<relative-path>` ← يُحلَّل داخل دليل المهارة\n\n```text\nskill:// URL resolution\n\nskill://pdf\n  -> <pdf-base>/SKILL.md\n\nskill://pdf/references/tables.md\n  -> <pdf-base>/references/tables.md\n\nGuards:\n- reject absolute paths\n- reject `..` traversal\n- reject any resolved path escaping <pdf-base>\n```\n\nتفاصيل الحل:\n\n- يجب أن يتطابق اسم المهارة تماماً\n- يتم فك ترميز URL للمسارات النسبية\n- تُرفض المسارات المطلقة\n- يُرفض اجتياز المسار (`..`)\n- يجب أن يظل المسار المحلول داخل `baseDir`\n- تُعيد الملفات المفقودة خطأ صريحاً `File not found`\n\nنوع المحتوى:\n\n- `.md` ← `text/markdown`\n- كل شيء آخر ← `text/plain`\n\nلا يُنفَّذ بحث احتياطي للأصول المفقودة.\n\n## المهارات مقابل XCSH.md والأوامر والأدوات والخطافات\n\n### المهارات مقابل XCSH.md\n\n- **المهارات**: حزم قدرات مُسماة واختيارية تُختار حسب سياق المهمة أو يُطلب بها صراحةً\n- **XCSH.md/ملفات السياق**: ملفات تعليمات دائمة تُحمَّل كقدرة ملف سياق وتُدمج وفق قواعد المستوى/العمق\n\nيمشي `src/discovery/agents-md.ts` تحديداً في الأدلة الأسلاف من `cwd` لاكتشاف ملفات `XCSH.md` المستقلة (حتى العمق 20)، مستبعداً مقاطع الأدلة المخفية.\n\n### المهارات مقابل أوامر الشريطة\n\n- **المهارات**: محتوى معرفة/سير عمل قابل للقراءة من النموذج\n- **أوامر الشريطة**: نقاط دخول أوامر يستدعيها المستخدم\n- `/skill:<name>` هو غلاف مريح يُحقن نص المهارة؛ لا يغير دلالات اكتشاف المهارة\n\n### المهارات مقابل الأدوات المخصصة\n\n- **المهارات**: محتوى توثيق/سير عمل يُحمَّل عبر سياق الموجه و`read`\n- **الأدوات المخصصة**: واجهات برمجية تنفيذية للأدوات يمكن للنموذج استدعاؤها مع مخططات وتأثيرات جانبية في وقت التشغيل\n\n### المهارات مقابل الخطافات\n\n- **المهارات**: محتوى سلبي\n- **الخطافات**: معترضون لوقت التشغيل مدفوعون بالأحداث يمكنهم حظر/تعديل السلوك أثناء التنفيذ\n\n## إرشادات التأليف العملية المرتبطة بمنطق الاكتشاف\n\n- ضع كل مهارة في دليلها الخاص: `<skills-root>/<skill-name>/SKILL.md`\n- أدرج دائماً frontmatter صريحاً لـ `name` و`description`\n- احتفظ بالأصول المشار إليها تحت نفس دليل المهارة وصل إليها بـ `skill://<name>/...`\n- للتصنيف المتداخل (`team/domain/skill`)، أشر `skills.customDirectories` إلى الدليل الأصل المتداخل؛ يظل الفحص نفسه غير متكرر\n- تجنب أسماء المهارات المكررة عبر المصادر؛ تفوز أول مطابقة بأولوية الموفر\n",
	"ar/index.md": "---\ntitle: توثيق xcsh\ndescription: >-\n  واجهة سطر أوامر للتطوير مدعومة بالذكاء الاصطناعي مع وكيل ترميز TypeScript\n  وطبقة Rust الأصلية للجلسات طويلة الأمد، ودعم MCP، وحزم المنصة.\nsidebar:\n  order: 0\n  label: نظرة عامة\ni18n:\n  sourceHash: b9288f42bf46\n  translator: machine\n---\n\nxcsh هي واجهة سطر أوامر للتطوير مدعومة بالذكاء الاصطناعي، تتضمن وكيل ترميز TypeScript وطبقة Rust أصلية (`pi-natives`). وهي تمتد على خط المصدر المفتوح\n[`badlogic/pi-mono`](https://github.com/badlogic/pi-mono) بإضافة بيئة تشغيل محصّنة، وجلسات طويلة الأمد مع التنقل في الشجرة والضغط، وأداة Python IPython، ودعم كامل لـ MCP، ونظام مهارات، وحزم للمنصة تستهدف Linux وmacOS وWindows.\n\n## من أين تبدأ\n\n- **[سياقات F5 XC](/runtime-tools/context-command)** — الاتصال بمستأجري F5 Distributed Cloud.\n  إنشاء السياقات، والتبديل بينها، وإدارة مساحات الأسماء وبيانات الاعتماد.\n- **الإعداد** — كيفية اكتشاف xcsh للإعداد وحله وتطبيق طبقاته.\n- **بيئة التشغيل والأدوات** — بيئات تشغيل أدوات bash / notebook / resolve وواجهة أوامر الشرطة المائلة.\n- **الجلسات** — سجل الإدخال للإلحاق فقط، والتنقل في الشجرة، والضغط، ونظام الذاكرة المستقل.\n- **العناصر الأصلية (Rust)** — معمارية الوحدة الإضافية N-API الخاصة بـ `pi-natives` التي تشغّل shell / PTY / الوسائط / البحث.\n- **MCP** — الإعداد، وبروتوكول الاتصال الداخلي، ودورة حياة بيئة التشغيل، وكيفية تأليف الخوادم والأدوات.\n- **الامتدادات والمهارات والإضافات** — التأليف، والتحميل، وقواعد المطابقة، والسوق، ومثبّت الإضافات.\n- **الموفرون والنماذج** — إعداد النماذج، والبث الداخلي، وبيئة تشغيل Python / IPython.\n- **واجهة المستخدم النصية (TUI)** — التصميم، وأمر `/tree`، وخطاطيف التكامل للامتدادات والأدوات المخصصة.\n\n## كيف يُنظَّم هذا المجموعة من التوثيق\n\nكل مجموعة رئيسية في الشريط الجانبي تُعيَّن لنظام فرعي من الوكيل. وضمن\nكل مجموعة، تسير الصفحات من \"نظرة عامة\" إلى \"التفاصيل الداخلية\" حتى تتمكن من التوقف عن القراءة\nحين يصبح لديك سياق كافٍ للمهمة التي تعمل عليها.\n",
	"ar/mcp/mcp-config.md": "---\ntitle: تهيئة MCP\ndescription: تهيئة خادم MCP والتحقق من صحته وإدارته لبيئة تشغيل وكيل البرمجة.\nsidebar:\n  order: 1\n  label: التهيئة\ni18n:\n  sourceHash: ef8b49458ce9\n  translator: machine\n---\n\n# تهيئة MCP في OMP\n\nيشرح هذا الدليل كيفية إضافة خوادم MCP وتعديلها والتحقق من صحتها لوكيل البرمجة OMP.\n\nالمصدر المرجعي في الكود:\n\n- أنواع تهيئة بيئة التشغيل: `packages/coding-agent/src/mcp/types.ts`\n- كاتب التهيئة: `packages/coding-agent/src/mcp/config-writer.ts`\n- المُحمِّل + التحقق: `packages/coding-agent/src/mcp/config.ts`\n- اكتشاف ملف `mcp.json` المستقل: `packages/coding-agent/src/discovery/mcp-json.ts`\n- المخطط: `packages/coding-agent/src/config/mcp-schema.json`\n\n## مواقع التهيئة المفضلة\n\nيمكن لـ OMP اكتشاف خوادم MCP من أدوات متعددة (`.claude/`، `.cursor/`، `.vscode/`، `opencode.json`، وغيرها)، ولكن للتهيئة الأصلية لـ OMP يُفضل عادةً استخدام أحد هذه الملفات:\n\n- على مستوى المشروع: `.xcsh/mcp.json`\n- على مستوى المستخدم: `~/.xcsh/mcp.json`\n\nيقبل OMP أيضاً ملفات احتياطية مستقلة في جذر المشروع:\n\n- `mcp.json`\n- `.mcp.json`\n\nاستخدم `.xcsh/mcp.json` عندما تريد أن يمتلك OMP التهيئة. استخدم `mcp.json` / `.mcp.json` في الجذر فقط عندما تريد ملفاً احتياطياً قابلاً للنقل يمكن لعملاء MCP الآخرين قراءته أيضاً.\n\n## إضافة مرجع المخطط\n\nأضف هذا السطر في أعلى الملف للحصول على الإكمال التلقائي والتحقق في المحرر:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {}\n}\n```\n\nيكتب OMP الآن هذا تلقائياً عندما تقوم عمليات `/mcp add` أو `/mcp enable` أو `/mcp disable` أو `/mcp reauth` أو غيرها من عمليات كتابة التهيئة بإنشاء أو تحديث ملف MCP مُدار بواسطة OMP.\n\n## هيكل الملف\n\nيدعم OMP هذا الهيكل على المستوى الأعلى:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"server-name\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"some-mcp-server\"]\n    }\n  },\n  \"disabledServers\": [\"server-name\"]\n}\n```\n\nالمفاتيح على المستوى الأعلى:\n\n- `$schema` — عنوان URL اختياري لمخطط JSON للأدوات\n- `mcpServers` — خريطة من اسم الخادم إلى تهيئة الخادم\n- `disabledServers` — قائمة حظر على مستوى المستخدم تُستخدم لإيقاف الخوادم المكتشفة بالاسم\n\nيجب أن تطابق أسماء الخوادم النمط `^[a-zA-Z0-9_.-]{1,100}$`.\n\n## حقول الخادم المدعومة\n\nالحقول المشتركة لجميع أنواع النقل:\n\n- `enabled?: boolean` — تخطي هذا الخادم عندما تكون القيمة `false`\n- `timeout?: number` — مهلة الاتصال بالمللي ثانية\n- `auth?: { ... }` — بيانات المصادقة الوصفية المستخدمة بواسطة OMP لعمليات OAuth/مفتاح API\n- `oauth?: { ... }` — إعدادات عميل OAuth الصريحة المستخدمة أثناء المصادقة/إعادة المصادقة\n\n### نقل `stdio`\n\n`stdio` هو الافتراضي عند حذف `type`.\n\nمطلوب:\n\n- `command: string`\n\nاختياري:\n\n- `type?: \"stdio\"`\n- `args?: string[]`\n- `env?: Record<string, string>`\n- `cwd?: string`\n\nمثال:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/Users/alice/projects\",\n        \"/Users/alice/Documents\"\n      ]\n    }\n  }\n}\n```\n\nيتبع هذا حزمة خادم نظام الملفات MCP الرسمية (`@modelcontextprotocol/server-filesystem`).\n\n### نقل `http`\n\nمطلوب:\n\n- `type: \"http\"`\n- `url: string`\n\nاختياري:\n\n- `headers?: Record<string, string>`\n\nمثال:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\nيتطابق هذا مع نقطة نهاية خادم GitHub MCP المستضاف من GitHub.\n\n### نقل `sse`\n\nمطلوب:\n\n- `type: \"sse\"`\n- `url: string`\n\nاختياري:\n\n- `headers?: Record<string, string>`\n\nمثال:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"legacy-remote\": {\n      \"type\": \"sse\",\n      \"url\": \"https://example.com/mcp/sse\"\n    }\n  }\n}\n```\n\nلا يزال `sse` مدعوماً للتوافق، لكن مواصفات MCP تفضل الآن Streamable HTTP (`type: \"http\"`) للخوادم الجديدة.\n\n## حقول المصادقة\n\nيفهم OMP كائنين متعلقين بالمصادقة.\n\n### `auth`\n\n```json\n{\n  \"type\": \"oauth\" | \"apikey\",\n  \"credentialId\": \"optional-stored-credential-id\",\n  \"tokenUrl\": \"optional-token-endpoint\",\n  \"clientId\": \"optional-client-id\",\n  \"clientSecret\": \"optional-client-secret\"\n}\n```\n\nاستخدم هذا عندما يجب على OMP تذكر كيفية إعادة تحميل بيانات الاعتماد لخادم ما.\n\n### `oauth`\n\n```json\n{\n  \"clientId\": \"...\",\n  \"clientSecret\": \"...\",\n  \"redirectUri\": \"...\",\n  \"callbackPort\": 3334,\n  \"callbackPath\": \"/oauth/callback\"\n}\n```\n\nاستخدم هذا عندما يتطلب خادم MCP إعدادات عميل OAuth صريحة.\n\nSlack هو المثال الأوضح حالياً. خادم MCP الخاص بـ Slack مستضاف على `https://mcp.slack.com/mcp`، ويستخدم Streamable HTTP، ويتطلب OAuth سري مع بيانات اعتماد عميل تطبيق Slack الخاص بك.\n\nمثال:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\nنقاط نهاية Slack ذات الصلة من وثائق Slack:\n\n- نقطة نهاية MCP: `https://mcp.slack.com/mcp`\n- نقطة نهاية التفويض: `https://slack.com/oauth/v2_user/authorize`\n- نقطة نهاية الرمز المميز: `https://slack.com/api/oauth.v2.user.access`\n\n## أمثلة شائعة للنسخ واللصق\n\n### خادم نظام الملفات عبر stdio\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/absolute/path/one\",\n        \"/absolute/path/two\"\n      ]\n    }\n  }\n}\n```\n\n### خادم GitHub المستضاف عبر HTTP\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\n### خادم GitHub المحلي عبر Docker\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"-i\",\n        \"--rm\",\n        \"-e\",\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\",\n        \"ghcr.io/github/github-mcp-server\"\n      ],\n      \"env\": {\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n      }\n    }\n  }\n}\n```\n\nيتطابق هذا مع صورة Docker المحلية الرسمية من GitHub `ghcr.io/github/github-mcp-server`.\n\n### خادم Slack المستضاف عبر OAuth\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\n## الأسرار وحل المتغيرات\n\nهذا هو الجزء الذي عادةً ما يُربك الناس.\n\n### في `.xcsh/mcp.json` و `~/.xcsh/mcp.json`\n\nقبل أن يُشغّل OMP خادماً أو يُرسل طلب HTTP، يحل قيم `env` و `headers` بهذه الطريقة:\n\n1. إذا بدأت القيمة بـ `!`، يُنفذها OMP كأمر shell ويستخدم المخرج القياسي بعد إزالة المسافات.\n2. وإلا يتحقق OMP أولاً مما إذا كانت القيمة تطابق اسم متغير بيئة.\n3. إذا لم يكن متغير البيئة ذلك معيناً، يستخدم OMP السلسلة النصية حرفياً.\n\nأمثلة:\n\n```json\n{\n  \"env\": {\n    \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n  },\n  \"headers\": {\n    \"X-MCP-Insiders\": \"true\"\n  }\n}\n```\n\nهذا يعني أن ما يلي صالح ومريح للأسرار المحلية:\n\n- `\"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"` → نسخ من بيئة shell الحالية\n- `\"Authorization\": \"Bearer hardcoded-token\"` → استخدام القيمة الحرفية\n- `\"Authorization\": \"!printf 'Bearer %s' \\\"$GITHUB_TOKEN\\\"\"` → بناء الترويسة من أمر\n\n### في `mcp.json` و `.mcp.json` في الجذر\n\nيقوم مُحمِّل الملف الاحتياطي المستقل أيضاً بتوسيع `${VAR}` و `${VAR:-default}` داخل السلاسل النصية أثناء الاكتشاف.\n\nمثال:\n\n```json\n{\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\",\n      \"headers\": {\n        \"Authorization\": \"Bearer ${GITHUB_TOKEN}\"\n      }\n    }\n  }\n}\n```\n\nإذا كنت تريد السلوك الأقل مفاجأة من OMP، فضّل `.xcsh/mcp.json` واستخدم قيم env/header صريحة.\n\n## `disabledServers`\n\n`disabledServers` مفيد بشكل رئيسي في ملف تهيئة المستخدم (`~/.xcsh/mcp.json`) عندما يتم اكتشاف خادم من مصدر آخر وتريد أن يتجاهله OMP دون تعديل تهيئة تلك الأداة الأخرى.\n\nمثال:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"disabledServers\": [\"github\", \"slack\"]\n}\n```\n\n## `/mcp add` مقابل تحرير JSON مباشرةً\n\nاستخدم `/mcp add` عندما تريد إعداداً موجَّهاً.\n\nاستخدم تحرير JSON المباشر عندما:\n\n- تحتاج إلى خيار نقل أو مصادقة لا يطلبه المعالج بعد\n- تريد لصق تعريف خادم من عميل MCP آخر\n- تريد التحقق المدعوم بالمخطط في محررك\n\nبعد التحرير، استخدم:\n\n- `/mcp reload` لإعادة اكتشاف الخوادم وإعادة الاتصال بها في الجلسة الحالية\n- `/mcp list` لمعرفة ملف التهيئة الذي جاء منه الخادم\n- `/mcp test <name>` لاختبار خادم واحد\n\n## قواعد التحقق التي يفرضها OMP\n\nمن `validateServerConfig()` في `packages/coding-agent/src/mcp/config.ts`:\n\n- `stdio` يتطلب `command`\n- `http` و `sse` يتطلبان `url`\n- لا يمكن للخادم تعيين كل من `command` و `url`\n- يتم رفض قيم `type` غير المعروفة\n\nالآثار العملية:\n\n- حذف `type` يعني `stdio`\n- إذا لصقت تهيئة خادم بعيد ونسيت `\"type\": \"http\"`، سيعامله OMP كـ `stdio` ويشتكي من أن `command` مفقود\n- `sse` يظل صالحاً للتوافق، لكن الخوادم المستضافة الجديدة يجب عادةً تهيئتها كـ `http`\n\n## الاكتشاف والأولوية\n\nلا يدمج OMP تعريفات الخوادم المكررة عبر الملفات. يتم ترتيب مزودي الاكتشاف حسب الأولوية، والتعريف ذو الأولوية الأعلى يفوز.\n\nفي الممارسة:\n\n- فضّل `.xcsh/mcp.json` أو `~/.xcsh/mcp.json` عندما تريد تجاوزاً خاصاً بـ OMP\n- حافظ على أسماء الخوادم فريدة عبر الأدوات عندما يكون ذلك ممكناً\n- استخدم `disabledServers` في تهيئة المستخدم عندما تستمر تهيئة طرف ثالث في إعادة تقديم خادم لا تريده\n\n## استكشاف الأخطاء وإصلاحها\n\n### `Server \"name\": stdio server requires \"command\" field`\n\nمن المحتمل أنك حذفت `type: \"http\"` على خادم بعيد.\n\n### `Server \"name\": both \"command\" and \"url\" are set`\n\nاختر نوع نقل واحد. يعامل OMP `command` كـ stdio و `url` كـ http/sse.\n\n### `/mcp add` نجح لكن الخادم لا يزال لا يتصل\n\nملف JSON صالح، لكن الخادم قد يكون غير قابل للوصول. استخدم `/mcp test <name>` وتحقق مما إذا كان:\n\n- الملف الثنائي أو صورة Docker موجودة\n- متغيرات البيئة المطلوبة معيّنة\n- عنوان URL البعيد قابل للوصول\n- رمز OAuth أو API صالح\n\n### الخادم موجود في تهيئة أداة أخرى لكن ليس في OMP\n\nشغّل `/mcp list`. يكتشف OMP العديد من ملفات MCP الخاصة بأطراف ثالثة، لكن التحميل على مستوى المشروع يمكن أيضاً تعطيله عبر إعداد `mcp.enableProjectConfig`.\n\n## المراجع\n\n- مواصفات نقل MCP: <https://modelcontextprotocol.io/specification/2025-03-26/basic/transports>\n- حزمة خادم نظام الملفات: <https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem>\n- خادم GitHub MCP: <https://github.com/github/github-mcp-server>\n- وثائق خادم Slack MCP: <https://docs.slack.dev/ai/slack-mcp-server/>\n",
	"ar/mcp/mcp-protocol-transports.md": "---\ntitle: بروتوكول MCP وآليات النقل الداخلية\ndescription: >-\n  MCP protocol implementation with stdio, SSE, and streamable HTTP transport\n  layers.\nsidebar:\n  order: 2\n  label: البروتوكول وطبقات النقل\ni18n:\n  sourceHash: 48632064dd00\n  translator: machine\n---\n\n# بروتوكول MCP وآليات النقل الداخلية\n\nيصف هذا المستند كيفية تنفيذ coding-agent لرسائل MCP JSON-RPC وكيفية فصل اهتمامات البروتوكول عن اهتمامات طبقة النقل.\n\n## النطاق\n\nيغطي:\n\n- تدفق طلبات/استجابات JSON-RPC والإشعارات\n- ربط الطلبات ودورة حياتها لطبقات نقل stdio وHTTP/SSE\n- سلوك المهلة الزمنية والإلغاء\n- نشر الأخطاء ومعالجة الحمولات غير الصالحة\n- حدود اختيار طبقة النقل (`stdio` مقابل `http`/`sse`)\n- مسؤوليات إعادة الاتصال/المحاولة التي تقع على مستوى طبقة النقل مقابل مستوى المدير\n\nلا يغطي تجربة تأليف الإضافات أو واجهة الأوامر.\n\n## ملفات التنفيذ\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/transports/stdio.ts`](../../packages/coding-agent/src/mcp/transports/stdio.ts)\n- [`src/mcp/transports/http.ts`](../../packages/coding-agent/src/mcp/transports/http.ts)\n- [`src/mcp/transports/index.ts`](../../packages/coding-agent/src/mcp/transports/index.ts)\n- [`src/mcp/json-rpc.ts`](../../packages/coding-agent/src/mcp/json-rpc.ts)\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n\n## حدود الطبقات\n\n### طبقة البروتوكول (JSON-RPC + أساليب MCP)\n\n- أشكال الرسائل معرّفة في `types.ts` (`JsonRpcRequest`، `JsonRpcNotification`، `JsonRpcResponse`، `JsonRpcMessage`).\n- منطق عميل MCP (`client.ts`) يحدد ترتيب الأساليب ومصافحة الجلسة:\n  1. طلب `initialize`\n  2. إشعار `notifications/initialized`\n  3. استدعاءات الأساليب مثل `tools/list`، `tools/call`\n\n### طبقة النقل (`MCPTransport`)\n\n`MCPTransport` تجرّد التسليم ودورة الحياة:\n\n- `request(method, params, options?) -> Promise<T>`\n- `notify(method, params?) -> Promise<void>`\n- `close()`\n- `connected`\n- دوال رد الاتصال الاختيارية: `onClose`، `onError`، `onNotification`\n\nتنفيذات طبقة النقل تملك تفاصيل التأطير والإدخال/الإخراج:\n\n- `StdioTransport`: JSON محدد بأسطر جديدة عبر stdio للعملية الفرعية\n- `HttpTransport`: JSON-RPC عبر HTTP POST، مع استجابات/استماع SSE اختيارية\n\n### تحذير مهم حالي\n\nدوال رد الاتصال لطبقة النقل (`onClose`، `onError`، `onNotification`) منفّذة، لكن تدفقات `MCPClient`/`MCPManager` الحالية لا تربط منطق إعادة الاتصال بهذه الدوال. الإشعارات تُستهلك فقط إذا سجّل المُستدعي معالِجات.\n\n## اختيار طبقة النقل\n\n`client.ts:createTransport()` تختار طبقة النقل من التكوين:\n\n- `type` محذوف أو `\"stdio\"` -> `createStdioTransport`\n- `\"http\"` أو `\"sse\"` -> `createHttpTransport`\n\n`\"sse\"` تُعامل كمتغير لطبقة نقل HTTP (نفس الفئة)، وليست تنفيذ طبقة نقل منفصل.\n\n## تدفق رسائل JSON-RPC والربط\n\n## معرّفات الطلبات\n\nكل طبقة نقل تولّد معرّفات لكل طلب (سلسلة `Math.random` + طابع زمني). المعرّفات هي رموز ربط محلية لطبقة النقل.\n\n## مسار ربط Stdio\n\n- الطلب الصادر يُسلسَل ككائن JSON واحد + `\\n`.\n- `#pendingRequests: Map<id, {resolve,reject}>` يخزّن الطلبات قيد التنفيذ.\n- حلقة القراءة تحلل JSONL من stdout وتستدعي `#handleMessage`.\n- إذا كانت الرسالة الواردة تحمل `id` مطابقاً، يُحلّ الطلب أو يُرفض.\n- إذا كانت الرسالة الواردة تحمل `method` بدون `id`، تُعامل كإشعار وتُرسل إلى `onNotification`.\n\nالمعرّفات غير المعروفة تُتجاهل (لا رفض، لا دالة رد خطأ).\n\n## مسار ربط HTTP\n\n- الطلب الصادر هو HTTP `POST` بجسم JSON ومعرّف مولّد.\n- مسار الاستجابة غير SSE: تحليل استجابة JSON-RPC واحدة وإرجاع `result`/طرح عند `error`.\n- مسار استجابة SSE (`Content-Type: text/event-stream`): بث الأحداث، إرجاع أول رسالة يتطابق `id` الخاص بها مع معرّف الطلب المتوقع وتحتوي على `result` أو `error`.\n- رسائل SSE التي تحمل `method` بدون `id` تُعامل كإشعارات.\n\nإذا انتهى بث SSE قبل الاستجابة المطابقة، يفشل الطلب برسالة `No response received for request ID ...`.\n\n## الإشعارات\n\nالعميل يُرسل إشعارات JSON-RPC عبر `transport.notify(...)`.\n\n- Stdio: يكتب إطار الإشعار إلى stdin (`jsonrpc`، `method`، `params` اختيارية) مع سطر جديد.\n- HTTP: يرسل جسم POST بدون `id`؛ النجاح يقبل `2xx` أو `202 Accepted`.\n\nالإشعارات المُبادرة من الخادم تُعرض فقط عبر `onNotification` لطبقة النقل؛ لا يوجد مشترك عام افتراضي في المدير/العميل.\n\n## آليات طبقة نقل Stdio الداخلية\n\n## دورة الحياة وانتقالات الحالة\n\n- الحالة الأولية: `connected=false`، `process=null`، خريطة الانتظار فارغة\n- `connect()`:\n  - إنشاء عملية فرعية بالأمر/المعاملات/المتغيرات/المسار المُكوّن\n  - تعليم الاتصال\n  - بدء حلقة قراءة stdout (`readJsonl`)\n  - بدء حلقة stderr (قراءة/تجاهل؛ صامتة حالياً)\n- `close()`:\n  - تعليم قطع الاتصال\n  - رفض جميع الطلبات المعلقة (`Transport closed`)\n  - إنهاء العملية الفرعية\n  - انتظار إيقاف حلقة القراءة\n  - إطلاق `onClose`\n\nإذا خرجت حلقة القراءة بشكل غير متوقع، يُفعّل `finally` دالة `#handleClose()` التي تؤدي نفس رفض الطلبات المعلقة ودالة رد الإغلاق.\n\n## المهلة الزمنية والإلغاء\n\nلكل طلب:\n\n- المهلة الافتراضية `config.timeout ?? 30000`\n- `AbortSignal` اختياري من المُستدعي\n- كل من الإلغاء والمهلة يرفضان الوعد المعلق وينظفان إدخال الخريطة\n\nالإلغاء محلي فقط: طبقة النقل لا ترسل إشعار إلغاء على مستوى البروتوكول إلى الخادم.\n\n## معالجة الحمولات غير الصالحة\n\nفي حلقة القراءة:\n\n- كل سطر JSONL محلل يُمرر إلى `#handleMessage` في `try/catch`\n- استثناءات معالجة الرسائل غير الصالحة/غير الصحيحة تُسقط (تعليق `Skip malformed lines`)\n- الحلقة تستمر، لذا رسالة سيئة واحدة لا تقتل الاتصال\n\nإذا طرح محلل التدفق الأساسي استثناءً، يُستدعى `onError` (عندما يكون الاتصال قائماً)، ثم يُغلق الاتصال.\n\n## سلوك قطع الاتصال/الفشل\n\nعند خروج العملية أو إغلاق التدفق:\n\n- جميع الطلبات قيد التنفيذ تُرفض برسالة `Transport closed`\n- لا إعادة تشغيل أو إعادة اتصال تلقائية\n- الطبقات الأعلى يجب أن تعيد الاتصال بإنشاء طبقة نقل جديدة\n\n## ملاحظات حول الضغط العكسي/البث\n\n- الكتابة الصادرة تستخدم `stdin.write()` + `flush()` بدون انتظار دلالات التفريغ.\n- لا توجد إدارة طابور صريحة أو علامة مائية عالية في طبقة النقل.\n- المعالجة الواردة مدفوعة بالتدفق (`for await` عبر `readJsonl`)، رسالة محللة واحدة في كل مرة.\n\n## آليات طبقة نقل HTTP/SSE الداخلية\n\n## دورة الحياة ودلالات الاتصال\n\nطبقة نقل HTTP لها حالة اتصال منطقية، لكن مسار الطلب بدون حالة لكل استدعاء HTTP:\n\n- `connect()` تضبط `connected=true` (لا مصافحة مقبس/جلسة)\n- تتبع جلسة الخادم الاختياري عبر ترويسة `Mcp-Session-Id`\n- `close()` ترسل اختيارياً `DELETE` مع `Mcp-Session-Id`، تلغي مستمع SSE، تطلق `onClose`\n\nلذا `connected` تعني \"طبقة النقل قابلة للاستخدام\"، وليس \"تدفق مستمر مُنشأ\".\n\n## سلوك ترويسة الجلسة\n\n- عند استجابة POST، إذا كانت ترويسة `Mcp-Session-Id` موجودة، تخزّنها طبقة النقل.\n- الطلبات/الإشعارات اللاحقة تتضمن `Mcp-Session-Id`.\n- `close()` تحاول إنهاء جلسة الخادم بـ HTTP DELETE؛ فشل الإنهاء يُتجاهل.\n\n## المهلة الزمنية والإلغاء\n\nلكل من `request()` و `notify()`:\n\n- المهلة تستخدم `AbortController` (`config.timeout ?? 30000`)\n- الإشارة الخارجية، إن وُجدت، تُدمج عبر `AbortSignal.any([...])`\n- معالجة AbortError تميّز بين إلغاء المُستدعي والمهلة\n\nالأخطاء المطروحة:\n\n- المهلة: `Request timeout after ...ms` (أو `SSE response timeout ...`، `Notify timeout ...`)\n- إلغاء المُستدعي: يُعاد طرح AbortError الأصلي عندما تكون الإشارة الخارجية ملغاة بالفعل\n\n## نشر أخطاء HTTP\n\nعند استجابة غير OK:\n\n- نص الاستجابة يُضمّن في الخطأ المطروح (`HTTP <status>: <text>`)\n- إن وُجدت، تلميحات المصادقة من `WWW-Authenticate` و `Mcp-Auth-Server` تُلحق\n\nعند كائن خطأ JSON-RPC:\n\n- يُطرح `MCP error <code>: <message>`\n\nفشل تحليل جسم JSON (`response.json()`) يُنشر كاستثناء تحليل.\n\n## سلوك SSE وأنماطه\n\nيوجد مساران لـ SSE:\n\n1. **استجابة SSE لكل طلب** (`#parseSSEResponse`)\n   - تُستخدم عندما يكون نوع محتوى استجابة POST هو `text/event-stream`\n   - تستهلك التدفق حتى العثور على معرّف الاستجابة المطابق\n   - يمكنها معالجة الإشعارات المتداخلة أثناء نفس التدفق\n\n2. **مستمع SSE في الخلفية** (`startSSEListener()`)\n   - مستمع GET اختياري للإشعارات المُبادرة من الخادم\n   - لا يُبدأ تلقائياً بواسطة مدير/عميل MCP حالياً\n   - إذا أرجع GET رمز `405`، يُعطّل المستمع نفسه بصمت (الخادم لا يدعم هذا النمط)\n\n## معالجة الحمولات غير الصالحة وقطع الاتصال\n\nأخطاء تحليل JSON لـ SSE تتصاعد من `readSseJson` وترفض الطلب/المستمع.\n\n- أخطاء تحليل SSE للطلب ترفض الطلب النشط.\n- أخطاء المستمع في الخلفية تُفعّل `onError` (باستثناء AbortError).\n- لا إعادة اتصال تلقائية للمستمع في الخلفية.\n\n## أداة `json-rpc.ts` مقابل تجريد طبقة النقل\n\n`src/mcp/json-rpc.ts` يوفر مساعدات `callMCP()` و `parseSSE()` لاستدعاءات MCP HTTP المباشرة (تُستخدم بواسطة تكامل Exa)، وليس تجريد `MCPTransport` المُستخدم بواسطة `MCPClient`/`MCPManager`.\n\nالاختلافات الملحوظة عن `HttpTransport`:\n\n- يحلل نص الاستجابة بالكامل أولاً، ثم يستخرج أول سطر `data:` (`parseSSE`)، مع احتياطي JSON\n- لا إدارة مهلة للطلب، لا واجهة إلغاء، لا معالجة معرّف الجلسة، لا دورة حياة لطبقة النقل\n- يُرجع كائن مظروف JSON-RPC الخام\n\nهذا المسار خفيف لكنه أقل متانة من تنفيذ طبقة النقل الكامل.\n\n## مسؤوليات إعادة المحاولة/إعادة الاتصال\n\n## مستوى طبقة النقل\n\nتنفيذات طبقة النقل الحالية **لا** تقوم بـ:\n\n- إعادة محاولة الطلبات الفاشلة\n- إعادة الاتصال بعد خروج عملية stdio\n- إعادة اتصال مستمعي SSE\n- إعادة إرسال الطلبات قيد التنفيذ بعد قطع الاتصال\n\nتفشل بسرعة وتنشر الأخطاء.\n\n## مستوى المدير/العميل\n\n`MCPManager` يتعامل مع اكتشاف/تنسيق الاتصال الأولي ويمكنه إعادة الاتصال فقط بتشغيل تدفقات الاتصال مرة أخرى (مسارات `connectToServer`/`discoverAndConnect`). لا يعالج تلقائياً طبقة نقل متصلة بالفعل عند دوال رد فشل وقت التشغيل.\n\n`MCPManager` لديه سلوك احتياطي عند بدء التشغيل للخوادم البطيئة (أدوات مؤجلة من ذاكرة التخزين المؤقت)، لكن ذلك احتياطي لتوفر الأدوات، وليس إعادة محاولة لطبقة النقل.\n\n## ملخص سيناريوهات الفشل\n\n- **سطر رسالة stdio غير صالح**: يُسقط؛ التدفق يستمر.\n- **انتهاء تدفق/عملية Stdio**: طبقة النقل تُغلق؛ الطلبات المعلقة تُرفض كـ `Transport closed`.\n- **HTTP غير 2xx**: الطلب/الإشعار يطرح خطأ HTTP.\n- **استجابة JSON غير صالحة**: استثناء التحليل يُنشر.\n- **انتهاء SSE بدون معرّف مطابق**: الطلب يفشل برسالة `No response received for request ID ...`.\n- **المهلة الزمنية**: خطأ مهلة خاص بطبقة النقل.\n- **إلغاء المُستدعي**: AbortError/السبب يُنشر من إشارة المُستدعي.\n\n## قاعدة الحدود العملية\n\nإذا كان الاهتمام يتعلق بشكل الرسالة، أو ربط المعرّفات، أو ترتيب أساليب MCP، فإنه ينتمي إلى منطق البروتوكول/العميل.\n\nإذا كان الاهتمام يتعلق بالتأطير (JSONL مقابل HTTP/SSE)، أو تحليل التدفق، أو دورة حياة fetch/spawn، أو ساعات المهلة، أو تفكيك الاتصال، فإنه ينتمي إلى تنفيذ طبقة النقل.\n",
	"ar/mcp/mcp-runtime-lifecycle.md": "---\ntitle: دورة حياة MCP أثناء التشغيل\ndescription: >-\n  MCP server process lifecycle from initialization through tool registration,\n  health monitoring, and shutdown.\nsidebar:\n  order: 3\n  label: دورة حياة التشغيل\ni18n:\n  sourceHash: d04cefaf38f8\n  translator: machine\n---\n\n# دورة حياة MCP أثناء التشغيل\n\nيصف هذا المستند كيفية اكتشاف خوادم MCP والاتصال بها وعرضها كأدوات وتحديثها وإيقافها في بيئة تشغيل coding-agent.\n\n## نظرة عامة على دورة الحياة\n\n1. **بدء تشغيل SDK** يستدعي `discoverAndLoadMCPTools()` (ما لم يكن MCP معطلاً).\n2. **الاكتشاف** (`loadAllMCPConfigs`) يحل تكوينات خوادم MCP من مصادر القدرات، ويرشح الإدخالات المعطلة/الخاصة بالمشروع/Exa، ويحتفظ ببيانات المصدر الوصفية.\n3. **مرحلة اتصال المدير** (`MCPManager.connectServers`) تبدأ الاتصال لكل خادم + `tools/list` بشكل متوازٍ.\n4. **بوابة البدء السريع** تنتظر حتى 250 مللي ثانية، ثم قد تُرجع:\n   - أدوات `MCPTool` محملة بالكامل،\n   - حالات فشل لكل خادم،\n   - أو أدوات `DeferredMCPTool` مخزنة مؤقتاً للخوادم التي لا تزال معلقة.\n5. **ربط SDK** يدمج أدوات MCP في سجل أدوات التشغيل للجلسة.\n6. **الجلسة النشطة** يمكنها تحديث أدوات MCP عبر تدفقات `/mcp` (`disconnectAll` + إعادة اكتشاف + `session.refreshMCPTools`).\n7. **الإيقاف** يحدث عندما يستدعي المُستدعون `disconnectServer`/`disconnectAll`؛ يقوم المدير أيضاً بمسح تسجيلات أدوات MCP للخوادم المنفصلة.\n\n## مرحلة الاكتشاف والتحميل\n\n### مسار الدخول من SDK\n\nتقوم `createAgentSession()` في `src/sdk.ts` ببدء تشغيل MCP عندما تكون `enableMCP` قيمتها true (الافتراضي):\n\n- تستدعي `discoverAndLoadMCPTools(cwd, { ... })`،\n- تمرر `authStorage` وتخزين الذاكرة المؤقتة وإعداد `mcp.enableProjectConfig`،\n- تضبط دائماً `filterExa: true`،\n- تسجل أخطاء التحميل/الاتصال لكل خادم،\n- تخزن المدير المُرجع في `toolSession.mcpManager` ونتيجة الجلسة.\n\nإذا كانت `enableMCP` قيمتها false، يتم تخطي اكتشاف MCP بالكامل.\n\n### اكتشاف التكوين والترشيح\n\nتقوم `loadAllMCPConfigs()` (`src/mcp/config.ts`) بتحميل عناصر خادم MCP القياسية من خلال اكتشاف القدرات، ثم تحولها إلى `MCPServerConfig` القديم.\n\nسلوك الترشيح:\n\n- `enableProjectConfig: false` يزيل الإدخالات على مستوى المشروع (`_source.level === \"project\"`).\n- الخوادم ذات `enabled: false` يتم تخطيها قبل محاولات الاتصال.\n- خوادم Exa تُرشح بشكل افتراضي ويتم استخراج مفاتيح API لتكامل أداة Exa الأصلية.\n\nتتضمن النتيجة كلاً من `configs` و`sources` (بيانات وصفية تُستخدم لاحقاً لتسمية المزود).\n\n### سلوك الفشل على مستوى الاكتشاف\n\nتميز `discoverAndLoadMCPTools()` بين فئتين من الفشل:\n\n- **فشل صعب في الاكتشاف** (استثناء من `manager.discoverAndConnect`، عادةً من اكتشاف التكوين): يُرجع مجموعة أدوات فارغة وخطأ اصطناعي واحد `{ path: \".mcp.json\", error }`.\n- **فشل وقت التشغيل/الاتصال لكل خادم**: يُرجع المدير نجاحاً جزئياً مع خريطة `errors`؛ تستمر الخوادم الأخرى.\n\nلذا لا يفشل بدء التشغيل جلسة الوكيل بأكملها عند فشل خوادم MCP فردية.\n\n## نموذج حالة المدير\n\nيتتبع `MCPManager` دورة حياة التشغيل بسجلات منفصلة:\n\n- `#connections: Map<string, MCPServerConnection>` — الخوادم المتصلة بالكامل.\n- `#pendingConnections: Map<string, Promise<MCPServerConnection>>` — المصافحة قيد التقدم.\n- `#pendingToolLoads: Map<string, Promise<{ connection, serverTools }>>` — متصل لكن الأدوات لا تزال قيد التحميل.\n- `#tools: CustomTool[]` — عرض أدوات MCP الحالي المعروض للمُستدعين.\n- `#sources: Map<string, SourceMeta>` — بيانات المزود/المصدر الوصفية حتى قبل اكتمال الاتصال.\n\nتستمد `getConnectionStatus(name)` الحالة من هذه الخرائط:\n\n- `connected` إذا كان في `#connections`،\n- `connecting` إذا كان في انتظار الاتصال أو انتظار تحميل الأدوات،\n- `disconnected` بخلاف ذلك.\n\n## إنشاء الاتصال وتوقيت البدء\n\n## خط أنابيب الاتصال لكل خادم\n\nلكل خادم مُكتشف في `connectServers()`:\n\n1. تخزين/تحديث بيانات المصدر الوصفية،\n2. التخطي إذا كان متصلاً/معلقاً بالفعل،\n3. التحقق من حقول النقل (`validateServerConfig`)،\n4. حل استبدالات المصادقة/الصَدَفة (`#resolveAuthConfig`)،\n5. استدعاء `connectToServer(name, resolvedConfig)`،\n6. استدعاء `listTools(connection)`،\n7. تخزين تعريفات الأدوات مؤقتاً (`MCPToolCache.set`) بأفضل جهد.\n\nسلوك `connectToServer()` (`src/mcp/client.ts`):\n\n- ينشئ نقل stdio أو HTTP/SSE،\n- ينفذ `initialize` + `notifications/initialized` لـ MCP،\n- يستخدم مهلة زمنية (`config.timeout` أو 30 ثانية افتراضياً)،\n- يغلق النقل عند فشل التهيئة.\n\n### بوابة البدء السريع + الرجوع المؤجل\n\nتنتظر `connectServers()` على سباق بين:\n\n- تسوية جميع مهام الاتصال/تحميل الأدوات، و\n- `STARTUP_TIMEOUT_MS = 250`.\n\nبعد 250 مللي ثانية:\n\n- المهام المكتملة تصبح أدوات `MCPTool` نشطة،\n- المهام المرفوضة تنتج أخطاء لكل خادم،\n- المهام المعلقة:\n  - تستخدم تعريفات الأدوات المخزنة مؤقتاً إن توفرت (`MCPToolCache.get`) لإنشاء `DeferredMCPTool`،\n  - وإلا تنتظر حتى تتم تسوية تلك المهام المعلقة.\n\nهذا نموذج بدء تشغيل هجين: إرجاع سريع عندما تتوفر الذاكرة المؤقتة، وانتظار صحيح عندما لا تتوفر.\n\n### سلوك الإكمال في الخلفية\n\nكل `toolsPromise` معلقة لها أيضاً استمرار في الخلفية يقوم في النهاية بـ:\n\n- استبدال شريحة أدوات ذلك الخادم في حالة المدير عبر `#replaceServerTools`،\n- كتابة الذاكرة المؤقتة،\n- تسجيل حالات الفشل المتأخرة فقط بعد بدء التشغيل (`allowBackgroundLogging`).\n\n## عرض الأدوات وتوفرها أثناء الجلسة النشطة\n\n### التسجيل عند البدء\n\nتحول `discoverAndLoadMCPTools()` أدوات المدير إلى `LoadedCustomTool[]` وتزين المسارات (`mcp:<server> via <providerName>` عند معرفته).\n\nثم تدفع `createAgentSession()` هذه الأدوات إلى `customTools`، التي تُغلف وتُضاف إلى سجل أدوات التشغيل بأسماء مثل `mcp_<server>_<tool>`.\n\n### استدعاءات الأدوات\n\n- `MCPTool` تستدعي الأدوات من خلال `MCPServerConnection` متصل بالفعل.\n- `DeferredMCPTool` تنتظر `waitForConnection(server)` قبل الاستدعاء؛ هذا يسمح للأدوات المخزنة مؤقتاً بالوجود قبل أن يكون الاتصال جاهزاً.\n\nكلاهما يُرجع مخرجات أدوات منظمة ويحول أخطاء النقل/الأدوات إلى محتوى أداة `MCP error: ...` (الإحباط يبقى إحباطاً).\n\n## مسارات التحديث/إعادة التحميل (البدء مقابل إعادة التحميل المباشر)\n\n### مسار البدء الأولي\n\n- اكتشاف/تحميل لمرة واحدة في `sdk.ts`،\n- يتم تسجيل الأدوات في سجل أدوات الجلسة الأولي.\n\n### مسار إعادة التحميل التفاعلي\n\nمسار `/mcp reload` (`src/modes/controllers/mcp-command-controller.ts`) يقوم بـ:\n\n1. `mcpManager.disconnectAll()`،\n2. `mcpManager.discoverAndConnect()`،\n3. `session.refreshMCPTools(mcpManager.getTools())`.\n\nتقوم `session.refreshMCPTools()` (`src/session/agent-session.ts`) بإزالة جميع أدوات `mcp_`، وإعادة تغليف أحدث أدوات MCP، وإعادة تفعيل مجموعة الأدوات بحيث تُطبق تغييرات MCP دون إعادة تشغيل الجلسة.\n\nيوجد أيضاً مسار متابعة للاتصالات المتأخرة: بعد انتظار خادم محدد، إذا أصبحت الحالة `connected`، يُعاد تشغيل `session.refreshMCPTools(...)` بحيث يتم إعادة ربط الأدوات المتاحة حديثاً في الجلسة.\n\n## الصحة وإعادة الاتصال وسلوك الفشل الجزئي\n\nسلوك التشغيل الحالي بسيط عمداً:\n\n- **لا يوجد مراقب صحة مستقل** في المدير/العميل.\n- **لا توجد حلقة إعادة اتصال تلقائية** عند انقطاع النقل.\n- المدير لا يشترك في أحداث `onClose`/`onError` للنقل؛ الحالة مدفوعة بالسجل.\n- إعادة الاتصال صريحة: تدفق إعادة التحميل أو استدعاء `connectServers()` المباشر.\n\nمن الناحية التشغيلية:\n\n- فشل خادم واحد لا يزيل الأدوات من الخوادم السليمة،\n- حالات فشل الاتصال/القائمة معزولة لكل خادم،\n- ذاكرة الأدوات المؤقتة والتحديثات في الخلفية تتم بأفضل جهد (يتم تسجيل التحذيرات/الأخطاء، بدون توقف صعب).\n\n## دلالات الإيقاف\n\n### إيقاف على مستوى الخادم\n\n`disconnectServer(name)`:\n\n- يزيل الإدخالات المعلقة/بيانات المصدر الوصفية،\n- يغلق النقل إذا كان متصلاً،\n- يزيل أدوات `mcp_` الخاصة بذلك الخادم من حالة المدير.\n\n### الإيقاف الشامل\n\n`disconnectAll()`:\n\n- يغلق جميع وسائل النقل النشطة باستخدام `Promise.allSettled`،\n- يمسح الخرائط المعلقة والمصادر والاتصالات وقائمة أدوات المدير.\n\nفي الربط الحالي، يُستخدم الإيقاف الصريح في تدفقات أوامر MCP (لإعادة التحميل/الإزالة/التعطيل). لا يوجد خطاف تخلص تلقائي منفصل للمدير في مسار البدء نفسه؛ المُستدعون مسؤولون عن استدعاء طرق فصل المدير عندما يحتاجون إلى إيقاف MCP محدد.\n\n## أوضاع الفشل والضمانات\n\n| السيناريو | السلوك | فشل صعب مقابل أفضل جهد |\n| --- | --- | --- |\n| فشل الاكتشاف (مسار تحميل القدرات/التكوين) | يُرجع المُحمّل أدوات فارغة + خطأ `.mcp.json` اصطناعي | بدء تشغيل الجلسة بأفضل جهد |\n| تكوين خادم غير صالح | يتم تخطي الخادم مع إدخال خطأ تحقق | أفضل جهد لكل خادم |\n| مهلة اتصال/فشل تهيئة | يتم تسجيل خطأ الخادم؛ تستمر الخوادم الأخرى | أفضل جهد لكل خادم |\n| `tools/list` لا يزال معلقاً عند البدء مع وجود ذاكرة مؤقتة | يتم إرجاع أدوات مؤجلة فوراً | بدء تشغيل سريع بأفضل جهد |\n| `tools/list` لا يزال معلقاً عند البدء بدون ذاكرة مؤقتة | ينتظر البدء حتى تتم تسوية المعلقات | انتظار صعب للصحة |\n| فشل تحميل أدوات متأخر في الخلفية | يتم التسجيل بعد بوابة البدء | تسجيل بأفضل جهد |\n| انقطاع النقل أثناء التشغيل | لا إعادة اتصال تلقائية؛ تفشل الاستدعاءات المستقبلية حتى إعادة الاتصال/التحميل | استعادة بأفضل جهد عبر إجراء يدوي |\n\n## واجهة API العامة\n\nيُعيد `src/mcp/index.ts` تصدير واجهات المُحمّل/المدير/العميل للمُستدعين الخارجيين. يعرض `src/sdk.ts` دالة `discoverMCPServers()` كغلاف ملائم يُرجع نفس شكل نتيجة المُحمّل.\n\n## ملفات التنفيذ\n\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts) — واجهة المُحمّل، تطبيع أخطاء الاكتشاف، تحويل `LoadedCustomTool`.\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts) — سجلات حالة دورة الحياة، تدفق الاتصال/القائمة المتوازي، التحديث/الفصل.\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts) — إعداد النقل، مصافحة التهيئة، القائمة/الاستدعاء/الفصل.\n- [`src/mcp/index.ts`](../../packages/coding-agent/src/mcp/index.ts) — صادرات واجهة وحدة MCP.\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — ربط البدء في سجل الجلسة/الأدوات.\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts) — اكتشاف/ترشيح/تحقق التكوين المُستخدم من المدير.\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts) — سلوك `MCPTool` و`DeferredMCPTool` أثناء التشغيل.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — إعادة ربط `refreshMCPTools` المباشرة.\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts) — تدفقات إعادة التحميل/إعادة الاتصال التفاعلية.\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — توكيل MCP للوكيل الفرعي عبر اتصالات المدير الأب.\n",
	"ar/mcp/mcp-server-tool-authoring.md": "---\ntitle: تأليف خادم وأدوات MCP\ndescription: دليل لبناء خوادم MCP مخصصة وتسجيل الأدوات لوكيل البرمجة.\nsidebar:\n  order: 4\n  label: تأليف الخادم والأدوات\ni18n:\n  sourceHash: 160e7560ef1f\n  translator: machine\n---\n\n# تأليف خادم وأدوات MCP\n\nيشرح هذا المستند كيف تتحول تعريفات خادم MCP إلى أدوات `mcp_*` قابلة للاستدعاء في coding-agent، وما يجب أن يتوقعه المشغلون عندما تكون التكوينات غير صالحة أو مكررة أو معطلة أو محمية بالمصادقة.\n\n## نظرة عامة على البنية\n\n```text\nConfig sources (.xcsh/.claude/.cursor/.vscode/mcp.json, mcp.json, etc.)\n  -> discovery providers normalize to canonical MCPServer\n  -> capability loader dedupes by server name (higher provider priority wins)\n  -> loadAllMCPConfigs converts to MCPServerConfig + skips enabled:false\n  -> MCPManager connects/listTools (with auth/header/env resolution)\n  -> MCPTool/DeferredMCPTool bridge exposes tools as mcp_<server>_<tool>\n  -> AgentSession.refreshMCPTools replaces live MCP tools immediately\n```\n\n## 1) نموذج تكوين الخادم والتحقق من الصحة\n\nيُعرّف `src/mcp/types.ts` الشكل المستخدم من قبل كتّاب تكوين MCP ووقت التشغيل:\n\n- `stdio` (الافتراضي عند غياب `type`): يتطلب `command`، اختياري `args`، `env`، `cwd`\n- `http`: يتطلب `url`، اختياري `headers`\n- `sse`: يتطلب `url`، اختياري `headers` (يُحتفظ به للتوافقية)\n- الحقول المشتركة: `enabled`، `timeout`، `auth`\n\nتفرض `validateServerConfig()` (`src/mcp/config.ts`) أساسيات النقل:\n\n- ترفض التكوينات التي تحدد كلاً من `command` و `url`\n- تتطلب `command` لـ stdio\n- تتطلب `url` لـ http/sse\n- ترفض `type` غير المعروف\n\nيطبق `config-writer.ts` هذا التحقق لعمليات الإضافة/التحديث ويتحقق أيضاً من أسماء الخوادم:\n\n- غير فارغ\n- حد أقصى 100 حرف\n- فقط `[a-zA-Z0-9_.-]`\n\n### مخاطر النقل\n\n- حذف `type` يعني stdio. إذا كنت تقصد HTTP/SSE لكن حذفت `type`، يصبح `command` إلزامياً.\n- لا يزال `sse` مقبولاً لكنه يُعامل كنقل HTTP داخلياً (`createHttpTransport`).\n- التحقق هيكلي وليس للوصولية: عنوان URL صالح نحوياً يمكن أن يفشل عند الاتصال.\n\n## 2) الاكتشاف والتطبيع والأسبقية\n\n### الاكتشاف القائم على القدرات\n\nتحمّل `loadAllMCPConfigs()` (`src/mcp/config.ts`) عناصر `MCPServer` الأساسية عبر `loadCapability(mcpCapability.id)`.\n\nثم تقوم طبقة القدرات (`src/capability/index.ts`) بـ:\n\n1. تحميل الموفرين حسب ترتيب الأولوية\n2. إزالة التكرارات حسب `server.name` (الفوز الأول = الأولوية الأعلى)\n3. التحقق من العناصر بعد إزالة التكرار\n\nالنتيجة: أسماء الخوادم المكررة عبر المصادر لا يتم دمجها. تعريف واحد يفوز؛ والتكرارات ذات الأولوية الأدنى تُظلَّل.\n\n### `.mcp.json` والملفات ذات الصلة\n\nيقرأ الموفر الاحتياطي المخصص في `src/discovery/mcp-json.ts` ملفات `mcp.json` و `.mcp.json` في جذر المشروع (أولوية منخفضة).\n\nعملياً تأتي خوادم MCP أيضاً من موفرين ذوي أولوية أعلى (على سبيل المثال `.xcsh/...` الأصلية ومجلدات التكوين الخاصة بالأدوات). إرشادات التأليف:\n\n- فضّل `.xcsh/mcp.json` (المشروع) أو `~/.xcsh/mcp.json` (المستخدم) للتحكم الصريح.\n- استخدم `mcp.json` / `.mcp.json` في الجذر عندما تحتاج توافقية احتياطية.\n- إعادة استخدام نفس اسم الخادم في مصادر متعددة يسبب تظليل الأسبقية وليس الدمج.\n\n### سلوك التطبيع\n\nيربط `convertToLegacyConfig()` (`src/mcp/config.ts`) خادم `MCPServer` الأساسي إلى `MCPServerConfig` لوقت التشغيل.\n\nالسلوك الرئيسي:\n\n- يُستنتج النقل كـ `server.transport ?? (command ? \"stdio\" : url ? \"http\" : \"stdio\")`\n- الخوادم المعطلة (`enabled === false`) تُسقط قبل الاتصال\n- الحقول الاختيارية تُحفظ عند وجودها\n\n### توسيع متغيرات البيئة أثناء الاكتشاف\n\nيوسّع `mcp-json.ts` العناصر النائبة للمتغيرات البيئية في حقول النصوص باستخدام `expandEnvVarsDeep()`:\n\n- يدعم `${VAR}` و `${VAR:-default}`\n- القيم غير المحلولة تبقى كنصوص حرفية `${VAR}`\n\nيقوم `mcp-json.ts` أيضاً بفحوصات نوع وقت التشغيل لـ JSON المستخدم ويسجل تحذيرات لقيم `enabled`/`timeout` غير الصالحة بدلاً من فشل الملف بأكمله.\n\n## 3) المصادقة وحل قيم وقت التشغيل\n\n`MCPManager.prepareConfig()`/`#resolveAuthConfig()` (`src/mcp/manager.ts`) هي المرحلة النهائية قبل الاتصال.\n\n### حقن بيانات اعتماد OAuth\n\nإذا كان التكوين يحتوي على:\n\n```ts\nauth: { type: \"oauth\", credentialId: \"...\" }\n```\n\nوبيانات الاعتماد موجودة في مخزن المصادقة:\n\n- `http`/`sse`: يحقن ترويسة `Authorization: Bearer <access_token>`\n- `stdio`: يحقن متغير البيئة `OAUTH_ACCESS_TOKEN`\n\nإذا فشل البحث عن بيانات الاعتماد، يسجل المدير تحذيراً ويتابع مع مصادقة غير محلولة.\n\n### حل قيم الترويسات/متغيرات البيئة\n\nقبل الاتصال، يحل المدير كل قيمة ترويسة/متغير بيئة عبر `resolveConfigValue()` (`src/config/resolve-config-value.ts`):\n\n- القيمة التي تبدأ بـ `!` => تنفيذ أمر shell، استخدام stdout المقصوص (مخزن مؤقتاً)\n- خلاف ذلك، تُعامل القيمة كاسم متغير بيئة أولاً (`process.env[name]`)، مع الرجوع للقيمة الحرفية\n- قيم الأوامر/المتغيرات غير المحلولة تُحذف من خريطة الترويسات/المتغيرات النهائية\n\nتحذير تشغيلي: هذا يعني أن اسم أمر/مفتاح سري خاطئ يمكن أن يزيل بصمت إدخال تلك الترويسة/المتغير، مما ينتج عنه أخطاء 401/403 أو فشل في بدء الخادم في المراحل اللاحقة.\n\n## 4) جسر الأدوات: من MCP إلى أدوات قابلة للاستدعاء بواسطة الوكيل\n\nيحوّل `src/mcp/tool-bridge.ts` تعريفات أدوات MCP إلى `CustomTool`s.\n\n### التسمية ونطاق التعارض\n\nتُنشأ أسماء الأدوات كالتالي:\n\n```text\nmcp_<sanitized_server_name>_<sanitized_tool_name>\n```\n\nالقواعد:\n\n- تحويل للأحرف الصغيرة\n- الأحرف غير `[a-z_]` تصبح `_`\n- الشرطات السفلية المتكررة تُدمج\n- بادئة `<server>_` الزائدة في اسم الأداة تُزال مرة واحدة\n\nهذا يتجنب كثيراً من التعارضات، لكن ليس جميعها. يمكن لأسماء خام مختلفة أن تُطهَّر لنفس المعرف (مثلاً `my-server` و `my.server` كلاهما يُطهَّر بشكل مشابه)، وإدراج السجل يعتمد على مبدأ الكتابة الأخيرة تفوز.\n\n### تعيين المخطط\n\nيحتفظ `convertSchema()` بمخطط JSON الخاص بـ MCP كما هو تقريباً لكنه يُصلح مخططات الكائنات التي تفتقد `properties` بـ `{}` لتوافقية الموفر.\n\n### تعيين التنفيذ\n\n`MCPTool.execute()` / `DeferredMCPTool.execute()`:\n\n- يستدعي `tools/call` الخاص بـ MCP\n- يُسطّح محتوى MCP إلى نص قابل للعرض\n- يُرجع تفاصيل منظمة (`serverName`، `mcpToolName`، بيانات الموفر الوصفية)\n- يربط `isError` المُبلَّغ عنه من الخادم بنتيجة نصية `Error: ...`\n- يربط أخطاء النقل/وقت التشغيل المُلقاة بـ `MCP error: ...`\n- يحافظ على دلالات الإلغاء بترجمة AbortError إلى `ToolAbortError`\n\n## 5) دورة حياة المشغل: الإضافة/التعديل/الإزالة والتحديثات الحية\n\nيكشف الوضع التفاعلي `/mcp` في `src/modes/controllers/mcp-command-controller.ts`.\n\nالعمليات المدعومة:\n\n- `add` (معالج أو إضافة سريعة)\n- `remove` / `rm`\n- `enable` / `disable`\n- `test`\n- `reauth` / `unauth`\n- `reload`\n\nعمليات كتابة التكوين ذرية (`writeMCPConfigFile`: ملف مؤقت + إعادة تسمية).\n\nبعد التغييرات، يستدعي المتحكم `#reloadMCP()`:\n\n1. `mcpManager.disconnectAll()`\n2. `mcpManager.discoverAndConnect()`\n3. `session.refreshMCPTools(mcpManager.getTools())`\n\nيستبدل `refreshMCPTools()` جميع إدخالات السجل `mcp_` ويُعيد تفعيل أحدث مجموعة أدوات MCP فوراً، لذا تسري التغييرات دون إعادة تشغيل الجلسة.\n\n### اختلافات الأوضاع\n\n- **الوضع التفاعلي/TUI**: `/mcp` يوفر تجربة مستخدم داخل التطبيق (معالج، تدفق OAuth، نص حالة الاتصال، إعادة ربط فورية لوقت التشغيل).\n- **تكامل SDK/بدون واجهة**: `discoverAndLoadMCPTools()` (`src/mcp/loader.ts`) يُرجع الأدوات المحملة + أخطاء لكل خادم؛ بدون تجربة مستخدم لأمر `/mcp`.\n\n## 6) أسطح الأخطاء المرئية للمستخدم\n\nسلاسل الأخطاء الشائعة التي يراها المستخدمون/المشغلون:\n\n- فشل التحقق عند الإضافة/التحديث:\n  - `Invalid server config: ...`\n  - `Server \"<name>\" already exists in <path>`\n- مشاكل وسائط الإضافة السريعة:\n  - `Use either --url or -- <command...>, not both.`\n  - `--token requires --url (HTTP/SSE transport).`\n- فشل الاتصال/الاختبار:\n  - `Failed to connect to \"<name>\": <message>`\n  - نص مساعدة المهلة يقترح زيادة المهلة\n  - نص مساعدة المصادقة لـ `401/403`\n- تدفقات المصادقة/OAuth:\n  - `Authentication required ... OAuth endpoints could not be discovered`\n  - `OAuth flow timed out. Please try again.`\n  - `OAuth authentication failed: ...`\n- استخدام خادم معطل:\n  - `Server \"<name>\" is disabled. Run /mcp enable <name> first.`\n\nيُعالج JSON المصدر غير الصالح في الاكتشاف عموماً كتحذيرات/سجلات؛ مسارات config-writer تُلقي أخطاء صريحة.\n\n## 7) إرشادات عملية للتأليف\n\nلتأليف MCP قوي في قاعدة الكود هذه:\n\n1. حافظ على أسماء الخوادم فريدة عالمياً عبر جميع مصادر التكوين القادرة على MCP.\n2. فضّل الأسماء الأبجدية الرقمية/الشرطة السفلية لتجنب تعارضات الأسماء المُطهَّرة في أسماء أدوات `mcp_*` المُنشأة.\n3. استخدم `type` صريحاً لتجنب الافتراضيات العرضية لـ stdio.\n4. عامل `enabled: false` كإيقاف تام: الخادم يُحذف من مجموعة الاتصال لوقت التشغيل.\n5. لتكوينات OAuth، خزّن `credentialId` صالحاً؛ وإلا يتم تخطي حقن المصادقة.\n6. إذا كنت تستخدم حل الأسرار القائم على الأوامر (`!cmd`)، تحقق من أن مخرجات الأمر مستقرة وغير فارغة.\n\n## ملفات التنفيذ\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts)\n- [`src/mcp/config-writer.ts`](../../packages/coding-agent/src/mcp/config-writer.ts)\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts)\n- [`src/discovery/mcp-json.ts`](../../packages/coding-agent/src/discovery/mcp-json.ts)\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/config/resolve-config-value.ts`](../../packages/coding-agent/src/config/resolve-config-value.ts)\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts)\n",
	"ar/natives/natives-addon-loader-runtime.md": "---\ntitle: بيئة تشغيل محمّل الإضافات الأصلية\ndescription: >-\n  N-API addon loader runtime with platform detection, fallback strategies, and\n  module resolution.\nsidebar:\n  order: 3\n  label: محمّل الإضافات\ni18n:\n  sourceHash: 743ea3e32c7c\n  translator: machine\n---\n\n# بيئة تشغيل محمّل الإضافات الأصلية\n\nيتعمق هذا المستند في طبقة تحميل/التحقق من الإضافات في `@f5-sales-demo/pi-natives`: كيف يقرر `native.ts` أي ملف `.node` سيتم تحميله، ومتى يتم تشغيل استخراج الحمولة المضمّنة، وكيف يتم الإبلاغ عن إخفاقات بدء التشغيل.\n\n## ملفات التنفيذ\n\n- `packages/natives/src/native.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/package.json`\n\n## النطاق والمسؤولية\n\nمسؤوليات المحمّل/بيئة التشغيل محدودة عن قصد:\n\n- بناء قائمة مرشحة لأسماء ملفات الإضافات والمجلدات بناءً على المنصة ووحدة المعالجة المركزية.\n- تجسيد إضافة مضمّنة اختيارياً في مجلد ذاكرة مؤقتة مُصدَّر لكل مستخدم.\n- تجربة المرشحين بترتيب حتمي.\n- رفض الإضافات القديمة أو غير المتوافقة عبر `validateNative` قبل كشف الربطات.\n\nخارج النطاق هنا: سلوك grep/text/highlight الخاص بالوحدات.\n\n## مدخلات بيئة التشغيل والحالة المشتقة\n\nعند تهيئة الوحدة (`export const native = loadNative();`)، يحسب `native.ts` السياق الثابت:\n\n- **وسم المنصة**: ``${process.platform}-${process.arch}`` (على سبيل المثال `darwin-arm64`).\n- **إصدار الحزمة**: من `packages/natives/package.json` (حقل `version`).\n- **المجلدات الأساسية**:\n  - `nativeDir`: المجلد المحلي للحزمة `packages/natives/native`.\n  - `execDir`: المجلد الذي يحتوي على `process.execPath`.\n  - `versionedDir`: `<getNativesDir()>/<packageVersion>`.\n  - بديل `userDataDir`:\n    - ويندوز: `%LOCALAPPDATA%/xcsh` (أو `%USERPROFILE%/AppData/Local/xcsh`).\n    - غير ويندوز: `~/.local/bin`.\n- **وضع الملف التنفيذي المُجمَّع** (`isCompiledBinary`): يكون `true` إذا تحقق أي مما يلي:\n  - متغير البيئة `PI_COMPILED` مُعيَّن، أو\n  - `import.meta.url` يحتوي على علامات Bun المضمّنة (`$bunfs`، `~BUN`، `%7EBUN`).\n- **تجاوز المتغير**: `PI_NATIVE_VARIANT` (`modern`/`baseline` فقط؛ القيم غير الصالحة يتم تجاهلها).\n- **المتغير المُختار**: التجاوز الصريح، وإلا كشف AVX2 أثناء التشغيل على x64 (`modern` إذا كان AVX2 متاحاً، وإلا `baseline`).\n\n## دعم المنصات وتحليل الوسوم\n\n`SUPPORTED_PLATFORMS` محددة بـ:\n\n- `linux-x64`\n- `linux-arm64`\n- `darwin-x64`\n- `darwin-arm64`\n- `win32-x64`\n\nتفاصيل السلوك:\n\n- لا يتم رفض المنصات غير المدعومة مسبقاً.\n- يحاول المحمّل جميع المرشحين المحسوبين أولاً.\n- إذا لم يتم تحميل أي شيء، يُطلق خطأً صريحاً بعدم دعم المنصة مع سرد الوسوم المدعومة.\n\nيحافظ هذا على تشخيصات مفيدة لحالات الاقتراب مع الاستمرار في الفشل الحاسم للأهداف غير المدعومة فعلاً.\n\n## اختيار المتغير (`modern` / `baseline` / افتراضي)\n\n### سلوك x64\n\n1. إذا كان `PI_NATIVE_VARIANT` يساوي `modern` أو `baseline`، تفوز تلك القيمة.\n2. وإلا يتم كشف دعم AVX2:\n   - لينكس: فحص `/proc/cpuinfo` بحثاً عن `avx2`.\n   - ماك: استعلام `sysctl` (`machdep.cpu.leaf7_features`، بديل `machdep.cpu.features`).\n   - ويندوز: تشغيل PowerShell `[System.Runtime.Intrinsics.X86.Avx2]::IsSupported`.\n3. النتيجة:\n   - AVX2 متاح -> `modern`\n   - AVX2 غير متاح/غير قابل للكشف -> `baseline`\n\n### سلوك غير x64\n\n- لا يُستخدم أي متغير؛ يبقى المحمّل على اسم الملف الافتراضي (`pi_natives.<platform>-<arch>.node`).\n\n### بناء اسم الملف\n\nبافتراض `tag = <platform>-<arch>`:\n\n- غير x64 أو بدون متغير: `pi_natives.<tag>.node`\n- x64 + `modern`: محاولة بالترتيب\n  1. `pi_natives.<tag>-modern.node`\n  2. `pi_natives.<tag>-baseline.node` (بديل مقصود)\n- x64 + `baseline`: فقط `pi_natives.<tag>-baseline.node`\n\n`addonLabel` المستخدم في رسائل الخطأ النهائية هو إما `<tag>` أو `<tag> (<variant>)`.\n\n## بناء مسارات المرشحين وترتيب البدائل\n\nيبني `native.ts` مجموعات المرشحين قبل أي استدعاء `require(...)`.\n\n### مرشحو الإصدار\n\nمبنية من قائمة أسماء الملفات المحلولة حسب المتغير ويتم البحث فيها بهذا الترتيب:\n\n- **بيئة تشغيل غير مُجمَّعة**:\n  1. `<nativeDir>/<filename>`\n  2. `<execDir>/<filename>`\n\n- **بيئة تشغيل مُجمَّعة** (`PI_COMPILED` أو علامات Bun المضمّنة):\n  1. `<versionedDir>/<filename>`\n  2. `<userDataDir>/<filename>`\n  3. `<nativeDir>/<filename>`\n  4. `<execDir>/<filename>`\n\n`dedupedCandidates` يزيل التكرارات مع الحفاظ على ترتيب الظهور الأول.\n\n### التسلسل النهائي أثناء التشغيل\n\nعند وقت التحميل:\n\n1. يتم إدراج مرشح الاستخراج المضمّن الاختياري (إذا تم إنتاجه) في المقدمة.\n2. يتم تجربة المرشحين المتبقين بعد إزالة التكرار بالترتيب.\n3. أول مرشح ينجح في كل من `require(...)` ويجتاز `validateNative(...)` يفوز.\n\n## دورة حياة استخراج الإضافة المضمّنة\n\nيحدد `embedded-addon.ts` شكل بيان مُولَّد:\n\n- `platformTag`\n- `version`\n- `files[]` حيث يحتوي كل إدخال على `variant`، `filename`، `filePath`\n\nالقيمة الافتراضية المسجلة حالياً هي `embeddedAddon: null`؛ قد تستبدل القطع الأثرية المُجمَّعة هذه ببيانات وصفية حقيقية.\n\n### آلة حالة الاستخراج\n\nيعمل الاستخراج (`maybeExtractEmbeddedAddon`) فقط عندما تمر جميع البوابات:\n\n1. `isCompiledBinary === true`\n2. `embeddedAddon !== null`\n3. `embeddedAddon.platformTag === platformTag`\n4. `embeddedAddon.version === packageVersion`\n5. يتم العثور على ملف مضمّن مناسب للمتغير\n\nاختيار ملف المتغير يعكس غرض المتغير أثناء التشغيل:\n\n- غير x64: تفضيل `default`، ثم أول ملف متاح.\n- x64 + `modern`: تفضيل `modern`، بديل `baseline`.\n- x64 + `baseline`: يتطلب `baseline`.\n\nسلوك التجسيد:\n\n1. التأكد من وجود `<versionedDir>` (`mkdirSync(..., { recursive: true })`).\n2. إذا كان `<versionedDir>/<selected filename>` موجوداً بالفعل، يتم إعادة استخدامه (بدون إعادة كتابة).\n3. وإلا يتم قراءة مصدر `filePath` المضمّن وكتابة الملف المستهدف.\n4. إرجاع المسار المستهدف لمحاولة التحميل ذات الأولوية الأعلى.\n\nعند الفشل، لا يتعطل الاستخراج فوراً؛ بل يُلحق إدخال خطأ (فشل إنشاء المجلد أو الكتابة) ويتابع المحمّل فحص المرشحين العاديين.\n\n## دورة الحياة وانتقالات الحالة\n\n```text\nInit\n  -> Compute platform/version/variant/candidate lists\n  -> (Compiled + embedded manifest matches?)\n       yes -> Try extract embedded to versionedDir (record errors, continue)\n       no  -> Skip extraction\n  -> For each runtime candidate in order:\n       require(candidate)\n       -> success: validateNative\n            -> pass: return bindings (READY)\n            -> fail: record error, continue\n       -> failure: record error, continue\n  -> none loaded:\n       if unsupported platform tag -> throw Unsupported platform\n       else -> throw Failed to load (full tried-path diagnostics + hints)\n```\n\n## فحوصات عقد `validateNative`\n\nيفرض `validateNative(bindings, source)` عقداً قائماً على الدوال فقط على `NativeBindings` عند بدء التشغيل.\n\nالآلية:\n\n- لكل اسم تصدير مطلوب، يتحقق من `typeof bindings[name] === \"function\"`.\n- يتم تجميع الأسماء المفقودة.\n- إذا كان أي منها مفقوداً، يُطلق المحمّل خطأً يتضمن:\n  - مسار الإضافة المصدر،\n  - قائمة التصديرات المفقودة،\n  - تلميح أمر إعادة البناء.\n\nهذه بوابة توافق صارمة ضد الملفات الثنائية القديمة والبنى الجزئية وانحراف الرموز/الأسماء.\n\n### تعيين واجهة JS ↔ التصدير الأصلي (بوابة التحقق)\n\n| اسم ربط JS المُفحوص في `validateNative` | اسم التصدير الأصلي المتوقع |\n| --- | --- |\n| `grep` | `grep` |\n| `glob` | `glob` |\n| `highlightCode` | `highlightCode` |\n| `executeShell` | `executeShell` |\n| `PtySession` | `PtySession` |\n| `Shell` | `Shell` |\n| `visibleWidth` | `visibleWidth` |\n| `getSystemInfo` | `getSystemInfo` |\n| `getWorkProfile` | `getWorkProfile` |\n| `invalidateFsScanCache` | `invalidateFsScanCache` |\n\nملاحظة: يُصرّح `bindings.ts` فقط عن العضو الأساسي `cancelWork(id)`؛ ملفات `types.ts` الخاصة بالوحدات تدمج إعلانات رموز إضافية يفرضها `validateNative`.\n\n## سلوك الفشل والتشخيصات\n\n## منصة غير مدعومة\n\nإذا فشل جميع المرشحين ولم يكن `platformTag` ضمن `SUPPORTED_PLATFORMS`، يُطلق المحمّل:\n\n- `Unsupported platform: <tag>`\n- قائمة كاملة بالمنصات المدعومة\n- إرشادات صريحة للإبلاغ عن المشكلة\n\n## أعراض الملف الثنائي القديم / عدم التطابق\n\nإشارة عدم التطابق القديمة النموذجية:\n\n- `Native addon missing exports (<candidate>). Missing: ...`\n\nالأسباب الشائعة:\n\n- ملف `.node` ثنائي قديم من إصدار/شكل API سابق للحزمة.\n- اختيار قطعة أثرية بمتغير خاطئ (لـ x64).\n- تصدير Rust جديد غير موجود في القطعة الأثرية المحمّلة.\n\nسلوك المحمّل:\n\n- يسجل إخفاقات التصديرات المفقودة لكل مرشح.\n- يستمر في فحص المرشحين المتبقين.\n- إذا لم ينجح أي مرشح في التحقق، يتضمن الخطأ النهائي كل مسار تمت تجربته مع رسالة فشل كل منها.\n\n## إخفاقات بدء تشغيل الملف الثنائي المُجمَّع\n\nفي وضع التجميع تتضمن التشخيصات النهائية:\n\n- مسارات ذاكرة التخزين المؤقت المُصدَّرة المتوقعة (`<versionedDir>/<filename>`)،\n- معالجة لحذف `<versionedDir>` القديم وإعادة التشغيل،\n- أوامر `curl` لتنزيل الإصدار المباشر لكل اسم ملف متوقع.\n\n## إخفاقات بدء التشغيل غير المُجمَّعة\n\nفي وضع الحزمة/بيئة التشغيل العادية تتضمن التشخيصات النهائية:\n\n- تلميح إعادة التثبيت (`bun install @f5-sales-demo/pi-natives`)،\n- أمر إعادة البناء المحلي (`bun --cwd=packages/natives run build`)،\n- تلميح اختياري لبناء متغير x64 (`TARGET_VARIANT=baseline|modern ...`).\n\n## سلوك بيئة التشغيل\n\n- يستخدم المحمّل دائماً سلسلة مرشحي الإصدار.\n- تعيين `PI_DEV` يُفعّل فقط التشخيصات لكل مرشح على وحدة التحكم (`Loaded native addon...` وأخطاء التحميل).\n",
	"ar/natives/natives-architecture.md": "---\ntitle: معمارية Natives\ndescription: >-\n  معمارية الإضافة الأصلية Rust N-API التي تربط TypeScript بالعمليات الخاصة\n  بالمنصة.\nsidebar:\n  order: 1\n  label: المعمارية\ni18n:\n  sourceHash: d38ed2437bb7\n  translator: machine\n---\n\n# معمارية Natives\n\n`@f5-sales-demo/pi-natives` هي حزمة مكونة من ثلاث طبقات:\n\n1. **طبقة الغلاف/API بـ TypeScript** تُعرض نقاط دخول مستقرة بـ JS/TS.\n2. **طبقة تحميل/التحقق من الإضافة** تحل وتُتحقق من الملف الثنائي `.node` لوقت التشغيل الحالي.\n3. **طبقة وحدة Rust N-API** تُنفذ العناصر الأولية الحساسة للأداء المُصدَّرة إلى JS.\n\nهذا المستند هو الأساس لمستندات أعمق على مستوى الوحدات.\n\n## ملفات التنفيذ\n\n- `packages/natives/src/index.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `crates/pi-natives/src/lib.rs`\n\n## الطبقة الأولى: طبقة الغلاف/API بـ TypeScript\n\n`packages/natives/src/index.ts` هو الحاوية العامة. يُجمّع الصادرات حسب نطاق القدرات ويُعيد تصدير الأغلفة المُكتَّبة بدلاً من كشف ارتباطات N-API الخام مباشرةً.\n\nالمجموعات الرئيسية الحالية:\n\n- **عناصر البحث/النص الأولية**: `grep`، `glob`، `text`، `highlight`\n- **عناصر التنفيذ/العمليات/الطرفية الأولية**: `shell`، `pty`، `ps`، `keys`\n- **عناصر النظام/الوسائط/التحويل الأولية**: `image`، `html`، `clipboard`، `system-info`، `work`\n\nيُعرّف `packages/natives/src/bindings.ts` عقد الواجهة الأساسية:\n\n- تبدأ `NativeBindings` بأعضاء مشتركة (`cancelWork(id: number)`)\n- تُضاف ارتباطات خاصة بكل وحدة عن طريق دمج الإعلانات من ملف `types.ts` الخاص بكل وحدة\n- تُوحّد `Cancellable` خيارات المهلة الزمنية وإشارة الإلغاء للأغلفة التي تُعرض إمكانية الإلغاء\n\n**العقد المضمون (مواجهة API):** يستورد المستهلكون من `@f5-sales-demo/pi-natives` ويستخدمون الأغلفة المُكتَّبة.\n\n**تفاصيل التنفيذ (قابلة للتغيير):** دمج الإعلانات وتخطيط الغلاف الداخلي (`src/<module>/index.ts`، `src/<module>/types.ts`).\n\n## الطبقة الثانية: تحميل الإضافة والتحقق منها\n\nيمتلك `packages/natives/src/native.ts` اختيار الإضافة في وقت التشغيل، والاستخراج الاختياري، والتحقق من صحة الصادرات.\n\n### نموذج تحديد المرشحين\n\n- علامة المنصة هي `\"${process.platform}-${process.arch}\"`.\n- العلامات المدعومة حالياً هي:\n  - `linux-x64`\n  - `linux-arm64`\n  - `darwin-x64`\n  - `darwin-arm64`\n  - `win32-x64`\n- يمكن لـ x64 استخدام متغيرات المعالج:\n  - `modern` (يدعم AVX2)\n  - `baseline` (احتياطي)\n- تستخدم المعماريات غير x64 اسم الملف الافتراضي (بدون لاحقة متغير).\n\nاستراتيجية تسمية الملفات:\n\n- الإصدار: `pi_natives.<platform>-<arch>.node`\n- إصدار متغير x64: `pi_natives.<platform>-<arch>-modern.node` و/أو `...-baseline.node`\n- يُفعّل `PI_DEV` تشخيصات المُحمّل دون تغيير أسماء ملفات الإضافة\n\n### اكتشاف المتغير الخاص بالمنصة\n\nبالنسبة لـ x64، يستخدم اختيار المتغير:\n\n- **Linux**: `/proc/cpuinfo`\n- **macOS**: `sysctl machdep.cpu.leaf7_features` / `machdep.cpu.features`\n- **Windows**: فحص PowerShell لـ `System.Runtime.Intrinsics.X86.Avx2`\n\nيمكن لـ `PI_NATIVE_VARIANT` إجبار اختيار `modern` أو `baseline` صراحةً.\n\n### نموذج توزيع الملفات الثنائية واستخراجها\n\nيتضمن `packages/natives/package.json` كلاً من `src` و`native` في الملفات المنشورة. يخزّن دليل `native/` القطع الأثرية المُبنية مسبقاً للمنصات المختلفة.\n\nبالنسبة للملفات الثنائية المُجمَّعة (علامات `PI_COMPILED` أو Bun لوقت التشغيل المدمج)، يكون سلوك المُحمّل كالتالي:\n\n1. التحقق من مسار ذاكرة التخزين المؤقت للمستخدم ذات الإصدار: `<getNativesDir()>/<packageVersion>/...`\n2. التحقق من موقع الملف الثنائي المُجمَّع القديم:\n   - Windows: `%LOCALAPPDATA%/xcsh` (احتياطي `%USERPROFILE%/AppData/Local/xcsh`)\n   - غير Windows: `~/.local/bin`\n3. الرجوع إلى المرشحين من دليل `native/` المُعبَّأ ودليل الملف القابل للتنفيذ\n\nإذا كان بيان الإضافة المدمجة موجوداً (`embedded-addon.ts` المُنشأ بواسطة `scripts/embed-native.ts`)، يمكن لـ `native.ts` تجسيد الملف الثنائي المدمج المطابق في دليل ذاكرة التخزين المؤقت ذات الإصدار قبل التحميل.\n\n### التحقق وأوضاع الفشل\n\nبعد `require(candidate)`، يتحقق `validateNative(...)` من الصادرات المطلوبة (على سبيل المثال `grep`، `glob`، `highlightCode`، `PtySession`، `Shell`، `getSystemInfo`، `getWorkProfile`، `invalidateFsScanCache`).\n\nمسارات الفشل صريحة:\n\n- **علامة منصة غير مدعومة**: يرمي خطأً مع قائمة المنصات المدعومة\n- **لا يوجد مرشح قابل للتحميل**: يرمي خطأً مع جميع المسارات المُجرَّبة وتلميحات المعالجة\n- **صادرات مفقودة**: يرمي خطأً مع الأسماء المفقودة بالضبط وأمر إعادة البناء\n- **أخطاء استخراج الإضافة المدمجة**: يسجّل أخطاء الدليل/الكتابة ويُدرجها في تشخيصات التحميل النهائية\n\n**العقد المضمون (مواجهة API):** إما أن ينجح تحميل الإضافة بمجموعة ارتباطات مُتحقَّق منها، أو يفشل فوراً مع نص خطأ قابل للتنفيذ.\n\n**تفاصيل التنفيذ (قابلة للتغيير):** ترتيب بحث المرشحين الدقيق وترتيب مسار الاحتياطي للملف الثنائي المُجمَّع.\n\n## الطبقة الثالثة: طبقة وحدة Rust N-API\n\n`crates/pi-natives/src/lib.rs` هو وحدة Rust الرئيسية التي تُعلن عن ملكية الوحدة المُصدَّرة:\n\n- `clipboard`\n- `fd`\n- `fs_cache`\n- `glob`\n- `glob_util`\n- `grep`\n- `highlight`\n- `html`\n- `image`\n- `keys`\n- `prof`\n- `ps`\n- `pty`\n- `shell`\n- `system_info`\n- `task`\n- `text`\n\nتُنفّذ هذه الوحدات رموز N-API المُستهلَكة والمُتحقَّق منها بواسطة `native.ts`. تُعرض الأسماء على مستوى JS من خلال أغلفة TS في `packages/natives/src`.\n\n**العقد المضمون (مواجهة API):** يجب أن تتطابق صادرات وحدة Rust مع أسماء الارتباطات المُتوقَّعة بواسطة `validateNative` ووحدات الغلاف.\n\n**تفاصيل التنفيذ (قابلة للتغيير):** التحليل الداخلي لوحدة Rust وحدود وحدة المساعد (`glob_util`، `task`، إلخ).\n\n## حدود الملكية\n\nعلى مستوى المعمارية، تُقسَّم الملكية كالتالي:\n\n- **ملكية غلاف/API بـ TypeScript (`packages/natives/src`)**\n  - تجميع API العام، وكتابة الخيارات، وبيئة JS المستقرة\n  - سطح الإلغاء (`timeoutMs`، `AbortSignal`) المُعرَّض للمستدعين\n- **ملكية المُحمّل (`packages/natives/src/native.ts`)**\n  - اختيار الملف الثنائي في وقت التشغيل\n  - اختيار متغير المعالج ومعالجة التجاوز\n  - استخراج الملف الثنائي المُجمَّع وفحص المرشحين\n  - التحقق الصارم من صادرات الملف الأصلي المطلوبة\n- **ملكية Rust (`crates/pi-natives/src`)**\n  - التنفيذ الخوارزمي وعلى مستوى النظام\n  - السلوك الأصلي للمنصة والمنطق الحساس للأداء\n  - تنفيذ رموز N-API التي تستهلكها أغلفة TS\n\n## تدفق وقت التشغيل (عالي المستوى)\n\n1. يستورد المستهلك من `@f5-sales-demo/pi-natives`.\n2. تستدعي وحدة الغلاف ارتباط `native` أحادي النسخة.\n3. يختار `native.ts` الملف الثنائي المرشح للمنصة/المعمارية/المتغير.\n4. يحدث استخراج الملف الثنائي المدمج الاختياري للتوزيعات المُجمَّعة.\n5. يُحمَّل الإضافة ويُتحقَّق من مجموعة الصادرات.\n6. يُعيد الغلاف نتائج مُكتَّبة إلى المستدعي.\n\n## قاموس المصطلحات\n\n- **الإضافة الأصلية**: ملف ثنائي `.node` يُحمَّل عبر Node-API (N-API).\n- **علامة المنصة**: زوج وقت التشغيل `platform-arch` (على سبيل المثال `darwin-arm64`).\n- **المتغير**: نكهة بناء خاصة بمعالج x64 (`modern` بـ AVX2، `baseline` احتياطي).\n- **الغلاف**: دالة/فئة TS تُوفر API مُكتَّبة فوق الصادرات الأصلية الخام.\n- **دمج الإعلانات**: أسلوب TS تستخدمه ملفات `types.ts` الخاصة بالوحدات لتوسيع `NativeBindings`.\n- **وضع الملف الثنائي المُجمَّع**: وضع وقت التشغيل حيث تكون واجهة سطر الأوامر مُجمَّعة ويتم تحليل الإضافات الأصلية من مسارات الاستخراج/ذاكرة التخزين المؤقت بدلاً من المسارات المحلية للحزمة فقط.\n- **الإضافة المدمجة**: بيانات وصفية للقطعة الأثرية للبناء ومراجع الملفات المُنشأة في `embedded-addon.ts` حتى تتمكن الملفات الثنائية المُجمَّعة من استخراج حمولات `.node` المطابقة.\n- **بوابة التحقق**: فحص `validateNative(...)` الذي يرفض الملفات الثنائية القديمة/غير المتطابقة التي تفتقر إلى الصادرات المطلوبة.\n",
	"ar/natives/natives-binding-contract.md": "---\ntitle: عقد الربط الأصلي (جانب TypeScript)\ndescription: عقد الربط من جانب TypeScript لاستدعاء دوال Rust الأصلية عبر N-API.\nsidebar:\n  order: 2\n  label: عقد الربط\ni18n:\n  sourceHash: 36dc5fed1f0a\n  translator: machine\n---\n\n# عقد الربط الأصلي (جانب TypeScript)\n\nيُحدّد هذا المستند العقد من جانب TypeScript الذي يقع بين مستدعي `@f5-sales-demo/pi-natives` والإضافة N-API المحمّلة.\n\nيركّز على ثلاثة عناصر:\n\n1. شكل العقد (`NativeBindings` + توسيع الوحدة)،\n2. سلوك الغلاف (`src/<module>/index.ts`)،\n3. سطح التصدير العام (`src/index.ts`).\n\n## ملفات التنفيذ\n\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/system-info/types.ts`\n- `packages/natives/src/system-info/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/work/types.ts`\n- `packages/natives/src/work/index.ts`\n\n## نموذج العقد\n\nيُعرّف `packages/natives/src/bindings.ts` العقد الأساسي:\n\n- `NativeBindings` (الواجهة الأساسية، تتضمن حاليًا `cancelWork(id: number): void`)\n- `Cancellable` (`timeoutMs?: number`، `signal?: AbortSignal`)\n- شكل ردّ الاتصال `TsFunc<T>` المستخدم من قِبَل ردود الاتصال الآمنة للخيوط في N-API\n\nتُضيف كل وحدة حقولها الخاصة عن طريق دمج التصريحات:\n\n```ts\n// packages/natives/src/<module>/types.ts\ndeclare module \"../bindings\" {\n interface NativeBindings {\n  grep(options: GrepOptions, onMatch?: TsFunc<GrepMatch>): Promise<GrepResult>;\n }\n}\n```\n\nيُبقي هذا على واجهة ربط مجمّعة واحدة دون الحاجة إلى ملف نوع مركزي ضخم.\n\n## دورة حياة دمج التصريحات وانتقالات الحالة\n\n### 1) تجميع النوع في وقت الترجمة\n\n- يوفّر `bindings.ts` الرمز الأساسي `NativeBindings`.\n- يُوسّع كل `src/<module>/types.ts` واجهة `NativeBindings`.\n- يستورد `src/native.ts` جميع ملفات `./<module>/types` كتأثيرات جانبية لضمان وجود العقد المدموج في النطاق حيث يُستخدم `NativeBindings`.\n\nانتقال الحالة: **العقد الأساسي** → **العقد المدموج**.\n\n### 2) تحميل الإضافة في وقت التشغيل وبوابة التحقق\n\n- يُحمّل `src/native.ts` الملفات الثنائية المرشّحة ذات الامتداد `.node`.\n- يُعامَل الكائن المحمّل على أنه `NativeBindings` ويُمرَّر فورًا عبر `validateNative(...)`.\n- يتحقق `validateNative` من مفاتيح التصدير المطلوبة عبر `typeof bindings[name] === \"function\"`.\n\nانتقال الحالة: **كائن الإضافة غير الموثوق** → **كائن الربط الأصلي المُتحقَّق منه** (أو فشل غير قابل للاسترداد).\n\n### 3) استدعاء الغلاف\n\n- تستدعي أغلفة الوحدات في `src/<module>/index.ts` الـ `native.<export>`.\n- تُكيّف الأغلفة القيم الافتراضية وشكل ردّ الاتصال (من نمط `(err, value)` إلى أنماط رد الاتصال التي تعتمد على القيمة فقط في واجهات برمجة JS).\n- يُعيد تصدير `src/index.ts` أغلفة الوحدات وأنواعها كواجهة برمجة تطبيقات عامة للحزمة.\n\nانتقال الحالة: **روابط خام مُتحقَّق منها** → **واجهة برمجة تطبيقات عامة سهلة الاستخدام**.\n\n## مسؤوليات الغلاف\n\nالأغلفة رفيعة بصورة مقصودة؛ إذ لا تُعيد تنفيذ المنطق الأصلي.\n\nالمسؤوليات الأساسية:\n\n- **تطبيع الحجج وتعيين قيمها الافتراضية**\n  - يحلّ `glob()` `options.path` إلى مسار مطلق ويضع قيمًا افتراضية لـ `hidden` و`gitignore` و`recursive`.\n  - يملأ `hasMatch()` الأعلام الافتراضية (`ignoreCase`، `multiline`) قبل استدعاء الدالة الأصلية.\n- **تكييف ردّ الاتصال**\n  - تحوّل `grep()` و`glob()` و`executeShell()` الـ `TsFunc<T>` (`error, value`) إلى رد اتصال المستخدم الذي يستقبل القيم الناجحة فقط.\n- **سلوك البيئة أو السياسة حول الاستدعاءات الأصلية**\n  - يُضيف غلاف الحافظة معالجة OSC52/Termux/بدون واجهة رسومية، ويتعامل مع النسخ على أنه بذل أفضل جهد.\n- **التسمية العامة وتنسيق إعادة التصدير**\n  - تُعيّن `searchContent()` تعيينًا إلى التصدير الأصلي `search`.\n\n## تنظيم سطح التصدير العام\n\n`packages/natives/src/index.ts` هو البرميل العام الرئيسي. يُجمّع الصادرات حسب نطاق القدرة:\n\n- البحث/النص: `grep`، `glob`، `text`، `highlight`\n- التنفيذ/العملية/الطرفية: `shell`، `pty`، `ps`، `keys`\n- النظام/الوسائط/التحويل: `image`، `html`، `clipboard`، `system-info`، `work`\n\nقاعدة للمشرفين: إذا لم يُعَد تصدير غلاف ما من `src/index.ts`، فهو ليس جزءًا من سطح الحزمة العام المقصود.\n\n## تعيين واجهة برمجة JS ↔ تصدير أصلي (تمثيلي)\n\nتستخدم جانب Rust أسماء تصدير N-API (مشتقة عادةً من تحويل `#[napi]` من snake_case إلى camelCase، مع أسماء مستعارة صريحة أحيانًا) يجب أن تتطابق مع مفاتيح الربط هذه.\n\n| الفئة | واجهة برمجة JS العامة (الغلاف) | مفتاح الربط الأصلي | نوع الإرجاع | غير متزامن؟ |\n|---|---|---|---|---|\n| Grep | `grep(options, onMatch?)` | `grep` | `Promise<GrepResult>` | نعم |\n| Grep | `searchContent(content, options)` | `search` | `SearchResult` | لا |\n| Grep | `hasMatch(content, pattern, opts?)` | `hasMatch` | `boolean` | لا |\n| Grep | `fuzzyFind(options)` | `fuzzyFind` | `Promise<FuzzyFindResult>` | نعم |\n| Glob | `glob(options, onMatch?)` | `glob` | `Promise<GlobResult>` | نعم |\n| Glob | `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `void` | لا |\n| Shell | `executeShell(options, onChunk?)` | `executeShell` | `Promise<ShellExecuteResult>` | نعم |\n| Shell | `Shell` | `Shell` | مُنشئ الفئة | غير مُنطبق |\n| PTY | `PtySession` | `PtySession` | مُنشئ الفئة | غير مُنطبق |\n| Text | `truncateToWidth(...)` | `truncateToWidth` | `string` | لا |\n| Text | `sliceWithWidth(...)` | `sliceWithWidth` | `SliceWithWidthResult` | لا |\n| Text | `visibleWidth(text)` | `visibleWidth` | `number` | لا |\n| Highlight | `highlightCode(code, lang, colors)` | `highlightCode` | `string` | لا |\n| HTML | `htmlToMarkdown(html, options?)` | `htmlToMarkdown` | `Promise<string>` | نعم |\n| System | `getSystemInfo()` | `getSystemInfo` | `SystemInfo` | لا |\n| Work | `getWorkProfile(lastSeconds)` | `getWorkProfile` | `WorkProfile` | لا |\n| Process | `killTree(pid, signal)` | `killTree` | `number` | لا |\n| Process | `listDescendants(pid)` | `listDescendants` | `number[]` | لا |\n| Clipboard | `copyToClipboard(text)` | `copyToClipboard` | `Promise<void>` (سلوك الغلاف ببذل أفضل جهد) | نعم |\n| Clipboard | `readImageFromClipboard()` | `readImageFromClipboard` | `Promise<ClipboardImage \\| null>` | نعم |\n| Keys | `parseKey(data, kittyProtocolActive)` | `parseKey` | `string \\| null` | لا |\n\n## الفروقات بين عقود المزامنة وغير المزامنة\n\nيمزج العقد بين واجهات برمجة التطبيقات المتزامنة وغير المتزامنة؛ وتُبقي الأغلفة على أسلوب الاستدعاء الأصلي بدلًا من فرض نموذج واحد:\n\n- **صادرات غير متزامنة قائمة على الوعود** للعمليات التي تتضمن إدخالًا/إخراجًا أو مهامًا طويلة الأمد (`grep`، `glob`، `htmlToMarkdown`، `executeShell`، الحافظة، عمليات الصور).\n- **صادرات متزامنة** للتحويلات/المحلّلات الحتمية في الذاكرة (`search`، `hasMatch`، التمييز البرمجي، عرض النص/التقطيع، تحليل المفاتيح، استعلامات العمليات).\n- **صادرات المُنشئ** للكائنات التشغيلية ذات الحالة (`Shell`، `PtySession`، `PhotonImage`).\n\nانعكاس على المشرفين: تغيير نمط تصدير قائم من متزامن إلى غير متزامن أو العكس يُعدّ تغييرًا كسرًا في العقد والواجهة البرمجية عبر الأغلفة والمستدعين.\n\n## أنماط كتابة الكائنات والتعدادات\n\n### أنماط الكائنات (كائنات JS بنمط `#[napi(object)]`)\n\nيُمثّل TypeScript القيم الأصلية ذات شكل الكائن كواجهات، على سبيل المثال:\n\n- `GrepResult`، `SearchResult`، `GlobResult`\n- `SystemInfo`، `WorkProfile`\n- `ClipboardImage`، `ParsedKittyResult`\n\nهذه عقود هيكلية في وقت الترجمة؛ وصحة الشكل في وقت التشغيل تقع على عاتق التنفيذ الأصلي.\n\n### أنماط التعدادات\n\nتُمثَّل التعدادات الرقمية الأصلية كقيم `const enum` في TypeScript:\n\n- `FileType` (`1=file`، `2=dir`، `3=symlink`)\n- `ImageFormat` (`0=PNG`، `1=JPEG`، `2=WEBP`، `3=GIF`)\n- `SamplingFilter`، `Ellipsis`، `KeyEventType`\n\nيرى المستدعون أعضاء التعداد بأسمائها؛ بينما يمرّر حدّ الربط أرقامًا.\n\n## كيفية اكتشاف عدم التطابق\n\nيحدث اكتشاف عدم التطابق على طبقتين:\n\n1. **فحوصات عقد TypeScript في وقت الترجمة**\n   - تستدعي الأغلفة `native.<name>` مقابل `NativeBindings` المدموجة.\n   - تؤدي مفاتيح الربط المفقودة أو المُعاد تسميتها إلى كسر فحص النوع في TypeScript داخل الأغلفة.\n\n2. **التحقق في وقت التشغيل في `validateNative`**\n   - بعد التحميل، يتحقق `native.ts` من الصادرات المطلوبة ويرمي استثناءً إن كان أيٌّ منها مفقودًا.\n   - تتضمن رسالة الخطأ المفاتيح المفقودة وتعليمات إعادة البناء.\n\nيُمكّن هذا من اكتشاف انحراف الثنائي القديم الشائع: يوجد الغلاف/النوع لكن ملف `.node` المحمّل لا يحتوي على التصدير.\n\n## سلوك الفشل والتحفظات\n\n### فشل التحميل/التحقق (فشل غير قابل للاسترداد)\n\n- يُرمى استثناء عند فشل تحميل الإضافة أو عدم دعم المنصة أثناء تهيئة الوحدة في `native.ts`.\n- يُرمى استثناء عند وجود صادرات مطلوبة مفقودة قبل أن تصبح الأغلفة قابلة للاستخدام.\n\nالأثر: تفشل الحزمة بسرعة بدلًا من تأجيل الفشل إلى الاستدعاء الأول.\n\n### فروقات السلوك على مستوى الغلاف\n\n- تُليّن بعض الأغلفة الفشل عن قصد (`copyToClipboard` تبذل أفضل جهد وتتجاهل فشل الاستدعاء الأصلي).\n- تتجاهل ردود الاتصال للبث حمولات خطأ ردّ الاتصال وتُعيد توجيه أحداث القيمة الناجحة فقط.\n\n### تحفظات على مستوى النوع (وقت التشغيل أشدّ دقةً من TypeScript)\n\n- الحقول الاختيارية في TypeScript لا تضمن الصحة الدلالية؛ إذ يمكن للطبقة الأصلية رفض القيم المشوّهة.\n- كتابة `const enum` لا تمنع القيم الرقمية خارج النطاق من المستدعين غير المكتوبين في وقت التشغيل.\n- يتحقق `validateNative` من وجود الصادرات المطلوبة وكونها دالة فحسب، وليس من توافق شكل الحجج/القيمة المُرجعة بعمق.\n- يتضمن `bindings.ts` `cancelWork(id)` في الواجهة الأساسية، لكن قائمة التحقق في وقت التشغيل الحالية لا تُطبّق هذا المفتاح.\n\n## قائمة فحص المشرف لتغييرات الربط\n\nعند إضافة تصدير أو تغييره، قم بتحديث جميع ما يلي:\n\n1. `src/<module>/types.ts` (التوسيع + أنواع العقد)\n2. `src/<module>/index.ts` (سلوك الغلاف)\n3. استيرادات `src/native.ts` لأنواع الوحدة (في حال إنشاء وحدة جديدة)\n4. فحوصات التصدير المطلوبة في `validateNative`\n5. إعادة التصديرات العامة في `src/index.ts`\n\nإغفال أيّ خطوة يُفضي إلى انحراف في وقت الترجمة أو فشل في وقت تحميل وقت التشغيل.\n",
	"ar/natives/natives-build-release-debugging.md": "---\ntitle: دليل تشغيل البناء والإصدار وتصحيح الأخطاء للمكونات الأصيلة\ndescription: >-\n  دليل تشغيل البناء والإصدار وتصحيح الأخطاء للإضافة الأصيلة بلغة Rust عبر\n  المنصات.\nsidebar:\n  order: 8\n  label: البناء والإصدار وتصحيح الأخطاء\ni18n:\n  sourceHash: efe47aa5b466\n  translator: machine\n---\n\n# دليل تشغيل البناء والإصدار وتصحيح الأخطاء للمكونات الأصيلة\n\nيصف هذا الدليل كيفية إنتاج خط أنابيب البناء الخاص بـ `@f5-sales-demo/pi-natives` لإضافات `.node`، وكيفية تحميل التوزيعات المُجمَّعة لها، وكيفية تصحيح أخطاء المُحمِّل/فشل البناء.\n\nيتبع هذا الدليل مصطلحات البنية المعمارية من `docs/natives-architecture.md`:\n\n- **إنتاج القطع الأثرية في وقت البناء** (`scripts/build-native.ts`)\n- **توليد بيان الإضافة المضمّنة** (`scripts/embed-native.ts`)\n- **تحميل الإضافة في وقت التشغيل + بوابة التحقق** (`src/native.ts`)\n\n## ملفات التنفيذ\n\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `packages/natives/src/native.ts`\n- `crates/pi-natives/Cargo.toml`\n\n## نظرة عامة على خط أنابيب البناء\n\n### 1) نقاط دخول البناء\n\nسكريبتات `packages/natives/package.json`:\n\n- `bun scripts/build-native.ts` (`build`) → بناء إصدار الإنتاج\n- `bun scripts/build-native.ts --dev` (`dev:native`) → بناء ملف التصحيح/التطوير (نفس تسمية المخرجات)\n- `bun scripts/embed-native.ts` (`embed:native`) → توليد `src/embedded-addon.ts` من الملفات المبنية\n\n### 2) بناء القطعة الأثرية بلغة Rust\n\nيشغّل `build-native.ts` أداة Cargo في `crates/pi-natives`:\n\n- الأمر الأساسي: `cargo build`\n- يضيف وضع الإصدار `--release` ما لم يُمرَّر `--dev`\n- يضيف الهدف العابر للمنصات `--target <CROSS_TARGET>`\n\nيُعلن `crates/pi-natives/Cargo.toml` عن `crate-type = [\"cdylib\"]`، لذا تُصدر أداة Cargo مكتبة مشتركة (`.so`/`.dylib`/`.dll`) يتم نسخها/إعادة تسميتها لاحقاً إلى اسم ملف إضافة `.node`.\n\n### 3) اكتشاف القطعة الأثرية وتثبيتها\n\nبعد اكتمال Cargo، يفحص `build-native.ts` أدلة المخرجات المرشّحة بالترتيب:\n\n1. `${CARGO_TARGET_DIR}` (إن كان مُعيَّناً)\n2. `<repo>/target`\n3. `crates/pi-natives/target`\n\nلكل جذر يتحقق من أدلة الملفات الشخصية:\n\n- البناء العابر للمنصات: `<root>/<crossTarget>/<profile>` ثم `<root>/<profile>`\n- البناء الأصيل: `<root>/<profile>`\n\nثم يبحث عن أحد الملفات:\n\n- `libpi_natives.so`\n- `libpi_natives.dylib`\n- `pi_natives.dll`\n- `libpi_natives.dll`\n\nعند الاكتشاف، يُثبِّت الملف بصورة ذرية في `packages/natives/native/` باستخدام دلالات الملف المؤقت + إعادة التسمية (يعالج احتياط Windows صراحةً حالات فشل استبدال DLL المقفولة).\n\n## نموذج الهدف/المتغير واصطلاحات التسمية\n\n## وسم المنصة\n\nيستخدم كل من البناء ووقت التشغيل وسم المنصة:\n\n`<platform>-<arch>` (مثال: `darwin-arm64`، `linux-x64`)\n\n## نموذج المتغير (x64 فقط)\n\nيدعم x64 متغيرات المعالج:\n\n- `modern` (مسار قادر على AVX2)\n- `baseline` (احتياطي)\n\nتستخدم المنصات غير x64 قطعة أثرية افتراضية واحدة (بدون لاحقة متغير).\n\n### أسماء ملفات المخرجات\n\nبناءات الإصدار:\n\n- x64: `pi_natives.<platform>-<arch>-modern.node` أو `...-baseline.node`\n- غير x64: `pi_natives.<platform>-<arch>.node`\n\nبناء التطوير (`--dev`):\n\n- يستخدم علامات ملف التصحيح لكنه يحتفظ بتسمية المخرجات القياسية ذات الوسم الخاص بالمنصة\n\nترتيب مرشّحات المُحمِّل في `native.ts`:\n\n- مرشّحات الإصدار\n- يُقدِّم وضع التجميع المرشّحات المستخرجة/المخزنة مؤقتاً قبل الملفات المحلية للحزمة\n\n## علامات البيئة وخيارات البناء\n\n## علامات وقت التشغيل\n\n- `PI_DEV` (سلوك المُحمِّل): تمكين تشخيصات المُحمِّل\n- `PI_NATIVE_VARIANT` (سلوك المُحمِّل، x64 فقط): فرض اختيار `modern` أو `baseline` في وقت التشغيل\n- `PI_COMPILED` (سلوك المُحمِّل): تمكين سلوك مرشّح/استخراج الثنائيات المُجمَّعة\n\n## علامات/خيارات وقت البناء\n\n- `--dev` (معامل السكريبت): بناء ملف التصحيح\n- `CROSS_TARGET`: يُمرَّر إلى Cargo `--target`\n- `TARGET_PLATFORM`: تجاوز تسمية وسم منصة المخرجات\n- `TARGET_ARCH`: تجاوز تسمية بنية المخرجات\n- `TARGET_VARIANT` (x64 فقط): فرض `modern` أو `baseline` لاسم ملف المخرجات وسياسة RUSTFLAGS\n- `CARGO_TARGET_DIR`: جذر إضافي عند البحث في مخرجات Cargo\n- `RUSTFLAGS`:\n  - إذا لم تكن مُعيَّنة وليس هناك تجميع عابر للمنصات، يضبط السكريبت:\n    - modern: `-C target-cpu=x86-64-v3`\n    - baseline: `-C target-cpu=x86-64-v2`\n    - غير x64 / بدون متغير: `-C target-cpu=native`\n  - إذا كانت مُعيَّنة مسبقاً، لا يتجاوزها السكريبت\n\n## حالات البناء/انتقالات دورة الحياة\n\n### دورة حياة البناء (`build-native.ts`)\n\n1. **التهيئة**: تحليل المعامِلات/البيئة (`--dev`، تجاوزات الهدف، علامات التجميع العابر)\n2. **تحديد المتغير**:\n   - غير x64 → لا متغير\n   - x64 + `TARGET_VARIANT` → متغير صريح\n   - بناء x64 عابر للمنصات بدون `TARGET_VARIANT` → خطأ فادح\n   - بناء x64 محلي بدون تجاوز → اكتشاف AVX2 للمضيف\n3. **التجميع**: تشغيل Cargo مع الملف الشخصي/الهدف المحدَّدين\n4. **تحديد موقع القطعة الأثرية**: فحص جذور الهدف/أدلة الملف الشخصي/أسماء المكتبات\n5. **التثبيت**: النسخ + إعادة التسمية الذرية في `packages/natives/native`\n6. **الاكتمال**: الإضافة جاهزة لمرشّحات المُحمِّل\n\nتحدث حالات الخروج بالفشل في أي مرحلة مع نص خطأ صريح (متغير غير صالح، فشل بناء cargo، مكتبة الإخراج مفقودة، فشل التثبيت/إعادة التسمية).\n\n### دورة حياة التضمين (`embed-native.ts`)\n\n1. **التهيئة**: حساب وسم المنصة من `TARGET_PLATFORM`/`TARGET_ARCH` أو قيم المضيف\n2. **مجموعة المرشّحات**:\n   - يتوقع x64 كلاً من `modern` و`baseline`\n   - يتوقع غير x64 ملفاً افتراضياً واحداً\n3. **التحقق من التوفر** في `packages/natives/native`\n4. **توليد البيان** (`src/embedded-addon.ts`) مع استيرادات `file` لـ Bun وإصدار الحزمة\n5. **جاهزية استخراج وقت التشغيل** لوضع التجميع\n\nيتجاوز `--reset` التحقق ويكتب بذرة بيان فارغة (`embeddedAddon = null`).\n\n## سير عمل التطوير مقابل السلوك المشحون/المُجمَّع\n\n## سير عمل التطوير المحلي\n\nالحلقة المحلية النموذجية:\n\n1. بناء الإضافة:\n   - إصدار الإنتاج: `bun --cwd=packages/natives run build`\n   - ملف التصحيح: `bun --cwd=packages/natives run dev:native`\n2. تعيين `PI_DEV=1` عند اختبار تشخيصات المُحمِّل\n3. يحلّ المُحمِّل في `native.ts` المرشّحات المحلية للحزمة `native/` (واحتياط دليل الملف التنفيذي)\n4. يفرض `validateNative` توافق الصادرات قبل استخدام الأغلفة للربط\n\n## سير عمل الثنائي المشحون/المُجمَّع\n\nفي وضع التجميع (`PI_COMPILED` أو علامات Bun المضمّنة):\n\n1. يحسب المُحمِّل دليل ذاكرة التخزين المؤقت ذا الإصدار: `<getNativesDir()>/<packageVersion>` (تشغيلياً `~/.xcsh/natives/<version>`)\n2. إذا تطابق البيان المضمَّن مع المنصة+الإصدار الحاليَّين، قد يستخرج المُحمِّل الملف المضمَّن المحدَّد في ذلك الدليل ذي الإصدار\n3. يتضمن ترتيب مرشّحات وقت التشغيل:\n   - دليل ذاكرة التخزين المؤقت ذو الإصدار\n   - دليل الثنائي المُجمَّع القديم (`%LOCALAPPDATA%/xcsh` على Windows، `~/.local/bin` في غيره)\n   - أدلة الحزمة/الملف التنفيذي\n4. لا تزال أول إضافة تُحمَّل بنجاح يجب أن تجتاز `validateNative`\n\nهذا هو السبب في ضرورة توافق توقعات التعبئة + مُحمِّل وقت التشغيل: يجب أن تتطابق أسماء الملفات ووسوم المنصات والرموز المُصدَّرة مع ما يفحصه `native.ts` ويتحقق منه.\n\n## تعيين JS API ↔ صادرات Rust (مجموعة فرعية من بوابة التحقق)\n\nيتطلب `native.ts` وجود هذه الصادرات المرئية من JS على الإضافة المحمَّلة. تُعيَّن إلى صادرات N-API في Rust ضمن `crates/pi-natives/src`:\n\n| اسم JS المطلوب بواسطة `validateNative` | إعلان صادر Rust | ملف مصدر Rust |\n| --- | --- | --- |\n| `glob` | `#[napi] pub fn glob(...)` | `crates/pi-natives/src/glob.rs` |\n| `grep` | `#[napi] pub fn grep(...)` | `crates/pi-natives/src/grep.rs` |\n| `search` | `#[napi] pub fn search(...)` | `crates/pi-natives/src/grep.rs` |\n| `highlightCode` | `#[napi] pub fn highlight_code(...)` | `crates/pi-natives/src/highlight.rs` |\n| `getSystemInfo` | `#[napi] pub fn get_system_info(...)` | `crates/pi-natives/src/system_info.rs` |\n| `getWorkProfile` | `#[napi] pub fn get_work_profile(...)` (صادر بصيغة camelCase) | `crates/pi-natives/src/prof.rs` |\n| `invalidateFsScanCache` | `#[napi] pub fn invalidate_fs_scan_cache(...)` | `crates/pi-natives/src/fs_cache.rs` |\n\nإذا كان أي رمز مطلوب مفقوداً، يفشل المُحمِّل بسرعة مع تلميح بإعادة البناء.\n\n## سلوك الفشل والتشخيصات\n\n## أعطال وقت البناء\n\n- تكوين متغير غير صالح:\n  - `TARGET_VARIANT` مُعيَّن على غير x64 → خطأ فوري\n  - بناء x64 عابر للمنصات بدون `TARGET_VARIANT` صريح → خطأ فوري\n- فشل بناء Cargo:\n  - يعرض السكريبت الخروج غير الصفري ومخرجات stderr\n- القطعة الأثرية غير موجودة:\n  - يطبع السكريبت كل دليل ملف شخصي تم التحقق منه\n- فشل التثبيت:\n  - رسالة صريحة؛ يتضمن Windows تلميحاً بالملف المقفول\n\n## أعطال مُحمِّل وقت التشغيل (`native.ts`)\n\n- وسم منصة غير مدعومة:\n  - يطرح استثناءً مع قائمة المنصات المدعومة\n- لا يمكن تحميل أي مرشّح:\n  - يطرح استثناءً مع قائمة أخطاء المرشّحات الكاملة وتلميحات المعالجة الخاصة بالوضع\n- صادرات مفقودة:\n  - يطرح استثناءً مع أسماء الرموز المفقودة بالضبط وأمر إعادة البناء\n- مشاكل استخراج التضمين:\n  - يتم تسجيل أخطاء mkdir/write الخاصة بالاستخراج وتضمينها في التشخيصات النهائية\n\n## مصفوفة استكشاف الأخطاء وإصلاحها\n\n| العَرَض | السبب المحتمل | التحقق | الإصلاح |\n| --- | --- | --- | --- |\n| `Native addon missing exports ... Missing: <name>` | ثنائي `.node` قديم، عدم تطابق اسم صادر Rust، أو تحميل ثنائي خاطئ | التشغيل مع `PI_DEV=1` لرؤية المسار المحمَّل؛ فحص قائمة الصادرات لذلك الملف | إعادة البناء `build`؛ التأكد من تطابق اسم صادر Rust `#[napi]` (أو الاسم المستعار الصريح عند الحاجة) مع مفتاح JS؛ حذف الملفات المخزنة/ذات الإصدار القديمة |\n| جهاز x64 يحمّل baseline عندما يُتوقع modern | `PI_NATIVE_VARIANT=baseline`، عدم اكتشاف AVX2، أو وجود ملف baseline فقط | التحقق من `PI_NATIVE_VARIANT`؛ فحص `native/` للملف `-modern` | بناء متغير modern (`TARGET_VARIANT=modern ... build`) والتأكد من شحن الملف |\n| البناء العابر للمنصات ينتج ثنائياً غير قابل للاستخدام/مُوسَماً بشكل خاطئ | عدم تطابق بين `CROSS_TARGET` و`TARGET_PLATFORM`/`TARGET_ARCH`، أو غياب `TARGET_VARIANT` لـ x64 | تأكيد مجموعة env واسم ملف المخرجات | إعادة التشغيل بقيم env متسقة و`TARGET_VARIANT` صريح لـ x64 |\n| فشل الثنائي المُجمَّع بعد الترقية | ذاكرة تخزين مؤقت مستخرجة قديمة (`~/.xcsh/natives/<old-or-mismatched-version>`) أو عدم تطابق البيان المضمَّن | فحص دليل natives ذي الإصدار وقائمة أخطاء المُحمِّل | حذف ذاكرة التخزين المؤقت لـ natives ذات الإصدار الخاص بإصدار الحزمة وإعادة التشغيل؛ إعادة توليد البيان المضمَّن أثناء التعبئة |\n| يفحص المُحمِّل مسارات كثيرة ولا يجد أياً منها | عدم تطابق المنصة أو غياب القطعة الأثرية للإصدار في `native/` الخاص بالحزمة | التحقق من `platformTag` مقابل اسم/أسماء الملف الفعلية | التأكد من أن اسم الملف المبني يتطابق تماماً مع اصطلاح `pi_natives.<platform>-<arch>(-variant).node` وأن الحزمة تتضمن `native/` |\n| يفشل `embed:native` بـ \"Incomplete native addons\" | ملفات المتغير المطلوبة لم تُبنَ قبل التضمين | التحقق من قائمة المتوقع مقابل الموجود في نص الخطأ | بناء الملفات المطلوبة أولاً (x64: كلاً من modern+baseline؛ غير x64: الافتراضي)، ثم إعادة تشغيل `embed:native` |\n\n## أوامر التشغيل\n\n```bash\n# قطعة أثرية للإصدار للمضيف الحالي\nbun --cwd=packages/natives run build\n\n# بناء قطعة أثرية لملف التصحيح\nbun --cwd=packages/natives run dev:native\n\n# بناء متغيرات x64 الصريحة\nTARGET_VARIANT=modern bun --cwd=packages/natives run build\nTARGET_VARIANT=baseline bun --cwd=packages/natives run build\n\n# توليد بيان الإضافة المضمّنة من الملفات الأصيلة المبنية\nbun --cwd=packages/natives run embed:native\n\n# إعادة تعيين البيان المضمَّن إلى بذرة فارغة\nbun --cwd=packages/natives run embed:native -- --reset\n```\n",
	"ar/natives/natives-media-system-utils.md": "---\ntitle: أدوات الوسائط والنظام الأصلية\ndescription: أدوات معالجة الوسائط الأصلية لالتقاط الشاشة ومعالجة الصور ومعلومات النظام.\nsidebar:\n  order: 7\n  label: أدوات الوسائط والنظام\ni18n:\n  sourceHash: 430898c177bc\n  translator: machine\n---\n\n# أدوات الوسائط والنظام الأصلية\n\nهذا المستند هو تحليل معمّق لنظام فرعي خاص بطبقة **البدائيات الأصلية للنظام/الوسائط/التحويل** الموصوفة في [`docs/natives-architecture.md`](./natives-architecture.md): `image` و`html` و`clipboard` وتحليل أداء `work`.\n\n## ملفات التنفيذ\n\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/prof.rs`\n- `crates/pi-natives/src/task.rs`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/work/index.ts`\n- `packages/natives/src/work/types.ts`\n\n> ملاحظة: لا يوجد ملف `crates/pi-natives/src/work.rs`؛ تحليل أداء العمل مُنفَّذ في `prof.rs` ويُغذَّى بالأدوات الموجودة في `task.rs`.\n\n## ربط واجهة TS البرمجية ↔ تصدير/وحدة Rust\n\n| تصدير TS (packages/natives)                 | تصدير N-API في Rust                                                     | وحدة Rust                             |\n| ------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------- |\n| `PhotonImage.parse(bytes)`                  | `PhotonImage::parse`                                                     | `image.rs`                            |\n| `PhotonImage#resize(width, height, filter)` | `PhotonImage::resize`                                                    | `image.rs`                            |\n| `PhotonImage#encode(format, quality)`       | `PhotonImage::encode`                                                    | `image.rs`                            |\n| `htmlToMarkdown(html, options)`             | `html_to_markdown`                                                       | `html.rs`                             |\n| `copyToClipboard(text)`                     | `copy_to_clipboard` + منطق احتياطي في TS                                 | `clipboard.rs` + `clipboard/index.ts` |\n| `readImageFromClipboard()`                  | `read_image_from_clipboard`                                              | `clipboard.rs`                        |\n| `getWorkProfile(lastSeconds)`               | `get_work_profile`                                                      | `prof.rs`                             |\n\n## حدود تنسيقات البيانات والتحويلات\n\n### الصورة (`image`)\n\n- **حد الإدخال في JS**: بايتات صورة مُرمَّزة من نوع `Uint8Array`.\n- **حد فك الترميز في Rust**: تُنسَخ البايتات إلى `Vec<u8>`، ويُخمَّن التنسيق باستخدام `ImageReader::with_guessed_format()`، ثم يُفكّ الترميز إلى `DynamicImage`.\n- **الحالة في الذاكرة**: يخزّن `PhotonImage` القيمة `Arc<DynamicImage>`.\n- **حد الإخراج**: تُعيد `encode(format, quality)` القيمة `Promise<Uint8Array>` (أي `Vec<u8>` في Rust).\n\nمعرّفات التنسيقات رقمية:\n\n- `0`: PNG\n- `1`: JPEG\n- `2`: WebP (مُرمِّز بدون فقدان)\n- `3`: GIF\n\nالقيود:\n\n- يُستخدَم `quality` فقط مع JPEG.\n- تتجاهل PNG/WebP/GIF القيمة `quality`.\n- تفشل معرّفات التنسيقات غير المدعومة (`Invalid image format: <id>`).\n\n### تحويل HTML (`html`)\n\n- **حد الإدخال في JS**: سلسلة نصية `string` من HTML + كائن اختياري `{ cleanContent?: boolean; skipImages?: boolean }`.\n- **حد التحويل في Rust**: يُحوَّل الإدخال `String` بواسطة `html_to_markdown_rs::convert`.\n- **حد الإخراج**: سلسلة نصية `string` بتنسيق Markdown.\n\nسلوك التحويل:\n\n- القيمة الافتراضية لـ `cleanContent` هي `false`.\n- عند تعيين `cleanContent=true`، تُفعَّل المعالجة المسبقة مع `PreprocessingPreset::Aggressive` وعلامات الإزالة الصارمة للتنقل والنماذج.\n- القيمة الافتراضية لـ `skipImages` هي `false`.\n\n### الحافظة (`clipboard`)\n\n- **مسار النص**:\n  - تُصدر TS أولاً OSC 52 (`\\x1b]52;c;<base64>\\x07`) عندما يكون stdout طرفية TTY.\n  - ثم يُحاوَل نسخ نفس النص عبر واجهة الحافظة الأصلية (`native.copyToClipboard`) كمحاولة أفضل جهد.\n  - على Termux، تحاول TS استخدام `termux-clipboard-set` أولاً.\n- **مسار قراءة الصورة**:\n  - يقرأ Rust الصورة الخام من `arboard`.\n  - يُعيد Rust ترميزها إلى بايتات PNG (مكتبة `image`)، ويُعيد `{ data: Uint8Array, mimeType: \"image/png\" }`.\n  - تُعيد TS القيمة `null` مبكراً على Termux أو جلسات Linux بدون خادم عرض (عند غياب `DISPLAY`/`WAYLAND_DISPLAY`).\n\n### تحليل أداء العمل (`work`)\n\n- **حد التجميع**: تُنتَج عينات التحليل بواسطة حراسات `profile_region(tag)` في `task::blocking` و`task::future`.\n- **تنسيق التخزين**: مخزن مؤقت دائري بحجم ثابت (`MAX_SAMPLES = 10_000`) يخزّن مسار المكدس + المدة (`μs`) + الطابع الزمني (`μs منذ بدء العملية`).\n- **حد الإخراج**: تُعيد `getWorkProfile(lastSeconds)` كائناً يحتوي على:\n  - `folded`: نص مكدس مطوي (مدخل رسم اللهب البياني)\n  - `summary`: جدول ملخص بتنسيق markdown\n  - `svg`: رسم SVG اختياري لرسم اللهب البياني\n  - `totalMs`، `sampleCount`\n\n## دورة الحياة وانتقالات الحالة\n\n### دورة حياة الصورة\n\n1. تُجدوِل `PhotonImage.parse(bytes)` مهمة فك ترميز حاجبة (`image.decode`).\n2. عند النجاح، يوجد مقبض `PhotonImage` أصلي في JS.\n3. تُنشئ `resize(...)` مقبضاً أصلياً جديداً (`image.resize`)، ويمكن أن يتواجد المقبض القديم والجديد معاً.\n4. تُجسِّد `encode(...)` البايتات (`image.encode`) دون تغيير أبعاد الصورة.\n\nانتقالات الفشل:\n\n- فشل اكتشاف التنسيق/فك الترميز يرفض وعد التحليل.\n- فشل الترميز يرفض وعد الترميز.\n- معرّف التنسيق غير الصالح يرفض وعد الترميز.\n\n### دورة حياة HTML\n\n1. تُجدوِل `htmlToMarkdown(html, options)` مهمة تحويل حاجبة.\n2. يعمل التحويل مع الخيارات الافتراضية (`cleanContent=false`، `skipImages=false`) ما لم تُحدَّد.\n3. تُعيد سلسلة markdown نصية أو ترفض.\n\nانتقالات الفشل:\n\n- فشل المحوِّل يُعيد وعداً مرفوضاً (`Conversion error: ...`).\n\n### دورة حياة الحافظة\n\n`copyToClipboard(text)` مُصمَّم عمداً كأفضل جهد ومتعدد المسارات:\n\n1. إذا كان TTY: محاولة كتابة OSC 52 (حمولة base64).\n2. محاولة أمر Termux عند تعيين `TERMUX_VERSION`.\n3. محاولة نسخ النص الأصلي عبر `arboard`.\n4. ابتلاع الأخطاء في طبقة TS.\n\nتختلف صرامة `readImageFromClipboard()` حسب المرحلة:\n\n1. تحظر TS بشكل صارم سياقات وقت التشغيل غير المدعومة (Termux/Linux بدون واجهة رسومية) وتُعيد `null`.\n2. تعمل قراءة `arboard` في Rust فقط عندما تسمح TS بذلك.\n3. يُربَط `ContentNotAvailable` بالقيمة `null`.\n4. أخطاء Rust الأخرى ترفض.\n\n### دورة حياة تحليل أداء العمل\n\n1. لا يوجد بدء صريح: التحليل يعمل دائماً عند تنفيذ مساعدات المهام.\n2. يسجّل كل نطاق مهمة مُجهَّز عينة واحدة عند إسقاط الحارس.\n3. تُستبدَل أقدم الإدخالات بعد الوصول لسعة المخزن المؤقت.\n4. تقرأ `getWorkProfile(lastSeconds)` نافذة زمنية وتستخرج منتجات المكدس المطوي/الملخص/SVG.\n\nانتقالات الفشل:\n\n- فشل توليد SVG هو فشل مرن (`svg: null`)، بينما يستمر إرجاع المكدس المطوي والملخص.\n- نافذة العينات الفارغة تُعيد بيانات مكدس مطوية فارغة و`svg: null`، وليس خطأً.\n\n## العمليات غير المدعومة وانتشار الأخطاء\n\n### الصورة\n\n- إدخال فك ترميز غير مدعوم أو بايتات تالفة: فشل صارم (رفض الوعد).\n- معرّف تنسيق ترميز غير مدعوم: فشل صارم.\n- لا يوجد مسار احتياطي لأفضل جهد في غلاف TS.\n\n### HTML\n\n- أخطاء التحويل هي فشل صارم (رفض).\n- حذف الخيارات يُعامَل كتعيين افتراضي بأفضل جهد، وليس فشلاً.\n\n### الحافظة\n\n- نسخ النص يعمل بأفضل جهد في طبقة TS: تُكبَت الأخطاء التشغيلية.\n- قراءة الصورة تُميِّز بين \"لا توجد صورة\" (`null`) والفشل التشغيلي (الرفض).\n- تُعامَل Termux/Linux بدون واجهة رسومية كسياقات غير مدعومة لقراءة الصورة (`null`).\n\n### تحليل أداء العمل\n\n- الاسترجاع صارم بالنسبة لاستدعاء الدالة نفسه، لكن توليد المنتجات يعمل جزئياً بأفضل جهد (`svg` قابل للقيمة null).\n- اقتطاع المخزن المؤقت هو سلوك متوقع (مخزن دائري)، وليس خطأ فقدان بيانات.\n\n## ملاحظات خاصة بالمنصة\n\n- **نص الحافظة**: يعتمد OSC 52 على دعم الطرفية؛ ويعتمد الوصول الأصلي للحافظة على بيئة سطح المكتب/الجلسة.\n- **قراءة صورة الحافظة**: محظورة في TS على Termux وLinux بدون خادم عرض.\n",
	"ar/natives/natives-rust-task-cancellation.md": "---\ntitle: تنفيذ مهام Rust الأصلية وإلغاؤها\ndescription: نموذج تنفيذ المهام غير المتزامنة في Rust مع دلالات الإلغاء التعاوني والتنظيف.\nsidebar:\n  order: 5\n  label: إلغاء المهام\ni18n:\n  sourceHash: 0fbf45c6d463\n  translator: machine\n---\n\n# تنفيذ مهام Rust الأصلية وإلغاؤها (`pi-natives`)\n\nيصف هذا المستند كيفية جدولة `crates/pi-natives` للعمل الأصلي، وكيفية تدفق الإلغاء من خيارات JS (`timeoutMs`، `AbortSignal`) إلى تنفيذ Rust.\n\n## ملفات التنفيذ\n\n- `crates/pi-natives/src/task.rs`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/fd.rs`\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/ps.rs`\n\n## العناصر الأولية الأساسية (`task.rs`)\n\nيعرّف `task.rs` ثلاثة عناصر أساسية:\n\n1. `task::blocking(tag, cancel_token, work)`\n   - يُغلّف `napi::AsyncTask` / `Task`.\n   - تعمل `compute()` على خيوط عمل libuv (للعمليات المكثفة بحوسبيًا أو استدعاءات النظام المتزامنة/الحاجبة).\n   - تُرجع `Promise<T>` في JS.\n\n2. `task::future(env, tag, work)`\n   - يُغلّف `env.spawn_future(...)`.\n   - يُشغّل العمل غير المتزامن على وقت تشغيل Tokio.\n   - تُرجع `PromiseRaw<'env, T>`.\n\n3. `CancelToken` / `AbortToken` / `AbortReason`\n   - تجمع `CancelToken::new(timeout_ms, signal)` بين الموعد النهائي والـ `AbortSignal` الاختياري.\n   - `CancelToken::heartbeat()` هو إلغاء تعاوني لحلقات الحجب.\n   - `CancelToken::wait()` هو انتظار إلغاء غير متزامن (`Signal` / `Timeout` / `User` Ctrl-C).\n   - يتيح `AbortToken` للكود الخارجي طلب الإلغاء (`abort(reason)`).\n\n## `blocking` مقابل `future`: نموذج التنفيذ والاختيار\n\n### استخدام `task::blocking`\n\nاستخدمه عندما يكون العمل مكثفًا حوسبيًا أو متزامنًا/حاجبًا بطبيعته:\n\n- المسح بالتعبيرات النمطية/الملفات (`grep`، `glob`، `fuzzy_find`)\n- عمليات حلقة PTY المتزامنة الداخلية (`run_pty_sync` عبر `spawn_blocking`)\n- تحويلات الحافظة/الصور/HTML\n\nالسلوك:\n\n- تستقبل إغلاق العمل نسخة مستنسخة من `CancelToken`.\n- يُلاحَظ الإلغاء فقط عند تحقق الكود من `ct.heartbeat()?`.\n- يرفض `Err(...)` من الإغلاق وعد JS.\n\n### استخدام `task::future`\n\nاستخدمه عندما يجب أن يعمل العمل بـ `await` على عمليات غير متزامنة:\n\n- تنسيق جلسات shell (`shell.run`، `executeShell`)\n- المسابقة بين المهام (`tokio::select!`) بين الإتمام والإلغاء\n\nالسلوك:\n\n- يمكن للمستقبل أن يتسابق بين الإتمام الطبيعي و`ct.wait()`.\n- في مسار الإلغاء، عادةً ما تنشر التنفيذات غير المتزامنة الإلغاء إلى الأنظمة الفرعية الداخلية (مثل `tokio_util::CancellationToken`) وتفرض الإلغاء القسري عند انتهاء مهلة السماحة اختياريًا.\n\n## تعيين واجهة برمجة JS ↔ صادرات Rust (المتعلقة بالمهام/الإلغاء)\n\n| واجهة JS | صادرات Rust (`#[napi]`) | المجدول | ربط الإلغاء |\n|---|---|---|---|\n| `grep(options, onMatch?)` | `grep` | `task::blocking(\"grep\", ct, ...)` | `CancelToken::new(options.timeoutMs, options.signal)` + `ct.heartbeat()` |\n| `glob(options, onMatch?)` | `glob` | `task::blocking(\"glob\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` في حلقة التصفية |\n| `fuzzyFind(options)` | `fuzzy_find` | `task::blocking(\"fuzzy_find\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` في حلقة التسجيل |\n| `shell.run(options, onChunk?)` | `Shell::run` | `task::future(env, \"shell.run\", ...)` | `ct.wait()` في مسابقة مع مهمة التشغيل؛ يتصل بـ `CancellationToken` في Tokio |\n| `executeShell(options, onChunk?)` | `execute_shell` | `task::future(env, \"shell.execute\", ...)` | مثل السابق |\n| `pty.start(options, onChunk?)` | `PtySession::start` | `task::future(env, \"pty.start\", ...)` + `spawn_blocking` داخلي | يُتحقق من `CancelToken` في حلقة PTY المتزامنة عبر `heartbeat()` |\n| `htmlToMarkdown(html, options?)` | `html_to_markdown` | `task::blocking(\"html_to_markdown\", (), ...)` | لا يوجد (رمز `()`) |\n| `PhotonImage.parse/encode/resize` | `PhotonImage::{parse,encode,resize}` | `task::blocking(...)` | لا يوجد (رمز `()`) |\n| `copyToClipboard/readImageFromClipboard` | `copy_to_clipboard` / `read_image_from_clipboard` | `task::blocking(...)` | لا يوجد (رمز `()`) |\n\nلا يستخدم كل من `text.rs` و`ps.rs` حاليًا `task::blocking`/`task::future`، وبالتالي لا يشاركان في مسار الإلغاء هذا.\n\n## دورة حياة الإلغاء وانتقالات الحالة\n\n### دورة حياة `CancelToken`\n\n`CancelToken` تعاونية وذات حالة:\n\n```text\nCreated\n  ├─ no signal + no timeout  -> passive token (never aborts unless externally emplaced)\n  ├─ signal registered        -> waits for AbortSignal callback\n  └─ deadline set             -> timeout check becomes active\n\nRunning\n  ├─ heartbeat()/wait() sees signal   -> AbortReason::Signal\n  ├─ heartbeat()/wait() sees deadline -> AbortReason::Timeout\n  ├─ wait() sees Ctrl-C               -> AbortReason::User\n  └─ no abort                         -> continue\n\nAborted (terminal)\n  └─ first abort reason wins (atomic flag + notifier)\n```\n\n### الإلغاء قبل البدء مقابل الإلغاء أثناء التنفيذ\n\n- **قبل البدء / قبل أول فحص للإلغاء**:\n  - يمكن لمستخدمي `task::future` الذين يتسابقون على `ct.wait()` حل الإلغاء فورًا بمجرد دخولهم `select!`.\n  - يلاحظ مستخدمو `task::blocking` الإلغاء فقط عند وصول كود الإغلاق إلى `heartbeat()`. إذا لم ينفّذ الإغلاق heartbeat مبكرًا، يتأخر الإلغاء.\n\n- **أثناء التنفيذ**:\n  - `blocking`: تُرجع `heartbeat()` التالية `Err(\"Aborted: ...\")`.\n  - `future`: يفوز فرع `ct.wait()` في `select!`، ثم يُلغي الكود الآلية غير المتزامنة التابعة (بالنسبة لـ shell: يُلغي رمز Tokio، وينتظر حتى 2 ثانية، ثم يُجبر على إلغاء المهمة).\n\n## توقعات النبضات لحلقات التشغيل الطويل\n\nيجب تشغيل `heartbeat()` بإيقاع منتظم في الحلقات ذات مجموعات العمل غير المحدودة أو الكبيرة.\n\nالأنماط الملاحظة:\n\n- `glob::filter_entries`: التحقق من كل إدخال قبل التصفية/المطابقة.\n- `fd::score_entries`: التحقق من كل مرشح تم مسحه.\n- `grep_sync`: فحص إلغاء صريح قبل مرحلة البحث المكثفة، بالإضافة إلى استدعاءات fs-cache التي تستقبل الرمز أيضًا.\n- `run_pty_sync`: التحقق في كل دورة حلقة (بإيقاع نوم ~16ms) وإيقاف العملية الابن عند الإلغاء.\n\nالقاعدة العملية: لا ينبغي أن تتجاوز أي حلقة على مدخلات خارجية الحجم فترة محدودة قصيرة دون نبضة.\n\n## سلوك الفشل ونشر الأخطاء إلى JS\n\n### المهام الحاجبة\n\nمسار الخطأ:\n\n1. يُرجع الإغلاق `Err(napi::Error)` (بما في ذلك إلغاء `heartbeat()`).\n2. تُرجع `Task::compute()` قيمة `Err`.\n3. يرفض `AsyncTask` وعد JS.\n\nسلاسل الأخطاء النموذجية:\n\n- `Aborted: Timeout`\n- `Aborted: Signal`\n- أخطاء النطاق (`Failed to decode image: ...`، `Conversion error: ...`، إلخ)\n\n### المهام المستقبلية\n\nمسار الخطأ:\n\n1. يُرجع جسم غير المتزامن `Err(napi::Error)` أو يُعيَّن فشل الانضمام (`... task failed: {err}`).\n2. يرفض الوعد المُولَّد بـ `task::future`.\n3. تُرجع بعض واجهات برمجة التطبيقات عن قصد نتائج إلغاء منظمة بدلًا من الرفض (`ShellRunResult`/`ShellExecuteResult` مع أعلام `cancelled`/`timed_out` و`exit_code: None`).\n\n### تقسيم تقارير الإلغاء\n\n- **الإلغاء كخطأ**: معظم الصادرات الحاجبة التي تستخدم `heartbeat()?`.\n- **الإلغاء كنتيجة مكتوبة**: واجهات برمجة تطبيقات الأوامر من نمط shell/pty التي تُنمذج الإلغاء في هياكل النتائج.\n\nاختر نموذجًا واحدًا لكل واجهة برمجة ووثّقه صراحةً.\n\n## المشكلات الشائعة\n\n1. **نبضة مفقودة في حلقات الحجب**\n   - الأعراض: يبدو أن المهلة/الإشارة مُتجاهلة حتى نهاية الحلقة.\n   - الحل: أضف `ct.heartbeat()?` في بداية الحلقة وقبل الخطوات المكثفة لكل عنصر.\n\n2. **أقسام غير قابلة للإلغاء لفترات طويلة**\n   - الأعراض: ارتفاع زمن الاستجابة للإلغاء خلال استدعاء واحد كبير (فك الترميز، الفرز، الضغط، إلخ).\n   - الحل: قسّم العمل إلى قطع مع حدود نبضات؛ إذا كان ذلك مستحيلًا، وثّق زمن الاستجابة.\n\n3. **حجب منفّذ غير المتزامن**\n   - الأعراض: تتوقف واجهة برمجة التطبيقات غير المتزامنة عند تشغيل كود مكثف حوسبيًا مباشرةً في المستقبل.\n   - الحل: انقل كتل CPU/المتزامن إلى `task::blocking` أو `tokio::task::spawn_blocking`.\n\n4. **دلالات إلغاء غير متسقة**\n   - الأعراض: ترفض إحدى واجهات برمجة التطبيقات عند الإلغاء، بينما تحل واجهة أخرى مع أعلام، مما يُربك المستدعين.\n   - الحل: وحّد المعالجة لكل نطاق واحتفظ بتوثيق المُغلّف متوافقًا.\n\n5. **نسيان جسر الإلغاء في المهام غير المتزامنة المتداخلة**\n   - الأعراض: يُلغى الرمز الخارجي لكن قراء الداخل/مهام العمليات الفرعية تستمر في العمل.\n   - الحل: اجسر الإلغاء إلى الرمز/الإشارة الداخلية وفرض مهلة السماحة والإلغاء القسري احتياطيًا.\n\n## قائمة مراجعة الصادرات القابلة للإلغاء الجديدة\n\n1. صنّف العمل بشكل صحيح:\n   - مكثف حوسبيًا أو حجب متزامن -> `task::blocking`\n   - إدخال/إخراج غير متزامن / تنسيق `await` -> `task::future`\n\n2. اكشف مدخلات الإلغاء عند الحاجة:\n   - أدرج `timeoutMs` و`signal` في خيارات `#[napi(object)]`\n   - أنشئ `let ct = task::CancelToken::new(timeout_ms, signal);`\n\n3. اربط الإلغاء عبر جميع الطبقات:\n   - حلقات الحجب: `ct.heartbeat()?` على فترات منتظمة\n   - تنسيق غير متزامن: تسابق مع `ct.wait()` وإلغاء المهام الفرعية/الرموز\n\n4. حدد عقد الإلغاء:\n   - رفض الوعد مع خطأ إلغاء، أو\n   - حل نوع مكتوب `{ cancelled, timedOut, ... }`\n   - احتفظ بهذا العقد متسقًا لعائلة واجهة برمجة التطبيقات\n\n5. انشر الأخطاء مع السياق:\n   - عيّن الأخطاء عبر `Error::from_reason(format!(\"...: {err}\"))`\n   - أدرج بادئات خاصة بالمرحلة (`spawn`، `decode`، `wait`، إلخ)\n\n6. تعامل مع الإلغاء قبل البدء وأثناء التنفيذ:\n   - يجب أن يحدث فحص/انتظار الإلغاء قبل الجسم المكلف وأثناء التنفيذ الطويل\n\n7. تحقق من عدم إساءة استخدام المنفّذ:\n   - لا عمل متزامن طويل مباشرةً داخل المستقبلات غير المتزامنة دون `spawn_blocking`/مُغلّف مهمة حاجبة\n",
	"ar/natives/natives-shell-pty-process.md": "---\ntitle: الغلاف الأصلي وPTY والعملية ومفاتيح الداخلية\ndescription: >-\n  تنفيذ الغلاف وإدارة PTY ودورة حياة العملية ومعالجة أحداث المفاتيح في الطبقة\n  الأصلية.\nsidebar:\n  order: 4\n  label: الغلاف وPTY والعملية\ni18n:\n  sourceHash: 00ea95614c6a\n  translator: machine\n---\n\n# الغلاف الأصلي وPTY والعملية ومفاتيح الداخلية\n\nتتناول هذه الوثيقة **العمليات الأولية للتنفيذ/العملية/الطرفية** في `@f5-sales-demo/pi-natives`: `shell` و`pty` و`ps` و`keys`، باستخدام المصطلحات المعمارية من `docs/natives-architecture.md`.\n\n## ملفات التنفيذ\n\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/shell/windows.rs` (Windows فقط)\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/ps.rs`\n- `crates/pi-natives/src/keys.rs`\n- `crates/pi-natives/src/task.rs` (سلوك الإلغاء المشترك المستخدم بواسطة shell/pty)\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/bindings.ts`\n\n## ملكية الطبقات\n\n- **طبقة غلاف/API في TypeScript** (`packages/natives/src/*`): نقاط دخول مكتوبة بالأنواع، وسطح الإلغاء (`timeoutMs`، `AbortSignal`)، وبيئة JavaScript.\n- **طبقة وحدة Rust N-API** (`crates/pi-natives/src/*`): تنفيذ عملية الغلاف/PTY، واجتياز شجرة العمليات/إنهاؤها، وتحليل تسلسلات المفاتيح.\n- **بوابة التحقق** (`native.ts`، على مستوى المعمارية): تضمن وجود الصادرات المطلوبة (`Shell`، `executeShell`، `PtySession`، `killTree`، `listDescendants`، ومساعدات المفاتيح) قبل استخدام الأغلفة.\n\n## النظام الفرعي للغلاف (`shell`)\n\n### نموذج API\n\nيتم عرض وضعَي تنفيذ:\n\n1. **لقطة واحدة** عبر `executeShell(options, onChunk?)`.\n2. **جلسة دائمة** عبر `new Shell(options?)` ثم `shell.run(...)` بصورة متكررة.\n\nيقوم كلاهما ببث الإخراج من خلال استدعاء خلفي آمن للخيوط ويعيد `{ exitCode?, cancelled, timedOut }`.\n\n### إنشاء الجلسة ونموذج البيئة\n\nينشئ Rust `brush_core::Shell` بالإعدادات التالية:\n\n- وضع غير تفاعلي،\n- `do_not_inherit_env: true`،\n- إعادة بناء صريحة للبيئة من بيئة المضيف،\n- قائمة تجاهل للمتغيرات الحساسة للغلاف (`PS1`، `PWD`، `SHLVL`، صادرات دوال bash، إلخ).\n\nسلوك بيئة الجلسة:\n\n- يُطبَّق `ShellOptions.sessionEnv` مرة واحدة عند إنشاء الجلسة.\n- يقتصر `ShellRunOptions.env` على نطاق الأمر (`EnvironmentScope::Command`) ويُزال بعد كل تشغيل.\n- يُدمج `PATH` بشكل خاص على Windows مع إزالة التكرار غير الحساس لحالة الأحرف.\n\nإثراء المسار الخاص بـ Windows فقط (`shell/windows.rs`): تُلحق مسارات Git-for-Windows المكتشفة (`cmd`، `bin`، `usr/bin`) إذا كانت موجودة ولم تُضمَّن مسبقاً.\n\n### دورة حياة وقت التشغيل وانتقالات الحالة\n\nيستخدم الغلاف الدائم (`Shell.run`) آلة الحالة التالية:\n\n- **خامل/غير مُهيأ**: `session: None`.\n- **قيد التشغيل**: أول استدعاء لـ `run()` ينشئ الجلسة بشكل كسول، ويخزن رمز `current_abort`، وينفذ الأمر.\n- **مكتمل + استمرارية**: إذا كان تدفق التحكم في التنفيذ `Normal`، يُمسح `current_abort` وتُعاد استخدام الجلسة.\n- **مكتمل + إنهاء**: إذا كان تدفق التحكم متعلقاً بالحلقة/النص البرمجي/خروج الغلاف (`BreakLoop`، `ContinueLoop`، `ReturnFromFunctionOrScript`، `ExitShell`)، تُسقط الجلسة (`session: None`).\n- **مُلغى/انتهت مهلته**: تُلغى مهمة التشغيل، وانتظار سماح (2 ثانية)، ثم إلغاء قسري؛ وتُسقط الجلسة.\n- **خطأ**: تُسقط الجلسة.\n\nيقوم الغلاف أحادي الاستخدام (`executeShell`) دائماً بإنشاء جلسة جديدة وإسقاطها لكل استدعاء.\n\n### سلوك البث/الإخراج\n\n- يُوجَّه كلٌّ من stdout وstderr إلى أنبوب مشترك ويُقرأان بصورة متزامنة.\n- يفك القارئ ترميز UTF-8 بشكل تدريجي؛ وتُصدر تسلسلات البايت غير الصالحة قطعاً بديلة `U+FFFD`.\n- بعد اكتمال العملية، يخضع استنزاف الإخراج لحراسي خمول وأقصى مدة (`250ms` خمول، `2s` أقصى) لتفادي التوقف بسبب المهام الخلفية التي تبقي واصفات البيانات مفتوحة.\n\n### الإلغاء والمهلة والمهام الخلفية\n\n- يُنشأ `CancelToken` من `timeoutMs` والإشارة الاختيارية `AbortSignal`.\n- عند الإلغاء/انتهاء المهلة، يُشغَّل رمز إلغاء الغلاف، ثم تحصل المهمة على نافذة سماح لمدة ثانيتين قبل الإلغاء القسري.\n- إذا حدث الإلغاء، تُنهى المهام الخلفية (`TERM`، ثم `KILL` بعد تأخير) باستخدام بيانات وظائف brush.\n\nسلوك `Shell.abort()`:\n\n- يُلغي فقط الأمر الجاري تشغيله لتلك النسخة من `Shell`،\n- لا يُنفِّذ شيئاً عند عدم وجود تشغيل نشط.\n\n### سلوك الفشل\n\nتشمل الأخطاء الشائعة التي تظهر:\n\n- فشل تهيئة الجلسة (`Failed to initialize shell`)،\n- أخطاء مجلد العمل الحالي (`Failed to set cwd`)،\n- فشل تعيين/إزالة البيئة،\n- فشل مصدر اللقطة،\n- فشل إنشاء/استنساخ الأنبوب،\n- فشل التنفيذ (`Shell execution failed: ...`)،\n- فشل غلاف المهمة (`Shell execution task failed: ...`).\n\nعلامات الإلغاء على مستوى النتيجة:\n\n- انتهاء المهلة -> `exitCode: undefined`، `timedOut: true`.\n- إشارة الإلغاء -> `exitCode: undefined`، `cancelled: true`.\n\n## النظام الفرعي لـ PTY (`pty`)\n\n### نموذج API\n\nيعرض `new PtySession()`:\n\n- `start(options, onChunk?) -> Promise<{ exitCode?, cancelled, timedOut }>`\n- `write(data)`\n- `resize(cols, rows)`\n- `kill()`\n\n### دورة حياة وقت التشغيل وانتقالات الحالة\n\nآلة حالة `PtySession`:\n\n- **خامل**: `core: None`.\n- **محجوز**: تُثبِّت `start()` قناة التحكم بصورة متزامنة (`core: Some`) قبل بدء العمل غير المتزامن، مما يجعل `write/resize/kill` صالحة فوراً.\n- **قيد التشغيل**: تعالج حلقة PTY المحجوبة حالة العملية الفرعية وأحداث القارئ ونبضات الإلغاء ورسائل التحكم.\n- **مغلق الطرفية**: خروج العملية الفرعية + اكتمال القارئ.\n- **منتهٍ**: يُعاد تعيين `core` إلى `None` دائماً بعد اكتمال مهمة البدء (نجاحاً أو خطأً).\n\nحارس التزامن:\n\n- يُعيد البدء أثناء التشغيل الفعلي `PTY session already running`.\n\n### أنماط الإنشاء/الإرفاق/الكتابة/القراءة/الإنهاء\n\n- يُفتح PTY عبر `portable_pty::native_pty_system().openpty(...)`.\n- يعمل الأمر حالياً كـ `sh -lc <command>` مع تجاوزات اختيارية لـ `cwd` والبيئة.\n- يُرسل `write()` بايتات خام إلى stdin لـ PTY.\n- يُثبِّت `resize()` الأبعاد (`cols 20..400`، `rows 5..200`) ويستدعي تغيير حجم الرئيسي.\n- يُعلِّم `kill()` التشغيل ملغىً ويقتل العملية الفرعية.\n\nمسار الإخراج:\n\n- يقرأ خيط قارئ مخصص دفق الرئيسي،\n- فك ترميز UTF-8 التدريجي مع استبدال `U+FFFD` عند البايتات غير الصالحة،\n- تُعاد توجيه القطع عبر استدعاء N-API الخلفي الآمن للخيوط.\n\n### دلالات الإلغاء والمهلة\n\n- يُغذِّي `timeoutMs` و`AbortSignal` `CancelToken`.\n- تستدعي الحلقة `ct.heartbeat()` دورياً؛ ويُشغِّل الإلغاءُ قتلَ العملية الفرعية.\n- يعتمد تصنيف المهلة على النص (`\"Timeout\"` كجزء من نص خطأ نبضة القلب).\n\n### سلوك الفشل\n\nتشمل أسطح الأخطاء:\n\n- فشل تخصيص/فتح PTY،\n- فشل إنشاء PTY،\n- فشل الحصول على الكاتب/القارئ،\n- فشل حالة/انتظار العملية الفرعية،\n- تسمم القفل،\n- قطع قناة التحكم (`PTY session is no longer available`).\n\nفشل استدعاءات التحكم عند عدم التشغيل:\n\n- تُعيد `write/resize/kill` `PTY session is not running`.\n\n## النظام الفرعي لشجرة العمليات (`ps`)\n\n### نموذج API\n\n- `killTree(pid, signal) -> number`\n- `listDescendants(pid) -> number[]`\n\nكما يُسجِّل غلاف TypeScript تكامل kill-tree الأصلي في الأدوات المشتركة عبر `setNativeKillTree(native.killTree)`.\n\n### التنفيذ الخاص بالمنصة\n\n- **Linux**: يقرأ بشكل تعاودي `/proc/<pid>/task/<pid>/children`.\n- **macOS**: يستخدم `libproc` `proc_listchildpids`.\n- **Windows**: يأخذ لقطة لجدول العمليات بـ `CreateToolhelp32Snapshot`، ويبني خريطة الوالد->الأبناء، وينهي بـ `OpenProcess(PROCESS_TERMINATE)` + `TerminateProcess`.\n\n### سلوك kill-tree\n\n- تُجمع العمليات الفرعية بشكل تعاودي.\n- يكون ترتيب القتل من الأسفل إلى الأعلى (العمليات الفرعية الأعمق أولاً) للحد من إعادة تبني الأيتام.\n- يُقتل pid الجذر أخيراً.\n- تكون قيمة الإرجاع هي عدد الإنهاءات الناجحة.\n\nسلوك الإشارة:\n\n- POSIX: يُمرَّر `signal` المُقدَّم إلى `kill`.\n- Windows: يُتجاهل `signal`؛ والإنهاء هو إنهاء غير مشروط للعملية.\n\n### سلوك الفشل\n\nهذه الوحدة مصممة عمداً لعدم إطلاق أخطاء على مستوى الواجهة:\n\n- تُتخطى فروع شجرة العمليات المفقودة/غير القابلة للوصول،\n- تُحسب إخفاقات قتل pid كل منها كغير ناجحة (وليست أخطاء)،\n- عادةً ما تُنتج فجوة البحث `[]` من `listDescendants` و`0` من `killTree`.\n\n## النظام الفرعي لتحليل المفاتيح (`keys`)\n\n### نموذج API\n\nالمساعدات المعروضة:\n\n- `parseKey(data, kittyProtocolActive)`\n- `matchesKey(data, keyId, kittyProtocolActive)`\n- `parseKittySequence(data)`\n- `matchesKittySequence(data, expectedCodepoint, expectedModifier)`\n- `matchesLegacySequence(data, keyName)`\n\n### نموذج التحليل\n\nيجمع المحلل:\n\n- تعيينات مباشرة أحادية البايت (`enter`، `tab`، `ctrl+<letter>`، ASCII قابل للطباعة)،\n- بحث O(1) في تسلسل الهروب القديم (خريطة PHF)،\n- تحليل `modifyOtherKeys` لـ xterm،\n- تحليل بروتوكول Kitty (`CSI u`، `CSI ~`، `CSI 1;...<letter>`)،\n- تطبيع إلى معرِّفات المفاتيح (`ctrl+c`، `shift+tab`، `pageUp`، `f5`، إلخ).\n\nمعالجة المُعدِّل:\n\n- تُقارَن فقط بتات shift/alt/ctrl لمطابقة المفاتيح،\n- تُقنَّع بتات القفل قبل المقارنات.\n\nسلوك التخطيط:\n\n- يُقيَّد الرجوع إلى التخطيط الأساسي عمداً حتى لا تُنشئ التخطيطات المعاد تعيينها تطابقات زائفة للأحرف/الرموز ASCII.\n\n### سلوك الفشل\n\n- تُنتج التسلسلات غير المعروفة أو غير الصالحة `null` من دوال التحليل.\n- تُعيد دوال المطابقة `false` عند فشل التحليل أو عدم التطابق.\n- لا يوجد سطح خطأ مُطلَق لإدخال المفاتيح المشوه.\n\n## تعيين واجهة JS للغلاف ↔ صادرات Rust\n\n### الغلاف + PTY + العملية\n\n| واجهة غلاف TypeScript | صادر Rust N-API | ملاحظات |\n|---|---|---|\n| `executeShell(options, onChunk?)` | `executeShell` (`execute_shell`) | تنفيذ غلاف أحادي الاستخدام |\n| `new Shell(options?)` | فئة `Shell` | جلسة غلاف دائمة |\n| `shell.run(options, onChunk?)` | `Shell::run` | يُعيد استخدام الجلسة عند تدفق التحكم باستمرارية |\n| `shell.abort()` | `Shell::abort` | يُلغي التشغيل النشط لتلك النسخة من الغلاف |\n| `new PtySession()` | فئة `PtySession` | جلسة PTY ذات حالة |\n| `pty.start(options, onChunk?)` | `PtySession::start` | تشغيل PTY تفاعلي |\n| `pty.write(data)` | `PtySession::write` | تمرير مباشر لـ stdin الخام |\n| `pty.resize(cols, rows)` | `PtySession::resize` | أبعاد طرفية محدودة |\n| `pty.kill()` | `PtySession::kill` | قتل قسري للعملية الفرعية النشطة في PTY |\n| `killTree(pid, signal)` | `killTree` (`kill_tree`) | إنهاء شجرة العمليات بدءاً من الأبناء |\n| `listDescendants(pid)` | `listDescendants` (`list_descendants`) | قائمة تعاودية بالعمليات الفرعية |\n\n### المفاتيح\n\n| واجهة غلاف TypeScript | صادر Rust N-API | ملاحظات |\n|---|---|---|\n| `matchesKittySequence(data, cp, mod)` | `matchesKittySequence` (`matches_kitty_sequence`) | مطابقة codepoint+modifier لـ Kitty |\n| `parseKey(data, kittyProtocolActive)` | `parseKey` (`parse_key`) | محلل معرِّف المفتاح المُطبَّع |\n| `matchesLegacySequence(data, keyName)` | `matchesLegacySequence` (`matches_legacy_sequence`) | فحص خريطة التسلسل القديم الدقيق |\n| `parseKittySequence(data)` | `parseKittySequence` (`parse_kitty_sequence`) | نتيجة تحليل Kitty المهيكلة |\n| `matchesKey(data, keyId, kittyProtocolActive)` | `matchesKey` (`matches_key`) | مطابقة مفاتيح عالية المستوى |\n\n## ملاحظات تنظيف الجلسات المهجورة والإنهاء\n\n- **جلسة الغلاف الدائمة**: إذا أُلغي تشغيل ما أو انتهت مهلته أو أخفق أو كان تدفق التحكم من غير النوع الاستمراري، يُسقط Rust صراحةً حالة الجلسة الداخلية. تحتفظ عمليات التشغيل العادية الناجحة بالجلسة لإعادة الاستخدام.\n- **جلسة PTY**: يُمسح `core` دائماً بعد انتهاء `start()`، بما في ذلك مسارات الفشل.\n- **لا يُعرض عقد قتل صريح مدفوع بمُنهي JS** من قِبل الأغلفة؛ يرتبط التنظيف أساساً بمسارات اكتمال التشغيل/الإلغاء. يجب على المستدعين استخدام `timeoutMs` أو `AbortSignal` أو `shell.abort()` أو `pty.kill()` للإنهاء الحتمي.\n",
	"ar/natives/natives-text-search-pipeline.md": "---\ntitle: خط أنابيب النص والبحث الأصلي\ndescription: >-\n  خط أنابيب البحث النصي الأصلي مع فهرسة محتوى الملفات المستندة إلى grep وglob\n  وripgrep.\nsidebar:\n  order: 6\n  label: خط أنابيب النص والبحث\ni18n:\n  sourceHash: 0e93462fdd12\n  translator: machine\n---\n\n# خط أنابيب النص/البحث الأصلي\n\nتُعيّن هذه الوثيقة السطح النصي/البحثي لـ `@f5-sales-demo/pi-natives` (`grep`، و`glob`، و`text`، و`highlight`) من أغلفة TypeScript إلى صادرات Rust N-API وعودةً إلى كائنات النتائج في JS.\n\nتتبع المصطلحات ملف `docs/natives-architecture.md`:\n\n- **الغلاف**: واجهة برمجة TS في `packages/natives/src/*`\n- **طبقة وحدة Rust**: صادرات N-API في `crates/pi-natives/src/*`\n- **ذاكرة تخزين الفحص المشتركة**: ذاكرة تخزين مدخلات المجلدات المدعومة بـ `fs_cache` والمستخدمة في تدفقات الاكتشاف/البحث\n\n## ملفات التنفيذ\n\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/glob_util.rs`\n- `crates/pi-natives/src/fs_cache.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/highlight.rs`\n- `crates/pi-natives/src/fd.rs`\n\n## تعيين واجهة برمجة JS ↔ صادرات Rust\n\n| واجهة برمجة الغلاف JS | صادر Rust (`#[napi]`، snake_case -> camelCase) | وحدة Rust |\n| --- | --- | --- |\n| `grep(options, onMatch?)` | `grep` | `grep.rs` |\n| `searchContent(content, options)` | `search` | `grep.rs` |\n| `hasMatch(content, pattern, options?)` | `hasMatch` | `grep.rs` |\n| `fuzzyFind(options)` | `fuzzyFind` | `fd.rs` |\n| `glob(options, onMatch?)` | `glob` | `glob.rs` |\n| `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `fs_cache.rs` |\n| `wrapTextWithAnsi(text, width)` | `wrapTextWithAnsi` | `text.rs` |\n| `truncateToWidth(text, maxWidth, ellipsis, pad)` | `truncateToWidth` | `text.rs` |\n| `sliceWithWidth(line, startCol, length, strict?)` | `sliceWithWidth` | `text.rs` |\n| `extractSegments(line, beforeEnd, afterStart, afterLen, strictAfter)` | `extractSegments` | `text.rs` |\n| `sanitizeText(text)` | `sanitizeText` | `text.rs` |\n| `visibleWidth(text)` | `visibleWidth` | `text.rs` |\n| `highlightCode(code, lang, colors)` | `highlightCode` | `highlight.rs` |\n| `supportsLanguage(lang)` | `supportsLanguage` | `highlight.rs` |\n| `getSupportedLanguages()` | `getSupportedLanguages` | `highlight.rs` |\n\n## نظرة عامة على خط الأنابيب حسب النظام الفرعي\n\n## 1) البحث بالتعبيرات النمطية (`grep`، و`searchContent`، و`hasMatch`)\n\n### تدفق المدخلات/الخيارات\n\n1. يُمرر الغلاف TS الخيارات إلى الوحدة الأصلية:\n   - يُمرر `grep/index.ts` الخيارات `options` دون تغيير إلى حد بعيد، ويُغلّف رد النداء من الشكل `(match) => void` إلى شكل رد نداء napi الآمن في الخيوط `(err, match)`.\n   - يُمرر `searchContent` و`hasMatch` النص/`Uint8Array` مباشرةً.\n2. تُفسّر هياكل خيارات Rust في `grep.rs` حقول camelCase (`ignoreCase`، و`maxCount`، و`contextBefore`، و`contextAfter`، و`maxColumns`، و`timeoutMs`).\n3. يُنشئ `grep` رمز `CancelToken` من `timeoutMs` + `AbortSignal` وينفذ داخل `task::blocking(\"grep\", ...)`.\n\n### فروع التنفيذ\n\n- **فرع الذاكرة (أداة مساعدة خالصة)**\n  - `search` ← `search_sync` ← `run_search` على بايتات المحتوى المقدمة.\n  - لا فحص لنظام الملفات، ولا `fs_cache`.\n- **فرع الملف الواحد (يعتمد على نظام الملفات)**\n  - يحل `grep_sync` المسار، ويتحقق من أن البيانات الوصفية تخص ملفاً، ثم يبث ما يصل إلى `MAX_FILE_BYTES` لكل ملف (`4 MiB`) عبر مطابق ripgrep.\n- **فرع المجلد (يعتمد على نظام الملفات)**\n  - البحث الاختياري في ذاكرة التخزين المؤقت عبر `fs_cache::get_or_scan` عند تعيين `cache: true`.\n  - إجراء فحص جديد عبر `fs_cache::force_rescan` عند تعيين `cache: false`.\n  - إعادة التحقق الاختيارية من النتائج الفارغة عند تجاوز عمر ذاكرة التخزين المؤقت للقيمة `empty_recheck_ms()`.\n  - تصفية المدخلات: ملفات فقط + تصفية glob اختيارية (`glob_util`) + تصفية النوع الاختيارية (`js`، `ts`، `rust`، إلخ).\n\n### دلالات البحث والتجميع\n\n- محرك التعبيرات النمطية: `grep_regex::RegexMatcherBuilder` مع `ignoreCase` و`multiline`.\n- حل السياق:\n  - تتجاوز `contextBefore/contextAfter` الخيار القديم `context`.\n  - تُصفّر أوضاع غير المحتوى مجموعة السياق.\n- أوضاع الإخراج:\n  - `content` => مدخل `GrepMatch` واحد لكل تطابق.\n  - يُعيّن كل من `count` و`filesWithMatches` إلى مدخلات نمط العدد (`lineNumber=0`، `line=\"\"`، يُعيَّن `matchCount`).\n- الحدود:\n  - يُطبَّق الإزاحة العالمية `offset` و`maxCount` عبر الملفات.\n  - يُستخدم المسار المتوازي فقط عند عدم تعيين `maxCount` وكون `offset == 0`؛ وإلا يُستخدم المسار التسلسلي للحفاظ على دلالات الإزاحة/الحد العالمية الحتمية.\n\n### تشكيل النتائج وإعادتها إلى JS\n\n- تُعيَّن حقول `SearchResult`/`GrepResult` في Rust إلى أنواع TS عبر تحويل حقول كائنات N-API.\n- تُقيَّد العدادات إلى `u32` قبل عبور N-API.\n- تُحذف القيم المنطقية الاختيارية ما لم تكن صحيحة في بعض المسارات (`limitReached`).\n- يتلقى رد نداء البث كل مدخل `GrepMatch` مُشكَّل (محتوى أو مدخل عدد).\n\n### سلوك الفشل\n\n- يُعيد `searchContent` قيمة `SearchResult.error` عند فشل التعبير النمطي/البحث عوضاً عن الرمي بخطأ.\n- يرفض `grep` عند الأخطاء الحادة (مسار غير صالح، glob/تعبير نمطي غير صالح، انقضاء المهلة/الإلغاء).\n- يُعيد `hasMatch` قيمة `Result<bool>` ويرمي عند أنماط غير صالحة أو أخطاء فك ترميز UTF-8.\n- تُتخطى أخطاء فتح/بحث الملفات في فحوصات الملفات المتعددة لكل ملف على حدة؛ ويستمر الفحص.\n\n### معالجة التعبيرات النمطية المشوهة\n\nيُعالج `grep.rs` الأقواس المعقوصة قبل تصريف التعبير النمطي:\n\n- تُهرَّب الأقواس المعقوصة الشبيهة بالتكرار غير الصالح (`{`/`}` -> `\\{`/`\\}`) عندما لا يمكنها تشكيل `{N}`، أو `{N,}`، أو `{N,M}`.\n- يمنع ذلك مقاطع قوالب الحروف الشائعة (مثل `${platform}`) من الفشل كتكرار مشوه.\n- لا تزال بنية التعبير النمطي غير الصالحة المتبقية تُعيد خطأ في التعبير النمطي.\n\n## 2) اكتشاف الملفات (`glob`) والبحث الضبابي في المسارات (`fuzzyFind`)\n\nيتشارك `glob` و`fuzzyFind` فحوصات `fs_cache`؛ بينما تختلف منطق المطابقة.\n\n### تدفق `glob`\n\n1. الغلاف TS (`glob/index.ts`):\n   - `path.resolve(options.path)`.\n   - القيم الافتراضية: `pattern=\"*\"`، و`hidden=false`، و`gitignore=true`، و`recursive=true`.\n2. يبني Rust `glob` الكائن `GlobConfig` ويصرّف النمط عبر `glob_util::compile_glob`.\n3. مصدر المدخلات:\n   - `cache=true` => `get_or_scan` + إعادة فحص اختيارية للنتائج القديمة-الفارغة.\n   - `cache=false` => `force_rescan(..., store=false)` (جديد فقط).\n4. التصفية:\n   - تخطي `.git` دائماً.\n   - تخطي `node_modules` ما لم يُطلب ذلك (`includeNodeModules` أو نمط يذكر node_modules).\n   - تطبيق مطابقة glob.\n   - تطبيق تصفية نوع الملف؛ تحل فلاتر `file/dir` للروابط الرمزية البيانات الوصفية للهدف.\n5. الترتيب الاختياري حسب وقت التعديل تنازلياً (`sortByMtime`) قبل الاقتصار على `maxResults`.\n\n### تدفق `fuzzyFind` (مُنفَّذ في `fd.rs`)\n\n1. يُصدَّر الغلاف TS من وحدة `grep`، لكن التنفيذ في Rust يقع في `fd.rs`.\n2. مصدر الفحص المشترك من `fs_cache` مع نفس منطق تقسيم ذاكرة التخزين المؤقت/بدونها وسياسة إعادة التحقق من الفارغ القديم.\n3. التسجيل:\n   - درجة مبنية على: التطابق التام / يبدأ بـ / يحتوي على / التسلسل الضبابي\n   - مسار تسجيل مُعيَّر بالفواصل وعلامات الترقيم\n   - مكافأة المجلد وكسر التعادل الحتمي (`score desc`، ثم `path asc`)\n4. تُستثنى مدخلات الروابط الرمزية من نتائج البحث الضبابي.\n\n### سلوك الفشل\n\n- نمط glob غير صالح => خطأ من `glob_util::compile_glob`.\n- يجب أن يكون جذر البحث مجلداً موجوداً (`resolve_search_path`)، وإلا حدث خطأ.\n- تنتشر الإلغاءات/انتهاءات المهل كأخطاء إجهاض عبر فحوصات `CancelToken::heartbeat()` في الحلقات.\n\n### معالجة أنماط glob المشوهة\n\n`glob_util::build_glob_pattern` متسامحة:\n\n- تُعيَّر `\\` إلى `/`.\n- تُضاف تلقائياً `**/` بادئةً للأنماط التكرارية البسيطة عند تعيين `recursive=true`.\n- تُغلق تلقائياً مجموعات التناوب `{...` غير المتوازنة قبل التصريف.\n\n## 3) دورة حياة الفحص/التخزين المشترك (`fs_cache`)\n\nيخزن `fs_cache` نتائج الفحص كمدخلات نسبية مُعيَّرة (`path`، و`fileType`، و`mtime` الاختيارية) مُفهرَسة بـ:\n\n- جذر البحث القانوني\n- `include_hidden`\n- `use_gitignore`\n\n### انتقالات حالة ذاكرة التخزين المؤقت\n\n1. **إخفاق / معطل**\n   - مدة الصلاحية `0` أو المفتاح غائب/منتهي الصلاحية -> `collect_entries` جديدة.\n2. **إصابة**\n   - عمر المدخل `< cache_ttl_ms()` -> إعادة المدخلات المخزنة + `cache_age_ms`.\n3. **إعادة التحقق من القديم-الفارغ** (سياسة الاستدعاء في `glob`/`grep`/`fd`)\n   - إذا أسفر الاستعلام عن صفر تطابقات وكان `cache_age_ms >= empty_recheck_ms()`، فأجرِ إعادة فحص واحدة.\n4. **الإبطال**\n   - `invalidateFsScanCache(path?)`:\n     - بدون وسيطة: مسح جميع المفاتيح\n     - مع وسيطة مسار: إزالة المفاتيح التي تتضمن جذورها بادئة مسار الهدف\n\n### مقايضة النتائج القديمة\n\n- تُفضّل ذاكرة التخزين المؤقت الفحوصات المتكررة المنخفضة الاستجابة على الاتساق الفوري.\n- يمكن أن تُعيد نافذة مدة الصلاحية نتائج إيجابية/سلبية قديمة.\n- تقلل إعادة التحقق من الفارغ من النتائج السلبية القديمة للفحوصات المخزنة الأقدم، بتكلفة فحص إضافي واحد.\n- الإبطال الصريح هو الخطاف المقصود للصحة بعد تعديلات الملفات.\n\n## 4) أدوات النص ANSI (`text`)\n\nهذه أدوات مساعدة خالصة في الذاكرة (بدون فحص لنظام الملفات).\n\n### الحدود والمسؤوليات\n\n- **يمتلك `text.rs` دلالات خلايا الطرفية**:\n  - تحليل تسلسلات ANSI\n  - العرض المدرك للمخططات البيانية والتقطيع\n  - سلوك الالتفاف/القطع/التعقيم\n- **قطع الأسطر في `grep.rs` (`maxColumns`) منفصلة**:\n  - قطع بسيط لحدود الأحرف للأسطر المطابقة مع `...`\n  - غير حافظة لحالة ANSI وغير مدركة لعرض خلايا الطرفية\n\n### السلوكيات الرئيسية\n\n- `wrapTextWithAnsi`: يلتف حسب العرض المرئي، ويحمل رموز SGR النشطة عبر الأسطر الملتفة.\n- `truncateToWidth`: قطع بالخلايا المرئية مع سياسة علامة الحذف (`Unicode`، و`Ascii`، و`Omit`)، وحشو أيمن اختياري، ومسار سريع يُعيد سلسلة JS الأصلية عند عدم التغيير.\n- `sliceWithWidth`: تقطيع الأعمدة مع تطبيق عرض صارم اختياري.\n- `extractSegments`: يستخرج المقاطع قبل/بعد تراكب مع استعادة حالة ANSI لمقطع `after`.\n- `sanitizeText`: يجرد هروبات ANSI + أحرف التحكم، ويتخلص من الوكلاء المنعزلين، ويُعيَّر CR/LF بإزالة `\\r`.\n- `visibleWidth`: يحسب خلايا الطرفية المرئية (تستخدم علامات التبويب `TAB_WIDTH` الثابتة من تنفيذ Rust).\n\n### سلوك الفشل\n\nتُعيد دوال النص عموماً مخرجات محولة حتمية؛ تقتصر الأخطاء على حدود تحويل سلاسل JS (فشل تحويلات وسيطات N-API).\n\n## 5) إبراز بنية الشيفرة (`highlight`)\n\n`highlight.rs` تحويل خالص (بدون نظام ملفات، بدون ذاكرة تخزين مؤقت).\n\n### التدفق\n\n1. يُمرر الغلاف `code`، و`lang` الاختيارية، ولوحة ألوان ANSI.\n2. يحل Rust البنية بـ:\n   - البحث بالرمز/الاسم\n   - البحث بالامتداد\n   - جدول الأسماء المستعارة الاحتياطي (`ts/tsx/js -> JavaScript`، إلخ)\n   - الرجوع إلى بنية النص العادي عند عدم الحل\n3. تحليل كل سطر بـ `ParseState` syntect ومكدس النطاق.\n4. تعيين النطاقات إلى 11 فئة لون دلالية وحقن/إعادة تعيين رموز ألوان ANSI.\n\n### سلوك الفشل\n\n- فشل تحليل السطر لا يُفشل الاستدعاء: يُضاف ذلك السطر دون إبراز ويستمر المعالجة.\n- اللغة المجهولة/غير المدعومة تنتقل إلى بنية النص العادي.\n\n## تدفقات الأداة المساعدة الخالصة مقابل المعتمدة على نظام الملفات\n\n| التدفق | الوصول إلى نظام الملفات | ذاكرة التخزين المشتركة | ملاحظات |\n| --- | --- | --- | --- |\n| `searchContent` / `hasMatch` | لا | لا | تعبير نمطي على البايتات/النص المقدم فقط |\n| دوال وحدة `text` | لا | لا | معالجة ANSI/العرض/التعقيم فقط |\n| دوال وحدة `highlight` | لا | لا | البنية + تلوين ANSI فقط |\n| `glob` | نعم | اختيارية | فحوصات المجلدات + تصفية glob |\n| `fuzzyFind` | نعم | اختيارية | فحوصات المجلدات + التسجيل الضبابي |\n| `grep` (مسار ملف/مجلد) | نعم | اختيارية (وضع المجلد) | ripgrep على الملفات، مع فلاتر/رد نداء اختيارية |\n\n## ملخص دورة الحياة الشاملة\n\n1. يستدعي المُستدعي الغلاف TS مع خيارات مُكتَّبة.\n2. يُعيَّر الغلاف القيم الافتراضية (ولا سيما `glob`) ويُمررها إلى صادر `native.*`.\n3. تُتحقق Rust/تُعيَّر الخيارات وتبني المُطابق/تهيئة البحث.\n4. بالنسبة لتدفقات نظام الملفات، تُفحص المدخلات (إصابة/إخفاق/إعادة فحص في ذاكرة التخزين المؤقت) ثم تُصفى/تُسجَّل.\n5. تستدعي حلقات العمال دورياً نبضة قلب الإلغاء؛ يمكن أن تُنهي انتهاءات المهل/الإلغاءات التنفيذ.\n6. تُشكّل Rust المخرجات إلى كائنات N-API (`lineNumber`، و`matchCount`، و`limitReached`، إلخ).\n7. يُعيد الغلاف TS كائنات JS مُكتَّبة (وردود نداء اختيارية لكل تطابق لـ `grep`/`glob`).\n",
	"ar/natives/porting-to-natives.md": "---\ntitle: الترحيل إلى pi-natives (N-API) — ملاحظات ميدانية\ndescription: >-\n  ملاحظات ميدانية لترحيل كود child_process وshell في Node.js إلى طبقة N-API\n  الأصلية في Rust.\nsidebar:\n  order: 9\n  label: الترحيل إلى pi-natives\ni18n:\n  sourceHash: 4f5150286535\n  translator: machine\n---\n\n# الترحيل إلى pi-natives (N-API) — ملاحظات ميدانية\n\nهذا دليل عملي لنقل المسارات الحرجة إلى `crates/pi-natives` وربطها من خلال روابط JS. وُجد هذا الدليل لتجنب تكرار نفس الأخطاء.\n\n## متى يجب الترحيل\n\nقم بالترحيل عندما تكون أي من هذه الحالات صحيحة:\n\n- المسار الحرج يعمل في حلقات العرض، أو تحديثات واجهة المستخدم المتكررة، أو الدفعات الكبيرة.\n- تخصيصات JS هي المهيمنة (تكرار السلاسل النصية، التراجع في التعبيرات النمطية، المصفوفات الكبيرة).\n- لديك بالفعل خط أساس في JS ويمكنك قياس أداء كلا الإصدارين جنبًا إلى جنب.\n- العمل مرتبط بوحدة المعالجة المركزية أو إدخال/إخراج حاجب يمكن تشغيله على مجموعة خيوط libuv.\n- العمل هو إدخال/إخراج غير متزامن يمكن تشغيله على وقت تشغيل Tokio (مثل تنفيذ الصدفة).\n\nتجنب الترحيلات التي تعتمد على حالة خاصة بـ JS فقط أو الاستيرادات الديناميكية. يجب أن تكون صادرات N-API صافية، بيانات داخلة/بيانات خارجة. العمل طويل الأمد يجب أن يمر عبر `task::blocking` (مرتبط بوحدة المعالجة المركزية/إدخال وإخراج حاجب) أو `task::future` (إدخال/إخراج غير متزامن) مع إمكانية الإلغاء.\n\n## تشريح تصدير أصلي\n\n**جانب Rust:**\n\n- التنفيذ يوجد في `crates/pi-natives/src/<module>.rs`. إذا أضفت وحدة جديدة، سجّلها في `crates/pi-natives/src/lib.rs`.\n- صدّر باستخدام `#[napi]`؛ يتم تحويل الصادرات بصيغة snake_case إلى camelCase تلقائيًا. استخدم `js_name` صريحًا فقط للأسماء المستعارة الحقيقية/الأسماء غير الافتراضية. استخدم `#[napi(object)]` للهياكل.\n- استخدم `task::blocking(tag, cancel_token, work)` (انظر `crates/pi-natives/src/task.rs`) للعمل المرتبط بوحدة المعالجة المركزية أو الحاجب. استخدم `task::future(env, tag, work)` للعمل غير المتزامن الذي يحتاج Tokio (مثل جلسات الصدفة). مرّر `CancelToken` عندما تعرض `timeoutMs` أو `AbortSignal`.\n\n**جانب JS:**\n\n- `packages/natives/src/bindings.ts` يحتوي على واجهة `NativeBindings` الأساسية.\n- `packages/natives/src/<module>/types.ts` يعرّف أنواع TS ويوسّع `NativeBindings` عبر دمج التصريحات.\n- `packages/natives/src/native.ts` يستورد كل ملف `<module>/types.ts` لتفعيل التصريحات.\n- `packages/natives/src/<module>/index.ts` يغلّف ربط `native` من `packages/natives/src/native.ts`.\n- `packages/natives/src/native.ts` يحمّل الإضافة و`validateNative` يفرض الصادرات المطلوبة.\n- `packages/natives/src/index.ts` يعيد تصدير المغلّف للمستدعين في `packages/*`.\n\n## قائمة مراجعة الترحيل\n\n1. **أضف تنفيذ Rust**\n\n- ضع المنطق الأساسي في دالة Rust عادية.\n- إذا كانت وحدة جديدة، أضفها إلى `crates/pi-natives/src/lib.rs`.\n- اعرضها باستخدام `#[napi]` حتى يبقى التحويل الافتراضي من snake_case إلى camelCase متسقًا.\n- حافظ على التوقيعات مملوكة وبسيطة: `String`، `Vec<String>`، `Uint8Array`، أو `Either<JsString, Uint8Array>` للمدخلات الكبيرة من السلاسل النصية/البايتات.\n- للعمل المرتبط بوحدة المعالجة المركزية أو الحاجب، استخدم `task::blocking`؛ للعمل غير المتزامن، استخدم `task::future`. مرّر `CancelToken` واستدعِ `heartbeat()` داخل الحلقات الطويلة.\n\n2. **اربط روابط JS**\n\n- أضف الأنواع وتوسيع `NativeBindings` في `packages/natives/src/<module>/types.ts`.\n- استورد `./<module>/types` في `packages/natives/src/native.ts` لتفعيل دمج التصريحات.\n- أضف مغلّفًا في `packages/natives/src/<module>/index.ts` يستدعي `native`.\n- أعد التصدير من `packages/natives/src/index.ts`.\n\n3. **حدّث التحقق من الأصلي**\n\n- أضف `checkFn(\"newExport\")` في `validateNative` (`packages/natives/src/native.ts`).\n\n4. **أضف اختبارات الأداء**\n\n- ضع اختبارات الأداء بجوار الحزمة المالكة (`packages/tui/bench`، `packages/natives/bench`، أو `packages/coding-agent/bench`).\n- اشمل خط الأساس في JS والنسخة الأصلية في نفس التشغيل.\n- استخدم `Bun.nanoseconds()` وعدد تكرارات ثابت.\n- حافظ على مدخلات اختبار الأداء صغيرة وواقعية (بيانات فعلية مشاهدة في المسار الحرج).\n\n5. **ابنِ الملف الثنائي الأصلي**\n\n- `bun --cwd=packages/natives run build`\n- استخدم `bun --cwd=packages/natives run build` واضبط `PI_DEV=1` إذا أردت تشخيصات المحمّل أثناء الاختبار.\n\n6. **شغّل اختبار الأداء**\n\n- `bun run packages/<pkg>/bench/<bench>.ts` (أو `bun --cwd=packages/natives run bench`)\n\n7. **قرّر بشأن الاستخدام**\n\n- إذا كان الأصلي أبطأ، **أبقِ على JS** واترك التصدير الأصلي غير مستخدم.\n- إذا كان الأصلي أسرع، بدّل مواقع الاستدعاء إلى المغلّف الأصلي.\n\n## نقاط الألم وكيفية تجنبها\n\n### 1) ملف `pi_natives.node` القديم يمنع الصادرات الجديدة\n\nيفضّل المحمّل الملف الثنائي الموسوم بالمنصة في `packages/natives/native` (`pi_natives.<platform>-<arch>.node`). `PI_DEV=1` الآن يُفعّل تشخيصات المحمّل فقط؛ لم يعد يتحول إلى اسم ملف إضافة تطوير منفصل. يوجد أيضًا ملف احتياطي `pi_natives.node`. الملفات الثنائية المُجمّعة تُستخرج إلى `~/.xcsh/natives/<version>/pi_natives.<platform>-<arch>.node`. إذا كان أي من هذه قديمًا، فلن تُحدَّث الصادرات.\n\n**الحل:** احذف الملف القديم قبل إعادة البناء.\n\n```bash\nrm packages/natives/native/pi_natives.linux-x64.node\nrm packages/natives/native/pi_natives.node\nbun --cwd=packages/natives run build\n```\n\nإذا كنت تشغّل ملفًا ثنائيًا مُجمّعًا، احذف مجلد الإضافة المُخزّن مؤقتًا:\n\n```bash\nrm -rf ~/.xcsh/natives/<version>\n```\n\nثم تحقق من وجود التصدير في الملف الثنائي:\n\n```bash\nbun -e 'const tag = `${process.platform}-${process.arch}`; const mod = require(`./packages/natives/native/pi_natives.${tag}.node`); console.log(Object.keys(mod).includes(\"newExport\"));'\n```\n\n### 2) أخطاء \"الصادرات المفقودة\" من `validateNative`\n\nهذا **جيد** — يمنع عدم التطابق الصامت. عندما ترى هذا:\n\n```\nNative addon missing exports ... Missing: visibleWidth\n```\n\nفهذا يعني أن ملفك الثنائي قديم، أو أن اسم تصدير Rust (أو الاسم المستعار الصريح عند استخدامه) لا يتطابق مع اسم JS، أو أن التصدير لم يُجمّع أبدًا. أصلح البناء وعدم تطابق التسمية، لا تُضعف التحقق.\n\n### 3) عدم تطابق توقيع Rust\n\nحافظ على البساطة والملكية. `String`، `Vec<String>`، و`Uint8Array` تعمل. تجنب المراجع مثل `&str` في الصادرات العامة. إذا كنت تحتاج بيانات منظمة، غلّفها في هياكل `#[napi(object)]`.\n\n### 4) أخطاء اختبار الأداء\n\n- لا تقارن مدخلات أو تخصيصات مختلفة.\n- حافظ على استخدام JS والأصلي لمصفوفات مدخلات متطابقة.\n- شغّل كليهما في نفس ملف اختبار الأداء لتجنب الانحراف.\n\n## قالب اختبار الأداء\n\n```ts\nconst ITERATIONS = 2000;\n\nfunction bench(name: string, fn: () => void): number {\n const start = Bun.nanoseconds();\n for (let i = 0; i < ITERATIONS; i++) fn();\n const elapsed = (Bun.nanoseconds() - start) / 1e6;\n console.log(`${name}: ${elapsed.toFixed(2)}ms total (${(elapsed / ITERATIONS).toFixed(6)}ms/op)`);\n return elapsed;\n}\n\nbench(\"feature/js\", () => {\n jsImpl(sample);\n});\n\nbench(\"feature/native\", () => {\n nativeImpl(sample);\n});\n```\n\n## قائمة مراجعة التحقق\n\n- `validateNative` ينجح (لا صادرات مفقودة).\n- `NativeBindings` مُوسَّعة في `packages/natives/src/<module>/types.ts` والمغلّف مُعاد تصديره في `packages/natives/src/index.ts`.\n- `Object.keys(require(...))` يتضمن تصديرك الجديد.\n- أرقام اختبار الأداء مُسجّلة في طلب السحب/الملاحظات.\n- موقع الاستدعاء مُحدَّث **فقط إذا** كان الأصلي أسرع أو مساويًا.\n\n## القاعدة العامة\n\n- إذا كان الأصلي أبطأ، **لا تبدّل**. أبقِ على التصدير للعمل المستقبلي، لكن واجهة المستخدم الطرفية يجب أن تبقى على المسار الأسرع.\n- إذا كان الأصلي أسرع، بدّل موقع الاستدعاء وأبقِ على اختبار الأداء في مكانه لاكتشاف التراجعات.\n",
	"ar/providers/models.md": "---\ntitle: تكوين النماذج والموفرين\ndescription: >-\n  سجل النماذج وتكوين الموفرين عبر models.yml مع التوجيه والتحويل الاحتياطي\n  والتسعير.\nsidebar:\n  order: 1\n  label: النماذج والموفرون\ni18n:\n  sourceHash: 8053df967ff6\n  translator: machine\n---\n\n# تكوين النماذج والموفرين (`models.yml`)\n\nيصف هذا المستند كيفية تحميل وكيل الترميز للنماذج حالياً، وتطبيق التجاوزات، وحل بيانات الاعتماد، واختيار النماذج في وقت التشغيل.\n\n## ما يتحكم في سلوك النماذج\n\nملفات التنفيذ الأساسية:\n\n- `src/config/model-registry.ts` — يحمّل النماذج المدمجة والمخصصة، وتجاوزات الموفر، والاكتشاف في وقت التشغيل، وتكامل المصادقة\n- `src/config/model-resolver.ts` — يحلّل أنماط النماذج ويختار النماذج الأولية/المصغّرة/البطيئة\n- `src/config/settings-schema.ts` — الإعدادات المتعلقة بالنماذج (`modelRoles`، وتفضيلات نقل الموفر)\n- `src/session/auth-storage.ts` — ترتيب حل مفتاح API ومصادقة OAuth\n- `packages/ai/src/models.ts` و`packages/ai/src/types.ts` — الموفرون/النماذج المدمجون وأنواع `Model`/`compat`\n\n## موقع ملف التكوين والسلوك القديم\n\nمسار التكوين الافتراضي:\n\n- `~/.xcsh/agent/models.yml`\n\nالسلوك القديم لا يزال موجوداً:\n\n- إذا كان `models.yml` مفقوداً وكان `models.json` موجوداً في نفس الموقع، يتم ترحيله إلى `models.yml`.\n- مسارات التكوين الصريحة بصيغة `.json` / `.jsonc` لا تزال مدعومة عند تمريرها برمجياً إلى `ModelRegistry`.\n\n## شكل `models.yml`\n\n```yaml\nconfigVersion: 1  # اختياري — يُكتب بواسطة الضبط التلقائي، يُستخدم لاكتشاف الترحيل\nproviders:\n  <provider-id>:\n    # تكوين على مستوى الموفر\nequivalence:\n  overrides:\n    <provider-id>/<model-id>: <canonical-model-id>\n  exclude:\n    - <provider-id>/<model-id>\n```\n\n`configVersion` هو عدد صحيح اختياري يُكتب بواسطة نظام الضبط التلقائي. عند وجوده، يستخدمه xcsh لاكتشاف التكوينات القديمة وترقيتها تلقائياً.\n\n`provider-id` هو مفتاح الموفر المعياري المستخدم عبر الاختيار وبحث المصادقة.\n\n`equivalence` اختياري ويُكوّن تجميع النماذج المعيارية فوق نماذج الموفر الملموسة:\n\n- `overrides` يعيّن محدداً ملموساً دقيقاً (`provider/modelId`) إلى معرّف معياري رسمي من المصدر الأعلى\n- `exclude` يُخرج محدداً ملموساً من التجميع المعياري\n\n## حقول مستوى الموفر\n\n```yaml\nproviders:\n  my-provider:\n    baseUrl: https://api.example.com/v1\n    apiKey: MY_PROVIDER_API_KEY\n    api: openai-completions\n    headers:\n      X-Team: platform\n    authHeader: true\n    auth: apiKey\n    discovery:\n      type: ollama\n    modelOverrides:\n      some-model-id:\n        name: Renamed model\n    models:\n      - id: some-model-id\n        name: Some Model\n        api: openai-completions\n        reasoning: false\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 128000\n        maxTokens: 16384\n        headers:\n          X-Model: value\n        compat:\n          supportsStore: true\n          supportsDeveloperRole: true\n          supportsReasoningEffort: true\n          maxTokensField: max_completion_tokens\n          openRouterRouting:\n            only: [anthropic]\n          vercelGatewayRouting:\n            order: [anthropic, openai]\n          extraBody:\n            gateway: m1-01\n            controller: mlx\n```\n\n### قيم `api` المسموح بها للموفر/النموذج\n\n- `openai-completions`\n- `openai-responses`\n- `openai-codex-responses`\n- `azure-openai-responses`\n- `anthropic-messages`\n- `google-generative-ai`\n- `google-vertex`\n\n### قيم المصادقة/الاكتشاف المسموح بها\n\n- `auth`: `apiKey` (افتراضي) أو `none`\n- `discovery.type`: `ollama`\n\n## قواعد التحقق (الحالية)\n\n### موفر مخصص كامل (`models` غير فارغة)\n\nالمطلوب:\n\n- `baseUrl`\n- `apiKey` ما لم يكن `auth: none`\n- `api` على مستوى الموفر أو لكل نموذج\n\n### موفر للتجاوز فقط (`models` مفقودة أو فارغة)\n\nيجب تعريف واحد على الأقل من:\n\n- `baseUrl`\n- `modelOverrides`\n- `discovery`\n\n### الاكتشاف\n\n- يتطلب `discovery` وجود `api` على مستوى الموفر.\n\n### فحوصات قيم النموذج\n\n- `id` مطلوب\n- يجب أن يكون `contextWindow` و`maxTokens` موجبَين إذا تم توفيرهما\n\n## ترتيب الدمج والتجاوز\n\nخط أنابيب ModelRegistry (عند التحديث):\n\n1. تحميل الموفرين/النماذج المدمجة من `@f5-sales-demo/pi-ai`.\n2. تحميل تكوين `models.yml` المخصص.\n3. تطبيق تجاوزات الموفر (`baseUrl`، `headers`) على النماذج المدمجة.\n4. تطبيق `modelOverrides` (لكل موفر ومعرّف نموذج).\n5. دمج `models` المخصصة:\n   - نفس `provider + id` يستبدل الموجود\n   - وإلا يُضاف\n6. تطبيق النماذج المكتشفة في وقت التشغيل (Ollama وLM Studio حالياً)، ثم إعادة تطبيق تجاوزات النموذج.\n\n## التكافؤ المعياري للنماذج والتجميع\n\nيحتفظ السجل بكل نموذج موفر ملموس ثم يبني طبقة معيارية فوقها.\n\nالمعرّفات المعيارية هي معرّفات رسمية من المصدر الأعلى فقط، على سبيل المثال:\n\n- `claude-opus-4-6`\n- `claude-haiku-4-5`\n- `gpt-5.3-codex`\n\n### تكوين التكافؤ في `models.yml`\n\nمثال:\n\n```yaml\nproviders:\n  zenmux:\n    baseUrl: https://api.zenmux.example/v1\n    apiKey: ZENMUX_API_KEY\n    api: openai-codex-responses\n    models:\n      - id: codex\n        name: Zenmux Codex\n        reasoning: true\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 200000\n        maxTokens: 32768\n\nequivalence:\n  overrides:\n    zenmux/codex: gpt-5.3-codex\n    p-codex/codex: gpt-5.3-codex\n  exclude:\n    - demo/codex-preview\n```\n\nترتيب البناء للتجميع المعياري:\n\n1. التجاوز الصريح من المستخدم من `equivalence.overrides`\n2. تطابقات المعرّف الرسمي المجمّعة من بيانات وصف النموذج المدمجة\n3. تطبيع اكتشافي محافظ لمتغيرات البوابة/الموفر\n4. الرجوع إلى معرّف النموذج الملموس الخاص به\n\nالاكتشافات الحالية ضيّقة عمداً:\n\n- يمكن حذف بادئات المصدر الأعلى المضمّنة عند وجودها، مثلاً `anthropic/...` أو `openai/...`\n- يمكن تطبيع متغيرات الإصدار بنقطة وشرطة فقط عندما تعيّن إلى معرّف رسمي موجود، مثلاً `4.6 -> 4-6`\n- لا يتم دمج العائلات أو الإصدارات الغامضة بدون تطابق مجمّع أو تجاوز صريح\n\n### سلوك الحل المعياري\n\nعندما تشترك متغيرات ملموسة متعددة في معرّف معياري، يستخدم الحل:\n\n1. التوافر والمصادقة\n2. `modelProviderOrder` في `config.yml`\n3. ترتيب السجل/الموفر الحالي إذا كان `modelProviderOrder` غير محدد\n\nيتم تجاهل الموفرين المعطّلين أو غير الموثّقين.\n\nتواصل حالة الجلسة والسجلات التاريخية تسجيل الموفر/النموذج الملموس الذي نفّذ الدور فعلياً.\n\nافتراضيات الموفر مقابل تجاوزات لكل نموذج:\n\n- `headers` الموفر هي خط الأساس.\n- `headers` النموذج تتجاوز مفاتيح رأس الموفر.\n- يمكن لـ `modelOverrides` تجاوز بيانات وصف النموذج (`name`، `reasoning`، `input`، `cost`، `contextWindow`، `maxTokens`، `headers`، `compat`، `contextPromotionTarget`).\n- يتم الدمج العميق لـ `compat` لكتل التوجيه المتداخلة (`openRouterRouting`، `vercelGatewayRouting`، `extraBody`).\n\n## تكامل الاكتشاف في وقت التشغيل\n\n### اكتشاف Ollama الضمني\n\nإذا لم يكن `ollama` مُكوَّناً صراحةً، يضيف السجل موفراً قابلاً للاكتشاف ضمنياً:\n\n- الموفر: `ollama`\n- api: `openai-completions`\n- عنوان URL الأساسي: `OLLAMA_BASE_URL` أو `http://127.0.0.1:11434`\n- وضع المصادقة: بدون مفتاح (سلوك `auth: none`)\n\nيستدعي الاكتشاف في وقت التشغيل `GET /api/tags` على Ollama ويُنشئ إدخالات نموذج بقيم افتراضية محلية.\n\n### اكتشاف llama.cpp الضمني\n\nإذا لم يكن `llama.cpp` مُكوَّناً صراحةً، يضيف السجل موفراً قابلاً للاكتشاف ضمنياً:\nملاحظة: يستخدم واجهة برمجة تطبيقات رسائل anthropic الأحدث بدلاً من openai-completions.\n\n- الموفر: `llama.cpp`\n- api: `openai-responses`\n- عنوان URL الأساسي: `LLAMA_CPP_BASE_URL` أو `http://127.0.0.1:8080`\n- وضع المصادقة: بدون مفتاح (سلوك `auth: none`)\n\nيستدعي الاكتشاف في وقت التشغيل `GET models` على llama.cpp ويُنشئ إدخالات نموذج بقيم افتراضية محلية.\n\n### اكتشاف LM Studio الضمني\n\nإذا لم يكن `lm-studio` مُكوَّناً صراحةً، يضيف السجل موفراً قابلاً للاكتشاف ضمنياً:\n\n- الموفر: `lm-studio`\n- api: `openai-completions`\n- عنوان URL الأساسي: `LM_STUDIO_BASE_URL` أو `http://127.0.0.1:1234/v1`\n- وضع المصادقة: بدون مفتاح (سلوك `auth: none`)\n\nيجلب الاكتشاف في وقت التشغيل النماذج (`GET /models`) ويُنشئ إدخالات نموذج بقيم افتراضية محلية.\n\n### اكتشاف الموفر الصريح\n\nيمكنك تكوين الاكتشاف بنفسك:\n\n```yaml\nproviders:\n  ollama:\n    baseUrl: http://127.0.0.1:11434\n    api: openai-completions\n    auth: none\n    discovery:\n      type: ollama\n      \n  llama.cpp:\n    baseUrl: http://127.0.0.1:8080\n    api: openai-responses\n    auth: none\n    discovery:\n      type: llama.cpp\n```\n\n### تسجيل موفر الامتداد\n\nيمكن للامتدادات تسجيل موفرين في وقت التشغيل (`pi.registerProvider(...)`), بما في ذلك:\n\n- استبدال النماذج/إضافتها لموفر\n- تسجيل معالج بث مخصص لمعرّفات API جديدة\n- تسجيل موفر OAuth مخصص\n\n## ترتيب حل المصادقة ومفتاح API\n\nعند طلب مفتاح لموفر، يكون الترتيب الفعّال:\n\n1. التجاوز في وقت التشغيل (CLI `--api-key`)\n2. بيانات اعتماد مفتاح API المخزّنة في `agent.db`\n3. بيانات اعتماد OAuth المخزّنة في `agent.db` (مع التحديث)\n4. تعيين متغير البيئة (`OPENAI_API_KEY`، `ANTHROPIC_API_KEY`، إلخ)\n5. محلّل الرجوع في ModelRegistry (موفر `apiKey` من `models.yml`، بدلالات اسم البيئة أو القيمة الحرفية)\n\nسلوك `apiKey` في `models.yml`:\n\n- تُعامَل القيمة أولاً كاسم متغير بيئة.\n- إذا لم يكن متغير البيئة موجوداً، تُستخدم السلسلة الحرفية كرمز مميز.\n\nإذا كان `authHeader: true` وكان `apiKey` الموفر محدداً، تحصل النماذج على:\n\n- رأس `Authorization: Bearer <resolved-key>` مُحقَن.\n\nالموفرون بدون مفتاح:\n\n- يُعامَل الموفرون المُعلَّمون بـ `auth: none` على أنهم متاحون بدون بيانات اعتماد.\n- يُعيد `getApiKey*` القيمة `kNoAuth` لهم.\n\n## توافر النموذج مقابل جميع النماذج\n\n- يُعيد `getAll()` سجل النماذج المُحمَّل (المدمجة + المخصصة المدموجة + المكتشفة).\n- يُصفّي `getAvailable()` للنماذج التي لا تحتاج مفتاحاً أو لها مصادقة قابلة للحل.\n\nلذا يمكن أن يوجد نموذج في السجل لكن لا يكون قابلاً للاختيار حتى تتوفر المصادقة.\n\n## حل النموذج في وقت التشغيل\n\n### CLI وتحليل الأنماط\n\nيدعم `model-resolver.ts`:\n\n- `provider/modelId` الدقيق\n- معرّف النموذج المعياري الدقيق\n- معرّف النموذج الدقيق (يُستنتج الموفر)\n- المطابقة الغامضة/بالسلسلة الجزئية\n- أنماط النطاق العام في `--models` (مثلاً `openai/*`، `*sonnet*`)\n- لاحقة اختيارية `:thinkingLevel` (`off|minimal|low|medium|high|xhigh`)\n\n`--provider` موروث؛ `--model` مفضّل.\n\nأولوية الحل للمحددات الدقيقة:\n\n1. `provider/modelId` الدقيق يتجاوز التجميع\n2. المعرّف المعياري الدقيق يُحلّ عبر الفهرس المعياري\n3. معرّف ملموس مجرّد دقيق لا يزال يعمل\n4. المطابقة الغامضة والعام تعمل بعد المسارات الدقيقة\n\n### أولوية اختيار النموذج الأولي\n\nيستخدم `findInitialModel(...)` هذا الترتيب:\n\n1. موفر+نموذج CLI صريح\n2. أول نموذج في النطاق (إذا لم يكن استئنافاً)\n3. الموفر/النموذج الافتراضي المحفوظ\n4. افتراضيات الموفر المعروفة (مثلاً OpenAI/Anthropic/إلخ) بين النماذج المتاحة\n5. أول نموذج متاح\n\n### أدوار الأسماء المستعارة والإعدادات\n\nأدوار النماذج المدعومة:\n\n- `default`، `smol`، `slow`، `plan`، `commit`\n\nأسماء الأدوار المستعارة مثل `pi/smol` تُوسَّع عبر `settings.modelRoles`. يمكن لكل قيمة دور أيضاً إلحاق محدد تفكير مثل `:minimal`، `:low`، `:medium`، أو `:high`.\n\nإذا كان الدور يشير إلى دور آخر، فإن النموذج المستهدف لا يزال يرث بصورة طبيعية وأي لاحقة صريحة على الدور المُحيل تفوز لذلك الاستخدام الخاص بالدور.\n\nالإعدادات ذات الصلة:\n\n- `modelRoles` (سجل)\n- `enabledModels` (قائمة أنماط النطاق)\n- `modelProviderOrder` (أولوية الموفر المعياري العامة)\n- `providers.kimiApiFormat` (تنسيق طلب `openai` أو `anthropic`)\n- `providers.openaiWebsockets` (تفضيل WebSocket بـ `auto|off|on` لنقل OpenAI Codex)\n\nقد يخزّن `modelRoles` إما:\n\n- `provider/modelId` لتثبيت متغير موفر ملموس\n- معرّفاً معيارياً مثل `gpt-5.3-codex` للسماح بتجميع الموفرين\n\nبالنسبة لـ `enabledModels` وـ `--models` في CLI:\n\n- تُوسَّع المعرّفات المعيارية الدقيقة إلى جميع المتغيرات الملموسة في تلك المجموعة المعيارية\n- إدخالات `provider/modelId` الصريحة تبقى دقيقة\n- العام والمطابقة الغامضة لا تزال تعمل على النماذج الملموسة\n\n## `/model` و`--list-models`\n\nتُبقي كلتا الواجهتين النماذج ذات البادئة المتعلقة بالموفر مرئية وقابلة للاختيار.\n\nتكشفان الآن أيضاً النماذج المعيارية/المجمّعة:\n\n- يتضمن `/model` عرضاً معيارياً جانباً بتبويبات الموفر\n- يطبع `--list-models` قسماً معيارياً بالإضافة إلى صفوف الموفر الملموسة\n\nيخزّن اختيار إدخال معياري المحدد المعياري. يخزّن اختيار صف موفر `provider/modelId` الصريح.\n\n## ترقية السياق (سلاسل الرجوع على مستوى النموذج)\n\nترقية السياق هي آلية استرداد من الفيض لمتغيرات السياق الصغيرة (مثلاً `*-spark`) التي تُرقّي تلقائياً إلى نسخة ذات سياق أكبر عندما يرفض API الطلب بخطأ طول سياق.\n\n### المشغّل والترتيب\n\nعندما يفشل دور بخطأ فيض سياق (مثلاً `context_length_exceeded`)، يحاول `AgentSession` الترقية **قبل** الرجوع إلى الضغط:\n\n1. إذا كان `contextPromotion.enabled` صحيحاً، يُحلّ هدف الترقية (انظر أدناه).\n2. إذا وُجد هدف، يتم التبديل إليه وإعادة محاولة الطلب — لا حاجة للضغط.\n3. إذا لم يتوفر هدف، يتم الرجوع إلى الضغط التلقائي على النموذج الحالي.\n\n### اختيار الهدف\n\nالاختيار مُوجَّه بالنموذج، لا بالدور:\n\n1. `currentModel.contextPromotionTarget` (إذا كان مُكوَّناً)\n2. أصغر نموذج بسياق أكبر لدى نفس الموفر + API\n\nيتم تجاهل المرشّحين ما لم تُحلّ بيانات الاعتماد (`ModelRegistry.getApiKey(...)`).\n\n### تسليم WebSocket لـ OpenAI Codex\n\nعند التبديل من/إلى `openai-codex-responses`، يُغلَق مفتاح حالة موفر الجلسة `openai-codex-responses` قبل تبديل النموذج. هذا يُسقط حالة نقل WebSocket حتى يبدأ الدور التالي نظيفاً على النموذج المُرقَّى.\n\n### سلوك الاستمرارية\n\nتستخدم الترقية التبديل المؤقت (`setModelTemporary`):\n\n- يُسجَّل كـ `model_change` مؤقت في تاريخ الجلسة\n- لا يعيد كتابة تعيين الدور المحفوظ\n\n### تكوين سلاسل الرجوع الصريحة\n\nقم بتكوين الرجوع مباشرةً في بيانات وصف النموذج عبر `contextPromotionTarget`.\n\nيقبل `contextPromotionTarget` إما:\n\n- `provider/model-id` (صريح)\n- `model-id` (يُحلّ داخل الموفر الحالي)\n\nمثال (`models.yml`) لـ Spark -> غير Spark على نفس الموفر:\n\n```yaml\nproviders:\n  openai-codex:\n    modelOverrides:\n      gpt-5.3-codex-spark:\n        contextPromotionTarget: openai-codex/gpt-5.3-codex\n```\n\nيُعيّن مولّد النماذج المدمج هذا أيضاً تلقائياً لنماذج `*-spark` عندما يوجد نموذج أساسي على نفس الموفر.\n\n## حقول التوافق والتوجيه\n\nيدعم `models.yml` هذه المجموعة الفرعية من `compat`:\n\n- `supportsStore`\n- `supportsDeveloperRole`\n- `supportsReasoningEffort`\n- `maxTokensField` (`max_completion_tokens` أو `max_tokens`)\n- `openRouterRouting.only` / `openRouterRouting.order`\n- `vercelGatewayRouting.only` / `vercelGatewayRouting.order`\n\nتُستهلك هذه الحقول بواسطة منطق نقل OpenAI-completions وتُجمَع مع الكشف التلقائي القائم على URL.\n\n## أمثلة عملية\n\n### نقطة نهاية متوافقة مع OpenAI محلية (بدون مصادقة)\n\n```yaml\nproviders:\n  local-openai:\n    baseUrl: http://127.0.0.1:8000/v1\n    auth: none\n    api: openai-completions\n    models:\n      - id: Qwen/Qwen2.5-Coder-32B-Instruct\n        name: Qwen 2.5 Coder 32B (local)\n```\n\n### وكيل مستضاف بمفتاح مستند إلى متغير البيئة\n\n```yaml\nproviders:\n  anthropic-proxy:\n    baseUrl: https://proxy.example.com/anthropic\n    apiKey: ANTHROPIC_PROXY_API_KEY\n    api: anthropic-messages\n    authHeader: true\n    models:\n      - id: claude-sonnet-4-20250514\n        name: Claude Sonnet 4 (Proxy)\n        reasoning: true\n        input: [text, image]\n```\n\n### تجاوز مسار الموفر المدمج + بيانات وصف النموذج\n\n```yaml\nproviders:\n  openrouter:\n    baseUrl: https://my-proxy.example.com/v1\n    headers:\n      X-Team: platform\n    modelOverrides:\n      anthropic/claude-sonnet-4:\n        name: Sonnet 4 (Corp)\n        compat:\n          openRouterRouting:\n            only: [anthropic]\n```\n\n## الضبط التلقائي لـ LiteLLM proxy\n\nعند تعيين متغيري البيئة `LITELLM_BASE_URL` و`LITELLM_API_KEY` معاً، يدير xcsh تلقائياً تكوين `models.yml` لـ LiteLLM proxy.\n\n### التوليد التلقائي عند أول تشغيل\n\nإذا لم يكن `models.yml` موجوداً واكتُشف متغيرا بيئة LiteLLM، يُنشئه xcsh تلقائياً:\n\n```yaml\n# Auto-generated by xcsh for LiteLLM proxy\n# API key resolved from LITELLM_API_KEY env var at runtime\nconfigVersion: 1\nproviders:\n  anthropic:\n    baseUrl: \"https://your-litellm-proxy.example.com/anthropic\"\n    apiKey: LITELLM_API_KEY\n```\n\nيُنشأ أيضاً `config.yml` افتراضي بإعدادات موفر الصور المعقولة.\n\n### الإصلاح الذاتي عند بدء التشغيل\n\nعند كل بدء تشغيل، يُشغّل `startupHealthCheck()` في سجل النماذج الفحوصات التالية:\n\n| الحالة | الإجراء |\n|-----------|--------|\n| `models.yml` مفقود | التوليد التلقائي من متغيرات البيئة |\n| `models.yml` تالف أو غير قابل للتحليل | نسخ احتياطي بـ `.bak`، إعادة التوليد |\n| `baseUrl` لا يتطابق مع `LITELLM_BASE_URL` | نسخ احتياطي بـ `.bak`، إعادة التوليد بعنوان URL الجديد |\n| `configVersion` مفقود أو قديم | نسخ احتياطي بـ `.bak`، إعادة التوليد بالإصدار الحالي |\n| التكوين سليم | لا إجراء |\n\nتُنشئ جميع الإصلاحات نسخاً احتياطية بـ `.bak` قبل الكتابة فوقها. جميع العمليات غير قابلة للتأثير المتكرر.\n\n### أمر CLI\n\n```bash\nxcsh setup litellm              # إنشاء أو إصلاح تكوين LiteLLM\nxcsh setup litellm --check      # التحقق بدون كتابة\nxcsh setup litellm --check --json  # إخراج التحقق قابل للقراءة آلياً\n```\n\n### متغيرات البيئة المطلوبة\n\n| المتغير | الغرض |\n|----------|---------|\n| `LITELLM_BASE_URL` | عنوان URL لـ LiteLLM proxy (مثلاً `https://your-proxy.example.com`). يجب أن يبدأ بـ `http://` أو `https://`. |\n| `LITELLM_API_KEY` | مفتاح API للوكيل. يُشار إليه بالاسم في التكوين المُولَّد ويُحلّ في وقت التشغيل. |\n\nإذا لم يكن أي من المتغيرين محدداً، يتم تخطي الضبط التلقائي بصمت.\n\n### إصدار التكوين\n\nتتضمن التكوينات المُولَّدة حقل `configVersion`. عندما يتغير تنسيق التوليد في الإصدارات المستقبلية، يكتشف xcsh التكوينات القديمة ويُرقّيها تلقائياً (مع نسخ احتياطي).\n\n## تحفّظ المستهلك القديم\n\nيتدفق معظم تكوين النماذج الآن عبر `models.yml` عبر `ModelRegistry`.\n\nيبقى مسار موروث واحد ملحوظ: لا يزال حل مصادقة Anthropic للبحث على الويب يقرأ `~/.xcsh/agent/models.json` مباشرةً في `src/web/search/auth.ts`.\n\nإذا كنت تعتمد على ذلك المسار المحدد، ضع في اعتبارك التوافق مع JSON حتى يُرحَّل ذلك الوحدة.\n\n## وضع الفشل\n\nإذا فشل `models.yml` في فحوصات المخطط أو التحقق:\n\n- إذا كان `LITELLM_BASE_URL` و`LITELLM_API_KEY` محدّدَين، يحاول فحص صحة بدء التشغيل الإصلاح التلقائي (نسخ الملف التالف احتياطياً، وإعادة التوليد من متغيرات البيئة). إذا نجح الإصلاح، يُعيد السجل تحميل التكوين المُصلَح.\n- إذا لم يكن الإصلاح التلقائي ممكناً (متغيرات البيئة غير محدّدة، فشل الكتابة)، يستمر السجل في العمل بالنماذج المدمجة.\n- يُكشف الخطأ عبر `ModelRegistry.getError()` ويُعرض في واجهة المستخدم/الإشعارات.\n",
	"ar/providers/provider-streaming-internals.md": "---\ntitle: الداخليات الداخلية لتدفق الموفر\ndescription: تنفيذ تدفق الموفر مع تحليل SSE، وعد الرموز، ومعالجة الضغط العكسي.\nsidebar:\n  order: 2\n  label: الداخليات الداخلية للتدفق\ni18n:\n  sourceHash: a32ffa769c4d\n  translator: machine\n---\n\n# الداخليات الداخلية لتدفق الموفر\n\nيشرح هذا المستند كيفية توحيد تدفق الرموز/الأدوات في `@f5-sales-demo/pi-ai`، ثم نشره عبر أحداث جلسات `@f5-sales-demo/pi-agent-core` و`coding-agent`.\n\n## التدفق الشامل من البداية إلى النهاية\n\n1. تقوم `streamSimple()` (`packages/ai/src/stream.ts`) بتعيين الخيارات العامة وإرسالها إلى دالة تدفق الموفر.\n2. تقوم دوال تدفق الموفر (`anthropic.ts`، `openai-responses.ts`، `google.ts`) بترجمة أحداث التدفق الأصلية للموفر إلى تسلسل `AssistantMessageEvent` الموحد.\n3. يدفع كل موفر الأحداث إلى `AssistantMessageEventStream` (`packages/ai/src/utils/event-stream.ts`)، والذي يُخفف أحداث دلتا ويكشف عن:\n   - التكرار غير المتزامن للتحديثات التدريجية\n   - `result()` للـ `AssistantMessage` النهائي\n4. تستهلك `agentLoop` (`packages/agent/src/agent-loop.ts`) تلك الأحداث، وتعدّل حالة المساعد أثناء التنفيذ، وتصدر أحداث `message_update` تحمل `assistantMessageEvent` الخام.\n5. تشترك `AgentSession` (`packages/coding-agent/src/session/agent-session.ts`) في أحداث الوكيل، وتحفظ الرسائل، وتشغّل خطافات الامتداد، وتطبق سلوكيات الجلسة (إعادة المحاولة، والضغط، وTTSR، وفحوصات إلغاء التدفق أثناء التحرير).\n\n## عقد التدفق الموحد في `@f5-sales-demo/pi-ai`\n\nتصدر جميع الموفرين نفس الشكل (`AssistantMessageEvent` في `packages/ai/src/types.ts`):\n\n- `start`\n- ثلاثية دورة حياة كتلة المحتوى:\n  - نص: `text_start` → `text_delta`* → `text_end`\n  - تفكير: `thinking_start` → `thinking_delta`* → `thinking_end`\n  - استدعاء أداة: `toolcall_start` → `toolcall_delta`* → `toolcall_end`\n- حدث نهائي:\n  - `done` مع `reason: \"stop\" | \"length\" | \"toolUse\"`\n  - أو `error` مع `reason: \"aborted\" | \"error\"`\n\nتضمن `AssistantMessageEventStream`:\n\n- يتم حل النتيجة النهائية بواسطة الحدث النهائي (`done` أو `error`)\n- يتم تجميع/تخفيف الدلتا (~50ms)\n- يتم مسح الدلتا المؤقتة قبل الأحداث غير الدلتا وقبل الاكتمال\n\n## سلوك تخفيف الدلتا وتنسيقها\n\nتعامل `AssistantMessageEventStream` أحداث `text_delta` و`thinking_delta` و`toolcall_delta` كأحداث قابلة للدمج:\n\n- يتم دمج الدلتا المؤقتة فقط عندما يتطابق **النوع + contentIndex**\n- يحتفظ الدمج بأحدث لقطة `partial`\n- تؤدي الأحداث غير الدلتا إلى مسح فوري\n\nيُلطّف هذا تدفقات الموفر عالية التردد لمستهلكي TUI/الأحداث، لكنه ليس ضغطًا عكسيًا للموفر: تنتج الموفرون بأقصى سرعة، بينما يخزن التدفق المحلي مؤقتًا.\n\n## تفاصيل توحيد الموفر\n\n## Anthropic (`anthropic-messages`)\n\nالمصدر: `packages/ai/src/providers/anthropic.ts`\n\nنقاط التوحيد:\n\n- `message_start` يُهيئ الاستخدام (رموز المدخلات/المخرجات/ذاكرة التخزين المؤقت)\n- `content_block_start` يُعيّن إلى بدايات النص/التفكير/استدعاء الأداة\n- `content_block_delta` يُعيّن:\n  - `text_delta` → `text_delta`\n  - `thinking_delta` → `thinking_delta`\n  - `input_json_delta` → `toolcall_delta`\n  - `signature_delta` يحدّث `thinkingSignature` فقط (بدون حدث)\n- `content_block_stop` يصدر `*_end` المقابل\n- `message_delta.stop_reason` يُعيّن عبر `mapStopReason()`\n\nتدفق وسيطات استدعاء الأداة:\n\n- تحمل كل كتلة أداة `partialJson` داخليًا\n- تُلحق كل دلتا JSON بـ `partialJson`\n- يُعاد تحليل `arguments` في كل دلتا عبر `parseStreamingJson()`\n- يُعيد `toolcall_end` التحليل مرة أخرى، ثم يُجرّد `partialJson`\n\n## OpenAI Responses (`openai-responses`)\n\nالمصدر: `packages/ai/src/providers/openai-responses.ts`\n\nنقاط التوحيد:\n\n- `response.output_item.added` يبدأ كتل التفكير/النص/استدعاء الدالة\n- أحداث ملخص التفكير (`response.reasoning_summary_text.delta`) تصبح `thinking_delta`\n- دلتا المخرجات/الرفض تصبح `text_delta`\n- `response.function_call_arguments.delta` يصبح `toolcall_delta`\n- `response.output_item.done` يصدر `thinking_end` / `text_end` / `toolcall_end`\n- `response.completed` يُعيّن الحالة إلى سبب الإيقاف والاستخدام\n\nتدفق وسيطات استدعاء الأداة:\n\n- نفس نمط تراكم `partialJson` كـ Anthropic\n- الموفرون الذين يرسلون فقط `response.function_call_arguments.done` يملؤون الوسيطات النهائية أيضًا\n- يتم توحيد معرّفات استدعاء الأداة كـ `\"<call_id>|<item_id>\"`\n\n## Google Generative AI (`google-generative-ai`)\n\nالمصدر: `packages/ai/src/providers/google.ts`\n\nنقاط التوحيد:\n\n- يتكرر على `candidate.content.parts`\n- تُقسّم أجزاء النص إلى تفكير مقابل نص عبر `isThinkingPart(part)`\n- تؤدي انتقالات الكتلة إلى إغلاق الكتلة السابقة قبل بدء كتلة جديدة\n- يُعامَل `part.functionCall` كاستدعاء أداة كامل (يُصدر البدء/الدلتا/النهاية فورًا)\n- يُعيّن سبب الإنهاء عبر `mapStopReason()` من `google-shared.ts`\n\nتدفق وسيطات استدعاء الأداة:\n\n- تصل وسيطات استدعاء الدالة ككائن منظم، وليس نصًا JSON تدريجيًا\n- يصدر التنفيذ `toolcall_delta` اصطناعيًا واحدًا يحتوي على `JSON.stringify(arguments)`\n- لا يلزم محلل JSON جزئي لـ Google في هذا المسار\n\n## تراكم JSON لاستدعاء الأداة الجزئي واستعادته\n\nيستخدم السلوك المشترك لـ Anthropic/OpenAI Responses دالة `parseStreamingJson()` (`packages/ai/src/utils/json-parse.ts`):\n\n1. محاولة `JSON.parse`\n2. الرجوع إلى محلل `partial-json` للأجزاء غير المكتملة\n3. إرجاع `{}` إذا فشل كلاهما\n\nالتداعيات:\n\n- لا تؤدي دلتا الوسيطات المشوهة أو المبتورة إلى تعطل معالجة التدفق فورًا\n- قد تكون `arguments` قيد التقدم مؤقتًا `{}`\n- يمكن لدلتا صالحة لاحقة استعادة الوسيطات المنظمة لأن التحليل يُعاد في كل إلحاق\n- يُجري `toolcall_end` محاولة تحليل أخيرة قبل الإصدار\n\n## أسباب الإيقاف مقابل أخطاء النقل/وقت التشغيل\n\nيتم تعيين أسباب إيقاف الموفر إلى `stopReason` الموحد:\n\n- Anthropic: `end_turn`→`stop`، `max_tokens`→`length`، `tool_use`→`toolUse`، حالات الأمان/الرفض→`error`\n- OpenAI Responses: `completed`→`stop`، `incomplete`→`length`، `failed/cancelled`→`error`\n- Google: `STOP`→`stop`، `MAX_TOKENS`→`length`، فئات الأمان/المحظور/استدعاء الدالة المشوه→`error`\n\nتنقسم دلالات الأخطاء إلى مرحلتين:\n\n1. **دلالات اكتمال النموذج** (سبب الإنهاء/الحالة التي أبلغ عنها الموفر)\n2. **فشل النقل/وقت التشغيل** (استثناءات الشبكة/العميل/المحلل/الإلغاء)\n\nإذا طرح تدفق الموفر استثناءً أو أشار إلى فشل، يلتقط كل غلاف موفر ويصدر حدث `error` نهائيًا بـ:\n\n- `stopReason = \"aborted\"` عندما يكون إشارة الإلغاء مُعيَّنة\n- وإلا `stopReason = \"error\"`\n- `errorMessage = formatErrorMessageWithRetryAfter(error)`\n\n## سلوك فشل تحليل القطعة/SSE المشوهة\n\nفي مسارات الموفر هذه، يتولى معالجة إطار القطعة/SSE حزم SDK الخاصة بالبائع (Anthropic SDK، OpenAI SDK، Google SDK). لا يُنفّذ هذا الكود محللًا مخصصًا لـ SSE هنا.\n\nالسلوك الملاحظ في التنفيذ الحالي:\n\n- يظهر فشل تحليل القطعة/SSE على مستوى SDK كاستثناء أو حدث `error` للتدفق\n- يحوّل غلاف الموفر ذلك إلى حدث `error` نهائي موحد\n- لا توجد استئناف/إعادة محاولة خاصة بالموفر داخل دالة التدفق نفسها\n- تُعالج إعادة المحاولة على مستوى أعلى في منطق الإعادة التلقائية لـ `AgentSession` (إعادة محاولة على مستوى الرسالة، وليس إعادة تشغيل قطعة التدفق)\n\n## حدود الإلغاء\n\nيتم تنظيم الإلغاء على طبقات:\n\n- طلب موفر الذكاء الاصطناعي: يتم تمرير `options.signal` إلى استدعاء تدفق عميل الموفر.\n- غلاف الموفر: بعد حلقة التدفق، تفرض الإشارة الملغاة مسار الخطأ (`\"Request was aborted\"`).\n- حلقة الوكيل: تتحقق من `signal.aborted` قبل معالجة كل حدث موفر ويمكنها تكوين رسالة مساعد ملغاة من آخر جزء.\n- عناصر تحكم الجلسة/الوكيل: `AgentSession.abort()` -> `agent.abort()` -> إلغاء وحدة التحكم المشتركة في الإلغاء.\n\nإلغاء تنفيذ الأداة منفصل عن إلغاء تدفق النموذج:\n\n- تستخدم مشغّلات الأداة `AbortSignal.any([agentSignal, steeringAbortSignal])`\n- يمكن لانقطاعات التوجيه إلغاء تنفيذ الأداة المتبقي مع الحفاظ على نتائج الأدوات التي تم إنتاجها بالفعل\n\n## حدود الضغط العكسي\n\nلا يوجد آلية ضغط عكسي صارمة بين تدفق SDK للموفر والمستهلكين في المصب:\n\n- تستخدم `EventStream` طوابير في الذاكرة بدون حجم أقصى\n- يقلل التخفيف من معدل تحديث واجهة المستخدم لكنه لا يُبطئ استيعاب الموفر\n- إذا تأخر المستهلكون بشكل كبير، يمكن أن تنمو الأحداث المُخزَّنة في الطابور حتى الاكتمال\n\nيُفضّل التصميم الحالي الاستجابة وبساطة الترتيب على التحكم في التدفق ذي المخزن المؤقت المحدود.\n\n## كيف تظهر أحداث التدفق كأحداث وكيل/جلسة\n\nتربط `agentLoop.streamAssistantResponse()` بين `AssistantMessageEvent` و`AgentEvent`:\n\n- عند `start`: تدفع رسالة مساعد مؤقتة وتصدر `message_start`\n- عند أحداث الكتلة (`text_*`، `thinking_*`، `toolcall_*`): تحدّث آخر رسالة مساعد، وتصدر `message_update` مع `assistantMessageEvent` الخام\n- عند النهاية (`done`/`error`): تحل الرسالة النهائية من `response.result()`، وتصدر `message_end`\n\nتستهلك `AgentSession` بعدها تلك الأحداث للسلوكيات على مستوى الجلسة:\n\n- يراقب TTSR أحداث `message_update.assistantMessageEvent` لـ `text_delta` و`toolcall_delta`\n- يفحص حارس التحرير المتدفق أحداث `toolcall_delta`/`toolcall_end` على استدعاءات `edit` ويمكنه الإلغاء مبكرًا\n- تكتب آلية الحفظ الرسائل النهائية عند `message_end`\n- تفحص إعادة المحاولة التلقائية `stopReason === \"error\"` للمساعد إضافة إلى تدقيق `errorMessage`\n\n## المسؤوليات الموحدة مقابل الخاصة بالموفر\n\nالموحدة (العقد المشترك):\n\n- شكل الحدث (`AssistantMessageEvent`)\n- استخراج النتيجة النهائية (`done`/`error`)\n- قواعد تخفيف الدلتا ودمجها\n- نموذج نشر أحداث الوكيل/الجلسة\n\nالخاصة بالموفر (غير مجرَّدة بالكامل):\n\n- تصنيفات الأحداث الأولية ومنطق التعيين\n- جداول ترجمة سبب الإيقاف\n- اصطلاحات معرّف استدعاء الأداة\n- دلالات كتلة التفكير/الاستدلال وتوقيعاتها\n- دلالات رموز الاستخدام وتوقيت توافرها\n- قيود تحويل الرسائل لكل API\n\n## ملفات التنفيذ\n\n- [`../../ai/src/stream.ts`](../../packages/ai/src/stream.ts) — إرسال الموفر، وتعيين الخيارات، وتوصيل مفتاح API/الجلسة.\n- [`../../ai/src/utils/event-stream.ts`](../../packages/ai/src/utils/event-stream.ts) — طابور التدفق العام + تخفيف دلتا المساعد.\n- [`../../ai/src/utils/json-parse.ts`](../../packages/ai/src/utils/json-parse.ts) — تحليل JSON الجزئي لوسيطات الأداة المتدفقة.\n- [`../../ai/src/providers/anthropic.ts`](../../packages/ai/src/providers/anthropic.ts) — ترجمة أحداث Anthropic وتراكم دلتا JSON للأداة.\n- [`../../ai/src/providers/openai-responses.ts`](../../packages/ai/src/providers/openai-responses.ts) — ترجمة أحداث OpenAI Responses وتعيين الحالة.\n- [`../../ai/src/providers/google.ts`](../../packages/ai/src/providers/google.ts) — ترجمة قطعة تدفق Gemini إلى كتلة.\n- [`../../ai/src/providers/google-shared.ts`](../../packages/ai/src/providers/google-shared.ts) — تعيين سبب إنهاء Gemini وقواعد التحويل المشتركة.\n- [`../../agent/src/agent-loop.ts`](../../packages/agent/src/agent-loop.ts) — استهلاك تدفق الموفر وتجسير `message_update`.\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — معالجة تحديثات التدفق على مستوى الجلسة، والإلغاء، وإعادة المحاولة، والحفظ.\n",
	"ar/providers/python-repl.md": "---\ntitle: أداة Python ووقت تشغيل IPython\ndescription: وقت تشغيل أداة Python REPL مع إدارة نواة IPython وتنفيذها والتقاط مخرجاتها.\nsidebar:\n  order: 3\n  label: Python وIPython\ni18n:\n  sourceHash: 70f0a034ecef\n  translator: machine\n---\n\n# أداة Python ووقت تشغيل IPython\n\nتصف هذه الوثيقة مكدس تنفيذ Python الحالي في `packages/coding-agent`.\nتغطي سلوك الأداة، ودورة حياة النواة/البوابة، ومعالجة البيئة، ودلالات التنفيذ، وعرض المخرجات، وأنماط الفشل التشغيلي.\n\n## النطاق والملفات الرئيسية\n\n- واجهة الأداة: `src/tools/python.ts`\n- تنسيق النواة لكل جلسة/استدعاء: `src/ipy/executor.ts`\n- بروتوكول النواة + تكامل البوابة: `src/ipy/kernel.ts`\n- منسق البوابة المحلية المشتركة: `src/ipy/gateway-coordinator.ts`\n- عارض الوضع التفاعلي لتشغيل Python الذي يبدأه المستخدم: `src/modes/components/python-execution.ts`\n- تصفية وقت التشغيل/البيئة وتحليل مسار Python: `src/ipy/runtime.ts`\n\n## ما هي أداة Python\n\nتُنفّذ أداة `python` خلية Python واحدة أو أكثر عبر نواة مدعومة ببوابة Jupyter Kernel Gateway (وليس عن طريق إطلاق `python -c` مباشرةً لكل خلية).\n\nمعاملات الأداة:\n\n```ts\n{\n  cells: Array<{ code: string; title?: string }>;\n  timeout?: number; // بالثواني، محدودة بين 1..600، الافتراضي 30\n  cwd?: string;\n  reset?: boolean; // إعادة تعيين النواة قبل الخلية الأولى فقط\n}\n```\n\nالأداة تعمل بـ `concurrency = \"exclusive\"` للجلسة، لذا لا تتداخل الاستدعاءات.\n\n## دورة حياة البوابة\n\n### الأوضاع\n\nهناك مساران للبوابة:\n\n1. **البوابة الخارجية** (`PI_PYTHON_GATEWAY_URL` مُضبوطة)\n   - تستخدم عنوان URL المُهيَّأ مباشرةً.\n   - مصادقة اختيارية عبر `PI_PYTHON_GATEWAY_TOKEN`.\n   - لا يتم إطلاق أي عملية بوابة محلية أو إدارتها.\n\n2. **البوابة المحلية المشتركة** (المسار الافتراضي)\n   - تستخدم عملية مشتركة واحدة يتم تنسيقها تحت `~/.xcsh/agent/python-gateway`.\n   - ملف البيانات الوصفية: `gateway.json`\n   - ملف القفل: `gateway.lock`\n   - أمر الإطلاق:\n     - `python -m kernel_gateway`\n     - مرتبطة بـ `127.0.0.1:<allocated-port>`\n     - فحص سلامة الإقلاع: `GET /api/kernelspecs`\n\n### تنسيق البوابة المحلية المشتركة\n\n`acquireSharedGateway()`:\n\n- تأخذ قفل الملف (`gateway.lock`) مع نبضة قلب.\n- تُعيد استخدام `gateway.json` إذا كان PID حياً واجتاز فحص السلامة.\n- تُنظّف المعلومات/PIDs القديمة عند الحاجة.\n- تبدأ بوابة جديدة حين لا توجد بوابة سليمة.\n\n`releaseSharedGateway()` هي في الوقت الحالي عملية بلا أثر (إيقاف تشغيل النواة لا يُفكّك البوابة المشتركة).\n\n`shutdownSharedGateway()` تُنهي العملية المشتركة صراحةً وتمسح بيانات البوابة الوصفية.\n\n### قيد مهم\n\n`python.sharedGateway=false` مرفوض عند بدء تشغيل النواة:\n\n- الخطأ: `Shared Python gateway required; local gateways are disabled`\n- لا يوجد وضع بوابة محلية غير مشتركة لكل عملية.\n\n## دورة حياة النواة\n\nيستخدم كل تنفيذ نواةً تُنشأ عبر `POST /api/kernels` على البوابة المحددة.\n\nتسلسل إقلاع النواة:\n\n1. فحص التوافر (`checkPythonKernelAvailability`)\n2. إنشاء النواة (`/api/kernels`)\n3. فتح WebSocket (`/api/kernels/:id/channels`)\n4. تهيئة بيئة النواة (`cwd`، متغيرات البيئة، `sys.path`)\n5. تنفيذ `PYTHON_PRELUDE`\n6. تحميل وحدات الامتداد من:\n   - المستخدم: `~/.xcsh/agent/modules/*.py`\n   - المشروع: `<cwd>/.xcsh/modules/*.py` (يتجاوز وحدة المستخدم بالاسم ذاته)\n\nإيقاف تشغيل النواة:\n\n- حذف النواة البعيدة عبر `DELETE /api/kernels/:id`\n- إغلاق WebSocket\n- استدعاء خطاف تحرير البوابة المشتركة (بلا أثر حالياً)\n\n## دلالات استمرارية الجلسة\n\nيتحكم `python.kernelMode` في إعادة استخدام النواة:\n\n- `session` (الافتراضي)\n  - يُعيد استخدام جلسات النواة مفهرسةً بهوية الجلسة + cwd.\n  - يتم تسلسل التنفيذ لكل جلسة عبر طابور انتظار.\n  - تُطرد الجلسات الخاملة بعد 5 دقائق.\n  - بحد أقصى 4 جلسات؛ تُطرد الأقدم عند تجاوز الحد.\n  - تكشف فحوصات النبضة النوى المتوقفة.\n  - يُسمح بإعادة التشغيل التلقائي مرة واحدة؛ التعطل المتكرر => فشل نهائي.\n\n- `per-call`\n  - يُنشئ نواةً جديدة لكل طلب تنفيذ.\n  - يُوقف النواة بعد انتهاء الطلب.\n  - لا استمرارية في الحالة بين الاستدعاءات.\n\n### سلوك الخلايا المتعددة في استدعاء أداة واحد\n\nتعمل الخلايا بالتسلسل في نفس نسخة النواة لذلك الاستدعاء.\n\nإذا أخفقت خلية وسيطة:\n\n- تبقى حالة الخلية الأسبق في الذاكرة.\n- تُعيد الأداة خطأً محدداً يشير إلى الخلية التي أخفقت.\n- لا تُنفَّذ الخلايا اللاحقة.\n\n`reset=true` يُطبَّق فقط على تنفيذ الخلية الأولى في ذلك الاستدعاء.\n\n## تصفية البيئة وتحليل وقت التشغيل\n\nتُصفَّى البيئة قبل إطلاق وقت تشغيل البوابة/النواة:\n\n- تشمل القائمة البيضاء المتغيرات الأساسية مثل `PATH`، `HOME`، متغيرات المنطقة، `VIRTUAL_ENV`، `PYTHONPATH`، وغيرها.\n- بوادئ مسموح بها: `LC_`، `XDG_`، `PI_`\n- تحذف القائمة السوداء مفاتيح API الشائعة (OpenAI/Anthropic/Gemini/إلخ.)\n\nترتيب اختيار وقت التشغيل:\n\n1. البيئة الافتراضية النشطة/الموجودة (`VIRTUAL_ENV`، ثم `<cwd>/.venv`، `<cwd>/venv`)\n2. البيئة الافتراضية المُدارة في `~/.xcsh/python-env`\n3. `python` أو `python3` في PATH\n\nعند اختيار بيئة افتراضية، يُضاف مسار bin/Scripts إلى مقدمة `PATH`.\n\nتهيئة بيئة النواة داخل Python أيضاً:\n\n- `os.chdir(cwd)`\n- حقن خريطة البيئة المُقدَّمة في `os.environ`\n- التأكد من أن cwd موجود في `sys.path`\n\n## توافر الأداة واختيار الوضع\n\nيتحكم `python.toolMode` (الافتراضي `both`) + التجاوز الاختياري `PI_PY` في الكشف:\n\n- `ipy-only`\n- `bash-only`\n- `both`\n\nالقيم المقبولة لـ `PI_PY`:\n\n- `0` / `bash` -> `bash-only`\n- `1` / `py` -> `ipy-only`\n- `mix` / `both` -> `both`\n\nإذا فشل الفحص المسبق لـ Python، يتراجع إنشاء الأداة إلى bash-only لتلك الجلسة.\n\n## تدفق التنفيذ والإلغاء/المهلة\n\n### مهلة مستوى الأداة\n\nمهلة أداة `python` بالثواني، الافتراضي 30، محدودة بين `1..600`.\n\nتجمع الأداة:\n\n- إشارة إلغاء المستدعي\n- إشارة إلغاء المهلة\n\nمع `AbortSignal.any(...)`.\n\n### إلغاء تنفيذ النواة\n\nعند الإلغاء/المهلة:\n\n- يُوسَم التنفيذ بالإلغاء.\n- تُحاوَل مقاطعة النواة عبر REST (`POST /interrupt`) وقناة التحكم `interrupt_request`.\n- تتضمن النتيجة `cancelled=true`.\n- تُضيف مسار المهلة تعليقاً على المخرجات: `Command timed out after <n> seconds`.\n\n### سلوك stdin\n\nالمدخلات التفاعلية stdin غير مدعومة.\n\nإذا أصدرت النواة `input_request`:\n\n- تسجّل الأداة `stdinRequested=true`\n- تُصدر نصاً توضيحياً\n- ترسل `input_reply` فارغاً\n- يُعامَل التنفيذ على أنه فشل في طبقة المنفّذ\n\n## التقاط المخرجات وعرضها\n\n### فئات المخرجات الملتقطة\n\nمن رسائل النواة:\n\n- `stream` -> مقاطع نص عادي\n- `display_data`/`execute_result` -> معالجة العرض الغني\n- `error` -> نص تتبع الخطأ\n- MIME مخصص `application/x-xcsh-status` -> أحداث الحالة المنظمة\n\nأولوية MIME في العرض:\n\n1. `text/markdown`\n2. `text/plain`\n3. `text/html` (يُحوَّل إلى Markdown أساسي)\n\nيُلتقط إضافةً إلى ذلك كمخرجات منظمة:\n\n- `application/json` -> بيانات شجرة JSON\n- `image/png` -> حمولات الصور\n- `application/x-xcsh-status` -> أحداث الحالة\n\n### التخزين والاقتطاع\n\nتتدفق المخرجات عبر `OutputSink` ويمكن استمرارها في تخزين الأدوات المساعدة.\n\nيمكن أن تتضمن نتائج الأداة بيانات وصفية للاقتطاع و`artifact://<id>` لاسترداد المخرجات الكاملة.\n\n### سلوك العارض\n\n- عارض الأداة (`python.ts`):\n  - يعرض كتل خلايا الكود مع حالة لكل خلية\n  - يعرض المعاينة المطوية افتراضياً لـ 10 أسطر\n  - يدعم الوضع الموسّع للمخرجات الكاملة وتفاصيل الحالة الأغنى\n- العارض التفاعلي (`python-execution.ts`):\n  - يُستخدم لتنفيذ Python الذي يبدأه المستخدم في TUI\n  - يعرض المعاينة المطوية افتراضياً لـ 20 سطراً\n  - يقيّد الأسطر الفردية الطويلة جداً إلى 4000 حرف لسلامة العرض\n  - يعرض إشعارات الإلغاء/الخطأ/الاقتطاع\n\n## دعم البوابة الخارجية\n\nاضبط:\n\n```bash\nexport PI_PYTHON_GATEWAY_URL=\"http://127.0.0.1:8888\"\n# اختياري:\nexport PI_PYTHON_GATEWAY_TOKEN=\"...\"\n```\n\nالاختلافات في السلوك عن البوابة المحلية المشتركة:\n\n- لا ملفات قفل/معلومات للبوابة المحلية\n- لا إطلاق/إنهاء لعملية محلية\n- تعمل فحوصات السلامة وعمليات CRUD للنواة على النقطة الطرفية الخارجية\n- تُعرض أخطاء المصادقة مع توجيه صريح بشأن الرمز المميز\n\n## استكشاف الأخطاء التشغيلية وإصلاحها (أنماط الفشل الحالية)\n\n- **أداة Python غير متاحة**\n  - تحقق من `python.toolMode` / `PI_PY`.\n  - إذا فشل الفحص المسبق، يتراجع وقت التشغيل إلى bash-only.\n\n- **أخطاء توافر النواة**\n  - يتطلب الوضع المحلي أن يكون كلٌّ من `kernel_gateway` و`ipykernel` قابلَين للاستيراد في وقت تشغيل Python المحلول.\n  - ثبّتهما بـ:\n\n    ```bash\n    python -m pip install jupyter_kernel_gateway ipykernel\n    ```\n\n- **`python.sharedGateway=false` يسبب فشل الإقلاع**\n  - هذا متوقع مع التطبيق الحالي.\n\n- **أخطاء مصادقة/وصول البوابة الخارجية**\n  - 401/403 -> اضبط `PI_PYTHON_GATEWAY_TOKEN`.\n  - مهلة/تعذر الوصول -> تحقق من URL/الشبكة وسلامة البوابة.\n\n- **تعليق التنفيذ ثم انتهاء المهلة**\n  - زد `timeout` الأداة (بحد أقصى 600 ثانية) إذا كان حجم العمل مشروعاً.\n  - للكود المتوقف، يُشغّل الإلغاء مقاطعة النواة لكن كود المستخدم قد يحتاج إلى إعادة هيكلة.\n\n- **مطالبات stdin/input في كود Python**\n  - `input()` غير مدعومة بشكل تفاعلي في مسار وقت التشغيل هذا؛ مرّر البيانات برمجياً.\n\n- **استنفاد الموارد (`EMFILE` / عدد كبير جداً من الملفات المفتوحة)**\n  - يُشغّل مدير الجلسات استرداد البوابة المشتركة (تفكيك الجلسة + إعادة تشغيل البوابة المشتركة).\n\n- **أخطاء دليل العمل**\n  - تتحقق الأداة من وجود `cwd` وكونه دليلاً قبل التنفيذ.\n\n## متغيرات البيئة ذات الصلة\n\n- `PI_PY` — تجاوز كشف الأداة (تعيين `bash-only`/`ipy-only`/`both` الموضّح أعلاه)\n- `PI_PYTHON_GATEWAY_URL` — استخدام بوابة خارجية\n- `PI_PYTHON_GATEWAY_TOKEN` — رمز مصادقة البوابة الخارجية الاختياري\n- `PI_PYTHON_SKIP_CHECK=1` — تجاوز فحوصات الإقلاع/التدفئة لـ Python\n- `PI_PYTHON_IPC_TRACE=1` — تسجيل آثار إرسال/استقبال IPC للنواة\n- `PI_DEBUG_STARTUP=1` — إصدار علامات تصحيح مرحلة الإقلاع\n",
	"ar/runtime-tools/bash-tool-runtime.md": "---\ntitle: بيئة تشغيل أداة Bash\ndescription: >-\n  بيئة تشغيل أداة Bash مع إدارة عمليات الصدفة، والعزل، والمهلة الزمنية، وتدفق\n  المخرجات.\nsidebar:\n  order: 1\n  label: أداة Bash\ni18n:\n  sourceHash: 18b12aa5dbd5\n  translator: machine\n---\n\n# بيئة تشغيل أداة Bash\n\nيصف هذا المستند مسار بيئة تشغيل **أداة `bash`** المستخدمة في استدعاءات أدوات الوكيل، بدءًا من تطبيع الأوامر إلى التنفيذ، والاقتطاع/القطع الأثرية، والعرض.\n\nكما يُوضّح أين يختلف السلوك في واجهة المستخدم النصية التفاعلية (TUI)، ووضع الطباعة، ووضع RPC، وتنفيذ الصدفة المُبادَر من المستخدم بعلامة التعجب (`!`).\n\n## النطاق وأسطح بيئة التشغيل\n\nهناك سطحان مختلفان لتنفيذ bash في coding-agent:\n\n1. **سطح استدعاء الأداة** (`toolName: \"bash\"`): يُستخدم عندما يستدعي النموذج أداة bash.\n   - نقطة الدخول: `BashTool.execute()`.\n2. **سطح أوامر علامة التعجب للمستخدم** (`!cmd` من الإدخال التفاعلي أو أمر RPC `bash`): مسار مساعد على مستوى الجلسة.\n   - نقطة الدخول: `AgentSession.executeBash()`.\n\nكلاهما يستخدم في النهاية `executeBash()` في `src/exec/bash-executor.ts` للتنفيذ بدون PTY، لكن مسار استدعاء الأداة فقط هو الذي يُشغّل منطق التطبيع/الاعتراض وعارض الأداة.\n\n## خط أنابيب استدعاء الأداة من البداية إلى النهاية\n\n## 1) تطبيع المدخلات ودمج المعاملات\n\nيقوم `BashTool.execute()` أولاً بتطبيع الأمر الخام عبر `normalizeBashCommand()`:\n\n- يستخرج `| head -n N`، `| head -N`، `| tail -n N`، `| tail -N` اللاحقة إلى حدود مُهيكَلة،\n- يقص المسافات البيضاء اللاحقة/السابقة،\n- يحافظ على المسافات البيضاء الداخلية كما هي.\n\nثم يدمج الحدود المُستخرَجة مع وسائط الأداة الصريحة:\n\n- وسائط `head`/`tail` الصريحة تتجاوز القيم المُستخرَجة،\n- القيم المُستخرَجة هي احتياطية فقط.\n\n### تنبيه\n\nتشير تعليقات `bash-normalize.ts` إلى إزالة `2>&1`، لكن التنفيذ الحالي لا يزيلها. سلوك وقت التشغيل لا يزال صحيحًا (stdout/stderr مدمجان بالفعل)، لكن سلوك التطبيع أضيق مما تُشير إليه التعليقات.\n\n## 2) الاعتراض الاختياري (مسار الأوامر المحظورة)\n\nإذا كان `bashInterceptor.enabled` مفعّلاً، يقوم `BashTool` بتحميل القواعد من الإعدادات وتشغيل `checkBashInterception()` على الأمر المُطبَّع.\n\nسلوك الاعتراض:\n\n- يُحظر الأمر **فقط** عندما:\n  - تتطابق قاعدة التعبير النمطي، و\n  - الأداة المُقترَحة موجودة في `ctx.toolNames`.\n- يتم تخطي قواعد التعبير النمطي غير الصالحة بصمت.\n- عند الحظر، يرمي `BashTool` خطأ `ToolError` برسالة:\n  - `Blocked: ...`\n  - الأمر الأصلي مضمّن.\n\nأنماط القواعد الافتراضية (المُعرَّفة في الكود) تستهدف الاستخدامات الخاطئة الشائعة:\n\n- قارئات الملفات (`cat`، `head`، `tail`، ...)\n- أدوات البحث (`grep`، `rg`، ...)\n- أدوات البحث عن الملفات (`find`، `fd`، ...)\n- المحررات الموضعية (`sed -i`، `perl -i`، `awk -i inplace`)\n- كتابة إعادة توجيه الصدفة (`echo ... > file`، إعادة توجيه heredoc)\n\n### تنبيه\n\nيتضمن `InterceptionResult` حقل `suggestedTool`، لكن `BashTool` حاليًا يعرض فقط نص الرسالة (لا يوجد حقل أداة مُقترَحة مُهيكَل في `details`).\n\n## 3) التحقق من CWD وتقييد المهلة الزمنية\n\nيتم حل `cwd` نسبةً إلى cwd الجلسة (`resolveToCwd`)، ثم التحقق منه عبر `stat`:\n\n- المسار المفقود -> `ToolError(\"Working directory does not exist: ...\")`\n- ليس مجلداً -> `ToolError(\"Working directory is not a directory: ...\")`\n\nتُقيَّد المهلة الزمنية في النطاق `[1, 3600]` ثانية وتُحوَّل إلى ميلي ثانية.\n\n## 4) تخصيص القطعة الأثرية\n\nقبل التنفيذ، تُخصّص الأداة مسار/معرّف قطعة أثرية (بأفضل جهد) لتخزين المخرجات المقتطعة.\n\n- فشل تخصيص القطعة الأثرية غير مُوقِف (يستمر التنفيذ بدون ملف تفريغ القطعة الأثرية)،\n- معرّف/مسار القطعة الأثرية يُمرَّر إلى مسار التنفيذ للحفظ الكامل للمخرجات عند الاقتطاع.\n\n## 5) اختيار تنفيذ PTY مقابل غير PTY\n\nيختار `BashTool` تنفيذ PTY فقط عندما تتحقق جميع الشروط التالية:\n\n- `bash.virtualTerminal === \"on\"`\n- `PI_NO_PTY !== \"1\"`\n- سياق الأداة يحتوي على واجهة مستخدم (`ctx.hasUI === true` و`ctx.ui` مُعيَّن)\n\nوإلا فإنه يستخدم `executeBash()` غير التفاعلي.\n\nهذا يعني أن وضع الطباعة وسياقات RPC/الأداة بدون واجهة مستخدم تستخدم دائمًا غير PTY.\n\n## محرك التنفيذ غير التفاعلي (`executeBash`)\n\n## نموذج إعادة استخدام جلسة الصدفة\n\nيُخزّن `executeBash()` مؤقتًا نُسَخ `Shell` الأصلية في خريطة عامة على مستوى العملية مُفهرَسة بـ:\n\n- مسار الصدفة،\n- بادئة الأمر المُهيأة،\n- مسار اللقطة،\n- بيئة الصدفة المُسَلسَلة،\n- مفتاح جلسة الوكيل الاختياري.\n\nلعمليات التنفيذ على مستوى الجلسة، يُمرِّر `AgentSession.executeBash()` القيمة `sessionKey: this.sessionId`، مما يعزل إعادة الاستخدام لكل جلسة.\n\nمسار استدعاء الأداة **لا** يُمرِّر `sessionKey`، لذا نطاق إعادة الاستخدام يعتمد على تكوين الصدفة/اللقطة/البيئة.\n\n## تكوين الصدفة وسلوك اللقطة\n\nعند كل استدعاء، يُحمّل المُنفِّذ تكوين صدفة الإعدادات (`shell`، `env`، `prefix` اختياري).\n\nإذا تضمنت الصدفة المُحدَّدة `bash`، يحاول `getOrCreateSnapshot()`:\n\n- تلتقط اللقطة الأسماء المستعارة/الدوال/الخيارات من ملف rc الخاص بالمستخدم،\n- إنشاء اللقطة بأفضل جهد،\n- الفشل يعود إلى عدم استخدام لقطة.\n\nإذا تم تكوين `prefix`، يصبح الأمر:\n\n```text\n<prefix> <command>\n```\n\n## التدفق والإلغاء\n\nيُدفّق `Shell.run()` القطع إلى دالة رد الاتصال. يُمرِّر المُنفِّذ كل قطعة إلى `OutputSink` ودالة رد اتصال `onChunk` الاختيارية.\n\nالإلغاء:\n\n- إشارة الإلغاء المُفعَّلة تُطلق `shellSession.abort(...)`،\n- المهلة الزمنية من النتيجة الأصلية تُعيَّن إلى `cancelled: true` + نص توضيحي،\n- الإلغاء الصريح يُعيد بالمثل `cancelled: true` + توضيح.\n\nلا يُرمى استثناء داخل المُنفِّذ عند المهلة الزمنية/الإلغاء؛ يُعيد `BashResult` مُهيكَل ويترك للمُستدعي تعيين دلالات الخطأ.\n\n## مسار PTY التفاعلي (`runInteractiveBashPty`)\n\nعند تفعيل PTY، تُشغّل الأداة `runInteractiveBashPty()` التي تفتح مكون تراكب وحدة التحكم وتُدير جلسة `PtySession` أصلية.\n\nأبرز السلوكيات:\n\n- طرفية xterm-headless الافتراضية تعرض نافذة العرض في التراكب،\n- إدخال لوحة المفاتيح يُطبَّع (بما في ذلك تسلسلات Kitty ومعالجة وضع مؤشر التطبيق)،\n- `esc` أثناء التشغيل يُنهي جلسة PTY،\n- تغيير حجم الطرفية يُنشَر إلى PTY (`session.resize(cols, rows)`).\n\nتُحقَن إعدادات تقوية البيئة الافتراضية للتشغيلات غير المُراقَبة:\n\n- تعطيل أدوات العرض المُقسَّم (`PAGER=cat`، `GIT_PAGER=cat`، إلخ.)،\n- تعطيل مطالبات المحرر (`GIT_EDITOR=true`، `EDITOR=true`، ...)،\n- تقليل مطالبات الطرفية/المصادقة (`GIT_TERMINAL_PROMPT=0`، `SSH_ASKPASS=/usr/bin/false`، `CI=1`)،\n- علامات أتمتة مدير الحزم/الأدوات للسلوك غير التفاعلي.\n\nتُطبَّع مخرجات PTY (`CRLF`/`CR` إلى `LF`، `sanitizeText`) وتُكتَب في `OutputSink`، بما في ذلك دعم تفريغ القطعة الأثرية.\n\nعند خطأ بدء/تشغيل PTY، يستقبل الحوض سطر `PTY error: ...` ويُنهى الأمر بكود خروج غير مُحدَّد.\n\n## معالجة المخرجات: التدفق، والاقتطاع، وتفريغ القطعة الأثرية\n\nيستخدم كلا مسارَي PTY وغير PTY حوض `OutputSink`.\n\n## دلالات OutputSink\n\n- يحتفظ بمخزن ذيلي في الذاكرة آمن لـ UTF-8 (`DEFAULT_MAX_BYTES`، حاليًا 50 كيلوبايت)،\n- يتتبع إجمالي البايتات/الأسطر المُشاهَدة،\n- إذا كان مسار القطعة الأثرية موجودًا وتجاوزت المخرجات (أو الملف نشط بالفعل)، يكتب التدفق الكامل إلى ملف القطعة الأثرية،\n- عند تجاوز عتبة الذاكرة، يقتطع المخزن المؤقت في الذاكرة إلى الذيل (آمن لحدود UTF-8)،\n- يُعلّم `truncated` عند حدوث التجاوز/تفريغ الملف.\n\nيُعيد `dump()`:\n\n- `output` (بادئة توضيحية محتملة)،\n- `truncated`،\n- `totalLines/totalBytes`،\n- `outputLines/outputBytes`،\n- `artifactId` إذا كان ملف القطعة الأثرية نشطًا.\n\n### تنبيه حول المخرجات الطويلة\n\nاقتطاع وقت التشغيل يعتمد على عتبة البايتات في `OutputSink` (50 كيلوبايت افتراضيًا). لا يفرض حدًا صارمًا بـ 2000 سطر في مسار الكود هذا.\n\n## تحديثات الأداة المباشرة\n\nللتنفيذ بدون PTY، يستخدم `BashTool` مخزن `TailBuffer` منفصل للتحديثات الجزئية ويُرسل لقطات `onUpdate` أثناء تشغيل الأمر.\n\nلتنفيذ PTY، يُعالَج العرض المباشر بواسطة تراكب واجهة مستخدم مخصص، وليس بقطع نص `onUpdate`.\n\n## تشكيل النتيجة، والبيانات الوصفية، وتعيين الأخطاء\n\nبعد التنفيذ:\n\n1. معالجة `cancelled`:\n   - إذا كانت إشارة الإلغاء مُفعَّلة -> يرمي `ToolAbortError` (دلالات الإلغاء)،\n   - وإلا -> يرمي `ToolError` (يُعامَل كفشل في الأداة).\n2. `timedOut` في PTY -> يرمي `ToolError`.\n3. تطبيق مرشحات head/tail على نص المخرجات النهائي (`applyHeadTail`، head ثم tail).\n4. المخرجات الفارغة تصبح `(no output)`.\n5. إرفاق بيانات وصفية للاقتطاع عبر `toolResult(...).truncationFromSummary(result, { direction: \"tail\" })`.\n6. تعيين كود الخروج:\n   - كود خروج مفقود -> `ToolError(\"... missing exit status\")`\n   - كود خروج غير صفري -> `ToolError(\"... Command exited with code N\")`\n   - كود خروج صفري -> نتيجة نجاح.\n\nهيكل حمولة النجاح:\n\n- `content`: نص المخرجات،\n- `details.meta.truncation` عند الاقتطاع، تتضمن:\n  - `direction`، `truncatedBy`، عدد الأسطر+البايتات الإجمالية/المخرجة،\n  - `shownRange`،\n  - `artifactId` عند توفره.\n\nلأن الأدوات المُدمَجة مُغلَّفة بـ `wrapToolWithMetaNotice()`، يُلحَق نص إشعار الاقتطاع بمحتوى النص النهائي تلقائيًا (على سبيل المثال: `Full: artifact://<id>`).\n\n## مسارات العرض\n\n## عارض استدعاء الأداة (`bashToolRenderer`)\n\nيُستخدم `bashToolRenderer` لرسائل استدعاء الأداة (`toolCall` / `toolResult`):\n\n- الوضع المطوي يعرض معاينة مقتطعة بصريًا حسب الأسطر،\n- الوضع الموسّع يعرض كل نص المخرجات المتاح حاليًا،\n- سطر التحذير يتضمن سبب الاقتطاع و`artifact://<id>` عند الاقتطاع،\n- قيمة المهلة الزمنية (من الوسائط) تُعرض في سطر البيانات الوصفية بالتذييل.\n\n### تنبيه: توسيع القطعة الأثرية الكاملة\n\nيحتوي `BashRenderContext` على `isFullOutput`، لكن مُنشئ سياق العارض الحالي لا يُعيّنه لنتائج أداة bash. العرض الموسّع لا يزال يستخدم النص الموجود بالفعل في محتوى النتيجة (مخرجات الذيل/المقتطعة) ما لم يوفر مُستدعٍ آخر محتوى القطعة الأثرية الكامل.\n\n## مكون أوامر علامة التعجب للمستخدم (`BashExecutionComponent`)\n\nمكون `BashExecutionComponent` مخصص لأوامر `!` الخاصة بالمستخدم في الوضع التفاعلي (وليس استدعاءات أدوات النموذج):\n\n- يُدفّق القطع مباشرةً،\n- المعاينة المطوية تحتفظ بآخر 20 سطرًا منطقيًا،\n- تقييد الأسطر عند 4000 حرف لكل سطر،\n- يعرض تحذيرات الاقتطاع والقطعة الأثرية عند وجود بيانات وصفية،\n- يُعلّم حالة الإلغاء/الخطأ/الخروج بشكل منفصل.\n\nيُوصَّل هذا المكون بواسطة `CommandController.handleBashCommand()` ويُغذّى من `AgentSession.executeBash()`.\n\n## اختلافات السلوك حسب الوضع\n\n| السطح                        | مسار الدخول                                            | مؤهل لـ PTY                                                         | تجربة مستخدم المخرجات المباشرة                                                           | عرض الأخطاء                                  |\n| ------------------------------ | ----------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------ |\n| استدعاء أداة تفاعلي          | `BashTool.execute`                                    | نعم، عندما `bash.virtualTerminal=on` وواجهة المستخدم موجودة و`PI_NO_PTY!=1` | تراكب PTY (تفاعلي) أو تحديثات ذيلية مُدفّقة                       | أخطاء الأداة تصبح `toolResult.isError`          |\n| استدعاء أداة وضع الطباعة           | `BashTool.execute`                                    | لا (لا يوجد سياق واجهة مستخدم)                                                   | لا تراكب TUI؛ المخرجات تظهر في تدفق الأحداث/نص المساعد النهائي | نفس تعيين أخطاء الأداة                          |\n| استدعاء أداة RPC (أدوات الوكيل)  | `BashTool.execute`                                    | عادةً لا توجد واجهة مستخدم -> غير PTY                                             | أحداث/نتائج أدوات مُهيكَلة                                           | نفس تعيين أخطاء الأداة                          |\n| أمر علامة التعجب التفاعلي (`!`) | `AgentSession.executeBash` + `BashExecutionComponent` | لا (يستخدم المُنفِّذ مباشرةً)                                          | مكون تنفيذ bash مخصص                                       | المتحكم يلتقط الاستثناءات ويعرض خطأ واجهة المستخدم |\n| أمر RPC `bash`             | `rpc-mode` -> `session.executeBash`                   | لا                                                                   | يُعيد `BashResult` مباشرةً                                            | المُستهلك يتعامل مع الحقول المُعادة                 |\n\n## تنبيهات تشغيلية\n\n- المُعترض يحظر الأوامر فقط عندما تكون الأداة المُقترَحة متاحة حاليًا في السياق.\n- إذا فشل تخصيص القطعة الأثرية، لا يزال الاقتطاع يحدث لكن لا يتوفر مرجع `artifact://` خلفي.\n- ذاكرة تخزين جلسة الصدفة المؤقتة ليس لديها إخلاء صريح في هذه الوحدة؛ مدة الحياة على نطاق العملية.\n- أسطح مهلة PTY وغير PTY تختلف:\n  - PTY يكشف حقل نتيجة `timedOut` صريح،\n  - غير PTY يُعيّن المهلة الزمنية إلى ملخص `cancelled + annotation`.\n\n## ملفات التنفيذ\n\n- [`src/tools/bash.ts`](../../packages/coding-agent/src/tools/bash.ts) — نقطة دخول الأداة، التطبيع/الاعتراض، اختيار PTY/غير PTY، تعيين النتائج/الأخطاء، عارض أداة bash.\n- [`src/tools/bash-normalize.ts`](../../packages/coding-agent/src/tools/bash-normalize.ts) — تطبيع الأوامر وتصفية head/tail بعد التشغيل.\n- [`src/tools/bash-interceptor.ts`](../../packages/coding-agent/src/tools/bash-interceptor.ts) — مطابقة قواعد المُعترض ورسائل الأوامر المحظورة.\n- [`src/exec/bash-executor.ts`](../../packages/coding-agent/src/exec/bash-executor.ts) — المُنفِّذ بدون PTY، إعادة استخدام جلسة الصدفة، توصيل الإلغاء، تكامل حوض المخرجات.\n- [`src/tools/bash-interactive.ts`](../../packages/coding-agent/src/tools/bash-interactive.ts) — بيئة تشغيل PTY، تراكب واجهة المستخدم، تطبيع الإدخال، إعدادات البيئة غير التفاعلية الافتراضية.\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — اقتطاع `OutputSink`/تفريغ القطعة الأثرية والبيانات الوصفية الملخصة.\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — أدوات مساعدة لتخصيص القطعة الأثرية ومخزن التدفق الذيلي.\n- [`src/tools/output-meta.ts`](../../packages/coding-agent/src/tools/output-meta.ts) — شكل بيانات الاقتطاع الوصفية + غلاف حقن الإشعار.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — `executeBash` على مستوى الجلسة، تسجيل الرسائل، دورة حياة الإلغاء.\n- [`src/modes/components/bash-execution.ts`](../../packages/coding-agent/src/modes/components/bash-execution.ts) — مكون تنفيذ أمر `!` التفاعلي.\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts) — توصيل تدفق/تحديث إكمال واجهة مستخدم أمر `!` التفاعلي.\n- [`src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts) — سطح أوامر RPC `bash` و`abort_bash`.\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — حل `artifact://<id>`.\n",
	"ar/runtime-tools/context-command.md": "---\ntitle: سياقات F5 XC\ndescription: >-\n  ربط xcsh بمستأجري F5 Distributed Cloud -- إنشاء سياقات المصادقة والتبديل بينها\n  وإدارتها.\nsidebar:\n  order: 1\n  label: سياقات F5 XC\ni18n:\n  sourceHash: a9cccbc338f0\n  translator: machine\n---\n\n# سياقات F5 XC\n\nيتصل xcsh بخدمة F5 Distributed Cloud من خلال **السياقات** -- وهي مجموعات بيانات اعتماد مسمّاة تربط عنوان URL للمستأجر ورمز API ومساحة الأسماء. إذا كنت قد استخدمت `kubectl config use-context` أو `kubectx` من قبل، فسير العمل مطابق تماماً: أنشئ سياقاً، وتبديل بين السياقات بالاسم، واستخدم `-` للعودة السريعة.\n\n## البدء\n\n### 1. إنشاء أول سياق\n\nتحتاج إلى ثلاثة أشياء من وحدة تحكم F5 XC الخاصة بك: عنوان URL للمستأجر، ورمز API، واختيارياً مساحة أسماء.\n\n```\n/context create production https://acme.console.ves.volterra.io p12k3-your-api-token\n```\n\n```\nContext 'production' created. Use /context activate production to switch to it.\n```\n\nأو استخدم المعالج الإرشادي إذا كنت تفضل المطالبات التفاعلية خطوة بخطوة:\n\n```\n/context wizard\n```\n\n### 2. تفعيله\n\n```\n/context production\n```\n\n```\n╭─ production ─────────────────────────────────────────────────╮\n│ XCSH_TENANT     acme                                         │\n│ XCSH_API_URL    https://acme.console.ves.volterra.io         │\n│ XCSH_API_TOKEN  ...oken                                      │\n│ Status          Connected (312ms)                            │\n├─ Environment ────────────────────────────────────────────────┤\n│ XCSH_NAMESPACE  default                                      │\n╰──────────────────────────────────────────────────────────────╯\n```\n\nبمجرد التفعيل، يحقن xcsh بيانات اعتماد المستأجر في جلستك. يمكن للوكيل الآن إجراء استدعاءات API لـ F5 XC، ويعرض شريط الحالة السياق النشط.\n\n### 3. إضافة المزيد من السياقات والتبديل بينها\n\n```\n/context create staging https://staging.console.ves.volterra.io p12k3-staging-token\n```\n\nالتبديل بالاسم -- لا حاجة لأمر فرعي:\n\n```\n/context staging\n```\n\nالعودة إلى السياق السابق (بأسلوب `cd -`):\n\n```\n/context -\n```\n\nاستدعاء `/context -` مرتين يعيدك إلى نقطة البداية.\n\n### 4. عرض ما لديك\n\n```\n/context\n```\n\n```\n  production           https://acme.console.ves.volterra.io\n* staging              https://staging.console.ves.volterra.io\n```\n\nعلامة `*` تشير إلى السياق النشط.\n\n## الأوامر اليومية\n\n| الأمر | ما يفعله |\n|---|---|\n| `/context` | عرض قائمة بجميع السياقات |\n| `/context <name>` | التبديل إلى سياق معين |\n| `/context -` | التبديل إلى السياق السابق |\n| `/context show` | عرض تفاصيل السياق النشط (مع إخفاء الرموز) |\n| `/context status` | عرض حالة المصادقة الحالية |\n\n## دورة حياة السياق\n\n| الأمر | ما يفعله |\n|---|---|\n| `/context create <name> <url> <token> [namespace]` | إنشاء سياق |\n| `/context delete <name> --confirm` | حذف سياق (يتطلب `--confirm`) |\n| `/context rename <old> <new>` | إعادة تسمية سياق |\n| `/context validate <name>` | اختبار بيانات الاعتماد دون التبديل |\n| `/context export [name] [--include-token]` | التصدير بتنسيق JSON (الرموز مخفية افتراضياً) |\n| `/context import <path-or-json> [--overwrite]` | الاستيراد من ملف أو JSON مضمّن |\n| `/context wizard` | إعداد تفاعلي إرشادي |\n\n## تبديل مساحات الأسماء\n\nلكل سياق مساحة أسماء افتراضية. يمكنك تبديلها دون تغيير السياق:\n\n```\n/context namespace system\n```\n\nيوفر الإكمال التلقائي بالتاب أسماء مساحات الأسماء من المستأجر النشط.\n\n## متغيرات البيئة على السياقات\n\nيمكن أن تحمل السياقات متغيرات بيئة إضافية تُحقن في جلستك عند التفعيل. مفيدة للتكوينات الخاصة بكل مستأجر والتي ليست جزءاً من مجموعة بيانات الاعتماد.\n\n```\n/context set CUSTOM_HEADER=x-acme-trace\n/context set LOG_LEVEL=debug\n/context env list\n/context unset LOG_LEVEL\n```\n\nالأسماء البديلة: `add` = `set`، `remove`/`clear` = `unset`.\n\n## الإكمال التلقائي بالتاب\n\nاكتب `/context ` واضغط Tab. تعرض القائمة المنسدلة:\n\n1. **أسماء السياقات** -- مع تلميحات عنوان URL للمستأجر، حتى تتمكن من التمييز بين المستأجرين\n2. **`-`** -- يظهر عندما تكون قد بدّلت سابقاً، ويعرض السياق الذي ستنتقل إليه\n3. **الأوامر الفرعية** -- `list`، `create`، `delete`، إلخ.\n\nتظهر أسماء السياقات أولاً لأن التبديل هو الإجراء الأكثر شيوعاً.\n\nيعمل الإكمال التلقائي على مستوى الأوامر الفرعية أيضاً: `/context activate <Tab>` يُكمل أسماء السياقات، `/context namespace <Tab>` يُكمل مساحات الأسماء، `/context unset <Tab>` يُكمل مفاتيح متغيرات البيئة المعروفة.\n\n## قواعد التسمية\n\nيجب أن تتكون أسماء السياقات من 1 إلى 64 حرفاً: أحرف، أرقام، شرطات، شرطات سفلية.\n\nتُرفض الأسماء التي تتعارض مع الأوامر الفرعية:\n\n```\n/context create list https://example.com tok\n```\n\n```\nError: Context name 'list' conflicts with a /context subcommand. Choose a different name.\n```\n\nمجموعة الأسماء المحجوزة الكاملة: `list`، `show`، `status`، `create`، `delete`، `rename`، `namespace`، `env`، `set`، `unset`، `add`، `remove`، `clear`، `activate`، `validate`، `export`، `import`، `wizard`، `help`. المقارنة غير حساسة لحالة الأحرف.\n\n## تجاوز متغيرات البيئة\n\nإذا كان `XCSH_API_URL` و `XCSH_API_TOKEN` معيّنين في بيئة الصدفة الخاصة بك قبل تشغيل xcsh، فإنهما يأخذان الأولوية على أي سياق. هذا مفيد لأنابيب CI/CD أو الجلسات لمرة واحدة حيث لا تريد إنشاء سياق دائم.\n\nعند التشغيل في هذا الوضع، يعرض `/context` بيانات الاعتماد المأخوذة من البيئة مع تسمية `(via env vars)`.\n\n## سلوك السياق السابق\n\n- **محدود بالجلسة**: يُعاد تعيين السياق السابق عند إعادة تشغيل xcsh. ولا يُحفظ على القرص.\n- **التبديل المتبادل**: `/context -` مرتين يعيدك إلى نقطة البداية.\n- **آمن عبر التعديلات**: إذا حذفت السياق السابق، يُمسح المؤشر. إذا أعدت تسميته، يتبع المؤشر الاسم الجديد.\n- **إعادة التفعيل لا تفعل شيئاً**: `/context production` عندما تكون بالفعل على `production` لا يُعيد تعيين مؤشر السياق السابق.\n\n## اصطلاحات التصميم\n\nتتبع تجربة المستخدم لـ `/context`:\n\n- **kubectx**: `kubectx <name>` للتبديل، `kubectx -` للسابق، `kubectx` بدون معاملات للعرض\n- **kubectl**: `kubectl config use-context` للصيغة الصريحة\n- **الصدفة**: `cd -` / `OLDPWD` لتتبع المجلد السابق\n",
	"ar/runtime-tools/custom-tools.md": "---\ntitle: الأدوات المخصصة\ndescription: تسجيل الأدوات المخصصة وتعريف المخطط وخط أنابيب التنفيذ لتوسيع قدرات العامل.\nsidebar:\n  order: 4\n  label: الأدوات المخصصة\ni18n:\n  sourceHash: 5f4a441fc2e2\n  translator: machine\n---\n\n# الأدوات المخصصة\n\nالأدوات المخصصة هي دوال قابلة للاستدعاء بواسطة النموذج، وتندمج في خط أنابيب تنفيذ الأدوات نفسه المستخدم مع الأدوات المدمجة.\n\nالأداة المخصصة هي وحدة TypeScript/JavaScript تُصدِّر مصنعاً (factory). يتلقى المصنع واجهة برمجية للمضيف (`CustomToolAPI`) ويعيد أداة واحدة أو مصفوفة من الأدوات.\n\n## ما هذا (وما ليس كذلك)\n\n- **الأداة المخصصة**: قابلة للاستدعاء بواسطة النموذج خلال الدور (`execute` + مخطط TypeBox).\n- **الامتداد**: إطار عمل دورة الحياة/الأحداث الذي يمكنه تسجيل الأدوات واعتراض/تعديل الأحداث.\n- **الخطاف (Hook)**: نصوص برمجية خارجية قبل/بعد الأوامر.\n- **المهارة (Skill)**: حزمة إرشادات/سياق ثابتة، وليست كوداً تنفيذياً للأداة.\n\nإذا كنت تحتاج إلى أن يستدعي النموذج الكود مباشرةً، فاستخدم أداة مخصصة.\n\n## مسارات التكامل في الكود الحالي\n\nثمة أسلوبان نشطان للتكامل:\n\n1. **الأدوات المخصصة المقدمة من SDK** (`options.customTools`)\n   - تُغلَّف في أدوات العامل عبر `CustomToolAdapter` أو أغلفة الامتداد.\n   - تُدرج دائماً في مجموعة الأدوات النشطة الأولية عند تمهيد SDK.\n\n2. **الوحدات المكتشفة من نظام الملفات عبر واجهة المُحمِّل** (`discoverAndLoadCustomTools` / `loadCustomTools`)\n   - مكشوفة كواجهات برمجية للمكتبة في `src/extensibility/custom-tools/loader.ts`.\n   - يمكن لكود المضيف استدعاؤها لاكتشاف وتحميل وحدات الأدوات من مسارات الإعداد/الموفر/الإضافة.\n\n```text\nتدفق استدعاء أداة النموذج\n\nاستدعاء أداة LLM\n   │\n   ▼\nسجل الأدوات (المدمجة + محولات الأدوات المخصصة)\n   │\n   ▼\nCustomTool.execute(toolCallId, params, onUpdate, ctx, signal)\n   │\n   ├─ onUpdate(...)  -> نتيجة جزئية متدفقة\n   └─ return result  -> محتوى/تفاصيل الأداة النهائية\n```\n\n## مواقع الاكتشاف (واجهة المُحمِّل)\n\n`discoverAndLoadCustomTools(configuredPaths, cwd, builtInToolNames)` تدمج:\n\n1. موفري القدرات (`toolCapability`)، بما في ذلك:\n   - إعداد OMP الأصلي (`~/.xcsh/agent/tools`, `.xcsh/tools`)\n   - إعداد Claude (`~/.claude/tools`, `.claude/tools`)\n   - إعداد Codex (`~/.codex/tools`, `.codex/tools`)\n   - موفر ذاكرة التخزين المؤقت لإضافة سوق Claude\n2. بيانات تعريف الإضافات المثبتة (`~/.xcsh/plugins/node_modules/*` عبر مُحمِّل الإضافات)\n3. المسارات المُعدَّة الصريحة الممررة إلى المُحمِّل\n\n### سلوك مهم\n\n- المسارات المحللة المكررة تُزال تلقائياً.\n- تعارضات أسماء الأدوات مرفوضة مقابل الأدوات المدمجة والأدوات المخصصة المحملة مسبقاً.\n- ملفات `.md` و`.json` تُكتشف كبيانات تعريف للأدوات من قِبل بعض الموفرين، لكن مُحمِّل الوحدة التنفيذية يرفضها كأدوات قابلة للتشغيل.\n- المسارات المُعدَّة النسبية تُحسم من `cwd`؛ ويُوسَّع الرمز `~`.\n\n## عقد الوحدة\n\nيجب أن تُصدِّر وحدة الأداة المخصصة دالة (يُفضَّل التصدير الافتراضي):\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = (pi) => ({\n name: \"repo_stats\",\n label: \"Repo Stats\",\n description: \"Counts tracked TypeScript files\",\n parameters: pi.typebox.Type.Object({\n  glob: pi.typebox.Type.Optional(pi.typebox.Type.String({ default: \"**/*.ts\" })),\n }),\n\n async execute(toolCallId, params, onUpdate, ctx, signal) {\n  onUpdate?.({\n   content: [{ type: \"text\", text: \"Scanning files...\" }],\n   details: { phase: \"scan\" },\n  });\n\n  const result = await pi.exec(\"git\", [\"ls-files\", params.glob ?? \"**/*.ts\"], { signal, cwd: pi.cwd });\n  if (result.killed) {\n   throw new Error(\"Scan was cancelled\");\n  }\n  if (result.code !== 0) {\n   throw new Error(result.stderr || \"git ls-files failed\");\n  }\n\n  const files = result.stdout.split(\"\\n\").filter(Boolean);\n  return {\n   content: [{ type: \"text\", text: `Found ${files.length} files` }],\n   details: { count: files.length, sample: files.slice(0, 10) },\n  };\n },\n\n onSession(event) {\n  if (event.reason === \"shutdown\") {\n   // cleanup resources if needed\n  }\n },\n});\n\nexport default factory;\n```\n\nنوع إرجاع المصنع:\n\n- `CustomTool`\n- `CustomTool[]`\n- `Promise<CustomTool | CustomTool[]>`\n\n## سطح الواجهة البرمجية الممررة إلى المصانع (`CustomToolAPI`)\n\nمن `types.ts` و`loader.ts`:\n\n- `cwd`: دليل عمل المضيف\n- `exec(command, args, options?)`: مساعد تنفيذ العمليات\n- `ui`: سياق واجهة المستخدم (يمكن أن يكون بدون تأثير في الأوضاع غير الرأسية)\n- `hasUI`: `false` في التدفقات غير التفاعلية\n- `logger`: مسجِّل الملفات المشترك\n- `typebox`: `@sinclair/typebox` المحقون\n- `pi`: صادرات `@f5-sales-demo/xcsh` المحقونة\n- `pushPendingAction(action)`: تسجيل إجراء معاينة لأداة `resolve` المخفية (`docs/resolve-tool-runtime.md`)\n\nيبدأ المُحمِّل بسياق واجهة مستخدم بدون تأثير، ويتطلب من كود المضيف استدعاء `setUIContext(...)` عند جاهزية واجهة المستخدم الحقيقية.\n\n## عقد التنفيذ والكتابة\n\nتوقيع `CustomTool.execute`:\n\n```ts\nexecute(toolCallId, params, onUpdate, ctx, signal)\n```\n\n- `params` مكتوب بشكل ثابت من مخطط TypeBox الخاص بك عبر `Static<TParams>`.\n- التحقق من صحة وسيطات وقت التشغيل يحدث قبل التنفيذ في حلقة العامل.\n- `onUpdate` يُصدر نتائج جزئية لبث واجهة المستخدم.\n- `ctx` يتضمن حالة الجلسة/النموذج ومساعد `abort()`.\n- `signal` يحمل إشارة الإلغاء.\n\n`CustomToolAdapter` يُجسِّر هذا إلى واجهة أداة العامل ويُعيد توجيه الاستدعاءات بالترتيب الصحيح للوسيطات.\n\n## كيفية كشف الأدوات للنموذج\n\n- تُغلَّف الأدوات في مثيلات `AgentTool` (`CustomToolAdapter` أو أغلفة الامتداد).\n- تُدرج في سجل أدوات الجلسة حسب الاسم.\n- في تمهيد SDK، تُدرج الأدوات المخصصة والمسجلة بواسطة الامتداد إدراجاً إجبارياً في المجموعة النشطة الأولية.\n- يتحقق `--tools` في CLI حالياً من أسماء الأدوات المدمجة فقط؛ ويُعالج إدراج الأدوات المخصصة عبر مسارات الاكتشاف/التسجيل وخيارات SDK.\n\n## خطافات التصيير\n\nخطافات التصيير الاختيارية:\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\nسلوك وقت التشغيل في TUI:\n\n- إذا كانت الخطافات موجودة، يُصيَّر ناتج الأداة داخل حاوية `Box`.\n- تتلقى `renderResult` القيمة `{ expanded, isPartial, spinnerFrame? }`.\n- أخطاء المُصيِّر تُلتقط وتُسجَّل؛ وتعود واجهة المستخدم إلى التصيير النصي الافتراضي.\n\n## معالجة الجلسة/الحالة\n\n`onSession(event, ctx)` الاختياري يتلقى أحداث دورة حياة الجلسة، بما في ذلك:\n\n- `start`, `switch`, `branch`, `tree`, `shutdown`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`, `todo_reminder`\n\nاستخدم `ctx.sessionManager` لإعادة بناء الحالة من السجل عند تغيُّر سياق الفرع/الجلسة.\n\n## دلالات الإخفاقات والإلغاء\n\n### الإخفاقات المتزامنة/غير المتزامنة\n\n- الإلقاء (أو الوعود المرفوضة) في `execute` يُعامَل كإخفاق للأداة.\n- وقت تشغيل العامل يحول الإخفاقات إلى رسائل نتيجة أداة مع `isError: true` ومحتوى نص الخطأ.\n- مع أغلفة الامتداد، يمكن لمعالجات `tool_result` إعادة كتابة المحتوى/التفاصيل وحتى تجاوز حالة الخطأ.\n\n### الإلغاء\n\n- إلغاء العامل يُشاع عبر `AbortSignal` إلى `execute`.\n- مرِّر `signal` إلى عمل العمليات الفرعية (`pi.exec(..., { signal })`) للإلغاء التعاوني.\n- `ctx.abort()` يسمح لأداة بطلب إلغاء عملية العامل الحالية.\n\n### أخطاء onSession\n\n- أخطاء `onSession` تُلتقط وتُسجَّل كتحذيرات؛ ولا تتسبب في تعطُّل الجلسة.\n\n## القيود الحقيقية التي يجب مراعاتها في التصميم\n\n- يجب أن تكون أسماء الأدوات فريدة عالمياً في السجل النشط.\n- فضِّل المخرجات المحددة بالمخطط والحتمية في `details` لإعادة بناء المُصيِّر/الحالة.\n- احرص على الحماية عند استخدام واجهة المستخدم مع `pi.hasUI`.\n- عامِل ملفات `.md`/`.json` في أدلة الأدوات كبيانات تعريف، وليس وحدات تنفيذية.\n",
	"ar/runtime-tools/notebook-tool-runtime.md": "---\ntitle: آليات تشغيل أداة Notebook الداخلية\ndescription: >-\n  وقت تشغيل أداة Jupyter notebook مع تنفيذ الخلايا ودورة حياة النواة وعرض\n  المخرجات.\nsidebar:\n  order: 2\n  label: أداة Notebook\ni18n:\n  sourceHash: c1bafcb245e4\n  translator: machine\n---\n\n# آليات تشغيل أداة Notebook الداخلية\n\nيصف هذا المستند التنفيذ الحالي لأداة `notebook` وعلاقتها بوقت تشغيل Python المدعوم بالنواة.\n\nالتمييز الجوهري: **`notebook` هي محرر دفاتر JSON، وليست منفّذة دفاتر**. تقوم بتعديل مصادر خلايا `.ipynb` مباشرةً؛ ولا تبدأ أو تتواصل مع نواة Python.\n\n## ملفات التنفيذ\n\n- [`src/tools/notebook.ts`](../../packages/coding-agent/src/tools/notebook.ts)\n- [`src/ipy/executor.ts`](../../packages/coding-agent/src/ipy/executor.ts)\n- [`src/ipy/kernel.ts`](../../packages/coding-agent/src/ipy/kernel.ts)\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts)\n- [`src/tools/python.ts`](../../packages/coding-agent/src/tools/python.ts)\n\n## 1) حدود وقت التشغيل: التحرير مقابل التنفيذ\n\n## أداة `notebook` (`src/tools/notebook.ts`)\n\n- تدعم `action: edit | insert | delete` على ملف `.ipynb`.\n- تحلّ المسار نسبةً إلى CWD الخاص بالجلسة (`resolveToCwd`).\n- تحمّل JSON الخاص بالدفتر، وتتحقق من صحة مصفوفة `cells`، وتتحقق من حدود `cell_index`.\n- تُطبّق تعديلات المصدر في الذاكرة وتكتب JSON الكامل للدفتر مجدداً باستخدام `JSON.stringify(notebook, null, 1)`.\n- تُعيد ملخصاً نصياً مع `details` منظّمة (`action` و`cellIndex` و`cellType` و`totalCells` و`cellSource`).\n\nلا توجد دورة حياة للنواة في هذه الأداة:\n\n- لا استحواذ على بوابة\n- لا معرّف جلسة للنواة\n- لا `execute_request`\n- لا قطع بث من قنوات النواة\n- لا التقاط لعرض غني (`image/png` وعرض JSON وحالة MIME)\n\n## مسار التنفيذ الشبيه بالدفتر (`src/tools/python.ts` + `src/ipy/*`)\n\nعندما يحتاج الوكيل إلى تشغيل كود Python على شكل خلايا (خلايا متتالية وحالة مستمرة وعروض غنية)، يمر ذلك عبر أداة **`python`**، وليس `notebook`.\n\nفي هذا المسار تكمن أوضاع النواة وسلوك إعادة التشغيل/الإلغاء وبث القطع واقتطاع مخرجات عناصر.\n\n## 2) دلالات معالجة خلايا الدفتر (أداة `notebook`)\n\n## تطبيع المصدر\n\nيُقسَّم `content` إلى `source: string[]` مع الحفاظ على علامات السطر الجديد:\n\n- يحتفظ كل سطر غير أخير بـ `\\n` الخاص به في نهايته\n- لا يُفرض سطر جديد في نهاية السطر الأخير\n\nيعكس هذا اصطلاحات JSON للدفتر ويتجنب إلحاق الأسطر عن طريق الخطأ عند التعديلات اللاحقة.\n\n## سلوك الإجراءات\n\n- `edit`\n  - يستبدل `cells[cell_index].source`\n  - يحافظ على `cell_type` الحالي\n- `insert`\n  - يدرج عند `[0..cellCount]`\n  - يكون `cell_type` افتراضياً `code`\n  - تُهيّئ خلايا الكود `execution_count: null` و`outputs: []`\n  - تُهيّئ خلايا markdown فقط `metadata` + `source`\n- `delete`\n  - يحذف `cells[cell_index]`\n  - يُعيد `source` المحذوف في التفاصيل لمعاينة المُصيِّر\n\n## أسطح الأخطاء\n\nتُرمى الأخطاء الحرجة في الحالات التالية:\n\n- ملف دفتر مفقود\n- JSON غير صالح\n- `cells` مفقود أو ليس مصفوفة\n- مؤشر خارج النطاق (للإدراج وغير الإدراج نطاقات صالحة مختلفة)\n- `content` مفقود عند `edit`/`insert`\n\nتتحول هذه الأخطاء إلى استجابات أداة من نوع `Error:` في المراحل الأعلى؛ يستخدم المُصيِّر مسار الدفتر ونص الخطأ المنسَّق.\n\n## 3) دلالات جلسة النواة (حيث تتواجد فعلياً)\n\nتُنفَّذ دلالات النواة في `executePython` / `PythonKernel` وتنطبق على أداة `python`.\n\n## الأوضاع\n\n`PythonKernelMode`:\n\n- `session` (الافتراضي)\n  - النوى مُخزَّنة مؤقتاً في خريطة `kernelSessions`\n  - بحد أقصى 4 جلسات؛ يُزال الأقدم عند الامتلاء\n  - تنظيف جلسات الخمول/الميتة كل 30 ثانية، انتهاء المهلة بعد 5 دقائق\n  - تُسلسل طابور كل جلسة التنفيذ (`session.queue`)\n- `per-call`\n  - ينشئ نواة للطلب\n  - يُنفّذ\n  - يُغلق النواة دائماً في `finally`\n\n## سلوك إعادة الضبط\n\nتمرر أداة `python` الأمر `reset` فقط للخلية الأولى في استدعاء متعدد الخلايا؛ تعمل الخلايا اللاحقة دائماً بـ `reset: false`.\n\n## موت النواة / إعادة التشغيل / إعادة المحاولة\n\nفي وضع الجلسة (`withKernelSession`):\n\n- يُكتشف موت النواة عبر نبضة القلب (فحص `kernel.isAlive()` كل 5 ثوانٍ) أو فشل التنفيذ.\n- يُشغّل حالة الموت قبل التنفيذ `restartKernelSession`.\n- مسار العطل وقت التنفيذ يُعيد المحاولة مرة واحدة: إعادة تشغيل النواة، وإعادة تشغيل المعالج.\n- `restartCount > 1` في نفس الجلسة يرمي `Python kernel restarted too many times in this session`.\n\nسلوك إعادة محاولة بدء التشغيل:\n\n- إنشاء نواة بوابة مشتركة يُعيد المحاولة مرة واحدة عند `SharedGatewayCreateError` مع HTTP 5xx.\n\nاسترداد نفاد الموارد:\n\n- يكتشف أخطاء `EMFILE`/`ENFILE`/\"Too many open files\"\n- يمسح الجلسات المتعقَّبة\n- يستدعي `shutdownSharedGateway()`\n- يُعيد محاولة إنشاء جلسة النواة مرة واحدة\n\n## 4) حقن متغيرات البيئة/الجلسة\n\nتستقبل النواة عند بدء التشغيل خريطة بيئة اختيارية من المُنفِّذ:\n\n- `PI_SESSION_FILE` (مسار ملف حالة الجلسة)\n- `ARTIFACTS` (مجلد العناصر)\n\nثم يُشغّل `PythonKernel.#initializeKernelEnvironment(...)` سكريبت التهيئة داخل النواة من أجل:\n\n- `os.chdir(cwd)`\n- حقن إدخالات البيئة في `os.environ`\n- إضافة cwd في مقدمة `sys.path` إذا كان غائباً\n\nالانعكاس:\n\n- تعتمد مساعدات البريليد التي تقرأ سياق الجلسة أو العناصر على متغيرات البيئة هذه في حالة عملية Python.\n\n## 5) معالجة البث/القطع والعرض (المسار المدعوم بالنواة)\n\nيعالج عميل النواة رسائل بروتوكول Jupyter لكل تنفيذ:\n\n- `stream` -> قطعة نصية إلى `onChunk`\n- `execute_result` / `display_data` ->\n  - يُختار نص العرض وفق أولوية MIME: `text/markdown` > `text/plain` > `text/html` المحوَّل\n  - تُلتقط المخرجات المنظَّمة بشكل منفصل:\n    - `application/json` -> `{ type: \"json\" }`\n    - `image/png` -> `{ type: \"image\" }`\n    - `application/x-xcsh-status` -> `{ type: \"status\" }` (بدون انبعاث نص)\n- `error` -> نص التتبع الخلفي يُدفع إلى تدفق القطع + بيانات تعريفية منظَّمة للخطأ\n- `input_request` -> يُصدر نص تحذير stdin، يُرسل `input_reply` فارغاً، يُشير إلى طلب stdin\n- ينتظر الاكتمال كلاً من `execute_reply` وحالة النواة `status=idle`\n\nالإلغاء/انتهاء المهلة:\n\n- يُشغّل إشارة الإيقاف `interrupt()` (REST `/interrupt` + `interrupt_request` عبر قناة التحكم)\n- تُشير النتيجة إلى `cancelled=true`\n- يُضيف مسار انتهاء المهلة إلى المخرجات `Command timed out after <n> seconds`\n\n## 6) الاقتطاع وسلوك العناصر\n\nيُستخدم `OutputSink` في `src/session/streaming-output.ts` في مسارات تنفيذ النواة (`executeWithKernel`):\n\n- يُعقِّم كل قطعة (`sanitizeText`)\n- يتتبع إجمالي الأسطر وأسطر المخرجات والبايتات\n- ملف إفراز عناصر اختياري (`artifactPath` و`artifactId`)\n- عندما تتجاوز المخزن المؤقت في الذاكرة الحد الأقصى (`DEFAULT_MAX_BYTES` ما لم يُعاد تعريفه):\n  - يُشير إلى الاقتطاع\n  - يحتفظ بالبايتات الأخيرة في الذاكرة (بحد آمن لـ UTF-8)\n  - يمكنه إفراز التدفق الكامل إلى مستودع العناصر\n\nتُعيد `dump()`:\n\n- نص المخرجات المرئية (ربما مقتطع من النهاية)\n- علامة الاقتطاع + الإحصاءات\n- معرّف العنصر (لمراجع `artifact://<id>`)\n\nتحوّل أداة `python` هذه البيانات التعريفية إلى إشعارات اقتطاع النتائج وتحذيرات TUI.\n\nأداة `notebook` **لا** تستخدم `OutputSink`؛ وليس لها مسار بث/اقتطاع عناصر لأنها لا تُنفّذ الكود.\n\n## 7) افتراضات المُصيِّر والتنسيق\n\n## مُصيِّر الدفتر (`notebookToolRenderer`)\n\n- عرض الاستدعاء: سطر الحالة مع الإجراء + مسار الدفتر + بيانات تعريفية للخلية/النوع\n- عرض النتيجة:\n  - ملخص النجاح مشتق من `details`\n  - تُصيَّر `cellSource` عبر `renderCodeCell`\n  - تضبط خلايا markdown تلميح اللغة `markdown`؛ لا يوجد تعديل صريح للغة في الخلايا الأخرى\n  - حد معاينة الكود المطوي هو `PREVIEW_LIMITS.COLLAPSED_LINES * 2`\n  - يدعم الوضع الموسَّع عبر خيارات العرض المشتركة\n  - يستخدم ذاكرة تخزين مؤقت للعرض مُفهرَسة بالعرض + حالة التوسعة\n\nافتراض عرض الأخطاء:\n\n- إذا بدأ أول محتوى نصي بـ `Error:`، يُنسّق المُصيِّر الأمر كمقطع خطأ للدفتر.\n\n## مُصيِّر Python (لمخرجات التنفيذ الفعلية)\n\nيتوقع عرض التنفيذ المدعوم بالنواة:\n\n- انتقالات حالة لكل خلية (`pending/running/complete/error`)\n- قسم حدث حالة منظَّم اختياري\n- أشجار مخرجات JSON اختيارية\n- تحذيرات الاقتطاع + مؤشر اختياري `artifact://<id>`\n\nسلوك هذا المُصيِّر غير مرتبط بنتائج تعديل JSON للدفتر، باستثناء أن كليهما يُعيد استخدام عناصر TUI المشتركة.\n\n## 8) الانحراف عن سلوك أداة Python العادية\n\nإذا كانت \"أداة Python العادية\" تعني مسار تنفيذ `python`:\n\n- تُنفّذ `python` الكود في نواة، وتُبقي الحالة وفق الوضع، وتبث القطع، وتلتقط العروض الغنية، وتعالج المقاطعات/انتهاءات المهلة، وتدعم اقتطاع المخرجات/العناصر.\n- تُجري `notebook` تحويلات حتمية لـ JSON الدفتر فقط؛ بلا تنفيذ، ولا حالة نواة، ولا تدفق قطع، ولا عروض مخرجات، ولا مسار عناصر.\n\nإذا احتاج سير عمل ما إلى كليهما:\n\n1. حرّر مصدر الدفتر بـ `notebook`\n2. نفّذ خلايا الكود عبر `python` (بتمرير الكود يدوياً)، وليس عبر `notebook`\n\nلا يوفر التنفيذ الحالي أداة واحدة تُجري في آنٍ واحد تحويلات على `.ipynb` وتُنفّذ خلايا الدفتر عبر سياق النواة.\n",
	"ar/runtime-tools/resolve-tool-runtime.md": "---\ntitle: الداخليات الخاصة بزمن تشغيل أداة Resolve\ndescription: >-\n  زمن تشغيل أداة Resolve لتحليل مسارات الملفات، وجلب المحتوى، والوصول إلى\n  الموارد المستندة إلى URL.\nsidebar:\n  order: 3\n  label: أداة Resolve\ni18n:\n  sourceHash: 06e8be8c5a3c\n  translator: machine\n---\n\n# الداخليات الخاصة بزمن تشغيل أداة Resolve\n\nيشرح هذا المستند كيفية نمذجة سير عمل المعاينة/التطبيق في coding-agent وكيف يمكن للأدوات المخصصة المشاركة عبر `pushPendingAction`.\n\n## النطاق والملفات الرئيسية\n\n- [`src/tools/resolve.ts`](../../packages/coding-agent/src/tools/resolve.ts)\n- [`src/tools/pending-action.ts`](../../packages/coding-agent/src/tools/pending-action.ts)\n- [`src/tools/ast-edit.ts`](../../packages/coding-agent/src/tools/ast-edit.ts)\n- [`src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts)\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n\n## ما الذي تقوم به `resolve`\n\n`resolve` هي أداة مخفية تُنهي إجراء معاينة معلّقاً.\n\n- `action: \"apply\"` تُنفّذ `apply(reason)` على الإجراء المعلّق وتثبّت التغييرات.\n- `action: \"discard\"` تستدعي `reject(reason)` إذا توفّرت؛ وإلا تتجاهل الإجراء برسالة \"تم التجاهل\" الافتراضية.\n\nإذا لم يكن ثمة إجراء معلّق، تفشل `resolve` برسالة:\n\n- `No pending action to resolve. Nothing to apply or discard.`\n\n## الإجراءات المعلّقة هي مكدّس (LIFO)\n\nتُخزَّن الإجراءات المعلّقة في `PendingActionStore` على شكل مكدّس دفع/سحب:\n\n- `push(action)` يضيف إجراءً معلّقاً جديداً في أعلى المكدّس.\n- `peek()` يفحص الإجراء الموجود في أعلى المكدّس الحالي.\n- `pop()` يُزيل الإجراء الموجود في الأعلى ويُعيده.\n- `hasPending` يشير إلى ما إذا كان المكدّس غير فارغ.\n\nتستهلك `resolve` دائماً الإجراء **الأعلى** أولاً (`pop()`)، لذا تُحلَّل الأدوات المنتجة لمعاينات متعددة بترتيب عكسي للتسجيل.\n\n## مثال على منتج مدمج (`ast_edit`)\n\nتُعاين `ast_edit` الاستبدالات البنيوية أولاً. عندما تحتوي المعاينة على استبدالات ولم تُطبَّق بعد، تدفع إجراءً معلّقاً يحتوي على:\n\n- label (ملخص مقروء للإنسان)\n- `sourceToolName` (`ast_edit`)\n- رد نداء `apply(reason: string)` يُعيد تشغيل تحرير AST مع `dryRun: false`\n\nتُمرّر `resolve(action=\"apply\", reason=\"...\")` قيمة `reason` إلى هذا رد النداء.\n\n## الأدوات المخصصة: `pushPendingAction`\n\nيمكن للأدوات المخصصة تسجيل إجراءات معلّقة متوافقة مع resolve عبر `CustomToolAPI.pushPendingAction(...)`.\n\n`CustomToolPendingAction`:\n\n- `label: string` (مطلوب)\n- `apply(reason: string): Promise<AgentToolResult<unknown>>` (مطلوب) — يُستدعى عند التطبيق؛ `reason` هي السلسلة المُمرَّرة إلى `resolve`\n- `reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>` (اختياري) — يُستدعى عند التجاهل؛ تحلّ قيمة الإرجاع محلّ رسالة \"تم التجاهل\" الافتراضية إذا توفّرت\n- `details?: unknown` (اختياري)\n- `sourceToolName?: string` (اختياري، القيمة الافتراضية `\"custom_tool\"`)\n\n### مثال على الاستخدام الأدنى\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = pi => ({\n name: \"batch_rename_preview\",\n label: \"Batch Rename Preview\",\n description: \"Previews renames and defers commit to resolve\",\n parameters: pi.typebox.Type.Object({\n  files: pi.typebox.Type.Array(pi.typebox.Type.String()),\n }),\n\n async execute(_toolCallId, params) {\n  const previewSummary = `Prepared rename plan for ${params.files.length} files`;\n\n  pi.pushPendingAction({\n   label: `Batch rename: ${params.files.length} files`,\n   sourceToolName: \"batch_rename_preview\",\n   apply: async (reason) => {\n    // apply writes here\n    return {\n     content: [{ type: \"text\", text: `Applied batch rename. Reason: ${reason}` }],\n    };\n   },\n   reject: async (reason) => {\n    // optional: cleanup or notify on discard\n    return {\n     content: [{ type: \"text\", text: `Discarded batch rename. Reason: ${reason}` }],\n    };\n   },\n  });\n\n  return {\n   content: [{ type: \"text\", text: `${previewSummary}. Call resolve to apply or discard.` }],\n  };\n },\n});\n\nexport default factory;\n```\n\n## توافر زمن التشغيل والأعطال\n\nيتمّ توصيل `pushPendingAction` بواسطة محمّل الأداة المخصصة باستخدام `PendingActionStore` للجلسة النشطة.\n\nإذا كان زمن التشغيل لا يحتوي على مخزن للإجراءات المعلّقة، يرمي `pushPendingAction` استثناءً:\n\n- `Pending action store unavailable for custom tools in this runtime.`\n\n## سلوك اختيار الأداة\n\nعندما تكون `PendingActionStore.hasPending` صحيحة، يُميل زمن تشغيل الوكيل اختيار الأداة نحو `resolve` بحيث تُنهى المعاينات المعلّقة صراحةً قبل استمرار تدفق الأداة الطبيعي.\n\n## إرشادات المطور\n\n- استخدم الإجراءات المعلّقة فقط للعمليات المدمّرة أو عالية التأثير التي ينبغي أن تدعم التطبيق/التجاهل الصريح.\n- اجعل `label` موجزاً ومحدداً؛ إذ يظهر في مخرجات عارض resolve.\n- تأكّد من أن `apply(reason)` حتمي وقابل للتكرار بما يكفي للتنفيذ مرة واحدة؛ `reason` معلوماتي ولا ينبغي أن يُغيّر السلوك.\n- نفّذ `reject(reason)` عندما يحتاج التجاهل إلى تنظيف (حالة مؤقتة، أقفال، إشعارات)؛ أغفله في المعاينات عديمة الحالة حيث تكفي الرسالة الافتراضية.\n- إذا كانت أداتك قادرة على تدريج معاينات متعددة، تذكّر دلالات LIFO: يُحلَّل الإجراء المدفوع الأخير أولاً.\n",
	"ar/runtime-tools/slash-command-internals.md": "---\ntitle: الداخليات الخاصة بأوامر Slash\ndescription: داخليات نظام أوامر Slash مع التسجيل وتحليل الوسيطات وإرسال التنفيذ.\nsidebar:\n  order: 5\n  label: أوامر Slash\ni18n:\n  sourceHash: 2cbd44a3de87\n  translator: machine\n---\n\n# الداخليات الخاصة بأوامر Slash\n\nيصف هذا المستند كيفية اكتشاف أوامر Slash وإزالة تكرارها وعرضها في الوضع التفاعلي وتوسيعها عند وقت المطالبة في `coding-agent`.\n\n## ملفات التنفيذ\n\n- [`src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n- [`src/capability/slash-command.ts`](../../packages/coding-agent/src/capability/slash-command.ts)\n- [`src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`src/discovery/claude.ts`](../../packages/coding-agent/src/discovery/claude.ts)\n- [`src/discovery/codex.ts`](../../packages/coding-agent/src/discovery/codex.ts)\n- [`src/discovery/claude-plugins.ts`](../../packages/coding-agent/src/discovery/claude-plugins.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n\n## 1) نموذج الاكتشاف\n\nأوامر Slash هي قدرة (`id: \"slash-commands\"`) مفهرسة باسم الأمر (`key: cmd => cmd.name`).\n\nيقوم سجل القدرات بتحميل جميع الموفرين المسجلين، مرتبةً تنازلياً حسب أولوية الموفر، ويزيل التكرارات بالمفتاح مع دلالات **الفوز للأول**.\n\n### أسبقية الموفر\n\nموفرو أوامر Slash الحاليون وأولوياتهم:\n\n1. `native` (OMP) — الأولوية `100`\n2. `claude` — الأولوية `80`\n3. `claude-plugins` — الأولوية `70`\n4. `codex` — الأولوية `70`\n\nسلوك التعادل: الموفرون ذوو الأولوية المتساوية يحتفظون بترتيب التسجيل. يسجّل ترتيب الاستيراد الحالي `claude-plugins` قبل `codex`، لذلك تفوز أوامر المكوّن الإضافي على أوامر codex في حالات تصادم الأسماء.\n\n### سلوك تصادم الأسماء\n\nبالنسبة لـ `slash-commands`، يتم حل التصادمات بشكل صارم عبر إزالة تكرارات القدرات:\n\n- يُحتفظ بالعنصر الأعلى أولويةً في `result.items`\n- تبقى التكرارات الأدنى أولويةً فقط في `result.all` وتُوسَم بـ `_shadowed = true`\n\nيسري هذا عبر الموفرين وأيضاً داخل موفر واحد إذا أعاد أسماءً مكررة.\n\n### سلوك فحص الملفات\n\nيستخدم الموفرون في الغالب `loadFilesFromDir(...)`, والذي يقوم حالياً بما يلي:\n\n- يعتمد على المطابقة غير التعاودية بشكل افتراضي (`*.md`)\n- يستخدم glob أصلياً مع `gitignore: true` و `hidden: false`\n- يقرأ كل ملف متطابق ويحوّله إلى `SlashCommand`\n\nلذلك لا يتم تحميل الملفات/الأدلة المخفية، ويتم تخطي المسارات المُتجاهَلة.\n\n## 2) مسارات المصدر الخاصة بكل موفر والأسبقية المحلية\n\n## الموفر `native` (`builtin.ts`)\n\nتأتي جذور البحث من أدلة `.xcsh`:\n\n- المشروع: `<cwd>/.xcsh/commands/*.md`\n- المستخدم: `~/.xcsh/agent/commands/*.md`\n\nيُعيد `getConfigDirs()` المشروع أولاً ثم المستخدم، لذلك **تتفوق الأوامر الأصلية للمشروع على الأوامر الأصلية للمستخدم** عند تصادم الأسماء.\n\n## الموفر `claude` (`claude.ts`)\n\nيحمّل:\n\n- المستخدم: `~/.claude/commands/*.md`\n- المشروع: `<cwd>/.claude/commands/*.md`\n\nيدفع الموفر عناصر المستخدم قبل عناصر المشروع، لذلك **تتفوق أوامر Claude للمستخدم على أوامر Claude للمشروع** في حالات التصادم بالاسم نفسه داخل هذا الموفر.\n\n## الموفر `codex` (`codex.ts`)\n\nيحمّل:\n\n- المستخدم: `~/.codex/commands/*.md`\n- المشروع: `<cwd>/.codex/commands/*.md`\n\nيتم تحميل الجانبين ثم تسطيحهما بترتيب المستخدم أولاً، لذلك **تتفوق أوامر Codex للمستخدم على أوامر Codex للمشروع** في حالات التصادم.\n\nيتم تحليل محتوى أوامر Codex مع إزالة frontmatter (`parseFrontmatter`)، ويمكن تجاوز اسم الأمر بواسطة `name` في frontmatter؛ وإلا يُستخدم اسم الملف.\n\n## الموفر `claude-plugins` (`claude-plugins.ts`)\n\nيحمّل جذور أوامر المكوّن الإضافي من `~/.claude/plugins/installed_plugins.json`، ثم يفحص `<pluginRoot>/commands/*.md`.\n\nيتبع الترتيب ترتيب تكرار السجل وترتيب إدخالات كل مكوّن إضافي من بيانات JSON تلك. لا توجد خطوة فرز إضافية.\n\n## 3) التحويل إلى `FileSlashCommand` في وقت التشغيل\n\nتُحوّل `loadSlashCommands()` في `src/extensibility/slash-commands.ts` عناصر القدرات إلى كائنات `FileSlashCommand` المستخدمة عند وقت المطالبة.\n\nلكل أمر:\n\n1. تحليل frontmatter/الجسم (`parseFrontmatter`)\n2. مصدر الوصف:\n   - `frontmatter.description` إذا كان موجوداً\n   - وإلا أول سطر غير فارغ من الجسم (منظّف، بحد أقصى 60 حرفاً مع `...`)\n3. الاحتفاظ بجسم التحليل كمحتوى قالب قابل للتنفيذ\n4. حساب سلسلة مصدر عرض مثل `via Claude Code Project`\n\nتعتمد شدة تحليل frontmatter على المصدر:\n\n- المستوى `native` -> أخطاء التحليل تكون `fatal`\n- المستويان `user`/`project` -> أخطاء التحليل تكون `warn` مع تحليل احتياطي\n\n### أوامر الاحتياط المدمجة\n\nبعد الأوامر المستندة إلى نظام الملفات/الموفر، يتم إلحاق قوالب أوامر مضمّنة (`EMBEDDED_COMMAND_TEMPLATES`) إذا لم تكن أسماؤها موجودة مسبقاً.\n\nتأتي المجموعة المضمّنة الحالية من `src/task/commands.ts` وتُستخدم كاحتياط (`source: \"bundled\"`).\n\n## 4) الوضع التفاعلي: مصادر قوائم الأوامر\n\nيجمع الوضع التفاعلي مصادر أوامر متعددة للإكمال التلقائي وتوجيه الأوامر.\n\nعند الإنشاء، يبني قائمة انتظار أوامر من:\n\n- الأوامر المدمجة (`BUILTIN_SLASH_COMMANDS`، تتضمن إكمال الوسيطات والتلميحات المضمّنة لأوامر محددة)\n- أوامر Slash المسجلة بالامتداد (`extensionRunner.getRegisteredCommands(...)`)\n- الأوامر المخصصة بـ TypeScript (`session.customCommands`)، مُعيَّنة إلى تسميات أوامر Slash\n- أوامر المهارة الاختيارية (`/skill:<name>`) عند تمكين `skills.enableSkillCommands`\n\nثم يستدعي `init()` الدالة `refreshSlashCommandState(...)` لتحميل الأوامر المستندة إلى الملفات وتثبيت موفر `CombinedAutocompleteProvider` واحد يحتوي على:\n\n- الأوامر المعلقة أعلاه\n- الأوامر المستندة إلى الملفات المكتشفة\n\nتقوم `refreshSlashCommandState(...)` أيضاً بتحديث `session.setSlashCommands(...)` حتى يستخدم توسيع المطالبة مجموعة أوامر الملف المكتشفة نفسها.\n\n### دورة حياة التحديث\n\nيتم تحديث حالة أوامر Slash:\n\n- أثناء تهيئة الوضع التفاعلي\n- بعد أن يغير `/move` دليل العمل (`handleMoveCommand` يستدعي `resetCapabilities()` ثم `refreshSlashCommandState(newCwd)`)\n\nلا يوجد مراقب ملفات مستمر لأدلة الأوامر.\n\n### العرض في أماكن أخرى\n\nتحمّل لوحة تحكم الامتدادات أيضاً قدرة `slash-commands` وتعرض إدخالات الأوامر النشطة والمظلّلة، بما في ذلك التكرارات `_shadowed`.\n\n## 5) موضع خط أنابيب المطالبة\n\nترتيب معالجة Slash في `AgentSession.prompt(...)` (عند `expandPromptTemplates !== false`):\n\n1. **أوامر الامتداد** (`#tryExecuteExtensionCommand`)  \n   إذا تطابق `/name` مع أمر مسجل بالامتداد، يتم تنفيذ المعالج فوراً وتعود المطالبة.\n2. **الأوامر المخصصة بـ TypeScript** (`#tryExecuteCustomCommand`)  \n   حدٌّ فاصل فقط: إذا تطابق، يتم التنفيذ وقد يُعيد:\n   - `string` -> استبدال نص المطالبة بذلك النص\n   - `void/undefined` -> يُعامَل كمعالَج؛ لا توجد مطالبة LLM\n3. **أوامر Slash المستندة إلى الملفات** (`expandSlashCommand`)  \n   إذا كان النص لا يزال يبدأ بـ `/`، محاولة توسيع أمر markdown.\n4. **قوالب المطالبة** (`expandPromptTemplate`)  \n   تُطبَّق بعد معالجة Slash/المخصص.\n5. **التسليم**\n   - الخامل: يُرسَل الطلب فوراً إلى الوكيل\n   - البث: يُوضع الطلب في قائمة انتظار كتوجيه أو متابعة حسب `streamingBehavior`\n\nهذا هو سبب وجود توسيع أوامر Slash قبل توسيع قالب المطالبة، وسبب قدرة الأوامر المخصصة على تحويل الشرطة المائلة الأمامية قبل مطابقة أوامر الملفات.\n\n## 6) دلالات التوسيع للأوامر المستندة إلى الملفات\n\nسلوك `expandSlashCommand(text, fileCommands)`:\n\n- يعمل فقط عند بدء النص بـ `/`\n- يحلل اسم الأمر من الرمز الأول بعد `/`\n- يحلل الوسيطات من النص المتبقي عبر `parseCommandArgs`\n- يجد تطابقاً دقيقاً للاسم في `fileCommands` المحمّلة\n- عند التطابق، يطبّق:\n  - الاستبدال الموضعي: `$1`، `$2`، ...\n  - الاستبدال الإجمالي: `$ARGUMENTS` و `$@`\n  - ثم تصيير القالب عبر `prompt.render` مع `{ args, ARGUMENTS, arguments }`\n- عند عدم التطابق، يُعيد النص الأصلي دون تغيير\n\n### محاذير `parseCommandArgs`\n\nالمحلل عبارة عن تقسيم بسيط يدرك الاقتباسات:\n\n- يدعم اقتباس `'فردي'` و`\"مزدوج\"` للحفاظ على المسافات\n- يزيل محددات الاقتباس\n- لا يُطبّق قواعد الهروب بالشرطة المائلة العكسية\n- الاقتباس غير المكتمل ليس خطأً؛ يستهلك المحلل حتى النهاية\n\n## 7) سلوك `/...` غير المعروف\n\nالمدخل غير المعروف لـ Slash **لا يُرفض** بواسطة منطق slash الأساسي.\n\nإذا لم يتم معالجة الأمر بواسطة طبقات الامتداد/المخصص/الملف، تُعيد `expandSlashCommand` النص الأصلي، وتمر مطالبة `/...` الحرفية عبر توسيع قالب المطالبة الطبيعي وتسليم LLM.\n\nيتعامل الوضع التفاعلي بشكل منفصل مع العديد من الأوامر المدمجة في `InputController` بشكل صارم (على سبيل المثال `/settings`، `/model`، `/mcp`، `/move`، `/exit`). تُستهلك هذه قبل `session.prompt(...)` وبالتالي لا تصل أبداً إلى توسيع أوامر الملف في ذلك المسار.\n\n## 8) الاختلافات في وقت البث مقارنةً بالخامل\n\n## مسار الخامل\n\n- `session.prompt(\"/x ...\")` يشغّل خط أنابيب الأوامر ويُنفّذ الأمر فوراً أو يُرسل النص الموسَّع مباشرةً.\n\n## مسار البث (`session.isStreaming === true`)\n\n- لا يزال `prompt(...)` يشغّل تحويلات الامتداد/المخصص/الملف/القالب أولاً\n- ثم يتطلب `streamingBehavior`:\n  - `\"steer\"` -> وضع رسالة المقاطعة في قائمة الانتظار (`agent.steer`)\n  - `\"followUp\"` -> وضع رسالة ما بعد الدور في قائمة الانتظار (`agent.followUp`)\n- إذا تم حذف `streamingBehavior`، يُلقي الطلب خطأً\n\n### سلوك بث مهم خاص بالأوامر\n\n- تُنفَّذ أوامر الامتداد فوراً حتى أثناء البث (لا تُوضع في قائمة انتظار كنص).\n- تُرفض بواسطة طرق `steer(...)`/`followUp(...)` المساعدة أوامر الامتداد (`#throwIfExtensionCommand`) لتجنب وضع نص الأمر في قائمة الانتظار للمعالجات التي يجب تشغيلها بشكل متزامن.\n- تستخدم إعادة تشغيل قائمة انتظار الضغط `isKnownSlashCommand(...)` لتحديد ما إذا كان يجب إعادة تشغيل الإدخالات في قائمة الانتظار عبر `session.prompt(...)` (لأوامر Slash المعروفة) مقابل طرق steer/follow-up الأولية.\n\n## 9) معالجة الأخطاء وأسطح الفشل\n\n- فشل تحميل الموفر معزول؛ يجمع السجل التحذيرات ويستمر مع الموفرين الآخرين.\n- عناصر أوامر Slash غير الصالحة (اسم/مسار/محتوى مفقود أو مستوى غير صالح) تُسقَط بواسطة التحقق من صحة القدرات.\n- فشل تحليل frontmatter:\n  - الأوامر الأصلية: يتصاعد خطأ التحليل الفادح\n  - الأوامر غير الأصلية: تحذير + تحليل احتياطي للمفتاح/القيمة\n- استثناءات معالج الأوامر الامتداد/المخصص تُلتقط وتُبلَّغ عبر قناة أخطاء الامتداد (أو مسجّل احتياطي للأوامر المخصصة التي لا تملك مشغّل امتداد)، وتُعامَل كمعالَجة (لا يوجد تنفيذ احتياطي غير مقصود).\n",
	"ar/runtime-tools/task-agent-discovery.md": "---\ntitle: اكتشاف واختيار وكيل المهام\ndescription: >-\n  منطق اكتشاف واختيار وكيل المهام لتوجيه العمل إلى أنواع الوكلاء الفرعيين\n  المتخصصين.\nsidebar:\n  order: 6\n  label: اكتشاف وكيل المهام\ni18n:\n  sourceHash: 8cf42457c672\n  translator: machine\n---\n\n# اكتشاف واختيار وكيل المهام\n\nيصف هذا المستند كيف يكتشف النظام الفرعي للمهام تعريفات الوكلاء، ويدمج المصادر المتعددة، ويحل الوكيل المطلوب في وقت التنفيذ.\n\nيغطي هذا المستند سلوك وقت التشغيل كما هو مُنفَّذ حالياً، بما في ذلك الأسبقية، ومعالجة التعريفات غير الصالحة، وقيود الإنشاء/العمق التي يمكن أن تجعل الوكيل غير متاح فعلياً.\n\n## ملفات التنفيذ\n\n- [`src/task/discovery.ts`](../../packages/coding-agent/src/task/discovery.ts)\n- [`src/task/agents.ts`](../../packages/coding-agent/src/task/agents.ts)\n- [`src/task/types.ts`](../../packages/coding-agent/src/task/types.ts)\n- [`src/task/index.ts`](../../packages/coding-agent/src/task/index.ts)\n- [`src/task/commands.ts`](../../packages/coding-agent/src/task/commands.ts)\n- [`src/prompts/agents/task.md`](../../packages/coding-agent/src/prompts/agents/task.md)\n- [`src/prompts/tools/task.md`](../../packages/coding-agent/src/prompts/tools/task.md)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/config.ts`](../../packages/coding-agent/src/config.ts)\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts)\n\n---\n\n## شكل تعريف الوكيل\n\nيتم تطبيع وكلاء المهام إلى `AgentDefinition` (`src/task/types.ts`):\n\n- `name`، `description`، `systemPrompt` (مطلوبة لوكيل محمَّل صالح)\n- اختيارية: `tools`، `spawns`، `model`، `thinkingLevel`، `output`\n- `source`: `\"bundled\" | \"user\" | \"project\"`\n- `filePath` اختياري\n\nيأتي التحليل من frontmatter عبر `parseAgentFields()` (`src/discovery/helpers.ts`):\n\n- `name` أو `description` مفقود => غير صالح (`null`)، المُستدعي يعامله كفشل في التحليل\n- `tools` يقبل CSV أو مصفوفة؛ إذا تم توفيره، يُضاف `submit_result` تلقائياً\n- `spawns` يقبل `*`، CSV، أو مصفوفة\n- سلوك التوافق العكسي: إذا كان `spawns` مفقوداً لكن `tools` يتضمن `task`، يصبح `spawns` هو `*`\n- `output` يُمرَّر كبيانات مخطط غير شفافة\n\n## الوكلاء المُضمَّنون\n\nالوكلاء المُضمَّنون يتم تضمينهم في وقت البناء (`src/task/agents.ts`) باستخدام استيراد النصوص.\n\n`EMBEDDED_AGENT_DEFS` يُعرِّف:\n\n- `explore`، `plan`، `designer`، `reviewer` من ملفات الموجِّهات\n- `task` و `quick_task` من نص `task.md` المشترك بالإضافة إلى frontmatter المحقون\n\nمسار التحميل:\n\n1. `loadBundledAgents()` يحلل الـ markdown المُضمَّن باستخدام `parseAgent(..., \"bundled\", \"fatal\")`\n2. النتائج تُخزَّن مؤقتاً في الذاكرة (`bundledAgentsCache`)\n3. `clearBundledAgentsCache()` هي إعادة تعيين ذاكرة التخزين المؤقت للاختبار فقط\n\nلأن التحليل المُضمَّن يستخدم `level: \"fatal\"`، فإن frontmatter المُضمَّن المشوَّه يرمي استثناءً ويمكن أن يُفشل الاكتشاف بالكامل.\n\n## اكتشاف نظام الملفات والإضافات\n\n`discoverAgents(cwd, home)` (`src/task/discovery.ts`) يدمج الوكلاء من أماكن متعددة قبل إلحاق التعريفات المُضمَّنة.\n\n### مدخلات الاكتشاف\n\n1. مجلدات وكلاء تكوين المستخدم من `getConfigDirs(\"agents\", { project: false })`\n2. أقرب مجلدات وكلاء المشروع من `findAllNearestProjectConfigDirs(\"agents\", cwd)`\n3. جذور إضافات Claude (`listClaudePluginRoots(home)`) مع مجلدات `agents/` الفرعية\n4. الوكلاء المُضمَّنون (`loadBundledAgents()`)\n\n### ترتيب المصدر الفعلي\n\nترتيب عائلة المصدر يأتي من `getConfigDirs(\"\", { project: false })`، المُشتق من `priorityList` في `src/config.ts`:\n\n1. `.xcsh`\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nلكل عائلة مصدر، ترتيب الاكتشاف هو:\n\n1. أقرب مجلد مشروع لذلك المصدر (إن وُجد)\n2. مجلد المستخدم لذلك المصدر\n\nبعد جميع مجلدات عائلات المصادر، تُلحق مجلدات `agents/` الخاصة بالإضافات (إضافات نطاق المشروع أولاً، ثم نطاق المستخدم).\n\nالوكلاء المُضمَّنون يُلحقون أخيراً.\n\n### تحذير مهم: التعليقات القديمة مقابل الكود الحالي\n\nتعليقات رأس `discovery.ts` لا تزال تذكر `.pi` ولا تذكر `.codex`/`.gemini`. الترتيب الفعلي في وقت التشغيل مدفوع بـ `src/config.ts` ويستخدم حالياً `.xcsh`، `.claude`، `.codex`، `.gemini`.\n\n## قواعد الدمج والتعارض\n\nيستخدم الاكتشاف إلغاء التكرار بأسلوب الأول يفوز بمطابقة دقيقة لـ `agent.name`:\n\n- `Set<string>` يتتبع الأسماء المرئية.\n- الوكلاء المحمَّلون يُسطَّحون بترتيب المجلد ويُحتفظ بهم فقط إذا كان الاسم غير مرئي.\n- الوكلاء المُضمَّنون يُفلترون مقابل نفس المجموعة ويُضافون فقط إذا كانوا لا يزالون غير مرئيين.\n\nالآثار المترتبة:\n\n- المشروع يتجاوز المستخدم لنفس عائلة المصدر.\n- عائلة المصدر ذات الأولوية الأعلى تتجاوز الأدنى (`.xcsh` قبل `.claude`، إلخ).\n- الوكلاء غير المُضمَّنين يتجاوزون الوكلاء المُضمَّنين بنفس الاسم.\n- مطابقة الأسماء حساسة لحالة الأحرف (`Task` و `task` مختلفان).\n- ضمن مجلد واحد، تُقرأ ملفات markdown بترتيب أسماء الملفات المعجمي قبل إلغاء التكرار.\n\n## سلوك ملف الوكيل غير الصالح/المفقود\n\nلكل مجلد (`loadAgentsFromDir`):\n\n- مجلد غير قابل للقراءة/مفقود: يُعامل كفارغ (`readdir(...).catch(() => [])`)\n- فشل قراءة أو تحليل الملف: يُسجَّل تحذير، يُتخطى الملف\n- مسار التحليل يستخدم `parseAgent(..., level: \"warn\")`\n\nسلوك فشل Frontmatter يأتي من `parseFrontmatter`:\n\n- خطأ التحليل عند مستوى `warn` يُسجِّل تحذيراً\n- المحلل يعود إلى محلل بسيط لسطور `key: value`\n- إذا كانت الحقول المطلوبة لا تزال مفقودة، يفشل `parseAgentFields`، ثم يُرمى `AgentParsingError` ويُلتقط من قبل المُستدعي (يُتخطى الملف)\n\nالتأثير الصافي: ملف وكيل مخصص سيئ واحد لا يوقف اكتشاف الملفات الأخرى.\n\n## بحث واختيار الوكيل\n\nالبحث هو بحث خطي بالاسم الدقيق:\n\n- `getAgent(agents, name)` => `agents.find(a => a.name === name)`\n\nفي تنفيذ المهمة (`TaskTool.execute`):\n\n1. يُعاد اكتشاف الوكلاء في وقت الاستدعاء (`discoverAgents(this.session.cwd)`)\n2. `params.agent` المطلوب يُحل عبر `getAgent`\n3. الوكيل المفقود يعيد استجابة أداة فورية:\n   - `Unknown agent \"...\". Available: ...`\n   - لا تعمل أي عملية فرعية\n\n### الوصف مقابل اكتشاف وقت التنفيذ\n\n`TaskTool.create()` يبني وصف الأداة من نتائج الاكتشاف في وقت التهيئة (`buildDescription`).\n\n`execute()` يُعيد اكتشاف الوكلاء مرة أخرى. لذا يمكن أن تختلف مجموعة وقت التشغيل عما تم إدراجه في وصف الأداة السابق إذا تغيرت ملفات الوكلاء أثناء الجلسة.\n\n## حواجز المخرجات المُهيكلة وأسبقية المخطط\n\nأسبقية مخطط المخرجات في وقت التشغيل في `TaskTool.execute`:\n\n1. `output` في frontmatter الوكيل\n2. `params.schema` في استدعاء المهمة\n3. `outputSchema` للجلسة الأم\n\n(`effectiveOutputSchema = effectiveAgent.output ?? outputSchema ?? this.session.outputSchema`)\n\nنص حاجز الحماية في وقت الموجِّه في `src/prompts/tools/task.md` يحذر من سلوك عدم التطابق لوكلاء المخرجات المُهيكلة (`explore`، `reviewer`): تعليمات تنسيق المخرجات في النص يمكن أن تتعارض مع المخطط المُدمج وتنتج مخرجات `null`.\n\nهذا توجيه، وليس منطق تحقق صارم في وقت التشغيل في `discoverAgents`.\n\n## تفاعل اكتشاف الأوامر\n\n`src/task/commands.ts` هو بنية تحتية موازية لأوامر سير العمل (وليست تعريفات وكلاء)، لكنها تتبع نفس النمط العام:\n\n- الاكتشاف من مزودي القدرات أولاً\n- إلغاء التكرار بالاسم بأسلوب الأول يفوز\n- إلحاق الأوامر المُضمَّنة إذا كانت لا تزال غير مرئية\n- البحث بالاسم الدقيق عبر `getCommand`\n\nفي `src/task/index.ts`، تُعاد تصدير مساعدات الأوامر مع مساعدات اكتشاف الوكلاء. اكتشاف الوكلاء نفسه لا يعتمد على اكتشاف الأوامر في وقت التشغيل.\n\n## قيود التوفر خارج نطاق الاكتشاف\n\nيمكن أن يكون الوكيل قابلاً للاكتشاف ولكنه لا يزال غير متاح للتشغيل بسبب حواجز التنفيذ.\n\n### سياسة إنشاء الأب\n\n`TaskTool.execute` يتحقق من `session.getSessionSpawns()`:\n\n- `\"*\"` => السماح بأي وكيل\n- `\"\"` => رفض الكل\n- قائمة CSV => السماح بالأسماء المدرجة فقط\n\nإذا رُفض: استجابة فورية `Cannot spawn '...'. Allowed: ...`.\n\n### حارس بيئي لمنع التكرار الذاتي\n\n`PI_BLOCKED_AGENT` يُقرأ عند إنشاء الأداة. إذا تطابق الطلب، يُرفض التنفيذ مع رسالة منع التكرار.\n\n### بوابة عمق التكرار (توفر أداة المهمة داخل الجلسات الفرعية)\n\nفي `runSubprocess` (`src/task/executor.ts`):\n\n- العمق يُحسب من `taskDepth`\n- `task.maxRecursionDepth` يتحكم في حد القطع\n- عند الوصول للعمق الأقصى:\n  - أداة `task` تُزال من قائمة أدوات الطفل\n  - `spawns` الخاص ببيئة الطفل يُعيَّن كفارغ\n\nلذا المستويات الأعمق لا يمكنها إنشاء مهام إضافية حتى لو كان تعريف الوكيل يتضمن `spawns`.\n\n## تحذير بشأن وضع التخطيط (التنفيذ الحالي)\n\n`TaskTool.execute` يحسب `effectiveAgent` لوضع التخطيط (يُقدِّم موجِّه وضع التخطيط، يفرض مجموعة فرعية من الأدوات للقراءة فقط، يُفرغ spawns)، لكن `runSubprocess` يُستدعى مع `agent` بدلاً من `effectiveAgent`.\n\nالتأثير الحالي:\n\n- تجاوز النموذج / مستوى التفكير / مخطط المخرجات مُشتقة من `effectiveAgent`\n- موجِّه النظام وقيود الأدوات/الإنشاء من `effectiveAgent` لا تُمرَّر في مسار الاستدعاء هذا\n\nهذا تحذير تنفيذي يستحق المعرفة عند قراءة توقعات سلوك وضع التخطيط.\n",
	"ar/sessions/compaction.md": "---\ntitle: الضغط وملخصات الفروع\ndescription: ضغط نافذة السياق وتوليد ملخصات الفروع للجلسات طويلة الأمد.\nsidebar:\n  order: 5\n  label: الضغط\ni18n:\n  sourceHash: dae425a900d8\n  translator: machine\n---\n\n# الضغط وملخصات الفروع\n\nالضغط وملخصات الفروع هما الآليتان اللتان تُبقيان الجلسات الطويلة قابلةً للاستخدام دون فقدان سياق العمل السابق.\n\n- **الضغط** يُعيد كتابة السجل القديم في صورة ملخص على الفرع الحالي.\n- **ملخص الفرع** يلتقط سياق الفرع المتروك أثناء التنقل عبر `/tree`.\n\nكلاهما يُحفظ كإدخالات جلسة ويُحوَّل مجدداً إلى رسائل سياق المستخدم عند إعادة بناء مدخلات نموذج اللغة الكبير (LLM).\n\n## ملفات التنفيذ الرئيسية\n\n- `src/session/compaction/compaction.ts`\n- `src/session/compaction/branch-summarization.ts`\n- `src/session/compaction/pruning.ts`\n- `src/session/compaction/utils.ts`\n- `src/session/session-manager.ts`\n- `src/session/agent-session.ts`\n- `src/session/messages.ts`\n- `src/extensibility/hooks/types.ts`\n- `src/config/settings-schema.ts`\n\n## نموذج إدخال الجلسة\n\nالضغط وملخصات الفروع هي إدخالات جلسة من الدرجة الأولى، وليست رسائل مساعد/مستخدم عادية.\n\n- `CompactionEntry`\n  - `type: \"compaction\"`\n  - `summary`، و`shortSummary` اختياري\n  - `firstKeptEntryId` (حد الضغط)\n  - `tokensBefore`\n  - `details` و`preserveData` و`fromExtension` اختيارية\n- `BranchSummaryEntry`\n  - `type: \"branch_summary\"`\n  - `fromId`، و`summary`\n  - `details` و`fromExtension` اختيارية\n\nعند إعادة بناء السياق (`buildSessionContext`):\n\n1. يُحوَّل آخر ضغط على المسار النشط إلى رسالة `compactionSummary` واحدة.\n2. تُعاد إضافة الإدخالات المحتفظ بها من `firstKeptEntryId` حتى نقطة الضغط.\n3. تُلحَق الإدخالات اللاحقة على المسار.\n4. تُحوَّل إدخالات `branch_summary` إلى رسائل `branchSummary`.\n5. تُحوَّل إدخالات `custom_message` إلى رسائل `custom`.\n\nثم تُحوَّل تلك الأدوار المخصصة إلى رسائل مستخدم موجهة لنموذج LLM في `convertToLlm()` باستخدام القوالب الثابتة:\n\n- `prompts/compaction/compaction-summary-context.md`\n- `prompts/compaction/branch-summary-context.md`\n\n## مسار الضغط\n\n### مُشغِّلات التفعيل\n\nيمكن تشغيل الضغط بثلاث طرق:\n\n1. **يدوي**: يستدعي `/compact [instructions]` الدالة `AgentSession.compact(...)`.\n2. **استعادة تلقائية من تجاوز الحد**: بعد خطأ مساعد مطابق لتجاوز سياق النافذة.\n3. **ضغط تلقائي عند تجاوز العتبة**: بعد دورة ناجحة عندما يتجاوز السياق العتبة المحددة.\n\n### شكل الضغط (مرئي)\n\n```text\nقبل الضغط:\n\n  entry:  0     1     2     3      4     5     6      7      8     9\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┘\n                └────────┬───────┘ └──────────────┬──────────────┘\n               messagesToSummarize            kept messages\n                                   ↑\n                          firstKeptEntryId (entry 4)\n\nبعد الضغط (إلحاق إدخال جديد):\n\n  entry:  0     1     2     3      4     5     6      7      8     9      10\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┬─────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │ cmp │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┴─────┘\n               └──────────┬──────┘ └──────────────────────┬───────────────────┘\n                 not sent to LLM                    sent to LLM\n                                                         ↑\n                                              starts from firstKeptEntryId\n\nما يراه نموذج LLM:\n\n  ┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐\n  │ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │\n  └────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘\n       ↑         ↑      └─────────────────┬────────────────┘\n    prompt   from cmp          messages from firstKeptEntryId\n```\n\n### الضغط عند تجاوز الحد مقابل الضغط عند بلوغ العتبة\n\nيتعمد المساران التلقائيان الاختلاف:\n\n- **ضغط إعادة المحاولة عند تجاوز الحد**\n  - المُشغِّل: يُكتشف أن خطأ المساعد في النموذج الحالي ناجم عن تجاوز سياق النافذة.\n  - تُحذف رسالة خطأ المساعد الفاشلة من حالة العميل النشطة قبل إعادة المحاولة.\n  - يُشغَّل الضغط التلقائي مع `reason: \"overflow\"` و`willRetry: true`.\n  - عند النجاح، يواصل العميل تلقائياً (`agent.continue()`) بعد الضغط.\n\n- **ضغط العتبة**\n  - المُشغِّل: `contextTokens > contextWindow - compaction.reserveTokens`.\n  - يُشغَّل مع `reason: \"threshold\"` و`willRetry: false`.\n  - عند النجاح، إذا كان `compaction.autoContinue !== false`، يُحقن موجه اصطناعي:\n    - `\"Continue if you have next steps.\"`\n\n### التقليم قبل الضغط\n\nقبل إجراء فحوصات الضغط، قد يُشغَّل تقليم نتائج الأدوات (`pruneToolOutputs`).\n\nسياسة التقليم الافتراضية:\n\n- حماية أحدث `40_000` رمز من مخرجات الأدوات.\n- اشتراط توفير ما لا يقل عن `20_000` رمز إجمالياً.\n- عدم تقليم نتائج الأدوات من `skill` أو `read`.\n\nتُستبدل نتائج الأدوات المُقلَّمة بـ:\n\n- `[Output truncated - N tokens]`\n\nإذا أفضى التقليم إلى تغيير الإدخالات، يُعاد كتابة تخزين الجلسة وتُحدَّث حالة رسائل العميل قبل اتخاذ قرارات الضغط.\n\n### منطق الحدود ونقطة القطع\n\nلا يأخذ `prepareCompaction()` في الاعتبار إلا الإدخالات منذ آخر إدخال ضغط (إن وُجد).\n\n1. إيجاد فهرس الضغط السابق.\n2. حساب `boundaryStart = prevCompactionIndex + 1`.\n3. تعديل `keepRecentTokens` باستخدام نسبة الاستخدام المقاسة عند توفرها.\n4. تشغيل `findCutPoint()` على نافذة الحدود.\n\nتشمل نقاط القطع الصالحة:\n\n- إدخالات الرسائل ذات الأدوار: `user`، `assistant`، `bashExecution`، `hookMessage`، `branchSummary`، `compactionSummary`\n- إدخالات `custom_message`\n- إدخالات `branch_summary`\n\nقاعدة صارمة: لا يجوز القطع عند `toolResult` أبداً.\n\nإذا وُجدت إدخالات بيانات وصفية غير رسائلية مباشرةً قبل نقطة القطع (`model_change`، `thinking_level_change`، التسميات، إلخ)، يُضمَّها إلى المنطقة المحتفظ بها بتحريك فهرس القطع للخلف حتى الوصول إلى رسالة أو حد ضغط.\n\n### معالجة الدورة المنقسمة\n\nإذا لم تكن نقطة القطع عند بداية دورة مستخدم، يعامل الضغط الأمر باعتباره دورة منقسمة.\n\nيعتمد اكتشاف بداية الدورة على هذه الحدود كبدايات دورة مستخدم:\n\n- `message.role === \"user\"`\n- `message.role === \"bashExecution\"`\n- إدخال `custom_message`\n- إدخال `branch_summary`\n\nيُولِّد ضغط الدورة المنقسمة ملخصين:\n\n1. ملخص السجل (`messagesToSummarize`)\n2. ملخص بادئة الدورة (`turnPrefixMessages`)\n\nيُدمج الملخص المخزن النهائي على النحو التالي:\n\n```markdown\n<history summary>\n\n---\n\n**Turn Context (split turn):**\n\n<turn prefix summary>\n```\n\n### توليد الملخص\n\nيبني `compact(...)` الملخصات من نص المحادثة المُسلسَل:\n\n1. تحويل الرسائل عبر `convertToLlm()`.\n2. التسلسل باستخدام `serializeConversation()`.\n3. التغليف في `<conversation>...</conversation>`.\n4. إضافة `<previous-summary>...</previous-summary>` اختيارياً.\n5. حقن سياق الخطاف اختيارياً كقائمة `<additional-context>`.\n6. تنفيذ موجه التلخيص مع `SUMMARIZATION_SYSTEM_PROMPT`.\n\nاختيار الموجه:\n\n- أول ضغط: `compaction-summary.md`\n- ضغط تكراري مع ملخص سابق: `compaction-update-summary.md`\n- المرور الثاني للدورة المنقسمة: `compaction-turn-prefix.md`\n- ملخص واجهة مستخدم مختصر: `compaction-short-summary.md`\n\nوضع التلخيص البعيد:\n\n- إذا كان `compaction.remoteEndpoint` مضبوطاً، يُرسل الضغط طلب POST يحتوي على:\n  - `{ systemPrompt, prompt }`\n- يتوقع استجابة JSON تحتوي على `{ summary }` على الأقل.\n\n### سياق عمليات الملفات في الملخصات\n\nيتتبع الضغط نشاط الملفات التراكمي باستخدام استدعاءات أدوات المساعد:\n\n- `read(path)` ← مجموعة القراءة\n- `write(path)` ← مجموعة التعديل\n- `edit(path)` ← مجموعة التعديل\n\nالسلوك التراكمي:\n\n- يتضمن تفاصيل الضغط السابق فقط عندما يكون الإدخال السابق ناتجاً عن المنصة (`fromExtension !== true`).\n- في الدورات المنقسمة، يتضمن أيضاً عمليات ملفات بادئة الدورة.\n- يستثني `readFiles` الملفاتِ التي جرى تعديلها أيضاً.\n\nتُلحَق وسوم الملفات بنص الملخص عبر قالب الموجه:\n\n```xml\n<read-files>\n...\n</read-files>\n<modified-files>\n...\n</modified-files>\n```\n\n### الحفظ والإعادة\n\nبعد توليد الملخص (أو الملخص المُقدَّم من الخطاف)، تقوم جلسة العميل بـ:\n\n1. إلحاق `CompactionEntry` باستخدام `appendCompaction(...)`.\n2. إعادة بناء السياق عبر `buildSessionContext()`.\n3. استبدال رسائل العميل الحية بالسياق المُعاد بناؤه.\n4. إطلاق حدث خطاف `session_compact`.\n\n## مسار تلخيص الفروع\n\nيرتبط تلخيص الفروع بالتنقل في الشجرة، لا بتجاوز حد الرموز.\n\n### المُشغِّل\n\nأثناء `navigateTree(...)`:\n\n1. حساب الإدخالات المتروكة من الورقة القديمة إلى الجد المشترك باستخدام `collectEntriesForBranchSummary(...)`.\n2. إذا طلب المُستدعي ملخصاً (`options.summarize`)، يُولَّد الملخص قبل تبديل الورقة.\n3. إذا وُجد ملخص، يُرفق في هدف التنقل باستخدام `branchWithSummary(...)`.\n\nيُشغَّل هذا عادةً من مسار `/tree` عندما يكون `branchSummary.enabled` مفعَّلاً.\n\n### شكل تبديل الفرع (مرئي)\n\n```text\nالشجرة قبل التنقل:\n\n         ┌─ B ─ C ─ D (old leaf, being abandoned)\n    A ───┤\n         └─ E ─ F (target)\n\nCommon ancestor: A\nEntries to summarize: B, C, D\n\nبعد التنقل مع الملخص:\n\n         ┌─ B ─ C ─ D ─ [summary of B,C,D]\n    A ───┤\n         └─ E ─ F (new leaf)\n```\n\n### التحضير وميزانية الرموز\n\nتحسب `generateBranchSummary(...)` الميزانية على النحو التالي:\n\n- `tokenBudget = model.contextWindow - branchSummary.reserveTokens`\n\nثم تقوم `prepareBranchEntries(...)` بـ:\n\n1. المرور الأول: جمع عمليات الملفات التراكمية من جميع الإدخالات المُلخَّصة، بما فيها تفاصيل `branch_summary` السابقة الناتجة عن المنصة.\n2. المرور الثاني: المشي من الأحدث إلى الأقدم، إضافة الرسائل حتى بلوغ الميزانية.\n3. تفضيل الحفاظ على السياق الأحدث.\n4. قد تُضمَّن إدخالات الملخص الكبيرة قرب حافة الميزانية لضمان الاستمرارية.\n\nتُضمَّن إدخالات الضغط كرسائل (`compactionSummary`) خلال مدخلات تلخيص الفروع.\n\n### توليد الملخص والحفظ\n\nيقوم تلخيص الفروع بـ:\n\n1. تحويل الرسائل المختارة وتسلسلها.\n2. تغليفها في `<conversation>`.\n3. استخدام تعليمات مخصصة إذا وُفِّرت، وإلا يستخدم `branch-summary.md`.\n4. استدعاء نموذج التلخيص مع `SUMMARIZATION_SYSTEM_PROMPT`.\n5. إضافة `branch-summary-preamble.md` في البداية.\n6. إلحاق وسوم عمليات الملفات.\n\nيُخزَّن الناتج كـ `BranchSummaryEntry` مع تفاصيل اختيارية (`readFiles`، `modifiedFiles`).\n\n## نقاط تواصل الامتدادات والخطافات\n\n### `session_before_compact`\n\nخطاف ما قبل الضغط.\n\nيمكنه:\n\n- إلغاء الضغط (`{ cancel: true }`)\n- توفير حمولة ضغط مخصصة كاملة (`{ compaction: CompactionResult }`)\n\n### `session.compacting`\n\nخطاف تخصيص الموجه/السياق للضغط الافتراضي.\n\nيمكنه إرجاع:\n\n- `prompt` (تجاوز موجه الملخص الأساسي)\n- `context` (سطور سياق إضافية تُحقن في `<additional-context>`)\n- `preserveData` (يُخزَّن على إدخال الضغط)\n\n### `session_compact`\n\nإشعار ما بعد الضغط يحتوي على `compactionEntry` المحفوظ وعلامة `fromExtension`.\n\n### `session_before_tree`\n\nيُشغَّل عند التنقل في الشجرة قبل توليد ملخص الفرع الافتراضي.\n\nيمكنه:\n\n- إلغاء التنقل\n- توفير `{ summary: { summary, details } }` مخصص يُستخدم عندما يطلب المستخدم التلخيص\n\n### `session_tree`\n\nحدث ما بعد التنقل يكشف عن الورقة الجديدة/القديمة وإدخال الملخص الاختياري.\n\n## سلوك وقت التشغيل ودلالات الفشل\n\n- يُلغي الضغط اليدوي عملية العميل الحالية أولاً.\n- يلغي `abortCompaction()` وحدات تحكم الضغط اليدوي والتلقائي معاً.\n- يُصدر الضغط التلقائي أحداث بدء/انتهاء جلسة لتحديثات واجهة المستخدم/الحالة.\n- يمكن للضغط التلقائي تجربة مرشحين نموذجيين متعددين وإعادة محاولة الأخطاء العابرة.\n- تُستثنى أخطاء التجاوز من مسار إعادة المحاولة العام لأنها تُعالَج بالضغط.\n- إذا فشل الضغط التلقائي:\n  - يُصدر مسار التجاوز `Context overflow recovery failed: ...`\n  - يُصدر مسار العتبة `Auto-compaction failed: ...`\n- يمكن إلغاء تلخيص الفروع عبر إشارة الإلغاء (مثل Escape)، مما يُعيد نتيجة تنقل ملغاة/متوقفة.\n\n## الإعدادات والقيم الافتراضية\n\nمن `settings-schema.ts`:\n\n- `compaction.enabled` = `true`\n- `compaction.reserveTokens` = `16384`\n- `compaction.keepRecentTokens` = `20000`\n- `compaction.autoContinue` = `true`\n- `compaction.remoteEndpoint` = `undefined`\n- `branchSummary.enabled` = `false`\n- `branchSummary.reserveTokens` = `16384`\n\nتُستهلك هذه القيم في وقت التشغيل بواسطة `AgentSession` ووحدات الضغط وتلخيص الفروع.\n",
	"ar/sessions/handoff-generation-pipeline.md": "---\ntitle: خط أنابيب توليد التسليم\ndescription: خط أنابيب توليد التسليم لإنشاء ملخصات جلسات قابلة للنقل للتعاون بين الفرق.\nsidebar:\n  order: 8\n  label: خط أنابيب التسليم\ni18n:\n  sourceHash: 03666084b5ac\n  translator: machine\n---\n\n# خط أنابيب توليد `/handoff`\n\nيصف هذا المستند كيفية تنفيذ وكيل البرمجة لأمر `/handoff` حاليًا: مسار التشغيل، وموجّه التوليد، والتقاط الإكمال، والتبديل بين الجلسات، وإعادة حقن السياق.\n\n## النطاق\n\nيغطي:\n\n- إرسال أمر `/handoff` التفاعلي\n- دورة حياة `AgentSession.handoff()` وانتقالات الحالة\n- كيفية التقاط مخرجات التسليم من مخرجات المساعد\n- كيفية اختلاف استمرار بيانات التسليم بين الجلسات القديمة والجديدة\n- سلوك واجهة المستخدم في حالات النجاح والإلغاء والفشل\n\nلا يغطي:\n\n- التنقل العام في الشجرة/الآليات الداخلية للفروع\n- أوامر الجلسة غير المتعلقة بالتسليم (`/new`، `/fork`، `/resume`)\n\n## ملفات التطبيق\n\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n\n## مسار التشغيل\n\n1. يُعلَن `/handoff` في بيانات تعريف أوامر الشرطة المائلة المدمجة (`slash-commands.ts`) مع تلميح اختياري مضمّن: `[focus instructions]`.\n2. في معالجة الإدخال التفاعلي (`InputController`)، يتم اعتراض نص الإرسال المطابق لـ `/handoff` أو `/handoff ...` قبل إرسال الموجّه الطبيعي.\n3. يُمسح المحرر ويُستدعى `handleHandoffCommand(customInstructions?)`.\n4. ينفّذ `CommandController.handleHandoffCommand` فحصًا مسبقًا باستخدام الإدخالات الحالية:\n   - يحسب الإدخالات من النوع `type === \"message\"`.\n   - إذا كانت `< 2`، يُصدر تحذيرًا: `Nothing to hand off (no messages yet)` ويعود.\n\nيوجد نفس حارس الحد الأدنى للمحتوى مرة أخرى داخل `AgentSession.handoff()` ويرمي خطأً إذا انتُهك. هذا يُضاعف الأمان على مستوى واجهة المستخدم ومستوى الجلسة معًا.\n\n## دورة الحياة الكاملة\n\n### 1) بدء توليد التسليم\n\n`AgentSession.handoff(customInstructions?)`:\n\n- يقرأ إدخالات الفرع الحالي (`sessionManager.getBranch()`)\n- يتحقق من الحد الأدنى لعدد الرسائل (`>= 2`)\n- ينشئ `#handoffAbortController`\n- يبني موجّهًا ثابتًا مضمّنًا يطلب وثيقة تسليم منظمة (`Goal`، `Constraints & Preferences`، `Progress`، `Key Decisions`، `Critical Context`، `Next Steps`)\n- يُلحق `Additional focus: ...` إذا تم توفير تعليمات مخصصة\n\nيُرسَل الموجّه عبر:\n\n```ts\nawait this.prompt(handoffPrompt, { expandPromptTemplates: false });\n```\n\n`expandPromptTemplates: false` يمنع توسيع قوالب الشرطة المائلة/الموجّهات لهذه الحمولة التعليمية الداخلية.\n\n### 2) التقاط الإكمال\n\nقبل إرسال الموجّه، يشترك `handoff()` في أحداث الجلسة وينتظر `agent_end`.\n\nعند `agent_end`، يستخرج نص التسليم من حالة الوكيل عن طريق المسح للخلف بحثًا عن أحدث رسالة `assistant`، ثم تسلسل جميع كتل `content` التي يكون فيها `type === \"text\"` مع `\\n`.\n\nافتراضات الاستخراج المهمة:\n\n- تُستخدم كتل النص فقط؛ يتم تجاهل المحتوى غير النصي.\n- يفترض أن آخر رسالة مساعد تقابل توليد التسليم.\n- لا يحلل أقسام markdown ولا يتحقق من الامتثال للتنسيق.\n- إذا لم يحتوِ مخرج المساعد على كتل نصية، يُعامَل التسليم على أنه مفقود.\n\n### 3) فحوصات الإلغاء\n\nيُعيد `handoff()` قيمة `undefined` عند تحقق أي من الشرطين:\n\n- عدم وجود نص تسليم ملتقط، أو\n- كون `#handoffAbortController.signal.aborted` صحيحًا\n\nيمسح دائمًا `#handoffAbortController` في `finally`.\n\n### 4) إنشاء جلسة جديدة\n\nإذا تم التقاط النص ولم يُلغَ:\n\n1. مسح كاتب الجلسة الحالية (`sessionManager.flush()`)\n2. بدء جلسة جديدة تمامًا (`sessionManager.newSession()`)\n3. إعادة تعيين حالة الوكيل في الذاكرة (`agent.reset()`)\n4. إعادة ربط `agent.sessionId` بمعرّف الجلسة الجديدة\n5. مسح مصفوفات السياق المنتظرة (`#steeringMessages`، `#followUpMessages`، `#pendingNextTurnMessages`)\n6. إعادة تعيين عداد تذكير المهام\n\nينشئ `newSession()` ترويسة جديدة وقائمة إدخالات فارغة (إعادة تعيين الورقة إلى `null`). في مسار التسليم، لا يُمرَّر `parentSession`.\n\n### 5) حقن سياق التسليم\n\nيُغلَّف مستند التسليم المُولَّد ويُلحق بالجلسة الجديدة كإدخال `custom_message`:\n\n```text\n<handoff-context>\n...handoff text...\n</handoff-context>\n\nThe above is a handoff document from a previous session. Use this context to continue the work seamlessly.\n```\n\nاستدعاء الإدراج:\n\n```ts\nthis.sessionManager.appendCustomMessageEntry(\"handoff\", handoffContent, true);\n```\n\nالدلالات:\n\n- `customType`: `\"handoff\"`\n- `display`: `true` (مرئي في إعادة بناء TUI)\n- نوع الإدخال: `custom_message` (يشارك في سياق نموذج اللغة الكبير)\n\n### 6) إعادة بناء سياق الوكيل النشط\n\nبعد الحقن:\n\n1. يحلّ `sessionManager.buildSessionContext()` قائمة الرسائل للورقة الحالية\n2. يجعل `agent.replaceMessages(sessionContext.messages)` رسالة التسليم المُحقَنة سياقًا نشطًا\n3. تُعيد الدالة `{ document: handoffText }`\n\nفي هذه المرحلة، يحتوي سياق نموذج اللغة الكبير النشط في الجلسة الجديدة على رسالة التسليم المُحقَنة، وليس النسخة القديمة.\n\n## نموذج الاستمرار: الجلسة القديمة مقابل الجلسة الجديدة\n\n### الجلسة القديمة\n\nأثناء التوليد، يظل استمرار الرسائل الطبيعي نشطًا. يُستمَر بالاستجابة التسليمية للمساعد كإدخال `message` عادي عند `message_end`.\n\nالنتيجة: تحتوي الجلسة الأصلية على التسليم المُولَّد المرئي كجزء من السجل التاريخي.\n\n### الجلسة الجديدة\n\nبعد إعادة تعيين الجلسة، يُستمَر بالتسليم كـ `custom_message` مع `customType: \"handoff\"`.\n\nيحوّل `buildSessionContext()` هذا الإدخال إلى رسالة سياق مخصصة/للمستخدم في وقت التشغيل عبر `createCustomMessage(...)` لتُدرَج في الموجّهات المستقبلية من الجلسة الجديدة.\n\n## سلوك المتحكم/واجهة المستخدم\n\nسلوك `CommandController.handleHandoffCommand`:\n\n- يستدعي `await session.handoff(customInstructions)`\n- إذا كانت النتيجة `undefined`: `showError(\"Handoff cancelled\")`\n- عند النجاح:\n  - `rebuildChatFromMessages()` (تحميل سياق الجلسة الجديدة، بما في ذلك التسليم المُحقَن)\n  - إبطال سطر الحالة والحد العلوي للمحرر\n  - إعادة تحميل المهام\n  - إلحاق سطر نجاح في الدردشة: `New session started with handoff context`\n- عند الاستثناء:\n  - إذا كانت الرسالة `\"Handoff cancelled\"` أو كان اسم الخطأ `AbortError`: `showError(\"Handoff cancelled\")`\n  - وإلا: `showError(\"Handoff failed: <message>\")`\n- يطلب التصيير في النهاية\n\n## دلالات الإلغاء (السلوك الحالي)\n\n### آلية الإلغاء على مستوى الجلسة\n\nيُوفّر `AgentSession`:\n\n- `abortHandoff()` ← يُلغي `#handoffAbortController`\n- `isGeneratingHandoff` ← صحيح أثناء وجود المتحكم\n\nعند استخدام مسار الإلغاء هذا، يرفض مشترك التسليم مع `Error(\"Handoff cancelled\")`، ويُعيّنه متحكم الأوامر إلى واجهة مستخدم الإلغاء.\n\n### قيد مسار `/handoff` التفاعلي\n\nفي التوصيل الحالي للمتحكم التفاعلي، لا يُثبّت `/handoff` معالجًا مخصصًا لـ Escape يستدعي `abortHandoff()` (على عكس مسارات الضغط/ملخص الفرع التي تتجاوز مؤقتًا `editor.onEscape`).\n\nالأثر العملي:\n\n- توجد دعم للإلغاء على مستوى الجلسة، لكن لا يوجد ربط مفتاح خاص بالتسليم في مسار أمر `/handoff`.\n- قد يظل مقاطعة المستخدم ممكنة من خلال مسارات إلغاء الوكيل الأشمل، لكن ذلك ليس نفس قناة الإلغاء الصريحة التي يستخدمها `abortHandoff()`.\n\n## التسليم الملغى مقابل الفاشل\n\nالتصنيف الحالي لواجهة المستخدم:\n\n- **ملغى/مُلغى**\n  - يُشغّل مسار `abortHandoff()` الخطأ `\"Handoff cancelled\"`، أو\n  - خطأ `AbortError` مُرمى\n  - تُظهر واجهة المستخدم `Handoff cancelled`\n\n- **فاشل**\n  - أي خطأ مُرمى آخر من `handoff()` / خط أنابيب الموجّه (أخطاء التحقق من النموذج/API، استثناءات وقت التشغيل، إلخ)\n  - تُظهر واجهة المستخدم `Handoff failed: ...`\n\nتفاصيل إضافية: إذا اكتمل التوليد لكن لم يُستخرج أي نص، يُعيد `handoff()` قيمة `undefined` ويُبلّغ المتحكم حاليًا عن **إلغاء**، وليس **فشل**.\n\n## حواجز الحماية للجلسات القصيرة والحد الأدنى للمحتوى\n\nيمنع حارسان التسليمات ذات الإشارة المنخفضة:\n\n- طبقة واجهة المستخدم (`handleHandoffCommand`): تحذّر وتعود مبكرًا لـ `< 2` إدخالات رسائل\n- طبقة الجلسة (`handoff()`): تُرمي نفس الشرط كخطأ\n\nيتجنب هذا إنشاء جلسة جديدة بسياق تسليم فارغ أو شبه فارغ.\n\n## ملخص انتقالات الحالة\n\nتدفق الحالة رفيع المستوى:\n\n1. اعتراض أمر الشرطة المائلة التفاعلية\n2. حارس عدد الرسائل المسبق\n3. إنشاء `#handoffAbortController` (`isGeneratingHandoff = true`)\n4. إرسال موجّه التسليم الداخلي (مرئي في الدردشة كتوليد مساعد طبيعي)\n5. عند `agent_end`، استخراج آخر نص للمساعد\n6. إذا كان مفقودًا/ملغى ← إعادة `undefined` أو مسار خطأ الإلغاء\n7. إذا كان موجودًا:\n   - مسح الجلسة القديمة\n   - إنشاء جلسة جديدة فارغة\n   - إعادة تعيين طوابير/عدادات وقت التشغيل\n   - إلحاق `custom_message(handoff)`\n   - إعادة بناء رسائل الوكيل النشط واستبدالها\n8. يعيد المتحكم بناء واجهة دردشة المستخدم ويُعلن النجاح\n9. مسح `#handoffAbortController` (`isGeneratingHandoff = false`)\n\n## الافتراضات والقيود المعروفة\n\n- استخراج التسليم استدلالي: \"آخر كتل نص المساعد\"؛ لا يوجد تحقق هيكلي.\n- لا يوجد فحص صارم بأن markdown المُولَّد يتبع تنسيق القسم المطلوب.\n- يُبلَّغ عن النص المُستخرَج المفقود كإلغاء في تجربة مستخدم المتحكم.\n- يفتقر تدفق التفاعل `/handoff` حاليًا إلى ربط Escape→`abortHandoff()` مخصص.\n- لا يُعيَّن بيانات تعريف النسب للجلسة الجديدة (`parentSession`) من خلال هذا المسار.\n",
	"ar/sessions/memory.md": "---\ntitle: الذاكرة المستقلة\ndescription: نظام ذاكرة مستقل لحفظ تفضيلات المستخدم وسياق المشروع والملاحظات عبر الجلسات.\nsidebar:\n  order: 7\n  label: الذاكرة المستقلة\ni18n:\n  sourceHash: 2aa9f516aa1e\n  translator: machine\n---\n\n# الذاكرة المستقلة\n\nعند تفعيلها، يقوم الوكيل تلقائيًا باستخراج المعرفة الدائمة من الجلسات السابقة ويحقن ملخصًا مضغوطًا في كل جلسة جديدة. بمرور الوقت، يبني مخزن ذاكرة على مستوى المشروع — قرارات تقنية، سير عمل متكرر، مشكلات شائعة — ينتقل تلقائيًا دون جهد يدوي.\n\nمعطّلة افتراضيًا. يمكن تفعيلها عبر `/settings` أو `config.yml`:\n\n```yaml\nmemories:\n  enabled: true\n```\n\n## الاستخدام\n\n### ما يتم حقنه\n\nعند بدء الجلسة، إذا وُجد ملخص ذاكرة للمشروع الحالي، يتم حقنه في موجه النظام ككتلة **إرشادات الذاكرة**. يُوجَّه الوكيل إلى:\n\n- التعامل مع الذاكرة كسياق استدلالي — مفيد للعمليات والقرارات السابقة، وليس مرجعيًا لحالة المستودع الحالية.\n- الإشارة إلى مسار مصنوعات الذاكرة عندما تُغيّر الذاكرة الخطة، وإقرانها بأدلة من المستودع الحالي قبل التنفيذ.\n- تفضيل حالة المستودع وتعليمات المستخدم عند تعارضها مع الذاكرة؛ والتعامل مع الذاكرة المتعارضة على أنها قديمة.\n\n### قراءة مصنوعات الذاكرة\n\nيمكن للوكيل قراءة ملفات الذاكرة مباشرةً باستخدام عناوين `memory://` مع أداة `read`:\n\n| العنوان | المحتوى |\n|---|---|\n| `memory://root` | الملخص المضغوط المحقون عند بدء التشغيل |\n| `memory://root/MEMORY.md` | مستند الذاكرة الكامل طويل المدى |\n| `memory://root/skills/<name>/SKILL.md` | دليل مهارة مُنشأ |\n\n### أمر `/memory` المائل\n\n| الأمر الفرعي | التأثير |\n|---|---|\n| `view` | عرض حمولة حقن الذاكرة الحالية |\n| `clear` / `reset` | حذف جميع بيانات الذاكرة والمصنوعات المُنشأة |\n| `enqueue` / `rebuild` | فرض تشغيل التجميع عند بدء التشغيل التالي |\n\n## كيف تعمل\n\nتُبنى الذكريات بواسطة خط أنابيب يعمل في الخلفية عند بدء التشغيل أو يُفعَّل يدويًا عبر الأمر المائل.\n\n**المرحلة الأولى — الاستخراج لكل جلسة:** لكل جلسة سابقة تغيّرت منذ آخر معالجة لها، يقرأ نموذج سجل الجلسة ويستخرج الإشارات الدائمة: القرارات التقنية، القيود، الإخفاقات المحلولة، سير العمل المتكرر. تُتخطى الجلسات الحديثة جدًا أو القديمة جدًا أو النشطة حاليًا. ينتج كل استخراج كتلة ذاكرة خام وملخصًا قصيرًا لتلك الجلسة.\n\n**المرحلة الثانية — التجميع:** بعد الاستخراج، تقرأ مرحلة نموذج ثانية جميع الاستخراجات لكل جلسة وتنتج ثلاثة مخرجات تُكتب على القرص:\n\n- `MEMORY.md` — مستند ذاكرة طويل المدى مُنسّق\n- `memory_summary.md` — النص المضغوط المحقون عند بدء الجلسة\n- `skills/` — أدلة إجرائية قابلة لإعادة الاستخدام، كل منها في دليل فرعي خاص\n\nتستخدم المرحلة الثانية قفلًا (lease) لمنع التشغيل المزدوج عند بدء عمليات متعددة في وقت واحد. تُحذف أدلة المهارات القديمة من التشغيلات السابقة تلقائيًا.\n\nتُفحص جميع المخرجات بحثًا عن الأسرار قبل كتابتها على القرص.\n\n### سلوك الاستخراج\n\nيُحدَّد سلوك استخراج الذاكرة والتجميع بالكامل بواسطة ملفات موجهات ثابتة في `src/prompts/memories/`.\n\n| الملف | الغرض | المتغيرات |\n|---|---|---|\n| `stage_one_system.md` | موجه النظام للاستخراج لكل جلسة | — |\n| `stage_one_input.md` | قالب دور المستخدم الذي يغلف محتوى الجلسة | `{{thread_id}}`، `{{response_items_json}}` |\n| `consolidation.md` | موجه التجميع عبر الجلسات | `{{raw_memories}}`، `{{rollout_summaries}}` |\n| `read_path.md` | إرشادات الذاكرة المحقونة في الجلسات الحية | `{{memory_summary}}` |\n\n### اختيار النموذج\n\nتعتمد الذاكرة على نظام أدوار النماذج.\n\n| المرحلة | الدور | الغرض |\n|---|---|---|\n| المرحلة الأولى (الاستخراج) | `default` | استخراج المعرفة لكل جلسة |\n| المرحلة الثانية (التجميع) | `smol` | التوليف عبر الجلسات |\n\nإذا لم يكن `smol` مُهيّأً، تعود المرحلة الثانية إلى دور `default`.\n\n## الإعدادات\n\n| الإعداد | القيمة الافتراضية | الوصف |\n|---|---|---|\n| `memories.enabled` | `false` | مفتاح التشغيل الرئيسي |\n| `memories.maxRolloutAgeDays` | `30` | لا تُعالج الجلسات الأقدم من هذا |\n| `memories.minRolloutIdleHours` | `12` | تُتخطى الجلسات النشطة مؤخرًا أكثر من هذا |\n| `memories.maxRolloutsPerStartup` | `64` | الحد الأقصى للجلسات المعالجة في بدء تشغيل واحد |\n| `memories.summaryInjectionTokenLimit` | `5000` | الحد الأقصى لرموز الملخص المحقون في موجه النظام |\n\nتتوفر مفاتيح ضبط إضافية (التزامن، مدد القفل، ميزانيات الرموز) في الإعدادات للاستخدام المتقدم.\n\n## الملفات الرئيسية\n\n- `src/memories/index.ts` — تنسيق خط الأنابيب، الحقن، معالجة الأوامر المائلة\n- `src/memories/storage.ts` — طابور المهام وسجل السلاسل المدعوم بـ SQLite\n- `src/prompts/memories/` — قوالب موجهات الذاكرة\n- `src/internal-urls/memory-protocol.ts` — معالج عناوين `memory://`\n",
	"ar/sessions/non-compaction-retry-policy.md": "---\ntitle: سياسة إعادة المحاولة التلقائية لغير الضغط\ndescription: سياسة إعادة المحاولة التلقائية لأعطال API العابرة خارج مسار الضغط.\nsidebar:\n  order: 6\n  label: سياسة إعادة المحاولة\ni18n:\n  sourceHash: 8999a0258dd8\n  translator: machine\n---\n\n# سياسة إعادة المحاولة التلقائية لغير الضغط\n\nيصف هذا المستند مسار إعادة المحاولة القياسي لأخطاء API في `AgentSession`.\n\nيستثني هذا المستند صراحةً استرداد تجاوز السياق عبر الضغط التلقائي. تتولى منطق الضغط معالجة التجاوز، وهو موثق بشكل منفصل في [`compaction.md`](./compaction.md).\n\n## ملفات التنفيذ\n\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/config/settings-schema.ts`](../../packages/coding-agent/src/config/settings-schema.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts)\n- [`../src/modes/rpc/rpc-client.ts`](../../packages/coding-agent/src/modes/rpc/rpc-client.ts)\n- [`../src/modes/rpc/rpc-types.ts`](../../packages/coding-agent/src/modes/rpc/rpc-types.ts)\n\n## حدود النطاق مقابل الضغط\n\nيتم التحقق من إعادة المحاولة والضغط من مسار `agent_end` نفسه، غير أنهما مفصولان عن قصد:\n\n1. يفحص `agent_end` آخر رسالة من المساعد.\n2. يعمل `#isRetryableError(...)` أولاً.\n3. إذا بدأت إعادة المحاولة، يُتخطى التحقق من الضغط في تلك الدورة.\n4. تُستثنى أخطاء تجاوز السياق استثناءً صارماً من تصنيف إعادة المحاولة (يختصر `isContextOverflow(...)` إعادة المحاولة).\n5. يمر التجاوز بعدها إلى `#checkCompaction(...)` بدلاً من إعادة المحاولة القياسية.\n\nإذن: تستخدم حالات الفشل من نوع الحمل الزائد/معدل الطلبات/الخادم/الشبكة سياسة إعادة المحاولة هذه؛ أما تجاوز نافذة السياق فيستخدم استرداد الضغط.\n\n## تصنيف إعادة المحاولة\n\nيتطلب `#isRetryableError(...)` استيفاء جميع الشروط التالية:\n\n- يكون `stopReason === \"error\"` للمساعد\n- وجود `errorMessage`\n- ألا تكون الرسالة تجاوزاً للسياق\n- مطابقة `errorMessage` لـ `#isRetryableErrorMessage(...)`\n\nمجموعة الأنماط القابلة لإعادة المحاولة الحالية (قائمة على التعبيرات النمطية):\n\n- overloaded\n- rate limit / usage limit / too many requests\n- فئات الخوادم المشابهة لـ HTTP: 429، 500، 502، 503، 504\n- service unavailable / server error / internal error\n- connection error / fetch failed\n- صياغة `retry delay`\n\nهذا تصنيف بمطابقة الأنماط النصية، وليس رموز أخطاء الموفر المكتوبة.\n\n## دورة حياة إعادة المحاولة وانتقالات الحالة\n\nحالة الجلسة المستخدمة في إعادة المحاولة:\n\n- `#retryAttempt: number` (`0` تعني الخمول)\n- `#retryPromise: Promise<void> | undefined` (يتتبع دورة حياة إعادة المحاولة الجارية)\n- `#retryResolve: (() => void) | undefined` (يحل `#retryPromise`)\n- `#retryAbortController: AbortController | undefined` (يلغي سكون التراجع)\n\nالتدفق (`#handleRetryableError`):\n\n1. قراءة مجموعة إعدادات `retry`.\n2. إذا كان `retry.enabled === false`، توقف فوراً (`false`، لم تبدأ إعادة المحاولة).\n3. زيادة `#retryAttempt`.\n4. إنشاء `#retryPromise` مرة واحدة (أول محاولة في السلسلة).\n5. إذا تجاوزت المحاولة `retry.maxRetries`، إصدار حدث الفشل النهائي والتوقف.\n6. حساب التأخير: `retry.baseDelayMs * 2^(attempt-1)`.\n7. لأخطاء حد الاستخدام، تحليل تلميحات إعادة المحاولة واستدعاء تخزين المصادقة (`markUsageLimitReached(...)`؛ إذا نجح تبديل الموفر/النموذج، يُجبر التأخير على `0`.\n8. إصدار `auto_retry_start`.\n9. إزالة رسالة خطأ المساعد الأخيرة من حالة وقت تشغيل العامل (تُحتفظ بها في سجل الجلسة الدائم).\n10. النوم مع دعم الإلغاء.\n11. عند الاستيقاظ، جدولة `agent.continue()` عبر `setTimeout(..., 0)`.\n\n### ما يُعيد تهيئة عدادات إعادة المحاولة\n\nيُعاد تعيين `#retryAttempt` إلى `0` في هذه الحالات:\n\n- أول رسالة مساعد ناجحة غير خاطئة وغير ملغاة بعد بدء إعادة المحاولة (تُصدر `auto_retry_end { success: true }`)\n- إلغاء إعادة المحاولة أثناء نوم التراجع\n- مسار تجاوز الحد الأقصى لإعادة المحاولة\n\nيحل `#retryPromise` ويصفى عند انتهاء سلسلة إعادة المحاولة (نجاح أو إلغاء أو تجاوز الحد الأقصى)، عبر `#resolveRetry()`.\n\n## التراجع ودلالات الحد الأقصى للمحاولات\n\nالإعدادات:\n\n- `retry.enabled` (الافتراضي `true`)\n- `retry.maxRetries` (الافتراضي `3`)\n- `retry.baseDelayMs` (الافتراضي `2000`)\n\nترقيم المحاولات:\n\n- يُزاد عداد المحاولات قبل التحقق من الحد الأقصى\n- تستخدم أحداث البدء المحاولة الحالية (بترقيم يبدأ من 1)\n- يُبلّغ حدث انتهاء تجاوز الحد الأقصى بـ `attempt: this.#retryAttempt - 1` (عدد آخر محاولة إعادة)\n\nتسلسل التراجع مع الإعدادات الافتراضية:\n\n- المحاولة 1: 2000 مللي ثانية\n- المحاولة 2: 4000 مللي ثانية\n- المحاولة 3: 8000 مللي ثانية\n\nتُستخدم مدخلات تجاوز التأخير فقط في مسار معالجة حد الاستخدام، وفقط للتأثير في قرار تبديل النموذج/الحساب في تخزين المصادقة. في مسار إعادة المحاولة الرئيسي غير المضغوط، يظل التراجع تأخيراً أسياً محلياً ما لم ينجح التبديل (`delayMs = 0`).\n\n## آليات الإلغاء\n\n### الإلغاء الصريح لإعادة المحاولة\n\n`abortRetry()`:\n\n- يلغي `#retryAbortController` (إن وُجد)\n- يحل وعد إعادة المحاولة (`#resolveRetry()`) لإلغاء حظر المنتظرين\n\nإذا أصاب الإلغاء أثناء النوم، يُصدر مسار الالتقاط:\n\n- `auto_retry_end { success: false, finalError: \"Retry cancelled\" }`\n- يُعيد تعيين المحاولة/وحدة التحكم\n\n### تفاعل الإلغاء العام للعملية\n\nيستدعي `abort()` الدالة `abortRetry()` قبل إلغاء تدفق العامل النشط. يضمن ذلك إلغاء تراجع إعادة المحاولة عند إصدار المستخدم أمر إلغاء عام.\n\n### تفاعل واجهة المستخدم النصية (TUI)\n\nعند `auto_retry_start`، تقوم EventController بـ:\n\n- تبديل معالج `Esc` إلى `session.abortRetry()`\n- عرض نص المحمّل: `Retrying (attempt/maxAttempts) in Ns… (esc to cancel)`\n\nعند `auto_retry_end`، تستعيد معالج `Esc` السابق وتمسح حالة المحمّل.\n\n## سلوك البث واكتمال الطلب\n\nيتوقف `prompt()` في نهاية المطاف على `#waitForRetry()` بعد عودة `agent.prompt(...)`.\n\nالتأثير:\n\n- لا يُحسم استدعاء الطلب بالكامل حتى تنتهي أي سلسلة إعادة محاولة مبدوءة (نجاح/فشل/إلغاء)\n- دورة حياة إعادة المحاولة جزء من حدود تنفيذ طلب منطقي واحد\n\nيمنع ذلك المستدعين من معاملة دورة التكرار قيد إعادة المحاولة على أنها مكتملة مبكراً.\n\n## الضوابط: الإعدادات وRPC\n\n### مفاتيح التهيئة\n\nمحددة في مخطط الإعدادات ضمن مجموعة retry:\n\n- `retry.enabled`\n- `retry.maxRetries`\n- `retry.baseDelayMs`\n\nمبدّلات برمجية في الجلسة:\n\n- `setAutoRetryEnabled(enabled)` تكتب `retry.enabled`\n- `autoRetryEnabled` تقرأ `retry.enabled`\n- `isRetrying` تُبلّغ عما إذا كانت وعد دورة حياة إعادة المحاولة نشطة\n\n### ضوابط RPC\n\nسطح أوامر RPC:\n\n- `set_auto_retry` → `session.setAutoRetryEnabled(command.enabled)`\n- `abort_retry` → `session.abortRetry()`\n\nمساعدات العميل:\n\n- `RpcClient.setAutoRetry(enabled)`\n- `RpcClient.abortRetry()`\n\nيُعيد كلا الأمرين استجابات النجاح؛ تأتي تفاصيل تقدم/فشل إعادة المحاولة من أحداث الجلسة المبثوثة، لا من حمولات استجابة الأوامر.\n\n## إصدار الأحداث وإظهار الفشل\n\nأحداث إعادة المحاولة على مستوى الجلسة:\n\n- `auto_retry_start { attempt, maxAttempts, delayMs, errorMessage }`\n- `auto_retry_end { success, attempt, finalError? }`\n\nالانتشار:\n\n- تُصدر عبر `AgentSession.subscribe(...)`\n- تُمرَّر إلى مشغّل الامتداد كأحداث امتداد\n- في وضع RPC، تُمرَّر مباشرةً ككائنات أحداث JSON (`session.subscribe(event => output(event))`)\n- في واجهة المستخدم النصية، تستهلكها `EventController` لواجهة المحمّل/الخطأ\n\nإظهار الفشل النهائي:\n\n- عند تجاوز الحد الأقصى أو الإلغاء، `auto_retry_end.success === false`\n- تعرض واجهة المستخدم النصية: `Retry failed after N attempts: <finalError>`\n- تستقبل الامتدادات/الخطافات `auto_retry_end` بالحقول ذاتها\n- يستقبل مستهلكو RPC كائن الحدث ذاته في تدفق stdout\n\n## شروط التوقف الدائم\n\nتتوقف إعادة المحاولة ولن تستمر تلقائياً عند حدوث أي مما يلي:\n\n- `retry.enabled` خاطئ\n- الخطأ غير مصنف كقابل لإعادة المحاولة\n- الخطأ تجاوز للسياق (مفوض إلى مسار الضغط)\n- تجاوز الحد الأقصى لإعادة المحاولة\n- إلغاء المستخدم لإعادة المحاولة (`abort_retry` أو `Esc` أثناء محمّل إعادة المحاولة)\n- يلغي الإلغاء العام (`abort`) إعادة المحاولة أولاً\n\nلا يزال بإمكان سلسلة إعادة محاولة جديدة البدء لاحقاً عند حدوث خطأ قابل لإعادة المحاولة في المستقبل بعد إعادة تعيين العدادات.\n\n## تحفظات التشغيل\n\n- التصنيف مطابقة نصية بتعبيرات نمطية؛ لا تُستخدم هنا أخطاء الموفر الهيكلية الخاصة بكل مزوّد.\n- تحذف إعادة المحاولة رسالة خطأ المساعد الفاشلة من **سياق وقت التشغيل** قبل الاستمرار، لكن سجل الجلسة لا يزال يحتفظ بإدخال الخطأ ذاك.\n- يكشف `RpcSessionState` حالياً عن `autoCompactionEnabled` لكن لا يكشف عن حقل `autoRetryEnabled`؛ يجب على مستدعي RPC تتبع حالة مبدّلهم الخاصة أو الاستعلام عن الإعدادات عبر واجهات API أخرى.\n",
	"ar/sessions/session-operations-export-share-fork-resume.md": "---\ntitle: 'عمليات الجلسة: التصدير، التفريغ، المشاركة، التفرع، الاستئناف'\ndescription: عمليات الجلسة للتصدير والمشاركة والتفرع واستئناف المحادثات.\nsidebar:\n  order: 3\n  label: العمليات\ni18n:\n  sourceHash: e3c210b29c3e\n  translator: machine\n---\n\n# عمليات الجلسة: export، dump، share، fork، resume/continue\n\nيصف هذا المستند السلوك المرئي للمشغّل لعمليات تصدير/مشاركة/تفرع/استئناف الجلسات كما هي مُنفَّذة حالياً.\n\n## ملفات التنفيذ\n\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/export/html/index.ts`](../../packages/coding-agent/src/export/html/index.ts)\n- [`../src/export/custom-share.ts`](../../packages/coding-agent/src/export/custom-share.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n\n## مصفوفة العمليات\n\n| العملية | مسار الدخول | تعديل الجلسة | إنشاء/تبديل ملف الجلسة | المخرج الناتج |\n|---|---|---|---|---|\n| `/dump` | أمر شرطة مائلة تفاعلي | لا | لا | نص في الحافظة |\n| `/export [path]` | أمر شرطة مائلة تفاعلي | لا | لا | ملف HTML |\n| `--export <session.jsonl> [outputPath]` | مسار سريع عند بدء CLI | لا يوجد تعديل جلسة أثناء التشغيل | لا توجد جلسة نشطة؛ يقرأ الملف المستهدف | ملف HTML |\n| `/share` | أمر شرطة مائلة تفاعلي | لا | لا | ملف HTML مؤقت + رابط مشاركة/gist |\n| `/fork` | أمر شرطة مائلة تفاعلي | نعم (تتغير هوية الجلسة النشطة) | ينشئ ملف جلسة جديد ويُبدّل الجلسة الحالية إليه (وضع الاستمرارية فقط) | ينسخ مجلد المخرجات إلى مساحة اسم الجلسة الجديدة عند وجوده |\n| `/resume` | أمر شرطة مائلة تفاعلي | نعم (يُستبدل الحالة النشطة في الذاكرة) | يتبدّل إلى ملف جلسة موجود محدد | لا شيء |\n| `--resume` | بدء CLI (منتقي) | نعم بعد إنشاء الجلسة | يفتح ملف جلسة موجود محدد | لا شيء |\n| `--resume <id\\|path>` | بدء CLI | نعم بعد إنشاء الجلسة | يفتح جلسة موجودة؛ حالة المشروع المختلف يمكن أن تُنشئ تفرعاً في المشروع الحالي | لا شيء |\n| `--continue` | بدء CLI | نعم بعد إنشاء الجلسة | يفتح مسار تتبع الطرفية أو أحدث جلسة؛ ينشئ جلسة جديدة إذا لم توجد أي جلسة | لا شيء |\n\n## التصدير والتفريغ\n\n### `/export [outputPath]` (تفاعلي)\n\nالمسار:\n\n1. يوجّه `InputController` أمر `/export...` إلى `CommandController.handleExportCommand`.\n2. يقسم الأمر على المسافات البيضاء ويستخدم فقط الوسيط الأول بعد `/export` كـ `outputPath`.\n3. يستدعي `AgentSession.exportToHtml()` الدالة `exportSessionToHtml(sessionManager, state, { outputPath, themeName })`.\n4. عند النجاح، تعرض واجهة المستخدم المسار وتفتح الملف في المتصفح.\n\nتفاصيل السلوك:\n\n- يتم رفض الوسائط `--copy` و `clipboard` و `copy` صراحةً مع تحذير باستخدام `/dump`.\n- يُضمّن التصدير رأس/إدخالات/طرف الجلسة بالإضافة إلى `systemPrompt` الحالي وأوصاف الأدوات من حالة الوكيل.\n- لا تُضاف أي إدخالات جلسة أثناء التصدير.\n\nتحذير:\n\n- تحليل الوسائط مبني على المسافات البيضاء (`text.split(/\\s+/)`)، لذا لا يتم الحفاظ على المسارات المقتبسة التي تحتوي على مسافات كمسار واحد عبر مسار الأمر هذا.\n\n### `--export <inputSessionFile> [outputPath]` (CLI)\n\nالمسار في `main.ts`:\n\n1. يُعالَج مبكراً (قبل بدء الجلسة التفاعلية).\n2. يستدعي `exportFromFile(inputPath, outputPath?)`.\n3. يحمّل `SessionManager.open(inputPath)` الإدخالات، ثم يُولَّد HTML ويُكتب.\n4. تطبع العملية `Exported to: ...` وتنتهي.\n\nتفاصيل السلوك:\n\n- ملف الإدخال المفقود يظهر كخطأ `File not found: <path>`.\n- لا ينشئ هذا المسار `AgentSession` ولا يُعدّل أي جلسة قيد التشغيل.\n\n### `/dump` (تصدير تفاعلي إلى الحافظة)\n\nالمسار:\n\n1. يستدعي `CommandController.handleDumpCommand()` الدالة `session.formatSessionAsText()`.\n2. إذا كانت سلسلة فارغة، يُبلَّغ `No messages to dump yet.`\n3. وإلا يُنسخ إلى الحافظة عبر `copyToClipboard` الأصلي.\n\nمحتوى التفريغ يتضمن:\n\n- موجّه النظام\n- النموذج النشط/مستوى التفكير\n- تعريفات الأدوات + المعلمات\n- رسائل المستخدم/المساعد\n- كتل التفكير واستدعاءات الأدوات\n- نتائج الأدوات وكتل التنفيذ (باستثناء إدخالات bash/python المميزة بـ `excludeFromContext`)\n- إدخالات مخصصة/hook/إشارات ملفات/ملخص الفروع/ملخص الضغط\n\nلا تُجرى أي تغييرات على استمرارية الجلسة عند التفريغ.\n\n## المشاركة\n\n`/share` تفاعلي فقط ويبدأ دائماً بتصدير الجلسة الحالية إلى ملف HTML مؤقت.\n\n### المرحلة 1: التصدير المؤقت\n\n- مسار الملف المؤقت: `${os.tmpdir()}/${Snowflake.next()}.html`\n- يستخدم `session.exportToHtml(tmpFile)`\n- إذا فشل التصدير (خاصةً الجلسات في الذاكرة)، تنتهي المشاركة بخطأ.\n\n### المرحلة 2: معالج المشاركة المخصص (إن وُجد)\n\nيتحقق `loadCustomShare()` من `~/.xcsh/agent` بحثاً عن أول مرشح موجود:\n\n- `share.ts`\n- `share.js`\n- `share.mjs`\n\nالمتطلبات:\n\n- يجب أن يُصدّر الوحدة افتراضياً دالة `(htmlPath) => Promise<CustomShareResult | string | undefined>`.\n\nإذا كان موجوداً وصالحاً:\n\n- تدخل واجهة المستخدم حالة تحميل `Sharing...`.\n- تفسير نتيجة المعالج:\n  - سلسلة نصية => تُعامل كرابط URL، يُعرض ويُفتح\n  - كائن => يُعرض `url` و/أو `message`؛ يُفتح `url`\n  - `undefined`/قيمة زائفة => `Session shared` عام\n- يُحذف الملف المؤقت بعد الاكتمال.\n\nسلوك الاحتياط الحرج:\n\n- إذا كان المعالج المخصص موجوداً لكن فشل تحميله، يُبلَّغ خطأ الأمر ويعود.\n- إذا نُفذ المعالج المخصص ورمى استثناءً، يُبلَّغ خطأ الأمر ويعود.\n- في كلتا حالتي الفشل، **لا** يتراجع إلى GitHub gist.\n- يحدث الاحتياط بـ gist فقط عندما لا يوجد سكريبت مشاركة مخصص.\n\n### المرحلة 3: الاحتياط الافتراضي بـ gist\n\nفقط عندما لا يُعثر على معالج مشاركة مخصص:\n\n1. يتحقق من `gh auth status`.\n2. يعرض حالة تحميل `Creating gist...`.\n3. ينفّذ `gh gist create --public=false <tmpFile>`.\n4. يحلّل رابط gist، يستخرج معرّف gist، يبني رابط المعاينة `https://gistpreview.github.io/?<id>`.\n5. يعرض كلاً من رابط المعاينة ورابط gist؛ يفتح المعاينة.\n\nدلالات الإلغاء/الإحباط في المشاركة:\n\n- يحتوي حالة التحميل على خطاف `onAbort` الذي يستعيد واجهة المحرر ويُبلّغ `Share cancelled`.\n- لا يُمرّر لأمر `gh gist create` الأساسي إشارة إحباط في مسار الشيفرة هذا؛ الإلغاء على مستوى واجهة المستخدم ويُتحقق منه بعد عودة الأمر.\n\n## التفرع\n\n`/fork` ينشئ جلسة جديدة من الجلسة الحالية ويُبدّل هوية الجلسة النشطة.\n\n### الشروط المسبقة والحراسات الفورية\n\n- إذا كان الوكيل يبث حالياً، يُرفض `/fork` مع تحذير.\n- تُمسح مؤشرات الحالة/التحميل في واجهة المستخدم قبل العملية.\n\n### مسار مستوى الجلسة\n\n`AgentSession.fork()`:\n\n1. يُطلق `session_before_switch` مع `reason: \"fork\"` (قابل للإلغاء).\n2. يُفرّغ عمليات الكتابة المعلقة.\n3. يستدعي `SessionManager.fork()`.\n4. ينسخ مجلد المخرجات من مساحة اسم الجلسة القديمة إلى الجديدة (بأفضل جهد؛ أخطاء النسخ غير ENOENT تُسجَّل ولا تكون قاتلة).\n5. يُحدّث `agent.sessionId`.\n6. يُطلق `session_switch` مع `reason: \"fork\"`.\n\nسلوك `SessionManager.fork()`:\n\n- يتطلب وضع الاستمرارية وملف جلسة موجود.\n- ينشئ معرّف جلسة جديد ومسار ملف JSONL جديد.\n- يعيد كتابة الرأس مع:\n  - `id` جديد\n  - طابع زمني جديد\n  - `cwd` بدون تغيير\n  - `parentSession` يُعيَّن إلى معرّف الجلسة السابقة\n- يحتفظ بجميع الإدخالات غير الرأسية بدون تغيير في الملف الجديد.\n\n### السلوك غير المستمر\n\n- يُعيد مدير الجلسة في الذاكرة `undefined` من `fork()`.\n- يُعيد `AgentSession.fork()` القيمة `false`.\n- تُبلّغ واجهة المستخدم `Fork failed (session not persisted or cancelled)`.\n\n## الاستئناف والمتابعة\n\n## `/resume` التفاعلي\n\nالمسار:\n\n1. يفتح منتقي الجلسات المملوء عبر `SessionManager.list(currentCwd, currentSessionDir)`.\n2. عند الاختيار، يستدعي `SelectorController.handleResumeSession(sessionPath)` الدالة `session.switchSession(sessionPath)`.\n3. تمسح واجهة المستخدم وتعيد بناء المحادثة والمهام، ثم تُبلّغ `Resumed session`.\n\nملاحظات:\n\n- يسرد هذا المنتقي فقط الجلسات في نطاق مجلد الجلسة الحالي.\n- لا يستخدم البحث العالمي عبر المشاريع.\n\n## `--resume` عبر CLI\n\n### `--resume` (بدون قيمة)\n\n- يسرد `main.ts` الجلسات لمجلد العمل/مجلد الجلسات الحالي ويفتح المنتقي.\n- يُفتح المسار المحدد بـ `SessionManager.open(selectedPath)` قبل إنشاء الجلسة.\n\n### `--resume <value>`\n\nترتيب حل `createSessionManager()`:\n\n1. إذا بدت القيمة كمسار (`/` أو `\\` أو `.jsonl`)، يُفتح مباشرة.\n2. وإلا تُعامل كبادئة معرّف:\n   - البحث في النطاق الحالي (`SessionManager.list(cwd, sessionDir)`)\n   - إذا لم يُعثر عليه ولا يوجد `sessionDir` صريح، البحث العالمي (`SessionManager.listAll()`)\n\nسلوك تطابق المعرّف عبر المشاريع:\n\n- إذا اختلف مجلد العمل للجلسة المطابقة عن مجلد العمل الحالي، يسأل CLI:\n  - `Session found in different project ... Fork into current directory? [y/N]`\n- عند الموافقة: `SessionManager.forkFrom(match.path, cwd, sessionDir)` ينشئ ملف تفرع محلي جديد.\n- عند الرفض/الافتراضي لغير TTY: ينتهي الأمر بخطأ.\n\n## `--continue` عبر CLI\n\n`SessionManager.continueRecent(cwd, sessionDir)`:\n\n1. يحلّ مجلد الجلسة لمجلد العمل الحالي.\n2. يقرأ مسار تتبع الطرفية أولاً.\n3. يتراجع إلى أحدث ملف جلسة مُعدَّل.\n4. يفتح الجلسة المُعثر عليها؛ إذا لم توجد أي جلسة، ينشئ جلسة جديدة.\n\nهذا سلوك عند البدء فقط؛ لا يوجد أمر شرطة مائلة تفاعلي `/continue`.\n\n## كيف يُغيّر تبديل الجلسة حالة التشغيل فعلياً\n\n`AgentSession.switchSession(sessionPath)` يُنفّذ الانتقال أثناء التشغيل المُستخدم في عمليات شبيهة بالاستئناف:\n\n1. يُطلق `session_before_switch` مع `reason: \"resume\"` و `targetSessionFile` (قابل للإلغاء).\n2. يفصل اشتراك أحداث الوكيل ويُحبط العمل الجاري.\n3. يمسح رسائل التوجيه/المتابعة/الدور التالي المُصطفة.\n4. يُفرّغ كتابات مدير الجلسة الحالي.\n5. `sessionManager.setSessionFile(sessionPath)` ويُحدّث `agent.sessionId`.\n6. يبني سياق الجلسة من الإدخالات المحمّلة.\n7. يُطلق `session_switch` مع `reason: \"resume\"`.\n8. يستبدل رسائل الوكيل من السياق.\n9. يستعيد النموذج (إذا كان متاحاً في السجل الحالي).\n10. يستعيد أو يُهيئ مستوى التفكير.\n11. يعيد توصيل اشتراك أحداث الوكيل.\n\nلا يُنشئ `switchSession()` بحد ذاته أي ملف جلسة جديد.\n\n## إطلاق الأحداث ونقاط الإلغاء\n\n### خطافات دورة حياة التبديل/التفرع\n\nلـ `newSession` و `fork` و `switchSession`:\n\n- حدث ما قبل: `session_before_switch`\n  - الأسباب: `new`، `fork`، `resume`\n  - قابل للإلغاء بإعادة `{ cancel: true }`\n- حدث ما بعد: `session_switch`\n  - نفس مجموعة الأسباب\n  - يتضمن `previousSessionFile`\n\nيعود `ExtensionRunner.emit()` مبكراً عند أول نتيجة إلغاء لحدث ما قبل.\n\n### سلوك `onSession` للأدوات المخصصة\n\nيربط جسر SDK أحداث جلسة الإضافة بردود نداء `onSession` للأدوات المخصصة:\n\n- `session_switch` -> `onSession({ reason: \"switch\", previousSessionFile })`\n- `session_branch` -> `reason: \"branch\"`\n- `session_start` -> `reason: \"start\"`\n- `session_tree` -> `reason: \"tree\"`\n- `session_shutdown` -> `reason: \"shutdown\"`\n\nهذه الردود رصدية؛ لا تُلغي التبديل/التفرع.\n\n### أسطح إلغاء أخرى ذات صلة بهذا المستند\n\n- `/fork` يُحظر أثناء البث (يجب على المستخدم الانتظار/إحباط الاستجابة الحالية أولاً).\n- يمكن إلغاء منتقي `/resume` بإغلاق المستخدم للمنتقي.\n- يمكن إلغاء `--resume <id>` عبر المشاريع برفض موجه التفرع.\n- `/share` لديه مسار إحباط في واجهة المستخدم (`Share cancelled`) لمسار gist؛ لا يربط دلالات إنهاء العملية لـ `gh gist create` في مسار الشيفرة هذا.\n\n## سلوك الجلسة غير المستمرة (في الذاكرة)\n\nعندما يُنشأ مدير الجلسة بـ `SessionManager.inMemory()` (`--no-session`):\n\n- مسار ملف الجلسة غائب.\n- `/export` و `/share` يفشلان مع `Cannot export in-memory session to HTML` (يُنقل إلى واجهة خطأ الأمر).\n- `/fork` يفشل لأن `SessionManager.fork()` يتطلب الاستمرارية.\n- `/dump` لا يزال يعمل لأنه يُسلسل حالة الوكيل في الذاكرة.\n- تُتجاوز دلالات resume/continue عبر CLI إذا تم تعيين `--no-session`، لأن إنشاء المدير يُعيد في الذاكرة فوراً.\n\n## تحذيرات التنفيذ المعروفة (حسب الشيفرة الحالية)\n\n- لا يتحقق `SelectorController.handleResumeSession()` من النتيجة المنطقية لـ `session.switchSession(...)`؛ يمكن أن يستمر التبديل المُلغى بواسطة خطاف عبر مسار إعادة رسم/حالة واجهة المستخدم \"Resumed session\".\n- فشل المشاركة المخصصة في `/share` لا يتدهور إلى احتياط gist الافتراضي؛ بل ينهي الأمر بخطأ.\n- تحليل وسائط `/export` بسيط ولا يحافظ على المسارات المقتبسة التي تحتوي على مسافات.\n",
	"ar/sessions/session-switching-and-recent-listing.md": "---\ntitle: تبديل الجلسة وعرض الجلسات الأخيرة\ndescription: آليات تبديل الجلسة وعرض الجلسات الأخيرة مع البحث والتصفية.\nsidebar:\n  order: 4\n  label: التبديل والجلسات الأخيرة\ni18n:\n  sourceHash: aae56130b508\n  translator: machine\n---\n\n# تبديل الجلسة وعرض الجلسات الأخيرة\n\nيصف هذا المستند كيفية اكتشاف وكيل البرمجة للجلسات الأخيرة، وتحليل أهداف `--resume`، وعرض أدوات اختيار الجلسة، وتبديل جلسة التشغيل النشطة.\n\nيركز على سلوك التنفيذ الحالي، بما في ذلك مسارات الاحتياط والتحفظات.\n\n## ملفات التنفيذ\n\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/cli/session-picker.ts`](../../packages/coding-agent/src/cli/session-picker.ts)\n- [`../src/modes/components/session-selector.ts`](../../packages/coding-agent/src/modes/components/session-selector.ts)\n- [`../src/modes/controllers/selector-controller.ts`](../../packages/coding-agent/src/modes/controllers/selector-controller.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n\n## اكتشاف الجلسات الأخيرة\n\n### نطاق الدليل\n\nيخزّن `SessionManager` الجلسات ضمن دليل مُحدَّد بنطاق cwd افتراضيًا:\n\n- `~/.xcsh/agent/sessions/--<cwd-encoded>--/*.jsonl`\n\nتقرأ `SessionManager.list(cwd, sessionDir?)` ذلك الدليل فقط ما لم يُقدَّم `sessionDir` صريح.\n\n### مساران للعرض بحمولات مختلفة\n\nثمة مساران مختلفان لمعالجة القوائم:\n\n1. `getRecentSessions(sessionDir, limit)` (عرض الترحيب/الملخص)\n   - يقرأ بادئة 4KB فقط (`readTextPrefix(..., 4096)`) من كل ملف.\n   - يحلل الترويسة ومعاينة أول نص مستخدم.\n   - يُعيد `RecentSessionInfo` خفيف الوزن مع أدوات جلب `name` و`timeAgo` كسولة.\n   - يرتب بترتيب تنازلي حسب `mtime` للملف.\n\n2. `SessionManager.list(...)` / `SessionManager.listAll()` (أدوات الاستئناف واستيفاء المعرّف)\n   - يقرأ ملفات الجلسة الكاملة.\n   - يبني كائنات `SessionInfo` (`id`، `cwd`، `title`، `messageCount`، `firstMessage`، `allMessagesText`، الطوابع الزمنية).\n   - يُسقط الجلسات ذات صفر من إدخالات `message`.\n   - يرتب بترتيب تنازلي حسب `modified`.\n\n### سلوك الاحتياط للبيانات الوصفية\n\nللملخصات الأخيرة (`RecentSessionInfo`):\n\n- تفضيل اسم العرض: `header.title` -> أول موجه مستخدم -> `header.id` -> اسم الملف\n- يُقطع الاسم إلى 40 حرفًا للعروض المدمجة\n- تُزال أحرف التحكم/أسطر السطر الجديد وتُصحح من الأسماء المستمدة من العنوان\n\nلإدخالات قائمة `SessionInfo`:\n\n- `title` هو `header.title` أو آخر `shortSummary` للضغط\n- `firstMessage` هو نص أول رسالة مستخدم أو `\"(no messages)\"`\n\n## تحليل `--continue` وتفضيل بصمة الطرفية\n\nتحلّ `SessionManager.continueRecent(cwd, sessionDir?)` الهدف بهذا الترتيب:\n\n1. قراءة بصمة نطاق الطرفية (`~/.xcsh/agent/terminal-sessions/<terminal-id>`)\n2. التحقق من صحة البصمة:\n   - يمكن تحديد الطرفية الحالية\n   - يتطابق cwd البصمة مع cwd الحالية (مقارنة المسار المحلول)\n   - الملف المُشار إليه لا يزال موجودًا\n3. إذا كانت البصمة غير صالحة/مفقودة، الاحتياط إلى أحدث ملف بـ mtime في دليل الجلسة (`findMostRecentSession`)\n4. إذا لم يُوجد أي ملف، إنشاء جلسة جديدة\n\nيُفضّل اشتقاق معرّف الطرفية مسار TTY ويحتاط إلى معرّفات قائمة على البيئة (`KITTY_WINDOW_ID`، `TMUX_PANE`، `TERM_SESSION_ID`، `WT_SESSION`).\n\nكتابات البصمة تبذل أفضل جهد ولا تُسبب فشلًا مميتًا.\n\n## تحليل هدف الاستئناف عند بدء التشغيل (`main.ts`)\n\n### `--resume <value>`\n\nتتعامل `createSessionManager(...)` مع `--resume` ذي قيمة نصية في وضعين:\n\n1. قيمة شبيهة بالمسار (تحتوي على `/`، أو `\\\\`، أو تنتهي بـ `.jsonl`)\n   - `SessionManager.open(sessionArg, parsed.sessionDir)` مباشرةً\n\n2. قيمة بادئة المعرّف\n   - البحث عن تطابق في `SessionManager.list(cwd, sessionDir)` عبر `id.startsWith(sessionArg)`\n   - إذا لم يكن هناك تطابق محلي ولم يُفرض `sessionDir`، جرّب `SessionManager.listAll()`\n   - يُستخدم أول تطابق (لا يوجد موجه للغموض)\n\nسلوك التطابق عبر المشاريع:\n\n- إذا اختلف cwd الجلسة المُطابقة عن cwd الحالية، يسأل CLI إذا كان المستخدم يريد التفريع إلى المشروع الحالي\n- نعم -> `SessionManager.forkFrom(...)`\n- لا -> يُطلق خطأ (`Session \"...\" is in another project (...)`)\n\nلا تطابق -> يُطلق خطأ (`Session \"...\" not found.`).\n\n### `--resume` (بدون قيمة)\n\nيُعالج بعد إنشاء مدير الجلسة الأولي:\n\n1. عرض الجلسات المحلية باستخدام `SessionManager.list(cwd, parsed.sessionDir)`\n2. إذا كانت فارغة: طباعة `No sessions found` والخروج مبكرًا\n3. فتح أداة الاختيار TUI (`selectSession`)\n4. إذا أُلغي: طباعة `No session selected` والخروج مبكرًا\n5. إذا اختير: `SessionManager.open(selectedPath)`\n\n### `--continue`\n\nيستخدم `SessionManager.continueRecent(...)` مباشرةً (السلوك المعتمد على البصمة أعلاه).\n\n## آليات الاختيار القائمة على أداة الاختيار\n\n## أداة اختيار CLI (`src/cli/session-picker.ts`)\n\nتُنشئ `selectSession(sessions)` واجهة TUI مستقلة مع `SessionSelectorComponent` وتحل مرة واحدة بالضبط:\n\n- الاختيار -> يحل المسار المختار\n- الإلغاء (Esc) -> يحل `null`\n- الخروج القسري (مسار Ctrl+C) -> يوقف TUI ويستدعي `process.exit(0)`\n\n## أداة الاختيار التفاعلية داخل الجلسة (`SelectorController.showSessionSelector`)\n\nالتدفق:\n\n1. جلب الجلسات من دليل الجلسة الحالي عبر `SessionManager.list(currentCwd, currentSessionDir)`\n2. تركيب `SessionSelectorComponent` في منطقة المحرر باستخدام `showSelector(...)`\n3. ردود النداء:\n   - الاختيار -> إغلاق أداة الاختيار واستدعاء `handleResumeSession(sessionPath)`\n   - الإلغاء -> استعادة المحرر وإعادة العرض\n   - الخروج -> `ctx.shutdown()`\n\n## سلوك مكوّن اختيار الجلسة\n\nتدعم `SessionList`:\n\n- التنقل بالأسهم/الصفحات\n- Enter للاختيار\n- Esc للإلغاء\n- Ctrl+C للخروج\n- البحث الضبابي عبر معرّف الجلسة/العنوان/cwd/أول رسالة/جميع الرسائل/المسار\n\nسلوك عرض القائمة الفارغة:\n\n- يعرض رسالة بدلًا من الانهيار\n- Enter على القائمة الفارغة لا يفعل شيئًا (لا رد نداء)\n- Esc/Ctrl+C لا يزالان يعملان\n\nتحفظ: نص واجهة المستخدم يقول `Press Tab to view all`، لكن هذا المكوّن لا يحتوي حاليًا على معالج Tab والتوصيل الحالي يسرد جلسات النطاق الحالي فحسب.\n\n## تنفيذ التبديل في وقت التشغيل (`AgentSession.switchSession`)\n\n`switchSession(sessionPath)` هو مسار التبديل الأساسي داخل العملية.\n\nدورة الحياة/انتقال الحالة:\n\n1. التقاط `previousSessionFile`\n2. إطلاق حدث هوك `session_before_switch` (`reason: \"resume\"`، قابل للإلغاء)\n3. إذا أُلغي -> إعادة `false` بدون تبديل\n4. قطع الاتصال عن تدفق أحداث الوكيل الحالي\n5. إلغاء تدفق التوليد/الأداة النشط\n6. مسح المخازن المؤقتة لرسائل التوجيه/المتابعة/الدور التالي المُنتظرة\n7. مسح كاتب الجلسة (`sessionManager.flush()`) لإبقاء الكتابات المعلقة\n8. `sessionManager.setSessionFile(sessionPath)`\n   - يحدث مؤشر ملف الجلسة\n   - يكتب بصمة الطرفية\n   - يُحمّل الإدخالات / يُهاجر / يحل الكتلة / يُعيد الفهرسة\n   - إذا كانت بيانات الملف مفقودة/غير صالحة: يُهيئ جلسة جديدة في ذلك المسار ويُعيد كتابة الترويسة\n9. تحديث `agent.sessionId`\n10. إعادة بناء السياق عبر `buildSessionContext()`\n11. إطلاق حدث هوك `session_switch` (`reason: \"resume\"`، `previousSessionFile`)\n12. استبدال رسائل الوكيل بالسياق المُعاد بناؤه\n13. استعادة النموذج الافتراضي من `sessionContext.models.default` إذا كان متاحًا وموجودًا في سجل النماذج\n14. استعادة مستوى التفكير:\n    - إذا كان الفرع يحتوي بالفعل على `thinking_level_change`، تطبيق مستوى الجلسة المحفوظ\n    - وإلا اشتقاق مستوى التفكير الافتراضي من الإعدادات، تقليصه لقدرة النموذج، تعيينه، وإضافة إدخال `thinking_level_change` جديد\n15. إعادة توصيل مستمعي الوكيل وإعادة `true`\n\n## إعادة بناء حالة واجهة المستخدم بعد التبديل التفاعلي\n\nتُجري `SelectorController.handleResumeSession` إعادة تعيين واجهة المستخدم حول `switchSession`:\n\n- إيقاف رسوم التحميل المتحركة\n- مسح حاوية الحالة\n- مسح واجهة الرسائل المعلقة وخريطة الأدوات المعلقة\n- إعادة تعيين مراجع مكوّن/رسالة البث\n- استدعاء `session.switchSession(...)`\n- مسح حاوية الدردشة وإعادة العرض من سياق الجلسة (`renderInitialMessages`)\n- إعادة تحميل المهام من مخرجات الجلسة الجديدة\n- عرض `Resumed session`\n\nوهكذا يُعاد بناء حالة المحادثة/المهام المرئية من ملف الجلسة الجديد.\n\n## الاستئناف عند بدء التشغيل مقابل التبديل داخل الجلسة\n\n### الاستئناف عند بدء التشغيل (`--continue`، `--resume`، الفتح المباشر)\n\n- يُختار ملف الجلسة قبل `createAgentSession(...)`.\n- تبني `sdk.ts` الكائن `existingSession = sessionManager.buildSessionContext()`.\n- تُستعاد رسائل الوكيل مرة واحدة أثناء إنشاء الجلسة.\n- يُختار النموذج/التفكير أثناء الإنشاء (بما في ذلك منطق الاستعادة/الاحتياط).\n- ثم يُشغّل الوضع التفاعلي `#restoreModeFromSession()` لإعادة الدخول إلى حالة الوضع المستمرة (plan/plan_paused حاليًا).\n\n### التبديل داخل الجلسة (مسار أداة اختيار `/resume`)\n\n- يستخدم `AgentSession.switchSession(...)` على `AgentSession` قيد التشغيل بالفعل.\n- تُعاد بناء الرسائل/النموذج/التفكير فورًا في مكانها.\n- تُطلق أحداث هوك `session_before_switch`/`session_switch`.\n- تُحدَّث دردشة واجهة المستخدم/المهام.\n- لا يُجرى استدعاء مخصص لاستعادة الوضع بعد التبديل في تدفق أداة الاختيار؛ سلوك إعادة الدخول إلى الوضع ليس متماثلًا مع `#restoreModeFromSession()` عند بدء التشغيل.\n\n## سلوك الفشل وحالات الحافة\n\n### مسارات الإلغاء\n\n- إلغاء أداة اختيار CLI -> يُعيد `null`، يطبع المُستدعي `No session selected`، تخرج العملية مبكرًا.\n- إلغاء أداة الاختيار التفاعلية -> يُستعاد المحرر، لا تغيير في الجلسة.\n- إلغاء الهوك (`session_before_switch`) -> تُعيد `switchSession()` القيمة `false`.\n\n### مسارات القائمة الفارغة\n\n- CLI `--resume` (بدون قيمة): تطبع القائمة الفارغة `No sessions found` وتخرج.\n- أداة الاختيار التفاعلية: تعرض القائمة الفارغة رسالة وتبقى قابلة للإلغاء.\n\n### ملف الجلسة الهدف مفقود/غير صالح\n\nعند الفتح/التبديل إلى مسار محدد (`setSessionFile`):\n\n- ENOENT -> يُعامل كفارغ -> تُهيأ جلسة جديدة في ذلك المسار بالضبط وتُستمر.\n- ترويسة مشوهة/غير صالحة (أو إدخالات محللة غير قابلة للقراءة فعليًا) -> تُعامل كفارغة -> تُهيأ جلسة جديدة وتُستمر.\n\nهذا سلوك استرداد، لا فشل صارم.\n\n### الفشل الصارم\n\nيمكن أن يُطلق التبديل/الفتح استثناءات عند أخطاء I/O الحقيقية (أخطاء الأذونات، أخطاء إعادة الكتابة، إلخ)، والتي تنتشر إلى المُستدعين.\n\n### تحفظات استيفاء بادئة المعرّف\n\n- يستخدم استيفاء المعرّف `startsWith` ويأخذ أول تطابق في القائمة المرتبة.\n- لا توجد واجهة للغموض إذا شاركت جلسات متعددة نفس البادئة.\n- تستبعد `SessionManager.list(...)` الجلسات ذات الصفر من الرسائل، لذا لا يمكن استئناف تلك الجلسات عبر استيفاء المعرّف/أداة اختيار القائمة.\n",
	"ar/sessions/session-tree-plan.md": "---\ntitle: بنية شجرة الجلسة\ndescription: بنية شجرة الجلسة مع التفريع والتنقل وعلاقات المحادثة بين الأصل والفرع.\nsidebar:\n  order: 2\n  label: بنية الشجرة\ni18n:\n  sourceHash: bd8b78d6c33a\n  translator: machine\n---\n\n# بنية شجرة الجلسة (الحالية)\n\nمرجع: [session.md](./session.md)\n\nيصف هذا المستند كيفية عمل تنقل شجرة الجلسة اليوم: نموذج الشجرة في الذاكرة، وقواعد حركة العقدة الطرفية، وسلوك التفريع، وتكامل الإضافات والأحداث.\n\n## ما هذا النظام الفرعي\n\nيُخزَّن الجلسة كسجل إدخالات يُلحَق فقط، غير أن السلوك أثناء التشغيل يعتمد على الشجرة:\n\n- كل إدخال غير ترويسي له `id` و`parentId`.\n- الموضع النشط هو `leafId` في `SessionManager`.\n- يؤدي إلحاق إدخال دائمًا إلى إنشاء فرع من العقدة الطرفية الحالية.\n- التفريع **لا** يُعيد كتابة السجل التاريخي؛ بل يغير فقط المكان الذي تشير إليه العقدة الطرفية قبل الإلحاق التالي.\n\nالملفات الرئيسية:\n\n- `src/session/session-manager.ts` — نموذج بيانات الشجرة، والاجتياز، وحركة العقدة الطرفية، واستخراج الفرع/الجلسة\n- `src/session/agent-session.ts` — تدفق تنقل `/tree`، والتلخيص، وإرسال الخطافات/الأحداث\n- `src/modes/components/tree-selector.ts` — سلوك واجهة الشجرة التفاعلية والتصفية\n- `src/modes/controllers/selector-controller.ts` — تنسيق أداة الاختيار لـ `/tree` و`/branch`\n- `src/modes/controllers/input-controller.ts` — توجيه الأوامر (`/tree`، و`/branch`، وسلوك الضغط المزدوج على Escape)\n- `src/session/messages.ts` — تحويل إدخالات `branch_summary` و`compaction` و`custom_message` إلى رسائل سياق LLM\n\n## نموذج بيانات الشجرة في `SessionManager`\n\nالفهارس أثناء التشغيل:\n\n- `#byId: Map<string, SessionEntry>` — بحث سريع عن أي إدخال\n- `#leafId: string | null` — الموضع الحالي في الشجرة\n- `#labelsById: Map<string, string>` — التسميات المحلولة حسب معرّف الإدخال المستهدف\n\nواجهات برمجة شجرة الشجرة:\n\n- `getBranch(fromId?)` يجتاز روابط الأصل حتى الجذر ويعيد مسار الجذر→العقدة\n- `getTree()` يعيد `SessionTreeNode[]` (`entry`، و`children`، و`label`)\n  - تصبح روابط الأصل مصفوفات أبناء\n  - تُعامَل الإدخالات التي تفتقر إلى أصول كجذور\n  - يُرتَّب الأبناء من الأقدم إلى الأحدث حسب الطابع الزمني\n- `getChildren(parentId)` يعيد الأبناء المباشرين\n- `getLabel(id)` يُحلّ التسمية الحالية من `labelsById`\n\n`getTree()` هي إسقاط أثناء التشغيل؛ يبقى الاستمرار عبر إدخالات JSONL المُلحَقة فقط.\n\n## دلالات حركة العقدة الطرفية\n\nثمة ثلاثة أساسيات لحركة العقدة الطرفية:\n\n1. `branch(entryId)`\n   - يتحقق من وجود الإدخال\n   - يضبط `leafId = entryId`\n   - لا يُكتَب أي إدخال جديد\n\n2. `resetLeaf()`\n   - يضبط `leafId = null`\n   - الإلحاق التالي ينشئ إدخال جذر جديد (`parentId = null`)\n\n3. `branchWithSummary(branchFromId, summary, details?, fromExtension?)`\n   - يقبل `branchFromId: string | null`\n   - يضبط `leafId = branchFromId`\n   - يُلحق إدخال `branch_summary` كفرع من تلك العقدة الطرفية\n   - عندما يكون `branchFromId` هو `null`، يُخزَّن `fromId` بصيغة `\"root\"`\n\n## سلوك تنقل `/tree` (ملف الجلسة ذاته)\n\n`AgentSession.navigateTree()` هي تنقل، وليست تفريعًا لملف.\n\nالتدفق:\n\n1. التحقق من الهدف وحساب المسار المتروك (`collectEntriesForBranchSummary`)\n2. إرسال `session_before_tree` مع `TreePreparation`\n3. تلخيص الإدخالات المتروكة اختياريًا (ملخص مقدَّم من الخطاف أو ملخص مدمج)\n4. حساب هدف العقدة الطرفية الجديدة:\n   - اختيار رسالة **مستخدم**: تنتقل العقدة الطرفية إلى أصلها، وتُعاد نص الرسالة لتعبئة المحرر مسبقًا\n   - اختيار **custom_message**: نفس قاعدة رسالة المستخدم (العقدة الطرفية = الأصل، والنص يُعبّئ المحرر مسبقًا)\n   - اختيار أي إدخال آخر: العقدة الطرفية = معرف الإدخال المختار\n5. تطبيق حركة العقدة الطرفية:\n   - مع ملخص: `branchWithSummary(newLeafId, ...)`\n   - بدون ملخص وكان `newLeafId === null`: `resetLeaf()`\n   - وإلا: `branch(newLeafId)`\n6. إعادة بناء سياق الوكيل من العقدة الطرفية الجديدة وإرسال `session_tree`\n\nمهم: تُربط إدخالات الملخص بـ **موضع التنقل الجديد**، لا بنهاية الفرع المتروك.\n\n## سلوك `/branch` (ملف جلسة جديد)\n\n`/branch` و`/tree` مختلفان عمدًا:\n\n- `/tree` ينقل داخل ملف الجلسة الحالي.\n- `/branch` ينشئ ملف فرع جلسة جديدًا (أو استبدالًا في الذاكرة لوضع عدم الاستمرار).\n\nتدفق `/branch` الموجه للمستخدم (`SelectorController.showUserMessageSelector` ← `AgentSession.branch`):\n\n- يجب أن يكون مصدر الفرع **رسالة مستخدم**.\n- يُستخرج نص المستخدم المختار لتعبئة المحرر مسبقًا.\n- إذا كانت رسالة المستخدم المختارة جذرًا (`parentId === null`): يبدأ جلسة جديدة عبر `newSession({ parentSession: previousSessionFile })`.\n- وإلا: `createBranchedSession(selectedEntry.parentId)` لتفريع السجل حتى حد المطالبة المختارة.\n\nتفاصيل `SessionManager.createBranchedSession(leafId)`:\n\n- يبني مسار الجذر→العقدة الطرفية عبر `getBranch(leafId)`؛ يُلقي خطأ إذا كان مفقودًا.\n- يستبعد إدخالات `label` الموجودة من المسار المنسوخ.\n- يُعيد بناء إدخالات تسمية جديدة من `labelsById` المحلولة للإدخالات التي تبقى في المسار.\n- الوضع المستمر: يكتب ملف JSONL جديدًا ويُبدّل المدير إليه؛ يعيد مسار الملف الجديد.\n- الوضع في الذاكرة: يستبدل الإدخالات في الذاكرة؛ يعيد `undefined`.\n\n## إعادة بناء السياق وتكامل الملخص/المخصص\n\n`buildSessionContext()` (في `session-manager.ts`) يُحلّ مسار الجذر→العقدة الطرفية النشطة ويبني حالة سياق LLM الفعلية:\n\n- يتتبع أحدث حالة تفكير/نموذج/وضع/ttsr على المسار.\n- يتعامل مع أحدث ضغط على المسار:\n  - يُرسل ملخص الضغط أولًا\n  - يُعيد تشغيل الرسائل المحتفظ بها من `firstKeptEntryId` حتى نقطة الضغط\n  - ثم يُعيد تشغيل الرسائل بعد الضغط\n- يشمل إدخالات `branch_summary` و`custom_message` كأجسام `AgentMessage`.\n\nثم يُعيّن `session/messages.ts` هذه الأنواع من الرسائل لمدخلات النموذج:\n\n- يصبح `branchSummary` و`compactionSummary` رسائل سياق ذات قالب بدور المستخدم\n- يصبح `custom`/`hookMessage` رسائل محتوى بدور المستخدم\n\nوبالتالي تغيّر حركة الشجرة السياق بتغيير مسار العقدة الطرفية النشطة، لا بتعديل الإدخالات القديمة.\n\n## التسميات وسلوك واجهة الشجرة\n\nاستمرار التسميات:\n\n- `appendLabelChange(targetId, label?)` يكتب إدخالات `label` على سلسلة العقدة الطرفية الحالية.\n- يُحدَّث `labelsById` فورًا (ضبط أو حذف).\n- يُحلّ `getTree()` التسمية الحالية على كل عقدة مُعادة.\n\nسلوك أداة اختيار الشجرة (`tree-selector.ts`):\n\n- يُسطّح الشجرة للتنقل، ويحتفظ بإبراز المسار النشط، ويُولي الأولوية لعرض الفرع النشط أولًا.\n- يدعم أوضاع التصفية: `default`، و`no-tools`، و`user-only`، و`labeled-only`، و`all`.\n- يدعم البحث بالنص الحر في المحتوى الدلالي المُقيَّم.\n- يفتح `Shift+L` تحرير التسمية المضمّن ويكتب عبر `appendLabelChange`.\n\nتوجيه الأوامر:\n\n- `/tree` يفتح دائمًا أداة اختيار الشجرة.\n- `/branch` يفتح أداة اختيار رسالة المستخدم إلا إذا كان `doubleEscapeAction=tree`، وفي هذه الحالة يستخدم أيضًا واجهة أداة اختيار الشجرة.\n\n## نقاط تواصل الإضافات والخطافات لعمليات الشجرة\n\nواجهة برمجة الإضافات وقت الأمر (`ExtensionCommandContext`):\n\n- `branch(entryId)` — إنشاء ملف جلسة متفرّع\n- `navigateTree(targetId, { summarize? })` — التنقل داخل الشجرة/الملف الحالي\n\nالأحداث حول تنقل الشجرة:\n\n- `session_before_tree`\n  - يستقبل `TreePreparation`:\n    - `targetId`\n    - `oldLeafId`\n    - `commonAncestorId`\n    - `entriesToSummarize`\n    - `userWantsSummary`\n  - قد يُلغي التنقل\n  - قد يُقدّم حمولة ملخص تُستخدم بدلًا من الملخص المدمج\n  - يستقبل إشارة إلغاء `signal` (مسار الإلغاء بـ Escape)\n- `session_tree`\n  - يُرسل `newLeafId`، و`oldLeafId`\n  - يتضمن `summaryEntry` عند إنشاء ملخص\n  - `fromExtension` يُشير إلى مصدر الملخص\n\nخطافات دورة الحياة المجاورة ذات الصلة:\n\n- `session_before_branch` / `session_branch` لتدفق `/branch`\n- `session_before_compact`، و`session.compacting`، و`session_compact` لإدخالات الضغط التي تؤثر لاحقًا على إعادة بناء سياق الشجرة\n\n## القيود الحقيقية وحالات الحواف\n\n- لا يمكن لـ `branch()` استهداف `null`؛ استخدم `resetLeaf()` لحالة الجذر قبل الإدخال الأول.\n- تدعم `branchWithSummary()` الهدف `null` وتُسجّل `fromId: \"root\"`.\n- اختيار العقدة الطرفية الحالية في أداة اختيار الشجرة لا يُحدث أي تأثير.\n- يتطلب التلخيص نموذجًا نشطًا؛ في حال غيابه، يفشل التنقل مع التلخيص فورًا.\n- إذا أُلغي التلخيص، يُلغى التنقل وتبقى العقدة الطرفية دون تغيير.\n- لا تُعيد الجلسات في الذاكرة أبدًا مسار ملف فرع من `createBranchedSession`.\n\n## التوافق مع الإصدارات القديمة المازال موجودًا\n\nلا تزال ترحيلات الجلسة تعمل عند التحميل:\n\n- v1→v2 يضيف `id`/`parentId` ويحوّل مرساة فهرس الضغط القديمة إلى مرساة معرّف\n- v2→v3 يُرحّل دور `hookMessage` القديم إلى `custom`\n\nالسلوك الحالي أثناء التشغيل هو دلالات شجرة الإصدار الثالث بعد الترحيل.\n",
	"ar/sessions/session.md": "---\ntitle: تخزين الجلسة ونموذج الإدخالات\ndescription: >-\n  Append-only session storage model with entry types, persistence, and migration\n  between formats.\nsidebar:\n  order: 1\n  label: التخزين ونموذج الإدخالات\ni18n:\n  sourceHash: 42fe17549e00\n  translator: machine\n---\n\n# تخزين الجلسة ونموذج الإدخالات\n\nهذا المستند هو المرجع الأساسي لكيفية تمثيل جلسات وكيل البرمجة، وحفظها، وترحيلها، وإعادة بنائها أثناء التشغيل.\n\n## النطاق\n\nيغطي:\n\n- تنسيق JSONL للجلسة وإدارة الإصدارات\n- تصنيف الإدخالات ودلالات الشجرة (`id`/`parentId` + مؤشر الورقة)\n- سلوك الترحيل/التوافق عند تحميل ملفات قديمة أو تالفة\n- إعادة بناء السياق (`buildSessionContext`)\n- ضمانات الحفظ، سلوك الفشل، الاقتطاع/تخزين البيانات الكبيرة خارجياً\n- تجريدات التخزين (`FileSessionStorage`، `MemorySessionStorage`) والأدوات المساعدة ذات الصلة\n\nلا يغطي سلوك عرض واجهة `/tree` بخلاف الدلالات التي تؤثر على بيانات الجلسة.\n\n## ملفات التنفيذ\n\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`src/session/messages.ts`](../../packages/coding-agent/src/session/messages.ts)\n- [`src/session/session-storage.ts`](../../packages/coding-agent/src/session/session-storage.ts)\n- [`src/session/history-storage.ts`](../../packages/coding-agent/src/session/history-storage.ts)\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts)\n\n## التخطيط على القرص\n\nالموقع الافتراضي لملف الجلسة:\n\n```text\n~/.xcsh/agent/sessions/--<cwd-encoded>--/<timestamp>_<sessionId>.jsonl\n```\n\nيُشتق `<cwd-encoded>` من دليل العمل عن طريق إزالة الشرطة المائلة الأولى واستبدال `/` و`\\\\` و`:` بـ `-`.\n\nموقع مخزن البيانات الكبيرة:\n\n```text\n~/.xcsh/agent/blobs/<sha256>\n```\n\nتُكتب ملفات مسار التنقل للطرفية تحت:\n\n```text\n~/.xcsh/agent/terminal-sessions/<terminal-id>\n```\n\nيتكون محتوى مسار التنقل من سطرين: دليل العمل الأصلي، ثم مسار ملف الجلسة. تفضل `continueRecent()` هذا المؤشر المحدد بنطاق الطرفية قبل البحث عن أحدث وقت تعديل.\n\n## تنسيق الملف\n\nملفات الجلسة بتنسيق JSONL: كائن JSON واحد لكل سطر.\n\n- السطر الأول هو دائماً رأس الجلسة (`type: \"session\"`).\n- الأسطر المتبقية هي قيم `SessionEntry`.\n- الإدخالات تعمل بنمط الإلحاق فقط أثناء التشغيل؛ التنقل بين الفروع يحرك مؤشراً (`leafId`) بدلاً من تعديل الإدخالات الموجودة.\n\n### الرأس (`SessionHeader`)\n\n```json\n{\n  \"type\": \"session\",\n  \"version\": 3,\n  \"id\": \"1f9d2a6b9c0d1234\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\",\n  \"cwd\": \"/work/pi\",\n  \"title\": \"optional session title\",\n  \"parentSession\": \"optional lineage marker\"\n}\n```\n\nملاحظات:\n\n- `version` اختياري في ملفات v1؛ غيابه يعني v1.\n- `parentSession` هو سلسلة نصية معتمة للتسلسل النسبي. الكود الحالي يكتب إما معرف جلسة أو مسار جلسة حسب التدفق (`fork`، `forkFrom`، `createBranchedSession`، أو `newSession({ parentSession })` الصريح). تعامل معه كبيانات وصفية، وليس مفتاحاً أجنبياً مُحدد النوع.\n\n### قاعدة الإدخال (`SessionEntryBase`)\n\nجميع الإدخالات غير الرأسية تتضمن:\n\n```json\n{\n  \"type\": \"...\",\n  \"id\": \"8-char-id\",\n  \"parentId\": \"previous-or-branch-parent\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\"\n}\n```\n\nيمكن أن يكون `parentId` بقيمة `null` للإدخال الجذري (أول إلحاق، أو بعد `resetLeaf()`).\n\n## تصنيف الإدخالات\n\n`SessionEntry` هو اتحاد من:\n\n- `message`\n- `thinking_level_change`\n- `model_change`\n- `compaction`\n- `branch_summary`\n- `custom`\n- `custom_message`\n- `label`\n- `ttsr_injection`\n- `session_init`\n- `mode_change`\n\n### `message`\n\nيخزن `AgentMessage` مباشرة.\n\n```json\n{\n  \"type\": \"message\",\n  \"id\": \"a1b2c3d4\",\n  \"parentId\": null,\n  \"timestamp\": \"2026-02-16T10:21:00.000Z\",\n  \"message\": {\n    \"role\": \"assistant\",\n    \"provider\": \"anthropic\",\n    \"model\": \"claude-sonnet-4-5\",\n    \"content\": [{ \"type\": \"text\", \"text\": \"Done.\" }],\n    \"usage\": { \"input\": 100, \"output\": 20, \"cacheRead\": 0, \"cacheWrite\": 0, \"cost\": { \"input\": 0, \"output\": 0, \"cacheRead\": 0, \"cacheWrite\": 0, \"total\": 0 } },\n    \"timestamp\": 1760000000000\n  }\n}\n```\n\n### `model_change`\n\n```json\n{\n  \"type\": \"model_change\",\n  \"id\": \"b1c2d3e4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:21:30.000Z\",\n  \"model\": \"openai/gpt-4o\",\n  \"role\": \"default\"\n}\n```\n\n`role` اختياري؛ غيابه يُعامل كـ `default` في إعادة بناء السياق.\n\n### `thinking_level_change`\n\n```json\n{\n  \"type\": \"thinking_level_change\",\n  \"id\": \"c1d2e3f4\",\n  \"parentId\": \"b1c2d3e4\",\n  \"timestamp\": \"2026-02-16T10:22:00.000Z\",\n  \"thinkingLevel\": \"high\"\n}\n```\n\n### `compaction`\n\n```json\n{\n  \"type\": \"compaction\",\n  \"id\": \"d1e2f3a4\",\n  \"parentId\": \"c1d2e3f4\",\n  \"timestamp\": \"2026-02-16T10:23:00.000Z\",\n  \"summary\": \"Conversation summary\",\n  \"shortSummary\": \"Short recap\",\n  \"firstKeptEntryId\": \"a1b2c3d4\",\n  \"tokensBefore\": 42000,\n  \"details\": { \"readFiles\": [\"src/a.ts\"] },\n  \"preserveData\": { \"hookState\": true },\n  \"fromExtension\": false\n}\n```\n\n### `branch_summary`\n\n```json\n{\n  \"type\": \"branch_summary\",\n  \"id\": \"e1f2a3b4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:24:00.000Z\",\n  \"fromId\": \"a1b2c3d4\",\n  \"summary\": \"Summary of abandoned path\",\n  \"details\": { \"note\": \"optional\" },\n  \"fromExtension\": true\n}\n```\n\nإذا كان التفرع من الجذر (`branchFromId === null`)، فإن `fromId` تكون السلسلة الحرفية `\"root\"`.\n\n### `custom`\n\nحفظ حالة الإضافات؛ يتم تجاهله بواسطة `buildSessionContext`.\n\n```json\n{\n  \"type\": \"custom\",\n  \"id\": \"f1a2b3c4\",\n  \"parentId\": \"e1f2a3b4\",\n  \"timestamp\": \"2026-02-16T10:25:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"data\": { \"state\": 1 }\n}\n```\n\n### `custom_message`\n\nرسالة مقدمة من الإضافة تشارك في سياق نموذج اللغة الكبير.\n\n```json\n{\n  \"type\": \"custom_message\",\n  \"id\": \"a2b3c4d5\",\n  \"parentId\": \"f1a2b3c4\",\n  \"timestamp\": \"2026-02-16T10:26:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"content\": \"Injected context\",\n  \"display\": true,\n  \"details\": { \"debug\": false }\n}\n```\n\n### `label`\n\n```json\n{\n  \"type\": \"label\",\n  \"id\": \"b2c3d4e5\",\n  \"parentId\": \"a2b3c4d5\",\n  \"timestamp\": \"2026-02-16T10:27:00.000Z\",\n  \"targetId\": \"a1b2c3d4\",\n  \"label\": \"checkpoint\"\n}\n```\n\n`label: undefined` يمسح التسمية لـ `targetId`.\n\n### `ttsr_injection`\n\n```json\n{\n  \"type\": \"ttsr_injection\",\n  \"id\": \"c2d3e4f5\",\n  \"parentId\": \"b2c3d4e5\",\n  \"timestamp\": \"2026-02-16T10:28:00.000Z\",\n  \"injectedRules\": [\"ruleA\", \"ruleB\"]\n}\n```\n\n### `session_init`\n\n```json\n{\n  \"type\": \"session_init\",\n  \"id\": \"d2e3f4a5\",\n  \"parentId\": \"c2d3e4f5\",\n  \"timestamp\": \"2026-02-16T10:29:00.000Z\",\n  \"systemPrompt\": \"...\",\n  \"task\": \"...\",\n  \"tools\": [\"read\", \"edit\"],\n  \"outputSchema\": { \"type\": \"object\" }\n}\n```\n\n### `mode_change`\n\n```json\n{\n  \"type\": \"mode_change\",\n  \"id\": \"e2f3a4b5\",\n  \"parentId\": \"d2e3f4a5\",\n  \"timestamp\": \"2026-02-16T10:30:00.000Z\",\n  \"mode\": \"plan\",\n  \"data\": { \"planFile\": \"/tmp/plan.md\" }\n}\n```\n\n## إدارة الإصدارات والترحيل\n\nإصدار الجلسة الحالي: `3`.\n\n### v1 -> v2\n\nيُطبق عندما يكون `version` في الرأس مفقوداً أو `< 2`:\n\n- يضيف `id` و`parentId` لكل إدخال غير رأسي.\n- يعيد بناء سلسلة أصل خطية باستخدام ترتيب الملف.\n- يرحل حقل الضغط `firstKeptEntryIndex` -> `firstKeptEntryId` عند وجوده.\n- يضبط `version = 2` في الرأس.\n\n### v2 -> v3\n\nيُطبق عندما يكون `version < 3` في الرأس:\n\n- لإدخالات `message`: يعيد كتابة `message.role === \"hookMessage\"` القديم إلى `\"custom\"`.\n- يضبط `version = 3` في الرأس.\n\n### مُحفز الترحيل والحفظ\n\n- تعمل عمليات الترحيل أثناء تحميل الجلسة (`setSessionFile`).\n- إذا تم تشغيل أي ترحيل، تتم إعادة كتابة الملف بالكامل على القرص فوراً.\n- يقوم الترحيل بتعديل الإدخالات في الذاكرة أولاً، ثم يحفظ JSONL المعاد كتابته.\n\n## سلوك التحميل والتوافق\n\nسلوك `loadEntriesFromFile(path)`:\n\n- ملف مفقود (`ENOENT`) -> يعيد `[]`.\n- الأسطر غير القابلة للتحليل يتم معالجتها بواسطة محلل JSONL المتساهل (`parseJsonlLenient`).\n- إذا لم يكن أول إدخال محلل رأس جلسة صالح (`type !== \"session\"` أو `id` نصي مفقود) -> يعيد `[]`.\n\nسلوك `SessionManager.setSessionFile()`:\n\n- `[]` من المحمل تُعامل كجلسة فارغة/غير موجودة ويتم استبدالها بملف جلسة مُهيأ جديد في ذلك المسار.\n- الملفات الصالحة تُحمل، وتُرحل إذا لزم الأمر، وتُحل مراجع البيانات الكبيرة، ثم تُفهرس.\n\n## دلالات الشجرة والورقة\n\nالنموذج الأساسي هو شجرة إلحاق فقط + مؤشر ورقة قابل للتغيير:\n\n- كل طريقة إلحاق تنشئ إدخالاً جديداً واحداً بالضبط يكون `parentId` الخاص به هو `leafId` الحالي.\n- يصبح الإدخال الجديد هو `leafId` الجديد.\n- `branch(entryId)` يحرك `leafId` فقط؛ الإدخالات الموجودة تبقى دون تغيير.\n- `resetLeaf()` يضبط `leafId = null`؛ الإلحاق التالي ينشئ إدخالاً جذرياً جديداً (`parentId: null`).\n- `branchWithSummary()` يضبط الورقة على هدف التفرع ويلحق إدخال `branch_summary`.\n\n`getEntries()` يعيد جميع الإدخالات غير الرأسية بترتيب الإدراج. لا تُحذف الإدخالات الموجودة في التشغيل العادي؛ إعادة الكتابة تحافظ على التاريخ المنطقي مع تحديث التمثيل (الترحيلات، النقل، مساعدات إعادة الكتابة المستهدفة).\n\n## إعادة بناء السياق (`buildSessionContext`)\n\n`buildSessionContext(entries, leafId, byId?)` يحدد ما يُرسل إلى النموذج.\n\nالخوارزمية:\n\n1. تحديد الورقة:\n   - `leafId === null` -> يعيد سياقاً فارغاً.\n   - `leafId` صريح -> يستخدم ذلك الإدخال إن وُجد.\n   - وإلا يرجع للإدخال الأخير.\n2. التنقل في سلسلة `parentId` من الورقة إلى الجذر ثم عكسها إلى مسار من الجذر إلى الورقة.\n3. اشتقاق حالة التشغيل عبر المسار:\n   - `thinkingLevel` من أحدث `thinking_level_change` (الافتراضي `\"off\"`)\n   - خريطة النماذج من إدخالات `model_change` (`role ?? \"default\"`)\n   - `models.default` الاحتياطي من مزود/نموذج رسالة المساعد إذا لم يوجد تغيير نموذج صريح\n   - `injectedTtsrRules` مكررة الإزالة من جميع إدخالات `ttsr_injection`\n   - الوضع/بيانات الوضع من أحدث `mode_change` (الوضع الافتراضي `\"none\"`)\n4. بناء قائمة الرسائل:\n   - إدخالات `message` تمر كما هي\n   - إدخالات `custom_message` تصبح رسائل `custom` من نوع AgentMessages عبر `createCustomMessage`\n   - إدخالات `branch_summary` تصبح رسائل `branchSummary` من نوع AgentMessages عبر `createBranchSummaryMessage`\n   - إذا وُجد `compaction` على المسار:\n     - يُصدر ملخص الضغط أولاً (`createCompactionSummaryMessage`)\n     - يُصدر إدخالات المسار بدءاً من `firstKeptEntryId` حتى حدود الضغط\n     - يُصدر الإدخالات بعد حدود الضغط\n\nإدخالات `custom` و`session_init` لا تحقن سياق النموذج مباشرة.\n\n## ضمانات الحفظ ونموذج الفشل\n\n### الحفظ مقابل الذاكرة\n\n- `SessionManager.create/open/continueRecent/forkFrom` -> وضع مستمر (`persist = true`).\n- `SessionManager.inMemory` -> وضع غير مستمر (`persist = false`) مع `MemorySessionStorage`.\n\n### خط أنابيب الكتابة\n\nتُسلسل عمليات الكتابة من خلال سلسلة وعود داخلية (`#persistChain`) و`NdjsonFileWriter`.\n\n- `append*` يحدث حالة الذاكرة فوراً.\n- يتم تأجيل الحفظ حتى وجود رسالة مساعد واحدة على الأقل.\n  - قبل أول مساعد: تُحتفظ الإدخالات في الذاكرة؛ لا يحدث إلحاق بالملف.\n  - عند وجود أول مساعد: تُفرغ الجلسة الكاملة في الذاكرة إلى الملف.\n  - بعد ذلك: الإدخالات الجديدة تُلحق تدريجياً.\n\nالمبرر في الكود: تجنب حفظ الجلسات التي لم تُنتج أبداً استجابة مساعد.\n\n### عمليات المتانة\n\n- `flush()` يفرغ الكاتب ويستدعي `fsync()`.\n- إعادة الكتابة الكاملة الذرية (`#rewriteFile`) تكتب إلى ملف مؤقت، تفرغ+fsync، تغلق، ثم تعيد التسمية فوق الهدف.\n- تُستخدم للترحيلات، `setSessionName`، `rewriteEntries`، عمليات النقل، وإعادة كتابة وسائط استدعاء الأدوات.\n\n### سلوك الخطأ\n\n- أخطاء الحفظ تُقفل (`#persistError`) وتُعاد رميها في العمليات اللاحقة.\n- يُسجل الخطأ الأول مرة واحدة مع سياق ملف الجلسة.\n- إغلاق الكاتب يتم بأفضل جهد لكنه ينشر أول خطأ ذي معنى.\n\n## ضوابط حجم البيانات وتخزين البيانات الكبيرة خارجياً\n\nقبل حفظ الإدخالات:\n\n- السلاسل النصية الكبيرة تُقتطع إلى `MAX_PERSIST_CHARS` (500,000 حرف) مع إشعار:\n  - `\"[Session persistence truncated large content]\"`\n- تُزال الحقول المؤقتة `partialJson` و`jsonlEvents`.\n- إذا كان الكائن يحتوي على كل من `content` و`lineCount`، يُعاد حساب عدد الأسطر بعد الاقتطاع.\n- كتل الصور في مصفوفات `content` مع طول base64 >= 1024 تُخزن خارجياً كمراجع بيانات كبيرة:\n  - تُخزن كـ `blob:sha256:<hash>`\n  - تُكتب البايتات الخام إلى مخزن البيانات الكبيرة (`BlobStore.put`)\n\nعند التحميل، تُحل مراجع البيانات الكبيرة مرة أخرى إلى base64 لكتل الصور في message/custom_message.\n\n## تجريدات التخزين\n\nواجهة `SessionStorage` توفر جميع عمليات نظام الملفات المستخدمة بواسطة `SessionManager`:\n\n- متزامنة: `ensureDirSync`، `existsSync`، `writeTextSync`، `statSync`، `listFilesSync`\n- غير متزامنة: `exists`، `readText`، `readTextPrefix`، `writeText`، `rename`، `unlink`، `openWriter`\n\nالتطبيقات:\n\n- `FileSessionStorage`: نظام ملفات حقيقي (Bun + node fs)\n- `MemorySessionStorage`: تطبيق في الذاكرة مدعوم بخريطة للاختبارات/الجلسات غير المستمرة\n\n`SessionStorageWriter` يكشف `writeLine`، `flush`، `fsync`، `close`، `getError`.\n\n## أدوات اكتشاف الجلسات\n\nمعرفة في `session-manager.ts`:\n\n- `getRecentSessions(sessionDir, limit)` -> بيانات وصفية خفيفة لواجهة المستخدم/منتقي الجلسات\n- `findMostRecentSession(sessionDir)` -> الأحدث حسب وقت التعديل\n- `list(cwd, sessionDir?)` -> الجلسات في نطاق مشروع واحد\n- `listAll()` -> الجلسات عبر جميع نطاقات المشاريع تحت `~/.xcsh/agent/sessions`\n\nاستخراج البيانات الوصفية يقرأ فقط بادئة (`readTextPrefix(..., 4096)`) حيثما أمكن.\n\n## ذو صلة لكن مختلف: تخزين سجل الأوامر\n\n`HistoryStorage` (`history-storage.ts`) هو نظام فرعي منفصل لـ SQLite لاسترجاع/بحث الأوامر، وليس لإعادة تشغيل الجلسة.\n\n- قاعدة البيانات: `~/.xcsh/agent/history.db`\n- الجدول: `history(id, prompt, created_at, cwd)`\n- فهرس FTS5: `history_fts` مع مزامنة مُدارة بالمُحفزات\n- يزيل تكرار الأوامر المتتالية المتطابقة باستخدام ذاكرة مؤقتة للأمر الأخير في الذاكرة\n- إدراج غير متزامن (`setImmediate`) حتى لا يحجب التقاط الأمر تنفيذ الدورة\n\nاستخدم ملفات الجلسة لرسم المحادثة/إعادة تشغيل الحالة؛ واستخدم `HistoryStorage` لتجربة مستخدم سجل الأوامر.\n",
	"ar/sessions/ttsr-injection-lifecycle.md": "---\ntitle: دورة حياة حقن TTSR\ndescription: >-\n  دورة حياة حقن TTSR (استخدام الأداة، ونتيجة الأداة، وتذكير النظام) لإدارة\n  السياق.\nsidebar:\n  order: 9\n  label: حقن TTSR\ni18n:\n  sourceHash: d6179a286584\n  translator: machine\n---\n\n# دورة حياة حقن TTSR\n\nتغطي هذه الوثيقة مسار تشغيل Time Traveling Stream Rules (TTSR) الحالي بدءًا من اكتشاف القواعد وصولًا إلى مقاطعة التدفق، وحقن إعادة المحاولة، وإشعارات الامتداد، ومعالجة حالة الجلسة.\n\n## ملفات التنفيذ\n\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/export/ttsr.ts`](../../packages/coding-agent/src/export/ttsr.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/prompts/system/ttsr-interrupt.md`](../../packages/coding-agent/src/prompts/system/ttsr-interrupt.md)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/types.ts`](../../packages/coding-agent/src/extensibility/extensions/types.ts)\n- [`../src/extensibility/hooks/types.ts`](../../packages/coding-agent/src/extensibility/hooks/types.ts)\n- [`../src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n\n## 1. تغذية الاكتشاف وتسجيل القواعد\n\nعند إنشاء الجلسة، تقوم `createAgentSession()` بتحميل جميع القواعد المكتشفة وبناء `TtsrManager`:\n\n```ts\nconst ttsrSettings = settings.getGroup(\"ttsr\");\nconst ttsrManager = new TtsrManager(ttsrSettings);\nconst rulesResult = await loadCapability<Rule>(ruleCapability.id, { cwd });\nfor (const rule of rulesResult.items) {\n  if (rule.ttsrTrigger) ttsrManager.addRule(rule);\n}\n```\n\n### سلوك إزالة التكرار قبل التسجيل\n\nتُزيل `loadCapability(\"rules\")` التكرارات استنادًا إلى `rule.name` مع منطق \"الأول يفوز\" (بأولوية مزود أعلى أولًا). تُحذف التكرارات المظللة قبل تسجيل TTSR.\n\n### سلوك `TtsrManager.addRule()`\n\nيُتخطى التسجيل عند:\n\n- غياب `rule.ttsrTrigger`\n- وجود قاعدة بالاسم `rule.name` نفسه مسجلة بالفعل في هذا المدير\n- فشل تجميع التعبير النمطي (إلقاء `new RegExp(rule.ttsrTrigger)`)\n\nتُسجَّل محفزات التعبير النمطي غير الصالحة كتحذيرات وتُتجاهل؛ ويستمر بدء تشغيل الجلسة.\n\n### تحفظ الإعدادات\n\nيُحمَّل `TtsrSettings.enabled` في المدير لكنه لا يُفحص حاليًا في بوابة وقت التشغيل. إذا كانت هناك قواعد، تستمر عملية المطابقة.\n\n## 2. دورة حياة مراقب التدفق\n\nيعمل اكتشاف TTSR داخل `AgentSession.#handleAgentEvent`.\n\n### بداية الدور\n\nعند `turn_start`، تُعاد تهيئة مؤقت التدفق:\n\n- `ttsrManager.resetBuffer()`\n\n### أثناء التدفق (`message_update`)\n\nعند وصول تحديثات المساعد وعند وجود قواعد:\n\n- مراقبة `text_delta` و`toolcall_delta`\n- إلحاق الدلتا بمؤقت المدير\n- استدعاء `check(buffer)`\n\nتُكرر `check()` على القواعد المسجلة وتُعيد جميع القواعد المطابقة التي تجتاز سياسة التكرار (`#canTrigger`).\n\n## 3. قرار التفعيل ومسار الإيقاف الفوري\n\nعند تطابق قاعدة واحدة أو أكثر:\n\n1. تُسجّل `markInjected(matches)` أسماء القواعد في حالة الحقن بالمدير.\n2. تُضاف القواعد المطابقة إلى قائمة انتظار `#pendingTtsrInjections`.\n3. يُعيَّن `#ttsrAbortPending = true`.\n4. يُستدعى `agent.abort()` فورًا.\n5. يُصدَر حدث `ttsr_triggered` بشكل غير متزامن (fire-and-forget).\n6. يُجدوَل عمل إعادة المحاولة عبر `setTimeout(..., 50)`.\n\nلا يُعلَّق الإيقاف على انتظار استدعاءات الامتداد.\n\n## 4. جدولة إعادة المحاولة، ووضع السياق، وحقن التذكير\n\nبعد مهلة 50 مللي ثانية:\n\n1. `#ttsrAbortPending = false`\n2. قراءة `ttsrManager.getSettings().contextMode`\n3. إذا كان `contextMode === \"discard\"`، يُحذف الإخراج الجزئي للمساعد باستخدام `agent.popMessage()`\n4. بناء محتوى الحقن من القواعد المعلقة باستخدام قالب `ttsr-interrupt.md`\n5. إلحاق رسالة مستخدم اصطناعية تحتوي على كتلة `<system-interrupt ...>` واحدة لكل قاعدة\n6. استدعاء `agent.continue()` لإعادة محاولة التوليد\n\nتُمسح الحقنات المعلقة بعد توليد المحتوى.\n\n### سلوك `contextMode` على الإخراج الجزئي\n\n- `discard`: تُحذف رسالة المساعد الجزئية/المجهضة قبل إعادة المحاولة.\n- `keep`: يبقى الإخراج الجزئي للمساعد في حالة المحادثة؛ ويُلحق التذكير بعده.\n\n## 5. سياسة التكرار ومنطق الفجوة\n\nيتتبع `TtsrManager` كلًّا من `#messageCount` و`lastInjectedAt` لكل قاعدة.\n\n### `repeatMode: \"once\"`\n\nيمكن لقاعدة ما أن تُفعَّل مرة واحدة فقط بعد أن تمتلك سجل حقن.\n\n### `repeatMode: \"after-gap\"`\n\nيمكن لقاعدة ما إعادة التفعيل فقط عندما:\n\n- `messageCount - lastInjectedAt >= repeatGap`\n\nيتزايد `messageCount` عند `turn_end`، لذا تُقاس الفجوة بعدد الأدوار المكتملة، لا بأجزاء التدفق.\n\n## 6. إصدار الأحداث وسطوح الامتداد/الخطاف\n\n### حدث الجلسة\n\nيتضمن `AgentSessionEvent`:\n\n```ts\n{ type: \"ttsr_triggered\"; rules: Rule[] }\n```\n\n### مشغّل الامتداد\n\nتُوجّه `#emitSessionEvent()` الحدث إلى:\n\n- مستمعي الامتداد (`ExtensionRunner.emit({ type: \"ttsr_triggered\", rules })`)\n- المشتركين المحليين في الجلسة\n\n### كتابة الخطاف والأداة المخصصة\n\n- تعرض واجهة API للامتداد `on(\"ttsr_triggered\", ...)`\n- تعرض واجهة API للخطاف `on(\"ttsr_triggered\", ...)`\n- تتلقى الأدوات المخصصة `onSession({ reason: \"ttsr_triggered\", rules })`\n\n### الفارق في العرض للوضع التفاعلي\n\nيستخدم الوضع التفاعلي `session.isTtsrAbortPending` لتجنب عرض سبب إيقاف المساعد المجهض بوصفه فشلًا مرئيًا أثناء مقاطعة TTSR، ويعرض `TtsrNotificationComponent` عند وصول الحدث.\n\n## 7. الاستمرارية وحالة الاستئناف (التنفيذ الحالي)\n\nيمتلك `SessionManager` دعمًا كاملًا للمخطط من أجل استمرارية القواعد المحقونة:\n\n- نوع الإدخال: `ttsr_injection`\n- واجهة API للإلحاق: `appendTtsrInjection(ruleNames)`\n- واجهة API للاستعلام: `getInjectedTtsrRules()`\n- يتضمن إعادة بناء السياق `SessionContext.injectedTtsrRules`\n\nيدعم `TtsrManager` أيضًا الاستعادة عبر `restoreInjected(ruleNames)`.\n\n### حالة التوصيل الحالية\n\nفي مسار وقت التشغيل الحالي:\n\n- لا تُلحق `AgentSession` إدخالات `ttsr_injection` عند تفعيل TTSR.\n- لا تستعيد `createAgentSession()` `existingSession.injectedTtsrRules` في `ttsrManager`.\n\nالأثر الصافي: يُطبَّق قمع القواعد المحقونة في الذاكرة للعملية الحية، لكنه لا يُستمر/يُستعاد حاليًا عبر إعادة تحميل/استئناف الجلسة بهذا المسار.\n\n## 8. حدود السباق وضمانات الترتيب\n\n### الإيقاف مقابل استدعاء إعادة المحاولة\n\n- الإيقاف متزامن من منظور معالج TTSR (استدعاء `agent.abort()` فوريًا)\n- إعادة المحاولة مؤجلة بمؤقت (`50ms`)\n- إشعار الامتداد غير متزامن وغير منتظَر عمدًا قبل جدولة الإيقاف/إعادة المحاولة\n\n### تطابقات متعددة في نافذة التدفق نفسها\n\nتُعيد `check()` جميع القواعد المؤهلة المطابقة حاليًا. تُحقن في دفعة واحدة في رسالة إعادة المحاولة التالية.\n\n### بين الإيقاف والاستمرار\n\nخلال نافذة المؤقت، قد تتغير الحالة (مقاطعة المستخدم، إجراءات الوضع، أحداث إضافية). استدعاء إعادة المحاولة هو بذل أفضل جهد: `agent.continue().catch(() => {})` يبتلع الأخطاء التالية.\n\n## 9. ملخص الحالات الحافة\n\n- تعبير نمطي غير صالح لـ `ttsr_trigger`: يُتخطى مع تحذير؛ وتستمر القواعد الأخرى.\n- أسماء قواعد مكررة في طبقة القدرات: تُظلل التكرارات ذات الأولوية الأدنى قبل التسجيل.\n- أسماء مكررة في طبقة المدير: يُتجاهل التسجيل الثاني.\n- `contextMode: \"keep\"`: قد يبقى الإخراج الجزئي المنتهك للقاعدة في السياق قبل إعادة محاولة التذكير.\n- يعتمد `repeat-after-gap` على تزايدات عدد الأدوار عند `turn_end`؛ أجزاء منتصف الدور لا تُقدّم عدادات الفجوة.\n",
	"ar/tui/theme.md": "---\ntitle: مرجع السمات\ndescription: مرجع سمات TUI مع رموز الألوان وإعدادات الخطوط وتخصيص السمات.\nsidebar:\n  order: 3\n  label: السمات\ni18n:\n  sourceHash: 7e962a7da157\n  translator: machine\n---\n\n# مرجع السمات\n\nيصف هذا المستند كيفية عمل نظام السمات في وكيل الترميز اليوم: المخطط، والتحميل، وسلوك وقت التشغيل، وأوضاع الفشل.\n\n## ما يتحكم فيه نظام السمات\n\nيتحكم نظام السمات في:\n\n- رموز ألوان المقدمة/الخلفية المستخدمة عبر TUI\n- محوّلات تنسيق markdown (`getMarkdownTheme()`)\n- محوّلات المحدد/المحرر/قائمة الإعدادات (`getSelectListTheme()`، `getEditorTheme()`، `getSettingsListTheme()`)\n- الإعداد المسبق للرموز + تجاوزات الرموز (`unicode`، `nerd`، `ascii`)\n- ألوان تمييز بناء الجملة المستخدمة بواسطة المُمَيِّز الأصلي (`@f5-sales-demo/pi-natives`)\n- ألوان أجزاء شريط الحالة\n\nالتنفيذ الأساسي: `src/modes/theme/theme.ts`.\n\n## شكل ملف JSON للسمة\n\nملفات السمات هي كائنات JSON يتم التحقق من صحتها مقابل مخطط وقت التشغيل في `theme.ts` (`ThemeJsonSchema`) والمعكوسة بواسطة `src/modes/theme/theme-schema.json`.\n\nالحقول ذات المستوى الأعلى:\n\n- `name` (مطلوب)\n- `colors` (مطلوب؛ جميع رموز الألوان مطلوبة)\n- `vars` (اختياري؛ متغيرات ألوان قابلة لإعادة الاستخدام)\n- `export` (اختياري؛ ألوان تصدير HTML)\n- `symbols` (اختياري)\n  - `preset` (اختياري: `unicode | nerd | ascii`)\n  - `overrides` (اختياري: تجاوزات مفتاح/قيمة لـ `SymbolKey`)\n\nتقبل قيم الألوان:\n\n- سلسلة hex (`\"#RRGGBB\"`)\n- مؤشر 256 لونًا (`0..255`)\n- سلسلة مرجع متغير (يتم حلها عبر `vars`)\n- سلسلة فارغة (`\"\"`) تعني الإعداد الافتراضي للطرفية (`\\x1b[39m` للمقدمة، `\\x1b[49m` للخلفية)\n\n## رموز الألوان المطلوبة (الحالية)\n\nجميع الرموز أدناه مطلوبة في `colors`.\n\n### النص الأساسي والحدود (11)\n\n`accent`، `border`، `borderAccent`، `borderMuted`، `success`، `error`، `warning`، `muted`، `dim`، `text`، `thinkingText`\n\n### كتل الخلفية (7)\n\n`selectedBg`، `userMessageBg`، `customMessageBg`، `toolPendingBg`، `toolSuccessBg`، `toolErrorBg`، `statusLineBg`\n\n### نص الرسائل/الأدوات (5)\n\n`userMessageText`، `customMessageText`، `customMessageLabel`، `toolTitle`، `toolOutput`\n\n### Markdown (10)\n\n`mdHeading`، `mdLink`، `mdLinkUrl`، `mdCode`، `mdCodeBlock`، `mdCodeBlockBorder`، `mdQuote`، `mdQuoteBorder`، `mdHr`، `mdListBullet`\n\n### فروق الأدوات + تمييز بناء الجملة (12)\n\n`toolDiffAdded`، `toolDiffRemoved`، `toolDiffContext`،\n`syntaxComment`، `syntaxKeyword`، `syntaxFunction`، `syntaxVariable`، `syntaxString`، `syntaxNumber`، `syntaxType`، `syntaxOperator`، `syntaxPunctuation`\n\n### حدود الوضع/التفكير (8)\n\n`thinkingOff`، `thinkingMinimal`، `thinkingLow`، `thinkingMedium`، `thinkingHigh`، `thinkingXhigh`، `bashMode`، `pythonMode`\n\n### ألوان أجزاء شريط الحالة (14)\n\n`statusLineSep`، `statusLineModel`، `statusLinePath`، `statusLineGitClean`، `statusLineGitDirty`، `statusLineContext`، `statusLineSpend`، `statusLineStaged`، `statusLineDirty`، `statusLineUntracked`، `statusLineOutput`، `statusLineCost`، `statusLineSubagents`\n\n## الرموز الاختيارية\n\n### قسم `export` (اختياري)\n\nيُستخدم لمساعدات تحديد سمات تصدير HTML:\n\n- `export.pageBg`\n- `export.cardBg`\n- `export.infoBg`\n\nإذا تم حذفه، يشتق كود التصدير القيم الافتراضية من ألوان السمة المحلولة.\n\n### قسم `symbols` (اختياري)\n\n- `symbols.preset` يضبط مجموعة الرموز الافتراضية على مستوى السمة.\n- `symbols.overrides` يمكنه تجاوز قيم `SymbolKey` الفردية.\n\nأولوية وقت التشغيل:\n\n1. تجاوز الإعدادات `symbolPreset` (إذا تم ضبطه)\n2. `symbols.preset` في JSON للسمة\n3. الاحتياطي `\"unicode\"`\n\nيتم تجاهل مفاتيح التجاوز غير الصالحة وتسجيلها (`logger.debug`).\n\n## مصادر السمات المدمجة مقابل المخصصة\n\nترتيب البحث عن السمات (`loadThemeJson`):\n\n1. السمات المدمجة المضمنة (`defaults/xcsh-dark.json` و `defaults/xcsh-light.json` المترجمة إلى `defaultThemes`)\n2. ملف السمة المخصصة: `<customThemesDir>/<name>.json`\n\nيأتي دليل السمات المخصصة من `getCustomThemesDir()`:\n\n- الافتراضي: `~/.xcsh/agent/themes`\n- يتم تجاوزه بواسطة `PI_CODING_AGENT_DIR` (`$PI_CODING_AGENT_DIR/themes`)\n\nتُرجع `getAvailableThemes()` الأسماء المدمجة والمخصصة مدمجةً، مرتبةً، مع أسبقية المدمجة عند تعارض الأسماء.\n\n## التحميل والتحقق والحل\n\nلملفات السمات المخصصة:\n\n1. قراءة JSON\n2. تحليل JSON\n3. التحقق من الصحة مقابل `ThemeJsonSchema`\n4. حل مراجع `vars` بشكل تكراري\n5. تحويل القيم المحلولة إلى ANSI حسب وضع قدرة الطرفية\n\nسلوك التحقق:\n\n- رموز الألوان المطلوبة المفقودة: رسالة خطأ مجمعة صريحة\n- أنواع/قيم رموز خاطئة: أخطاء تحقق مع مسار JSON\n- ملف سمة غير معروف: `Theme not found: <name>`\n\nسلوك مرجع المتغيرات:\n\n- يدعم المراجع المتداخلة\n- يُلقي خطأً عند مرجع متغير مفقود\n- يُلقي خطأً عند المراجع الدائرية\n\n## سلوك وضع ألوان الطرفية\n\nاكتشاف وضع الألوان (`detectColorMode`):\n\n- `COLORTERM=truecolor|24bit` => truecolor\n- `WT_SESSION` => truecolor\n- `TERM` في `dumb`، `linux`، أو فارغ => 256color\n- وإلا => truecolor\n\nسلوك التحويل:\n\n- hex -> `Bun.color(..., \"ansi-16m\" | \"ansi-256\")`\n- رقمي -> `38;5` / `48;5` ANSI\n- `\"\"` -> إعادة ضبط fg/bg الافتراضي\n\n## سلوك التبديل في وقت التشغيل\n\n### السمة الأولية (`initTheme`)\n\nتقوم `main.ts` بتهيئة السمة مع الإعدادات:\n\n- `symbolPreset`\n- `colorBlindMode`\n- `theme.dark`\n- `theme.light`\n\nيستخدم الاختيار التلقائي لفتحة السمة اكتشاف خلفية `COLORFGBG`:\n\n- تحليل مؤشر الخلفية من `COLORFGBG`\n- `< 8` => فتحة داكنة (`theme.dark`)\n- `>= 8` => فتحة فاتحة (`theme.light`)\n- فشل التحليل => فتحة داكنة\n\nالإعدادات الافتراضية الحالية من مخطط الإعدادات:\n\n- `theme.dark = \"xcsh-dark\"`\n- `theme.light = \"xcsh-light\"`\n- `symbolPreset = \"unicode\"`\n- `colorBlindMode = false`\n\n### التبديل الصريح (`setTheme`)\n\n- تحميل السمة المحددة\n- تحديث المفرد العام `theme`\n- بدء المراقب اختياريًا\n- تشغيل رد النداء `onThemeChange`\n\nعند الفشل:\n\n- الرجوع إلى السمة `dark` المدمجة\n- إرجاع `{ success: false, error }`\n\n### تبديل المعاينة (`previewTheme`)\n\n- تطبيق سمة معاينة مؤقتة على `theme` العام\n- **لا** يغير الإعدادات المحفوظة بحد ذاته\n- إرجاع النجاح/الخطأ دون استبدال احتياطي\n\nتستخدم واجهة مستخدم الإعدادات هذا للمعاينة المباشرة وتستعيد السمة السابقة عند الإلغاء.\n\n## المراقبون وإعادة التحميل المباشر\n\nعند تمكين المراقب (`setTheme(..., true)` / التهيئة التفاعلية):\n\n- يراقب فقط مسار الملف المخصص `<customThemesDir>/<currentTheme>.json`\n- السمات المدمجة لا تُراقب فعليًا\n- `change` للملف: محاولة إعادة التحميل (مُؤخرة)\n- `rename`/حذف الملف: الرجوع إلى `dark`، إغلاق المراقب\n\nيقوم الوضع التلقائي أيضًا بتثبيت مستمع `SIGWINCH` ويمكنه إعادة تقييم تعيين فتحة داكن/فاتح عند تغير حالة الطرفية.\n\n## سلوك وضع عمى الألوان\n\nيغير `colorBlindMode` رمزًا واحدًا فقط في وقت التشغيل:\n\n- يتم ضبط `toolDiffAdded` باستخدام HSV (تحويل اللون الأخضر نحو الأزرق)\n- يُطبَّق الضبط فقط عندما تكون القيمة المحلولة سلسلة hex\n\nالرموز الأخرى لا تتغير.\n\n## مكان حفظ إعدادات السمة\n\nيتم حفظ الإعدادات المتعلقة بالسمة بواسطة `Settings` في ملف YAML للإعداد العام:\n\n- المسار: `<agentDir>/config.yml`\n- دليل الوكيل الافتراضي: `~/.xcsh/agent`\n- الملف الافتراضي الفعلي: `~/.xcsh/agent/config.yml`\n\nالمفاتيح المحفوظة:\n\n- `theme.dark`\n- `theme.light`\n- `symbolPreset`\n- `colorBlindMode`\n\nيوجد ترحيل قديم: يتم ترحيل `theme: \"name\"` المسطح القديم إلى `theme.dark` أو `theme.light` المتداخل بناءً على اكتشاف السطوع.\n\n## إنشاء سمة مخصصة (عملي)\n\n1. أنشئ ملفًا في دليل السمات المخصصة، مثلًا `~/.xcsh/agent/themes/my-theme.json`.\n2. أدرج `name`، وإختياريًا `vars`، و**جميع** رموز `colors` المطلوبة.\n3. أدرج اختياريًا `symbols` و `export`.\n4. حدد السمة في الإعدادات (`Display -> Dark theme` أو `Display -> Light theme`) بحسب فتحة التحديد التلقائي التي تريدها.\n\nهيكل عظمي أدنى. كل مفتاح في `colors` مطلوب — يرفض المدقق في وقت التشغيل\n(`additionalProperties: false`) كلًا من المفاتيح المفقودة والمفاتيح غير المعروفة.\nللاطلاع على تطبيقات المرجع المشحونة راجع\n[`packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json)\nو [`xcsh-light.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-light.json).\n\nيحتوي شريط الحالة على نظامَي ألوان متوازيَين موثقَين في المشكلة #242:\n\n- ألوان النص بصيغة Hex (`statusLinePath`، `statusLineGitClean`، `statusLineGitDirty`،\n  `statusLineStaged`، `statusLineDirty`، `statusLineUntracked`) تُشغّل عرض non-powerline.\n- مؤشرات لوحة الألوان 256 (`statusLine<Segment>Bg` / `statusLine<Segment>Fg`)\n  تُشغّل تعبئات أجزاء powerline. وهي مستقلة عن مفاتيح hex أعلاه —\n  يجب ضبط كليهما.\n\n```json\n{\n  \"name\": \"my-theme\",\n  \"vars\": {\n    \"accent\": \"#7aa2f7\",\n    \"muted\": 244\n  },\n  \"colors\": {\n    \"accent\": \"accent\",\n    \"chromeAccent\": \"accent\",\n    \"spinnerAccent\": \"accent\",\n    \"contentAccent\": \"muted\",\n    \"border\": \"#4c566a\",\n    \"borderAccent\": \"accent\",\n    \"borderMuted\": \"muted\",\n    \"success\": \"#9ece6a\",\n    \"error\": \"#f7768e\",\n    \"warning\": \"#e0af68\",\n    \"muted\": \"muted\",\n    \"dim\": 240,\n    \"gutterSuccess\": \"#7dcfff\",\n    \"gutterWarning\": \"#e0af68\",\n    \"text\": \"\",\n    \"thinkingText\": \"muted\",\n\n    \"selectedBg\": \"#2a2f45\",\n    \"userMessageBg\": \"#1f2335\",\n    \"userMessageText\": \"\",\n    \"customMessageBg\": \"#24283b\",\n    \"customMessageText\": \"\",\n    \"customMessageLabel\": \"accent\",\n    \"toolPendingBg\": \"#1f2335\",\n    \"toolSuccessBg\": \"#1f2d2a\",\n    \"toolErrorBg\": \"#2d1f2a\",\n    \"toolTitle\": \"\",\n    \"toolOutput\": \"muted\",\n\n    \"mdHeading\": \"accent\",\n    \"mdLink\": \"accent\",\n    \"mdLinkUrl\": \"muted\",\n    \"mdCode\": \"#c0caf5\",\n    \"mdCodeBlock\": \"#c0caf5\",\n    \"mdCodeBlockBorder\": \"muted\",\n    \"mdQuote\": \"muted\",\n    \"mdQuoteBorder\": \"muted\",\n    \"mdHr\": \"muted\",\n    \"mdListBullet\": \"accent\",\n\n    \"toolDiffAdded\": \"#9ece6a\",\n    \"toolDiffRemoved\": \"#f7768e\",\n    \"toolDiffContext\": \"muted\",\n\n    \"syntaxComment\": \"#565f89\",\n    \"syntaxKeyword\": \"#bb9af7\",\n    \"syntaxFunction\": \"#7aa2f7\",\n    \"syntaxVariable\": \"#c0caf5\",\n    \"syntaxString\": \"#9ece6a\",\n    \"syntaxNumber\": \"#ff9e64\",\n    \"syntaxType\": \"#2ac3de\",\n    \"syntaxOperator\": \"#89ddff\",\n    \"syntaxPunctuation\": \"#9aa5ce\",\n    \"syntaxControl\": \"#bb9af7\",\n\n    \"thinkingOff\": 240,\n    \"thinkingMinimal\": 244,\n    \"thinkingLow\": \"#7aa2f7\",\n    \"thinkingMedium\": \"#2ac3de\",\n    \"thinkingHigh\": \"#bb9af7\",\n    \"thinkingXhigh\": \"#f7768e\",\n\n    \"bashMode\": \"#2ac3de\",\n    \"pythonMode\": \"#bb9af7\",\n\n    \"statusLineBg\": \"#16161e\",\n    \"statusLineSep\": 240,\n    \"statusLineModel\": \"#bb9af7\",\n    \"statusLinePath\": \"#7aa2f7\",\n    \"statusLineGitClean\": \"#9ece6a\",\n    \"statusLineGitDirty\": \"#e0af68\",\n    \"statusLineContext\": \"#2ac3de\",\n    \"statusLineSpend\": \"#7dcfff\",\n    \"statusLineStaged\": \"#9ece6a\",\n    \"statusLineDirty\": \"#e0af68\",\n    \"statusLineUntracked\": \"#f7768e\",\n    \"statusLineOutput\": \"#c0caf5\",\n    \"statusLineCost\": \"#ff9e64\",\n    \"statusLineSubagents\": \"#bb9af7\",\n\n    \"statusLineOsIconBg\": 7,\n    \"statusLineOsIconFg\": 232,\n    \"statusLinePathBg\": 4,\n    \"statusLinePathFg\": 254,\n    \"statusLineGitCleanBg\": 2,\n    \"statusLineGitCleanFg\": 0,\n    \"statusLineGitDirtyBg\": 3,\n    \"statusLineGitDirtyFg\": 0,\n    \"statusLineGitStagedBg\": 64,\n    \"statusLineGitStagedFg\": 0,\n    \"statusLineGitUntrackedBg\": 39,\n    \"statusLineGitUntrackedFg\": 0,\n    \"statusLineGitConflictBg\": 1,\n    \"statusLineGitConflictFg\": 7,\n    \"statusLinePlanModeBg\": 236,\n    \"statusLinePlanModeFg\": 117,\n    \"statusLineProfileXcshBg\": \"accent\",\n    \"statusLineProfileXcshFg\": 231\n  }\n}\n```\n\n## اختبار السمات المخصصة\n\nاستخدم هذه الطريقة:\n\n1. ابدأ الوضع التفاعلي (المراقب مُمكَّن منذ بدء التشغيل).\n2. افتح الإعدادات وقم بمعاينة قيم السمة (مباشر `previewTheme`).\n3. لملفات السمات المخصصة، قم بتعديل JSON أثناء التشغيل وتأكد من إعادة التحميل التلقائي عند الحفظ.\n4. اختبر الأسطح الحيوية:\n   - عرض markdown\n   - كتل الأدوات (معلق/ناجح/خطأ)\n   - عرض الفروق (مضاف/محذوف/سياق)\n   - قابلية قراءة شريط الحالة\n   - تغييرات حدود مستوى التفكير\n   - ألوان حدود وضع bash/python\n5. تحقق من صحة كلا الإعدادات المسبقة للرموز إذا كانت سمتك تعتمد على عرض/مظهر الرموز.\n\n## القيود والتحفظات الفعلية\n\n- جميع رموز `colors` مطلوبة للسمات المخصصة.\n- `export` و `symbols` اختياريان.\n- `$schema` في JSON للسمة إعلامي؛ يُفرض التحقق في وقت التشغيل بواسطة مخطط TypeBox المترجم في الكود.\n- فشل `setTheme` يرجع إلى `dark`؛ فشل `previewTheme` لا يستبدل السمة الحالية.\n- أخطاء إعادة تحميل مراقب الملف تُبقي السمة المحملة الحالية حتى يتم تشغيل إعادة تحميل ناجحة أو مسار احتياطي.\n",
	"ar/tui/tree.md": "---\ntitle: مرجع أمر Tree\ndescription: مرجع أمر /tree لتصور سجل الجلسة وفروع المحادثة.\nsidebar:\n  order: 4\n  label: أمر /tree\ni18n:\n  sourceHash: ee0e412fe993\n  translator: machine\n---\n\n# مرجع أمر `/tree`\n\nيفتح `/tree` متصفح **شجرة الجلسة** التفاعلي. يتيح لك الانتقال إلى أي إدخال في ملف الجلسة الحالي والمتابعة من تلك النقطة.\n\nهذه عملية انتقال داخلي إلى ورقة في الملف، وليست تصدير جلسة جديدة.\n\n## ما يفعله `/tree`\n\n- يبني شجرة من إدخالات الجلسة الحالية (`SessionManager.getTree()`)\n- يفتح `TreeSelectorComponent` مع التنقل بلوحة المفاتيح والفلاتر والبحث\n- عند الاختيار، يستدعي `AgentSession.navigateTree(targetId, { summarize, customInstructions })`\n- يعيد بناء المحادثة المرئية من مسار الورقة الجديدة\n- يملأ مسبقاً نص المحرر اختيارياً عند اختيار رسالة مستخدم/مخصصة\n\nالتنفيذ الأساسي:\n\n- `src/modes/controllers/input-controller.ts` (`/tree`، تشغيل اختصارات لوحة المفاتيح، سلوك الضغط المزدوج على Escape)\n- `src/modes/controllers/selector-controller.ts` (تشغيل واجهة الشجرة + تدفق مطالبة الملخص)\n- `src/modes/components/tree-selector.ts` (التنقل، الفلاتر، البحث، التسميات، العرض)\n- `src/session/agent-session.ts` (تبديل الورقة في `navigateTree` + الملخص الاختياري)\n- `src/session/session-manager.ts` (`getTree`، `branch`، `branchWithSummary`، `resetLeaf`، استمرارية التسمية)\n\n## كيفية فتحه\n\nأي من الطرق التالية يفتح نفس المحدد:\n\n- `/tree`\n- إجراء اختصار لوحة المفاتيح المُكوَّن `tree`\n- الضغط المزدوج على Escape عند محرر فارغ عندما يكون `doubleEscapeAction = \"tree\"` (الافتراضي)\n- `/branch` عندما يكون `doubleEscapeAction = \"tree\"` (يوجه إلى محدد الشجرة بدلاً من منتقي الفروع للمستخدم فقط)\n\n## نموذج واجهة مستخدم الشجرة\n\nيتم عرض الشجرة من مؤشرات العناصر الأم لإدخالات الجلسة (`id` / `parentId`).\n\n- يتم ترتيب العناصر الأبناء تصاعدياً حسب الطابع الزمني (الأقدم أولاً، الأحدث في الأسفل)\n- يُشار إلى الفرع النشط (المسار من الجذر إلى الورقة الحالية) بنقطة\n- تُعرض التسميات (إن وُجدت) بالشكل `[label]` قبل نص العقدة\n- إذا وُجدت جذور متعددة (سلاسل أصل مقطوعة/معطلة)، تُعرض تحت جذر تفرع افتراضي\n\n```text\nمثال على عرض الشجرة (المسار النشط مُشار إليه بـ•):\n\n├─ user: \"Start task\"\n│  └─ assistant: \"Plan\"\n│     ├─ • user: \"Try approach A\"\n│     │  └─ • assistant: \"A result\"\n│     │     └─ • [milestone] user: \"Continue A\"\n│     └─ user: \"Try approach B\"\n│        └─ assistant: \"B result\"\n```\n\nيُعيد المحدد التمركز حول الاختيار الحالي ويُظهر ما يصل إلى:\n\n- `max(5, floor(terminalHeight / 2))` صفاً\n\n## اختصارات لوحة المفاتيح داخل محدد الشجرة\n\n- `Up` / `Down`: تحريك الاختيار (مع التفاف)\n- `Left` / `Right`: الصفحة السابقة / التالية\n- `Enter`: اختيار العقدة\n- `Esc`: مسح البحث إذا كان نشطاً؛ وإلا إغلاق المحدد\n- `Ctrl+C`: إغلاق المحدد\n- `Type`: إلحاق محرف بعبارة البحث\n- `Backspace`: حذف محرف بحث\n- `Shift+L`: تعديل/مسح تسمية الإدخال المحدد\n- `Ctrl+O`: تدوير الفلتر للأمام\n- `Shift+Ctrl+O`: تدوير الفلتر للخلف\n- `Alt+D/T/U/L/A`: الانتقال مباشرة إلى وضع فلتر محدد\n\n## الفلاتر ودلالات البحث\n\nأوضاع الفلتر (`TreeList`):\n\n1. `default`\n2. `no-tools`\n3. `user-only`\n4. `labeled-only`\n5. `all`\n\n### `default`\n\nيُظهر معظم العقد التحاورية، لكنه يُخفي أنواع إدخالات المحاسبة:\n\n- `label`\n- `custom`\n- `model_change`\n- `thinking_level_change`\n\n### `no-tools`\n\nمثل `default`، بالإضافة إلى إخفاء رسائل `toolResult`.\n\n### `user-only`\n\nإدخالات `message` التي دورها `user` فقط.\n\n### `labeled-only`\n\nالإدخالات التي تحل حالياً إلى تسمية فقط.\n\n### `all`\n\nكل شيء في شجرة الجلسة، بما في ذلك إدخالات المحاسبة/المخصصة.\n\n### سلوك عقدة المساعد للأدوات فقط\n\nتُخفى رسائل المساعد التي تحتوي على **استدعاءات أدوات فقط** (بلا نص) افتراضياً في جميع طرق العرض المُصفّاة إلا في حالتين:\n\n- الرسالة بها خطأ/أُجهضت (`stopReason` ليس `stop`/`toolUse`)، أو\n- هي الورقة الحالية (تبقى مرئية دائماً)\n\n### سلوك البحث\n\n- تُقسَّم عبارة الاستعلام بالمسافات\n- المطابقة غير حساسة لحالة الأحرف\n- يجب أن تتطابق جميع الرموز (دلالات AND)\n- يشمل النص القابل للبحث التسمية والدور والمحتوى المحدد بالنوع (نص الرسالة، نص ملخص الفرع، النوع المخصص، مقتطفات أوامر الأدوات، إلخ.)\n\n## نتائج الاختيار (مهم)\n\nيحسب `navigateTree` سلوك الورقة الجديدة من نوع الإدخال المحدد:\n\n### اختيار رسالة `user`\n\n- تصبح الورقة الجديدة هي `parentId` الإدخال المحدد\n- إذا كان الأصل `null` (رسالة مستخدم جذرية)، تُعاد تهيئة الورقة إلى الجذر (`resetLeaf()`)\n- يُنسخ نص الرسالة المحددة إلى المحرر للتعديل/إعادة الإرسال\n\n### اختيار `custom_message`\n\n- نفس قاعدة الورقة كرسائل المستخدم (`parentId`)\n- يُستخرج محتوى النص ويُنسخ إلى المحرر\n\n### اختيار عقدة غير مستخدم (مساعد/أداة/ملخص/ضغط/محاسبة مخصصة/إلخ.)\n\n- تصبح الورقة الجديدة هي معرف العقدة المحددة\n- لا يُملأ المحرر مسبقاً\n\n### اختيار الورقة الحالية\n\n- لا إجراء؛ يُغلق المحدد مع \"موجود بالفعل في هذه النقطة\"\n\n```text\nقرار الاختيار (مبسط):\n\nselected node\n   │\n   ├─ is current leaf? ── yes ──> close selector (no-op)\n   │\n   ├─ is user/custom_message? ── yes ──> leaf := parentId (or resetLeaf for root)\n   │                                     + prefill editor text\n   │\n   └─ otherwise ──> leaf := selected node id\n                    + no editor prefill\n```\n\n## تدفق الملخص عند التبديل\n\nيتحكم في مطالبة الملخص الإعداد `branchSummary.enabled` (الافتراضي: `false`).\n\nعند التفعيل، بعد اختيار عقدة تسأل الواجهة:\n\n- `No summary`\n- `Summarize`\n- `Summarize with custom prompt`\n\nتفاصيل التدفق:\n\n- الضغط على Escape في مطالبة الملخص يعيد فتح محدد الشجرة\n- إلغاء المطالبة المخصصة يعود إلى حلقة اختيار الملخص\n- أثناء إنشاء الملخص، تُظهر الواجهة مؤشر تحميل وتربط `Esc` بـ `abortBranchSummary()`\n- إذا أُجهض إنشاء الملخص، يُعاد فتح محدد الشجرة ولا يُطبق أي انتقال\n\nدواخل `navigateTree`:\n\n- يجمع إدخالات الفرع المهجور من الورقة القديمة إلى السلف المشترك\n- يُصدر `session_before_tree` (يمكن للإضافات الإلغاء أو حقن الملخص)\n- يستخدم جهاز التلخيص الافتراضي فقط إذا طُلب وكان ضرورياً\n- يطبق الانتقال بـ:\n  - `branchWithSummary(...)` عند وجود ملخص\n  - `branch(newLeafId)` للانتقال غير الجذري بدون ملخص\n  - `resetLeaf()` للانتقال الجذري بدون ملخص\n- يستبدل محادثة الوكيل بسياق جلسة معاد بناؤه\n- يُصدر `session_tree`\n\nملاحظة: إذا طلب المستخدم ملخصاً ولكن لا يوجد شيء للتلخيص، يستمر التنقل دون إنشاء إدخال ملخص.\n\n## التسميات\n\nتستدعي تعديلات التسمية في واجهة الشجرة `appendLabelChange(targetId, label)`.\n\n- تسمية غير فارغة تضبط/تحدث التسمية المحلولة\n- تسمية فارغة تمسحها\n- تُخزَّن التسميات كإدخالات `label` للإلحاق فقط\n- تعرض عقد الشجرة حالة التسمية المحلولة، وليس سجل إدخالات التسمية الخام\n\n## `/tree` مقابل العمليات المجاورة\n\n| العملية | النطاق | النتيجة |\n|---|---|---|\n| `/tree` | ملف الجلسة الحالي | ينقل الورقة إلى النقطة المحددة (نفس الملف) |\n| `/branch` | عادةً ملف الجلسة الحالي -> ملف جلسة جديد | افتراضياً يتفرع من رسالة **مستخدم** محددة إلى ملف جلسة جديد؛ إذا كان `doubleEscapeAction = \"tree\"`، يفتح `/branch` واجهة تنقل الشجرة بدلاً من ذلك |\n| `/fork` | جلسة الجلسة الحالية بالكامل | يكرر الجلسة إلى ملف جلسة جديد محفوظ |\n| `/resume` | قائمة الجلسات | يتبدل إلى ملف جلسة آخر |\n\nالتمييز الرئيسي: `/tree` هو أداة تنقل/إعادة تموضع داخل ملف جلسة واحد. كل من `/branch` و`/fork` و`/resume` تغير سياق ملف الجلسة.\n\n## سير عمل المشغل\n\n### إعادة التشغيل من مطالبة مستخدم سابقة دون فقدان الفرع الحالي\n\n1. `/tree`\n2. البحث عن رسالة مستخدم سابقة واختيارها\n3. اختيار `No summary` (أو التلخيص إذا لزم)\n4. تعديل النص المملوء مسبقاً في المحرر\n5. الإرسال\n\nالأثر: ينمو فرع جديد من النقطة المحددة داخل نفس ملف الجلسة.\n\n### مغادرة الفرع الحالي مع علامة تمييز سياقية\n\n1. تفعيل `branchSummary.enabled`\n2. `/tree` واختيار العقدة المستهدفة\n3. اختيار `Summarize` (أو مطالبة مخصصة)\n\nالأثر: يُلحق إدخال `branch_summary` عند الموضع المستهدف قبل المتابعة.\n\n### فحص إدخالات المحاسبة المخفية\n\n1. `/tree`\n2. الضغط على `Alt+A` (الكل)\n3. البحث عن `model`، `thinking`، `custom`، أو التسميات\n\nالأثر: فحص الجدول الزمني الداخلي الكامل، وليس فقط العقد التحاورية.\n\n### وضع علامات على نقاط التحول للرجوع إليها لاحقاً\n\n1. `/tree`\n2. الانتقال إلى الإدخال\n3. `Shift+L` وضبط تسمية\n4. لاحقاً استخدام `Alt+L` (`labeled-only`) للانتقال بسرعة\n\nالأثر: تنقل سريع بين معالم الفروع الدائمة.\n",
	"ar/tui/tui-runtime-internals.md": "---\ntitle: الداخليات الخاصة ببيئة تشغيل TUI\ndescription: >-\n  الداخليات الخاصة ببيئة تشغيل واجهة المستخدم الطرفية (TUI) تشمل خط أنابيب العرض\n  ومعالجة المدخلات وإدارة الحالة.\nsidebar:\n  order: 2\n  label: الداخليات الخاصة ببيئة التشغيل\ni18n:\n  sourceHash: cc8f7dcce46a\n  translator: machine\n---\n\n# الداخليات الخاصة ببيئة تشغيل TUI\n\nيوضح هذا المستند مسار بيئة التشغيل غير المتعلق بالسمة، من المدخلات الطرفية إلى المخرجات المعروضة في الوضع التفاعلي. ويركز على السلوك في `packages/tui` وتكاملها من وحدات تحكم `packages/coding-agent`.\n\n## طبقات بيئة التشغيل والملكية\n\n- **محرك `packages/tui`**: دورة حياة الطرفية، وتطبيع stdin، وتوجيه التركيز، وجدولة العرض، والرسم التفاضلي، وتركيب الطبقات العلوية، ووضع مؤشر الأجهزة.\n- **الوضع التفاعلي لـ `packages/coding-agent`**: يبني شجرة المكونات، ويربط عمليات رد الاتصال للمحرر وخرائط المفاتيح، ويتفاعل مع أحداث الوكيل/الجلسة، ويترجم حالة المجال (البث، وتنفيذ الأدوات، وإعادة المحاولة، ووضع الخطة) إلى مكونات واجهة المستخدم.\n\nقاعدة الحدود: محرك TUI محايد من الناحية الدلالية للرسائل. لا يعرف سوى `Component.render(width)` و`handleInput(data)` والتركيز والطبقات العلوية. تبقى دلالات الوكيل في وحدات تحكم التفاعل.\n\n## ملفات التنفيذ\n\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/components/custom-editor.ts`](../../packages/coding-agent/src/modes/components/custom-editor.ts)\n- [`../../tui/src/tui.ts`](../../packages/tui/src/tui.ts)\n- [`../../tui/src/terminal.ts`](../../packages/tui/src/terminal.ts)\n- [`../../tui/src/editor-component.ts`](../../packages/tui/src/editor-component.ts)\n- [`../../tui/src/stdin-buffer.ts`](../../packages/tui/src/stdin-buffer.ts)\n- [`../../tui/src/components/loader.ts`](../../packages/tui/src/components/loader.ts)\n\n## التشغيل وتجميع شجرة المكونات\n\nينشئ `InteractiveMode` الكائن `TUI(new ProcessTerminal(), showHardwareCursor)` ويُنشئ حاويات دائمة:\n\n- `chatContainer`\n- `pendingMessagesContainer`\n- `statusContainer`\n- `todoContainer`\n- `statusLine`\n- `editorContainer` (يحتوي على `CustomEditor`)\n\nيقوم `init()` بتوصيل الشجرة بهذا الترتيب، ويركز المحرر، ويسجل معالجات المدخلات عبر `InputController`، ويبدأ TUI، ويطلب عرضًا مفروضًا.\n\nيقوم العرض المفروض (`requestRender(true)`) بإعادة تعيين ذاكرات التخزين المؤقت للأسطر السابقة وحجوزات المؤشر قبل إعادة الرسم.\n\n## دورة حياة الطرفية وتطبيع stdin\n\n`ProcessTerminal.start()`:\n\n1. يُمكّن الوضع الخام واللصق بين القوسين.\n2. يرفق معالج تغيير الحجم.\n3. ينشئ `StdinBuffer` لتقسيم أجزاء الإفلات الجزئية إلى تسلسلات كاملة.\n4. يستعلم عن دعم بروتوكول لوحة مفاتيح Kitty (`CSI ? u`)، ثم يُمكّن علامات البروتوكول إذا كانت مدعومة.\n5. على Windows، يحاول تمكين إدخال VT عبر علامات وضع `kernel32`.\n\nسلوك `StdinBuffer`:\n\n- يخزن مؤقتًا تسلسلات الإفلات المجزأة (CSI/OSC/DCS/APC/SS3).\n- يصدر `data` فقط عند اكتمال التسلسل أو مهلة التدفق.\n- يكتشف اللصق بين القوسين ويصدر حدث `paste` مع النص الملصق الخام.\n\nهذا يمنع أجزاء الإفلات الجزئية من أن تُفسَّر خطأً كضغطات مفاتيح عادية.\n\n## توجيه المدخلات ونموذج التركيز\n\nمسار المدخلات:\n\n`stdin -> ProcessTerminal -> StdinBuffer -> TUI.#handleInput -> focusedComponent.handleInput`\n\nتفاصيل التوجيه:\n\n1. يُشغّل TUI أولًا مستمعي المدخلات المسجلين (`addInputListener`)، مما يتيح سلوك الاستهلاك/التحويل.\n2. يتعامل TUI مع اختصار تصحيح الأخطاء العام (`shift+ctrl+d`) قبل إرسال المكونات.\n3. إذا كان المكوّن المركّز ينتمي إلى طبقة علوية مخفية/غير مرئية الآن، يُعيد TUI تعيين التركيز إلى الطبقة العلوية المرئية التالية أو التركيز المحفوظ قبل الطبقة العلوية.\n4. يتم تصفية أحداث تحرير المفاتيح ما لم يضبط المكوّن المركّز `wantsKeyRelease = true`.\n5. بعد الإرسال، يجدول TUI العرض.\n\nيبدّل `setFocus()` أيضًا `Focusable.focused`، الذي يتحكم في ما إذا كانت المكونات تصدر `CURSOR_MARKER` لوضع مؤشر الأجهزة.\n\n## تقسيم معالجة المفاتيح: المحرر مقابل وحدة التحكم\n\nيعترض `CustomEditor` أولًا التوليفات ذات الأولوية العالية (escape وctrl-c/d/z وctrl-v ومتغيرات ctrl-p وctrl-t وalt-up والمفاتيح المخصصة للامتداد) ويفوّض الباقي إلى سلوك `Editor` الأساسي (تحرير النص، والسجل، والإكمال التلقائي، وحركة المؤشر).\n\nثم يربط `InputController.setupKeyHandlers()` عمليات رد الاتصال بالمحرر بإجراءات الوضع:\n\n- الإلغاء/الخروج من الوضع عند `Escape`\n- الإيقاف عند `Ctrl+C` المزدوج أو `Ctrl+D` مع محرر فارغ\n- التعليق/الاستئناف عند `Ctrl+Z`\n- اختصارات أوامر الشرطة المائلة والمحدد\n- تبديل المتابعة/إلغاء الانتظار وتبديل التوسيع\n\nهذا يبقي تحليل المفاتيح/آليات المحرر في `packages/tui` ودلالات الوضع في وحدات تحكم coding-agent.\n\n## حلقة العرض واستراتيجية الفرق\n\nيتم تقليص `TUI.requestRender()` إلى عرض واحد لكل دورة باستخدام `process.nextTick`. تتدمج تغييرات الحالة المتعددة في نفس الدورة.\n\nخط أنابيب `#doRender()`:\n\n1. عرض شجرة المكونات الجذرية إلى `newLines`.\n2. تركيب الطبقات العلوية المرئية (إن وجدت).\n3. استخراج `CURSOR_MARKER` وإزالته من أسطر نافذة العرض المرئية.\n4. إلحاق لواحق إعادة تعيين المقطع لأسطر غير الصور.\n5. اختيار إعادة الرسم الكاملة مقابل الترقيع التفاضلي:\n   - الإطار الأول\n   - تغيير العرض\n   - الانكماش مع تمكين `clearOnShrink` وعدم وجود طبقات علوية\n   - التعديلات فوق منفذ العرض السابق\n6. للتحديثات التفاضلية، ترقيع نطاق السطر المتغير فقط ومسح الأسطر اللاحقة القديمة عند الحاجة.\n7. إعادة وضع مؤشر الأجهزة لدعم IME.\n\nتستخدم عمليات كتابة العرض وضع الإخراج المتزامن (`CSI ? 2026 h/l`) لتقليل الوميض/التشويه.\n\n## قيود أمان العرض\n\nفحوصات الأمان الحرجة في `TUI`:\n\n- يجب ألا تتجاوز الأسطر المعروضة غير الصور عرض الطرفية؛ يؤدي التجاوز إلى رمي خطأ وكتابة تشخيصات الأعطال.\n- يتضمن تركيب الطبقات العلوية اقتطاعًا دفاعيًا والتحقق من العرض بعد التركيب.\n- تفرض تغييرات العرض إعادة الرسم الكاملة لأن دلالات التفاف النص تتغير.\n- يتم تثبيت موضع المؤشر قبل الحركة.\n\nهذه القيود هي تطبيق بيئة التشغيل، وليس مجرد اتفاقيات.\n\n## معالجة تغيير الحجم\n\nأحداث تغيير الحجم مدفوعة بالأحداث من `ProcessTerminal` إلى `TUI.requestRender()`.\n\nالتأثيرات:\n\n- يُشغّل أي تغيير في العرض إعادة الرسم الكاملة.\n- يتجنب تتبع نافذة العرض/الأعلى (`#previousViewportTop` و`#maxLinesRendered`) الحسابات الرياضية النسبية غير الصالحة للمؤشر عند تغيير المحتوى أو حجم الطرفية.\n- يمكن أن تعتمد رؤية الطبقة العلوية على أبعاد الطرفية (`OverlayOptions.visible`)؛ يُصحَّح التركيز عندما تصبح الطبقات العلوية غير مرئية بعد تغيير الحجم.\n\n## البث والتحديثات التدريجية لواجهة المستخدم\n\nيشترك `EventController` في `AgentSessionEvent` ويحدّث واجهة المستخدم تدريجيًا:\n\n- `agent_start`: يبدأ المحمّل في `statusContainer`.\n- `message_start` للمساعد: ينشئ `streamingComponent` ويثبّته.\n- `message_update`: يحدّث محتوى المساعد المتدفق؛ وينشئ/يحدّث مكونات تنفيذ الأدوات عند ظهور استدعاءات الأداة.\n- `tool_execution_update/end`: يحدّث مكونات نتائج الأداة وحالة الاكتمال.\n- `message_end`: ينهي تدفق المساعد، ويعالج التعليقات التوضيحية المجهضة/الخاطئة، ويضع علامة على وسيطات الأداة المعلقة كاملةً عند التوقف الطبيعي.\n- `agent_end`: يوقف المحمّلات، ويمسح حالة التدفق العابرة، ويدفع تبديل النموذج المؤجل، ويصدر إشعار الاكتمال إذا كان في الخلفية.\n\nتجميع أداة القراءة ذو حالة مقصودة (`#lastReadGroup`) لدمج استدعاءات أداة القراءة المتتالية في كتلة مرئية واحدة حتى يحدث فاصل غير قراءة.\n\n## تنسيق الحالة والمحمّل\n\nملكية مسار الحالة:\n\n- يحتوي `statusContainer` على محمّلات عابرة (`loadingAnimation` و`autoCompactionLoader` و`retryLoader`).\n- يعرض `statusLine` مؤشرات الحالة/الخطافات/الخطة الدائمة ويشغّل تحديثات الحدود العلوية للمحرر.\n\nسلوك المحمّل:\n\n- يحدّث `Loader` كل 80 مللي ثانية عبر الفاصل الزمني ويطلب العرض في كل إطار.\n- تُستبدل معالجات Escape مؤقتًا أثناء الضغط التلقائي وإعادة المحاولة التلقائية لإلغاء تلك العمليات.\n- في مسارات الإنهاء/الإلغاء، تستعيد وحدات التحكم معالجات Escape السابقة وتوقف/تمسح مكونات المحمّل.\n\n## انتقالات الوضع والتشغيل في الخلفية\n\n### أوضاع إدخال Bash/Python\n\nتبدّل بادئات نص المدخلات علامات وضع حدود المحرر:\n\n- `!` -> وضع bash\n- `$` (بادئة غير حرفية قالب) -> وضع python\n\nيخرج Escape من الوضع غير النشط بمسح نص المحرر واستعادة لون الحدود؛ عندما يكون التنفيذ نشطًا، يجهض Escape المهمة الجارية بدلًا من ذلك.\n\n### وضع الخطة\n\nيتتبع `InteractiveMode` علامات وضع الخطة وحالة سطر الحالة والأدوات النشطة وتبديل النموذج. يُحدّث الدخول/الخروج إدخالات وضع الجلسة وحالة الحالة/واجهة المستخدم، بما في ذلك تبديل النموذج المؤجل إذا كان البث نشطًا.\n\n### التعليق/الاستئناف (`Ctrl+Z`)\n\n`InputController.handleCtrlZ()`:\n\n1. يسجّل معالج `SIGCONT` لمرة واحدة لإعادة تشغيل TUI وفرض العرض.\n2. يوقف TUI قبل التعليق.\n3. يرسل `SIGTSTP` إلى مجموعة العملية.\n\n### وضع الخلفية (`/background` أو `/bg`)\n\n`handleBackgroundCommand()`:\n\n- يرفض عند الخمول.\n- يبدّل سياق واجهة مستخدم الأداة إلى غير تفاعلي (`hasUI=false`) حتى تفشل أدوات واجهة المستخدم التفاعلية بسرعة.\n- يوقف المحمّلات/سطر الحالة وإلغاء الاشتراك في معالج الحدث الأمامي.\n- يشترك في معالج أحداث الخلفية (ينتظر أساسًا `agent_end`).\n- يوقف TUI ويرسل `SIGTSTP` (مسار التحكم في مهام POSIX).\n\nعند `agent_end` في الخلفية مع عدم وجود عمل في قائمة الانتظار، ترسل وحدة التحكم إشعار الاكتمال وتُغلق.\n\n## مسارات الإلغاء\n\nمدخلات الإلغاء الأساسية:\n\n- `Escape` أثناء محمّل التدفق النشط: يستعيد الرسائل المنتظرة إلى المحرر ويجهض الوكيل.\n- `Escape` أثناء تنفيذ bash/python: يجهض الأمر الجاري.\n- `Escape` أثناء الضغط التلقائي/إعادة المحاولة: يستدعي أساليب إجهاض مخصصة من خلال معالجات escape المؤقتة.\n- `Ctrl+C` ضغطة واحدة: مسح المحرر؛ ضغطتان في غضون 500 مللي ثانية: إيقاف التشغيل.\n\nالإلغاء مشروط بالحالة؛ يمكن أن يعني نفس المفتاح الإجهاض أو الخروج من الوضع أو تشغيل المحدد أو عدم الفعل وفقًا لحالة بيئة التشغيل.\n\n## السلوك المدفوع بالأحداث مقابل السلوك المقيّد\n\nالتحديثات المدفوعة بالأحداث:\n\n- أحداث جلسة الوكيل (`EventController`)\n- عمليات رد اتصال مدخلات المفاتيح (`InputController`)\n- رد اتصال تغيير حجم الطرفية\n- مراقبو السمة/الفرع في `InteractiveMode`\n\nالمسارات المقيّدة/المخفّفة:\n\n- يتم تقليص عرض TUI إلى الدورة (`requestRender` التدمجي).\n- الرسوم المتحركة للمحمّل ذات فترة ثابتة (80 مللي ثانية)، وكل إطار يطلب عرضًا.\n- تحديثات الإكمال التلقائي للمحرر (داخل `Editor`) تستخدم مؤقتات تخفيف، مما يقلل من تراجع إعادة الحساب أثناء الكتابة.\n\nلذا تمزج بيئة التشغيل انتقالات الحالة المدفوعة بالأحداث مع إيقاع عرض مقيّد للحفاظ على الاستجابة التفاعلية دون عواصف إعادة الرسم.\n",
	"ar/tui/tui.md": "---\ntitle: تكامل TUI للإضافات والأدوات المخصصة\ndescription: عقد تكامل TUI للإضافات والأدوات المخصصة ومعالجات العرض المخصصة.\nsidebar:\n  order: 1\n  label: تكامل الإضافات\ni18n:\n  sourceHash: 47f8f2b2045e\n  translator: machine\n---\n\n# تكامل TUI للإضافات والأدوات المخصصة\n\nتغطي هذه الوثيقة عقد TUI **الحالي** المستخدم من قِبَل `packages/coding-agent` و`packages/tui` لواجهة مستخدم الإضافات، وواجهة مستخدم الأدوات المخصصة، ومعالجات العرض المخصصة.\n\n## ما هو هذا النظام الفرعي\n\nيتكون وقت التشغيل من طبقتين:\n\n- **محرك العرض (`packages/tui`)**: معالج طرفية تفاضلي، إرسال المدخلات، التركيز، الطبقات العلوية، وضع المؤشر.\n- **طبقة التكامل (`packages/coding-agent`)**: تركيب مكونات الإضافات/الأدوات المخصصة، وربط اختصارات المفاتيح/السمة، واستعادة حالة المحرر.\n\n## سلوك وقت التشغيل حسب الوضع\n\n| الوضع | توافر `ctx.ui.custom(...)` | ملاحظات |\n| --- | --- | --- |\n| TUI تفاعلي | مدعوم | يُركَّب المكوّن في منطقة المحرر، ويُركَّز عليه، ويجب أن يستدعي `done(result)` للحل. |\n| خلفي/بدون رأس | غير تفاعلي | سياق واجهة المستخدم بلا تأثير (`hasUI === false`). |\n| وضع RPC | غير مدعوم | يُعيد `custom()` القيمة `Promise<never>` ولا يركّب مكونات TUI. |\n\nإذا كانت إضافتك/أداتك قادرة على العمل في الوضع غير التفاعلي، استخدم `ctx.hasUI` / `pi.hasUI` كحارس.\n\n## عقد المكوّن الأساسي (`@f5-sales-demo/pi-tui`)\n\nيُعرِّف `packages/tui/src/tui.ts`:\n\n```ts\nexport interface Component {\n  render(width: number): string[];\n  handleInput?(data: string): void;\n  wantsKeyRelease?: boolean;\n  invalidate(): void;\n}\n```\n\n`Focusable` منفصل:\n\n```ts\nexport interface Focusable {\n  focused: boolean;\n}\n```\n\nيستخدم سلوك المؤشر `CURSOR_MARKER` (وليس `getCursorPosition`). تُصدر المكونات المُركَّزة العلامة في النص المعروض؛ يستخرجها `TUI` ويضع مؤشر الأجهزة.\n\n## قيود العرض (أمان الطرفية)\n\nيجب أن يكون مخرج `render(width)` آمناً للطرفية:\n\n1. **لا تتجاوز `width` في أي سطر أبداً**. يُطلق المعالج استثناءً إذا تجاوز سطر غير صوري الحد.\n2. **قِس العرض المرئي**، وليس طول السلسلة: استخدم `visibleWidth()`.\n3. **اقطع/التف النص المدرك لـ ANSI** باستخدام `truncateToWidth()` / `wrapTextWithAnsi()`.\n4. **عقّم الجداول/المحتوى** من المصادر الخارجية باستخدام `replaceTabs()` (ومعقّمات المستوى الأعلى في مسارات عرض coding-agent).\n\nالنمط الأدنى:\n\n```ts\nimport { replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\n\nrender(width: number): string[] {\n  return this.lines.map(line => truncateToWidth(replaceTabs(line), width));\n}\n```\n\n## معالجة المدخلات واختصارات المفاتيح\n\n### مطابقة المفاتيح الخام\n\nاستخدم `matchesKey(data, \"...\")` لمفاتيح التنقل والتركيبات.\n\n### احترام اختصارات مفاتيح التطبيق التي يضبطها المستخدم\n\nتستقبل مصانع واجهة مستخدم الإضافة `KeybindingsManager` (في الوضع التفاعلي) حتى تتمكن من مراعاة الإجراءات المعيّنة بدلاً من ترميز المفاتيح:\n\n```ts\nif (keybindings.matches(data, \"interrupt\")) {\n  done(undefined);\n  return;\n}\n```\n\n### أحداث تحرير المفاتيح/التكرار\n\nتُصفَّى أحداث تحرير المفاتيح ما لم يضبط مكوّنك:\n\n```ts\nwantsKeyRelease = true;\n```\n\nعندها استخدم `isKeyRelease()` / `isKeyRepeat()` عند الحاجة.\n\n## التركيز والطبقات العلوية والمؤشر\n\n- يوجّه `TUI.setFocus(component)` المدخلات إلى ذلك المكوّن.\n- توجد واجهات برمجية للطبقات العلوية في `TUI` (`showOverlay`، `OverlayHandle`)، لكن تركيب `ctx.ui.custom` في الوضع التفاعلي يستبدل منطقة مكوّن المحرر مباشرةً حالياً.\n- يوجد الخيار `custom(..., options?: { overlay?: boolean })` في أنواع الإضافات؛ يتجاهل تركيب الإضافات التفاعلي هذا الخيار حالياً.\n\n## نقاط التركيب وعقود الإرجاع\n\n## 1) واجهة مستخدم الإضافة (`ExtensionUIContext`)\n\nالتوقيع الحالي (`extensibility/extensions/types.ts`):\n\n```ts\ncustom<T>(\n  factory: (\n    tui: TUI,\n    theme: Theme,\n    keybindings: KeybindingsManager,\n    done: (result: T) => void,\n  ) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n  options?: { overlay?: boolean },\n): Promise<T>\n```\n\nالسلوك في الوضع التفاعلي (`extension-ui-controller.ts`):\n\n- يحفظ نص المحرر.\n- يستبدل مكوّن المحرر بمكوّنك.\n- يُركّز على مكوّنك.\n- عند `done(result)`: يستدعي `component.dispose?.()` ويستعيد المحرر والنص، ويُركّز على المحرر، ويحل الوعد.\n\nلذا فإن `done(...)` إلزامي للإتمام.\n\n## 2) سياق واجهة مستخدم الخطاف/الأداة المخصصة (كتابة قديمة)\n\n`HookUIContext.custom` مكتوب بصيغة `(tui, theme, done)` في أنواع الخطاف/الأداة المخصصة.\nالاستجابة التفاعلية الأساسية تستدعي المصانع بـ `(tui, theme, keybindings, done)`. يمكن لمستهلكي JS استخدام الوسيطة الإضافية؛ لا تزال التوافقية على مستوى الأنواع تعكس التوقيع القديم المكوّن من 3 وسيطات.\n\nتستخدم الأدوات المخصصة عادةً نقطة دخول واجهة المستخدم ذاتها عبر كائن `pi.ui` المحدد للمصنع، ثم تُعيد القيمة المحددة في محتوى الأداة العادي:\n\n```ts\nasync execute(toolCallId, params, onUpdate, ctx, signal) {\n  if (!pi.hasUI) {\n    return { content: [{ type: \"text\", text: \"UI unavailable\" }] };\n  }\n\n  const picked = await pi.ui.custom<string | undefined>((tui, theme, done) => {\n    const component = new MyPickerComponent(done, signal);\n    return component;\n  });\n\n  return { content: [{ type: \"text\", text: picked ? `Picked: ${picked}` : \"Cancelled\" }] };\n}\n```\n\n## 3) معالجات عرض استدعاء/نتيجة الأداة المخصصة\n\nيمكن للأدوات المخصصة وأدوات الإضافات إرجاع المكونات من:\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\nيتضمن `options` حالياً:\n\n- `expanded: boolean`\n- `isPartial: boolean`\n- `spinnerFrame?: number`\n\nتُركَّب هذه المعالجات بواسطة `ToolExecutionComponent`.\n\n## دورة الحياة والإلغاء\n\n- `dispose()` اختياري على مستوى الأنواع، لكن ينبغي تنفيذه عندما تمتلك مؤقتات أو عمليات فرعية أو مراقبين أو مقابس أو طبقات علوية.\n- ينبغي استدعاء `done(...)` مرة واحدة بالضبط من تدفق مكوّنك.\n- لواجهة مستخدم طويلة الأمد قابلة للإلغاء، اقرن `CancellableLoader` بـ `AbortSignal` واستدع `done(...)` من `onAbort`.\n\nمثال على نمط الإلغاء:\n\n```ts\nconst loader = new CancellableLoader(tui, theme.fg(\"accent\"), theme.fg(\"muted\"), \"Working...\");\nloader.onAbort = () => done(undefined);\nvoid doWork(loader.signal).then(result => done(result));\nreturn loader;\n```\n\n## مثال واقعي لمكوّن مخصص (أمر إضافة)\n\n```ts\nimport type { Component } from \"@f5-sales-demo/pi-tui\";\nimport { SelectList, matchesKey, replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\nimport { getSelectListTheme, type ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nclass Picker implements Component {\n  list: SelectList;\n  keybindings: any;\n  done: (value: string | undefined) => void;\n\n  constructor(\n    items: Array<{ value: string; label: string }>,\n    keybindings: any,\n    done: (value: string | undefined) => void,\n  ) {\n    this.list = new SelectList(items, 8, getSelectListTheme());\n    this.keybindings = keybindings;\n    this.done = done;\n    this.list.onSelect = item => this.done(item.value);\n    this.list.onCancel = () => this.done(undefined);\n  }\n\n  handleInput(data: string): void {\n    if (this.keybindings.matches(data, \"interrupt\")) {\n      this.done(undefined);\n      return;\n    }\n    this.list.handleInput(data);\n  }\n\n  render(width: number): string[] {\n    return this.list.render(width).map(line => truncateToWidth(replaceTabs(line), width));\n  }\n\n  invalidate(): void {\n    this.list.invalidate();\n  }\n}\n\nexport default function extension(pi: ExtensionAPI): void {\n  pi.registerCommand(\"pick-model\", {\n    description: \"Pick a model profile\",\n    handler: async (_args, ctx) => {\n      if (!ctx.hasUI) return;\n\n      const selected = await ctx.ui.custom<string | undefined>((tui, theme, keybindings, done) => {\n        const items = [\n          { value: \"fast\", label: theme.fg(\"accent\", \"Fast\") },\n          { value: \"balanced\", label: \"Balanced\" },\n          { value: \"quality\", label: \"Quality\" },\n        ];\n        return new Picker(items, keybindings, done);\n      });\n\n      if (selected) ctx.ui.notify(`Selected profile: ${selected}`, \"info\");\n    },\n  });\n}\n```\n\n## ملفات التنفيذ الرئيسية\n\n- `packages/tui/src/tui.ts` — `Component`، `Focusable`، علامة المؤشر، التركيز، الطبقة العلوية، إرسال المدخلات.\n- `packages/tui/src/utils.ts` — أساسيات العرض/القطع/التعقيم.\n- `packages/tui/src/keys.ts` / `keybindings.ts` — تحليل المفاتيح وتعيين الإجراءات القابلة للضبط.\n- `packages/coding-agent/src/modes/controllers/extension-ui-controller.ts` — التركيب/الفك التفاعلي لواجهة مستخدم الإضافة/الخطاف/الأداة المخصصة.\n- `packages/coding-agent/src/extensibility/extensions/types.ts` — عقود واجهة مستخدم الإضافة ومعالج العرض.\n- `packages/coding-agent/src/extensibility/hooks/types.ts` — عقد واجهة مستخدم الخطاف (التوقيع المخصص القديم).\n- `packages/coding-agent/src/extensibility/custom-tools/types.ts` — عقود تنفيذ/عرض الأداة المخصصة.\n- `packages/coding-agent/src/modes/components/tool-execution.ts` — تركيب مكونات `renderCall`/`renderResult` وخيارات الحالة الجزئية.\n- `packages/coding-agent/src/tools/context.ts` — نشر سياق واجهة مستخدم الأداة (`hasUI`، `ui`).\n",
	"de/configuration/blob-artifact-architecture.md": "---\ntitle: Blob- und Artefakt-Speicherarchitektur\ndescription: >-\n  Content-adressierbarer Blob-Speicher und Artefakt-Registry für Session-Medien,\n  Screenshots und Tool-Ausgaben.\nsidebar:\n  order: 7\n  label: Blob- & Artefaktspeicher\ni18n:\n  sourceHash: 70d255f48d5b\n  translator: machine\n---\n\n# Blob- und Artefakt-Speicherarchitektur\n\nDieses Dokument beschreibt, wie coding-agent große/binäre Nutzdaten außerhalb der Session-JSONL speichert, wie gekürzte Tool-Ausgaben persistiert werden und wie interne URLs (`artifact://`, `agent://`) auf gespeicherte Daten zurückaufgelöst werden.\n\n## Warum zwei Speichersysteme existieren\n\nDie Laufzeitumgebung verwendet zwei unterschiedliche Persistenzmechanismen für verschiedene Datenformen:\n\n- **Content-adressierte Blobs** (`blob:sha256:<hash>`): globaler, binärorientierter Speicher zur Externalisierung großer Image-Base64-Nutzdaten aus persistierten Session-Einträgen.\n- **Session-gebundene Artefakte** (Dateien unter `<sessionFile-ohne-.jsonl>/`): sitzungsbezogene Textdateien für vollständige Tool-Ausgaben und Subagent-Ausgaben.\n\nSie sind bewusst getrennt:\n\n- Blob-Speicher optimiert Deduplizierung und stabile Referenzen durch Content-Hash,\n- Artefakt-Speicher optimiert Append-only-Session-Tooling und Abruf durch Menschen/Tools über lokale IDs.\n\n## Speichergrenzen und Verzeichnisstruktur auf der Festplatte\n\n## Blob-Store-Grenze (global)\n\n`SessionManager` erstellt `BlobStore(getBlobsDir())`, sodass Blob-Dateien in einem gemeinsamen globalen Blob-Verzeichnis liegen (nicht in einem Session-Ordner).\n\nBlob-Dateinamensgebung:\n\n- Dateipfad: `<blobsDir>/<sha256-hex>`\n- keine Erweiterung\n- in Einträgen gespeicherter Referenzstring: `blob:sha256:<sha256-hex>`\n\nImplikationen:\n\n- gleicher binärer Inhalt über Sessions hinweg wird zum selben Hash/Pfad aufgelöst,\n- Schreibvorgänge sind auf Inhaltsebene idempotent,\n- Blobs können jede einzelne Session-Datei überdauern.\n\n## Artefakt-Grenze (session-lokal)\n\n`ArtifactManager` leitet das Artefaktverzeichnis vom Session-Dateipfad ab:\n\n- Session-Datei: `.../<timestamp>_<sessionId>.jsonl`\n- Artefaktverzeichnis: `.../<timestamp>_<sessionId>/` (`.jsonl` entfernt)\n\nArtefakttypen teilen sich dieses Verzeichnis:\n\n- gekürzte Tool-Ausgabedateien: `<numericId>.<toolType>.log` (für `artifact://`)\n- Subagent-Ausgabedateien: `<outputId>.md` (für `agent://`)\n\n## ID- und Namenvergabe-Schemata\n\n## Blob-IDs: Content-Hash\n\n`BlobStore.put()` berechnet SHA-256 über die rohen Binärbytes und gibt zurück:\n\n- `hash`: Hex-Digest,\n- `path`: `<blobsDir>/<hash>`,\n- `ref`: `blob:sha256:<hash>`.\n\nEs wird kein session-lokaler Zähler verwendet.\n\n## Artefakt-IDs: session-lokale monotone Ganzzahl\n\n`ArtifactManager` durchsucht beim ersten Zugriff vorhandene `*.log`-Artefaktdateien, um die maximale vorhandene numerische ID zu finden, und setzt `nextId = max + 1`.\n\nVergabeverhalten:\n\n- Dateiformat: `{id}.{toolType}.log`\n- IDs sind sequenzielle Strings (`\"0\"`, `\"1\"`, ...)\n- Wiederaufnahme überschreibt keine vorhandenen Artefakte, da der Scan vor der Vergabe stattfindet.\n\nWenn das Artefaktverzeichnis fehlt, ergibt der Scan eine leere Liste und die Vergabe beginnt bei `0`.\n\n## Agent-Ausgabe-IDs (`agent://`)\n\n`AgentOutputManager` vergibt IDs für Subagent-Ausgaben als `<index>-<requestedId>` (optional verschachtelt unter einem Eltern-Präfix, z.B. `0-Parent.1-Child`). Es durchsucht vorhandene `.md`-Dateien bei der Initialisierung, um bei der Wiederaufnahme beim nächsten Index fortzufahren.\n\n## Persistenz-Datenfluss\n\n## 1) Umschreibepfad bei der Session-Eintrags-Persistierung\n\nBevor Session-Einträge geschrieben werden (`#rewriteFile` / inkrementelle Persistierung), ruft `SessionManager` `prepareEntryForPersistence()` (über `truncateForPersistence`) auf.\n\nWichtige Verhaltensweisen:\n\n1. **Kürzung langer Strings**: Übergroße Strings werden abgeschnitten und mit `\"[Session persistence truncated large content]\"` suffixiert.\n2. **Entfernung transienter Felder**: `partialJson` und `jsonlEvents` werden aus persistierten Einträgen entfernt.\n3. **Bild-Externalisierung in Blobs**:\n   - gilt nur für Bildblöcke in `content`-Arrays,\n   - nur wenn `data` nicht bereits eine Blob-Referenz ist,\n   - nur wenn die Base64-Länge mindestens den Schwellenwert erreicht (`BLOB_EXTERNALIZE_THRESHOLD = 1024`),\n   - ersetzt Inline-Base64 durch `blob:sha256:<hash>`.\n\nDies hält die Session-JSONL kompakt und bewahrt gleichzeitig die Wiederherstellbarkeit.\n\n## 2) Rehydrierungspfad beim Session-Laden\n\nBeim Öffnen einer Session (`setSessionFile`) führt `SessionManager` nach den Migrationen `resolveBlobRefsInEntries()` aus.\n\nFür jeden Message-/Custom-Message-Bildblock mit `blob:sha256:<hash>`:\n\n- liest Blob-Bytes aus dem Blob-Store,\n- konvertiert Bytes zurück in Base64,\n- mutiert den In-Memory-Eintrag zu Inline-Base64 für Laufzeit-Konsumenten.\n\nWenn der Blob fehlt:\n\n- `resolveImageData()` protokolliert eine Warnung,\n- gibt den ursprünglichen Referenzstring unverändert zurück,\n- das Laden wird fortgesetzt (kein harter Absturz).\n\n## 3) Tool-Ausgabe-Spill-/Kürzungspfad\n\n`OutputSink` steuert die Streaming-Ausgabe in Bash/Python/SSH und verwandten Executoren.\n\nVerhalten:\n\n1. Jeder Chunk wird bereinigt und an den In-Memory-Tail-Buffer angehängt.\n2. Wenn die In-Memory-Bytes den Spill-Schwellenwert überschreiten (`DEFAULT_MAX_BYTES`, 50KB), markiert der Sink die Ausgabe als gekürzt.\n3. Wenn ein Artefaktpfad verfügbar ist, öffnet der Sink einen File-Writer und schreibt:\n   - vorhandenen gepufferten Inhalt einmalig,\n   - alle nachfolgenden Chunks.\n4. Der In-Memory-Buffer wird immer auf das Tail-Fenster für die Anzeige getrimmt.\n5. `dump()` gibt eine Zusammenfassung einschließlich `artifactId` nur zurück, wenn der File-Sink erfolgreich erstellt wurde.\n\nPraktischer Effekt:\n\n- UI/Tool-Rückgabe zeigt den gekürzten Tail,\n- die vollständige Ausgabe wird in der Artefaktdatei gespeichert und als `artifact://<id>` referenziert.\n\nWenn die Erstellung des File-Sinks fehlschlägt (I/O-Fehler, fehlender Pfad, etc.), fällt der Sink stillschweigend auf reine In-Memory-Kürzung zurück; die vollständige Ausgabe wird nicht persistiert.\n\n## URL-Zugriffsmodell\n\n## `blob:`-Referenzen\n\n`blob:sha256:<hash>` ist eine Persistenz-Referenz innerhalb von Session-Eintrags-Nutzdaten, kein internes URL-Schema, das vom Router verarbeitet wird. Die Auflösung erfolgt durch `SessionManager` während des Session-Ladens.\n\n## `artifact://<id>`\n\nVerarbeitet durch `ArtifactProtocolHandler`:\n\n- erfordert ein aktives Session-Artefaktverzeichnis,\n- ID muss numerisch sein,\n- Auflösung durch Matching des Dateinamenpräfixes `<id>.`,\n- gibt Rohtext (`text/plain`) aus der passenden `.log`-Datei zurück,\n- bei fehlendem Eintrag enthält der Fehler eine Liste verfügbarer Artefakt-IDs.\n\nVerhalten bei fehlendem Verzeichnis:\n\n- wenn das Artefaktverzeichnis nicht existiert, wird `No artifacts directory found` geworfen.\n\n## `agent://<id>`\n\nVerarbeitet durch `AgentProtocolHandler` über `<artifactsDir>/<id>.md`:\n\n- einfache Form gibt Markdown-Text zurück,\n- `/path`- oder `?q=`-Formen führen JSON-Extraktion durch,\n- Pfad- und Query-Extraktion können nicht kombiniert werden,\n- wenn Extraktion angefordert wird, muss der Dateiinhalt als JSON parsebar sein.\n\nVerhalten bei fehlendem Verzeichnis:\n\n- wirft `No artifacts directory found`.\n\nVerhalten bei fehlender Ausgabe:\n\n- wirft `Not found: <id>` mit verfügbaren IDs aus vorhandenen `.md`-Dateien.\n\nRead-Tool-Integration:\n\n- `read` unterstützt Offset/Limit-Paginierung für Nicht-Extraktions-Lesevorgänge interner URLs,\n- lehnt `offset/limit` ab, wenn `agent://`-Extraktion verwendet wird.\n\n## Wiederaufnahme-, Fork- und Verschiebe-Semantik\n\n## Wiederaufnahme\n\n- `ArtifactManager` durchsucht vorhandene `{id}.*.log`-Dateien bei der ersten Vergabe und setzt die Nummerierung fort.\n- `AgentOutputManager` durchsucht vorhandene `.md`-Ausgabe-IDs und setzt die Nummerierung fort.\n- `SessionManager` rehydriert Blob-Referenzen zu Base64 beim Laden.\n\n## Fork\n\n`SessionManager.fork()` erstellt eine neue Session-Datei mit neuer Session-ID und `parentSession`-Verknüpfung und gibt dann alte/neue Dateipfade zurück. Das Kopieren der Artefakte wird von `AgentSession.fork()` übernommen:\n\n- versucht rekursives Kopieren des alten Artefaktverzeichnisses in das neue Artefaktverzeichnis,\n- fehlendes altes Verzeichnis wird toleriert,\n- Nicht-ENOENT-Kopierfehler werden als Warnungen protokolliert und der Fork wird dennoch abgeschlossen.\n\nID-Implikationen nach dem Fork:\n\n- wenn das Kopieren erfolgreich war, setzen die Artefaktzähler in der neuen Session nach der maximalen kopierten ID fort,\n- wenn das Kopieren fehlgeschlagen/übersprungen wurde, beginnen die Artefakt-IDs der neuen Session bei `0`.\n\nBlob-Implikationen nach dem Fork:\n\n- Blobs sind global und content-adressiert, daher ist kein Kopieren des Blob-Verzeichnisses erforderlich.\n\n## Verschieben in ein neues cwd\n\n`SessionManager.moveTo()` benennt sowohl die Session-Datei als auch das Artefaktverzeichnis in das neue Standard-Session-Verzeichnis um, mit Rollback-Logik falls ein späterer Schritt fehlschlägt. Dies bewahrt die Artefakt-Identität bei gleichzeitiger Verlagerung des Session-Scopes.\n\n## Fehlerbehandlung und Fallback-Pfade\n\n| Fall | Verhalten |\n| --- | --- |\n| Blob-Datei fehlt bei Rehydrierung | Warnung und Beibehaltung des `blob:sha256:`-Referenzstrings im Speicher |\n| Blob-Lese-ENOENT über `BlobStore.get` | Gibt `null` zurück |\n| Artefaktverzeichnis fehlt (`ArtifactManager.listFiles`) | Gibt leere Liste zurück (Vergabe kann neu beginnen) |\n| Artefaktverzeichnis fehlt (`artifact://` / `agent://`) | Wirft explizit `No artifacts directory found` |\n| Artefakt-ID nicht gefunden | Wirft Fehler mit Auflistung verfügbarer IDs |\n| OutputSink-Artefakt-Writer-Initialisierung schlägt fehl | Fährt mit Tail-only-Kürzung fort (kein Vollausgabe-Artefakt) |\n| Keine Session-Datei (einige Task-Pfade) | Task-Tool fällt auf temporäres Artefaktverzeichnis für Subagent-Ausgaben zurück |\n\n## Binäre Blob-Externalisierung vs. Text-Ausgabe-Artefakte\n\n- **Blob-Externalisierung** ist für binäre Bild-Nutzdaten innerhalb persistierter Session-Eintrags-Inhalte; sie ersetzt Inline-Base64 in JSONL durch stabile Content-Referenzen.\n- **Artefakte** sind Klartextdateien für Ausführungsausgaben und Subagent-Ausgaben; sie sind über session-lokale IDs durch interne URLs adressierbar.\n\nDie beiden Systeme überschneiden sich nur indirekt (beide reduzieren die Session-JSONL-Aufblähung), haben aber unterschiedliche Identitäts-, Lebensdauer- und Abrufpfade.\n\n## Implementierungsdateien\n\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts) — Blob-Referenzformat, Hashing, Put/Get, Externalisierungs-/Auflösungs-Hilfsfunktionen.\n- [`src/session/artifacts.ts`](../../packages/coding-agent/src/session/artifacts.ts) — Session-Artefaktverzeichnismodell und numerische Artefakt-ID-Vergabe.\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — `OutputSink`-Kürzungs-/Spill-to-File-Verhalten und Zusammenfassungs-Metadaten.\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts) — Persistenz-Transformationen, Blob-Rehydrierung beim Laden, Session-Fork-/Verschiebe-Interaktionen.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — Artefaktverzeichnis-Kopie beim interaktiven Fork.\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — Tool-Artefakt-Manager-Bootstrap und pro-Tool-Artefaktpfad-Vergabe.\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — `artifact://`-Resolver.\n- [`src/internal-urls/agent-protocol.ts`](../../packages/coding-agent/src/internal-urls/agent-protocol.ts) — `agent://`-Resolver + JSON-Extraktion.\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — Internes URL-Router-Wiring und Artefaktverzeichnis-Resolver.\n- [`src/task/output-manager.ts`](../../packages/coding-agent/src/task/output-manager.ts) — Session-gebundene Agent-Ausgabe-ID-Vergabe für `agent://`.\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — Subagent-Ausgabe-Artefakt-Schreibvorgänge (`<id>.md`) und temporärer Artefaktverzeichnis-Fallback.\n",
	"de/configuration/config-usage.md": "---\ntitle: Konfigurationserkennung und -auflösung\ndescription: >-\n  Wie xcsh Konfigurationen aus Projekt-, Benutzer- und\n  Unternehmens-Stammverzeichnissen erkennt, auflöst und schichtet.\nsidebar:\n  order: 1\n  label: Konfiguration\ni18n:\n  sourceHash: e38bd9792499\n  translator: machine\n---\n\n# Konfigurationserkennung und -auflösung\n\nDieses Dokument beschreibt, wie der Coding-Agent Konfigurationen heute auflöst: welche Stammverzeichnisse gescannt werden, wie die Prioritätsreihenfolge funktioniert und wie aufgelöste Konfigurationen von Settings, Skills, Hooks, Tools und Erweiterungen konsumiert werden.\n\n## Geltungsbereich\n\nPrimäre Implementierung:\n\n- `src/config.ts`\n- `src/config/settings.ts`\n- `src/config/settings-schema.ts`\n- `src/discovery/builtin.ts`\n- `src/discovery/helpers.ts`\n\nWichtige Integrationspunkte:\n\n- `src/capability/index.ts`\n- `src/discovery/index.ts`\n- `src/extensibility/skills.ts`\n- `src/extensibility/hooks/loader.ts`\n- `src/extensibility/custom-tools/loader.ts`\n- `src/extensibility/extensions/loader.ts`\n\n---\n\n## Auflösungsablauf (visuell)\n\n```text\n         Config roots (ordered)\n┌───────────────────────────────────────┐\n│ 1) ~/.xcsh/agent + <cwd>/.xcsh          │\n│ 2) ~/.claude   + <cwd>/.claude        │\n│ 3) ~/.codex    + <cwd>/.codex         │\n│ 4) ~/.gemini   + <cwd>/.gemini        │\n└───────────────────────────────────────┘\n                    │\n                    ▼\n        config.ts helper resolution\n  (getConfigDirs/findConfigFile/findNearest...)\n                    │\n                    ▼\n       capability providers enumerate items\n (native, claude, codex, gemini, agents, etc.)\n                    │\n                    ▼\n      priority sort + per-capability dedup\n                    │\n                    ▼\n          subsystem-specific consumption\n   (settings, skills, hooks, tools, extensions)\n```\n\n## 1) Konfigurationsstammverzeichnisse und Quellreihenfolge\n\n## Kanonische Stammverzeichnisse\n\n`src/config.ts` definiert eine feste Quellprioritätsliste:\n\n1. `.xcsh` (nativ)\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nBenutzerebenen-Basisverzeichnisse:\n\n- `~/.xcsh/agent`\n- `~/.claude`\n- `~/.codex`\n- `~/.gemini`\n\nProjektebenen-Basisverzeichnisse:\n\n- `<cwd>/.xcsh`\n- `<cwd>/.claude`\n- `<cwd>/.codex`\n- `<cwd>/.gemini`\n\n`CONFIG_DIR_NAME` ist `.xcsh` (`packages/utils/src/dirs.ts`).\n\n## Wichtige Einschränkung\n\nDie generischen Hilfsfunktionen in `src/config.ts` schließen `.pi` in der Quellerkennungsreihenfolge **nicht** ein.\n\n---\n\n## 2) Kernerkennungs-Hilfsfunktionen (`src/config.ts`)\n\n## `getConfigDirs(subpath, options)`\n\nGibt geordnete Einträge zurück:\n\n- Benutzerebenen-Einträge zuerst (nach Quellpriorität)\n- Dann Projektebenen-Einträge (nach derselben Quellpriorität)\n\nOptionen:\n\n- `user` (Standard `true`)\n- `project` (Standard `true`)\n- `cwd` (Standard `getProjectDir()`)\n- `existingOnly` (Standard `false`)\n\nDiese API wird für verzeichnisbasierte Konfigurationssuchen verwendet (Befehle, Hooks, Tools, Agenten usw.).\n\n## `findConfigFile(subpath, options)` / `findConfigFileWithMeta(...)`\n\nSucht nach der ersten existierenden Datei über die geordneten Basisverzeichnisse hinweg und gibt den ersten Treffer zurück (nur Pfad oder Pfad+Metadaten).\n\n## `findAllNearestProjectConfigDirs(subpath, cwd)`\n\nDurchläuft übergeordnete Verzeichnisse nach oben und gibt das **nächstgelegene existierende Verzeichnis pro Quellbasis** zurück (`.xcsh`, `.claude`, `.codex`, `.gemini`), sortiert die Ergebnisse dann nach Quellpriorität.\n\nVerwenden Sie dies, wenn Projektkonfiguration von übergeordneten Verzeichnissen geerbt werden soll (Monorepo-/verschachteltes Workspace-Verhalten).\n\n---\n\n## 3) Datei-Konfigurations-Wrapper (`ConfigFile<T>` in `src/config.ts`)\n\n`ConfigFile<T>` ist der schemavalidierte Lader für einzelne Konfigurationsdateien.\n\nUnterstützte Formate:\n\n- `.yml` / `.yaml`\n- `.json` / `.jsonc`\n\nVerhalten:\n\n- Validiert geparste Daten mit AJV gegen ein bereitgestelltes TypeBox-Schema.\n- Speichert das Ladeergebnis im Cache bis `invalidate()`.\n- Gibt ein Drei-Zustands-Ergebnis über `tryLoad()` zurück:\n  - `ok`\n  - `not-found`\n  - `error` (`ConfigError` mit Schema-/Parse-Kontext)\n\nLegacy-Migration wird weiterhin unterstützt:\n\n- Wenn der Zielpfad `.yml`/`.yaml` ist, wird eine benachbarte `.json`-Datei einmalig automatisch migriert (`migrateJsonToYml`).\n\n---\n\n## 4) Settings-Auflösungsmodell (`src/config/settings.ts`)\n\nDas Laufzeit-Settings-Modell ist geschichtet:\n\n1. Globale Einstellungen: `~/.xcsh/agent/config.yml`\n2. Projekteinstellungen: erkannt über die Settings-Capability (`settings.json` von Providern)\n3. Laufzeit-Überschreibungen: im Arbeitsspeicher, nicht persistent\n4. Schema-Standardwerte: aus `SETTINGS_SCHEMA`\n\nEffektiver Lesepfad:\n\n`defaults <- global <- project <- overrides`\n\nSchreibverhalten:\n\n- `settings.set(...)` schreibt in die **globale** Schicht (`config.yml`) und reiht eine Hintergrundspeicherung ein.\n- Projekteinstellungen sind schreibgeschützt aus der Capability-Erkennung.\n\n## Migrationsverhalten noch aktiv\n\nBeim Start, wenn `config.yml` fehlt:\n\n1. Migration von `~/.xcsh/agent/settings.json` (bei Erfolg in `.bak` umbenannt)\n2. Zusammenführung mit Legacy-DB-Einstellungen aus `agent.db`\n3. Zusammengeführtes Ergebnis in `config.yml` schreiben\n\nFeld-Level-Migrationen in `#migrateRawSettings`:\n\n- `queueMode` -> `steeringMode`\n- `ask.timeout` Millisekunden -> Sekunden, wenn der alte Wert wie ms aussieht (`> 1000`)\n- Legacy-flaches `theme: \"...\"` -> `theme.dark/theme.light`-Struktur\n\n---\n\n## 5) Capability-/Discovery-Integration\n\nDie meisten Nicht-Kern-Konfigurationsladevorgänge laufen über die Capability-Registry (`src/capability/index.ts` + `src/discovery/index.ts`).\n\n## Provider-Reihenfolge\n\nProvider werden nach numerischer Priorität sortiert (höher zuerst). Beispielprioritäten:\n\n- Native OMP (`builtin.ts`): `100`\n- Claude: `80`\n- Codex / Agenten / Claude Marketplace: `70`\n- Gemini: `60`\n\n```text\nProvider precedence (higher wins)\n\nnative (.xcsh)          priority 100\nclaude                 priority  80\ncodex / agents / ...   priority  70\ngemini                 priority  60\n```\n\n## Deduplizierungs-Semantik\n\nCapabilities definieren einen `key(item)`:\n\n- Gleicher Schlüssel => erstes Element gewinnt (Element mit höherer Priorität/früher geladen)\n- Kein Schlüssel (`undefined`) => keine Deduplizierung, alle Elemente werden beibehalten\n\nRelevante Schlüssel:\n\n- Skills: `name`\n- Tools: `name`\n- Hooks: `${type}:${tool}:${name}`\n- Erweiterungsmodule: `name`\n- Erweiterungen: `name`\n- Settings: keine Deduplizierung (alle Elemente werden beibehalten)\n\n---\n\n## 6) Nativer `.xcsh`-Provider-Verhalten (`src/discovery/builtin.ts`)\n\nDer native Provider (`id: native`) liest aus:\n\n- Projekt: `<cwd>/.xcsh/...`\n- Benutzer: `~/.xcsh/agent/...`\n\n### Verzeichnis-Zulassungsregel\n\n`builtin.ts` schließt ein Konfigurationsstammverzeichnis nur ein, wenn das Verzeichnis existiert **und nicht leer ist** (`ifNonEmptyDir`).\n\n### Bereichsspezifisches Laden\n\n- Skills: `skills/*/SKILL.md`\n- Slash-Befehle: `commands/*.md`\n- Regeln: `rules/*.{md,mdc}`\n- Prompts: `prompts/*.md`\n- Anweisungen: `instructions/*.md`\n- Hooks: `hooks/pre/*`, `hooks/post/*`\n- Tools: `tools/*.json|*.md` und `tools/<name>/index.ts`\n- Erweiterungsmodule: erkannt unter `extensions/` (+ Legacy `settings.json.extensions` String-Array)\n- Erweiterungen: `extensions/<name>/gemini-extension.json`\n- Settings-Capability: `settings.json`\n\n### Nuance bei der nächstgelegenen Projektsuche\n\nFür `SYSTEM.md` und `XCSH.md` verwendet der native Provider die Suche im nächstgelegenen übergeordneten `.xcsh`-Projektverzeichnis (aufwärts), erfordert aber weiterhin, dass das `.xcsh`-Verzeichnis nicht leer ist.\n\n---\n\n## 7) Wie wichtige Subsysteme Konfiguration konsumieren\n\n## Settings-Subsystem\n\n- `Settings.init()` lädt die globale `config.yml` + erkannte Projekt-`settings.json`-Capability-Elemente.\n- Nur Capability-Elemente mit `level === \"project\"` werden in die Projektschicht zusammengeführt.\n\n## Skills-Subsystem\n\n- `extensibility/skills.ts` lädt über `loadCapability(skillCapability.id, { cwd })`.\n- Wendet Quellumschalter und Filter an (`ignoredSkills`, `includeSkills`, benutzerdefinierte Verzeichnisse).\n- Legacy-benannte Umschalter existieren weiterhin (`skills.enablePiUser`, `skills.enablePiProject`), aber sie steuern den nativen Provider (`provider === \"native\"`).\n\n## Hooks-Subsystem\n\n- `discoverAndLoadHooks()` löst Hook-Pfade aus der Hook-Capability + explizit konfigurierte Pfade auf.\n- Lädt dann Module über Bun-Import.\n\n## Tools-Subsystem\n\n- `discoverAndLoadCustomTools()` löst Tool-Pfade aus der Tool-Capability + Plugin-Tool-Pfade + explizit konfigurierte Pfade auf.\n- Deklarative `.md/.json`-Tool-Dateien sind nur Metadaten; das Laden von ausführbarem Code erwartet Code-Module.\n\n## Erweiterungs-Subsystem\n\n- `discoverAndLoadExtensions()` löst Erweiterungsmodule aus der Erweiterungsmodul-Capability plus explizite Pfade auf.\n- Die aktuelle Implementierung behält absichtlich nur Capability-Elemente mit `_source.provider === \"native\"` vor dem Laden bei.\n\n---\n\n## 8) Prioritätsregeln, auf die man sich verlassen kann\n\nVerwenden Sie dieses mentale Modell:\n\n1. Die Quellverzeichnis-Reihenfolge aus `config.ts` bestimmt die Kandidatenpfad-Reihenfolge.\n2. Die Capability-Provider-Priorität bestimmt die providerübergreifende Rangfolge.\n3. Die Capability-Schlüssel-Deduplizierung bestimmt das Kollisionsverhalten (erstes gewinnt bei schlüsselbasierten Capabilities).\n4. Subsystemspezifische Zusammenführungslogik kann die effektive Rangfolge weiter ändern (insbesondere bei Settings).\n\n### Settings-spezifischer Vorbehalt\n\nSettings-Capability-Elemente werden nicht dedupliziert; `Settings.#loadProjectSettings()` führt ein Deep-Merge der Projektelemente in der zurückgegebenen Reihenfolge durch. Da die Zusammenführung spätere Elementwerte über frühere Werte anwendet, hängt das effektive Überschreibungsverhalten von der Provider-Emissionsreihenfolge ab, nicht nur von der Capability-Schlüssel-Semantik.\n\n---\n\n## 9) Legacy-/Kompatibilitätsverhalten, die noch vorhanden sind\n\n- `ConfigFile` JSON -> YAML-Migration für YAML-Zieldateien.\n- Settings-Migration von `settings.json` und `agent.db` zu `config.yml`.\n- Settings-Schlüssel-Migrationen (`queueMode`, `ask.timeout`, flaches `theme`).\n- Erweiterungsmanifest-Kompatibilität: Der Lader akzeptiert sowohl `package.json.xcsh`- als auch `package.json.pi`-Manifestabschnitte.\n- Legacy-Einstellungsnamen `skills.enablePiUser` / `skills.enablePiProject` sind weiterhin aktive Steuerungen für die native Skill-Quelle.\n\nWenn diese Kompatibilitätspfade im Code entfernt werden, aktualisieren Sie dieses Dokument sofort; mehrere Laufzeitverhalten hängen heute noch von ihnen ab.\n",
	"de/configuration/environment-variables.md": "---\ntitle: Umgebungsvariablen\ndescription: >-\n  Referenz für Laufzeit-Umgebungsvariablen zur Konfiguration und\n  Verhaltenssteuerung von xcsh.\nsidebar:\n  order: 2\n  label: Umgebungsvariablen\ni18n:\n  sourceHash: e2890f963c02\n  translator: machine\n---\n\n# Umgebungsvariablen (Aktuelle Laufzeit-Referenz)\n\nDiese Referenz wurde aus aktuellen Codepfaden abgeleitet in:\n\n- `packages/coding-agent/src/**`\n- `packages/ai/src/**` (Provider-/Auth-Auflösung, die vom coding-agent verwendet wird)\n- `packages/utils/src/**` und `packages/tui/src/**`, wo diese Variablen die Laufzeit des coding-agent direkt beeinflussen\n\nEs wird ausschließlich aktives Verhalten dokumentiert.\n\n## Auflösungsmodell und Priorität\n\nDie meisten Laufzeit-Lookups verwenden `$env` aus `@f5-sales-demo/pi-utils` (`packages/utils/src/env.ts`).\n\n`$env` Ladereihenfolge:\n\n1. Vorhandene Prozessumgebung (`Bun.env`)\n2. Projekt `.env` (`$PWD/.env`) für noch nicht gesetzte Schlüssel\n3. Home `.env` (`~/.env`) für noch nicht gesetzte Schlüssel\n\nZusätzliche Regel in `.env`-Dateien: `XCSH_*`-Schlüssel werden während des Parsens auf `PI_*`-Schlüssel gespiegelt.\n\n---\n\n## 1) Modell-/Provider-Authentifizierung\n\nDiese werden über `getEnvApiKey()` (`packages/ai/src/stream.ts`) konsumiert, sofern nicht anders angegeben.\n\n### Kern-Provider-Anmeldeinformationen\n\n| Variable                        | Verwendet für | Erforderlich wenn                                             | Hinweise / Priorität                                                                                |\n|---------------------------------|---|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|\n| `ANTHROPIC_OAUTH_TOKEN`         | Anthropic API-Auth | Verwendung von Anthropic mit OAuth-Token-Authentifizierung    | Hat Vorrang vor `ANTHROPIC_API_KEY` bei der Provider-Auth-Auflösung                                 |\n| `ANTHROPIC_API_KEY`             | Anthropic API-Auth | Verwendung von Anthropic ohne OAuth-Token                     | Fallback nach `ANTHROPIC_OAUTH_TOKEN`                                                               |\n| `ANTHROPIC_FOUNDRY_API_KEY`     | Anthropic über Azure Foundry / Enterprise-Gateway | `CLAUDE_CODE_USE_FOUNDRY` aktiviert                           | Hat Vorrang vor `ANTHROPIC_OAUTH_TOKEN` und `ANTHROPIC_API_KEY` wenn Foundry-Modus aktiviert ist    |\n| `OPENAI_API_KEY`                | OpenAI-Auth | Verwendung von OpenAI-Familie-Providern ohne explizites apiKey-Argument | Wird von OpenAI Completions/Responses-Providern verwendet                                           |\n| `GEMINI_API_KEY`                | Google Gemini-Auth | Verwendung von `google`-Provider-Modellen                     | Primärer Schlüssel für Gemini-Provider-Zuordnung                                                    |\n| `GOOGLE_API_KEY`               | Gemini Image-Tool Auth-Fallback | Verwendung des `gemini_image`-Tools ohne `GEMINI_API_KEY`     | Wird vom Image-Tool-Fallback-Pfad des coding-agent verwendet                                       |\n| `GROQ_API_KEY`                  | Groq-Auth | Verwendung von Groq-Modellen                                  |                                                                                                     |\n| `CEREBRAS_API_KEY`              | Cerebras-Auth | Verwendung von Cerebras-Modellen                              |                                                                                                     |\n| `TOGETHER_API_KEY`              | Together-Auth | Verwendung des `together`-Providers                           |                                                                                                     |\n| `HUGGINGFACE_HUB_TOKEN`         | Hugging Face-Auth | Verwendung des `huggingface`-Providers                        | Primäre Hugging Face Token-Umgebungsvariable                                                        |\n| `HF_TOKEN`                      | Hugging Face-Auth | Verwendung des `huggingface`-Providers                        | Fallback wenn `HUGGINGFACE_HUB_TOKEN` nicht gesetzt ist                                             |\n| `SYNTHETIC_API_KEY`             | Synthetic-Auth | Verwendung von Synthetic-Modellen                             |                                                                                                     |\n| `NVIDIA_API_KEY`                | NVIDIA-Auth | Verwendung des `nvidia`-Providers                             |                                                                                                     |\n| `NANO_GPT_API_KEY`              | NanoGPT-Auth | Verwendung des `nanogpt`-Providers                            |                                                                                                     |\n| `VENICE_API_KEY`                | Venice-Auth | Verwendung des `venice`-Providers                             |                                                                                                     |\n| `LITELLM_API_KEY`               | LiteLLM-Auth | Verwendung des `litellm`-Providers                            | OpenAI-kompatibler LiteLLM-Proxy-Schlüssel. Wenn zusammen mit `LITELLM_BASE_URL` gesetzt, wird die Auto-Konfiguration von `models.yml` aktiviert |\n| `LM_STUDIO_API_KEY`             | LM Studio-Auth (optional) | Verwendung des `lm-studio`-Providers mit authentifizierten Hosts | Lokales LM Studio läuft normalerweise ohne Auth; jeder nicht-leere Token funktioniert wenn ein Schlüssel erforderlich ist |\n| `OLLAMA_API_KEY`                | Ollama-Auth (optional) | Verwendung des `ollama`-Providers mit authentifizierten Hosts | Lokales Ollama läuft normalerweise ohne Auth; jeder nicht-leere Token funktioniert wenn ein Schlüssel erforderlich ist |\n| `LLAMA_CPP_API_KEY`             | Ollama-Auth (optional) | Verwendung von `llama-server` mit `--api-key`-Parameter      | Lokales llama.cpp läuft normalerweise ohne Auth; jeder nicht-leere Token funktioniert wenn ein Schlüssel konfiguriert ist |\n| `XIAOMI_API_KEY`                | Xiaomi MiMo-Auth | Verwendung des `xiaomi`-Providers                             |                                                                                                     |\n| `MOONSHOT_API_KEY`              | Moonshot-Auth | Verwendung des `moonshot`-Providers                           |                                                                                                     |\n| `XAI_API_KEY`                   | xAI-Auth | Verwendung von xAI-Modellen                                   |                                                                                                     |\n| `OPENROUTER_API_KEY`            | OpenRouter-Auth | Verwendung von OpenRouter-Modellen                            | Wird auch vom Image-Tool verwendet wenn bevorzugter/automatischer Provider OpenRouter ist           |\n| `MISTRAL_API_KEY`               | Mistral-Auth | Verwendung von Mistral-Modellen                               |                                                                                                     |\n| `ZAI_API_KEY`                   | z.ai-Auth | Verwendung von z.ai-Modellen                                  | Wird auch vom z.ai-Websuch-Provider verwendet                                                      |\n| `MINIMAX_API_KEY`               | MiniMax-Auth | Verwendung des `minimax`-Providers                            |                                                                                                     |\n| `MINIMAX_CODE_API_KEY`          | MiniMax Code-Auth | Verwendung des `minimax-code`-Providers                       |                                                                                                     |\n| `MINIMAX_CODE_CN_API_KEY`       | MiniMax Code CN-Auth | Verwendung des `minimax-code-cn`-Providers                    |                                                                                                     |\n| `OPENCODE_API_KEY`              | OpenCode-Auth | Verwendung von OpenCode-Modellen                              |                                                                                                     |\n| `QIANFAN_API_KEY`               | Qianfan-Auth | Verwendung des `qianfan`-Providers                            |                                                                                                     |\n| `QWEN_OAUTH_TOKEN`              | Qwen Portal-Auth | Verwendung von `qwen-portal` mit OAuth-Token                  | Hat Vorrang vor `QWEN_PORTAL_API_KEY`                                                               |\n| `QWEN_PORTAL_API_KEY`           | Qwen Portal-Auth | Verwendung von `qwen-portal` mit API-Schlüssel               | Fallback nach `QWEN_OAUTH_TOKEN`                                                                    |\n| `ZENMUX_API_KEY`                | ZenMux-Auth | Verwendung des `zenmux`-Providers                             | Wird für ZenMux OpenAI- und Anthropic-kompatible Routen verwendet                                  |\n| `VLLM_API_KEY`                  | vLLM Auth/Discovery-Opt-in | Verwendung des `vllm`-Providers (lokale OpenAI-kompatible Server) | Jeder nicht-leere Wert funktioniert für lokale Server ohne Auth                                    |\n| `CURSOR_ACCESS_TOKEN`           | Cursor-Provider-Auth | Verwendung des Cursor-Providers                               |                                                                                                     |\n| `AI_GATEWAY_API_KEY`            | Vercel AI Gateway-Auth | Verwendung des `vercel-ai-gateway`-Providers                  |                                                                                                     |\n| `CLOUDFLARE_AI_GATEWAY_API_KEY` | Cloudflare AI Gateway-Auth | Verwendung des `cloudflare-ai-gateway`-Providers              | Basis-URL muss als `https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic` konfiguriert werden |\n\n### GitHub/Copilot-Token-Ketten\n\n| Variable | Verwendet für | Kette |\n|---|---|---|\n| `COPILOT_GITHUB_TOKEN` | GitHub Copilot-Provider-Auth | `COPILOT_GITHUB_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN` |\n| `GH_TOKEN` | Copilot-Fallback; GitHub API-Auth im Web-Scraper | Im Web-Scraper: `GITHUB_TOKEN` → `GH_TOKEN` |\n| `GITHUB_TOKEN` | Copilot-Fallback; GitHub API-Auth im Web-Scraper | Im Web-Scraper: wird vor `GH_TOKEN` geprüft |\n\n---\n\n## 2) Provider-spezifische Laufzeitkonfiguration\n\n### Anthropic Foundry Gateway (Azure / Enterprise-Proxy)\n\nWenn `CLAUDE_CODE_USE_FOUNDRY` aktiviert ist, wechseln Anthropic-Anfragen in den Foundry-Modus:\n\n- Die Basis-URL wird aus `FOUNDRY_BASE_URL` aufgelöst (Fallback bleibt die Modell-/Standard-Basis-URL wenn nicht gesetzt).\n- Die API-Schlüssel-Auflösung für den Provider `anthropic` wird:\n  `ANTHROPIC_FOUNDRY_API_KEY` → `ANTHROPIC_OAUTH_TOKEN` → `ANTHROPIC_API_KEY`.\n- `ANTHROPIC_CUSTOM_HEADERS` wird als komma-/zeilengetrennte `key: value`-Paare geparst und in die Request-Header zusammengeführt.\n- TLS-Client-/Server-Material kann aus Umgebungswerten injiziert werden:\n  `NODE_EXTRA_CA_CERTS`, `CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`.\n  Jeder akzeptiert entweder:\n  - einen Dateisystempfad zu PEM-Inhalt, oder\n  - Inline-PEM (einschließlich escaped `\\n`-Sequenzen).\n\n| Variable | Werttyp | Verhalten |\n|---|---|---|\n| `CLAUDE_CODE_USE_FOUNDRY` | Boolean-ähnlicher String (`1`, `true`, `yes`, `on`) | Aktiviert den Foundry-Modus für den Anthropic-Provider |\n| `FOUNDRY_BASE_URL` | URL-String | Anthropic-Endpunkt-Basis-URL im Foundry-Modus |\n| `ANTHROPIC_FOUNDRY_API_KEY` | Token-String | Wird für `Authorization: Bearer <token>` verwendet |\n| `ANTHROPIC_CUSTOM_HEADERS` | Header-Listen-String | Zusätzliche Header; Format `header-a: value, header-b: value` oder zeilengetrennt |\n| `NODE_EXTRA_CA_CERTS` | PEM-Pfad oder Inline-PEM | Zusätzliche CA-Kette für Server-Zertifikatsvalidierung |\n| `CLAUDE_CODE_CLIENT_CERT` | PEM-Pfad oder Inline-PEM | mTLS-Client-Zertifikat |\n| `CLAUDE_CODE_CLIENT_KEY` | PEM-Pfad oder Inline-PEM | mTLS-Client-Privatschlüssel (muss mit Zertifikat gepaart sein) |\n\n### Amazon Bedrock\n\n| Variable | Standard / Verhalten |\n|---|---|\n| `AWS_REGION` | Primäre Regionsquelle |\n| `AWS_DEFAULT_REGION` | Fallback wenn `AWS_REGION` nicht gesetzt |\n| `AWS_PROFILE` | Aktiviert den Auth-Pfad mit benanntem Profil |\n| `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` | Aktiviert den IAM-Schlüssel-Auth-Pfad |\n| `AWS_BEARER_TOKEN_BEDROCK` | Aktiviert den Bearer-Token-Auth-Pfad |\n| `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` / `AWS_CONTAINER_CREDENTIALS_FULL_URI` | Aktiviert den ECS-Task-Anmeldeinformationspfad |\n| `AWS_WEB_IDENTITY_TOKEN_FILE` + `AWS_ROLE_ARN` | Aktiviert den Web-Identity-Auth-Pfad |\n| `AWS_BEDROCK_SKIP_AUTH` | Wenn `1`, werden Dummy-Anmeldeinformationen injiziert (Proxy-/Nicht-Auth-Szenarien) |\n| `AWS_BEDROCK_FORCE_HTTP1` | Wenn `1`, wird der Node HTTP/1 Request-Handler erzwungen |\n\nRegions-Fallback im Provider-Code: `options.region` → `AWS_REGION` → `AWS_DEFAULT_REGION` → `us-east-1`.\n\n### Azure OpenAI Responses\n\n| Variable | Standard / Verhalten |\n|---|---|\n| `AZURE_OPENAI_API_KEY` | Erforderlich, sofern kein API-Schlüssel als Option übergeben wird |\n| `AZURE_OPENAI_API_VERSION` | Standard `v1` |\n| `AZURE_OPENAI_BASE_URL` | Direkte Basis-URL-Überschreibung |\n| `AZURE_OPENAI_RESOURCE_NAME` | Wird zur Konstruktion der Basis-URL verwendet: `https://<resource>.openai.azure.com/openai/v1` |\n| `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` | Optionaler Zuordnungs-String: `modelId=deploymentName,model2=deployment2` |\n\nBasis-URL-Auflösung: Option `azureBaseUrl` → Umgebungsvariable `AZURE_OPENAI_BASE_URL` → Option/Umgebungsvariable Ressourcenname → `model.baseUrl`.\n\n### Google Vertex AI\n\n| Variable | Erforderlich? | Hinweise |\n|---|---|---|\n| `GOOGLE_CLOUD_PROJECT` | Ja (sofern nicht in Optionen übergeben) | Fallback: `GCLOUD_PROJECT` |\n| `GCLOUD_PROJECT` | Fallback | Wird als alternative Projekt-ID-Quelle verwendet |\n| `GOOGLE_CLOUD_LOCATION` | Ja (sofern nicht in Optionen übergeben) | Kein Standard im Provider |\n| `GOOGLE_APPLICATION_CREDENTIALS` | Bedingt | Wenn gesetzt, muss die Datei existieren; andernfalls wird der ADC-Fallback-Pfad geprüft (`~/.config/gcloud/application_default_credentials.json`) |\n\n### Kimi\n\n| Variable | Standard / Verhalten |\n|---|---|\n| `KIMI_CODE_OAUTH_HOST` | Primäre OAuth-Host-Überschreibung |\n| `KIMI_OAUTH_HOST` | Fallback OAuth-Host-Überschreibung |\n| `KIMI_CODE_BASE_URL` | Überschreibt die Kimi-Nutzungsendpunkt-Basis-URL (`usage/kimi.ts`) |\n\nOAuth-Host-Kette: `KIMI_CODE_OAUTH_HOST` → `KIMI_OAUTH_HOST` → `https://auth.kimi.com`.\n\n### Antigravity/Gemini Image-Kompatibilität\n\n| Variable | Standard / Verhalten |\n|---|---|\n| `PI_AI_ANTIGRAVITY_VERSION` | Überschreibt das Antigravity User-Agent Versions-Tag im Gemini CLI-Provider |\n\n### OpenAI Codex Responses (Feature-/Debug-Steuerungen)\n\n| Variable | Verhalten |\n|---|---|\n| `PI_CODEX_DEBUG` | `1`/`true` aktiviert Codex-Provider-Debug-Protokollierung |\n| `PI_CODEX_WEBSOCKET` | `1`/`true` aktiviert WebSocket-Transport-Präferenz |\n| `PI_CODEX_WEBSOCKET_V2` | `1`/`true` aktiviert WebSocket v2-Pfad |\n| `PI_CODEX_WEBSOCKET_IDLE_TIMEOUT_MS` | Positive Ganzzahl-Überschreibung (Standard 300000) |\n| `PI_CODEX_WEBSOCKET_RETRY_BUDGET` | Nicht-negative Ganzzahl-Überschreibung (Standard 5) |\n| `PI_CODEX_WEBSOCKET_RETRY_DELAY_MS` | Positive Ganzzahl-Basis-Backoff-Überschreibung (Standard 500) |\n\n### Cursor-Provider-Debug\n\n| Variable | Verhalten |\n|---|---|\n| `DEBUG_CURSOR` | Aktiviert Provider-Debug-Protokolle; `2`/`verbose` für detaillierte Payload-Auszüge |\n| `DEBUG_CURSOR_LOG` | Optionaler Dateipfad für JSONL-Debug-Protokollausgabe |\n\n### Prompt-Cache-Kompatibilitätsschalter\n\n| Variable | Verhalten |\n|---|---|\n| `PI_CACHE_RETENTION` | Wenn `long`, wird lange Aufbewahrung aktiviert wo unterstützt (`anthropic`, `openai-responses`, Bedrock-Aufbewahrungsauflösung) |\n\n---\n\n## 3) Websuche-Subsystem\n\n### Suchprovider-Anmeldeinformationen\n\n| Variable | Verwendet von |\n|---|---|\n| `EXA_API_KEY` | Exa-Suchprovider und Exa MCP-Tools |\n| `BRAVE_API_KEY` | Brave-Suchprovider |\n| `PERPLEXITY_API_KEY` | Perplexity-Suchprovider API-Schlüssel-Modus |\n| `TAVILY_API_KEY` | Tavily-Suchprovider |\n| `ZAI_API_KEY` | z.ai-Suchprovider (prüft auch gespeichertes OAuth in `agent.db`) |\n| `OPENAI_API_KEY` / Codex OAuth in DB | Codex-Suchprovider Verfügbarkeit/Auth |\n\n### Anthropic-Websuche Auth-Kette\n\n`packages/coding-agent/src/web/search/auth.ts` löst Anthropic-Websuche-Anmeldeinformationen in dieser Reihenfolge auf:\n\n1. `ANTHROPIC_SEARCH_API_KEY` (+ optionales `ANTHROPIC_SEARCH_BASE_URL`)\n2. `models.json`-Provider-Eintrag mit `api: \"anthropic-messages\"`\n3. Anthropic OAuth-Anmeldeinformationen aus `agent.db` (darf nicht innerhalb eines 5-Minuten-Puffers ablaufen)\n4. Generischer Anthropic-Umgebungsvariablen-Fallback: Provider-Schlüssel (`ANTHROPIC_FOUNDRY_API_KEY`/`ANTHROPIC_OAUTH_TOKEN`/`ANTHROPIC_API_KEY`) + optionales `ANTHROPIC_BASE_URL` (`FOUNDRY_BASE_URL` wenn Foundry-Modus aktiviert ist)\n\nZugehörige Variablen:\n\n| Variable | Standard / Verhalten |\n|---|---|\n| `ANTHROPIC_SEARCH_API_KEY` | Expliziter Suchschlüssel mit höchster Priorität |\n| `ANTHROPIC_SEARCH_BASE_URL` | Standard ist `https://api.anthropic.com` wenn nicht angegeben |\n| `ANTHROPIC_SEARCH_MODEL` | Standard ist `claude-haiku-4-5` |\n| `ANTHROPIC_BASE_URL` | Generische Fallback-Basis-URL für Stufe-4-Auth-Pfad |\n\n### Perplexity OAuth-Flow Verhaltensflag\n\n| Variable | Verhalten |\n|---|---|\n| `PI_AUTH_NO_BORROW` | Wenn gesetzt, wird der macOS Native-App-Token-Borrowing-Pfad im Perplexity-Login-Flow deaktiviert |\n\n---\n\n## 4) Python-Tooling und Kernel-Laufzeit\n\n| Variable | Standard / Verhalten |\n|---|---|\n| `PI_PY` | Python-Tool-Modus-Überschreibung: `0`/`bash`=`bash-only`, `1`/`py`=`ipy-only`, `mix`/`both`=`both`; ungültige Werte werden ignoriert |\n| `PI_PYTHON_SKIP_CHECK` | Wenn `1`, werden Python-Kernel-Verfügbarkeitsprüfungen/Warmup-Prüfungen übersprungen |\n| `PI_PYTHON_GATEWAY_URL` | Wenn gesetzt, wird ein externer Kernel-Gateway anstelle des lokalen Shared-Gateway verwendet |\n| `PI_PYTHON_GATEWAY_TOKEN` | Optionales Auth-Token für externen Gateway (`Authorization: token <value>`) |\n| `PI_PYTHON_IPC_TRACE` | Wenn `1`, aktiviert den Low-Level-IPC-Trace-Pfad im Kernel-Modul |\n| `VIRTUAL_ENV` | Venv-Pfad mit höchster Priorität für die Python-Laufzeitauflösung |\n\nZusätzliches bedingtes Verhalten:\n\n- Wenn `BUN_ENV=test` oder `NODE_ENV=test`, werden Python-Verfügbarkeitsprüfungen als OK behandelt und das Warming wird übersprungen.\n- Die Python-Umgebungsfilterung blockiert gängige API-Schlüssel und erlaubt sichere Basisvariablen + `LC_`-, `XDG_`-, `PI_`-Präfixe.\n\n---\n\n## 5) Agent-/Laufzeit-Verhaltensschalter\n\n| Variable                   | Standard / Verhalten                                                                         |\n|----------------------------|----------------------------------------------------------------------------------------------|\n| `PI_SMOL_MODEL`            | Ephemere Modellrollen-Überschreibung für `smol` (CLI `--smol` hat Vorrang)                   |\n| `PI_SLOW_MODEL`            | Ephemere Modellrollen-Überschreibung für `slow` (CLI `--slow` hat Vorrang)                   |\n| `PI_PLAN_MODEL`            | Ephemere Modellrollen-Überschreibung für `plan` (CLI `--plan` hat Vorrang)                   |\n| `PI_NO_TITLE`              | Wenn gesetzt (jeder nicht-leere Wert), wird die automatische Sitzungstitelgenerierung bei der ersten Benutzernachricht deaktiviert |\n| `NULL_PROMPT`              | Wenn `true`, gibt der System-Prompt-Builder einen leeren String zurück                       |\n| `PI_BLOCKED_AGENT`         | Blockiert einen bestimmten Subagent-Typ im Task-Tool                                        |\n| `PI_SUBPROCESS_CMD`        | Überschreibt den Subagent-Spawn-Befehl (`xcsh` / `xcsh.cmd`-Auflösungsumgehung)              |\n| `PI_TASK_MAX_OUTPUT_BYTES` | Maximale erfasste Ausgabe-Bytes pro Subagent (Standard `500000`)                             |\n| `PI_TASK_MAX_OUTPUT_LINES` | Maximale erfasste Ausgabezeilen pro Subagent (Standard `5000`)                               |\n| `PI_TIMING`                | Wenn `1`, aktiviert Startup-/Tool-Timing-Instrumentierungsprotokolle                         |\n| `PI_DEBUG_STARTUP`         | Aktiviert Startup-Stufen-Debug-Ausgaben auf stderr in mehreren Startup-Pfaden                |\n| `PI_PACKAGE_DIR`           | Überschreibt die Auflösung des Paket-Asset-Basisverzeichnisses (Docs/Beispiele/Changelog-Pfadsuche) |\n| `PI_DISABLE_LSPMUX`        | Wenn `1`, deaktiviert lspmux-Erkennung/-Integration und erzwingt direktes LSP-Server-Spawning |\n| `LITELLM_BASE_URL`         | LiteLLM-Proxy-Basis-URL. Wenn zusammen mit `LITELLM_API_KEY` gesetzt, wird die Auto-Generierung von `models.yml` beim ersten Start und Selbstheilung bei jedem Start ausgelöst |\n| `LM_STUDIO_BASE_URL`       | Standard-Überschreibung der impliziten LM Studio-Discovery-Basis-URL (`http://127.0.0.1:1234/v1` wenn nicht gesetzt) |\n| `OLLAMA_BASE_URL`          | Standard-Überschreibung der impliziten Ollama-Discovery-Basis-URL (`http://127.0.0.1:11434` wenn nicht gesetzt) |\n| `LLAMA_CPP_BASE_URL`       | Standard-Überschreibung der impliziten Llama.cpp-Discovery-Basis-URL (`http://127.0.0.1:8080` wenn nicht gesetzt) |\n| `PI_EDIT_VARIANT`          | Wenn `hashline`, wird der Hashline-Lese-/Grep-Anzeigemodus erzwungen wenn das Edit-Tool verfügbar ist |\n| `PI_NO_PTY`                | Wenn `1`, wird der interaktive PTY-Pfad für das Bash-Tool deaktiviert                        |\n\n`PI_NO_PTY` wird auch intern gesetzt wenn CLI `--no-pty` verwendet wird.\n\n---\n\n## 6) Speicher- und Konfigurationswurzelpfade\n\nDiese werden über `@f5-sales-demo/pi-utils/dirs` konsumiert und beeinflussen, wo der coding-agent Daten speichert.\n\n| Variable | Standard / Verhalten |\n|---|---|\n| `PI_CONFIG_DIR` | Konfigurationswurzel-Verzeichnisname unter Home (Standard `.xcsh`) |\n| `PI_CODING_AGENT_DIR` | Vollständige Überschreibung für das Agent-Verzeichnis (Standard `~/<PI_CONFIG_DIR oder .xcsh>/agent`) |\n| `PWD` | Wird beim Abgleich des kanonischen aktuellen Arbeitsverzeichnisses in Pfad-Helfern verwendet |\n\n---\n\n## 7) Shell-/Tool-Ausführungsumgebung\n\n(Aus `packages/utils/src/procmgr.ts` und der Bash-Tool-Integration des coding-agent.)\n\n| Variable | Verhalten |\n|---|---|\n| `PI_BASH_NO_CI` | Unterdrückt die automatische `CI=true`-Injektion in die Umgebung gestarteter Shells |\n| `CLAUDE_BASH_NO_CI` | Legacy-Alias-Fallback für `PI_BASH_NO_CI` |\n| `PI_BASH_NO_LOGIN` | Vorgesehen zur Deaktivierung des Login-Shell-Modus |\n| `CLAUDE_BASH_NO_LOGIN` | Legacy-Alias-Fallback für `PI_BASH_NO_LOGIN` |\n| `PI_SHELL_PREFIX` | Optionaler Befehls-Präfix-Wrapper |\n| `CLAUDE_CODE_SHELL_PREFIX` | Legacy-Alias-Fallback für `PI_SHELL_PREFIX` |\n| `VISUAL` | Bevorzugter externer Editor-Befehl |\n| `EDITOR` | Fallback externer Editor-Befehl |\n\nHinweis zur aktuellen Implementierung: `PI_BASH_NO_LOGIN`/`CLAUDE_BASH_NO_LOGIN` werden gelesen, aber die aktuelle `getShellArgs()`-Implementierung gibt in beiden Zweigen `['-l','-c']` zurück (effektiv heute ein No-Op).\n\n---\n\n## 8) UI/Theme/Sitzungserkennung (automatisch erkannte Umgebung)\n\nDiese werden als Laufzeitsignale gelesen; sie werden normalerweise vom Terminal/Betriebssystem gesetzt und nicht manuell konfiguriert.\n\n| Variable | Verwendet für |\n|---|---|\n| `COLORTERM`, `TERM`, `WT_SESSION` | Farbfähigkeitserkennung (Theme-Farbmodus) |\n| `COLORFGBG` | Automatische Hell-/Dunkelerkennung des Terminal-Hintergrunds |\n| `TERM_PROGRAM`, `TERM_PROGRAM_VERSION`, `TERMINAL_EMULATOR` | Terminal-Identität im System-Prompt/Kontext |\n| `KDE_FULL_SESSION`, `XDG_CURRENT_DESKTOP`, `DESKTOP_SESSION`, `XDG_SESSION_DESKTOP`, `GDMSESSION`, `WINDOWMANAGER` | Desktop-/Fenstermanager-Erkennung im System-Prompt/Kontext |\n| `KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION` | Stabile Sitzungs-Breadcrumb-IDs pro Terminal |\n| `SHELL`, `ComSpec`, `TERM_PROGRAM`, `TERM` | Systeminfo-Diagnose |\n| `APPDATA`, `XDG_CONFIG_HOME` | lspmux-Konfigurationspfad-Auflösung |\n| `HOME` | Pfadverkürzung in der MCP-Befehlsoberfläche |\n\n---\n\n## 9) Native Loader-/Debug-Flags\n\n| Variable | Verhalten |\n|---|---|\n| `PI_DEV` | Aktiviert ausführliche Diagnosemeldungen beim Laden nativer Addons in `packages/natives` |\n\n## 10) TUI-Laufzeit-Flags (gemeinsames Paket, beeinflusst die coding-agent UX)\n\n| Variable | Verhalten |\n|---|---|\n| `PI_NOTIFICATIONS` | `off` / `0` / `false` unterdrückt Desktop-Benachrichtigungen |\n| `PI_TUI_WRITE_LOG` | Wenn gesetzt, werden TUI-Schreibvorgänge in eine Datei protokolliert |\n| `PI_HARDWARE_CURSOR` | Wenn `1`, wird der Hardware-Cursor-Modus aktiviert |\n| `PI_CLEAR_ON_SHRINK` | Wenn `1`, werden leere Zeilen gelöscht wenn der Inhalt schrumpft |\n| `PI_DEBUG_REDRAW` | Wenn `1`, wird die Redraw-Debug-Protokollierung aktiviert |\n| `PI_TUI_DEBUG` | Wenn `1`, wird der tiefe TUI-Debug-Dump-Pfad aktiviert |\n\n---\n\n## 11) Commit-Generierungssteuerungen\n\n| Variable | Verhalten |\n|---|---|\n| `PI_COMMIT_TEST_FALLBACK` | Wenn `true` (Groß-/Kleinschreibung egal), wird der Commit-Fallback-Generierungspfad erzwungen |\n| `PI_COMMIT_NO_FALLBACK` | Wenn `true`, wird der Fallback deaktiviert wenn der Agent keinen Vorschlag zurückgibt |\n| `PI_COMMIT_MAP_REDUCE` | Wenn `false`, wird der Map-Reduce-Commit-Analysepfad deaktiviert |\n| `DEBUG` | Wenn gesetzt, werden Commit-Agent-Fehler-Stack-Traces ausgegeben |\n\n---\n\n## Sicherheitsrelevante Variablen\n\nBehandeln Sie diese als Geheimnisse; protokollieren oder committen Sie sie nicht:\n\n- Provider-/API-Schlüssel und OAuth-/Bearer-Anmeldeinformationen (alle `*_API_KEY`, `*_TOKEN`, OAuth Access-/Refresh-Tokens)\n- Cloud-Anmeldeinformationen (`AWS_*`, `GOOGLE_APPLICATION_CREDENTIALS`-Pfad kann Service-Account-Material offenlegen)\n- Such-/Provider-Auth-Variablen (`EXA_API_KEY`, `BRAVE_API_KEY`, `PERPLEXITY_API_KEY`, Anthropic-Suchschlüssel)\n- Foundry mTLS-Material (`CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`, `NODE_EXTRA_CA_CERTS` wenn es auf private CA-Bundles verweist)\n\nDie Python-Laufzeit entfernt auch explizit viele gängige Schlüsselvariablen vor dem Starten von Kernel-Unterprozessen (`packages/coding-agent/src/ipy/runtime.ts`).\n",
	"de/configuration/fs-scan-cache-architecture.md": "---\ntitle: Dateisystem-Scan-Cache-Architektur\ndescription: >-\n  Dateisystem-Scan-Cache-Vertrag für schnelle Dateierkennung mit\n  Stale-While-Revalidate-Semantik.\nsidebar:\n  order: 8\n  label: Dateisystem-Scan-Cache\ni18n:\n  sourceHash: 2a2bde1726ac\n  translator: machine\n---\n\n# Architekturvertrag für den Dateisystem-Scan-Cache\n\nDieses Dokument definiert den aktuellen Vertrag für den gemeinsam genutzten Dateisystem-Scan-Cache, der in Rust (`crates/pi-natives/src/fs_cache.rs`) implementiert ist und von nativen Discovery-/Such-APIs genutzt wird, die für `packages/coding-agent` bereitgestellt werden.\n\n## Was dieser Cache ist\n\nDer Cache speichert vollständige Verzeichnis-Scan-Eintragslisten (`GlobMatch[]`), die nach Scan-Scope und Traversierungsrichtlinie indiziert sind, und ermöglicht es übergeordneten Operationen (Glob-Filterung, Fuzzy-Bewertung, Grep-Dateiauswahl), gegen diese gecachten Einträge zu arbeiten.\n\nPrimäre Ziele:\n\n- wiederholte Dateisystem-Durchläufe für wiederholte Discovery-/Suchaufrufe vermeiden\n- Konsistenz zwischen `glob`, `fuzzyFind` und `grep` sicherstellen, wenn sie dieselbe Scan-Richtlinie teilen\n- explizite Veralterungs-Wiederherstellung für leere Ergebnisse und explizite Invalidierung nach Datei-Mutationen ermöglichen\n\n## Eigentümerschaft und öffentliche Schnittstelle\n\n- Cache-Implementierung und -Richtlinie: `crates/pi-natives/src/fs_cache.rs`\n- Native Konsumenten:\n  - `crates/pi-natives/src/glob.rs`\n  - `crates/pi-natives/src/fd.rs` (`fuzzyFind`)\n  - `crates/pi-natives/src/grep.rs`\n- JS-Binding/Export:\n  - `packages/natives/src/glob/index.ts` (`invalidateFsScanCache`)\n  - `packages/natives/src/glob/types.ts`\n  - `packages/natives/src/grep/types.ts`\n- Coding-Agent-Mutations-Invalidierungshelfer:\n  - `packages/coding-agent/src/tools/fs-cache-invalidation.ts`\n\n## Cache-Schlüssel-Partitionierung (harter Vertrag)\n\nJeder Eintrag wird indiziert durch:\n\n- kanonisierter `root`-Verzeichnispfad\n- `include_hidden` Boolean\n- `use_gitignore` Boolean\n\nImplikationen:\n\n- Scans mit und ohne versteckte Dateien teilen sich **keine** Einträge.\n- Scans mit und ohne Gitignore-Beachtung teilen sich **keine** Einträge.\n- Konsumenten müssen stabile Semantik für Hidden-/Gitignore-Verhalten übergeben; das Ändern eines der Flags erzeugt eine andere Cache-Partition.\n\nDie `node_modules`-Einbeziehung ist **nicht** im Cache-Schlüssel enthalten. Der Cache speichert Einträge mit eingeschlossenen `node_modules`; die konsumentenspezifische Filterung wird nach dem Abruf angewendet.\n\n## Scan-Erfassungsverhalten\n\nDie Cache-Befüllung verwendet einen deterministischen Walker (`ignore::WalkBuilder`), der durch `include_hidden` und `use_gitignore` konfiguriert wird:\n\n- `follow_links(false)`\n- sortiert nach Dateipfad\n- `.git` wird immer übersprungen\n- `node_modules` wird zum Zeitpunkt des Cache-Scans immer erfasst (und optional später gefiltert)\n- Eintrags-Dateityp + `mtime` werden über `symlink_metadata` erfasst\n\nSuch-Wurzelverzeichnisse werden durch `resolve_search_path` aufgelöst:\n\n- relative Pfade werden gegen das aktuelle cwd aufgelöst\n- das Ziel muss ein existierendes Verzeichnis sein\n- das Wurzelverzeichnis wird wenn möglich kanonisiert\n\n## Frische- und Eviktionsrichtlinie\n\nGlobale Richtlinie (über Umgebungsvariablen überschreibbar):\n\n- `FS_SCAN_CACHE_TTL_MS` (Standard `1000`)\n- `FS_SCAN_EMPTY_RECHECK_MS` (Standard `200`)\n- `FS_SCAN_CACHE_MAX_ENTRIES` (Standard `16`)\n\nVerhalten:\n\n- `get_or_scan(...)`\n  - wenn TTL `0` ist: Cache vollständig umgehen, immer frischer Scan (`cache_age_ms = 0`)\n  - bei Cache-Treffer innerhalb der TTL: gecachte Einträge + nicht-null `cache_age_ms` zurückgeben\n  - bei abgelaufenem Treffer: Schlüssel eviktieren, neu scannen, frischen Eintrag speichern\n- Maximale Eintragsanzahl wird durch Ältester-zuerst-Eviktierung nach `created_at` durchgesetzt\n\n## Schnelle Nachprüfung bei leeren Ergebnissen (getrennt von normalen Treffern)\n\nNormaler Cache-Treffer:\n\n- ein Cache-Treffer innerhalb der TTL gibt gecachte Einträge zurück und tut nichts weiter.\n\nSchnelle Nachprüfung bei leeren Ergebnissen:\n\n- dies ist eine **aufruferseitige** Richtlinie unter Verwendung von `ScanResult.cache_age_ms`\n- wenn das gefilterte/abgefragte Ergebnis leer ist und das Alter des gecachten Scans mindestens `empty_recheck_ms()` beträgt, führt der Aufrufer einen `force_rescan(...)` durch und versucht es erneut\n- dient dazu, falsch-negative Ergebnisse durch Veralterung zu reduzieren, wenn Dateien kürzlich hinzugefügt wurden, der Cache aber noch innerhalb der TTL liegt\n\nAktuelle Konsumenten:\n\n- `glob`: prüft erneut, wenn gefilterte Treffer leer sind und das Scan-Alter den Schwellenwert überschreitet\n- `fuzzyFind` (`fd.rs`): prüft nur erneut, wenn die Abfrage nicht leer ist und bewertete Treffer leer sind\n- `grep`: prüft erneut, wenn die ausgewählte Kandidaten-Dateiliste leer ist\n\n## Konsumenten-Standardwerte und Cache-Nutzung\n\nDer Cache ist Opt-in bei allen exponierten APIs (`cache?: boolean`, Standard `false`).\n\nAktuelle Standardwerte in nativen APIs:\n\n- `glob`: `hidden=false`, `gitignore=true`, `cache=false`\n- `fuzzyFind`: `hidden=false`, `gitignore=true`, `cache=false`\n- `grep`: `hidden=true`, `cache=false`, und der Cache-Scan verwendet immer `use_gitignore=true`\n\nCoding-Agent-Aufrufer heute:\n\n- Hochvolumige Mention-Kandidaten-Discovery aktiviert den Cache:\n  - `packages/coding-agent/src/utils/file-mentions.ts`\n  - Profil: `hidden=true`, `gitignore=true`, `includeNodeModules=true`, `cache=true`\n- Tool-Level-`grep`-Integration deaktiviert derzeit den Scan-Cache (`cache: false`):\n  - `packages/coding-agent/src/tools/grep.ts`\n\n## Invalidierungsvertrag\n\nNativer Invalidierungs-Einstiegspunkt:\n\n- `invalidateFsScanCache(path?: string)`\n  - mit `path`: Cache-Einträge entfernen, deren Root ein Präfix des Zielpfades ist\n  - ohne Pfad: alle Scan-Cache-Einträge löschen\n\nDetails zur Pfadbehandlung:\n\n- relative Invalidierungspfade werden gegen cwd aufgelöst\n- die Invalidierung versucht eine Kanonisierung\n- wenn das Ziel nicht existiert (z.B. bei Löschung), wird als Fallback das übergeordnete Verzeichnis kanonisiert und der Dateiname wenn möglich wieder angehängt\n- dies bewahrt das Invalidierungsverhalten für Erstellen/Löschen/Umbenennen, bei denen eine Seite möglicherweise nicht existiert\n\n## Verantwortlichkeiten beim Coding-Agent-Mutationsablauf\n\nCoding-Agent-Code muss nach erfolgreichen Dateisystem-Mutationen invalidieren.\n\nZentrale Helfer:\n\n- `invalidateFsScanAfterWrite(path)`\n- `invalidateFsScanAfterDelete(path)`\n- `invalidateFsScanAfterRename(oldPath, newPath)` (invalidiert beide Seiten, wenn die Pfade unterschiedlich sind)\n\nAktuelle Mutations-Tool-Aufrufstellen:\n\n- `packages/coding-agent/src/tools/write.ts`\n- `packages/coding-agent/src/patch/index.ts` (Hashline-/Patch-/Replace-Abläufe)\n\nRegel: Wenn ein Ablauf Dateisystem-Inhalte oder -Positionen mutiert und diese Helfer umgeht, sind Cache-Veralterungs-Bugs zu erwarten.\n\n## Einen neuen Cache-Konsumenten sicher hinzufügen\n\nBeim Einführen der Cache-Nutzung in einem neuen Scanner-/Suchpfad:\n\n1. **Stabile Scan-Richtlinien-Eingaben verwenden**\n   - Hidden-/Gitignore-Semantik zuerst festlegen\n   - diese konsistent an `get_or_scan`/`force_rescan` übergeben, damit Cache-Partitionen beabsichtigt sind\n\n2. **Cache-Daten als nur durch Traversierungsrichtlinie vorgefiltert behandeln**\n   - toolspezifische Filterung (Glob-Muster, Typfilter, node_modules-Regeln) nach dem Abruf anwenden\n   - niemals annehmen, dass gecachte Einträge bereits Ihre übergeordneten Filter widerspiegeln\n\n3. **Schnelle Nachprüfung bei leeren Ergebnissen nur bei Risiko falsch-negativer Veralterung implementieren**\n   - `scan.cache_age_ms >= empty_recheck_ms()` verwenden\n   - einmal mit `force_rescan(..., store=true, ...)` wiederholen\n   - diesen Pfad von der normalen Cache-Treffer-Logik getrennt halten\n\n4. **No-Cache-Modus explizit beachten**\n   - wenn der Aufrufer den Cache deaktiviert, `force_rescan(..., store=false, ...)` aufrufen\n   - den gemeinsamen Cache nicht in einem No-Cache-Anfragepfad befüllen\n\n5. **Mutations-Invalidierung für jeden neuen Schreibpfad verdrahten**\n   - nach erfolgreichem Schreiben/Bearbeiten/Löschen/Umbenennen den Coding-Agent-Invalidierungshelfer aufrufen\n   - bei Umbenennen/Verschieben beide alten und neuen Pfade invalidieren\n\n6. **Keine Pro-Aufruf-TTL-Regler hinzufügen**\n   - der aktuelle Vertrag sieht nur eine globale Richtlinie vor (über Umgebungsvariablen konfiguriert), keine Pro-Anfrage-TTL-Überschreibung\n\n## Bekannte Grenzen\n\n- Der Cache-Scope ist prozesslokal im Speicher (`DashMap`), nicht persistent über Prozessneustarts hinweg.\n- Der Cache speichert Scan-Einträge, keine endgültigen Tool-Ergebnisse.\n- `glob`/`fuzzyFind`/`grep` teilen Scan-Einträge nur, wenn die Schlüsseldimensionen (`root`, `hidden`, `gitignore`) übereinstimmen.\n- `.git` wird unabhängig von Aufruferoptionen immer zum Zeitpunkt der Scan-Erfassung ausgeschlossen.\n",
	"de/configuration/hooks.md": "---\ntitle: Hooks\ndescription: >-\n  Hook-System für Pre/Post-Event-Automatisierung im Lebenszyklus des\n  Coding-Agenten.\nsidebar:\n  order: 4\n  label: Hooks\ni18n:\n  sourceHash: cdbec10bc405\n  translator: machine\n---\n\n# Hooks\n\nDieses Dokument beschreibt den **aktuellen Hook-Subsystem-Code** in `src/extensibility/hooks/*`.\n\n## Aktueller Status im Laufzeitbetrieb\n\nDas Hook-Paket (`src/extensibility/hooks/`) wird weiterhin exportiert und ist als API-Oberfläche nutzbar, jedoch initialisiert die Standard-CLI-Laufzeit nun den Pfad des **Extension-Runners**. Im aktuellen Startablauf:\n\n- `--hook` wird als Alias für `--extension` behandelt (CLI-Pfade werden in `additionalExtensionPaths` zusammengeführt)\n- Werkzeuge werden durch `ExtensionToolWrapper`, nicht durch `HookToolWrapper`, umschlossen\n- Kontexttransformationen und Lebenszyklusemissionen werden über `ExtensionRunner` abgewickelt\n\nDieses Dokument beschreibt daher die Implementierung des Hook-Subsystems selbst (Typen/Loader/Runner/Wrapper), einschließlich des Legacy-Verhaltens und der Einschränkungen.\n\n## Wichtige Dateien\n\n- `src/extensibility/hooks/types.ts` — Hook-Kontext, Ereignistypen und Ergebnisverträge\n- `src/extensibility/hooks/loader.ts` — Modulladung und Hook-Erkennungsbrücke\n- `src/extensibility/hooks/runner.ts` — Ereignisweiterleitung, Befehlssuche und Fehlersignalisierung\n- `src/extensibility/hooks/tool-wrapper.ts` — Pre/Post-Werkzeug-Abfangwrapper\n- `src/extensibility/hooks/index.ts` — Exporte/Re-Exporte\n\n## Was ein Hook-Modul ist\n\nEin Hook-Modul muss eine Factory als Standard-Export bereitstellen:\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function hook(pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName === \"bash\" && String(event.input.command ?? \"\").includes(\"rm -rf\")) {\n   return { block: true, reason: \"blocked by policy\" };\n  }\n });\n}\n```\n\nDie Factory kann:\n\n- Ereignis-Handler mit `pi.on(...)` registrieren\n- Persistente benutzerdefinierte Nachrichten mit `pi.sendMessage(...)` senden\n- Nicht-LLM-Status mit `pi.appendEntry(...)` persistieren\n- Slash-Befehle über `pi.registerCommand(...)` registrieren\n- Benutzerdefinierte Nachrichten-Renderer über `pi.registerMessageRenderer(...)` registrieren\n- Shell-Befehle über `pi.exec(...)` ausführen\n\n## Erkennung und Laden\n\n`discoverAndLoadHooks(configuredPaths, cwd)` führt folgende Schritte aus:\n\n1. Erkannte Hooks aus der Capability-Registry laden (`loadCapability(\"hooks\")`)\n2. Explizit konfigurierte Pfade anhängen (dedupliziert nach absolutem Pfad)\n3. `loadHooks(allPaths, cwd)` aufrufen\n\n`loadHooks` importiert anschließend jeden Pfad und erwartet eine `default`-Funktion.\n\n### Pfadauflösung\n\n`loader.ts` löst Hook-Pfade wie folgt auf:\n\n- Absoluter Pfad: wird unverändert verwendet\n- `~`-Pfad: wird expandiert\n- Relativer Pfad: wird gegen `cwd` aufgelöst\n\n### Wichtige Legacy-Diskrepanz\n\nErkennungsanbieter für `hookCapability` modellieren weiterhin Shell-artige Pre/Post-Hook-Dateien (z. B. `.claude/hooks/pre/*`, `.xcsh/.../hooks/pre/*`).\n\nDer hier verwendete Hook-Loader nutzt dynamischen Modulimport und erfordert eine Standard-JS/TS-Hook-Factory. Wenn ein erkannter Hook-Pfad nicht als Modul importierbar ist, schlägt das Laden fehl und wird in `LoadHooksResult.errors` gemeldet.\n\n## Ereignisoberflächen\n\nHook-Ereignisse sind in `types.ts` stark typisiert.\n\n### Sitzungsereignisse\n\n- `session_start`\n- `session_before_switch` → kann `{ cancel?: boolean }` zurückgeben\n- `session_switch`\n- `session_before_branch` → kann `{ cancel?: boolean; skipConversationRestore?: boolean }` zurückgeben\n- `session_branch`\n- `session_before_compact` → kann `{ cancel?: boolean; compaction?: CompactionResult }` zurückgeben\n- `session.compacting` → kann `{ context?: string[]; prompt?: string; preserveData?: Record<string, unknown> }` zurückgeben\n- `session_compact`\n- `session_before_tree` → kann `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }` zurückgeben\n- `session_tree`\n- `session_shutdown`\n\n### Agenten-/Kontextereignisse\n\n- `context` → kann `{ messages?: Message[] }` zurückgeben\n- `before_agent_start` → kann `{ message?: { customType; content; display; details } }` zurückgeben\n- `agent_start`\n- `agent_end`\n- `turn_start`\n- `turn_end`\n- `auto_compaction_start`\n- `auto_compaction_end`\n- `auto_retry_start`\n- `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### Werkzeugereignisse (Pre/Post-Modell)\n\n- `tool_call` (vor der Ausführung) → kann `{ block?: boolean; reason?: string }` zurückgeben\n- `tool_result` (nach der Ausführung) → kann `{ content?; details?; isError? }` zurückgeben\n\nDies ist das Pre/Post-Abfangmodell des Hook-Subsystems.\n\n```text\nHook-Werkzeug-Abfangablauf\n\ntool_call-Handler\n   │\n   ├─ ein { block: true }? ── ja ──> throw (Werkzeug blockiert)\n   │\n   └─ nein\n      │\n      ▼\n   Zugrunde liegendes Werkzeug ausführen\n      │\n      ├─ Erfolg ──> tool_result-Handler können { content, details } überschreiben\n      │\n      └─ Fehler  ──> tool_result(isError=true) emittieren, dann ursprünglichen Fehler erneut auslösen\n```\n\n## Ausführungsmodell und Mutationssemantik\n\n### 1) Vor der Ausführung: `tool_call`\n\n`HookToolWrapper.execute()` emittiert `tool_call` vor der Werkzeugausführung.\n\n- Wenn ein Handler `{ block: true }` zurückgibt, wird die Ausführung gestoppt\n- Wenn ein Handler eine Ausnahme auslöst, schlägt der Wrapper fehl und blockiert die Ausführung\n- Der zurückgegebene `reason` wird zum Text der ausgelösten Ausnahme\n\n### 2) Werkzeugausführung\n\nDas zugrunde liegende Werkzeug wird normal ausgeführt, sofern es nicht blockiert ist.\n\n### 3) Nach der Ausführung: `tool_result`\n\nNach dem Erfolg emittiert der Wrapper `tool_result` mit:\n\n- `toolName`, `toolCallId`, `input`\n- `content`\n- `details`\n- `isError: false`\n\nWenn ein Handler Überschreibungen zurückgibt:\n\n- `content` kann den Ergebnisinhalt ersetzen\n- `details` kann die Ergebnisdetails ersetzen\n\nBei einem Werkzeugfehler emittiert der Wrapper `tool_result` mit `isError: true` und Fehlertext-Inhalt und löst dann den ursprünglichen Fehler erneut aus.\n\n### Was Hooks mutieren können\n\n- LLM-Kontext für einen einzelnen Aufruf über `context` (Ersetzungskette für `messages`)\n- Werkzeugausgabe-Inhalt/-Details bei erfolgreichen Werkzeugaufrufen (`tool_result`-Pfad)\n- Vor-Agenten injizierte Nachricht über `before_agent_start`\n- Abbruch/benutzerdefinierte Komprimierung/Baumverhalten über `session_before_*` und `session.compacting`\n\n### Was Hooks in dieser Implementierung nicht mutieren können\n\n- Rohe Werkzeug-Eingabeparameter an Ort und Stelle (nur Blockieren/Zulassen bei `tool_call`)\n- Ausführungsfortsetzung nach ausgelösten Werkzeugfehlern (Fehlerpfad löst erneut aus)\n- Finalen Erfolgs-/Fehlerstatus im Wrapper-Verhalten (zurückgegebenes `isError` ist typisiert, wird aber nicht von `HookToolWrapper` angewendet)\n\n## Reihenfolge und Konfliktverhalten\n\n### Reihenfolge auf Erkennungsebene\n\nCapability-Anbieter werden nach Priorität sortiert (höchste zuerst). Deduplizierung erfolgt nach Capability-Schlüssel, der erste gewinnt.\n\nFür `hooks` lautet der Capability-Schlüssel `${type}:${tool}:${name}`. Überlagerte Duplikate von Anbietern mit niedrigerer Priorität werden markiert und aus der effektiven Erkennungsliste ausgeschlossen.\n\n### Ladereihenfolge\n\n`discoverAndLoadHooks` erstellt eine flache `allPaths`-Liste, dedupliziert nach aufgelöstem absolutem Pfad, dann iteriert `loadHooks` in dieser Reihenfolge.\nDie Dateireihenfolge innerhalb jedes erkannten Verzeichnisses hängt von der `readdir`-Ausgabe ab; der Hook-Loader führt keine zusätzliche Sortierung durch.\n\n### Handler-Reihenfolge zur Laufzeit\n\nInnerhalb von `HookRunner` ist die Reihenfolge durch die Registrierungssequenz deterministisch:\n\n1. Reihenfolge des Hooks-Arrays\n2. Handler-Registrierungsreihenfolge pro Hook/Ereignis\n\nKonfliktverhalten nach Ereignistyp:\n\n- `tool_call`: Das zuletzt zurückgegebene Ergebnis gewinnt, sofern kein Handler blockiert; der erste Block schließt kurz\n- `tool_result`: Das zuletzt zurückgegebene Überschreiben gewinnt (kein Kurzschluss)\n- `context`: Verkettet; jeder Handler empfängt die Nachrichtenausgabe des vorherigen Handlers\n- `before_agent_start`: Die erste zurückgegebene Nachricht wird beibehalten; spätere Nachrichten werden ignoriert\n- `session_before_*`: Das zuletzt zurückgegebene Ergebnis wird verfolgt; `cancel: true` schließt sofort kurz\n- `session.compacting`: Das zuletzt zurückgegebene Ergebnis gewinnt\n\nKonflikte bei Befehlen/Renderern:\n\n- `getCommand(name)` gibt den ersten Treffer über alle Hooks hinweg zurück (zuerst geladen gewinnt)\n- `getMessageRenderer(customType)` gibt den ersten Treffer zurück\n- `getRegisteredCommands()` gibt alle Befehle zurück (keine Deduplizierung)\n\n## UI-Interaktionen (`HookContext.ui`)\n\n`HookUIContext` umfasst:\n\n- `select`, `confirm`, `input`, `editor`\n- `notify`\n- `setStatus`\n- `custom`\n- `setEditorText`, `getEditorText`\n- `theme`-Getter\n\n`ctx.hasUI` gibt an, ob eine interaktive Benutzeroberfläche verfügbar ist.\n\nBeim Betrieb ohne Benutzeroberfläche ist das Standardverhalten des No-Op-Kontexts:\n\n- `select/input/editor` geben `undefined` zurück\n- `confirm` gibt `false` zurück\n- `notify`, `setStatus`, `setEditorText` sind No-Ops\n- `getEditorText` gibt `\"\"` zurück\n\n### Statuszeilen-Verhalten\n\nHook-Statustext, der über `ctx.ui.setStatus(key, text)` gesetzt wird:\n\n- wird pro Schlüssel gespeichert\n- wird nach Schlüsselname sortiert\n- wird bereinigt (`\\r`, `\\n`, `\\t` → Leerzeichen; wiederholte Leerzeichen werden zusammengefasst)\n- wird für die Anzeige verbunden und auf die Breite gekürzt\n\n## Fehlerweiterleitung und Fallback\n\n### Zur Ladezeit\n\n- Ungültiges Modul oder fehlender Standard-Export → wird in `LoadHooksResult.errors` erfasst\n- Das Laden wird für andere Hooks fortgesetzt\n\n### Zur Ereigniszeit\n\n`HookRunner.emit(...)` fängt Handler-Fehler für die meisten Ereignisse ab und emittiert `HookError` an Listener (`hookPath`, `event`, `error`), dann wird fortgefahren.\n\n`emitToolCall(...)` ist strenger: Handler-Fehler werden dort nicht unterdrückt; sie propagieren zum Aufrufer. In `HookToolWrapper` blockiert dies den Werkzeugaufruf (Fail-Safe).\n\n## Realistische API-Beispiele\n\n### Unsichere Bash-Befehle blockieren\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName !== \"bash\") return;\n  const cmd = String(event.input.command ?? \"\");\n  if (!cmd.includes(\"rm -rf\")) return;\n\n  if (!ctx.hasUI) return { block: true, reason: \"rm -rf blocked (no UI)\" };\n  const ok = await ctx.ui.confirm(\"Dangerous command\", `Allow: ${cmd}`);\n  if (!ok) return { block: true, reason: \"user denied command\" };\n });\n}\n```\n\n### Werkzeugausgabe nach der Ausführung schwärzen\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_result\", async event => {\n  if (event.toolName !== \"read\" || event.isError) return;\n\n  const redacted = event.content.map(chunk => {\n   if (chunk.type !== \"text\") return chunk;\n   return { ...chunk, text: chunk.text.replaceAll(/API_KEY=\\S+/g, \"API_KEY=[REDACTED]\") };\n  });\n\n  return { content: redacted };\n });\n}\n```\n\n### Modellkontext pro LLM-Aufruf modifizieren\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"context\", async event => {\n  const filtered = event.messages.filter(msg => !(msg.role === \"custom\" && msg.customType === \"debug-only\"));\n  return { messages: filtered };\n });\n}\n```\n\n### Slash-Befehl mit befehlssicheren Kontextmethoden registrieren\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.registerCommand(\"handoff\", {\n  description: \"Create a new session with setup message\",\n  handler: async (_args, ctx) => {\n   await ctx.waitForIdle();\n   await ctx.newSession({\n    parentSession: ctx.sessionManager.getSessionFile(),\n    setup: async sm => {\n     sm.appendMessage({\n      role: \"user\",\n      content: [{ type: \"text\", text: \"Continue from prior session summary.\" }],\n      timestamp: Date.now(),\n     });\n    },\n   });\n  },\n });\n}\n```\n\n## Export-Oberfläche\n\n`src/extensibility/hooks/index.ts` exportiert:\n\n- Lade-APIs (`discoverAndLoadHooks`, `loadHooks`)\n- Runner und Wrapper (`HookRunner`, `HookToolWrapper`)\n- Alle Hook-Typen\n- `execCommand`-Re-Export\n\nUnd das Paket-Root (`src/index.ts`) re-exportiert Hook-**Typen** als Legacy-Kompatibilitätsoberfläche.\n",
	"de/configuration/porting-from-pi-mono.md": "---\ntitle: 'Portierung von pi-mono: Ein praktischer Merge-Leitfaden'\ndescription: >-\n  Praktischer Leitfaden für die Migration von Code aus dem pi-mono-Monorepo in\n  die xcsh-Codebasis.\nsidebar:\n  order: 9\n  label: Portierung von pi-mono\ni18n:\n  sourceHash: fd4e8c09303d\n  translator: machine\n---\n\n# Portierung von pi-mono: Ein praktischer Merge-Leitfaden\n\nDieser Leitfaden ist eine wiederholbare Checkliste für die Portierung von Änderungen aus pi-mono in dieses Repository.\nVerwenden Sie ihn für jeden Merge: einzelne Datei, Feature-Branch oder vollständiger Release-Sync.\n\n## Letzter Synchronisationspunkt\n\n**Commit:** `b21b42d032919de2f2e6920a76fa9a37c3920c0a`\n**Datum:** 2026-03-22\n\nAktualisieren Sie diesen Abschnitt nach jeder Synchronisation; verwenden Sie nicht den vorherigen Bereich erneut.\n\nWenn Sie eine neue Synchronisation starten, generieren Sie Patches ab diesem Commit:\n\n```bash\ngit format-patch b21b42d032919de2f2e6920a76fa9a37c3920c0a..HEAD --stdout > changes.patch\n```\n\n## 0) Umfang definieren\n\n- Identifizieren Sie die Upstream-Referenz (Commit, Tag oder PR).\n- Listen Sie die Pakete oder Ordner auf, die Sie bearbeiten möchten.\n- Entscheiden Sie, welche Features im Umfang enthalten sind und welche absichtlich übersprungen werden.\n\n## 1) Code sicher übernehmen\n\n- Bevorzugen Sie ein sauberes, fokussiertes Diff anstelle einer Kopie des gesamten Inhalts.\n- Vermeiden Sie das Kopieren von Build-Artefakten oder generierten Dateien.\n- Wenn Upstream neue Dateien hinzugefügt hat, fügen Sie diese explizit hinzu und überprüfen Sie den Inhalt.\n\n## 2) Import-Erweiterungskonventionen einhalten\n\nDie meisten Runtime-TypeScript-Quellen lassen `.js` bei internen Imports weg, aber einige Test-/Bench-Einstiegspunkte behalten `.js` für ESM-Runtime-Kompatibilität bei. Folgen Sie dem bestehenden Stil des lokalen Pakets; entfernen Sie nicht pauschal Erweiterungen.\n\n- In `packages/coding-agent`-Runtime-Quellen interne Imports ohne Erweiterung belassen, es sei denn, Nicht-TS-Assets werden importiert.\n- In `packages/tui/test` und `packages/natives/bench` `.js` beibehalten, wo umgebende Dateien es bereits verwenden.\n- Echte Dateierweiterungen beibehalten, wenn sie vom Tooling benötigt werden (z. B. `.json`, `.css`, `.md`-Text-Einbettungen).\n- Beispiel: `import { x } from \"./foo.js\";` → `import { x } from \"./foo\";` (nur wenn die Paketkonvention erweiterungslos ist).\n\n## 3) Import-Scopes ersetzen\n\nUpstream verwendet andere Paket-Scopes. Ersetzen Sie diese konsistent.\n\n- Ersetzen Sie alte Scopes durch den hier verwendeten lokalen Scope.\n- Beispiele (an die tatsächlich portierten Pakete anpassen):\n  - `@mariozechner/pi-coding-agent` → `@f5-sales-demo/xcsh`\n  - `@mariozechner/pi-agent-core` → `@f5-sales-demo/pi-agent-core`\n  - `@mariozechner/pi-tui` → `@f5-sales-demo/pi-tui`\n  - `@mariozechner/pi-ai` → `@f5-sales-demo/pi-ai`\n\n## 4) Bun-APIs verwenden, wo sie Node verbessern\n\nWir laufen auf Bun. Ersetzen Sie Node-APIs nur, wenn Bun eine bessere Alternative bietet.\n\n**Ersetzen:**\n\n- Prozess-Spawning: `child_process.spawn` → Bun Shell `$` für einfache Befehle, `Bun.spawn`/`Bun.spawnSync` für Streaming oder lang laufende Arbeiten\n- Datei-I/O: `fs.readFileSync` → `Bun.file().text()` / `Bun.write()`\n- HTTP-Clients: `node-fetch`, `axios` → natives `fetch`\n- Krypto-Hashing: `node:crypto` → Web Crypto oder `Bun.hash`\n- SQLite: `better-sqlite3` → `bun:sqlite`\n- Env-Laden: `dotenv` → Bun lädt `.env` automatisch\n\n**NICHT ersetzen (diese funktionieren in Bun einwandfrei):**\n\n- `os.homedir()` — NICHT ersetzen durch `Bun.env.HOME`, `Bun.env.HOME` oder wörtliches `\"~\"`\n- `os.tmpdir()` — NICHT ersetzen durch `Bun.env.TMPDIR || \"/tmp\"` oder hartcodierte Pfade\n- `fs.mkdtempSync()` — NICHT ersetzen durch manuelle Pfadkonstruktion\n- `path.join()`, `path.resolve()`, usw. — diese sind in Ordnung\n\n**Import-Stil:** Verwenden Sie das `node:`-Präfix nur mit Namespace-Imports (keine benannten Imports aus `node:fs` oder `node:path`).\n\n**Zusätzliche Bun-Konventionen:**\n\n- Bevorzugen Sie Bun Shell `$` für kurze, nicht-streamende Befehle; verwenden Sie `Bun.spawn` nur, wenn Sie Streaming-I/O oder Prozesskontrolle benötigen.\n- Verwenden Sie `Bun.file()`/`Bun.write()` für Dateien und `node:fs/promises` für Verzeichnisse.\n- Vermeiden Sie `Bun.file().exists()`-Prüfungen; verwenden Sie `isEnoent`-Behandlung in try/catch.\n- Bevorzugen Sie `Bun.sleep(ms)` gegenüber `setTimeout`-Wrappern.\n\n**Falsch:**\n\n```typescript\n// BROKEN: env vars may be undefined, \"~\" is not expanded\nconst home = Bun.env.HOME || \"~\";\nconst tmp = Bun.env.TMPDIR || \"/tmp\";\n```\n\n**Korrekt:**\n\n```typescript\nimport * as os from \"node:os\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst configDir = path.join(os.homedir(), \".config\", \"myapp\");\nconst tempDir = fs.mkdtempSync(path.join(os.tmpdir(), \"myapp-\"));\n```\n\n## 5) Bun-Einbettungen bevorzugen (kein Kopieren)\n\nKopieren Sie keine Runtime-Assets oder Vendor-Dateien zur Build-Zeit.\n\n- Wenn Upstream Assets in einen dist-Ordner kopiert, ersetzen Sie dies durch Bun-freundliche Einbettungen.\n- Prompts sind statische `.md`-Dateien; verwenden Sie Bun-Text-Imports (`with { type: \"text\" }`) und Handlebars anstelle von Inline-Prompt-Strings.\n- Verwenden Sie `import.meta.dir` + `Bun.file` zum Laden benachbarter Nicht-Text-Ressourcen.\n- Behalten Sie Assets im Repository und lassen Sie den Bundler sie einschließen.\n- Eliminieren Sie Kopier-Skripte, es sei denn, der Benutzer fordert sie explizit an.\n- Wenn Upstream eine gebündelte Fallback-Datei zur Laufzeit liest, ersetzen Sie Dateisystem-Reads durch einen Bun-Text-Einbettungs-Import.\n  - Beispiel (Codex-Anweisungen-Fallback):\n    - `const FALLBACK_PROMPT_PATH = join(import.meta.dir, \"codex-instructions.md\");` -> entfernt\n    - `import FALLBACK_INSTRUCTIONS from \"./codex-instructions.md\" with { type: \"text\" };`\n    - Verwenden Sie `return FALLBACK_INSTRUCTIONS;` anstelle von `readFileSync(FALLBACK_PROMPT_PATH, \"utf8\")`\n\n## 6) `package.json` sorgfältig portieren\n\nBehandeln Sie `package.json` als Vertrag. Mergen Sie bewusst.\n\n- Behalten Sie bestehende `name`, `version`, `type`, `exports` und `bin` bei, es sei denn, die Portierung erfordert Änderungen.\n- Ersetzen Sie npm/node-Skripte durch Bun-Äquivalente (z. B. `bun check`, `bun test`).\n- Stellen Sie sicher, dass Abhängigkeiten den korrekten Scope verwenden.\n- Downgraden Sie keine Abhängigkeiten, um Typfehler zu beheben; upgraden Sie stattdessen.\n- Validieren Sie Workspace-Paketlinks und `peerDependencies`.\n\n## 7) Code-Stil und Tooling angleichen\n\n- Behalten Sie bestehende Formatierungskonventionen bei.\n- Führen Sie kein `any` ein, es sei denn, es ist erforderlich.\n- Vermeiden Sie dynamische Imports und Inline-Typ-Imports; verwenden Sie nur Top-Level-Imports.\n- Erstellen Sie Prompts niemals im Code; Prompts sind statische `.md`-Dateien, die mit Handlebars gerendert werden.\n- Im coding-agent niemals `console.log`/`console.warn`/`console.error` verwenden; verwenden Sie `logger` aus `@f5-sales-demo/pi-utils`.\n- Verwenden Sie `Promise.withResolvers()` anstelle von `new Promise((resolve, reject) => ...)`.\n- **Keine `private`/`protected`/`public`-Schlüsselwörter bei Klassenfeldern oder -methoden.** Verwenden Sie ES `#`-private-Felder für Kapselung; lassen Sie zugängliche Member ohne Schlüsselwort (bare). Die einzige Ausnahme sind Constructor-Parameter-Properties (`constructor(private readonly x: T)`), wo das Schlüsselwort von TypeScript verlangt wird. Wenn Sie Upstream-Code portieren, der `private foo` oder `protected bar` verwendet, konvertieren Sie zu `#foo` (privat) oder bare `bar` (zugänglich).\n- Bevorzugen Sie vorhandene Helfer und Utilities gegenüber neuem Ad-hoc-Code.\n- Bewahren Sie die bereits in diesem Repository vorgenommenen Bun-first-Infrastrukturänderungen:\n  - Runtime ist Bun (keine Node-Einstiegspunkte).\n  - Paketmanager ist Bun (keine npm-Lockfiles).\n  - Schwere Node-APIs (`child_process`, `readline`) sind durch Bun-Äquivalente ersetzt.\n  - Leichtgewichtige Node-APIs (`os.homedir`, `os.tmpdir`, `fs.mkdtempSync`, `path.*`) werden beibehalten.\n  - CLI-Shebangs verwenden `bun` (nicht `node`, nicht `tsx`).\n  - Pakete verwenden Quelldateien direkt (kein TypeScript-Build-Schritt).\n  - CI-Workflows verwenden Bun für Install/Check/Test.\n\n## 8) Alte Kompatibilitätsschichten entfernen\n\nSofern nicht angefordert, entfernen Sie Upstream-Kompatibilitäts-Shims.\n\n- Löschen Sie alte APIs, die ersetzt wurden.\n- Aktualisieren Sie alle Aufrufstellen direkt auf die neue API.\n- Behalten Sie keine `*_v2` oder parallelen Versionen bei.\n\n## 9) Dokumentation und Referenzen aktualisieren\n\n- Ersetzen Sie pi-mono-Repository-Links, wo angemessen.\n- Aktualisieren Sie Beispiele zur Verwendung von Bun und korrekten Paket-Scopes.\n- Stellen Sie sicher, dass README-Anweisungen noch dem aktuellen Repository-Verhalten entsprechen.\n\n## 10) Die Portierung validieren\n\nFühren Sie die Standardprüfungen nach Änderungen durch:\n\n- `bun check`\n\nWenn das Repository bereits fehlschlagende Prüfungen hat, die nichts mit Ihren Änderungen zu tun haben, weisen Sie darauf hin.\nTests verwenden Buns Runner (nicht Vitest), aber führen Sie `bun test` nur aus, wenn explizit angefordert.\n\n## 11) Verbesserte Features schützen (Regressions-Fallen-Liste)\n\nWenn Sie das Verhalten lokal bereits verbessert haben, behandeln Sie dies als **nicht verhandelbar**. Notieren Sie vor der Portierung\ndie Verbesserungen und fügen Sie explizite Prüfungen hinzu, damit sie beim Merge nicht verloren gehen.\n\n- **Erwartetes Verhalten einfrieren**: Fügen Sie für jede Verbesserung eine kurze \"Vorher/Nachher\"-Notiz hinzu (Eingaben, Ausgaben,\n  Standardwerte, Grenzfälle). Dies verhindert stilles Zurückrollen.\n- **Alt → Neu-APIs abbilden**: Wenn Upstream Konzepte umbenannt hat (Hooks → Extensions, Custom Tools → Tools, usw.),\n  stellen Sie sicher, dass jeder alte Einstiegspunkt weiterhin durchgereicht wird. Ein verpasstes Flag oder Export bedeutet verlorene Funktionalität.\n- **Exports überprüfen**: Prüfen Sie `package.json` `exports`, öffentliche Typen und Barrel-Dateien. Upstream-Portierungen\n  vergessen oft, lokale Ergänzungen erneut zu exportieren.\n- **Nicht-Happy-Paths abdecken**: Wenn Sie Fehlerbehandlung, Timeouts oder Fallback-Logik behoben haben, fügen Sie einen Test oder\n  zumindest eine manuelle Checkliste hinzu, die diese Pfade durchläuft.\n- **Standardwerte und Config-Merge-Reihenfolge prüfen**: Verbesserungen leben oft in Standardwerten. Bestätigen Sie, dass neue Standardwerte\n  nicht zurückgesetzt wurden (z. B. neue Config-Priorität, deaktivierte Features, Tool-Listen).\n- **Env/Shell-Verhalten auditieren**: Wenn Sie Ausführung oder Sandboxing behoben haben, überprüfen Sie, ob der neue Pfad weiterhin Ihre\n  bereinigten Umgebungsvariablen verwendet und keine Alias/Funktions-Überschreibungen erneut einführt.\n- **Gezielte Beispiele erneut ausführen**: Behalten Sie einen minimalen Satz von \"known good\"-Beispielen und führen Sie diese nach der Portierung aus\n  (CLI-Flags, Extension-Registrierung, Tool-Ausführung).\n\n## 12) Überarbeiteten Code erkennen und behandeln\n\nPrüfen Sie vor der Portierung einer Datei, ob Upstream sie signifikant refaktoriert hat:\n\n```bash\n# Compare the file you're about to port against what you have locally\ngit diff HEAD upstream/main -- path/to/file.ts\n```\n\nWenn das Diff zeigt, dass die Datei **überarbeitet** wurde (nicht nur gepatcht):\n\n- Neue Abstraktionen, umbenannte Konzepte, zusammengeführte Module, geänderter Datenfluss\n\nDann müssen Sie **die neue Implementierung gründlich lesen**, bevor Sie portieren. Blindes Mergen von überarbeitetem Code verliert Funktionalität, weil:\n\nHinweis: Der interaktive Modus wurde kürzlich in Controllers/Utils/Types aufgeteilt. Wenn Sie verwandte Änderungen zurückportieren, portieren Sie Updates in die einzelnen Dateien, die wir erstellt haben, und stellen Sie sicher, dass die `interactive-mode.ts`-Verdrahtung synchron bleibt.\n\n1. **Standardwerte ändern sich stillschweigend** - Eine neue Variable `defaultFoo = [a, b]` kann ein altes `getAllFoo()` ersetzen, das `[a, b, c, d, e]` zurückgab.\n\n2. **API-Optionen gehen verloren** - Wenn Systeme zusammengeführt werden (z. B. `hooks` + `customTools` → `extensions`), werden alte Optionen möglicherweise nicht zur neuen Implementierung durchgereicht.\n\n3. **Code-Pfade werden veraltet** - Ein umbenanntes Konzept (z. B. `hookMessage` → `custom`) erfordert Aktualisierungen in jedem Switch-Statement, Type Guard und Handler — nicht nur in der Definition.\n\n4. **Kontext/Fähigkeiten schrumpfen** - Alte APIs haben möglicherweise `{ logger, typebox, pi }` exponiert, was neue APIs vergessen haben einzuschließen.\n\n### Semantischer Portierungsprozess\n\nWenn Upstream ein Modul überarbeitet hat:\n\n1. **Die alte Implementierung lesen** - Verstehen Sie, was sie tat, welche Optionen sie akzeptierte, was sie exponierte.\n\n2. **Die neue Implementierung lesen** - Verstehen Sie die neuen Abstraktionen und wie sie auf altes Verhalten abgebildet werden.\n\n3. **Feature-Parität überprüfen** - Bestätigen Sie für jede Fähigkeit im alten Code, dass der neue Code sie beibehält oder explizit entfernt.\n\n4. **Nach Überbleibseln suchen** - Suchen Sie nach alten Namen/Konzepten, die möglicherweise in Switch-Statements, Handlern, UI-Komponenten übersehen wurden.\n\n5. **Die Grenzen testen** - CLI-Flags, SDK-Optionen, Event-Handler, Standardwerte — hier verstecken sich Regressionen.\n\n### Schnelle Prüfungen\n\n```bash\n# Find all uses of an old concept that may need updating\nrg \"oldConceptName\" --type ts\n\n# Compare default values between versions\ngit show upstream/main:path/to/file.ts | rg \"default|DEFAULT\"\n\n# Check if all enum/union values have handlers\nrg \"case \\\"\" path/to/file.ts\n```\n\n## 13) Schnelle Audit-Checkliste\n\nVerwenden Sie dies als letzten Durchgang, bevor Sie fertig sind:\n\n- [ ] Import-Erweiterungen folgen der lokalen Paketkonvention (kein pauschales `.js`-Entfernen)\n- [ ] Keine Node-only-APIs in neuem/portiertem Code\n- [ ] Alle Paket-Scopes aktualisiert\n- [ ] `package.json`-Skripte verwenden Bun\n- [ ] Prompts sind `.md`-Text-Imports (keine Inline-Prompt-Strings)\n- [ ] Kein `console.*` im coding-agent (verwenden Sie `logger`)\n- [ ] Assets laden über Bun-Einbettungsmuster (keine Kopier-Skripte)\n- [ ] Tests oder Prüfungen laufen (oder sind explizit als blockiert markiert)\n- [ ] Keine Funktionalitätsregressionen (siehe Abschnitte 11-12)\n\n## 14) Commit-Nachricht-Format\n\nWenn Sie einen Backport committen, folgen Sie dem Repository-Format `<type>(scope): <Beschreibung in Vergangenheitsform>` und behalten Sie den Commit-Bereich im Titel bei.\n\n```\nfix(coding-agent): backported pi-mono changes (<from>..<to>)\n\npackages/<package>:\n- <type>: <description>\n- <type>: <description> (#<issue> by @<contributor>)\n\npackages/<other-package>:\n- <type>: <description>\n```\n\n**Beispiel:**\n\n```\nfix(coding-agent): backported pi-mono changes (9f3eef65f..52532c7c0)\n\npackages/ai:\n- fix: handle \"sensitive\" stop reason from Anthropic API\n- fix: normalize tool call IDs with special characters for Responses API\n- fix: add overflow detection for Bedrock, MiniMax, Kimi providers\n- fix: 429 status is rate limiting, not context overflow\n\npackages/tui:\n- fix: refactored autocomplete state tracking\n- fix: file autocomplete should not trigger on empty text\n- fix: configurable autocomplete max visible items\n- fix: improved table column width calculation with word-aware wrapping\n\npackages/coding-agent:\n- fix: preserve external config.yml edits on save (#1046 by @nicobailonMD)\n- fix: resolve macOS NFD and curly quote variants in file paths\n```\n\n**Regeln:**\n\n- Änderungen nach Paket gruppieren\n- Konventionelle Commit-Typen verwenden (`fix`, `feat`, `refactor`, `perf`, `docs`)\n- Upstream-Issue/PR-Nummern und Contributor-Zuordnung für externe Beiträge einschließen\n- Der Commit-Bereich im Titel hilft beim Tracking von Synchronisationspunkten\n\n## 15) Beabsichtigte Abweichungen\n\nUnser Fork hat architektonische Entscheidungen, die von Upstream abweichen. **Portieren Sie diese Upstream-Muster nicht:**\n\n### UI-Architektur\n\n| Upstream                                    | Unser Fork                                                | Grund                                                                 |\n| ------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------- |\n| `FooterDataProvider`-Klasse                 | `StatusLineComponent`                                     | Einfachere, integrierte Statuszeile                                   |\n| `ctx.ui.setHeader()` / `ctx.ui.setFooter()` | Stub in Nicht-TUI-Modi                                    | Implementiert in TUI, No-Op anderswo                                  |\n| `ctx.ui.setEditorComponent()`               | Stub in Nicht-TUI-Modi                                    | Implementiert in TUI, No-Op anderswo                                  |\n| `InteractiveModeOptions`-Options-Objekt     | Positionsbasierte Constructor-Args (Options-Typ weiterhin exportiert) | Constructor-Signatur beibehalten; Typ aktualisieren, wenn Upstream Felder hinzufügt |\n\n### Komponentenbenennung\n\n| Upstream                     | Unser Fork              |\n| ---------------------------- | ----------------------- |\n| `extension-input.ts`         | `hook-input.ts`         |\n| `extension-selector.ts`      | `hook-selector.ts`      |\n| `ExtensionInputComponent`    | `HookInputComponent`    |\n| `ExtensionSelectorComponent` | `HookSelectorComponent` |\n\n### API-Benennung\n\n| Upstream                                 | Unser Fork                               | Anmerkungen                               |\n| ---------------------------------------- | ---------------------------------------- | ----------------------------------------- |\n| `sessionManager.appendSessionInfo(name)` | `sessionManager.setSessionName(name)`    | Wir verwenden durchgehend `sessionName`   |\n| `sessionManager.getSessionName()`        | `sessionManager.getSessionName()`        | Gleich (wir haben vereinheitlicht, um mit Upstreams RPC übereinzustimmen) |\n| `agent.sessionName` / `setSessionName()` | `agent.sessionName` / `setSessionName()` | Gleich                                    |\n\n### Dateikonsolidierung\n\n| Upstream                                           | Unser Fork                              | Grund                                   |\n| -------------------------------------------------- | --------------------------------------- | --------------------------------------- |\n| `clipboard.ts` + `clipboard-image.ts` (Tool-Dateien) | `@f5-sales-demo/pi-natives` Clipboard-Modul | In N-API-native Implementierung zusammengeführt |\n\n### Test-Framework\n\n| Upstream                  | Unser Fork                    |\n| ------------------------- | ----------------------------- |\n| `vitest` mit `vi.mock()`  | `bun:test` mit `vi` von Bun   |\n| `node:test`-Assertions    | `expect()`-Matcher            |\n\n### Tool-Architektur\n\n| Upstream                            | Unser Fork                                                        | Anmerkungen                                               |\n| ----------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------- |\n| `createTool(cwd: string, options?)` | `createTools(session: ToolSession)` via `BUILTIN_TOOLS`-Registry  | Tool-Factories akzeptieren `ToolSession` und können `null` zurückgeben |\n| Per-Tool `*Operations`-Interfaces   | Per-Tool-Interfaces bleiben (`FindOperations`, `GrepOperations`)  | Verwendet für SSH/Remote-Überschreibungen                 |\n| Node.js `fs/promises` überall       | `Bun.file()`/`Bun.write()` für Dateien; `node:fs/promises` für Verzeichnisse | Bun-APIs bevorzugen, wenn sie vereinfachen               |\n\n### Auth-Speicherung\n\n| Upstream                        | Unser Fork                                  | Anmerkungen                                  |\n| ------------------------------- | ------------------------------------------- | -------------------------------------------- |\n| `proper-lockfile` + `auth.json` | `agent.db` (bun:sqlite)                     | Anmeldedaten ausschließlich in `agent.db` gespeichert |\n| Einzelne Anmeldedaten pro Provider | Multi-Credential mit Round-Robin-Auswahl   | Session-Affinität und Backoff-Logik beibehalten |\n\n### Extensions\n\n| Upstream                      | Unser Fork                                 |\n| ----------------------------- | ------------------------------------------ |\n| `jiti` für TypeScript-Laden   | Natives Bun `import()`                     |\n| `pkg.pi`-Manifest-Feld        | `pkg.xcsh ?? pkg.pi` (unser Namespace bevorzugt) |\n\n### Diese Upstream-Features überspringen\n\nBeim Portieren diese Dateien/Features **vollständig überspringen**:\n\n- `footer-data-provider.ts` — wir verwenden StatusLineComponent\n- `clipboard-image.ts` — Clipboard ist im `@f5-sales-demo/pi-natives` N-API-Modul\n- GitHub-Workflow-Dateien — wir haben unsere eigene CI\n- `models.generated.ts` — automatisch generiert, lokal neu generieren (als models.json stattdessen)\n\n### Features, die wir hinzugefügt haben (diese bewahren)\n\nDiese existieren in unserem Fork, aber nicht in Upstream. **Niemals überschreiben:**\n\n- `StatusLineComponent` im interaktiven Modus\n- Multi-Credential-Auth mit Session-Affinität\n- Fähigkeitsbasiertes Discovery-System (`defineCapability`, `registerProvider`, `loadCapability`, `skillCapability`, usw.)\n- MCP/Exa/SSH-Integrationen\n- LSP-Writethrough für Format-on-Save\n- Bash-Interception (`checkBashInterception`)\n- Fuzzy-Pfadvorschläge im Read-Tool\n",
	"de/configuration/rpc.md": "---\ntitle: RPC-Protokollreferenz\ndescription: >-\n  JSON-RPC-Protokollreferenz für die Interprozesskommunikation zwischen\n  xcsh-Komponenten.\nsidebar:\n  order: 5\n  label: RPC-Protokoll\ni18n:\n  sourceHash: b4a3ddaf08ab\n  translator: machine\n---\n\n# RPC-Protokollreferenz\n\nDer RPC-Modus führt den Coding-Agenten als zeilengetrenntes JSON-Protokoll über stdio aus.\n\n- **stdin**: Befehle (`RpcCommand`) und Erweiterungs-UI-Antworten\n- **stdout**: Befehlsantworten (`RpcResponse`), Sitzungs-/Agenten-Ereignisse, Erweiterungs-UI-Anfragen\n\nPrimäre Implementierung:\n\n- `src/modes/rpc/rpc-mode.ts`\n- `src/modes/rpc/rpc-types.ts`\n- `src/session/agent-session.ts`\n- `packages/agent/src/agent.ts`\n- `packages/agent/src/agent-loop.ts`\n\n## Start\n\n```bash\nxcsh --mode rpc [reguläre CLI-Optionen]\n```\n\nHinweise zum Verhalten:\n\n- `@file`-CLI-Argumente werden im RPC-Modus abgelehnt.\n- Der RPC-Modus deaktiviert standardmäßig die automatische Generierung von Sitzungstiteln, um einen zusätzlichen Modellaufruf zu vermeiden.\n- Der RPC-Modus setzt workflow-verändernde `todo.*`-, `task.*`- und `async.*`-Einstellungen auf ihre eingebauten Standardwerte zurück, anstatt Benutzerüberschreibungen zu übernehmen.\n- Der Prozess liest stdin als JSONL (`readJsonl(Bun.stdin.stream())`).\n- Wenn stdin geschlossen wird, beendet sich der Prozess mit Exit-Code `0`.\n- Antworten/Ereignisse werden als ein JSON-Objekt pro Zeile geschrieben.\n\n## Transport und Framing\n\nJeder Frame ist ein einzelnes JSON-Objekt gefolgt von `\\n`.\n\nEs gibt keinen Umschlag über die Objektstruktur hinaus.\n\n### Ausgehende Frame-Kategorien (stdout)\n\n1. `RpcResponse` (`{ type: \"response\", ... }`)\n2. `AgentSessionEvent`-Objekte (`agent_start`, `message_update`, etc.)\n3. `RpcExtensionUIRequest` (`{ type: \"extension_ui_request\", ... }`)\n4. Erweiterungsfehler (`{ type: \"extension_error\", extensionPath, event, error }`)\n\n### Eingehende Frame-Kategorien (stdin)\n\n1. `RpcCommand`\n2. `RpcExtensionUIResponse` (`{ type: \"extension_ui_response\", ... }`)\n\n## Anfrage/Antwort-Korrelation\n\nAlle Befehle akzeptieren ein optionales `id?: string`.\n\n- Falls angegeben, geben normale Befehlsantworten dieselbe `id` zurück.\n- `RpcClient` nutzt dies zur Auflösung ausstehender Anfragen.\n\nWichtiges Randverhalten zur Laufzeit:\n\n- Antworten auf unbekannte Befehle werden mit `id: undefined` ausgegeben (auch wenn die Anfrage eine `id` hatte).\n- Parse-/Handler-Ausnahmen in der Eingabeschleife geben `command: \"parse\"` mit `id: undefined` aus.\n- `prompt` und `abort_and_prompt` geben sofortigen Erfolg zurück und können dann eine spätere Fehlerantwort mit **derselben** ID ausgeben, falls die asynchrone Prompt-Planung fehlschlägt.\n\n## Befehlsschema (kanonisch)\n\n`RpcCommand` ist in `src/modes/rpc/rpc-types.ts` definiert:\n\n### Prompting\n\n- `{ id?, type: \"prompt\", message: string, images?: ImageContent[], streamingBehavior?: \"steer\" | \"followUp\" }`\n- `{ id?, type: \"steer\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"follow_up\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"abort\" }`\n- `{ id?, type: \"abort_and_prompt\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"new_session\", parentSession?: string }`\n\n### Zustand\n\n- `{ id?, type: \"get_state\" }`\n- `{ id?, type: \"set_todos\", phases: TodoPhase[] }`\n- `{ id?, type: \"set_host_tools\", tools: RpcHostToolDefinition[] }`\n\n### Modell\n\n- `{ id?, type: \"set_model\", provider: string, modelId: string }`\n- `{ id?, type: \"cycle_model\" }`\n- `{ id?, type: \"get_available_models\" }`\n\n### Denkmodus\n\n- `{ id?, type: \"set_thinking_level\", level: ThinkingLevel }`\n- `{ id?, type: \"cycle_thinking_level\" }`\n\n### Warteschlangenmodi\n\n- `{ id?, type: \"set_steering_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_follow_up_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_interrupt_mode\", mode: \"immediate\" | \"wait\" }`\n\n### Kompaktierung\n\n- `{ id?, type: \"compact\", customInstructions?: string }`\n- `{ id?, type: \"set_auto_compaction\", enabled: boolean }`\n\n### Wiederholung\n\n- `{ id?, type: \"set_auto_retry\", enabled: boolean }`\n- `{ id?, type: \"abort_retry\" }`\n\n### Bash\n\n- `{ id?, type: \"bash\", command: string }`\n- `{ id?, type: \"abort_bash\" }`\n\n### Sitzung\n\n- `{ id?, type: \"get_session_stats\" }`\n- `{ id?, type: \"export_html\", outputPath?: string }`\n- `{ id?, type: \"switch_session\", sessionPath: string }`\n- `{ id?, type: \"branch\", entryId: string }`\n- `{ id?, type: \"get_branch_messages\" }`\n- `{ id?, type: \"get_last_assistant_text\" }`\n- `{ id?, type: \"set_session_name\", name: string }`\n\n### Nachrichten\n\n- `{ id?, type: \"get_messages\" }`\n\n## Antwortschema\n\nAlle Befehlsergebnisse verwenden `RpcResponse`:\n\n- Erfolg: `{ id?, type: \"response\", command: <command>, success: true, data?: ... }`\n- Fehler: `{ id?, type: \"response\", command: string, success: false, error: string }`\n\nDaten-Payloads sind befehlsspezifisch und in `rpc-types.ts` definiert.\n\n### `get_state`-Payload\n\n```json\n{\n  \"model\": { \"provider\": \"...\", \"id\": \"...\" },\n  \"thinkingLevel\": \"off|minimal|low|medium|high|xhigh\",\n  \"isStreaming\": false,\n  \"isCompacting\": false,\n  \"steeringMode\": \"all|one-at-a-time\",\n  \"followUpMode\": \"all|one-at-a-time\",\n  \"interruptMode\": \"immediate|wait\",\n  \"sessionFile\": \"...\",\n  \"sessionId\": \"...\",\n  \"sessionName\": \"...\",\n  \"autoCompactionEnabled\": true,\n  \"messageCount\": 0,\n  \"queuedMessageCount\": 0,\n  \"todoPhases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Todos\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the tool surface\",\n          \"status\": \"in_progress\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n### `set_todos`-Payload\n\nErsetzt den In-Memory-Todo-Zustand für die aktuelle Sitzung und gibt die normalisierte Phasenliste zurück:\n\n```json\n{\n  \"id\": \"req_2\",\n  \"type\": \"set_todos\",\n  \"phases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Evaluation\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the read tool surface\",\n          \"status\": \"in_progress\"\n        },\n        {\n          \"id\": \"task-2\",\n          \"content\": \"Exercise edit operations\",\n          \"status\": \"pending\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nDies ist nützlich für Hosts, die einen Plan vor dem ersten Prompt vorausfüllen möchten.\n\n### `set_host_tools`-Payload\n\nErsetzt den aktuellen Satz von Host-eigenen Tools, die der RPC-Server über stdio zurückrufen kann:\n\n```json\n{\n  \"id\": \"req_3\",\n  \"type\": \"set_host_tools\",\n  \"tools\": [\n    {\n      \"name\": \"echo_host\",\n      \"label\": \"Echo Host\",\n      \"description\": \"Echo a value from the embedding host\",\n      \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"message\": { \"type\": \"string\" }\n        },\n        \"required\": [\"message\"],\n        \"additionalProperties\": false\n      }\n    }\n  ]\n}\n```\n\nDer Antwort-Payload ist:\n\n```json\n{\n  \"toolNames\": [\"echo_host\"]\n}\n```\n\nDiese Tools werden der aktiven Sitzungs-Tool-Registry vor dem nächsten Modellaufruf hinzugefügt. Ein erneutes Senden von `set_host_tools` ersetzt den vorherigen Host-eigenen Satz.\n\n## Ereignisstrom-Schema\n\nDer RPC-Modus leitet `AgentSessionEvent`-Objekte von `AgentSession.subscribe(...)` weiter.\n\nHäufige Ereignistypen:\n\n- `agent_start`, `agent_end`\n- `turn_start`, `turn_end`\n- `message_start`, `message_update`, `message_end`\n- `tool_execution_start`, `tool_execution_update`, `tool_execution_end`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n- `todo_auto_clear`\n\nFehler des Erweiterungs-Runners werden separat ausgegeben als:\n\n```json\n{ \"type\": \"extension_error\", \"extensionPath\": \"...\", \"event\": \"...\", \"error\": \"...\" }\n```\n\n`message_update` enthält Streaming-Deltas in `assistantMessageEvent` (Text-/Denk-/Toolcall-Deltas).\n\n## Prompt/Warteschlangen-Nebenläufigkeit und Reihenfolge\n\nDies ist das wichtigste operative Verhalten.\n\n### Sofortige Bestätigung vs. Abschluss\n\n`prompt` und `abort_and_prompt` werden **sofort bestätigt**:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n```\n\nDas bedeutet:\n\n- Befehlsannahme != Ausführungsabschluss\n- Der endgültige Abschluss wird über `agent_end` beobachtet\n\n### Während des Streamings\n\n`AgentSession.prompt()` erfordert `streamingBehavior` während des aktiven Streamings:\n\n- `\"steer\"` => eingereihte Steuerungsnachricht (Unterbrechungspfad)\n- `\"followUp\"` => eingereihte Folgenachricht (Nach-Runde-Pfad)\n\nFalls während des Streamings weggelassen, schlägt der Prompt fehl.\n\n### Warteschlangen-Standardwerte\n\nAus dem Coding-Agent-Einstellungsschema (`packages/coding-agent/src/config/settings-schema.ts`):\n\n- `steeringMode`: `\"one-at-a-time\"`\n- `followUpMode`: `\"one-at-a-time\"`\n- `interruptMode`: `\"wait\"`\n\n### Modus-Semantik\n\n- `set_steering_mode` / `set_follow_up_mode`\n  - `\"one-at-a-time\"`: eine eingereihte Nachricht pro Runde abarbeiten\n  - `\"all\"`: gesamte Warteschlange auf einmal abarbeiten\n- `set_interrupt_mode`\n  - `\"immediate\"`: Tool-Ausführung prüft die Steuerung zwischen Tool-Aufrufen; ausstehende Steuerung kann verbleibende Tool-Aufrufe in der Runde abbrechen\n  - `\"wait\"`: Steuerung bis zum Rundenabschluss aufschieben\n\n## Erweiterungs-UI-Subprotokoll\n\nErweiterungen im RPC-Modus verwenden Anfrage/Antwort-UI-Frames.\n\n### Ausgehende Anfrage\n\n`RpcExtensionUIRequest` (`type: \"extension_ui_request\"`) Methoden:\n\n- `select`, `confirm`, `input`, `editor`\n- `notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text`\n\nLaufzeithinweis:\n\n- Die automatische Sitzungstitel-Generierung ist im RPC-Modus deaktiviert, und `setTitle`-UI-Anfragen werden standardmäßig ebenfalls unterdrückt, da die meisten Hosts keine sinnvolle Terminal-Titel-Oberfläche haben. Setzen Sie `PI_RPC_EMIT_TITLE=1`, um das UI-Ereignis wieder zu aktivieren.\n\nBeispiel:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"123\", \"method\": \"confirm\", \"title\": \"Confirm\", \"message\": \"Continue?\", \"timeout\": 30000 }\n```\n\n### Eingehende Antwort\n\n`RpcExtensionUIResponse` (`type: \"extension_ui_response\"`):\n\n- `{ type: \"extension_ui_response\", id: string, value: string }`\n- `{ type: \"extension_ui_response\", id: string, confirmed: boolean }`\n- `{ type: \"extension_ui_response\", id: string, cancelled: true }`\n\nFalls ein Dialog ein Timeout hat, löst der RPC-Modus auf einen Standardwert auf, wenn das Timeout/Abbruch ausgelöst wird.\n\n## Host-Tool-Subprotokoll\n\nRPC-Hosts können dem Agenten benutzerdefinierte Tools bereitstellen, indem sie `set_host_tools` senden und dann Ausführungsanfragen über denselben Transport bedienen.\n\n### Ausgehende Anfrage\n\nWenn der Agent möchte, dass der Host eines dieser Tools ausführt, gibt der RPC-Modus Folgendes aus:\n\n```json\n{\n  \"type\": \"host_tool_call\",\n  \"id\": \"host_1\",\n  \"toolCallId\": \"toolu_123\",\n  \"toolName\": \"echo_host\",\n  \"arguments\": { \"message\": \"hello\" }\n}\n```\n\nFalls die Tool-Ausführung später abgebrochen wird, gibt der RPC-Modus Folgendes aus:\n\n```json\n{\n  \"type\": \"host_tool_cancel\",\n  \"id\": \"host_cancel_1\",\n  \"targetId\": \"host_1\"\n}\n```\n\n### Eingehende Updates und Abschluss\n\nHosts können optional den Fortschritt streamen:\n\n```json\n{\n  \"type\": \"host_tool_update\",\n  \"id\": \"host_1\",\n  \"partialResult\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"working\" }]\n  }\n}\n```\n\nDer Abschluss verwendet:\n\n```json\n{\n  \"type\": \"host_tool_result\",\n  \"id\": \"host_1\",\n  \"result\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"done\" }]\n  }\n}\n```\n\nSetzen Sie `isError: true` bei `host_tool_result`, um den zurückgegebenen Inhalt als Tool-Fehler darzustellen.\n\n## Fehlermodell und Wiederherstellbarkeit\n\n### Fehler auf Befehlsebene\n\nFehler haben `success: false` mit einem String `error`.\n\n```json\n{ \"id\": \"req_2\", \"type\": \"response\", \"command\": \"set_model\", \"success\": false, \"error\": \"Model not found: provider/model\" }\n```\n\n### Erwartungen zur Wiederherstellbarkeit\n\n- Die meisten Befehlsfehler sind wiederherstellbar; der Prozess bleibt aktiv.\n- Fehlerhaftes JSONL / Parse-Loop-Ausnahmen geben eine `parse`-Fehlerantwort aus und lesen nachfolgende Zeilen weiter.\n- Ein leerer `set_session_name` wird abgelehnt (`Session name cannot be empty`).\n- Erweiterungs-UI-Antworten mit unbekannter `id` werden ignoriert.\n- Prozessbeendigungsbedingungen sind das Schließen von stdin oder ein expliziter durch eine Erweiterung ausgelöster Shutdown.\n\n## Kompakte Befehlsabläufe\n\n### 1) Prompt und Stream\n\nstdin:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"prompt\", \"message\": \"Summarize this repo\" }\n```\n\nstdout-Sequenz (typisch):\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n{ \"type\": \"agent_start\" }\n{ \"type\": \"message_update\", \"assistantMessageEvent\": { \"type\": \"text_delta\", \"delta\": \"...\" }, \"message\": { \"role\": \"assistant\", \"content\": [] } }\n{ \"type\": \"agent_end\", \"messages\": [] }\n```\n\n### 2) Prompt während des Streamings mit expliziter Warteschlangen-Richtlinie\n\nstdin:\n\n```json\n{ \"id\": \"req_2\", \"type\": \"prompt\", \"message\": \"Also include risks\", \"streamingBehavior\": \"followUp\" }\n```\n\n### 3) Warteschlangenverhalten inspizieren und anpassen\n\nstdin:\n\n```json\n{ \"id\": \"q1\", \"type\": \"get_state\" }\n{ \"id\": \"q2\", \"type\": \"set_steering_mode\", \"mode\": \"all\" }\n{ \"id\": \"q3\", \"type\": \"set_interrupt_mode\", \"mode\": \"wait\" }\n```\n\n### 4) Erweiterungs-UI-Roundtrip\n\nstdout:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"ui_7\", \"method\": \"input\", \"title\": \"Branch name\", \"placeholder\": \"feature/...\" }\n```\n\nstdin:\n\n```json\n{ \"type\": \"extension_ui_response\", \"id\": \"ui_7\", \"value\": \"feature/rpc-host\" }\n```\n\n## Hinweise zum `RpcClient`-Helfer\n\n`src/modes/rpc/rpc-client.ts` ist ein Komfort-Wrapper, nicht die Protokolldefinition.\n\nAktuelle Helfer-Eigenschaften:\n\n- Startet `bun <cliPath> --mode rpc`\n- Korreliert Antworten über generierte `req_<n>`-IDs\n- Leitet nur erkannte `AgentEvent`-Typen an Listener weiter\n- Unterstützt Host-eigene benutzerdefinierte Tools über `setCustomTools()` und automatische Behandlung von `host_tool_call` / `host_tool_cancel`\n- Stellt **nicht** für jeden Protokollbefehl Hilfsmethoden bereit (zum Beispiel sind `set_interrupt_mode` und `set_session_name` in den Protokolltypen vorhanden, aber nicht als dedizierte Methoden gewrapped)\n\nVerwenden Sie rohe Protokoll-Frames, wenn Sie vollständige Oberflächenabdeckung benötigen.\n",
	"de/configuration/sdk.md": "---\ntitle: SDK\ndescription: >-\n  SDK zur Entwicklung benutzerdefinierter Agenten und Integrationen auf Basis\n  der xcsh-Coding-Agent-Laufzeitumgebung.\nsidebar:\n  order: 6\n  label: SDK\ni18n:\n  sourceHash: 80f3a4374241\n  translator: machine\n---\n\n# SDK\n\nDas SDK ist die prozessinterne Integrationsoberfläche für `@f5-sales-demo/xcsh`.\nVerwenden Sie es, wenn Sie direkten Zugriff auf den Agentenzustand, Event-Streaming, Tool-Verkabelung und Sitzungssteuerung aus Ihrem eigenen Bun/Node-Prozess benötigen.\n\nWenn Sie sprachübergreifende/prozessisolierte Kommunikation benötigen, verwenden Sie stattdessen den RPC-Modus.\n\n## Installation\n\n```bash\nbun add @f5-sales-demo/xcsh\n```\n\n## Einstiegspunkte\n\n`@f5-sales-demo/xcsh` exportiert die SDK-APIs aus dem Paketstamm (sowie über `@f5-sales-demo/xcsh/sdk`).\n\nKernexporte für Einbetter:\n\n- `createAgentSession`\n- `SessionManager`\n- `Settings`\n- `AuthStorage`\n- `ModelRegistry`\n- `discoverAuthStorage`\n- Discovery-Hilfsfunktionen (`discoverExtensions`, `discoverSkills`, `discoverContextFiles`, `discoverPromptTemplates`, `discoverSlashCommands`, `discoverCustomTSCommands`, `discoverMCPServers`)\n- Werkzeug-Factory-Oberfläche (`createTools`, `BUILTIN_TOOLS`, Werkzeugklassen)\n\n## Schnellstart (automatische Discovery-Standardeinstellungen)\n\n```ts\nimport { createAgentSession } from \"@f5-sales-demo/xcsh\";\n\nconst { session, modelFallbackMessage } = await createAgentSession();\n\nif (modelFallbackMessage) {\n process.stderr.write(`${modelFallbackMessage}\\n`);\n}\n\nconst unsubscribe = session.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Summarize this repository in 3 bullets.\");\nunsubscribe();\nawait session.dispose();\n```\n\n## Was `createAgentSession()` standardmäßig entdeckt\n\n`createAgentSession()` folgt dem Prinzip „Bereitstellen zum Überschreiben, Weglassen zum Entdecken\".\n\nWenn weggelassen, wird Folgendes aufgelöst:\n\n- `cwd`: `getProjectDir()`\n- `agentDir`: `~/.xcsh/agent` (via `getAgentDir()`)\n- `authStorage`: `discoverAuthStorage(agentDir)`\n- `modelRegistry`: `new ModelRegistry(authStorage)` + `await refresh()`\n- `settings`: `await Settings.init({ cwd, agentDir })`\n- `sessionManager`: `SessionManager.create(cwd)` (dateibasiert)\n- Fähigkeiten/Kontextdateien/Prompt-Vorlagen/Slash-Befehle/Erweiterungen/benutzerdefinierte TS-Befehle\n- Eingebaute Werkzeuge via `createTools(...)`\n- MCP-Werkzeuge (standardmäßig aktiviert)\n- LSP-Integration (standardmäßig aktiviert)\n\n### Erforderliche vs. optionale Eingaben\n\nNormalerweise müssen Sie nur angeben, was Sie steuern möchten:\n\n- **Muss bereitgestellt werden**: nichts für eine minimale Sitzung\n- **Wird in Einbettern üblicherweise explizit angegeben**:\n    - `sessionManager` (wenn Sie In-Memory oder einen benutzerdefinierten Speicherort benötigen)\n    - `authStorage` + `modelRegistry` (wenn Sie den Lebenszyklus von Anmeldeinformationen/Modellen selbst verwalten)\n    - `model` oder `modelPattern` (wenn eine deterministische Modellauswahl wichtig ist)\n    - `settings` (wenn Sie isolierte/Test-Konfiguration benötigen)\n\n## Sitzungsmanager-Verhalten (persistent vs. In-Memory)\n\n`AgentSession` verwendet immer einen `SessionManager`; das Verhalten hängt davon ab, welche Factory Sie verwenden.\n\n### Dateibasiert (Standard)\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.create(process.cwd()),\n});\n\nconsole.log(session.sessionFile); // absoluter .jsonl-Pfad\n```\n\n- Persistiert Konversations-/Nachrichten-/Zustandsdeltas in Sitzungsdateien.\n- Unterstützt Fortsetzen/Öffnen/Auflisten/Fork-Workflows.\n- `session.sessionFile` ist definiert.\n\n### In-Memory\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.inMemory(),\n});\n\nconsole.log(session.sessionFile); // undefined\n```\n\n- Keine Dateisystempersistenz.\n- Nützlich für Tests, kurzlebige Worker, anfragegültige Agenten.\n- Sitzungsmethoden funktionieren weiterhin, persistenzspezifische Verhaltensweisen (Datei-Fortsetzung/Fork-Pfade) sind jedoch naturgemäß eingeschränkt.\n\n### Hilfsfunktionen zum Fortsetzen/Öffnen/Auflisten\n\n```ts\nimport { SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst recent = await SessionManager.continueRecent(process.cwd());\nconst listed = await SessionManager.list(process.cwd());\nconst opened = listed[0] ? await SessionManager.open(listed[0].path) : null;\n```\n\n## Modell- und Authentifizierungsverkabelung\n\n`createAgentSession()` verwendet `ModelRegistry` + `AuthStorage` für die Modellauswahl und API-Schlüsselauflösung.\n\n### Explizite Verkabelung\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst available = modelRegistry.getAvailable();\nif (available.length === 0) throw new Error(\"No authenticated models available\");\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n model: available[0],\n thinkingLevel: \"medium\",\n sessionManager: SessionManager.inMemory(),\n});\n```\n\n### Auswahlreihenfolge bei weggelassenem `model`\n\nWenn kein explizites `model`/`modelPattern` angegeben ist:\n\n1. Modell aus bestehender Sitzung wiederherstellen (falls wiederherstellbar + Schlüssel verfügbar)\n2. Standard-Modellrolle aus den Einstellungen (`default`)\n3. Erstes verfügbares Modell mit gültiger Authentifizierung\n\nFalls die Wiederherstellung fehlschlägt, erklärt `modelFallbackMessage` den Fallback.\n\n### Authentifizierungspriorität\n\n`AuthStorage.getApiKey(...)` löst in dieser Reihenfolge auf:\n\n1. Laufzeit-Override (`setRuntimeApiKey`)\n2. Gespeicherte Anmeldeinformationen in `agent.db`\n3. Provider-Umgebungsvariablen\n4. Benutzerdefinierter Provider-Resolver-Fallback (falls konfiguriert)\n\n## Event-Abonnementmodell\n\nAbonnieren Sie mit `session.subscribe(listener)`; es wird eine Abmelde-Funktion zurückgegeben.\n\n```ts\nconst unsubscribe = session.subscribe(event => {\n switch (event.type) {\n  case \"agent_start\":\n  case \"turn_start\":\n  case \"tool_execution_start\":\n   break;\n  case \"message_update\":\n   if (event.assistantMessageEvent.type === \"text_delta\") {\n    process.stdout.write(event.assistantMessageEvent.delta);\n   }\n   break;\n }\n});\n```\n\n`AgentSessionEvent` umfasst kern-`AgentEvent`s sowie sitzungsebene-Events:\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n## Prompt-Lebenszyklus\n\n`session.prompt(text, options?)` ist der primäre Einstiegspunkt.\n\nVerhalten:\n\n1. Optionale Befehls-/Vorlagenexpansion (`/`-Befehle, benutzerdefinierte Befehle, Datei-Slash-Befehle, Prompt-Vorlagen)\n2. Wenn gerade gestreamt wird:\n    - erfordert `streamingBehavior: \"steer\" | \"followUp\"`\n    - wird in die Warteschlange gestellt, anstatt die Arbeit zu verwerfen\n3. Wenn inaktiv:\n    - validiert Modell + API-Schlüssel\n    - fügt Benutzernachricht an\n    - startet Agenten-Turn\n\nVerwandte APIs:\n\n- `sendUserMessage(content, { deliverAs? })`\n- `steer(text, images?)`\n- `followUp(text, images?)`\n- `sendCustomMessage({ customType, content, ... }, { deliverAs?, triggerTurn? })`\n- `abort()`\n\n## Werkzeuge und Erweiterungsintegration\n\n### Eingebaute Werkzeuge und Filterung\n\n- Eingebaute Werkzeuge stammen aus `createTools(...)` und `BUILTIN_TOOLS`.\n- `toolNames` fungiert als Zulassungsliste für eingebaute Werkzeuge.\n- `customTools` und erweiterungsregistrierte Werkzeuge sind weiterhin enthalten.\n- Versteckte Werkzeuge (zum Beispiel `submit_result`) sind standardmäßig deaktiviert, sofern sie nicht durch Optionen erforderlich sind.\n\n```ts\nconst { session } = await createAgentSession({\n toolNames: [\"read\", \"grep\", \"find\", \"write\"],\n requireSubmitResultTool: true,\n});\n```\n\n### Erweiterungen\n\n- `extensions`: Inline-`ExtensionFactory[]`\n- `additionalExtensionPaths`: Zusätzliche Erweiterungsdateien laden\n- `disableExtensionDiscovery`: Automatisches Erweiterungsscanning deaktivieren\n- `preloadedExtensions`: Bereits geladenen Erweiterungssatz wiederverwenden\n\n### Laufzeit-Werkzeugsatz-Änderungen\n\n`AgentSession` unterstützt Laufzeit-Aktivierungsaktualisierungen:\n\n- `getActiveToolNames()`\n- `getAllToolNames()`\n- `setActiveToolsByName(names)`\n- `refreshMCPTools(mcpTools)`\n\nDer System-Prompt wird neu erstellt, um aktive Werkzeugänderungen widerzuspiegeln.\n\n## Discovery-Hilfsfunktionen\n\nVerwenden Sie diese, wenn Sie partielle Kontrolle ohne Neuerstellen der internen Discovery-Logik wünschen:\n\n- `discoverAuthStorage(agentDir?)`\n- `discoverExtensions(cwd?)`\n- `discoverSkills(cwd?, _agentDir?, settings?)`\n- `discoverContextFiles(cwd?, _agentDir?)`\n- `discoverPromptTemplates(cwd?, agentDir?)`\n- `discoverSlashCommands(cwd?)`\n- `discoverCustomTSCommands(cwd?, agentDir?)`\n- `discoverMCPServers(cwd?)`\n- `buildSystemPrompt(options?)`\n\n## Subagenten-orientierte Optionen\n\nFür SDK-Nutzer, die Orchestratoren erstellen (ähnlich dem Aufgaben-Executor-Ablauf):\n\n- `outputSchema`: übergibt strukturierte Ausgabeerwartung an den Werkzeugkontext\n- `requireSubmitResultTool`: erzwingt die Einbeziehung des `submit_result`-Werkzeugs\n- `taskDepth`: Rekursionstiefenkontext für verschachtelte Aufgabensitzungen\n- `parentTaskPrefix`: Artefakt-Benennungspräfix für verschachtelte Aufgabenausgaben\n\nDiese sind für normale Einzelagenten-Einbettung optional.\n\n## Rückgabewert von `createAgentSession()`\n\n```ts\ntype CreateAgentSessionResult = {\n session: AgentSession;\n extensionsResult: LoadExtensionsResult;\n setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;\n mcpManager?: MCPManager;\n modelFallbackMessage?: string;\n lspServers?: Array<{ name: string; status: \"ready\" | \"error\"; fileTypes: string[]; error?: string }>;\n};\n```\n\nVerwenden Sie `setToolUIContext(...)` nur, wenn Ihr Einbetter UI-Fähigkeiten bereitstellt, in die Werkzeuge/Erweiterungen aufrufen sollen.\n\n## Minimales gesteuertes Einbettungsbeispiel\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n Settings,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst settings = Settings.isolated({\n \"compaction.enabled\": true,\n \"retry.enabled\": true,\n});\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n settings,\n sessionManager: SessionManager.inMemory(),\n toolNames: [\"read\", \"grep\", \"find\", \"edit\", \"write\"],\n enableMCP: false,\n enableLsp: true,\n});\n\nsession.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Find all TODO comments in this repo and propose fixes.\");\nawait session.dispose();\n```\n",
	"de/configuration/secrets.md": "---\ntitle: Geheimnis-Verschleierung\ndescription: >-\n  Pipeline zur Verschleierung sensibler Werte aus Sitzungsprotokollen und\n  Ausgaben.\nsidebar:\n  order: 3\n  label: Geheimnisse\ni18n:\n  sourceHash: 1d9dc101c614\n  translator: machine\n---\n\n# Geheimnis-Verschleierung\n\nVerhindert, dass sensible Werte (API-Schlüssel, Token, Passwörter) an LLM-Anbieter gesendet werden. Wenn aktiviert, werden Geheimnisse durch deterministische Platzhalter ersetzt, bevor sie den Prozess verlassen, und in Werkzeugaufruf-Argumenten, die vom Modell zurückgegeben werden, wiederhergestellt.\n\n## Aktivierung\n\nStandardmäßig aktiviert. Umschalten über die `/settings`-Oberfläche oder direkt in `config.yml`:\n\n```yaml\nsecrets:\n  enabled: false\n```\n\n## Funktionsweise\n\n1. Beim Sitzungsstart werden Geheimnisse aus zwei Quellen gesammelt:\n   - **Umgebungsvariablen**, die gängigen Geheimnis-Mustern entsprechen (`*_KEY`, `*_SECRET`, `*_TOKEN`, `*_PASSWORD` usw.) mit Werten >= 8 Zeichen\n   - **`secrets.yml`-Dateien** (siehe unten)\n\n2. Ausgehende Nachrichten an das LLM haben alle Geheimniswerte durch Platzhalter wie `<<$env:S0>>`, `<<$env:S1>>` usw. ersetzt.\n\n3. Werkzeugaufruf-Argumente, die vom Modell zurückgegeben werden, werden rekursiv durchsucht und Platzhalter werden vor der Ausführung auf ihre ursprünglichen Werte zurückgesetzt.\n\nZwei Modi steuern, was mit jedem Geheimnis geschieht:\n\n| Modus | Verhalten | Umkehrbar |\n|---|---|---|\n| `obfuscate` (Standard) | Ersetzt durch indizierten Platzhalter `<<$env:SN>>` | Ja (in Werkzeugargumenten entschleiert) |\n| `replace` | Ersetzt durch eine deterministische gleichlange Zeichenkette | Nein (einwegig) |\n\n## secrets.yml\n\nDefinieren Sie benutzerdefinierte Geheimnis-Einträge in YAML. Es werden zwei Speicherorte geprüft:\n\n| Ebene | Pfad | Zweck |\n|---|---|---|\n| Global | `~/.xcsh/agent/secrets.yml` | Geheimnisse über alle Projekte hinweg |\n| Projekt | `<cwd>/.xcsh/secrets.yml` | Projektspezifische Geheimnisse |\n\nProjekteinträge überschreiben globale Einträge mit übereinstimmendem `content`.\n\n### Schema\n\nJeder Eintrag im Array verfügt über folgende Felder:\n\n| Feld | Typ | Erforderlich | Beschreibung |\n|---|---|---|---|\n| `type` | `\"plain\"` oder `\"regex\"` | Ja | Übereinstimmungsstrategie |\n| `content` | Zeichenkette | Ja | Der Geheimniswert (plain) oder das Regex-Muster (regex) |\n| `mode` | `\"obfuscate\"` oder `\"replace\"` | Nein | Standard: `\"obfuscate\"` |\n| `replacement` | Zeichenkette | Nein | Benutzerdefinierter Ersatz (nur Modus replace) |\n| `flags` | Zeichenkette | Nein | Regex-Flags (nur Typ regex) |\n\n### Beispiele\n\n#### Einfache Geheimnisse\n\n```yaml\n# Einen bestimmten API-Schlüssel verschleiern (Standardmodus)\n- type: plain\n  content: sk-proj-abc123def456\n\n# Ein Datenbankpasswort durch eine feste Zeichenkette ersetzen\n- type: plain\n  content: hunter2\n  mode: replace\n  replacement: \"********\"\n```\n\n#### Regex-Geheimnisse\n\n```yaml\n# Beliebigen AWS-artigen Schlüssel verschleiern\n- type: regex\n  content: \"AKIA[0-9A-Z]{16}\"\n\n# Groß-/Kleinschreibungsunabhängige Übereinstimmung mit expliziten Flags\n- type: regex\n  content: \"api[_-]?key\\\\s*=\\\\s*\\\\w+\"\n  flags: \"i\"\n\n# Regex-Literal-Syntax (Muster und Flags in einer Zeichenkette)\n- type: regex\n  content: \"/bearer\\\\s+[a-zA-Z0-9._~+\\\\/=-]+/i\"\n```\n\nRegex-Einträge suchen immer global (das Flag `g` wird automatisch erzwungen). Die Regex-Literal-Syntax `/muster/flags` wird als Alternative zu separaten Feldern `content` + `flags` unterstützt. Maskierte Schrägstriche innerhalb des Musters (`\\\\/`) werden korrekt behandelt.\n\n#### Modus replace mit Regex\n\n```yaml\n# Verbindungszeichenketten einwegig ersetzen (nicht umkehrbar)\n- type: regex\n  content: \"postgres://[^\\\\s]+\"\n  mode: replace\n  replacement: \"postgres://***\"\n```\n\n## Interaktion mit der Erkennung von Umgebungsvariablen\n\nUmgebungsvariablen werden immer zuerst gesammelt. Dateidefinierte Einträge werden danach angehängt, sodass Dateieinträge Geheimnisse abdecken können, die nicht in Umgebungsvariablen gespeichert sind (Konfigurationsdateien, hartcodierte Werte usw.). Wenn derselbe Wert in beiden vorkommt, hat der Modus des Dateieintrags Vorrang.\n\n## Wichtige Dateien\n\n- `src/secrets/index.ts` -- Laden, Zusammenführen, Sammlung von Umgebungsvariablen\n- `src/secrets/obfuscator.ts` -- Klasse `SecretObfuscator`, Platzhaltergenerierung, Nachrichtenverschleierung\n- `src/secrets/regex.ts` -- Regex-Literal-Parsing und -Kompilierung\n- `src/config/settings-schema.ts` -- Definition der Einstellung `secrets.enabled`\n",
	"de/extensions/extension-loading.md": "---\ntitle: Erweiterungsladen (TypeScript/JavaScript-Module)\ndescription: >-\n  TypeScript- und JavaScript-Modullading-Pipeline für Erweiterungen mit\n  Auflösung, Validierung und Caching.\nsidebar:\n  order: 2\n  label: Erweiterungsladen\ni18n:\n  sourceHash: a8cea231c660\n  translator: machine\n---\n\n# Erweiterungsladen (TypeScript/JavaScript-Module)\n\nDieses Dokument beschreibt, wie der Coding-Agent **Erweiterungsmodule** (`.ts`/`.js`) beim Start erkennt und lädt.\n\nEs behandelt **nicht** `gemini-extension.json`-Manifest-Erweiterungen (separat dokumentiert).\n\n## Was dieses Subsystem leistet\n\nDas Erweiterungsladen erstellt eine Liste von Modul-Einstiegsdateien, importiert jedes Modul mit Bun, führt seine Factory aus und gibt zurück:\n\n- geladene Erweiterungsdefinitionen\n- pfadbezogene Ladefehler (ohne den gesamten Ladevorgang abzubrechen)\n- ein gemeinsames Erweiterungs-Laufzeitobjekt, das später von `ExtensionRunner` verwendet wird\n\n## Primäre Implementierungsdateien\n\n- `src/extensibility/extensions/loader.ts` — Pfaderkennung + Import/Ausführung\n- `src/extensibility/extensions/index.ts` — öffentliche Exporte\n- `src/extensibility/extensions/runner.ts` — Laufzeit-/Ereignisausführung nach dem Laden\n- `src/discovery/builtin.ts` — nativer Auto-Discovery-Provider für Erweiterungsmodule\n- `src/config/settings.ts` — lädt zusammengeführte `extensions`/`disabledExtensions`-Einstellungen\n\n---\n\n## Eingaben für das Erweiterungsladen\n\n### 1) Automatisch erkannte native Erweiterungsmodule\n\n`discoverAndLoadExtensions()` fragt zunächst Discovery-Provider nach `extension-module`-Capability-Einträgen und behält dann nur Provider-`native`-Einträge.\n\nEffektive native Speicherorte:\n\n- Projekt: `<cwd>/.xcsh/extensions`\n- Benutzer: `~/.xcsh/agent/extensions`\n\nPfad-Wurzeln stammen vom nativen Provider (`SOURCE_PATHS.native`).\n\nHinweise:\n\n- Die native Auto-Discovery basiert derzeit auf `.xcsh`.\n- Legacy `.pi` wird in `package.json`-Manifest-Schlüsseln (`pi.extensions`) weiterhin akzeptiert, jedoch nicht als native Wurzel hier.\n\n### 2) Explizit konfigurierte Pfade\n\nNach der Auto-Discovery werden konfigurierte Pfade angehängt und aufgelöst.\n\nKonfigurierte Pfadquellen im Haupt-Session-Startpfad (`sdk.ts`):\n\n1. CLI-bereitgestellte Pfade (`--extension/-e`, und `--hook` wird ebenfalls als Erweiterungspfad behandelt)\n2. Einstellungs-Array `extensions` (zusammengeführte globale und Projekteinstellungen)\n\nGlobale Einstellungsdatei:\n\n- `~/.xcsh/agent/config.yml` (oder benutzerdefiniertes Agent-Verzeichnis via `PI_CODING_AGENT_DIR`)\n\nProjekteinstellungsdatei:\n\n- `<cwd>/.xcsh/settings.json`\n\nBeispiele:\n\n```yaml\n# ~/.xcsh/agent/config.yml\nextensions:\n  - ~/my-exts/safety.ts\n  - ./local/ext-pack\n```\n\n```json\n{\n  \"extensions\": [\"./.xcsh/extensions/my-extra\"]\n}\n```\n\n---\n\n## Aktivierungs-/Deaktivierungssteuerung\n\n### Discovery deaktivieren\n\n- CLI: `--no-extensions`\n- SDK-Option: `disableExtensionDiscovery`\n\nVerhaltensaufteilung:\n\n- SDK: wenn `disableExtensionDiscovery=true`, werden `additionalExtensionPaths` weiterhin über `loadExtensions()` geladen.\n- CLI-Pfadaufbau (`main.ts`) löscht CLI-Erweiterungspfade derzeit, wenn `--no-extensions` gesetzt ist, sodass explizite `-e/--hook` in diesem Modus nicht weitergeleitet werden.\n\n### Bestimmte Erweiterungsmodule deaktivieren\n\nDie Einstellung `disabledExtensions` filtert nach Erweiterungs-ID-Format:\n\n- `extension-module:<derivedName>`\n\n`derivedName` basiert auf dem Einstiegspfad (`getExtensionNameFromPath`), zum Beispiel:\n\n- `/x/foo.ts` -> `foo`\n- `/x/bar/index.ts` -> `bar`\n\nBeispiel:\n\n```yaml\ndisabledExtensions:\n  - extension-module:foo\n```\n\n---\n\n## Pfad- und Eintragsauflösung\n\n### Pfadnormalisierung\n\nFür konfigurierte Pfade:\n\n1. Unicode-Leerzeichen normalisieren\n2. `~` erweitern\n3. Bei relativen Pfaden gegen aktuelles `cwd` auflösen\n\n### Wenn der konfigurierte Pfad eine Datei ist\n\nSie wird direkt als Moduleintragskandidat verwendet.\n\n### Wenn der konfigurierte Pfad ein Verzeichnis ist\n\nAuflösungsreihenfolge:\n\n1. `package.json` in diesem Verzeichnis mit `xcsh.extensions` (oder Legacy `pi.extensions`) -> deklarierte Einträge verwenden\n2. `index.ts`\n3. `index.js`\n4. Andernfalls eine Ebene nach Erweiterungseinträgen scannen:\n   - direkte `*.ts` / `*.js`\n   - Unterverzeichnis `index.ts` / `index.js`\n   - Unterverzeichnis `package.json` mit `xcsh.extensions` / `pi.extensions`\n\nRegeln und Einschränkungen:\n\n- keine rekursive Erkennung über eine Unterverzeichnisebene hinaus\n- deklarierte `extensions`-Manifest-Einträge werden relativ zu diesem Paketverzeichnis aufgelöst\n- deklarierte Einträge werden nur einbezogen, wenn die Datei vorhanden ist/der Zugriff erlaubt ist\n- bei `*/index.{ts,js}`-Paaren wird TypeScript gegenüber JavaScript bevorzugt\n- Symlinks werden als zulässige Dateien/Verzeichnisse behandelt\n\n### Ignorierverhalten unterscheidet sich je nach Quelle\n\n- Native Auto-Discovery (`discoverExtensionModulePaths` in Discovery-Hilfsfunktionen) verwendet nativen Glob mit `gitignore: true` und `hidden: false`.\n- Explizites konfiguriertes Verzeichnis-Scanning in `loader.ts` verwendet `readdir`-Regeln und wendet **keine** Gitignore-Filterung an.\n\n---\n\n## Ladereihenfolge und Vorrang\n\n`discoverAndLoadExtensions()` erstellt eine geordnete Liste und ruft dann `loadExtensions()` auf.\n\nReihenfolge:\n\n1. Nativ automatisch erkannte Module\n2. Explizit konfigurierte Pfade (in angegebener Reihenfolge)\n\nIn `sdk.ts` lautet die konfigurierte Reihenfolge:\n\n1. Zusätzliche CLI-Pfade\n2. Einstellungen `extensions`\n\nDeduplizierung:\n\n- absolut pfadbasiert\n- erster gefundener Pfad hat Vorrang\n- spätere Duplikate werden ignoriert\n\nImplikation: Wenn derselbe Modulpfad sowohl automatisch erkannt als auch explizit konfiguriert ist, wird er einmal an der ersten Position geladen (Phase der automatischen Erkennung).\n\n---\n\n## Modulimport und Factory-Vertrag\n\nJeder Kandidatenpfad wird mit dynamischem Import geladen:\n\n- `await import(resolvedPath)`\n- Factory ist `module.default ?? module`\n- Factory muss eine Funktion sein (`ExtensionFactory`)\n\nWenn der Export keine Funktion ist, schlägt dieser Pfad mit einem strukturierten Fehler fehl und das Laden wird fortgesetzt.\n\n---\n\n## Fehlerbehandlung und Isolation\n\n### Während des Ladens\n\nPro Erweiterungspfad werden Fehler als `{ path, error }` erfasst und verhindern nicht das Laden anderer Pfade.\n\nHäufige Fälle:\n\n- Importfehler / fehlende Datei\n- ungültiger Factory-Export (keine Funktion)\n- Ausnahme beim Ausführen der Factory\n\n### Laufzeit-Isolationsmodell\n\n- Erweiterungen sind **nicht sandboxed** (gleicher Prozess/Laufzeit).\n- Sie teilen einen `EventBus` und eine `ExtensionRuntime`-Instanz.\n- Während des Ladens werfen Laufzeit-Aktionsmethoden absichtlich `ExtensionRuntimeNotInitializedError`; die Aktionsverdrahtung erfolgt später in `ExtensionRunner.initialize()`.\n\n### Nach dem Laden\n\nWenn Ereignisse durch `ExtensionRunner` laufen, werden Handler-Ausnahmen abgefangen und als Erweiterungsfehler ausgegeben, anstatt die Runner-Schleife zum Absturz zu bringen.\n\n---\n\n## Minimale Benutzer-/Projekt-Layout-Beispiele\n\n### Benutzerebene\n\n```text\n~/.xcsh/agent/\n  config.yml\n  extensions/\n    guardrails.ts\n    audit/\n      index.ts\n```\n\n### Projektebene\n\n```text\n<repo>/\n  .xcsh/\n    settings.json\n    extensions/\n      checks/\n        package.json\n      lint-gates.ts\n```\n\n`checks/package.json`:\n\n```json\n{\n  \"xcsh\": {\n    \"extensions\": [\"./src/check-a.ts\", \"./src/check-b.js\"]\n  }\n}\n```\n\nLegacy-Manifest-Schlüssel wird weiterhin akzeptiert:\n\n```json\n{\n  \"pi\": {\n    \"extensions\": [\"./index.ts\"]\n  }\n}\n```\n",
	"de/extensions/extensions.md": "---\ntitle: Erweiterungen\ndescription: >-\n  Übersicht über die Erweiterungs-Laufzeitumgebung mit Typen,\n  Runner-Lebenszyklus, Registrierung und Erkennung.\nsidebar:\n  order: 1\n  label: Übersicht\ni18n:\n  sourceHash: 14cc16dbd98b\n  translator: machine\n---\n\n# Erweiterungen\n\nPrimärer Leitfaden zur Erstellung von Laufzeiterweiterungen in `packages/coding-agent`.\n\nDieses Dokument behandelt die aktuelle Erweiterungs-Laufzeitumgebung in:\n\n- `src/extensibility/extensions/types.ts`\n- `src/extensibility/extensions/runner.ts`\n- `src/extensibility/extensions/wrapper.ts`\n- `src/extensibility/extensions/index.ts`\n- `src/modes/controllers/extension-ui-controller.ts`\n\nInformationen zu Erkennungspfaden und Regeln zum Laden aus dem Dateisystem finden Sie unter `docs/extension-loading.md`.\n\n## Was eine Erweiterung ist\n\nEine Erweiterung ist ein TS/JS-Modul, das eine Standard-Factory exportiert:\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nexport default function myExtension(pi: ExtensionAPI) {\n // register handlers/tools/commands/renderers\n}\n```\n\nErweiterungen können alle der folgenden Elemente in einem Modul kombinieren:\n\n- Ereignis-Handler (`pi.on(...)`)\n- LLM-aufrufbare Werkzeuge (`pi.registerTool(...)`)\n- Slash-Befehle (`pi.registerCommand(...)`)\n- Tastatürkürzel und Flags\n- Benutzerdefiniertes Nachrichten-Rendering\n- Sitzungs-/Nachrichten-Injektions-APIs (`sendMessage`, `sendUserMessage`, `appendEntry`)\n\n## Laufzeitmodell\n\n1. Erweiterungen werden importiert und ihre Factory-Funktionen ausgeführt.\n2. Während dieser Ladephase sind Registrierungsmethoden gültig; Laufzeit-Aktionsmethoden sind noch nicht initialisiert.\n3. `ExtensionRunner.initialize(...)` verdrahtet Live-Aktionen/Kontexte für den aktiven Modus.\n4. Sitzungs-/Agenten-/Werkzeug-Lebenszyklus-Ereignisse werden an Handler ausgegeben.\n5. Jede Werkzeugausführung wird mit Erweiterungsabfang umhüllt (`tool_call` / `tool_result`).\n\n```text\nExtension lifecycle (simplified)\n\nload paths\n   │\n   ▼\nimport module + run factory (registration only)\n   │\n   ▼\nExtensionRunner.initialize(mode/session/tool registry)\n   │\n   ├─ emit session/agent events to handlers\n   ├─ wrap tool execution (tool_call/tool_result)\n   └─ expose runtime actions (sendMessage, setActiveTools, ...)\n```\n\nWichtige Einschränkung aus `loader.ts`:\n\n- Der Aufruf von Aktionsmethoden wie `pi.sendMessage()` während des Ladens der Erweiterung löst `ExtensionRuntimeNotInitializedError` aus\n- Zuerst registrieren; Laufzeitverhalten aus Ereignissen/Befehlen/Werkzeugen ausführen\n\n## Schnellstart\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\nimport { Type } from \"@sinclair/typebox\";\n\nexport default function (pi: ExtensionAPI) {\n pi.setLabel(\"Safety + Utilities\");\n\n pi.on(\"session_start\", async (_event, ctx) => {\n  ctx.ui.notify(`Extension loaded in ${ctx.cwd}`, \"info\");\n });\n\n pi.on(\"tool_call\", async (event) => {\n  if (event.toolName === \"bash\" && event.input.command?.includes(\"rm -rf\")) {\n   return { block: true, reason: \"Blocked by extension policy\" };\n  }\n });\n\n pi.registerTool({\n  name: \"hello_extension\",\n  label: \"Hello Extension\",\n  description: \"Return a greeting\",\n  parameters: Type.Object({ name: Type.String() }),\n  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {\n   return {\n    content: [{ type: \"text\", text: `Hello, ${params.name}` }],\n    details: { greeted: params.name },\n   };\n  },\n });\n\n pi.registerCommand(\"hello-ext\", {\n  description: \"Show queue state\",\n  handler: async (_args, ctx) => {\n   ctx.ui.notify(`pending=${ctx.hasPendingMessages()}`, \"info\");\n  },\n });\n}\n```\n\n## API-Oberflächen der Erweiterung\n\n## 1) Registrierung und Aktionen (`ExtensionAPI`)\n\nKernmethoden:\n\n- `on(event, handler)`\n- `registerTool`, `registerCommand`, `registerShortcut`, `registerFlag`\n- `registerMessageRenderer`\n- `sendMessage`, `sendUserMessage`, `appendEntry`\n- `getActiveTools`, `getAllTools`, `setActiveTools`\n- `getSessionName`, `setSessionName`\n- `setModel`, `getThinkingLevel`, `setThinkingLevel`\n- `registerProvider`\n- `events` (gemeinsamer Ereignisbus)\n\nIm interaktiven Modus werden `input`-Handler vor der integrierten Prüfung auf automatischen Titel der ersten Nachricht ausgeführt. Erweiterungen, die `await pi.setSessionName(...)` aus `input` aufrufen, können den dauerhaft gespeicherten Sitzungsnamen festlegen und verhindern, dass der automatisch generierte Standardtitel für diese Sitzung ausgeführt wird.\n\nEbenfalls verfügbar:\n\n- `pi.logger`\n- `pi.typebox`\n- `pi.pi` (Paketexporte)\n\n### Semantik der Nachrichtenübermittlung\n\n`pi.sendMessage(message, options)` unterstützt:\n\n- `deliverAs: \"steer\"` (Standard) — unterbricht den aktuellen Durchlauf\n- `deliverAs: \"followUp\"` — in die Warteschlange eingereiht, um nach dem aktuellen Durchlauf ausgeführt zu werden\n- `deliverAs: \"nextTurn\"` — gespeichert und beim nächsten Benutzer-Prompt injiziert\n- `triggerTurn: true` — startet einen Durchlauf im Leerlauf (`nextTurn` ignoriert dies)\n\n`pi.sendUserMessage(content, { deliverAs })` durchläuft immer den Prompt-Ablauf; während des Streamings wird es als Steer/Follow-up in die Warteschlange eingereiht.\n\n## 2) Handler-Kontext (`ExtensionContext`)\n\nHandler und Werkzeug-`execute` erhalten `ctx` mit:\n\n- `ui`\n- `hasUI`\n- `cwd`\n- `sessionManager` (schreibgeschützt)\n- `modelRegistry`, `model`\n- `getContextUsage()`\n- `compact(...)`\n- `isIdle()`, `hasPendingMessages()`, `abort()`\n- `shutdown()`\n- `getSystemPrompt()`\n\n## 3) Befehlskontext (`ExtensionCommandContext`)\n\nBefehlshandler erhalten zusätzlich:\n\n- `waitForIdle()`\n- `newSession(...)`\n- `switchSession(...)`\n- `branch(entryId)`\n- `navigateTree(targetId, { summarize })`\n- `reload()`\n\nVerwenden Sie den Befehlskontext für sitzungsgesteuerte Abläufe; diese Methoden sind absichtlich von allgemeinen Ereignis-Handlern getrennt.\n\n## Ereignis-Oberfläche (aktuelle Namen und Verhalten)\n\nKanonische Ereignis-Unions und Payload-Typen befinden sich in `types.ts`.\n\n### Sitzungslebenszyklus\n\n- `session_start`\n- `session_before_switch` / `session_switch`\n- `session_before_branch` / `session_branch`\n- `session_before_compact` / `session.compacting` / `session_compact`\n- `session_before_tree` / `session_tree`\n- `session_shutdown`\n\nAbbrechbare Vor-Ereignisse:\n\n- `session_before_switch` → `{ cancel?: boolean }`\n- `session_before_branch` → `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_before_compact` → `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session_before_tree` → `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n\n### Prompt- und Durchlauf-Lebenszyklus\n\n- `input`\n- `before_agent_start`\n- `context`\n- `agent_start` / `agent_end`\n- `turn_start` / `turn_end`\n- `message_start` / `message_update` / `message_end`\n\n### Werkzeug-Lebenszyklus\n\n- `tool_call` (vor der Ausführung, kann blockieren)\n- `tool_result` (nach der Ausführung, kann Inhalt/Details/isError patchen)\n- `tool_execution_start` / `tool_execution_update` / `tool_execution_end` (Beobachtbarkeit)\n\n`tool_result` ist middleware-artig: Handler werden in Erweiterungsreihenfolge ausgeführt und jeder sieht vorherige Änderungen.\n\n### Zuverlässigkeits-/Laufzeitsignale\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### Abfangen von Benutzerbefehlen\n\n- `user_bash` (überschreiben mit `{ result }`)\n- `user_python` (überschreiben mit `{ result }`)\n\n### `resources_discover`\n\n`resources_discover` existiert in Erweiterungstypen und `ExtensionRunner`.\nAktueller Laufzeithinweis: `ExtensionRunner.emitResourcesDiscover(...)` ist implementiert, aber es gibt keine `AgentSession`-Aufrufstellen, die es in der aktuellen Codebasis aufrufen.\n\n## Details zur Werkzeugentwicklung\n\n`registerTool` verwendet `ToolDefinition` aus `types.ts`.\n\nAktuelle `execute`-Signatur:\n\n```ts\nexecute(\n toolCallId,\n params,\n signal,\n onUpdate,\n ctx,\n): Promise<AgentToolResult>\n```\n\nVorlage:\n\n```ts\npi.registerTool({\n name: \"my_tool\",\n label: \"My Tool\",\n description: \"...\",\n parameters: Type.Object({}),\n async execute(_id, _params, signal, onUpdate, ctx) {\n  if (signal?.aborted) {\n   return { content: [{ type: \"text\", text: \"Cancelled\" }] };\n  }\n  onUpdate?.({ content: [{ type: \"text\", text: \"Working...\" }] });\n  return { content: [{ type: \"text\", text: \"Done\" }], details: {} };\n },\n onSession(event, ctx) {\n  // reason: start|switch|branch|tree|shutdown\n },\n renderCall(args, theme) {\n  // optional TUI render\n },\n renderResult(result, options, theme, args) {\n  // optional TUI render\n },\n});\n```\n\n`tool_call`/`tool_result` fangen alle Werkzeuge ab, sobald die Registry in `sdk.ts` umhüllt ist, einschließlich integrierter und erweiterungs-/benutzerdefinierter Werkzeuge.\n\n## UI-Integrationspunkte\n\n`ctx.ui` implementiert das `ExtensionUIContext`-Interface. Die Unterstützung unterscheidet sich je nach Modus.\n\n### Interaktiver Modus (`extension-ui-controller.ts`)\n\nUnterstützt:\n\n- Dialoge: `select`, `confirm`, `input`, `editor`\n- Benachrichtigungen/Status/Editor-Text/Terminal-Eingabe/benutzerdefinierte Overlays\n- Auflistung/Laden von Designs nach Name (`setTheme` unterstützt String-Namen)\n- Umschalten der erweiterten Werkzeugansicht\n\nAktuell keine Aktion ausführende Methoden in diesem Controller:\n\n- `setFooter`\n- `setHeader`\n- `setEditorComponent`\n\nHinweis: `setWidget` leitet derzeit über `setHookWidget(...)` zum Statuszeilen-Text weiter.\n\n### RPC-Modus (`rpc-mode.ts`)\n\n`ctx.ui` wird durch RPC-`extension_ui_request`-Ereignisse unterstützt:\n\n- Dialog-Methoden (`select`, `confirm`, `input`, `editor`) mit Hin- und Rückkommunikation zu Client-Antworten\n- Fire-and-Forget-Methoden senden Anfragen aus (`notify`, `setStatus`, `setWidget` für String-Arrays, `setTitle`, `setEditorText`)\n\nNicht unterstützt/keine Aktion ausführend in der RPC-Implementierung:\n\n- `onTerminalInput`\n- `custom`\n- `setFooter`, `setHeader`, `setEditorComponent`\n- `setWorkingMessage`\n- Design-Umschalten/-Laden (`setTheme` gibt Fehler zurück)\n- Werkzeug-Erweiterungssteuerungen sind inaktiv\n\n### Print-/Headless-/Subagent-Pfade\n\nWenn kein UI-Kontext der Runner-Initialisierung übergeben wird, ist `ctx.hasUI` `false` und Methoden sind keine Aktion ausführend/geben Standardwerte zurück.\n\n### Hintergrund-Interaktiver Modus\n\nDer Hintergrundmodus installiert ein nicht-interaktives UI-Kontextobjekt. In der aktuellen Implementierung kann `ctx.hasUI` weiterhin `true` sein, während interaktive Dialoge Standardwerte/keine Aktionen zurückgeben.\n\n## Sitzungs- und Zustandsmuster\n\nFür dauerhaften Erweiterungsstatus:\n\n1. Persistieren mit `pi.appendEntry(customType, data)`.\n2. Zustand aus `ctx.sessionManager.getBranch()` bei `session_start`, `session_branch`, `session_tree` neu aufbauen.\n3. Werkzeugresultat-`details` strukturiert halten, wenn der Zustand aus der Werkzeugresultat-Historie sichtbar/rekonstruierbar sein soll.\n\nBeispiel-Rekonstruktionsmuster:\n\n```ts\npi.on(\"session_start\", async (_event, ctx) => {\n let latest;\n for (const entry of ctx.sessionManager.getBranch()) {\n  if (entry.type === \"custom\" && entry.customType === \"my-state\") {\n   latest = entry.data;\n  }\n }\n // restore from latest\n});\n```\n\n## Rendering-Erweiterungspunkte\n\n## Benutzerdefinierter Nachrichten-Renderer\n\n```ts\npi.registerMessageRenderer(\"my-type\", (message, { expanded }, theme) => {\n // return pi-tui Component\n});\n```\n\nWird vom interaktiven Rendering verwendet, wenn benutzerdefinierte Nachrichten angezeigt werden.\n\n## Werkzeugaufruf-/Ergebnis-Renderer\n\nStellen Sie `renderCall` / `renderResult` bei `registerTool`-Definitionen für benutzerdefinierte Werkzeugvisualisierung in TUI bereit.\n\n## Einschränkungen und Fallstricke\n\n- Laufzeitaktionen sind während des Ladens der Erweiterung nicht verfügbar.\n- `tool_call`-Fehler blockieren die Ausführung (fail-closed).\n- Namenskonflikte von Befehlen mit integrierten Befehlen werden mit Diagnosemeldungen übersprungen.\n- Reservierte Tastenkürzel werden ignoriert (`ctrl+c`, `ctrl+d`, `ctrl+z`, `ctrl+k`, `ctrl+p`, `ctrl+l`, `ctrl+o`, `ctrl+t`, `ctrl+g`, `shift+tab`, `shift+ctrl+p`, `alt+enter`, `escape`, `enter`).\n- Behandeln Sie `ctx.reload()` als terminal für den aktuellen Befehlshandler-Frame.\n\n## Erweiterungen vs. Hooks vs. Custom-Tools\n\nVerwenden Sie die richtige Oberfläche:\n\n- **Erweiterungen** (`src/extensibility/extensions/*`): einheitliches System (Ereignisse + Werkzeuge + Befehle + Renderer + Provider-Registrierung).\n- **Hooks** (`src/extensibility/hooks/*`): separate Legacy-Ereignis-API.\n- **Custom-Tools** (`src/extensibility/custom-tools/*`): werkzeugfokussierte Module; wenn sie zusammen mit Erweiterungen geladen werden, werden sie angepasst und durchlaufen weiterhin Erweiterungs-Abfang-Wrapper.\n\nWenn Sie ein Paket benötigen, das Richtlinien, Werkzeuge, Befehls-UX und Rendering gemeinsam verwaltet, verwenden Sie Erweiterungen.\n",
	"de/extensions/gemini-manifest-extensions.md": "---\ntitle: Gemini Manifest-Erweiterungen\ndescription: >-\n  Gemini-Manifest-Erweiterungsformat für plattformübergreifende Skill- und\n  Agent-Kompatibilität.\nsidebar:\n  order: 7\n  label: Gemini-Manifest\ni18n:\n  sourceHash: 7134165a5f6d\n  translator: machine\n---\n\n# Gemini Manifest-Erweiterungen (`gemini-extension.json`)\n\nDieses Dokument beschreibt, wie der Coding-Agent Gemini-artige Manifest-Erweiterungen (`gemini-extension.json`) erkennt und in die Fähigkeit `extensions` einliest.\n\nEs behandelt **nicht** das Laden von TypeScript/JavaScript-Erweiterungsmodulen (`extensions/*.ts`, `index.ts`, `package.json xcsh.extensions`), das in `extension-loading.md` dokumentiert ist.\n\n## Implementierungsdateien\n\n- [`../src/discovery/gemini.ts`](../../packages/coding-agent/src/discovery/gemini.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/capability/extension.ts`](../../packages/coding-agent/src/capability/extension.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/loader.ts`](../../packages/coding-agent/src/extensibility/extensions/loader.ts)\n\n---\n\n## Was erkannt wird\n\nDer Gemini-Provider (`id: gemini`, Priorität `60`) registriert einen `extensions`-Loader, der zwei feste Wurzelpfade durchsucht:\n\n- Benutzer: `~/.gemini/extensions`\n- Projekt: `<cwd>/.gemini/extensions`\n\nDie Pfadauflösung erfolgt direkt über `ctx.home` und `ctx.cwd` mittels `getUserPath()` / `getProjectPath()`.\n\nWichtige Bereichsregel: Die Projektsuche ist auf **cwd-only** beschränkt. Übergeordnete Verzeichnisse werden nicht durchsucht.\n\n---\n\n## Regeln für das Verzeichnis-Scanning\n\nFür jeden Wurzelpfad (`~/.gemini/extensions` und `<cwd>/.gemini/extensions`) führt die Erkennung folgende Schritte aus:\n\n1. `readDirEntries(root)`\n2. Nur direkte untergeordnete Verzeichnisse behalten (`entry.isDirectory()`)\n3. Für jedes untergeordnete Element `<name>` wird genau folgendes versucht zu lesen:\n   - `<root>/<name>/gemini-extension.json`\n\nEs erfolgt keine rekursive Suche über eine Verzeichnisebene hinaus.\n\n### Versteckte Verzeichnisse\n\nDie Gemini-Manifest-Erkennung filtert Verzeichnisnamen mit Punkt-Präfix **nicht** heraus. Wenn ein verstecktes untergeordnetes Verzeichnis existiert und `gemini-extension.json` enthält, wird es berücksichtigt.\n\n### Fehlende/nicht lesbare Dateien\n\nWenn `gemini-extension.json` fehlt oder nicht lesbar ist, wird das Verzeichnis stillschweigend übersprungen (keine Warnung).\n\n---\n\n## Manifest-Struktur (gemäß Implementierung)\n\nDer Fähigkeitstyp definiert folgende Manifest-Struktur:\n\n```ts\ninterface ExtensionManifest {\n name?: string;\n description?: string;\n mcpServers?: Record<string, Omit<MCPServer, \"name\" | \"_source\">>;\n tools?: unknown[];\n context?: unknown;\n}\n```\n\nDas Verhalten zur Erkennungszeit ist absichtlich locker:\n\n- Eine erfolgreiche JSON-Analyse wird vorausgesetzt.\n- Es findet keine Laufzeit-Schemavalidierung für Feldtypen/-inhalte über die JSON-Syntax hinaus statt.\n- Das geparste Objekt wird als `manifest` im Fähigkeitselement gespeichert.\n\n### Namens-Normalisierung\n\n`Extension.name` wird wie folgt gesetzt:\n\n1. `manifest.name`, sofern nicht `null`/`undefined`\n2. andernfalls der Name des Erweiterungsverzeichnisses\n\nHier wird keine Überprüfung des String-Typs durchgeführt.\n\n---\n\n## Materialisierung in Fähigkeitselemente\n\nEin gültig geparste Manifest erzeugt ein `Extension`-Fähigkeitselement:\n\n```ts\n{\n name: manifest.name ?? <directory-name>,\n path: <extension-directory>,\n manifest: <parsed-json>,\n level: \"user\" | \"project\",\n _source: {\n  provider: \"gemini\",\n  providerName: \"Gemini CLI\" // attached by capability registry\n  path: <absolute-manifest-path>,\n  level: \"user\" | \"project\"\n }\n}\n```\n\nHinweise:\n\n- `_source.path` wird durch `createSourceMeta()` auf einen absoluten Pfad normalisiert.\n- Die Fähigkeitsvalidierung auf Registry-Ebene für `extensions` prüft nur das Vorhandensein von `name` und `path`.\n- Manifest-Inhalte (`mcpServers`, `tools`, `context`) werden bei der Erkennung nicht validiert.\n\n---\n\n## Fehlerbehandlung und Warnungssemantik\n\n### Mit Warnung\n\n- Ungültiges JSON in einer Manifest-Datei:\n  - Warnungsformat: `Invalid JSON in <manifestPath>`\n\n### Ohne Warnung (stilles Überspringen)\n\n- `extensions`-Verzeichnis fehlt\n- Untergeordnetes Verzeichnis enthält kein `gemini-extension.json`\n- Manifest-Datei nicht lesbar\n- Manifest-JSON ist syntaktisch gültig, aber semantisch unvollständig/ungewöhnlich\n\nDas bedeutet, dass teilweise Gültigkeit akzeptiert wird: Nur syntaktische JSON-Fehler lösen eine Warnung aus.\n\n---\n\n## Vorrang und Deduplizierung mit anderen Quellen\n\nDie Fähigkeit `extensions` wird durch die Fähigkeits-Registry über alle Provider hinweg aggregiert.\n\nAktuelle Provider für diese Fähigkeit:\n\n- `native` (`packages/coding-agent/src/discovery/builtin.ts`) Priorität `100`\n- `gemini` (`packages/coding-agent/src/discovery/gemini.ts`) Priorität `60`\n\nDer Deduplizierungsschlüssel ist `ext.name` (`extensionCapability.key = ext => ext.name`).\n\n### Providerübergreifender Vorrang\n\nDer Provider mit höherer Priorität gewinnt bei doppelten Erweiterungsnamen.\n\n- Wenn `native` und `gemini` beide den Erweiterungsnamen `foo` ausgeben, wird das native Element behalten.\n- Das Duplikat mit niedrigerer Priorität verbleibt nur in `result.all` mit `_shadowed = true`.\n\n### Reihenfolgeeffekte innerhalb eines Providers\n\nDa die Deduplizierung nach dem Prinzip „zuerst gesehen gewinnt\" funktioniert, ist die Reihenfolge der Elemente innerhalb eines Providers relevant.\n\n- Der Gemini-Loader fügt **Benutzer zuerst**, dann **Projekt** hinzu.\n- Daher werden bei doppelten Namen zwischen `~/.gemini/extensions` und `<cwd>/.gemini/extensions` der Benutzereintrag behalten und der Projekteintrag zurückgestellt.\n\nIm Gegensatz dazu erstellt der native Provider die Konfigurationsverzeichnisreihenfolge anders (`project` dann `user` in `getConfigDirs()`), sodass die interne Zurückstellung beim nativen Provider in entgegengesetzter Richtung erfolgt.\n\n---\n\n## Zusammenfassung des Verhaltens für Benutzer vs. Projekt\n\nSpeziell für Gemini-Manifeste gilt:\n\n- Sowohl der Benutzer- als auch der Projektwurzelpfad werden bei jedem Ladevorgang durchsucht.\n- Der Projektwurzelpfad ist auf `<cwd>/.gemini/extensions` festgelegt (keine Suche in übergeordneten Verzeichnissen).\n- Doppelte Namen innerhalb der Gemini-Quelle werden zugunsten des Benutzers aufgelöst.\n- Doppelte Namen gegenüber Providern mit höherer Priorität (insbesondere native) verlieren aufgrund der Priorität.\n\n---\n\n## Grenze: Erkennungsmetadaten vs. Laufzeit-Erweiterungsladung\n\nDie Erkennung von `gemini-extension.json` liefert derzeit Fähigkeitsmetadaten (`Extension`-Elemente). Ausführbare TS/JS-Erweiterungsmodule werden dabei **nicht** direkt geladen.\n\nDas Laden von Laufzeitmodulen (`discoverAndLoadExtensions()` / `loadExtensions()`) verwendet `extension-modules` und explizite Pfade und filtert automatisch erkannte Module derzeit ausschließlich auf den Provider `native`.\n\nPraktische Auswirkung:\n\n- Gemini-Manifest-Erweiterungen sind als Fähigkeitsdatensätze auffindbar.\n- Sie werden vom Erweiterungs-Loader-Pipeline nicht von sich aus als ausführbare Laufzeiterweiterungsmodule ausgeführt.\n\nDiese Grenze ist in der aktuellen Implementierung beabsichtigt und erklärt, warum Manifest-Erkennung und die Ladung ausführbarer Module auseinanderfallen können.\n",
	"de/extensions/marketplace.md": "---\ntitle: Marktplatz-Plugin-System\ndescription: >-\n  Marktplatz-Plugin-System zum Entdecken, Installieren und Verwalten kuratierter\n  Plugin-Sammlungen.\nsidebar:\n  order: 4\n  label: Marktplatz\ni18n:\n  sourceHash: 71d9f8f93a81\n  translator: machine\n---\n\n# Marktplatz-Plugin-System\n\nDas Marktplatz-System ermöglicht das Entdecken, Installieren und Verwalten von Plugins aus Git-gehosteten Katalogen. Es ist kompatibel mit dem Claude Code Plugin-Registrierungsformat.\n\n## Schnellstart\n\n```\n/marketplace add anthropics/f5-sales-demo-marketplace\n/marketplace install wordpress.com@f5-sales-demo-marketplace\n```\n\nOder geben Sie einfach `/marketplace` ohne Argumente ein, um den interaktiven Plugin-Browser zu öffnen.\n\n## Konzepte\n\nEin **Marktplatz** ist ein Git-Repository (oder lokales Verzeichnis), das eine Katalogdatei unter `.xcsh-plugin/marketplace.json` enthält. Der Katalog listet verfügbare Plugins mit ihren Quellen, Beschreibungen und Metadaten auf.\n\nEin **Plugin** ist ein Verzeichnis, das Skills, Befehle, Hooks, MCP-Server oder LSP-Server enthält. Plugins werden durch `name@marktplatz` identifiziert (z. B. `code-review@f5-sales-demo-marketplace`).\n\n**Geltungsbereiche**: Plugins können in zwei Geltungsbereichen installiert werden:\n\n- **user** (Standard) – in allen Projekten verfügbar, gespeichert unter `~/.xcsh/plugins/installed_plugins.json`\n- **project** – nur im aktuellen Projekt verfügbar, gespeichert unter `.xcsh/installed_plugins.json`\n\nProjektbezogene Installationen überschatten benutzerbezogene Installationen desselben Plugins.\n\n## Befehle\n\n### Interaktiver Modus\n\n| Befehl | Wirkung |\n|---|---|\n| `/marketplace` | Interaktiven Plugin-Browser öffnen (installieren) |\n\n### Marktplatzverwaltung\n\n| Befehl | Wirkung |\n|---|---|\n| `/marketplace add <source>` | Eine Marktplatzquelle hinzufügen |\n| `/marketplace remove <name>` | Einen Marktplatz entfernen |\n| `/marketplace update [name]` | Katalog(e) neu abrufen; Name weglassen, um alle zu aktualisieren |\n| `/marketplace list` | Konfigurierte Marktplätze auflisten |\n\n### Plugin-Operationen\n\n| Befehl | Wirkung |\n|---|---|\n| `/marketplace discover [marketplace]` | Verfügbare Plugins durchsuchen |\n| `/marketplace install [--force] [--scope user\\|project] name@marketplace` | Ein Plugin installieren |\n| `/marketplace uninstall [--scope user\\|project] name@marketplace` | Ein Plugin deinstallieren |\n| `/marketplace installed` | Installierte Marktplatz-Plugins auflisten |\n| `/marketplace upgrade [--scope user\\|project] [name@marketplace]` | Ein oder alle Plugins aktualisieren |\n\n### CLI-Entsprechungen\n\nDieselben Operationen sind über die Befehlszeile verfügbar:\n\n```\nxcsh plugin marketplace add <source>\nxcsh plugin marketplace remove <name>\nxcsh plugin marketplace update [name]\nxcsh plugin marketplace list\nxcsh plugin discover [marketplace]\nxcsh plugin install --scope project name@marketplace\n```\n\n## Marktplatzquellen\n\nWenn Sie `/marketplace add <source>` ausführen, klassifiziert das System die Quelle:\n\n| Quellenformat | Typ | Beispiel |\n|---|---|---|\n| `owner/repo` | GitHub-Kurzform | `anthropics/f5-sales-demo-marketplace` |\n| `https://...*.json` | Direkte Katalog-URL | `https://example.com/marketplace.json` |\n| `https://...*.git` oder `git@...` | Git-Repository | `https://github.com/org/repo.git` |\n| `./path` oder `~/path` oder `/path` | Lokales Verzeichnis | `./my-marketplace` |\n\nDas System klont das Repository (oder liest das lokale Verzeichnis), sucht `.xcsh-plugin/marketplace.json`, validiert es und speichert den Katalog lokal im Cache.\n\n## Katalogformat (marketplace.json)\n\nEin Marktplatzkatalog liegt unter `.xcsh-plugin/marketplace.json` im Repository-Stammverzeichnis:\n\n```json\n{\n  \"$schema\": \"https://anthropic.com/claude-code/marketplace.schema.json\",\n  \"name\": \"my-marketplace\",\n  \"owner\": {\n    \"name\": \"Your Name\",\n    \"email\": \"you@example.com\"\n  },\n  \"description\": \"A collection of plugins\",\n  \"plugins\": [\n    {\n      \"name\": \"my-plugin\",\n      \"description\": \"What this plugin does\",\n      \"source\": \"./plugins/my-plugin\",\n      \"category\": \"development\",\n      \"homepage\": \"https://github.com/you/my-plugin\"\n    }\n  ]\n}\n```\n\n### Pflichtfelder\n\n| Feld | Beschreibung |\n|---|---|\n| `name` | Marktplatzname. Kleinbuchstaben, alphanumerisch, Bindestriche und Punkte. Muss mit alphanumerischem Zeichen beginnen und enden. Maximal 64 Zeichen. |\n| `owner.name` | Name des Marktplatzbetreibers |\n| `plugins` | Array von Plugin-Einträgen |\n\n### Plugin-Eintragsfelder\n\n| Feld | Erforderlich | Beschreibung |\n|---|---|---|\n| `name` | ja | Plugin-Name (dieselben Regeln wie beim Marktplatznamen) |\n| `source` | ja | Wo das Plugin zu finden ist (siehe unten) |\n| `description` | nein | Kurzbeschreibung |\n| `version` | nein | Versionsstring |\n| `author` | nein | `{ name, email? }` |\n| `homepage` | nein | URL |\n| `category` | nein | Kategoriestring (z. B. `development`, `productivity`, `security`) |\n| `tags` | nein | Array von String-Tags |\n| `strict` | nein | Boolescher Wert |\n| `commands` | nein | Bereitgestellte Slash-Befehle |\n| `agents` | nein | Bereitgestellte Agenten |\n| `hooks` | nein | Hook-Definitionen |\n| `mcpServers` | nein | MCP-Server-Definitionen |\n| `lspServers` | nein | LSP-Server-Definitionen |\n\n### Plugin-Quellenformate\n\nDas Feld `source` unterstützt mehrere Formate:\n\n**Relativer Pfad** (innerhalb des Marktplatz-Repos):\n\n```json\n\"source\": \"./plugins/my-plugin\"\n```\n\n**Git-Repository-URL**:\n\n```json\n\"source\": {\n  \"source\": \"url\",\n  \"url\": \"https://github.com/org/repo.git\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**GitHub-Kurzform**:\n\n```json\n\"source\": {\n  \"source\": \"github\",\n  \"repo\": \"org/repo\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Git-Unterverzeichnis** (Monorepo):\n\n```json\n\"source\": {\n  \"source\": \"git-subdir\",\n  \"url\": \"https://github.com/org/monorepo.git\",\n  \"path\": \"plugins/my-plugin\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**npm-Paket**:\n\n```json\n\"source\": {\n  \"source\": \"npm\",\n  \"package\": \"@scope/my-plugin\",\n  \"version\": \"1.0.0\"\n}\n```\n\n## Verzeichnisstruktur auf dem Datenträger\n\n```\n~/.xcsh/\n  config/\n    marketplaces.json          # Registrierung hinzugefügter Marktplätze\n  plugins/\n    installed_plugins.json     # Benutzerbezogen installierte Plugins\n    cache/\n      marketplaces/            # Gecachte Marktplatzkataloge\n      plugins/                 # Gecachte Plugin-Verzeichnisse\n\n<project>/.xcsh/\n  installed_plugins.json       # Projektbezogen installierte Plugins\n```\n\n## Benennungsregeln\n\nMarktplatz- und Plugin-Namen müssen:\n\n- Mit einem Kleinbuchstaben oder einer Ziffer beginnen und enden\n- Nur Kleinbuchstaben, Ziffern, Bindestriche und Punkte enthalten\n- Höchstens 64 Zeichen lang sein\n\nPlugin-IDs (`name@marktplatz`) dürfen insgesamt höchstens 128 Zeichen lang sein.\n\nGültige Beispiele: `my-plugin`, `code-review`, `wordpress.com`, `ai-firstify`\nUngültige Beispiele: `-bad`, `bad-`, `.bad`, `Bad`, `under_score`\n",
	"de/extensions/plugin-manager-installer-plumbing.md": "---\ntitle: Plugin-Manager und Installer-Mechanismen\ndescription: >-\n  Interna des Plugin-Managers zu Installation, Validierung,\n  Abhängigkeitsauflösung und Lebenszyklusverwaltung.\nsidebar:\n  order: 5\n  label: Plugin-Manager\ni18n:\n  sourceHash: 9c33e5a2c22a\n  translator: machine\n---\n\n# Plugin-Manager und Installer-Mechanismen\n\nDieses Dokument beschreibt, wie `xcsh plugin`-Operationen den Plugin-Zustand auf der Festplatte verändern und wie installierte Plugins zu Laufzeitfähigkeiten werden (heute Werkzeuge, Hooks/Commands-Pfadauflösung verfügbar).\n\n## Umfang und Architektur\n\nEs gibt zwei Plugin-Management-Implementierungen im Codebase:\n\n1. **Aktiver Pfad für CLI-Befehle**: `PluginManager` (`src/extensibility/plugins/manager.ts`)\n2. **Veraltetes Hilfsmodul**: Installer-Funktionen (`src/extensibility/plugins/installer.ts`)\n\nDie Ausführung von `xcsh plugin ...`-Befehlen erfolgt über `PluginManager`.\n\n`installer.ts` dokumentiert weiterhin wichtige Sicherheitsprüfungen und Dateisystemverhalten, ist jedoch nicht der Pfad, der von `src/commands/plugin.ts` + `src/cli/plugin-cli.ts` verwendet wird.\n\n## Lebenszyklus: von der CLI-Aufrufung bis zur Laufzeitverfügbarkeit\n\n```text\nxcsh plugin <action> ...\n  -> src/commands/plugin.ts\n  -> runPluginCommand(...) in src/cli/plugin-cli.ts\n  -> PluginManager method (install/list/uninstall/link/...) \n  -> mutate ~/.xcsh/plugins/{package.json,node_modules,xcsh-plugins.lock.json}\n  -> runtime discovery: discoverAndLoadCustomTools(...)\n  -> getAllPluginToolPaths(cwd)\n  -> custom tool loader imports tool modules\n```\n\n### Befehls-Einstiegspunkte\n\n- `src/commands/plugin.ts` definiert Befehle/Flags und leitet an `runPluginCommand` weiter.\n- `src/cli/plugin-cli.ts` ordnet Unterbefehle den `PluginManager`-Methoden zu:\n  - `install`, `uninstall`, `list`, `link`, `doctor`, `features`, `config`, `enable`, `disable`\n- Es existiert keine explizite `update`-Aktion; eine Aktualisierung erfolgt durch erneutes Ausführen von `install` mit einem neuen Paket-/Versionsspezifikation.\n\n## Modell auf der Festplatte\n\nDer globale Plugin-Zustand liegt unter `~/.xcsh/plugins`:\n\n- `package.json` — Abhängigkeitsmanifest, das von `bun install`/`bun uninstall` verwendet wird\n- `node_modules/` — installierte Plugin-Pakete oder Symlinks\n- `xcsh-plugins.lock.json` — Laufzeitzustand:\n  - pro Plugin aktiviert/deaktiviert\n  - ausgewählte Feature-Menge pro Plugin\n  - persistente Plugin-Einstellungen\n\nProjektlokale Überschreibungen befinden sich unter:\n\n- `<cwd>/.xcsh/plugin-overrides.json`\n\nÜberschreibungen sind aus der Perspektive von Manager/Loader schreibgeschützt (kein Schreibpfad vorhanden) und können Plugins deaktivieren oder Features/Einstellungen für dieses Projekt überschreiben.\n\n## Analyse der Plugin-Spezifikation und Interpretation von Metadaten\n\n## Grammatik der Install-Spezifikation\n\n`parsePluginSpec` (`parser.ts`) unterstützt:\n\n- `pkg` -> `features: null` (Standardverhalten)\n- `pkg[*]` -> alle Manifest-Features aktivieren\n- `pkg[]` -> keine optionalen Features aktivieren\n- `pkg[a,b]` -> benannte Features aktivieren\n- `@scope/pkg@1.2.3[feat]` -> Scoped + versioniertes Paket mit expliziter Feature-Auswahl\n\n`extractPackageName` entfernt das Versionssuffix für die Pfadsuche auf der Festplatte nach der Installation.\n\n## Manifestquelle und erforderliche Felder\n\nDas Manifest wird wie folgt aufgelöst:\n\n1. `package.json.xcsh`\n2. Fallback `package.json.pi`\n3. Fallback `{ version: package.version }`\n\nImplikationen:\n\n- Es gibt keine strikte Schemavalidierung im Manager/Loader.\n- Ein Paket ohne `xcsh`/`pi` ist weiterhin installierbar und auflistbar.\n- Das Laden von Laufzeit-Plugins (`getEnabledPlugins`) überspringt Pakete ohne `xcsh`/`pi`-Manifest.\n- `manifest.version` wird immer aus der Paket-`version` überschrieben.\n\nEin fehlerhaftes `package.json`-JSON ist ein harter Fehler beim Lesen; eine fehlerhafte Manifeststruktur kann später erst dann fehlschlagen, wenn bestimmte Felder verwendet werden.\n\n## Installations-/Aktualisierungsablauf (`PluginManager.install`)\n\n1. Feature-Klammernsyntax aus der Install-Spezifikation analysieren.\n2. Paketnamen gegen Regex + Denylist für Shell-Metazeichen validieren.\n3. Sicherstellen, dass `package.json` des Plugins vorhanden ist (`xcsh-plugins`, private Abhängigkeitszuordnung).\n4. `bun install <packageSpec>` in `~/.xcsh/plugins` ausführen.\n5. `node_modules/<name>/package.json` des installierten Pakets lesen.\n6. Manifest auflösen und `enabledFeatures` berechnen:\n   - `[*]`: alle deklarierten Features (oder `null`, wenn keine Feature-Zuordnung vorhanden)\n   - `[a,b]`: validiert, ob jedes Feature in der Manifest-Feature-Zuordnung existiert\n   - `[]`: leere Feature-Liste\n   - bare Spezifikation: `null` (Standardrichtlinie später im Loader verwenden)\n7. Laufzeitzustand der Lockdatei aktualisieren (Upsert): `{ version, enabledFeatures, enabled: true }`.\n\n### Aktualisierungssemantik\n\nDa die Aktualisierung installationsgesteuert ist:\n\n- `xcsh plugin install pkg@newVersion` aktualisiert die Abhängigkeit und die Lockdatei-Version.\n- Bestehende Einstellungen bleiben erhalten; der Zustandseintrag wird für Version/Features/Aktiviert überschrieben.\n- Es gibt keine separate Logik zum „Prüfen auf Updates\" oder zur transaktionalen Migration.\n\n## Entfernungsablauf (`PluginManager.uninstall`)\n\n1. Paketnamen validieren.\n2. `bun uninstall <name>` im Plugin-Verzeichnis ausführen.\n3. Plugin-Laufzeitzustand aus der Lockdatei entfernen:\n   - `config.plugins[name]`\n   - `config.settings[name]`\n\nSchlägt der Uninstall-Befehl fehl, wird der Laufzeitzustand nicht geändert.\n\n## Auflistungsablauf (`PluginManager.list`)\n\n1. Plugin-Abhängigkeitszuordnung aus `~/.xcsh/plugins/package.json` lesen.\n2. Laufzeit-Konfiguration der Lockdatei laden (fehlende Datei -> leere Standards).\n3. Projektüberschreibungen laden (`<cwd>/.xcsh/plugin-overrides.json`, Parse-/Lesefehler -> leeres Objekt mit Warnung).\n4. Für jede Abhängigkeit mit einer auflösbaren `package.json`:\n   - `InstalledPlugin`-Datensatz erstellen\n   - Feature-/Aktivierungszustand zusammenführen:\n     - Basis aus Lockdatei (oder Standards)\n     - Projektüberschreibungen können die Feature-Auswahl ersetzen\n     - Projektliste `disabled` markiert Plugin als deaktiviert\n\nDies ist der effektive Zustand, der von der CLI-Statusausgabe und den Einstellungs-/Feature-Operationen verwendet wird.\n\n## Link-Ablauf (`PluginManager.link`)\n\n`link` unterstützt die lokale Plugin-Entwicklung, indem ein lokales Paket als Symlink in `~/.xcsh/plugins/node_modules/<pkg.name>` eingebunden wird.\n\nVerhalten:\n\n1. `localPath` gegen Manager-cwd auflösen.\n2. Lokale `package.json` und `name`-Feld voraussetzen.\n3. Sicherstellen, dass Plugin-Verzeichnisse vorhanden sind.\n4. Bei scoped Namen das Scope-Verzeichnis erstellen.\n5. Vorhandenen Pfad am Ziel-Link-Ort entfernen.\n6. Symlink erstellen.\n7. Laufzeit-Lockdatei-Eintrag als aktiviert mit Standard-Features hinzufügen (`null`).\n\nHinweis: Das aktuelle `PluginManager.link` erzwingt nicht die im alten `installer.ts` vorhandene Cwd-Pfadgrenzenprüfung (`normalizedPath.startsWith(normalizedCwd)`), daher liegt die Vertrauensverantwortung beim Aufrufer.\n\n## Laufzeit-Laden: vom installierten Plugin zu aufrufbaren Fähigkeiten\n\n## Erkennungsfilter\n\n`getEnabledPlugins(cwd)` (`plugins/loader.ts`) liest:\n\n- Plugin-Abhängigkeitsmanifest (`package.json`)\n- Laufzeitzustand der Lockdatei\n- Projektüberschreibungen über `getConfigDirPaths(\"plugin-overrides.json\", { user: false, cwd })`\n\nFilterung:\n\n- überspringen, wenn kein Plugin-`package.json` vorhanden\n- überspringen, wenn Manifest (`xcsh`/`pi`) fehlt\n- überspringen, wenn in Lockdatei global deaktiviert\n- überspringen, wenn im Projekt deaktiviert\n\n## Fähigkeits-Pfadauflösung\n\nFür jedes aktivierte Plugin:\n\n- `resolvePluginToolPaths(plugin)`\n- `resolvePluginHookPaths(plugin)`\n- `resolvePluginCommandPaths(plugin)`\n\nJeder Resolver enthält Basiseinträge sowie Feature-Einträge:\n\n- explizite Feature-Liste -> nur ausgewählte Features\n- `enabledFeatures === null` -> Features aktivieren, die mit `default: true` markiert sind\n\nFehlende Dateien werden stillschweigend übersprungen (`existsSync`-Prüfung).\n\n## Aktuelle Unterschiede bei der Laufzeit-Verkabelung\n\n- **Werkzeuge sind heute in die Laufzeit eingebunden** über `discoverAndLoadCustomTools` (`custom-tools/loader.ts`), das `getAllPluginToolPaths(cwd)` aufruft.\n- Pfade werden bei der Erkennung benutzerdefinierter Werkzeuge anhand des aufgelösten absoluten Pfads dedupliziert (`seen`-Menge, erster Pfad gewinnt).\n- **Hooks/Commands-Resolver existieren** und werden exportiert, aber dieser Codepfad verdrahtet sie derzeit nicht auf dieselbe Weise in eine Laufzeit-Registry wie Werkzeuge.\n\n## Details zur Sperr-/Zustandsverwaltung\n\n`PluginManager` speichert die Laufzeit-Konfiguration pro Instanz im Speicher (`#runtimeConfig`) und lädt sie verzögert einmalig.\n\nLadeverhalten:\n\n- Lockdatei fehlt -> `{ plugins: {}, settings: {} }`\n- Lockdatei-Lese-/Parse-Fehler -> Warnung + dieselben leeren Standards\n\nSpeicherverhalten:\n\n- schreibt bei jeder Mutation die vollständige Lockdatei-JSON mit Einrückung\n\nEs gibt keine prozessübergreifende Sperrung oder Zusammenführungsstrategie; gleichzeitige Schreiber können sich gegenseitig überschreiben.\n\n## Sicherheitsprüfungen und Vertrauensgrenzen\n\n## Eingabe-/Paketvalidierung\n\nDer aktive Manager-Pfad erzwingt die Paketnamen-Validierung:\n\n- Regex für scoped/unscoped Paketspezifikationen (optional mit Version)\n- explizite Denylist für Shell-Metazeichen (`[;&|`$(){}[]<>\\\\]`)\n\nDies begrenzt das Risiko von Command-Injection beim Aufrufen von `bun install/uninstall`.\n\n## Dateisystem-Vertrauensgrenze\n\n- Plugin-Code wird in-process ausgeführt, wenn benutzerdefinierte Werkzeug-Module importiert werden; keine Sandbox.\n- Relative Pfade im Manifest werden gegen das Plugin-Paketverzeichnis verknüpft und nur auf Existenz geprüft.\n- Das Plugin-Paket selbst ist nach der Installation vertrauenswürdiger Code.\n\n## Nur im alten Installer vorhandene Prüfungen\n\n`installer.ts` enthält zusätzliche Link-Zeitprüfungen, die nicht im `PluginManager.link` gespiegelt sind:\n\n- Lokaler Pfad muss innerhalb des Projekt-Cwd aufgelöst werden\n- Zusätzliche Paketnamen-/Pfaddurchquerungsschutzmaßnahmen für die Benennung von Symlink-Zielen\n\nDa die CLI `PluginManager` verwendet, sind diese strengeren Link-Prüfungen derzeit nicht auf dem Hauptpfad.\n\n## Fehler-, Teilerfolgs- und Rollback-Verhalten\n\nDer Plugin-Manager ist nicht transaktional.\n\n| Operationsphase | Fehlerverhalten | Rollback |\n| --- | --- | --- |\n| `bun install` schlägt fehl | Installation bricht mit stderr ab | N/A (noch keine Zustandsschreibvorgänge) |\n| Installation erfolgreich, dann Manifest-/Feature-Validierung schlägt fehl | Befehl schlägt fehl | Kein Uninstall-Rollback; Abhängigkeit kann in `node_modules`/`package.json` verbleiben |\n| Installation erfolgreich, dann Schreiben der Lockdatei schlägt fehl | Befehl schlägt fehl | Kein Rollback des installierten Pakets |\n| `bun uninstall` erfolgreich, Schreiben der Lockdatei schlägt fehl | Befehl schlägt fehl | Paket entfernt, veralteter Laufzeitzustand kann verbleiben |\n| `link` entfernt altes Ziel, dann schlägt Symlink-Erstellung fehl | Befehl schlägt fehl | Keine Wiederherstellung des vorherigen Links/Verzeichnisses |\n\nIn der Praxis kann `doctor --fix` einige Abweichungen beheben (`bun install`, Bereinigung verwaister Konfigurationen, Bereinigung ungültiger Features), dies geschieht jedoch nach bestem Bemühen.\n\n## Zusammenfassung des Verhaltens bei fehlerhaftem/fehlendem Manifest\n\n- Fehlendes `xcsh`/`pi`-Feld:\n  - Installieren/Auflisten: toleriert (minimales Manifest)\n  - Laufzeit-Erkennung aktivierter Plugins: als Nicht-Plugin übersprungen\n- Fehlendes Feature, das durch Install-Spezifikation oder `features --set/--enable` referenziert wird: harter Fehler mit Liste verfügbarer Features\n- Ungültige `plugin-overrides.json`: ignoriert mit Fallback auf `{}` in Manager- und Loader-Pfaden\n- Fehlende Werkzeug-/Hook-/Befehlsdatei-Pfade, die durch das Manifest referenziert werden: während der Resolver-Expansion stillschweigend ignoriert; nur von `doctor` als Fehler markiert\n\n## Modusunterschiede und Vorrang\n\n- `--dry-run` (install): gibt synthetisches Installationsergebnis zurück, keine Dateisystem-/Netzwerk-/Zustandsschreibvorgänge.\n- `--json`: nur Ausgabeformatierung, keine Verhaltensänderung.\n- Projektüberschreibungen haben immer Vorrang vor der globalen Lockdatei für die Feature-/Einstellungsansicht.\n- Effektive Aktivierung ist `runtimeEnabled && !projectDisabled`.\n\n## Implementierungsdateien\n\n- [`src/commands/plugin.ts`](../../packages/coding-agent/src/commands/plugin.ts) — CLI-Befehlsdeklaration und Flag-Zuordnung\n- [`src/cli/plugin-cli.ts`](../../packages/coding-agent/src/cli/plugin-cli.ts) — Aktionsverteilung, benutzerseitige Befehlshandler\n- [`src/extensibility/plugins/manager.ts`](../../packages/coding-agent/src/extensibility/plugins/manager.ts) — aktive Implementierung von Installieren/Entfernen/Auflisten/Verknüpfen/Zustand/Doctor\n- [`src/extensibility/plugins/installer.ts`](../../packages/coding-agent/src/extensibility/plugins/installer.ts) — veraltete Installer-Hilfsfunktionen und zusätzliche Link-Sicherheitsprüfungen\n- [`src/extensibility/plugins/loader.ts`](../../packages/coding-agent/src/extensibility/plugins/loader.ts) — Erkennung aktivierter Plugins und Pfadauflösung für Werkzeuge/Hooks/Befehle\n- [`src/extensibility/plugins/parser.ts`](../../packages/coding-agent/src/extensibility/plugins/parser.ts) — Hilfsfunktionen zur Analyse von Install-Spezifikationen und Paketnamen\n- [`src/extensibility/plugins/types.ts`](../../packages/coding-agent/src/extensibility/plugins/types.ts) — Typverträge für Manifest/Laufzeit/Überschreibungen\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts) — Laufzeit-Verkabelung für von Plugins bereitgestellte Werkzeug-Module\n",
	"de/extensions/rulebook-matching-pipeline.md": "---\ntitle: Rulebook-Matching-Pipeline\ndescription: >-\n  Rulebook-Matching-Pipeline zur Auswahl und Anwendung kontextspezifischer\n  Anweisungssätze auf Agenten-Sitzungen.\nsidebar:\n  order: 6\n  label: Rulebook-Matching\ni18n:\n  sourceHash: a16a9c565053\n  translator: machine\n---\n\n# Rulebook-Matching-Pipeline\n\nDieses Dokument beschreibt, wie der Coding-Agent Regeln aus unterstützten Konfigurationsformaten erkennt, sie in eine einheitliche `Rule`-Struktur normalisiert, Vorrangkonflikte auflöst und das Ergebnis aufteilt in:\n\n- **Rulebook-Regeln** (dem Modell über System-Prompt + `rule://`-URLs verfügbar)\n- **TTSR-Regeln** (Time-Travel-Stream-Unterbrechungsregeln)\n\nEs spiegelt die aktuelle Implementierung wider, einschließlich partieller Semantiken und Metadaten, die zwar geparst, aber nicht erzwungen werden.\n\n## Implementierungsdateien\n\n- [`../src/capability/rule.ts`](../../packages/coding-agent/src/capability/rule.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/discovery/index.ts`](../../packages/coding-agent/src/discovery/index.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/cursor.ts`](../../packages/coding-agent/src/discovery/cursor.ts)\n- [`../src/discovery/windsurf.ts`](../../packages/coding-agent/src/discovery/windsurf.ts)\n- [`../src/discovery/cline.ts`](../../packages/coding-agent/src/discovery/cline.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/system-prompt.ts`](../../packages/coding-agent/src/system-prompt.ts)\n- [`../src/internal-urls/rule-protocol.ts`](../../packages/coding-agent/src/internal-urls/rule-protocol.ts)\n- [`../src/utils/frontmatter.ts`](../../packages/coding-agent/src/utils/frontmatter.ts)\n\n## 1. Kanonische Regelstruktur\n\nAlle Provider normalisieren Quelldateien in `Rule`:\n\n```ts\ninterface Rule {\n  name: string;\n  path: string;\n  content: string;\n  globs?: string[];\n  alwaysApply?: boolean;\n  description?: string;\n  ttsrTrigger?: string;\n  _source: SourceMeta;\n}\n```\n\nDie Capability-Identität ist `rule.name` (`ruleCapability.key = rule => rule.name`).\n\nKonsequenz: Vorrang und Deduplizierung sind **ausschließlich namensbasiert**. Zwei verschiedene Dateien mit demselben `name` werden als dieselbe logische Regel betrachtet.\n\n## 2. Erkennungsquellen und Normalisierung\n\n`src/discovery/index.ts` registriert Provider automatisch. Für `rules` sind die aktuellen Provider:\n\n- `native` (Priorität `100`)\n- `cursor` (Priorität `50`)\n- `windsurf` (Priorität `50`)\n- `cline` (Priorität `40`)\n\n### Nativer Provider (`builtin.ts`)\n\nLädt `.xcsh`-Regeln aus:\n\n- Projekt: `<cwd>/.xcsh/rules/*.{md,mdc}`\n- Benutzer: `~/.xcsh/agent/rules/*.{md,mdc}`\n\nNormalisierung:\n\n- `name` = Dateiname ohne `.md`/`.mdc`\n- Frontmatter wird über `parseFrontmatter` geparst\n- `content` = Body (Frontmatter entfernt)\n- `globs`, `alwaysApply`, `description`, `ttsr_trigger` werden direkt zugeordnet\n\nWichtiger Hinweis: `globs` wird als `string[] | undefined` gecastet, ohne Elementfilterung in diesem Provider.\n\n### Cursor-Provider (`cursor.ts`)\n\nLädt aus:\n\n- Benutzer: `~/.cursor/rules/*.{mdc,md}`\n- Projekt: `<cwd>/.cursor/rules/*.{mdc,md}`\n\nNormalisierung (`transformMDCRule`):\n\n- `description`: wird nur beibehalten, wenn es ein String ist\n- `alwaysApply`: nur `true` wird übernommen (`false` wird zu `undefined`)\n- `globs`: akzeptiert Arrays (nur String-Elemente) oder einzelne Strings\n- `ttsr_trigger`: nur String\n- `name` aus Dateiname ohne Erweiterung\n\n### Windsurf-Provider (`windsurf.ts`)\n\nLädt aus:\n\n- Benutzer: `~/.codeium/windsurf/memories/global_rules.md` (fester Regelname `global_rules`)\n- Projekt: `<cwd>/.windsurf/rules/*.md`\n\nNormalisierung:\n\n- `globs`: Array-von-Strings oder einzelner String\n- `alwaysApply`, `description` werden aus Frontmatter gecastet\n- `ttsr_trigger`: nur String\n- `name` aus Dateiname für Projektregeln\n\n### Cline-Provider (`cline.ts`)\n\nSucht aufwärts von `cwd` nach dem nächsten `.clinerules`:\n\n- falls Verzeichnis: lädt `*.md` darin\n- falls Datei: lädt einzelne Datei als Regel mit dem Namen `clinerules`\n\nNormalisierung:\n\n- `globs`: Array-von-Strings oder einzelner String\n- `alwaysApply`: nur wenn Boolean\n- `description`: nur String\n- `ttsr_trigger`: nur String\n\n## 3. Frontmatter-Parsing-Verhalten und Mehrdeutigkeiten\n\nAlle Provider verwenden `parseFrontmatter` (`utils/frontmatter.ts`) mit folgender Semantik:\n\n1. Frontmatter wird nur geparst, wenn der Inhalt mit `---` beginnt und ein abschließendes `\\n---` vorhanden ist.\n2. Der Body wird nach der Frontmatter-Extraktion getrimmt.\n3. Wenn das YAML-Parsing fehlschlägt:\n   - eine Warnung wird protokolliert,\n   - der Parser fällt auf einfaches `key: value`-Zeilenparsing zurück (`^(\\w+):\\s*(.*)$`).\n\nKonsequenzen der Mehrdeutigkeit:\n\n- Der Fallback-Parser unterstützt keine Arrays, verschachtelten Objekte, Quoting-Regeln oder Schlüssel mit Bindestrichen.\n- Fallback-Werte werden zu Strings (zum Beispiel wird `alwaysApply: true` zum String `\"true\"`), sodass Provider, die Boolean-/String-Typen erfordern, Metadaten möglicherweise verwerfen.\n- `ttsr_trigger` funktioniert im Fallback (Unterstrich-Schlüssel); Schlüssel wie `thinking-level` hingegen nicht.\n- Dateien ohne gültiges Frontmatter werden dennoch als Regeln mit leeren Metadaten und vollständigem Inhalt als Body geladen.\n\n## 4. Provider-Vorrang und Deduplizierung\n\n`loadCapability(\"rules\")` (`capability/index.ts`) führt Provider-Ausgaben zusammen und dedupliziert dann nach `rule.name`.\n\n### Vorrangmodell\n\n- Provider werden nach Priorität absteigend sortiert.\n- Bei gleicher Priorität gilt die Registrierungsreihenfolge (`cursor` vor `windsurf` aus `discovery/index.ts`).\n- Deduplizierung nach dem First-Wins-Prinzip: der zuerst angetroffene Regelname wird beibehalten; spätere Einträge mit gleichem Namen werden in `all` als `_shadowed` markiert und aus `items` ausgeschlossen.\n\nDie effektive Provider-Reihenfolge für Regeln ist derzeit:\n\n1. `native` (100)\n2. `cursor` (50)\n3. `windsurf` (50)\n4. `cline` (40)\n\n### Hinweis zur Reihenfolge innerhalb eines Providers\n\nInnerhalb eines Providers ergibt sich die Elementreihenfolge aus der Glob-Ergebnisreihenfolge von `loadFilesFromDir` plus der expliziten Push-Reihenfolge. Dies ist für den normalen Gebrauch ausreichend deterministisch, wird im Code jedoch nicht explizit sortiert.\n\nBemerkenswerte Unterschiede in der Quellreihenfolge:\n\n- `native` fügt zuerst Projekt- dann Benutzer-Konfigurationsverzeichnisse an.\n- `cursor` fügt zuerst Benutzer- dann Projektergebnisse an.\n- `windsurf` fügt zuerst die Benutzer-`global_rules` an, dann Projektregeln.\n- `cline` lädt nur die nächstgelegene `.clinerules`-Quelle.\n\n## 5. Aufteilung in Rulebook-, Always-Apply- und TTSR-Kategorien\n\nNach der Regelerkennung in `createAgentSession` (`sdk.ts`):\n\n1. Alle erkannten Regeln werden durchsucht.\n2. Regeln mit `condition` (Frontmatter-Schlüssel; `ttsr_trigger` / `ttsrTrigger` werden als Fallback akzeptiert) werden beim `TtsrManager` registriert.\n3. Eine separate `rulebookRules`-Liste wird mit folgendem Prädikat erstellt:\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && !rule.alwaysApply && !!rule.description\n```\n\n4. Eine `alwaysApplyRules`-Liste wird erstellt:\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && rule.alwaysApply === true\n```\n\n### Kategorien-Verhalten\n\n- **TTSR-Kategorie**: jede Regel mit `condition` (Beschreibung nicht erforderlich). Hat Vorrang vor anderen Kategorien.\n- **Always-Apply-Kategorie**: `alwaysApply === true`, kein TTSR. Vollständiger Inhalt wird in den System-Prompt injiziert. Über `rule://` auflösbar.\n- **Rulebook-Kategorie**: muss eine Beschreibung haben, darf kein TTSR sein, darf nicht `alwaysApply` sein. Wird im System-Prompt nach Name+Beschreibung aufgelistet; Inhalt wird bei Bedarf über `rule://` gelesen.\n- Eine Regel mit sowohl `condition` als auch `alwaysApply` wird nur in die TTSR-Kategorie eingeordnet (TTSR hat Vorrang).\n- Eine Regel mit sowohl `alwaysApply` als auch `description` wird nur in die Always-Apply-Kategorie eingeordnet (nicht ins Rulebook).\n\n## 6. Wie Metadaten die Laufzeitoberflächen beeinflussen\n\n### `description`\n\n- Erforderlich für die Aufnahme ins Rulebook.\n- Wird im `<rules>`-Block des System-Prompts gerendert.\n- Fehlende Beschreibung bedeutet, dass die Regel nicht über `rule://` verfügbar ist und nicht in den System-Prompt-Regeln aufgelistet wird.\n\n### `globs`\n\n- Wird in der `Rule` weitergereicht.\n- Wird als `<glob>...</glob>`-Einträge im Regelblock des System-Prompts gerendert.\n- Wird im Regel-UI-Status (`extensions`-Modusliste) angezeigt.\n- **Wird in dieser Pipeline nicht für automatisches Matching erzwungen.** Es gibt keinen Laufzeit-Glob-Matcher, der Regeln nach aktueller Datei/Tool-Ziel auswählt.\n\n### `alwaysApply`\n\n- Wird von Providern geparst und beibehalten.\n- Wird in der UI-Anzeige verwendet (`\"always\"`-Trigger-Label im Extensions-State-Manager).\n- Wird als Ausschlussbedingung für `rulebookRules` verwendet.\n- **Vollständiger Regelinhalt wird automatisch in den System-Prompt injiziert** (vor dem Rulebook-Regelabschnitt).\n- Die Regel ist auch über `rule://<name>` zum erneuten Lesen adressierbar.\n\n### `ttsr_trigger`\n\n- Wird auf `rule.ttsrTrigger` abgebildet.\n- Falls vorhanden, wird die Regel an den TTSR-Manager weitergeleitet, nicht ans Rulebook.\n\n## 7. System-Prompt-Inklusionspfad\n\n`buildSystemPromptInternal` erhält sowohl `rules` (Rulebook) als auch `alwaysApplyRules`.\n\nAlways-Apply-Regeln werden zuerst gerendert und injizieren ihren Rohinhalt direkt in den Prompt.\n\nRulebook-Regeln werden in einem `# Rules`-Abschnitt gerendert mit:\n\n- `Read rule://<name> when working in matching domain`\n- Name, `description` und optionaler `<glob>`-Liste jeder Regel\n\nDies ist beratend/kontextuell: Der Prompt-Text bittet das Modell, zutreffende Regeln zu lesen, aber der Code erzwingt keine Glob-Anwendbarkeit.\n\n## 8. `rule://`-Internes-URL-Verhalten\n\n`RuleProtocolHandler` wird registriert mit:\n\n```ts\nnew RuleProtocolHandler({ getRules: () => [...rulebookRules, ...alwaysApplyRules] })\n```\n\nImplikationen:\n\n- `rule://<name>` wird sowohl gegen **rulebookRules** als auch gegen **alwaysApplyRules** aufgelöst.\n- Reine TTSR-Regeln und Regeln ohne Beschreibung und ohne `alwaysApply` sind nicht über `rule://` adressierbar.\n- Die Auflösung erfolgt über exakte Namensübereinstimmung.\n- Unbekannte Namen geben einen Fehler mit einer Liste der verfügbaren Regelnamen zurück.\n- Der zurückgegebene Inhalt ist der rohe `rule.content` (Frontmatter entfernt), Inhaltstyp `text/markdown`.\n\n## 9. Bekannte partielle / nicht erzwungene Semantiken\n\n1. Provider-Beschreibungen erwähnen Legacy-Dateien (`.cursorrules`, `.windsurfrules`), aber die aktuellen Loader-Codepfade lesen diese Dateien tatsächlich nicht.\n2. `globs`-Metadaten werden dem Prompt/der UI bereitgestellt, aber nicht durch die Regelauswahllogik erzwungen.\n3. Die Regelauswahl für `rule://` umfasst Rulebook- und Always-Apply-Regeln, aber keine reinen TTSR-Regeln.\n4. Erkennungswarnungen (`loadCapability(\"rules\").warnings`) werden erzeugt, aber `createAgentSession` gibt sie in diesem Pfad derzeit nicht aus bzw. protokolliert sie nicht.\n",
	"de/extensions/skills.md": "---\ntitle: Skills\ndescription: >-\n  Skills-System zur Registrierung, Erkennung und dem Aufruf spezialisierter\n  Fähigkeiten im Coding-Agenten.\nsidebar:\n  order: 3\n  label: Skills\ni18n:\n  sourceHash: 3e062cc13851\n  translator: machine\n---\n\n# Skills\n\nSkills sind dateibasierte Fähigkeitspakete, die beim Start erkannt und dem Modell bereitgestellt werden als:\n\n- kompakte Metadaten im System-Prompt (Name + Beschreibung)\n- On-Demand-Inhalte über `read skill://...`\n- optionale interaktive `/skill:<name>`-Befehle\n\nDieses Dokument beschreibt das aktuelle Laufzeitverhalten in `src/extensibility/skills.ts`, `src/discovery/builtin.ts`, `src/internal-urls/skill-protocol.ts` und `src/discovery/agents-md.ts`.\n\n## Was ein Skill in dieser Codebasis ist\n\nEin erkannter Skill wird wie folgt dargestellt:\n\n- `name`\n- `description`\n- `filePath` (der `SKILL.md`-Pfad)\n- `baseDir` (Skill-Verzeichnis)\n- Quell-Metadaten (`provider`, `level`, Pfad)\n\nDie Laufzeitumgebung erfordert für die Gültigkeit nur `name` und `path`. In der Praxis hängt die Übereinstimmungsqualität davon ab, dass `description` aussagekräftig ist.\n\n## Erforderliche Struktur und Erwartungen an SKILL.md\n\n### Verzeichnisstruktur\n\nFür die providerbasierte Erkennung (native/Claude/Codex/Agents/Plugin-Provider) werden Skills als **eine Ebene unterhalb von `skills/`** erkannt:\n\n- `<skills-root>/<skill-name>/SKILL.md`\n\nVerschachtelte Muster wie `<skills-root>/group/<skill>/SKILL.md` werden von Provider-Loadern nicht erkannt.\n\nBei `skills.customDirectories` verwendet die Suche dieselbe nicht-rekursive Struktur (`*/SKILL.md`).\n\n```text\nProvider-erkannte Struktur (nicht-rekursiv unter skills/):\n\n<root>/skills/\n  ├─ postgres/\n  │   └─ SKILL.md      ✅ erkannt\n  ├─ pdf/\n  │   └─ SKILL.md      ✅ erkannt\n  └─ team/\n      └─ internal/\n          └─ SKILL.md  ❌ von Provider-Loadern nicht erkannt\n\nDie Suche in benutzerdefinierten Verzeichnissen ist ebenfalls nicht-rekursiv, daher werden verschachtelte Pfade ignoriert, sofern Sie `customDirectories` nicht auf das übergeordnete verschachtelte Verzeichnis verweisen.\n```\n\n### `SKILL.md`-Frontmatter\n\nUnterstützte Frontmatter-Felder des Skill-Typs:\n\n- `name?: string`\n- `description?: string`\n- `globs?: string[]`\n- `alwaysApply?: boolean`\n- zusätzliche Schlüssel werden als unbekannte Metadaten gespeichert\n\nAktuelles Laufzeitverhalten:\n\n- `name` verwendet standardmäßig den Namen des Skill-Verzeichnisses\n- `description` ist erforderlich für:\n  - native `.xcsh`-Provider-Skill-Erkennung (`requireDescription: true`)\n  - `skills.customDirectories`-Scans über `scanSkillsFromDir` in `src/discovery/helpers.ts` (nicht-rekursiv)\n- Nicht-native Provider können Skills ohne Beschreibung laden\n\n## Erkennungs-Pipeline\n\n`discoverSkills()` in `src/extensibility/skills.ts` führt zwei Durchläufe durch:\n\n1. **Fähigkeits-Provider** über `loadCapability(\"skills\")`\n2. **Benutzerdefinierte Verzeichnisse** über `scanSkillsFromDir(..., { requireDescription: true })` (einstufige Verzeichnisaufzählung)\n\nWenn `skills.enabled` den Wert `false` hat, gibt die Erkennung keine Skills zurück.\n\n### Integrierte Skill-Provider und Prioritäten\n\nDie Provider-Reihenfolge ist prioritätsbasiert (höher gewinnt), bei Gleichstand gilt die Registrierungsreihenfolge.\n\nAktuell registrierte Skill-Provider:\n\n1. `native` (Priorität 100) — `.xcsh`-Benutzer-/Projekt-Skills über `src/discovery/builtin.ts`\n2. `claude` (Priorität 80)\n3. Priorität-70-Gruppe (in Registrierungsreihenfolge):\n   - `claude-plugins`\n   - `agents`\n   - `codex`\n\nDer Deduplizierungsschlüssel ist der Skill-Name. Das erste Element mit einem bestimmten Namen gewinnt.\n\n### Quell-Schalter und Filterung\n\n`discoverSkills()` wendet folgende Steuerungen an:\n\n- Quell-Schalter: `enableCodexUser`, `enableClaudeUser`, `enableClaudeProject`, `enablePiUser`, `enablePiProject`\n- Glob-Filter auf Skill-Name:\n  - `ignoredSkills` (ausschließen)\n  - `includeSkills` (Zulassungsliste; leer bedeutet alle einschließen)\n\nFilterreihenfolge:\n\n1. Quelle aktiviert\n2. nicht ignoriert\n3. eingeschlossen (wenn Einschlussliste vorhanden)\n\nFür andere Provider als codex/claude/native (zum Beispiel `agents`, `claude-plugins`) fällt die Aktivierung derzeit auf folgenden Standardwert zurück: aktiviert, wenn **irgendein** integrierter Quell-Schalter aktiviert ist.\n\n### Kollisions- und Duplikatbehandlung\n\n- Fähigkeits-Deduplizierung behält bereits den ersten Skill pro Name (Provider mit höchster Priorität)\n- `extensibility/skills.ts` führt zusätzlich folgendes durch:\n  - Deduplizierung identischer Dateien über `realpath` (symlink-sicher)\n  - Ausgabe von Kollisionswarnungen, wenn ein späterer Skill-Name in Konflikt gerät\n  - Bereitstellung der komfortablen `discoverSkillsFromDir({ dir, source })`-API als schlanker Adapter über `scanSkillsFromDir`\n- Skills aus benutzerdefinierten Verzeichnissen werden nach Provider-Skills zusammengeführt und folgen demselben Kollisionsverhalten\n\n## Laufzeitnutzungsverhalten\n\n### System-Prompt-Bereitstellung\n\nDie System-Prompt-Konstruktion (`src/system-prompt.ts`) verwendet erkannte Skills wie folgt:\n\n- wenn das `read`-Werkzeug verfügbar ist:\n  - erkannte Skills-Liste in Prompt einschließen\n- andernfalls:\n  - erkannte Liste weglassen\n\nTask-Tool-Subagenten erhalten die erkannte/bereitgestellte Skills-Liste der Sitzung über die normale Sitzungserstellung; es gibt keine aufgabenspezifische Skill-Pinning-Überschreibung.\n\n### Interaktive `/skill:<name>`-Befehle\n\nWenn `skills.enableSkillCommands` den Wert `true` hat, registriert der interaktive Modus einen Slash-Befehl pro erkanntem Skill.\n\nVerhalten von `/skill:<name> [args]`:\n\n- liest die Skill-Datei direkt aus `filePath`\n- entfernt Frontmatter\n- fügt den Skill-Inhalt als benutzerdefinierte Folgemeldung ein\n- hängt Metadaten an (`Skill: <path>`, optional `User: <args>`)\n\n## Verhalten der `skill://`-URL\n\n`src/internal-urls/skill-protocol.ts` unterstützt:\n\n- `skill://<name>` → wird zur `SKILL.md` des jeweiligen Skills aufgelöst\n- `skill://<name>/<relative-path>` → wird innerhalb des Skill-Verzeichnisses aufgelöst\n\n```text\nskill://-URL-Auflösung\n\nskill://pdf\n  -> <pdf-base>/SKILL.md\n\nskill://pdf/references/tables.md\n  -> <pdf-base>/references/tables.md\n\nSchutzmaßnahmen:\n- absolute Pfade ablehnen\n- `..`-Traversierung ablehnen\n- jeden aufgelösten Pfad ablehnen, der <pdf-base> verlässt\n```\n\nAuflösungsdetails:\n\n- Skill-Name muss exakt übereinstimmen\n- relative Pfade werden URL-dekodiert\n- absolute Pfade werden abgelehnt\n- Pfad-Traversierung (`..`) wird abgelehnt\n- aufgelöster Pfad muss innerhalb von `baseDir` verbleiben\n- fehlende Dateien geben einen expliziten `File not found`-Fehler zurück\n\nInhaltstyp:\n\n- `.md` => `text/markdown`\n- alles andere => `text/plain`\n\nFür fehlende Ressourcen wird keine Fallback-Suche durchgeführt.\n\n## Skills vs. XCSH.md, Befehle, Werkzeuge, Hooks\n\n### Skills vs. XCSH.md\n\n- **Skills**: benannte, optionale Fähigkeitspakete, die nach Aufgabenkontext ausgewählt oder explizit angefordert werden\n- **XCSH.md/Kontextdateien**: persistente Anweisungsdateien, die als Kontext-Datei-Fähigkeit geladen und nach Ebenen-/Tiefenregeln zusammengeführt werden\n\n`src/discovery/agents-md.ts` durchläuft explizit übergeordnete Verzeichnisse ausgehend von `cwd`, um eigenständige `XCSH.md`-Dateien zu erkennen (bis zu Tiefe 20), wobei Segmente versteckter Verzeichnisse ausgeschlossen werden.\n\n### Skills vs. Slash-Befehle\n\n- **Skills**: modelllesbare Wissens-/Workflow-Inhalte\n- **Slash-Befehle**: benutzerseitig aufgerufene Befehls-Einstiegspunkte\n- `/skill:<name>` ist ein komfortabler Wrapper, der Skill-Text einfügt; er ändert nicht die Skill-Erkennungssemantik\n\n### Skills vs. benutzerdefinierte Werkzeuge\n\n- **Skills**: Dokumentations-/Workflow-Inhalte, die über Prompt-Kontext und `read` geladen werden\n- **Benutzerdefinierte Werkzeuge**: ausführbare Werkzeug-APIs, die vom Modell mit Schemata und Laufzeit-Seiteneffekten aufgerufen werden können\n\n### Skills vs. Hooks\n\n- **Skills**: passive Inhalte\n- **Hooks**: ereignisgesteuerte Laufzeit-Interceptoren, die Verhalten während der Ausführung blockieren/ändern können\n\n## Praktische Anleitungen zur Erstellung im Zusammenhang mit der Erkennungslogik\n\n- Legen Sie jeden Skill in seinem eigenen Verzeichnis ab: `<skills-root>/<skill-name>/SKILL.md`\n- Fügen Sie immer explizite Frontmatter-Felder `name` und `description` hinzu\n- Halten Sie referenzierte Ressourcen im selben Skill-Verzeichnis und greifen Sie darauf mit `skill://<name>/...` zu\n- Für verschachtelte Taxonomien (`team/domain/skill`) verweisen Sie `skills.customDirectories` auf das übergeordnete verschachtelte Verzeichnis; die Suche selbst bleibt nicht-rekursiv\n- Vermeiden Sie doppelte Skill-Namen über Quellen hinweg; der erste Treffer gewinnt nach Provider-Priorität\n",
	"de/index.md": "---\ntitle: xcsh Dokumentation\ndescription: >-\n  KI-gestützte Entwicklungs-CLI mit TypeScript Coding Agent und Rust Native\n  Layer für langlebige Sitzungen, MCP-Unterstützung und Plattform-Packaging.\nsidebar:\n  order: 0\n  label: Überblick\ni18n:\n  sourceHash: b9288f42bf46\n  translator: machine\n---\n\nxcsh ist eine KI-gestützte Entwicklungs-CLI mit einem TypeScript Coding Agent und einem\nRust Native Layer (`pi-natives`). Es erweitert die Open-Source-Linie\n[`badlogic/pi-mono`](https://github.com/badlogic/pi-mono) um eine\ngehärtete Laufzeitumgebung, langlebige Sitzungen mit Baumnavigation und Komprimierung,\nein Python IPython Tool, vollständige MCP-Unterstützung, ein Skills-System und\nPlattform-Packaging für Linux, macOS und Windows.\n\n## Wo Sie beginnen sollten\n\n- **[F5 XC Kontexte](/runtime-tools/context-command)** — Verbindung zu F5 Distributed Cloud\n  Tenants herstellen. Kontexte erstellen, zwischen ihnen wechseln, Namespaces und Anmeldeinformationen verwalten.\n- **Konfiguration** — wie xcsh Konfigurationen erkennt, auflöst und schichtet.\n- **Laufzeit & Tools** — die Bash- / Notebook- / Resolve-Tool-Laufzeitumgebungen und die\n  Slash-Befehlsoberfläche.\n- **Sitzungen** — Append-Only-Eintragsprotokoll, Baumnavigation, Komprimierung und das\n  autonome Gedächtnissystem.\n- **Natives (Rust)** — Architektur des `pi-natives` N-API-Addons, das\n  Shell / PTY / Medien / Suche antreibt.\n- **MCP** — Konfiguration, Protokollinterna, Laufzeit-Lebenszyklus und wie\n  Server und Tools erstellt werden.\n- **Erweiterungen, Skills & Plugins** — Erstellung, Laden, Matching-Regeln, der\n  Marketplace und der Plugin-Installer.\n- **Anbieter & Modelle** — Modellkonfiguration, Streaming-Interna und die\n  Python / IPython-Laufzeitumgebung.\n- **TUI** — Theming, der `/tree`-Befehl und Integrations-Hooks für\n  Erweiterungen und benutzerdefinierte Tools.\n\n## Wie diese Dokumentation organisiert ist\n\nJede Hauptgruppe in der Seitenleiste entspricht einem Subsystem des Agenten. Innerhalb\neiner Gruppe verlaufen die Seiten von \"Überblick\" bis \"Interna\", sodass Sie mit dem Lesen\naufhören können, sobald Sie genügend Kontext für die jeweilige Aufgabe haben.\n",
	"de/mcp/mcp-config.md": "---\ntitle: MCP-Konfiguration\ndescription: >-\n  MCP-Server-Konfiguration, Validierung und Verwaltung für die\n  Coding-Agent-Laufzeitumgebung.\nsidebar:\n  order: 1\n  label: Konfiguration\ni18n:\n  sourceHash: ef8b49458ce9\n  translator: machine\n---\n\n# MCP-Konfiguration in OMP\n\nDieser Leitfaden erklärt, wie Sie MCP-Server für den OMP Coding Agent hinzufügen, bearbeiten und validieren.\n\nMaßgebliche Quellen im Code:\n\n- Laufzeit-Konfigurationstypen: `packages/coding-agent/src/mcp/types.ts`\n- Konfigurations-Writer: `packages/coding-agent/src/mcp/config-writer.ts`\n- Loader + Validierung: `packages/coding-agent/src/mcp/config.ts`\n- Eigenständige `mcp.json`-Erkennung: `packages/coding-agent/src/discovery/mcp-json.ts`\n- Schema: `packages/coding-agent/src/config/mcp-schema.json`\n\n## Bevorzugte Konfigurationsorte\n\nOMP kann MCP-Server aus mehreren Tools erkennen (`.claude/`, `.cursor/`, `.vscode/`, `opencode.json` und weiteren), aber für OMP-native Konfiguration sollten Sie in der Regel eine dieser Dateien verwenden:\n\n- Projekt: `.xcsh/mcp.json`\n- Benutzer: `~/.xcsh/mcp.json`\n\nOMP akzeptiert auch eigenständige Fallback-Dateien im Projektstammverzeichnis:\n\n- `mcp.json`\n- `.mcp.json`\n\nVerwenden Sie `.xcsh/mcp.json`, wenn OMP die Konfiguration verwalten soll. Verwenden Sie `mcp.json` / `.mcp.json` im Stammverzeichnis nur, wenn Sie eine portable Fallback-Datei wünschen, die auch andere MCP-Clients lesen können.\n\n## Schema-Referenz hinzufügen\n\nFügen Sie diese Zeile am Anfang der Datei hinzu, um Editor-Autovervollständigung und Validierung zu aktivieren:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {}\n}\n```\n\nOMP schreibt dies jetzt automatisch, wenn `/mcp add`, `/mcp enable`, `/mcp disable`, `/mcp reauth` oder andere konfigurationsschreibende Abläufe eine OMP-verwaltete MCP-Datei erstellen oder aktualisieren.\n\n## Dateistruktur\n\nOMP unterstützt diese Struktur auf oberster Ebene:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"server-name\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"some-mcp-server\"]\n    }\n  },\n  \"disabledServers\": [\"server-name\"]\n}\n```\n\nSchlüssel auf oberster Ebene:\n\n- `$schema` — optionale JSON-Schema-URL für Tooling\n- `mcpServers` — Zuordnung von Servernamen zu Serverkonfiguration\n- `disabledServers` — Benutzer-Sperrliste, die zum Deaktivieren erkannter Server nach Name verwendet wird\n\nServernamen müssen dem Muster `^[a-zA-Z0-9_.-]{1,100}$` entsprechen.\n\n## Unterstützte Serverfelder\n\nGemeinsame Felder für jeden Transport:\n\n- `enabled?: boolean` — überspringt diesen Server wenn `false`\n- `timeout?: number` — Verbindungs-Timeout in Millisekunden\n- `auth?: { ... }` — Auth-Metadaten, die von OMP für OAuth/API-Key-Abläufe verwendet werden\n- `oauth?: { ... }` — explizite OAuth-Client-Einstellungen, die während Auth/Reauth verwendet werden\n\n### `stdio`-Transport\n\n`stdio` ist der Standard, wenn `type` weggelassen wird.\n\nErforderlich:\n\n- `command: string`\n\nOptional:\n\n- `type?: \"stdio\"`\n- `args?: string[]`\n- `env?: Record<string, string>`\n- `cwd?: string`\n\nBeispiel:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/Users/alice/projects\",\n        \"/Users/alice/Documents\"\n      ]\n    }\n  }\n}\n```\n\nDies folgt dem offiziellen Filesystem-MCP-Server-Paket (`@modelcontextprotocol/server-filesystem`).\n\n### `http`-Transport\n\nErforderlich:\n\n- `type: \"http\"`\n- `url: string`\n\nOptional:\n\n- `headers?: Record<string, string>`\n\nBeispiel:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\nDies entspricht GitHubs gehostetem GitHub-MCP-Server-Endpunkt.\n\n### `sse`-Transport\n\nErforderlich:\n\n- `type: \"sse\"`\n- `url: string`\n\nOptional:\n\n- `headers?: Record<string, string>`\n\nBeispiel:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"legacy-remote\": {\n      \"type\": \"sse\",\n      \"url\": \"https://example.com/mcp/sse\"\n    }\n  }\n}\n```\n\n`sse` wird aus Kompatibilitätsgründen weiterhin unterstützt, aber die MCP-Spezifikation bevorzugt jetzt Streamable HTTP (`type: \"http\"`) für neue Server.\n\n## Auth-Felder\n\nOMP versteht zwei auth-bezogene Objekte.\n\n### `auth`\n\n```json\n{\n  \"type\": \"oauth\" | \"apikey\",\n  \"credentialId\": \"optional-stored-credential-id\",\n  \"tokenUrl\": \"optional-token-endpoint\",\n  \"clientId\": \"optional-client-id\",\n  \"clientSecret\": \"optional-client-secret\"\n}\n```\n\nVerwenden Sie dies, wenn OMP sich merken soll, wie Anmeldeinformationen für einen Server wiederhergestellt werden.\n\n### `oauth`\n\n```json\n{\n  \"clientId\": \"...\",\n  \"clientSecret\": \"...\",\n  \"redirectUri\": \"...\",\n  \"callbackPort\": 3334,\n  \"callbackPath\": \"/oauth/callback\"\n}\n```\n\nVerwenden Sie dies, wenn der MCP-Server explizite OAuth-Client-Einstellungen erfordert.\n\nSlack ist das derzeit deutlichste Beispiel. Slacks MCP-Server wird unter `https://mcp.slack.com/mcp` gehostet, verwendet Streamable HTTP und erfordert vertrauliches OAuth mit den Client-Anmeldeinformationen Ihrer Slack-App.\n\nBeispiel:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\nRelevante Slack-Endpunkte aus der Slack-Dokumentation:\n\n- MCP-Endpunkt: `https://mcp.slack.com/mcp`\n- Autorisierungs-Endpunkt: `https://slack.com/oauth/v2_user/authorize`\n- Token-Endpunkt: `https://slack.com/api/oauth.v2.user.access`\n\n## Gängige Kopiervorlagen\n\n### Filesystem-Server über stdio\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/absolute/path/one\",\n        \"/absolute/path/two\"\n      ]\n    }\n  }\n}\n```\n\n### GitHub gehosteter Server über HTTP\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\n### GitHub lokaler Server über Docker\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"-i\",\n        \"--rm\",\n        \"-e\",\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\",\n        \"ghcr.io/github/github-mcp-server\"\n      ],\n      \"env\": {\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n      }\n    }\n  }\n}\n```\n\nDies entspricht GitHubs offiziellem lokalem Docker-Image `ghcr.io/github/github-mcp-server`.\n\n### Slack gehosteter Server über OAuth\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\n## Geheimnisse und Variablenauflösung\n\nDies ist der Teil, der die meisten Probleme verursacht.\n\n### In `.xcsh/mcp.json` und `~/.xcsh/mcp.json`\n\nBevor OMP einen Server startet oder eine HTTP-Anfrage sendet, werden `env`- und `headers`-Werte wie folgt aufgelöst:\n\n1. Wenn ein Wert mit `!` beginnt, führt OMP ihn als Shell-Befehl aus und verwendet die getrimmte Standardausgabe.\n2. Andernfalls prüft OMP zunächst, ob der Wert einem Umgebungsvariablennamen entspricht.\n3. Wenn diese Umgebungsvariable nicht gesetzt ist, verwendet OMP den String literal.\n\nBeispiele:\n\n```json\n{\n  \"env\": {\n    \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n  },\n  \"headers\": {\n    \"X-MCP-Insiders\": \"true\"\n  }\n}\n```\n\nDas bedeutet, Folgendes ist gültig und praktisch für lokale Geheimnisse:\n\n- `\"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"` → aus der aktuellen Shell-Umgebung kopieren\n- `\"Authorization\": \"Bearer hardcoded-token\"` → den literalen Wert verwenden\n- `\"Authorization\": \"!printf 'Bearer %s' \\\"$GITHUB_TOKEN\\\"\"` → den Header aus einem Befehl erstellen\n\n### In `mcp.json` und `.mcp.json` im Stammverzeichnis\n\nDer eigenständige Fallback-Loader expandiert auch `${VAR}` und `${VAR:-default}` in Strings während der Erkennung.\n\nBeispiel:\n\n```json\n{\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\",\n      \"headers\": {\n        \"Authorization\": \"Bearer ${GITHUB_TOKEN}\"\n      }\n    }\n  }\n}\n```\n\nWenn Sie das am wenigsten überraschende OMP-Verhalten wünschen, bevorzugen Sie `.xcsh/mcp.json` und verwenden Sie explizite env/header-Werte.\n\n## `disabledServers`\n\n`disabledServers` ist hauptsächlich in der Benutzer-Konfigurationsdatei (`~/.xcsh/mcp.json`) nützlich, wenn ein Server aus einer anderen Quelle erkannt wird und Sie möchten, dass OMP ihn ignoriert, ohne die Konfiguration des anderen Tools zu bearbeiten.\n\nBeispiel:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"disabledServers\": [\"github\", \"slack\"]\n}\n```\n\n## `/mcp add` vs. direktes Bearbeiten der JSON-Datei\n\nVerwenden Sie `/mcp add`, wenn Sie eine geführte Einrichtung wünschen.\n\nVerwenden Sie direktes JSON-Bearbeiten, wenn:\n\n- Sie einen Transport oder eine Auth-Option benötigen, nach der der Assistent noch nicht fragt\n- Sie eine Serverdefinition aus einem anderen MCP-Client einfügen möchten\n- Sie schema-basierte Validierung in Ihrem Editor wünschen\n\nNach dem Bearbeiten verwenden Sie:\n\n- `/mcp reload` um Server in der aktuellen Sitzung neu zu erkennen und zu verbinden\n- `/mcp list` um zu sehen, aus welcher Konfigurationsdatei ein Server stammt\n- `/mcp test <name>` um einen einzelnen Server zu testen\n\n## Validierungsregeln, die OMP durchsetzt\n\nAus `validateServerConfig()` in `packages/coding-agent/src/mcp/config.ts`:\n\n- `stdio` erfordert `command`\n- `http` und `sse` erfordern `url`\n- ein Server kann nicht sowohl `command` als auch `url` setzen\n- unbekannte `type`-Werte werden abgelehnt\n\nPraktische Auswirkungen:\n\n- Das Weglassen von `type` bedeutet `stdio`\n- Wenn Sie eine Remote-Server-Konfiguration einfügen und `\"type\": \"http\"` vergessen, behandelt OMP sie als `stdio` und beschwert sich, dass `command` fehlt\n- `sse` bleibt aus Kompatibilitätsgründen gültig, aber neue gehostete Server sollten in der Regel als `http` konfiguriert werden\n\n## Erkennung und Priorität\n\nOMP führt keine doppelten Serverdefinitionen aus verschiedenen Dateien zusammen. Erkennungsanbieter werden priorisiert, und die höher priorisierte Definition gewinnt.\n\nIn der Praxis:\n\n- bevorzugen Sie `.xcsh/mcp.json` oder `~/.xcsh/mcp.json`, wenn Sie eine OMP-spezifische Überschreibung wünschen\n- halten Sie Servernamen nach Möglichkeit über Tools hinweg eindeutig\n- verwenden Sie `disabledServers` in der Benutzerkonfiguration, wenn eine Drittanbieter-Konfiguration immer wieder einen Server einführt, den Sie nicht wünschen\n\n## Fehlerbehebung\n\n### `Server \"name\": stdio server requires \"command\" field`\n\nSie haben wahrscheinlich `type: \"http\"` bei einem Remote-Server weggelassen.\n\n### `Server \"name\": both \"command\" and \"url\" are set`\n\nWählen Sie einen Transport. OMP behandelt `command` als stdio und `url` als http/sse.\n\n### `/mcp add` hat funktioniert, aber der Server verbindet sich trotzdem nicht\n\nDie JSON-Datei ist gültig, aber der Server ist möglicherweise trotzdem nicht erreichbar. Verwenden Sie `/mcp test <name>` und prüfen Sie, ob:\n\n- die Binärdatei oder das Docker-Image existiert\n- erforderliche Umgebungsvariablen gesetzt sind\n- die Remote-URL erreichbar ist\n- das OAuth- oder API-Token gültig ist\n\n### Der Server existiert in der Konfiguration eines anderen Tools, aber nicht in OMP\n\nFühren Sie `/mcp list` aus. OMP erkennt viele Drittanbieter-MCP-Dateien, aber das Laden auf Projektebene kann auch über die Einstellung `mcp.enableProjectConfig` deaktiviert werden.\n\n## Referenzen\n\n- MCP-Transport-Spezifikation: <https://modelcontextprotocol.io/specification/2025-03-26/basic/transports>\n- Filesystem-Server-Paket: <https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem>\n- GitHub MCP-Server: <https://github.com/github/github-mcp-server>\n- Slack MCP-Server-Dokumentation: <https://docs.slack.dev/ai/slack-mcp-server/>\n",
	"de/mcp/mcp-protocol-transports.md": "---\ntitle: MCP-Protokoll und Transport-Interna\ndescription: >-\n  MCP-Protokollimplementierung mit stdio-, SSE- und\n  Streamable-HTTP-Transportschichten.\nsidebar:\n  order: 2\n  label: Protokoll & Transporte\ni18n:\n  sourceHash: 48632064dd00\n  translator: machine\n---\n\n# MCP-Protokoll und Transport-Interna\n\nDieses Dokument beschreibt, wie coding-agent MCP-JSON-RPC-Messaging implementiert und wie Protokollbelange von Transportbelangen getrennt werden.\n\n## Geltungsbereich\n\nBehandelt:\n\n- JSON-RPC-Request/Response- und Notification-Ablauf\n- Request-Korrelation und Lebenszyklus für stdio- und HTTP/SSE-Transporte\n- Timeout- und Abbruchverhalten\n- Fehlerweitergabe und Behandlung fehlerhafter Payloads\n- Transportauswahl-Grenzen (`stdio` vs `http`/`sse`)\n- Welche Reconnect-/Retry-Verantwortlichkeiten auf Transport-Ebene vs. Manager-Ebene liegen\n\nBehandelt nicht die Extension-Authoring-UX oder die Befehlsoberfläche.\n\n## Implementierungsdateien\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/transports/stdio.ts`](../../packages/coding-agent/src/mcp/transports/stdio.ts)\n- [`src/mcp/transports/http.ts`](../../packages/coding-agent/src/mcp/transports/http.ts)\n- [`src/mcp/transports/index.ts`](../../packages/coding-agent/src/mcp/transports/index.ts)\n- [`src/mcp/json-rpc.ts`](../../packages/coding-agent/src/mcp/json-rpc.ts)\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n\n## Schichtgrenzen\n\n### Protokollschicht (JSON-RPC + MCP-Methoden)\n\n- Nachrichtenstrukturen sind in `types.ts` definiert (`JsonRpcRequest`, `JsonRpcNotification`, `JsonRpcResponse`, `JsonRpcMessage`).\n- Die MCP-Client-Logik (`client.ts`) bestimmt die Methodenreihenfolge und den Session-Handshake:\n  1. `initialize`-Request\n  2. `notifications/initialized`-Notification\n  3. Methodenaufrufe wie `tools/list`, `tools/call`\n\n### Transportschicht (`MCPTransport`)\n\n`MCPTransport` abstrahiert Zustellung und Lebenszyklus:\n\n- `request(method, params, options?) -> Promise<T>`\n- `notify(method, params?) -> Promise<void>`\n- `close()`\n- `connected`\n- Optionale Callbacks: `onClose`, `onError`, `onNotification`\n\nTransport-Implementierungen verwalten Framing- und I/O-Details:\n\n- `StdioTransport`: Newline-delimitiertes JSON über Subprocess-stdio\n- `HttpTransport`: JSON-RPC über HTTP POST, mit optionalen SSE-Responses/Listening\n\n### Wichtiger aktueller Vorbehalt\n\nTransport-Callbacks (`onClose`, `onError`, `onNotification`) sind implementiert, aber die aktuellen `MCPClient`/`MCPManager`-Abläufe verdrahten keine Reconnection-Logik mit diesen Callbacks. Notifications werden nur konsumiert, wenn der Aufrufer Handler registriert.\n\n## Transportauswahl\n\n`client.ts:createTransport()` wählt den Transport basierend auf der Konfiguration:\n\n- `type` weggelassen oder `\"stdio\"` -> `createStdioTransport`\n- `\"http\"` oder `\"sse\"` -> `createHttpTransport`\n\n`\"sse\"` wird als HTTP-Transport-Variante behandelt (gleiche Klasse), nicht als separate Transport-Implementierung.\n\n## JSON-RPC-Nachrichtenfluss und Korrelation\n\n## Request-IDs\n\nJeder Transport generiert pro Request IDs (`Math.random` + Timestamp-String). IDs sind transport-lokale Korrelations-Token.\n\n## Stdio-Korrelationspfad\n\n- Ausgehende Requests werden als ein JSON-Objekt + `\\n` serialisiert.\n- `#pendingRequests: Map<id, {resolve,reject}>` speichert laufende Requests.\n- Die Lese-Schleife parst JSONL von stdout und ruft `#handleMessage` auf.\n- Wenn eine eingehende Nachricht eine passende `id` hat, wird der Request aufgelöst/abgelehnt.\n- Wenn eine eingehende Nachricht `method` hat und keine `id`, wird sie als Notification behandelt und an `onNotification` gesendet.\n\nUnbekannte IDs werden ignoriert (keine Ablehnung, kein Error-Callback).\n\n## HTTP-Korrelationspfad\n\n- Ausgehender Request ist ein HTTP `POST` mit JSON-Body und generierter `id`.\n- Nicht-SSE-Response-Pfad: Eine JSON-RPC-Response parsen und `result` zurückgeben / bei `error` werfen.\n- SSE-Response-Pfad (`Content-Type: text/event-stream`): Events streamen, erste Nachricht zurückgeben, deren `id` der erwarteten Request-ID entspricht und `result` oder `error` enthält.\n- SSE-Nachrichten mit `method` und ohne `id` werden als Notifications behandelt.\n\nWenn der SSE-Stream vor einer passenden Response endet, schlägt der Request fehl mit `No response received for request ID ...`.\n\n## Notifications\n\nDer Client sendet JSON-RPC-Notifications über `transport.notify(...)`.\n\n- Stdio: Schreibt den Notification-Frame auf stdin (`jsonrpc`, `method`, optionale `params`) plus Newline.\n- HTTP: Sendet POST-Body ohne `id`; Erfolg akzeptiert `2xx` oder `202 Accepted`.\n\nServer-initiierte Notifications werden nur über den Transport-`onNotification`-Callback verfügbar gemacht; es gibt keinen globalen Standard-Subscriber im Manager/Client.\n\n## Stdio-Transport-Interna\n\n## Lebenszyklus und Zustandsübergänge\n\n- Initial: `connected=false`, `process=null`, Pending-Map leer\n- `connect()`:\n  - Subprocess mit konfiguriertem Command/Args/Env/Cwd starten\n  - Als verbunden markieren\n  - Stdout-Lese-Schleife starten (`readJsonl`)\n  - Stderr-Schleife starten (lesen/verwerfen; derzeit lautlos)\n- `close()`:\n  - Als getrennt markieren\n  - Alle ausstehenden Requests ablehnen (`Transport closed`)\n  - Subprocess beenden\n  - Auf Shutdown der Lese-Schleife warten\n  - `onClose` auslösen\n\nWenn die Lese-Schleife unerwartet endet, löst `finally` `#handleClose()` aus, das die gleiche Ablehnung ausstehender Requests und den Close-Callback durchführt.\n\n## Timeout und Abbruch\n\nPro Request:\n\n- Timeout standardmäßig `config.timeout ?? 30000`\n- Optionales `AbortSignal` vom Aufrufer\n- Abbruch und Timeout lehnen beide das ausstehende Promise ab und bereinigen den Map-Eintrag\n\nDer Abbruch ist nur lokal: Der Transport sendet keine Abbruch-Notification auf Protokollebene an den Server.\n\n## Behandlung fehlerhafter Payloads\n\nIn der Lese-Schleife:\n\n- Jede geparste JSONL-Zeile wird im `try/catch` an `#handleMessage` übergeben\n- Ausnahmen bei der Behandlung fehlerhafter/ungültiger Nachrichten werden verworfen (Kommentar `Skip malformed lines`)\n- Die Schleife fährt fort, sodass eine fehlerhafte Nachricht die Verbindung nicht beendet\n\nWenn der zugrundeliegende Stream-Parser wirft, wird `onError` aufgerufen (wenn noch verbunden), dann wird die Verbindung geschlossen.\n\n## Disconnect-/Fehlerverhalten\n\nWenn der Prozess endet oder der Stream geschlossen wird:\n\n- Alle laufenden Requests werden mit `Transport closed` abgelehnt\n- Kein automatischer Neustart oder Reconnect\n- Höhere Schichten müssen durch Erstellen eines neuen Transports reconnecten\n\n## Backpressure-/Streaming-Hinweise\n\n- Ausgehende Schreibvorgänge verwenden `stdin.write()` + `flush()` ohne Warten auf Drain-Semantik.\n- Es gibt kein explizites Queue- oder High-Watermark-Management im Transport.\n- Die eingehende Verarbeitung ist stream-gesteuert (`for await` über `readJsonl`), jeweils eine geparste Nachricht.\n\n## HTTP/SSE-Transport-Interna\n\n## Lebenszyklus und Verbindungssemantik\n\nDer HTTP-Transport hat einen logischen Verbindungszustand, aber der Request-Pfad ist zustandslos pro HTTP-Aufruf:\n\n- `connect()` setzt `connected=true` (kein Socket-/Session-Handshake)\n- Optionales Server-Session-Tracking über den `Mcp-Session-Id`-Header\n- `close()` sendet optional `DELETE` mit `Mcp-Session-Id`, bricht den SSE-Listener ab, löst `onClose` aus\n\n`connected` bedeutet also \"Transport nutzbar\", nicht \"persistenter Stream aufgebaut\".\n\n## Session-Header-Verhalten\n\n- Bei der POST-Response wird, wenn der `Mcp-Session-Id`-Header vorhanden ist, dieser vom Transport gespeichert.\n- Nachfolgende Requests/Notifications enthalten `Mcp-Session-Id`.\n- `close()` versucht, die Server-Session mit HTTP DELETE zu beenden; Beendigungsfehler werden ignoriert.\n\n## Timeout und Abbruch\n\nFür sowohl `request()` als auch `notify()`:\n\n- Timeout verwendet `AbortController` (`config.timeout ?? 30000`)\n- Externes Signal wird, falls vorhanden, über `AbortSignal.any([...])` zusammengeführt\n- AbortError-Behandlung unterscheidet zwischen Aufrufer-Abbruch und Timeout\n\nGeworfene Fehler:\n\n- Timeout: `Request timeout after ...ms` (oder `SSE response timeout ...`, `Notify timeout ...`)\n- Aufrufer-Abbruch: Ursprünglicher AbortError wird erneut geworfen, wenn das externe Signal bereits abgebrochen ist\n\n## HTTP-Fehlerweitergabe\n\nBei nicht-OK-Response:\n\n- Response-Text wird in den geworfenen Fehler aufgenommen (`HTTP <status>: <text>`)\n- Falls vorhanden, werden Auth-Hinweise aus `WWW-Authenticate` und `Mcp-Auth-Server` angehängt\n\nBei JSON-RPC-Fehlerobjekt:\n\n- Wirft `MCP error <code>: <message>`\n\nFehlerhafter JSON-Body (Fehler bei `response.json()`) wird als Parse-Exception weitergegeben.\n\n## SSE-Verhalten und Modi\n\nEs existieren zwei SSE-Pfade:\n\n1. **Per-Request-SSE-Response** (`#parseSSEResponse`)\n   - Wird verwendet, wenn der POST-Response-Content-Type `text/event-stream` ist\n   - Konsumiert den Stream, bis eine passende Response-ID gefunden wird\n   - Kann verschachtelte Notifications während desselben Streams verarbeiten\n\n2. **Hintergrund-SSE-Listener** (`startSSEListener()`)\n   - Optionaler GET-Listener für server-initiierte Notifications\n   - Wird derzeit nicht automatisch vom MCP-Manager/Client gestartet\n   - Wenn GET `405` zurückgibt, deaktiviert sich der Listener stillschweigend (Server unterstützt diesen Modus nicht)\n\n## Behandlung fehlerhafter Payloads und Disconnect\n\nSSE-JSON-Parsing-Fehler propagieren aus `readSseJson` und lehnen Request/Listener ab.\n\n- Request-SSE-Parse-Fehler lehnen den aktiven Request ab.\n- Hintergrund-Listener-Fehler lösen `onError` aus (außer AbortError).\n- Kein Auto-Reconnect für den Hintergrund-Listener.\n\n## `json-rpc.ts`-Utility vs. Transport-Abstraktion\n\n`src/mcp/json-rpc.ts` stellt `callMCP()` und `parseSSE()`-Hilfsfunktionen für direkte HTTP-MCP-Aufrufe bereit (verwendet von der Exa-Integration), nicht die `MCPTransport`-Abstraktion, die von `MCPClient`/`MCPManager` verwendet wird.\n\nBemerkenswerte Unterschiede zu `HttpTransport`:\n\n- Parst zuerst den gesamten Response-Text, extrahiert dann die erste `data:`-Zeile (`parseSSE`), mit JSON-Fallback\n- Kein Request-Timeout-Management, keine Abort-API, kein Session-ID-Handling, kein Transport-Lebenszyklus\n- Gibt das rohe JSON-RPC-Envelope-Objekt zurück\n\nDieser Pfad ist leichtgewichtig, aber weniger robust als die vollständige Transport-Implementierung.\n\n## Retry-/Reconnect-Verantwortlichkeiten\n\n## Transport-Ebene\n\nAktuelle Transport-Implementierungen führen **nicht** durch:\n\n- Retry fehlgeschlagener Requests\n- Reconnect nach stdio-Prozess-Ende\n- Reconnect von SSE-Listenern\n- Erneutes Senden laufender Requests nach Disconnect\n\nSie schlagen schnell fehl und propagieren Fehler.\n\n## Manager-/Client-Ebene\n\n`MCPManager` übernimmt die Discovery-/Initial-Connection-Orchestrierung und kann nur durch erneutes Ausführen der Connect-Abläufe reconnecten (`connectToServer`/`discoverAndConnect`-Pfade). Es heilt einen bereits verbundenen Transport bei Laufzeit-Fehler-Callbacks nicht automatisch.\n\n`MCPManager` verfügt über Startup-Fallback-Verhalten für langsame Server (verzögerte Tools aus dem Cache), aber das ist Tool-Verfügbarkeits-Fallback, kein Transport-Retry.\n\n## Zusammenfassung der Fehlerszenarien\n\n- **Fehlerhafte stdio-Nachrichtenzeile**: Wird verworfen; Stream fährt fort.\n- **Stdio-Stream/Prozess endet**: Transport schließt; ausstehende Requests werden als `Transport closed` abgelehnt.\n- **HTTP non-2xx**: Request/Notify wirft HTTP-Fehler.\n- **Ungültige JSON-Response**: Parse-Exception wird weitergegeben.\n- **SSE endet ohne passende ID**: Request schlägt fehl mit `No response received for request ID ...`.\n- **Timeout**: Transport-spezifischer Timeout-Fehler.\n- **Aufrufer-Abbruch**: AbortError/Reason wird vom Aufrufer-Signal weitergegeben.\n\n## Praktische Grenzregel\n\nWenn es um Nachrichtenstruktur, ID-Korrelation oder MCP-Methodenreihenfolge geht, gehört es zur Protokoll-/Client-Logik.\n\nWenn es um Framing (JSONL vs HTTP/SSE), Stream-Parsing, Fetch-/Spawn-Lebenszyklus, Timeout-Timer oder Verbindungsabbau geht, gehört es zur Transport-Implementierung.\n",
	"de/mcp/mcp-runtime-lifecycle.md": "---\ntitle: MCP-Laufzeit-Lebenszyklus\ndescription: >-\n  Lebenszyklus von MCP-Serverprozessen von der Initialisierung über\n  Tool-Registrierung, Gesundheitsüberwachung bis zum Herunterfahren.\nsidebar:\n  order: 3\n  label: Laufzeit-Lebenszyklus\ni18n:\n  sourceHash: d04cefaf38f8\n  translator: machine\n---\n\n# MCP-Laufzeit-Lebenszyklus\n\nDieses Dokument beschreibt, wie MCP-Server in der Coding-Agent-Laufzeitumgebung entdeckt, verbunden, als Tools bereitgestellt, aktualisiert und beendet werden.\n\n## Lebenszyklus im Überblick\n\n1. **SDK-Start** ruft `discoverAndLoadMCPTools()` auf (sofern MCP nicht deaktiviert ist).\n2. **Entdeckung** (`loadAllMCPConfigs`) löst MCP-Server-Konfigurationen aus Capability-Quellen auf, filtert deaktivierte/Projekt-/Exa-Einträge und bewahrt Quell-Metadaten.\n3. **Manager-Verbindungsphase** (`MCPManager.connectServers`) startet pro Server die Verbindung + `tools/list` parallel.\n4. **Schnelles Startup-Gate** wartet bis zu 250ms und kann dann zurückgeben:\n   - vollständig geladene `MCPTool`s,\n   - Fehler pro Server,\n   - oder gecachte `DeferredMCPTool`s für noch ausstehende Server.\n5. **SDK-Verdrahtung** führt MCP-Tools in die Laufzeit-Tool-Registry für die Sitzung zusammen.\n6. **Live-Sitzung** kann MCP-Tools über `/mcp`-Abläufe aktualisieren (`disconnectAll` + Neuentdeckung + `session.refreshMCPTools`).\n7. **Beendigung** erfolgt, wenn Aufrufer `disconnectServer`/`disconnectAll` aufrufen; der Manager löscht auch MCP-Tool-Registrierungen für getrennte Server.\n\n## Entdeckungs- und Ladephase\n\n### Einstiegspfad vom SDK\n\n`createAgentSession()` in `src/sdk.ts` führt den MCP-Start durch, wenn `enableMCP` auf true gesetzt ist (Standard):\n\n- ruft `discoverAndLoadMCPTools(cwd, { ... })` auf,\n- übergibt `authStorage`, Cache-Speicher und die Einstellung `mcp.enableProjectConfig`,\n- setzt immer `filterExa: true`,\n- protokolliert Lade-/Verbindungsfehler pro Server,\n- speichert den zurückgegebenen Manager in `toolSession.mcpManager` und im Sitzungsergebnis.\n\nWenn `enableMCP` auf false gesetzt ist, wird die MCP-Entdeckung vollständig übersprungen.\n\n### Konfigurationsentdeckung und Filterung\n\n`loadAllMCPConfigs()` (`src/mcp/config.ts`) lädt kanonische MCP-Server-Einträge über die Capability-Entdeckung und konvertiert sie in das Legacy-Format `MCPServerConfig`.\n\nFilterverhalten:\n\n- `enableProjectConfig: false` entfernt Einträge auf Projektebene (`_source.level === \"project\"`).\n- Server mit `enabled: false` werden vor Verbindungsversuchen übersprungen.\n- Exa-Server werden standardmäßig herausgefiltert und API-Schlüssel werden für die native Exa-Tool-Integration extrahiert.\n\nDas Ergebnis enthält sowohl `configs` als auch `sources` (Metadaten, die später für die Provider-Kennzeichnung verwendet werden).\n\n### Fehlerverhalten auf Entdeckungsebene\n\n`discoverAndLoadMCPTools()` unterscheidet zwei Fehlerklassen:\n\n- **Harter Entdeckungsfehler** (Ausnahme von `manager.discoverAndConnect`, typischerweise aus der Konfigurationsentdeckung): gibt ein leeres Tool-Set und einen synthetischen Fehler `{ path: \".mcp.json\", error }` zurück.\n- **Laufzeit-/Verbindungsfehler pro Server**: der Manager gibt Teilerfolge mit einer `errors`-Map zurück; andere Server fahren fort.\n\nDer Start lässt also nicht die gesamte Agent-Sitzung fehlschlagen, wenn einzelne MCP-Server ausfallen.\n\n## Manager-Zustandsmodell\n\n`MCPManager` verfolgt den Laufzeit-Lebenszyklus mit separaten Registries:\n\n- `#connections: Map<string, MCPServerConnection>` — vollständig verbundene Server.\n- `#pendingConnections: Map<string, Promise<MCPServerConnection>>` — Handshake in Bearbeitung.\n- `#pendingToolLoads: Map<string, Promise<{ connection, serverTools }>>` — verbunden, aber Tools werden noch geladen.\n- `#tools: CustomTool[]` — aktuelle MCP-Tool-Ansicht, die Aufrufern bereitgestellt wird.\n- `#sources: Map<string, SourceMeta>` — Provider-/Quell-Metadaten, auch bevor die Verbindung abgeschlossen ist.\n\n`getConnectionStatus(name)` leitet den Status aus diesen Maps ab:\n\n- `connected` wenn in `#connections`,\n- `connecting` wenn ausstehende Verbindung oder ausstehender Tool-Ladevorgang,\n- `disconnected` andernfalls.\n\n## Verbindungsaufbau und Startup-Timing\n\n## Verbindungspipeline pro Server\n\nFür jeden entdeckten Server in `connectServers()`:\n\n1. Quell-Metadaten speichern/aktualisieren,\n2. überspringen, wenn bereits verbunden/ausstehend,\n3. Transport-Felder validieren (`validateServerConfig`),\n4. Auth-/Shell-Substitutionen auflösen (`#resolveAuthConfig`),\n5. `connectToServer(name, resolvedConfig)` aufrufen,\n6. `listTools(connection)` aufrufen,\n7. Tool-Definitionen best-effort cachen (`MCPToolCache.set`).\n\nVerhalten von `connectToServer()` (`src/mcp/client.ts`):\n\n- erstellt stdio- oder HTTP/SSE-Transport,\n- führt MCP `initialize` + `notifications/initialized` durch,\n- verwendet Timeout (`config.timeout` oder 30s Standard),\n- schließt den Transport bei Initialisierungsfehler.\n\n### Schnelles Startup-Gate + Deferred-Fallback\n\n`connectServers()` wartet auf ein Race zwischen:\n\n- allen abgeschlossenen Verbindungs-/Tool-Lade-Aufgaben und\n- `STARTUP_TIMEOUT_MS = 250`.\n\nNach 250ms:\n\n- erfüllte Aufgaben werden zu aktiven `MCPTool`s,\n- abgelehnte Aufgaben erzeugen Fehler pro Server,\n- noch ausstehende Aufgaben:\n  - verwenden gecachte Tool-Definitionen wenn verfügbar (`MCPToolCache.get`), um `DeferredMCPTool`s zu erstellen,\n  - andernfalls wird gewartet, bis diese ausstehenden Aufgaben abgeschlossen sind.\n\nDies ist ein hybrides Startup-Modell: schnelle Rückgabe wenn Cache verfügbar ist, Korrektheitswarten wenn kein Cache vorhanden ist.\n\n### Hintergrund-Abschlussverhalten\n\nJedes ausstehende `toolsPromise` hat auch eine Hintergrund-Fortsetzung, die letztendlich:\n\n- den Tool-Anteil dieses Servers im Manager-Zustand über `#replaceServerTools` ersetzt,\n- den Cache beschreibt,\n- späte Fehler erst nach dem Startup protokolliert (`allowBackgroundLogging`).\n\n## Tool-Bereitstellung und Verfügbarkeit in der Live-Sitzung\n\n### Startup-Registrierung\n\n`discoverAndLoadMCPTools()` konvertiert Manager-Tools in `LoadedCustomTool[]` und dekoriert Pfade (`mcp:<server> via <providerName>` wenn bekannt).\n\n`createAgentSession()` fügt diese Tools dann in `customTools` ein, die gewrappt und mit Namen wie `mcp_<server>_<tool>` zur Laufzeit-Tool-Registry hinzugefügt werden.\n\n### Tool-Aufrufe\n\n- `MCPTool` ruft Tools über eine bereits bestehende `MCPServerConnection` auf.\n- `DeferredMCPTool` wartet auf `waitForConnection(server)` vor dem Aufruf; dies ermöglicht es, gecachte Tools bereitzustellen, bevor die Verbindung bereit ist.\n\nBeide geben strukturierte Tool-Ausgaben zurück und konvertieren Transport-/Tool-Fehler in `MCP error: ...`-Tool-Inhalte (Abbruch bleibt Abbruch).\n\n## Aktualisierungs-/Neuladeungs-Pfade (Startup vs. Live-Neuladung)\n\n### Initialer Startup-Pfad\n\n- einmalige Entdeckung/Ladung in `sdk.ts`,\n- Tools werden in der initialen Sitzungs-Tool-Registry registriert.\n\n### Interaktiver Neuladeungs-Pfad\n\nDer `/mcp reload`-Pfad (`src/modes/controllers/mcp-command-controller.ts`) führt aus:\n\n1. `mcpManager.disconnectAll()`,\n2. `mcpManager.discoverAndConnect()`,\n3. `session.refreshMCPTools(mcpManager.getTools())`.\n\n`session.refreshMCPTools()` (`src/session/agent-session.ts`) entfernt alle `mcp_`-Tools, wrappt die neuesten MCP-Tools erneut und reaktiviert das Tool-Set, sodass MCP-Änderungen ohne Neustart der Sitzung wirksam werden.\n\nEs gibt auch einen Folgepfad für späte Verbindungen: Nach dem Warten auf einen bestimmten Server wird, wenn der Status `connected` wird, erneut `session.refreshMCPTools(...)` ausgeführt, sodass neu verfügbare Tools in der Sitzung neu gebunden werden.\n\n## Gesundheit, Wiederverbindung und Verhalten bei Teilausfällen\n\nDas aktuelle Laufzeitverhalten ist bewusst minimal gehalten:\n\n- **Kein autonomer Gesundheitsmonitor** im Manager/Client.\n- **Keine automatische Wiederverbindungsschleife** wenn ein Transport abbricht.\n- Der Manager abonniert keine `onClose`/`onError`-Ereignisse des Transports; der Status wird registry-gesteuert ermittelt.\n- Wiederverbindung ist explizit: über den Neuladeungs-Ablauf oder direkten `connectServers()`-Aufruf.\n\nIm Betrieb:\n\n- der Ausfall eines Servers entfernt keine Tools von gesunden Servern,\n- Verbindungs-/Listenauflistungsfehler sind pro Server isoliert,\n- Tool-Cache und Hintergrund-Aktualisierungen sind best-effort (Warnungen/Fehler werden protokolliert, kein harter Stopp).\n\n## Beendigungssemantik\n\n### Beendigung auf Serverebene\n\n`disconnectServer(name)`:\n\n- entfernt ausstehende Einträge/Quell-Metadaten,\n- schließt den Transport wenn verbunden,\n- entfernt die `mcp_`-Tools dieses Servers aus dem Manager-Zustand.\n\n### Globale Beendigung\n\n`disconnectAll()`:\n\n- schließt alle aktiven Transporte mit `Promise.allSettled`,\n- löscht ausstehende Maps, Quellen, Verbindungen und die Manager-Tool-Liste.\n\nIn der aktuellen Verdrahtung wird die explizite Beendigung in MCP-Befehlsabläufen verwendet (für Neuladung/Entfernung/Deaktivierung). Es gibt keinen separaten automatischen Manager-Disposal-Hook im Startup-Pfad selbst; Aufrufer sind dafür verantwortlich, die Disconnect-Methoden des Managers aufzurufen, wenn sie ein deterministisches MCP-Herunterfahren benötigen.\n\n## Fehlermodi und Garantien\n\n| Szenario | Verhalten | Harter Fehler vs. Best-Effort |\n| --- | --- | --- |\n| Entdeckung wirft Ausnahme (Capability-/Konfigurationsladeepfad) | Loader gibt leere Tools + synthetischen `.mcp.json`-Fehler zurück | Best-Effort-Sitzungsstart |\n| Ungültige Serverkonfiguration | Server wird mit Validierungsfehlereintrag übersprungen | Best-Effort pro Server |\n| Verbindungs-Timeout/Initialisierungsfehler | Serverfehler wird aufgezeichnet; andere fahren fort | Best-Effort pro Server |\n| `tools/list` beim Startup noch ausstehend mit Cache-Treffer | Deferred-Tools werden sofort zurückgegeben | Best-Effort schneller Start |\n| `tools/list` beim Startup noch ausstehend ohne Cache | Startup wartet auf Abschluss der ausstehenden Aufgaben | Hartes Warten auf Korrektheit |\n| Später Hintergrund-Tool-Ladefehler | Wird nach dem Startup-Gate protokolliert | Best-Effort-Protokollierung |\n| Laufzeit-Transportabbruch | Keine automatische Wiederverbindung; zukünftige Aufrufe schlagen fehl bis zur Wiederverbindung/Neuladung | Best-Effort-Wiederherstellung durch manuelle Aktion |\n\n## Öffentliche API-Oberfläche\n\n`src/mcp/index.ts` re-exportiert Loader/Manager/Client-APIs für externe Aufrufer. `src/sdk.ts` stellt `discoverMCPServers()` als Komfort-Wrapper bereit, der die gleiche Loader-Ergebnisstruktur zurückgibt.\n\n## Implementierungsdateien\n\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts) — Loader-Fassade, Normalisierung von Entdeckungsfehlern, `LoadedCustomTool`-Konvertierung.\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts) — Lebenszyklus-Zustandsregistries, paralleler Verbindungs-/Listenauflistungsablauf, Aktualisierung/Trennung.\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts) — Transport-Setup, Initialisierungs-Handshake, Auflisten/Aufrufen/Trennen.\n- [`src/mcp/index.ts`](../../packages/coding-agent/src/mcp/index.ts) — MCP-Modul-API-Exporte.\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — Startup-Verdrahtung in Sitzung/Tool-Registry.\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts) — Konfigurationsentdeckung/-filterung/-validierung, die vom Manager verwendet wird.\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts) — `MCPTool`- und `DeferredMCPTool`-Laufzeitverhalten.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — `refreshMCPTools` Live-Neubindung.\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts) — Interaktive Neuladeungs-/Wiederverbindungsabläufe.\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — Subagent-MCP-Proxying über übergeordnete Manager-Verbindungen.\n",
	"de/mcp/mcp-server-tool-authoring.md": "---\ntitle: MCP-Server- und Tool-Erstellung\ndescription: >-\n  Leitfaden zum Erstellen benutzerdefinierter MCP-Server und zur Registrierung\n  von Tools für den Coding-Agent.\nsidebar:\n  order: 4\n  label: Server- & Tool-Erstellung\ni18n:\n  sourceHash: 160e7560ef1f\n  translator: machine\n---\n\n# MCP-Server- und Tool-Erstellung\n\nDieses Dokument erläutert, wie MCP-Server-Definitionen zu aufrufbaren `mcp_*`-Tools im Coding-Agent werden, und was Operatoren erwarten sollten, wenn Konfigurationen ungültig, dupliziert, deaktiviert oder durch Authentifizierung geschützt sind.\n\n## Architektur im Überblick\n\n```text\nConfig sources (.xcsh/.claude/.cursor/.vscode/mcp.json, mcp.json, etc.)\n  -> discovery providers normalize to canonical MCPServer\n  -> capability loader dedupes by server name (higher provider priority wins)\n  -> loadAllMCPConfigs converts to MCPServerConfig + skips enabled:false\n  -> MCPManager connects/listTools (with auth/header/env resolution)\n  -> MCPTool/DeferredMCPTool bridge exposes tools as mcp_<server>_<tool>\n  -> AgentSession.refreshMCPTools replaces live MCP tools immediately\n```\n\n## 1) Server-Konfigurationsmodell und Validierung\n\n`src/mcp/types.ts` definiert die Erstellungsstruktur, die von MCP-Konfigurationsautoren und zur Laufzeit verwendet wird:\n\n- `stdio` (Standard, wenn `type` fehlt): erfordert `command`, optional `args`, `env`, `cwd`\n- `http`: erfordert `url`, optional `headers`\n- `sse`: erfordert `url`, optional `headers` (aus Kompatibilitätsgründen beibehalten)\n- Gemeinsame Felder: `enabled`, `timeout`, `auth`\n\n`validateServerConfig()` (`src/mcp/config.ts`) erzwingt grundlegende Transport-Regeln:\n\n- Lehnt Konfigurationen ab, die sowohl `command` als auch `url` setzen\n- Erfordert `command` für stdio\n- Erfordert `url` für http/sse\n- Lehnt unbekannte `type`-Werte ab\n\n`config-writer.ts` wendet diese Validierung für Hinzufüge-/Aktualisierungsoperationen an und validiert auch Servernamen:\n\n- Nicht leer\n- Maximal 100 Zeichen\n- Nur `[a-zA-Z0-9_.-]`\n\n### Transport-Fallstricke\n\n- Fehlendes `type` bedeutet stdio. Wenn Sie HTTP/SSE beabsichtigt, aber `type` weggelassen haben, wird `command` obligatorisch.\n- `sse` wird weiterhin akzeptiert, aber intern als HTTP-Transport behandelt (`createHttpTransport`).\n- Die Validierung ist strukturell, nicht erreichbarkeitsbezogen: Eine syntaktisch gültige URL kann trotzdem beim Verbindungsaufbau fehlschlagen.\n\n## 2) Erkennung, Normalisierung und Prioritäten\n\n### Capability-basierte Erkennung\n\n`loadAllMCPConfigs()` (`src/mcp/config.ts`) lädt kanonische `MCPServer`-Einträge über `loadCapability(mcpCapability.id)`.\n\nDie Capability-Schicht (`src/capability/index.ts`) führt dann folgendes aus:\n\n1. Lädt Provider in Prioritätsreihenfolge\n2. Dedupliziert nach `server.name` (erster Treffer = höchste Priorität)\n3. Validiert die deduplizierten Einträge\n\nErgebnis: Doppelte Servernamen über verschiedene Quellen hinweg werden nicht zusammengeführt. Eine Definition gewinnt; Duplikate mit niedrigerer Priorität werden überdeckt.\n\n### `.mcp.json` und verwandte Dateien\n\nDer dedizierte Fallback-Provider in `src/discovery/mcp-json.ts` liest `mcp.json` und `.mcp.json` aus dem Projektstammverzeichnis (niedrige Priorität).\n\nIn der Praxis kommen MCP-Server auch von Providern mit höherer Priorität (zum Beispiel native `.xcsh/...`- und toolspezifische Konfigurationsverzeichnisse). Hinweise zur Erstellung:\n\n- Bevorzugen Sie `.xcsh/mcp.json` (Projekt) oder `~/.xcsh/mcp.json` (Benutzer) für explizite Kontrolle.\n- Verwenden Sie `mcp.json` / `.mcp.json` im Stammverzeichnis, wenn Sie Fallback-Kompatibilität benötigen.\n- Die Wiederverwendung desselben Servernamens in mehreren Quellen verursacht Prioritäts-Überdeckung, keine Zusammenführung.\n\n### Normalisierungsverhalten\n\n`convertToLegacyConfig()` (`src/mcp/config.ts`) bildet kanonische `MCPServer` auf die Laufzeit-`MCPServerConfig` ab.\n\nWichtiges Verhalten:\n\n- Transport wird abgeleitet als `server.transport ?? (command ? \"stdio\" : url ? \"http\" : \"stdio\")`\n- Deaktivierte Server (`enabled === false`) werden vor dem Verbindungsaufbau entfernt\n- Optionale Felder werden beibehalten, wenn vorhanden\n\n### Umgebungsvariablen-Expansion während der Erkennung\n\n`mcp-json.ts` expandiert Umgebungsvariablen-Platzhalter in String-Feldern mit `expandEnvVarsDeep()`:\n\n- Unterstützt `${VAR}` und `${VAR:-default}`\n- Nicht aufgelöste Werte bleiben als literale `${VAR}`-Strings erhalten\n\n`mcp-json.ts` führt auch Laufzeit-Typprüfungen für Benutzer-JSON durch und protokolliert Warnungen für ungültige `enabled`/`timeout`-Werte, anstatt die gesamte Datei fehlschlagen zu lassen.\n\n## 3) Authentifizierung und Laufzeitwert-Auflösung\n\n`MCPManager.prepareConfig()`/`#resolveAuthConfig()` (`src/mcp/manager.ts`) ist der letzte Durchlauf vor dem Verbindungsaufbau.\n\n### OAuth-Anmeldedaten-Injektion\n\nWenn die Konfiguration folgendes enthält:\n\n```ts\nauth: { type: \"oauth\", credentialId: \"...\" }\n```\n\nund die Anmeldedaten im Auth-Speicher existieren:\n\n- `http`/`sse`: Injiziert `Authorization: Bearer <access_token>`-Header\n- `stdio`: Injiziert `OAUTH_ACCESS_TOKEN`-Umgebungsvariable\n\nWenn die Anmeldedaten-Suche fehlschlägt, protokolliert der Manager eine Warnung und fährt mit nicht aufgelöster Authentifizierung fort.\n\n### Header-/Umgebungsvariablen-Wertauflösung\n\nVor dem Verbindungsaufbau löst der Manager jeden Header-/Umgebungsvariablen-Wert über `resolveConfigValue()` (`src/config/resolve-config-value.ts`) auf:\n\n- Wert beginnt mit `!` => Shell-Befehl ausführen, getrimmte Stdout-Ausgabe verwenden (gecacht)\n- Andernfalls wird der Wert zuerst als Umgebungsvariablenname behandelt (`process.env[name]`), Fallback auf den literalen Wert\n- Nicht aufgelöste Befehls-/Umgebungsvariablen-Werte werden aus der finalen Header-/Umgebungsvariablen-Map weggelassen\n\nBetrieblicher Hinweis: Dies bedeutet, dass ein falsch geschriebener Secret-Befehl/Umgebungsvariablen-Schlüssel stillschweigend diesen Header-/Umgebungsvariablen-Eintrag entfernen kann, was nachgelagert 401/403-Fehler oder Server-Startfehler verursacht.\n\n## 4) Tool-Bridge: MCP -> Agent-aufrufbare Tools\n\n`src/mcp/tool-bridge.ts` konvertiert MCP-Tool-Definitionen in `CustomTool`s.\n\n### Benennung und Kollisionsdomäne\n\nTool-Namen werden generiert als:\n\n```text\nmcp_<sanitized_server_name>_<sanitized_tool_name>\n```\n\nRegeln:\n\n- Kleinbuchstaben\n- Nicht-`[a-z_]`-Zeichen werden zu `_`\n- Wiederholte Unterstriche werden zusammengefasst\n- Redundantes `<server>_`-Präfix im Tool-Namen wird einmal entfernt\n\nDies vermeidet viele Kollisionen, aber nicht alle. Verschiedene Rohnamen können dennoch zum selben Bezeichner sanitisiert werden (zum Beispiel werden `my-server` und `my.server` ähnlich sanitisiert), und die Registry-Einfügung erfolgt nach dem Last-Write-Wins-Prinzip.\n\n### Schema-Abbildung\n\n`convertSchema()` behält das MCP-JSON-Schema weitgehend unverändert bei, ergänzt aber Objekt-Schemas ohne `properties` mit `{}` für Provider-Kompatibilität.\n\n### Ausführungs-Abbildung\n\n`MCPTool.execute()` / `DeferredMCPTool.execute()`:\n\n- Ruft MCP `tools/call` auf\n- Flacht MCP-Inhalte in darstellbaren Text ab\n- Gibt strukturierte Details zurück (`serverName`, `mcpToolName`, Provider-Metadaten)\n- Bildet vom Server gemeldete `isError`-Werte auf `Error: ...`-Textergebnisse ab\n- Bildet geworfene Transport-/Laufzeitfehler auf `MCP error: ...` ab\n- Bewahrt Abbruch-Semantik durch Übersetzung von AbortError in `ToolAbortError`\n\n## 5) Operator-Lebenszyklus: Hinzufügen/Bearbeiten/Entfernen und Live-Updates\n\nDer interaktive Modus stellt `/mcp` in `src/modes/controllers/mcp-command-controller.ts` bereit.\n\nUnterstützte Operationen:\n\n- `add` (Assistent oder Schnellhinzufügung)\n- `remove` / `rm`\n- `enable` / `disable`\n- `test`\n- `reauth` / `unauth`\n- `reload`\n\nKonfigurationsschreibvorgänge sind atomar (`writeMCPConfigFile`: temporäre Datei + Umbenennung).\n\nNach Änderungen ruft der Controller `#reloadMCP()` auf:\n\n1. `mcpManager.disconnectAll()`\n2. `mcpManager.discoverAndConnect()`\n3. `session.refreshMCPTools(mcpManager.getTools())`\n\n`refreshMCPTools()` ersetzt alle `mcp_`-Registry-Einträge und reaktiviert sofort den neuesten MCP-Tool-Satz, sodass Änderungen ohne Neustart der Sitzung wirksam werden.\n\n### Modusunterschiede\n\n- **Interaktiver/TUI-Modus**: `/mcp` bietet eine In-App-Benutzeroberfläche (Assistent, OAuth-Flow, Verbindungsstatus-Text, sofortige Laufzeit-Neubindung).\n- **SDK/Headless-Integration**: `discoverAndLoadMCPTools()` (`src/mcp/loader.ts`) gibt geladene Tools + Fehler pro Server zurück; keine `/mcp`-Befehls-UX.\n\n## 6) Benutzer-sichtbare Fehlermeldungen\n\nHäufige Fehlermeldungen, die Benutzer/Operatoren sehen:\n\n- Validierungsfehler beim Hinzufügen/Aktualisieren:\n  - `Invalid server config: ...`\n  - `Server \"<name>\" already exists in <path>`\n- Probleme mit Schnellhinzufügungs-Argumenten:\n  - `Use either --url or -- <command...>, not both.`\n  - `--token requires --url (HTTP/SSE transport).`\n- Verbindungs-/Testfehler:\n  - `Failed to connect to \"<name>\": <message>`\n  - Timeout-Hilfetext schlägt vor, das Timeout zu erhöhen\n  - Auth-Hilfetext für `401/403`\n- Auth/OAuth-Flows:\n  - `Authentication required ... OAuth endpoints could not be discovered`\n  - `OAuth flow timed out. Please try again.`\n  - `OAuth authentication failed: ...`\n- Verwendung deaktivierter Server:\n  - `Server \"<name>\" is disabled. Run /mcp enable <name> first.`\n\nFehlerhaftes Quell-JSON bei der Erkennung wird generell als Warnungen/Logs behandelt; Config-Writer-Pfade werfen explizite Fehler.\n\n## 7) Praktische Hinweise zur Erstellung\n\nFür robuste MCP-Erstellung in dieser Codebasis:\n\n1. Halten Sie Servernamen global eindeutig über alle MCP-fähigen Konfigurationsquellen hinweg.\n2. Bevorzugen Sie alphanumerische/Unterstrich-Namen, um Kollisionen durch sanitisierte Namen in generierten `mcp_*`-Tool-Namen zu vermeiden.\n3. Verwenden Sie explizite `type`-Angaben, um versehentliche stdio-Standards zu vermeiden.\n4. Behandeln Sie `enabled: false` als vollständige Deaktivierung: Der Server wird aus dem Laufzeit-Verbindungsset ausgelassen.\n5. Speichern Sie für OAuth-Konfigurationen eine gültige `credentialId`; andernfalls wird die Auth-Injektion übersprungen.\n6. Wenn Sie befehlsbasierte Secret-Auflösung (`!cmd`) verwenden, überprüfen Sie, dass die Befehlsausgabe stabil und nicht leer ist.\n\n## Implementierungsdateien\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts)\n- [`src/mcp/config-writer.ts`](../../packages/coding-agent/src/mcp/config-writer.ts)\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts)\n- [`src/discovery/mcp-json.ts`](../../packages/coding-agent/src/discovery/mcp-json.ts)\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/config/resolve-config-value.ts`](../../packages/coding-agent/src/config/resolve-config-value.ts)\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts)\n",
	"de/natives/natives-addon-loader-runtime.md": "---\ntitle: Natives Addon Loader Runtime\ndescription: >-\n  N-API Addon-Loader-Runtime mit Plattformerkennung, Fallback-Strategien und\n  Modulauflösung.\nsidebar:\n  order: 3\n  label: Addon-Loader\ni18n:\n  sourceHash: 743ea3e32c7c\n  translator: machine\n---\n\n# Natives Addon Loader Runtime\n\nDieses Dokument gibt einen umfassenden Einblick in die Addon-Lade- und Validierungsschicht in `@f5-sales-demo/pi-natives`: wie `native.ts` entscheidet, welche `.node`-Datei geladen wird, wann die Extraktion eingebetteter Payloads ausgeführt wird und wie Startfehler gemeldet werden.\n\n## Implementierungsdateien\n\n- `packages/natives/src/native.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/package.json`\n\n## Umfang und Verantwortung\n\nDie Verantwortlichkeiten des Loaders/der Runtime sind bewusst eng gefasst:\n\n- Erstellen einer plattform- und CPU-abhängigen Kandidatenliste für Addon-Dateinamen und -Verzeichnisse.\n- Optionales Materialisieren eines eingebetteten Addons in ein versioniertes benutzerspezifisches Cache-Verzeichnis.\n- Versuchen der Kandidaten in deterministischer Reihenfolge.\n- Ablehnen veralteter oder inkompatibler Addons über `validateNative`, bevor Bindungen freigegeben werden.\n\nNicht im Umfang enthalten: modulspezifisches Grep/Text/Highlight-Verhalten.\n\n## Laufzeiteingaben und abgeleiteter Zustand\n\nBei der Modulinitialisierung (`export const native = loadNative();`) berechnet `native.ts` statischen Kontext:\n\n- **Plattform-Tag**: ``${process.platform}-${process.arch}`` (zum Beispiel `darwin-arm64`).\n- **Paketversion**: aus `packages/natives/package.json` (Feld `version`).\n- **Kernverzeichnisse**:\n  - `nativeDir`: paketlokales Verzeichnis `packages/natives/native`.\n  - `execDir`: Verzeichnis, das `process.execPath` enthält.\n  - `versionedDir`: `<getNativesDir()>/<packageVersion>`.\n  - Fallback `userDataDir`:\n    - Windows: `%LOCALAPPDATA%/xcsh` (oder `%USERPROFILE%/AppData/Local/xcsh`).\n    - Nicht-Windows: `~/.local/bin`.\n- **Compiled-Binary-Modus** (`isCompiledBinary`): true, wenn einer der folgenden Punkte zutrifft:\n  - Umgebungsvariable `PI_COMPILED` ist gesetzt, oder\n  - `import.meta.url` enthält Bun-eingebettete Markierungen (`$bunfs`, `~BUN`, `%7EBUN`).\n- **Varianten-Override**: `PI_NATIVE_VARIANT` (nur `modern`/`baseline`; ungültige Werte werden ignoriert).\n- **Ausgewählte Variante**: expliziter Override, andernfalls AVX2-Laufzeiterkennung auf x64 (`modern` bei AVX2, sonst `baseline`).\n\n## Plattformunterstützung und Tag-Auflösung\n\n`SUPPORTED_PLATFORMS` ist fest definiert auf:\n\n- `linux-x64`\n- `linux-arm64`\n- `darwin-x64`\n- `darwin-arm64`\n- `win32-x64`\n\nVerhaltensdetail:\n\n- Nicht unterstützte Plattformen werden nicht vorab abgelehnt.\n- Der Loader versucht trotzdem alle berechneten Kandidaten zuerst.\n- Wenn nichts geladen werden kann, wird ein expliziter Fehler für nicht unterstützte Plattformen ausgelöst, der die unterstützten Tags auflistet.\n\nDies bewahrt nützliche Diagnoseinformationen für Grenzfälle, schlägt aber dennoch hart fehl bei wirklich nicht unterstützten Zielen.\n\n## Variantenauswahl (`modern` / `baseline` / Standard)\n\n### x64-Verhalten\n\n1. Wenn `PI_NATIVE_VARIANT` den Wert `modern` oder `baseline` hat, gewinnt dieser Wert.\n2. Andernfalls wird die AVX2-Unterstützung erkannt:\n   - Linux: `/proc/cpuinfo` nach `avx2` durchsuchen.\n   - macOS: `sysctl` abfragen (`machdep.cpu.leaf7_features`, Fallback `machdep.cpu.features`).\n   - Windows: PowerShell `[System.Runtime.Intrinsics.X86.Avx2]::IsSupported` ausführen.\n3. Ergebnis:\n   - AVX2 verfügbar -> `modern`\n   - AVX2 nicht verfügbar/nicht erkennbar -> `baseline`\n\n### Nicht-x64-Verhalten\n\n- Es wird keine Variante verwendet; der Loader bleibt beim Standard-Dateinamen (`pi_natives.<platform>-<arch>.node`).\n\n### Dateinamenkonstruktion\n\nGegeben `tag = <platform>-<arch>`:\n\n- Nicht-x64 oder keine Variante: `pi_natives.<tag>.node`\n- x64 + `modern`: in dieser Reihenfolge versuchen\n  1. `pi_natives.<tag>-modern.node`\n  2. `pi_natives.<tag>-baseline.node` (beabsichtigter Fallback)\n- x64 + `baseline`: nur `pi_natives.<tag>-baseline.node`\n\nDas `addonLabel`, das in endgültigen Fehlermeldungen verwendet wird, ist entweder `<tag>` oder `<tag> (<variant>)`.\n\n## Konstruktion von Kandidatenpfaden und Fallback-Reihenfolge\n\n`native.ts` erstellt Kandidatenpools vor jedem `require(...)`-Aufruf.\n\n### Release-Kandidaten\n\nAus der varianten-aufgelösten Dateinamenliste erstellt und in dieser Reihenfolge durchsucht:\n\n- **Nicht-kompilierte Runtime**:\n  1. `<nativeDir>/<filename>`\n  2. `<execDir>/<filename>`\n\n- **Kompilierte Runtime** (`PI_COMPILED` oder Bun-eingebettete Markierungen):\n  1. `<versionedDir>/<filename>`\n  2. `<userDataDir>/<filename>`\n  3. `<nativeDir>/<filename>`\n  4. `<execDir>/<filename>`\n\n`dedupedCandidates` entfernt Duplikate unter Beibehaltung der Reihenfolge des ersten Auftretens.\n\n### Endgültige Laufzeitsequenz\n\nBeim Laden:\n\n1. Ein optionaler eingebetteter Extraktionskandidat (sofern vorhanden) wird an vorderster Stelle eingefügt.\n2. Die verbleibenden deduplizierten Kandidaten werden in Reihenfolge versucht.\n3. Der erste Kandidat, der sowohl `require(...)`d wird als auch `validateNative(...)` besteht, gewinnt.\n\n## Lebenszyklus der Extraktion eingebetteter Addons\n\n`embedded-addon.ts` definiert eine generierte Manifest-Form:\n\n- `platformTag`\n- `version`\n- `files[]`, wobei jeder Eintrag `variant`, `filename`, `filePath` enthält\n\nDas aktuell eingecheckte Standard-Manifest ist `embeddedAddon: null`; kompilierte Artefakte können dies durch echte Metadaten ersetzen.\n\n### Zustandsmaschine für die Extraktion\n\nDie Extraktion (`maybeExtractEmbeddedAddon`) wird nur ausgeführt, wenn alle Bedingungen erfüllt sind:\n\n1. `isCompiledBinary === true`\n2. `embeddedAddon !== null`\n3. `embeddedAddon.platformTag === platformTag`\n4. `embeddedAddon.version === packageVersion`\n5. Eine varianten-geeignete eingebettete Datei wurde gefunden\n\nDie Variantendateiauswahl spiegelt die Laufzeit-Variantenabsicht wider:\n\n- Nicht-x64: `default` bevorzugen, dann erste verfügbare Datei.\n- x64 + `modern`: `modern` bevorzugen, Fallback auf `baseline`.\n- x64 + `baseline`: `baseline` erforderlich.\n\nMaterialisierungsverhalten:\n\n1. Sicherstellen, dass `<versionedDir>` existiert (`mkdirSync(..., { recursive: true })`).\n2. Wenn `<versionedDir>/<selected filename>` bereits existiert, wird es wiederverwendet (kein Neuschreiben).\n3. Andernfalls eingebettete Quelldatei `filePath` lesen und Zieldatei schreiben.\n4. Zielpfad für den Ladeversuch mit höchster Priorität zurückgeben.\n\nBei einem Fehler stürzt die Extraktion nicht sofort ab; stattdessen wird ein Fehlereintrag (Verzeichniserstellung oder Schreibfehler) hinzugefügt und der Loader fährt mit der normalen Kandidatenprüfung fort.\n\n## Lebenszyklus und Zustandsübergänge\n\n```text\nInit\n  -> Plattform/Version/Variante/Kandidatenlisten berechnen\n  -> (Kompiliert + eingebettetes Manifest stimmt überein?)\n       ja  -> Eingebettetes in versionedDir zu extrahieren versuchen (Fehler aufzeichnen, fortfahren)\n       nein -> Extraktion überspringen\n  -> Für jeden Laufzeitkandidaten in Reihenfolge:\n       require(candidate)\n       -> Erfolg: validateNative\n            -> bestanden: Bindungen zurückgeben (READY)\n            -> fehlgeschlagen: Fehler aufzeichnen, fortfahren\n       -> Fehlschlag: Fehler aufzeichnen, fortfahren\n  -> Keiner geladen:\n       wenn nicht unterstütztes Plattform-Tag -> Nicht unterstützte Plattform auslösen\n       sonst -> Laden fehlgeschlagen auslösen (vollständige Diagnoseinformationen zu versuchten Pfaden + Hinweise)\n```\n\n## Vertragsprüfungen in `validateNative`\n\n`validateNative(bindings, source)` erzwingt beim Start einen rein funktionsbasierten Vertrag über `NativeBindings`.\n\nMechanismus:\n\n- Für jeden erforderlichen Exportnamen wird `typeof bindings[name] === \"function\"` geprüft.\n- Fehlende Namen werden aggregiert.\n- Wenn welche fehlen, löst der Loader aus:\n  - Quell-Addon-Pfad,\n  - Liste fehlender Exporte,\n  - Hinweis zum Rebuild-Befehl.\n\nDies ist ein hartes Kompatibilitätstor gegen veraltete Binärdateien, unvollständige Builds und Symbol-/Namensabweichungen.\n\n### Zuordnung JS-API ↔ nativer Export (Validierungstor)\n\n| Im `validateNative` geprüfter JS-Bindungsname | Erwarteter nativer Exportname |\n| --- | --- |\n| `grep` | `grep` |\n| `glob` | `glob` |\n| `highlightCode` | `highlightCode` |\n| `executeShell` | `executeShell` |\n| `PtySession` | `PtySession` |\n| `Shell` | `Shell` |\n| `visibleWidth` | `visibleWidth` |\n| `getSystemInfo` | `getSystemInfo` |\n| `getWorkProfile` | `getWorkProfile` |\n| `invalidateFsScanCache` | `invalidateFsScanCache` |\n\nHinweis: `bindings.ts` deklariert nur das Basismember `cancelWork(id)`; die Moduldatei `types.ts` führt per Deklarations-Merge zusätzliche Symbole ein, die `validateNative` erzwingt.\n\n## Fehlerverhalten und Diagnose\n\n## Nicht unterstützte Plattform\n\nWenn alle Kandidaten fehlschlagen und `platformTag` nicht in `SUPPORTED_PLATFORMS` enthalten ist, löst der Loader aus:\n\n- `Unsupported platform: <tag>`\n- Vollständige Liste der unterstützten Plattformen\n- Explizite Anleitung zur Problemmeldung\n\n## Symptome bei veralteter Binärdatei / Nichtübereinstimmung\n\nTypisches Signal für eine veraltete Nichtübereinstimmung:\n\n- `Native addon missing exports (<candidate>). Missing: ...`\n\nHäufige Ursachen:\n\n- Alte `.node`-Binärdatei aus einer früheren Paketversion/API-Form.\n- Falsch ausgewähltes Variantenartefakt (bei x64).\n- Neuer Rust-Export nicht im geladenen Artefakt vorhanden.\n\nLoader-Verhalten:\n\n- Aufzeichnung von Fehlern wegen fehlender Exporte pro Kandidat.\n- Fortfahren mit der Prüfung der verbleibenden Kandidaten.\n- Wenn kein Kandidat validiert wird, enthält der abschließende Fehler jeden versuchten Pfad mit der jeweiligen Fehlermeldung.\n\n## Startfehler bei kompilierten Binärdateien\n\nIn der Diagnose im kompilierten Modus sind enthalten:\n\n- Erwartete versionierte Cache-Zielpfade (`<versionedDir>/<filename>`),\n- Behebungshinweis zum Löschen des veralteten `<versionedDir>` und erneutem Ausführen,\n- Direkte `curl`-Befehle zum Release-Download für jeden erwarteten Dateinamen.\n\n## Startfehler im nicht-kompilierten Modus\n\nIn der Diagnose im normalen Paket-/Runtime-Modus sind enthalten:\n\n- Neuinstallationshinweis (`bun install @f5-sales-demo/pi-natives`),\n- Lokaler Rebuild-Befehl (`bun --cwd=packages/natives run build`),\n- Optionaler x64-Varianten-Build-Hinweis (`TARGET_VARIANT=baseline|modern ...`).\n\n## Laufzeitverhalten\n\n- Der Loader verwendet immer die Release-Kandidatenkette.\n- Das Setzen von `PI_DEV` aktiviert nur kandidatenweise Konsolendiagnose (`Loaded native addon...` und Ladefehler).\n",
	"de/natives/natives-architecture.md": "---\ntitle: Natives-Architektur\ndescription: >-\n  Rust N-API Native-Addon-Architektur als Brücke zwischen TypeScript und\n  plattformspezifischen Operationen.\nsidebar:\n  order: 1\n  label: Architektur\ni18n:\n  sourceHash: d38ed2437bb7\n  translator: machine\n---\n\n# Natives-Architektur\n\n`@f5-sales-demo/pi-natives` ist ein dreischichtiger Stack:\n\n1. **TypeScript-Wrapper/API-Schicht** stellt stabile JS/TS-Einstiegspunkte bereit.\n2. **Addon-Lade-/Validierungsschicht** löst die `.node`-Binärdatei für die aktuelle Laufzeitumgebung auf und validiert sie.\n3. **Rust N-API-Modulschicht** implementiert performancekritische Primitive, die nach JS exportiert werden.\n\nDieses Dokument bildet die Grundlage für tiefergehende Dokumentationen auf Modulebene.\n\n## Implementierungsdateien\n\n- `packages/natives/src/index.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `crates/pi-natives/src/lib.rs`\n\n## Schicht 1: TypeScript-Wrapper/API-Schicht\n\n`packages/natives/src/index.ts` ist das öffentliche Barrel-Modul. Es gruppiert Exporte nach Fähigkeitsdomäne und re-exportiert typisierte Wrapper, anstatt rohe N-API-Bindings direkt freizugeben.\n\nAktuelle Top-Level-Gruppen:\n\n- **Such-/Textprimitive**: `grep`, `glob`, `text`, `highlight`\n- **Ausführungs-/Prozess-/Terminalprimitive**: `shell`, `pty`, `ps`, `keys`\n- **System-/Medien-/Konvertierungsprimitive**: `image`, `html`, `clipboard`, `system-info`, `work`\n\n`packages/natives/src/bindings.ts` definiert den Basis-Interface-Vertrag:\n\n- `NativeBindings` beginnt mit gemeinsamen Mitgliedern (`cancelWork(id: number)`)\n- Modulspezifische Bindings werden durch Declaration Merging aus der jeweiligen `types.ts` jedes Moduls hinzugefügt\n- `Cancellable` standardisiert Timeout- und Abort-Signal-Optionen für Wrapper, die Abbruchfunktionalität bereitstellen\n\n**Garantierter Vertrag (API-seitig):** Konsumenten importieren aus `@f5-sales-demo/pi-natives` und verwenden typisierte Wrapper.\n\n**Implementierungsdetail (kann sich ändern):** Declaration Merging und internes Wrapper-Layout (`src/<module>/index.ts`, `src/<module>/types.ts`).\n\n## Schicht 2: Addon-Laden und -Validierung\n\n`packages/natives/src/native.ts` ist verantwortlich für die Laufzeit-Addon-Auswahl, optionale Extraktion und Export-Validierung.\n\n### Kandidaten-Auflösungsmodell\n\n- Der Plattform-Tag ist `\"${process.platform}-${process.arch}\"`.\n- Aktuell unterstützte Tags sind:\n  - `linux-x64`\n  - `linux-arm64`\n  - `darwin-x64`\n  - `darwin-arm64`\n  - `win32-x64`\n- x64 kann CPU-Varianten verwenden:\n  - `modern` (AVX2-fähig)\n  - `baseline` (Fallback)\n- Nicht-x64 verwendet den Standard-Dateinamen (ohne Varianten-Suffix).\n\nDateinamen-Strategie:\n\n- Release: `pi_natives.<platform>-<arch>.node`\n- x64-Varianten-Release: `pi_natives.<platform>-<arch>-modern.node` und/oder `...-baseline.node`\n- `PI_DEV` aktiviert Loader-Diagnosen, ändert aber keine Addon-Dateinamen\n\n### Plattformspezifische Variantenerkennung\n\nFür x64 verwendet die Variantenauswahl:\n\n- **Linux**: `/proc/cpuinfo`\n- **macOS**: `sysctl machdep.cpu.leaf7_features` / `machdep.cpu.features`\n- **Windows**: PowerShell-Prüfung auf `System.Runtime.Intrinsics.X86.Avx2`\n\n`PI_NATIVE_VARIANT` kann `modern` oder `baseline` explizit erzwingen.\n\n### Binärdistributions- und Extraktionsmodell\n\n`packages/natives/package.json` enthält sowohl `src` als auch `native` in den veröffentlichten Dateien. Das `native/`-Verzeichnis speichert vorgefertigte Plattform-Artefakte.\n\nFür kompilierte Binärdateien (`PI_COMPILED` oder eingebettete Bun-Laufzeit-Marker) verhält sich der Loader wie folgt:\n\n1. Prüfung des versionierten Benutzer-Cache-Pfads: `<getNativesDir()>/<packageVersion>/...`\n2. Prüfung des Legacy-Speicherorts für kompilierte Binärdateien:\n   - Windows: `%LOCALAPPDATA%/xcsh` (Fallback `%USERPROFILE%/AppData/Local/xcsh`)\n   - Nicht-Windows: `~/.local/bin`\n3. Fallback auf das gepackte `native/`-Verzeichnis und Kandidaten im ausführbaren Verzeichnis\n\nFalls ein eingebettetes Addon-Manifest vorhanden ist (`embedded-addon.ts`, generiert durch `scripts/embed-native.ts`), kann `native.ts` die passende eingebettete Binärdatei vor dem Laden in das versionierte Cache-Verzeichnis materialisieren.\n\n### Validierung und Fehlermodi\n\nNach `require(candidate)` überprüft `validateNative(...)` die erforderlichen Exporte (zum Beispiel `grep`, `glob`, `highlightCode`, `PtySession`, `Shell`, `getSystemInfo`, `getWorkProfile`, `invalidateFsScanCache`).\n\nFehlerpfade sind explizit:\n\n- **Nicht unterstützter Plattform-Tag**: wirft einen Fehler mit der Liste unterstützter Plattformen\n- **Kein ladbarer Kandidat**: wirft einen Fehler mit allen versuchten Pfaden und Behebungshinweisen\n- **Fehlende Exporte**: wirft einen Fehler mit den genauen fehlenden Namen und dem Rebuild-Befehl\n- **Extraktionsfehler bei eingebetteten Addons**: protokolliert Verzeichnis-/Schreibfehler und fügt sie in die abschließende Ladediagnose ein\n\n**Garantierter Vertrag (API-seitig):** Das Laden des Addons gelingt entweder mit einem validierten Binding-Set oder schlägt schnell mit umsetzbarem Fehlertext fehl.\n\n**Implementierungsdetail (kann sich ändern):** Genaue Kandidaten-Suchreihenfolge und Reihenfolge der Fallback-Pfade für kompilierte Binärdateien.\n\n## Schicht 3: Rust N-API-Modulschicht\n\n`crates/pi-natives/src/lib.rs` ist das Rust-Eingangsmodul, das die exportierte Modul-Zugehörigkeit deklariert:\n\n- `clipboard`\n- `fd`\n- `fs_cache`\n- `glob`\n- `glob_util`\n- `grep`\n- `highlight`\n- `html`\n- `image`\n- `keys`\n- `prof`\n- `ps`\n- `pty`\n- `shell`\n- `system_info`\n- `task`\n- `text`\n\nDiese Module implementieren die N-API-Symbole, die von `native.ts` konsumiert und validiert werden. JS-seitige Namen werden durch die TS-Wrapper in `packages/natives/src` bereitgestellt.\n\n**Garantierter Vertrag (API-seitig):** Rust-Modulexporte müssen mit den von `validateNative` und den Wrapper-Modulen erwarteten Binding-Namen übereinstimmen.\n\n**Implementierungsdetail (kann sich ändern):** Interne Rust-Modul-Zerlegung und Hilfsmodul-Grenzen (`glob_util`, `task` usw.).\n\n## Zuständigkeitsgrenzen\n\nAuf Architekturebene ist die Zuständigkeit wie folgt aufgeteilt:\n\n- **TS-Wrapper/API-Zuständigkeit (`packages/natives/src`)**\n  - Öffentliche API-Gruppierung, Options-Typisierung und stabile JS-Ergonomie\n  - Abbruch-Oberfläche (`timeoutMs`, `AbortSignal`), die Aufrufern bereitgestellt wird\n- **Loader-Zuständigkeit (`packages/natives/src/native.ts`)**\n  - Laufzeit-Binärauswahl\n  - CPU-Variantenauswahl und Override-Behandlung\n  - Extraktion kompilierter Binärdateien und Kandidatenprüfung\n  - Harte Validierung der erforderlichen nativen Exporte\n- **Rust-Zuständigkeit (`crates/pi-natives/src`)**\n  - Algorithmische und systembezogene Implementierung\n  - Plattformnatives Verhalten und performancesensitive Logik\n  - N-API-Symbol-Implementierung, die von TS-Wrappern konsumiert wird\n\n## Laufzeitablauf (Übersicht)\n\n1. Der Konsument importiert aus `@f5-sales-demo/pi-natives`.\n2. Das Wrapper-Modul ruft das Singleton-`native`-Binding auf.\n3. `native.ts` wählt die Kandidaten-Binärdatei für Plattform/Architektur/Variante aus.\n4. Optionale Extraktion eingebetteter Binärdateien erfolgt bei kompilierten Distributionen.\n5. Das Addon wird geladen und das Export-Set wird validiert.\n6. Der Wrapper gibt typisierte Ergebnisse an den Aufrufer zurück.\n\n## Glossar\n\n- **Native Addon**: Eine `.node`-Binärdatei, die über Node-API (N-API) geladen wird.\n- **Plattform-Tag**: Laufzeit-Tupel `platform-arch` (zum Beispiel `darwin-arm64`).\n- **Variante**: x64-CPU-spezifischer Build-Flavor (`modern` AVX2, `baseline` Fallback).\n- **Wrapper**: TS-Funktion/-Klasse, die eine typisierte API über rohe native Exporte bereitstellt.\n- **Declaration Merging**: TS-Technik, die von `types.ts`-Dateien der Module verwendet wird, um `NativeBindings` zu erweitern.\n- **Kompilierter Binärmodus**: Laufzeitmodus, in dem die CLI gebündelt ist und native Addons aus extrahierten/Cache-Pfaden anstatt nur aus paketlokalen Pfaden aufgelöst werden.\n- **Eingebettetes Addon**: Build-Artefakt-Metadaten und Dateireferenzen, die in `embedded-addon.ts` generiert werden, damit kompilierte Binärdateien passende `.node`-Payloads extrahieren können.\n- **Validierungs-Gate**: `validateNative(...)`-Prüfung, die veraltete/nicht übereinstimmende Binärdateien mit fehlenden erforderlichen Exporten ablehnt.\n",
	"de/natives/natives-binding-contract.md": "---\ntitle: Nativer Binding-Vertrag (TypeScript-Seite)\ndescription: >-\n  TypeScript-seitiger Binding-Vertrag für den Aufruf nativer Rust-Funktionen\n  über N-API.\nsidebar:\n  order: 2\n  label: Binding-Vertrag\ni18n:\n  sourceHash: 36dc5fed1f0a\n  translator: machine\n---\n\n# Nativer Binding-Vertrag (TypeScript-Seite)\n\nDieses Dokument definiert den TypeScript-seitigen Vertrag, der zwischen den Aufrufern von `@f5-sales-demo/pi-natives` und dem geladenen N-API-Addon steht.\n\nEs konzentriert sich auf drei Aspekte:\n\n1. Vertragsform (`NativeBindings` + Modul-Augmentierung),\n2. Wrapper-Verhalten (`src/<module>/index.ts`),\n3. Öffentliche Exportoberfläche (`src/index.ts`).\n\n## Implementierungsdateien\n\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/system-info/types.ts`\n- `packages/natives/src/system-info/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/work/types.ts`\n- `packages/natives/src/work/index.ts`\n\n## Vertragsmodell\n\n`packages/natives/src/bindings.ts` definiert den Basisvertrag:\n\n- `NativeBindings` (Basisinterface, enthält derzeit `cancelWork(id: number): void`)\n- `Cancellable` (`timeoutMs?: number`, `signal?: AbortSignal`)\n- `TsFunc<T>` Callback-Signatur, die von N-API-threadsicheren Callbacks verwendet wird\n\nJedes Modul fügt seine eigenen Felder durch Deklarationszusammenführung hinzu:\n\n```ts\n// packages/natives/src/<module>/types.ts\ndeclare module \"../bindings\" {\n interface NativeBindings {\n  grep(options: GrepOptions, onMatch?: TsFunc<GrepMatch>): Promise<GrepResult>;\n }\n}\n```\n\nDies ergibt ein aggregiertes Binding-Interface ohne eine monolithische zentrale Typdatei.\n\n## Lebenszyklus der Deklarationszusammenführung und Zustandsübergänge\n\n### 1) Typzusammenstellung zur Kompilierzeit\n\n- `bindings.ts` stellt das Basis-Symbol `NativeBindings` bereit.\n- Jede `src/<module>/types.ts` augmentiert `NativeBindings`.\n- `src/native.ts` importiert alle `./<module>/types`-Dateien für Seiteneffekte, damit der zusammengeführte Vertrag im Geltungsbereich ist, wo `NativeBindings` verwendet wird.\n\nZustandsübergang: **Basisvertrag** → **Zusammengeführter Vertrag**.\n\n### 2) Laufzeit-Addon-Laden und Validierungsschranke\n\n- `src/native.ts` lädt Kandidaten-`.node`-Binärdateien.\n- Das geladene Objekt wird als `NativeBindings` behandelt und sofort durch `validateNative(...)` geleitet.\n- `validateNative` überprüft erforderliche Exportschlüssel mittels `typeof bindings[name] === \"function\"`.\n\nZustandsübergang: **Nicht vertrauenswürdiges Addon-Objekt** → **Validiertes natives Binding-Objekt** (oder harter Fehler).\n\n### 3) Wrapper-Aufruf\n\n- Modul-Wrapper in `src/<module>/index.ts` rufen `native.<export>` auf.\n- Wrapper passen Standardwerte und Callback-Signaturen an (`(err, value)` zu Nur-Wert-Callback-Mustern in JS-APIs).\n- `src/index.ts` re-exportiert Modul-Wrapper/Typen als öffentliche Paket-API.\n\nZustandsübergang: **Validierte Roh-Bindings** → **Ergonomische öffentliche API**.\n\n## Verantwortlichkeiten der Wrapper\n\nWrapper sind bewusst dünn gehalten; sie reimplementieren keine native Logik.\n\nHauptverantwortlichkeiten:\n\n- **Argument-Normalisierung/Standardwerte**\n  - `glob()` löst `options.path` zu einem absoluten Pfad auf und setzt Standardwerte für `hidden`, `gitignore`, `recursive`.\n  - `hasMatch()` füllt Standard-Flags (`ignoreCase`, `multiline`) vor dem nativen Aufruf.\n- **Callback-Anpassung**\n  - `grep()`, `glob()`, `executeShell()` konvertieren `TsFunc<T>` (`error, value`) in einen Benutzer-Callback, der nur erfolgreiche Werte empfängt.\n- **Umgebungs- oder Richtlinienverhalten um native Aufrufe**\n  - Der Clipboard-Wrapper fügt OSC52/Termux/Headless-Behandlung hinzu und behandelt das Kopieren als Best-Effort.\n- **Öffentliche Benennung und Re-Export-Kuratierung**\n  - `searchContent()` wird auf den nativen Export `search` abgebildet.\n\n## Organisation der öffentlichen Exportoberfläche\n\n`packages/natives/src/index.ts` ist das kanonische öffentliche Barrel. Es gruppiert Exporte nach Fähigkeitsdomäne:\n\n- Suche/Text: `grep`, `glob`, `text`, `highlight`\n- Ausführung/Prozess/Terminal: `shell`, `pty`, `ps`, `keys`\n- System/Medien/Konvertierung: `image`, `html`, `clipboard`, `system-info`, `work`\n\nRegel für Maintainer: Wenn ein Wrapper nicht aus `src/index.ts` re-exportiert wird, gehört er nicht zur beabsichtigten öffentlichen Paketoberfläche.\n\n## JS-API ↔ Native-Export-Zuordnung (repräsentativ)\n\nDie Rust-Seite verwendet N-API-Exportnamen (typischerweise durch `#[napi]` snake_case → camelCase-Konvertierung, gelegentlich mit expliziten Aliasen), die mit diesen Binding-Schlüsseln übereinstimmen müssen.\n\n| Kategorie | Öffentliche JS-API (Wrapper) | Nativer Binding-Schlüssel | Rückgabetyp | Async? |\n|---|---|---|---|---|\n| Grep | `grep(options, onMatch?)` | `grep` | `Promise<GrepResult>` | Ja |\n| Grep | `searchContent(content, options)` | `search` | `SearchResult` | Nein |\n| Grep | `hasMatch(content, pattern, opts?)` | `hasMatch` | `boolean` | Nein |\n| Grep | `fuzzyFind(options)` | `fuzzyFind` | `Promise<FuzzyFindResult>` | Ja |\n| Glob | `glob(options, onMatch?)` | `glob` | `Promise<GlobResult>` | Ja |\n| Glob | `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `void` | Nein |\n| Shell | `executeShell(options, onChunk?)` | `executeShell` | `Promise<ShellExecuteResult>` | Ja |\n| Shell | `Shell` | `Shell` | Klassenkonstruktor | N/A |\n| PTY | `PtySession` | `PtySession` | Klassenkonstruktor | N/A |\n| Text | `truncateToWidth(...)` | `truncateToWidth` | `string` | Nein |\n| Text | `sliceWithWidth(...)` | `sliceWithWidth` | `SliceWithWidthResult` | Nein |\n| Text | `visibleWidth(text)` | `visibleWidth` | `number` | Nein |\n| Highlight | `highlightCode(code, lang, colors)` | `highlightCode` | `string` | Nein |\n| HTML | `htmlToMarkdown(html, options?)` | `htmlToMarkdown` | `Promise<string>` | Ja |\n| System | `getSystemInfo()` | `getSystemInfo` | `SystemInfo` | Nein |\n| Work | `getWorkProfile(lastSeconds)` | `getWorkProfile` | `WorkProfile` | Nein |\n| Prozess | `killTree(pid, signal)` | `killTree` | `number` | Nein |\n| Prozess | `listDescendants(pid)` | `listDescendants` | `number[]` | Nein |\n| Clipboard | `copyToClipboard(text)` | `copyToClipboard` | `Promise<void>` (Best-Effort-Wrapper-Verhalten) | Ja |\n| Clipboard | `readImageFromClipboard()` | `readImageFromClipboard` | `Promise<ClipboardImage \\| null>` | Ja |\n| Keys | `parseKey(data, kittyProtocolActive)` | `parseKey` | `string \\| null` | Nein |\n\n## Unterschiede zwischen synchronem und asynchronem Vertrag\n\nDer Vertrag mischt synchrone und asynchrone APIs; Wrapper bewahren den nativen Aufrufstil, anstatt ein einzelnes Modell zu erzwingen:\n\n- **Promise-basierte asynchrone Exporte** für I/O oder langlebige Arbeit (`grep`, `glob`, `htmlToMarkdown`, `executeShell`, Clipboard, Bildoperationen).\n- **Synchrone Exporte** für deterministische In-Memory-Transformationen/Parser (`search`, `hasMatch`, Highlighting, Textbreite/-Slicing, Schlüssel-Parsing, Prozessabfragen).\n- **Konstruktor-Exporte** für zustandsbehaftete Laufzeitobjekte (`Shell`, `PtySession`, `PhotonImage`).\n\nImplikation für Maintainer: Das Ändern von synchron ↔ asynchron für einen bestehenden Export ist eine breaking API- und Vertragsänderung über Wrapper und Aufrufer hinweg.\n\n## Objekt- und Enum-Typisierungsmuster\n\n### Objektmuster (JS-Objekte im `#[napi(object)]`-Stil)\n\nTS modelliert objektförmige native Werte als Interfaces, zum Beispiel:\n\n- `GrepResult`, `SearchResult`, `GlobResult`\n- `SystemInfo`, `WorkProfile`\n- `ClipboardImage`, `ParsedKittyResult`\n\nDies sind strukturelle Verträge zur Kompilierzeit; die Korrektheit der Laufzeitform liegt in der Verantwortung der nativen Implementierung.\n\n### Enum-Muster\n\nNumerische native Enums werden als `const enum`-Werte in TS dargestellt:\n\n- `FileType` (`1=file`, `2=dir`, `3=symlink`)\n- `ImageFormat` (`0=PNG`, `1=JPEG`, `2=WEBP`, `3=GIF`)\n- `SamplingFilter`, `Ellipsis`, `KeyEventType`\n\nAufrufer sehen benannte Enum-Mitglieder; an der Binding-Grenze werden Zahlen übergeben.\n\n## Wie Abweichungen erkannt werden\n\nDie Erkennung von Abweichungen erfolgt auf zwei Ebenen:\n\n1. **Vertragsprüfungen durch TypeScript zur Kompilierzeit**\n   - Wrapper rufen `native.<name>` gegen das zusammengeführte `NativeBindings` auf.\n   - Fehlende/umbenannte Binding-Schlüssel brechen die TS-Typprüfung in Wrappern.\n\n2. **Laufzeitvalidierung in `validateNative`**\n   - Nach dem Laden prüft `native.ts` die erforderlichen Exporte und wirft einen Fehler, wenn welche fehlen.\n   - Die Fehlermeldung enthält die fehlenden Schlüssel und eine Rebuild-Anweisung.\n\nDies fängt den häufigen Fall veralteter Binärdateien ab: Wrapper/Typ existiert, aber das geladene `.node` besitzt den Export nicht.\n\n## Fehlerverhalten und Einschränkungen\n\n### Lade-/Validierungsfehler (harte Fehler)\n\n- Addon-Ladefehler oder nicht unterstützte Plattform lösen während der Modulinitialisierung in `native.ts` eine Exception aus.\n- Fehlende erforderliche Exporte lösen eine Exception aus, bevor Wrapper nutzbar sind.\n\nEffekt: Das Paket schlägt sofort fehl, anstatt den Fehler bis zum ersten Aufruf aufzuschieben.\n\n### Verhaltensunterschiede auf Wrapper-Ebene\n\n- Einige Wrapper mildern Fehler bewusst ab (`copyToClipboard` arbeitet nach dem Best-Effort-Prinzip und schluckt native Fehler).\n- Streaming-Callbacks ignorieren Callback-Fehler-Payloads und leiten nur erfolgreiche Wert-Events weiter.\n\n### Einschränkungen auf Typebene (Laufzeit strenger als TS)\n\n- Optionale TS-Felder garantieren keine semantische Gültigkeit; die native Schicht kann trotzdem fehlerhafte Werte ablehnen.\n- `const enum`-Typisierung verhindert nicht, dass zur Laufzeit numerische Werte außerhalb des gültigen Bereichs von untypisierten Aufrufern übergeben werden.\n- `validateNative` prüft nur die Anwesenheit und den Funktionscharakter der erforderlichen Exporte, nicht die tiefe Argument-/Rückgabe-Form-Kompatibilität.\n- `bindings.ts` enthält `cancelWork(id)` im Basisinterface, aber die aktuelle Laufzeit-Validierungsliste erzwingt diesen Schlüssel nicht.\n\n## Maintainer-Checkliste für Binding-Änderungen\n\nBeim Hinzufügen/Ändern eines Exports sind alle folgenden Stellen zu aktualisieren:\n\n1. `src/<module>/types.ts` (Augmentierung + Vertragstypen)\n2. `src/<module>/index.ts` (Wrapper-Verhalten)\n3. `src/native.ts`-Importe für die Modultypen (bei neuem Modul)\n4. `validateNative` Prüfungen der erforderlichen Exporte\n5. `src/index.ts` öffentliche Re-Exporte\n\nDas Auslassen eines Schrittes erzeugt entweder Kompilierzeit-Abweichungen oder Laufzeit-Ladefehler.\n",
	"de/natives/natives-build-release-debugging.md": "---\ntitle: 'Natives Build-, Release- und Debugging-Runbook'\ndescription: >-\n  Build-, Release- und Debugging-Runbook für das Rust Native Addon über alle\n  Plattformen hinweg.\nsidebar:\n  order: 8\n  label: 'Build, Release & Debugging'\ni18n:\n  sourceHash: efe47aa5b466\n  translator: machine\n---\n\n# Natives Build-, Release- und Debugging-Runbook\n\nDieses Runbook beschreibt, wie die `@f5-sales-demo/pi-natives`-Build-Pipeline `.node`-Addons erzeugt, wie kompilierte Distributionen diese laden und wie Loader-/Build-Fehler debuggt werden können.\n\nEs folgt den Architekturbegriffen aus `docs/natives-architecture.md`:\n\n- **Build-Zeit-Artefakterzeugung** (`scripts/build-native.ts`)\n- **Eingebettete Addon-Manifest-Generierung** (`scripts/embed-native.ts`)\n- **Laufzeit-Addon-Laden + Validierungs-Gate** (`src/native.ts`)\n\n## Implementierungsdateien\n\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `packages/natives/src/native.ts`\n- `crates/pi-natives/Cargo.toml`\n\n## Build-Pipeline-Übersicht\n\n### 1) Build-Einstiegspunkte\n\n`packages/natives/package.json`-Skripte:\n\n- `bun scripts/build-native.ts` (`build`) → Release-Build\n- `bun scripts/build-native.ts --dev` (`dev:native`) → Debug-/Dev-Profil-Build (gleiche Ausgabebezeichnung)\n- `bun scripts/embed-native.ts` (`embed:native`) → generiert `src/embedded-addon.ts` aus gebauten Dateien\n\n### 2) Rust-Artefakt-Build\n\n`build-native.ts` führt Cargo in `crates/pi-natives` aus:\n\n- Basisbefehl: `cargo build`\n- Release-Modus fügt `--release` hinzu, sofern nicht `--dev` übergeben wird\n- Cross-Target fügt `--target <CROSS_TARGET>` hinzu\n\n`crates/pi-natives/Cargo.toml` deklariert `crate-type = [\"cdylib\"]`, sodass Cargo eine Shared Library (`.so`/`.dylib`/`.dll`) erzeugt, die dann in einen `.node`-Addon-Dateinamen kopiert/umbenannt wird.\n\n### 3) Artefakterkennung und Installation\n\nNach Abschluss von Cargo durchsucht `build-native.ts` Kandidaten-Ausgabeverzeichnisse in dieser Reihenfolge:\n\n1. `${CARGO_TARGET_DIR}` (falls gesetzt)\n2. `<repo>/target`\n3. `crates/pi-natives/target`\n\nFür jedes Stammverzeichnis werden Profilverzeichnisse geprüft:\n\n- Cross-Build: `<root>/<crossTarget>/<profile>` dann `<root>/<profile>`\n- Nativer Build: `<root>/<profile>`\n\nDann wird nach einer der folgenden Dateien gesucht:\n\n- `libpi_natives.so`\n- `libpi_natives.dylib`\n- `pi_natives.dll`\n- `libpi_natives.dll`\n\nBei Fund wird atomar nach `packages/natives/native/` installiert, mit Temp-Datei + Umbenennung-Semantik (Windows-Fallback behandelt fehlgeschlagene Ersetzungen gesperrter DLLs explizit).\n\n## Target-/Variantenmodell und Namenskonventionen\n\n## Plattform-Tag\n\nSowohl Build als auch Laufzeit verwenden den Plattform-Tag:\n\n`<platform>-<arch>` (Beispiel: `darwin-arm64`, `linux-x64`)\n\n## Variantenmodell (nur x64)\n\nx64 unterstützt CPU-Varianten:\n\n- `modern` (AVX2-fähiger Pfad)\n- `baseline` (Fallback)\n\nNicht-x64 verwendet ein einzelnes Standard-Artefakt (kein Varianten-Suffix).\n\n### Ausgabedateinamen\n\nRelease-Builds:\n\n- x64: `pi_natives.<platform>-<arch>-modern.node` oder `...-baseline.node`\n- Nicht-x64: `pi_natives.<platform>-<arch>.node`\n\nDev-Build (`--dev`):\n\n- Verwendet Debug-Profil-Flags, behält aber die standardmäßige plattform-getaggte Ausgabebezeichnung bei\n\nLaufzeit-Loader-Kandidatenreihenfolge in `native.ts`:\n\n- Release-Kandidaten\n- Kompilierter Modus stellt extrahierte/Cache-Kandidaten vor paketlokale Dateien\n\n## Umgebungs-Flags und Build-Optionen\n\n## Laufzeit-Flags\n\n- `PI_DEV` (Loader-Verhalten): aktiviert Loader-Diagnosen\n- `PI_NATIVE_VARIANT` (Loader-Verhalten, nur x64): erzwingt `modern`- oder `baseline`-Auswahl zur Laufzeit\n- `PI_COMPILED` (Loader-Verhalten): aktiviert Verhalten für kompilierte-Binärdatei-Kandidaten/Extraktion\n\n## Build-Zeit-Flags/Optionen\n\n- `--dev` (Skript-Argument): baut Debug-Profil\n- `CROSS_TARGET`: wird an Cargo `--target` übergeben\n- `TARGET_PLATFORM`: überschreibt die Plattform-Tag-Benennung der Ausgabe\n- `TARGET_ARCH`: überschreibt die Arch-Benennung der Ausgabe\n- `TARGET_VARIANT` (nur x64): erzwingt `modern` oder `baseline` für Ausgabedateiname und RUSTFLAGS-Richtlinie\n- `CARGO_TARGET_DIR`: zusätzliches Stammverzeichnis bei der Suche nach Cargo-Ausgaben\n- `RUSTFLAGS`:\n  - falls nicht gesetzt und kein Cross-Compiling, setzt das Skript:\n    - modern: `-C target-cpu=x86-64-v3`\n    - baseline: `-C target-cpu=x86-64-v2`\n    - Nicht-x64 / keine Variante: `-C target-cpu=native`\n  - falls bereits gesetzt, überschreibt das Skript nicht\n\n## Build-Zustände/Lebenszyklusübergänge\n\n### Build-Lebenszyklus (`build-native.ts`)\n\n1. **Init**: Argumente/Umgebung parsen (`--dev`, Target-Überschreibungen, Cross-Flags)\n2. **Variantenauflösung**:\n   - Nicht-x64 → keine Variante\n   - x64 + `TARGET_VARIANT` → explizite Variante\n   - x64 Cross-Build ohne `TARGET_VARIANT` → harter Fehler\n   - x64 lokaler Build ohne Überschreibung → Host-AVX2 erkennen\n3. **Kompilieren**: Cargo mit aufgelöstem Profil/Target ausführen\n4. **Artefakt lokalisieren**: Target-Stammverzeichnisse/Profilverzeichnisse/Bibliotheksnamen durchsuchen\n5. **Installieren**: Kopieren + atomares Umbenennen nach `packages/natives/native`\n6. **Abschluss**: Addon bereit für Loader-Kandidaten\n\nFehlerabbrüche treten in jeder Phase mit explizitem Fehlertext auf (ungültige Variante, fehlgeschlagener Cargo-Build, fehlende Ausgabebibliothek, Installations-/Umbenennungsfehler).\n\n### Embed-Lebenszyklus (`embed-native.ts`)\n\n1. **Init**: Plattform-Tag aus `TARGET_PLATFORM`/`TARGET_ARCH` oder Host-Werten berechnen\n2. **Kandidatenmenge**:\n   - x64 erwartet sowohl `modern` als auch `baseline`\n   - Nicht-x64 erwartet eine Standard-Datei\n3. **Verfügbarkeit validieren** in `packages/natives/native`\n4. **Manifest generieren** (`src/embedded-addon.ts`) mit Bun `file`-Imports und Paketversion\n5. **Laufzeitextraktion bereit** für kompilierten Modus\n\n`--reset` umgeht die Validierung und schreibt einen Null-Manifest-Stub (`embeddedAddon = null`).\n\n## Entwicklungsworkflow vs. ausgeliefertes/kompiliertes Verhalten\n\n## Lokaler Entwicklungsworkflow\n\nTypische lokale Schleife:\n\n1. Addon bauen:\n   - Release: `bun --cwd=packages/natives run build`\n   - Debug-Profil: `bun --cwd=packages/natives run dev:native`\n2. `PI_DEV=1` setzen beim Testen von Loader-Diagnosen\n3. Loader in `native.ts` löst paketlokale `native/`- (und Executable-Verzeichnis-Fallback-)Kandidaten auf\n4. `validateNative` erzwingt Export-Kompatibilität, bevor Wrapper das Binding verwenden\n\n## Ausgelieferter/kompilierter Binärdatei-Workflow\n\nIm kompilierten Modus (`PI_COMPILED` oder Bun-Embedded-Marker):\n\n1. Loader berechnet versioniertes Cache-Verzeichnis: `<getNativesDir()>/<packageVersion>` (operativ `~/.xcsh/natives/<version>`)\n2. Wenn das eingebettete Manifest mit der aktuellen Plattform+Version übereinstimmt, kann der Loader die ausgewählte eingebettete Datei in dieses versionierte Verzeichnis extrahieren\n3. Laufzeit-Kandidatenreihenfolge umfasst:\n   - Versioniertes Cache-Verzeichnis\n   - Legacy-Verzeichnis für kompilierte Binärdateien (`%LOCALAPPDATA%/xcsh` unter Windows, `~/.local/bin` andernorts)\n   - Paket-/Executable-Verzeichnisse\n4. Das erste erfolgreich geladene Addon muss weiterhin `validateNative` bestehen\n\nDeshalb müssen Paketierung und Laufzeit-Loader-Erwartungen übereinstimmen: Dateinamen, Plattform-Tags und exportierte Symbole müssen mit dem übereinstimmen, was `native.ts` prüft und validiert.\n\n## JS-API ↔ Rust-Export-Zuordnung (Validierungs-Gate-Teilmenge)\n\n`native.ts` erfordert, dass diese JS-sichtbaren Exporte auf dem geladenen Addon existieren. Sie werden auf Rust N-API-Exporte in `crates/pi-natives/src` abgebildet:\n\n| JS-Name erforderlich von `validateNative` | Rust-Export-Deklaration | Rust-Quelldatei |\n| --- | --- | --- |\n| `glob` | `#[napi] pub fn glob(...)` | `crates/pi-natives/src/glob.rs` |\n| `grep` | `#[napi] pub fn grep(...)` | `crates/pi-natives/src/grep.rs` |\n| `search` | `#[napi] pub fn search(...)` | `crates/pi-natives/src/grep.rs` |\n| `highlightCode` | `#[napi] pub fn highlight_code(...)` | `crates/pi-natives/src/highlight.rs` |\n| `getSystemInfo` | `#[napi] pub fn get_system_info(...)` | `crates/pi-natives/src/system_info.rs` |\n| `getWorkProfile` | `#[napi] pub fn get_work_profile(...)` (camel-cased Export) | `crates/pi-natives/src/prof.rs` |\n| `invalidateFsScanCache` | `#[napi] pub fn invalidate_fs_scan_cache(...)` | `crates/pi-natives/src/fs_cache.rs` |\n\nWenn ein erforderliches Symbol fehlt, bricht der Loader sofort mit einem Rebuild-Hinweis ab.\n\n## Fehlerverhalten und Diagnosen\n\n## Build-Zeit-Fehler\n\n- Ungültige Variantenkonfiguration:\n  - `TARGET_VARIANT` auf Nicht-x64 gesetzt → sofortiger Fehler\n  - x64 Cross-Build ohne explizites `TARGET_VARIANT` → sofortiger Fehler\n- Cargo-Build-Fehler:\n  - Skript gibt Nicht-Null-Exit und stderr aus\n- Artefakt nicht gefunden:\n  - Skript gibt jedes geprüfte Profilverzeichnis aus\n- Installationsfehler:\n  - explizite Meldung; Windows enthält Hinweis auf gesperrte Datei\n\n## Laufzeit-Loader-Fehler (`native.ts`)\n\n- Nicht unterstützter Plattform-Tag:\n  - wirft Fehler mit Liste unterstützter Plattformen\n- Kein Kandidat konnte geladen werden:\n  - wirft Fehler mit vollständiger Kandidaten-Fehlerliste und modusspezifischen Behebungshinweisen\n- Fehlende Exporte:\n  - wirft Fehler mit exakten fehlenden Symbolnamen und Rebuild-Befehl\n- Probleme bei der eingebetteten Extraktion:\n  - mkdir-/Schreibfehler bei der Extraktion werden aufgezeichnet und in die finale Diagnose aufgenommen\n\n## Fehlerbehebungsmatrix\n\n| Symptom | Wahrscheinliche Ursache | Überprüfen | Behebung |\n| --- | --- | --- | --- |\n| `Native addon missing exports ... Missing: <name>` | Veraltete `.node`-Binärdatei, Rust-Export-Namens-Diskrepanz oder falsche Binärdatei geladen | Mit `PI_DEV=1` ausführen, um den geladenen Pfad zu sehen; Export-Liste für diese Datei prüfen | `build` neu bauen; sicherstellen, dass der Rust-`#[napi]`-Exportname (oder expliziter Alias bei Bedarf) mit dem JS-Schlüssel übereinstimmt; veraltete gecachte/versionierte Dateien entfernen |\n| x64-Maschine lädt baseline, obwohl modern erwartet | `PI_NATIVE_VARIANT=baseline`, kein AVX2 erkannt, oder nur baseline-Datei vorhanden | `PI_NATIVE_VARIANT` prüfen; `native/` auf `-modern`-Datei inspizieren | Modern-Variante bauen (`TARGET_VARIANT=modern ... build`) und sicherstellen, dass die Datei ausgeliefert wird |\n| Cross-Build erzeugt unbrauchbare/falsch beschriftete Binärdatei | Diskrepanz zwischen `CROSS_TARGET` und `TARGET_PLATFORM`/`TARGET_ARCH`, oder fehlendes `TARGET_VARIANT` für x64 | Umgebungs-Tupel und Ausgabedateiname bestätigen | Mit konsistenten Umgebungswerten und explizitem x64-`TARGET_VARIANT` erneut ausführen |\n| Kompilierte Binärdatei schlägt nach Upgrade fehl | Veralteter extrahierter Cache (`~/.xcsh/natives/<alte-oder-nicht-übereinstimmende-version>`) oder Diskrepanz im eingebetteten Manifest | Versioniertes Natives-Verzeichnis und Loader-Fehlerliste inspizieren | Versionierten Natives-Cache für die Paketversion löschen und erneut ausführen; eingebettetes Manifest während der Paketierung neu generieren |\n| Loader prüft viele Pfade und keiner funktioniert | Plattform-Diskrepanz oder fehlendes Release-Artefakt im Paket `native/` | `platformTag` mit tatsächlichem/n Dateinamen vergleichen | Sicherstellen, dass der gebaute Dateiname exakt der `pi_natives.<platform>-<arch>(-variant).node`-Konvention entspricht und das Paket `native/` enthält |\n| `embed:native` schlägt fehl mit \"Incomplete native addons\" | Erforderliche Variantendateien nicht vor dem Einbetten gebaut | Erwartete vs. gefundene Liste im Fehlertext prüfen | Erforderliche Dateien zuerst bauen (x64: sowohl modern+baseline; Nicht-x64: Standard), dann `embed:native` erneut ausführen |\n\n## Operative Befehle\n\n```bash\n# Release-Artefakt für den aktuellen Host\nbun --cwd=packages/natives run build\n\n# Debug-Profil-Artefakt-Build\nbun --cwd=packages/natives run dev:native\n\n# Explizite x64-Varianten bauen\nTARGET_VARIANT=modern bun --cwd=packages/natives run build\nTARGET_VARIANT=baseline bun --cwd=packages/natives run build\n\n# Eingebettetes Addon-Manifest aus gebauten Native-Dateien generieren\nbun --cwd=packages/natives run embed:native\n\n# Eingebettetes Manifest auf Null-Stub zurücksetzen\nbun --cwd=packages/natives run embed:native -- --reset\n```\n",
	"de/natives/natives-media-system-utils.md": "---\ntitle: Native Media- und System-Dienstprogramme\ndescription: >-\n  Native Dienstprogramme zur Medienverarbeitung für Screenshots, Bildbearbeitung\n  und Systeminformationen.\nsidebar:\n  order: 7\n  label: Media & System-Utils\ni18n:\n  sourceHash: 430898c177bc\n  translator: machine\n---\n\n# Native Media- und System-Dienstprogramme\n\nDieses Dokument ist ein detaillierter Einblick in das Subsystem der **System/Media/Konvertierungs-Primitiven**-Schicht, die in [`docs/natives-architecture.md`](./natives-architecture.md) beschrieben wird: `image`, `html`, `clipboard` und `work`-Profiling.\n\n## Implementierungsdateien\n\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/prof.rs`\n- `crates/pi-natives/src/task.rs`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/work/index.ts`\n- `packages/natives/src/work/types.ts`\n\n> Hinweis: Es gibt keine `crates/pi-natives/src/work.rs`; Work-Profiling ist in `prof.rs` implementiert und wird durch Instrumentierung in `task.rs` gespeist.\n\n## TS-API ↔ Rust Export-/Modul-Zuordnung\n\n| TS-Export (packages/natives)                | Rust N-API Export                                                       | Rust-Modul                            |\n| ------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------- |\n| `PhotonImage.parse(bytes)`                  | `PhotonImage::parse`                                                     | `image.rs`                            |\n| `PhotonImage#resize(width, height, filter)` | `PhotonImage::resize`                                                    | `image.rs`                            |\n| `PhotonImage#encode(format, quality)`       | `PhotonImage::encode`                                                    | `image.rs`                            |\n| `htmlToMarkdown(html, options)`             | `html_to_markdown`                                                       | `html.rs`                             |\n| `copyToClipboard(text)`                     | `copy_to_clipboard` + TS-Fallback-Logik                                  | `clipboard.rs` + `clipboard/index.ts` |\n| `readImageFromClipboard()`                  | `read_image_from_clipboard`                                              | `clipboard.rs`                        |\n| `getWorkProfile(lastSeconds)`               | `get_work_profile`                                                      | `prof.rs`                             |\n\n## Datenformat-Grenzen und Konvertierungen\n\n### Bild (`image`)\n\n- **JS-Eingabegrenze**: `Uint8Array` mit codierten Bildbytes.\n- **Rust-Decodierungsgrenze**: Bytes werden in `Vec<u8>` kopiert, das Format wird mit `ImageReader::with_guessed_format()` erkannt und dann zu `DynamicImage` decodiert.\n- **In-Memory-Zustand**: `PhotonImage` speichert `Arc<DynamicImage>`.\n- **Ausgabegrenze**: `encode(format, quality)` gibt `Promise<Uint8Array>` zurück (Rust `Vec<u8>`).\n\nFormat-IDs sind numerisch:\n\n- `0`: PNG\n- `1`: JPEG\n- `2`: WebP (verlustfreier Encoder)\n- `3`: GIF\n\nEinschränkungen:\n\n- `quality` wird nur für JPEG verwendet.\n- PNG/WebP/GIF ignorieren `quality`.\n- Nicht unterstützte Format-IDs führen zu einem Fehler (`Invalid image format: <id>`).\n\n### HTML-Konvertierung (`html`)\n\n- **JS-Eingabegrenze**: HTML `string` + optionales Objekt `{ cleanContent?: boolean; skipImages?: boolean }`.\n- **Rust-Konvertierungsgrenze**: Der `String`-Input wird durch `html_to_markdown_rs::convert` konvertiert.\n- **Ausgabegrenze**: Markdown `string`.\n\nKonvertierungsverhalten:\n\n- `cleanContent` ist standardmäßig `false`.\n- Bei `cleanContent=true` wird Vorverarbeitung mit `PreprocessingPreset::Aggressive` und Hard-Removal-Flags für Navigation/Formulare aktiviert.\n- `skipImages` ist standardmäßig `false`.\n\n### Zwischenablage (`clipboard`)\n\n- **Text-Pfad**:\n  - TS sendet zunächst OSC 52 (`\\x1b]52;c;<base64>\\x07`), wenn stdout ein TTY ist.\n  - Derselbe Text wird anschließend als Best-Effort über die native Zwischenablage-API (`native.copyToClipboard`) versucht.\n  - Unter Termux versucht TS zuerst `termux-clipboard-set`.\n- **Bild-Lesepfad**:\n  - Rust liest das Rohbild aus `arboard`.\n  - Rust re-codiert es zu PNG-Bytes (`image`-Crate) und gibt `{ data: Uint8Array, mimeType: \"image/png\" }` zurück.\n  - TS gibt frühzeitig `null` zurück bei Termux oder Linux-Sitzungen ohne Display-Server (fehlende `DISPLAY`/`WAYLAND_DISPLAY`).\n\n### Work-Profiling (`work`)\n\n- **Erfassungsgrenze**: Profiling-Samples werden durch `profile_region(tag)`-Guards in `task::blocking` und `task::future` erzeugt.\n- **Speicherformat**: Ringpuffer fester Größe (`MAX_SAMPLES = 10_000`), der Stack-Pfad + Dauer (`μs`) + Zeitstempel (`μs seit Prozessstart`) speichert.\n- **Ausgabegrenze**: `getWorkProfile(lastSeconds)` gibt ein Objekt zurück:\n  - `folded`: Folded-Stack-Text (Flamegraph-Eingabe)\n  - `summary`: Markdown-Tabellen-Zusammenfassung\n  - `svg`: optionales Flamegraph-SVG\n  - `totalMs`, `sampleCount`\n\n## Lebenszyklus und Zustandsübergänge\n\n### Bild-Lebenszyklus\n\n1. `PhotonImage.parse(bytes)` plant eine blockierende Decodierungs-Aufgabe (`image.decode`).\n2. Bei Erfolg existiert ein nativer `PhotonImage`-Handle in JS.\n3. `resize(...)` erstellt einen neuen nativen Handle (`image.resize`), alter und neuer Handle können koexistieren.\n4. `encode(...)` materialisiert Bytes (`image.encode`), ohne die Bildabmessungen zu verändern.\n\nFehlerübergänge:\n\n- Fehler bei Formaterkennung/Decodierung lehnt das Parse-Promise ab.\n- Fehler bei der Codierung lehnt das Encode-Promise ab.\n- Ungültige Format-ID lehnt das Encode-Promise ab.\n\n### HTML-Lebenszyklus\n\n1. `htmlToMarkdown(html, options)` plant eine blockierende Konvertierungsaufgabe.\n2. Die Konvertierung läuft mit Standardoptionen (`cleanContent=false`, `skipImages=false`), sofern nicht anders angegeben.\n3. Gibt einen Markdown-String zurück oder lehnt ab.\n\nFehlerübergänge:\n\n- Konvertierungsfehler gibt ein abgelehntes Promise zurück (`Conversion error: ...`).\n\n### Zwischenablage-Lebenszyklus\n\n`copyToClipboard(text)` ist absichtlich Best-Effort und nutzt mehrere Pfade:\n\n1. Bei TTY: Versuch eines OSC-52-Schreibvorgangs (Base64-Payload).\n2. Versuch des Termux-Befehls, wenn `TERMUX_VERSION` gesetzt ist.\n3. Versuch einer nativen `arboard`-Textkopie.\n4. Fehler werden auf TS-Ebene unterdrückt.\n\n`readImageFromClipboard()` unterscheidet sich in der Strenge je nach Phase:\n\n1. TS blockiert nicht unterstützte Laufzeitkontexte (Termux/headless Linux) hart zu `null`.\n2. Rust `arboard`-Lesevorgang wird nur ausgeführt, wenn TS es erlaubt.\n3. `ContentNotAvailable` wird auf `null` abgebildet.\n4. Andere Rust-Fehler führen zur Ablehnung.\n\n### Work-Profiling-Lebenszyklus\n\n1. Kein expliziter Start: Profiling ist immer aktiv, wenn Task-Helfer ausgeführt werden.\n2. Jeder instrumentierte Task-Scope zeichnet beim Drop des Guards ein Sample auf.\n3. Samples überschreiben die ältesten Einträge, nachdem die Pufferkapazität erreicht ist.\n4. `getWorkProfile(lastSeconds)` liest ein Zeitfenster und leitet Folded-/Summary-/SVG-Artefakte ab.\n\nFehlerübergänge:\n\n- SVG-Generierungsfehler ist ein Soft-Fail (`svg: null`), während Folded und Summary weiterhin zurückgegeben werden.\n- Ein leeres Sample-Fenster gibt leere Folded-Daten und `svg: null` zurück, keinen Fehler.\n\n## Nicht unterstützte Operationen und Fehlerweiterleitung\n\n### Bild\n\n- Nicht unterstützte Decodierungs-Eingabe oder beschädigte Bytes: strikter Fehler (Promise-Ablehnung).\n- Nicht unterstützte Encode-Format-ID: strikter Fehler.\n- Kein Best-Effort-Fallback-Pfad im TS-Wrapper.\n\n### HTML\n\n- Konvertierungsfehler sind strikte Fehler (Ablehnung).\n- Fehlende Optionen werden als Best-Effort-Standardwerte behandelt, nicht als Fehler.\n\n### Zwischenablage\n\n- Textkopie ist Best-Effort auf TS-Ebene: Betriebsfehler werden unterdrückt.\n- Bild-Lesevorgang unterscheidet zwischen \"kein Bild\" (`null`) und Betriebsfehler (Ablehnung).\n- Termux/headless Linux werden als nicht unterstützte Kontexte für den Bild-Lesevorgang behandelt (`null`).\n\n### Work-Profiling\n\n- Der Abruf ist strikt für den Funktionsaufruf selbst, aber die Artefakt-Generierung ist teilweise Best-Effort (`svg` nullable).\n- Puffertrunkierung ist erwartetes Verhalten (Ringpuffer), kein Datenverlust-Bug.\n\n## Plattform-Hinweise\n\n- **Zwischenablage-Text**: OSC 52 hängt von der Terminal-Unterstützung ab; nativer Zwischenablagezugriff hängt von der Desktop-Umgebung/Sitzung ab.\n- **Zwischenablage-Bild lesen**: In TS blockiert für Termux und Linux ohne Display-Server.\n",
	"de/natives/natives-rust-task-cancellation.md": "---\ntitle: Native Rust-Aufgabenausführung und Abbruch\ndescription: >-\n  Rust async Aufgabenausführungsmodell mit kooperativem Abbruch und\n  Bereinigungssemantik.\nsidebar:\n  order: 5\n  label: Aufgabenabbruch\ni18n:\n  sourceHash: 0fbf45c6d463\n  translator: machine\n---\n\n# Native Rust-Aufgabenausführung und Abbruch (`pi-natives`)\n\nDieses Dokument beschreibt, wie `crates/pi-natives` native Arbeit plant und wie Abbrüche von JS-Optionen (`timeoutMs`, `AbortSignal`) zur Rust-Ausführung fließen.\n\n## Implementierungsdateien\n\n- `crates/pi-natives/src/task.rs`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/fd.rs`\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/ps.rs`\n\n## Kernprimitive (`task.rs`)\n\n`task.rs` definiert drei Kernbestandteile:\n\n1. `task::blocking(tag, cancel_token, work)`\n   - Kapselt `napi::AsyncTask` / `Task`.\n   - `compute()` wird auf libuv-Worker-Threads ausgeführt (für CPU-intensive oder blockierende/synchrone Systemaufrufe).\n   - Gibt ein JS `Promise<T>` zurück.\n\n2. `task::future(env, tag, work)`\n   - Kapselt `env.spawn_future(...)`.\n   - Führt asynchrone Arbeit auf der Tokio-Laufzeitumgebung aus.\n   - Gibt `PromiseRaw<'env, T>` zurück.\n\n3. `CancelToken` / `AbortToken` / `AbortReason`\n   - `CancelToken::new(timeout_ms, signal)` kombiniert Deadline + optionales `AbortSignal`.\n   - `CancelToken::heartbeat()` ist kooperativer Abbruch für blockierende Schleifen.\n   - `CancelToken::wait()` ist asynchrones Abbruch-Warten (`Signal` / `Timeout` / `User` Ctrl-C).\n   - `AbortToken` ermöglicht es externem Code, einen Abbruch anzufordern (`abort(reason)`).\n\n## `blocking` vs. `future`: Ausführungsmodell und Auswahl\n\n### `task::blocking` verwenden\n\nVerwenden, wenn die Arbeit CPU-intensiv oder grundlegend synchron/blockierend ist:\n\n- Regex-/Datei-Scanning (`grep`, `glob`, `fuzzy_find`)\n- Synchrone PTY-Schleifeninterna (`run_pty_sync` über `spawn_blocking`)\n- Zwischenablage-/Bild-/HTML-Konvertierungen\n\nVerhalten:\n\n- Der Arbeits-Closure erhält ein geklontes `CancelToken`.\n- Abbrüche werden nur dort beobachtet, wo Code `ct.heartbeat()?` prüft.\n- Closure `Err(...)` lehnt das JS-Promise ab.\n\n### `task::future` verwenden\n\nVerwenden, wenn die Arbeit asynchrone Operationen `await`en muss:\n\n- Shell-Session-Orchestrierung (`shell.run`, `executeShell`)\n- Aufgaben-Racing (`tokio::select!`) zwischen Abschluss und Abbruch\n\nVerhalten:\n\n- Ein Future kann den normalen Abschluss gegen `ct.wait()` abwägen.\n- Beim Abbruchpfad propagieren asynchrone Implementierungen den Abbruch typischerweise an innere Subsysteme (z. B. `tokio_util::CancellationToken`) und erzwingen optional einen Abbruch nach einem Kulanz-Timeout.\n\n## JS-API ↔ Rust-Export-Zuordnung (aufgaben-/abbruchrelevant)\n\n| JS-seitige API | Rust-Export (`#[napi]`) | Planer | Abbruch-Anbindung |\n|---|---|---|---|\n| `grep(options, onMatch?)` | `grep` | `task::blocking(\"grep\", ct, ...)` | `CancelToken::new(options.timeoutMs, options.signal)` + `ct.heartbeat()` |\n| `glob(options, onMatch?)` | `glob` | `task::blocking(\"glob\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` in Filterschleife |\n| `fuzzyFind(options)` | `fuzzy_find` | `task::blocking(\"fuzzy_find\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` in Bewertungsschleife |\n| `shell.run(options, onChunk?)` | `Shell::run` | `task::future(env, \"shell.run\", ...)` | `ct.wait()` gegen Laufaufgabe abgewogen; überbrückt zu Tokio `CancellationToken` |\n| `executeShell(options, onChunk?)` | `execute_shell` | `task::future(env, \"shell.execute\", ...)` | wie oben |\n| `pty.start(options, onChunk?)` | `PtySession::start` | `task::future(env, \"pty.start\", ...)` + inneres `spawn_blocking` | `CancelToken` in synchroner PTY-Schleife über `heartbeat()` geprüft |\n| `htmlToMarkdown(html, options?)` | `html_to_markdown` | `task::blocking(\"html_to_markdown\", (), ...)` | keine (`()` Token) |\n| `PhotonImage.parse/encode/resize` | `PhotonImage::{parse,encode,resize}` | `task::blocking(...)` | keine (`()` Token) |\n| `copyToClipboard/readImageFromClipboard` | `copy_to_clipboard` / `read_image_from_clipboard` | `task::blocking(...)` | keine (`()` Token) |\n\n`text.rs` und `ps.rs` verwenden derzeit weder `task::blocking` noch `task::future` und nehmen daher nicht an diesem Abbruchpfad teil.\n\n## Abbruch-Lebenszyklus und Zustandsübergänge\n\n### `CancelToken`-Lebenszyklus\n\n`CancelToken` ist kooperativ und zustandsbehaftet:\n\n```text\nErstellt\n  ├─ kein Signal + kein Timeout  -> passives Token (bricht nie ab, außer extern gesetzt)\n  ├─ Signal registriert           -> wartet auf AbortSignal-Callback\n  └─ Deadline gesetzt             -> Timeout-Prüfung wird aktiv\n\nLaufend\n  ├─ heartbeat()/wait() sieht Signal   -> AbortReason::Signal\n  ├─ heartbeat()/wait() sieht Deadline -> AbortReason::Timeout\n  ├─ wait() sieht Ctrl-C               -> AbortReason::User\n  └─ kein Abbruch                      -> fortsetzen\n\nAbgebrochen (terminal)\n  └─ erster Abbruchgrund gewinnt (atomares Flag + Benachrichtiger)\n```\n\n### Abbruch vor dem Start vs. während der Ausführung\n\n- **Vor dem Start / vor der ersten Abbruchprüfung**:\n  - `task::future`-Nutzer, die auf `ct.wait()` abwägen, können den Abbruch sofort auflösen, sobald sie `select!` betreten.\n  - `task::blocking`-Nutzer beobachten den Abbruch nur, wenn der Closure-Code `heartbeat()` erreicht. Wenn der Closure nicht frühzeitig einen Heartbeat sendet, verzögert sich der Abbruch.\n\n- **Während der Ausführung**:\n  - `blocking`: das nächste `heartbeat()` gibt `Err(\"Aborted: ...\")` zurück.\n  - `future`: der `ct.wait()`-Zweig gewinnt `select!`, dann bricht Code untergeordnete asynchrone Mechanismen ab (bei Shell: Tokio-Token abbrechen, bis zu 2 s warten, dann Aufgabe zwangsweise beenden).\n\n## Heartbeat-Anforderungen für langlaufende Schleifen\n\n`heartbeat()` muss in Schleifen mit unbegrenzten oder großen Arbeitsmengen in vorhersehbarer Kadenz ausgeführt werden.\n\nBeobachtete Muster:\n\n- `glob::filter_entries`: jeden Eintrag vor dem Filtern/Abgleichen prüfen.\n- `fd::score_entries`: jeden gescannten Kandidaten prüfen.\n- `grep_sync`: explizite Abbruchprüfung vor der intensiven Suchphase, plus fs-Cache-Aufrufe, die ebenfalls das Token erhalten.\n- `run_pty_sync`: jeden Schleifentakt prüfen (~16 ms Sleep-Kadenz) und Kind-Prozess bei Abbruch beenden.\n\nPraktische Regel: Keine Schleife über extern große Eingaben sollte ein kurzes begrenztes Intervall ohne Heartbeat überschreiten.\n\n## Fehlerverhalten und Fehlerpropagation zu JS\n\n### Blockierende Aufgaben\n\nFehlerpfad:\n\n1. Closure gibt `Err(napi::Error)` zurück (einschließlich `heartbeat()`-Abbruch).\n2. `Task::compute()` gibt `Err` zurück.\n3. `AsyncTask` lehnt JS-Promise ab.\n\nTypische Fehlerzeichenketten:\n\n- `Aborted: Timeout`\n- `Aborted: Signal`\n- Domänenfehler (`Failed to decode image: ...`, `Conversion error: ...`, usw.)\n\n### Asynchrone Aufgaben\n\nFehlerpfad:\n\n1. Asynchroner Body gibt `Err(napi::Error)` zurück oder Join-Fehler wird zugeordnet (`... task failed: {err}`).\n2. `task::future`-erstelltes Promise wird abgelehnt.\n3. Einige APIs geben absichtlich strukturierte Abbruchergebnisse statt einer Ablehnung zurück (`ShellRunResult`/`ShellExecuteResult` mit `cancelled`/`timed_out`-Flags und `exit_code: None`).\n\n### Aufteilung der Abbruchmeldung\n\n- **Abbruch als Fehler**: die meisten blockierenden Exporte verwenden `heartbeat()?`.\n- **Abbruch als typisiertes Ergebnis**: Shell/PTY-Befehls-APIs, die Abbrüche in Ergebnisstrukturen modellieren.\n\nWählen Sie ein Modell pro API und dokumentieren Sie es explizit.\n\n## Häufige Fallstricke\n\n1. **Fehlender Heartbeat in blockierenden Schleifen**\n   - Symptom: Timeout/Signal erscheint ignoriert, bis die Schleife endet.\n   - Behebung: `ct.heartbeat()?` am Schleifenanfang und vor kostspieligen Schritten pro Element hinzufügen.\n\n2. **Lange nicht abbrechbare Abschnitte**\n   - Symptom: Abbruchlatenz steigt bei einzelnen großen Aufrufen (Dekodierung, Sortierung, Komprimierung usw.).\n   - Behebung: Arbeit in Stücke mit Heartbeat-Grenzen aufteilen; falls nicht möglich, Latenz dokumentieren.\n\n3. **Blockierung des asynchronen Executors**\n   - Symptom: Asynchrone API blockiert, wenn synchron-intensiver Code direkt in einem Future ausgeführt wird.\n   - Behebung: CPU-/Sync-Blöcke in `task::blocking` oder `tokio::task::spawn_blocking` verschieben.\n\n4. **Inkonsistente Abbruchsemantik**\n   - Symptom: Eine API lehnt bei Abbruch ab, eine andere löst mit Flags auf und verwirrt Aufrufer.\n   - Behebung: Pro Domäne standardisieren und Wrapper-Dokumentation aktuell halten.\n\n5. **Vergessene Abbruchbrücke in verschachtelten asynchronen Aufgaben**\n   - Symptom: Äußeres Token wird abgebrochen, aber innere Leser-/Teilprozessaufgaben laufen weiter.\n   - Behebung: Abbruch zum inneren Token/Signal überbrücken und Kulanz-Timeout + erzwungenen Abbruch als Fallback durchsetzen.\n\n## Checkliste für neue abbrechbare Exporte\n\n1. Arbeit korrekt klassifizieren:\n   - CPU-intensiv oder synchron blockierend -> `task::blocking`\n   - Asynchrone E/A / `await`-Orchestrierung -> `task::future`\n\n2. Abbrucheingaben bei Bedarf verfügbar machen:\n   - `timeoutMs` und `signal` in `#[napi(object)]`-Optionen einschließen\n   - `let ct = task::CancelToken::new(timeout_ms, signal);` erstellen\n\n3. Abbruch durch alle Schichten verdrahten:\n   - Blockierende Schleifen: `ct.heartbeat()?` in stabilen Intervallen\n   - Asynchrone Orchestrierung: gegen `ct.wait()` abwägen und Unteraufgaben/Token abbrechen\n\n4. Abbruchvertrag festlegen:\n   - Promise mit Abbruchfehler ablehnen, oder\n   - typisiertes `{ cancelled, timedOut, ... }` auflösen\n   - diesen Vertrag für die API-Familie konsistent halten\n\n5. Fehler mit Kontext propagieren:\n   - Fehler über `Error::from_reason(format!(\"...: {err}\"))` zuordnen\n   - phasenspezifische Präfixe einschließen (`spawn`, `decode`, `wait`, usw.)\n\n6. Abbruch vor dem Start und während der Ausführung behandeln:\n   - Abbruchprüfung/-warten muss vor dem kostspieligen Body und während langer Ausführung erfolgen\n\n7. Sicherstellen, dass kein Executor-Missbrauch vorliegt:\n   - Keine langen synchronen Arbeiten direkt in asynchronen Futures ohne `spawn_blocking`/blockierende Aufgaben-Kapselung\n",
	"de/natives/natives-shell-pty-process.md": "---\ntitle: 'Natives Shell-, PTY-, Prozess- und Tastatur-Interna'\ndescription: >-\n  Shell-Ausführung, PTY-Verwaltung, Prozesslebenszyklus und\n  Tastaturereignisverarbeitung in der nativen Schicht.\nsidebar:\n  order: 4\n  label: 'Shell, PTY & Prozess'\ni18n:\n  sourceHash: 00ea95614c6a\n  translator: machine\n---\n\n# Natives Shell-, PTY-, Prozess- und Tastatur-Interna\n\nDieses Dokument behandelt die **Ausführungs-/Prozess-/Terminal-Primitive** in `@f5-sales-demo/pi-natives`: `shell`, `pty`, `ps` und `keys`, unter Verwendung der Architekturbegriffe aus `docs/natives-architecture.md`.\n\n## Implementierungsdateien\n\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/shell/windows.rs` (nur Windows)\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/ps.rs`\n- `crates/pi-natives/src/keys.rs`\n- `crates/pi-natives/src/task.rs` (gemeinsames Abbruchverhalten, verwendet von shell/pty)\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/bindings.ts`\n\n## Schichtzuständigkeit\n\n- **TS-Wrapper-/API-Schicht** (`packages/natives/src/*`): typisierte Einstiegspunkte, Abbruchoberfläche (`timeoutMs`, `AbortSignal`) und JS-Ergonomie.\n- **Rust-N-API-Modulschicht** (`crates/pi-natives/src/*`): Shell-/PTY-Prozessausführung, Prozessbaum-Traversierung/-Beendigung und Tastensequenz-Analyse.\n- **Validierungs-Gate** (`native.ts`, Architekturebene): stellt sicher, dass erforderliche Exporte (`Shell`, `executeShell`, `PtySession`, `killTree`, `listDescendants`, Schlüsselhilfsfunktionen) vorhanden sind, bevor Wrapper verwendet werden.\n\n## Shell-Subsystem (`shell`)\n\n### API-Modell\n\nZwei Ausführungsmodi werden bereitgestellt:\n\n1. **Einmalig** über `executeShell(options, onChunk?)`.\n2. **Persistente Sitzung** über `new Shell(options?)`, dann wiederholtes `shell.run(...)`.\n\nBeide streamen die Ausgabe über einen threadsicheren Callback und geben `{ exitCode?, cancelled, timedOut }` zurück.\n\n### Sitzungserstellung und Umgebungsmodell\n\nRust erstellt `brush_core::Shell` mit:\n\n- nicht-interaktivem Modus,\n- `do_not_inherit_env: true`,\n- expliziter Umgebungsrekonstruktion aus der Host-Umgebung,\n- Ausschlussliste für shell-sensitive Variablen (`PS1`, `PWD`, `SHLVL`, Bash-Funktionsexporte usw.).\n\nVerhalten der Sitzungsumgebung:\n\n- `ShellOptions.sessionEnv` wird einmalig bei der Sitzungserstellung angewendet.\n- `ShellRunOptions.env` ist befehlsbezogen (`EnvironmentScope::Command`) und wird nach jedem Lauf zurückgesetzt.\n- `PATH` wird unter Windows speziell mit Groß-/Kleinschreibungs-unempfindlicher Deduplizierung zusammengeführt.\n\nNur-Windows-Pfaderweiterung (`shell/windows.rs`): Erkannte Git-for-Windows-Pfade (`cmd`, `bin`, `usr/bin`) werden angehängt, sofern vorhanden und noch nicht enthalten.\n\n### Laufzeit-Lebenszyklus und Zustandsübergänge\n\nDie persistente Shell (`Shell.run`) verwendet diese Zustandsmaschine:\n\n- **Inaktiv/Nicht initialisiert**: `session: None`.\n- **Laufend**: das erste `run()` erstellt die Sitzung verzögert, speichert das `current_abort`-Token und führt den Befehl aus.\n- **Abgeschlossen + Keepalive**: wenn der Ausführungssteuerungsfluss `Normal` ist, wird `current_abort` gelöscht und die Sitzung wiederverwendet.\n- **Abgeschlossen + Abbau**: wenn der Steuerungsfluss schleifen-/skript-/shell-exit-bezogen ist (`BreakLoop`, `ContinueLoop`, `ReturnFromFunctionOrScript`, `ExitShell`), wird die Sitzung verworfen (`session: None`).\n- **Abgebrochen/Zeitüberschreitung**: Der Lauf-Task wird abgebrochen, Wartezeit (2 s), dann erzwungener Abbruch; Sitzung wird verworfen.\n- **Fehler**: Sitzung wird verworfen.\n\nDie einmalige Shell (`executeShell`) erstellt und verwirft bei jedem Aufruf immer eine neue Sitzung.\n\n### Streaming-/Ausgabeverhalten\n\n- Stdout/Stderr werden in eine gemeinsame Pipe geleitet und gleichzeitig gelesen.\n- Der Leseprozess dekodiert UTF-8 inkrementell; ungültige Bytesequenzen erzeugen `U+FFFD`-Ersatz-Chunks.\n- Nach Abschluss des Prozesses hat der Ausgabe-Drain Leerlauf-/Maximumsbeschränkungen (`250 ms` Leerlauf, `2 s` Maximum), um ein Hängenbleiben bei Hintergrundjobs zu vermeiden, die Dateideskriptoren offenhalten.\n\n### Abbruch, Zeitüberschreitung und Hintergrundjobs\n\n- `CancelToken` wird aus `timeoutMs` und einem optionalen `AbortSignal` konstruiert.\n- Bei Abbruch/Zeitüberschreitung wird der Shell-Abbruch-Token ausgelöst, anschließend erhält der Task ein 2-s-Zeitfenster für einen geordneten Abbruch, bevor ein erzwungener Abbruch erfolgt.\n- Bei einem Abbruch werden Hintergrundjobs beendet (`TERM`, dann verzögertes `KILL`) unter Verwendung von Brush-Job-Metadaten.\n\nVerhalten von `Shell.abort()`:\n\n- bricht nur den aktuell laufenden Befehl für diese `Shell`-Instanz ab,\n- ist eine erfolgreiche Nulloperation, wenn nichts ausgeführt wird.\n\n### Fehlerverhalten\n\nHäufig auftretende Fehler umfassen:\n\n- Sitzungsinitialisierungsfehler (`Failed to initialize shell`),\n- Arbeitsverzeichnisfehler (`Failed to set cwd`),\n- Fehler beim Setzen/Zurücksetzen der Umgebung,\n- Snapshot-Quellfehler,\n- Fehler beim Erstellen/Klonen von Pipes,\n- Ausführungsfehler (`Shell execution failed: ...`),\n- Task-Wrapper-Fehler (`Shell execution task failed: ...`).\n\nErgebnisebene der Abbruch-Flags:\n\n- Zeitüberschreitung -> `exitCode: undefined`, `timedOut: true`.\n- Abbruchsignal -> `exitCode: undefined`, `cancelled: true`.\n\n## PTY-Subsystem (`pty`)\n\n### API-Modell\n\n`new PtySession()` stellt bereit:\n\n- `start(options, onChunk?) -> Promise<{ exitCode?, cancelled, timedOut }>`\n- `write(data)`\n- `resize(cols, rows)`\n- `kill()`\n\n### Laufzeit-Lebenszyklus und Zustandsübergänge\n\n`PtySession`-Zustandsmaschine:\n\n- **Inaktiv**: `core: None`.\n- **Reserviert**: `start()` installiert den Steuerkanal synchron (`core: Some`), bevor die asynchrone Arbeit beginnt, sodass `write/resize/kill` sofort gültig werden.\n- **Laufend**: Die blockierende PTY-Schleife verarbeitet den Kindprozesszustand, Leseereignisse, Abbruch-Heartbeat und Steuernachrichten.\n- **Terminal geschlossen**: Kindprozess-Exit + Leseabschluss.\n- **Abgeschlossen**: `core` wird nach Abschluss des Start-Tasks immer auf `None` zurückgesetzt (Erfolg oder Fehler).\n\nNebenläufigkeits-Guard:\n\n- Ein Start während bereits ausgeführter Sitzung gibt `PTY session already running` zurück.\n\n### Spawn-/Anhänge-/Schreib-/Lese-/Beendigungsmuster\n\n- PTY wird über `portable_pty::native_pty_system().openpty(...)` geöffnet.\n- Befehle werden derzeit als `sh -lc <command>` mit optionalem `cwd` und Umgebungsüberschreibungen ausgeführt.\n- `write()` sendet rohe Bytes an PTY-Stdin.\n- `resize()` begrenzt Dimensionen (`cols 20..400`, `rows 5..200`) und ruft die Master-Größenänderung auf.\n- `kill()` markiert den Lauf als abgebrochen und beendet den Kindprozess.\n\nAusgabepfad:\n\n- ein dedizierter Leser-Thread liest den Master-Stream,\n- inkrementelle UTF-8-Dekodierung mit `U+FFFD`-Ersatz bei ungültigen Bytes,\n- Chunks werden über den N-API-Threadsafe-Callback weitergeleitet.\n\n### Abbruch- und Zeitüberschreitungssemantik\n\n- `timeoutMs` und `AbortSignal` speisen einen `CancelToken`.\n- Die Schleife ruft periodisch `ct.heartbeat()` auf; der Abbruch löst das Beenden des Kindprozesses aus.\n- Die Zeitüberschreitungsklassifizierung ist zeichenkettenbasiert (Teilstring `\"Timeout\"` im Heartbeat-Fehler).\n\n### Fehlerverhalten\n\nFehlerfälle umfassen:\n\n- PTY-Zuweisungs-/Öffnungsfehler,\n- PTY-Spawn-Fehler,\n- Fehler beim Erwerb von Writer/Reader,\n- Fehler beim Kindprozessstatus/-warten,\n- Lock-Vergiftung,\n- Steuerkanal-Trennung (`PTY session is no longer available`).\n\nSteueraufruf-Fehler bei nicht laufender Sitzung:\n\n- `write/resize/kill` geben `PTY session is not running` zurück.\n\n## Prozessbaum-Subsystem (`ps`)\n\n### API-Modell\n\n- `killTree(pid, signal) -> number`\n- `listDescendants(pid) -> number[]`\n\nDer TS-Wrapper registriert außerdem die native Kill-Tree-Integration in gemeinsame Dienstprogramme über `setNativeKillTree(native.killTree)`.\n\n### Plattformspezifische Implementierung\n\n- **Linux**: liest rekursiv `/proc/<pid>/task/<pid>/children`.\n- **macOS**: verwendet `libproc` `proc_listchildpids`.\n- **Windows**: erstellt einen Snapshot der Prozesstabelle mit `CreateToolhelp32Snapshot`, baut eine Eltern->Kinder-Zuordnung auf und beendet mit `OpenProcess(PROCESS_TERMINATE)` + `TerminateProcess`.\n\n### Kill-Tree-Verhalten\n\n- Nachkommen werden rekursiv gesammelt.\n- Die Beendigungsreihenfolge ist bottom-up (tiefste Nachkommen zuerst), um Neu-Elternschaft von Waisen zu reduzieren.\n- Der Root-PID wird zuletzt beendet.\n- Der Rückgabewert ist die Anzahl erfolgreicher Beendigungen.\n\nSignalverhalten:\n\n- POSIX: Das angegebene `signal` wird an `kill` übergeben.\n- Windows: `signal` wird ignoriert; die Beendigung ist eine bedingungslose Prozessbeendigung.\n\n### Fehlerverhalten\n\nDieses Modul wirft absichtlich keine Fehler auf der API-Oberfläche:\n\n- fehlende/nicht zugängliche Prozessbaumzweige werden übersprungen,\n- Kill-Fehler pro PID werden als erfolglos gezählt (keine Fehler),\n- ein Nachschlage-Fehltreffer liefert typischerweise `[]` von `listDescendants` und `0` von `killTree`.\n\n## Tastaturanalyse-Subsystem (`keys`)\n\n### API-Modell\n\nBereitgestellte Hilfsfunktionen:\n\n- `parseKey(data, kittyProtocolActive)`\n- `matchesKey(data, keyId, kittyProtocolActive)`\n- `parseKittySequence(data)`\n- `matchesKittySequence(data, expectedCodepoint, expectedModifier)`\n- `matchesLegacySequence(data, keyName)`\n\n### Analysemodell\n\nDer Parser kombiniert:\n\n- direkte Einzelbyte-Zuordnungen (`enter`, `tab`, `ctrl+<Buchstabe>`, druckbares ASCII),\n- O(1)-Legacy-Escape-Sequenz-Nachschlagen (PHF-Map),\n- xterm-`modifyOtherKeys`-Analyse,\n- Kitty-Protokoll-Analyse (`CSI u`, `CSI ~`, `CSI 1;...<Buchstabe>`),\n- Normalisierung zu Tasten-IDs (`ctrl+c`, `shift+tab`, `pageUp`, `f5` usw.).\n\nModifikatorverarbeitung:\n\n- Nur Shift-/Alt-/Ctrl-Bits werden für den Tastenabgleich verglichen,\n- Lock-Bits werden vor dem Vergleich maskiert.\n\nLayout-Verhalten:\n\n- Der Basis-Layout-Fallback ist absichtlich eingeschränkt, sodass remappte Layouts keine falschen Übereinstimmungen für ASCII-Buchstaben/-Symbole erzeugen.\n\n### Fehlerverhalten\n\n- Nicht erkannte oder ungültige Sequenzen erzeugen `null` aus Parse-Funktionen.\n- Abgleichsfunktionen geben `false` bei Parse-Fehler oder Nichtübereinstimmung zurück.\n- Kein geworfener Fehler für fehlerhafte Tastatureingaben.\n\n## JS-Wrapper-API ↔ Rust-Export-Zuordnung\n\n### Shell + PTY + Prozess\n\n| TS-Wrapper-API | Rust-N-API-Export | Hinweise |\n|---|---|---|\n| `executeShell(options, onChunk?)` | `executeShell` (`execute_shell`) | Einmalige Shell-Ausführung |\n| `new Shell(options?)` | `Shell`-Klasse | Persistente Shell-Sitzung |\n| `shell.run(options, onChunk?)` | `Shell::run` | Sitzung bei Keepalive-Steuerungsfluss wiederverwenden |\n| `shell.abort()` | `Shell::abort` | Bricht aktiven Lauf für diese Shell-Instanz ab |\n| `new PtySession()` | `PtySession`-Klasse | Zustandsbehaftete PTY-Sitzung |\n| `pty.start(options, onChunk?)` | `PtySession::start` | Interaktiver PTY-Lauf |\n| `pty.write(data)` | `PtySession::write` | Rohe Stdin-Weiterleitung |\n| `pty.resize(cols, rows)` | `PtySession::resize` | Begrenzte Terminaldimensionen |\n| `pty.kill()` | `PtySession::kill` | Erzwingt die Beendigung des aktiven PTY-Kindprozesses |\n| `killTree(pid, signal)` | `killTree` (`kill_tree`) | Kindprozesse-zuerst-Prozessbaum-Beendigung |\n| `listDescendants(pid)` | `listDescendants` (`list_descendants`) | Rekursive Nachkommenauflistung |\n\n### Tasten\n\n| TS-Wrapper-API | Rust-N-API-Export | Hinweise |\n|---|---|---|\n| `matchesKittySequence(data, cp, mod)` | `matchesKittySequence` (`matches_kitty_sequence`) | Kitty-Codepoint+Modifikator-Abgleich |\n| `parseKey(data, kittyProtocolActive)` | `parseKey` (`parse_key`) | Normalisierter Tasten-ID-Parser |\n| `matchesLegacySequence(data, keyName)` | `matchesLegacySequence` (`matches_legacy_sequence`) | Exakte Legacy-Sequenz-Map-Prüfung |\n| `parseKittySequence(data)` | `parseKittySequence` (`parse_kitty_sequence`) | Strukturiertes Kitty-Parse-Ergebnis |\n| `matchesKey(data, keyId, kittyProtocolActive)` | `matchesKey` (`matches_key`) | High-Level-Tastenabgleich |\n\n## Bereinigung verlassener Sitzungen und Finalisierungshinweise\n\n- **Persistente Shell-Sitzung**: Wenn ein Lauf abgebrochen/durch Zeitüberschreitung beendet/fehlergeschlagen/ohne Keepalive-Steuerungsfluss ist, verwirft Rust explizit den internen Sitzungszustand. Erfolgreiche normale Läufe behalten die Sitzung zur Wiederverwendung.\n- **PTY-Sitzung**: `core` wird immer nach Abschluss von `start()` gelöscht, einschließlich Fehlerpfade.\n- **Kein expliziter JS-Finalizer-gesteuerter Kill-Vertrag** wird durch Wrapper bereitgestellt; die Bereinigung ist primär an Laufabschluss-/Abbruchpfade gebunden. Aufrufer sollten `timeoutMs`, `AbortSignal`, `shell.abort()` oder `pty.kill()` für einen deterministischen Abbau verwenden.\n",
	"de/natives/natives-text-search-pipeline.md": "---\ntitle: Natives Text- und Such-Pipeline\ndescription: >-\n  Native Textsuch-Pipeline mit grep-, glob- und ripgrep-basierter\n  Dateiinhaltsindizierung.\nsidebar:\n  order: 6\n  label: Text- & Such-Pipeline\ni18n:\n  sourceHash: 0e93462fdd12\n  translator: machine\n---\n\n# Natives Text/Such-Pipeline\n\nDieses Dokument bildet die Text/Such-Oberfläche (`grep`, `glob`, `text`, `highlight`) von `@f5-sales-demo/pi-natives` ab — von TypeScript-Wrappern zu Rust N-API-Exporten und zurück zu JS-Ergebnisobjekten.\n\nDie Terminologie folgt `docs/natives-architecture.md`:\n\n- **Wrapper**: TS-API in `packages/natives/src/*`\n- **Rust-Modulschicht**: N-API-Exporte in `crates/pi-natives/src/*`\n- **Geteilter Scan-Cache**: `fs_cache`-gestützter Verzeichniseintrags-Cache, der von Discovery/Such-Abläufen verwendet wird\n\n## Implementierungsdateien\n\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/glob_util.rs`\n- `crates/pi-natives/src/fs_cache.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/highlight.rs`\n- `crates/pi-natives/src/fd.rs`\n\n## JS-API ↔ Rust-Export-Zuordnung\n\n| JS-Wrapper-API | Rust-Export (`#[napi]`, snake_case -> camelCase) | Rust-Modul |\n| --- | --- | --- |\n| `grep(options, onMatch?)` | `grep` | `grep.rs` |\n| `searchContent(content, options)` | `search` | `grep.rs` |\n| `hasMatch(content, pattern, options?)` | `hasMatch` | `grep.rs` |\n| `fuzzyFind(options)` | `fuzzyFind` | `fd.rs` |\n| `glob(options, onMatch?)` | `glob` | `glob.rs` |\n| `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `fs_cache.rs` |\n| `wrapTextWithAnsi(text, width)` | `wrapTextWithAnsi` | `text.rs` |\n| `truncateToWidth(text, maxWidth, ellipsis, pad)` | `truncateToWidth` | `text.rs` |\n| `sliceWithWidth(line, startCol, length, strict?)` | `sliceWithWidth` | `text.rs` |\n| `extractSegments(line, beforeEnd, afterStart, afterLen, strictAfter)` | `extractSegments` | `text.rs` |\n| `sanitizeText(text)` | `sanitizeText` | `text.rs` |\n| `visibleWidth(text)` | `visibleWidth` | `text.rs` |\n| `highlightCode(code, lang, colors)` | `highlightCode` | `highlight.rs` |\n| `supportsLanguage(lang)` | `supportsLanguage` | `highlight.rs` |\n| `getSupportedLanguages()` | `getSupportedLanguages` | `highlight.rs` |\n\n## Pipeline-Übersicht nach Subsystem\n\n## 1) Regex-Suche (`grep`, `searchContent`, `hasMatch`)\n\n### Eingabe-/Options-Ablauf\n\n1. Der TS-Wrapper leitet Optionen an die native Schicht weiter:\n   - `grep/index.ts` übergibt `options` weitgehend unverändert und wandelt den Callback von `(match) => void` in die napi-threadsafe-Callback-Form `(err, match)` um.\n   - `searchContent` und `hasMatch` übergeben String/`Uint8Array` direkt.\n2. Rust-Option-Structs in `grep.rs` deserialisieren camelCase-Felder (`ignoreCase`, `maxCount`, `contextBefore`, `contextAfter`, `maxColumns`, `timeoutMs`).\n3. `grep` erstellt ein `CancelToken` aus `timeoutMs` + `AbortSignal` und läuft innerhalb von `task::blocking(\"grep\", ...)`.\n\n### Ausführungszweige\n\n- **In-Memory-Zweig (reines Hilfsmittel)**\n  - `search` → `search_sync` → `run_search` auf den bereitgestellten Inhaltsbytes.\n  - Kein Dateisystem-Scan, kein `fs_cache`.\n- **Einzeldatei-Zweig (dateisystemabhängig)**\n  - `grep_sync` löst den Pfad auf, prüft ob Metadaten eine Datei sind, streamt bis zu `MAX_FILE_BYTES` pro Datei (`4 MiB`) durch den Ripgrep-Matcher.\n- **Verzeichnis-Zweig (dateisystemabhängig)**\n  - Optionaler Cache-Lookup über `fs_cache::get_or_scan` bei `cache: true`.\n  - Frischer Scan über `fs_cache::force_rescan` bei `cache: false`.\n  - Optionale Leer-Ergebnis-Neuprüfung wenn das Cache-Alter `empty_recheck_ms()` überschreitet.\n  - Eintragsfilterung: nur Dateien + optionaler Glob-Filter (`glob_util`) + optionales Typ-Filter-Mapping (`js`, `ts`, `rust`, etc.).\n\n### Such-/Sammlungssemantik\n\n- Regex-Engine: `grep_regex::RegexMatcherBuilder` mit `ignoreCase` und `multiline`.\n- Kontext-Auflösung:\n  - `contextBefore/contextAfter` überschreiben das Legacy-`context`.\n  - Nicht-Inhaltsmodi setzen die Kontextsammlung auf null.\n- Ausgabemodi:\n  - `content` => ein `GrepMatch` pro Treffer.\n  - `count` und `filesWithMatches` werden beide auf Zählstil-Einträge abgebildet (`lineNumber=0`, `line=\"\"`, `matchCount` gesetzt).\n- Limits:\n  - Globaler `offset` und `maxCount` werden dateienübergreifend angewendet.\n  - Der parallele Pfad wird nur verwendet wenn `maxCount` nicht gesetzt ist und `offset == 0`; andernfalls bewahrt der sequentielle Pfad deterministische globale Offset/Limit-Semantik.\n\n### Ergebnisformung zurück zu JS\n\n- Rust-`SearchResult`/`GrepResult`-Felder werden über N-API-Objektfeldkonvertierung auf TS-Typen abgebildet.\n- Zähler werden auf `u32` begrenzt bevor sie die N-API-Grenze überschreiten.\n- Optionale Booleans werden ausgelassen, es sei denn sie sind in einigen Pfaden wahr (`limitReached`).\n- Der Streaming-Callback empfängt jeden geformten `GrepMatch` (Inhalts- oder Zähleintrag).\n\n### Fehlerverhalten\n\n- `searchContent` gibt `SearchResult.error` für Regex-/Suchfehler zurück anstatt zu werfen.\n- `grep` lehnt bei schweren Fehlern ab (ungültiger Pfad, ungültiges Glob/Regex, Abbruch-Timeout/Abort).\n- `hasMatch` gibt `Result<bool>` zurück und wirft bei ungültigem Muster/UTF-8-Dekodierungsfehlern.\n- Datei-Öffnungs-/Suchfehler bei Multi-Datei-Scans werden pro Datei übersprungen; der Scan wird fortgesetzt.\n\n### Behandlung fehlerhafter Regex\n\n`grep.rs` bereinigt geschweifte Klammern vor der Regex-Kompilierung:\n\n- Ungültige wiederholungsähnliche geschweifte Klammern werden escaped (`{`/`}` -> `\\{`/`\\}`), wenn sie nicht `{N}`, `{N,}`, `{N,M}` bilden können.\n- Dies verhindert, dass häufige Literal-Template-Fragmente (zum Beispiel `${platform}`) als fehlerhafte Wiederholungen fehlschlagen.\n- Verbleibende ungültige Regex-Syntax gibt weiterhin einen Regex-Fehler zurück.\n\n## 2) Datei-Discovery (`glob`) und Fuzzy-Pfadsuche (`fuzzyFind`)\n\n`glob` und `fuzzyFind` teilen sich `fs_cache`-Scans; die Matching-Logik unterscheidet sich.\n\n### `glob`-Ablauf\n\n1. TS-Wrapper (`glob/index.ts`):\n   - `path.resolve(options.path)`.\n   - Standardwerte: `pattern=\"*\"`, `hidden=false`, `gitignore=true`, `recursive=true`.\n2. Rust `glob` erstellt `GlobConfig` und kompiliert das Muster über `glob_util::compile_glob`.\n3. Eintragsquelle:\n   - `cache=true` => `get_or_scan` + optionaler veralteter-Leer-`force_rescan`.\n   - `cache=false` => `force_rescan(..., store=false)` (nur frisch).\n4. Filterung:\n   - `.git` wird immer übersprungen.\n   - `node_modules` wird übersprungen, es sei denn angefordert (`includeNodeModules` oder Muster erwähnt node_modules).\n   - Glob-Match anwenden.\n   - Dateityp-Filter anwenden; Symlink-`file/dir`-Filter lösen Ziel-Metadaten auf.\n5. Optionale Sortierung nach mtime absteigend (`sortByMtime`) vor dem Abschneiden auf `maxResults`.\n\n### `fuzzyFind`-Ablauf (implementiert in `fd.rs`)\n\n1. Der TS-Wrapper wird aus dem `grep`-Modul exportiert, aber die Rust-Implementierung befindet sich in `fd.rs`.\n2. Gemeinsame Scan-Quelle aus `fs_cache` mit derselben Cache/Kein-Cache-Aufteilung und Richtlinie zur Neuprüfung veralteter Leer-Ergebnisse.\n3. Bewertung:\n   - Exakt / beginnt-mit / enthält / Subsequenz-basierter Fuzzy-Score\n   - Separator/Interpunktions-normalisierter Bewertungspfad\n   - Verzeichnis-Bonus und deterministischer Gleichstandsbruch (`score absteigend`, dann `path aufsteigend`)\n4. Symlink-Einträge werden von Fuzzy-Ergebnissen ausgeschlossen.\n\n### Fehlerverhalten\n\n- Ungültiges Glob-Muster => Fehler von `glob_util::compile_glob`.\n- Der Suchwurzel muss ein existierendes Verzeichnis sein (`resolve_search_path`), andernfalls Fehler.\n- Abbrüche/Timeouts werden als Abort-Fehler über `CancelToken::heartbeat()`-Prüfungen in Schleifen weitergegeben.\n\n### Behandlung fehlerhafter Globs\n\n`glob_util::build_glob_pattern` ist tolerant:\n\n- Normalisiert `\\` zu `/`.\n- Fügt einfachen rekursiven Mustern automatisch `**/` voran wenn `recursive=true`.\n- Schließt automatisch unbalancierte `{...`-Alternationsgruppen vor der Kompilierung.\n\n## 3) Geteilter Scan-/Cache-Lebenszyklus (`fs_cache`)\n\n`fs_cache` speichert Scan-Ergebnisse als normalisierte relative Einträge (`path`, `fileType`, optionale `mtime`), indiziert durch:\n\n- Kanonischen Suchwurzel\n- `include_hidden`\n- `use_gitignore`\n\n### Cache-Zustandsübergänge\n\n1. **Miss / deaktiviert**\n   - TTL ist `0` oder Schlüssel fehlt/abgelaufen -> frisches `collect_entries`.\n2. **Hit**\n   - Eintragsalter `< cache_ttl_ms()` -> gecachte Einträge + `cache_age_ms` zurückgeben.\n3. **Neuprüfung veralteter Leer-Ergebnisse** (Aufrufer-Richtlinie in `glob`/`grep`/`fd`)\n   - Wenn eine Abfrage null Treffer liefert und `cache_age_ms >= empty_recheck_ms()`, einen Rescan erzwingen.\n4. **Invalidierung**\n   - `invalidateFsScanCache(path?)`:\n     - Kein Argument: alle Schlüssel löschen\n     - Pfad-Argument: Schlüssel entfernen, deren Wurzel den Zielpfad als Präfix hat\n\n### Kompromiss bei veralteten Ergebnissen\n\n- Der Cache bevorzugt niedrige Latenz bei wiederholten Scans gegenüber sofortiger Konsistenz.\n- Das TTL-Fenster kann veraltete positive/negative Ergebnisse zurückgeben.\n- Die Neuprüfung leerer Ergebnisse reduziert veraltete Negative für ältere gecachte Scans auf Kosten eines zusätzlichen Scans.\n- Explizite Invalidierung ist der vorgesehene Korrektheitsmechanismus nach Dateimutationen.\n\n## 4) ANSI-Text-Hilfsfunktionen (`text`)\n\nDies sind reine In-Memory-Hilfsfunktionen (kein Dateisystem-Scanning).\n\n### Grenzen und Verantwortlichkeiten\n\n- **`text.rs` besitzt Terminal-Zell-Semantik**:\n  - ANSI-Sequenz-Parsing\n  - Graphem-bewusste Breite und Slicing\n  - Wrap/Truncate/Sanitize-Verhalten\n- **`grep.rs`-Zeilenkürzung (`maxColumns`) ist separat**:\n  - Einfache Zeichengrenzen-Kürzung von Trefferzeilen mit `...`\n  - Nicht ANSI-Zustand-erhaltend und nicht Terminal-Zellbreiten-bewusst\n\n### Schlüsselverhalten\n\n- `wrapTextWithAnsi`: Umbricht nach sichtbarer Breite, überträgt aktive SGR-Codes über umbrochene Zeilen.\n- `truncateToWidth`: Sichtbare-Zellen-Kürzung mit Ellipsis-Richtlinie (`Unicode`, `Ascii`, `Omit`), optionalem rechten Padding und Fast-Path, der den originalen JS-String zurückgibt wenn unverändert.\n- `sliceWithWidth`: Spalten-Slicing mit optionaler strikter Breitendurchsetzung.\n- `extractSegments`: Extrahiert Vorher/Nachher-Segmente um ein Overlay, wobei der ANSI-Zustand für das `after`-Segment wiederhergestellt wird.\n- `sanitizeText`: Entfernt ANSI-Escapes + Steuerzeichen, verwirft einzelne Surrogate, normalisiert CR/LF durch Entfernung von `\\r`.\n- `visibleWidth`: Zählt sichtbare Terminal-Zellen (Tabs verwenden feste `TAB_WIDTH` aus der Rust-Implementierung).\n\n### Fehlerverhalten\n\nTextfunktionen geben generell deterministisch transformierte Ausgaben zurück; Fehler beschränken sich auf JS-String-Konvertierungsgrenzen (N-API-Argumentkonvertierungsfehler).\n\n## 5) Syntax-Highlighting (`highlight`)\n\n`highlight.rs` ist reine Transformation (kein FS, kein Cache).\n\n### Ablauf\n\n1. Der Wrapper leitet `code`, optionales `lang` und eine ANSI-Farbpalette weiter.\n2. Rust löst die Syntax auf durch:\n   - Token/Name-Lookup\n   - Erweiterungs-Lookup\n   - Alias-Tabellen-Fallback (`ts/tsx/js -> JavaScript`, etc.)\n   - Fallback auf Klartext-Syntax wenn nicht aufgelöst\n3. Jede Zeile wird mit syntect `ParseState` und Scope-Stack geparst.\n4. Scopes werden auf 11 semantische Farbkategorien abgebildet und ANSI-Farbcodes eingefügt/zurückgesetzt.\n\n### Fehlerverhalten\n\n- Ein Parse-Fehler pro Zeile lässt den Aufruf nicht fehlschlagen: diese Zeile wird ohne Highlighting angehängt und die Verarbeitung wird fortgesetzt.\n- Unbekannte/nicht unterstützte Sprache fällt auf Klartext-Syntax zurück.\n\n## Reine Hilfsfunktionen vs. dateisystemabhängige Abläufe\n\n| Ablauf | Dateisystemzugriff | Geteilter Cache | Anmerkungen |\n| --- | --- | --- | --- |\n| `searchContent` / `hasMatch` | Nein | Nein | Regex nur auf bereitgestellten Bytes/String |\n| `text`-Modulfunktionen | Nein | Nein | Nur ANSI/Breite/Bereinigung |\n| `highlight`-Modulfunktionen | Nein | Nein | Nur Syntax + ANSI-Färbung |\n| `glob` | Ja | Optional | Verzeichnis-Scans + Glob-Filterung |\n| `fuzzyFind` | Ja | Optional | Verzeichnis-Scans + Fuzzy-Bewertung |\n| `grep` (Datei-/Verzeichnispfad) | Ja | Optional (Verzeichnismodus) | Ripgrep über Dateien, optionale Filter/Callback |\n\n## End-to-End-Lebenszyklus-Zusammenfassung\n\n1. Der Aufrufer ruft den TS-Wrapper mit typisierten Optionen auf.\n2. Der Wrapper normalisiert Standardwerte (insbesondere `glob`) und leitet an den `native.*`-Export weiter.\n3. Rust validiert/normalisiert Optionen und erstellt Matcher/Such-Konfiguration.\n4. Für Dateisystem-Abläufe werden Einträge gescannt (Cache-Hit/Miss/Rescan) und dann gefiltert/bewertet.\n5. Worker-Schleifen rufen periodisch den Cancel-Heartbeat auf; Timeout/Abort kann die Ausführung beenden.\n6. Rust formt Ausgaben in N-API-Objekte (`lineNumber`, `matchCount`, `limitReached`, etc.).\n7. Der TS-Wrapper gibt typisierte JS-Objekte zurück (und optionale Pro-Match-Callbacks für `grep`/`glob`).\n",
	"de/natives/porting-to-natives.md": "---\ntitle: Portierung zu pi-natives (N-API) — Feldnotizen\ndescription: >-\n  Feldnotizen für die Migration von Node.js child_process und Shell-Code zur\n  Rust N-API nativen Schicht.\nsidebar:\n  order: 9\n  label: Portierung zu pi-natives\ni18n:\n  sourceHash: 4f5150286535\n  translator: machine\n---\n\n# Portierung zu pi-natives (N-API) — Feldnotizen\n\nDies ist ein praxisorientierter Leitfaden zum Verschieben von Hot Paths in `crates/pi-natives` und deren Anbindung über die JS-Bindings. Er existiert, um zu vermeiden, dass dieselben Fehler zweimal auftreten.\n\n## Wann portieren\n\nPortieren Sie, wenn einer der folgenden Punkte zutrifft:\n\n- Der Hot Path läuft in Render-Schleifen, engen UI-Updates oder großen Batches.\n- JS-Allokationen dominieren (String-Churn, Regex-Backtracking, große Arrays).\n- Sie haben bereits eine JS-Baseline und können beide Versionen nebeneinander benchmarken.\n- Die Arbeit ist CPU-gebunden oder blockierendes I/O, das im libuv-Thread-Pool laufen kann.\n- Die Arbeit ist asynchrones I/O, das auf Tokios Runtime laufen kann (z. B. Shell-Ausführung).\n\nVermeiden Sie Portierungen, die von JS-exklusivem State oder dynamischen Imports abhängen. N-API-Exports sollten pur sein, Data-in/Data-out. Lang laufende Arbeit sollte über `task::blocking` (CPU-gebunden/blockierendes I/O) oder `task::future` (asynchrones I/O) mit Abbruchmöglichkeit geleitet werden.\n\n## Anatomie eines nativen Exports\n\n**Rust-Seite:**\n\n- Die Implementierung befindet sich in `crates/pi-natives/src/<module>.rs`. Wenn Sie ein neues Modul hinzufügen, registrieren Sie es in `crates/pi-natives/src/lib.rs`.\n- Exportieren Sie mit `#[napi]`; snake_case-Exports werden automatisch in camelCase konvertiert. Verwenden Sie explizites `js_name` nur für echte Aliase/nicht-standardmäßige Namen. Verwenden Sie `#[napi(object)]` für Structs.\n- Verwenden Sie `task::blocking(tag, cancel_token, work)` (siehe `crates/pi-natives/src/task.rs`) für CPU-gebundene oder blockierende Arbeit. Verwenden Sie `task::future(env, tag, work)` für asynchrone Arbeit, die Tokio benötigt (z. B. Shell-Sessions). Übergeben Sie ein `CancelToken`, wenn Sie `timeoutMs` oder `AbortSignal` exponieren.\n\n**JS-Seite:**\n\n- `packages/natives/src/bindings.ts` enthält das Basis-`NativeBindings`-Interface.\n- `packages/natives/src/<module>/types.ts` definiert TS-Typen und erweitert `NativeBindings` via Declaration Merging.\n- `packages/natives/src/native.ts` importiert jede `<module>/types.ts`-Datei, um die Deklarationen zu aktivieren.\n- `packages/natives/src/<module>/index.ts` umhüllt das `native`-Binding aus `packages/natives/src/native.ts`.\n- `packages/natives/src/native.ts` lädt das Addon und `validateNative` erzwingt die erforderlichen Exports.\n- `packages/natives/src/index.ts` re-exportiert den Wrapper für Aufrufer in `packages/*`.\n\n## Portierungs-Checkliste\n\n1. **Rust-Implementierung hinzufügen**\n\n- Platzieren Sie die Kernlogik in einer einfachen Rust-Funktion.\n- Wenn es ein neues Modul ist, fügen Sie es zu `crates/pi-natives/src/lib.rs` hinzu.\n- Exponieren Sie es mit `#[napi]`, damit das Standard-Mapping snake_case -> camelCase konsistent bleibt.\n- Halten Sie Signaturen owned und einfach: `String`, `Vec<String>`, `Uint8Array` oder `Either<JsString, Uint8Array>` für große String-/Byte-Eingaben.\n- Für CPU-gebundene oder blockierende Arbeit verwenden Sie `task::blocking`; für asynchrone Arbeit verwenden Sie `task::future`. Übergeben Sie ein `CancelToken` und rufen Sie `heartbeat()` in langen Schleifen auf.\n\n2. **JS-Bindings verdrahten**\n\n- Fügen Sie die Typen und die `NativeBindings`-Erweiterung in `packages/natives/src/<module>/types.ts` hinzu.\n- Importieren Sie `./<module>/types` in `packages/natives/src/native.ts`, um Declaration Merging auszulösen.\n- Fügen Sie einen Wrapper in `packages/natives/src/<module>/index.ts` hinzu, der `native` aufruft.\n- Re-exportieren Sie aus `packages/natives/src/index.ts`.\n\n3. **Native-Validierung aktualisieren**\n\n- Fügen Sie `checkFn(\"newExport\")` in `validateNative` (`packages/natives/src/native.ts`) hinzu.\n\n4. **Benchmarks hinzufügen**\n\n- Platzieren Sie Benchmarks neben dem zugehörigen Paket (`packages/tui/bench`, `packages/natives/bench` oder `packages/coding-agent/bench`).\n- Fügen Sie eine JS-Baseline und die native Version im selben Durchlauf ein.\n- Verwenden Sie `Bun.nanoseconds()` und eine feste Iterationsanzahl.\n- Halten Sie die Benchmark-Eingaben klein und realistisch (tatsächliche Daten aus dem Hot Path).\n\n5. **Native Binary bauen**\n\n- `bun --cwd=packages/natives run build`\n- Verwenden Sie `bun --cwd=packages/natives run build` und setzen Sie `PI_DEV=1`, wenn Sie beim Testen Loader-Diagnosen sehen möchten.\n\n6. **Benchmark ausführen**\n\n- `bun run packages/<pkg>/bench/<bench>.ts` (oder `bun --cwd=packages/natives run bench`)\n\n7. **Über Verwendung entscheiden**\n\n- Wenn native langsamer ist, **behalten Sie JS** bei und lassen Sie den nativen Export ungenutzt.\n- Wenn native schneller ist, wechseln Sie die Aufrufstellen zum nativen Wrapper.\n\n## Schmerzpunkte und wie man sie vermeidet\n\n### 1) Veraltete `pi_natives.node` verhindert neue Exports\n\nDer Loader bevorzugt die plattform-getaggte Binary in `packages/natives/native` (`pi_natives.<platform>-<arch>.node`). `PI_DEV=1` aktiviert jetzt nur Loader-Diagnosen; es wechselt nicht mehr zu einem separaten Dev-Addon-Dateinamen. Es gibt auch ein Fallback `pi_natives.node`. Kompilierte Binaries werden nach `~/.xcsh/natives/<version>/pi_natives.<platform>-<arch>.node` extrahiert. Wenn eine dieser Dateien veraltet ist, werden Exports nicht aktualisiert.\n\n**Lösung:** Entfernen Sie die veraltete Datei vor dem Neubauen.\n\n```bash\nrm packages/natives/native/pi_natives.linux-x64.node\nrm packages/natives/native/pi_natives.node\nbun --cwd=packages/natives run build\n```\n\nWenn Sie eine kompilierte Binary verwenden, löschen Sie das zwischengespeicherte Addon-Verzeichnis:\n\n```bash\nrm -rf ~/.xcsh/natives/<version>\n```\n\nÜberprüfen Sie dann, ob der Export in der Binary vorhanden ist:\n\n```bash\nbun -e 'const tag = `${process.platform}-${process.arch}`; const mod = require(`./packages/natives/native/pi_natives.${tag}.node`); console.log(Object.keys(mod).includes(\"newExport\"));'\n```\n\n### 2) \"Missing exports\"-Fehler von `validateNative`\n\nDas ist **gut** — es verhindert stille Abweichungen. Wenn Sie dies sehen:\n\n```\nNative addon missing exports ... Missing: visibleWidth\n```\n\nbedeutet das, dass Ihre Binary veraltet ist, der Rust-Exportname (oder expliziter Alias, wenn verwendet) nicht mit dem JS-Namen übereinstimmt, oder der Export nie kompiliert wurde. Beheben Sie den Build und die Namensabweichung, schwächen Sie nicht die Validierung.\n\n### 3) Rust-Signatur-Mismatch\n\nHalten Sie es einfach und owned. `String`, `Vec<String>` und `Uint8Array` funktionieren. Vermeiden Sie Referenzen wie `&str` in öffentlichen Exports. Wenn Sie strukturierte Daten benötigen, umhüllen Sie sie in `#[napi(object)]`-Structs.\n\n### 4) Benchmarking-Fehler\n\n- Vergleichen Sie keine unterschiedlichen Eingaben oder Allokationen.\n- Verwenden Sie für JS und native identische Eingabe-Arrays.\n- Führen Sie beide in derselben Benchmark-Datei aus, um Verzerrungen zu vermeiden.\n\n## Benchmark-Vorlage\n\n```ts\nconst ITERATIONS = 2000;\n\nfunction bench(name: string, fn: () => void): number {\n const start = Bun.nanoseconds();\n for (let i = 0; i < ITERATIONS; i++) fn();\n const elapsed = (Bun.nanoseconds() - start) / 1e6;\n console.log(`${name}: ${elapsed.toFixed(2)}ms total (${(elapsed / ITERATIONS).toFixed(6)}ms/op)`);\n return elapsed;\n}\n\nbench(\"feature/js\", () => {\n jsImpl(sample);\n});\n\nbench(\"feature/native\", () => {\n nativeImpl(sample);\n});\n```\n\n## Verifizierungs-Checkliste\n\n- `validateNative` besteht (keine fehlenden Exports).\n- `NativeBindings` ist in `packages/natives/src/<module>/types.ts` erweitert und der Wrapper ist in `packages/natives/src/index.ts` re-exportiert.\n- `Object.keys(require(...))` enthält Ihren neuen Export.\n- Benchmark-Ergebnisse sind im PR/in den Notizen dokumentiert.\n- Aufrufstelle **nur dann** aktualisiert, wenn native schneller oder gleichwertig ist.\n\n## Faustregel\n\n- Wenn native langsamer ist, **wechseln Sie nicht**. Behalten Sie den Export für zukünftige Arbeit, aber das TUI sollte auf dem schnelleren Pfad bleiben.\n- Wenn native schneller ist, wechseln Sie die Aufrufstelle und behalten Sie den Benchmark bei, um Regressionen zu erkennen.\n",
	"de/providers/models.md": "---\ntitle: Modell- und Anbieterkonfiguration\ndescription: >-\n  Modellregistrierung und Anbieterkonfiguration über models.yml mit Routing,\n  Fallback und Preisgestaltung.\nsidebar:\n  order: 1\n  label: Modelle & Anbieter\ni18n:\n  sourceHash: 8053df967ff6\n  translator: machine\n---\n\n# Modell- und Anbieterkonfiguration (`models.yml`)\n\nDieses Dokument beschreibt, wie der Coding-Agent derzeit Modelle lädt, Überschreibungen anwendet, Anmeldedaten auflöst und Modelle zur Laufzeit auswählt.\n\n## Was das Modellverhalten steuert\n\nPrimäre Implementierungsdateien:\n\n- `src/config/model-registry.ts` — lädt integrierte + benutzerdefinierte Modelle, Anbieter-Überschreibungen, Laufzeit-Erkennung, Auth-Integration\n- `src/config/model-resolver.ts` — parst Modellmuster und wählt initial/smol/slow-Modelle aus\n- `src/config/settings-schema.ts` — modellbezogene Einstellungen (`modelRoles`, Anbieter-Transport-Präferenzen)\n- `src/session/auth-storage.ts` — Auflösungsreihenfolge für API-Schlüssel + OAuth\n- `packages/ai/src/models.ts` und `packages/ai/src/types.ts` — integrierte Anbieter/Modelle und `Model`/`compat`-Typen\n\n## Speicherort der Konfigurationsdatei und Legacy-Verhalten\n\nStandardmäßiger Konfigurationspfad:\n\n- `~/.xcsh/agent/models.yml`\n\nNoch vorhandenes Legacy-Verhalten:\n\n- Wenn `models.yml` fehlt und `models.json` am selben Speicherort existiert, wird sie zu `models.yml` migriert.\n- Explizite `.json`- / `.jsonc`-Konfigurationspfade werden weiterhin unterstützt, wenn sie programmatisch an `ModelRegistry` übergeben werden.\n\n## Struktur von `models.yml`\n\n```yaml\nconfigVersion: 1  # optional — wird von Auto-Config geschrieben, für Migrationserkennung verwendet\nproviders:\n  <provider-id>:\n    # Konfiguration auf Anbieterebene\nequivalence:\n  overrides:\n    <provider-id>/<model-id>: <canonical-model-id>\n  exclude:\n    - <provider-id>/<model-id>\n```\n\n`configVersion` ist eine optionale Ganzzahl, die vom Auto-Config-System geschrieben wird. Wenn vorhanden, verwendet xcsh sie zur Erkennung veralteter Konfigurationen und zum automatischen Upgrade.\n\n`provider-id` ist der kanonische Anbieterschlüssel, der für Auswahl und Auth-Suche verwendet wird.\n\n`equivalence` ist optional und konfiguriert die kanonische Modellgruppierung auf Basis konkreter Anbietermodelle:\n\n- `overrides` ordnet einen exakten konkreten Selektor (`provider/modelId`) einer offiziellen kanonischen Upstream-ID zu\n- `exclude` schließt einen konkreten Selektor von der kanonischen Gruppierung aus\n\n## Felder auf Anbieterebene\n\n```yaml\nproviders:\n  my-provider:\n    baseUrl: https://api.example.com/v1\n    apiKey: MY_PROVIDER_API_KEY\n    api: openai-completions\n    headers:\n      X-Team: platform\n    authHeader: true\n    auth: apiKey\n    discovery:\n      type: ollama\n    modelOverrides:\n      some-model-id:\n        name: Renamed model\n    models:\n      - id: some-model-id\n        name: Some Model\n        api: openai-completions\n        reasoning: false\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 128000\n        maxTokens: 16384\n        headers:\n          X-Model: value\n        compat:\n          supportsStore: true\n          supportsDeveloperRole: true\n          supportsReasoningEffort: true\n          maxTokensField: max_completion_tokens\n          openRouterRouting:\n            only: [anthropic]\n          vercelGatewayRouting:\n            order: [anthropic, openai]\n          extraBody:\n            gateway: m1-01\n            controller: mlx\n```\n\n### Erlaubte `api`-Werte für Anbieter/Modelle\n\n- `openai-completions`\n- `openai-responses`\n- `openai-codex-responses`\n- `azure-openai-responses`\n- `anthropic-messages`\n- `google-generative-ai`\n- `google-vertex`\n\n### Erlaubte auth/discovery-Werte\n\n- `auth`: `apiKey` (Standard) oder `none`\n- `discovery.type`: `ollama`\n\n## Validierungsregeln (aktuell)\n\n### Vollständiger benutzerdefinierter Anbieter (`models` ist nicht leer)\n\nErforderlich:\n\n- `baseUrl`\n- `apiKey` sofern nicht `auth: none`\n- `api` auf Anbieterebene oder pro Modell\n\n### Nur-Überschreibungs-Anbieter (`models` fehlt oder leer)\n\nMuss mindestens eines der folgenden definieren:\n\n- `baseUrl`\n- `modelOverrides`\n- `discovery`\n\n### Discovery\n\n- `discovery` erfordert `api` auf Anbieterebene.\n\n### Modellwert-Prüfungen\n\n- `id` erforderlich\n- `contextWindow` und `maxTokens` müssen positiv sein, wenn angegeben\n\n## Zusammenführungs- und Überschreibungsreihenfolge\n\nModelRegistry-Pipeline (beim Aktualisieren):\n\n1. Integrierte Anbieter/Modelle aus `@f5-sales-demo/pi-ai` laden.\n2. Benutzerdefinierte `models.yml`-Konfiguration laden.\n3. Anbieter-Überschreibungen (`baseUrl`, `headers`) auf integrierte Modelle anwenden.\n4. `modelOverrides` anwenden (pro Anbieter + Modell-ID).\n5. Benutzerdefinierte `models` zusammenführen:\n   - gleicher `provider + id` ersetzt vorhandenes\n   - ansonsten anhängen\n6. Zur Laufzeit erkannte Modelle anwenden (derzeit Ollama und LM Studio), dann Modell-Überschreibungen erneut anwenden.\n\n## Kanonische Modelläquivalenz und Zusammenfassung\n\nDie Registry behält jedes konkrete Anbietermodell und baut dann eine kanonische Ebene darüber auf.\n\nKanonische IDs sind ausschließlich offizielle Upstream-IDs, zum Beispiel:\n\n- `claude-opus-4-6`\n- `claude-haiku-4-5`\n- `gpt-5.3-codex`\n\n### `models.yml` Äquivalenzkonfiguration\n\nBeispiel:\n\n```yaml\nproviders:\n  zenmux:\n    baseUrl: https://api.zenmux.example/v1\n    apiKey: ZENMUX_API_KEY\n    api: openai-codex-responses\n    models:\n      - id: codex\n        name: Zenmux Codex\n        reasoning: true\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 200000\n        maxTokens: 32768\n\nequivalence:\n  overrides:\n    zenmux/codex: gpt-5.3-codex\n    p-codex/codex: gpt-5.3-codex\n  exclude:\n    - demo/codex-preview\n```\n\nAufbaureihenfolge für die kanonische Gruppierung:\n\n1. Exakte Benutzer-Überschreibung aus `equivalence.overrides`\n2. Gebündelte Übereinstimmungen offizieller IDs aus integrierten Modell-Metadaten\n3. Konservative heuristische Normalisierung für Gateway-/Anbietervarianten\n4. Fallback auf die eigene ID des konkreten Modells\n\nAktuelle Heuristiken sind absichtlich eng gefasst:\n\n- Eingebettete Upstream-Präfixe können entfernt werden, wenn vorhanden, zum Beispiel `anthropic/...` oder `openai/...`\n- Punkt- und Bindestrich-Versionsvarianten können nur normalisiert werden, wenn sie auf eine vorhandene offizielle ID abbilden, zum Beispiel `4.6 -> 4-6`\n- Mehrdeutige Familien oder Versionen werden ohne gebündelte Übereinstimmung oder explizite Überschreibung nicht zusammengeführt\n\n### Kanonisches Auflösungsverhalten\n\nWenn mehrere konkrete Varianten eine kanonische ID teilen, verwendet die Auflösung:\n\n1. Verfügbarkeit und Auth\n2. `config.yml` `modelProviderOrder`\n3. Vorhandene Registry-/Anbieterreihenfolge, wenn `modelProviderOrder` nicht gesetzt ist\n\nDeaktivierte oder nicht authentifizierte Anbieter werden übersprungen.\n\nSitzungsstatus und Protokolle zeichnen weiterhin den konkreten Anbieter/das Modell auf, das den Turn tatsächlich ausgeführt hat.\n\nAnbieter-Standardwerte vs. Überschreibungen pro Modell:\n\n- Anbieter-`headers` sind die Basis.\n- Modell-`headers` überschreiben Header-Schlüssel des Anbieters.\n- `modelOverrides` können Modell-Metadaten überschreiben (`name`, `reasoning`, `input`, `cost`, `contextWindow`, `maxTokens`, `headers`, `compat`, `contextPromotionTarget`).\n- `compat` wird für verschachtelte Routing-Blöcke (`openRouterRouting`, `vercelGatewayRouting`, `extraBody`) tief zusammengeführt.\n\n## Integration der Laufzeit-Erkennung\n\n### Implizite Ollama-Erkennung\n\nWenn `ollama` nicht explizit konfiguriert ist, fügt die Registry einen impliziten erkennbaren Anbieter hinzu:\n\n- Anbieter: `ollama`\n- API: `openai-completions`\n- Basis-URL: `OLLAMA_BASE_URL` oder `http://127.0.0.1:11434`\n- Auth-Modus: schlüssellos (`auth: none`-Verhalten)\n\nDie Laufzeit-Erkennung ruft `GET /api/tags` bei Ollama auf und synthetisiert Modelleinträge mit lokalen Standardwerten.\n\n### Implizite llama.cpp-Erkennung\n\nWenn `llama.cpp` nicht explizit konfiguriert ist, fügt die Registry einen impliziten erkennbaren Anbieter hinzu:\nHinweis: Es wird die neuere Anthropic Messages API anstelle von openai-completions verwendet.\n\n- Anbieter: `llama.cpp`\n- API: `openai-responses`\n- Basis-URL: `LLAMA_CPP_BASE_URL` oder `http://127.0.0.1:8080`\n- Auth-Modus: schlüssellos (`auth: none`-Verhalten)\n\nDie Laufzeit-Erkennung ruft `GET models` bei llama.cpp auf und synthetisiert Modelleinträge mit lokalen Standardwerten.\n\n### Implizite LM Studio-Erkennung\n\nWenn `lm-studio` nicht explizit konfiguriert ist, fügt die Registry einen impliziten erkennbaren Anbieter hinzu:\n\n- Anbieter: `lm-studio`\n- API: `openai-completions`\n- Basis-URL: `LM_STUDIO_BASE_URL` oder `http://127.0.0.1:1234/v1`\n- Auth-Modus: schlüssellos (`auth: none`-Verhalten)\n\nDie Laufzeit-Erkennung ruft Modelle ab (`GET /models`) und synthetisiert Modelleinträge mit lokalen Standardwerten.\n\n### Explizite Anbieter-Erkennung\n\nSie können die Erkennung selbst konfigurieren:\n\n```yaml\nproviders:\n  ollama:\n    baseUrl: http://127.0.0.1:11434\n    api: openai-completions\n    auth: none\n    discovery:\n      type: ollama\n      \n  llama.cpp:\n    baseUrl: http://127.0.0.1:8080\n    api: openai-responses\n    auth: none\n    discovery:\n      type: llama.cpp\n```\n\n### Anbieterregistrierung durch Erweiterungen\n\nErweiterungen können Anbieter zur Laufzeit registrieren (`pi.registerProvider(...)`), einschließlich:\n\n- Modellersetzung/-anhängung für einen Anbieter\n- Registrierung benutzerdefinierter Stream-Handler für neue API-IDs\n- Registrierung benutzerdefinierter OAuth-Anbieter\n\n## Auth- und API-Schlüssel-Auflösungsreihenfolge\n\nBeim Anfordern eines Schlüssels für einen Anbieter ist die effektive Reihenfolge:\n\n1. Laufzeit-Überschreibung (CLI `--api-key`)\n2. Gespeicherte API-Schlüssel-Anmeldedaten in `agent.db`\n3. Gespeicherte OAuth-Anmeldedaten in `agent.db` (mit Aktualisierung)\n4. Umgebungsvariablen-Zuordnung (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc.)\n5. ModelRegistry-Fallback-Resolver (Anbieter-`apiKey` aus `models.yml`, Umgebungsname-oder-Literal-Semantik)\n\n`models.yml` `apiKey`-Verhalten:\n\n- Der Wert wird zuerst als Umgebungsvariablenname behandelt.\n- Wenn keine Umgebungsvariable existiert, wird der literale String als Token verwendet.\n\nWenn `authHeader: true` und Anbieter-`apiKey` gesetzt ist, erhalten Modelle:\n\n- `Authorization: Bearer <aufgelöster-Schlüssel>` als Header eingefügt.\n\nSchlüssellose Anbieter:\n\n- Anbieter mit der Kennzeichnung `auth: none` werden als verfügbar ohne Anmeldedaten behandelt.\n- `getApiKey*` gibt für sie `kNoAuth` zurück.\n\n## Modellverfügbarkeit vs. alle Modelle\n\n- `getAll()` gibt die geladene Modell-Registry zurück (integriert + zusammengeführte benutzerdefinierte + erkannte).\n- `getAvailable()` filtert auf Modelle, die schlüssellos sind oder auflösbare Auth haben.\n\nEin Modell kann also in der Registry existieren, aber erst auswählbar sein, wenn Auth verfügbar ist.\n\n## Laufzeit-Modellauflösung\n\n### CLI und Muster-Parsing\n\n`model-resolver.ts` unterstützt:\n\n- Exakt `provider/modelId`\n- Exakte kanonische Modell-ID\n- Exakte Modell-ID (Anbieter wird abgeleitet)\n- Fuzzy-/Teilstring-Abgleich\n- Glob-Bereichsmuster in `--models` (z.B. `openai/*`, `*sonnet*`)\n- Optionales `:thinkingLevel`-Suffix (`off|minimal|low|medium|high|xhigh`)\n\n`--provider` ist veraltet; `--model` wird bevorzugt.\n\nAuflösungspriorität für exakte Selektoren:\n\n1. Exakt `provider/modelId` umgeht die Zusammenfassung\n2. Exakte kanonische ID wird über den kanonischen Index aufgelöst\n3. Exakte nackte konkrete ID funktioniert weiterhin\n4. Fuzzy- und Glob-Abgleich werden nach den exakten Pfaden ausgeführt\n\n### Priorität bei der initialen Modellauswahl\n\n`findInitialModel(...)` verwendet diese Reihenfolge:\n\n1. Expliziter CLI-Anbieter+Modell\n2. Erstes bereichsbezogenes Modell (wenn nicht fortgesetzt wird)\n3. Gespeicherter Standard-Anbieter/Modell\n4. Bekannte Anbieter-Standards (z.B. OpenAI/Anthropic/etc.) unter den verfügbaren Modellen\n5. Erstes verfügbares Modell\n\n### Rollenaliase und Einstellungen\n\nUnterstützte Modellrollen:\n\n- `default`, `smol`, `slow`, `plan`, `commit`\n\nRollenaliase wie `pi/smol` werden über `settings.modelRoles` erweitert. Jeder Rollenwert kann auch einen Thinking-Selektor wie `:minimal`, `:low`, `:medium` oder `:high` anhängen.\n\nWenn eine Rolle auf eine andere Rolle verweist, erbt das Zielmodell weiterhin normal und jedes explizite Suffix der verweisenden Rolle gewinnt für diese rollenspezifische Verwendung.\n\nVerwandte Einstellungen:\n\n- `modelRoles` (Record)\n- `enabledModels` (bereichsbezogene Musterliste)\n- `modelProviderOrder` (globale kanonische Anbieterpräzedenz)\n- `providers.kimiApiFormat` (`openai` oder `anthropic` Anfrageformat)\n- `providers.openaiWebsockets` (`auto|off|on` WebSocket-Präferenz für OpenAI Codex-Transport)\n\n`modelRoles` kann entweder speichern:\n\n- `provider/modelId` um eine konkrete Anbietervariante festzulegen\n- Eine kanonische ID wie `gpt-5.3-codex` um Anbieter-Zusammenfassung zu ermöglichen\n\nFür `enabledModels` und CLI `--models`:\n\n- Exakte kanonische IDs werden zu allen konkreten Varianten in dieser kanonischen Gruppe erweitert\n- Explizite `provider/modelId`-Einträge bleiben exakt\n- Globs und Fuzzy-Abgleiche operieren weiterhin auf konkreten Modellen\n\n## `/model` und `--list-models`\n\nBeide Oberflächen halten anbieter-präfixierte Modelle sichtbar und auswählbar.\n\nSie zeigen jetzt auch kanonische/zusammengefasste Modelle an:\n\n- `/model` enthält eine kanonische Ansicht neben den Anbieter-Tabs\n- `--list-models` gibt einen kanonischen Abschnitt plus die konkreten Anbieterzeilen aus\n\nDie Auswahl eines kanonischen Eintrags speichert den kanonischen Selektor. Die Auswahl einer Anbieterzeile speichert den expliziten `provider/modelId`.\n\n## Kontext-Promotion (Fallback-Ketten auf Modellebene)\n\nKontext-Promotion ist ein Überlauf-Wiederherstellungsmechanismus für Varianten mit kleinem Kontext (zum Beispiel `*-spark`), der automatisch zu einem Geschwistermodell mit größerem Kontext wechselt, wenn die API eine Anfrage mit einem Kontextlängenfehler ablehnt.\n\n### Auslöser und Reihenfolge\n\nWenn ein Turn mit einem Kontext-Überlauf-Fehler fehlschlägt (z.B. `context_length_exceeded`), versucht `AgentSession` eine Promotion **bevor** auf Komprimierung zurückgefallen wird:\n\n1. Wenn `contextPromotion.enabled` true ist, ein Promotionsziel auflösen (siehe unten).\n2. Wenn ein Ziel gefunden wird, zu diesem wechseln und die Anfrage erneut versuchen — keine Komprimierung nötig.\n3. Wenn kein Ziel verfügbar ist, zur Auto-Komprimierung auf dem aktuellen Modell übergehen.\n\n### Zielauswahl\n\nDie Auswahl ist modellgesteuert, nicht rollengesteuert:\n\n1. `currentModel.contextPromotionTarget` (falls konfiguriert)\n2. Kleinstes Modell mit größerem Kontext beim selben Anbieter + API\n\nKandidaten werden ignoriert, sofern keine Anmeldedaten aufgelöst werden können (`ModelRegistry.getApiKey(...)`).\n\n### OpenAI Codex WebSocket-Übergabe\n\nBeim Wechsel von/zu `openai-codex-responses` wird der Sitzungsanbieterstatus-Schlüssel `openai-codex-responses` vor dem Modellwechsel geschlossen. Dies verwirft den WebSocket-Transportstatus, sodass der nächste Turn sauber auf dem beförderten Modell startet.\n\n### Persistenzverhalten\n\nPromotion verwendet temporäres Umschalten (`setModelTemporary`):\n\n- wird als temporäre `model_change` in der Sitzungshistorie aufgezeichnet\n- schreibt die gespeicherte Rollenzuordnung nicht um\n\n### Konfiguration expliziter Fallback-Ketten\n\nKonfigurieren Sie den Fallback direkt in den Modell-Metadaten über `contextPromotionTarget`.\n\n`contextPromotionTarget` akzeptiert entweder:\n\n- `provider/model-id` (explizit)\n- `model-id` (innerhalb des aktuellen Anbieters aufgelöst)\n\nBeispiel (`models.yml`) für Spark -> Nicht-Spark beim selben Anbieter:\n\n```yaml\nproviders:\n  openai-codex:\n    modelOverrides:\n      gpt-5.3-codex-spark:\n        contextPromotionTarget: openai-codex/gpt-5.3-codex\n```\n\nDer integrierte Modellgenerator weist dies auch automatisch für `*-spark`-Modelle zu, wenn ein Basismodell beim selben Anbieter existiert.\n\n## Kompatibilitäts- und Routing-Felder\n\n`models.yml` unterstützt diese `compat`-Teilmenge:\n\n- `supportsStore`\n- `supportsDeveloperRole`\n- `supportsReasoningEffort`\n- `maxTokensField` (`max_completion_tokens` oder `max_tokens`)\n- `openRouterRouting.only` / `openRouterRouting.order`\n- `vercelGatewayRouting.only` / `vercelGatewayRouting.order`\n\nDiese werden von der OpenAI-Completions-Transportlogik verarbeitet und mit URL-basierter Auto-Erkennung kombiniert.\n\n## Praktische Beispiele\n\n### Lokaler OpenAI-kompatibler Endpunkt (ohne Auth)\n\n```yaml\nproviders:\n  local-openai:\n    baseUrl: http://127.0.0.1:8000/v1\n    auth: none\n    api: openai-completions\n    models:\n      - id: Qwen/Qwen2.5-Coder-32B-Instruct\n        name: Qwen 2.5 Coder 32B (local)\n```\n\n### Gehosteter Proxy mit umgebungsbasiertem Schlüssel\n\n```yaml\nproviders:\n  anthropic-proxy:\n    baseUrl: https://proxy.example.com/anthropic\n    apiKey: ANTHROPIC_PROXY_API_KEY\n    api: anthropic-messages\n    authHeader: true\n    models:\n      - id: claude-sonnet-4-20250514\n        name: Claude Sonnet 4 (Proxy)\n        reasoning: true\n        input: [text, image]\n```\n\n### Überschreibung der integrierten Anbieterroute + Modell-Metadaten\n\n```yaml\nproviders:\n  openrouter:\n    baseUrl: https://my-proxy.example.com/v1\n    headers:\n      X-Team: platform\n    modelOverrides:\n      anthropic/claude-sonnet-4:\n        name: Sonnet 4 (Corp)\n        compat:\n          openRouterRouting:\n            only: [anthropic]\n```\n\n## LiteLLM-Proxy Auto-Konfiguration\n\nWenn sowohl die Umgebungsvariablen `LITELLM_BASE_URL` als auch `LITELLM_API_KEY` gesetzt sind, verwaltet xcsh automatisch die `models.yml`-Konfiguration für den LiteLLM-Proxy.\n\n### Automatische Generierung beim ersten Start\n\nWenn `models.yml` nicht existiert und LiteLLM-Umgebungsvariablen erkannt werden, generiert xcsh sie automatisch:\n\n```yaml\n# Auto-generated by xcsh for LiteLLM proxy\n# API key resolved from LITELLM_API_KEY env var at runtime\nconfigVersion: 1\nproviders:\n  anthropic:\n    baseUrl: \"https://your-litellm-proxy.example.com/anthropic\"\n    apiKey: LITELLM_API_KEY\n```\n\nEine Standard-`config.yml` wird ebenfalls mit sinnvollen Bildanbieter-Einstellungen generiert.\n\n### Selbstheilung beim Start\n\nBei jedem Start führt `startupHealthCheck()` in der Modell-Registry die folgenden Prüfungen durch:\n\n| Bedingung | Aktion |\n|-----------|--------|\n| `models.yml` fehlt | Automatisch aus Umgebungsvariablen generieren |\n| `models.yml` beschädigt oder nicht parsebar | Backup als `.bak`, neu generieren |\n| `baseUrl` stimmt nicht mit `LITELLM_BASE_URL` überein | Backup als `.bak`, mit neuer URL neu generieren |\n| `configVersion` fehlt oder veraltet | Backup als `.bak`, mit aktueller Version neu generieren |\n| Konfiguration ist gesund | Keine Aktion |\n\nAlle Reparaturen erstellen `.bak`-Backups vor dem Überschreiben. Alle Operationen sind idempotent.\n\n### CLI-Befehl\n\n```bash\nxcsh setup litellm              # LiteLLM-Konfiguration generieren oder reparieren\nxcsh setup litellm --check      # Validierung ohne Schreibvorgang\nxcsh setup litellm --check --json  # Maschinenlesbare Validierungsausgabe\n```\n\n### Erforderliche Umgebungsvariablen\n\n| Variable | Zweck |\n|----------|-------|\n| `LITELLM_BASE_URL` | LiteLLM-Proxy-URL (z.B. `https://your-proxy.example.com`). Muss mit `http://` oder `https://` beginnen. |\n| `LITELLM_API_KEY` | API-Schlüssel für den Proxy. Wird namentlich in der generierten Konfiguration referenziert, zur Laufzeit aufgelöst. |\n\nWenn eine der Variablen nicht gesetzt ist, wird die Auto-Konfiguration stillschweigend übersprungen.\n\n### Konfigurations-Versionierung\n\nGenerierte Konfigurationen enthalten ein `configVersion`-Feld. Wenn sich das generierte Format in zukünftigen Releases ändert, erkennt xcsh veraltete Konfigurationen und aktualisiert sie automatisch (mit Backup).\n\n## Hinweis zu Legacy-Konsumenten\n\nDie meiste Modellkonfiguration fließt jetzt über `models.yml` via `ModelRegistry`.\n\nEin bemerkenswerter Legacy-Pfad bleibt bestehen: Die Anthropic-Auth-Auflösung für die Websuche liest weiterhin direkt `~/.xcsh/agent/models.json` in `src/web/search/auth.ts`.\n\nWenn Sie auf diesen spezifischen Pfad angewiesen sind, beachten Sie die JSON-Kompatibilität, bis dieses Modul migriert ist.\n\n## Fehlerverhalten\n\nWenn `models.yml` Schema- oder Validierungsprüfungen nicht besteht:\n\n- Wenn `LITELLM_BASE_URL` und `LITELLM_API_KEY` gesetzt sind, versucht die Startup-Health-Check eine automatische Reparatur (beschädigte Datei sichern, aus Umgebungsvariablen neu generieren). Wenn die Reparatur erfolgreich ist, lädt die Registry die reparierte Konfiguration neu.\n- Wenn eine automatische Reparatur nicht möglich ist (Umgebungsvariablen nicht gesetzt, Schreibfehler), arbeitet die Registry weiterhin mit integrierten Modellen.\n- Der Fehler wird über `ModelRegistry.getError()` bereitgestellt und in der UI/Benachrichtigungen angezeigt.\n",
	"de/providers/provider-streaming-internals.md": "---\ntitle: Interne Streaming-Mechanismen des Providers\ndescription: >-\n  Implementierung des Provider-Streamings mit SSE-Parsing, Token-Zählung und\n  Backpressure-Handling.\nsidebar:\n  order: 2\n  label: Interne Streaming-Mechanismen\ni18n:\n  sourceHash: a32ffa769c4d\n  translator: machine\n---\n\n# Interne Streaming-Mechanismen des Providers\n\nDieses Dokument erläutert, wie Token-/Werkzeug-Streaming in `@f5-sales-demo/pi-ai` normalisiert und anschließend über `@f5-sales-demo/pi-agent-core` sowie `coding-agent`-Sitzungsereignisse weitergeleitet wird.\n\n## Vollständiger Ablauf\n\n1. `streamSimple()` (`packages/ai/src/stream.ts`) ordnet generische Optionen zu und leitet an eine Provider-Stream-Funktion weiter.\n2. Provider-Stream-Funktionen (`anthropic.ts`, `openai-responses.ts`, `google.ts`) übersetzen provider-native Stream-Ereignisse in die einheitliche `AssistantMessageEvent`-Sequenz.\n3. Jeder Provider überträgt Ereignisse in `AssistantMessageEventStream` (`packages/ai/src/utils/event-stream.ts`), das Delta-Ereignisse drosselt und Folgendes bereitstellt:\n   - asynchrone Iteration für inkrementelle Aktualisierungen\n   - `result()` für das finale `AssistantMessage`\n4. `agentLoop` (`packages/agent/src/agent-loop.ts`) verarbeitet diese Ereignisse, ändert den laufenden Assistentenstatus und gibt `message_update`-Ereignisse mit dem rohen `assistantMessageEvent` aus.\n5. `AgentSession` (`packages/coding-agent/src/session/agent-session.ts`) abonniert Agent-Ereignisse, speichert Nachrichten, steuert Erweiterungs-Hooks und wendet Sitzungsverhalten an (Wiederholung, Komprimierung, TTSR, Streaming-Edit-Abbruchprüfungen).\n\n## Einheitlicher Stream-Vertrag in `@f5-sales-demo/pi-ai`\n\nAlle Provider geben dieselbe Struktur aus (`AssistantMessageEvent` in `packages/ai/src/types.ts`):\n\n- `start`\n- Inhaltsblock-Lebenszyklus-Triplets:\n  - Text: `text_start` → `text_delta`* → `text_end`\n  - Denken: `thinking_start` → `thinking_delta`* → `thinking_end`\n  - Werkzeugaufruf: `toolcall_start` → `toolcall_delta`* → `toolcall_end`\n- Abschlussereignis:\n  - `done` mit `reason: \"stop\" | \"length\" | \"toolUse\"`\n  - oder `error` mit `reason: \"aborted\" | \"error\"`\n\n`AssistantMessageEventStream` gewährleistet:\n\n- das finale Ergebnis wird durch das Abschlussereignis aufgelöst (`done` oder `error`)\n- Deltas werden gebündelt/gedrosselt (~50 ms)\n- gepufferte Deltas werden vor Nicht-Delta-Ereignissen und vor dem Abschluss geleert\n\n## Delta-Drosselung und Harmonisierungsverhalten\n\n`AssistantMessageEventStream` behandelt `text_delta`, `thinking_delta` und `toolcall_delta` als zusammenführbare Ereignisse:\n\n- gepufferte Deltas werden nur zusammengeführt, wenn **type + contentIndex** übereinstimmen\n- die Zusammenführung behält den neuesten `partial`-Snapshot\n- Nicht-Delta-Ereignisse erzwingen eine sofortige Leerung\n\nDies glättet hochfrequente Provider-Streams für TUI-/Ereignis-Consumer, stellt jedoch keine Provider-Backpressure dar: Provider produzieren weiterhin mit voller Geschwindigkeit, während der lokale Stream puffert.\n\n## Details zur Provider-Normalisierung\n\n## Anthropic (`anthropic-messages`)\n\nQuelle: `packages/ai/src/providers/anthropic.ts`\n\nNormalisierungspunkte:\n\n- `message_start` initialisiert die Nutzung (Eingabe-/Ausgabe-/Cache-Token)\n- `content_block_start` wird auf Text-/Denk-/Werkzeugaufruf-Starts abgebildet\n- `content_block_delta` wird abgebildet:\n  - `text_delta` → `text_delta`\n  - `thinking_delta` → `thinking_delta`\n  - `input_json_delta` → `toolcall_delta`\n  - `signature_delta` aktualisiert nur `thinkingSignature` (kein Ereignis)\n- `content_block_stop` gibt das entsprechende `*_end` aus\n- `message_delta.stop_reason` wird über `mapStopReason()` abgebildet\n\nStreaming der Werkzeugaufruf-Argumente:\n\n- jeder Werkzeugblock enthält internen `partialJson`\n- jedes JSON-Delta wird an `partialJson` angehängt\n- `arguments` werden bei jedem Delta über `parseStreamingJson()` neu geparst\n- `toolcall_end` parst einmal mehr, dann wird `partialJson` entfernt\n\n## OpenAI Responses (`openai-responses`)\n\nQuelle: `packages/ai/src/providers/openai-responses.ts`\n\nNormalisierungspunkte:\n\n- `response.output_item.added` startet Reasoning-/Text-/Funktionsaufruf-Blöcke\n- Reasoning-Summary-Ereignisse (`response.reasoning_summary_text.delta`) werden zu `thinking_delta`\n- Ausgabe-/Zurückweisungs-Deltas werden zu `text_delta`\n- `response.function_call_arguments.delta` wird zu `toolcall_delta`\n- `response.output_item.done` gibt `thinking_end` / `text_end` / `toolcall_end` aus\n- `response.completed` bildet Status auf Stop-Grund und Nutzung ab\n\nStreaming der Werkzeugaufruf-Argumente:\n\n- dasselbe `partialJson`-Akkumulationsmuster wie bei Anthropic\n- Provider, die nur `response.function_call_arguments.done` senden, füllen dennoch die finalen Argumente\n- Werkzeugaufruf-IDs werden als `\"<call_id>|<item_id>\"` normalisiert\n\n## Google Generative AI (`google-generative-ai`)\n\nQuelle: `packages/ai/src/providers/google.ts`\n\nNormalisierungspunkte:\n\n- iteriert `candidate.content.parts`\n- Textteile werden durch `isThinkingPart(part)` in Denken vs. Text aufgeteilt\n- Blockwechsel schließen den vorherigen Block, bevor ein neuer gestartet wird\n- `part.functionCall` wird als vollständiger Werkzeugaufruf behandelt (Start/Delta/Ende werden sofort ausgegeben)\n- Beendigungsgrund wird durch `mapStopReason()` aus `google-shared.ts` abgebildet\n\nStreaming der Werkzeugaufruf-Argumente:\n\n- Funktionsaufruf-Argumente kommen als strukturiertes Objekt an, nicht als inkrementeller JSON-Text\n- die Implementierung gibt ein synthetisches `toolcall_delta` mit `JSON.stringify(arguments)` aus\n- für Google ist in diesem Pfad kein partieller JSON-Parser erforderlich\n\n## Partielle Werkzeugaufruf-JSON-Akkumulation und -Wiederherstellung\n\nGemeinsames Verhalten für Anthropic/OpenAI Responses verwendet `parseStreamingJson()` (`packages/ai/src/utils/json-parse.ts`):\n\n1. `JSON.parse` versuchen\n2. Fallback auf `partial-json`-Parser für unvollständige Fragmente\n3. falls beide fehlschlagen, `{}` zurückgeben\n\nImplikationen:\n\n- fehlerhafte oder abgeschnittene Argument-Deltas führen nicht sofort zum Absturz der Stream-Verarbeitung\n- laufende `arguments` können vorübergehend `{}` sein\n- spätere gültige Deltas können strukturierte Argumente wiederherstellen, da das Parsing bei jedem Anhängen erneut versucht wird\n- das finale `toolcall_end` führt vor der Ausgabe einen weiteren Parse-Versuch durch\n\n## Stop-Gründe vs. Transport-/Laufzeitfehler\n\nProvider-Stop-Gründe werden auf normalisierte `stopReason` abgebildet:\n\n- Anthropic: `end_turn`→`stop`, `max_tokens`→`length`, `tool_use`→`toolUse`, Sicherheits-/Zurückweisungsfälle→`error`\n- OpenAI Responses: `completed`→`stop`, `incomplete`→`length`, `failed/cancelled`→`error`\n- Google: `STOP`→`stop`, `MAX_TOKENS`→`length`, Sicherheits-/Verbots-/fehlerhafte-Funktionsaufruf-Klassen→`error`\n\nFehlersemantiken sind in zwei Stufen aufgeteilt:\n\n1. **Modellabschluss-Semantik** (vom Provider gemeldeter Beendigungsgrund/-status)\n2. **Transport-/Laufzeitfehler** (Netzwerk-/Client-/Parser-/Abbruchausnahmen)\n\nFalls der Provider-Stream einen Fehler auslöst oder einen Fehler signalisiert, fängt jeder Provider-Wrapper diesen ab und gibt ein abschließendes `error`-Ereignis aus mit:\n\n- `stopReason = \"aborted\"` wenn das Abbruchsignal gesetzt ist\n- andernfalls `stopReason = \"error\"`\n- `errorMessage = formatErrorMessageWithRetryAfter(error)`\n\n## Verhalten bei fehlerhaften Chunks / SSE-Parse-Fehlern\n\nFür diese Provider-Pfade wird das Chunk-/SSE-Framing von Vendor-SDK-Streams verarbeitet (Anthropic SDK, OpenAI SDK, Google SDK). Dieser Code implementiert hier keinen eigenen SSE-Decoder.\n\nBeobachtetes Verhalten in der aktuellen Implementierung:\n\n- fehlerhafte Chunk-/SSE-Analyse auf SDK-Ebene führt zu einer Ausnahme oder einem Stream-`error`-Ereignis\n- der Provider-Wrapper wandelt dies in ein einheitliches abschließendes `error`-Ereignis um\n- kein provider-spezifisches Fortsetzen/Wiederholen innerhalb der Stream-Funktion selbst\n- übergeordnete Wiederholungen werden in der `AgentSession`-Auto-Retry-Logik verarbeitet (Nachrichtenebenen-Wiederholung, keine Stream-Chunk-Wiedergabe)\n\n## Abbruchgrenzen\n\nDer Abbruch ist mehrschichtig:\n\n- KI-Provider-Anfrage: `options.signal` wird in den Provider-Client-Stream-Aufruf übergeben.\n- Provider-Wrapper: nach der Stream-Schleife erzwingt ein abgebrochenes Signal den Fehlerpfad (`\"Request was aborted\"`).\n- Agent-Schleife: prüft `signal.aborted` vor der Verarbeitung jedes Provider-Ereignisses und kann eine abgebrochene Assistentennachricht aus dem aktuellen Teilstand synthetisieren.\n- Sitzungs-/Agent-Steuerungen: `AgentSession.abort()` -> `agent.abort()` -> gemeinsame Abbruch-Controller-Stornierung.\n\nDer Abbruch der Werkzeugausführung ist vom Abbruch des Modell-Streams getrennt:\n\n- Werkzeug-Runner verwenden `AbortSignal.any([agentSignal, steeringAbortSignal])`\n- Steuerungsunterbrechungen können die verbleibende Werkzeugausführung abbrechen, während bereits produzierte Werkzeugergebnisse erhalten bleiben\n\n## Backpressure-Grenzen\n\nEs gibt keinen harten Backpressure-Mechanismus zwischen dem Provider-SDK-Stream und nachgelagerten Consumern:\n\n- `EventStream` verwendet In-Memory-Warteschlangen ohne maximale Größe\n- Drosselung reduziert die UI-Aktualisierungsrate, verlangsamt jedoch nicht die Provider-Aufnahme\n- wenn Consumer erheblich zurückliegen, können sich wartende Ereignisse bis zum Abschluss ansammeln\n\nDas aktuelle Design bevorzugt Reaktionsfähigkeit und einfache Sortierung gegenüber einer Flusssteuerung mit begrenztem Puffer.\n\n## Wie Stream-Ereignisse als Agent-/Sitzungsereignisse erscheinen\n\n`agentLoop.streamAssistantResponse()` verbindet `AssistantMessageEvent` mit `AgentEvent`:\n\n- bei `start`: schiebt Platzhalter-Assistentennachricht ein und gibt `message_start` aus\n- bei Blockereignissen (`text_*`, `thinking_*`, `toolcall_*`): aktualisiert die letzte Assistentennachricht, gibt `message_update` mit rohem `assistantMessageEvent` aus\n- bei Abschluss (`done`/`error`): löst die finale Nachricht aus `response.result()` auf, gibt `message_end` aus\n\n`AgentSession` verarbeitet diese Ereignisse dann für sitzungsweite Verhaltensweisen:\n\n- TTSR beobachtet `message_update.assistantMessageEvent` auf `text_delta` und `toolcall_delta`\n- der Streaming-Edit-Schutz prüft `toolcall_delta`/`toolcall_end` bei `edit`-Aufrufen und kann frühzeitig abbrechen\n- die Persistenz schreibt finalisierte Nachrichten bei `message_end`\n- Auto-Retry prüft den Assistenten-`stopReason === \"error\"` sowie `errorMessage`-Heuristiken\n\n## Einheitliche vs. provider-spezifische Zuständigkeiten\n\nEinheitlich (gemeinsamer Vertrag):\n\n- Ereignisform (`AssistantMessageEvent`)\n- Extraktion des finalen Ergebnisses (`done`/`error`)\n- Delta-Drosselung und Zusammenführungsregeln\n- Agent-/Sitzungs-Ereignisweiterleitungsmodell\n\nProvider-spezifisch (nicht vollständig abstrahiert):\n\n- Upstream-Ereignistaxonomien und Abbildungslogik\n- Übersetzungstabellen für Stop-Gründe\n- Konventionen für Werkzeugaufruf-IDs\n- Semantik und Signaturen von Reasoning-/Denk-Blöcken\n- Semantik der Nutzungs-Token und Verfügbarkeitstiming\n- Nachrichtenkonvertierungseinschränkungen je API\n\n## Implementierungsdateien\n\n- [`../../ai/src/stream.ts`](../../packages/ai/src/stream.ts) — Provider-Weiterleitung, Options-Abbildung, API-Schlüssel-/Sitzungs-Verkabelung.\n- [`../../ai/src/utils/event-stream.ts`](../../packages/ai/src/utils/event-stream.ts) — generische Stream-Warteschlange und Assistenten-Delta-Drosselung.\n- [`../../ai/src/utils/json-parse.ts`](../../packages/ai/src/utils/json-parse.ts) — partielles JSON-Parsing für gestreamte Werkzeugargumente.\n- [`../../ai/src/providers/anthropic.ts`](../../packages/ai/src/providers/anthropic.ts) — Anthropic-Ereignisübersetzung und Werkzeug-JSON-Delta-Akkumulation.\n- [`../../ai/src/providers/openai-responses.ts`](../../packages/ai/src/providers/openai-responses.ts) — OpenAI-Responses-Ereignisübersetzung und Statusabbildung.\n- [`../../ai/src/providers/google.ts`](../../packages/ai/src/providers/google.ts) — Gemini-Stream-Chunk-zu-Block-Übersetzung.\n- [`../../ai/src/providers/google-shared.ts`](../../packages/ai/src/providers/google-shared.ts) — Gemini-Beendigungsgrund-Abbildung und gemeinsame Konvertierungsregeln.\n- [`../../agent/src/agent-loop.ts`](../../packages/agent/src/agent-loop.ts) — Provider-Stream-Verarbeitung und `message_update`-Brücke.\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — sitzungsweite Verarbeitung von Streaming-Aktualisierungen, Abbruch, Wiederholung und Persistenz.\n",
	"de/providers/python-repl.md": "---\ntitle: Python-Werkzeug und IPython-Laufzeit\ndescription: >-\n  Python-REPL-Werkzeug-Laufzeit mit IPython-Kernel-Verwaltung, Ausführung und\n  Ausgabeerfassung.\nsidebar:\n  order: 3\n  label: Python & IPython\ni18n:\n  sourceHash: 70f0a034ecef\n  translator: machine\n---\n\n# Python-Werkzeug und IPython-Laufzeit\n\nDieses Dokument beschreibt den aktuellen Python-Ausführungs-Stack in `packages/coding-agent`.\nEs behandelt Werkzeugverhalten, Kernel-/Gateway-Lebenszyklus, Umgebungsbehandlung, Ausführungssemantik, Ausgabedarstellung und betriebliche Fehlermodi.\n\n## Geltungsbereich und wichtige Dateien\n\n- Werkzeugoberfläche: `src/tools/python.ts`\n- Sitzungs-/aufrufbezogene Kernel-Orchestrierung: `src/ipy/executor.ts`\n- Kernel-Protokoll + Gateway-Integration: `src/ipy/kernel.ts`\n- Gemeinsamer lokaler Gateway-Koordinator: `src/ipy/gateway-coordinator.ts`\n- Interaktiver Renderer für benutzerausgelöste Python-Ausführungen: `src/modes/components/python-execution.ts`\n- Laufzeit-/Umgebungsfilterung und Python-Auflösung: `src/ipy/runtime.ts`\n\n## Was das Python-Werkzeug ist\n\nDas `python`-Werkzeug führt eine oder mehrere Python-Zellen über einen durch Jupyter Kernel Gateway unterstützten Kernel aus (nicht durch direktes Starten von `python -c` pro Zelle).\n\nWerkzeugparameter:\n\n```ts\n{\n  cells: Array<{ code: string; title?: string }>;\n  timeout?: number; // Sekunden, begrenzt auf 1..600, Standard 30\n  cwd?: string;\n  reset?: boolean; // Kernel nur vor der ersten Zelle zurücksetzen\n}\n```\n\nDas Werkzeug ist `concurrency = \"exclusive\"` für eine Sitzung, sodass Aufrufe sich nicht überschneiden.\n\n## Gateway-Lebenszyklus\n\n### Modi\n\nEs gibt zwei Gateway-Pfade:\n\n1. **Externer Gateway** (`PI_PYTHON_GATEWAY_URL` gesetzt)\n   - Verwendet die konfigurierte URL direkt.\n   - Optionale Authentifizierung mit `PI_PYTHON_GATEWAY_TOKEN`.\n   - Es wird kein lokaler Gateway-Prozess gestartet oder verwaltet.\n\n2. **Lokaler gemeinsamer Gateway** (Standardpfad)\n   - Verwendet einen einzigen gemeinsamen Prozess, koordiniert unter `~/.xcsh/agent/python-gateway`.\n   - Metadatendatei: `gateway.json`\n   - Sperrdatei: `gateway.lock`\n   - Startbefehl:\n     - `python -m kernel_gateway`\n     - gebunden an `127.0.0.1:<allocated-port>`\n     - Systemdiagnose beim Start: `GET /api/kernelspecs`\n\n### Koordination des lokalen gemeinsamen Gateways\n\n`acquireSharedGateway()`:\n\n- Setzt eine Dateisperre (`gateway.lock`) mit Heartbeat.\n- Verwendet `gateway.json` erneut, wenn PID aktiv ist und die Systemdiagnose bestanden wird.\n- Bereinigt veraltete Informationen/PIDs bei Bedarf.\n- Startet einen neuen Gateway, wenn kein funktionsfähiger vorhanden ist.\n\n`releaseSharedGateway()` ist derzeit eine No-Op-Funktion (Kernel-Herunterfahren beendet den gemeinsamen Gateway nicht).\n\n`shutdownSharedGateway()` beendet den gemeinsamen Prozess explizit und löscht die Gateway-Metadaten.\n\n### Wichtige Einschränkung\n\n`python.sharedGateway=false` wird beim Kernel-Start abgelehnt:\n\n- Fehler: `Shared Python gateway required; local gateways are disabled`\n- Es gibt keinen prozessbezogenen nicht gemeinsamen lokalen Gateway-Modus.\n\n## Kernel-Lebenszyklus\n\nJede Ausführung verwendet einen Kernel, der über `POST /api/kernels` am ausgewählten Gateway erstellt wird.\n\nKernel-Startsequenz:\n\n1. Verfügbarkeitsprüfung (`checkPythonKernelAvailability`)\n2. Kernel erstellen (`/api/kernels`)\n3. WebSocket öffnen (`/api/kernels/:id/channels`)\n4. Kernel-Umgebung initialisieren (`cwd`, Umgebungsvariablen, `sys.path`)\n5. `PYTHON_PRELUDE` ausführen\n6. Erweiterungsmodule laden aus:\n   - Benutzer: `~/.xcsh/agent/modules/*.py`\n   - Projekt: `<cwd>/.xcsh/modules/*.py` (überschreibt gleichnamige Benutzermodule)\n\nKernel-Herunterfahren:\n\n- Löscht den Remote-Kernel über `DELETE /api/kernels/:id`\n- Schließt den WebSocket\n- Ruft den gemeinsamen Gateway-Freigabe-Hook auf (derzeit No-Op)\n\n## Semantik der Sitzungsbeibehaltung\n\n`python.kernelMode` steuert die Kernel-Wiederverwendung:\n\n- `session` (Standard)\n  - Verwendet Kernel-Sitzungen wieder, die nach Sitzungsidentität + cwd verschlüsselt sind.\n  - Die Ausführung wird pro Sitzung über eine Warteschlange serialisiert.\n  - Inaktive Sitzungen werden nach 5 Minuten entfernt.\n  - Maximal 4 Sitzungen; die älteste wird bei Überschreitung entfernt.\n  - Heartbeat-Prüfungen erkennen ausgefallene Kernel.\n  - Automatischer Neustart einmal erlaubt; wiederholter Absturz => schwerwiegender Fehler.\n\n- `per-call`\n  - Erstellt einen neuen Kernel für jede Ausführungsanfrage.\n  - Fährt den Kernel nach der Anfrage herunter.\n  - Keine aufrufübergreifende Zustandsbeibehaltung.\n\n### Mehrzellenverhalten in einem einzelnen Werkzeugaufruf\n\nZellen werden sequenziell in derselben Kernel-Instanz für diesen Werkzeugaufruf ausgeführt.\n\nWenn eine Zwischenzelle fehlschlägt:\n\n- Der Status früherer Zellen verbleibt im Speicher.\n- Das Werkzeug gibt einen gezielten Fehler zurück, der angibt, welche Zelle fehlgeschlagen ist.\n- Spätere Zellen werden nicht ausgeführt.\n\n`reset=true` gilt nur für die erste Zellenausführung in diesem Aufruf.\n\n## Umgebungsfilterung und Laufzeitauflösung\n\nDie Umgebung wird vor dem Starten der Gateway-/Kernel-Laufzeit gefiltert:\n\n- Die Zulassungsliste enthält Kernvariablen wie `PATH`, `HOME`, Locale-Variablen, `VIRTUAL_ENV`, `PYTHONPATH` usw.\n- Zulassungspräfixe: `LC_`, `XDG_`, `PI_`\n- Die Sperrliste entfernt gängige API-Schlüssel (OpenAI/Anthropic/Gemini/usw.)\n\nReihenfolge der Laufzeitauswahl:\n\n1. Aktives/gefundenes venv (`VIRTUAL_ENV`, dann `<cwd>/.venv`, `<cwd>/venv`)\n2. Verwaltetes venv unter `~/.xcsh/python-env`\n3. `python` oder `python3` im PATH\n\nWenn ein venv ausgewählt wird, wird sein bin/Scripts-Pfad dem `PATH` vorangestellt.\n\nDie Kernel-Umgebungsinitialisierung in Python:\n\n- `os.chdir(cwd)`\n- Fügt die bereitgestellte Umgebungszuordnung in `os.environ` ein\n- Stellt sicher, dass cwd in `sys.path` enthalten ist\n\n## Werkzeugverfügbarkeit und Modusauswahl\n\n`python.toolMode` (Standard `both`) + optionale `PI_PY`-Überschreibung steuert die Bereitstellung:\n\n- `ipy-only`\n- `bash-only`\n- `both`\n\nAkzeptierte `PI_PY`-Werte:\n\n- `0` / `bash` -> `bash-only`\n- `1` / `py` -> `ipy-only`\n- `mix` / `both` -> `both`\n\nWenn die Python-Vorabprüfung fehlschlägt, wird die Werkzeugerstellung für diese Sitzung auf bash-only herabgestuft.\n\n## Ausführungsablauf und Abbruch/Timeout\n\n### Timeout auf Werkzeugebene\n\nDer `python`-Werkzeug-Timeout ist in Sekunden angegeben, Standard 30, begrenzt auf `1..600`.\n\nDas Werkzeug kombiniert:\n\n- Abbruchsignal des Aufrufers\n- Timeout-Abbruchsignal\n\nmit `AbortSignal.any(...)`.\n\n### Abbruch der Kernel-Ausführung\n\nBei Abbruch/Timeout:\n\n- Die Ausführung wird als abgebrochen markiert.\n- Ein Kernel-Interrupt wird über REST (`POST /interrupt`) und den Steuerkanal `interrupt_request` versucht.\n- Das Ergebnis enthält `cancelled=true`.\n- Der Timeout-Pfad versieht die Ausgabe mit dem Hinweis `Command timed out after <n> seconds`.\n\n### stdin-Verhalten\n\nInteraktives stdin wird nicht unterstützt.\n\nWenn der Kernel `input_request` ausgibt:\n\n- Das Werkzeug setzt `stdinRequested=true`\n- Gibt einen erklärenden Text aus\n- Sendet eine leere `input_reply`\n- Die Ausführung wird auf Executor-Ebene als Fehler behandelt\n\n## Ausgabeerfassung und -darstellung\n\n### Erfasste Ausgabeklassen\n\nAus Kernel-Nachrichten:\n\n- `stream` -> einfache Textblöcke\n- `display_data`/`execute_result` -> Verarbeitung umfangreicher Anzeigen\n- `error` -> Traceback-Text\n- benutzerdefinierter MIME-Typ `application/x-xcsh-status` -> strukturierte Statusereignisse\n\nMIME-Vorrangordnung bei der Anzeige:\n\n1. `text/markdown`\n2. `text/plain`\n3. `text/html` (in einfaches Markdown konvertiert)\n\nZusätzlich als strukturierte Ausgaben erfasst:\n\n- `application/json` -> JSON-Baumdaten\n- `image/png` -> Bild-Nutzdaten\n- `application/x-xcsh-status` -> Statusereignisse\n\n### Speicherung und Kürzung\n\nDie Ausgabe wird durch `OutputSink` gestreamt und kann im Artefaktspeicher gespeichert werden.\n\nWerkzeugergebnisse können Kürzungsmetadaten und `artifact://<id>` für die vollständige Ausgabewiederherstellung enthalten.\n\n### Renderer-Verhalten\n\n- Werkzeug-Renderer (`python.ts`):\n  - zeigt Code-Zellenblöcke mit zellenspezifischem Status an\n  - Vorschau im eingeklappten Zustand zeigt standardmäßig 10 Zeilen\n  - unterstützt den erweiterten Modus für vollständige Ausgabe und umfangreichere Statusdetails\n- Interaktiver Renderer (`python-execution.ts`):\n  - wird für benutzerausgelöste Python-Ausführung in der TUI verwendet\n  - Vorschau im eingeklappten Zustand zeigt standardmäßig 20 Zeilen\n  - begrenzt sehr lange einzelne Zeilen auf 4000 Zeichen für sichere Anzeige\n  - zeigt Abbruch-/Fehler-/Kürzungshinweise an\n\n## Unterstützung externer Gateways\n\nSetzen Sie:\n\n```bash\nexport PI_PYTHON_GATEWAY_URL=\"http://127.0.0.1:8888\"\n# Optional:\nexport PI_PYTHON_GATEWAY_TOKEN=\"...\"\n```\n\nVerhaltensunterschiede gegenüber dem lokalen gemeinsamen Gateway:\n\n- Keine lokalen Gateway-Sperr-/Informationsdateien\n- Kein lokales Prozessstarten/-beenden\n- Systemdiagnosen und Kernel-CRUD werden gegen den externen Endpunkt ausgeführt\n- Authentifizierungsfehler werden mit explizitem Token-Hinweis angezeigt\n\n## Betriebliche Fehlerbehebung (aktuelle Fehlermodi)\n\n- **Python-Werkzeug nicht verfügbar**\n  - Prüfen Sie `python.toolMode` / `PI_PY`.\n  - Wenn die Vorabprüfung fehlschlägt, fällt die Laufzeit auf bash-only zurück.\n\n- **Kernel-Verfügbarkeitsfehler**\n  - Der lokale Modus erfordert, dass sowohl `kernel_gateway` als auch `ipykernel` in der aufgelösten Python-Laufzeit importierbar sind.\n  - Installieren mit:\n\n    ```bash\n    python -m pip install jupyter_kernel_gateway ipykernel\n    ```\n\n- **`python.sharedGateway=false` verursacht Startfehler**\n  - Dies ist mit der aktuellen Implementierung zu erwarten.\n\n- **Authentifizierungs-/Erreichbarkeitsfehler beim externen Gateway**\n  - 401/403 -> `PI_PYTHON_GATEWAY_TOKEN` setzen.\n  - Timeout/nicht erreichbar -> URL/Netzwerk und Gateway-Zustand überprüfen.\n\n- **Ausführung hängt und läuft in Timeout**\n  - Erhöhen Sie den Werkzeug-`timeout` (max. 600 s), wenn die Arbeitslast legitim ist.\n  - Bei hängendem Code löst der Abbruch einen Kernel-Interrupt aus, aber der Benutzercode muss möglicherweise noch refaktoriert werden.\n\n- **stdin/Eingabeaufforderungen in Python-Code**\n  - `input()` wird in diesem Laufzeitpfad nicht interaktiv unterstützt; übergeben Sie Daten programmatisch.\n\n- **Ressourcenerschöpfung (`EMFILE` / zu viele offene Dateien)**\n  - Der Sitzungsmanager löst eine Wiederherstellung des gemeinsamen Gateways aus (Sitzungsabbau + Neustart des gemeinsamen Gateways).\n\n- **Fehler beim Arbeitsverzeichnis**\n  - Das Werkzeug prüft vor der Ausführung, ob `cwd` vorhanden und ein Verzeichnis ist.\n\n## Relevante Umgebungsvariablen\n\n- `PI_PY` — Überschreibung der Werkzeugbereitstellung (Zuordnung `bash-only`/`ipy-only`/`both` wie oben)\n- `PI_PYTHON_GATEWAY_URL` — externen Gateway verwenden\n- `PI_PYTHON_GATEWAY_TOKEN` — optionales Authentifizierungstoken für externen Gateway\n- `PI_PYTHON_SKIP_CHECK=1` — Python-Vorab-/Warmprüfungen umgehen\n- `PI_PYTHON_IPC_TRACE=1` — Kernel-IPC-Sende-/Empfangsspuren protokollieren\n- `PI_DEBUG_STARTUP=1` — Debug-Markierungen für Startphasen ausgeben\n",
	"de/runtime-tools/bash-tool-runtime.md": "---\ntitle: Bash-Tool-Laufzeitumgebung\ndescription: >-\n  Bash-Tool-Laufzeitumgebung mit Shell-Prozessverwaltung, Sandboxing, Timeout\n  und Ausgabe-Streaming.\nsidebar:\n  order: 1\n  label: Bash-Tool\ni18n:\n  sourceHash: 18b12aa5dbd5\n  translator: machine\n---\n\n# Bash-Tool-Laufzeitumgebung\n\nDieses Dokument beschreibt den Laufzeitpfad des **`bash`-Tools**, der von Agent-Tool-Aufrufen verwendet wird – von der Befehlsnormalisierung über die Ausführung, Trunkierung/Artefakte bis hin zum Rendering.\n\nEs weist zudem auf Verhaltensunterschiede im interaktiven TUI, im Print-Modus, im RPC-Modus und bei der vom Benutzer initiierten Bang-(`!`)-Shell-Ausführung hin.\n\n## Geltungsbereich und Laufzeitoberflächen\n\nEs gibt zwei verschiedene Bash-Ausführungsoberflächen in coding-agent:\n\n1. **Tool-Aufruf-Oberfläche** (`toolName: \"bash\"`): wird verwendet, wenn das Modell das Bash-Tool aufruft.\n   - Einstiegspunkt: `BashTool.execute()`.\n2. **Benutzer-Bang-Befehl-Oberfläche** (`!cmd` aus interaktiver Eingabe oder RPC-`bash`-Befehl): Hilfspfad auf Session-Ebene.\n   - Einstiegspunkt: `AgentSession.executeBash()`.\n\nBeide verwenden letztendlich `executeBash()` in `src/exec/bash-executor.ts` für die Nicht-PTY-Ausführung, aber nur der Tool-Aufruf-Pfad führt Normalisierung/Interception und Tool-Renderer-Logik aus.\n\n## End-to-End-Tool-Aufruf-Pipeline\n\n## 1) Eingabenormalisierung und Parameter-Zusammenführung\n\n`BashTool.execute()` normalisiert zunächst den Rohbefehl über `normalizeBashCommand()`:\n\n- extrahiert nachgestelltes `| head -n N`, `| head -N`, `| tail -n N`, `| tail -N` in strukturierte Limits,\n- entfernt führende/nachgestellte Leerzeichen,\n- behält interne Leerzeichen bei.\n\nAnschließend werden extrahierte Limits mit expliziten Tool-Argumenten zusammengeführt:\n\n- explizite `head`/`tail`-Argumente überschreiben extrahierte Werte,\n- extrahierte Werte dienen nur als Fallback.\n\n### Einschränkung\n\nKommentare in `bash-normalize.ts` erwähnen das Entfernen von `2>&1`, aber die aktuelle Implementierung entfernt es nicht. Das Laufzeitverhalten ist dennoch korrekt (stdout/stderr werden bereits zusammengeführt), aber das Normalisierungsverhalten ist enger als die Kommentare vermuten lassen.\n\n## 2) Optionale Interception (Blockierte-Befehle-Pfad)\n\nWenn `bashInterceptor.enabled` aktiv ist, lädt `BashTool` Regeln aus den Einstellungen und führt `checkBashInterception()` gegen den normalisierten Befehl aus.\n\nInterception-Verhalten:\n\n- ein Befehl wird **nur dann** blockiert, wenn:\n  - eine Regex-Regel übereinstimmt, und\n  - das vorgeschlagene Tool in `ctx.toolNames` vorhanden ist.\n- ungültige Regex-Regeln werden stillschweigend übersprungen.\n- bei Blockierung wirft `BashTool` einen `ToolError` mit der Nachricht:\n  - `Blocked: ...`\n  - der ursprüngliche Befehl ist enthalten.\n\nStandard-Regelmuster (im Code definiert) zielen auf häufige Fehlverwendungen ab:\n\n- Dateileser (`cat`, `head`, `tail`, ...)\n- Suchwerkzeuge (`grep`, `rg`, ...)\n- Dateifinder (`find`, `fd`, ...)\n- In-Place-Editoren (`sed -i`, `perl -i`, `awk -i inplace`)\n- Shell-Umleitungsschreibvorgänge (`echo ... > file`, Heredoc-Umleitung)\n\n### Einschränkung\n\n`InterceptionResult` enthält `suggestedTool`, aber `BashTool` gibt derzeit nur den Nachrichtentext aus (kein strukturiertes Suggested-Tool-Feld in `details`).\n\n## 3) CWD-Validierung und Timeout-Begrenzung\n\n`cwd` wird relativ zum Session-cwd aufgelöst (`resolveToCwd`) und dann über `stat` validiert:\n\n- fehlender Pfad -> `ToolError(\"Working directory does not exist: ...\")`\n- kein Verzeichnis -> `ToolError(\"Working directory is not a directory: ...\")`\n\nDer Timeout wird auf `[1, 3600]` Sekunden begrenzt und in Millisekunden umgerechnet.\n\n## 4) Artefakt-Zuweisung\n\nVor der Ausführung weist das Tool einen Artefaktpfad/-ID (Best-Effort) für die Speicherung trunkierter Ausgaben zu.\n\n- ein Fehler bei der Artefakt-Zuweisung ist nicht-fatal (die Ausführung wird ohne Artefakt-Spilldatei fortgesetzt),\n- Artefakt-ID/Pfad werden in den Ausführungspfad übergeben, um die vollständige Ausgabe bei Trunkierung zu persistieren.\n\n## 5) PTY- vs. Nicht-PTY-Ausführungsauswahl\n\n`BashTool` wählt PTY-Ausführung nur, wenn alle Bedingungen erfüllt sind:\n\n- `bash.virtualTerminal === \"on\"`\n- `PI_NO_PTY !== \"1\"`\n- der Tool-Kontext hat eine UI (`ctx.hasUI === true` und `ctx.ui` gesetzt)\n\nAndernfalls wird die nicht-interaktive `executeBash()`-Funktion verwendet.\n\nDas bedeutet, dass der Print-Modus und Nicht-UI-RPC/Tool-Kontexte immer Nicht-PTY verwenden.\n\n## Nicht-interaktive Ausführungs-Engine (`executeBash`)\n\n## Shell-Session-Wiederverwendungsmodell\n\n`executeBash()` cached native `Shell`-Instanzen in einer prozessglobalen Map, die anhand folgender Schlüssel indiziert wird:\n\n- Shell-Pfad,\n- konfiguriertes Befehlspräfix,\n- Snapshot-Pfad,\n- serialisierte Shell-Umgebung,\n- optionaler Agent-Session-Schlüssel.\n\nFür Ausführungen auf Session-Ebene übergibt `AgentSession.executeBash()` den Parameter `sessionKey: this.sessionId`, wodurch die Wiederverwendung pro Session isoliert wird.\n\nDer Tool-Aufruf-Pfad übergibt **keinen** `sessionKey`, sodass der Wiederverwendungsbereich auf Shell-Konfiguration/Snapshot/Umgebung basiert.\n\n## Shell-Konfiguration und Snapshot-Verhalten\n\nBei jedem Aufruf lädt der Executor die Shell-Konfiguration aus den Einstellungen (`shell`, `env`, optionales `prefix`).\n\nWenn die ausgewählte Shell `bash` enthält, wird `getOrCreateSnapshot()` versucht:\n\n- der Snapshot erfasst Aliase/Funktionen/Optionen aus der Benutzer-RC,\n- die Snapshot-Erstellung erfolgt nach dem Best-Effort-Prinzip,\n- bei Fehler wird auf keinen Snapshot zurückgefallen.\n\nWenn `prefix` konfiguriert ist, wird der Befehl zu:\n\n```text\n<prefix> <command>\n```\n\n## Streaming und Abbruch\n\n`Shell.run()` streamt Chunks an einen Callback. Der Executor leitet jeden Chunk an `OutputSink` und einen optionalen `onChunk`-Callback weiter.\n\nAbbruch:\n\n- ein abgebrochenes Signal löst `shellSession.abort(...)` aus,\n- ein Timeout aus dem nativen Ergebnis wird auf `cancelled: true` + Annotationstext abgebildet,\n- ein expliziter Abbruch gibt ebenfalls `cancelled: true` + Annotation zurück.\n\nInnerhalb des Executors wird bei Timeout/Abbruch keine Exception geworfen; er gibt ein strukturiertes `BashResult` zurück und überlässt dem Aufrufer die Fehlersemantik.\n\n## Interaktiver PTY-Pfad (`runInteractiveBashPty`)\n\nWenn PTY aktiviert ist, führt das Tool `runInteractiveBashPty()` aus, das eine Overlay-Konsolen-Komponente öffnet und eine native `PtySession` steuert.\n\nVerhaltens-Highlights:\n\n- xterm-headless virtuelles Terminal rendert den Viewport im Overlay,\n- Tastatureingaben werden normalisiert (einschließlich Kitty-Sequenzen und Application-Cursor-Modus-Behandlung),\n- `esc` während der Ausführung beendet die PTY-Session,\n- Terminal-Größenänderungen werden an das PTY weitergegeben (`session.resize(cols, rows)`).\n\nStandardmäßige Umgebungs-Härtungen werden für unbeaufsichtigte Ausführungen injiziert:\n\n- Pager deaktiviert (`PAGER=cat`, `GIT_PAGER=cat`, etc.),\n- Editor-Eingabeaufforderungen deaktiviert (`GIT_EDITOR=true`, `EDITOR=true`, ...),\n- Terminal-/Authentifizierungs-Eingabeaufforderungen reduziert (`GIT_TERMINAL_PROMPT=0`, `SSH_ASKPASS=/usr/bin/false`, `CI=1`),\n- Paketmanager-/Tool-Automatisierungsflags für nicht-interaktives Verhalten.\n\nPTY-Ausgaben werden normalisiert (`CRLF`/`CR` zu `LF`, `sanitizeText`) und in `OutputSink` geschrieben, einschließlich Artefakt-Spill-Unterstützung.\n\nBei PTY-Start-/Laufzeitfehlern erhält der Sink eine `PTY error: ...`-Zeile und der Befehl wird mit undefiniertem Exit-Code finalisiert.\n\n## Ausgabebehandlung: Streaming, Trunkierung, Artefakt-Spill\n\nSowohl PTY- als auch Nicht-PTY-Pfade verwenden `OutputSink`.\n\n## OutputSink-Semantik\n\n- hält einen In-Memory-UTF-8-sicheren Tail-Puffer (`DEFAULT_MAX_BYTES`, derzeit 50KB),\n- verfolgt die insgesamt gesehenen Bytes/Zeilen,\n- wenn ein Artefaktpfad existiert und die Ausgabe überläuft (oder die Datei bereits aktiv ist), wird der vollständige Stream in die Artefaktdatei geschrieben,\n- wenn der Speicherschwellenwert überschritten wird, wird der In-Memory-Puffer auf den Tail gekürzt (UTF-8-Grenz-sicher),\n- markiert `truncated`, wenn ein Überlauf/Datei-Spill auftritt.\n\n`dump()` gibt zurück:\n\n- `output` (möglicherweise mit annotiertem Präfix),\n- `truncated`,\n- `totalLines/totalBytes`,\n- `outputLines/outputBytes`,\n- `artifactId`, falls eine Artefaktdatei aktiv war.\n\n### Einschränkung bei langer Ausgabe\n\nDie Laufzeit-Trunkierung basiert auf dem Byte-Schwellenwert in `OutputSink` (standardmäßig 50KB). In diesem Codepfad wird kein hartes 2000-Zeilen-Limit erzwungen.\n\n## Live-Tool-Updates\n\nFür die Nicht-PTY-Ausführung verwendet `BashTool` einen separaten `TailBuffer` für partielle Updates und gibt `onUpdate`-Snapshots aus, während der Befehl läuft.\n\nFür die PTY-Ausführung wird das Live-Rendering durch ein benutzerdefiniertes UI-Overlay gehandhabt, nicht durch `onUpdate`-Text-Chunks.\n\n## Ergebnisformung, Metadaten und Fehlerzuordnung\n\nNach der Ausführung:\n\n1. `cancelled`-Behandlung:\n   - wenn das Abort-Signal abgebrochen ist -> wirft `ToolAbortError` (Abbruch-Semantik),\n   - andernfalls -> wirft `ToolError` (wird als Tool-Fehler behandelt).\n2. PTY `timedOut` -> wirft `ToolError`.\n3. Head/Tail-Filter auf den finalen Ausgabetext anwenden (`applyHeadTail`, Head dann Tail).\n4. leere Ausgabe wird zu `(no output)`.\n5. Trunkierungs-Metadaten über `toolResult(...).truncationFromSummary(result, { direction: \"tail\" })` anhängen.\n6. Exit-Code-Zuordnung:\n   - fehlender Exit-Code -> `ToolError(\"... missing exit status\")`\n   - Exit-Code ungleich Null -> `ToolError(\"... Command exited with code N\")`\n   - Exit-Code Null -> Erfolgsergebnis.\n\nErfolgs-Payload-Struktur:\n\n- `content`: Textausgabe,\n- `details.meta.truncation` bei Trunkierung, einschließlich:\n  - `direction`, `truncatedBy`, Gesamt-/Ausgabe-Zeilen+Byte-Zähler,\n  - `shownRange`,\n  - `artifactId` wenn verfügbar.\n\nDa integrierte Tools mit `wrapToolWithMetaNotice()` umschlossen werden, wird der Trunkierungshinweis-Text automatisch an den finalen Textinhalt angehängt (zum Beispiel: `Full: artifact://<id>`).\n\n## Rendering-Pfade\n\n## Tool-Aufruf-Renderer (`bashToolRenderer`)\n\n`bashToolRenderer` wird für Tool-Aufruf-Nachrichten (`toolCall` / `toolResult`) verwendet:\n\n- der eingeklappte Modus zeigt eine visuell zeilentrunkierte Vorschau,\n- der ausgeklappte Modus zeigt den gesamten derzeit verfügbaren Ausgabetext,\n- die Warnzeile enthält den Trunkierungsgrund und `artifact://<id>` bei Trunkierung,\n- der Timeout-Wert (aus den Argumenten) wird in der Fußzeilen-Metadatenzeile angezeigt.\n\n### Einschränkung: Vollständige Artefakt-Erweiterung\n\n`BashRenderContext` hat `isFullOutput`, aber der aktuelle Renderer-Kontext-Builder setzt es nicht für Bash-Tool-Ergebnisse. Die erweiterte Ansicht verwendet weiterhin den bereits im Ergebnisinhalt vorhandenen Text (Tail-/trunkierte Ausgabe), es sei denn, ein anderer Aufrufer stellt den vollständigen Artefaktinhalt bereit.\n\n## Benutzer-Bang-Befehl-Komponente (`BashExecutionComponent`)\n\n`BashExecutionComponent` ist für Benutzer-`!`-Befehle im interaktiven Modus gedacht (nicht für Modell-Tool-Aufrufe):\n\n- streamt Chunks live,\n- die eingeklappte Vorschau behält die letzten 20 logischen Zeilen,\n- Zeilenbegrenzung bei 4000 Zeichen pro Zeile,\n- zeigt Trunkierungs- und Artefaktwarnungen an, wenn Metadaten vorhanden sind,\n- markiert abgebrochene/Fehler-/Exit-Zustände separat.\n\nDiese Komponente wird von `CommandController.handleBashCommand()` verdrahtet und von `AgentSession.executeBash()` gespeist.\n\n## Modusspezifische Verhaltensunterschiede\n\n| Oberfläche                       | Einstiegspfad                                         | PTY-fähig                                                            | Live-Ausgabe-UX                                                                | Fehlermeldung                                           |\n| -------------------------------- | ----------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------- |\n| Interaktiver Tool-Aufruf         | `BashTool.execute`                                    | Ja, wenn `bash.virtualTerminal=on` und UI vorhanden und `PI_NO_PTY!=1` | PTY-Overlay (interaktiv) oder gestreamte Tail-Updates                          | Tool-Fehler werden zu `toolResult.isError`              |\n| Print-Modus Tool-Aufruf          | `BashTool.execute`                                    | Nein (kein UI-Kontext)                                               | Kein TUI-Overlay; Ausgabe erscheint im Event-Stream/finalen Assistenten-Textfluss | Gleiche Tool-Fehler-Zuordnung                           |\n| RPC Tool-Aufruf (Agent-Tooling)  | `BashTool.execute`                                    | Üblicherweise keine UI -> Nicht-PTY                                  | Strukturierte Tool-Events/Ergebnisse                                           | Gleiche Tool-Fehler-Zuordnung                           |\n| Interaktiver Bang-Befehl (`!`)   | `AgentSession.executeBash` + `BashExecutionComponent` | Nein (verwendet Executor direkt)                                     | Dedizierte Bash-Ausführungskomponente                                          | Controller fängt Exceptions ab und zeigt UI-Fehler      |\n| RPC-`bash`-Befehl                | `rpc-mode` -> `session.executeBash`                   | Nein                                                                 | Gibt `BashResult` direkt zurück                                                | Consumer behandelt zurückgegebene Felder                |\n\n## Operationelle Einschränkungen\n\n- Der Interceptor blockiert Befehle nur, wenn das vorgeschlagene Tool derzeit im Kontext verfügbar ist.\n- Wenn die Artefakt-Zuweisung fehlschlägt, findet die Trunkierung trotzdem statt, aber es ist keine `artifact://`-Rückreferenz verfügbar.\n- Der Shell-Session-Cache hat in diesem Modul keine explizite Bereinigung; die Lebensdauer ist prozessbezogen.\n- PTY- und Nicht-PTY-Timeout-Oberflächen unterscheiden sich:\n  - PTY stellt ein explizites `timedOut`-Ergebnisfeld bereit,\n  - Nicht-PTY bildet Timeout auf `cancelled + Annotation`-Zusammenfassung ab.\n\n## Implementierungsdateien\n\n- [`src/tools/bash.ts`](../../packages/coding-agent/src/tools/bash.ts) — Tool-Einstiegspunkt, Normalisierung/Interception, PTY/Nicht-PTY-Auswahl, Ergebnis-/Fehlerzuordnung, Bash-Tool-Renderer.\n- [`src/tools/bash-normalize.ts`](../../packages/coding-agent/src/tools/bash-normalize.ts) — Befehlsnormalisierung und Head/Tail-Filterung nach der Ausführung.\n- [`src/tools/bash-interceptor.ts`](../../packages/coding-agent/src/tools/bash-interceptor.ts) — Interceptor-Regelabgleich und Blockierte-Befehle-Nachrichten.\n- [`src/exec/bash-executor.ts`](../../packages/coding-agent/src/exec/bash-executor.ts) — Nicht-PTY-Executor, Shell-Session-Wiederverwendung, Abbruch-Verdrahtung, Output-Sink-Integration.\n- [`src/tools/bash-interactive.ts`](../../packages/coding-agent/src/tools/bash-interactive.ts) — PTY-Laufzeitumgebung, Overlay-UI, Eingabenormalisierung, nicht-interaktive Umgebungs-Standardwerte.\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — `OutputSink`-Trunkierung/Artefakt-Spill und Zusammenfassungs-Metadaten.\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — Artefakt-Zuweisungs-Hilfsfunktionen und Streaming-Tail-Puffer.\n- [`src/tools/output-meta.ts`](../../packages/coding-agent/src/tools/output-meta.ts) — Trunkierungs-Metadaten-Struktur + Hinweis-Injektions-Wrapper.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — `executeBash` auf Session-Ebene, Nachrichtenaufzeichnung, Abbruch-Lebenszyklus.\n- [`src/modes/components/bash-execution.ts`](../../packages/coding-agent/src/modes/components/bash-execution.ts) — Interaktive `!`-Befehlsausführungskomponente.\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts) — Verdrahtung für interaktiven `!`-Befehl-UI-Stream/Update-Abschluss.\n- [`src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts) — RPC-`bash`- und `abort_bash`-Befehlsoberfläche.\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — `artifact://<id>`-Auflösung.\n",
	"de/runtime-tools/context-command.md": "---\ntitle: F5 XC Kontexte\ndescription: >-\n  Verbinden Sie xcsh mit F5 Distributed Cloud Tenants -- erstellen, wechseln und\n  verwalten Sie Authentifizierungskontexte.\nsidebar:\n  order: 1\n  label: F5 XC Kontexte\ni18n:\n  sourceHash: a9cccbc338f0\n  translator: machine\n---\n\n# F5 XC Kontexte\n\nxcsh verbindet sich mit F5 Distributed Cloud über **Kontexte** -- benannte Anmeldedatensätze, die eine Tenant-URL, ein API-Token und einen Namespace binden. Wenn Sie bereits `kubectl config use-context` oder `kubectx` verwendet haben, ist der Workflow identisch: Erstellen Sie einen Kontext, wechseln Sie zwischen ihnen per Name und verwenden Sie `-`, um zurückzuwechseln.\n\n## Erste Schritte\n\n### 1. Erstellen Sie Ihren ersten Kontext\n\nSie benötigen drei Dinge aus Ihrer F5 XC Konsole: die Tenant-URL, ein API-Token und optional einen Namespace.\n\n```\n/context create production https://acme.console.ves.volterra.io p12k3-your-api-token\n```\n\n```\nContext 'production' created. Use /context activate production to switch to it.\n```\n\nOder verwenden Sie den geführten Assistenten, wenn Sie schrittweise Eingabeaufforderungen bevorzugen:\n\n```\n/context wizard\n```\n\n### 2. Aktivieren Sie ihn\n\n```\n/context production\n```\n\n```\n╭─ production ─────────────────────────────────────────────────╮\n│ XCSH_TENANT     acme                                         │\n│ XCSH_API_URL    https://acme.console.ves.volterra.io         │\n│ XCSH_API_TOKEN  ...oken                                      │\n│ Status          Connected (312ms)                            │\n├─ Environment ────────────────────────────────────────────────┤\n│ XCSH_NAMESPACE  default                                      │\n╰──────────────────────────────────────────────────────────────╯\n```\n\nNach der Aktivierung injiziert xcsh die Tenant-Anmeldedaten in Ihre Sitzung. Der Agent kann nun F5 XC API-Aufrufe durchführen, und die Statuszeile zeigt den aktiven Kontext an.\n\n### 3. Fügen Sie weitere Kontexte hinzu und wechseln Sie zwischen ihnen\n\n```\n/context create staging https://staging.console.ves.volterra.io p12k3-staging-token\n```\n\nWechseln Sie per Name -- kein Unterbefehl-Verb erforderlich:\n\n```\n/context staging\n```\n\nWechseln Sie zurück zum vorherigen Kontext (im `cd -`-Stil):\n\n```\n/context -\n```\n\nZweimaliges Aufrufen von `/context -` bringt Sie zurück zum Ausgangspunkt.\n\n### 4. Sehen Sie, was Sie haben\n\n```\n/context\n```\n\n```\n  production           https://acme.console.ves.volterra.io\n* staging              https://staging.console.ves.volterra.io\n```\n\nDas `*` markiert den aktiven Kontext.\n\n## Alltägliche Befehle\n\n| Befehl | Was er bewirkt |\n|---|---|\n| `/context` | Alle Kontexte auflisten |\n| `/context <name>` | Zu einem Kontext wechseln |\n| `/context -` | Zum vorherigen Kontext wechseln |\n| `/context show` | Details des aktiven Kontexts anzeigen (Tokens maskiert) |\n| `/context status` | Aktuellen Authentifizierungsstatus anzeigen |\n\n## Kontext-Lebenszyklus\n\n| Befehl | Was er bewirkt |\n|---|---|\n| `/context create <name> <url> <token> [namespace]` | Einen Kontext erstellen |\n| `/context delete <name> --confirm` | Einen Kontext löschen (erfordert `--confirm`) |\n| `/context rename <old> <new>` | Einen Kontext umbenennen |\n| `/context validate <name>` | Anmeldedaten testen ohne zu wechseln |\n| `/context export [name] [--include-token]` | Als JSON exportieren (Tokens standardmäßig maskiert) |\n| `/context import <path-or-json> [--overwrite]` | Aus Datei oder Inline-JSON importieren |\n| `/context wizard` | Geführte interaktive Einrichtung |\n\n## Namespaces wechseln\n\nJeder Kontext hat einen Standard-Namespace. Wechseln Sie ihn, ohne den Kontext zu ändern:\n\n```\n/context namespace system\n```\n\nTab-Vervollständigung bietet Namespace-Namen vom aktiven Tenant an.\n\n## Umgebungsvariablen bei Kontexten\n\nKontexte können zusätzliche Umgebungsvariablen enthalten, die bei der Aktivierung in Ihre Sitzung injiziert werden. Nützlich für Tenant-spezifische Konfiguration, die nicht Teil des Anmeldedatensatzes ist.\n\n```\n/context set CUSTOM_HEADER=x-acme-trace\n/context set LOG_LEVEL=debug\n/context env list\n/context unset LOG_LEVEL\n```\n\nAliase: `add` = `set`, `remove`/`clear` = `unset`.\n\n## Tab-Vervollständigung\n\nGeben Sie `/context ` ein und drücken Sie Tab. Das Dropdown zeigt:\n\n1. **Kontextnamen** -- mit Tenant-URL-Hinweisen, damit Sie Tenants unterscheiden können\n2. **`-`** -- erscheint, wenn Sie zuvor gewechselt haben, zeigt an, zu welchem Kontext Sie wechseln würden\n3. **Unterbefehle** -- `list`, `create`, `delete`, etc.\n\nKontextnamen erscheinen zuerst, da das Wechseln die häufigste Aktion ist.\n\nVervollständigungen auf Unterbefehl-Ebene funktionieren ebenfalls: `/context activate <Tab>` vervollständigt Kontextnamen, `/context namespace <Tab>` vervollständigt Namespaces, `/context unset <Tab>` vervollständigt bekannte Umgebungsvariablen-Schlüssel.\n\n## Namensregeln\n\nKontextnamen müssen 1-64 Zeichen lang sein: Buchstaben, Ziffern, Bindestriche, Unterstriche.\n\nNamen, die mit Unterbefehlen kollidieren, werden abgelehnt:\n\n```\n/context create list https://example.com tok\n```\n\n```\nError: Context name 'list' conflicts with a /context subcommand. Choose a different name.\n```\n\nDie vollständige reservierte Menge: `list`, `show`, `status`, `create`, `delete`, `rename`, `namespace`, `env`, `set`, `unset`, `add`, `remove`, `clear`, `activate`, `validate`, `export`, `import`, `wizard`, `help`. Der Vergleich ist groß-/kleinschreibungsunabhängig.\n\n## Überschreibung durch Umgebungsvariablen\n\nWenn `XCSH_API_URL` und `XCSH_API_TOKEN` in Ihrer Shell-Umgebung gesetzt sind, bevor Sie xcsh starten, haben sie Vorrang vor jedem Kontext. Dies ist nützlich für CI/CD-Pipelines oder einmalige Sitzungen, in denen Sie keinen persistenten Kontext erstellen möchten.\n\nIn diesem Modus zeigt `/context` die aus der Umgebung stammenden Anmeldedaten mit einem `(via env vars)`-Label an.\n\n## Verhalten des vorherigen Kontexts\n\n- **Sitzungsbezogen**: Der vorherige Kontext wird beim Neustart von xcsh zurückgesetzt. Er wird nicht auf der Festplatte persistiert.\n- **Ping-Pong**: Zweimaliges `/context -` bringt Sie zurück zum Ausgangspunkt.\n- **Sicher bei Mutationen**: Wenn Sie den vorherigen Kontext löschen, wird der Zeiger gelöscht. Wenn Sie ihn umbenennen, folgt der Zeiger dem neuen Namen.\n- **Erneute Aktivierung ist ein No-Op**: `/context production`, wenn Sie bereits auf `production` sind, setzt den vorherigen Zeiger nicht zurück.\n\n## Design-Konventionen\n\nDie `/context`-UX folgt:\n\n- **kubectx**: `kubectx <name>` zum Wechseln, `kubectx -` für den vorherigen, bloßes `kubectx` zum Auflisten\n- **kubectl**: `kubectl config use-context` für die explizite Form\n- **Shell**: `cd -` / `OLDPWD` für die Verfolgung des vorherigen Verzeichnisses\n",
	"de/runtime-tools/custom-tools.md": "---\ntitle: Benutzerdefinierte Werkzeuge\ndescription: >-\n  Registrierung benutzerdefinierter Werkzeuge, Schemadefinition und\n  Ausführungs-Pipeline zur Erweiterung des Agenten.\nsidebar:\n  order: 4\n  label: Benutzerdefinierte Werkzeuge\ni18n:\n  sourceHash: 5f4a441fc2e2\n  translator: machine\n---\n\n# Benutzerdefinierte Werkzeuge\n\nBenutzerdefinierte Werkzeuge sind modellabrufbare Funktionen, die sich in dieselbe Werkzeugausführungs-Pipeline wie eingebaute Werkzeuge einklinken.\n\nEin benutzerdefiniertes Werkzeug ist ein TypeScript/JavaScript-Modul, das eine Factory exportiert. Die Factory empfängt eine Host-API (`CustomToolAPI`) und gibt ein Werkzeug oder ein Array von Werkzeugen zurück.\n\n## Was dies ist (und was nicht)\n\n- **Benutzerdefiniertes Werkzeug**: vom Modell während eines Durchlaufs aufrufbar (`execute` + TypeBox-Schema).\n- **Erweiterung**: Lebenszyklus-/Ereignisframework, das Werkzeuge registrieren und Ereignisse abfangen/modifizieren kann.\n- **Hook**: externe Pre/Post-Befehlsskripte.\n- **Skill**: statisches Leitfaden-/Kontextpaket, kein ausführbarer Werkzeugcode.\n\nWenn das Modell Code direkt aufrufen soll, verwenden Sie ein benutzerdefiniertes Werkzeug.\n\n## Integrationspfade im aktuellen Code\n\nEs gibt zwei aktive Integrationsstile:\n\n1. **SDK-bereitgestellte benutzerdefinierte Werkzeuge** (`options.customTools`)\n   - Werden über `CustomToolAdapter` oder Erweiterungskapselungen in Agentenwerkzeuge umgewandelt.\n   - Immer im initialen aktiven Werkzeugsatz beim SDK-Bootstrap enthalten.\n\n2. **Dateisystem-erkannte Module über Loader-API** (`discoverAndLoadCustomTools` / `loadCustomTools`)\n   - Als Bibliotheks-APIs in `src/extensibility/custom-tools/loader.ts` verfügbar.\n   - Host-Code kann diese aufrufen, um Werkzeugmodule aus Konfigurations-/Provider-/Plugin-Pfaden zu entdecken und zu laden.\n\n```text\nModel tool call flow\n\nLLM tool call\n   │\n   ▼\nTool registry (built-ins + custom tool adapters)\n   │\n   ▼\nCustomTool.execute(toolCallId, params, onUpdate, ctx, signal)\n   │\n   ├─ onUpdate(...)  -> streamed partial result\n   └─ return result  -> final tool content/details\n```\n\n## Erkennungsorte (Loader-API)\n\n`discoverAndLoadCustomTools(configuredPaths, cwd, builtInToolNames)` führt folgende Quellen zusammen:\n\n1. Capability-Provider (`toolCapability`), einschließlich:\n   - Native OMP-Konfiguration (`~/.xcsh/agent/tools`, `.xcsh/tools`)\n   - Claude-Konfiguration (`~/.claude/tools`, `.claude/tools`)\n   - Codex-Konfiguration (`~/.codex/tools`, `.codex/tools`)\n   - Claude-Marktplatz-Plugin-Cache-Provider\n2. Installierte Plugin-Manifeste (`~/.xcsh/plugins/node_modules/*` über Plugin-Loader)\n3. Explizit konfigurierte Pfade, die an den Loader übergeben werden\n\n### Wichtiges Verhalten\n\n- Doppelt aufgelöste Pfade werden dedupliziert.\n- Werkzeugnamenkonflikte werden gegenüber eingebauten Werkzeugen und bereits geladenen benutzerdefinierten Werkzeugen abgelehnt.\n- `.md`- und `.json`-Dateien werden von einigen Providern als Werkzeugmetadaten erkannt, aber der ausführbare Modullader lehnt sie als ausführbare Werkzeuge ab.\n- Relative konfigurierte Pfade werden ausgehend von `cwd` aufgelöst; `~` wird expandiert.\n\n## Modulvertrag\n\nEin benutzerdefiniertes Werkzeugmodul muss eine Funktion exportieren (Standard-Export bevorzugt):\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = (pi) => ({\n name: \"repo_stats\",\n label: \"Repo Stats\",\n description: \"Counts tracked TypeScript files\",\n parameters: pi.typebox.Type.Object({\n  glob: pi.typebox.Type.Optional(pi.typebox.Type.String({ default: \"**/*.ts\" })),\n }),\n\n async execute(toolCallId, params, onUpdate, ctx, signal) {\n  onUpdate?.({\n   content: [{ type: \"text\", text: \"Scanning files...\" }],\n   details: { phase: \"scan\" },\n  });\n\n  const result = await pi.exec(\"git\", [\"ls-files\", params.glob ?? \"**/*.ts\"], { signal, cwd: pi.cwd });\n  if (result.killed) {\n   throw new Error(\"Scan was cancelled\");\n  }\n  if (result.code !== 0) {\n   throw new Error(result.stderr || \"git ls-files failed\");\n  }\n\n  const files = result.stdout.split(\"\\n\").filter(Boolean);\n  return {\n   content: [{ type: \"text\", text: `Found ${files.length} files` }],\n   details: { count: files.length, sample: files.slice(0, 10) },\n  };\n },\n\n onSession(event) {\n  if (event.reason === \"shutdown\") {\n   // cleanup resources if needed\n  }\n },\n});\n\nexport default factory;\n```\n\nFactory-Rückgabetyp:\n\n- `CustomTool`\n- `CustomTool[]`\n- `Promise<CustomTool | CustomTool[]>`\n\n## API-Oberfläche, die an Factories übergeben wird (`CustomToolAPI`)\n\nAus `types.ts` und `loader.ts`:\n\n- `cwd`: Host-Arbeitsverzeichnis\n- `exec(command, args, options?)`: Hilfsfunktion zur Prozessausführung\n- `ui`: UI-Kontext (kann im Headless-Modus ein No-Op sein)\n- `hasUI`: `false` in nicht-interaktiven Abläufen\n- `logger`: gemeinsam genutzter Datei-Logger\n- `typebox`: injiziertes `@sinclair/typebox`\n- `pi`: injizierte `@f5-sales-demo/xcsh`-Exporte\n- `pushPendingAction(action)`: registriert eine Vorschauaktion für das versteckte `resolve`-Werkzeug (`docs/resolve-tool-runtime.md`)\n\nDer Loader startet mit einem No-Op-UI-Kontext und erfordert, dass der Host-Code `setUIContext(...)` aufruft, sobald die tatsächliche UI bereit ist.\n\n## Ausführungsvertrag und Typisierung\n\n`CustomTool.execute`-Signatur:\n\n```ts\nexecute(toolCallId, params, onUpdate, ctx, signal)\n```\n\n- `params` ist aus Ihrem TypeBox-Schema über `Static<TParams>` statisch typisiert.\n- Die Laufzeitargumentvalidierung erfolgt vor der Ausführung in der Agentenschleife.\n- `onUpdate` gibt partielle Ergebnisse für UI-Streaming aus.\n- `ctx` enthält den Sitzungs-/Modellzustand und eine `abort()`-Hilfsfunktion.\n- `signal` überträgt den Abbruch.\n\n`CustomToolAdapter` vermittelt dies an die Agentenwerkzeugschnittstelle und leitet Aufrufe in der richtigen Argumentreihenfolge weiter.\n\n## Wie Werkzeuge dem Modell bereitgestellt werden\n\n- Werkzeuge werden in `AgentTool`-Instanzen umgewandelt (`CustomToolAdapter` oder Erweiterungskapselungen).\n- Sie werden nach Namen in die Sitzungswerkzeugregistrierung eingefügt.\n- Beim SDK-Bootstrap werden benutzerdefinierte und erweiterungsregistrierte Werkzeuge zwingend in den initialen aktiven Satz aufgenommen.\n- CLI `--tools` validiert derzeit nur eingebaute Werkzeugnamen; die Einbindung benutzerdefinierter Werkzeuge erfolgt über Erkennungs-/Registrierungspfade und SDK-Optionen.\n\n## Rendering-Hooks\n\nOptionale Rendering-Hooks:\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\nLaufzeitverhalten in der TUI:\n\n- Wenn Hooks vorhanden sind, wird die Werkzeugausgabe in einem `Box`-Container gerendert.\n- `renderResult` empfängt `{ expanded, isPartial, spinnerFrame? }`.\n- Renderer-Fehler werden abgefangen und protokolliert; die UI fällt auf Standard-Textrendering zurück.\n\n## Sitzungs-/Zustandsbehandlung\n\nDas optionale `onSession(event, ctx)` empfängt Sitzungslebenszyklus-Ereignisse, einschließlich:\n\n- `start`, `switch`, `branch`, `tree`, `shutdown`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`, `todo_reminder`\n\nVerwenden Sie `ctx.sessionManager`, um den Zustand aus dem Verlauf wiederherzustellen, wenn sich der Branch-/Sitzungskontext ändert.\n\n## Fehler und Abbruchsemantik\n\n### Synchrone/asynchrone Fehler\n\n- Das Auslösen (oder abgelehnte Promises) in `execute` wird als Werkzeugfehler behandelt.\n- Die Agentenlaufzeit wandelt Fehler in Werkzeugergebnismeldungen mit `isError: true` und Fehlertextinhalt um.\n- Mit Erweiterungskapselungen können `tool_result`-Handler Inhalt/Details weiter umschreiben und sogar den Fehlerstatus überschreiben.\n\n### Abbruch\n\n- Der Agentenabbruch wird über `AbortSignal` an `execute` weitergegeben.\n- Leiten Sie `signal` an Subprozessarbeiten weiter (`pi.exec(..., { signal })`), um kooperativen Abbruch zu ermöglichen.\n- `ctx.abort()` ermöglicht es einem Werkzeug, den Abbruch der aktuellen Agentenoperation anzufordern.\n\n### onSession-Fehler\n\n- `onSession`-Fehler werden abgefangen und als Warnungen protokolliert; sie führen nicht zum Absturz der Sitzung.\n\n## Reale Einschränkungen beim Design\n\n- Werkzeugnamen müssen in der aktiven Registrierung global eindeutig sein.\n- Bevorzugen Sie deterministische, schemaförmige Ausgaben in `details` für Renderer-/Zustandsrekonstruktion.\n- Schützen Sie die UI-Nutzung mit `pi.hasUI`.\n- Behandeln Sie `.md`/`.json`-Dateien in Werkzeugverzeichnissen als Metadaten, nicht als ausführbare Module.\n",
	"de/runtime-tools/notebook-tool-runtime.md": "---\ntitle: Interna der Notebook-Werkzeug-Laufzeitumgebung\ndescription: >-\n  Jupyter-Notebook-Werkzeug-Laufzeitumgebung mit Zellenausführung,\n  Kernel-Lebenszyklus und Ausgabe-Rendering.\nsidebar:\n  order: 2\n  label: Notebook-Werkzeug\ni18n:\n  sourceHash: c1bafcb245e4\n  translator: machine\n---\n\n# Interna der Notebook-Werkzeug-Laufzeitumgebung\n\nDieses Dokument beschreibt die aktuelle Implementierung des `notebook`-Werkzeugs und seine Beziehung zur kernel-gestützten Python-Laufzeitumgebung.\n\nDie entscheidende Unterscheidung: **`notebook` ist ein JSON-Notebook-Editor, kein Notebook-Executor**. Es bearbeitet `.ipynb`-Zellenquellen direkt; es startet keinen Python-Kernel und kommuniziert auch nicht mit einem solchen.\n\n## Implementierungsdateien\n\n- [`src/tools/notebook.ts`](../../packages/coding-agent/src/tools/notebook.ts)\n- [`src/ipy/executor.ts`](../../packages/coding-agent/src/ipy/executor.ts)\n- [`src/ipy/kernel.ts`](../../packages/coding-agent/src/ipy/kernel.ts)\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts)\n- [`src/tools/python.ts`](../../packages/coding-agent/src/tools/python.ts)\n\n## 1) Laufzeitgrenze: Bearbeitung vs. Ausführung\n\n## `notebook`-Werkzeug (`src/tools/notebook.ts`)\n\n- Unterstützt `action: edit | insert | delete` für eine `.ipynb`-Datei.\n- Löst den Pfad relativ zum Sitzungs-CWD auf (`resolveToCwd`).\n- Lädt Notebook-JSON, validiert das `cells`-Array und die `cell_index`-Grenzen.\n- Wendet Quellbearbeitungen im Arbeitsspeicher an und schreibt das vollständige Notebook-JSON mit `JSON.stringify(notebook, null, 1)` zurück.\n- Gibt eine textuelle Zusammenfassung sowie strukturierte `details` zurück (`action`, `cellIndex`, `cellType`, `totalCells`, `cellSource`).\n\nIn diesem Werkzeug existiert kein Kernel-Lebenszyklus:\n\n- keine Gateway-Erfassung\n- keine Kernel-Sitzungs-ID\n- kein `execute_request`\n- keine Stream-Chunks aus Kernel-Kanälen\n- keine Rich-Display-Erfassung (`image/png`, JSON-Display, Status-MIME)\n\n## Notebook-ähnlicher Ausführungspfad (`src/tools/python.ts` + `src/ipy/*`)\n\nWenn der Agent zellenartigen Python-Code ausführen muss (sequenzielle Zellen, persistenter Zustand, Rich-Displays), wird das über das **`python`-Werkzeug** abgewickelt, nicht über `notebook`.\n\nDort sind Kernel-Modi, Neustart-/Abbruchverhalten, Chunk-Streaming und die Kürzung von Ausgabe-Artefakten implementiert.\n\n## 2) Semantik der Notebook-Zellenverarbeitung (`notebook`-Werkzeug)\n\n## Quellnormalisierung\n\n`content` wird in `source: string[]` mit Zeilenumbruch-Erhaltung aufgeteilt:\n\n- Jede nicht-letzte Zeile behält das abschließende `\\n`\n- Die letzte Zeile hat keinen erzwungenen abschließenden Zeilenumbruch\n\nDies entspricht den Notebook-JSON-Konventionen und vermeidet eine versehentliche Zeilenzusammenfassung bei späteren Bearbeitungen.\n\n## Aktionsverhalten\n\n- `edit`\n  - ersetzt `cells[cell_index].source`\n  - behält den vorhandenen `cell_type` bei\n- `insert`\n  - fügt an Position `[0..cellCount]` ein\n  - `cell_type` ist standardmäßig `code`\n  - Code-Zellen initialisieren `execution_count: null` und `outputs: []`\n  - Markdown-Zellen initialisieren nur `metadata` + `source`\n- `delete`\n  - entfernt `cells[cell_index]`\n  - gibt die entfernte `source` in den Details für die Renderer-Vorschau zurück\n\n## Fehlermeldungen\n\nSchwerwiegende Fehler werden ausgelöst bei:\n\n- fehlender Notebook-Datei\n- ungültigem JSON\n- fehlendem oder nicht-array-artigem `cells`\n- außerhalb des gültigen Bereichs liegendem Index (Einfügen und Nicht-Einfügen haben unterschiedliche gültige Bereiche)\n- fehlendem `content` für `edit`/`insert`\n\nDiese werden zu `Error:`-Werkzeugantworten im vorgelagerten System; der Renderer verwendet den Notebook-Pfad und den formatierten Fehlertext.\n\n## 3) Kernel-Sitzungssemantik (wo sie tatsächlich existiert)\n\nDie Kernel-Semantik ist in `executePython` / `PythonKernel` implementiert und gilt für das `python`-Werkzeug.\n\n## Modi\n\n`PythonKernelMode`:\n\n- `session` (Standard)\n  - Kernels werden in der `kernelSessions`-Map zwischengespeichert\n  - maximal 4 Sitzungen; älteste wird bei Überschreitung entfernt\n  - Bereinigung inaktiver/toter Sitzungen alle 30 Sekunden, Timeout nach 5 Minuten\n  - sitzungsbezogene Warteschlange serialisiert die Ausführung (`session.queue`)\n- `per-call`\n  - erstellt einen Kernel für die Anfrage\n  - führt aus\n  - fährt den Kernel immer in `finally` herunter\n\n## Rücksetzverhalten\n\nDas `python`-Werkzeug übergibt `reset` nur für die erste Zelle in einem Mehrfachzellen-Aufruf; spätere Zellen werden immer mit `reset: false` ausgeführt.\n\n## Kernel-Absturz / Neustart / Wiederholung\n\nIm Sitzungsmodus (`withKernelSession`):\n\n- Abgestürzter Kernel wird durch Heartbeat erkannt (`kernel.isAlive()`-Prüfung alle 5 Sekunden) oder durch einen Ausführungsfehler.\n- Ein vor der Ausführung erkannter toter Zustand löst `restartKernelSession` aus.\n- Ein Absturz während der Ausführung wird einmal wiederholt: Kernel neu starten, Handler erneut ausführen.\n- `restartCount > 1` in derselben Sitzung wirft `Python kernel restarted too many times in this session`.\n\nStartwiederholungsverhalten:\n\n- Die Kernel-Erstellung eines gemeinsam genutzten Gateways wird bei `SharedGatewayCreateError` mit HTTP 5xx einmal wiederholt.\n\nWiederherstellung bei Ressourcenerschöpfung:\n\n- Erkennt Fehler vom Typ `EMFILE`/`ENFILE`/„Too many open files\"\n- Bereinigt verfolgte Sitzungen\n- Ruft `shutdownSharedGateway()` auf\n- Versucht die Kernel-Sitzungserstellung einmal erneut\n\n## 4) Injektion von Umgebungs-/Sitzungsvariablen\n\nDer Kernel-Start erhält eine optionale Umgebungsvariablen-Map vom Executor:\n\n- `PI_SESSION_FILE` (Pfad zur Sitzungszustandsdatei)\n- `ARTIFACTS` (Artefaktverzeichnis)\n\n`PythonKernel.#initializeKernelEnvironment(...)` führt dann im Kernel ein Initialisierungsskript aus, um:\n\n- `os.chdir(cwd)` auszuführen\n- Umgebungseinträge in `os.environ` einzufügen\n- das CWD in `sys.path` voranzustellen, falls nicht vorhanden\n\nImplikation:\n\n- Präambel-Hilfsfunktionen, die Sitzungs- oder Artefaktkontext lesen, verlassen sich auf diese Umgebungsvariablen im Python-Prozesszustand.\n\n## 5) Streaming-/Chunk- und Display-Verarbeitung (kernel-gestützter Pfad)\n\nDer Kernel-Client verarbeitet Jupyter-Protokollnachrichten pro Ausführung:\n\n- `stream` -> Text-Chunk an `onChunk`\n- `execute_result` / `display_data` ->\n  - Anzeigetext nach MIME-Priorität ausgewählt: `text/markdown` > `text/plain` > konvertiertes `text/html`\n  - strukturierte Ausgaben werden separat erfasst:\n    - `application/json` -> `{ type: \"json\" }`\n    - `image/png` -> `{ type: \"image\" }`\n    - `application/x-xcsh-status` -> `{ type: \"status\" }` (keine Textausgabe)\n- `error` -> Traceback-Text wird in den Chunk-Stream übertragen + strukturierte Fehlermetadaten\n- `input_request` -> gibt eine stdin-Warnmeldung aus, sendet leere `input_reply`, markiert stdin als angefordert\n- Der Abschluss wartet auf sowohl `execute_reply` als auch Kernel `status=idle`\n\nAbbruch/Timeout:\n\n- Abbruchsignal löst `interrupt()` aus (REST `/interrupt` + Control-Kanal `interrupt_request`)\n- Ergebnis wird mit `cancelled=true` markiert\n- Timeout-Pfad ergänzt die Ausgabe mit `Command timed out after <n> seconds`\n\n## 6) Kürzung und Artefaktverhalten\n\n`OutputSink` in `src/session/streaming-output.ts` wird von Kernel-Ausführungspfaden (`executeWithKernel`) verwendet:\n\n- bereinigt jeden Chunk (`sanitizeText`)\n- verfolgt Gesamt-/Ausgabezeilen und Bytes\n- optionale Artefakt-Spill-Datei (`artifactPath`, `artifactId`)\n- wenn der Arbeitsspeicherpuffer den Schwellenwert überschreitet (`DEFAULT_MAX_BYTES`, sofern nicht überschrieben):\n  - markiert als gekürzt\n  - behält die letzten Bytes im Arbeitsspeicher (UTF-8-sichere Grenze)\n  - kann den vollständigen Stream in einen Artefakt-Sink auslagern\n\n`dump()` gibt zurück:\n\n- sichtbaren Ausgabetext (möglicherweise am Ende gekürzt)\n- Kürzungs-Flag + Zählungen\n- Artefakt-ID (für `artifact://<id>`-Referenzen)\n\nDas `python`-Werkzeug wandelt diese Metadaten in Kürzungshinweise im Ergebnis und TUI-Warnungen um.\n\nDas `notebook`-Werkzeug verwendet `OutputSink` **nicht**; es hat keine Stream-/Artefakt-Kürzungspipeline, da es keinen Code ausführt.\n\n## 7) Renderer-Annahmen und Formatierung\n\n## Notebook-Renderer (`notebookToolRenderer`)\n\n- Aufrufansicht: Statuszeile mit Aktion + Notebook-Pfad + Zellen-/Typ-Metadaten\n- Ergebnisansicht:\n  - Erfolgszusammenfassung aus `details` abgeleitet\n  - `cellSource` wird über `renderCodeCell` gerendert\n  - Markdown-Zellen setzen den Sprachhinweis `markdown`; andere Zellen haben keine explizite Sprachüberschreibung\n  - Limit für eingeklappte Code-Vorschau ist `PREVIEW_LIMITS.COLLAPSED_LINES * 2`\n  - unterstützt den erweiterten Modus über gemeinsame Render-Optionen\n  - verwendet Render-Cache mit Schlüssel aus Breite + erweitertem Zustand\n\nAnnahme bei der Fehlerwiedergabe:\n\n- Wenn der erste Textinhalt mit `Error:` beginnt, formatiert der Renderer diesen als Notebook-Fehlerblock.\n\n## Python-Renderer (für tatsächliche Ausführungsausgabe)\n\nDas kernel-gestützte Ausführungs-Rendering erwartet:\n\n- zellenweise Statusübergänge (`pending/running/complete/error`)\n- optionalen strukturierten Statustereignisabschnitt\n- optionale JSON-Ausgabebäume\n- Kürzungswarnungen + optionalen `artifact://<id>`-Zeiger\n\nDieses Renderer-Verhalten steht in keiner Beziehung zu den Ergebnissen der `notebook`-JSON-Bearbeitung, außer dass beide gemeinsame TUI-Primitive wiederverwenden.\n\n## 8) Abweichung vom einfachen Python-Werkzeug-Verhalten\n\nWenn „einfaches Python-Werkzeug\" den `python`-Ausführungspfad bedeutet:\n\n- `python` führt Code in einem Kernel aus, hält den Zustand je nach Modus aufrecht, streamt Chunks, erfasst Rich-Displays, behandelt Interrupts/Timeouts und unterstützt die Ausgabekürzung/-artefakte.\n- `notebook` führt ausschließlich deterministische Notebook-JSON-Mutationen durch; keine Ausführung, kein Kernel-Zustand, kein Chunk-Stream, keine Display-Ausgaben, keine Artefakt-Pipeline.\n\nWenn ein Workflow beides erfordert:\n\n1. Notebook-Quelle mit `notebook` bearbeiten\n2. Code-Zellen über `python` ausführen (Code manuell übergeben), nicht über `notebook`\n\nDie aktuelle Implementierung bietet kein einzelnes Werkzeug, das sowohl `.ipynb` mutiert als auch Notebook-Zellen über einen Kernel-Kontext ausführt.\n",
	"de/runtime-tools/resolve-tool-runtime.md": "---\ntitle: Resolve-Tool – Laufzeitinterna\ndescription: >-\n  Resolve tool runtime for file path resolution, content fetching, and URL-based\n  resource access.\nsidebar:\n  order: 3\n  label: Resolve-Tool\ni18n:\n  sourceHash: 06e8be8c5a3c\n  translator: machine\n---\n\n# Resolve-Tool – Laufzeitinterna\n\nDieses Dokument erklärt, wie Vorschau-/Anwenden-Workflows im Coding-Agent modelliert sind und wie benutzerdefinierte Tools über `pushPendingAction` daran teilnehmen können.\n\n## Geltungsbereich und wichtige Dateien\n\n- [`src/tools/resolve.ts`](../../packages/coding-agent/src/tools/resolve.ts)\n- [`src/tools/pending-action.ts`](../../packages/coding-agent/src/tools/pending-action.ts)\n- [`src/tools/ast-edit.ts`](../../packages/coding-agent/src/tools/ast-edit.ts)\n- [`src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts)\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n\n## Was `resolve` tut\n\n`resolve` ist ein verborgenes Tool, das eine ausstehende Vorschau-Aktion abschließt.\n\n- `action: \"apply\"` führt `apply(reason)` auf der ausstehenden Aktion aus und persistiert die Änderungen.\n- `action: \"discard\"` ruft `reject(reason)` auf, falls vorhanden; andernfalls verwirft es die Aktion mit einer Standard-Nachricht \"Discarded\".\n\nWenn keine ausstehende Aktion existiert, schlägt `resolve` mit folgender Meldung fehl:\n\n- `No pending action to resolve. Nothing to apply or discard.`\n\n## Ausstehende Aktionen sind ein Stack (LIFO)\n\nAusstehende Aktionen werden im `PendingActionStore` als Push/Pop-Stack gespeichert:\n\n- `push(action)` fügt eine neue ausstehende Aktion oben hinzu.\n- `peek()` inspiziert die aktuelle oberste Aktion.\n- `pop()` entfernt die oberste Aktion und gibt sie zurück.\n- `hasPending` gibt an, ob der Stack nicht leer ist.\n\n`resolve` konsumiert immer zuerst die **oberste** ausstehende Aktion (`pop()`), sodass mehrere Vorschau-erzeugende Tools in umgekehrter Reihenfolge ihrer Registrierung aufgelöst werden.\n\n## Beispiel eines eingebauten Produzenten (`ast_edit`)\n\n`ast_edit` zeigt zunächst eine Vorschau struktureller Ersetzungen an. Wenn die Vorschau Ersetzungen enthält und noch nicht angewendet wurde, wird eine ausstehende Aktion auf den Stack gelegt, die Folgendes enthält:\n\n- Label (menschenlesbare Zusammenfassung)\n- `sourceToolName` (`ast_edit`)\n- `apply(reason: string)`-Callback, der die AST-Bearbeitung mit `dryRun: false` erneut ausführt\n\n`resolve(action=\"apply\", reason=\"...\")` übergibt `reason` an diesen Callback.\n\n## Benutzerdefinierte Tools: `pushPendingAction`\n\nBenutzerdefinierte Tools können resolve-kompatible ausstehende Aktionen über `CustomToolAPI.pushPendingAction(...)` registrieren.\n\n`CustomToolPendingAction`:\n\n- `label: string` (erforderlich)\n- `apply(reason: string): Promise<AgentToolResult<unknown>>` (erforderlich) — wird beim Anwenden aufgerufen; `reason` ist die an `resolve` übergebene Zeichenkette\n- `reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>` (optional) — wird beim Verwerfen aufgerufen; der Rückgabewert ersetzt die Standard-Nachricht \"Discarded\", falls vorhanden\n- `details?: unknown` (optional)\n- `sourceToolName?: string` (optional, Standardwert ist `\"custom_tool\"`)\n\n### Minimales Nutzungsbeispiel\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = pi => ({\n name: \"batch_rename_preview\",\n label: \"Batch Rename Preview\",\n description: \"Previews renames and defers commit to resolve\",\n parameters: pi.typebox.Type.Object({\n  files: pi.typebox.Type.Array(pi.typebox.Type.String()),\n }),\n\n async execute(_toolCallId, params) {\n  const previewSummary = `Prepared rename plan for ${params.files.length} files`;\n\n  pi.pushPendingAction({\n   label: `Batch rename: ${params.files.length} files`,\n   sourceToolName: \"batch_rename_preview\",\n   apply: async (reason) => {\n    // apply writes here\n    return {\n     content: [{ type: \"text\", text: `Applied batch rename. Reason: ${reason}` }],\n    };\n   },\n   reject: async (reason) => {\n    // optional: cleanup or notify on discard\n    return {\n     content: [{ type: \"text\", text: `Discarded batch rename. Reason: ${reason}` }],\n    };\n   },\n  });\n\n  return {\n   content: [{ type: \"text\", text: `${previewSummary}. Call resolve to apply or discard.` }],\n  };\n },\n});\n\nexport default factory;\n```\n\n## Laufzeitverfügbarkeit und Fehler\n\n`pushPendingAction` wird vom Custom-Tool-Loader unter Verwendung des aktiven Sitzungs-`PendingActionStore` verdrahtet.\n\nWenn die Laufzeitumgebung keinen Pending-Action-Store hat, wirft `pushPendingAction` einen Fehler:\n\n- `Pending action store unavailable for custom tools in this runtime.`\n\n## Tool-Choice-Verhalten\n\nWenn `PendingActionStore.hasPending` den Wert `true` hat, bevorzugt die Agenten-Laufzeit die Tool-Auswahl von `resolve`, damit ausstehende Vorschauen explizit abgeschlossen werden, bevor der normale Tool-Ablauf fortgesetzt wird.\n\n## Hinweise für Entwickler\n\n- Verwenden Sie ausstehende Aktionen nur für destruktive oder schwerwiegende Operationen, die ein explizites Anwenden/Verwerfen unterstützen sollen.\n- Halten Sie `label` prägnant und spezifisch; es wird in der Ausgabe des Resolve-Renderers angezeigt.\n- Stellen Sie sicher, dass `apply(reason)` deterministisch und hinreichend idempotent für eine einmalige Ausführung ist; `reason` ist informativ und sollte das Verhalten nicht ändern.\n- Implementieren Sie `reject(reason)`, wenn das Verwerfen eine Bereinigung erfordert (temporärer Zustand, Sperren, Benachrichtigungen); lassen Sie es bei zustandslosen Vorschauen weg, bei denen die Standardnachricht ausreicht.\n- Wenn Ihr Tool mehrere Vorschauen bereitstellen kann, beachten Sie die LIFO-Semantik: Die zuletzt hinzugefügte Aktion wird zuerst aufgelöst.\n",
	"de/runtime-tools/slash-command-internals.md": "---\ntitle: Interna des Slash-Befehl-Systems\ndescription: >-\n  Interna des Slash-Befehl-Systems mit Registrierung, Argument-Parsing und\n  Ausführungs-Dispatch.\nsidebar:\n  order: 5\n  label: Slash-Befehle\ni18n:\n  sourceHash: 2cbd44a3de87\n  translator: machine\n---\n\n# Interna des Slash-Befehl-Systems\n\nDieses Dokument beschreibt, wie Slash-Befehle in `coding-agent` erkannt, dedupliziert, im interaktiven Modus angezeigt und zur Prompt-Zeit expandiert werden.\n\n## Implementierungsdateien\n\n- [`src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n- [`src/capability/slash-command.ts`](../../packages/coding-agent/src/capability/slash-command.ts)\n- [`src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`src/discovery/claude.ts`](../../packages/coding-agent/src/discovery/claude.ts)\n- [`src/discovery/codex.ts`](../../packages/coding-agent/src/discovery/codex.ts)\n- [`src/discovery/claude-plugins.ts`](../../packages/coding-agent/src/discovery/claude-plugins.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n\n## 1) Erkennungsmodell\n\nSlash-Befehle sind eine Fähigkeit (`id: \"slash-commands\"`), die nach Befehlsname verschlüsselt ist (`key: cmd => cmd.name`).\n\nDie Fähigkeitsregistrierung lädt alle registrierten Anbieter, sortiert nach Anbieterpriorität absteigend, und dedupliziert nach Schlüssel mit **First-wins**-Semantik.\n\n### Anbieterrangfolge\n\nAktuelle Slash-Befehl-Anbieter und Prioritäten:\n\n1. `native` (OMP) — Priorität `100`\n2. `claude` — Priorität `80`\n3. `claude-plugins` — Priorität `70`\n4. `codex` — Priorität `70`\n\nGleichstandsverhalten: Anbieter mit gleicher Priorität behalten die Registrierungsreihenfolge. Die aktuelle Importreihenfolge registriert `claude-plugins` vor `codex`, daher gewinnen Plugin-Befehle bei Namenskollisionen gegenüber Codex-Befehlen.\n\n### Namenskollisionsverhalten\n\nBei `slash-commands` werden Kollisionen strikt durch Fähigkeits-Dedup aufgelöst:\n\n- Das Element mit der höchsten Priorität wird in `result.items` behalten\n- Duplikate mit niedrigerer Priorität verbleiben nur in `result.all` und werden mit `_shadowed = true` markiert\n\nDies gilt sowohl über Anbieter hinweg als auch innerhalb eines Anbieters, wenn er doppelte Namen zurückgibt.\n\n### Verhalten beim Datei-Scanning\n\nAnbieter verwenden größtenteils `loadFilesFromDir(...)`, das aktuell:\n\n- standardmäßig nicht-rekursives Matching (`*.md`) verwendet\n- nativen Glob mit `gitignore: true`, `hidden: false` nutzt\n- jede gefundene Datei liest und in einen `SlashCommand` umwandelt\n\nVersteckte Dateien/Verzeichnisse werden daher nicht geladen und ignorierte Pfade werden übersprungen.\n\n## 2) Anbieterspezifische Quellpfade und lokale Rangfolge\n\n## `native`-Anbieter (`builtin.ts`)\n\nSuchstammverzeichnisse stammen aus `.xcsh`-Verzeichnissen:\n\n- Projekt: `<cwd>/.xcsh/commands/*.md`\n- Benutzer: `~/.xcsh/agent/commands/*.md`\n\n`getConfigDirs()` gibt zuerst das Projekt zurück, dann den Benutzer, sodass **native Projektbefehle bei Namenskollisionen native Benutzerbefehle überschreiben**.\n\n## `claude`-Anbieter (`claude.ts`)\n\nLädt:\n\n- Benutzer: `~/.claude/commands/*.md`\n- Projekt: `<cwd>/.claude/commands/*.md`\n\nDer Anbieter fügt Benutzerelemente vor Projektelementen ein, sodass **Claude-Benutzerbefehle bei gleichen Namenskollisionen innerhalb dieses Anbieters Claude-Projektbefehle überschreiben**.\n\n## `codex`-Anbieter (`codex.ts`)\n\nLädt:\n\n- Benutzer: `~/.codex/commands/*.md`\n- Projekt: `<cwd>/.codex/commands/*.md`\n\nBeide Seiten werden geladen und dann in Benutzer-zuerst-Reihenfolge zusammengeführt, sodass **Codex-Benutzerbefehle bei Kollisionen Codex-Projektbefehle überschreiben**.\n\nCodex-Befehlsinhalt wird mit Frontmatter-Entfernung geparst (`parseFrontmatter`), und der Befehlsname kann durch Frontmatter `name` überschrieben werden; andernfalls wird der Dateiname verwendet.\n\n## `claude-plugins`-Anbieter (`claude-plugins.ts`)\n\nLädt Plugin-Befehlsstammverzeichnisse aus `~/.claude/plugins/installed_plugins.json` und scannt dann `<pluginRoot>/commands/*.md`.\n\nDie Reihenfolge folgt der Registrierungsiterationsreihenfolge und der Eintragsreihenfolge pro Plugin aus diesen JSON-Daten. Es gibt keinen zusätzlichen Sortierschritt.\n\n## 3) Materialisierung zur Laufzeit als `FileSlashCommand`\n\n`loadSlashCommands()` in `src/extensibility/slash-commands.ts` konvertiert Fähigkeitselemente in `FileSlashCommand`-Objekte, die zur Prompt-Zeit verwendet werden.\n\nFür jeden Befehl:\n\n1. Frontmatter/Body parsen (`parseFrontmatter`)\n2. Beschreibungsquelle:\n   - `frontmatter.description`, sofern vorhanden\n   - andernfalls die erste nicht leere Body-Zeile (getrimmt, max. 60 Zeichen mit `...`)\n3. Geparstem Body als ausführbaren Template-Inhalt behalten\n4. Eine Anzeigequellzeichenkette wie `via Claude Code Project` berechnen\n\nDer Schweregrad des Frontmatter-Parsens ist quellenabhängig:\n\n- Ebene `native` -> Parse-Fehler sind `fatal`\n- Ebenen `user`/`project` -> Parse-Fehler sind `warn` mit Fallback-Parsing\n\n### Eingebettete Fallback-Befehle\n\nNach Dateisystem-/Anbieterbefehlen werden eingebettete Befehlsvorlagen angehängt (`EMBEDDED_COMMAND_TEMPLATES`), sofern ihre Namen noch nicht vorhanden sind.\n\nDer aktuelle eingebettete Satz stammt aus `src/task/commands.ts` und wird als Fallback verwendet (`source: \"bundled\"`).\n\n## 4) Interaktiver Modus: Woher die Befehlslisten stammen\n\nDer interaktive Modus kombiniert mehrere Befehlsquellen für Autovervollständigung und Befehlsrouting.\n\nZur Konstruktionszeit erstellt er eine ausstehende Befehlsliste aus:\n\n- Eingebauten Befehlen (`BUILTIN_SLASH_COMMANDS`, einschließlich Argument-Vervollständigung und Inline-Hinweisen für ausgewählte Befehle)\n- Erweiterungsregistrierten Slash-Befehlen (`extensionRunner.getRegisteredCommands(...)`)\n- TypeScript-Benutzerbefehlen (`session.customCommands`), auf Slash-Befehlsbezeichnungen abgebildet\n- Optionalen Skill-Befehlen (`/skill:<name>`), wenn `skills.enableSkillCommands` aktiviert ist\n\nDann ruft `init()` `refreshSlashCommandState(...)` auf, um dateibasierte Befehle zu laden und einen `CombinedAutocompleteProvider` zu installieren, der enthält:\n\n- die oben genannten ausstehenden Befehle\n- gefundene dateibasierte Befehle\n\n`refreshSlashCommandState(...)` aktualisiert auch `session.setSlashCommands(...)`, damit die Prompt-Expansion denselben gefundenen Dateibefehlssatz verwendet.\n\n### Aktualisierungslebenszyklus\n\nDer Slash-Befehlsstatus wird aktualisiert:\n\n- während der interaktiven Initialisierung\n- nachdem `/move` das Arbeitsverzeichnis ändert (`handleMoveCommand` ruft `resetCapabilities()` dann `refreshSlashCommandState(newCwd)` auf)\n\nEs gibt keinen kontinuierlichen Datei-Watcher für Befehlsverzeichnisse.\n\n### Weitere Anzeige\n\nDas Erweiterungs-Dashboard lädt ebenfalls die `slash-commands`-Fähigkeit und zeigt aktive/überschattete Befehlseinträge an, einschließlich `_shadowed`-Duplikaten.\n\n## 5) Platzierung in der Prompt-Pipeline\n\n`AgentSession.prompt(...)`-Slash-Verarbeitungsreihenfolge (wenn `expandPromptTemplates !== false`):\n\n1. **Erweiterungsbefehle** (`#tryExecuteExtensionCommand`)  \n   Wenn `/name` mit einem erweiterungsregistrierten Befehl übereinstimmt, wird der Handler sofort ausgeführt und der Prompt kehrt zurück.\n2. **TypeScript-Benutzerbefehle** (`#tryExecuteCustomCommand`)  \n   Nur Grenze: Bei Übereinstimmung wird er ausgeführt und kann zurückgeben:\n   - `string` -> Prompt-Text durch diesen String ersetzen\n   - `void/undefined` -> wird als behandelt betrachtet; kein LLM-Prompt\n3. **Dateibasierte Slash-Befehle** (`expandSlashCommand`)  \n   Wenn der Text noch mit `/` beginnt, wird die Markdown-Befehlsexpansion versucht.\n4. **Prompt-Vorlagen** (`expandPromptTemplate`)  \n   Nach der Slash-/Benutzerbefehls-Verarbeitung angewendet.\n5. **Zustellung**\n   - Leerlauf: Prompt wird sofort an den Agenten gesendet\n   - Streaming: Prompt wird je nach `streamingBehavior` als Steer/Follow-up in die Warteschlange gestellt\n\nDeshalb liegt die Slash-Befehlsexpansion vor der Prompt-Vorlagenexpansion, und deshalb können Benutzerbefehle den führenden Schrägstrich entfernen, bevor der Dateibefehl-Abgleich erfolgt.\n\n## 6) Expansionssemantik für dateibasierte Slash-Befehle\n\nVerhalten von `expandSlashCommand(text, fileCommands)`:\n\n- wird nur ausgeführt, wenn der Text mit `/` beginnt\n- parst den Befehlsnamen aus dem ersten Token nach `/`\n- parst Argumente aus dem verbleibenden Text via `parseCommandArgs`\n- sucht eine exakte Namensübereinstimmung in den geladenen `fileCommands`\n- bei Übereinstimmung wird angewendet:\n  - Positionsersetzung: `$1`, `$2`, ...\n  - Aggregatersetzung: `$ARGUMENTS` und `$@`\n  - dann Template-Rendering via `prompt.render` mit `{ args, ARGUMENTS, arguments }`\n- bei keiner Übereinstimmung wird der Originaltext unverändert zurückgegeben\n\n### Einschränkungen von `parseCommandArgs`\n\nDer Parser ist ein einfaches, anführungszeichenfähiges Splitting:\n\n- unterstützt `'einfache'` und `\"doppelte\"` Anführungszeichen, um Leerzeichen zu erhalten\n- entfernt Anführungszeichen-Begrenzer\n- implementiert keine Backslash-Escape-Regeln\n- nicht abgeschlossene Anführungszeichen sind kein Fehler; der Parser konsumiert bis zum Ende\n\n## 7) Verhalten bei unbekanntem `/...`\n\nUnbekannte Slash-Eingaben werden durch die zentrale Slash-Logik **nicht abgelehnt**.\n\nWenn ein Befehl nicht von der Erweiterungs-, Benutzer- oder Dateiebene behandelt wird, gibt `expandSlashCommand` den Originaltext zurück, und der wörtliche `/...`-Prompt durchläuft die normale Prompt-Vorlagenexpansion und LLM-Zustellung.\n\nDer interaktive Modus behandelt viele eingebaute Befehle separat direkt in `InputController` (zum Beispiel `/settings`, `/model`, `/mcp`, `/move`, `/exit`). Diese werden vor `session.prompt(...)` verarbeitet und erreichen daher in diesem Pfad niemals die Dateibefehlsexpansion.\n\n## 8) Unterschiede zwischen Streaming und Leerlauf\n\n## Leerlauf-Pfad\n\n- `session.prompt(\"/x ...\")` führt die Befehlspipeline aus und entweder wird der Befehl sofort ausgeführt oder der expandierte Text wird direkt gesendet.\n\n## Streaming-Pfad (`session.isStreaming === true`)\n\n- `prompt(...)` führt weiterhin zuerst Erweiterungs-/Benutzer-/Datei-/Vorlagetransformationen durch\n- erfordert dann `streamingBehavior`:\n  - `\"steer\"` -> Interrupt-Nachricht in die Warteschlange stellen (`agent.steer`)\n  - `\"followUp\"` -> Nachricht nach der Runde in die Warteschlange stellen (`agent.followUp`)\n- wenn `streamingBehavior` fehlt, wirft der Prompt einen Fehler\n\n### Wichtiges befehlsspezifisches Streaming-Verhalten\n\n- Erweiterungsbefehle werden auch während des Streamings sofort ausgeführt (nicht als Text in die Warteschlange gestellt).\n- Die Hilfsmethoden `steer(...)`/`followUp(...)` lehnen Erweiterungsbefehle ab (`#throwIfExtensionCommand`), um zu vermeiden, dass Befehlstext für Handler in die Warteschlange gestellt wird, die synchron ausgeführt werden müssen.\n- Die Kompaktierungswarteschlangen-Wiedergabe verwendet `isKnownSlashCommand(...)`, um zu entscheiden, ob in der Warteschlange befindliche Einträge via `session.prompt(...)` (bei bekannten Slash-Befehlen) oder via Raw-Steer-/Follow-up-Methoden wiedergegeben werden sollen.\n\n## 9) Fehlerbehandlung und Fehlerflächen\n\n- Anbieterladefehler sind isoliert; die Registrierung sammelt Warnungen und fährt mit anderen Anbietern fort.\n- Ungültige Slash-Befehlselemente (fehlender Name/Pfad/Inhalt oder ungültige Ebene) werden durch die Fähigkeitsvalidierung verworfen.\n- Frontmatter-Parse-Fehler:\n  - native Befehle: fataler Parse-Fehler wird weitergegeben\n  - nicht-native Befehle: Warnung + Fallback-Schlüssel/Wert-Parsing\n- Ausnahmen von Erweiterungs-/Benutzerbefehlshandlern werden abgefangen und über den Erweiterungsfehlerkanal gemeldet (oder Logger-Fallback für Benutzerbefehle ohne Erweiterungs-Runner) und als behandelt betrachtet (keine unbeabsichtigte Fallback-Ausführung).\n",
	"de/runtime-tools/task-agent-discovery.md": "---\ntitle: Task-Agent-Erkennung und -Auswahl\ndescription: >-\n  Erkennungs- und Auswahllogik für Task-Agents zur Weiterleitung von Arbeit an\n  spezialisierte Subagent-Typen.\nsidebar:\n  order: 6\n  label: Task-Agent-Erkennung\ni18n:\n  sourceHash: 8cf42457c672\n  translator: machine\n---\n\n# Task-Agent-Erkennung und -Auswahl\n\nDieses Dokument beschreibt, wie das Task-Subsystem Agent-Definitionen erkennt, mehrere Quellen zusammenführt und einen angeforderten Agent zur Ausführungszeit auflöst.\n\nEs behandelt das Laufzeitverhalten, wie es heute implementiert ist, einschließlich Vorrangregeln, Behandlung ungültiger Definitionen und Spawn-/Tiefenbeschränkungen, die einen Agent effektiv unerreichbar machen können.\n\n## Implementierungsdateien\n\n- [`src/task/discovery.ts`](../../packages/coding-agent/src/task/discovery.ts)\n- [`src/task/agents.ts`](../../packages/coding-agent/src/task/agents.ts)\n- [`src/task/types.ts`](../../packages/coding-agent/src/task/types.ts)\n- [`src/task/index.ts`](../../packages/coding-agent/src/task/index.ts)\n- [`src/task/commands.ts`](../../packages/coding-agent/src/task/commands.ts)\n- [`src/prompts/agents/task.md`](../../packages/coding-agent/src/prompts/agents/task.md)\n- [`src/prompts/tools/task.md`](../../packages/coding-agent/src/prompts/tools/task.md)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/config.ts`](../../packages/coding-agent/src/config.ts)\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts)\n\n---\n\n## Form der Agent-Definition\n\nTask-Agents werden in `AgentDefinition` (`src/task/types.ts`) normalisiert:\n\n- `name`, `description`, `systemPrompt` (erforderlich für einen gültig geladenen Agent)\n- optionale `tools`, `spawns`, `model`, `thinkingLevel`, `output`\n- `source`: `\"bundled\" | \"user\" | \"project\"`\n- optionaler `filePath`\n\nDas Parsing erfolgt über Frontmatter mittels `parseAgentFields()` (`src/discovery/helpers.ts`):\n\n- fehlender `name` oder `description` => ungültig (`null`), Aufrufer behandelt dies als Parse-Fehler\n- `tools` akzeptiert CSV oder Array; falls angegeben, wird `submit_result` automatisch hinzugefügt\n- `spawns` akzeptiert `*`, CSV oder Array\n- Abwärtskompatibilitätsverhalten: falls `spawns` fehlt, aber `tools` `task` enthält, wird `spawns` zu `*`\n- `output` wird als opake Schema-Daten durchgereicht\n\n## Mitgelieferte Agents\n\nMitgelieferte Agents werden zur Build-Zeit eingebettet (`src/task/agents.ts`) mittels Text-Imports.\n\n`EMBEDDED_AGENT_DEFS` definiert:\n\n- `explore`, `plan`, `designer`, `reviewer` aus Prompt-Dateien\n- `task` und `quick_task` aus dem gemeinsamen `task.md`-Body plus injiziertem Frontmatter\n\nLadepfad:\n\n1. `loadBundledAgents()` parst eingebettetes Markdown mit `parseAgent(..., \"bundled\", \"fatal\")`\n2. Ergebnisse werden im Speicher gecacht (`bundledAgentsCache`)\n3. `clearBundledAgentsCache()` ist ein nur für Tests vorgesehener Cache-Reset\n\nDa das Parsing der mitgelieferten Agents `level: \"fatal\"` verwendet, führt fehlerhaftes mitgeliefertes Frontmatter zu einem Throw und kann die Erkennung vollständig fehlschlagen lassen.\n\n## Dateisystem- und Plugin-Erkennung\n\n`discoverAgents(cwd, home)` (`src/task/discovery.ts`) führt Agents aus mehreren Quellen zusammen, bevor mitgelieferte Definitionen angehängt werden.\n\n### Erkennungsquellen\n\n1. Benutzer-Konfigurationsverzeichnisse für Agents aus `getConfigDirs(\"agents\", { project: false })`\n2. Nächstgelegene Projekt-Agent-Verzeichnisse aus `findAllNearestProjectConfigDirs(\"agents\", cwd)`\n3. Claude-Plugin-Stammverzeichnisse (`listClaudePluginRoots(home)`) mit `agents/`-Unterverzeichnissen\n4. Mitgelieferte Agents (`loadBundledAgents()`)\n\n### Tatsächliche Quellreihenfolge\n\nDie Reihenfolge der Quellfamilien ergibt sich aus `getConfigDirs(\"\", { project: false })`, das von `priorityList` in `src/config.ts` abgeleitet wird:\n\n1. `.xcsh`\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nFür jede Quellfamilie ist die Erkennungsreihenfolge:\n\n1. Nächstgelegenes Projektverzeichnis für diese Quelle (falls gefunden)\n2. Benutzerverzeichnis für diese Quelle\n\nNach allen Quellfamilien-Verzeichnissen werden Plugin-`agents/`-Verzeichnisse angehängt (zuerst Projekt-Scope-Plugins, dann Benutzer-Scope).\n\nMitgelieferte Agents werden zuletzt angehängt.\n\n### Wichtiger Vorbehalt: veraltete Kommentare vs. aktueller Code\n\nDie Header-Kommentare in `discovery.ts` erwähnen noch `.pi` und nennen `.codex`/`.gemini` nicht. Die tatsächliche Laufzeitreihenfolge wird von `src/config.ts` gesteuert und verwendet derzeit `.xcsh`, `.claude`, `.codex`, `.gemini`.\n\n## Zusammenführung und Kollisionsregeln\n\nDie Erkennung verwendet First-Wins-Deduplizierung nach exaktem `agent.name`:\n\n- Ein `Set<string>` verfolgt bereits gesehene Namen.\n- Geladene Agents werden in Verzeichnisreihenfolge abgeflacht und nur beibehalten, wenn der Name noch nicht gesehen wurde.\n- Mitgelieferte Agents werden gegen dasselbe Set gefiltert und nur hinzugefügt, wenn sie noch nicht gesehen wurden.\n\nAuswirkungen:\n\n- Projekt überschreibt Benutzer für dieselbe Quellfamilie.\n- Höher priorisierte Quellfamilie überschreibt niedrigere (`.xcsh` vor `.claude`, etc.).\n- Nicht-mitgelieferte Agents überschreiben mitgelieferte Agents mit demselben Namen.\n- Namensabgleich ist case-sensitiv (`Task` und `task` sind unterschiedlich).\n- Innerhalb eines Verzeichnisses werden Markdown-Dateien vor der Deduplizierung in lexikographischer Dateinamen-Reihenfolge gelesen.\n\n## Verhalten bei ungültigen/fehlenden Agent-Dateien\n\nPro Verzeichnis (`loadAgentsFromDir`):\n\n- unlesbar/fehlendes Verzeichnis: wird als leer behandelt (`readdir(...).catch(() => [])`)\n- Dateilesung oder Parse-Fehler: Warnung wird protokolliert, Datei wird übersprungen\n- Parse-Pfad verwendet `parseAgent(..., level: \"warn\")`\n\nVerhalten bei Frontmatter-Fehlern stammt von `parseFrontmatter`:\n\n- Parse-Fehler auf `warn`-Level protokolliert eine Warnung\n- Parser fällt auf einen einfachen `key: value`-Zeilenparser zurück\n- wenn erforderliche Felder weiterhin fehlen, schlägt `parseAgentFields` fehl, dann wird `AgentParsingError` geworfen und vom Aufrufer abgefangen (Datei wird übersprungen)\n\nNettoeffekt: Eine fehlerhafte benutzerdefinierte Agent-Datei bricht die Erkennung anderer Dateien nicht ab.\n\n## Agent-Suche und -Auswahl\n\nDie Suche ist eine exakte Namens-Linearsuche:\n\n- `getAgent(agents, name)` => `agents.find(a => a.name === name)`\n\nBei der Task-Ausführung (`TaskTool.execute`):\n\n1. Agents werden zum Aufrufzeitpunkt neu erkannt (`discoverAgents(this.session.cwd)`)\n2. angeforderter `params.agent` wird über `getAgent` aufgelöst\n3. fehlender Agent gibt eine sofortige Tool-Antwort zurück:\n   - `Unknown agent \"...\". Available: ...`\n   - kein Subprozess wird gestartet\n\n### Beschreibung vs. Erkennung zur Ausführungszeit\n\n`TaskTool.create()` erstellt die Tool-Beschreibung aus den Erkennungsergebnissen zum Initialisierungszeitpunkt (`buildDescription`).\n\n`execute()` erkennt Agents erneut. Daher kann sich die Laufzeitmenge von dem unterscheiden, was in der früheren Tool-Beschreibung aufgelistet war, wenn sich Agent-Dateien während der Sitzung geändert haben.\n\n## Structured-Output-Schutzmechanismen und Schema-Vorrang\n\nVorrang des Laufzeit-Ausgabeschemas in `TaskTool.execute`:\n\n1. Agent-Frontmatter `output`\n2. Task-Aufruf `params.schema`\n3. Eltern-Sitzung `outputSchema`\n\n(`effectiveOutputSchema = effectiveAgent.output ?? outputSchema ?? this.session.outputSchema`)\n\nDer Prompt-Schutzmechanismus-Text in `src/prompts/tools/task.md` warnt vor Abweichungsverhalten bei Structured-Output-Agents (`explore`, `reviewer`): Ausgabeformat-Anweisungen im Prosatext können mit dem eingebauten Schema in Konflikt geraten und `null`-Ausgaben erzeugen.\n\nDies ist eine Orientierungshilfe, keine harte Laufzeit-Validierungslogik in `discoverAgents`.\n\n## Interaktion mit der Befehlserkennung\n\n`src/task/commands.ts` ist eine parallele Infrastruktur für Workflow-Befehle (keine Agent-Definitionen), folgt aber dem gleichen Gesamtmuster:\n\n- zuerst von Capability-Providern erkennen\n- nach Namen mit First-Wins deduplizieren\n- mitgelieferte Befehle anhängen, wenn noch nicht gesehen\n- exakte Namenssuche über `getCommand`\n\nIn `src/task/index.ts` werden Befehls-Hilfsfunktionen zusammen mit Agent-Erkennungs-Hilfsfunktionen re-exportiert. Die Agent-Erkennung selbst hängt zur Laufzeit nicht von der Befehlserkennung ab.\n\n## Verfügbarkeitsbeschränkungen jenseits der Erkennung\n\nEin Agent kann erkennbar, aber dennoch nicht ausführbar sein, aufgrund von Ausführungsschutzmechanismen.\n\n### Eltern-Spawn-Richtlinie\n\n`TaskTool.execute` prüft `session.getSessionSpawns()`:\n\n- `\"*\"` => alle erlauben\n- `\"\"` => alle ablehnen\n- CSV-Liste => nur aufgelistete Namen erlauben\n\nBei Ablehnung: sofortige `Cannot spawn '...'. Allowed: ...`-Antwort.\n\n### Blockierte Selbstrekursions-Umgebungssperre\n\n`PI_BLOCKED_AGENT` wird bei der Tool-Konstruktion gelesen. Wenn die Anfrage übereinstimmt, wird die Ausführung mit einer Rekursionsverhinderungsnachricht abgelehnt.\n\n### Rekursionstiefe-Steuerung (Task-Tool-Verfügbarkeit in Kind-Sitzungen)\n\nIn `runSubprocess` (`src/task/executor.ts`):\n\n- Tiefe wird aus `taskDepth` berechnet\n- `task.maxRecursionDepth` steuert den Grenzwert\n- bei maximaler Tiefe:\n  - `task`-Tool wird aus der Kind-Tool-Liste entfernt\n  - Kind-`spawns`-Umgebung wird auf leer gesetzt\n\nTiefere Ebenen können daher keine weiteren Tasks spawnen, selbst wenn die Agent-Definition `spawns` enthält.\n\n## Planmodus-Vorbehalt (aktuelle Implementierung)\n\n`TaskTool.execute` berechnet einen `effectiveAgent` für den Planmodus (stellt Planmodus-Prompt voran, erzwingt schreibgeschützte Tool-Teilmenge, leert Spawns), aber `runSubprocess` wird mit `agent` anstelle von `effectiveAgent` aufgerufen.\n\nAktuelle Auswirkung:\n\n- Model-Override / Thinking-Level / Ausgabeschema werden von `effectiveAgent` abgeleitet\n- System-Prompt und Tool-/Spawn-Einschränkungen von `effectiveAgent` werden in diesem Aufrufpfad nicht weitergegeben\n\nDies ist ein Implementierungsvorbehalt, den man kennen sollte, wenn man Erwartungen an das Planmodus-Verhalten liest.\n",
	"de/sessions/compaction.md": "---\ntitle: Kompaktierung und Branch-Zusammenfassungen\ndescription: >-\n  Kontextfenster-Kompaktierung und Branch-Zusammenfassungsgenerierung für\n  langlebige Sitzungen.\nsidebar:\n  order: 5\n  label: Kompaktierung\ni18n:\n  sourceHash: dae425a900d8\n  translator: machine\n---\n\n# Kompaktierung und Branch-Zusammenfassungen\n\nKompaktierung und Branch-Zusammenfassungen sind die beiden Mechanismen, die lange Sitzungen nutzbar halten, ohne den Kontext früherer Arbeit zu verlieren.\n\n- **Kompaktierung** schreibt alte Historie in eine Zusammenfassung auf dem aktuellen Branch um.\n- **Branch-Zusammenfassung** erfasst den Kontext verlassener Branches während der `/tree`-Navigation.\n\nBeide werden als Sitzungseinträge persistiert und beim Neuaufbau der LLM-Eingabe wieder in Benutzer-Kontextnachrichten umgewandelt.\n\n## Wichtige Implementierungsdateien\n\n- `src/session/compaction/compaction.ts`\n- `src/session/compaction/branch-summarization.ts`\n- `src/session/compaction/pruning.ts`\n- `src/session/compaction/utils.ts`\n- `src/session/session-manager.ts`\n- `src/session/agent-session.ts`\n- `src/session/messages.ts`\n- `src/extensibility/hooks/types.ts`\n- `src/config/settings-schema.ts`\n\n## Sitzungseintrags-Modell\n\nKompaktierung und Branch-Zusammenfassungen sind erstklassige Sitzungseinträge, keine einfachen Assistant/User-Nachrichten.\n\n- `CompactionEntry`\n  - `type: \"compaction\"`\n  - `summary`, optionale `shortSummary`\n  - `firstKeptEntryId` (Kompaktierungsgrenze)\n  - `tokensBefore`\n  - optionale `details`, `preserveData`, `fromExtension`\n- `BranchSummaryEntry`\n  - `type: \"branch_summary\"`\n  - `fromId`, `summary`\n  - optionale `details`, `fromExtension`\n\nWenn der Kontext neu aufgebaut wird (`buildSessionContext`):\n\n1. Die neueste Kompaktierung auf dem aktiven Pfad wird in eine `compactionSummary`-Nachricht umgewandelt.\n2. Behaltene Einträge von `firstKeptEntryId` bis zum Kompaktierungspunkt werden wieder eingefügt.\n3. Spätere Einträge auf dem Pfad werden angehängt.\n4. `branch_summary`-Einträge werden in `branchSummary`-Nachrichten umgewandelt.\n5. `custom_message`-Einträge werden in `custom`-Nachrichten umgewandelt.\n\nDiese benutzerdefinierten Rollen werden dann in `convertToLlm()` unter Verwendung der statischen Vorlagen in LLM-seitige Benutzernachrichten transformiert:\n\n- `prompts/compaction/compaction-summary-context.md`\n- `prompts/compaction/branch-summary-context.md`\n\n## Kompaktierungs-Pipeline\n\n### Auslöser\n\nDie Kompaktierung kann auf drei Arten ausgeführt werden:\n\n1. **Manuell**: `/compact [instructions]` ruft `AgentSession.compact(...)` auf.\n2. **Automatische Überlauf-Wiederherstellung**: nach einem Assistant-Fehler, der als Kontextüberlauf erkannt wird.\n3. **Automatische Schwellenwert-Kompaktierung**: nach einem erfolgreichen Zug, wenn der Kontext den Schwellenwert überschreitet.\n\n### Kompaktierungsform (visuell)\n\n```text\nBefore compaction:\n\n  entry:  0     1     2     3      4     5     6      7      8     9\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┘\n                └────────┬───────┘ └──────────────┬──────────────┘\n               messagesToSummarize            kept messages\n                                   ↑\n                          firstKeptEntryId (entry 4)\n\nAfter compaction (new entry appended):\n\n  entry:  0     1     2     3      4     5     6      7      8     9      10\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┬─────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │ cmp │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┴─────┘\n               └──────────┬──────┘ └──────────────────────┬───────────────────┘\n                 not sent to LLM                    sent to LLM\n                                                         ↑\n                                              starts from firstKeptEntryId\n\nWhat the LLM sees:\n\n  ┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐\n  │ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │\n  └────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘\n       ↑         ↑      └─────────────────┬────────────────┘\n    prompt   from cmp          messages from firstKeptEntryId\n```\n\n### Überlauf-Wiederholung vs. Schwellenwert-Kompaktierung\n\nDie beiden automatischen Pfade sind absichtlich unterschiedlich:\n\n- **Überlauf-Wiederholungs-Kompaktierung**\n  - Auslöser: Ein Assistant-Fehler des aktuellen Modells wird als Kontextüberlauf erkannt.\n  - Die fehlgeschlagene Assistant-Fehlernachricht wird vor der Wiederholung aus dem aktiven Agent-Zustand entfernt.\n  - Die automatische Kompaktierung wird mit `reason: \"overflow\"` und `willRetry: true` ausgeführt.\n  - Bei Erfolg setzt der Agent automatisch fort (`agent.continue()`) nach der Kompaktierung.\n\n- **Schwellenwert-Kompaktierung**\n  - Auslöser: `contextTokens > contextWindow - compaction.reserveTokens`.\n  - Wird mit `reason: \"threshold\"` und `willRetry: false` ausgeführt.\n  - Bei Erfolg wird, wenn `compaction.autoContinue !== false`, ein synthetischer Prompt injiziert:\n    - `\"Continue if you have next steps.\"`\n\n### Vor-Kompaktierungs-Bereinigung\n\nVor den Kompaktierungsprüfungen kann eine Tool-Ergebnis-Bereinigung ausgeführt werden (`pruneToolOutputs`).\n\nStandard-Bereinigungsrichtlinie:\n\n- Die neuesten `40_000` Tool-Ausgabe-Token schützen.\n- Mindestens `20_000` geschätzte Gesamteinsparungen an Token erfordern.\n- Niemals Tool-Ergebnisse von `skill` oder `read` bereinigen.\n\nBereinigte Tool-Ergebnisse werden ersetzt durch:\n\n- `[Output truncated - N tokens]`\n\nWenn die Bereinigung Einträge ändert, wird der Sitzungsspeicher neu geschrieben und der Agent-Nachrichtenzustand vor Kompaktierungsentscheidungen aktualisiert.\n\n### Grenz- und Schnittpunkt-Logik\n\n`prepareCompaction()` berücksichtigt nur Einträge seit dem letzten Kompaktierungseintrag (falls vorhanden).\n\n1. Vorherigen Kompaktierungsindex finden.\n2. `boundaryStart = prevCompactionIndex + 1` berechnen.\n3. `keepRecentTokens` unter Verwendung des gemessenen Nutzungsverhältnisses anpassen, wenn verfügbar.\n4. `findCutPoint()` über das Grenzfenster ausführen.\n\nGültige Schnittpunkte umfassen:\n\n- Nachrichteneinträge mit Rollen: `user`, `assistant`, `bashExecution`, `hookMessage`, `branchSummary`, `compactionSummary`\n- `custom_message`-Einträge\n- `branch_summary`-Einträge\n\nHarte Regel: Niemals bei `toolResult` schneiden.\n\nWenn sich nicht-Nachrichten-Metadateneinträge unmittelbar vor dem Schnittpunkt befinden (`model_change`, `thinking_level_change`, Labels usw.), werden diese in den behaltenen Bereich gezogen, indem der Schnittindex rückwärts verschoben wird, bis eine Nachricht oder Kompaktierungsgrenze erreicht ist.\n\n### Geteilte-Runde-Behandlung\n\nWenn der Schnittpunkt nicht am Beginn einer Benutzerrunde liegt, behandelt die Kompaktierung dies als geteilte Runde.\n\nDie Rundenbeginn-Erkennung behandelt folgende als Benutzerrunden-Grenzen:\n\n- `message.role === \"user\"`\n- `message.role === \"bashExecution\"`\n- `custom_message`-Eintrag\n- `branch_summary`-Eintrag\n\nDie Kompaktierung bei geteilten Runden erzeugt zwei Zusammenfassungen:\n\n1. Historie-Zusammenfassung (`messagesToSummarize`)\n2. Runden-Präfix-Zusammenfassung (`turnPrefixMessages`)\n\nDie endgültige gespeicherte Zusammenfassung wird zusammengeführt als:\n\n```markdown\n<history summary>\n\n---\n\n**Turn Context (split turn):**\n\n<turn prefix summary>\n```\n\n### Zusammenfassungsgenerierung\n\n`compact(...)` erstellt Zusammenfassungen aus serialisiertem Konversationstext:\n\n1. Nachrichten über `convertToLlm()` konvertieren.\n2. Mit `serializeConversation()` serialisieren.\n3. In `<conversation>...</conversation>` einwickeln.\n4. Optional `<previous-summary>...</previous-summary>` einschließen.\n5. Optional Hook-Kontext als `<additional-context>`-Liste injizieren.\n6. Zusammenfassungs-Prompt mit `SUMMARIZATION_SYSTEM_PROMPT` ausführen.\n\nPrompt-Auswahl:\n\n- Erste Kompaktierung: `compaction-summary.md`\n- Iterative Kompaktierung mit vorheriger Zusammenfassung: `compaction-update-summary.md`\n- Geteilte-Runde zweiter Durchlauf: `compaction-turn-prefix.md`\n- Kurze UI-Zusammenfassung: `compaction-short-summary.md`\n\nRemote-Zusammenfassungsmodus:\n\n- Wenn `compaction.remoteEndpoint` gesetzt ist, sendet die Kompaktierung einen POST:\n  - `{ systemPrompt, prompt }`\n- Erwartet JSON, das mindestens `{ summary }` enthält.\n\n### Dateioperations-Kontext in Zusammenfassungen\n\nDie Kompaktierung verfolgt kumulative Dateiaktivitäten anhand von Assistant-Tool-Aufrufen:\n\n- `read(path)` → Lese-Menge\n- `write(path)` → Geändert-Menge\n- `edit(path)` → Geändert-Menge\n\nKumulatives Verhalten:\n\n- Vorherige Kompaktierungsdetails werden nur einbezogen, wenn der vorherige Eintrag pi-generiert ist (`fromExtension !== true`).\n- Bei geteilten Runden werden auch Runden-Präfix-Dateioperationen einbezogen.\n- `readFiles` schließt Dateien aus, die auch geändert wurden.\n\nDer Zusammenfassungstext erhält Datei-Tags, die über die Prompt-Vorlage angehängt werden:\n\n```xml\n<read-files>\n...\n</read-files>\n<modified-files>\n...\n</modified-files>\n```\n\n### Persistierung und Neuladung\n\nNach der Zusammenfassungsgenerierung (oder einer vom Hook bereitgestellten Zusammenfassung) führt die Agent-Sitzung folgendes aus:\n\n1. `CompactionEntry` mit `appendCompaction(...)` anhängen.\n2. Kontext über `buildSessionContext()` neu aufbauen.\n3. Live-Agent-Nachrichten durch den neu aufgebauten Kontext ersetzen.\n4. `session_compact`-Hook-Ereignis auslösen.\n\n## Branch-Zusammenfassungs-Pipeline\n\nDie Branch-Zusammenfassung ist an die Baumnavigation gebunden, nicht an den Token-Überlauf.\n\n### Auslöser\n\nWährend `navigateTree(...)`:\n\n1. Verlassene Einträge vom alten Blatt zum gemeinsamen Vorfahren mittels `collectEntriesForBranchSummary(...)` berechnen.\n2. Wenn der Aufrufer eine Zusammenfassung angefordert hat (`options.summarize`), Zusammenfassung vor dem Blattwechsel generieren.\n3. Wenn eine Zusammenfassung existiert, diese am Navigationsziel mittels `branchWithSummary(...)` anhängen.\n\nOperativ wird dies üblicherweise durch den `/tree`-Ablauf gesteuert, wenn `branchSummary.enabled` aktiviert ist.\n\n### Branch-Wechsel-Form (visuell)\n\n```text\nTree before navigation:\n\n         ┌─ B ─ C ─ D (old leaf, being abandoned)\n    A ───┤\n         └─ E ─ F (target)\n\nCommon ancestor: A\nEntries to summarize: B, C, D\n\nAfter navigation with summary:\n\n         ┌─ B ─ C ─ D ─ [summary of B,C,D]\n    A ───┤\n         └─ E ─ F (new leaf)\n```\n\n### Vorbereitung und Token-Budget\n\n`generateBranchSummary(...)` berechnet das Budget als:\n\n- `tokenBudget = model.contextWindow - branchSummary.reserveTokens`\n\n`prepareBranchEntries(...)` führt dann folgendes aus:\n\n1. Erster Durchlauf: Kumulative Dateioperationen aus allen zusammenzufassenden Einträgen sammeln, einschließlich vorheriger pi-generierter `branch_summary`-Details.\n2. Zweiter Durchlauf: Von neuest nach ältestgehend Nachrichten hinzufügen, bis das Token-Budget erreicht ist.\n3. Bevorzugt die Beibehaltung des jüngsten Kontexts.\n4. Kann dennoch große Zusammenfassungseinträge nahe der Budgetgrenze für Kontinuität einschließen.\n\nKompaktierungseinträge werden während der Branch-Zusammenfassungseingabe als Nachrichten (`compactionSummary`) einbezogen.\n\n### Zusammenfassungsgenerierung und Persistierung\n\nBranch-Zusammenfassung:\n\n1. Konvertiert und serialisiert ausgewählte Nachrichten.\n2. Wickelt sie in `<conversation>` ein.\n3. Verwendet benutzerdefinierte Anweisungen, falls bereitgestellt, ansonsten `branch-summary.md`.\n4. Ruft das Zusammenfassungsmodell mit `SUMMARIZATION_SYSTEM_PROMPT` auf.\n5. Stellt `branch-summary-preamble.md` voran.\n6. Hängt Dateioperations-Tags an.\n\nDas Ergebnis wird als `BranchSummaryEntry` mit optionalen Details (`readFiles`, `modifiedFiles`) gespeichert.\n\n## Erweiterungs- und Hook-Berührungspunkte\n\n### `session_before_compact`\n\nVor-Kompaktierungs-Hook.\n\nKann:\n\n- Kompaktierung abbrechen (`{ cancel: true }`)\n- Vollständige benutzerdefinierte Kompaktierungs-Nutzlast bereitstellen (`{ compaction: CompactionResult }`)\n\n### `session.compacting`\n\nPrompt/Kontext-Anpassungs-Hook für die Standard-Kompaktierung.\n\nKann zurückgeben:\n\n- `prompt` (Basis-Zusammenfassungs-Prompt überschreiben)\n- `context` (zusätzliche Kontextzeilen, die in `<additional-context>` injiziert werden)\n- `preserveData` (wird im Kompaktierungseintrag gespeichert)\n\n### `session_compact`\n\nNach-Kompaktierungs-Benachrichtigung mit gespeichertem `compactionEntry` und `fromExtension`-Flag.\n\n### `session_before_tree`\n\nWird bei der Baumnavigation vor der Standard-Branch-Zusammenfassungsgenerierung ausgeführt.\n\nKann:\n\n- Navigation abbrechen\n- Benutzerdefinierte `{ summary: { summary, details } }` bereitstellen, die verwendet wird, wenn der Benutzer eine Zusammenfassung angefordert hat\n\n### `session_tree`\n\nNach-Navigations-Ereignis, das neues/altes Blatt und optionalen Zusammenfassungseintrag bereitstellt.\n\n## Laufzeitverhalten und Fehler-Semantik\n\n- Manuelle Kompaktierung bricht zuerst die aktuelle Agent-Operation ab.\n- `abortCompaction()` bricht sowohl manuelle als auch automatische Kompaktierungs-Controller ab.\n- Automatische Kompaktierung löst Start-/End-Sitzungsereignisse für UI-/Zustandsaktualisierungen aus.\n- Automatische Kompaktierung kann mehrere Modellkandidaten ausprobieren und transiente Fehler wiederholen.\n- Überlauffehler werden vom generischen Wiederholungspfad ausgeschlossen, da sie durch die Kompaktierung behandelt werden.\n- Wenn die automatische Kompaktierung fehlschlägt:\n  - Überlaufpfad gibt aus: `Context overflow recovery failed: ...`\n  - Schwellenwertpfad gibt aus: `Auto-compaction failed: ...`\n- Branch-Zusammenfassung kann über ein Abbruchsignal (z.B. Escape) abgebrochen werden und gibt ein abgebrochenes Navigationsergebnis zurück.\n\n## Einstellungen und Standardwerte\n\nAus `settings-schema.ts`:\n\n- `compaction.enabled` = `true`\n- `compaction.reserveTokens` = `16384`\n- `compaction.keepRecentTokens` = `20000`\n- `compaction.autoContinue` = `true`\n- `compaction.remoteEndpoint` = `undefined`\n- `branchSummary.enabled` = `false`\n- `branchSummary.reserveTokens` = `16384`\n\nDiese Werte werden zur Laufzeit von `AgentSession` und den Kompaktierungs-/Branch-Zusammenfassungsmodulen verwendet.\n",
	"de/sessions/handoff-generation-pipeline.md": "---\ntitle: Handoff-Generierungs-Pipeline\ndescription: >-\n  Handoff-Generierungs-Pipeline zur Erstellung portabler\n  Sitzungszusammenfassungen für die Teamzusammenarbeit.\nsidebar:\n  order: 8\n  label: Handoff-Pipeline\ni18n:\n  sourceHash: 03666084b5ac\n  translator: machine\n---\n\n# `/handoff`-Generierungs-Pipeline\n\nDieses Dokument beschreibt, wie der Coding-Agent `/handoff` derzeit implementiert: Auslösepfad, Generierungs-Prompt, Erfassung der Fertigstellung, Sitzungswechsel und Kontextwiedereinspeisung.\n\n## Geltungsbereich\n\nAbgedeckt:\n\n- Interaktive `/handoff`-Befehlsweiterleitung\n- `AgentSession.handoff()`-Lebenszyklus und Zustandsübergänge\n- Wie die Handoff-Ausgabe aus der Assistenten-Ausgabe erfasst wird\n- Wie alte/neue Sitzungen Handoff-Daten unterschiedlich persistieren\n- UI-Verhalten bei Erfolg, Abbruch und Fehler\n\nNicht abgedeckt:\n\n- Generische Baumnavigation/Branch-Interna\n- Nicht-Handoff-Sitzungsbefehle (`/new`, `/fork`, `/resume`)\n\n## Implementierungsdateien\n\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n\n## Auslösepfad\n\n1. `/handoff` wird in den integrierten Slash-Command-Metadaten (`slash-commands.ts`) mit optionalem Inline-Hinweis deklariert: `[focus instructions]`.\n2. Bei der interaktiven Eingabeverarbeitung (`InputController`) wird eingegebener Text, der `/handoff` oder `/handoff ...` entspricht, vor der normalen Prompt-Übermittlung abgefangen.\n3. Der Editor wird geleert und `handleHandoffCommand(customInstructions?)` wird aufgerufen.\n4. `CommandController.handleHandoffCommand` führt eine Vorabprüfung anhand der aktuellen Einträge durch:\n   - Zählt Einträge vom `type === \"message\"`.\n   - Wenn `< 2`, wird gewarnt: `Nothing to hand off (no messages yet)` und die Funktion kehrt zurück.\n\nDieselbe Mindestinhalt-Prüfung existiert erneut innerhalb von `AgentSession.handoff()` und wirft einen Fehler, wenn sie verletzt wird. Dies dupliziert die Sicherheit sowohl auf der UI- als auch auf der Sitzungsebene.\n\n## End-to-End-Lebenszyklus\n\n### 1) Handoff-Generierung starten\n\n`AgentSession.handoff(customInstructions?)`:\n\n- Liest die aktuellen Branch-Einträge (`sessionManager.getBranch()`)\n- Validiert die Mindestanzahl an Nachrichten (`>= 2`)\n- Erstellt `#handoffAbortController`\n- Baut einen festen, Inline-Prompt auf, der ein strukturiertes Handoff-Dokument anfordert (`Goal`, `Constraints & Preferences`, `Progress`, `Key Decisions`, `Critical Context`, `Next Steps`)\n- Fügt `Additional focus: ...` hinzu, wenn benutzerdefinierte Anweisungen bereitgestellt werden\n\nDer Prompt wird gesendet über:\n\n```ts\nawait this.prompt(handoffPrompt, { expandPromptTemplates: false });\n```\n\n`expandPromptTemplates: false` verhindert die Slash-/Prompt-Template-Expansion dieser internen Anweisungsnutzlast.\n\n### 2) Fertigstellung erfassen\n\nVor dem Senden des Prompts abonniert `handoff()` Sitzungsereignisse und wartet auf `agent_end`.\n\nBei `agent_end` extrahiert es den Handoff-Text aus dem Agentenzustand, indem es rückwärts nach der neuesten `assistant`-Nachricht sucht und dann alle `content`-Blöcke mit `type === \"text\"` mit `\\n` verkettet.\n\nWichtige Extraktionsannahmen:\n\n- Nur Textblöcke werden verwendet; Nicht-Text-Inhalte werden ignoriert.\n- Es wird angenommen, dass die neueste Assistenten-Nachricht der Handoff-Generierung entspricht.\n- Es werden keine Markdown-Abschnitte geparst oder Formatkonformität validiert.\n- Wenn die Assistenten-Ausgabe keine Textblöcke enthält, wird der Handoff als fehlend behandelt.\n\n### 3) Abbruchprüfungen\n\n`handoff()` gibt `undefined` zurück, wenn eine der folgenden Bedingungen zutrifft:\n\n- kein erfasster Handoff-Text, oder\n- `#handoffAbortController.signal.aborted` ist true\n\nEs löscht `#handoffAbortController` immer im `finally`-Block.\n\n### 4) Neue Sitzung erstellen\n\nWenn Text erfasst und nicht abgebrochen wurde:\n\n1. Aktuelle Sitzung flushen (`sessionManager.flush()`)\n2. Eine brandneue Sitzung starten (`sessionManager.newSession()`)\n3. In-Memory-Agentenzustand zurücksetzen (`agent.reset()`)\n4. `agent.sessionId` an neue Sitzungs-ID binden\n5. Warteschlangen-Kontext-Arrays leeren (`#steeringMessages`, `#followUpMessages`, `#pendingNextTurnMessages`)\n6. Todo-Erinnerungszähler zurücksetzen\n\n`newSession()` erstellt einen neuen Header und eine leere Eintragsliste (Leaf wird auf `null` zurückgesetzt). Im Handoff-Pfad wird keine `parentSession` übergeben.\n\n### 5) Handoff-Kontext-Einspeisung\n\nDas generierte Handoff-Dokument wird umschlossen und der neuen Sitzung als `custom_message`-Eintrag hinzugefügt:\n\n```text\n<handoff-context>\n...handoff text...\n</handoff-context>\n\nThe above is a handoff document from a previous session. Use this context to continue the work seamlessly.\n```\n\nEinfügeaufruf:\n\n```ts\nthis.sessionManager.appendCustomMessageEntry(\"handoff\", handoffContent, true);\n```\n\nSemantik:\n\n- `customType`: `\"handoff\"`\n- `display`: `true` (sichtbar beim TUI-Neuaufbau)\n- Eintragstyp: `custom_message` (nimmt am LLM-Kontext teil)\n\n### 6) Aktiven Agentenkontext neu aufbauen\n\nNach der Einspeisung:\n\n1. `sessionManager.buildSessionContext()` löst die Nachrichtenliste für das aktuelle Leaf auf\n2. `agent.replaceMessages(sessionContext.messages)` macht die eingespeiste Handoff-Nachricht zum aktiven Kontext\n3. Die Methode gibt `{ document: handoffText }` zurück\n\nAn diesem Punkt enthält der aktive LLM-Kontext in der neuen Sitzung die eingespeiste Handoff-Nachricht, nicht das alte Transkript.\n\n## Persistenzmodell: alte Sitzung vs. neue Sitzung\n\n### Alte Sitzung\n\nWährend der Generierung bleibt die normale Nachrichtenpersistenz aktiv. Die Assistenten-Handoff-Antwort wird als regulärer `message`-Eintrag bei `message_end` persistiert.\n\nErgebnis: Die ursprüngliche Sitzung enthält den sichtbaren generierten Handoff als Teil des historischen Transkripts.\n\n### Neue Sitzung\n\nNach dem Sitzungsreset wird der Handoff als `custom_message` mit `customType: \"handoff\"` persistiert.\n\n`buildSessionContext()` konvertiert diesen Eintrag in eine Laufzeit-Custom/User-Kontext-Nachricht via `createCustomMessage(...)`, sodass er in zukünftigen Prompts der neuen Sitzung enthalten ist.\n\n## Controller/UI-Verhalten\n\n`CommandController.handleHandoffCommand`-Verhalten:\n\n- Ruft `await session.handoff(customInstructions)` auf\n- Wenn das Ergebnis `undefined` ist: `showError(\"Handoff cancelled\")`\n- Bei Erfolg:\n  - `rebuildChatFromMessages()` (lädt neuen Sitzungskontext, einschließlich eingespeistem Handoff)\n  - Invalidiert Statuszeile und oberen Editor-Rand\n  - Lädt Todos neu\n  - Hängt Erfolgs-Chatzeile an: `New session started with handoff context`\n- Bei Ausnahme:\n  - Wenn die Nachricht `\"Handoff cancelled\"` lautet oder der Fehlername `AbortError` ist: `showError(\"Handoff cancelled\")`\n  - Andernfalls: `showError(\"Handoff failed: <message>\")`\n- Fordert am Ende ein Rendern an\n\n## Abbruch-Semantik (aktuelles Verhalten)\n\n### Abbruchprimitiv auf Sitzungsebene\n\n`AgentSession` stellt bereit:\n\n- `abortHandoff()` → bricht `#handoffAbortController` ab\n- `isGeneratingHandoff` → true, solange der Controller existiert\n\nWenn dieser Abbruchpfad verwendet wird, lehnt der Handoff-Subscriber mit `Error(\"Handoff cancelled\")` ab, und der Command-Controller bildet dies auf die Abbruch-UI ab.\n\n### Einschränkung des interaktiven `/handoff`-Pfads\n\nIn der aktuellen interaktiven Controller-Verdrahtung installiert `/handoff` keinen dedizierten Escape-Handler, der `abortHandoff()` aufruft (im Gegensatz zu Kompaktierungs-/Branch-Summary-Pfaden, die `editor.onEscape` temporär überschreiben).\n\nPraktische Auswirkung:\n\n- Es gibt Abbruchunterstützung auf Sitzungsebene, aber keinen handoff-spezifischen Keybinding-Hook im `/handoff`-Befehlspfad.\n- Benutzerunterbrechung kann weiterhin über breitere Agent-Abbruchpfade erfolgen, aber das ist nicht derselbe explizite Abbruchkanal, der von `abortHandoff()` verwendet wird.\n\n## Abgebrochen vs. fehlgeschlagener Handoff\n\nAktuelle UI-Klassifikation:\n\n- **Abgebrochen/Storniert**\n  - `abortHandoff()`-Pfad löst `\"Handoff cancelled\"` aus, oder\n  - geworfener `AbortError`\n  - UI zeigt `Handoff cancelled`\n\n- **Fehlgeschlagen**\n  - jeder andere geworfene Fehler von `handoff()` / Prompt-Pipeline (Modell-/API-Validierungsfehler, Laufzeitausnahmen usw.)\n  - UI zeigt `Handoff failed: ...`\n\nZusätzliche Nuance: Wenn die Generierung abgeschlossen wird, aber kein Text extrahiert wird, gibt `handoff()` `undefined` zurück und der Controller meldet aktuell **Abgebrochen**, nicht **Fehlgeschlagen**.\n\n## Kurzsitzungs- und Mindestinhalt-Schutzmaßnahmen\n\nZwei Schutzmaßnahmen verhindern Handoffs mit geringem Signal:\n\n- UI-Ebene (`handleHandoffCommand`): warnt und kehrt frühzeitig zurück bei `< 2` Nachrichteneinträgen\n- Sitzungsebene (`handoff()`): wirft dieselbe Bedingung als Fehler\n\nDies vermeidet die Erstellung einer neuen Sitzung mit leerem/nahezu leerem Handoff-Kontext.\n\n## Zustandsübergangs-Zusammenfassung\n\nÜbergeordneter Zustandsfluss:\n\n1. Interaktiver Slash-Befehl wird abgefangen\n2. Vorabprüfung der Nachrichtenanzahl\n3. `#handoffAbortController` wird erstellt (`isGeneratingHandoff = true`)\n4. Interner Handoff-Prompt wird übermittelt (sichtbar im Chat als normale Assistenten-Generierung)\n5. Bei `agent_end` wird der letzte Assistenten-Text extrahiert\n6. Wenn fehlend/abgebrochen → `undefined` zurückgeben oder Abbruch-Fehlerpfad\n7. Wenn vorhanden:\n   - Alte Sitzung flushen\n   - Neue leere Sitzung erstellen\n   - Laufzeit-Warteschlangen/Zähler zurücksetzen\n   - `custom_message(handoff)` anhängen\n   - Aktive Agenten-Nachrichten neu aufbauen und ersetzen\n8. Controller baut Chat-UI neu auf und meldet Erfolg\n9. `#handoffAbortController` wird geleert (`isGeneratingHandoff = false`)\n\n## Bekannte Annahmen und Einschränkungen\n\n- Handoff-Extraktion ist heuristisch: \"letzte Assistenten-Textblöcke\"; keine strukturelle Validierung.\n- Keine harte Prüfung, ob das generierte Markdown dem angeforderten Abschnittsformat folgt.\n- Fehlender extrahierter Text wird in der Controller-UX als Abbruch gemeldet.\n- Der interaktive `/handoff`-Fluss hat derzeit keine dedizierte Escape→`abortHandoff()`-Bindung.\n- Neue Sitzungs-Herkunftsmetadaten (`parentSession`) werden von diesem Pfad nicht gesetzt.\n",
	"de/sessions/memory.md": "---\ntitle: Autonomer Speicher\ndescription: >-\n  Autonomes Speichersystem zur Persistierung von Benutzereinstellungen,\n  Projektkontext und Feedback über Sitzungen hinweg.\nsidebar:\n  order: 7\n  label: Autonomer Speicher\ni18n:\n  sourceHash: 2aa9f516aa1e\n  translator: machine\n---\n\n# Autonomer Speicher\n\nWenn aktiviert, extrahiert der Agent automatisch dauerhaftes Wissen aus vergangenen Sitzungen und injiziert eine kompakte Zusammenfassung in jede neue Sitzung. Im Laufe der Zeit baut er einen projektbezogenen Wissensspeicher auf — technische Entscheidungen, wiederkehrende Arbeitsabläufe, Fallstricke — der ohne manuellen Aufwand übertragen wird.\n\nStandardmäßig deaktiviert. Aktivierung über `/settings` oder `config.yml`:\n\n```yaml\nmemories:\n  enabled: true\n```\n\n## Verwendung\n\n### Was injiziert wird\n\nBeim Sitzungsstart wird, sofern eine Speicherzusammenfassung für das aktuelle Projekt existiert, diese als **Memory Guidance**-Block in den System-Prompt injiziert. Der Agent wird angewiesen:\n\n- Den Speicher als heuristischen Kontext zu behandeln — nützlich für Prozesse und frühere Entscheidungen, aber nicht maßgeblich für den aktuellen Repository-Zustand.\n- Den Pfad des Speicher-Artefakts zu zitieren, wenn der Speicher den Plan ändert, und ihn mit aktuellen Repository-Belegen abzugleichen, bevor gehandelt wird.\n- Den Repository-Zustand und Benutzeranweisungen zu bevorzugen, wenn diese mit dem Speicher in Konflikt stehen; widersprüchlichen Speicher als veraltet zu behandeln.\n\n### Speicher-Artefakte lesen\n\nDer Agent kann Speicherdateien direkt über `memory://`-URLs mit dem `read`-Tool lesen:\n\n| URL | Inhalt |\n|---|---|\n| `memory://root` | Kompakte Zusammenfassung, die beim Start injiziert wird |\n| `memory://root/MEMORY.md` | Vollständiges Langzeitspeicher-Dokument |\n| `memory://root/skills/<name>/SKILL.md` | Ein generiertes Skill-Playbook |\n\n### `/memory`-Slash-Befehl\n\n| Unterbefehl | Wirkung |\n|---|---|\n| `view` | Aktuelle Speicher-Injektions-Nutzlast anzeigen |\n| `clear` / `reset` | Alle Speicherdaten und generierten Artefakte löschen |\n| `enqueue` / `rebuild` | Konsolidierung beim nächsten Start erzwingen |\n\n## Funktionsweise\n\nSpeicher werden durch eine Hintergrund-Pipeline aufgebaut, die beim Start oder manuell über einen Slash-Befehl ausgelöst wird.\n\n**Phase 1 — Extraktion pro Sitzung:** Für jede vergangene Sitzung, die sich seit der letzten Verarbeitung geändert hat, liest ein Modell den Sitzungsverlauf und extrahiert dauerhaftes Signal: technische Entscheidungen, Einschränkungen, behobene Fehler, wiederkehrende Arbeitsabläufe. Sitzungen, die zu neu, zu alt oder derzeit aktiv sind, werden übersprungen. Jede Extraktion erzeugt einen rohen Speicherblock und eine kurze Zusammenfassung für diese Sitzung.\n\n**Phase 2 — Konsolidierung:** Nach der Extraktion liest ein zweiter Modelldurchlauf alle sitzungsbezogenen Extraktionen und erzeugt drei Ausgaben, die auf die Festplatte geschrieben werden:\n\n- `MEMORY.md` — ein kuratiertes Langzeitspeicher-Dokument\n- `memory_summary.md` — der kompakte Text, der beim Sitzungsstart injiziert wird\n- `skills/` — wiederverwendbare prozedurale Playbooks, jeweils in einem eigenen Unterverzeichnis\n\nPhase 2 verwendet eine Sperre, um doppelte Ausführung zu verhindern, wenn mehrere Prozesse gleichzeitig starten. Veraltete Skill-Verzeichnisse aus früheren Durchläufen werden automatisch bereinigt.\n\nAlle Ausgaben werden vor dem Schreiben auf die Festplatte auf Geheimnisse geprüft.\n\n### Extraktionsverhalten\n\nDas Extraktions- und Konsolidierungsverhalten wird vollständig durch statische Prompt-Dateien in `src/prompts/memories/` gesteuert.\n\n| Datei | Zweck | Variablen |\n|---|---|---|\n| `stage_one_system.md` | System-Prompt für die sitzungsbezogene Extraktion | — |\n| `stage_one_input.md` | Benutzer-Turn-Vorlage, die Sitzungsinhalte umschließt | `{{thread_id}}`, `{{response_items_json}}` |\n| `consolidation.md` | Prompt für die sitzungsübergreifende Konsolidierung | `{{raw_memories}}`, `{{rollout_summaries}}` |\n| `read_path.md` | Speicher-Leitfaden, der in Live-Sitzungen injiziert wird | `{{memory_summary}}` |\n\n### Modellauswahl\n\nDer Speicher nutzt das bestehende Modellrollen-System.\n\n| Phase | Rolle | Zweck |\n|---|---|---|\n| Phase 1 (Extraktion) | `default` | Wissensextraktion pro Sitzung |\n| Phase 2 (Konsolidierung) | `smol` | Sitzungsübergreifende Synthese |\n\nWenn `smol` nicht konfiguriert ist, fällt Phase 2 auf die `default`-Rolle zurück.\n\n## Konfiguration\n\n| Einstellung | Standard | Beschreibung |\n|---|---|---|\n| `memories.enabled` | `false` | Hauptschalter |\n| `memories.maxRolloutAgeDays` | `30` | Sitzungen, die älter als dieser Wert sind, werden nicht verarbeitet |\n| `memories.minRolloutIdleHours` | `12` | Sitzungen, die kürzlicher als dieser Wert aktiv waren, werden übersprungen |\n| `memories.maxRolloutsPerStartup` | `64` | Obergrenze für verarbeitete Sitzungen bei einem einzelnen Start |\n| `memories.summaryInjectionTokenLimit` | `5000` | Maximale Token-Anzahl der Zusammenfassung, die in den System-Prompt injiziert wird |\n\nZusätzliche Einstellungsmöglichkeiten (Parallelität, Sperrdauern, Token-Budgets) sind in der Konfiguration für fortgeschrittene Nutzung verfügbar.\n\n## Wichtige Dateien\n\n- `src/memories/index.ts` — Pipeline-Orchestrierung, Injektion, Slash-Befehl-Verarbeitung\n- `src/memories/storage.ts` — SQLite-basierte Aufgabenwarteschlange und Thread-Registrierung\n- `src/prompts/memories/` — Speicher-Prompt-Vorlagen\n- `src/internal-urls/memory-protocol.ts` — `memory://`-URL-Handler\n",
	"de/sessions/non-compaction-retry-policy.md": "---\ntitle: Automatische Wiederholungsrichtlinie (ohne Komprimierung)\ndescription: >-\n  Automatische Wiederholungsrichtlinie für transiente API-Fehler außerhalb des\n  Komprimierungspfads.\nsidebar:\n  order: 6\n  label: Wiederholungsrichtlinie\ni18n:\n  sourceHash: 8999a0258dd8\n  translator: machine\n---\n\n# Automatische Wiederholungsrichtlinie (ohne Komprimierung)\n\nDieses Dokument beschreibt den standardmäßigen API-Fehler-Wiederholungspfad in `AgentSession`.\n\nEs schließt explizit die Kontextüberlauf-Wiederherstellung über automatische Komprimierung aus. Überläufe werden durch Komprimierungslogik behandelt und sind separat in [`compaction.md`](./compaction.md) dokumentiert.\n\n## Implementierungsdateien\n\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/config/settings-schema.ts`](../../packages/coding-agent/src/config/settings-schema.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts)\n- [`../src/modes/rpc/rpc-client.ts`](../../packages/coding-agent/src/modes/rpc/rpc-client.ts)\n- [`../src/modes/rpc/rpc-types.ts`](../../packages/coding-agent/src/modes/rpc/rpc-types.ts)\n\n## Abgrenzung gegenüber der Komprimierung\n\nWiederholung und Komprimierung werden aus demselben `agent_end`-Pfad geprüft, sind jedoch bewusst getrennt:\n\n1. `agent_end` prüft die letzte Assistenznachricht.\n2. `#isRetryableError(...)` wird zuerst ausgeführt.\n3. Wenn eine Wiederholung eingeleitet wird, werden Komprimierungsprüfungen für diesen Durchlauf übersprungen.\n4. Kontextüberlauf-Fehler sind hart von der Wiederholungsklassifikation ausgeschlossen (`isContextOverflow(...)` unterbricht die Wiederholung vorzeitig).\n5. Überläufe fallen daher zu `#checkCompaction(...)` durch, anstatt zur Standard-Wiederholung.\n\nZusammenfassung: Überlastungs-, Ratenlimit-, Server- und netzwerkbedingte Fehler verwenden diese Wiederholungsrichtlinie; Kontextfenster-Überläufe verwenden die Komprimierungs-Wiederherstellung.\n\n## Wiederholungsklassifikation\n\n`#isRetryableError(...)` erfordert alle der folgenden Bedingungen:\n\n- Assistent-`stopReason === \"error\"`\n- `errorMessage` ist vorhanden\n- Nachricht ist **kein** Kontextüberlauf\n- `errorMessage` entspricht `#isRetryableErrorMessage(...)`\n\nAktueller Satz wiederholbarer Muster (regex-basiert):\n\n- overloaded\n- rate limit / usage limit / too many requests\n- HTTP-ähnliche Serverklassen: 429, 500, 502, 503, 504\n- service unavailable / server error / internal error\n- connection error / fetch failed\n- `retry delay`-Formulierungen\n\nDies ist eine zeichenkettenbasierte Musterklassifikation, keine typisierte Anbieter-Fehlercodes.\n\n## Wiederholungslebenszyklus und Zustandsübergänge\n\nSitzungszustand, der von der Wiederholung verwendet wird:\n\n- `#retryAttempt: number` (`0` bedeutet inaktiv)\n- `#retryPromise: Promise<void> | undefined` (verfolgt den laufenden Wiederholungslebenszyklus)\n- `#retryResolve: (() => void) | undefined` (löst `#retryPromise` auf)\n- `#retryAbortController: AbortController | undefined` (bricht den Backoff-Schlaf ab)\n\nAblauf (`#handleRetryableError`):\n\n1. Einstellungsgruppe `retry` lesen.\n2. Wenn `retry.enabled === false`, sofort stoppen (`false`, keine Wiederholung gestartet).\n3. `#retryAttempt` erhöhen.\n4. `#retryPromise` einmalig erstellen (erster Versuch in einer Kette).\n5. Wenn der Versuch `retry.maxRetries` überschreitet, abschließendes Fehlerereignis ausgeben und stoppen.\n6. Verzögerung berechnen: `retry.baseDelayMs * 2^(attempt-1)`.\n7. Bei Nutzungslimit-Fehlern Wiederholungshinweise analysieren und Auth-Speicher aufrufen (`markUsageLimitReached(...)`); wenn Anbieter-/Modellwechsel erfolgreich, Verzögerung auf `0` erzwingen.\n8. `auto_retry_start` ausgeben.\n9. Die abschließende Assistent-Fehlermeldung aus dem Agent-Laufzeitzustand entfernen (in der persistierten Sitzungshistorie weiterhin gespeichert).\n10. Mit Abbruchunterstützung schlafen.\n11. Beim Aufwachen `agent.continue()` über `setTimeout(..., 0)` planen.\n\n### Was die Wiederholungszähler zurücksetzt\n\n`#retryAttempt` wird in diesen Fällen auf `0` zurückgesetzt:\n\n- erste erfolgreiche, nicht fehlerhafte, nicht abgebrochene Assistenznachricht nach begonnenen Wiederholungen (gibt `auto_retry_end { success: true }` aus)\n- Wiederholungsabbruch während des Backoff-Schlafs\n- Pfad bei überschrittener maximaler Wiederholungsanzahl\n\n`#retryPromise` wird aufgelöst/bereinigt, wenn die Wiederholungskette endet (Erfolg, Abbruch oder Maximum überschritten), über `#resolveRetry()`.\n\n## Backoff und Semantik der maximalen Versuche\n\nEinstellungen:\n\n- `retry.enabled` (Standard: `true`)\n- `retry.maxRetries` (Standard: `3`)\n- `retry.baseDelayMs` (Standard: `2000`)\n\nVersuchsnummerierung:\n\n- Versuchszähler wird vor der Maximalprüfung erhöht\n- Startereignisse verwenden den aktuellen Versuch (1-basiert)\n- Das Ende-Ereignis bei überschrittenem Maximum meldet `attempt: this.#retryAttempt - 1` (letzter versuchter Wiederholungszähler)\n\nBackoff-Sequenz mit Standardeinstellungen:\n\n- Versuch 1: 2000 ms\n- Versuch 2: 4000 ms\n- Versuch 3: 8000 ms\n\nVerzögerungsüberschreibungseingaben werden nur im Nutzungslimit-Behandlungspfad verwendet und nur zur Beeinflussung der Auth-Speicher-Modell-/Kontowechselentscheidung. Im Haupt-Nicht-Komprimierungs-Wiederholungspfad bleibt der Backoff eine lokale exponentielle Verzögerung, sofern der Wechsel nicht erfolgreich ist (`delayMs = 0`).\n\n## Abbruchmechanismen\n\n### Expliziter Wiederholungsabbruch\n\n`abortRetry()`:\n\n- bricht `#retryAbortController` ab (falls vorhanden)\n- löst das Wiederholungsversprechen auf (`#resolveRetry()`), damit Wartende entsperrt werden\n\nWenn der Abbruch während des Schlafs eintritt, gibt der Catch-Pfad aus:\n\n- `auto_retry_end { success: false, finalError: \"Retry cancelled\" }`\n- setzt Versuch/Controller zurück\n\n### Interaktion mit globalem Operationsabbruch\n\n`abort()` ruft `abortRetry()` auf, bevor der aktive Agent-Stream abgebrochen wird. Dies stellt sicher, dass der Wiederholungs-Backoff abgebrochen wird, wenn der Benutzer einen allgemeinen Abbruch auslöst.\n\n### TUI-Interaktion\n\nBei `auto_retry_start` führt EventController Folgendes aus:\n\n- tauscht den `Esc`-Handler gegen `session.abortRetry()` aus\n- rendert Ladetext: `Retrying (attempt/maxAttempts) in Ns… (esc to cancel)`\n\nBei `auto_retry_end` wird der vorherige `Esc`-Handler wiederhergestellt und der Ladezustand geleert.\n\n## Streaming- und Prompt-Abschlussverhalten\n\n`prompt()` wartet letztendlich nach der Rückgabe von `agent.prompt(...)` auf `#waitForRetry()`.\n\nAuswirkung:\n\n- Ein Prompt-Aufruf wird erst vollständig aufgelöst, wenn eine gestartete Wiederholungskette endet (Erfolg/Fehler/Abbruch)\n- Der Wiederholungslebenszyklus ist Teil einer logischen Prompt-Ausführungsgrenze\n\nDies verhindert, dass Aufrufer einen sich wiederholenden Durchlauf zu früh als abgeschlossen behandeln.\n\n## Steuerung: Einstellungen und RPC\n\n### Konfigurationsparameter\n\nIn dem Einstellungsschema unter der Wiederholungsgruppe definiert:\n\n- `retry.enabled`\n- `retry.maxRetries`\n- `retry.baseDelayMs`\n\nProgrammatische Umschalter in der Sitzung:\n\n- `setAutoRetryEnabled(enabled)` schreibt `retry.enabled`\n- `autoRetryEnabled` liest `retry.enabled`\n- `isRetrying` meldet, ob das Wiederholungslebenszyklus-Versprechen aktiv ist\n\n### RPC-Steuerung\n\nRPC-Befehlsoberfläche:\n\n- `set_auto_retry` → `session.setAutoRetryEnabled(command.enabled)`\n- `abort_retry` → `session.abortRetry()`\n\nClient-Hilfsfunktionen:\n\n- `RpcClient.setAutoRetry(enabled)`\n- `RpcClient.abortRetry()`\n\nBeide Befehle geben Erfolgsantworten zurück; Fortschritts-/Fehlerdetails der Wiederholung kommen aus gestreamten Sitzungsereignissen, nicht aus Befehlsantwort-Nutzdaten.\n\n## Ereignisausgabe und Fehlerdarstellung\n\nWiederholungsereignisse auf Sitzungsebene:\n\n- `auto_retry_start { attempt, maxAttempts, delayMs, errorMessage }`\n- `auto_retry_end { success, attempt, finalError? }`\n\nWeitergabe:\n\n- ausgegeben über `AgentSession.subscribe(...)`\n- als Erweiterungsereignisse an den Erweiterungs-Runner weitergeleitet\n- im RPC-Modus direkt als JSON-Ereignisobjekte weitergeleitet (`session.subscribe(event => output(event))`)\n- in der TUI von `EventController` für Lade-/Fehler-UI verarbeitet\n\nDarstellung abschließender Fehler:\n\n- Bei überschrittenem Maximum oder Abbruch gilt `auto_retry_end.success === false`\n- TUI zeigt: `Retry failed after N attempts: <finalError>`\n- Erweiterungen/Hooks empfangen `auto_retry_end` mit denselben Feldern\n- RPC-Konsumenten empfangen dasselbe Ereignisobjekt im stdout-Stream\n\n## Permanente Stoppbedingungen\n\nDie Wiederholung stoppt und setzt nicht automatisch fort, wenn einer dieser Fälle eintritt:\n\n- `retry.enabled` ist false\n- Fehler ist nicht wiederholungsklassifiziert\n- Fehler ist Kontextüberlauf (an den Komprimierungspfad delegiert)\n- Maximale Wiederholungsanzahl überschritten\n- Benutzer bricht Wiederholung ab (`abort_retry` oder `Esc` während des Wiederholungs-Laders)\n- Globaler Abbruch (`abort`) bricht die Wiederholung zuerst ab\n\nEine neue Wiederholungskette kann nach dem Zurücksetzen der Zähler bei einem zukünftigen wiederholbaren Fehler erneut starten.\n\n## Betriebliche Einschränkungen\n\n- Die Klassifikation erfolgt durch Regex-Textabgleich; anbieterspezifische strukturierte Fehler werden hier nicht verwendet.\n- Die Wiederholung entfernt den fehlgeschlagenen Assistent-Fehler aus dem **Laufzeitkontext** vor dem Fortsetzen, die Sitzungshistorie behält diesen Fehlereintrag jedoch weiterhin.\n- `RpcSessionState` stellt derzeit `autoCompactionEnabled`, aber kein `autoRetryEnabled`-Feld bereit; RPC-Aufrufer müssen ihren eigenen Umschaltzustand verfolgen oder die Einstellungen über andere APIs abfragen.\n",
	"de/sessions/session-operations-export-share-fork-resume.md": "---\ntitle: 'Sitzungsoperationen: Exportieren, Dump, Teilen, Forken, Fortsetzen'\ndescription: >-\n  Sitzungsoperationen zum Exportieren, Teilen, Forken und Fortsetzen von\n  Konversationen.\nsidebar:\n  order: 3\n  label: Operationen\ni18n:\n  sourceHash: e3c210b29c3e\n  translator: machine\n---\n\n# Sitzungsoperationen: export, dump, share, fork, resume/continue\n\nDieses Dokument beschreibt das für Operatoren sichtbare Verhalten der Sitzungsoperationen Export/Teilen/Fork/Fortsetzen, wie sie derzeit implementiert sind.\n\n## Implementierungsdateien\n\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/export/html/index.ts`](../../packages/coding-agent/src/export/html/index.ts)\n- [`../src/export/custom-share.ts`](../../packages/coding-agent/src/export/custom-share.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n\n## Operationsmatrix\n\n| Operation | Einstiegspfad | Sitzungsmutation | Sitzungsdatei-Erstellung/-Wechsel | Ausgabeartefakt |\n|---|---|---|---|---|\n| `/dump` | Interaktiver Slash-Befehl | Nein | Nein | Zwischenablage-Text |\n| `/export [path]` | Interaktiver Slash-Befehl | Nein | Nein | HTML-Datei |\n| `--export <session.jsonl> [outputPath]` | CLI-Start-Schnellpfad | Keine Laufzeit-Sitzungsmutation | Keine aktive Sitzung; liest Zieldatei | HTML-Datei |\n| `/share` | Interaktiver Slash-Befehl | Nein | Nein | Temporäre HTML + Share-URL/Gist |\n| `/fork` | Interaktiver Slash-Befehl | Ja (aktive Sitzungsidentität ändert sich) | Erstellt neue Sitzungsdatei und wechselt die aktuelle Sitzung dorthin (nur im persistenten Modus) | Kopiert das Artefaktverzeichnis in den neuen Sitzungsnamensraum, falls vorhanden |\n| `/resume` | Interaktiver Slash-Befehl | Ja (aktiver In-Memory-Zustand wird ersetzt) | Wechselt zu ausgewählter bestehender Sitzungsdatei | Keines |\n| `--resume` | CLI-Start (Auswahldialog) | Ja, nach Sitzungserstellung | Öffnet ausgewählte bestehende Sitzungsdatei | Keines |\n| `--resume <id\\|path>` | CLI-Start | Ja, nach Sitzungserstellung | Öffnet bestehende Sitzung; projektübergreifender Fall kann in aktuelles Projekt forken | Keines |\n| `--continue` | CLI-Start | Ja, nach Sitzungserstellung | Öffnet Terminal-Breadcrumb oder zuletzt verwendete Sitzung; erstellt eine neue, falls keine existiert | Keines |\n\n## Export und Dump\n\n### `/export [outputPath]` (interaktiv)\n\nAblauf:\n\n1. `InputController` leitet `/export...` an `CommandController.handleExportCommand` weiter.\n2. Der Befehl teilt anhand von Leerzeichen und verwendet nur das erste Argument nach `/export` als `outputPath`.\n3. `AgentSession.exportToHtml()` ruft `exportSessionToHtml(sessionManager, state, { outputPath, themeName })` auf.\n4. Bei Erfolg zeigt die UI den Pfad an und öffnet die Datei im Browser.\n\nVerhaltensdetails:\n\n- Die Argumente `--copy`, `clipboard` und `copy` werden explizit mit einem Hinweis auf `/dump` abgelehnt.\n- Der Export bettet den Sitzungs-Header/Einträge/Blatt sowie den aktuellen `systemPrompt` und Tool-Beschreibungen aus dem Agent-Zustand ein.\n- Während des Exports werden keine Sitzungseinträge hinzugefügt.\n\nEinschränkung:\n\n- Das Argument-Parsing basiert auf Leerzeichen (`text.split(/\\s+/)`), sodass Pfade mit Leerzeichen in Anführungszeichen von diesem Befehlspfad nicht als einzelner Pfad erhalten bleiben.\n\n### `--export <inputSessionFile> [outputPath]` (CLI)\n\nAblauf in `main.ts`:\n\n1. Wird früh behandelt (vor interaktivem/Sitzungs-Start).\n2. Ruft `exportFromFile(inputPath, outputPath?)` auf.\n3. `SessionManager.open(inputPath)` lädt die Einträge, dann wird HTML generiert und geschrieben.\n4. Der Prozess gibt `Exported to: ...` aus und beendet sich.\n\nVerhaltensdetails:\n\n- Eine fehlende Eingabedatei wird als `File not found: <path>` angezeigt.\n- Dieser Pfad erstellt keine `AgentSession` und mutiert keine laufende Sitzung.\n\n### `/dump` (interaktiver Zwischenablage-Export)\n\nAblauf:\n\n1. `CommandController.handleDumpCommand()` ruft `session.formatSessionAsText()` auf.\n2. Bei leerem String wird `No messages to dump yet.` gemeldet.\n3. Andernfalls wird über natives `copyToClipboard` in die Zwischenablage kopiert.\n\nDer Dump-Inhalt umfasst:\n\n- System-Prompt\n- Aktives Modell/Denk-Level\n- Tool-Definitionen + Parameter\n- Benutzer-/Assistenten-Nachrichten\n- Denkblöcke und Tool-Aufrufe\n- Tool-Ergebnisse und Ausführungsblöcke (außer `excludeFromContext` Bash/Python-Einträge)\n- Benutzerdefinierte/Hook-/Dateierwähnungs-/Branch-Zusammenfassungs-/Kompaktierungs-Zusammenfassungseinträge\n\nDurch das Dumpen werden keine Sitzungspersistenz-Änderungen vorgenommen.\n\n## Teilen\n\n`/share` ist ausschließlich interaktiv und beginnt immer mit dem Export der aktuellen Sitzung in eine temporäre HTML-Datei.\n\n### Phase 1: Temporärer Export\n\n- Temporärer Dateipfad: `${os.tmpdir()}/${Snowflake.next()}.html`\n- Verwendet `session.exportToHtml(tmpFile)`\n- Falls der Export fehlschlägt (insbesondere bei In-Memory-Sitzungen), endet das Teilen mit einem Fehler.\n\n### Phase 2: Benutzerdefinierter Share-Handler (falls vorhanden)\n\n`loadCustomShare()` prüft `~/.xcsh/agent` auf den ersten vorhandenen Kandidaten:\n\n- `share.ts`\n- `share.js`\n- `share.mjs`\n\nAnforderungen:\n\n- Das Modul muss als Default-Export eine Funktion `(htmlPath) => Promise<CustomShareResult | string | undefined>` bereitstellen.\n\nFalls vorhanden und gültig:\n\n- Die UI wechselt in den `Sharing...`-Ladezustand.\n- Ergebnisinterpretation des Handlers:\n  - String => wird als URL behandelt, angezeigt und geöffnet\n  - Objekt => `url` und/oder `message` werden angezeigt; `url` wird geöffnet\n  - `undefined`/falsy => generische Meldung `Session shared`\n- Die temporäre Datei wird nach Abschluss gelöscht.\n\nKritisches Fallback-Verhalten:\n\n- Falls ein benutzerdefinierter Handler existiert, aber das Laden fehlschlägt, gibt der Befehl einen Fehler aus und kehrt zurück.\n- Falls ein benutzerdefinierter Handler ausgeführt wird und einen Fehler wirft, gibt der Befehl einen Fehler aus und kehrt zurück.\n- In beiden Fehlerfällen wird **nicht** auf GitHub Gist zurückgefallen.\n- Das Gist-Fallback findet nur statt, wenn kein benutzerdefiniertes Share-Skript existiert.\n\n### Phase 3: Standard-Gist-Fallback\n\nNur wenn kein benutzerdefinierter Share-Handler gefunden wird:\n\n1. Validiert `gh auth status`.\n2. Zeigt `Creating gist...`-Ladeanzeige.\n3. Führt `gh gist create --public=false <tmpFile>` aus.\n4. Parst die Gist-URL, leitet die Gist-ID ab, erstellt die Vorschau-URL `https://gistpreview.github.io/?<id>`.\n5. Zeigt sowohl Vorschau- als auch Gist-URLs an; öffnet die Vorschau.\n\nAbbruch-/Abort-Semantik beim Teilen:\n\n- Der Ladeindikator hat einen `onAbort`-Hook, der die Editor-UI wiederherstellt und `Share cancelled` meldet.\n- Dem zugrunde liegenden `gh gist create`-Befehl wird in diesem Codepfad kein Abort-Signal übergeben; der Abbruch erfolgt auf UI-Ebene und wird nach Rückkehr des Befehls geprüft.\n\n## Fork\n\n`/fork` erstellt eine neue Sitzung aus der aktuellen und wechselt die aktive Sitzungsidentität.\n\n### Vorbedingungen und sofortige Prüfungen\n\n- Falls der Agent streamt, wird `/fork` mit einer Warnung abgelehnt.\n- UI-Status-/Ladeindikatoren werden vor der Operation gelöscht.\n\n### Ablauf auf Sitzungsebene\n\n`AgentSession.fork()`:\n\n1. Emittiert `session_before_switch` mit `reason: \"fork\"` (abbrechbar).\n2. Leert ausstehende Schreibvorgänge.\n3. Ruft `SessionManager.fork()` auf.\n4. Kopiert das Artefaktverzeichnis vom alten Sitzungsnamensraum in den neuen Namensraum (Best-Effort; Kopierfehler, die nicht ENOENT sind, werden protokolliert, sind aber nicht fatal).\n5. Aktualisiert `agent.sessionId`.\n6. Emittiert `session_switch` mit `reason: \"fork\"`.\n\nVerhalten von `SessionManager.fork()`:\n\n- Erfordert den persistenten Modus und eine bestehende Sitzungsdatei.\n- Erstellt eine neue Sitzungs-ID und einen neuen JSONL-Dateipfad.\n- Schreibt den Header mit folgenden Änderungen neu:\n  - neue `id`\n  - neuer Zeitstempel\n  - `cwd` unverändert\n  - `parentSession` wird auf die vorherige Sitzungs-ID gesetzt\n- Behält alle Nicht-Header-Einträge in der neuen Datei unverändert bei.\n\n### Nicht-persistentes Verhalten\n\n- Der In-Memory-Sitzungsmanager gibt `undefined` von `fork()` zurück.\n- `AgentSession.fork()` gibt `false` zurück.\n- Die UI meldet `Fork failed (session not persisted or cancelled)`.\n\n## Fortsetzen und Weiterführen\n\n## Interaktives `/resume`\n\nAblauf:\n\n1. Öffnet den Sitzungsauswahldialog, befüllt über `SessionManager.list(currentCwd, currentSessionDir)`.\n2. Bei Auswahl ruft `SelectorController.handleResumeSession(sessionPath)` `session.switchSession(sessionPath)` auf.\n3. Die UI löscht/baut Chat und Todos neu auf und meldet dann `Resumed session`.\n\nHinweise:\n\n- Dieser Auswahldialog listet nur Sitzungen im aktuellen Sitzungsverzeichnis-Bereich auf.\n- Er verwendet keine globale projektübergreifende Suche.\n\n## CLI `--resume`\n\n### `--resume` (ohne Wert)\n\n- `main.ts` listet Sitzungen für das aktuelle cwd/sessionDir auf und öffnet den Auswahldialog.\n- Der ausgewählte Pfad wird mit `SessionManager.open(selectedPath)` vor der Sitzungserstellung geöffnet.\n\n### `--resume <value>`\n\nAuflösungsreihenfolge in `createSessionManager()`:\n\n1. Falls der Wert wie ein Pfad aussieht (`/`, `\\` oder `.jsonl`), direkt öffnen.\n2. Andernfalls als ID-Präfix behandeln:\n   - im aktuellen Bereich suchen (`SessionManager.list(cwd, sessionDir)`)\n   - falls nicht gefunden und kein explizites `sessionDir`, global suchen (`SessionManager.listAll()`)\n\nVerhalten bei projektübergreifendem ID-Match:\n\n- Falls das cwd der gefundenen Sitzung vom aktuellen cwd abweicht, fragt die CLI:\n  - `Session found in different project ... Fork into current directory? [y/N]`\n- Bei Ja: `SessionManager.forkFrom(match.path, cwd, sessionDir)` erstellt eine neue lokale geforkte Datei.\n- Bei Nein/Non-TTY-Standard: Der Befehl gibt einen Fehler aus.\n\n## CLI `--continue`\n\n`SessionManager.continueRecent(cwd, sessionDir)`:\n\n1. Löst das Sitzungsverzeichnis für das aktuelle cwd auf.\n2. Liest zuerst den Terminal-spezifischen Breadcrumb.\n3. Fällt auf die zuletzt geänderte Sitzungsdatei zurück.\n4. Öffnet die gefundene Sitzung; falls keine existiert, wird eine neue Sitzung erstellt.\n\nDies ist ein reines Start-Verhalten; es gibt keinen interaktiven `/continue`-Slash-Befehl.\n\n## Wie der Sitzungswechsel den Laufzeitzustand tatsächlich mutiert\n\n`AgentSession.switchSession(sessionPath)` führt den Laufzeitübergang durch, der von Resume-ähnlichen Operationen verwendet wird:\n\n1. Emittiert `session_before_switch` mit `reason: \"resume\"` und `targetSessionFile` (abbrechbar).\n2. Trennt die Agent-Event-Subscription und bricht laufende Arbeit ab.\n3. Löscht ausstehende Steering-/Follow-up-/Next-Turn-Nachrichten.\n4. Leert ausstehende Schreibvorgänge des Sitzungsmanagers.\n5. `sessionManager.setSessionFile(sessionPath)` und aktualisiert `agent.sessionId`.\n6. Erstellt den Sitzungskontext aus den geladenen Einträgen.\n7. Emittiert `session_switch` mit `reason: \"resume\"`.\n8. Ersetzt die Agent-Nachrichten aus dem Kontext.\n9. Stellt das Modell wieder her (falls in der aktuellen Registry verfügbar).\n10. Stellt das Denk-Level wieder her oder initialisiert es.\n11. Verbindet die Agent-Event-Subscription erneut.\n\nDurch `switchSession()` selbst wird keine neue Sitzungsdatei erstellt.\n\n## Event-Emissionen und Abbruchpunkte\n\n### Switch/Fork-Lebenszyklus-Hooks\n\nFür `newSession`, `fork` und `switchSession`:\n\n- Vorher-Event: `session_before_switch`\n  - Gründe: `new`, `fork`, `resume`\n  - Abbrechbar durch Rückgabe von `{ cancel: true }`\n- Nachher-Event: `session_switch`\n  - Gleicher Grund-Satz\n  - Enthält `previousSessionFile`\n\n`ExtensionRunner.emit()` kehrt beim ersten abbrechenden Vorher-Event-Ergebnis frühzeitig zurück.\n\n### `onSession`-Verhalten benutzerdefinierter Tools\n\nDie SDK-Bridge leitet Extension-Sitzungsevents an `onSession`-Callbacks benutzerdefinierter Tools weiter:\n\n- `session_switch` -> `onSession({ reason: \"switch\", previousSessionFile })`\n- `session_branch` -> `reason: \"branch\"`\n- `session_start` -> `reason: \"start\"`\n- `session_tree` -> `reason: \"tree\"`\n- `session_shutdown` -> `reason: \"shutdown\"`\n\nDiese Callbacks sind observational; sie brechen keinen Switch/Fork ab.\n\n### Weitere für dieses Dokument relevante Abbruchoberflächen\n\n- `/fork` wird während des Streamings blockiert (der Benutzer muss die aktuelle Antwort erst abwarten/abbrechen).\n- Der `/resume`-Auswahldialog kann vom Benutzer durch Schließen des Dialogs abgebrochen werden.\n- Projektübergreifendes `--resume <id>` kann durch Ablehnung der Fork-Aufforderung abgebrochen werden.\n- `/share` hat einen UI-Abbruchpfad (`Share cancelled`) für den Gist-Ablauf; es verdrahtet keine Prozess-Kill-Semantik für `gh gist create` in diesem Codepfad.\n\n## Nicht-persistentes (In-Memory) Sitzungsverhalten\n\nWenn der Sitzungsmanager mit `SessionManager.inMemory()` (`--no-session`) erstellt wird:\n\n- Der Sitzungsdateipfad ist nicht vorhanden.\n- `/export` und `/share` scheitern mit `Cannot export in-memory session to HTML` (weitergeleitet an die Befehlsfehler-UI).\n- `/fork` scheitert, da `SessionManager.fork()` Persistenz erfordert.\n- `/dump` funktioniert weiterhin, da es den In-Memory-Agent-Zustand serialisiert.\n- CLI-Resume/Continue-Semantiken werden umgangen, wenn `--no-session` gesetzt ist, da die Manager-Erstellung sofort In-Memory zurückgibt.\n\n## Bekannte Implementierungseinschränkungen (Stand aktueller Code)\n\n- `SelectorController.handleResumeSession()` prüft nicht das boolesche Ergebnis von `session.switchSession(...)`; ein durch einen Hook abgebrochener Wechsel kann dennoch den UI-Pfad \"Resumed session\" (Neuzeichnen/Status) durchlaufen.\n- Fehler beim benutzerdefinierten Teilen bei `/share` fallen nicht auf das Standard-Gist-Fallback zurück; sie beenden den Befehl mit einem Fehler.\n- Die Argument-Tokenisierung bei `/export` ist vereinfacht und erhält keine Pfade mit Leerzeichen in Anführungszeichen.\n",
	"de/sessions/session-switching-and-recent-listing.md": "---\ntitle: Sitzungswechsel und Liste der zuletzt verwendeten Sitzungen\ndescription: >-\n  Mechanismen zum Sitzungswechsel sowie die Auflistung zuletzt verwendeter\n  Sitzungen mit Such- und Filterfunktionen.\nsidebar:\n  order: 4\n  label: Wechseln & Zuletzt verwendet\ni18n:\n  sourceHash: aae56130b508\n  translator: machine\n---\n\n# Sitzungswechsel und Liste der zuletzt verwendeten Sitzungen\n\nDieses Dokument beschreibt, wie der Coding-Agent zuletzt verwendete Sitzungen ermittelt, `--resume`-Ziele auflöst, Sitzungsauswahldialoge anzeigt und die aktive Laufzeitsitzung wechselt.\n\nDer Schwerpunkt liegt auf dem aktuellen Implementierungsverhalten, einschließlich Fallback-Pfaden und Einschränkungen.\n\n## Implementierungsdateien\n\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/cli/session-picker.ts`](../../packages/coding-agent/src/cli/session-picker.ts)\n- [`../src/modes/components/session-selector.ts`](../../packages/coding-agent/src/modes/components/session-selector.ts)\n- [`../src/modes/controllers/selector-controller.ts`](../../packages/coding-agent/src/modes/controllers/selector-controller.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n\n## Ermittlung zuletzt verwendeter Sitzungen\n\n### Verzeichnisbereich\n\n`SessionManager` speichert Sitzungen standardmäßig in einem cwd-bezogenen Verzeichnis:\n\n- `~/.xcsh/agent/sessions/--<cwd-encoded>--/*.jsonl`\n\n`SessionManager.list(cwd, sessionDir?)` liest ausschließlich dieses Verzeichnis, sofern kein explizites `sessionDir` angegeben wird.\n\n### Zwei Auflistungspfade mit unterschiedlichen Nutzdaten\n\nEs gibt zwei verschiedene Auflistungs-Pipelines:\n\n1. `getRecentSessions(sessionDir, limit)` (Willkommens-/Zusammenfassungsansicht)\n   - Liest nur ein 4-KB-Präfix (`readTextPrefix(..., 4096)`) aus jeder Datei.\n   - Parst Header + früheste Benutzertextvorschau.\n   - Gibt ein schlankes `RecentSessionInfo`-Objekt mit verzögerten `name`- und `timeAgo`-Gettern zurück.\n   - Sortiert nach Datei-`mtime` absteigend.\n\n2. `SessionManager.list(...)` / `SessionManager.listAll()` (Sitzungsauswahl für Wiederaufnahme und ID-Abgleich)\n   - Liest vollständige Sitzungsdateien.\n   - Erstellt `SessionInfo`-Objekte (`id`, `cwd`, `title`, `messageCount`, `firstMessage`, `allMessagesText`, Zeitstempel).\n   - Verwirft Sitzungen mit null `message`-Einträgen.\n   - Sortiert nach `modified` absteigend.\n\n### Metadaten-Fallback-Verhalten\n\nFür aktuelle Zusammenfassungen (`RecentSessionInfo`):\n\n- Präferenz für den Anzeigenamen: `header.title` -> erste Benutzereingabe -> `header.id` -> Dateiname\n- Der Name wird für kompakte Anzeigen auf 40 Zeichen gekürzt\n- Steuerzeichen/Zeilenumbrüche werden aus titelabgeleiteten Namen entfernt/bereinigt\n\nFür `SessionInfo`-Listeneinträge:\n\n- `title` ist `header.title` oder der neueste Kompaktierungs-`shortSummary`\n- `firstMessage` ist der Text der ersten Benutzernachricht oder `\"(no messages)\"`\n\n## `--continue`-Auflösung und Terminalnavigations-Präferenz\n\n`SessionManager.continueRecent(cwd, sessionDir?)` löst das Ziel in dieser Reihenfolge auf:\n\n1. Terminalspezifischen Breadcrumb lesen (`~/.xcsh/agent/terminal-sessions/<terminal-id>`)\n2. Breadcrumb validieren:\n   - Das aktuelle Terminal kann identifiziert werden\n   - Das Breadcrumb-cwd stimmt mit dem aktuellen cwd überein (aufgelöster Pfadvergleich)\n   - Die referenzierte Datei ist noch vorhanden\n3. Wenn der Breadcrumb ungültig/fehlend ist, auf die neueste Datei nach mtime im Sitzungsverzeichnis zurückfallen (`findMostRecentSession`)\n4. Wenn keine Datei gefunden wird, eine neue Sitzung erstellen\n\nDie Terminal-ID-Ableitung bevorzugt den TTY-Pfad und greift auf umgebungsbasierte Bezeichner zurück (`KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION`).\n\nBreadcrumb-Schreibvorgänge erfolgen nach bestem Bemühen und sind nicht fatal.\n\n## Auflösung des Wiederaufnahmeziels beim Start (`main.ts`)\n\n### `--resume <value>`\n\n`createSessionManager(...)` verarbeitet einen zeichenkettenbasierten `--resume`-Wert in zwei Modi:\n\n1. Pfadähnlicher Wert (enthält `/`, `\\\\` oder endet mit `.jsonl`)\n   - direktes `SessionManager.open(sessionArg, parsed.sessionDir)`\n\n2. ID-Präfix-Wert\n   - Treffer in `SessionManager.list(cwd, sessionDir)` über `id.startsWith(sessionArg)` suchen\n   - Wenn kein lokaler Treffer und `sessionDir` nicht erzwungen, `SessionManager.listAll()` versuchen\n   - Der erste Treffer wird verwendet (keine Mehrdeutigkeitsabfrage)\n\nVerhalten bei projektübergreifenden Treffern:\n\n- Wenn das cwd der gefundenen Sitzung vom aktuellen cwd abweicht, fragt die CLI, ob in das aktuelle Projekt verzweigt werden soll\n- Ja -> `SessionManager.forkFrom(...)`\n- Nein -> Fehler wird ausgelöst (`Session \"...\" is in another project (...)`)\n\nKein Treffer -> Fehler wird ausgelöst (`Session \"...\" not found.`).\n\n### `--resume` (kein Wert)\n\nWird nach der anfänglichen Sitzungsmanager-Erstellung behandelt:\n\n1. Lokale Sitzungen mit `SessionManager.list(cwd, parsed.sessionDir)` auflisten\n2. Wenn leer: `No sessions found` ausgeben und frühzeitig beenden\n3. TUI-Auswahldialog öffnen (`selectSession`)\n4. Wenn abgebrochen: `No session selected` ausgeben und frühzeitig beenden\n5. Wenn ausgewählt: `SessionManager.open(selectedPath)`\n\n### `--continue`\n\nVerwendet `SessionManager.continueRecent(...)` direkt (Breadcrumb-First-Verhalten wie oben beschrieben).\n\n## Interna der auswahlbasierten Sitzungsauswahl\n\n## CLI-Auswahldialog (`src/cli/session-picker.ts`)\n\n`selectSession(sessions)` erstellt eine eigenständige TUI mit `SessionSelectorComponent` und löst genau einmal auf:\n\n- Auswahl -> gibt den ausgewählten Pfad zurück\n- Abbruch (Esc) -> gibt `null` zurück\n- Hartes Beenden (Ctrl+C-Pfad) -> stoppt die TUI und ruft `process.exit(0)` auf\n\n## Interaktiver In-Sitzungs-Auswahldialog (`SelectorController.showSessionSelector`)\n\nAblauf:\n\n1. Sitzungen aus dem aktuellen Sitzungsverzeichnis über `SessionManager.list(currentCwd, currentSessionDir)` abrufen\n2. `SessionSelectorComponent` im Editor-Bereich über `showSelector(...)` einbinden\n3. Rückrufe:\n   - Auswahl -> Auswahldialog schließen und `handleResumeSession(sessionPath)` aufrufen\n   - Abbruch -> Editor wiederherstellen und neu rendern\n   - Beenden -> `ctx.shutdown()`\n\n## Verhalten der Sitzungsauswahl-Komponente\n\n`SessionList` unterstützt:\n\n- Pfeil-/Seitennavigation\n- Eingabe zur Auswahl\n- Esc zum Abbrechen\n- Ctrl+C zum Beenden\n- Fuzzy-Suche über Sitzungs-ID/Titel/cwd/erste Nachricht/alle Nachrichten/Pfad\n\nDarstellungsverhalten bei leerer Liste:\n\n- Zeigt eine Meldung an, anstatt abzustürzen\n- Eingabe bei leerer Liste hat keine Wirkung (kein Rückruf)\n- Esc/Ctrl+C funktionieren weiterhin\n\nHinweis: Der UI-Text lautet `Press Tab to view all`, aber diese Komponente hat derzeit keinen Tab-Handler, und die aktuelle Verdrahtung listet nur Sitzungen im aktuellen Bereich auf.\n\n## Ausführung des Laufzeit-Sitzungswechsels (`AgentSession.switchSession`)\n\n`switchSession(sessionPath)` ist der zentrale In-Process-Wechselpfad.\n\nLebenszyklus/Zustandsübergang:\n\n1. `previousSessionFile` erfassen\n2. Hook-Ereignis `session_before_switch` auslösen (`reason: \"resume\"`, abbrechbar)\n3. Wenn abgebrochen -> `false` zurückgeben, kein Wechsel\n4. Vom aktuellen Agent-Ereignisstrom trennen\n5. Aktiven Generierungs-/Werkzeugablauf abbrechen\n6. Warteschlangen für Steuerungs-/Folgemeldungen/Nächste-Runde-Nachrichtenpuffer leeren\n7. Sitzungsschreiber leeren (`sessionManager.flush()`), um ausstehende Schreibvorgänge zu persistieren\n8. `sessionManager.setSessionFile(sessionPath)`\n   - Aktualisiert den Sitzungsdateizeiger\n   - Schreibt Terminal-Breadcrumb\n   - Lädt Einträge / migriert / löst Blobs auf / indiziert neu\n   - Bei fehlender/ungültiger Datei: Initialisiert eine neue Sitzung unter diesem Pfad und schreibt den Header neu\n9. `agent.sessionId` aktualisieren\n10. Kontext über `buildSessionContext()` neu aufbauen\n11. Hook-Ereignis `session_switch` auslösen (`reason: \"resume\"`, `previousSessionFile`)\n12. Agent-Nachrichten durch neu aufgebauten Kontext ersetzen\n13. Standardmodell aus `sessionContext.models.default` wiederherstellen, wenn verfügbar und im Modellregister vorhanden\n14. Denk-Ebene wiederherstellen:\n    - Wenn der Zweig bereits `thinking_level_change` enthält, gespeicherte Sitzungsebene anwenden\n    - Andernfalls Standard-Denk-Ebene aus Einstellungen ableiten, auf Modellkapazität begrenzen, setzen und neuen `thinking_level_change`-Eintrag anfügen\n15. Agent-Listener neu verbinden und `true` zurückgeben\n\n## UI-Zustandswiederherstellung nach interaktivem Wechsel\n\n`SelectorController.handleResumeSession` führt UI-Reset um `switchSession` herum durch:\n\n- Ladeanimation stoppen\n- Statuscontainer leeren\n- Ausstehende Nachrichten-UI und ausstehende Werkzeugzuordnung zurücksetzen\n- Streaming-Komponenten-/Nachrichtenreferenzen zurücksetzen\n- `session.switchSession(...)` aufrufen\n- Chat-Container leeren und aus Sitzungskontext neu rendern (`renderInitialMessages`)\n- Aufgaben aus neuen Sitzungsartefakten neu laden\n- `Resumed session` anzeigen\n\nDer sichtbare Gesprächs-/Aufgabenstatus wird also aus der neuen Sitzungsdatei neu aufgebaut.\n\n## Wiederaufnahme beim Start vs. In-Sitzungs-Wechsel\n\n### Wiederaufnahme beim Start (`--continue`, `--resume`, direktes Öffnen)\n\n- Die Sitzungsdatei wird vor `createAgentSession(...)` ausgewählt.\n- `sdk.ts` erstellt `existingSession = sessionManager.buildSessionContext()`.\n- Agent-Nachrichten werden einmalig während der Sitzungserstellung wiederhergestellt.\n- Modell/Denken werden während der Erstellung ausgewählt (einschließlich Wiederherstellungs-/Fallback-Logik).\n- Der interaktive Modus führt dann `#restoreModeFromSession()` aus, um den persistierten Moduszustand wieder aufzunehmen (derzeit plan/plan_paused).\n\n### In-Sitzungs-Wechsel (`/resume`-artiger Auswahlpfad)\n\n- Verwendet `AgentSession.switchSession(...)` in einer bereits laufenden `AgentSession`.\n- Nachrichten/Modell/Denken werden sofort an Ort und Stelle neu aufgebaut.\n- Hook-Ereignisse `session_before_switch`/`session_switch` werden ausgelöst.\n- UI-Chat/Aufgaben werden aktualisiert.\n- Nach dem Wechsel wird kein dedizierter Moduswiederherstellungsaufruf im Auswahlpfad durchgeführt; das Moduswiederaufnahmeverhalten ist nicht symmetrisch mit dem Start-`#restoreModeFromSession()`.\n\n## Fehler- und Randfall-Verhalten\n\n### Abbruchpfade\n\n- CLI-Auswahldialogabbruch -> gibt `null` zurück, Aufrufer gibt `No session selected` aus, Prozess endet frühzeitig.\n- Interaktiver Auswahldialogabbruch -> Editor wird wiederhergestellt, keine Sitzungsänderung.\n- Hook-Abbruch (`session_before_switch`) -> `switchSession()` gibt `false` zurück.\n\n### Leere Listenpfade\n\n- CLI `--resume` (kein Wert): Leere Liste gibt `No sessions found` aus und beendet.\n- Interaktiver Auswahldialog: Leere Liste zeigt Meldung an und bleibt abbrechbar.\n\n### Fehlende/ungültige Zielsitzungsdatei\n\nBeim Öffnen/Wechseln zu einem bestimmten Pfad (`setSessionFile`):\n\n- ENOENT -> wird als leer behandelt -> neue Sitzung wird unter diesem genauen Pfad initialisiert und persistiert.\n- Fehlerhafter/ungültiger Header (oder effektiv nicht lesbare geparste Einträge) -> wird als leer behandelt -> neue Sitzung wird initialisiert und persistiert.\n\nDies ist Wiederherstellungsverhalten, kein harter Fehler.\n\n### Schwerwiegende Fehler\n\nWechseln/Öffnen kann bei echten E/A-Fehlern (Berechtigungsfehler, Schreibfehler usw.) weiterhin Ausnahmen auslösen, die an die Aufrufer weitergegeben werden.\n\n### Einschränkungen beim ID-Präfix-Abgleich\n\n- Der ID-Abgleich verwendet `startsWith` und nimmt den ersten Treffer in der sortierten Liste.\n- Keine Mehrdeutigkeits-UI, wenn mehrere Sitzungen denselben Präfix teilen.\n- `SessionManager.list(...)` schließt Sitzungen mit null Nachrichten aus, sodass diese Sitzungen nicht über ID-Abgleich/Listenauswahl wiederaufgenommen werden können.\n",
	"de/sessions/session-tree-plan.md": "---\ntitle: Session-Baumarchitektur\ndescription: >-\n  Session-Baumarchitektur mit Verzweigung, Navigation und\n  Eltern-Kind-Konversationsbeziehungen.\nsidebar:\n  order: 2\n  label: Baumarchitektur\ni18n:\n  sourceHash: bd8b78d6c33a\n  translator: machine\n---\n\n# Session-Baumarchitektur (aktuell)\n\nReferenz: [session.md](./session.md)\n\nDieses Dokument beschreibt, wie die Session-Baumnavigation heute funktioniert: In-Memory-Baummodell, Blattbewegungsregeln, Verzweigungsverhalten und Extension-/Event-Integration.\n\n## Was dieses Subsystem ist\n\nDie Session wird als Append-only-Eintragsprotokoll gespeichert, aber das Laufzeitverhalten ist baumbasiert:\n\n- Jeder Nicht-Header-Eintrag hat `id` und `parentId`.\n- Die aktive Position ist `leafId` im `SessionManager`.\n- Das Anhängen eines Eintrags erzeugt immer ein Kind des aktuellen Blatts.\n- Verzweigung schreibt die Historie **nicht** um; sie ändert nur, wohin das Blatt vor dem nächsten Anhängen zeigt.\n\nSchlüsseldateien:\n\n- `src/session/session-manager.ts` — Baumdatenmodell, Traversierung, Blattbewegung, Branch-/Session-Extraktion\n- `src/session/agent-session.ts` — `/tree`-Navigationsablauf, Zusammenfassung, Hook-/Event-Emission\n- `src/modes/components/tree-selector.ts` — Interaktives Baum-UI-Verhalten und Filterung\n- `src/modes/controllers/selector-controller.ts` — Selector-Orchestrierung für `/tree` und `/branch`\n- `src/modes/controllers/input-controller.ts` — Befehlsrouting (`/tree`, `/branch`, Doppel-Escape-Verhalten)\n- `src/session/messages.ts` — Konvertierung von `branch_summary`-, `compaction`- und `custom_message`-Einträgen in LLM-Kontextnachrichten\n\n## Baumdatenmodell in `SessionManager`\n\nLaufzeitindizes:\n\n- `#byId: Map<string, SessionEntry>` — schnelle Suche für jeden Eintrag\n- `#leafId: string | null` — aktuelle Position im Baum\n- `#labelsById: Map<string, string>` — aufgelöste Labels nach Zieleintrags-ID\n\nBaum-APIs:\n\n- `getBranch(fromId?)` folgt den Elternverknüpfungen zur Wurzel und gibt den Wurzel→Knoten-Pfad zurück\n- `getTree()` gibt `SessionTreeNode[]` zurück (`entry`, `children`, `label`)\n  - Elternverknüpfungen werden zu Kind-Arrays\n  - Einträge mit fehlenden Eltern werden als Wurzeln behandelt\n  - Kinder werden älteste→neueste nach Zeitstempel sortiert\n- `getChildren(parentId)` gibt direkte Kinder zurück\n- `getLabel(id)` löst das aktuelle Label aus `labelsById` auf\n\n`getTree()` ist eine Laufzeitprojektion; die Persistenz bleibt bei Append-only-JSONL-Einträgen.\n\n## Blattbewegungssemantik\n\nEs gibt drei Blattbewegungs-Primitive:\n\n1. `branch(entryId)`\n   - Validiert, dass der Eintrag existiert\n   - Setzt `leafId = entryId`\n   - Es wird kein neuer Eintrag geschrieben\n\n2. `resetLeaf()`\n   - Setzt `leafId = null`\n   - Das nächste Anhängen erzeugt einen neuen Wurzeleintrag (`parentId = null`)\n\n3. `branchWithSummary(branchFromId, summary, details?, fromExtension?)`\n   - Akzeptiert `branchFromId: string | null`\n   - Setzt `leafId = branchFromId`\n   - Hängt einen `branch_summary`-Eintrag als Kind dieses Blatts an\n   - Wenn `branchFromId` `null` ist, wird `fromId` als `\"root\"` persistiert\n\n## `/tree`-Navigationsverhalten (gleiche Session-Datei)\n\n`AgentSession.navigateTree()` ist Navigation, kein Datei-Forking.\n\nAblauf:\n\n1. Ziel validieren und aufgegebenen Pfad berechnen (`collectEntriesForBranchSummary`)\n2. `session_before_tree` mit `TreePreparation` emittieren\n3. Optional aufgegebene Einträge zusammenfassen (Hook-bereitgestellte Zusammenfassung oder eingebauter Summarizer)\n4. Neues Blattziel berechnen:\n   - Auswahl einer **user**-Nachricht: Blatt bewegt sich zum Elternteil, und der Nachrichtentext wird für die Editor-Vorbefüllung zurückgegeben\n   - Auswahl einer **custom_message**: gleiche Regel wie bei User-Nachricht (Blatt = Elternteil, Text befüllt Editor vor)\n   - Auswahl eines anderen Eintrags: Blatt = ausgewählte Eintrags-ID\n5. Blattbewegung anwenden:\n   - mit Zusammenfassung: `branchWithSummary(newLeafId, ...)`\n   - ohne Zusammenfassung und `newLeafId === null`: `resetLeaf()`\n   - andernfalls: `branch(newLeafId)`\n6. Agentenkontext vom neuen Blatt neu aufbauen und `session_tree` emittieren\n\nWichtig: Zusammenfassungseinträge werden an der **neuen Navigationsposition** angehängt, nicht am Ende des aufgegebenen Zweigs.\n\n## `/branch`-Verhalten (neue Session-Datei)\n\n`/branch` und `/tree` sind absichtlich unterschiedlich:\n\n- `/tree` navigiert innerhalb der aktuellen Session-Datei.\n- `/branch` erstellt eine neue Session-Branch-Datei (oder einen In-Memory-Ersatz für den nicht-persistenten Modus).\n\nBenutzergerichteter `/branch`-Ablauf (`SelectorController.showUserMessageSelector` → `AgentSession.branch`):\n\n- Die Branch-Quelle muss eine **User-Nachricht** sein.\n- Der ausgewählte Benutzertext wird für die Editor-Vorbefüllung extrahiert.\n- Wenn die ausgewählte User-Nachricht die Wurzel ist (`parentId === null`): eine neue Session starten via `newSession({ parentSession: previousSessionFile })`.\n- Andernfalls: `createBranchedSession(selectedEntry.parentId)` um die Historie bis zur ausgewählten Prompt-Grenze zu forken.\n\nSpezifika von `SessionManager.createBranchedSession(leafId)`:\n\n- Baut den Wurzel→Blatt-Pfad via `getBranch(leafId)` auf; wirft einen Fehler wenn fehlend.\n- Schließt existierende `label`-Einträge vom kopierten Pfad aus.\n- Baut frische Label-Einträge aus aufgelösten `labelsById` für Einträge neu auf, die im Pfad verbleiben.\n- Persistenter Modus: schreibt eine neue JSONL-Datei und wechselt den Manager dorthin; gibt den neuen Dateipfad zurück.\n- In-Memory-Modus: ersetzt die In-Memory-Einträge; gibt `undefined` zurück.\n\n## Kontextrekonstruktion und Integration von Zusammenfassungen/Custom-Nachrichten\n\n`buildSessionContext()` (in `session-manager.ts`) löst den aktiven Wurzel→Blatt-Pfad auf und baut den effektiven LLM-Kontextzustand auf:\n\n- Verfolgt den letzten Thinking-/Model-/Mode-/TTSR-Zustand auf dem Pfad.\n- Behandelt die letzte Kompaktierung auf dem Pfad:\n  - gibt zuerst die Kompaktierungszusammenfassung aus\n  - spielt behaltene Nachrichten von `firstKeptEntryId` bis zum Kompaktierungspunkt ab\n  - spielt dann Post-Kompaktierungsnachrichten ab\n- Enthält `branch_summary`- und `custom_message`-Einträge als `AgentMessage`-Objekte.\n\n`session/messages.ts` bildet diese Nachrichtentypen dann für die Modelleingabe ab:\n\n- `branchSummary` und `compactionSummary` werden zu User-Rollen-Template-Kontextnachrichten\n- `custom`/`hookMessage` werden zu User-Rollen-Inhaltsnachrichten\n\nBaumbewegung ändert den Kontext also durch Änderung des aktiven Blattpfads, nicht durch Mutation alter Einträge.\n\n## Labels und Baum-UI-Verhalten\n\nLabel-Persistenz:\n\n- `appendLabelChange(targetId, label?)` schreibt `label`-Einträge in der aktuellen Blattkette.\n- `labelsById` wird sofort aktualisiert (setzen oder löschen).\n- `getTree()` löst das aktuelle Label für jeden zurückgegebenen Knoten auf.\n\nBaum-Selector-Verhalten (`tree-selector.ts`):\n\n- Flacht den Baum für die Navigation ab, behält die Hervorhebung des aktiven Pfads bei und priorisiert die Anzeige des aktiven Zweigs zuerst.\n- Unterstützt Filtermodi: `default`, `no-tools`, `user-only`, `labeled-only`, `all`.\n- Unterstützt Freitext-Suche über gerenderten semantischen Inhalt.\n- `Shift+L` öffnet die Inline-Label-Bearbeitung und schreibt via `appendLabelChange`.\n\nBefehlsrouting:\n\n- `/tree` öffnet immer den Baum-Selector.\n- `/branch` öffnet den User-Nachrichten-Selector, es sei denn `doubleEscapeAction=tree`, in diesem Fall wird ebenfalls die Baum-Selector-UX verwendet.\n\n## Extension- und Hook-Berührungspunkte für Baumoperationen\n\nBefehlszeit-Extension-API (`ExtensionCommandContext`):\n\n- `branch(entryId)` — gebranchigte Session-Datei erstellen\n- `navigateTree(targetId, { summarize? })` — innerhalb des aktuellen Baums/der Datei bewegen\n\nEvents rund um die Baumnavigation:\n\n- `session_before_tree`\n  - empfängt `TreePreparation`:\n    - `targetId`\n    - `oldLeafId`\n    - `commonAncestorId`\n    - `entriesToSummarize`\n    - `userWantsSummary`\n  - kann die Navigation abbrechen\n  - kann eine Zusammenfassungs-Payload bereitstellen, die anstelle des eingebauten Summarizers verwendet wird\n  - empfängt ein Abort-`signal` (Escape-Abbruchpfad)\n- `session_tree`\n  - emittiert `newLeafId`, `oldLeafId`\n  - enthält `summaryEntry` wenn eine Zusammenfassung erstellt wurde\n  - `fromExtension` gibt den Ursprung der Zusammenfassung an\n\nAngrenzende aber verwandte Lifecycle-Hooks:\n\n- `session_before_branch` / `session_branch` für den `/branch`-Ablauf\n- `session_before_compact`, `session.compacting`, `session_compact` für Kompaktierungseinträge, die später die Baum-Kontextrekonstruktion beeinflussen\n\n## Reale Einschränkungen und Randbedingungen\n\n- `branch()` kann nicht auf `null` zielen; verwenden Sie `resetLeaf()` für den Wurzel-vor-erstem-Eintrag-Zustand.\n- `branchWithSummary()` unterstützt `null` als Ziel und zeichnet `fromId: \"root\"` auf.\n- Die Auswahl des aktuellen Blatts im Baum-Selector ist ein No-Op.\n- Zusammenfassung erfordert ein aktives Modell; wenn keines vorhanden ist, schlägt die Zusammenfassungsnavigation sofort fehl.\n- Wenn die Zusammenfassung abgebrochen wird, wird die Navigation abgebrochen und das Blatt bleibt unverändert.\n- In-Memory-Sessions geben niemals einen Branch-Dateipfad von `createBranchedSession` zurück.\n\n## Noch vorhandene Legacy-Kompatibilität\n\nSession-Migrationen werden beim Laden weiterhin ausgeführt:\n\n- v1→v2 fügt `id`/`parentId` hinzu und konvertiert den Kompaktierungsindex-Anker zu einem ID-Anker\n- v2→v3 migriert die Legacy-`hookMessage`-Rolle zu `custom`\n\nDas aktuelle Laufzeitverhalten entspricht Version-3-Baumsemantik nach der Migration.\n",
	"de/sessions/session.md": "---\ntitle: Sitzungsspeicherung und Eintragsmodell\ndescription: >-\n  Append-only-Sitzungsspeichermodell mit Eintragstypen, Persistenz und Migration\n  zwischen Formaten.\nsidebar:\n  order: 1\n  label: Speicherung & Eintragsmodell\ni18n:\n  sourceHash: 42fe17549e00\n  translator: machine\n---\n\n# Sitzungsspeicherung und Eintragsmodell\n\nDieses Dokument ist die maßgebliche Quelle dafür, wie Coding-Agent-Sitzungen repräsentiert, persistiert, migriert und zur Laufzeit rekonstruiert werden.\n\n## Geltungsbereich\n\nUmfasst:\n\n- JSONL-Format und Versionierung von Sitzungen\n- Eintragstaxonomie und Baumsemantik (`id`/`parentId` + Blattzeiger)\n- Migrations-/Kompatibilitätsverhalten beim Laden alter oder fehlerhafter Dateien\n- Kontextrekonstruktion (`buildSessionContext`)\n- Persistenzgarantien, Fehlerverhalten, Kürzung/Blob-Externalisierung\n- Speicherabstraktionen (`FileSessionStorage`, `MemorySessionStorage`) und zugehörige Hilfsprogramme\n\nUmfasst nicht das `/tree`-UI-Rendering-Verhalten über die Semantik hinaus, die Sitzungsdaten betrifft.\n\n## Implementierungsdateien\n\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`src/session/messages.ts`](../../packages/coding-agent/src/session/messages.ts)\n- [`src/session/session-storage.ts`](../../packages/coding-agent/src/session/session-storage.ts)\n- [`src/session/history-storage.ts`](../../packages/coding-agent/src/session/history-storage.ts)\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts)\n\n## Layout auf der Festplatte\n\nStandardmäßiger Speicherort für Sitzungsdateien:\n\n```text\n~/.xcsh/agent/sessions/--<cwd-encoded>--/<timestamp>_<sessionId>.jsonl\n```\n\n`<cwd-encoded>` wird aus dem Arbeitsverzeichnis abgeleitet, indem der führende Schrägstrich entfernt und `/`, `\\\\` sowie `:` durch `-` ersetzt werden.\n\nSpeicherort des Blob-Stores:\n\n```text\n~/.xcsh/agent/blobs/<sha256>\n```\n\nTerminal-Breadcrumb-Dateien werden geschrieben unter:\n\n```text\n~/.xcsh/agent/terminal-sessions/<terminal-id>\n```\n\nDer Breadcrumb-Inhalt besteht aus zwei Zeilen: dem ursprünglichen Arbeitsverzeichnis und dem Pfad zur Sitzungsdatei. `continueRecent()` bevorzugt diesen terminalspezifischen Zeiger, bevor nach der neuesten mtime gesucht wird.\n\n## Dateiformat\n\nSitzungsdateien sind im JSONL-Format: ein JSON-Objekt pro Zeile.\n\n- Zeile 1 ist immer der Sitzungsheader (`type: \"session\"`).\n- Die übrigen Zeilen sind `SessionEntry`-Werte.\n- Einträge werden zur Laufzeit nur angehängt (Append-only); bei der Branchnavigation wird ein Zeiger (`leafId`) verschoben, anstatt bestehende Einträge zu verändern.\n\n### Header (`SessionHeader`)\n\n```json\n{\n  \"type\": \"session\",\n  \"version\": 3,\n  \"id\": \"1f9d2a6b9c0d1234\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\",\n  \"cwd\": \"/work/pi\",\n  \"title\": \"optional session title\",\n  \"parentSession\": \"optional lineage marker\"\n}\n```\n\nHinweise:\n\n- `version` ist in v1-Dateien optional; Abwesenheit bedeutet v1.\n- `parentSession` ist eine opake Abstammungszeichenkette. Der aktuelle Code schreibt je nach Ablauf entweder eine Sitzungs-ID oder einen Sitzungspfad (`fork`, `forkFrom`, `createBranchedSession` oder explizit `newSession({ parentSession })`). Behandeln Sie dies als Metadaten, nicht als typisierten Fremdschlüssel.\n\n### Eintragsbasis (`SessionEntryBase`)\n\nAlle Nicht-Header-Einträge enthalten:\n\n```json\n{\n  \"type\": \"...\",\n  \"id\": \"8-char-id\",\n  \"parentId\": \"previous-or-branch-parent\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\"\n}\n```\n\n`parentId` kann `null` sein für einen Wurzeleintrag (erstes Anhängen oder nach `resetLeaf()`).\n\n## Eintragstaxonomie\n\n`SessionEntry` ist die Vereinigung von:\n\n- `message`\n- `thinking_level_change`\n- `model_change`\n- `compaction`\n- `branch_summary`\n- `custom`\n- `custom_message`\n- `label`\n- `ttsr_injection`\n- `session_init`\n- `mode_change`\n\n### `message`\n\nSpeichert eine `AgentMessage` direkt.\n\n```json\n{\n  \"type\": \"message\",\n  \"id\": \"a1b2c3d4\",\n  \"parentId\": null,\n  \"timestamp\": \"2026-02-16T10:21:00.000Z\",\n  \"message\": {\n    \"role\": \"assistant\",\n    \"provider\": \"anthropic\",\n    \"model\": \"claude-sonnet-4-5\",\n    \"content\": [{ \"type\": \"text\", \"text\": \"Done.\" }],\n    \"usage\": { \"input\": 100, \"output\": 20, \"cacheRead\": 0, \"cacheWrite\": 0, \"cost\": { \"input\": 0, \"output\": 0, \"cacheRead\": 0, \"cacheWrite\": 0, \"total\": 0 } },\n    \"timestamp\": 1760000000000\n  }\n}\n```\n\n### `model_change`\n\n```json\n{\n  \"type\": \"model_change\",\n  \"id\": \"b1c2d3e4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:21:30.000Z\",\n  \"model\": \"openai/gpt-4o\",\n  \"role\": \"default\"\n}\n```\n\n`role` ist optional; fehlend wird bei der Kontextrekonstruktion als `default` behandelt.\n\n### `thinking_level_change`\n\n```json\n{\n  \"type\": \"thinking_level_change\",\n  \"id\": \"c1d2e3f4\",\n  \"parentId\": \"b1c2d3e4\",\n  \"timestamp\": \"2026-02-16T10:22:00.000Z\",\n  \"thinkingLevel\": \"high\"\n}\n```\n\n### `compaction`\n\n```json\n{\n  \"type\": \"compaction\",\n  \"id\": \"d1e2f3a4\",\n  \"parentId\": \"c1d2e3f4\",\n  \"timestamp\": \"2026-02-16T10:23:00.000Z\",\n  \"summary\": \"Conversation summary\",\n  \"shortSummary\": \"Short recap\",\n  \"firstKeptEntryId\": \"a1b2c3d4\",\n  \"tokensBefore\": 42000,\n  \"details\": { \"readFiles\": [\"src/a.ts\"] },\n  \"preserveData\": { \"hookState\": true },\n  \"fromExtension\": false\n}\n```\n\n### `branch_summary`\n\n```json\n{\n  \"type\": \"branch_summary\",\n  \"id\": \"e1f2a3b4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:24:00.000Z\",\n  \"fromId\": \"a1b2c3d4\",\n  \"summary\": \"Summary of abandoned path\",\n  \"details\": { \"note\": \"optional\" },\n  \"fromExtension\": true\n}\n```\n\nWenn vom Wurzelknoten abgezweigt wird (`branchFromId === null`), ist `fromId` die Literalzeichenkette `\"root\"`.\n\n### `custom`\n\nErweiterungs-Zustandspersistenz; wird von `buildSessionContext` ignoriert.\n\n```json\n{\n  \"type\": \"custom\",\n  \"id\": \"f1a2b3c4\",\n  \"parentId\": \"e1f2a3b4\",\n  \"timestamp\": \"2026-02-16T10:25:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"data\": { \"state\": 1 }\n}\n```\n\n### `custom_message`\n\nVon einer Erweiterung bereitgestellte Nachricht, die am LLM-Kontext teilnimmt.\n\n```json\n{\n  \"type\": \"custom_message\",\n  \"id\": \"a2b3c4d5\",\n  \"parentId\": \"f1a2b3c4\",\n  \"timestamp\": \"2026-02-16T10:26:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"content\": \"Injected context\",\n  \"display\": true,\n  \"details\": { \"debug\": false }\n}\n```\n\n### `label`\n\n```json\n{\n  \"type\": \"label\",\n  \"id\": \"b2c3d4e5\",\n  \"parentId\": \"a2b3c4d5\",\n  \"timestamp\": \"2026-02-16T10:27:00.000Z\",\n  \"targetId\": \"a1b2c3d4\",\n  \"label\": \"checkpoint\"\n}\n```\n\n`label: undefined` entfernt ein Label für `targetId`.\n\n### `ttsr_injection`\n\n```json\n{\n  \"type\": \"ttsr_injection\",\n  \"id\": \"c2d3e4f5\",\n  \"parentId\": \"b2c3d4e5\",\n  \"timestamp\": \"2026-02-16T10:28:00.000Z\",\n  \"injectedRules\": [\"ruleA\", \"ruleB\"]\n}\n```\n\n### `session_init`\n\n```json\n{\n  \"type\": \"session_init\",\n  \"id\": \"d2e3f4a5\",\n  \"parentId\": \"c2d3e4f5\",\n  \"timestamp\": \"2026-02-16T10:29:00.000Z\",\n  \"systemPrompt\": \"...\",\n  \"task\": \"...\",\n  \"tools\": [\"read\", \"edit\"],\n  \"outputSchema\": { \"type\": \"object\" }\n}\n```\n\n### `mode_change`\n\n```json\n{\n  \"type\": \"mode_change\",\n  \"id\": \"e2f3a4b5\",\n  \"parentId\": \"d2e3f4a5\",\n  \"timestamp\": \"2026-02-16T10:30:00.000Z\",\n  \"mode\": \"plan\",\n  \"data\": { \"planFile\": \"/tmp/plan.md\" }\n}\n```\n\n## Versionierung und Migration\n\nAktuelle Sitzungsversion: `3`.\n\n### v1 -> v2\n\nWird angewendet, wenn der Header `version` fehlt oder `< 2` ist:\n\n- Fügt `id` und `parentId` zu jedem Nicht-Header-Eintrag hinzu.\n- Rekonstruiert eine lineare Elternkette anhand der Dateireihenfolge.\n- Migriert das Compaction-Feld `firstKeptEntryIndex` -> `firstKeptEntryId`, wenn vorhanden.\n- Setzt Header `version = 2`.\n\n### v2 -> v3\n\nWird angewendet, wenn Header `version < 3`:\n\n- Für `message`-Einträge: Schreibt das veraltete `message.role === \"hookMessage\"` zu `\"custom\"` um.\n- Setzt Header `version = 3`.\n\n### Migrationsauslöser und Persistenz\n\n- Migrationen werden beim Laden der Sitzung ausgeführt (`setSessionFile`).\n- Wenn eine Migration ausgeführt wurde, wird die gesamte Datei sofort auf die Festplatte neu geschrieben.\n- Die Migration verändert zuerst die In-Memory-Einträge und persistiert dann das neu geschriebene JSONL.\n\n## Lade- und Kompatibilitätsverhalten\n\nVerhalten von `loadEntriesFromFile(path)`:\n\n- Fehlende Datei (`ENOENT`) -> gibt `[]` zurück.\n- Nicht parsbare Zeilen werden vom nachsichtigen JSONL-Parser (`parseJsonlLenient`) behandelt.\n- Wenn der erste geparste Eintrag kein gültiger Sitzungsheader ist (`type !== \"session\"` oder fehlende Zeichenkette `id`) -> gibt `[]` zurück.\n\nVerhalten von `SessionManager.setSessionFile()`:\n\n- `[]` vom Loader wird als leere/nicht vorhandene Sitzung behandelt und durch eine neue initialisierte Sitzungsdatei an diesem Pfad ersetzt.\n- Gültige Dateien werden geladen, bei Bedarf migriert, Blob-Referenzen aufgelöst und dann indiziert.\n\n## Baum- und Blattsemantik\n\nDas zugrundeliegende Modell ist ein Append-only-Baum + veränderlicher Blattzeiger:\n\n- Jede Append-Methode erstellt genau einen neuen Eintrag, dessen `parentId` die aktuelle `leafId` ist.\n- Der neue Eintrag wird zur neuen `leafId`.\n- `branch(entryId)` verschiebt nur `leafId`; bestehende Einträge bleiben unverändert.\n- `resetLeaf()` setzt `leafId = null`; das nächste Anhängen erstellt einen neuen Wurzeleintrag (`parentId: null`).\n- `branchWithSummary()` setzt das Blatt auf das Branchziel und hängt einen `branch_summary`-Eintrag an.\n\n`getEntries()` gibt alle Nicht-Header-Einträge in Einfügereihenfolge zurück. Bestehende Einträge werden im normalen Betrieb nicht gelöscht; Neuschreibungen bewahren die logische Historie, während die Repräsentation aktualisiert wird (Migrationen, Verschiebungen, gezielte Neuschreibungshilfen).\n\n## Kontextrekonstruktion (`buildSessionContext`)\n\n`buildSessionContext(entries, leafId, byId?)` ermittelt, was an das Modell gesendet wird.\n\nAlgorithmus:\n\n1. Blatt bestimmen:\n   - `leafId === null` -> leeren Kontext zurückgeben.\n   - explizite `leafId` -> diesen Eintrag verwenden, falls gefunden.\n   - andernfalls Fallback auf den letzten Eintrag.\n2. Die `parentId`-Kette vom Blatt zur Wurzel durchlaufen und zum Wurzel->Blatt-Pfad umkehren.\n3. Laufzeitzustand über den Pfad ableiten:\n   - `thinkingLevel` aus dem neuesten `thinking_level_change` (Standard `\"off\"`)\n   - Modellzuordnung aus `model_change`-Einträgen (`role ?? \"default\"`)\n   - Fallback `models.default` aus dem Provider/Modell der Assistentennachricht, wenn keine explizite Modelländerung vorliegt\n   - Deduplizierte `injectedTtsrRules` aus allen `ttsr_injection`-Einträgen\n   - Modus/Modusdaten aus dem neuesten `mode_change` (Standardmodus `\"none\"`)\n4. Nachrichtenliste erstellen:\n   - `message`-Einträge werden durchgereicht\n   - `custom_message`-Einträge werden über `createCustomMessage` zu `custom`-AgentMessages\n   - `branch_summary`-Einträge werden über `createBranchSummaryMessage` zu `branchSummary`-AgentMessages\n   - Wenn eine `compaction` auf dem Pfad existiert:\n     - Zuerst die Compaction-Zusammenfassung ausgeben (`createCompactionSummaryMessage`)\n     - Pfadeinträge ab `firstKeptEntryId` bis zur Compaction-Grenze ausgeben\n     - Einträge nach der Compaction-Grenze ausgeben\n\n`custom`- und `session_init`-Einträge injizieren keinen Modellkontext direkt.\n\n## Persistenzgarantien und Fehlermodell\n\n### Persistenz vs. In-Memory\n\n- `SessionManager.create/open/continueRecent/forkFrom` -> persistenter Modus (`persist = true`).\n- `SessionManager.inMemory` -> nicht-persistenter Modus (`persist = false`) mit `MemorySessionStorage`.\n\n### Schreibpipeline\n\nSchreibvorgänge werden über eine interne Promise-Kette (`#persistChain`) und `NdjsonFileWriter` serialisiert.\n\n- `append*` aktualisiert den In-Memory-Zustand sofort.\n- Die Persistierung wird aufgeschoben, bis mindestens eine Assistentennachricht existiert.\n  - Vor der ersten Assistentennachricht: Einträge werden im Speicher gehalten; kein Dateischreibvorgang erfolgt.\n  - Wenn die erste Assistentennachricht existiert: Die vollständige In-Memory-Sitzung wird in die Datei geflusht.\n  - Danach: Neue Einträge werden inkrementell angehängt.\n\nBegründung im Code: Vermeidung der Persistierung von Sitzungen, die nie eine Assistentenantwort erzeugt haben.\n\n### Haltbarkeitsoperationen\n\n- `flush()` flusht den Writer und ruft `fsync()` auf.\n- Atomare vollständige Neuschreibungen (`#rewriteFile`) schreiben in eine temporäre Datei, flushen+fsyncen, schließen und benennen dann über die Zieldatei um.\n- Wird verwendet für Migrationen, `setSessionName`, `rewriteEntries`, Verschiebungsoperationen und Neuschreibungen von Tool-Call-Argumenten.\n\n### Fehlerverhalten\n\n- Persistenzfehler werden gespeichert (`#persistError`) und bei nachfolgenden Operationen erneut ausgelöst.\n- Der erste Fehler wird einmalig mit Sitzungsdateikontext protokolliert.\n- Das Schließen des Writers erfolgt bestmöglich, propagiert aber den ersten bedeutsamen Fehler.\n\n## Datengrößenkontrollen und Blob-Externalisierung\n\nVor der Persistierung von Einträgen:\n\n- Große Zeichenketten werden auf `MAX_PERSIST_CHARS` (500.000 Zeichen) gekürzt mit Hinweis:\n  - `\"[Session persistence truncated large content]\"`\n- Transiente Felder `partialJson` und `jsonlEvents` werden entfernt.\n- Wenn ein Objekt sowohl `content` als auch `lineCount` hat, wird die Zeilenzahl nach der Kürzung neu berechnet.\n- Bildblöcke in `content`-Arrays mit Base64-Länge >= 1024 werden zu Blob-Referenzen externalisiert:\n  - Gespeichert als `blob:sha256:<hash>`\n  - Rohdaten werden in den Blob-Store geschrieben (`BlobStore.put`)\n\nBeim Laden werden Blob-Referenzen für Bildblöcke in message/custom_message zurück zu Base64 aufgelöst.\n\n## Speicherabstraktionen\n\nDas `SessionStorage`-Interface stellt alle Dateisystemoperationen bereit, die von `SessionManager` verwendet werden:\n\n- synchron: `ensureDirSync`, `existsSync`, `writeTextSync`, `statSync`, `listFilesSync`\n- asynchron: `exists`, `readText`, `readTextPrefix`, `writeText`, `rename`, `unlink`, `openWriter`\n\nImplementierungen:\n\n- `FileSessionStorage`: echtes Dateisystem (Bun + node fs)\n- `MemorySessionStorage`: Map-basierte In-Memory-Implementierung für Tests/nicht-persistente Sitzungen\n\n`SessionStorageWriter` stellt `writeLine`, `flush`, `fsync`, `close`, `getError` bereit.\n\n## Hilfsprogramme zur Sitzungserkennung\n\nDefiniert in `session-manager.ts`:\n\n- `getRecentSessions(sessionDir, limit)` -> leichtgewichtige Metadaten für UI/Sitzungsauswahl\n- `findMostRecentSession(sessionDir)` -> neueste nach mtime\n- `list(cwd, sessionDir?)` -> Sitzungen in einem Projektbereich\n- `listAll()` -> Sitzungen über alle Projektbereiche unter `~/.xcsh/agent/sessions`\n\nDie Metadatenextraktion liest nach Möglichkeit nur ein Präfix (`readTextPrefix(..., 4096)`).\n\n## Verwandt, aber eigenständig: Prompt-Verlaufsspeicherung\n\n`HistoryStorage` (`history-storage.ts`) ist ein separates SQLite-Subsystem für Prompt-Rückruf/-Suche, nicht für die Sitzungswiedergabe.\n\n- Datenbank: `~/.xcsh/agent/history.db`\n- Tabelle: `history(id, prompt, created_at, cwd)`\n- FTS5-Index: `history_fts` mit trigger-gestützter Synchronisation\n- Dedupliziert aufeinanderfolgende identische Prompts mittels In-Memory-Letzter-Prompt-Cache\n- Asynchrone Einfügung (`setImmediate`), damit die Prompt-Erfassung die Ausführung des Turns nicht blockiert\n\nVerwenden Sie Sitzungsdateien für die Konversationsgraph-/Zustandswiedergabe; verwenden Sie `HistoryStorage` für die Prompt-Verlauf-UX.\n",
	"de/sessions/ttsr-injection-lifecycle.md": "---\ntitle: TTSR-Injektionslebenszyklus\ndescription: >-\n  TTSR (tool-use, tool-result, system-reminder) Injektionslebenszyklus für\n  Kontextmanagement.\nsidebar:\n  order: 9\n  label: TTSR-Injektion\ni18n:\n  sourceHash: d6179a286584\n  translator: machine\n---\n\n# TTSR-Injektionslebenszyklus\n\nDieses Dokument behandelt den aktuellen Time Traveling Stream Rules (TTSR) Laufzeitpfad von der Regelerkennung über Stream-Unterbrechung, Retry-Injektion, Extension-Benachrichtigungen bis hin zur Sitzungszustandsverwaltung.\n\n## Implementierungsdateien\n\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/export/ttsr.ts`](../../packages/coding-agent/src/export/ttsr.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/prompts/system/ttsr-interrupt.md`](../../packages/coding-agent/src/prompts/system/ttsr-interrupt.md)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/types.ts`](../../packages/coding-agent/src/extensibility/extensions/types.ts)\n- [`../src/extensibility/hooks/types.ts`](../../packages/coding-agent/src/extensibility/hooks/types.ts)\n- [`../src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n\n## 1. Discovery-Feed und Regelregistrierung\n\nBei der Sitzungserstellung lädt `createAgentSession()` alle erkannten Regeln und erstellt einen `TtsrManager`:\n\n```ts\nconst ttsrSettings = settings.getGroup(\"ttsr\");\nconst ttsrManager = new TtsrManager(ttsrSettings);\nconst rulesResult = await loadCapability<Rule>(ruleCapability.id, { cwd });\nfor (const rule of rulesResult.items) {\n  if (rule.ttsrTrigger) ttsrManager.addRule(rule);\n}\n```\n\n### Deduplizierungsverhalten vor der Registrierung\n\n`loadCapability(\"rules\")` dedupliziert nach `rule.name` mit First-Wins-Semantik (höhere Provider-Priorität zuerst). Überdeckte Duplikate werden vor der TTSR-Registrierung entfernt.\n\n### `TtsrManager.addRule()`-Verhalten\n\nDie Registrierung wird übersprungen, wenn:\n\n- `rule.ttsrTrigger` nicht vorhanden ist\n- eine Regel mit demselben `rule.name` bereits in diesem Manager registriert wurde\n- der reguläre Ausdruck nicht kompiliert werden kann (`new RegExp(rule.ttsrTrigger)` wirft einen Fehler)\n\nUngültige Regex-Trigger werden als Warnungen protokolliert und ignoriert; der Sitzungsstart wird fortgesetzt.\n\n### Einstellungshinweis\n\n`TtsrSettings.enabled` wird in den Manager geladen, wird aber derzeit nicht bei der Laufzeitsteuerung geprüft. Wenn Regeln existieren, wird der Abgleich trotzdem durchgeführt.\n\n## 2. Streaming-Monitor-Lebenszyklus\n\nDie TTSR-Erkennung läuft innerhalb von `AgentSession.#handleAgentEvent`.\n\n### Turn-Start\n\nBei `turn_start` wird der Stream-Buffer zurückgesetzt:\n\n- `ttsrManager.resetBuffer()`\n\n### Während des Streams (`message_update`)\n\nWenn Assistenten-Updates eintreffen und Regeln existieren:\n\n- `text_delta` und `toolcall_delta` überwachen\n- Delta in den Manager-Buffer anhängen\n- `check(buffer)` aufrufen\n\n`check()` iteriert über registrierte Regeln und gibt alle übereinstimmenden Regeln zurück, die die Wiederholungsrichtlinie (`#canTrigger`) bestehen.\n\n## 3. Trigger-Entscheidung und sofortiger Abbruchpfad\n\nWenn eine oder mehrere Regeln übereinstimmen:\n\n1. `markInjected(matches)` zeichnet Regelnamen im Injektionszustand des Managers auf.\n2. Übereinstimmende Regeln werden in `#pendingTtsrInjections` eingereiht.\n3. `#ttsrAbortPending = true`.\n4. `agent.abort()` wird sofort aufgerufen.\n5. Das `ttsr_triggered`-Event wird asynchron emittiert (fire-and-forget).\n6. Die Retry-Arbeit wird über `setTimeout(..., 50)` geplant.\n\nDer Abbruch wird nicht durch Extension-Callbacks blockiert.\n\n## 4. Retry-Planung, Kontextmodus und Erinnerungsinjektion\n\nNach dem 50ms-Timeout:\n\n1. `#ttsrAbortPending = false`\n2. `ttsrManager.getSettings().contextMode` auslesen\n3. Wenn `contextMode === \"discard\"`, partielle Assistenten-Ausgabe mit `agent.popMessage()` verwerfen\n4. Injektionsinhalt aus ausstehenden Regeln mithilfe der `ttsr-interrupt.md`-Vorlage erstellen\n5. Eine synthetische Benutzernachricht anhängen, die einen `<system-interrupt ...>`-Block pro Regel enthält\n6. `agent.continue()` aufrufen, um die Generierung erneut zu starten\n\nDie Vorlagen-Payload ist:\n\n```xml\n<system-interrupt reason=\"rule_violation\" rule=\"{{name}}\" path=\"{{path}}\">\n...\n{{content}}\n</system-interrupt>\n```\n\nAusstehende Injektionen werden nach der Inhaltsgenerierung gelöscht.\n\n### `contextMode`-Verhalten bei partieller Ausgabe\n\n- `discard`: Die partielle/abgebrochene Assistentennachricht wird vor dem Retry entfernt.\n- `keep`: Die partielle Assistenten-Ausgabe verbleibt im Konversationszustand; die Erinnerung wird danach angehängt.\n\n## 5. Wiederholungsrichtlinie und Lückenlogik\n\n`TtsrManager` verfolgt `#messageCount` und pro Regel `lastInjectedAt`.\n\n### `repeatMode: \"once\"`\n\nEine Regel kann nur einmal ausgelöst werden, nachdem sie einen Injektionseintrag hat.\n\n### `repeatMode: \"after-gap\"`\n\nEine Regel kann erneut ausgelöst werden, nur wenn:\n\n- `messageCount - lastInjectedAt >= repeatGap`\n\n`messageCount` wird bei `turn_end` inkrementiert, daher wird die Lücke in abgeschlossenen Turns gemessen, nicht in Stream-Chunks.\n\n## 6. Event-Emission und Extension-/Hook-Oberflächen\n\n### Sitzungs-Event\n\n`AgentSessionEvent` enthält:\n\n```ts\n{ type: \"ttsr_triggered\"; rules: Rule[] }\n```\n\n### Extension-Runner\n\n`#emitSessionEvent()` leitet das Event weiter an:\n\n- Extension-Listener (`ExtensionRunner.emit({ type: \"ttsr_triggered\", rules })`)\n- Lokale Sitzungsabonnenten\n\n### Hook- und Custom-Tool-Typisierung\n\n- Die Extension-API stellt `on(\"ttsr_triggered\", ...)` bereit\n- Die Hook-API stellt `on(\"ttsr_triggered\", ...)` bereit\n- Custom Tools erhalten `onSession({ reason: \"ttsr_triggered\", rules })`\n\n### Unterschied bei der Darstellung im interaktiven Modus\n\nDer interaktive Modus verwendet `session.isTtsrAbortPending`, um die Anzeige des abgebrochenen Assistenten-Stoppgrunds als sichtbaren Fehler während der TTSR-Unterbrechung zu unterdrücken, und rendert eine `TtsrNotificationComponent`, wenn das Event eintrifft.\n\n## 7. Persistenz und Wiederaufnahmezustand (aktuelle Implementierung)\n\n`SessionManager` hat volle Schemaunterstützung für die Persistenz injizierter Regeln:\n\n- Eintragstyp: `ttsr_injection`\n- Anhänge-API: `appendTtsrInjection(ruleNames)`\n- Abfrage-API: `getInjectedTtsrRules()`\n- Die Kontextrekonstruktion enthält `SessionContext.injectedTtsrRules`\n\n`TtsrManager` unterstützt auch die Wiederherstellung über `restoreInjected(ruleNames)`.\n\n### Aktueller Verdrahtungsstatus\n\nIm aktuellen Laufzeitpfad:\n\n- `AgentSession` fügt keine `ttsr_injection`-Einträge hinzu, wenn TTSR ausgelöst wird.\n- `createAgentSession()` stellt `existingSession.injectedTtsrRules` nicht in den `ttsrManager` zurück.\n\nNettoeffekt: Die Unterdrückung injizierter Regeln wird im Arbeitsspeicher für den laufenden Prozess durchgesetzt, wird aber derzeit über diesen Pfad nicht persistent gespeichert/wiederhergestellt bei Sitzungsneuladung/-wiederaufnahme.\n\n## 8. Race-Condition-Grenzen und Reihenfolgegarantien\n\n### Abbruch vs. Retry-Callback\n\n- Der Abbruch ist aus TTSR-Handler-Perspektive synchron (`agent.abort()` wird sofort aufgerufen)\n- Der Retry wird durch Timer verzögert (`50ms`)\n- Die Extension-Benachrichtigung ist asynchron und wird absichtlich nicht vor der Abbruch-/Retry-Planung abgewartet\n\n### Mehrere Übereinstimmungen im selben Stream-Fenster\n\n`check()` gibt alle derzeit übereinstimmenden berechtigten Regeln zurück. Sie werden als Batch in der nächsten Retry-Nachricht injiziert.\n\n### Zwischen Abbruch und Fortsetzung\n\nWährend des Timer-Fensters kann sich der Zustand ändern (Benutzerunterbrechung, Modusaktionen, zusätzliche Events). Der Retry-Aufruf erfolgt nach dem Best-Effort-Prinzip: `agent.continue().catch(() => {})` schluckt Folgefehler.\n\n## 9. Zusammenfassung der Randfälle\n\n- Ungültiger `ttsr_trigger`-Regex: wird mit Warnung übersprungen; andere Regeln funktionieren weiter.\n- Doppelte Regelnamen auf Capability-Ebene: Duplikate mit niedrigerer Priorität werden vor der Registrierung überdeckt.\n- Doppelte Namen auf Manager-Ebene: Die zweite Registrierung wird ignoriert.\n- `contextMode: \"keep\"`: Partielle verletzende Ausgabe kann vor dem Erinnerungs-Retry im Kontext verbleiben.\n- Repeat-after-gap hängt von Turn-Zähler-Inkrementen bei `turn_end` ab; Chunks innerhalb eines Turns erhöhen die Lückenzähler nicht.\n",
	"de/tui/theme.md": "---\ntitle: Theming-Referenz\ndescription: >-\n  TUI-Theming-Referenz mit Farb-Tokens, Schriftarteinstellungen und\n  Theme-Anpassung.\nsidebar:\n  order: 3\n  label: Theming\ni18n:\n  sourceHash: 7e962a7da157\n  translator: machine\n---\n\n# Theming-Referenz\n\nDieses Dokument beschreibt, wie das Theming im Coding-Agent funktioniert: Schema, Laden, Laufzeitverhalten und Fehlermodi.\n\n## Was das Theme-System steuert\n\nDas Theme-System steuert:\n\n- Vordergrund-/Hintergrund-Farb-Tokens, die im gesamten TUI verwendet werden\n- Markdown-Styling-Adapter (`getMarkdownTheme()`)\n- Selektor-/Editor-/Einstellungslisten-Adapter (`getSelectListTheme()`, `getEditorTheme()`, `getSettingsListTheme()`)\n- Symbol-Voreinstellung + Symbol-Überschreibungen (`unicode`, `nerd`, `ascii`)\n- Syntaxhervorhebungsfarben des nativen Highlighters (`@f5-sales-demo/pi-natives`)\n- Farben der Statuszeilensegmente\n\nPrimäre Implementierung: `src/modes/theme/theme.ts`.\n\n## JSON-Struktur des Themes\n\nTheme-Dateien sind JSON-Objekte, die gegen das Laufzeitschema in `theme.ts` (`ThemeJsonSchema`) validiert werden und durch `src/modes/theme/theme-schema.json` gespiegelt werden.\n\nFelder der obersten Ebene:\n\n- `name` (erforderlich)\n- `colors` (erforderlich; alle Farb-Tokens erforderlich)\n- `vars` (optional; wiederverwendbare Farbvariablen)\n- `export` (optional; HTML-Export-Farben)\n- `symbols` (optional)\n  - `preset` (optional: `unicode | nerd | ascii`)\n  - `overrides` (optional: Schlüssel/Wert-Überschreibungen für `SymbolKey`)\n\nFarbwerte akzeptieren:\n\n- Hex-Zeichenkette (`\"#RRGGBB\"`)\n- 256-Farb-Index (`0..255`)\n- Variablenreferenz-Zeichenkette (wird durch `vars` aufgelöst)\n- Leere Zeichenkette (`\"\"`) bedeutet Terminal-Standard (`\\x1b[39m` fg, `\\x1b[49m` bg)\n\n## Erforderliche Farb-Tokens (aktuell)\n\nAlle nachfolgenden Tokens sind in `colors` erforderlich.\n\n### Kerntexte und Rahmen (11)\n\n`accent`, `border`, `borderAccent`, `borderMuted`, `success`, `error`, `warning`, `muted`, `dim`, `text`, `thinkingText`\n\n### Hintergrundblöcke (7)\n\n`selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`, `statusLineBg`\n\n### Nachrichten-/Werkzeugtext (5)\n\n`userMessageText`, `customMessageText`, `customMessageLabel`, `toolTitle`, `toolOutput`\n\n### Markdown (10)\n\n`mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet`\n\n### Werkzeug-Diff + Syntaxhervorhebung (12)\n\n`toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext`,\n`syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation`\n\n### Modus-/Denkrahmen (8)\n\n`thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh`, `bashMode`, `pythonMode`\n\n### Statuszeilensegment-Farben (14)\n\n`statusLineSep`, `statusLineModel`, `statusLinePath`, `statusLineGitClean`, `statusLineGitDirty`, `statusLineContext`, `statusLineSpend`, `statusLineStaged`, `statusLineDirty`, `statusLineUntracked`, `statusLineOutput`, `statusLineCost`, `statusLineSubagents`\n\n## Optionale Tokens\n\n### Abschnitt `export` (optional)\n\nWird für HTML-Export-Theming-Hilfsfunktionen verwendet:\n\n- `export.pageBg`\n- `export.cardBg`\n- `export.infoBg`\n\nWenn weggelassen, leitet der Export-Code Standardwerte aus den aufgelösten Theme-Farben ab.\n\n### Abschnitt `symbols` (optional)\n\n- `symbols.preset` legt einen Standard-Symbolsatz auf Theme-Ebene fest.\n- `symbols.overrides` kann einzelne `SymbolKey`-Werte überschreiben.\n\nLaufzeit-Rangfolge:\n\n1. Einstellungs-`symbolPreset`-Überschreibung (falls gesetzt)\n2. Theme-JSON `symbols.preset`\n3. Fallback `\"unicode\"`\n\nUngültige Überschreibungsschlüssel werden ignoriert und protokolliert (`logger.debug`).\n\n## Eingebaute vs. benutzerdefinierte Theme-Quellen\n\nReihenfolge der Theme-Suche (`loadThemeJson`):\n\n1. Eingebaute, eingebettete Themes (`defaults/xcsh-dark.json` und `defaults/xcsh-light.json`, kompiliert in `defaultThemes`)\n2. Benutzerdefinierte Theme-Datei: `<customThemesDir>/<name>.json`\n\nDas Verzeichnis für benutzerdefinierte Themes stammt von `getCustomThemesDir()`:\n\n- Standard: `~/.xcsh/agent/themes`\n- Überschrieben durch `PI_CODING_AGENT_DIR` (`$PI_CODING_AGENT_DIR/themes`)\n\n`getAvailableThemes()` gibt zusammengeführte eingebaute + benutzerdefinierte Namen zurück, sortiert, wobei eingebaute Themes bei Namenskollisionen Vorrang haben.\n\n## Laden, Validierung und Auflösung\n\nFür benutzerdefinierte Theme-Dateien:\n\n1. JSON lesen\n2. JSON parsen\n3. Gegen `ThemeJsonSchema` validieren\n4. `vars`-Referenzen rekursiv auflösen\n5. Aufgelöste Werte nach ANSI gemäß Terminal-Fähigkeitsmodus konvertieren\n\nValidierungsverhalten:\n\n- Fehlende erforderliche Farb-Tokens: explizite gruppierte Fehlermeldung\n- Falsche Token-Typen/-Werte: Validierungsfehler mit JSON-Pfad\n- Unbekannte Theme-Datei: `Theme not found: <name>`\n\nVerhalten von Variablenreferenzen:\n\n- Unterstützt verschachtelte Referenzen\n- Löst Ausnahme bei fehlender Variablenreferenz aus\n- Löst Ausnahme bei Zirkularreferenzen aus\n\n## Verhalten des Terminal-Farbmodus\n\nFarbmoduserkennung (`detectColorMode`):\n\n- `COLORTERM=truecolor|24bit` => Truecolor\n- `WT_SESSION` => Truecolor\n- `TERM` in `dumb`, `linux` oder leer => 256-Farben\n- andernfalls => Truecolor\n\nKonvertierungsverhalten:\n\n- Hex -> `Bun.color(..., \"ansi-16m\" | \"ansi-256\")`\n- Numerisch -> `38;5` / `48;5` ANSI\n- `\"\"` -> Standard fg/bg-Reset\n\n## Laufzeit-Wechselverhalten\n\n### Initiales Theme (`initTheme`)\n\n`main.ts` initialisiert das Theme mit Einstellungen:\n\n- `symbolPreset`\n- `colorBlindMode`\n- `theme.dark`\n- `theme.light`\n\nDie automatische Theme-Slot-Auswahl verwendet `COLORFGBG`-Hintergrunderkennung:\n\n- Hintergrundindex aus `COLORFGBG` parsen\n- `< 8` => Dunkel-Slot (`theme.dark`)\n- `>= 8` => Hell-Slot (`theme.light`)\n- Parse-Fehler => Dunkel-Slot\n\nAktuelle Standardwerte aus dem Einstellungsschema:\n\n- `theme.dark = \"xcsh-dark\"`\n- `theme.light = \"xcsh-light\"`\n- `symbolPreset = \"unicode\"`\n- `colorBlindMode = false`\n\n### Expliziter Wechsel (`setTheme`)\n\n- Lädt ausgewähltes Theme\n- Aktualisiert globales `theme`-Singleton\n- Startet optional einen Watcher\n- Löst `onThemeChange`-Callback aus\n\nBei Fehler:\n\n- Fällt auf eingebautes `dark` zurück\n- Gibt `{ success: false, error }` zurück\n\n### Vorschau-Wechsel (`previewTheme`)\n\n- Wendet temporäres Vorschau-Theme auf globales `theme` an\n- Ändert **nicht** die persistierten Einstellungen\n- Gibt Erfolg/Fehler ohne Fallback-Ersatz zurück\n\nDie Einstellungs-UI nutzt dies für die Live-Vorschau und stellt beim Abbrechen das vorherige Theme wieder her.\n\n## Watcher und Live-Reload\n\nWenn der Watcher aktiviert ist (`setTheme(..., true)` / interaktive Initialisierung):\n\n- Überwacht nur den benutzerdefinierten Dateipfad `<customThemesDir>/<currentTheme>.json`\n- Eingebaute Themes werden effektiv nicht überwacht\n- Datei `change`: Versucht Neuladen (entprellt)\n- Datei `rename`/Löschen: Fällt auf `dark` zurück, schließt Watcher\n\nDer Auto-Modus installiert außerdem einen `SIGWINCH`-Listener und kann die Dunkel-/Hell-Slot-Zuordnung neu auswerten, wenn sich der Terminal-Status ändert.\n\n## Verhalten des Farbenblind-Modus\n\n`colorBlindMode` ändert zur Laufzeit nur ein Token:\n\n- `toolDiffAdded` wird per HSV angepasst (Grün in Richtung Blau verschoben)\n- Die Anpassung wird nur angewendet, wenn der aufgelöste Wert eine Hex-Zeichenkette ist\n\nAlle anderen Tokens bleiben unverändert.\n\n## Wo Theme-Einstellungen gespeichert werden\n\nTheme-bezogene Einstellungen werden von `Settings` in der globalen Konfigurations-YAML gespeichert:\n\n- Pfad: `<agentDir>/config.yml`\n- Standard-Agent-Verzeichnis: `~/.xcsh/agent`\n- Effektive Standarddatei: `~/.xcsh/agent/config.yml`\n\nGespeicherte Schlüssel:\n\n- `theme.dark`\n- `theme.light`\n- `symbolPreset`\n- `colorBlindMode`\n\nEine Legacy-Migration existiert: Das alte flache `theme: \"name\"` wird basierend auf der Luminanz-Erkennung zu verschachteltem `theme.dark` oder `theme.light` migriert.\n\n## Erstellen eines benutzerdefinierten Themes (praktisch)\n\n1. Erstellen Sie eine Datei im Verzeichnis für benutzerdefinierte Themes, z. B. `~/.xcsh/agent/themes/my-theme.json`.\n2. Geben Sie `name`, optionale `vars` und **alle erforderlichen** `colors`-Tokens an.\n3. Fügen Sie optional `symbols` und `export` hinzu.\n4. Wählen Sie das Theme in den Einstellungen (`Anzeige -> Dunkles Theme` oder `Anzeige -> Helles Theme`) aus, je nachdem, welchen Auto-Slot Sie möchten.\n\nMinimales Grundgerüst. Jeder Schlüssel in `colors` ist erforderlich — der Laufzeit-Validator\n(`additionalProperties: false`) weist sowohl fehlende als auch unbekannte Schlüssel zurück.\nAls mitgelieferte Referenzimplementierungen siehe\n[`packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json)\nund [`xcsh-light.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-light.json).\n\nDie Statuszeile verfügt über zwei parallele Farbsysteme, die in Issue #242 dokumentiert sind:\n\n- Hex-Textfarben (`statusLinePath`, `statusLineGitClean`, `statusLineGitDirty`,\n  `statusLineStaged`, `statusLineDirty`, `statusLineUntracked`) steuern das Nicht-Powerline-\n  Rendering.\n- 256-Farb-Palettenindizes (`statusLine<Segment>Bg` / `statusLine<Segment>Fg`)\n  steuern Powerline-Segment-Füllungen. Sie sind unabhängig von den Hex-Schlüsseln oben —\n  beide müssen gesetzt werden.\n\n```json\n{\n  \"name\": \"my-theme\",\n  \"vars\": {\n    \"accent\": \"#7aa2f7\",\n    \"muted\": 244\n  },\n  \"colors\": {\n    \"accent\": \"accent\",\n    \"chromeAccent\": \"accent\",\n    \"spinnerAccent\": \"accent\",\n    \"contentAccent\": \"muted\",\n    \"border\": \"#4c566a\",\n    \"borderAccent\": \"accent\",\n    \"borderMuted\": \"muted\",\n    \"success\": \"#9ece6a\",\n    \"error\": \"#f7768e\",\n    \"warning\": \"#e0af68\",\n    \"muted\": \"muted\",\n    \"dim\": 240,\n    \"gutterSuccess\": \"#7dcfff\",\n    \"gutterWarning\": \"#e0af68\",\n    \"text\": \"\",\n    \"thinkingText\": \"muted\",\n\n    \"selectedBg\": \"#2a2f45\",\n    \"userMessageBg\": \"#1f2335\",\n    \"userMessageText\": \"\",\n    \"customMessageBg\": \"#24283b\",\n    \"customMessageText\": \"\",\n    \"customMessageLabel\": \"accent\",\n    \"toolPendingBg\": \"#1f2335\",\n    \"toolSuccessBg\": \"#1f2d2a\",\n    \"toolErrorBg\": \"#2d1f2a\",\n    \"toolTitle\": \"\",\n    \"toolOutput\": \"muted\",\n\n    \"mdHeading\": \"accent\",\n    \"mdLink\": \"accent\",\n    \"mdLinkUrl\": \"muted\",\n    \"mdCode\": \"#c0caf5\",\n    \"mdCodeBlock\": \"#c0caf5\",\n    \"mdCodeBlockBorder\": \"muted\",\n    \"mdQuote\": \"muted\",\n    \"mdQuoteBorder\": \"muted\",\n    \"mdHr\": \"muted\",\n    \"mdListBullet\": \"accent\",\n\n    \"toolDiffAdded\": \"#9ece6a\",\n    \"toolDiffRemoved\": \"#f7768e\",\n    \"toolDiffContext\": \"muted\",\n\n    \"syntaxComment\": \"#565f89\",\n    \"syntaxKeyword\": \"#bb9af7\",\n    \"syntaxFunction\": \"#7aa2f7\",\n    \"syntaxVariable\": \"#c0caf5\",\n    \"syntaxString\": \"#9ece6a\",\n    \"syntaxNumber\": \"#ff9e64\",\n    \"syntaxType\": \"#2ac3de\",\n    \"syntaxOperator\": \"#89ddff\",\n    \"syntaxPunctuation\": \"#9aa5ce\",\n    \"syntaxControl\": \"#bb9af7\",\n\n    \"thinkingOff\": 240,\n    \"thinkingMinimal\": 244,\n    \"thinkingLow\": \"#7aa2f7\",\n    \"thinkingMedium\": \"#2ac3de\",\n    \"thinkingHigh\": \"#bb9af7\",\n    \"thinkingXhigh\": \"#f7768e\",\n\n    \"bashMode\": \"#2ac3de\",\n    \"pythonMode\": \"#bb9af7\",\n\n    \"statusLineBg\": \"#16161e\",\n    \"statusLineSep\": 240,\n    \"statusLineModel\": \"#bb9af7\",\n    \"statusLinePath\": \"#7aa2f7\",\n    \"statusLineGitClean\": \"#9ece6a\",\n    \"statusLineGitDirty\": \"#e0af68\",\n    \"statusLineContext\": \"#2ac3de\",\n    \"statusLineSpend\": \"#7dcfff\",\n    \"statusLineStaged\": \"#9ece6a\",\n    \"statusLineDirty\": \"#e0af68\",\n    \"statusLineUntracked\": \"#f7768e\",\n    \"statusLineOutput\": \"#c0caf5\",\n    \"statusLineCost\": \"#ff9e64\",\n    \"statusLineSubagents\": \"#bb9af7\",\n\n    \"statusLineOsIconBg\": 7,\n    \"statusLineOsIconFg\": 232,\n    \"statusLinePathBg\": 4,\n    \"statusLinePathFg\": 254,\n    \"statusLineGitCleanBg\": 2,\n    \"statusLineGitCleanFg\": 0,\n    \"statusLineGitDirtyBg\": 3,\n    \"statusLineGitDirtyFg\": 0,\n    \"statusLineGitStagedBg\": 64,\n    \"statusLineGitStagedFg\": 0,\n    \"statusLineGitUntrackedBg\": 39,\n    \"statusLineGitUntrackedFg\": 0,\n    \"statusLineGitConflictBg\": 1,\n    \"statusLineGitConflictFg\": 7,\n    \"statusLinePlanModeBg\": 236,\n    \"statusLinePlanModeFg\": 117,\n    \"statusLineProfileXcshBg\": \"accent\",\n    \"statusLineProfileXcshFg\": 231\n  }\n}\n```\n\n## Benutzerdefinierte Themes testen\n\nVerwenden Sie diesen Workflow:\n\n1. Starten Sie den interaktiven Modus (Watcher wird beim Start aktiviert).\n2. Öffnen Sie die Einstellungen und zeigen Sie Theme-Werte in der Vorschau an (Live-`previewTheme`).\n3. Bearbeiten Sie bei benutzerdefinierten Theme-Dateien das JSON während der Laufzeit und bestätigen Sie das automatische Neuladen beim Speichern.\n4. Testen Sie kritische Oberflächen:\n   - Markdown-Rendering\n   - Werkzeugblöcke (ausstehend/erfolgreich/fehlerhaft)\n   - Diff-Rendering (hinzugefügt/entfernt/Kontext)\n   - Lesbarkeit der Statuszeile\n   - Rahmenänderungen bei Denkstufen\n   - Rahmenfarben für Bash-/Python-Modus\n5. Validieren Sie beide Symbol-Voreinstellungen, wenn Ihr Theme von der Glyphenbreite/-darstellung abhängt.\n\n## Reale Einschränkungen und Vorbehalte\n\n- Alle `colors`-Tokens sind für benutzerdefinierte Themes erforderlich.\n- `export` und `symbols` sind optional.\n- `$schema` in Theme-JSON ist informativ; die Laufzeitvalidierung wird durch das kompilierte TypeBox-Schema im Code erzwungen.\n- `setTheme`-Fehler fallen auf `dark` zurück; `previewTheme`-Fehler ersetzen das aktuelle Theme nicht.\n- Watcher-Reload-Fehler behalten das aktuell geladene Theme bei, bis ein erfolgreiches Neuladen oder ein Fallback-Pfad ausgelöst wird.\n",
	"de/tui/tree.md": "---\ntitle: Tree-Befehlsreferenz\ndescription: >-\n  /tree-Befehlsreferenz zur Visualisierung des Sitzungsverlaufs und der\n  Gesprächsverzweigungen.\nsidebar:\n  order: 4\n  label: /tree-Befehl\ni18n:\n  sourceHash: ee0e412fe993\n  translator: machine\n---\n\n# `/tree`-Befehlsreferenz\n\n`/tree` öffnet den interaktiven **Sitzungsbaum**-Navigator. Er ermöglicht es Ihnen, zu einem beliebigen Eintrag in der aktuellen Sitzungsdatei zu springen und von diesem Punkt aus fortzufahren.\n\nDies ist eine dateiinterne Blattverschiebung, kein neuer Sitzungsexport.\n\n## Was `/tree` bewirkt\n\n- Erstellt einen Baum aus den aktuellen Sitzungseinträgen (`SessionManager.getTree()`)\n- Öffnet `TreeSelectorComponent` mit Tastaturnavigation, Filtern und Suche\n- Bei Auswahl wird `AgentSession.navigateTree(targetId, { summarize, customInstructions })` aufgerufen\n- Baut den sichtbaren Chat vom neuen Blattpfad aus neu auf\n- Füllt optional den Editor-Text vor, wenn eine Benutzer-/benutzerdefinierte Nachricht ausgewählt wird\n\nPrimäre Implementierung:\n\n- `src/modes/controllers/input-controller.ts` (`/tree`, Tastenkürzel-Verdrahtung, Doppel-Escape-Verhalten)\n- `src/modes/controllers/selector-controller.ts` (Baum-UI-Start + Zusammenfassungs-Prompt-Ablauf)\n- `src/modes/components/tree-selector.ts` (Navigation, Filter, Suche, Labels, Rendering)\n- `src/session/agent-session.ts` (`navigateTree` Blattwechsel + optionale Zusammenfassung)\n- `src/session/session-manager.ts` (`getTree`, `branch`, `branchWithSummary`, `resetLeaf`, Label-Persistenz)\n\n## Wie man ihn öffnet\n\nJede der folgenden Methoden öffnet denselben Selektor:\n\n- `/tree`\n- Konfigurierte Tastenkürzel-Aktion `tree`\n- Doppel-Escape bei leerem Editor, wenn `doubleEscapeAction = \"tree\"` (Standard)\n- `/branch` wenn `doubleEscapeAction = \"tree\"` (leitet zum Baum-Selektor statt zum Nur-Benutzer-Verzweigungswähler weiter)\n\n## Baum-UI-Modell\n\nDer Baum wird aus den Eltern-Zeigern der Sitzungseinträge (`id` / `parentId`) gerendert.\n\n- Kinder werden nach Zeitstempel aufsteigend sortiert (ältere zuerst, neuere weiter unten)\n- Der aktive Zweig (Pfad von der Wurzel zum aktuellen Blatt) ist mit einem Aufzählungszeichen markiert\n- Labels (falls vorhanden) werden als `[label]` vor dem Knotentext gerendert\n- Falls mehrere Wurzeln existieren (verwaiste/unterbrochene Elternketten), werden sie unter einer virtuellen Verzweigungswurzel angezeigt\n\n```text\nExample tree view (active path marked with •):\n\n├─ user: \"Start task\"\n│  └─ assistant: \"Plan\"\n│     ├─ • user: \"Try approach A\"\n│     │  └─ • assistant: \"A result\"\n│     │     └─ • [milestone] user: \"Continue A\"\n│     └─ user: \"Try approach B\"\n│        └─ assistant: \"B result\"\n```\n\nDer Selektor zentriert sich um die aktuelle Auswahl und zeigt bis zu:\n\n- `max(5, floor(terminalHeight / 2))` Zeilen\n\n## Tastenkürzel innerhalb des Baum-Selektors\n\n- `Up` / `Down`: Auswahl verschieben (umhüllend)\n- `Left` / `Right`: Seite hoch / Seite runter\n- `Enter`: Knoten auswählen\n- `Esc`: Suche löschen, falls aktiv; andernfalls Selektor schließen\n- `Ctrl+C`: Selektor schließen\n- `Tippen`: An Suchanfrage anhängen\n- `Backspace`: Suchzeichen löschen\n- `Shift+L`: Label des ausgewählten Eintrags bearbeiten/löschen\n- `Ctrl+O`: Filter vorwärts durchschalten\n- `Shift+Ctrl+O`: Filter rückwärts durchschalten\n- `Alt+D/T/U/L/A`: Direkt zu einem bestimmten Filtermodus springen\n\n## Filter und Suchsemantik\n\nFiltermodi (`TreeList`):\n\n1. `default`\n2. `no-tools`\n3. `user-only`\n4. `labeled-only`\n5. `all`\n\n### `default`\n\nZeigt die meisten Gesprächsknoten, blendet aber verwaltungstechnische Eintragstypen aus:\n\n- `label`\n- `custom`\n- `model_change`\n- `thinking_level_change`\n\n### `no-tools`\n\nWie `default`, blendet zusätzlich `toolResult`-Nachrichten aus.\n\n### `user-only`\n\nNur `message`-Einträge, bei denen die Rolle `user` ist.\n\n### `labeled-only`\n\nNur Einträge, die aktuell zu einem Label aufgelöst werden.\n\n### `all`\n\nAlles im Sitzungsbaum, einschließlich Verwaltungs-/benutzerdefinierter Einträge.\n\n### Verhalten bei reinen Tool-Assistenten-Knoten\n\nAssistenten-Nachrichten, die **nur Tool-Aufrufe** enthalten (kein Text), werden standardmäßig in allen gefilterten Ansichten ausgeblendet, es sei denn:\n\n- Die Nachricht ist fehlerhaft/abgebrochen (`stopReason` nicht `stop`/`toolUse`), oder\n- es ist das aktuelle Blatt (wird immer sichtbar gehalten)\n\n### Suchverhalten\n\n- Die Anfrage wird durch Leerzeichen tokenisiert\n- Der Abgleich ist nicht groß-/kleinschreibungssensitiv\n- Alle Token müssen übereinstimmen (UND-Semantik)\n- Durchsuchbarer Text umfasst Label, Rolle und typspezifischen Inhalt (Nachrichtentext, Verzweigungszusammenfassungstext, benutzerdefinierter Typ, Tool-Befehlsausschnitte usw.)\n\n## Auswahlergebnisse (wichtig)\n\n`navigateTree` berechnet das neue Blattverhalten anhand des ausgewählten Eintragstyps:\n\n### Auswahl einer `user`-Nachricht\n\n- Das neue Blatt wird die `parentId` des ausgewählten Eintrags\n- Wenn der Elternknoten `null` ist (Wurzel-Benutzernachricht), wird das Blatt auf die Wurzel zurückgesetzt (`resetLeaf()`)\n- Der ausgewählte Nachrichtentext wird zur Bearbeitung/erneuten Übermittlung in den Editor kopiert\n\n### Auswahl einer `custom_message`\n\n- Gleiche Blattregel wie bei Benutzernachrichten (`parentId`)\n- Der Textinhalt wird extrahiert und in den Editor kopiert\n\n### Auswahl eines Nicht-Benutzer-Knotens (Assistent/Tool/Zusammenfassung/Kompaktierung/benutzerdefinierte Verwaltung/usw.)\n\n- Das neue Blatt wird die ID des ausgewählten Knotens\n- Der Editor wird nicht vorausgefüllt\n\n### Auswahl des aktuellen Blatts\n\n- Keine Aktion; der Selektor schließt sich mit \"Bereits an diesem Punkt\"\n\n```text\nSelection decision (simplified):\n\nselected node\n   │\n   ├─ is current leaf? ── yes ──> close selector (no-op)\n   │\n   ├─ is user/custom_message? ── yes ──> leaf := parentId (or resetLeaf for root)\n   │                                     + prefill editor text\n   │\n   └─ otherwise ──> leaf := selected node id\n                    + no editor prefill\n```\n\n## Zusammenfassung-bei-Wechsel-Ablauf\n\nDer Zusammenfassungs-Prompt wird durch `branchSummary.enabled` gesteuert (Standard: `false`).\n\nWenn aktiviert, fragt die UI nach der Knotenauswahl:\n\n- `Keine Zusammenfassung`\n- `Zusammenfassen`\n- `Mit benutzerdefiniertem Prompt zusammenfassen`\n\nAblaufdetails:\n\n- Escape im Zusammenfassungs-Prompt öffnet den Baum-Selektor erneut\n- Abbruch des benutzerdefinierten Prompts kehrt zur Zusammenfassungsauswahl-Schleife zurück\n- Während der Zusammenfassung zeigt die UI einen Ladeindikator und bindet `Esc` an `abortBranchSummary()`\n- Wenn die Zusammenfassung abgebrochen wird, öffnet sich der Baum-Selektor erneut und es wird keine Verschiebung angewendet\n\n`navigateTree`-Interna:\n\n- Sammelt verlassene Zweigeinträge vom alten Blatt bis zum gemeinsamen Vorfahren\n- Löst `session_before_tree` aus (Erweiterungen können abbrechen oder Zusammenfassung einfügen)\n- Verwendet den Standard-Zusammenfasser nur wenn angefordert und benötigt\n- Wendet die Verschiebung an mit:\n  - `branchWithSummary(...)` wenn eine Zusammenfassung existiert\n  - `branch(newLeafId)` für Nicht-Wurzel-Verschiebung ohne Zusammenfassung\n  - `resetLeaf()` für Wurzel-Verschiebung ohne Zusammenfassung\n- Ersetzt die Agentenkonversation durch den neu aufgebauten Sitzungskontext\n- Löst `session_tree` aus\n\nHinweis: Wenn der Benutzer eine Zusammenfassung anfordert, aber nichts zusammenzufassen ist, wird die Navigation fortgesetzt, ohne einen Zusammenfassungseintrag zu erstellen.\n\n## Labels\n\nLabel-Bearbeitungen in der Baum-UI rufen `appendLabelChange(targetId, label)` auf.\n\n- Nicht-leeres Label setzt/aktualisiert das aufgelöste Label\n- Leeres Label löscht es\n- Labels werden als Nur-Anhängen-`label`-Einträge gespeichert\n- Baumknoten zeigen den aufgelösten Label-Zustand an, nicht den rohen Label-Eintragsverlauf\n\n## `/tree` im Vergleich zu benachbarten Operationen\n\n| Operation | Geltungsbereich | Ergebnis |\n|---|---|---|\n| `/tree` | Aktuelle Sitzungsdatei | Verschiebt das Blatt zum ausgewählten Punkt (gleiche Datei) |\n| `/branch` | Normalerweise aktuelle Sitzungsdatei -> neue Sitzungsdatei | Verzweigt standardmäßig von der ausgewählten **Benutzer**-Nachricht in eine neue Sitzungsdatei; wenn `doubleEscapeAction = \"tree\"`, öffnet `/branch` stattdessen die Baumnavigations-UI |\n| `/fork` | Gesamte aktuelle Sitzung | Dupliziert die Sitzung in eine neue persistierte Sitzungsdatei |\n| `/resume` | Sitzungsliste | Wechselt zu einer anderen Sitzungsdatei |\n\nWichtige Unterscheidung: `/tree` ist ein Navigations-/Repositionierungswerkzeug innerhalb einer Sitzungsdatei. `/branch`, `/fork` und `/resume` ändern alle den Sitzungsdatei-Kontext.\n\n## Operator-Workflows\n\n### Von einem früheren Benutzer-Prompt erneut ausführen, ohne den aktuellen Zweig zu verlieren\n\n1. `/tree`\n2. Frühere Benutzernachricht suchen/auswählen\n3. `Keine Zusammenfassung` wählen (oder zusammenfassen, falls benötigt)\n4. Vorausgefüllten Text im Editor bearbeiten\n5. Absenden\n\nEffekt: Ein neuer Zweig wächst vom ausgewählten Punkt innerhalb derselben Sitzungsdatei.\n\n### Aktuellen Zweig mit Kontextbrodkrümel verlassen\n\n1. `branchSummary.enabled` aktivieren\n2. `/tree` und Zielknoten auswählen\n3. `Zusammenfassen` wählen (oder benutzerdefinierten Prompt)\n\nEffekt: Ein `branch_summary`-Eintrag wird an der Zielposition angehängt, bevor fortgefahren wird.\n\n### Versteckte Verwaltungseinträge untersuchen\n\n1. `/tree`\n2. `Alt+A` drücken (alle)\n3. Nach `model`, `thinking`, `custom` oder Labels suchen\n\nEffekt: Vollständige interne Zeitleiste inspizieren, nicht nur Gesprächsknoten.\n\n### Ankerpunkte für spätere Sprünge markieren\n\n1. `/tree`\n2. Zu einem Eintrag navigieren\n3. `Shift+L` und Label setzen\n4. Später `Alt+L` (`labeled-only`) für schnelle Sprünge verwenden\n\nEffekt: Schnelle Navigation zwischen dauerhaften Verzweigungslandmarken.\n",
	"de/tui/tui-runtime-internals.md": "---\ntitle: TUI Laufzeit-Interna\ndescription: >-\n  Interna der Terminal-UI-Laufzeit, einschließlich Rendering-Pipeline,\n  Eingabeverarbeitung und Zustandsverwaltung.\nsidebar:\n  order: 2\n  label: Laufzeit-Interna\ni18n:\n  sourceHash: cc8f7dcce46a\n  translator: machine\n---\n\n# TUI Laufzeit-Interna\n\nDieses Dokument beschreibt den Laufzeitpfad außerhalb des Themes – von der Terminalein­gabe bis zur gerenderten Ausgabe im interaktiven Modus. Der Fokus liegt auf dem Verhalten in `packages/tui` und dessen Integration durch `packages/coding-agent`-Controller.\n\n## Laufzeitschichten und Zuständigkeiten\n\n- **`packages/tui`-Engine**: Terminal-Lebenszyklus, stdin-Normalisierung, Fokus-Routing, Render-Scheduling, differenzielles Zeichnen, Overlay-Komposition, Hardware-Cursor-Platzierung.\n- **`packages/coding-agent` interaktiver Modus**: erstellt den Komponentenbaum, bindet Editor-Callbacks und Keymaps, reagiert auf Agent-/Session-Ereignisse und übersetzt den Domänenzustand (Streaming, Werkzeugausführung, Wiederholungen, Plan-Modus) in UI-Komponenten.\n\nGrenzregel: Die TUI-Engine ist nachrichtenagnostisch. Sie kennt nur `Component.render(width)`, `handleInput(data)`, Fokus und Overlays. Agent-Semantik verbleibt in den interaktiven Controllern.\n\n## Implementierungsdateien\n\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/components/custom-editor.ts`](../../packages/coding-agent/src/modes/components/custom-editor.ts)\n- [`../../tui/src/tui.ts`](../../packages/tui/src/tui.ts)\n- [`../../tui/src/terminal.ts`](../../packages/tui/src/terminal.ts)\n- [`../../tui/src/editor-component.ts`](../../packages/tui/src/editor-component.ts)\n- [`../../tui/src/stdin-buffer.ts`](../../packages/tui/src/stdin-buffer.ts)\n- [`../../tui/src/components/loader.ts`](../../packages/tui/src/components/loader.ts)\n\n## Start und Aufbau des Komponentenbaums\n\n`InteractiveMode` instanziiert `TUI(new ProcessTerminal(), showHardwareCursor)` und erstellt persistente Container:\n\n- `chatContainer`\n- `pendingMessagesContainer`\n- `statusContainer`\n- `todoContainer`\n- `statusLine`\n- `editorContainer` (enthält `CustomEditor`)\n\n`init()` verdrahtet den Baum in dieser Reihenfolge, setzt den Fokus auf den Editor, registriert Eingabe-Handler über `InputController`, startet die TUI und fordert ein erzwungenes Rendering an.\n\nEin erzwungenes Rendering (`requestRender(true)`) setzt Zeilencaches und Cursor-Bookkeeping zurück, bevor neu gezeichnet wird.\n\n## Terminal-Lebenszyklus und stdin-Normalisierung\n\n`ProcessTerminal.start()`:\n\n1. Aktiviert Raw-Modus und Bracketed Paste.\n2. Fügt einen Resize-Handler ein.\n3. Erstellt einen `StdinBuffer`, um fragmentierte Escape-Chunks in vollständige Sequenzen aufzuteilen.\n4. Fragt die Unterstützung des Kitty-Keyboard-Protokolls ab (`CSI ? u`) und aktiviert Protokoll-Flags, sofern unterstützt.\n5. Versucht unter Windows die VT-Eingabe-Aktivierung über `kernel32`-Modus-Flags.\n\nVerhalten von `StdinBuffer`:\n\n- Puffert fragmentierte Escape-Sequenzen (CSI/OSC/DCS/APC/SS3).\n- Gibt `data` nur aus, wenn eine Sequenz vollständig ist oder nach einem Timeout geleert wird.\n- Erkennt Bracketed Paste und gibt ein `paste`-Ereignis mit dem unverarbeiteten eingefügten Text aus.\n\nDies verhindert, dass fragmentierte Escape-Chunks fälschlicherweise als normale Tastenanschläge interpretiert werden.\n\n## Eingabe-Routing und Fokusmodell\n\nEingabepfad:\n\n`stdin -> ProcessTerminal -> StdinBuffer -> TUI.#handleInput -> focusedComponent.handleInput`\n\nDetails zum Routing:\n\n1. Die TUI führt zunächst registrierte Eingabe-Listener aus (`addInputListener`), was ein Verbrauchs-/Transformationsverhalten ermöglicht.\n2. Die TUI verarbeitet den globalen Debug-Shortcut (`shift+ctrl+d`), bevor die Komponente den Dispatch erhält.\n3. Falls die fokussierte Komponente zu einem Overlay gehört, das nun ausgeblendet/unsichtbar ist, weist die TUI den Fokus dem nächsten sichtbaren Overlay oder dem gespeicherten Fokus vor dem Overlay zu.\n4. Key-Release-Ereignisse werden herausgefiltert, es sei denn, die fokussierte Komponente setzt `wantsKeyRelease = true`.\n5. Nach dem Dispatch plant die TUI ein Rendering.\n\n`setFocus()` schaltet auch `Focusable.focused` um, was steuert, ob Komponenten `CURSOR_MARKER` für die Hardware-Cursor-Platzierung ausgeben.\n\n## Aufteilung der Tastaturverarbeitung: Editor vs. Controller\n\n`CustomEditor` fängt hochpriore Kombinationen zuerst ab (Escape, Strg-C/D/Z, Strg-V, Strg-P-Varianten, Strg-T, Alt-Pfeil-hoch, benutzerdefinierte Erweiterungstasten) und delegiert den Rest an das Basisverhalten von `Editor` (Textbearbeitung, Verlauf, Autovervollständigung, Cursor-Bewegung).\n\n`InputController.setupKeyHandlers()` bindet anschließend Editor-Callbacks an Modus-Aktionen:\n\n- Abbruch / Modusbeendigung bei `Escape`\n- Herunterfahren bei doppeltem `Strg+C` oder `Strg+D` bei leerem Editor\n- Suspend/Resume bei `Strg+Z`\n- Slash-Befehle und Selektor-Hotkeys\n- Toggles für Folgeverarbeitung/Warteschlangen-Dequeue und Erweiterungsstufen\n\nDadurch verbleiben die Tastaturanalyse und Editor-Mechanik in `packages/tui`, während die Modussemantik in den Coding-Agent-Controllern bleibt.\n\n## Render-Schleife und Diff-Strategie\n\n`TUI.requestRender()` wird per `process.nextTick` auf ein Rendering pro Tick entprellt. Mehrere Zustandsänderungen im selben Durchlauf werden zusammengefasst.\n\nPipeline von `#doRender()`:\n\n1. Rendert den Root-Komponentenbaum zu `newLines`.\n2. Setzt sichtbare Overlays zusammen (sofern vorhanden).\n3. Extrahiert und entfernt `CURSOR_MARKER` aus den sichtbaren Viewport-Zeilen.\n4. Fügt Segment-Reset-Suffixe für Nicht-Bild-Zeilen an.\n5. Wählt zwischen vollständigem Neuzeichnen und differentiellem Patch:\n   - Erstes Frame\n   - Breitenänderung\n   - Verkleinerung mit aktiviertem `clearOnShrink` und ohne Overlays\n   - Bearbeitungen oberhalb des vorherigen Viewports\n6. Bei differenziellen Aktualisierungen wird nur der geänderte Zeilenbereich gepatcht und veraltete nachlaufende Zeilen werden bei Bedarf gelöscht.\n7. Neupositionierung des Hardware-Cursors für IME-Unterstützung.\n\nRender-Schreibvorgänge verwenden den synchronisierten Ausgabemodus (`CSI ? 2026 h/l`), um Flimmern und Tearing zu reduzieren.\n\n## Sicherheitsbeschränkungen beim Rendering\n\nKritische Sicherheitsprüfungen in `TUI`:\n\n- Gerenderte Nicht-Bild-Zeilen dürfen die Terminalbreite nicht überschreiten; bei Überlauf wird eine Ausnahme ausgelöst und Absturz-Diagnosedaten werden geschrieben.\n- Die Overlay-Komposition umfasst defensive Kürzung und eine Breitenprüfung nach der Komposition.\n- Breitenänderungen erzwingen ein vollständiges Neuzeichnen, da sich die Umbruchsemantik ändert.\n- Die Cursorposition wird vor der Bewegung begrenzt.\n\nDiese Einschränkungen sind Laufzeit-Erzwingungen, nicht nur Konventionen.\n\n## Resize-Verarbeitung\n\nResize-Ereignisse werden ereignisgesteuert von `ProcessTerminal` an `TUI.requestRender()` weitergeleitet.\n\nAuswirkungen:\n\n- Jede Breitenänderung löst ein vollständiges Neuzeichnen aus.\n- Das Viewport-/Top-Tracking (`#previousViewportTop`, `#maxLinesRendered`) vermeidet ungültige relative Cursor-Berechnungen bei Änderungen des Inhalts oder der Terminalgröße.\n- Die Overlay-Sichtbarkeit kann von den Terminalabmessungen abhängen (`OverlayOptions.visible`); der Fokus wird korrigiert, wenn Overlays nach einem Resize nicht mehr sichtbar sind.\n\n## Streaming und inkrementelle UI-Aktualisierungen\n\n`EventController` abonniert `AgentSessionEvent` und aktualisiert die UI inkrementell:\n\n- `agent_start`: Startet den Loader in `statusContainer`.\n- `message_start` Assistent: Erstellt `streamingComponent` und bindet es ein.\n- `message_update`: Aktualisiert den gestreamten Assistenteninhalt; erstellt/aktualisiert Werkzeugausführungs-Komponenten, sobald Werkzeugaufrufe erscheinen.\n- `tool_execution_update/end`: Aktualisiert Werkzeugergebnis-Komponenten und den Abschluss-Status.\n- `message_end`: Schließt den Assistenten-Stream ab, verarbeitet abgebrochene/Fehler-Annotationen, markiert ausstehende Werkzeugargumente bei normalem Stop als abgeschlossen.\n- `agent_end`: Stoppt Loader, löscht transienten Stream-Zustand, führt aufgeschobene Modellwechsel durch, gibt bei Hintergrundausführung eine Abschlussbenachrichtigung aus.\n\nDas Gruppieren von Read-Werkzeugen ist absichtlich zustandsbehaftet (`#lastReadGroup`), um aufeinanderfolgende Read-Werkzeugaufrufe zu einem visuellen Block zusammenzufassen, bis ein Nicht-Read-Unterbrecher auftritt.\n\n## Status- und Loader-Orchestrierung\n\nZuständigkeit für die Status-Lane:\n\n- `statusContainer` enthält transiente Loader (`loadingAnimation`, `autoCompactionLoader`, `retryLoader`).\n- `statusLine` rendert persistente Status-/Hook-/Plan-Indikatoren und steuert die Aktualisierungen des oberen Editor-Rahmens.\n\nLoader-Verhalten:\n\n- `Loader` aktualisiert alle 80 ms über ein Intervall und fordert pro Frame ein Rendering an.\n- Escape-Handler werden während der automatischen Komprimierung und automatischer Wiederholung temporär überschrieben, um diese Vorgänge abbrechen zu können.\n- Bei Beendigungs-/Abbruchpfaden stellen Controller die vorherigen Escape-Handler wieder her und stoppen/löschen Loader-Komponenten.\n\n## Modusübergänge und Hintergrundausführung\n\n### Bash-/Python-Eingabemodi\n\nTexteingabe-Präfixe schalten Editor-Rahmenmodus-Flags um:\n\n- `!` -> Bash-Modus\n- `$` (kein Template-Literal-Präfix) -> Python-Modus\n\nEscape beendet den inaktiven Modus durch Löschen des Editor-Texts und Wiederherstellen der Rahmenfarbe; bei aktiver Ausführung bricht Escape stattdessen die laufende Aufgabe ab.\n\n### Plan-Modus\n\n`InteractiveMode` verfolgt Plan-Modus-Flags, Status-Zeilen-Zustand, aktive Werkzeuge und Modellwechsel. Beim Ein-/Austritt werden Session-Modus-Einträge sowie Status und UI-Zustand aktualisiert, einschließlich eines aufgeschobenen Modellwechsels, wenn Streaming aktiv ist.\n\n### Suspend/Resume (`Strg+Z`)\n\n`InputController.handleCtrlZ()`:\n\n1. Registriert einen einmaligen `SIGCONT`-Handler, um die TUI neu zu starten und ein erzwungenes Rendering anzufordern.\n2. Stoppt die TUI vor dem Suspend.\n3. Sendet `SIGTSTP` an die Prozessgruppe.\n\n### Hintergrundmodus (`/background` oder `/bg`)\n\n`handleBackgroundCommand()`:\n\n- Lehnt ab, wenn im Leerlauf.\n- Wechselt den Werkzeug-UI-Kontext zu nicht-interaktiv (`hasUI=false`), sodass interaktive UI-Werkzeuge sofort fehlschlagen.\n- Stoppt Loader/Status-Zeile und hebt die Subscription des Vordergrundereignis-Handlers auf.\n- Abonniert den Hintergrundereignis-Handler (wartet primär auf `agent_end`).\n- Stoppt die TUI und sendet `SIGTSTP` (POSIX-Job-Control-Pfad).\n\nBei `agent_end` im Hintergrund ohne ausstehende Arbeit sendet der Controller eine Abschlussbenachrichtigung und fährt herunter.\n\n## Abbruchpfade\n\nPrimäre Abbrucheingaben:\n\n- `Escape` während aktivem Stream-Loader: Stellt wartende Nachrichten in den Editor zurück und bricht den Agenten ab.\n- `Escape` während der Bash-/Python-Ausführung: Bricht den laufenden Befehl ab.\n- `Escape` während automatischer Komprimierung/Wiederholung: Ruft dedizierte Abbruchmethoden über temporäre Escape-Handler auf.\n- `Strg+C` einfach: Editor leeren; doppelt innerhalb von 500 ms: Herunterfahren.\n\nAbbrüche sind zustandsbedingt; dieselbe Taste kann je nach Laufzeitzustand Abbruch, Modusbeendigung, Selektor-Auslösung oder Nichts bedeuten.\n\n## Ereignisgesteuerte vs. gedrosselte Verarbeitung\n\nEreignisgesteuerte Aktualisierungen:\n\n- Agent-Session-Ereignisse (`EventController`)\n- Tastatur-Eingabe-Callbacks (`InputController`)\n- Terminal-Resize-Callback\n- Theme-/Branch-Watcher in `InteractiveMode`\n\nGedrosselte/entprellte Pfade:\n\n- TUI-Rendering ist Tick-entprellt (`requestRender`-Zusammenfassung).\n- Loader-Animation ist festintervallbasiert (80 ms), jedes Frame fordert ein Rendering an.\n- Editor-Autovervollständigungs-Aktualisierungen (innerhalb von `Editor`) verwenden Entprell-Timer, um Neuberechnungen während der Eingabe zu reduzieren.\n\nDie Laufzeit kombiniert somit ereignisgesteuerte Zustandsübergänge mit begrenzter Render-Kadenz, um Interaktivität reaktionsfähig zu halten, ohne Repaint-Stürme zu verursachen.\n",
	"de/tui/tui.md": "---\ntitle: TUI-Integration für Erweiterungen und benutzerdefinierte Tools\ndescription: >-\n  TUI-Integrationsvertrag für Erweiterungen, benutzerdefinierte Tools und\n  benutzerdefinierte Renderer.\nsidebar:\n  order: 1\n  label: Erweiterungsintegration\ni18n:\n  sourceHash: 47f8f2b2045e\n  translator: machine\n---\n\n# TUI-Integration für Erweiterungen und benutzerdefinierte Tools\n\nDieses Dokument behandelt den **aktuellen** TUI-Vertrag, der von `packages/coding-agent` und `packages/tui` für Erweiterungs-UI, benutzerdefinierte Tool-UI und benutzerdefinierte Renderer verwendet wird.\n\n## Was dieses Subsystem ist\n\nDie Laufzeitumgebung hat zwei Schichten:\n\n- **Rendering-Engine (`packages/tui`)**: differenzieller Terminal-Renderer, Eingabeverteilung, Fokus, Overlays, Cursorplatzierung.\n- **Integrationsschicht (`packages/coding-agent`)**: bindet Erweiterungs-/benutzerdefinierte-Tool-Komponenten ein, verbindet Tastenkürzel/Theme und stellt den Editor-Zustand wieder her.\n\n## Laufzeitverhalten nach Modus\n\n| Modus | Verfügbarkeit von `ctx.ui.custom(...)` | Hinweise |\n| --- | --- | --- |\n| Interaktive TUI | Unterstützt | Die Komponente wird im Editor-Bereich eingebunden, fokussiert und muss `done(result)` aufrufen, um aufzulösen. |\n| Hintergrund/Headless | Nicht interaktiv | UI-Kontext ist ein No-Op (`hasUI === false`). |\n| RPC-Modus | Nicht unterstützt | `custom()` gibt `Promise<never>` zurück und bindet keine TUI-Komponenten ein. |\n\nWenn Ihre Erweiterung/Ihr Tool im nicht-interaktiven Modus laufen kann, prüfen Sie mit `ctx.hasUI` / `pi.hasUI`.\n\n## Kern-Komponentenvertrag (`@f5-sales-demo/pi-tui`)\n\n`packages/tui/src/tui.ts` definiert:\n\n```ts\nexport interface Component {\n  render(width: number): string[];\n  handleInput?(data: string): void;\n  wantsKeyRelease?: boolean;\n  invalidate(): void;\n}\n```\n\n`Focusable` ist separat:\n\n```ts\nexport interface Focusable {\n  focused: boolean;\n}\n```\n\nDas Cursorverhalten verwendet `CURSOR_MARKER` (nicht `getCursorPosition`). Fokussierte Komponenten geben den Marker im gerenderten Text aus; `TUI` extrahiert ihn und positioniert den Hardware-Cursor.\n\n## Rendering-Einschränkungen (Terminal-Sicherheit)\n\nIhre `render(width)`-Ausgabe muss terminal-sicher sein:\n\n1. **Überschreiten Sie niemals `width` in einer Zeile**. Der Renderer wirft einen Fehler, wenn eine Nicht-Bild-Zeile überläuft.\n2. **Messen Sie die visuelle Breite**, nicht die String-Länge: verwenden Sie `visibleWidth()`.\n3. **Kürzen/umbrechen Sie ANSI-bewussten Text** mit `truncateToWidth()` / `wrapTextWithAnsi()`.\n4. **Bereinigen Sie Tabs/Inhalte** aus externen Quellen mit `replaceTabs()` (und übergeordneten Bereinigungsfunktionen in den Render-Pfaden von coding-agent).\n\nMinimales Muster:\n\n```ts\nimport { replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\n\nrender(width: number): string[] {\n  return this.lines.map(line => truncateToWidth(replaceTabs(line), width));\n}\n```\n\n## Eingabeverarbeitung und Tastenkürzel\n\n### Rohe Tastenerkennung\n\nVerwenden Sie `matchesKey(data, \"...\")` für Navigationstasten und Kombinationen.\n\n### Benutzer-konfigurierte App-Tastenkürzel beachten\n\nErweiterungs-UI-Factories erhalten einen `KeybindingsManager` (interaktiver Modus), damit Sie zugeordnete Aktionen statt hartcodierter Tasten verwenden können:\n\n```ts\nif (keybindings.matches(data, \"interrupt\")) {\n  done(undefined);\n  return;\n}\n```\n\n### Tastenfreigabe-/Wiederholungsereignisse\n\nTastenfreigabe-Ereignisse werden gefiltert, es sei denn, Ihre Komponente setzt:\n\n```ts\nwantsKeyRelease = true;\n```\n\nVerwenden Sie dann bei Bedarf `isKeyRelease()` / `isKeyRepeat()`.\n\n## Fokus, Overlays und Cursor\n\n- `TUI.setFocus(component)` leitet Eingaben an diese Komponente weiter.\n- Overlay-APIs existieren in `TUI` (`showOverlay`, `OverlayHandle`), aber die Einbindung über `ctx.ui.custom` einer Erweiterung im interaktiven Modus ersetzt derzeit direkt den Editor-Komponentenbereich.\n- Die Option `custom(..., options?: { overlay?: boolean })` existiert in Erweiterungstypen; die interaktive Erweiterungseinbindung ignoriert diese Option derzeit.\n\n## Einbindungspunkte und Rückgabeverträge\n\n## 1) Erweiterungs-UI (`ExtensionUIContext`)\n\nAktuelle Signatur (`extensibility/extensions/types.ts`):\n\n```ts\ncustom<T>(\n  factory: (\n    tui: TUI,\n    theme: Theme,\n    keybindings: KeybindingsManager,\n    done: (result: T) => void,\n  ) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n  options?: { overlay?: boolean },\n): Promise<T>\n```\n\nVerhalten im interaktiven Modus (`extension-ui-controller.ts`):\n\n- Speichert den Editor-Text.\n- Ersetzt die Editor-Komponente durch Ihre Komponente.\n- Fokussiert Ihre Komponente.\n- Bei `done(result)`: ruft `component.dispose?.()` auf, stellt Editor + Text wieder her, fokussiert den Editor, löst das Promise auf.\n\nDaher ist `done(...)` für die Fertigstellung zwingend erforderlich.\n\n## 2) Hook/Custom-Tool-UI-Kontext (Legacy-Typisierung)\n\n`HookUIContext.custom` ist in Hook/Custom-Tool-Typen als `(tui, theme, done)` typisiert.\nDie zugrunde liegende interaktive Implementierung ruft Factories mit `(tui, theme, keybindings, done)` auf. JS-Konsumenten können das zusätzliche Argument verwenden; die Typebene-Kompatibilität spiegelt weiterhin die 3-Argument-Legacy-Signatur wider.\n\nBenutzerdefinierte Tools verwenden typischerweise denselben UI-Einstiegspunkt über das factory-bezogene `pi.ui`-Objekt und geben dann den ausgewählten Wert im normalen Tool-Inhalt zurück:\n\n```ts\nasync execute(toolCallId, params, onUpdate, ctx, signal) {\n  if (!pi.hasUI) {\n    return { content: [{ type: \"text\", text: \"UI unavailable\" }] };\n  }\n\n  const picked = await pi.ui.custom<string | undefined>((tui, theme, done) => {\n    const component = new MyPickerComponent(done, signal);\n    return component;\n  });\n\n  return { content: [{ type: \"text\", text: picked ? `Picked: ${picked}` : \"Cancelled\" }] };\n}\n```\n\n## 3) Benutzerdefinierte Tool-Aufruf-/Ergebnis-Renderer\n\nBenutzerdefinierte Tools und Erweiterungstools können Komponenten zurückgeben von:\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\n`options` enthält derzeit:\n\n- `expanded: boolean`\n- `isPartial: boolean`\n- `spinnerFrame?: number`\n\nDiese Renderer werden von `ToolExecutionComponent` eingebunden.\n\n## Lebenszyklus und Abbruch\n\n- `dispose()` ist auf Typebene optional, sollte aber implementiert werden, wenn Sie Timer, Unterprozesse, Watcher, Sockets oder Overlays besitzen.\n- `done(...)` sollte genau einmal aus Ihrem Komponentenfluss aufgerufen werden.\n- Für abbrechbare, langlebige UI kombinieren Sie `CancellableLoader` mit `AbortSignal` und rufen `done(...)` von `onAbort` auf.\n\nBeispiel für ein Abbruchmuster:\n\n```ts\nconst loader = new CancellableLoader(tui, theme.fg(\"accent\"), theme.fg(\"muted\"), \"Working...\");\nloader.onAbort = () => done(undefined);\nvoid doWork(loader.signal).then(result => done(result));\nreturn loader;\n```\n\n## Realistisches Beispiel einer benutzerdefinierten Komponente (Erweiterungsbefehl)\n\n```ts\nimport type { Component } from \"@f5-sales-demo/pi-tui\";\nimport { SelectList, matchesKey, replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\nimport { getSelectListTheme, type ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nclass Picker implements Component {\n  list: SelectList;\n  keybindings: any;\n  done: (value: string | undefined) => void;\n\n  constructor(\n    items: Array<{ value: string; label: string }>,\n    keybindings: any,\n    done: (value: string | undefined) => void,\n  ) {\n    this.list = new SelectList(items, 8, getSelectListTheme());\n    this.keybindings = keybindings;\n    this.done = done;\n    this.list.onSelect = item => this.done(item.value);\n    this.list.onCancel = () => this.done(undefined);\n  }\n\n  handleInput(data: string): void {\n    if (this.keybindings.matches(data, \"interrupt\")) {\n      this.done(undefined);\n      return;\n    }\n    this.list.handleInput(data);\n  }\n\n  render(width: number): string[] {\n    return this.list.render(width).map(line => truncateToWidth(replaceTabs(line), width));\n  }\n\n  invalidate(): void {\n    this.list.invalidate();\n  }\n}\n\nexport default function extension(pi: ExtensionAPI): void {\n  pi.registerCommand(\"pick-model\", {\n    description: \"Pick a model profile\",\n    handler: async (_args, ctx) => {\n      if (!ctx.hasUI) return;\n\n      const selected = await ctx.ui.custom<string | undefined>((tui, theme, keybindings, done) => {\n        const items = [\n          { value: \"fast\", label: theme.fg(\"accent\", \"Fast\") },\n          { value: \"balanced\", label: \"Balanced\" },\n          { value: \"quality\", label: \"Quality\" },\n        ];\n        return new Picker(items, keybindings, done);\n      });\n\n      if (selected) ctx.ui.notify(`Selected profile: ${selected}`, \"info\");\n    },\n  });\n}\n```\n\n## Wichtige Implementierungsdateien\n\n- `packages/tui/src/tui.ts` — `Component`, `Focusable`, Cursor-Marker, Fokus, Overlay, Eingabeverteilung.\n- `packages/tui/src/utils.ts` — Breiten-/Kürzungs-/Bereinigungsprimitive.\n- `packages/tui/src/keys.ts` / `keybindings.ts` — Tastenanalyse und konfigurierbare Aktionszuordnung.\n- `packages/coding-agent/src/modes/controllers/extension-ui-controller.ts` — interaktive Ein-/Ausbindung für Erweiterungs-/Hook-/Custom-Tool-UI.\n- `packages/coding-agent/src/extensibility/extensions/types.ts` — Erweiterungs-UI- und Renderer-Verträge.\n- `packages/coding-agent/src/extensibility/hooks/types.ts` — Hook-UI-Vertrag (Legacy-Custom-Signatur).\n- `packages/coding-agent/src/extensibility/custom-tools/types.ts` — Verträge für Ausführung/Rendering benutzerdefinierter Tools.\n- `packages/coding-agent/src/modes/components/tool-execution.ts` — Einbindung von `renderCall`/`renderResult`-Komponenten und Partial-State-Optionen.\n- `packages/coding-agent/src/tools/context.ts` — Tool-UI-Kontextpropagierung (`hasUI`, `ui`).\n",
	"en/configuration/blob-artifact-architecture.md": "---\ntitle: Blob and Artifact Storage Architecture\ndescription: Content-addressable blob store and artifact registry for session media, screenshots, and tool outputs.\nsidebar:\n  order: 7\n  label: Blob & artifact storage\n---\n\n# Blob and artifact storage architecture\n\nThis document describes how coding-agent stores large/binary payloads outside session JSONL, how truncated tool output is persisted, and how internal URLs (`artifact://`, `agent://`) resolve back to stored data.\n\n## Why two storage systems exist\n\nThe runtime uses two different persistence mechanisms for different data shapes:\n\n- **Content-addressed blobs** (`blob:sha256:<hash>`): global, binary-oriented storage used to externalize large image base64 payloads from persisted session entries.\n- **Session-scoped artifacts** (files under `<sessionFile-without-.jsonl>/`): per-session text files used for full tool outputs and subagent outputs.\n\nThey are intentionally separate:\n\n- blob storage optimizes deduplication and stable references by content hash,\n- artifact storage optimizes append-only session tooling and human/tool retrieval by local IDs.\n\n## Storage boundaries and on-disk layout\n\n## Blob store boundary (global)\n\n`SessionManager` constructs `BlobStore(getBlobsDir())`, so blob files live in a shared global blob directory (not in a session folder).\n\nBlob file naming:\n\n- file path: `<blobsDir>/<sha256-hex>`\n- no extension\n- reference string stored in entries: `blob:sha256:<sha256-hex>`\n\nImplications:\n\n- same binary content across sessions resolves to the same hash/path,\n- writes are idempotent at the content level,\n- blobs can outlive any individual session file.\n\n## Artifact boundary (session-local)\n\n`ArtifactManager` derives artifact directory from session file path:\n\n- session file: `.../<timestamp>_<sessionId>.jsonl`\n- artifacts directory: `.../<timestamp>_<sessionId>/` (strip `.jsonl`)\n\nArtifact types share this directory:\n\n- truncated tool output files: `<numericId>.<toolType>.log` (for `artifact://`)\n- subagent output files: `<outputId>.md` (for `agent://`)\n\n## ID and name allocation schemes\n\n## Blob IDs: content hash\n\n`BlobStore.put()` computes SHA-256 over raw binary bytes and returns:\n\n- `hash`: hex digest,\n- `path`: `<blobsDir>/<hash>`,\n- `ref`: `blob:sha256:<hash>`.\n\nNo session-local counter is used.\n\n## Artifact IDs: session-local monotonic integer\n\n`ArtifactManager` scans existing `*.log` artifact files on first use to find max existing numeric ID and sets `nextId = max + 1`.\n\nAllocation behavior:\n\n- file format: `{id}.{toolType}.log`\n- IDs are sequential strings (`\"0\"`, `\"1\"`, ...)\n- resume does not overwrite existing artifacts because scan happens before allocation.\n\nIf artifact directory is missing, scanning yields empty list and allocation starts from `0`.\n\n## Agent output IDs (`agent://`)\n\n`AgentOutputManager` allocates IDs for subagent outputs as `<index>-<requestedId>` (optionally nested under parent prefix, e.g. `0-Parent.1-Child`). It scans existing `.md` files on initialization to continue from the next index on resume.\n\n## Persistence dataflow\n\n## 1) Session entry persistence rewrite path\n\nBefore session entries are written (`#rewriteFile` / incremental persist), `SessionManager` calls `prepareEntryForPersistence()` (via `truncateForPersistence`).\n\nKey behaviors:\n\n1. **Large string truncation**: oversized strings are cut and suffixed with `\"[Session persistence truncated large content]\"`.\n2. **Transient field stripping**: `partialJson` and `jsonlEvents` are removed from persisted entries.\n3. **Image externalization to blobs**:\n   - only applies to image blocks in `content` arrays,\n   - only when `data` is not already a blob ref,\n   - only when base64 length is at least threshold (`BLOB_EXTERNALIZE_THRESHOLD = 1024`),\n   - replaces inline base64 with `blob:sha256:<hash>`.\n\nThis keeps session JSONL compact while preserving recoverability.\n\n## 2) Session load rehydration path\n\nWhen opening a session (`setSessionFile`), after migrations, `SessionManager` runs `resolveBlobRefsInEntries()`.\n\nFor each message/custom-message image block with `blob:sha256:<hash>`:\n\n- reads blob bytes from blob store,\n- converts bytes back to base64,\n- mutates in-memory entry to inline base64 for runtime consumers.\n\nIf blob is missing:\n\n- `resolveImageData()` logs warning,\n- returns original ref string unchanged,\n- load continues (no hard crash).\n\n## 3) Tool output spill/truncation path\n\n`OutputSink` powers streaming output in bash/python/ssh and related executors.\n\nBehavior:\n\n1. Every chunk is sanitized and appended to in-memory tail buffer.\n2. When in-memory bytes exceed spill threshold (`DEFAULT_MAX_BYTES`, 50KB), sink marks output truncated.\n3. If an artifact path is available, sink opens a file writer and writes:\n   - existing buffered content once,\n   - all subsequent chunks.\n4. In-memory buffer is always trimmed to tail window for display.\n5. `dump()` returns summary including `artifactId` only when file sink was successfully created.\n\nPractical effect:\n\n- UI/tool return shows truncated tail,\n- full output is preserved in artifact file and referenced as `artifact://<id>`.\n\nIf file sink creation fails (I/O error, missing path, etc.), sink silently falls back to in-memory truncation only; full output is not persisted.\n\n## URL access model\n\n## `blob:` references\n\n`blob:sha256:<hash>` is a persistence reference inside session entry payloads, not an internal URL scheme handled by the router. Resolution is done by `SessionManager` during session load.\n\n## `artifact://<id>`\n\nHandled by `ArtifactProtocolHandler`:\n\n- requires active session artifact directory,\n- ID must be numeric,\n- resolves by matching filename prefix `<id>.`,\n- returns raw text (`text/plain`) from the matched `.log` file,\n- when missing, error includes list of available artifact IDs.\n\nMissing directory behavior:\n\n- if artifacts directory does not exist, throws `No artifacts directory found`.\n\n## `agent://<id>`\n\nHandled by `AgentProtocolHandler` over `<artifactsDir>/<id>.md`:\n\n- plain form returns markdown text,\n- `/path` or `?q=` forms perform JSON extraction,\n- path and query extraction cannot be combined,\n- if extraction requested, file content must parse as JSON.\n\nMissing directory behavior:\n\n- throws `No artifacts directory found`.\n\nMissing output behavior:\n\n- throws `Not found: <id>` with available IDs from existing `.md` files.\n\nRead tool integration:\n\n- `read` supports offset/limit pagination for non-extraction internal URL reads,\n- rejects `offset/limit` when `agent://` extraction is used.\n\n## Resume, fork, and move semantics\n\n## Resume\n\n- `ArtifactManager` scans existing `{id}.*.log` files on first allocation and continues numbering.\n- `AgentOutputManager` scans existing `.md` output IDs and continues numbering.\n- `SessionManager` rehydrates blob refs to base64 on load.\n\n## Fork\n\n`SessionManager.fork()` creates a new session file with new session ID and `parentSession` link, then returns old/new file paths. Artifact copying is handled by `AgentSession.fork()`:\n\n- attempts recursive copy of old artifact directory to new artifact directory,\n- missing old directory is tolerated,\n- non-ENOENT copy errors are logged as warnings and fork still completes.\n\nID implications after fork:\n\n- if copy succeeded, artifact counters in new session continue after max copied ID,\n- if copy failed/skipped, new session artifact IDs start from `0`.\n\nBlob implications after fork:\n\n- blobs are global and content-addressed, so no blob directory copy is required.\n\n## Move to new cwd\n\n`SessionManager.moveTo()` renames both session file and artifact directory to the new default session directory, with rollback logic if a later step fails. This preserves artifact identity while relocating session scope.\n\n## Failure handling and fallback paths\n\n| Case | Behavior |\n| --- | --- |\n| Blob file missing during rehydration | Warn and keep `blob:sha256:` ref string in-memory |\n| Blob read ENOENT via `BlobStore.get` | Returns `null` |\n| Artifact directory missing (`ArtifactManager.listFiles`) | Returns empty list (allocation can start fresh) |\n| Artifact directory missing (`artifact://` / `agent://`) | Throws explicit `No artifacts directory found` |\n| Artifact ID not found | Throws with available IDs listing |\n| OutputSink artifact writer init fails | Continues with tail-only truncation (no full-output artifact) |\n| No session file (some task paths) | Task tool falls back to temp artifacts directory for subagent outputs |\n\n## Binary blob externalization vs text-output artifacts\n\n- **Blob externalization** is for binary image payloads inside persisted session entry content; it replaces inline base64 in JSONL with stable content refs.\n- **Artifacts** are plain text files for execution output and subagent output; they are addressable by session-local IDs through internal URLs.\n\nThe two systems intersect only indirectly (both reduce session JSONL bloat) but have different identity, lifetime, and retrieval paths.\n\n## Implementation files\n\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts) — blob reference format, hashing, put/get, externalize/resolve helpers.\n- [`src/session/artifacts.ts`](../../packages/coding-agent/src/session/artifacts.ts) — session artifact directory model and numeric artifact ID allocation.\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — `OutputSink` truncation/spill-to-file behavior and summary metadata.\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts) — persistence transforms, blob rehydration on load, session fork/move interactions.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — artifact directory copy during interactive fork.\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — tool artifact manager bootstrap and per-tool artifact path allocation.\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — `artifact://` resolver.\n- [`src/internal-urls/agent-protocol.ts`](../../packages/coding-agent/src/internal-urls/agent-protocol.ts) — `agent://` resolver + JSON extraction.\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — internal URL router wiring and artifacts-dir resolver.\n- [`src/task/output-manager.ts`](../../packages/coding-agent/src/task/output-manager.ts) — session-scoped agent output ID allocation for `agent://`.\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — subagent output artifact writes (`<id>.md`) and temp artifact directory fallback.\n",
	"en/configuration/config-usage.md": "---\ntitle: Configuration Discovery and Resolution\ndescription: How xcsh discovers, resolves, and layers configuration from project, user, and enterprise roots.\nsidebar:\n  order: 1\n  label: Configuration\n---\n\n# Configuration Discovery and Resolution\n\nThis document describes how the coding-agent resolves configuration today: which roots are scanned, how precedence works, and how resolved config is consumed by settings, skills, hooks, tools, and extensions.\n\n## Scope\n\nPrimary implementation:\n\n- `src/config.ts`\n- `src/config/settings.ts`\n- `src/config/settings-schema.ts`\n- `src/discovery/builtin.ts`\n- `src/discovery/helpers.ts`\n\nKey integration points:\n\n- `src/capability/index.ts`\n- `src/discovery/index.ts`\n- `src/extensibility/skills.ts`\n- `src/extensibility/hooks/loader.ts`\n- `src/extensibility/custom-tools/loader.ts`\n- `src/extensibility/extensions/loader.ts`\n\n---\n\n## Resolution flow (visual)\n\n```text\n         Config roots (ordered)\n┌───────────────────────────────────────┐\n│ 1) ~/.xcsh/agent + <cwd>/.xcsh          │\n│ 2) ~/.claude   + <cwd>/.claude        │\n│ 3) ~/.codex    + <cwd>/.codex         │\n│ 4) ~/.gemini   + <cwd>/.gemini        │\n└───────────────────────────────────────┘\n                    │\n                    ▼\n        config.ts helper resolution\n  (getConfigDirs/findConfigFile/findNearest...)\n                    │\n                    ▼\n       capability providers enumerate items\n (native, claude, codex, gemini, agents, etc.)\n                    │\n                    ▼\n      priority sort + per-capability dedup\n                    │\n                    ▼\n          subsystem-specific consumption\n   (settings, skills, hooks, tools, extensions)\n```\n\n## 1) Config roots and source order\n\n## Canonical roots\n\n`src/config.ts` defines a fixed source priority list:\n\n1. `.xcsh` (native)\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nUser-level bases:\n\n- `~/.xcsh/agent`\n- `~/.claude`\n- `~/.codex`\n- `~/.gemini`\n\nProject-level bases:\n\n- `<cwd>/.xcsh`\n- `<cwd>/.claude`\n- `<cwd>/.codex`\n- `<cwd>/.gemini`\n\n`CONFIG_DIR_NAME` is `.xcsh` (`packages/utils/src/dirs.ts`).\n\n## Important constraint\n\nThe generic helpers in `src/config.ts` do **not** include `.pi` in source discovery order.\n\n---\n\n## 2) Core discovery helpers (`src/config.ts`)\n\n## `getConfigDirs(subpath, options)`\n\nReturns ordered entries:\n\n- User-level entries first (by source priority)\n- Then project-level entries (by same source priority)\n\nOptions:\n\n- `user` (default `true`)\n- `project` (default `true`)\n- `cwd` (default `getProjectDir()`)\n- `existingOnly` (default `false`)\n\nThis API is used for directory-based config lookups (commands, hooks, tools, agents, etc.).\n\n## `findConfigFile(subpath, options)` / `findConfigFileWithMeta(...)`\n\nSearches for the first existing file across ordered bases, returns first match (path-only or path+metadata).\n\n## `findAllNearestProjectConfigDirs(subpath, cwd)`\n\nWalks parent directories upward and returns the **nearest existing directory per source base** (`.xcsh`, `.claude`, `.codex`, `.gemini`), then sorts results by source priority.\n\nUse this when project config should be inherited from ancestor directories (monorepo/nested workspace behavior).\n\n---\n\n## 3) File config wrapper (`ConfigFile<T>` in `src/config.ts`)\n\n`ConfigFile<T>` is the schema-validated loader for single config files.\n\nSupported formats:\n\n- `.yml` / `.yaml`\n- `.json` / `.jsonc`\n\nBehavior:\n\n- Validates parsed data with AJV against a provided TypeBox schema.\n- Caches load result until `invalidate()`.\n- Returns tri-state result via `tryLoad()`:\n  - `ok`\n  - `not-found`\n  - `error` (`ConfigError` with schema/parse context)\n\nLegacy migration still supported:\n\n- If target path is `.yml`/`.yaml`, a sibling `.json` is auto-migrated once (`migrateJsonToYml`).\n\n---\n\n## 4) Settings resolution model (`src/config/settings.ts`)\n\nThe runtime settings model is layered:\n\n1. Global settings: `~/.xcsh/agent/config.yml`\n2. Project settings: discovered via settings capability (`settings.json` from providers)\n3. Runtime overrides: in-memory, non-persistent\n4. Schema defaults: from `SETTINGS_SCHEMA`\n\nEffective read path:\n\n`defaults <- global <- project <- overrides`\n\nWrite behavior:\n\n- `settings.set(...)` writes to the **global** layer (`config.yml`) and queues background save.\n- Project settings are read-only from capability discovery.\n\n## Migration behavior still active\n\nOn startup, if `config.yml` is missing:\n\n1. Migrate from `~/.xcsh/agent/settings.json` (renamed to `.bak` on success)\n2. Merge with legacy DB settings from `agent.db`\n3. Write merged result to `config.yml`\n\nField-level migrations in `#migrateRawSettings`:\n\n- `queueMode` -> `steeringMode`\n- `ask.timeout` milliseconds -> seconds when old value looks like ms (`> 1000`)\n- Legacy flat `theme: \"...\"` -> `theme.dark/theme.light` structure\n\n---\n\n## 5) Capability/discovery integration\n\nMost non-core config loading flows through the capability registry (`src/capability/index.ts` + `src/discovery/index.ts`).\n\n## Provider ordering\n\nProviders are sorted by numeric priority (higher first). Example priorities:\n\n- Native OMP (`builtin.ts`): `100`\n- Claude: `80`\n- Codex / agents / Claude marketplace: `70`\n- Gemini: `60`\n\n```text\nProvider precedence (higher wins)\n\nnative (.xcsh)          priority 100\nclaude                 priority  80\ncodex / agents / ...   priority  70\ngemini                 priority  60\n```\n\n## Dedup semantics\n\nCapabilities define a `key(item)`:\n\n- same key => first item wins (higher-priority/earlier-loaded item)\n- no key (`undefined`) => no dedup, all items retained\n\nRelevant keys:\n\n- skills: `name`\n- tools: `name`\n- hooks: `${type}:${tool}:${name}`\n- extension modules: `name`\n- extensions: `name`\n- settings: no dedup (all items preserved)\n\n---\n\n## 6) Native `.xcsh` provider behavior (`src/discovery/builtin.ts`)\n\nNative provider (`id: native`) reads from:\n\n- project: `<cwd>/.xcsh/...`\n- user: `~/.xcsh/agent/...`\n\n### Directory admission rule\n\n`builtin.ts` only includes a config root if the directory exists **and is non-empty** (`ifNonEmptyDir`).\n\n### Scope-specific loading\n\n- Skills: `skills/*/SKILL.md`\n- Slash commands: `commands/*.md`\n- Rules: `rules/*.{md,mdc}`\n- Prompts: `prompts/*.md`\n- Instructions: `instructions/*.md`\n- Hooks: `hooks/pre/*`, `hooks/post/*`\n- Tools: `tools/*.json|*.md` and `tools/<name>/index.ts`\n- Extension modules: discovered under `extensions/` (+ legacy `settings.json.extensions` string array)\n- Extensions: `extensions/<name>/gemini-extension.json`\n- Settings capability: `settings.json`\n\n### Nearest-project lookup nuance\n\nFor `SYSTEM.md` and `XCSH.md`, native provider uses nearest-ancestor project `.xcsh` directory search (walk-up) but still requires the `.xcsh` dir to be non-empty.\n\n---\n\n## 7) How major subsystems consume config\n\n## Settings subsystem\n\n- `Settings.init()` loads global `config.yml` + discovered project `settings.json` capability items.\n- Only capability items with `level === \"project\"` are merged into project layer.\n\n## Skills subsystem\n\n- `extensibility/skills.ts` loads via `loadCapability(skillCapability.id, { cwd })`.\n- Applies source toggles and filters (`ignoredSkills`, `includeSkills`, custom dirs).\n- Legacy-named toggles still exist (`skills.enablePiUser`, `skills.enablePiProject`) but they gate the native provider (`provider === \"native\"`).\n\n## Hooks subsystem\n\n- `discoverAndLoadHooks()` resolves hook paths from hook capability + explicit configured paths.\n- Then loads modules via Bun import.\n\n## Tools subsystem\n\n- `discoverAndLoadCustomTools()` resolves tool paths from tool capability + plugin tool paths + explicit configured paths.\n- Declarative `.md/.json` tool files are metadata only; executable loading expects code modules.\n\n## Extensions subsystem\n\n- `discoverAndLoadExtensions()` resolves extension modules from extension-module capability plus explicit paths.\n- Current implementation intentionally keeps only capability items with `_source.provider === \"native\"` before loading.\n\n---\n\n## 8) Precedence rules to rely on\n\nUse this mental model:\n\n1. Source directory ordering from `config.ts` determines candidate path order.\n2. Capability provider priority determines cross-provider precedence.\n3. Capability key dedup determines collision behavior (first wins for keyed capabilities).\n4. Subsystem-specific merge logic can further change effective precedence (especially settings).\n\n### Settings-specific caveat\n\nSettings capability items are not deduplicated; `Settings.#loadProjectSettings()` deep-merges project items in returned order. Because merge applies later item values over earlier values, effective override behavior depends on provider emission order, not just capability key semantics.\n\n---\n\n## 9) Legacy/compatibility behaviors still present\n\n- `ConfigFile` JSON -> YAML migration for YAML-targeted files.\n- Settings migration from `settings.json` and `agent.db` to `config.yml`.\n- Settings key migrations (`queueMode`, `ask.timeout`, flat `theme`).\n- Extension manifest compatibility: loader accepts both `package.json.xcsh` and `package.json.pi` manifest sections.\n- Legacy setting names `skills.enablePiUser` / `skills.enablePiProject` are still active gates for native skill source.\n\nIf these compatibility paths are removed in code, update this document immediately; several runtime behaviors still depend on them today.\n",
	"en/configuration/environment-variables.md": "---\ntitle: Environment Variables\ndescription: Runtime environment variable reference for xcsh configuration and behavior control.\nsidebar:\n  order: 2\n  label: Environment variables\n---\n\n# Environment Variables (Current Runtime Reference)\n\nThis reference is derived from current code paths in:\n\n- `packages/coding-agent/src/**`\n- `packages/ai/src/**` (provider/auth resolution used by coding-agent)\n- `packages/utils/src/**` and `packages/tui/src/**` where those vars directly affect coding-agent runtime\n\nIt documents only active behavior.\n\n## Resolution model and precedence\n\nMost runtime lookups use `$env` from `@f5-sales-demo/pi-utils` (`packages/utils/src/env.ts`).\n\n`$env` loading order:\n\n1. Existing process environment (`Bun.env`)\n2. Project `.env` (`$PWD/.env`) for keys not already set\n3. Home `.env` (`~/.env`) for keys not already set\n\nAdditional rule in `.env` files: `XCSH_*` keys are mirrored to `PI_*` keys during parse.\n\n---\n\n## 1) Model/provider authentication\n\nThese are consumed via `getEnvApiKey()` (`packages/ai/src/stream.ts`) unless noted otherwise.\n\n### Core provider credentials\n\n| Variable                        | Used for | Required when                                                 | Notes / precedence                                                                                  |\n|---------------------------------|---|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|\n| `ANTHROPIC_OAUTH_TOKEN`         | Anthropic API auth | Using Anthropic with OAuth token auth                         | Takes precedence over `ANTHROPIC_API_KEY` for provider auth resolution                              |\n| `ANTHROPIC_API_KEY`             | Anthropic API auth | Using Anthropic without OAuth token                           | Fallback after `ANTHROPIC_OAUTH_TOKEN`                                                              |\n| `ANTHROPIC_FOUNDRY_API_KEY`     | Anthropic via Azure Foundry / enterprise gateway | `CLAUDE_CODE_USE_FOUNDRY` enabled                             | Takes precedence over `ANTHROPIC_OAUTH_TOKEN` and `ANTHROPIC_API_KEY` when Foundry mode is enabled  |\n| `OPENAI_API_KEY`                | OpenAI auth | Using OpenAI-family providers without explicit apiKey argument | Used by OpenAI Completions/Responses providers                                                      |\n| `GEMINI_API_KEY`                | Google Gemini auth | Using `google` provider models                                | Primary key for Gemini provider mapping                                                             |\n| `GOOGLE_API_KEY`                | Gemini image tool auth fallback | Using `gemini_image` tool without `GEMINI_API_KEY`            | Used by coding-agent image tool fallback path                                                       |\n| `GROQ_API_KEY`                  | Groq auth | Using Groq models                                             |                                                                                                     |\n| `CEREBRAS_API_KEY`              | Cerebras auth | Using Cerebras models                                         |                                                                                                     |\n| `TOGETHER_API_KEY`              | Together auth | Using `together` provider                                     |                                                                                                     |\n| `HUGGINGFACE_HUB_TOKEN`         | Hugging Face auth | Using `huggingface` provider                                  | Primary Hugging Face token env var                                                                  |\n| `HF_TOKEN`                      | Hugging Face auth | Using `huggingface` provider                                  | Fallback when `HUGGINGFACE_HUB_TOKEN` is unset                                                      |\n| `SYNTHETIC_API_KEY`             | Synthetic auth | Using Synthetic models                                        |                                                                                                     |\n| `NVIDIA_API_KEY`                | NVIDIA auth | Using `nvidia` provider                                       |                                                                                                     |\n| `NANO_GPT_API_KEY`              | NanoGPT auth | Using `nanogpt` provider                                      |                                                                                                     |\n| `VENICE_API_KEY`                | Venice auth | Using `venice` provider                                       |                                                                                                     |\n| `LITELLM_API_KEY`               | LiteLLM auth | Using `litellm` provider                                      | OpenAI-compatible LiteLLM proxy key. When set with `LITELLM_BASE_URL`, enables auto-config of `models.yml` |\n| `LM_STUDIO_API_KEY`             | LM Studio auth (optional) | Using `lm-studio` provider with authenticated hosts           | Local LM Studio usually runs without auth; any non-empty token works when a key is required         |\n| `OLLAMA_API_KEY`                | Ollama auth (optional) | Using `ollama` provider with authenticated hosts              | Local Ollama usually runs without auth; any non-empty token works when a key is required            |\n| `LLAMA_CPP_API_KEY`             | Ollama auth (optional) | Using `llama-server` with `--api-key` parameter              | Local llama.cpp usually runs without auth; any non-empty token works when a key is configured       |\n| `XIAOMI_API_KEY`                | Xiaomi MiMo auth | Using `xiaomi` provider                                       |                                                                                                     |\n| `MOONSHOT_API_KEY`              | Moonshot auth | Using `moonshot` provider                                     |                                                                                                     |\n| `XAI_API_KEY`                   | xAI auth | Using xAI models                                              |                                                                                                     |\n| `OPENROUTER_API_KEY`            | OpenRouter auth | Using OpenRouter models                                       | Also used by image tool when preferred/auto provider is OpenRouter                                  |\n| `MISTRAL_API_KEY`               | Mistral auth | Using Mistral models                                          |                                                                                                     |\n| `ZAI_API_KEY`                   | z.ai auth | Using z.ai models                                             | Also used by z.ai web search provider                                                               |\n| `MINIMAX_API_KEY`               | MiniMax auth | Using `minimax` provider                                      |                                                                                                     |\n| `MINIMAX_CODE_API_KEY`          | MiniMax Code auth | Using `minimax-code` provider                                 |                                                                                                     |\n| `MINIMAX_CODE_CN_API_KEY`       | MiniMax Code CN auth | Using `minimax-code-cn` provider                              |                                                                                                     |\n| `OPENCODE_API_KEY`              | OpenCode auth | Using OpenCode models                                         |                                                                                                     |\n| `QIANFAN_API_KEY`               | Qianfan auth | Using `qianfan` provider                                      |                                                                                                     |\n| `QWEN_OAUTH_TOKEN`              | Qwen Portal auth | Using `qwen-portal` with OAuth token                          | Takes precedence over `QWEN_PORTAL_API_KEY`                                                         |\n| `QWEN_PORTAL_API_KEY`           | Qwen Portal auth | Using `qwen-portal` with API key                              | Fallback after `QWEN_OAUTH_TOKEN`                                                                   |\n| `ZENMUX_API_KEY`                | ZenMux auth | Using `zenmux` provider                                       | Used for ZenMux OpenAI and Anthropic-compatible routes                                              |\n| `VLLM_API_KEY`                  | vLLM auth/discovery opt-in | Using `vllm` provider (local OpenAI-compatible servers)       | Any non-empty value works for no-auth local servers                                                 |\n| `CURSOR_ACCESS_TOKEN`           | Cursor provider auth | Using Cursor provider                                         |                                                                                                     |\n| `AI_GATEWAY_API_KEY`            | Vercel AI Gateway auth | Using `vercel-ai-gateway` provider                            |                                                                                                     |\n| `CLOUDFLARE_AI_GATEWAY_API_KEY` | Cloudflare AI Gateway auth | Using `cloudflare-ai-gateway` provider                        | Base URL must be configured as `https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic` |\n\n### GitHub/Copilot token chains\n\n| Variable | Used for | Chain |\n|---|---|---|\n| `COPILOT_GITHUB_TOKEN` | GitHub Copilot provider auth | `COPILOT_GITHUB_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN` |\n| `GH_TOKEN` | Copilot fallback; GitHub API auth in web scraper | In web scraper: `GITHUB_TOKEN` → `GH_TOKEN` |\n| `GITHUB_TOKEN` | Copilot fallback; GitHub API auth in web scraper | In web scraper: checked before `GH_TOKEN` |\n\n---\n\n## 2) Provider-specific runtime configuration\n\n### Anthropic Foundry Gateway (Azure / enterprise proxy)\n\nWhen `CLAUDE_CODE_USE_FOUNDRY` is enabled, Anthropic requests switch to Foundry mode:\n\n- Base URL resolves from `FOUNDRY_BASE_URL` (fallback remains model/default base URL if unset).\n- API key resolution for provider `anthropic` becomes:\n  `ANTHROPIC_FOUNDRY_API_KEY` → `ANTHROPIC_OAUTH_TOKEN` → `ANTHROPIC_API_KEY`.\n- `ANTHROPIC_CUSTOM_HEADERS` is parsed as comma/newline-separated `key: value` pairs and merged into request headers.\n- TLS client/server material can be injected from env values:\n  `NODE_EXTRA_CA_CERTS`, `CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`.\n  Each accepts either:\n  - a filesystem path to PEM content, or\n  - inline PEM (including escaped `\\n` sequences).\n\n| Variable | Value type | Behavior |\n|---|---|---|\n| `CLAUDE_CODE_USE_FOUNDRY` | Boolean-like string (`1`, `true`, `yes`, `on`) | Enables Foundry mode for Anthropic provider |\n| `FOUNDRY_BASE_URL` | URL string | Anthropic endpoint base URL in Foundry mode |\n| `ANTHROPIC_FOUNDRY_API_KEY` | Token string | Used for `Authorization: Bearer <token>` |\n| `ANTHROPIC_CUSTOM_HEADERS` | Header list string | Extra headers; format `header-a: value, header-b: value` or newline-separated |\n| `NODE_EXTRA_CA_CERTS` | PEM path or inline PEM | Extra CA chain for server certificate validation |\n| `CLAUDE_CODE_CLIENT_CERT` | PEM path or inline PEM | mTLS client certificate |\n| `CLAUDE_CODE_CLIENT_KEY` | PEM path or inline PEM | mTLS client private key (must be paired with cert) |\n\n### Amazon Bedrock\n\n| Variable | Default / behavior |\n|---|---|\n| `AWS_REGION` | Primary region source |\n| `AWS_DEFAULT_REGION` | Fallback if `AWS_REGION` unset |\n| `AWS_PROFILE` | Enables named profile auth path |\n| `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` | Enables IAM key auth path |\n| `AWS_BEARER_TOKEN_BEDROCK` | Enables bearer token auth path |\n| `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` / `AWS_CONTAINER_CREDENTIALS_FULL_URI` | Enables ECS task credential path |\n| `AWS_WEB_IDENTITY_TOKEN_FILE` + `AWS_ROLE_ARN` | Enables web identity auth path |\n| `AWS_BEDROCK_SKIP_AUTH` | If `1`, injects dummy credentials (proxy/non-auth scenarios) |\n| `AWS_BEDROCK_FORCE_HTTP1` | If `1`, forces Node HTTP/1 request handler |\n\nRegion fallback in provider code: `options.region` → `AWS_REGION` → `AWS_DEFAULT_REGION` → `us-east-1`.\n\n### Azure OpenAI Responses\n\n| Variable | Default / behavior |\n|---|---|\n| `AZURE_OPENAI_API_KEY` | Required unless API key passed as option |\n| `AZURE_OPENAI_API_VERSION` | Default `v1` |\n| `AZURE_OPENAI_BASE_URL` | Direct base URL override |\n| `AZURE_OPENAI_RESOURCE_NAME` | Used to construct base URL: `https://<resource>.openai.azure.com/openai/v1` |\n| `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` | Optional mapping string: `modelId=deploymentName,model2=deployment2` |\n\nBase URL resolution: option `azureBaseUrl` → env `AZURE_OPENAI_BASE_URL` → option/env resource name → `model.baseUrl`.\n\n### Google Vertex AI\n\n| Variable | Required? | Notes |\n|---|---|---|\n| `GOOGLE_CLOUD_PROJECT` | Yes (unless passed in options) | Fallback: `GCLOUD_PROJECT` |\n| `GCLOUD_PROJECT` | Fallback | Used as alternate project ID source |\n| `GOOGLE_CLOUD_LOCATION` | Yes (unless passed in options) | No default in provider |\n| `GOOGLE_APPLICATION_CREDENTIALS` | Conditional | If set, file must exist; otherwise ADC fallback path is checked (`~/.config/gcloud/application_default_credentials.json`) |\n\n### Kimi\n\n| Variable | Default / behavior |\n|---|---|\n| `KIMI_CODE_OAUTH_HOST` | Primary OAuth host override |\n| `KIMI_OAUTH_HOST` | Fallback OAuth host override |\n| `KIMI_CODE_BASE_URL` | Overrides Kimi usage endpoint base URL (`usage/kimi.ts`) |\n\nOAuth host chain: `KIMI_CODE_OAUTH_HOST` → `KIMI_OAUTH_HOST` → `https://auth.kimi.com`.\n\n### Antigravity/Gemini image compatibility\n\n| Variable | Default / behavior |\n|---|---|\n| `PI_AI_ANTIGRAVITY_VERSION` | Overrides Antigravity user-agent version tag in Gemini CLI provider |\n\n### OpenAI Codex responses (feature/debug controls)\n\n| Variable | Behavior |\n|---|---|\n| `PI_CODEX_DEBUG` | `1`/`true` enables Codex provider debug logging |\n| `PI_CODEX_WEBSOCKET` | `1`/`true` enables websocket transport preference |\n| `PI_CODEX_WEBSOCKET_V2` | `1`/`true` enables websocket v2 path |\n| `PI_CODEX_WEBSOCKET_IDLE_TIMEOUT_MS` | Positive integer override (default 300000) |\n| `PI_CODEX_WEBSOCKET_RETRY_BUDGET` | Non-negative integer override (default 5) |\n| `PI_CODEX_WEBSOCKET_RETRY_DELAY_MS` | Positive integer base backoff override (default 500) |\n\n### Cursor provider debug\n\n| Variable | Behavior |\n|---|---|\n| `DEBUG_CURSOR` | Enables provider debug logs; `2`/`verbose` for detailed payload snippets |\n| `DEBUG_CURSOR_LOG` | Optional file path for JSONL debug log output |\n\n### Prompt cache compatibility switch\n\n| Variable | Behavior |\n|---|---|\n| `PI_CACHE_RETENTION` | If `long`, enables long retention where supported (`anthropic`, `openai-responses`, Bedrock retention resolution) |\n\n---\n\n## 3) Web search subsystem\n\n### Search provider credentials\n\n| Variable | Used by |\n|---|---|\n| `EXA_API_KEY` | Exa search provider and Exa MCP tools |\n| `BRAVE_API_KEY` | Brave search provider |\n| `PERPLEXITY_API_KEY` | Perplexity search provider API-key mode |\n| `TAVILY_API_KEY` | Tavily search provider |\n| `ZAI_API_KEY` | z.ai search provider (also checks stored OAuth in `agent.db`) |\n| `OPENAI_API_KEY` / Codex OAuth in DB | Codex search provider availability/auth |\n\n### Anthropic web search auth chain\n\n`packages/coding-agent/src/web/search/auth.ts` resolves Anthropic web-search credentials in this order:\n\n1. `ANTHROPIC_SEARCH_API_KEY` (+ optional `ANTHROPIC_SEARCH_BASE_URL`)\n2. `models.json` provider entry with `api: \"anthropic-messages\"`\n3. Anthropic OAuth credentials from `agent.db` (must not expire within 5-minute buffer)\n4. Generic Anthropic env fallback: provider key (`ANTHROPIC_FOUNDRY_API_KEY`/`ANTHROPIC_OAUTH_TOKEN`/`ANTHROPIC_API_KEY`) + optional `ANTHROPIC_BASE_URL` (`FOUNDRY_BASE_URL` when Foundry mode is enabled)\n\nRelated vars:\n\n| Variable | Default / behavior |\n|---|---|\n| `ANTHROPIC_SEARCH_API_KEY` | Highest-priority explicit search key |\n| `ANTHROPIC_SEARCH_BASE_URL` | Defaults to `https://api.anthropic.com` when omitted |\n| `ANTHROPIC_SEARCH_MODEL` | Defaults to `claude-haiku-4-5` |\n| `ANTHROPIC_BASE_URL` | Generic fallback base URL for tier-4 auth path |\n\n### Perplexity OAuth flow behavior flag\n\n| Variable | Behavior |\n|---|---|\n| `PI_AUTH_NO_BORROW` | If set, disables macOS native-app token borrowing path in Perplexity login flow |\n\n---\n\n## 4) Python tooling and kernel runtime\n\n| Variable | Default / behavior |\n|---|---|\n| `PI_PY` | Python tool mode override: `0`/`bash`=`bash-only`, `1`/`py`=`ipy-only`, `mix`/`both`=`both`; invalid values ignored |\n| `PI_PYTHON_SKIP_CHECK` | If `1`, skips Python kernel availability checks/warm checks |\n| `PI_PYTHON_GATEWAY_URL` | If set, uses external kernel gateway instead of local shared gateway |\n| `PI_PYTHON_GATEWAY_TOKEN` | Optional auth token for external gateway (`Authorization: token <value>`) |\n| `PI_PYTHON_IPC_TRACE` | If `1`, enables low-level IPC trace path in kernel module |\n| `VIRTUAL_ENV` | Highest-priority venv path for Python runtime resolution |\n\nExtra conditional behavior:\n\n- If `BUN_ENV=test` or `NODE_ENV=test`, Python availability checks are treated as OK and warming is skipped.\n- Python env filtering denies common API keys and allows safe base vars + `LC_`, `XDG_`, `PI_` prefixes.\n\n---\n\n## 5) Agent/runtime behavior toggles\n\n| Variable                   | Default / behavior                                                                           |\n|----------------------------|----------------------------------------------------------------------------------------------|\n| `PI_SMOL_MODEL`            | Ephemeral model-role override for `smol` (CLI `--smol` takes precedence)                     |\n| `PI_SLOW_MODEL`            | Ephemeral model-role override for `slow` (CLI `--slow` takes precedence)                     |\n| `PI_PLAN_MODEL`            | Ephemeral model-role override for `plan` (CLI `--plan` takes precedence)                     |\n| `PI_NO_TITLE`              | If set (any non-empty value), disables auto session title generation on first user message   |\n| `NULL_PROMPT`              | If `true`, system prompt builder returns empty string                                        |\n| `PI_BLOCKED_AGENT`         | Blocks a specific subagent type in task tool                                                 |\n| `PI_SUBPROCESS_CMD`        | Overrides subagent spawn command (`xcsh` / `xcsh.cmd` resolution bypass)                       |\n| `PI_TASK_MAX_OUTPUT_BYTES` | Max captured output bytes per subagent (default `500000`)                                    |\n| `PI_TASK_MAX_OUTPUT_LINES` | Max captured output lines per subagent (default `5000`)                                      |\n| `PI_TIMING`                | If `1`, enables startup/tool timing instrumentation logs                                     |\n| `PI_DEBUG_STARTUP`         | Enables startup stage debug prints to stderr in multiple startup paths                       |\n| `PI_PACKAGE_DIR`           | Overrides package asset base dir resolution (docs/examples/changelog path lookup)            |\n| `PI_DISABLE_LSPMUX`        | If `1`, disables lspmux detection/integration and forces direct LSP server spawning          |\n| `LITELLM_BASE_URL`         | LiteLLM proxy base URL. When set with `LITELLM_API_KEY`, triggers auto-generation of `models.yml` on first run and self-healing on every startup |\n| `LM_STUDIO_BASE_URL`       | Default implicit LM Studio discovery base URL override (`http://127.0.0.1:1234/v1` if unset) |\n| `OLLAMA_BASE_URL`          | Default implicit Ollama discovery base URL override (`http://127.0.0.1:11434` if unset)      |\n| `LLAMA_CPP_BASE_URL`       | Default implicit Llama.cpp discovery base URL override (`http://127.0.0.1:8080` if unset)    |\n| `PI_EDIT_VARIANT`          | If `hashline`, forces hashline read/grep display mode when edit tool available               |\n| `PI_NO_PTY`                | If `1`, disables interactive PTY path for bash tool                                          |\n\n`PI_NO_PTY` is also set internally when CLI `--no-pty` is used.\n\n---\n\n## 6) Storage and config root paths\n\nThese are consumed via `@f5-sales-demo/pi-utils/dirs` and affect where coding-agent stores data.\n\n| Variable | Default / behavior |\n|---|---|\n| `PI_CONFIG_DIR` | Config root dirname under home (default `.xcsh`) |\n| `PI_CODING_AGENT_DIR` | Full override for agent directory (default `~/<PI_CONFIG_DIR or .xcsh>/agent`) |\n| `PWD` | Used when matching canonical current working directory in path helpers |\n\n---\n\n## 7) Shell/tool execution environment\n\n(From `packages/utils/src/procmgr.ts` and coding-agent bash tool integration.)\n\n| Variable | Behavior |\n|---|---|\n| `PI_BASH_NO_CI` | Suppresses automatic `CI=true` injection into spawned shell env |\n| `CLAUDE_BASH_NO_CI` | Legacy alias fallback for `PI_BASH_NO_CI` |\n| `PI_BASH_NO_LOGIN` | Intended to disable login shell mode |\n| `CLAUDE_BASH_NO_LOGIN` | Legacy alias fallback for `PI_BASH_NO_LOGIN` |\n| `PI_SHELL_PREFIX` | Optional command prefix wrapper |\n| `CLAUDE_CODE_SHELL_PREFIX` | Legacy alias fallback for `PI_SHELL_PREFIX` |\n| `VISUAL` | Preferred external editor command |\n| `EDITOR` | Fallback external editor command |\n\nCurrent implementation note: `PI_BASH_NO_LOGIN`/`CLAUDE_BASH_NO_LOGIN` are read, but current `getShellArgs()` returns `['-l','-c']` in both branches (effectively no-op today).\n\n---\n\n## 8) UI/theme/session detection (auto-detected env)\n\nThese are read as runtime signals; they are usually set by the terminal/OS rather than manually configured.\n\n| Variable | Used for |\n|---|---|\n| `COLORTERM`, `TERM`, `WT_SESSION` | Color capability detection (theme color mode) |\n| `COLORFGBG` | Terminal background light/dark auto-detection |\n| `TERM_PROGRAM`, `TERM_PROGRAM_VERSION`, `TERMINAL_EMULATOR` | Terminal identity in system prompt/context |\n| `KDE_FULL_SESSION`, `XDG_CURRENT_DESKTOP`, `DESKTOP_SESSION`, `XDG_SESSION_DESKTOP`, `GDMSESSION`, `WINDOWMANAGER` | Desktop/window-manager detection in system prompt/context |\n| `KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION` | Stable per-terminal session breadcrumb IDs |\n| `SHELL`, `ComSpec`, `TERM_PROGRAM`, `TERM` | System info diagnostics |\n| `APPDATA`, `XDG_CONFIG_HOME` | lspmux config path resolution |\n| `HOME` | Path shortening in MCP command UI |\n\n---\n\n## 9) Native loader/debug flags\n\n| Variable | Behavior |\n|---|---|\n| `PI_DEV` | Enables verbose native addon load diagnostics in `packages/natives` |\n\n## 10) TUI runtime flags (shared package, affects coding-agent UX)\n\n| Variable | Behavior |\n|---|---|\n| `PI_NOTIFICATIONS` | `off` / `0` / `false` suppress desktop notifications |\n| `PI_TUI_WRITE_LOG` | If set, logs TUI writes to file |\n| `PI_HARDWARE_CURSOR` | If `1`, enables hardware cursor mode |\n| `PI_CLEAR_ON_SHRINK` | If `1`, clears empty rows when content shrinks |\n| `PI_DEBUG_REDRAW` | If `1`, enables redraw debug logging |\n| `PI_TUI_DEBUG` | If `1`, enables deep TUI debug dump path |\n\n---\n\n## 11) Commit generation controls\n\n| Variable | Behavior |\n|---|---|\n| `PI_COMMIT_TEST_FALLBACK` | If `true` (case-insensitive), force commit fallback generation path |\n| `PI_COMMIT_NO_FALLBACK` | If `true`, disables fallback when agent returns no proposal |\n| `PI_COMMIT_MAP_REDUCE` | If `false`, disables map-reduce commit analysis path |\n| `DEBUG` | If set, commit agent error stack traces are printed |\n\n---\n\n## Security-sensitive variables\n\nTreat these as secrets; do not log or commit them:\n\n- Provider/API keys and OAuth/bearer credentials (all `*_API_KEY`, `*_TOKEN`, OAuth access/refresh tokens)\n- Cloud credentials (`AWS_*`, `GOOGLE_APPLICATION_CREDENTIALS` path may expose service-account material)\n- Search/provider auth vars (`EXA_API_KEY`, `BRAVE_API_KEY`, `PERPLEXITY_API_KEY`, Anthropic search keys)\n- Foundry mTLS material (`CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`, `NODE_EXTRA_CA_CERTS` when it points to private CA bundles)\n\nPython runtime also explicitly strips many common key vars before spawning kernel subprocesses (`packages/coding-agent/src/ipy/runtime.ts`).\n",
	"en/configuration/fs-scan-cache-architecture.md": "---\ntitle: Filesystem Scan Cache Architecture\ndescription: Filesystem scan cache contract for fast file discovery with stale-while-revalidate semantics.\nsidebar:\n  order: 8\n  label: Filesystem scan cache\n---\n\n# Filesystem Scan Cache Architecture Contract\n\nThis document defines the current contract for the shared filesystem scan cache implemented in Rust (`crates/pi-natives/src/fs_cache.rs`) and consumed by native discovery/search APIs exposed to `packages/coding-agent`.\n\n## What this cache is\n\nThe cache stores full directory-scan entry lists (`GlobMatch[]`) keyed by scan scope and traversal policy, then lets higher-level operations (glob filtering, fuzzy scoring, grep file selection) run against those cached entries.\n\nPrimary goals:\n\n- avoid repeated filesystem walks for repeated discovery/search calls\n- keep consistency across `glob`, `fuzzyFind`, and `grep` when they share the same scan policy\n- allow explicit staleness recovery for empty results and explicit invalidation after file mutations\n\n## Ownership and public surface\n\n- Cache implementation and policy: `crates/pi-natives/src/fs_cache.rs`\n- Native consumers:\n  - `crates/pi-natives/src/glob.rs`\n  - `crates/pi-natives/src/fd.rs` (`fuzzyFind`)\n  - `crates/pi-natives/src/grep.rs`\n- JS binding/export:\n  - `packages/natives/src/glob/index.ts` (`invalidateFsScanCache`)\n  - `packages/natives/src/glob/types.ts`\n  - `packages/natives/src/grep/types.ts`\n- Coding-agent mutation invalidation helpers:\n  - `packages/coding-agent/src/tools/fs-cache-invalidation.ts`\n\n## Cache key partitioning (hard contract)\n\nEach entry is keyed by:\n\n- canonicalized `root` directory path\n- `include_hidden` boolean\n- `use_gitignore` boolean\n\nImplications:\n\n- Hidden and non-hidden scans do **not** share entries.\n- Gitignore-respecting and ignore-disabled scans do **not** share entries.\n- Consumers must pass stable semantics for hidden/gitignore behavior; changing either flag creates a different cache partition.\n\n`node_modules` inclusion is **not** in the cache key. The cache stores entries with `node_modules` included; per-consumer filtering is applied after retrieval.\n\n## Scan collection behavior\n\nCache population uses a deterministic walker (`ignore::WalkBuilder`) configured by `include_hidden` and `use_gitignore`:\n\n- `follow_links(false)`\n- sorted by file path\n- `.git` is always skipped\n- `node_modules` is always collected at cache-scan time (and optionally filtered later)\n- entry file type + `mtime` are captured via `symlink_metadata`\n\nSearch roots are resolved by `resolve_search_path`:\n\n- relative paths are resolved against current cwd\n- target must be an existing directory\n- root is canonicalized when possible\n\n## Freshness and eviction policy\n\nGlobal policy (environment-overridable):\n\n- `FS_SCAN_CACHE_TTL_MS` (default `1000`)\n- `FS_SCAN_EMPTY_RECHECK_MS` (default `200`)\n- `FS_SCAN_CACHE_MAX_ENTRIES` (default `16`)\n\nBehavior:\n\n- `get_or_scan(...)`\n  - if TTL is `0`: bypass cache entirely, always fresh scan (`cache_age_ms = 0`)\n  - on cache hit within TTL: return cached entries + non-zero `cache_age_ms`\n  - on expired hit: evict key, rescan, store fresh entry\n- max entry enforcement is oldest-first eviction by `created_at`\n\n## Empty-result fast recheck (separate from normal hits)\n\nNormal cache hit:\n\n- a cache hit inside TTL returns cached entries and does nothing else.\n\nEmpty-result fast recheck:\n\n- this is a **caller-side** policy using `ScanResult.cache_age_ms`\n- if filtered/query result is empty and cached scan age is at least `empty_recheck_ms()`, caller performs one `force_rescan(...)` and retries\n- intended to reduce stale-negative results when files were recently added but cache is still within TTL\n\nCurrent consumers:\n\n- `glob`: rechecks when filtered matches are empty and scan age exceeds threshold\n- `fuzzyFind` (`fd.rs`): rechecks only when query is non-empty and scored matches are empty\n- `grep`: rechecks when selected candidate file list is empty\n\n## Consumer defaults and cache usage\n\nCache is opt-in on all exposed APIs (`cache?: boolean`, default `false`).\n\nCurrent defaults in native APIs:\n\n- `glob`: `hidden=false`, `gitignore=true`, `cache=false`\n- `fuzzyFind`: `hidden=false`, `gitignore=true`, `cache=false`\n- `grep`: `hidden=true`, `cache=false`, and cache scan always uses `use_gitignore=true`\n\nCoding-agent callers today:\n\n- High-volume mention candidate discovery enables cache:\n  - `packages/coding-agent/src/utils/file-mentions.ts`\n  - profile: `hidden=true`, `gitignore=true`, `includeNodeModules=true`, `cache=true`\n- Tool-level `grep` integration currently disables scan cache (`cache: false`):\n  - `packages/coding-agent/src/tools/grep.ts`\n\n## Invalidation contract\n\nNative invalidation entrypoint:\n\n- `invalidateFsScanCache(path?: string)`\n  - with `path`: remove cache entries whose root is a prefix of target path\n  - without path: clear all scan cache entries\n\nPath handling details:\n\n- relative invalidation paths are resolved against cwd\n- invalidation attempts canonicalization\n- if target does not exist (e.g., delete), fallback canonicalizes parent and reattaches filename when possible\n- this preserves invalidation behavior for create/delete/rename where one side may not exist\n\n## Coding-agent mutation flow responsibilities\n\nCoding-agent code must invalidate after successful filesystem mutations.\n\nCentral helpers:\n\n- `invalidateFsScanAfterWrite(path)`\n- `invalidateFsScanAfterDelete(path)`\n- `invalidateFsScanAfterRename(oldPath, newPath)` (invalidates both sides when paths differ)\n\nCurrent mutation tool callsites:\n\n- `packages/coding-agent/src/tools/write.ts`\n- `packages/coding-agent/src/patch/index.ts` (hashline/patch/replace flows)\n\nRule: if a flow mutates filesystem content or location and bypasses these helpers, cache staleness bugs are expected.\n\n## Adding a new cache consumer safely\n\nWhen introducing cache use in a new scanner/search path:\n\n1. **Use stable scan policy inputs**\n   - decide hidden/gitignore semantics first\n   - pass them consistently to `get_or_scan`/`force_rescan` so cache partitions are intentional\n\n2. **Treat cache data as pre-filtered only by traversal policy**\n   - apply tool-specific filtering (glob patterns, type filters, node_modules rules) after retrieval\n   - never assume cached entries already reflect your higher-level filters\n\n3. **Implement empty-result fast recheck only for stale-negative risk**\n   - use `scan.cache_age_ms >= empty_recheck_ms()`\n   - retry once with `force_rescan(..., store=true, ...)`\n   - keep this path separate from normal cache-hit logic\n\n4. **Respect no-cache mode explicitly**\n   - when caller disables cache, call `force_rescan(..., store=false, ...)`\n   - do not populate shared cache in a no-cache request path\n\n5. **Wire mutation invalidation for any new write path**\n   - after successful write/edit/delete/rename, call the coding-agent invalidation helper\n   - for rename/move, invalidate both old and new paths\n\n6. **Do not add per-call TTL knobs**\n   - current contract is global policy only (env-configured), no per-request TTL override\n\n## Known boundaries\n\n- Cache scope is process-local in-memory (`DashMap`), not persisted across process restarts.\n- Cache stores scan entries, not final tool results.\n- `glob`/`fuzzyFind`/`grep` share scan entries only when key dimensions (`root`, `hidden`, `gitignore`) match.\n- `.git` is always excluded at scan collection time regardless of caller options.\n",
	"en/configuration/hooks.md": "---\ntitle: Hooks\ndescription: Hook system for pre/post event automation in the coding agent lifecycle.\nsidebar:\n  order: 4\n  label: Hooks\n---\n\n# Hooks\n\nThis document describes the **current hook subsystem code** in `src/extensibility/hooks/*`.\n\n## Current status in runtime\n\nThe hook package (`src/extensibility/hooks/`) is still exported and usable as an API surface, but the default CLI runtime now initializes the **extension runner** path. In current startup flow:\n\n- `--hook` is treated as an alias for `--extension` (CLI paths are merged into `additionalExtensionPaths`)\n- tools are wrapped by `ExtensionToolWrapper`, not `HookToolWrapper`\n- context transforms and lifecycle emissions go through `ExtensionRunner`\n\nSo this file documents the hook subsystem implementation itself (types/loader/runner/wrapper), including legacy behavior and constraints.\n\n## Key files\n\n- `src/extensibility/hooks/types.ts` — hook context, event types, and result contracts\n- `src/extensibility/hooks/loader.ts` — module loading and hook discovery bridge\n- `src/extensibility/hooks/runner.ts` — event dispatch, command lookup, error signaling\n- `src/extensibility/hooks/tool-wrapper.ts` — pre/post tool interception wrapper\n- `src/extensibility/hooks/index.ts` — exports/re-exports\n\n## What a hook module is\n\nA hook module must default-export a factory:\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function hook(pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName === \"bash\" && String(event.input.command ?? \"\").includes(\"rm -rf\")) {\n   return { block: true, reason: \"blocked by policy\" };\n  }\n });\n}\n```\n\nThe factory can:\n\n- register event handlers with `pi.on(...)`\n- send persistent custom messages with `pi.sendMessage(...)`\n- persist non-LLM state with `pi.appendEntry(...)`\n- register slash commands via `pi.registerCommand(...)`\n- register custom message renderers via `pi.registerMessageRenderer(...)`\n- run shell commands via `pi.exec(...)`\n\n## Discovery and loading\n\n`discoverAndLoadHooks(configuredPaths, cwd)` does:\n\n1. Load discovered hooks from capability registry (`loadCapability(\"hooks\")`)\n2. Append explicitly configured paths (deduped by absolute path)\n3. Call `loadHooks(allPaths, cwd)`\n\n`loadHooks` then imports each path and expects a `default` function.\n\n### Path resolution\n\n`loader.ts` resolves hook paths as:\n\n- absolute path: used as-is\n- `~` path: expanded\n- relative path: resolved against `cwd`\n\n### Important legacy mismatch\n\nDiscovery providers for `hookCapability` still model pre/post shell-style hook files (for example `.claude/hooks/pre/*`, `.xcsh/.../hooks/pre/*`).\n\nThe hook loader here uses dynamic module import and requires a default JS/TS hook factory. If a discovered hook path is not importable as a module, load fails and is reported in `LoadHooksResult.errors`.\n\n## Event surfaces\n\nHook events are strongly typed in `types.ts`.\n\n### Session events\n\n- `session_start`\n- `session_before_switch` → can return `{ cancel?: boolean }`\n- `session_switch`\n- `session_before_branch` → can return `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_branch`\n- `session_before_compact` → can return `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session.compacting` → can return `{ context?: string[]; prompt?: string; preserveData?: Record<string, unknown> }`\n- `session_compact`\n- `session_before_tree` → can return `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n- `session_tree`\n- `session_shutdown`\n\n### Agent/context events\n\n- `context` → can return `{ messages?: Message[] }`\n- `before_agent_start` → can return `{ message?: { customType; content; display; details } }`\n- `agent_start`\n- `agent_end`\n- `turn_start`\n- `turn_end`\n- `auto_compaction_start`\n- `auto_compaction_end`\n- `auto_retry_start`\n- `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### Tool events (pre/post model)\n\n- `tool_call` (pre-execution) → can return `{ block?: boolean; reason?: string }`\n- `tool_result` (post-execution) → can return `{ content?; details?; isError? }`\n\nThis is the hook subsystem’s core pre/post interception model.\n\n```text\nHook tool interception flow\n\ntool_call handlers\n   │\n   ├─ any { block: true }? ── yes ──> throw (tool blocked)\n   │\n   └─ no\n      │\n      ▼\n   execute underlying tool\n      │\n      ├─ success ──> tool_result handlers can override { content, details }\n      │\n      └─ error   ──> emit tool_result(isError=true) then rethrow original error\n```\n\n## Execution model and mutation semantics\n\n### 1) Pre-execution: `tool_call`\n\n`HookToolWrapper.execute()` emits `tool_call` before tool execution.\n\n- if any handler returns `{ block: true }`, execution stops\n- if handler throws, wrapper fails closed and blocks execution\n- returned `reason` becomes the thrown error text\n\n### 2) Tool execution\n\nUnderlying tool executes normally if not blocked.\n\n### 3) Post-execution: `tool_result`\n\nAfter success, wrapper emits `tool_result` with:\n\n- `toolName`, `toolCallId`, `input`\n- `content`\n- `details`\n- `isError: false`\n\nIf handler returns overrides:\n\n- `content` can replace result content\n- `details` can replace result details\n\nOn tool failure, wrapper emits `tool_result` with `isError: true` and error text content, then rethrows original error.\n\n### What hooks can mutate\n\n- LLM context for a single call via `context` (`messages` replacement chain)\n- tool output content/details on successful tool calls (`tool_result` path)\n- pre-agent injected message via `before_agent_start`\n- cancellation/custom compaction/tree behavior via `session_before_*` and `session.compacting`\n\n### What hooks cannot mutate in this implementation\n\n- raw tool input parameters in-place (only block/allow on `tool_call`)\n- execution continuation after thrown tool errors (error path rethrows)\n- final success/error status in wrapper behavior (returned `isError` is typed but not applied by `HookToolWrapper`)\n\n## Ordering and conflict behavior\n\n### Discovery-level ordering\n\nCapability providers are priority-sorted (higher first). Dedupe is by capability key, first wins.\n\nFor `hooks`, capability key is `${type}:${tool}:${name}`. Shadowed duplicates from lower-priority providers are marked and excluded from effective discovered list.\n\n### Load order\n\n`discoverAndLoadHooks` builds a flat `allPaths` list, deduped by resolved absolute path, then `loadHooks` iterates in that order.\nFile order within each discovered directory depends on `readdir` output; the hook loader does not perform an additional sort.\n\n### Runtime handler order\n\nInside `HookRunner`, order is deterministic by registration sequence:\n\n1. hooks array order\n2. handler registration order per hook/event\n\nConflict behavior by event type:\n\n- `tool_call`: last returned result wins unless a handler blocks; first block short-circuits\n- `tool_result`: last returned override wins (no short-circuit)\n- `context`: chained; each handler receives prior handler’s message output\n- `before_agent_start`: first returned message is kept; later messages ignored\n- `session_before_*`: latest returned result is tracked; `cancel: true` short-circuits immediately\n- `session.compacting`: latest returned result wins\n\nCommand/renderer conflicts:\n\n- `getCommand(name)` returns first match across hooks (first loaded wins)\n- `getMessageRenderer(customType)` returns first match\n- `getRegisteredCommands()` returns all commands (no dedupe)\n\n## UI interactions (`HookContext.ui`)\n\n`HookUIContext` includes:\n\n- `select`, `confirm`, `input`, `editor`\n- `notify`\n- `setStatus`\n- `custom`\n- `setEditorText`, `getEditorText`\n- `theme` getter\n\n`ctx.hasUI` indicates whether interactive UI is available.\n\nWhen running with no UI, the default no-op context behavior is:\n\n- `select/input/editor` return `undefined`\n- `confirm` returns `false`\n- `notify`, `setStatus`, `setEditorText` are no-ops\n- `getEditorText` returns `\"\"`\n\n### Status line behavior\n\nHook status text set via `ctx.ui.setStatus(key, text)` is:\n\n- stored per key\n- sorted by key name\n- sanitized (`\\r`, `\\n`, `\\t` → spaces; repeated spaces collapsed)\n- joined and width-truncated for display\n\n## Error propagation and fallback\n\n### Load-time\n\n- invalid module or missing default export → captured in `LoadHooksResult.errors`\n- loading continues for other hooks\n\n### Event-time\n\n`HookRunner.emit(...)` catches handler errors for most events and emits `HookError` to listeners (`hookPath`, `event`, `error`), then continues.\n\n`emitToolCall(...)` is stricter: handler errors are not swallowed there; they propagate to caller. In `HookToolWrapper`, this blocks the tool call (fail-safe).\n\n## Realistic API examples\n\n### Block unsafe bash commands\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName !== \"bash\") return;\n  const cmd = String(event.input.command ?? \"\");\n  if (!cmd.includes(\"rm -rf\")) return;\n\n  if (!ctx.hasUI) return { block: true, reason: \"rm -rf blocked (no UI)\" };\n  const ok = await ctx.ui.confirm(\"Dangerous command\", `Allow: ${cmd}`);\n  if (!ok) return { block: true, reason: \"user denied command\" };\n });\n}\n```\n\n### Redact tool output on post-execution\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_result\", async event => {\n  if (event.toolName !== \"read\" || event.isError) return;\n\n  const redacted = event.content.map(chunk => {\n   if (chunk.type !== \"text\") return chunk;\n   return { ...chunk, text: chunk.text.replaceAll(/API_KEY=\\S+/g, \"API_KEY=[REDACTED]\") };\n  });\n\n  return { content: redacted };\n });\n}\n```\n\n### Modify model context per LLM call\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"context\", async event => {\n  const filtered = event.messages.filter(msg => !(msg.role === \"custom\" && msg.customType === \"debug-only\"));\n  return { messages: filtered };\n });\n}\n```\n\n### Register slash command with command-safe context methods\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.registerCommand(\"handoff\", {\n  description: \"Create a new session with setup message\",\n  handler: async (_args, ctx) => {\n   await ctx.waitForIdle();\n   await ctx.newSession({\n    parentSession: ctx.sessionManager.getSessionFile(),\n    setup: async sm => {\n     sm.appendMessage({\n      role: \"user\",\n      content: [{ type: \"text\", text: \"Continue from prior session summary.\" }],\n      timestamp: Date.now(),\n     });\n    },\n   });\n  },\n });\n}\n```\n\n## Export surface\n\n`src/extensibility/hooks/index.ts` exports:\n\n- loading APIs (`discoverAndLoadHooks`, `loadHooks`)\n- runner and wrapper (`HookRunner`, `HookToolWrapper`)\n- all hook types\n- `execCommand` re-export\n\nAnd package root (`src/index.ts`) re-exports hook **types** as a legacy compatibility surface.\n",
	"en/configuration/porting-from-pi-mono.md": "---\ntitle: \"Porting From pi-mono: A Practical Merge Guide\"\ndescription: Practical guide for migrating code from the pi-mono monorepo into the xcsh codebase.\nsidebar:\n  order: 9\n  label: Porting from pi-mono\n---\n\n# Porting From pi-mono: A Practical Merge Guide\n\nThis guide is a repeatable checklist for porting changes from pi-mono into this repo.\nUse it for any merge: single file, feature branch, or full release sync.\n\n## Last Sync Point\n\n**Commit:** `b21b42d032919de2f2e6920a76fa9a37c3920c0a`\n**Date:** 2026-03-22\n\nUpdate this section after each sync; do not reuse the previous range.\n\nWhen starting a new sync, generate patches from this commit forward:\n\n```bash\ngit format-patch b21b42d032919de2f2e6920a76fa9a37c3920c0a..HEAD --stdout > changes.patch\n```\n\n## 0) Define the scope\n\n- Identify the upstream reference (commit, tag, or PR).\n- List the packages or folders you plan to touch.\n- Decide which features are in-scope and which are intentionally skipped.\n\n## 1) Bring code over safely\n\n- Prefer a clean, focused diff rather than a wholesale copy.\n- Avoid copying built artifacts or generated files.\n- If upstream added new files, add them explicitly and review contents.\n\n## 2) Match import extension conventions\n\nMost runtime TypeScript sources omit `.js` in internal imports, but some test/bench entrypoints keep `.js` for ESM\nruntime compatibility. Follow the local package’s existing style; do not blanket-strip extensions.\n\n- In `packages/coding-agent` runtime sources, keep internal imports extensionless unless importing non-TS assets.\n- In `packages/tui/test` and `packages/natives/bench`, keep `.js` where surrounding files already use it.\n- Keep real file extensions when required by tooling (e.g., `.json`, `.css`, `.md` text embeds).\n- Example: `import { x } from \"./foo.js\";` → `import { x } from \"./foo\";` (only when the package convention is extensionless).\n\n## 3) Replace import scopes\n\nUpstream uses different package scopes. Replace them consistently.\n\n- Replace old scopes with the local scope used here.\n- Examples (adjust to match the actual packages you are porting):\n  - `@mariozechner/pi-coding-agent` → `@f5-sales-demo/xcsh`\n  - `@mariozechner/pi-agent-core` → `@f5-sales-demo/pi-agent-core`\n  - `@mariozechner/pi-tui` → `@f5-sales-demo/pi-tui`\n  - `@mariozechner/pi-ai` → `@f5-sales-demo/pi-ai`\n\n## 4) Use Bun APIs where they improve on Node\n\nWe run on Bun. Replace Node APIs only when Bun provides a better alternative.\n\n**DO replace:**\n\n- Process spawning: `child_process.spawn` → Bun Shell `$` for simple commands, `Bun.spawn`/`Bun.spawnSync` for streaming or long-running work\n- File I/O: `fs.readFileSync` → `Bun.file().text()` / `Bun.write()`\n- HTTP clients: `node-fetch`, `axios` → native `fetch`\n- Crypto hashing: `node:crypto` → Web Crypto or `Bun.hash`\n- SQLite: `better-sqlite3` → `bun:sqlite`\n- Env loading: `dotenv` → Bun loads `.env` automatically\n\n**DO NOT replace (these work fine in Bun):**\n\n- `os.homedir()` — do NOT replace with `Bun.env.HOME`, `Bun.env.HOME`, or literal `\"~\"`\n- `os.tmpdir()` — do NOT replace with `Bun.env.TMPDIR || \"/tmp\"` or hardcoded paths\n- `fs.mkdtempSync()` — do NOT replace with manual path construction\n- `path.join()`, `path.resolve()`, etc. — these are fine\n\n**Import style:** Use the `node:` prefix with namespace imports only (no named imports from `node:fs` or `node:path`).\n\n**Additional Bun conventions:**\n\n- Prefer Bun Shell `$` for short, non-streaming commands; use `Bun.spawn` only when you need streaming I/O or process control.\n- Use `Bun.file()`/`Bun.write()` for files and `node:fs/promises` for directories.\n- Avoid `Bun.file().exists()` checks; use `isEnoent` handling in try/catch.\n- Prefer `Bun.sleep(ms)` over `setTimeout` wrappers.\n\n**Wrong:**\n\n```typescript\n// BROKEN: env vars may be undefined, \"~\" is not expanded\nconst home = Bun.env.HOME || \"~\";\nconst tmp = Bun.env.TMPDIR || \"/tmp\";\n```\n\n**Correct:**\n\n```typescript\nimport * as os from \"node:os\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst configDir = path.join(os.homedir(), \".config\", \"myapp\");\nconst tempDir = fs.mkdtempSync(path.join(os.tmpdir(), \"myapp-\"));\n```\n\n## 5) Prefer Bun embeds (no copying)\n\nDo not copy runtime assets or vendor files at build time.\n\n- If upstream copies assets into a dist folder, replace with Bun-friendly embeds.\n- Prompts are static `.md` files; use Bun text imports (`with { type: \"text\" }`) and Handlebars instead of inline prompt strings.\n- Use `import.meta.dir` + `Bun.file` to load adjacent non-text resources.\n- Keep assets in-repo and let the bundler include them.\n- Eliminate copy scripts unless the user explicitly requests them.\n- If upstream reads a bundled fallback file at runtime, replace filesystem reads with a Bun text embed import.\n  - Example (Codex instructions fallback):\n    - `const FALLBACK_PROMPT_PATH = join(import.meta.dir, \"codex-instructions.md\");` -> removed\n    - `import FALLBACK_INSTRUCTIONS from \"./codex-instructions.md\" with { type: \"text\" };`\n    - Use `return FALLBACK_INSTRUCTIONS;` instead of `readFileSync(FALLBACK_PROMPT_PATH, \"utf8\")`\n\n## 6) Port `package.json` carefully\n\nTreat `package.json` as a contract. Merge intentionally.\n\n- Keep existing `name`, `version`, `type`, `exports`, and `bin` unless the port requires changes.\n- Replace npm/node scripts with Bun equivalents (e.g., `bun check`, `bun test`).\n- Ensure dependencies use the correct scope.\n- Do not downgrade dependencies to fix type errors; upgrade instead.\n- Validate workspace package links and `peerDependencies`.\n\n## 7) Align code style and tooling\n\n- Keep existing formatting conventions.\n- Do not introduce `any` unless required.\n- Avoid dynamic imports and inline type imports; use top-level imports only.\n- Never build prompts in code; prompts are static `.md` files rendered with Handlebars.\n- In coding-agent, never use `console.log`/`console.warn`/`console.error`; use `logger` from `@f5-sales-demo/pi-utils`.\n- Use `Promise.withResolvers()` instead of `new Promise((resolve, reject) => ...)`.\n- **No `private`/`protected`/`public` keywords on class fields or methods.** Use ES `#` private fields for encapsulation; leave accessible members bare (no keyword). The only exception is constructor parameter properties (`constructor(private readonly x: T)`), where the keyword is required by TypeScript. When porting upstream code that uses `private foo` or `protected bar`, convert to `#foo` (private) or bare `bar` (accessible).\n- Prefer existing helpers and utilities over new ad-hoc code.\n- Preserve Bun-first infrastructure changes already made in this repo:\n  - Runtime is Bun (no Node entry points).\n  - Package manager is Bun (no npm lockfiles).\n  - Heavy Node APIs (`child_process`, `readline`) are replaced with Bun equivalents.\n  - Lightweight Node APIs (`os.homedir`, `os.tmpdir`, `fs.mkdtempSync`, `path.*`) are kept.\n  - CLI shebangs use `bun` (not `node`, not `tsx`).\n  - Packages use source files directly (no TypeScript build step).\n  - CI workflows run Bun for install/check/test.\n\n## 8) Remove old compatibility layers\n\nUnless requested, remove upstream compatibility shims.\n\n- Delete old APIs that were replaced.\n- Update all call sites to the new API directly.\n- Do not keep `*_v2` or parallel versions.\n\n## 9) Update docs and references\n\n- Replace pi-mono repo links where appropriate.\n- Update examples to use Bun and correct package scopes.\n- Ensure README instructions still match the current repo behavior.\n\n## 10) Validate the port\n\nRun the standard checks after changes:\n\n- `bun check`\n\nIf the repo already has failing checks unrelated to your changes, call that out.\nTests use Bun's runner (not Vitest), but only run `bun test` when explicitly requested.\n\n## 11) Protect improved features (regression trap list)\n\nIf you already improved behavior locally, treat those as **non‑negotiable**. Before porting, write down\nthe improvements and add explicit checks so they don’t get lost in the merge.\n\n- **Freeze the expected behavior**: add a short “before/after” note for each improvement (inputs, outputs,\n  defaults, edge cases). This prevents silent rollback.\n- **Map old → new APIs**: if upstream renamed concepts (hooks → extensions, custom tools → tools, etc.),\n  ensure every old entry point still wires through. One missed flag or export equals lost functionality.\n- **Verify exports**: check `package.json` `exports`, public types, and barrel files. Upstream ports often\n  forget to re-export local additions.\n- **Cover non‑happy paths**: if you fixed error handling, timeouts, or fallback logic, add a test or at\n  least a manual checklist that exercises those paths.\n- **Check defaults and config merge order**: improvements often live in defaults. Confirm new defaults\n  didn’t revert (e.g., new config precedence, disabled features, tool lists).\n- **Audit env/shell behavior**: if you fixed execution or sandboxing, verify the new path still uses your\n  sanitized env and does not reintroduce alias/function overrides.\n- **Re-run targeted samples**: keep a minimal set of \"known good\" examples and run them after the port\n  (CLI flags, extension registration, tool execution).\n\n## 12) Detect and handle reworked code\n\nBefore porting a file, check if upstream significantly refactored it:\n\n```bash\n# Compare the file you're about to port against what you have locally\ngit diff HEAD upstream/main -- path/to/file.ts\n```\n\nIf the diff shows the file was **reworked** (not just patched):\n\n- New abstractions, renamed concepts, merged modules, changed data flow\n\nThen you must **read the new implementation thoroughly** before porting. Blind merging of reworked code loses functionality because:\n\nNote: interactive mode was recently split into controllers/utils/types. When backporting related changes, port updates into the individual files we created and ensure `interactive-mode.ts` wiring stays in sync.\n\n1. **Defaults change silently** - A new variable `defaultFoo = [a, b]` may replace an old `getAllFoo()` that returned `[a, b, c, d, e]`.\n\n2. **API options get dropped** - When systems merge (e.g., `hooks` + `customTools` → `extensions`), old options may not wire through to the new implementation.\n\n3. **Code paths go stale** - A renamed concept (e.g., `hookMessage` → `custom`) needs updates in every switch statement, type guard, and handler—not just the definition.\n\n4. **Context/capabilities shrink** - Old APIs may have exposed `{ logger, typebox, pi }` that new APIs forgot to include.\n\n### Semantic porting process\n\nWhen upstream reworked a module:\n\n1. **Read the old implementation** - Understand what it did, what options it accepted, what it exposed.\n\n2. **Read the new implementation** - Understand the new abstractions and how they map to old behavior.\n\n3. **Verify feature parity** - For each capability in the old code, confirm the new code preserves it or explicitly removes it.\n\n4. **Grep for stragglers** - Search for old names/concepts that may have been missed in switch statements, handlers, UI components.\n\n5. **Test the boundaries** - CLI flags, SDK options, event handlers, default values—these are where regressions hide.\n\n### Quick checks\n\n```bash\n# Find all uses of an old concept that may need updating\nrg \"oldConceptName\" --type ts\n\n# Compare default values between versions\ngit show upstream/main:path/to/file.ts | rg \"default|DEFAULT\"\n\n# Check if all enum/union values have handlers\nrg \"case \\\"\" path/to/file.ts\n```\n\n## 13) Quick audit checklist\n\nUse this as a final pass before you finish:\n\n- [ ] Import extensions follow the local package convention (no blanket `.js` stripping)\n- [ ] No Node-only APIs in new/ported code\n- [ ] All package scopes updated\n- [ ] `package.json` scripts use Bun\n- [ ] Prompts are `.md` text imports (no inline prompt strings)\n- [ ] No `console.*` in coding-agent (use `logger`)\n- [ ] Assets load via Bun embed patterns (no copy scripts)\n- [ ] Tests or checks run (or explicitly noted as blocked)\n- [ ] No functionality regressions (see sections 11-12)\n\n## 14) Commit message format\n\nWhen committing a backport, follow the repo format `<type>(scope): <past-tense description>` and keep the commit\nrange in the title.\n\n```\nfix(coding-agent): backported pi-mono changes (<from>..<to>)\n\npackages/<package>:\n- <type>: <description>\n- <type>: <description> (#<issue> by @<contributor>)\n\npackages/<other-package>:\n- <type>: <description>\n```\n\n**Example:**\n\n```\nfix(coding-agent): backported pi-mono changes (9f3eef65f..52532c7c0)\n\npackages/ai:\n- fix: handle \"sensitive\" stop reason from Anthropic API\n- fix: normalize tool call IDs with special characters for Responses API\n- fix: add overflow detection for Bedrock, MiniMax, Kimi providers\n- fix: 429 status is rate limiting, not context overflow\n\npackages/tui:\n- fix: refactored autocomplete state tracking\n- fix: file autocomplete should not trigger on empty text\n- fix: configurable autocomplete max visible items\n- fix: improved table column width calculation with word-aware wrapping\n\npackages/coding-agent:\n- fix: preserve external config.yml edits on save (#1046 by @nicobailonMD)\n- fix: resolve macOS NFD and curly quote variants in file paths\n```\n\n**Rules:**\n\n- Group changes by package\n- Use conventional commit types (`fix`, `feat`, `refactor`, `perf`, `docs`)\n- Include upstream issue/PR numbers and contributor attribution for external contributions\n- The commit range in the title helps track sync points\n\n## 15) Intentional Divergences\n\nOur fork has architectural decisions that differ from upstream. **Do not port these upstream patterns:**\n\n### UI Architecture\n\n| Upstream                                    | Our Fork                                                  | Reason                                                                |\n| ------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------- |\n| `FooterDataProvider` class                  | `StatusLineComponent`                                     | Simpler, integrated status line                                       |\n| `ctx.ui.setHeader()` / `ctx.ui.setFooter()` | Stub in non-TUI modes                                     | Implemented in TUI, no-op elsewhere                                   |\n| `ctx.ui.setEditorComponent()`               | Stub in non-TUI modes                                     | Implemented in TUI, no-op elsewhere                                   |\n| `InteractiveModeOptions` options object     | Positional constructor args (options type still exported) | Keep constructor signature; update the type when upstream adds fields |\n\n### Component Naming\n\n| Upstream                     | Our Fork                |\n| ---------------------------- | ----------------------- |\n| `extension-input.ts`         | `hook-input.ts`         |\n| `extension-selector.ts`      | `hook-selector.ts`      |\n| `ExtensionInputComponent`    | `HookInputComponent`    |\n| `ExtensionSelectorComponent` | `HookSelectorComponent` |\n\n### API Naming\n\n| Upstream                                 | Our Fork                                 | Notes                                     |\n| ---------------------------------------- | ---------------------------------------- | ----------------------------------------- |\n| `sessionManager.appendSessionInfo(name)` | `sessionManager.setSessionName(name)`    | We use `sessionName` throughout           |\n| `sessionManager.getSessionName()`        | `sessionManager.getSessionName()`        | Same (we unified to match upstream's RPC) |\n| `agent.sessionName` / `setSessionName()` | `agent.sessionName` / `setSessionName()` | Same                                      |\n\n### File Consolidation\n\n| Upstream                                           | Our Fork                                | Reason                                  |\n| -------------------------------------------------- | --------------------------------------- | --------------------------------------- |\n| `clipboard.ts` + `clipboard-image.ts` (tool files) | `@f5-sales-demo/pi-natives` clipboard module | Merged into N-API native implementation |\n\n### Test Framework\n\n| Upstream                  | Our Fork                      |\n| ------------------------- | ----------------------------- |\n| `vitest` with `vi.mock()` | `bun:test` with `vi` from bun |\n| `node:test` assertions    | `expect()` matchers           |\n\n### Tool Architecture\n\n| Upstream                            | Our Fork                                                          | Notes                                                     |\n| ----------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------- |\n| `createTool(cwd: string, options?)` | `createTools(session: ToolSession)` via `BUILTIN_TOOLS` registry  | Tool factories accept `ToolSession` and can return `null` |\n| Per-tool `*Operations` interfaces   | Per-tool interfaces remain (`FindOperations`, `GrepOperations`)   | Used for SSH/remote overrides                             |\n| Node.js `fs/promises` everywhere    | `Bun.file()`/`Bun.write()` for files; `node:fs/promises` for dirs | Prefer Bun APIs when they simplify                        |\n\n### Auth Storage\n\n| Upstream                        | Our Fork                                    | Notes                                        |\n| ------------------------------- | ------------------------------------------- | -------------------------------------------- |\n| `proper-lockfile` + `auth.json` | `agent.db` (bun:sqlite)                     | Credentials stored exclusively in `agent.db` |\n| Single credential per provider  | Multi-credential with round-robin selection | Session affinity and backoff logic preserved |\n\n### Extensions\n\n| Upstream                      | Our Fork                                   |\n| ----------------------------- | ------------------------------------------ |\n| `jiti` for TypeScript loading | Native Bun `import()`                      |\n| `pkg.pi` manifest field       | `pkg.xcsh ?? pkg.pi` (prefer our namespace) |\n\n### Skip These Upstream Features\n\nWhen porting, **skip** these files/features entirely:\n\n- `footer-data-provider.ts` — we use StatusLineComponent\n- `clipboard-image.ts` — clipboard is in `@f5-sales-demo/pi-natives` N-API module\n- GitHub workflow files — we have our own CI\n- `models.generated.ts` — auto-generated, regenerate locally (as models.json instead)\n\n### Features We Added (Preserve These)\n\nThese exist in our fork but not upstream. **Never overwrite:**\n\n- `StatusLineComponent` in interactive mode\n- Multi-credential auth with session affinity\n- Capability-based discovery system (`defineCapability`, `registerProvider`, `loadCapability`, `skillCapability`, etc.)\n- MCP/Exa/SSH integrations\n- LSP writethrough for format-on-save\n- Bash interception (`checkBashInterception`)\n- Fuzzy path suggestions in read tool\n",
	"en/configuration/rpc.md": "---\ntitle: RPC Protocol Reference\ndescription: JSON-RPC protocol reference for inter-process communication between xcsh components.\nsidebar:\n  order: 5\n  label: RPC protocol\n---\n\n# RPC Protocol Reference\n\nRPC mode runs the coding agent as a newline-delimited JSON protocol over stdio.\n\n- **stdin**: commands (`RpcCommand`) and extension UI responses\n- **stdout**: command responses (`RpcResponse`), session/agent events, extension UI requests\n\nPrimary implementation:\n\n- `src/modes/rpc/rpc-mode.ts`\n- `src/modes/rpc/rpc-types.ts`\n- `src/session/agent-session.ts`\n- `packages/agent/src/agent.ts`\n- `packages/agent/src/agent-loop.ts`\n\n## Startup\n\n```bash\nxcsh --mode rpc [regular CLI options]\n```\n\nBehavior notes:\n\n- `@file` CLI arguments are rejected in RPC mode.\n- RPC mode disables automatic session title generation by default to avoid an extra model call.\n- RPC mode resets workflow-altering `todo.*`, `task.*`, and `async.*` settings to their built-in defaults instead of inheriting user overrides.\n- The process reads stdin as JSONL (`readJsonl(Bun.stdin.stream())`).\n- When stdin closes, the process exits with code `0`.\n- Responses/events are written as one JSON object per line.\n\n## Transport and Framing\n\nEach frame is a single JSON object followed by `\\n`.\n\nThere is no envelope beyond the object shape itself.\n\n### Outbound frame categories (stdout)\n\n1. `RpcResponse` (`{ type: \"response\", ... }`)\n2. `AgentSessionEvent` objects (`agent_start`, `message_update`, etc.)\n3. `RpcExtensionUIRequest` (`{ type: \"extension_ui_request\", ... }`)\n4. Extension errors (`{ type: \"extension_error\", extensionPath, event, error }`)\n\n### Inbound frame categories (stdin)\n\n1. `RpcCommand`\n2. `RpcExtensionUIResponse` (`{ type: \"extension_ui_response\", ... }`)\n\n## Request/Response Correlation\n\nAll commands accept optional `id?: string`.\n\n- If provided, normal command responses echo the same `id`.\n- `RpcClient` relies on this for pending-request resolution.\n\nImportant edge behavior from runtime:\n\n- Unknown command responses are emitted with `id: undefined` (even if the request had an `id`).\n- Parse/handler exceptions in the input loop emit `command: \"parse\"` with `id: undefined`.\n- `prompt` and `abort_and_prompt` return immediate success, then may emit a later error response with the **same** id if async prompt scheduling fails.\n\n## Command Schema (canonical)\n\n`RpcCommand` is defined in `src/modes/rpc/rpc-types.ts`:\n\n### Prompting\n\n- `{ id?, type: \"prompt\", message: string, images?: ImageContent[], streamingBehavior?: \"steer\" | \"followUp\" }`\n- `{ id?, type: \"steer\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"follow_up\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"abort\" }`\n- `{ id?, type: \"abort_and_prompt\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"new_session\", parentSession?: string }`\n\n### State\n\n- `{ id?, type: \"get_state\" }`\n- `{ id?, type: \"set_todos\", phases: TodoPhase[] }`\n- `{ id?, type: \"set_host_tools\", tools: RpcHostToolDefinition[] }`\n\n### Model\n\n- `{ id?, type: \"set_model\", provider: string, modelId: string }`\n- `{ id?, type: \"cycle_model\" }`\n- `{ id?, type: \"get_available_models\" }`\n\n### Thinking\n\n- `{ id?, type: \"set_thinking_level\", level: ThinkingLevel }`\n- `{ id?, type: \"cycle_thinking_level\" }`\n\n### Queue modes\n\n- `{ id?, type: \"set_steering_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_follow_up_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_interrupt_mode\", mode: \"immediate\" | \"wait\" }`\n\n### Compaction\n\n- `{ id?, type: \"compact\", customInstructions?: string }`\n- `{ id?, type: \"set_auto_compaction\", enabled: boolean }`\n\n### Retry\n\n- `{ id?, type: \"set_auto_retry\", enabled: boolean }`\n- `{ id?, type: \"abort_retry\" }`\n\n### Bash\n\n- `{ id?, type: \"bash\", command: string }`\n- `{ id?, type: \"abort_bash\" }`\n\n### Session\n\n- `{ id?, type: \"get_session_stats\" }`\n- `{ id?, type: \"export_html\", outputPath?: string }`\n- `{ id?, type: \"switch_session\", sessionPath: string }`\n- `{ id?, type: \"branch\", entryId: string }`\n- `{ id?, type: \"get_branch_messages\" }`\n- `{ id?, type: \"get_last_assistant_text\" }`\n- `{ id?, type: \"set_session_name\", name: string }`\n\n### Messages\n\n- `{ id?, type: \"get_messages\" }`\n\n## Response Schema\n\nAll command results use `RpcResponse`:\n\n- Success: `{ id?, type: \"response\", command: <command>, success: true, data?: ... }`\n- Failure: `{ id?, type: \"response\", command: string, success: false, error: string }`\n\nData payloads are command-specific and defined in `rpc-types.ts`.\n\n### `get_state` payload\n\n```json\n{\n  \"model\": { \"provider\": \"...\", \"id\": \"...\" },\n  \"thinkingLevel\": \"off|minimal|low|medium|high|xhigh\",\n  \"isStreaming\": false,\n  \"isCompacting\": false,\n  \"steeringMode\": \"all|one-at-a-time\",\n  \"followUpMode\": \"all|one-at-a-time\",\n  \"interruptMode\": \"immediate|wait\",\n  \"sessionFile\": \"...\",\n  \"sessionId\": \"...\",\n  \"sessionName\": \"...\",\n  \"autoCompactionEnabled\": true,\n  \"messageCount\": 0,\n  \"queuedMessageCount\": 0,\n  \"todoPhases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Todos\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the tool surface\",\n          \"status\": \"in_progress\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n### `set_todos` payload\n\nReplaces the in-memory todo state for the current session and returns the normalized phase list:\n\n```json\n{\n  \"id\": \"req_2\",\n  \"type\": \"set_todos\",\n  \"phases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Evaluation\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the read tool surface\",\n          \"status\": \"in_progress\"\n        },\n        {\n          \"id\": \"task-2\",\n          \"content\": \"Exercise edit operations\",\n          \"status\": \"pending\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nThis is useful for hosts that want to pre-seed a plan before the first prompt.\n\n### `set_host_tools` payload\n\nReplaces the current set of host-owned tools that the RPC server may call back\ninto over stdio:\n\n```json\n{\n  \"id\": \"req_3\",\n  \"type\": \"set_host_tools\",\n  \"tools\": [\n    {\n      \"name\": \"echo_host\",\n      \"label\": \"Echo Host\",\n      \"description\": \"Echo a value from the embedding host\",\n      \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"message\": { \"type\": \"string\" }\n        },\n        \"required\": [\"message\"],\n        \"additionalProperties\": false\n      }\n    }\n  ]\n}\n```\n\nThe response payload is:\n\n```json\n{\n  \"toolNames\": [\"echo_host\"]\n}\n```\n\nThese tools are added to the active session tool registry before the next model\ncall. Re-sending `set_host_tools` replaces the previous host-owned set.\n\n## Event Stream Schema\n\nRPC mode forwards `AgentSessionEvent` objects from `AgentSession.subscribe(...)`.\n\nCommon event types:\n\n- `agent_start`, `agent_end`\n- `turn_start`, `turn_end`\n- `message_start`, `message_update`, `message_end`\n- `tool_execution_start`, `tool_execution_update`, `tool_execution_end`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n- `todo_auto_clear`\n\nExtension runner errors are emitted separately as:\n\n```json\n{ \"type\": \"extension_error\", \"extensionPath\": \"...\", \"event\": \"...\", \"error\": \"...\" }\n```\n\n`message_update` includes streaming deltas in `assistantMessageEvent` (text/thinking/toolcall deltas).\n\n## Prompt/Queue Concurrency and Ordering\n\nThis is the most important operational behavior.\n\n### Immediate ack vs completion\n\n`prompt` and `abort_and_prompt` are **acknowledged immediately**:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n```\n\nThat means:\n\n- command acceptance != run completion\n- final completion is observed via `agent_end`\n\n### While streaming\n\n`AgentSession.prompt()` requires `streamingBehavior` during active streaming:\n\n- `\"steer\"` => queued steering message (interrupt path)\n- `\"followUp\"` => queued follow-up message (post-turn path)\n\nIf omitted during streaming, prompt fails.\n\n### Queue defaults\n\nFrom the coding-agent settings schema (`packages/coding-agent/src/config/settings-schema.ts`):\n\n- `steeringMode`: `\"one-at-a-time\"`\n- `followUpMode`: `\"one-at-a-time\"`\n- `interruptMode`: `\"wait\"`\n\n### Mode semantics\n\n- `set_steering_mode` / `set_follow_up_mode`\n  - `\"one-at-a-time\"`: dequeue one queued message per turn\n  - `\"all\"`: dequeue entire queue at once\n- `set_interrupt_mode`\n  - `\"immediate\"`: tool execution checks steering between tool calls; pending steering can abort remaining tool calls in the turn\n  - `\"wait\"`: defer steering until turn completion\n\n## Extension UI Sub-Protocol\n\nExtensions in RPC mode use request/response UI frames.\n\n### Outbound request\n\n`RpcExtensionUIRequest` (`type: \"extension_ui_request\"`) methods:\n\n- `select`, `confirm`, `input`, `editor`\n- `notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text`\n\nRuntime note:\n\n- Automatic session title generation is disabled in RPC mode, and `setTitle` UI\n  requests are also suppressed by default because most hosts do not have a\n  meaningful terminal-title surface. Set `PI_RPC_EMIT_TITLE=1` to opt back in to\n  the UI event only.\n\nExample:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"123\", \"method\": \"confirm\", \"title\": \"Confirm\", \"message\": \"Continue?\", \"timeout\": 30000 }\n```\n\n### Inbound response\n\n`RpcExtensionUIResponse` (`type: \"extension_ui_response\"`):\n\n- `{ type: \"extension_ui_response\", id: string, value: string }`\n- `{ type: \"extension_ui_response\", id: string, confirmed: boolean }`\n- `{ type: \"extension_ui_response\", id: string, cancelled: true }`\n\nIf a dialog has a timeout, RPC mode resolves to a default value when timeout/abort fires.\n\n## Host Tool Sub-Protocol\n\nRPC hosts can expose custom tools to the agent by sending `set_host_tools`, then\nserving execution requests over the same transport.\n\n### Outbound request\n\nWhen the agent wants the host to execute one of those tools, RPC mode emits:\n\n```json\n{\n  \"type\": \"host_tool_call\",\n  \"id\": \"host_1\",\n  \"toolCallId\": \"toolu_123\",\n  \"toolName\": \"echo_host\",\n  \"arguments\": { \"message\": \"hello\" }\n}\n```\n\nIf the tool execution is later aborted, RPC mode emits:\n\n```json\n{\n  \"type\": \"host_tool_cancel\",\n  \"id\": \"host_cancel_1\",\n  \"targetId\": \"host_1\"\n}\n```\n\n### Inbound updates and completion\n\nHosts can optionally stream progress:\n\n```json\n{\n  \"type\": \"host_tool_update\",\n  \"id\": \"host_1\",\n  \"partialResult\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"working\" }]\n  }\n}\n```\n\nCompletion uses:\n\n```json\n{\n  \"type\": \"host_tool_result\",\n  \"id\": \"host_1\",\n  \"result\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"done\" }]\n  }\n}\n```\n\nSet `isError: true` on `host_tool_result` to surface the returned content as a\ntool error.\n\n## Error Model and Recoverability\n\n### Command-level failures\n\nFailures are `success: false` with string `error`.\n\n```json\n{ \"id\": \"req_2\", \"type\": \"response\", \"command\": \"set_model\", \"success\": false, \"error\": \"Model not found: provider/model\" }\n```\n\n### Recoverability expectations\n\n- Most command failures are recoverable; process remains alive.\n- Malformed JSONL / parse-loop exceptions emit a `parse` error response and continue reading subsequent lines.\n- Empty `set_session_name` is rejected (`Session name cannot be empty`).\n- Extension UI responses with unknown `id` are ignored.\n- Process termination conditions are stdin close or explicit extension-triggered shutdown.\n\n## Compact Command Flows\n\n### 1) Prompt and stream\n\nstdin:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"prompt\", \"message\": \"Summarize this repo\" }\n```\n\nstdout sequence (typical):\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n{ \"type\": \"agent_start\" }\n{ \"type\": \"message_update\", \"assistantMessageEvent\": { \"type\": \"text_delta\", \"delta\": \"...\" }, \"message\": { \"role\": \"assistant\", \"content\": [] } }\n{ \"type\": \"agent_end\", \"messages\": [] }\n```\n\n### 2) Prompt during streaming with explicit queue policy\n\nstdin:\n\n```json\n{ \"id\": \"req_2\", \"type\": \"prompt\", \"message\": \"Also include risks\", \"streamingBehavior\": \"followUp\" }\n```\n\n### 3) Inspect and tune queue behavior\n\nstdin:\n\n```json\n{ \"id\": \"q1\", \"type\": \"get_state\" }\n{ \"id\": \"q2\", \"type\": \"set_steering_mode\", \"mode\": \"all\" }\n{ \"id\": \"q3\", \"type\": \"set_interrupt_mode\", \"mode\": \"wait\" }\n```\n\n### 4) Extension UI round trip\n\nstdout:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"ui_7\", \"method\": \"input\", \"title\": \"Branch name\", \"placeholder\": \"feature/...\" }\n```\n\nstdin:\n\n```json\n{ \"type\": \"extension_ui_response\", \"id\": \"ui_7\", \"value\": \"feature/rpc-host\" }\n```\n\n## Notes on `RpcClient` helper\n\n`src/modes/rpc/rpc-client.ts` is a convenience wrapper, not the protocol definition.\n\nCurrent helper characteristics:\n\n- Spawns `bun <cliPath> --mode rpc`\n- Correlates responses by generated `req_<n>` ids\n- Dispatches only recognized `AgentEvent` types to listeners\n- Supports host-owned custom tools via `setCustomTools()` and automatic handling of `host_tool_call` / `host_tool_cancel`\n- Does **not** expose helper methods for every protocol command (for example, `set_interrupt_mode` and `set_session_name` are in protocol types but not wrapped as dedicated methods)\n\nUse raw protocol frames if you need complete surface coverage.\n",
	"en/configuration/sdk.md": "---\ntitle: SDK\ndescription: SDK for building custom agents and integrations on top of the xcsh coding agent runtime.\nsidebar:\n  order: 6\n  label: SDK\n---\n\n# SDK\n\nThe SDK is the in-process integration surface for `@f5-sales-demo/xcsh`.\nUse it when you want direct access to agent state, event streaming, tool wiring, and session control from your own Bun/Node process.\n\nIf you need cross-language/process isolation, use RPC mode instead.\n\n## Installation\n\n```bash\nbun add @f5-sales-demo/xcsh\n```\n\n## Entry points\n\n`@f5-sales-demo/xcsh` exports the SDK APIs from the package root (and also via `@f5-sales-demo/xcsh/sdk`).\n\nCore exports for embedders:\n\n- `createAgentSession`\n- `SessionManager`\n- `Settings`\n- `AuthStorage`\n- `ModelRegistry`\n- `discoverAuthStorage`\n- Discovery helpers (`discoverExtensions`, `discoverSkills`, `discoverContextFiles`, `discoverPromptTemplates`, `discoverSlashCommands`, `discoverCustomTSCommands`, `discoverMCPServers`)\n- Tool factory surface (`createTools`, `BUILTIN_TOOLS`, tool classes)\n\n## Quick start (auto-discovery defaults)\n\n```ts\nimport { createAgentSession } from \"@f5-sales-demo/xcsh\";\n\nconst { session, modelFallbackMessage } = await createAgentSession();\n\nif (modelFallbackMessage) {\n process.stderr.write(`${modelFallbackMessage}\\n`);\n}\n\nconst unsubscribe = session.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Summarize this repository in 3 bullets.\");\nunsubscribe();\nawait session.dispose();\n```\n\n## What `createAgentSession()` discovers by default\n\n`createAgentSession()` follows “provide to override, omit to discover”.\n\nIf omitted, it resolves:\n\n- `cwd`: `getProjectDir()`\n- `agentDir`: `~/.xcsh/agent` (via `getAgentDir()`)\n- `authStorage`: `discoverAuthStorage(agentDir)`\n- `modelRegistry`: `new ModelRegistry(authStorage)` + `await refresh()`\n- `settings`: `await Settings.init({ cwd, agentDir })`\n- `sessionManager`: `SessionManager.create(cwd)` (file-backed)\n- skills/context files/prompt templates/slash commands/extensions/custom TS commands\n- built-in tools via `createTools(...)`\n- MCP tools (enabled by default)\n- LSP integration (enabled by default)\n\n### Required vs optional inputs\n\nTypically you must provide only what you want to control:\n\n- **Must provide**: nothing for a minimal session\n- **Usually provide explicitly** in embedders:\n    - `sessionManager` (if you need in-memory or custom location)\n    - `authStorage` + `modelRegistry` (if you own credential/model lifecycle)\n    - `model` or `modelPattern` (if deterministic model selection matters)\n    - `settings` (if you need isolated/test config)\n\n## Session manager behavior (persistent vs in-memory)\n\n`AgentSession` always uses a `SessionManager`; behavior depends on which factory you use.\n\n### File-backed (default)\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.create(process.cwd()),\n});\n\nconsole.log(session.sessionFile); // absolute .jsonl path\n```\n\n- Persists conversation/messages/state deltas to session files.\n- Supports resume/open/list/fork workflows.\n- `session.sessionFile` is defined.\n\n### In-memory\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.inMemory(),\n});\n\nconsole.log(session.sessionFile); // undefined\n```\n\n- No filesystem persistence.\n- Useful for tests, ephemeral workers, request-scoped agents.\n- Session methods still work, but persistence-specific behaviors (file resume/fork paths) are naturally limited.\n\n### Resume/open/list helpers\n\n```ts\nimport { SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst recent = await SessionManager.continueRecent(process.cwd());\nconst listed = await SessionManager.list(process.cwd());\nconst opened = listed[0] ? await SessionManager.open(listed[0].path) : null;\n```\n\n## Model and auth wiring\n\n`createAgentSession()` uses `ModelRegistry` + `AuthStorage` for model selection and API key resolution.\n\n### Explicit wiring\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst available = modelRegistry.getAvailable();\nif (available.length === 0) throw new Error(\"No authenticated models available\");\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n model: available[0],\n thinkingLevel: \"medium\",\n sessionManager: SessionManager.inMemory(),\n});\n```\n\n### Selection order when `model` is omitted\n\nWhen no explicit `model`/`modelPattern` is provided:\n\n1. restore model from existing session (if restorable + key available)\n2. settings default model role (`default`)\n3. first available model with valid auth\n\nIf restore fails, `modelFallbackMessage` explains fallback.\n\n### Auth priority\n\n`AuthStorage.getApiKey(...)` resolves in this order:\n\n1. runtime override (`setRuntimeApiKey`)\n2. stored credentials in `agent.db`\n3. provider environment variables\n4. custom-provider resolver fallback (if configured)\n\n## Event subscription model\n\nSubscribe with `session.subscribe(listener)`; it returns an unsubscribe function.\n\n```ts\nconst unsubscribe = session.subscribe(event => {\n switch (event.type) {\n  case \"agent_start\":\n  case \"turn_start\":\n  case \"tool_execution_start\":\n   break;\n  case \"message_update\":\n   if (event.assistantMessageEvent.type === \"text_delta\") {\n    process.stdout.write(event.assistantMessageEvent.delta);\n   }\n   break;\n }\n});\n```\n\n`AgentSessionEvent` includes core `AgentEvent` plus session-level events:\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n## Prompt lifecycle\n\n`session.prompt(text, options?)` is the primary entry point.\n\nBehavior:\n\n1. optional command/template expansion (`/` commands, custom commands, file slash commands, prompt templates)\n2. if currently streaming:\n    - requires `streamingBehavior: \"steer\" | \"followUp\"`\n    - queues instead of throwing work away\n3. if idle:\n    - validates model + API key\n    - appends user message\n    - starts agent turn\n\nRelated APIs:\n\n- `sendUserMessage(content, { deliverAs? })`\n- `steer(text, images?)`\n- `followUp(text, images?)`\n- `sendCustomMessage({ customType, content, ... }, { deliverAs?, triggerTurn? })`\n- `abort()`\n\n## Tools and extension integration\n\n### Built-ins and filtering\n\n- Built-ins come from `createTools(...)` and `BUILTIN_TOOLS`.\n- `toolNames` acts as an allowlist for built-ins.\n- `customTools` and extension-registered tools are still included.\n- Hidden tools (for example `submit_result`) are opt-in unless required by options.\n\n```ts\nconst { session } = await createAgentSession({\n toolNames: [\"read\", \"grep\", \"find\", \"write\"],\n requireSubmitResultTool: true,\n});\n```\n\n### Extensions\n\n- `extensions`: inline `ExtensionFactory[]`\n- `additionalExtensionPaths`: load extra extension files\n- `disableExtensionDiscovery`: disable automatic extension scanning\n- `preloadedExtensions`: reuse already loaded extension set\n\n### Runtime tool set changes\n\n`AgentSession` supports runtime activation updates:\n\n- `getActiveToolNames()`\n- `getAllToolNames()`\n- `setActiveToolsByName(names)`\n- `refreshMCPTools(mcpTools)`\n\nSystem prompt is rebuilt to reflect active tool changes.\n\n## Discovery helpers\n\nUse these when you want partial control without recreating internal discovery logic:\n\n- `discoverAuthStorage(agentDir?)`\n- `discoverExtensions(cwd?)`\n- `discoverSkills(cwd?, _agentDir?, settings?)`\n- `discoverContextFiles(cwd?, _agentDir?)`\n- `discoverPromptTemplates(cwd?, agentDir?)`\n- `discoverSlashCommands(cwd?)`\n- `discoverCustomTSCommands(cwd?, agentDir?)`\n- `discoverMCPServers(cwd?)`\n- `buildSystemPrompt(options?)`\n\n## Subagent-oriented options\n\nFor SDK consumers building orchestrators (similar to task executor flow):\n\n- `outputSchema`: passes structured output expectation into tool context\n- `requireSubmitResultTool`: forces `submit_result` tool inclusion\n- `taskDepth`: recursion-depth context for nested task sessions\n- `parentTaskPrefix`: artifact naming prefix for nested task outputs\n\nThese are optional for normal single-agent embedding.\n\n## `createAgentSession()` return value\n\n```ts\ntype CreateAgentSessionResult = {\n session: AgentSession;\n extensionsResult: LoadExtensionsResult;\n setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;\n mcpManager?: MCPManager;\n modelFallbackMessage?: string;\n lspServers?: Array<{ name: string; status: \"ready\" | \"error\"; fileTypes: string[]; error?: string }>;\n};\n```\n\nUse `setToolUIContext(...)` only if your embedder provides UI capabilities that tools/extensions should call into.\n\n## Minimal controlled embed example\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n Settings,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst settings = Settings.isolated({\n \"compaction.enabled\": true,\n \"retry.enabled\": true,\n});\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n settings,\n sessionManager: SessionManager.inMemory(),\n toolNames: [\"read\", \"grep\", \"find\", \"edit\", \"write\"],\n enableMCP: false,\n enableLsp: true,\n});\n\nsession.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Find all TODO comments in this repo and propose fixes.\");\nawait session.dispose();\n```\n",
	"en/configuration/secrets.md": "---\ntitle: Secret Obfuscation\ndescription: Secret obfuscation pipeline that redacts sensitive values from session logs and outputs.\nsidebar:\n  order: 3\n  label: Secrets\n---\n\n# Secret Obfuscation\n\nPrevents sensitive values (API keys, tokens, passwords) from being sent to LLM providers. When enabled, secrets are replaced with deterministic placeholders before leaving the process, and restored in tool call arguments returned by the model.\n\n## Enabling\n\nEnabled by default. Toggle via `/settings` UI or directly in `config.yml`:\n\n```yaml\nsecrets:\n  enabled: false\n```\n\n## How it works\n\n1. On session startup, secrets are collected from two sources:\n   - **Environment variables** matching common secret patterns (`*_KEY`, `*_SECRET`, `*_TOKEN`, `*_PASSWORD`, etc.) with values >= 8 characters\n   - **`secrets.yml` files** (see below)\n\n2. Outbound messages to the LLM have all secret values replaced with placeholders like `<<$env:S0>>`, `<<$env:S1>>`, etc.\n\n3. Tool call arguments returned by the model are deep-walked and placeholders are restored to original values before execution.\n\nTwo modes control what happens to each secret:\n\n| Mode | Behavior | Reversible |\n|---|---|---|\n| `obfuscate` (default) | Replaced with indexed placeholder `<<$env:SN>>` | Yes (deobfuscated in tool args) |\n| `replace` | Replaced with deterministic same-length string | No (one-way) |\n\n## secrets.yml\n\nDefine custom secret entries in YAML. Two locations are checked:\n\n| Level | Path | Purpose |\n|---|---|---|\n| Global | `~/.xcsh/agent/secrets.yml` | Secrets across all projects |\n| Project | `<cwd>/.xcsh/secrets.yml` | Project-specific secrets |\n\nProject entries override global entries with matching `content`.\n\n### Schema\n\nEach entry in the array has these fields:\n\n| Field | Type | Required | Description |\n|---|---|---|---|\n| `type` | `\"plain\"` or `\"regex\"` | Yes | Match strategy |\n| `content` | string | Yes | The secret value (plain) or regex pattern (regex) |\n| `mode` | `\"obfuscate\"` or `\"replace\"` | No | Default: `\"obfuscate\"` |\n| `replacement` | string | No | Custom replacement (replace mode only) |\n| `flags` | string | No | Regex flags (regex type only) |\n\n### Examples\n\n#### Plain secrets\n\n```yaml\n# Obfuscate a specific API key (default mode)\n- type: plain\n  content: sk-proj-abc123def456\n\n# Replace a database password with a fixed string\n- type: plain\n  content: hunter2\n  mode: replace\n  replacement: \"********\"\n```\n\n#### Regex secrets\n\n```yaml\n# Obfuscate any AWS-style key\n- type: regex\n  content: \"AKIA[0-9A-Z]{16}\"\n\n# Case-insensitive match with explicit flags\n- type: regex\n  content: \"api[_-]?key\\\\s*=\\\\s*\\\\w+\"\n  flags: \"i\"\n\n# Regex literal syntax (pattern and flags in one string)\n- type: regex\n  content: \"/bearer\\\\s+[a-zA-Z0-9._~+\\\\/=-]+/i\"\n```\n\nRegex entries always scan globally (the `g` flag is enforced automatically). The regex literal syntax `/pattern/flags` is supported as an alternative to separate `content` + `flags` fields. Escaped slashes within the pattern (`\\\\/`) are handled correctly.\n\n#### Replace mode with regex\n\n```yaml\n# One-way replace connection strings (not reversible)\n- type: regex\n  content: \"postgres://[^\\\\s]+\"\n  mode: replace\n  replacement: \"postgres://***\"\n```\n\n## Interaction with env var detection\n\nEnvironment variables are always collected first. File-defined entries are appended after, so file entries can cover secrets that don't live in env vars (config files, hardcoded values, etc.). If the same value appears in both, the file entry's mode takes precedence.\n\n## Key files\n\n- `src/secrets/index.ts` -- loading, merging, env var collection\n- `src/secrets/obfuscator.ts` -- `SecretObfuscator` class, placeholder generation, message obfuscation\n- `src/secrets/regex.ts` -- regex literal parsing and compilation\n- `src/config/settings-schema.ts` -- `secrets.enabled` setting definition\n",
	"en/extensions/extension-loading.md": "---\ntitle: Extension Loading (TypeScript/JavaScript Modules)\ndescription: TypeScript and JavaScript module loading pipeline for extensions with resolution, validation, and caching.\nsidebar:\n  order: 2\n  label: Extension loading\n---\n\n# Extension Loading (TypeScript/JavaScript Modules)\n\nThis document covers how the coding agent discovers and loads **extension modules** (`.ts`/`.js`) at startup.\n\nIt does **not** cover `gemini-extension.json` manifest extensions (documented separately).\n\n## What this subsystem does\n\nExtension loading builds a list of module entry files, imports each module with Bun, executes its factory, and returns:\n\n- loaded extension definitions\n- per-path load errors (without aborting the whole load)\n- a shared extension runtime object used later by `ExtensionRunner`\n\n## Primary implementation files\n\n- `src/extensibility/extensions/loader.ts` — path discovery + import/execution\n- `src/extensibility/extensions/index.ts` — public exports\n- `src/extensibility/extensions/runner.ts` — runtime/event execution after load\n- `src/discovery/builtin.ts` — native auto-discovery provider for extension modules\n- `src/config/settings.ts` — loads merged `extensions` / `disabledExtensions` settings\n\n---\n\n## Inputs to extension loading\n\n### 1) Auto-discovered native extension modules\n\n`discoverAndLoadExtensions()` first asks discovery providers for `extension-module` capability items, then keeps only provider `native` items.\n\nEffective native locations:\n\n- Project: `<cwd>/.xcsh/extensions`\n- User: `~/.xcsh/agent/extensions`\n\nPath roots come from the native provider (`SOURCE_PATHS.native`).\n\nNotes:\n\n- Native auto-discovery is currently `.xcsh` based.\n- Legacy `.pi` is still accepted in `package.json` manifest keys (`pi.extensions`), but not as a native root here.\n\n### 2) Explicitly configured paths\n\nAfter auto-discovery, configured paths are appended and resolved.\n\nConfigured path sources in the main session startup path (`sdk.ts`):\n\n1. CLI-provided paths (`--extension/-e`, and `--hook` is also treated as an extension path)\n2. Settings `extensions` array (merged global + project settings)\n\nGlobal settings file:\n\n- `~/.xcsh/agent/config.yml` (or custom agent dir via `PI_CODING_AGENT_DIR`)\n\nProject settings file:\n\n- `<cwd>/.xcsh/settings.json`\n\nExamples:\n\n```yaml\n# ~/.xcsh/agent/config.yml\nextensions:\n  - ~/my-exts/safety.ts\n  - ./local/ext-pack\n```\n\n```json\n{\n  \"extensions\": [\"./.xcsh/extensions/my-extra\"]\n}\n```\n\n---\n\n## Enable/disable controls\n\n### Disable discovery\n\n- CLI: `--no-extensions`\n- SDK option: `disableExtensionDiscovery`\n\nBehavior split:\n\n- SDK: when `disableExtensionDiscovery=true`, it still loads `additionalExtensionPaths` via `loadExtensions()`.\n- CLI path building (`main.ts`) currently clears CLI extension paths when `--no-extensions` is set, so explicit `-e/--hook` are not forwarded in that mode.\n\n### Disable specific extension modules\n\n`disabledExtensions` setting filters by extension id format:\n\n- `extension-module:<derivedName>`\n\n`derivedName` is based on entry path (`getExtensionNameFromPath`), for example:\n\n- `/x/foo.ts` -> `foo`\n- `/x/bar/index.ts` -> `bar`\n\nExample:\n\n```yaml\ndisabledExtensions:\n  - extension-module:foo\n```\n\n---\n\n## Path and entry resolution\n\n### Path normalization\n\nFor configured paths:\n\n1. Normalize unicode spaces\n2. Expand `~`\n3. If relative, resolve against current `cwd`\n\n### If configured path is a file\n\nIt is used directly as a module entry candidate.\n\n### If configured path is a directory\n\nResolution order:\n\n1. `package.json` in that directory with `xcsh.extensions` (or legacy `pi.extensions`) -> use declared entries\n2. `index.ts`\n3. `index.js`\n4. Otherwise scan one level for extension entries:\n   - direct `*.ts` / `*.js`\n   - subdir `index.ts` / `index.js`\n   - subdir `package.json` with `xcsh.extensions` / `pi.extensions`\n\nRules and constraints:\n\n- no recursive discovery beyond one subdirectory level\n- declared `extensions` manifest entries are resolved relative to that package directory\n- declared entries are included only if file exists/access is allowed\n- in `*/index.{ts,js}` pairs, TypeScript is preferred over JavaScript\n- symlinks are treated as eligible files/directories\n\n### Ignore behavior differs by source\n\n- Native auto-discovery (`discoverExtensionModulePaths` in discovery helpers) uses native glob with `gitignore: true` and `hidden: false`.\n- Explicit configured directory scanning in `loader.ts` uses `readdir` rules and does **not** apply gitignore filtering.\n\n---\n\n## Load order and precedence\n\n`discoverAndLoadExtensions()` builds one ordered list and then calls `loadExtensions()`.\n\nOrder:\n\n1. Native auto-discovered modules\n2. Explicit configured paths (in provided order)\n\nIn `sdk.ts`, configured order is:\n\n1. CLI additional paths\n2. Settings `extensions`\n\nDe-duplication:\n\n- absolute path based\n- first seen path wins\n- later duplicates are ignored\n\nImplication: if the same module path is both auto-discovered and explicitly configured, it is loaded once at the first position (auto-discovered stage).\n\n---\n\n## Module import and factory contract\n\nEach candidate path is loaded with dynamic import:\n\n- `await import(resolvedPath)`\n- factory is `module.default ?? module`\n- factory must be a function (`ExtensionFactory`)\n\nIf export is not a function, that path fails with a structured error and loading continues.\n\n---\n\n## Failure handling and isolation\n\n### During loading\n\nPer extension path, failures are captured as `{ path, error }` and do not stop other paths from loading.\n\nCommon cases:\n\n- import failure / missing file\n- invalid factory export (non-function)\n- exception thrown while executing factory\n\n### Runtime isolation model\n\n- Extensions are **not sandboxed** (same process/runtime).\n- They share one `EventBus` and one `ExtensionRuntime` instance.\n- During load, runtime action methods intentionally throw `ExtensionRuntimeNotInitializedError`; action wiring happens later in `ExtensionRunner.initialize()`.\n\n### After loading\n\nWhen events run through `ExtensionRunner`, handler exceptions are caught and emitted as extension errors instead of crashing the runner loop.\n\n---\n\n## Minimal user/project layout examples\n\n### User-level\n\n```text\n~/.xcsh/agent/\n  config.yml\n  extensions/\n    guardrails.ts\n    audit/\n      index.ts\n```\n\n### Project-level\n\n```text\n<repo>/\n  .xcsh/\n    settings.json\n    extensions/\n      checks/\n        package.json\n      lint-gates.ts\n```\n\n`checks/package.json`:\n\n```json\n{\n  \"xcsh\": {\n    \"extensions\": [\"./src/check-a.ts\", \"./src/check-b.js\"]\n  }\n}\n```\n\nLegacy manifest key still accepted:\n\n```json\n{\n  \"pi\": {\n    \"extensions\": [\"./index.ts\"]\n  }\n}\n```\n",
	"en/extensions/extensions.md": "---\ntitle: Extensions\ndescription: Extension runtime overview covering types, runner lifecycle, registration, and discovery.\nsidebar:\n  order: 1\n  label: Overview\n---\n\n# Extensions\n\nPrimary guide for authoring runtime extensions in `packages/coding-agent`.\n\nThis document covers the current extension runtime in:\n\n- `src/extensibility/extensions/types.ts`\n- `src/extensibility/extensions/runner.ts`\n- `src/extensibility/extensions/wrapper.ts`\n- `src/extensibility/extensions/index.ts`\n- `src/modes/controllers/extension-ui-controller.ts`\n\nFor discovery paths and filesystem loading rules, see `docs/extension-loading.md`.\n\n## What an extension is\n\nAn extension is a TS/JS module exporting a default factory:\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nexport default function myExtension(pi: ExtensionAPI) {\n // register handlers/tools/commands/renderers\n}\n```\n\nExtensions can combine all of the following in one module:\n\n- event handlers (`pi.on(...)`)\n- LLM-callable tools (`pi.registerTool(...)`)\n- slash commands (`pi.registerCommand(...)`)\n- keyboard shortcuts and flags\n- custom message rendering\n- session/message injection APIs (`sendMessage`, `sendUserMessage`, `appendEntry`)\n\n## Runtime model\n\n1. Extensions are imported and their factory functions run.\n2. During that load phase, registration methods are valid; runtime action methods are not yet initialized.\n3. `ExtensionRunner.initialize(...)` wires live actions/contexts for the active mode.\n4. Session/agent/tool lifecycle events are emitted to handlers.\n5. Every tool execution is wrapped with extension interception (`tool_call` / `tool_result`).\n\n```text\nExtension lifecycle (simplified)\n\nload paths\n   │\n   ▼\nimport module + run factory (registration only)\n   │\n   ▼\nExtensionRunner.initialize(mode/session/tool registry)\n   │\n   ├─ emit session/agent events to handlers\n   ├─ wrap tool execution (tool_call/tool_result)\n   └─ expose runtime actions (sendMessage, setActiveTools, ...)\n```\n\nImportant constraint from `loader.ts`:\n\n- calling action methods like `pi.sendMessage()` during extension load throws `ExtensionRuntimeNotInitializedError`\n- register first; perform runtime behavior from events/commands/tools\n\n## Quick start\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\nimport { Type } from \"@sinclair/typebox\";\n\nexport default function (pi: ExtensionAPI) {\n pi.setLabel(\"Safety + Utilities\");\n\n pi.on(\"session_start\", async (_event, ctx) => {\n  ctx.ui.notify(`Extension loaded in ${ctx.cwd}`, \"info\");\n });\n\n pi.on(\"tool_call\", async (event) => {\n  if (event.toolName === \"bash\" && event.input.command?.includes(\"rm -rf\")) {\n   return { block: true, reason: \"Blocked by extension policy\" };\n  }\n });\n\n pi.registerTool({\n  name: \"hello_extension\",\n  label: \"Hello Extension\",\n  description: \"Return a greeting\",\n  parameters: Type.Object({ name: Type.String() }),\n  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {\n   return {\n    content: [{ type: \"text\", text: `Hello, ${params.name}` }],\n    details: { greeted: params.name },\n   };\n  },\n });\n\n pi.registerCommand(\"hello-ext\", {\n  description: \"Show queue state\",\n  handler: async (_args, ctx) => {\n   ctx.ui.notify(`pending=${ctx.hasPendingMessages()}`, \"info\");\n  },\n });\n}\n```\n\n## Extension API surfaces\n\n## 1) Registration and actions (`ExtensionAPI`)\n\nCore methods:\n\n- `on(event, handler)`\n- `registerTool`, `registerCommand`, `registerShortcut`, `registerFlag`\n- `registerMessageRenderer`\n- `sendMessage`, `sendUserMessage`, `appendEntry`\n- `getActiveTools`, `getAllTools`, `setActiveTools`\n- `getSessionName`, `setSessionName`\n- `setModel`, `getThinkingLevel`, `setThinkingLevel`\n- `registerProvider`\n- `events` (shared event bus)\n\nIn interactive mode, `input` handlers run before the built-in first-message auto-title check. Extensions that call `await pi.setSessionName(...)` from `input` can set the persisted session name and prevent the default auto-generated title from running for that session.\n\nAlso exposed:\n\n- `pi.logger`\n- `pi.typebox`\n- `pi.pi` (package exports)\n\n### Message delivery semantics\n\n`pi.sendMessage(message, options)` supports:\n\n- `deliverAs: \"steer\"` (default) — interrupts current run\n- `deliverAs: \"followUp\"` — queued to run after current run\n- `deliverAs: \"nextTurn\"` — stored and injected on the next user prompt\n- `triggerTurn: true` — starts a turn when idle (`nextTurn` ignores this)\n\n`pi.sendUserMessage(content, { deliverAs })` always goes through prompt flow; while streaming it queues as steer/follow-up.\n\n## 2) Handler context (`ExtensionContext`)\n\nHandlers and tool `execute` receive `ctx` with:\n\n- `ui`\n- `hasUI`\n- `cwd`\n- `sessionManager` (read-only)\n- `modelRegistry`, `model`\n- `getContextUsage()`\n- `compact(...)`\n- `isIdle()`, `hasPendingMessages()`, `abort()`\n- `shutdown()`\n- `getSystemPrompt()`\n\n## 3) Command context (`ExtensionCommandContext`)\n\nCommand handlers additionally get:\n\n- `waitForIdle()`\n- `newSession(...)`\n- `switchSession(...)`\n- `branch(entryId)`\n- `navigateTree(targetId, { summarize })`\n- `reload()`\n\nUse command context for session-control flows; these methods are intentionally separated from general event handlers.\n\n## Event surface (current names and behavior)\n\nCanonical event unions and payload types are in `types.ts`.\n\n### Session lifecycle\n\n- `session_start`\n- `session_before_switch` / `session_switch`\n- `session_before_branch` / `session_branch`\n- `session_before_compact` / `session.compacting` / `session_compact`\n- `session_before_tree` / `session_tree`\n- `session_shutdown`\n\nCancelable pre-events:\n\n- `session_before_switch` → `{ cancel?: boolean }`\n- `session_before_branch` → `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_before_compact` → `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session_before_tree` → `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n\n### Prompt and turn lifecycle\n\n- `input`\n- `before_agent_start`\n- `context`\n- `agent_start` / `agent_end`\n- `turn_start` / `turn_end`\n- `message_start` / `message_update` / `message_end`\n\n### Tool lifecycle\n\n- `tool_call` (pre-exec, may block)\n- `tool_result` (post-exec, may patch content/details/isError)\n- `tool_execution_start` / `tool_execution_update` / `tool_execution_end` (observability)\n\n`tool_result` is middleware-style: handlers run in extension order and each sees prior modifications.\n\n### Reliability/runtime signals\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### User command interception\n\n- `user_bash` (override with `{ result }`)\n- `user_python` (override with `{ result }`)\n\n### `resources_discover`\n\n`resources_discover` exists in extension types and `ExtensionRunner`.\nCurrent runtime note: `ExtensionRunner.emitResourcesDiscover(...)` is implemented, but there are no `AgentSession` callsites invoking it in the current codebase.\n\n## Tool authoring details\n\n`registerTool` uses `ToolDefinition` from `types.ts`.\n\nCurrent `execute` signature:\n\n```ts\nexecute(\n toolCallId,\n params,\n signal,\n onUpdate,\n ctx,\n): Promise<AgentToolResult>\n```\n\nTemplate:\n\n```ts\npi.registerTool({\n name: \"my_tool\",\n label: \"My Tool\",\n description: \"...\",\n parameters: Type.Object({}),\n async execute(_id, _params, signal, onUpdate, ctx) {\n  if (signal?.aborted) {\n   return { content: [{ type: \"text\", text: \"Cancelled\" }] };\n  }\n  onUpdate?.({ content: [{ type: \"text\", text: \"Working...\" }] });\n  return { content: [{ type: \"text\", text: \"Done\" }], details: {} };\n },\n onSession(event, ctx) {\n  // reason: start|switch|branch|tree|shutdown\n },\n renderCall(args, theme) {\n  // optional TUI render\n },\n renderResult(result, options, theme, args) {\n  // optional TUI render\n },\n});\n```\n\n`tool_call`/`tool_result` intercept all tools once the registry is wrapped in `sdk.ts`, including built-ins and extension/custom tools.\n\n## UI integration points\n\n`ctx.ui` implements the `ExtensionUIContext` interface. Support differs by mode.\n\n### Interactive mode (`extension-ui-controller.ts`)\n\nSupported:\n\n- dialogs: `select`, `confirm`, `input`, `editor`\n- notifications/status/editor text/terminal input/custom overlays\n- theme listing/loading by name (`setTheme` supports string names)\n- tools expanded toggle\n\nCurrent no-op methods in this controller:\n\n- `setFooter`\n- `setHeader`\n- `setEditorComponent`\n\nAlso note: `setWidget` currently routes to status-line text via `setHookWidget(...)`.\n\n### RPC mode (`rpc-mode.ts`)\n\n`ctx.ui` is backed by RPC `extension_ui_request` events:\n\n- dialog methods (`select`, `confirm`, `input`, `editor`) round-trip to client responses\n- fire-and-forget methods emit requests (`notify`, `setStatus`, `setWidget` for string arrays, `setTitle`, `setEditorText`)\n\nUnsupported/no-op in RPC implementation:\n\n- `onTerminalInput`\n- `custom`\n- `setFooter`, `setHeader`, `setEditorComponent`\n- `setWorkingMessage`\n- theme switching/loading (`setTheme` returns failure)\n- tool expansion controls are inert\n\n### Print/headless/subagent paths\n\nWhen no UI context is supplied to runner init, `ctx.hasUI` is `false` and methods are no-op/default-returning.\n\n### Background interactive mode\n\nBackground mode installs a non-interactive UI context object. In current implementation, `ctx.hasUI` may still be `true` while interactive dialogs return defaults/no-op behavior.\n\n## Session and state patterns\n\nFor durable extension state:\n\n1. Persist with `pi.appendEntry(customType, data)`.\n2. Rebuild state from `ctx.sessionManager.getBranch()` on `session_start`, `session_branch`, `session_tree`.\n3. Keep tool result `details` structured when state should be visible/reconstructible from tool result history.\n\nExample reconstruction pattern:\n\n```ts\npi.on(\"session_start\", async (_event, ctx) => {\n let latest;\n for (const entry of ctx.sessionManager.getBranch()) {\n  if (entry.type === \"custom\" && entry.customType === \"my-state\") {\n   latest = entry.data;\n  }\n }\n // restore from latest\n});\n```\n\n## Rendering extension points\n\n## Custom message renderer\n\n```ts\npi.registerMessageRenderer(\"my-type\", (message, { expanded }, theme) => {\n // return pi-tui Component\n});\n```\n\nUsed by interactive rendering when custom messages are displayed.\n\n## Tool call/result renderer\n\nProvide `renderCall` / `renderResult` on `registerTool` definitions for custom tool visualization in TUI.\n\n## Constraints and pitfalls\n\n- Runtime actions are unavailable during extension load.\n- `tool_call` errors block execution (fail-closed).\n- Command name conflicts with built-ins are skipped with diagnostics.\n- Reserved shortcuts are ignored (`ctrl+c`, `ctrl+d`, `ctrl+z`, `ctrl+k`, `ctrl+p`, `ctrl+l`, `ctrl+o`, `ctrl+t`, `ctrl+g`, `shift+tab`, `shift+ctrl+p`, `alt+enter`, `escape`, `enter`).\n- Treat `ctx.reload()` as terminal for the current command handler frame.\n\n## Extensions vs hooks vs custom-tools\n\nUse the right surface:\n\n- **Extensions** (`src/extensibility/extensions/*`): unified system (events + tools + commands + renderers + provider registration).\n- **Hooks** (`src/extensibility/hooks/*`): separate legacy event API.\n- **Custom-tools** (`src/extensibility/custom-tools/*`): tool-focused modules; when loaded alongside extensions they are adapted and still pass through extension interception wrappers.\n\nIf you need one package that owns policy, tools, command UX, and rendering together, use extensions.\n",
	"en/extensions/gemini-manifest-extensions.md": "---\ntitle: Gemini Manifest Extensions\ndescription: Gemini manifest extension format for cross-platform skill and agent compatibility.\nsidebar:\n  order: 7\n  label: Gemini manifest\n---\n\n# Gemini Manifest Extensions (`gemini-extension.json`)\n\nThis document covers how the coding-agent discovers and parses Gemini-style manifest extensions (`gemini-extension.json`) into the `extensions` capability.\n\nIt does **not** cover TypeScript/JavaScript extension module loading (`extensions/*.ts`, `index.ts`, `package.json xcsh.extensions`), which is documented in `extension-loading.md`.\n\n## Implementation files\n\n- [`../src/discovery/gemini.ts`](../../packages/coding-agent/src/discovery/gemini.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/capability/extension.ts`](../../packages/coding-agent/src/capability/extension.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/loader.ts`](../../packages/coding-agent/src/extensibility/extensions/loader.ts)\n\n---\n\n## What gets discovered\n\nThe Gemini provider (`id: gemini`, priority `60`) registers an `extensions` loader that scans two fixed roots:\n\n- User: `~/.gemini/extensions`\n- Project: `<cwd>/.gemini/extensions`\n\nPath resolution is direct from `ctx.home` and `ctx.cwd` via `getUserPath()` / `getProjectPath()`.\n\nImportant scope rule: project lookup is **cwd-only**. It does not walk parent directories.\n\n---\n\n## Directory scan rules\n\nFor each root (`~/.gemini/extensions` and `<cwd>/.gemini/extensions`), discovery does:\n\n1. `readDirEntries(root)`\n2. keep only direct child directories (`entry.isDirectory()`)\n3. for each child `<name>`, attempt to read exactly:\n   - `<root>/<name>/gemini-extension.json`\n\nThere is no recursive scan beyond one directory level.\n\n### Hidden directories\n\nGemini manifest discovery does **not** filter out dot-prefixed directory names. If a hidden child directory exists and contains `gemini-extension.json`, it is considered.\n\n### Missing/unreadable files\n\nIf `gemini-extension.json` is missing or unreadable, that directory is skipped silently (no warning).\n\n---\n\n## Manifest shape (as implemented)\n\nThe capability type defines this manifest shape:\n\n```ts\ninterface ExtensionManifest {\n name?: string;\n description?: string;\n mcpServers?: Record<string, Omit<MCPServer, \"name\" | \"_source\">>;\n tools?: unknown[];\n context?: unknown;\n}\n```\n\nDiscovery-time behavior is intentionally loose:\n\n- JSON parse success is required.\n- There is no runtime schema validation for field types/content beyond JSON syntax.\n- The parsed object is stored as `manifest` on the capability item.\n\n### Name normalization\n\n`Extension.name` is set to:\n\n1. `manifest.name` if it is not `null`/`undefined`\n2. otherwise the extension directory name\n\nNo string-type enforcement is applied here.\n\n---\n\n## Materialization into capability items\n\nA valid parsed manifest creates one `Extension` capability item:\n\n```ts\n{\n name: manifest.name ?? <directory-name>,\n path: <extension-directory>,\n manifest: <parsed-json>,\n level: \"user\" | \"project\",\n _source: {\n  provider: \"gemini\",\n  providerName: \"Gemini CLI\" // attached by capability registry\n  path: <absolute-manifest-path>,\n  level: \"user\" | \"project\"\n }\n}\n```\n\nNotes:\n\n- `_source.path` is normalized to an absolute path by `createSourceMeta()`.\n- Registry-level capability validation for `extensions` only checks presence of `name` and `path`.\n- Manifest internals (`mcpServers`, `tools`, `context`) are not validated during discovery.\n\n---\n\n## Error handling and warning semantics\n\n### Warned\n\n- Invalid JSON in a manifest file:\n  - warning format: `Invalid JSON in <manifestPath>`\n\n### Not warned (silent skip)\n\n- `extensions` directory missing\n- child directory has no `gemini-extension.json`\n- unreadable manifest file\n- manifest JSON is syntactically valid but semantically odd/incomplete\n\nThis means partial validity is accepted: only syntactic JSON failure emits a warning.\n\n---\n\n## Precedence and deduplication with other sources\n\n`extensions` capability is aggregated across providers by the capability registry.\n\nCurrent providers for this capability:\n\n- `native` (`packages/coding-agent/src/discovery/builtin.ts`) priority `100`\n- `gemini` (`packages/coding-agent/src/discovery/gemini.ts`) priority `60`\n\nDedup key is `ext.name` (`extensionCapability.key = ext => ext.name`).\n\n### Cross-provider precedence\n\nHigher-priority provider wins on duplicate extension names.\n\n- If `native` and `gemini` both emit extension name `foo`, the native item is kept.\n- Lower-priority duplicate is retained only in `result.all` with `_shadowed = true`.\n\n### Intra-provider order effects\n\nBecause dedup is “first seen wins”, provider-local item order matters.\n\n- Gemini loader appends **user first**, then **project**.\n- Therefore, duplicate names between `~/.gemini/extensions` and `<cwd>/.gemini/extensions` keep the user entry and shadow the project entry.\n\nBy contrast, native provider builds config dir order differently (`project` then `user` in `getConfigDirs()`), so native intra-provider shadowing is the opposite direction.\n\n---\n\n## User vs project behavior summary\n\nFor Gemini manifests specifically:\n\n- Both user and project roots are scanned every load.\n- Project root is fixed to `<cwd>/.gemini/extensions` (no ancestor walk).\n- Duplicate names inside Gemini source resolve to user-first.\n- Duplicate names against higher-priority providers (notably native) lose by priority.\n\n---\n\n## Boundary: discovery metadata vs runtime extension loading\n\n`gemini-extension.json` discovery currently feeds capability metadata (`Extension` items). It does **not** directly load runnable TS/JS extension modules.\n\nRuntime module loading (`discoverAndLoadExtensions()` / `loadExtensions()`) uses `extension-modules` and explicit paths, and currently filters auto-discovered modules to provider `native` only.\n\nPractical implication:\n\n- Gemini manifest extensions are discoverable as capability records.\n- They are not, by themselves, executed as runtime extension modules by the extension loader pipeline.\n\nThis boundary is intentional in current implementation and explains why manifest discovery and executable module loading can diverge.\n",
	"en/extensions/marketplace.md": "---\ntitle: Marketplace Plugin System\ndescription: Marketplace plugin system for discovering, installing, and managing curated plugin collections.\nsidebar:\n  order: 4\n  label: Marketplace\n---\n\n# Marketplace plugin system\n\nThe marketplace system lets you discover, install, and manage plugins from Git-hosted catalogs. It is compatible with the Claude Code plugin registry format.\n\n## Quick start\n\n```\n/marketplace add anthropics/f5-sales-demo-marketplace\n/marketplace install wordpress.com@f5-sales-demo-marketplace\n```\n\nOr just type `/marketplace` with no arguments to open the interactive plugin browser.\n\n## Concepts\n\nA **marketplace** is a Git repository (or local directory) containing a catalog file at `.xcsh-plugin/marketplace.json`. The catalog lists available plugins with their sources, descriptions, and metadata.\n\nA **plugin** is a directory containing skills, commands, hooks, MCP servers, or LSP servers. Plugins are identified by `name@marketplace` (e.g. `code-review@f5-sales-demo-marketplace`).\n\n**Scopes**: plugins can be installed at two scopes:\n\n- **user** (default) -- available in all projects, stored in `~/.xcsh/plugins/installed_plugins.json`\n- **project** -- available only in the current project, stored in `.xcsh/installed_plugins.json`\n\nProject-scoped installs shadow user-scoped installs of the same plugin.\n\n## Commands\n\n### Interactive mode\n\n| Command | Effect |\n|---|---|\n| `/marketplace` | Open interactive plugin browser (install) |\n\n### Marketplace management\n\n| Command | Effect |\n|---|---|\n| `/marketplace add <source>` | Add a marketplace source |\n| `/marketplace remove <name>` | Remove a marketplace |\n| `/marketplace update [name]` | Re-fetch catalog(s); omit name to update all |\n| `/marketplace list` | List configured marketplaces |\n\n### Plugin operations\n\n| Command | Effect |\n|---|---|\n| `/marketplace discover [marketplace]` | Browse available plugins |\n| `/marketplace install [--force] [--scope user\\|project] name@marketplace` | Install a plugin |\n| `/marketplace uninstall [--scope user\\|project] name@marketplace` | Uninstall a plugin |\n| `/marketplace installed` | List installed marketplace plugins |\n| `/marketplace upgrade [--scope user\\|project] [name@marketplace]` | Upgrade one or all plugins |\n\n### CLI equivalents\n\nThe same operations are available from the command line:\n\n```\nxcsh plugin marketplace add <source>\nxcsh plugin marketplace remove <name>\nxcsh plugin marketplace update [name]\nxcsh plugin marketplace list\nxcsh plugin discover [marketplace]\nxcsh plugin install --scope project name@marketplace\n```\n\n## Marketplace sources\n\nWhen you run `/marketplace add <source>`, the system classifies the source:\n\n| Source format | Type | Example |\n|---|---|---|\n| `owner/repo` | GitHub shorthand | `anthropics/f5-sales-demo-marketplace` |\n| `https://...*.json` | Direct catalog URL | `https://example.com/marketplace.json` |\n| `https://...*.git` or `git@...` | Git repository | `https://github.com/org/repo.git` |\n| `./path` or `~/path` or `/path` | Local directory | `./my-marketplace` |\n\nThe system clones the repository (or reads the local directory), locates `.xcsh-plugin/marketplace.json`, validates it, and caches the catalog locally.\n\n## Catalog format (marketplace.json)\n\nA marketplace catalog lives at `.xcsh-plugin/marketplace.json` in the repository root:\n\n```json\n{\n  \"$schema\": \"https://anthropic.com/claude-code/marketplace.schema.json\",\n  \"name\": \"my-marketplace\",\n  \"owner\": {\n    \"name\": \"Your Name\",\n    \"email\": \"you@example.com\"\n  },\n  \"description\": \"A collection of plugins\",\n  \"plugins\": [\n    {\n      \"name\": \"my-plugin\",\n      \"description\": \"What this plugin does\",\n      \"source\": \"./plugins/my-plugin\",\n      \"category\": \"development\",\n      \"homepage\": \"https://github.com/you/my-plugin\"\n    }\n  ]\n}\n```\n\n### Required fields\n\n| Field | Description |\n|---|---|\n| `name` | Marketplace name. Lowercase alphanumeric, hyphens, and dots. Must start and end with alphanumeric. Max 64 chars. |\n| `owner.name` | Marketplace owner name |\n| `plugins` | Array of plugin entries |\n\n### Plugin entry fields\n\n| Field | Required | Description |\n|---|---|---|\n| `name` | yes | Plugin name (same rules as marketplace name) |\n| `source` | yes | Where to find the plugin (see below) |\n| `description` | no | Short description |\n| `version` | no | Version string |\n| `author` | no | `{ name, email? }` |\n| `homepage` | no | URL |\n| `category` | no | Category string (e.g. `development`, `productivity`, `security`) |\n| `tags` | no | Array of string tags |\n| `strict` | no | Boolean |\n| `commands` | no | Slash commands provided |\n| `agents` | no | Agents provided |\n| `hooks` | no | Hook definitions |\n| `mcpServers` | no | MCP server definitions |\n| `lspServers` | no | LSP server definitions |\n\n### Plugin source formats\n\nThe `source` field supports several formats:\n\n**Relative path** (within the marketplace repo):\n\n```json\n\"source\": \"./plugins/my-plugin\"\n```\n\n**Git repository URL**:\n\n```json\n\"source\": {\n  \"source\": \"url\",\n  \"url\": \"https://github.com/org/repo.git\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**GitHub shorthand**:\n\n```json\n\"source\": {\n  \"source\": \"github\",\n  \"repo\": \"org/repo\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Git subdirectory** (monorepo):\n\n```json\n\"source\": {\n  \"source\": \"git-subdir\",\n  \"url\": \"https://github.com/org/monorepo.git\",\n  \"path\": \"plugins/my-plugin\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**npm package**:\n\n```json\n\"source\": {\n  \"source\": \"npm\",\n  \"package\": \"@scope/my-plugin\",\n  \"version\": \"1.0.0\"\n}\n```\n\n## On-disk layout\n\n```\n~/.xcsh/\n  config/\n    marketplaces.json          # Registry of added marketplaces\n  plugins/\n    installed_plugins.json     # User-scoped installed plugins\n    cache/\n      marketplaces/            # Cached marketplace catalogs\n      plugins/                 # Cached plugin directories\n\n<project>/.xcsh/\n  installed_plugins.json       # Project-scoped installed plugins\n```\n\n## Naming rules\n\nMarketplace and plugin names must:\n\n- Start and end with a lowercase letter or digit\n- Contain only lowercase letters, digits, hyphens, and dots\n- Be at most 64 characters\n\nPlugin IDs (`name@marketplace`) must be at most 128 characters total.\n\nValid examples: `my-plugin`, `code-review`, `wordpress.com`, `ai-firstify`\nInvalid examples: `-bad`, `bad-`, `.bad`, `Bad`, `under_score`\n",
	"en/extensions/plugin-manager-installer-plumbing.md": "---\ntitle: Plugin Manager and Installer Plumbing\ndescription: Plugin manager internals covering installation, validation, dependency resolution, and lifecycle management.\nsidebar:\n  order: 5\n  label: Plugin manager\n---\n\n# Plugin manager and installer plumbing\n\nThis document describes how `xcsh plugin` operations mutate plugin state on disk and how installed plugins become runtime capabilities (tools today, hooks/commands path resolution available).\n\n## Scope and architecture\n\nThere are two plugin-management implementations in the codebase:\n\n1. **Active path used by CLI commands**: `PluginManager` (`src/extensibility/plugins/manager.ts`)\n2. **Legacy helper module**: installer functions (`src/extensibility/plugins/installer.ts`)\n\n`xcsh plugin ...` command execution goes through `PluginManager`.\n\n`installer.ts` still documents important safety checks and filesystem behavior, but it is not the path used by `src/commands/plugin.ts` + `src/cli/plugin-cli.ts`.\n\n## Lifecycle: from CLI invocation to runtime availability\n\n```text\nxcsh plugin <action> ...\n  -> src/commands/plugin.ts\n  -> runPluginCommand(...) in src/cli/plugin-cli.ts\n  -> PluginManager method (install/list/uninstall/link/...) \n  -> mutate ~/.xcsh/plugins/{package.json,node_modules,xcsh-plugins.lock.json}\n  -> runtime discovery: discoverAndLoadCustomTools(...)\n  -> getAllPluginToolPaths(cwd)\n  -> custom tool loader imports tool modules\n```\n\n### Command entrypoints\n\n- `src/commands/plugin.ts` defines command/flags and forwards to `runPluginCommand`.\n- `src/cli/plugin-cli.ts` maps subcommands to `PluginManager` methods:\n  - `install`, `uninstall`, `list`, `link`, `doctor`, `features`, `config`, `enable`, `disable`\n- No explicit `update` action exists; update is done by re-running `install` with a new package/version spec.\n\n## On-disk model\n\nGlobal plugin state lives under `~/.xcsh/plugins`:\n\n- `package.json` — dependency manifest used by `bun install`/`bun uninstall`\n- `node_modules/` — installed plugin packages or symlinks\n- `xcsh-plugins.lock.json` — runtime state:\n  - enabled/disabled per plugin\n  - selected feature set per plugin\n  - persisted plugin settings\n\nProject-local overrides live at:\n\n- `<cwd>/.xcsh/plugin-overrides.json`\n\nOverrides are read-only from manager/loader perspective (no write path here) and can disable plugins or override features/settings for this project.\n\n## Plugin spec parsing and metadata interpretation\n\n## Install spec grammar\n\n`parsePluginSpec` (`parser.ts`) supports:\n\n- `pkg` -> `features: null` (defaults behavior)\n- `pkg[*]` -> enable all manifest features\n- `pkg[]` -> enable no optional features\n- `pkg[a,b]` -> enable named features\n- `@scope/pkg@1.2.3[feat]` -> scoped + versioned package with explicit feature selection\n\n`extractPackageName` strips version suffix for on-disk path lookup after install.\n\n## Manifest source and required fields\n\nManifest is resolved as:\n\n1. `package.json.xcsh`\n2. fallback `package.json.pi`\n3. fallback `{ version: package.version }`\n\nImplications:\n\n- There is no strict schema validation in manager/loader.\n- A package missing `xcsh`/`pi` is still installable and listable.\n- Runtime plugin loading (`getEnabledPlugins`) skips packages without `xcsh`/`pi` manifest.\n- `manifest.version` is always overwritten from package `version`.\n\nMalformed `package.json` JSON is a hard failure at read time; malformed manifest shape may fail later only when specific fields are consumed.\n\n## Install/update flow (`PluginManager.install`)\n\n1. Parse feature bracket syntax from install spec.\n2. Validate package name against regex + shell-metacharacter denylist.\n3. Ensure plugin `package.json` exists (`xcsh-plugins`, private dependencies map).\n4. Run `bun install <packageSpec>` in `~/.xcsh/plugins`.\n5. Read installed package `node_modules/<name>/package.json`.\n6. Resolve manifest and compute `enabledFeatures`:\n   - `[*]`: all declared features (or `null` if no feature map)\n   - `[a,b]`: validates each feature exists in manifest features map\n   - `[]`: empty feature list\n   - bare spec: `null` (use defaults policy later in loader)\n7. Upsert lockfile runtime state: `{ version, enabledFeatures, enabled: true }`.\n\n### Update semantics\n\nBecause update is install-driven:\n\n- `xcsh plugin install pkg@newVersion` updates dependency and lockfile version.\n- Existing settings are preserved; state entry is overwritten for version/features/enabled.\n- No separate “check updates” or transactional migration logic exists.\n\n## Remove flow (`PluginManager.uninstall`)\n\n1. Validate package name.\n2. Run `bun uninstall <name>` in plugin dir.\n3. Remove plugin runtime state from lockfile:\n   - `config.plugins[name]`\n   - `config.settings[name]`\n\nIf uninstall command fails, runtime state is not changed.\n\n## List flow (`PluginManager.list`)\n\n1. Read plugin dependency map from `~/.xcsh/plugins/package.json`.\n2. Load lockfile runtime config (missing file -> empty defaults).\n3. Load project overrides (`<cwd>/.xcsh/plugin-overrides.json`, parse/read errors -> empty object with warning).\n4. For each dependency with a resolvable package.json:\n   - build `InstalledPlugin` record\n   - merge feature/enable state:\n     - base from lockfile (or defaults)\n     - project overrides can replace feature selection\n     - project `disabled` list masks plugin as disabled\n\nThis is the effective state used by CLI status output and settings/features operations.\n\n## Link flow (`PluginManager.link`)\n\n`link` supports local plugin development by symlinking a local package into `~/.xcsh/plugins/node_modules/<pkg.name>`.\n\nBehavior:\n\n1. Resolve `localPath` against manager cwd.\n2. Require local `package.json` and `name` field.\n3. Ensure plugin dirs exist.\n4. For scoped names, create scope directory.\n5. Remove existing path at target link location.\n6. Create symlink.\n7. Add runtime lockfile entry enabled with default features (`null`).\n\nCaveat: current `PluginManager.link` does not enforce the `cwd` path-boundary check present in legacy `installer.ts` (`normalizedPath.startsWith(normalizedCwd)`), so trust is the caller’s responsibility.\n\n## Runtime loading: from installed plugin to callable capabilities\n\n## Discovery gate\n\n`getEnabledPlugins(cwd)` (`plugins/loader.ts`) reads:\n\n- plugin dependency manifest (`package.json`)\n- lockfile runtime state\n- project overrides via `getConfigDirPaths(\"plugin-overrides.json\", { user: false, cwd })`\n\nFiltering:\n\n- skip if no plugin package.json\n- skip if manifest (`xcsh`/`pi`) absent\n- skip if globally disabled in lockfile\n- skip if project-disabled\n\n## Capability path resolution\n\nFor each enabled plugin:\n\n- `resolvePluginToolPaths(plugin)`\n- `resolvePluginHookPaths(plugin)`\n- `resolvePluginCommandPaths(plugin)`\n\nEach resolver includes base entries plus feature entries:\n\n- explicit feature list -> only selected features\n- `enabledFeatures === null` -> enable features marked `default: true`\n\nMissing files are silently skipped (`existsSync` guard).\n\n## Current runtime wiring differences\n\n- **Tools are wired into runtime today** via `discoverAndLoadCustomTools` (`custom-tools/loader.ts`), which calls `getAllPluginToolPaths(cwd)`.\n- Paths are de-duplicated by resolved absolute path in custom tool discovery (`seen` set, first path wins).\n- **Hooks/commands resolvers exist** and are exported, but this code path does not currently wire them into a runtime registry in the same way tools are wired.\n\n## Lock/state management details\n\n`PluginManager` caches runtime config in memory per instance (`#runtimeConfig`) and lazily loads once.\n\nLoad behavior:\n\n- lockfile missing -> `{ plugins: {}, settings: {} }`\n- lockfile read/parse failure -> warning + same empty defaults\n\nSave behavior:\n\n- writes full lockfile JSON pretty-printed each mutation\n\nNo cross-process locking or merge strategy exists; concurrent writers can overwrite each other.\n\n## Safety checks and trust boundaries\n\n## Input/package validation\n\nActive manager path enforces package-name validation:\n\n- regex for scoped/unscoped package specs (optionally with version)\n- explicit shell metacharacter denylist (`[;&|`$(){}[]<>\\\\]`)\n\nThis limits command-injection risk when invoking `bun install/uninstall`.\n\n## Filesystem trust boundary\n\n- Plugin code executes in-process when custom tool modules are imported; no sandboxing.\n- Manifest relative paths are joined against plugin package directory and only existence-checked.\n- The plugin package itself is trusted code once installed.\n\n## Legacy installer-only checks\n\n`installer.ts` includes additional link-time checks not mirrored in `PluginManager.link`:\n\n- local path must resolve inside project cwd\n- extra package name/path traversal guards for symlink target naming\n\nBecause CLI uses `PluginManager`, these stricter link guards are not currently on the main path.\n\n## Failure, partial success, and rollback behavior\n\nThe plugin manager is not transactional.\n\n| Operation stage | Failure behavior | Rollback |\n| --- | --- | --- |\n| `bun install` fails | install aborts with stderr | N/A (no state writes yet) |\n| Install succeeds, then manifest/feature validation fails | command fails | No uninstall rollback; dependency may remain in `node_modules`/`package.json` |\n| Install succeeds, then lockfile write fails | command fails | No rollback of installed package |\n| `bun uninstall` succeeds, lockfile write fails | command fails | Package removed, stale runtime state may remain |\n| `link` removes old target then symlink creation fails | command fails | No restoration of previous link/dir |\n\nOperationally, `doctor --fix` can repair some drift (`bun install`, orphaned config cleanup, invalid-feature cleanup), but it is best-effort.\n\n## Malformed/missing manifest behavior summary\n\n- Missing `xcsh`/`pi` field:\n  - install/list: tolerated (minimal manifest)\n  - runtime enabled-plugin discovery: skipped as non-plugin\n- Missing feature referenced by install spec or `features --set/--enable`: hard error with available feature list\n- Invalid `plugin-overrides.json`: ignored with fallback to `{}` in both manager and loader paths\n- Missing tool/hook/command file paths referenced by manifest: silently ignored during resolver expansion; flagged as errors only by `doctor`\n\n## Mode differences and precedence\n\n- `--dry-run` (install): returns synthetic install result, no filesystem/network/state writes.\n- `--json`: output formatting only, no behavior change.\n- Project overrides always take precedence over global lockfile for feature/settings view.\n- Effective enablement is `runtimeEnabled && !projectDisabled`.\n\n## Implementation files\n\n- [`src/commands/plugin.ts`](../../packages/coding-agent/src/commands/plugin.ts) — CLI command declaration and flag mapping\n- [`src/cli/plugin-cli.ts`](../../packages/coding-agent/src/cli/plugin-cli.ts) — action dispatch, user-facing command handlers\n- [`src/extensibility/plugins/manager.ts`](../../packages/coding-agent/src/extensibility/plugins/manager.ts) — active install/remove/list/link/state/doctor implementation\n- [`src/extensibility/plugins/installer.ts`](../../packages/coding-agent/src/extensibility/plugins/installer.ts) — legacy installer helpers and additional link safety checks\n- [`src/extensibility/plugins/loader.ts`](../../packages/coding-agent/src/extensibility/plugins/loader.ts) — enabled-plugin discovery and tool/hook/command path resolution\n- [`src/extensibility/plugins/parser.ts`](../../packages/coding-agent/src/extensibility/plugins/parser.ts) — install spec and package-name parsing helpers\n- [`src/extensibility/plugins/types.ts`](../../packages/coding-agent/src/extensibility/plugins/types.ts) — manifest/runtime/override type contracts\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts) — runtime wiring for plugin-provided tool modules\n",
	"en/extensions/rulebook-matching-pipeline.md": "---\ntitle: Rulebook Matching Pipeline\ndescription: Rulebook matching pipeline for selecting and applying context-specific instruction sets to agent sessions.\nsidebar:\n  order: 6\n  label: Rulebook matching\n---\n\n# Rulebook Matching Pipeline\n\nThis document describes how coding-agent discovers rules from supported config formats, normalizes them into a single `Rule` shape, resolves precedence conflicts, and splits the result into:\n\n- **Rulebook rules** (available to the model via system prompt + `rule://` URLs)\n- **TTSR rules** (time-travel stream interruption rules)\n\nIt reflects the current implementation, including partial semantics and metadata that is parsed but not enforced.\n\n## Implementation files\n\n- [`../src/capability/rule.ts`](../../packages/coding-agent/src/capability/rule.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/discovery/index.ts`](../../packages/coding-agent/src/discovery/index.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/cursor.ts`](../../packages/coding-agent/src/discovery/cursor.ts)\n- [`../src/discovery/windsurf.ts`](../../packages/coding-agent/src/discovery/windsurf.ts)\n- [`../src/discovery/cline.ts`](../../packages/coding-agent/src/discovery/cline.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/system-prompt.ts`](../../packages/coding-agent/src/system-prompt.ts)\n- [`../src/internal-urls/rule-protocol.ts`](../../packages/coding-agent/src/internal-urls/rule-protocol.ts)\n- [`../src/utils/frontmatter.ts`](../../packages/coding-agent/src/utils/frontmatter.ts)\n\n## 1. Canonical rule shape\n\nAll providers normalize source files into `Rule`:\n\n```ts\ninterface Rule {\n  name: string;\n  path: string;\n  content: string;\n  globs?: string[];\n  alwaysApply?: boolean;\n  description?: string;\n  ttsrTrigger?: string;\n  _source: SourceMeta;\n}\n```\n\nCapability identity is `rule.name` (`ruleCapability.key = rule => rule.name`).\n\nConsequence: precedence and deduplication are **name-based only**. Two different files with the same `name` are considered the same logical rule.\n\n## 2. Discovery sources and normalization\n\n`src/discovery/index.ts` auto-registers providers. For `rules`, current providers are:\n\n- `native` (priority `100`)\n- `cursor` (priority `50`)\n- `windsurf` (priority `50`)\n- `cline` (priority `40`)\n\n### Native provider (`builtin.ts`)\n\nLoads `.xcsh` rules from:\n\n- project: `<cwd>/.xcsh/rules/*.{md,mdc}`\n- user: `~/.xcsh/agent/rules/*.{md,mdc}`\n\nNormalization:\n\n- `name` = filename without `.md`/`.mdc`\n- frontmatter parsed via `parseFrontmatter`\n- `content` = body (frontmatter stripped)\n- `globs`, `alwaysApply`, `description`, `ttsr_trigger` mapped directly\n\nImportant caveat: `globs` is cast as `string[] | undefined` with no element filtering in this provider.\n\n### Cursor provider (`cursor.ts`)\n\nLoads from:\n\n- user: `~/.cursor/rules/*.{mdc,md}`\n- project: `<cwd>/.cursor/rules/*.{mdc,md}`\n\nNormalization (`transformMDCRule`):\n\n- `description`: kept only if string\n- `alwaysApply`: only `true` is preserved (`false` becomes `undefined`)\n- `globs`: accepts array (string elements only) or single string\n- `ttsr_trigger`: string only\n- `name` from filename without extension\n\n### Windsurf provider (`windsurf.ts`)\n\nLoads from:\n\n- user: `~/.codeium/windsurf/memories/global_rules.md` (fixed rule name `global_rules`)\n- project: `<cwd>/.windsurf/rules/*.md`\n\nNormalization:\n\n- `globs`: array-of-string or single string\n- `alwaysApply`, `description` cast from frontmatter\n- `ttsr_trigger`: string only\n- `name` from filename for project rules\n\n### Cline provider (`cline.ts`)\n\nSearches upward from `cwd` for nearest `.clinerules`:\n\n- if directory: loads `*.md` inside it\n- if file: loads single file as rule named `clinerules`\n\nNormalization:\n\n- `globs`: array-of-string or single string\n- `alwaysApply`: only if boolean\n- `description`: string only\n- `ttsr_trigger`: string only\n\n## 3. Frontmatter parsing behavior and ambiguity\n\nAll providers use `parseFrontmatter` (`utils/frontmatter.ts`) with these semantics:\n\n1. Frontmatter is parsed only when content starts with `---` and has a closing `\\n---`.\n2. Body is trimmed after frontmatter extraction.\n3. If YAML parse fails:\n   - warning is logged,\n   - parser falls back to simple `key: value` line parsing (`^(\\w+):\\s*(.*)$`).\n\nAmbiguity consequences:\n\n- Fallback parser does not support arrays, nested objects, quoting rules, or hyphenated keys.\n- Fallback values become strings (for example `alwaysApply: true` becomes string `\"true\"`), so providers requiring boolean/string types may drop metadata.\n- `ttsr_trigger` works in fallback (underscore key); keys like `thinking-level` would not.\n- Files without valid frontmatter still load as rules with empty metadata and full content body.\n\n## 4. Provider precedence and deduplication\n\n`loadCapability(\"rules\")` (`capability/index.ts`) merges provider outputs and then deduplicates by `rule.name`.\n\n### Precedence model\n\n- Providers are ordered by priority descending.\n- Equal priority keeps registration order (`cursor` before `windsurf` from `discovery/index.ts`).\n- Dedup is first-wins: first encountered rule name is kept; later same-name items are marked `_shadowed` in `all` and excluded from `items`.\n\nEffective rule provider order is currently:\n\n1. `native` (100)\n2. `cursor` (50)\n3. `windsurf` (50)\n4. `cline` (40)\n\n### Intra-provider ordering caveat\n\nWithin a provider, item order comes from `loadFilesFromDir` glob result ordering plus explicit push order. This is deterministic enough for normal use but not explicitly sorted in code.\n\nNotable source-order differences:\n\n- `native` appends project then user config dirs.\n- `cursor` appends user then project results.\n- `windsurf` appends user `global_rules` first, then project rules.\n- `cline` loads only nearest `.clinerules` source.\n\n## 5. Split into Rulebook, Always-Apply, and TTSR buckets\n\nAfter rule discovery in `createAgentSession` (`sdk.ts`):\n\n1. All discovered rules are scanned.\n2. Rules with `condition` (frontmatter key; `ttsr_trigger` / `ttsrTrigger` accepted as fallback) are registered into `TtsrManager`.\n3. A separate `rulebookRules` list is built with this predicate:\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && !rule.alwaysApply && !!rule.description\n```\n\n4. An `alwaysApplyRules` list is built:\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && rule.alwaysApply === true\n```\n\n### Bucket behavior\n\n- **TTSR bucket**: any rule with `condition` (description not required). Takes priority over other buckets.\n- **Always-apply bucket**: `alwaysApply === true`, not TTSR. Full content injected into system prompt. Resolvable via `rule://`.\n- **Rulebook bucket**: must have description, must not be TTSR, must not be `alwaysApply`. Listed in system prompt by name+description; content read on demand via `rule://`.\n- A rule with both `condition` and `alwaysApply` goes to TTSR only (TTSR takes priority).\n- A rule with both `alwaysApply` and `description` goes to always-apply only (not rulebook).\n\n## 6. How metadata affects runtime surfaces\n\n### `description`\n\n- Required for inclusion in rulebook.\n- Rendered in system prompt `<rules>` block.\n- Missing description means rule is not available via `rule://` and not listed in system prompt rules.\n\n### `globs`\n\n- Carried through on `Rule`.\n- Rendered as `<glob>...</glob>` entries in the system prompt rules block.\n- Exposed in rules UI state (`extensions` mode list).\n- **Not enforced for automatic matching in this pipeline.** There is no runtime glob matcher selecting rules by current file/tool target.\n\n### `alwaysApply`\n\n- Parsed and preserved by providers.\n- Used in UI display (`\"always\"` trigger label in extensions state manager).\n- Used as an exclusion condition from `rulebookRules`.\n- **Full rule content is auto-injected into the system prompt** (before the rulebook rules section).\n- Rule is also addressable via `rule://<name>` for re-reading.\n\n### `ttsr_trigger`\n\n- Mapped to `rule.ttsrTrigger`.\n- If present, rule is routed to TTSR manager, not rulebook.\n\n## 7. System prompt inclusion path\n\n`buildSystemPromptInternal` receives both `rules` (rulebook) and `alwaysApplyRules`.\n\nAlways-apply rules are rendered first, injecting their raw content directly into the prompt.\n\nRulebook rules are rendered in a `# Rules` section with:\n\n- `Read rule://<name> when working in matching domain`\n- Each rule's `name`, `description`, and optional `<glob>` list\n\nThis is advisory/contextual: prompt text asks the model to read applicable rules, but code does not enforce glob applicability.\n\n## 8. `rule://` internal URL behavior\n\n`RuleProtocolHandler` is registered with:\n\n```ts\nnew RuleProtocolHandler({ getRules: () => [...rulebookRules, ...alwaysApplyRules] })\n```\n\nImplications:\n\n- `rule://<name>` resolves against both **rulebookRules** and **alwaysApplyRules**.\n- TTSR-only rules and rules with no description and no `alwaysApply` are not addressable via `rule://`.\n- Resolution is exact name match.\n- Unknown names return error listing available rule names.\n- Returned content is raw `rule.content` (frontmatter stripped), content type `text/markdown`.\n\n## 9. Known partial / non-enforced semantics\n\n1. Provider descriptions mention legacy files (`.cursorrules`, `.windsurfrules`), but current loader code paths do not actually read those files.\n2. `globs` metadata is surfaced to prompt/UI but not enforced by rule selection logic.\n3. Rule selection for `rule://` includes rulebook and always-apply rules, but not TTSR-only rules.\n4. Discovery warnings (`loadCapability(\"rules\").warnings`) are produced but `createAgentSession` does not currently surface/log them in this path.\n",
	"en/extensions/skills.md": "---\ntitle: Skills\ndescription: Skills system for registering, discovering, and invoking specialized capabilities in the coding agent.\nsidebar:\n  order: 3\n  label: Skills\n---\n\n# Skills\n\nSkills are file-backed capability packs discovered at startup and exposed to the model as:\n\n- lightweight metadata in the system prompt (name + description)\n- on-demand content via `read skill://...`\n- optional interactive `/skill:<name>` commands\n\nThis document covers current runtime behavior in `src/extensibility/skills.ts`, `src/discovery/builtin.ts`, `src/internal-urls/skill-protocol.ts`, and `src/discovery/agents-md.ts`.\n\n## What a skill is in this codebase\n\nA discovered skill is represented as:\n\n- `name`\n- `description`\n- `filePath` (the `SKILL.md` path)\n- `baseDir` (skill directory)\n- source metadata (`provider`, `level`, path)\n\nThe runtime only requires `name` and `path` for validity. In practice, matching quality depends on `description` being meaningful.\n\n## Required layout and SKILL.md expectations\n\n### Directory layout\n\nFor provider-based discovery (native/Claude/Codex/Agents/plugin providers), skills are discovered as **one level under `skills/`**:\n\n- `<skills-root>/<skill-name>/SKILL.md`\n\nNested patterns like `<skills-root>/group/<skill>/SKILL.md` are not discovered by provider loaders.\n\nFor `skills.customDirectories`, scanning uses the same non-recursive layout (`*/SKILL.md`).\n\n```text\nProvider-discovered layout (non-recursive under skills/):\n\n<root>/skills/\n  ├─ postgres/\n  │   └─ SKILL.md      ✅ discovered\n  ├─ pdf/\n  │   └─ SKILL.md      ✅ discovered\n  └─ team/\n      └─ internal/\n          └─ SKILL.md  ❌ not discovered by provider loaders\n\nCustom-directory scanning is also non-recursive, so nested paths are ignored unless you point `customDirectories` at that nested parent.\n```\n\n### `SKILL.md` frontmatter\n\nSupported frontmatter fields on the skill type:\n\n- `name?: string`\n- `description?: string`\n- `globs?: string[]`\n- `alwaysApply?: boolean`\n- additional keys are preserved as unknown metadata\n\nCurrent runtime behavior:\n\n- `name` defaults to the skill directory name\n- `description` is required for:\n  - native `.xcsh` provider skill discovery (`requireDescription: true`)\n  - `skills.customDirectories` scans via `scanSkillsFromDir` in `src/discovery/helpers.ts` (non-recursive)\n- non-native providers can load skills without description\n\n## Discovery pipeline\n\n`discoverSkills()` in `src/extensibility/skills.ts` does two passes:\n\n1. **Capability providers** via `loadCapability(\"skills\")`\n2. **Custom directories** via `scanSkillsFromDir(..., { requireDescription: true })` (one-level directory enumeration)\n\nIf `skills.enabled` is `false`, discovery returns no skills.\n\n### Built-in skill providers and precedence\n\nProvider ordering is priority-first (higher wins), then registration order for ties.\n\nCurrent registered skill providers:\n\n1. `native` (priority 100) — `.xcsh` user/project skills via `src/discovery/builtin.ts`\n2. `claude` (priority 80)\n3. priority 70 group (in registration order):\n   - `claude-plugins`\n   - `agents`\n   - `codex`\n\nDedup key is skill name. First item with a given name wins.\n\n### Source toggles and filtering\n\n`discoverSkills()` applies these controls:\n\n- source toggles: `enableCodexUser`, `enableClaudeUser`, `enableClaudeProject`, `enablePiUser`, `enablePiProject`\n- glob filters on skill name:\n  - `ignoredSkills` (exclude)\n  - `includeSkills` (include allowlist; empty means include all)\n\nFilter order is:\n\n1. source enabled\n2. not ignored\n3. included (if include list present)\n\nFor providers other than codex/claude/native (for example `agents`, `claude-plugins`), enablement currently falls back to: enabled if **any** built-in source toggle is enabled.\n\n### Collision and duplicate handling\n\n- Capability dedup already keeps first skill per name (highest-precedence provider)\n- `extensibility/skills.ts` additionally:\n  - de-duplicates identical files by `realpath` (symlink-safe)\n  - emits collision warnings when a later skill name conflicts\n  - keeps the convenience `discoverSkillsFromDir({ dir, source })` API as a thin adapter over `scanSkillsFromDir`\n- Custom-directory skills are merged after provider skills and follow the same collision behavior\n\n## Runtime usage behavior\n\n### System prompt exposure\n\nSystem prompt construction (`src/system-prompt.ts`) uses discovered skills as follows:\n\n- if `read` tool is available:\n  - include discovered skills list in prompt\n- otherwise:\n  - omit discovered list\n\nTask tool subagents receive the session's discovered/provided skills list via normal session creation; there is no per-task skill pinning override.\n\n### Interactive `/skill:<name>` commands\n\nIf `skills.enableSkillCommands` is true, interactive mode registers one slash command per discovered skill.\n\n`/skill:<name> [args]` behavior:\n\n- reads the skill file directly from `filePath`\n- strips frontmatter\n- injects skill body as a follow-up custom message\n- appends metadata (`Skill: <path>`, optional `User: <args>`)\n\n## `skill://` URL behavior\n\n`src/internal-urls/skill-protocol.ts` supports:\n\n- `skill://<name>` → resolves to that skill's `SKILL.md`\n- `skill://<name>/<relative-path>` → resolves inside that skill directory\n\n```text\nskill:// URL resolution\n\nskill://pdf\n  -> <pdf-base>/SKILL.md\n\nskill://pdf/references/tables.md\n  -> <pdf-base>/references/tables.md\n\nGuards:\n- reject absolute paths\n- reject `..` traversal\n- reject any resolved path escaping <pdf-base>\n```\n\nResolution details:\n\n- skill name must match exactly\n- relative paths are URL-decoded\n- absolute paths are rejected\n- path traversal (`..`) is rejected\n- resolved path must remain within `baseDir`\n- missing files return an explicit `File not found` error\n\nContent type:\n\n- `.md` => `text/markdown`\n- everything else => `text/plain`\n\nNo fallback search is performed for missing assets.\n\n## Skills vs XCSH.md, commands, tools, hooks\n\n### Skills vs XCSH.md\n\n- **Skills**: named, optional capability packs selected by task context or explicitly requested\n- **XCSH.md/context files**: persistent instruction files loaded as context-file capability and merged by level/depth rules\n\n`src/discovery/agents-md.ts` specifically walks ancestor directories from `cwd` to discover standalone `XCSH.md` files (up to depth 20), excluding hidden-directory segments.\n\n### Skills vs slash commands\n\n- **Skills**: model-readable knowledge/workflow content\n- **Slash commands**: user-invoked command entry points\n- `/skill:<name>` is a convenience wrapper that injects skill text; it does not change skill discovery semantics\n\n### Skills vs custom tools\n\n- **Skills**: documentation/workflow content loaded through prompt context and `read`\n- **Custom tools**: executable tool APIs callable by the model with schemas and runtime side effects\n\n### Skills vs hooks\n\n- **Skills**: passive content\n- **Hooks**: event-driven runtime interceptors that can block/modify behavior during execution\n\n## Practical authoring guidance tied to discovery logic\n\n- Put each skill in its own directory: `<skills-root>/<skill-name>/SKILL.md`\n- Always include explicit `name` and `description` frontmatter\n- Keep referenced assets under the same skill directory and access with `skill://<name>/...`\n- For nested taxonomy (`team/domain/skill`), point `skills.customDirectories` to the nested parent directory; scanning itself remains non-recursive\n- Avoid duplicate skill names across sources; first match wins by provider precedence\n",
	"en/index.md": "---\ntitle: xcsh Documentation\ndescription: AI-powered development CLI with TypeScript coding agent and Rust native layer for long-lived sessions, MCP support, and platform packaging.\nsidebar:\n  order: 0\n  label: Overview\n---\n\nxcsh is an AI-powered development CLI with a TypeScript coding agent and a\nRust native layer (`pi-natives`). It extends the open-source\n[`badlogic/pi-mono`](https://github.com/badlogic/pi-mono) line with a\nhardened runtime, long-lived sessions with tree navigation and compaction,\na Python IPython tool, full MCP support, a skills system, and platform\npackaging targeting Linux, macOS, and Windows.\n\n## Where to start\n\n- **[F5 XC Contexts](/runtime-tools/context-command)** — connect to F5 Distributed Cloud\n  tenants. Create contexts, switch between them, manage namespaces and credentials.\n- **Configuration** — how xcsh discovers, resolves, and layers configuration.\n- **Runtime & Tools** — the bash / notebook / resolve tool runtimes and the\n  slash-command surface.\n- **Sessions** — append-only entry log, tree navigation, compaction, and the\n  autonomous memory system.\n- **Natives (Rust)** — architecture of the `pi-natives` N-API addon that\n  powers shell / PTY / media / search.\n- **MCP** — configuration, protocol internals, runtime lifecycle, and how to\n  author servers and tools.\n- **Extensions, Skills & Plugins** — authoring, loading, matching rules, the\n  marketplace, and the plugin installer.\n- **Providers & Models** — model configuration, streaming internals, and the\n  Python / IPython runtime.\n- **TUI** — theming, the `/tree` command, and integration hooks for\n  extensions and custom tools.\n\n## How this doc set is organized\n\nEach top-level group in the sidebar maps to a subsystem of the agent. Within\na group, pages run from \"overview\" to \"internals\" so you can stop reading\nwhen you have enough context for the task at hand.\n",
	"en/mcp/mcp-config.md": "---\ntitle: MCP Configuration\ndescription: MCP server configuration, validation, and management for the coding agent runtime.\nsidebar:\n  order: 1\n  label: Configuration\n---\n\n# MCP configuration in OMP\n\nThis guide explains how to add, edit, and validate MCP servers for the OMP coding agent.\n\nSource of truth in code:\n\n- Runtime config types: `packages/coding-agent/src/mcp/types.ts`\n- Config writer: `packages/coding-agent/src/mcp/config-writer.ts`\n- Loader + validation: `packages/coding-agent/src/mcp/config.ts`\n- Standalone `mcp.json` discovery: `packages/coding-agent/src/discovery/mcp-json.ts`\n- Schema: `packages/coding-agent/src/config/mcp-schema.json`\n\n## Preferred config locations\n\nOMP can discover MCP servers from multiple tools (`.claude/`, `.cursor/`, `.vscode/`, `opencode.json`, and more), but for OMP-native configuration you should usually use one of these files:\n\n- Project: `.xcsh/mcp.json`\n- User: `~/.xcsh/mcp.json`\n\nOMP also accepts fallback standalone files in the project root:\n\n- `mcp.json`\n- `.mcp.json`\n\nUse `.xcsh/mcp.json` when you want OMP to own the configuration. Use root `mcp.json` / `.mcp.json` only when you want a portable fallback file that other MCP clients may also read.\n\n## Add a schema reference\n\nAdd this line at the top of the file for editor autocomplete and validation:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {}\n}\n```\n\nOMP now writes this automatically when `/mcp add`, `/mcp enable`, `/mcp disable`, `/mcp reauth`, or other config-writing flows create or update an OMP-managed MCP file.\n\n## File shape\n\nOMP supports this top-level structure:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"server-name\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"some-mcp-server\"]\n    }\n  },\n  \"disabledServers\": [\"server-name\"]\n}\n```\n\nTop-level keys:\n\n- `$schema` — optional JSON Schema URL for tooling\n- `mcpServers` — map of server name to server config\n- `disabledServers` — user-level denylist used to turn off discovered servers by name\n\nServer names must match `^[a-zA-Z0-9_.-]{1,100}$`.\n\n## Supported server fields\n\nShared fields for every transport:\n\n- `enabled?: boolean` — skip this server when `false`\n- `timeout?: number` — connection timeout in milliseconds\n- `auth?: { ... }` — auth metadata used by OMP for OAuth/API-key flows\n- `oauth?: { ... }` — explicit OAuth client settings used during auth/reauth\n\n### `stdio` transport\n\n`stdio` is the default when `type` is omitted.\n\nRequired:\n\n- `command: string`\n\nOptional:\n\n- `type?: \"stdio\"`\n- `args?: string[]`\n- `env?: Record<string, string>`\n- `cwd?: string`\n\nExample:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/Users/alice/projects\",\n        \"/Users/alice/Documents\"\n      ]\n    }\n  }\n}\n```\n\nThis follows the official Filesystem MCP server package (`@modelcontextprotocol/server-filesystem`).\n\n### `http` transport\n\nRequired:\n\n- `type: \"http\"`\n- `url: string`\n\nOptional:\n\n- `headers?: Record<string, string>`\n\nExample:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\nThis matches GitHub's hosted GitHub MCP server endpoint.\n\n### `sse` transport\n\nRequired:\n\n- `type: \"sse\"`\n- `url: string`\n\nOptional:\n\n- `headers?: Record<string, string>`\n\nExample:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"legacy-remote\": {\n      \"type\": \"sse\",\n      \"url\": \"https://example.com/mcp/sse\"\n    }\n  }\n}\n```\n\n`sse` is still supported for compatibility, but the MCP spec now prefers Streamable HTTP (`type: \"http\"`) for new servers.\n\n## Auth fields\n\nOMP understands two auth-related objects.\n\n### `auth`\n\n```json\n{\n  \"type\": \"oauth\" | \"apikey\",\n  \"credentialId\": \"optional-stored-credential-id\",\n  \"tokenUrl\": \"optional-token-endpoint\",\n  \"clientId\": \"optional-client-id\",\n  \"clientSecret\": \"optional-client-secret\"\n}\n```\n\nUse this when OMP should remember how to rehydrate credentials for a server.\n\n### `oauth`\n\n```json\n{\n  \"clientId\": \"...\",\n  \"clientSecret\": \"...\",\n  \"redirectUri\": \"...\",\n  \"callbackPort\": 3334,\n  \"callbackPath\": \"/oauth/callback\"\n}\n```\n\nUse this when the MCP server requires explicit OAuth client settings.\n\nSlack is the clearest current example. Slack's MCP server is hosted at `https://mcp.slack.com/mcp`, uses Streamable HTTP, and requires confidential OAuth with your Slack app's client credentials.\n\nExample:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\nRelevant Slack endpoints from Slack's docs:\n\n- MCP endpoint: `https://mcp.slack.com/mcp`\n- Authorization endpoint: `https://slack.com/oauth/v2_user/authorize`\n- Token endpoint: `https://slack.com/api/oauth.v2.user.access`\n\n## Common copy-paste examples\n\n### Filesystem server via stdio\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/absolute/path/one\",\n        \"/absolute/path/two\"\n      ]\n    }\n  }\n}\n```\n\n### GitHub hosted server via HTTP\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\n### GitHub local server via Docker\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"-i\",\n        \"--rm\",\n        \"-e\",\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\",\n        \"ghcr.io/github/github-mcp-server\"\n      ],\n      \"env\": {\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n      }\n    }\n  }\n}\n```\n\nThis matches GitHub's official local Docker image `ghcr.io/github/github-mcp-server`.\n\n### Slack hosted server via OAuth\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\n## Secrets and variable resolution\n\nThis is the part that usually trips people up.\n\n### In `.xcsh/mcp.json` and `~/.xcsh/mcp.json`\n\nBefore OMP launches a server or makes an HTTP request, it resolves `env` and `headers` values like this:\n\n1. If a value starts with `!`, OMP runs it as a shell command and uses trimmed stdout.\n2. Otherwise OMP first checks whether the value matches an environment variable name.\n3. If that environment variable is not set, OMP uses the string literally.\n\nExamples:\n\n```json\n{\n  \"env\": {\n    \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n  },\n  \"headers\": {\n    \"X-MCP-Insiders\": \"true\"\n  }\n}\n```\n\nThat means this is valid and convenient for local secrets:\n\n- `\"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"` → copy from the current shell environment\n- `\"Authorization\": \"Bearer hardcoded-token\"` → use the literal value\n- `\"Authorization\": \"!printf 'Bearer %s' \\\"$GITHUB_TOKEN\\\"\"` → build the header from a command\n\n### In root `mcp.json` and `.mcp.json`\n\nThe standalone fallback loader also expands `${VAR}` and `${VAR:-default}` inside strings during discovery.\n\nExample:\n\n```json\n{\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\",\n      \"headers\": {\n        \"Authorization\": \"Bearer ${GITHUB_TOKEN}\"\n      }\n    }\n  }\n}\n```\n\nIf you want the least surprising OMP behavior, prefer `.xcsh/mcp.json` and use explicit env/header values.\n\n## `disabledServers`\n\n`disabledServers` is mainly useful in the user config file (`~/.xcsh/mcp.json`) when a server is discovered from some other source and you want OMP to ignore it without editing that other tool's config.\n\nExample:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"disabledServers\": [\"github\", \"slack\"]\n}\n```\n\n## `/mcp add` vs editing JSON directly\n\nUse `/mcp add` when you want guided setup.\n\nUse direct JSON editing when:\n\n- you need a transport or auth option the wizard does not prompt for yet\n- you want to paste a server definition from another MCP client\n- you want schema-backed validation in your editor\n\nAfter editing, use:\n\n- `/mcp reload` to rediscover and reconnect servers in the current session\n- `/mcp list` to see which config file a server came from\n- `/mcp test <name>` to test a single server\n\n## Validation rules OMP enforces\n\nFrom `validateServerConfig()` in `packages/coding-agent/src/mcp/config.ts`:\n\n- `stdio` requires `command`\n- `http` and `sse` require `url`\n- a server cannot set both `command` and `url`\n- unknown `type` values are rejected\n\nPractical implications:\n\n- Omitting `type` means `stdio`\n- If you paste a remote server config and forget `\"type\": \"http\"`, OMP will treat it as `stdio` and complain that `command` is missing\n- `sse` remains valid for compatibility, but new hosted servers should usually be configured as `http`\n\n## Discovery and precedence\n\nOMP does not merge duplicate server definitions across files. Discovery providers are prioritized, and the higher-priority definition wins.\n\nIn practice:\n\n- prefer `.xcsh/mcp.json` or `~/.xcsh/mcp.json` when you want an OMP-specific override\n- keep server names unique across tools when possible\n- use `disabledServers` in the user config when a third-party config keeps reintroducing a server you do not want\n\n## Troubleshooting\n\n### `Server \"name\": stdio server requires \"command\" field`\n\nYou probably omitted `type: \"http\"` on a remote server.\n\n### `Server \"name\": both \"command\" and \"url\" are set`\n\nPick one transport. OMP treats `command` as stdio and `url` as http/sse.\n\n### `/mcp add` worked but the server still does not connect\n\nThe JSON is valid, but the server may still be unreachable. Use `/mcp test <name>` and check whether:\n\n- the binary or Docker image exists\n- required environment variables are set\n- the remote URL is reachable\n- the OAuth or API token is valid\n\n### The server exists in another tool's config but not in OMP\n\nRun `/mcp list`. OMP discovers many third-party MCP files, but project-level loading can also be disabled via the `mcp.enableProjectConfig` setting.\n\n## References\n\n- MCP transport spec: <https://modelcontextprotocol.io/specification/2025-03-26/basic/transports>\n- Filesystem server package: <https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem>\n- GitHub MCP server: <https://github.com/github/github-mcp-server>\n- Slack MCP server docs: <https://docs.slack.dev/ai/slack-mcp-server/>\n",
	"en/mcp/mcp-protocol-transports.md": "---\ntitle: MCP Protocol and Transport Internals\ndescription: MCP protocol implementation with stdio, SSE, and streamable HTTP transport layers.\nsidebar:\n  order: 2\n  label: Protocol & transports\n---\n\n# MCP Protocol and Transport Internals\n\nThis document describes how coding-agent implements MCP JSON-RPC messaging and how protocol concerns are split from transport concerns.\n\n## Scope\n\nCovers:\n\n- JSON-RPC request/response and notification flow\n- Request correlation and lifecycle for stdio and HTTP/SSE transports\n- Timeout and cancellation behavior\n- Error propagation and malformed payload handling\n- Transport selection boundaries (`stdio` vs `http`/`sse`)\n- Which reconnect/retry responsibilities are transport-level vs manager-level\n\nDoes not cover extension authoring UX or command UI.\n\n## Implementation files\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/transports/stdio.ts`](../../packages/coding-agent/src/mcp/transports/stdio.ts)\n- [`src/mcp/transports/http.ts`](../../packages/coding-agent/src/mcp/transports/http.ts)\n- [`src/mcp/transports/index.ts`](../../packages/coding-agent/src/mcp/transports/index.ts)\n- [`src/mcp/json-rpc.ts`](../../packages/coding-agent/src/mcp/json-rpc.ts)\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n\n## Layer boundaries\n\n### Protocol layer (JSON-RPC + MCP methods)\n\n- Message shapes are defined in `types.ts` (`JsonRpcRequest`, `JsonRpcNotification`, `JsonRpcResponse`, `JsonRpcMessage`).\n- MCP client logic (`client.ts`) decides method order and session handshake:\n  1. `initialize` request\n  2. `notifications/initialized` notification\n  3. method calls like `tools/list`, `tools/call`\n\n### Transport layer (`MCPTransport`)\n\n`MCPTransport` abstracts delivery and lifecycle:\n\n- `request(method, params, options?) -> Promise<T>`\n- `notify(method, params?) -> Promise<void>`\n- `close()`\n- `connected`\n- optional callbacks: `onClose`, `onError`, `onNotification`\n\nTransport implementations own framing and I/O details:\n\n- `StdioTransport`: newline-delimited JSON over subprocess stdio\n- `HttpTransport`: JSON-RPC over HTTP POST, with optional SSE responses/listening\n\n### Important current caveat\n\nTransport callbacks (`onClose`, `onError`, `onNotification`) are implemented, but current `MCPClient`/`MCPManager` flows do not wire reconnection logic to these callbacks. Notifications are only consumed if caller registers handlers.\n\n## Transport selection\n\n`client.ts:createTransport()` chooses transport from config:\n\n- `type` omitted or `\"stdio\"` -> `createStdioTransport`\n- `\"http\"` or `\"sse\"` -> `createHttpTransport`\n\n`\"sse\"` is treated as an HTTP transport variant (same class), not a separate transport implementation.\n\n## JSON-RPC message flow and correlation\n\n## Request IDs\n\nEach transport generates per-request IDs (`Math.random` + timestamp string). IDs are transport-local correlation tokens.\n\n## Stdio correlation path\n\n- Outbound request is serialized as one JSON object + `\\n`.\n- `#pendingRequests: Map<id, {resolve,reject}>` stores in-flight requests.\n- Read loop parses JSONL from stdout and calls `#handleMessage`.\n- If inbound message has matching `id`, request resolves/rejects.\n- If inbound message has `method` and no `id`, treated as notification and sent to `onNotification`.\n\nUnknown IDs are ignored (no rejection, no error callback).\n\n## HTTP correlation path\n\n- Outbound request is HTTP `POST` with JSON body and generated `id`.\n- Non-SSE response path: parse one JSON-RPC response and return `result`/throw on `error`.\n- SSE response path (`Content-Type: text/event-stream`): stream events, return first message whose `id` matches expected request ID and has `result` or `error`.\n- SSE messages with `method` and no `id` are treated as notifications.\n\nIf SSE stream ends before matching response, request fails with `No response received for request ID ...`.\n\n## Notifications\n\nClient emits JSON-RPC notifications via `transport.notify(...)`.\n\n- Stdio: writes notification frame to stdin (`jsonrpc`, `method`, optional `params`) plus newline.\n- HTTP: sends POST body without `id`; success accepts `2xx` or `202 Accepted`.\n\nServer-initiated notifications are only surfaced through transport `onNotification`; there is no default global subscriber in manager/client.\n\n## Stdio transport internals\n\n## Lifecycle and state transitions\n\n- Initial: `connected=false`, `process=null`, pending map empty\n- `connect()`:\n  - spawn subprocess with configured command/args/env/cwd\n  - mark connected\n  - start stdout read loop (`readJsonl`)\n  - start stderr loop (read/discard; currently silent)\n- `close()`:\n  - mark disconnected\n  - reject all pending requests (`Transport closed`)\n  - kill subprocess\n  - await read loop shutdown\n  - emit `onClose`\n\nIf read loop exits unexpectedly, `finally` triggers `#handleClose()` which performs the same pending-request rejection and close callback.\n\n## Timeout and cancellation\n\nPer request:\n\n- timeout defaults to `config.timeout ?? 30000`\n- optional `AbortSignal` from caller\n- abort and timeout both reject the pending promise and clean map entry\n\nCancellation is local only: transport does not send protocol-level cancellation notification to the server.\n\n## Malformed payload handling\n\nIn read loop:\n\n- each parsed JSONL line is passed to `#handleMessage` in `try/catch`\n- malformed/invalid message handling exceptions are dropped (`Skip malformed lines` comment)\n- loop continues, so one bad message does not kill the connection\n\nIf the underlying stream parser throws, `onError` is invoked (when still connected), then connection closes.\n\n## Disconnect/failure behavior\n\nWhen process exits or stream closes:\n\n- all in-flight requests are rejected with `Transport closed`\n- no automatic restart or reconnect\n- higher layers must reconnect by creating a new transport\n\n## Backpressure/streaming notes\n\n- Outbound writes use `stdin.write()` + `flush()` without awaiting drain semantics.\n- There is no explicit queue or high-watermark management in transport.\n- Inbound processing is stream-driven (`for await` over `readJsonl`), one parsed message at a time.\n\n## HTTP/SSE transport internals\n\n## Lifecycle and connection semantics\n\nHTTP transport has logical connection state, but request path is stateless per HTTP call:\n\n- `connect()` sets `connected=true` (no socket/session handshake)\n- optional server session tracking via `Mcp-Session-Id` header\n- `close()` optionally sends `DELETE` with `Mcp-Session-Id`, aborts SSE listener, emits `onClose`\n\nSo `connected` means \"transport usable\", not \"persistent stream established\".\n\n## Session header behavior\n\n- On POST response, if `Mcp-Session-Id` header is present, transport stores it.\n- Subsequent requests/notifications include `Mcp-Session-Id`.\n- `close()` tries to terminate server session with HTTP DELETE; termination failures are ignored.\n\n## Timeout and cancellation\n\nFor both `request()` and `notify()`:\n\n- timeout uses `AbortController` (`config.timeout ?? 30000`)\n- external signal, if provided, is merged via `AbortSignal.any([...])`\n- AbortError handling distinguishes caller abort vs timeout\n\nErrors thrown:\n\n- timeout: `Request timeout after ...ms` (or `SSE response timeout ...`, `Notify timeout ...`)\n- caller abort: original AbortError is rethrown when external signal is already aborted\n\n## HTTP error propagation\n\nOn non-OK response:\n\n- response text is included in thrown error (`HTTP <status>: <text>`)\n- if present, auth hints from `WWW-Authenticate` and `Mcp-Auth-Server` are appended\n\nOn JSON-RPC error object:\n\n- throws `MCP error <code>: <message>`\n\nMalformed JSON body (`response.json()` failure) propagates as parse exception.\n\n## SSE behavior and modes\n\nTwo SSE paths exist:\n\n1. **Per-request SSE response** (`#parseSSEResponse`)\n   - used when POST response content type is `text/event-stream`\n   - consumes stream until matching response id found\n   - can process interleaved notifications during same stream\n\n2. **Background SSE listener** (`startSSEListener()`)\n   - optional GET listener for server-initiated notifications\n   - currently not automatically started by MCP manager/client\n   - if GET returns `405`, listener silently disables itself (server does not support this mode)\n\n## Malformed payload and disconnect handling\n\nSSE JSON parsing errors bubble out of `readSseJson` and reject request/listener.\n\n- Request SSE parse errors reject the active request.\n- Background listener errors trigger `onError` (except AbortError).\n- No auto-reconnect for background listener.\n\n## `json-rpc.ts` utility vs transport abstraction\n\n`src/mcp/json-rpc.ts` provides `callMCP()` and `parseSSE()` helpers for direct HTTP MCP calls (used by Exa integration), not the `MCPTransport` abstraction used by `MCPClient`/`MCPManager`.\n\nNotable differences from `HttpTransport`:\n\n- parses entire response text first, then extracts first `data:` line (`parseSSE`), with JSON fallback\n- no request timeout management, no abort API, no session-id handling, no transport lifecycle\n- returns raw JSON-RPC envelope object\n\nThis path is lightweight but less robust than full transport implementation.\n\n## Retry/reconnect responsibilities\n\n## Transport-level\n\nCurrent transport implementations do **not**:\n\n- retry failed requests\n- reconnect after stdio process exit\n- reconnect SSE listeners\n- resend in-flight requests after disconnect\n\nThey fail fast and propagate errors.\n\n## Manager/client-level\n\n`MCPManager` handles discovery/initial connection orchestration and can reconnect only by running connect flows again (`connectToServer`/`discoverAndConnect` paths). It does not auto-heal an already connected transport on runtime failure callbacks.\n\n`MCPManager` does have startup fallback behavior for slow servers (deferred tools from cache), but that is tool availability fallback, not transport retry.\n\n## Failure scenarios summary\n\n- **Malformed stdio message line**: dropped; stream continues.\n- **Stdio stream/process ends**: transport closes; pending requests rejected as `Transport closed`.\n- **HTTP non-2xx**: request/notify throws HTTP error.\n- **Invalid JSON response**: parse exception propagated.\n- **SSE ends without matching id**: request fails with `No response received for request ID ...`.\n- **Timeout**: transport-specific timeout error.\n- **Caller abort**: AbortError/reason propagated from caller signal.\n\n## Practical boundary rule\n\nIf the concern is message shape, id correlation, or MCP method ordering, it belongs to protocol/client logic.\n\nIf the concern is framing (JSONL vs HTTP/SSE), stream parsing, fetch/spawn lifecycle, timeout clocks, or connection teardown, it belongs to transport implementation.\n",
	"en/mcp/mcp-runtime-lifecycle.md": "---\ntitle: MCP Runtime Lifecycle\ndescription: MCP server process lifecycle from initialization through tool registration, health monitoring, and shutdown.\nsidebar:\n  order: 3\n  label: Runtime lifecycle\n---\n\n# MCP runtime lifecycle\n\nThis document describes how MCP servers are discovered, connected, exposed as tools, refreshed, and torn down in the coding-agent runtime.\n\n## Lifecycle at a glance\n\n1. **SDK startup** calls `discoverAndLoadMCPTools()` (unless MCP is disabled).\n2. **Discovery** (`loadAllMCPConfigs`) resolves MCP server configs from capability sources, filters disabled/project/Exa entries, and preserves source metadata.\n3. **Manager connect phase** (`MCPManager.connectServers`) starts per-server connect + `tools/list` in parallel.\n4. **Fast startup gate** waits up to 250ms, then may return:\n   - fully loaded `MCPTool`s,\n   - failures per server,\n   - or cached `DeferredMCPTool`s for still-pending servers.\n5. **SDK wiring** merges MCP tools into runtime tool registry for the session.\n6. **Live session** can refresh MCP tools via `/mcp` flows (`disconnectAll` + rediscover + `session.refreshMCPTools`).\n7. **Teardown** happens when callers invoke `disconnectServer`/`disconnectAll`; manager also clears MCP tool registrations for disconnected servers.\n\n## Discovery and load phase\n\n### Entry path from SDK\n\n`createAgentSession()` in `src/sdk.ts` performs MCP startup when `enableMCP` is true (default):\n\n- calls `discoverAndLoadMCPTools(cwd, { ... })`,\n- passes `authStorage`, cache storage, and `mcp.enableProjectConfig` setting,\n- always sets `filterExa: true`,\n- logs per-server load/connect errors,\n- stores returned manager in `toolSession.mcpManager` and session result.\n\nIf `enableMCP` is false, MCP discovery is skipped entirely.\n\n### Config discovery and filtering\n\n`loadAllMCPConfigs()` (`src/mcp/config.ts`) loads canonical MCP server items through capability discovery, then converts to legacy `MCPServerConfig`.\n\nFiltering behavior:\n\n- `enableProjectConfig: false` removes project-level entries (`_source.level === \"project\"`).\n- `enabled: false` servers are skipped before connect attempts.\n- Exa servers are filtered out by default and API keys are extracted for native Exa tool integration.\n\nResult includes both `configs` and `sources` (metadata used later for provider labeling).\n\n### Discovery-level failure behavior\n\n`discoverAndLoadMCPTools()` distinguishes two failure classes:\n\n- **Discovery hard failure** (exception from `manager.discoverAndConnect`, typically from config discovery): returns an empty tool set and one synthetic error `{ path: \".mcp.json\", error }`.\n- **Per-server runtime/connect failure**: manager returns partial success with `errors` map; other servers continue.\n\nSo startup does not fail the whole agent session when individual MCP servers fail.\n\n## Manager state model\n\n`MCPManager` tracks runtime lifecycle with separate registries:\n\n- `#connections: Map<string, MCPServerConnection>` — fully connected servers.\n- `#pendingConnections: Map<string, Promise<MCPServerConnection>>` — handshake in progress.\n- `#pendingToolLoads: Map<string, Promise<{ connection, serverTools }>>` — connected but tools still loading.\n- `#tools: CustomTool[]` — current MCP tool view exposed to callers.\n- `#sources: Map<string, SourceMeta>` — provider/source metadata even before connect completes.\n\n`getConnectionStatus(name)` derives status from these maps:\n\n- `connected` if in `#connections`,\n- `connecting` if pending connect or pending tool load,\n- `disconnected` otherwise.\n\n## Connection establishment and startup timing\n\n## Per-server connect pipeline\n\nFor each discovered server in `connectServers()`:\n\n1. store/update source metadata,\n2. skip if already connected/pending,\n3. validate transport fields (`validateServerConfig`),\n4. resolve auth/shell substitutions (`#resolveAuthConfig`),\n5. call `connectToServer(name, resolvedConfig)`,\n6. call `listTools(connection)`,\n7. cache tool definitions (`MCPToolCache.set`) best-effort.\n\n`connectToServer()` behavior (`src/mcp/client.ts`):\n\n- creates stdio or HTTP/SSE transport,\n- performs MCP `initialize` + `notifications/initialized`,\n- uses timeout (`config.timeout` or 30s default),\n- closes transport on init failure.\n\n### Fast startup gate + deferred fallback\n\n`connectServers()` waits on a race between:\n\n- all connect/tool-load tasks settled, and\n- `STARTUP_TIMEOUT_MS = 250`.\n\nAfter 250ms:\n\n- fulfilled tasks become live `MCPTool`s,\n- rejected tasks produce per-server errors,\n- still-pending tasks:\n  - use cached tool definitions if available (`MCPToolCache.get`) to create `DeferredMCPTool`s,\n  - otherwise block until those pending tasks settle.\n\nThis is a hybrid startup model: fast return when cache is available, correctness wait when cache is not.\n\n### Background completion behavior\n\nEach pending `toolsPromise` also has a background continuation that eventually:\n\n- replaces that server’s tool slice in manager state via `#replaceServerTools`,\n- writes cache,\n- logs late failures only after startup (`allowBackgroundLogging`).\n\n## Tool exposure and live-session availability\n\n### Startup registration\n\n`discoverAndLoadMCPTools()` converts manager tools into `LoadedCustomTool[]` and decorates paths (`mcp:<server> via <providerName>` when known).\n\n`createAgentSession()` then pushes these tools into `customTools`, which are wrapped and added to the runtime tool registry with names like `mcp_<server>_<tool>`.\n\n### Tool calls\n\n- `MCPTool` calls tools through an already connected `MCPServerConnection`.\n- `DeferredMCPTool` waits for `waitForConnection(server)` before calling; this allows cached tools to exist before connection is ready.\n\nBoth return structured tool output and convert transport/tool errors into `MCP error: ...` tool content (abort remains abort).\n\n## Refresh/reload paths (startup vs live reload)\n\n### Initial startup path\n\n- one-time discovery/load in `sdk.ts`,\n- tools are registered in initial session tool registry.\n\n### Interactive reload path\n\n`/mcp reload` path (`src/modes/controllers/mcp-command-controller.ts`) does:\n\n1. `mcpManager.disconnectAll()`,\n2. `mcpManager.discoverAndConnect()`,\n3. `session.refreshMCPTools(mcpManager.getTools())`.\n\n`session.refreshMCPTools()` (`src/session/agent-session.ts`) removes all `mcp_` tools, re-wraps latest MCP tools, and re-activates tool set so MCP changes apply without restarting session.\n\nThere is also a follow-up path for late connections: after waiting for a specific server, if status becomes `connected`, it re-runs `session.refreshMCPTools(...)` so newly available tools are rebound in-session.\n\n## Health, reconnect, and partial failure behavior\n\nCurrent runtime behavior is intentionally minimal:\n\n- **No autonomous health monitor** in manager/client.\n- **No automatic reconnect loop** when a transport drops.\n- Manager does not subscribe to transport `onClose`/`onError`; status is registry-driven.\n- Reconnect is explicit: reload flow or direct `connectServers()` invocation.\n\nOperationally:\n\n- one server failing does not remove tools from healthy servers,\n- connect/list failures are isolated per server,\n- tool cache and background updates are best-effort (warnings/errors logged, no hard stop).\n\n## Teardown semantics\n\n### Server-level teardown\n\n`disconnectServer(name)`:\n\n- removes pending entries/source metadata,\n- closes transport if connected,\n- removes that server’s `mcp_` tools from manager state.\n\n### Global teardown\n\n`disconnectAll()`:\n\n- closes all active transports with `Promise.allSettled`,\n- clears pending maps, sources, connections, and manager tool list.\n\nIn current wiring, explicit teardown is used in MCP command flows (for reload/remove/disable). There is no separate automatic manager disposal hook in the startup path itself; callers are responsible for invoking manager disconnect methods when they need deterministic MCP shutdown.\n\n## Failure modes and guarantees\n\n| Scenario | Behavior | Hard fail vs best-effort |\n| --- | --- | --- |\n| Discovery throws (capability/config load path) | Loader returns empty tools + synthetic `.mcp.json` error | Best-effort session startup |\n| Invalid server config | Server skipped with validation error entry | Best-effort per server |\n| Connect timeout/init failure | Server error recorded; others continue | Best-effort per server |\n| `tools/list` still pending at startup with cache hit | Deferred tools returned immediately | Best-effort fast startup |\n| `tools/list` still pending at startup without cache | Startup waits for pending to settle | Hard wait for correctness |\n| Late background tool-load failure | Logged after startup gate | Best-effort logging |\n| Runtime dropped transport | No automatic reconnect; future calls fail until reconnect/reload | Best-effort recovery via manual action |\n\n## Public API surface\n\n`src/mcp/index.ts` re-exports loader/manager/client APIs for external callers. `src/sdk.ts` exposes `discoverMCPServers()` as a convenience wrapper returning the same loader result shape.\n\n## Implementation files\n\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts) — loader facade, discovery error normalization, `LoadedCustomTool` conversion.\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts) — lifecycle state registries, parallel connect/list flow, refresh/disconnect.\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts) — transport setup, initialize handshake, list/call/disconnect.\n- [`src/mcp/index.ts`](../../packages/coding-agent/src/mcp/index.ts) — MCP module API exports.\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — startup wiring into session/tool registry.\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts) — config discovery/filtering/validation used by manager.\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts) — `MCPTool` and `DeferredMCPTool` runtime behavior.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — `refreshMCPTools` live rebinding.\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts) — interactive reload/reconnect flows.\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — subagent MCP proxying via parent manager connections.\n",
	"en/mcp/mcp-server-tool-authoring.md": "---\ntitle: MCP Server and Tool Authoring\ndescription: Guide to building custom MCP servers and registering tools for the coding agent.\nsidebar:\n  order: 4\n  label: Server & tool authoring\n---\n\n# MCP server and tool authoring\n\nThis document explains how MCP server definitions become callable `mcp_*` tools in coding-agent, and what operators should expect when configs are invalid, duplicated, disabled, or auth-gated.\n\n## Architecture at a glance\n\n```text\nConfig sources (.xcsh/.claude/.cursor/.vscode/mcp.json, mcp.json, etc.)\n  -> discovery providers normalize to canonical MCPServer\n  -> capability loader dedupes by server name (higher provider priority wins)\n  -> loadAllMCPConfigs converts to MCPServerConfig + skips enabled:false\n  -> MCPManager connects/listTools (with auth/header/env resolution)\n  -> MCPTool/DeferredMCPTool bridge exposes tools as mcp_<server>_<tool>\n  -> AgentSession.refreshMCPTools replaces live MCP tools immediately\n```\n\n## 1) Server config model and validation\n\n`src/mcp/types.ts` defines the authoring shape used by MCP config writers and runtime:\n\n- `stdio` (default when `type` missing): requires `command`, optional `args`, `env`, `cwd`\n- `http`: requires `url`, optional `headers`\n- `sse`: requires `url`, optional `headers` (kept for compatibility)\n- shared fields: `enabled`, `timeout`, `auth`\n\n`validateServerConfig()` (`src/mcp/config.ts`) enforces transport basics:\n\n- rejects configs that set both `command` and `url`\n- requires `command` for stdio\n- requires `url` for http/sse\n- rejects unknown `type`\n\n`config-writer.ts` applies this validation for add/update operations and also validates server names:\n\n- non-empty\n- max 100 chars\n- only `[a-zA-Z0-9_.-]`\n\n### Transport pitfalls\n\n- `type` omitted means stdio. If you intended HTTP/SSE but omitted `type`, `command` becomes mandatory.\n- `sse` is still accepted but treated as HTTP transport internally (`createHttpTransport`).\n- Validation is structural, not reachability: a syntactically valid URL can still fail at connect time.\n\n## 2) Discovery, normalization, and precedence\n\n### Capability-based discovery\n\n`loadAllMCPConfigs()` (`src/mcp/config.ts`) loads canonical `MCPServer` items via `loadCapability(mcpCapability.id)`.\n\nThe capability layer (`src/capability/index.ts`) then:\n\n1. loads providers in priority order\n2. dedupes by `server.name` (first win = highest priority)\n3. validates deduped items\n\nResult: duplicate server names across sources are not merged. One definition wins; lower-priority duplicates are shadowed.\n\n### `.mcp.json` and related files\n\nThe dedicated fallback provider in `src/discovery/mcp-json.ts` reads project-root `mcp.json` and `.mcp.json` (low priority).\n\nIn practice MCP servers also come from higher-priority providers (for example native `.xcsh/...` and tool-specific config dirs). Authoring guidance:\n\n- Prefer `.xcsh/mcp.json` (project) or `~/.xcsh/mcp.json` (user) for explicit control.\n- Use root `mcp.json` / `.mcp.json` when you need fallback compatibility.\n- Reusing the same server name in multiple sources causes precedence shadowing, not merge.\n\n### Normalization behavior\n\n`convertToLegacyConfig()` (`src/mcp/config.ts`) maps canonical `MCPServer` to runtime `MCPServerConfig`.\n\nKey behavior:\n\n- transport inferred as `server.transport ?? (command ? \"stdio\" : url ? \"http\" : \"stdio\")`\n- disabled servers (`enabled === false`) are dropped before connection\n- optional fields are preserved when present\n\n### Environment expansion during discovery\n\n`mcp-json.ts` expands env placeholders in string fields with `expandEnvVarsDeep()`:\n\n- supports `${VAR}` and `${VAR:-default}`\n- unresolved values remain literal `${VAR}` strings\n\n`mcp-json.ts` also performs runtime type checks for user JSON and logs warnings for invalid `enabled`/`timeout` values instead of hard-failing the whole file.\n\n## 3) Auth and runtime value resolution\n\n`MCPManager.prepareConfig()`/`#resolveAuthConfig()` (`src/mcp/manager.ts`) is the final pre-connect pass.\n\n### OAuth credential injection\n\nIf config has:\n\n```ts\nauth: { type: \"oauth\", credentialId: \"...\" }\n```\n\nand credential exists in auth storage:\n\n- `http`/`sse`: injects `Authorization: Bearer <access_token>` header\n- `stdio`: injects `OAUTH_ACCESS_TOKEN` env var\n\nIf credential lookup fails, manager logs a warning and continues with unresolved auth.\n\n### Header/env value resolution\n\nBefore connect, manager resolves each header/env value via `resolveConfigValue()` (`src/config/resolve-config-value.ts`):\n\n- value starting with `!` => execute shell command, use trimmed stdout (cached)\n- otherwise, treat value as environment variable name first (`process.env[name]`), fallback to literal value\n- unresolved command/env values are omitted from final headers/env map\n\nOperational caveat: this means a mistyped secret command/env key can silently remove that header/env entry, producing downstream 401/403 or server startup failures.\n\n## 4) Tool bridge: MCP -> agent-callable tools\n\n`src/mcp/tool-bridge.ts` converts MCP tool definitions into `CustomTool`s.\n\n### Naming and collision domain\n\nTool names are generated as:\n\n```text\nmcp_<sanitized_server_name>_<sanitized_tool_name>\n```\n\nRules:\n\n- lowercases\n- non-`[a-z_]` chars become `_`\n- repeated underscores collapse\n- redundant `<server>_` prefix in tool name is stripped once\n\nThis avoids many collisions, but not all. Different raw names can still sanitize to the same identifier (for example `my-server` and `my.server` both sanitize similarly), and registry insertion is last-write-wins.\n\n### Schema mapping\n\n`convertSchema()` keeps MCP JSON Schema mostly as-is but patches object schemas missing `properties` with `{}` for provider compatibility.\n\n### Execution mapping\n\n`MCPTool.execute()` / `DeferredMCPTool.execute()`:\n\n- calls MCP `tools/call`\n- flattens MCP content into displayable text\n- returns structured details (`serverName`, `mcpToolName`, provider metadata)\n- maps server-reported `isError` to `Error: ...` text result\n- maps thrown transport/runtime failures to `MCP error: ...`\n- preserves abort semantics by translating AbortError into `ToolAbortError`\n\n## 5) Operator lifecycle: add/edit/remove and live updates\n\nInteractive mode exposes `/mcp` in `src/modes/controllers/mcp-command-controller.ts`.\n\nSupported operations:\n\n- `add` (wizard or quick-add)\n- `remove` / `rm`\n- `enable` / `disable`\n- `test`\n- `reauth` / `unauth`\n- `reload`\n\nConfig writes are atomic (`writeMCPConfigFile`: temp file + rename).\n\nAfter changes, controller calls `#reloadMCP()`:\n\n1. `mcpManager.disconnectAll()`\n2. `mcpManager.discoverAndConnect()`\n3. `session.refreshMCPTools(mcpManager.getTools())`\n\n`refreshMCPTools()` replaces all `mcp_` registry entries and immediately re-activates the latest MCP tool set, so changes take effect without restarting the session.\n\n### Mode differences\n\n- **Interactive/TUI mode**: `/mcp` gives in-app UX (wizard, OAuth flow, connection status text, immediate runtime rebinding).\n- **SDK/headless integration**: `discoverAndLoadMCPTools()` (`src/mcp/loader.ts`) returns loaded tools + per-server errors; no `/mcp` command UX.\n\n## 6) User-visible error surfaces\n\nCommon error strings users/operators see:\n\n- add/update validation failures:\n  - `Invalid server config: ...`\n  - `Server \"<name>\" already exists in <path>`\n- quick-add argument issues:\n  - `Use either --url or -- <command...>, not both.`\n  - `--token requires --url (HTTP/SSE transport).`\n- connect/test failures:\n  - `Failed to connect to \"<name>\": <message>`\n  - timeout help text suggests increasing timeout\n  - auth help text for `401/403`\n- auth/OAuth flows:\n  - `Authentication required ... OAuth endpoints could not be discovered`\n  - `OAuth flow timed out. Please try again.`\n  - `OAuth authentication failed: ...`\n- disabled server usage:\n  - `Server \"<name>\" is disabled. Run /mcp enable <name> first.`\n\nBad source JSON in discovery is generally handled as warnings/logs; config-writer paths throw explicit errors.\n\n## 7) Practical authoring guidance\n\nFor robust MCP authoring in this codebase:\n\n1. Keep server names globally unique across all MCP-capable config sources.\n2. Prefer alphanumeric/underscore names to avoid sanitized-name collisions in generated `mcp_*` tool names.\n3. Use explicit `type` to avoid accidental stdio defaults.\n4. Treat `enabled: false` as hard-off: server is omitted from runtime connect set.\n5. For OAuth configs, store a valid `credentialId`; otherwise auth injection is skipped.\n6. If using command-based secret resolution (`!cmd`), verify command output is stable and non-empty.\n\n## Implementation files\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts)\n- [`src/mcp/config-writer.ts`](../../packages/coding-agent/src/mcp/config-writer.ts)\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts)\n- [`src/discovery/mcp-json.ts`](../../packages/coding-agent/src/discovery/mcp-json.ts)\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/config/resolve-config-value.ts`](../../packages/coding-agent/src/config/resolve-config-value.ts)\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts)\n",
	"en/natives/natives-addon-loader-runtime.md": "---\ntitle: Natives Addon Loader Runtime\ndescription: N-API addon loader runtime with platform detection, fallback strategies, and module resolution.\nsidebar:\n  order: 3\n  label: Addon loader\n---\n\n# Natives Addon Loader Runtime\n\nThis document deep-dives the addon loading/validation layer in `@f5-sales-demo/pi-natives`: how `native.ts` decides which `.node` file to load, when embedded payload extraction runs, and how startup failures are reported.\n\n## Implementation files\n\n- `packages/natives/src/native.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/package.json`\n\n## Scope and responsibility\n\nLoader/runtime responsibilities are intentionally narrow:\n\n- Build a platform/CPU-aware candidate list for addon filenames and directories.\n- Optionally materialize an embedded addon into a versioned per-user cache directory.\n- Attempt candidates in deterministic order.\n- Reject stale or incompatible addons via `validateNative` before exposing bindings.\n\nOut of scope here: module-specific grep/text/highlight behavior.\n\n## Runtime inputs and derived state\n\nAt module initialization (`export const native = loadNative();`), `native.ts` computes static context:\n\n- **Platform tag**: ``${process.platform}-${process.arch}`` (for example `darwin-arm64`).\n- **Package version**: from `packages/natives/package.json` (`version` field).\n- **Core directories**:\n  - `nativeDir`: package-local `packages/natives/native`.\n  - `execDir`: directory containing `process.execPath`.\n  - `versionedDir`: `<getNativesDir()>/<packageVersion>`.\n  - `userDataDir` fallback:\n    - Windows: `%LOCALAPPDATA%/xcsh` (or `%USERPROFILE%/AppData/Local/xcsh`).\n    - Non-Windows: `~/.local/bin`.\n- **Compiled-binary mode** (`isCompiledBinary`): true if any of:\n  - `PI_COMPILED` env var is set, or\n  - `import.meta.url` contains Bun-embedded markers (`$bunfs`, `~BUN`, `%7EBUN`).\n- **Variant override**: `PI_NATIVE_VARIANT` (`modern`/`baseline` only; invalid values ignored).\n- **Selected variant**: explicit override, otherwise runtime AVX2 detection on x64 (`modern` if AVX2, else `baseline`).\n\n## Platform support and tag resolution\n\n`SUPPORTED_PLATFORMS` is fixed to:\n\n- `linux-x64`\n- `linux-arm64`\n- `darwin-x64`\n- `darwin-arm64`\n- `win32-x64`\n\nBehavior detail:\n\n- Unsupported platforms are not rejected up-front.\n- Loader still tries all computed candidates first.\n- If nothing loads, it throws an explicit unsupported-platform error listing supported tags.\n\nThis preserves useful diagnostics for near-miss cases while still failing hard for truly unsupported targets.\n\n## Variant selection (`modern` / `baseline` / default)\n\n### x64 behavior\n\n1. If `PI_NATIVE_VARIANT` is `modern` or `baseline`, that value wins.\n2. Else detect AVX2 support:\n   - Linux: scan `/proc/cpuinfo` for `avx2`.\n   - macOS: query `sysctl` (`machdep.cpu.leaf7_features`, fallback `machdep.cpu.features`).\n   - Windows: run PowerShell `[System.Runtime.Intrinsics.X86.Avx2]::IsSupported`.\n3. Result:\n   - AVX2 available -> `modern`\n   - AVX2 unavailable/undetectable -> `baseline`\n\n### Non-x64 behavior\n\n- No variant is used; loader stays on the default filename (`pi_natives.<platform>-<arch>.node`).\n\n### Filename construction\n\nGiven `tag = <platform>-<arch>`:\n\n- Non-x64 or no variant: `pi_natives.<tag>.node`\n- x64 + `modern`: try in order\n  1. `pi_natives.<tag>-modern.node`\n  2. `pi_natives.<tag>-baseline.node` (intentional fallback)\n- x64 + `baseline`: only `pi_natives.<tag>-baseline.node`\n\nThe `addonLabel` used in final error messages is either `<tag>` or `<tag> (<variant>)`.\n\n## Candidate path construction and fallback ordering\n\n`native.ts` builds candidate pools before any `require(...)` call.\n\n### Release candidates\n\nBuilt from variant-resolved filename list and searched in this order:\n\n- **Non-compiled runtime**:\n  1. `<nativeDir>/<filename>`\n  2. `<execDir>/<filename>`\n\n- **Compiled runtime** (`PI_COMPILED` or Bun embedded markers):\n  1. `<versionedDir>/<filename>`\n  2. `<userDataDir>/<filename>`\n  3. `<nativeDir>/<filename>`\n  4. `<execDir>/<filename>`\n\n`dedupedCandidates` removes duplicates while preserving first occurrence order.\n\n### Final runtime sequence\n\nAt load time:\n\n1. Optional embedded extraction candidate (if produced) is inserted at the front.\n2. Remaining deduplicated candidates are tried in order.\n3. First candidate that both `require(...)`s and passes `validateNative(...)` wins.\n\n## Embedded addon extraction lifecycle\n\n`embedded-addon.ts` defines a generated manifest shape:\n\n- `platformTag`\n- `version`\n- `files[]` where each entry has `variant`, `filename`, `filePath`\n\nCurrent checked-in default is `embeddedAddon: null`; compiled artifacts may replace this with real metadata.\n\n### Extraction state machine\n\nExtraction (`maybeExtractEmbeddedAddon`) runs only when all gates pass:\n\n1. `isCompiledBinary === true`\n2. `embeddedAddon !== null`\n3. `embeddedAddon.platformTag === platformTag`\n4. `embeddedAddon.version === packageVersion`\n5. A variant-appropriate embedded file is found\n\nVariant file selection mirrors runtime variant intent:\n\n- Non-x64: prefer `default`, then first available file.\n- x64 + `modern`: prefer `modern`, fallback to `baseline`.\n- x64 + `baseline`: require `baseline`.\n\nMaterialization behavior:\n\n1. Ensure `<versionedDir>` exists (`mkdirSync(..., { recursive: true })`).\n2. If `<versionedDir>/<selected filename>` already exists, reuse it (no rewrite).\n3. Else read embedded source `filePath` and write target file.\n4. Return target path for highest-priority load attempt.\n\nOn failure, extraction does not crash immediately; it appends an error entry (directory creation or write failure) and loader proceeds to normal candidate probing.\n\n## Lifecycle and state transitions\n\n```text\nInit\n  -> Compute platform/version/variant/candidate lists\n  -> (Compiled + embedded manifest matches?)\n       yes -> Try extract embedded to versionedDir (record errors, continue)\n       no  -> Skip extraction\n  -> For each runtime candidate in order:\n       require(candidate)\n       -> success: validateNative\n            -> pass: return bindings (READY)\n            -> fail: record error, continue\n       -> failure: record error, continue\n  -> none loaded:\n       if unsupported platform tag -> throw Unsupported platform\n       else -> throw Failed to load (full tried-path diagnostics + hints)\n```\n\n## `validateNative` contract checks\n\n`validateNative(bindings, source)` enforces a function-only contract over `NativeBindings` at startup.\n\nMechanics:\n\n- For each required export name, it checks `typeof bindings[name] === \"function\"`.\n- Missing names are aggregated.\n- If any are missing, loader throws:\n  - source addon path,\n  - missing export list,\n  - rebuild command hint.\n\nThis is a hard compatibility gate against stale binaries, partial builds, and symbol/name drift.\n\n### JS API ↔ native export mapping (validation gate)\n\n| JS binding name checked in `validateNative` | Expected native export name |\n| --- | --- |\n| `grep` | `grep` |\n| `glob` | `glob` |\n| `highlightCode` | `highlightCode` |\n| `executeShell` | `executeShell` |\n| `PtySession` | `PtySession` |\n| `Shell` | `Shell` |\n| `visibleWidth` | `visibleWidth` |\n| `getSystemInfo` | `getSystemInfo` |\n| `getWorkProfile` | `getWorkProfile` |\n| `invalidateFsScanCache` | `invalidateFsScanCache` |\n\nNote: `bindings.ts` declares only the base `cancelWork(id)` member; module `types.ts` files declaration-merge additional symbols that `validateNative` enforces.\n\n## Failure behavior and diagnostics\n\n## Unsupported platform\n\nIf all candidates fail and `platformTag` is not in `SUPPORTED_PLATFORMS`, loader throws:\n\n- `Unsupported platform: <tag>`\n- Full supported-platform list\n- Explicit issue-reporting guidance\n\n## Stale binary / mismatch symptoms\n\nTypical stale mismatch signal:\n\n- `Native addon missing exports (<candidate>). Missing: ...`\n\nCommon causes:\n\n- Old `.node` binary from previous package version/API shape.\n- Wrong variant artifact selected (for x64).\n- New Rust export not present in loaded artifact.\n\nLoader behavior:\n\n- Records per-candidate missing-export failures.\n- Continues probing remaining candidates.\n- If no candidate validates, final error includes every attempted path with each failure message.\n\n## Compiled-binary startup failures\n\nIn compiled mode final diagnostics include:\n\n- expected versioned cache target paths (`<versionedDir>/<filename>`),\n- remediation to delete stale `<versionedDir>` and rerun,\n- direct release download `curl` commands for each expected filename.\n\n## Non-compiled startup failures\n\nIn normal package/runtime mode final diagnostics include:\n\n- reinstall hint (`bun install @f5-sales-demo/pi-natives`),\n- local rebuild command (`bun --cwd=packages/natives run build`),\n- optional x64 variant build hint (`TARGET_VARIANT=baseline|modern ...`).\n\n## Runtime behavior\n\n- The loader always uses the release candidate chain.\n- Setting `PI_DEV` only enables per-candidate console diagnostics (`Loaded native addon...` and load errors).\n",
	"en/natives/natives-architecture.md": "---\ntitle: Natives Architecture\ndescription: Rust N-API native addon architecture bridging TypeScript and platform-specific operations.\nsidebar:\n  order: 1\n  label: Architecture\n---\n\n# Natives Architecture\n\n`@f5-sales-demo/pi-natives` is a three-layer stack:\n\n1. **TypeScript wrapper/API layer** exposes stable JS/TS entrypoints.\n2. **Addon loading/validation layer** resolves and validates the `.node` binary for the current runtime.\n3. **Rust N-API module layer** implements performance-critical primitives exported to JS.\n\nThis document is the foundation for deeper module-level docs.\n\n## Implementation files\n\n- `packages/natives/src/index.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `crates/pi-natives/src/lib.rs`\n\n## Layer 1: TypeScript wrapper/API layer\n\n`packages/natives/src/index.ts` is the public barrel. It groups exports by capability domain and re-exports typed wrappers rather than exposing raw N-API bindings directly.\n\nCurrent top-level groups:\n\n- **Search/text primitives**: `grep`, `glob`, `text`, `highlight`\n- **Execution/process/terminal primitives**: `shell`, `pty`, `ps`, `keys`\n- **System/media/conversion primitives**: `image`, `html`, `clipboard`, `system-info`, `work`\n\n`packages/natives/src/bindings.ts` defines the base interface contract:\n\n- `NativeBindings` starts with shared members (`cancelWork(id: number)`)\n- module-specific bindings are added by declaration merging from each module’s `types.ts`\n- `Cancellable` standardizes timeout and abort-signal options for wrappers that expose cancellation\n\n**Guaranteed contract (API-facing):** consumers import from `@f5-sales-demo/pi-natives` and use typed wrappers.\n\n**Implementation detail (may change):** declaration merging and internal wrapper layout (`src/<module>/index.ts`, `src/<module>/types.ts`).\n\n## Layer 2: Addon loading and validation\n\n`packages/natives/src/native.ts` owns runtime addon selection, optional extraction, and export validation.\n\n### Candidate resolution model\n\n- Platform tag is `\"${process.platform}-${process.arch}\"`.\n- Supported tags are currently:\n  - `linux-x64`\n  - `linux-arm64`\n  - `darwin-x64`\n  - `darwin-arm64`\n  - `win32-x64`\n- x64 can use CPU variants:\n  - `modern` (AVX2-capable)\n  - `baseline` (fallback)\n- Non-x64 uses the default filename (no variant suffix).\n\nFilename strategy:\n\n- Release: `pi_natives.<platform>-<arch>.node`\n- x64 variant release: `pi_natives.<platform>-<arch>-modern.node` and/or `...-baseline.node`\n- `PI_DEV` enables loader diagnostics but does not change addon filenames\n\n### Platform-specific variant detection\n\nFor x64, variant selection uses:\n\n- **Linux**: `/proc/cpuinfo`\n- **macOS**: `sysctl machdep.cpu.leaf7_features` / `machdep.cpu.features`\n- **Windows**: PowerShell check for `System.Runtime.Intrinsics.X86.Avx2`\n\n`PI_NATIVE_VARIANT` can explicitly force `modern` or `baseline`.\n\n### Binary distribution and extraction model\n\n`packages/natives/package.json` includes both `src` and `native` in published files. The `native/` directory stores prebuilt platform artifacts.\n\nFor compiled binaries (`PI_COMPILED` or Bun embedded runtime markers), loader behavior is:\n\n1. Check versioned user cache path: `<getNativesDir()>/<packageVersion>/...`\n2. Check legacy compiled-binary location:\n   - Windows: `%LOCALAPPDATA%/xcsh` (fallback `%USERPROFILE%/AppData/Local/xcsh`)\n   - non-Windows: `~/.local/bin`\n3. Fall back to packaged `native/` and executable directory candidates\n\nIf an embedded addon manifest is present (`embedded-addon.ts` generated by `scripts/embed-native.ts`), `native.ts` can materialize the matching embedded binary into the versioned cache directory before loading.\n\n### Validation and failure modes\n\nAfter `require(candidate)`, `validateNative(...)` verifies required exports (for example `grep`, `glob`, `highlightCode`, `PtySession`, `Shell`, `getSystemInfo`, `getWorkProfile`, `invalidateFsScanCache`).\n\nFailure paths are explicit:\n\n- **Unsupported platform tag**: throws with supported platform list\n- **No loadable candidate**: throws with all attempted paths and remediation hints\n- **Missing exports**: throws with exact missing names and rebuild command\n- **Embedded extraction errors**: records directory/write failures and includes them in final load diagnostics\n\n**Guaranteed contract (API-facing):** addon load either succeeds with a validated binding set or fails fast with actionable error text.\n\n**Implementation detail (may change):** exact candidate search order and compiled-binary fallback path ordering.\n\n## Layer 3: Rust N-API module layer\n\n`crates/pi-natives/src/lib.rs` is the Rust entry module that declares exported module ownership:\n\n- `clipboard`\n- `fd`\n- `fs_cache`\n- `glob`\n- `glob_util`\n- `grep`\n- `highlight`\n- `html`\n- `image`\n- `keys`\n- `prof`\n- `ps`\n- `pty`\n- `shell`\n- `system_info`\n- `task`\n- `text`\n\nThese modules implement the N-API symbols consumed and validated by `native.ts`. JS-level names are surfaced through the TS wrappers in `packages/natives/src`.\n\n**Guaranteed contract (API-facing):** Rust module exports must match the binding names expected by `validateNative` and wrapper modules.\n\n**Implementation detail (may change):** internal Rust module decomposition and helper module boundaries (`glob_util`, `task`, etc.).\n\n## Ownership boundaries\n\nAt architecture level, ownership is split as follows:\n\n- **TS wrapper/API ownership (`packages/natives/src`)**\n  - public API grouping, option typing, and stable JS ergonomics\n  - cancellation surface (`timeoutMs`, `AbortSignal`) exposed to callers\n- **Loader ownership (`packages/natives/src/native.ts`)**\n  - runtime binary selection\n  - CPU variant selection and override handling\n  - compiled-binary extraction and candidate probing\n  - hard validation of required native exports\n- **Rust ownership (`crates/pi-natives/src`)**\n  - algorithmic and system-level implementation\n  - platform-native behavior and performance-sensitive logic\n  - N-API symbol implementation that TS wrappers consume\n\n## Runtime flow (high level)\n\n1. Consumer imports from `@f5-sales-demo/pi-natives`.\n2. Wrapper module calls into singleton `native` binding.\n3. `native.ts` selects candidate binary for platform/arch/variant.\n4. Optional embedded binary extraction occurs for compiled distributions.\n5. Addon is loaded and export set is validated.\n6. Wrapper returns typed results to caller.\n\n## Glossary\n\n- **Native addon**: A `.node` binary loaded via Node-API (N-API).\n- **Platform tag**: Runtime tuple `platform-arch` (for example `darwin-arm64`).\n- **Variant**: x64 CPU-specific build flavor (`modern` AVX2, `baseline` fallback).\n- **Wrapper**: TS function/class that provides typed API over raw native exports.\n- **Declaration merging**: TS technique used by module `types.ts` files to extend `NativeBindings`.\n- **Compiled binary mode**: Runtime mode where the CLI is bundled and native addons are resolved from extracted/cache paths instead of only package-local paths.\n- **Embedded addon**: Build artifact metadata and file references generated into `embedded-addon.ts` so compiled binaries can extract matching `.node` payloads.\n- **Validation gate**: `validateNative(...)` check that rejects stale/mismatched binaries missing required exports.\n",
	"en/natives/natives-binding-contract.md": "---\ntitle: Natives Binding Contract (TypeScript Side)\ndescription: TypeScript-side binding contract for calling into Rust native functions via N-API.\nsidebar:\n  order: 2\n  label: Binding contract\n---\n\n# Natives Binding Contract (TypeScript Side)\n\nThis document defines the TypeScript-side contract that sits between `@f5-sales-demo/pi-natives` callers and the loaded N-API addon.\n\nIt focuses on three pieces:\n\n1. contract shape (`NativeBindings` + module augmentation),\n2. wrapper behavior (`src/<module>/index.ts`),\n3. public export surface (`src/index.ts`).\n\n## Implementation files\n\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/system-info/types.ts`\n- `packages/natives/src/system-info/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/work/types.ts`\n- `packages/natives/src/work/index.ts`\n\n## Contract model\n\n`packages/natives/src/bindings.ts` defines the base contract:\n\n- `NativeBindings` (base interface, currently includes `cancelWork(id: number): void`)\n- `Cancellable` (`timeoutMs?: number`, `signal?: AbortSignal`)\n- `TsFunc<T>` callback shape used by N-API threadsafe callbacks\n\nEach module adds its own fields by declaration merging:\n\n```ts\n// packages/natives/src/<module>/types.ts\ndeclare module \"../bindings\" {\n interface NativeBindings {\n  grep(options: GrepOptions, onMatch?: TsFunc<GrepMatch>): Promise<GrepResult>;\n }\n}\n```\n\nThis keeps one aggregate binding interface without a monolithic central type file.\n\n## Declaration-merging lifecycle and state transitions\n\n### 1) Compile-time type assembly\n\n- `bindings.ts` provides the base `NativeBindings` symbol.\n- Every `src/<module>/types.ts` augments `NativeBindings`.\n- `src/native.ts` imports all `./<module>/types` files for side effects so the merged contract is in scope where `NativeBindings` is used.\n\nState transition: **Base contract** → **Merged contract**.\n\n### 2) Runtime addon load and validation gate\n\n- `src/native.ts` loads candidate `.node` binaries.\n- Loaded object is treated as `NativeBindings` and immediately passed through `validateNative(...)`.\n- `validateNative` verifies required export keys by `typeof bindings[name] === \"function\"`.\n\nState transition: **Untrusted addon object** → **Validated native binding object** (or hard failure).\n\n### 3) Wrapper invocation\n\n- Module wrappers in `src/<module>/index.ts` call `native.<export>`.\n- Wrappers adapt defaults and callback shape (`(err, value)` to value-only callback patterns in JS APIs).\n- `src/index.ts` re-exports module wrappers/types as the public package API.\n\nState transition: **Validated raw bindings** → **Ergonomic public API**.\n\n## Wrapper responsibilities\n\nWrappers are intentionally thin; they do not re-implement native logic.\n\nPrimary responsibilities:\n\n- **Argument normalization/defaulting**\n  - `glob()` resolves `options.path` to absolute path and defaults `hidden`, `gitignore`, `recursive`.\n  - `hasMatch()` fills default flags (`ignoreCase`, `multiline`) before native call.\n- **Callback adaptation**\n  - `grep()`, `glob()`, `executeShell()` convert `TsFunc<T>` (`error, value`) into user callback receiving only successful values.\n- **Environment or policy behavior around native calls**\n  - Clipboard wrapper adds OSC52/Termux/headless handling and treats copy as best effort.\n- **Public naming and re-export curation**\n  - `searchContent()` maps to native export `search`.\n\n## Public export surface organization\n\n`packages/natives/src/index.ts` is the canonical public barrel. It groups exports by capability domain:\n\n- Search/text: `grep`, `glob`, `text`, `highlight`\n- Execution/process/terminal: `shell`, `pty`, `ps`, `keys`\n- System/media/conversion: `image`, `html`, `clipboard`, `system-info`, `work`\n\nMaintainer rule: if a wrapper is not re-exported from `src/index.ts`, it is not part of the intended public package surface.\n\n## JS API ↔ native export mapping (representative)\n\nThe Rust side uses N-API export names (typically from `#[napi]` snake_case -> camelCase conversion, with occasional explicit aliases) that must match these binding keys.\n\n| Category | Public JS API (wrapper) | Native binding key | Return type | Async? |\n|---|---|---|---|---|\n| Grep | `grep(options, onMatch?)` | `grep` | `Promise<GrepResult>` | Yes |\n| Grep | `searchContent(content, options)` | `search` | `SearchResult` | No |\n| Grep | `hasMatch(content, pattern, opts?)` | `hasMatch` | `boolean` | No |\n| Grep | `fuzzyFind(options)` | `fuzzyFind` | `Promise<FuzzyFindResult>` | Yes |\n| Glob | `glob(options, onMatch?)` | `glob` | `Promise<GlobResult>` | Yes |\n| Glob | `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `void` | No |\n| Shell | `executeShell(options, onChunk?)` | `executeShell` | `Promise<ShellExecuteResult>` | Yes |\n| Shell | `Shell` | `Shell` | class constructor | N/A |\n| PTY | `PtySession` | `PtySession` | class constructor | N/A |\n| Text | `truncateToWidth(...)` | `truncateToWidth` | `string` | No |\n| Text | `sliceWithWidth(...)` | `sliceWithWidth` | `SliceWithWidthResult` | No |\n| Text | `visibleWidth(text)` | `visibleWidth` | `number` | No |\n| Highlight | `highlightCode(code, lang, colors)` | `highlightCode` | `string` | No |\n| HTML | `htmlToMarkdown(html, options?)` | `htmlToMarkdown` | `Promise<string>` | Yes |\n| System | `getSystemInfo()` | `getSystemInfo` | `SystemInfo` | No |\n| Work | `getWorkProfile(lastSeconds)` | `getWorkProfile` | `WorkProfile` | No |\n| Process | `killTree(pid, signal)` | `killTree` | `number` | No |\n| Process | `listDescendants(pid)` | `listDescendants` | `number[]` | No |\n| Clipboard | `copyToClipboard(text)` | `copyToClipboard` | `Promise<void>` (best effort wrapper behavior) | Yes |\n| Clipboard | `readImageFromClipboard()` | `readImageFromClipboard` | `Promise<ClipboardImage \\| null>` | Yes |\n| Keys | `parseKey(data, kittyProtocolActive)` | `parseKey` | `string \\| null` | No |\n\n## Sync vs async contract differences\n\nThe contract mixes sync and async APIs; wrappers preserve native call style rather than forcing one model:\n\n- **Promise-based async exports** for I/O or long-running work (`grep`, `glob`, `htmlToMarkdown`, `executeShell`, clipboard, image operations).\n- **Synchronous exports** for deterministic in-memory transforms/parsers (`search`, `hasMatch`, highlighting, text width/slicing, key parsing, process queries).\n- **Constructor exports** for stateful runtime objects (`Shell`, `PtySession`, `PhotonImage`).\n\nImplication for maintainers: changing sync ↔ async for an existing export is a breaking API and contract change across wrappers and callers.\n\n## Object and enum typing patterns\n\n### Object patterns (`#[napi(object)]`-style JS objects)\n\nTS models object-shaped native values as interfaces, for example:\n\n- `GrepResult`, `SearchResult`, `GlobResult`\n- `SystemInfo`, `WorkProfile`\n- `ClipboardImage`, `ParsedKittyResult`\n\nThese are structural contracts at compile time; runtime shape correctness is owned by native implementation.\n\n### Enum patterns\n\nNumeric native enums are represented as `const enum` values in TS:\n\n- `FileType` (`1=file`, `2=dir`, `3=symlink`)\n- `ImageFormat` (`0=PNG`, `1=JPEG`, `2=WEBP`, `3=GIF`)\n- `SamplingFilter`, `Ellipsis`, `KeyEventType`\n\nCallers see named enum members; the binding boundary passes numbers.\n\n## How mismatches are caught\n\nMismatch detection happens at two layers:\n\n1. **Compile-time TypeScript contract checks**\n   - Wrappers call `native.<name>` against merged `NativeBindings`.\n   - Missing/renamed binding keys break TS type-checking in wrappers.\n\n2. **Runtime validation in `validateNative`**\n   - After load, `native.ts` checks required exports and throws if any are missing.\n   - Error message includes missing keys and rebuild instruction.\n\nThis catches the common stale-binary drift: wrapper/type exists but loaded `.node` lacks the export.\n\n## Failure behavior and caveats\n\n### Load/validation failures (hard failures)\n\n- Addon load failure or unsupported platform throws during module init in `native.ts`.\n- Missing required exports throws before wrappers are usable.\n\nEffect: package fails fast rather than deferring failure to first call.\n\n### Wrapper-level behavior differences\n\n- Some wrappers intentionally soften failures (`copyToClipboard` is best effort and swallows native failure).\n- Streaming callbacks ignore callback error payloads and only forward successful value events.\n\n### Type-level caveats (runtime stricter than TS)\n\n- TS optional fields do not guarantee semantic validity; native layer can still reject malformed values.\n- `const enum` typing does not prevent out-of-range numeric values from untyped callers at runtime.\n- `validateNative` checks only presence/function-ness of required exports, not deep argument/return-shape compatibility.\n- `bindings.ts` includes `cancelWork(id)` in the base interface, but current runtime validation list does not enforce that key.\n\n## Maintainer checklist for binding changes\n\nWhen adding/changing an export, update all of:\n\n1. `src/<module>/types.ts` (augmentation + contract types)\n2. `src/<module>/index.ts` (wrapper behavior)\n3. `src/native.ts` imports for the module types (if new module)\n4. `validateNative` required export checks\n5. `src/index.ts` public re-exports\n\nSkipping any step creates either compile-time drift or runtime load-time failure.\n",
	"en/natives/natives-build-release-debugging.md": "---\ntitle: Natives Build, Release, and Debugging Runbook\ndescription: Build, release, and debugging runbook for the Rust native addon across platforms.\nsidebar:\n  order: 8\n  label: Build, release & debugging\n---\n\n# Natives Build, Release, and Debugging Runbook\n\nThis runbook describes how the `@f5-sales-demo/pi-natives` build pipeline produces `.node` addons, how compiled distributions load them, and how to debug loader/build failures.\n\nIt follows the architecture terms from `docs/natives-architecture.md`:\n\n- **build-time artifact production** (`scripts/build-native.ts`)\n- **embedded addon manifest generation** (`scripts/embed-native.ts`)\n- **runtime addon loading + validation gate** (`src/native.ts`)\n\n## Implementation files\n\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `packages/natives/src/native.ts`\n- `crates/pi-natives/Cargo.toml`\n\n## Build pipeline overview\n\n### 1) Build entrypoints\n\n`packages/natives/package.json` scripts:\n\n- `bun scripts/build-native.ts` (`build`) → release build\n- `bun scripts/build-native.ts --dev` (`dev:native`) → debug/dev profile build (same output naming)\n- `bun scripts/embed-native.ts` (`embed:native`) → generate `src/embedded-addon.ts` from built files\n\n### 2) Rust artifact build\n\n`build-native.ts` runs Cargo in `crates/pi-natives`:\n\n- base command: `cargo build`\n- release mode adds `--release` unless `--dev` is passed\n- cross target adds `--target <CROSS_TARGET>`\n\n`crates/pi-natives/Cargo.toml` declares `crate-type = [\"cdylib\"]`, so Cargo emits a shared library (`.so`/`.dylib`/`.dll`) that is then copied/renamed to a `.node` addon filename.\n\n### 3) Artifact discovery and install\n\nAfter Cargo completes, `build-native.ts` scans candidate output directories in order:\n\n1. `${CARGO_TARGET_DIR}` (if set)\n2. `<repo>/target`\n3. `crates/pi-natives/target`\n\nFor each root it checks profile directories:\n\n- cross build: `<root>/<crossTarget>/<profile>` then `<root>/<profile>`\n- native build: `<root>/<profile>`\n\nThen it looks for one of:\n\n- `libpi_natives.so`\n- `libpi_natives.dylib`\n- `pi_natives.dll`\n- `libpi_natives.dll`\n\nWhen found, it atomically installs into `packages/natives/native/` with temp-file + rename semantics (Windows fallback handles locked DLL replacement failures explicitly).\n\n## Target/variant model and naming conventions\n\n## Platform tag\n\nBoth build and runtime use platform tag:\n\n`<platform>-<arch>` (example: `darwin-arm64`, `linux-x64`)\n\n## Variant model (x64 only)\n\nx64 supports CPU variants:\n\n- `modern` (AVX2-capable path)\n- `baseline` (fallback)\n\nNon-x64 uses a single default artifact (no variant suffix).\n\n### Output filenames\n\nRelease builds:\n\n- x64: `pi_natives.<platform>-<arch>-modern.node` or `...-baseline.node`\n- non-x64: `pi_natives.<platform>-<arch>.node`\n\nDev build (`--dev`):\n\n- Uses debug profile flags but keeps standard platform-tagged output naming\n\nRuntime loader candidate order in `native.ts`:\n\n- release candidates\n- compiled mode prepends extracted/cache candidates before package-local files\n\n## Environment flags and build options\n\n## Runtime flags\n\n- `PI_DEV` (loader behavior): enable loader diagnostics\n- `PI_NATIVE_VARIANT` (loader behavior, x64 only): force `modern` or `baseline` selection at runtime\n- `PI_COMPILED` (loader behavior): enable compiled-binary candidate/extraction behavior\n\n## Build-time flags/options\n\n- `--dev` (script arg): build debug profile\n- `CROSS_TARGET`: passed to Cargo `--target`\n- `TARGET_PLATFORM`: override output platform tag naming\n- `TARGET_ARCH`: override output arch naming\n- `TARGET_VARIANT` (x64 only): force `modern` or `baseline` for output filename and RUSTFLAGS policy\n- `CARGO_TARGET_DIR`: additional root when searching Cargo outputs\n- `RUSTFLAGS`:\n  - if unset and not cross-compiling, script sets:\n    - modern: `-C target-cpu=x86-64-v3`\n    - baseline: `-C target-cpu=x86-64-v2`\n    - non-x64 / no variant: `-C target-cpu=native`\n  - if already set, script does not override\n\n## Build state/lifecycle transitions\n\n### Build lifecycle (`build-native.ts`)\n\n1. **Init**: parse args/env (`--dev`, target overrides, cross flags)\n2. **Variant resolve**:\n   - non-x64 → no variant\n   - x64 + `TARGET_VARIANT` → explicit variant\n   - x64 cross-build without `TARGET_VARIANT` → hard error\n   - x64 local build without override → detect host AVX2\n3. **Compile**: run Cargo with resolved profile/target\n4. **Locate artifact**: scan target roots/profile dirs/library names\n5. **Install**: copy + atomic rename into `packages/natives/native`\n6. **Complete**: output addon ready for loader candidates\n\nFailure exits happen at any stage with explicit error text (invalid variant, failed cargo build, missing output library, install/rename failure).\n\n### Embed lifecycle (`embed-native.ts`)\n\n1. **Init**: compute platform tag from `TARGET_PLATFORM`/`TARGET_ARCH` or host values\n2. **Candidate set**:\n   - x64 expects both `modern` and `baseline`\n   - non-x64 expects one default file\n3. **Validate availability** in `packages/natives/native`\n4. **Generate manifest** (`src/embedded-addon.ts`) with Bun `file` imports and package version\n5. **Runtime extraction ready** for compiled mode\n\n`--reset` bypasses validation and writes a null manifest stub (`embeddedAddon = null`).\n\n## Dev workflow vs shipped/compiled behavior\n\n## Local development workflow\n\nTypical local loop:\n\n1. Build addon:\n   - release: `bun --cwd=packages/natives run build`\n   - debug profile: `bun --cwd=packages/natives run dev:native`\n2. Set `PI_DEV=1` when testing loader diagnostics\n3. Loader in `native.ts` resolves package-local `native/` (and executable-dir fallback) candidates\n4. `validateNative` enforces export compatibility before wrappers use the binding\n\n## Shipped/compiled binary workflow\n\nIn compiled mode (`PI_COMPILED` or Bun embedded markers):\n\n1. Loader computes versioned cache dir: `<getNativesDir()>/<packageVersion>` (operationally `~/.xcsh/natives/<version>`)\n2. If embedded manifest matches current platform+version, loader may extract selected embedded file into that versioned dir\n3. Runtime candidate order includes:\n   - versioned cache dir\n   - legacy compiled-binary dir (`%LOCALAPPDATA%/xcsh` on Windows, `~/.local/bin` elsewhere)\n   - package/executable directories\n4. First successfully loaded addon still must pass `validateNative`\n\nThis is why packaging + runtime loader expectations must align: filenames, platform tags, and exported symbols must match what `native.ts` probes and validates.\n\n## JS API ↔ Rust export mapping (validation gate subset)\n\n`native.ts` requires these JS-visible exports to exist on the loaded addon. They map to Rust N-API exports in `crates/pi-natives/src`:\n\n| JS name required by `validateNative` | Rust export declaration | Rust source file |\n| --- | --- | --- |\n| `glob` | `#[napi] pub fn glob(...)` | `crates/pi-natives/src/glob.rs` |\n| `grep` | `#[napi] pub fn grep(...)` | `crates/pi-natives/src/grep.rs` |\n| `search` | `#[napi] pub fn search(...)` | `crates/pi-natives/src/grep.rs` |\n| `highlightCode` | `#[napi] pub fn highlight_code(...)` | `crates/pi-natives/src/highlight.rs` |\n| `getSystemInfo` | `#[napi] pub fn get_system_info(...)` | `crates/pi-natives/src/system_info.rs` |\n| `getWorkProfile` | `#[napi] pub fn get_work_profile(...)` (camel-cased export) | `crates/pi-natives/src/prof.rs` |\n| `invalidateFsScanCache` | `#[napi] pub fn invalidate_fs_scan_cache(...)` | `crates/pi-natives/src/fs_cache.rs` |\n\nIf any required symbol is missing, loader fails fast with a rebuild hint.\n\n## Failure behavior and diagnostics\n\n## Build-time failures\n\n- Invalid variant configuration:\n  - `TARGET_VARIANT` set on non-x64 → immediate error\n  - x64 cross-build without explicit `TARGET_VARIANT` → immediate error\n- Cargo build failure:\n  - script surfaces non-zero exit and stderr\n- Artifact not found:\n  - script prints every checked profile directory\n- Install failure:\n  - explicit message; Windows includes locked-file hint\n\n## Runtime loader failures (`native.ts`)\n\n- Unsupported platform tag:\n  - throws with supported platform list\n- No candidate could load:\n  - throws with full candidate error list and mode-specific remediation hints\n- Missing exports:\n  - throws with exact missing symbol names and rebuild command\n- Embedded extraction problems:\n  - extraction mkdir/write errors recorded and included in final diagnostics\n\n## Troubleshooting matrix\n\n| Symptom | Likely cause | Verify | Fix |\n| --- | --- | --- | --- |\n| `Native addon missing exports ... Missing: <name>` | Stale `.node` binary, Rust export name mismatch, or wrong binary loaded | Run with `PI_DEV=1` to see loaded path; inspect export list for that file | Rebuild `build`; ensure Rust `#[napi]` export name (or explicit alias when needed) matches JS key; remove stale cached/versioned files |\n| x64 machine loads baseline when modern expected | `PI_NATIVE_VARIANT=baseline`, no AVX2 detected, or only baseline file present | Check `PI_NATIVE_VARIANT`; inspect `native/` for `-modern` file | Build modern variant (`TARGET_VARIANT=modern ... build`) and ensure file is shipped |\n| Cross-build produces unusable/wrong-labeled binary | Mismatch between `CROSS_TARGET` and `TARGET_PLATFORM`/`TARGET_ARCH`, or missing `TARGET_VARIANT` for x64 | Confirm env tuple and output filename | Re-run with consistent env values and explicit x64 `TARGET_VARIANT` |\n| Compiled binary fails after upgrade | Stale extracted cache (`~/.xcsh/natives/<old-or-mismatched-version>`) or embedded manifest mismatch | Inspect versioned natives dir and loader error list | Delete versioned natives cache for the package version and rerun; regenerate embedded manifest during packaging |\n| Loader probes many paths and none work | Platform mismatch or missing release artifact in package `native/` | Check `platformTag` vs actual filename(s) | Ensure built filename exactly matches `pi_natives.<platform>-<arch>(-variant).node` convention and package includes `native/` |\n| `embed:native` fails with \"Incomplete native addons\" | Required variant files not built before embedding | Check expected vs found list in error text | Build required files first (x64: both modern+baseline; non-x64: default), then rerun `embed:native` |\n\n## Operational commands\n\n```bash\n# Release artifact for current host\nbun --cwd=packages/natives run build\n\n# Debug profile artifact build\nbun --cwd=packages/natives run dev:native\n\n# Build explicit x64 variants\nTARGET_VARIANT=modern bun --cwd=packages/natives run build\nTARGET_VARIANT=baseline bun --cwd=packages/natives run build\n\n# Generate embedded addon manifest from built native files\nbun --cwd=packages/natives run embed:native\n\n# Reset embedded manifest to null stub\nbun --cwd=packages/natives run embed:native -- --reset\n```\n",
	"en/natives/natives-media-system-utils.md": "---\ntitle: Natives Media and System Utilities\ndescription: Native media processing utilities for screenshots, image handling, and system information.\nsidebar:\n  order: 7\n  label: Media & system utils\n---\n\n# Natives media + system utilities\n\nThis document is a subsystem deep-dive for the **system/media/conversion primitives** layer described in [`docs/natives-architecture.md`](./natives-architecture.md): `image`, `html`, `clipboard`, and `work` profiling.\n\n## Implementation files\n\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/prof.rs`\n- `crates/pi-natives/src/task.rs`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/work/index.ts`\n- `packages/natives/src/work/types.ts`\n\n> Note: there is no `crates/pi-natives/src/work.rs`; work profiling is implemented in `prof.rs` and fed by instrumentation in `task.rs`.\n\n## TS API ↔ Rust export/module mapping\n\n| TS export (packages/natives)                | Rust N-API export                                                       | Rust module                           |\n| ------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------- |\n| `PhotonImage.parse(bytes)`                  | `PhotonImage::parse`                                                     | `image.rs`                            |\n| `PhotonImage#resize(width, height, filter)` | `PhotonImage::resize`                                                    | `image.rs`                            |\n| `PhotonImage#encode(format, quality)`       | `PhotonImage::encode`                                                    | `image.rs`                            |\n| `htmlToMarkdown(html, options)`             | `html_to_markdown`                                                       | `html.rs`                             |\n| `copyToClipboard(text)`                     | `copy_to_clipboard` + TS fallback logic                                  | `clipboard.rs` + `clipboard/index.ts` |\n| `readImageFromClipboard()`                  | `read_image_from_clipboard`                                              | `clipboard.rs`                        |\n| `getWorkProfile(lastSeconds)`               | `get_work_profile`                                                      | `prof.rs`                             |\n\n## Data format boundaries and conversions\n\n### Image (`image`)\n\n- **JS input boundary**: `Uint8Array` encoded image bytes.\n- **Rust decode boundary**: bytes are copied to `Vec<u8>`, format is guessed with `ImageReader::with_guessed_format()`, then decoded to `DynamicImage`.\n- **In-memory state**: `PhotonImage` stores `Arc<DynamicImage>`.\n- **Output boundary**: `encode(format, quality)` returns `Promise<Uint8Array>` (Rust `Vec<u8>`).\n\nFormat IDs are numeric:\n\n- `0`: PNG\n- `1`: JPEG\n- `2`: WebP (lossless encoder)\n- `3`: GIF\n\nConstraints:\n\n- `quality` is only used for JPEG.\n- PNG/WebP/GIF ignore `quality`.\n- Unsupported format IDs fail (`Invalid image format: <id>`).\n\n### HTML conversion (`html`)\n\n- **JS input boundary**: HTML `string` + optional object `{ cleanContent?: boolean; skipImages?: boolean }`.\n- **Rust conversion boundary**: `String` input is converted by `html_to_markdown_rs::convert`.\n- **Output boundary**: Markdown `string`.\n\nConversion behavior:\n\n- `cleanContent` defaults to `false`.\n- When `cleanContent=true`, preprocessing is enabled with `PreprocessingPreset::Aggressive` and hard-removal flags for navigation/forms.\n- `skipImages` defaults to `false`.\n\n### Clipboard (`clipboard`)\n\n- **Text path**:\n  - TS first emits OSC 52 (`\\x1b]52;c;<base64>\\x07`) when stdout is a TTY.\n  - Same text is then attempted via native clipboard API (`native.copyToClipboard`) as best-effort.\n  - On Termux, TS attempts `termux-clipboard-set` first.\n- **Image read path**:\n  - Rust reads raw image from `arboard`.\n  - Rust re-encodes it to PNG bytes (`image` crate), returns `{ data: Uint8Array, mimeType: \"image/png\" }`.\n  - TS returns `null` early on Termux or Linux sessions without display server (`DISPLAY`/`WAYLAND_DISPLAY` missing).\n\n### Work profiling (`work`)\n\n- **Collection boundary**: profiling samples are produced by `profile_region(tag)` guards in `task::blocking` and `task::future`.\n- **Storage format**: fixed-size circular buffer (`MAX_SAMPLES = 10_000`) storing stack path + duration (`μs`) + timestamp (`μs since process start`).\n- **Output boundary**: `getWorkProfile(lastSeconds)` returns object:\n  - `folded`: folded-stack text (flamegraph input)\n  - `summary`: markdown table summary\n  - `svg`: optional flamegraph SVG\n  - `totalMs`, `sampleCount`\n\n## Lifecycle and state transitions\n\n### Image lifecycle\n\n1. `PhotonImage.parse(bytes)` schedules a blocking decode task (`image.decode`).\n2. On success, a native `PhotonImage` handle exists in JS.\n3. `resize(...)` creates a new native handle (`image.resize`), old and new handles can coexist.\n4. `encode(...)` materializes bytes (`image.encode`) without mutating image dimensions.\n\nFailure transitions:\n\n- Format detection/decode failure rejects parse promise.\n- Encode failure rejects encode promise.\n- Invalid format ID rejects encode promise.\n\n### HTML lifecycle\n\n1. `htmlToMarkdown(html, options)` schedules a blocking conversion task.\n2. Conversion runs with defaulted options (`cleanContent=false`, `skipImages=false`) unless specified.\n3. Returns markdown string or rejects.\n\nFailure transitions:\n\n- Converter failure returns rejected promise (`Conversion error: ...`).\n\n### Clipboard lifecycle\n\n`copyToClipboard(text)` is intentionally best-effort and multi-path:\n\n1. If TTY: attempt OSC 52 write (base64 payload).\n2. Try Termux command when `TERMUX_VERSION` is set.\n3. Try native `arboard` text copy.\n4. Swallow errors at TS layer.\n\n`readImageFromClipboard()` strictness differs by stage:\n\n1. TS hard-gates unsupported runtime contexts (Termux/headless Linux) to `null`.\n2. Rust `arboard` read runs only when TS allows it.\n3. `ContentNotAvailable` maps to `null`.\n4. Other Rust errors reject.\n\n### Work profiling lifecycle\n\n1. No explicit start: profiling is always on when task helpers execute.\n2. Every instrumented task scope records one sample on guard drop.\n3. Samples overwrite oldest entries after buffer capacity is reached.\n4. `getWorkProfile(lastSeconds)` reads a time window and derives folded/summary/svg artifacts.\n\nFailure transitions:\n\n- SVG generation failure is soft-fail (`svg: null`), while folded and summary still return.\n- Empty sample window returns empty folded data and `svg: null`, not an error.\n\n## Unsupported operations and error propagation\n\n### Image\n\n- Unsupported decode input or corrupted bytes: strict failure (promise rejection).\n- Unsupported encode format ID: strict failure.\n- No best-effort fallback path in TS wrapper.\n\n### HTML\n\n- Conversion errors are strict failures (rejection).\n- Option omission is best-effort defaulting, not failure.\n\n### Clipboard\n\n- Text copy is best-effort at TS layer: operational failures are suppressed.\n- Image read distinguishes \"no image\" (`null`) from operational failure (rejection).\n- Termux/headless Linux are treated as unsupported contexts for image read (`null`).\n\n### Work profiling\n\n- Retrieval is strict for function call itself, but artifact generation is partially best-effort (`svg` nullable).\n- Buffer truncation is expected behavior (ring buffer), not data loss bug.\n\n## Platform caveats\n\n- **Clipboard text**: OSC 52 depends on terminal support; native clipboard access depends on desktop environment/session.\n- **Clipboard image read**: blocked in TS for Termux and Linux without display server.\n",
	"en/natives/natives-rust-task-cancellation.md": "---\ntitle: Native Rust Task Execution and Cancellation\ndescription: Rust async task execution model with cooperative cancellation and cleanup semantics.\nsidebar:\n  order: 5\n  label: Task cancellation\n---\n\n# Native Rust task execution and cancellation (`pi-natives`)\n\nThis document describes how `crates/pi-natives` schedules native work and how cancellation flows from JS options (`timeoutMs`, `AbortSignal`) to Rust execution.\n\n## Implementation files\n\n- `crates/pi-natives/src/task.rs`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/fd.rs`\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/ps.rs`\n\n## Core primitives (`task.rs`)\n\n`task.rs` defines three core pieces:\n\n1. `task::blocking(tag, cancel_token, work)`\n   - Wraps `napi::AsyncTask` / `Task`.\n   - `compute()` runs on libuv worker threads (for CPU-bound or blocking/sync system calls).\n   - Returns a JS `Promise<T>`.\n\n2. `task::future(env, tag, work)`\n   - Wraps `env.spawn_future(...)`.\n   - Runs async work on Tokio runtime.\n   - Returns `PromiseRaw<'env, T>`.\n\n3. `CancelToken` / `AbortToken` / `AbortReason`\n   - `CancelToken::new(timeout_ms, signal)` combines deadline + optional `AbortSignal`.\n   - `CancelToken::heartbeat()` is cooperative cancellation for blocking loops.\n   - `CancelToken::wait()` is async cancellation wait (`Signal` / `Timeout` / `User` Ctrl-C).\n   - `AbortToken` lets external code request abort (`abort(reason)`).\n\n## `blocking` vs `future`: execution model and selection\n\n### Use `task::blocking`\n\nUse when work is CPU-heavy or fundamentally synchronous/blocking:\n\n- regex/file scanning (`grep`, `glob`, `fuzzy_find`)\n- synchronous PTY loop internals (`run_pty_sync` via `spawn_blocking`)\n- clipboard/image/html conversions\n\nBehavior:\n\n- Work closure receives a cloned `CancelToken`.\n- Cancellation is only observed where code checks `ct.heartbeat()?`.\n- Closure `Err(...)` rejects JS promise.\n\n### Use `task::future`\n\nUse when work must `await` async operations:\n\n- shell session orchestration (`shell.run`, `executeShell`)\n- task racing (`tokio::select!`) between completion and cancellation\n\nBehavior:\n\n- Future can race normal completion against `ct.wait()`.\n- On cancel path, async implementations typically propagate cancellation to inner subsystems (e.g., `tokio_util::CancellationToken`) and optionally force abort on grace timeout.\n\n## JS API ↔ Rust export mapping (task/cancel relevant)\n\n| JS-facing API | Rust export (`#[napi]`) | Scheduler | Cancellation hookup |\n|---|---|---|---|\n| `grep(options, onMatch?)` | `grep` | `task::blocking(\"grep\", ct, ...)` | `CancelToken::new(options.timeoutMs, options.signal)` + `ct.heartbeat()` |\n| `glob(options, onMatch?)` | `glob` | `task::blocking(\"glob\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` in filter loop |\n| `fuzzyFind(options)` | `fuzzy_find` | `task::blocking(\"fuzzy_find\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` in scoring loop |\n| `shell.run(options, onChunk?)` | `Shell::run` | `task::future(env, \"shell.run\", ...)` | `ct.wait()` raced against run task; bridges to Tokio `CancellationToken` |\n| `executeShell(options, onChunk?)` | `execute_shell` | `task::future(env, \"shell.execute\", ...)` | same as above |\n| `pty.start(options, onChunk?)` | `PtySession::start` | `task::future(env, \"pty.start\", ...)` + inner `spawn_blocking` | `CancelToken` checked in sync PTY loop via `heartbeat()` |\n| `htmlToMarkdown(html, options?)` | `html_to_markdown` | `task::blocking(\"html_to_markdown\", (), ...)` | none (`()` token) |\n| `PhotonImage.parse/encode/resize` | `PhotonImage::{parse,encode,resize}` | `task::blocking(...)` | none (`()` token) |\n| `copyToClipboard/readImageFromClipboard` | `copy_to_clipboard` / `read_image_from_clipboard` | `task::blocking(...)` | none (`()` token) |\n\n`text.rs` and `ps.rs` currently do not use `task::blocking`/`task::future` and therefore do not participate in this cancellation path.\n\n## Cancellation lifecycle and state transitions\n\n### `CancelToken` lifecycle\n\n`CancelToken` is cooperative and stateful:\n\n```text\nCreated\n  ├─ no signal + no timeout  -> passive token (never aborts unless externally emplaced)\n  ├─ signal registered        -> waits for AbortSignal callback\n  └─ deadline set             -> timeout check becomes active\n\nRunning\n  ├─ heartbeat()/wait() sees signal   -> AbortReason::Signal\n  ├─ heartbeat()/wait() sees deadline -> AbortReason::Timeout\n  ├─ wait() sees Ctrl-C               -> AbortReason::User\n  └─ no abort                         -> continue\n\nAborted (terminal)\n  └─ first abort reason wins (atomic flag + notifier)\n```\n\n### Before-start vs mid-execution cancellation\n\n- **Before start / before first cancellation check**:\n  - `task::future` users that race on `ct.wait()` can resolve cancel immediately once they enter `select!`.\n  - `task::blocking` users only observe cancellation when closure code reaches `heartbeat()`. If closure does not heartbeat early, cancellation is delayed.\n\n- **Mid-execution**:\n  - `blocking`: next `heartbeat()` returns `Err(\"Aborted: ...\")`.\n  - `future`: `ct.wait()` branch wins `select!`, then code cancels subordinate async machinery (for shell: cancels Tokio token, waits up to 2s, then aborts task).\n\n## Heartbeat expectations for long-running loops\n\n`heartbeat()` must run at predictable cadence in loops with unbounded or large work sets.\n\nObserved patterns:\n\n- `glob::filter_entries`: check each entry before filtering/matching.\n- `fd::score_entries`: check each scanned candidate.\n- `grep_sync`: explicit cancellation check before heavy search phase, plus fs-cache calls that also receive token.\n- `run_pty_sync`: check every loop tick (~16ms sleep cadence) and kill child on cancellation.\n\nPractical rule: no loop over external-size input should exceed a short bounded interval without a heartbeat.\n\n## Failure behavior and error propagation to JS\n\n### Blocking tasks\n\nError path:\n\n1. Closure returns `Err(napi::Error)` (including `heartbeat()` abort).\n2. `Task::compute()` returns `Err`.\n3. `AsyncTask` rejects JS promise.\n\nTypical error strings:\n\n- `Aborted: Timeout`\n- `Aborted: Signal`\n- domain errors (`Failed to decode image: ...`, `Conversion error: ...`, etc.)\n\n### Future tasks\n\nError path:\n\n1. Async body returns `Err(napi::Error)` or join failure is mapped (`... task failed: {err}`).\n2. `task::future`-spawned promise rejects.\n3. Some APIs intentionally return structured cancellation results instead of rejection (`ShellRunResult`/`ShellExecuteResult` with `cancelled`/`timed_out` flags and `exit_code: None`).\n\n### Cancellation reporting split\n\n- **Abort as error**: most blocking exports using `heartbeat()?`.\n- **Abort as typed result**: shell/pty style command APIs that model cancellation in result structs.\n\nChoose one model per API and document it explicitly.\n\n## Common pitfalls\n\n1. **Missing heartbeat in blocking loops**\n   - Symptom: timeout/signal appears ignored until loop ends.\n   - Fix: add `ct.heartbeat()?` at loop top and before expensive per-item steps.\n\n2. **Long uncancelable sections**\n   - Symptom: cancellation latency spikes during single large call (decode, sort, compression, etc.).\n   - Fix: split work into chunks with heartbeat boundaries; if impossible, document latency.\n\n3. **Blocking async executor**\n   - Symptom: async API stalls when sync-heavy code runs directly in future.\n   - Fix: move CPU/sync blocks to `task::blocking` or `tokio::task::spawn_blocking`.\n\n4. **Inconsistent cancel semantics**\n   - Symptom: one API rejects on cancel, another resolves with flags, confusing callers.\n   - Fix: standardize per domain and keep wrapper docs aligned.\n\n5. **Forgetting cancellation bridge in nested async tasks**\n   - Symptom: outer token is cancelled but inner readers/subprocess tasks keep running.\n   - Fix: bridge cancellation to inner token/signal and enforce grace timeout + forced abort fallback.\n\n## Checklist for new cancellable exports\n\n1. Classify work correctly:\n   - CPU-bound or sync blocking -> `task::blocking`\n   - async I/O / `await` orchestration -> `task::future`\n\n2. Expose cancel inputs when needed:\n   - include `timeoutMs` and `signal` in `#[napi(object)]` options\n   - create `let ct = task::CancelToken::new(timeout_ms, signal);`\n\n3. Wire cancellation through all layers:\n   - blocking loops: `ct.heartbeat()?` at stable intervals\n   - async orchestration: race with `ct.wait()` and cancel sub-tasks/tokens\n\n4. Decide cancellation contract:\n   - reject promise with abort error, or\n   - resolve typed `{ cancelled, timedOut, ... }`\n   - keep this contract consistent for the API family\n\n5. Propagate failures with context:\n   - map errors via `Error::from_reason(format!(\"...: {err}\"))`\n   - include stage-specific prefixes (`spawn`, `decode`, `wait`, etc.)\n\n6. Handle before-start and mid-flight cancellation:\n   - cancellation check/await must happen before expensive body and during long execution\n\n7. Validate no executor misuse:\n   - no long sync work directly inside async futures without `spawn_blocking`/blocking task wrapper\n",
	"en/natives/natives-shell-pty-process.md": "---\ntitle: Natives Shell, PTY, Process, and Key Internals\ndescription: Shell execution, PTY management, process lifecycle, and key event handling in the native layer.\nsidebar:\n  order: 4\n  label: Shell, PTY & process\n---\n\n# Natives Shell, PTY, Process, and Key Internals\n\nThis document covers the **execution/process/terminal primitives** in `@f5-sales-demo/pi-natives`: `shell`, `pty`, `ps`, and `keys`, using the architecture terms from `docs/natives-architecture.md`.\n\n## Implementation files\n\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/shell/windows.rs` (Windows only)\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/ps.rs`\n- `crates/pi-natives/src/keys.rs`\n- `crates/pi-natives/src/task.rs` (shared cancellation behavior used by shell/pty)\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/bindings.ts`\n\n## Layer ownership\n\n- **TS wrapper/API layer** (`packages/natives/src/*`): typed entrypoints, cancellation surface (`timeoutMs`, `AbortSignal`), and JS ergonomics.\n- **Rust N-API module layer** (`crates/pi-natives/src/*`): shell/PTY process execution, process-tree traversal/termination, and key-sequence parsing.\n- **Validation gate** (`native.ts`, architecture-level): ensures required exports (`Shell`, `executeShell`, `PtySession`, `killTree`, `listDescendants`, key helpers) exist before wrappers are used.\n\n## Shell subsystem (`shell`)\n\n### API model\n\nTwo execution modes are exposed:\n\n1. **One-shot** via `executeShell(options, onChunk?)`.\n2. **Persistent session** via `new Shell(options?)` then `shell.run(...)` repeatedly.\n\nBoth stream output through a threadsafe callback and return `{ exitCode?, cancelled, timedOut }`.\n\n### Session creation and environment model\n\nRust creates `brush_core::Shell` with:\n\n- non-interactive mode,\n- `do_not_inherit_env: true`,\n- explicit environment reconstruction from host env,\n- skip-list for shell-sensitive vars (`PS1`, `PWD`, `SHLVL`, bash function exports, etc.).\n\nSession env behavior:\n\n- `ShellOptions.sessionEnv` is applied once at session creation.\n- `ShellRunOptions.env` is command-scoped (`EnvironmentScope::Command`) and popped after each run.\n- `PATH` is merged specially on Windows with case-insensitive dedupe.\n\nWindows-only path enrichment (`shell/windows.rs`): discovered Git-for-Windows paths (`cmd`, `bin`, `usr/bin`) are appended if present and not already included.\n\n### Runtime lifecycle and state transitions\n\nPersistent shell (`Shell.run`) uses this state machine:\n\n- **Idle/Uninitialized**: `session: None`.\n- **Running**: first `run()` lazily creates session, stores `current_abort` token, executes command.\n- **Completed + keepalive**: if execution control flow is `Normal`, `current_abort` is cleared and session is reused.\n- **Completed + teardown**: if control flow is loop/script/shell-exit related (`BreakLoop`, `ContinueLoop`, `ReturnFromFunctionOrScript`, `ExitShell`), session is dropped (`session: None`).\n- **Cancelled/Timed out**: run task is cancelled, grace wait (2s), then force-abort; session is dropped.\n- **Error**: session is dropped.\n\nOne-shot shell (`executeShell`) always creates and drops a fresh session per call.\n\n### Streaming/output behavior\n\n- Stdout/stderr are routed into a shared pipe and read concurrently.\n- Reader decodes UTF-8 incrementally; invalid byte sequences emit `U+FFFD` replacement chunks.\n- After process completion, output drain has idle/max guards (`250ms` idle, `2s` max) to avoid hanging on background jobs keeping descriptors open.\n\n### Cancellation, timeout, and background jobs\n\n- `CancelToken` is constructed from `timeoutMs` and optional `AbortSignal`.\n- On cancellation/timeout, shell cancellation token is triggered, then task gets a 2s graceful window before forced abort.\n- If cancellation occurs, background jobs are terminated (`TERM`, then delayed `KILL`) using brush job metadata.\n\n`Shell.abort()` behavior:\n\n- aborts only current running command for that `Shell` instance,\n- no-op success when nothing is running.\n\n### Failure behavior\n\nCommon surfaced errors include:\n\n- session init failures (`Failed to initialize shell`),\n- cwd errors (`Failed to set cwd`),\n- env set/pop failures,\n- snapshot source failures,\n- pipe creation/clone failures,\n- execution failure (`Shell execution failed: ...`),\n- task wrapper failures (`Shell execution task failed: ...`).\n\nResult-level cancellation flags:\n\n- timeout -> `exitCode: undefined`, `timedOut: true`.\n- abort signal -> `exitCode: undefined`, `cancelled: true`.\n\n## PTY subsystem (`pty`)\n\n### API model\n\n`new PtySession()` exposes:\n\n- `start(options, onChunk?) -> Promise<{ exitCode?, cancelled, timedOut }>`\n- `write(data)`\n- `resize(cols, rows)`\n- `kill()`\n\n### Runtime lifecycle and state transitions\n\n`PtySession` state machine:\n\n- **Idle**: `core: None`.\n- **Reserved**: `start()` installs control channel synchronously (`core: Some`) before async work begins, so `write/resize/kill` become immediately valid.\n- **Running**: blocking PTY loop handles child state, reader events, cancellation heartbeat, and control messages.\n- **Terminal closed**: child exit + reader completion.\n- **Finalized**: `core` is always reset to `None` after start task completion (success or error).\n\nConcurrency guard:\n\n- starting while already running returns `PTY session already running`.\n\n### Spawn/attach/write/read/terminate patterns\n\n- PTY opened via `portable_pty::native_pty_system().openpty(...)`.\n- Command currently runs as `sh -lc <command>` with optional `cwd` and env overrides.\n- `write()` sends raw bytes to PTY stdin.\n- `resize()` clamps dimensions (`cols 20..400`, `rows 5..200`) and calls master resize.\n- `kill()` marks run as cancelled and kills child process.\n\nOutput path:\n\n- dedicated reader thread reads master stream,\n- incremental UTF-8 decode with `U+FFFD` replacement on invalid bytes,\n- chunks forwarded through N-API threadsafe callback.\n\n### Cancellation and timeout semantics\n\n- `timeoutMs` and `AbortSignal` feed a `CancelToken`.\n- loop calls `ct.heartbeat()` periodically; abort triggers child kill.\n- timeout classification is string-based (`\"Timeout\"` substring in heartbeat error).\n\n### Failure behavior\n\nError surfaces include:\n\n- PTY allocation/open failure,\n- PTY spawn failure,\n- writer/reader acquisition failure,\n- child status/wait failures,\n- lock poisoning,\n- control-channel disconnection (`PTY session is no longer available`).\n\nControl call failures when not running:\n\n- `write/resize/kill` return `PTY session is not running`.\n\n## Process-tree subsystem (`ps`)\n\n### API model\n\n- `killTree(pid, signal) -> number`\n- `listDescendants(pid) -> number[]`\n\nTS wrapper also registers native kill-tree integration into shared utils via `setNativeKillTree(native.killTree)`.\n\n### Platform-specific implementation\n\n- **Linux**: recursively reads `/proc/<pid>/task/<pid>/children`.\n- **macOS**: uses `libproc` `proc_listchildpids`.\n- **Windows**: snapshots process table with `CreateToolhelp32Snapshot`, builds parent->children map, terminates with `OpenProcess(PROCESS_TERMINATE)` + `TerminateProcess`.\n\n### Kill-tree behavior\n\n- Descendants are collected recursively.\n- Kill order is bottom-up (deepest descendants first) to reduce orphan re-parenting.\n- Root pid is killed last.\n- Return value is count of successful terminations.\n\nSignal behavior:\n\n- POSIX: provided `signal` is passed to `kill`.\n- Windows: `signal` is ignored; termination is unconditional process terminate.\n\n### Failure behavior\n\nThis module is intentionally non-throwing at API surface:\n\n- missing/inaccessible process tree branches are skipped,\n- per-pid kill failures are counted as unsuccessful (not errors),\n- lookup miss typically yields `[]` from `listDescendants` and `0` from `killTree`.\n\n## Key parsing subsystem (`keys`)\n\n### API model\n\nExposed helpers:\n\n- `parseKey(data, kittyProtocolActive)`\n- `matchesKey(data, keyId, kittyProtocolActive)`\n- `parseKittySequence(data)`\n- `matchesKittySequence(data, expectedCodepoint, expectedModifier)`\n- `matchesLegacySequence(data, keyName)`\n\n### Parsing model\n\nThe parser combines:\n\n- direct single-byte mappings (`enter`, `tab`, `ctrl+<letter>`, printable ASCII),\n- O(1) legacy escape-sequence lookup (PHF map),\n- xterm `modifyOtherKeys` parsing,\n- Kitty protocol parsing (`CSI u`, `CSI ~`, `CSI 1;...<letter>`),\n- normalization to key IDs (`ctrl+c`, `shift+tab`, `pageUp`, `f5`, etc.).\n\nModifier handling:\n\n- only shift/alt/ctrl bits are compared for key matching,\n- lock bits are masked out before comparisons.\n\nLayout behavior:\n\n- base-layout fallback is intentionally constrained so remapped layouts do not create false matches for ASCII letters/symbols.\n\n### Failure behavior\n\n- Unrecognized or invalid sequences produce `null` from parse functions.\n- Match functions return `false` on parse failure or mismatch.\n- No thrown error surface for malformed key input.\n\n## JS wrapper API ↔ Rust export mapping\n\n### Shell + PTY + Process\n\n| TS wrapper API | Rust N-API export | Notes |\n|---|---|---|\n| `executeShell(options, onChunk?)` | `executeShell` (`execute_shell`) | One-shot shell execution |\n| `new Shell(options?)` | `Shell` class | Persistent shell session |\n| `shell.run(options, onChunk?)` | `Shell::run` | Reuses session on keepalive control flow |\n| `shell.abort()` | `Shell::abort` | Aborts active run for that shell instance |\n| `new PtySession()` | `PtySession` class | Stateful PTY session |\n| `pty.start(options, onChunk?)` | `PtySession::start` | Interactive PTY run |\n| `pty.write(data)` | `PtySession::write` | Raw stdin passthrough |\n| `pty.resize(cols, rows)` | `PtySession::resize` | Clamped terminal dimensions |\n| `pty.kill()` | `PtySession::kill` | Force-kills active PTY child |\n| `killTree(pid, signal)` | `killTree` (`kill_tree`) | Children-first process tree termination |\n| `listDescendants(pid)` | `listDescendants` (`list_descendants`) | Recursive descendants listing |\n\n### Keys\n\n| TS wrapper API | Rust N-API export | Notes |\n|---|---|---|\n| `matchesKittySequence(data, cp, mod)` | `matchesKittySequence` (`matches_kitty_sequence`) | Kitty codepoint+modifier match |\n| `parseKey(data, kittyProtocolActive)` | `parseKey` (`parse_key`) | Normalized key-id parser |\n| `matchesLegacySequence(data, keyName)` | `matchesLegacySequence` (`matches_legacy_sequence`) | Exact legacy sequence map check |\n| `parseKittySequence(data)` | `parseKittySequence` (`parse_kitty_sequence`) | Structured Kitty parse result |\n| `matchesKey(data, keyId, kittyProtocolActive)` | `matchesKey` (`matches_key`) | High-level key matcher |\n\n## Abandoned session cleanup and finalization notes\n\n- **Shell persistent session**: if a run is cancelled/timed out/errors/non-keepalive control flow, Rust explicitly drops the internal session state. Successful normal runs keep the session for reuse.\n- **PTY session**: `core` is always cleared after `start()` finishes, including failure paths.\n- **No explicit JS finalizer-driven kill contract** is exposed by wrappers; cleanup is primarily tied to run completion/cancellation paths. Callers should use `timeoutMs`, `AbortSignal`, `shell.abort()`, or `pty.kill()` for deterministic teardown.\n",
	"en/natives/natives-text-search-pipeline.md": "---\ntitle: Natives Text and Search Pipeline\ndescription: Native text search pipeline with grep, glob, and ripgrep-based file content indexing.\nsidebar:\n  order: 6\n  label: Text & search pipeline\n---\n\n# Natives Text/Search Pipeline\n\nThis document maps the `@f5-sales-demo/pi-natives` text/search surface (`grep`, `glob`, `text`, `highlight`) from TypeScript wrappers to Rust N-API exports and back to JS result objects.\n\nTerminology follows `docs/natives-architecture.md`:\n\n- **Wrapper**: TS API in `packages/natives/src/*`\n- **Rust module layer**: N-API exports in `crates/pi-natives/src/*`\n- **Shared scan cache**: `fs_cache`-backed directory-entry cache used by discovery/search flows\n\n## Implementation files\n\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/glob_util.rs`\n- `crates/pi-natives/src/fs_cache.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/highlight.rs`\n- `crates/pi-natives/src/fd.rs`\n\n## JS API ↔ Rust export mapping\n\n| JS wrapper API | Rust export (`#[napi]`, snake_case -> camelCase) | Rust module |\n| --- | --- | --- |\n| `grep(options, onMatch?)` | `grep` | `grep.rs` |\n| `searchContent(content, options)` | `search` | `grep.rs` |\n| `hasMatch(content, pattern, options?)` | `hasMatch` | `grep.rs` |\n| `fuzzyFind(options)` | `fuzzyFind` | `fd.rs` |\n| `glob(options, onMatch?)` | `glob` | `glob.rs` |\n| `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `fs_cache.rs` |\n| `wrapTextWithAnsi(text, width)` | `wrapTextWithAnsi` | `text.rs` |\n| `truncateToWidth(text, maxWidth, ellipsis, pad)` | `truncateToWidth` | `text.rs` |\n| `sliceWithWidth(line, startCol, length, strict?)` | `sliceWithWidth` | `text.rs` |\n| `extractSegments(line, beforeEnd, afterStart, afterLen, strictAfter)` | `extractSegments` | `text.rs` |\n| `sanitizeText(text)` | `sanitizeText` | `text.rs` |\n| `visibleWidth(text)` | `visibleWidth` | `text.rs` |\n| `highlightCode(code, lang, colors)` | `highlightCode` | `highlight.rs` |\n| `supportsLanguage(lang)` | `supportsLanguage` | `highlight.rs` |\n| `getSupportedLanguages()` | `getSupportedLanguages` | `highlight.rs` |\n\n## Pipeline overview by subsystem\n\n## 1) Regex search (`grep`, `searchContent`, `hasMatch`)\n\n### Input/options flow\n\n1. TS wrapper forwards options to native:\n   - `grep/index.ts` passes `options` mostly unchanged and wraps callback from `(match) => void` to napi threadsafe callback shape `(err, match)`.\n   - `searchContent` and `hasMatch` pass string/`Uint8Array` directly.\n2. Rust option structs in `grep.rs` deserialize camelCase fields (`ignoreCase`, `maxCount`, `contextBefore`, `contextAfter`, `maxColumns`, `timeoutMs`).\n3. `grep` creates `CancelToken` from `timeoutMs` + `AbortSignal` and runs inside `task::blocking(\"grep\", ...)`.\n\n### Execution branches\n\n- **In-memory branch (pure utility)**\n  - `search` → `search_sync` → `run_search` on provided content bytes.\n  - No filesystem scan, no `fs_cache`.\n- **Single-file branch (filesystem-dependent)**\n  - `grep_sync` resolves path, checks metadata is file, streams up to `MAX_FILE_BYTES` per file (`4 MiB`) through ripgrep matcher.\n- **Directory branch (filesystem-dependent)**\n  - Optional cache lookup via `fs_cache::get_or_scan` when `cache: true`.\n  - Fresh scan via `fs_cache::force_rescan` when `cache: false`.\n  - Optional empty-result recheck when cache age exceeds `empty_recheck_ms()`.\n  - Entry filtering: file-only + optional glob filter (`glob_util`) + optional type filter mapping (`js`, `ts`, `rust`, etc.).\n\n### Search/collection semantics\n\n- Regex engine: `grep_regex::RegexMatcherBuilder` with `ignoreCase` and `multiline`.\n- Context resolution:\n  - `contextBefore/contextAfter` override legacy `context`.\n  - Non-content modes zero out context collection.\n- Output modes:\n  - `content` => one `GrepMatch` per hit.\n  - `count` and `filesWithMatches` both map to count-style entries (`lineNumber=0`, `line=\"\"`, `matchCount` set).\n- Limits:\n  - Global `offset` and `maxCount` applied across files.\n  - Parallel path is used only when `maxCount` is unset and `offset == 0`; otherwise sequential path preserves deterministic global offset/limit semantics.\n\n### Result shaping back to JS\n\n- Rust `SearchResult`/`GrepResult` fields map to TS types via N-API object field conversion.\n- Counters are clamped to `u32` before crossing N-API.\n- Optional booleans are omitted unless true in some paths (`limitReached`).\n- Streaming callback receives each shaped `GrepMatch` (content or count entry).\n\n### Failure behavior\n\n- `searchContent` returns `SearchResult.error` for regex/search failures instead of throwing.\n- `grep` rejects on hard errors (invalid path, invalid glob/regex, cancellation timeout/abort).\n- `hasMatch` returns `Result<bool>` and throws on invalid pattern/UTF-8 decoding errors.\n- File open/search errors in multi-file scans are skipped per-file; scan continues.\n\n### Malformed regex handling\n\n`grep.rs` sanitizes braces before regex compile:\n\n- Invalid repetition-like braces are escaped (`{`/`}` -> `\\{`/`\\}`) when they cannot form `{N}`, `{N,}`, `{N,M}`.\n- This prevents common literal-template fragments (for example `${platform}`) from failing as malformed repetition.\n- Remaining invalid regex syntax still returns a regex error.\n\n## 2) File discovery (`glob`) and fuzzy path search (`fuzzyFind`)\n\n`glob` and `fuzzyFind` share `fs_cache` scans; matching logic differs.\n\n### `glob` flow\n\n1. TS wrapper (`glob/index.ts`):\n   - `path.resolve(options.path)`.\n   - Defaults: `pattern=\"*\"`, `hidden=false`, `gitignore=true`, `recursive=true`.\n2. Rust `glob` builds `GlobConfig` and compiles pattern via `glob_util::compile_glob`.\n3. Entry source:\n   - `cache=true` => `get_or_scan` + optional stale-empty `force_rescan`.\n   - `cache=false` => `force_rescan(..., store=false)` (fresh only).\n4. Filtering:\n   - Skip `.git` always.\n   - Skip `node_modules` unless requested (`includeNodeModules` or pattern mentioning node_modules).\n   - Apply glob match.\n   - Apply file-type filter; symlink `file/dir` filters resolve target metadata.\n5. Optional sort by mtime desc (`sortByMtime`) before truncating to `maxResults`.\n\n### `fuzzyFind` flow (implemented in `fd.rs`)\n\n1. TS wrapper is exported from `grep` module, but Rust implementation lives in `fd.rs`.\n2. Shared scan source from `fs_cache` with same cache/no-cache split and stale-empty recheck policy.\n3. Scoring:\n   - exact / starts-with / contains / subsequence-based fuzzy score\n   - separator/punctuation-normalized scoring path\n   - directory bonus and deterministic tie-break (`score desc`, then `path asc`)\n4. Symlink entries are excluded from fuzzy results.\n\n### Failure behavior\n\n- Invalid glob pattern => error from `glob_util::compile_glob`.\n- Search root must be an existing directory (`resolve_search_path`), otherwise error.\n- Cancellation/timeouts propagate as abort errors via `CancelToken::heartbeat()` checks in loops.\n\n### Malformed glob handling\n\n`glob_util::build_glob_pattern` is tolerant:\n\n- Normalizes `\\` to `/`.\n- Auto-prefixes simple recursive patterns with `**/` when `recursive=true`.\n- Auto-closes unbalanced `{...` alternation groups before compile.\n\n## 3) Shared scan/cache lifecycle (`fs_cache`)\n\n`fs_cache` stores scan results as normalized relative entries (`path`, `fileType`, optional `mtime`) keyed by:\n\n- canonical search root\n- `include_hidden`\n- `use_gitignore`\n\n### Cache state transitions\n\n1. **Miss / disabled**\n   - TTL is `0` or key absent/expired -> fresh `collect_entries`.\n2. **Hit**\n   - Entry age `< cache_ttl_ms()` -> return cached entries + `cache_age_ms`.\n3. **Stale-empty recheck** (caller policy in `glob`/`grep`/`fd`)\n   - If query yields zero matches and `cache_age_ms >= empty_recheck_ms()`, force one rescan.\n4. **Invalidation**\n   - `invalidateFsScanCache(path?)`:\n     - no arg: clear all keys\n     - path arg: remove keys whose root prefixes that target path\n\n### Stale-result tradeoff\n\n- Cache favors low-latency repeated scans over immediate consistency.\n- TTL window can return stale positives/negatives.\n- Empty-result recheck reduces stale negatives for older cached scans at the cost of one extra scan.\n- Explicit invalidation is the intended correctness hook after file mutations.\n\n## 4) ANSI text utilities (`text`)\n\nThese are pure, in-memory utilities (no filesystem scanning).\n\n### Boundaries and responsibilities\n\n- **`text.rs` owns terminal-cell semantics**:\n  - ANSI sequence parsing\n  - grapheme-aware width and slicing\n  - wrap/truncate/sanitize behavior\n- **`grep.rs` line truncation (`maxColumns`) is separate**:\n  - simple character-boundary truncation of matched lines with `...`\n  - not ANSI-state-preserving and not terminal-cell width aware\n\n### Key behaviors\n\n- `wrapTextWithAnsi`: wraps by visible width, carries active SGR codes across wrapped lines.\n- `truncateToWidth`: visible-cell truncation with ellipsis policy (`Unicode`, `Ascii`, `Omit`), optional right padding, and fast-path returning original JS string when unchanged.\n- `sliceWithWidth`: column slicing with optional strict width enforcement.\n- `extractSegments`: extracts before/after segments around an overlay while restoring ANSI state for the `after` segment.\n- `sanitizeText`: strips ANSI escapes + control chars, drops lone surrogates, normalizes CR/LF by removing `\\r`.\n- `visibleWidth`: counts visible terminal cells (tabs use fixed `TAB_WIDTH` from Rust implementation).\n\n### Failure behavior\n\nText functions generally return deterministic transformed output; errors are limited to JS string conversion boundaries (N-API argument conversion failures).\n\n## 5) Syntax highlighting (`highlight`)\n\n`highlight.rs` is pure transformation (no FS, no cache).\n\n### Flow\n\n1. Wrapper forwards `code`, optional `lang`, and ANSI color palette.\n2. Rust resolves syntax by:\n   - token/name lookup\n   - extension lookup\n   - alias table fallback (`ts/tsx/js -> JavaScript`, etc.)\n   - fallback to plain text syntax when unresolved\n3. Parse each line with syntect `ParseState` and scope stack.\n4. Map scopes to 11 semantic color categories and inject/reset ANSI color codes.\n\n### Failure behavior\n\n- Per-line parse failure does not fail the call: that line is appended unhighlighted and processing continues.\n- Unknown/unsupported language falls back to plain text syntax.\n\n## Pure utility vs filesystem-dependent flows\n\n| Flow | Filesystem access | Shared cache | Notes |\n| --- | --- | --- | --- |\n| `searchContent` / `hasMatch` | No | No | regex on provided bytes/string only |\n| `text` module functions | No | No | ANSI/width/sanitization only |\n| `highlight` module functions | No | No | syntax + ANSI coloring only |\n| `glob` | Yes | Optional | directory scans + glob filtering |\n| `fuzzyFind` | Yes | Optional | directory scans + fuzzy scoring |\n| `grep` (file/dir path) | Yes | Optional (dir mode) | ripgrep over files, optional filters/callback |\n\n## End-to-end lifecycle summary\n\n1. Caller invokes TS wrapper with typed options.\n2. Wrapper normalizes defaults (notably `glob`) and forwards to `native.*` export.\n3. Rust validates/normalizes options and builds matcher/search config.\n4. For filesystem flows, entries are scanned (cache hit/miss/rescan) then filtered/scored.\n5. Worker loops periodically call cancel heartbeat; timeout/abort can terminate execution.\n6. Rust shapes outputs into N-API objects (`lineNumber`, `matchCount`, `limitReached`, etc.).\n7. TS wrapper returns typed JS objects (and optional per-match callbacks for `grep`/`glob`).\n",
	"en/natives/porting-to-natives.md": "---\ntitle: Porting to pi-natives (N-API) — Field Notes\ndescription: Field notes for migrating Node.js child_process and shell code to the Rust N-API native layer.\nsidebar:\n  order: 9\n  label: Porting to pi-natives\n---\n\n# Porting to pi-natives (N-API) — Field Notes\n\nThis is a practical guide for moving hot paths into `crates/pi-natives` and wiring them through the JS bindings. It exists to avoid the same failures happening twice.\n\n## When to port\n\nPort when any of these are true:\n\n- The hot path runs in render loops, tight UI updates, or large batches.\n- JS allocations dominate (string churn, regex backtracking, large arrays).\n- You already have a JS baseline and can benchmark both versions side by side.\n- The work is CPU-bound or blocking I/O that can run on the libuv thread pool.\n- The work is async I/O that can run on Tokio's runtime (e.g., shell execution).\n\nAvoid ports that depend on JS-only state or dynamic imports. N-API exports should be pure, data-in/data-out. Long-running work should go through `task::blocking` (CPU-bound/blocking I/O) or `task::future` (async I/O) with cancellation.\n\n## Anatomy of a native export\n\n**Rust side:**\n\n- Implementation lives in `crates/pi-natives/src/<module>.rs`. If you add a new module, register it in `crates/pi-natives/src/lib.rs`.\n- Export with `#[napi]`; snake_case exports are converted to camelCase automatically. Use explicit `js_name` only for true aliases/non-default names. Use `#[napi(object)]` for structs.\n- Use `task::blocking(tag, cancel_token, work)` (see `crates/pi-natives/src/task.rs`) for CPU-bound or blocking work. Use `task::future(env, tag, work)` for async work that needs Tokio (e.g., shell sessions). Pass a `CancelToken` when you expose `timeoutMs` or `AbortSignal`.\n\n**JS side:**\n\n- `packages/natives/src/bindings.ts` holds the base `NativeBindings` interface.\n- `packages/natives/src/<module>/types.ts` defines TS types and augments `NativeBindings` via declaration merging.\n- `packages/natives/src/native.ts` imports each `<module>/types.ts` file to activate the declarations.\n- `packages/natives/src/<module>/index.ts` wraps the `native` binding from `packages/natives/src/native.ts`.\n- `packages/natives/src/native.ts` loads the addon and `validateNative` enforces required exports.\n- `packages/natives/src/index.ts` re-exports the wrapper for callers in `packages/*`.\n\n## Porting checklist\n\n1. **Add the Rust implementation**\n\n- Put the core logic in a plain Rust function.\n- If it’s a new module, add it to `crates/pi-natives/src/lib.rs`.\n- Expose it with `#[napi]` so the default snake_case -> camelCase mapping stays consistent.\n- Keep signatures owned and simple: `String`, `Vec<String>`, `Uint8Array`, or `Either<JsString, Uint8Array>` for large string/byte inputs.\n- For CPU-bound or blocking work, use `task::blocking`; for async work, use `task::future`. Pass a `CancelToken` and call `heartbeat()` inside long loops.\n\n2. **Wire JS bindings**\n\n- Add the types and `NativeBindings` augmentation in `packages/natives/src/<module>/types.ts`.\n- Import `./<module>/types` in `packages/natives/src/native.ts` to trigger declaration merging.\n- Add a wrapper in `packages/natives/src/<module>/index.ts` that calls `native`.\n- Re-export from `packages/natives/src/index.ts`.\n\n3. **Update native validation**\n\n- Add `checkFn(\"newExport\")` in `validateNative` (`packages/natives/src/native.ts`).\n\n4. **Add benchmarks**\n\n- Put benchmarks next to the owning package (`packages/tui/bench`, `packages/natives/bench`, or `packages/coding-agent/bench`).\n- Include a JS baseline and native version in the same run.\n- Use `Bun.nanoseconds()` and a fixed iteration count.\n- Keep the benchmark inputs small and realistic (actual data seen in the hot path).\n\n5. **Build the native binary**\n\n- `bun --cwd=packages/natives run build`\n- Use `bun --cwd=packages/natives run build` and set `PI_DEV=1` if you want loader diagnostics while testing.\n\n6. **Run the benchmark**\n\n- `bun run packages/<pkg>/bench/<bench>.ts` (or `bun --cwd=packages/natives run bench`)\n\n7. **Decide on usage**\n\n- If native is slower, **keep JS** and leave the native export unused.\n- If native is faster, switch call sites to the native wrapper.\n\n## Pain points and how to avoid them\n\n### 1) Stale `pi_natives.node` prevents new exports\n\nThe loader prefers the platform-tagged binary in `packages/natives/native` (`pi_natives.<platform>-<arch>.node`). `PI_DEV=1` now only enables loader diagnostics; it no longer switches to a separate dev addon filename. There is also a fallback `pi_natives.node`. Compiled binaries extract to `~/.xcsh/natives/<version>/pi_natives.<platform>-<arch>.node`. If any of these are stale, exports won’t update.\n\n**Fix:** remove the stale file before rebuilding.\n\n```bash\nrm packages/natives/native/pi_natives.linux-x64.node\nrm packages/natives/native/pi_natives.node\nbun --cwd=packages/natives run build\n```\n\nIf you’re running a compiled binary, delete the cached addon directory:\n\n```bash\nrm -rf ~/.xcsh/natives/<version>\n```\n\nThen verify the export exists in the binary:\n\n```bash\nbun -e 'const tag = `${process.platform}-${process.arch}`; const mod = require(`./packages/natives/native/pi_natives.${tag}.node`); console.log(Object.keys(mod).includes(\"newExport\"));'\n```\n\n### 2) “Missing exports” errors from `validateNative`\n\nThis is **good** — it prevents silent mismatches. When you see this:\n\n```\nNative addon missing exports ... Missing: visibleWidth\n```\n\nit means your binary is stale, the Rust export name (or explicit alias when used) doesn’t match the JS name, or the export never compiled in. Fix the build and the naming mismatch, don’t weaken validation.\n\n### 3) Rust signature mismatch\n\nKeep it simple and owned. `String`, `Vec<String>`, and `Uint8Array` work. Avoid references like `&str` in public exports. If you need structured data, wrap it in `#[napi(object)]` structs.\n\n### 4) Benchmarking mistakes\n\n- Don’t compare different inputs or allocations.\n- Keep JS and native using identical input arrays.\n- Run both in the same benchmark file to avoid skew.\n\n## Benchmark template\n\n```ts\nconst ITERATIONS = 2000;\n\nfunction bench(name: string, fn: () => void): number {\n const start = Bun.nanoseconds();\n for (let i = 0; i < ITERATIONS; i++) fn();\n const elapsed = (Bun.nanoseconds() - start) / 1e6;\n console.log(`${name}: ${elapsed.toFixed(2)}ms total (${(elapsed / ITERATIONS).toFixed(6)}ms/op)`);\n return elapsed;\n}\n\nbench(\"feature/js\", () => {\n jsImpl(sample);\n});\n\nbench(\"feature/native\", () => {\n nativeImpl(sample);\n});\n```\n\n## Verification checklist\n\n- `validateNative` passes (no missing exports).\n- `NativeBindings` is augmented in `packages/natives/src/<module>/types.ts` and the wrapper is re-exported in `packages/natives/src/index.ts`.\n- `Object.keys(require(...))` includes your new export.\n- Bench numbers recorded in the PR/notes.\n- Call site updated **only if** native is faster or equal.\n\n## Rule of thumb\n\n- If native is slower, **do not switch**. Keep the export for future work, but the TUI should stay on the faster path.\n- If native is faster, switch the call site and keep the benchmark in place to catch regressions.\n",
	"en/providers/models.md": "---\ntitle: Model and Provider Configuration\ndescription: Model registry and provider configuration via models.yml with routing, fallback, and pricing.\nsidebar:\n  order: 1\n  label: Models & providers\n---\n\n# Model and Provider Configuration (`models.yml`)\n\nThis document describes how the coding-agent currently loads models, applies overrides, resolves credentials, and chooses models at runtime.\n\n## What controls model behavior\n\nPrimary implementation files:\n\n- `src/config/model-registry.ts` — loads built-in + custom models, provider overrides, runtime discovery, auth integration\n- `src/config/model-resolver.ts` — parses model patterns and selects initial/smol/slow models\n- `src/config/settings-schema.ts` — model-related settings (`modelRoles`, provider transport preferences)\n- `src/session/auth-storage.ts` — API key + OAuth resolution order\n- `packages/ai/src/models.ts` and `packages/ai/src/types.ts` — built-in providers/models and `Model`/`compat` types\n\n## Config file location and legacy behavior\n\nDefault config path:\n\n- `~/.xcsh/agent/models.yml`\n\nLegacy behavior still present:\n\n- If `models.yml` is missing and `models.json` exists at the same location, it is migrated to `models.yml`.\n- Explicit `.json` / `.jsonc` config paths are still supported when passed programmatically to `ModelRegistry`.\n\n## `models.yml` shape\n\n```yaml\nconfigVersion: 1  # optional — written by auto-config, used for migration detection\nproviders:\n  <provider-id>:\n    # provider-level config\nequivalence:\n  overrides:\n    <provider-id>/<model-id>: <canonical-model-id>\n  exclude:\n    - <provider-id>/<model-id>\n```\n\n`configVersion` is an optional integer written by the auto-config system. When present, xcsh uses it to detect outdated configs and auto-upgrade them.\n\n`provider-id` is the canonical provider key used across selection and auth lookup.\n\n`equivalence` is optional and configures canonical model grouping on top of concrete provider models:\n\n- `overrides` maps an exact concrete selector (`provider/modelId`) to an official upstream canonical id\n- `exclude` opts a concrete selector out of canonical grouping\n\n## Provider-level fields\n\n```yaml\nproviders:\n  my-provider:\n    baseUrl: https://api.example.com/v1\n    apiKey: MY_PROVIDER_API_KEY\n    api: openai-completions\n    headers:\n      X-Team: platform\n    authHeader: true\n    auth: apiKey\n    discovery:\n      type: ollama\n    modelOverrides:\n      some-model-id:\n        name: Renamed model\n    models:\n      - id: some-model-id\n        name: Some Model\n        api: openai-completions\n        reasoning: false\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 128000\n        maxTokens: 16384\n        headers:\n          X-Model: value\n        compat:\n          supportsStore: true\n          supportsDeveloperRole: true\n          supportsReasoningEffort: true\n          maxTokensField: max_completion_tokens\n          openRouterRouting:\n            only: [anthropic]\n          vercelGatewayRouting:\n            order: [anthropic, openai]\n          extraBody:\n            gateway: m1-01\n            controller: mlx\n```\n\n### Allowed provider/model `api` values\n\n- `openai-completions`\n- `openai-responses`\n- `openai-codex-responses`\n- `azure-openai-responses`\n- `anthropic-messages`\n- `google-generative-ai`\n- `google-vertex`\n\n### Allowed auth/discovery values\n\n- `auth`: `apiKey` (default) or `none`\n- `discovery.type`: `ollama`\n\n## Validation rules (current)\n\n### Full custom provider (`models` is non-empty)\n\nRequired:\n\n- `baseUrl`\n- `apiKey` unless `auth: none`\n- `api` at provider level or each model\n\n### Override-only provider (`models` missing or empty)\n\nMust define at least one of:\n\n- `baseUrl`\n- `modelOverrides`\n- `discovery`\n\n### Discovery\n\n- `discovery` requires provider-level `api`.\n\n### Model value checks\n\n- `id` required\n- `contextWindow` and `maxTokens` must be positive if provided\n\n## Merge and override order\n\nModelRegistry pipeline (on refresh):\n\n1. Load built-in providers/models from `@f5-sales-demo/pi-ai`.\n2. Load `models.yml` custom config.\n3. Apply provider overrides (`baseUrl`, `headers`) to built-in models.\n4. Apply `modelOverrides` (per provider + model id).\n5. Merge custom `models`:\n   - same `provider + id` replaces existing\n   - otherwise append\n6. Apply runtime-discovered models (currently Ollama and LM Studio), then re-apply model overrides.\n\n## Canonical model equivalence and coalescing\n\nThe registry keeps every concrete provider model and then builds a canonical layer above them.\n\nCanonical ids are official upstream ids only, for example:\n\n- `claude-opus-4-6`\n- `claude-haiku-4-5`\n- `gpt-5.3-codex`\n\n### `models.yml` equivalence config\n\nExample:\n\n```yaml\nproviders:\n  zenmux:\n    baseUrl: https://api.zenmux.example/v1\n    apiKey: ZENMUX_API_KEY\n    api: openai-codex-responses\n    models:\n      - id: codex\n        name: Zenmux Codex\n        reasoning: true\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 200000\n        maxTokens: 32768\n\nequivalence:\n  overrides:\n    zenmux/codex: gpt-5.3-codex\n    p-codex/codex: gpt-5.3-codex\n  exclude:\n    - demo/codex-preview\n```\n\nBuild order for canonical grouping:\n\n1. exact user override from `equivalence.overrides`\n2. bundled official-id matches from built-in model metadata\n3. conservative heuristic normalization for gateway/provider variants\n4. fallback to the concrete model's own id\n\nCurrent heuristics are intentionally narrow:\n\n- embedded upstream prefixes can be stripped when present, for example `anthropic/...` or `openai/...`\n- dotted and dashed version variants can normalize only when they map to an existing official id, for example `4.6 -> 4-6`\n- ambiguous families or versions are not merged without a bundled match or explicit override\n\n### Canonical resolution behavior\n\nWhen multiple concrete variants share a canonical id, resolution uses:\n\n1. availability and auth\n2. `config.yml` `modelProviderOrder`\n3. existing registry/provider order if `modelProviderOrder` is unset\n\nDisabled or unauthenticated providers are skipped.\n\nSession state and transcripts continue to record the concrete provider/model that actually executed the turn.\n\nProvider defaults vs per-model overrides:\n\n- Provider `headers` are baseline.\n- Model `headers` override provider header keys.\n- `modelOverrides` can override model metadata (`name`, `reasoning`, `input`, `cost`, `contextWindow`, `maxTokens`, `headers`, `compat`, `contextPromotionTarget`).\n- `compat` is deep-merged for nested routing blocks (`openRouterRouting`, `vercelGatewayRouting`, `extraBody`).\n\n## Runtime discovery integration\n\n### Implicit Ollama discovery\n\nIf `ollama` is not explicitly configured, registry adds an implicit discoverable provider:\n\n- provider: `ollama`\n- api: `openai-completions`\n- base URL: `OLLAMA_BASE_URL` or `http://127.0.0.1:11434`\n- auth mode: keyless (`auth: none` behavior)\n\nRuntime discovery calls `GET /api/tags` on Ollama and synthesizes model entries with local defaults.\n\n### Implicit llama.cpp discovery\n\nIf `llama.cpp` is not explicitly configured, registry adds an implicit discoverable provider:\nNote: it's using the newer antropic messages api instead of the openai-competions.\n\n- provider: `llama.cpp`\n- api: `openai-responses`\n- base URL: `LLAMA_CPP_BASE_URL` or `http://127.0.0.1:8080`\n- auth mode: keyless (`auth: none` behavior)\n\nRuntime discovery calls `GET models` on llama.cpp and synthesizes model entries with local defaults.\n\n### Implicit LM Studio discovery\n\nIf `lm-studio` is not explicitly configured, registry adds an implicit discoverable provider:\n\n- provider: `lm-studio`\n- api: `openai-completions`\n- base URL: `LM_STUDIO_BASE_URL` or `http://127.0.0.1:1234/v1`\n- auth mode: keyless (`auth: none` behavior)\n\nRuntime discovery fetches models (`GET /models`) and synthesizes model entries with local defaults.\n\n### Explicit provider discovery\n\nYou can configure discovery yourself:\n\n```yaml\nproviders:\n  ollama:\n    baseUrl: http://127.0.0.1:11434\n    api: openai-completions\n    auth: none\n    discovery:\n      type: ollama\n      \n  llama.cpp:\n    baseUrl: http://127.0.0.1:8080\n    api: openai-responses\n    auth: none\n    discovery:\n      type: llama.cpp\n```\n\n### Extension provider registration\n\nExtensions can register providers at runtime (`pi.registerProvider(...)`), including:\n\n- model replacement/append for a provider\n- custom stream handler registration for new API IDs\n- custom OAuth provider registration\n\n## Auth and API key resolution order\n\nWhen requesting a key for a provider, effective order is:\n\n1. Runtime override (CLI `--api-key`)\n2. Stored API key credential in `agent.db`\n3. Stored OAuth credential in `agent.db` (with refresh)\n4. Environment variable mapping (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc.)\n5. ModelRegistry fallback resolver (provider `apiKey` from `models.yml`, env-name-or-literal semantics)\n\n`models.yml` `apiKey` behavior:\n\n- Value is first treated as an environment variable name.\n- If no env var exists, the literal string is used as the token.\n\nIf `authHeader: true` and provider `apiKey` is set, models get:\n\n- `Authorization: Bearer <resolved-key>` header injected.\n\nKeyless providers:\n\n- Providers marked `auth: none` are treated as available without credentials.\n- `getApiKey*` returns `kNoAuth` for them.\n\n## Model availability vs all models\n\n- `getAll()` returns the loaded model registry (built-in + merged custom + discovered).\n- `getAvailable()` filters to models that are keyless or have resolvable auth.\n\nSo a model can exist in registry but not be selectable until auth is available.\n\n## Runtime model resolution\n\n### CLI and pattern parsing\n\n`model-resolver.ts` supports:\n\n- exact `provider/modelId`\n- exact canonical model id\n- exact model id (provider inferred)\n- fuzzy/substring matching\n- glob scope patterns in `--models` (e.g. `openai/*`, `*sonnet*`)\n- optional `:thinkingLevel` suffix (`off|minimal|low|medium|high|xhigh`)\n\n`--provider` is legacy; `--model` is preferred.\n\nResolution precedence for exact selectors:\n\n1. exact `provider/modelId` bypasses coalescing\n2. exact canonical id resolves through the canonical index\n3. exact bare concrete id still works\n4. fuzzy and glob matching run after the exact paths\n\n### Initial model selection priority\n\n`findInitialModel(...)` uses this order:\n\n1. explicit CLI provider+model\n2. first scoped model (if not resuming)\n3. saved default provider/model\n4. known provider defaults (e.g. OpenAI/Anthropic/etc.) among available models\n5. first available model\n\n### Role aliases and settings\n\nSupported model roles:\n\n- `default`, `smol`, `slow`, `plan`, `commit`\n\nRole aliases like `pi/smol` expand through `settings.modelRoles`. Each role value can also append a thinking selector such as `:minimal`, `:low`, `:medium`, or `:high`.\n\nIf a role points at another role, the target model still inherits normally and any explicit suffix on the referring role wins for that role-specific use.\n\nRelated settings:\n\n- `modelRoles` (record)\n- `enabledModels` (scoped pattern list)\n- `modelProviderOrder` (global canonical-provider precedence)\n- `providers.kimiApiFormat` (`openai` or `anthropic` request format)\n- `providers.openaiWebsockets` (`auto|off|on` websocket preference for OpenAI Codex transport)\n\n`modelRoles` may store either:\n\n- `provider/modelId` to pin a concrete provider variant\n- a canonical id such as `gpt-5.3-codex` to allow provider coalescing\n\nFor `enabledModels` and CLI `--models`:\n\n- exact canonical ids expand to all concrete variants in that canonical group\n- explicit `provider/modelId` entries stay exact\n- globs and fuzzy matches still operate on concrete models\n\n## `/model` and `--list-models`\n\nBoth surfaces keep provider-prefixed models visible and selectable.\n\nThey now also expose canonical/coalesced models:\n\n- `/model` includes a canonical view alongside provider tabs\n- `--list-models` prints a canonical section plus the concrete provider rows\n\nSelecting a canonical entry stores the canonical selector. Selecting a provider row stores the explicit `provider/modelId`.\n\n## Context promotion (model-level fallback chains)\n\nContext promotion is an overflow recovery mechanism for small-context variants (for example `*-spark`) that automatically promotes to a larger-context sibling when the API rejects a request with a context length error.\n\n### Trigger and order\n\nWhen a turn fails with a context overflow error (e.g. `context_length_exceeded`), `AgentSession` attempts promotion **before** falling back to compaction:\n\n1. If `contextPromotion.enabled` is true, resolve a promotion target (see below).\n2. If a target is found, switch to it and retry the request — no compaction needed.\n3. If no target is available, fall through to auto-compaction on the current model.\n\n### Target selection\n\nSelection is model-driven, not role-driven:\n\n1. `currentModel.contextPromotionTarget` (if configured)\n2. smallest larger-context model on the same provider + API\n\nCandidates are ignored unless credentials resolve (`ModelRegistry.getApiKey(...)`).\n\n### OpenAI Codex websocket handoff\n\nIf switching from/to `openai-codex-responses`, session provider state key `openai-codex-responses` is closed before model switch. This drops websocket transport state so the next turn starts clean on the promoted model.\n\n### Persistence behavior\n\nPromotion uses temporary switching (`setModelTemporary`):\n\n- recorded as a temporary `model_change` in session history\n- does not rewrite saved role mapping\n\n### Configuring explicit fallback chains\n\nConfigure fallback directly in model metadata via `contextPromotionTarget`.\n\n`contextPromotionTarget` accepts either:\n\n- `provider/model-id` (explicit)\n- `model-id` (resolved within current provider)\n\nExample (`models.yml`) for Spark -> non-Spark on the same provider:\n\n```yaml\nproviders:\n  openai-codex:\n    modelOverrides:\n      gpt-5.3-codex-spark:\n        contextPromotionTarget: openai-codex/gpt-5.3-codex\n```\n\nThe built-in model generator also assigns this automatically for `*-spark` models when a same-provider base model exists.\n\n## Compatibility and routing fields\n\n`models.yml` supports this `compat` subset:\n\n- `supportsStore`\n- `supportsDeveloperRole`\n- `supportsReasoningEffort`\n- `maxTokensField` (`max_completion_tokens` or `max_tokens`)\n- `openRouterRouting.only` / `openRouterRouting.order`\n- `vercelGatewayRouting.only` / `vercelGatewayRouting.order`\n\nThese are consumed by the OpenAI-completions transport logic and combined with URL-based auto-detection.\n\n## Practical examples\n\n### Local OpenAI-compatible endpoint (no auth)\n\n```yaml\nproviders:\n  local-openai:\n    baseUrl: http://127.0.0.1:8000/v1\n    auth: none\n    api: openai-completions\n    models:\n      - id: Qwen/Qwen2.5-Coder-32B-Instruct\n        name: Qwen 2.5 Coder 32B (local)\n```\n\n### Hosted proxy with env-based key\n\n```yaml\nproviders:\n  anthropic-proxy:\n    baseUrl: https://proxy.example.com/anthropic\n    apiKey: ANTHROPIC_PROXY_API_KEY\n    api: anthropic-messages\n    authHeader: true\n    models:\n      - id: claude-sonnet-4-20250514\n        name: Claude Sonnet 4 (Proxy)\n        reasoning: true\n        input: [text, image]\n```\n\n### Override built-in provider route + model metadata\n\n```yaml\nproviders:\n  openrouter:\n    baseUrl: https://my-proxy.example.com/v1\n    headers:\n      X-Team: platform\n    modelOverrides:\n      anthropic/claude-sonnet-4:\n        name: Sonnet 4 (Corp)\n        compat:\n          openRouterRouting:\n            only: [anthropic]\n```\n\n## LiteLLM proxy auto-configuration\n\nWhen both `LITELLM_BASE_URL` and `LITELLM_API_KEY` environment variables are set, xcsh automatically manages `models.yml` configuration for the LiteLLM proxy.\n\n### First-run auto-generation\n\nIf `models.yml` does not exist and LiteLLM env vars are detected, xcsh generates it automatically:\n\n```yaml\n# Auto-generated by xcsh for LiteLLM proxy\n# API key resolved from LITELLM_API_KEY env var at runtime\nconfigVersion: 1\nproviders:\n  anthropic:\n    baseUrl: \"https://your-litellm-proxy.example.com/anthropic\"\n    apiKey: LITELLM_API_KEY\n```\n\nA default `config.yml` is also generated with sensible image provider settings.\n\n### Startup self-healing\n\nOn every startup, `startupHealthCheck()` in the model registry runs the following checks:\n\n| Condition | Action |\n|-----------|--------|\n| `models.yml` missing | Auto-generate from env vars |\n| `models.yml` corrupt or unparseable | Backup to `.bak`, regenerate |\n| `baseUrl` doesn't match `LITELLM_BASE_URL` | Backup to `.bak`, regenerate with new URL |\n| `configVersion` missing or outdated | Backup to `.bak`, regenerate with current version |\n| Config is healthy | No action |\n\nAll repairs create `.bak` backups before overwriting. All operations are idempotent.\n\n### CLI command\n\n```bash\nxcsh setup litellm              # Generate or fix LiteLLM config\nxcsh setup litellm --check      # Validate without writing\nxcsh setup litellm --check --json  # Machine-readable validation output\n```\n\n### Required environment variables\n\n| Variable | Purpose |\n|----------|---------|\n| `LITELLM_BASE_URL` | LiteLLM proxy URL (e.g. `https://your-proxy.example.com`). Must start with `http://` or `https://`. |\n| `LITELLM_API_KEY` | API key for the proxy. Referenced by name in generated config, resolved at runtime. |\n\nIf either variable is unset, auto-configuration is silently skipped.\n\n### Config versioning\n\nGenerated configs include a `configVersion` field. When the generated format changes in future releases, xcsh detects outdated configs and automatically upgrades them (with backup).\n\n## Legacy consumer caveat\n\nMost model configuration now flows through `models.yml` via `ModelRegistry`.\n\nOne notable legacy path remains: web-search Anthropic auth resolution still reads `~/.xcsh/agent/models.json` directly in `src/web/search/auth.ts`.\n\nIf you rely on that specific path, keep JSON compatibility in mind until that module is migrated.\n\n## Failure mode\n\nIf `models.yml` fails schema or validation checks:\n\n- If `LITELLM_BASE_URL` and `LITELLM_API_KEY` are set, the startup health check attempts auto-repair (backup corrupt file, regenerate from env vars). If repair succeeds, the registry reloads the fixed config.\n- If auto-repair is not possible (env vars unset, write failure), the registry keeps operating with built-in models.\n- Error is exposed via `ModelRegistry.getError()` and surfaced in UI/notifications.\n",
	"en/providers/provider-streaming-internals.md": "---\ntitle: Provider Streaming Internals\ndescription: Provider streaming implementation with SSE parsing, token counting, and backpressure handling.\nsidebar:\n  order: 2\n  label: Streaming internals\n---\n\n# Provider streaming internals\n\nThis document explains how token/tool streaming is normalized in `@f5-sales-demo/pi-ai`, then propagated through `@f5-sales-demo/pi-agent-core` and `coding-agent` session events.\n\n## End-to-end flow\n\n1. `streamSimple()` (`packages/ai/src/stream.ts`) maps generic options and dispatches to a provider stream function.\n2. Provider stream functions (`anthropic.ts`, `openai-responses.ts`, `google.ts`) translate provider-native stream events into the unified `AssistantMessageEvent` sequence.\n3. Each provider pushes events into `AssistantMessageEventStream` (`packages/ai/src/utils/event-stream.ts`), which throttles delta events and exposes:\n   - async iteration for incremental updates\n   - `result()` for final `AssistantMessage`\n4. `agentLoop` (`packages/agent/src/agent-loop.ts`) consumes those events, mutates in-flight assistant state, and emits `message_update` events carrying the raw `assistantMessageEvent`.\n5. `AgentSession` (`packages/coding-agent/src/session/agent-session.ts`) subscribes to agent events, persists messages, drives extension hooks, and applies session behaviors (retry, compaction, TTSR, streaming-edit abort checks).\n\n## Unified stream contract in `@f5-sales-demo/pi-ai`\n\nAll providers emit the same shape (`AssistantMessageEvent` in `packages/ai/src/types.ts`):\n\n- `start`\n- content block lifecycle triplets:\n  - text: `text_start` → `text_delta`* → `text_end`\n  - thinking: `thinking_start` → `thinking_delta`* → `thinking_end`\n  - tool call: `toolcall_start` → `toolcall_delta`* → `toolcall_end`\n- terminal event:\n  - `done` with `reason: \"stop\" | \"length\" | \"toolUse\"`\n  - or `error` with `reason: \"aborted\" | \"error\"`\n\n`AssistantMessageEventStream` guarantees:\n\n- final result is resolved by terminal event (`done` or `error`)\n- deltas are batched/throttled (~50ms)\n- buffered deltas are flushed before non-delta events and before completion\n\n## Delta throttling and harmonization behavior\n\n`AssistantMessageEventStream` treats `text_delta`, `thinking_delta`, and `toolcall_delta` as mergeable events:\n\n- buffered deltas are merged only when **type + contentIndex** match\n- merge keeps the latest `partial` snapshot\n- non-delta events force immediate flush\n\nThis smooths high-frequency provider streams for TUI/event consumers, but is not provider backpressure: providers still produce at full speed, while the local stream buffers.\n\n## Provider normalization details\n\n## Anthropic (`anthropic-messages`)\n\nSource: `packages/ai/src/providers/anthropic.ts`\n\nNormalization points:\n\n- `message_start` initializes usage (input/output/cache tokens)\n- `content_block_start` maps to text/thinking/toolcall starts\n- `content_block_delta` maps:\n  - `text_delta` → `text_delta`\n  - `thinking_delta` → `thinking_delta`\n  - `input_json_delta` → `toolcall_delta`\n  - `signature_delta` updates `thinkingSignature` only (no event)\n- `content_block_stop` emits corresponding `*_end`\n- `message_delta.stop_reason` maps via `mapStopReason()`\n\nTool-call argument streaming:\n\n- each tool block carries internal `partialJson`\n- every JSON delta appends to `partialJson`\n- `arguments` are reparsed on each delta via `parseStreamingJson()`\n- `toolcall_end` reparses once more, then strips `partialJson`\n\n## OpenAI Responses (`openai-responses`)\n\nSource: `packages/ai/src/providers/openai-responses.ts`\n\nNormalization points:\n\n- `response.output_item.added` starts reasoning/text/function-call blocks\n- reasoning summary events (`response.reasoning_summary_text.delta`) become `thinking_delta`\n- output/refusal deltas become `text_delta`\n- `response.function_call_arguments.delta` becomes `toolcall_delta`\n- `response.output_item.done` emits `thinking_end` / `text_end` / `toolcall_end`\n- `response.completed` maps status to stop reason and usage\n\nTool-call argument streaming:\n\n- same `partialJson` accumulation pattern as Anthropic\n- providers that send only `response.function_call_arguments.done` still populate final args\n- tool call IDs are normalized as `\"<call_id>|<item_id>\"`\n\n## Google Generative AI (`google-generative-ai`)\n\nSource: `packages/ai/src/providers/google.ts`\n\nNormalization points:\n\n- iterates `candidate.content.parts`\n- text parts are split into thinking vs text by `isThinkingPart(part)`\n- block transitions close previous block before starting a new one\n- `part.functionCall` is treated as a complete tool call (start/delta/end emitted immediately)\n- finish reason mapped by `mapStopReason()` from `google-shared.ts`\n\nTool-call argument streaming:\n\n- function call args arrive as structured object, not incremental JSON text\n- implementation emits one synthetic `toolcall_delta` containing `JSON.stringify(arguments)`\n- no partial JSON parser needed for Google in this path\n\n## Partial tool-call JSON accumulation and recovery\n\nShared behavior for Anthropic/OpenAI Responses uses `parseStreamingJson()` (`packages/ai/src/utils/json-parse.ts`):\n\n1. try `JSON.parse`\n2. fallback to `partial-json` parser for incomplete fragments\n3. if both fail, return `{}`\n\nImplications:\n\n- malformed or truncated argument deltas do not crash stream processing immediately\n- in-progress `arguments` may temporarily be `{}`\n- later valid deltas can recover structured arguments because parsing is retried on every append\n- final `toolcall_end` performs one more parse attempt before emission\n\n## Stop reasons vs transport/runtime errors\n\nProvider stop reasons are mapped to normalized `stopReason`:\n\n- Anthropic: `end_turn`→`stop`, `max_tokens`→`length`, `tool_use`→`toolUse`, safety/refusal cases→`error`\n- OpenAI Responses: `completed`→`stop`, `incomplete`→`length`, `failed/cancelled`→`error`\n- Google: `STOP`→`stop`, `MAX_TOKENS`→`length`, safety/prohibited/malformed-function-call classes→`error`\n\nError semantics are split in two stages:\n\n1. **Model completion semantics** (provider reported finish reason/status)\n2. **Transport/runtime failure** (network/client/parser/abort exceptions)\n\nIf provider stream throws or signals failure, each provider wrapper catches and emits terminal `error` event with:\n\n- `stopReason = \"aborted\"` when abort signal is set\n- otherwise `stopReason = \"error\"`\n- `errorMessage = formatErrorMessageWithRetryAfter(error)`\n\n## Malformed chunk / SSE parse failure behavior\n\nFor these provider paths, chunk/SSE framing is handled by vendor SDK streams (Anthropic SDK, OpenAI SDK, Google SDK). This code does not implement a custom SSE decoder here.\n\nObserved behavior in current implementation:\n\n- malformed chunk/SSE parsing at SDK level surfaces as an exception or stream `error` event\n- provider wrapper converts that into unified terminal `error` event\n- no provider-specific resume/retry inside the stream function itself\n- higher-level retries are handled in `AgentSession` auto-retry logic (message-level retry, not stream-chunk replay)\n\n## Cancellation boundaries\n\nCancellation is layered:\n\n- AI provider request: `options.signal` is passed into provider client stream call.\n- Provider wrapper: after stream loop, aborted signal forces error path (`\"Request was aborted\"`).\n- Agent loop: checks `signal.aborted` before handling each provider event and can synthesize an aborted assistant message from the latest partial.\n- Session/agent controls: `AgentSession.abort()` -> `agent.abort()` -> shared abort controller cancellation.\n\nTool execution cancellation is separate from model stream cancellation:\n\n- tool runners use `AbortSignal.any([agentSignal, steeringAbortSignal])`\n- steering interrupts can abort remaining tool execution while preserving already-produced tool results\n\n## Backpressure boundaries\n\nThere is no hard backpressure mechanism between provider SDK stream and downstream consumers:\n\n- `EventStream` uses in-memory queues with no max size\n- throttling reduces UI update rate but does not slow provider intake\n- if consumers lag significantly, queued events can grow until completion\n\nCurrent design favors responsiveness and simple ordering over bounded-buffer flow control.\n\n## How stream events surface as agent/session events\n\n`agentLoop.streamAssistantResponse()` bridges `AssistantMessageEvent` to `AgentEvent`:\n\n- on `start`: pushes placeholder assistant message and emits `message_start`\n- on block events (`text_*`, `thinking_*`, `toolcall_*`): updates last assistant message, emits `message_update` with raw `assistantMessageEvent`\n- on terminal (`done`/`error`): resolves final message from `response.result()`, emits `message_end`\n\n`AgentSession` then consumes those events for session-level behaviors:\n\n- TTSR watches `message_update.assistantMessageEvent` for `text_delta` and `toolcall_delta`\n- streaming edit guard inspects `toolcall_delta`/`toolcall_end` on `edit` calls and can abort early\n- persistence writes finalized messages at `message_end`\n- auto-retry examines assistant `stopReason === \"error\"` plus `errorMessage` heuristics\n\n## Unified vs provider-specific responsibilities\n\nUnified (common contract):\n\n- event shape (`AssistantMessageEvent`)\n- final result extraction (`done`/`error`)\n- delta throttling + merge rules\n- agent/session event propagation model\n\nProvider-specific (not fully abstracted):\n\n- upstream event taxonomies and mapping logic\n- stop-reason translation tables\n- tool-call ID conventions\n- reasoning/thinking block semantics and signatures\n- usage token semantics and availability timing\n- message conversion constraints per API\n\n## Implementation files\n\n- [`../../ai/src/stream.ts`](../../packages/ai/src/stream.ts) — provider dispatch, option mapping, API key/session plumbing.\n- [`../../ai/src/utils/event-stream.ts`](../../packages/ai/src/utils/event-stream.ts) — generic stream queue + assistant delta throttling.\n- [`../../ai/src/utils/json-parse.ts`](../../packages/ai/src/utils/json-parse.ts) — partial JSON parsing for streamed tool arguments.\n- [`../../ai/src/providers/anthropic.ts`](../../packages/ai/src/providers/anthropic.ts) — Anthropic event translation and tool JSON delta accumulation.\n- [`../../ai/src/providers/openai-responses.ts`](../../packages/ai/src/providers/openai-responses.ts) — OpenAI Responses event translation and status mapping.\n- [`../../ai/src/providers/google.ts`](../../packages/ai/src/providers/google.ts) — Gemini stream chunk-to-block translation.\n- [`../../ai/src/providers/google-shared.ts`](../../packages/ai/src/providers/google-shared.ts) — Gemini finish-reason mapping and shared conversion rules.\n- [`../../agent/src/agent-loop.ts`](../../packages/agent/src/agent-loop.ts) — provider stream consumption and `message_update` bridging.\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — session-level handling of streaming updates, abort, retry, and persistence.\n",
	"en/providers/python-repl.md": "---\ntitle: Python Tool and IPython Runtime\ndescription: Python REPL tool runtime with IPython kernel management, execution, and output capture.\nsidebar:\n  order: 3\n  label: Python & IPython\n---\n\n# Python Tool and IPython Runtime\n\nThis document describes the current Python execution stack in `packages/coding-agent`.\nIt covers tool behavior, kernel/gateway lifecycle, environment handling, execution semantics, output rendering, and operational failure modes.\n\n## Scope and Key Files\n\n- Tool surface: `src/tools/python.ts`\n- Session/per-call kernel orchestration: `src/ipy/executor.ts`\n- Kernel protocol + gateway integration: `src/ipy/kernel.ts`\n- Shared local gateway coordinator: `src/ipy/gateway-coordinator.ts`\n- Interactive-mode renderer for user-triggered Python runs: `src/modes/components/python-execution.ts`\n- Runtime/env filtering and Python resolution: `src/ipy/runtime.ts`\n\n## What the Python tool is\n\nThe `python` tool executes one or more Python cells through a Jupyter Kernel Gateway-backed kernel (not by spawning `python -c` directly per cell).\n\nTool params:\n\n```ts\n{\n  cells: Array<{ code: string; title?: string }>;\n  timeout?: number; // seconds, clamped to 1..600, default 30\n  cwd?: string;\n  reset?: boolean; // reset kernel before first cell only\n}\n```\n\nThe tool is `concurrency = \"exclusive\"` for a session, so calls do not overlap.\n\n## Gateway lifecycle\n\n### Modes\n\nThere are two gateway paths:\n\n1. **External gateway** (`PI_PYTHON_GATEWAY_URL` set)\n   - Uses the configured URL directly.\n   - Optional auth with `PI_PYTHON_GATEWAY_TOKEN`.\n   - No local gateway process is spawned or managed.\n\n2. **Local shared gateway** (default path)\n   - Uses a single shared process coordinated under `~/.xcsh/agent/python-gateway`.\n   - Metadata file: `gateway.json`\n   - Lock file: `gateway.lock`\n   - Spawn command:\n     - `python -m kernel_gateway`\n     - bound to `127.0.0.1:<allocated-port>`\n     - startup health check: `GET /api/kernelspecs`\n\n### Local shared gateway coordination\n\n`acquireSharedGateway()`:\n\n- Takes a file lock (`gateway.lock`) with heartbeat.\n- Reuses `gateway.json` if PID is alive and health check passes.\n- Cleans stale info/PIDs when needed.\n- Starts a new gateway when no healthy one exists.\n\n`releaseSharedGateway()` is currently a no-op (kernel shutdown does not tear down shared gateway).\n\n`shutdownSharedGateway()` explicitly terminates the shared process and clears gateway metadata.\n\n### Important constraint\n\n`python.sharedGateway=false` is rejected at kernel start:\n\n- Error: `Shared Python gateway required; local gateways are disabled`\n- There is no per-process non-shared local gateway mode.\n\n## Kernel lifecycle\n\nEach execution uses a kernel created via `POST /api/kernels` on the selected gateway.\n\nKernel startup sequence:\n\n1. Availability check (`checkPythonKernelAvailability`)\n2. Create kernel (`/api/kernels`)\n3. Open websocket (`/api/kernels/:id/channels`)\n4. Initialize kernel env (`cwd`, env vars, `sys.path`)\n5. Execute `PYTHON_PRELUDE`\n6. Load extension modules from:\n   - user: `~/.xcsh/agent/modules/*.py`\n   - project: `<cwd>/.xcsh/modules/*.py` (overrides same-name user module)\n\nKernel shutdown:\n\n- Deletes remote kernel via `DELETE /api/kernels/:id`\n- Closes websocket\n- Calls shared gateway release hook (no-op today)\n\n## Session persistence semantics\n\n`python.kernelMode` controls kernel reuse:\n\n- `session` (default)\n  - Reuses kernel sessions keyed by session identity + cwd.\n  - Execution is serialized per session via a queue.\n  - Idle sessions are evicted after 5 minutes.\n  - At most 4 sessions; oldest is evicted on overflow.\n  - Heartbeat checks detect dead kernels.\n  - Auto-restart allowed once; repeated crash => hard failure.\n\n- `per-call`\n  - Creates a fresh kernel for each execute request.\n  - Shuts kernel down after the request.\n  - No cross-call state persistence.\n\n### Multi-cell behavior in a single tool call\n\nCells run sequentially in the same kernel instance for that tool call.\n\nIf an intermediate cell fails:\n\n- Earlier cell state remains in memory.\n- Tool returns a targeted error indicating which cell failed.\n- Later cells are not executed.\n\n`reset=true` only applies to the first cell execution in that call.\n\n## Environment filtering and runtime resolution\n\nEnvironment is filtered before launching gateway/kernel runtime:\n\n- Allowlist includes core vars like `PATH`, `HOME`, locale vars, `VIRTUAL_ENV`, `PYTHONPATH`, etc.\n- Allow-prefixes: `LC_`, `XDG_`, `PI_`\n- Denylist strips common API keys (OpenAI/Anthropic/Gemini/etc.)\n\nRuntime selection order:\n\n1. Active/located venv (`VIRTUAL_ENV`, then `<cwd>/.venv`, `<cwd>/venv`)\n2. Managed venv at `~/.xcsh/python-env`\n3. `python` or `python3` on PATH\n\nWhen a venv is selected, its bin/Scripts path is prepended to `PATH`.\n\nKernel env initialization inside Python also:\n\n- `os.chdir(cwd)`\n- injects provided env map into `os.environ`\n- ensures cwd is in `sys.path`\n\n## Tool availability and mode selection\n\n`python.toolMode` (default `both`) + optional `PI_PY` override controls exposure:\n\n- `ipy-only`\n- `bash-only`\n- `both`\n\n`PI_PY` accepted values:\n\n- `0` / `bash` -> `bash-only`\n- `1` / `py` -> `ipy-only`\n- `mix` / `both` -> `both`\n\nIf Python preflight fails, tool creation degrades to bash-only for that session.\n\n## Execution flow and cancellation/timeout\n\n### Tool-level timeout\n\n`python` tool timeout is in seconds, default 30, clamped to `1..600`.\n\nThe tool combines:\n\n- caller abort signal\n- timeout abort signal\n\nwith `AbortSignal.any(...)`.\n\n### Kernel execution cancellation\n\nOn abort/timeout:\n\n- Execution is marked cancelled.\n- Kernel interrupt is attempted via REST (`POST /interrupt`) and control-channel `interrupt_request`.\n- Result includes `cancelled=true`.\n- Timeout path annotates output as `Command timed out after <n> seconds`.\n\n### stdin behavior\n\nInteractive stdin is not supported.\n\nIf kernel emits `input_request`:\n\n- Tool records `stdinRequested=true`\n- Emits explanatory text\n- Sends empty `input_reply`\n- Execution is treated as failure at executor layer\n\n## Output capture and rendering\n\n### Captured output classes\n\nFrom kernel messages:\n\n- `stream` -> plain text chunks\n- `display_data`/`execute_result` -> rich display handling\n- `error` -> traceback text\n- custom MIME `application/x-xcsh-status` -> structured status events\n\nDisplay MIME precedence:\n\n1. `text/markdown`\n2. `text/plain`\n3. `text/html` (converted to basic markdown)\n\nAdditionally captured as structured outputs:\n\n- `application/json` -> JSON tree data\n- `image/png` -> image payloads\n- `application/x-xcsh-status` -> status events\n\n### Storage and truncation\n\nOutput is streamed through `OutputSink` and may be persisted to artifact storage.\n\nTool results can include truncation metadata and `artifact://<id>` for full output recovery.\n\n### Renderer behavior\n\n- Tool renderer (`python.ts`):\n  - shows code-cell blocks with per-cell status\n  - collapsed preview defaults to 10 lines\n  - supports expanded mode for full output and richer status detail\n- Interactive renderer (`python-execution.ts`):\n  - used for user-triggered Python execution in TUI\n  - collapsed preview defaults to 20 lines\n  - clamps very long individual lines to 4000 chars for display safety\n  - shows cancellation/error/truncation notices\n\n## External gateway support\n\nSet:\n\n```bash\nexport PI_PYTHON_GATEWAY_URL=\"http://127.0.0.1:8888\"\n# Optional:\nexport PI_PYTHON_GATEWAY_TOKEN=\"...\"\n```\n\nBehavior differences from local shared gateway:\n\n- No local gateway lock/info files\n- No local process spawn/termination\n- Health checks and kernel CRUD run against external endpoint\n- Auth failures are surfaced with explicit token guidance\n\n## Operational troubleshooting (current failure modes)\n\n- **Python tool not available**\n  - Check `python.toolMode` / `PI_PY`.\n  - If preflight fails, runtime falls back to bash-only.\n\n- **Kernel availability errors**\n  - Local mode requires both `kernel_gateway` and `ipykernel` importable in resolved Python runtime.\n  - Install with:\n\n    ```bash\n    python -m pip install jupyter_kernel_gateway ipykernel\n    ```\n\n- **`python.sharedGateway=false` causes startup failure**\n  - This is expected with current implementation.\n\n- **External gateway auth/reachability failures**\n  - 401/403 -> set `PI_PYTHON_GATEWAY_TOKEN`.\n  - timeout/unreachable -> verify URL/network and gateway health.\n\n- **Execution hangs then times out**\n  - Increase tool `timeout` (max 600s) if workload is legitimate.\n  - For stuck code, cancellation triggers kernel interrupt but user code may still need refactor.\n\n- **stdin/input prompts in Python code**\n  - `input()` is not supported interactively in this runtime path; pass data programmatically.\n\n- **Resource exhaustion (`EMFILE` / too many open files)**\n  - Session manager triggers shared-gateway recovery (session teardown + shared gateway restart).\n\n- **Working directory errors**\n  - Tool validates `cwd` exists and is a directory before execution.\n\n## Relevant environment variables\n\n- `PI_PY` — tool exposure override (`bash-only`/`ipy-only`/`both` mapping above)\n- `PI_PYTHON_GATEWAY_URL` — use external gateway\n- `PI_PYTHON_GATEWAY_TOKEN` — optional external gateway auth token\n- `PI_PYTHON_SKIP_CHECK=1` — bypass Python preflight/warm checks\n- `PI_PYTHON_IPC_TRACE=1` — log kernel IPC send/receive traces\n- `PI_DEBUG_STARTUP=1` — emit startup-stage debug markers\n",
	"en/runtime-tools/bash-tool-runtime.md": "---\ntitle: Bash Tool Runtime\ndescription: Bash tool runtime with shell process management, sandboxing, timeout, and output streaming.\nsidebar:\n  order: 1\n  label: Bash tool\n---\n\n# Bash tool runtime\n\nThis document describes the **`bash` tool** runtime path used by agent tool calls, from command normalization to execution, truncation/artifacts, and rendering.\n\nIt also calls out where behavior diverges in interactive TUI, print mode, RPC mode, and user-initiated bang (`!`) shell execution.\n\n## Scope and runtime surfaces\n\nThere are two different bash execution surfaces in coding-agent:\n\n1. **Tool-call surface** (`toolName: \"bash\"`): used when the model calls the bash tool.\n   - Entry point: `BashTool.execute()`.\n2. **User bang-command surface** (`!cmd` from interactive input or RPC `bash` command): session-level helper path.\n   - Entry point: `AgentSession.executeBash()`.\n\nBoth eventually use `executeBash()` in `src/exec/bash-executor.ts` for non-PTY execution, but only the tool-call path runs normalization/interception and tool renderer logic.\n\n## End-to-end tool-call pipeline\n\n## 1) Input normalization and parameter merge\n\n`BashTool.execute()` first normalizes the raw command via `normalizeBashCommand()`:\n\n- extracts trailing `| head -n N`, `| head -N`, `| tail -n N`, `| tail -N` into structured limits,\n- trims trailing/leading whitespace,\n- keeps internal whitespace intact.\n\nThen it merges extracted limits with explicit tool args:\n\n- explicit `head`/`tail` args override extracted values,\n- extracted values are fallback only.\n\n### Caveat\n\n`bash-normalize.ts` comments mention stripping `2>&1`, but current implementation does not remove it. Runtime behavior is still correct (stdout/stderr are already merged), but the normalization behavior is narrower than comments suggest.\n\n## 2) Optional interception (blocked-command path)\n\nIf `bashInterceptor.enabled` is true, `BashTool` loads rules from settings and runs `checkBashInterception()` against the normalized command.\n\nInterception behavior:\n\n- command is blocked **only** when:\n  - regex rule matches, and\n  - the suggested tool is present in `ctx.toolNames`.\n- invalid regex rules are silently skipped.\n- on block, `BashTool` throws `ToolError` with message:\n  - `Blocked: ...`\n  - original command included.\n\nDefault rule patterns (defined in code) target common misuses:\n\n- file readers (`cat`, `head`, `tail`, ...)\n- search tools (`grep`, `rg`, ...)\n- file finders (`find`, `fd`, ...)\n- in-place editors (`sed -i`, `perl -i`, `awk -i inplace`)\n- shell redirection writes (`echo ... > file`, heredoc redirection)\n\n### Caveat\n\n`InterceptionResult` includes `suggestedTool`, but `BashTool` currently surfaces only the message text (no structured suggested-tool field in `details`).\n\n## 3) CWD validation and timeout clamping\n\n`cwd` is resolved relative to session cwd (`resolveToCwd`), then validated via `stat`:\n\n- missing path -> `ToolError(\"Working directory does not exist: ...\")`\n- non-directory -> `ToolError(\"Working directory is not a directory: ...\")`\n\nTimeout is clamped to `[1, 3600]` seconds and converted to milliseconds.\n\n## 4) Artifact allocation\n\nBefore execution, the tool allocates an artifact path/id (best-effort) for truncated output storage.\n\n- artifact allocation failure is non-fatal (execution continues without artifact spill file),\n- artifact id/path are passed into execution path for full-output persistence on truncation.\n\n## 5) PTY vs non-PTY execution selection\n\n`BashTool` chooses PTY execution only when all are true:\n\n- `bash.virtualTerminal === \"on\"`\n- `PI_NO_PTY !== \"1\"`\n- tool context has UI (`ctx.hasUI === true` and `ctx.ui` set)\n\nOtherwise it uses non-interactive `executeBash()`.\n\nThat means print mode and non-UI RPC/tool contexts always use non-PTY.\n\n## Non-interactive execution engine (`executeBash`)\n\n## Shell session reuse model\n\n`executeBash()` caches native `Shell` instances in a process-global map keyed by:\n\n- shell path,\n- configured command prefix,\n- snapshot path,\n- serialized shell env,\n- optional agent session key.\n\nFor session-level executions, `AgentSession.executeBash()` passes `sessionKey: this.sessionId`, isolating reuse per session.\n\nTool-call path does **not** pass `sessionKey`, so reuse scope is based on shell config/snapshot/env.\n\n## Shell config and snapshot behavior\n\nAt each call, executor loads settings shell config (`shell`, `env`, optional `prefix`).\n\nIf selected shell includes `bash`, it attempts `getOrCreateSnapshot()`:\n\n- snapshot captures aliases/functions/options from user rc,\n- snapshot creation is best-effort,\n- failure falls back to no snapshot.\n\nIf `prefix` is configured, command becomes:\n\n```text\n<prefix> <command>\n```\n\n## Streaming and cancellation\n\n`Shell.run()` streams chunks to callback. Executor pipes each chunk into `OutputSink` and optional `onChunk` callback.\n\nCancellation:\n\n- aborted signal triggers `shellSession.abort(...)`,\n- timeout from native result is mapped to `cancelled: true` + annotation text,\n- explicit cancellation similarly returns `cancelled: true` + annotation.\n\nNo exception is thrown inside executor for timeout/cancel; it returns structured `BashResult` and lets caller map error semantics.\n\n## Interactive PTY path (`runInteractiveBashPty`)\n\nWhen PTY is enabled, tool runs `runInteractiveBashPty()` which opens an overlay console component and drives a native `PtySession`.\n\nBehavior highlights:\n\n- xterm-headless virtual terminal renders viewport in overlay,\n- keyboard input is normalized (including Kitty sequences and application cursor mode handling),\n- `esc` while running kills the PTY session,\n- terminal resize propagates to PTY (`session.resize(cols, rows)`).\n\nEnvironment hardening defaults are injected for unattended runs:\n\n- pagers disabled (`PAGER=cat`, `GIT_PAGER=cat`, etc.),\n- editor prompts disabled (`GIT_EDITOR=true`, `EDITOR=true`, ...),\n- terminal/auth prompts reduced (`GIT_TERMINAL_PROMPT=0`, `SSH_ASKPASS=/usr/bin/false`, `CI=1`),\n- package-manager/tool automation flags for non-interactive behavior.\n\nPTY output is normalized (`CRLF`/`CR` to `LF`, `sanitizeText`) and written into `OutputSink`, including artifact spill support.\n\nOn PTY startup/runtime error, sink receives `PTY error: ...` line and command finalizes with undefined exit code.\n\n## Output handling: streaming, truncation, artifact spill\n\nBoth PTY and non-PTY paths use `OutputSink`.\n\n## OutputSink semantics\n\n- keeps an in-memory UTF-8-safe tail buffer (`DEFAULT_MAX_BYTES`, currently 50KB),\n- tracks total bytes/lines seen,\n- if artifact path exists and output overflows (or file already active), writes full stream to artifact file,\n- when memory threshold overflows, trims in-memory buffer to tail (UTF-8 boundary safe),\n- marks `truncated` when overflow/file spill occurs.\n\n`dump()` returns:\n\n- `output` (possibly annotated prefix),\n- `truncated`,\n- `totalLines/totalBytes`,\n- `outputLines/outputBytes`,\n- `artifactId` if artifact file was active.\n\n### Long-output caveat\n\nRuntime truncation is byte-threshold based in `OutputSink` (50KB default). It does not enforce a hard 2000-line cap in this code path.\n\n## Live tool updates\n\nFor non-PTY execution, `BashTool` uses a separate `TailBuffer` for partial updates and emits `onUpdate` snapshots while command is running.\n\nFor PTY execution, live rendering is handled by custom UI overlay, not by `onUpdate` text chunks.\n\n## Result shaping, metadata, and error mapping\n\nAfter execution:\n\n1. `cancelled` handling:\n   - if abort signal is aborted -> throw `ToolAbortError` (abort semantics),\n   - else -> throw `ToolError` (treated as tool failure).\n2. PTY `timedOut` -> throw `ToolError`.\n3. apply head/tail filters to final output text (`applyHeadTail`, head then tail).\n4. empty output becomes `(no output)`.\n5. attach truncation metadata via `toolResult(...).truncationFromSummary(result, { direction: \"tail\" })`.\n6. exit-code mapping:\n   - missing exit code -> `ToolError(\"... missing exit status\")`\n   - non-zero exit -> `ToolError(\"... Command exited with code N\")`\n   - zero exit -> success result.\n\nSuccess payload structure:\n\n- `content`: text output,\n- `details.meta.truncation` when truncated, including:\n  - `direction`, `truncatedBy`, total/output line+byte counts,\n  - `shownRange`,\n  - `artifactId` when available.\n\nBecause built-in tools are wrapped with `wrapToolWithMetaNotice()`, truncation notice text is appended to final text content automatically (for example: `Full: artifact://<id>`).\n\n## Rendering paths\n\n## Tool-call renderer (`bashToolRenderer`)\n\n`bashToolRenderer` is used for tool-call messages (`toolCall` / `toolResult`):\n\n- collapsed mode shows visual-line-truncated preview,\n- expanded mode shows all currently available output text,\n- warning line includes truncation reason and `artifact://<id>` when truncated,\n- timeout value (from args) is shown in footer metadata line.\n\n### Caveat: full artifact expansion\n\n`BashRenderContext` has `isFullOutput`, but current renderer context builder does not set it for bash tool results. Expanded view still uses the text already in result content (tail/truncated output) unless another caller provides full artifact content.\n\n## User bang-command component (`BashExecutionComponent`)\n\n`BashExecutionComponent` is for user `!` commands in interactive mode (not model tool calls):\n\n- streams chunks live,\n- collapsed preview keeps last 20 logical lines,\n- line clamp at 4000 chars per line,\n- shows truncation + artifact warnings when metadata is present,\n- marks cancelled/error/exit state separately.\n\nThis component is wired by `CommandController.handleBashCommand()` and fed from `AgentSession.executeBash()`.\n\n## Mode-specific behavior differences\n\n| Surface                        | Entry path                                            | PTY eligible                                                         | Live output UX                                                           | Error surfacing                                  |\n| ------------------------------ | ----------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------ |\n| Interactive tool call          | `BashTool.execute`                                    | Yes, when `bash.virtualTerminal=on` and UI exists and `PI_NO_PTY!=1` | PTY overlay (interactive) or streamed tail updates                       | Tool errors become `toolResult.isError`          |\n| Print mode tool call           | `BashTool.execute`                                    | No (no UI context)                                                   | No TUI overlay; output appears in event stream/final assistant text flow | Same tool error mapping                          |\n| RPC tool call (agent tooling)  | `BashTool.execute`                                    | Usually no UI -> non-PTY                                             | Structured tool events/results                                           | Same tool error mapping                          |\n| Interactive bang command (`!`) | `AgentSession.executeBash` + `BashExecutionComponent` | No (uses executor directly)                                          | Dedicated bash execution component                                       | Controller catches exceptions and shows UI error |\n| RPC `bash` command             | `rpc-mode` -> `session.executeBash`                   | No                                                                   | Returns `BashResult` directly                                            | Consumer handles returned fields                 |\n\n## Operational caveats\n\n- Interceptor only blocks commands when suggested tool is currently available in context.\n- If artifact allocation fails, truncation still occurs but no `artifact://` back-reference is available.\n- Shell session cache has no explicit eviction in this module; lifetime is process-scoped.\n- PTY and non-PTY timeout surfaces differ:\n  - PTY exposes explicit `timedOut` result field,\n  - non-PTY maps timeout into `cancelled + annotation` summary.\n\n## Implementation files\n\n- [`src/tools/bash.ts`](../../packages/coding-agent/src/tools/bash.ts) — tool entrypoint, normalization/interception, PTY/non-PTY selection, result/error mapping, bash tool renderer.\n- [`src/tools/bash-normalize.ts`](../../packages/coding-agent/src/tools/bash-normalize.ts) — command normalization and post-run head/tail filtering.\n- [`src/tools/bash-interceptor.ts`](../../packages/coding-agent/src/tools/bash-interceptor.ts) — interceptor rule matching and blocked-command messages.\n- [`src/exec/bash-executor.ts`](../../packages/coding-agent/src/exec/bash-executor.ts) — non-PTY executor, shell session reuse, cancellation wiring, output sink integration.\n- [`src/tools/bash-interactive.ts`](../../packages/coding-agent/src/tools/bash-interactive.ts) — PTY runtime, overlay UI, input normalization, non-interactive env defaults.\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — `OutputSink` truncation/artifact spill and summary metadata.\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — artifact allocation helpers and streaming tail buffer.\n- [`src/tools/output-meta.ts`](../../packages/coding-agent/src/tools/output-meta.ts) — truncation metadata shape + notice injection wrapper.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — session-level `executeBash`, message recording, abort lifecycle.\n- [`src/modes/components/bash-execution.ts`](../../packages/coding-agent/src/modes/components/bash-execution.ts) — interactive `!` command execution component.\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts) — wiring for interactive `!` command UI stream/update completion.\n- [`src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts) — RPC `bash` and `abort_bash` command surface.\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — `artifact://<id>` resolution.\n",
	"en/runtime-tools/context-command.md": "---\ntitle: \"F5 XC Contexts\"\ndescription: Connect xcsh to F5 Distributed Cloud tenants -- create, switch, and manage authentication contexts.\nsidebar:\n  order: 1\n  label: F5 XC Contexts\n---\n\n# F5 XC Contexts\n\nxcsh connects to F5 Distributed Cloud through **contexts** -- named credential sets that bind a tenant URL, API token, and namespace. If you've used `kubectl config use-context` or `kubectx`, the workflow is identical: create a context, switch between them by name, and use `-` to flip back.\n\n## Getting started\n\n### 1. Create your first context\n\nYou need three things from your F5 XC console: the tenant URL, an API token, and optionally a namespace.\n\n```\n/context create production https://acme.console.ves.volterra.io p12k3-your-api-token\n```\n\n```\nContext 'production' created. Use /context activate production to switch to it.\n```\n\nOr use the guided wizard if you prefer step-by-step prompts:\n\n```\n/context wizard\n```\n\n### 2. Activate it\n\n```\n/context production\n```\n\n```\n╭─ production ─────────────────────────────────────────────────╮\n│ XCSH_TENANT     acme                                         │\n│ XCSH_API_URL    https://acme.console.ves.volterra.io         │\n│ XCSH_API_TOKEN  ...oken                                      │\n│ Status          Connected (312ms)                            │\n├─ Environment ────────────────────────────────────────────────┤\n│ XCSH_NAMESPACE  default                                      │\n╰──────────────────────────────────────────────────────────────╯\n```\n\nOnce activated, xcsh injects the tenant credentials into your session. The agent can now make F5 XC API calls, and the status line shows the active context.\n\n### 3. Add more contexts and switch between them\n\n```\n/context create staging https://staging.console.ves.volterra.io p12k3-staging-token\n```\n\nSwitch by name -- no subcommand verb needed:\n\n```\n/context staging\n```\n\nSwitch back to the previous context (`cd -` style):\n\n```\n/context -\n```\n\nCalling `/context -` twice returns you to where you started.\n\n### 4. See what you have\n\n```\n/context\n```\n\n```\n  production           https://acme.console.ves.volterra.io\n* staging              https://staging.console.ves.volterra.io\n```\n\nThe `*` marks the active context.\n\n## Everyday commands\n\n| Command | What it does |\n|---|---|\n| `/context` | List all contexts |\n| `/context <name>` | Switch to a context |\n| `/context -` | Switch to the previous context |\n| `/context show` | Show active context details (tokens masked) |\n| `/context status` | Show current auth status |\n\n## Context lifecycle\n\n| Command | What it does |\n|---|---|\n| `/context create <name> <url> <token> [namespace]` | Create a context |\n| `/context delete <name> --confirm` | Delete a context (requires `--confirm`) |\n| `/context rename <old> <new>` | Rename a context |\n| `/context validate <name>` | Test credentials without switching |\n| `/context export [name] [--include-token]` | Export as JSON (tokens masked by default) |\n| `/context import <path-or-json> [--overwrite]` | Import from file or inline JSON |\n| `/context wizard` | Guided interactive setup |\n\n## Switching namespaces\n\nEach context has a default namespace. Switch it without changing the context:\n\n```\n/context namespace system\n```\n\nTab completion offers namespace names from the active tenant.\n\n## Environment variables on contexts\n\nContexts can carry extra environment variables that are injected into your session on activation. Useful for per-tenant configuration that isn't part of the credential set.\n\n```\n/context set CUSTOM_HEADER=x-acme-trace\n/context set LOG_LEVEL=debug\n/context env list\n/context unset LOG_LEVEL\n```\n\nAliases: `add` = `set`, `remove`/`clear` = `unset`.\n\n## Tab completion\n\nType `/context ` and press Tab. The dropdown shows:\n\n1. **Context names** -- with tenant URL hints, so you can tell tenants apart\n2. **`-`** -- appears when you've switched before, shows which context you'd flip to\n3. **Subcommands** -- `list`, `create`, `delete`, etc.\n\nContext names appear first because switching is the most common action.\n\nSubcommand-level completions also work: `/context activate <Tab>` completes context names, `/context namespace <Tab>` completes namespaces, `/context unset <Tab>` completes known env var keys.\n\n## Naming rules\n\nContext names must be 1-64 characters: letters, digits, hyphens, underscores.\n\nNames that collide with subcommands are rejected:\n\n```\n/context create list https://example.com tok\n```\n\n```\nError: Context name 'list' conflicts with a /context subcommand. Choose a different name.\n```\n\nThe full reserved set: `list`, `show`, `status`, `create`, `delete`, `rename`, `namespace`, `env`, `set`, `unset`, `add`, `remove`, `clear`, `activate`, `validate`, `export`, `import`, `wizard`, `help`. Comparison is case-insensitive.\n\n## Environment variable override\n\nIf `XCSH_API_URL` and `XCSH_API_TOKEN` are set in your shell environment before launching xcsh, they take precedence over any context. This is useful for CI/CD pipelines or one-off sessions where you don't want to create a persistent context.\n\nWhen running in this mode, `/context` shows the environment-sourced credentials with a `(via env vars)` label.\n\n## Previous context behavior\n\n- **Session-scoped**: the previous context resets when you restart xcsh. It is not persisted to disk.\n- **Ping-pong**: `/context -` twice returns you to where you started.\n- **Safe across mutations**: if you delete the previous context, the pointer is cleared. If you rename it, the pointer follows the new name.\n- **Re-activation is a no-op**: `/context production` when already on `production` does not reset the previous pointer.\n\n## Design conventions\n\nThe `/context` UX follows:\n\n- **kubectx**: `kubectx <name>` for switching, `kubectx -` for previous, bare `kubectx` for listing\n- **kubectl**: `kubectl config use-context` for the explicit form\n- **Shell**: `cd -` / `OLDPWD` for previous-directory tracking\n",
	"en/runtime-tools/custom-tools.md": "---\ntitle: Custom Tools\ndescription: Custom tool registration, schema definition, and execution pipeline for extending the agent.\nsidebar:\n  order: 4\n  label: Custom tools\n---\n\n# Custom Tools\n\nCustom tools are model-callable functions that plug into the same tool execution pipeline as built-in tools.\n\nA custom tool is a TypeScript/JavaScript module that exports a factory. The factory receives a host API (`CustomToolAPI`) and returns one tool or an array of tools.\n\n## What this is (and is not)\n\n- **Custom tool**: callable by the model during a turn (`execute` + TypeBox schema).\n- **Extension**: lifecycle/event framework that can register tools and intercept/modify events.\n- **Hook**: external pre/post command scripts.\n- **Skill**: static guidance/context package, not executable tool code.\n\nIf you need the model to call code directly, use a custom tool.\n\n## Integration paths in current code\n\nThere are two active integration styles:\n\n1. **SDK-provided custom tools** (`options.customTools`)\n   - Wrapped into agent tools via `CustomToolAdapter` or extension wrappers.\n   - Always included in the initial active tool set in SDK bootstrap.\n\n2. **Filesystem-discovered modules via loader API** (`discoverAndLoadCustomTools` / `loadCustomTools`)\n   - Exposed as library APIs in `src/extensibility/custom-tools/loader.ts`.\n   - Host code can call these to discover and load tool modules from config/provider/plugin paths.\n\n```text\nModel tool call flow\n\nLLM tool call\n   │\n   ▼\nTool registry (built-ins + custom tool adapters)\n   │\n   ▼\nCustomTool.execute(toolCallId, params, onUpdate, ctx, signal)\n   │\n   ├─ onUpdate(...)  -> streamed partial result\n   └─ return result  -> final tool content/details\n```\n\n## Discovery locations (loader API)\n\n`discoverAndLoadCustomTools(configuredPaths, cwd, builtInToolNames)` merges:\n\n1. Capability providers (`toolCapability`), including:\n   - Native OMP config (`~/.xcsh/agent/tools`, `.xcsh/tools`)\n   - Claude config (`~/.claude/tools`, `.claude/tools`)\n   - Codex config (`~/.codex/tools`, `.codex/tools`)\n   - Claude marketplace plugin cache provider\n2. Installed plugin manifests (`~/.xcsh/plugins/node_modules/*` via plugin loader)\n3. Explicit configured paths passed to the loader\n\n### Important behavior\n\n- Duplicate resolved paths are deduplicated.\n- Tool name conflicts are rejected against built-ins and already-loaded custom tools.\n- `.md` and `.json` files are discovered as tool metadata by some providers, but the executable module loader rejects them as runnable tools.\n- Relative configured paths are resolved from `cwd`; `~` is expanded.\n\n## Module contract\n\nA custom tool module must export a function (default export preferred):\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = (pi) => ({\n name: \"repo_stats\",\n label: \"Repo Stats\",\n description: \"Counts tracked TypeScript files\",\n parameters: pi.typebox.Type.Object({\n  glob: pi.typebox.Type.Optional(pi.typebox.Type.String({ default: \"**/*.ts\" })),\n }),\n\n async execute(toolCallId, params, onUpdate, ctx, signal) {\n  onUpdate?.({\n   content: [{ type: \"text\", text: \"Scanning files...\" }],\n   details: { phase: \"scan\" },\n  });\n\n  const result = await pi.exec(\"git\", [\"ls-files\", params.glob ?? \"**/*.ts\"], { signal, cwd: pi.cwd });\n  if (result.killed) {\n   throw new Error(\"Scan was cancelled\");\n  }\n  if (result.code !== 0) {\n   throw new Error(result.stderr || \"git ls-files failed\");\n  }\n\n  const files = result.stdout.split(\"\\n\").filter(Boolean);\n  return {\n   content: [{ type: \"text\", text: `Found ${files.length} files` }],\n   details: { count: files.length, sample: files.slice(0, 10) },\n  };\n },\n\n onSession(event) {\n  if (event.reason === \"shutdown\") {\n   // cleanup resources if needed\n  }\n },\n});\n\nexport default factory;\n```\n\nFactory return type:\n\n- `CustomTool`\n- `CustomTool[]`\n- `Promise<CustomTool | CustomTool[]>`\n\n## API surface passed to factories (`CustomToolAPI`)\n\nFrom `types.ts` and `loader.ts`:\n\n- `cwd`: host working directory\n- `exec(command, args, options?)`: process execution helper\n- `ui`: UI context (can be no-op in headless modes)\n- `hasUI`: `false` in non-interactive flows\n- `logger`: shared file logger\n- `typebox`: injected `@sinclair/typebox`\n- `pi`: injected `@f5-sales-demo/xcsh` exports\n- `pushPendingAction(action)`: register a preview action for hidden `resolve` tool (`docs/resolve-tool-runtime.md`)\n\nLoader starts with a no-op UI context and requires host code to call `setUIContext(...)` when real UI is ready.\n\n## Execution contract and typing\n\n`CustomTool.execute` signature:\n\n```ts\nexecute(toolCallId, params, onUpdate, ctx, signal)\n```\n\n- `params` is statically typed from your TypeBox schema via `Static<TParams>`.\n- Runtime argument validation happens before execution in the agent loop.\n- `onUpdate` emits partial results for UI streaming.\n- `ctx` includes session/model state and an `abort()` helper.\n- `signal` carries cancellation.\n\n`CustomToolAdapter` bridges this to the agent tool interface and forwards calls in the correct argument order.\n\n## How tools are exposed to the model\n\n- Tools are wrapped into `AgentTool` instances (`CustomToolAdapter` or extension wrappers).\n- They are inserted into the session tool registry by name.\n- In SDK bootstrap, custom and extension-registered tools are force-included in the initial active set.\n- CLI `--tools` currently validates only built-in tool names; custom tool inclusion is handled through discovery/registration paths and SDK options.\n\n## Rendering hooks\n\nOptional rendering hooks:\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\nRuntime behavior in TUI:\n\n- If hooks exist, tool output is rendered inside a `Box` container.\n- `renderResult` receives `{ expanded, isPartial, spinnerFrame? }`.\n- Renderer errors are caught and logged; UI falls back to default text rendering.\n\n## Session/state handling\n\nOptional `onSession(event, ctx)` receives session lifecycle events, including:\n\n- `start`, `switch`, `branch`, `tree`, `shutdown`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`, `todo_reminder`\n\nUse `ctx.sessionManager` to reconstruct state from history when branch/session context changes.\n\n## Failures and cancellation semantics\n\n### Synchronous/async failures\n\n- Throwing (or rejected promises) in `execute` is treated as tool failure.\n- Agent runtime converts failures into tool result messages with `isError: true` and error text content.\n- With extension wrappers, `tool_result` handlers can further rewrite content/details and even override error status.\n\n### Cancellation\n\n- Agent abort propagates through `AbortSignal` to `execute`.\n- Forward `signal` to subprocess work (`pi.exec(..., { signal })`) for cooperative cancellation.\n- `ctx.abort()` lets a tool request abort of the current agent operation.\n\n### onSession errors\n\n- `onSession` errors are caught and logged as warnings; they do not crash the session.\n\n## Real constraints to design for\n\n- Tool names must be globally unique in the active registry.\n- Prefer deterministic, schema-shaped outputs in `details` for renderer/state reconstruction.\n- Guard UI usage with `pi.hasUI`.\n- Treat `.md`/`.json` in tool directories as metadata, not executable modules.\n",
	"en/runtime-tools/notebook-tool-runtime.md": "---\ntitle: Notebook Tool Runtime Internals\ndescription: Jupyter notebook tool runtime with cell execution, kernel lifecycle, and output rendering.\nsidebar:\n  order: 2\n  label: Notebook tool\n---\n\n# Notebook tool runtime internals\n\nThis document describes the current `notebook` tool implementation and its relationship to the kernel-backed Python runtime.\n\nThe critical distinction: **`notebook` is a JSON notebook editor, not a notebook executor**. It edits `.ipynb` cell sources directly; it does not start or talk to a Python kernel.\n\n## Implementation files\n\n- [`src/tools/notebook.ts`](../../packages/coding-agent/src/tools/notebook.ts)\n- [`src/ipy/executor.ts`](../../packages/coding-agent/src/ipy/executor.ts)\n- [`src/ipy/kernel.ts`](../../packages/coding-agent/src/ipy/kernel.ts)\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts)\n- [`src/tools/python.ts`](../../packages/coding-agent/src/tools/python.ts)\n\n## 1) Runtime boundary: editing vs executing\n\n## `notebook` tool (`src/tools/notebook.ts`)\n\n- Supports `action: edit | insert | delete` on a `.ipynb` file.\n- Resolves path relative to session CWD (`resolveToCwd`).\n- Loads notebook JSON, validates `cells` array, validates `cell_index` bounds.\n- Applies source edits in-memory and writes full notebook JSON back with `JSON.stringify(notebook, null, 1)`.\n- Returns textual summary + structured `details` (`action`, `cellIndex`, `cellType`, `totalCells`, `cellSource`).\n\nNo kernel lifecycle exists in this tool:\n\n- no gateway acquisition\n- no kernel session ID\n- no `execute_request`\n- no stream chunks from kernel channels\n- no rich display capture (`image/png`, JSON display, status MIME)\n\n## Notebook-like execution path (`src/tools/python.ts` + `src/ipy/*`)\n\nWhen the agent needs to run cell-style Python code (sequential cells, persistent state, rich displays), that goes through the **`python` tool**, not `notebook`.\n\nThat path is where kernel modes, restart/cancel behavior, chunk streaming, and output artifact truncation live.\n\n## 2) Notebook cell handling semantics (`notebook` tool)\n\n## Source normalization\n\n`content` is split into `source: string[]` with newline preservation:\n\n- each non-final line keeps trailing `\\n`\n- final line has no forced trailing newline\n\nThis mirrors notebook JSON conventions and avoids accidental line concatenation on later edits.\n\n## Action behavior\n\n- `edit`\n  - replaces `cells[cell_index].source`\n  - preserves existing `cell_type`\n- `insert`\n  - inserts at `[0..cellCount]`\n  - `cell_type` defaults to `code`\n  - code cells initialize `execution_count: null` and `outputs: []`\n  - markdown cells initialize only `metadata` + `source`\n- `delete`\n  - removes `cells[cell_index]`\n  - returns removed `source` in details for renderer preview\n\n## Error surfaces\n\nHard failures are thrown for:\n\n- missing notebook file\n- invalid JSON\n- missing/non-array `cells`\n- out-of-range index (insert and non-insert have different valid ranges)\n- missing `content` for `edit`/`insert`\n\nThese become `Error:` tool responses upstream; renderer uses notebook path + formatted error text.\n\n## 3) Kernel session semantics (where they actually exist)\n\nKernel semantics are implemented in `executePython` / `PythonKernel` and apply to the `python` tool.\n\n## Modes\n\n`PythonKernelMode`:\n\n- `session` (default)\n  - kernels cached in `kernelSessions` map\n  - max 4 sessions; oldest evicted on overflow\n  - idle/dead cleanup every 30s, timeout after 5 minutes\n  - per-session queue serializes execution (`session.queue`)\n- `per-call`\n  - creates kernel for request\n  - executes\n  - always shuts down kernel in `finally`\n\n## Reset behavior\n\n`python` tool passes `reset` only for the first cell in a multi-cell call; later cells always run with `reset: false`.\n\n## Kernel death / restart / retry\n\nIn session mode (`withKernelSession`):\n\n- dead kernel detected by heartbeat (`kernel.isAlive()` check every 5s) or execute failure.\n- pre-run dead state triggers `restartKernelSession`.\n- execute-time crash path retries once: restart kernel, rerun handler.\n- `restartCount > 1` in same session throws `Python kernel restarted too many times in this session`.\n\nStartup retry behavior:\n\n- shared gateway kernel creation retries once on `SharedGatewayCreateError` with HTTP 5xx.\n\nResource exhaustion recovery:\n\n- detects `EMFILE`/`ENFILE`/\"Too many open files\" style failures\n- clears tracked sessions\n- calls `shutdownSharedGateway()`\n- retries kernel session creation once\n\n## 4) Environment/session variable injection\n\nKernel startup receives optional env map from executor:\n\n- `PI_SESSION_FILE` (session state file path)\n- `ARTIFACTS` (artifact directory)\n\n`PythonKernel.#initializeKernelEnvironment(...)` then runs init script inside kernel to:\n\n- `os.chdir(cwd)`\n- inject env entries into `os.environ`\n- prepend cwd to `sys.path` if missing\n\nImplication:\n\n- prelude helpers that read session or artifact context rely on these env vars in Python process state.\n\n## 5) Streaming/chunk and display handling (kernel-backed path)\n\nThe kernel client processes Jupyter protocol messages per execution:\n\n- `stream` -> text chunk to `onChunk`\n- `execute_result` / `display_data` ->\n  - display text chosen by MIME precedence: `text/markdown` > `text/plain` > converted `text/html`\n  - structured outputs captured separately:\n    - `application/json` -> `{ type: \"json\" }`\n    - `image/png` -> `{ type: \"image\" }`\n    - `application/x-xcsh-status` -> `{ type: \"status\" }` (no text emission)\n- `error` -> traceback text pushed to chunk stream + structured error metadata\n- `input_request` -> emits stdin warning text, sends empty `input_reply`, marks stdin requested\n- completion waits for both `execute_reply` and kernel `status=idle`\n\nCancellation/timeout:\n\n- abort signal triggers `interrupt()` (REST `/interrupt` + control-channel `interrupt_request`)\n- result marks `cancelled=true`\n- timeout path annotates output with `Command timed out after <n> seconds`\n\n## 6) Truncation and artifact behavior\n\n`OutputSink` in `src/session/streaming-output.ts` is used by kernel execution paths (`executeWithKernel`):\n\n- sanitizes every chunk (`sanitizeText`)\n- tracks total/output lines and bytes\n- optional artifact spill file (`artifactPath`, `artifactId`)\n- when in-memory buffer exceeds threshold (`DEFAULT_MAX_BYTES` unless overridden):\n  - marks truncated\n  - keeps tail bytes in memory (UTF-8 safe boundary)\n  - can spill full stream to artifact sink\n\n`dump()` returns:\n\n- visible output text (possibly tail-truncated)\n- truncation flag + counts\n- artifact ID (for `artifact://<id>` references)\n\n`python` tool converts this metadata into result truncation notices and TUI warnings.\n\n`notebook` tool does **not** use `OutputSink`; it has no stream/artifact truncation pipeline because it does not execute code.\n\n## 7) Renderer assumptions and formatting\n\n## Notebook renderer (`notebookToolRenderer`)\n\n- call view: status line with action + notebook path + cell/type metadata\n- result view:\n  - success summary derived from `details`\n  - `cellSource` rendered via `renderCodeCell`\n  - markdown cells set language hint `markdown`; other cells have no explicit language override\n  - collapsed code preview limit is `PREVIEW_LIMITS.COLLAPSED_LINES * 2`\n  - supports expanded mode via shared render options\n  - uses render cache keyed by width + expanded state\n\nError rendering assumption:\n\n- if first text content starts with `Error:`, renderer formats as notebook error block.\n\n## Python renderer (for actual execution output)\n\nKernel-backed execution rendering expects:\n\n- per-cell status transitions (`pending/running/complete/error`)\n- optional structured status event section\n- optional JSON output trees\n- truncation warnings + optional `artifact://<id>` pointer\n\nThis renderer behavior is unrelated to `notebook` JSON editing results except that both reuse shared TUI primitives.\n\n## 8) Divergence from plain Python tool behavior\n\nIf \"plain Python tool\" means `python` execution path:\n\n- `python` executes code in a kernel, persists state by mode, streams chunks, captures rich displays, handles interrupts/timeouts, and supports output truncation/artifacts.\n- `notebook` performs deterministic notebook JSON mutations only; no execution, no kernel state, no chunk stream, no display outputs, no artifact pipeline.\n\nIf a workflow needs both:\n\n1. edit notebook source with `notebook`\n2. execute code cells via `python` (manually passing code), not through `notebook`\n\nCurrent implementation does not provide a single tool that both mutates `.ipynb` and executes notebook cells through kernel context.\n",
	"en/runtime-tools/resolve-tool-runtime.md": "---\ntitle: Resolve Tool Runtime Internals\ndescription: Resolve tool runtime for file path resolution, content fetching, and URL-based resource access.\nsidebar:\n  order: 3\n  label: Resolve tool\n---\n\n# Resolve tool runtime internals\n\nThis document explains how preview/apply workflows are modeled in coding-agent and how custom tools can participate via `pushPendingAction`.\n\n## Scope and key files\n\n- [`src/tools/resolve.ts`](../../packages/coding-agent/src/tools/resolve.ts)\n- [`src/tools/pending-action.ts`](../../packages/coding-agent/src/tools/pending-action.ts)\n- [`src/tools/ast-edit.ts`](../../packages/coding-agent/src/tools/ast-edit.ts)\n- [`src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts)\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n\n## What `resolve` does\n\n`resolve` is a hidden tool that finalizes a pending preview action.\n\n- `action: \"apply\"` executes `apply(reason)` on the pending action and persists changes.\n- `action: \"discard\"` invokes `reject(reason)` if provided; otherwise drops the action with a default \"Discarded\" message.\n\nIf no pending action exists, `resolve` fails with:\n\n- `No pending action to resolve. Nothing to apply or discard.`\n\n## Pending actions are a stack (LIFO)\n\nPending actions are stored in `PendingActionStore` as a push/pop stack:\n\n- `push(action)` adds a new pending action on top.\n- `peek()` inspects the current top action.\n- `pop()` removes and returns the top action.\n- `hasPending` indicates whether the stack is non-empty.\n\n`resolve` always consumes the **topmost** pending action first (`pop()`), so multiple preview-producing tools resolve in reverse order of registration.\n\n## Built-in producer example (`ast_edit`)\n\n`ast_edit` previews structural replacements first. When the preview has replacements and is not applied yet, it pushes a pending action that contains:\n\n- label (human-readable summary)\n- `sourceToolName` (`ast_edit`)\n- `apply(reason: string)` callback that reruns AST edit with `dryRun: false`\n\n`resolve(action=\"apply\", reason=\"...\")` passes `reason` into this callback.\n\n## Custom tools: `pushPendingAction`\n\nCustom tools can register resolve-compatible pending actions through `CustomToolAPI.pushPendingAction(...)`.\n\n`CustomToolPendingAction`:\n\n- `label: string` (required)\n- `apply(reason: string): Promise<AgentToolResult<unknown>>` (required) — invoked on apply; `reason` is the string passed to `resolve`\n- `reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>` (optional) — invoked on discard; return value replaces the default \"Discarded\" message if provided\n- `details?: unknown` (optional)\n- `sourceToolName?: string` (optional, defaults to `\"custom_tool\"`)\n\n### Minimal usage example\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = pi => ({\n name: \"batch_rename_preview\",\n label: \"Batch Rename Preview\",\n description: \"Previews renames and defers commit to resolve\",\n parameters: pi.typebox.Type.Object({\n  files: pi.typebox.Type.Array(pi.typebox.Type.String()),\n }),\n\n async execute(_toolCallId, params) {\n  const previewSummary = `Prepared rename plan for ${params.files.length} files`;\n\n  pi.pushPendingAction({\n   label: `Batch rename: ${params.files.length} files`,\n   sourceToolName: \"batch_rename_preview\",\n   apply: async (reason) => {\n    // apply writes here\n    return {\n     content: [{ type: \"text\", text: `Applied batch rename. Reason: ${reason}` }],\n    };\n   },\n   reject: async (reason) => {\n    // optional: cleanup or notify on discard\n    return {\n     content: [{ type: \"text\", text: `Discarded batch rename. Reason: ${reason}` }],\n    };\n   },\n  });\n\n  return {\n   content: [{ type: \"text\", text: `${previewSummary}. Call resolve to apply or discard.` }],\n  };\n },\n});\n\nexport default factory;\n```\n\n## Runtime availability and failures\n\n`pushPendingAction` is wired by the custom tool loader using the active session `PendingActionStore`.\n\nIf the runtime has no pending-action store, `pushPendingAction` throws:\n\n- `Pending action store unavailable for custom tools in this runtime.`\n\n## Tool-choice behavior\n\nWhen `PendingActionStore.hasPending` is true, the agent runtime biases tool choice to `resolve` so pending previews are explicitly finalized before normal tool flow continues.\n\n## Developer guidance\n\n- Use pending actions only for destructive or high-impact operations that should support explicit apply/discard.\n- Keep `label` concise and specific; it is shown in resolve renderer output.\n- Ensure `apply(reason)` is deterministic and idempotent enough for one-shot execution; `reason` is informational and should not change behavior.\n- Implement `reject(reason)` when the discard needs cleanup (temp state, locks, notifications); omit it for stateless previews where the default message suffices.\n- If your tool can stage multiple previews, remember LIFO semantics: latest pushed action resolves first.\n",
	"en/runtime-tools/slash-command-internals.md": "---\ntitle: Slash Command Internals\ndescription: Slash command system internals with registration, argument parsing, and execution dispatch.\nsidebar:\n  order: 5\n  label: Slash commands\n---\n\n# Slash command internals\n\nThis document describes how slash commands are discovered, deduplicated, surfaced in interactive mode, and expanded at prompt time in `coding-agent`.\n\n## Implementation files\n\n- [`src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n- [`src/capability/slash-command.ts`](../../packages/coding-agent/src/capability/slash-command.ts)\n- [`src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`src/discovery/claude.ts`](../../packages/coding-agent/src/discovery/claude.ts)\n- [`src/discovery/codex.ts`](../../packages/coding-agent/src/discovery/codex.ts)\n- [`src/discovery/claude-plugins.ts`](../../packages/coding-agent/src/discovery/claude-plugins.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n\n## 1) Discovery model\n\nSlash commands are a capability (`id: \"slash-commands\"`) keyed by command name (`key: cmd => cmd.name`).\n\nThe capability registry loads all registered providers, sorted by provider priority descending, and deduplicates by key with **first wins** semantics.\n\n### Provider precedence\n\nCurrent slash-command providers and priorities:\n\n1. `native` (OMP) — priority `100`\n2. `claude` — priority `80`\n3. `claude-plugins` — priority `70`\n4. `codex` — priority `70`\n\nTie behavior: equal-priority providers keep registration order. Current import order registers `claude-plugins` before `codex`, so plugin commands win over codex commands on name collisions.\n\n### Name-collision behavior\n\nFor `slash-commands`, collisions are resolved strictly by capability dedup:\n\n- highest-precedence item is kept in `result.items`\n- lower-precedence duplicates remain only in `result.all` and are marked `_shadowed = true`\n\nThis applies across providers and also within a provider if it returns duplicate names.\n\n### File scanning behavior\n\nProviders mostly use `loadFilesFromDir(...)`, which currently:\n\n- defaults to non-recursive matching (`*.md`)\n- uses native glob with `gitignore: true`, `hidden: false`\n- reads each matched file and transforms it into a `SlashCommand`\n\nSo hidden files/directories are not loaded, and ignored paths are skipped.\n\n## 2) Provider-specific source paths and local precedence\n\n## `native` provider (`builtin.ts`)\n\nSearch roots come from `.xcsh` directories:\n\n- project: `<cwd>/.xcsh/commands/*.md`\n- user: `~/.xcsh/agent/commands/*.md`\n\n`getConfigDirs()` returns project first, then user, so **project native commands beat user native commands** when names collide.\n\n## `claude` provider (`claude.ts`)\n\nLoads:\n\n- user: `~/.claude/commands/*.md`\n- project: `<cwd>/.claude/commands/*.md`\n\nThe provider pushes user items before project items, so **user Claude commands beat project Claude commands** on same-name collisions inside this provider.\n\n## `codex` provider (`codex.ts`)\n\nLoads:\n\n- user: `~/.codex/commands/*.md`\n- project: `<cwd>/.codex/commands/*.md`\n\nBoth sides are loaded then flattened in user-first order, so **user Codex commands beat project Codex commands** on collisions.\n\nCodex command content is parsed with frontmatter stripping (`parseFrontmatter`), and command name can be overridden by frontmatter `name`; otherwise filename is used.\n\n## `claude-plugins` provider (`claude-plugins.ts`)\n\nLoads plugin command roots from `~/.claude/plugins/installed_plugins.json`, then scans `<pluginRoot>/commands/*.md`.\n\nOrdering follows registry iteration order and per-plugin entry order from that JSON data. There is no additional sort step.\n\n## 3) Materialization to runtime `FileSlashCommand`\n\n`loadSlashCommands()` in `src/extensibility/slash-commands.ts` converts capability items into `FileSlashCommand` objects used at prompt time.\n\nFor each command:\n\n1. parse frontmatter/body (`parseFrontmatter`)\n2. description source:\n   - `frontmatter.description` if present\n   - else first non-empty body line (trimmed, max 60 chars with `...`)\n3. keep parsed body as executable template content\n4. compute a display source string like `via Claude Code Project`\n\nFrontmatter parse severity is source-dependent:\n\n- `native` level -> parse errors are `fatal`\n- `user`/`project` levels -> parse errors are `warn` with fallback parsing\n\n### Bundled fallback commands\n\nAfter filesystem/provider commands, embedded command templates are appended (`EMBEDDED_COMMAND_TEMPLATES`) if their names are not already present.\n\nCurrent embedded set comes from `src/task/commands.ts` and is used as a fallback (`source: \"bundled\"`).\n\n## 4) Interactive mode: where command lists come from\n\nInteractive mode combines multiple command sources for autocomplete and command routing.\n\nAt construction time it builds a pending command list from:\n\n- built-ins (`BUILTIN_SLASH_COMMANDS`, includes argument completion and inline hints for selected commands)\n- extension-registered slash commands (`extensionRunner.getRegisteredCommands(...)`)\n- TypeScript custom commands (`session.customCommands`), mapped to slash command labels\n- optional skill commands (`/skill:<name>`) when `skills.enableSkillCommands` is enabled\n\nThen `init()` calls `refreshSlashCommandState(...)` to load file-based commands and install one `CombinedAutocompleteProvider` containing:\n\n- pending commands above\n- discovered file-based commands\n\n`refreshSlashCommandState(...)` also updates `session.setSlashCommands(...)` so prompt expansion uses the same discovered file command set.\n\n### Refresh lifecycle\n\nSlash command state is refreshed:\n\n- during interactive init\n- after `/move` changes working directory (`handleMoveCommand` calls `resetCapabilities()` then `refreshSlashCommandState(newCwd)`)\n\nThere is no continuous file watcher for command directories.\n\n### Other surfacing\n\nThe Extensions dashboard also loads `slash-commands` capability and displays active/shadowed command entries, including `_shadowed` duplicates.\n\n## 5) Prompt pipeline placement\n\n`AgentSession.prompt(...)` slash handling order (when `expandPromptTemplates !== false`):\n\n1. **Extension commands** (`#tryExecuteExtensionCommand`)  \n   If `/name` matches extension-registered command, handler executes immediately and prompt returns.\n2. **TypeScript custom commands** (`#tryExecuteCustomCommand`)  \n   Boundary only: if matched, it executes and may return:\n   - `string` -> replace prompt text with that string\n   - `void/undefined` -> treated as handled; no LLM prompt\n3. **File-based slash commands** (`expandSlashCommand`)  \n   If text still starts with `/`, attempt markdown command expansion.\n4. **Prompt templates** (`expandPromptTemplate`)  \n   Applied after slash/custom processing.\n5. **Delivery**\n   - idle: prompt is sent immediately to agent\n   - streaming: prompt is queued as steer/follow-up depending on `streamingBehavior`\n\nThis is why slash command expansion sits before prompt-template expansion, and why custom commands can transform away the leading slash before file-command matching.\n\n## 6) Expansion semantics for file-based slash commands\n\n`expandSlashCommand(text, fileCommands)` behavior:\n\n- only runs when text begins with `/`\n- parses command name from first token after `/`\n- parses args from remaining text via `parseCommandArgs`\n- finds exact name match in loaded `fileCommands`\n- if matched, applies:\n  - positional replacement: `$1`, `$2`, ...\n  - aggregate replacement: `$ARGUMENTS` and `$@`\n  - then template rendering via `prompt.render` with `{ args, ARGUMENTS, arguments }`\n- if no match, returns original text unchanged\n\n### `parseCommandArgs` caveats\n\nThe parser is simple quote-aware splitting:\n\n- supports `'single'` and `\"double\"` quoting to keep spaces\n- strips quote delimiters\n- does not implement backslash escaping rules\n- unmatched quote is not an error; parser consumes until end\n\n## 7) Unknown `/...` behavior\n\nUnknown slash input is **not rejected** by core slash logic.\n\nIf command is not handled by extension/custom/file layers, `expandSlashCommand` returns original text, and the literal `/...` prompt proceeds through normal prompt-template expansion and LLM delivery.\n\nInteractive mode separately hard-handles many built-ins in `InputController` (for example `/settings`, `/model`, `/mcp`, `/move`, `/exit`). Those are consumed before `session.prompt(...)` and therefore never reach file-command expansion in that path.\n\n## 8) Streaming-time differences vs idle\n\n## Idle path\n\n- `session.prompt(\"/x ...\")` runs command pipeline and either executes command immediately or sends expanded text directly.\n\n## Streaming path (`session.isStreaming === true`)\n\n- `prompt(...)` still runs extension/custom/file/template transforms first\n- then requires `streamingBehavior`:\n  - `\"steer\"` -> queue interrupt message (`agent.steer`)\n  - `\"followUp\"` -> queue post-turn message (`agent.followUp`)\n- if `streamingBehavior` is omitted, prompt throws an error\n\n### Important command-specific streaming behavior\n\n- Extension commands are executed immediately even during streaming (not queued as text).\n- `steer(...)`/`followUp(...)` helper methods reject extension commands (`#throwIfExtensionCommand`) to avoid queuing command text for handlers that must run synchronously.\n- Compaction queue replay uses `isKnownSlashCommand(...)` to decide whether queued entries should be replayed via `session.prompt(...)` (for known slash commands) vs raw steer/follow-up methods.\n\n## 9) Error handling and failure surfaces\n\n- Provider load failures are isolated; registry collects warnings and continues with other providers.\n- Invalid slash command items (missing name/path/content or invalid level) are dropped by capability validation.\n- Frontmatter parse failures:\n  - native commands: fatal parse error bubbles\n  - non-native commands: warning + fallback key/value parse\n- Extension/custom command handler exceptions are caught and reported via extension error channel (or logger fallback for custom commands without extension runner), and treated as handled (no unintended fallback execution).\n",
	"en/runtime-tools/task-agent-discovery.md": "---\ntitle: Task Agent Discovery and Selection\ndescription: Task agent discovery and selection logic for routing work to specialized subagent types.\nsidebar:\n  order: 6\n  label: Task agent discovery\n---\n\n# Task Agent Discovery and Selection\n\nThis document describes how the task subsystem discovers agent definitions, merges multiple sources, and resolves a requested agent at execution time.\n\nIt covers runtime behavior as implemented today, including precedence, invalid-definition handling, and spawn/depth constraints that can make an agent effectively unavailable.\n\n## Implementation files\n\n- [`src/task/discovery.ts`](../../packages/coding-agent/src/task/discovery.ts)\n- [`src/task/agents.ts`](../../packages/coding-agent/src/task/agents.ts)\n- [`src/task/types.ts`](../../packages/coding-agent/src/task/types.ts)\n- [`src/task/index.ts`](../../packages/coding-agent/src/task/index.ts)\n- [`src/task/commands.ts`](../../packages/coding-agent/src/task/commands.ts)\n- [`src/prompts/agents/task.md`](../../packages/coding-agent/src/prompts/agents/task.md)\n- [`src/prompts/tools/task.md`](../../packages/coding-agent/src/prompts/tools/task.md)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/config.ts`](../../packages/coding-agent/src/config.ts)\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts)\n\n---\n\n## Agent definition shape\n\nTask agents normalize into `AgentDefinition` (`src/task/types.ts`):\n\n- `name`, `description`, `systemPrompt` (required for a valid loaded agent)\n- optional `tools`, `spawns`, `model`, `thinkingLevel`, `output`\n- `source`: `\"bundled\" | \"user\" | \"project\"`\n- optional `filePath`\n\nParsing comes from frontmatter via `parseAgentFields()` (`src/discovery/helpers.ts`):\n\n- missing `name` or `description` => invalid (`null`), caller treats as parse failure\n- `tools` accepts CSV or array; if provided, `submit_result` is auto-added\n- `spawns` accepts `*`, CSV, or array\n- backward-compat behavior: if `spawns` missing but `tools` includes `task`, `spawns` becomes `*`\n- `output` is passed through as opaque schema data\n\n## Bundled agents\n\nBundled agents are embedded at build time (`src/task/agents.ts`) using text imports.\n\n`EMBEDDED_AGENT_DEFS` defines:\n\n- `explore`, `plan`, `designer`, `reviewer` from prompt files\n- `task` and `quick_task` from shared `task.md` body plus injected frontmatter\n\nLoading path:\n\n1. `loadBundledAgents()` parses embedded markdown with `parseAgent(..., \"bundled\", \"fatal\")`\n2. results are cached in-memory (`bundledAgentsCache`)\n3. `clearBundledAgentsCache()` is test-only cache reset\n\nBecause bundled parsing uses `level: \"fatal\"`, malformed bundled frontmatter throws and can fail discovery entirely.\n\n## Filesystem and plugin discovery\n\n`discoverAgents(cwd, home)` (`src/task/discovery.ts`) merges agents from multiple places before appending bundled definitions.\n\n### Discovery inputs\n\n1. User config agent dirs from `getConfigDirs(\"agents\", { project: false })`\n2. Nearest project agent dirs from `findAllNearestProjectConfigDirs(\"agents\", cwd)`\n3. Claude plugin roots (`listClaudePluginRoots(home)`) with `agents/` subdirs\n4. Bundled agents (`loadBundledAgents()`)\n\n### Actual source order\n\nSource-family order comes from `getConfigDirs(\"\", { project: false })`, which is derived from `priorityList` in `src/config.ts`:\n\n1. `.xcsh`\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nFor each source family, discovery order is:\n\n1. nearest project dir for that source (if found)\n2. user dir for that source\n\nAfter all source-family dirs, plugin `agents/` dirs are appended (project-scope plugins first, then user-scope).\n\nBundled agents are appended last.\n\n### Important caveat: stale comments vs current code\n\n`discovery.ts` header comments still mention `.pi` and do not mention `.codex`/`.gemini`. Actual runtime order is driven by `src/config.ts` and currently uses `.xcsh`, `.claude`, `.codex`, `.gemini`.\n\n## Merge and collision rules\n\nDiscovery uses first-wins dedup by exact `agent.name`:\n\n- A `Set<string>` tracks seen names.\n- Loaded agents are flattened in directory order and kept only if name unseen.\n- Bundled agents are filtered against the same set and only added if still unseen.\n\nImplications:\n\n- Project overrides user for same source family.\n- Higher-priority source family overrides lower (`.xcsh` before `.claude`, etc.).\n- Non-bundled agents override bundled agents with the same name.\n- Name matching is case-sensitive (`Task` and `task` are distinct).\n- Within one directory, markdown files are read in lexicographic filename order before dedup.\n\n## Invalid/missing agent file behavior\n\nPer directory (`loadAgentsFromDir`):\n\n- unreadable/missing directory: treated as empty (`readdir(...).catch(() => [])`)\n- file read or parse failure: warning logged, file skipped\n- parse path uses `parseAgent(..., level: \"warn\")`\n\nFrontmatter failure behavior comes from `parseFrontmatter`:\n\n- parse error at `warn` level logs warning\n- parser falls back to a simple `key: value` line parser\n- if required fields are still missing, `parseAgentFields` fails, then `AgentParsingError` is thrown and caught by caller (file skipped)\n\nNet effect: one bad custom agent file does not abort discovery of other files.\n\n## Agent lookup and selection\n\nLookup is exact-name linear search:\n\n- `getAgent(agents, name)` => `agents.find(a => a.name === name)`\n\nIn task execution (`TaskTool.execute`):\n\n1. agents are rediscovered at call time (`discoverAgents(this.session.cwd)`)\n2. requested `params.agent` is resolved through `getAgent`\n3. missing agent returns immediate tool response:\n   - `Unknown agent \"...\". Available: ...`\n   - no subprocess runs\n\n### Description vs execution-time discovery\n\n`TaskTool.create()` builds the tool description from discovery results at initialization time (`buildDescription`).\n\n`execute()` rediscoveres agents again. So the runtime set can differ from what was listed in the earlier tool description if agent files changed mid-session.\n\n## Structured-output guardrails and schema precedence\n\nRuntime output schema precedence in `TaskTool.execute`:\n\n1. agent frontmatter `output`\n2. task call `params.schema`\n3. parent session `outputSchema`\n\n(`effectiveOutputSchema = effectiveAgent.output ?? outputSchema ?? this.session.outputSchema`)\n\nPrompt-time guardrail text in `src/prompts/tools/task.md` warns about mismatch behavior for structured-output agents (`explore`, `reviewer`): output-format instructions in prose can conflict with built-in schema and produce `null` outputs.\n\nThis is guidance, not hard runtime validation logic in `discoverAgents`.\n\n## Command discovery interaction\n\n`src/task/commands.ts` is parallel infrastructure for workflow commands (not agent definitions), but it follows the same overall pattern:\n\n- discover from capability providers first\n- deduplicate by name with first-wins\n- append bundled commands if still unseen\n- exact-name lookup via `getCommand`\n\nIn `src/task/index.ts`, command helpers are re-exported with agent discovery helpers. Agent discovery itself does not depend on command discovery at runtime.\n\n## Availability constraints beyond discovery\n\nAn agent can be discoverable but still unavailable to run because of execution guardrails.\n\n### Parent spawn policy\n\n`TaskTool.execute` checks `session.getSessionSpawns()`:\n\n- `\"*\"` => allow any\n- `\"\"` => deny all\n- CSV list => allow only listed names\n\nIf denied: immediate `Cannot spawn '...'. Allowed: ...` response.\n\n### Blocked self-recursion env guard\n\n`PI_BLOCKED_AGENT` is read at tool construction. If request matches, execution is rejected with recursion-prevention message.\n\n### Recursion-depth gating (task tool availability inside child sessions)\n\nIn `runSubprocess` (`src/task/executor.ts`):\n\n- depth computed from `taskDepth`\n- `task.maxRecursionDepth` controls cutoff\n- when at max depth:\n  - `task` tool is removed from child tool list\n  - child `spawns` env is set to empty\n\nSo deeper levels cannot spawn further tasks even if the agent definition includes `spawns`.\n\n## Plan mode caveat (current implementation)\n\n`TaskTool.execute` computes an `effectiveAgent` for plan mode (prepends plan-mode prompt, forces read-only tool subset, clears spawns), but `runSubprocess` is called with `agent` rather than `effectiveAgent`.\n\nCurrent effect:\n\n- model override / thinking level / output schema are derived from `effectiveAgent`\n- system prompt and tool/spawn restrictions from `effectiveAgent` are not passed through in this call path\n\nThis is an implementation caveat worth knowing when reading plan-mode behavior expectations.\n",
	"en/sessions/compaction.md": "---\ntitle: Compaction and Branch Summaries\ndescription: Context window compaction and branch summary generation for long-lived sessions.\nsidebar:\n  order: 5\n  label: Compaction\n---\n\n# Compaction and Branch Summaries\n\nCompaction and branch summaries are the two mechanisms that keep long sessions usable without losing prior work context.\n\n- **Compaction** rewrites old history into a summary on the current branch.\n- **Branch summary** captures abandoned branch context during `/tree` navigation.\n\nBoth are persisted as session entries and converted back into user-context messages when rebuilding LLM input.\n\n## Key implementation files\n\n- `src/session/compaction/compaction.ts`\n- `src/session/compaction/branch-summarization.ts`\n- `src/session/compaction/pruning.ts`\n- `src/session/compaction/utils.ts`\n- `src/session/session-manager.ts`\n- `src/session/agent-session.ts`\n- `src/session/messages.ts`\n- `src/extensibility/hooks/types.ts`\n- `src/config/settings-schema.ts`\n\n## Session entry model\n\nCompaction and branch summaries are first-class session entries, not plain assistant/user messages.\n\n- `CompactionEntry`\n  - `type: \"compaction\"`\n  - `summary`, optional `shortSummary`\n  - `firstKeptEntryId` (compaction boundary)\n  - `tokensBefore`\n  - optional `details`, `preserveData`, `fromExtension`\n- `BranchSummaryEntry`\n  - `type: \"branch_summary\"`\n  - `fromId`, `summary`\n  - optional `details`, `fromExtension`\n\nWhen context is rebuilt (`buildSessionContext`):\n\n1. Latest compaction on the active path is converted to one `compactionSummary` message.\n2. Kept entries from `firstKeptEntryId` to the compaction point are re-included.\n3. Later entries on the path are appended.\n4. `branch_summary` entries are converted to `branchSummary` messages.\n5. `custom_message` entries are converted to `custom` messages.\n\nThose custom roles are then transformed into LLM-facing user messages in `convertToLlm()` using the static templates:\n\n- `prompts/compaction/compaction-summary-context.md`\n- `prompts/compaction/branch-summary-context.md`\n\n## Compaction pipeline\n\n### Triggers\n\nCompaction can run in three ways:\n\n1. **Manual**: `/compact [instructions]` calls `AgentSession.compact(...)`.\n2. **Automatic overflow recovery**: after an assistant error that matches context overflow.\n3. **Automatic threshold compaction**: after a successful turn when context exceeds threshold.\n\n### Compaction shape (visual)\n\n```text\nBefore compaction:\n\n  entry:  0     1     2     3      4     5     6      7      8     9\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┘\n                └────────┬───────┘ └──────────────┬──────────────┘\n               messagesToSummarize            kept messages\n                                   ↑\n                          firstKeptEntryId (entry 4)\n\nAfter compaction (new entry appended):\n\n  entry:  0     1     2     3      4     5     6      7      8     9      10\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┬─────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │ cmp │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┴─────┘\n               └──────────┬──────┘ └──────────────────────┬───────────────────┘\n                 not sent to LLM                    sent to LLM\n                                                         ↑\n                                              starts from firstKeptEntryId\n\nWhat the LLM sees:\n\n  ┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐\n  │ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │\n  └────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘\n       ↑         ↑      └─────────────────┬────────────────┘\n    prompt   from cmp          messages from firstKeptEntryId\n```\n\n### Overflow-retry vs threshold compaction\n\nThe two automatic paths are intentionally different:\n\n- **Overflow-retry compaction**\n  - Trigger: current-model assistant error is detected as context overflow.\n  - The failing assistant error message is removed from active agent state before retry.\n  - Auto compaction runs with `reason: \"overflow\"` and `willRetry: true`.\n  - On success, agent auto-continues (`agent.continue()`) after compaction.\n\n- **Threshold compaction**\n  - Trigger: `contextTokens > contextWindow - compaction.reserveTokens`.\n  - Runs with `reason: \"threshold\"` and `willRetry: false`.\n  - On success, if `compaction.autoContinue !== false`, injects a synthetic prompt:\n    - `\"Continue if you have next steps.\"`\n\n### Pre-compaction pruning\n\nBefore compaction checks, tool-result pruning may run (`pruneToolOutputs`).\n\nDefault prune policy:\n\n- Protect newest `40_000` tool-output tokens.\n- Require at least `20_000` total estimated savings.\n- Never prune tool results from `skill` or `read`.\n\nPruned tool results are replaced with:\n\n- `[Output truncated - N tokens]`\n\nIf pruning changes entries, session storage is rewritten and agent message state is refreshed before compaction decisions.\n\n### Boundary and cut-point logic\n\n`prepareCompaction()` only considers entries since the last compaction entry (if any).\n\n1. Find previous compaction index.\n2. Compute `boundaryStart = prevCompactionIndex + 1`.\n3. Adapt `keepRecentTokens` using measured usage ratio when available.\n4. Run `findCutPoint()` over the boundary window.\n\nValid cut points include:\n\n- message entries with roles: `user`, `assistant`, `bashExecution`, `hookMessage`, `branchSummary`, `compactionSummary`\n- `custom_message` entries\n- `branch_summary` entries\n\nHard rule: never cut at `toolResult`.\n\nIf there are non-message metadata entries immediately before the cut point (`model_change`, `thinking_level_change`, labels, etc.), they are pulled into the kept region by moving cut index backward until a message or compaction boundary is hit.\n\n### Split-turn handling\n\nIf cut point is not at a user-turn start, compaction treats it as a split turn.\n\nTurn start detection treats these as user-turn boundaries:\n\n- `message.role === \"user\"`\n- `message.role === \"bashExecution\"`\n- `custom_message` entry\n- `branch_summary` entry\n\nSplit-turn compaction generates two summaries:\n\n1. History summary (`messagesToSummarize`)\n2. Turn-prefix summary (`turnPrefixMessages`)\n\nFinal stored summary is merged as:\n\n```markdown\n<history summary>\n\n---\n\n**Turn Context (split turn):**\n\n<turn prefix summary>\n```\n\n### Summary generation\n\n`compact(...)` builds summaries from serialized conversation text:\n\n1. Convert messages via `convertToLlm()`.\n2. Serialize with `serializeConversation()`.\n3. Wrap in `<conversation>...</conversation>`.\n4. Optionally include `<previous-summary>...</previous-summary>`.\n5. Optionally inject hook context as `<additional-context>` list.\n6. Execute summarization prompt with `SUMMARIZATION_SYSTEM_PROMPT`.\n\nPrompt selection:\n\n- first compaction: `compaction-summary.md`\n- iterative compaction with prior summary: `compaction-update-summary.md`\n- split-turn second pass: `compaction-turn-prefix.md`\n- short UI summary: `compaction-short-summary.md`\n\nRemote summarization mode:\n\n- If `compaction.remoteEndpoint` is set, compaction POSTs:\n  - `{ systemPrompt, prompt }`\n- Expects JSON containing at least `{ summary }`.\n\n### File-operation context in summaries\n\nCompaction tracks cumulative file activity using assistant tool calls:\n\n- `read(path)` → read set\n- `write(path)` → modified set\n- `edit(path)` → modified set\n\nCumulative behavior:\n\n- Includes prior compaction details only when prior entry is pi-generated (`fromExtension !== true`).\n- In split turns, includes turn-prefix file ops too.\n- `readFiles` excludes files also modified.\n\nSummary text gets file tags appended via prompt template:\n\n```xml\n<read-files>\n...\n</read-files>\n<modified-files>\n...\n</modified-files>\n```\n\n### Persist and reload\n\nAfter summary generation (or hook-provided summary), agent session:\n\n1. Appends `CompactionEntry` with `appendCompaction(...)`.\n2. Rebuilds context via `buildSessionContext()`.\n3. Replaces live agent messages with rebuilt context.\n4. Emits `session_compact` hook event.\n\n## Branch summarization pipeline\n\nBranch summarization is tied to tree navigation, not token overflow.\n\n### Trigger\n\nDuring `navigateTree(...)`:\n\n1. Compute abandoned entries from old leaf to common ancestor using `collectEntriesForBranchSummary(...)`.\n2. If caller requested summary (`options.summarize`), generate summary before switching leaf.\n3. If summary exists, attach it at the navigation target using `branchWithSummary(...)`.\n\nOperationally this is commonly driven by `/tree` flow when `branchSummary.enabled` is enabled.\n\n### Branch switch shape (visual)\n\n```text\nTree before navigation:\n\n         ┌─ B ─ C ─ D (old leaf, being abandoned)\n    A ───┤\n         └─ E ─ F (target)\n\nCommon ancestor: A\nEntries to summarize: B, C, D\n\nAfter navigation with summary:\n\n         ┌─ B ─ C ─ D ─ [summary of B,C,D]\n    A ───┤\n         └─ E ─ F (new leaf)\n```\n\n### Preparation and token budget\n\n`generateBranchSummary(...)` computes budget as:\n\n- `tokenBudget = model.contextWindow - branchSummary.reserveTokens`\n\n`prepareBranchEntries(...)` then:\n\n1. First pass: collect cumulative file ops from all summarized entries, including prior pi-generated `branch_summary` details.\n2. Second pass: walk newest → oldest, adding messages until token budget is reached.\n3. Prefer preserving recent context.\n4. May still include large summary entries near budget edge for continuity.\n\nCompaction entries are included as messages (`compactionSummary`) during branch summarization input.\n\n### Summary generation and persistence\n\nBranch summarization:\n\n1. Converts and serializes selected messages.\n2. Wraps in `<conversation>`.\n3. Uses custom instructions if supplied, otherwise `branch-summary.md`.\n4. Calls summarization model with `SUMMARIZATION_SYSTEM_PROMPT`.\n5. Prepends `branch-summary-preamble.md`.\n6. Appends file-operation tags.\n\nResult is stored as `BranchSummaryEntry` with optional details (`readFiles`, `modifiedFiles`).\n\n## Extension and hook touchpoints\n\n### `session_before_compact`\n\nPre-compaction hook.\n\nCan:\n\n- cancel compaction (`{ cancel: true }`)\n- provide full custom compaction payload (`{ compaction: CompactionResult }`)\n\n### `session.compacting`\n\nPrompt/context customization hook for default compaction.\n\nCan return:\n\n- `prompt` (override base summary prompt)\n- `context` (extra context lines injected into `<additional-context>`)\n- `preserveData` (stored on compaction entry)\n\n### `session_compact`\n\nPost-compaction notification with saved `compactionEntry` and `fromExtension` flag.\n\n### `session_before_tree`\n\nRuns on tree navigation before default branch summary generation.\n\nCan:\n\n- cancel navigation\n- provide custom `{ summary: { summary, details } }` used when user requested summarization\n\n### `session_tree`\n\nPost-navigation event exposing new/old leaf and optional summary entry.\n\n## Runtime behavior and failure semantics\n\n- Manual compaction aborts current agent operation first.\n- `abortCompaction()` cancels both manual and auto-compaction controllers.\n- Auto compaction emits start/end session events for UI/state updates.\n- Auto compaction can try multiple model candidates and retry transient failures.\n- Overflow errors are excluded from generic retry path because they are handled by compaction.\n- If auto-compaction fails:\n  - overflow path emits `Context overflow recovery failed: ...`\n  - threshold path emits `Auto-compaction failed: ...`\n- Branch summarization can be cancelled via abort signal (e.g., Escape), returning canceled/aborted navigation result.\n\n## Settings and defaults\n\nFrom `settings-schema.ts`:\n\n- `compaction.enabled` = `true`\n- `compaction.reserveTokens` = `16384`\n- `compaction.keepRecentTokens` = `20000`\n- `compaction.autoContinue` = `true`\n- `compaction.remoteEndpoint` = `undefined`\n- `branchSummary.enabled` = `false`\n- `branchSummary.reserveTokens` = `16384`\n\nThese values are consumed at runtime by `AgentSession` and compaction/branch summarization modules.\n",
	"en/sessions/handoff-generation-pipeline.md": "---\ntitle: Handoff Generation Pipeline\ndescription: Handoff generation pipeline for creating portable session summaries for team collaboration.\nsidebar:\n  order: 8\n  label: Handoff pipeline\n---\n\n# `/handoff` generation pipeline\n\nThis document describes how the coding-agent implements `/handoff` today: trigger path, generation prompt, completion capture, session switch, and context reinjection.\n\n## Scope\n\nCovers:\n\n- Interactive `/handoff` command dispatch\n- `AgentSession.handoff()` lifecycle and state transitions\n- How handoff output is captured from assistant output\n- How old/new sessions persist handoff data differently\n- UI behavior for success, cancel, and failure\n\nDoes not cover:\n\n- Generic tree navigation/branch internals\n- Non-handoff session commands (`/new`, `/fork`, `/resume`)\n\n## Implementation files\n\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n\n## Trigger path\n\n1. `/handoff` is declared in builtin slash command metadata (`slash-commands.ts`) with optional inline hint: `[focus instructions]`.\n2. In interactive input handling (`InputController`), submit text matching `/handoff` or `/handoff ...` is intercepted before normal prompt submission.\n3. The editor is cleared and `handleHandoffCommand(customInstructions?)` is called.\n4. `CommandController.handleHandoffCommand` performs a preflight guard using current entries:\n   - Counts `type === \"message\"` entries.\n   - If `< 2`, it warns: `Nothing to hand off (no messages yet)` and returns.\n\nThe same minimum-content guard exists again inside `AgentSession.handoff()` and throws if violated. This duplicates safety at both UI and session layers.\n\n## End-to-end lifecycle\n\n### 1) Start handoff generation\n\n`AgentSession.handoff(customInstructions?)`:\n\n- Reads current branch entries (`sessionManager.getBranch()`)\n- Validates minimum message count (`>= 2`)\n- Creates `#handoffAbortController`\n- Builds a fixed, inline prompt requesting a structured handoff document (`Goal`, `Constraints & Preferences`, `Progress`, `Key Decisions`, `Critical Context`, `Next Steps`)\n- Appends `Additional focus: ...` if custom instructions are provided\n\nPrompt is sent via:\n\n```ts\nawait this.prompt(handoffPrompt, { expandPromptTemplates: false });\n```\n\n`expandPromptTemplates: false` prevents slash/prompt-template expansion of this internal instruction payload.\n\n### 2) Capture completion\n\nBefore sending prompt, `handoff()` subscribes to session events and waits for `agent_end`.\n\nOn `agent_end`, it extracts handoff text from agent state by scanning backward for the most recent `assistant` message, then concatenating all `content` blocks where `type === \"text\"` with `\\n`.\n\nImportant extraction assumptions:\n\n- Only text blocks are used; non-text content is ignored.\n- It assumes the latest assistant message corresponds to handoff generation.\n- It does not parse markdown sections or validate format compliance.\n- If assistant output has no text blocks, handoff is treated as missing.\n\n### 3) Cancellation checks\n\n`handoff()` returns `undefined` when either condition holds:\n\n- no captured handoff text, or\n- `#handoffAbortController.signal.aborted` is true\n\nIt always clears `#handoffAbortController` in `finally`.\n\n### 4) New session creation\n\nIf text was captured and not aborted:\n\n1. Flush current session writer (`sessionManager.flush()`)\n2. Start a brand-new session (`sessionManager.newSession()`)\n3. Reset in-memory agent state (`agent.reset()`)\n4. Rebind `agent.sessionId` to new session id\n5. Clear queued context arrays (`#steeringMessages`, `#followUpMessages`, `#pendingNextTurnMessages`)\n6. Reset todo reminder counter\n\n`newSession()` creates a fresh header and empty entry list (leaf reset to `null`). In the handoff path, no `parentSession` is passed.\n\n### 5) Handoff-context injection\n\nThe generated handoff document is wrapped and appended to the new session as a `custom_message` entry:\n\n```text\n<handoff-context>\n...handoff text...\n</handoff-context>\n\nThe above is a handoff document from a previous session. Use this context to continue the work seamlessly.\n```\n\nInsertion call:\n\n```ts\nthis.sessionManager.appendCustomMessageEntry(\"handoff\", handoffContent, true);\n```\n\nSemantics:\n\n- `customType`: `\"handoff\"`\n- `display`: `true` (visible in TUI rebuild)\n- Entry type: `custom_message` (participates in LLM context)\n\n### 6) Rebuild active agent context\n\nAfter injection:\n\n1. `sessionManager.buildSessionContext()` resolves message list for current leaf\n2. `agent.replaceMessages(sessionContext.messages)` makes the injected handoff message active context\n3. Method returns `{ document: handoffText }`\n\nAt this point, the active LLM context in the new session contains the injected handoff message, not the old transcript.\n\n## Persistence model: old session vs new session\n\n### Old session\n\nDuring generation, normal message persistence remains active. The assistant handoff response is persisted as a regular `message` entry on `message_end`.\n\nResult: the original session contains the visible generated handoff as part of historical transcript.\n\n### New session\n\nAfter session reset, handoff is persisted as `custom_message` with `customType: \"handoff\"`.\n\n`buildSessionContext()` converts this entry into a runtime custom/user-context message via `createCustomMessage(...)`, so it is included in future prompts from the new session.\n\n## Controller/UI behavior\n\n`CommandController.handleHandoffCommand` behavior:\n\n- Calls `await session.handoff(customInstructions)`\n- If result is `undefined`: `showError(\"Handoff cancelled\")`\n- On success:\n  - `rebuildChatFromMessages()` (loads new session context, including injected handoff)\n  - invalidates status line and editor top border\n  - reloads todos\n  - appends success chat line: `New session started with handoff context`\n- On exception:\n  - if message is `\"Handoff cancelled\"` or error name is `AbortError`: `showError(\"Handoff cancelled\")`\n  - otherwise: `showError(\"Handoff failed: <message>\")`\n- Requests render at end\n\n## Cancellation semantics (current behavior)\n\n### Session-level cancellation primitive\n\n`AgentSession` exposes:\n\n- `abortHandoff()` → aborts `#handoffAbortController`\n- `isGeneratingHandoff` → true while controller exists\n\nWhen this abort path is used, the handoff subscriber rejects with `Error(\"Handoff cancelled\")`, and command controller maps it to cancellation UI.\n\n### Interactive `/handoff` path limitation\n\nIn current interactive controller wiring, `/handoff` does not install a dedicated Escape handler that calls `abortHandoff()` (unlike compaction/branch-summary paths that temporarily override `editor.onEscape`).\n\nPractical impact:\n\n- There is session-level cancellation support, but no handoff-specific keybinding hook in the `/handoff` command path.\n- User interruption may still occur through broader agent abort paths, but that is not the same explicit cancellation channel used by `abortHandoff()`.\n\n## Aborted vs failed handoff\n\nCurrent UI classification:\n\n- **Aborted/cancelled**\n  - `abortHandoff()` path triggers `\"Handoff cancelled\"`, or\n  - thrown `AbortError`\n  - UI shows `Handoff cancelled`\n\n- **Failed**\n  - any other thrown error from `handoff()` / prompt pipeline (model/API validation errors, runtime exceptions, etc.)\n  - UI shows `Handoff failed: ...`\n\nAdditional nuance: if generation completes but no text is extracted, `handoff()` returns `undefined` and controller currently reports **cancelled**, not **failed**.\n\n## Short-session and minimum-content guardrails\n\nTwo guards prevent low-signal handoffs:\n\n- UI layer (`handleHandoffCommand`): warns and returns early for `< 2` message entries\n- Session layer (`handoff()`): throws the same condition as an error\n\nThis avoids creating a new session with empty/near-empty handoff context.\n\n## State transition summary\n\nHigh-level state flow:\n\n1. Interactive slash command intercepted\n2. Preflight message-count guard\n3. `#handoffAbortController` created (`isGeneratingHandoff = true`)\n4. Internal handoff prompt submitted (visible in chat as normal assistant generation)\n5. On `agent_end`, last assistant text extracted\n6. If missing/aborted → return `undefined` or cancellation error path\n7. If present:\n   - flush old session\n   - create new empty session\n   - reset runtime queues/counters\n   - append `custom_message(handoff)`\n   - rebuild and replace active agent messages\n8. Controller rebuilds chat UI and announces success\n9. `#handoffAbortController` cleared (`isGeneratingHandoff = false`)\n\n## Known assumptions and limitations\n\n- Handoff extraction is heuristic: \"last assistant text blocks\"; no structural validation.\n- No hard check that generated markdown follows requested section format.\n- Missing extracted text is reported as cancellation in controller UX.\n- `/handoff` interactive flow currently lacks a dedicated Escape→`abortHandoff()` binding.\n- New session lineage metadata (`parentSession`) is not set by this path.\n",
	"en/sessions/memory.md": "---\ntitle: Autonomous Memory\ndescription: Autonomous memory system for persisting user preferences, project context, and feedback across sessions.\nsidebar:\n  order: 7\n  label: Autonomous memory\n---\n\n# Autonomous Memory\n\nWhen enabled, the agent automatically extracts durable knowledge from past sessions and injects a compact summary into each new session. Over time it builds a project-scoped memory store — technical decisions, recurring workflows, pitfalls — that carries forward without manual effort.\n\nDisabled by default. Enable via `/settings` or `config.yml`:\n\n```yaml\nmemories:\n  enabled: true\n```\n\n## Usage\n\n### What gets injected\n\nAt session start, if a memory summary exists for the current project, it is injected into the system prompt as a **Memory Guidance** block. The agent is instructed to:\n\n- Treat memory as heuristic context — useful for process and prior decisions, not authoritative on current repo state.\n- Cite the memory artifact path when memory changes the plan, and pair it with current-repo evidence before acting.\n- Prefer repo state and user instruction when they conflict with memory; treat conflicting memory as stale.\n\n### Reading memory artifacts\n\nThe agent can read memory files directly using `memory://` URLs with the `read` tool:\n\n| URL | Content |\n|---|---|\n| `memory://root` | Compact summary injected at startup |\n| `memory://root/MEMORY.md` | Full long-term memory document |\n| `memory://root/skills/<name>/SKILL.md` | A generated skill playbook |\n\n### `/memory` slash command\n\n| Subcommand | Effect |\n|---|---|\n| `view` | Show the current memory injection payload |\n| `clear` / `reset` | Delete all memory data and generated artifacts |\n| `enqueue` / `rebuild` | Force consolidation to run at next startup |\n\n## How it works\n\nMemories are built by a background pipeline that at startup or manually triggered via slash command.\n\n**Phase 1 — per-session extraction:** For each past session that has changed since it was last processed, a model reads the session history and extracts durable signal: technical decisions, constraints, resolved failures, recurring workflows. Sessions that are too recent, too old, or currently active are skipped. Each extraction produces a raw memory block and a short synopsis for that session.\n\n**Phase 2 — consolidation:** After extraction, a second model pass reads all per-session extractions and produces three outputs written to disk:\n\n- `MEMORY.md` — a curated long-term memory document\n- `memory_summary.md` — the compact text injected at session start\n- `skills/` — reusable procedural playbooks, each in its own subdirectory\n\nPhase 2 uses a lease to prevent double-running when multiple processes start simultaneously. Stale skill directories from prior runs are pruned automatically.\n\nAll output is scanned for secrets before being written to disk.\n\n### Extraction behavior\n\nMemory extraction and consolidation behavior is driven entirely by static prompt files in `src/prompts/memories/`.\n\n| File | Purpose | Variables |\n|---|---|---|\n| `stage_one_system.md` | System prompt for per-session extraction | — |\n| `stage_one_input.md` | User-turn template wrapping session content | `{{thread_id}}`, `{{response_items_json}}` |\n| `consolidation.md` | Prompt for cross-session consolidation | `{{raw_memories}}`, `{{rollout_summaries}}` |\n| `read_path.md` | Memory guidance injected into live sessions | `{{memory_summary}}` |\n\n### Model selection\n\nMemory piggybacks on the model role system.\n\n| Phase | Role | Purpose |\n|---|---|---|\n| Phase 1 (extraction) | `default` | Per-session knowledge extraction |\n| Phase 2 (consolidation) | `smol` | Cross-session synthesis |\n\nIf `smol` is not configured, Phase 2 falls back to the `default` role.\n\n## Configuration\n\n| Setting | Default | Description |\n|---|---|---|\n| `memories.enabled` | `false` | Master switch |\n| `memories.maxRolloutAgeDays` | `30` | Sessions older than this are not processed |\n| `memories.minRolloutIdleHours` | `12` | Sessions active more recently than this are skipped |\n| `memories.maxRolloutsPerStartup` | `64` | Cap on sessions processed in a single startup |\n| `memories.summaryInjectionTokenLimit` | `5000` | Max tokens of the summary injected into the system prompt |\n\nAdditional tuning knobs (concurrency, lease durations, token budgets) are available in config for advanced use.\n\n## Key files\n\n- `src/memories/index.ts` — pipeline orchestration, injection, slash command handling\n- `src/memories/storage.ts` — SQLite-backed job queue and thread registry\n- `src/prompts/memories/` — memory prompt templates\n- `src/internal-urls/memory-protocol.ts` — `memory://` URL handler\n",
	"en/sessions/non-compaction-retry-policy.md": "---\ntitle: Non-Compaction Auto-Retry Policy\ndescription: Auto-retry policy for transient API failures outside the compaction path.\nsidebar:\n  order: 6\n  label: Retry policy\n---\n\n# Non-compaction auto-retry policy\n\nThis document describes the standard API-error retry path in `AgentSession`.\n\nIt explicitly excludes context-overflow recovery via auto-compaction. Overflow is handled by compaction logic and is documented separately in [`compaction.md`](./compaction.md).\n\n## Implementation files\n\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/config/settings-schema.ts`](../../packages/coding-agent/src/config/settings-schema.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts)\n- [`../src/modes/rpc/rpc-client.ts`](../../packages/coding-agent/src/modes/rpc/rpc-client.ts)\n- [`../src/modes/rpc/rpc-types.ts`](../../packages/coding-agent/src/modes/rpc/rpc-types.ts)\n\n## Scope boundary vs compaction\n\nRetry and compaction are checked from the same `agent_end` path, but they are intentionally separated:\n\n1. `agent_end` inspects the last assistant message.\n2. `#isRetryableError(...)` runs first.\n3. If retry is initiated, compaction checks are skipped for that turn.\n4. Context-overflow errors are hard-excluded from retry classification (`isContextOverflow(...)` short-circuits retry).\n5. Overflow therefore falls through to `#checkCompaction(...)` instead of standard retry.\n\nSo: overload/rate/server/network-style failures use this retry policy; context-window overflow uses compaction recovery.\n\n## Retry classification\n\n`#isRetryableError(...)` requires all of the following:\n\n- assistant `stopReason === \"error\"`\n- `errorMessage` exists\n- message is **not** context overflow\n- `errorMessage` matches `#isRetryableErrorMessage(...)`\n\nCurrent retryable pattern set (regex-based):\n\n- overloaded\n- rate limit / usage limit / too many requests\n- HTTP-like server classes: 429, 500, 502, 503, 504\n- service unavailable / server error / internal error\n- connection error / fetch failed\n- `retry delay` wording\n\nThis is string-pattern classification, not typed provider error codes.\n\n## Retry lifecycle and state transitions\n\nSession state used by retry:\n\n- `#retryAttempt: number` (`0` means idle)\n- `#retryPromise: Promise<void> | undefined` (tracks in-progress retry lifecycle)\n- `#retryResolve: (() => void) | undefined` (resolves `#retryPromise`)\n- `#retryAbortController: AbortController | undefined` (cancels backoff sleep)\n\nFlow (`#handleRetryableError`):\n\n1. Read `retry` settings group.\n2. If `retry.enabled === false`, stop immediately (`false`, no retry started).\n3. Increment `#retryAttempt`.\n4. Create `#retryPromise` once (first attempt in a chain).\n5. If attempt exceeded `retry.maxRetries`, emit final failure event and stop.\n6. Compute delay: `retry.baseDelayMs * 2^(attempt-1)`.\n7. For usage-limit errors, parse retry hints and call auth storage (`markUsageLimitReached(...)`); if provider/model switch succeeds, force delay to `0`.\n8. Emit `auto_retry_start`.\n9. Remove the trailing assistant error message from agent runtime state (kept in persisted session history).\n10. Sleep with abort support.\n11. On wake, schedule `agent.continue()` via `setTimeout(..., 0)`.\n\n### What resets retry counters\n\n`#retryAttempt` resets to `0` in these cases:\n\n- first successful non-error, non-aborted assistant message after retries started (emits `auto_retry_end { success: true }`)\n- retry cancellation during backoff sleep\n- max retries exceeded path\n\n`#retryPromise` resolves/clears when retry chain ends (success, cancellation, or max-exceeded), via `#resolveRetry()`.\n\n## Backoff and max-attempt semantics\n\nSettings:\n\n- `retry.enabled` (default `true`)\n- `retry.maxRetries` (default `3`)\n- `retry.baseDelayMs` (default `2000`)\n\nAttempt numbering:\n\n- attempt counter is incremented before max-check\n- start events use current attempt (1-based)\n- max-exceeded end event reports `attempt: this.#retryAttempt - 1` (last attempted retry count)\n\nBackoff sequence with default settings:\n\n- attempt 1: 2000 ms\n- attempt 2: 4000 ms\n- attempt 3: 8000 ms\n\nDelay override inputs are only used in the usage-limit handling path, and only to influence auth-storage model/account switching decision. In the main non-compaction retry path, backoff remains local exponential delay unless switching succeeds (`delayMs = 0`).\n\n## Abort mechanics\n\n### Explicit retry abort\n\n`abortRetry()`:\n\n- aborts `#retryAbortController` (if present)\n- resolves retry promise (`#resolveRetry()`) so awaiters are unblocked\n\nIf abort hits while sleeping, catch path emits:\n\n- `auto_retry_end { success: false, finalError: \"Retry cancelled\" }`\n- resets attempt/controller\n\n### Global operation abort interaction\n\n`abort()` calls `abortRetry()` before aborting the active agent stream. This guarantees retry backoff is cancelled when user issues a general abort.\n\n### TUI interaction\n\nOn `auto_retry_start`, EventController:\n\n- swaps `Esc` handler to `session.abortRetry()`\n- renders loader text: `Retrying (attempt/maxAttempts) in Ns… (esc to cancel)`\n\nOn `auto_retry_end`, it restores prior `Esc` handler and clears loader state.\n\n## Streaming and prompt completion behavior\n\n`prompt()` ultimately waits on `#waitForRetry()` after `agent.prompt(...)` returns.\n\nEffect:\n\n- a prompt call does not fully resolve until any started retry chain finishes (success/failure/cancel)\n- retry lifecycle is part of one logical prompt execution boundary\n\nThis prevents callers from treating a retrying turn as complete too early.\n\n## Controls: settings and RPC\n\n### Configuration knobs\n\nDefined in settings schema under retry group:\n\n- `retry.enabled`\n- `retry.maxRetries`\n- `retry.baseDelayMs`\n\nProgrammatic toggles in session:\n\n- `setAutoRetryEnabled(enabled)` writes `retry.enabled`\n- `autoRetryEnabled` reads `retry.enabled`\n- `isRetrying` reports whether retry lifecycle promise is active\n\n### RPC controls\n\nRPC command surface:\n\n- `set_auto_retry` → `session.setAutoRetryEnabled(command.enabled)`\n- `abort_retry` → `session.abortRetry()`\n\nClient helpers:\n\n- `RpcClient.setAutoRetry(enabled)`\n- `RpcClient.abortRetry()`\n\nBoth commands return success responses; retry progress/failure details come from streamed session events, not command response payloads.\n\n## Event emission and failure surfacing\n\nSession-level retry events:\n\n- `auto_retry_start { attempt, maxAttempts, delayMs, errorMessage }`\n- `auto_retry_end { success, attempt, finalError? }`\n\nPropagation:\n\n- emitted through `AgentSession.subscribe(...)`\n- forwarded to extension runner as extension events\n- in RPC mode, forwarded directly as JSON event objects (`session.subscribe(event => output(event))`)\n- in TUI, consumed by `EventController` for loader/error UI\n\nFinal failure surfacing:\n\n- On max-exceeded or cancellation, `auto_retry_end.success === false`\n- TUI shows: `Retry failed after N attempts: <finalError>`\n- Extensions/hooks receive `auto_retry_end` with same fields\n- RPC consumers receive same event object on stdout stream\n\n## Permanent stop conditions\n\nRetry stops and will not auto-continue when any of these occur:\n\n- `retry.enabled` is false\n- error is not retry-classified\n- error is context overflow (delegated to compaction path)\n- max retries exceeded\n- user cancels retry (`abort_retry` or `Esc` during retry loader)\n- global abort (`abort`) cancels retry first\n\nA new retry chain can still start later on a future retryable error after counters reset.\n\n## Operational caveats\n\n- Classification is regex text matching; provider-specific structured errors are not used here.\n- Retry strips the failing assistant error from **runtime context** before re-continue, but session history still keeps that error entry.\n- `RpcSessionState` currently exposes `autoCompactionEnabled` but not an `autoRetryEnabled` field; RPC callers must track their own toggle state or query settings through other APIs.\n",
	"en/sessions/session-operations-export-share-fork-resume.md": "---\ntitle: \"Session Operations: Export, Dump, Share, Fork, Resume\"\ndescription: Session operations for exporting, sharing, forking, and resuming conversations.\nsidebar:\n  order: 3\n  label: Operations\n---\n\n# Session Operations: export, dump, share, fork, resume/continue\n\nThis document describes operator-visible behavior for session export/share/fork/resume operations as currently implemented.\n\n## Implementation files\n\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/export/html/index.ts`](../../packages/coding-agent/src/export/html/index.ts)\n- [`../src/export/custom-share.ts`](../../packages/coding-agent/src/export/custom-share.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n\n## Operation matrix\n\n| Operation | Entry path | Session mutation | Session file creation/switch | Output artifact |\n|---|---|---|---|---|\n| `/dump` | Interactive slash command | No | No | Clipboard text |\n| `/export [path]` | Interactive slash command | No | No | HTML file |\n| `--export <session.jsonl> [outputPath]` | CLI startup fast-path | No runtime session mutation | No active session; reads target file | HTML file |\n| `/share` | Interactive slash command | No | No | Temp HTML + share URL/gist |\n| `/fork` | Interactive slash command | Yes (active session identity changes) | Creates new session file and switches current session to it (persistent mode only) | Copies artifact directory to new session namespace when present |\n| `/resume` | Interactive slash command | Yes (active in-memory state replaced) | Switches to selected existing session file | None |\n| `--resume` | CLI startup (picker) | Yes after session creation | Opens selected existing session file | None |\n| `--resume <id\\|path>` | CLI startup | Yes after session creation | Opens existing session; cross-project case can fork into current project | None |\n| `--continue` | CLI startup | Yes after session creation | Opens terminal breadcrumb or most-recent session; creates new one if none exists | None |\n\n## Export and dump\n\n### `/export [outputPath]` (interactive)\n\nFlow:\n\n1. `InputController` routes `/export...` to `CommandController.handleExportCommand`.\n2. The command splits on whitespace and uses only the first argument after `/export` as `outputPath`.\n3. `AgentSession.exportToHtml()` calls `exportSessionToHtml(sessionManager, state, { outputPath, themeName })`.\n4. On success, UI shows path and opens the file in browser.\n\nBehavior details:\n\n- `--copy`, `clipboard`, and `copy` arguments are explicitly rejected with a warning to use `/dump`.\n- Export embeds session header/entries/leaf plus current `systemPrompt` and tool descriptions from agent state.\n- No session entries are appended during export.\n\nCaveat:\n\n- Argument parsing is whitespace-based (`text.split(/\\s+/)`), so quoted paths with spaces are not preserved as a single path by this command path.\n\n### `--export <inputSessionFile> [outputPath]` (CLI)\n\nFlow in `main.ts`:\n\n1. Handled early (before interactive/session startup).\n2. Calls `exportFromFile(inputPath, outputPath?)`.\n3. `SessionManager.open(inputPath)` loads entries, then HTML is generated and written.\n4. Process prints `Exported to: ...` and exits.\n\nBehavior details:\n\n- Missing input file surfaces as `File not found: <path>`.\n- This path does not create an `AgentSession` and does not mutate any running session.\n\n### `/dump` (interactive clipboard export)\n\nFlow:\n\n1. `CommandController.handleDumpCommand()` calls `session.formatSessionAsText()`.\n2. If empty string, reports `No messages to dump yet.`\n3. Otherwise copies to clipboard via native `copyToClipboard`.\n\nDump content includes:\n\n- System prompt\n- Active model/thinking level\n- Tool definitions + parameters\n- User/assistant messages\n- Thinking blocks and tool calls\n- Tool results and execution blocks (except `excludeFromContext` bash/python entries)\n- Custom/hook/file mention/branch summary/compaction summary entries\n\nNo session persistence changes are made by dumping.\n\n## Share\n\n`/share` is interactive-only and always starts by exporting current session to a temp HTML file.\n\n### Phase 1: temp export\n\n- Temp file path: `${os.tmpdir()}/${Snowflake.next()}.html`\n- Uses `session.exportToHtml(tmpFile)`\n- If export fails (notably in-memory sessions), share ends with error.\n\n### Phase 2: custom share handler (if present)\n\n`loadCustomShare()` checks `~/.xcsh/agent` for first existing candidate:\n\n- `share.ts`\n- `share.js`\n- `share.mjs`\n\nRequirements:\n\n- Module must default-export a function `(htmlPath) => Promise<CustomShareResult | string | undefined>`.\n\nIf present and valid:\n\n- UI enters `Sharing...` loader state.\n- Handler result interpretation:\n  - string => treated as URL, shown and opened\n  - object => `url` and/or `message` shown; `url` opened\n  - `undefined`/falsy => generic `Session shared`\n- Temp file is removed after completion.\n\nCritical fallback behavior:\n\n- If custom handler exists but loading fails, command errors and returns.\n- If custom handler executes and throws, command errors and returns.\n- In both failure cases, it **does not** fall back to GitHub gist.\n- Gist fallback happens only when no custom share script exists.\n\n### Phase 3: default gist fallback\n\nOnly when no custom share handler is found:\n\n1. Validates `gh auth status`.\n2. Shows `Creating gist...` loader.\n3. Runs `gh gist create --public=false <tmpFile>`.\n4. Parses gist URL, derives gist id, builds preview URL `https://gistpreview.github.io/?<id>`.\n5. Shows both preview and gist URLs; opens preview.\n\nCancellation/abort semantics in share:\n\n- Loader has `onAbort` hook that restores editor UI and reports `Share cancelled`.\n- The underlying `gh gist create` command is not passed an abort signal in this code path; cancellation is UI-level and checked after command returns.\n\n## Fork\n\n`/fork` creates a new session from the current one and switches the active session identity.\n\n### Preconditions and immediate guards\n\n- If agent is streaming, `/fork` is rejected with warning.\n- UI status/loading indicators are cleared before operation.\n\n### Session-level flow\n\n`AgentSession.fork()`:\n\n1. Emits `session_before_switch` with `reason: \"fork\"` (cancellable).\n2. Flushes pending writes.\n3. Calls `SessionManager.fork()`.\n4. Copies artifacts directory from old session namespace to new namespace (best-effort; non-ENOENT copy failures are logged, not fatal).\n5. Updates `agent.sessionId`.\n6. Emits `session_switch` with `reason: \"fork\"`.\n\n`SessionManager.fork()` behavior:\n\n- Requires persistent mode and existing session file.\n- Creates new session id and new JSONL file path.\n- Rewrites header with:\n  - new `id`\n  - new timestamp\n  - `cwd` unchanged\n  - `parentSession` set to previous session id\n- Keeps all non-header entries unchanged in the new file.\n\n### Non-persistent behavior\n\n- In-memory session manager returns `undefined` from `fork()`.\n- `AgentSession.fork()` returns `false`.\n- UI reports `Fork failed (session not persisted or cancelled)`.\n\n## Resume and continue\n\n## Interactive `/resume`\n\nFlow:\n\n1. Opens session selector populated via `SessionManager.list(currentCwd, currentSessionDir)`.\n2. On selection, `SelectorController.handleResumeSession(sessionPath)` calls `session.switchSession(sessionPath)`.\n3. UI clears/rebuilds chat and todos, then reports `Resumed session`.\n\nNotes:\n\n- This picker only lists sessions in the current session directory scope.\n- It does not use global cross-project search.\n\n## CLI `--resume`\n\n### `--resume` (no value)\n\n- `main.ts` lists sessions for current cwd/sessionDir and opens picker.\n- Selected path is opened with `SessionManager.open(selectedPath)` before session creation.\n\n### `--resume <value>`\n\n`createSessionManager()` resolution order:\n\n1. If value looks like path (`/`, `\\`, or `.jsonl`), open directly.\n2. Else treat as id prefix:\n   - search current scope (`SessionManager.list(cwd, sessionDir)`)\n   - if not found and no explicit `sessionDir`, search global (`SessionManager.listAll()`)\n\nCross-project id match behavior:\n\n- If matched session cwd differs from current cwd, CLI asks:\n  - `Session found in different project ... Fork into current directory? [y/N]`\n- On yes: `SessionManager.forkFrom(match.path, cwd, sessionDir)` creates a new local forked file.\n- On no/non-TTY default: command errors.\n\n## CLI `--continue`\n\n`SessionManager.continueRecent(cwd, sessionDir)`:\n\n1. Resolves session dir for current cwd.\n2. Reads terminal-scoped breadcrumb first.\n3. Falls back to most recently modified session file.\n4. Opens found session; if none exists, creates new session.\n\nThis is startup-only behavior; there is no interactive `/continue` slash command.\n\n## How session switching actually mutates runtime state\n\n`AgentSession.switchSession(sessionPath)` does the runtime transition used by resume-like operations:\n\n1. Emit `session_before_switch` with `reason: \"resume\"` and `targetSessionFile` (cancellable).\n2. Disconnect agent event subscription and abort in-flight work.\n3. Clear queued steering/follow-up/next-turn messages.\n4. Flush current session manager writes.\n5. `sessionManager.setSessionFile(sessionPath)` and update `agent.sessionId`.\n6. Build session context from loaded entries.\n7. Emit `session_switch` with `reason: \"resume\"`.\n8. Replace agent messages from context.\n9. Restore model (if available in current registry).\n10. Restore or initialize thinking level.\n11. Reconnect agent event subscription.\n\nNo new session file is created by `switchSession()` itself.\n\n## Event emissions and cancellation points\n\n### Switch/fork lifecycle hooks\n\nFor `newSession`, `fork`, and `switchSession`:\n\n- Before event: `session_before_switch`\n  - reasons: `new`, `fork`, `resume`\n  - cancellable by returning `{ cancel: true }`\n- After event: `session_switch`\n  - same reason set\n  - includes `previousSessionFile`\n\n`ExtensionRunner.emit()` returns early on the first cancelling before-event result.\n\n### Custom tool `onSession` behavior\n\nSDK bridges extension session events to custom tool `onSession` callbacks:\n\n- `session_switch` -> `onSession({ reason: \"switch\", previousSessionFile })`\n- `session_branch` -> `reason: \"branch\"`\n- `session_start` -> `reason: \"start\"`\n- `session_tree` -> `reason: \"tree\"`\n- `session_shutdown` -> `reason: \"shutdown\"`\n\nThese callbacks are observational; they do not cancel switch/fork.\n\n### Other cancellation surfaces relevant to this doc\n\n- `/fork` is blocked while streaming (user must wait/abort current response first).\n- `/resume` selector can be cancelled by user closing selector.\n- Cross-project `--resume <id>` can be cancelled by declining fork prompt.\n- `/share` has UI abort path (`Share cancelled`) for gist flow; it does not wire process-kill semantics for `gh gist create` in this code path.\n\n## Non-persistent (in-memory) session behavior\n\nWhen session manager is created with `SessionManager.inMemory()` (`--no-session`):\n\n- Session file path is absent.\n- `/export` and `/share` fail with `Cannot export in-memory session to HTML` (propagated to command error UI).\n- `/fork` fails because `SessionManager.fork()` requires persistence.\n- `/dump` still works because it serializes in-memory agent state.\n- CLI resume/continue semantics are bypassed if `--no-session` is set, because manager creation returns in-memory immediately.\n\n## Known implementation caveats (as of current code)\n\n- `SelectorController.handleResumeSession()` does not check the boolean result from `session.switchSession(...)`; a hook-cancelled switch can still proceed through UI \"Resumed session\" repaint/status path.\n- `/share` custom-share failures do not degrade to default gist fallback; they terminate the command with error.\n- `/export` argument tokenization is simplistic and does not preserve quoted paths with spaces.\n",
	"en/sessions/session-switching-and-recent-listing.md": "---\ntitle: Session Switching and Recent Session Listing\ndescription: Session switching mechanics and recent session listing with search and filtering.\nsidebar:\n  order: 4\n  label: Switching & recent\n---\n\n# Session switching and recent session listing\n\nThis document describes how coding-agent discovers recent sessions, resolves `--resume` targets, presents session pickers, and switches the active runtime session.\n\nIt focuses on current implementation behavior, including fallback paths and caveats.\n\n## Implementation files\n\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/cli/session-picker.ts`](../../packages/coding-agent/src/cli/session-picker.ts)\n- [`../src/modes/components/session-selector.ts`](../../packages/coding-agent/src/modes/components/session-selector.ts)\n- [`../src/modes/controllers/selector-controller.ts`](../../packages/coding-agent/src/modes/controllers/selector-controller.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n\n## Recent-session discovery\n\n### Directory scope\n\n`SessionManager` stores sessions under a cwd-scoped directory by default:\n\n- `~/.xcsh/agent/sessions/--<cwd-encoded>--/*.jsonl`\n\n`SessionManager.list(cwd, sessionDir?)` reads only that directory unless an explicit `sessionDir` is provided.\n\n### Two listing paths with different payloads\n\nThere are two different listing pipelines:\n\n1. `getRecentSessions(sessionDir, limit)` (welcome/summary view)\n   - Reads only a 4KB prefix (`readTextPrefix(..., 4096)`) from each file.\n   - Parses header + earliest user text preview.\n   - Returns lightweight `RecentSessionInfo` with lazy `name` and `timeAgo` getters.\n   - Sorts by file `mtime` descending.\n\n2. `SessionManager.list(...)` / `SessionManager.listAll()` (resume pickers and ID matching)\n   - Reads full session files.\n   - Builds `SessionInfo` objects (`id`, `cwd`, `title`, `messageCount`, `firstMessage`, `allMessagesText`, timestamps).\n   - Drops sessions with zero `message` entries.\n   - Sorts by `modified` descending.\n\n### Metadata fallback behavior\n\nFor recent summaries (`RecentSessionInfo`):\n\n- display name preference: `header.title` -> first user prompt -> `header.id` -> filename\n- name is truncated to 40 chars for compact displays\n- control characters/newlines are stripped/sanitized from title-derived names\n\nFor `SessionInfo` list entries:\n\n- `title` is `header.title` or latest compaction `shortSummary`\n- `firstMessage` is first user message text or `\"(no messages)\"`\n\n## `--continue` resolution and terminal breadcrumb preference\n\n`SessionManager.continueRecent(cwd, sessionDir?)` resolves the target in this order:\n\n1. Read terminal-scoped breadcrumb (`~/.xcsh/agent/terminal-sessions/<terminal-id>`)\n2. Validate breadcrumb:\n   - current terminal can be identified\n   - breadcrumb cwd matches current cwd (resolved path compare)\n   - referenced file still exists\n3. If breadcrumb is invalid/missing, fall back to newest file by mtime in the session dir (`findMostRecentSession`)\n4. If none found, create a new session\n\nTerminal ID derivation prefers TTY path and falls back to env-based identifiers (`KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION`).\n\nBreadcrumb writes are best-effort and non-fatal.\n\n## Startup-time resume target resolution (`main.ts`)\n\n### `--resume <value>`\n\n`createSessionManager(...)` handles string-valued `--resume` in two modes:\n\n1. Path-like value (contains `/`, `\\\\`, or ends with `.jsonl`)\n   - direct `SessionManager.open(sessionArg, parsed.sessionDir)`\n\n2. ID prefix value\n   - find match in `SessionManager.list(cwd, sessionDir)` by `id.startsWith(sessionArg)`\n   - if no local match and `sessionDir` is not forced, try `SessionManager.listAll()`\n   - first match is used (no ambiguity prompt)\n\nCross-project match behavior:\n\n- if matched session cwd differs from current cwd, CLI prompts whether to fork into current project\n- yes -> `SessionManager.forkFrom(...)`\n- no -> throws error (`Session \"...\" is in another project (...)`)\n\nNo match -> throws error (`Session \"...\" not found.`).\n\n### `--resume` (no value)\n\nHandled after initial session-manager construction:\n\n1. list local sessions with `SessionManager.list(cwd, parsed.sessionDir)`\n2. if empty: print `No sessions found` and exit early\n3. open TUI picker (`selectSession`)\n4. if canceled: print `No session selected` and exit early\n5. if selected: `SessionManager.open(selectedPath)`\n\n### `--continue`\n\nUses `SessionManager.continueRecent(...)` directly (breadcrumb-first behavior above).\n\n## Picker-based selection internals\n\n## CLI picker (`src/cli/session-picker.ts`)\n\n`selectSession(sessions)` creates a standalone TUI with `SessionSelectorComponent` and resolves exactly once:\n\n- selection -> resolves selected path\n- cancel (Esc) -> resolves `null`\n- hard exit (Ctrl+C path) -> stops TUI and `process.exit(0)`\n\n## Interactive in-session picker (`SelectorController.showSessionSelector`)\n\nFlow:\n\n1. fetch sessions from current session dir via `SessionManager.list(currentCwd, currentSessionDir)`\n2. mount `SessionSelectorComponent` in editor area using `showSelector(...)`\n3. callbacks:\n   - select -> close selector and call `handleResumeSession(sessionPath)`\n   - cancel -> restore editor and rerender\n   - exit -> `ctx.shutdown()`\n\n## Session selector component behavior\n\n`SessionList` supports:\n\n- arrow/page navigation\n- Enter to select\n- Esc to cancel\n- Ctrl+C to exit\n- fuzzy search across session id/title/cwd/first message/all messages/path\n\nEmpty-list render behavior:\n\n- renders a message instead of crashing\n- Enter on empty does nothing (no callback)\n- Esc/Ctrl+C still work\n\nCaveat: UI text says `Press Tab to view all`, but this component currently has no Tab handler and current wiring only lists current-scope sessions.\n\n## Runtime switch execution (`AgentSession.switchSession`)\n\n`switchSession(sessionPath)` is the core in-process switch path.\n\nLifecycle/state transition:\n\n1. capture `previousSessionFile`\n2. emit `session_before_switch` hook event (`reason: \"resume\"`, cancellable)\n3. if canceled -> return `false` with no switch\n4. disconnect from current agent event stream\n5. abort active generation/tool flow\n6. clear queued steering/follow-up/next-turn message buffers\n7. flush session writer (`sessionManager.flush()`) to persist pending writes\n8. `sessionManager.setSessionFile(sessionPath)`\n   - updates session file pointer\n   - writes terminal breadcrumb\n   - loads entries / migrates / blob-resolves / reindexes\n   - if missing/invalid file data: initializes a new session at that path and rewrites header\n9. update `agent.sessionId`\n10. rebuild context via `buildSessionContext()`\n11. emit `session_switch` hook event (`reason: \"resume\"`, `previousSessionFile`)\n12. replace agent messages with rebuilt context\n13. restore default model from `sessionContext.models.default` if available and present in model registry\n14. restore thinking level:\n    - if branch already has `thinking_level_change`, apply saved session level\n    - else derive default thinking level from settings, clamp to model capability, set it, and append a new `thinking_level_change` entry\n15. reconnect agent listeners and return `true`\n\n## UI state rebuild after interactive switch\n\n`SelectorController.handleResumeSession` performs UI reset around `switchSession`:\n\n- stop loading animation\n- clear status container\n- clear pending-message UI and pending tool map\n- reset streaming component/message references\n- call `session.switchSession(...)`\n- clear chat container and rerender from session context (`renderInitialMessages`)\n- reload todos from new session artifacts\n- show `Resumed session`\n\nSo visible conversation/todo state is rebuilt from the new session file.\n\n## Startup resume vs in-session switch\n\n### Startup resume (`--continue`, `--resume`, direct open)\n\n- Session file is chosen before `createAgentSession(...)`.\n- `sdk.ts` builds `existingSession = sessionManager.buildSessionContext()`.\n- Agent messages are restored once during session creation.\n- Model/thinking are selected during creation (including restore/fallback logic).\n- Interactive mode then runs `#restoreModeFromSession()` to re-enter persisted mode state (currently plan/plan_paused).\n\n### In-session switch (`/resume`-style selector path)\n\n- Uses `AgentSession.switchSession(...)` on an already-running `AgentSession`.\n- Messages/model/thinking are rebuilt immediately in place.\n- Hook `session_before_switch`/`session_switch` events are emitted.\n- UI chat/todos are refreshed.\n- No dedicated post-switch mode restore call is made in selector flow; mode re-entry behavior is not symmetric with startup `#restoreModeFromSession()`.\n\n## Failure and edge-case behavior\n\n### Cancellation paths\n\n- CLI picker cancel -> returns `null`, caller prints `No session selected`, process exits early.\n- Interactive picker cancel -> editor restored, no session change.\n- Hook cancellation (`session_before_switch`) -> `switchSession()` returns `false`.\n\n### Empty list paths\n\n- CLI `--resume` (no value): empty list prints `No sessions found` and exits.\n- Interactive selector: empty list renders message and remains cancellable.\n\n### Missing/invalid target session file\n\nWhen opening/switching to a specific path (`setSessionFile`):\n\n- ENOENT -> treated as empty -> new session initialized at that exact path and persisted.\n- malformed/invalid header (or effectively unreadable parsed entries) -> treated as empty -> new session initialized and persisted.\n\nThis is recovery behavior, not hard failure.\n\n### Hard failures\n\nSwitch/open can still throw on true I/O failures (permission errors, rewrite failures, etc.), which propagate to callers.\n\n### ID prefix matching caveats\n\n- ID matching uses `startsWith` and takes first match in sorted list.\n- No ambiguity UI if multiple sessions share prefix.\n- `SessionManager.list(...)` excludes sessions with zero messages, so those sessions are not resumable via ID match/list picker.\n",
	"en/sessions/session-tree-plan.md": "---\ntitle: Session Tree Architecture\ndescription: Session tree architecture with branching, navigation, and parent-child conversation relationships.\nsidebar:\n  order: 2\n  label: Tree architecture\n---\n\n# Session tree architecture (current)\n\nReference: [session.md](./session.md)\n\nThis document describes how session tree navigation works today: in-memory tree model, leaf movement rules, branching behavior, and extension/event integration.\n\n## What this subsystem is\n\nThe session is stored as an append-only entry log, but runtime behavior is tree-based:\n\n- Every non-header entry has `id` and `parentId`.\n- The active position is `leafId` in `SessionManager`.\n- Appending an entry always creates a child of the current leaf.\n- Branching does **not** rewrite history; it only changes where the leaf points before the next append.\n\nKey files:\n\n- `src/session/session-manager.ts` — tree data model, traversal, leaf movement, branch/session extraction\n- `src/session/agent-session.ts` — `/tree` navigation flow, summarization, hook/event emission\n- `src/modes/components/tree-selector.ts` — interactive tree UI behavior and filtering\n- `src/modes/controllers/selector-controller.ts` — selector orchestration for `/tree` and `/branch`\n- `src/modes/controllers/input-controller.ts` — command routing (`/tree`, `/branch`, double-escape behavior)\n- `src/session/messages.ts` — conversion of `branch_summary`, `compaction`, and `custom_message` entries into LLM context messages\n\n## Tree data model in `SessionManager`\n\nRuntime indices:\n\n- `#byId: Map<string, SessionEntry>` — fast lookup for any entry\n- `#leafId: string | null` — current position in the tree\n- `#labelsById: Map<string, string>` — resolved labels by target entry id\n\nTree APIs:\n\n- `getBranch(fromId?)` walks parent links to root and returns root→node path\n- `getTree()` returns `SessionTreeNode[]` (`entry`, `children`, `label`)\n  - parent links become children arrays\n  - entries with missing parents are treated as roots\n  - children are sorted oldest→newest by timestamp\n- `getChildren(parentId)` returns direct children\n- `getLabel(id)` resolves current label from `labelsById`\n\n`getTree()` is a runtime projection; persistence remains append-only JSONL entries.\n\n## Leaf movement semantics\n\nThere are three leaf movement primitives:\n\n1. `branch(entryId)`\n   - Validates entry exists\n   - Sets `leafId = entryId`\n   - No new entry is written\n\n2. `resetLeaf()`\n   - Sets `leafId = null`\n   - Next append creates a new root entry (`parentId = null`)\n\n3. `branchWithSummary(branchFromId, summary, details?, fromExtension?)`\n   - Accepts `branchFromId: string | null`\n   - Sets `leafId = branchFromId`\n   - Appends a `branch_summary` entry as child of that leaf\n   - When `branchFromId` is `null`, `fromId` is persisted as `\"root\"`\n\n## `/tree` navigation behavior (same session file)\n\n`AgentSession.navigateTree()` is navigation, not file forking.\n\nFlow:\n\n1. Validate target and compute abandoned path (`collectEntriesForBranchSummary`)\n2. Emit `session_before_tree` with `TreePreparation`\n3. Optionally summarize abandoned entries (hook-provided summary or built-in summarizer)\n4. Compute new leaf target:\n   - selecting a **user** message: leaf moves to its parent, and message text is returned for editor prefill\n   - selecting a **custom_message**: same rule as user message (leaf = parent, text prefills editor)\n   - selecting any other entry: leaf = selected entry id\n5. Apply leaf move:\n   - with summary: `branchWithSummary(newLeafId, ...)`\n   - without summary and `newLeafId === null`: `resetLeaf()`\n   - otherwise: `branch(newLeafId)`\n6. Rebuild agent context from new leaf and emit `session_tree`\n\nImportant: summary entries are attached at the **new navigation position**, not on the abandoned branch tail.\n\n## `/branch` behavior (new session file)\n\n`/branch` and `/tree` are intentionally different:\n\n- `/tree` navigates within the current session file.\n- `/branch` creates a new session branch file (or in-memory replacement for non-persistent mode).\n\nUser-facing `/branch` flow (`SelectorController.showUserMessageSelector` → `AgentSession.branch`):\n\n- Branch source must be a **user message**.\n- Selected user text is extracted for editor prefill.\n- If selected user message is root (`parentId === null`): start a new session via `newSession({ parentSession: previousSessionFile })`.\n- Otherwise: `createBranchedSession(selectedEntry.parentId)` to fork history up to the selected prompt boundary.\n\n`SessionManager.createBranchedSession(leafId)` specifics:\n\n- Builds root→leaf path via `getBranch(leafId)`; throws if missing.\n- Excludes existing `label` entries from copied path.\n- Rebuilds fresh label entries from resolved `labelsById` for entries that remain in path.\n- Persistent mode: writes new JSONL file and switches manager to it; returns new file path.\n- In-memory mode: replaces in-memory entries; returns `undefined`.\n\n## Context reconstruction and summary/custom integration\n\n`buildSessionContext()` (in `session-manager.ts`) resolves the active root→leaf path and builds effective LLM context state:\n\n- Tracks latest thinking/model/mode/ttsr state on path.\n- Handles latest compaction on path:\n  - emits compaction summary first\n  - replays kept messages from `firstKeptEntryId` to compaction point\n  - then replays post-compaction messages\n- Includes `branch_summary` and `custom_message` entries as `AgentMessage` objects.\n\n`session/messages.ts` then maps these message types for model input:\n\n- `branchSummary` and `compactionSummary` become user-role templated context messages\n- `custom`/`hookMessage` become user-role content messages\n\nSo tree movement changes context by changing the active leaf path, not by mutating old entries.\n\n## Labels and tree UI behavior\n\nLabel persistence:\n\n- `appendLabelChange(targetId, label?)` writes `label` entries on the current leaf chain.\n- `labelsById` is updated immediately (set or delete).\n- `getTree()` resolves current label onto each returned node.\n\nTree selector behavior (`tree-selector.ts`):\n\n- Flattens tree for navigation, keeps active-path highlighting, and prioritizes displaying the active branch first.\n- Supports filter modes: `default`, `no-tools`, `user-only`, `labeled-only`, `all`.\n- Supports free-text search over rendered semantic content.\n- `Shift+L` opens inline label editing and writes via `appendLabelChange`.\n\nCommand routing:\n\n- `/tree` always opens tree selector.\n- `/branch` opens user-message selector unless `doubleEscapeAction=tree`, in which case it also uses tree selector UX.\n\n## Extension and hook touchpoints for tree operations\n\nCommand-time extension API (`ExtensionCommandContext`):\n\n- `branch(entryId)` — create branched session file\n- `navigateTree(targetId, { summarize? })` — move within current tree/file\n\nEvents around tree navigation:\n\n- `session_before_tree`\n  - receives `TreePreparation`:\n    - `targetId`\n    - `oldLeafId`\n    - `commonAncestorId`\n    - `entriesToSummarize`\n    - `userWantsSummary`\n  - may cancel navigation\n  - may provide summary payload used instead of built-in summarizer\n  - receives abort `signal` (Escape cancellation path)\n- `session_tree`\n  - emits `newLeafId`, `oldLeafId`\n  - includes `summaryEntry` when a summary was created\n  - `fromExtension` indicates summary origin\n\nAdjacent but related lifecycle hooks:\n\n- `session_before_branch` / `session_branch` for `/branch` flow\n- `session_before_compact`, `session.compacting`, `session_compact` for compaction entries that later affect tree-context reconstruction\n\n## Real constraints and edge conditions\n\n- `branch()` cannot target `null`; use `resetLeaf()` for root-before-first-entry state.\n- `branchWithSummary()` supports `null` target and records `fromId: \"root\"`.\n- Selecting current leaf in tree selector is a no-op.\n- Summarization requires an active model; if absent, summarize navigation fails fast.\n- If summarization is aborted, navigation is cancelled and leaf is unchanged.\n- In-memory sessions never return a branch file path from `createBranchedSession`.\n\n## Legacy compatibility still present\n\nSession migrations still run on load:\n\n- v1→v2 adds `id`/`parentId` and converts compaction index anchor to id anchor\n- v2→v3 migrates legacy `hookMessage` role to `custom`\n\nCurrent runtime behavior is version-3 tree semantics after migration.\n",
	"en/sessions/session.md": "---\ntitle: Session Storage and Entry Model\ndescription: Append-only session storage model with entry types, persistence, and migration between formats.\nsidebar:\n  order: 1\n  label: Storage & entry model\n---\n\n# Session Storage and Entry Model\n\nThis document is the source of truth for how coding-agent sessions are represented, persisted, migrated, and reconstructed at runtime.\n\n## Scope\n\nCovers:\n\n- Session JSONL format and versioning\n- Entry taxonomy and tree semantics (`id`/`parentId` + leaf pointer)\n- Migration/compatibility behavior when loading old or malformed files\n- Context reconstruction (`buildSessionContext`)\n- Persistence guarantees, failure behavior, truncation/blob externalization\n- Storage abstractions (`FileSessionStorage`, `MemorySessionStorage`) and related utilities\n\nDoes not cover `/tree` UI rendering behavior beyond semantics that affect session data.\n\n## Implementation Files\n\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`src/session/messages.ts`](../../packages/coding-agent/src/session/messages.ts)\n- [`src/session/session-storage.ts`](../../packages/coding-agent/src/session/session-storage.ts)\n- [`src/session/history-storage.ts`](../../packages/coding-agent/src/session/history-storage.ts)\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts)\n\n## On-Disk Layout\n\nDefault session file location:\n\n```text\n~/.xcsh/agent/sessions/--<cwd-encoded>--/<timestamp>_<sessionId>.jsonl\n```\n\n`<cwd-encoded>` is derived from the working directory by stripping leading slash and replacing `/`, `\\\\`, and `:` with `-`.\n\nBlob store location:\n\n```text\n~/.xcsh/agent/blobs/<sha256>\n```\n\nTerminal breadcrumb files are written under:\n\n```text\n~/.xcsh/agent/terminal-sessions/<terminal-id>\n```\n\nBreadcrumb content is two lines: original cwd, then session file path. `continueRecent()` prefers this terminal-scoped pointer before scanning most-recent mtime.\n\n## File Format\n\nSession files are JSONL: one JSON object per line.\n\n- Line 1 is always the session header (`type: \"session\"`).\n- Remaining lines are `SessionEntry` values.\n- Entries are append-only at runtime; branch navigation moves a pointer (`leafId`) rather than mutating existing entries.\n\n### Header (`SessionHeader`)\n\n```json\n{\n  \"type\": \"session\",\n  \"version\": 3,\n  \"id\": \"1f9d2a6b9c0d1234\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\",\n  \"cwd\": \"/work/pi\",\n  \"title\": \"optional session title\",\n  \"parentSession\": \"optional lineage marker\"\n}\n```\n\nNotes:\n\n- `version` is optional in v1 files; absence means v1.\n- `parentSession` is an opaque lineage string. Current code writes either a session id or a session path depending on flow (`fork`, `forkFrom`, `createBranchedSession`, or explicit `newSession({ parentSession })`). Treat as metadata, not a typed foreign key.\n\n### Entry Base (`SessionEntryBase`)\n\nAll non-header entries include:\n\n```json\n{\n  \"type\": \"...\",\n  \"id\": \"8-char-id\",\n  \"parentId\": \"previous-or-branch-parent\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\"\n}\n```\n\n`parentId` can be `null` for a root entry (first append, or after `resetLeaf()`).\n\n## Entry Taxonomy\n\n`SessionEntry` is the union of:\n\n- `message`\n- `thinking_level_change`\n- `model_change`\n- `compaction`\n- `branch_summary`\n- `custom`\n- `custom_message`\n- `label`\n- `ttsr_injection`\n- `session_init`\n- `mode_change`\n\n### `message`\n\nStores an `AgentMessage` directly.\n\n```json\n{\n  \"type\": \"message\",\n  \"id\": \"a1b2c3d4\",\n  \"parentId\": null,\n  \"timestamp\": \"2026-02-16T10:21:00.000Z\",\n  \"message\": {\n    \"role\": \"assistant\",\n    \"provider\": \"anthropic\",\n    \"model\": \"claude-sonnet-4-5\",\n    \"content\": [{ \"type\": \"text\", \"text\": \"Done.\" }],\n    \"usage\": { \"input\": 100, \"output\": 20, \"cacheRead\": 0, \"cacheWrite\": 0, \"cost\": { \"input\": 0, \"output\": 0, \"cacheRead\": 0, \"cacheWrite\": 0, \"total\": 0 } },\n    \"timestamp\": 1760000000000\n  }\n}\n```\n\n### `model_change`\n\n```json\n{\n  \"type\": \"model_change\",\n  \"id\": \"b1c2d3e4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:21:30.000Z\",\n  \"model\": \"openai/gpt-4o\",\n  \"role\": \"default\"\n}\n```\n\n`role` is optional; missing is treated as `default` in context reconstruction.\n\n### `thinking_level_change`\n\n```json\n{\n  \"type\": \"thinking_level_change\",\n  \"id\": \"c1d2e3f4\",\n  \"parentId\": \"b1c2d3e4\",\n  \"timestamp\": \"2026-02-16T10:22:00.000Z\",\n  \"thinkingLevel\": \"high\"\n}\n```\n\n### `compaction`\n\n```json\n{\n  \"type\": \"compaction\",\n  \"id\": \"d1e2f3a4\",\n  \"parentId\": \"c1d2e3f4\",\n  \"timestamp\": \"2026-02-16T10:23:00.000Z\",\n  \"summary\": \"Conversation summary\",\n  \"shortSummary\": \"Short recap\",\n  \"firstKeptEntryId\": \"a1b2c3d4\",\n  \"tokensBefore\": 42000,\n  \"details\": { \"readFiles\": [\"src/a.ts\"] },\n  \"preserveData\": { \"hookState\": true },\n  \"fromExtension\": false\n}\n```\n\n### `branch_summary`\n\n```json\n{\n  \"type\": \"branch_summary\",\n  \"id\": \"e1f2a3b4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:24:00.000Z\",\n  \"fromId\": \"a1b2c3d4\",\n  \"summary\": \"Summary of abandoned path\",\n  \"details\": { \"note\": \"optional\" },\n  \"fromExtension\": true\n}\n```\n\nIf branching from root (`branchFromId === null`), `fromId` is the literal string `\"root\"`.\n\n### `custom`\n\nExtension state persistence; ignored by `buildSessionContext`.\n\n```json\n{\n  \"type\": \"custom\",\n  \"id\": \"f1a2b3c4\",\n  \"parentId\": \"e1f2a3b4\",\n  \"timestamp\": \"2026-02-16T10:25:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"data\": { \"state\": 1 }\n}\n```\n\n### `custom_message`\n\nExtension-provided message that does participate in LLM context.\n\n```json\n{\n  \"type\": \"custom_message\",\n  \"id\": \"a2b3c4d5\",\n  \"parentId\": \"f1a2b3c4\",\n  \"timestamp\": \"2026-02-16T10:26:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"content\": \"Injected context\",\n  \"display\": true,\n  \"details\": { \"debug\": false }\n}\n```\n\n### `label`\n\n```json\n{\n  \"type\": \"label\",\n  \"id\": \"b2c3d4e5\",\n  \"parentId\": \"a2b3c4d5\",\n  \"timestamp\": \"2026-02-16T10:27:00.000Z\",\n  \"targetId\": \"a1b2c3d4\",\n  \"label\": \"checkpoint\"\n}\n```\n\n`label: undefined` clears a label for `targetId`.\n\n### `ttsr_injection`\n\n```json\n{\n  \"type\": \"ttsr_injection\",\n  \"id\": \"c2d3e4f5\",\n  \"parentId\": \"b2c3d4e5\",\n  \"timestamp\": \"2026-02-16T10:28:00.000Z\",\n  \"injectedRules\": [\"ruleA\", \"ruleB\"]\n}\n```\n\n### `session_init`\n\n```json\n{\n  \"type\": \"session_init\",\n  \"id\": \"d2e3f4a5\",\n  \"parentId\": \"c2d3e4f5\",\n  \"timestamp\": \"2026-02-16T10:29:00.000Z\",\n  \"systemPrompt\": \"...\",\n  \"task\": \"...\",\n  \"tools\": [\"read\", \"edit\"],\n  \"outputSchema\": { \"type\": \"object\" }\n}\n```\n\n### `mode_change`\n\n```json\n{\n  \"type\": \"mode_change\",\n  \"id\": \"e2f3a4b5\",\n  \"parentId\": \"d2e3f4a5\",\n  \"timestamp\": \"2026-02-16T10:30:00.000Z\",\n  \"mode\": \"plan\",\n  \"data\": { \"planFile\": \"/tmp/plan.md\" }\n}\n```\n\n## Versioning and Migration\n\nCurrent session version: `3`.\n\n### v1 -> v2\n\nApplied when header `version` is missing or `< 2`:\n\n- Adds `id` and `parentId` to each non-header entry.\n- Reconstructs a linear parent chain using file order.\n- Migrates compaction field `firstKeptEntryIndex` -> `firstKeptEntryId` when present.\n- Sets header `version = 2`.\n\n### v2 -> v3\n\nApplied when header `version < 3`:\n\n- For `message` entries: rewrites legacy `message.role === \"hookMessage\"` to `\"custom\"`.\n- Sets header `version = 3`.\n\n### Migration Trigger and Persistence\n\n- Migrations run during session load (`setSessionFile`).\n- If any migration ran, the entire file is rewritten to disk immediately.\n- Migration mutates in-memory entries first, then persists rewritten JSONL.\n\n## Load and Compatibility Behavior\n\n`loadEntriesFromFile(path)` behavior:\n\n- Missing file (`ENOENT`) -> returns `[]`.\n- Non-parseable lines are handled by lenient JSONL parser (`parseJsonlLenient`).\n- If first parsed entry is not a valid session header (`type !== \"session\"` or missing string `id`) -> returns `[]`.\n\n`SessionManager.setSessionFile()` behavior:\n\n- `[]` from loader is treated as empty/nonexistent session and replaced with a new initialized session file at that path.\n- Valid files are loaded, migrated if needed, blob refs resolved, then indexed.\n\n## Tree and Leaf Semantics\n\nThe underlying model is append-only tree + mutable leaf pointer:\n\n- Every append method creates exactly one new entry whose `parentId` is current `leafId`.\n- The new entry becomes the new `leafId`.\n- `branch(entryId)` moves only `leafId`; existing entries remain unchanged.\n- `resetLeaf()` sets `leafId = null`; next append creates a new root entry (`parentId: null`).\n- `branchWithSummary()` sets leaf to branch target and appends a `branch_summary` entry.\n\n`getEntries()` returns all non-header entries in insertion order. Existing entries are not deleted in normal operation; rewrites preserve logical history while updating representation (migrations, move, targeted rewrite helpers).\n\n## Context Reconstruction (`buildSessionContext`)\n\n`buildSessionContext(entries, leafId, byId?)` resolves what is sent to the model.\n\nAlgorithm:\n\n1. Determine leaf:\n   - `leafId === null` -> return empty context.\n   - explicit `leafId` -> use that entry if found.\n   - otherwise fallback to last entry.\n2. Walk `parentId` chain from leaf to root and reverse to root->leaf path.\n3. Derive runtime state across path:\n   - `thinkingLevel` from latest `thinking_level_change` (default `\"off\"`)\n   - model map from `model_change` entries (`role ?? \"default\"`)\n   - fallback `models.default` from assistant message provider/model if no explicit model change\n   - deduplicated `injectedTtsrRules` from all `ttsr_injection` entries\n   - mode/modeData from latest `mode_change` (default mode `\"none\"`)\n4. Build message list:\n   - `message` entries pass through\n   - `custom_message` entries become `custom` AgentMessages via `createCustomMessage`\n   - `branch_summary` entries become `branchSummary` AgentMessages via `createBranchSummaryMessage`\n   - if a `compaction` exists on path:\n     - emit compaction summary first (`createCompactionSummaryMessage`)\n     - emit path entries starting at `firstKeptEntryId` up to the compaction boundary\n     - emit entries after the compaction boundary\n\n`custom` and `session_init` entries do not inject model context directly.\n\n## Persistence Guarantees and Failure Model\n\n### Persist vs in-memory\n\n- `SessionManager.create/open/continueRecent/forkFrom` -> persistent mode (`persist = true`).\n- `SessionManager.inMemory` -> non-persistent mode (`persist = false`) with `MemorySessionStorage`.\n\n### Write pipeline\n\nWrites are serialized through an internal promise chain (`#persistChain`) and `NdjsonFileWriter`.\n\n- `append*` updates in-memory state immediately.\n- Persistence is deferred until at least one assistant message exists.\n  - Before first assistant: entries are retained in memory; no file append occurs.\n  - When first assistant exists: full in-memory session is flushed to file.\n  - Afterwards: new entries append incrementally.\n\nRationale in code: avoid persisting sessions that never produced an assistant response.\n\n### Durability operations\n\n- `flush()` flushes writer and calls `fsync()`.\n- Atomic full rewrites (`#rewriteFile`) write to temp file, flush+fsync, close, then rename over target.\n- Used for migrations, `setSessionName`, `rewriteEntries`, move operations, and tool-call arg rewrites.\n\n### Error behavior\n\n- Persistence errors are latched (`#persistError`) and rethrown on subsequent operations.\n- First error is logged once with session file context.\n- Writer close is best-effort but propagates the first meaningful error.\n\n## Data Size Controls and Blob Externalization\n\nBefore persisting entries:\n\n- Large strings are truncated to `MAX_PERSIST_CHARS` (500,000 chars) with notice:\n  - `\"[Session persistence truncated large content]\"`\n- Transient fields `partialJson` and `jsonlEvents` are removed.\n- If object has both `content` and `lineCount`, line count is recomputed after truncation.\n- Image blocks in `content` arrays with base64 length >= 1024 are externalized to blob refs:\n  - stored as `blob:sha256:<hash>`\n  - raw bytes written to blob store (`BlobStore.put`)\n\nOn load, blob refs are resolved back to base64 for message/custom_message image blocks.\n\n## Storage Abstractions\n\n`SessionStorage` interface provides all filesystem operations used by `SessionManager`:\n\n- sync: `ensureDirSync`, `existsSync`, `writeTextSync`, `statSync`, `listFilesSync`\n- async: `exists`, `readText`, `readTextPrefix`, `writeText`, `rename`, `unlink`, `openWriter`\n\nImplementations:\n\n- `FileSessionStorage`: real filesystem (Bun + node fs)\n- `MemorySessionStorage`: map-backed in-memory implementation for tests/non-persistent sessions\n\n`SessionStorageWriter` exposes `writeLine`, `flush`, `fsync`, `close`, `getError`.\n\n## Session Discovery Utilities\n\nDefined in `session-manager.ts`:\n\n- `getRecentSessions(sessionDir, limit)` -> lightweight metadata for UI/session picker\n- `findMostRecentSession(sessionDir)` -> newest by mtime\n- `list(cwd, sessionDir?)` -> sessions in one project scope\n- `listAll()` -> sessions across all project scopes under `~/.xcsh/agent/sessions`\n\nMetadata extraction reads only a prefix (`readTextPrefix(..., 4096)`) where possible.\n\n## Related but Distinct: Prompt History Storage\n\n`HistoryStorage` (`history-storage.ts`) is a separate SQLite subsystem for prompt recall/search, not session replay.\n\n- DB: `~/.xcsh/agent/history.db`\n- Table: `history(id, prompt, created_at, cwd)`\n- FTS5 index: `history_fts` with trigger-maintained sync\n- Deduplicates consecutive identical prompts using in-memory last-prompt cache\n- Async insertion (`setImmediate`) so prompt capture does not block turn execution\n\nUse session files for conversation graph/state replay; use `HistoryStorage` for prompt history UX.\n",
	"en/sessions/ttsr-injection-lifecycle.md": "---\ntitle: TTSR Injection Lifecycle\ndescription: TTSR (tool-use, tool-result, system-reminder) injection lifecycle for context management.\nsidebar:\n  order: 9\n  label: TTSR injection\n---\n\n# TTSR Injection Lifecycle\n\nThis document covers the current Time Traveling Stream Rules (TTSR) runtime path from rule discovery to stream interruption, retry injection, extension notifications, and session-state handling.\n\n## Implementation files\n\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/export/ttsr.ts`](../../packages/coding-agent/src/export/ttsr.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/prompts/system/ttsr-interrupt.md`](../../packages/coding-agent/src/prompts/system/ttsr-interrupt.md)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/types.ts`](../../packages/coding-agent/src/extensibility/extensions/types.ts)\n- [`../src/extensibility/hooks/types.ts`](../../packages/coding-agent/src/extensibility/hooks/types.ts)\n- [`../src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n\n## 1. Discovery feed and rule registration\n\nAt session creation, `createAgentSession()` loads all discovered rules and constructs a `TtsrManager`:\n\n```ts\nconst ttsrSettings = settings.getGroup(\"ttsr\");\nconst ttsrManager = new TtsrManager(ttsrSettings);\nconst rulesResult = await loadCapability<Rule>(ruleCapability.id, { cwd });\nfor (const rule of rulesResult.items) {\n  if (rule.ttsrTrigger) ttsrManager.addRule(rule);\n}\n```\n\n### Pre-registration dedupe behavior\n\n`loadCapability(\"rules\")` deduplicates by `rule.name` with first-wins semantics (higher provider priority first). Shadowed duplicates are removed before TTSR registration.\n\n### `TtsrManager.addRule()` behavior\n\nRegistration is skipped when:\n\n- `rule.ttsrTrigger` is absent\n- a rule with the same `rule.name` was already registered in this manager\n- the regex fails to compile (`new RegExp(rule.ttsrTrigger)` throws)\n\nInvalid regex triggers are logged as warnings and ignored; session startup continues.\n\n### Setting caveat\n\n`TtsrSettings.enabled` is loaded into the manager but is not currently checked in runtime gating. If rules exist, matching still runs.\n\n## 2. Streaming monitor lifecycle\n\nTTSR detection runs inside `AgentSession.#handleAgentEvent`.\n\n### Turn start\n\nOn `turn_start`, the stream buffer is reset:\n\n- `ttsrManager.resetBuffer()`\n\n### During stream (`message_update`)\n\nWhen assistant updates arrive and rules exist:\n\n- monitor `text_delta` and `toolcall_delta`\n- append delta into manager buffer\n- call `check(buffer)`\n\n`check()` iterates registered rules and returns all matching rules that pass repeat policy (`#canTrigger`).\n\n## 3. Trigger decision and immediate abort path\n\nWhen one or more rules match:\n\n1. `markInjected(matches)` records rule names in manager injection state.\n2. matched rules are queued in `#pendingTtsrInjections`.\n3. `#ttsrAbortPending = true`.\n4. `agent.abort()` is called immediately.\n5. `ttsr_triggered` event is emitted asynchronously (fire-and-forget).\n6. retry work is scheduled via `setTimeout(..., 50)`.\n\nAbort is not blocked on extension callbacks.\n\n## 4. Retry scheduling, context mode, and reminder injection\n\nAfter the 50ms timeout:\n\n1. `#ttsrAbortPending = false`\n2. read `ttsrManager.getSettings().contextMode`\n3. if `contextMode === \"discard\"`, drop partial assistant output with `agent.popMessage()`\n4. build injection content from pending rules using `ttsr-interrupt.md` template\n5. append a synthetic user message containing one `<system-interrupt ...>` block per rule\n6. call `agent.continue()` to retry generation\n\nTemplate payload is:\n\n```xml\n<system-interrupt reason=\"rule_violation\" rule=\"{{name}}\" path=\"{{path}}\">\n...\n{{content}}\n</system-interrupt>\n```\n\nPending injections are cleared after content generation.\n\n### `contextMode` behavior on partial output\n\n- `discard`: partial/aborted assistant message is removed before retry.\n- `keep`: partial assistant output remains in conversation state; reminder is appended after it.\n\n## 5. Repeat policy and gap logic\n\n`TtsrManager` tracks `#messageCount` and per-rule `lastInjectedAt`.\n\n### `repeatMode: \"once\"`\n\nA rule can trigger only once after it has an injection record.\n\n### `repeatMode: \"after-gap\"`\n\nA rule can re-trigger only when:\n\n- `messageCount - lastInjectedAt >= repeatGap`\n\n`messageCount` increments on `turn_end`, so gap is measured in completed turns, not stream chunks.\n\n## 6. Event emission and extension/hook surfaces\n\n### Session event\n\n`AgentSessionEvent` includes:\n\n```ts\n{ type: \"ttsr_triggered\"; rules: Rule[] }\n```\n\n### Extension runner\n\n`#emitSessionEvent()` routes the event to:\n\n- extension listeners (`ExtensionRunner.emit({ type: \"ttsr_triggered\", rules })`)\n- local session subscribers\n\n### Hook and custom-tool typing\n\n- extension API exposes `on(\"ttsr_triggered\", ...)`\n- hook API exposes `on(\"ttsr_triggered\", ...)`\n- custom tools receive `onSession({ reason: \"ttsr_triggered\", rules })`\n\n### Interactive-mode rendering difference\n\nInteractive mode uses `session.isTtsrAbortPending` to suppress showing the aborted assistant stop reason as a visible failure during TTSR interruption, and renders a `TtsrNotificationComponent` when the event arrives.\n\n## 7. Persistence and resume state (current implementation)\n\n`SessionManager` has full schema support for injected-rule persistence:\n\n- entry type: `ttsr_injection`\n- append API: `appendTtsrInjection(ruleNames)`\n- query API: `getInjectedTtsrRules()`\n- context reconstruction includes `SessionContext.injectedTtsrRules`\n\n`TtsrManager` also supports restoration via `restoreInjected(ruleNames)`.\n\n### Current wiring status\n\nIn the current runtime path:\n\n- `AgentSession` does not append `ttsr_injection` entries when TTSR triggers.\n- `createAgentSession()` does not restore `existingSession.injectedTtsrRules` back into `ttsrManager`.\n\nNet effect: injected-rule suppression is enforced in-memory for the live process, but is not currently persisted/restored across session reload/resume by this path.\n\n## 8. Race boundaries and ordering guarantees\n\n### Abort vs retry callback\n\n- abort is synchronous from TTSR handler perspective (`agent.abort()` called immediately)\n- retry is deferred by timer (`50ms`)\n- extension notification is asynchronous and intentionally not awaited before abort/retry scheduling\n\n### Multiple matches in same stream window\n\n`check()` returns all currently matching eligible rules. They are injected as a batch on the next retry message.\n\n### Between abort and continue\n\nDuring the timer window, state can change (user interruption, mode actions, additional events). The retry call is best-effort: `agent.continue().catch(() => {})` swallows follow-up errors.\n\n## 9. Edge cases summary\n\n- Invalid `ttsr_trigger` regex: skipped with warning; other rules continue.\n- Duplicate rule names at capability layer: lower-priority duplicates are shadowed before registration.\n- Duplicate names at manager layer: second registration is ignored.\n- `contextMode: \"keep\"`: partial violating output can remain in context before reminder retry.\n- Repeat-after-gap depends on turn count increments at `turn_end`; mid-turn chunks do not advance gap counters.\n",
	"en/tui/theme.md": "---\ntitle: Theming Reference\ndescription: TUI theming reference with color tokens, font settings, and theme customization.\nsidebar:\n  order: 3\n  label: Theming\n---\n\n# Theming Reference\n\nThis document describes how theming works in the coding-agent today: schema, loading, runtime behavior, and failure modes.\n\n## What the theme system controls\n\nThe theme system drives:\n\n- foreground/background color tokens used across the TUI\n- markdown styling adapters (`getMarkdownTheme()`)\n- selector/editor/settings list adapters (`getSelectListTheme()`, `getEditorTheme()`, `getSettingsListTheme()`)\n- symbol preset + symbol overrides (`unicode`, `nerd`, `ascii`)\n- syntax highlighting colors used by native highlighter (`@f5-sales-demo/pi-natives`)\n- status line segment colors\n\nPrimary implementation: `src/modes/theme/theme.ts`.\n\n## Theme JSON shape\n\nTheme files are JSON objects validated against the runtime schema in `theme.ts` (`ThemeJsonSchema`) and mirrored by `src/modes/theme/theme-schema.json`.\n\nTop-level fields:\n\n- `name` (required)\n- `colors` (required; all color tokens required)\n- `vars` (optional; reusable color variables)\n- `export` (optional; HTML export colors)\n- `symbols` (optional)\n  - `preset` (optional: `unicode | nerd | ascii`)\n  - `overrides` (optional: key/value overrides for `SymbolKey`)\n\nColor values accept:\n\n- hex string (`\"#RRGGBB\"`)\n- 256-color index (`0..255`)\n- variable reference string (resolved through `vars`)\n- empty string (`\"\"`) meaning terminal default (`\\x1b[39m` fg, `\\x1b[49m` bg)\n\n## Required color tokens (current)\n\nAll tokens below are required in `colors`.\n\n### Core text and borders (11)\n\n`accent`, `border`, `borderAccent`, `borderMuted`, `success`, `error`, `warning`, `muted`, `dim`, `text`, `thinkingText`\n\n### Background blocks (7)\n\n`selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`, `statusLineBg`\n\n### Message/tool text (5)\n\n`userMessageText`, `customMessageText`, `customMessageLabel`, `toolTitle`, `toolOutput`\n\n### Markdown (10)\n\n`mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet`\n\n### Tool diff + syntax highlighting (12)\n\n`toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext`,\n`syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation`\n\n### Mode/thinking borders (8)\n\n`thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh`, `bashMode`, `pythonMode`\n\n### Status line segment colors (14)\n\n`statusLineSep`, `statusLineModel`, `statusLinePath`, `statusLineGitClean`, `statusLineGitDirty`, `statusLineContext`, `statusLineSpend`, `statusLineStaged`, `statusLineDirty`, `statusLineUntracked`, `statusLineOutput`, `statusLineCost`, `statusLineSubagents`\n\n## Optional tokens\n\n### `export` section (optional)\n\nUsed for HTML export theming helpers:\n\n- `export.pageBg`\n- `export.cardBg`\n- `export.infoBg`\n\nIf omitted, export code derives defaults from resolved theme colors.\n\n### `symbols` section (optional)\n\n- `symbols.preset` sets a theme-level default symbol set.\n- `symbols.overrides` can override individual `SymbolKey` values.\n\nRuntime precedence:\n\n1. settings `symbolPreset` override (if set)\n2. theme JSON `symbols.preset`\n3. fallback `\"unicode\"`\n\nInvalid override keys are ignored and logged (`logger.debug`).\n\n## Built-in vs custom theme sources\n\nTheme lookup order (`loadThemeJson`):\n\n1. built-in embedded themes (`defaults/xcsh-dark.json` and `defaults/xcsh-light.json` compiled into `defaultThemes`)\n2. custom theme file: `<customThemesDir>/<name>.json`\n\nCustom themes directory comes from `getCustomThemesDir()`:\n\n- default: `~/.xcsh/agent/themes`\n- overridden by `PI_CODING_AGENT_DIR` (`$PI_CODING_AGENT_DIR/themes`)\n\n`getAvailableThemes()` returns merged built-in + custom names, sorted, with built-ins taking precedence on name collision.\n\n## Loading, validation, and resolution\n\nFor custom theme files:\n\n1. read JSON\n2. parse JSON\n3. validate against `ThemeJsonSchema`\n4. resolve `vars` references recursively\n5. convert resolved values to ANSI by terminal capability mode\n\nValidation behavior:\n\n- missing required color tokens: explicit grouped error message\n- bad token types/values: validation errors with JSON path\n- unknown theme file: `Theme not found: <name>`\n\nVar reference behavior:\n\n- supports nested references\n- throws on missing variable reference\n- throws on circular references\n\n## Terminal color mode behavior\n\nColor mode detection (`detectColorMode`):\n\n- `COLORTERM=truecolor|24bit` => truecolor\n- `WT_SESSION` => truecolor\n- `TERM` in `dumb`, `linux`, or empty => 256color\n- otherwise => truecolor\n\nConversion behavior:\n\n- hex -> `Bun.color(..., \"ansi-16m\" | \"ansi-256\")`\n- numeric -> `38;5` / `48;5` ANSI\n- `\"\"` -> default fg/bg reset\n\n## Runtime switching behavior\n\n### Initial theme (`initTheme`)\n\n`main.ts` initializes theme with settings:\n\n- `symbolPreset`\n- `colorBlindMode`\n- `theme.dark`\n- `theme.light`\n\nAuto theme slot selection uses `COLORFGBG` background detection:\n\n- parse background index from `COLORFGBG`\n- `< 8` => dark slot (`theme.dark`)\n- `>= 8` => light slot (`theme.light`)\n- parse failure => dark slot\n\nCurrent defaults from settings schema:\n\n- `theme.dark = \"xcsh-dark\"`\n- `theme.light = \"xcsh-light\"`\n- `symbolPreset = \"unicode\"`\n- `colorBlindMode = false`\n\n### Explicit switching (`setTheme`)\n\n- loads selected theme\n- updates global `theme` singleton\n- optionally starts watcher\n- triggers `onThemeChange` callback\n\nOn failure:\n\n- falls back to built-in `dark`\n- returns `{ success: false, error }`\n\n### Preview switching (`previewTheme`)\n\n- applies temporary preview theme to global `theme`\n- does **not** change persisted settings by itself\n- returns success/error without fallback replacement\n\nSettings UI uses this for live preview and restores prior theme on cancel.\n\n## Watchers and live reload\n\nWhen watcher is enabled (`setTheme(..., true)` / interactive init):\n\n- only watches custom file path `<customThemesDir>/<currentTheme>.json`\n- built-ins are effectively not watched\n- file `change`: attempts reload (debounced)\n- file `rename`/delete: falls back to `dark`, closes watcher\n\nAuto mode also installs a `SIGWINCH` listener and can re-evaluate dark/light slot mapping when terminal state changes.\n\n## Color-blind mode behavior\n\n`colorBlindMode` changes only one token at runtime:\n\n- `toolDiffAdded` is HSV-adjusted (green shifted toward blue)\n- adjustment is applied only when resolved value is a hex string\n\nOther tokens are unchanged.\n\n## Where theme settings are persisted\n\nTheme-related settings are persisted by `Settings` to global config YAML:\n\n- path: `<agentDir>/config.yml`\n- default agent dir: `~/.xcsh/agent`\n- effective default file: `~/.xcsh/agent/config.yml`\n\nPersisted keys:\n\n- `theme.dark`\n- `theme.light`\n- `symbolPreset`\n- `colorBlindMode`\n\nLegacy migration exists: old flat `theme: \"name\"` is migrated to nested `theme.dark` or `theme.light` based on luminance detection.\n\n## Creating a custom theme (practical)\n\n1. Create file in custom themes dir, e.g. `~/.xcsh/agent/themes/my-theme.json`.\n2. Include `name`, optional `vars`, and **all required** `colors` tokens.\n3. Optionally include `symbols` and `export`.\n4. Select the theme in Settings (`Display -> Dark theme` or `Display -> Light theme`) depending on which auto slot you want.\n\nMinimal skeleton. Every key in `colors` is required — the runtime validator\n(`additionalProperties: false`) rejects both missing keys and unknown keys.\nFor the shipped reference implementations see\n[`packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json)\nand [`xcsh-light.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-light.json).\n\nThe status line has two parallel color systems documented in issue #242:\n\n- Hex text colors (`statusLinePath`, `statusLineGitClean`, `statusLineGitDirty`,\n  `statusLineStaged`, `statusLineDirty`, `statusLineUntracked`) drive non-powerline\n  rendering.\n- 256-color palette indices (`statusLine<Segment>Bg` / `statusLine<Segment>Fg`)\n  drive powerline segment fills. They are independent of the hex keys above —\n  both must be set.\n\n```json\n{\n  \"name\": \"my-theme\",\n  \"vars\": {\n    \"accent\": \"#7aa2f7\",\n    \"muted\": 244\n  },\n  \"colors\": {\n    \"accent\": \"accent\",\n    \"chromeAccent\": \"accent\",\n    \"spinnerAccent\": \"accent\",\n    \"contentAccent\": \"muted\",\n    \"border\": \"#4c566a\",\n    \"borderAccent\": \"accent\",\n    \"borderMuted\": \"muted\",\n    \"success\": \"#9ece6a\",\n    \"error\": \"#f7768e\",\n    \"warning\": \"#e0af68\",\n    \"muted\": \"muted\",\n    \"dim\": 240,\n    \"gutterSuccess\": \"#7dcfff\",\n    \"gutterWarning\": \"#e0af68\",\n    \"text\": \"\",\n    \"thinkingText\": \"muted\",\n\n    \"selectedBg\": \"#2a2f45\",\n    \"userMessageBg\": \"#1f2335\",\n    \"userMessageText\": \"\",\n    \"customMessageBg\": \"#24283b\",\n    \"customMessageText\": \"\",\n    \"customMessageLabel\": \"accent\",\n    \"toolPendingBg\": \"#1f2335\",\n    \"toolSuccessBg\": \"#1f2d2a\",\n    \"toolErrorBg\": \"#2d1f2a\",\n    \"toolTitle\": \"\",\n    \"toolOutput\": \"muted\",\n\n    \"mdHeading\": \"accent\",\n    \"mdLink\": \"accent\",\n    \"mdLinkUrl\": \"muted\",\n    \"mdCode\": \"#c0caf5\",\n    \"mdCodeBlock\": \"#c0caf5\",\n    \"mdCodeBlockBorder\": \"muted\",\n    \"mdQuote\": \"muted\",\n    \"mdQuoteBorder\": \"muted\",\n    \"mdHr\": \"muted\",\n    \"mdListBullet\": \"accent\",\n\n    \"toolDiffAdded\": \"#9ece6a\",\n    \"toolDiffRemoved\": \"#f7768e\",\n    \"toolDiffContext\": \"muted\",\n\n    \"syntaxComment\": \"#565f89\",\n    \"syntaxKeyword\": \"#bb9af7\",\n    \"syntaxFunction\": \"#7aa2f7\",\n    \"syntaxVariable\": \"#c0caf5\",\n    \"syntaxString\": \"#9ece6a\",\n    \"syntaxNumber\": \"#ff9e64\",\n    \"syntaxType\": \"#2ac3de\",\n    \"syntaxOperator\": \"#89ddff\",\n    \"syntaxPunctuation\": \"#9aa5ce\",\n    \"syntaxControl\": \"#bb9af7\",\n\n    \"thinkingOff\": 240,\n    \"thinkingMinimal\": 244,\n    \"thinkingLow\": \"#7aa2f7\",\n    \"thinkingMedium\": \"#2ac3de\",\n    \"thinkingHigh\": \"#bb9af7\",\n    \"thinkingXhigh\": \"#f7768e\",\n\n    \"bashMode\": \"#2ac3de\",\n    \"pythonMode\": \"#bb9af7\",\n\n    \"statusLineBg\": \"#16161e\",\n    \"statusLineSep\": 240,\n    \"statusLineModel\": \"#bb9af7\",\n    \"statusLinePath\": \"#7aa2f7\",\n    \"statusLineGitClean\": \"#9ece6a\",\n    \"statusLineGitDirty\": \"#e0af68\",\n    \"statusLineContext\": \"#2ac3de\",\n    \"statusLineSpend\": \"#7dcfff\",\n    \"statusLineStaged\": \"#9ece6a\",\n    \"statusLineDirty\": \"#e0af68\",\n    \"statusLineUntracked\": \"#f7768e\",\n    \"statusLineOutput\": \"#c0caf5\",\n    \"statusLineCost\": \"#ff9e64\",\n    \"statusLineSubagents\": \"#bb9af7\",\n\n    \"statusLineOsIconBg\": 7,\n    \"statusLineOsIconFg\": 232,\n    \"statusLinePathBg\": 4,\n    \"statusLinePathFg\": 254,\n    \"statusLineGitCleanBg\": 2,\n    \"statusLineGitCleanFg\": 0,\n    \"statusLineGitDirtyBg\": 3,\n    \"statusLineGitDirtyFg\": 0,\n    \"statusLineGitStagedBg\": 64,\n    \"statusLineGitStagedFg\": 0,\n    \"statusLineGitUntrackedBg\": 39,\n    \"statusLineGitUntrackedFg\": 0,\n    \"statusLineGitConflictBg\": 1,\n    \"statusLineGitConflictFg\": 7,\n    \"statusLinePlanModeBg\": 236,\n    \"statusLinePlanModeFg\": 117,\n    \"statusLineProfileXcshBg\": \"accent\",\n    \"statusLineProfileXcshFg\": 231\n  }\n}\n```\n\n## Testing custom themes\n\nUse this workflow:\n\n1. Start interactive mode (watcher enabled from startup).\n2. Open settings and preview theme values (live `previewTheme`).\n3. For custom theme files, edit the JSON while running and confirm auto-reload on save.\n4. Exercise critical surfaces:\n   - markdown rendering\n   - tool blocks (pending/success/error)\n   - diff rendering (added/removed/context)\n   - status line readability\n   - thinking level border changes\n   - bash/python mode border colors\n5. Validate both symbol presets if your theme depends on glyph width/appearance.\n\n## Real constraints and caveats\n\n- All `colors` tokens are required for custom themes.\n- `export` and `symbols` are optional.\n- `$schema` in theme JSON is informational; runtime validation is enforced by compiled TypeBox schema in code.\n- `setTheme` failure falls back to `dark`; `previewTheme` failure does not replace current theme.\n- File watcher reload errors keep the current loaded theme until a successful reload or fallback path is triggered.\n",
	"en/tui/tree.md": "---\ntitle: Tree Command Reference\ndescription: /tree command reference for visualizing session history and conversation branches.\nsidebar:\n  order: 4\n  label: /tree command\n---\n\n# `/tree` Command Reference\n\n`/tree` opens the interactive **Session Tree** navigator. It lets you jump to any entry in the current session file and continue from that point.\n\nThis is an in-file leaf move, not a new session export.\n\n## What `/tree` does\n\n- Builds a tree from current session entries (`SessionManager.getTree()`)\n- Opens `TreeSelectorComponent` with keyboard navigation, filters, and search\n- On selection, calls `AgentSession.navigateTree(targetId, { summarize, customInstructions })`\n- Rebuilds visible chat from the new leaf path\n- Optionally prefills editor text when selecting a user/custom message\n\nPrimary implementation:\n\n- `src/modes/controllers/input-controller.ts` (`/tree`, keybinding wiring, double-escape behavior)\n- `src/modes/controllers/selector-controller.ts` (tree UI launch + summary prompt flow)\n- `src/modes/components/tree-selector.ts` (navigation, filters, search, labels, rendering)\n- `src/session/agent-session.ts` (`navigateTree` leaf switching + optional summary)\n- `src/session/session-manager.ts` (`getTree`, `branch`, `branchWithSummary`, `resetLeaf`, label persistence)\n\n## How to open it\n\nAny of the following opens the same selector:\n\n- `/tree`\n- configured keybinding action `tree`\n- double-escape on empty editor when `doubleEscapeAction = \"tree\"` (default)\n- `/branch` when `doubleEscapeAction = \"tree\"` (routes to tree selector instead of user-only branch picker)\n\n## Tree UI model\n\nThe tree is rendered from session entry parent pointers (`id` / `parentId`).\n\n- Children are sorted by timestamp ascending (older first, newer lower)\n- Active branch (path from root to current leaf) is marked with a bullet\n- Labels (if present) render as `[label]` before node text\n- If multiple roots exist (orphaned/broken parent chains), they are shown under a virtual branching root\n\n```text\nExample tree view (active path marked with •):\n\n├─ user: \"Start task\"\n│  └─ assistant: \"Plan\"\n│     ├─ • user: \"Try approach A\"\n│     │  └─ • assistant: \"A result\"\n│     │     └─ • [milestone] user: \"Continue A\"\n│     └─ user: \"Try approach B\"\n│        └─ assistant: \"B result\"\n```\n\nThe selector recenters around current selection and shows up to:\n\n- `max(5, floor(terminalHeight / 2))` rows\n\n## Keybindings inside tree selector\n\n- `Up` / `Down`: move selection (wraps)\n- `Left` / `Right`: page up / page down\n- `Enter`: select node\n- `Esc`: clear search if active; otherwise close selector\n- `Ctrl+C`: close selector\n- `Type`: append to search query\n- `Backspace`: delete search character\n- `Shift+L`: edit/clear label on selected entry\n- `Ctrl+O`: cycle filter forward\n- `Shift+Ctrl+O`: cycle filter backward\n- `Alt+D/T/U/L/A`: jump directly to specific filter mode\n\n## Filters and search semantics\n\nFilter modes (`TreeList`):\n\n1. `default`\n2. `no-tools`\n3. `user-only`\n4. `labeled-only`\n5. `all`\n\n### `default`\n\nShows most conversational nodes, but hides bookkeeping entry types:\n\n- `label`\n- `custom`\n- `model_change`\n- `thinking_level_change`\n\n### `no-tools`\n\nSame as `default`, plus hides `toolResult` messages.\n\n### `user-only`\n\nOnly `message` entries where role is `user`.\n\n### `labeled-only`\n\nOnly entries that currently resolve to a label.\n\n### `all`\n\nEverything in the session tree, including bookkeeping/custom entries.\n\n### Tool-only assistant node behavior\n\nAssistant messages that contain **only tool calls** (no text) are hidden by default in all filtered views unless:\n\n- message is error/aborted (`stopReason` not `stop`/`toolUse`), or\n- it is the current leaf (always kept visible)\n\n### Search behavior\n\n- Query is tokenized by spaces\n- Matching is case-insensitive\n- All tokens must match (AND semantics)\n- Searchable text includes label, role, and type-specific content (message text, branch summary text, custom type, tool command snippets, etc.)\n\n## Selection outcomes (important)\n\n`navigateTree` computes new leaf behavior from selected entry type:\n\n### Selecting `user` message\n\n- New leaf becomes selected entry’s `parentId`\n- If parent is `null` (root user message), leaf resets to root (`resetLeaf()`)\n- Selected message text is copied to editor for editing/resubmit\n\n### Selecting `custom_message`\n\n- Same leaf rule as user messages (`parentId`)\n- Text content is extracted and copied to editor\n\n### Selecting non-user node (assistant/tool/summary/compaction/custom bookkeeping/etc.)\n\n- New leaf becomes selected node id\n- Editor is not prefilled\n\n### Selecting current leaf\n\n- No-op; selector closes with “Already at this point”\n\n```text\nSelection decision (simplified):\n\nselected node\n   │\n   ├─ is current leaf? ── yes ──> close selector (no-op)\n   │\n   ├─ is user/custom_message? ── yes ──> leaf := parentId (or resetLeaf for root)\n   │                                     + prefill editor text\n   │\n   └─ otherwise ──> leaf := selected node id\n                    + no editor prefill\n```\n\n## Summary-on-switch flow\n\nSummary prompt is controlled by `branchSummary.enabled` (default: `false`).\n\nWhen enabled, after picking a node the UI asks:\n\n- `No summary`\n- `Summarize`\n- `Summarize with custom prompt`\n\nFlow details:\n\n- Escape in summary prompt reopens tree selector\n- Custom prompt cancellation returns to summary choice loop\n- During summarization, UI shows loader and binds `Esc` to `abortBranchSummary()`\n- If summarization aborts, tree selector reopens and no move is applied\n\n`navigateTree` internals:\n\n- Collects abandoned-branch entries from old leaf to common ancestor\n- Emits `session_before_tree` (extensions can cancel or inject summary)\n- Uses default summarizer only if requested and needed\n- Applies move with:\n  - `branchWithSummary(...)` when summary exists\n  - `branch(newLeafId)` for non-root move without summary\n  - `resetLeaf()` for root move without summary\n- Replaces agent conversation with rebuilt session context\n- Emits `session_tree`\n\nNote: if user requests summary but there is nothing to summarize, navigation proceeds without creating a summary entry.\n\n## Labels\n\nLabel edits in tree UI call `appendLabelChange(targetId, label)`.\n\n- non-empty label sets/updates resolved label\n- empty label clears it\n- labels are stored as append-only `label` entries\n- tree nodes display resolved label state, not raw label-entry history\n\n## `/tree` vs adjacent operations\n\n| Operation | Scope | Result |\n|---|---|---|\n| `/tree` | Current session file | Moves leaf to selected point (same file) |\n| `/branch` | Usually current session file -> new session file | By default branches from selected **user** message into a new session file; if `doubleEscapeAction = \"tree\"`, `/branch` opens tree navigation UI instead |\n| `/fork` | Whole current session | Duplicates session into a new persisted session file |\n| `/resume` | Session list | Switches to another session file |\n\nKey distinction: `/tree` is a navigation/repositioning tool inside one session file. `/branch`, `/fork`, and `/resume` all change session-file context.\n\n## Operator workflows\n\n### Re-run from an earlier user prompt without losing current branch\n\n1. `/tree`\n2. search/select earlier user message\n3. choose `No summary` (or summarize if needed)\n4. edit prefilled text in editor\n5. submit\n\nEffect: new branch grows from selected point within same session file.\n\n### Leave current branch with context breadcrumb\n\n1. enable `branchSummary.enabled`\n2. `/tree` and select target node\n3. choose `Summarize` (or custom prompt)\n\nEffect: a `branch_summary` entry is appended at the target position before continuing.\n\n### Investigate hidden bookkeeping entries\n\n1. `/tree`\n2. press `Alt+A` (all)\n3. search for `model`, `thinking`, `custom`, or labels\n\nEffect: inspect full internal timeline, not just conversational nodes.\n\n### Bookmark pivot points for later jumps\n\n1. `/tree`\n2. move to entry\n3. `Shift+L` and set label\n4. later use `Alt+L` (`labeled-only`) to jump quickly\n\nEffect: fast navigation among durable branch landmarks.\n",
	"en/tui/tui-runtime-internals.md": "---\ntitle: TUI Runtime Internals\ndescription: Terminal UI runtime internals covering rendering pipeline, input handling, and state management.\nsidebar:\n  order: 2\n  label: Runtime internals\n---\n\n# TUI runtime internals\n\nThis document maps the non-theme runtime path from terminal input to rendered output in interactive mode. It focuses on behavior in `packages/tui` and its integration from `packages/coding-agent` controllers.\n\n## Runtime layers and ownership\n\n- **`packages/tui` engine**: terminal lifecycle, stdin normalization, focus routing, render scheduling, differential painting, overlay composition, hardware cursor placement.\n- **`packages/coding-agent` interactive mode**: builds component tree, binds editor callbacks and keymaps, reacts to agent/session events, and translates domain state (streaming, tool execution, retries, plan mode) into UI components.\n\nBoundary rule: the TUI engine is message-agnostic. It only knows `Component.render(width)`, `handleInput(data)`, focus, and overlays. Agent semantics stay in interactive controllers.\n\n## Implementation files\n\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/components/custom-editor.ts`](../../packages/coding-agent/src/modes/components/custom-editor.ts)\n- [`../../tui/src/tui.ts`](../../packages/tui/src/tui.ts)\n- [`../../tui/src/terminal.ts`](../../packages/tui/src/terminal.ts)\n- [`../../tui/src/editor-component.ts`](../../packages/tui/src/editor-component.ts)\n- [`../../tui/src/stdin-buffer.ts`](../../packages/tui/src/stdin-buffer.ts)\n- [`../../tui/src/components/loader.ts`](../../packages/tui/src/components/loader.ts)\n\n## Boot and component tree assembly\n\n`InteractiveMode` constructs `TUI(new ProcessTerminal(), showHardwareCursor)` and creates persistent containers:\n\n- `chatContainer`\n- `pendingMessagesContainer`\n- `statusContainer`\n- `todoContainer`\n- `statusLine`\n- `editorContainer` (holds `CustomEditor`)\n\n`init()` wires the tree in that order, focuses the editor, registers input handlers via `InputController`, starts TUI, and requests a forced render.\n\nA forced render (`requestRender(true)`) resets previous-line caches and cursor bookkeeping before repainting.\n\n## Terminal lifecycle and stdin normalization\n\n`ProcessTerminal.start()`:\n\n1. Enables raw mode and bracketed paste.\n2. Attaches resize handler.\n3. Creates a `StdinBuffer` to split partial escape chunks into complete sequences.\n4. Queries Kitty keyboard protocol support (`CSI ? u`), then enables protocol flags if supported.\n5. On Windows, attempts VT input enablement via `kernel32` mode flags.\n\n`StdinBuffer` behavior:\n\n- Buffers fragmented escape sequences (CSI/OSC/DCS/APC/SS3).\n- Emits `data` only when a sequence is complete or timeout-flushed.\n- Detects bracketed paste and emits a `paste` event with raw pasted text.\n\nThis prevents partial escape chunks from being misinterpreted as normal keypresses.\n\n## Input routing and focus model\n\nInput path:\n\n`stdin -> ProcessTerminal -> StdinBuffer -> TUI.#handleInput -> focusedComponent.handleInput`\n\nRouting details:\n\n1. TUI runs registered input listeners first (`addInputListener`), allowing consume/transform behavior.\n2. TUI handles global debug shortcut (`shift+ctrl+d`) before component dispatch.\n3. If focused component belongs to an overlay that is now hidden/invisible, TUI reassigns focus to next visible overlay or saved pre-overlay focus.\n4. Key release events are filtered unless focused component sets `wantsKeyRelease = true`.\n5. After dispatch, TUI schedules render.\n\n`setFocus()` also toggles `Focusable.focused`, which controls whether components emit `CURSOR_MARKER` for hardware cursor placement.\n\n## Key handling split: editor vs controller\n\n`CustomEditor` intercepts high-priority combos first (escape, ctrl-c/d/z, ctrl-v, ctrl-p variants, ctrl-t, alt-up, extension custom keys) and delegates the rest to base `Editor` behavior (text editing, history, autocomplete, cursor movement).\n\n`InputController.setupKeyHandlers()` then binds editor callbacks to mode actions:\n\n- cancellation / mode exits on `Escape`\n- shutdown on double `Ctrl+C` or empty-editor `Ctrl+D`\n- suspend/resume on `Ctrl+Z`\n- slash-command and selector hotkeys\n- follow-up/dequeue toggles and expansion toggles\n\nThis keeps key parsing/editor mechanics in `packages/tui` and mode semantics in coding-agent controllers.\n\n## Render loop and diffing strategy\n\n`TUI.requestRender()` is debounced to one render per tick using `process.nextTick`. Multiple state changes in the same turn coalesce.\n\n`#doRender()` pipeline:\n\n1. Render root component tree to `newLines`.\n2. Composite visible overlays (if any).\n3. Extract and strip `CURSOR_MARKER` from visible viewport lines.\n4. Append segment reset suffixes for non-image lines.\n5. Choose full repaint vs differential patch:\n   - first frame\n   - width change\n   - shrink with `clearOnShrink` enabled and no overlays\n   - edits above previous viewport\n6. For differential updates, patch only changed line range and clear stale trailing lines when needed.\n7. Reposition hardware cursor for IME support.\n\nRender writes use synchronized output mode (`CSI ? 2026 h/l`) to reduce flicker/tearing.\n\n## Render safety constraints\n\nCritical safety checks in `TUI`:\n\n- Non-image rendered lines must not exceed terminal width; overflow throws and writes crash diagnostics.\n- Overlay compositing includes defensive truncation and post-composite width verification.\n- Width changes force full redraw because wrapping semantics change.\n- Cursor position is clamped before movement.\n\nThese constraints are runtime enforcement, not just conventions.\n\n## Resize handling\n\nResize events are event-driven from `ProcessTerminal` to `TUI.requestRender()`.\n\nEffects:\n\n- Any width change triggers full redraw.\n- Viewport/top tracking (`#previousViewportTop`, `#maxLinesRendered`) avoids invalid relative cursor math when content or terminal size changes.\n- Overlay visibility can depend on terminal dimensions (`OverlayOptions.visible`); focus is corrected when overlays become non-visible after resize.\n\n## Streaming and incremental UI updates\n\n`EventController` subscribes to `AgentSessionEvent` and updates UI incrementally:\n\n- `agent_start`: starts loader in `statusContainer`.\n- `message_start` assistant: creates `streamingComponent` and mounts it.\n- `message_update`: updates streaming assistant content; creates/updates tool execution components as tool calls appear.\n- `tool_execution_update/end`: updates tool result components and completion state.\n- `message_end`: finalizes assistant stream, handles aborted/error annotations, marks pending tool args complete on normal stop.\n- `agent_end`: stops loaders, clears transient stream state, flushes deferred model switch, issues completion notification if backgrounded.\n\nRead-tool grouping is intentionally stateful (`#lastReadGroup`) to coalesce consecutive read tool calls into one visual block until a non-read break occurs.\n\n## Status and loader orchestration\n\nStatus lane ownership:\n\n- `statusContainer` holds transient loaders (`loadingAnimation`, `autoCompactionLoader`, `retryLoader`).\n- `statusLine` renders persistent status/hooks/plan indicators and drives editor top border updates.\n\nLoader behavior:\n\n- `Loader` updates every 80ms via interval and requests render each frame.\n- Escape handlers are temporarily overridden during auto-compaction and auto-retry to cancel those operations.\n- On end/cancel paths, controllers restore prior escape handlers and stop/clear loader components.\n\n## Mode transitions and backgrounding\n\n### Bash/Python input modes\n\nInput text prefixes toggle editor border mode flags:\n\n- `!` -> bash mode\n- `$` (non-template literal prefix) -> python mode\n\nEscape exits inactive mode by clearing editor text and restoring border color; when execution is active, escape aborts the running task instead.\n\n### Plan mode\n\n`InteractiveMode` tracks plan mode flags, status-line state, active tools, and model switching. Enter/exit updates session mode entries and status/UI state, including deferred model switch if streaming is active.\n\n### Suspend/resume (`Ctrl+Z`)\n\n`InputController.handleCtrlZ()`:\n\n1. Registers one-shot `SIGCONT` handler to restart TUI and force render.\n2. Stops TUI before suspend.\n3. Sends `SIGTSTP` to process group.\n\n### Background mode (`/background` or `/bg`)\n\n`handleBackgroundCommand()`:\n\n- Rejects when idle.\n- Switches tool UI context to non-interactive (`hasUI=false`) so interactive UI tools fail fast.\n- Stops loaders/status line and unsubscribes foreground event handler.\n- Subscribes background event handler (primarily waits for `agent_end`).\n- Stops TUI and sends `SIGTSTP` (POSIX job control path).\n\nOn `agent_end` in background with no queued work, controller sends completion notification and shuts down.\n\n## Cancellation paths\n\nPrimary cancellation inputs:\n\n- `Escape` during active stream loader: restores queued messages to editor and aborts agent.\n- `Escape` during bash/python execution: aborts running command.\n- `Escape` during auto-compaction/retry: invokes dedicated abort methods through temporary escape handlers.\n- `Ctrl+C` single press: clear editor; double press within 500ms: shutdown.\n\nCancellation is state-conditional; same key can mean abort, mode-exit, selector trigger, or no-op depending on runtime state.\n\n## Event-driven vs throttled behavior\n\nEvent-driven updates:\n\n- Agent session events (`EventController`)\n- Key input callbacks (`InputController`)\n- terminal resize callback\n- theme/branch watchers in `InteractiveMode`\n\nThrottled/debounced paths:\n\n- TUI rendering is tick-debounced (`requestRender` coalescing).\n- Loader animation is fixed-interval (80ms), each frame requesting render.\n- Editor autocomplete updates (inside `Editor`) use debounce timers, reducing recompute churn during typing.\n\nThe runtime therefore mixes event-driven state transitions with bounded render cadence to keep interactivity responsive without repaint storms.\n",
	"en/tui/tui.md": "---\ntitle: TUI Integration for Extensions and Custom Tools\ndescription: TUI integration contract for extensions, custom tools, and custom renderers.\nsidebar:\n  order: 1\n  label: Extension integration\n---\n\n# TUI integration for extensions and custom tools\n\nThis document covers the **current** TUI contract used by `packages/coding-agent` and `packages/tui` for extension UI, custom tool UI, and custom renderers.\n\n## What this subsystem is\n\nThe runtime has two layers:\n\n- **Rendering engine (`packages/tui`)**: differential terminal renderer, input dispatch, focus, overlays, cursor placement.\n- **Integration layer (`packages/coding-agent`)**: mounts extension/custom-tool components, wires keybindings/theme, and restores editor state.\n\n## Runtime behavior by mode\n\n| Mode | `ctx.ui.custom(...)` availability | Notes |\n| --- | --- | --- |\n| Interactive TUI | Supported | Component is mounted in the editor area, focused, and must call `done(result)` to resolve. |\n| Background/headless | Not interactive | UI context is no-op (`hasUI === false`). |\n| RPC mode | Not supported | `custom()` returns `Promise<never>` and does not mount TUI components. |\n\nIf your extension/tool can run in non-interactive mode, guard with `ctx.hasUI` / `pi.hasUI`.\n\n## Core component contract (`@f5-sales-demo/pi-tui`)\n\n`packages/tui/src/tui.ts` defines:\n\n```ts\nexport interface Component {\n  render(width: number): string[];\n  handleInput?(data: string): void;\n  wantsKeyRelease?: boolean;\n  invalidate(): void;\n}\n```\n\n`Focusable` is separate:\n\n```ts\nexport interface Focusable {\n  focused: boolean;\n}\n```\n\nCursor behavior uses `CURSOR_MARKER` (not `getCursorPosition`). Focused components emit the marker in rendered text; `TUI` extracts it and positions the hardware cursor.\n\n## Rendering constraints (terminal safety)\n\nYour `render(width)` output must be terminal-safe:\n\n1. **Never exceed `width` on any line**. The renderer throws if a non-image line overflows.\n2. **Measure visual width**, not string length: use `visibleWidth()`.\n3. **Truncate/wrap ANSI-aware text** with `truncateToWidth()` / `wrapTextWithAnsi()`.\n4. **Sanitize tabs/content** from external sources using `replaceTabs()` (and higher-level sanitizers in coding-agent render paths).\n\nMinimal pattern:\n\n```ts\nimport { replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\n\nrender(width: number): string[] {\n  return this.lines.map(line => truncateToWidth(replaceTabs(line), width));\n}\n```\n\n## Input handling and keybindings\n\n### Raw key matching\n\nUse `matchesKey(data, \"...\")` for navigation keys and combos.\n\n### Respect user-configured app keybindings\n\nExtension UI factories receive a `KeybindingsManager` (interactive mode) so you can honor mapped actions instead of hardcoding keys:\n\n```ts\nif (keybindings.matches(data, \"interrupt\")) {\n  done(undefined);\n  return;\n}\n```\n\n### Key release/repeat events\n\nKey release events are filtered unless your component sets:\n\n```ts\nwantsKeyRelease = true;\n```\n\nThen use `isKeyRelease()` / `isKeyRepeat()` if needed.\n\n## Focus, overlays, and cursor\n\n- `TUI.setFocus(component)` routes input to that component.\n- Overlay APIs exist in `TUI` (`showOverlay`, `OverlayHandle`), but extension `ctx.ui.custom` mounting in interactive mode currently replaces the editor component area directly.\n- The `custom(..., options?: { overlay?: boolean })` option exists in extension types; interactive extension mounting currently ignores this option.\n\n## Mount points and return contracts\n\n## 1) Extension UI (`ExtensionUIContext`)\n\nCurrent signature (`extensibility/extensions/types.ts`):\n\n```ts\ncustom<T>(\n  factory: (\n    tui: TUI,\n    theme: Theme,\n    keybindings: KeybindingsManager,\n    done: (result: T) => void,\n  ) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n  options?: { overlay?: boolean },\n): Promise<T>\n```\n\nBehavior in interactive mode (`extension-ui-controller.ts`):\n\n- Saves editor text.\n- Replaces editor component with your component.\n- Focuses your component.\n- On `done(result)`: calls `component.dispose?.()`, restores editor + text, focuses editor, resolves promise.\n\nSo `done(...)` is mandatory for completion.\n\n## 2) Hook/custom-tool UI context (legacy typing)\n\n`HookUIContext.custom` is typed as `(tui, theme, done)` in hook/custom-tool types.\nUnderlying interactive implementation calls factories with `(tui, theme, keybindings, done)`. JS consumers can use the extra arg; type-level compatibility still reflects the 3-arg legacy signature.\n\nCustom tools typically use the same UI entrypoint via the factory-scoped `pi.ui` object, then return the selected value in normal tool content:\n\n```ts\nasync execute(toolCallId, params, onUpdate, ctx, signal) {\n  if (!pi.hasUI) {\n    return { content: [{ type: \"text\", text: \"UI unavailable\" }] };\n  }\n\n  const picked = await pi.ui.custom<string | undefined>((tui, theme, done) => {\n    const component = new MyPickerComponent(done, signal);\n    return component;\n  });\n\n  return { content: [{ type: \"text\", text: picked ? `Picked: ${picked}` : \"Cancelled\" }] };\n}\n```\n\n## 3) Custom tool call/result renderers\n\nCustom tools and extension tools can return components from:\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\n`options` currently includes:\n\n- `expanded: boolean`\n- `isPartial: boolean`\n- `spinnerFrame?: number`\n\nThese renderers are mounted by `ToolExecutionComponent`.\n\n## Lifecycle and cancellation\n\n- `dispose()` is optional at type level but should be implemented when you own timers, subprocesses, watchers, sockets, or overlays.\n- `done(...)` should be called exactly once from your component flow.\n- For cancellable long-running UI, pair `CancellableLoader` with `AbortSignal` and call `done(...)` from `onAbort`.\n\nExample cancellation pattern:\n\n```ts\nconst loader = new CancellableLoader(tui, theme.fg(\"accent\"), theme.fg(\"muted\"), \"Working...\");\nloader.onAbort = () => done(undefined);\nvoid doWork(loader.signal).then(result => done(result));\nreturn loader;\n```\n\n## Realistic custom component example (extension command)\n\n```ts\nimport type { Component } from \"@f5-sales-demo/pi-tui\";\nimport { SelectList, matchesKey, replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\nimport { getSelectListTheme, type ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nclass Picker implements Component {\n  list: SelectList;\n  keybindings: any;\n  done: (value: string | undefined) => void;\n\n  constructor(\n    items: Array<{ value: string; label: string }>,\n    keybindings: any,\n    done: (value: string | undefined) => void,\n  ) {\n    this.list = new SelectList(items, 8, getSelectListTheme());\n    this.keybindings = keybindings;\n    this.done = done;\n    this.list.onSelect = item => this.done(item.value);\n    this.list.onCancel = () => this.done(undefined);\n  }\n\n  handleInput(data: string): void {\n    if (this.keybindings.matches(data, \"interrupt\")) {\n      this.done(undefined);\n      return;\n    }\n    this.list.handleInput(data);\n  }\n\n  render(width: number): string[] {\n    return this.list.render(width).map(line => truncateToWidth(replaceTabs(line), width));\n  }\n\n  invalidate(): void {\n    this.list.invalidate();\n  }\n}\n\nexport default function extension(pi: ExtensionAPI): void {\n  pi.registerCommand(\"pick-model\", {\n    description: \"Pick a model profile\",\n    handler: async (_args, ctx) => {\n      if (!ctx.hasUI) return;\n\n      const selected = await ctx.ui.custom<string | undefined>((tui, theme, keybindings, done) => {\n        const items = [\n          { value: \"fast\", label: theme.fg(\"accent\", \"Fast\") },\n          { value: \"balanced\", label: \"Balanced\" },\n          { value: \"quality\", label: \"Quality\" },\n        ];\n        return new Picker(items, keybindings, done);\n      });\n\n      if (selected) ctx.ui.notify(`Selected profile: ${selected}`, \"info\");\n    },\n  });\n}\n```\n\n## Key implementation files\n\n- `packages/tui/src/tui.ts` — `Component`, `Focusable`, cursor marker, focus, overlay, input dispatch.\n- `packages/tui/src/utils.ts` — width/truncation/sanitization primitives.\n- `packages/tui/src/keys.ts` / `keybindings.ts` — key parsing and configurable action mapping.\n- `packages/coding-agent/src/modes/controllers/extension-ui-controller.ts` — interactive mounting/unmounting for extension/hook/custom-tool UI.\n- `packages/coding-agent/src/extensibility/extensions/types.ts` — extension UI and renderer contracts.\n- `packages/coding-agent/src/extensibility/hooks/types.ts` — hook UI contract (legacy custom signature).\n- `packages/coding-agent/src/extensibility/custom-tools/types.ts` — custom tool execute/render contracts.\n- `packages/coding-agent/src/modes/components/tool-execution.ts` — mounting `renderCall`/`renderResult` components and partial-state options.\n- `packages/coding-agent/src/tools/context.ts` — tool UI context propagation (`hasUI`, `ui`).\n",
	"es/configuration/blob-artifact-architecture.md": "---\ntitle: Arquitectura de almacenamiento de blobs y artefactos\ndescription: >-\n  Almacén de blobs con direccionamiento por contenido y registro de artefactos\n  para medios de sesión, capturas de pantalla y salidas de herramientas.\nsidebar:\n  order: 7\n  label: Almacenamiento de blobs y artefactos\ni18n:\n  sourceHash: 70d255f48d5b\n  translator: machine\n---\n\n# Arquitectura de almacenamiento de blobs y artefactos\n\nEste documento describe cómo coding-agent almacena cargas útiles grandes/binarias fuera del JSONL de sesión, cómo se persisten las salidas truncadas de herramientas y cómo las URLs internas (`artifact://`, `agent://`) se resuelven de vuelta a los datos almacenados.\n\n## Por qué existen dos sistemas de almacenamiento\n\nEl runtime utiliza dos mecanismos de persistencia diferentes para diferentes formas de datos:\n\n- **Blobs con direccionamiento por contenido** (`blob:sha256:<hash>`): almacenamiento global orientado a binarios, utilizado para externalizar cargas útiles grandes de imágenes en base64 de las entradas de sesión persistidas.\n- **Artefactos con alcance de sesión** (archivos bajo `<archivoSesión-sin-.jsonl>/`): archivos de texto por sesión utilizados para salidas completas de herramientas y salidas de subagentes.\n\nEstán separados intencionalmente:\n\n- el almacenamiento de blobs optimiza la deduplicación y las referencias estables mediante hash de contenido,\n- el almacenamiento de artefactos optimiza las herramientas de sesión de solo adición y la recuperación por humanos/herramientas mediante IDs locales.\n\n## Límites de almacenamiento y disposición en disco\n\n## Límite del almacén de blobs (global)\n\n`SessionManager` construye `BlobStore(getBlobsDir())`, por lo que los archivos de blobs residen en un directorio de blobs global compartido (no en una carpeta de sesión).\n\nNomenclatura de archivos de blobs:\n\n- ruta del archivo: `<blobsDir>/<sha256-hex>`\n- sin extensión\n- cadena de referencia almacenada en las entradas: `blob:sha256:<sha256-hex>`\n\nImplicaciones:\n\n- el mismo contenido binario en diferentes sesiones se resuelve al mismo hash/ruta,\n- las escrituras son idempotentes a nivel de contenido,\n- los blobs pueden sobrevivir a cualquier archivo de sesión individual.\n\n## Límite de artefactos (local a la sesión)\n\n`ArtifactManager` deriva el directorio de artefactos a partir de la ruta del archivo de sesión:\n\n- archivo de sesión: `.../<timestamp>_<sessionId>.jsonl`\n- directorio de artefactos: `.../<timestamp>_<sessionId>/` (se elimina `.jsonl`)\n\nLos tipos de artefactos comparten este directorio:\n\n- archivos de salida truncada de herramientas: `<numericId>.<toolType>.log` (para `artifact://`)\n- archivos de salida de subagentes: `<outputId>.md` (para `agent://`)\n\n## Esquemas de asignación de IDs y nombres\n\n## IDs de blobs: hash de contenido\n\n`BlobStore.put()` calcula SHA-256 sobre los bytes binarios en bruto y devuelve:\n\n- `hash`: resumen hexadecimal,\n- `path`: `<blobsDir>/<hash>`,\n- `ref`: `blob:sha256:<hash>`.\n\nNo se utiliza ningún contador local de sesión.\n\n## IDs de artefactos: entero monotónico local a la sesión\n\n`ArtifactManager` escanea los archivos de artefactos `*.log` existentes en el primer uso para encontrar el ID numérico máximo existente y establece `nextId = max + 1`.\n\nComportamiento de asignación:\n\n- formato de archivo: `{id}.{toolType}.log`\n- los IDs son cadenas secuenciales (`\"0\"`, `\"1\"`, ...)\n- la reanudación no sobrescribe artefactos existentes porque el escaneo ocurre antes de la asignación.\n\nSi el directorio de artefactos no existe, el escaneo produce una lista vacía y la asignación comienza desde `0`.\n\n## IDs de salida de agentes (`agent://`)\n\n`AgentOutputManager` asigna IDs para las salidas de subagentes como `<index>-<requestedId>` (opcionalmente anidados bajo un prefijo padre, por ejemplo `0-Parent.1-Child`). Escanea los archivos `.md` existentes durante la inicialización para continuar desde el siguiente índice en la reanudación.\n\n## Flujo de datos de persistencia\n\n## 1) Ruta de reescritura de persistencia de entradas de sesión\n\nAntes de que las entradas de sesión se escriban (`#rewriteFile` / persistencia incremental), `SessionManager` llama a `prepareEntryForPersistence()` (a través de `truncateForPersistence`).\n\nComportamientos clave:\n\n1. **Truncado de cadenas grandes**: las cadenas sobredimensionadas se cortan y se les añade el sufijo `\"[Session persistence truncated large content]\"`.\n2. **Eliminación de campos transitorios**: `partialJson` y `jsonlEvents` se eliminan de las entradas persistidas.\n3. **Externalización de imágenes a blobs**:\n   - solo se aplica a bloques de imagen en arrays `content`,\n   - solo cuando `data` no es ya una referencia de blob,\n   - solo cuando la longitud del base64 alcanza al menos el umbral (`BLOB_EXTERNALIZE_THRESHOLD = 1024`),\n   - reemplaza el base64 en línea con `blob:sha256:<hash>`.\n\nEsto mantiene el JSONL de sesión compacto mientras preserva la recuperabilidad.\n\n## 2) Ruta de rehidratación en la carga de sesión\n\nAl abrir una sesión (`setSessionFile`), después de las migraciones, `SessionManager` ejecuta `resolveBlobRefsInEntries()`.\n\nPara cada bloque de imagen de message/custom-message con `blob:sha256:<hash>`:\n\n- lee los bytes del blob desde el almacén de blobs,\n- convierte los bytes de vuelta a base64,\n- muta la entrada en memoria para incluir el base64 en línea para los consumidores del runtime.\n\nSi el blob no se encuentra:\n\n- `resolveImageData()` registra una advertencia,\n- devuelve la cadena de referencia original sin cambios,\n- la carga continúa (sin fallo crítico).\n\n## 3) Ruta de volcado/truncado de salida de herramientas\n\n`OutputSink` potencia la salida en streaming en bash/python/ssh y ejecutores relacionados.\n\nComportamiento:\n\n1. Cada fragmento se sanitiza y se añade al buffer de cola en memoria.\n2. Cuando los bytes en memoria exceden el umbral de volcado (`DEFAULT_MAX_BYTES`, 50KB), el sink marca la salida como truncada.\n3. Si hay disponible una ruta de artefacto, el sink abre un escritor de archivos y escribe:\n   - el contenido almacenado en buffer existente una vez,\n   - todos los fragmentos subsiguientes.\n4. El buffer en memoria siempre se recorta a la ventana de cola para visualización.\n5. `dump()` devuelve un resumen que incluye `artifactId` solo cuando el escritor de archivos se creó exitosamente.\n\nEfecto práctico:\n\n- La UI/retorno de herramienta muestra la cola truncada,\n- la salida completa se preserva en el archivo de artefacto y se referencia como `artifact://<id>`.\n\nSi la creación del escritor de archivos falla (error de E/S, ruta inexistente, etc.), el sink recurre silenciosamente al truncado solo en memoria; la salida completa no se persiste.\n\n## Modelo de acceso por URL\n\n## Referencias `blob:`\n\n`blob:sha256:<hash>` es una referencia de persistencia dentro de las cargas útiles de entradas de sesión, no un esquema de URL interno manejado por el enrutador. La resolución la realiza `SessionManager` durante la carga de sesión.\n\n## `artifact://<id>`\n\nManejado por `ArtifactProtocolHandler`:\n\n- requiere un directorio de artefactos de sesión activo,\n- el ID debe ser numérico,\n- se resuelve comparando el prefijo del nombre de archivo `<id>.`,\n- devuelve texto sin formato (`text/plain`) del archivo `.log` coincidente,\n- cuando no se encuentra, el error incluye una lista de IDs de artefactos disponibles.\n\nComportamiento con directorio inexistente:\n\n- si el directorio de artefactos no existe, lanza `No artifacts directory found`.\n\n## `agent://<id>`\n\nManejado por `AgentProtocolHandler` sobre `<artifactsDir>/<id>.md`:\n\n- la forma simple devuelve texto markdown,\n- las formas `/path` o `?q=` realizan extracción JSON,\n- la extracción por ruta y por consulta no se pueden combinar,\n- si se solicita extracción, el contenido del archivo debe parsearse como JSON.\n\nComportamiento con directorio inexistente:\n\n- lanza `No artifacts directory found`.\n\nComportamiento con salida inexistente:\n\n- lanza `Not found: <id>` con los IDs disponibles de los archivos `.md` existentes.\n\nIntegración con la herramienta read:\n\n- `read` soporta paginación con offset/limit para lecturas de URLs internas sin extracción,\n- rechaza `offset/limit` cuando se utiliza extracción de `agent://`.\n\n## Semánticas de reanudación, bifurcación y movimiento\n\n## Reanudación\n\n- `ArtifactManager` escanea los archivos `{id}.*.log` existentes en la primera asignación y continúa la numeración.\n- `AgentOutputManager` escanea los IDs de salida `.md` existentes y continúa la numeración.\n- `SessionManager` rehidrata las referencias de blob a base64 durante la carga.\n\n## Bifurcación (fork)\n\n`SessionManager.fork()` crea un nuevo archivo de sesión con un nuevo ID de sesión y un enlace `parentSession`, luego devuelve las rutas de archivo antigua/nueva. La copia de artefactos es manejada por `AgentSession.fork()`:\n\n- intenta una copia recursiva del directorio de artefactos antiguo al nuevo directorio de artefactos,\n- se tolera la ausencia del directorio antiguo,\n- los errores de copia que no sean ENOENT se registran como advertencias y la bifurcación se completa igualmente.\n\nImplicaciones de IDs después de la bifurcación:\n\n- si la copia tuvo éxito, los contadores de artefactos en la nueva sesión continúan después del ID máximo copiado,\n- si la copia falló/se omitió, los IDs de artefactos de la nueva sesión comienzan desde `0`.\n\nImplicaciones de blobs después de la bifurcación:\n\n- los blobs son globales y con direccionamiento por contenido, por lo que no se requiere copia del directorio de blobs.\n\n## Mover a un nuevo cwd\n\n`SessionManager.moveTo()` renombra tanto el archivo de sesión como el directorio de artefactos al nuevo directorio de sesión predeterminado, con lógica de rollback si un paso posterior falla. Esto preserva la identidad de los artefactos mientras se reubica el alcance de la sesión.\n\n## Manejo de fallos y rutas de respaldo\n\n| Caso | Comportamiento |\n| --- | --- |\n| Archivo de blob inexistente durante la rehidratación | Advierte y mantiene la cadena de referencia `blob:sha256:` en memoria |\n| Blob con ENOENT al leer vía `BlobStore.get` | Devuelve `null` |\n| Directorio de artefactos inexistente (`ArtifactManager.listFiles`) | Devuelve lista vacía (la asignación puede comenzar desde cero) |\n| Directorio de artefactos inexistente (`artifact://` / `agent://`) | Lanza explícitamente `No artifacts directory found` |\n| ID de artefacto no encontrado | Lanza con listado de IDs disponibles |\n| Fallo en la inicialización del escritor de artefactos de OutputSink | Continúa con truncado solo de cola (sin artefacto de salida completa) |\n| Sin archivo de sesión (algunas rutas de tareas) | La herramienta Task recurre a un directorio temporal de artefactos para las salidas de subagentes |\n\n## Externalización de blobs binarios vs artefactos de salida de texto\n\n- La **externalización de blobs** es para cargas útiles de imágenes binarias dentro del contenido de entradas de sesión persistidas; reemplaza el base64 en línea en el JSONL con referencias de contenido estables.\n- Los **artefactos** son archivos de texto plano para salidas de ejecución y salidas de subagentes; son direccionables mediante IDs locales de sesión a través de URLs internas.\n\nLos dos sistemas se intersectan solo indirectamente (ambos reducen la inflación del JSONL de sesión) pero tienen diferentes identidades, ciclos de vida y rutas de recuperación.\n\n## Archivos de implementación\n\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts) — formato de referencia de blobs, hashing, put/get, helpers de externalización/resolución.\n- [`src/session/artifacts.ts`](../../packages/coding-agent/src/session/artifacts.ts) — modelo de directorio de artefactos de sesión y asignación numérica de IDs de artefactos.\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — comportamiento de truncado/volcado a archivo de `OutputSink` y metadatos de resumen.\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts) — transformaciones de persistencia, rehidratación de blobs en la carga, interacciones de bifurcación/movimiento de sesión.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — copia del directorio de artefactos durante la bifurcación interactiva.\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — bootstrap del gestor de artefactos de herramientas y asignación de rutas de artefactos por herramienta.\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — resolutor de `artifact://`.\n- [`src/internal-urls/agent-protocol.ts`](../../packages/coding-agent/src/internal-urls/agent-protocol.ts) — resolutor de `agent://` + extracción JSON.\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — cableado del enrutador de URLs internas y resolutor del directorio de artefactos.\n- [`src/task/output-manager.ts`](../../packages/coding-agent/src/task/output-manager.ts) — asignación de IDs de salida de agentes con alcance de sesión para `agent://`.\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — escrituras de artefactos de salida de subagentes (`<id>.md`) y respaldo con directorio temporal de artefactos.\n",
	"es/configuration/config-usage.md": "---\ntitle: Descubrimiento y resolución de configuración\ndescription: >-\n  Cómo xcsh descubre, resuelve y superpone la configuración desde las raíces de\n  proyecto, usuario y empresa.\nsidebar:\n  order: 1\n  label: Configuración\ni18n:\n  sourceHash: e38bd9792499\n  translator: machine\n---\n\n# Descubrimiento y resolución de configuración\n\nEste documento describe cómo el coding-agent resuelve la configuración actualmente: qué raíces se escanean, cómo funciona la precedencia y cómo la configuración resuelta es consumida por settings, skills, hooks, tools y extensions.\n\n## Alcance\n\nImplementación principal:\n\n- `src/config.ts`\n- `src/config/settings.ts`\n- `src/config/settings-schema.ts`\n- `src/discovery/builtin.ts`\n- `src/discovery/helpers.ts`\n\nPuntos de integración clave:\n\n- `src/capability/index.ts`\n- `src/discovery/index.ts`\n- `src/extensibility/skills.ts`\n- `src/extensibility/hooks/loader.ts`\n- `src/extensibility/custom-tools/loader.ts`\n- `src/extensibility/extensions/loader.ts`\n\n---\n\n## Flujo de resolución (visual)\n\n```text\n         Config roots (ordered)\n┌───────────────────────────────────────┐\n│ 1) ~/.xcsh/agent + <cwd>/.xcsh          │\n│ 2) ~/.claude   + <cwd>/.claude        │\n│ 3) ~/.codex    + <cwd>/.codex         │\n│ 4) ~/.gemini   + <cwd>/.gemini        │\n└───────────────────────────────────────┘\n                    │\n                    ▼\n        config.ts helper resolution\n  (getConfigDirs/findConfigFile/findNearest...)\n                    │\n                    ▼\n       capability providers enumerate items\n (native, claude, codex, gemini, agents, etc.)\n                    │\n                    ▼\n      priority sort + per-capability dedup\n                    │\n                    ▼\n          subsystem-specific consumption\n   (settings, skills, hooks, tools, extensions)\n```\n\n## 1) Raíces de configuración y orden de fuentes\n\n## Raíces canónicas\n\n`src/config.ts` define una lista fija de prioridad de fuentes:\n\n1. `.xcsh` (nativo)\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nBases a nivel de usuario:\n\n- `~/.xcsh/agent`\n- `~/.claude`\n- `~/.codex`\n- `~/.gemini`\n\nBases a nivel de proyecto:\n\n- `<cwd>/.xcsh`\n- `<cwd>/.claude`\n- `<cwd>/.codex`\n- `<cwd>/.gemini`\n\n`CONFIG_DIR_NAME` es `.xcsh` (`packages/utils/src/dirs.ts`).\n\n## Restricción importante\n\nLos helpers genéricos en `src/config.ts` **no** incluyen `.pi` en el orden de descubrimiento de fuentes.\n\n---\n\n## 2) Helpers principales de descubrimiento (`src/config.ts`)\n\n## `getConfigDirs(subpath, options)`\n\nDevuelve entradas ordenadas:\n\n- Primero las entradas a nivel de usuario (por prioridad de fuente)\n- Luego las entradas a nivel de proyecto (con la misma prioridad de fuente)\n\nOpciones:\n\n- `user` (por defecto `true`)\n- `project` (por defecto `true`)\n- `cwd` (por defecto `getProjectDir()`)\n- `existingOnly` (por defecto `false`)\n\nEsta API se utiliza para búsquedas de configuración basadas en directorios (commands, hooks, tools, agents, etc.).\n\n## `findConfigFile(subpath, options)` / `findConfigFileWithMeta(...)`\n\nBusca el primer archivo existente a través de las bases ordenadas, devuelve la primera coincidencia (solo ruta o ruta+metadatos).\n\n## `findAllNearestProjectConfigDirs(subpath, cwd)`\n\nRecorre los directorios padre hacia arriba y devuelve el **directorio existente más cercano por cada base de fuente** (`.xcsh`, `.claude`, `.codex`, `.gemini`), luego ordena los resultados por prioridad de fuente.\n\nUtilice esto cuando la configuración del proyecto deba heredarse de directorios ancestros (comportamiento de monorepo/workspace anidado).\n\n---\n\n## 3) Wrapper de archivo de configuración (`ConfigFile<T>` en `src/config.ts`)\n\n`ConfigFile<T>` es el cargador con validación de esquema para archivos de configuración individuales.\n\nFormatos soportados:\n\n- `.yml` / `.yaml`\n- `.json` / `.jsonc`\n\nComportamiento:\n\n- Valida los datos parseados con AJV contra un esquema TypeBox proporcionado.\n- Almacena en caché el resultado de carga hasta que se llama a `invalidate()`.\n- Devuelve un resultado de tres estados mediante `tryLoad()`:\n  - `ok`\n  - `not-found`\n  - `error` (`ConfigError` con contexto de esquema/parseo)\n\nMigración legacy aún soportada:\n\n- Si la ruta objetivo es `.yml`/`.yaml`, un archivo hermano `.json` se migra automáticamente una vez (`migrateJsonToYml`).\n\n---\n\n## 4) Modelo de resolución de settings (`src/config/settings.ts`)\n\nEl modelo de settings en tiempo de ejecución está organizado por capas:\n\n1. Settings globales: `~/.xcsh/agent/config.yml`\n2. Settings de proyecto: descubiertos mediante la capability de settings (`settings.json` desde los providers)\n3. Overrides en tiempo de ejecución: en memoria, no persistentes\n4. Valores por defecto del esquema: desde `SETTINGS_SCHEMA`\n\nRuta de lectura efectiva:\n\n`defaults <- global <- project <- overrides`\n\nComportamiento de escritura:\n\n- `settings.set(...)` escribe en la capa **global** (`config.yml`) y encola un guardado en segundo plano.\n- Los settings de proyecto son de solo lectura desde el descubrimiento de capabilities.\n\n## Comportamiento de migración aún activo\n\nAl iniciar, si `config.yml` no existe:\n\n1. Migrar desde `~/.xcsh/agent/settings.json` (renombrado a `.bak` en caso de éxito)\n2. Combinar con settings legacy de la base de datos desde `agent.db`\n3. Escribir el resultado combinado en `config.yml`\n\nMigraciones a nivel de campo en `#migrateRawSettings`:\n\n- `queueMode` -> `steeringMode`\n- `ask.timeout` milisegundos -> segundos cuando el valor antiguo parece estar en ms (`> 1000`)\n- Legacy plano `theme: \"...\"` -> estructura `theme.dark/theme.light`\n\n---\n\n## 5) Integración con capability/discovery\n\nLa mayoría de los flujos de carga de configuración no esenciales pasan a través del registro de capabilities (`src/capability/index.ts` + `src/discovery/index.ts`).\n\n## Ordenamiento de providers\n\nLos providers se ordenan por prioridad numérica (mayor primero). Ejemplos de prioridades:\n\n- Native OMP (`builtin.ts`): `100`\n- Claude: `80`\n- Codex / agents / Claude marketplace: `70`\n- Gemini: `60`\n\n```text\nProvider precedence (higher wins)\n\nnative (.xcsh)          priority 100\nclaude                 priority  80\ncodex / agents / ...   priority  70\ngemini                 priority  60\n```\n\n## Semántica de deduplicación\n\nLas capabilities definen una `key(item)`:\n\n- misma key => el primer elemento gana (elemento de mayor prioridad/cargado primero)\n- sin key (`undefined`) => sin deduplicación, todos los elementos se conservan\n\nKeys relevantes:\n\n- skills: `name`\n- tools: `name`\n- hooks: `${type}:${tool}:${name}`\n- extension modules: `name`\n- extensions: `name`\n- settings: sin deduplicación (todos los elementos se conservan)\n\n---\n\n## 6) Comportamiento del provider nativo `.xcsh` (`src/discovery/builtin.ts`)\n\nEl provider nativo (`id: native`) lee desde:\n\n- proyecto: `<cwd>/.xcsh/...`\n- usuario: `~/.xcsh/agent/...`\n\n### Regla de admisión de directorios\n\n`builtin.ts` solo incluye una raíz de configuración si el directorio existe **y no está vacío** (`ifNonEmptyDir`).\n\n### Carga específica por alcance\n\n- Skills: `skills/*/SKILL.md`\n- Slash commands: `commands/*.md`\n- Rules: `rules/*.{md,mdc}`\n- Prompts: `prompts/*.md`\n- Instructions: `instructions/*.md`\n- Hooks: `hooks/pre/*`, `hooks/post/*`\n- Tools: `tools/*.json|*.md` y `tools/<name>/index.ts`\n- Extension modules: descubiertos bajo `extensions/` (+ array de strings legacy `settings.json.extensions`)\n- Extensions: `extensions/<name>/gemini-extension.json`\n- Settings capability: `settings.json`\n\n### Matiz de búsqueda de proyecto más cercano\n\nPara `SYSTEM.md` y `XCSH.md`, el provider nativo utiliza la búsqueda del directorio `.xcsh` de proyecto ancestro más cercano (recorrido ascendente), pero aún requiere que el directorio `.xcsh` no esté vacío.\n\n---\n\n## 7) Cómo los subsistemas principales consumen la configuración\n\n## Subsistema de settings\n\n- `Settings.init()` carga el `config.yml` global + los elementos de capability de proyecto `settings.json` descubiertos.\n- Solo los elementos de capability con `level === \"project\"` se combinan en la capa de proyecto.\n\n## Subsistema de skills\n\n- `extensibility/skills.ts` carga mediante `loadCapability(skillCapability.id, { cwd })`.\n- Aplica toggles de fuente y filtros (`ignoredSkills`, `includeSkills`, directorios personalizados).\n- Los toggles con nombres legacy aún existen (`skills.enablePiUser`, `skills.enablePiProject`) pero controlan el provider nativo (`provider === \"native\"`).\n\n## Subsistema de hooks\n\n- `discoverAndLoadHooks()` resuelve las rutas de hooks desde la capability de hooks + rutas configuradas explícitamente.\n- Luego carga los módulos mediante importación de Bun.\n\n## Subsistema de tools\n\n- `discoverAndLoadCustomTools()` resuelve las rutas de tools desde la capability de tools + rutas de tools de plugins + rutas configuradas explícitamente.\n- Los archivos de tools declarativos `.md/.json` son solo metadatos; la carga ejecutable espera módulos de código.\n\n## Subsistema de extensions\n\n- `discoverAndLoadExtensions()` resuelve los módulos de extensión desde la capability de extension-module más rutas explícitas.\n- La implementación actual mantiene intencionalmente solo los elementos de capability con `_source.provider === \"native\"` antes de cargar.\n\n---\n\n## 8) Reglas de precedencia en las que confiar\n\nUtilice este modelo mental:\n\n1. El ordenamiento de directorios fuente de `config.ts` determina el orden de rutas candidatas.\n2. La prioridad del provider de capability determina la precedencia entre providers.\n3. La deduplicación por key de capability determina el comportamiento en colisiones (el primero gana para capabilities con key).\n4. La lógica de combinación específica del subsistema puede cambiar aún más la precedencia efectiva (especialmente en settings).\n\n### Advertencia específica de settings\n\nLos elementos de capability de settings no se deduplican; `Settings.#loadProjectSettings()` realiza un deep-merge de los elementos de proyecto en el orden devuelto. Dado que el merge aplica los valores del último elemento sobre los anteriores, el comportamiento efectivo de sobreescritura depende del orden de emisión del provider, no solo de la semántica de key de capability.\n\n---\n\n## 9) Comportamientos legacy/de compatibilidad aún presentes\n\n- Migración de JSON -> YAML en `ConfigFile` para archivos dirigidos a YAML.\n- Migración de settings desde `settings.json` y `agent.db` hacia `config.yml`.\n- Migraciones de keys de settings (`queueMode`, `ask.timeout`, `theme` plano).\n- Compatibilidad de manifiesto de extensiones: el cargador acepta tanto las secciones de manifiesto `package.json.xcsh` como `package.json.pi`.\n- Los nombres legacy de settings `skills.enablePiUser` / `skills.enablePiProject` siguen siendo controles activos para la fuente de skills nativa.\n\nSi estas rutas de compatibilidad se eliminan del código, actualice este documento inmediatamente; varios comportamientos en tiempo de ejecución aún dependen de ellas actualmente.\n",
	"es/configuration/environment-variables.md": "---\ntitle: Variables de Entorno\ndescription: >-\n  Referencia de variables de entorno en tiempo de ejecución para la\n  configuración y control del comportamiento de xcsh.\nsidebar:\n  order: 2\n  label: Variables de entorno\ni18n:\n  sourceHash: e2890f963c02\n  translator: machine\n---\n\n# Variables de Entorno (Referencia Actual en Tiempo de Ejecución)\n\nEsta referencia se deriva de las rutas de código actuales en:\n\n- `packages/coding-agent/src/**`\n- `packages/ai/src/**` (resolución de proveedor/autenticación utilizada por coding-agent)\n- `packages/utils/src/**` y `packages/tui/src/**` donde esas variables afectan directamente el tiempo de ejecución de coding-agent\n\nDocumenta únicamente el comportamiento activo.\n\n## Modelo de resolución y precedencia\n\nLa mayoría de las búsquedas en tiempo de ejecución utilizan `$env` de `@f5-sales-demo/pi-utils` (`packages/utils/src/env.ts`).\n\nOrden de carga de `$env`:\n\n1. Entorno de proceso existente (`Bun.env`)\n2. `.env` del proyecto (`$PWD/.env`) para claves no establecidas previamente\n3. `.env` del directorio home (`~/.env`) para claves no establecidas previamente\n\nRegla adicional en archivos `.env`: las claves `XCSH_*` se reflejan como claves `PI_*` durante el análisis.\n\n---\n\n## 1) Autenticación de modelo/proveedor\n\nEstas se consumen a través de `getEnvApiKey()` (`packages/ai/src/stream.ts`) a menos que se indique lo contrario.\n\n### Credenciales principales del proveedor\n\n| Variable                        | Uso | Requerida cuando                                                 | Notas / precedencia                                                                                  |\n|---------------------------------|---|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|\n| `ANTHROPIC_OAUTH_TOKEN`         | Autenticación API de Anthropic | Se usa Anthropic con autenticación por token OAuth                         | Tiene precedencia sobre `ANTHROPIC_API_KEY` para la resolución de autenticación del proveedor                              |\n| `ANTHROPIC_API_KEY`             | Autenticación API de Anthropic | Se usa Anthropic sin token OAuth                           | Respaldo después de `ANTHROPIC_OAUTH_TOKEN`                                                              |\n| `ANTHROPIC_FOUNDRY_API_KEY`     | Anthropic vía Azure Foundry / gateway empresarial | `CLAUDE_CODE_USE_FOUNDRY` habilitado                             | Tiene precedencia sobre `ANTHROPIC_OAUTH_TOKEN` y `ANTHROPIC_API_KEY` cuando el modo Foundry está habilitado  |\n| `OPENAI_API_KEY`                | Autenticación de OpenAI | Se usan proveedores de la familia OpenAI sin argumento apiKey explícito | Usado por los proveedores OpenAI Completions/Responses                                                      |\n| `GEMINI_API_KEY`                | Autenticación de Google Gemini | Se usan modelos del proveedor `google`                                | Clave principal para el mapeo del proveedor Gemini                                                             |\n| `GOOGLE_API_KEY`                | Respaldo de autenticación para herramienta de imagen Gemini | Se usa la herramienta `gemini_image` sin `GEMINI_API_KEY`            | Usado por la ruta de respaldo de la herramienta de imagen de coding-agent                                                       |\n| `GROQ_API_KEY`                  | Autenticación de Groq | Se usan modelos de Groq                                             |                                                                                                     |\n| `CEREBRAS_API_KEY`              | Autenticación de Cerebras | Se usan modelos de Cerebras                                         |                                                                                                     |\n| `TOGETHER_API_KEY`              | Autenticación de Together | Se usa el proveedor `together`                                     |                                                                                                     |\n| `HUGGINGFACE_HUB_TOKEN`         | Autenticación de Hugging Face | Se usa el proveedor `huggingface`                                  | Variable de entorno principal del token de Hugging Face                                                                  |\n| `HF_TOKEN`                      | Autenticación de Hugging Face | Se usa el proveedor `huggingface`                                  | Respaldo cuando `HUGGINGFACE_HUB_TOKEN` no está establecido                                                      |\n| `SYNTHETIC_API_KEY`             | Autenticación de Synthetic | Se usan modelos de Synthetic                                        |                                                                                                     |\n| `NVIDIA_API_KEY`                | Autenticación de NVIDIA | Se usa el proveedor `nvidia`                                       |                                                                                                     |\n| `NANO_GPT_API_KEY`              | Autenticación de NanoGPT | Se usa el proveedor `nanogpt`                                      |                                                                                                     |\n| `VENICE_API_KEY`                | Autenticación de Venice | Se usa el proveedor `venice`                                       |                                                                                                     |\n| `LITELLM_API_KEY`               | Autenticación de LiteLLM | Se usa el proveedor `litellm`                                      | Clave de proxy LiteLLM compatible con OpenAI. Cuando se establece con `LITELLM_BASE_URL`, habilita la autoconfiguración de `models.yml` |\n| `LM_STUDIO_API_KEY`             | Autenticación de LM Studio (opcional) | Se usa el proveedor `lm-studio` con hosts autenticados           | LM Studio local generalmente se ejecuta sin autenticación; cualquier token no vacío funciona cuando se requiere una clave         |\n| `OLLAMA_API_KEY`                | Autenticación de Ollama (opcional) | Se usa el proveedor `ollama` con hosts autenticados              | Ollama local generalmente se ejecuta sin autenticación; cualquier token no vacío funciona cuando se requiere una clave            |\n| `LLAMA_CPP_API_KEY`             | Autenticación de Ollama (opcional) | Se usa `llama-server` con el parámetro `--api-key`              | llama.cpp local generalmente se ejecuta sin autenticación; cualquier token no vacío funciona cuando se configura una clave       |\n| `XIAOMI_API_KEY`                | Autenticación de Xiaomi MiMo | Se usa el proveedor `xiaomi`                                       |                                                                                                     |\n| `MOONSHOT_API_KEY`              | Autenticación de Moonshot | Se usa el proveedor `moonshot`                                     |                                                                                                     |\n| `XAI_API_KEY`                   | Autenticación de xAI | Se usan modelos de xAI                                              |                                                                                                     |\n| `OPENROUTER_API_KEY`            | Autenticación de OpenRouter | Se usan modelos de OpenRouter                                       | También usado por la herramienta de imagen cuando el proveedor preferido/automático es OpenRouter                                  |\n| `MISTRAL_API_KEY`               | Autenticación de Mistral | Se usan modelos de Mistral                                          |                                                                                                     |\n| `ZAI_API_KEY`                   | Autenticación de z.ai | Se usan modelos de z.ai                                             | También usado por el proveedor de búsqueda web de z.ai                                                               |\n| `MINIMAX_API_KEY`               | Autenticación de MiniMax | Se usa el proveedor `minimax`                                      |                                                                                                     |\n| `MINIMAX_CODE_API_KEY`          | Autenticación de MiniMax Code | Se usa el proveedor `minimax-code`                                 |                                                                                                     |\n| `MINIMAX_CODE_CN_API_KEY`       | Autenticación de MiniMax Code CN | Se usa el proveedor `minimax-code-cn`                              |                                                                                                     |\n| `OPENCODE_API_KEY`              | Autenticación de OpenCode | Se usan modelos de OpenCode                                         |                                                                                                     |\n| `QIANFAN_API_KEY`               | Autenticación de Qianfan | Se usa el proveedor `qianfan`                                      |                                                                                                     |\n| `QWEN_OAUTH_TOKEN`              | Autenticación de Qwen Portal | Se usa `qwen-portal` con token OAuth                          | Tiene precedencia sobre `QWEN_PORTAL_API_KEY`                                                         |\n| `QWEN_PORTAL_API_KEY`           | Autenticación de Qwen Portal | Se usa `qwen-portal` con clave API                              | Respaldo después de `QWEN_OAUTH_TOKEN`                                                                   |\n| `ZENMUX_API_KEY`                | Autenticación de ZenMux | Se usa el proveedor `zenmux`                                       | Usado para las rutas compatibles con OpenAI y Anthropic de ZenMux                                              |\n| `VLLM_API_KEY`                  | Autenticación/descubrimiento opt-in de vLLM | Se usa el proveedor `vllm` (servidores locales compatibles con OpenAI)       | Cualquier valor no vacío funciona para servidores locales sin autenticación                                                 |\n| `CURSOR_ACCESS_TOKEN`           | Autenticación del proveedor Cursor | Se usa el proveedor Cursor                                         |                                                                                                     |\n| `AI_GATEWAY_API_KEY`            | Autenticación de Vercel AI Gateway | Se usa el proveedor `vercel-ai-gateway`                            |                                                                                                     |\n| `CLOUDFLARE_AI_GATEWAY_API_KEY` | Autenticación de Cloudflare AI Gateway | Se usa el proveedor `cloudflare-ai-gateway`                        | La URL base debe configurarse como `https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic` |\n\n### Cadenas de tokens de GitHub/Copilot\n\n| Variable | Uso | Cadena |\n|---|---|---|\n| `COPILOT_GITHUB_TOKEN` | Autenticación del proveedor GitHub Copilot | `COPILOT_GITHUB_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN` |\n| `GH_TOKEN` | Respaldo de Copilot; autenticación API de GitHub en web scraper | En web scraper: `GITHUB_TOKEN` → `GH_TOKEN` |\n| `GITHUB_TOKEN` | Respaldo de Copilot; autenticación API de GitHub en web scraper | En web scraper: se verifica antes de `GH_TOKEN` |\n\n---\n\n## 2) Configuración en tiempo de ejecución específica del proveedor\n\n### Anthropic Foundry Gateway (Azure / proxy empresarial)\n\nCuando `CLAUDE_CODE_USE_FOUNDRY` está habilitado, las solicitudes a Anthropic cambian al modo Foundry:\n\n- La URL base se resuelve desde `FOUNDRY_BASE_URL` (el respaldo permanece como la URL base del modelo/predeterminada si no está establecida).\n- La resolución de clave API para el proveedor `anthropic` se convierte en:\n  `ANTHROPIC_FOUNDRY_API_KEY` → `ANTHROPIC_OAUTH_TOKEN` → `ANTHROPIC_API_KEY`.\n- `ANTHROPIC_CUSTOM_HEADERS` se analiza como pares `clave: valor` separados por comas/saltos de línea y se fusionan en las cabeceras de la solicitud.\n- El material TLS de cliente/servidor puede inyectarse desde valores de entorno:\n  `NODE_EXTRA_CA_CERTS`, `CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`.\n  Cada uno acepta:\n  - una ruta del sistema de archivos al contenido PEM, o\n  - PEM en línea (incluyendo secuencias `\\n` escapadas).\n\n| Variable | Tipo de valor | Comportamiento |\n|---|---|---|\n| `CLAUDE_CODE_USE_FOUNDRY` | Cadena tipo booleano (`1`, `true`, `yes`, `on`) | Habilita el modo Foundry para el proveedor Anthropic |\n| `FOUNDRY_BASE_URL` | Cadena URL | URL base del endpoint de Anthropic en modo Foundry |\n| `ANTHROPIC_FOUNDRY_API_KEY` | Cadena de token | Usado para `Authorization: Bearer <token>` |\n| `ANTHROPIC_CUSTOM_HEADERS` | Cadena de lista de cabeceras | Cabeceras adicionales; formato `header-a: valor, header-b: valor` o separadas por saltos de línea |\n| `NODE_EXTRA_CA_CERTS` | Ruta PEM o PEM en línea | Cadena CA adicional para validación de certificado del servidor |\n| `CLAUDE_CODE_CLIENT_CERT` | Ruta PEM o PEM en línea | Certificado de cliente mTLS |\n| `CLAUDE_CODE_CLIENT_KEY` | Ruta PEM o PEM en línea | Clave privada del cliente mTLS (debe emparejarse con el certificado) |\n\n### Amazon Bedrock\n\n| Variable | Predeterminado / comportamiento |\n|---|---|\n| `AWS_REGION` | Fuente principal de región |\n| `AWS_DEFAULT_REGION` | Respaldo si `AWS_REGION` no está establecida |\n| `AWS_PROFILE` | Habilita la ruta de autenticación por perfil con nombre |\n| `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` | Habilita la ruta de autenticación por clave IAM |\n| `AWS_BEARER_TOKEN_BEDROCK` | Habilita la ruta de autenticación por token bearer |\n| `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` / `AWS_CONTAINER_CREDENTIALS_FULL_URI` | Habilita la ruta de credenciales de tarea ECS |\n| `AWS_WEB_IDENTITY_TOKEN_FILE` + `AWS_ROLE_ARN` | Habilita la ruta de autenticación por identidad web |\n| `AWS_BEDROCK_SKIP_AUTH` | Si es `1`, inyecta credenciales ficticias (escenarios de proxy/sin autenticación) |\n| `AWS_BEDROCK_FORCE_HTTP1` | Si es `1`, fuerza el manejador de solicitudes HTTP/1 de Node |\n\nRespaldo de región en el código del proveedor: `options.region` → `AWS_REGION` → `AWS_DEFAULT_REGION` → `us-east-1`.\n\n### Azure OpenAI Responses\n\n| Variable | Predeterminado / comportamiento |\n|---|---|\n| `AZURE_OPENAI_API_KEY` | Requerida a menos que se pase la clave API como opción |\n| `AZURE_OPENAI_API_VERSION` | Predeterminado `v1` |\n| `AZURE_OPENAI_BASE_URL` | Sobrescritura directa de URL base |\n| `AZURE_OPENAI_RESOURCE_NAME` | Usado para construir la URL base: `https://<resource>.openai.azure.com/openai/v1` |\n| `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` | Cadena de mapeo opcional: `modelId=deploymentName,model2=deployment2` |\n\nResolución de URL base: opción `azureBaseUrl` → env `AZURE_OPENAI_BASE_URL` → opción/env resource name → `model.baseUrl`.\n\n### Google Vertex AI\n\n| Variable | ¿Requerida? | Notas |\n|---|---|---|\n| `GOOGLE_CLOUD_PROJECT` | Sí (a menos que se pase en opciones) | Respaldo: `GCLOUD_PROJECT` |\n| `GCLOUD_PROJECT` | Respaldo | Usado como fuente alternativa de ID de proyecto |\n| `GOOGLE_CLOUD_LOCATION` | Sí (a menos que se pase en opciones) | Sin valor predeterminado en el proveedor |\n| `GOOGLE_APPLICATION_CREDENTIALS` | Condicional | Si se establece, el archivo debe existir; de lo contrario, se verifica la ruta de respaldo ADC (`~/.config/gcloud/application_default_credentials.json`) |\n\n### Kimi\n\n| Variable | Predeterminado / comportamiento |\n|---|---|\n| `KIMI_CODE_OAUTH_HOST` | Sobrescritura principal del host OAuth |\n| `KIMI_OAUTH_HOST` | Sobrescritura de respaldo del host OAuth |\n| `KIMI_CODE_BASE_URL` | Sobrescribe la URL base del endpoint de uso de Kimi (`usage/kimi.ts`) |\n\nCadena de host OAuth: `KIMI_CODE_OAUTH_HOST` → `KIMI_OAUTH_HOST` → `https://auth.kimi.com`.\n\n### Compatibilidad Antigravity/Gemini image\n\n| Variable | Predeterminado / comportamiento |\n|---|---|\n| `PI_AI_ANTIGRAVITY_VERSION` | Sobrescribe la etiqueta de versión del user-agent de Antigravity en el proveedor Gemini CLI |\n\n### OpenAI Codex responses (controles de funcionalidad/depuración)\n\n| Variable | Comportamiento |\n|---|---|\n| `PI_CODEX_DEBUG` | `1`/`true` habilita el registro de depuración del proveedor Codex |\n| `PI_CODEX_WEBSOCKET` | `1`/`true` habilita la preferencia de transporte websocket |\n| `PI_CODEX_WEBSOCKET_V2` | `1`/`true` habilita la ruta websocket v2 |\n| `PI_CODEX_WEBSOCKET_IDLE_TIMEOUT_MS` | Sobrescritura de entero positivo (predeterminado 300000) |\n| `PI_CODEX_WEBSOCKET_RETRY_BUDGET` | Sobrescritura de entero no negativo (predeterminado 5) |\n| `PI_CODEX_WEBSOCKET_RETRY_DELAY_MS` | Sobrescritura de retroceso base de entero positivo (predeterminado 500) |\n\n### Depuración del proveedor Cursor\n\n| Variable | Comportamiento |\n|---|---|\n| `DEBUG_CURSOR` | Habilita registros de depuración del proveedor; `2`/`verbose` para fragmentos detallados del payload |\n| `DEBUG_CURSOR_LOG` | Ruta de archivo opcional para salida de registro de depuración JSONL |\n\n### Interruptor de compatibilidad de caché de prompts\n\n| Variable | Comportamiento |\n|---|---|\n| `PI_CACHE_RETENTION` | Si es `long`, habilita retención larga donde sea compatible (`anthropic`, `openai-responses`, resolución de retención de Bedrock) |\n\n---\n\n## 3) Subsistema de búsqueda web\n\n### Credenciales del proveedor de búsqueda\n\n| Variable | Usado por |\n|---|---|\n| `EXA_API_KEY` | Proveedor de búsqueda Exa y herramientas MCP de Exa |\n| `BRAVE_API_KEY` | Proveedor de búsqueda Brave |\n| `PERPLEXITY_API_KEY` | Proveedor de búsqueda Perplexity en modo clave API |\n| `TAVILY_API_KEY` | Proveedor de búsqueda Tavily |\n| `ZAI_API_KEY` | Proveedor de búsqueda z.ai (también verifica OAuth almacenado en `agent.db`) |\n| `OPENAI_API_KEY` / OAuth de Codex en BD | Disponibilidad/autenticación del proveedor de búsqueda Codex |\n\n### Cadena de autenticación de búsqueda web de Anthropic\n\n`packages/coding-agent/src/web/search/auth.ts` resuelve las credenciales de búsqueda web de Anthropic en este orden:\n\n1. `ANTHROPIC_SEARCH_API_KEY` (+ `ANTHROPIC_SEARCH_BASE_URL` opcional)\n2. Entrada del proveedor en `models.json` con `api: \"anthropic-messages\"`\n3. Credenciales OAuth de Anthropic desde `agent.db` (no deben expirar dentro de un búfer de 5 minutos)\n4. Respaldo genérico de env de Anthropic: clave del proveedor (`ANTHROPIC_FOUNDRY_API_KEY`/`ANTHROPIC_OAUTH_TOKEN`/`ANTHROPIC_API_KEY`) + `ANTHROPIC_BASE_URL` opcional (`FOUNDRY_BASE_URL` cuando el modo Foundry está habilitado)\n\nVariables relacionadas:\n\n| Variable | Predeterminado / comportamiento |\n|---|---|\n| `ANTHROPIC_SEARCH_API_KEY` | Clave de búsqueda explícita de mayor prioridad |\n| `ANTHROPIC_SEARCH_BASE_URL` | Predeterminado `https://api.anthropic.com` cuando se omite |\n| `ANTHROPIC_SEARCH_MODEL` | Predeterminado `claude-haiku-4-5` |\n| `ANTHROPIC_BASE_URL` | URL base de respaldo genérica para la ruta de autenticación de nivel 4 |\n\n### Indicador de comportamiento del flujo OAuth de Perplexity\n\n| Variable | Comportamiento |\n|---|---|\n| `PI_AUTH_NO_BORROW` | Si se establece, deshabilita la ruta de préstamo de token de aplicación nativa de macOS en el flujo de inicio de sesión de Perplexity |\n\n---\n\n## 4) Herramientas de Python y tiempo de ejecución del kernel\n\n| Variable | Predeterminado / comportamiento |\n|---|---|\n| `PI_PY` | Sobrescritura del modo de herramienta Python: `0`/`bash`=`bash-only`, `1`/`py`=`ipy-only`, `mix`/`both`=`both`; los valores inválidos se ignoran |\n| `PI_PYTHON_SKIP_CHECK` | Si es `1`, omite las verificaciones de disponibilidad/calentamiento del kernel Python |\n| `PI_PYTHON_GATEWAY_URL` | Si se establece, usa un gateway de kernel externo en lugar del gateway compartido local |\n| `PI_PYTHON_GATEWAY_TOKEN` | Token de autenticación opcional para el gateway externo (`Authorization: token <value>`) |\n| `PI_PYTHON_IPC_TRACE` | Si es `1`, habilita la ruta de traza IPC de bajo nivel en el módulo del kernel |\n| `VIRTUAL_ENV` | Ruta de venv de mayor prioridad para la resolución del tiempo de ejecución de Python |\n\nComportamiento condicional adicional:\n\n- Si `BUN_ENV=test` o `NODE_ENV=test`, las verificaciones de disponibilidad de Python se tratan como correctas y se omite el calentamiento.\n- El filtrado de entorno de Python deniega claves API comunes y permite variables base seguras + prefijos `LC_`, `XDG_`, `PI_`.\n\n---\n\n## 5) Interruptores de comportamiento del agente/tiempo de ejecución\n\n| Variable                   | Predeterminado / comportamiento                                                                           |\n|----------------------------|----------------------------------------------------------------------------------------------|\n| `PI_SMOL_MODEL`            | Sobrescritura efímera de rol de modelo para `smol` (CLI `--smol` tiene precedencia)                     |\n| `PI_SLOW_MODEL`            | Sobrescritura efímera de rol de modelo para `slow` (CLI `--slow` tiene precedencia)                     |\n| `PI_PLAN_MODEL`            | Sobrescritura efímera de rol de modelo para `plan` (CLI `--plan` tiene precedencia)                     |\n| `PI_NO_TITLE`              | Si se establece (cualquier valor no vacío), deshabilita la generación automática de título de sesión en el primer mensaje del usuario   |\n| `NULL_PROMPT`              | Si es `true`, el constructor de prompt del sistema devuelve una cadena vacía                                        |\n| `PI_BLOCKED_AGENT`         | Bloquea un tipo específico de subagente en la herramienta de tareas                                                 |\n| `PI_SUBPROCESS_CMD`        | Sobrescribe el comando de generación de subagente (omisión de resolución `xcsh` / `xcsh.cmd`)                       |\n| `PI_TASK_MAX_OUTPUT_BYTES` | Máximo de bytes de salida capturados por subagente (predeterminado `500000`)                                    |\n| `PI_TASK_MAX_OUTPUT_LINES` | Máximo de líneas de salida capturadas por subagente (predeterminado `5000`)                                      |\n| `PI_TIMING`                | Si es `1`, habilita registros de instrumentación de temporización de inicio/herramientas                                     |\n| `PI_DEBUG_STARTUP`         | Habilita impresiones de depuración de la etapa de inicio en stderr en múltiples rutas de inicio                       |\n| `PI_PACKAGE_DIR`           | Sobrescribe la resolución del directorio base de activos del paquete (búsqueda de rutas de docs/examples/changelog)            |\n| `PI_DISABLE_LSPMUX`        | Si es `1`, deshabilita la detección/integración de lspmux y fuerza la generación directa del servidor LSP          |\n| `LITELLM_BASE_URL`         | URL base del proxy LiteLLM. Cuando se establece con `LITELLM_API_KEY`, activa la autogeneración de `models.yml` en la primera ejecución y la autocorrección en cada inicio |\n| `LM_STUDIO_BASE_URL`       | Sobrescritura de la URL base de descubrimiento implícito predeterminada de LM Studio (`http://127.0.0.1:1234/v1` si no se establece) |\n| `OLLAMA_BASE_URL`          | Sobrescritura de la URL base de descubrimiento implícito predeterminada de Ollama (`http://127.0.0.1:11434` si no se establece)      |\n| `LLAMA_CPP_BASE_URL`       | Sobrescritura de la URL base de descubrimiento implícito predeterminada de Llama.cpp (`http://127.0.0.1:8080` si no se establece)    |\n| `PI_EDIT_VARIANT`          | Si es `hashline`, fuerza el modo de visualización de lectura/grep hashline cuando la herramienta de edición está disponible               |\n| `PI_NO_PTY`                | Si es `1`, deshabilita la ruta PTY interactiva para la herramienta bash                                          |\n\n`PI_NO_PTY` también se establece internamente cuando se usa CLI `--no-pty`.\n\n---\n\n## 6) Rutas raíz de almacenamiento y configuración\n\nEstas se consumen a través de `@f5-sales-demo/pi-utils/dirs` y afectan dónde coding-agent almacena datos.\n\n| Variable | Predeterminado / comportamiento |\n|---|---|\n| `PI_CONFIG_DIR` | Nombre del directorio raíz de configuración bajo home (predeterminado `.xcsh`) |\n| `PI_CODING_AGENT_DIR` | Sobrescritura completa del directorio del agente (predeterminado `~/<PI_CONFIG_DIR o .xcsh>/agent`) |\n| `PWD` | Usado al coincidir con el directorio de trabajo actual canónico en los helpers de ruta |\n\n---\n\n## 7) Entorno de ejecución de shell/herramientas\n\n(De `packages/utils/src/procmgr.ts` e integración de la herramienta bash de coding-agent.)\n\n| Variable | Comportamiento |\n|---|---|\n| `PI_BASH_NO_CI` | Suprime la inyección automática de `CI=true` en el entorno del shell generado |\n| `CLAUDE_BASH_NO_CI` | Alias heredado de respaldo para `PI_BASH_NO_CI` |\n| `PI_BASH_NO_LOGIN` | Destinado a deshabilitar el modo de shell de inicio de sesión |\n| `CLAUDE_BASH_NO_LOGIN` | Alias heredado de respaldo para `PI_BASH_NO_LOGIN` |\n| `PI_SHELL_PREFIX` | Envoltorio de prefijo de comando opcional |\n| `CLAUDE_CODE_SHELL_PREFIX` | Alias heredado de respaldo para `PI_SHELL_PREFIX` |\n| `VISUAL` | Comando de editor externo preferido |\n| `EDITOR` | Comando de editor externo de respaldo |\n\nNota de implementación actual: `PI_BASH_NO_LOGIN`/`CLAUDE_BASH_NO_LOGIN` se leen, pero la implementación actual de `getShellArgs()` devuelve `['-l','-c']` en ambas ramas (efectivamente sin efecto hoy).\n\n---\n\n## 8) Detección de UI/tema/sesión (entorno autodetectado)\n\nEstas se leen como señales en tiempo de ejecución; generalmente son establecidas por el terminal/SO en lugar de configurarse manualmente.\n\n| Variable | Uso |\n|---|---|\n| `COLORTERM`, `TERM`, `WT_SESSION` | Detección de capacidad de color (modo de color del tema) |\n| `COLORFGBG` | Autodetección de fondo claro/oscuro del terminal |\n| `TERM_PROGRAM`, `TERM_PROGRAM_VERSION`, `TERMINAL_EMULATOR` | Identidad del terminal en el prompt del sistema/contexto |\n| `KDE_FULL_SESSION`, `XDG_CURRENT_DESKTOP`, `DESKTOP_SESSION`, `XDG_SESSION_DESKTOP`, `GDMSESSION`, `WINDOWMANAGER` | Detección de escritorio/gestor de ventanas en el prompt del sistema/contexto |\n| `KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION` | IDs de referencia de sesión estables por terminal |\n| `SHELL`, `ComSpec`, `TERM_PROGRAM`, `TERM` | Diagnósticos de información del sistema |\n| `APPDATA`, `XDG_CONFIG_HOME` | Resolución de ruta de configuración de lspmux |\n| `HOME` | Acortamiento de rutas en la UI de comandos MCP |\n\n---\n\n## 9) Indicadores de cargador nativo/depuración\n\n| Variable | Comportamiento |\n|---|---|\n| `PI_DEV` | Habilita diagnósticos detallados de carga de complementos nativos en `packages/natives` |\n\n## 10) Indicadores de tiempo de ejecución de TUI (paquete compartido, afecta la UX de coding-agent)\n\n| Variable | Comportamiento |\n|---|---|\n| `PI_NOTIFICATIONS` | `off` / `0` / `false` suprime las notificaciones de escritorio |\n| `PI_TUI_WRITE_LOG` | Si se establece, registra las escrituras de TUI en un archivo |\n| `PI_HARDWARE_CURSOR` | Si es `1`, habilita el modo de cursor por hardware |\n| `PI_CLEAR_ON_SHRINK` | Si es `1`, limpia las filas vacías cuando el contenido se reduce |\n| `PI_DEBUG_REDRAW` | Si es `1`, habilita el registro de depuración de redibujado |\n| `PI_TUI_DEBUG` | Si es `1`, habilita la ruta de volcado de depuración profunda de TUI |\n\n---\n\n## 11) Controles de generación de commits\n\n| Variable | Comportamiento |\n|---|---|\n| `PI_COMMIT_TEST_FALLBACK` | Si es `true` (insensible a mayúsculas), fuerza la ruta de generación de respaldo de commits |\n| `PI_COMMIT_NO_FALLBACK` | Si es `true`, deshabilita el respaldo cuando el agente no devuelve ninguna propuesta |\n| `PI_COMMIT_MAP_REDUCE` | Si es `false`, deshabilita la ruta de análisis map-reduce de commits |\n| `DEBUG` | Si se establece, se imprimen las trazas de pila de errores del agente de commits |\n\n---\n\n## Variables sensibles de seguridad\n\nTrate estas como secretos; no las registre ni las confirme en el control de versiones:\n\n- Claves API de proveedor y credenciales OAuth/bearer (todas las `*_API_KEY`, `*_TOKEN`, tokens de acceso/actualización OAuth)\n- Credenciales de nube (`AWS_*`, la ruta de `GOOGLE_APPLICATION_CREDENTIALS` puede exponer material de cuenta de servicio)\n- Variables de autenticación de búsqueda/proveedor (`EXA_API_KEY`, `BRAVE_API_KEY`, `PERPLEXITY_API_KEY`, claves de búsqueda de Anthropic)\n- Material mTLS de Foundry (`CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`, `NODE_EXTRA_CA_CERTS` cuando apunta a paquetes de CA privados)\n\nEl tiempo de ejecución de Python también elimina explícitamente muchas variables de clave comunes antes de generar subprocesos del kernel (`packages/coding-agent/src/ipy/runtime.ts`).\n",
	"es/configuration/fs-scan-cache-architecture.md": "---\ntitle: Arquitectura de la caché de escaneo del sistema de archivos\ndescription: >-\n  Contrato de la caché de escaneo del sistema de archivos para descubrimiento\n  rápido de archivos con semánticas de stale-while-revalidate.\nsidebar:\n  order: 8\n  label: Caché de escaneo del sistema de archivos\ni18n:\n  sourceHash: 2a2bde1726ac\n  translator: machine\n---\n\n# Contrato de arquitectura de la caché de escaneo del sistema de archivos\n\nEste documento define el contrato actual para la caché compartida de escaneo del sistema de archivos implementada en Rust (`crates/pi-natives/src/fs_cache.rs`) y consumida por las APIs nativas de descubrimiento/búsqueda expuestas a `packages/coding-agent`.\n\n## Qué es esta caché\n\nLa caché almacena listas completas de entradas de escaneo de directorios (`GlobMatch[]`) indexadas por alcance de escaneo y política de recorrido, y luego permite que las operaciones de nivel superior (filtrado glob, puntuación difusa, selección de archivos grep) se ejecuten contra esas entradas almacenadas en caché.\n\nObjetivos principales:\n\n- evitar recorridos repetidos del sistema de archivos para llamadas repetidas de descubrimiento/búsqueda\n- mantener consistencia entre `glob`, `fuzzyFind` y `grep` cuando comparten la misma política de escaneo\n- permitir recuperación explícita de obsolescencia para resultados vacíos e invalidación explícita después de mutaciones de archivos\n\n## Propiedad y superficie pública\n\n- Implementación de la caché y política: `crates/pi-natives/src/fs_cache.rs`\n- Consumidores nativos:\n  - `crates/pi-natives/src/glob.rs`\n  - `crates/pi-natives/src/fd.rs` (`fuzzyFind`)\n  - `crates/pi-natives/src/grep.rs`\n- Binding/exportación JS:\n  - `packages/natives/src/glob/index.ts` (`invalidateFsScanCache`)\n  - `packages/natives/src/glob/types.ts`\n  - `packages/natives/src/grep/types.ts`\n- Helpers de invalidación por mutación del coding-agent:\n  - `packages/coding-agent/src/tools/fs-cache-invalidation.ts`\n\n## Particionamiento de la clave de caché (contrato rígido)\n\nCada entrada está indexada por:\n\n- ruta del directorio `root` canonicalizada\n- booleano `include_hidden`\n- booleano `use_gitignore`\n\nImplicaciones:\n\n- Los escaneos con y sin archivos ocultos **no** comparten entradas.\n- Los escaneos que respetan gitignore y los que deshabilitan ignore **no** comparten entradas.\n- Los consumidores deben pasar semánticas estables para el comportamiento de ocultos/gitignore; cambiar cualquiera de las banderas crea una partición de caché diferente.\n\nLa inclusión de `node_modules` **no** está en la clave de caché. La caché almacena entradas con `node_modules` incluido; el filtrado por consumidor se aplica después de la recuperación.\n\n## Comportamiento de recolección del escaneo\n\nLa población de la caché utiliza un walker determinista (`ignore::WalkBuilder`) configurado por `include_hidden` y `use_gitignore`:\n\n- `follow_links(false)`\n- ordenado por ruta de archivo\n- `.git` siempre se omite\n- `node_modules` siempre se recolecta en el momento del escaneo de caché (y opcionalmente se filtra después)\n- el tipo de archivo de la entrada + `mtime` se capturan mediante `symlink_metadata`\n\nLas raíces de búsqueda se resuelven mediante `resolve_search_path`:\n\n- las rutas relativas se resuelven contra el cwd actual\n- el objetivo debe ser un directorio existente\n- la raíz se canonicaliza cuando es posible\n\n## Política de frescura y desalojo\n\nPolítica global (sobrescribible por variables de entorno):\n\n- `FS_SCAN_CACHE_TTL_MS` (por defecto `1000`)\n- `FS_SCAN_EMPTY_RECHECK_MS` (por defecto `200`)\n- `FS_SCAN_CACHE_MAX_ENTRIES` (por defecto `16`)\n\nComportamiento:\n\n- `get_or_scan(...)`\n  - si TTL es `0`: omitir la caché completamente, siempre escaneo fresco (`cache_age_ms = 0`)\n  - en acierto de caché dentro del TTL: devolver entradas en caché + `cache_age_ms` distinto de cero\n  - en acierto expirado: desalojar la clave, re-escanear, almacenar entrada fresca\n- la aplicación del máximo de entradas es desalojo del más antiguo primero por `created_at`\n\n## Re-verificación rápida de resultado vacío (separada de los aciertos normales)\n\nAcierto normal de caché:\n\n- un acierto de caché dentro del TTL devuelve las entradas en caché y no hace nada más.\n\nRe-verificación rápida de resultado vacío:\n\n- esta es una política **del lado del llamador** que utiliza `ScanResult.cache_age_ms`\n- si el resultado filtrado/de consulta está vacío y la antigüedad del escaneo en caché es al menos `empty_recheck_ms()`, el llamador realiza un `force_rescan(...)` y reintenta\n- diseñada para reducir resultados falsos negativos obsoletos cuando se agregaron archivos recientemente pero la caché aún está dentro del TTL\n\nConsumidores actuales:\n\n- `glob`: re-verifica cuando las coincidencias filtradas están vacías y la antigüedad del escaneo excede el umbral\n- `fuzzyFind` (`fd.rs`): re-verifica solo cuando la consulta no está vacía y las coincidencias puntuadas están vacías\n- `grep`: re-verifica cuando la lista de archivos candidatos seleccionados está vacía\n\n## Valores predeterminados del consumidor y uso de la caché\n\nLa caché es opt-in en todas las APIs expuestas (`cache?: boolean`, por defecto `false`).\n\nValores predeterminados actuales en las APIs nativas:\n\n- `glob`: `hidden=false`, `gitignore=true`, `cache=false`\n- `fuzzyFind`: `hidden=false`, `gitignore=true`, `cache=false`\n- `grep`: `hidden=true`, `cache=false`, y el escaneo de caché siempre usa `use_gitignore=true`\n\nLlamadores del coding-agent actualmente:\n\n- El descubrimiento de candidatos de mención de alto volumen habilita la caché:\n  - `packages/coding-agent/src/utils/file-mentions.ts`\n  - perfil: `hidden=true`, `gitignore=true`, `includeNodeModules=true`, `cache=true`\n- La integración de `grep` a nivel de herramienta actualmente deshabilita la caché de escaneo (`cache: false`):\n  - `packages/coding-agent/src/tools/grep.ts`\n\n## Contrato de invalidación\n\nPunto de entrada de invalidación nativo:\n\n- `invalidateFsScanCache(path?: string)`\n  - con `path`: eliminar entradas de caché cuya raíz sea un prefijo de la ruta objetivo\n  - sin path: limpiar todas las entradas de la caché de escaneo\n\nDetalles del manejo de rutas:\n\n- las rutas de invalidación relativas se resuelven contra el cwd\n- la invalidación intenta la canonicalización\n- si el objetivo no existe (p. ej., eliminación), el fallback canonicaliza el padre y readjunta el nombre de archivo cuando es posible\n- esto preserva el comportamiento de invalidación para crear/eliminar/renombrar donde un lado puede no existir\n\n## Responsabilidades del flujo de mutación del coding-agent\n\nEl código del coding-agent debe invalidar después de mutaciones exitosas del sistema de archivos.\n\nHelpers centrales:\n\n- `invalidateFsScanAfterWrite(path)`\n- `invalidateFsScanAfterDelete(path)`\n- `invalidateFsScanAfterRename(oldPath, newPath)` (invalida ambos lados cuando las rutas difieren)\n\nSitios de llamada actuales de herramientas de mutación:\n\n- `packages/coding-agent/src/tools/write.ts`\n- `packages/coding-agent/src/patch/index.ts` (flujos hashline/patch/replace)\n\nRegla: si un flujo muta el contenido o la ubicación del sistema de archivos y omite estos helpers, se esperan errores de obsolescencia de la caché.\n\n## Añadir un nuevo consumidor de caché de forma segura\n\nAl introducir el uso de la caché en un nuevo scanner/ruta de búsqueda:\n\n1. **Usar entradas estables de política de escaneo**\n   - decidir primero las semánticas de ocultos/gitignore\n   - pasarlas consistentemente a `get_or_scan`/`force_rescan` para que las particiones de caché sean intencionales\n\n2. **Tratar los datos de la caché como pre-filtrados solo por política de recorrido**\n   - aplicar el filtrado específico de la herramienta (patrones glob, filtros de tipo, reglas de node_modules) después de la recuperación\n   - nunca asumir que las entradas en caché ya reflejan sus filtros de nivel superior\n\n3. **Implementar la re-verificación rápida de resultado vacío solo para riesgo de falso negativo obsoleto**\n   - usar `scan.cache_age_ms >= empty_recheck_ms()`\n   - reintentar una vez con `force_rescan(..., store=true, ...)`\n   - mantener esta ruta separada de la lógica normal de acierto de caché\n\n4. **Respetar el modo sin caché explícitamente**\n   - cuando el llamador deshabilita la caché, llamar a `force_rescan(..., store=false, ...)`\n   - no poblar la caché compartida en una ruta de solicitud sin caché\n\n5. **Conectar la invalidación por mutación para cualquier nueva ruta de escritura**\n   - después de una escritura/edición/eliminación/renombrado exitoso, llamar al helper de invalidación del coding-agent\n   - para renombrar/mover, invalidar tanto las rutas antiguas como las nuevas\n\n6. **No añadir controles de TTL por llamada**\n   - el contrato actual es solo política global (configurada por variables de entorno), sin sobrescritura de TTL por solicitud\n\n## Límites conocidos\n\n- El alcance de la caché es local al proceso en memoria (`DashMap`), no se persiste entre reinicios del proceso.\n- La caché almacena entradas de escaneo, no resultados finales de herramientas.\n- `glob`/`fuzzyFind`/`grep` comparten entradas de escaneo solo cuando las dimensiones clave (`root`, `hidden`, `gitignore`) coinciden.\n- `.git` siempre se excluye en el momento de la recolección del escaneo independientemente de las opciones del llamador.\n",
	"es/configuration/hooks.md": "---\ntitle: Hooks\ndescription: >-\n  Sistema de hooks para la automatización de eventos previos/posteriores en el\n  ciclo de vida del agente de codificación.\nsidebar:\n  order: 4\n  label: Hooks\ni18n:\n  sourceHash: cdbec10bc405\n  translator: machine\n---\n\n# Hooks\n\nEste documento describe el **código actual del subsistema de hooks** en `src/extensibility/hooks/*`.\n\n## Estado actual en tiempo de ejecución\n\nEl paquete de hooks (`src/extensibility/hooks/`) sigue exportándose y siendo utilizable como superficie de API, pero el tiempo de ejecución predeterminado de la CLI ahora inicializa la ruta del **ejecutor de extensiones**. En el flujo de inicio actual:\n\n- `--hook` se trata como un alias de `--extension` (las rutas de CLI se fusionan en `additionalExtensionPaths`)\n- las herramientas están envueltas por `ExtensionToolWrapper`, no por `HookToolWrapper`\n- las transformaciones de contexto y las emisiones de ciclo de vida pasan por `ExtensionRunner`\n\nPor lo tanto, este archivo documenta la implementación del subsistema de hooks en sí (tipos/cargador/ejecutor/envoltorio), incluyendo el comportamiento heredado y las restricciones.\n\n## Archivos clave\n\n- `src/extensibility/hooks/types.ts` — contexto de hook, tipos de evento y contratos de resultado\n- `src/extensibility/hooks/loader.ts` — carga de módulos y puente de descubrimiento de hooks\n- `src/extensibility/hooks/runner.ts` — despacho de eventos, búsqueda de comandos y señalización de errores\n- `src/extensibility/hooks/tool-wrapper.ts` — envoltorio de intercepción previo/posterior de herramientas\n- `src/extensibility/hooks/index.ts` — exportaciones/reexportaciones\n\n## Qué es un módulo hook\n\nUn módulo hook debe exportar por defecto una fábrica:\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function hook(pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName === \"bash\" && String(event.input.command ?? \"\").includes(\"rm -rf\")) {\n   return { block: true, reason: \"blocked by policy\" };\n  }\n });\n}\n```\n\nLa fábrica puede:\n\n- registrar manejadores de eventos con `pi.on(...)`\n- enviar mensajes personalizados persistentes con `pi.sendMessage(...)`\n- persistir estado no-LLM con `pi.appendEntry(...)`\n- registrar comandos slash mediante `pi.registerCommand(...)`\n- registrar renderizadores de mensajes personalizados mediante `pi.registerMessageRenderer(...)`\n- ejecutar comandos de shell mediante `pi.exec(...)`\n\n## Descubrimiento y carga\n\n`discoverAndLoadHooks(configuredPaths, cwd)` realiza lo siguiente:\n\n1. Carga los hooks descubiertos desde el registro de capacidades (`loadCapability(\"hooks\")`)\n2. Añade las rutas configuradas explícitamente (deduplicadas por ruta absoluta)\n3. Llama a `loadHooks(allPaths, cwd)`\n\n`loadHooks` luego importa cada ruta y espera una función `default`.\n\n### Resolución de rutas\n\n`loader.ts` resuelve las rutas de hooks de la siguiente manera:\n\n- ruta absoluta: se usa tal cual\n- ruta `~`: expandida\n- ruta relativa: resuelta contra `cwd`\n\n### Discrepancia heredada importante\n\nLos proveedores de descubrimiento para `hookCapability` siguen modelando archivos de hook de estilo shell pre/post (por ejemplo, `.claude/hooks/pre/*`, `.xcsh/.../hooks/pre/*`).\n\nEl cargador de hooks aquí usa importación dinámica de módulos y requiere una fábrica de hooks JS/TS por defecto. Si una ruta de hook descubierta no es importable como módulo, la carga falla y se reporta en `LoadHooksResult.errors`.\n\n## Superficies de eventos\n\nLos eventos de hook están fuertemente tipados en `types.ts`.\n\n### Eventos de sesión\n\n- `session_start`\n- `session_before_switch` → puede retornar `{ cancel?: boolean }`\n- `session_switch`\n- `session_before_branch` → puede retornar `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_branch`\n- `session_before_compact` → puede retornar `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session.compacting` → puede retornar `{ context?: string[]; prompt?: string; preserveData?: Record<string, unknown> }`\n- `session_compact`\n- `session_before_tree` → puede retornar `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n- `session_tree`\n- `session_shutdown`\n\n### Eventos de agente/contexto\n\n- `context` → puede retornar `{ messages?: Message[] }`\n- `before_agent_start` → puede retornar `{ message?: { customType; content; display; details } }`\n- `agent_start`\n- `agent_end`\n- `turn_start`\n- `turn_end`\n- `auto_compaction_start`\n- `auto_compaction_end`\n- `auto_retry_start`\n- `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### Eventos de herramienta (modelo previo/posterior)\n\n- `tool_call` (pre-ejecución) → puede retornar `{ block?: boolean; reason?: string }`\n- `tool_result` (post-ejecución) → puede retornar `{ content?; details?; isError? }`\n\nEste es el modelo de intercepción previo/posterior central del subsistema de hooks.\n\n```text\nHook tool interception flow\n\ntool_call handlers\n   │\n   ├─ any { block: true }? ── yes ──> throw (tool blocked)\n   │\n   └─ no\n      │\n      ▼\n   execute underlying tool\n      │\n      ├─ success ──> tool_result handlers can override { content, details }\n      │\n      └─ error   ──> emit tool_result(isError=true) then rethrow original error\n```\n\n## Modelo de ejecución y semántica de mutación\n\n### 1) Pre-ejecución: `tool_call`\n\n`HookToolWrapper.execute()` emite `tool_call` antes de la ejecución de la herramienta.\n\n- si algún manejador retorna `{ block: true }`, la ejecución se detiene\n- si el manejador lanza una excepción, el envoltorio falla de forma cerrada y bloquea la ejecución\n- el `reason` retornado se convierte en el texto del error lanzado\n\n### 2) Ejecución de la herramienta\n\nLa herramienta subyacente se ejecuta normalmente si no está bloqueada.\n\n### 3) Post-ejecución: `tool_result`\n\nTras el éxito, el envoltorio emite `tool_result` con:\n\n- `toolName`, `toolCallId`, `input`\n- `content`\n- `details`\n- `isError: false`\n\nSi el manejador retorna sobreescrituras:\n\n- `content` puede reemplazar el contenido del resultado\n- `details` puede reemplazar los detalles del resultado\n\nEn caso de fallo de la herramienta, el envoltorio emite `tool_result` con `isError: true` y el contenido del texto de error, luego relanza el error original.\n\n### Qué pueden mutar los hooks\n\n- el contexto LLM para una sola llamada mediante `context` (cadena de reemplazo de `messages`)\n- el contenido/detalles de la salida de la herramienta en llamadas exitosas (ruta `tool_result`)\n- el mensaje inyectado pre-agente mediante `before_agent_start`\n- el comportamiento de cancelación/compactación personalizada/árbol mediante `session_before_*` y `session.compacting`\n\n### Qué no pueden mutar los hooks en esta implementación\n\n- los parámetros de entrada de la herramienta en su lugar (solo bloquear/permitir en `tool_call`)\n- la continuación de la ejecución tras errores lanzados por la herramienta (la ruta de error relanza)\n- el estado final de éxito/error en el comportamiento del envoltorio (el `isError` retornado está tipado pero no es aplicado por `HookToolWrapper`)\n\n## Ordenamiento y comportamiento de conflictos\n\n### Ordenamiento a nivel de descubrimiento\n\nLos proveedores de capacidades se ordenan por prioridad (mayor primero). La deduplicación es por clave de capacidad, gana el primero.\n\nPara `hooks`, la clave de capacidad es `${type}:${tool}:${name}`. Los duplicados sombreados de proveedores de menor prioridad se marcan y se excluyen de la lista descubierta efectiva.\n\n### Orden de carga\n\n`discoverAndLoadHooks` construye una lista plana `allPaths`, deduplicada por ruta absoluta resuelta, luego `loadHooks` itera en ese orden.\nEl orden de los archivos dentro de cada directorio descubierto depende de la salida de `readdir`; el cargador de hooks no realiza una ordenación adicional.\n\n### Orden de manejadores en tiempo de ejecución\n\nDentro de `HookRunner`, el orden es determinista por secuencia de registro:\n\n1. orden del arreglo de hooks\n2. orden de registro de manejadores por hook/evento\n\nComportamiento de conflictos por tipo de evento:\n\n- `tool_call`: gana el último resultado retornado, a menos que un manejador bloquee; el primer bloqueo produce un cortocircuito\n- `tool_result`: gana la última sobreescritura retornada (sin cortocircuito)\n- `context`: encadenado; cada manejador recibe la salida de mensajes del manejador anterior\n- `before_agent_start`: se conserva el primer mensaje retornado; los mensajes posteriores se ignoran\n- `session_before_*`: se rastrea el último resultado retornado; `cancel: true` produce un cortocircuito inmediato\n- `session.compacting`: gana el último resultado retornado\n\nConflictos de comandos/renderizadores:\n\n- `getCommand(name)` retorna la primera coincidencia entre hooks (gana el primero cargado)\n- `getMessageRenderer(customType)` retorna la primera coincidencia\n- `getRegisteredCommands()` retorna todos los comandos (sin deduplicación)\n\n## Interacciones de UI (`HookContext.ui`)\n\n`HookUIContext` incluye:\n\n- `select`, `confirm`, `input`, `editor`\n- `notify`\n- `setStatus`\n- `custom`\n- `setEditorText`, `getEditorText`\n- getter de `theme`\n\n`ctx.hasUI` indica si la UI interactiva está disponible.\n\nCuando se ejecuta sin UI, el comportamiento predeterminado del contexto sin operación es:\n\n- `select/input/editor` retornan `undefined`\n- `confirm` retorna `false`\n- `notify`, `setStatus`, `setEditorText` son operaciones sin efecto\n- `getEditorText` retorna `\"\"`\n\n### Comportamiento de la línea de estado\n\nEl texto de estado del hook establecido mediante `ctx.ui.setStatus(key, text)`:\n\n- se almacena por clave\n- se ordena por nombre de clave\n- se sanea (`\\r`, `\\n`, `\\t` → espacios; espacios repetidos colapsados)\n- se une y se trunca por ancho para la visualización\n\n## Propagación de errores y recuperación\n\n### En tiempo de carga\n\n- módulo inválido o exportación por defecto faltante → capturado en `LoadHooksResult.errors`\n- la carga continúa para otros hooks\n\n### En tiempo de evento\n\n`HookRunner.emit(...)` captura los errores de los manejadores para la mayoría de los eventos y emite `HookError` a los escuchadores (`hookPath`, `event`, `error`), luego continúa.\n\n`emitToolCall(...)` es más estricto: los errores de los manejadores no se absorben allí; se propagan al llamador. En `HookToolWrapper`, esto bloquea la llamada a la herramienta (a prueba de fallos).\n\n## Ejemplos de API realistas\n\n### Bloquear comandos bash inseguros\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName !== \"bash\") return;\n  const cmd = String(event.input.command ?? \"\");\n  if (!cmd.includes(\"rm -rf\")) return;\n\n  if (!ctx.hasUI) return { block: true, reason: \"rm -rf blocked (no UI)\" };\n  const ok = await ctx.ui.confirm(\"Dangerous command\", `Allow: ${cmd}`);\n  if (!ok) return { block: true, reason: \"user denied command\" };\n });\n}\n```\n\n### Redactar la salida de la herramienta en post-ejecución\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_result\", async event => {\n  if (event.toolName !== \"read\" || event.isError) return;\n\n  const redacted = event.content.map(chunk => {\n   if (chunk.type !== \"text\") return chunk;\n   return { ...chunk, text: chunk.text.replaceAll(/API_KEY=\\S+/g, \"API_KEY=[REDACTED]\") };\n  });\n\n  return { content: redacted };\n });\n}\n```\n\n### Modificar el contexto del modelo por llamada LLM\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"context\", async event => {\n  const filtered = event.messages.filter(msg => !(msg.role === \"custom\" && msg.customType === \"debug-only\"));\n  return { messages: filtered };\n });\n}\n```\n\n### Registrar un comando slash con métodos de contexto seguros para comandos\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.registerCommand(\"handoff\", {\n  description: \"Create a new session with setup message\",\n  handler: async (_args, ctx) => {\n   await ctx.waitForIdle();\n   await ctx.newSession({\n    parentSession: ctx.sessionManager.getSessionFile(),\n    setup: async sm => {\n     sm.appendMessage({\n      role: \"user\",\n      content: [{ type: \"text\", text: \"Continue from prior session summary.\" }],\n      timestamp: Date.now(),\n     });\n    },\n   });\n  },\n });\n}\n```\n\n## Superficie de exportación\n\n`src/extensibility/hooks/index.ts` exporta:\n\n- APIs de carga (`discoverAndLoadHooks`, `loadHooks`)\n- ejecutor y envoltorio (`HookRunner`, `HookToolWrapper`)\n- todos los tipos de hooks\n- reexportación de `execCommand`\n\nY la raíz del paquete (`src/index.ts`) reexporta los **tipos** de hook como superficie de compatibilidad heredada.\n",
	"es/configuration/porting-from-pi-mono.md": "---\ntitle: 'Migración desde pi-mono: Una guía práctica de merge'\ndescription: Guía práctica para migrar código del monorepo pi-mono al código base de xcsh.\nsidebar:\n  order: 9\n  label: Migración desde pi-mono\ni18n:\n  sourceHash: fd4e8c09303d\n  translator: machine\n---\n\n# Migración desde pi-mono: Una guía práctica de merge\n\nEsta guía es una lista de verificación repetible para portar cambios desde pi-mono a este repositorio.\nÚsela para cualquier merge: un solo archivo, rama de funcionalidad o sincronización de versión completa.\n\n## Último punto de sincronización\n\n**Commit:** `b21b42d032919de2f2e6920a76fa9a37c3920c0a`\n**Fecha:** 2026-03-22\n\nActualice esta sección después de cada sincronización; no reutilice el rango anterior.\n\nAl iniciar una nueva sincronización, genere los parches desde este commit en adelante:\n\n```bash\ngit format-patch b21b42d032919de2f2e6920a76fa9a37c3920c0a..HEAD --stdout > changes.patch\n```\n\n## 0) Definir el alcance\n\n- Identifique la referencia upstream (commit, tag o PR).\n- Liste los paquetes o carpetas que planea modificar.\n- Decida qué funcionalidades están dentro del alcance y cuáles se omiten intencionalmente.\n\n## 1) Traer el código de forma segura\n\n- Prefiera un diff limpio y enfocado en lugar de una copia al por mayor.\n- Evite copiar artefactos compilados o archivos generados.\n- Si upstream agregó nuevos archivos, agréguelos explícitamente y revise su contenido.\n\n## 2) Seguir las convenciones de extensión en imports\n\nLa mayoría de los archivos TypeScript de tiempo de ejecución omiten `.js` en los imports internos, pero algunos entrypoints de test/bench mantienen `.js` para compatibilidad con ESM en tiempo de ejecución. Siga el estilo existente del paquete local; no elimine extensiones de forma indiscriminada.\n\n- En los archivos de tiempo de ejecución de `packages/coding-agent`, mantenga los imports internos sin extensión a menos que importe recursos no-TS.\n- En `packages/tui/test` y `packages/natives/bench`, mantenga `.js` donde los archivos circundantes ya lo usan.\n- Mantenga las extensiones de archivo reales cuando las herramientas lo requieran (por ejemplo, `.json`, `.css`, embeds de texto `.md`).\n- Ejemplo: `import { x } from \"./foo.js\";` → `import { x } from \"./foo\";` (solo cuando la convención del paquete es sin extensión).\n\n## 3) Reemplazar los scopes de imports\n\nUpstream usa diferentes scopes de paquetes. Reemplácelos de manera consistente.\n\n- Reemplace los scopes antiguos con el scope local usado aquí.\n- Ejemplos (ajuste para que coincidan con los paquetes reales que está portando):\n  - `@mariozechner/pi-coding-agent` → `@f5-sales-demo/xcsh`\n  - `@mariozechner/pi-agent-core` → `@f5-sales-demo/pi-agent-core`\n  - `@mariozechner/pi-tui` → `@f5-sales-demo/pi-tui`\n  - `@mariozechner/pi-ai` → `@f5-sales-demo/pi-ai`\n\n## 4) Usar APIs de Bun cuando mejoren respecto a Node\n\nEjecutamos sobre Bun. Reemplace las APIs de Node solo cuando Bun proporcione una alternativa mejor.\n\n**SÍ reemplazar:**\n\n- Ejecución de procesos: `child_process.spawn` → Bun Shell `$` para comandos simples, `Bun.spawn`/`Bun.spawnSync` para streaming o trabajo de larga duración\n- E/S de archivos: `fs.readFileSync` → `Bun.file().text()` / `Bun.write()`\n- Clientes HTTP: `node-fetch`, `axios` → `fetch` nativo\n- Hashing criptográfico: `node:crypto` → Web Crypto o `Bun.hash`\n- SQLite: `better-sqlite3` → `bun:sqlite`\n- Carga de variables de entorno: `dotenv` → Bun carga `.env` automáticamente\n\n**NO reemplazar (estos funcionan correctamente en Bun):**\n\n- `os.homedir()` — NO reemplazar con `Bun.env.HOME`, `Bun.env.HOME` o el literal `\"~\"`\n- `os.tmpdir()` — NO reemplazar con `Bun.env.TMPDIR || \"/tmp\"` o rutas hardcodeadas\n- `fs.mkdtempSync()` — NO reemplazar con construcción manual de rutas\n- `path.join()`, `path.resolve()`, etc. — estos están bien\n\n**Estilo de import:** Use el prefijo `node:` con imports de namespace únicamente (sin imports con nombre desde `node:fs` o `node:path`).\n\n**Convenciones adicionales de Bun:**\n\n- Prefiera Bun Shell `$` para comandos cortos sin streaming; use `Bun.spawn` solo cuando necesite streaming de E/S o control de procesos.\n- Use `Bun.file()`/`Bun.write()` para archivos y `node:fs/promises` para directorios.\n- Evite verificaciones con `Bun.file().exists()`; use manejo con `isEnoent` en try/catch.\n- Prefiera `Bun.sleep(ms)` sobre wrappers de `setTimeout`.\n\n**Incorrecto:**\n\n```typescript\n// BROKEN: env vars may be undefined, \"~\" is not expanded\nconst home = Bun.env.HOME || \"~\";\nconst tmp = Bun.env.TMPDIR || \"/tmp\";\n```\n\n**Correcto:**\n\n```typescript\nimport * as os from \"node:os\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst configDir = path.join(os.homedir(), \".config\", \"myapp\");\nconst tempDir = fs.mkdtempSync(path.join(os.tmpdir(), \"myapp-\"));\n```\n\n## 5) Preferir embeds de Bun (sin copiar)\n\nNo copie assets de tiempo de ejecución ni archivos de vendor en tiempo de compilación.\n\n- Si upstream copia assets a una carpeta dist, reemplace con embeds compatibles con Bun.\n- Los prompts son archivos `.md` estáticos; use imports de texto de Bun (`with { type: \"text\" }`) y Handlebars en lugar de cadenas de prompt inline.\n- Use `import.meta.dir` + `Bun.file` para cargar recursos adyacentes que no sean texto.\n- Mantenga los assets en el repositorio y deje que el bundler los incluya.\n- Elimine los scripts de copia a menos que el usuario los solicite explícitamente.\n- Si upstream lee un archivo fallback empaquetado en tiempo de ejecución, reemplace las lecturas del sistema de archivos con un import de texto embed de Bun.\n  - Ejemplo (fallback de instrucciones de Codex):\n    - `const FALLBACK_PROMPT_PATH = join(import.meta.dir, \"codex-instructions.md\");` -> eliminado\n    - `import FALLBACK_INSTRUCTIONS from \"./codex-instructions.md\" with { type: \"text\" };`\n    - Use `return FALLBACK_INSTRUCTIONS;` en lugar de `readFileSync(FALLBACK_PROMPT_PATH, \"utf8\")`\n\n## 6) Portar `package.json` con cuidado\n\nTrate `package.json` como un contrato. Haga el merge de forma intencional.\n\n- Mantenga los valores existentes de `name`, `version`, `type`, `exports` y `bin` a menos que el port lo requiera.\n- Reemplace los scripts de npm/node con equivalentes de Bun (por ejemplo, `bun check`, `bun test`).\n- Asegúrese de que las dependencias usen el scope correcto.\n- No rebaje versiones de dependencias para corregir errores de tipos; actualice en su lugar.\n- Valide los enlaces de paquetes del workspace y `peerDependencies`.\n\n## 7) Alinear estilo de código y herramientas\n\n- Mantenga las convenciones de formato existentes.\n- No introduzca `any` a menos que sea necesario.\n- Evite imports dinámicos e imports de tipos inline; use solo imports de nivel superior.\n- Nunca construya prompts en código; los prompts son archivos `.md` estáticos renderizados con Handlebars.\n- En coding-agent, nunca use `console.log`/`console.warn`/`console.error`; use `logger` de `@f5-sales-demo/pi-utils`.\n- Use `Promise.withResolvers()` en lugar de `new Promise((resolve, reject) => ...)`.\n- **No use las palabras clave `private`/`protected`/`public` en campos o métodos de clase.** Use campos privados ES `#` para encapsulación; deje los miembros accesibles sin palabra clave. La única excepción son las propiedades de parámetros del constructor (`constructor(private readonly x: T)`), donde la palabra clave es requerida por TypeScript. Al portar código upstream que usa `private foo` o `protected bar`, convierta a `#foo` (privado) o `bar` sin modificador (accesible).\n- Prefiera helpers y utilidades existentes sobre código ad-hoc nuevo.\n- Preserve los cambios de infraestructura Bun-first ya realizados en este repositorio:\n  - El runtime es Bun (sin entry points de Node).\n  - El gestor de paquetes es Bun (sin lockfiles de npm).\n  - Las APIs pesadas de Node (`child_process`, `readline`) están reemplazadas con equivalentes de Bun.\n  - Las APIs ligeras de Node (`os.homedir`, `os.tmpdir`, `fs.mkdtempSync`, `path.*`) se mantienen.\n  - Los shebangs de CLI usan `bun` (no `node`, no `tsx`).\n  - Los paquetes usan archivos fuente directamente (sin paso de compilación de TypeScript).\n  - Los workflows de CI ejecutan Bun para install/check/test.\n\n## 8) Eliminar capas de compatibilidad antiguas\n\nA menos que se solicite, elimine los shims de compatibilidad de upstream.\n\n- Elimine las APIs antiguas que fueron reemplazadas.\n- Actualice todos los sitios de llamada a la nueva API directamente.\n- No mantenga versiones `*_v2` ni paralelas.\n\n## 9) Actualizar documentación y referencias\n\n- Reemplace los enlaces al repositorio pi-mono donde sea apropiado.\n- Actualice los ejemplos para usar Bun y los scopes de paquetes correctos.\n- Asegúrese de que las instrucciones del README aún coincidan con el comportamiento actual del repositorio.\n\n## 10) Validar el port\n\nEjecute las verificaciones estándar después de los cambios:\n\n- `bun check`\n\nSi el repositorio ya tiene verificaciones fallando no relacionadas con sus cambios, repórtelo.\nLas pruebas usan el runner de Bun (no Vitest), pero solo ejecute `bun test` cuando se solicite explícitamente.\n\n## 11) Proteger funcionalidades mejoradas (lista de trampas de regresión)\n\nSi ya mejoró el comportamiento localmente, trate esas mejoras como **no negociables**. Antes de portar, documente\nlas mejoras y agregue verificaciones explícitas para que no se pierdan en el merge.\n\n- **Congele el comportamiento esperado**: agregue una nota breve de \"antes/después\" para cada mejora (entradas, salidas,\n  valores por defecto, casos límite). Esto previene reversiones silenciosas.\n- **Mapee APIs antiguas → nuevas**: si upstream renombró conceptos (hooks → extensions, custom tools → tools, etc.),\n  asegúrese de que cada punto de entrada antiguo aún se conecte correctamente. Un flag o export omitido equivale a funcionalidad perdida.\n- **Verifique los exports**: revise los `exports` de `package.json`, tipos públicos y archivos barrel. Los ports de upstream a menudo\n  olvidan re-exportar adiciones locales.\n- **Cubra los caminos no felices**: si corrigió manejo de errores, timeouts o lógica de fallback, agregue una prueba o al\n  menos una lista de verificación manual que ejercite esos caminos.\n- **Verifique valores por defecto y orden de merge de configuración**: las mejoras a menudo residen en los valores por defecto. Confirme que los nuevos valores por defecto\n  no se revirtieron (por ejemplo, nueva precedencia de configuración, funcionalidades deshabilitadas, listas de herramientas).\n- **Audite el comportamiento de env/shell**: si corrigió la ejecución o el sandboxing, verifique que el nuevo camino aún use su\n  env sanitizado y no reintroduzca overrides de alias/funciones.\n- **Re-ejecute muestras específicas**: mantenga un conjunto mínimo de ejemplos \"known good\" y ejecútelos después del port\n  (flags de CLI, registro de extensiones, ejecución de herramientas).\n\n## 12) Detectar y manejar código refactorizado\n\nAntes de portar un archivo, verifique si upstream lo refactorizó significativamente:\n\n```bash\n# Compare the file you're about to port against what you have locally\ngit diff HEAD upstream/main -- path/to/file.ts\n```\n\nSi el diff muestra que el archivo fue **refactorizado** (no solo parcheado):\n\n- Nuevas abstracciones, conceptos renombrados, módulos fusionados, flujo de datos cambiado\n\nEntonces debe **leer la nueva implementación completamente** antes de portar. El merge a ciegas de código refactorizado pierde funcionalidad porque:\n\nNota: el modo interactivo fue recientemente dividido en controllers/utils/types. Al retroportar cambios relacionados, porte las actualizaciones a los archivos individuales que creamos y asegúrese de que el cableado de `interactive-mode.ts` se mantenga sincronizado.\n\n1. **Los valores por defecto cambian silenciosamente** - Una nueva variable `defaultFoo = [a, b]` puede reemplazar un antiguo `getAllFoo()` que retornaba `[a, b, c, d, e]`.\n\n2. **Las opciones de API se eliminan** - Cuando los sistemas se fusionan (por ejemplo, `hooks` + `customTools` → `extensions`), las opciones antiguas pueden no conectarse a la nueva implementación.\n\n3. **Los caminos de código quedan obsoletos** - Un concepto renombrado (por ejemplo, `hookMessage` → `custom`) necesita actualizaciones en cada switch statement, type guard y handler, no solo en la definición.\n\n4. **El contexto/capacidades se reducen** - Las APIs antiguas pueden haber expuesto `{ logger, typebox, pi }` que las nuevas APIs olvidaron incluir.\n\n### Proceso de porteo semántico\n\nCuando upstream refactorizó un módulo:\n\n1. **Lea la implementación antigua** - Entienda qué hacía, qué opciones aceptaba, qué exponía.\n\n2. **Lea la implementación nueva** - Entienda las nuevas abstracciones y cómo se mapean al comportamiento anterior.\n\n3. **Verifique la paridad de funcionalidades** - Para cada capacidad del código antiguo, confirme que el código nuevo la preserva o la elimina explícitamente.\n\n4. **Busque remanentes** - Busque nombres/conceptos antiguos que puedan haberse omitido en switch statements, handlers, componentes de UI.\n\n5. **Pruebe los límites** - Flags de CLI, opciones del SDK, event handlers, valores por defecto: aquí es donde se esconden las regresiones.\n\n### Verificaciones rápidas\n\n```bash\n# Find all uses of an old concept that may need updating\nrg \"oldConceptName\" --type ts\n\n# Compare default values between versions\ngit show upstream/main:path/to/file.ts | rg \"default|DEFAULT\"\n\n# Check if all enum/union values have handlers\nrg \"case \\\"\" path/to/file.ts\n```\n\n## 13) Lista de verificación de auditoría rápida\n\nUse esto como una pasada final antes de terminar:\n\n- [ ] Las extensiones de import siguen la convención del paquete local (sin eliminación indiscriminada de `.js`)\n- [ ] No hay APIs exclusivas de Node en código nuevo/portado\n- [ ] Todos los scopes de paquetes actualizados\n- [ ] Los scripts de `package.json` usan Bun\n- [ ] Los prompts son imports de texto `.md` (sin cadenas de prompt inline)\n- [ ] No hay `console.*` en coding-agent (usar `logger`)\n- [ ] Los assets se cargan mediante patrones de embed de Bun (sin scripts de copia)\n- [ ] Las pruebas o verificaciones se ejecutan (o se indica explícitamente que están bloqueadas)\n- [ ] No hay regresiones de funcionalidad (ver secciones 11-12)\n\n## 14) Formato de mensajes de commit\n\nAl hacer commit de un backport, siga el formato del repositorio `<type>(scope): <descripción en pasado>` y mantenga el\nrango de commits en el título.\n\n```\nfix(coding-agent): backported pi-mono changes (<from>..<to>)\n\npackages/<package>:\n- <type>: <description>\n- <type>: <description> (#<issue> by @<contributor>)\n\npackages/<other-package>:\n- <type>: <description>\n```\n\n**Ejemplo:**\n\n```\nfix(coding-agent): backported pi-mono changes (9f3eef65f..52532c7c0)\n\npackages/ai:\n- fix: handle \"sensitive\" stop reason from Anthropic API\n- fix: normalize tool call IDs with special characters for Responses API\n- fix: add overflow detection for Bedrock, MiniMax, Kimi providers\n- fix: 429 status is rate limiting, not context overflow\n\npackages/tui:\n- fix: refactored autocomplete state tracking\n- fix: file autocomplete should not trigger on empty text\n- fix: configurable autocomplete max visible items\n- fix: improved table column width calculation with word-aware wrapping\n\npackages/coding-agent:\n- fix: preserve external config.yml edits on save (#1046 by @nicobailonMD)\n- fix: resolve macOS NFD and curly quote variants in file paths\n```\n\n**Reglas:**\n\n- Agrupe los cambios por paquete\n- Use tipos de commit convencionales (`fix`, `feat`, `refactor`, `perf`, `docs`)\n- Incluya números de issue/PR de upstream y atribución del contribuidor para contribuciones externas\n- El rango de commits en el título ayuda a rastrear los puntos de sincronización\n\n## 15) Divergencias intencionales\n\nNuestro fork tiene decisiones arquitectónicas que difieren de upstream. **No porte estos patrones de upstream:**\n\n### Arquitectura de UI\n\n| Upstream                                    | Nuestro fork                                              | Razón                                                                 |\n| ------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------- |\n| Clase `FooterDataProvider`                  | `StatusLineComponent`                                     | Línea de estado más simple e integrada                                |\n| `ctx.ui.setHeader()` / `ctx.ui.setFooter()` | Stub en modos no-TUI                                     | Implementado en TUI, no-op en otros casos                             |\n| `ctx.ui.setEditorComponent()`               | Stub en modos no-TUI                                     | Implementado en TUI, no-op en otros casos                             |\n| Objeto de opciones `InteractiveModeOptions` | Args posicionales en constructor (el tipo de opciones aún se exporta) | Mantener la firma del constructor; actualizar el tipo cuando upstream agregue campos |\n\n### Nombres de componentes\n\n| Upstream                     | Nuestro fork            |\n| ---------------------------- | ----------------------- |\n| `extension-input.ts`         | `hook-input.ts`         |\n| `extension-selector.ts`      | `hook-selector.ts`      |\n| `ExtensionInputComponent`    | `HookInputComponent`    |\n| `ExtensionSelectorComponent` | `HookSelectorComponent` |\n\n### Nombres de API\n\n| Upstream                                 | Nuestro fork                             | Notas                                     |\n| ---------------------------------------- | ---------------------------------------- | ----------------------------------------- |\n| `sessionManager.appendSessionInfo(name)` | `sessionManager.setSessionName(name)`    | Usamos `sessionName` en todo el código    |\n| `sessionManager.getSessionName()`        | `sessionManager.getSessionName()`        | Igual (unificamos para coincidir con el RPC de upstream) |\n| `agent.sessionName` / `setSessionName()` | `agent.sessionName` / `setSessionName()` | Igual                                     |\n\n### Consolidación de archivos\n\n| Upstream                                           | Nuestro fork                            | Razón                                   |\n| -------------------------------------------------- | --------------------------------------- | --------------------------------------- |\n| `clipboard.ts` + `clipboard-image.ts` (archivos de tool) | Módulo clipboard de `@f5-sales-demo/pi-natives` | Fusionado en implementación nativa N-API |\n\n### Framework de pruebas\n\n| Upstream                  | Nuestro fork                  |\n| ------------------------- | ----------------------------- |\n| `vitest` con `vi.mock()`  | `bun:test` con `vi` de bun   |\n| Aserciones de `node:test`  | Matchers `expect()`           |\n\n### Arquitectura de herramientas\n\n| Upstream                            | Nuestro fork                                                      | Notas                                                     |\n| ----------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------- |\n| `createTool(cwd: string, options?)` | `createTools(session: ToolSession)` vía registro `BUILTIN_TOOLS`  | Las factorías de tools aceptan `ToolSession` y pueden retornar `null` |\n| Interfaces `*Operations` por tool   | Las interfaces por tool se mantienen (`FindOperations`, `GrepOperations`) | Usadas para overrides SSH/remotos                         |\n| `fs/promises` de Node.js en todos lados | `Bun.file()`/`Bun.write()` para archivos; `node:fs/promises` para directorios | Preferir APIs de Bun cuando simplifiquen                  |\n\n### Almacenamiento de autenticación\n\n| Upstream                        | Nuestro fork                                | Notas                                        |\n| ------------------------------- | ------------------------------------------- | -------------------------------------------- |\n| `proper-lockfile` + `auth.json` | `agent.db` (bun:sqlite)                     | Credenciales almacenadas exclusivamente en `agent.db` |\n| Una sola credencial por proveedor | Multi-credencial con selección round-robin  | Lógica de afinidad de sesión y backoff preservada |\n\n### Extensiones\n\n| Upstream                      | Nuestro fork                               |\n| ----------------------------- | ------------------------------------------ |\n| `jiti` para carga de TypeScript | `import()` nativo de Bun                  |\n| Campo de manifiesto `pkg.pi`   | `pkg.xcsh ?? pkg.pi` (preferir nuestro namespace) |\n\n### Omitir estas funcionalidades de upstream\n\nAl portar, **omita** estos archivos/funcionalidades por completo:\n\n- `footer-data-provider.ts` — usamos StatusLineComponent\n- `clipboard-image.ts` — el clipboard está en el módulo N-API de `@f5-sales-demo/pi-natives`\n- Archivos de workflow de GitHub — tenemos nuestro propio CI\n- `models.generated.ts` — auto-generado, regenerar localmente (como models.json en su lugar)\n\n### Funcionalidades que agregamos (preservar estas)\n\nEstas existen en nuestro fork pero no en upstream. **Nunca sobrescribir:**\n\n- `StatusLineComponent` en modo interactivo\n- Autenticación multi-credencial con afinidad de sesión\n- Sistema de descubrimiento basado en capacidades (`defineCapability`, `registerProvider`, `loadCapability`, `skillCapability`, etc.)\n- Integraciones MCP/Exa/SSH\n- Writethrough LSP para format-on-save\n- Intercepción de Bash (`checkBashInterception`)\n- Sugerencias de rutas difusas en la herramienta read\n",
	"es/configuration/rpc.md": "---\ntitle: Referencia del Protocolo RPC\ndescription: >-\n  Referencia del protocolo JSON-RPC para la comunicación entre procesos de los\n  componentes de xcsh.\nsidebar:\n  order: 5\n  label: Protocolo RPC\ni18n:\n  sourceHash: b4a3ddaf08ab\n  translator: machine\n---\n\n# Referencia del Protocolo RPC\n\nEl modo RPC ejecuta el agente de codificación como un protocolo JSON delimitado por saltos de línea sobre stdio.\n\n- **stdin**: comandos (`RpcCommand`) y respuestas de UI de extensiones\n- **stdout**: respuestas a comandos (`RpcResponse`), eventos de sesión/agente, solicitudes de UI de extensiones\n\nImplementación principal:\n\n- `src/modes/rpc/rpc-mode.ts`\n- `src/modes/rpc/rpc-types.ts`\n- `src/session/agent-session.ts`\n- `packages/agent/src/agent.ts`\n- `packages/agent/src/agent-loop.ts`\n\n## Inicio\n\n```bash\nxcsh --mode rpc [regular CLI options]\n```\n\nNotas de comportamiento:\n\n- Los argumentos CLI `@file` se rechazan en modo RPC.\n- El modo RPC deshabilita la generación automática de títulos de sesión por defecto para evitar una llamada adicional al modelo.\n- El modo RPC restablece las configuraciones que alteran el flujo de trabajo `todo.*`, `task.*` y `async.*` a sus valores predeterminados integrados en lugar de heredar las personalizaciones del usuario.\n- El proceso lee stdin como JSONL (`readJsonl(Bun.stdin.stream())`).\n- Cuando stdin se cierra, el proceso termina con código `0`.\n- Las respuestas/eventos se escriben como un objeto JSON por línea.\n\n## Transporte y Enmarcado\n\nCada trama es un único objeto JSON seguido de `\\n`.\n\nNo hay envolvente más allá de la forma del objeto en sí.\n\n### Categorías de tramas salientes (stdout)\n\n1. `RpcResponse` (`{ type: \"response\", ... }`)\n2. Objetos `AgentSessionEvent` (`agent_start`, `message_update`, etc.)\n3. `RpcExtensionUIRequest` (`{ type: \"extension_ui_request\", ... }`)\n4. Errores de extensión (`{ type: \"extension_error\", extensionPath, event, error }`)\n\n### Categorías de tramas entrantes (stdin)\n\n1. `RpcCommand`\n2. `RpcExtensionUIResponse` (`{ type: \"extension_ui_response\", ... }`)\n\n## Correlación de Solicitud/Respuesta\n\nTodos los comandos aceptan un `id?: string` opcional.\n\n- Si se proporciona, las respuestas normales a comandos repiten el mismo `id`.\n- `RpcClient` depende de esto para la resolución de solicitudes pendientes.\n\nComportamiento importante en casos límite del runtime:\n\n- Las respuestas a comandos desconocidos se emiten con `id: undefined` (incluso si la solicitud tenía un `id`).\n- Las excepciones de análisis/manejador en el bucle de entrada emiten `command: \"parse\"` con `id: undefined`.\n- `prompt` y `abort_and_prompt` retornan éxito inmediato, y luego pueden emitir una respuesta de error posterior con el **mismo** id si la programación asíncrona del prompt falla.\n\n## Esquema de Comandos (canónico)\n\n`RpcCommand` está definido en `src/modes/rpc/rpc-types.ts`:\n\n### Prompting\n\n- `{ id?, type: \"prompt\", message: string, images?: ImageContent[], streamingBehavior?: \"steer\" | \"followUp\" }`\n- `{ id?, type: \"steer\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"follow_up\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"abort\" }`\n- `{ id?, type: \"abort_and_prompt\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"new_session\", parentSession?: string }`\n\n### Estado\n\n- `{ id?, type: \"get_state\" }`\n- `{ id?, type: \"set_todos\", phases: TodoPhase[] }`\n- `{ id?, type: \"set_host_tools\", tools: RpcHostToolDefinition[] }`\n\n### Modelo\n\n- `{ id?, type: \"set_model\", provider: string, modelId: string }`\n- `{ id?, type: \"cycle_model\" }`\n- `{ id?, type: \"get_available_models\" }`\n\n### Pensamiento\n\n- `{ id?, type: \"set_thinking_level\", level: ThinkingLevel }`\n- `{ id?, type: \"cycle_thinking_level\" }`\n\n### Modos de cola\n\n- `{ id?, type: \"set_steering_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_follow_up_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_interrupt_mode\", mode: \"immediate\" | \"wait\" }`\n\n### Compactación\n\n- `{ id?, type: \"compact\", customInstructions?: string }`\n- `{ id?, type: \"set_auto_compaction\", enabled: boolean }`\n\n### Reintento\n\n- `{ id?, type: \"set_auto_retry\", enabled: boolean }`\n- `{ id?, type: \"abort_retry\" }`\n\n### Bash\n\n- `{ id?, type: \"bash\", command: string }`\n- `{ id?, type: \"abort_bash\" }`\n\n### Sesión\n\n- `{ id?, type: \"get_session_stats\" }`\n- `{ id?, type: \"export_html\", outputPath?: string }`\n- `{ id?, type: \"switch_session\", sessionPath: string }`\n- `{ id?, type: \"branch\", entryId: string }`\n- `{ id?, type: \"get_branch_messages\" }`\n- `{ id?, type: \"get_last_assistant_text\" }`\n- `{ id?, type: \"set_session_name\", name: string }`\n\n### Mensajes\n\n- `{ id?, type: \"get_messages\" }`\n\n## Esquema de Respuestas\n\nTodos los resultados de comandos utilizan `RpcResponse`:\n\n- Éxito: `{ id?, type: \"response\", command: <command>, success: true, data?: ... }`\n- Fallo: `{ id?, type: \"response\", command: string, success: false, error: string }`\n\nLos datos de respuesta son específicos de cada comando y están definidos en `rpc-types.ts`.\n\n### Payload de `get_state`\n\n```json\n{\n  \"model\": { \"provider\": \"...\", \"id\": \"...\" },\n  \"thinkingLevel\": \"off|minimal|low|medium|high|xhigh\",\n  \"isStreaming\": false,\n  \"isCompacting\": false,\n  \"steeringMode\": \"all|one-at-a-time\",\n  \"followUpMode\": \"all|one-at-a-time\",\n  \"interruptMode\": \"immediate|wait\",\n  \"sessionFile\": \"...\",\n  \"sessionId\": \"...\",\n  \"sessionName\": \"...\",\n  \"autoCompactionEnabled\": true,\n  \"messageCount\": 0,\n  \"queuedMessageCount\": 0,\n  \"todoPhases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Todos\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the tool surface\",\n          \"status\": \"in_progress\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n### Payload de `set_todos`\n\nReemplaza el estado de tareas en memoria para la sesión actual y devuelve la lista normalizada de fases:\n\n```json\n{\n  \"id\": \"req_2\",\n  \"type\": \"set_todos\",\n  \"phases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Evaluation\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the read tool surface\",\n          \"status\": \"in_progress\"\n        },\n        {\n          \"id\": \"task-2\",\n          \"content\": \"Exercise edit operations\",\n          \"status\": \"pending\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nEsto es útil para hosts que desean pre-cargar un plan antes del primer prompt.\n\n### Payload de `set_host_tools`\n\nReemplaza el conjunto actual de herramientas propiedad del host que el servidor RPC puede invocar de vuelta a través de stdio:\n\n```json\n{\n  \"id\": \"req_3\",\n  \"type\": \"set_host_tools\",\n  \"tools\": [\n    {\n      \"name\": \"echo_host\",\n      \"label\": \"Echo Host\",\n      \"description\": \"Echo a value from the embedding host\",\n      \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"message\": { \"type\": \"string\" }\n        },\n        \"required\": [\"message\"],\n        \"additionalProperties\": false\n      }\n    }\n  ]\n}\n```\n\nEl payload de respuesta es:\n\n```json\n{\n  \"toolNames\": [\"echo_host\"]\n}\n```\n\nEstas herramientas se agregan al registro de herramientas de la sesión activa antes de la siguiente llamada al modelo. Reenviar `set_host_tools` reemplaza el conjunto anterior propiedad del host.\n\n## Esquema del Flujo de Eventos\n\nEl modo RPC reenvía objetos `AgentSessionEvent` desde `AgentSession.subscribe(...)`.\n\nTipos de eventos comunes:\n\n- `agent_start`, `agent_end`\n- `turn_start`, `turn_end`\n- `message_start`, `message_update`, `message_end`\n- `tool_execution_start`, `tool_execution_update`, `tool_execution_end`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n- `todo_auto_clear`\n\nLos errores del ejecutor de extensiones se emiten por separado como:\n\n```json\n{ \"type\": \"extension_error\", \"extensionPath\": \"...\", \"event\": \"...\", \"error\": \"...\" }\n```\n\n`message_update` incluye deltas de streaming en `assistantMessageEvent` (deltas de texto/pensamiento/llamada de herramienta).\n\n## Concurrencia y Ordenamiento de Prompt/Cola\n\nEste es el comportamiento operacional más importante.\n\n### Confirmación inmediata vs completación\n\n`prompt` y `abort_and_prompt` se **confirman inmediatamente**:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n```\n\nEsto significa:\n\n- aceptación del comando != completación de la ejecución\n- la completación final se observa mediante `agent_end`\n\n### Durante el streaming\n\n`AgentSession.prompt()` requiere `streamingBehavior` durante el streaming activo:\n\n- `\"steer\"` => mensaje de dirección en cola (ruta de interrupción)\n- `\"followUp\"` => mensaje de seguimiento en cola (ruta post-turno)\n\nSi se omite durante el streaming, el prompt falla.\n\n### Valores predeterminados de la cola\n\nDel esquema de configuraciones del agente de codificación (`packages/coding-agent/src/config/settings-schema.ts`):\n\n- `steeringMode`: `\"one-at-a-time\"`\n- `followUpMode`: `\"one-at-a-time\"`\n- `interruptMode`: `\"wait\"`\n\n### Semántica de los modos\n\n- `set_steering_mode` / `set_follow_up_mode`\n  - `\"one-at-a-time\"`: desencola un mensaje en cola por turno\n  - `\"all\"`: desencola toda la cola de una vez\n- `set_interrupt_mode`\n  - `\"immediate\"`: la ejecución de herramientas verifica la dirección entre llamadas de herramientas; la dirección pendiente puede abortar las llamadas de herramientas restantes en el turno\n  - `\"wait\"`: posterga la dirección hasta la completación del turno\n\n## Sub-Protocolo de UI de Extensiones\n\nLas extensiones en modo RPC utilizan tramas de solicitud/respuesta de UI.\n\n### Solicitud saliente\n\nMétodos de `RpcExtensionUIRequest` (`type: \"extension_ui_request\"`):\n\n- `select`, `confirm`, `input`, `editor`\n- `notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text`\n\nNota del runtime:\n\n- La generación automática de títulos de sesión está deshabilitada en modo RPC, y las solicitudes de UI `setTitle` también se suprimen por defecto porque la mayoría de los hosts no tienen una superficie significativa de título de terminal. Configure `PI_RPC_EMIT_TITLE=1` para volver a habilitar solo el evento de UI.\n\nEjemplo:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"123\", \"method\": \"confirm\", \"title\": \"Confirm\", \"message\": \"Continue?\", \"timeout\": 30000 }\n```\n\n### Respuesta entrante\n\n`RpcExtensionUIResponse` (`type: \"extension_ui_response\"`):\n\n- `{ type: \"extension_ui_response\", id: string, value: string }`\n- `{ type: \"extension_ui_response\", id: string, confirmed: boolean }`\n- `{ type: \"extension_ui_response\", id: string, cancelled: true }`\n\nSi un diálogo tiene un tiempo de espera, el modo RPC resuelve a un valor predeterminado cuando el tiempo de espera/cancelación se activa.\n\n## Sub-Protocolo de Herramientas del Host\n\nLos hosts RPC pueden exponer herramientas personalizadas al agente enviando `set_host_tools`, y luego atendiendo las solicitudes de ejecución a través del mismo transporte.\n\n### Solicitud saliente\n\nCuando el agente necesita que el host ejecute una de esas herramientas, el modo RPC emite:\n\n```json\n{\n  \"type\": \"host_tool_call\",\n  \"id\": \"host_1\",\n  \"toolCallId\": \"toolu_123\",\n  \"toolName\": \"echo_host\",\n  \"arguments\": { \"message\": \"hello\" }\n}\n```\n\nSi la ejecución de la herramienta se cancela posteriormente, el modo RPC emite:\n\n```json\n{\n  \"type\": \"host_tool_cancel\",\n  \"id\": \"host_cancel_1\",\n  \"targetId\": \"host_1\"\n}\n```\n\n### Actualizaciones entrantes y completación\n\nLos hosts pueden opcionalmente transmitir progreso:\n\n```json\n{\n  \"type\": \"host_tool_update\",\n  \"id\": \"host_1\",\n  \"partialResult\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"working\" }]\n  }\n}\n```\n\nLa completación utiliza:\n\n```json\n{\n  \"type\": \"host_tool_result\",\n  \"id\": \"host_1\",\n  \"result\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"done\" }]\n  }\n}\n```\n\nEstablezca `isError: true` en `host_tool_result` para exponer el contenido devuelto como un error de herramienta.\n\n## Modelo de Errores y Recuperabilidad\n\n### Fallos a nivel de comando\n\nLos fallos son `success: false` con un `error` de tipo string.\n\n```json\n{ \"id\": \"req_2\", \"type\": \"response\", \"command\": \"set_model\", \"success\": false, \"error\": \"Model not found: provider/model\" }\n```\n\n### Expectativas de recuperabilidad\n\n- La mayoría de los fallos de comandos son recuperables; el proceso permanece activo.\n- Las excepciones de JSONL malformado / bucle de análisis emiten una respuesta de error `parse` y continúan leyendo las líneas siguientes.\n- Un `set_session_name` vacío se rechaza (`Session name cannot be empty`).\n- Las respuestas de UI de extensiones con `id` desconocido se ignoran.\n- Las condiciones de terminación del proceso son el cierre de stdin o un apagado explícito desencadenado por una extensión.\n\n## Flujos de Comandos Compactos\n\n### 1) Prompt y streaming\n\nstdin:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"prompt\", \"message\": \"Summarize this repo\" }\n```\n\nSecuencia de stdout (típica):\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n{ \"type\": \"agent_start\" }\n{ \"type\": \"message_update\", \"assistantMessageEvent\": { \"type\": \"text_delta\", \"delta\": \"...\" }, \"message\": { \"role\": \"assistant\", \"content\": [] } }\n{ \"type\": \"agent_end\", \"messages\": [] }\n```\n\n### 2) Prompt durante streaming con política de cola explícita\n\nstdin:\n\n```json\n{ \"id\": \"req_2\", \"type\": \"prompt\", \"message\": \"Also include risks\", \"streamingBehavior\": \"followUp\" }\n```\n\n### 3) Inspeccionar y ajustar el comportamiento de la cola\n\nstdin:\n\n```json\n{ \"id\": \"q1\", \"type\": \"get_state\" }\n{ \"id\": \"q2\", \"type\": \"set_steering_mode\", \"mode\": \"all\" }\n{ \"id\": \"q3\", \"type\": \"set_interrupt_mode\", \"mode\": \"wait\" }\n```\n\n### 4) Ida y vuelta de UI de extensión\n\nstdout:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"ui_7\", \"method\": \"input\", \"title\": \"Branch name\", \"placeholder\": \"feature/...\" }\n```\n\nstdin:\n\n```json\n{ \"type\": \"extension_ui_response\", \"id\": \"ui_7\", \"value\": \"feature/rpc-host\" }\n```\n\n## Notas sobre el helper `RpcClient`\n\n`src/modes/rpc/rpc-client.ts` es un wrapper de conveniencia, no la definición del protocolo.\n\nCaracterísticas actuales del helper:\n\n- Inicia `bun <cliPath> --mode rpc`\n- Correlaciona respuestas mediante ids generados `req_<n>`\n- Despacha solo tipos de `AgentEvent` reconocidos a los listeners\n- Soporta herramientas personalizadas propiedad del host mediante `setCustomTools()` y manejo automático de `host_tool_call` / `host_tool_cancel`\n- **No** expone métodos helper para todos los comandos del protocolo (por ejemplo, `set_interrupt_mode` y `set_session_name` están en los tipos del protocolo pero no están envueltos como métodos dedicados)\n\nUtilice tramas de protocolo sin procesar si necesita cobertura completa de la superficie.\n",
	"es/configuration/sdk.md": "---\ntitle: SDK\ndescription: >-\n  SDK para construir agentes personalizados e integraciones sobre el runtime del\n  agente de codificación xcsh.\nsidebar:\n  order: 6\n  label: SDK\ni18n:\n  sourceHash: 80f3a4374241\n  translator: machine\n---\n\n# SDK\n\nEl SDK es la superficie de integración en proceso para `@f5-sales-demo/xcsh`.\nÚselo cuando desee acceso directo al estado del agente, transmisión de eventos, conexión de herramientas y control de sesión desde su propio proceso Bun/Node.\n\nSi necesita aislamiento entre lenguajes o procesos, utilice el modo RPC en su lugar.\n\n## Instalación\n\n```bash\nbun add @f5-sales-demo/xcsh\n```\n\n## Puntos de entrada\n\n`@f5-sales-demo/xcsh` exporta las APIs del SDK desde la raíz del paquete (y también a través de `@f5-sales-demo/xcsh/sdk`).\n\nExportaciones principales para integradores:\n\n- `createAgentSession`\n- `SessionManager`\n- `Settings`\n- `AuthStorage`\n- `ModelRegistry`\n- `discoverAuthStorage`\n- Ayudantes de descubrimiento (`discoverExtensions`, `discoverSkills`, `discoverContextFiles`, `discoverPromptTemplates`, `discoverSlashCommands`, `discoverCustomTSCommands`, `discoverMCPServers`)\n- Superficie de fábrica de herramientas (`createTools`, `BUILTIN_TOOLS`, clases de herramientas)\n\n## Inicio rápido (valores predeterminados de autodescubrimiento)\n\n```ts\nimport { createAgentSession } from \"@f5-sales-demo/xcsh\";\n\nconst { session, modelFallbackMessage } = await createAgentSession();\n\nif (modelFallbackMessage) {\n process.stderr.write(`${modelFallbackMessage}\\n`);\n}\n\nconst unsubscribe = session.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Summarize this repository in 3 bullets.\");\nunsubscribe();\nawait session.dispose();\n```\n\n## Qué descubre `createAgentSession()` por defecto\n\n`createAgentSession()` sigue el principio \"proveer para sobrescribir, omitir para descubrir\".\n\nSi se omite, resuelve:\n\n- `cwd`: `getProjectDir()`\n- `agentDir`: `~/.xcsh/agent` (a través de `getAgentDir()`)\n- `authStorage`: `discoverAuthStorage(agentDir)`\n- `modelRegistry`: `new ModelRegistry(authStorage)` + `await refresh()`\n- `settings`: `await Settings.init({ cwd, agentDir })`\n- `sessionManager`: `SessionManager.create(cwd)` (respaldado por archivo)\n- habilidades/archivos de contexto/plantillas de prompts/comandos slash/extensiones/comandos TS personalizados\n- herramientas integradas a través de `createTools(...)`\n- herramientas MCP (habilitadas por defecto)\n- integración LSP (habilitada por defecto)\n\n### Entradas requeridas vs opcionales\n\nNormalmente solo debe proporcionar lo que desea controlar:\n\n- **Debe proporcionar**: nada para una sesión mínima\n- **Generalmente se proporcionan explícitamente** en integradores:\n    - `sessionManager` (si necesita en memoria o ubicación personalizada)\n    - `authStorage` + `modelRegistry` (si gestiona el ciclo de vida de credenciales/modelos)\n    - `model` o `modelPattern` (si la selección determinística de modelos es importante)\n    - `settings` (si necesita configuración aislada/de prueba)\n\n## Comportamiento del gestor de sesión (persistente vs en memoria)\n\n`AgentSession` siempre utiliza un `SessionManager`; el comportamiento depende de qué fábrica use.\n\n### Respaldado por archivo (predeterminado)\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.create(process.cwd()),\n});\n\nconsole.log(session.sessionFile); // absolute .jsonl path\n```\n\n- Persiste conversaciones/mensajes/deltas de estado en archivos de sesión.\n- Admite flujos de trabajo de reanudación/apertura/listado/bifurcación.\n- `session.sessionFile` está definido.\n\n### En memoria\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.inMemory(),\n});\n\nconsole.log(session.sessionFile); // undefined\n```\n\n- Sin persistencia en el sistema de archivos.\n- Útil para pruebas, trabajadores efímeros y agentes con ámbito de solicitud.\n- Los métodos de sesión siguen funcionando, pero los comportamientos específicos de persistencia (rutas de reanudación/bifurcación de archivos) son naturalmente limitados.\n\n### Ayudantes de reanudación/apertura/listado\n\n```ts\nimport { SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst recent = await SessionManager.continueRecent(process.cwd());\nconst listed = await SessionManager.list(process.cwd());\nconst opened = listed[0] ? await SessionManager.open(listed[0].path) : null;\n```\n\n## Conexión de modelos y autenticación\n\n`createAgentSession()` utiliza `ModelRegistry` + `AuthStorage` para la selección de modelos y la resolución de claves de API.\n\n### Conexión explícita\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst available = modelRegistry.getAvailable();\nif (available.length === 0) throw new Error(\"No authenticated models available\");\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n model: available[0],\n thinkingLevel: \"medium\",\n sessionManager: SessionManager.inMemory(),\n});\n```\n\n### Orden de selección cuando se omite `model`\n\nCuando no se proporciona un `model`/`modelPattern` explícito:\n\n1. restaurar el modelo desde la sesión existente (si es restaurable y la clave está disponible)\n2. rol de modelo predeterminado de la configuración (`default`)\n3. primer modelo disponible con autenticación válida\n\nSi la restauración falla, `modelFallbackMessage` explica el mecanismo de respaldo.\n\n### Prioridad de autenticación\n\n`AuthStorage.getApiKey(...)` resuelve en este orden:\n\n1. anulación en tiempo de ejecución (`setRuntimeApiKey`)\n2. credenciales almacenadas en `agent.db`\n3. variables de entorno del proveedor\n4. respaldo del resolver de proveedor personalizado (si está configurado)\n\n## Modelo de suscripción a eventos\n\nSuscríbase con `session.subscribe(listener)`; devuelve una función de cancelación de suscripción.\n\n```ts\nconst unsubscribe = session.subscribe(event => {\n switch (event.type) {\n  case \"agent_start\":\n  case \"turn_start\":\n  case \"tool_execution_start\":\n   break;\n  case \"message_update\":\n   if (event.assistantMessageEvent.type === \"text_delta\") {\n    process.stdout.write(event.assistantMessageEvent.delta);\n   }\n   break;\n }\n});\n```\n\n`AgentSessionEvent` incluye el `AgentEvent` principal más los eventos a nivel de sesión:\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n## Ciclo de vida del prompt\n\n`session.prompt(text, options?)` es el punto de entrada principal.\n\nComportamiento:\n\n1. expansión opcional de comandos/plantillas (comandos `/`, comandos personalizados, comandos slash de archivo, plantillas de prompts)\n2. si actualmente está transmitiendo:\n    - requiere `streamingBehavior: \"steer\" | \"followUp\"`\n    - encola en lugar de descartar el trabajo\n3. si está inactivo:\n    - valida el modelo y la clave de API\n    - agrega el mensaje del usuario\n    - inicia el turno del agente\n\nAPIs relacionadas:\n\n- `sendUserMessage(content, { deliverAs? })`\n- `steer(text, images?)`\n- `followUp(text, images?)`\n- `sendCustomMessage({ customType, content, ... }, { deliverAs?, triggerTurn? })`\n- `abort()`\n\n## Herramientas e integración de extensiones\n\n### Integrados y filtrado\n\n- Los integrados provienen de `createTools(...)` y `BUILTIN_TOOLS`.\n- `toolNames` actúa como una lista de permitidos para los integrados.\n- Las herramientas `customTools` y las registradas por extensiones siguen incluidas.\n- Las herramientas ocultas (por ejemplo, `submit_result`) son opt-in a menos que las opciones lo requieran.\n\n```ts\nconst { session } = await createAgentSession({\n toolNames: [\"read\", \"grep\", \"find\", \"write\"],\n requireSubmitResultTool: true,\n});\n```\n\n### Extensiones\n\n- `extensions`: `ExtensionFactory[]` en línea\n- `additionalExtensionPaths`: cargar archivos de extensión adicionales\n- `disableExtensionDiscovery`: deshabilitar el escaneo automático de extensiones\n- `preloadedExtensions`: reutilizar un conjunto de extensiones ya cargadas\n\n### Cambios en el conjunto de herramientas en tiempo de ejecución\n\n`AgentSession` admite actualizaciones de activación en tiempo de ejecución:\n\n- `getActiveToolNames()`\n- `getAllToolNames()`\n- `setActiveToolsByName(names)`\n- `refreshMCPTools(mcpTools)`\n\nEl prompt del sistema se reconstruye para reflejar los cambios en las herramientas activas.\n\n## Ayudantes de descubrimiento\n\nÚselos cuando desee control parcial sin recrear la lógica de descubrimiento interna:\n\n- `discoverAuthStorage(agentDir?)`\n- `discoverExtensions(cwd?)`\n- `discoverSkills(cwd?, _agentDir?, settings?)`\n- `discoverContextFiles(cwd?, _agentDir?)`\n- `discoverPromptTemplates(cwd?, agentDir?)`\n- `discoverSlashCommands(cwd?)`\n- `discoverCustomTSCommands(cwd?, agentDir?)`\n- `discoverMCPServers(cwd?)`\n- `buildSystemPrompt(options?)`\n\n## Opciones orientadas a subagentes\n\nPara consumidores del SDK que construyen orquestadores (similar al flujo de ejecutor de tareas):\n\n- `outputSchema`: pasa la expectativa de salida estructurada al contexto de la herramienta\n- `requireSubmitResultTool`: fuerza la inclusión de la herramienta `submit_result`\n- `taskDepth`: contexto de profundidad de recursión para sesiones de tareas anidadas\n- `parentTaskPrefix`: prefijo de nomenclatura de artefactos para salidas de tareas anidadas\n\nEstos son opcionales para la integración normal de agente único.\n\n## Valor de retorno de `createAgentSession()`\n\n```ts\ntype CreateAgentSessionResult = {\n session: AgentSession;\n extensionsResult: LoadExtensionsResult;\n setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;\n mcpManager?: MCPManager;\n modelFallbackMessage?: string;\n lspServers?: Array<{ name: string; status: \"ready\" | \"error\"; fileTypes: string[]; error?: string }>;\n};\n```\n\nUtilice `setToolUIContext(...)` solo si su integrador proporciona capacidades de interfaz de usuario que las herramientas/extensiones deben invocar.\n\n## Ejemplo de integración controlada mínima\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n Settings,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst settings = Settings.isolated({\n \"compaction.enabled\": true,\n \"retry.enabled\": true,\n});\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n settings,\n sessionManager: SessionManager.inMemory(),\n toolNames: [\"read\", \"grep\", \"find\", \"edit\", \"write\"],\n enableMCP: false,\n enableLsp: true,\n});\n\nsession.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Find all TODO comments in this repo and propose fixes.\");\nawait session.dispose();\n```\n",
	"es/configuration/secrets.md": "---\ntitle: Ofuscación de Secretos\ndescription: >-\n  Pipeline de ofuscación de secretos que redacta valores sensibles de los\n  registros de sesión y las salidas.\nsidebar:\n  order: 3\n  label: Secretos\ni18n:\n  sourceHash: 1d9dc101c614\n  translator: machine\n---\n\n# Ofuscación de Secretos\n\nEvita que valores sensibles (claves API, tokens, contraseñas) sean enviados a los proveedores de LLM. Cuando está habilitado, los secretos se reemplazan con marcadores de posición determinísticos antes de salir del proceso, y se restauran en los argumentos de llamada a herramientas devueltos por el modelo.\n\n## Habilitación\n\nHabilitado por defecto. Se puede alternar a través de la interfaz `/settings` o directamente en `config.yml`:\n\n```yaml\nsecrets:\n  enabled: false\n```\n\n## Cómo funciona\n\n1. Al iniciar la sesión, los secretos se recopilan de dos fuentes:\n   - **Variables de entorno** que coinciden con patrones comunes de secretos (`*_KEY`, `*_SECRET`, `*_TOKEN`, `*_PASSWORD`, etc.) con valores de >= 8 caracteres\n   - **Archivos `secrets.yml`** (ver más abajo)\n\n2. Los mensajes salientes al LLM tienen todos los valores secretos reemplazados con marcadores de posición como `<<$env:S0>>`, `<<$env:S1>>`, etc.\n\n3. Los argumentos de llamada a herramientas devueltos por el modelo se recorren en profundidad y los marcadores de posición se restauran a los valores originales antes de la ejecución.\n\nDos modos controlan lo que sucede con cada secreto:\n\n| Modo | Comportamiento | Reversible |\n|---|---|---|\n| `obfuscate` (predeterminado) | Reemplazado con marcador de posición indexado `<<$env:SN>>` | Sí (desofuscado en argumentos de herramientas) |\n| `replace` | Reemplazado con cadena determinística de la misma longitud | No (unidireccional) |\n\n## secrets.yml\n\nDefine entradas de secretos personalizadas en YAML. Se verifican dos ubicaciones:\n\n| Nivel | Ruta | Propósito |\n|---|---|---|\n| Global | `~/.xcsh/agent/secrets.yml` | Secretos para todos los proyectos |\n| Proyecto | `<cwd>/.xcsh/secrets.yml` | Secretos específicos del proyecto |\n\nLas entradas de proyecto anulan las entradas globales con `content` coincidente.\n\n### Esquema\n\nCada entrada en el arreglo tiene estos campos:\n\n| Campo | Tipo | Requerido | Descripción |\n|---|---|---|---|\n| `type` | `\"plain\"` o `\"regex\"` | Sí | Estrategia de coincidencia |\n| `content` | string | Sí | El valor del secreto (plain) o patrón regex (regex) |\n| `mode` | `\"obfuscate\"` o `\"replace\"` | No | Predeterminado: `\"obfuscate\"` |\n| `replacement` | string | No | Reemplazo personalizado (solo modo replace) |\n| `flags` | string | No | Flags de regex (solo tipo regex) |\n\n### Ejemplos\n\n#### Secretos de texto plano\n\n```yaml\n# Ofuscar una clave API específica (modo predeterminado)\n- type: plain\n  content: sk-proj-abc123def456\n\n# Reemplazar una contraseña de base de datos con una cadena fija\n- type: plain\n  content: hunter2\n  mode: replace\n  replacement: \"********\"\n```\n\n#### Secretos con regex\n\n```yaml\n# Ofuscar cualquier clave de estilo AWS\n- type: regex\n  content: \"AKIA[0-9A-Z]{16}\"\n\n# Coincidencia sin distinción de mayúsculas/minúsculas con flags explícitos\n- type: regex\n  content: \"api[_-]?key\\\\s*=\\\\s*\\\\w+\"\n  flags: \"i\"\n\n# Sintaxis literal de regex (patrón y flags en una sola cadena)\n- type: regex\n  content: \"/bearer\\\\s+[a-zA-Z0-9._~+\\\\/=-]+/i\"\n```\n\nLas entradas regex siempre escanean globalmente (el flag `g` se aplica automáticamente). La sintaxis literal de regex `/patrón/flags` es compatible como alternativa a los campos separados `content` + `flags`. Las barras escapadas dentro del patrón (`\\\\/`) se manejan correctamente.\n\n#### Modo replace con regex\n\n```yaml\n# Reemplazo unidireccional de cadenas de conexión (no reversible)\n- type: regex\n  content: \"postgres://[^\\\\s]+\"\n  mode: replace\n  replacement: \"postgres://***\"\n```\n\n## Interacción con la detección de variables de entorno\n\nLas variables de entorno siempre se recopilan primero. Las entradas definidas en archivos se agregan después, por lo que las entradas de archivo pueden cubrir secretos que no están en variables de entorno (archivos de configuración, valores codificados, etc.). Si el mismo valor aparece en ambos, el modo de la entrada del archivo tiene precedencia.\n\n## Archivos clave\n\n- `src/secrets/index.ts` -- carga, fusión, recopilación de variables de entorno\n- `src/secrets/obfuscator.ts` -- clase `SecretObfuscator`, generación de marcadores de posición, ofuscación de mensajes\n- `src/secrets/regex.ts` -- análisis y compilación de literales regex\n- `src/config/settings-schema.ts` -- definición del ajuste `secrets.enabled`\n",
	"es/extensions/extension-loading.md": "---\ntitle: Carga de extensiones (Módulos TypeScript/JavaScript)\ndescription: >-\n  Pipeline de carga de módulos TypeScript y JavaScript para extensiones con\n  resolución, validación y caché.\nsidebar:\n  order: 2\n  label: Carga de extensiones\ni18n:\n  sourceHash: a8cea231c660\n  translator: machine\n---\n\n# Carga de extensiones (Módulos TypeScript/JavaScript)\n\nEste documento cubre cómo el agente de codificación descubre y carga **módulos de extensión** (`.ts`/`.js`) durante el inicio.\n\n**No** cubre las extensiones de manifiesto `gemini-extension.json` (documentadas por separado).\n\n## Qué hace este subsistema\n\nLa carga de extensiones construye una lista de archivos de entrada de módulos, importa cada módulo con Bun, ejecuta su factoría y devuelve:\n\n- definiciones de extensiones cargadas\n- errores de carga por ruta (sin abortar toda la carga)\n- un objeto compartido de runtime de extensiones utilizado posteriormente por `ExtensionRunner`\n\n## Archivos de implementación principales\n\n- `src/extensibility/extensions/loader.ts` — descubrimiento de rutas + importación/ejecución\n- `src/extensibility/extensions/index.ts` — exportaciones públicas\n- `src/extensibility/extensions/runner.ts` — ejecución de runtime/eventos después de la carga\n- `src/discovery/builtin.ts` — proveedor nativo de auto-descubrimiento para módulos de extensión\n- `src/config/settings.ts` — carga la configuración fusionada de `extensions` / `disabledExtensions`\n\n---\n\n## Entradas para la carga de extensiones\n\n### 1) Módulos de extensión nativos auto-descubiertos\n\n`discoverAndLoadExtensions()` primero solicita a los proveedores de descubrimiento elementos con capacidad `extension-module`, luego conserva solo los elementos del proveedor `native`.\n\nUbicaciones nativas efectivas:\n\n- Proyecto: `<cwd>/.xcsh/extensions`\n- Usuario: `~/.xcsh/agent/extensions`\n\nLas rutas raíz provienen del proveedor nativo (`SOURCE_PATHS.native`).\n\nNotas:\n\n- El auto-descubrimiento nativo actualmente se basa en `.xcsh`.\n- El legado `.pi` todavía se acepta en las claves de manifiesto de `package.json` (`pi.extensions`), pero no como raíz nativa aquí.\n\n### 2) Rutas configuradas explícitamente\n\nDespués del auto-descubrimiento, las rutas configuradas se agregan y resuelven.\n\nFuentes de rutas configuradas en la ruta principal de inicio de sesión (`sdk.ts`):\n\n1. Rutas proporcionadas por CLI (`--extension/-e`, y `--hook` también se trata como una ruta de extensión)\n2. Array `extensions` de la configuración (configuración global + proyecto fusionadas)\n\nArchivo de configuración global:\n\n- `~/.xcsh/agent/config.yml` (o directorio de agente personalizado vía `PI_CODING_AGENT_DIR`)\n\nArchivo de configuración del proyecto:\n\n- `<cwd>/.xcsh/settings.json`\n\nEjemplos:\n\n```yaml\n# ~/.xcsh/agent/config.yml\nextensions:\n  - ~/my-exts/safety.ts\n  - ./local/ext-pack\n```\n\n```json\n{\n  \"extensions\": [\"./.xcsh/extensions/my-extra\"]\n}\n```\n\n---\n\n## Controles de habilitación/deshabilitación\n\n### Deshabilitar el descubrimiento\n\n- CLI: `--no-extensions`\n- Opción SDK: `disableExtensionDiscovery`\n\nDivisión de comportamiento:\n\n- SDK: cuando `disableExtensionDiscovery=true`, aún carga `additionalExtensionPaths` vía `loadExtensions()`.\n- La construcción de rutas del CLI (`main.ts`) actualmente limpia las rutas de extensión del CLI cuando se establece `--no-extensions`, por lo que las opciones explícitas `-e/--hook` no se reenvían en ese modo.\n\n### Deshabilitar módulos de extensión específicos\n\nLa configuración `disabledExtensions` filtra por formato de id de extensión:\n\n- `extension-module:<derivedName>`\n\n`derivedName` se basa en la ruta de entrada (`getExtensionNameFromPath`), por ejemplo:\n\n- `/x/foo.ts` -> `foo`\n- `/x/bar/index.ts` -> `bar`\n\nEjemplo:\n\n```yaml\ndisabledExtensions:\n  - extension-module:foo\n```\n\n---\n\n## Resolución de rutas y entradas\n\n### Normalización de rutas\n\nPara rutas configuradas:\n\n1. Normalizar espacios unicode\n2. Expandir `~`\n3. Si es relativa, resolver contra el `cwd` actual\n\n### Si la ruta configurada es un archivo\n\nSe utiliza directamente como candidata de entrada de módulo.\n\n### Si la ruta configurada es un directorio\n\nOrden de resolución:\n\n1. `package.json` en ese directorio con `xcsh.extensions` (o legado `pi.extensions`) -> usar las entradas declaradas\n2. `index.ts`\n3. `index.js`\n4. En caso contrario, escanear un nivel en busca de entradas de extensión:\n   - `*.ts` / `*.js` directos\n   - subdirectorio `index.ts` / `index.js`\n   - subdirectorio `package.json` con `xcsh.extensions` / `pi.extensions`\n\nReglas y restricciones:\n\n- sin descubrimiento recursivo más allá de un nivel de subdirectorio\n- las entradas declaradas en el manifiesto `extensions` se resuelven relativas al directorio del paquete\n- las entradas declaradas se incluyen solo si el archivo existe/el acceso está permitido\n- en pares `*/index.{ts,js}`, TypeScript se prefiere sobre JavaScript\n- los enlaces simbólicos se tratan como archivos/directorios elegibles\n\n### El comportamiento de ignorar difiere según la fuente\n\n- El auto-descubrimiento nativo (`discoverExtensionModulePaths` en los helpers de descubrimiento) usa glob nativo con `gitignore: true` y `hidden: false`.\n- El escaneo de directorios configurados explícitamente en `loader.ts` usa reglas de `readdir` y **no** aplica filtrado por gitignore.\n\n---\n\n## Orden de carga y precedencia\n\n`discoverAndLoadExtensions()` construye una lista ordenada y luego llama a `loadExtensions()`.\n\nOrden:\n\n1. Módulos auto-descubiertos nativos\n2. Rutas configuradas explícitamente (en el orden proporcionado)\n\nEn `sdk.ts`, el orden configurado es:\n\n1. Rutas adicionales del CLI\n2. `extensions` de la configuración\n\nDeduplicación:\n\n- basada en ruta absoluta\n- la primera ruta encontrada prevalece\n- los duplicados posteriores se ignoran\n\nImplicación: si la misma ruta de módulo es tanto auto-descubierta como configurada explícitamente, se carga una sola vez en la primera posición (etapa de auto-descubrimiento).\n\n---\n\n## Importación de módulos y contrato de factoría\n\nCada ruta candidata se carga con importación dinámica:\n\n- `await import(resolvedPath)`\n- la factoría es `module.default ?? module`\n- la factoría debe ser una función (`ExtensionFactory`)\n\nSi la exportación no es una función, esa ruta falla con un error estructurado y la carga continúa.\n\n---\n\n## Manejo de errores y aislamiento\n\n### Durante la carga\n\nPor cada ruta de extensión, los fallos se capturan como `{ path, error }` y no detienen la carga de otras rutas.\n\nCasos comunes:\n\n- fallo de importación / archivo no encontrado\n- exportación de factoría inválida (no es una función)\n- excepción lanzada durante la ejecución de la factoría\n\n### Modelo de aislamiento en tiempo de ejecución\n\n- Las extensiones **no están aisladas en sandbox** (mismo proceso/runtime).\n- Comparten un único `EventBus` y una única instancia de `ExtensionRuntime`.\n- Durante la carga, los métodos de acción del runtime lanzan intencionalmente `ExtensionRuntimeNotInitializedError`; la conexión de acciones ocurre posteriormente en `ExtensionRunner.initialize()`.\n\n### Después de la carga\n\nCuando los eventos se ejecutan a través de `ExtensionRunner`, las excepciones de los manejadores se capturan y se emiten como errores de extensión en lugar de hacer fallar el bucle del runner.\n\n---\n\n## Ejemplos mínimos de estructura a nivel de usuario/proyecto\n\n### Nivel de usuario\n\n```text\n~/.xcsh/agent/\n  config.yml\n  extensions/\n    guardrails.ts\n    audit/\n      index.ts\n```\n\n### Nivel de proyecto\n\n```text\n<repo>/\n  .xcsh/\n    settings.json\n    extensions/\n      checks/\n        package.json\n      lint-gates.ts\n```\n\n`checks/package.json`:\n\n```json\n{\n  \"xcsh\": {\n    \"extensions\": [\"./src/check-a.ts\", \"./src/check-b.js\"]\n  }\n}\n```\n\nClave de manifiesto legada aún aceptada:\n\n```json\n{\n  \"pi\": {\n    \"extensions\": [\"./index.ts\"]\n  }\n}\n```\n",
	"es/extensions/extensions.md": "---\ntitle: Extensiones\ndescription: >-\n  Descripción general del runtime de extensiones que cubre tipos, ciclo de vida\n  del runner, registro y descubrimiento.\nsidebar:\n  order: 1\n  label: Descripción general\ni18n:\n  sourceHash: 14cc16dbd98b\n  translator: machine\n---\n\n# Extensiones\n\nGuía principal para crear extensiones de runtime en `packages/coding-agent`.\n\nEste documento cubre el runtime de extensiones actual en:\n\n- `src/extensibility/extensions/types.ts`\n- `src/extensibility/extensions/runner.ts`\n- `src/extensibility/extensions/wrapper.ts`\n- `src/extensibility/extensions/index.ts`\n- `src/modes/controllers/extension-ui-controller.ts`\n\nPara rutas de descubrimiento y reglas de carga del sistema de archivos, consulte `docs/extension-loading.md`.\n\n## Qué es una extensión\n\nUna extensión es un módulo TS/JS que exporta una fábrica predeterminada:\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nexport default function myExtension(pi: ExtensionAPI) {\n // register handlers/tools/commands/renderers\n}\n```\n\nLas extensiones pueden combinar todo lo siguiente en un solo módulo:\n\n- manejadores de eventos (`pi.on(...)`)\n- herramientas invocables por LLM (`pi.registerTool(...)`)\n- comandos de barra diagonal (`pi.registerCommand(...)`)\n- atajos de teclado e indicadores\n- renderización personalizada de mensajes\n- APIs de inyección de sesión/mensaje (`sendMessage`, `sendUserMessage`, `appendEntry`)\n\n## Modelo de runtime\n\n1. Las extensiones se importan y se ejecutan sus funciones de fábrica.\n2. Durante esa fase de carga, los métodos de registro son válidos; los métodos de acción de runtime aún no están inicializados.\n3. `ExtensionRunner.initialize(...)` conecta acciones/contextos activos para el modo activo.\n4. Los eventos de ciclo de vida de sesión/agente/herramienta se emiten a los manejadores.\n5. Cada ejecución de herramienta se envuelve con intercepción de extensión (`tool_call` / `tool_result`).\n\n```text\nExtension lifecycle (simplified)\n\nload paths\n   │\n   ▼\nimport module + run factory (registration only)\n   │\n   ▼\nExtensionRunner.initialize(mode/session/tool registry)\n   │\n   ├─ emit session/agent events to handlers\n   ├─ wrap tool execution (tool_call/tool_result)\n   └─ expose runtime actions (sendMessage, setActiveTools, ...)\n```\n\nRestricción importante de `loader.ts`:\n\n- llamar a métodos de acción como `pi.sendMessage()` durante la carga de la extensión lanza `ExtensionRuntimeNotInitializedError`\n- registre primero; realice el comportamiento de runtime desde eventos/comandos/herramientas\n\n## Inicio rápido\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\nimport { Type } from \"@sinclair/typebox\";\n\nexport default function (pi: ExtensionAPI) {\n pi.setLabel(\"Safety + Utilities\");\n\n pi.on(\"session_start\", async (_event, ctx) => {\n  ctx.ui.notify(`Extension loaded in ${ctx.cwd}`, \"info\");\n });\n\n pi.on(\"tool_call\", async (event) => {\n  if (event.toolName === \"bash\" && event.input.command?.includes(\"rm -rf\")) {\n   return { block: true, reason: \"Blocked by extension policy\" };\n  }\n });\n\n pi.registerTool({\n  name: \"hello_extension\",\n  label: \"Hello Extension\",\n  description: \"Return a greeting\",\n  parameters: Type.Object({ name: Type.String() }),\n  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {\n   return {\n    content: [{ type: \"text\", text: `Hello, ${params.name}` }],\n    details: { greeted: params.name },\n   };\n  },\n });\n\n pi.registerCommand(\"hello-ext\", {\n  description: \"Show queue state\",\n  handler: async (_args, ctx) => {\n   ctx.ui.notify(`pending=${ctx.hasPendingMessages()}`, \"info\");\n  },\n });\n}\n```\n\n## Superficies de la API de extensión\n\n## 1) Registro y acciones (`ExtensionAPI`)\n\nMétodos principales:\n\n- `on(event, handler)`\n- `registerTool`, `registerCommand`, `registerShortcut`, `registerFlag`\n- `registerMessageRenderer`\n- `sendMessage`, `sendUserMessage`, `appendEntry`\n- `getActiveTools`, `getAllTools`, `setActiveTools`\n- `getSessionName`, `setSessionName`\n- `setModel`, `getThinkingLevel`, `setThinkingLevel`\n- `registerProvider`\n- `events` (bus de eventos compartido)\n\nEn el modo interactivo, los manejadores `input` se ejecutan antes de la verificación automática de título del primer mensaje incorporada. Las extensiones que llaman a `await pi.setSessionName(...)` desde `input` pueden establecer el nombre de sesión persistente e impedir que el título autogenerado predeterminado se ejecute para esa sesión.\n\nTambién expuesto:\n\n- `pi.logger`\n- `pi.typebox`\n- `pi.pi` (exportaciones del paquete)\n\n### Semántica de entrega de mensajes\n\n`pi.sendMessage(message, options)` admite:\n\n- `deliverAs: \"steer\"` (predeterminado) — interrumpe la ejecución actual\n- `deliverAs: \"followUp\"` — en cola para ejecutarse después de la ejecución actual\n- `deliverAs: \"nextTurn\"` — almacenado e inyectado en el siguiente prompt del usuario\n- `triggerTurn: true` — inicia un turno cuando está inactivo (`nextTurn` ignora esto)\n\n`pi.sendUserMessage(content, { deliverAs })` siempre pasa por el flujo de prompt; mientras hace streaming, se pone en cola como steer/follow-up.\n\n## 2) Contexto del manejador (`ExtensionContext`)\n\nLos manejadores y `execute` de herramientas reciben `ctx` con:\n\n- `ui`\n- `hasUI`\n- `cwd`\n- `sessionManager` (solo lectura)\n- `modelRegistry`, `model`\n- `getContextUsage()`\n- `compact(...)`\n- `isIdle()`, `hasPendingMessages()`, `abort()`\n- `shutdown()`\n- `getSystemPrompt()`\n\n## 3) Contexto de comando (`ExtensionCommandContext`)\n\nLos manejadores de comandos también obtienen:\n\n- `waitForIdle()`\n- `newSession(...)`\n- `switchSession(...)`\n- `branch(entryId)`\n- `navigateTree(targetId, { summarize })`\n- `reload()`\n\nUse el contexto de comando para flujos de control de sesión; estos métodos están separados intencionalmente de los manejadores de eventos generales.\n\n## Superficie de eventos (nombres y comportamiento actuales)\n\nLas uniones de eventos canónicas y los tipos de carga útil están en `types.ts`.\n\n### Ciclo de vida de sesión\n\n- `session_start`\n- `session_before_switch` / `session_switch`\n- `session_before_branch` / `session_branch`\n- `session_before_compact` / `session.compacting` / `session_compact`\n- `session_before_tree` / `session_tree`\n- `session_shutdown`\n\nEventos previos cancelables:\n\n- `session_before_switch` → `{ cancel?: boolean }`\n- `session_before_branch` → `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_before_compact` → `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session_before_tree` → `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n\n### Ciclo de vida de prompt y turno\n\n- `input`\n- `before_agent_start`\n- `context`\n- `agent_start` / `agent_end`\n- `turn_start` / `turn_end`\n- `message_start` / `message_update` / `message_end`\n\n### Ciclo de vida de herramienta\n\n- `tool_call` (pre-ejecución, puede bloquear)\n- `tool_result` (post-ejecución, puede modificar content/details/isError)\n- `tool_execution_start` / `tool_execution_update` / `tool_execution_end` (Observabilidad)\n\n`tool_result` es de estilo middleware: los manejadores se ejecutan en orden de extensión y cada uno ve las modificaciones anteriores.\n\n### Señales de confiabilidad/runtime\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### Intercepción de comandos del usuario\n\n- `user_bash` (anular con `{ result }`)\n- `user_python` (anular con `{ result }`)\n\n### `resources_discover`\n\n`resources_discover` existe en los tipos de extensión y en `ExtensionRunner`.\nNota del runtime actual: `ExtensionRunner.emitResourcesDiscover(...)` está implementado, pero no hay sitios de llamada en `AgentSession` que lo invoquen en el código base actual.\n\n## Detalles de creación de herramientas\n\n`registerTool` usa `ToolDefinition` de `types.ts`.\n\nFirma actual de `execute`:\n\n```ts\nexecute(\n toolCallId,\n params,\n signal,\n onUpdate,\n ctx,\n): Promise<AgentToolResult>\n```\n\nPlantilla:\n\n```ts\npi.registerTool({\n name: \"my_tool\",\n label: \"My Tool\",\n description: \"...\",\n parameters: Type.Object({}),\n async execute(_id, _params, signal, onUpdate, ctx) {\n  if (signal?.aborted) {\n   return { content: [{ type: \"text\", text: \"Cancelled\" }] };\n  }\n  onUpdate?.({ content: [{ type: \"text\", text: \"Working...\" }] });\n  return { content: [{ type: \"text\", text: \"Done\" }], details: {} };\n },\n onSession(event, ctx) {\n  // reason: start|switch|branch|tree|shutdown\n },\n renderCall(args, theme) {\n  // optional TUI render\n },\n renderResult(result, options, theme, args) {\n  // optional TUI render\n },\n});\n```\n\n`tool_call`/`tool_result` intercepta todas las herramientas una vez que el registro se envuelve en `sdk.ts`, incluidas las incorporadas y las herramientas personalizadas/de extensión.\n\n## Puntos de integración de la interfaz de usuario\n\n`ctx.ui` implementa la interfaz `ExtensionUIContext`. El soporte difiere según el modo.\n\n### Modo interactivo (`extension-ui-controller.ts`)\n\nCompatible:\n\n- diálogos: `select`, `confirm`, `input`, `editor`\n- notificaciones/estado/texto del editor/entrada de terminal/superposiciones personalizadas\n- listado/carga de temas por nombre (`setTheme` admite nombres de cadena)\n- alternancia de herramientas expandidas\n\nMétodos sin operación actuales en este controlador:\n\n- `setFooter`\n- `setHeader`\n- `setEditorComponent`\n\nTambién tenga en cuenta: `setWidget` actualmente enruta al texto de la línea de estado mediante `setHookWidget(...)`.\n\n### Modo RPC (`rpc-mode.ts`)\n\n`ctx.ui` está respaldado por eventos RPC `extension_ui_request`:\n\n- los métodos de diálogo (`select`, `confirm`, `input`, `editor`) van y vienen a las respuestas del cliente\n- los métodos de disparar y olvidar emiten solicitudes (`notify`, `setStatus`, `setWidget` para matrices de cadenas, `setTitle`, `setEditorText`)\n\nNo compatible/sin operación en la implementación RPC:\n\n- `onTerminalInput`\n- `custom`\n- `setFooter`, `setHeader`, `setEditorComponent`\n- `setWorkingMessage`\n- cambio/carga de temas (`setTheme` devuelve fallo)\n- los controles de expansión de herramientas son inertes\n\n### Rutas de impresión/sin cabecera/subagente\n\nCuando no se suministra contexto de interfaz de usuario al inicio del runner, `ctx.hasUI` es `false` y los métodos son sin operación/con valor predeterminado.\n\n### Modo interactivo en segundo plano\n\nEl modo en segundo plano instala un objeto de contexto de interfaz de usuario no interactivo. En la implementación actual, `ctx.hasUI` puede seguir siendo `true` mientras que los diálogos interactivos devuelven valores predeterminados/comportamiento sin operación.\n\n## Patrones de sesión y estado\n\nPara el estado de extensión duradero:\n\n1. Persistir con `pi.appendEntry(customType, data)`.\n2. Reconstruir el estado desde `ctx.sessionManager.getBranch()` en `session_start`, `session_branch`, `session_tree`.\n3. Mantener los `details` del resultado de la herramienta estructurados cuando el estado deba ser visible/reconstruible desde el historial de resultados de la herramienta.\n\nEjemplo de patrón de reconstrucción:\n\n```ts\npi.on(\"session_start\", async (_event, ctx) => {\n let latest;\n for (const entry of ctx.sessionManager.getBranch()) {\n  if (entry.type === \"custom\" && entry.customType === \"my-state\") {\n   latest = entry.data;\n  }\n }\n // restore from latest\n});\n```\n\n## Puntos de extensión de renderización\n\n## Renderizador de mensajes personalizado\n\n```ts\npi.registerMessageRenderer(\"my-type\", (message, { expanded }, theme) => {\n // return pi-tui Component\n});\n```\n\nUtilizado por la renderización interactiva cuando se muestran mensajes personalizados.\n\n## Renderizador de llamada/resultado de herramienta\n\nProporcione `renderCall` / `renderResult` en las definiciones de `registerTool` para la visualización personalizada de herramientas en TUI.\n\n## Restricciones y errores comunes\n\n- Las acciones de runtime no están disponibles durante la carga de la extensión.\n- Los errores de `tool_call` bloquean la ejecución (fallo cerrado).\n- Los conflictos de nombre de comando con los incorporados se omiten con diagnósticos.\n- Los atajos reservados se ignoran (`ctrl+c`, `ctrl+d`, `ctrl+z`, `ctrl+k`, `ctrl+p`, `ctrl+l`, `ctrl+o`, `ctrl+t`, `ctrl+g`, `shift+tab`, `shift+ctrl+p`, `alt+enter`, `escape`, `enter`).\n- Trate `ctx.reload()` como terminal para el marco del manejador de comandos actual.\n\n## Extensiones vs hooks vs herramientas personalizadas\n\nUse la superficie correcta:\n\n- **Extensiones** (`src/extensibility/extensions/*`): sistema unificado (eventos + herramientas + comandos + renderizadores + registro de proveedor).\n- **Hooks** (`src/extensibility/hooks/*`): API de evento heredada independiente.\n- **Herramientas personalizadas** (`src/extensibility/custom-tools/*`): módulos orientados a herramientas; cuando se cargan junto con extensiones, se adaptan y aún pasan por los wrappers de intercepción de extensión.\n\nSi necesita un paquete que gestione política, herramientas, UX de comandos y renderización de forma conjunta, use extensiones.\n",
	"es/extensions/gemini-manifest-extensions.md": "---\ntitle: Extensiones de manifiesto Gemini\ndescription: >-\n  Formato de extensiones de manifiesto Gemini para compatibilidad entre\n  plataformas de habilidades y agentes.\nsidebar:\n  order: 7\n  label: Manifiesto Gemini\ni18n:\n  sourceHash: 7134165a5f6d\n  translator: machine\n---\n\n# Extensiones de manifiesto Gemini (`gemini-extension.json`)\n\nEste documento describe cómo el agente de codificación descubre y analiza las extensiones de manifiesto de estilo Gemini (`gemini-extension.json`) en la capacidad `extensions`.\n\n**No** cubre la carga de módulos de extensión TypeScript/JavaScript (`extensions/*.ts`, `index.ts`, `package.json xcsh.extensions`), que está documentada en `extension-loading.md`.\n\n## Archivos de implementación\n\n- [`../src/discovery/gemini.ts`](../../packages/coding-agent/src/discovery/gemini.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/capability/extension.ts`](../../packages/coding-agent/src/capability/extension.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/loader.ts`](../../packages/coding-agent/src/extensibility/extensions/loader.ts)\n\n---\n\n## Qué se descubre\n\nEl proveedor Gemini (`id: gemini`, prioridad `60`) registra un cargador `extensions` que analiza dos raíces fijas:\n\n- Usuario: `~/.gemini/extensions`\n- Proyecto: `<cwd>/.gemini/extensions`\n\nLa resolución de rutas se realiza directamente desde `ctx.home` y `ctx.cwd` a través de `getUserPath()` / `getProjectPath()`.\n\nRegla de ámbito importante: la búsqueda del proyecto es **solo cwd**. No recorre los directorios padre.\n\n---\n\n## Reglas de análisis de directorios\n\nPara cada raíz (`~/.gemini/extensions` y `<cwd>/.gemini/extensions`), el proceso de descubrimiento realiza:\n\n1. `readDirEntries(root)`\n2. conserva solo los subdirectorios directos (`entry.isDirectory()`)\n3. para cada hijo `<name>`, intenta leer exactamente:\n   - `<root>/<name>/gemini-extension.json`\n\nNo existe análisis recursivo más allá de un nivel de directorio.\n\n### Directorios ocultos\n\nEl descubrimiento de manifiestos Gemini **no** filtra los nombres de directorio con prefijo de punto. Si existe un subdirectorio oculto que contiene `gemini-extension.json`, se considera.\n\n### Archivos faltantes o ilegibles\n\nSi `gemini-extension.json` no existe o no puede leerse, ese directorio se omite silenciosamente (sin advertencia).\n\n---\n\n## Forma del manifiesto (según implementación)\n\nEl tipo de capacidad define esta forma de manifiesto:\n\n```ts\ninterface ExtensionManifest {\n name?: string;\n description?: string;\n mcpServers?: Record<string, Omit<MCPServer, \"name\" | \"_source\">>;\n tools?: unknown[];\n context?: unknown;\n}\n```\n\nEl comportamiento durante el descubrimiento es deliberadamente flexible:\n\n- Se requiere que el análisis JSON sea exitoso.\n- No hay validación de esquema en tiempo de ejecución para tipos/contenido de campos más allá de la sintaxis JSON.\n- El objeto analizado se almacena como `manifest` en el elemento de capacidad.\n\n### Normalización del nombre\n\n`Extension.name` se establece en:\n\n1. `manifest.name` si no es `null`/`undefined`\n2. de lo contrario, el nombre del directorio de la extensión\n\nNo se aplica ninguna verificación de tipo de cadena aquí.\n\n---\n\n## Materialización en elementos de capacidad\n\nUn manifiesto analizado válido crea un elemento de capacidad `Extension`:\n\n```ts\n{\n name: manifest.name ?? <directory-name>,\n path: <extension-directory>,\n manifest: <parsed-json>,\n level: \"user\" | \"project\",\n _source: {\n  provider: \"gemini\",\n  providerName: \"Gemini CLI\" // adjuntado por el registro de capacidades\n  path: <absolute-manifest-path>,\n  level: \"user\" | \"project\"\n }\n}\n```\n\nNotas:\n\n- `_source.path` se normaliza a una ruta absoluta mediante `createSourceMeta()`.\n- La validación de capacidades a nivel de registro para `extensions` solo verifica la presencia de `name` y `path`.\n- Los elementos internos del manifiesto (`mcpServers`, `tools`, `context`) no se validan durante el descubrimiento.\n\n---\n\n## Manejo de errores y semántica de advertencias\n\n### Con advertencia\n\n- JSON no válido en un archivo de manifiesto:\n  - formato de advertencia: `Invalid JSON in <manifestPath>`\n\n### Sin advertencia (omisión silenciosa)\n\n- directorio `extensions` inexistente\n- el subdirectorio no tiene `gemini-extension.json`\n- archivo de manifiesto ilegible\n- el JSON del manifiesto es sintácticamente válido pero semánticamente inusual/incompleto\n\nEsto significa que se acepta validez parcial: solo el fallo sintáctico de JSON emite una advertencia.\n\n---\n\n## Precedencia y deduplicación con otras fuentes\n\nLa capacidad `extensions` se agrega entre proveedores mediante el registro de capacidades.\n\nProveedores actuales para esta capacidad:\n\n- `native` (`packages/coding-agent/src/discovery/builtin.ts`) prioridad `100`\n- `gemini` (`packages/coding-agent/src/discovery/gemini.ts`) prioridad `60`\n\nLa clave de deduplicación es `ext.name` (`extensionCapability.key = ext => ext.name`).\n\n### Precedencia entre proveedores\n\nEl proveedor de mayor prioridad prevalece ante nombres de extensión duplicados.\n\n- Si tanto `native` como `gemini` emiten el nombre de extensión `foo`, se conserva el elemento nativo.\n- El duplicado de menor prioridad se retiene únicamente en `result.all` con `_shadowed = true`.\n\n### Efectos del orden dentro del mismo proveedor\n\nDado que la deduplicación funciona con el principio de \"primer elemento visto gana\", el orden de los elementos locales del proveedor importa.\n\n- El cargador Gemini añade primero los de **usuario** y luego los de **proyecto**.\n- Por lo tanto, los nombres duplicados entre `~/.gemini/extensions` y `<cwd>/.gemini/extensions` conservan la entrada del usuario y proyectan sombra sobre la entrada del proyecto.\n\nPor el contrario, el proveedor nativo construye el orden de directorio de configuración de manera diferente (`project` antes que `user` en `getConfigDirs()`), por lo que la proyección de sombra interna del proveedor nativo funciona en dirección opuesta.\n\n---\n\n## Resumen del comportamiento usuario vs. proyecto\n\nPara los manifiestos Gemini específicamente:\n\n- Ambas raíces, de usuario y de proyecto, se analizan en cada carga.\n- La raíz del proyecto está fija en `<cwd>/.gemini/extensions` (sin recorrido de directorios ancestros).\n- Los nombres duplicados dentro de la fuente Gemini se resuelven dando prioridad al usuario.\n- Los nombres duplicados frente a proveedores de mayor prioridad (especialmente el nativo) pierden por prioridad.\n\n---\n\n## Límite: metadatos de descubrimiento vs. carga de extensiones en tiempo de ejecución\n\nEl descubrimiento de `gemini-extension.json` actualmente alimenta los metadatos de capacidad (elementos `Extension`). **No** carga directamente módulos de extensión TS/JS ejecutables.\n\nLa carga de módulos en tiempo de ejecución (`discoverAndLoadExtensions()` / `loadExtensions()`) utiliza `extension-modules` y rutas explícitas, y actualmente filtra los módulos autodescubiertos al proveedor `native` únicamente.\n\nImplicación práctica:\n\n- Las extensiones de manifiesto Gemini son detectables como registros de capacidad.\n- Por sí mismas, no son ejecutadas como módulos de extensión en tiempo de ejecución por el pipeline del cargador de extensiones.\n\nEste límite es intencional en la implementación actual y explica por qué el descubrimiento de manifiestos y la carga de módulos ejecutables pueden divergir.\n",
	"es/extensions/marketplace.md": "---\ntitle: Sistema de plugins del Marketplace\ndescription: >-\n  Sistema de plugins del marketplace para descubrir, instalar y gestionar\n  colecciones de plugins seleccionadas.\nsidebar:\n  order: 4\n  label: Marketplace\ni18n:\n  sourceHash: 71d9f8f93a81\n  translator: machine\n---\n\n# Sistema de plugins del Marketplace\n\nEl sistema de marketplace le permite descubrir, instalar y gestionar plugins desde catálogos alojados en Git. Es compatible con el formato de registro de plugins de Claude Code.\n\n## Inicio rápido\n\n```\n/marketplace add anthropics/f5-sales-demo-marketplace\n/marketplace install wordpress.com@f5-sales-demo-marketplace\n```\n\nO simplemente escriba `/marketplace` sin argumentos para abrir el navegador interactivo de plugins.\n\n## Conceptos\n\nUn **marketplace** es un repositorio Git (o directorio local) que contiene un archivo de catálogo en `.xcsh-plugin/marketplace.json`. El catálogo lista los plugins disponibles con sus fuentes, descripciones y metadatos.\n\nUn **plugin** es un directorio que contiene habilidades, comandos, hooks, servidores MCP o servidores LSP. Los plugins se identifican mediante `name@marketplace` (p. ej., `code-review@f5-sales-demo-marketplace`).\n\n**Ámbitos**: los plugins pueden instalarse en dos ámbitos:\n\n- **user** (predeterminado) -- disponible en todos los proyectos, almacenado en `~/.xcsh/plugins/installed_plugins.json`\n- **project** -- disponible únicamente en el proyecto actual, almacenado en `.xcsh/installed_plugins.json`\n\nLas instalaciones con ámbito de proyecto tienen prioridad sobre las instalaciones con ámbito de usuario del mismo plugin.\n\n## Comandos\n\n### Modo interactivo\n\n| Comando | Efecto |\n|---|---|\n| `/marketplace` | Abrir el navegador interactivo de plugins (instalación) |\n\n### Gestión del marketplace\n\n| Comando | Efecto |\n|---|---|\n| `/marketplace add <source>` | Agregar una fuente de marketplace |\n| `/marketplace remove <name>` | Eliminar un marketplace |\n| `/marketplace update [name]` | Volver a obtener catálogo(s); omita el nombre para actualizar todos |\n| `/marketplace list` | Listar los marketplaces configurados |\n\n### Operaciones con plugins\n\n| Comando | Efecto |\n|---|---|\n| `/marketplace discover [marketplace]` | Explorar los plugins disponibles |\n| `/marketplace install [--force] [--scope user\\|project] name@marketplace` | Instalar un plugin |\n| `/marketplace uninstall [--scope user\\|project] name@marketplace` | Desinstalar un plugin |\n| `/marketplace installed` | Listar los plugins de marketplace instalados |\n| `/marketplace upgrade [--scope user\\|project] [name@marketplace]` | Actualizar uno o todos los plugins |\n\n### Equivalentes de línea de comandos\n\nLas mismas operaciones están disponibles desde la línea de comandos:\n\n```\nxcsh plugin marketplace add <source>\nxcsh plugin marketplace remove <name>\nxcsh plugin marketplace update [name]\nxcsh plugin marketplace list\nxcsh plugin discover [marketplace]\nxcsh plugin install --scope project name@marketplace\n```\n\n## Fuentes de marketplace\n\nCuando ejecuta `/marketplace add <source>`, el sistema clasifica la fuente:\n\n| Formato de fuente | Tipo | Ejemplo |\n|---|---|---|\n| `owner/repo` | Abreviatura de GitHub | `anthropics/f5-sales-demo-marketplace` |\n| `https://...*.json` | URL directa del catálogo | `https://example.com/marketplace.json` |\n| `https://...*.git` o `git@...` | Repositorio Git | `https://github.com/org/repo.git` |\n| `./path` o `~/path` o `/path` | Directorio local | `./my-marketplace` |\n\nEl sistema clona el repositorio (o lee el directorio local), localiza `.xcsh-plugin/marketplace.json`, lo valida y almacena el catálogo en caché localmente.\n\n## Formato del catálogo (marketplace.json)\n\nUn catálogo de marketplace se encuentra en `.xcsh-plugin/marketplace.json` en la raíz del repositorio:\n\n```json\n{\n  \"$schema\": \"https://anthropic.com/claude-code/marketplace.schema.json\",\n  \"name\": \"my-marketplace\",\n  \"owner\": {\n    \"name\": \"Your Name\",\n    \"email\": \"you@example.com\"\n  },\n  \"description\": \"A collection of plugins\",\n  \"plugins\": [\n    {\n      \"name\": \"my-plugin\",\n      \"description\": \"What this plugin does\",\n      \"source\": \"./plugins/my-plugin\",\n      \"category\": \"development\",\n      \"homepage\": \"https://github.com/you/my-plugin\"\n    }\n  ]\n}\n```\n\n### Campos obligatorios\n\n| Campo | Descripción |\n|---|---|\n| `name` | Nombre del marketplace. Alfanumérico en minúsculas, guiones y puntos. Debe comenzar y terminar con un carácter alfanumérico. Máximo 64 caracteres. |\n| `owner.name` | Nombre del propietario del marketplace |\n| `plugins` | Array de entradas de plugins |\n\n### Campos de entrada de plugins\n\n| Campo | Obligatorio | Descripción |\n|---|---|---|\n| `name` | sí | Nombre del plugin (mismas reglas que el nombre del marketplace) |\n| `source` | sí | Dónde encontrar el plugin (véase a continuación) |\n| `description` | no | Descripción breve |\n| `version` | no | Cadena de versión |\n| `author` | no | `{ name, email? }` |\n| `homepage` | no | URL |\n| `category` | no | Cadena de categoría (p. ej., `development`, `productivity`, `security`) |\n| `tags` | no | Array de etiquetas de cadena |\n| `strict` | no | Booleano |\n| `commands` | no | Comandos slash proporcionados |\n| `agents` | no | Agentes proporcionados |\n| `hooks` | no | Definiciones de hooks |\n| `mcpServers` | no | Definiciones de servidores MCP |\n| `lspServers` | no | Definiciones de servidores LSP |\n\n### Formatos de fuente de plugins\n\nEl campo `source` admite varios formatos:\n\n**Ruta relativa** (dentro del repositorio del marketplace):\n\n```json\n\"source\": \"./plugins/my-plugin\"\n```\n\n**URL de repositorio Git**:\n\n```json\n\"source\": {\n  \"source\": \"url\",\n  \"url\": \"https://github.com/org/repo.git\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Abreviatura de GitHub**:\n\n```json\n\"source\": {\n  \"source\": \"github\",\n  \"repo\": \"org/repo\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Subdirectorio de Git** (monorepo):\n\n```json\n\"source\": {\n  \"source\": \"git-subdir\",\n  \"url\": \"https://github.com/org/monorepo.git\",\n  \"path\": \"plugins/my-plugin\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Paquete npm**:\n\n```json\n\"source\": {\n  \"source\": \"npm\",\n  \"package\": \"@scope/my-plugin\",\n  \"version\": \"1.0.0\"\n}\n```\n\n## Estructura en disco\n\n```\n~/.xcsh/\n  config/\n    marketplaces.json          # Registro de marketplaces agregados\n  plugins/\n    installed_plugins.json     # Plugins instalados con ámbito de usuario\n    cache/\n      marketplaces/            # Catálogos de marketplace en caché\n      plugins/                 # Directorios de plugins en caché\n\n<project>/.xcsh/\n  installed_plugins.json       # Plugins instalados con ámbito de proyecto\n```\n\n## Reglas de nomenclatura\n\nLos nombres de marketplace y de plugins deben:\n\n- Comenzar y terminar con una letra minúscula o un dígito\n- Contener únicamente letras minúsculas, dígitos, guiones y puntos\n- Tener como máximo 64 caracteres\n\nLos IDs de plugin (`name@marketplace`) deben tener como máximo 128 caracteres en total.\n\nEjemplos válidos: `my-plugin`, `code-review`, `wordpress.com`, `ai-firstify`\nEjemplos no válidos: `-bad`, `bad-`, `.bad`, `Bad`, `under_score`\n",
	"es/extensions/plugin-manager-installer-plumbing.md": "---\ntitle: Gestión de plugins e instalación interna\ndescription: >-\n  Aspectos internos del gestor de plugins que cubren instalación, validación,\n  resolución de dependencias y gestión del ciclo de vida.\nsidebar:\n  order: 5\n  label: Gestor de plugins\ni18n:\n  sourceHash: 9c33e5a2c22a\n  translator: machine\n---\n\n# Gestión de plugins e instalación interna\n\nEste documento describe cómo las operaciones `xcsh plugin` modifican el estado de los plugins en disco y cómo los plugins instalados se convierten en capacidades en tiempo de ejecución (herramientas hoy en día, con resolución de rutas para hooks/comandos disponible).\n\n## Alcance y arquitectura\n\nExisten dos implementaciones de gestión de plugins en el código base:\n\n1. **Ruta activa utilizada por los comandos CLI**: `PluginManager` (`src/extensibility/plugins/manager.ts`)\n2. **Módulo auxiliar heredado**: funciones del instalador (`src/extensibility/plugins/installer.ts`)\n\nLa ejecución del comando `xcsh plugin ...` pasa por `PluginManager`.\n\n`installer.ts` sigue documentando verificaciones de seguridad importantes y comportamiento del sistema de archivos, pero no es la ruta utilizada por `src/commands/plugin.ts` + `src/cli/plugin-cli.ts`.\n\n## Ciclo de vida: desde la invocación CLI hasta la disponibilidad en tiempo de ejecución\n\n```text\nxcsh plugin <action> ...\n  -> src/commands/plugin.ts\n  -> runPluginCommand(...) in src/cli/plugin-cli.ts\n  -> PluginManager method (install/list/uninstall/link/...) \n  -> mutate ~/.xcsh/plugins/{package.json,node_modules,xcsh-plugins.lock.json}\n  -> runtime discovery: discoverAndLoadCustomTools(...)\n  -> getAllPluginToolPaths(cwd)\n  -> custom tool loader imports tool modules\n```\n\n### Puntos de entrada de comandos\n\n- `src/commands/plugin.ts` define el comando/indicadores y reenvía a `runPluginCommand`.\n- `src/cli/plugin-cli.ts` mapea los subcomandos a los métodos de `PluginManager`:\n  - `install`, `uninstall`, `list`, `link`, `doctor`, `features`, `config`, `enable`, `disable`\n- No existe una acción `update` explícita; la actualización se realiza volviendo a ejecutar `install` con una nueva especificación de paquete/versión.\n\n## Modelo en disco\n\nEl estado global de los plugins reside en `~/.xcsh/plugins`:\n\n- `package.json` — manifiesto de dependencias utilizado por `bun install`/`bun uninstall`\n- `node_modules/` — paquetes de plugins instalados o enlaces simbólicos\n- `xcsh-plugins.lock.json` — estado en tiempo de ejecución:\n  - habilitado/deshabilitado por plugin\n  - conjunto de características seleccionadas por plugin\n  - configuraciones de plugin persistidas\n\nLas anulaciones locales del proyecto residen en:\n\n- `<cwd>/.xcsh/plugin-overrides.json`\n\nLas anulaciones son de solo lectura desde la perspectiva del gestor/cargador (no existe ruta de escritura aquí) y pueden deshabilitar plugins o anular características/configuraciones para este proyecto.\n\n## Análisis de especificaciones de plugins e interpretación de metadatos\n\n## Gramática de especificaciones de instalación\n\n`parsePluginSpec` (`parser.ts`) admite:\n\n- `pkg` -> `features: null` (comportamiento predeterminado)\n- `pkg[*]` -> habilitar todas las características del manifiesto\n- `pkg[]` -> no habilitar características opcionales\n- `pkg[a,b]` -> habilitar características con nombre\n- `@scope/pkg@1.2.3[feat]` -> paquete con ámbito + versión con selección explícita de características\n\n`extractPackageName` elimina el sufijo de versión para la búsqueda de rutas en disco después de la instalación.\n\n## Fuente del manifiesto y campos requeridos\n\nEl manifiesto se resuelve como:\n\n1. `package.json.xcsh`\n2. alternativa `package.json.pi`\n3. alternativa `{ version: package.version }`\n\nImplicaciones:\n\n- No existe validación de esquema estricta en el gestor/cargador.\n- Un paquete sin manifiesto `xcsh`/`pi` sigue siendo instalable y listable.\n- La carga de plugins en tiempo de ejecución (`getEnabledPlugins`) omite los paquetes sin manifiesto `xcsh`/`pi`.\n- `manifest.version` siempre se sobreescribe desde la `version` del paquete.\n\nUn JSON malformado en `package.json` es un error grave en el momento de la lectura; una forma de manifiesto malformada puede fallar más adelante solo cuando se consumen campos específicos.\n\n## Flujo de instalación/actualización (`PluginManager.install`)\n\n1. Analizar la sintaxis de corchetes de características de la especificación de instalación.\n2. Validar el nombre del paquete contra la expresión regular + lista de denegación de metacaracteres de shell.\n3. Asegurarse de que `package.json` del plugin exista (`xcsh-plugins`, mapa de dependencias privadas).\n4. Ejecutar `bun install <packageSpec>` en `~/.xcsh/plugins`.\n5. Leer el `node_modules/<name>/package.json` del paquete instalado.\n6. Resolver el manifiesto y calcular `enabledFeatures`:\n   - `[*]`: todas las características declaradas (o `null` si no hay mapa de características)\n   - `[a,b]`: valida que cada característica exista en el mapa de características del manifiesto\n   - `[]`: lista de características vacía\n   - especificación simple: `null` (usar la política de valores predeterminados más adelante en el cargador)\n7. Actualizar el estado en tiempo de ejecución del archivo de bloqueo: `{ version, enabledFeatures, enabled: true }`.\n\n### Semántica de actualización\n\nDado que la actualización está basada en la instalación:\n\n- `xcsh plugin install pkg@newVersion` actualiza la dependencia y la versión del archivo de bloqueo.\n- La configuración existente se preserva; la entrada de estado se sobreescribe para versión/características/habilitado.\n- No existe lógica separada de \"verificar actualizaciones\" ni de migración transaccional.\n\n## Flujo de eliminación (`PluginManager.uninstall`)\n\n1. Validar el nombre del paquete.\n2. Ejecutar `bun uninstall <name>` en el directorio de plugins.\n3. Eliminar el estado en tiempo de ejecución del plugin del archivo de bloqueo:\n   - `config.plugins[name]`\n   - `config.settings[name]`\n\nSi el comando de desinstalación falla, el estado en tiempo de ejecución no se modifica.\n\n## Flujo de listado (`PluginManager.list`)\n\n1. Leer el mapa de dependencias de plugins desde `~/.xcsh/plugins/package.json`.\n2. Cargar la configuración en tiempo de ejecución del archivo de bloqueo (archivo faltante -> valores predeterminados vacíos).\n3. Cargar las anulaciones del proyecto (`<cwd>/.xcsh/plugin-overrides.json`, errores de análisis/lectura -> objeto vacío con advertencia).\n4. Para cada dependencia con un `package.json` resoluble:\n   - construir el registro `InstalledPlugin`\n   - combinar el estado de características/habilitación:\n     - base desde el archivo de bloqueo (o valores predeterminados)\n     - las anulaciones del proyecto pueden reemplazar la selección de características\n     - la lista `disabled` del proyecto enmascara el plugin como deshabilitado\n\nEste es el estado efectivo utilizado por la salida de estado del CLI y las operaciones de configuración/características.\n\n## Flujo de enlace (`PluginManager.link`)\n\n`link` admite el desarrollo local de plugins creando un enlace simbólico de un paquete local en `~/.xcsh/plugins/node_modules/<pkg.name>`.\n\nComportamiento:\n\n1. Resolver `localPath` contra el cwd del gestor.\n2. Requerir `package.json` local y el campo `name`.\n3. Asegurarse de que existan los directorios del plugin.\n4. Para nombres con ámbito, crear el directorio de ámbito.\n5. Eliminar la ruta existente en la ubicación del enlace de destino.\n6. Crear el enlace simbólico.\n7. Agregar una entrada al archivo de bloqueo en tiempo de ejecución habilitada con características predeterminadas (`null`).\n\nAdvertencia: el `PluginManager.link` actual no aplica la verificación de límite de ruta `cwd` presente en el `installer.ts` heredado (`normalizedPath.startsWith(normalizedCwd)`), por lo que la confianza es responsabilidad del llamador.\n\n## Carga en tiempo de ejecución: del plugin instalado a las capacidades invocables\n\n## Puerta de descubrimiento\n\n`getEnabledPlugins(cwd)` (`plugins/loader.ts`) lee:\n\n- manifiesto de dependencias del plugin (`package.json`)\n- estado en tiempo de ejecución del archivo de bloqueo\n- anulaciones del proyecto mediante `getConfigDirPaths(\"plugin-overrides.json\", { user: false, cwd })`\n\nFiltrado:\n\n- omitir si no hay `package.json` del plugin\n- omitir si el manifiesto (`xcsh`/`pi`) está ausente\n- omitir si está deshabilitado globalmente en el archivo de bloqueo\n- omitir si está deshabilitado por el proyecto\n\n## Resolución de rutas de capacidades\n\nPara cada plugin habilitado:\n\n- `resolvePluginToolPaths(plugin)`\n- `resolvePluginHookPaths(plugin)`\n- `resolvePluginCommandPaths(plugin)`\n\nCada resolvedor incluye entradas base más entradas de características:\n\n- lista de características explícita -> solo las características seleccionadas\n- `enabledFeatures === null` -> habilitar las características marcadas como `default: true`\n\nLos archivos faltantes se omiten silenciosamente (protección con `existsSync`).\n\n## Diferencias actuales en el cableado en tiempo de ejecución\n\n- **Las herramientas están conectadas al tiempo de ejecución hoy** mediante `discoverAndLoadCustomTools` (`custom-tools/loader.ts`), que llama a `getAllPluginToolPaths(cwd)`.\n- Las rutas se deduplicarán por ruta absoluta resuelta en el descubrimiento de herramientas personalizadas (conjunto `seen`, la primera ruta gana).\n- **Los resolutores de hooks/comandos existen** y se exportan, pero esta ruta de código actualmente no los conecta a un registro en tiempo de ejecución de la misma manera en que se conectan las herramientas.\n\n## Detalles de gestión de bloqueo/estado\n\n`PluginManager` almacena en caché la configuración en tiempo de ejecución en memoria por instancia (`#runtimeConfig`) y la carga de forma diferida una vez.\n\nComportamiento de carga:\n\n- archivo de bloqueo faltante -> `{ plugins: {}, settings: {} }`\n- fallo de lectura/análisis del archivo de bloqueo -> advertencia + mismos valores predeterminados vacíos\n\nComportamiento de guardado:\n\n- escribe el JSON completo del archivo de bloqueo con formato de sangría en cada mutación\n\nNo existe bloqueo entre procesos ni estrategia de fusión; los escritores concurrentes pueden sobreescribirse mutuamente.\n\n## Verificaciones de seguridad y límites de confianza\n\n## Validación de entrada/paquetes\n\nLa ruta del gestor activo aplica la validación del nombre del paquete:\n\n- expresión regular para especificaciones de paquetes con o sin ámbito (opcionalmente con versión)\n- lista de denegación explícita de metacaracteres de shell (`[;&|`$(){}[]<>\\\\]`)\n\nEsto limita el riesgo de inyección de comandos al invocar `bun install/uninstall`.\n\n## Límite de confianza del sistema de archivos\n\n- El código del plugin se ejecuta en proceso cuando se importan los módulos de herramientas personalizadas; no existe aislamiento.\n- Las rutas relativas del manifiesto se unen al directorio del paquete del plugin y solo se verifica su existencia.\n- El paquete del plugin en sí es código de confianza una vez instalado.\n\n## Verificaciones exclusivas del instalador heredado\n\n`installer.ts` incluye verificaciones adicionales en el momento del enlace que no se replican en `PluginManager.link`:\n\n- la ruta local debe resolverse dentro del cwd del proyecto\n- protecciones adicionales de nombre de paquete/recorrido de ruta para la denominación del destino del enlace simbólico\n\nDado que el CLI utiliza `PluginManager`, estas protecciones de enlace más estrictas no están actualmente en la ruta principal.\n\n## Comportamiento ante fallos, éxito parcial y reversión\n\nEl gestor de plugins no es transaccional.\n\n| Etapa de la operación | Comportamiento ante fallo | Reversión |\n| --- | --- | --- |\n| `bun install` falla | la instalación se interrumpe con stderr | N/A (aún no se han escrito estados) |\n| La instalación tiene éxito, luego falla la validación del manifiesto/características | el comando falla | Sin reversión de desinstalación; la dependencia puede permanecer en `node_modules`/`package.json` |\n| La instalación tiene éxito, luego falla la escritura del archivo de bloqueo | el comando falla | Sin reversión del paquete instalado |\n| `bun uninstall` tiene éxito, falla la escritura del archivo de bloqueo | el comando falla | Paquete eliminado, puede quedar estado en tiempo de ejecución obsoleto |\n| `link` elimina el destino anterior y luego falla la creación del enlace simbólico | el comando falla | Sin restauración del enlace/directorio anterior |\n\nOperativamente, `doctor --fix` puede reparar algunas desviaciones (`bun install`, limpieza de configuración huérfana, limpieza de características inválidas), pero es de mejor esfuerzo.\n\n## Resumen del comportamiento ante manifiestos malformados o faltantes\n\n- Campo `xcsh`/`pi` faltante:\n  - instalación/listado: tolerado (manifiesto mínimo)\n  - descubrimiento de plugins habilitados en tiempo de ejecución: omitido como no-plugin\n- Característica faltante referenciada por la especificación de instalación o `features --set/--enable`: error grave con la lista de características disponibles\n- `plugin-overrides.json` inválido: ignorado con alternativa a `{}` tanto en las rutas del gestor como del cargador\n- Rutas de archivos de herramientas/hooks/comandos faltantes referenciadas por el manifiesto: ignoradas silenciosamente durante la expansión del resolvedor; señaladas como errores solo por `doctor`\n\n## Diferencias de modo y precedencia\n\n- `--dry-run` (install): devuelve un resultado de instalación sintético, sin escrituras en el sistema de archivos/red/estado.\n- `--json`: solo formato de salida, sin cambio de comportamiento.\n- Las anulaciones del proyecto siempre tienen precedencia sobre el archivo de bloqueo global para la vista de características/configuración.\n- La habilitación efectiva es `runtimeEnabled && !projectDisabled`.\n\n## Archivos de implementación\n\n- [`src/commands/plugin.ts`](../../packages/coding-agent/src/commands/plugin.ts) — declaración del comando CLI y mapeo de indicadores\n- [`src/cli/plugin-cli.ts`](../../packages/coding-agent/src/cli/plugin-cli.ts) — despacho de acciones, manejadores de comandos orientados al usuario\n- [`src/extensibility/plugins/manager.ts`](../../packages/coding-agent/src/extensibility/plugins/manager.ts) — implementación activa de instalación/eliminación/listado/enlace/estado/doctor\n- [`src/extensibility/plugins/installer.ts`](../../packages/coding-agent/src/extensibility/plugins/installer.ts) — ayudantes del instalador heredado y verificaciones adicionales de seguridad de enlace\n- [`src/extensibility/plugins/loader.ts`](../../packages/coding-agent/src/extensibility/plugins/loader.ts) — descubrimiento de plugins habilitados y resolución de rutas de herramientas/hooks/comandos\n- [`src/extensibility/plugins/parser.ts`](../../packages/coding-agent/src/extensibility/plugins/parser.ts) — ayudantes de análisis de especificaciones de instalación y nombres de paquetes\n- [`src/extensibility/plugins/types.ts`](../../packages/coding-agent/src/extensibility/plugins/types.ts) — contratos de tipos de manifiesto/tiempo de ejecución/anulación\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts) — cableado en tiempo de ejecución para módulos de herramientas proporcionados por plugins\n",
	"es/extensions/rulebook-matching-pipeline.md": "---\ntitle: Canalización de coincidencia de Rulebook\ndescription: >-\n  Canalización de coincidencia de Rulebook para seleccionar y aplicar conjuntos\n  de instrucciones específicos del contexto en sesiones de agente.\nsidebar:\n  order: 6\n  label: Coincidencia de Rulebook\ni18n:\n  sourceHash: a16a9c565053\n  translator: machine\n---\n\n# Canalización de coincidencia de Rulebook\n\nEste documento describe cómo el agente de codificación descubre reglas a partir de los formatos de configuración compatibles, las normaliza en una única forma `Rule`, resuelve conflictos de precedencia y divide el resultado en:\n\n- **Reglas de Rulebook** (disponibles para el modelo a través del system prompt + URLs `rule://`)\n- **Reglas TTSR** (reglas de interrupción de flujo de viaje en el tiempo)\n\nRefleja la implementación actual, incluyendo semánticas parciales y metadatos que se analizan pero no se aplican.\n\n## Archivos de implementación\n\n- [`../src/capability/rule.ts`](../../packages/coding-agent/src/capability/rule.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/discovery/index.ts`](../../packages/coding-agent/src/discovery/index.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/cursor.ts`](../../packages/coding-agent/src/discovery/cursor.ts)\n- [`../src/discovery/windsurf.ts`](../../packages/coding-agent/src/discovery/windsurf.ts)\n- [`../src/discovery/cline.ts`](../../packages/coding-agent/src/discovery/cline.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/system-prompt.ts`](../../packages/coding-agent/src/system-prompt.ts)\n- [`../src/internal-urls/rule-protocol.ts`](../../packages/coding-agent/src/internal-urls/rule-protocol.ts)\n- [`../src/utils/frontmatter.ts`](../../packages/coding-agent/src/utils/frontmatter.ts)\n\n## 1. Forma canónica de una regla\n\nTodos los proveedores normalizan los archivos fuente en `Rule`:\n\n```ts\ninterface Rule {\n  name: string;\n  path: string;\n  content: string;\n  globs?: string[];\n  alwaysApply?: boolean;\n  description?: string;\n  ttsrTrigger?: string;\n  _source: SourceMeta;\n}\n```\n\nLa identidad de la capacidad es `rule.name` (`ruleCapability.key = rule => rule.name`).\n\nConsecuencia: la precedencia y la deduplicación se basan **únicamente en el nombre**. Dos archivos diferentes con el mismo `name` se consideran la misma regla lógica.\n\n## 2. Fuentes de descubrimiento y normalización\n\n`src/discovery/index.ts` registra proveedores automáticamente. Para `rules`, los proveedores actuales son:\n\n- `native` (prioridad `100`)\n- `cursor` (prioridad `50`)\n- `windsurf` (prioridad `50`)\n- `cline` (prioridad `40`)\n\n### Proveedor nativo (`builtin.ts`)\n\nCarga reglas `.xcsh` desde:\n\n- proyecto: `<cwd>/.xcsh/rules/*.{md,mdc}`\n- usuario: `~/.xcsh/agent/rules/*.{md,mdc}`\n\nNormalización:\n\n- `name` = nombre de archivo sin `.md`/`.mdc`\n- frontmatter analizado mediante `parseFrontmatter`\n- `content` = cuerpo (frontmatter eliminado)\n- `globs`, `alwaysApply`, `description`, `ttsr_trigger` mapeados directamente\n\nAdvertencia importante: `globs` se convierte en `string[] | undefined` sin filtrado de elementos en este proveedor.\n\n### Proveedor Cursor (`cursor.ts`)\n\nCarga desde:\n\n- usuario: `~/.cursor/rules/*.{mdc,md}`\n- proyecto: `<cwd>/.cursor/rules/*.{mdc,md}`\n\nNormalización (`transformMDCRule`):\n\n- `description`: se conserva solo si es una cadena\n- `alwaysApply`: solo se preserva `true` (`false` pasa a ser `undefined`)\n- `globs`: acepta un array (solo elementos de tipo string) o una cadena única\n- `ttsr_trigger`: solo cadena\n- `name` del nombre de archivo sin extensión\n\n### Proveedor Windsurf (`windsurf.ts`)\n\nCarga desde:\n\n- usuario: `~/.codeium/windsurf/memories/global_rules.md` (nombre de regla fijo `global_rules`)\n- proyecto: `<cwd>/.windsurf/rules/*.md`\n\nNormalización:\n\n- `globs`: array de cadenas o cadena única\n- `alwaysApply`, `description` convertidos desde el frontmatter\n- `ttsr_trigger`: solo cadena\n- `name` del nombre de archivo para reglas de proyecto\n\n### Proveedor Cline (`cline.ts`)\n\nBusca hacia arriba desde `cwd` el `.clinerules` más cercano:\n\n- si es directorio: carga `*.md` dentro de él\n- si es archivo: carga el archivo único como una regla con el nombre `clinerules`\n\nNormalización:\n\n- `globs`: array de cadenas o cadena única\n- `alwaysApply`: solo si es booleano\n- `description`: solo cadena\n- `ttsr_trigger`: solo cadena\n\n## 3. Comportamiento de análisis del frontmatter y ambigüedad\n\nTodos los proveedores utilizan `parseFrontmatter` (`utils/frontmatter.ts`) con estas semánticas:\n\n1. El frontmatter se analiza solo cuando el contenido comienza con `---` y tiene un cierre `\\n---`.\n2. El cuerpo se recorta tras la extracción del frontmatter.\n3. Si el análisis YAML falla:\n   - se registra una advertencia,\n   - el analizador recurre al análisis simple de líneas `key: value` (`^(\\w+):\\s*(.*)$`).\n\nConsecuencias de la ambigüedad:\n\n- El analizador de respaldo no admite arrays, objetos anidados, reglas de comillas ni claves con guiones.\n- Los valores de respaldo se convierten en cadenas (por ejemplo, `alwaysApply: true` se convierte en la cadena `\"true\"`), por lo que los proveedores que requieren tipos booleanos o de cadena pueden descartar metadatos.\n- `ttsr_trigger` funciona en modo de respaldo (clave con guion bajo); claves como `thinking-level` no lo harían.\n- Los archivos sin frontmatter válido se siguen cargando como reglas con metadatos vacíos y el cuerpo de contenido completo.\n\n## 4. Precedencia de proveedores y deduplicación\n\n`loadCapability(\"rules\")` (`capability/index.ts`) combina las salidas de los proveedores y luego deduplica por `rule.name`.\n\n### Modelo de precedencia\n\n- Los proveedores se ordenan por prioridad descendente.\n- A igual prioridad, se conserva el orden de registro (`cursor` antes que `windsurf` según `discovery/index.ts`).\n- La deduplicación es de primero en ganar: el primer nombre de regla encontrado se conserva; los elementos con el mismo nombre que aparecen después se marcan como `_shadowed` en `all` y se excluyen de `items`.\n\nEl orden efectivo de proveedores de reglas actualmente es:\n\n1. `native` (100)\n2. `cursor` (50)\n3. `windsurf` (50)\n4. `cline` (40)\n\n### Advertencia sobre el orden interno del proveedor\n\nDentro de un proveedor, el orden de los elementos proviene del resultado de la búsqueda glob de `loadFilesFromDir` más el orden explícito de inserción. Esto es suficientemente determinista para el uso normal, pero no está ordenado explícitamente en el código.\n\nDiferencias notables en el orden de las fuentes:\n\n- `native` agrega primero los directorios de configuración del proyecto y luego los del usuario.\n- `cursor` agrega primero los resultados del usuario y luego los del proyecto.\n- `windsurf` agrega primero `global_rules` del usuario y luego las reglas del proyecto.\n- `cline` carga únicamente la fuente `.clinerules` más cercana.\n\n## 5. División en cubos de Rulebook, siempre-aplicar y TTSR\n\nTras el descubrimiento de reglas en `createAgentSession` (`sdk.ts`):\n\n1. Se analizan todas las reglas descubiertas.\n2. Las reglas con `condition` (clave de frontmatter; `ttsr_trigger` / `ttsrTrigger` se aceptan como alternativa) se registran en `TtsrManager`.\n3. Se construye una lista `rulebookRules` separada con este predicado:\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && !rule.alwaysApply && !!rule.description\n```\n\n4. Se construye una lista `alwaysApplyRules`:\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && rule.alwaysApply === true\n```\n\n### Comportamiento de los cubos\n\n- **Cubo TTSR**: cualquier regla con `condition` (no se requiere descripción). Tiene prioridad sobre los demás cubos.\n- **Cubo siempre-aplicar**: `alwaysApply === true`, no TTSR. El contenido completo se inyecta en el system prompt. Se puede resolver mediante `rule://`.\n- **Cubo Rulebook**: debe tener descripción, no ser TTSR y no ser `alwaysApply`. Se lista en el system prompt por nombre y descripción; el contenido se lee bajo demanda mediante `rule://`.\n- Una regla con tanto `condition` como `alwaysApply` va únicamente a TTSR (TTSR tiene prioridad).\n- Una regla con tanto `alwaysApply` como `description` va únicamente a siempre-aplicar (no al Rulebook).\n\n## 6. Cómo los metadatos afectan las superficies en tiempo de ejecución\n\n### `description`\n\n- Requerido para la inclusión en el Rulebook.\n- Se renderiza en el bloque `<rules>` del system prompt.\n- La ausencia de descripción significa que la regla no está disponible a través de `rule://` y no aparece en la sección de reglas del system prompt.\n\n### `globs`\n\n- Se transfiere en `Rule`.\n- Se renderiza como entradas `<glob>...</glob>` en el bloque de reglas del system prompt.\n- Se expone en el estado de la interfaz de reglas (lista del modo `extensions`).\n- **No se aplica para la coincidencia automática en esta canalización.** No hay un selector de reglas en tiempo de ejecución basado en glob según el archivo actual o el destino de la herramienta.\n\n### `alwaysApply`\n\n- Analizado y preservado por los proveedores.\n- Utilizado en la visualización de la interfaz (etiqueta de activación `\"always\"` en el gestor de estado de extensiones).\n- Utilizado como condición de exclusión de `rulebookRules`.\n- **El contenido completo de la regla se inyecta automáticamente en el system prompt** (antes de la sección de reglas del Rulebook).\n- La regla también es direccionable mediante `rule://<name>` para su relectura.\n\n### `ttsr_trigger`\n\n- Mapeado a `rule.ttsrTrigger`.\n- Si está presente, la regla se enruta al gestor TTSR, no al Rulebook.\n\n## 7. Ruta de inclusión en el system prompt\n\n`buildSystemPromptInternal` recibe tanto `rules` (Rulebook) como `alwaysApplyRules`.\n\nLas reglas de siempre-aplicar se renderizan primero, inyectando su contenido sin procesar directamente en el prompt.\n\nLas reglas del Rulebook se renderizan en una sección `# Rules` con:\n\n- `Read rule://<name> when working in matching domain`\n- El `name`, la `description` y la lista opcional de `<glob>` de cada regla\n\nEsto es orientativo/contextual: el texto del prompt solicita al modelo que lea las reglas aplicables, pero el código no aplica la aplicabilidad de los globs.\n\n## 8. Comportamiento de la URL interna `rule://`\n\n`RuleProtocolHandler` se registra con:\n\n```ts\nnew RuleProtocolHandler({ getRules: () => [...rulebookRules, ...alwaysApplyRules] })\n```\n\nImplicaciones:\n\n- `rule://<name>` se resuelve contra tanto **rulebookRules** como **alwaysApplyRules**.\n- Las reglas solo-TTSR y las reglas sin descripción ni `alwaysApply` no son direccionables mediante `rule://`.\n- La resolución es por coincidencia exacta del nombre.\n- Los nombres desconocidos devuelven un error que lista los nombres de reglas disponibles.\n- El contenido devuelto es el `rule.content` sin procesar (frontmatter eliminado), con tipo de contenido `text/markdown`.\n\n## 9. Semánticas parciales / no aplicadas conocidas\n\n1. Las descripciones de los proveedores mencionan archivos heredados (`.cursorrules`, `.windsurfrules`), pero las rutas de carga actuales no leen realmente esos archivos.\n2. Los metadatos `globs` se exponen al prompt/interfaz, pero no son aplicados por la lógica de selección de reglas.\n3. La selección de reglas para `rule://` incluye las reglas del Rulebook y de siempre-aplicar, pero no las reglas solo-TTSR.\n4. Las advertencias de descubrimiento (`loadCapability(\"rules\").warnings`) se generan, pero `createAgentSession` no las muestra ni las registra actualmente en esta ruta.\n",
	"es/extensions/skills.md": "---\ntitle: Habilidades\ndescription: >-\n  Sistema de habilidades para registrar, descubrir e invocar capacidades\n  especializadas en el agente de codificación.\nsidebar:\n  order: 3\n  label: Habilidades\ni18n:\n  sourceHash: 3e062cc13851\n  translator: machine\n---\n\n# Habilidades\n\nLas habilidades son paquetes de capacidades respaldados por archivos que se descubren al inicio y se exponen al modelo como:\n\n- metadatos ligeros en el prompt del sistema (nombre + descripción)\n- contenido bajo demanda mediante `read skill://...`\n- comandos interactivos opcionales `/skill:<name>`\n\nEste documento cubre el comportamiento actual en tiempo de ejecución en `src/extensibility/skills.ts`, `src/discovery/builtin.ts`, `src/internal-urls/skill-protocol.ts` y `src/discovery/agents-md.ts`.\n\n## Qué es una habilidad en este código base\n\nUna habilidad descubierta se representa como:\n\n- `name`\n- `description`\n- `filePath` (la ruta de `SKILL.md`)\n- `baseDir` (directorio de la habilidad)\n- metadatos de origen (`provider`, `level`, ruta)\n\nEl tiempo de ejecución solo requiere `name` y `path` para la validez. En la práctica, la calidad de coincidencia depende de que `description` sea significativo.\n\n## Estructura requerida y expectativas de SKILL.md\n\n### Estructura de directorios\n\nPara el descubrimiento basado en proveedores (proveedores native/Claude/Codex/Agents/plugin), las habilidades se descubren como **un nivel bajo `skills/`**:\n\n- `<skills-root>/<skill-name>/SKILL.md`\n\nLos patrones anidados como `<skills-root>/group/<skill>/SKILL.md` no son descubiertos por los cargadores de proveedores.\n\nPara `skills.customDirectories`, el escaneo utiliza la misma estructura no recursiva (`*/SKILL.md`).\n\n```text\nProvider-discovered layout (non-recursive under skills/):\n\n<root>/skills/\n  ├─ postgres/\n  │   └─ SKILL.md      ✅ discovered\n  ├─ pdf/\n  │   └─ SKILL.md      ✅ discovered\n  └─ team/\n      └─ internal/\n          └─ SKILL.md  ❌ not discovered by provider loaders\n\nCustom-directory scanning is also non-recursive, so nested paths are ignored unless you point `customDirectories` at that nested parent.\n```\n\n### Frontmatter de `SKILL.md`\n\nCampos de frontmatter compatibles con el tipo de habilidad:\n\n- `name?: string`\n- `description?: string`\n- `globs?: string[]`\n- `alwaysApply?: boolean`\n- las claves adicionales se conservan como metadatos desconocidos\n\nComportamiento actual en tiempo de ejecución:\n\n- `name` toma por defecto el nombre del directorio de la habilidad\n- `description` es requerido para:\n  - el descubrimiento de habilidades del proveedor native `.xcsh` (`requireDescription: true`)\n  - los escaneos de `skills.customDirectories` mediante `scanSkillsFromDir` en `src/discovery/helpers.ts` (no recursivo)\n- los proveedores no nativos pueden cargar habilidades sin descripción\n\n## Proceso de descubrimiento\n\n`discoverSkills()` en `src/extensibility/skills.ts` realiza dos pasadas:\n\n1. **Proveedores de capacidades** mediante `loadCapability(\"skills\")`\n2. **Directorios personalizados** mediante `scanSkillsFromDir(..., { requireDescription: true })` (enumeración de directorios de un nivel)\n\nSi `skills.enabled` es `false`, el descubrimiento no devuelve habilidades.\n\n### Proveedores de habilidades integrados y precedencia\n\nEl orden de los proveedores es primero por prioridad (mayor gana), luego por orden de registro en caso de empate.\n\nProveedores de habilidades registrados actualmente:\n\n1. `native` (prioridad 100) — habilidades de usuario/proyecto `.xcsh` mediante `src/discovery/builtin.ts`\n2. `claude` (prioridad 80)\n3. grupo de prioridad 70 (en orden de registro):\n   - `claude-plugins`\n   - `agents`\n   - `codex`\n\nLa clave de deduplicación es el nombre de la habilidad. El primer elemento con un nombre dado gana.\n\n### Controles de origen y filtrado\n\n`discoverSkills()` aplica estos controles:\n\n- controles de origen: `enableCodexUser`, `enableClaudeUser`, `enableClaudeProject`, `enablePiUser`, `enablePiProject`\n- filtros de glob en el nombre de la habilidad:\n  - `ignoredSkills` (excluir)\n  - `includeSkills` (lista de permitidos de inclusión; vacío significa incluir todo)\n\nEl orden de filtrado es:\n\n1. origen habilitado\n2. no ignorado\n3. incluido (si hay lista de inclusión presente)\n\nPara proveedores distintos de codex/claude/native (por ejemplo `agents`, `claude-plugins`), la habilitación actualmente recurre a: habilitado si **cualquier** control de origen integrado está habilitado.\n\n### Manejo de colisiones y duplicados\n\n- La deduplicación de capacidades ya mantiene la primera habilidad por nombre (proveedor de mayor precedencia)\n- `extensibility/skills.ts` adicionalmente:\n  - deduplica archivos idénticos por `realpath` (seguro para enlaces simbólicos)\n  - emite advertencias de colisión cuando el nombre de una habilidad posterior entra en conflicto\n  - mantiene la API de conveniencia `discoverSkillsFromDir({ dir, source })` como un adaptador delgado sobre `scanSkillsFromDir`\n- Las habilidades de directorios personalizados se fusionan después de las habilidades de los proveedores y siguen el mismo comportamiento de colisión\n\n## Comportamiento de uso en tiempo de ejecución\n\n### Exposición en el prompt del sistema\n\nLa construcción del prompt del sistema (`src/system-prompt.ts`) utiliza las habilidades descubiertas de la siguiente manera:\n\n- si la herramienta `read` está disponible:\n  - incluir la lista de habilidades descubiertas en el prompt\n- de lo contrario:\n  - omitir la lista descubierta\n\nLos subagentes de la herramienta de tareas reciben la lista de habilidades descubiertas/proporcionadas de la sesión mediante la creación normal de sesión; no existe anulación de fijación de habilidades por tarea.\n\n### Comandos interactivos `/skill:<name>`\n\nSi `skills.enableSkillCommands` es true, el modo interactivo registra un comando slash por cada habilidad descubierta.\n\nComportamiento de `/skill:<name> [args]`:\n\n- lee el archivo de la habilidad directamente desde `filePath`\n- elimina el frontmatter\n- inyecta el cuerpo de la habilidad como un mensaje personalizado de seguimiento\n- agrega metadatos (`Skill: <path>`, `User: <args>` opcional)\n\n## Comportamiento de URL `skill://`\n\n`src/internal-urls/skill-protocol.ts` admite:\n\n- `skill://<name>` → resuelve al `SKILL.md` de esa habilidad\n- `skill://<name>/<relative-path>` → resuelve dentro del directorio de esa habilidad\n\n```text\nskill:// URL resolution\n\nskill://pdf\n  -> <pdf-base>/SKILL.md\n\nskill://pdf/references/tables.md\n  -> <pdf-base>/references/tables.md\n\nGuards:\n- reject absolute paths\n- reject `..` traversal\n- reject any resolved path escaping <pdf-base>\n```\n\nDetalles de resolución:\n\n- el nombre de la habilidad debe coincidir exactamente\n- las rutas relativas se decodifican con URL\n- las rutas absolutas son rechazadas\n- el traversal de rutas (`..`) es rechazado\n- la ruta resuelta debe permanecer dentro de `baseDir`\n- los archivos faltantes devuelven un error explícito de `File not found`\n\nTipo de contenido:\n\n- `.md` => `text/markdown`\n- todo lo demás => `text/plain`\n\nNo se realiza búsqueda de reserva para activos faltantes.\n\n## Habilidades vs XCSH.md, comandos, herramientas, hooks\n\n### Habilidades vs XCSH.md\n\n- **Habilidades**: paquetes de capacidades opcionales y con nombre seleccionados por el contexto de la tarea o solicitados explícitamente\n- **XCSH.md/archivos de contexto**: archivos de instrucciones persistentes cargados como capacidad de archivo de contexto y fusionados por reglas de nivel/profundidad\n\n`src/discovery/agents-md.ts` recorre específicamente los directorios ancestros desde `cwd` para descubrir archivos `XCSH.md` independientes (hasta una profundidad de 20), excluyendo segmentos de directorios ocultos.\n\n### Habilidades vs comandos slash\n\n- **Habilidades**: contenido de conocimiento/flujo de trabajo legible por el modelo\n- **Comandos slash**: puntos de entrada de comandos invocados por el usuario\n- `/skill:<name>` es un envoltorio de conveniencia que inyecta texto de la habilidad; no cambia la semántica de descubrimiento de habilidades\n\n### Habilidades vs herramientas personalizadas\n\n- **Habilidades**: contenido de documentación/flujo de trabajo cargado a través del contexto del prompt y `read`\n- **Herramientas personalizadas**: APIs de herramientas ejecutables invocables por el modelo con esquemas y efectos secundarios en tiempo de ejecución\n\n### Habilidades vs hooks\n\n- **Habilidades**: contenido pasivo\n- **Hooks**: interceptores de tiempo de ejecución basados en eventos que pueden bloquear/modificar el comportamiento durante la ejecución\n\n## Guía práctica de autoría vinculada a la lógica de descubrimiento\n\n- Coloque cada habilidad en su propio directorio: `<skills-root>/<skill-name>/SKILL.md`\n- Incluya siempre frontmatter explícito de `name` y `description`\n- Mantenga los activos referenciados bajo el mismo directorio de la habilidad y acceda a ellos con `skill://<name>/...`\n- Para taxonomía anidada (`team/domain/skill`), apunte `skills.customDirectories` al directorio padre anidado; el escaneo en sí permanece no recursivo\n- Evite nombres de habilidades duplicados entre fuentes; la primera coincidencia gana por precedencia del proveedor\n",
	"es/index.md": "---\ntitle: Documentación de xcsh\ndescription: >-\n  CLI de desarrollo impulsada por IA con agente de codificación TypeScript y\n  capa nativa en Rust para sesiones de larga duración, soporte MCP y empaquetado\n  de plataforma.\nsidebar:\n  order: 0\n  label: Descripción general\ni18n:\n  sourceHash: b9288f42bf46\n  translator: machine\n---\n\nxcsh es una CLI de desarrollo impulsada por IA con un agente de codificación en TypeScript y una\ncapa nativa en Rust (`pi-natives`). Extiende la línea de código abierto\n[`badlogic/pi-mono`](https://github.com/badlogic/pi-mono) con un\nruntime reforzado, sesiones de larga duración con navegación en árbol y compactación,\nuna herramienta Python IPython, soporte completo de MCP, un sistema de habilidades y\nempaquetado de plataforma dirigido a Linux, macOS y Windows.\n\n## Por dónde empezar\n\n- **[Contextos F5 XC](/runtime-tools/context-command)** — conectarse a tenants de F5 Distributed Cloud.\n  Crear contextos, alternar entre ellos, gestionar espacios de nombres y credenciales.\n- **Configuración** — cómo xcsh descubre, resuelve y organiza por capas la configuración.\n- **Runtime y herramientas** — los runtimes de bash / notebook / herramienta resolve y la\n  superficie de comandos con barra diagonal.\n- **Sesiones** — registro de entradas de solo adición, navegación en árbol, compactación y el\n  sistema de memoria autónomo.\n- **Nativos (Rust)** — arquitectura del addon N-API `pi-natives` que\n  potencia shell / PTY / media / búsqueda.\n- **MCP** — configuración, aspectos internos del protocolo, ciclo de vida del runtime y cómo\n  crear servidores y herramientas.\n- **Extensiones, habilidades y plugins** — autoría, carga, reglas de coincidencia, el\n  marketplace y el instalador de plugins.\n- **Proveedores y modelos** — configuración de modelos, aspectos internos del streaming y el\n  runtime de Python / IPython.\n- **TUI** — temas, el comando `/tree` y hooks de integración para\n  extensiones y herramientas personalizadas.\n\n## Cómo está organizado este conjunto de documentación\n\nCada grupo de nivel superior en la barra lateral corresponde a un subsistema del agente. Dentro\nde un grupo, las páginas van desde \"descripción general\" hasta \"aspectos internos\", de modo que pueda dejar de leer\ncuando tenga suficiente contexto para la tarea que tiene entre manos.\n",
	"es/mcp/mcp-config.md": "---\ntitle: Configuración de MCP\ndescription: >-\n  Configuración, validación y gestión de servidores MCP para el entorno de\n  ejecución del agente de programación.\nsidebar:\n  order: 1\n  label: Configuración\ni18n:\n  sourceHash: ef8b49458ce9\n  translator: machine\n---\n\n# Configuración de MCP en OMP\n\nEsta guía explica cómo agregar, editar y validar servidores MCP para el agente de programación de OMP.\n\nFuente de referencia en el código:\n\n- Tipos de configuración del entorno de ejecución: `packages/coding-agent/src/mcp/types.ts`\n- Escritor de configuración: `packages/coding-agent/src/mcp/config-writer.ts`\n- Cargador + validación: `packages/coding-agent/src/mcp/config.ts`\n- Descubrimiento de `mcp.json` independiente: `packages/coding-agent/src/discovery/mcp-json.ts`\n- Esquema: `packages/coding-agent/src/config/mcp-schema.json`\n\n## Ubicaciones de configuración preferidas\n\nOMP puede descubrir servidores MCP desde múltiples herramientas (`.claude/`, `.cursor/`, `.vscode/`, `opencode.json`, y más), pero para la configuración nativa de OMP normalmente debería usar uno de estos archivos:\n\n- Proyecto: `.xcsh/mcp.json`\n- Usuario: `~/.xcsh/mcp.json`\n\nOMP también acepta archivos independientes de respaldo en la raíz del proyecto:\n\n- `mcp.json`\n- `.mcp.json`\n\nUse `.xcsh/mcp.json` cuando desee que OMP sea el propietario de la configuración. Use `mcp.json` / `.mcp.json` en la raíz solo cuando desee un archivo de respaldo portable que otros clientes MCP también puedan leer.\n\n## Agregar una referencia al esquema\n\nAgregue esta línea al inicio del archivo para obtener autocompletado y validación en el editor:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {}\n}\n```\n\nOMP ahora escribe esto automáticamente cuando `/mcp add`, `/mcp enable`, `/mcp disable`, `/mcp reauth` u otros flujos de escritura de configuración crean o actualizan un archivo MCP gestionado por OMP.\n\n## Estructura del archivo\n\nOMP soporta esta estructura de nivel superior:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"server-name\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"some-mcp-server\"]\n    }\n  },\n  \"disabledServers\": [\"server-name\"]\n}\n```\n\nClaves de nivel superior:\n\n- `$schema` — URL opcional del JSON Schema para herramientas\n- `mcpServers` — mapa de nombre de servidor a configuración del servidor\n- `disabledServers` — lista de exclusión a nivel de usuario utilizada para desactivar servidores descubiertos por nombre\n\nLos nombres de servidor deben coincidir con `^[a-zA-Z0-9_.-]{1,100}$`.\n\n## Campos de servidor soportados\n\nCampos compartidos para todos los transportes:\n\n- `enabled?: boolean` — omitir este servidor cuando es `false`\n- `timeout?: number` — tiempo de espera de conexión en milisegundos\n- `auth?: { ... }` — metadatos de autenticación utilizados por OMP para flujos de OAuth/API-key\n- `oauth?: { ... }` — configuración explícita de cliente OAuth utilizada durante la autenticación/reautenticación\n\n### Transporte `stdio`\n\n`stdio` es el valor predeterminado cuando se omite `type`.\n\nRequerido:\n\n- `command: string`\n\nOpcional:\n\n- `type?: \"stdio\"`\n- `args?: string[]`\n- `env?: Record<string, string>`\n- `cwd?: string`\n\nEjemplo:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/Users/alice/projects\",\n        \"/Users/alice/Documents\"\n      ]\n    }\n  }\n}\n```\n\nEsto sigue el paquete oficial del servidor MCP de sistema de archivos (`@modelcontextprotocol/server-filesystem`).\n\n### Transporte `http`\n\nRequerido:\n\n- `type: \"http\"`\n- `url: string`\n\nOpcional:\n\n- `headers?: Record<string, string>`\n\nEjemplo:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\nEsto coincide con el endpoint del servidor MCP alojado de GitHub.\n\n### Transporte `sse`\n\nRequerido:\n\n- `type: \"sse\"`\n- `url: string`\n\nOpcional:\n\n- `headers?: Record<string, string>`\n\nEjemplo:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"legacy-remote\": {\n      \"type\": \"sse\",\n      \"url\": \"https://example.com/mcp/sse\"\n    }\n  }\n}\n```\n\n`sse` todavía es soportado por compatibilidad, pero la especificación MCP ahora prefiere Streamable HTTP (`type: \"http\"`) para nuevos servidores.\n\n## Campos de autenticación\n\nOMP entiende dos objetos relacionados con la autenticación.\n\n### `auth`\n\n```json\n{\n  \"type\": \"oauth\" | \"apikey\",\n  \"credentialId\": \"optional-stored-credential-id\",\n  \"tokenUrl\": \"optional-token-endpoint\",\n  \"clientId\": \"optional-client-id\",\n  \"clientSecret\": \"optional-client-secret\"\n}\n```\n\nUse esto cuando OMP deba recordar cómo rehidratar las credenciales de un servidor.\n\n### `oauth`\n\n```json\n{\n  \"clientId\": \"...\",\n  \"clientSecret\": \"...\",\n  \"redirectUri\": \"...\",\n  \"callbackPort\": 3334,\n  \"callbackPath\": \"/oauth/callback\"\n}\n```\n\nUse esto cuando el servidor MCP requiera configuración explícita de cliente OAuth.\n\nSlack es el ejemplo actual más claro. El servidor MCP de Slack está alojado en `https://mcp.slack.com/mcp`, utiliza Streamable HTTP y requiere OAuth confidencial con las credenciales de cliente de su aplicación de Slack.\n\nEjemplo:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\nEndpoints relevantes de Slack según la documentación de Slack:\n\n- Endpoint MCP: `https://mcp.slack.com/mcp`\n- Endpoint de autorización: `https://slack.com/oauth/v2_user/authorize`\n- Endpoint de token: `https://slack.com/api/oauth.v2.user.access`\n\n## Ejemplos comunes para copiar y pegar\n\n### Servidor de sistema de archivos vía stdio\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/absolute/path/one\",\n        \"/absolute/path/two\"\n      ]\n    }\n  }\n}\n```\n\n### Servidor alojado de GitHub vía HTTP\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\n### Servidor local de GitHub vía Docker\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"-i\",\n        \"--rm\",\n        \"-e\",\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\",\n        \"ghcr.io/github/github-mcp-server\"\n      ],\n      \"env\": {\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n      }\n    }\n  }\n}\n```\n\nEsto coincide con la imagen Docker oficial local de GitHub `ghcr.io/github/github-mcp-server`.\n\n### Servidor alojado de Slack vía OAuth\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\n## Secretos y resolución de variables\n\nEsta es la parte que normalmente causa confusión.\n\n### En `.xcsh/mcp.json` y `~/.xcsh/mcp.json`\n\nAntes de que OMP lance un servidor o realice una solicitud HTTP, resuelve los valores de `env` y `headers` de la siguiente manera:\n\n1. Si un valor comienza con `!`, OMP lo ejecuta como un comando de shell y utiliza la salida estándar recortada.\n2. De lo contrario, OMP primero verifica si el valor coincide con el nombre de una variable de entorno.\n3. Si esa variable de entorno no está configurada, OMP utiliza la cadena literalmente.\n\nEjemplos:\n\n```json\n{\n  \"env\": {\n    \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n  },\n  \"headers\": {\n    \"X-MCP-Insiders\": \"true\"\n  }\n}\n```\n\nEsto significa que lo siguiente es válido y conveniente para secretos locales:\n\n- `\"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"` → copiar del entorno de shell actual\n- `\"Authorization\": \"Bearer hardcoded-token\"` → usar el valor literal\n- `\"Authorization\": \"!printf 'Bearer %s' \\\"$GITHUB_TOKEN\\\"\"` → construir el encabezado a partir de un comando\n\n### En `mcp.json` y `.mcp.json` en la raíz\n\nEl cargador de respaldo independiente también expande `${VAR}` y `${VAR:-default}` dentro de las cadenas durante el descubrimiento.\n\nEjemplo:\n\n```json\n{\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\",\n      \"headers\": {\n        \"Authorization\": \"Bearer ${GITHUB_TOKEN}\"\n      }\n    }\n  }\n}\n```\n\nSi desea el comportamiento menos sorpresivo de OMP, prefiera `.xcsh/mcp.json` y use valores explícitos de env/header.\n\n## `disabledServers`\n\n`disabledServers` es principalmente útil en el archivo de configuración de usuario (`~/.xcsh/mcp.json`) cuando un servidor es descubierto desde otra fuente y desea que OMP lo ignore sin editar la configuración de esa otra herramienta.\n\nEjemplo:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"disabledServers\": [\"github\", \"slack\"]\n}\n```\n\n## `/mcp add` vs editar el JSON directamente\n\nUse `/mcp add` cuando desee una configuración guiada.\n\nUse la edición directa del JSON cuando:\n\n- necesite un transporte u opción de autenticación que el asistente aún no solicita\n- desee pegar una definición de servidor desde otro cliente MCP\n- desee validación respaldada por esquema en su editor\n\nDespués de editar, use:\n\n- `/mcp reload` para redescubrir y reconectar servidores en la sesión actual\n- `/mcp list` para ver de qué archivo de configuración proviene un servidor\n- `/mcp test <name>` para probar un servidor individual\n\n## Reglas de validación que OMP aplica\n\nDesde `validateServerConfig()` en `packages/coding-agent/src/mcp/config.ts`:\n\n- `stdio` requiere `command`\n- `http` y `sse` requieren `url`\n- un servidor no puede configurar tanto `command` como `url`\n- los valores de `type` desconocidos son rechazados\n\nImplicaciones prácticas:\n\n- Omitir `type` significa `stdio`\n- Si pega una configuración de servidor remoto y olvida `\"type\": \"http\"`, OMP lo tratará como `stdio` y se quejará de que falta `command`\n- `sse` sigue siendo válido por compatibilidad, pero los nuevos servidores alojados normalmente deberían configurarse como `http`\n\n## Descubrimiento y precedencia\n\nOMP no fusiona definiciones de servidor duplicadas entre archivos. Los proveedores de descubrimiento tienen prioridad, y la definición de mayor prioridad prevalece.\n\nEn la práctica:\n\n- prefiera `.xcsh/mcp.json` o `~/.xcsh/mcp.json` cuando desee una anulación específica de OMP\n- mantenga los nombres de servidor únicos entre herramientas cuando sea posible\n- use `disabledServers` en la configuración de usuario cuando una configuración de terceros siga reintroduciendo un servidor que no desea\n\n## Solución de problemas\n\n### `Server \"name\": stdio server requires \"command\" field`\n\nProbablemente omitió `type: \"http\"` en un servidor remoto.\n\n### `Server \"name\": both \"command\" and \"url\" are set`\n\nElija un transporte. OMP trata `command` como stdio y `url` como http/sse.\n\n### `/mcp add` funcionó pero el servidor aún no se conecta\n\nEl JSON es válido, pero el servidor puede seguir siendo inaccesible. Use `/mcp test <name>` y verifique si:\n\n- el binario o la imagen Docker existe\n- las variables de entorno requeridas están configuradas\n- la URL remota es accesible\n- el token OAuth o API es válido\n\n### El servidor existe en la configuración de otra herramienta pero no en OMP\n\nEjecute `/mcp list`. OMP descubre muchos archivos MCP de terceros, pero la carga a nivel de proyecto también puede deshabilitarse mediante la configuración `mcp.enableProjectConfig`.\n\n## Referencias\n\n- Especificación de transporte MCP: <https://modelcontextprotocol.io/specification/2025-03-26/basic/transports>\n- Paquete del servidor de sistema de archivos: <https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem>\n- Servidor MCP de GitHub: <https://github.com/github/github-mcp-server>\n- Documentación del servidor MCP de Slack: <https://docs.slack.dev/ai/slack-mcp-server/>\n",
	"es/mcp/mcp-protocol-transports.md": "---\ntitle: Protocolo MCP e Internos de Transporte\ndescription: >-\n  MCP protocol implementation with stdio, SSE, and streamable HTTP transport\n  layers.\nsidebar:\n  order: 2\n  label: Protocolo y transportes\ni18n:\n  sourceHash: 48632064dd00\n  translator: machine\n---\n\n# Protocolo MCP e Internos de Transporte\n\nEste documento describe cómo coding-agent implementa la mensajería JSON-RPC de MCP y cómo las responsabilidades del protocolo se separan de las responsabilidades de transporte.\n\n## Alcance\n\nCubre:\n\n- Flujo de solicitud/respuesta y notificaciones JSON-RPC\n- Correlación de solicitudes y ciclo de vida para transportes stdio y HTTP/SSE\n- Comportamiento de timeout y cancelación\n- Propagación de errores y manejo de payloads malformados\n- Límites de selección de transporte (`stdio` vs `http`/`sse`)\n- Qué responsabilidades de reconexión/reintento son a nivel de transporte vs a nivel de manager\n\nNo cubre la experiencia de usuario para autoría de extensiones ni la interfaz de comandos.\n\n## Archivos de implementación\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/transports/stdio.ts`](../../packages/coding-agent/src/mcp/transports/stdio.ts)\n- [`src/mcp/transports/http.ts`](../../packages/coding-agent/src/mcp/transports/http.ts)\n- [`src/mcp/transports/index.ts`](../../packages/coding-agent/src/mcp/transports/index.ts)\n- [`src/mcp/json-rpc.ts`](../../packages/coding-agent/src/mcp/json-rpc.ts)\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n\n## Límites entre capas\n\n### Capa de protocolo (JSON-RPC + métodos MCP)\n\n- Las formas de los mensajes se definen en `types.ts` (`JsonRpcRequest`, `JsonRpcNotification`, `JsonRpcResponse`, `JsonRpcMessage`).\n- La lógica del cliente MCP (`client.ts`) decide el orden de los métodos y el handshake de sesión:\n  1. Solicitud `initialize`\n  2. Notificación `notifications/initialized`\n  3. Llamadas a métodos como `tools/list`, `tools/call`\n\n### Capa de transporte (`MCPTransport`)\n\n`MCPTransport` abstrae la entrega y el ciclo de vida:\n\n- `request(method, params, options?) -> Promise<T>`\n- `notify(method, params?) -> Promise<void>`\n- `close()`\n- `connected`\n- callbacks opcionales: `onClose`, `onError`, `onNotification`\n\nLas implementaciones de transporte son responsables del enmarcado y los detalles de E/S:\n\n- `StdioTransport`: JSON delimitado por saltos de línea sobre stdio de subproceso\n- `HttpTransport`: JSON-RPC sobre HTTP POST, con respuestas/escucha SSE opcionales\n\n### Advertencia importante actual\n\nLos callbacks de transporte (`onClose`, `onError`, `onNotification`) están implementados, pero los flujos actuales de `MCPClient`/`MCPManager` no conectan la lógica de reconexión a estos callbacks. Las notificaciones solo se consumen si el llamador registra manejadores.\n\n## Selección de transporte\n\n`client.ts:createTransport()` elige el transporte según la configuración:\n\n- `type` omitido o `\"stdio\"` -> `createStdioTransport`\n- `\"http\"` o `\"sse\"` -> `createHttpTransport`\n\n`\"sse\"` se trata como una variante de transporte HTTP (misma clase), no como una implementación de transporte separada.\n\n## Flujo de mensajes JSON-RPC y correlación\n\n## IDs de solicitud\n\nCada transporte genera IDs por solicitud (cadena de `Math.random` + timestamp). Los IDs son tokens de correlación locales al transporte.\n\n## Ruta de correlación Stdio\n\n- La solicitud saliente se serializa como un objeto JSON + `\\n`.\n- `#pendingRequests: Map<id, {resolve,reject}>` almacena las solicitudes en tránsito.\n- El bucle de lectura parsea JSONL desde stdout y llama a `#handleMessage`.\n- Si el mensaje entrante tiene un `id` coincidente, la solicitud se resuelve/rechaza.\n- Si el mensaje entrante tiene `method` y no tiene `id`, se trata como notificación y se envía a `onNotification`.\n\nLos IDs desconocidos se ignoran (sin rechazo, sin callback de error).\n\n## Ruta de correlación HTTP\n\n- La solicitud saliente es un HTTP `POST` con cuerpo JSON e `id` generado.\n- Ruta de respuesta no-SSE: parsea una respuesta JSON-RPC y retorna `result`/lanza error en `error`.\n- Ruta de respuesta SSE (`Content-Type: text/event-stream`): transmite eventos por stream, retorna el primer mensaje cuyo `id` coincida con el ID de solicitud esperado y tenga `result` o `error`.\n- Los mensajes SSE con `method` y sin `id` se tratan como notificaciones.\n\nSi el stream SSE termina antes de la respuesta coincidente, la solicitud falla con `No response received for request ID ...`.\n\n## Notificaciones\n\nEl cliente emite notificaciones JSON-RPC mediante `transport.notify(...)`.\n\n- Stdio: escribe la trama de notificación en stdin (`jsonrpc`, `method`, `params` opcional) más salto de línea.\n- HTTP: envía cuerpo POST sin `id`; el éxito acepta `2xx` o `202 Accepted`.\n\nLas notificaciones iniciadas por el servidor solo se exponen a través de `onNotification` del transporte; no hay un suscriptor global predeterminado en manager/client.\n\n## Internos del transporte Stdio\n\n## Ciclo de vida y transiciones de estado\n\n- Inicial: `connected=false`, `process=null`, mapa de pendientes vacío\n- `connect()`:\n  - genera subproceso con command/args/env/cwd configurados\n  - marca como conectado\n  - inicia bucle de lectura de stdout (`readJsonl`)\n  - inicia bucle de stderr (lectura/descarte; actualmente silencioso)\n- `close()`:\n  - marca como desconectado\n  - rechaza todas las solicitudes pendientes (`Transport closed`)\n  - mata el subproceso\n  - espera el cierre del bucle de lectura\n  - emite `onClose`\n\nSi el bucle de lectura termina inesperadamente, `finally` dispara `#handleClose()` que realiza el mismo rechazo de solicitudes pendientes y callback de cierre.\n\n## Timeout y cancelación\n\nPor solicitud:\n\n- timeout por defecto `config.timeout ?? 30000`\n- `AbortSignal` opcional del llamador\n- tanto abort como timeout rechazan la promesa pendiente y limpian la entrada del mapa\n\nLa cancelación es solo local: el transporte no envía notificación de cancelación a nivel de protocolo al servidor.\n\n## Manejo de payloads malformados\n\nEn el bucle de lectura:\n\n- cada línea JSONL parseada se pasa a `#handleMessage` en `try/catch`\n- las excepciones de manejo de mensajes malformados/inválidos se descartan (comentario `Skip malformed lines`)\n- el bucle continúa, por lo que un mensaje incorrecto no mata la conexión\n\nSi el parser del stream subyacente lanza una excepción, se invoca `onError` (cuando aún está conectado), luego la conexión se cierra.\n\n## Comportamiento de desconexión/fallo\n\nCuando el proceso termina o el stream se cierra:\n\n- todas las solicitudes en tránsito se rechazan con `Transport closed`\n- no hay reinicio ni reconexión automática\n- las capas superiores deben reconectar creando un nuevo transporte\n\n## Notas sobre contrapresión/streaming\n\n- Las escrituras salientes usan `stdin.write()` + `flush()` sin esperar semánticas de drain.\n- No hay cola explícita ni gestión de high-watermark en el transporte.\n- El procesamiento entrante es dirigido por stream (`for await` sobre `readJsonl`), un mensaje parseado a la vez.\n\n## Internos del transporte HTTP/SSE\n\n## Ciclo de vida y semánticas de conexión\n\nEl transporte HTTP tiene estado de conexión lógico, pero la ruta de solicitud es stateless por llamada HTTP:\n\n- `connect()` establece `connected=true` (sin handshake de socket/sesión)\n- seguimiento opcional de sesión del servidor mediante header `Mcp-Session-Id`\n- `close()` opcionalmente envía `DELETE` con `Mcp-Session-Id`, aborta el listener SSE, emite `onClose`\n\nPor lo tanto, `connected` significa \"transporte utilizable\", no \"stream persistente establecido\".\n\n## Comportamiento del header de sesión\n\n- En la respuesta POST, si el header `Mcp-Session-Id` está presente, el transporte lo almacena.\n- Las solicitudes/notificaciones subsiguientes incluyen `Mcp-Session-Id`.\n- `close()` intenta terminar la sesión del servidor con HTTP DELETE; los fallos de terminación se ignoran.\n\n## Timeout y cancelación\n\nPara tanto `request()` como `notify()`:\n\n- timeout usa `AbortController` (`config.timeout ?? 30000`)\n- la señal externa, si se proporciona, se fusiona mediante `AbortSignal.any([...])`\n- el manejo de AbortError distingue entre abort del llamador vs timeout\n\nErrores lanzados:\n\n- timeout: `Request timeout after ...ms` (o `SSE response timeout ...`, `Notify timeout ...`)\n- abort del llamador: el AbortError original se relanza cuando la señal externa ya está abortada\n\n## Propagación de errores HTTP\n\nEn respuesta no-OK:\n\n- el texto de respuesta se incluye en el error lanzado (`HTTP <status>: <text>`)\n- si están presentes, las pistas de autenticación de `WWW-Authenticate` y `Mcp-Auth-Server` se anexan\n\nEn objeto de error JSON-RPC:\n\n- lanza `MCP error <code>: <message>`\n\nEl fallo al parsear el cuerpo JSON (`response.json()`) se propaga como excepción de parsing.\n\n## Comportamiento y modos SSE\n\nExisten dos rutas SSE:\n\n1. **Respuesta SSE por solicitud** (`#parseSSEResponse`)\n   - se usa cuando el tipo de contenido de la respuesta POST es `text/event-stream`\n   - consume el stream hasta encontrar el id de respuesta coincidente\n   - puede procesar notificaciones intercaladas durante el mismo stream\n\n2. **Listener SSE en segundo plano** (`startSSEListener()`)\n   - listener GET opcional para notificaciones iniciadas por el servidor\n   - actualmente no se inicia automáticamente por MCP manager/client\n   - si GET retorna `405`, el listener se desactiva silenciosamente (el servidor no soporta este modo)\n\n## Manejo de payloads malformados y desconexión\n\nLos errores de parsing JSON en SSE surgen de `readSseJson` y rechazan la solicitud/listener.\n\n- Los errores de parsing SSE de solicitud rechazan la solicitud activa.\n- Los errores del listener en segundo plano disparan `onError` (excepto AbortError).\n- No hay reconexión automática para el listener en segundo plano.\n\n## `json-rpc.ts` utilidad vs abstracción de transporte\n\n`src/mcp/json-rpc.ts` proporciona los helpers `callMCP()` y `parseSSE()` para llamadas MCP directas por HTTP (usadas por la integración de Exa), no la abstracción `MCPTransport` usada por `MCPClient`/`MCPManager`.\n\nDiferencias notables respecto a `HttpTransport`:\n\n- parsea primero el texto completo de la respuesta, luego extrae la primera línea `data:` (`parseSSE`), con fallback a JSON\n- sin gestión de timeout de solicitud, sin API de abort, sin manejo de session-id, sin ciclo de vida de transporte\n- retorna el objeto envelope JSON-RPC sin procesar\n\nEsta ruta es ligera pero menos robusta que la implementación completa de transporte.\n\n## Responsabilidades de reintento/reconexión\n\n## Nivel de transporte\n\nLas implementaciones actuales de transporte **no**:\n\n- reintentan solicitudes fallidas\n- reconectan tras la salida del proceso stdio\n- reconectan listeners SSE\n- reenvían solicitudes en tránsito después de una desconexión\n\nFallan rápidamente y propagan errores.\n\n## Nivel de manager/client\n\n`MCPManager` maneja la orquestación de descubrimiento/conexión inicial y puede reconectar solo ejecutando los flujos de conexión nuevamente (rutas `connectToServer`/`discoverAndConnect`). No auto-repara un transporte ya conectado ante callbacks de fallo en tiempo de ejecución.\n\n`MCPManager` tiene comportamiento de fallback en el arranque para servidores lentos (herramientas diferidas desde caché), pero eso es fallback de disponibilidad de herramientas, no reintento de transporte.\n\n## Resumen de escenarios de fallo\n\n- **Línea de mensaje stdio malformada**: descartada; el stream continúa.\n- **Stream/proceso stdio termina**: el transporte se cierra; solicitudes pendientes rechazadas como `Transport closed`.\n- **HTTP no-2xx**: request/notify lanza error HTTP.\n- **Respuesta JSON inválida**: excepción de parsing propagada.\n- **SSE termina sin id coincidente**: la solicitud falla con `No response received for request ID ...`.\n- **Timeout**: error de timeout específico del transporte.\n- **Abort del llamador**: AbortError/razón propagada desde la señal del llamador.\n\n## Regla práctica de límites\n\nSi la responsabilidad es la forma del mensaje, la correlación de id, o el orden de métodos MCP, pertenece a la lógica de protocolo/client.\n\nSi la responsabilidad es el enmarcado (JSONL vs HTTP/SSE), el parsing de streams, el ciclo de vida de fetch/spawn, los relojes de timeout, o el cierre de conexión, pertenece a la implementación de transporte.\n",
	"es/mcp/mcp-runtime-lifecycle.md": "---\ntitle: Ciclo de vida del runtime MCP\ndescription: >-\n  Ciclo de vida del proceso del servidor MCP desde la inicialización hasta el\n  registro de herramientas, monitoreo de salud y apagado.\nsidebar:\n  order: 3\n  label: Ciclo de vida del runtime\ni18n:\n  sourceHash: d04cefaf38f8\n  translator: machine\n---\n\n# Ciclo de vida del runtime MCP\n\nEste documento describe cómo los servidores MCP son descubiertos, conectados, expuestos como herramientas, actualizados y finalizados en el runtime del coding-agent.\n\n## Ciclo de vida en resumen\n\n1. **Inicio del SDK** llama a `discoverAndLoadMCPTools()` (a menos que MCP esté deshabilitado).\n2. **Descubrimiento** (`loadAllMCPConfigs`) resuelve las configuraciones de servidores MCP desde las fuentes de capacidades, filtra las entradas deshabilitadas/de proyecto/Exa, y preserva los metadatos de origen.\n3. **Fase de conexión del manager** (`MCPManager.connectServers`) inicia la conexión por servidor + `tools/list` en paralelo.\n4. **Puerta de inicio rápido** espera hasta 250ms, luego puede retornar:\n   - `MCPTool`s completamente cargados,\n   - fallos por servidor,\n   - o `DeferredMCPTool`s en caché para servidores aún pendientes.\n5. **Cableado del SDK** fusiona las herramientas MCP en el registro de herramientas del runtime para la sesión.\n6. **Sesión activa** puede actualizar las herramientas MCP a través de los flujos `/mcp` (`disconnectAll` + redescubrir + `session.refreshMCPTools`).\n7. **Finalización** ocurre cuando los llamadores invocan `disconnectServer`/`disconnectAll`; el manager también limpia los registros de herramientas MCP para servidores desconectados.\n\n## Fase de descubrimiento y carga\n\n### Ruta de entrada desde el SDK\n\n`createAgentSession()` en `src/sdk.ts` realiza el inicio de MCP cuando `enableMCP` es true (por defecto):\n\n- llama a `discoverAndLoadMCPTools(cwd, { ... })`,\n- pasa `authStorage`, almacenamiento de caché y la configuración `mcp.enableProjectConfig`,\n- siempre establece `filterExa: true`,\n- registra errores de carga/conexión por servidor,\n- almacena el manager retornado en `toolSession.mcpManager` y en el resultado de la sesión.\n\nSi `enableMCP` es false, el descubrimiento MCP se omite por completo.\n\n### Descubrimiento y filtrado de configuración\n\n`loadAllMCPConfigs()` (`src/mcp/config.ts`) carga los elementos canónicos de servidores MCP a través del descubrimiento de capacidades, luego los convierte a `MCPServerConfig` legacy.\n\nComportamiento de filtrado:\n\n- `enableProjectConfig: false` elimina las entradas a nivel de proyecto (`_source.level === \"project\"`).\n- Los servidores con `enabled: false` se omiten antes de los intentos de conexión.\n- Los servidores Exa se filtran por defecto y las claves API se extraen para la integración nativa de la herramienta Exa.\n\nEl resultado incluye tanto `configs` como `sources` (metadatos usados posteriormente para el etiquetado de proveedores).\n\n### Comportamiento de fallos a nivel de descubrimiento\n\n`discoverAndLoadMCPTools()` distingue dos clases de fallos:\n\n- **Fallo duro de descubrimiento** (excepción de `manager.discoverAndConnect`, típicamente del descubrimiento de configuración): retorna un conjunto de herramientas vacío y un error sintético `{ path: \".mcp.json\", error }`.\n- **Fallo de runtime/conexión por servidor**: el manager retorna éxito parcial con un mapa de `errors`; los demás servidores continúan.\n\nPor lo tanto, el inicio no hace fallar toda la sesión del agente cuando servidores MCP individuales fallan.\n\n## Modelo de estado del manager\n\n`MCPManager` rastrea el ciclo de vida del runtime con registros separados:\n\n- `#connections: Map<string, MCPServerConnection>` — servidores completamente conectados.\n- `#pendingConnections: Map<string, Promise<MCPServerConnection>>` — handshake en progreso.\n- `#pendingToolLoads: Map<string, Promise<{ connection, serverTools }>>` — conectados pero herramientas aún cargando.\n- `#tools: CustomTool[]` — vista actual de herramientas MCP expuesta a los llamadores.\n- `#sources: Map<string, SourceMeta>` — metadatos de proveedor/origen incluso antes de que la conexión se complete.\n\n`getConnectionStatus(name)` deriva el estado de estos mapas:\n\n- `connected` si está en `#connections`,\n- `connecting` si tiene conexión pendiente o carga de herramientas pendiente,\n- `disconnected` en caso contrario.\n\n## Establecimiento de conexión y temporización de inicio\n\n## Pipeline de conexión por servidor\n\nPara cada servidor descubierto en `connectServers()`:\n\n1. almacenar/actualizar metadatos de origen,\n2. omitir si ya está conectado/pendiente,\n3. validar campos de transporte (`validateServerConfig`),\n4. resolver sustituciones de autenticación/shell (`#resolveAuthConfig`),\n5. llamar a `connectToServer(name, resolvedConfig)`,\n6. llamar a `listTools(connection)`,\n7. cachear definiciones de herramientas (`MCPToolCache.set`) con mejor esfuerzo.\n\nComportamiento de `connectToServer()` (`src/mcp/client.ts`):\n\n- crea transporte stdio o HTTP/SSE,\n- realiza `initialize` + `notifications/initialized` de MCP,\n- usa timeout (`config.timeout` o 30s por defecto),\n- cierra el transporte en caso de fallo de inicialización.\n\n### Puerta de inicio rápido + respaldo diferido\n\n`connectServers()` espera en una carrera entre:\n\n- todas las tareas de conexión/carga de herramientas resueltas, y\n- `STARTUP_TIMEOUT_MS = 250`.\n\nDespués de 250ms:\n\n- las tareas cumplidas se convierten en `MCPTool`s activos,\n- las tareas rechazadas producen errores por servidor,\n- las tareas aún pendientes:\n  - usan definiciones de herramientas en caché si están disponibles (`MCPToolCache.get`) para crear `DeferredMCPTool`s,\n  - de lo contrario bloquean hasta que esas tareas pendientes se resuelvan.\n\nEste es un modelo de inicio híbrido: retorno rápido cuando la caché está disponible, espera de corrección cuando no lo está.\n\n### Comportamiento de completado en segundo plano\n\nCada `toolsPromise` pendiente también tiene una continuación en segundo plano que eventualmente:\n\n- reemplaza la porción de herramientas de ese servidor en el estado del manager mediante `#replaceServerTools`,\n- escribe la caché,\n- registra fallos tardíos solo después del inicio (`allowBackgroundLogging`).\n\n## Exposición de herramientas y disponibilidad en sesión activa\n\n### Registro en el inicio\n\n`discoverAndLoadMCPTools()` convierte las herramientas del manager en `LoadedCustomTool[]` y decora las rutas (`mcp:<server> via <providerName>` cuando se conoce).\n\n`createAgentSession()` luego agrega estas herramientas a `customTools`, que son envueltas y añadidas al registro de herramientas del runtime con nombres como `mcp_<server>_<tool>`.\n\n### Llamadas a herramientas\n\n- `MCPTool` llama a herramientas a través de una `MCPServerConnection` ya conectada.\n- `DeferredMCPTool` espera a `waitForConnection(server)` antes de llamar; esto permite que las herramientas en caché existan antes de que la conexión esté lista.\n\nAmbos retornan salida de herramienta estructurada y convierten errores de transporte/herramienta en contenido de herramienta `MCP error: ...` (abort permanece como abort).\n\n## Rutas de actualización/recarga (inicio vs recarga en vivo)\n\n### Ruta de inicio inicial\n\n- descubrimiento/carga único en `sdk.ts`,\n- las herramientas se registran en el registro de herramientas de la sesión inicial.\n\n### Ruta de recarga interactiva\n\nLa ruta `/mcp reload` (`src/modes/controllers/mcp-command-controller.ts`) ejecuta:\n\n1. `mcpManager.disconnectAll()`,\n2. `mcpManager.discoverAndConnect()`,\n3. `session.refreshMCPTools(mcpManager.getTools())`.\n\n`session.refreshMCPTools()` (`src/session/agent-session.ts`) elimina todas las herramientas `mcp_`, re-envuelve las herramientas MCP más recientes y reactiva el conjunto de herramientas para que los cambios MCP se apliquen sin reiniciar la sesión.\n\nTambién existe una ruta de seguimiento para conexiones tardías: después de esperar a un servidor específico, si el estado se convierte en `connected`, se re-ejecuta `session.refreshMCPTools(...)` para que las herramientas recién disponibles se vuelvan a vincular en la sesión.\n\n## Salud, reconexión y comportamiento de fallo parcial\n\nEl comportamiento actual del runtime es intencionalmente mínimo:\n\n- **No hay monitor de salud autónomo** en el manager/cliente.\n- **No hay bucle de reconexión automática** cuando un transporte se interrumpe.\n- El manager no se suscribe a `onClose`/`onError` del transporte; el estado está basado en registros.\n- La reconexión es explícita: flujo de recarga o invocación directa de `connectServers()`.\n\nOperacionalmente:\n\n- el fallo de un servidor no elimina las herramientas de servidores saludables,\n- los fallos de conexión/listado están aislados por servidor,\n- la caché de herramientas y las actualizaciones en segundo plano son de mejor esfuerzo (se registran advertencias/errores, sin parada forzosa).\n\n## Semánticas de finalización\n\n### Finalización a nivel de servidor\n\n`disconnectServer(name)`:\n\n- elimina entradas pendientes/metadatos de origen,\n- cierra el transporte si está conectado,\n- elimina las herramientas `mcp_` de ese servidor del estado del manager.\n\n### Finalización global\n\n`disconnectAll()`:\n\n- cierra todos los transportes activos con `Promise.allSettled`,\n- limpia mapas pendientes, orígenes, conexiones y lista de herramientas del manager.\n\nEn el cableado actual, la finalización explícita se usa en los flujos de comandos MCP (para recargar/eliminar/deshabilitar). No hay un hook de disposición automática del manager separado en la ruta de inicio en sí; los llamadores son responsables de invocar los métodos de desconexión del manager cuando necesitan un apagado MCP determinístico.\n\n## Modos de fallo y garantías\n\n| Escenario | Comportamiento | Fallo duro vs mejor esfuerzo |\n| --- | --- | --- |\n| El descubrimiento lanza excepción (ruta de carga de capacidad/configuración) | El loader retorna herramientas vacías + error sintético `.mcp.json` | Inicio de sesión con mejor esfuerzo |\n| Configuración de servidor inválida | Servidor omitido con entrada de error de validación | Mejor esfuerzo por servidor |\n| Timeout de conexión/fallo de inicialización | Error del servidor registrado; los demás continúan | Mejor esfuerzo por servidor |\n| `tools/list` aún pendiente en el inicio con acierto de caché | Herramientas diferidas retornadas inmediatamente | Inicio rápido con mejor esfuerzo |\n| `tools/list` aún pendiente en el inicio sin caché | El inicio espera a que los pendientes se resuelvan | Espera dura por corrección |\n| Fallo tardío de carga de herramientas en segundo plano | Registrado después de la puerta de inicio | Registro con mejor esfuerzo |\n| Transporte interrumpido en runtime | Sin reconexión automática; las llamadas futuras fallan hasta reconexión/recarga | Recuperación con mejor esfuerzo mediante acción manual |\n\n## Superficie de API pública\n\n`src/mcp/index.ts` re-exporta las APIs de loader/manager/cliente para llamadores externos. `src/sdk.ts` expone `discoverMCPServers()` como un wrapper de conveniencia que retorna la misma forma de resultado del loader.\n\n## Archivos de implementación\n\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts) — fachada del loader, normalización de errores de descubrimiento, conversión a `LoadedCustomTool`.\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts) — registros de estado del ciclo de vida, flujo paralelo de conexión/listado, actualización/desconexión.\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts) — configuración de transporte, handshake de inicialización, listado/llamada/desconexión.\n- [`src/mcp/index.ts`](../../packages/coding-agent/src/mcp/index.ts) — exportaciones de la API del módulo MCP.\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — cableado de inicio en el registro de sesión/herramientas.\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts) — descubrimiento/filtrado/validación de configuración usado por el manager.\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts) — comportamiento en runtime de `MCPTool` y `DeferredMCPTool`.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — re-vinculación en vivo de `refreshMCPTools`.\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts) — flujos interactivos de recarga/reconexión.\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — proxy MCP de subagente a través de conexiones del manager padre.\n",
	"es/mcp/mcp-server-tool-authoring.md": "---\ntitle: Autoría de servidores y herramientas MCP\ndescription: >-\n  Guía para construir servidores MCP personalizados y registrar herramientas\n  para el agente de codificación.\nsidebar:\n  order: 4\n  label: Autoría de servidores y herramientas\ni18n:\n  sourceHash: 160e7560ef1f\n  translator: machine\n---\n\n# Autoría de servidores y herramientas MCP\n\nEste documento explica cómo las definiciones de servidores MCP se convierten en herramientas `mcp_*` invocables en coding-agent, y qué deben esperar los operadores cuando las configuraciones son inválidas, duplicadas, deshabilitadas o protegidas por autenticación.\n\n## Arquitectura general\n\n```text\nConfig sources (.xcsh/.claude/.cursor/.vscode/mcp.json, mcp.json, etc.)\n  -> discovery providers normalize to canonical MCPServer\n  -> capability loader dedupes by server name (higher provider priority wins)\n  -> loadAllMCPConfigs converts to MCPServerConfig + skips enabled:false\n  -> MCPManager connects/listTools (with auth/header/env resolution)\n  -> MCPTool/DeferredMCPTool bridge exposes tools as mcp_<server>_<tool>\n  -> AgentSession.refreshMCPTools replaces live MCP tools immediately\n```\n\n## 1) Modelo de configuración del servidor y validación\n\n`src/mcp/types.ts` define la forma de autoría utilizada por los escritores de configuración MCP y el entorno de ejecución:\n\n- `stdio` (valor por defecto cuando `type` está ausente): requiere `command`, opcionales `args`, `env`, `cwd`\n- `http`: requiere `url`, opcionales `headers`\n- `sse`: requiere `url`, opcionales `headers` (mantenido por compatibilidad)\n- campos compartidos: `enabled`, `timeout`, `auth`\n\n`validateServerConfig()` (`src/mcp/config.ts`) aplica las reglas básicas de transporte:\n\n- rechaza configuraciones que definen tanto `command` como `url`\n- requiere `command` para stdio\n- requiere `url` para http/sse\n- rechaza `type` desconocido\n\n`config-writer.ts` aplica esta validación para operaciones de agregar/actualizar y también valida los nombres de servidor:\n\n- no vacío\n- máximo 100 caracteres\n- solo `[a-zA-Z0-9_.-]`\n\n### Problemas comunes con el transporte\n\n- `type` omitido significa stdio. Si su intención era HTTP/SSE pero omitió `type`, `command` se vuelve obligatorio.\n- `sse` todavía se acepta pero se trata internamente como transporte HTTP (`createHttpTransport`).\n- La validación es estructural, no de accesibilidad: una URL sintácticamente válida puede fallar al momento de la conexión.\n\n## 2) Descubrimiento, normalización y precedencia\n\n### Descubrimiento basado en capacidades\n\n`loadAllMCPConfigs()` (`src/mcp/config.ts`) carga elementos canónicos `MCPServer` mediante `loadCapability(mcpCapability.id)`.\n\nLa capa de capacidades (`src/capability/index.ts`) entonces:\n\n1. carga proveedores en orden de prioridad\n2. elimina duplicados por `server.name` (el primero gana = mayor prioridad)\n3. valida los elementos sin duplicados\n\nResultado: los nombres de servidor duplicados entre distintas fuentes no se fusionan. Una definición gana; los duplicados de menor prioridad quedan ocultos.\n\n### `.mcp.json` y archivos relacionados\n\nEl proveedor de respaldo dedicado en `src/discovery/mcp-json.ts` lee `mcp.json` y `.mcp.json` de la raíz del proyecto (baja prioridad).\n\nEn la práctica, los servidores MCP también provienen de proveedores de mayor prioridad (por ejemplo, directorios nativos `.xcsh/...` y directorios de configuración específicos de herramientas). Guía de autoría:\n\n- Prefiera `.xcsh/mcp.json` (proyecto) o `~/.xcsh/mcp.json` (usuario) para control explícito.\n- Use `mcp.json` / `.mcp.json` en la raíz cuando necesite compatibilidad de respaldo.\n- Reutilizar el mismo nombre de servidor en múltiples fuentes causa ocultamiento por precedencia, no fusión.\n\n### Comportamiento de normalización\n\n`convertToLegacyConfig()` (`src/mcp/config.ts`) mapea el `MCPServer` canónico a `MCPServerConfig` de tiempo de ejecución.\n\nComportamiento clave:\n\n- transporte inferido como `server.transport ?? (command ? \"stdio\" : url ? \"http\" : \"stdio\")`\n- los servidores deshabilitados (`enabled === false`) se eliminan antes de la conexión\n- los campos opcionales se preservan cuando están presentes\n\n### Expansión de variables de entorno durante el descubrimiento\n\n`mcp-json.ts` expande marcadores de posición de variables de entorno en campos de texto con `expandEnvVarsDeep()`:\n\n- soporta `${VAR}` y `${VAR:-default}`\n- los valores no resueltos permanecen como cadenas literales `${VAR}`\n\n`mcp-json.ts` también realiza verificaciones de tipo en tiempo de ejecución para JSON de usuario y registra advertencias para valores inválidos de `enabled`/`timeout` en lugar de hacer fallar todo el archivo.\n\n## 3) Autenticación y resolución de valores en tiempo de ejecución\n\n`MCPManager.prepareConfig()`/`#resolveAuthConfig()` (`src/mcp/manager.ts`) es el paso final antes de la conexión.\n\n### Inyección de credenciales OAuth\n\nSi la configuración tiene:\n\n```ts\nauth: { type: \"oauth\", credentialId: \"...\" }\n```\n\ny la credencial existe en el almacenamiento de autenticación:\n\n- `http`/`sse`: inyecta el encabezado `Authorization: Bearer <access_token>`\n- `stdio`: inyecta la variable de entorno `OAUTH_ACCESS_TOKEN`\n\nSi la búsqueda de credenciales falla, el manager registra una advertencia y continúa con la autenticación sin resolver.\n\n### Resolución de valores de encabezados/variables de entorno\n\nAntes de conectar, el manager resuelve cada valor de encabezado/variable de entorno mediante `resolveConfigValue()` (`src/config/resolve-config-value.ts`):\n\n- valor que comienza con `!` => ejecuta comando de shell, usa stdout recortado (en caché)\n- de lo contrario, trata el valor como nombre de variable de entorno primero (`process.env[name]`), con respaldo al valor literal\n- los valores de comando/variable de entorno no resueltos se omiten del mapa final de encabezados/variables de entorno\n\nAdvertencia operativa: esto significa que un comando secreto o clave de variable de entorno mal escrita puede eliminar silenciosamente esa entrada de encabezado/variable de entorno, produciendo errores 401/403 o fallos de inicio del servidor aguas abajo.\n\n## 4) Puente de herramientas: MCP -> herramientas invocables por el agente\n\n`src/mcp/tool-bridge.ts` convierte las definiciones de herramientas MCP en `CustomTool`s.\n\n### Nombrado y dominio de colisiones\n\nLos nombres de herramientas se generan como:\n\n```text\nmcp_<sanitized_server_name>_<sanitized_tool_name>\n```\n\nReglas:\n\n- se convierte a minúsculas\n- caracteres que no son `[a-z_]` se convierten en `_`\n- guiones bajos repetidos se colapsan\n- el prefijo redundante `<server>_` en el nombre de la herramienta se elimina una vez\n\nEsto evita muchas colisiones, pero no todas. Diferentes nombres originales pueden sanitizarse al mismo identificador (por ejemplo `my-server` y `my.server` se sanitizan de forma similar), y la inserción en el registro es último-en-escribir-gana.\n\n### Mapeo de esquema\n\n`convertSchema()` mantiene el JSON Schema de MCP mayormente tal cual, pero corrige esquemas de objetos que carecen de `properties` con `{}` para compatibilidad con proveedores.\n\n### Mapeo de ejecución\n\n`MCPTool.execute()` / `DeferredMCPTool.execute()`:\n\n- llama a `tools/call` de MCP\n- aplana el contenido MCP en texto visualizable\n- devuelve detalles estructurados (`serverName`, `mcpToolName`, metadatos del proveedor)\n- mapea `isError` reportado por el servidor a resultado de texto `Error: ...`\n- mapea fallos de transporte/tiempo de ejecución lanzados a `MCP error: ...`\n- preserva la semántica de cancelación traduciendo AbortError en `ToolAbortError`\n\n## 5) Ciclo de vida del operador: agregar/editar/eliminar y actualizaciones en vivo\n\nEl modo interactivo expone `/mcp` en `src/modes/controllers/mcp-command-controller.ts`.\n\nOperaciones soportadas:\n\n- `add` (asistente o adición rápida)\n- `remove` / `rm`\n- `enable` / `disable`\n- `test`\n- `reauth` / `unauth`\n- `reload`\n\nLas escrituras de configuración son atómicas (`writeMCPConfigFile`: archivo temporal + renombrado).\n\nDespués de los cambios, el controlador llama a `#reloadMCP()`:\n\n1. `mcpManager.disconnectAll()`\n2. `mcpManager.discoverAndConnect()`\n3. `session.refreshMCPTools(mcpManager.getTools())`\n\n`refreshMCPTools()` reemplaza todas las entradas `mcp_` del registro y reactiva inmediatamente el conjunto más reciente de herramientas MCP, de modo que los cambios toman efecto sin reiniciar la sesión.\n\n### Diferencias según el modo\n\n- **Modo interactivo/TUI**: `/mcp` proporciona UX dentro de la aplicación (asistente, flujo OAuth, texto de estado de conexión, reasociación inmediata en tiempo de ejecución).\n- **Integración SDK/headless**: `discoverAndLoadMCPTools()` (`src/mcp/loader.ts`) devuelve herramientas cargadas + errores por servidor; sin UX del comando `/mcp`.\n\n## 6) Superficies de error visibles para el usuario\n\nCadenas de error comunes que ven los usuarios/operadores:\n\n- fallos de validación al agregar/actualizar:\n  - `Invalid server config: ...`\n  - `Server \"<name>\" already exists in <path>`\n- problemas con argumentos de adición rápida:\n  - `Use either --url or -- <command...>, not both.`\n  - `--token requires --url (HTTP/SSE transport).`\n- fallos de conexión/prueba:\n  - `Failed to connect to \"<name>\": <message>`\n  - texto de ayuda sobre timeout sugiere aumentar el tiempo de espera\n  - texto de ayuda de autenticación para `401/403`\n- flujos de autenticación/OAuth:\n  - `Authentication required ... OAuth endpoints could not be discovered`\n  - `OAuth flow timed out. Please try again.`\n  - `OAuth authentication failed: ...`\n- uso de servidor deshabilitado:\n  - `Server \"<name>\" is disabled. Run /mcp enable <name> first.`\n\nEl JSON de fuente incorrecto en el descubrimiento generalmente se maneja como advertencias/registros; las rutas de config-writer lanzan errores explícitos.\n\n## 7) Guía práctica de autoría\n\nPara una autoría MCP robusta en esta base de código:\n\n1. Mantenga los nombres de servidor globalmente únicos entre todas las fuentes de configuración compatibles con MCP.\n2. Prefiera nombres alfanuméricos/con guión bajo para evitar colisiones de nombres sanitizados en los nombres de herramientas `mcp_*` generados.\n3. Use `type` explícito para evitar valores por defecto accidentales de stdio.\n4. Trate `enabled: false` como apagado total: el servidor se omite del conjunto de conexión en tiempo de ejecución.\n5. Para configuraciones OAuth, almacene un `credentialId` válido; de lo contrario la inyección de autenticación se omite.\n6. Si usa resolución de secretos basada en comandos (`!cmd`), verifique que la salida del comando sea estable y no vacía.\n\n## Archivos de implementación\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts)\n- [`src/mcp/config-writer.ts`](../../packages/coding-agent/src/mcp/config-writer.ts)\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts)\n- [`src/discovery/mcp-json.ts`](../../packages/coding-agent/src/discovery/mcp-json.ts)\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/config/resolve-config-value.ts`](../../packages/coding-agent/src/config/resolve-config-value.ts)\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts)\n",
	"es/natives/natives-addon-loader-runtime.md": "---\ntitle: Runtime del Cargador de Addons Nativos\ndescription: >-\n  N-API addon loader runtime with platform detection, fallback strategies, and\n  module resolution.\nsidebar:\n  order: 3\n  label: Cargador de addons\ni18n:\n  sourceHash: 743ea3e32c7c\n  translator: machine\n---\n\n# Runtime del Cargador de Addons Nativos\n\nEste documento profundiza en la capa de carga/validación de addons en `@f5-sales-demo/pi-natives`: cómo `native.ts` decide qué archivo `.node` cargar, cuándo se ejecuta la extracción del payload embebido y cómo se reportan los fallos de inicio.\n\n## Archivos de implementación\n\n- `packages/natives/src/native.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/package.json`\n\n## Alcance y responsabilidad\n\nLas responsabilidades del cargador/runtime son intencionalmente limitadas:\n\n- Construir una lista de candidatos para nombres de archivo y directorios de addons según la plataforma y CPU.\n- Opcionalmente materializar un addon embebido en un directorio de caché versionado por usuario.\n- Intentar los candidatos en orden determinístico.\n- Rechazar addons obsoletos o incompatibles mediante `validateNative` antes de exponer los bindings.\n\nFuera de alcance aquí: comportamiento específico de módulos como grep/text/highlight.\n\n## Entradas del runtime y estado derivado\n\nDurante la inicialización del módulo (`export const native = loadNative();`), `native.ts` computa el contexto estático:\n\n- **Etiqueta de plataforma**: ``${process.platform}-${process.arch}`` (por ejemplo `darwin-arm64`).\n- **Versión del paquete**: desde `packages/natives/package.json` (campo `version`).\n- **Directorios principales**:\n  - `nativeDir`: `packages/natives/native` local al paquete.\n  - `execDir`: directorio que contiene `process.execPath`.\n  - `versionedDir`: `<getNativesDir()>/<packageVersion>`.\n  - Fallback de `userDataDir`:\n    - Windows: `%LOCALAPPDATA%/xcsh` (o `%USERPROFILE%/AppData/Local/xcsh`).\n    - No-Windows: `~/.local/bin`.\n- **Modo binario compilado** (`isCompiledBinary`): verdadero si se cumple alguna de las siguientes:\n  - La variable de entorno `PI_COMPILED` está definida, o\n  - `import.meta.url` contiene marcadores embebidos de Bun (`$bunfs`, `~BUN`, `%7EBUN`).\n- **Sobreescritura de variante**: `PI_NATIVE_VARIANT` (solo `modern`/`baseline`; los valores inválidos se ignoran).\n- **Variante seleccionada**: sobreescritura explícita; de lo contrario, detección de AVX2 en tiempo de ejecución en x64 (`modern` si AVX2, sino `baseline`).\n\n## Soporte de plataformas y resolución de etiquetas\n\n`SUPPORTED_PLATFORMS` está fijado a:\n\n- `linux-x64`\n- `linux-arm64`\n- `darwin-x64`\n- `darwin-arm64`\n- `win32-x64`\n\nDetalle del comportamiento:\n\n- Las plataformas no soportadas no se rechazan de forma anticipada.\n- El cargador aún intenta todos los candidatos computados primero.\n- Si nada se carga, lanza un error explícito de plataforma no soportada listando las etiquetas soportadas.\n\nEsto preserva diagnósticos útiles para casos cercanos mientras falla de forma contundente para objetivos verdaderamente no soportados.\n\n## Selección de variante (`modern` / `baseline` / por defecto)\n\n### Comportamiento en x64\n\n1. Si `PI_NATIVE_VARIANT` es `modern` o `baseline`, ese valor prevalece.\n2. De lo contrario, detectar soporte AVX2:\n   - Linux: escanear `/proc/cpuinfo` buscando `avx2`.\n   - macOS: consultar `sysctl` (`machdep.cpu.leaf7_features`, fallback `machdep.cpu.features`).\n   - Windows: ejecutar PowerShell `[System.Runtime.Intrinsics.X86.Avx2]::IsSupported`.\n3. Resultado:\n   - AVX2 disponible -> `modern`\n   - AVX2 no disponible/no detectable -> `baseline`\n\n### Comportamiento en no-x64\n\n- No se utiliza ninguna variante; el cargador permanece con el nombre de archivo por defecto (`pi_natives.<platform>-<arch>.node`).\n\n### Construcción del nombre de archivo\n\nDado `tag = <platform>-<arch>`:\n\n- No-x64 o sin variante: `pi_natives.<tag>.node`\n- x64 + `modern`: intentar en orden\n  1. `pi_natives.<tag>-modern.node`\n  2. `pi_natives.<tag>-baseline.node` (fallback intencional)\n- x64 + `baseline`: solo `pi_natives.<tag>-baseline.node`\n\nLa etiqueta `addonLabel` usada en los mensajes de error finales es `<tag>` o `<tag> (<variant>)`.\n\n## Construcción de rutas candidatas y orden de fallback\n\n`native.ts` construye pools de candidatos antes de cualquier llamada `require(...)`.\n\n### Candidatos de release\n\nConstruidos a partir de la lista de nombres de archivo resueltos por variante y buscados en este orden:\n\n- **Runtime no compilado**:\n  1. `<nativeDir>/<filename>`\n  2. `<execDir>/<filename>`\n\n- **Runtime compilado** (`PI_COMPILED` o marcadores embebidos de Bun):\n  1. `<versionedDir>/<filename>`\n  2. `<userDataDir>/<filename>`\n  3. `<nativeDir>/<filename>`\n  4. `<execDir>/<filename>`\n\n`dedupedCandidates` elimina duplicados preservando el orden de la primera ocurrencia.\n\n### Secuencia final en runtime\n\nEn tiempo de carga:\n\n1. El candidato de extracción embebida opcional (si se produce) se inserta al frente.\n2. Los candidatos deduplicados restantes se intentan en orden.\n3. El primer candidato que tanto se cargue con `require(...)` como pase `validateNative(...)` gana.\n\n## Ciclo de vida de la extracción de addon embebido\n\n`embedded-addon.ts` define una forma de manifiesto generado:\n\n- `platformTag`\n- `version`\n- `files[]` donde cada entrada tiene `variant`, `filename`, `filePath`\n\nEl valor por defecto registrado actualmente es `embeddedAddon: null`; los artefactos compilados pueden reemplazar esto con metadatos reales.\n\n### Máquina de estados de extracción\n\nLa extracción (`maybeExtractEmbeddedAddon`) se ejecuta solo cuando todas las condiciones se cumplen:\n\n1. `isCompiledBinary === true`\n2. `embeddedAddon !== null`\n3. `embeddedAddon.platformTag === platformTag`\n4. `embeddedAddon.version === packageVersion`\n5. Se encuentra un archivo embebido apropiado para la variante\n\nLa selección de archivo por variante refleja la intención de variante del runtime:\n\n- No-x64: preferir `default`, luego el primer archivo disponible.\n- x64 + `modern`: preferir `modern`, fallback a `baseline`.\n- x64 + `baseline`: requerir `baseline`.\n\nComportamiento de materialización:\n\n1. Asegurar que `<versionedDir>` existe (`mkdirSync(..., { recursive: true })`).\n2. Si `<versionedDir>/<selected filename>` ya existe, reutilizarlo (sin reescritura).\n3. De lo contrario, leer el `filePath` de la fuente embebida y escribir el archivo destino.\n4. Devolver la ruta destino para el intento de carga de máxima prioridad.\n\nEn caso de fallo, la extracción no falla inmediatamente; agrega una entrada de error (fallo en la creación del directorio o escritura) y el cargador procede con el sondeo normal de candidatos.\n\n## Ciclo de vida y transiciones de estado\n\n```text\nInit\n  -> Compute platform/version/variant/candidate lists\n  -> (Compiled + embedded manifest matches?)\n       yes -> Try extract embedded to versionedDir (record errors, continue)\n       no  -> Skip extraction\n  -> For each runtime candidate in order:\n       require(candidate)\n       -> success: validateNative\n            -> pass: return bindings (READY)\n            -> fail: record error, continue\n       -> failure: record error, continue\n  -> none loaded:\n       if unsupported platform tag -> throw Unsupported platform\n       else -> throw Failed to load (full tried-path diagnostics + hints)\n```\n\n## Verificaciones del contrato de `validateNative`\n\n`validateNative(bindings, source)` aplica un contrato exclusivamente de funciones sobre `NativeBindings` en el inicio.\n\nMecánica:\n\n- Para cada nombre de exportación requerido, verifica `typeof bindings[name] === \"function\"`.\n- Los nombres faltantes se agregan.\n- Si alguno falta, el cargador lanza:\n  - ruta del addon fuente,\n  - lista de exportaciones faltantes,\n  - sugerencia de comando de reconstrucción.\n\nEsta es una puerta de compatibilidad estricta contra binarios obsoletos, compilaciones parciales y desviaciones de símbolos/nombres.\n\n### Mapeo de API JS ↔ exportaciones nativas (puerta de validación)\n\n| Nombre del binding JS verificado en `validateNative` | Nombre de exportación nativa esperado |\n| --- | --- |\n| `grep` | `grep` |\n| `glob` | `glob` |\n| `highlightCode` | `highlightCode` |\n| `executeShell` | `executeShell` |\n| `PtySession` | `PtySession` |\n| `Shell` | `Shell` |\n| `visibleWidth` | `visibleWidth` |\n| `getSystemInfo` | `getSystemInfo` |\n| `getWorkProfile` | `getWorkProfile` |\n| `invalidateFsScanCache` | `invalidateFsScanCache` |\n\nNota: `bindings.ts` declara solo el miembro base `cancelWork(id)`; los archivos `types.ts` de los módulos hacen declaration-merge de símbolos adicionales que `validateNative` aplica.\n\n## Comportamiento de fallos y diagnósticos\n\n## Plataforma no soportada\n\nSi todos los candidatos fallan y `platformTag` no está en `SUPPORTED_PLATFORMS`, el cargador lanza:\n\n- `Unsupported platform: <tag>`\n- Lista completa de plataformas soportadas\n- Guía explícita para reportar problemas\n\n## Síntomas de binario obsoleto / desajuste\n\nSeñal típica de desajuste obsoleto:\n\n- `Native addon missing exports (<candidate>). Missing: ...`\n\nCausas comunes:\n\n- Binario `.node` antiguo de una versión/forma de API anterior del paquete.\n- Artefacto de variante incorrecto seleccionado (para x64).\n- Nueva exportación de Rust no presente en el artefacto cargado.\n\nComportamiento del cargador:\n\n- Registra fallos de exportaciones faltantes por candidato.\n- Continúa sondeando los candidatos restantes.\n- Si ningún candidato se valida, el error final incluye cada ruta intentada con cada mensaje de fallo.\n\n## Fallos de inicio en binario compilado\n\nEn modo compilado, los diagnósticos finales incluyen:\n\n- rutas objetivo esperadas de caché versionado (`<versionedDir>/<filename>`),\n- remediación para eliminar el `<versionedDir>` obsoleto y volver a ejecutar,\n- comandos directos de descarga de release con `curl` para cada nombre de archivo esperado.\n\n## Fallos de inicio no compilado\n\nEn modo normal de paquete/runtime, los diagnósticos finales incluyen:\n\n- sugerencia de reinstalación (`bun install @f5-sales-demo/pi-natives`),\n- comando de reconstrucción local (`bun --cwd=packages/natives run build`),\n- sugerencia opcional de compilación de variante x64 (`TARGET_VARIANT=baseline|modern ...`).\n\n## Comportamiento del runtime\n\n- El cargador siempre utiliza la cadena de candidatos de release.\n- Establecer `PI_DEV` solo habilita diagnósticos por candidato en consola (`Loaded native addon...` y errores de carga).\n",
	"es/natives/natives-architecture.md": "---\ntitle: Arquitectura de Natives\ndescription: >-\n  Arquitectura de addon nativo Rust N-API que conecta TypeScript con operaciones\n  específicas de plataforma.\nsidebar:\n  order: 1\n  label: Arquitectura\ni18n:\n  sourceHash: d38ed2437bb7\n  translator: machine\n---\n\n# Arquitectura de Natives\n\n`@f5-sales-demo/pi-natives` es una pila de tres capas:\n\n1. **Capa de wrapper/API en TypeScript** expone puntos de entrada estables en JS/TS.\n2. **Capa de carga/validación del addon** resuelve y valida el binario `.node` para el entorno de ejecución actual.\n3. **Capa del módulo Rust N-API** implementa primitivas de rendimiento crítico exportadas a JS.\n\nEste documento es la base para documentación más detallada a nivel de módulo.\n\n## Archivos de implementación\n\n- `packages/natives/src/index.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `crates/pi-natives/src/lib.rs`\n\n## Capa 1: Capa de wrapper/API en TypeScript\n\n`packages/natives/src/index.ts` es el barrel público. Agrupa las exportaciones por dominio de capacidad y re-exporta wrappers tipados en lugar de exponer directamente los bindings N-API crudos.\n\nGrupos de nivel superior actuales:\n\n- **Primitivas de búsqueda/texto**: `grep`, `glob`, `text`, `highlight`\n- **Primitivas de ejecución/proceso/terminal**: `shell`, `pty`, `ps`, `keys`\n- **Primitivas de sistema/medios/conversión**: `image`, `html`, `clipboard`, `system-info`, `work`\n\n`packages/natives/src/bindings.ts` define el contrato de interfaz base:\n\n- `NativeBindings` comienza con miembros compartidos (`cancelWork(id: number)`)\n- los bindings específicos de módulo se añaden mediante declaration merging desde el `types.ts` de cada módulo\n- `Cancellable` estandariza las opciones de timeout y abort-signal para los wrappers que exponen cancelación\n\n**Contrato garantizado (orientado a la API):** los consumidores importan desde `@f5-sales-demo/pi-natives` y utilizan wrappers tipados.\n\n**Detalle de implementación (puede cambiar):** declaration merging y la disposición interna de los wrappers (`src/<module>/index.ts`, `src/<module>/types.ts`).\n\n## Capa 2: Carga y validación del addon\n\n`packages/natives/src/native.ts` es responsable de la selección del addon en tiempo de ejecución, la extracción opcional y la validación de exportaciones.\n\n### Modelo de resolución de candidatos\n\n- La etiqueta de plataforma es `\"${process.platform}-${process.arch}\"`.\n- Las etiquetas soportadas actualmente son:\n  - `linux-x64`\n  - `linux-arm64`\n  - `darwin-x64`\n  - `darwin-arm64`\n  - `win32-x64`\n- x64 puede usar variantes de CPU:\n  - `modern` (compatible con AVX2)\n  - `baseline` (alternativa)\n- Las arquitecturas no-x64 usan el nombre de archivo predeterminado (sin sufijo de variante).\n\nEstrategia de nombres de archivo:\n\n- Release: `pi_natives.<platform>-<arch>.node`\n- Release con variante x64: `pi_natives.<platform>-<arch>-modern.node` y/o `...-baseline.node`\n- `PI_DEV` habilita diagnósticos del cargador pero no cambia los nombres de archivo del addon\n\n### Detección de variante específica por plataforma\n\nPara x64, la selección de variante utiliza:\n\n- **Linux**: `/proc/cpuinfo`\n- **macOS**: `sysctl machdep.cpu.leaf7_features` / `machdep.cpu.features`\n- **Windows**: verificación mediante PowerShell de `System.Runtime.Intrinsics.X86.Avx2`\n\n`PI_NATIVE_VARIANT` puede forzar explícitamente `modern` o `baseline`.\n\n### Modelo de distribución y extracción de binarios\n\n`packages/natives/package.json` incluye tanto `src` como `native` en los archivos publicados. El directorio `native/` almacena artefactos precompilados de plataforma.\n\nPara binarios compilados (marcadores de runtime `PI_COMPILED` o Bun embebido), el comportamiento del cargador es:\n\n1. Verificar la ruta de caché de usuario versionada: `<getNativesDir()>/<packageVersion>/...`\n2. Verificar la ubicación heredada de binarios compilados:\n   - Windows: `%LOCALAPPDATA%/xcsh` (alternativa `%USERPROFILE%/AppData/Local/xcsh`)\n   - no-Windows: `~/.local/bin`\n3. Recurrir al directorio `native/` empaquetado y a los candidatos del directorio del ejecutable\n\nSi un manifiesto de addon embebido está presente (`embedded-addon.ts` generado por `scripts/embed-native.ts`), `native.ts` puede materializar el binario embebido correspondiente en el directorio de caché versionado antes de cargarlo.\n\n### Validación y modos de fallo\n\nDespués de `require(candidate)`, `validateNative(...)` verifica las exportaciones requeridas (por ejemplo `grep`, `glob`, `highlightCode`, `PtySession`, `Shell`, `getSystemInfo`, `getWorkProfile`, `invalidateFsScanCache`).\n\nLas rutas de fallo son explícitas:\n\n- **Etiqueta de plataforma no soportada**: lanza un error con la lista de plataformas soportadas\n- **Sin candidato cargable**: lanza un error con todas las rutas intentadas y sugerencias de remediación\n- **Exportaciones faltantes**: lanza un error con los nombres exactos faltantes y el comando de reconstrucción\n- **Errores de extracción embebida**: registra fallos de directorio/escritura y los incluye en el diagnóstico final de carga\n\n**Contrato garantizado (orientado a la API):** la carga del addon tiene éxito con un conjunto de bindings validado o falla rápidamente con un texto de error accionable.\n\n**Detalle de implementación (puede cambiar):** el orden exacto de búsqueda de candidatos y el orden de rutas alternativas para binarios compilados.\n\n## Capa 3: Capa del módulo Rust N-API\n\n`crates/pi-natives/src/lib.rs` es el módulo de entrada de Rust que declara la propiedad de los módulos exportados:\n\n- `clipboard`\n- `fd`\n- `fs_cache`\n- `glob`\n- `glob_util`\n- `grep`\n- `highlight`\n- `html`\n- `image`\n- `keys`\n- `prof`\n- `ps`\n- `pty`\n- `shell`\n- `system_info`\n- `task`\n- `text`\n\nEstos módulos implementan los símbolos N-API consumidos y validados por `native.ts`. Los nombres a nivel de JS se exponen a través de los wrappers de TS en `packages/natives/src`.\n\n**Contrato garantizado (orientado a la API):** las exportaciones del módulo Rust deben coincidir con los nombres de binding esperados por `validateNative` y los módulos wrapper.\n\n**Detalle de implementación (puede cambiar):** la descomposición interna de módulos Rust y los límites de módulos auxiliares (`glob_util`, `task`, etc.).\n\n## Límites de propiedad\n\nA nivel de arquitectura, la propiedad se divide de la siguiente manera:\n\n- **Propiedad del wrapper/API en TS (`packages/natives/src`)**\n  - agrupación de la API pública, tipado de opciones y ergonomía estable en JS\n  - superficie de cancelación (`timeoutMs`, `AbortSignal`) expuesta a los llamadores\n- **Propiedad del cargador (`packages/natives/src/native.ts`)**\n  - selección del binario en tiempo de ejecución\n  - selección de variante de CPU y manejo de sobreescrituras\n  - extracción de binarios compilados y sondeo de candidatos\n  - validación estricta de las exportaciones nativas requeridas\n- **Propiedad de Rust (`crates/pi-natives/src`)**\n  - implementación algorítmica y a nivel de sistema\n  - comportamiento nativo de plataforma y lógica sensible al rendimiento\n  - implementación de símbolos N-API que los wrappers de TS consumen\n\n## Flujo en tiempo de ejecución (alto nivel)\n\n1. El consumidor importa desde `@f5-sales-demo/pi-natives`.\n2. El módulo wrapper llama al binding singleton `native`.\n3. `native.ts` selecciona el binario candidato para la plataforma/arquitectura/variante.\n4. La extracción opcional del binario embebido ocurre para distribuciones compiladas.\n5. El addon se carga y el conjunto de exportaciones se valida.\n6. El wrapper devuelve resultados tipados al llamador.\n\n## Glosario\n\n- **Addon nativo**: Un binario `.node` cargado a través de Node-API (N-API).\n- **Etiqueta de plataforma**: Tupla de tiempo de ejecución `platform-arch` (por ejemplo `darwin-arm64`).\n- **Variante**: Sabor de compilación específico para CPU x64 (`modern` AVX2, `baseline` alternativa).\n- **Wrapper**: Función/clase de TS que proporciona una API tipada sobre las exportaciones nativas crudas.\n- **Declaration merging**: Técnica de TS utilizada por los archivos `types.ts` de los módulos para extender `NativeBindings`.\n- **Modo de binario compilado**: Modo de ejecución donde el CLI está empaquetado y los addons nativos se resuelven desde rutas extraídas/de caché en lugar de solo rutas locales del paquete.\n- **Addon embebido**: Metadatos de artefactos de compilación y referencias de archivos generados en `embedded-addon.ts` para que los binarios compilados puedan extraer los payloads `.node` correspondientes.\n- **Puerta de validación**: Verificación `validateNative(...)` que rechaza binarios obsoletos/incompatibles que carecen de las exportaciones requeridas.\n",
	"es/natives/natives-binding-contract.md": "---\ntitle: Contrato de Binding Nativo (Lado TypeScript)\ndescription: >-\n  Contrato de binding del lado TypeScript para invocar funciones nativas de Rust\n  a través de N-API.\nsidebar:\n  order: 2\n  label: Contrato de binding\ni18n:\n  sourceHash: 36dc5fed1f0a\n  translator: machine\n---\n\n# Contrato de Binding Nativo (Lado TypeScript)\n\nEste documento define el contrato del lado TypeScript que se sitúa entre los consumidores de `@f5-sales-demo/pi-natives` y el addon N-API cargado.\n\nSe centra en tres piezas:\n\n1. forma del contrato (`NativeBindings` + augmentación de módulo),\n2. comportamiento del wrapper (`src/<module>/index.ts`),\n3. superficie de exportación pública (`src/index.ts`).\n\n## Archivos de implementación\n\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/system-info/types.ts`\n- `packages/natives/src/system-info/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/work/types.ts`\n- `packages/natives/src/work/index.ts`\n\n## Modelo del contrato\n\n`packages/natives/src/bindings.ts` define el contrato base:\n\n- `NativeBindings` (interfaz base, actualmente incluye `cancelWork(id: number): void`)\n- `Cancellable` (`timeoutMs?: number`, `signal?: AbortSignal`)\n- `TsFunc<T>` forma de callback utilizada por los callbacks threadsafe de N-API\n\nCada módulo agrega sus propios campos mediante fusión de declaraciones (declaration merging):\n\n```ts\n// packages/natives/src/<module>/types.ts\ndeclare module \"../bindings\" {\n interface NativeBindings {\n  grep(options: GrepOptions, onMatch?: TsFunc<GrepMatch>): Promise<GrepResult>;\n }\n}\n```\n\nEsto mantiene una única interfaz de binding agregada sin un archivo de tipos monolítico central.\n\n## Ciclo de vida de la fusión de declaraciones y transiciones de estado\n\n### 1) Ensamblaje de tipos en tiempo de compilación\n\n- `bindings.ts` proporciona el símbolo base `NativeBindings`.\n- Cada `src/<module>/types.ts` augmenta `NativeBindings`.\n- `src/native.ts` importa todos los archivos `./<module>/types` por sus efectos secundarios para que el contrato fusionado esté en alcance donde se utiliza `NativeBindings`.\n\nTransición de estado: **Contrato base** → **Contrato fusionado**.\n\n### 2) Carga del addon en tiempo de ejecución y puerta de validación\n\n- `src/native.ts` carga los binarios `.node` candidatos.\n- El objeto cargado se trata como `NativeBindings` y se pasa inmediatamente a través de `validateNative(...)`.\n- `validateNative` verifica las claves de exportación requeridas mediante `typeof bindings[name] === \"function\"`.\n\nTransición de estado: **Objeto addon no confiable** → **Objeto de binding nativo validado** (o fallo definitivo).\n\n### 3) Invocación del wrapper\n\n- Los wrappers de módulo en `src/<module>/index.ts` llaman a `native.<export>`.\n- Los wrappers adaptan valores por defecto y la forma del callback (de `(err, value)` a patrones de callback solo con valor en las APIs de JS).\n- `src/index.ts` reexporta los wrappers/tipos de módulo como la API pública del paquete.\n\nTransición de estado: **Bindings crudos validados** → **API pública ergonómica**.\n\n## Responsabilidades del wrapper\n\nLos wrappers son intencionalmente delgados; no reimplementan la lógica nativa.\n\nResponsabilidades principales:\n\n- **Normalización/valores por defecto de argumentos**\n  - `glob()` resuelve `options.path` a una ruta absoluta y establece valores por defecto para `hidden`, `gitignore`, `recursive`.\n  - `hasMatch()` completa los flags por defecto (`ignoreCase`, `multiline`) antes de la llamada nativa.\n- **Adaptación de callbacks**\n  - `grep()`, `glob()`, `executeShell()` convierten `TsFunc<T>` (`error, value`) en un callback de usuario que recibe solo valores exitosos.\n- **Comportamiento de entorno o política alrededor de las llamadas nativas**\n  - El wrapper del portapapeles agrega manejo de OSC52/Termux/headless y trata la copia como mejor esfuerzo.\n- **Nombrado público y curación de reexportaciones**\n  - `searchContent()` se mapea a la exportación nativa `search`.\n\n## Organización de la superficie de exportación pública\n\n`packages/natives/src/index.ts` es el barrel público canónico. Agrupa las exportaciones por dominio de capacidad:\n\n- Búsqueda/texto: `grep`, `glob`, `text`, `highlight`\n- Ejecución/proceso/terminal: `shell`, `pty`, `ps`, `keys`\n- Sistema/medios/conversión: `image`, `html`, `clipboard`, `system-info`, `work`\n\nRegla para mantenedores: si un wrapper no se reexporta desde `src/index.ts`, no forma parte de la superficie pública intencionada del paquete.\n\n## Mapeo de API JS ↔ exportación nativa (representativo)\n\nEl lado Rust utiliza nombres de exportación N-API (típicamente de la conversión `#[napi]` snake_case -> camelCase, con alias explícitos ocasionales) que deben coincidir con estas claves de binding.\n\n| Categoría | API JS pública (wrapper) | Clave de binding nativo | Tipo de retorno | ¿Async? |\n|---|---|---|---|---|\n| Grep | `grep(options, onMatch?)` | `grep` | `Promise<GrepResult>` | Sí |\n| Grep | `searchContent(content, options)` | `search` | `SearchResult` | No |\n| Grep | `hasMatch(content, pattern, opts?)` | `hasMatch` | `boolean` | No |\n| Grep | `fuzzyFind(options)` | `fuzzyFind` | `Promise<FuzzyFindResult>` | Sí |\n| Glob | `glob(options, onMatch?)` | `glob` | `Promise<GlobResult>` | Sí |\n| Glob | `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `void` | No |\n| Shell | `executeShell(options, onChunk?)` | `executeShell` | `Promise<ShellExecuteResult>` | Sí |\n| Shell | `Shell` | `Shell` | constructor de clase | N/A |\n| PTY | `PtySession` | `PtySession` | constructor de clase | N/A |\n| Text | `truncateToWidth(...)` | `truncateToWidth` | `string` | No |\n| Text | `sliceWithWidth(...)` | `sliceWithWidth` | `SliceWithWidthResult` | No |\n| Text | `visibleWidth(text)` | `visibleWidth` | `number` | No |\n| Highlight | `highlightCode(code, lang, colors)` | `highlightCode` | `string` | No |\n| HTML | `htmlToMarkdown(html, options?)` | `htmlToMarkdown` | `Promise<string>` | Sí |\n| System | `getSystemInfo()` | `getSystemInfo` | `SystemInfo` | No |\n| Work | `getWorkProfile(lastSeconds)` | `getWorkProfile` | `WorkProfile` | No |\n| Process | `killTree(pid, signal)` | `killTree` | `number` | No |\n| Process | `listDescendants(pid)` | `listDescendants` | `number[]` | No |\n| Clipboard | `copyToClipboard(text)` | `copyToClipboard` | `Promise<void>` (comportamiento de mejor esfuerzo del wrapper) | Sí |\n| Clipboard | `readImageFromClipboard()` | `readImageFromClipboard` | `Promise<ClipboardImage \\| null>` | Sí |\n| Keys | `parseKey(data, kittyProtocolActive)` | `parseKey` | `string \\| null` | No |\n\n## Diferencias del contrato entre sync y async\n\nEl contrato mezcla APIs síncronas y asíncronas; los wrappers preservan el estilo de llamada nativo en lugar de forzar un solo modelo:\n\n- **Exportaciones async basadas en Promise** para I/O o trabajo de larga duración (`grep`, `glob`, `htmlToMarkdown`, `executeShell`, portapapeles, operaciones de imagen).\n- **Exportaciones síncronas** para transformaciones/parsers determinísticos en memoria (`search`, `hasMatch`, resaltado de sintaxis, ancho/segmentación de texto, parseo de teclas, consultas de procesos).\n- **Exportaciones de constructores** para objetos con estado en tiempo de ejecución (`Shell`, `PtySession`, `PhotonImage`).\n\nImplicación para mantenedores: cambiar sync ↔ async para una exportación existente es un cambio de API y contrato que rompe compatibilidad a través de wrappers y consumidores.\n\n## Patrones de tipado de objetos y enums\n\n### Patrones de objetos (objetos JS estilo `#[napi(object)]`)\n\nTS modela los valores nativos con forma de objeto como interfaces, por ejemplo:\n\n- `GrepResult`, `SearchResult`, `GlobResult`\n- `SystemInfo`, `WorkProfile`\n- `ClipboardImage`, `ParsedKittyResult`\n\nEstos son contratos estructurales en tiempo de compilación; la corrección de la forma en tiempo de ejecución es responsabilidad de la implementación nativa.\n\n### Patrones de enums\n\nLos enums nativos numéricos se representan como valores `const enum` en TS:\n\n- `FileType` (`1=file`, `2=dir`, `3=symlink`)\n- `ImageFormat` (`0=PNG`, `1=JPEG`, `2=WEBP`, `3=GIF`)\n- `SamplingFilter`, `Ellipsis`, `KeyEventType`\n\nLos consumidores ven miembros nombrados del enum; la frontera del binding pasa números.\n\n## Cómo se detectan las inconsistencias\n\nLa detección de inconsistencias ocurre en dos capas:\n\n1. **Verificaciones del contrato TypeScript en tiempo de compilación**\n   - Los wrappers llaman a `native.<name>` contra `NativeBindings` fusionado.\n   - Las claves de binding faltantes/renombradas rompen la verificación de tipos TS en los wrappers.\n\n2. **Validación en tiempo de ejecución en `validateNative`**\n   - Después de la carga, `native.ts` verifica las exportaciones requeridas y lanza una excepción si alguna falta.\n   - El mensaje de error incluye las claves faltantes e instrucciones de reconstrucción.\n\nEsto detecta la deriva común de binarios obsoletos: el wrapper/tipo existe pero el `.node` cargado carece de la exportación.\n\n## Comportamiento de fallos y advertencias\n\n### Fallos de carga/validación (fallos definitivos)\n\n- El fallo de carga del addon o una plataforma no soportada lanza una excepción durante la inicialización del módulo en `native.ts`.\n- Las exportaciones requeridas faltantes lanzan una excepción antes de que los wrappers sean utilizables.\n\nEfecto: el paquete falla rápidamente en lugar de diferir el fallo a la primera llamada.\n\n### Diferencias de comportamiento a nivel de wrapper\n\n- Algunos wrappers intencionalmente suavizan los fallos (`copyToClipboard` es de mejor esfuerzo y absorbe el fallo nativo).\n- Los callbacks de streaming ignoran los payloads de error del callback y solo reenvían eventos de valor exitosos.\n\n### Advertencias a nivel de tipos (el runtime es más estricto que TS)\n\n- Los campos opcionales en TS no garantizan validez semántica; la capa nativa aún puede rechazar valores malformados.\n- El tipado `const enum` no previene que valores numéricos fuera de rango sean pasados por consumidores sin tipado en tiempo de ejecución.\n- `validateNative` verifica solo la presencia y que sean funciones las exportaciones requeridas, no la compatibilidad profunda de forma de argumentos/retorno.\n- `bindings.ts` incluye `cancelWork(id)` en la interfaz base, pero la lista de validación en tiempo de ejecución actual no aplica esa clave.\n\n## Lista de verificación para mantenedores ante cambios de binding\n\nAl agregar/cambiar una exportación, actualice todos los siguientes:\n\n1. `src/<module>/types.ts` (augmentación + tipos del contrato)\n2. `src/<module>/index.ts` (comportamiento del wrapper)\n3. Importaciones de `src/native.ts` para los tipos del módulo (si es un módulo nuevo)\n4. Verificaciones de exportaciones requeridas en `validateNative`\n5. Reexportaciones públicas en `src/index.ts`\n\nOmitir cualquier paso crea ya sea deriva en tiempo de compilación o fallo en tiempo de ejecución durante la carga.\n",
	"es/natives/natives-build-release-debugging.md": "---\ntitle: 'Guía de compilación, publicación y depuración de Natives'\ndescription: >-\n  Guía de compilación, publicación y depuración del addon nativo de Rust en\n  múltiples plataformas.\nsidebar:\n  order: 8\n  label: 'Compilación, publicación y depuración'\ni18n:\n  sourceHash: efe47aa5b466\n  translator: machine\n---\n\n# Guía de compilación, publicación y depuración de Natives\n\nEsta guía describe cómo el pipeline de compilación de `@f5-sales-demo/pi-natives` produce addons `.node`, cómo las distribuciones compiladas los cargan y cómo depurar fallos del cargador/compilación.\n\nSigue los términos de arquitectura de `docs/natives-architecture.md`:\n\n- **producción de artefactos en tiempo de compilación** (`scripts/build-native.ts`)\n- **generación del manifiesto de addon embebido** (`scripts/embed-native.ts`)\n- **carga del addon en tiempo de ejecución + puerta de validación** (`src/native.ts`)\n\n## Archivos de implementación\n\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `packages/natives/src/native.ts`\n- `crates/pi-natives/Cargo.toml`\n\n## Visión general del pipeline de compilación\n\n### 1) Puntos de entrada de compilación\n\nScripts de `packages/natives/package.json`:\n\n- `bun scripts/build-native.ts` (`build`) → compilación de release\n- `bun scripts/build-native.ts --dev` (`dev:native`) → compilación con perfil debug/dev (mismo esquema de nombres de salida)\n- `bun scripts/embed-native.ts` (`embed:native`) → genera `src/embedded-addon.ts` a partir de los archivos compilados\n\n### 2) Compilación del artefacto Rust\n\n`build-native.ts` ejecuta Cargo en `crates/pi-natives`:\n\n- comando base: `cargo build`\n- el modo release añade `--release` a menos que se pase `--dev`\n- la compilación cruzada añade `--target <CROSS_TARGET>`\n\n`crates/pi-natives/Cargo.toml` declara `crate-type = [\"cdylib\"]`, por lo que Cargo emite una biblioteca compartida (`.so`/`.dylib`/`.dll`) que luego se copia/renombra a un nombre de archivo de addon `.node`.\n\n### 3) Descubrimiento e instalación del artefacto\n\nDespués de que Cargo finaliza, `build-native.ts` escanea directorios de salida candidatos en orden:\n\n1. `${CARGO_TARGET_DIR}` (si está definido)\n2. `<repo>/target`\n3. `crates/pi-natives/target`\n\nPara cada raíz verifica directorios de perfil:\n\n- compilación cruzada: `<root>/<crossTarget>/<profile>` luego `<root>/<profile>`\n- compilación nativa: `<root>/<profile>`\n\nLuego busca uno de:\n\n- `libpi_natives.so`\n- `libpi_natives.dylib`\n- `pi_natives.dll`\n- `libpi_natives.dll`\n\nCuando lo encuentra, lo instala atómicamente en `packages/natives/native/` con semántica de archivo temporal + renombrado (el fallback de Windows maneja explícitamente los fallos de reemplazo de DLL bloqueadas).\n\n## Modelo de destino/variante y convenciones de nombres\n\n## Etiqueta de plataforma\n\nTanto la compilación como el tiempo de ejecución usan la etiqueta de plataforma:\n\n`<platform>-<arch>` (ejemplo: `darwin-arm64`, `linux-x64`)\n\n## Modelo de variantes (solo x64)\n\nx64 soporta variantes de CPU:\n\n- `modern` (ruta con capacidad AVX2)\n- `baseline` (fallback)\n\nLas arquitecturas que no son x64 usan un único artefacto por defecto (sin sufijo de variante).\n\n### Nombres de archivos de salida\n\nCompilaciones de release:\n\n- x64: `pi_natives.<platform>-<arch>-modern.node` o `...-baseline.node`\n- no-x64: `pi_natives.<platform>-<arch>.node`\n\nCompilación dev (`--dev`):\n\n- Usa flags de perfil debug pero mantiene el esquema estándar de nombres con etiqueta de plataforma\n\nOrden de candidatos del cargador en tiempo de ejecución en `native.ts`:\n\n- candidatos de release\n- el modo compilado antepone candidatos extraídos/en caché antes de los archivos locales del paquete\n\n## Flags de entorno y opciones de compilación\n\n## Flags de tiempo de ejecución\n\n- `PI_DEV` (comportamiento del cargador): habilitar diagnósticos del cargador\n- `PI_NATIVE_VARIANT` (comportamiento del cargador, solo x64): forzar la selección de `modern` o `baseline` en tiempo de ejecución\n- `PI_COMPILED` (comportamiento del cargador): habilitar el comportamiento de candidatos/extracción de binarios compilados\n\n## Flags/opciones de tiempo de compilación\n\n- `--dev` (argumento del script): compilar con perfil debug\n- `CROSS_TARGET`: se pasa a Cargo como `--target`\n- `TARGET_PLATFORM`: sobreescribir el nombre de la etiqueta de plataforma en la salida\n- `TARGET_ARCH`: sobreescribir el nombre de la arquitectura en la salida\n- `TARGET_VARIANT` (solo x64): forzar `modern` o `baseline` para el nombre de archivo de salida y la política de RUSTFLAGS\n- `CARGO_TARGET_DIR`: raíz adicional al buscar salidas de Cargo\n- `RUSTFLAGS`:\n  - si no está definido y no se está compilando de forma cruzada, el script establece:\n    - modern: `-C target-cpu=x86-64-v3`\n    - baseline: `-C target-cpu=x86-64-v2`\n    - no-x64 / sin variante: `-C target-cpu=native`\n  - si ya está definido, el script no lo sobreescribe\n\n## Transiciones de estado/ciclo de vida de la compilación\n\n### Ciclo de vida de compilación (`build-native.ts`)\n\n1. **Inicialización**: parsear argumentos/entorno (`--dev`, sobreescrituras de destino, flags de compilación cruzada)\n2. **Resolución de variante**:\n   - no-x64 → sin variante\n   - x64 + `TARGET_VARIANT` → variante explícita\n   - compilación cruzada x64 sin `TARGET_VARIANT` → error grave\n   - compilación local x64 sin sobreescritura → detectar AVX2 del host\n3. **Compilar**: ejecutar Cargo con perfil/destino resuelto\n4. **Localizar artefacto**: escanear raíces de destino/directorios de perfil/nombres de biblioteca\n5. **Instalar**: copiar + renombrado atómico en `packages/natives/native`\n6. **Completado**: addon listo para los candidatos del cargador\n\nLas salidas por fallo ocurren en cualquier etapa con texto de error explícito (variante inválida, fallo en cargo build, biblioteca de salida faltante, fallo de instalación/renombrado).\n\n### Ciclo de vida de embed (`embed-native.ts`)\n\n1. **Inicialización**: calcular la etiqueta de plataforma desde `TARGET_PLATFORM`/`TARGET_ARCH` o valores del host\n2. **Conjunto de candidatos**:\n   - x64 espera ambos `modern` y `baseline`\n   - no-x64 espera un archivo por defecto\n3. **Validar disponibilidad** en `packages/natives/native`\n4. **Generar manifiesto** (`src/embedded-addon.ts`) con imports `file` de Bun y versión del paquete\n5. **Extracción en tiempo de ejecución lista** para modo compilado\n\n`--reset` omite la validación y escribe un stub de manifiesto nulo (`embeddedAddon = null`).\n\n## Flujo de trabajo de desarrollo vs comportamiento en producción/compilado\n\n## Flujo de trabajo de desarrollo local\n\nBucle local típico:\n\n1. Compilar addon:\n   - release: `bun --cwd=packages/natives run build`\n   - perfil debug: `bun --cwd=packages/natives run dev:native`\n2. Establecer `PI_DEV=1` al probar diagnósticos del cargador\n3. El cargador en `native.ts` resuelve candidatos locales del paquete en `native/` (y fallback del directorio del ejecutable)\n4. `validateNative` impone compatibilidad de exports antes de que los wrappers usen el binding\n\n## Flujo de trabajo de binario distribuido/compilado\n\nEn modo compilado (`PI_COMPILED` o marcadores embebidos de Bun):\n\n1. El cargador calcula el directorio de caché versionado: `<getNativesDir()>/<packageVersion>` (operacionalmente `~/.xcsh/natives/<version>`)\n2. Si el manifiesto embebido coincide con la plataforma+versión actual, el cargador puede extraer el archivo embebido seleccionado en ese directorio versionado\n3. El orden de candidatos en tiempo de ejecución incluye:\n   - directorio de caché versionado\n   - directorio de binarios compilados legacy (`%LOCALAPPDATA%/xcsh` en Windows, `~/.local/bin` en otros)\n   - directorios del paquete/ejecutable\n4. El primer addon cargado exitosamente aún debe pasar `validateNative`\n\nPor esto las expectativas de empaquetado + cargador en tiempo de ejecución deben estar alineadas: los nombres de archivo, etiquetas de plataforma y símbolos exportados deben coincidir con lo que `native.ts` sondea y valida.\n\n## Mapeo de API JS ↔ exports de Rust (subconjunto de la puerta de validación)\n\n`native.ts` requiere que estos exports visibles desde JS existan en el addon cargado. Se mapean a exports N-API de Rust en `crates/pi-natives/src`:\n\n| Nombre JS requerido por `validateNative` | Declaración de export en Rust | Archivo fuente Rust |\n| --- | --- | --- |\n| `glob` | `#[napi] pub fn glob(...)` | `crates/pi-natives/src/glob.rs` |\n| `grep` | `#[napi] pub fn grep(...)` | `crates/pi-natives/src/grep.rs` |\n| `search` | `#[napi] pub fn search(...)` | `crates/pi-natives/src/grep.rs` |\n| `highlightCode` | `#[napi] pub fn highlight_code(...)` | `crates/pi-natives/src/highlight.rs` |\n| `getSystemInfo` | `#[napi] pub fn get_system_info(...)` | `crates/pi-natives/src/system_info.rs` |\n| `getWorkProfile` | `#[napi] pub fn get_work_profile(...)` (export en camelCase) | `crates/pi-natives/src/prof.rs` |\n| `invalidateFsScanCache` | `#[napi] pub fn invalidate_fs_scan_cache(...)` | `crates/pi-natives/src/fs_cache.rs` |\n\nSi falta algún símbolo requerido, el cargador falla inmediatamente con una sugerencia de recompilación.\n\n## Comportamiento en fallos y diagnósticos\n\n## Fallos en tiempo de compilación\n\n- Configuración de variante inválida:\n  - `TARGET_VARIANT` definido en no-x64 → error inmediato\n  - compilación cruzada x64 sin `TARGET_VARIANT` explícito → error inmediato\n- Fallo en la compilación de Cargo:\n  - el script muestra el código de salida distinto de cero y stderr\n- Artefacto no encontrado:\n  - el script imprime cada directorio de perfil verificado\n- Fallo en la instalación:\n  - mensaje explícito; Windows incluye sugerencia sobre archivo bloqueado\n\n## Fallos del cargador en tiempo de ejecución (`native.ts`)\n\n- Etiqueta de plataforma no soportada:\n  - lanza excepción con lista de plataformas soportadas\n- Ningún candidato pudo cargarse:\n  - lanza excepción con lista completa de errores de candidatos y sugerencias de remediación específicas del modo\n- Exports faltantes:\n  - lanza excepción con nombres exactos de símbolos faltantes y comando de recompilación\n- Problemas de extracción embebida:\n  - errores de mkdir/escritura de extracción se registran e incluyen en los diagnósticos finales\n\n## Matriz de resolución de problemas\n\n| Síntoma | Causa probable | Verificar | Solución |\n| --- | --- | --- | --- |\n| `Native addon missing exports ... Missing: <name>` | Binario `.node` obsoleto, desajuste en nombre de export de Rust, o se cargó el binario incorrecto | Ejecutar con `PI_DEV=1` para ver la ruta cargada; inspeccionar la lista de exports de ese archivo | Recompilar con `build`; asegurar que el nombre de export `#[napi]` de Rust (o alias explícito cuando sea necesario) coincida con la clave JS; eliminar archivos obsoletos en caché/versionados |\n| La máquina x64 carga baseline cuando se esperaba modern | `PI_NATIVE_VARIANT=baseline`, no se detectó AVX2, o solo el archivo baseline está presente | Verificar `PI_NATIVE_VARIANT`; inspeccionar `native/` buscando archivo `-modern` | Compilar variante modern (`TARGET_VARIANT=modern ... build`) y asegurar que el archivo se incluya en la distribución |\n| La compilación cruzada produce un binario inutilizable/mal etiquetado | Desajuste entre `CROSS_TARGET` y `TARGET_PLATFORM`/`TARGET_ARCH`, o falta `TARGET_VARIANT` para x64 | Confirmar la tupla de entorno y el nombre del archivo de salida | Re-ejecutar con valores de entorno consistentes y `TARGET_VARIANT` explícito para x64 |\n| El binario compilado falla después de una actualización | Caché extraída obsoleta (`~/.xcsh/natives/<versión-antigua-o-no-coincidente>`) o desajuste del manifiesto embebido | Inspeccionar el directorio de natives versionado y la lista de errores del cargador | Eliminar la caché de natives versionada para la versión del paquete y re-ejecutar; regenerar el manifiesto embebido durante el empaquetado |\n| El cargador sondea muchas rutas y ninguna funciona | Desajuste de plataforma o artefacto de release faltante en `native/` del paquete | Verificar `platformTag` vs nombre(s) de archivo reales | Asegurar que el nombre del archivo compilado coincida exactamente con la convención `pi_natives.<platform>-<arch>(-variant).node` y que el paquete incluya `native/` |\n| `embed:native` falla con \"Incomplete native addons\" | Los archivos de variante requeridos no se compilaron antes de embeber | Verificar la lista de esperados vs encontrados en el texto del error | Compilar los archivos requeridos primero (x64: ambos modern+baseline; no-x64: por defecto), luego re-ejecutar `embed:native` |\n\n## Comandos operativos\n\n```bash\n# Release artifact for current host\nbun --cwd=packages/natives run build\n\n# Debug profile artifact build\nbun --cwd=packages/natives run dev:native\n\n# Build explicit x64 variants\nTARGET_VARIANT=modern bun --cwd=packages/natives run build\nTARGET_VARIANT=baseline bun --cwd=packages/natives run build\n\n# Generate embedded addon manifest from built native files\nbun --cwd=packages/natives run embed:native\n\n# Reset embedded manifest to null stub\nbun --cwd=packages/natives run embed:native -- --reset\n```\n",
	"es/natives/natives-media-system-utils.md": "---\ntitle: Utilidades nativas de medios y sistema\ndescription: >-\n  Utilidades nativas de procesamiento de medios para capturas de pantalla,\n  manejo de imágenes e información del sistema.\nsidebar:\n  order: 7\n  label: Utilidades de medios y sistema\ni18n:\n  sourceHash: 430898c177bc\n  translator: machine\n---\n\n# Utilidades nativas de medios + sistema\n\nEste documento es una inmersión profunda en el subsistema de la capa de **primitivas de sistema/medios/conversión** descrita en [`docs/natives-architecture.md`](./natives-architecture.md): `image`, `html`, `clipboard` y perfilado de `work`.\n\n## Archivos de implementación\n\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/prof.rs`\n- `crates/pi-natives/src/task.rs`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/work/index.ts`\n- `packages/natives/src/work/types.ts`\n\n> Nota: no existe `crates/pi-natives/src/work.rs`; el perfilado de trabajo está implementado en `prof.rs` y alimentado por la instrumentación en `task.rs`.\n\n## Mapeo de API TS ↔ exportaciones/módulos Rust\n\n| Exportación TS (packages/natives)           | Exportación N-API de Rust                                               | Módulo Rust                           |\n| ------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------- |\n| `PhotonImage.parse(bytes)`                  | `PhotonImage::parse`                                                     | `image.rs`                            |\n| `PhotonImage#resize(width, height, filter)` | `PhotonImage::resize`                                                    | `image.rs`                            |\n| `PhotonImage#encode(format, quality)`       | `PhotonImage::encode`                                                    | `image.rs`                            |\n| `htmlToMarkdown(html, options)`             | `html_to_markdown`                                                       | `html.rs`                             |\n| `copyToClipboard(text)`                     | `copy_to_clipboard` + lógica de respaldo en TS                           | `clipboard.rs` + `clipboard/index.ts` |\n| `readImageFromClipboard()`                  | `read_image_from_clipboard`                                              | `clipboard.rs`                        |\n| `getWorkProfile(lastSeconds)`               | `get_work_profile`                                                      | `prof.rs`                             |\n\n## Límites de formato de datos y conversiones\n\n### Imagen (`image`)\n\n- **Límite de entrada JS**: `Uint8Array` con bytes de imagen codificada.\n- **Límite de decodificación Rust**: los bytes se copian a `Vec<u8>`, el formato se detecta con `ImageReader::with_guessed_format()`, luego se decodifica a `DynamicImage`.\n- **Estado en memoria**: `PhotonImage` almacena `Arc<DynamicImage>`.\n- **Límite de salida**: `encode(format, quality)` retorna `Promise<Uint8Array>` (`Vec<u8>` en Rust).\n\nLos IDs de formato son numéricos:\n\n- `0`: PNG\n- `1`: JPEG\n- `2`: WebP (codificador sin pérdida)\n- `3`: GIF\n\nRestricciones:\n\n- `quality` solo se utiliza para JPEG.\n- PNG/WebP/GIF ignoran `quality`.\n- Los IDs de formato no soportados fallan (`Invalid image format: <id>`).\n\n### Conversión HTML (`html`)\n\n- **Límite de entrada JS**: `string` HTML + objeto opcional `{ cleanContent?: boolean; skipImages?: boolean }`.\n- **Límite de conversión Rust**: la entrada `String` es convertida por `html_to_markdown_rs::convert`.\n- **Límite de salida**: `string` Markdown.\n\nComportamiento de conversión:\n\n- `cleanContent` tiene valor predeterminado `false`.\n- Cuando `cleanContent=true`, se habilita el preprocesamiento con `PreprocessingPreset::Aggressive` y flags de eliminación forzada para navegación/formularios.\n- `skipImages` tiene valor predeterminado `false`.\n\n### Portapapeles (`clipboard`)\n\n- **Ruta de texto**:\n  - TS primero emite OSC 52 (`\\x1b]52;c;<base64>\\x07`) cuando stdout es un TTY.\n  - El mismo texto se intenta luego a través de la API nativa del portapapeles (`native.copyToClipboard`) como mejor esfuerzo.\n  - En Termux, TS intenta `termux-clipboard-set` primero.\n- **Ruta de lectura de imagen**:\n  - Rust lee la imagen cruda desde `arboard`.\n  - Rust la recodifica a bytes PNG (crate `image`), retorna `{ data: Uint8Array, mimeType: \"image/png\" }`.\n  - TS retorna `null` anticipadamente en Termux o sesiones Linux sin servidor de pantalla (`DISPLAY`/`WAYLAND_DISPLAY` ausentes).\n\n### Perfilado de trabajo (`work`)\n\n- **Límite de recolección**: las muestras de perfilado son producidas por guardas `profile_region(tag)` en `task::blocking` y `task::future`.\n- **Formato de almacenamiento**: buffer circular de tamaño fijo (`MAX_SAMPLES = 10_000`) que almacena ruta de pila + duración (`μs`) + marca de tiempo (`μs desde el inicio del proceso`).\n- **Límite de salida**: `getWorkProfile(lastSeconds)` retorna un objeto:\n  - `folded`: texto de pila plegada (entrada para flamegraph)\n  - `summary`: tabla resumen en markdown\n  - `svg`: SVG de flamegraph opcional\n  - `totalMs`, `sampleCount`\n\n## Ciclo de vida y transiciones de estado\n\n### Ciclo de vida de imagen\n\n1. `PhotonImage.parse(bytes)` programa una tarea de decodificación bloqueante (`image.decode`).\n2. En caso de éxito, existe un handle nativo `PhotonImage` en JS.\n3. `resize(...)` crea un nuevo handle nativo (`image.resize`), los handles antiguo y nuevo pueden coexistir.\n4. `encode(...)` materializa bytes (`image.encode`) sin mutar las dimensiones de la imagen.\n\nTransiciones de fallo:\n\n- Fallo en la detección de formato/decodificación rechaza la promesa de parse.\n- Fallo en la codificación rechaza la promesa de encode.\n- ID de formato inválido rechaza la promesa de encode.\n\n### Ciclo de vida de HTML\n\n1. `htmlToMarkdown(html, options)` programa una tarea de conversión bloqueante.\n2. La conversión se ejecuta con opciones predeterminadas (`cleanContent=false`, `skipImages=false`) a menos que se especifiquen.\n3. Retorna un string markdown o rechaza.\n\nTransiciones de fallo:\n\n- Fallo del convertidor retorna una promesa rechazada (`Conversion error: ...`).\n\n### Ciclo de vida del portapapeles\n\n`copyToClipboard(text)` es intencionalmente de mejor esfuerzo y multi-ruta:\n\n1. Si es TTY: intenta escritura OSC 52 (payload en base64).\n2. Intenta comando Termux cuando `TERMUX_VERSION` está establecido.\n3. Intenta copia de texto nativa con `arboard`.\n4. Suprime errores en la capa TS.\n\n`readImageFromClipboard()` difiere en rigurosidad según la etapa:\n\n1. TS bloquea estrictamente contextos de ejecución no soportados (Termux/Linux sin interfaz gráfica) retornando `null`.\n2. La lectura de `arboard` en Rust se ejecuta solo cuando TS lo permite.\n3. `ContentNotAvailable` se mapea a `null`.\n4. Otros errores de Rust rechazan.\n\n### Ciclo de vida del perfilado de trabajo\n\n1. Sin inicio explícito: el perfilado está siempre activo cuando se ejecutan los helpers de tareas.\n2. Cada ámbito de tarea instrumentado registra una muestra al destruir la guarda.\n3. Las muestras sobrescriben las entradas más antiguas después de alcanzar la capacidad del buffer.\n4. `getWorkProfile(lastSeconds)` lee una ventana de tiempo y deriva artefactos plegados/resumen/svg.\n\nTransiciones de fallo:\n\n- Fallo en la generación de SVG es un fallo suave (`svg: null`), mientras que folded y summary aún se retornan.\n- Una ventana de muestras vacía retorna datos plegados vacíos y `svg: null`, no un error.\n\n## Operaciones no soportadas y propagación de errores\n\n### Imagen\n\n- Entrada de decodificación no soportada o bytes corruptos: fallo estricto (rechazo de promesa).\n- ID de formato de codificación no soportado: fallo estricto.\n- Sin ruta de respaldo de mejor esfuerzo en el wrapper de TS.\n\n### HTML\n\n- Los errores de conversión son fallos estrictos (rechazo).\n- La omisión de opciones se maneja con valores predeterminados de mejor esfuerzo, no como fallo.\n\n### Portapapeles\n\n- La copia de texto es de mejor esfuerzo en la capa TS: los fallos operacionales se suprimen.\n- La lectura de imagen distingue \"sin imagen\" (`null`) de fallo operacional (rechazo).\n- Termux/Linux sin interfaz gráfica se tratan como contextos no soportados para lectura de imagen (`null`).\n\n### Perfilado de trabajo\n\n- La recuperación es estricta para la llamada a la función en sí, pero la generación de artefactos es parcialmente de mejor esfuerzo (`svg` nullable).\n- El truncamiento del buffer es comportamiento esperado (buffer circular), no un error de pérdida de datos.\n\n## Consideraciones de plataforma\n\n- **Texto del portapapeles**: OSC 52 depende del soporte del terminal; el acceso nativo al portapapeles depende del entorno de escritorio/sesión.\n- **Lectura de imagen del portapapeles**: bloqueada en TS para Termux y Linux sin servidor de pantalla.\n",
	"es/natives/natives-rust-task-cancellation.md": "---\ntitle: Ejecución y cancelación de tareas nativas en Rust\ndescription: >-\n  Modelo de ejecución de tareas async en Rust con cancelación cooperativa y\n  semánticas de limpieza.\nsidebar:\n  order: 5\n  label: Cancelación de tareas\ni18n:\n  sourceHash: 0fbf45c6d463\n  translator: machine\n---\n\n# Ejecución y cancelación de tareas nativas en Rust (`pi-natives`)\n\nEste documento describe cómo `crates/pi-natives` planifica el trabajo nativo y cómo la cancelación fluye desde las opciones de JS (`timeoutMs`, `AbortSignal`) hasta la ejecución en Rust.\n\n## Archivos de implementación\n\n- `crates/pi-natives/src/task.rs`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/fd.rs`\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/ps.rs`\n\n## Primitivas principales (`task.rs`)\n\n`task.rs` define tres piezas fundamentales:\n\n1. `task::blocking(tag, cancel_token, work)`\n   - Envuelve `napi::AsyncTask` / `Task`.\n   - `compute()` se ejecuta en hilos de trabajo de libuv (para llamadas al sistema bloqueantes/síncronas o con uso intensivo de CPU).\n   - Devuelve una `Promise<T>` de JS.\n\n2. `task::future(env, tag, work)`\n   - Envuelve `env.spawn_future(...)`.\n   - Ejecuta trabajo asíncrono en el runtime de Tokio.\n   - Devuelve `PromiseRaw<'env, T>`.\n\n3. `CancelToken` / `AbortToken` / `AbortReason`\n   - `CancelToken::new(timeout_ms, signal)` combina un plazo límite + un `AbortSignal` opcional.\n   - `CancelToken::heartbeat()` es la cancelación cooperativa para bucles bloqueantes.\n   - `CancelToken::wait()` es la espera de cancelación asíncrona (`Signal` / `Timeout` / `User` Ctrl-C).\n   - `AbortToken` permite que código externo solicite la cancelación (`abort(reason)`).\n\n## `blocking` vs `future`: modelo de ejecución y selección\n\n### Usar `task::blocking`\n\nSe usa cuando el trabajo tiene uso intensivo de CPU o es fundamentalmente síncrono/bloqueante:\n\n- escaneo de archivos/regex (`grep`, `glob`, `fuzzy_find`)\n- bucle síncrono interno de PTY (`run_pty_sync` mediante `spawn_blocking`)\n- conversiones de portapapeles/imagen/html\n\nComportamiento:\n\n- La clausura de trabajo recibe un `CancelToken` clonado.\n- La cancelación solo se observa donde el código verifica `ct.heartbeat()?`.\n- Un `Err(...)` en la clausura rechaza la promesa de JS.\n\n### Usar `task::future`\n\nSe usa cuando el trabajo debe hacer `await` de operaciones asíncronas:\n\n- orquestación de sesiones de shell (`shell.run`, `executeShell`)\n- competencia de tareas (`tokio::select!`) entre completación y cancelación\n\nComportamiento:\n\n- El future puede competir la completación normal contra `ct.wait()`.\n- En la ruta de cancelación, las implementaciones asíncronas típicamente propagan la cancelación a los subsistemas internos (por ejemplo, `tokio_util::CancellationToken`) y opcionalmente fuerzan la cancelación tras un tiempo de gracia.\n\n## Mapeo de API JS ↔ exportación Rust (relevante para tareas/cancelación)\n\n| API expuesta a JS | Exportación Rust (`#[napi]`) | Planificador | Conexión de cancelación |\n|---|---|---|---|\n| `grep(options, onMatch?)` | `grep` | `task::blocking(\"grep\", ct, ...)` | `CancelToken::new(options.timeoutMs, options.signal)` + `ct.heartbeat()` |\n| `glob(options, onMatch?)` | `glob` | `task::blocking(\"glob\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` en el bucle de filtrado |\n| `fuzzyFind(options)` | `fuzzy_find` | `task::blocking(\"fuzzy_find\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` en el bucle de puntuación |\n| `shell.run(options, onChunk?)` | `Shell::run` | `task::future(env, \"shell.run\", ...)` | `ct.wait()` compitiendo contra la tarea de ejecución; se conecta con `CancellationToken` de Tokio |\n| `executeShell(options, onChunk?)` | `execute_shell` | `task::future(env, \"shell.execute\", ...)` | igual que el anterior |\n| `pty.start(options, onChunk?)` | `PtySession::start` | `task::future(env, \"pty.start\", ...)` + `spawn_blocking` interno | `CancelToken` verificado en el bucle síncrono de PTY mediante `heartbeat()` |\n| `htmlToMarkdown(html, options?)` | `html_to_markdown` | `task::blocking(\"html_to_markdown\", (), ...)` | ninguna (token `()`) |\n| `PhotonImage.parse/encode/resize` | `PhotonImage::{parse,encode,resize}` | `task::blocking(...)` | ninguna (token `()`) |\n| `copyToClipboard/readImageFromClipboard` | `copy_to_clipboard` / `read_image_from_clipboard` | `task::blocking(...)` | ninguna (token `()`) |\n\n`text.rs` y `ps.rs` actualmente no usan `task::blocking`/`task::future` y por lo tanto no participan en esta ruta de cancelación.\n\n## Ciclo de vida de la cancelación y transiciones de estado\n\n### Ciclo de vida de `CancelToken`\n\n`CancelToken` es cooperativo y con estado:\n\n```text\nCreated\n  ├─ no signal + no timeout  -> passive token (never aborts unless externally emplaced)\n  ├─ signal registered        -> waits for AbortSignal callback\n  └─ deadline set             -> timeout check becomes active\n\nRunning\n  ├─ heartbeat()/wait() sees signal   -> AbortReason::Signal\n  ├─ heartbeat()/wait() sees deadline -> AbortReason::Timeout\n  ├─ wait() sees Ctrl-C               -> AbortReason::User\n  └─ no abort                         -> continue\n\nAborted (terminal)\n  └─ first abort reason wins (atomic flag + notifier)\n```\n\n### Cancelación antes del inicio vs durante la ejecución\n\n- **Antes del inicio / antes de la primera verificación de cancelación**:\n  - Los usuarios de `task::future` que compiten con `ct.wait()` pueden resolver la cancelación inmediatamente una vez que entran en `select!`.\n  - Los usuarios de `task::blocking` solo observan la cancelación cuando el código de la clausura alcanza `heartbeat()`. Si la clausura no hace heartbeat temprano, la cancelación se retrasa.\n\n- **Durante la ejecución**:\n  - `blocking`: el siguiente `heartbeat()` devuelve `Err(\"Aborted: ...\")`.\n  - `future`: la rama `ct.wait()` gana el `select!`, luego el código cancela la maquinaria asíncrona subordinada (para shell: cancela el token de Tokio, espera hasta 2s, luego aborta la tarea).\n\n## Expectativas de heartbeat para bucles de larga duración\n\n`heartbeat()` debe ejecutarse con una cadencia predecible en bucles con conjuntos de trabajo ilimitados o grandes.\n\nPatrones observados:\n\n- `glob::filter_entries`: verifica cada entrada antes de filtrar/comparar.\n- `fd::score_entries`: verifica cada candidato escaneado.\n- `grep_sync`: verificación explícita de cancelación antes de la fase de búsqueda pesada, además de llamadas a fs-cache que también reciben el token.\n- `run_pty_sync`: verifica en cada tick del bucle (~16ms de cadencia de sleep) y mata el proceso hijo al cancelar.\n\nRegla práctica: ningún bucle sobre entrada de tamaño externo debe exceder un intervalo corto acotado sin un heartbeat.\n\n## Comportamiento de fallos y propagación de errores a JS\n\n### Tareas bloqueantes\n\nRuta de error:\n\n1. La clausura devuelve `Err(napi::Error)` (incluyendo cancelación por `heartbeat()`).\n2. `Task::compute()` devuelve `Err`.\n3. `AsyncTask` rechaza la promesa de JS.\n\nCadenas de error típicas:\n\n- `Aborted: Timeout`\n- `Aborted: Signal`\n- Errores de dominio (`Failed to decode image: ...`, `Conversion error: ...`, etc.)\n\n### Tareas de tipo future\n\nRuta de error:\n\n1. El cuerpo asíncrono devuelve `Err(napi::Error)` o el fallo del join se mapea (`... task failed: {err}`).\n2. La promesa generada por `task::future` se rechaza.\n3. Algunas APIs devuelven intencionalmente resultados de cancelación estructurados en lugar de rechazo (`ShellRunResult`/`ShellExecuteResult` con flags `cancelled`/`timed_out` y `exit_code: None`).\n\n### División en el reporte de cancelación\n\n- **Cancelación como error**: la mayoría de las exportaciones bloqueantes que usan `heartbeat()?`.\n- **Cancelación como resultado tipado**: APIs estilo shell/pty de comandos que modelan la cancelación en estructuras de resultado.\n\nElija un modelo por API y documéntelo explícitamente.\n\n## Errores comunes\n\n1. **Heartbeat faltante en bucles bloqueantes**\n   - Síntoma: el timeout/signal parece ignorarse hasta que el bucle termina.\n   - Solución: agregar `ct.heartbeat()?` al inicio del bucle y antes de pasos costosos por elemento.\n\n2. **Secciones largas no cancelables**\n   - Síntoma: picos de latencia en la cancelación durante una sola llamada grande (decodificación, ordenamiento, compresión, etc.).\n   - Solución: dividir el trabajo en fragmentos con límites de heartbeat; si es imposible, documentar la latencia.\n\n3. **Bloqueo del ejecutor asíncrono**\n   - Síntoma: la API asíncrona se detiene cuando código pesado síncrono se ejecuta directamente en el future.\n   - Solución: mover bloques de CPU/síncronos a `task::blocking` o `tokio::task::spawn_blocking`.\n\n4. **Semánticas de cancelación inconsistentes**\n   - Síntoma: una API rechaza al cancelar, otra resuelve con flags, confundiendo a los consumidores.\n   - Solución: estandarizar por dominio y mantener la documentación del wrapper alineada.\n\n5. **Olvidar el puente de cancelación en tareas asíncronas anidadas**\n   - Síntoma: el token externo se cancela pero las tareas internas de lectores/subprocesos siguen ejecutándose.\n   - Solución: conectar la cancelación al token/signal interno y aplicar un tiempo de gracia + cancelación forzada como respaldo.\n\n## Lista de verificación para nuevas exportaciones cancelables\n\n1. Clasificar el trabajo correctamente:\n   - Con uso intensivo de CPU o bloqueo síncrono -> `task::blocking`\n   - I/O asíncrono / orquestación con `await` -> `task::future`\n\n2. Exponer las entradas de cancelación cuando sea necesario:\n   - incluir `timeoutMs` y `signal` en las opciones `#[napi(object)]`\n   - crear `let ct = task::CancelToken::new(timeout_ms, signal);`\n\n3. Conectar la cancelación a través de todas las capas:\n   - bucles bloqueantes: `ct.heartbeat()?` a intervalos estables\n   - orquestación asíncrona: competir con `ct.wait()` y cancelar sub-tareas/tokens\n\n4. Decidir el contrato de cancelación:\n   - rechazar la promesa con un error de cancelación, o\n   - resolver con un tipo `{ cancelled, timedOut, ... }`\n   - mantener este contrato consistente para la familia de APIs\n\n5. Propagar fallos con contexto:\n   - mapear errores mediante `Error::from_reason(format!(\"...: {err}\"))`\n   - incluir prefijos específicos de etapa (`spawn`, `decode`, `wait`, etc.)\n\n6. Manejar la cancelación antes del inicio y durante la ejecución:\n   - la verificación/espera de cancelación debe ocurrir antes del cuerpo costoso y durante la ejecución prolongada\n\n7. Validar que no hay mal uso del ejecutor:\n   - no ejecutar trabajo síncrono largo directamente dentro de futures asíncronos sin `spawn_blocking`/wrapper de tarea bloqueante\n",
	"es/natives/natives-shell-pty-process.md": "---\ntitle: 'Internos de Shell, PTY, Proceso y Teclas en Nativos'\ndescription: >-\n  Ejecución de shell, gestión de PTY, ciclo de vida de procesos y manejo de\n  eventos de teclado en la capa nativa.\nsidebar:\n  order: 4\n  label: 'Shell, PTY y proceso'\ni18n:\n  sourceHash: 00ea95614c6a\n  translator: machine\n---\n\n# Internos de Shell, PTY, Proceso y Teclas en Nativos\n\nEste documento cubre las **primitivas de ejecución/proceso/terminal** en `@f5-sales-demo/pi-natives`: `shell`, `pty`, `ps` y `keys`, utilizando los términos de arquitectura de `docs/natives-architecture.md`.\n\n## Archivos de implementación\n\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/shell/windows.rs` (solo Windows)\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/ps.rs`\n- `crates/pi-natives/src/keys.rs`\n- `crates/pi-natives/src/task.rs` (comportamiento de cancelación compartido utilizado por shell/pty)\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/bindings.ts`\n\n## Propiedad por capa\n\n- **Capa de envoltura/API de TS** (`packages/natives/src/*`): puntos de entrada tipados, superficie de cancelación (`timeoutMs`, `AbortSignal`) y ergonomía de JS.\n- **Capa del módulo N-API de Rust** (`crates/pi-natives/src/*`): ejecución de procesos shell/PTY, recorrido/terminación del árbol de procesos y análisis de secuencias de teclas.\n- **Puerta de validación** (`native.ts`, nivel de arquitectura): garantiza que las exportaciones requeridas (`Shell`, `executeShell`, `PtySession`, `killTree`, `listDescendants`, helpers de teclas) existan antes de que se utilicen los envoltorios.\n\n## Subsistema Shell (`shell`)\n\n### Modelo de API\n\nSe exponen dos modos de ejecución:\n\n1. **Ejecución única** mediante `executeShell(options, onChunk?)`.\n2. **Sesión persistente** mediante `new Shell(options?)` seguido de llamadas repetidas a `shell.run(...)`.\n\nAmbos transmiten la salida a través de un callback seguro para hilos y devuelven `{ exitCode?, cancelled, timedOut }`.\n\n### Creación de sesión y modelo de entorno\n\nRust crea `brush_core::Shell` con:\n\n- modo no interactivo,\n- `do_not_inherit_env: true`,\n- reconstrucción explícita del entorno a partir del entorno del host,\n- lista de exclusión para variables sensibles al shell (`PS1`, `PWD`, `SHLVL`, exportaciones de funciones bash, etc.).\n\nComportamiento del entorno de sesión:\n\n- `ShellOptions.sessionEnv` se aplica una sola vez en la creación de la sesión.\n- `ShellRunOptions.env` tiene ámbito de comando (`EnvironmentScope::Command`) y se elimina tras cada ejecución.\n- `PATH` se fusiona de forma especial en Windows con deduplicación sin distinción entre mayúsculas y minúsculas.\n\nEnriquecimiento de rutas exclusivo de Windows (`shell/windows.rs`): las rutas de Git-for-Windows descubiertas (`cmd`, `bin`, `usr/bin`) se añaden al final si están presentes y no se han incluido ya.\n\n### Ciclo de vida en tiempo de ejecución y transiciones de estado\n\nEl shell persistente (`Shell.run`) utiliza esta máquina de estados:\n\n- **Inactivo/No inicializado**: `session: None`.\n- **En ejecución**: el primer `run()` crea la sesión de forma diferida, almacena el token `current_abort` y ejecuta el comando.\n- **Completado + keepalive**: si el flujo de control de ejecución es `Normal`, `current_abort` se borra y la sesión se reutiliza.\n- **Completado + desmontaje**: si el flujo de control está relacionado con bucle/script/salida del shell (`BreakLoop`, `ContinueLoop`, `ReturnFromFunctionOrScript`, `ExitShell`), la sesión se descarta (`session: None`).\n- **Cancelado/Tiempo de espera agotado**: la tarea de ejecución se cancela, espera de gracia (2 s) y luego se fuerza el aborto; la sesión se descarta.\n- **Error**: la sesión se descarta.\n\nEl shell de ejecución única (`executeShell`) siempre crea y descarta una sesión nueva por llamada.\n\n### Comportamiento de transmisión/salida\n\n- La salida estándar y de error se enrutan a una tubería compartida y se leen de forma concurrente.\n- El lector decodifica UTF-8 de forma incremental; las secuencias de bytes inválidas emiten fragmentos de reemplazo `U+FFFD`.\n- Tras la finalización del proceso, el drenaje de salida tiene guardas de inactividad/máximo (`250 ms` de inactividad, `2 s` máximo) para evitar bloqueos cuando trabajos en segundo plano mantienen descriptores abiertos.\n\n### Cancelación, tiempo de espera y trabajos en segundo plano\n\n- `CancelToken` se construye a partir de `timeoutMs` y un `AbortSignal` opcional.\n- Al cancelar/agotar el tiempo, se activa el token de cancelación del shell; luego la tarea tiene una ventana de gracia de 2 s antes del aborto forzado.\n- Si se produce la cancelación, los trabajos en segundo plano se terminan (`TERM`, luego `KILL` diferido) mediante los metadatos de trabajos de brush.\n\nComportamiento de `Shell.abort()`:\n\n- aborta únicamente el comando en ejecución actual de esa instancia de `Shell`,\n- no tiene efecto (éxito sin operación) cuando no hay nada en ejecución.\n\n### Comportamiento ante fallos\n\nLos errores más comunes que se exponen incluyen:\n\n- fallos de inicialización de sesión (`Failed to initialize shell`),\n- errores de directorio de trabajo (`Failed to set cwd`),\n- fallos de establecimiento/extracción de entorno,\n- fallos de la fuente de instantánea,\n- fallos de creación/clonación de tubería,\n- fallo de ejecución (`Shell execution failed: ...`),\n- fallos del envoltorio de tarea (`Shell execution task failed: ...`).\n\nIndicadores de cancelación a nivel de resultado:\n\n- tiempo de espera agotado -> `exitCode: undefined`, `timedOut: true`.\n- señal de aborto -> `exitCode: undefined`, `cancelled: true`.\n\n## Subsistema PTY (`pty`)\n\n### Modelo de API\n\n`new PtySession()` expone:\n\n- `start(options, onChunk?) -> Promise<{ exitCode?, cancelled, timedOut }>`\n- `write(data)`\n- `resize(cols, rows)`\n- `kill()`\n\n### Ciclo de vida en tiempo de ejecución y transiciones de estado\n\nMáquina de estados de `PtySession`:\n\n- **Inactivo**: `core: None`.\n- **Reservado**: `start()` instala el canal de control de forma síncrona (`core: Some`) antes de que comience el trabajo asíncrono, por lo que `write/resize/kill` pasan a ser válidos de inmediato.\n- **En ejecución**: el bucle de PTY bloqueante gestiona el estado del proceso hijo, los eventos del lector, el latido de cancelación y los mensajes de control.\n- **Terminal cerrado**: salida del proceso hijo + finalización del lector.\n- **Finalizado**: `core` siempre se restablece a `None` después de que la tarea de inicio se complete (con éxito o con error).\n\nGuardia de concurrencia:\n\n- iniciar mientras ya está en ejecución devuelve `PTY session already running`.\n\n### Patrones de creación/adjunto/escritura/lectura/terminación\n\n- PTY abierto mediante `portable_pty::native_pty_system().openpty(...)`.\n- El comando actualmente se ejecuta como `sh -lc <command>` con anulaciones opcionales de `cwd` y entorno.\n- `write()` envía bytes sin procesar a la entrada estándar del PTY.\n- `resize()` limita las dimensiones (`cols 20..400`, `rows 5..200`) y llama al redimensionamiento del maestro.\n- `kill()` marca la ejecución como cancelada y termina el proceso hijo.\n\nRuta de salida:\n\n- un hilo lector dedicado lee el flujo maestro,\n- decodificación incremental de UTF-8 con reemplazo `U+FFFD` en bytes inválidos,\n- fragmentos reenviados a través del callback seguro para hilos de N-API.\n\n### Semántica de cancelación y tiempo de espera\n\n- `timeoutMs` y `AbortSignal` alimentan un `CancelToken`.\n- el bucle llama a `ct.heartbeat()` periódicamente; el aborto activa la terminación del proceso hijo.\n- la clasificación del tiempo de espera se basa en cadenas (subcadena `\"Timeout\"` en el error de latido).\n\n### Comportamiento ante fallos\n\nLas superficies de error incluyen:\n\n- fallo de asignación/apertura de PTY,\n- fallo de inicio del PTY,\n- fallo de adquisición del escritor/lector,\n- fallos de estado/espera del proceso hijo,\n- envenenamiento de bloqueo,\n- desconexión del canal de control (`PTY session is no longer available`).\n\nFallos de llamadas de control cuando no está en ejecución:\n\n- `write/resize/kill` devuelven `PTY session is not running`.\n\n## Subsistema de árbol de procesos (`ps`)\n\n### Modelo de API\n\n- `killTree(pid, signal) -> number`\n- `listDescendants(pid) -> number[]`\n\nEl envoltorio de TS también registra la integración nativa de kill-tree en las utilidades compartidas mediante `setNativeKillTree(native.killTree)`.\n\n### Implementación específica por plataforma\n\n- **Linux**: lee recursivamente `/proc/<pid>/task/<pid>/children`.\n- **macOS**: utiliza `libproc` `proc_listchildpids`.\n- **Windows**: realiza una instantánea de la tabla de procesos con `CreateToolhelp32Snapshot`, construye un mapa padre->hijos y termina con `OpenProcess(PROCESS_TERMINATE)` + `TerminateProcess`.\n\n### Comportamiento de kill-tree\n\n- Los descendientes se recopilan de forma recursiva.\n- El orden de terminación es de abajo hacia arriba (los descendientes más profundos primero) para reducir la reasignación de procesos huérfanos.\n- El pid raíz se termina en último lugar.\n- El valor de retorno es el recuento de terminaciones exitosas.\n\nComportamiento de señales:\n\n- POSIX: la `signal` proporcionada se pasa a `kill`.\n- Windows: `signal` se ignora; la terminación es un proceso de terminación incondicional.\n\n### Comportamiento ante fallos\n\nEste módulo es intencionalmente no lanzador de excepciones en la superficie de la API:\n\n- las ramas del árbol de procesos faltantes o inaccesibles se omiten,\n- los fallos de terminación por pid se contabilizan como no exitosos (no como errores),\n- una búsqueda fallida típicamente produce `[]` de `listDescendants` y `0` de `killTree`.\n\n## Subsistema de análisis de teclas (`keys`)\n\n### Modelo de API\n\nHelpers expuestos:\n\n- `parseKey(data, kittyProtocolActive)`\n- `matchesKey(data, keyId, kittyProtocolActive)`\n- `parseKittySequence(data)`\n- `matchesKittySequence(data, expectedCodepoint, expectedModifier)`\n- `matchesLegacySequence(data, keyName)`\n\n### Modelo de análisis\n\nEl analizador combina:\n\n- asignaciones directas de un solo byte (`enter`, `tab`, `ctrl+<letter>`, ASCII imprimible),\n- búsqueda O(1) de secuencias de escape heredadas (mapa PHF),\n- análisis de `modifyOtherKeys` de xterm,\n- análisis del protocolo Kitty (`CSI u`, `CSI ~`, `CSI 1;...<letter>`),\n- normalización a IDs de tecla (`ctrl+c`, `shift+tab`, `pageUp`, `f5`, etc.).\n\nManejo de modificadores:\n\n- solo se comparan los bits de shift/alt/ctrl para la coincidencia de teclas,\n- los bits de bloqueo se enmascaran antes de las comparaciones.\n\nComportamiento de distribución:\n\n- la reserva de distribución base está intencionalmente limitada para que las distribuciones reasignadas no creen coincidencias falsas para letras/símbolos ASCII.\n\n### Comportamiento ante fallos\n\n- Las secuencias no reconocidas o inválidas producen `null` desde las funciones de análisis.\n- Las funciones de coincidencia devuelven `false` ante un fallo de análisis o una discrepancia.\n- No se expone ninguna superficie de error lanzado para entradas de teclas malformadas.\n\n## Mapeo de API del envoltorio JS ↔ exportaciones de Rust\n\n### Shell + PTY + Proceso\n\n| API del envoltorio TS | Exportación N-API de Rust | Notas |\n|---|---|---|\n| `executeShell(options, onChunk?)` | `executeShell` (`execute_shell`) | Ejecución de shell de un solo uso |\n| `new Shell(options?)` | clase `Shell` | Sesión de shell persistente |\n| `shell.run(options, onChunk?)` | `Shell::run` | Reutiliza la sesión en flujo de control keepalive |\n| `shell.abort()` | `Shell::abort` | Aborta la ejecución activa de esa instancia de shell |\n| `new PtySession()` | clase `PtySession` | Sesión PTY con estado |\n| `pty.start(options, onChunk?)` | `PtySession::start` | Ejecución PTY interactiva |\n| `pty.write(data)` | `PtySession::write` | Paso directo de entrada estándar sin procesar |\n| `pty.resize(cols, rows)` | `PtySession::resize` | Dimensiones del terminal con límites aplicados |\n| `pty.kill()` | `PtySession::kill` | Termina forzosamente el proceso hijo PTY activo |\n| `killTree(pid, signal)` | `killTree` (`kill_tree`) | Terminación del árbol de procesos con los hijos primero |\n| `listDescendants(pid)` | `listDescendants` (`list_descendants`) | Listado recursivo de descendientes |\n\n### Teclas\n\n| API del envoltorio TS | Exportación N-API de Rust | Notas |\n|---|---|---|\n| `matchesKittySequence(data, cp, mod)` | `matchesKittySequence` (`matches_kitty_sequence`) | Coincidencia de codepoint+modificador Kitty |\n| `parseKey(data, kittyProtocolActive)` | `parseKey` (`parse_key`) | Analizador de ID de tecla normalizado |\n| `matchesLegacySequence(data, keyName)` | `matchesLegacySequence` (`matches_legacy_sequence`) | Comprobación exacta del mapa de secuencias heredadas |\n| `parseKittySequence(data)` | `parseKittySequence` (`parse_kitty_sequence`) | Resultado de análisis estructurado de Kitty |\n| `matchesKey(data, keyId, kittyProtocolActive)` | `matchesKey` (`matches_key`) | Comparador de teclas de alto nivel |\n\n## Notas sobre limpieza de sesiones abandonadas y finalización\n\n- **Sesión de shell persistente**: si una ejecución se cancela/agota el tiempo de espera/falla/tiene un flujo de control que no es keepalive, Rust descarta explícitamente el estado de sesión interno. Las ejecuciones normales exitosas mantienen la sesión para su reutilización.\n- **Sesión PTY**: `core` siempre se borra después de que `start()` finalice, incluidas las rutas de error.\n- **No se expone ningún contrato de terminación explícito impulsado por finalizador de JS** por parte de los envoltorios; la limpieza está vinculada principalmente a las rutas de finalización/cancelación de ejecución. Los llamadores deben utilizar `timeoutMs`, `AbortSignal`, `shell.abort()` o `pty.kill()` para un desmontaje determinista.\n",
	"es/natives/natives-text-search-pipeline.md": "---\ntitle: Canalización de texto nativo y búsqueda\ndescription: >-\n  Canalización de búsqueda de texto nativo con indexación de contenido de\n  archivos basada en grep, glob y ripgrep.\nsidebar:\n  order: 6\n  label: Canalización de texto y búsqueda\ni18n:\n  sourceHash: 0e93462fdd12\n  translator: machine\n---\n\n# Canalización de texto/búsqueda nativa\n\nEste documento mapea la superficie de texto/búsqueda (`grep`, `glob`, `text`, `highlight`) de `@f5-sales-demo/pi-natives` desde los envoltorios de TypeScript hasta las exportaciones N-API de Rust y de vuelta a los objetos de resultado de JS.\n\nLa terminología sigue `docs/natives-architecture.md`:\n\n- **Wrapper**: API de TS en `packages/natives/src/*`\n- **Capa de módulo Rust**: exportaciones N-API en `crates/pi-natives/src/*`\n- **Caché de escaneo compartido**: caché de entradas de directorio respaldada por `fs_cache` utilizada por los flujos de descubrimiento/búsqueda\n\n## Archivos de implementación\n\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/glob_util.rs`\n- `crates/pi-natives/src/fs_cache.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/highlight.rs`\n- `crates/pi-natives/src/fd.rs`\n\n## Mapeo de API JS ↔ exportación Rust\n\n| API del wrapper JS | Exportación Rust (`#[napi]`, snake_case -> camelCase) | Módulo Rust |\n| --- | --- | --- |\n| `grep(options, onMatch?)` | `grep` | `grep.rs` |\n| `searchContent(content, options)` | `search` | `grep.rs` |\n| `hasMatch(content, pattern, options?)` | `hasMatch` | `grep.rs` |\n| `fuzzyFind(options)` | `fuzzyFind` | `fd.rs` |\n| `glob(options, onMatch?)` | `glob` | `glob.rs` |\n| `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `fs_cache.rs` |\n| `wrapTextWithAnsi(text, width)` | `wrapTextWithAnsi` | `text.rs` |\n| `truncateToWidth(text, maxWidth, ellipsis, pad)` | `truncateToWidth` | `text.rs` |\n| `sliceWithWidth(line, startCol, length, strict?)` | `sliceWithWidth` | `text.rs` |\n| `extractSegments(line, beforeEnd, afterStart, afterLen, strictAfter)` | `extractSegments` | `text.rs` |\n| `sanitizeText(text)` | `sanitizeText` | `text.rs` |\n| `visibleWidth(text)` | `visibleWidth` | `text.rs` |\n| `highlightCode(code, lang, colors)` | `highlightCode` | `highlight.rs` |\n| `supportsLanguage(lang)` | `supportsLanguage` | `highlight.rs` |\n| `getSupportedLanguages()` | `getSupportedLanguages` | `highlight.rs` |\n\n## Descripción general de la canalización por subsistema\n\n## 1) Búsqueda por expresión regular (`grep`, `searchContent`, `hasMatch`)\n\n### Flujo de entrada/opciones\n\n1. El wrapper de TS reenvía las opciones al módulo nativo:\n   - `grep/index.ts` pasa las `options` casi sin cambios y envuelve el callback de `(match) => void` a la forma de callback thread-safe de napi `(err, match)`.\n   - `searchContent` y `hasMatch` pasan directamente una cadena de texto o `Uint8Array`.\n2. Las estructuras de opciones de Rust en `grep.rs` deserializan campos en camelCase (`ignoreCase`, `maxCount`, `contextBefore`, `contextAfter`, `maxColumns`, `timeoutMs`).\n3. `grep` crea un `CancelToken` a partir de `timeoutMs` + `AbortSignal` y se ejecuta dentro de `task::blocking(\"grep\", ...)`.\n\n### Ramas de ejecución\n\n- **Rama en memoria (utilidad pura)**\n  - `search` → `search_sync` → `run_search` sobre los bytes de contenido proporcionados.\n  - Sin escaneo del sistema de archivos, sin `fs_cache`.\n- **Rama de archivo único (dependiente del sistema de archivos)**\n  - `grep_sync` resuelve la ruta, verifica que los metadatos correspondan a un archivo y transmite hasta `MAX_FILE_BYTES` por archivo (`4 MiB`) a través del comparador de ripgrep.\n- **Rama de directorio (dependiente del sistema de archivos)**\n  - Búsqueda opcional en caché mediante `fs_cache::get_or_scan` cuando `cache: true`.\n  - Escaneo nuevo mediante `fs_cache::force_rescan` cuando `cache: false`.\n  - Reverificación opcional de resultados vacíos cuando la antigüedad de la caché supera `empty_recheck_ms()`.\n  - Filtrado de entradas: solo archivos + filtro glob opcional (`glob_util`) + mapeo de filtro de tipo opcional (`js`, `ts`, `rust`, etc.).\n\n### Semántica de búsqueda/recolección\n\n- Motor de expresiones regulares: `grep_regex::RegexMatcherBuilder` con `ignoreCase` y `multiline`.\n- Resolución de contexto:\n  - `contextBefore/contextAfter` anulan el `context` heredado.\n  - Los modos que no son de contenido anulan la recolección de contexto.\n- Modos de salida:\n  - `content` => un `GrepMatch` por coincidencia.\n  - `count` y `filesWithMatches` se mapean a entradas de estilo contador (`lineNumber=0`, `line=\"\"`, `matchCount` establecido).\n- Límites:\n  - `offset` global y `maxCount` aplicados entre archivos.\n  - La ruta paralela se usa solo cuando `maxCount` no está definido y `offset == 0`; de lo contrario, la ruta secuencial preserva la semántica determinista de offset/límite global.\n\n### Conformación del resultado de vuelta a JS\n\n- Los campos de `SearchResult`/`GrepResult` de Rust se mapean a tipos de TS mediante la conversión de campos de objetos N-API.\n- Los contadores se limitan a `u32` antes de cruzar N-API.\n- Los booleanos opcionales se omiten a menos que sean verdaderos en algunas rutas (`limitReached`).\n- El callback de transmisión recibe cada `GrepMatch` conformado (entrada de contenido o de contador).\n\n### Comportamiento ante fallos\n\n- `searchContent` devuelve `SearchResult.error` en caso de fallos de expresión regular/búsqueda en lugar de lanzar una excepción.\n- `grep` rechaza ante errores graves (ruta inválida, glob/expresión regular inválida, tiempo de espera de cancelación/cancelación).\n- `hasMatch` devuelve `Result<bool>` y lanza una excepción ante errores de patrón inválido/decodificación UTF-8.\n- Los errores de apertura/búsqueda de archivos en escaneos de múltiples archivos se omiten por archivo; el escaneo continúa.\n\n### Manejo de expresiones regulares malformadas\n\n`grep.rs` sanea las llaves antes de compilar la expresión regular:\n\n- Las llaves con apariencia de repetición inválida se escapan (`{`/`}` -> `\\{`/`\\}`) cuando no pueden formar `{N}`, `{N,}`, `{N,M}`.\n- Esto evita que fragmentos literales de plantillas comunes (por ejemplo, `${platform}`) fallen como repeticiones malformadas.\n- La sintaxis de expresión regular inválida restante aún devuelve un error de expresión regular.\n\n## 2) Descubrimiento de archivos (`glob`) y búsqueda difusa de rutas (`fuzzyFind`)\n\n`glob` y `fuzzyFind` comparten los escaneos de `fs_cache`; la lógica de coincidencia difiere.\n\n### Flujo de `glob`\n\n1. Wrapper de TS (`glob/index.ts`):\n   - `path.resolve(options.path)`.\n   - Valores predeterminados: `pattern=\"*\"`, `hidden=false`, `gitignore=true`, `recursive=true`.\n2. Rust `glob` construye `GlobConfig` y compila el patrón mediante `glob_util::compile_glob`.\n3. Fuente de entradas:\n   - `cache=true` => `get_or_scan` + `force_rescan` opcional para caché vacía expirada.\n   - `cache=false` => `force_rescan(..., store=false)` (solo nuevo).\n4. Filtrado:\n   - Omitir `.git` siempre.\n   - Omitir `node_modules` a menos que se solicite (`includeNodeModules` o patrón que mencione node_modules).\n   - Aplicar coincidencia glob.\n   - Aplicar filtro de tipo de archivo; los filtros de `file/dir` de enlaces simbólicos resuelven los metadatos del destino.\n5. Ordenamiento opcional por mtime descendente (`sortByMtime`) antes de truncar a `maxResults`.\n\n### Flujo de `fuzzyFind` (implementado en `fd.rs`)\n\n1. El wrapper de TS se exporta desde el módulo `grep`, pero la implementación de Rust reside en `fd.rs`.\n2. Fuente de escaneo compartida desde `fs_cache` con la misma división de caché/sin caché y política de reverificación de vacío expirado.\n3. Puntuación:\n   - puntuación difusa basada en exacta / comienza con / contiene / subsecuencia\n   - ruta de puntuación normalizada por separadores/puntuación\n   - bonificación por directorio y desempate determinista (`score desc`, luego `path asc`)\n4. Las entradas de enlaces simbólicos se excluyen de los resultados difusos.\n\n### Comportamiento ante fallos\n\n- Patrón glob inválido => error de `glob_util::compile_glob`.\n- La raíz de búsqueda debe ser un directorio existente (`resolve_search_path`), de lo contrario error.\n- La cancelación/los tiempos de espera se propagan como errores de cancelación mediante verificaciones de `CancelToken::heartbeat()` en los bucles.\n\n### Manejo de globs malformados\n\n`glob_util::build_glob_pattern` es tolerante:\n\n- Normaliza `\\` a `/`.\n- Agrega automáticamente el prefijo `**/` a patrones recursivos simples cuando `recursive=true`.\n- Cierra automáticamente grupos de alternancia `{...` no balanceados antes de compilar.\n\n## 3) Ciclo de vida del escaneo/caché compartido (`fs_cache`)\n\n`fs_cache` almacena los resultados del escaneo como entradas relativas normalizadas (`path`, `fileType`, `mtime` opcional) indexadas por:\n\n- raíz de búsqueda canónica\n- `include_hidden`\n- `use_gitignore`\n\n### Transiciones de estado de la caché\n\n1. **Fallo / deshabilitada**\n   - TTL es `0` o clave ausente/expirada -> nuevo `collect_entries`.\n2. **Acierto**\n   - Antigüedad de entrada `< cache_ttl_ms()` -> devolver entradas en caché + `cache_age_ms`.\n3. **Reverificación de vacío expirado** (política del llamador en `glob`/`grep`/`fd`)\n   - Si la consulta produce cero coincidencias y `cache_age_ms >= empty_recheck_ms()`, forzar un nuevo escaneo.\n4. **Invalidación**\n   - `invalidateFsScanCache(path?)`:\n     - sin argumento: borrar todas las claves\n     - argumento de ruta: eliminar claves cuya raíz tiene como prefijo esa ruta destino\n\n### Compensación de resultados expirados\n\n- La caché favorece los escaneos repetidos de baja latencia sobre la consistencia inmediata.\n- La ventana de TTL puede devolver positivos/negativos expirados.\n- La reverificación de resultados vacíos reduce los negativos expirados en escaneos en caché más antiguos a costa de un escaneo adicional.\n- La invalidación explícita es el mecanismo de corrección previsto tras mutaciones de archivos.\n\n## 4) Utilidades de texto ANSI (`text`)\n\nEstas son utilidades puramente en memoria (sin escaneo del sistema de archivos).\n\n### Límites y responsabilidades\n\n- **`text.rs` gestiona la semántica de celdas de terminal**:\n  - análisis de secuencias ANSI\n  - ancho y segmentación con reconocimiento de grafemas\n  - comportamiento de ajuste/truncado/saneamiento\n- **El truncado de líneas en `grep.rs` (`maxColumns`) es independiente**:\n  - truncado simple por límite de caracteres de líneas coincidentes con `...`\n  - no preserva el estado ANSI y no tiene reconocimiento del ancho de celda de terminal\n\n### Comportamientos clave\n\n- `wrapTextWithAnsi`: ajusta por ancho visible, transporta los códigos SGR activos a través de las líneas ajustadas.\n- `truncateToWidth`: truncado de celdas visibles con política de puntos suspensivos (`Unicode`, `Ascii`, `Omit`), relleno derecho opcional y ruta rápida que devuelve la cadena JS original cuando no cambia.\n- `sliceWithWidth`: segmentación de columnas con aplicación de ancho estricto opcional.\n- `extractSegments`: extrae segmentos antes/después alrededor de una superposición mientras restaura el estado ANSI para el segmento `after`.\n- `sanitizeText`: elimina secuencias ANSI + caracteres de control, descarta sustitutos sueltos, normaliza CR/LF eliminando `\\r`.\n- `visibleWidth`: cuenta las celdas de terminal visibles (las tabulaciones usan `TAB_WIDTH` fijo de la implementación de Rust).\n\n### Comportamiento ante fallos\n\nLas funciones de texto generalmente devuelven una salida transformada determinista; los errores se limitan a los límites de conversión de cadenas JS (fallos de conversión de argumentos N-API).\n\n## 5) Resaltado de sintaxis (`highlight`)\n\n`highlight.rs` es transformación pura (sin FS, sin caché).\n\n### Flujo\n\n1. El wrapper reenvía `code`, `lang` opcional y la paleta de colores ANSI.\n2. Rust resuelve la sintaxis mediante:\n   - búsqueda por token/nombre\n   - búsqueda por extensión\n   - tabla de alias de respaldo (`ts/tsx/js -> JavaScript`, etc.)\n   - recurso de sintaxis de texto sin formato cuando no se resuelve\n3. Analiza cada línea con `ParseState` de syntect y la pila de alcances.\n4. Mapea los alcances a 11 categorías de color semánticas e inyecta/restablece los códigos de color ANSI.\n\n### Comportamiento ante fallos\n\n- El fallo al analizar una línea no falla la llamada: esa línea se agrega sin resaltar y el procesamiento continúa.\n- El lenguaje desconocido/no compatible recurre a la sintaxis de texto sin formato.\n\n## Utilidades puras vs. flujos dependientes del sistema de archivos\n\n| Flujo | Acceso al sistema de archivos | Caché compartida | Notas |\n| --- | --- | --- | --- |\n| `searchContent` / `hasMatch` | No | No | expresión regular solo sobre bytes/cadena proporcionados |\n| Funciones del módulo `text` | No | No | solo ANSI/ancho/saneamiento |\n| Funciones del módulo `highlight` | No | No | solo sintaxis + coloreado ANSI |\n| `glob` | Sí | Opcional | escaneos de directorio + filtrado glob |\n| `fuzzyFind` | Sí | Opcional | escaneos de directorio + puntuación difusa |\n| `grep` (ruta de archivo/directorio) | Sí | Opcional (modo directorio) | ripgrep sobre archivos, filtros/callback opcionales |\n\n## Resumen del ciclo de vida de extremo a extremo\n\n1. El llamador invoca el wrapper de TS con opciones tipadas.\n2. El wrapper normaliza los valores predeterminados (especialmente `glob`) y los reenvía a la exportación `native.*`.\n3. Rust valida/normaliza las opciones y construye la configuración del comparador/búsqueda.\n4. Para los flujos del sistema de archivos, las entradas se escanean (acierto/fallo/reescaneo de caché) y luego se filtran/puntúan.\n5. Los bucles de trabajo llaman periódicamente al heartbeat de cancelación; el tiempo de espera/cancelación puede terminar la ejecución.\n6. Rust conforma las salidas en objetos N-API (`lineNumber`, `matchCount`, `limitReached`, etc.).\n7. El wrapper de TS devuelve objetos JS tipados (y callbacks opcionales por coincidencia para `grep`/`glob`).\n",
	"es/natives/porting-to-natives.md": "---\ntitle: Portando a pi-natives (N-API) — Notas de campo\ndescription: >-\n  Notas de campo para migrar código de child_process y shell de Node.js a la\n  capa nativa Rust N-API.\nsidebar:\n  order: 9\n  label: Portando a pi-natives\ni18n:\n  sourceHash: 4f5150286535\n  translator: machine\n---\n\n# Portando a pi-natives (N-API) — Notas de campo\n\nEsta es una guía práctica para mover rutas críticas a `crates/pi-natives` y conectarlas a través de los bindings de JS. Existe para evitar que los mismos errores ocurran dos veces.\n\n## Cuándo portar\n\nPorte cuando cualquiera de estas condiciones sea verdadera:\n\n- La ruta crítica se ejecuta en bucles de renderizado, actualizaciones frecuentes de UI o lotes grandes.\n- Las asignaciones de JS dominan (rotación de strings, backtracking de regex, arrays grandes).\n- Ya tiene una línea base en JS y puede comparar ambas versiones en paralelo.\n- El trabajo está limitado por CPU o es I/O bloqueante que puede ejecutarse en el pool de hilos de libuv.\n- El trabajo es I/O asíncrono que puede ejecutarse en el runtime de Tokio (por ejemplo, ejecución de shell).\n\nEvite portar lo que dependa de estado exclusivo de JS o importaciones dinámicas. Las exportaciones de N-API deben ser puras, datos de entrada/datos de salida. El trabajo de larga duración debe pasar por `task::blocking` (limitado por CPU/I/O bloqueante) o `task::future` (I/O asíncrono) con cancelación.\n\n## Anatomía de una exportación nativa\n\n**Lado Rust:**\n\n- La implementación reside en `crates/pi-natives/src/<module>.rs`. Si añade un nuevo módulo, regístrelo en `crates/pi-natives/src/lib.rs`.\n- Exporte con `#[napi]`; las exportaciones en snake_case se convierten a camelCase automáticamente. Use `js_name` explícito solo para alias verdaderos/nombres no predeterminados. Use `#[napi(object)]` para structs.\n- Use `task::blocking(tag, cancel_token, work)` (ver `crates/pi-natives/src/task.rs`) para trabajo limitado por CPU o bloqueante. Use `task::future(env, tag, work)` para trabajo asíncrono que necesite Tokio (por ejemplo, sesiones de shell). Pase un `CancelToken` cuando exponga `timeoutMs` o `AbortSignal`.\n\n**Lado JS:**\n\n- `packages/natives/src/bindings.ts` contiene la interfaz base `NativeBindings`.\n- `packages/natives/src/<module>/types.ts` define los tipos TS y amplía `NativeBindings` mediante declaration merging.\n- `packages/natives/src/native.ts` importa cada archivo `<module>/types.ts` para activar las declaraciones.\n- `packages/natives/src/<module>/index.ts` envuelve el binding `native` de `packages/natives/src/native.ts`.\n- `packages/natives/src/native.ts` carga el addon y `validateNative` valida las exportaciones requeridas.\n- `packages/natives/src/index.ts` re-exporta el wrapper para los consumidores en `packages/*`.\n\n## Lista de verificación para portar\n\n1. **Añadir la implementación en Rust**\n\n- Coloque la lógica principal en una función Rust simple.\n- Si es un nuevo módulo, añádalo a `crates/pi-natives/src/lib.rs`.\n- Expóngalo con `#[napi]` para que el mapeo predeterminado snake_case -> camelCase se mantenga consistente.\n- Mantenga las firmas propias y simples: `String`, `Vec<String>`, `Uint8Array`, o `Either<JsString, Uint8Array>` para entradas grandes de string/bytes.\n- Para trabajo limitado por CPU o bloqueante, use `task::blocking`; para trabajo asíncrono, use `task::future`. Pase un `CancelToken` y llame a `heartbeat()` dentro de bucles largos.\n\n2. **Conectar los bindings JS**\n\n- Añada los tipos y la ampliación de `NativeBindings` en `packages/natives/src/<module>/types.ts`.\n- Importe `./<module>/types` en `packages/natives/src/native.ts` para activar el declaration merging.\n- Añada un wrapper en `packages/natives/src/<module>/index.ts` que llame a `native`.\n- Re-exporte desde `packages/natives/src/index.ts`.\n\n3. **Actualizar la validación nativa**\n\n- Añada `checkFn(\"newExport\")` en `validateNative` (`packages/natives/src/native.ts`).\n\n4. **Añadir benchmarks**\n\n- Coloque los benchmarks junto al paquete propietario (`packages/tui/bench`, `packages/natives/bench`, o `packages/coding-agent/bench`).\n- Incluya una línea base JS y la versión nativa en la misma ejecución.\n- Use `Bun.nanoseconds()` y un conteo de iteraciones fijo.\n- Mantenga las entradas del benchmark pequeñas y realistas (datos reales observados en la ruta crítica).\n\n5. **Compilar el binario nativo**\n\n- `bun --cwd=packages/natives run build`\n- Use `bun --cwd=packages/natives run build` y establezca `PI_DEV=1` si desea diagnósticos del loader durante las pruebas.\n\n6. **Ejecutar el benchmark**\n\n- `bun run packages/<pkg>/bench/<bench>.ts` (o `bun --cwd=packages/natives run bench`)\n\n7. **Decidir sobre el uso**\n\n- Si lo nativo es más lento, **mantenga JS** y deje la exportación nativa sin usar.\n- Si lo nativo es más rápido, cambie los sitios de llamada al wrapper nativo.\n\n## Puntos problemáticos y cómo evitarlos\n\n### 1) Un `pi_natives.node` obsoleto impide nuevas exportaciones\n\nEl loader prefiere el binario etiquetado por plataforma en `packages/natives/native` (`pi_natives.<platform>-<arch>.node`). `PI_DEV=1` ahora solo habilita diagnósticos del loader; ya no cambia a un nombre de archivo de addon de desarrollo separado. También existe un fallback `pi_natives.node`. Los binarios compilados se extraen a `~/.xcsh/natives/<version>/pi_natives.<platform>-<arch>.node`. Si alguno de estos está obsoleto, las exportaciones no se actualizarán.\n\n**Solución:** elimine el archivo obsoleto antes de recompilar.\n\n```bash\nrm packages/natives/native/pi_natives.linux-x64.node\nrm packages/natives/native/pi_natives.node\nbun --cwd=packages/natives run build\n```\n\nSi está ejecutando un binario compilado, elimine el directorio del addon en caché:\n\n```bash\nrm -rf ~/.xcsh/natives/<version>\n```\n\nLuego verifique que la exportación existe en el binario:\n\n```bash\nbun -e 'const tag = `${process.platform}-${process.arch}`; const mod = require(`./packages/natives/native/pi_natives.${tag}.node`); console.log(Object.keys(mod).includes(\"newExport\"));'\n```\n\n### 2) Errores de \"Missing exports\" de `validateNative`\n\nEsto es **bueno** — previene desajustes silenciosos. Cuando vea esto:\n\n```\nNative addon missing exports ... Missing: visibleWidth\n```\n\nsignifica que su binario está obsoleto, el nombre de la exportación Rust (o el alias explícito cuando se usa) no coincide con el nombre JS, o la exportación nunca se compiló. Corrija la compilación y la discrepancia de nombres, no debilite la validación.\n\n### 3) Discrepancia de firma en Rust\n\nManténgalo simple y propio. `String`, `Vec<String>` y `Uint8Array` funcionan. Evite referencias como `&str` en exportaciones públicas. Si necesita datos estructurados, envuélvalos en structs con `#[napi(object)]`.\n\n### 4) Errores en benchmarking\n\n- No compare entradas o asignaciones diferentes.\n- Mantenga JS y nativo usando arrays de entrada idénticos.\n- Ejecute ambos en el mismo archivo de benchmark para evitar sesgos.\n\n## Plantilla de benchmark\n\n```ts\nconst ITERATIONS = 2000;\n\nfunction bench(name: string, fn: () => void): number {\n const start = Bun.nanoseconds();\n for (let i = 0; i < ITERATIONS; i++) fn();\n const elapsed = (Bun.nanoseconds() - start) / 1e6;\n console.log(`${name}: ${elapsed.toFixed(2)}ms total (${(elapsed / ITERATIONS).toFixed(6)}ms/op)`);\n return elapsed;\n}\n\nbench(\"feature/js\", () => {\n jsImpl(sample);\n});\n\nbench(\"feature/native\", () => {\n nativeImpl(sample);\n});\n```\n\n## Lista de verificación final\n\n- `validateNative` pasa (sin exportaciones faltantes).\n- `NativeBindings` está ampliado en `packages/natives/src/<module>/types.ts` y el wrapper está re-exportado en `packages/natives/src/index.ts`.\n- `Object.keys(require(...))` incluye su nueva exportación.\n- Números del benchmark registrados en el PR/notas.\n- Sitio de llamada actualizado **solo si** lo nativo es más rápido o igual.\n\n## Regla general\n\n- Si lo nativo es más lento, **no cambie**. Mantenga la exportación para trabajo futuro, pero la TUI debe permanecer en la ruta más rápida.\n- Si lo nativo es más rápido, cambie el sitio de llamada y mantenga el benchmark en su lugar para detectar regresiones.\n",
	"es/providers/models.md": "---\ntitle: Configuración de modelos y proveedores\ndescription: >-\n  Registro de modelos y configuración de proveedores mediante models.yml con\n  enrutamiento, respaldo y precios.\nsidebar:\n  order: 1\n  label: Modelos y proveedores\ni18n:\n  sourceHash: 8053df967ff6\n  translator: machine\n---\n\n# Configuración de modelos y proveedores (`models.yml`)\n\nEste documento describe cómo el agente de codificación carga actualmente los modelos, aplica anulaciones, resuelve credenciales y selecciona modelos en tiempo de ejecución.\n\n## Qué controla el comportamiento de los modelos\n\nArchivos de implementación principales:\n\n- `src/config/model-registry.ts` — carga modelos integrados y personalizados, anulaciones de proveedores, descubrimiento en tiempo de ejecución, integración de autenticación\n- `src/config/model-resolver.ts` — analiza patrones de modelos y selecciona modelos inicial/smol/slow\n- `src/config/settings-schema.ts` — configuraciones relacionadas con modelos (`modelRoles`, preferencias de transporte del proveedor)\n- `src/session/auth-storage.ts` — orden de resolución de clave API + OAuth\n- `packages/ai/src/models.ts` y `packages/ai/src/types.ts` — proveedores/modelos integrados y tipos `Model`/`compat`\n\n## Ubicación del archivo de configuración y comportamiento heredado\n\nRuta de configuración predeterminada:\n\n- `~/.xcsh/agent/models.yml`\n\nComportamiento heredado que aún está presente:\n\n- Si `models.yml` no existe y `models.json` existe en la misma ubicación, se migra a `models.yml`.\n- Las rutas de configuración explícitas `.json` / `.jsonc` aún se admiten cuando se pasan programáticamente a `ModelRegistry`.\n\n## Estructura de `models.yml`\n\n```yaml\nconfigVersion: 1  # optional — written by auto-config, used for migration detection\nproviders:\n  <provider-id>:\n    # provider-level config\nequivalence:\n  overrides:\n    <provider-id>/<model-id>: <canonical-model-id>\n  exclude:\n    - <provider-id>/<model-id>\n```\n\n`configVersion` es un entero opcional escrito por el sistema de autoconfiguración. Cuando está presente, xcsh lo usa para detectar configuraciones desactualizadas y actualizarlas automáticamente.\n\n`provider-id` es la clave canónica del proveedor utilizada en la selección y la búsqueda de autenticación.\n\n`equivalence` es opcional y configura la agrupación canónica de modelos sobre los modelos de proveedor concretos:\n\n- `overrides` asigna un selector concreto exacto (`provider/modelId`) a un id canónico oficial de nivel superior\n- `exclude` excluye un selector concreto de la agrupación canónica\n\n## Campos a nivel de proveedor\n\n```yaml\nproviders:\n  my-provider:\n    baseUrl: https://api.example.com/v1\n    apiKey: MY_PROVIDER_API_KEY\n    api: openai-completions\n    headers:\n      X-Team: platform\n    authHeader: true\n    auth: apiKey\n    discovery:\n      type: ollama\n    modelOverrides:\n      some-model-id:\n        name: Renamed model\n    models:\n      - id: some-model-id\n        name: Some Model\n        api: openai-completions\n        reasoning: false\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 128000\n        maxTokens: 16384\n        headers:\n          X-Model: value\n        compat:\n          supportsStore: true\n          supportsDeveloperRole: true\n          supportsReasoningEffort: true\n          maxTokensField: max_completion_tokens\n          openRouterRouting:\n            only: [anthropic]\n          vercelGatewayRouting:\n            order: [anthropic, openai]\n          extraBody:\n            gateway: m1-01\n            controller: mlx\n```\n\n### Valores de `api` permitidos para proveedor/modelo\n\n- `openai-completions`\n- `openai-responses`\n- `openai-codex-responses`\n- `azure-openai-responses`\n- `anthropic-messages`\n- `google-generative-ai`\n- `google-vertex`\n\n### Valores permitidos de auth/discovery\n\n- `auth`: `apiKey` (predeterminado) o `none`\n- `discovery.type`: `ollama`\n\n## Reglas de validación (actuales)\n\n### Proveedor completamente personalizado (`models` no está vacío)\n\nObligatorio:\n\n- `baseUrl`\n- `apiKey` a menos que `auth: none`\n- `api` a nivel de proveedor o en cada modelo\n\n### Proveedor solo de anulación (`models` faltante o vacío)\n\nDebe definir al menos uno de:\n\n- `baseUrl`\n- `modelOverrides`\n- `discovery`\n\n### Discovery\n\n- `discovery` requiere `api` a nivel de proveedor.\n\n### Verificaciones de valores del modelo\n\n- `id` es obligatorio\n- `contextWindow` y `maxTokens` deben ser positivos si se proporcionan\n\n## Orden de combinación y anulación\n\nProceso de ModelRegistry (al actualizar):\n\n1. Cargar proveedores/modelos integrados desde `@f5-sales-demo/pi-ai`.\n2. Cargar la configuración personalizada de `models.yml`.\n3. Aplicar anulaciones de proveedor (`baseUrl`, `headers`) a los modelos integrados.\n4. Aplicar `modelOverrides` (por proveedor + id de modelo).\n5. Combinar `models` personalizados:\n   - el mismo `provider + id` reemplaza al existente\n   - de lo contrario, se anexa\n6. Aplicar modelos descubiertos en tiempo de ejecución (actualmente Ollama y LM Studio), y luego volver a aplicar las anulaciones de modelo.\n\n## Equivalencia canónica y coalescencia de modelos\n\nEl registro mantiene cada modelo de proveedor concreto y luego construye una capa canónica sobre ellos.\n\nLos ids canónicos son únicamente ids oficiales de nivel superior, por ejemplo:\n\n- `claude-opus-4-6`\n- `claude-haiku-4-5`\n- `gpt-5.3-codex`\n\n### Configuración de equivalencia en `models.yml`\n\nEjemplo:\n\n```yaml\nproviders:\n  zenmux:\n    baseUrl: https://api.zenmux.example/v1\n    apiKey: ZENMUX_API_KEY\n    api: openai-codex-responses\n    models:\n      - id: codex\n        name: Zenmux Codex\n        reasoning: true\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 200000\n        maxTokens: 32768\n\nequivalence:\n  overrides:\n    zenmux/codex: gpt-5.3-codex\n    p-codex/codex: gpt-5.3-codex\n  exclude:\n    - demo/codex-preview\n```\n\nOrden de construcción para la agrupación canónica:\n\n1. anulación de usuario exacta desde `equivalence.overrides`\n2. coincidencias de id oficial incluidas desde los metadatos del modelo integrado\n3. normalización heurística conservadora para variantes de gateway/proveedor\n4. reserva al id propio del modelo concreto\n\nLas heurísticas actuales son intencionalmente restrictivas:\n\n- los prefijos de nivel superior incrustados se pueden eliminar cuando están presentes, por ejemplo `anthropic/...` o `openai/...`\n- las variantes de versión con puntos y guiones pueden normalizarse solo cuando se asignan a un id oficial existente, por ejemplo `4.6 -> 4-6`\n- las familias o versiones ambiguas no se combinan sin una coincidencia incluida o una anulación explícita\n\n### Comportamiento de resolución canónica\n\nCuando múltiples variantes concretas comparten un id canónico, la resolución usa:\n\n1. disponibilidad y autenticación\n2. `modelProviderOrder` en `config.yml`\n3. orden de registro/proveedor existente si `modelProviderOrder` no está definido\n\nLos proveedores deshabilitados o no autenticados se omiten.\n\nEl estado de sesión y las transcripciones continúan registrando el proveedor/modelo concreto que ejecutó el turno.\n\nValores predeterminados del proveedor frente a anulaciones por modelo:\n\n- Los `headers` del proveedor son la base.\n- Los `headers` del modelo anulan las claves de encabezado del proveedor.\n- `modelOverrides` puede anular metadatos del modelo (`name`, `reasoning`, `input`, `cost`, `contextWindow`, `maxTokens`, `headers`, `compat`, `contextPromotionTarget`).\n- `compat` se combina en profundidad para los bloques de enrutamiento anidados (`openRouterRouting`, `vercelGatewayRouting`, `extraBody`).\n\n## Integración de descubrimiento en tiempo de ejecución\n\n### Descubrimiento implícito de Ollama\n\nSi `ollama` no está configurado explícitamente, el registro añade un proveedor de descubrimiento implícito:\n\n- proveedor: `ollama`\n- api: `openai-completions`\n- URL base: `OLLAMA_BASE_URL` o `http://127.0.0.1:11434`\n- modo de autenticación: sin clave (`auth: none`)\n\nEl descubrimiento en tiempo de ejecución llama a `GET /api/tags` en Ollama y sintetiza entradas de modelo con valores predeterminados locales.\n\n### Descubrimiento implícito de llama.cpp\n\nSi `llama.cpp` no está configurado explícitamente, el registro añade un proveedor de descubrimiento implícito:\nNota: utiliza la API de mensajes de Anthropic más reciente en lugar de openai-completions.\n\n- proveedor: `llama.cpp`\n- api: `openai-responses`\n- URL base: `LLAMA_CPP_BASE_URL` o `http://127.0.0.1:8080`\n- modo de autenticación: sin clave (`auth: none`)\n\nEl descubrimiento en tiempo de ejecución llama a `GET models` en llama.cpp y sintetiza entradas de modelo con valores predeterminados locales.\n\n### Descubrimiento implícito de LM Studio\n\nSi `lm-studio` no está configurado explícitamente, el registro añade un proveedor de descubrimiento implícito:\n\n- proveedor: `lm-studio`\n- api: `openai-completions`\n- URL base: `LM_STUDIO_BASE_URL` o `http://127.0.0.1:1234/v1`\n- modo de autenticación: sin clave (`auth: none`)\n\nEl descubrimiento en tiempo de ejecución obtiene los modelos (`GET /models`) y sintetiza entradas de modelo con valores predeterminados locales.\n\n### Descubrimiento de proveedor explícito\n\nPuede configurar el descubrimiento manualmente:\n\n```yaml\nproviders:\n  ollama:\n    baseUrl: http://127.0.0.1:11434\n    api: openai-completions\n    auth: none\n    discovery:\n      type: ollama\n      \n  llama.cpp:\n    baseUrl: http://127.0.0.1:8080\n    api: openai-responses\n    auth: none\n    discovery:\n      type: llama.cpp\n```\n\n### Registro de proveedor por extensión\n\nLas extensiones pueden registrar proveedores en tiempo de ejecución (`pi.registerProvider(...)`), incluyendo:\n\n- reemplazo/anexo de modelos para un proveedor\n- registro de manejador de flujo personalizado para nuevos IDs de API\n- registro de proveedor OAuth personalizado\n\n## Orden de resolución de autenticación y clave API\n\nAl solicitar una clave para un proveedor, el orden efectivo es:\n\n1. Anulación en tiempo de ejecución (CLI `--api-key`)\n2. Credencial de clave API almacenada en `agent.db`\n3. Credencial OAuth almacenada en `agent.db` (con actualización)\n4. Asignación de variables de entorno (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc.)\n5. Resolvedor de reserva de ModelRegistry (`apiKey` del proveedor desde `models.yml`, semántica de nombre-de-entorno-o-literal)\n\nComportamiento de `apiKey` en `models.yml`:\n\n- El valor se trata primero como un nombre de variable de entorno.\n- Si no existe la variable de entorno, se usa la cadena literal como token.\n\nSi `authHeader: true` y el `apiKey` del proveedor está definido, los modelos reciben:\n\n- encabezado `Authorization: Bearer <clave-resuelta>` inyectado.\n\nProveedores sin clave:\n\n- Los proveedores marcados como `auth: none` se tratan como disponibles sin credenciales.\n- `getApiKey*` devuelve `kNoAuth` para ellos.\n\n## Disponibilidad de modelos frente a todos los modelos\n\n- `getAll()` devuelve el registro de modelos cargado (integrados + personalizados combinados + descubiertos).\n- `getAvailable()` filtra a los modelos que no requieren clave o tienen autenticación resoluble.\n\nPor lo tanto, un modelo puede existir en el registro pero no ser seleccionable hasta que la autenticación esté disponible.\n\n## Resolución de modelos en tiempo de ejecución\n\n### CLI y análisis de patrones\n\n`model-resolver.ts` admite:\n\n- `provider/modelId` exacto\n- id de modelo canónico exacto\n- id de modelo exacto (proveedor inferido)\n- coincidencia difusa/por subcadena\n- patrones de alcance glob en `--models` (p. ej., `openai/*`, `*sonnet*`)\n- sufijo opcional `:thinkingLevel` (`off|minimal|low|medium|high|xhigh`)\n\n`--provider` es heredado; se prefiere `--model`.\n\nPrecedencia de resolución para selectores exactos:\n\n1. `provider/modelId` exacto omite la coalescencia\n2. el id canónico exacto se resuelve a través del índice canónico\n3. el id concreto simple exacto sigue funcionando\n4. la coincidencia difusa y glob se ejecuta después de las rutas exactas\n\n### Prioridad de selección del modelo inicial\n\n`findInitialModel(...)` usa este orden:\n\n1. proveedor+modelo explícito del CLI\n2. primer modelo del alcance (si no se está reanudando)\n3. proveedor/modelo predeterminado guardado\n4. valores predeterminados de proveedores conocidos (p. ej., OpenAI/Anthropic/etc.) entre los modelos disponibles\n5. primer modelo disponible\n\n### Alias de roles y configuraciones\n\nRoles de modelo admitidos:\n\n- `default`, `smol`, `slow`, `plan`, `commit`\n\nLos alias de roles como `pi/smol` se expanden a través de `settings.modelRoles`. Cada valor de rol también puede añadir un selector de razonamiento como `:minimal`, `:low`, `:medium` o `:high`.\n\nSi un rol apunta a otro rol, el modelo destino sigue siendo heredado normalmente y cualquier sufijo explícito en el rol referenciante tiene prioridad para ese uso específico del rol.\n\nConfiguraciones relacionadas:\n\n- `modelRoles` (registro)\n- `enabledModels` (lista de patrones de alcance)\n- `modelProviderOrder` (precedencia canónica global de proveedores)\n- `providers.kimiApiFormat` (formato de solicitud `openai` o `anthropic`)\n- `providers.openaiWebsockets` (preferencia de websocket `auto|off|on` para el transporte OpenAI Codex)\n\n`modelRoles` puede almacenar:\n\n- `provider/modelId` para fijar una variante de proveedor concreta\n- un id canónico como `gpt-5.3-codex` para permitir la coalescencia de proveedores\n\nPara `enabledModels` y CLI `--models`:\n\n- los ids canónicos exactos se expanden a todas las variantes concretas en ese grupo canónico\n- las entradas explícitas `provider/modelId` permanecen exactas\n- los globs y las coincidencias difusas aún operan sobre modelos concretos\n\n## `/model` y `--list-models`\n\nAmbas superficies mantienen los modelos con prefijo de proveedor visibles y seleccionables.\n\nAhora también exponen modelos canónicos/coalescidos:\n\n- `/model` incluye una vista canónica junto a las pestañas de proveedor\n- `--list-models` imprime una sección canónica además de las filas de proveedor concreto\n\nSeleccionar una entrada canónica almacena el selector canónico. Seleccionar una fila de proveedor almacena el `provider/modelId` explícito.\n\n## Promoción de contexto (cadenas de reserva a nivel de modelo)\n\nLa promoción de contexto es un mecanismo de recuperación por desbordamiento para variantes de contexto pequeño (por ejemplo, `*-spark`) que promueve automáticamente a un hermano de mayor contexto cuando la API rechaza una solicitud con un error de longitud de contexto.\n\n### Activación y orden\n\nCuando un turno falla con un error de desbordamiento de contexto (p. ej., `context_length_exceeded`), `AgentSession` intenta la promoción **antes** de recurrir a la compactación:\n\n1. Si `contextPromotion.enabled` es verdadero, resolver un destino de promoción (ver más abajo).\n2. Si se encuentra un destino, cambiar a él y reintentar la solicitud — no se necesita compactación.\n3. Si no hay destino disponible, pasar a la compactación automática en el modelo actual.\n\n### Selección del destino\n\nLa selección está impulsada por el modelo, no por el rol:\n\n1. `currentModel.contextPromotionTarget` (si está configurado)\n2. el modelo de mayor contexto más pequeño en el mismo proveedor + API\n\nLos candidatos se ignoran a menos que las credenciales se resuelvan (`ModelRegistry.getApiKey(...)`).\n\n### Transferencia de websocket de OpenAI Codex\n\nAl cambiar desde/hacia `openai-codex-responses`, la clave de estado de proveedor de sesión `openai-codex-responses` se cierra antes del cambio de modelo. Esto elimina el estado de transporte de websocket para que el siguiente turno comience limpio en el modelo promovido.\n\n### Comportamiento de persistencia\n\nLa promoción usa conmutación temporal (`setModelTemporary`):\n\n- registrada como un `model_change` temporal en el historial de sesión\n- no reescribe la asignación de roles guardada\n\n### Configuración de cadenas de reserva explícitas\n\nConfigure la reserva directamente en los metadatos del modelo mediante `contextPromotionTarget`.\n\n`contextPromotionTarget` acepta:\n\n- `provider/model-id` (explícito)\n- `model-id` (resuelto dentro del proveedor actual)\n\nEjemplo (`models.yml`) para Spark -> no-Spark en el mismo proveedor:\n\n```yaml\nproviders:\n  openai-codex:\n    modelOverrides:\n      gpt-5.3-codex-spark:\n        contextPromotionTarget: openai-codex/gpt-5.3-codex\n```\n\nEl generador de modelos integrado también asigna esto automáticamente para los modelos `*-spark` cuando existe un modelo base en el mismo proveedor.\n\n## Campos de compatibilidad y enrutamiento\n\n`models.yml` admite este subconjunto de `compat`:\n\n- `supportsStore`\n- `supportsDeveloperRole`\n- `supportsReasoningEffort`\n- `maxTokensField` (`max_completion_tokens` o `max_tokens`)\n- `openRouterRouting.only` / `openRouterRouting.order`\n- `vercelGatewayRouting.only` / `vercelGatewayRouting.order`\n\nEstos son consumidos por la lógica de transporte de OpenAI-completions y se combinan con la detección automática basada en URL.\n\n## Ejemplos prácticos\n\n### Punto de conexión compatible con OpenAI local (sin autenticación)\n\n```yaml\nproviders:\n  local-openai:\n    baseUrl: http://127.0.0.1:8000/v1\n    auth: none\n    api: openai-completions\n    models:\n      - id: Qwen/Qwen2.5-Coder-32B-Instruct\n        name: Qwen 2.5 Coder 32B (local)\n```\n\n### Proxy alojado con clave basada en variables de entorno\n\n```yaml\nproviders:\n  anthropic-proxy:\n    baseUrl: https://proxy.example.com/anthropic\n    apiKey: ANTHROPIC_PROXY_API_KEY\n    api: anthropic-messages\n    authHeader: true\n    models:\n      - id: claude-sonnet-4-20250514\n        name: Claude Sonnet 4 (Proxy)\n        reasoning: true\n        input: [text, image]\n```\n\n### Anular ruta de proveedor integrado + metadatos del modelo\n\n```yaml\nproviders:\n  openrouter:\n    baseUrl: https://my-proxy.example.com/v1\n    headers:\n      X-Team: platform\n    modelOverrides:\n      anthropic/claude-sonnet-4:\n        name: Sonnet 4 (Corp)\n        compat:\n          openRouterRouting:\n            only: [anthropic]\n```\n\n## Autoconfiguración del proxy LiteLLM\n\nCuando las variables de entorno `LITELLM_BASE_URL` y `LITELLM_API_KEY` están definidas, xcsh gestiona automáticamente la configuración de `models.yml` para el proxy LiteLLM.\n\n### Generación automática en la primera ejecución\n\nSi `models.yml` no existe y se detectan las variables de entorno de LiteLLM, xcsh lo genera automáticamente:\n\n```yaml\n# Auto-generated by xcsh for LiteLLM proxy\n# API key resolved from LITELLM_API_KEY env var at runtime\nconfigVersion: 1\nproviders:\n  anthropic:\n    baseUrl: \"https://your-litellm-proxy.example.com/anthropic\"\n    apiKey: LITELLM_API_KEY\n```\n\nTambién se genera un `config.yml` predeterminado con configuraciones de proveedor de imágenes sensatas.\n\n### Autocorrección al inicio\n\nEn cada inicio, `startupHealthCheck()` en el registro de modelos ejecuta las siguientes comprobaciones:\n\n| Condición | Acción |\n|-----------|--------|\n| `models.yml` faltante | Generar automáticamente desde variables de entorno |\n| `models.yml` corrupto o no analizable | Hacer copia de seguridad como `.bak`, regenerar |\n| `baseUrl` no coincide con `LITELLM_BASE_URL` | Hacer copia de seguridad como `.bak`, regenerar con la nueva URL |\n| `configVersion` faltante o desactualizado | Hacer copia de seguridad como `.bak`, regenerar con la versión actual |\n| La configuración está en buen estado | Sin acción |\n\nTodas las reparaciones crean copias de seguridad `.bak` antes de sobrescribir. Todas las operaciones son idempotentes.\n\n### Comando CLI\n\n```bash\nxcsh setup litellm              # Generate or fix LiteLLM config\nxcsh setup litellm --check      # Validate without writing\nxcsh setup litellm --check --json  # Machine-readable validation output\n```\n\n### Variables de entorno requeridas\n\n| Variable | Propósito |\n|----------|---------|\n| `LITELLM_BASE_URL` | URL del proxy LiteLLM (p. ej., `https://your-proxy.example.com`). Debe comenzar con `http://` o `https://`. |\n| `LITELLM_API_KEY` | Clave API para el proxy. Referenciada por nombre en la configuración generada, resuelta en tiempo de ejecución. |\n\nSi alguna de las variables no está definida, la autoconfiguración se omite silenciosamente.\n\n### Versionado de configuración\n\nLas configuraciones generadas incluyen un campo `configVersion`. Cuando el formato generado cambie en versiones futuras, xcsh detecta las configuraciones desactualizadas y las actualiza automáticamente (con copia de seguridad).\n\n## Advertencia sobre consumidores heredados\n\nLa mayor parte de la configuración de modelos ahora fluye a través de `models.yml` mediante `ModelRegistry`.\n\nQueda una ruta heredada notable: la resolución de autenticación de Anthropic para búsqueda web aún lee `~/.xcsh/agent/models.json` directamente en `src/web/search/auth.ts`.\n\nSi depende de esa ruta específica, tenga en cuenta la compatibilidad con JSON hasta que ese módulo sea migrado.\n\n## Modo de fallo\n\nSi `models.yml` falla las comprobaciones de esquema o validación:\n\n- Si `LITELLM_BASE_URL` y `LITELLM_API_KEY` están definidos, la comprobación de estado al inicio intenta la reparación automática (hacer copia de seguridad del archivo corrupto, regenerar desde las variables de entorno). Si la reparación tiene éxito, el registro recarga la configuración corregida.\n- Si la reparación automática no es posible (variables de entorno no definidas, fallo de escritura), el registro continúa operando con los modelos integrados.\n- El error se expone a través de `ModelRegistry.getError()` y se muestra en la interfaz de usuario/notificaciones.\n",
	"es/providers/provider-streaming-internals.md": "---\ntitle: Internos del Streaming de Proveedores\ndescription: >-\n  Implementación del streaming de proveedores con análisis SSE, conteo de tokens\n  y manejo de contrapresión.\nsidebar:\n  order: 2\n  label: Internos del streaming\ni18n:\n  sourceHash: a32ffa769c4d\n  translator: machine\n---\n\n# Internos del streaming de proveedores\n\nEste documento explica cómo el streaming de tokens/herramientas se normaliza en `@f5-sales-demo/pi-ai`, y luego se propaga a través de `@f5-sales-demo/pi-agent-core` y los eventos de sesión de `coding-agent`.\n\n## Flujo de extremo a extremo\n\n1. `streamSimple()` (`packages/ai/src/stream.ts`) mapea opciones genéricas y despacha a una función de stream del proveedor.\n2. Las funciones de stream del proveedor (`anthropic.ts`, `openai-responses.ts`, `google.ts`) traducen los eventos de stream nativos del proveedor a la secuencia unificada de `AssistantMessageEvent`.\n3. Cada proveedor envía eventos a `AssistantMessageEventStream` (`packages/ai/src/utils/event-stream.ts`), que regula los eventos delta y expone:\n   - iteración asíncrona para actualizaciones incrementales\n   - `result()` para el `AssistantMessage` final\n4. `agentLoop` (`packages/agent/src/agent-loop.ts`) consume esos eventos, muta el estado del asistente en curso y emite eventos `message_update` que transportan el `assistantMessageEvent` sin procesar.\n5. `AgentSession` (`packages/coding-agent/src/session/agent-session.ts`) se suscribe a los eventos del agente, persiste mensajes, ejecuta hooks de extensión y aplica comportamientos de sesión (reintento, compactación, TTSR, verificaciones de aborto de edición en streaming).\n\n## Contrato unificado de stream en `@f5-sales-demo/pi-ai`\n\nTodos los proveedores emiten la misma forma (`AssistantMessageEvent` en `packages/ai/src/types.ts`):\n\n- `start`\n- tripletas de ciclo de vida de bloques de contenido:\n  - texto: `text_start` → `text_delta`* → `text_end`\n  - pensamiento: `thinking_start` → `thinking_delta`* → `thinking_end`\n  - llamada a herramienta: `toolcall_start` → `toolcall_delta`* → `toolcall_end`\n- evento terminal:\n  - `done` con `reason: \"stop\" | \"length\" | \"toolUse\"`\n  - o `error` con `reason: \"aborted\" | \"error\"`\n\n`AssistantMessageEventStream` garantiza:\n\n- el resultado final se resuelve mediante el evento terminal (`done` o `error`)\n- los deltas se agrupan/regulan (~50ms)\n- los deltas almacenados en búfer se vacían antes de los eventos no-delta y antes de la finalización\n\n## Comportamiento de regulación y armonización de deltas\n\n`AssistantMessageEventStream` trata `text_delta`, `thinking_delta` y `toolcall_delta` como eventos combinables:\n\n- los deltas almacenados en búfer se combinan solo cuando **type + contentIndex** coinciden\n- la combinación mantiene la última instantánea `partial`\n- los eventos no-delta fuerzan un vaciado inmediato\n\nEsto suaviza los streams de alta frecuencia del proveedor para consumidores TUI/eventos, pero no es contrapresión del proveedor: los proveedores siguen produciendo a máxima velocidad, mientras el stream local almacena en búfer.\n\n## Detalles de normalización por proveedor\n\n## Anthropic (`anthropic-messages`)\n\nFuente: `packages/ai/src/providers/anthropic.ts`\n\nPuntos de normalización:\n\n- `message_start` inicializa el uso (tokens de entrada/salida/caché)\n- `content_block_start` se mapea a inicios de texto/pensamiento/llamada a herramienta\n- `content_block_delta` mapea:\n  - `text_delta` → `text_delta`\n  - `thinking_delta` → `thinking_delta`\n  - `input_json_delta` → `toolcall_delta`\n  - `signature_delta` actualiza `thinkingSignature` solamente (sin evento)\n- `content_block_stop` emite el `*_end` correspondiente\n- `message_delta.stop_reason` se mapea mediante `mapStopReason()`\n\nStreaming de argumentos de llamadas a herramientas:\n\n- cada bloque de herramienta lleva un `partialJson` interno\n- cada delta JSON se anexa a `partialJson`\n- los `arguments` se reanalizan en cada delta mediante `parseStreamingJson()`\n- `toolcall_end` reanaliza una vez más, luego elimina `partialJson`\n\n## OpenAI Responses (`openai-responses`)\n\nFuente: `packages/ai/src/providers/openai-responses.ts`\n\nPuntos de normalización:\n\n- `response.output_item.added` inicia bloques de razonamiento/texto/llamada a función\n- los eventos de resumen de razonamiento (`response.reasoning_summary_text.delta`) se convierten en `thinking_delta`\n- los deltas de salida/rechazo se convierten en `text_delta`\n- `response.function_call_arguments.delta` se convierte en `toolcall_delta`\n- `response.output_item.done` emite `thinking_end` / `text_end` / `toolcall_end`\n- `response.completed` mapea el estado a razón de parada y uso\n\nStreaming de argumentos de llamadas a herramientas:\n\n- mismo patrón de acumulación `partialJson` que Anthropic\n- los proveedores que envían solo `response.function_call_arguments.done` aún pueblan los argumentos finales\n- los IDs de llamada a herramienta se normalizan como `\"<call_id>|<item_id>\"`\n\n## Google Generative AI (`google-generative-ai`)\n\nFuente: `packages/ai/src/providers/google.ts`\n\nPuntos de normalización:\n\n- itera sobre `candidate.content.parts`\n- las partes de texto se dividen en pensamiento vs texto mediante `isThinkingPart(part)`\n- las transiciones de bloque cierran el bloque anterior antes de iniciar uno nuevo\n- `part.functionCall` se trata como una llamada a herramienta completa (start/delta/end se emiten inmediatamente)\n- la razón de finalización se mapea mediante `mapStopReason()` desde `google-shared.ts`\n\nStreaming de argumentos de llamadas a herramientas:\n\n- los argumentos de llamada a función llegan como objeto estructurado, no como texto JSON incremental\n- la implementación emite un `toolcall_delta` sintético que contiene `JSON.stringify(arguments)`\n- no se necesita un analizador de JSON parcial para Google en esta ruta\n\n## Acumulación y recuperación de JSON parcial en llamadas a herramientas\n\nEl comportamiento compartido para Anthropic/OpenAI Responses utiliza `parseStreamingJson()` (`packages/ai/src/utils/json-parse.ts`):\n\n1. intenta `JSON.parse`\n2. recurre al analizador `partial-json` para fragmentos incompletos\n3. si ambos fallan, retorna `{}`\n\nImplicaciones:\n\n- los deltas de argumentos malformados o truncados no hacen fallar el procesamiento del stream inmediatamente\n- los `arguments` en progreso pueden ser temporalmente `{}`\n- deltas válidos posteriores pueden recuperar argumentos estructurados porque el análisis se reintenta en cada anexión\n- el `toolcall_end` final realiza un intento más de análisis antes de la emisión\n\n## Razones de parada vs errores de transporte/ejecución\n\nLas razones de parada del proveedor se mapean a `stopReason` normalizado:\n\n- Anthropic: `end_turn`→`stop`, `max_tokens`→`length`, `tool_use`→`toolUse`, casos de seguridad/rechazo→`error`\n- OpenAI Responses: `completed`→`stop`, `incomplete`→`length`, `failed/cancelled`→`error`\n- Google: `STOP`→`stop`, `MAX_TOKENS`→`length`, clases de seguridad/prohibido/llamada-a-función-malformada→`error`\n\nLa semántica de errores se divide en dos etapas:\n\n1. **Semántica de finalización del modelo** (razón de finalización/estado reportado por el proveedor)\n2. **Fallo de transporte/ejecución** (excepciones de red/cliente/analizador/aborto)\n\nSi el stream del proveedor lanza una excepción o señala fallo, cada wrapper de proveedor captura y emite un evento terminal `error` con:\n\n- `stopReason = \"aborted\"` cuando la señal de aborto está activada\n- de lo contrario `stopReason = \"error\"`\n- `errorMessage = formatErrorMessageWithRetryAfter(error)`\n\n## Comportamiento ante chunks malformados / fallos de análisis SSE\n\nPara estas rutas de proveedores, el enmarcado de chunks/SSE es manejado por los streams del SDK del vendedor (SDK de Anthropic, SDK de OpenAI, SDK de Google). Este código no implementa un decodificador SSE personalizado aquí.\n\nComportamiento observado en la implementación actual:\n\n- el análisis de chunks/SSE malformados a nivel de SDK se manifiesta como una excepción o evento `error` del stream\n- el wrapper del proveedor lo convierte en un evento terminal `error` unificado\n- no hay reanudación/reintento específico del proveedor dentro de la función de stream en sí\n- los reintentos de nivel superior se manejan en la lógica de reintento automático de `AgentSession` (reintento a nivel de mensaje, no reproducción de chunks del stream)\n\n## Límites de cancelación\n\nLa cancelación se organiza en capas:\n\n- Solicitud al proveedor de IA: `options.signal` se pasa a la llamada de stream del cliente del proveedor.\n- Wrapper del proveedor: después del bucle del stream, una señal abortada fuerza la ruta de error (`\"Request was aborted\"`).\n- Bucle del agente: verifica `signal.aborted` antes de manejar cada evento del proveedor y puede sintetizar un mensaje de asistente abortado a partir del parcial más reciente.\n- Controles de sesión/agente: `AgentSession.abort()` -> `agent.abort()` -> cancelación del controlador de aborto compartido.\n\nLa cancelación de ejecución de herramientas es independiente de la cancelación del stream del modelo:\n\n- los ejecutores de herramientas usan `AbortSignal.any([agentSignal, steeringAbortSignal])`\n- las interrupciones de dirección pueden abortar la ejecución restante de herramientas mientras preservan los resultados de herramientas ya producidos\n\n## Límites de contrapresión\n\nNo existe un mecanismo de contrapresión rígido entre el stream del SDK del proveedor y los consumidores posteriores:\n\n- `EventStream` usa colas en memoria sin tamaño máximo\n- la regulación reduce la tasa de actualización de la UI pero no ralentiza la ingesta del proveedor\n- si los consumidores se retrasan significativamente, los eventos en cola pueden crecer hasta la finalización\n\nEl diseño actual favorece la capacidad de respuesta y el ordenamiento simple sobre el control de flujo con búfer acotado.\n\n## Cómo los eventos de stream se manifiestan como eventos de agente/sesión\n\n`agentLoop.streamAssistantResponse()` conecta `AssistantMessageEvent` con `AgentEvent`:\n\n- en `start`: inserta un mensaje de asistente provisional y emite `message_start`\n- en eventos de bloque (`text_*`, `thinking_*`, `toolcall_*`): actualiza el último mensaje del asistente, emite `message_update` con el `assistantMessageEvent` sin procesar\n- en terminal (`done`/`error`): resuelve el mensaje final de `response.result()`, emite `message_end`\n\n`AgentSession` luego consume esos eventos para comportamientos a nivel de sesión:\n\n- TTSR observa `message_update.assistantMessageEvent` buscando `text_delta` y `toolcall_delta`\n- la protección de edición en streaming inspecciona `toolcall_delta`/`toolcall_end` en llamadas `edit` y puede abortar anticipadamente\n- la persistencia escribe mensajes finalizados en `message_end`\n- el reintento automático examina `stopReason === \"error\"` del asistente más heurísticas de `errorMessage`\n\n## Responsabilidades unificadas vs específicas del proveedor\n\nUnificadas (contrato común):\n\n- forma del evento (`AssistantMessageEvent`)\n- extracción del resultado final (`done`/`error`)\n- regulación de deltas + reglas de combinación\n- modelo de propagación de eventos agente/sesión\n\nEspecíficas del proveedor (no completamente abstraídas):\n\n- taxonomías de eventos upstream y lógica de mapeo\n- tablas de traducción de razón de parada\n- convenciones de ID de llamada a herramienta\n- semántica de bloques de razonamiento/pensamiento y firmas\n- semántica de tokens de uso y disponibilidad temporal\n- restricciones de conversión de mensajes por API\n\n## Archivos de implementación\n\n- [`../../ai/src/stream.ts`](../../packages/ai/src/stream.ts) — despacho de proveedor, mapeo de opciones, canalización de clave API/sesión.\n- [`../../ai/src/utils/event-stream.ts`](../../packages/ai/src/utils/event-stream.ts) — cola genérica de stream + regulación de deltas del asistente.\n- [`../../ai/src/utils/json-parse.ts`](../../packages/ai/src/utils/json-parse.ts) — análisis de JSON parcial para argumentos de herramientas en streaming.\n- [`../../ai/src/providers/anthropic.ts`](../../packages/ai/src/providers/anthropic.ts) — traducción de eventos de Anthropic y acumulación de deltas JSON de herramientas.\n- [`../../ai/src/providers/openai-responses.ts`](../../packages/ai/src/providers/openai-responses.ts) — traducción de eventos de OpenAI Responses y mapeo de estados.\n- [`../../ai/src/providers/google.ts`](../../packages/ai/src/providers/google.ts) — traducción de chunks de stream de Gemini a bloques.\n- [`../../ai/src/providers/google-shared.ts`](../../packages/ai/src/providers/google-shared.ts) — mapeo de razón de finalización de Gemini y reglas de conversión compartidas.\n- [`../../agent/src/agent-loop.ts`](../../packages/agent/src/agent-loop.ts) — consumo del stream del proveedor y conexión de `message_update`.\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — manejo a nivel de sesión de actualizaciones en streaming, aborto, reintento y persistencia.\n",
	"es/providers/python-repl.md": "---\ntitle: Herramienta Python y Runtime de IPython\ndescription: >-\n  Runtime de herramienta Python REPL con gestión de kernel IPython, ejecución y\n  captura de salida.\nsidebar:\n  order: 3\n  label: Python e IPython\ni18n:\n  sourceHash: 70f0a034ecef\n  translator: machine\n---\n\n# Herramienta Python y Runtime de IPython\n\nEste documento describe la pila de ejecución de Python actual en `packages/coding-agent`.\nCubre el comportamiento de la herramienta, el ciclo de vida del kernel/gateway, el manejo del entorno, la semántica de ejecución, el renderizado de salida y los modos de fallo operativos.\n\n## Alcance y archivos clave\n\n- Superficie de la herramienta: `src/tools/python.ts`\n- Orquestación de kernel por sesión/llamada: `src/ipy/executor.ts`\n- Protocolo de kernel + integración de gateway: `src/ipy/kernel.ts`\n- Coordinador de gateway local compartido: `src/ipy/gateway-coordinator.ts`\n- Renderizador de modo interactivo para ejecuciones Python activadas por el usuario: `src/modes/components/python-execution.ts`\n- Filtrado de runtime/entorno y resolución de Python: `src/ipy/runtime.ts`\n\n## Qué es la herramienta Python\n\nLa herramienta `python` ejecuta una o más celdas Python a través de un kernel respaldado por Jupyter Kernel Gateway (no invocando `python -c` directamente por celda).\n\nParámetros de la herramienta:\n\n```ts\n{\n  cells: Array<{ code: string; title?: string }>;\n  timeout?: number; // segundos, limitado a 1..600, valor predeterminado 30\n  cwd?: string;\n  reset?: boolean; // reinicia el kernel solo antes de la primera celda\n}\n```\n\nLa herramienta tiene `concurrency = \"exclusive\"` para una sesión, por lo que las llamadas no se superponen.\n\n## Ciclo de vida del gateway\n\n### Modos\n\nExisten dos rutas de gateway:\n\n1. **Gateway externo** (`PI_PYTHON_GATEWAY_URL` configurado)\n   - Utiliza la URL configurada directamente.\n   - Autenticación opcional con `PI_PYTHON_GATEWAY_TOKEN`.\n   - No se inicia ni gestiona ningún proceso de gateway local.\n\n2. **Gateway local compartido** (ruta predeterminada)\n   - Utiliza un único proceso compartido coordinado en `~/.xcsh/agent/python-gateway`.\n   - Archivo de metadatos: `gateway.json`\n   - Archivo de bloqueo: `gateway.lock`\n   - Comando de inicio:\n     - `python -m kernel_gateway`\n     - enlazado a `127.0.0.1:<puerto-asignado>`\n     - verificación de salud al inicio: `GET /api/kernelspecs`\n\n### Coordinación del gateway local compartido\n\n`acquireSharedGateway()`:\n\n- Adquiere un bloqueo de archivo (`gateway.lock`) con heartbeat.\n- Reutiliza `gateway.json` si el PID está activo y la verificación de salud es exitosa.\n- Limpia información/PIDs obsoletos cuando es necesario.\n- Inicia un nuevo gateway cuando no existe ninguno en buen estado.\n\n`releaseSharedGateway()` es actualmente una operación sin efecto (el apagado del kernel no desmonta el gateway compartido).\n\n`shutdownSharedGateway()` termina explícitamente el proceso compartido y borra los metadatos del gateway.\n\n### Restricción importante\n\n`python.sharedGateway=false` es rechazado al iniciar el kernel:\n\n- Error: `Shared Python gateway required; local gateways are disabled`\n- No existe un modo de gateway local no compartido por proceso.\n\n## Ciclo de vida del kernel\n\nCada ejecución utiliza un kernel creado mediante `POST /api/kernels` en el gateway seleccionado.\n\nSecuencia de inicio del kernel:\n\n1. Verificación de disponibilidad (`checkPythonKernelAvailability`)\n2. Crear kernel (`/api/kernels`)\n3. Abrir websocket (`/api/kernels/:id/channels`)\n4. Inicializar entorno del kernel (`cwd`, variables de entorno, `sys.path`)\n5. Ejecutar `PYTHON_PRELUDE`\n6. Cargar módulos de extensión desde:\n   - usuario: `~/.xcsh/agent/modules/*.py`\n   - proyecto: `<cwd>/.xcsh/modules/*.py` (reemplaza el módulo de usuario con el mismo nombre)\n\nApagado del kernel:\n\n- Elimina el kernel remoto mediante `DELETE /api/kernels/:id`\n- Cierra el websocket\n- Invoca el hook de liberación del gateway compartido (sin efecto actualmente)\n\n## Semántica de persistencia de sesión\n\n`python.kernelMode` controla la reutilización del kernel:\n\n- `session` (predeterminado)\n  - Reutiliza sesiones de kernel identificadas por la identidad de sesión + cwd.\n  - La ejecución se serializa por sesión mediante una cola.\n  - Las sesiones inactivas se eliminan después de 5 minutos.\n  - Máximo 4 sesiones; la más antigua se elimina al superar el límite.\n  - Las verificaciones de heartbeat detectan kernels muertos.\n  - Se permite un reinicio automático; un fallo repetido resulta en error definitivo.\n\n- `per-call`\n  - Crea un kernel nuevo para cada solicitud de ejecución.\n  - Apaga el kernel tras la solicitud.\n  - No hay persistencia de estado entre llamadas.\n\n### Comportamiento de múltiples celdas en una sola llamada a la herramienta\n\nLas celdas se ejecutan secuencialmente en la misma instancia del kernel para esa llamada.\n\nSi una celda intermedia falla:\n\n- El estado de las celdas anteriores permanece en memoria.\n- La herramienta devuelve un error específico indicando qué celda falló.\n- Las celdas posteriores no se ejecutan.\n\n`reset=true` solo se aplica a la primera ejecución de celda en esa llamada.\n\n## Filtrado de entorno y resolución de runtime\n\nEl entorno se filtra antes de iniciar el runtime del gateway/kernel:\n\n- La lista de permitidos incluye variables principales como `PATH`, `HOME`, variables de locale, `VIRTUAL_ENV`, `PYTHONPATH`, etc.\n- Prefijos permitidos: `LC_`, `XDG_`, `PI_`\n- La lista de denegados elimina claves de API comunes (OpenAI/Anthropic/Gemini/etc.)\n\nOrden de selección del runtime:\n\n1. Venv activo/localizado (`VIRTUAL_ENV`, luego `<cwd>/.venv`, `<cwd>/venv`)\n2. Venv gestionado en `~/.xcsh/python-env`\n3. `python` o `python3` en PATH\n\nCuando se selecciona un venv, su ruta bin/Scripts se antepone a `PATH`.\n\nLa inicialización del entorno del kernel dentro de Python también:\n\n- `os.chdir(cwd)`\n- inyecta el mapa de entorno proporcionado en `os.environ`\n- asegura que cwd esté en `sys.path`\n\n## Disponibilidad de la herramienta y selección de modo\n\n`python.toolMode` (predeterminado `both`) + anulación opcional de `PI_PY` controla la exposición:\n\n- `ipy-only`\n- `bash-only`\n- `both`\n\nValores aceptados por `PI_PY`:\n\n- `0` / `bash` -> `bash-only`\n- `1` / `py` -> `ipy-only`\n- `mix` / `both` -> `both`\n\nSi la verificación previa de Python falla, la creación de la herramienta se degrada a bash-only para esa sesión.\n\n## Flujo de ejecución y cancelación/timeout\n\n### Timeout a nivel de herramienta\n\nEl timeout de la herramienta `python` está en segundos, predeterminado 30, limitado a `1..600`.\n\nLa herramienta combina:\n\n- señal de cancelación del llamador\n- señal de cancelación por timeout\n\ncon `AbortSignal.any(...)`.\n\n### Cancelación de ejecución del kernel\n\nAl cancelar/timeout:\n\n- La ejecución se marca como cancelada.\n- Se intenta interrumpir el kernel mediante REST (`POST /interrupt`) y `interrupt_request` en el canal de control.\n- El resultado incluye `cancelled=true`.\n- La ruta de timeout anota la salida como `Command timed out after <n> seconds`.\n\n### Comportamiento de stdin\n\nLa entrada stdin interactiva no está soportada.\n\nSi el kernel emite `input_request`:\n\n- La herramienta registra `stdinRequested=true`\n- Emite texto explicativo\n- Envía una `input_reply` vacía\n- La ejecución se trata como fallo en la capa del executor\n\n## Captura de salida y renderizado\n\n### Clases de salida capturadas\n\nDesde los mensajes del kernel:\n\n- `stream` -> fragmentos de texto plano\n- `display_data`/`execute_result` -> manejo de visualización enriquecida\n- `error` -> texto de traceback\n- MIME personalizado `application/x-xcsh-status` -> eventos de estado estructurados\n\nPrecedencia de MIME para visualización:\n\n1. `text/markdown`\n2. `text/plain`\n3. `text/html` (convertido a markdown básico)\n\nAdicionalmente capturados como salidas estructuradas:\n\n- `application/json` -> datos de árbol JSON\n- `image/png` -> cargas útiles de imagen\n- `application/x-xcsh-status` -> eventos de estado\n\n### Almacenamiento y truncamiento\n\nLa salida se transmite a través de `OutputSink` y puede persistirse en almacenamiento de artefactos.\n\nLos resultados de la herramienta pueden incluir metadatos de truncamiento y `artifact://<id>` para recuperación de salida completa.\n\n### Comportamiento del renderizador\n\n- Renderizador de herramienta (`python.ts`):\n  - muestra bloques de celdas de código con estado por celda\n  - la vista previa contraída muestra por defecto 10 líneas\n  - soporta modo expandido para salida completa y detalle de estado más rico\n- Renderizador interactivo (`python-execution.ts`):\n  - utilizado para ejecución de Python activada por el usuario en TUI\n  - la vista previa contraída muestra por defecto 20 líneas\n  - limita las líneas individuales muy largas a 4000 caracteres por seguridad en la visualización\n  - muestra avisos de cancelación/error/truncamiento\n\n## Soporte de gateway externo\n\nConfigurar:\n\n```bash\nexport PI_PYTHON_GATEWAY_URL=\"http://127.0.0.1:8888\"\n# Opcional:\nexport PI_PYTHON_GATEWAY_TOKEN=\"...\"\n```\n\nDiferencias de comportamiento respecto al gateway local compartido:\n\n- Sin archivos de bloqueo/información de gateway local\n- Sin inicio/terminación de proceso local\n- Las verificaciones de salud y las operaciones CRUD de kernel se ejecutan contra el endpoint externo\n- Los fallos de autenticación se muestran con orientación explícita sobre el token\n\n## Resolución de problemas operativos (modos de fallo actuales)\n\n- **Herramienta Python no disponible**\n  - Verificar `python.toolMode` / `PI_PY`.\n  - Si la verificación previa falla, el runtime vuelve a bash-only.\n\n- **Errores de disponibilidad del kernel**\n  - El modo local requiere que tanto `kernel_gateway` como `ipykernel` sean importables en el runtime de Python resuelto.\n  - Instalar con:\n\n    ```bash\n    python -m pip install jupyter_kernel_gateway ipykernel\n    ```\n\n- **`python.sharedGateway=false` causa fallo en el inicio**\n  - Esto es esperado con la implementación actual.\n\n- **Fallos de autenticación/accesibilidad del gateway externo**\n  - 401/403 -> configurar `PI_PYTHON_GATEWAY_TOKEN`.\n  - timeout/inaccesible -> verificar URL/red y estado del gateway.\n\n- **La ejecución se bloquea y luego expira**\n  - Aumentar el `timeout` de la herramienta (máximo 600s) si la carga de trabajo es legítima.\n  - Para código bloqueado, la cancelación activa la interrupción del kernel, pero el código de usuario puede requerir refactorización.\n\n- **Prompts de stdin/input en código Python**\n  - `input()` no está soportado de forma interactiva en esta ruta de runtime; pasar datos de forma programática.\n\n- **Agotamiento de recursos (`EMFILE` / demasiados archivos abiertos)**\n  - El gestor de sesiones activa la recuperación del gateway compartido (desmontaje de sesión + reinicio del gateway compartido).\n\n- **Errores de directorio de trabajo**\n  - La herramienta valida que `cwd` exista y sea un directorio antes de la ejecución.\n\n## Variables de entorno relevantes\n\n- `PI_PY` — anulación de exposición de la herramienta (mapeo `bash-only`/`ipy-only`/`both` descrito anteriormente)\n- `PI_PYTHON_GATEWAY_URL` — usar gateway externo\n- `PI_PYTHON_GATEWAY_TOKEN` — token de autenticación opcional para gateway externo\n- `PI_PYTHON_SKIP_CHECK=1` — omitir verificaciones previas/de calentamiento de Python\n- `PI_PYTHON_IPC_TRACE=1` — registrar trazas de envío/recepción IPC del kernel\n- `PI_DEBUG_STARTUP=1` — emitir marcadores de depuración de etapa de inicio\n",
	"es/runtime-tools/bash-tool-runtime.md": "---\ntitle: Tiempo de ejecución de la herramienta Bash\ndescription: >-\n  Tiempo de ejecución de la herramienta Bash con gestión de procesos de shell,\n  aislamiento, tiempo de espera y transmisión de salida.\nsidebar:\n  order: 1\n  label: Herramienta Bash\ni18n:\n  sourceHash: 18b12aa5dbd5\n  translator: machine\n---\n\n# Tiempo de ejecución de la herramienta Bash\n\nEste documento describe la ruta de tiempo de ejecución de la **herramienta `bash`** utilizada por las llamadas a herramientas del agente, desde la normalización de comandos hasta la ejecución, truncado/artefactos y renderizado.\n\nTambién señala dónde difiere el comportamiento en la TUI interactiva, el modo de impresión, el modo RPC y la ejecución de shell bang (`!`) iniciada por el usuario.\n\n## Alcance y superficies de tiempo de ejecución\n\nExisten dos superficies de ejecución bash diferentes en el agente de codificación:\n\n1. **Superficie de llamada a herramienta** (`toolName: \"bash\"`): utilizada cuando el modelo llama a la herramienta bash.\n   - Punto de entrada: `BashTool.execute()`.\n2. **Superficie de comando bang de usuario** (`!cmd` desde entrada interactiva o comando RPC `bash`): ruta auxiliar a nivel de sesión.\n   - Punto de entrada: `AgentSession.executeBash()`.\n\nAmbas utilizan finalmente `executeBash()` en `src/exec/bash-executor.ts` para la ejecución sin PTY, pero solo la ruta de llamada a herramienta ejecuta la lógica de normalización/interceptación y de renderizado de herramienta.\n\n## Canalización de llamada a herramienta de extremo a extremo\n\n## 1) Normalización de entrada y fusión de parámetros\n\n`BashTool.execute()` primero normaliza el comando en bruto mediante `normalizeBashCommand()`:\n\n- extrae los sufijos `| head -n N`, `| head -N`, `| tail -n N`, `| tail -N` en límites estructurados,\n- elimina los espacios en blanco al inicio y al final,\n- mantiene intactos los espacios en blanco internos.\n\nLuego fusiona los límites extraídos con los argumentos explícitos de la herramienta:\n\n- los argumentos explícitos `head`/`tail` tienen precedencia sobre los valores extraídos,\n- los valores extraídos son solo de reserva.\n\n### Advertencia\n\nLos comentarios de `bash-normalize.ts` mencionan la eliminación de `2>&1`, pero la implementación actual no lo elimina. El comportamiento en tiempo de ejecución sigue siendo correcto (stdout/stderr ya se combinan), pero el comportamiento de normalización es más limitado de lo que sugieren los comentarios.\n\n## 2) Interceptación opcional (ruta de comando bloqueado)\n\nSi `bashInterceptor.enabled` es verdadero, `BashTool` carga las reglas de configuración y ejecuta `checkBashInterception()` contra el comando normalizado.\n\nComportamiento de interceptación:\n\n- el comando se bloquea **solo** cuando:\n  - la regla de expresión regular coincide, y\n  - la herramienta sugerida está presente en `ctx.toolNames`.\n- las reglas de expresión regular no válidas se omiten silenciosamente.\n- al bloquearse, `BashTool` lanza `ToolError` con el mensaje:\n  - `Blocked: ...`\n  - comando original incluido.\n\nLos patrones de reglas predeterminados (definidos en el código) apuntan a usos incorrectos comunes:\n\n- lectores de archivos (`cat`, `head`, `tail`, ...),\n- herramientas de búsqueda (`grep`, `rg`, ...),\n- buscadores de archivos (`find`, `fd`, ...),\n- editores en sitio (`sed -i`, `perl -i`, `awk -i inplace`),\n- escrituras de redirección de shell (`echo ... > file`, redirección heredoc).\n\n### Advertencia\n\n`InterceptionResult` incluye `suggestedTool`, pero `BashTool` actualmente solo muestra el texto del mensaje (sin campo de herramienta sugerida estructurado en `details`).\n\n## 3) Validación de CWD y limitación de tiempo de espera\n\n`cwd` se resuelve de manera relativa al cwd de sesión (`resolveToCwd`), luego se valida mediante `stat`:\n\n- ruta inexistente -> `ToolError(\"Working directory does not exist: ...\")`\n- no es un directorio -> `ToolError(\"Working directory is not a directory: ...\")`\n\nEl tiempo de espera se limita a `[1, 3600]` segundos y se convierte a milisegundos.\n\n## 4) Asignación de artefactos\n\nAntes de la ejecución, la herramienta asigna una ruta/id de artefacto (con mejor esfuerzo) para el almacenamiento de salida truncada.\n\n- el fallo en la asignación de artefactos no es fatal (la ejecución continúa sin archivo de desbordamiento de artefacto),\n- el id/ruta del artefacto se pasan a la ruta de ejecución para la persistencia de salida completa en caso de truncado.\n\n## 5) Selección de ejecución PTY vs sin PTY\n\n`BashTool` elige la ejecución PTY solo cuando se cumplen todas las condiciones:\n\n- `bash.virtualTerminal === \"on\"`\n- `PI_NO_PTY !== \"1\"`\n- el contexto de la herramienta tiene interfaz de usuario (`ctx.hasUI === true` y `ctx.ui` definido)\n\nDe lo contrario, utiliza `executeBash()` no interactivo.\n\nEsto significa que el modo de impresión y los contextos RPC/herramienta sin interfaz de usuario siempre usan sin PTY.\n\n## Motor de ejecución no interactiva (`executeBash`)\n\n## Modelo de reutilización de sesión de shell\n\n`executeBash()` almacena en caché instancias nativas de `Shell` en un mapa global de proceso con clave por:\n\n- ruta de shell,\n- prefijo de comando configurado,\n- ruta de instantánea,\n- entorno de shell serializado,\n- clave de sesión de agente opcional.\n\nPara ejecuciones a nivel de sesión, `AgentSession.executeBash()` pasa `sessionKey: this.sessionId`, aislando la reutilización por sesión.\n\nLa ruta de llamada a herramienta **no** pasa `sessionKey`, por lo que el alcance de reutilización se basa en la configuración de shell/instantánea/entorno.\n\n## Configuración de shell y comportamiento de instantáneas\n\nEn cada llamada, el ejecutor carga la configuración de shell de los ajustes (`shell`, `env`, `prefix` opcional).\n\nSi el shell seleccionado incluye `bash`, intenta `getOrCreateSnapshot()`:\n\n- la instantánea captura alias/funciones/opciones del rc del usuario,\n- la creación de instantáneas es con mejor esfuerzo,\n- el fallo recurre a ninguna instantánea.\n\nSi se configura `prefix`, el comando se convierte en:\n\n```text\n<prefix> <command>\n```\n\n## Transmisión y cancelación\n\n`Shell.run()` transmite fragmentos al callback. El ejecutor canaliza cada fragmento a `OutputSink` y al callback `onChunk` opcional.\n\nCancelación:\n\n- la señal abortada activa `shellSession.abort(...)`,\n- el tiempo de espera del resultado nativo se asigna a `cancelled: true` + texto de anotación,\n- la cancelación explícita también devuelve `cancelled: true` + anotación.\n\nNo se lanza ninguna excepción dentro del ejecutor por tiempo de espera/cancelación; devuelve un `BashResult` estructurado y deja que el llamador mapee la semántica de error.\n\n## Ruta PTY interactiva (`runInteractiveBashPty`)\n\nCuando PTY está habilitado, la herramienta ejecuta `runInteractiveBashPty()` que abre un componente de consola superpuesta y dirige una `PtySession` nativa.\n\nAspectos destacados del comportamiento:\n\n- la terminal virtual xterm-headless renderiza la vista en superposición,\n- la entrada de teclado se normaliza (incluida la gestión de secuencias Kitty y el modo de cursor de aplicación),\n- `esc` durante la ejecución termina la sesión PTY,\n- el cambio de tamaño de terminal se propaga al PTY (`session.resize(cols, rows)`).\n\nSe inyectan valores predeterminados de refuerzo del entorno para ejecuciones desatendidas:\n\n- paginadores deshabilitados (`PAGER=cat`, `GIT_PAGER=cat`, etc.),\n- indicaciones del editor deshabilitadas (`GIT_EDITOR=true`, `EDITOR=true`, ...),\n- indicaciones de terminal/autenticación reducidas (`GIT_TERMINAL_PROMPT=0`, `SSH_ASKPASS=/usr/bin/false`, `CI=1`),\n- indicadores de automatización de gestor de paquetes/herramienta para comportamiento no interactivo.\n\nLa salida PTY se normaliza (`CRLF`/`CR` a `LF`, `sanitizeText`) y se escribe en `OutputSink`, incluido el soporte de desbordamiento a artefacto.\n\nEn caso de error de inicio/tiempo de ejecución de PTY, el sink recibe una línea `PTY error: ...` y el comando finaliza con código de salida indefinido.\n\n## Gestión de salida: transmisión, truncado y desbordamiento a artefacto\n\nTanto las rutas PTY como las sin PTY utilizan `OutputSink`.\n\n## Semántica de OutputSink\n\n- mantiene un búfer de cola en memoria seguro para UTF-8 (`DEFAULT_MAX_BYTES`, actualmente 50 KB),\n- rastrea el total de bytes/líneas observados,\n- si existe una ruta de artefacto y la salida se desborda (o el archivo ya está activo), escribe el flujo completo en el archivo de artefacto,\n- cuando el umbral de memoria se desborda, recorta el búfer en memoria a la cola (seguro para límites UTF-8),\n- marca `truncated` cuando ocurre desbordamiento/escritura en archivo.\n\n`dump()` devuelve:\n\n- `output` (posiblemente con prefijo anotado),\n- `truncated`,\n- `totalLines/totalBytes`,\n- `outputLines/outputBytes`,\n- `artifactId` si el archivo de artefacto estaba activo.\n\n### Advertencia sobre salidas largas\n\nEl truncado en tiempo de ejecución se basa en el umbral de bytes en `OutputSink` (50 KB por defecto). No impone un límite estricto de 2000 líneas en esta ruta de código.\n\n## Actualizaciones en vivo de la herramienta\n\nPara la ejecución sin PTY, `BashTool` usa un `TailBuffer` separado para actualizaciones parciales y emite instantáneas `onUpdate` mientras el comando está en ejecución.\n\nPara la ejecución PTY, el renderizado en vivo es gestionado por la superposición de interfaz de usuario personalizada, no por fragmentos de texto `onUpdate`.\n\n## Conformación de resultados, metadatos y mapeo de errores\n\nDespués de la ejecución:\n\n1. Gestión de `cancelled`:\n   - si la señal de aborto está activada -> lanzar `ToolAbortError` (semántica de aborto),\n   - de lo contrario -> lanzar `ToolError` (tratado como fallo de herramienta).\n2. `timedOut` de PTY -> lanzar `ToolError`.\n3. aplicar filtros head/tail al texto de salida final (`applyHeadTail`, head primero, luego tail).\n4. la salida vacía se convierte en `(no output)`.\n5. adjuntar metadatos de truncado mediante `toolResult(...).truncationFromSummary(result, { direction: \"tail\" })`.\n6. mapeo de código de salida:\n   - código de salida faltante -> `ToolError(\"... missing exit status\")`\n   - salida no cero -> `ToolError(\"... Command exited with code N\")`\n   - salida cero -> resultado exitoso.\n\nEstructura de carga útil exitosa:\n\n- `content`: salida de texto,\n- `details.meta.truncation` cuando se trunca, incluyendo:\n  - `direction`, `truncatedBy`, conteos totales/de salida de líneas + bytes,\n  - `shownRange`,\n  - `artifactId` cuando está disponible.\n\nDebido a que las herramientas integradas están envueltas con `wrapToolWithMetaNotice()`, el texto de aviso de truncado se agrega automáticamente al contenido de texto final (por ejemplo: `Full: artifact://<id>`).\n\n## Rutas de renderizado\n\n## Renderizador de llamada a herramienta (`bashToolRenderer`)\n\n`bashToolRenderer` se utiliza para mensajes de llamada a herramienta (`toolCall` / `toolResult`):\n\n- el modo contraído muestra una vista previa truncada por líneas visuales,\n- el modo expandido muestra todo el texto de salida disponible actualmente,\n- la línea de advertencia incluye la razón del truncado y `artifact://<id>` cuando se trunca,\n- el valor de tiempo de espera (de los argumentos) se muestra en la línea de metadatos del pie de página.\n\n### Advertencia: expansión completa de artefacto\n\n`BashRenderContext` tiene `isFullOutput`, pero el constructor de contexto de renderizador actual no lo establece para los resultados de la herramienta bash. La vista expandida sigue utilizando el texto ya presente en el contenido del resultado (salida de cola/truncada) a menos que otro llamador proporcione el contenido completo del artefacto.\n\n## Componente de comando bang de usuario (`BashExecutionComponent`)\n\n`BashExecutionComponent` es para comandos `!` de usuario en modo interactivo (no llamadas a herramienta del modelo):\n\n- transmite fragmentos en vivo,\n- la vista previa contraída mantiene las últimas 20 líneas lógicas,\n- límite de línea a 4000 caracteres por línea,\n- muestra advertencias de truncado + artefacto cuando hay metadatos presentes,\n- marca el estado de cancelado/error/salida por separado.\n\nEste componente es conectado por `CommandController.handleBashCommand()` y alimentado desde `AgentSession.executeBash()`.\n\n## Diferencias de comportamiento específicas por modo\n\n| Superficie                          | Ruta de entrada                                        | Elegible para PTY                                                          | UX de salida en vivo                                                        | Exposición de errores                                            |\n| ----------------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------- | --------------------------------------------------------------------------- | ---------------------------------------------------------------- |\n| Llamada a herramienta interactiva   | `BashTool.execute`                                     | Sí, cuando `bash.virtualTerminal=on` y existe interfaz de usuario y `PI_NO_PTY!=1` | Superposición PTY (interactiva) o actualizaciones de cola transmitidas      | Los errores de herramienta se convierten en `toolResult.isError` |\n| Llamada a herramienta en modo impresión | `BashTool.execute`                                 | No (sin contexto de interfaz de usuario)                                   | Sin superposición TUI; la salida aparece en el flujo de eventos/texto final del asistente | Mismo mapeo de error de herramienta                             |\n| Llamada a herramienta RPC (herramientas del agente) | `BashTool.execute`                    | Generalmente sin interfaz de usuario -> sin PTY                            | Eventos/resultados de herramienta estructurados                             | Mismo mapeo de error de herramienta                             |\n| Comando bang interactivo (`!`)      | `AgentSession.executeBash` + `BashExecutionComponent` | No (usa el ejecutor directamente)                                          | Componente de ejecución bash dedicado                                       | El controlador captura excepciones y muestra error de interfaz de usuario |\n| Comando RPC `bash`                  | `rpc-mode` -> `session.executeBash`                    | No                                                                         | Devuelve `BashResult` directamente                                          | El consumidor gestiona los campos devueltos                      |\n\n## Advertencias operativas\n\n- El interceptor solo bloquea comandos cuando la herramienta sugerida está disponible actualmente en el contexto.\n- Si la asignación de artefactos falla, el truncado sigue ocurriendo pero no hay referencia inversa `artifact://` disponible.\n- La caché de sesiones de shell no tiene evacuación explícita en este módulo; su duración es de alcance de proceso.\n- Las superficies de tiempo de espera de PTY y sin PTY difieren:\n  - PTY expone el campo de resultado explícito `timedOut`,\n  - sin PTY mapea el tiempo de espera en el resumen `cancelled + annotation`.\n\n## Archivos de implementación\n\n- [`src/tools/bash.ts`](../../packages/coding-agent/src/tools/bash.ts) — punto de entrada de la herramienta, normalización/interceptación, selección PTY/sin PTY, mapeo de resultado/error, renderizador de herramienta bash.\n- [`src/tools/bash-normalize.ts`](../../packages/coding-agent/src/tools/bash-normalize.ts) — normalización de comandos y filtrado head/tail posterior a la ejecución.\n- [`src/tools/bash-interceptor.ts`](../../packages/coding-agent/src/tools/bash-interceptor.ts) — coincidencia de reglas del interceptor y mensajes de comando bloqueado.\n- [`src/exec/bash-executor.ts`](../../packages/coding-agent/src/exec/bash-executor.ts) — ejecutor sin PTY, reutilización de sesión de shell, conexión de cancelación, integración de sink de salida.\n- [`src/tools/bash-interactive.ts`](../../packages/coding-agent/src/tools/bash-interactive.ts) — tiempo de ejecución PTY, interfaz de usuario superpuesta, normalización de entrada, valores predeterminados de entorno no interactivo.\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — truncado/desbordamiento a artefacto de `OutputSink` y metadatos de resumen.\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — ayudantes de asignación de artefactos y búfer de cola de transmisión.\n- [`src/tools/output-meta.ts`](../../packages/coding-agent/src/tools/output-meta.ts) — forma de metadatos de truncado + envoltorio de inyección de avisos.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — `executeBash` a nivel de sesión, registro de mensajes, ciclo de vida de aborto.\n- [`src/modes/components/bash-execution.ts`](../../packages/coding-agent/src/modes/components/bash-execution.ts) — componente de ejecución de comando `!` interactivo.\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts) — conexión para la finalización de flujo/actualización de interfaz de usuario del comando `!` interactivo.\n- [`src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts) — superficie de comandos RPC `bash` y `abort_bash`.\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — resolución de `artifact://<id>`.\n",
	"es/runtime-tools/context-command.md": "---\ntitle: Contextos de F5 XC\ndescription: >-\n  Conecte xcsh a tenants de F5 Distributed Cloud -- cree, cambie y administre\n  contextos de autenticación.\nsidebar:\n  order: 1\n  label: Contextos de F5 XC\ni18n:\n  sourceHash: a9cccbc338f0\n  translator: machine\n---\n\n# Contextos de F5 XC\n\nxcsh se conecta a F5 Distributed Cloud a través de **contextos** -- conjuntos de credenciales con nombre que vinculan una URL de tenant, un token de API y un namespace. Si ha utilizado `kubectl config use-context` o `kubectx`, el flujo de trabajo es idéntico: cree un contexto, cambie entre ellos por nombre y use `-` para volver al anterior.\n\n## Primeros pasos\n\n### 1. Cree su primer contexto\n\nNecesita tres elementos de su consola de F5 XC: la URL del tenant, un token de API y, opcionalmente, un namespace.\n\n```\n/context create production https://acme.console.ves.volterra.io p12k3-your-api-token\n```\n\n```\nContext 'production' created. Use /context activate production to switch to it.\n```\n\nO utilice el asistente guiado si prefiere indicaciones paso a paso:\n\n```\n/context wizard\n```\n\n### 2. Actívelo\n\n```\n/context production\n```\n\n```\n╭─ production ─────────────────────────────────────────────────╮\n│ XCSH_TENANT     acme                                         │\n│ XCSH_API_URL    https://acme.console.ves.volterra.io         │\n│ XCSH_API_TOKEN  ...oken                                      │\n│ Status          Connected (312ms)                            │\n├─ Environment ────────────────────────────────────────────────┤\n│ XCSH_NAMESPACE  default                                      │\n╰──────────────────────────────────────────────────────────────╯\n```\n\nUna vez activado, xcsh inyecta las credenciales del tenant en su sesión. El agente ahora puede realizar llamadas a la API de F5 XC, y la línea de estado muestra el contexto activo.\n\n### 3. Agregue más contextos y cambie entre ellos\n\n```\n/context create staging https://staging.console.ves.volterra.io p12k3-staging-token\n```\n\nCambie por nombre -- no se necesita un verbo de subcomando:\n\n```\n/context staging\n```\n\nVuelva al contexto anterior (estilo `cd -`):\n\n```\n/context -\n```\n\nLlamar a `/context -` dos veces le devuelve al punto de partida.\n\n### 4. Vea lo que tiene\n\n```\n/context\n```\n\n```\n  production           https://acme.console.ves.volterra.io\n* staging              https://staging.console.ves.volterra.io\n```\n\nEl `*` marca el contexto activo.\n\n## Comandos cotidianos\n\n| Comando | Qué hace |\n|---|---|\n| `/context` | Listar todos los contextos |\n| `/context <name>` | Cambiar a un contexto |\n| `/context -` | Cambiar al contexto anterior |\n| `/context show` | Mostrar detalles del contexto activo (tokens enmascarados) |\n| `/context status` | Mostrar el estado de autenticación actual |\n\n## Ciclo de vida del contexto\n\n| Comando | Qué hace |\n|---|---|\n| `/context create <name> <url> <token> [namespace]` | Crear un contexto |\n| `/context delete <name> --confirm` | Eliminar un contexto (requiere `--confirm`) |\n| `/context rename <old> <new>` | Renombrar un contexto |\n| `/context validate <name>` | Probar credenciales sin cambiar de contexto |\n| `/context export [name] [--include-token]` | Exportar como JSON (tokens enmascarados por defecto) |\n| `/context import <path-or-json> [--overwrite]` | Importar desde archivo o JSON en línea |\n| `/context wizard` | Configuración interactiva guiada |\n\n## Cambio de namespaces\n\nCada contexto tiene un namespace predeterminado. Cámbielo sin modificar el contexto:\n\n```\n/context namespace system\n```\n\nEl autocompletado con Tab ofrece nombres de namespace del tenant activo.\n\n## Variables de entorno en contextos\n\nLos contextos pueden llevar variables de entorno adicionales que se inyectan en su sesión al activarse. Útil para configuraciones por tenant que no forman parte del conjunto de credenciales.\n\n```\n/context set CUSTOM_HEADER=x-acme-trace\n/context set LOG_LEVEL=debug\n/context env list\n/context unset LOG_LEVEL\n```\n\nAlias: `add` = `set`, `remove`/`clear` = `unset`.\n\n## Autocompletado con Tab\n\nEscriba `/context ` y presione Tab. El menú desplegable muestra:\n\n1. **Nombres de contexto** -- con indicaciones de URL del tenant, para que pueda distinguir los tenants\n2. **`-`** -- aparece cuando ha cambiado anteriormente, muestra a qué contexto volvería\n3. **Subcomandos** -- `list`, `create`, `delete`, etc.\n\nLos nombres de contexto aparecen primero porque cambiar de contexto es la acción más común.\n\nLos autocompletados a nivel de subcomando también funcionan: `/context activate <Tab>` completa nombres de contexto, `/context namespace <Tab>` completa namespaces, `/context unset <Tab>` completa claves de variables de entorno conocidas.\n\n## Reglas de nomenclatura\n\nLos nombres de contexto deben tener entre 1 y 64 caracteres: letras, dígitos, guiones y guiones bajos.\n\nLos nombres que colisionan con subcomandos son rechazados:\n\n```\n/context create list https://example.com tok\n```\n\n```\nError: Context name 'list' conflicts with a /context subcommand. Choose a different name.\n```\n\nEl conjunto completo de palabras reservadas: `list`, `show`, `status`, `create`, `delete`, `rename`, `namespace`, `env`, `set`, `unset`, `add`, `remove`, `clear`, `activate`, `validate`, `export`, `import`, `wizard`, `help`. La comparación no distingue entre mayúsculas y minúsculas.\n\n## Sobreescritura por variables de entorno\n\nSi `XCSH_API_URL` y `XCSH_API_TOKEN` están configuradas en el entorno de su shell antes de iniciar xcsh, tienen prioridad sobre cualquier contexto. Esto es útil para pipelines de CI/CD o sesiones puntuales en las que no desea crear un contexto persistente.\n\nCuando se ejecuta en este modo, `/context` muestra las credenciales obtenidas del entorno con una etiqueta `(via env vars)`.\n\n## Comportamiento del contexto anterior\n\n- **Alcance de sesión**: el contexto anterior se restablece cuando reinicia xcsh. No se persiste en disco.\n- **Ping-pong**: `/context -` dos veces le devuelve al punto de partida.\n- **Seguro ante mutaciones**: si elimina el contexto anterior, el puntero se limpia. Si lo renombra, el puntero sigue al nuevo nombre.\n- **La reactivación es un no-op**: `/context production` cuando ya está en `production` no restablece el puntero anterior.\n\n## Convenciones de diseño\n\nLa experiencia de usuario de `/context` sigue:\n\n- **kubectx**: `kubectx <name>` para cambiar, `kubectx -` para el anterior, `kubectx` sin argumentos para listar\n- **kubectl**: `kubectl config use-context` para la forma explícita\n- **Shell**: `cd -` / `OLDPWD` para el seguimiento del directorio anterior\n",
	"es/runtime-tools/custom-tools.md": "---\ntitle: Herramientas Personalizadas\ndescription: >-\n  Registro de herramientas personalizadas, definición de esquemas y pipeline de\n  ejecución para extender el agente.\nsidebar:\n  order: 4\n  label: Herramientas personalizadas\ni18n:\n  sourceHash: 5f4a441fc2e2\n  translator: machine\n---\n\n# Herramientas Personalizadas\n\nLas herramientas personalizadas son funciones invocables por el modelo que se integran en el mismo pipeline de ejecución de herramientas que las herramientas incorporadas.\n\nUna herramienta personalizada es un módulo TypeScript/JavaScript que exporta una factoría. La factoría recibe una API del host (`CustomToolAPI`) y retorna una herramienta o un arreglo de herramientas.\n\n## Qué es esto (y qué no es)\n\n- **Herramienta personalizada**: invocable por el modelo durante un turno (`execute` + esquema TypeBox).\n- **Extensión**: framework de ciclo de vida/eventos que puede registrar herramientas e interceptar/modificar eventos.\n- **Hook**: scripts externos de pre/post comando.\n- **Skill**: paquete estático de guía/contexto, no código de herramienta ejecutable.\n\nSi necesita que el modelo invoque código directamente, use una herramienta personalizada.\n\n## Rutas de integración en el código actual\n\nExisten dos estilos de integración activos:\n\n1. **Herramientas personalizadas proporcionadas por el SDK** (`options.customTools`)\n   - Envueltas en herramientas del agente mediante `CustomToolAdapter` o wrappers de extensión.\n   - Siempre incluidas en el conjunto inicial de herramientas activas durante el bootstrap del SDK.\n\n2. **Módulos descubiertos en el sistema de archivos mediante la API del cargador** (`discoverAndLoadCustomTools` / `loadCustomTools`)\n   - Expuestas como APIs de biblioteca en `src/extensibility/custom-tools/loader.ts`.\n   - El código del host puede invocarlas para descubrir y cargar módulos de herramientas desde rutas de configuración/proveedor/plugin.\n\n```text\nFlujo de invocación de herramientas del modelo\n\nInvocación de herramienta por el LLM\n   │\n   ▼\nRegistro de herramientas (incorporadas + adaptadores de herramientas personalizadas)\n   │\n   ▼\nCustomTool.execute(toolCallId, params, onUpdate, ctx, signal)\n   │\n   ├─ onUpdate(...)  -> resultado parcial transmitido\n   └─ return result  -> contenido/detalles finales de la herramienta\n```\n\n## Ubicaciones de descubrimiento (API del cargador)\n\n`discoverAndLoadCustomTools(configuredPaths, cwd, builtInToolNames)` fusiona:\n\n1. Proveedores de capacidad (`toolCapability`), incluyendo:\n   - Configuración nativa OMP (`~/.xcsh/agent/tools`, `.xcsh/tools`)\n   - Configuración de Claude (`~/.claude/tools`, `.claude/tools`)\n   - Configuración de Codex (`~/.codex/tools`, `.codex/tools`)\n   - Proveedor de caché de plugins del marketplace de Claude\n2. Manifiestos de plugins instalados (`~/.xcsh/plugins/node_modules/*` mediante el cargador de plugins)\n3. Rutas configuradas explícitas pasadas al cargador\n\n### Comportamiento importante\n\n- Las rutas resueltas duplicadas se deduplicar.\n- Los conflictos de nombres de herramientas se rechazan contra las incorporadas y las herramientas personalizadas ya cargadas.\n- Los archivos `.md` y `.json` son descubiertos como metadatos de herramientas por algunos proveedores, pero el cargador de módulos ejecutables los rechaza como herramientas ejecutables.\n- Las rutas configuradas relativas se resuelven desde `cwd`; `~` se expande.\n\n## Contrato del módulo\n\nUn módulo de herramienta personalizada debe exportar una función (se prefiere la exportación por defecto):\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = (pi) => ({\n name: \"repo_stats\",\n label: \"Repo Stats\",\n description: \"Counts tracked TypeScript files\",\n parameters: pi.typebox.Type.Object({\n  glob: pi.typebox.Type.Optional(pi.typebox.Type.String({ default: \"**/*.ts\" })),\n }),\n\n async execute(toolCallId, params, onUpdate, ctx, signal) {\n  onUpdate?.({\n   content: [{ type: \"text\", text: \"Scanning files...\" }],\n   details: { phase: \"scan\" },\n  });\n\n  const result = await pi.exec(\"git\", [\"ls-files\", params.glob ?? \"**/*.ts\"], { signal, cwd: pi.cwd });\n  if (result.killed) {\n   throw new Error(\"Scan was cancelled\");\n  }\n  if (result.code !== 0) {\n   throw new Error(result.stderr || \"git ls-files failed\");\n  }\n\n  const files = result.stdout.split(\"\\n\").filter(Boolean);\n  return {\n   content: [{ type: \"text\", text: `Found ${files.length} files` }],\n   details: { count: files.length, sample: files.slice(0, 10) },\n  };\n },\n\n onSession(event) {\n  if (event.reason === \"shutdown\") {\n   // cleanup resources if needed\n  }\n },\n});\n\nexport default factory;\n```\n\nTipo de retorno de la factoría:\n\n- `CustomTool`\n- `CustomTool[]`\n- `Promise<CustomTool | CustomTool[]>`\n\n## Superficie de API pasada a las factorías (`CustomToolAPI`)\n\nDesde `types.ts` y `loader.ts`:\n\n- `cwd`: directorio de trabajo del host\n- `exec(command, args, options?)`: helper de ejecución de procesos\n- `ui`: contexto de UI (puede ser no-op en modos headless)\n- `hasUI`: `false` en flujos no interactivos\n- `logger`: logger compartido de archivos\n- `typebox`: `@sinclair/typebox` inyectado\n- `pi`: exportaciones de `@f5-sales-demo/xcsh` inyectadas\n- `pushPendingAction(action)`: registrar una acción de vista previa para la herramienta oculta `resolve` (`docs/resolve-tool-runtime.md`)\n\nEl cargador comienza con un contexto de UI no-op y requiere que el código del host invoque `setUIContext(...)` cuando la UI real esté lista.\n\n## Contrato de ejecución y tipado\n\nFirma de `CustomTool.execute`:\n\n```ts\nexecute(toolCallId, params, onUpdate, ctx, signal)\n```\n\n- `params` está tipado estáticamente desde su esquema TypeBox mediante `Static<TParams>`.\n- La validación de argumentos en tiempo de ejecución ocurre antes de la ejecución en el bucle del agente.\n- `onUpdate` emite resultados parciales para la transmisión en la UI.\n- `ctx` incluye el estado de sesión/modelo y un helper `abort()`.\n- `signal` transporta la cancelación.\n\n`CustomToolAdapter` conecta esto con la interfaz de herramientas del agente y reenvía las invocaciones en el orden correcto de argumentos.\n\n## Cómo se exponen las herramientas al modelo\n\n- Las herramientas se envuelven en instancias de `AgentTool` (`CustomToolAdapter` o wrappers de extensión).\n- Se insertan en el registro de herramientas de la sesión por nombre.\n- En el bootstrap del SDK, las herramientas personalizadas y las registradas por extensiones se incluyen forzosamente en el conjunto activo inicial.\n- `--tools` en CLI actualmente valida solo nombres de herramientas incorporadas; la inclusión de herramientas personalizadas se maneja a través de las rutas de descubrimiento/registro y las opciones del SDK.\n\n## Hooks de renderizado\n\nHooks de renderizado opcionales:\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\nComportamiento en tiempo de ejecución en TUI:\n\n- Si los hooks existen, la salida de la herramienta se renderiza dentro de un contenedor `Box`.\n- `renderResult` recibe `{ expanded, isPartial, spinnerFrame? }`.\n- Los errores del renderizador se capturan y registran; la UI recurre al renderizado de texto por defecto.\n\n## Manejo de sesión/estado\n\nEl método opcional `onSession(event, ctx)` recibe eventos del ciclo de vida de la sesión, incluyendo:\n\n- `start`, `switch`, `branch`, `tree`, `shutdown`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`, `todo_reminder`\n\nUse `ctx.sessionManager` para reconstruir el estado desde el historial cuando cambie el contexto de rama/sesión.\n\n## Semánticas de fallos y cancelación\n\n### Fallos síncronos/asíncronos\n\n- Lanzar excepciones (o promesas rechazadas) en `execute` se trata como fallo de la herramienta.\n- El runtime del agente convierte los fallos en mensajes de resultado de herramienta con `isError: true` y contenido de texto del error.\n- Con wrappers de extensión, los manejadores de `tool_result` pueden reescribir adicionalmente contenido/detalles e incluso anular el estado de error.\n\n### Cancelación\n\n- La cancelación del agente se propaga a través de `AbortSignal` hacia `execute`.\n- Reenvíe `signal` al trabajo de subprocesos (`pi.exec(..., { signal })`) para cancelación cooperativa.\n- `ctx.abort()` permite que una herramienta solicite la cancelación de la operación actual del agente.\n\n### Errores de onSession\n\n- Los errores de `onSession` se capturan y registran como advertencias; no provocan el fallo de la sesión.\n\n## Restricciones reales a considerar en el diseño\n\n- Los nombres de herramientas deben ser globalmente únicos en el registro activo.\n- Prefiera salidas deterministas con forma de esquema en `details` para la reconstrucción del renderizador/estado.\n- Proteja el uso de la UI con `pi.hasUI`.\n- Trate los archivos `.md`/`.json` en directorios de herramientas como metadatos, no como módulos ejecutables.\n",
	"es/runtime-tools/notebook-tool-runtime.md": "---\ntitle: Componentes internos del tiempo de ejecución de la herramienta Notebook\ndescription: >-\n  Tiempo de ejecución de la herramienta de cuadernos Jupyter con ejecución de\n  celdas, ciclo de vida del kernel y renderizado de resultados.\nsidebar:\n  order: 2\n  label: Herramienta Notebook\ni18n:\n  sourceHash: c1bafcb245e4\n  translator: machine\n---\n\n# Componentes internos del tiempo de ejecución de la herramienta Notebook\n\nEste documento describe la implementación actual de la herramienta `notebook` y su relación con el tiempo de ejecución de Python respaldado por kernel.\n\nLa distinción fundamental: **`notebook` es un editor de cuadernos JSON, no un ejecutor de cuadernos**. Edita directamente las fuentes de celdas de archivos `.ipynb`; no inicia ni se comunica con un kernel de Python.\n\n## Archivos de implementación\n\n- [`src/tools/notebook.ts`](../../packages/coding-agent/src/tools/notebook.ts)\n- [`src/ipy/executor.ts`](../../packages/coding-agent/src/ipy/executor.ts)\n- [`src/ipy/kernel.ts`](../../packages/coding-agent/src/ipy/kernel.ts)\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts)\n- [`src/tools/python.ts`](../../packages/coding-agent/src/tools/python.ts)\n\n## 1) Límite de tiempo de ejecución: edición frente a ejecución\n\n## Herramienta `notebook` (`src/tools/notebook.ts`)\n\n- Admite `action: edit | insert | delete` sobre un archivo `.ipynb`.\n- Resuelve la ruta relativa al CWD de la sesión (`resolveToCwd`).\n- Carga el JSON del cuaderno, valida el array `cells` y los límites de `cell_index`.\n- Aplica las ediciones de fuente en memoria y escribe el JSON completo del cuaderno con `JSON.stringify(notebook, null, 1)`.\n- Devuelve un resumen textual más `details` estructurados (`action`, `cellIndex`, `cellType`, `totalCells`, `cellSource`).\n\nNo existe ciclo de vida del kernel en esta herramienta:\n\n- sin adquisición de gateway\n- sin ID de sesión de kernel\n- sin `execute_request`\n- sin fragmentos de stream desde los canales del kernel\n- sin captura de visualización enriquecida (`image/png`, visualización JSON, MIME de estado)\n\n## Ruta de ejecución similar a un cuaderno (`src/tools/python.ts` + `src/ipy/*`)\n\nCuando el agente necesita ejecutar código Python al estilo de celdas (celdas secuenciales, estado persistente, visualizaciones enriquecidas), eso se gestiona a través de la herramienta **`python`**, no de `notebook`.\n\nEn esa ruta es donde residen los modos de kernel, el comportamiento de reinicio/cancelación, el streaming de fragmentos y el truncamiento de artefactos de salida.\n\n## 2) Semántica de manejo de celdas del cuaderno (herramienta `notebook`)\n\n## Normalización de fuente\n\n`content` se divide en `source: string[]` con preservación de saltos de línea:\n\n- cada línea que no sea la final conserva el `\\n` final\n- la línea final no tiene salto de línea forzado al final\n\nEsto refleja las convenciones del JSON de cuadernos y evita la concatenación accidental de líneas en ediciones posteriores.\n\n## Comportamiento de las acciones\n\n- `edit`\n  - reemplaza `cells[cell_index].source`\n  - preserva el `cell_type` existente\n- `insert`\n  - inserta en `[0..cellCount]`\n  - `cell_type` tiene como valor predeterminado `code`\n  - las celdas de código inicializan `execution_count: null` y `outputs: []`\n  - las celdas markdown solo inicializan `metadata` + `source`\n- `delete`\n  - elimina `cells[cell_index]`\n  - devuelve el `source` eliminado en los detalles para la vista previa del renderizador\n\n## Superficies de error\n\nSe lanzan errores críticos en los siguientes casos:\n\n- archivo de cuaderno no encontrado\n- JSON inválido\n- `cells` ausente o que no es un array\n- índice fuera de rango (inserción y no inserción tienen rangos válidos diferentes)\n- `content` ausente para `edit`/`insert`\n\nEstos se convierten en respuestas de herramienta con `Error:` en los niveles superiores; el renderizador utiliza la ruta del cuaderno más el texto de error formateado.\n\n## 3) Semántica de sesión del kernel (donde realmente existen)\n\nLa semántica del kernel se implementa en `executePython` / `PythonKernel` y se aplica a la herramienta `python`.\n\n## Modos\n\n`PythonKernelMode`:\n\n- `session` (predeterminado)\n  - kernels almacenados en caché en el mapa `kernelSessions`\n  - máximo 4 sesiones; la más antigua se elimina al desbordarse\n  - limpieza de sesiones inactivas/muertas cada 30s, tiempo de espera tras 5 minutos\n  - la cola por sesión serializa la ejecución (`session.queue`)\n- `per-call`\n  - crea un kernel para la solicitud\n  - ejecuta\n  - siempre cierra el kernel en `finally`\n\n## Comportamiento de reinicio\n\nLa herramienta `python` pasa `reset` solo para la primera celda en una llamada de múltiples celdas; las celdas posteriores siempre se ejecutan con `reset: false`.\n\n## Muerte del kernel / reinicio / reintento\n\nEn modo sesión (`withKernelSession`):\n\n- el kernel muerto se detecta mediante latido (`kernel.isAlive()` comprobado cada 5s) o por fallo de ejecución.\n- el estado muerto previo a la ejecución desencadena `restartKernelSession`.\n- la ruta de fallo en tiempo de ejecución reintenta una vez: reinicia el kernel y vuelve a ejecutar el manejador.\n- `restartCount > 1` en la misma sesión lanza `Python kernel restarted too many times in this session`.\n\nComportamiento de reintento en el inicio:\n\n- la creación del kernel de gateway compartido reintenta una vez ante `SharedGatewayCreateError` con HTTP 5xx.\n\nRecuperación por agotamiento de recursos:\n\n- detecta fallos del tipo `EMFILE`/`ENFILE`/\"Too many open files\"\n- limpia las sesiones rastreadas\n- llama a `shutdownSharedGateway()`\n- reintenta la creación de la sesión del kernel una vez\n\n## 4) Inyección de variables de entorno/sesión\n\nEl inicio del kernel recibe un mapa de entorno opcional del ejecutor:\n\n- `PI_SESSION_FILE` (ruta del archivo de estado de sesión)\n- `ARTIFACTS` (directorio de artefactos)\n\n`PythonKernel.#initializeKernelEnvironment(...)` luego ejecuta el script de inicialización dentro del kernel para:\n\n- `os.chdir(cwd)`\n- inyectar entradas de entorno en `os.environ`\n- anteponer cwd a `sys.path` si no está presente\n\nImplicación:\n\n- los helpers de preludio que leen el contexto de sesión o artefactos dependen de estas variables de entorno en el estado del proceso Python.\n\n## 5) Manejo de streaming/fragmentos y visualizaciones (ruta respaldada por kernel)\n\nEl cliente del kernel procesa mensajes del protocolo Jupyter por ejecución:\n\n- `stream` -> fragmento de texto hacia `onChunk`\n- `execute_result` / `display_data` ->\n  - texto de visualización elegido por precedencia MIME: `text/markdown` > `text/plain` > `text/html` convertido\n  - salidas estructuradas capturadas por separado:\n    - `application/json` -> `{ type: \"json\" }`\n    - `image/png` -> `{ type: \"image\" }`\n    - `application/x-xcsh-status` -> `{ type: \"status\" }` (sin emisión de texto)\n- `error` -> texto de traceback enviado al stream de fragmentos + metadatos de error estructurados\n- `input_request` -> emite texto de advertencia de stdin, envía `input_reply` vacío, marca stdin como solicitado\n- la finalización espera tanto `execute_reply` como el estado `status=idle` del kernel\n\nCancelación/tiempo de espera:\n\n- la señal de cancelación activa `interrupt()` (REST `/interrupt` + `interrupt_request` por canal de control)\n- el resultado marca `cancelled=true`\n- la ruta de tiempo de espera anota la salida con `Command timed out after <n> seconds`\n\n## 6) Comportamiento de truncamiento y artefactos\n\n`OutputSink` en `src/session/streaming-output.ts` es utilizado por las rutas de ejecución del kernel (`executeWithKernel`):\n\n- sanea cada fragmento (`sanitizeText`)\n- rastrea el total de líneas, líneas de salida y bytes\n- archivo de desbordamiento de artefacto opcional (`artifactPath`, `artifactId`)\n- cuando el búfer en memoria supera el umbral (`DEFAULT_MAX_BYTES` salvo que se sobrescriba):\n  - marca como truncado\n  - conserva los bytes finales en memoria (límite seguro UTF-8)\n  - puede desbordar el stream completo hacia el sumidero de artefactos\n\n`dump()` devuelve:\n\n- texto de salida visible (posiblemente truncado por el final)\n- indicador de truncamiento + conteos\n- ID de artefacto (para referencias `artifact://<id>`)\n\nLa herramienta `python` convierte estos metadatos en avisos de truncamiento de resultados y advertencias en la TUI.\n\nLa herramienta `notebook` **no** utiliza `OutputSink`; no tiene pipeline de stream/truncamiento de artefactos porque no ejecuta código.\n\n## 7) Suposiciones del renderizador y formato\n\n## Renderizador de cuadernos (`notebookToolRenderer`)\n\n- vista de llamada: línea de estado con acción + ruta del cuaderno + metadatos de celda/tipo\n- vista de resultado:\n  - resumen de éxito derivado de `details`\n  - `cellSource` renderizado mediante `renderCodeCell`\n  - las celdas markdown establecen la pista de lenguaje `markdown`; otras celdas no tienen anulación de lenguaje explícita\n  - el límite de vista previa de código colapsada es `PREVIEW_LIMITS.COLLAPSED_LINES * 2`\n  - admite modo expandido mediante opciones de renderizado compartidas\n  - utiliza caché de renderizado con clave por ancho + estado expandido\n\nSuposición de renderizado de errores:\n\n- si el primer contenido de texto comienza con `Error:`, el renderizador lo formatea como bloque de error de cuaderno.\n\n## Renderizador de Python (para la salida de ejecución real)\n\nEl renderizado de ejecución respaldada por kernel espera:\n\n- transiciones de estado por celda (`pending/running/complete/error`)\n- sección opcional de eventos de estado estructurado\n- árboles opcionales de salida JSON\n- advertencias de truncamiento + puntero opcional a `artifact://<id>`\n\nEl comportamiento de este renderizador no está relacionado con los resultados de edición JSON de `notebook`, salvo que ambos reutilizan primitivas TUI compartidas.\n\n## 8) Divergencia respecto al comportamiento de la herramienta Python simple\n\nSi por \"herramienta Python simple\" se entiende la ruta de ejecución de `python`:\n\n- `python` ejecuta código en un kernel, persiste el estado según el modo, transmite fragmentos, captura visualizaciones enriquecidas, gestiona interrupciones/tiempos de espera y admite truncamiento de salida/artefactos.\n- `notebook` realiza únicamente mutaciones deterministas del JSON del cuaderno; sin ejecución, sin estado del kernel, sin stream de fragmentos, sin salidas de visualización, sin pipeline de artefactos.\n\nSi un flujo de trabajo necesita ambas capacidades:\n\n1. editar la fuente del cuaderno con `notebook`\n2. ejecutar celdas de código mediante `python` (pasando el código manualmente), no a través de `notebook`\n\nLa implementación actual no proporciona una sola herramienta que a la vez mute el archivo `.ipynb` y ejecute celdas del cuaderno a través del contexto del kernel.\n",
	"es/runtime-tools/resolve-tool-runtime.md": "---\ntitle: Aspectos internos del tiempo de ejecución de la herramienta Resolve\ndescription: >-\n  Tiempo de ejecución de la herramienta Resolve para resolución de rutas de\n  archivo, obtención de contenido y acceso a recursos basados en URL.\nsidebar:\n  order: 3\n  label: Herramienta Resolve\ni18n:\n  sourceHash: 06e8be8c5a3c\n  translator: machine\n---\n\n# Aspectos internos del tiempo de ejecución de la herramienta Resolve\n\nEste documento explica cómo se modelan los flujos de trabajo de vista previa/aplicación en el agente de codificación y cómo las herramientas personalizadas pueden participar mediante `pushPendingAction`.\n\n## Alcance y archivos clave\n\n- [`src/tools/resolve.ts`](../../packages/coding-agent/src/tools/resolve.ts)\n- [`src/tools/pending-action.ts`](../../packages/coding-agent/src/tools/pending-action.ts)\n- [`src/tools/ast-edit.ts`](../../packages/coding-agent/src/tools/ast-edit.ts)\n- [`src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts)\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n\n## Qué hace `resolve`\n\n`resolve` es una herramienta oculta que finaliza una acción de vista previa pendiente.\n\n- `action: \"apply\"` ejecuta `apply(reason)` sobre la acción pendiente y persiste los cambios.\n- `action: \"discard\"` invoca `reject(reason)` si se proporciona; de lo contrario, descarta la acción con un mensaje predeterminado \"Discarded\".\n\nSi no existe ninguna acción pendiente, `resolve` falla con:\n\n- `No pending action to resolve. Nothing to apply or discard.`\n\n## Las acciones pendientes forman una pila (LIFO)\n\nLas acciones pendientes se almacenan en `PendingActionStore` como una pila de tipo push/pop:\n\n- `push(action)` añade una nueva acción pendiente en la cima.\n- `peek()` inspecciona la acción actual en la cima.\n- `pop()` elimina y devuelve la acción en la cima.\n- `hasPending` indica si la pila no está vacía.\n\n`resolve` siempre consume la acción pendiente **más reciente** primero (`pop()`), por lo que las herramientas que producen múltiples vistas previas se resuelven en orden inverso al de su registro.\n\n## Ejemplo de productor integrado (`ast_edit`)\n\n`ast_edit` previsualiza primero los reemplazos estructurales. Cuando la vista previa tiene reemplazos y aún no se ha aplicado, agrega una acción pendiente que contiene:\n\n- etiqueta (resumen legible por humanos)\n- `sourceToolName` (`ast_edit`)\n- callback `apply(reason: string)` que vuelve a ejecutar la edición AST con `dryRun: false`\n\n`resolve(action=\"apply\", reason=\"...\")` pasa `reason` a este callback.\n\n## Herramientas personalizadas: `pushPendingAction`\n\nLas herramientas personalizadas pueden registrar acciones pendientes compatibles con resolve mediante `CustomToolAPI.pushPendingAction(...)`.\n\n`CustomToolPendingAction`:\n\n- `label: string` (obligatorio)\n- `apply(reason: string): Promise<AgentToolResult<unknown>>` (obligatorio) — se invoca al aplicar; `reason` es la cadena pasada a `resolve`\n- `reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>` (opcional) — se invoca al descartar; el valor de retorno reemplaza el mensaje predeterminado \"Discarded\" si se proporciona\n- `details?: unknown` (opcional)\n- `sourceToolName?: string` (opcional, por defecto `\"custom_tool\"`)\n\n### Ejemplo de uso mínimo\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = pi => ({\n name: \"batch_rename_preview\",\n label: \"Batch Rename Preview\",\n description: \"Previews renames and defers commit to resolve\",\n parameters: pi.typebox.Type.Object({\n  files: pi.typebox.Type.Array(pi.typebox.Type.String()),\n }),\n\n async execute(_toolCallId, params) {\n  const previewSummary = `Prepared rename plan for ${params.files.length} files`;\n\n  pi.pushPendingAction({\n   label: `Batch rename: ${params.files.length} files`,\n   sourceToolName: \"batch_rename_preview\",\n   apply: async (reason) => {\n    // apply writes here\n    return {\n     content: [{ type: \"text\", text: `Applied batch rename. Reason: ${reason}` }],\n    };\n   },\n   reject: async (reason) => {\n    // optional: cleanup or notify on discard\n    return {\n     content: [{ type: \"text\", text: `Discarded batch rename. Reason: ${reason}` }],\n    };\n   },\n  });\n\n  return {\n   content: [{ type: \"text\", text: `${previewSummary}. Call resolve to apply or discard.` }],\n  };\n },\n});\n\nexport default factory;\n```\n\n## Disponibilidad en tiempo de ejecución y fallos\n\n`pushPendingAction` es conectado por el cargador de herramientas personalizadas mediante el `PendingActionStore` de la sesión activa.\n\nSi el tiempo de ejecución no dispone de un almacén de acciones pendientes, `pushPendingAction` lanza:\n\n- `Pending action store unavailable for custom tools in this runtime.`\n\n## Comportamiento de selección de herramientas\n\nCuando `PendingActionStore.hasPending` es verdadero, el tiempo de ejecución del agente inclina la selección de herramientas hacia `resolve`, de modo que las vistas previas pendientes se finalicen explícitamente antes de que continúe el flujo normal de herramientas.\n\n## Orientación para desarrolladores\n\n- Utilice acciones pendientes únicamente para operaciones destructivas o de alto impacto que deban admitir aplicación/descarte explícito.\n- Mantenga `label` conciso y específico; se muestra en la salida del renderizador de resolve.\n- Asegúrese de que `apply(reason)` sea determinista e idempotente para una ejecución de un solo disparo; `reason` es informativo y no debe modificar el comportamiento.\n- Implemente `reject(reason)` cuando el descarte requiera limpieza (estado temporal, bloqueos, notificaciones); omítalo en vistas previas sin estado donde el mensaje predeterminado sea suficiente.\n- Si su herramienta puede preparar múltiples vistas previas, recuerde la semántica LIFO: la última acción enviada se resuelve primero.\n",
	"es/runtime-tools/slash-command-internals.md": "---\ntitle: Aspectos internos de los comandos de barra\ndescription: >-\n  Aspectos internos del sistema de comandos de barra con registro, análisis de\n  argumentos y despacho de ejecución.\nsidebar:\n  order: 5\n  label: Comandos de barra\ni18n:\n  sourceHash: 2cbd44a3de87\n  translator: machine\n---\n\n# Aspectos internos de los comandos de barra\n\nEste documento describe cómo los comandos de barra se descubren, deduplicación, se presentan en modo interactivo y se expanden en el momento del prompt en `coding-agent`.\n\n## Archivos de implementación\n\n- [`src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n- [`src/capability/slash-command.ts`](../../packages/coding-agent/src/capability/slash-command.ts)\n- [`src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`src/discovery/claude.ts`](../../packages/coding-agent/src/discovery/claude.ts)\n- [`src/discovery/codex.ts`](../../packages/coding-agent/src/discovery/codex.ts)\n- [`src/discovery/claude-plugins.ts`](../../packages/coding-agent/src/discovery/claude-plugins.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n\n## 1) Modelo de descubrimiento\n\nLos comandos de barra son una capacidad (`id: \"slash-commands\"`) indexada por nombre de comando (`key: cmd => cmd.name`).\n\nEl registro de capacidades carga todos los proveedores registrados, ordenados por prioridad de proveedor de forma descendente, y elimina duplicados por clave con semántica de **el primero gana**.\n\n### Precedencia de proveedores\n\nProveedores actuales de comandos de barra y sus prioridades:\n\n1. `native` (OMP) — prioridad `100`\n2. `claude` — prioridad `80`\n3. `claude-plugins` — prioridad `70`\n4. `codex` — prioridad `70`\n\nComportamiento en empates: los proveedores con igual prioridad mantienen el orden de registro. El orden de importación actual registra `claude-plugins` antes que `codex`, por lo que los comandos de plugin prevalecen sobre los comandos de codex en colisiones de nombres.\n\n### Comportamiento en colisiones de nombres\n\nPara `slash-commands`, las colisiones se resuelven estrictamente mediante deduplicación de capacidades:\n\n- el elemento de mayor precedencia se conserva en `result.items`\n- los duplicados de menor precedencia permanecen únicamente en `result.all` y se marcan con `_shadowed = true`\n\nEsto se aplica entre proveedores y también dentro de un proveedor si devuelve nombres duplicados.\n\n### Comportamiento de análisis de archivos\n\nLos proveedores utilizan principalmente `loadFilesFromDir(...)`, que actualmente:\n\n- tiene como valor predeterminado la coincidencia no recursiva (`*.md`)\n- utiliza glob nativo con `gitignore: true`, `hidden: false`\n- lee cada archivo coincidente y lo transforma en un `SlashCommand`\n\nPor lo tanto, los archivos y directorios ocultos no se cargan, y las rutas ignoradas se omiten.\n\n## 2) Rutas de origen específicas del proveedor y precedencia local\n\n## Proveedor `native` (`builtin.ts`)\n\nLas raíces de búsqueda provienen de directorios `.xcsh`:\n\n- proyecto: `<cwd>/.xcsh/commands/*.md`\n- usuario: `~/.xcsh/agent/commands/*.md`\n\n`getConfigDirs()` devuelve primero el proyecto y luego el usuario, por lo que **los comandos nativos del proyecto prevalecen sobre los comandos nativos del usuario** cuando los nombres colisionan.\n\n## Proveedor `claude` (`claude.ts`)\n\nCarga:\n\n- usuario: `~/.claude/commands/*.md`\n- proyecto: `<cwd>/.claude/commands/*.md`\n\nEl proveedor inserta los elementos del usuario antes que los del proyecto, por lo que **los comandos Claude del usuario prevalecen sobre los comandos Claude del proyecto** en colisiones de mismo nombre dentro de este proveedor.\n\n## Proveedor `codex` (`codex.ts`)\n\nCarga:\n\n- usuario: `~/.codex/commands/*.md`\n- proyecto: `<cwd>/.codex/commands/*.md`\n\nAmbos lados se cargan y luego se aplanan en orden del usuario primero, por lo que **los comandos Codex del usuario prevalecen sobre los comandos Codex del proyecto** en colisiones.\n\nEl contenido de los comandos Codex se analiza con eliminación de frontmatter (`parseFrontmatter`), y el nombre del comando puede ser reemplazado por el frontmatter `name`; de lo contrario, se utiliza el nombre del archivo.\n\n## Proveedor `claude-plugins` (`claude-plugins.ts`)\n\nCarga las raíces de comandos de plugins desde `~/.claude/plugins/installed_plugins.json`, luego analiza `<pluginRoot>/commands/*.md`.\n\nEl orden sigue el orden de iteración del registro y el orden de entrada por plugin de ese dato JSON. No hay un paso de ordenación adicional.\n\n## 3) Materialización al `FileSlashCommand` en tiempo de ejecución\n\n`loadSlashCommands()` en `src/extensibility/slash-commands.ts` convierte los elementos de capacidad en objetos `FileSlashCommand` utilizados en el momento del prompt.\n\nPara cada comando:\n\n1. analizar frontmatter/cuerpo (`parseFrontmatter`)\n2. fuente de descripción:\n   - `frontmatter.description` si está presente\n   - de lo contrario, la primera línea no vacía del cuerpo (recortada, máximo 60 caracteres con `...`)\n3. conservar el cuerpo analizado como contenido de plantilla ejecutable\n4. calcular una cadena de fuente de visualización como `via Claude Code Project`\n\nLa severidad del análisis de frontmatter depende de la fuente:\n\n- nivel `native` -> los errores de análisis son `fatal`\n- niveles `user`/`project` -> los errores de análisis son `warn` con análisis de respaldo\n\n### Comandos de respaldo integrados\n\nDespués de los comandos del sistema de archivos/proveedor, se añaden plantillas de comandos integrados (`EMBEDDED_COMMAND_TEMPLATES`) si sus nombres no están ya presentes.\n\nEl conjunto integrado actual proviene de `src/task/commands.ts` y se utiliza como respaldo (`source: \"bundled\"`).\n\n## 4) Modo interactivo: de dónde provienen las listas de comandos\n\nEl modo interactivo combina múltiples fuentes de comandos para el autocompletado y el enrutamiento de comandos.\n\nEn el momento de construcción, genera una lista de comandos pendientes a partir de:\n\n- comandos integrados (`BUILTIN_SLASH_COMMANDS`, incluye completado de argumentos e indicaciones en línea para comandos seleccionados)\n- comandos de barra registrados por extensiones (`extensionRunner.getRegisteredCommands(...)`)\n- comandos personalizados TypeScript (`session.customCommands`), mapeados a etiquetas de comandos de barra\n- comandos de habilidad opcionales (`/skill:<name>`) cuando `skills.enableSkillCommands` está habilitado\n\nLuego, `init()` llama a `refreshSlashCommandState(...)` para cargar los comandos basados en archivos e instala un `CombinedAutocompleteProvider` que contiene:\n\n- los comandos pendientes anteriores\n- los comandos basados en archivos descubiertos\n\n`refreshSlashCommandState(...)` también actualiza `session.setSlashCommands(...)` para que la expansión del prompt utilice el mismo conjunto de comandos de archivo descubiertos.\n\n### Ciclo de vida de actualización\n\nEl estado de los comandos de barra se actualiza:\n\n- durante la inicialización interactiva\n- después de que `/move` cambia el directorio de trabajo (`handleMoveCommand` llama a `resetCapabilities()` y luego a `refreshSlashCommandState(newCwd)`)\n\nNo existe un observador de archivos continuo para los directorios de comandos.\n\n### Otras superficies de presentación\n\nEl panel de Extensiones también carga la capacidad `slash-commands` y muestra las entradas de comandos activos/sombreados, incluidos los duplicados `_shadowed`.\n\n## 5) Posición en el pipeline de prompt\n\nOrden de manejo de barras en `AgentSession.prompt(...)` (cuando `expandPromptTemplates !== false`):\n\n1. **Comandos de extensión** (`#tryExecuteExtensionCommand`)  \n   Si `/name` coincide con un comando registrado por extensión, el manejador se ejecuta inmediatamente y el prompt retorna.\n2. **Comandos personalizados TypeScript** (`#tryExecuteCustomCommand`)  \n   Solo límite: si hay coincidencia, se ejecuta y puede retornar:\n   - `string` -> reemplaza el texto del prompt con esa cadena\n   - `void/undefined` -> se trata como manejado; no hay prompt LLM\n3. **Comandos de barra basados en archivos** (`expandSlashCommand`)  \n   Si el texto aún comienza con `/`, se intenta la expansión del comando markdown.\n4. **Plantillas de prompt** (`expandPromptTemplate`)  \n   Se aplican después del procesamiento de barra/personalizado.\n5. **Entrega**\n   - inactivo: el prompt se envía inmediatamente al agente\n   - en streaming: el prompt se pone en cola como steer/follow-up dependiendo de `streamingBehavior`\n\nPor eso la expansión de comandos de barra se sitúa antes de la expansión de plantillas de prompt, y por eso los comandos personalizados pueden transformar la barra inicial antes de la coincidencia de comandos de archivo.\n\n## 6) Semántica de expansión para comandos de barra basados en archivos\n\nComportamiento de `expandSlashCommand(text, fileCommands)`:\n\n- solo se ejecuta cuando el texto comienza con `/`\n- analiza el nombre del comando a partir del primer token después de `/`\n- analiza los argumentos del texto restante mediante `parseCommandArgs`\n- busca una coincidencia exacta de nombre en los `fileCommands` cargados\n- si hay coincidencia, aplica:\n  - reemplazo posicional: `$1`, `$2`, ...\n  - reemplazo agregado: `$ARGUMENTS` y `$@`\n  - luego renderizado de plantilla mediante `prompt.render` con `{ args, ARGUMENTS, arguments }`\n- si no hay coincidencia, devuelve el texto original sin cambios\n\n### Advertencias de `parseCommandArgs`\n\nEl analizador es una división simple con reconocimiento de comillas:\n\n- admite comillas `'simples'` y `\"dobles\"` para mantener los espacios\n- elimina los delimitadores de comillas\n- no implementa reglas de escape con barra invertida\n- una comilla sin cerrar no es un error; el analizador consume hasta el final\n\n## 7) Comportamiento desconocido de `/...`\n\nLa entrada de barra desconocida **no es rechazada** por la lógica central de comandos de barra.\n\nSi el comando no es manejado por las capas de extensión/personalizado/archivo, `expandSlashCommand` devuelve el texto original, y el prompt literal `/...` continúa a través de la expansión normal de plantillas de prompt y la entrega al LLM.\n\nEl modo interactivo maneja directamente muchos comandos integrados en `InputController` (por ejemplo `/settings`, `/model`, `/mcp`, `/move`, `/exit`). Estos se consumen antes de `session.prompt(...)` y por lo tanto nunca llegan a la expansión de comandos de archivo en esa ruta.\n\n## 8) Diferencias en el camino de streaming frente al camino inactivo\n\n## Camino inactivo\n\n- `session.prompt(\"/x ...\")` ejecuta el pipeline de comandos y ya sea ejecuta el comando inmediatamente o envía el texto expandido directamente.\n\n## Camino de streaming (`session.isStreaming === true`)\n\n- `prompt(...)` aún ejecuta las transformaciones de extensión/personalizado/archivo/plantilla primero\n- luego requiere `streamingBehavior`:\n  - `\"steer\"` -> pone en cola un mensaje de interrupción (`agent.steer`)\n  - `\"followUp\"` -> pone en cola un mensaje post-turno (`agent.followUp`)\n- si se omite `streamingBehavior`, el prompt lanza un error\n\n### Comportamiento de streaming específico por comando importante\n\n- Los comandos de extensión se ejecutan inmediatamente incluso durante el streaming (no se ponen en cola como texto).\n- Los métodos auxiliares `steer(...)`/`followUp(...)` rechazan los comandos de extensión (`#throwIfExtensionCommand`) para evitar poner en cola texto de comandos para manejadores que deben ejecutarse de forma síncrona.\n- La reproducción de la cola de compactación utiliza `isKnownSlashCommand(...)` para decidir si las entradas en cola deben reproducirse mediante `session.prompt(...)` (para comandos de barra conocidos) frente a los métodos raw steer/follow-up.\n\n## 9) Manejo de errores y superficies de fallo\n\n- Los fallos de carga del proveedor están aislados; el registro recopila advertencias y continúa con otros proveedores.\n- Los elementos de comandos de barra inválidos (nombre/ruta/contenido faltante o nivel inválido) son descartados por la validación de capacidades.\n- Fallos de análisis de frontmatter:\n  - comandos nativos: el error de análisis fatal se propaga\n  - comandos no nativos: advertencia + análisis de respaldo clave/valor\n- Las excepciones de los manejadores de comandos de extensión/personalizado son capturadas y reportadas a través del canal de error de extensión (o el respaldo de logger para comandos personalizados sin ejecutor de extensión), y se tratan como manejadas (sin ejecución de respaldo no intencionada).\n",
	"es/runtime-tools/task-agent-discovery.md": "---\ntitle: Descubrimiento y Selección de Agentes de Tarea\ndescription: >-\n  Lógica de descubrimiento y selección de agentes de tarea para enrutar trabajo\n  a tipos de subagentes especializados.\nsidebar:\n  order: 6\n  label: Descubrimiento de agentes de tarea\ni18n:\n  sourceHash: 8cf42457c672\n  translator: machine\n---\n\n# Descubrimiento y Selección de Agentes de Tarea\n\nEste documento describe cómo el subsistema de tareas descubre definiciones de agentes, fusiona múltiples fuentes y resuelve un agente solicitado en tiempo de ejecución.\n\nCubre el comportamiento en tiempo de ejecución tal como está implementado actualmente, incluyendo precedencia, manejo de definiciones inválidas y restricciones de generación/profundidad que pueden hacer que un agente sea efectivamente no disponible.\n\n## Archivos de implementación\n\n- [`src/task/discovery.ts`](../../packages/coding-agent/src/task/discovery.ts)\n- [`src/task/agents.ts`](../../packages/coding-agent/src/task/agents.ts)\n- [`src/task/types.ts`](../../packages/coding-agent/src/task/types.ts)\n- [`src/task/index.ts`](../../packages/coding-agent/src/task/index.ts)\n- [`src/task/commands.ts`](../../packages/coding-agent/src/task/commands.ts)\n- [`src/prompts/agents/task.md`](../../packages/coding-agent/src/prompts/agents/task.md)\n- [`src/prompts/tools/task.md`](../../packages/coding-agent/src/prompts/tools/task.md)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/config.ts`](../../packages/coding-agent/src/config.ts)\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts)\n\n---\n\n## Forma de la definición de agente\n\nLos agentes de tarea se normalizan en `AgentDefinition` (`src/task/types.ts`):\n\n- `name`, `description`, `systemPrompt` (requeridos para un agente cargado válido)\n- opcionales: `tools`, `spawns`, `model`, `thinkingLevel`, `output`\n- `source`: `\"bundled\" | \"user\" | \"project\"`\n- `filePath` opcional\n\nEl análisis proviene del frontmatter mediante `parseAgentFields()` (`src/discovery/helpers.ts`):\n\n- `name` o `description` faltantes => inválido (`null`), el llamador lo trata como fallo de análisis\n- `tools` acepta CSV o array; si se proporciona, `submit_result` se agrega automáticamente\n- `spawns` acepta `*`, CSV o array\n- comportamiento de compatibilidad hacia atrás: si `spawns` falta pero `tools` incluye `task`, `spawns` se convierte en `*`\n- `output` se pasa tal cual como datos de esquema opacos\n\n## Agentes integrados\n\nLos agentes integrados se incrustan en tiempo de compilación (`src/task/agents.ts`) usando importaciones de texto.\n\n`EMBEDDED_AGENT_DEFS` define:\n\n- `explore`, `plan`, `designer`, `reviewer` desde archivos de prompt\n- `task` y `quick_task` desde el cuerpo compartido `task.md` más frontmatter inyectado\n\nRuta de carga:\n\n1. `loadBundledAgents()` analiza el markdown incrustado con `parseAgent(..., \"bundled\", \"fatal\")`\n2. los resultados se almacenan en caché en memoria (`bundledAgentsCache`)\n3. `clearBundledAgentsCache()` es un reinicio de caché solo para pruebas\n\nDado que el análisis de los integrados usa `level: \"fatal\"`, un frontmatter mal formado en los integrados lanza una excepción y puede hacer fallar el descubrimiento por completo.\n\n## Descubrimiento desde sistema de archivos y plugins\n\n`discoverAgents(cwd, home)` (`src/task/discovery.ts`) fusiona agentes de múltiples lugares antes de agregar las definiciones integradas.\n\n### Entradas de descubrimiento\n\n1. Directorios de agentes de la configuración de usuario desde `getConfigDirs(\"agents\", { project: false })`\n2. Directorios de agentes del proyecto más cercano desde `findAllNearestProjectConfigDirs(\"agents\", cwd)`\n3. Raíces de plugins de Claude (`listClaudePluginRoots(home)`) con subdirectorios `agents/`\n4. Agentes integrados (`loadBundledAgents()`)\n\n### Orden real de las fuentes\n\nEl orden de las familias de fuentes proviene de `getConfigDirs(\"\", { project: false })`, que se deriva de `priorityList` en `src/config.ts`:\n\n1. `.xcsh`\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nPara cada familia de fuentes, el orden de descubrimiento es:\n\n1. directorio del proyecto más cercano para esa fuente (si se encuentra)\n2. directorio de usuario para esa fuente\n\nDespués de todos los directorios de familias de fuentes, se agregan los directorios `agents/` de plugins (primero plugins de ámbito de proyecto, luego de ámbito de usuario).\n\nLos agentes integrados se agregan al final.\n\n### Advertencia importante: comentarios desactualizados vs código actual\n\nLos comentarios de encabezado de `discovery.ts` aún mencionan `.pi` y no mencionan `.codex`/`.gemini`. El orden real en tiempo de ejecución está controlado por `src/config.ts` y actualmente usa `.xcsh`, `.claude`, `.codex`, `.gemini`.\n\n## Reglas de fusión y colisión\n\nEl descubrimiento usa deduplicación por primera aparición basada en el `agent.name` exacto:\n\n- Un `Set<string>` rastrea los nombres ya vistos.\n- Los agentes cargados se aplanan en orden de directorio y se mantienen solo si el nombre no se ha visto.\n- Los agentes integrados se filtran contra el mismo conjunto y solo se agregan si aún no se han visto.\n\nImplicaciones:\n\n- El proyecto sobrescribe al usuario para la misma familia de fuentes.\n- La familia de fuentes de mayor prioridad sobrescribe a la de menor prioridad (`.xcsh` antes que `.claude`, etc.).\n- Los agentes no integrados sobrescriben a los agentes integrados con el mismo nombre.\n- La coincidencia de nombres es sensible a mayúsculas y minúsculas (`Task` y `task` son distintos).\n- Dentro de un directorio, los archivos markdown se leen en orden lexicográfico de nombre de archivo antes de la deduplicación.\n\n## Comportamiento ante archivos de agente inválidos/faltantes\n\nPor directorio (`loadAgentsFromDir`):\n\n- directorio ilegible/faltante: tratado como vacío (`readdir(...).catch(() => [])`)\n- fallo en lectura o análisis de archivo: se registra advertencia, se omite el archivo\n- la ruta de análisis usa `parseAgent(..., level: \"warn\")`\n\nEl comportamiento ante fallos de frontmatter proviene de `parseFrontmatter`:\n\n- error de análisis en nivel `warn` registra una advertencia\n- el analizador recurre a un analizador simple línea por línea de `key: value`\n- si los campos requeridos siguen faltando, `parseAgentFields` falla, entonces se lanza `AgentParsingError` y es capturado por el llamador (se omite el archivo)\n\nEfecto neto: un archivo de agente personalizado defectuoso no aborta el descubrimiento de otros archivos.\n\n## Búsqueda y selección de agentes\n\nLa búsqueda es una búsqueda lineal por nombre exacto:\n\n- `getAgent(agents, name)` => `agents.find(a => a.name === name)`\n\nEn la ejecución de tareas (`TaskTool.execute`):\n\n1. los agentes se redescubren en el momento de la llamada (`discoverAgents(this.session.cwd)`)\n2. el `params.agent` solicitado se resuelve a través de `getAgent`\n3. un agente faltante devuelve una respuesta inmediata de la herramienta:\n   - `Unknown agent \"...\". Available: ...`\n   - no se ejecuta ningún subproceso\n\n### Descripción vs descubrimiento en tiempo de ejecución\n\n`TaskTool.create()` construye la descripción de la herramienta a partir de los resultados de descubrimiento en el momento de la inicialización (`buildDescription`).\n\n`execute()` redescubre los agentes nuevamente. Por lo tanto, el conjunto en tiempo de ejecución puede diferir de lo que se listó en la descripción anterior de la herramienta si los archivos de agente cambiaron durante la sesión.\n\n## Guardarraíles de salida estructurada y precedencia de esquema\n\nPrecedencia del esquema de salida en tiempo de ejecución en `TaskTool.execute`:\n\n1. `output` del frontmatter del agente\n2. `params.schema` de la llamada a la tarea\n3. `outputSchema` de la sesión padre\n\n(`effectiveOutputSchema = effectiveAgent.output ?? outputSchema ?? this.session.outputSchema`)\n\nEl texto de guardarraíl en el prompt en `src/prompts/tools/task.md` advierte sobre el comportamiento de desajuste para agentes de salida estructurada (`explore`, `reviewer`): las instrucciones de formato de salida en prosa pueden entrar en conflicto con el esquema integrado y producir salidas `null`.\n\nEsto es orientación, no lógica de validación en tiempo de ejecución dentro de `discoverAgents`.\n\n## Interacción con el descubrimiento de comandos\n\n`src/task/commands.ts` es infraestructura paralela para comandos de flujo de trabajo (no definiciones de agentes), pero sigue el mismo patrón general:\n\n- descubrir primero desde proveedores de capacidades\n- deduplicar por nombre con primera aparición gana\n- agregar comandos integrados si aún no se han visto\n- búsqueda por nombre exacto mediante `getCommand`\n\nEn `src/task/index.ts`, los helpers de comandos se re-exportan junto con los helpers de descubrimiento de agentes. El descubrimiento de agentes en sí no depende del descubrimiento de comandos en tiempo de ejecución.\n\n## Restricciones de disponibilidad más allá del descubrimiento\n\nUn agente puede ser descubrible pero aún no estar disponible para ejecutarse debido a guardarraíles de ejecución.\n\n### Política de generación del padre\n\n`TaskTool.execute` verifica `session.getSessionSpawns()`:\n\n- `\"*\"` => permitir cualquiera\n- `\"\"` => denegar todos\n- lista CSV => permitir solo los nombres listados\n\nSi se deniega: respuesta inmediata `Cannot spawn '...'. Allowed: ...`.\n\n### Guardia de entorno para auto-recursión bloqueada\n\n`PI_BLOCKED_AGENT` se lee en la construcción de la herramienta. Si la solicitud coincide, la ejecución se rechaza con un mensaje de prevención de recursión.\n\n### Control de profundidad de recursión (disponibilidad de herramienta task dentro de sesiones hijas)\n\nEn `runSubprocess` (`src/task/executor.ts`):\n\n- la profundidad se calcula a partir de `taskDepth`\n- `task.maxRecursionDepth` controla el límite\n- cuando se alcanza la profundidad máxima:\n  - la herramienta `task` se elimina de la lista de herramientas del hijo\n  - el `spawns` del entorno hijo se establece como vacío\n\nPor lo tanto, los niveles más profundos no pueden generar más tareas incluso si la definición del agente incluye `spawns`.\n\n## Advertencia sobre el modo plan (implementación actual)\n\n`TaskTool.execute` calcula un `effectiveAgent` para el modo plan (antepone el prompt del modo plan, fuerza un subconjunto de herramientas de solo lectura, limpia spawns), pero `runSubprocess` se llama con `agent` en lugar de `effectiveAgent`.\n\nEfecto actual:\n\n- la sobrescritura del modelo / nivel de pensamiento / esquema de salida se derivan de `effectiveAgent`\n- el prompt del sistema y las restricciones de herramientas/spawns de `effectiveAgent` no se pasan a través de esta ruta de llamada\n\nEsta es una advertencia de implementación que vale la pena conocer al leer las expectativas de comportamiento del modo plan.\n",
	"es/sessions/compaction.md": "---\ntitle: Compactación y Resúmenes de Rama\ndescription: >-\n  Compactación de la ventana de contexto y generación de resúmenes de rama para\n  sesiones de larga duración.\nsidebar:\n  order: 5\n  label: Compactación\ni18n:\n  sourceHash: dae425a900d8\n  translator: machine\n---\n\n# Compactación y Resúmenes de Rama\n\nLa compactación y los resúmenes de rama son los dos mecanismos que mantienen las sesiones largas utilizables sin perder el contexto de trabajo previo.\n\n- **Compactación** reescribe el historial antiguo en un resumen dentro de la rama actual.\n- **Resumen de rama** captura el contexto de ramas abandonadas durante la navegación con `/tree`.\n\nAmbos se persisten como entradas de sesión y se convierten de nuevo en mensajes de contexto de usuario al reconstruir la entrada del LLM.\n\n## Archivos de implementación clave\n\n- `src/session/compaction/compaction.ts`\n- `src/session/compaction/branch-summarization.ts`\n- `src/session/compaction/pruning.ts`\n- `src/session/compaction/utils.ts`\n- `src/session/session-manager.ts`\n- `src/session/agent-session.ts`\n- `src/session/messages.ts`\n- `src/extensibility/hooks/types.ts`\n- `src/config/settings-schema.ts`\n\n## Modelo de entradas de sesión\n\nLa compactación y los resúmenes de rama son entradas de sesión de primera clase, no mensajes simples de asistente/usuario.\n\n- `CompactionEntry`\n  - `type: \"compaction\"`\n  - `summary`, `shortSummary` opcional\n  - `firstKeptEntryId` (límite de compactación)\n  - `tokensBefore`\n  - `details`, `preserveData`, `fromExtension` opcionales\n- `BranchSummaryEntry`\n  - `type: \"branch_summary\"`\n  - `fromId`, `summary`\n  - `details`, `fromExtension` opcionales\n\nCuando se reconstruye el contexto (`buildSessionContext`):\n\n1. La última compactación en la ruta activa se convierte en un mensaje `compactionSummary`.\n2. Las entradas conservadas desde `firstKeptEntryId` hasta el punto de compactación se reincluyen.\n3. Las entradas posteriores en la ruta se agregan al final.\n4. Las entradas `branch_summary` se convierten en mensajes `branchSummary`.\n5. Las entradas `custom_message` se convierten en mensajes `custom`.\n\nEsos roles personalizados se transforman luego en mensajes de usuario orientados al LLM en `convertToLlm()` usando las plantillas estáticas:\n\n- `prompts/compaction/compaction-summary-context.md`\n- `prompts/compaction/branch-summary-context.md`\n\n## Pipeline de compactación\n\n### Disparadores\n\nLa compactación puede ejecutarse de tres formas:\n\n1. **Manual**: `/compact [instrucciones]` llama a `AgentSession.compact(...)`.\n2. **Recuperación automática por desbordamiento**: después de un error del asistente que coincide con desbordamiento de contexto.\n3. **Compactación automática por umbral**: después de un turno exitoso cuando el contexto excede el umbral.\n\n### Forma de la compactación (visual)\n\n```text\nAntes de la compactación:\n\n  entry:  0     1     2     3      4     5     6      7      8     9\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┘\n                └────────┬───────┘ └──────────────┬──────────────┘\n               messagesToSummarize            kept messages\n                                   ↑\n                          firstKeptEntryId (entry 4)\n\nDespués de la compactación (nueva entrada agregada):\n\n  entry:  0     1     2     3      4     5     6      7      8     9      10\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┬─────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │ cmp │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┴─────┘\n               └──────────┬──────┘ └──────────────────────┬───────────────────┘\n                 no se envía al LLM                  se envía al LLM\n                                                         ↑\n                                              comienza desde firstKeptEntryId\n\nLo que ve el LLM:\n\n  ┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐\n  │ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │\n  └────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘\n       ↑         ↑      └─────────────────┬────────────────┘\n    prompt   from cmp          mensajes desde firstKeptEntryId\n```\n\n### Compactación por desbordamiento-reintento vs por umbral\n\nLas dos rutas automáticas son intencionalmente diferentes:\n\n- **Compactación por desbordamiento-reintento**\n  - Disparador: se detecta que el error del asistente del modelo actual es un desbordamiento de contexto.\n  - El mensaje de error del asistente fallido se elimina del estado activo del agente antes del reintento.\n  - La compactación automática se ejecuta con `reason: \"overflow\"` y `willRetry: true`.\n  - Si tiene éxito, el agente continúa automáticamente (`agent.continue()`) después de la compactación.\n\n- **Compactación por umbral**\n  - Disparador: `contextTokens > contextWindow - compaction.reserveTokens`.\n  - Se ejecuta con `reason: \"threshold\"` y `willRetry: false`.\n  - Si tiene éxito, si `compaction.autoContinue !== false`, inyecta un prompt sintético:\n    - `\"Continue if you have next steps.\"`\n\n### Poda pre-compactación\n\nAntes de las verificaciones de compactación, puede ejecutarse la poda de resultados de herramientas (`pruneToolOutputs`).\n\nPolítica de poda predeterminada:\n\n- Proteger los `40_000` tokens más recientes de salida de herramientas.\n- Requerir al menos `20_000` tokens totales de ahorro estimado.\n- Nunca podar resultados de herramientas de `skill` o `read`.\n\nLos resultados de herramientas podados se reemplazan con:\n\n- `[Output truncated - N tokens]`\n\nSi la poda modifica entradas, el almacenamiento de sesión se reescribe y el estado de mensajes del agente se actualiza antes de las decisiones de compactación.\n\n### Lógica de límite y punto de corte\n\n`prepareCompaction()` solo considera entradas desde la última entrada de compactación (si existe).\n\n1. Encontrar el índice de compactación anterior.\n2. Calcular `boundaryStart = prevCompactionIndex + 1`.\n3. Adaptar `keepRecentTokens` usando la relación de uso medida cuando esté disponible.\n4. Ejecutar `findCutPoint()` sobre la ventana de límite.\n\nLos puntos de corte válidos incluyen:\n\n- Entradas de mensaje con roles: `user`, `assistant`, `bashExecution`, `hookMessage`, `branchSummary`, `compactionSummary`\n- Entradas `custom_message`\n- Entradas `branch_summary`\n\nRegla estricta: nunca cortar en `toolResult`.\n\nSi hay entradas de metadatos que no son mensajes inmediatamente antes del punto de corte (`model_change`, `thinking_level_change`, etiquetas, etc.), se incorporan a la región conservada moviendo el índice de corte hacia atrás hasta que se encuentre un mensaje o un límite de compactación.\n\n### Manejo de turnos divididos\n\nSi el punto de corte no está al inicio de un turno de usuario, la compactación lo trata como un turno dividido.\n\nLa detección de inicio de turno trata estos como límites de turno de usuario:\n\n- `message.role === \"user\"`\n- `message.role === \"bashExecution\"`\n- Entrada `custom_message`\n- Entrada `branch_summary`\n\nLa compactación de turno dividido genera dos resúmenes:\n\n1. Resumen del historial (`messagesToSummarize`)\n2. Resumen del prefijo del turno (`turnPrefixMessages`)\n\nEl resumen final almacenado se fusiona como:\n\n```markdown\n<history summary>\n\n---\n\n**Turn Context (split turn):**\n\n<turn prefix summary>\n```\n\n### Generación de resúmenes\n\n`compact(...)` construye resúmenes a partir de texto de conversación serializado:\n\n1. Convertir mensajes via `convertToLlm()`.\n2. Serializar con `serializeConversation()`.\n3. Envolver en `<conversation>...</conversation>`.\n4. Opcionalmente incluir `<previous-summary>...</previous-summary>`.\n5. Opcionalmente inyectar contexto de hook como lista `<additional-context>`.\n6. Ejecutar el prompt de resumen con `SUMMARIZATION_SYSTEM_PROMPT`.\n\nSelección de prompt:\n\n- primera compactación: `compaction-summary.md`\n- compactación iterativa con resumen previo: `compaction-update-summary.md`\n- segunda pasada de turno dividido: `compaction-turn-prefix.md`\n- resumen corto para UI: `compaction-short-summary.md`\n\nModo de resumen remoto:\n\n- Si `compaction.remoteEndpoint` está configurado, la compactación envía POST:\n  - `{ systemPrompt, prompt }`\n- Espera JSON que contenga al menos `{ summary }`.\n\n### Contexto de operaciones de archivo en resúmenes\n\nLa compactación rastrea la actividad acumulativa de archivos usando llamadas a herramientas del asistente:\n\n- `read(path)` → conjunto de lectura\n- `write(path)` → conjunto de modificados\n- `edit(path)` → conjunto de modificados\n\nComportamiento acumulativo:\n\n- Incluye detalles de compactación anterior solo cuando la entrada previa es generada por pi (`fromExtension !== true`).\n- En turnos divididos, incluye también las operaciones de archivo del prefijo del turno.\n- `readFiles` excluye archivos que también fueron modificados.\n\nEl texto del resumen recibe etiquetas de archivo agregadas via plantilla de prompt:\n\n```xml\n<read-files>\n...\n</read-files>\n<modified-files>\n...\n</modified-files>\n```\n\n### Persistir y recargar\n\nDespués de la generación del resumen (o resumen proporcionado por hook), la sesión del agente:\n\n1. Agrega `CompactionEntry` con `appendCompaction(...)`.\n2. Reconstruye el contexto via `buildSessionContext()`.\n3. Reemplaza los mensajes activos del agente con el contexto reconstruido.\n4. Emite el evento de hook `session_compact`.\n\n## Pipeline de resumen de rama\n\nEl resumen de rama está vinculado a la navegación del árbol, no al desbordamiento de tokens.\n\n### Disparador\n\nDurante `navigateTree(...)`:\n\n1. Calcular las entradas abandonadas desde la hoja antigua hasta el ancestro común usando `collectEntriesForBranchSummary(...)`.\n2. Si el llamador solicitó resumen (`options.summarize`), generar el resumen antes de cambiar de hoja.\n3. Si existe resumen, adjuntarlo en el destino de navegación usando `branchWithSummary(...)`.\n\nOperacionalmente esto es comúnmente impulsado por el flujo `/tree` cuando `branchSummary.enabled` está habilitado.\n\n### Forma del cambio de rama (visual)\n\n```text\nÁrbol antes de la navegación:\n\n         ┌─ B ─ C ─ D (hoja antigua, siendo abandonada)\n    A ───┤\n         └─ E ─ F (destino)\n\nAncestro común: A\nEntradas a resumir: B, C, D\n\nDespués de la navegación con resumen:\n\n         ┌─ B ─ C ─ D ─ [resumen de B,C,D]\n    A ───┤\n         └─ E ─ F (nueva hoja)\n```\n\n### Preparación y presupuesto de tokens\n\n`generateBranchSummary(...)` calcula el presupuesto como:\n\n- `tokenBudget = model.contextWindow - branchSummary.reserveTokens`\n\n`prepareBranchEntries(...)` luego:\n\n1. Primera pasada: recopilar operaciones de archivo acumulativas de todas las entradas resumidas, incluyendo detalles previos de `branch_summary` generados por pi.\n2. Segunda pasada: recorrer desde la más reciente → más antigua, agregando mensajes hasta alcanzar el presupuesto de tokens.\n3. Preferir preservar el contexto reciente.\n4. Puede aún incluir entradas de resumen grandes cerca del límite del presupuesto para continuidad.\n\nLas entradas de compactación se incluyen como mensajes (`compactionSummary`) durante la entrada del resumen de rama.\n\n### Generación y persistencia del resumen\n\nResumen de rama:\n\n1. Convierte y serializa los mensajes seleccionados.\n2. Envuelve en `<conversation>`.\n3. Usa instrucciones personalizadas si se proporcionan, de lo contrario `branch-summary.md`.\n4. Llama al modelo de resumen con `SUMMARIZATION_SYSTEM_PROMPT`.\n5. Antepone `branch-summary-preamble.md`.\n6. Agrega etiquetas de operaciones de archivo.\n\nEl resultado se almacena como `BranchSummaryEntry` con detalles opcionales (`readFiles`, `modifiedFiles`).\n\n## Puntos de contacto de extensión y hooks\n\n### `session_before_compact`\n\nHook pre-compactación.\n\nPuede:\n\n- cancelar la compactación (`{ cancel: true }`)\n- proporcionar un payload de compactación personalizado completo (`{ compaction: CompactionResult }`)\n\n### `session.compacting`\n\nHook de personalización de prompt/contexto para la compactación predeterminada.\n\nPuede devolver:\n\n- `prompt` (sobreescribir el prompt base de resumen)\n- `context` (líneas de contexto extra inyectadas en `<additional-context>`)\n- `preserveData` (almacenado en la entrada de compactación)\n\n### `session_compact`\n\nNotificación post-compactación con `compactionEntry` guardado y flag `fromExtension`.\n\n### `session_before_tree`\n\nSe ejecuta en la navegación del árbol antes de la generación predeterminada del resumen de rama.\n\nPuede:\n\n- cancelar la navegación\n- proporcionar `{ summary: { summary, details } }` personalizado usado cuando el usuario solicitó resumen\n\n### `session_tree`\n\nEvento post-navegación que expone la hoja nueva/antigua y la entrada de resumen opcional.\n\n## Comportamiento en tiempo de ejecución y semántica de fallos\n\n- La compactación manual aborta primero la operación actual del agente.\n- `abortCompaction()` cancela tanto los controladores de compactación manual como automática.\n- La compactación automática emite eventos de sesión de inicio/fin para actualizaciones de UI/estado.\n- La compactación automática puede intentar múltiples modelos candidatos y reintentar fallos transitorios.\n- Los errores de desbordamiento se excluyen de la ruta de reintento genérica porque son manejados por la compactación.\n- Si la compactación automática falla:\n  - la ruta de desbordamiento emite `Context overflow recovery failed: ...`\n  - la ruta de umbral emite `Auto-compaction failed: ...`\n- El resumen de rama puede cancelarse via señal de aborto (por ejemplo, Escape), devolviendo un resultado de navegación cancelado/abortado.\n\n## Configuración y valores predeterminados\n\nDesde `settings-schema.ts`:\n\n- `compaction.enabled` = `true`\n- `compaction.reserveTokens` = `16384`\n- `compaction.keepRecentTokens` = `20000`\n- `compaction.autoContinue` = `true`\n- `compaction.remoteEndpoint` = `undefined`\n- `branchSummary.enabled` = `false`\n- `branchSummary.reserveTokens` = `16384`\n\nEstos valores son consumidos en tiempo de ejecución por `AgentSession` y los módulos de compactación/resumen de rama.\n",
	"es/sessions/handoff-generation-pipeline.md": "---\ntitle: Pipeline de generación de handoff\ndescription: >-\n  Pipeline de generación de handoff para crear resúmenes de sesión portátiles\n  para la colaboración en equipo.\nsidebar:\n  order: 8\n  label: Pipeline de handoff\ni18n:\n  sourceHash: 03666084b5ac\n  translator: machine\n---\n\n# Pipeline de generación de `/handoff`\n\nEste documento describe cómo el agente de codificación implementa `/handoff` actualmente: ruta de activación, prompt de generación, captura de finalización, cambio de sesión y reinyección de contexto.\n\n## Alcance\n\nCubre:\n\n- Despacho interactivo del comando `/handoff`\n- Ciclo de vida y transiciones de estado de `AgentSession.handoff()`\n- Cómo se captura la salida del handoff desde la salida del asistente\n- Cómo las sesiones antiguas/nuevas persisten los datos de handoff de forma diferente\n- Comportamiento de la interfaz de usuario para éxito, cancelación y fallo\n\nNo cubre:\n\n- Navegación genérica de árbol e internos de ramificación\n- Comandos de sesión que no son de handoff (`/new`, `/fork`, `/resume`)\n\n## Archivos de implementación\n\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n\n## Ruta de activación\n\n1. `/handoff` se declara en los metadatos de comandos slash integrados (`slash-commands.ts`) con una sugerencia en línea opcional: `[focus instructions]`.\n2. En el manejo interactivo de entrada (`InputController`), el texto enviado que coincide con `/handoff` o `/handoff ...` es interceptado antes del envío normal del prompt.\n3. El editor se limpia y se llama a `handleHandoffCommand(customInstructions?)`.\n4. `CommandController.handleHandoffCommand` realiza una verificación previa usando las entradas actuales:\n   - Cuenta las entradas con `type === \"message\"`.\n   - Si `< 2`, advierte: `Nothing to hand off (no messages yet)` y retorna.\n\nLa misma guarda de contenido mínimo existe nuevamente dentro de `AgentSession.handoff()` y lanza un error si se viola. Esto duplica la seguridad tanto en la capa de interfaz de usuario como en la de sesión.\n\n## Ciclo de vida completo\n\n### 1) Iniciar la generación del handoff\n\n`AgentSession.handoff(customInstructions?)`:\n\n- Lee las entradas de la rama actual (`sessionManager.getBranch()`)\n- Valida el conteo mínimo de mensajes (`>= 2`)\n- Crea `#handoffAbortController`\n- Construye un prompt fijo en línea que solicita un documento de handoff estructurado (`Goal`, `Constraints & Preferences`, `Progress`, `Key Decisions`, `Critical Context`, `Next Steps`)\n- Agrega `Additional focus: ...` si se proporcionan instrucciones personalizadas\n\nEl prompt se envía mediante:\n\n```ts\nawait this.prompt(handoffPrompt, { expandPromptTemplates: false });\n```\n\n`expandPromptTemplates: false` evita la expansión de slash/plantillas de prompt de esta carga de instrucciones interna.\n\n### 2) Captura de la finalización\n\nAntes de enviar el prompt, `handoff()` se suscribe a los eventos de sesión y espera `agent_end`.\n\nAl recibir `agent_end`, extrae el texto del handoff del estado del agente buscando hacia atrás el mensaje `assistant` más reciente, luego concatena todos los bloques `content` donde `type === \"text\"` con `\\n`.\n\nSupuestos importantes de extracción:\n\n- Solo se usan bloques de texto; el contenido que no sea texto se ignora.\n- Se asume que el último mensaje del asistente corresponde a la generación del handoff.\n- No analiza secciones markdown ni valida el cumplimiento del formato.\n- Si la salida del asistente no tiene bloques de texto, el handoff se trata como ausente.\n\n### 3) Verificaciones de cancelación\n\n`handoff()` retorna `undefined` cuando se cumple alguna de las siguientes condiciones:\n\n- no hay texto de handoff capturado, o\n- `#handoffAbortController.signal.aborted` es verdadero\n\nSiempre limpia `#handoffAbortController` en `finally`.\n\n### 4) Creación de nueva sesión\n\nSi se capturó texto y no fue abortado:\n\n1. Vaciar el escritor de la sesión actual (`sessionManager.flush()`)\n2. Iniciar una sesión completamente nueva (`sessionManager.newSession()`)\n3. Reiniciar el estado del agente en memoria (`agent.reset()`)\n4. Reasignar `agent.sessionId` al id de la nueva sesión\n5. Limpiar los arreglos de contexto en cola (`#steeringMessages`, `#followUpMessages`, `#pendingNextTurnMessages`)\n6. Reiniciar el contador de recordatorio de tareas pendientes\n\n`newSession()` crea un encabezado nuevo y una lista de entradas vacía (la hoja se reinicia a `null`). En la ruta de handoff, no se pasa ningún `parentSession`.\n\n### 5) Inyección del contexto de handoff\n\nEl documento de handoff generado se envuelve y se agrega a la nueva sesión como una entrada `custom_message`:\n\n```text\n<handoff-context>\n...handoff text...\n</handoff-context>\n\nThe above is a handoff document from a previous session. Use this context to continue the work seamlessly.\n```\n\nLlamada de inserción:\n\n```ts\nthis.sessionManager.appendCustomMessageEntry(\"handoff\", handoffContent, true);\n```\n\nSemántica:\n\n- `customType`: `\"handoff\"`\n- `display`: `true` (visible en la reconstrucción de la TUI)\n- Tipo de entrada: `custom_message` (participa en el contexto del LLM)\n\n### 6) Reconstrucción del contexto activo del agente\n\nDespués de la inyección:\n\n1. `sessionManager.buildSessionContext()` resuelve la lista de mensajes para la hoja actual\n2. `agent.replaceMessages(sessionContext.messages)` hace que el mensaje de handoff inyectado sea el contexto activo\n3. El método retorna `{ document: handoffText }`\n\nEn este punto, el contexto activo del LLM en la nueva sesión contiene el mensaje de handoff inyectado, no la transcripción anterior.\n\n## Modelo de persistencia: sesión antigua vs sesión nueva\n\n### Sesión antigua\n\nDurante la generación, la persistencia normal de mensajes permanece activa. La respuesta de handoff del asistente se persiste como una entrada regular `message` al ocurrir `message_end`.\n\nResultado: la sesión original contiene el handoff generado y visible como parte de la transcripción histórica.\n\n### Sesión nueva\n\nDespués del reinicio de sesión, el handoff se persiste como `custom_message` con `customType: \"handoff\"`.\n\n`buildSessionContext()` convierte esta entrada en un mensaje de contexto personalizado/de usuario en tiempo de ejecución mediante `createCustomMessage(...)`, de modo que se incluye en los futuros prompts de la nueva sesión.\n\n## Comportamiento del controlador/interfaz de usuario\n\nComportamiento de `CommandController.handleHandoffCommand`:\n\n- Llama a `await session.handoff(customInstructions)`\n- Si el resultado es `undefined`: `showError(\"Handoff cancelled\")`\n- En caso de éxito:\n  - `rebuildChatFromMessages()` (carga el nuevo contexto de sesión, incluido el handoff inyectado)\n  - invalida la línea de estado y el borde superior del editor\n  - recarga las tareas pendientes\n  - agrega una línea de chat de éxito: `New session started with handoff context`\n- En caso de excepción:\n  - si el mensaje es `\"Handoff cancelled\"` o el nombre del error es `AbortError`: `showError(\"Handoff cancelled\")`\n  - de lo contrario: `showError(\"Handoff failed: <message>\")`\n- Solicita renderizado al final\n\n## Semántica de cancelación (comportamiento actual)\n\n### Primitiva de cancelación a nivel de sesión\n\n`AgentSession` expone:\n\n- `abortHandoff()` → aborta `#handoffAbortController`\n- `isGeneratingHandoff` → verdadero mientras existe el controlador\n\nCuando se usa esta ruta de aborto, el suscriptor del handoff rechaza con `Error(\"Handoff cancelled\")`, y el controlador de comandos lo mapea a la interfaz de usuario de cancelación.\n\n### Limitación de la ruta interactiva de `/handoff`\n\nEn el cableado actual del controlador interactivo, `/handoff` no instala un manejador dedicado de Escape que llame a `abortHandoff()` (a diferencia de las rutas de compactación/resumen de rama que anulan temporalmente `editor.onEscape`).\n\nImpacto práctico:\n\n- Existe soporte de cancelación a nivel de sesión, pero no hay un enlace de tecla específico para handoff en la ruta del comando `/handoff`.\n- La interrupción del usuario aún puede ocurrir mediante rutas de aborto de agente más amplias, pero eso no es el mismo canal de cancelación explícito usado por `abortHandoff()`.\n\n## Handoff abortado vs handoff fallido\n\nClasificación actual de la interfaz de usuario:\n\n- **Abortado/cancelado**\n  - La ruta `abortHandoff()` activa `\"Handoff cancelled\"`, o\n  - se lanza `AbortError`\n  - La interfaz muestra `Handoff cancelled`\n\n- **Fallido**\n  - cualquier otro error lanzado desde `handoff()` / la canalización de prompts (errores de validación de modelo/API, excepciones en tiempo de ejecución, etc.)\n  - La interfaz muestra `Handoff failed: ...`\n\nMatiz adicional: si la generación se completa pero no se extrae texto, `handoff()` retorna `undefined` y el controlador actualmente reporta **cancelado**, no **fallido**.\n\n## Salvaguardas de sesión corta y contenido mínimo\n\nDos guardas evitan handoffs con poca información:\n\n- Capa de interfaz de usuario (`handleHandoffCommand`): advierte y retorna anticipadamente para `< 2` entradas de mensajes\n- Capa de sesión (`handoff()`): lanza la misma condición como error\n\nEsto evita crear una nueva sesión con un contexto de handoff vacío o casi vacío.\n\n## Resumen de transiciones de estado\n\nFlujo de estado de alto nivel:\n\n1. Comando slash interactivo interceptado\n2. Guarda de conteo de mensajes previa\n3. `#handoffAbortController` creado (`isGeneratingHandoff = true`)\n4. Prompt de handoff interno enviado (visible en el chat como generación normal del asistente)\n5. Al recibir `agent_end`, se extrae el último texto del asistente\n6. Si falta/fue abortado → retornar `undefined` o ruta de error de cancelación\n7. Si está presente:\n   - vaciar la sesión antigua\n   - crear una nueva sesión vacía\n   - reiniciar colas/contadores en tiempo de ejecución\n   - agregar `custom_message(handoff)`\n   - reconstruir y reemplazar los mensajes activos del agente\n8. El controlador reconstruye la interfaz de chat y anuncia el éxito\n9. `#handoffAbortController` limpiado (`isGeneratingHandoff = false`)\n\n## Supuestos y limitaciones conocidos\n\n- La extracción del handoff es heurística: \"últimos bloques de texto del asistente\"; sin validación estructural.\n- No hay verificación estricta de que el markdown generado siga el formato de sección solicitado.\n- El texto extraído faltante se reporta como cancelación en la experiencia de usuario del controlador.\n- El flujo interactivo de `/handoff` actualmente carece de un enlace dedicado Escape→`abortHandoff()`.\n- Los metadatos de linaje de la nueva sesión (`parentSession`) no se establecen mediante esta ruta.\n",
	"es/sessions/memory.md": "---\ntitle: Memoria Autónoma\ndescription: >-\n  Sistema de memoria autónoma para persistir preferencias del usuario, contexto\n  del proyecto y retroalimentación entre sesiones.\nsidebar:\n  order: 7\n  label: Memoria autónoma\ni18n:\n  sourceHash: 2aa9f516aa1e\n  translator: machine\n---\n\n# Memoria Autónoma\n\nCuando está habilitada, el agente extrae automáticamente conocimiento duradero de sesiones anteriores e inyecta un resumen compacto en cada nueva sesión. Con el tiempo construye un almacén de memoria con alcance de proyecto — decisiones técnicas, flujos de trabajo recurrentes, problemas conocidos — que se transmite hacia adelante sin esfuerzo manual.\n\nDeshabilitada por defecto. Habilítela mediante `/settings` o `config.yml`:\n\n```yaml\nmemories:\n  enabled: true\n```\n\n## Uso\n\n### Qué se inyecta\n\nAl inicio de la sesión, si existe un resumen de memoria para el proyecto actual, se inyecta en el prompt del sistema como un bloque de **Memory Guidance**. El agente recibe instrucciones de:\n\n- Tratar la memoria como contexto heurístico — útil para procesos y decisiones previas, no autoritativo sobre el estado actual del repositorio.\n- Citar la ruta del artefacto de memoria cuando la memoria cambie el plan, y combinarlo con evidencia del repositorio actual antes de actuar.\n- Preferir el estado del repositorio y las instrucciones del usuario cuando entren en conflicto con la memoria; tratar la memoria en conflicto como obsoleta.\n\n### Lectura de artefactos de memoria\n\nEl agente puede leer archivos de memoria directamente usando URLs `memory://` con la herramienta `read`:\n\n| URL | Contenido |\n|---|---|\n| `memory://root` | Resumen compacto inyectado al inicio |\n| `memory://root/MEMORY.md` | Documento completo de memoria a largo plazo |\n| `memory://root/skills/<name>/SKILL.md` | Un manual de procedimientos de habilidad generado |\n\n### Comando slash `/memory`\n\n| Subcomando | Efecto |\n|---|---|\n| `view` | Mostrar el contenido actual de inyección de memoria |\n| `clear` / `reset` | Eliminar todos los datos de memoria y artefactos generados |\n| `enqueue` / `rebuild` | Forzar la consolidación para que se ejecute en el próximo inicio |\n\n## Cómo funciona\n\nLas memorias se construyen mediante un pipeline en segundo plano que se ejecuta al inicio o se activa manualmente mediante un comando slash.\n\n**Fase 1 — extracción por sesión:** Para cada sesión pasada que ha cambiado desde que fue procesada por última vez, un modelo lee el historial de la sesión y extrae señal duradera: decisiones técnicas, restricciones, fallos resueltos, flujos de trabajo recurrentes. Las sesiones demasiado recientes, demasiado antiguas o actualmente activas se omiten. Cada extracción produce un bloque de memoria sin procesar y una sinopsis breve para esa sesión.\n\n**Fase 2 — consolidación:** Después de la extracción, una segunda pasada del modelo lee todas las extracciones por sesión y produce tres salidas escritas en disco:\n\n- `MEMORY.md` — un documento de memoria a largo plazo curado\n- `memory_summary.md` — el texto compacto inyectado al inicio de la sesión\n- `skills/` — manuales de procedimientos reutilizables, cada uno en su propio subdirectorio\n\nLa Fase 2 utiliza un lease para prevenir la doble ejecución cuando múltiples procesos inician simultáneamente. Los directorios de habilidades obsoletos de ejecuciones anteriores se eliminan automáticamente.\n\nToda la salida se escanea en busca de secretos antes de escribirse en disco.\n\n### Comportamiento de extracción\n\nEl comportamiento de extracción y consolidación de memoria está controlado completamente por archivos de prompt estáticos en `src/prompts/memories/`.\n\n| Archivo | Propósito | Variables |\n|---|---|---|\n| `stage_one_system.md` | Prompt del sistema para la extracción por sesión | — |\n| `stage_one_input.md` | Plantilla de turno de usuario que envuelve el contenido de la sesión | `{{thread_id}}`, `{{response_items_json}}` |\n| `consolidation.md` | Prompt para la consolidación entre sesiones | `{{raw_memories}}`, `{{rollout_summaries}}` |\n| `read_path.md` | Guía de memoria inyectada en sesiones activas | `{{memory_summary}}` |\n\n### Selección de modelo\n\nLa memoria se apoya en el sistema de roles de modelo.\n\n| Fase | Rol | Propósito |\n|---|---|---|\n| Fase 1 (extracción) | `default` | Extracción de conocimiento por sesión |\n| Fase 2 (consolidación) | `smol` | Síntesis entre sesiones |\n\nSi `smol` no está configurado, la Fase 2 recurre al rol `default`.\n\n## Configuración\n\n| Ajuste | Predeterminado | Descripción |\n|---|---|---|\n| `memories.enabled` | `false` | Interruptor principal |\n| `memories.maxRolloutAgeDays` | `30` | Las sesiones más antiguas que esto no se procesan |\n| `memories.minRolloutIdleHours` | `12` | Las sesiones activas más recientemente que esto se omiten |\n| `memories.maxRolloutsPerStartup` | `64` | Límite de sesiones procesadas en un solo inicio |\n| `memories.summaryInjectionTokenLimit` | `5000` | Máximo de tokens del resumen inyectado en el prompt del sistema |\n\nControles de ajuste adicionales (concurrencia, duraciones de lease, presupuestos de tokens) están disponibles en la configuración para uso avanzado.\n\n## Archivos clave\n\n- `src/memories/index.ts` — orquestación del pipeline, inyección, manejo de comandos slash\n- `src/memories/storage.ts` — cola de trabajos respaldada por SQLite y registro de hilos\n- `src/prompts/memories/` — plantillas de prompts de memoria\n- `src/internal-urls/memory-protocol.ts` — manejador de URLs `memory://`\n",
	"es/sessions/non-compaction-retry-policy.md": "---\ntitle: Política de reintento automático sin compactación\ndescription: >-\n  Política de reintento automático para fallos de API transitorios fuera de la\n  ruta de compactación.\nsidebar:\n  order: 6\n  label: Política de reintento\ni18n:\n  sourceHash: 8999a0258dd8\n  translator: machine\n---\n\n# Política de reintento automático sin compactación\n\nEste documento describe la ruta estándar de reintento por error de API en `AgentSession`.\n\nExcluye explícitamente la recuperación por desbordamiento de contexto mediante auto-compactación. El desbordamiento es gestionado por la lógica de compactación y está documentado por separado en [`compaction.md`](./compaction.md).\n\n## Archivos de implementación\n\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/config/settings-schema.ts`](../../packages/coding-agent/src/config/settings-schema.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts)\n- [`../src/modes/rpc/rpc-client.ts`](../../packages/coding-agent/src/modes/rpc/rpc-client.ts)\n- [`../src/modes/rpc/rpc-types.ts`](../../packages/coding-agent/src/modes/rpc/rpc-types.ts)\n\n## Límite de alcance frente a compactación\n\nEl reintento y la compactación se verifican desde la misma ruta `agent_end`, pero están intencionalmente separados:\n\n1. `agent_end` inspecciona el último mensaje del asistente.\n2. `#isRetryableError(...)` se ejecuta primero.\n3. Si se inicia un reintento, las comprobaciones de compactación se omiten para ese turno.\n4. Los errores de desbordamiento de contexto están excluidos de forma estricta de la clasificación de reintento (`isContextOverflow(...)` cortocircuita el reintento).\n5. El desbordamiento cae, por tanto, hacia `#checkCompaction(...)` en lugar del reintento estándar.\n\nEn resumen: los fallos de tipo sobrecarga/límite de tasa/servidor/red utilizan esta política de reintento; el desbordamiento de la ventana de contexto utiliza la recuperación por compactación.\n\n## Clasificación de reintentos\n\n`#isRetryableError(...)` requiere que se cumplan todas las siguientes condiciones:\n\n- `stopReason === \"error\"` del asistente\n- `errorMessage` existe\n- el mensaje **no** es un desbordamiento de contexto\n- `errorMessage` coincide con `#isRetryableErrorMessage(...)`\n\nConjunto de patrones reintentables actuales (basados en expresiones regulares):\n\n- overloaded\n- rate limit / usage limit / too many requests\n- clases de servidor similares a HTTP: 429, 500, 502, 503, 504\n- service unavailable / server error / internal error\n- connection error / fetch failed\n- expresión `retry delay`\n\nEsta es una clasificación por patrones de cadena de texto, no mediante códigos de error tipificados del proveedor.\n\n## Ciclo de vida del reintento y transiciones de estado\n\nEstado de sesión utilizado por el reintento:\n\n- `#retryAttempt: number` (`0` significa inactivo)\n- `#retryPromise: Promise<void> | undefined` (rastrea el ciclo de vida del reintento en curso)\n- `#retryResolve: (() => void) | undefined` (resuelve `#retryPromise`)\n- `#retryAbortController: AbortController | undefined` (cancela el reposo de retroceso exponencial)\n\nFlujo (`#handleRetryableError`):\n\n1. Leer el grupo de configuración `retry`.\n2. Si `retry.enabled === false`, detener inmediatamente (`false`, no se inicia ningún reintento).\n3. Incrementar `#retryAttempt`.\n4. Crear `#retryPromise` una sola vez (primer intento en una cadena).\n5. Si el intento supera `retry.maxRetries`, emitir el evento de fallo final y detenerse.\n6. Calcular el retardo: `retry.baseDelayMs * 2^(attempt-1)`.\n7. Para errores de límite de uso, analizar las sugerencias de reintento y llamar al almacenamiento de autenticación (`markUsageLimitReached(...)`); si el cambio de proveedor/modelo tiene éxito, forzar el retardo a `0`.\n8. Emitir `auto_retry_start`.\n9. Eliminar el mensaje de error del asistente al final del estado de ejecución del agente (se conserva en el historial de sesión persistido).\n10. Esperar con soporte de cancelación.\n11. Al despertar, programar `agent.continue()` mediante `setTimeout(..., 0)`.\n\n### Qué restablece los contadores de reintento\n\n`#retryAttempt` se restablece a `0` en estos casos:\n\n- primer mensaje del asistente exitoso, sin error y sin cancelación, después de que comenzaron los reintentos (emite `auto_retry_end { success: true }`)\n- cancelación del reintento durante el reposo de retroceso exponencial\n- ruta de máximo de reintentos superado\n\n`#retryPromise` se resuelve y borra cuando la cadena de reintentos termina (éxito, cancelación o máximo superado), mediante `#resolveRetry()`.\n\n## Semántica del retroceso exponencial y del número máximo de intentos\n\nConfiguración:\n\n- `retry.enabled` (predeterminado `true`)\n- `retry.maxRetries` (predeterminado `3`)\n- `retry.baseDelayMs` (predeterminado `2000`)\n\nNumeración de intentos:\n\n- el contador de intentos se incrementa antes de la comprobación del máximo\n- los eventos de inicio utilizan el intento actual (base 1)\n- el evento de fin por máximo superado reporta `attempt: this.#retryAttempt - 1` (último recuento de reintentos intentados)\n\nSecuencia de retroceso exponencial con la configuración predeterminada:\n\n- intento 1: 2000 ms\n- intento 2: 4000 ms\n- intento 3: 8000 ms\n\nLas entradas de anulación de retardo solo se utilizan en la ruta de gestión del límite de uso, y únicamente para influir en la decisión de cambio de modelo/cuenta en el almacenamiento de autenticación. En la ruta principal de reintento sin compactación, el retroceso permanece como retardo exponencial local a menos que el cambio tenga éxito (`delayMs = 0`).\n\n## Mecánica de cancelación\n\n### Cancelación explícita de reintento\n\n`abortRetry()`:\n\n- cancela `#retryAbortController` (si está presente)\n- resuelve la promesa de reintento (`#resolveRetry()`) para desbloquear a los que estén esperando\n\nSi la cancelación ocurre durante el reposo, la ruta de captura emite:\n\n- `auto_retry_end { success: false, finalError: \"Retry cancelled\" }`\n- restablece el intento/controlador\n\n### Interacción con la cancelación global de operación\n\n`abort()` llama a `abortRetry()` antes de cancelar el flujo del agente activo. Esto garantiza que el retroceso del reintento se cancela cuando el usuario emite una cancelación general.\n\n### Interacción con la interfaz TUI\n\nAl recibir `auto_retry_start`, EventController:\n\n- intercambia el manejador de `Esc` por `session.abortRetry()`\n- renderiza el texto del indicador de carga: `Retrying (attempt/maxAttempts) in Ns… (esc to cancel)`\n\nAl recibir `auto_retry_end`, restaura el manejador de `Esc` anterior y limpia el estado del indicador de carga.\n\n## Comportamiento del flujo de datos y de la finalización del prompt\n\n`prompt()` finalmente espera en `#waitForRetry()` después de que `agent.prompt(...)` retorna.\n\nEfecto:\n\n- una llamada a prompt no se resuelve completamente hasta que cualquier cadena de reintentos iniciada finalice (éxito/fallo/cancelación)\n- el ciclo de vida del reintento forma parte de un límite lógico de ejecución de prompt\n\nEsto evita que los llamadores traten un turno en proceso de reintento como completado prematuramente.\n\n## Controles: configuración y RPC\n\n### Parámetros de configuración\n\nDefinidos en el esquema de configuración bajo el grupo retry:\n\n- `retry.enabled`\n- `retry.maxRetries`\n- `retry.baseDelayMs`\n\nControles programáticos en la sesión:\n\n- `setAutoRetryEnabled(enabled)` escribe `retry.enabled`\n- `autoRetryEnabled` lee `retry.enabled`\n- `isRetrying` informa si la promesa del ciclo de vida de reintento está activa\n\n### Controles RPC\n\nSuperficie de comandos RPC:\n\n- `set_auto_retry` → `session.setAutoRetryEnabled(command.enabled)`\n- `abort_retry` → `session.abortRetry()`\n\nAsistentes del cliente:\n\n- `RpcClient.setAutoRetry(enabled)`\n- `RpcClient.abortRetry()`\n\nAmbos comandos devuelven respuestas de éxito; los detalles del progreso/fallo del reintento provienen de los eventos de sesión en flujo, no de las cargas de respuesta del comando.\n\n## Emisión de eventos y presentación de fallos\n\nEventos de reintento a nivel de sesión:\n\n- `auto_retry_start { attempt, maxAttempts, delayMs, errorMessage }`\n- `auto_retry_end { success, attempt, finalError? }`\n\nPropagación:\n\n- emitidos a través de `AgentSession.subscribe(...)`\n- reenviados al ejecutor de extensiones como eventos de extensión\n- en modo RPC, reenviados directamente como objetos de evento JSON (`session.subscribe(event => output(event))`)\n- en la interfaz TUI, consumidos por `EventController` para la interfaz de carga/error\n\nPresentación del fallo final:\n\n- cuando se supera el máximo o se cancela, `auto_retry_end.success === false`\n- la interfaz TUI muestra: `Retry failed after N attempts: <finalError>`\n- las extensiones/hooks reciben `auto_retry_end` con los mismos campos\n- los consumidores RPC reciben el mismo objeto de evento en el flujo de salida estándar\n\n## Condiciones de detención permanente\n\nEl reintento se detiene y no continuará automáticamente cuando ocurra alguna de estas situaciones:\n\n- `retry.enabled` es false\n- el error no está clasificado como reintentable\n- el error es un desbordamiento de contexto (delegado a la ruta de compactación)\n- se supera el número máximo de reintentos\n- el usuario cancela el reintento (`abort_retry` o `Esc` durante el indicador de carga de reintento)\n- la cancelación global (`abort`) cancela el reintento primero\n\nUna nueva cadena de reintentos puede iniciarse más adelante ante un error reintentable futuro, una vez que los contadores se restablezcan.\n\n## Advertencias operativas\n\n- La clasificación es por coincidencia de texto mediante expresiones regulares; los errores estructurados específicos del proveedor no se utilizan aquí.\n- El reintento elimina el error del asistente fallido del **contexto de ejecución** antes de continuar, pero el historial de sesión conserva esa entrada de error.\n- `RpcSessionState` actualmente expone `autoCompactionEnabled` pero no un campo `autoRetryEnabled`; los llamadores RPC deben rastrear su propio estado de activación o consultar la configuración a través de otras APIs.\n",
	"es/sessions/session-operations-export-share-fork-resume.md": "---\ntitle: 'Operaciones de sesión: Exportar, Dump, Compartir, Fork, Reanudar'\ndescription: >-\n  Operaciones de sesión para exportar, compartir, bifurcar y reanudar\n  conversaciones.\nsidebar:\n  order: 3\n  label: Operaciones\ni18n:\n  sourceHash: e3c210b29c3e\n  translator: machine\n---\n\n# Operaciones de sesión: export, dump, share, fork, resume/continue\n\nEste documento describe el comportamiento visible para el operador de las operaciones de sesión export/share/fork/resume tal como están implementadas actualmente.\n\n## Archivos de implementación\n\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/export/html/index.ts`](../../packages/coding-agent/src/export/html/index.ts)\n- [`../src/export/custom-share.ts`](../../packages/coding-agent/src/export/custom-share.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n\n## Matriz de operaciones\n\n| Operación | Ruta de entrada | Mutación de sesión | Creación/cambio de archivo de sesión | Artefacto de salida |\n|---|---|---|---|---|\n| `/dump` | Comando slash interactivo | No | No | Texto en portapapeles |\n| `/export [path]` | Comando slash interactivo | No | No | Archivo HTML |\n| `--export <session.jsonl> [outputPath]` | Ruta rápida de inicio CLI | Sin mutación de sesión en tiempo de ejecución | Sin sesión activa; lee el archivo objetivo | Archivo HTML |\n| `/share` | Comando slash interactivo | No | No | HTML temporal + URL de compartir/gist |\n| `/fork` | Comando slash interactivo | Sí (la identidad de sesión activa cambia) | Crea nuevo archivo de sesión y cambia la sesión actual a este (solo en modo persistente) | Copia el directorio de artefactos al nuevo namespace de sesión cuando está presente |\n| `/resume` | Comando slash interactivo | Sí (el estado activo en memoria es reemplazado) | Cambia al archivo de sesión existente seleccionado | Ninguno |\n| `--resume` | Inicio CLI (selector) | Sí después de la creación de sesión | Abre el archivo de sesión existente seleccionado | Ninguno |\n| `--resume <id\\|path>` | Inicio CLI | Sí después de la creación de sesión | Abre sesión existente; el caso entre proyectos puede bifurcar hacia el proyecto actual | Ninguno |\n| `--continue` | Inicio CLI | Sí después de la creación de sesión | Abre la miga de pan del terminal o la sesión más reciente; crea una nueva si no existe ninguna | Ninguno |\n\n## Export y dump\n\n### `/export [outputPath]` (interactivo)\n\nFlujo:\n\n1. `InputController` enruta `/export...` a `CommandController.handleExportCommand`.\n2. El comando divide por espacios en blanco y usa solo el primer argumento después de `/export` como `outputPath`.\n3. `AgentSession.exportToHtml()` llama a `exportSessionToHtml(sessionManager, state, { outputPath, themeName })`.\n4. En caso de éxito, la UI muestra la ruta y abre el archivo en el navegador.\n\nDetalles de comportamiento:\n\n- Los argumentos `--copy`, `clipboard` y `copy` son rechazados explícitamente con una advertencia para usar `/dump`.\n- La exportación incrusta encabezado/entradas/hoja de sesión más el `systemPrompt` actual y las descripciones de herramientas del estado del agente.\n- No se añaden entradas de sesión durante la exportación.\n\nAdvertencia:\n\n- El análisis de argumentos se basa en espacios en blanco (`text.split(/\\s+/)`), por lo que las rutas entre comillas con espacios no se preservan como una sola ruta en esta ruta de comando.\n\n### `--export <inputSessionFile> [outputPath]` (CLI)\n\nFlujo en `main.ts`:\n\n1. Se maneja tempranamente (antes del inicio interactivo/de sesión).\n2. Llama a `exportFromFile(inputPath, outputPath?)`.\n3. `SessionManager.open(inputPath)` carga las entradas, luego se genera y escribe el HTML.\n4. El proceso imprime `Exported to: ...` y termina.\n\nDetalles de comportamiento:\n\n- Un archivo de entrada faltante se muestra como `File not found: <path>`.\n- Esta ruta no crea un `AgentSession` y no muta ninguna sesión en ejecución.\n\n### `/dump` (exportación interactiva al portapapeles)\n\nFlujo:\n\n1. `CommandController.handleDumpCommand()` llama a `session.formatSessionAsText()`.\n2. Si devuelve cadena vacía, reporta `No messages to dump yet.`\n3. De lo contrario, copia al portapapeles mediante `copyToClipboard` nativo.\n\nEl contenido del dump incluye:\n\n- Prompt del sistema\n- Modelo activo/nivel de razonamiento\n- Definiciones de herramientas + parámetros\n- Mensajes de usuario/asistente\n- Bloques de razonamiento y llamadas a herramientas\n- Resultados de herramientas y bloques de ejecución (excepto entradas bash/python con `excludeFromContext`)\n- Entradas personalizadas/hook/mención de archivos/resumen de rama/resumen de compactación\n\nNo se realizan cambios de persistencia de sesión al hacer dump.\n\n## Share\n\n`/share` es solo interactivo y siempre comienza exportando la sesión actual a un archivo HTML temporal.\n\n### Fase 1: exportación temporal\n\n- Ruta del archivo temporal: `${os.tmpdir()}/${Snowflake.next()}.html`\n- Usa `session.exportToHtml(tmpFile)`\n- Si la exportación falla (notablemente en sesiones en memoria), share termina con error.\n\n### Fase 2: manejador de compartir personalizado (si está presente)\n\n`loadCustomShare()` busca en `~/.xcsh/agent` el primer candidato existente:\n\n- `share.ts`\n- `share.js`\n- `share.mjs`\n\nRequisitos:\n\n- El módulo debe exportar por defecto una función `(htmlPath) => Promise<CustomShareResult | string | undefined>`.\n\nSi está presente y es válido:\n\n- La UI entra en estado de carga `Sharing...`.\n- Interpretación del resultado del manejador:\n  - string => se trata como URL, se muestra y se abre\n  - object => se muestran `url` y/o `message`; se abre `url`\n  - `undefined`/falsy => `Session shared` genérico\n- El archivo temporal se elimina después de completarse.\n\nComportamiento crítico de respaldo:\n\n- Si el manejador personalizado existe pero falla al cargarse, el comando genera error y retorna.\n- Si el manejador personalizado se ejecuta y lanza una excepción, el comando genera error y retorna.\n- En ambos casos de fallo, **no** recurre al gist de GitHub como respaldo.\n- El respaldo a gist solo ocurre cuando no existe ningún script de compartir personalizado.\n\n### Fase 3: respaldo predeterminado a gist\n\nSolo cuando no se encuentra ningún manejador de compartir personalizado:\n\n1. Valida `gh auth status`.\n2. Muestra el indicador de carga `Creating gist...`.\n3. Ejecuta `gh gist create --public=false <tmpFile>`.\n4. Analiza la URL del gist, deriva el id del gist, construye la URL de vista previa `https://gistpreview.github.io/?<id>`.\n5. Muestra tanto la URL de vista previa como la del gist; abre la vista previa.\n\nSemánticas de cancelación/aborto en share:\n\n- El indicador de carga tiene un hook `onAbort` que restaura la UI del editor y reporta `Share cancelled`.\n- El comando subyacente `gh gist create` no recibe una señal de aborto en esta ruta de código; la cancelación es a nivel de UI y se verifica después de que el comando retorna.\n\n## Fork\n\n`/fork` crea una nueva sesión a partir de la actual y cambia la identidad de sesión activa.\n\n### Precondiciones y guardas inmediatas\n\n- Si el agente está en streaming, `/fork` se rechaza con advertencia.\n- Los indicadores de estado/carga de la UI se limpian antes de la operación.\n\n### Flujo a nivel de sesión\n\n`AgentSession.fork()`:\n\n1. Emite `session_before_switch` con `reason: \"fork\"` (cancelable).\n2. Descarga las escrituras pendientes.\n3. Llama a `SessionManager.fork()`.\n4. Copia el directorio de artefactos del namespace de sesión antiguo al nuevo (mejor esfuerzo; los fallos de copia que no son ENOENT se registran en log, no son fatales).\n5. Actualiza `agent.sessionId`.\n6. Emite `session_switch` con `reason: \"fork\"`.\n\nComportamiento de `SessionManager.fork()`:\n\n- Requiere modo persistente y archivo de sesión existente.\n- Crea nuevo id de sesión y nueva ruta de archivo JSONL.\n- Reescribe el encabezado con:\n  - nuevo `id`\n  - nueva marca de tiempo\n  - `cwd` sin cambios\n  - `parentSession` establecido al id de sesión anterior\n- Mantiene todas las entradas que no son encabezado sin cambios en el nuevo archivo.\n\n### Comportamiento no persistente\n\n- El administrador de sesiones en memoria devuelve `undefined` de `fork()`.\n- `AgentSession.fork()` devuelve `false`.\n- La UI reporta `Fork failed (session not persisted or cancelled)`.\n\n## Resume y continue\n\n## `/resume` interactivo\n\nFlujo:\n\n1. Abre el selector de sesión poblado mediante `SessionManager.list(currentCwd, currentSessionDir)`.\n2. Al seleccionar, `SelectorController.handleResumeSession(sessionPath)` llama a `session.switchSession(sessionPath)`.\n3. La UI limpia/reconstruye el chat y los pendientes, luego reporta `Resumed session`.\n\nNotas:\n\n- Este selector solo lista sesiones en el ámbito del directorio de sesión actual.\n- No usa búsqueda global entre proyectos.\n\n## CLI `--resume`\n\n### `--resume` (sin valor)\n\n- `main.ts` lista sesiones para el cwd/sessionDir actual y abre el selector.\n- La ruta seleccionada se abre con `SessionManager.open(selectedPath)` antes de la creación de sesión.\n\n### `--resume <value>`\n\nOrden de resolución de `createSessionManager()`:\n\n1. Si el valor parece una ruta (`/`, `\\`, o `.jsonl`), abre directamente.\n2. De lo contrario, trata como prefijo de id:\n   - busca en el ámbito actual (`SessionManager.list(cwd, sessionDir)`)\n   - si no se encuentra y no hay `sessionDir` explícito, busca globalmente (`SessionManager.listAll()`)\n\nComportamiento de coincidencia de id entre proyectos:\n\n- Si el cwd de la sesión coincidente difiere del cwd actual, el CLI pregunta:\n  - `Session found in different project ... Fork into current directory? [y/N]`\n- Al aceptar: `SessionManager.forkFrom(match.path, cwd, sessionDir)` crea un nuevo archivo local bifurcado.\n- Al rechazar/predeterminado sin TTY: el comando genera error.\n\n## CLI `--continue`\n\n`SessionManager.continueRecent(cwd, sessionDir)`:\n\n1. Resuelve el directorio de sesión para el cwd actual.\n2. Lee primero la miga de pan con ámbito de terminal.\n3. Recurre como respaldo al archivo de sesión modificado más recientemente.\n4. Abre la sesión encontrada; si no existe ninguna, crea una nueva sesión.\n\nEste es un comportamiento solo de inicio; no existe un comando slash interactivo `/continue`.\n\n## Cómo el cambio de sesión muta realmente el estado en tiempo de ejecución\n\n`AgentSession.switchSession(sessionPath)` realiza la transición en tiempo de ejecución utilizada por las operaciones tipo resume:\n\n1. Emite `session_before_switch` con `reason: \"resume\"` y `targetSessionFile` (cancelable).\n2. Desconecta la suscripción de eventos del agente y aborta el trabajo en curso.\n3. Limpia los mensajes encolados de steering/seguimiento/siguiente turno.\n4. Descarga las escrituras del administrador de sesión actual.\n5. `sessionManager.setSessionFile(sessionPath)` y actualiza `agent.sessionId`.\n6. Construye el contexto de sesión a partir de las entradas cargadas.\n7. Emite `session_switch` con `reason: \"resume\"`.\n8. Reemplaza los mensajes del agente desde el contexto.\n9. Restaura el modelo (si está disponible en el registro actual).\n10. Restaura o inicializa el nivel de razonamiento.\n11. Reconecta la suscripción de eventos del agente.\n\n`switchSession()` en sí mismo no crea ningún archivo de sesión nuevo.\n\n## Emisiones de eventos y puntos de cancelación\n\n### Hooks del ciclo de vida de switch/fork\n\nPara `newSession`, `fork` y `switchSession`:\n\n- Evento anterior: `session_before_switch`\n  - razones: `new`, `fork`, `resume`\n  - cancelable devolviendo `{ cancel: true }`\n- Evento posterior: `session_switch`\n  - mismo conjunto de razones\n  - incluye `previousSessionFile`\n\n`ExtensionRunner.emit()` retorna tempranamente con el primer resultado de evento anterior que cancele.\n\n### Comportamiento de `onSession` en herramientas personalizadas\n\nLos puentes del SDK conectan eventos de sesión de extensiones a callbacks `onSession` de herramientas personalizadas:\n\n- `session_switch` -> `onSession({ reason: \"switch\", previousSessionFile })`\n- `session_branch` -> `reason: \"branch\"`\n- `session_start` -> `reason: \"start\"`\n- `session_tree` -> `reason: \"tree\"`\n- `session_shutdown` -> `reason: \"shutdown\"`\n\nEstos callbacks son observacionales; no cancelan switch/fork.\n\n### Otras superficies de cancelación relevantes para este documento\n\n- `/fork` se bloquea durante el streaming (el usuario debe esperar/abortar la respuesta actual primero).\n- El selector de `/resume` puede cancelarse si el usuario cierra el selector.\n- `--resume <id>` entre proyectos puede cancelarse rechazando el prompt de fork.\n- `/share` tiene ruta de aborto en la UI (`Share cancelled`) para el flujo de gist; no conecta semánticas de proceso-kill para `gh gist create` en esta ruta de código.\n\n## Comportamiento de sesión no persistente (en memoria)\n\nCuando el administrador de sesiones se crea con `SessionManager.inMemory()` (`--no-session`):\n\n- La ruta del archivo de sesión está ausente.\n- `/export` y `/share` fallan con `Cannot export in-memory session to HTML` (propagado a la UI de error del comando).\n- `/fork` falla porque `SessionManager.fork()` requiere persistencia.\n- `/dump` sigue funcionando porque serializa el estado del agente en memoria.\n- Las semánticas de resume/continue del CLI se omiten si `--no-session` está establecido, porque la creación del administrador devuelve en memoria inmediatamente.\n\n## Advertencias de implementación conocidas (a partir del código actual)\n\n- `SelectorController.handleResumeSession()` no verifica el resultado booleano de `session.switchSession(...)`; un cambio cancelado por un hook puede aún proceder a través de la ruta de repintado/estado de la UI \"Resumed session\".\n- Los fallos de `/share` con compartir personalizado no degradan al respaldo de gist predeterminado; terminan el comando con error.\n- La tokenización de argumentos de `/export` es simplista y no preserva rutas entre comillas con espacios.\n",
	"es/sessions/session-switching-and-recent-listing.md": "---\ntitle: Cambio de sesión y listado de sesiones recientes\ndescription: >-\n  Mecánicas de cambio de sesión y listado de sesiones recientes con búsqueda y\n  filtrado.\nsidebar:\n  order: 4\n  label: Cambio y recientes\ni18n:\n  sourceHash: aae56130b508\n  translator: machine\n---\n\n# Cambio de sesión y listado de sesiones recientes\n\nEste documento describe cómo coding-agent descubre sesiones recientes, resuelve objetivos de `--resume`, presenta selectores de sesión y cambia la sesión activa en tiempo de ejecución.\n\nSe centra en el comportamiento de la implementación actual, incluyendo rutas de respaldo y advertencias.\n\n## Archivos de implementación\n\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/cli/session-picker.ts`](../../packages/coding-agent/src/cli/session-picker.ts)\n- [`../src/modes/components/session-selector.ts`](../../packages/coding-agent/src/modes/components/session-selector.ts)\n- [`../src/modes/controllers/selector-controller.ts`](../../packages/coding-agent/src/modes/controllers/selector-controller.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n\n## Descubrimiento de sesiones recientes\n\n### Alcance del directorio\n\n`SessionManager` almacena sesiones bajo un directorio con alcance al cwd por defecto:\n\n- `~/.xcsh/agent/sessions/--<cwd-encoded>--/*.jsonl`\n\n`SessionManager.list(cwd, sessionDir?)` lee solo ese directorio a menos que se proporcione un `sessionDir` explícito.\n\n### Dos rutas de listado con diferentes cargas útiles\n\nExisten dos pipelines de listado diferentes:\n\n1. `getRecentSessions(sessionDir, limit)` (vista de bienvenida/resumen)\n   - Lee solo un prefijo de 4KB (`readTextPrefix(..., 4096)`) de cada archivo.\n   - Analiza el encabezado + vista previa del texto más antiguo del usuario.\n   - Devuelve `RecentSessionInfo` ligero con getters lazy para `name` y `timeAgo`.\n   - Ordena por `mtime` del archivo de forma descendente.\n\n2. `SessionManager.list(...)` / `SessionManager.listAll()` (selectores de reanudación y coincidencia de ID)\n   - Lee los archivos de sesión completos.\n   - Construye objetos `SessionInfo` (`id`, `cwd`, `title`, `messageCount`, `firstMessage`, `allMessagesText`, marcas de tiempo).\n   - Descarta sesiones con cero entradas de `message`.\n   - Ordena por `modified` de forma descendente.\n\n### Comportamiento de respaldo de metadatos\n\nPara resúmenes recientes (`RecentSessionInfo`):\n\n- preferencia de nombre para mostrar: `header.title` -> primer prompt del usuario -> `header.id` -> nombre del archivo\n- el nombre se trunca a 40 caracteres para visualizaciones compactas\n- los caracteres de control/saltos de línea se eliminan/sanitizan de los nombres derivados del título\n\nPara entradas de lista `SessionInfo`:\n\n- `title` es `header.title` o el último `shortSummary` de compactación\n- `firstMessage` es el texto del primer mensaje del usuario o `\"(no messages)\"`\n\n## Resolución de `--continue` y preferencia de breadcrumb del terminal\n\n`SessionManager.continueRecent(cwd, sessionDir?)` resuelve el objetivo en este orden:\n\n1. Leer el breadcrumb con alcance al terminal (`~/.xcsh/agent/terminal-sessions/<terminal-id>`)\n2. Validar el breadcrumb:\n   - el terminal actual puede ser identificado\n   - el cwd del breadcrumb coincide con el cwd actual (comparación de rutas resueltas)\n   - el archivo referenciado aún existe\n3. Si el breadcrumb es inválido/no existe, recurrir al archivo más reciente por mtime en el directorio de sesiones (`findMostRecentSession`)\n4. Si no se encuentra ninguno, crear una nueva sesión\n\nLa derivación del ID del terminal prefiere la ruta TTY y recurre a identificadores basados en variables de entorno (`KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION`).\n\nLas escrituras de breadcrumb son de mejor esfuerzo y no fatales.\n\n## Resolución del objetivo de reanudación en tiempo de inicio (`main.ts`)\n\n### `--resume <value>`\n\n`createSessionManager(...)` maneja `--resume` con valor de cadena en dos modos:\n\n1. Valor tipo ruta (contiene `/`, `\\\\`, o termina con `.jsonl`)\n   - `SessionManager.open(sessionArg, parsed.sessionDir)` directo\n\n2. Valor de prefijo de ID\n   - busca coincidencia en `SessionManager.list(cwd, sessionDir)` mediante `id.startsWith(sessionArg)`\n   - si no hay coincidencia local y `sessionDir` no está forzado, intenta `SessionManager.listAll()`\n   - se usa la primera coincidencia (sin prompt de ambigüedad)\n\nComportamiento de coincidencia entre proyectos:\n\n- si el cwd de la sesión coincidente difiere del cwd actual, el CLI pregunta si bifurcar al proyecto actual\n- sí -> `SessionManager.forkFrom(...)`\n- no -> lanza error (`Session \"...\" is in another project (...)`)\n\nSin coincidencia -> lanza error (`Session \"...\" not found.`).\n\n### `--resume` (sin valor)\n\nSe maneja después de la construcción inicial del session-manager:\n\n1. listar sesiones locales con `SessionManager.list(cwd, parsed.sessionDir)`\n2. si está vacío: imprimir `No sessions found` y salir tempranamente\n3. abrir selector TUI (`selectSession`)\n4. si se cancela: imprimir `No session selected` y salir tempranamente\n5. si se selecciona: `SessionManager.open(selectedPath)`\n\n### `--continue`\n\nUsa `SessionManager.continueRecent(...)` directamente (comportamiento de breadcrumb primero descrito anteriormente).\n\n## Detalles internos de selección por selector\n\n## Selector CLI (`src/cli/session-picker.ts`)\n\n`selectSession(sessions)` crea una TUI independiente con `SessionSelectorComponent` y se resuelve exactamente una vez:\n\n- selección -> resuelve con la ruta seleccionada\n- cancelar (Esc) -> resuelve con `null`\n- salida forzada (ruta Ctrl+C) -> detiene la TUI y `process.exit(0)`\n\n## Selector interactivo dentro de sesión (`SelectorController.showSessionSelector`)\n\nFlujo:\n\n1. obtener sesiones del directorio de sesión actual vía `SessionManager.list(currentCwd, currentSessionDir)`\n2. montar `SessionSelectorComponent` en el área del editor usando `showSelector(...)`\n3. callbacks:\n   - seleccionar -> cerrar selector y llamar a `handleResumeSession(sessionPath)`\n   - cancelar -> restaurar editor y rerenderizar\n   - salir -> `ctx.shutdown()`\n\n## Comportamiento del componente selector de sesión\n\n`SessionList` soporta:\n\n- navegación con flechas/página\n- Enter para seleccionar\n- Esc para cancelar\n- Ctrl+C para salir\n- búsqueda difusa a través de id/título/cwd/primer mensaje/todos los mensajes/ruta de la sesión\n\nComportamiento de renderizado con lista vacía:\n\n- renderiza un mensaje en lugar de fallar\n- Enter en lista vacía no hace nada (sin callback)\n- Esc/Ctrl+C siguen funcionando\n\nAdvertencia: El texto de la UI dice `Press Tab to view all`, pero este componente actualmente no tiene manejador de Tab y el cableado actual solo lista sesiones del alcance actual.\n\n## Ejecución del cambio en tiempo de ejecución (`AgentSession.switchSession`)\n\n`switchSession(sessionPath)` es la ruta principal de cambio dentro del proceso.\n\nCiclo de vida/transición de estado:\n\n1. capturar `previousSessionFile`\n2. emitir evento hook `session_before_switch` (`reason: \"resume\"`, cancelable)\n3. si se cancela -> retorna `false` sin cambio\n4. desconectar del flujo de eventos del agente actual\n5. abortar generación/flujo de herramientas activo\n6. limpiar buffers de mensajes de steering/seguimiento/siguiente turno en cola\n7. vaciar el escritor de sesión (`sessionManager.flush()`) para persistir escrituras pendientes\n8. `sessionManager.setSessionFile(sessionPath)`\n   - actualiza el puntero del archivo de sesión\n   - escribe el breadcrumb del terminal\n   - carga entradas / migra / resuelve blobs / reindexa\n   - si los datos del archivo están ausentes/son inválidos: inicializa una nueva sesión en esa ruta y reescribe el encabezado\n9. actualizar `agent.sessionId`\n10. reconstruir contexto vía `buildSessionContext()`\n11. emitir evento hook `session_switch` (`reason: \"resume\"`, `previousSessionFile`)\n12. reemplazar mensajes del agente con el contexto reconstruido\n13. restaurar modelo por defecto desde `sessionContext.models.default` si está disponible y presente en el registro de modelos\n14. restaurar nivel de pensamiento:\n    - si la rama ya tiene `thinking_level_change`, aplicar el nivel de sesión guardado\n    - de lo contrario, derivar el nivel de pensamiento por defecto de la configuración, ajustar a la capacidad del modelo, establecerlo y agregar una nueva entrada `thinking_level_change`\n15. reconectar listeners del agente y retornar `true`\n\n## Reconstrucción del estado de la UI después del cambio interactivo\n\n`SelectorController.handleResumeSession` realiza el reinicio de la UI alrededor de `switchSession`:\n\n- detener animación de carga\n- limpiar contenedor de estado\n- limpiar UI de mensaje pendiente y mapa de herramientas pendientes\n- reiniciar componente de streaming/referencias de mensaje\n- llamar a `session.switchSession(...)`\n- limpiar contenedor del chat y rerenderizar desde el contexto de sesión (`renderInitialMessages`)\n- recargar todos desde los artefactos de la nueva sesión\n- mostrar `Resumed session`\n\nAsí que el estado visible de conversación/todos se reconstruye desde el nuevo archivo de sesión.\n\n## Reanudación al inicio vs cambio dentro de sesión\n\n### Reanudación al inicio (`--continue`, `--resume`, apertura directa)\n\n- El archivo de sesión se elige antes de `createAgentSession(...)`.\n- `sdk.ts` construye `existingSession = sessionManager.buildSessionContext()`.\n- Los mensajes del agente se restauran una vez durante la creación de la sesión.\n- El modelo/pensamiento se seleccionan durante la creación (incluyendo lógica de restauración/respaldo).\n- El modo interactivo luego ejecuta `#restoreModeFromSession()` para re-entrar al estado de modo persistido (actualmente plan/plan_paused).\n\n### Cambio dentro de sesión (ruta del selector estilo `/resume`)\n\n- Usa `AgentSession.switchSession(...)` en un `AgentSession` ya en ejecución.\n- Los mensajes/modelo/pensamiento se reconstruyen inmediatamente en su lugar.\n- Se emiten eventos hook `session_before_switch`/`session_switch`.\n- Se refrescan el chat/todos de la UI.\n- No se realiza una llamada dedicada de restauración de modo post-cambio en el flujo del selector; el comportamiento de re-entrada al modo no es simétrico con `#restoreModeFromSession()` del inicio.\n\n## Comportamiento ante fallos y casos límite\n\n### Rutas de cancelación\n\n- Cancelación del selector CLI -> retorna `null`, el llamador imprime `No session selected`, el proceso sale tempranamente.\n- Cancelación del selector interactivo -> se restaura el editor, sin cambio de sesión.\n- Cancelación por hook (`session_before_switch`) -> `switchSession()` retorna `false`.\n\n### Rutas con lista vacía\n\n- CLI `--resume` (sin valor): lista vacía imprime `No sessions found` y sale.\n- Selector interactivo: lista vacía renderiza mensaje y permanece cancelable.\n\n### Archivo de sesión objetivo ausente/inválido\n\nAl abrir/cambiar a una ruta específica (`setSessionFile`):\n\n- ENOENT -> tratado como vacío -> nueva sesión inicializada en esa ruta exacta y persistida.\n- encabezado malformado/inválido (o entradas parseadas efectivamente ilegibles) -> tratado como vacío -> nueva sesión inicializada y persistida.\n\nEsto es comportamiento de recuperación, no fallo duro.\n\n### Fallos duros\n\nEl cambio/apertura aún puede lanzar excepciones en fallos de E/S reales (errores de permisos, fallos de reescritura, etc.), que se propagan a los llamadores.\n\n### Advertencias sobre coincidencia de prefijo de ID\n\n- La coincidencia de ID usa `startsWith` y toma la primera coincidencia en la lista ordenada.\n- No hay UI de ambigüedad si múltiples sesiones comparten prefijo.\n- `SessionManager.list(...)` excluye sesiones con cero mensajes, por lo que esas sesiones no son reanudables vía coincidencia de ID/selector de lista.\n",
	"es/sessions/session-tree-plan.md": "---\ntitle: Arquitectura del Árbol de Sesión\ndescription: >-\n  Arquitectura del árbol de sesión con ramificación, navegación y relaciones de\n  conversación padre-hijo.\nsidebar:\n  order: 2\n  label: Arquitectura de árbol\ni18n:\n  sourceHash: bd8b78d6c33a\n  translator: machine\n---\n\n# Arquitectura del árbol de sesión (actual)\n\nReferencia: [session.md](./session.md)\n\nEste documento describe cómo funciona actualmente la navegación del árbol de sesión: modelo de árbol en memoria, reglas de movimiento de hojas, comportamiento de ramificación e integración de extensiones/eventos.\n\n## Qué es este subsistema\n\nLa sesión se almacena como un registro de entradas de solo adición (append-only), pero el comportamiento en tiempo de ejecución está basado en un árbol:\n\n- Cada entrada que no sea de encabezado tiene `id` y `parentId`.\n- La posición activa es `leafId` en `SessionManager`.\n- Añadir una entrada siempre crea un hijo de la hoja actual.\n- La ramificación **no** reescribe el historial; solo cambia a dónde apunta la hoja antes de la siguiente adición.\n\nArchivos clave:\n\n- `src/session/session-manager.ts` — modelo de datos del árbol, recorrido, movimiento de hojas, extracción de ramas/sesiones\n- `src/session/agent-session.ts` — flujo de navegación `/tree`, resumen, emisión de hooks/eventos\n- `src/modes/components/tree-selector.ts` — comportamiento interactivo de la UI del árbol y filtrado\n- `src/modes/controllers/selector-controller.ts` — orquestación del selector para `/tree` y `/branch`\n- `src/modes/controllers/input-controller.ts` — enrutamiento de comandos (`/tree`, `/branch`, comportamiento de doble escape)\n- `src/session/messages.ts` — conversión de entradas `branch_summary`, `compaction` y `custom_message` en mensajes de contexto para el LLM\n\n## Modelo de datos del árbol en `SessionManager`\n\nÍndices en tiempo de ejecución:\n\n- `#byId: Map<string, SessionEntry>` — búsqueda rápida de cualquier entrada\n- `#leafId: string | null` — posición actual en el árbol\n- `#labelsById: Map<string, string>` — etiquetas resueltas por id de entrada objetivo\n\nAPIs del árbol:\n\n- `getBranch(fromId?)` recorre los enlaces de padre hasta la raíz y devuelve la ruta raíz→nodo\n- `getTree()` devuelve `SessionTreeNode[]` (`entry`, `children`, `label`)\n  - los enlaces de padre se convierten en arrays de hijos\n  - las entradas con padres faltantes se tratan como raíces\n  - los hijos se ordenan de más antiguo a más reciente por marca de tiempo\n- `getChildren(parentId)` devuelve los hijos directos\n- `getLabel(id)` resuelve la etiqueta actual desde `labelsById`\n\n`getTree()` es una proyección en tiempo de ejecución; la persistencia sigue siendo entradas JSONL de solo adición.\n\n## Semántica de movimiento de hojas\n\nExisten tres primitivas de movimiento de hojas:\n\n1. `branch(entryId)`\n   - Valida que la entrada exista\n   - Establece `leafId = entryId`\n   - No se escribe ninguna entrada nueva\n\n2. `resetLeaf()`\n   - Establece `leafId = null`\n   - La siguiente adición crea una nueva entrada raíz (`parentId = null`)\n\n3. `branchWithSummary(branchFromId, summary, details?, fromExtension?)`\n   - Acepta `branchFromId: string | null`\n   - Establece `leafId = branchFromId`\n   - Añade una entrada `branch_summary` como hija de esa hoja\n   - Cuando `branchFromId` es `null`, `fromId` se persiste como `\"root\"`\n\n## Comportamiento de navegación `/tree` (mismo archivo de sesión)\n\n`AgentSession.navigateTree()` es navegación, no bifurcación de archivos.\n\nFlujo:\n\n1. Validar el objetivo y calcular la ruta abandonada (`collectEntriesForBranchSummary`)\n2. Emitir `session_before_tree` con `TreePreparation`\n3. Opcionalmente resumir las entradas abandonadas (resumen proporcionado por hook o resumidor integrado)\n4. Calcular el nuevo objetivo de hoja:\n   - al seleccionar un mensaje de **usuario**: la hoja se mueve a su padre, y el texto del mensaje se devuelve para prellenar el editor\n   - al seleccionar un **custom_message**: misma regla que para mensajes de usuario (hoja = padre, el texto prellena el editor)\n   - al seleccionar cualquier otra entrada: hoja = id de la entrada seleccionada\n5. Aplicar el movimiento de hoja:\n   - con resumen: `branchWithSummary(newLeafId, ...)`\n   - sin resumen y `newLeafId === null`: `resetLeaf()`\n   - en otro caso: `branch(newLeafId)`\n6. Reconstruir el contexto del agente desde la nueva hoja y emitir `session_tree`\n\nImportante: las entradas de resumen se adjuntan en la **nueva posición de navegación**, no en la cola de la rama abandonada.\n\n## Comportamiento de `/branch` (nuevo archivo de sesión)\n\n`/branch` y `/tree` son intencionalmente diferentes:\n\n- `/tree` navega dentro del archivo de sesión actual.\n- `/branch` crea un nuevo archivo de rama de sesión (o un reemplazo en memoria para el modo no persistente).\n\nFlujo de `/branch` orientado al usuario (`SelectorController.showUserMessageSelector` → `AgentSession.branch`):\n\n- El origen de la rama debe ser un **mensaje de usuario**.\n- El texto del usuario seleccionado se extrae para prellenar el editor.\n- Si el mensaje de usuario seleccionado es raíz (`parentId === null`): iniciar una nueva sesión mediante `newSession({ parentSession: previousSessionFile })`.\n- En otro caso: `createBranchedSession(selectedEntry.parentId)` para bifurcar el historial hasta el límite del prompt seleccionado.\n\nEspecificaciones de `SessionManager.createBranchedSession(leafId)`:\n\n- Construye la ruta raíz→hoja mediante `getBranch(leafId)`; lanza error si no existe.\n- Excluye las entradas `label` existentes de la ruta copiada.\n- Reconstruye entradas de etiqueta frescas desde `labelsById` resueltas para las entradas que permanecen en la ruta.\n- Modo persistente: escribe un nuevo archivo JSONL y cambia el manager a él; devuelve la nueva ruta del archivo.\n- Modo en memoria: reemplaza las entradas en memoria; devuelve `undefined`.\n\n## Reconstrucción de contexto e integración de resúmenes/custom\n\n`buildSessionContext()` (en `session-manager.ts`) resuelve la ruta activa raíz→hoja y construye el estado de contexto efectivo del LLM:\n\n- Rastrea el último estado de thinking/model/mode/ttsr en la ruta.\n- Maneja la última compactación en la ruta:\n  - emite primero el resumen de compactación\n  - reproduce los mensajes conservados desde `firstKeptEntryId` hasta el punto de compactación\n  - luego reproduce los mensajes posteriores a la compactación\n- Incluye entradas `branch_summary` y `custom_message` como objetos `AgentMessage`.\n\n`session/messages.ts` luego mapea estos tipos de mensaje para la entrada del modelo:\n\n- `branchSummary` y `compactionSummary` se convierten en mensajes de contexto con plantilla y rol de usuario\n- `custom`/`hookMessage` se convierten en mensajes de contenido con rol de usuario\n\nAsí, el movimiento en el árbol cambia el contexto al cambiar la ruta activa de la hoja, no mutando entradas antiguas.\n\n## Etiquetas y comportamiento de la UI del árbol\n\nPersistencia de etiquetas:\n\n- `appendLabelChange(targetId, label?)` escribe entradas `label` en la cadena de la hoja actual.\n- `labelsById` se actualiza inmediatamente (establecer o eliminar).\n- `getTree()` resuelve la etiqueta actual en cada nodo devuelto.\n\nComportamiento del selector de árbol (`tree-selector.ts`):\n\n- Aplana el árbol para navegación, mantiene el resaltado de la ruta activa y prioriza mostrar primero la rama activa.\n- Soporta modos de filtro: `default`, `no-tools`, `user-only`, `labeled-only`, `all`.\n- Soporta búsqueda de texto libre sobre el contenido semántico renderizado.\n- `Shift+L` abre la edición de etiquetas en línea y escribe mediante `appendLabelChange`.\n\nEnrutamiento de comandos:\n\n- `/tree` siempre abre el selector de árbol.\n- `/branch` abre el selector de mensajes de usuario a menos que `doubleEscapeAction=tree`, en cuyo caso también utiliza la UX del selector de árbol.\n\n## Puntos de contacto de extensiones y hooks para operaciones del árbol\n\nAPI de extensión en tiempo de comando (`ExtensionCommandContext`):\n\n- `branch(entryId)` — crear archivo de sesión ramificada\n- `navigateTree(targetId, { summarize? })` — moverse dentro del árbol/archivo actual\n\nEventos alrededor de la navegación del árbol:\n\n- `session_before_tree`\n  - recibe `TreePreparation`:\n    - `targetId`\n    - `oldLeafId`\n    - `commonAncestorId`\n    - `entriesToSummarize`\n    - `userWantsSummary`\n  - puede cancelar la navegación\n  - puede proporcionar un payload de resumen utilizado en lugar del resumidor integrado\n  - recibe `signal` de aborto (ruta de cancelación por Escape)\n- `session_tree`\n  - emite `newLeafId`, `oldLeafId`\n  - incluye `summaryEntry` cuando se creó un resumen\n  - `fromExtension` indica el origen del resumen\n\nHooks de ciclo de vida adyacentes pero relacionados:\n\n- `session_before_branch` / `session_branch` para el flujo de `/branch`\n- `session_before_compact`, `session.compacting`, `session_compact` para entradas de compactación que posteriormente afectan la reconstrucción del contexto del árbol\n\n## Restricciones reales y condiciones de borde\n\n- `branch()` no puede apuntar a `null`; use `resetLeaf()` para el estado raíz previo a la primera entrada.\n- `branchWithSummary()` soporta objetivo `null` y registra `fromId: \"root\"`.\n- Seleccionar la hoja actual en el selector de árbol es una operación sin efecto (no-op).\n- El resumen requiere un modelo activo; si está ausente, la navegación con resumen falla inmediatamente.\n- Si el resumen se aborta, la navegación se cancela y la hoja permanece sin cambios.\n- Las sesiones en memoria nunca devuelven una ruta de archivo de rama desde `createBranchedSession`.\n\n## Compatibilidad heredada aún presente\n\nLas migraciones de sesión siguen ejecutándose al cargar:\n\n- v1→v2 añade `id`/`parentId` y convierte el ancla de índice de compactación a ancla de id\n- v2→v3 migra el rol heredado `hookMessage` a `custom`\n\nEl comportamiento actual en tiempo de ejecución utiliza la semántica de árbol versión 3 después de la migración.\n",
	"es/sessions/session.md": "---\ntitle: Almacenamiento de Sesiones y Modelo de Entradas\ndescription: >-\n  Modelo de almacenamiento de sesiones de solo escritura con tipos de entrada,\n  persistencia y migración entre formatos.\nsidebar:\n  order: 1\n  label: Almacenamiento y modelo de entradas\ni18n:\n  sourceHash: 42fe17549e00\n  translator: machine\n---\n\n# Almacenamiento de Sesiones y Modelo de Entradas\n\nEste documento es la fuente de verdad sobre cómo las sesiones del agente de codificación se representan, persisten, migran y reconstruyen en tiempo de ejecución.\n\n## Alcance\n\nCubre:\n\n- Formato JSONL de sesión y versionado\n- Taxonomía de entradas y semántica de árbol (`id`/`parentId` + puntero de hoja)\n- Comportamiento de migración/compatibilidad al cargar archivos antiguos o mal formados\n- Reconstrucción de contexto (`buildSessionContext`)\n- Garantías de persistencia, comportamiento ante fallos, truncamiento/externalización de blobs\n- Abstracciones de almacenamiento (`FileSessionStorage`, `MemorySessionStorage`) y utilidades relacionadas\n\nNo cubre el comportamiento de renderizado de la UI `/tree` más allá de la semántica que afecta los datos de sesión.\n\n## Archivos de Implementación\n\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`src/session/messages.ts`](../../packages/coding-agent/src/session/messages.ts)\n- [`src/session/session-storage.ts`](../../packages/coding-agent/src/session/session-storage.ts)\n- [`src/session/history-storage.ts`](../../packages/coding-agent/src/session/history-storage.ts)\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts)\n\n## Estructura en Disco\n\nUbicación predeterminada del archivo de sesión:\n\n```text\n~/.xcsh/agent/sessions/--<cwd-encoded>--/<timestamp>_<sessionId>.jsonl\n```\n\n`<cwd-encoded>` se deriva del directorio de trabajo eliminando la barra inicial y reemplazando `/`, `\\\\` y `:` por `-`.\n\nUbicación del almacén de blobs:\n\n```text\n~/.xcsh/agent/blobs/<sha256>\n```\n\nLos archivos de referencia de terminal se escriben en:\n\n```text\n~/.xcsh/agent/terminal-sessions/<terminal-id>\n```\n\nEl contenido de la referencia son dos líneas: el cwd original, seguido de la ruta del archivo de sesión. `continueRecent()` prefiere este puntero con alcance de terminal antes de buscar el mtime más reciente.\n\n## Formato de Archivo\n\nLos archivos de sesión son JSONL: un objeto JSON por línea.\n\n- La línea 1 es siempre el encabezado de sesión (`type: \"session\"`).\n- Las líneas restantes son valores `SessionEntry`.\n- Las entradas son de solo escritura (append-only) en tiempo de ejecución; la navegación entre ramas mueve un puntero (`leafId`) en lugar de mutar entradas existentes.\n\n### Encabezado (`SessionHeader`)\n\n```json\n{\n  \"type\": \"session\",\n  \"version\": 3,\n  \"id\": \"1f9d2a6b9c0d1234\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\",\n  \"cwd\": \"/work/pi\",\n  \"title\": \"optional session title\",\n  \"parentSession\": \"optional lineage marker\"\n}\n```\n\nNotas:\n\n- `version` es opcional en archivos v1; su ausencia significa v1.\n- `parentSession` es una cadena opaca de linaje. El código actual escribe ya sea un id de sesión o una ruta de sesión dependiendo del flujo (`fork`, `forkFrom`, `createBranchedSession`, o `newSession({ parentSession })` explícito). Trátelo como metadatos, no como una clave foránea tipada.\n\n### Base de Entrada (`SessionEntryBase`)\n\nTodas las entradas que no son encabezado incluyen:\n\n```json\n{\n  \"type\": \"...\",\n  \"id\": \"8-char-id\",\n  \"parentId\": \"previous-or-branch-parent\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\"\n}\n```\n\n`parentId` puede ser `null` para una entrada raíz (primer append, o después de `resetLeaf()`).\n\n## Taxonomía de Entradas\n\n`SessionEntry` es la unión de:\n\n- `message`\n- `thinking_level_change`\n- `model_change`\n- `compaction`\n- `branch_summary`\n- `custom`\n- `custom_message`\n- `label`\n- `ttsr_injection`\n- `session_init`\n- `mode_change`\n\n### `message`\n\nAlmacena un `AgentMessage` directamente.\n\n```json\n{\n  \"type\": \"message\",\n  \"id\": \"a1b2c3d4\",\n  \"parentId\": null,\n  \"timestamp\": \"2026-02-16T10:21:00.000Z\",\n  \"message\": {\n    \"role\": \"assistant\",\n    \"provider\": \"anthropic\",\n    \"model\": \"claude-sonnet-4-5\",\n    \"content\": [{ \"type\": \"text\", \"text\": \"Done.\" }],\n    \"usage\": { \"input\": 100, \"output\": 20, \"cacheRead\": 0, \"cacheWrite\": 0, \"cost\": { \"input\": 0, \"output\": 0, \"cacheRead\": 0, \"cacheWrite\": 0, \"total\": 0 } },\n    \"timestamp\": 1760000000000\n  }\n}\n```\n\n### `model_change`\n\n```json\n{\n  \"type\": \"model_change\",\n  \"id\": \"b1c2d3e4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:21:30.000Z\",\n  \"model\": \"openai/gpt-4o\",\n  \"role\": \"default\"\n}\n```\n\n`role` es opcional; su ausencia se trata como `default` en la reconstrucción de contexto.\n\n### `thinking_level_change`\n\n```json\n{\n  \"type\": \"thinking_level_change\",\n  \"id\": \"c1d2e3f4\",\n  \"parentId\": \"b1c2d3e4\",\n  \"timestamp\": \"2026-02-16T10:22:00.000Z\",\n  \"thinkingLevel\": \"high\"\n}\n```\n\n### `compaction`\n\n```json\n{\n  \"type\": \"compaction\",\n  \"id\": \"d1e2f3a4\",\n  \"parentId\": \"c1d2e3f4\",\n  \"timestamp\": \"2026-02-16T10:23:00.000Z\",\n  \"summary\": \"Conversation summary\",\n  \"shortSummary\": \"Short recap\",\n  \"firstKeptEntryId\": \"a1b2c3d4\",\n  \"tokensBefore\": 42000,\n  \"details\": { \"readFiles\": [\"src/a.ts\"] },\n  \"preserveData\": { \"hookState\": true },\n  \"fromExtension\": false\n}\n```\n\n### `branch_summary`\n\n```json\n{\n  \"type\": \"branch_summary\",\n  \"id\": \"e1f2a3b4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:24:00.000Z\",\n  \"fromId\": \"a1b2c3d4\",\n  \"summary\": \"Summary of abandoned path\",\n  \"details\": { \"note\": \"optional\" },\n  \"fromExtension\": true\n}\n```\n\nSi se ramifica desde la raíz (`branchFromId === null`), `fromId` es la cadena literal `\"root\"`.\n\n### `custom`\n\nPersistencia de estado de extensiones; ignorado por `buildSessionContext`.\n\n```json\n{\n  \"type\": \"custom\",\n  \"id\": \"f1a2b3c4\",\n  \"parentId\": \"e1f2a3b4\",\n  \"timestamp\": \"2026-02-16T10:25:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"data\": { \"state\": 1 }\n}\n```\n\n### `custom_message`\n\nMensaje proporcionado por una extensión que sí participa en el contexto del LLM.\n\n```json\n{\n  \"type\": \"custom_message\",\n  \"id\": \"a2b3c4d5\",\n  \"parentId\": \"f1a2b3c4\",\n  \"timestamp\": \"2026-02-16T10:26:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"content\": \"Injected context\",\n  \"display\": true,\n  \"details\": { \"debug\": false }\n}\n```\n\n### `label`\n\n```json\n{\n  \"type\": \"label\",\n  \"id\": \"b2c3d4e5\",\n  \"parentId\": \"a2b3c4d5\",\n  \"timestamp\": \"2026-02-16T10:27:00.000Z\",\n  \"targetId\": \"a1b2c3d4\",\n  \"label\": \"checkpoint\"\n}\n```\n\n`label: undefined` elimina una etiqueta para `targetId`.\n\n### `ttsr_injection`\n\n```json\n{\n  \"type\": \"ttsr_injection\",\n  \"id\": \"c2d3e4f5\",\n  \"parentId\": \"b2c3d4e5\",\n  \"timestamp\": \"2026-02-16T10:28:00.000Z\",\n  \"injectedRules\": [\"ruleA\", \"ruleB\"]\n}\n```\n\n### `session_init`\n\n```json\n{\n  \"type\": \"session_init\",\n  \"id\": \"d2e3f4a5\",\n  \"parentId\": \"c2d3e4f5\",\n  \"timestamp\": \"2026-02-16T10:29:00.000Z\",\n  \"systemPrompt\": \"...\",\n  \"task\": \"...\",\n  \"tools\": [\"read\", \"edit\"],\n  \"outputSchema\": { \"type\": \"object\" }\n}\n```\n\n### `mode_change`\n\n```json\n{\n  \"type\": \"mode_change\",\n  \"id\": \"e2f3a4b5\",\n  \"parentId\": \"d2e3f4a5\",\n  \"timestamp\": \"2026-02-16T10:30:00.000Z\",\n  \"mode\": \"plan\",\n  \"data\": { \"planFile\": \"/tmp/plan.md\" }\n}\n```\n\n## Versionado y Migración\n\nVersión actual de sesión: `3`.\n\n### v1 -> v2\n\nSe aplica cuando `version` del encabezado está ausente o es `< 2`:\n\n- Añade `id` y `parentId` a cada entrada que no sea encabezado.\n- Reconstruye una cadena de padres lineal usando el orden del archivo.\n- Migra el campo de compactación `firstKeptEntryIndex` -> `firstKeptEntryId` cuando está presente.\n- Establece `version = 2` en el encabezado.\n\n### v2 -> v3\n\nSe aplica cuando `version` del encabezado es `< 3`:\n\n- Para entradas `message`: reescribe el legado `message.role === \"hookMessage\"` a `\"custom\"`.\n- Establece `version = 3` en el encabezado.\n\n### Activación de Migración y Persistencia\n\n- Las migraciones se ejecutan durante la carga de la sesión (`setSessionFile`).\n- Si alguna migración se ejecutó, el archivo completo se reescribe en disco inmediatamente.\n- La migración muta las entradas en memoria primero, luego persiste el JSONL reescrito.\n\n## Comportamiento de Carga y Compatibilidad\n\nComportamiento de `loadEntriesFromFile(path)`:\n\n- Archivo faltante (`ENOENT`) -> retorna `[]`.\n- Las líneas no parseables son manejadas por el parser JSONL tolerante (`parseJsonlLenient`).\n- Si la primera entrada parseada no es un encabezado de sesión válido (`type !== \"session\"` o falta `id` como cadena) -> retorna `[]`.\n\nComportamiento de `SessionManager.setSessionFile()`:\n\n- `[]` del cargador se trata como sesión vacía/inexistente y se reemplaza con un nuevo archivo de sesión inicializado en esa ruta.\n- Los archivos válidos se cargan, migran si es necesario, se resuelven las referencias de blobs y luego se indexan.\n\n## Semántica de Árbol y Hoja\n\nEl modelo subyacente es un árbol de solo escritura + puntero de hoja mutable:\n\n- Cada método de append crea exactamente una nueva entrada cuyo `parentId` es el `leafId` actual.\n- La nueva entrada se convierte en el nuevo `leafId`.\n- `branch(entryId)` mueve solo `leafId`; las entradas existentes permanecen sin cambios.\n- `resetLeaf()` establece `leafId = null`; el siguiente append crea una nueva entrada raíz (`parentId: null`).\n- `branchWithSummary()` establece la hoja en el destino de la rama y añade una entrada `branch_summary`.\n\n`getEntries()` retorna todas las entradas que no son encabezado en orden de inserción. Las entradas existentes no se eliminan en operación normal; las reescrituras preservan el historial lógico mientras actualizan la representación (migraciones, movimiento, ayudantes de reescritura dirigida).\n\n## Reconstrucción de Contexto (`buildSessionContext`)\n\n`buildSessionContext(entries, leafId, byId?)` resuelve lo que se envía al modelo.\n\nAlgoritmo:\n\n1. Determinar la hoja:\n   - `leafId === null` -> retorna contexto vacío.\n   - `leafId` explícito -> usa esa entrada si se encuentra.\n   - de lo contrario, recurre a la última entrada.\n2. Recorre la cadena de `parentId` desde la hoja hasta la raíz e invierte al camino raíz->hoja.\n3. Deriva el estado en tiempo de ejecución a lo largo del camino:\n   - `thinkingLevel` del `thinking_level_change` más reciente (por defecto `\"off\"`)\n   - mapa de modelos de las entradas `model_change` (`role ?? \"default\"`)\n   - `models.default` de respaldo del proveedor/modelo del mensaje del asistente si no hay cambio de modelo explícito\n   - `injectedTtsrRules` deduplicadas de todas las entradas `ttsr_injection`\n   - modo/modeData del `mode_change` más reciente (modo por defecto `\"none\"`)\n4. Construir la lista de mensajes:\n   - Las entradas `message` pasan directamente\n   - Las entradas `custom_message` se convierten en `AgentMessages` de tipo `custom` vía `createCustomMessage`\n   - Las entradas `branch_summary` se convierten en `AgentMessages` de tipo `branchSummary` vía `createBranchSummaryMessage`\n   - Si existe una `compaction` en el camino:\n     - emite primero el resumen de compactación (`createCompactionSummaryMessage`)\n     - emite las entradas del camino comenzando en `firstKeptEntryId` hasta el límite de compactación\n     - emite las entradas después del límite de compactación\n\nLas entradas `custom` y `session_init` no inyectan contexto del modelo directamente.\n\n## Garantías de Persistencia y Modelo de Fallos\n\n### Persistencia vs en memoria\n\n- `SessionManager.create/open/continueRecent/forkFrom` -> modo persistente (`persist = true`).\n- `SessionManager.inMemory` -> modo no persistente (`persist = false`) con `MemorySessionStorage`.\n\n### Pipeline de escritura\n\nLas escrituras se serializan a través de una cadena de promesas interna (`#persistChain`) y `NdjsonFileWriter`.\n\n- `append*` actualiza el estado en memoria inmediatamente.\n- La persistencia se difiere hasta que exista al menos un mensaje del asistente.\n  - Antes del primer asistente: las entradas se retienen en memoria; no se realiza ningún append al archivo.\n  - Cuando existe el primer asistente: toda la sesión en memoria se vuelca al archivo.\n  - Después: las nuevas entradas se añaden incrementalmente.\n\nJustificación en el código: evitar persistir sesiones que nunca produjeron una respuesta del asistente.\n\n### Operaciones de durabilidad\n\n- `flush()` vuelca el escritor y llama a `fsync()`.\n- Las reescrituras completas atómicas (`#rewriteFile`) escriben en un archivo temporal, realizan flush+fsync, cierran y luego renombran sobre el destino.\n- Se usan para migraciones, `setSessionName`, `rewriteEntries`, operaciones de movimiento y reescrituras de argumentos de llamadas a herramientas.\n\n### Comportamiento ante errores\n\n- Los errores de persistencia se enganchan (`#persistError`) y se relanzan en operaciones subsiguientes.\n- El primer error se registra una vez con el contexto del archivo de sesión.\n- El cierre del escritor es de mejor esfuerzo pero propaga el primer error significativo.\n\n## Controles de Tamaño de Datos y Externalización de Blobs\n\nAntes de persistir las entradas:\n\n- Las cadenas grandes se truncan a `MAX_PERSIST_CHARS` (500.000 caracteres) con aviso:\n  - `\"[Session persistence truncated large content]\"`\n- Los campos transitorios `partialJson` y `jsonlEvents` se eliminan.\n- Si el objeto tiene tanto `content` como `lineCount`, el conteo de líneas se recalcula después del truncamiento.\n- Los bloques de imagen en arrays `content` con longitud base64 >= 1024 se externalizan a referencias de blob:\n  - almacenados como `blob:sha256:<hash>`\n  - los bytes crudos se escriben en el almacén de blobs (`BlobStore.put`)\n\nAl cargar, las referencias de blob se resuelven de vuelta a base64 para los bloques de imagen de message/custom_message.\n\n## Abstracciones de Almacenamiento\n\nLa interfaz `SessionStorage` proporciona todas las operaciones de sistema de archivos utilizadas por `SessionManager`:\n\n- síncronas: `ensureDirSync`, `existsSync`, `writeTextSync`, `statSync`, `listFilesSync`\n- asíncronas: `exists`, `readText`, `readTextPrefix`, `writeText`, `rename`, `unlink`, `openWriter`\n\nImplementaciones:\n\n- `FileSessionStorage`: sistema de archivos real (Bun + node fs)\n- `MemorySessionStorage`: implementación en memoria respaldada por mapa para pruebas/sesiones no persistentes\n\n`SessionStorageWriter` expone `writeLine`, `flush`, `fsync`, `close`, `getError`.\n\n## Utilidades de Descubrimiento de Sesiones\n\nDefinidas en `session-manager.ts`:\n\n- `getRecentSessions(sessionDir, limit)` -> metadatos ligeros para la UI/selector de sesiones\n- `findMostRecentSession(sessionDir)` -> la más reciente por mtime\n- `list(cwd, sessionDir?)` -> sesiones en el alcance de un proyecto\n- `listAll()` -> sesiones en todos los alcances de proyecto bajo `~/.xcsh/agent/sessions`\n\nLa extracción de metadatos lee solo un prefijo (`readTextPrefix(..., 4096)`) cuando es posible.\n\n## Relacionado pero Distinto: Almacenamiento de Historial de Prompts\n\n`HistoryStorage` (`history-storage.ts`) es un subsistema SQLite separado para la recuperación/búsqueda de prompts, no para la reproducción de sesiones.\n\n- BD: `~/.xcsh/agent/history.db`\n- Tabla: `history(id, prompt, created_at, cwd)`\n- Índice FTS5: `history_fts` con sincronización mantenida por triggers\n- Deduplica prompts idénticos consecutivos usando una caché en memoria del último prompt\n- Inserción asíncrona (`setImmediate`) para que la captura de prompts no bloquee la ejecución del turno\n\nUse los archivos de sesión para la reproducción del grafo/estado de la conversación; use `HistoryStorage` para la UX del historial de prompts.\n",
	"es/sessions/ttsr-injection-lifecycle.md": "---\ntitle: Ciclo de vida de la inyección TTSR\ndescription: >-\n  Ciclo de vida de la inyección TTSR (tool-use, tool-result, system-reminder)\n  para la gestión de contexto.\nsidebar:\n  order: 9\n  label: Inyección TTSR\ni18n:\n  sourceHash: d6179a286584\n  translator: machine\n---\n\n# Ciclo de vida de la inyección TTSR\n\nEste documento cubre la ruta de ejecución actual de las Time Traveling Stream Rules (TTSR), desde el descubrimiento de reglas hasta la interrupción del flujo, la inyección de reintentos, las notificaciones de extensiones y el manejo del estado de sesión.\n\n## Archivos de implementación\n\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/export/ttsr.ts`](../../packages/coding-agent/src/export/ttsr.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/prompts/system/ttsr-interrupt.md`](../../packages/coding-agent/src/prompts/system/ttsr-interrupt.md)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/types.ts`](../../packages/coding-agent/src/extensibility/extensions/types.ts)\n- [`../src/extensibility/hooks/types.ts`](../../packages/coding-agent/src/extensibility/hooks/types.ts)\n- [`../src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n\n## 1. Alimentación de descubrimiento y registro de reglas\n\nAl crear la sesión, `createAgentSession()` carga todas las reglas descubiertas y construye un `TtsrManager`:\n\n```ts\nconst ttsrSettings = settings.getGroup(\"ttsr\");\nconst ttsrManager = new TtsrManager(ttsrSettings);\nconst rulesResult = await loadCapability<Rule>(ruleCapability.id, { cwd });\nfor (const rule of rulesResult.items) {\n  if (rule.ttsrTrigger) ttsrManager.addRule(rule);\n}\n```\n\n### Comportamiento de deduplicación previo al registro\n\n`loadCapability(\"rules\")` deduplica por `rule.name` con semántica de primera coincidencia gana (mayor prioridad de proveedor primero). Los duplicados sombreados se eliminan antes del registro TTSR.\n\n### Comportamiento de `TtsrManager.addRule()`\n\nEl registro se omite cuando:\n\n- `rule.ttsrTrigger` está ausente\n- una regla con el mismo `rule.name` ya fue registrada en este manager\n- la expresión regular falla al compilar (`new RegExp(rule.ttsrTrigger)` lanza una excepción)\n\nLos triggers con expresiones regulares inválidas se registran como advertencias y se ignoran; el inicio de sesión continúa.\n\n### Advertencia sobre configuración\n\n`TtsrSettings.enabled` se carga en el manager pero actualmente no se verifica en el control de ejecución. Si existen reglas, la coincidencia se ejecuta de todos modos.\n\n## 2. Ciclo de vida del monitor de flujo\n\nLa detección TTSR se ejecuta dentro de `AgentSession.#handleAgentEvent`.\n\n### Inicio de turno\n\nEn `turn_start`, el buffer del flujo se reinicia:\n\n- `ttsrManager.resetBuffer()`\n\n### Durante el flujo (`message_update`)\n\nCuando llegan actualizaciones del asistente y existen reglas:\n\n- se monitorean `text_delta` y `toolcall_delta`\n- se añade el delta al buffer del manager\n- se llama a `check(buffer)`\n\n`check()` itera las reglas registradas y devuelve todas las reglas coincidentes que pasan la política de repetición (`#canTrigger`).\n\n## 3. Decisión de activación y ruta de cancelación inmediata\n\nCuando una o más reglas coinciden:\n\n1. `markInjected(matches)` registra los nombres de las reglas en el estado de inyección del manager.\n2. las reglas coincidentes se ponen en cola en `#pendingTtsrInjections`.\n3. `#ttsrAbortPending = true`.\n4. `agent.abort()` se llama inmediatamente.\n5. el evento `ttsr_triggered` se emite de forma asíncrona (fire-and-forget).\n6. el trabajo de reintento se programa mediante `setTimeout(..., 50)`.\n\nLa cancelación no se bloquea esperando los callbacks de extensiones.\n\n## 4. Programación de reintentos, modo de contexto e inyección de recordatorio\n\nDespués del timeout de 50ms:\n\n1. `#ttsrAbortPending = false`\n2. se lee `ttsrManager.getSettings().contextMode`\n3. si `contextMode === \"discard\"`, se descarta la salida parcial del asistente con `agent.popMessage()`\n4. se construye el contenido de inyección a partir de las reglas pendientes usando la plantilla `ttsr-interrupt.md`\n5. se añade un mensaje sintético del usuario que contiene un bloque `<system-interrupt ...>` por cada regla\n6. se llama a `agent.continue()` para reintentar la generación\n\nEl payload de la plantilla es:\n\n```xml\n<system-interrupt reason=\"rule_violation\" rule=\"{{name}}\" path=\"{{path}}\">\n...\n{{content}}\n</system-interrupt>\n```\n\nLas inyecciones pendientes se limpian después de la generación del contenido.\n\n### Comportamiento de `contextMode` sobre la salida parcial\n\n- `discard`: el mensaje parcial/cancelado del asistente se elimina antes del reintento.\n- `keep`: la salida parcial del asistente permanece en el estado de la conversación; el recordatorio se añade después de ella.\n\n## 5. Política de repetición y lógica de intervalo\n\n`TtsrManager` rastrea `#messageCount` y `lastInjectedAt` por regla.\n\n### `repeatMode: \"once\"`\n\nUna regla solo puede activarse una vez después de tener un registro de inyección.\n\n### `repeatMode: \"after-gap\"`\n\nUna regla puede reactivarse solo cuando:\n\n- `messageCount - lastInjectedAt >= repeatGap`\n\n`messageCount` se incrementa en `turn_end`, por lo que el intervalo se mide en turnos completados, no en fragmentos del flujo.\n\n## 6. Emisión de eventos y superficies de extensiones/hooks\n\n### Evento de sesión\n\n`AgentSessionEvent` incluye:\n\n```ts\n{ type: \"ttsr_triggered\"; rules: Rule[] }\n```\n\n### Ejecutor de extensiones\n\n`#emitSessionEvent()` enruta el evento a:\n\n- listeners de extensiones (`ExtensionRunner.emit({ type: \"ttsr_triggered\", rules })`)\n- suscriptores locales de sesión\n\n### Tipado de hooks y herramientas personalizadas\n\n- la API de extensiones expone `on(\"ttsr_triggered\", ...)`\n- la API de hooks expone `on(\"ttsr_triggered\", ...)`\n- las herramientas personalizadas reciben `onSession({ reason: \"ttsr_triggered\", rules })`\n\n### Diferencia de renderizado en modo interactivo\n\nEl modo interactivo usa `session.isTtsrAbortPending` para suprimir la visualización de la razón de detención del asistente cancelado como un fallo visible durante la interrupción TTSR, y renderiza un `TtsrNotificationComponent` cuando llega el evento.\n\n## 7. Persistencia y estado de reanudación (implementación actual)\n\n`SessionManager` tiene soporte completo de esquema para la persistencia de reglas inyectadas:\n\n- tipo de entrada: `ttsr_injection`\n- API de adición: `appendTtsrInjection(ruleNames)`\n- API de consulta: `getInjectedTtsrRules()`\n- la reconstrucción de contexto incluye `SessionContext.injectedTtsrRules`\n\n`TtsrManager` también soporta restauración mediante `restoreInjected(ruleNames)`.\n\n### Estado actual del cableado\n\nEn la ruta de ejecución actual:\n\n- `AgentSession` no añade entradas `ttsr_injection` cuando se activa TTSR.\n- `createAgentSession()` no restaura `existingSession.injectedTtsrRules` de vuelta al `ttsrManager`.\n\nEfecto neto: la supresión de reglas inyectadas se aplica en memoria para el proceso activo, pero actualmente no se persiste/restaura entre recargas/reanudaciones de sesión a través de esta ruta.\n\n## 8. Límites de condiciones de carrera y garantías de ordenamiento\n\n### Cancelación vs callback de reintento\n\n- la cancelación es síncrona desde la perspectiva del handler TTSR (`agent.abort()` se llama inmediatamente)\n- el reintento se difiere mediante temporizador (`50ms`)\n- la notificación a extensiones es asíncrona e intencionalmente no se espera antes de la programación de cancelación/reintento\n\n### Múltiples coincidencias en la misma ventana de flujo\n\n`check()` devuelve todas las reglas elegibles que coinciden actualmente. Se inyectan como un lote en el siguiente mensaje de reintento.\n\n### Entre cancelación y continuación\n\nDurante la ventana del temporizador, el estado puede cambiar (interrupción del usuario, acciones de modo, eventos adicionales). La llamada de reintento es de mejor esfuerzo: `agent.continue().catch(() => {})` absorbe los errores subsecuentes.\n\n## 9. Resumen de casos límite\n\n- Expresión regular `ttsr_trigger` inválida: se omite con advertencia; las demás reglas continúan.\n- Nombres de reglas duplicados en la capa de capacidades: los duplicados de menor prioridad se sombrean antes del registro.\n- Nombres duplicados en la capa del manager: el segundo registro se ignora.\n- `contextMode: \"keep\"`: la salida parcial que viola las reglas puede permanecer en el contexto antes del reintento con recordatorio.\n- El intervalo de repetición (repeat-after-gap) depende de los incrementos del contador de turnos en `turn_end`; los fragmentos a mitad de turno no avanzan los contadores de intervalo.\n",
	"es/tui/theme.md": "---\ntitle: Referencia de Temas\ndescription: >-\n  Referencia de temas de TUI con tokens de color, configuración de fuentes y\n  personalización de temas.\nsidebar:\n  order: 3\n  label: Temas\ni18n:\n  sourceHash: 7e962a7da157\n  translator: machine\n---\n\n# Referencia de Temas\n\nEste documento describe cómo funciona el sistema de temas en el coding-agent actualmente: esquema, carga, comportamiento en tiempo de ejecución y modos de fallo.\n\n## Qué controla el sistema de temas\n\nEl sistema de temas gestiona:\n\n- tokens de color de primer plano/fondo utilizados en toda la TUI\n- adaptadores de estilos de markdown (`getMarkdownTheme()`)\n- adaptadores de selector/editor/lista de configuración (`getSelectListTheme()`, `getEditorTheme()`, `getSettingsListTheme()`)\n- preset de símbolos + sobrecargas de símbolos (`unicode`, `nerd`, `ascii`)\n- colores de resaltado de sintaxis utilizados por el resaltador nativo (`@f5-sales-demo/pi-natives`)\n- colores de los segmentos de la línea de estado\n\nImplementación principal: `src/modes/theme/theme.ts`.\n\n## Estructura JSON del tema\n\nLos archivos de tema son objetos JSON validados contra el esquema en tiempo de ejecución en `theme.ts` (`ThemeJsonSchema`) y reflejados en `src/modes/theme/theme-schema.json`.\n\nCampos de nivel superior:\n\n- `name` (requerido)\n- `colors` (requerido; todos los tokens de color son requeridos)\n- `vars` (opcional; variables de color reutilizables)\n- `export` (opcional; colores para exportación HTML)\n- `symbols` (opcional)\n  - `preset` (opcional: `unicode | nerd | ascii`)\n  - `overrides` (opcional: sobrecargas clave/valor para `SymbolKey`)\n\nLos valores de color aceptan:\n\n- cadena hexadecimal (`\"#RRGGBB\"`)\n- índice de color de 256 colores (`0..255`)\n- cadena de referencia a variable (resuelta a través de `vars`)\n- cadena vacía (`\"\"`) que significa valor predeterminado del terminal (`\\x1b[39m` fg, `\\x1b[49m` bg)\n\n## Tokens de color requeridos (actuales)\n\nTodos los tokens a continuación son requeridos en `colors`.\n\n### Texto y bordes principales (11)\n\n`accent`, `border`, `borderAccent`, `borderMuted`, `success`, `error`, `warning`, `muted`, `dim`, `text`, `thinkingText`\n\n### Bloques de fondo (7)\n\n`selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`, `statusLineBg`\n\n### Texto de mensajes/herramientas (5)\n\n`userMessageText`, `customMessageText`, `customMessageLabel`, `toolTitle`, `toolOutput`\n\n### Markdown (10)\n\n`mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet`\n\n### Diff de herramientas + resaltado de sintaxis (12)\n\n`toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext`,\n`syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation`\n\n### Bordes de modo/pensamiento (8)\n\n`thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh`, `bashMode`, `pythonMode`\n\n### Colores de segmentos de la línea de estado (14)\n\n`statusLineSep`, `statusLineModel`, `statusLinePath`, `statusLineGitClean`, `statusLineGitDirty`, `statusLineContext`, `statusLineSpend`, `statusLineStaged`, `statusLineDirty`, `statusLineUntracked`, `statusLineOutput`, `statusLineCost`, `statusLineSubagents`\n\n## Tokens opcionales\n\n### Sección `export` (opcional)\n\nUtilizada para los helpers de temas en exportación HTML:\n\n- `export.pageBg`\n- `export.cardBg`\n- `export.infoBg`\n\nSi se omite, el código de exportación deriva valores predeterminados de los colores del tema resueltos.\n\n### Sección `symbols` (opcional)\n\n- `symbols.preset` establece un conjunto de símbolos predeterminado a nivel de tema.\n- `symbols.overrides` puede sobrecargar valores individuales de `SymbolKey`.\n\nPrecedencia en tiempo de ejecución:\n\n1. sobrecarga de `symbolPreset` en configuración (si está establecida)\n2. `symbols.preset` del JSON del tema\n3. respaldo `\"unicode\"`\n\nLas claves de sobrecarga inválidas se ignoran y se registran (`logger.debug`).\n\n## Fuentes de temas integrados vs personalizados\n\nOrden de búsqueda de temas (`loadThemeJson`):\n\n1. temas integrados embebidos (`defaults/xcsh-dark.json` y `defaults/xcsh-light.json` compilados en `defaultThemes`)\n2. archivo de tema personalizado: `<customThemesDir>/<name>.json`\n\nEl directorio de temas personalizados proviene de `getCustomThemesDir()`:\n\n- predeterminado: `~/.xcsh/agent/themes`\n- sobreescrito por `PI_CODING_AGENT_DIR` (`$PI_CODING_AGENT_DIR/themes`)\n\n`getAvailableThemes()` devuelve los nombres integrados + personalizados fusionados, ordenados, con los integrados teniendo precedencia en caso de colisión de nombres.\n\n## Carga, validación y resolución\n\nPara archivos de temas personalizados:\n\n1. leer JSON\n2. parsear JSON\n3. validar contra `ThemeJsonSchema`\n4. resolver referencias de `vars` recursivamente\n5. convertir valores resueltos a ANSI según el modo de capacidad del terminal\n\nComportamiento de validación:\n\n- tokens de color requeridos faltantes: mensaje de error agrupado explícito\n- tipos/valores de tokens incorrectos: errores de validación con ruta JSON\n- archivo de tema desconocido: `Theme not found: <name>`\n\nComportamiento de referencia a variables:\n\n- soporta referencias anidadas\n- lanza error en referencia a variable faltante\n- lanza error en referencias circulares\n\n## Comportamiento del modo de color del terminal\n\nDetección del modo de color (`detectColorMode`):\n\n- `COLORTERM=truecolor|24bit` => truecolor\n- `WT_SESSION` => truecolor\n- `TERM` en `dumb`, `linux`, o vacío => 256color\n- de lo contrario => truecolor\n\nComportamiento de conversión:\n\n- hex -> `Bun.color(..., \"ansi-16m\" | \"ansi-256\")`\n- numérico -> ANSI `38;5` / `48;5`\n- `\"\"` -> reset de fg/bg predeterminado\n\n## Comportamiento de cambio en tiempo de ejecución\n\n### Tema inicial (`initTheme`)\n\n`main.ts` inicializa el tema con la configuración:\n\n- `symbolPreset`\n- `colorBlindMode`\n- `theme.dark`\n- `theme.light`\n\nLa selección automática de ranura de tema utiliza la detección de fondo de `COLORFGBG`:\n\n- parsear el índice de fondo de `COLORFGBG`\n- `< 8` => ranura oscura (`theme.dark`)\n- `>= 8` => ranura clara (`theme.light`)\n- fallo en el parseo => ranura oscura\n\nValores predeterminados actuales del esquema de configuración:\n\n- `theme.dark = \"xcsh-dark\"`\n- `theme.light = \"xcsh-light\"`\n- `symbolPreset = \"unicode\"`\n- `colorBlindMode = false`\n\n### Cambio explícito (`setTheme`)\n\n- carga el tema seleccionado\n- actualiza el singleton global `theme`\n- opcionalmente inicia el watcher\n- dispara el callback `onThemeChange`\n\nEn caso de fallo:\n\n- recurre al tema integrado `dark`\n- devuelve `{ success: false, error }`\n\n### Cambio de vista previa (`previewTheme`)\n\n- aplica un tema de vista previa temporal al `theme` global\n- **no** cambia la configuración persistida por sí mismo\n- devuelve éxito/error sin reemplazo de respaldo\n\nLa UI de configuración utiliza esto para vista previa en vivo y restaura el tema anterior al cancelar.\n\n## Watchers y recarga en vivo\n\nCuando el watcher está habilitado (`setTheme(..., true)` / inicialización interactiva):\n\n- solo vigila la ruta del archivo personalizado `<customThemesDir>/<currentTheme>.json`\n- los integrados efectivamente no se vigilan\n- archivo `change`: intenta recargar (con debounce)\n- archivo `rename`/eliminado: recurre a `dark`, cierra el watcher\n\nEl modo automático también instala un listener `SIGWINCH` y puede reevaluar el mapeo de ranura oscura/clara cuando el estado del terminal cambia.\n\n## Comportamiento del modo para daltonismo\n\n`colorBlindMode` cambia solo un token en tiempo de ejecución:\n\n- `toolDiffAdded` se ajusta en HSV (verde desplazado hacia azul)\n- el ajuste se aplica solo cuando el valor resuelto es una cadena hexadecimal\n\nLos demás tokens no se modifican.\n\n## Dónde se persiste la configuración de temas\n\nLa configuración relacionada con temas se persiste mediante `Settings` en el archivo YAML de configuración global:\n\n- ruta: `<agentDir>/config.yml`\n- directorio de agente predeterminado: `~/.xcsh/agent`\n- archivo predeterminado efectivo: `~/.xcsh/agent/config.yml`\n\nClaves persistidas:\n\n- `theme.dark`\n- `theme.light`\n- `symbolPreset`\n- `colorBlindMode`\n\nExiste migración de legado: el antiguo `theme: \"name\"` plano se migra a `theme.dark` o `theme.light` anidado basándose en la detección de luminancia.\n\n## Creación de un tema personalizado (práctico)\n\n1. Crear un archivo en el directorio de temas personalizados, p. ej. `~/.xcsh/agent/themes/my-theme.json`.\n2. Incluir `name`, `vars` opcional, y **todos los** tokens de `colors` requeridos.\n3. Opcionalmente incluir `symbols` y `export`.\n4. Seleccionar el tema en Configuración (`Display -> Dark theme` o `Display -> Light theme`) dependiendo de la ranura automática que desee.\n\nEsqueleto mínimo. Cada clave en `colors` es requerida — el validador en tiempo de ejecución\n(`additionalProperties: false`) rechaza tanto claves faltantes como claves desconocidas.\nPara las implementaciones de referencia incluidas, consulte\n[`packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json)\ny [`xcsh-light.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-light.json).\n\nLa línea de estado tiene dos sistemas de color paralelos documentados en el issue #242:\n\n- Colores de texto hexadecimales (`statusLinePath`, `statusLineGitClean`, `statusLineGitDirty`,\n  `statusLineStaged`, `statusLineDirty`, `statusLineUntracked`) controlan el\n  renderizado sin powerline.\n- Índices de paleta de 256 colores (`statusLine<Segment>Bg` / `statusLine<Segment>Fg`)\n  controlan el relleno de segmentos powerline. Son independientes de las claves hexadecimales anteriores —\n  ambos deben configurarse.\n\n```json\n{\n  \"name\": \"my-theme\",\n  \"vars\": {\n    \"accent\": \"#7aa2f7\",\n    \"muted\": 244\n  },\n  \"colors\": {\n    \"accent\": \"accent\",\n    \"chromeAccent\": \"accent\",\n    \"spinnerAccent\": \"accent\",\n    \"contentAccent\": \"muted\",\n    \"border\": \"#4c566a\",\n    \"borderAccent\": \"accent\",\n    \"borderMuted\": \"muted\",\n    \"success\": \"#9ece6a\",\n    \"error\": \"#f7768e\",\n    \"warning\": \"#e0af68\",\n    \"muted\": \"muted\",\n    \"dim\": 240,\n    \"gutterSuccess\": \"#7dcfff\",\n    \"gutterWarning\": \"#e0af68\",\n    \"text\": \"\",\n    \"thinkingText\": \"muted\",\n\n    \"selectedBg\": \"#2a2f45\",\n    \"userMessageBg\": \"#1f2335\",\n    \"userMessageText\": \"\",\n    \"customMessageBg\": \"#24283b\",\n    \"customMessageText\": \"\",\n    \"customMessageLabel\": \"accent\",\n    \"toolPendingBg\": \"#1f2335\",\n    \"toolSuccessBg\": \"#1f2d2a\",\n    \"toolErrorBg\": \"#2d1f2a\",\n    \"toolTitle\": \"\",\n    \"toolOutput\": \"muted\",\n\n    \"mdHeading\": \"accent\",\n    \"mdLink\": \"accent\",\n    \"mdLinkUrl\": \"muted\",\n    \"mdCode\": \"#c0caf5\",\n    \"mdCodeBlock\": \"#c0caf5\",\n    \"mdCodeBlockBorder\": \"muted\",\n    \"mdQuote\": \"muted\",\n    \"mdQuoteBorder\": \"muted\",\n    \"mdHr\": \"muted\",\n    \"mdListBullet\": \"accent\",\n\n    \"toolDiffAdded\": \"#9ece6a\",\n    \"toolDiffRemoved\": \"#f7768e\",\n    \"toolDiffContext\": \"muted\",\n\n    \"syntaxComment\": \"#565f89\",\n    \"syntaxKeyword\": \"#bb9af7\",\n    \"syntaxFunction\": \"#7aa2f7\",\n    \"syntaxVariable\": \"#c0caf5\",\n    \"syntaxString\": \"#9ece6a\",\n    \"syntaxNumber\": \"#ff9e64\",\n    \"syntaxType\": \"#2ac3de\",\n    \"syntaxOperator\": \"#89ddff\",\n    \"syntaxPunctuation\": \"#9aa5ce\",\n    \"syntaxControl\": \"#bb9af7\",\n\n    \"thinkingOff\": 240,\n    \"thinkingMinimal\": 244,\n    \"thinkingLow\": \"#7aa2f7\",\n    \"thinkingMedium\": \"#2ac3de\",\n    \"thinkingHigh\": \"#bb9af7\",\n    \"thinkingXhigh\": \"#f7768e\",\n\n    \"bashMode\": \"#2ac3de\",\n    \"pythonMode\": \"#bb9af7\",\n\n    \"statusLineBg\": \"#16161e\",\n    \"statusLineSep\": 240,\n    \"statusLineModel\": \"#bb9af7\",\n    \"statusLinePath\": \"#7aa2f7\",\n    \"statusLineGitClean\": \"#9ece6a\",\n    \"statusLineGitDirty\": \"#e0af68\",\n    \"statusLineContext\": \"#2ac3de\",\n    \"statusLineSpend\": \"#7dcfff\",\n    \"statusLineStaged\": \"#9ece6a\",\n    \"statusLineDirty\": \"#e0af68\",\n    \"statusLineUntracked\": \"#f7768e\",\n    \"statusLineOutput\": \"#c0caf5\",\n    \"statusLineCost\": \"#ff9e64\",\n    \"statusLineSubagents\": \"#bb9af7\",\n\n    \"statusLineOsIconBg\": 7,\n    \"statusLineOsIconFg\": 232,\n    \"statusLinePathBg\": 4,\n    \"statusLinePathFg\": 254,\n    \"statusLineGitCleanBg\": 2,\n    \"statusLineGitCleanFg\": 0,\n    \"statusLineGitDirtyBg\": 3,\n    \"statusLineGitDirtyFg\": 0,\n    \"statusLineGitStagedBg\": 64,\n    \"statusLineGitStagedFg\": 0,\n    \"statusLineGitUntrackedBg\": 39,\n    \"statusLineGitUntrackedFg\": 0,\n    \"statusLineGitConflictBg\": 1,\n    \"statusLineGitConflictFg\": 7,\n    \"statusLinePlanModeBg\": 236,\n    \"statusLinePlanModeFg\": 117,\n    \"statusLineProfileXcshBg\": \"accent\",\n    \"statusLineProfileXcshFg\": 231\n  }\n}\n```\n\n## Pruebas de temas personalizados\n\nUtilice este flujo de trabajo:\n\n1. Iniciar el modo interactivo (watcher habilitado desde el arranque).\n2. Abrir configuración y previsualizar los valores del tema (`previewTheme` en vivo).\n3. Para archivos de temas personalizados, editar el JSON mientras se ejecuta y confirmar la recarga automática al guardar.\n4. Ejercitar las superficies críticas:\n   - renderizado de markdown\n   - bloques de herramientas (pendiente/éxito/error)\n   - renderizado de diff (añadido/eliminado/contexto)\n   - legibilidad de la línea de estado\n   - cambios de borde por nivel de pensamiento\n   - colores de borde del modo bash/python\n5. Validar ambos presets de símbolos si su tema depende del ancho/apariencia de los glifos.\n\n## Restricciones reales y advertencias\n\n- Todos los tokens de `colors` son requeridos para temas personalizados.\n- `export` y `symbols` son opcionales.\n- `$schema` en el JSON del tema es informativo; la validación en tiempo de ejecución es aplicada por el esquema TypeBox compilado en el código.\n- El fallo de `setTheme` recurre a `dark`; el fallo de `previewTheme` no reemplaza el tema actual.\n- Los errores de recarga del watcher de archivos mantienen el tema cargado actualmente hasta que una recarga exitosa o una ruta de respaldo sea activada.\n",
	"es/tui/tree.md": "---\ntitle: Referencia del comando Tree\ndescription: >-\n  Referencia del comando /tree para visualizar el historial de sesiones y las\n  ramas de conversación.\nsidebar:\n  order: 4\n  label: Comando /tree\ni18n:\n  sourceHash: ee0e412fe993\n  translator: machine\n---\n\n# Referencia del comando `/tree`\n\n`/tree` abre el navegador interactivo del **Árbol de Sesión**. Le permite saltar a cualquier entrada en el archivo de sesión actual y continuar desde ese punto.\n\nSe trata de un movimiento de hoja dentro del archivo, no una exportación a nueva sesión.\n\n## Qué hace `/tree`\n\n- Construye un árbol a partir de las entradas de la sesión actual (`SessionManager.getTree()`)\n- Abre `TreeSelectorComponent` con navegación por teclado, filtros y búsqueda\n- Al seleccionar, llama a `AgentSession.navigateTree(targetId, { summarize, customInstructions })`\n- Reconstruye el chat visible desde la nueva ruta de hoja\n- Opcionalmente prerellena el texto del editor al seleccionar un mensaje de usuario/personalizado\n\nImplementación principal:\n\n- `src/modes/controllers/input-controller.ts` (`/tree`, vinculación de atajos de teclado, comportamiento de doble escape)\n- `src/modes/controllers/selector-controller.ts` (lanzamiento de la interfaz del árbol + flujo de solicitud de resumen)\n- `src/modes/components/tree-selector.ts` (navegación, filtros, búsqueda, etiquetas, renderizado)\n- `src/session/agent-session.ts` (`navigateTree` cambio de hoja + resumen opcional)\n- `src/session/session-manager.ts` (`getTree`, `branch`, `branchWithSummary`, `resetLeaf`, persistencia de etiquetas)\n\n## Cómo abrirlo\n\nCualquiera de las siguientes opciones abre el mismo selector:\n\n- `/tree`\n- acción de atajo de teclado configurada `tree`\n- doble escape con el editor vacío cuando `doubleEscapeAction = \"tree\"` (por defecto)\n- `/branch` cuando `doubleEscapeAction = \"tree\"` (redirige al selector de árbol en lugar del selector de ramas solo de usuario)\n\n## Modelo de interfaz del árbol\n\nEl árbol se renderiza a partir de los punteros de padre de las entradas de sesión (`id` / `parentId`).\n\n- Los hijos se ordenan por marca de tiempo ascendente (los más antiguos primero, los más recientes abajo)\n- La rama activa (ruta desde la raíz hasta la hoja actual) se marca con una viñeta\n- Las etiquetas (si están presentes) se muestran como `[etiqueta]` antes del texto del nodo\n- Si existen múltiples raíces (cadenas de padres huérfanas/rotas), se muestran bajo una raíz virtual de ramificación\n\n```text\nEjemplo de vista de árbol (ruta activa marcada con •):\n\n├─ user: \"Start task\"\n│  └─ assistant: \"Plan\"\n│     ├─ • user: \"Try approach A\"\n│     │  └─ • assistant: \"A result\"\n│     │     └─ • [milestone] user: \"Continue A\"\n│     └─ user: \"Try approach B\"\n│        └─ assistant: \"B result\"\n```\n\nEl selector se recentra alrededor de la selección actual y muestra hasta:\n\n- `max(5, floor(terminalHeight / 2))` filas\n\n## Atajos de teclado dentro del selector de árbol\n\n- `Up` / `Down`: mover selección (cíclico)\n- `Left` / `Right`: página arriba / página abajo\n- `Enter`: seleccionar nodo\n- `Esc`: limpiar búsqueda si está activa; de lo contrario cerrar selector\n- `Ctrl+C`: cerrar selector\n- `Type`: añadir a la consulta de búsqueda\n- `Backspace`: eliminar carácter de búsqueda\n- `Shift+L`: editar/limpiar etiqueta en la entrada seleccionada\n- `Ctrl+O`: ciclar filtro hacia adelante\n- `Shift+Ctrl+O`: ciclar filtro hacia atrás\n- `Alt+D/T/U/L/A`: saltar directamente a un modo de filtro específico\n\n## Semántica de filtros y búsqueda\n\nModos de filtro (`TreeList`):\n\n1. `default`\n2. `no-tools`\n3. `user-only`\n4. `labeled-only`\n5. `all`\n\n### `default`\n\nMuestra la mayoría de los nodos conversacionales, pero oculta los tipos de entrada de gestión interna:\n\n- `label`\n- `custom`\n- `model_change`\n- `thinking_level_change`\n\n### `no-tools`\n\nIgual que `default`, además oculta los mensajes `toolResult`.\n\n### `user-only`\n\nSolo entradas de tipo `message` donde el rol es `user`.\n\n### `labeled-only`\n\nSolo entradas que actualmente resuelven a una etiqueta.\n\n### `all`\n\nTodo en el árbol de sesión, incluyendo entradas de gestión interna/personalizadas.\n\n### Comportamiento de nodos de asistente solo con herramientas\n\nLos mensajes del asistente que contienen **solo llamadas a herramientas** (sin texto) están ocultos por defecto en todas las vistas filtradas a menos que:\n\n- el mensaje sea de error/abortado (`stopReason` no sea `stop`/`toolUse`), o\n- sea la hoja actual (siempre se mantiene visible)\n\n### Comportamiento de búsqueda\n\n- La consulta se tokeniza por espacios\n- La coincidencia no distingue mayúsculas de minúsculas\n- Todos los tokens deben coincidir (semántica AND)\n- El texto buscable incluye etiqueta, rol y contenido específico del tipo (texto del mensaje, texto de resumen de rama, tipo personalizado, fragmentos de comandos de herramientas, etc.)\n\n## Resultados de la selección (importante)\n\n`navigateTree` calcula el nuevo comportamiento de hoja a partir del tipo de entrada seleccionada:\n\n### Seleccionar un mensaje de `user`\n\n- La nueva hoja se convierte en el `parentId` de la entrada seleccionada\n- Si el padre es `null` (mensaje de usuario raíz), la hoja se reinicia a la raíz (`resetLeaf()`)\n- El texto del mensaje seleccionado se copia al editor para editar/reenviar\n\n### Seleccionar un `custom_message`\n\n- Misma regla de hoja que los mensajes de usuario (`parentId`)\n- El contenido de texto se extrae y se copia al editor\n\n### Seleccionar un nodo que no es de usuario (asistente/herramienta/resumen/compactación/gestión interna personalizada/etc.)\n\n- La nueva hoja se convierte en el id del nodo seleccionado\n- El editor no se prerellena\n\n### Seleccionar la hoja actual\n\n- Sin operación; el selector se cierra con \"Already at this point\"\n\n```text\nDecisión de selección (simplificada):\n\nselected node\n   │\n   ├─ is current leaf? ── yes ──> close selector (no-op)\n   │\n   ├─ is user/custom_message? ── yes ──> leaf := parentId (or resetLeaf for root)\n   │                                     + prefill editor text\n   │\n   └─ otherwise ──> leaf := selected node id\n                    + no editor prefill\n```\n\n## Flujo de resumen al cambiar\n\nLa solicitud de resumen se controla mediante `branchSummary.enabled` (por defecto: `false`).\n\nCuando está habilitado, después de seleccionar un nodo la interfaz pregunta:\n\n- `No summary`\n- `Summarize`\n- `Summarize with custom prompt`\n\nDetalles del flujo:\n\n- Escape en la solicitud de resumen reabre el selector de árbol\n- La cancelación de la solicitud personalizada retorna al bucle de elección de resumen\n- Durante la generación del resumen, la interfaz muestra un indicador de carga y vincula `Esc` a `abortBranchSummary()`\n- Si el resumen se aborta, el selector de árbol se reabre y no se aplica ningún movimiento\n\nInternos de `navigateTree`:\n\n- Recopila las entradas de la rama abandonada desde la hoja antigua hasta el ancestro común\n- Emite `session_before_tree` (las extensiones pueden cancelar o inyectar resumen)\n- Usa el resumidor por defecto solo si se solicita y es necesario\n- Aplica el movimiento con:\n  - `branchWithSummary(...)` cuando existe resumen\n  - `branch(newLeafId)` para movimiento no raíz sin resumen\n  - `resetLeaf()` para movimiento a la raíz sin resumen\n- Reemplaza la conversación del agente con el contexto de sesión reconstruido\n- Emite `session_tree`\n\nNota: si el usuario solicita un resumen pero no hay nada que resumir, la navegación procede sin crear una entrada de resumen.\n\n## Etiquetas\n\nLas ediciones de etiquetas en la interfaz del árbol llaman a `appendLabelChange(targetId, label)`.\n\n- Una etiqueta no vacía establece/actualiza la etiqueta resuelta\n- Una etiqueta vacía la elimina\n- Las etiquetas se almacenan como entradas `label` de solo adición\n- Los nodos del árbol muestran el estado de etiqueta resuelto, no el historial sin procesar de entradas de etiqueta\n\n## `/tree` vs operaciones adyacentes\n\n| Operación | Alcance | Resultado |\n|---|---|---|\n| `/tree` | Archivo de sesión actual | Mueve la hoja al punto seleccionado (mismo archivo) |\n| `/branch` | Generalmente archivo de sesión actual -> nuevo archivo de sesión | Por defecto ramifica desde el mensaje de **usuario** seleccionado a un nuevo archivo de sesión; si `doubleEscapeAction = \"tree\"`, `/branch` abre la interfaz de navegación de árbol en su lugar |\n| `/fork` | Toda la sesión actual | Duplica la sesión en un nuevo archivo de sesión persistido |\n| `/resume` | Lista de sesiones | Cambia a otro archivo de sesión |\n\nDistinción clave: `/tree` es una herramienta de navegación/reposicionamiento dentro de un archivo de sesión. `/branch`, `/fork` y `/resume` cambian el contexto del archivo de sesión.\n\n## Flujos de trabajo del operador\n\n### Re-ejecutar desde una solicitud de usuario anterior sin perder la rama actual\n\n1. `/tree`\n2. buscar/seleccionar mensaje de usuario anterior\n3. elegir `No summary` (o resumir si es necesario)\n4. editar el texto prerrellenado en el editor\n5. enviar\n\nEfecto: una nueva rama crece desde el punto seleccionado dentro del mismo archivo de sesión.\n\n### Abandonar la rama actual con una referencia de contexto\n\n1. habilitar `branchSummary.enabled`\n2. `/tree` y seleccionar el nodo destino\n3. elegir `Summarize` (o solicitud personalizada)\n\nEfecto: se añade una entrada `branch_summary` en la posición destino antes de continuar.\n\n### Investigar entradas de gestión interna ocultas\n\n1. `/tree`\n2. presionar `Alt+A` (all)\n3. buscar `model`, `thinking`, `custom`, o etiquetas\n\nEfecto: inspeccionar la línea de tiempo interna completa, no solo los nodos conversacionales.\n\n### Marcar puntos de pivote para saltos posteriores\n\n1. `/tree`\n2. moverse a la entrada\n3. `Shift+L` y establecer etiqueta\n4. posteriormente usar `Alt+L` (`labeled-only`) para saltar rápidamente\n\nEfecto: navegación rápida entre puntos de referencia duraderos de ramas.\n",
	"es/tui/tui-runtime-internals.md": "---\ntitle: Componentes internos del runtime TUI\ndescription: >-\n  Componentes internos del runtime de la interfaz de usuario de terminal, que\n  cubren la canalización de renderizado, el manejo de entrada y la gestión de\n  estado.\nsidebar:\n  order: 2\n  label: Componentes internos del runtime\ni18n:\n  sourceHash: cc8f7dcce46a\n  translator: machine\n---\n\n# Componentes internos del runtime TUI\n\nEste documento describe la ruta de runtime sin tema desde la entrada de terminal hasta la salida renderizada en modo interactivo. Se centra en el comportamiento de `packages/tui` y su integración desde los controladores de `packages/coding-agent`.\n\n## Capas del runtime y responsabilidades\n\n- **Motor `packages/tui`**: ciclo de vida del terminal, normalización de stdin, enrutamiento de foco, programación de renderizado, pintura diferencial, composición de superposiciones, posicionamiento del cursor de hardware.\n- **Modo interactivo de `packages/coding-agent`**: construye el árbol de Componentes, vincula callbacks del editor y keymaps, reacciona a eventos del agente/sesión y traduce el estado del dominio (streaming, ejecución de herramientas, reintentos, modo plan) en Componentes de UI.\n\nRegla de límite: el motor TUI es agnóstico respecto a los mensajes. Solo conoce `Component.render(width)`, `handleInput(data)`, foco y superposiciones. La semántica del agente permanece en los controladores interactivos.\n\n## Archivos de implementación\n\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/components/custom-editor.ts`](../../packages/coding-agent/src/modes/components/custom-editor.ts)\n- [`../../tui/src/tui.ts`](../../packages/tui/src/tui.ts)\n- [`../../tui/src/terminal.ts`](../../packages/tui/src/terminal.ts)\n- [`../../tui/src/editor-component.ts`](../../packages/tui/src/editor-component.ts)\n- [`../../tui/src/stdin-buffer.ts`](../../packages/tui/src/stdin-buffer.ts)\n- [`../../tui/src/components/loader.ts`](../../packages/tui/src/components/loader.ts)\n\n## Inicio y ensamblaje del árbol de Componentes\n\n`InteractiveMode` construye `TUI(new ProcessTerminal(), showHardwareCursor)` y crea contenedores persistentes:\n\n- `chatContainer`\n- `pendingMessagesContainer`\n- `statusContainer`\n- `todoContainer`\n- `statusLine`\n- `editorContainer` (contiene `CustomEditor`)\n\n`init()` conecta el árbol en ese orden, enfoca el editor, registra manejadores de entrada mediante `InputController`, inicia el TUI y solicita un renderizado forzado.\n\nUn renderizado forzado (`requestRender(true)`) restablece las cachés de líneas previas y el seguimiento del cursor antes de repintar.\n\n## Ciclo de vida del terminal y normalización de stdin\n\n`ProcessTerminal.start()`:\n\n1. Habilita el modo raw y el pegado entre corchetes.\n2. Adjunta el manejador de redimensionamiento.\n3. Crea un `StdinBuffer` para dividir fragmentos de escape parciales en secuencias completas.\n4. Consulta la compatibilidad con el protocolo de teclado Kitty (`CSI ? u`) y habilita los indicadores de protocolo si es compatible.\n5. En Windows, intenta la habilitación de entrada VT mediante indicadores de modo de `kernel32`.\n\nComportamiento de `StdinBuffer`:\n\n- Almacena en búfer secuencias de escape fragmentadas (CSI/OSC/DCS/APC/SS3).\n- Emite `data` solo cuando una secuencia está completa o se vacía por tiempo de espera.\n- Detecta el pegado entre corchetes y emite un evento `paste` con el texto pegado sin procesar.\n\nEsto evita que los fragmentos de escape parciales sean malinterpretados como pulsaciones de teclas normales.\n\n## Enrutamiento de entrada y modelo de foco\n\nRuta de entrada:\n\n`stdin -> ProcessTerminal -> StdinBuffer -> TUI.#handleInput -> focusedComponent.handleInput`\n\nDetalles de enrutamiento:\n\n1. El TUI ejecuta primero los listeners de entrada registrados (`addInputListener`), permitiendo el comportamiento de consumo/transformación.\n2. El TUI maneja el atajo de depuración global (`shift+ctrl+d`) antes del despacho al componente.\n3. Si el componente enfocado pertenece a una superposición que ahora está oculta/invisible, el TUI reasigna el foco a la siguiente superposición visible o al foco guardado anterior a la superposición.\n4. Los eventos de liberación de teclas se filtran a menos que el componente enfocado establezca `wantsKeyRelease = true`.\n5. Después del despacho, el TUI programa el renderizado.\n\n`setFocus()` también activa/desactiva `Focusable.focused`, que controla si los Componentes emiten `CURSOR_MARKER` para el posicionamiento del cursor de hardware.\n\n## División del manejo de teclas: editor vs controlador\n\n`CustomEditor` intercepta primero las combinaciones de alta prioridad (escape, ctrl-c/d/z, ctrl-v, variantes de ctrl-p, ctrl-t, alt-arriba, teclas personalizadas de extensión) y delega el resto al comportamiento base de `Editor` (edición de texto, historial, autocompletado, movimiento del cursor).\n\n`InputController.setupKeyHandlers()` luego vincula los callbacks del editor a las acciones del modo:\n\n- cancelación / salidas de modo en `Escape`\n- apagado con doble `Ctrl+C` o `Ctrl+D` con editor vacío\n- suspensión/reanudación en `Ctrl+Z`\n- comandos slash y atajos de selector\n- alternadores de seguimiento/desencolar y alternadores de expansión\n\nEsto mantiene el análisis de teclas/mecánicas del editor en `packages/tui` y la semántica del modo en los controladores del coding-agent.\n\n## Bucle de renderizado y estrategia de diferenciación\n\n`TUI.requestRender()` tiene antirrebote para un renderizado por tick usando `process.nextTick`. Múltiples cambios de estado en el mismo turno se fusionan.\n\nCanalización de `#doRender()`:\n\n1. Renderizar el árbol de Componentes raíz en `newLines`.\n2. Componer superposiciones visibles (si las hay).\n3. Extraer y eliminar `CURSOR_MARKER` de las líneas del viewport visible.\n4. Añadir sufijos de restablecimiento de segmento para las líneas sin imágenes.\n5. Elegir entre repintado completo o parche diferencial:\n   - primer fotograma\n   - cambio de ancho\n   - reducción con `clearOnShrink` habilitado y sin superposiciones\n   - ediciones por encima del viewport anterior\n6. Para actualizaciones diferenciales, parchear solo el rango de líneas cambiado y limpiar las líneas finales obsoletas cuando sea necesario.\n7. Reposicionar el cursor de hardware para compatibilidad con IME.\n\nLas escrituras de renderizado utilizan el modo de salida sincronizada (`CSI ? 2026 h/l`) para reducir el parpadeo/desgarramiento.\n\n## Restricciones de seguridad del renderizado\n\nComprobaciones de seguridad críticas en `TUI`:\n\n- Las líneas renderizadas sin imágenes no deben exceder el ancho del terminal; el desbordamiento lanza una excepción y escribe diagnósticos de fallo.\n- La composición de superposiciones incluye truncamiento defensivo y verificación de ancho posterior a la composición.\n- Los cambios de ancho fuerzan un redibujado completo porque cambia la semántica de ajuste de línea.\n- La posición del cursor se limita antes del movimiento.\n\nEstas restricciones son aplicación en tiempo de ejecución, no solo convenciones.\n\n## Manejo del redimensionamiento\n\nLos eventos de redimensionamiento se manejan mediante eventos desde `ProcessTerminal` hasta `TUI.requestRender()`.\n\nEfectos:\n\n- Cualquier cambio de ancho activa un redibujado completo.\n- El seguimiento del viewport/parte superior (`#previousViewportTop`, `#maxLinesRendered`) evita cálculos de cursor relativos inválidos cuando cambia el contenido o el tamaño del terminal.\n- La visibilidad de las superposiciones puede depender de las dimensiones del terminal (`OverlayOptions.visible`); el foco se corrige cuando las superposiciones dejan de ser visibles después del redimensionamiento.\n\n## Streaming y actualizaciones de UI incrementales\n\n`EventController` se suscribe a `AgentSessionEvent` y actualiza la UI de forma incremental:\n\n- `agent_start`: inicia el cargador en `statusContainer`.\n- `message_start` asistente: crea `streamingComponent` y lo monta.\n- `message_update`: actualiza el contenido del asistente en streaming; crea/actualiza Componentes de ejecución de herramientas a medida que aparecen las llamadas a herramientas.\n- `tool_execution_update/end`: actualiza los Componentes de resultado de herramientas y el estado de finalización.\n- `message_end`: finaliza el stream del asistente, maneja anotaciones de anulación/error, marca los argumentos de herramienta pendientes como completos en parada normal.\n- `agent_end`: detiene los cargadores, limpia el estado de stream transitorio, vacía el cambio de modelo diferido, emite una notificación de finalización si está en segundo plano.\n\nLa agrupación de herramientas de lectura es deliberadamente con estado (`#lastReadGroup`) para fusionar llamadas consecutivas a herramientas de lectura en un bloque visual hasta que ocurra una interrupción que no sea de lectura.\n\n## Orquestación de estado y cargador\n\nResponsabilidades del carril de estado:\n\n- `statusContainer` contiene cargadores transitorios (`loadingAnimation`, `autoCompactionLoader`, `retryLoader`).\n- `statusLine` renderiza indicadores de estado/hooks/plan persistentes e impulsa las actualizaciones del borde superior del editor.\n\nComportamiento del cargador:\n\n- `Loader` se actualiza cada 80ms mediante un intervalo y solicita renderizado en cada fotograma.\n- Los manejadores de escape se anulan temporalmente durante la compactación automática y el reintento automático para cancelar esas operaciones.\n- En las rutas de finalización/cancelación, los controladores restauran los manejadores de escape anteriores y detienen/limpian los Componentes del cargador.\n\n## Transiciones de modo y segundo plano\n\n### Modos de entrada Bash/Python\n\nLos prefijos de texto de entrada alternan los indicadores del modo de borde del editor:\n\n- `!` -> modo bash\n- `$` (prefijo que no es de cadena de plantilla) -> modo python\n\nEscape sale del modo inactivo borrando el texto del editor y restaurando el color del borde; cuando la ejecución está activa, escape aborta la tarea en ejecución en su lugar.\n\n### Modo plan\n\n`InteractiveMode` rastrea los indicadores de modo plan, el estado de la línea de estado, las herramientas activas y el cambio de modelo. La entrada/salida actualiza las entradas de modo de sesión y el estado del estado/UI, incluido el cambio de modelo diferido si el streaming está activo.\n\n### Suspensión/reanudación (`Ctrl+Z`)\n\n`InputController.handleCtrlZ()`:\n\n1. Registra un manejador `SIGCONT` de un solo uso para reiniciar el TUI y forzar el renderizado.\n2. Detiene el TUI antes de suspender.\n3. Envía `SIGTSTP` al grupo de procesos.\n\n### Modo en segundo plano (`/background` o `/bg`)\n\n`handleBackgroundCommand()`:\n\n- Rechaza cuando está inactivo.\n- Cambia el contexto de UI de herramientas a no interactivo (`hasUI=false`) para que las herramientas de UI interactivas fallen rápidamente.\n- Detiene los cargadores/línea de estado y cancela la suscripción del manejador de eventos en primer plano.\n- Se suscribe al manejador de eventos en segundo plano (principalmente espera `agent_end`).\n- Detiene el TUI y envía `SIGTSTP` (ruta de control de trabajos POSIX).\n\nEn `agent_end` en segundo plano sin trabajo en cola, el controlador envía una notificación de finalización y se apaga.\n\n## Rutas de cancelación\n\nEntradas de cancelación principales:\n\n- `Escape` durante el cargador de stream activo: restaura los mensajes en cola al editor y aborta el agente.\n- `Escape` durante la ejecución de bash/python: aborta el comando en ejecución.\n- `Escape` durante la compactación automática/reintento: invoca métodos de aborto dedicados a través de manejadores de escape temporales.\n- `Ctrl+C` pulsación simple: limpiar el editor; doble pulsación en 500ms: apagado.\n\nLa cancelación es condicional al estado; la misma tecla puede significar abortar, salir del modo, activar el selector o ser un no-op dependiendo del estado en tiempo de ejecución.\n\n## Comportamiento basado en eventos vs. con limitación de velocidad\n\nActualizaciones basadas en eventos:\n\n- Eventos de sesión del agente (`EventController`)\n- Callbacks de entrada de teclas (`InputController`)\n- Callback de redimensionamiento del terminal\n- Observadores de tema/rama en `InteractiveMode`\n\nRutas con limitación/antirrebote:\n\n- El renderizado del TUI tiene antirrebote por tick (fusión de `requestRender`).\n- La animación del cargador tiene intervalo fijo (80ms), con cada fotograma solicitando renderizado.\n- Las actualizaciones de autocompletado del editor (dentro de `Editor`) utilizan temporizadores de antirrebote, reduciendo el cálculo redundante durante la escritura.\n\nEl runtime, por tanto, mezcla transiciones de estado basadas en eventos con una cadencia de renderizado acotada para mantener la interactividad responsiva sin tormentas de repintado.\n",
	"es/tui/tui.md": "---\ntitle: Integración TUI para extensiones y herramientas personalizadas\ndescription: >-\n  Contrato de integración TUI para extensiones, herramientas personalizadas y\n  renderizadores personalizados.\nsidebar:\n  order: 1\n  label: Integración de extensiones\ni18n:\n  sourceHash: 47f8f2b2045e\n  translator: machine\n---\n\n# Integración TUI para extensiones y herramientas personalizadas\n\nEste documento cubre el contrato TUI **actual** utilizado por `packages/coding-agent` y `packages/tui` para la interfaz de extensiones, la interfaz de herramientas personalizadas y los renderizadores personalizados.\n\n## Qué es este subsistema\n\nEl entorno de ejecución tiene dos capas:\n\n- **Motor de renderizado (`packages/tui`)**: renderizador diferencial de terminal, despacho de entrada, foco, superposiciones, posicionamiento del cursor.\n- **Capa de integración (`packages/coding-agent`)**: monta los componentes de extensiones/herramientas personalizadas, conecta las asociaciones de teclas/tema y restaura el estado del editor.\n\n## Comportamiento del entorno de ejecución por modo\n\n| Modo | Disponibilidad de `ctx.ui.custom(...)` | Notas |\n| --- | --- | --- |\n| TUI interactivo | Soportado | El componente se monta en el área del editor, recibe el foco y debe llamar a `done(result)` para resolver. |\n| En segundo plano/sin interfaz | No interactivo | El contexto de UI es no-op (`hasUI === false`). |\n| Modo RPC | No soportado | `custom()` devuelve `Promise<never>` y no monta componentes TUI. |\n\nSi su extensión/herramienta puede ejecutarse en modo no interactivo, proteja con `ctx.hasUI` / `pi.hasUI`.\n\n## Contrato principal del componente (`@f5-sales-demo/pi-tui`)\n\n`packages/tui/src/tui.ts` define:\n\n```ts\nexport interface Component {\n  render(width: number): string[];\n  handleInput?(data: string): void;\n  wantsKeyRelease?: boolean;\n  invalidate(): void;\n}\n```\n\n`Focusable` es independiente:\n\n```ts\nexport interface Focusable {\n  focused: boolean;\n}\n```\n\nEl comportamiento del cursor usa `CURSOR_MARKER` (no `getCursorPosition`). Los componentes con foco emiten el marcador en el texto renderizado; `TUI` lo extrae y posiciona el cursor de hardware.\n\n## Restricciones de renderizado (seguridad de terminal)\n\nLa salida de su `render(width)` debe ser segura para la terminal:\n\n1. **Nunca exceda `width` en ninguna línea**. El renderizador lanza un error si una línea que no es imagen desborda.\n2. **Mida el ancho visual**, no la longitud de la cadena: use `visibleWidth()`.\n3. **Trunque/ajuste texto compatible con ANSI** con `truncateToWidth()` / `wrapTextWithAnsi()`.\n4. **Sanitice tabulaciones/contenido** de fuentes externas usando `replaceTabs()` (y sanitizadores de nivel superior en las rutas de renderizado de coding-agent).\n\nPatrón mínimo:\n\n```ts\nimport { replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\n\nrender(width: number): string[] {\n  return this.lines.map(line => truncateToWidth(replaceTabs(line), width));\n}\n```\n\n## Manejo de entrada y asociaciones de teclas\n\n### Coincidencia de teclas sin procesar\n\nUse `matchesKey(data, \"...\")` para teclas de navegación y combinaciones.\n\n### Respete las asociaciones de teclas configuradas por el usuario\n\nLas fábricas de UI de extensiones reciben un `KeybindingsManager` (modo interactivo) para que pueda respetar las acciones mapeadas en lugar de codificar teclas de forma fija:\n\n```ts\nif (keybindings.matches(data, \"interrupt\")) {\n  done(undefined);\n  return;\n}\n```\n\n### Eventos de liberación/repetición de teclas\n\nLos eventos de liberación de teclas se filtran a menos que su componente establezca:\n\n```ts\nwantsKeyRelease = true;\n```\n\nLuego use `isKeyRelease()` / `isKeyRepeat()` si es necesario.\n\n## Foco, superposiciones y cursor\n\n- `TUI.setFocus(component)` dirige la entrada a ese componente.\n- Las APIs de superposición existen en `TUI` (`showOverlay`, `OverlayHandle`), pero el montaje de `ctx.ui.custom` de extensiones en modo interactivo actualmente reemplaza directamente el área del componente del editor.\n- La opción `custom(..., options?: { overlay?: boolean })` existe en los tipos de extensión; el montaje interactivo de extensiones actualmente ignora esta opción.\n\n## Puntos de montaje y contratos de retorno\n\n## 1) UI de extensión (`ExtensionUIContext`)\n\nFirma actual (`extensibility/extensions/types.ts`):\n\n```ts\ncustom<T>(\n  factory: (\n    tui: TUI,\n    theme: Theme,\n    keybindings: KeybindingsManager,\n    done: (result: T) => void,\n  ) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n  options?: { overlay?: boolean },\n): Promise<T>\n```\n\nComportamiento en modo interactivo (`extension-ui-controller.ts`):\n\n- Guarda el texto del editor.\n- Reemplaza el componente del editor con su componente.\n- Enfoca su componente.\n- Al llamar `done(result)`: ejecuta `component.dispose?.()`, restaura el editor + texto, enfoca el editor, resuelve la promesa.\n\nPor lo tanto, `done(...)` es obligatorio para la finalización.\n\n## 2) Contexto de UI de hooks/herramientas personalizadas (tipado legacy)\n\n`HookUIContext.custom` está tipado como `(tui, theme, done)` en los tipos de hooks/herramientas personalizadas.\nLa implementación interactiva subyacente llama a las fábricas con `(tui, theme, keybindings, done)`. Los consumidores JS pueden usar el argumento adicional; la compatibilidad a nivel de tipos aún refleja la firma legacy de 3 argumentos.\n\nLas herramientas personalizadas típicamente usan el mismo punto de entrada de UI a través del objeto `pi.ui` del ámbito de la fábrica, y luego devuelven el valor seleccionado en el contenido normal de la herramienta:\n\n```ts\nasync execute(toolCallId, params, onUpdate, ctx, signal) {\n  if (!pi.hasUI) {\n    return { content: [{ type: \"text\", text: \"UI unavailable\" }] };\n  }\n\n  const picked = await pi.ui.custom<string | undefined>((tui, theme, done) => {\n    const component = new MyPickerComponent(done, signal);\n    return component;\n  });\n\n  return { content: [{ type: \"text\", text: picked ? `Picked: ${picked}` : \"Cancelled\" }] };\n}\n```\n\n## 3) Renderizadores personalizados de llamadas/resultados de herramientas\n\nLas herramientas personalizadas y las herramientas de extensión pueden devolver componentes desde:\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\n`options` actualmente incluye:\n\n- `expanded: boolean`\n- `isPartial: boolean`\n- `spinnerFrame?: number`\n\nEstos renderizadores son montados por `ToolExecutionComponent`.\n\n## Ciclo de vida y cancelación\n\n- `dispose()` es opcional a nivel de tipos pero debe implementarse cuando posea temporizadores, subprocesos, observadores, sockets o superposiciones.\n- `done(...)` debe llamarse exactamente una vez desde el flujo de su componente.\n- Para UI de larga duración cancelable, combine `CancellableLoader` con `AbortSignal` y llame a `done(...)` desde `onAbort`.\n\nEjemplo de patrón de cancelación:\n\n```ts\nconst loader = new CancellableLoader(tui, theme.fg(\"accent\"), theme.fg(\"muted\"), \"Working...\");\nloader.onAbort = () => done(undefined);\nvoid doWork(loader.signal).then(result => done(result));\nreturn loader;\n```\n\n## Ejemplo realista de componente personalizado (comando de extensión)\n\n```ts\nimport type { Component } from \"@f5-sales-demo/pi-tui\";\nimport { SelectList, matchesKey, replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\nimport { getSelectListTheme, type ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nclass Picker implements Component {\n  list: SelectList;\n  keybindings: any;\n  done: (value: string | undefined) => void;\n\n  constructor(\n    items: Array<{ value: string; label: string }>,\n    keybindings: any,\n    done: (value: string | undefined) => void,\n  ) {\n    this.list = new SelectList(items, 8, getSelectListTheme());\n    this.keybindings = keybindings;\n    this.done = done;\n    this.list.onSelect = item => this.done(item.value);\n    this.list.onCancel = () => this.done(undefined);\n  }\n\n  handleInput(data: string): void {\n    if (this.keybindings.matches(data, \"interrupt\")) {\n      this.done(undefined);\n      return;\n    }\n    this.list.handleInput(data);\n  }\n\n  render(width: number): string[] {\n    return this.list.render(width).map(line => truncateToWidth(replaceTabs(line), width));\n  }\n\n  invalidate(): void {\n    this.list.invalidate();\n  }\n}\n\nexport default function extension(pi: ExtensionAPI): void {\n  pi.registerCommand(\"pick-model\", {\n    description: \"Pick a model profile\",\n    handler: async (_args, ctx) => {\n      if (!ctx.hasUI) return;\n\n      const selected = await ctx.ui.custom<string | undefined>((tui, theme, keybindings, done) => {\n        const items = [\n          { value: \"fast\", label: theme.fg(\"accent\", \"Fast\") },\n          { value: \"balanced\", label: \"Balanced\" },\n          { value: \"quality\", label: \"Quality\" },\n        ];\n        return new Picker(items, keybindings, done);\n      });\n\n      if (selected) ctx.ui.notify(`Selected profile: ${selected}`, \"info\");\n    },\n  });\n}\n```\n\n## Archivos de implementación clave\n\n- `packages/tui/src/tui.ts` — `Component`, `Focusable`, marcador de cursor, foco, superposición, despacho de entrada.\n- `packages/tui/src/utils.ts` — primitivas de ancho/truncamiento/sanitización.\n- `packages/tui/src/keys.ts` / `keybindings.ts` — análisis de teclas y mapeo de acciones configurables.\n- `packages/coding-agent/src/modes/controllers/extension-ui-controller.ts` — montaje/desmontaje interactivo para UI de extensiones/hooks/herramientas personalizadas.\n- `packages/coding-agent/src/extensibility/extensions/types.ts` — contratos de UI y renderizadores de extensiones.\n- `packages/coding-agent/src/extensibility/hooks/types.ts` — contrato de UI de hooks (firma custom legacy).\n- `packages/coding-agent/src/extensibility/custom-tools/types.ts` — contratos de ejecución/renderizado de herramientas personalizadas.\n- `packages/coding-agent/src/modes/components/tool-execution.ts` — montaje de componentes `renderCall`/`renderResult` y opciones de estado parcial.\n- `packages/coding-agent/src/tools/context.ts` — propagación del contexto de UI de herramientas (`hasUI`, `ui`).\n",
	"fr/configuration/blob-artifact-architecture.md": "---\ntitle: Architecture du stockage de blobs et d'artefacts\ndescription: >-\n  Content-addressable blob store and artifact registry for session media,\n  screenshots, and tool outputs.\nsidebar:\n  order: 7\n  label: Stockage de blobs et d'artefacts\ni18n:\n  sourceHash: 70d255f48d5b\n  translator: machine\n---\n\n# Architecture du stockage de blobs et d'artefacts\n\nCe document décrit comment coding-agent stocke les charges utiles volumineuses/binaires en dehors du JSONL de session, comment les sorties d'outils tronquées sont persistées, et comment les URLs internes (`artifact://`, `agent://`) se résolvent vers les données stockées.\n\n## Pourquoi deux systèmes de stockage existent\n\nLe runtime utilise deux mécanismes de persistance différents pour des formes de données différentes :\n\n- **Blobs adressés par contenu** (`blob:sha256:<hash>`) : stockage global, orienté binaire, utilisé pour externaliser les charges utiles base64 d'images volumineuses des entrées de session persistées.\n- **Artefacts à portée de session** (fichiers sous `<sessionFile-without-.jsonl>/`) : fichiers texte par session utilisés pour les sorties complètes d'outils et les sorties de sous-agents.\n\nIls sont intentionnellement séparés :\n\n- le stockage de blobs optimise la déduplication et les références stables par hash de contenu,\n- le stockage d'artefacts optimise l'outillage de session en ajout seul et la récupération par humain/outil via des IDs locaux.\n\n## Limites de stockage et disposition sur disque\n\n## Limite du magasin de blobs (global)\n\n`SessionManager` construit `BlobStore(getBlobsDir())`, ainsi les fichiers blob résident dans un répertoire de blobs global partagé (pas dans un dossier de session).\n\nNommage des fichiers blob :\n\n- chemin du fichier : `<blobsDir>/<sha256-hex>`\n- pas d'extension\n- chaîne de référence stockée dans les entrées : `blob:sha256:<sha256-hex>`\n\nImplications :\n\n- le même contenu binaire à travers les sessions se résout vers le même hash/chemin,\n- les écritures sont idempotentes au niveau du contenu,\n- les blobs peuvent survivre à n'importe quel fichier de session individuel.\n\n## Limite des artefacts (local à la session)\n\n`ArtifactManager` dérive le répertoire d'artefacts à partir du chemin du fichier de session :\n\n- fichier de session : `.../<timestamp>_<sessionId>.jsonl`\n- répertoire d'artefacts : `.../<timestamp>_<sessionId>/` (suppression de `.jsonl`)\n\nLes types d'artefacts partagent ce répertoire :\n\n- fichiers de sortie d'outil tronqués : `<numericId>.<toolType>.log` (pour `artifact://`)\n- fichiers de sortie de sous-agent : `<outputId>.md` (pour `agent://`)\n\n## Schémas d'allocation d'IDs et de noms\n\n## IDs de blob : hash de contenu\n\n`BlobStore.put()` calcule le SHA-256 sur les octets binaires bruts et retourne :\n\n- `hash` : condensé hexadécimal,\n- `path` : `<blobsDir>/<hash>`,\n- `ref` : `blob:sha256:<hash>`.\n\nAucun compteur local à la session n'est utilisé.\n\n## IDs d'artefact : entier monotone local à la session\n\n`ArtifactManager` parcourt les fichiers d'artefacts `*.log` existants lors de la première utilisation pour trouver l'ID numérique maximum existant et définit `nextId = max + 1`.\n\nComportement d'allocation :\n\n- format de fichier : `{id}.{toolType}.log`\n- les IDs sont des chaînes séquentielles (`\"0\"`, `\"1\"`, ...)\n- la reprise n'écrase pas les artefacts existants car le parcours se fait avant l'allocation.\n\nSi le répertoire d'artefacts est manquant, le parcours retourne une liste vide et l'allocation commence à `0`.\n\n## IDs de sortie d'agent (`agent://`)\n\n`AgentOutputManager` alloue les IDs pour les sorties de sous-agents sous la forme `<index>-<requestedId>` (optionnellement imbriqués sous un préfixe parent, par ex. `0-Parent.1-Child`). Il parcourt les fichiers `.md` existants à l'initialisation pour continuer à partir de l'index suivant lors de la reprise.\n\n## Flux de données de persistance\n\n## 1) Chemin de réécriture de persistance des entrées de session\n\nAvant que les entrées de session soient écrites (`#rewriteFile` / persistance incrémentale), `SessionManager` appelle `prepareEntryForPersistence()` (via `truncateForPersistence`).\n\nComportements clés :\n\n1. **Troncation de grandes chaînes** : les chaînes surdimensionnées sont coupées et suffixées avec `\"[Session persistence truncated large content]\"`.\n2. **Suppression des champs transitoires** : `partialJson` et `jsonlEvents` sont supprimés des entrées persistées.\n3. **Externalisation des images vers les blobs** :\n   - s'applique uniquement aux blocs d'image dans les tableaux `content`,\n   - uniquement quand `data` n'est pas déjà une référence blob,\n   - uniquement quand la longueur base64 atteint au moins le seuil (`BLOB_EXTERNALIZE_THRESHOLD = 1024`),\n   - remplace le base64 en ligne par `blob:sha256:<hash>`.\n\nCela maintient le JSONL de session compact tout en préservant la récupérabilité.\n\n## 2) Chemin de réhydratation au chargement de session\n\nLors de l'ouverture d'une session (`setSessionFile`), après les migrations, `SessionManager` exécute `resolveBlobRefsInEntries()`.\n\nPour chaque bloc d'image message/message-personnalisé avec `blob:sha256:<hash>` :\n\n- lit les octets du blob depuis le magasin de blobs,\n- convertit les octets en base64,\n- modifie l'entrée en mémoire pour intégrer le base64 en ligne pour les consommateurs du runtime.\n\nSi le blob est manquant :\n\n- `resolveImageData()` journalise un avertissement,\n- retourne la chaîne de référence originale inchangée,\n- le chargement continue (pas de plantage).\n\n## 3) Chemin de débordement/troncation de sortie d'outil\n\n`OutputSink` alimente la sortie en flux continu dans bash/python/ssh et les exécuteurs associés.\n\nComportement :\n\n1. Chaque morceau est assaini et ajouté au tampon de queue en mémoire.\n2. Quand les octets en mémoire dépassent le seuil de débordement (`DEFAULT_MAX_BYTES`, 50 Ko), le sink marque la sortie comme tronquée.\n3. Si un chemin d'artefact est disponible, le sink ouvre un écrivain de fichier et écrit :\n   - le contenu tamponné existant une fois,\n   - tous les morceaux suivants.\n4. Le tampon en mémoire est toujours rogné à la fenêtre de queue pour l'affichage.\n5. `dump()` retourne un résumé incluant `artifactId` uniquement quand le sink de fichier a été créé avec succès.\n\nEffet pratique :\n\n- l'UI/le retour d'outil affiche la queue tronquée,\n- la sortie complète est préservée dans le fichier d'artefact et référencée comme `artifact://<id>`.\n\nSi la création du sink de fichier échoue (erreur d'E/S, chemin manquant, etc.), le sink bascule silencieusement vers la troncation en mémoire seule ; la sortie complète n'est pas persistée.\n\n## Modèle d'accès par URL\n\n## Références `blob:`\n\n`blob:sha256:<hash>` est une référence de persistance à l'intérieur des charges utiles des entrées de session, pas un schéma d'URL interne géré par le routeur. La résolution est effectuée par `SessionManager` pendant le chargement de session.\n\n## `artifact://<id>`\n\nGéré par `ArtifactProtocolHandler` :\n\n- nécessite un répertoire d'artefacts de session actif,\n- l'ID doit être numérique,\n- résolu en faisant correspondre le préfixe du nom de fichier `<id>.`,\n- retourne du texte brut (`text/plain`) depuis le fichier `.log` correspondant,\n- en cas d'absence, l'erreur inclut la liste des IDs d'artefacts disponibles.\n\nComportement en cas de répertoire manquant :\n\n- si le répertoire d'artefacts n'existe pas, lance `No artifacts directory found`.\n\n## `agent://<id>`\n\nGéré par `AgentProtocolHandler` sur `<artifactsDir>/<id>.md` :\n\n- la forme simple retourne du texte markdown,\n- les formes `/path` ou `?q=` effectuent une extraction JSON,\n- l'extraction par chemin et par requête ne peuvent pas être combinées,\n- si l'extraction est demandée, le contenu du fichier doit pouvoir être analysé comme JSON.\n\nComportement en cas de répertoire manquant :\n\n- lance `No artifacts directory found`.\n\nComportement en cas de sortie manquante :\n\n- lance `Not found: <id>` avec les IDs disponibles à partir des fichiers `.md` existants.\n\nIntégration de l'outil read :\n\n- `read` prend en charge la pagination offset/limit pour les lectures d'URL internes sans extraction,\n- rejette `offset/limit` quand l'extraction `agent://` est utilisée.\n\n## Sémantique de reprise, fork et déplacement\n\n## Reprise\n\n- `ArtifactManager` parcourt les fichiers `{id}.*.log` existants lors de la première allocation et continue la numérotation.\n- `AgentOutputManager` parcourt les IDs de sortie `.md` existants et continue la numérotation.\n- `SessionManager` réhydrate les références blob en base64 au chargement.\n\n## Fork\n\n`SessionManager.fork()` crée un nouveau fichier de session avec un nouvel ID de session et un lien `parentSession`, puis retourne les anciens/nouveaux chemins de fichiers. La copie des artefacts est gérée par `AgentSession.fork()` :\n\n- tente une copie récursive de l'ancien répertoire d'artefacts vers le nouveau répertoire d'artefacts,\n- l'absence de l'ancien répertoire est tolérée,\n- les erreurs de copie non-ENOENT sont journalisées comme avertissements et le fork se termine quand même.\n\nImplications sur les IDs après le fork :\n\n- si la copie a réussi, les compteurs d'artefacts dans la nouvelle session continuent après l'ID maximum copié,\n- si la copie a échoué/été ignorée, les IDs d'artefacts de la nouvelle session commencent à `0`.\n\nImplications sur les blobs après le fork :\n\n- les blobs sont globaux et adressés par contenu, donc aucune copie de répertoire de blobs n'est nécessaire.\n\n## Déplacement vers un nouveau répertoire de travail\n\n`SessionManager.moveTo()` renomme à la fois le fichier de session et le répertoire d'artefacts vers le nouveau répertoire de session par défaut, avec une logique de restauration si une étape ultérieure échoue. Cela préserve l'identité des artefacts tout en relocalisent la portée de la session.\n\n## Gestion des échecs et chemins de repli\n\n| Cas | Comportement |\n| --- | --- |\n| Fichier blob manquant pendant la réhydratation | Avertit et conserve la chaîne de référence `blob:sha256:` en mémoire |\n| ENOENT lors de la lecture blob via `BlobStore.get` | Retourne `null` |\n| Répertoire d'artefacts manquant (`ArtifactManager.listFiles`) | Retourne une liste vide (l'allocation peut repartir de zéro) |\n| Répertoire d'artefacts manquant (`artifact://` / `agent://`) | Lance explicitement `No artifacts directory found` |\n| ID d'artefact non trouvé | Lance avec la liste des IDs disponibles |\n| Échec d'initialisation de l'écrivain d'artefact OutputSink | Continue avec la troncation de queue seule (pas d'artefact de sortie complète) |\n| Pas de fichier de session (certains chemins de tâche) | L'outil task bascule vers un répertoire d'artefacts temporaire pour les sorties de sous-agents |\n\n## Externalisation de blobs binaires vs artefacts de sortie texte\n\n- **L'externalisation de blobs** concerne les charges utiles d'images binaires à l'intérieur du contenu des entrées de session persistées ; elle remplace le base64 en ligne dans le JSONL par des références stables de contenu.\n- **Les artefacts** sont des fichiers texte brut pour les sorties d'exécution et les sorties de sous-agents ; ils sont adressables par des IDs locaux à la session via des URLs internes.\n\nLes deux systèmes ne se croisent qu'indirectement (tous deux réduisent le gonflement du JSONL de session) mais ont des chemins d'identité, de durée de vie et de récupération différents.\n\n## Fichiers d'implémentation\n\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts) — format de référence blob, hachage, put/get, helpers d'externalisation/résolution.\n- [`src/session/artifacts.ts`](../../packages/coding-agent/src/session/artifacts.ts) — modèle de répertoire d'artefacts de session et allocation d'IDs d'artefacts numériques.\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — comportement de troncation/débordement vers fichier de `OutputSink` et métadonnées de résumé.\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts) — transformations de persistance, réhydratation des blobs au chargement, interactions fork/déplacement de session.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — copie du répertoire d'artefacts pendant le fork interactif.\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — amorçage du gestionnaire d'artefacts d'outils et allocation de chemin d'artefact par outil.\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — résolveur `artifact://`.\n- [`src/internal-urls/agent-protocol.ts`](../../packages/coding-agent/src/internal-urls/agent-protocol.ts) — résolveur `agent://` + extraction JSON.\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — câblage du routeur d'URL internes et résolveur de répertoire d'artefacts.\n- [`src/task/output-manager.ts`](../../packages/coding-agent/src/task/output-manager.ts) — allocation d'IDs de sortie d'agent à portée de session pour `agent://`.\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — écritures d'artefacts de sortie de sous-agent (`<id>.md`) et repli vers un répertoire d'artefacts temporaire.\n",
	"fr/configuration/config-usage.md": "---\ntitle: Découverte et résolution de la configuration\ndescription: >-\n  Comment xcsh découvre, résout et superpose la configuration à partir des\n  racines de projet, d'utilisateur et d'entreprise.\nsidebar:\n  order: 1\n  label: Configuration\ni18n:\n  sourceHash: e38bd9792499\n  translator: machine\n---\n\n# Découverte et résolution de la configuration\n\nCe document décrit la façon dont l'agent de codage résout la configuration aujourd'hui : quelles racines sont analysées, comment la priorité fonctionne, et comment la configuration résolue est consommée par les paramètres, les compétences, les hooks, les outils et les extensions.\n\n## Périmètre\n\nImplémentation principale :\n\n- `src/config.ts`\n- `src/config/settings.ts`\n- `src/config/settings-schema.ts`\n- `src/discovery/builtin.ts`\n- `src/discovery/helpers.ts`\n\nPoints d'intégration clés :\n\n- `src/capability/index.ts`\n- `src/discovery/index.ts`\n- `src/extensibility/skills.ts`\n- `src/extensibility/hooks/loader.ts`\n- `src/extensibility/custom-tools/loader.ts`\n- `src/extensibility/extensions/loader.ts`\n\n---\n\n## Flux de résolution (visuel)\n\n```text\n         Config roots (ordered)\n┌───────────────────────────────────────┐\n│ 1) ~/.xcsh/agent + <cwd>/.xcsh          │\n│ 2) ~/.claude   + <cwd>/.claude        │\n│ 3) ~/.codex    + <cwd>/.codex         │\n│ 4) ~/.gemini   + <cwd>/.gemini        │\n└───────────────────────────────────────┘\n                    │\n                    ▼\n        config.ts helper resolution\n  (getConfigDirs/findConfigFile/findNearest...)\n                    │\n                    ▼\n       capability providers enumerate items\n (native, claude, codex, gemini, agents, etc.)\n                    │\n                    ▼\n      priority sort + per-capability dedup\n                    │\n                    ▼\n          subsystem-specific consumption\n   (settings, skills, hooks, tools, extensions)\n```\n\n## 1) Racines de configuration et ordre des sources\n\n## Racines canoniques\n\n`src/config.ts` définit une liste de priorité des sources fixe :\n\n1. `.xcsh` (natif)\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nBases au niveau utilisateur :\n\n- `~/.xcsh/agent`\n- `~/.claude`\n- `~/.codex`\n- `~/.gemini`\n\nBases au niveau projet :\n\n- `<cwd>/.xcsh`\n- `<cwd>/.claude`\n- `<cwd>/.codex`\n- `<cwd>/.gemini`\n\n`CONFIG_DIR_NAME` est `.xcsh` (`packages/utils/src/dirs.ts`).\n\n## Contrainte importante\n\nLes helpers génériques dans `src/config.ts` **n'incluent pas** `.pi` dans l'ordre de découverte des sources.\n\n---\n\n## 2) Helpers de découverte principaux (`src/config.ts`)\n\n## `getConfigDirs(subpath, options)`\n\nRetourne des entrées ordonnées :\n\n- Les entrées au niveau utilisateur en premier (par priorité de source)\n- Puis les entrées au niveau projet (par la même priorité de source)\n\nOptions :\n\n- `user` (défaut `true`)\n- `project` (défaut `true`)\n- `cwd` (défaut `getProjectDir()`)\n- `existingOnly` (défaut `false`)\n\nCette API est utilisée pour les recherches de configuration basées sur des répertoires (commandes, hooks, outils, agents, etc.).\n\n## `findConfigFile(subpath, options)` / `findConfigFileWithMeta(...)`\n\nRecherche le premier fichier existant parmi les bases ordonnées, retourne la première correspondance (chemin seul ou chemin+métadonnées).\n\n## `findAllNearestProjectConfigDirs(subpath, cwd)`\n\nRemonte les répertoires parents et retourne le **répertoire existant le plus proche par base de source** (`.xcsh`, `.claude`, `.codex`, `.gemini`), puis trie les résultats par priorité de source.\n\nÀ utiliser lorsque la configuration de projet doit être héritée depuis des répertoires ancêtres (comportement de monorepo/espace de travail imbriqué).\n\n---\n\n## 3) Wrapper de fichier de configuration (`ConfigFile<T>` dans `src/config.ts`)\n\n`ConfigFile<T>` est le chargeur validé par schéma pour les fichiers de configuration individuels.\n\nFormats pris en charge :\n\n- `.yml` / `.yaml`\n- `.json` / `.jsonc`\n\nComportement :\n\n- Valide les données analysées avec AJV par rapport à un schéma TypeBox fourni.\n- Met en cache le résultat du chargement jusqu'à l'appel de `invalidate()`.\n- Retourne un résultat à trois états via `tryLoad()` :\n  - `ok`\n  - `not-found`\n  - `error` (`ConfigError` avec le contexte de schéma/analyse)\n\nLa migration héritée est toujours prise en charge :\n\n- Si le chemin cible est `.yml`/`.yaml`, un fichier `.json` adjacent est automatiquement migré une fois (`migrateJsonToYml`).\n\n---\n\n## 4) Modèle de résolution des paramètres (`src/config/settings.ts`)\n\nLe modèle de paramètres d'exécution est superposé en couches :\n\n1. Paramètres globaux : `~/.xcsh/agent/config.yml`\n2. Paramètres de projet : découverts via la capacité de paramètres (`settings.json` des fournisseurs)\n3. Surcharges d'exécution : en mémoire, non persistantes\n4. Valeurs par défaut du schéma : issues de `SETTINGS_SCHEMA`\n\nChemin de lecture effectif :\n\n`defaults <- global <- project <- overrides`\n\nComportement d'écriture :\n\n- `settings.set(...)` écrit dans la couche **globale** (`config.yml`) et met en file d'attente une sauvegarde en arrière-plan.\n- Les paramètres de projet sont en lecture seule depuis la découverte des capacités.\n\n## Comportement de migration toujours actif\n\nAu démarrage, si `config.yml` est absent :\n\n1. Migration depuis `~/.xcsh/agent/settings.json` (renommé en `.bak` en cas de succès)\n2. Fusion avec les paramètres hérités de la base de données depuis `agent.db`\n3. Écriture du résultat fusionné dans `config.yml`\n\nMigrations au niveau des champs dans `#migrateRawSettings` :\n\n- `queueMode` -> `steeringMode`\n- Millisecondes de `ask.timeout` -> secondes lorsque l'ancienne valeur ressemble à des ms (`> 1000`)\n- Structure héritée plate `theme: \"...\"` -> structure `theme.dark/theme.light`\n\n---\n\n## 5) Intégration capacité/découverte\n\nLa plupart des flux de chargement de configuration non essentiels passent par le registre de capacités (`src/capability/index.ts` + `src/discovery/index.ts`).\n\n## Ordre des fournisseurs\n\nLes fournisseurs sont triés par priorité numérique (les plus élevées en premier). Exemples de priorités :\n\n- OMP natif (`builtin.ts`) : `100`\n- Claude : `80`\n- Codex / agents / Claude marketplace : `70`\n- Gemini : `60`\n\n```text\nProvider precedence (higher wins)\n\nnative (.xcsh)          priority 100\nclaude                 priority  80\ncodex / agents / ...   priority  70\ngemini                 priority  60\n```\n\n## Sémantique de déduplication\n\nLes capacités définissent une `key(item)` :\n\n- même clé => le premier élément l'emporte (élément de priorité supérieure/chargé en premier)\n- pas de clé (`undefined`) => pas de déduplication, tous les éléments sont conservés\n\nClés pertinentes :\n\n- compétences : `name`\n- outils : `name`\n- hooks : `${type}:${tool}:${name}`\n- modules d'extension : `name`\n- extensions : `name`\n- paramètres : pas de déduplication (tous les éléments sont préservés)\n\n---\n\n## 6) Comportement du fournisseur natif `.xcsh` (`src/discovery/builtin.ts`)\n\nLe fournisseur natif (`id: native`) lit depuis :\n\n- projet : `<cwd>/.xcsh/...`\n- utilisateur : `~/.xcsh/agent/...`\n\n### Règle d'admission de répertoire\n\n`builtin.ts` n'inclut une racine de configuration que si le répertoire existe **et est non vide** (`ifNonEmptyDir`).\n\n### Chargement spécifique à la portée\n\n- Compétences : `skills/*/SKILL.md`\n- Commandes slash : `commands/*.md`\n- Règles : `rules/*.{md,mdc}`\n- Prompts : `prompts/*.md`\n- Instructions : `instructions/*.md`\n- Hooks : `hooks/pre/*`, `hooks/post/*`\n- Outils : `tools/*.json|*.md` et `tools/<name>/index.ts`\n- Modules d'extension : découverts sous `extensions/` (+ tableau de chaînes hérité `settings.json.extensions`)\n- Extensions : `extensions/<name>/gemini-extension.json`\n- Capacité de paramètres : `settings.json`\n\n### Nuance de la recherche de projet le plus proche\n\nPour `SYSTEM.md` et `XCSH.md`, le fournisseur natif utilise une recherche de répertoire `.xcsh` de projet ancêtre le plus proche (remontée de répertoires), mais exige toujours que le répertoire `.xcsh` soit non vide.\n\n---\n\n## 7) Comment les sous-systèmes majeurs consomment la configuration\n\n## Sous-système des paramètres\n\n- `Settings.init()` charge le fichier global `config.yml` + les éléments de capacité `settings.json` de projet découverts.\n- Seuls les éléments de capacité avec `level === \"project\"` sont fusionnés dans la couche projet.\n\n## Sous-système des compétences\n\n- `extensibility/skills.ts` charge via `loadCapability(skillCapability.id, { cwd })`.\n- Applique les bascules de source et les filtres (`ignoredSkills`, `includeSkills`, répertoires personnalisés).\n- Des bascules aux noms hérités existent toujours (`skills.enablePiUser`, `skills.enablePiProject`) mais elles conditionnent le fournisseur natif (`provider === \"native\"`).\n\n## Sous-système des hooks\n\n- `discoverAndLoadHooks()` résout les chemins de hooks depuis la capacité de hook + les chemins configurés explicitement.\n- Charge ensuite les modules via l'import Bun.\n\n## Sous-système des outils\n\n- `discoverAndLoadCustomTools()` résout les chemins d'outils depuis la capacité d'outil + les chemins d'outils de plugin + les chemins configurés explicitement.\n- Les fichiers d'outils déclaratifs `.md/.json` sont uniquement des métadonnées ; le chargement exécutable attend des modules de code.\n\n## Sous-système des extensions\n\n- `discoverAndLoadExtensions()` résout les modules d'extension depuis la capacité de module d'extension ainsi que les chemins explicites.\n- L'implémentation actuelle conserve intentionnellement uniquement les éléments de capacité avec `_source.provider === \"native\"` avant le chargement.\n\n---\n\n## 8) Règles de priorité sur lesquelles s'appuyer\n\nUtilisez ce modèle mental :\n\n1. L'ordre des répertoires de sources depuis `config.ts` détermine l'ordre des chemins candidats.\n2. La priorité du fournisseur de capacités détermine la priorité entre fournisseurs.\n3. La déduplication par clé de capacité détermine le comportement en cas de collision (le premier l'emporte pour les capacités à clé).\n4. La logique de fusion spécifique au sous-système peut modifier davantage la priorité effective (en particulier pour les paramètres).\n\n### Mise en garde spécifique aux paramètres\n\nLes éléments de capacité de paramètres ne sont pas dédupliqués ; `Settings.#loadProjectSettings()` fusionne profondément les éléments de projet dans l'ordre retourné. Étant donné que la fusion applique les valeurs des éléments ultérieurs sur les valeurs antérieures, le comportement de surcharge effectif dépend de l'ordre d'émission du fournisseur, et pas seulement de la sémantique des clés de capacité.\n\n---\n\n## 9) Comportements hérités/de compatibilité toujours présents\n\n- Migration JSON -> YAML de `ConfigFile` pour les fichiers ciblant YAML.\n- Migration des paramètres depuis `settings.json` et `agent.db` vers `config.yml`.\n- Migrations de clés de paramètres (`queueMode`, `ask.timeout`, `theme` plat).\n- Compatibilité du manifeste d'extension : le chargeur accepte les sections de manifeste `package.json.xcsh` et `package.json.pi`.\n- Les noms de paramètres hérités `skills.enablePiUser` / `skills.enablePiProject` sont toujours des conditions actives pour la source de compétences native.\n\nSi ces chemins de compatibilité sont supprimés du code, mettez à jour ce document immédiatement ; plusieurs comportements d'exécution en dépendent encore aujourd'hui.\n",
	"fr/configuration/environment-variables.md": "---\ntitle: Variables d'environnement\ndescription: >-\n  Référence des variables d'environnement d'exécution pour la configuration et\n  le contrôle du comportement de xcsh.\nsidebar:\n  order: 2\n  label: Variables d'environnement\ni18n:\n  sourceHash: e2890f963c02\n  translator: machine\n---\n\n# Variables d'environnement (Référence d'exécution actuelle)\n\nCette référence est dérivée des chemins de code actuels dans :\n\n- `packages/coding-agent/src/**`\n- `packages/ai/src/**` (résolution fournisseur/authentification utilisée par coding-agent)\n- `packages/utils/src/**` et `packages/tui/src/**` lorsque ces variables affectent directement l'exécution de coding-agent\n\nElle ne documente que le comportement actif.\n\n## Modèle de résolution et ordre de priorité\n\nLa plupart des recherches à l'exécution utilisent `$env` de `@f5-sales-demo/pi-utils` (`packages/utils/src/env.ts`).\n\nOrdre de chargement de `$env` :\n\n1. Environnement de processus existant (`Bun.env`)\n2. `.env` du projet (`$PWD/.env`) pour les clés non déjà définies\n3. `.env` du répertoire personnel (`~/.env`) pour les clés non déjà définies\n\nRègle supplémentaire dans les fichiers `.env` : les clés `XCSH_*` sont dupliquées vers les clés `PI_*` lors de l'analyse.\n\n---\n\n## 1) Authentification modèle/fournisseur\n\nCelles-ci sont consommées via `getEnvApiKey()` (`packages/ai/src/stream.ts`) sauf indication contraire.\n\n### Identifiants des fournisseurs principaux\n\n| Variable                        | Utilisée pour | Requise quand                                                 | Notes / priorité                                                                                    |\n|---------------------------------|---|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|\n| `ANTHROPIC_OAUTH_TOKEN`         | Authentification API Anthropic | Utilisation d'Anthropic avec authentification par token OAuth  | Prioritaire sur `ANTHROPIC_API_KEY` pour la résolution de l'authentification fournisseur             |\n| `ANTHROPIC_API_KEY`             | Authentification API Anthropic | Utilisation d'Anthropic sans token OAuth                      | Solution de repli après `ANTHROPIC_OAUTH_TOKEN`                                                     |\n| `ANTHROPIC_FOUNDRY_API_KEY`     | Anthropic via Azure Foundry / passerelle entreprise | `CLAUDE_CODE_USE_FOUNDRY` activé                              | Prioritaire sur `ANTHROPIC_OAUTH_TOKEN` et `ANTHROPIC_API_KEY` lorsque le mode Foundry est activé   |\n| `OPENAI_API_KEY`                | Authentification OpenAI | Utilisation des fournisseurs de la famille OpenAI sans argument apiKey explicite | Utilisée par les fournisseurs OpenAI Completions/Responses                                          |\n| `GEMINI_API_KEY`                | Authentification Google Gemini | Utilisation des modèles du fournisseur `google`               | Clé principale pour le mappage du fournisseur Gemini                                                |\n| `GOOGLE_API_KEY`                | Solution de repli pour l'authentification de l'outil d'image Gemini | Utilisation de l'outil `gemini_image` sans `GEMINI_API_KEY`   | Utilisée par le chemin de repli de l'outil d'image de coding-agent                                  |\n| `GROQ_API_KEY`                  | Authentification Groq | Utilisation des modèles Groq                                  |                                                                                                     |\n| `CEREBRAS_API_KEY`              | Authentification Cerebras | Utilisation des modèles Cerebras                              |                                                                                                     |\n| `TOGETHER_API_KEY`              | Authentification Together | Utilisation du fournisseur `together`                         |                                                                                                     |\n| `HUGGINGFACE_HUB_TOKEN`         | Authentification Hugging Face | Utilisation du fournisseur `huggingface`                      | Variable d'environnement principale du token Hugging Face                                           |\n| `HF_TOKEN`                      | Authentification Hugging Face | Utilisation du fournisseur `huggingface`                      | Solution de repli lorsque `HUGGINGFACE_HUB_TOKEN` n'est pas défini                                  |\n| `SYNTHETIC_API_KEY`             | Authentification Synthetic | Utilisation des modèles Synthetic                             |                                                                                                     |\n| `NVIDIA_API_KEY`                | Authentification NVIDIA | Utilisation du fournisseur `nvidia`                           |                                                                                                     |\n| `NANO_GPT_API_KEY`              | Authentification NanoGPT | Utilisation du fournisseur `nanogpt`                          |                                                                                                     |\n| `VENICE_API_KEY`                | Authentification Venice | Utilisation du fournisseur `venice`                           |                                                                                                     |\n| `LITELLM_API_KEY`               | Authentification LiteLLM | Utilisation du fournisseur `litellm`                          | Clé de proxy LiteLLM compatible OpenAI. Lorsque définie avec `LITELLM_BASE_URL`, active la configuration automatique de `models.yml` |\n| `LM_STUDIO_API_KEY`             | Authentification LM Studio (optionnelle) | Utilisation du fournisseur `lm-studio` avec des hôtes authentifiés | LM Studio local fonctionne généralement sans authentification ; tout token non vide suffit lorsqu'une clé est requise |\n| `OLLAMA_API_KEY`                | Authentification Ollama (optionnelle) | Utilisation du fournisseur `ollama` avec des hôtes authentifiés | Ollama local fonctionne généralement sans authentification ; tout token non vide suffit lorsqu'une clé est requise |\n| `LLAMA_CPP_API_KEY`             | Authentification Ollama (optionnelle) | Utilisation de `llama-server` avec le paramètre `--api-key`  | llama.cpp local fonctionne généralement sans authentification ; tout token non vide suffit lorsqu'une clé est configurée |\n| `XIAOMI_API_KEY`                | Authentification Xiaomi MiMo | Utilisation du fournisseur `xiaomi`                           |                                                                                                     |\n| `MOONSHOT_API_KEY`              | Authentification Moonshot | Utilisation du fournisseur `moonshot`                         |                                                                                                     |\n| `XAI_API_KEY`                   | Authentification xAI | Utilisation des modèles xAI                                   |                                                                                                     |\n| `OPENROUTER_API_KEY`            | Authentification OpenRouter | Utilisation des modèles OpenRouter                            | Également utilisée par l'outil d'image lorsque le fournisseur préféré/auto est OpenRouter           |\n| `MISTRAL_API_KEY`               | Authentification Mistral | Utilisation des modèles Mistral                               |                                                                                                     |\n| `ZAI_API_KEY`                   | Authentification z.ai | Utilisation des modèles z.ai                                  | Également utilisée par le fournisseur de recherche web z.ai                                         |\n| `MINIMAX_API_KEY`               | Authentification MiniMax | Utilisation du fournisseur `minimax`                          |                                                                                                     |\n| `MINIMAX_CODE_API_KEY`          | Authentification MiniMax Code | Utilisation du fournisseur `minimax-code`                     |                                                                                                     |\n| `MINIMAX_CODE_CN_API_KEY`       | Authentification MiniMax Code CN | Utilisation du fournisseur `minimax-code-cn`                  |                                                                                                     |\n| `OPENCODE_API_KEY`              | Authentification OpenCode | Utilisation des modèles OpenCode                              |                                                                                                     |\n| `QIANFAN_API_KEY`               | Authentification Qianfan | Utilisation du fournisseur `qianfan`                          |                                                                                                     |\n| `QWEN_OAUTH_TOKEN`              | Authentification Qwen Portal | Utilisation de `qwen-portal` avec un token OAuth              | Prioritaire sur `QWEN_PORTAL_API_KEY`                                                               |\n| `QWEN_PORTAL_API_KEY`           | Authentification Qwen Portal | Utilisation de `qwen-portal` avec une clé API                 | Solution de repli après `QWEN_OAUTH_TOKEN`                                                          |\n| `ZENMUX_API_KEY`                | Authentification ZenMux | Utilisation du fournisseur `zenmux`                           | Utilisée pour les routes compatibles ZenMux OpenAI et Anthropic                                     |\n| `VLLM_API_KEY`                  | Authentification/découverte vLLM | Utilisation du fournisseur `vllm` (serveurs locaux compatibles OpenAI) | Toute valeur non vide suffit pour les serveurs locaux sans authentification                          |\n| `CURSOR_ACCESS_TOKEN`           | Authentification du fournisseur Cursor | Utilisation du fournisseur Cursor                             |                                                                                                     |\n| `AI_GATEWAY_API_KEY`            | Authentification Vercel AI Gateway | Utilisation du fournisseur `vercel-ai-gateway`                |                                                                                                     |\n| `CLOUDFLARE_AI_GATEWAY_API_KEY` | Authentification Cloudflare AI Gateway | Utilisation du fournisseur `cloudflare-ai-gateway`            | L'URL de base doit être configurée comme `https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic` |\n\n### Chaînes de tokens GitHub/Copilot\n\n| Variable | Utilisée pour | Chaîne |\n|---|---|---|\n| `COPILOT_GITHUB_TOKEN` | Authentification du fournisseur GitHub Copilot | `COPILOT_GITHUB_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN` |\n| `GH_TOKEN` | Solution de repli Copilot ; authentification API GitHub dans le scraper web | Dans le scraper web : `GITHUB_TOKEN` → `GH_TOKEN` |\n| `GITHUB_TOKEN` | Solution de repli Copilot ; authentification API GitHub dans le scraper web | Dans le scraper web : vérifiée avant `GH_TOKEN` |\n\n---\n\n## 2) Configuration d'exécution spécifique aux fournisseurs\n\n### Passerelle Anthropic Foundry (Azure / proxy entreprise)\n\nLorsque `CLAUDE_CODE_USE_FOUNDRY` est activé, les requêtes Anthropic passent en mode Foundry :\n\n- L'URL de base se résout depuis `FOUNDRY_BASE_URL` (l'URL de base par défaut du modèle est utilisée si non définie).\n- La résolution de la clé API pour le fournisseur `anthropic` devient :\n  `ANTHROPIC_FOUNDRY_API_KEY` → `ANTHROPIC_OAUTH_TOKEN` → `ANTHROPIC_API_KEY`.\n- `ANTHROPIC_CUSTOM_HEADERS` est analysée comme des paires `clé: valeur` séparées par des virgules/sauts de ligne et fusionnées dans les en-têtes de requête.\n- Le matériel TLS client/serveur peut être injecté depuis les valeurs d'environnement :\n  `NODE_EXTRA_CA_CERTS`, `CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`.\n  Chacune accepte soit :\n  - un chemin de système de fichiers vers du contenu PEM, soit\n  - du PEM en ligne (y compris les séquences `\\n` échappées).\n\n| Variable | Type de valeur | Comportement |\n|---|---|---|\n| `CLAUDE_CODE_USE_FOUNDRY` | Chaîne de type booléen (`1`, `true`, `yes`, `on`) | Active le mode Foundry pour le fournisseur Anthropic |\n| `FOUNDRY_BASE_URL` | Chaîne URL | URL de base du point de terminaison Anthropic en mode Foundry |\n| `ANTHROPIC_FOUNDRY_API_KEY` | Chaîne de token | Utilisée pour `Authorization: Bearer <token>` |\n| `ANTHROPIC_CUSTOM_HEADERS` | Chaîne de liste d'en-têtes | En-têtes supplémentaires ; format `header-a: valeur, header-b: valeur` ou séparés par des sauts de ligne |\n| `NODE_EXTRA_CA_CERTS` | Chemin PEM ou PEM en ligne | Chaîne CA supplémentaire pour la validation du certificat serveur |\n| `CLAUDE_CODE_CLIENT_CERT` | Chemin PEM ou PEM en ligne | Certificat client mTLS |\n| `CLAUDE_CODE_CLIENT_KEY` | Chemin PEM ou PEM en ligne | Clé privée client mTLS (doit être associée au certificat) |\n\n### Amazon Bedrock\n\n| Variable | Valeur par défaut / comportement |\n|---|---|\n| `AWS_REGION` | Source de région principale |\n| `AWS_DEFAULT_REGION` | Solution de repli si `AWS_REGION` n'est pas définie |\n| `AWS_PROFILE` | Active le chemin d'authentification par profil nommé |\n| `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` | Active le chemin d'authentification par clé IAM |\n| `AWS_BEARER_TOKEN_BEDROCK` | Active le chemin d'authentification par token porteur |\n| `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` / `AWS_CONTAINER_CREDENTIALS_FULL_URI` | Active le chemin d'identifiants de tâche ECS |\n| `AWS_WEB_IDENTITY_TOKEN_FILE` + `AWS_ROLE_ARN` | Active le chemin d'authentification par identité web |\n| `AWS_BEDROCK_SKIP_AUTH` | Si `1`, injecte des identifiants factices (scénarios proxy/sans authentification) |\n| `AWS_BEDROCK_FORCE_HTTP1` | Si `1`, force le gestionnaire de requêtes HTTP/1 de Node |\n\nSolution de repli de région dans le code du fournisseur : `options.region` → `AWS_REGION` → `AWS_DEFAULT_REGION` → `us-east-1`.\n\n### Azure OpenAI Responses\n\n| Variable | Valeur par défaut / comportement |\n|---|---|\n| `AZURE_OPENAI_API_KEY` | Requise sauf si la clé API est passée en option |\n| `AZURE_OPENAI_API_VERSION` | Par défaut `v1` |\n| `AZURE_OPENAI_BASE_URL` | Remplacement direct de l'URL de base |\n| `AZURE_OPENAI_RESOURCE_NAME` | Utilisée pour construire l'URL de base : `https://<resource>.openai.azure.com/openai/v1` |\n| `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` | Chaîne de mappage optionnelle : `modelId=deploymentName,model2=deployment2` |\n\nRésolution de l'URL de base : option `azureBaseUrl` → env `AZURE_OPENAI_BASE_URL` → option/env resource name → `model.baseUrl`.\n\n### Google Vertex AI\n\n| Variable | Requise ? | Notes |\n|---|---|---|\n| `GOOGLE_CLOUD_PROJECT` | Oui (sauf si passée dans les options) | Solution de repli : `GCLOUD_PROJECT` |\n| `GCLOUD_PROJECT` | Solution de repli | Utilisée comme source alternative d'ID de projet |\n| `GOOGLE_CLOUD_LOCATION` | Oui (sauf si passée dans les options) | Pas de valeur par défaut dans le fournisseur |\n| `GOOGLE_APPLICATION_CREDENTIALS` | Conditionnelle | Si définie, le fichier doit exister ; sinon le chemin de repli ADC est vérifié (`~/.config/gcloud/application_default_credentials.json`) |\n\n### Kimi\n\n| Variable | Valeur par défaut / comportement |\n|---|---|\n| `KIMI_CODE_OAUTH_HOST` | Remplacement principal de l'hôte OAuth |\n| `KIMI_OAUTH_HOST` | Remplacement de l'hôte OAuth en solution de repli |\n| `KIMI_CODE_BASE_URL` | Remplace l'URL de base du point de terminaison d'utilisation Kimi (`usage/kimi.ts`) |\n\nChaîne de l'hôte OAuth : `KIMI_CODE_OAUTH_HOST` → `KIMI_OAUTH_HOST` → `https://auth.kimi.com`.\n\n### Compatibilité Antigravity/Gemini image\n\n| Variable | Valeur par défaut / comportement |\n|---|---|\n| `PI_AI_ANTIGRAVITY_VERSION` | Remplace le tag de version user-agent Antigravity dans le fournisseur Gemini CLI |\n\n### OpenAI Codex responses (contrôles de fonctionnalité/débogage)\n\n| Variable | Comportement |\n|---|---|\n| `PI_CODEX_DEBUG` | `1`/`true` active la journalisation de débogage du fournisseur Codex |\n| `PI_CODEX_WEBSOCKET` | `1`/`true` active la préférence de transport websocket |\n| `PI_CODEX_WEBSOCKET_V2` | `1`/`true` active le chemin websocket v2 |\n| `PI_CODEX_WEBSOCKET_IDLE_TIMEOUT_MS` | Remplacement par entier positif (par défaut 300000) |\n| `PI_CODEX_WEBSOCKET_RETRY_BUDGET` | Remplacement par entier non négatif (par défaut 5) |\n| `PI_CODEX_WEBSOCKET_RETRY_DELAY_MS` | Remplacement du backoff de base par entier positif (par défaut 500) |\n\n### Débogage du fournisseur Cursor\n\n| Variable | Comportement |\n|---|---|\n| `DEBUG_CURSOR` | Active les journaux de débogage du fournisseur ; `2`/`verbose` pour des extraits de charge utile détaillés |\n| `DEBUG_CURSOR_LOG` | Chemin de fichier optionnel pour la sortie du journal de débogage JSONL |\n\n### Commutateur de compatibilité du cache de prompts\n\n| Variable | Comportement |\n|---|---|\n| `PI_CACHE_RETENTION` | Si `long`, active la rétention longue lorsqu'elle est prise en charge (`anthropic`, `openai-responses`, résolution de rétention Bedrock) |\n\n---\n\n## 3) Sous-système de recherche web\n\n### Identifiants des fournisseurs de recherche\n\n| Variable | Utilisée par |\n|---|---|\n| `EXA_API_KEY` | Fournisseur de recherche Exa et outils MCP Exa |\n| `BRAVE_API_KEY` | Fournisseur de recherche Brave |\n| `PERPLEXITY_API_KEY` | Mode clé API du fournisseur de recherche Perplexity |\n| `TAVILY_API_KEY` | Fournisseur de recherche Tavily |\n| `ZAI_API_KEY` | Fournisseur de recherche z.ai (vérifie également l'OAuth stocké dans `agent.db`) |\n| `OPENAI_API_KEY` / OAuth Codex dans la BD | Disponibilité/authentification du fournisseur de recherche Codex |\n\n### Chaîne d'authentification de la recherche web Anthropic\n\n`packages/coding-agent/src/web/search/auth.ts` résout les identifiants de recherche web Anthropic dans cet ordre :\n\n1. `ANTHROPIC_SEARCH_API_KEY` (+ `ANTHROPIC_SEARCH_BASE_URL` optionnelle)\n2. Entrée de fournisseur `models.json` avec `api: \"anthropic-messages\"`\n3. Identifiants OAuth Anthropic depuis `agent.db` (ne doivent pas expirer dans un délai tampon de 5 minutes)\n4. Solution de repli env Anthropic générique : clé fournisseur (`ANTHROPIC_FOUNDRY_API_KEY`/`ANTHROPIC_OAUTH_TOKEN`/`ANTHROPIC_API_KEY`) + `ANTHROPIC_BASE_URL` optionnelle (`FOUNDRY_BASE_URL` lorsque le mode Foundry est activé)\n\nVariables associées :\n\n| Variable | Valeur par défaut / comportement |\n|---|---|\n| `ANTHROPIC_SEARCH_API_KEY` | Clé de recherche explicite de plus haute priorité |\n| `ANTHROPIC_SEARCH_BASE_URL` | Par défaut `https://api.anthropic.com` si omise |\n| `ANTHROPIC_SEARCH_MODEL` | Par défaut `claude-haiku-4-5` |\n| `ANTHROPIC_BASE_URL` | URL de base de repli générique pour le chemin d'authentification de niveau 4 |\n\n### Drapeau de comportement du flux OAuth Perplexity\n\n| Variable | Comportement |\n|---|---|\n| `PI_AUTH_NO_BORROW` | Si définie, désactive le chemin d'emprunt de token d'application native macOS dans le flux de connexion Perplexity |\n\n---\n\n## 4) Outillage Python et environnement d'exécution du noyau\n\n| Variable | Valeur par défaut / comportement |\n|---|---|\n| `PI_PY` | Remplacement du mode d'outil Python : `0`/`bash`=`bash-only`, `1`/`py`=`ipy-only`, `mix`/`both`=`both` ; les valeurs invalides sont ignorées |\n| `PI_PYTHON_SKIP_CHECK` | Si `1`, ignore les vérifications de disponibilité/préchauffage du noyau Python |\n| `PI_PYTHON_GATEWAY_URL` | Si définie, utilise une passerelle de noyau externe au lieu de la passerelle partagée locale |\n| `PI_PYTHON_GATEWAY_TOKEN` | Token d'authentification optionnel pour la passerelle externe (`Authorization: token <value>`) |\n| `PI_PYTHON_IPC_TRACE` | Si `1`, active le chemin de trace IPC de bas niveau dans le module noyau |\n| `VIRTUAL_ENV` | Chemin venv de plus haute priorité pour la résolution de l'environnement Python |\n\nComportement conditionnel supplémentaire :\n\n- Si `BUN_ENV=test` ou `NODE_ENV=test`, les vérifications de disponibilité Python sont considérées comme OK et le préchauffage est ignoré.\n- Le filtrage de l'environnement Python refuse les clés API courantes et autorise les variables de base sûres + les préfixes `LC_`, `XDG_`, `PI_`.\n\n---\n\n## 5) Bascules de comportement agent/exécution\n\n| Variable                   | Valeur par défaut / comportement                                                             |\n|----------------------------|----------------------------------------------------------------------------------------------|\n| `PI_SMOL_MODEL`            | Remplacement éphémère de rôle de modèle pour `smol` (CLI `--smol` est prioritaire)          |\n| `PI_SLOW_MODEL`            | Remplacement éphémère de rôle de modèle pour `slow` (CLI `--slow` est prioritaire)          |\n| `PI_PLAN_MODEL`            | Remplacement éphémère de rôle de modèle pour `plan` (CLI `--plan` est prioritaire)          |\n| `PI_NO_TITLE`              | Si définie (toute valeur non vide), désactive la génération automatique du titre de session au premier message utilisateur |\n| `NULL_PROMPT`              | Si `true`, le constructeur de prompt système retourne une chaîne vide                        |\n| `PI_BLOCKED_AGENT`         | Bloque un type de sous-agent spécifique dans l'outil de tâche                                |\n| `PI_SUBPROCESS_CMD`        | Remplace la commande de lancement de sous-agent (contournement de la résolution `xcsh` / `xcsh.cmd`) |\n| `PI_TASK_MAX_OUTPUT_BYTES` | Nombre maximum d'octets de sortie capturés par sous-agent (par défaut `500000`)              |\n| `PI_TASK_MAX_OUTPUT_LINES` | Nombre maximum de lignes de sortie capturées par sous-agent (par défaut `5000`)              |\n| `PI_TIMING`                | Si `1`, active les journaux d'instrumentation de chronométrage démarrage/outil               |\n| `PI_DEBUG_STARTUP`         | Active les impressions de débogage des étapes de démarrage sur stderr dans plusieurs chemins de démarrage |\n| `PI_PACKAGE_DIR`           | Remplace la résolution du répertoire de base des actifs du package (recherche de chemins docs/exemples/changelog) |\n| `PI_DISABLE_LSPMUX`        | Si `1`, désactive la détection/intégration lspmux et force le lancement direct du serveur LSP |\n| `LITELLM_BASE_URL`         | URL de base du proxy LiteLLM. Lorsque définie avec `LITELLM_API_KEY`, déclenche la génération automatique de `models.yml` au premier lancement et l'auto-réparation à chaque démarrage |\n| `LM_STUDIO_BASE_URL`       | Remplacement de l'URL de base de découverte implicite par défaut de LM Studio (`http://127.0.0.1:1234/v1` si non définie) |\n| `OLLAMA_BASE_URL`          | Remplacement de l'URL de base de découverte implicite par défaut d'Ollama (`http://127.0.0.1:11434` si non définie) |\n| `LLAMA_CPP_BASE_URL`       | Remplacement de l'URL de base de découverte implicite par défaut de Llama.cpp (`http://127.0.0.1:8080` si non définie) |\n| `PI_EDIT_VARIANT`          | Si `hashline`, force le mode d'affichage hashline read/grep lorsque l'outil d'édition est disponible |\n| `PI_NO_PTY`                | Si `1`, désactive le chemin PTY interactif pour l'outil bash                                 |\n\n`PI_NO_PTY` est également définie en interne lorsque CLI `--no-pty` est utilisé.\n\n---\n\n## 6) Chemins racine de stockage et de configuration\n\nCeux-ci sont consommés via `@f5-sales-demo/pi-utils/dirs` et affectent l'emplacement de stockage des données de coding-agent.\n\n| Variable | Valeur par défaut / comportement |\n|---|---|\n| `PI_CONFIG_DIR` | Nom du répertoire racine de configuration sous le répertoire personnel (par défaut `.xcsh`) |\n| `PI_CODING_AGENT_DIR` | Remplacement complet du répertoire de l'agent (par défaut `~/<PI_CONFIG_DIR ou .xcsh>/agent`) |\n| `PWD` | Utilisée pour la correspondance du répertoire de travail actuel canonique dans les utilitaires de chemin |\n\n---\n\n## 7) Environnement d'exécution shell/outil\n\n(Depuis `packages/utils/src/procmgr.ts` et l'intégration de l'outil bash de coding-agent.)\n\n| Variable | Comportement |\n|---|---|\n| `PI_BASH_NO_CI` | Supprime l'injection automatique de `CI=true` dans l'environnement shell créé |\n| `CLAUDE_BASH_NO_CI` | Alias hérité de solution de repli pour `PI_BASH_NO_CI` |\n| `PI_BASH_NO_LOGIN` | Destinée à désactiver le mode shell de connexion |\n| `CLAUDE_BASH_NO_LOGIN` | Alias hérité de solution de repli pour `PI_BASH_NO_LOGIN` |\n| `PI_SHELL_PREFIX` | Wrapper de préfixe de commande optionnel |\n| `CLAUDE_CODE_SHELL_PREFIX` | Alias hérité de solution de repli pour `PI_SHELL_PREFIX` |\n| `VISUAL` | Commande préférée de l'éditeur externe |\n| `EDITOR` | Commande de l'éditeur externe en solution de repli |\n\nNote d'implémentation actuelle : `PI_BASH_NO_LOGIN`/`CLAUDE_BASH_NO_LOGIN` sont lues, mais l'implémentation actuelle de `getShellArgs()` retourne `['-l','-c']` dans les deux branches (effectivement sans effet aujourd'hui).\n\n---\n\n## 8) Détection UI/thème/session (env auto-détecté)\n\nCelles-ci sont lues comme signaux d'exécution ; elles sont généralement définies par le terminal/système d'exploitation plutôt que configurées manuellement.\n\n| Variable | Utilisée pour |\n|---|---|\n| `COLORTERM`, `TERM`, `WT_SESSION` | Détection des capacités de couleur (mode de couleur du thème) |\n| `COLORFGBG` | Auto-détection clair/sombre de l'arrière-plan du terminal |\n| `TERM_PROGRAM`, `TERM_PROGRAM_VERSION`, `TERMINAL_EMULATOR` | Identité du terminal dans le prompt système/contexte |\n| `KDE_FULL_SESSION`, `XDG_CURRENT_DESKTOP`, `DESKTOP_SESSION`, `XDG_SESSION_DESKTOP`, `GDMSESSION`, `WINDOWMANAGER` | Détection du bureau/gestionnaire de fenêtres dans le prompt système/contexte |\n| `KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION` | Identifiants stables de fil d'Ariane de session par terminal |\n| `SHELL`, `ComSpec`, `TERM_PROGRAM`, `TERM` | Diagnostics d'informations système |\n| `APPDATA`, `XDG_CONFIG_HOME` | Résolution du chemin de configuration lspmux |\n| `HOME` | Raccourcissement de chemin dans l'interface de commande MCP |\n\n---\n\n## 9) Drapeaux de chargeur natif/débogage\n\n| Variable | Comportement |\n|---|---|\n| `PI_DEV` | Active les diagnostics verbeux de chargement d'addon natif dans `packages/natives` |\n\n## 10) Drapeaux d'exécution TUI (package partagé, affecte l'UX de coding-agent)\n\n| Variable | Comportement |\n|---|---|\n| `PI_NOTIFICATIONS` | `off` / `0` / `false` supprime les notifications de bureau |\n| `PI_TUI_WRITE_LOG` | Si définie, journalise les écritures TUI dans un fichier |\n| `PI_HARDWARE_CURSOR` | Si `1`, active le mode curseur matériel |\n| `PI_CLEAR_ON_SHRINK` | Si `1`, efface les lignes vides lorsque le contenu se réduit |\n| `PI_DEBUG_REDRAW` | Si `1`, active la journalisation de débogage du redessin |\n| `PI_TUI_DEBUG` | Si `1`, active le chemin de dump de débogage approfondi TUI |\n\n---\n\n## 11) Contrôles de génération de commits\n\n| Variable | Comportement |\n|---|---|\n| `PI_COMMIT_TEST_FALLBACK` | Si `true` (insensible à la casse), force le chemin de génération de commit de repli |\n| `PI_COMMIT_NO_FALLBACK` | Si `true`, désactive la solution de repli lorsque l'agent ne retourne aucune proposition |\n| `PI_COMMIT_MAP_REDUCE` | Si `false`, désactive le chemin d'analyse map-reduce des commits |\n| `DEBUG` | Si définie, les traces de pile d'erreurs de l'agent de commit sont affichées |\n\n---\n\n## Variables sensibles en matière de sécurité\n\nTraitez celles-ci comme des secrets ; ne les journalisez pas et ne les committez pas :\n\n- Clés API/fournisseur et identifiants OAuth/porteur (toutes les `*_API_KEY`, `*_TOKEN`, tokens d'accès/rafraîchissement OAuth)\n- Identifiants cloud (`AWS_*`, le chemin `GOOGLE_APPLICATION_CREDENTIALS` peut exposer du matériel de compte de service)\n- Variables d'authentification de recherche/fournisseur (`EXA_API_KEY`, `BRAVE_API_KEY`, `PERPLEXITY_API_KEY`, clés de recherche Anthropic)\n- Matériel mTLS Foundry (`CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`, `NODE_EXTRA_CA_CERTS` lorsqu'il pointe vers des bundles CA privés)\n\nL'environnement d'exécution Python supprime également explicitement de nombreuses variables de clés courantes avant de lancer les sous-processus de noyau (`packages/coding-agent/src/ipy/runtime.ts`).\n",
	"fr/configuration/fs-scan-cache-architecture.md": "---\ntitle: Architecture du cache de scan du système de fichiers\ndescription: >-\n  Contrat du cache de scan du système de fichiers pour une découverte rapide de\n  fichiers avec sémantique stale-while-revalidate.\nsidebar:\n  order: 8\n  label: Cache de scan du système de fichiers\ni18n:\n  sourceHash: 2a2bde1726ac\n  translator: machine\n---\n\n# Contrat d'architecture du cache de scan du système de fichiers\n\nCe document définit le contrat actuel du cache partagé de scan du système de fichiers implémenté en Rust (`crates/pi-natives/src/fs_cache.rs`) et consommé par les API natives de découverte/recherche exposées à `packages/coding-agent`.\n\n## Ce qu'est ce cache\n\nLe cache stocke des listes complètes d'entrées de scan de répertoires (`GlobMatch[]`) indexées par portée de scan et politique de parcours, puis permet aux opérations de niveau supérieur (filtrage glob, scoring flou, sélection de fichiers grep) de s'exécuter sur ces entrées mises en cache.\n\nObjectifs principaux :\n\n- éviter les parcours répétés du système de fichiers pour des appels répétés de découverte/recherche\n- maintenir la cohérence entre `glob`, `fuzzyFind` et `grep` lorsqu'ils partagent la même politique de scan\n- permettre la récupération explicite en cas d'obsolescence pour les résultats vides et l'invalidation explicite après des mutations de fichiers\n\n## Propriété et surface publique\n\n- Implémentation du cache et politique : `crates/pi-natives/src/fs_cache.rs`\n- Consommateurs natifs :\n  - `crates/pi-natives/src/glob.rs`\n  - `crates/pi-natives/src/fd.rs` (`fuzzyFind`)\n  - `crates/pi-natives/src/grep.rs`\n- Binding/export JS :\n  - `packages/natives/src/glob/index.ts` (`invalidateFsScanCache`)\n  - `packages/natives/src/glob/types.ts`\n  - `packages/natives/src/grep/types.ts`\n- Helpers d'invalidation de mutation du coding-agent :\n  - `packages/coding-agent/src/tools/fs-cache-invalidation.ts`\n\n## Partitionnement de la clé de cache (contrat strict)\n\nChaque entrée est indexée par :\n\n- chemin canonicalisé du répertoire `root`\n- booléen `include_hidden`\n- booléen `use_gitignore`\n\nImplications :\n\n- Les scans avec et sans fichiers cachés ne partagent **pas** d'entrées.\n- Les scans respectant le gitignore et ceux désactivant l'ignorance ne partagent **pas** d'entrées.\n- Les consommateurs doivent passer une sémantique stable pour le comportement hidden/gitignore ; changer l'un ou l'autre flag crée une partition de cache différente.\n\nL'inclusion de `node_modules` n'est **pas** dans la clé de cache. Le cache stocke les entrées avec `node_modules` inclus ; le filtrage par consommateur est appliqué après la récupération.\n\n## Comportement de collecte du scan\n\nLe peuplement du cache utilise un walker déterministe (`ignore::WalkBuilder`) configuré par `include_hidden` et `use_gitignore` :\n\n- `follow_links(false)`\n- trié par chemin de fichier\n- `.git` est toujours ignoré\n- `node_modules` est toujours collecté au moment du scan du cache (et optionnellement filtré ensuite)\n- le type de fichier d'entrée + `mtime` sont capturés via `symlink_metadata`\n\nLes racines de recherche sont résolues par `resolve_search_path` :\n\n- les chemins relatifs sont résolus par rapport au cwd courant\n- la cible doit être un répertoire existant\n- la racine est canonicalisée quand c'est possible\n\n## Politique de fraîcheur et d'éviction\n\nPolitique globale (configurable par variable d'environnement) :\n\n- `FS_SCAN_CACHE_TTL_MS` (défaut `1000`)\n- `FS_SCAN_EMPTY_RECHECK_MS` (défaut `200`)\n- `FS_SCAN_CACHE_MAX_ENTRIES` (défaut `16`)\n\nComportement :\n\n- `get_or_scan(...)`\n  - si le TTL est `0` : contournement complet du cache, toujours un scan frais (`cache_age_ms = 0`)\n  - sur un hit de cache dans le TTL : retourne les entrées en cache + `cache_age_ms` non nul\n  - sur un hit expiré : éviction de la clé, rescan, stockage d'une entrée fraîche\n- l'application du nombre maximum d'entrées se fait par éviction du plus ancien selon `created_at`\n\n## Revérification rapide des résultats vides (distincte des hits normaux)\n\nHit de cache normal :\n\n- un hit de cache dans le TTL retourne les entrées en cache et ne fait rien d'autre.\n\nRevérification rapide des résultats vides :\n\n- c'est une politique **côté appelant** utilisant `ScanResult.cache_age_ms`\n- si le résultat filtré/requêté est vide et que l'âge du scan en cache est d'au moins `empty_recheck_ms()`, l'appelant effectue un `force_rescan(...)` et réessaie\n- destiné à réduire les résultats faux négatifs obsolètes lorsque des fichiers ont été récemment ajoutés mais que le cache est encore dans le TTL\n\nConsommateurs actuels :\n\n- `glob` : revérifie quand les correspondances filtrées sont vides et que l'âge du scan dépasse le seuil\n- `fuzzyFind` (`fd.rs`) : revérifie uniquement quand la requête est non vide et que les correspondances scorées sont vides\n- `grep` : revérifie quand la liste de fichiers candidats sélectionnés est vide\n\n## Valeurs par défaut des consommateurs et utilisation du cache\n\nLe cache est optionnel sur toutes les API exposées (`cache?: boolean`, défaut `false`).\n\nValeurs par défaut actuelles dans les API natives :\n\n- `glob` : `hidden=false`, `gitignore=true`, `cache=false`\n- `fuzzyFind` : `hidden=false`, `gitignore=true`, `cache=false`\n- `grep` : `hidden=true`, `cache=false`, et le scan du cache utilise toujours `use_gitignore=true`\n\nAppelants du coding-agent aujourd'hui :\n\n- La découverte de candidats de mention à haut volume active le cache :\n  - `packages/coding-agent/src/utils/file-mentions.ts`\n  - profil : `hidden=true`, `gitignore=true`, `includeNodeModules=true`, `cache=true`\n- L'intégration `grep` au niveau outil désactive actuellement le cache de scan (`cache: false`) :\n  - `packages/coding-agent/src/tools/grep.ts`\n\n## Contrat d'invalidation\n\nPoint d'entrée d'invalidation natif :\n\n- `invalidateFsScanCache(path?: string)`\n  - avec `path` : supprime les entrées du cache dont la racine est un préfixe du chemin cible\n  - sans path : efface toutes les entrées du cache de scan\n\nDétails de gestion des chemins :\n\n- les chemins d'invalidation relatifs sont résolus par rapport au cwd\n- l'invalidation tente la canonicalisation\n- si la cible n'existe pas (par ex., suppression), le fallback canonicalise le parent et rattache le nom de fichier quand c'est possible\n- cela préserve le comportement d'invalidation pour les créations/suppressions/renommages où un côté peut ne pas exister\n\n## Responsabilités du flux de mutation du coding-agent\n\nLe code du coding-agent doit invalider après des mutations réussies du système de fichiers.\n\nHelpers centraux :\n\n- `invalidateFsScanAfterWrite(path)`\n- `invalidateFsScanAfterDelete(path)`\n- `invalidateFsScanAfterRename(oldPath, newPath)` (invalide les deux côtés quand les chemins diffèrent)\n\nPoints d'appel actuels des outils de mutation :\n\n- `packages/coding-agent/src/tools/write.ts`\n- `packages/coding-agent/src/patch/index.ts` (flux hashline/patch/replace)\n\nRègle : si un flux mute le contenu ou l'emplacement du système de fichiers et contourne ces helpers, des bugs d'obsolescence du cache sont à prévoir.\n\n## Ajouter un nouveau consommateur de cache en toute sécurité\n\nLors de l'introduction de l'utilisation du cache dans un nouveau chemin de scanner/recherche :\n\n1. **Utilisez des entrées de politique de scan stables**\n   - décidez d'abord la sémantique hidden/gitignore\n   - passez-les de manière cohérente à `get_or_scan`/`force_rescan` pour que les partitions de cache soient intentionnelles\n\n2. **Traitez les données du cache comme pré-filtrées uniquement par la politique de parcours**\n   - appliquez le filtrage spécifique à l'outil (patterns glob, filtres de type, règles node_modules) après la récupération\n   - ne supposez jamais que les entrées en cache reflètent déjà vos filtres de niveau supérieur\n\n3. **Implémentez la revérification rapide des résultats vides uniquement pour le risque de faux négatifs obsolètes**\n   - utilisez `scan.cache_age_ms >= empty_recheck_ms()`\n   - réessayez une fois avec `force_rescan(..., store=true, ...)`\n   - gardez ce chemin séparé de la logique normale de hit de cache\n\n4. **Respectez explicitement le mode sans cache**\n   - quand l'appelant désactive le cache, appelez `force_rescan(..., store=false, ...)`\n   - ne peuplez pas le cache partagé dans un chemin de requête sans cache\n\n5. **Connectez l'invalidation de mutation pour tout nouveau chemin d'écriture**\n   - après une écriture/édition/suppression/renommage réussi, appelez le helper d'invalidation du coding-agent\n   - pour un renommage/déplacement, invalidez à la fois l'ancien et le nouveau chemin\n\n6. **N'ajoutez pas de contrôles TTL par appel**\n   - le contrat actuel est une politique globale uniquement (configurée par env), pas de surcharge TTL par requête\n\n## Limites connues\n\n- La portée du cache est en mémoire au niveau du processus (`DashMap`), non persistée entre les redémarrages de processus.\n- Le cache stocke les entrées de scan, pas les résultats finaux des outils.\n- `glob`/`fuzzyFind`/`grep` partagent les entrées de scan uniquement quand les dimensions clés (`root`, `hidden`, `gitignore`) correspondent.\n- `.git` est toujours exclu au moment de la collecte du scan, indépendamment des options de l'appelant.\n",
	"fr/configuration/hooks.md": "---\ntitle: Hooks\ndescription: >-\n  Système de hooks pour l'automatisation des événements pré/post dans le cycle\n  de vie de l'agent de codage.\nsidebar:\n  order: 4\n  label: Hooks\ni18n:\n  sourceHash: cdbec10bc405\n  translator: machine\n---\n\n# Hooks\n\nCe document décrit le **code actuel du sous-système de hooks** dans `src/extensibility/hooks/*`.\n\n## État actuel dans le runtime\n\nLe paquet de hooks (`src/extensibility/hooks/`) est toujours exporté et utilisable comme surface d'API, mais le runtime CLI par défaut initialise désormais le chemin du **runner d'extension**. Dans le flux de démarrage actuel :\n\n- `--hook` est traité comme un alias de `--extension` (les chemins CLI sont fusionnés dans `additionalExtensionPaths`)\n- les outils sont encapsulés par `ExtensionToolWrapper`, et non par `HookToolWrapper`\n- les transformations de contexte et les émissions de cycle de vie passent par `ExtensionRunner`\n\nCe fichier documente donc l'implémentation du sous-système de hooks lui-même (types/loader/runner/wrapper), y compris le comportement et les contraintes hérités.\n\n## Fichiers clés\n\n- `src/extensibility/hooks/types.ts` — contexte de hook, types d'événements et contrats de résultat\n- `src/extensibility/hooks/loader.ts` — chargement de module et pont de découverte de hooks\n- `src/extensibility/hooks/runner.ts` — distribution d'événements, recherche de commandes et signalisation d'erreurs\n- `src/extensibility/hooks/tool-wrapper.ts` — wrapper d'interception d'outils pré/post\n- `src/extensibility/hooks/index.ts` — exports/réexports\n\n## Ce qu'est un module hook\n\nUn module hook doit exporter par défaut une factory :\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function hook(pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName === \"bash\" && String(event.input.command ?? \"\").includes(\"rm -rf\")) {\n   return { block: true, reason: \"blocked by policy\" };\n  }\n });\n}\n```\n\nLa factory peut :\n\n- enregistrer des gestionnaires d'événements avec `pi.on(...)`\n- envoyer des messages personnalisés persistants avec `pi.sendMessage(...)`\n- conserver un état non-LLM avec `pi.appendEntry(...)`\n- enregistrer des commandes slash via `pi.registerCommand(...)`\n- enregistrer des rendus de messages personnalisés via `pi.registerMessageRenderer(...)`\n- exécuter des commandes shell via `pi.exec(...)`\n\n## Découverte et chargement\n\n`discoverAndLoadHooks(configuredPaths, cwd)` effectue les opérations suivantes :\n\n1. Charger les hooks découverts depuis le registre de capacités (`loadCapability(\"hooks\")`)\n2. Ajouter les chemins configurés explicitement (dédupliqués par chemin absolu)\n3. Appeler `loadHooks(allPaths, cwd)`\n\n`loadHooks` importe ensuite chaque chemin et attend une fonction `default`.\n\n### Résolution de chemin\n\n`loader.ts` résout les chemins de hooks comme suit :\n\n- chemin absolu : utilisé tel quel\n- chemin `~` : développé\n- chemin relatif : résolu par rapport à `cwd`\n\n### Incompatibilité héritée importante\n\nLes fournisseurs de découverte pour `hookCapability` modélisent encore des fichiers de hooks shell de style pré/post (par exemple `.claude/hooks/pre/*`, `.xcsh/.../hooks/pre/*`).\n\nLe chargeur de hooks ici utilise l'import de module dynamique et nécessite une factory de hook JS/TS par défaut. Si un chemin de hook découvert n'est pas importable en tant que module, le chargement échoue et est signalé dans `LoadHooksResult.errors`.\n\n## Surfaces d'événements\n\nLes événements de hook sont fortement typés dans `types.ts`.\n\n### Événements de session\n\n- `session_start`\n- `session_before_switch` → peut retourner `{ cancel?: boolean }`\n- `session_switch`\n- `session_before_branch` → peut retourner `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_branch`\n- `session_before_compact` → peut retourner `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session.compacting` → peut retourner `{ context?: string[]; prompt?: string; preserveData?: Record<string, unknown> }`\n- `session_compact`\n- `session_before_tree` → peut retourner `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n- `session_tree`\n- `session_shutdown`\n\n### Événements agent/contexte\n\n- `context` → peut retourner `{ messages?: Message[] }`\n- `before_agent_start` → peut retourner `{ message?: { customType; content; display; details } }`\n- `agent_start`\n- `agent_end`\n- `turn_start`\n- `turn_end`\n- `auto_compaction_start`\n- `auto_compaction_end`\n- `auto_retry_start`\n- `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### Événements d'outils (modèle pré/post)\n\n- `tool_call` (pré-exécution) → peut retourner `{ block?: boolean; reason?: string }`\n- `tool_result` (post-exécution) → peut retourner `{ content?; details?; isError? }`\n\nIl s'agit du modèle d'interception pré/post au cœur du sous-système de hooks.\n\n```text\nFlux d'interception des outils par les hooks\n\nGestionnaires tool_call\n   │\n   ├─ any { block: true }? ── oui ──> throw (outil bloqué)\n   │\n   └─ non\n      │\n      ▼\n   exécution de l'outil sous-jacent\n      │\n      ├─ succès ──> les gestionnaires tool_result peuvent remplacer { content, details }\n      │\n      └─ erreur  ──> émettre tool_result(isError=true) puis relancer l'erreur d'origine\n```\n\n## Modèle d'exécution et sémantique de mutation\n\n### 1) Pré-exécution : `tool_call`\n\n`HookToolWrapper.execute()` émet `tool_call` avant l'exécution de l'outil.\n\n- si un gestionnaire retourne `{ block: true }`, l'exécution s'arrête\n- si un gestionnaire lève une exception, le wrapper échoue de façon sécurisée et bloque l'exécution\n- le `reason` retourné devient le texte de l'erreur levée\n\n### 2) Exécution de l'outil\n\nL'outil sous-jacent s'exécute normalement s'il n'est pas bloqué.\n\n### 3) Post-exécution : `tool_result`\n\nAprès un succès, le wrapper émet `tool_result` avec :\n\n- `toolName`, `toolCallId`, `input`\n- `content`\n- `details`\n- `isError: false`\n\nSi un gestionnaire retourne des substitutions :\n\n- `content` peut remplacer le contenu du résultat\n- `details` peut remplacer les détails du résultat\n\nEn cas d'échec de l'outil, le wrapper émet `tool_result` avec `isError: true` et le texte d'erreur en contenu, puis relance l'erreur d'origine.\n\n### Ce que les hooks peuvent muter\n\n- le contexte LLM pour un seul appel via `context` (chaîne de remplacement de `messages`)\n- le contenu/détails de sortie d'outil lors d'appels d'outils réussis (chemin `tool_result`)\n- le message injecté avant l'agent via `before_agent_start`\n- le comportement d'annulation/compaction personnalisée/arbre via `session_before_*` et `session.compacting`\n\n### Ce que les hooks ne peuvent pas muter dans cette implémentation\n\n- les paramètres d'entrée bruts de l'outil en place (uniquement bloquer/autoriser sur `tool_call`)\n- la continuation de l'exécution après des erreurs d'outil levées (le chemin d'erreur relance)\n- le statut final de succès/erreur dans le comportement du wrapper (le `isError` retourné est typé mais non appliqué par `HookToolWrapper`)\n\n## Ordre et comportement en cas de conflit\n\n### Ordre au niveau de la découverte\n\nLes fournisseurs de capacités sont triés par priorité (les plus élevées en premier). La déduplication se fait par clé de capacité, le premier l'emporte.\n\nPour `hooks`, la clé de capacité est `${type}:${tool}:${name}`. Les doublons masqués provenant de fournisseurs de priorité inférieure sont marqués et exclus de la liste découverte effective.\n\n### Ordre de chargement\n\n`discoverAndLoadHooks` construit une liste plate `allPaths`, dédupliquée par chemin absolu résolu, puis `loadHooks` itère dans cet ordre.\nL'ordre des fichiers dans chaque répertoire découvert dépend de la sortie de `readdir` ; le chargeur de hooks n'effectue pas de tri supplémentaire.\n\n### Ordre des gestionnaires au runtime\n\nDans `HookRunner`, l'ordre est déterministe par séquence d'enregistrement :\n\n1. ordre du tableau des hooks\n2. ordre d'enregistrement des gestionnaires par hook/événement\n\nComportement en cas de conflit par type d'événement :\n\n- `tool_call` : le dernier résultat retourné l'emporte sauf si un gestionnaire bloque ; le premier blocage court-circuite\n- `tool_result` : la dernière substitution retournée l'emporte (pas de court-circuit)\n- `context` : chaîné ; chaque gestionnaire reçoit la sortie de messages du gestionnaire précédent\n- `before_agent_start` : le premier message retourné est conservé ; les messages ultérieurs sont ignorés\n- `session_before_*` : le dernier résultat retourné est suivi ; `cancel: true` court-circuite immédiatement\n- `session.compacting` : le dernier résultat retourné l'emporte\n\nConflits de commandes/rendus :\n\n- `getCommand(name)` retourne la première correspondance parmi les hooks (le premier chargé l'emporte)\n- `getMessageRenderer(customType)` retourne la première correspondance\n- `getRegisteredCommands()` retourne toutes les commandes (sans déduplication)\n\n## Interactions UI (`HookContext.ui`)\n\n`HookUIContext` inclut :\n\n- `select`, `confirm`, `input`, `editor`\n- `notify`\n- `setStatus`\n- `custom`\n- `setEditorText`, `getEditorText`\n- getter `theme`\n\n`ctx.hasUI` indique si une interface utilisateur interactive est disponible.\n\nLors d'une exécution sans UI, le comportement par défaut du contexte sans opération est :\n\n- `select/input/editor` retournent `undefined`\n- `confirm` retourne `false`\n- `notify`, `setStatus`, `setEditorText` sont des no-ops\n- `getEditorText` retourne `\"\"`\n\n### Comportement de la ligne de statut\n\nLe texte de statut de hook défini via `ctx.ui.setStatus(key, text)` est :\n\n- stocké par clé\n- trié par nom de clé\n- assaini (`\\r`, `\\n`, `\\t` → espaces ; espaces répétés réduits)\n- joint et tronqué en largeur pour l'affichage\n\n## Propagation des erreurs et repli\n\n### Au chargement\n\n- module invalide ou export par défaut manquant → capturé dans `LoadHooksResult.errors`\n- le chargement continue pour les autres hooks\n\n### Au moment de l'événement\n\n`HookRunner.emit(...)` capture les erreurs de gestionnaire pour la plupart des événements et émet `HookError` aux écouteurs (`hookPath`, `event`, `error`), puis continue.\n\n`emitToolCall(...)` est plus strict : les erreurs de gestionnaire n'y sont pas absorbées ; elles se propagent à l'appelant. Dans `HookToolWrapper`, cela bloque l'appel d'outil (sécurité par défaut).\n\n## Exemples d'API réalistes\n\n### Bloquer les commandes bash dangereuses\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName !== \"bash\") return;\n  const cmd = String(event.input.command ?? \"\");\n  if (!cmd.includes(\"rm -rf\")) return;\n\n  if (!ctx.hasUI) return { block: true, reason: \"rm -rf blocked (no UI)\" };\n  const ok = await ctx.ui.confirm(\"Dangerous command\", `Allow: ${cmd}`);\n  if (!ok) return { block: true, reason: \"user denied command\" };\n });\n}\n```\n\n### Masquer la sortie d'outil en post-exécution\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_result\", async event => {\n  if (event.toolName !== \"read\" || event.isError) return;\n\n  const redacted = event.content.map(chunk => {\n   if (chunk.type !== \"text\") return chunk;\n   return { ...chunk, text: chunk.text.replaceAll(/API_KEY=\\S+/g, \"API_KEY=[REDACTED]\") };\n  });\n\n  return { content: redacted };\n });\n}\n```\n\n### Modifier le contexte du modèle par appel LLM\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"context\", async event => {\n  const filtered = event.messages.filter(msg => !(msg.role === \"custom\" && msg.customType === \"debug-only\"));\n  return { messages: filtered };\n });\n}\n```\n\n### Enregistrer une commande slash avec des méthodes de contexte sécurisées pour les commandes\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.registerCommand(\"handoff\", {\n  description: \"Create a new session with setup message\",\n  handler: async (_args, ctx) => {\n   await ctx.waitForIdle();\n   await ctx.newSession({\n    parentSession: ctx.sessionManager.getSessionFile(),\n    setup: async sm => {\n     sm.appendMessage({\n      role: \"user\",\n      content: [{ type: \"text\", text: \"Continue from prior session summary.\" }],\n      timestamp: Date.now(),\n     });\n    },\n   });\n  },\n });\n}\n```\n\n## Surface d'export\n\n`src/extensibility/hooks/index.ts` exporte :\n\n- les API de chargement (`discoverAndLoadHooks`, `loadHooks`)\n- le runner et le wrapper (`HookRunner`, `HookToolWrapper`)\n- tous les types de hooks\n- réexport de `execCommand`\n\nEt la racine du paquet (`src/index.ts`) réexporte les **types** de hooks comme surface de compatibilité héritée.\n",
	"fr/configuration/porting-from-pi-mono.md": "---\ntitle: 'Portage depuis pi-mono : Guide pratique de fusion'\ndescription: >-\n  Guide pratique pour migrer du code depuis le monorepo pi-mono vers la base de\n  code xcsh.\nsidebar:\n  order: 9\n  label: Portage depuis pi-mono\ni18n:\n  sourceHash: fd4e8c09303d\n  translator: machine\n---\n\n# Portage depuis pi-mono : Guide pratique de fusion\n\nCe guide est une liste de vérification reproductible pour porter des modifications de pi-mono vers ce dépôt.\nUtilisez-le pour toute fusion : fichier unique, branche de fonctionnalité ou synchronisation complète d'une version.\n\n## Dernier point de synchronisation\n\n**Commit :** `b21b42d032919de2f2e6920a76fa9a37c3920c0a`\n**Date :** 2026-03-22\n\nMettez à jour cette section après chaque synchronisation ; ne réutilisez pas la plage précédente.\n\nLorsque vous démarrez une nouvelle synchronisation, générez les patches depuis ce commit :\n\n```bash\ngit format-patch b21b42d032919de2f2e6920a76fa9a37c3920c0a..HEAD --stdout > changes.patch\n```\n\n## 0) Définir le périmètre\n\n- Identifiez la référence amont (commit, tag ou PR).\n- Listez les packages ou dossiers que vous prévoyez de modifier.\n- Décidez quelles fonctionnalités sont dans le périmètre et lesquelles sont intentionnellement exclues.\n\n## 1) Importer le code en toute sécurité\n\n- Privilégiez un diff propre et ciblé plutôt qu'une copie en bloc.\n- Évitez de copier les artéfacts de build ou les fichiers générés.\n- Si l'amont a ajouté de nouveaux fichiers, ajoutez-les explicitement et vérifiez leur contenu.\n\n## 2) Respecter les conventions d'extensions d'import\n\nLa plupart des sources TypeScript de production omettent `.js` dans les imports internes, mais certains points d'entrée de test/benchmark conservent `.js` pour la compatibilité ESM à l'exécution. Suivez le style existant du package local ; ne supprimez pas systématiquement les extensions.\n\n- Dans les sources de production de `packages/coding-agent`, gardez les imports internes sans extension sauf pour l'import d'assets non-TS.\n- Dans `packages/tui/test` et `packages/natives/bench`, conservez `.js` là où les fichiers environnants l'utilisent déjà.\n- Conservez les vraies extensions de fichier lorsque l'outillage l'exige (par ex. `.json`, `.css`, intégrations de texte `.md`).\n- Exemple : `import { x } from \"./foo.js\";` → `import { x } from \"./foo\";` (uniquement lorsque la convention du package est sans extension).\n\n## 3) Remplacer les scopes d'import\n\nL'amont utilise des scopes de package différents. Remplacez-les de manière cohérente.\n\n- Remplacez les anciens scopes par le scope local utilisé ici.\n- Exemples (ajustez en fonction des packages réellement portés) :\n  - `@mariozechner/pi-coding-agent` → `@f5-sales-demo/xcsh`\n  - `@mariozechner/pi-agent-core` → `@f5-sales-demo/pi-agent-core`\n  - `@mariozechner/pi-tui` → `@f5-sales-demo/pi-tui`\n  - `@mariozechner/pi-ai` → `@f5-sales-demo/pi-ai`\n\n## 4) Utiliser les API Bun lorsqu'elles améliorent celles de Node\n\nNous fonctionnons sur Bun. Remplacez les API Node uniquement lorsque Bun fournit une meilleure alternative.\n\n**À REMPLACER :**\n\n- Lancement de processus : `child_process.spawn` → Bun Shell `$` pour les commandes simples, `Bun.spawn`/`Bun.spawnSync` pour le streaming ou les tâches longues\n- E/S fichier : `fs.readFileSync` → `Bun.file().text()` / `Bun.write()`\n- Clients HTTP : `node-fetch`, `axios` → `fetch` natif\n- Hachage crypto : `node:crypto` → Web Crypto ou `Bun.hash`\n- SQLite : `better-sqlite3` → `bun:sqlite`\n- Chargement d'env : `dotenv` → Bun charge `.env` automatiquement\n\n**NE PAS REMPLACER (ces API fonctionnent correctement dans Bun) :**\n\n- `os.homedir()` — NE PAS remplacer par `Bun.env.HOME`, `Bun.env.HOME`, ou le littéral `\"~\"`\n- `os.tmpdir()` — NE PAS remplacer par `Bun.env.TMPDIR || \"/tmp\"` ou des chemins codés en dur\n- `fs.mkdtempSync()` — NE PAS remplacer par une construction manuelle de chemin\n- `path.join()`, `path.resolve()`, etc. — ces fonctions conviennent\n\n**Style d'import :** Utilisez le préfixe `node:` avec des imports de namespace uniquement (pas d'imports nommés depuis `node:fs` ou `node:path`).\n\n**Conventions Bun supplémentaires :**\n\n- Privilégiez Bun Shell `$` pour les commandes courtes sans streaming ; utilisez `Bun.spawn` uniquement lorsque vous avez besoin d'E/S en streaming ou de contrôle du processus.\n- Utilisez `Bun.file()`/`Bun.write()` pour les fichiers et `node:fs/promises` pour les répertoires.\n- Évitez les vérifications `Bun.file().exists()` ; utilisez la gestion `isEnoent` dans un try/catch.\n- Préférez `Bun.sleep(ms)` aux wrappers `setTimeout`.\n\n**Incorrect :**\n\n```typescript\n// CASSÉ : les variables d'env peuvent être undefined, \"~\" n'est pas résolu\nconst home = Bun.env.HOME || \"~\";\nconst tmp = Bun.env.TMPDIR || \"/tmp\";\n```\n\n**Correct :**\n\n```typescript\nimport * as os from \"node:os\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst configDir = path.join(os.homedir(), \".config\", \"myapp\");\nconst tempDir = fs.mkdtempSync(path.join(os.tmpdir(), \"myapp-\"));\n```\n\n## 5) Privilégier les intégrations Bun (pas de copie)\n\nNe copiez pas les assets d'exécution ni les fichiers vendors au moment du build.\n\n- Si l'amont copie des assets dans un dossier dist, remplacez par des intégrations compatibles Bun.\n- Les prompts sont des fichiers `.md` statiques ; utilisez les imports texte Bun (`with { type: \"text\" }`) et Handlebars au lieu de chaînes de prompt inline.\n- Utilisez `import.meta.dir` + `Bun.file` pour charger les ressources non-texte adjacentes.\n- Gardez les assets dans le dépôt et laissez le bundler les inclure.\n- Éliminez les scripts de copie sauf si l'utilisateur les demande explicitement.\n- Si l'amont lit un fichier de repli bundlé à l'exécution, remplacez les lectures du système de fichiers par un import d'intégration texte Bun.\n  - Exemple (repli des instructions Codex) :\n    - `const FALLBACK_PROMPT_PATH = join(import.meta.dir, \"codex-instructions.md\");` -> supprimé\n    - `import FALLBACK_INSTRUCTIONS from \"./codex-instructions.md\" with { type: \"text\" };`\n    - Utilisez `return FALLBACK_INSTRUCTIONS;` au lieu de `readFileSync(FALLBACK_PROMPT_PATH, \"utf8\")`\n\n## 6) Porter `package.json` avec précaution\n\nTraitez `package.json` comme un contrat. Fusionnez intentionnellement.\n\n- Conservez les `name`, `version`, `type`, `exports` et `bin` existants sauf si le portage nécessite des modifications.\n- Remplacez les scripts npm/node par des équivalents Bun (par ex. `bun check`, `bun test`).\n- Assurez-vous que les dépendances utilisent le bon scope.\n- Ne rétrogradez pas les dépendances pour corriger des erreurs de type ; mettez à jour à la place.\n- Validez les liens de packages workspace et les `peerDependencies`.\n\n## 7) Aligner le style de code et l'outillage\n\n- Conservez les conventions de formatage existantes.\n- N'introduisez pas de `any` sauf si nécessaire.\n- Évitez les imports dynamiques et les imports de type inline ; utilisez uniquement des imports de premier niveau.\n- Ne construisez jamais de prompts dans le code ; les prompts sont des fichiers `.md` statiques rendus avec Handlebars.\n- Dans coding-agent, n'utilisez jamais `console.log`/`console.warn`/`console.error` ; utilisez `logger` depuis `@f5-sales-demo/pi-utils`.\n- Utilisez `Promise.withResolvers()` au lieu de `new Promise((resolve, reject) => ...)`.\n- **Pas de mots-clés `private`/`protected`/`public` sur les champs ou méthodes de classe.** Utilisez les champs privés ES `#` pour l'encapsulation ; laissez les membres accessibles sans mot-clé. La seule exception concerne les propriétés de paramètre de constructeur (`constructor(private readonly x: T)`), où le mot-clé est requis par TypeScript. Lors du portage de code amont utilisant `private foo` ou `protected bar`, convertissez en `#foo` (privé) ou `bar` nu (accessible).\n- Privilégiez les helpers et utilitaires existants plutôt que du code ad-hoc nouveau.\n- Préservez les modifications d'infrastructure Bun-first déjà réalisées dans ce dépôt :\n  - Le runtime est Bun (pas de points d'entrée Node).\n  - Le gestionnaire de packages est Bun (pas de lockfiles npm).\n  - Les API Node lourdes (`child_process`, `readline`) sont remplacées par des équivalents Bun.\n  - Les API Node légères (`os.homedir`, `os.tmpdir`, `fs.mkdtempSync`, `path.*`) sont conservées.\n  - Les shebangs CLI utilisent `bun` (ni `node`, ni `tsx`).\n  - Les packages utilisent directement les fichiers sources (pas d'étape de build TypeScript).\n  - Les workflows CI exécutent Bun pour install/check/test.\n\n## 8) Supprimer les anciennes couches de compatibilité\n\nSauf demande contraire, supprimez les shims de compatibilité amont.\n\n- Supprimez les anciennes API qui ont été remplacées.\n- Mettez à jour tous les sites d'appel vers la nouvelle API directement.\n- Ne conservez pas de versions `*_v2` ou parallèles.\n\n## 9) Mettre à jour la documentation et les références\n\n- Remplacez les liens vers le dépôt pi-mono là où c'est approprié.\n- Mettez à jour les exemples pour utiliser Bun et les bons scopes de package.\n- Assurez-vous que les instructions du README correspondent toujours au comportement actuel du dépôt.\n\n## 10) Valider le portage\n\nExécutez les vérifications standard après les modifications :\n\n- `bun check`\n\nSi le dépôt a déjà des vérifications en échec sans rapport avec vos modifications, signalez-le.\nLes tests utilisent le runner de Bun (pas Vitest), mais n'exécutez `bun test` que sur demande explicite.\n\n## 11) Protéger les fonctionnalités améliorées (liste des pièges de régression)\n\nSi vous avez déjà amélioré un comportement localement, traitez ces améliorations comme **non négociables**. Avant le portage, notez\nles améliorations et ajoutez des vérifications explicites pour qu'elles ne se perdent pas dans la fusion.\n\n- **Figez le comportement attendu** : ajoutez une note courte « avant/après » pour chaque amélioration (entrées, sorties,\n  valeurs par défaut, cas limites). Cela empêche un retour arrière silencieux.\n- **Mappez ancien → nouveau API** : si l'amont a renommé des concepts (hooks → extensions, custom tools → tools, etc.),\n  assurez-vous que chaque ancien point d'entrée est toujours connecté. Un flag ou export manqué équivaut à une fonctionnalité perdue.\n- **Vérifiez les exports** : contrôlez les `exports` de `package.json`, les types publics et les fichiers barrel. Les portages amont\n  oublient souvent de ré-exporter les ajouts locaux.\n- **Couvrez les chemins non nominaux** : si vous avez corrigé la gestion d'erreurs, les timeouts ou la logique de repli, ajoutez un test ou au\n  moins une checklist manuelle qui exerce ces chemins.\n- **Vérifiez les valeurs par défaut et l'ordre de fusion des configurations** : les améliorations résident souvent dans les valeurs par défaut. Confirmez que les nouvelles valeurs par défaut\n  n'ont pas été rétablies (par ex. nouvelle préséance de configuration, fonctionnalités désactivées, listes d'outils).\n- **Auditez le comportement env/shell** : si vous avez corrigé l'exécution ou le sandboxing, vérifiez que le nouveau chemin utilise toujours votre\n  environnement assaini et ne réintroduit pas de surcharges d'alias/fonctions.\n- **Ré-exécutez des exemples ciblés** : gardez un ensemble minimal d'exemples « connus comme bons » et exécutez-les après le portage\n  (flags CLI, enregistrement d'extensions, exécution d'outils).\n\n## 12) Détecter et gérer le code remanié\n\nAvant de porter un fichier, vérifiez si l'amont l'a significativement refactorisé :\n\n```bash\n# Comparez le fichier que vous êtes sur le point de porter avec ce que vous avez localement\ngit diff HEAD upstream/main -- path/to/file.ts\n```\n\nSi le diff montre que le fichier a été **remanié** (pas simplement corrigé) :\n\n- Nouvelles abstractions, concepts renommés, modules fusionnés, flux de données modifié\n\nAlors vous devez **lire la nouvelle implémentation en détail** avant de porter. La fusion aveugle de code remanié fait perdre des fonctionnalités car :\n\nNote : le mode interactif a récemment été découpé en controllers/utils/types. Lors du rétro-portage de modifications associées, portez les mises à jour dans les fichiers individuels que nous avons créés et assurez-vous que le câblage de `interactive-mode.ts` reste synchronisé.\n\n1. **Les valeurs par défaut changent silencieusement** - Une nouvelle variable `defaultFoo = [a, b]` peut remplacer un ancien `getAllFoo()` qui retournait `[a, b, c, d, e]`.\n\n2. **Les options d'API sont supprimées** - Lorsque des systèmes fusionnent (par ex. `hooks` + `customTools` → `extensions`), les anciennes options peuvent ne pas être connectées à la nouvelle implémentation.\n\n3. **Des chemins de code deviennent obsolètes** - Un concept renommé (par ex. `hookMessage` → `custom`) nécessite des mises à jour dans chaque instruction switch, garde de type et gestionnaire — pas seulement dans la définition.\n\n4. **Le contexte/les capacités se réduisent** - Les anciennes API pouvaient exposer `{ logger, typebox, pi }` que les nouvelles API ont oublié d'inclure.\n\n### Processus de portage sémantique\n\nLorsque l'amont a remanié un module :\n\n1. **Lisez l'ancienne implémentation** - Comprenez ce qu'elle faisait, quelles options elle acceptait, ce qu'elle exposait.\n\n2. **Lisez la nouvelle implémentation** - Comprenez les nouvelles abstractions et comment elles correspondent à l'ancien comportement.\n\n3. **Vérifiez la parité fonctionnelle** - Pour chaque capacité de l'ancien code, confirmez que le nouveau code la préserve ou la supprime explicitement.\n\n4. **Cherchez les oublis** - Recherchez les anciens noms/concepts qui ont pu être manqués dans les instructions switch, gestionnaires, composants UI.\n\n5. **Testez les frontières** - Flags CLI, options SDK, gestionnaires d'événements, valeurs par défaut — c'est là que les régressions se cachent.\n\n### Vérifications rapides\n\n```bash\n# Trouver toutes les utilisations d'un ancien concept qui pourrait nécessiter une mise à jour\nrg \"oldConceptName\" --type ts\n\n# Comparer les valeurs par défaut entre les versions\ngit show upstream/main:path/to/file.ts | rg \"default|DEFAULT\"\n\n# Vérifier si toutes les valeurs d'enum/union ont des gestionnaires\nrg \"case \\\"\" path/to/file.ts\n```\n\n## 13) Checklist d'audit rapide\n\nUtilisez ceci comme passe finale avant de terminer :\n\n- [ ] Les extensions d'import suivent la convention du package local (pas de suppression systématique de `.js`)\n- [ ] Pas d'API exclusives à Node dans le code nouveau/porté\n- [ ] Tous les scopes de package mis à jour\n- [ ] Les scripts de `package.json` utilisent Bun\n- [ ] Les prompts sont des imports texte `.md` (pas de chaînes de prompt inline)\n- [ ] Pas de `console.*` dans coding-agent (utiliser `logger`)\n- [ ] Les assets sont chargés via les patterns d'intégration Bun (pas de scripts de copie)\n- [ ] Les tests ou vérifications s'exécutent (ou sont explicitement signalés comme bloqués)\n- [ ] Pas de régressions fonctionnelles (voir sections 11-12)\n\n## 14) Format des messages de commit\n\nLors du commit d'un rétro-portage, suivez le format du dépôt `<type>(scope): <description au passé>` et conservez la plage de commits\ndans le titre.\n\n```\nfix(coding-agent): backported pi-mono changes (<from>..<to>)\n\npackages/<package>:\n- <type>: <description>\n- <type>: <description> (#<issue> by @<contributor>)\n\npackages/<other-package>:\n- <type>: <description>\n```\n\n**Exemple :**\n\n```\nfix(coding-agent): backported pi-mono changes (9f3eef65f..52532c7c0)\n\npackages/ai:\n- fix: handle \"sensitive\" stop reason from Anthropic API\n- fix: normalize tool call IDs with special characters for Responses API\n- fix: add overflow detection for Bedrock, MiniMax, Kimi providers\n- fix: 429 status is rate limiting, not context overflow\n\npackages/tui:\n- fix: refactored autocomplete state tracking\n- fix: file autocomplete should not trigger on empty text\n- fix: configurable autocomplete max visible items\n- fix: improved table column width calculation with word-aware wrapping\n\npackages/coding-agent:\n- fix: preserve external config.yml edits on save (#1046 by @nicobailonMD)\n- fix: resolve macOS NFD and curly quote variants in file paths\n```\n\n**Règles :**\n\n- Regroupez les modifications par package\n- Utilisez les types de commit conventionnels (`fix`, `feat`, `refactor`, `perf`, `docs`)\n- Incluez les numéros d'issue/PR amont et l'attribution des contributeurs pour les contributions externes\n- La plage de commits dans le titre aide à suivre les points de synchronisation\n\n## 15) Divergences intentionnelles\n\nNotre fork a des décisions architecturales qui diffèrent de l'amont. **Ne portez pas ces patterns amont :**\n\n### Architecture UI\n\n| Amont                                       | Notre fork                                                | Raison                                                                |\n| ------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------- |\n| Classe `FooterDataProvider`                 | `StatusLineComponent`                                     | Ligne de statut plus simple et intégrée                               |\n| `ctx.ui.setHeader()` / `ctx.ui.setFooter()` | Stub en modes non-TUI                                     | Implémenté dans TUI, no-op ailleurs                                   |\n| `ctx.ui.setEditorComponent()`               | Stub en modes non-TUI                                     | Implémenté dans TUI, no-op ailleurs                                   |\n| Objet d'options `InteractiveModeOptions`    | Arguments positionnels de constructeur (le type d'options reste exporté) | Conserver la signature du constructeur ; mettre à jour le type lorsque l'amont ajoute des champs |\n\n### Nommage des composants\n\n| Amont                        | Notre fork              |\n| ---------------------------- | ----------------------- |\n| `extension-input.ts`         | `hook-input.ts`         |\n| `extension-selector.ts`      | `hook-selector.ts`      |\n| `ExtensionInputComponent`    | `HookInputComponent`    |\n| `ExtensionSelectorComponent` | `HookSelectorComponent` |\n\n### Nommage des API\n\n| Amont                                    | Notre fork                               | Notes                                     |\n| ---------------------------------------- | ---------------------------------------- | ----------------------------------------- |\n| `sessionManager.appendSessionInfo(name)` | `sessionManager.setSessionName(name)`    | Nous utilisons `sessionName` partout      |\n| `sessionManager.getSessionName()`        | `sessionManager.getSessionName()`        | Identique (nous avons unifié pour correspondre au RPC de l'amont) |\n| `agent.sessionName` / `setSessionName()` | `agent.sessionName` / `setSessionName()` | Identique                                 |\n\n### Consolidation de fichiers\n\n| Amont                                              | Notre fork                              | Raison                                  |\n| -------------------------------------------------- | --------------------------------------- | --------------------------------------- |\n| `clipboard.ts` + `clipboard-image.ts` (fichiers d'outils) | Module clipboard de `@f5-sales-demo/pi-natives` | Fusionné dans l'implémentation native N-API |\n\n### Framework de test\n\n| Amont                     | Notre fork                    |\n| ------------------------- | ----------------------------- |\n| `vitest` avec `vi.mock()` | `bun:test` avec `vi` de bun  |\n| Assertions `node:test`    | Matchers `expect()`           |\n\n### Architecture des outils\n\n| Amont                               | Notre fork                                                        | Notes                                                     |\n| ----------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------- |\n| `createTool(cwd: string, options?)` | `createTools(session: ToolSession)` via le registre `BUILTIN_TOOLS` | Les factories d'outils acceptent `ToolSession` et peuvent retourner `null` |\n| Interfaces `*Operations` par outil  | Les interfaces par outil restent (`FindOperations`, `GrepOperations`) | Utilisées pour les surcharges SSH/distantes              |\n| `fs/promises` Node.js partout       | `Bun.file()`/`Bun.write()` pour les fichiers ; `node:fs/promises` pour les répertoires | Privilégier les API Bun quand elles simplifient          |\n\n### Stockage de l'authentification\n\n| Amont                           | Notre fork                                  | Notes                                        |\n| ------------------------------- | ------------------------------------------- | -------------------------------------------- |\n| `proper-lockfile` + `auth.json` | `agent.db` (bun:sqlite)                     | Identifiants stockés exclusivement dans `agent.db` |\n| Un seul identifiant par fournisseur | Multi-identifiants avec sélection round-robin | Affinité de session et logique de backoff préservées |\n\n### Extensions\n\n| Amont                             | Notre fork                                 |\n| --------------------------------- | ------------------------------------------ |\n| `jiti` pour le chargement TypeScript | `import()` natif de Bun                   |\n| Champ de manifeste `pkg.pi`       | `pkg.xcsh ?? pkg.pi` (privilégier notre namespace) |\n\n### Ignorer ces fonctionnalités amont\n\nLors du portage, **ignorez** ces fichiers/fonctionnalités entièrement :\n\n- `footer-data-provider.ts` — nous utilisons StatusLineComponent\n- `clipboard-image.ts` — le clipboard est dans le module N-API `@f5-sales-demo/pi-natives`\n- Fichiers de workflow GitHub — nous avons notre propre CI\n- `models.generated.ts` — auto-généré, regénérer localement (sous forme de models.json à la place)\n\n### Fonctionnalités que nous avons ajoutées (à préserver)\n\nCelles-ci existent dans notre fork mais pas dans l'amont. **Ne jamais écraser :**\n\n- `StatusLineComponent` en mode interactif\n- Authentification multi-identifiants avec affinité de session\n- Système de découverte basé sur les capacités (`defineCapability`, `registerProvider`, `loadCapability`, `skillCapability`, etc.)\n- Intégrations MCP/Exa/SSH\n- Writethrough LSP pour le formatage à la sauvegarde\n- Interception Bash (`checkBashInterception`)\n- Suggestions de chemin floues dans l'outil de lecture\n",
	"fr/configuration/rpc.md": "---\ntitle: Référence du protocole RPC\ndescription: >-\n  Référence du protocole JSON-RPC pour la communication inter-processus entre\n  les composants xcsh.\nsidebar:\n  order: 5\n  label: Protocole RPC\ni18n:\n  sourceHash: b4a3ddaf08ab\n  translator: machine\n---\n\n# Référence du protocole RPC\n\nLe mode RPC exécute l'agent de programmation en tant que protocole JSON délimité par des retours à la ligne sur stdio.\n\n- **stdin** : commandes (`RpcCommand`) et réponses d'interface utilisateur d'extension\n- **stdout** : réponses aux commandes (`RpcResponse`), événements de session/agent, requêtes d'interface utilisateur d'extension\n\nImplémentation principale :\n\n- `src/modes/rpc/rpc-mode.ts`\n- `src/modes/rpc/rpc-types.ts`\n- `src/session/agent-session.ts`\n- `packages/agent/src/agent.ts`\n- `packages/agent/src/agent-loop.ts`\n\n## Démarrage\n\n```bash\nxcsh --mode rpc [regular CLI options]\n```\n\nNotes de comportement :\n\n- Les arguments CLI `@file` sont rejetés en mode RPC.\n- Le mode RPC désactive par défaut la génération automatique du titre de session pour éviter un appel supplémentaire au modèle.\n- Le mode RPC réinitialise les paramètres `todo.*`, `task.*` et `async.*` modifiant le workflow à leurs valeurs par défaut intégrées au lieu d'hériter des surcharges utilisateur.\n- Le processus lit stdin en JSONL (`readJsonl(Bun.stdin.stream())`).\n- Lorsque stdin se ferme, le processus se termine avec le code `0`.\n- Les réponses/événements sont écrits sous forme d'un objet JSON par ligne.\n\n## Transport et cadrage\n\nChaque trame est un objet JSON unique suivi de `\\n`.\n\nIl n'y a pas d'enveloppe au-delà de la forme de l'objet lui-même.\n\n### Catégories de trames sortantes (stdout)\n\n1. `RpcResponse` (`{ type: \"response\", ... }`)\n2. Objets `AgentSessionEvent` (`agent_start`, `message_update`, etc.)\n3. `RpcExtensionUIRequest` (`{ type: \"extension_ui_request\", ... }`)\n4. Erreurs d'extension (`{ type: \"extension_error\", extensionPath, event, error }`)\n\n### Catégories de trames entrantes (stdin)\n\n1. `RpcCommand`\n2. `RpcExtensionUIResponse` (`{ type: \"extension_ui_response\", ... }`)\n\n## Corrélation requête/réponse\n\nToutes les commandes acceptent un `id?: string` optionnel.\n\n- S'il est fourni, les réponses normales aux commandes renvoient le même `id`.\n- `RpcClient` s'appuie sur ce mécanisme pour la résolution des requêtes en attente.\n\nComportement limite important du runtime :\n\n- Les réponses aux commandes inconnues sont émises avec `id: undefined` (même si la requête avait un `id`).\n- Les exceptions d'analyse/de gestionnaire dans la boucle d'entrée émettent `command: \"parse\"` avec `id: undefined`.\n- `prompt` et `abort_and_prompt` renvoient un succès immédiat, puis peuvent émettre une réponse d'erreur ultérieure avec le **même** id si la planification asynchrone du prompt échoue.\n\n## Schéma des commandes (canonique)\n\n`RpcCommand` est défini dans `src/modes/rpc/rpc-types.ts` :\n\n### Prompts\n\n- `{ id?, type: \"prompt\", message: string, images?: ImageContent[], streamingBehavior?: \"steer\" | \"followUp\" }`\n- `{ id?, type: \"steer\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"follow_up\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"abort\" }`\n- `{ id?, type: \"abort_and_prompt\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"new_session\", parentSession?: string }`\n\n### État\n\n- `{ id?, type: \"get_state\" }`\n- `{ id?, type: \"set_todos\", phases: TodoPhase[] }`\n- `{ id?, type: \"set_host_tools\", tools: RpcHostToolDefinition[] }`\n\n### Modèle\n\n- `{ id?, type: \"set_model\", provider: string, modelId: string }`\n- `{ id?, type: \"cycle_model\" }`\n- `{ id?, type: \"get_available_models\" }`\n\n### Réflexion\n\n- `{ id?, type: \"set_thinking_level\", level: ThinkingLevel }`\n- `{ id?, type: \"cycle_thinking_level\" }`\n\n### Modes de file d'attente\n\n- `{ id?, type: \"set_steering_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_follow_up_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_interrupt_mode\", mode: \"immediate\" | \"wait\" }`\n\n### Compaction\n\n- `{ id?, type: \"compact\", customInstructions?: string }`\n- `{ id?, type: \"set_auto_compaction\", enabled: boolean }`\n\n### Réessai\n\n- `{ id?, type: \"set_auto_retry\", enabled: boolean }`\n- `{ id?, type: \"abort_retry\" }`\n\n### Bash\n\n- `{ id?, type: \"bash\", command: string }`\n- `{ id?, type: \"abort_bash\" }`\n\n### Session\n\n- `{ id?, type: \"get_session_stats\" }`\n- `{ id?, type: \"export_html\", outputPath?: string }`\n- `{ id?, type: \"switch_session\", sessionPath: string }`\n- `{ id?, type: \"branch\", entryId: string }`\n- `{ id?, type: \"get_branch_messages\" }`\n- `{ id?, type: \"get_last_assistant_text\" }`\n- `{ id?, type: \"set_session_name\", name: string }`\n\n### Messages\n\n- `{ id?, type: \"get_messages\" }`\n\n## Schéma des réponses\n\nTous les résultats de commande utilisent `RpcResponse` :\n\n- Succès : `{ id?, type: \"response\", command: <command>, success: true, data?: ... }`\n- Échec : `{ id?, type: \"response\", command: string, success: false, error: string }`\n\nLes charges utiles de données sont spécifiques à chaque commande et définies dans `rpc-types.ts`.\n\n### Charge utile de `get_state`\n\n```json\n{\n  \"model\": { \"provider\": \"...\", \"id\": \"...\" },\n  \"thinkingLevel\": \"off|minimal|low|medium|high|xhigh\",\n  \"isStreaming\": false,\n  \"isCompacting\": false,\n  \"steeringMode\": \"all|one-at-a-time\",\n  \"followUpMode\": \"all|one-at-a-time\",\n  \"interruptMode\": \"immediate|wait\",\n  \"sessionFile\": \"...\",\n  \"sessionId\": \"...\",\n  \"sessionName\": \"...\",\n  \"autoCompactionEnabled\": true,\n  \"messageCount\": 0,\n  \"queuedMessageCount\": 0,\n  \"todoPhases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Todos\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the tool surface\",\n          \"status\": \"in_progress\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n### Charge utile de `set_todos`\n\nRemplace l'état des tâches en mémoire pour la session courante et renvoie la liste normalisée des phases :\n\n```json\n{\n  \"id\": \"req_2\",\n  \"type\": \"set_todos\",\n  \"phases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Evaluation\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the read tool surface\",\n          \"status\": \"in_progress\"\n        },\n        {\n          \"id\": \"task-2\",\n          \"content\": \"Exercise edit operations\",\n          \"status\": \"pending\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nCeci est utile pour les hôtes qui souhaitent pré-alimenter un plan avant le premier prompt.\n\n### Charge utile de `set_host_tools`\n\nRemplace l'ensemble actuel d'outils appartenant à l'hôte que le serveur RPC peut rappeler\nvia stdio :\n\n```json\n{\n  \"id\": \"req_3\",\n  \"type\": \"set_host_tools\",\n  \"tools\": [\n    {\n      \"name\": \"echo_host\",\n      \"label\": \"Echo Host\",\n      \"description\": \"Echo a value from the embedding host\",\n      \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"message\": { \"type\": \"string\" }\n        },\n        \"required\": [\"message\"],\n        \"additionalProperties\": false\n      }\n    }\n  ]\n}\n```\n\nLa charge utile de réponse est :\n\n```json\n{\n  \"toolNames\": [\"echo_host\"]\n}\n```\n\nCes outils sont ajoutés au registre d'outils de la session active avant le prochain appel\nau modèle. Renvoyer `set_host_tools` remplace l'ensemble précédent appartenant à l'hôte.\n\n## Schéma du flux d'événements\n\nLe mode RPC transmet les objets `AgentSessionEvent` depuis `AgentSession.subscribe(...)`.\n\nTypes d'événements courants :\n\n- `agent_start`, `agent_end`\n- `turn_start`, `turn_end`\n- `message_start`, `message_update`, `message_end`\n- `tool_execution_start`, `tool_execution_update`, `tool_execution_end`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n- `todo_auto_clear`\n\nLes erreurs du lanceur d'extensions sont émises séparément sous la forme :\n\n```json\n{ \"type\": \"extension_error\", \"extensionPath\": \"...\", \"event\": \"...\", \"error\": \"...\" }\n```\n\n`message_update` inclut les deltas de streaming dans `assistantMessageEvent` (deltas de texte/réflexion/appel d'outil).\n\n## Concurrence et ordonnancement des prompts/files d'attente\n\nC'est le comportement opérationnel le plus important.\n\n### Accusé de réception immédiat vs complétion\n\n`prompt` et `abort_and_prompt` sont **acquittés immédiatement** :\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n```\n\nCela signifie :\n\n- l'acceptation de la commande != la complétion de l'exécution\n- la complétion finale est observée via `agent_end`\n\n### Pendant le streaming\n\n`AgentSession.prompt()` nécessite `streamingBehavior` pendant le streaming actif :\n\n- `\"steer\"` => message de pilotage en file d'attente (chemin d'interruption)\n- `\"followUp\"` => message de suivi en file d'attente (chemin post-tour)\n\nSi omis pendant le streaming, le prompt échoue.\n\n### Valeurs par défaut des files d'attente\n\nDepuis le schéma de paramètres de l'agent de programmation (`packages/coding-agent/src/config/settings-schema.ts`) :\n\n- `steeringMode` : `\"one-at-a-time\"`\n- `followUpMode` : `\"one-at-a-time\"`\n- `interruptMode` : `\"wait\"`\n\n### Sémantique des modes\n\n- `set_steering_mode` / `set_follow_up_mode`\n  - `\"one-at-a-time\"` : retirer un message en file d'attente par tour\n  - `\"all\"` : retirer l'intégralité de la file d'attente en une fois\n- `set_interrupt_mode`\n  - `\"immediate\"` : l'exécution d'outil vérifie le pilotage entre les appels d'outils ; le pilotage en attente peut annuler les appels d'outils restants du tour\n  - `\"wait\"` : différer le pilotage jusqu'à la complétion du tour\n\n## Sous-protocole d'interface utilisateur des extensions\n\nLes extensions en mode RPC utilisent des trames requête/réponse d'interface utilisateur.\n\n### Requête sortante\n\nMéthodes de `RpcExtensionUIRequest` (`type: \"extension_ui_request\"`) :\n\n- `select`, `confirm`, `input`, `editor`\n- `notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text`\n\nNote sur le runtime :\n\n- La génération automatique du titre de session est désactivée en mode RPC, et les requêtes\n  d'interface utilisateur `setTitle` sont également supprimées par défaut car la plupart des hôtes\n  ne disposent pas d'une surface de titre de terminal significative. Définissez `PI_RPC_EMIT_TITLE=1`\n  pour réactiver uniquement l'événement d'interface utilisateur.\n\nExemple :\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"123\", \"method\": \"confirm\", \"title\": \"Confirm\", \"message\": \"Continue?\", \"timeout\": 30000 }\n```\n\n### Réponse entrante\n\n`RpcExtensionUIResponse` (`type: \"extension_ui_response\"`) :\n\n- `{ type: \"extension_ui_response\", id: string, value: string }`\n- `{ type: \"extension_ui_response\", id: string, confirmed: boolean }`\n- `{ type: \"extension_ui_response\", id: string, cancelled: true }`\n\nSi un dialogue a un délai d'expiration, le mode RPC résout vers une valeur par défaut lorsque le délai/l'annulation se déclenche.\n\n## Sous-protocole des outils hôte\n\nLes hôtes RPC peuvent exposer des outils personnalisés à l'agent en envoyant `set_host_tools`, puis\nen servant les requêtes d'exécution sur le même transport.\n\n### Requête sortante\n\nLorsque l'agent veut que l'hôte exécute l'un de ces outils, le mode RPC émet :\n\n```json\n{\n  \"type\": \"host_tool_call\",\n  \"id\": \"host_1\",\n  \"toolCallId\": \"toolu_123\",\n  \"toolName\": \"echo_host\",\n  \"arguments\": { \"message\": \"hello\" }\n}\n```\n\nSi l'exécution de l'outil est ensuite annulée, le mode RPC émet :\n\n```json\n{\n  \"type\": \"host_tool_cancel\",\n  \"id\": \"host_cancel_1\",\n  \"targetId\": \"host_1\"\n}\n```\n\n### Mises à jour entrantes et complétion\n\nLes hôtes peuvent optionnellement diffuser la progression :\n\n```json\n{\n  \"type\": \"host_tool_update\",\n  \"id\": \"host_1\",\n  \"partialResult\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"working\" }]\n  }\n}\n```\n\nLa complétion utilise :\n\n```json\n{\n  \"type\": \"host_tool_result\",\n  \"id\": \"host_1\",\n  \"result\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"done\" }]\n  }\n}\n```\n\nDéfinissez `isError: true` sur `host_tool_result` pour exposer le contenu retourné comme une\nerreur d'outil.\n\n## Modèle d'erreurs et récupérabilité\n\n### Échecs au niveau des commandes\n\nLes échecs ont `success: false` avec une `error` de type chaîne de caractères.\n\n```json\n{ \"id\": \"req_2\", \"type\": \"response\", \"command\": \"set_model\", \"success\": false, \"error\": \"Model not found: provider/model\" }\n```\n\n### Attentes de récupérabilité\n\n- La plupart des échecs de commande sont récupérables ; le processus reste actif.\n- Les JSONL malformés / exceptions de la boucle d'analyse émettent une réponse d'erreur `parse` et continuent à lire les lignes suivantes.\n- Un `set_session_name` vide est rejeté (`Session name cannot be empty`).\n- Les réponses d'interface utilisateur d'extension avec un `id` inconnu sont ignorées.\n- Les conditions de terminaison du processus sont la fermeture de stdin ou un arrêt explicitement déclenché par une extension.\n\n## Flux compacts de commandes\n\n### 1) Prompt et streaming\n\nstdin :\n\n```json\n{ \"id\": \"req_1\", \"type\": \"prompt\", \"message\": \"Summarize this repo\" }\n```\n\nSéquence stdout (typique) :\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n{ \"type\": \"agent_start\" }\n{ \"type\": \"message_update\", \"assistantMessageEvent\": { \"type\": \"text_delta\", \"delta\": \"...\" }, \"message\": { \"role\": \"assistant\", \"content\": [] } }\n{ \"type\": \"agent_end\", \"messages\": [] }\n```\n\n### 2) Prompt pendant le streaming avec politique de file d'attente explicite\n\nstdin :\n\n```json\n{ \"id\": \"req_2\", \"type\": \"prompt\", \"message\": \"Also include risks\", \"streamingBehavior\": \"followUp\" }\n```\n\n### 3) Inspecter et ajuster le comportement de la file d'attente\n\nstdin :\n\n```json\n{ \"id\": \"q1\", \"type\": \"get_state\" }\n{ \"id\": \"q2\", \"type\": \"set_steering_mode\", \"mode\": \"all\" }\n{ \"id\": \"q3\", \"type\": \"set_interrupt_mode\", \"mode\": \"wait\" }\n```\n\n### 4) Aller-retour d'interface utilisateur d'extension\n\nstdout :\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"ui_7\", \"method\": \"input\", \"title\": \"Branch name\", \"placeholder\": \"feature/...\" }\n```\n\nstdin :\n\n```json\n{ \"type\": \"extension_ui_response\", \"id\": \"ui_7\", \"value\": \"feature/rpc-host\" }\n```\n\n## Notes sur l'utilitaire `RpcClient`\n\n`src/modes/rpc/rpc-client.ts` est un wrapper de commodité, pas la définition du protocole.\n\nCaractéristiques actuelles de l'utilitaire :\n\n- Lance `bun <cliPath> --mode rpc`\n- Corrèle les réponses par des identifiants générés `req_<n>`\n- Ne distribue que les types `AgentEvent` reconnus aux écouteurs\n- Prend en charge les outils personnalisés appartenant à l'hôte via `setCustomTools()` et la gestion automatique de `host_tool_call` / `host_tool_cancel`\n- N'expose **pas** de méthodes utilitaires pour chaque commande du protocole (par exemple, `set_interrupt_mode` et `set_session_name` sont dans les types du protocole mais ne sont pas encapsulés en tant que méthodes dédiées)\n\nUtilisez les trames brutes du protocole si vous avez besoin d'une couverture complète de la surface.\n",
	"fr/configuration/sdk.md": "---\ntitle: SDK\ndescription: >-\n  SDK pour la création d'agents personnalisés et d'intégrations sur le runtime\n  de l'agent de codage xcsh.\nsidebar:\n  order: 6\n  label: SDK\ni18n:\n  sourceHash: 80f3a4374241\n  translator: machine\n---\n\n# SDK\n\nLe SDK est la surface d'intégration en processus pour `@f5-sales-demo/xcsh`.\nUtilisez-le lorsque vous souhaitez un accès direct à l'état de l'agent, au flux d'événements, au câblage des outils et au contrôle de session depuis votre propre processus Bun/Node.\n\nSi vous avez besoin d'une isolation inter-langages ou inter-processus, utilisez plutôt le mode RPC.\n\n## Installation\n\n```bash\nbun add @f5-sales-demo/xcsh\n```\n\n## Points d'entrée\n\n`@f5-sales-demo/xcsh` exporte les API du SDK depuis la racine du paquet (ainsi que via `@f5-sales-demo/xcsh/sdk`).\n\nExports principaux pour les intégrateurs :\n\n- `createAgentSession`\n- `SessionManager`\n- `Settings`\n- `AuthStorage`\n- `ModelRegistry`\n- `discoverAuthStorage`\n- Assistants de découverte (`discoverExtensions`, `discoverSkills`, `discoverContextFiles`, `discoverPromptTemplates`, `discoverSlashCommands`, `discoverCustomTSCommands`, `discoverMCPServers`)\n- Surface de fabrique d'outils (`createTools`, `BUILTIN_TOOLS`, classes d'outils)\n\n## Démarrage rapide (valeurs par défaut de découverte automatique)\n\n```ts\nimport { createAgentSession } from \"@f5-sales-demo/xcsh\";\n\nconst { session, modelFallbackMessage } = await createAgentSession();\n\nif (modelFallbackMessage) {\n process.stderr.write(`${modelFallbackMessage}\\n`);\n}\n\nconst unsubscribe = session.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Summarize this repository in 3 bullets.\");\nunsubscribe();\nawait session.dispose();\n```\n\n## Ce que `createAgentSession()` découvre par défaut\n\n`createAgentSession()` suit le principe « fournir pour remplacer, omettre pour découvrir ».\n\nEn cas d'omission, il résout :\n\n- `cwd` : `getProjectDir()`\n- `agentDir` : `~/.xcsh/agent` (via `getAgentDir()`)\n- `authStorage` : `discoverAuthStorage(agentDir)`\n- `modelRegistry` : `new ModelRegistry(authStorage)` + `await refresh()`\n- `settings` : `await Settings.init({ cwd, agentDir })`\n- `sessionManager` : `SessionManager.create(cwd)` (sauvegarde sur fichier)\n- compétences/fichiers de contexte/modèles de prompt/commandes slash/extensions/commandes TS personnalisées\n- outils intégrés via `createTools(...)`\n- outils MCP (activés par défaut)\n- intégration LSP (activée par défaut)\n\n### Entrées obligatoires et optionnelles\n\nEn général, vous ne devez fournir que ce que vous souhaitez contrôler :\n\n- **Obligatoire** : rien pour une session minimale\n- **Habituellement fourni explicitement** dans les intégrateurs :\n    - `sessionManager` (si vous avez besoin d'une mémoire volatile ou d'un emplacement personnalisé)\n    - `authStorage` + `modelRegistry` (si vous gérez le cycle de vie des identifiants et des modèles)\n    - `model` ou `modelPattern` (si la sélection déterministe du modèle est importante)\n    - `settings` (si vous avez besoin d'une configuration isolée ou de test)\n\n## Comportement du gestionnaire de session (persistant ou en mémoire)\n\n`AgentSession` utilise toujours un `SessionManager` ; le comportement dépend de la fabrique utilisée.\n\n### Sauvegarde sur fichier (par défaut)\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.create(process.cwd()),\n});\n\nconsole.log(session.sessionFile); // chemin absolu .jsonl\n```\n\n- Persiste les deltas de conversation/messages/état dans des fichiers de session.\n- Prend en charge les flux de travail reprendre/ouvrir/lister/bifurquer.\n- `session.sessionFile` est défini.\n\n### En mémoire\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.inMemory(),\n});\n\nconsole.log(session.sessionFile); // undefined\n```\n\n- Aucune persistance sur le système de fichiers.\n- Utile pour les tests, les workers éphémères et les agents à portée de requête.\n- Les méthodes de session fonctionnent toujours, mais les comportements spécifiques à la persistance (reprise de fichier/chemins de bifurcation) sont naturellement limités.\n\n### Assistants de reprise/ouverture/liste\n\n```ts\nimport { SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst recent = await SessionManager.continueRecent(process.cwd());\nconst listed = await SessionManager.list(process.cwd());\nconst opened = listed[0] ? await SessionManager.open(listed[0].path) : null;\n```\n\n## Câblage du modèle et de l'authentification\n\n`createAgentSession()` utilise `ModelRegistry` + `AuthStorage` pour la sélection du modèle et la résolution de la clé API.\n\n### Câblage explicite\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst available = modelRegistry.getAvailable();\nif (available.length === 0) throw new Error(\"No authenticated models available\");\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n model: available[0],\n thinkingLevel: \"medium\",\n sessionManager: SessionManager.inMemory(),\n});\n```\n\n### Ordre de sélection lorsque `model` est omis\n\nLorsqu'aucun `model`/`modelPattern` explicite n'est fourni :\n\n1. restaurer le modèle depuis la session existante (si restaurable + clé disponible)\n2. rôle de modèle par défaut dans les paramètres (`default`)\n3. premier modèle disponible avec une authentification valide\n\nSi la restauration échoue, `modelFallbackMessage` explique le repli.\n\n### Priorité d'authentification\n\n`AuthStorage.getApiKey(...)` résout dans cet ordre :\n\n1. remplacement au runtime (`setRuntimeApiKey`)\n2. identifiants stockés dans `agent.db`\n3. variables d'environnement du fournisseur\n4. résolveur de repli de fournisseur personnalisé (si configuré)\n\n## Modèle d'abonnement aux événements\n\nAbonnez-vous avec `session.subscribe(listener)` ; la fonction retourne un désabonnement.\n\n```ts\nconst unsubscribe = session.subscribe(event => {\n switch (event.type) {\n  case \"agent_start\":\n  case \"turn_start\":\n  case \"tool_execution_start\":\n   break;\n  case \"message_update\":\n   if (event.assistantMessageEvent.type === \"text_delta\") {\n    process.stdout.write(event.assistantMessageEvent.delta);\n   }\n   break;\n }\n});\n```\n\n`AgentSessionEvent` inclut les `AgentEvent` de base ainsi que les événements au niveau de la session :\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n## Cycle de vie des prompts\n\n`session.prompt(text, options?)` est le point d'entrée principal.\n\nComportement :\n\n1. expansion optionnelle de commandes/modèles (commandes `/`, commandes personnalisées, commandes slash de fichier, modèles de prompt)\n2. si un flux est en cours :\n    - nécessite `streamingBehavior: \"steer\" | \"followUp\"`\n    - met en file d'attente au lieu de perdre le travail\n3. si inactif :\n    - valide le modèle + la clé API\n    - ajoute le message utilisateur\n    - démarre le tour de l'agent\n\nAPI associées :\n\n- `sendUserMessage(content, { deliverAs? })`\n- `steer(text, images?)`\n- `followUp(text, images?)`\n- `sendCustomMessage({ customType, content, ... }, { deliverAs?, triggerTurn? })`\n- `abort()`\n\n## Outils et intégration des extensions\n\n### Outils intégrés et filtrage\n\n- Les outils intégrés proviennent de `createTools(...)` et `BUILTIN_TOOLS`.\n- `toolNames` agit comme une liste d'autorisation pour les outils intégrés.\n- Les outils `customTools` et enregistrés par les extensions sont toujours inclus.\n- Les outils cachés (par exemple `submit_result`) sont optionnels sauf s'ils sont requis par les options.\n\n```ts\nconst { session } = await createAgentSession({\n toolNames: [\"read\", \"grep\", \"find\", \"write\"],\n requireSubmitResultTool: true,\n});\n```\n\n### Extensions\n\n- `extensions` : `ExtensionFactory[]` en ligne\n- `additionalExtensionPaths` : chargement de fichiers d'extension supplémentaires\n- `disableExtensionDiscovery` : désactivation de l'analyse automatique des extensions\n- `preloadedExtensions` : réutilisation d'un ensemble d'extensions déjà chargées\n\n### Modifications du jeu d'outils au runtime\n\n`AgentSession` prend en charge les mises à jour d'activation au runtime :\n\n- `getActiveToolNames()`\n- `getAllToolNames()`\n- `setActiveToolsByName(names)`\n- `refreshMCPTools(mcpTools)`\n\nLe prompt système est reconstruit pour refléter les modifications des outils actifs.\n\n## Assistants de découverte\n\nUtilisez-les lorsque vous souhaitez un contrôle partiel sans recréer la logique de découverte interne :\n\n- `discoverAuthStorage(agentDir?)`\n- `discoverExtensions(cwd?)`\n- `discoverSkills(cwd?, _agentDir?, settings?)`\n- `discoverContextFiles(cwd?, _agentDir?)`\n- `discoverPromptTemplates(cwd?, agentDir?)`\n- `discoverSlashCommands(cwd?)`\n- `discoverCustomTSCommands(cwd?, agentDir?)`\n- `discoverMCPServers(cwd?)`\n- `buildSystemPrompt(options?)`\n\n## Options orientées sous-agent\n\nPour les consommateurs du SDK construisant des orchestrateurs (similaire au flux d'exécution de tâches) :\n\n- `outputSchema` : transmet l'attente de sortie structurée dans le contexte de l'outil\n- `requireSubmitResultTool` : force l'inclusion de l'outil `submit_result`\n- `taskDepth` : contexte de profondeur de récursion pour les sessions de tâches imbriquées\n- `parentTaskPrefix` : préfixe de nommage des artefacts pour les sorties de tâches imbriquées\n\nCes options sont facultatives pour l'intégration normale d'un agent unique.\n\n## Valeur de retour de `createAgentSession()`\n\n```ts\ntype CreateAgentSessionResult = {\n session: AgentSession;\n extensionsResult: LoadExtensionsResult;\n setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;\n mcpManager?: MCPManager;\n modelFallbackMessage?: string;\n lspServers?: Array<{ name: string; status: \"ready\" | \"error\"; fileTypes: string[]; error?: string }>;\n};\n```\n\nUtilisez `setToolUIContext(...)` uniquement si votre intégrateur fournit des capacités d'interface utilisateur que les outils/extensions doivent appeler.\n\n## Exemple d'intégration contrôlée minimale\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n Settings,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst settings = Settings.isolated({\n \"compaction.enabled\": true,\n \"retry.enabled\": true,\n});\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n settings,\n sessionManager: SessionManager.inMemory(),\n toolNames: [\"read\", \"grep\", \"find\", \"edit\", \"write\"],\n enableMCP: false,\n enableLsp: true,\n});\n\nsession.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Find all TODO comments in this repo and propose fixes.\");\nawait session.dispose();\n```\n",
	"fr/configuration/secrets.md": "---\ntitle: Obscurcissement des secrets\ndescription: >-\n  Pipeline d'obscurcissement des secrets qui rédige les valeurs sensibles des\n  journaux de session et des sorties.\nsidebar:\n  order: 3\n  label: Secrets\ni18n:\n  sourceHash: 1d9dc101c614\n  translator: machine\n---\n\n# Obscurcissement des secrets\n\nEmpêche l'envoi de valeurs sensibles (clés API, jetons, mots de passe) aux fournisseurs LLM. Lorsqu'il est activé, les secrets sont remplacés par des espaces réservés déterministes avant de quitter le processus, puis restaurés dans les arguments des appels d'outils retournés par le modèle.\n\n## Activation\n\nActivé par défaut. Basculez via l'interface `/settings` ou directement dans `config.yml` :\n\n```yaml\nsecrets:\n  enabled: false\n```\n\n## Fonctionnement\n\n1. Au démarrage de la session, les secrets sont collectés depuis deux sources :\n   - **Les variables d'environnement** correspondant aux modèles de secrets courants (`*_KEY`, `*_SECRET`, `*_TOKEN`, `*_PASSWORD`, etc.) avec des valeurs de 8 caractères ou plus\n   - **Les fichiers `secrets.yml`** (voir ci-dessous)\n\n2. Les messages sortants vers le LLM ont toutes leurs valeurs secrètes remplacées par des espaces réservés tels que `<<$env:S0>>`, `<<$env:S1>>`, etc.\n\n3. Les arguments des appels d'outils retournés par le modèle sont parcourus en profondeur et les espaces réservés sont restaurés à leurs valeurs d'origine avant l'exécution.\n\nDeux modes contrôlent le traitement de chaque secret :\n\n| Mode | Comportement | Réversible |\n|---|---|---|\n| `obfuscate` (défaut) | Remplacé par un espace réservé indexé `<<$env:SN>>` | Oui (désofusqué dans les arguments d'outils) |\n| `replace` | Remplacé par une chaîne déterministe de même longueur | Non (sens unique) |\n\n## secrets.yml\n\nDéfinissez des entrées de secrets personnalisées en YAML. Deux emplacements sont vérifiés :\n\n| Niveau | Chemin | Objectif |\n|---|---|---|\n| Global | `~/.xcsh/agent/secrets.yml` | Secrets communs à tous les projets |\n| Projet | `<cwd>/.xcsh/secrets.yml` | Secrets spécifiques au projet |\n\nLes entrées de projet remplacent les entrées globales ayant un `content` correspondant.\n\n### Schéma\n\nChaque entrée du tableau possède ces champs :\n\n| Champ | Type | Requis | Description |\n|---|---|---|---|\n| `type` | `\"plain\"` ou `\"regex\"` | Oui | Stratégie de correspondance |\n| `content` | chaîne | Oui | La valeur secrète (plain) ou le motif regex (regex) |\n| `mode` | `\"obfuscate\"` ou `\"replace\"` | Non | Par défaut : `\"obfuscate\"` |\n| `replacement` | chaîne | Non | Remplacement personnalisé (mode replace uniquement) |\n| `flags` | chaîne | Non | Indicateurs regex (type regex uniquement) |\n\n### Exemples\n\n#### Secrets en clair\n\n```yaml\n# Obscurcir une clé API spécifique (mode par défaut)\n- type: plain\n  content: sk-proj-abc123def456\n\n# Remplacer un mot de passe de base de données par une chaîne fixe\n- type: plain\n  content: hunter2\n  mode: replace\n  replacement: \"********\"\n```\n\n#### Secrets regex\n\n```yaml\n# Obscurcir toute clé de style AWS\n- type: regex\n  content: \"AKIA[0-9A-Z]{16}\"\n\n# Correspondance insensible à la casse avec des indicateurs explicites\n- type: regex\n  content: \"api[_-]?key\\\\s*=\\\\s*\\\\w+\"\n  flags: \"i\"\n\n# Syntaxe littérale regex (motif et indicateurs dans une seule chaîne)\n- type: regex\n  content: \"/bearer\\\\s+[a-zA-Z0-9._~+\\\\/=-]+/i\"\n```\n\nLes entrées regex effectuent toujours une analyse globale (l'indicateur `g` est appliqué automatiquement). La syntaxe littérale regex `/pattern/flags` est prise en charge comme alternative aux champs séparés `content` + `flags`. Les barres obliques échappées dans le motif (`\\\\/`) sont gérées correctement.\n\n#### Mode replace avec regex\n\n```yaml\n# Remplacement irréversible des chaînes de connexion (non réversible)\n- type: regex\n  content: \"postgres://[^\\\\s]+\"\n  mode: replace\n  replacement: \"postgres://***\"\n```\n\n## Interaction avec la détection des variables d'environnement\n\nLes variables d'environnement sont toujours collectées en premier. Les entrées définies dans les fichiers sont ajoutées ensuite, de sorte que ces entrées peuvent couvrir des secrets qui ne se trouvent pas dans les variables d'environnement (fichiers de configuration, valeurs codées en dur, etc.). Si la même valeur apparaît dans les deux, le mode de l'entrée de fichier est prioritaire.\n\n## Fichiers clés\n\n- `src/secrets/index.ts` -- chargement, fusion, collecte des variables d'environnement\n- `src/secrets/obfuscator.ts` -- classe `SecretObfuscator`, génération des espaces réservés, obscurcissement des messages\n- `src/secrets/regex.ts` -- analyse et compilation des littéraux regex\n- `src/config/settings-schema.ts` -- définition du paramètre `secrets.enabled`\n",
	"fr/extensions/extension-loading.md": "---\ntitle: Chargement des extensions (Modules TypeScript/JavaScript)\ndescription: >-\n  Pipeline de chargement des modules TypeScript et JavaScript pour les\n  extensions, avec résolution, validation et mise en cache.\nsidebar:\n  order: 2\n  label: Chargement des extensions\ni18n:\n  sourceHash: a8cea231c660\n  translator: machine\n---\n\n# Chargement des extensions (Modules TypeScript/JavaScript)\n\nCe document explique comment l'agent de codage découvre et charge les **modules d'extension** (`.ts`/`.js`) au démarrage.\n\nIl ne couvre **pas** les extensions de manifeste `gemini-extension.json` (documentées séparément).\n\n## Rôle de ce sous-système\n\nLe chargement des extensions construit une liste de fichiers d'entrée de modules, importe chaque module avec Bun, exécute sa fabrique et retourne :\n\n- les définitions d'extensions chargées\n- les erreurs de chargement par chemin (sans interrompre l'ensemble du chargement)\n- un objet de runtime d'extension partagé, utilisé ultérieurement par `ExtensionRunner`\n\n## Fichiers d'implémentation principaux\n\n- `src/extensibility/extensions/loader.ts` — découverte des chemins + import/exécution\n- `src/extensibility/extensions/index.ts` — exports publics\n- `src/extensibility/extensions/runner.ts` — exécution du runtime/des événements après le chargement\n- `src/discovery/builtin.ts` — fournisseur de découverte automatique natif pour les modules d'extension\n- `src/config/settings.ts` — charge les paramètres fusionnés `extensions` / `disabledExtensions`\n\n---\n\n## Entrées du chargement des extensions\n\n### 1) Modules d'extension natifs découverts automatiquement\n\n`discoverAndLoadExtensions()` interroge d'abord les fournisseurs de découverte pour les éléments de capacité `extension-module`, puis ne conserve que les éléments du fournisseur `native`.\n\nEmplacements natifs effectifs :\n\n- Projet : `<cwd>/.xcsh/extensions`\n- Utilisateur : `~/.xcsh/agent/extensions`\n\nLes racines de chemins proviennent du fournisseur natif (`SOURCE_PATHS.native`).\n\nRemarques :\n\n- La découverte automatique native est actuellement basée sur `.xcsh`.\n- Le format `.pi` hérité est toujours accepté dans les clés de manifeste `package.json` (`pi.extensions`), mais pas en tant que racine native ici.\n\n### 2) Chemins configurés explicitement\n\nAprès la découverte automatique, les chemins configurés sont ajoutés et résolus.\n\nSources de chemins configurés dans le chemin de démarrage de session principale (`sdk.ts`) :\n\n1. Chemins fournis par la CLI (`--extension/-e`, et `--hook` est également traité comme un chemin d'extension)\n2. Tableau `extensions` des paramètres (paramètres globaux + projet fusionnés)\n\nFichier de paramètres global :\n\n- `~/.xcsh/agent/config.yml` (ou répertoire d'agent personnalisé via `PI_CODING_AGENT_DIR`)\n\nFichier de paramètres du projet :\n\n- `<cwd>/.xcsh/settings.json`\n\nExemples :\n\n```yaml\n# ~/.xcsh/agent/config.yml\nextensions:\n  - ~/my-exts/safety.ts\n  - ./local/ext-pack\n```\n\n```json\n{\n  \"extensions\": [\"./.xcsh/extensions/my-extra\"]\n}\n```\n\n---\n\n## Contrôles d'activation/désactivation\n\n### Désactiver la découverte\n\n- CLI : `--no-extensions`\n- Option SDK : `disableExtensionDiscovery`\n\nComportement selon le contexte :\n\n- SDK : lorsque `disableExtensionDiscovery=true`, il charge tout de même les `additionalExtensionPaths` via `loadExtensions()`.\n- La construction des chemins CLI (`main.ts`) efface actuellement les chemins d'extension CLI lorsque `--no-extensions` est défini, de sorte que les options explicites `-e/--hook` ne sont pas transmises dans ce mode.\n\n### Désactiver des modules d'extension spécifiques\n\nLe paramètre `disabledExtensions` filtre selon le format d'identifiant d'extension :\n\n- `extension-module:<derivedName>`\n\n`derivedName` est basé sur le chemin d'entrée (`getExtensionNameFromPath`), par exemple :\n\n- `/x/foo.ts` -> `foo`\n- `/x/bar/index.ts` -> `bar`\n\nExemple :\n\n```yaml\ndisabledExtensions:\n  - extension-module:foo\n```\n\n---\n\n## Résolution des chemins et des entrées\n\n### Normalisation des chemins\n\nPour les chemins configurés :\n\n1. Normalisation des espaces unicode\n2. Expansion de `~`\n3. Si relatif, résolution par rapport au `cwd` courant\n\n### Si le chemin configuré est un fichier\n\nIl est utilisé directement comme candidat d'entrée de module.\n\n### Si le chemin configuré est un répertoire\n\nOrdre de résolution :\n\n1. `package.json` dans ce répertoire avec `xcsh.extensions` (ou `pi.extensions` hérité) -> utilise les entrées déclarées\n2. `index.ts`\n3. `index.js`\n4. Sinon, analyse d'un niveau pour les entrées d'extension :\n   - `*.ts` / `*.js` directs\n   - `index.ts` / `index.js` dans un sous-répertoire\n   - `package.json` dans un sous-répertoire avec `xcsh.extensions` / `pi.extensions`\n\nRègles et contraintes :\n\n- pas de découverte récursive au-delà d'un niveau de sous-répertoire\n- les entrées de manifeste `extensions` déclarées sont résolues par rapport à ce répertoire de paquet\n- les entrées déclarées ne sont incluses que si le fichier existe et si l'accès est autorisé\n- dans les paires `*/index.{ts,js}`, TypeScript est préféré à JavaScript\n- les liens symboliques sont traités comme des fichiers/répertoires éligibles\n\n### Le comportement d'ignorance diffère selon la source\n\n- La découverte automatique native (`discoverExtensionModulePaths` dans les assistants de découverte) utilise un glob natif avec `gitignore: true` et `hidden: false`.\n- L'analyse de répertoire configuré explicitement dans `loader.ts` utilise les règles `readdir` et n'applique **pas** le filtrage gitignore.\n\n---\n\n## Ordre de chargement et priorité\n\n`discoverAndLoadExtensions()` construit une liste ordonnée unique, puis appelle `loadExtensions()`.\n\nOrdre :\n\n1. Modules découverts automatiquement en mode natif\n2. Chemins configurés explicitement (dans l'ordre fourni)\n\nDans `sdk.ts`, l'ordre configuré est :\n\n1. Chemins supplémentaires de la CLI\n2. `extensions` des paramètres\n\nDéduplication :\n\n- basée sur le chemin absolu\n- le premier chemin rencontré est conservé\n- les doublons ultérieurs sont ignorés\n\nImplication : si le même chemin de module est à la fois découvert automatiquement et configuré explicitement, il est chargé une seule fois à la première position (étape de découverte automatique).\n\n---\n\n## Import du module et contrat de fabrique\n\nChaque chemin candidat est chargé avec un import dynamique :\n\n- `await import(resolvedPath)`\n- la fabrique est `module.default ?? module`\n- la fabrique doit être une fonction (`ExtensionFactory`)\n\nSi l'export n'est pas une fonction, ce chemin échoue avec une erreur structurée et le chargement continue.\n\n---\n\n## Gestion des échecs et isolation\n\n### Pendant le chargement\n\nPar chemin d'extension, les échecs sont capturés sous la forme `{ path, error }` et n'empêchent pas le chargement des autres chemins.\n\nCas courants :\n\n- échec d'import / fichier manquant\n- export de fabrique invalide (non-fonction)\n- exception levée lors de l'exécution de la fabrique\n\n### Modèle d'isolation du runtime\n\n- Les extensions ne sont **pas isolées** (même processus/runtime).\n- Elles partagent un `EventBus` et une instance `ExtensionRuntime`.\n- Pendant le chargement, les méthodes d'action du runtime lèvent intentionnellement `ExtensionRuntimeNotInitializedError` ; le câblage des actions intervient ultérieurement dans `ExtensionRunner.initialize()`.\n\n### Après le chargement\n\nLorsque les événements transitent par `ExtensionRunner`, les exceptions des gestionnaires sont interceptées et émises en tant qu'erreurs d'extension plutôt que de faire planter la boucle du runner.\n\n---\n\n## Exemples de structures minimales utilisateur/projet\n\n### Au niveau utilisateur\n\n```text\n~/.xcsh/agent/\n  config.yml\n  extensions/\n    guardrails.ts\n    audit/\n      index.ts\n```\n\n### Au niveau projet\n\n```text\n<repo>/\n  .xcsh/\n    settings.json\n    extensions/\n      checks/\n        package.json\n      lint-gates.ts\n```\n\n`checks/package.json` :\n\n```json\n{\n  \"xcsh\": {\n    \"extensions\": [\"./src/check-a.ts\", \"./src/check-b.js\"]\n  }\n}\n```\n\nClé de manifeste héritée toujours acceptée :\n\n```json\n{\n  \"pi\": {\n    \"extensions\": [\"./index.ts\"]\n  }\n}\n```\n",
	"fr/extensions/extensions.md": "---\ntitle: Extensions\ndescription: >-\n  Présentation du runtime des extensions couvrant les types, le cycle de vie du\n  runner, l'enregistrement et la découverte.\nsidebar:\n  order: 1\n  label: Vue d'ensemble\ni18n:\n  sourceHash: 14cc16dbd98b\n  translator: machine\n---\n\n# Extensions\n\nGuide principal pour la création d'extensions runtime dans `packages/coding-agent`.\n\nCe document couvre le runtime d'extension actuel dans :\n\n- `src/extensibility/extensions/types.ts`\n- `src/extensibility/extensions/runner.ts`\n- `src/extensibility/extensions/wrapper.ts`\n- `src/extensibility/extensions/index.ts`\n- `src/modes/controllers/extension-ui-controller.ts`\n\nPour les chemins de découverte et les règles de chargement du système de fichiers, consultez `docs/extension-loading.md`.\n\n## Qu'est-ce qu'une extension\n\nUne extension est un module TS/JS exportant une factory par défaut :\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nexport default function myExtension(pi: ExtensionAPI) {\n // register handlers/tools/commands/renderers\n}\n```\n\nLes extensions peuvent combiner tous les éléments suivants dans un seul module :\n\n- gestionnaires d'événements (`pi.on(...)`)\n- outils appelables par LLM (`pi.registerTool(...)`)\n- commandes slash (`pi.registerCommand(...)`)\n- raccourcis clavier et indicateurs\n- rendu de messages personnalisé\n- API d'injection de session/message (`sendMessage`, `sendUserMessage`, `appendEntry`)\n\n## Modèle de runtime\n\n1. Les extensions sont importées et leurs fonctions factory sont exécutées.\n2. Durant cette phase de chargement, les méthodes d'enregistrement sont valides ; les méthodes d'action runtime ne sont pas encore initialisées.\n3. `ExtensionRunner.initialize(...)` connecte les actions/contextes actifs pour le mode en cours.\n4. Les événements du cycle de vie de session/agent/outil sont émis vers les gestionnaires.\n5. Chaque exécution d'outil est enveloppée avec l'interception d'extension (`tool_call` / `tool_result`).\n\n```text\nExtension lifecycle (simplified)\n\nload paths\n   │\n   ▼\nimport module + run factory (registration only)\n   │\n   ▼\nExtensionRunner.initialize(mode/session/tool registry)\n   │\n   ├─ emit session/agent events to handlers\n   ├─ wrap tool execution (tool_call/tool_result)\n   └─ expose runtime actions (sendMessage, setActiveTools, ...)\n```\n\nContrainte importante de `loader.ts` :\n\n- appeler des méthodes d'action comme `pi.sendMessage()` lors du chargement de l'extension lève `ExtensionRuntimeNotInitializedError`\n- enregistrez d'abord ; effectuez le comportement runtime depuis les événements/commandes/outils\n\n## Démarrage rapide\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\nimport { Type } from \"@sinclair/typebox\";\n\nexport default function (pi: ExtensionAPI) {\n pi.setLabel(\"Safety + Utilities\");\n\n pi.on(\"session_start\", async (_event, ctx) => {\n  ctx.ui.notify(`Extension loaded in ${ctx.cwd}`, \"info\");\n });\n\n pi.on(\"tool_call\", async (event) => {\n  if (event.toolName === \"bash\" && event.input.command?.includes(\"rm -rf\")) {\n   return { block: true, reason: \"Blocked by extension policy\" };\n  }\n });\n\n pi.registerTool({\n  name: \"hello_extension\",\n  label: \"Hello Extension\",\n  description: \"Return a greeting\",\n  parameters: Type.Object({ name: Type.String() }),\n  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {\n   return {\n    content: [{ type: \"text\", text: `Hello, ${params.name}` }],\n    details: { greeted: params.name },\n   };\n  },\n });\n\n pi.registerCommand(\"hello-ext\", {\n  description: \"Show queue state\",\n  handler: async (_args, ctx) => {\n   ctx.ui.notify(`pending=${ctx.hasPendingMessages()}`, \"info\");\n  },\n });\n}\n```\n\n## Surfaces de l'API d'extension\n\n## 1) Enregistrement et actions (`ExtensionAPI`)\n\nMéthodes principales :\n\n- `on(event, handler)`\n- `registerTool`, `registerCommand`, `registerShortcut`, `registerFlag`\n- `registerMessageRenderer`\n- `sendMessage`, `sendUserMessage`, `appendEntry`\n- `getActiveTools`, `getAllTools`, `setActiveTools`\n- `getSessionName`, `setSessionName`\n- `setModel`, `getThinkingLevel`, `setThinkingLevel`\n- `registerProvider`\n- `events` (bus d'événements partagé)\n\nEn mode interactif, les gestionnaires `input` s'exécutent avant la vérification automatique du titre du premier message intégrée. Les extensions qui appellent `await pi.setSessionName(...)` depuis `input` peuvent définir le nom de session persisté et empêcher le titre auto-généré par défaut de s'exécuter pour cette session.\n\nÉgalement exposés :\n\n- `pi.logger`\n- `pi.typebox`\n- `pi.pi` (exports du paquet)\n\n### Sémantique de livraison des messages\n\n`pi.sendMessage(message, options)` prend en charge :\n\n- `deliverAs: \"steer\"` (par défaut) — interrompt l'exécution en cours\n- `deliverAs: \"followUp\"` — mis en file d'attente pour s'exécuter après l'exécution en cours\n- `deliverAs: \"nextTurn\"` — stocké et injecté lors de la prochaine invite utilisateur\n- `triggerTurn: true` — démarre un tour lorsqu'inactif (`nextTurn` ignore cela)\n\n`pi.sendUserMessage(content, { deliverAs })` passe toujours par le flux d'invite ; lors du streaming, il est mis en file d'attente en tant que steer/follow-up.\n\n## 2) Contexte du gestionnaire (`ExtensionContext`)\n\nLes gestionnaires et les `execute` d'outils reçoivent `ctx` avec :\n\n- `ui`\n- `hasUI`\n- `cwd`\n- `sessionManager` (lecture seule)\n- `modelRegistry`, `model`\n- `getContextUsage()`\n- `compact(...)`\n- `isIdle()`, `hasPendingMessages()`, `abort()`\n- `shutdown()`\n- `getSystemPrompt()`\n\n## 3) Contexte de commande (`ExtensionCommandContext`)\n\nLes gestionnaires de commandes reçoivent en plus :\n\n- `waitForIdle()`\n- `newSession(...)`\n- `switchSession(...)`\n- `branch(entryId)`\n- `navigateTree(targetId, { summarize })`\n- `reload()`\n\nUtilisez le contexte de commande pour les flux de contrôle de session ; ces méthodes sont intentionnellement séparées des gestionnaires d'événements généraux.\n\n## Surface des événements (noms et comportements actuels)\n\nLes unions d'événements canoniques et les types de charge utile se trouvent dans `types.ts`.\n\n### Cycle de vie de la session\n\n- `session_start`\n- `session_before_switch` / `session_switch`\n- `session_before_branch` / `session_branch`\n- `session_before_compact` / `session.compacting` / `session_compact`\n- `session_before_tree` / `session_tree`\n- `session_shutdown`\n\nPré-événements annulables :\n\n- `session_before_switch` → `{ cancel?: boolean }`\n- `session_before_branch` → `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_before_compact` → `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session_before_tree` → `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n\n### Cycle de vie de l'invite et du tour\n\n- `input`\n- `before_agent_start`\n- `context`\n- `agent_start` / `agent_end`\n- `turn_start` / `turn_end`\n- `message_start` / `message_update` / `message_end`\n\n### Cycle de vie de l'outil\n\n- `tool_call` (pré-exécution, peut bloquer)\n- `tool_result` (post-exécution, peut modifier le contenu/les détails/isError)\n- `tool_execution_start` / `tool_execution_update` / `tool_execution_end` (observabilité)\n\n`tool_result` est de style middleware : les gestionnaires s'exécutent dans l'ordre des extensions et chacun voit les modifications précédentes.\n\n### Signaux de fiabilité/runtime\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### Interception des commandes utilisateur\n\n- `user_bash` (remplacement avec `{ result }`)\n- `user_python` (remplacement avec `{ result }`)\n\n### `resources_discover`\n\n`resources_discover` existe dans les types d'extension et `ExtensionRunner`.\nNote sur le runtime actuel : `ExtensionRunner.emitResourcesDiscover(...)` est implémenté, mais il n'existe aucun point d'appel `AgentSession` l'invoquant dans la base de code actuelle.\n\n## Détails de la création d'outils\n\n`registerTool` utilise `ToolDefinition` depuis `types.ts`.\n\nSignature `execute` actuelle :\n\n```ts\nexecute(\n toolCallId,\n params,\n signal,\n onUpdate,\n ctx,\n): Promise<AgentToolResult>\n```\n\nModèle :\n\n```ts\npi.registerTool({\n name: \"my_tool\",\n label: \"My Tool\",\n description: \"...\",\n parameters: Type.Object({}),\n async execute(_id, _params, signal, onUpdate, ctx) {\n  if (signal?.aborted) {\n   return { content: [{ type: \"text\", text: \"Cancelled\" }] };\n  }\n  onUpdate?.({ content: [{ type: \"text\", text: \"Working...\" }] });\n  return { content: [{ type: \"text\", text: \"Done\" }], details: {} };\n },\n onSession(event, ctx) {\n  // reason: start|switch|branch|tree|shutdown\n },\n renderCall(args, theme) {\n  // optional TUI render\n },\n renderResult(result, options, theme, args) {\n  // optional TUI render\n },\n});\n```\n\n`tool_call`/`tool_result` intercepte tous les outils une fois le registre encapsulé dans `sdk.ts`, y compris les outils intégrés et les outils d'extension/personnalisés.\n\n## Points d'intégration de l'interface utilisateur\n\n`ctx.ui` implémente l'interface `ExtensionUIContext`. La prise en charge varie selon le mode.\n\n### Mode interactif (`extension-ui-controller.ts`)\n\nPris en charge :\n\n- dialogues : `select`, `confirm`, `input`, `editor`\n- notifications/statut/texte de l'éditeur/saisie terminal/superpositions personnalisées\n- liste/chargement des thèmes par nom (`setTheme` prend en charge les noms de chaînes)\n- bascule d'expansion des outils\n\nMéthodes sans effet dans ce contrôleur :\n\n- `setFooter`\n- `setHeader`\n- `setEditorComponent`\n\nÀ noter également : `setWidget` route actuellement vers le texte de la ligne de statut via `setHookWidget(...)`.\n\n### Mode RPC (`rpc-mode.ts`)\n\n`ctx.ui` est soutenu par des événements RPC `extension_ui_request` :\n\n- les méthodes de dialogue (`select`, `confirm`, `input`, `editor`) effectuent des allers-retours vers les réponses client\n- les méthodes fire-and-forget émettent des requêtes (`notify`, `setStatus`, `setWidget` pour les tableaux de chaînes, `setTitle`, `setEditorText`)\n\nNon pris en charge/sans effet dans l'implémentation RPC :\n\n- `onTerminalInput`\n- `custom`\n- `setFooter`, `setHeader`, `setEditorComponent`\n- `setWorkingMessage`\n- changement/chargement de thème (`setTheme` renvoie une erreur)\n- les contrôles d'expansion des outils sont inactifs\n\n### Chemins print/headless/subagent\n\nLorsqu'aucun contexte UI n'est fourni à l'initialisation du runner, `ctx.hasUI` est `false` et les méthodes sont sans effet/retournent des valeurs par défaut.\n\n### Mode interactif en arrière-plan\n\nLe mode arrière-plan installe un objet de contexte UI non interactif. Dans l'implémentation actuelle, `ctx.hasUI` peut toujours être `true` tandis que les dialogues interactifs retournent des valeurs par défaut/comportement sans effet.\n\n## Modèles de session et d'état\n\nPour un état d'extension durable :\n\n1. Persistez avec `pi.appendEntry(customType, data)`.\n2. Reconstruisez l'état depuis `ctx.sessionManager.getBranch()` lors de `session_start`, `session_branch`, `session_tree`.\n3. Maintenez les `details` des résultats d'outils structurés lorsque l'état doit être visible/reconstructible depuis l'historique des résultats d'outils.\n\nExemple de modèle de reconstruction :\n\n```ts\npi.on(\"session_start\", async (_event, ctx) => {\n let latest;\n for (const entry of ctx.sessionManager.getBranch()) {\n  if (entry.type === \"custom\" && entry.customType === \"my-state\") {\n   latest = entry.data;\n  }\n }\n // restore from latest\n});\n```\n\n## Points d'extension du rendu\n\n## Renderer de messages personnalisé\n\n```ts\npi.registerMessageRenderer(\"my-type\", (message, { expanded }, theme) => {\n // return pi-tui Component\n});\n```\n\nUtilisé par le rendu interactif lorsque des messages personnalisés sont affichés.\n\n## Renderer d'appel/résultat d'outil\n\nFournissez `renderCall` / `renderResult` dans les définitions `registerTool` pour une visualisation personnalisée des outils dans le TUI.\n\n## Contraintes et pièges\n\n- Les actions runtime ne sont pas disponibles lors du chargement de l'extension.\n- Les erreurs `tool_call` bloquent l'exécution (échec fermé).\n- Les conflits de noms de commandes avec les commandes intégrées sont ignorés avec des diagnostics.\n- Les raccourcis réservés sont ignorés (`ctrl+c`, `ctrl+d`, `ctrl+z`, `ctrl+k`, `ctrl+p`, `ctrl+l`, `ctrl+o`, `ctrl+t`, `ctrl+g`, `shift+tab`, `shift+ctrl+p`, `alt+enter`, `escape`, `enter`).\n- Traitez `ctx.reload()` comme terminal pour le cadre du gestionnaire de commande en cours.\n\n## Extensions vs hooks vs custom-tools\n\nUtilisez la bonne surface :\n\n- **Extensions** (`src/extensibility/extensions/*`) : système unifié (événements + outils + commandes + renderers + enregistrement de fournisseur).\n- **Hooks** (`src/extensibility/hooks/*`) : API d'événements légacy séparée.\n- **Custom-tools** (`src/extensibility/custom-tools/*`) : modules axés sur les outils ; lorsqu'ils sont chargés aux côtés des extensions, ils sont adaptés et passent toujours par les wrappers d'interception d'extension.\n\nSi vous avez besoin d'un seul paquet qui gère la politique, les outils, l'expérience utilisateur des commandes et le rendu ensemble, utilisez les extensions.\n",
	"fr/extensions/gemini-manifest-extensions.md": "---\ntitle: Extensions de manifeste Gemini\ndescription: >-\n  Format d'extension de manifeste Gemini pour la compatibilité des compétences\n  et agents multi-plateformes.\nsidebar:\n  order: 7\n  label: Manifeste Gemini\ni18n:\n  sourceHash: 7134165a5f6d\n  translator: machine\n---\n\n# Extensions de manifeste Gemini (`gemini-extension.json`)\n\nCe document explique comment l'agent de codage découvre et analyse les extensions de manifeste de style Gemini (`gemini-extension.json`) dans la capacité `extensions`.\n\nIl ne couvre **pas** le chargement des modules d'extension TypeScript/JavaScript (`extensions/*.ts`, `index.ts`, `package.json xcsh.extensions`), qui est documenté dans `extension-loading.md`.\n\n## Fichiers d'implémentation\n\n- [`../src/discovery/gemini.ts`](../../packages/coding-agent/src/discovery/gemini.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/capability/extension.ts`](../../packages/coding-agent/src/capability/extension.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/loader.ts`](../../packages/coding-agent/src/extensibility/extensions/loader.ts)\n\n---\n\n## Ce qui est découvert\n\nLe fournisseur Gemini (`id: gemini`, priorité `60`) enregistre un chargeur `extensions` qui analyse deux racines fixes :\n\n- Utilisateur : `~/.gemini/extensions`\n- Projet : `<cwd>/.gemini/extensions`\n\nLa résolution de chemin s'effectue directement depuis `ctx.home` et `ctx.cwd` via `getUserPath()` / `getProjectPath()`.\n\nRègle de portée importante : la recherche dans le projet est **limitée au répertoire courant (cwd)**. Elle ne remonte pas les répertoires parents.\n\n---\n\n## Règles d'analyse des répertoires\n\nPour chaque racine (`~/.gemini/extensions` et `<cwd>/.gemini/extensions`), la découverte effectue les opérations suivantes :\n\n1. `readDirEntries(root)`\n2. ne conserver que les sous-répertoires directs (`entry.isDirectory()`)\n3. pour chaque enfant `<name>`, tenter de lire exactement :\n   - `<root>/<name>/gemini-extension.json`\n\nIl n'y a pas d'analyse récursive au-delà d'un niveau de répertoire.\n\n### Répertoires cachés\n\nLa découverte de manifestes Gemini ne filtre **pas** les noms de répertoires préfixés par un point. Si un sous-répertoire caché existe et contient un fichier `gemini-extension.json`, il est pris en compte.\n\n### Fichiers manquants ou illisibles\n\nSi `gemini-extension.json` est absent ou illisible, ce répertoire est ignoré silencieusement (sans avertissement).\n\n---\n\n## Structure du manifeste (telle qu'implémentée)\n\nLe type de capacité définit la structure de manifeste suivante :\n\n```ts\ninterface ExtensionManifest {\n name?: string;\n description?: string;\n mcpServers?: Record<string, Omit<MCPServer, \"name\" | \"_source\">>;\n tools?: unknown[];\n context?: unknown;\n}\n```\n\nLe comportement au moment de la découverte est intentionnellement permissif :\n\n- La réussite de l'analyse JSON est requise.\n- Il n'y a pas de validation de schéma à l'exécution pour les types/contenus des champs au-delà de la syntaxe JSON.\n- L'objet analysé est stocké en tant que `manifest` sur l'élément de capacité.\n\n### Normalisation du nom\n\n`Extension.name` est défini comme suit :\n\n1. `manifest.name` s'il n'est pas `null`/`undefined`\n2. sinon, le nom du répertoire de l'extension\n\nAucun contrôle de type chaîne n'est appliqué ici.\n\n---\n\n## Matérialisation en éléments de capacité\n\nUn manifeste correctement analysé crée un élément de capacité `Extension` :\n\n```ts\n{\n name: manifest.name ?? <directory-name>,\n path: <extension-directory>,\n manifest: <parsed-json>,\n level: \"user\" | \"project\",\n _source: {\n  provider: \"gemini\",\n  providerName: \"Gemini CLI\" // attaché par le registre de capacités\n  path: <absolute-manifest-path>,\n  level: \"user\" | \"project\"\n }\n}\n```\n\nRemarques :\n\n- `_source.path` est normalisé en chemin absolu par `createSourceMeta()`.\n- La validation des capacités au niveau du registre pour `extensions` vérifie uniquement la présence de `name` et `path`.\n- Les éléments internes du manifeste (`mcpServers`, `tools`, `context`) ne sont pas validés lors de la découverte.\n\n---\n\n## Gestion des erreurs et sémantique des avertissements\n\n### Avec avertissement\n\n- JSON invalide dans un fichier manifeste :\n  - format d'avertissement : `Invalid JSON in <manifestPath>`\n\n### Sans avertissement (ignoré silencieusement)\n\n- répertoire `extensions` absent\n- le sous-répertoire ne contient pas de fichier `gemini-extension.json`\n- fichier manifeste illisible\n- le JSON du manifeste est syntaxiquement valide mais sémantiquement incomplet ou inhabituel\n\nCela signifie que la validité partielle est acceptée : seul un échec syntaxique JSON déclenche un avertissement.\n\n---\n\n## Priorité et déduplication avec d'autres sources\n\nLa capacité `extensions` est agrégée entre les fournisseurs par le registre de capacités.\n\nFournisseurs actuels pour cette capacité :\n\n- `native` (`packages/coding-agent/src/discovery/builtin.ts`) priorité `100`\n- `gemini` (`packages/coding-agent/src/discovery/gemini.ts`) priorité `60`\n\nLa clé de déduplication est `ext.name` (`extensionCapability.key = ext => ext.name`).\n\n### Priorité entre fournisseurs\n\nLe fournisseur de priorité supérieure l'emporte en cas de noms d'extensions identiques.\n\n- Si `native` et `gemini` émettent tous deux le nom d'extension `foo`, l'élément natif est conservé.\n- Le doublon de priorité inférieure est conservé uniquement dans `result.all` avec `_shadowed = true`.\n\n### Effets de l'ordre intra-fournisseur\n\nÉtant donné que la déduplication fonctionne selon le principe « premier arrivé, premier servi », l'ordre des éléments au sein d'un fournisseur a son importance.\n\n- Le chargeur Gemini ajoute d'abord les entrées **utilisateur**, puis les entrées **projet**.\n- Par conséquent, les noms en double entre `~/.gemini/extensions` et `<cwd>/.gemini/extensions` conservent l'entrée utilisateur et masquent l'entrée projet.\n\nEn revanche, le fournisseur natif construit l'ordre des répertoires de configuration différemment (`project` puis `user` dans `getConfigDirs()`), de sorte que le masquage intra-fournisseur natif s'effectue dans la direction opposée.\n\n---\n\n## Résumé du comportement utilisateur vs projet\n\nPour les manifestes Gemini spécifiquement :\n\n- Les deux racines, utilisateur et projet, sont analysées à chaque chargement.\n- La racine du projet est fixée à `<cwd>/.gemini/extensions` (sans remontée des répertoires parents).\n- Les noms en double au sein de la source Gemini sont résolus en faveur de l'utilisateur.\n- Les noms en double face aux fournisseurs de priorité supérieure (notamment natif) sont perdants par priorité.\n\n---\n\n## Limite : métadonnées de découverte vs chargement d'extension à l'exécution\n\nLa découverte de `gemini-extension.json` alimente actuellement les métadonnées de capacité (éléments `Extension`). Elle ne charge **pas** directement les modules d'extension TS/JS exécutables.\n\nLe chargement des modules à l'exécution (`discoverAndLoadExtensions()` / `loadExtensions()`) utilise `extension-modules` et des chemins explicites, et filtre actuellement les modules découverts automatiquement au seul fournisseur `native`.\n\nImplication pratique :\n\n- Les extensions de manifeste Gemini sont découvrables en tant qu'enregistrements de capacités.\n- Elles ne sont pas, à elles seules, exécutées en tant que modules d'extension à l'exécution par le pipeline de chargement des extensions.\n\nCette limite est intentionnelle dans l'implémentation actuelle et explique pourquoi la découverte de manifestes et le chargement de modules exécutables peuvent diverger.\n",
	"fr/extensions/marketplace.md": "---\ntitle: Système de plugins de la Place de marché\ndescription: >-\n  Système de plugins de la place de marché pour découvrir, installer et gérer\n  des collections de plugins organisées.\nsidebar:\n  order: 4\n  label: Place de marché\ni18n:\n  sourceHash: 71d9f8f93a81\n  translator: machine\n---\n\n# Système de plugins de la Place de marché\n\nLe système de place de marché vous permet de découvrir, d'installer et de gérer des plugins à partir de catalogues hébergés sur Git. Il est compatible avec le format de registre de plugins Claude Code.\n\n## Démarrage rapide\n\n```\n/marketplace add anthropics/f5-sales-demo-marketplace\n/marketplace install wordpress.com@f5-sales-demo-marketplace\n```\n\nOu tapez simplement `/marketplace` sans argument pour ouvrir le navigateur de plugins interactif.\n\n## Concepts\n\nUne **place de marché** est un dépôt Git (ou un répertoire local) contenant un fichier de catalogue à l'emplacement `.xcsh-plugin/marketplace.json`. Le catalogue répertorie les plugins disponibles avec leurs sources, descriptions et métadonnées.\n\nUn **plugin** est un répertoire contenant des compétences, des commandes, des hooks, des serveurs MCP ou des serveurs LSP. Les plugins sont identifiés par `name@marketplace` (p. ex. `code-review@f5-sales-demo-marketplace`).\n\n**Portées** : les plugins peuvent être installés à deux niveaux de portée :\n\n- **user** (par défaut) — disponible dans tous les projets, stocké dans `~/.xcsh/plugins/installed_plugins.json`\n- **project** — disponible uniquement dans le projet courant, stocké dans `.xcsh/installed_plugins.json`\n\nLes installations à portée projet masquent les installations à portée utilisateur du même plugin.\n\n## Commandes\n\n### Mode interactif\n\n| Commande | Effet |\n|---|---|\n| `/marketplace` | Ouvrir le navigateur de plugins interactif (installation) |\n\n### Gestion de la place de marché\n\n| Commande | Effet |\n|---|---|\n| `/marketplace add <source>` | Ajouter une source de place de marché |\n| `/marketplace remove <name>` | Supprimer une place de marché |\n| `/marketplace update [name]` | Récupérer à nouveau le(s) catalogue(s) ; omettre le nom pour tout mettre à jour |\n| `/marketplace list` | Lister les places de marché configurées |\n\n### Opérations sur les plugins\n\n| Commande | Effet |\n|---|---|\n| `/marketplace discover [marketplace]` | Parcourir les plugins disponibles |\n| `/marketplace install [--force] [--scope user\\|project] name@marketplace` | Installer un plugin |\n| `/marketplace uninstall [--scope user\\|project] name@marketplace` | Désinstaller un plugin |\n| `/marketplace installed` | Lister les plugins de la place de marché installés |\n| `/marketplace upgrade [--scope user\\|project] [name@marketplace]` | Mettre à jour un ou tous les plugins |\n\n### Équivalents en ligne de commande\n\nLes mêmes opérations sont disponibles depuis la ligne de commande :\n\n```\nxcsh plugin marketplace add <source>\nxcsh plugin marketplace remove <name>\nxcsh plugin marketplace update [name]\nxcsh plugin marketplace list\nxcsh plugin discover [marketplace]\nxcsh plugin install --scope project name@marketplace\n```\n\n## Sources de la place de marché\n\nLorsque vous exécutez `/marketplace add <source>`, le système classifie la source :\n\n| Format de source | Type | Exemple |\n|---|---|---|\n| `owner/repo` | Raccourci GitHub | `anthropics/f5-sales-demo-marketplace` |\n| `https://...*.json` | URL de catalogue directe | `https://example.com/marketplace.json` |\n| `https://...*.git` ou `git@...` | Dépôt Git | `https://github.com/org/repo.git` |\n| `./path` ou `~/path` ou `/path` | Répertoire local | `./my-marketplace` |\n\nLe système clone le dépôt (ou lit le répertoire local), localise `.xcsh-plugin/marketplace.json`, le valide et met le catalogue en cache localement.\n\n## Format du catalogue (marketplace.json)\n\nUn catalogue de place de marché se trouve à l'emplacement `.xcsh-plugin/marketplace.json` à la racine du dépôt :\n\n```json\n{\n  \"$schema\": \"https://anthropic.com/claude-code/marketplace.schema.json\",\n  \"name\": \"my-marketplace\",\n  \"owner\": {\n    \"name\": \"Your Name\",\n    \"email\": \"you@example.com\"\n  },\n  \"description\": \"A collection of plugins\",\n  \"plugins\": [\n    {\n      \"name\": \"my-plugin\",\n      \"description\": \"What this plugin does\",\n      \"source\": \"./plugins/my-plugin\",\n      \"category\": \"development\",\n      \"homepage\": \"https://github.com/you/my-plugin\"\n    }\n  ]\n}\n```\n\n### Champs obligatoires\n\n| Champ | Description |\n|---|---|\n| `name` | Nom de la place de marché. Alphanumérique en minuscules, tirets et points. Doit commencer et se terminer par un caractère alphanumérique. Maximum 64 caractères. |\n| `owner.name` | Nom du propriétaire de la place de marché |\n| `plugins` | Tableau des entrées de plugins |\n\n### Champs d'une entrée de plugin\n\n| Champ | Obligatoire | Description |\n|---|---|---|\n| `name` | oui | Nom du plugin (mêmes règles que pour le nom de la place de marché) |\n| `source` | oui | Emplacement du plugin (voir ci-dessous) |\n| `description` | non | Courte description |\n| `version` | non | Chaîne de version |\n| `author` | non | `{ name, email? }` |\n| `homepage` | non | URL |\n| `category` | non | Chaîne de catégorie (p. ex. `development`, `productivity`, `security`) |\n| `tags` | non | Tableau de tags sous forme de chaînes |\n| `strict` | non | Booléen |\n| `commands` | non | Commandes slash fournies |\n| `agents` | non | Agents fournis |\n| `hooks` | non | Définitions de hooks |\n| `mcpServers` | non | Définitions de serveurs MCP |\n| `lspServers` | non | Définitions de serveurs LSP |\n\n### Formats de source de plugin\n\nLe champ `source` prend en charge plusieurs formats :\n\n**Chemin relatif** (dans le dépôt de la place de marché) :\n\n```json\n\"source\": \"./plugins/my-plugin\"\n```\n\n**URL de dépôt Git** :\n\n```json\n\"source\": {\n  \"source\": \"url\",\n  \"url\": \"https://github.com/org/repo.git\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Raccourci GitHub** :\n\n```json\n\"source\": {\n  \"source\": \"github\",\n  \"repo\": \"org/repo\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Sous-répertoire Git** (monorepo) :\n\n```json\n\"source\": {\n  \"source\": \"git-subdir\",\n  \"url\": \"https://github.com/org/monorepo.git\",\n  \"path\": \"plugins/my-plugin\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Package npm** :\n\n```json\n\"source\": {\n  \"source\": \"npm\",\n  \"package\": \"@scope/my-plugin\",\n  \"version\": \"1.0.0\"\n}\n```\n\n## Structure sur disque\n\n```\n~/.xcsh/\n  config/\n    marketplaces.json          # Registre des places de marché ajoutées\n  plugins/\n    installed_plugins.json     # Plugins installés à portée utilisateur\n    cache/\n      marketplaces/            # Catalogues de places de marché mis en cache\n      plugins/                 # Répertoires de plugins mis en cache\n\n<project>/.xcsh/\n  installed_plugins.json       # Plugins installés à portée projet\n```\n\n## Règles de nommage\n\nLes noms de places de marché et de plugins doivent :\n\n- Commencer et se terminer par une lettre minuscule ou un chiffre\n- Contenir uniquement des lettres minuscules, des chiffres, des tirets et des points\n- Ne pas dépasser 64 caractères\n\nLes identifiants de plugins (`name@marketplace`) ne doivent pas dépasser 128 caractères au total.\n\nExemples valides : `my-plugin`, `code-review`, `wordpress.com`, `ai-firstify`\nExemples invalides : `-bad`, `bad-`, `.bad`, `Bad`, `under_score`\n",
	"fr/extensions/plugin-manager-installer-plumbing.md": "---\ntitle: Gestion des plugins et mécanique d'installation\ndescription: >-\n  Fonctionnement interne du gestionnaire de plugins couvrant l'installation, la\n  validation, la résolution des dépendances et la gestion du cycle de vie.\nsidebar:\n  order: 5\n  label: Gestionnaire de plugins\ni18n:\n  sourceHash: 9c33e5a2c22a\n  translator: machine\n---\n\n# Gestion des plugins et mécanique d'installation\n\nCe document décrit comment les opérations `xcsh plugin` modifient l'état des plugins sur le disque et comment les plugins installés deviennent des capacités d'exécution (les outils aujourd'hui, la résolution de chemin pour les hooks et commandes est disponible).\n\n## Périmètre et architecture\n\nDeux implémentations de gestion des plugins coexistent dans la base de code :\n\n1. **Chemin actif utilisé par les commandes CLI** : `PluginManager` (`src/extensibility/plugins/manager.ts`)\n2. **Module auxiliaire historique** : fonctions d'installation (`src/extensibility/plugins/installer.ts`)\n\nL'exécution des commandes `xcsh plugin ...` passe par `PluginManager`.\n\n`installer.ts` documente toujours des vérifications de sécurité importantes et le comportement du système de fichiers, mais ce n'est pas le chemin utilisé par `src/commands/plugin.ts` + `src/cli/plugin-cli.ts`.\n\n## Cycle de vie : de l'invocation CLI à la disponibilité à l'exécution\n\n```text\nxcsh plugin <action> ...\n  -> src/commands/plugin.ts\n  -> runPluginCommand(...) in src/cli/plugin-cli.ts\n  -> PluginManager method (install/list/uninstall/link/...) \n  -> mutate ~/.xcsh/plugins/{package.json,node_modules,xcsh-plugins.lock.json}\n  -> runtime discovery: discoverAndLoadCustomTools(...)\n  -> getAllPluginToolPaths(cwd)\n  -> custom tool loader imports tool modules\n```\n\n### Points d'entrée des commandes\n\n- `src/commands/plugin.ts` définit la commande, les indicateurs et les transmet à `runPluginCommand`.\n- `src/cli/plugin-cli.ts` fait correspondre les sous-commandes aux méthodes de `PluginManager` :\n  - `install`, `uninstall`, `list`, `link`, `doctor`, `features`, `config`, `enable`, `disable`\n- Il n'existe pas d'action `update` explicite ; la mise à jour s'effectue en réexécutant `install` avec une nouvelle spécification de paquet ou de version.\n\n## Modèle sur disque\n\nL'état global des plugins réside dans `~/.xcsh/plugins` :\n\n- `package.json` — manifeste des dépendances utilisé par `bun install`/`bun uninstall`\n- `node_modules/` — paquets de plugins installés ou liens symboliques\n- `xcsh-plugins.lock.json` — état d'exécution :\n  - activation/désactivation par plugin\n  - ensemble de fonctionnalités sélectionné par plugin\n  - paramètres de plugin persistés\n\nLes remplacements locaux au projet résident dans :\n\n- `<cwd>/.xcsh/plugin-overrides.json`\n\nLes remplacements sont en lecture seule du point de vue du gestionnaire/chargeur (aucun chemin d'écriture ici) et peuvent désactiver des plugins ou remplacer les fonctionnalités et paramètres pour ce projet.\n\n## Analyse des spécifications de plugins et interprétation des métadonnées\n\n## Grammaire des spécifications d'installation\n\n`parsePluginSpec` (`parser.ts`) prend en charge :\n\n- `pkg` -> `features: null` (comportement par défaut)\n- `pkg[*]` -> activer toutes les fonctionnalités du manifeste\n- `pkg[]` -> n'activer aucune fonctionnalité optionnelle\n- `pkg[a,b]` -> activer les fonctionnalités nommées\n- `@scope/pkg@1.2.3[feat]` -> paquet scopé et versionné avec sélection explicite de fonctionnalités\n\n`extractPackageName` supprime le suffixe de version pour la recherche du chemin sur disque après l'installation.\n\n## Source du manifeste et champs requis\n\nLe manifeste est résolu comme suit :\n\n1. `package.json.xcsh`\n2. repli sur `package.json.pi`\n3. repli sur `{ version: package.version }`\n\nImplications :\n\n- Il n'existe pas de validation de schéma stricte dans le gestionnaire ou le chargeur.\n- Un paquet sans manifeste `xcsh`/`pi` est toujours installable et listable.\n- Le chargement des plugins à l'exécution (`getEnabledPlugins`) ignore les paquets sans manifeste `xcsh`/`pi`.\n- `manifest.version` est toujours écrasé par la `version` du paquet.\n\nUn JSON `package.json` malformé constitue une erreur bloquante au moment de la lecture ; une structure de manifeste malformée peut échouer plus tard uniquement lorsque des champs spécifiques sont consommés.\n\n## Flux d'installation/mise à jour (`PluginManager.install`)\n\n1. Analyser la syntaxe des crochets de fonctionnalités à partir de la spécification d'installation.\n2. Valider le nom du paquet par rapport à l'expression régulière et à la liste de refus des métacaractères shell.\n3. S'assurer que le `package.json` du plugin existe (carte des dépendances privées `xcsh-plugins`).\n4. Exécuter `bun install <packageSpec>` dans `~/.xcsh/plugins`.\n5. Lire le `node_modules/<name>/package.json` du paquet installé.\n6. Résoudre le manifeste et calculer `enabledFeatures` :\n   - `[*]` : toutes les fonctionnalités déclarées (ou `null` si aucune carte de fonctionnalités)\n   - `[a,b]` : valide que chaque fonctionnalité existe dans la carte des fonctionnalités du manifeste\n   - `[]` : liste de fonctionnalités vide\n   - spécification brute : `null` (utiliser la politique par défaut plus tard dans le chargeur)\n7. Mettre à jour l'entrée d'état d'exécution dans le fichier de verrouillage : `{ version, enabledFeatures, enabled: true }`.\n\n### Sémantique de mise à jour\n\nÉtant donné que la mise à jour est pilotée par l'installation :\n\n- `xcsh plugin install pkg@newVersion` met à jour la dépendance et la version dans le fichier de verrouillage.\n- Les paramètres existants sont préservés ; l'entrée d'état est écrasée pour la version, les fonctionnalités et l'activation.\n- Il n'existe pas de logique de « vérification des mises à jour » ni de migration transactionnelle.\n\n## Flux de suppression (`PluginManager.uninstall`)\n\n1. Valider le nom du paquet.\n2. Exécuter `bun uninstall <name>` dans le répertoire des plugins.\n3. Supprimer l'état d'exécution du plugin dans le fichier de verrouillage :\n   - `config.plugins[name]`\n   - `config.settings[name]`\n\nSi la commande de désinstallation échoue, l'état d'exécution n'est pas modifié.\n\n## Flux de listage (`PluginManager.list`)\n\n1. Lire la carte des dépendances des plugins depuis `~/.xcsh/plugins/package.json`.\n2. Charger la configuration d'exécution du fichier de verrouillage (fichier manquant -> valeurs par défaut vides).\n3. Charger les remplacements du projet (`<cwd>/.xcsh/plugin-overrides.json`, erreurs d'analyse ou de lecture -> objet vide avec avertissement).\n4. Pour chaque dépendance avec un `package.json` résolvable :\n   - construire un enregistrement `InstalledPlugin`\n   - fusionner l'état des fonctionnalités et de l'activation :\n     - base issue du fichier de verrouillage (ou valeurs par défaut)\n     - les remplacements du projet peuvent remplacer la sélection de fonctionnalités\n     - la liste `disabled` du projet masque le plugin comme désactivé\n\nIl s'agit de l'état effectif utilisé par la sortie de statut CLI et les opérations de paramètres et fonctionnalités.\n\n## Flux de liaison (`PluginManager.link`)\n\n`link` prend en charge le développement local de plugins en créant un lien symbolique d'un paquet local vers `~/.xcsh/plugins/node_modules/<pkg.name>`.\n\nComportement :\n\n1. Résoudre `localPath` par rapport au répertoire de travail courant du gestionnaire.\n2. Exiger un `package.json` local et un champ `name`.\n3. S'assurer que les répertoires de plugins existent.\n4. Pour les noms scopés, créer le répertoire de scope.\n5. Supprimer le chemin existant à l'emplacement du lien cible.\n6. Créer le lien symbolique.\n7. Ajouter une entrée dans le fichier de verrouillage d'exécution, activée avec les fonctionnalités par défaut (`null`).\n\nMise en garde : l'implémentation actuelle de `PluginManager.link` n'applique pas la vérification de limite de chemin `cwd` présente dans `installer.ts` historique (`normalizedPath.startsWith(normalizedCwd)`), la confiance est donc de la responsabilité de l'appelant.\n\n## Chargement à l'exécution : du plugin installé aux capacités invocables\n\n## Porte de découverte\n\n`getEnabledPlugins(cwd)` (`plugins/loader.ts`) lit :\n\n- le manifeste des dépendances des plugins (`package.json`)\n- l'état d'exécution du fichier de verrouillage\n- les remplacements du projet via `getConfigDirPaths(\"plugin-overrides.json\", { user: false, cwd })`\n\nFiltrage :\n\n- ignorer si aucun `package.json` de plugin\n- ignorer si le manifeste (`xcsh`/`pi`) est absent\n- ignorer si désactivé globalement dans le fichier de verrouillage\n- ignorer si désactivé au niveau du projet\n\n## Résolution des chemins de capacités\n\nPour chaque plugin activé :\n\n- `resolvePluginToolPaths(plugin)`\n- `resolvePluginHookPaths(plugin)`\n- `resolvePluginCommandPaths(plugin)`\n\nChaque résolveur inclut des entrées de base ainsi que des entrées de fonctionnalités :\n\n- liste de fonctionnalités explicite -> uniquement les fonctionnalités sélectionnées\n- `enabledFeatures === null` -> activer les fonctionnalités marquées `default: true`\n\nLes fichiers manquants sont ignorés silencieusement (protection par `existsSync`).\n\n## Différences de câblage à l'exécution actuelles\n\n- **Les outils sont câblés dans l'exécution aujourd'hui** via `discoverAndLoadCustomTools` (`custom-tools/loader.ts`), qui appelle `getAllPluginToolPaths(cwd)`.\n- Les chemins sont dédupliqués par chemin absolu résolu lors de la découverte des outils personnalisés (ensemble `seen`, le premier chemin l'emporte).\n- **Les résolveurs de hooks et commandes existent** et sont exportés, mais ce chemin de code ne les câble pas actuellement dans un registre d'exécution de la même façon que les outils.\n\n## Détails de la gestion des verrous et de l'état\n\n`PluginManager` met en cache la configuration d'exécution en mémoire par instance (`#runtimeConfig`) et la charge paresseusement une seule fois.\n\nComportement au chargement :\n\n- fichier de verrouillage manquant -> `{ plugins: {}, settings: {} }`\n- échec de lecture ou d'analyse du fichier de verrouillage -> avertissement + mêmes valeurs par défaut vides\n\nComportement à la sauvegarde :\n\n- écrit le JSON complet du fichier de verrouillage avec indentation à chaque mutation\n\nIl n'existe pas de verrouillage inter-processus ni de stratégie de fusion ; des writers concurrents peuvent s'écraser mutuellement.\n\n## Vérifications de sécurité et limites de confiance\n\n## Validation des entrées et des paquets\n\nLe chemin actif du gestionnaire applique la validation des noms de paquets :\n\n- expression régulière pour les spécifications de paquets scopés et non scopés (avec version optionnelle)\n- liste de refus explicite des métacaractères shell (`[;&|`$(){}[]<>\\\\]`)\n\nCela limite le risque d'injection de commandes lors de l'invocation de `bun install/uninstall`.\n\n## Limite de confiance du système de fichiers\n\n- Le code du plugin s'exécute en cours de processus lorsque les modules d'outils personnalisés sont importés ; aucun bac à sable n'est utilisé.\n- Les chemins relatifs du manifeste sont joints au répertoire du paquet plugin et seule leur existence est vérifiée.\n- Le paquet plugin lui-même est du code de confiance une fois installé.\n\n## Vérifications exclusives à l'installateur historique\n\n`installer.ts` inclut des vérifications supplémentaires au moment de la liaison qui ne sont pas reproduites dans `PluginManager.link` :\n\n- le chemin local doit être résolu à l'intérieur du répertoire de travail courant du projet\n- protections supplémentaires contre le parcours de nom de paquet et de chemin pour le nommage de la cible du lien symbolique\n\nÉtant donné que le CLI utilise `PluginManager`, ces protections de liaison plus strictes ne sont pas actuellement sur le chemin principal.\n\n## Comportement en cas d'échec, de succès partiel et de rollback\n\nLe gestionnaire de plugins n'est pas transactionnel.\n\n| Étape de l'opération | Comportement en cas d'échec | Rollback |\n| --- | --- | --- |\n| `bun install` échoue | l'installation s'interrompt avec stderr | N/A (aucune écriture d'état encore effectuée) |\n| L'installation réussit, puis la validation du manifeste ou des fonctionnalités échoue | la commande échoue | Aucun rollback de désinstallation ; la dépendance peut rester dans `node_modules`/`package.json` |\n| L'installation réussit, puis l'écriture du fichier de verrouillage échoue | la commande échoue | Aucun rollback du paquet installé |\n| `bun uninstall` réussit, puis l'écriture du fichier de verrouillage échoue | la commande échoue | Le paquet est supprimé, un état d'exécution obsolète peut subsister |\n| `link` supprime l'ancienne cible puis la création du lien symbolique échoue | la commande échoue | Aucune restauration du lien ou répertoire précédent |\n\nEn pratique, `doctor --fix` peut corriger certains écarts (`bun install`, nettoyage de configuration orpheline, nettoyage de fonctionnalités invalides), mais il s'agit d'une opération au mieux.\n\n## Résumé du comportement en cas de manifeste malformé ou manquant\n\n- Champ `xcsh`/`pi` manquant :\n  - installation/listage : toléré (manifeste minimal)\n  - découverte des plugins activés à l'exécution : ignoré en tant que non-plugin\n- Fonctionnalité manquante référencée par la spécification d'installation ou `features --set/--enable` : erreur bloquante avec liste des fonctionnalités disponibles\n- `plugin-overrides.json` invalide : ignoré avec repli sur `{}` dans les chemins du gestionnaire et du chargeur\n- Chemins de fichiers d'outils, hooks ou commandes référencés par le manifeste mais manquants : ignorés silencieusement lors de l'expansion du résolveur ; signalés comme erreurs uniquement par `doctor`\n\n## Différences de modes et précédence\n\n- `--dry-run` (installation) : renvoie un résultat d'installation synthétique, aucune écriture sur le système de fichiers, le réseau ou l'état.\n- `--json` : formatage de la sortie uniquement, aucun changement de comportement.\n- Les remplacements du projet ont toujours la priorité sur le fichier de verrouillage global pour la vue des fonctionnalités et paramètres.\n- L'activation effective est `runtimeEnabled && !projectDisabled`.\n\n## Fichiers d'implémentation\n\n- [`src/commands/plugin.ts`](../../packages/coding-agent/src/commands/plugin.ts) — déclaration de commande CLI et correspondance des indicateurs\n- [`src/cli/plugin-cli.ts`](../../packages/coding-agent/src/cli/plugin-cli.ts) — dispatch des actions, gestionnaires de commandes destinés à l'utilisateur\n- [`src/extensibility/plugins/manager.ts`](../../packages/coding-agent/src/extensibility/plugins/manager.ts) — implémentation active de install/remove/list/link/state/doctor\n- [`src/extensibility/plugins/installer.ts`](../../packages/coding-agent/src/extensibility/plugins/installer.ts) — auxiliaires d'installation historiques et vérifications de sécurité supplémentaires pour la liaison\n- [`src/extensibility/plugins/loader.ts`](../../packages/coding-agent/src/extensibility/plugins/loader.ts) — découverte des plugins activés et résolution des chemins d'outils, hooks et commandes\n- [`src/extensibility/plugins/parser.ts`](../../packages/coding-agent/src/extensibility/plugins/parser.ts) — auxiliaires d'analyse des spécifications d'installation et des noms de paquets\n- [`src/extensibility/plugins/types.ts`](../../packages/coding-agent/src/extensibility/plugins/types.ts) — contrats de types pour les manifestes, l'exécution et les remplacements\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts) — câblage à l'exécution des modules d'outils fournis par les plugins\n",
	"fr/extensions/rulebook-matching-pipeline.md": "---\ntitle: Pipeline de correspondance du Rulebook\ndescription: >-\n  Pipeline de correspondance du rulebook pour la sélection et l'application\n  d'ensembles d'instructions spécifiques au contexte dans les sessions d'agent.\nsidebar:\n  order: 6\n  label: Correspondance du rulebook\ni18n:\n  sourceHash: a16a9c565053\n  translator: machine\n---\n\n# Pipeline de correspondance du Rulebook\n\nCe document décrit comment coding-agent découvre les règles à partir des formats de configuration pris en charge, les normalise en une forme `Rule` unique, résout les conflits de précédence et divise le résultat en :\n\n- **Règles du Rulebook** (disponibles pour le modèle via le prompt système + URLs `rule://`)\n- **Règles TTSR** (règles d'interruption de flux time-travel)\n\nIl reflète l'implémentation actuelle, y compris les sémantiques partielles et les métadonnées qui sont analysées mais non appliquées.\n\n## Fichiers d'implémentation\n\n- [`../src/capability/rule.ts`](../../packages/coding-agent/src/capability/rule.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/discovery/index.ts`](../../packages/coding-agent/src/discovery/index.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/cursor.ts`](../../packages/coding-agent/src/discovery/cursor.ts)\n- [`../src/discovery/windsurf.ts`](../../packages/coding-agent/src/discovery/windsurf.ts)\n- [`../src/discovery/cline.ts`](../../packages/coding-agent/src/discovery/cline.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/system-prompt.ts`](../../packages/coding-agent/src/system-prompt.ts)\n- [`../src/internal-urls/rule-protocol.ts`](../../packages/coding-agent/src/internal-urls/rule-protocol.ts)\n- [`../src/utils/frontmatter.ts`](../../packages/coding-agent/src/utils/frontmatter.ts)\n\n## 1. Forme canonique de la règle\n\nTous les fournisseurs normalisent les fichiers sources en `Rule` :\n\n```ts\ninterface Rule {\n  name: string;\n  path: string;\n  content: string;\n  globs?: string[];\n  alwaysApply?: boolean;\n  description?: string;\n  ttsrTrigger?: string;\n  _source: SourceMeta;\n}\n```\n\nL'identité de la capacité est `rule.name` (`ruleCapability.key = rule => rule.name`).\n\nConséquence : la précédence et la déduplication sont **basées uniquement sur le nom**. Deux fichiers différents portant le même `name` sont considérés comme la même règle logique.\n\n## 2. Sources de découverte et normalisation\n\n`src/discovery/index.ts` enregistre automatiquement les fournisseurs. Pour `rules`, les fournisseurs actuels sont :\n\n- `native` (priorité `100`)\n- `cursor` (priorité `50`)\n- `windsurf` (priorité `50`)\n- `cline` (priorité `40`)\n\n### Fournisseur natif (`builtin.ts`)\n\nCharge les règles `.xcsh` depuis :\n\n- projet : `<cwd>/.xcsh/rules/*.{md,mdc}`\n- utilisateur : `~/.xcsh/agent/rules/*.{md,mdc}`\n\nNormalisation :\n\n- `name` = nom de fichier sans `.md`/`.mdc`\n- frontmatter analysé via `parseFrontmatter`\n- `content` = corps (frontmatter retiré)\n- `globs`, `alwaysApply`, `description`, `ttsr_trigger` mappés directement\n\nMise en garde importante : `globs` est converti en `string[] | undefined` sans filtrage des éléments dans ce fournisseur.\n\n### Fournisseur Cursor (`cursor.ts`)\n\nCharge depuis :\n\n- utilisateur : `~/.cursor/rules/*.{mdc,md}`\n- projet : `<cwd>/.cursor/rules/*.{mdc,md}`\n\nNormalisation (`transformMDCRule`) :\n\n- `description` : conservé uniquement si chaîne de caractères\n- `alwaysApply` : seul `true` est préservé (`false` devient `undefined`)\n- `globs` : accepte un tableau (éléments chaîne uniquement) ou une chaîne unique\n- `ttsr_trigger` : chaîne uniquement\n- `name` à partir du nom de fichier sans extension\n\n### Fournisseur Windsurf (`windsurf.ts`)\n\nCharge depuis :\n\n- utilisateur : `~/.codeium/windsurf/memories/global_rules.md` (nom de règle fixe `global_rules`)\n- projet : `<cwd>/.windsurf/rules/*.md`\n\nNormalisation :\n\n- `globs` : tableau de chaînes ou chaîne unique\n- `alwaysApply`, `description` convertis depuis le frontmatter\n- `ttsr_trigger` : chaîne uniquement\n- `name` à partir du nom de fichier pour les règles de projet\n\n### Fournisseur Cline (`cline.ts`)\n\nRecherche en remontant depuis `cwd` le `.clinerules` le plus proche :\n\n- si répertoire : charge les `*.md` qu'il contient\n- si fichier : charge le fichier unique comme règle nommée `clinerules`\n\nNormalisation :\n\n- `globs` : tableau de chaînes ou chaîne unique\n- `alwaysApply` : uniquement si booléen\n- `description` : chaîne uniquement\n- `ttsr_trigger` : chaîne uniquement\n\n## 3. Comportement de l'analyse du frontmatter et ambiguïté\n\nTous les fournisseurs utilisent `parseFrontmatter` (`utils/frontmatter.ts`) avec ces sémantiques :\n\n1. Le frontmatter est analysé uniquement lorsque le contenu commence par `---` et possède un `\\n---` de fermeture.\n2. Le corps est nettoyé (trim) après l'extraction du frontmatter.\n3. Si l'analyse YAML échoue :\n   - un avertissement est journalisé,\n   - l'analyseur bascule vers une analyse simple ligne par ligne `key: value` (`^(\\w+):\\s*(.*)$`).\n\nConséquences liées à l'ambiguïté :\n\n- L'analyseur de repli ne prend pas en charge les tableaux, les objets imbriqués, les règles de citation ou les clés avec tirets.\n- Les valeurs de repli deviennent des chaînes (par exemple `alwaysApply: true` devient la chaîne `\"true\"`), de sorte que les fournisseurs nécessitant des types booléen/chaîne peuvent perdre des métadonnées.\n- `ttsr_trigger` fonctionne en mode repli (clé avec underscore) ; des clés comme `thinking-level` ne fonctionneraient pas.\n- Les fichiers sans frontmatter valide sont tout de même chargés comme règles avec des métadonnées vides et le contenu complet comme corps.\n\n## 4. Précédence des fournisseurs et déduplication\n\n`loadCapability(\"rules\")` (`capability/index.ts`) fusionne les sorties des fournisseurs puis déduplique par `rule.name`.\n\n### Modèle de précédence\n\n- Les fournisseurs sont ordonnés par priorité décroissante.\n- À priorité égale, l'ordre d'enregistrement est conservé (`cursor` avant `windsurf` depuis `discovery/index.ts`).\n- La déduplication fonctionne en premier arrivé, premier servi : le premier nom de règle rencontré est conservé ; les éléments ultérieurs portant le même nom sont marqués `_shadowed` dans `all` et exclus de `items`.\n\nL'ordre effectif des fournisseurs de règles est actuellement :\n\n1. `native` (100)\n2. `cursor` (50)\n3. `windsurf` (50)\n4. `cline` (40)\n\n### Mise en garde sur l'ordre intra-fournisseur\n\nAu sein d'un fournisseur, l'ordre des éléments provient de l'ordre des résultats du glob de `loadFilesFromDir` plus l'ordre explicite d'ajout (push). Ceci est suffisamment déterministe pour une utilisation normale mais n'est pas explicitement trié dans le code.\n\nDifférences notables d'ordre selon les sources :\n\n- `native` ajoute les répertoires de configuration du projet puis ceux de l'utilisateur.\n- `cursor` ajoute les résultats de l'utilisateur puis ceux du projet.\n- `windsurf` ajoute d'abord le `global_rules` de l'utilisateur, puis les règles du projet.\n- `cline` charge uniquement la source `.clinerules` la plus proche.\n\n## 5. Répartition dans les catégories Rulebook, Always-Apply et TTSR\n\nAprès la découverte des règles dans `createAgentSession` (`sdk.ts`) :\n\n1. Toutes les règles découvertes sont parcourues.\n2. Les règles avec `condition` (clé frontmatter ; `ttsr_trigger` / `ttsrTrigger` accepté comme alternative) sont enregistrées dans `TtsrManager`.\n3. Une liste `rulebookRules` séparée est construite avec ce prédicat :\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && !rule.alwaysApply && !!rule.description\n```\n\n4. Une liste `alwaysApplyRules` est construite :\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && rule.alwaysApply === true\n```\n\n### Comportement des catégories\n\n- **Catégorie TTSR** : toute règle avec `condition` (description non requise). Prend la priorité sur les autres catégories.\n- **Catégorie always-apply** : `alwaysApply === true`, non TTSR. Le contenu complet est injecté dans le prompt système. Résolvable via `rule://`.\n- **Catégorie rulebook** : doit avoir une description, ne doit pas être TTSR, ne doit pas être `alwaysApply`. Listée dans le prompt système par nom+description ; le contenu est lu à la demande via `rule://`.\n- Une règle ayant à la fois `condition` et `alwaysApply` va uniquement dans TTSR (TTSR a la priorité).\n- Une règle ayant à la fois `alwaysApply` et `description` va uniquement dans always-apply (pas dans le rulebook).\n\n## 6. Comment les métadonnées affectent les surfaces d'exécution\n\n### `description`\n\n- Requis pour l'inclusion dans le rulebook.\n- Affiché dans le bloc `<rules>` du prompt système.\n- L'absence de description signifie que la règle n'est pas disponible via `rule://` et n'est pas listée dans les règles du prompt système.\n\n### `globs`\n\n- Transporté sur `Rule`.\n- Affiché comme entrées `<glob>...</glob>` dans le bloc des règles du prompt système.\n- Exposé dans l'état UI des règles (liste de mode `extensions`).\n- **Non appliqué pour la correspondance automatique dans ce pipeline.** Il n'y a pas de matcher de glob à l'exécution sélectionnant les règles par fichier courant/cible d'outil.\n\n### `alwaysApply`\n\n- Analysé et préservé par les fournisseurs.\n- Utilisé dans l'affichage UI (libellé de déclencheur `\"always\"` dans le gestionnaire d'état des extensions).\n- Utilisé comme condition d'exclusion de `rulebookRules`.\n- **Le contenu complet de la règle est auto-injecté dans le prompt système** (avant la section des règles du rulebook).\n- La règle est également accessible via `rule://<name>` pour relecture.\n\n### `ttsr_trigger`\n\n- Mappé vers `rule.ttsrTrigger`.\n- Si présent, la règle est routée vers le gestionnaire TTSR, pas vers le rulebook.\n\n## 7. Chemin d'inclusion dans le prompt système\n\n`buildSystemPromptInternal` reçoit à la fois `rules` (rulebook) et `alwaysApplyRules`.\n\nLes règles always-apply sont rendues en premier, injectant leur contenu brut directement dans le prompt.\n\nLes règles du rulebook sont rendues dans une section `# Rules` avec :\n\n- `Read rule://<name> when working in matching domain`\n- Le `name`, la `description` de chaque règle, et la liste optionnelle de `<glob>`\n\nCeci est consultatif/contextuel : le texte du prompt demande au modèle de lire les règles applicables, mais le code n'applique pas l'applicabilité des globs.\n\n## 8. Comportement de l'URL interne `rule://`\n\n`RuleProtocolHandler` est enregistré avec :\n\n```ts\nnew RuleProtocolHandler({ getRules: () => [...rulebookRules, ...alwaysApplyRules] })\n```\n\nImplications :\n\n- `rule://<name>` résout à la fois les **rulebookRules** et les **alwaysApplyRules**.\n- Les règles uniquement TTSR et les règles sans description et sans `alwaysApply` ne sont pas accessibles via `rule://`.\n- La résolution est une correspondance exacte de nom.\n- Les noms inconnus renvoient une erreur listant les noms de règles disponibles.\n- Le contenu retourné est le `rule.content` brut (frontmatter retiré), type de contenu `text/markdown`.\n\n## 9. Sémantiques partielles / non appliquées connues\n\n1. Les descriptions des fournisseurs mentionnent des fichiers hérités (`.cursorrules`, `.windsurfrules`), mais les chemins de code de chargement actuels ne lisent pas réellement ces fichiers.\n2. Les métadonnées `globs` sont exposées au prompt/UI mais ne sont pas appliquées par la logique de sélection des règles.\n3. La sélection de règles pour `rule://` inclut les règles du rulebook et always-apply, mais pas les règles uniquement TTSR.\n4. Les avertissements de découverte (`loadCapability(\"rules\").warnings`) sont produits mais `createAgentSession` ne les expose/journalise pas actuellement dans ce chemin.\n",
	"fr/extensions/skills.md": "---\ntitle: Compétences\ndescription: >-\n  Système de compétences pour enregistrer, découvrir et invoquer des capacités\n  spécialisées dans l'agent de codage.\nsidebar:\n  order: 3\n  label: Compétences\ni18n:\n  sourceHash: 3e062cc13851\n  translator: machine\n---\n\n# Compétences\n\nLes compétences sont des packs de capacités stockés dans des fichiers, découverts au démarrage et exposés au modèle sous la forme :\n\n- de métadonnées légères dans le prompt système (nom + description)\n- de contenu à la demande via `read skill://...`\n- de commandes interactives optionnelles `/skill:<name>`\n\nCe document décrit le comportement actuel du moteur d'exécution dans `src/extensibility/skills.ts`, `src/discovery/builtin.ts`, `src/internal-urls/skill-protocol.ts` et `src/discovery/agents-md.ts`.\n\n## Ce qu'est une compétence dans cette base de code\n\nUne compétence découverte est représentée par :\n\n- `name`\n- `description`\n- `filePath` (le chemin `SKILL.md`)\n- `baseDir` (répertoire de la compétence)\n- métadonnées de source (`provider`, `level`, chemin)\n\nLe moteur d'exécution exige uniquement `name` et `path` pour la validité. En pratique, la qualité de la correspondance dépend du caractère significatif de la `description`.\n\n## Structure requise et attentes relatives à SKILL.md\n\n### Structure des répertoires\n\nPour la découverte basée sur les fournisseurs (fournisseurs natifs/Claude/Codex/Agents/plugin), les compétences sont découvertes à **un niveau sous `skills/`** :\n\n- `<skills-root>/<skill-name>/SKILL.md`\n\nLes structures imbriquées telles que `<skills-root>/group/<skill>/SKILL.md` ne sont pas découvertes par les chargeurs de fournisseurs.\n\nPour `skills.customDirectories`, l'analyse utilise la même structure non récursive (`*/SKILL.md`).\n\n```text\nStructure découverte par les fournisseurs (non récursive sous skills/) :\n\n<root>/skills/\n  ├─ postgres/\n  │   └─ SKILL.md      ✅ découvert\n  ├─ pdf/\n  │   └─ SKILL.md      ✅ découvert\n  └─ team/\n      └─ internal/\n          └─ SKILL.md  ❌ non découvert par les chargeurs de fournisseurs\n\nL'analyse des répertoires personnalisés est également non récursive, les chemins imbriqués sont donc ignorés, sauf si vous faites pointer `customDirectories` vers ce répertoire parent imbriqué.\n```\n\n### Frontmatter de `SKILL.md`\n\nChamps frontmatter pris en charge sur le type de compétence :\n\n- `name?: string`\n- `description?: string`\n- `globs?: string[]`\n- `alwaysApply?: boolean`\n- les clés supplémentaires sont conservées en tant que métadonnées inconnues\n\nComportement actuel du moteur d'exécution :\n\n- `name` prend par défaut le nom du répertoire de la compétence\n- `description` est requise pour :\n  - la découverte de compétences du fournisseur `.xcsh` natif (`requireDescription: true`)\n  - les analyses `skills.customDirectories` via `scanSkillsFromDir` dans `src/discovery/helpers.ts` (non récursif)\n- les fournisseurs non natifs peuvent charger des compétences sans description\n\n## Pipeline de découverte\n\n`discoverSkills()` dans `src/extensibility/skills.ts` effectue deux passes :\n\n1. **Fournisseurs de capacités** via `loadCapability(\"skills\")`\n2. **Répertoires personnalisés** via `scanSkillsFromDir(..., { requireDescription: true })` (énumération de répertoires à un niveau)\n\nSi `skills.enabled` est `false`, la découverte ne renvoie aucune compétence.\n\n### Fournisseurs de compétences intégrés et précédence\n\nL'ordre des fournisseurs est prioritaire (la valeur la plus élevée l'emporte), puis l'ordre d'enregistrement pour les égalités.\n\nFournisseurs de compétences actuellement enregistrés :\n\n1. `native` (priorité 100) — compétences utilisateur/projet `.xcsh` via `src/discovery/builtin.ts`\n2. `claude` (priorité 80)\n3. groupe de priorité 70 (dans l'ordre d'enregistrement) :\n   - `claude-plugins`\n   - `agents`\n   - `codex`\n\nLa clé de déduplication est le nom de la compétence. Le premier élément portant un nom donné l'emporte.\n\n### Bascules de source et filtrage\n\n`discoverSkills()` applique ces contrôles :\n\n- bascules de source : `enableCodexUser`, `enableClaudeUser`, `enableClaudeProject`, `enablePiUser`, `enablePiProject`\n- filtres glob sur le nom de compétence :\n  - `ignoredSkills` (exclusion)\n  - `includeSkills` (liste d'autorisation d'inclusion ; vide signifie tout inclure)\n\nL'ordre des filtres est :\n\n1. source activée\n2. non ignorée\n3. incluse (si la liste d'inclusion est présente)\n\nPour les fournisseurs autres que codex/claude/native (par exemple `agents`, `claude-plugins`), l'activation revient actuellement à : activé si **n'importe quelle** bascule de source intégrée est activée.\n\n### Gestion des collisions et des doublons\n\n- La déduplication des capacités conserve déjà la première compétence par nom (fournisseur de priorité la plus élevée)\n- `extensibility/skills.ts` en outre :\n  - déduplique les fichiers identiques par `realpath` (compatible avec les liens symboliques)\n  - émet des avertissements de collision lorsqu'un nom de compétence ultérieur est en conflit\n  - conserve l'API pratique `discoverSkillsFromDir({ dir, source })` comme adaptateur allégé sur `scanSkillsFromDir`\n- Les compétences des répertoires personnalisés sont fusionnées après les compétences des fournisseurs et suivent le même comportement de collision\n\n## Comportement d'utilisation au moment de l'exécution\n\n### Exposition dans le prompt système\n\nLa construction du prompt système (`src/system-prompt.ts`) utilise les compétences découvertes comme suit :\n\n- si l'outil `read` est disponible :\n  - inclure la liste des compétences découvertes dans le prompt\n- sinon :\n  - omettre la liste découverte\n\nLes sous-agents d'outils de tâche reçoivent la liste de compétences découvertes/fournies de la session via la création de session normale ; il n'existe pas de remplacement d'épinglage de compétences par tâche.\n\n### Commandes interactives `/skill:<name>`\n\nSi `skills.enableSkillCommands` est true, le mode interactif enregistre une commande slash par compétence découverte.\n\nComportement de `/skill:<name> [args]` :\n\n- lit le fichier de compétence directement depuis `filePath`\n- supprime le frontmatter\n- injecte le corps de la compétence en tant que message personnalisé de suivi\n- ajoute des métadonnées (`Skill: <path>`, `User: <args>` optionnel)\n\n## Comportement des URL `skill://`\n\n`src/internal-urls/skill-protocol.ts` prend en charge :\n\n- `skill://<name>` → résout vers le `SKILL.md` de cette compétence\n- `skill://<name>/<relative-path>` → résout à l'intérieur du répertoire de cette compétence\n\n```text\nRésolution des URL skill://\n\nskill://pdf\n  -> <pdf-base>/SKILL.md\n\nskill://pdf/references/tables.md\n  -> <pdf-base>/references/tables.md\n\nProtections :\n- rejette les chemins absolus\n- rejette la traversée `..`\n- rejette tout chemin résolu qui sort de <pdf-base>\n```\n\nDétails de résolution :\n\n- le nom de la compétence doit correspondre exactement\n- les chemins relatifs sont décodés en URL\n- les chemins absolus sont rejetés\n- la traversée de chemin (`..`) est rejetée\n- le chemin résolu doit rester dans `baseDir`\n- les fichiers manquants renvoient une erreur explicite `File not found`\n\nType de contenu :\n\n- `.md` => `text/markdown`\n- tout le reste => `text/plain`\n\nAucune recherche de secours n'est effectuée pour les ressources manquantes.\n\n## Compétences vs XCSH.md, commandes, outils, hooks\n\n### Compétences vs XCSH.md\n\n- **Compétences** : packs de capacités nommés et optionnels, sélectionnés par le contexte de la tâche ou explicitement demandés\n- **XCSH.md/fichiers de contexte** : fichiers d'instructions persistants chargés en tant que capacité de fichier de contexte et fusionnés selon les règles de niveau/profondeur\n\n`src/discovery/agents-md.ts` parcourt spécifiquement les répertoires ancêtres depuis `cwd` pour découvrir les fichiers `XCSH.md` autonomes (jusqu'à une profondeur de 20), en excluant les segments de répertoires cachés.\n\n### Compétences vs commandes slash\n\n- **Compétences** : contenu de connaissances/flux de travail lisible par le modèle\n- **Commandes slash** : points d'entrée de commandes invoquées par l'utilisateur\n- `/skill:<name>` est un raccourci pratique qui injecte le texte de la compétence ; il ne modifie pas la sémantique de découverte des compétences\n\n### Compétences vs outils personnalisés\n\n- **Compétences** : contenu de documentation/flux de travail chargé via le contexte du prompt et `read`\n- **Outils personnalisés** : API d'outils exécutables appelables par le modèle avec des schémas et des effets secondaires au moment de l'exécution\n\n### Compétences vs hooks\n\n- **Compétences** : contenu passif\n- **Hooks** : intercepteurs d'exécution pilotés par événements pouvant bloquer/modifier le comportement durant l'exécution\n\n## Conseils de création pratiques liés à la logique de découverte\n\n- Placez chaque compétence dans son propre répertoire : `<skills-root>/<skill-name>/SKILL.md`\n- Incluez toujours un frontmatter explicite `name` et `description`\n- Conservez les ressources référencées sous le même répertoire de compétence et accédez-y avec `skill://<name>/...`\n- Pour une taxonomie imbriquée (`team/domain/skill`), faites pointer `skills.customDirectories` vers le répertoire parent imbriqué ; l'analyse elle-même reste non récursive\n- Évitez les noms de compétences dupliqués entre les sources ; la première correspondance l'emporte selon la précédence du fournisseur\n",
	"fr/index.md": "---\ntitle: Documentation xcsh\ndescription: >-\n  AI-powered development CLI with TypeScript coding agent and Rust native layer\n  for long-lived sessions, MCP support, and platform packaging.\nsidebar:\n  order: 0\n  label: Vue d'ensemble\ni18n:\n  sourceHash: b9288f42bf46\n  translator: machine\n---\n\nxcsh est un CLI de développement alimenté par l'IA, doté d'un agent de codage TypeScript et d'une couche native Rust (`pi-natives`). Il étend la lignée open-source\n[`badlogic/pi-mono`](https://github.com/badlogic/pi-mono) avec un runtime renforcé, des sessions de longue durée avec navigation arborescente et compaction, un outil Python IPython, un support MCP complet, un système de compétences, et un packaging plateforme ciblant Linux, macOS et Windows.\n\n## Par où commencer\n\n- **[Contextes F5 XC](/runtime-tools/context-command)** — connectez-vous aux tenants F5 Distributed Cloud. Créez des contextes, basculez entre eux, gérez les espaces de noms et les identifiants.\n- **Configuration** — comment xcsh découvre, résout et superpose la configuration.\n- **Runtime et outils** — les environnements d'exécution bash / notebook / resolve et la surface des commandes slash.\n- **Sessions** — journal d'entrées en ajout seul, navigation arborescente, compaction et système de mémoire autonome.\n- **Natives (Rust)** — architecture de l'addon N-API `pi-natives` qui alimente shell / PTY / média / recherche.\n- **MCP** — configuration, mécanismes internes du protocole, cycle de vie du runtime, et comment créer des serveurs et des outils.\n- **Extensions, compétences et plugins** — création, chargement, règles de correspondance, marketplace et installateur de plugins.\n- **Fournisseurs et modèles** — configuration des modèles, mécanismes internes du streaming et runtime Python / IPython.\n- **TUI** — thèmes, commande `/tree`, et points d'intégration pour les extensions et outils personnalisés.\n\n## Organisation de cette documentation\n\nChaque groupe de premier niveau dans la barre latérale correspond à un sous-système de l'agent. Au sein d'un groupe, les pages vont de « vue d'ensemble » à « mécanismes internes », de sorte que vous pouvez arrêter votre lecture dès que vous disposez de suffisamment de contexte pour la tâche en cours.\n",
	"fr/mcp/mcp-config.md": "---\ntitle: Configuration MCP\ndescription: >-\n  Configuration, validation et gestion du serveur MCP pour le runtime de l'agent\n  de codage.\nsidebar:\n  order: 1\n  label: Configuration\ni18n:\n  sourceHash: ef8b49458ce9\n  translator: machine\n---\n\n# Configuration MCP dans OMP\n\nCe guide explique comment ajouter, modifier et valider des serveurs MCP pour l'agent de codage OMP.\n\nSource de référence dans le code :\n\n- Types de configuration du runtime : `packages/coding-agent/src/mcp/types.ts`\n- Écrivain de configuration : `packages/coding-agent/src/mcp/config-writer.ts`\n- Chargeur + validation : `packages/coding-agent/src/mcp/config.ts`\n- Découverte `mcp.json` autonome : `packages/coding-agent/src/discovery/mcp-json.ts`\n- Schéma : `packages/coding-agent/src/config/mcp-schema.json`\n\n## Emplacements de configuration recommandés\n\nOMP peut découvrir des serveurs MCP depuis plusieurs outils (`.claude/`, `.cursor/`, `.vscode/`, `opencode.json`, et d'autres), mais pour une configuration native OMP, vous devriez généralement utiliser l'un de ces fichiers :\n\n- Projet : `.xcsh/mcp.json`\n- Utilisateur : `~/.xcsh/mcp.json`\n\nOMP accepte également des fichiers autonomes de repli à la racine du projet :\n\n- `mcp.json`\n- `.mcp.json`\n\nUtilisez `.xcsh/mcp.json` lorsque vous souhaitez qu'OMP gère la configuration. Utilisez `mcp.json` / `.mcp.json` à la racine uniquement lorsque vous souhaitez un fichier de repli portable que d'autres clients MCP pourront également lire.\n\n## Ajouter une référence de schéma\n\nAjoutez cette ligne en haut du fichier pour l'autocomplétion et la validation dans l'éditeur :\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {}\n}\n```\n\nOMP écrit désormais ceci automatiquement lorsque `/mcp add`, `/mcp enable`, `/mcp disable`, `/mcp reauth`, ou d'autres flux d'écriture de configuration créent ou mettent à jour un fichier MCP géré par OMP.\n\n## Structure du fichier\n\nOMP prend en charge cette structure de niveau supérieur :\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"server-name\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"some-mcp-server\"]\n    }\n  },\n  \"disabledServers\": [\"server-name\"]\n}\n```\n\nClés de niveau supérieur :\n\n- `$schema` — URL de schéma JSON optionnelle pour les outils\n- `mcpServers` — correspondance entre le nom du serveur et sa configuration\n- `disabledServers` — liste de refus au niveau utilisateur permettant de désactiver les serveurs découverts par nom\n\nLes noms de serveurs doivent correspondre à `^[a-zA-Z0-9_.-]{1,100}$`.\n\n## Champs de serveur pris en charge\n\nChamps partagés pour chaque transport :\n\n- `enabled?: boolean` — ignore ce serveur si `false`\n- `timeout?: number` — délai d'expiration de connexion en millisecondes\n- `auth?: { ... }` — métadonnées d'authentification utilisées par OMP pour les flux OAuth/clé API\n- `oauth?: { ... }` — paramètres client OAuth explicites utilisés lors de l'authentification/réauthentification\n\n### Transport `stdio`\n\n`stdio` est la valeur par défaut lorsque `type` est omis.\n\nObligatoire :\n\n- `command: string`\n\nOptionnel :\n\n- `type?: \"stdio\"`\n- `args?: string[]`\n- `env?: Record<string, string>`\n- `cwd?: string`\n\nExemple :\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/Users/alice/projects\",\n        \"/Users/alice/Documents\"\n      ]\n    }\n  }\n}\n```\n\nCela suit le package officiel du serveur MCP Filesystem (`@modelcontextprotocol/server-filesystem`).\n\n### Transport `http`\n\nObligatoire :\n\n- `type: \"http\"`\n- `url: string`\n\nOptionnel :\n\n- `headers?: Record<string, string>`\n\nExemple :\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\nCela correspond au point de terminaison hébergé du serveur MCP GitHub.\n\n### Transport `sse`\n\nObligatoire :\n\n- `type: \"sse\"`\n- `url: string`\n\nOptionnel :\n\n- `headers?: Record<string, string>`\n\nExemple :\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"legacy-remote\": {\n      \"type\": \"sse\",\n      \"url\": \"https://example.com/mcp/sse\"\n    }\n  }\n}\n```\n\n`sse` est toujours pris en charge pour la compatibilité, mais la spécification MCP préconise désormais le HTTP Streamable (`type: \"http\"`) pour les nouveaux serveurs.\n\n## Champs d'authentification\n\nOMP comprend deux objets liés à l'authentification.\n\n### `auth`\n\n```json\n{\n  \"type\": \"oauth\" | \"apikey\",\n  \"credentialId\": \"optional-stored-credential-id\",\n  \"tokenUrl\": \"optional-token-endpoint\",\n  \"clientId\": \"optional-client-id\",\n  \"clientSecret\": \"optional-client-secret\"\n}\n```\n\nUtilisez ceci lorsqu'OMP doit mémoriser comment réhydrater les identifiants d'un serveur.\n\n### `oauth`\n\n```json\n{\n  \"clientId\": \"...\",\n  \"clientSecret\": \"...\",\n  \"redirectUri\": \"...\",\n  \"callbackPort\": 3334,\n  \"callbackPath\": \"/oauth/callback\"\n}\n```\n\nUtilisez ceci lorsque le serveur MCP nécessite des paramètres client OAuth explicites.\n\nSlack est l'exemple le plus clair à ce jour. Le serveur MCP de Slack est hébergé sur `https://mcp.slack.com/mcp`, utilise le HTTP Streamable et nécessite un OAuth confidentiel avec les identifiants client de votre application Slack.\n\nExemple :\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\nPoints de terminaison Slack pertinents issus de la documentation Slack :\n\n- Point de terminaison MCP : `https://mcp.slack.com/mcp`\n- Point de terminaison d'autorisation : `https://slack.com/oauth/v2_user/authorize`\n- Point de terminaison de jeton : `https://slack.com/api/oauth.v2.user.access`\n\n## Exemples courants à copier-coller\n\n### Serveur Filesystem via stdio\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/absolute/path/one\",\n        \"/absolute/path/two\"\n      ]\n    }\n  }\n}\n```\n\n### Serveur hébergé GitHub via HTTP\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\n### Serveur local GitHub via Docker\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"-i\",\n        \"--rm\",\n        \"-e\",\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\",\n        \"ghcr.io/github/github-mcp-server\"\n      ],\n      \"env\": {\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n      }\n    }\n  }\n}\n```\n\nCela correspond à l'image Docker locale officielle de GitHub `ghcr.io/github/github-mcp-server`.\n\n### Serveur hébergé Slack via OAuth\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\n## Secrets et résolution de variables\n\nC'est la partie qui pose généralement problème.\n\n### Dans `.xcsh/mcp.json` et `~/.xcsh/mcp.json`\n\nAvant qu'OMP lance un serveur ou effectue une requête HTTP, il résout les valeurs `env` et `headers` de la manière suivante :\n\n1. Si une valeur commence par `!`, OMP l'exécute comme une commande shell et utilise la sortie standard nettoyée.\n2. Sinon, OMP vérifie d'abord si la valeur correspond au nom d'une variable d'environnement.\n3. Si cette variable d'environnement n'est pas définie, OMP utilise la chaîne littéralement.\n\nExemples :\n\n```json\n{\n  \"env\": {\n    \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n  },\n  \"headers\": {\n    \"X-MCP-Insiders\": \"true\"\n  }\n}\n```\n\nCela signifie que les formulations suivantes sont valides et pratiques pour les secrets locaux :\n\n- `\"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"` → copie depuis l'environnement shell courant\n- `\"Authorization\": \"Bearer hardcoded-token\"` → utilise la valeur littérale\n- `\"Authorization\": \"!printf 'Bearer %s' \\\"$GITHUB_TOKEN\\\"\"` → construit l'en-tête à partir d'une commande\n\n### Dans `mcp.json` et `.mcp.json` à la racine\n\nLe chargeur de repli autonome développe également `${VAR}` et `${VAR:-default}` dans les chaînes lors de la découverte.\n\nExemple :\n\n```json\n{\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\",\n      \"headers\": {\n        \"Authorization\": \"Bearer ${GITHUB_TOKEN}\"\n      }\n    }\n  }\n}\n```\n\nSi vous souhaitez le comportement OMP le moins surprenant, préférez `.xcsh/mcp.json` et utilisez des valeurs explicites pour les variables d'environnement et les en-têtes.\n\n## `disabledServers`\n\n`disabledServers` est principalement utile dans le fichier de configuration utilisateur (`~/.xcsh/mcp.json`) lorsqu'un serveur est découvert depuis une autre source et que vous souhaitez qu'OMP l'ignore sans modifier la configuration de cet autre outil.\n\nExemple :\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"disabledServers\": [\"github\", \"slack\"]\n}\n```\n\n## `/mcp add` vs modification directe du JSON\n\nUtilisez `/mcp add` lorsque vous souhaitez une configuration guidée.\n\nUtilisez la modification directe du JSON lorsque :\n\n- vous avez besoin d'un transport ou d'une option d'authentification que l'assistant ne propose pas encore\n- vous souhaitez coller une définition de serveur depuis un autre client MCP\n- vous souhaitez une validation basée sur le schéma dans votre éditeur\n\nAprès modification, utilisez :\n\n- `/mcp reload` pour redécouvrir et reconnecter les serveurs dans la session courante\n- `/mcp list` pour voir de quel fichier de configuration provient un serveur\n- `/mcp test <name>` pour tester un serveur individuel\n\n## Règles de validation appliquées par OMP\n\nDepuis `validateServerConfig()` dans `packages/coding-agent/src/mcp/config.ts` :\n\n- `stdio` nécessite `command`\n- `http` et `sse` nécessitent `url`\n- un serveur ne peut pas définir à la fois `command` et `url`\n- les valeurs `type` inconnues sont rejetées\n\nImplications pratiques :\n\n- Omettre `type` signifie `stdio`\n- Si vous collez une configuration de serveur distant et oubliez `\"type\": \"http\"`, OMP le traitera comme `stdio` et se plaindra que `command` est manquant\n- `sse` reste valide pour la compatibilité, mais les nouveaux serveurs hébergés devraient généralement être configurés en `http`\n\n## Découverte et priorité\n\nOMP ne fusionne pas les définitions de serveurs en double entre les fichiers. Les fournisseurs de découverte sont classés par priorité, et la définition de priorité supérieure l'emporte.\n\nEn pratique :\n\n- préférez `.xcsh/mcp.json` ou `~/.xcsh/mcp.json` lorsque vous souhaitez une surcharge spécifique à OMP\n- gardez les noms de serveurs uniques entre les outils si possible\n- utilisez `disabledServers` dans la configuration utilisateur lorsqu'une configuration tierce réintroduit continuellement un serveur que vous ne souhaitez pas\n\n## Résolution des problèmes\n\n### `Server \"name\": stdio server requires \"command\" field`\n\nVous avez probablement omis `type: \"http\"` sur un serveur distant.\n\n### `Server \"name\": both \"command\" and \"url\" are set`\n\nChoisissez un transport. OMP traite `command` comme stdio et `url` comme http/sse.\n\n### `/mcp add` a fonctionné mais le serveur ne se connecte toujours pas\n\nLe JSON est valide, mais le serveur peut ne pas être accessible. Utilisez `/mcp test <name>` et vérifiez si :\n\n- le binaire ou l'image Docker existe\n- les variables d'environnement requises sont définies\n- l'URL distante est accessible\n- le jeton OAuth ou API est valide\n\n### Le serveur existe dans la configuration d'un autre outil mais pas dans OMP\n\nExécutez `/mcp list`. OMP découvre de nombreux fichiers MCP tiers, mais le chargement au niveau du projet peut également être désactivé via le paramètre `mcp.enableProjectConfig`.\n\n## Références\n\n- Spécification du transport MCP : <https://modelcontextprotocol.io/specification/2025-03-26/basic/transports>\n- Package du serveur Filesystem : <https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem>\n- Serveur MCP GitHub : <https://github.com/github/github-mcp-server>\n- Documentation du serveur MCP Slack : <https://docs.slack.dev/ai/slack-mcp-server/>\n",
	"fr/mcp/mcp-protocol-transports.md": "---\ntitle: Protocole MCP et mécanismes internes de transport\ndescription: >-\n  Implémentation du protocole MCP avec les couches de transport stdio, SSE et\n  HTTP streamable.\nsidebar:\n  order: 2\n  label: Protocole & transports\ni18n:\n  sourceHash: 48632064dd00\n  translator: machine\n---\n\n# Protocole MCP et mécanismes internes de transport\n\nCe document décrit comment coding-agent implémente la messagerie MCP JSON-RPC et comment les préoccupations protocolaires sont séparées des préoccupations de transport.\n\n## Périmètre\n\nCouvre :\n\n- Le flux requête/réponse et notification JSON-RPC\n- La corrélation des requêtes et le cycle de vie pour les transports stdio et HTTP/SSE\n- Le comportement du timeout et de l'annulation\n- La propagation des erreurs et la gestion des payloads malformés\n- Les frontières de sélection du transport (`stdio` vs `http`/`sse`)\n- Quelles responsabilités de reconnexion/réessai relèvent du niveau transport vs du niveau manager\n\nNe couvre pas l'expérience utilisateur d'écriture d'extensions ni l'interface de commande.\n\n## Fichiers d'implémentation\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/transports/stdio.ts`](../../packages/coding-agent/src/mcp/transports/stdio.ts)\n- [`src/mcp/transports/http.ts`](../../packages/coding-agent/src/mcp/transports/http.ts)\n- [`src/mcp/transports/index.ts`](../../packages/coding-agent/src/mcp/transports/index.ts)\n- [`src/mcp/json-rpc.ts`](../../packages/coding-agent/src/mcp/json-rpc.ts)\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n\n## Frontières des couches\n\n### Couche protocole (JSON-RPC + méthodes MCP)\n\n- Les formes de messages sont définies dans `types.ts` (`JsonRpcRequest`, `JsonRpcNotification`, `JsonRpcResponse`, `JsonRpcMessage`).\n- La logique client MCP (`client.ts`) détermine l'ordre des méthodes et le handshake de session :\n  1. Requête `initialize`\n  2. Notification `notifications/initialized`\n  3. Appels de méthodes comme `tools/list`, `tools/call`\n\n### Couche transport (`MCPTransport`)\n\n`MCPTransport` abstrait la livraison et le cycle de vie :\n\n- `request(method, params, options?) -> Promise<T>`\n- `notify(method, params?) -> Promise<void>`\n- `close()`\n- `connected`\n- Callbacks optionnels : `onClose`, `onError`, `onNotification`\n\nLes implémentations de transport gèrent le cadrage et les détails d'E/S :\n\n- `StdioTransport` : JSON délimité par des sauts de ligne via stdio de sous-processus\n- `HttpTransport` : JSON-RPC via HTTP POST, avec réponses/écoute SSE optionnelles\n\n### Avertissement important actuel\n\nLes callbacks de transport (`onClose`, `onError`, `onNotification`) sont implémentés, mais les flux actuels de `MCPClient`/`MCPManager` ne connectent pas de logique de reconnexion à ces callbacks. Les notifications ne sont consommées que si l'appelant enregistre des gestionnaires.\n\n## Sélection du transport\n\n`client.ts:createTransport()` choisit le transport à partir de la configuration :\n\n- `type` omis ou `\"stdio\"` -> `createStdioTransport`\n- `\"http\"` ou `\"sse\"` -> `createHttpTransport`\n\n`\"sse\"` est traité comme une variante du transport HTTP (même classe), pas comme une implémentation de transport séparée.\n\n## Flux de messages JSON-RPC et corrélation\n\n## Identifiants de requête\n\nChaque transport génère des identifiants par requête (chaîne `Math.random` + timestamp). Les identifiants sont des jetons de corrélation locaux au transport.\n\n## Chemin de corrélation stdio\n\n- La requête sortante est sérialisée en un objet JSON + `\\n`.\n- `#pendingRequests: Map<id, {resolve,reject}>` stocke les requêtes en cours.\n- La boucle de lecture parse le JSONL depuis stdout et appelle `#handleMessage`.\n- Si le message entrant a un `id` correspondant, la requête est résolue/rejetée.\n- Si le message entrant a une `method` et pas d'`id`, il est traité comme une notification et envoyé à `onNotification`.\n\nLes identifiants inconnus sont ignorés (pas de rejet, pas de callback d'erreur).\n\n## Chemin de corrélation HTTP\n\n- La requête sortante est un `POST` HTTP avec un corps JSON et un `id` généré.\n- Chemin de réponse non-SSE : parse une réponse JSON-RPC unique et retourne `result`/lance une exception sur `error`.\n- Chemin de réponse SSE (`Content-Type: text/event-stream`) : diffuse les événements en streaming, retourne le premier message dont l'`id` correspond à l'identifiant de requête attendu et contient `result` ou `error`.\n- Les messages SSE avec `method` et sans `id` sont traités comme des notifications.\n\nSi le flux SSE se termine avant la réponse correspondante, la requête échoue avec `No response received for request ID ...`.\n\n## Notifications\n\nLe client émet des notifications JSON-RPC via `transport.notify(...)`.\n\n- Stdio : écrit la trame de notification sur stdin (`jsonrpc`, `method`, `params` optionnel) plus un saut de ligne.\n- HTTP : envoie un corps POST sans `id` ; le succès accepte `2xx` ou `202 Accepted`.\n\nLes notifications initiées par le serveur ne sont exposées que via le `onNotification` du transport ; il n'y a pas d'abonné global par défaut dans le manager/client.\n\n## Mécanismes internes du transport stdio\n\n## Cycle de vie et transitions d'état\n\n- Initial : `connected=false`, `process=null`, map des requêtes en attente vide\n- `connect()` :\n  - lance le sous-processus avec la commande/les arguments/l'environnement/le répertoire de travail configurés\n  - marque comme connecté\n  - démarre la boucle de lecture stdout (`readJsonl`)\n  - démarre la boucle stderr (lecture/suppression ; actuellement silencieuse)\n- `close()` :\n  - marque comme déconnecté\n  - rejette toutes les requêtes en attente (`Transport closed`)\n  - tue le sous-processus\n  - attend l'arrêt de la boucle de lecture\n  - émet `onClose`\n\nSi la boucle de lecture se termine de manière inattendue, `finally` déclenche `#handleClose()` qui effectue le même rejet des requêtes en attente et le callback de fermeture.\n\n## Timeout et annulation\n\nPar requête :\n\n- le timeout par défaut est `config.timeout ?? 30000`\n- `AbortSignal` optionnel de l'appelant\n- l'abandon et le timeout rejettent tous deux la promesse en attente et nettoient l'entrée de la map\n\nL'annulation est uniquement locale : le transport n'envoie pas de notification d'annulation au niveau protocole au serveur.\n\n## Gestion des payloads malformés\n\nDans la boucle de lecture :\n\n- chaque ligne JSONL parsée est passée à `#handleMessage` dans un `try/catch`\n- les exceptions de gestion de messages malformés/invalides sont ignorées (commentaire `Skip malformed lines`)\n- la boucle continue, donc un message défectueux ne tue pas la connexion\n\nSi le parseur du flux sous-jacent lance une exception, `onError` est invoqué (quand encore connecté), puis la connexion se ferme.\n\n## Comportement de déconnexion/échec\n\nQuand le processus se termine ou que le flux se ferme :\n\n- toutes les requêtes en cours sont rejetées avec `Transport closed`\n- pas de redémarrage ou reconnexion automatique\n- les couches supérieures doivent se reconnecter en créant un nouveau transport\n\n## Notes sur la contrepression/le streaming\n\n- Les écritures sortantes utilisent `stdin.write()` + `flush()` sans attendre la sémantique de vidange.\n- Il n'y a pas de gestion explicite de file d'attente ou de seuil haut dans le transport.\n- Le traitement entrant est piloté par le flux (`for await` sur `readJsonl`), un message parsé à la fois.\n\n## Mécanismes internes du transport HTTP/SSE\n\n## Cycle de vie et sémantique de connexion\n\nLe transport HTTP a un état de connexion logique, mais le chemin de requête est sans état par appel HTTP :\n\n- `connect()` met `connected=true` (pas de handshake socket/session)\n- suivi optionnel de session serveur via l'en-tête `Mcp-Session-Id`\n- `close()` envoie optionnellement un `DELETE` avec `Mcp-Session-Id`, abandonne l'écouteur SSE, émet `onClose`\n\nDonc `connected` signifie « transport utilisable », pas « flux persistant établi ».\n\n## Comportement de l'en-tête de session\n\n- À la réponse POST, si l'en-tête `Mcp-Session-Id` est présent, le transport le stocke.\n- Les requêtes/notifications suivantes incluent `Mcp-Session-Id`.\n- `close()` tente de terminer la session serveur avec un HTTP DELETE ; les échecs de terminaison sont ignorés.\n\n## Timeout et annulation\n\nPour `request()` et `notify()` :\n\n- le timeout utilise `AbortController` (`config.timeout ?? 30000`)\n- le signal externe, s'il est fourni, est fusionné via `AbortSignal.any([...])`\n- la gestion de l'AbortError distingue l'abandon par l'appelant du timeout\n\nErreurs lancées :\n\n- timeout : `Request timeout after ...ms` (ou `SSE response timeout ...`, `Notify timeout ...`)\n- abandon par l'appelant : l'AbortError original est relancée quand le signal externe est déjà abandonné\n\n## Propagation des erreurs HTTP\n\nSur une réponse non-OK :\n\n- le texte de la réponse est inclus dans l'erreur lancée (`HTTP <status>: <text>`)\n- si présents, les indices d'authentification de `WWW-Authenticate` et `Mcp-Auth-Server` sont ajoutés\n\nSur un objet erreur JSON-RPC :\n\n- lance `MCP error <code>: <message>`\n\nL'échec de parsing du corps JSON (`response.json()`) se propage comme une exception de parsing.\n\n## Comportement SSE et modes\n\nDeux chemins SSE existent :\n\n1. **Réponse SSE par requête** (`#parseSSEResponse`)\n   - utilisé quand le type de contenu de la réponse POST est `text/event-stream`\n   - consomme le flux jusqu'à ce que l'identifiant de réponse correspondant soit trouvé\n   - peut traiter des notifications entrelacées pendant le même flux\n\n2. **Écouteur SSE en arrière-plan** (`startSSEListener()`)\n   - écouteur GET optionnel pour les notifications initiées par le serveur\n   - actuellement non démarré automatiquement par le manager/client MCP\n   - si le GET retourne `405`, l'écouteur se désactive silencieusement (le serveur ne supporte pas ce mode)\n\n## Gestion des payloads malformés et des déconnexions\n\nLes erreurs de parsing JSON SSE remontent depuis `readSseJson` et rejettent la requête/l'écouteur.\n\n- Les erreurs de parsing SSE de requête rejettent la requête active.\n- Les erreurs de l'écouteur en arrière-plan déclenchent `onError` (sauf AbortError).\n- Pas de reconnexion automatique pour l'écouteur en arrière-plan.\n\n## Utilitaire `json-rpc.ts` vs abstraction de transport\n\n`src/mcp/json-rpc.ts` fournit les helpers `callMCP()` et `parseSSE()` pour les appels MCP HTTP directs (utilisés par l'intégration Exa), pas l'abstraction `MCPTransport` utilisée par `MCPClient`/`MCPManager`.\n\nDifférences notables par rapport à `HttpTransport` :\n\n- parse d'abord le texte complet de la réponse, puis extrait la première ligne `data:` (`parseSSE`), avec fallback JSON\n- pas de gestion de timeout de requête, pas d'API d'abandon, pas de gestion de session-id, pas de cycle de vie de transport\n- retourne l'objet enveloppe JSON-RPC brut\n\nCe chemin est léger mais moins robuste que l'implémentation complète du transport.\n\n## Responsabilités de réessai/reconnexion\n\n## Niveau transport\n\nLes implémentations de transport actuelles ne font **pas** :\n\n- réessayer les requêtes échouées\n- se reconnecter après la fin du processus stdio\n- reconnecter les écouteurs SSE\n- renvoyer les requêtes en cours après une déconnexion\n\nElles échouent rapidement et propagent les erreurs.\n\n## Niveau manager/client\n\n`MCPManager` gère l'orchestration de découverte/connexion initiale et ne peut se reconnecter qu'en relançant les flux de connexion (chemins `connectToServer`/`discoverAndConnect`). Il ne répare pas automatiquement un transport déjà connecté lors de callbacks d'échec à l'exécution.\n\n`MCPManager` dispose d'un comportement de repli au démarrage pour les serveurs lents (outils différés depuis le cache), mais il s'agit d'un repli de disponibilité d'outils, pas d'un réessai de transport.\n\n## Résumé des scénarios d'échec\n\n- **Ligne de message stdio malformée** : ignorée ; le flux continue.\n- **Fin du flux/processus stdio** : le transport se ferme ; les requêtes en attente sont rejetées avec `Transport closed`.\n- **HTTP non-2xx** : la requête/notification lance une erreur HTTP.\n- **Réponse JSON invalide** : exception de parsing propagée.\n- **SSE se termine sans identifiant correspondant** : la requête échoue avec `No response received for request ID ...`.\n- **Timeout** : erreur de timeout spécifique au transport.\n- **Abandon par l'appelant** : AbortError/raison propagée depuis le signal de l'appelant.\n\n## Règle pratique de frontière\n\nSi la préoccupation concerne la forme du message, la corrélation d'identifiants ou l'ordonnancement des méthodes MCP, elle appartient à la logique protocole/client.\n\nSi la préoccupation concerne le cadrage (JSONL vs HTTP/SSE), le parsing de flux, le cycle de vie fetch/spawn, les horloges de timeout ou le démontage de connexion, elle appartient à l'implémentation du transport.\n",
	"fr/mcp/mcp-runtime-lifecycle.md": "---\ntitle: Cycle de vie MCP à l'exécution\ndescription: >-\n  Cycle de vie des processus de serveur MCP, de l'initialisation à\n  l'enregistrement des outils, la surveillance de l'état de santé et l'arrêt.\nsidebar:\n  order: 3\n  label: Cycle de vie à l'exécution\ni18n:\n  sourceHash: d04cefaf38f8\n  translator: machine\n---\n\n# Cycle de vie MCP à l'exécution\n\nCe document décrit comment les serveurs MCP sont découverts, connectés, exposés en tant qu'outils, rafraîchis et arrêtés dans le runtime de l'agent de codage.\n\n## Cycle de vie en un coup d'œil\n\n1. **Le démarrage du SDK** appelle `discoverAndLoadMCPTools()` (sauf si MCP est désactivé).\n2. **La découverte** (`loadAllMCPConfigs`) résout les configurations de serveurs MCP à partir des sources de capacités, filtre les entrées désactivées/projet/Exa, et préserve les métadonnées de source.\n3. **La phase de connexion du gestionnaire** (`MCPManager.connectServers`) lance la connexion par serveur + `tools/list` en parallèle.\n4. **La porte de démarrage rapide** attend jusqu'à 250 ms, puis peut retourner :\n   - des `MCPTool` entièrement chargés,\n   - des échecs par serveur,\n   - ou des `DeferredMCPTool` mis en cache pour les serveurs encore en attente.\n5. **Le câblage du SDK** fusionne les outils MCP dans le registre d'outils du runtime pour la session.\n6. **La session active** peut rafraîchir les outils MCP via les flux `/mcp` (`disconnectAll` + redécouverte + `session.refreshMCPTools`).\n7. **L'arrêt** se produit lorsque les appelants invoquent `disconnectServer`/`disconnectAll` ; le gestionnaire supprime également les enregistrements d'outils MCP pour les serveurs déconnectés.\n\n## Phase de découverte et de chargement\n\n### Chemin d'entrée depuis le SDK\n\n`createAgentSession()` dans `src/sdk.ts` effectue le démarrage MCP lorsque `enableMCP` est vrai (par défaut) :\n\n- appelle `discoverAndLoadMCPTools(cwd, { ... })`,\n- transmet `authStorage`, le stockage de cache et le paramètre `mcp.enableProjectConfig`,\n- définit toujours `filterExa: true`,\n- journalise les erreurs de chargement/connexion par serveur,\n- stocke le gestionnaire retourné dans `toolSession.mcpManager` et le résultat de session.\n\nSi `enableMCP` est faux, la découverte MCP est entièrement ignorée.\n\n### Découverte et filtrage de la configuration\n\n`loadAllMCPConfigs()` (`src/mcp/config.ts`) charge les éléments canoniques de serveurs MCP via la découverte de capacités, puis les convertit en `MCPServerConfig` legacy.\n\nComportement de filtrage :\n\n- `enableProjectConfig: false` supprime les entrées au niveau projet (`_source.level === \"project\"`).\n- Les serveurs avec `enabled: false` sont ignorés avant les tentatives de connexion.\n- Les serveurs Exa sont filtrés par défaut et les clés API sont extraites pour l'intégration native de l'outil Exa.\n\nLe résultat inclut à la fois `configs` et `sources` (métadonnées utilisées ultérieurement pour l'étiquetage des fournisseurs).\n\n### Comportement en cas d'échec au niveau de la découverte\n\n`discoverAndLoadMCPTools()` distingue deux classes d'échec :\n\n- **Échec critique de la découverte** (exception provenant de `manager.discoverAndConnect`, typiquement de la découverte de configuration) : retourne un ensemble d'outils vide et une erreur synthétique `{ path: \".mcp.json\", error }`.\n- **Échec d'exécution/connexion par serveur** : le gestionnaire retourne un succès partiel avec une map `errors` ; les autres serveurs continuent.\n\nAinsi, le démarrage ne fait pas échouer l'ensemble de la session de l'agent lorsque des serveurs MCP individuels échouent.\n\n## Modèle d'état du gestionnaire\n\n`MCPManager` suit le cycle de vie à l'exécution avec des registres séparés :\n\n- `#connections: Map<string, MCPServerConnection>` — serveurs entièrement connectés.\n- `#pendingConnections: Map<string, Promise<MCPServerConnection>>` — négociation en cours.\n- `#pendingToolLoads: Map<string, Promise<{ connection, serverTools }>>` — connectés mais outils encore en chargement.\n- `#tools: CustomTool[]` — vue actuelle des outils MCP exposée aux appelants.\n- `#sources: Map<string, SourceMeta>` — métadonnées fournisseur/source même avant que la connexion ne soit terminée.\n\n`getConnectionStatus(name)` dérive le statut à partir de ces maps :\n\n- `connected` si présent dans `#connections`,\n- `connecting` si en attente de connexion ou de chargement d'outils,\n- `disconnected` sinon.\n\n## Établissement de la connexion et chronologie du démarrage\n\n## Pipeline de connexion par serveur\n\nPour chaque serveur découvert dans `connectServers()` :\n\n1. stocker/mettre à jour les métadonnées de source,\n2. ignorer si déjà connecté/en attente,\n3. valider les champs de transport (`validateServerConfig`),\n4. résoudre les substitutions d'authentification/shell (`#resolveAuthConfig`),\n5. appeler `connectToServer(name, resolvedConfig)`,\n6. appeler `listTools(connection)`,\n7. mettre en cache les définitions d'outils (`MCPToolCache.set`) au mieux.\n\nComportement de `connectToServer()` (`src/mcp/client.ts`) :\n\n- crée un transport stdio ou HTTP/SSE,\n- effectue `initialize` MCP + `notifications/initialized`,\n- utilise un délai d'expiration (`config.timeout` ou 30 s par défaut),\n- ferme le transport en cas d'échec de l'initialisation.\n\n### Porte de démarrage rapide + solution de repli différée\n\n`connectServers()` attend une course entre :\n\n- la résolution de toutes les tâches de connexion/chargement d'outils, et\n- `STARTUP_TIMEOUT_MS = 250`.\n\nAprès 250 ms :\n\n- les tâches réussies deviennent des `MCPTool` actifs,\n- les tâches rejetées produisent des erreurs par serveur,\n- les tâches encore en attente :\n  - utilisent les définitions d'outils mises en cache si disponibles (`MCPToolCache.get`) pour créer des `DeferredMCPTool`,\n  - sinon bloquent jusqu'à ce que ces tâches en attente soient résolues.\n\nIl s'agit d'un modèle de démarrage hybride : retour rapide lorsque le cache est disponible, attente de correction lorsque le cache ne l'est pas.\n\n### Comportement de complétion en arrière-plan\n\nChaque `toolsPromise` en attente a également une continuation en arrière-plan qui finit par :\n\n- remplacer la tranche d'outils de ce serveur dans l'état du gestionnaire via `#replaceServerTools`,\n- écrire le cache,\n- journaliser les échecs tardifs uniquement après le démarrage (`allowBackgroundLogging`).\n\n## Exposition des outils et disponibilité en session active\n\n### Enregistrement au démarrage\n\n`discoverAndLoadMCPTools()` convertit les outils du gestionnaire en `LoadedCustomTool[]` et décore les chemins (`mcp:<server> via <providerName>` lorsque connu).\n\n`createAgentSession()` pousse ensuite ces outils dans `customTools`, qui sont encapsulés et ajoutés au registre d'outils du runtime avec des noms comme `mcp_<server>_<tool>`.\n\n### Appels d'outils\n\n- `MCPTool` appelle les outils via une `MCPServerConnection` déjà connectée.\n- `DeferredMCPTool` attend `waitForConnection(server)` avant d'appeler ; cela permet aux outils mis en cache d'exister avant que la connexion ne soit prête.\n\nLes deux retournent une sortie d'outil structurée et convertissent les erreurs de transport/outil en contenu d'outil `MCP error: ...` (l'abandon reste un abandon).\n\n## Chemins de rafraîchissement/rechargement (démarrage vs rechargement en direct)\n\n### Chemin de démarrage initial\n\n- découverte/chargement unique dans `sdk.ts`,\n- les outils sont enregistrés dans le registre d'outils de la session initiale.\n\n### Chemin de rechargement interactif\n\nLe chemin `/mcp reload` (`src/modes/controllers/mcp-command-controller.ts`) effectue :\n\n1. `mcpManager.disconnectAll()`,\n2. `mcpManager.discoverAndConnect()`,\n3. `session.refreshMCPTools(mcpManager.getTools())`.\n\n`session.refreshMCPTools()` (`src/session/agent-session.ts`) supprime tous les outils `mcp_`, ré-encapsule les derniers outils MCP et réactive l'ensemble d'outils afin que les modifications MCP s'appliquent sans redémarrer la session.\n\nIl existe également un chemin de suivi pour les connexions tardives : après avoir attendu un serveur spécifique, si le statut devient `connected`, il ré-exécute `session.refreshMCPTools(...)` afin que les outils nouvellement disponibles soient reliés dans la session.\n\n## Santé, reconnexion et comportement en cas d'échec partiel\n\nLe comportement actuel du runtime est intentionnellement minimal :\n\n- **Pas de moniteur de santé autonome** dans le gestionnaire/client.\n- **Pas de boucle de reconnexion automatique** lorsqu'un transport se déconnecte.\n- Le gestionnaire ne s'abonne pas aux événements `onClose`/`onError` du transport ; le statut est piloté par le registre.\n- La reconnexion est explicite : flux de rechargement ou invocation directe de `connectServers()`.\n\nEn pratique :\n\n- l'échec d'un serveur ne supprime pas les outils des serveurs sains,\n- les échecs de connexion/listing sont isolés par serveur,\n- le cache d'outils et les mises à jour en arrière-plan fonctionnent au mieux (avertissements/erreurs journalisés, pas d'arrêt brutal).\n\n## Sémantique de l'arrêt\n\n### Arrêt au niveau du serveur\n\n`disconnectServer(name)` :\n\n- supprime les entrées en attente/métadonnées de source,\n- ferme le transport si connecté,\n- supprime les outils `mcp_` de ce serveur de l'état du gestionnaire.\n\n### Arrêt global\n\n`disconnectAll()` :\n\n- ferme tous les transports actifs avec `Promise.allSettled`,\n- vide les maps en attente, les sources, les connexions et la liste d'outils du gestionnaire.\n\nDans le câblage actuel, l'arrêt explicite est utilisé dans les flux de commandes MCP (pour rechargement/suppression/désactivation). Il n'y a pas de hook de disposition automatique du gestionnaire séparé dans le chemin de démarrage lui-même ; les appelants sont responsables d'invoquer les méthodes de déconnexion du gestionnaire lorsqu'ils ont besoin d'un arrêt MCP déterministe.\n\n## Modes d'échec et garanties\n\n| Scénario | Comportement | Échec critique vs au mieux |\n| --- | --- | --- |\n| La découverte lève une exception (chemin de chargement capacité/config) | Le chargeur retourne des outils vides + erreur synthétique `.mcp.json` | Démarrage de session au mieux |\n| Configuration de serveur invalide | Serveur ignoré avec entrée d'erreur de validation | Au mieux par serveur |\n| Délai de connexion dépassé/échec d'initialisation | Erreur du serveur enregistrée ; les autres continuent | Au mieux par serveur |\n| `tools/list` encore en attente au démarrage avec cache disponible | Outils différés retournés immédiatement | Démarrage rapide au mieux |\n| `tools/list` encore en attente au démarrage sans cache | Le démarrage attend la résolution des tâches en attente | Attente stricte pour la correction |\n| Échec tardif du chargement d'outils en arrière-plan | Journalisé après la porte de démarrage | Journalisation au mieux |\n| Transport interrompu à l'exécution | Pas de reconnexion automatique ; les appels futurs échouent jusqu'à reconnexion/rechargement | Récupération au mieux via action manuelle |\n\n## Surface d'API publique\n\n`src/mcp/index.ts` ré-exporte les API du chargeur/gestionnaire/client pour les appelants externes. `src/sdk.ts` expose `discoverMCPServers()` comme un wrapper de commodité retournant la même forme de résultat du chargeur.\n\n## Fichiers d'implémentation\n\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts) — façade du chargeur, normalisation des erreurs de découverte, conversion en `LoadedCustomTool`.\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts) — registres d'état du cycle de vie, flux parallèle de connexion/listing, rafraîchissement/déconnexion.\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts) — configuration du transport, négociation d'initialisation, listing/appel/déconnexion.\n- [`src/mcp/index.ts`](../../packages/coding-agent/src/mcp/index.ts) — exports de l'API du module MCP.\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — câblage de démarrage dans la session/le registre d'outils.\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts) — découverte/filtrage/validation de la configuration utilisée par le gestionnaire.\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts) — comportement à l'exécution de `MCPTool` et `DeferredMCPTool`.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — reliage en direct `refreshMCPTools`.\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts) — flux interactifs de rechargement/reconnexion.\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — proxy MCP de sous-agent via les connexions du gestionnaire parent.\n",
	"fr/mcp/mcp-server-tool-authoring.md": "---\ntitle: Création de serveurs et d'outils MCP\ndescription: >-\n  Guide pour construire des serveurs MCP personnalisés et enregistrer des outils\n  pour l'agent de codage.\nsidebar:\n  order: 4\n  label: Création de serveurs et d'outils\ni18n:\n  sourceHash: 160e7560ef1f\n  translator: machine\n---\n\n# Création de serveurs et d'outils MCP\n\nCe document explique comment les définitions de serveurs MCP deviennent des outils `mcp_*` appelables dans coding-agent, et ce à quoi les opérateurs doivent s'attendre lorsque les configurations sont invalides, dupliquées, désactivées ou protégées par authentification.\n\n## Architecture en un coup d'œil\n\n```text\nConfig sources (.xcsh/.claude/.cursor/.vscode/mcp.json, mcp.json, etc.)\n  -> discovery providers normalize to canonical MCPServer\n  -> capability loader dedupes by server name (higher provider priority wins)\n  -> loadAllMCPConfigs converts to MCPServerConfig + skips enabled:false\n  -> MCPManager connects/listTools (with auth/header/env resolution)\n  -> MCPTool/DeferredMCPTool bridge exposes tools as mcp_<server>_<tool>\n  -> AgentSession.refreshMCPTools replaces live MCP tools immediately\n```\n\n## 1) Modèle de configuration serveur et validation\n\n`src/mcp/types.ts` définit la forme de création utilisée par les auteurs de configuration MCP et le runtime :\n\n- `stdio` (par défaut lorsque `type` est absent) : nécessite `command`, optionnellement `args`, `env`, `cwd`\n- `http` : nécessite `url`, optionnellement `headers`\n- `sse` : nécessite `url`, optionnellement `headers` (conservé pour la compatibilité)\n- champs partagés : `enabled`, `timeout`, `auth`\n\n`validateServerConfig()` (`src/mcp/config.ts`) applique les règles de base du transport :\n\n- rejette les configurations qui définissent à la fois `command` et `url`\n- exige `command` pour stdio\n- exige `url` pour http/sse\n- rejette les `type` inconnus\n\n`config-writer.ts` applique cette validation pour les opérations d'ajout/mise à jour et valide également les noms de serveurs :\n\n- non vide\n- maximum 100 caractères\n- uniquement `[a-zA-Z0-9_.-]`\n\n### Pièges liés au transport\n\n- `type` omis signifie stdio. Si vous aviez l'intention d'utiliser HTTP/SSE mais avez omis `type`, `command` devient obligatoire.\n- `sse` est toujours accepté mais traité comme un transport HTTP en interne (`createHttpTransport`).\n- La validation est structurelle, pas de vérification d'accessibilité : une URL syntaxiquement valide peut toujours échouer au moment de la connexion.\n\n## 2) Découverte, normalisation et priorité\n\n### Découverte basée sur les capacités\n\n`loadAllMCPConfigs()` (`src/mcp/config.ts`) charge les éléments canoniques `MCPServer` via `loadCapability(mcpCapability.id)`.\n\nLa couche de capacités (`src/capability/index.ts`) effectue ensuite :\n\n1. le chargement des fournisseurs par ordre de priorité\n2. la déduplication par `server.name` (premier arrivé = priorité la plus haute)\n3. la validation des éléments dédupliqués\n\nRésultat : les noms de serveurs dupliqués entre les sources ne sont pas fusionnés. Une seule définition l'emporte ; les doublons de priorité inférieure sont masqués.\n\n### `.mcp.json` et fichiers associés\n\nLe fournisseur de secours dédié dans `src/discovery/mcp-json.ts` lit les fichiers `mcp.json` et `.mcp.json` à la racine du projet (priorité basse).\n\nEn pratique, les serveurs MCP proviennent également de fournisseurs de priorité supérieure (par exemple les répertoires natifs `.xcsh/...` et les répertoires de configuration spécifiques aux outils). Conseils de création :\n\n- Préférez `.xcsh/mcp.json` (projet) ou `~/.xcsh/mcp.json` (utilisateur) pour un contrôle explicite.\n- Utilisez `mcp.json` / `.mcp.json` à la racine lorsque vous avez besoin d'une compatibilité de secours.\n- Réutiliser le même nom de serveur dans plusieurs sources provoque un masquage par priorité, pas une fusion.\n\n### Comportement de normalisation\n\n`convertToLegacyConfig()` (`src/mcp/config.ts`) fait correspondre le `MCPServer` canonique au `MCPServerConfig` du runtime.\n\nComportement clé :\n\n- transport déduit comme `server.transport ?? (command ? \"stdio\" : url ? \"http\" : \"stdio\")`\n- les serveurs désactivés (`enabled === false`) sont supprimés avant la connexion\n- les champs optionnels sont préservés lorsqu'ils sont présents\n\n### Expansion des variables d'environnement lors de la découverte\n\n`mcp-json.ts` développe les espaces réservés d'environnement dans les champs de type chaîne avec `expandEnvVarsDeep()` :\n\n- prend en charge `${VAR}` et `${VAR:-default}`\n- les valeurs non résolues restent des chaînes littérales `${VAR}`\n\n`mcp-json.ts` effectue également des vérifications de type au runtime pour le JSON utilisateur et journalise des avertissements pour les valeurs `enabled`/`timeout` invalides au lieu de faire échouer l'ensemble du fichier.\n\n## 3) Authentification et résolution des valeurs au runtime\n\n`MCPManager.prepareConfig()`/`#resolveAuthConfig()` (`src/mcp/manager.ts`) constitue la passe finale avant la connexion.\n\n### Injection des identifiants OAuth\n\nSi la configuration contient :\n\n```ts\nauth: { type: \"oauth\", credentialId: \"...\" }\n```\n\net que l'identifiant existe dans le stockage d'authentification :\n\n- `http`/`sse` : injecte l'en-tête `Authorization: Bearer <access_token>`\n- `stdio` : injecte la variable d'environnement `OAUTH_ACCESS_TOKEN`\n\nSi la recherche de l'identifiant échoue, le gestionnaire journalise un avertissement et continue avec l'authentification non résolue.\n\n### Résolution des valeurs d'en-têtes et de variables d'environnement\n\nAvant la connexion, le gestionnaire résout chaque valeur d'en-tête/variable d'environnement via `resolveConfigValue()` (`src/config/resolve-config-value.ts`) :\n\n- une valeur commençant par `!` => exécute une commande shell, utilise la sortie standard nettoyée (mise en cache)\n- sinon, traite la valeur comme un nom de variable d'environnement d'abord (`process.env[name]`), repli sur la valeur littérale\n- les valeurs de commande/variable d'environnement non résolues sont omises de la carte finale des en-têtes/variables d'environnement\n\nMise en garde opérationnelle : cela signifie qu'une clé de commande/variable d'environnement secrète mal saisie peut supprimer silencieusement cette entrée d'en-tête/variable d'environnement, produisant des erreurs 401/403 ou des échecs de démarrage du serveur en aval.\n\n## 4) Pont d'outils : MCP -> outils appelables par l'agent\n\n`src/mcp/tool-bridge.ts` convertit les définitions d'outils MCP en `CustomTool`s.\n\n### Nommage et domaine de collision\n\nLes noms d'outils sont générés comme suit :\n\n```text\nmcp_<sanitized_server_name>_<sanitized_tool_name>\n```\n\nRègles :\n\n- mise en minuscules\n- les caractères non `[a-z_]` deviennent `_`\n- les underscores répétés sont réduits\n- le préfixe `<server>_` redondant dans le nom de l'outil est supprimé une fois\n\nCela évite de nombreuses collisions, mais pas toutes. Des noms bruts différents peuvent toujours être nettoyés vers le même identifiant (par exemple `my-server` et `my.server` sont nettoyés de manière similaire), et l'insertion dans le registre fonctionne en dernier-écrit-gagne.\n\n### Correspondance de schéma\n\n`convertSchema()` conserve le JSON Schema MCP essentiellement tel quel mais corrige les schémas d'objets auxquels il manque `properties` avec `{}` pour la compatibilité avec les fournisseurs.\n\n### Correspondance d'exécution\n\n`MCPTool.execute()` / `DeferredMCPTool.execute()` :\n\n- appelle MCP `tools/call`\n- aplatit le contenu MCP en texte affichable\n- retourne des détails structurés (`serverName`, `mcpToolName`, métadonnées du fournisseur)\n- fait correspondre `isError` signalé par le serveur à un résultat textuel `Error: ...`\n- fait correspondre les échecs de transport/runtime lancés à `MCP error: ...`\n- préserve la sémantique d'annulation en traduisant AbortError en `ToolAbortError`\n\n## 5) Cycle de vie opérateur : ajout/modification/suppression et mises à jour en direct\n\nLe mode interactif expose `/mcp` dans `src/modes/controllers/mcp-command-controller.ts`.\n\nOpérations prises en charge :\n\n- `add` (assistant ou ajout rapide)\n- `remove` / `rm`\n- `enable` / `disable`\n- `test`\n- `reauth` / `unauth`\n- `reload`\n\nLes écritures de configuration sont atomiques (`writeMCPConfigFile` : fichier temporaire + renommage).\n\nAprès les modifications, le contrôleur appelle `#reloadMCP()` :\n\n1. `mcpManager.disconnectAll()`\n2. `mcpManager.discoverAndConnect()`\n3. `session.refreshMCPTools(mcpManager.getTools())`\n\n`refreshMCPTools()` remplace toutes les entrées du registre `mcp_` et réactive immédiatement le dernier ensemble d'outils MCP, de sorte que les modifications prennent effet sans redémarrer la session.\n\n### Différences selon les modes\n\n- **Mode interactif/TUI** : `/mcp` offre une expérience utilisateur dans l'application (assistant, flux OAuth, texte de statut de connexion, réassociation immédiate au runtime).\n- **Intégration SDK/headless** : `discoverAndLoadMCPTools()` (`src/mcp/loader.ts`) retourne les outils chargés + les erreurs par serveur ; pas d'expérience utilisateur de commande `/mcp`.\n\n## 6) Surfaces d'erreur visibles par l'utilisateur\n\nChaînes d'erreur courantes que les utilisateurs/opérateurs rencontrent :\n\n- échecs de validation à l'ajout/mise à jour :\n  - `Invalid server config: ...`\n  - `Server \"<name>\" already exists in <path>`\n- problèmes d'arguments pour l'ajout rapide :\n  - `Use either --url or -- <command...>, not both.`\n  - `--token requires --url (HTTP/SSE transport).`\n- échecs de connexion/test :\n  - `Failed to connect to \"<name>\": <message>`\n  - texte d'aide sur le timeout suggérant d'augmenter le délai d'attente\n  - texte d'aide sur l'authentification pour `401/403`\n- flux d'authentification/OAuth :\n  - `Authentication required ... OAuth endpoints could not be discovered`\n  - `OAuth flow timed out. Please try again.`\n  - `OAuth authentication failed: ...`\n- utilisation d'un serveur désactivé :\n  - `Server \"<name>\" is disabled. Run /mcp enable <name> first.`\n\nUn JSON source invalide lors de la découverte est généralement traité comme des avertissements/journaux ; les chemins de config-writer lèvent des erreurs explicites.\n\n## 7) Conseils pratiques de création\n\nPour une création MCP robuste dans cette base de code :\n\n1. Gardez les noms de serveurs globalement uniques à travers toutes les sources de configuration compatibles MCP.\n2. Préférez les noms alphanumériques/underscore pour éviter les collisions de noms nettoyés dans les noms d'outils `mcp_*` générés.\n3. Utilisez un `type` explicite pour éviter les défauts stdio accidentels.\n4. Traitez `enabled: false` comme un arrêt définitif : le serveur est omis de l'ensemble de connexion au runtime.\n5. Pour les configurations OAuth, stockez un `credentialId` valide ; sinon l'injection d'authentification est ignorée.\n6. Si vous utilisez la résolution de secrets basée sur les commandes (`!cmd`), vérifiez que la sortie de la commande est stable et non vide.\n\n## Fichiers d'implémentation\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts)\n- [`src/mcp/config-writer.ts`](../../packages/coding-agent/src/mcp/config-writer.ts)\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts)\n- [`src/discovery/mcp-json.ts`](../../packages/coding-agent/src/discovery/mcp-json.ts)\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/config/resolve-config-value.ts`](../../packages/coding-agent/src/config/resolve-config-value.ts)\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts)\n",
	"fr/natives/natives-addon-loader-runtime.md": "---\ntitle: Runtime du chargeur d'addon natif\ndescription: >-\n  N-API addon loader runtime with platform detection, fallback strategies, and\n  module resolution.\nsidebar:\n  order: 3\n  label: Chargeur d'addon\ni18n:\n  sourceHash: 743ea3e32c7c\n  translator: machine\n---\n\n# Runtime du chargeur d'addon natif\n\nCe document examine en profondeur la couche de chargement/validation des addons dans `@f5-sales-demo/pi-natives` : comment `native.ts` décide quel fichier `.node` charger, quand l'extraction de la charge utile embarquée s'exécute, et comment les échecs au démarrage sont signalés.\n\n## Fichiers d'implémentation\n\n- `packages/natives/src/native.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/package.json`\n\n## Périmètre et responsabilité\n\nLes responsabilités du chargeur/runtime sont intentionnellement restreintes :\n\n- Construire une liste de candidats tenant compte de la plateforme et du CPU pour les noms de fichiers et répertoires des addons.\n- Optionnellement matérialiser un addon embarqué dans un répertoire de cache versionné par utilisateur.\n- Essayer les candidats dans un ordre déterministe.\n- Rejeter les addons obsolètes ou incompatibles via `validateNative` avant d'exposer les bindings.\n\nHors périmètre ici : le comportement spécifique aux modules grep/text/highlight.\n\n## Entrées du runtime et état dérivé\n\nÀ l'initialisation du module (`export const native = loadNative();`), `native.ts` calcule le contexte statique :\n\n- **Tag de plateforme** : ``${process.platform}-${process.arch}`` (par exemple `darwin-arm64`).\n- **Version du package** : depuis `packages/natives/package.json` (champ `version`).\n- **Répertoires principaux** :\n  - `nativeDir` : local au package `packages/natives/native`.\n  - `execDir` : répertoire contenant `process.execPath`.\n  - `versionedDir` : `<getNativesDir()>/<packageVersion>`.\n  - Repli `userDataDir` :\n    - Windows : `%LOCALAPPDATA%/xcsh` (ou `%USERPROFILE%/AppData/Local/xcsh`).\n    - Non-Windows : `~/.local/bin`.\n- **Mode binaire compilé** (`isCompiledBinary`) : vrai si l'une des conditions suivantes est remplie :\n  - La variable d'environnement `PI_COMPILED` est définie, ou\n  - `import.meta.url` contient des marqueurs embarqués Bun (`$bunfs`, `~BUN`, `%7EBUN`).\n- **Surcharge de variante** : `PI_NATIVE_VARIANT` (`modern`/`baseline` uniquement ; les valeurs invalides sont ignorées).\n- **Variante sélectionnée** : surcharge explicite, sinon détection AVX2 au runtime sur x64 (`modern` si AVX2, sinon `baseline`).\n\n## Support des plateformes et résolution des tags\n\n`SUPPORTED_PLATFORMS` est fixé à :\n\n- `linux-x64`\n- `linux-arm64`\n- `darwin-x64`\n- `darwin-arm64`\n- `win32-x64`\n\nDétail du comportement :\n\n- Les plateformes non supportées ne sont pas rejetées immédiatement.\n- Le chargeur essaie d'abord tous les candidats calculés.\n- Si rien ne se charge, il lève une erreur explicite de plateforme non supportée listant les tags supportés.\n\nCela préserve des diagnostics utiles pour les cas presque compatibles tout en échouant de manière ferme pour les cibles véritablement non supportées.\n\n## Sélection de variante (`modern` / `baseline` / défaut)\n\n### Comportement x64\n\n1. Si `PI_NATIVE_VARIANT` est `modern` ou `baseline`, cette valeur prévaut.\n2. Sinon, détecter le support AVX2 :\n   - Linux : scanner `/proc/cpuinfo` pour `avx2`.\n   - macOS : interroger `sysctl` (`machdep.cpu.leaf7_features`, repli sur `machdep.cpu.features`).\n   - Windows : exécuter PowerShell `[System.Runtime.Intrinsics.X86.Avx2]::IsSupported`.\n3. Résultat :\n   - AVX2 disponible -> `modern`\n   - AVX2 indisponible/indétectable -> `baseline`\n\n### Comportement non-x64\n\n- Aucune variante n'est utilisée ; le chargeur reste sur le nom de fichier par défaut (`pi_natives.<platform>-<arch>.node`).\n\n### Construction du nom de fichier\n\nÉtant donné `tag = <platform>-<arch>` :\n\n- Non-x64 ou pas de variante : `pi_natives.<tag>.node`\n- x64 + `modern` : essayer dans l'ordre\n  1. `pi_natives.<tag>-modern.node`\n  2. `pi_natives.<tag>-baseline.node` (repli intentionnel)\n- x64 + `baseline` : uniquement `pi_natives.<tag>-baseline.node`\n\nLe `addonLabel` utilisé dans les messages d'erreur finaux est soit `<tag>` soit `<tag> (<variant>)`.\n\n## Construction des chemins candidats et ordre de repli\n\n`native.ts` construit des pools de candidats avant tout appel `require(...)`.\n\n### Candidats de release\n\nConstruits à partir de la liste de noms de fichiers résolus par variante et recherchés dans cet ordre :\n\n- **Runtime non compilé** :\n  1. `<nativeDir>/<filename>`\n  2. `<execDir>/<filename>`\n\n- **Runtime compilé** (`PI_COMPILED` ou marqueurs embarqués Bun) :\n  1. `<versionedDir>/<filename>`\n  2. `<userDataDir>/<filename>`\n  3. `<nativeDir>/<filename>`\n  4. `<execDir>/<filename>`\n\n`dedupedCandidates` supprime les doublons tout en préservant l'ordre de première occurrence.\n\n### Séquence finale au runtime\n\nAu moment du chargement :\n\n1. Le candidat optionnel d'extraction embarquée (s'il a été produit) est inséré en tête.\n2. Les candidats dédupliqués restants sont essayés dans l'ordre.\n3. Le premier candidat qui réussit à la fois le `require(...)` et passe `validateNative(...)` est retenu.\n\n## Cycle de vie de l'extraction de l'addon embarqué\n\n`embedded-addon.ts` définit une structure de manifeste généré :\n\n- `platformTag`\n- `version`\n- `files[]` où chaque entrée possède `variant`, `filename`, `filePath`\n\nLa valeur par défaut actuellement committée est `embeddedAddon: null` ; les artefacts compilés peuvent remplacer ceci par de véritables métadonnées.\n\n### Machine à états de l'extraction\n\nL'extraction (`maybeExtractEmbeddedAddon`) s'exécute uniquement lorsque toutes les conditions sont remplies :\n\n1. `isCompiledBinary === true`\n2. `embeddedAddon !== null`\n3. `embeddedAddon.platformTag === platformTag`\n4. `embeddedAddon.version === packageVersion`\n5. Un fichier embarqué approprié à la variante est trouvé\n\nLa sélection du fichier de variante reflète l'intention de variante au runtime :\n\n- Non-x64 : préférer `default`, puis le premier fichier disponible.\n- x64 + `modern` : préférer `modern`, repli sur `baseline`.\n- x64 + `baseline` : exiger `baseline`.\n\nComportement de matérialisation :\n\n1. S'assurer que `<versionedDir>` existe (`mkdirSync(..., { recursive: true })`).\n2. Si `<versionedDir>/<selected filename>` existe déjà, le réutiliser (pas de réécriture).\n3. Sinon, lire le `filePath` source embarqué et écrire le fichier cible.\n4. Retourner le chemin cible pour la tentative de chargement de plus haute priorité.\n\nEn cas d'échec, l'extraction ne plante pas immédiatement ; elle ajoute une entrée d'erreur (échec de création de répertoire ou d'écriture) et le chargeur passe au sondage normal des candidats.\n\n## Cycle de vie et transitions d'état\n\n```text\nInit\n  -> Compute platform/version/variant/candidate lists\n  -> (Compiled + embedded manifest matches?)\n       yes -> Try extract embedded to versionedDir (record errors, continue)\n       no  -> Skip extraction\n  -> For each runtime candidate in order:\n       require(candidate)\n       -> success: validateNative\n            -> pass: return bindings (READY)\n            -> fail: record error, continue\n       -> failure: record error, continue\n  -> none loaded:\n       if unsupported platform tag -> throw Unsupported platform\n       else -> throw Failed to load (full tried-path diagnostics + hints)\n```\n\n## Vérifications contractuelles de `validateNative`\n\n`validateNative(bindings, source)` applique un contrat exclusivement basé sur les fonctions sur `NativeBindings` au démarrage.\n\nMécanisme :\n\n- Pour chaque nom d'export requis, il vérifie `typeof bindings[name] === \"function\"`.\n- Les noms manquants sont agrégés.\n- Si certains sont manquants, le chargeur lève une erreur contenant :\n  - le chemin de l'addon source,\n  - la liste des exports manquants,\n  - une indication de commande de reconstruction.\n\nIl s'agit d'une barrière de compatibilité stricte contre les binaires obsolètes, les builds partiels et la dérive de symboles/noms.\n\n### Correspondance API JS ↔ exports natifs (barrière de validation)\n\n| Nom du binding JS vérifié dans `validateNative` | Nom d'export natif attendu |\n| --- | --- |\n| `grep` | `grep` |\n| `glob` | `glob` |\n| `highlightCode` | `highlightCode` |\n| `executeShell` | `executeShell` |\n| `PtySession` | `PtySession` |\n| `Shell` | `Shell` |\n| `visibleWidth` | `visibleWidth` |\n| `getSystemInfo` | `getSystemInfo` |\n| `getWorkProfile` | `getWorkProfile` |\n| `invalidateFsScanCache` | `invalidateFsScanCache` |\n\nNote : `bindings.ts` déclare uniquement le membre de base `cancelWork(id)` ; les fichiers `types.ts` des modules effectuent une fusion de déclarations pour les symboles supplémentaires que `validateNative` impose.\n\n## Comportement en cas d'échec et diagnostics\n\n## Plateforme non supportée\n\nSi tous les candidats échouent et que `platformTag` n'est pas dans `SUPPORTED_PLATFORMS`, le chargeur lève :\n\n- `Unsupported platform: <tag>`\n- La liste complète des plateformes supportées\n- Des indications explicites pour signaler un problème\n\n## Symptômes de binaire obsolète / incompatible\n\nSignal typique d'incompatibilité obsolète :\n\n- `Native addon missing exports (<candidate>). Missing: ...`\n\nCauses courantes :\n\n- Ancien binaire `.node` provenant d'une version précédente du package/de la forme de l'API.\n- Mauvais artefact de variante sélectionné (pour x64).\n- Nouvel export Rust absent de l'artefact chargé.\n\nComportement du chargeur :\n\n- Enregistre les échecs d'exports manquants par candidat.\n- Continue le sondage des candidats restants.\n- Si aucun candidat n'est validé, l'erreur finale inclut chaque chemin tenté avec chaque message d'échec.\n\n## Échecs de démarrage en mode binaire compilé\n\nEn mode compilé, les diagnostics finaux incluent :\n\n- les chemins cibles attendus du cache versionné (`<versionedDir>/<filename>`),\n- une remédiation consistant à supprimer le `<versionedDir>` obsolète et relancer,\n- des commandes `curl` de téléchargement direct de la release pour chaque nom de fichier attendu.\n\n## Échecs de démarrage en mode non compilé\n\nEn mode package/runtime normal, les diagnostics finaux incluent :\n\n- une indication de réinstallation (`bun install @f5-sales-demo/pi-natives`),\n- une commande de reconstruction locale (`bun --cwd=packages/natives run build`),\n- une indication optionnelle de build de variante x64 (`TARGET_VARIANT=baseline|modern ...`).\n\n## Comportement au runtime\n\n- Le chargeur utilise toujours la chaîne de candidats de release.\n- Définir `PI_DEV` active uniquement les diagnostics par candidat dans la console (`Loaded native addon...` et les erreurs de chargement).\n",
	"fr/natives/natives-architecture.md": "---\ntitle: Architecture des natifs\ndescription: >-\n  Architecture d'addon natif Rust N-API reliant TypeScript et les opérations\n  spécifiques à la plateforme.\nsidebar:\n  order: 1\n  label: Architecture\ni18n:\n  sourceHash: d38ed2437bb7\n  translator: machine\n---\n\n# Architecture des natifs\n\n`@f5-sales-demo/pi-natives` est une pile à trois couches :\n\n1. **Couche wrapper/API TypeScript** expose des points d'entrée JS/TS stables.\n2. **Couche de chargement/validation de l'addon** résout et valide le binaire `.node` pour l'environnement d'exécution courant.\n3. **Couche module Rust N-API** implémente les primitives critiques en termes de performances exportées vers JS.\n\nCe document constitue le fondement des documentations approfondies au niveau des modules.\n\n## Fichiers d'implémentation\n\n- `packages/natives/src/index.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `crates/pi-natives/src/lib.rs`\n\n## Couche 1 : couche wrapper/API TypeScript\n\n`packages/natives/src/index.ts` est le barrel public. Il regroupe les exports par domaine de capacité et réexporte des wrappers typés plutôt que d'exposer directement les liaisons N-API brutes.\n\nGroupes de premier niveau actuels :\n\n- **Primitives de recherche/texte** : `grep`, `glob`, `text`, `highlight`\n- **Primitives d'exécution/processus/terminal** : `shell`, `pty`, `ps`, `keys`\n- **Primitives système/média/conversion** : `image`, `html`, `clipboard`, `system-info`, `work`\n\n`packages/natives/src/bindings.ts` définit le contrat d'interface de base :\n\n- `NativeBindings` commence par des membres partagés (`cancelWork(id: number)`)\n- les liaisons spécifiques aux modules sont ajoutées par fusion de déclarations depuis le fichier `types.ts` de chaque module\n- `Cancellable` standardise les options de délai d'expiration et de signal d'abandon pour les wrappers qui exposent l'annulation\n\n**Contrat garanti (côté API) :** les consommateurs importent depuis `@f5-sales-demo/pi-natives` et utilisent des wrappers typés.\n\n**Détail d'implémentation (susceptible de changer) :** la fusion de déclarations et la disposition interne des wrappers (`src/<module>/index.ts`, `src/<module>/types.ts`).\n\n## Couche 2 : chargement et validation de l'addon\n\n`packages/natives/src/native.ts` gère la sélection de l'addon à l'exécution, l'extraction optionnelle et la validation des exports.\n\n### Modèle de résolution des candidats\n\n- Le tag de Plateforme est `\"${process.platform}-${process.arch}\"`.\n- Les tags pris en charge sont actuellement :\n  - `linux-x64`\n  - `linux-arm64`\n  - `darwin-x64`\n  - `darwin-arm64`\n  - `win32-x64`\n- x64 peut utiliser des variantes CPU :\n  - `modern` (compatible AVX2)\n  - `baseline` (repli)\n- Les architectures non-x64 utilisent le nom de fichier par défaut (sans suffixe de variante).\n\nStratégie de nommage des fichiers :\n\n- Version release : `pi_natives.<platform>-<arch>.node`\n- Version release avec variante x64 : `pi_natives.<platform>-<arch>-modern.node` et/ou `...-baseline.node`\n- `PI_DEV` active les diagnostics du chargeur mais ne modifie pas les noms de fichiers des addons\n\n### Détection de variante spécifique à la Plateforme\n\nPour x64, la sélection de variante utilise :\n\n- **Linux** : `/proc/cpuinfo`\n- **macOS** : `sysctl machdep.cpu.leaf7_features` / `machdep.cpu.features`\n- **Windows** : vérification PowerShell de `System.Runtime.Intrinsics.X86.Avx2`\n\n`PI_NATIVE_VARIANT` peut forcer explicitement `modern` ou `baseline`.\n\n### Modèle de distribution et d'extraction des binaires\n\n`packages/natives/package.json` inclut à la fois `src` et `native` dans les fichiers publiés. Le répertoire `native/` stocke les artefacts précompilés par Plateforme.\n\nPour les binaires compilés (marqueurs d'environnement d'exécution `PI_COMPILED` ou Bun embarqué), le comportement du chargeur est le suivant :\n\n1. Vérifier le chemin de cache utilisateur versionné : `<getNativesDir()>/<packageVersion>/...`\n2. Vérifier l'emplacement hérité des binaires compilés :\n   - Windows : `%LOCALAPPDATA%/xcsh` (repli : `%USERPROFILE%/AppData/Local/xcsh`)\n   - non-Windows : `~/.local/bin`\n3. Se rabattre sur les candidats du répertoire `native/` packagé et du répertoire de l'exécutable\n\nSi un manifeste d'addon embarqué est présent (`embedded-addon.ts` généré par `scripts/embed-native.ts`), `native.ts` peut matérialiser le binaire embarqué correspondant dans le répertoire de cache versionné avant le chargement.\n\n### Validation et modes d'échec\n\nAprès `require(candidate)`, `validateNative(...)` vérifie les exports requis (par exemple `grep`, `glob`, `highlightCode`, `PtySession`, `Shell`, `getSystemInfo`, `getWorkProfile`, `invalidateFsScanCache`).\n\nLes chemins d'échec sont explicites :\n\n- **Tag de Plateforme non pris en charge** : lève une exception avec la liste des Plateformes prises en charge\n- **Aucun candidat chargeable** : lève une exception avec tous les chemins tentés et des indications de remédiation\n- **Exports manquants** : lève une exception avec les noms manquants exacts et la commande de reconstruction\n- **Erreurs d'extraction de l'addon embarqué** : enregistre les échecs de répertoire/écriture et les inclut dans les diagnostics de chargement final\n\n**Contrat garanti (côté API) :** le chargement de l'addon soit réussit avec un ensemble de liaisons validées, soit échoue rapidement avec un message d'erreur actionnable.\n\n**Détail d'implémentation (susceptible de changer) :** l'ordre exact de recherche des candidats et l'ordre des chemins de repli pour les binaires compilés.\n\n## Couche 3 : couche module Rust N-API\n\n`crates/pi-natives/src/lib.rs` est le module d'entrée Rust qui déclare la propriété des modules exportés :\n\n- `clipboard`\n- `fd`\n- `fs_cache`\n- `glob`\n- `glob_util`\n- `grep`\n- `highlight`\n- `html`\n- `image`\n- `keys`\n- `prof`\n- `ps`\n- `pty`\n- `shell`\n- `system_info`\n- `task`\n- `text`\n\nCes modules implémentent les symboles N-API consommés et validés par `native.ts`. Les noms au niveau JS sont exposés via les wrappers TS dans `packages/natives/src`.\n\n**Contrat garanti (côté API) :** les exports du module Rust doivent correspondre aux noms de liaisons attendus par `validateNative` et les modules wrapper.\n\n**Détail d'implémentation (susceptible de changer) :** la décomposition interne des modules Rust et les frontières des modules auxiliaires (`glob_util`, `task`, etc.).\n\n## Délimitation des responsabilités\n\nAu niveau architectural, les responsabilités sont réparties comme suit :\n\n- **Responsabilité du wrapper/API TS (`packages/natives/src`)**\n  - groupement de l'API publique, typage des options et ergonomie JS stable\n  - surface d'annulation (`timeoutMs`, `AbortSignal`) exposée aux appelants\n- **Responsabilité du chargeur (`packages/natives/src/native.ts`)**\n  - sélection du binaire à l'exécution\n  - sélection de la variante CPU et gestion des substitutions\n  - extraction des binaires compilés et sondage des candidats\n  - validation stricte des exports natifs requis\n- **Responsabilité Rust (`crates/pi-natives/src`)**\n  - implémentation algorithmique et au niveau système\n  - comportement natif à la Plateforme et logique sensible aux performances\n  - implémentation des symboles N-API consommés par les wrappers TS\n\n## Flux d'exécution (vue d'ensemble)\n\n1. Le consommateur importe depuis `@f5-sales-demo/pi-natives`.\n2. Le module wrapper appelle la liaison `native` singleton.\n3. `native.ts` sélectionne le binaire candidat pour la Plateforme/architecture/variante.\n4. L'extraction optionnelle du binaire embarqué s'effectue pour les distributions compilées.\n5. L'addon est chargé et l'ensemble des exports est validé.\n6. Le wrapper retourne des résultats typés à l'appelant.\n\n## Glossaire\n\n- **Addon natif** : un binaire `.node` chargé via Node-API (N-API).\n- **Tag de Plateforme** : tuple d'exécution `platform-arch` (par exemple `darwin-arm64`).\n- **Variante** : saveur de compilation spécifique au CPU x64 (`modern` AVX2, `baseline` repli).\n- **Wrapper** : fonction/classe TS qui fournit une API typée au-dessus des exports natifs bruts.\n- **Fusion de déclarations** : technique TS utilisée par les fichiers `types.ts` des modules pour étendre `NativeBindings`.\n- **Mode binaire compilé** : mode d'exécution dans lequel le CLI est regroupé et les addons natifs sont résolus depuis des chemins extraits/de cache plutôt que depuis les seuls chemins locaux au package.\n- **Addon embarqué** : métadonnées d'artefact de build et références de fichiers générées dans `embedded-addon.ts` afin que les binaires compilés puissent extraire les charges utiles `.node` correspondantes.\n- **Porte de validation** : vérification `validateNative(...)` qui rejette les binaires obsolètes ou non concordants dont il manque des exports requis.\n",
	"fr/natives/natives-binding-contract.md": "---\ntitle: Contrat de liaison natif (côté TypeScript)\ndescription: >-\n  Contrat de liaison côté TypeScript pour l'appel des fonctions natives Rust via\n  N-API.\nsidebar:\n  order: 2\n  label: Contrat de liaison\ni18n:\n  sourceHash: 36dc5fed1f0a\n  translator: machine\n---\n\n# Contrat de liaison natif (côté TypeScript)\n\nCe document définit le contrat côté TypeScript qui se situe entre les appelants de `@f5-sales-demo/pi-natives` et l'addon N-API chargé.\n\nIl se concentre sur trois éléments :\n\n1. la forme du contrat (`NativeBindings` + augmentation de module),\n2. le comportement des wrappers (`src/<module>/index.ts`),\n3. la surface d'export publique (`src/index.ts`).\n\n## Fichiers d'implémentation\n\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/system-info/types.ts`\n- `packages/natives/src/system-info/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/work/types.ts`\n- `packages/natives/src/work/index.ts`\n\n## Modèle de contrat\n\n`packages/natives/src/bindings.ts` définit le contrat de base :\n\n- `NativeBindings` (interface de base, inclut actuellement `cancelWork(id: number): void`)\n- `Cancellable` (`timeoutMs?: number`, `signal?: AbortSignal`)\n- `TsFunc<T>` forme de callback utilisée par les callbacks threadsafe N-API\n\nChaque module ajoute ses propres champs par fusion de déclarations :\n\n```ts\n// packages/natives/src/<module>/types.ts\ndeclare module \"../bindings\" {\n interface NativeBindings {\n  grep(options: GrepOptions, onMatch?: TsFunc<GrepMatch>): Promise<GrepResult>;\n }\n}\n```\n\nCela maintient une interface de liaison agrégée unique sans fichier de types central monolithique.\n\n## Cycle de vie de la fusion de déclarations et transitions d'état\n\n### 1) Assemblage des types à la compilation\n\n- `bindings.ts` fournit le symbole `NativeBindings` de base.\n- Chaque `src/<module>/types.ts` augmente `NativeBindings`.\n- `src/native.ts` importe tous les fichiers `./<module>/types` pour leurs effets de bord afin que le contrat fusionné soit dans la portée où `NativeBindings` est utilisé.\n\nTransition d'état : **Contrat de base** → **Contrat fusionné**.\n\n### 2) Chargement de l'addon à l'exécution et porte de validation\n\n- `src/native.ts` charge les binaires `.node` candidats.\n- L'objet chargé est traité comme `NativeBindings` et immédiatement passé à travers `validateNative(...)`.\n- `validateNative` vérifie les clés d'export requises via `typeof bindings[name] === \"function\"`.\n\nTransition d'état : **Objet addon non fiable** → **Objet de liaison natif validé** (ou échec définitif).\n\n### 3) Invocation des wrappers\n\n- Les wrappers de module dans `src/<module>/index.ts` appellent `native.<export>`.\n- Les wrappers adaptent les valeurs par défaut et la forme des callbacks (`(err, value)` vers des patterns de callback à valeur uniquement dans les API JS).\n- `src/index.ts` ré-exporte les wrappers/types de module comme API publique du package.\n\nTransition d'état : **Liaisons brutes validées** → **API publique ergonomique**.\n\n## Responsabilités des wrappers\n\nLes wrappers sont intentionnellement minces ; ils ne ré-implémentent pas la logique native.\n\nResponsabilités principales :\n\n- **Normalisation/valeurs par défaut des arguments**\n  - `glob()` résout `options.path` en chemin absolu et définit les valeurs par défaut pour `hidden`, `gitignore`, `recursive`.\n  - `hasMatch()` remplit les drapeaux par défaut (`ignoreCase`, `multiline`) avant l'appel natif.\n- **Adaptation des callbacks**\n  - `grep()`, `glob()`, `executeShell()` convertissent `TsFunc<T>` (`error, value`) en callback utilisateur recevant uniquement les valeurs réussies.\n- **Comportement d'environnement ou de politique autour des appels natifs**\n  - Le wrapper du presse-papiers ajoute la gestion OSC52/Termux/headless et traite la copie comme un effort au mieux.\n- **Nommage public et curation des ré-exports**\n  - `searchContent()` correspond à l'export natif `search`.\n\n## Organisation de la surface d'export publique\n\n`packages/natives/src/index.ts` est le barrel public canonique. Il regroupe les exports par domaine de capacité :\n\n- Recherche/texte : `grep`, `glob`, `text`, `highlight`\n- Exécution/processus/terminal : `shell`, `pty`, `ps`, `keys`\n- Système/média/conversion : `image`, `html`, `clipboard`, `system-info`, `work`\n\nRègle pour les mainteneurs : si un wrapper n'est pas ré-exporté depuis `src/index.ts`, il ne fait pas partie de la surface publique prévue du package.\n\n## Correspondance API JS ↔ export natif (représentatif)\n\nLe côté Rust utilise des noms d'export N-API (typiquement issus de la conversion `#[napi]` snake_case -> camelCase, avec des alias explicites occasionnels) qui doivent correspondre à ces clés de liaison.\n\n| Catégorie | API JS publique (wrapper) | Clé de liaison native | Type de retour | Async ? |\n|---|---|---|---|---|\n| Grep | `grep(options, onMatch?)` | `grep` | `Promise<GrepResult>` | Oui |\n| Grep | `searchContent(content, options)` | `search` | `SearchResult` | Non |\n| Grep | `hasMatch(content, pattern, opts?)` | `hasMatch` | `boolean` | Non |\n| Grep | `fuzzyFind(options)` | `fuzzyFind` | `Promise<FuzzyFindResult>` | Oui |\n| Glob | `glob(options, onMatch?)` | `glob` | `Promise<GlobResult>` | Oui |\n| Glob | `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `void` | Non |\n| Shell | `executeShell(options, onChunk?)` | `executeShell` | `Promise<ShellExecuteResult>` | Oui |\n| Shell | `Shell` | `Shell` | constructeur de classe | N/A |\n| PTY | `PtySession` | `PtySession` | constructeur de classe | N/A |\n| Text | `truncateToWidth(...)` | `truncateToWidth` | `string` | Non |\n| Text | `sliceWithWidth(...)` | `sliceWithWidth` | `SliceWithWidthResult` | Non |\n| Text | `visibleWidth(text)` | `visibleWidth` | `number` | Non |\n| Highlight | `highlightCode(code, lang, colors)` | `highlightCode` | `string` | Non |\n| HTML | `htmlToMarkdown(html, options?)` | `htmlToMarkdown` | `Promise<string>` | Oui |\n| System | `getSystemInfo()` | `getSystemInfo` | `SystemInfo` | Non |\n| Work | `getWorkProfile(lastSeconds)` | `getWorkProfile` | `WorkProfile` | Non |\n| Process | `killTree(pid, signal)` | `killTree` | `number` | Non |\n| Process | `listDescendants(pid)` | `listDescendants` | `number[]` | Non |\n| Clipboard | `copyToClipboard(text)` | `copyToClipboard` | `Promise<void>` (comportement wrapper au mieux) | Oui |\n| Clipboard | `readImageFromClipboard()` | `readImageFromClipboard` | `Promise<ClipboardImage \\| null>` | Oui |\n| Keys | `parseKey(data, kittyProtocolActive)` | `parseKey` | `string \\| null` | Non |\n\n## Différences de contrat synchrone vs asynchrone\n\nLe contrat mélange des API synchrones et asynchrones ; les wrappers préservent le style d'appel natif plutôt que de forcer un modèle unique :\n\n- **Exports asynchrones basés sur les Promise** pour les E/S ou les travaux de longue durée (`grep`, `glob`, `htmlToMarkdown`, `executeShell`, presse-papiers, opérations sur les images).\n- **Exports synchrones** pour les transformations/parseurs déterministes en mémoire (`search`, `hasMatch`, coloration syntaxique, largeur/découpage de texte, analyse de touches, requêtes de processus).\n- **Exports de constructeurs** pour les objets runtime à état (`Shell`, `PtySession`, `PhotonImage`).\n\nImplication pour les mainteneurs : changer synchrone ↔ asynchrone pour un export existant constitue un changement d'API et de contrat cassant à travers les wrappers et les appelants.\n\n## Patterns de typage pour les objets et les enums\n\n### Patterns d'objets (objets JS style `#[napi(object)]`)\n\nTS modélise les valeurs natives de forme objet comme des interfaces, par exemple :\n\n- `GrepResult`, `SearchResult`, `GlobResult`\n- `SystemInfo`, `WorkProfile`\n- `ClipboardImage`, `ParsedKittyResult`\n\nCe sont des contrats structurels à la compilation ; la correction de la forme à l'exécution est de la responsabilité de l'implémentation native.\n\n### Patterns d'enums\n\nLes enums natifs numériques sont représentés comme des valeurs `const enum` en TS :\n\n- `FileType` (`1=file`, `2=dir`, `3=symlink`)\n- `ImageFormat` (`0=PNG`, `1=JPEG`, `2=WEBP`, `3=GIF`)\n- `SamplingFilter`, `Ellipsis`, `KeyEventType`\n\nLes appelants voient les membres nommés de l'enum ; la frontière de liaison transmet des nombres.\n\n## Comment les incohérences sont détectées\n\nLa détection des incohérences se fait à deux niveaux :\n\n1. **Vérifications du contrat TypeScript à la compilation**\n   - Les wrappers appellent `native.<name>` contre `NativeBindings` fusionné.\n   - Les clés de liaison manquantes/renommées cassent la vérification de types TS dans les wrappers.\n\n2. **Validation à l'exécution dans `validateNative`**\n   - Après le chargement, `native.ts` vérifie les exports requis et lève une exception si l'un d'eux est manquant.\n   - Le message d'erreur inclut les clés manquantes et les instructions de reconstruction.\n\nCela détecte la dérive courante de binaire obsolète : le wrapper/type existe mais le `.node` chargé ne possède pas l'export.\n\n## Comportement en cas d'échec et avertissements\n\n### Échecs de chargement/validation (échecs définitifs)\n\n- L'échec de chargement de l'addon ou une plateforme non supportée lève une exception lors de l'initialisation du module dans `native.ts`.\n- Les exports requis manquants lèvent une exception avant que les wrappers soient utilisables.\n\nEffet : le package échoue rapidement plutôt que de différer l'échec au premier appel.\n\n### Différences de comportement au niveau des wrappers\n\n- Certains wrappers adoucissent intentionnellement les échecs (`copyToClipboard` fonctionne au mieux et absorbe les échecs natifs).\n- Les callbacks de streaming ignorent les charges d'erreur des callbacks et ne transmettent que les événements de valeurs réussies.\n\n### Avertissements au niveau des types (l'exécution est plus stricte que TS)\n\n- Les champs optionnels TS ne garantissent pas la validité sémantique ; la couche native peut toujours rejeter des valeurs malformées.\n- Le typage `const enum` n'empêche pas les valeurs numériques hors limites provenant d'appelants non typés à l'exécution.\n- `validateNative` vérifie uniquement la présence/nature de fonction des exports requis, pas la compatibilité profonde des formes d'arguments/retours.\n- `bindings.ts` inclut `cancelWork(id)` dans l'interface de base, mais la liste de validation à l'exécution actuelle n'impose pas cette clé.\n\n## Liste de contrôle pour les mainteneurs lors de changements de liaison\n\nLors de l'ajout/modification d'un export, mettez à jour tous les éléments suivants :\n\n1. `src/<module>/types.ts` (augmentation + types de contrat)\n2. `src/<module>/index.ts` (comportement du wrapper)\n3. Imports de `src/native.ts` pour les types du module (si nouveau module)\n4. Vérifications des exports requis dans `validateNative`\n5. Ré-exports publics dans `src/index.ts`\n\nSauter l'une de ces étapes crée soit une dérive à la compilation, soit un échec à l'exécution au moment du chargement.\n",
	"fr/natives/natives-build-release-debugging.md": "---\ntitle: 'Manuel d''exploitation — Compilation, publication et débogage des natifs'\ndescription: >-\n  Manuel de compilation, publication et débogage pour le module natif Rust sur\n  toutes les plateformes.\nsidebar:\n  order: 8\n  label: 'Compilation, publication et débogage'\ni18n:\n  sourceHash: efe47aa5b466\n  translator: machine\n---\n\n# Manuel d'exploitation — Compilation, publication et débogage des natifs\n\nCe manuel décrit comment le pipeline de compilation de `@f5-sales-demo/pi-natives` produit des modules `.node`, comment les distributions compilées les chargent, et comment déboguer les échecs de chargeur ou de compilation.\n\nIl suit les termes architecturaux définis dans `docs/natives-architecture.md` :\n\n- **production d'artefacts à la compilation** (`scripts/build-native.ts`)\n- **génération du manifeste de module embarqué** (`scripts/embed-native.ts`)\n- **chargement du module au moment de l'exécution + validation** (`src/native.ts`)\n\n## Fichiers d'implémentation\n\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `packages/natives/src/native.ts`\n- `crates/pi-natives/Cargo.toml`\n\n## Vue d'ensemble du pipeline de compilation\n\n### 1) Points d'entrée de la compilation\n\nScripts de `packages/natives/package.json` :\n\n- `bun scripts/build-native.ts` (`build`) → compilation en mode release\n- `bun scripts/build-native.ts --dev` (`dev:native`) → compilation en profil debug/dev (même nommage de sortie)\n- `bun scripts/embed-native.ts` (`embed:native`) → génération de `src/embedded-addon.ts` à partir des fichiers compilés\n\n### 2) Compilation de l'artefact Rust\n\n`build-native.ts` exécute Cargo dans `crates/pi-natives` :\n\n- commande de base : `cargo build`\n- le mode release ajoute `--release` sauf si `--dev` est passé\n- la cible croisée ajoute `--target <CROSS_TARGET>`\n\n`crates/pi-natives/Cargo.toml` déclare `crate-type = [\"cdylib\"]`, ce qui pousse Cargo à émettre une bibliothèque partagée (`.so`/`.dylib`/`.dll`) qui est ensuite copiée et renommée en un nom de fichier de module `.node`.\n\n### 3) Découverte et installation des artefacts\n\nAprès l'achèvement de Cargo, `build-native.ts` analyse les répertoires de sortie candidats dans cet ordre :\n\n1. `${CARGO_TARGET_DIR}` (si défini)\n2. `<repo>/target`\n3. `crates/pi-natives/target`\n\nPour chaque racine, il vérifie les répertoires de profil :\n\n- compilation croisée : `<root>/<crossTarget>/<profile>` puis `<root>/<profile>`\n- compilation native : `<root>/<profile>`\n\nEnsuite, il recherche l'un des fichiers suivants :\n\n- `libpi_natives.so`\n- `libpi_natives.dylib`\n- `pi_natives.dll`\n- `libpi_natives.dll`\n\nUne fois trouvé, il est installé de manière atomique dans `packages/natives/native/` avec des sémantiques de fichier temporaire + renommage (le repli Windows gère explicitement les échecs de remplacement de DLL verrouillée).\n\n## Modèle de cible/variante et conventions de nommage\n\n## Tag de plateforme\n\nLa compilation et l'exécution utilisent toutes deux un tag de plateforme :\n\n`<platform>-<arch>` (exemples : `darwin-arm64`, `linux-x64`)\n\n## Modèle de variante (x64 uniquement)\n\nx64 prend en charge les variantes CPU :\n\n- `modern` (chemin compatible AVX2)\n- `baseline` (repli)\n\nLes architectures non-x64 utilisent un seul artefact par défaut (sans suffixe de variante).\n\n### Noms de fichiers de sortie\n\nCompilations release :\n\n- x64 : `pi_natives.<platform>-<arch>-modern.node` ou `...-baseline.node`\n- non-x64 : `pi_natives.<platform>-<arch>.node`\n\nCompilation dev (`--dev`) :\n\n- Utilise les indicateurs de profil debug mais conserve le nommage de sortie standard avec tag de plateforme\n\nOrdre des candidats du chargeur au moment de l'exécution dans `native.ts` :\n\n- candidats release\n- le mode compilé fait précéder les candidats extraits/mis en cache avant les fichiers locaux au paquet\n\n## Indicateurs d'environnement et options de compilation\n\n## Indicateurs d'exécution\n\n- `PI_DEV` (comportement du chargeur) : activer les diagnostics du chargeur\n- `PI_NATIVE_VARIANT` (comportement du chargeur, x64 uniquement) : forcer la sélection de `modern` ou `baseline` au moment de l'exécution\n- `PI_COMPILED` (comportement du chargeur) : activer le comportement candidat/extraction pour les binaires compilés\n\n## Indicateurs/options de compilation\n\n- `--dev` (argument du script) : compiler le profil debug\n- `CROSS_TARGET` : passé à Cargo `--target`\n- `TARGET_PLATFORM` : remplacer le nommage du tag de plateforme en sortie\n- `TARGET_ARCH` : remplacer le nommage de l'architecture en sortie\n- `TARGET_VARIANT` (x64 uniquement) : forcer `modern` ou `baseline` pour le nom de fichier de sortie et la politique RUSTFLAGS\n- `CARGO_TARGET_DIR` : racine supplémentaire lors de la recherche des sorties Cargo\n- `RUSTFLAGS` :\n  - si non défini et sans compilation croisée, le script définit :\n    - modern : `-C target-cpu=x86-64-v3`\n    - baseline : `-C target-cpu=x86-64-v2`\n    - non-x64 / sans variante : `-C target-cpu=native`\n  - si déjà défini, le script ne remplace pas la valeur\n\n## Transitions d'état/cycle de vie de la compilation\n\n### Cycle de vie de la compilation (`build-native.ts`)\n\n1. **Initialisation** : analyse des arguments/variables d'environnement (`--dev`, remplacements de cible, indicateurs croisés)\n2. **Résolution de la variante** :\n   - non-x64 → pas de variante\n   - x64 + `TARGET_VARIANT` → variante explicite\n   - compilation croisée x64 sans `TARGET_VARIANT` → erreur bloquante\n   - compilation locale x64 sans remplacement → détection AVX2 sur l'hôte\n3. **Compilation** : exécution de Cargo avec le profil/la cible résolus\n4. **Localisation de l'artefact** : analyse des racines cibles, des répertoires de profil et des noms de bibliothèque\n5. **Installation** : copie + renommage atomique dans `packages/natives/native`\n6. **Achèvement** : module prêt pour les candidats du chargeur\n\nDes échecs avec sortie se produisent à n'importe quelle étape avec un message d'erreur explicite (variante invalide, échec de la compilation Cargo, bibliothèque de sortie manquante, échec d'installation/renommage).\n\n### Cycle de vie d'intégration (`embed-native.ts`)\n\n1. **Initialisation** : calcul du tag de plateforme à partir de `TARGET_PLATFORM`/`TARGET_ARCH` ou des valeurs de l'hôte\n2. **Ensemble de candidats** :\n   - x64 attend à la fois `modern` et `baseline`\n   - non-x64 attend un seul fichier par défaut\n3. **Validation de la disponibilité** dans `packages/natives/native`\n4. **Génération du manifeste** (`src/embedded-addon.ts`) avec les imports de `file` Bun et la version du paquet\n5. **Extraction au moment de l'exécution prête** pour le mode compilé\n\n`--reset` contourne la validation et écrit un stub de manifeste null (`embeddedAddon = null`).\n\n## Flux de travail de développement local vs comportement en production/compilé\n\n## Flux de travail de développement local\n\nBoucle locale typique :\n\n1. Compiler le module :\n   - release : `bun --cwd=packages/natives run build`\n   - profil debug : `bun --cwd=packages/natives run dev:native`\n2. Définir `PI_DEV=1` lors du test des diagnostics du chargeur\n3. Le chargeur dans `native.ts` résout les candidats locaux au paquet `native/` (et le repli par répertoire d'exécutable)\n4. `validateNative` applique la compatibilité des exports avant que les enveloppes n'utilisent le binding\n\n## Flux de travail binaire en production/compilé\n\nEn mode compilé (`PI_COMPILED` ou marqueurs embarqués Bun) :\n\n1. Le chargeur calcule le répertoire de cache versionné : `<getNativesDir()>/<packageVersion>` (en pratique `~/.xcsh/natives/<version>`)\n2. Si le manifeste embarqué correspond à la plateforme+version actuelle, le chargeur peut extraire le fichier embarqué sélectionné dans ce répertoire versionné\n3. L'ordre des candidats au moment de l'exécution comprend :\n   - le répertoire de cache versionné\n   - le répertoire du binaire compilé hérité (`%LOCALAPPDATA%/xcsh` sous Windows, `~/.local/bin` ailleurs)\n   - les répertoires du paquet/exécutable\n4. Le premier module chargé avec succès doit toujours passer `validateNative`\n\nC'est pourquoi le packaging et les attentes du chargeur au moment de l'exécution doivent être alignés : les noms de fichiers, les tags de plateforme et les symboles exportés doivent correspondre à ce que `native.ts` sonde et valide.\n\n## Correspondance API JS ↔ export Rust (sous-ensemble de la validation)\n\n`native.ts` exige que ces exports visibles en JS existent sur le module chargé. Ils correspondent aux exports Rust N-API dans `crates/pi-natives/src` :\n\n| Nom JS requis par `validateNative` | Déclaration d'export Rust | Fichier source Rust |\n| --- | --- | --- |\n| `glob` | `#[napi] pub fn glob(...)` | `crates/pi-natives/src/glob.rs` |\n| `grep` | `#[napi] pub fn grep(...)` | `crates/pi-natives/src/grep.rs` |\n| `search` | `#[napi] pub fn search(...)` | `crates/pi-natives/src/grep.rs` |\n| `highlightCode` | `#[napi] pub fn highlight_code(...)` | `crates/pi-natives/src/highlight.rs` |\n| `getSystemInfo` | `#[napi] pub fn get_system_info(...)` | `crates/pi-natives/src/system_info.rs` |\n| `getWorkProfile` | `#[napi] pub fn get_work_profile(...)` (export en camelCase) | `crates/pi-natives/src/prof.rs` |\n| `invalidateFsScanCache` | `#[napi] pub fn invalidate_fs_scan_cache(...)` | `crates/pi-natives/src/fs_cache.rs` |\n\nSi un symbole requis est manquant, le chargeur échoue rapidement avec une indication de recompilation.\n\n## Comportement en cas d'échec et diagnostics\n\n## Échecs à la compilation\n\n- Configuration de variante invalide :\n  - `TARGET_VARIANT` défini sur non-x64 → erreur immédiate\n  - compilation croisée x64 sans `TARGET_VARIANT` explicite → erreur immédiate\n- Échec de la compilation Cargo :\n  - le script signale le code de sortie non nul et la sortie d'erreur standard\n- Artefact non trouvé :\n  - le script affiche chaque répertoire de profil vérifié\n- Échec d'installation :\n  - message explicite ; Windows inclut une indication de fichier verrouillé\n\n## Échecs du chargeur au moment de l'exécution (`native.ts`)\n\n- Tag de plateforme non pris en charge :\n  - lève une exception avec la liste des plateformes prises en charge\n- Aucun candidat n'a pu être chargé :\n  - lève une exception avec la liste complète des erreurs des candidats et des indications de remédiation spécifiques au mode\n- Exports manquants :\n  - lève une exception avec les noms exacts des symboles manquants et la commande de recompilation\n- Problèmes d'extraction embarquée :\n  - les erreurs de création de répertoire/écriture lors de l'extraction sont enregistrées et incluses dans les diagnostics finaux\n\n## Matrice de dépannage\n\n| Symptôme | Cause probable | Vérification | Correction |\n| --- | --- | --- | --- |\n| `Native addon missing exports ... Missing: <name>` | Binaire `.node` obsolète, incompatibilité de nom d'export Rust, ou chargement du mauvais binaire | Exécuter avec `PI_DEV=1` pour voir le chemin chargé ; inspecter la liste des exports de ce fichier | Recompiler avec `build` ; vérifier que le nom de l'export Rust `#[napi]` (ou l'alias explicite si nécessaire) correspond à la clé JS ; supprimer les fichiers en cache/versionnés obsolètes |\n| Une machine x64 charge baseline alors que modern est attendu | `PI_NATIVE_VARIANT=baseline`, AVX2 non détecté, ou seul le fichier baseline est présent | Vérifier `PI_NATIVE_VARIANT` ; inspecter `native/` pour le fichier `-modern` | Compiler la variante modern (`TARGET_VARIANT=modern ... build`) et s'assurer que le fichier est distribué |\n| La compilation croisée produit un binaire inutilisable ou mal étiqueté | Incompatibilité entre `CROSS_TARGET` et `TARGET_PLATFORM`/`TARGET_ARCH`, ou `TARGET_VARIANT` manquant pour x64 | Confirmer le tuple d'environnement et le nom du fichier de sortie | Relancer avec des valeurs d'environnement cohérentes et un `TARGET_VARIANT` x64 explicite |\n| Le binaire compilé échoue après une mise à niveau | Cache extrait obsolète (`~/.xcsh/natives/<ancienne-version-ou-version-incompatible>`) ou incompatibilité du manifeste embarqué | Inspecter le répertoire des natifs versionné et la liste des erreurs du chargeur | Supprimer le cache des natifs versionné pour la version du paquet concernée et relancer ; regénérer le manifeste embarqué lors du packaging |\n| Le chargeur sonde de nombreux chemins sans succès | Incompatibilité de plateforme ou artefact release manquant dans `native/` du paquet | Vérifier `platformTag` par rapport au(x) nom(s) de fichier(s) réel(s) | S'assurer que le nom du fichier compilé correspond exactement à la convention `pi_natives.<platform>-<arch>(-variant).node` et que le paquet inclut `native/` |\n| `embed:native` échoue avec « Incomplete native addons » | Les fichiers de variante requis n'ont pas été compilés avant l'intégration | Vérifier la liste des fichiers attendus vs trouvés dans le texte d'erreur | Compiler d'abord les fichiers requis (x64 : à la fois modern+baseline ; non-x64 : défaut), puis relancer `embed:native` |\n\n## Commandes opérationnelles\n\n```bash\n# Artefact release pour l'hôte actuel\nbun --cwd=packages/natives run build\n\n# Compilation d'artefact en profil debug\nbun --cwd=packages/natives run dev:native\n\n# Compilation des variantes x64 explicites\nTARGET_VARIANT=modern bun --cwd=packages/natives run build\nTARGET_VARIANT=baseline bun --cwd=packages/natives run build\n\n# Génération du manifeste de module embarqué à partir des fichiers natifs compilés\nbun --cwd=packages/natives run embed:native\n\n# Réinitialisation du manifeste embarqué en stub null\nbun --cwd=packages/natives run embed:native -- --reset\n```\n",
	"fr/natives/natives-media-system-utils.md": "---\ntitle: Utilitaires natifs pour les médias et le système\ndescription: >-\n  Utilitaires natifs de traitement multimédia pour les captures d'écran, la\n  gestion d'images et les informations système.\nsidebar:\n  order: 7\n  label: Utilitaires médias & système\ni18n:\n  sourceHash: 430898c177bc\n  translator: machine\n---\n\n# Utilitaires natifs médias + système\n\nCe document est une analyse approfondie du sous-système pour la couche de **primitives système/médias/conversion** décrite dans [`docs/natives-architecture.md`](./natives-architecture.md) : `image`, `html`, `clipboard` et le profilage `work`.\n\n## Fichiers d'implémentation\n\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/prof.rs`\n- `crates/pi-natives/src/task.rs`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/work/index.ts`\n- `packages/natives/src/work/types.ts`\n\n> Note : il n'y a pas de `crates/pi-natives/src/work.rs` ; le profilage work est implémenté dans `prof.rs` et alimenté par l'instrumentation dans `task.rs`.\n\n## Correspondance API TS ↔ export/module Rust\n\n| Export TS (packages/natives)                | Export N-API Rust                                                       | Module Rust                           |\n| ------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------- |\n| `PhotonImage.parse(bytes)`                  | `PhotonImage::parse`                                                     | `image.rs`                            |\n| `PhotonImage#resize(width, height, filter)` | `PhotonImage::resize`                                                    | `image.rs`                            |\n| `PhotonImage#encode(format, quality)`       | `PhotonImage::encode`                                                    | `image.rs`                            |\n| `htmlToMarkdown(html, options)`             | `html_to_markdown`                                                       | `html.rs`                             |\n| `copyToClipboard(text)`                     | `copy_to_clipboard` + logique de repli TS                                | `clipboard.rs` + `clipboard/index.ts` |\n| `readImageFromClipboard()`                  | `read_image_from_clipboard`                                              | `clipboard.rs`                        |\n| `getWorkProfile(lastSeconds)`               | `get_work_profile`                                                      | `prof.rs`                             |\n\n## Frontières de format de données et conversions\n\n### Image (`image`)\n\n- **Frontière d'entrée JS** : octets d'image encodés en `Uint8Array`.\n- **Frontière de décodage Rust** : les octets sont copiés dans un `Vec<u8>`, le format est deviné avec `ImageReader::with_guessed_format()`, puis décodé en `DynamicImage`.\n- **État en mémoire** : `PhotonImage` stocke un `Arc<DynamicImage>`.\n- **Frontière de sortie** : `encode(format, quality)` retourne `Promise<Uint8Array>` (`Vec<u8>` côté Rust).\n\nLes identifiants de format sont numériques :\n\n- `0` : PNG\n- `1` : JPEG\n- `2` : WebP (encodeur sans perte)\n- `3` : GIF\n\nContraintes :\n\n- `quality` n'est utilisé que pour JPEG.\n- PNG/WebP/GIF ignorent `quality`.\n- Les identifiants de format non supportés échouent (`Invalid image format: <id>`).\n\n### Conversion HTML (`html`)\n\n- **Frontière d'entrée JS** : `string` HTML + objet optionnel `{ cleanContent?: boolean; skipImages?: boolean }`.\n- **Frontière de conversion Rust** : l'entrée `String` est convertie par `html_to_markdown_rs::convert`.\n- **Frontière de sortie** : `string` Markdown.\n\nComportement de conversion :\n\n- `cleanContent` vaut `false` par défaut.\n- Lorsque `cleanContent=true`, le prétraitement est activé avec `PreprocessingPreset::Aggressive` et des indicateurs de suppression stricte pour la navigation/les formulaires.\n- `skipImages` vaut `false` par défaut.\n\n### Presse-papiers (`clipboard`)\n\n- **Chemin texte** :\n  - TS émet d'abord OSC 52 (`\\x1b]52;c;<base64>\\x07`) lorsque stdout est un TTY.\n  - Le même texte est ensuite tenté via l'API native du presse-papiers (`native.copyToClipboard`) en mode « meilleur effort ».\n  - Sur Termux, TS tente d'abord `termux-clipboard-set`.\n- **Chemin de lecture d'image** :\n  - Rust lit l'image brute depuis `arboard`.\n  - Rust la ré-encode en octets PNG (crate `image`), retourne `{ data: Uint8Array, mimeType: \"image/png\" }`.\n  - TS retourne `null` immédiatement sur Termux ou les sessions Linux sans serveur d'affichage (`DISPLAY`/`WAYLAND_DISPLAY` absents).\n\n### Profilage work (`work`)\n\n- **Frontière de collecte** : les échantillons de profilage sont produits par les gardes `profile_region(tag)` dans `task::blocking` et `task::future`.\n- **Format de stockage** : tampon circulaire de taille fixe (`MAX_SAMPLES = 10_000`) stockant le chemin de pile + la durée (`μs`) + l'horodatage (`μs depuis le démarrage du processus`).\n- **Frontière de sortie** : `getWorkProfile(lastSeconds)` retourne un objet :\n  - `folded` : texte de pile repliée (entrée pour flamegraph)\n  - `summary` : tableau résumé en markdown\n  - `svg` : SVG flamegraph optionnel\n  - `totalMs`, `sampleCount`\n\n## Cycle de vie et transitions d'état\n\n### Cycle de vie de l'image\n\n1. `PhotonImage.parse(bytes)` planifie une tâche bloquante de décodage (`image.decode`).\n2. En cas de succès, un handle natif `PhotonImage` existe côté JS.\n3. `resize(...)` crée un nouveau handle natif (`image.resize`), l'ancien et le nouveau handle peuvent coexister.\n4. `encode(...)` matérialise les octets (`image.encode`) sans modifier les dimensions de l'image.\n\nTransitions d'échec :\n\n- L'échec de détection de format/décodage rejette la promesse de parse.\n- L'échec d'encodage rejette la promesse d'encode.\n- Un identifiant de format invalide rejette la promesse d'encode.\n\n### Cycle de vie HTML\n\n1. `htmlToMarkdown(html, options)` planifie une tâche bloquante de conversion.\n2. La conversion s'exécute avec les options par défaut (`cleanContent=false`, `skipImages=false`) sauf spécification contraire.\n3. Retourne une chaîne markdown ou rejette.\n\nTransitions d'échec :\n\n- L'échec du convertisseur retourne une promesse rejetée (`Conversion error: ...`).\n\n### Cycle de vie du presse-papiers\n\n`copyToClipboard(text)` est intentionnellement en mode « meilleur effort » et multi-chemin :\n\n1. Si TTY : tentative d'écriture OSC 52 (payload base64).\n2. Tentative de la commande Termux lorsque `TERMUX_VERSION` est défini.\n3. Tentative de copie texte native via `arboard`.\n4. Les erreurs sont absorbées au niveau de la couche TS.\n\n`readImageFromClipboard()` diffère en rigueur selon l'étape :\n\n1. TS bloque strictement les contextes d'exécution non supportés (Termux/Linux sans interface graphique) en retournant `null`.\n2. La lecture Rust `arboard` ne s'exécute que lorsque TS l'autorise.\n3. `ContentNotAvailable` est mappé vers `null`.\n4. Les autres erreurs Rust rejettent.\n\n### Cycle de vie du profilage work\n\n1. Pas de démarrage explicite : le profilage est toujours actif lorsque les helpers de tâches s'exécutent.\n2. Chaque portée de tâche instrumentée enregistre un échantillon lors de la destruction du garde.\n3. Les échantillons écrasent les entrées les plus anciennes une fois la capacité du tampon atteinte.\n4. `getWorkProfile(lastSeconds)` lit une fenêtre temporelle et dérive les artefacts folded/summary/svg.\n\nTransitions d'échec :\n\n- L'échec de génération SVG est un échec souple (`svg: null`), tandis que folded et summary sont toujours retournés.\n- Une fenêtre d'échantillons vide retourne des données folded vides et `svg: null`, ce n'est pas une erreur.\n\n## Opérations non supportées et propagation des erreurs\n\n### Image\n\n- Entrée de décodage non supportée ou octets corrompus : échec strict (rejet de la promesse).\n- Identifiant de format d'encodage non supporté : échec strict.\n- Pas de chemin de repli « meilleur effort » dans le wrapper TS.\n\n### HTML\n\n- Les erreurs de conversion sont des échecs stricts (rejet).\n- L'omission d'options utilise les valeurs par défaut en mode « meilleur effort », ce n'est pas un échec.\n\n### Presse-papiers\n\n- La copie de texte est en mode « meilleur effort » au niveau de la couche TS : les échecs opérationnels sont supprimés.\n- La lecture d'image distingue « pas d'image » (`null`) d'un échec opérationnel (rejet).\n- Termux/Linux sans interface graphique sont traités comme des contextes non supportés pour la lecture d'image (`null`).\n\n### Profilage work\n\n- La récupération est stricte pour l'appel de fonction lui-même, mais la génération d'artefacts est partiellement en mode « meilleur effort » (`svg` nullable).\n- La troncature du tampon est un comportement attendu (tampon circulaire), pas un bug de perte de données.\n\n## Particularités par plateforme\n\n- **Texte du presse-papiers** : OSC 52 dépend du support du terminal ; l'accès natif au presse-papiers dépend de l'environnement de bureau/session.\n- **Lecture d'image du presse-papiers** : bloquée côté TS pour Termux et Linux sans serveur d'affichage.\n",
	"fr/natives/natives-rust-task-cancellation.md": "---\ntitle: Exécution de tâches Rust native et annulation\ndescription: >-\n  Modèle d'exécution de tâches asynchrones Rust avec annulation coopérative et\n  sémantiques de nettoyage.\nsidebar:\n  order: 5\n  label: Annulation de tâche\ni18n:\n  sourceHash: 0fbf45c6d463\n  translator: machine\n---\n\n# Exécution de tâches Rust native et annulation (`pi-natives`)\n\nCe document décrit la manière dont `crates/pi-natives` planifie le travail natif et la façon dont l'annulation se propage depuis les options JS (`timeoutMs`, `AbortSignal`) jusqu'à l'exécution Rust.\n\n## Fichiers d'implémentation\n\n- `crates/pi-natives/src/task.rs`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/fd.rs`\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/ps.rs`\n\n## Primitives fondamentales (`task.rs`)\n\n`task.rs` définit trois éléments fondamentaux :\n\n1. `task::blocking(tag, cancel_token, work)`\n   - Encapsule `napi::AsyncTask` / `Task`.\n   - `compute()` s'exécute sur les threads de travail libuv (pour les appels système liés au CPU ou bloquants/synchrones).\n   - Retourne une `Promise<T>` JS.\n\n2. `task::future(env, tag, work)`\n   - Encapsule `env.spawn_future(...)`.\n   - Exécute le travail asynchrone sur le runtime Tokio.\n   - Retourne `PromiseRaw<'env, T>`.\n\n3. `CancelToken` / `AbortToken` / `AbortReason`\n   - `CancelToken::new(timeout_ms, signal)` combine une échéance et un `AbortSignal` optionnel.\n   - `CancelToken::heartbeat()` est l'annulation coopérative pour les boucles bloquantes.\n   - `CancelToken::wait()` est l'attente d'annulation asynchrone (`Signal` / `Timeout` / `User` Ctrl-C).\n   - `AbortToken` permet au code externe de demander une annulation (`abort(reason)`).\n\n## `blocking` vs `future` : modèle d'exécution et sélection\n\n### Utiliser `task::blocking`\n\nÀ utiliser lorsque le travail est intensif en CPU ou fondamentalement synchrone/bloquant :\n\n- analyse regex/fichiers (`grep`, `glob`, `fuzzy_find`)\n- parties internes de la boucle PTY synchrone (`run_pty_sync` via `spawn_blocking`)\n- conversions presse-papiers/image/html\n\nComportement :\n\n- La fermeture de travail reçoit un `CancelToken` cloné.\n- L'annulation n'est observée qu'aux endroits où le code vérifie `ct.heartbeat()?`.\n- Une fermeture `Err(...)` rejette la promesse JS.\n\n### Utiliser `task::future`\n\nÀ utiliser lorsque le travail doit `await` des opérations asynchrones :\n\n- orchestration de sessions shell (`shell.run`, `executeShell`)\n- course de tâches (`tokio::select!`) entre la complétion et l'annulation\n\nComportement :\n\n- Le future peut mettre en compétition la complétion normale contre `ct.wait()`.\n- Sur le chemin d'annulation, les implémentations asynchrones propagent généralement l'annulation vers les sous-systèmes internes (ex. : `tokio_util::CancellationToken`) et forcent éventuellement l'abandon à l'expiration du délai de grâce.\n\n## Correspondance API JS ↔ export Rust (pertinente pour les tâches/annulations)\n\n| API côté JS | Export Rust (`#[napi]`) | Planificateur | Branchement d'annulation |\n|---|---|---|---|\n| `grep(options, onMatch?)` | `grep` | `task::blocking(\"grep\", ct, ...)` | `CancelToken::new(options.timeoutMs, options.signal)` + `ct.heartbeat()` |\n| `glob(options, onMatch?)` | `glob` | `task::blocking(\"glob\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` dans la boucle de filtre |\n| `fuzzyFind(options)` | `fuzzy_find` | `task::blocking(\"fuzzy_find\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` dans la boucle de notation |\n| `shell.run(options, onChunk?)` | `Shell::run` | `task::future(env, \"shell.run\", ...)` | `ct.wait()` en compétition avec la tâche d'exécution ; pont vers le `CancellationToken` Tokio |\n| `executeShell(options, onChunk?)` | `execute_shell` | `task::future(env, \"shell.execute\", ...)` | identique à ci-dessus |\n| `pty.start(options, onChunk?)` | `PtySession::start` | `task::future(env, \"pty.start\", ...)` + `spawn_blocking` interne | `CancelToken` vérifié dans la boucle PTY synchrone via `heartbeat()` |\n| `htmlToMarkdown(html, options?)` | `html_to_markdown` | `task::blocking(\"html_to_markdown\", (), ...)` | aucun (jeton `()`) |\n| `PhotonImage.parse/encode/resize` | `PhotonImage::{parse,encode,resize}` | `task::blocking(...)` | aucun (jeton `()`) |\n| `copyToClipboard/readImageFromClipboard` | `copy_to_clipboard` / `read_image_from_clipboard` | `task::blocking(...)` | aucun (jeton `()`) |\n\n`text.rs` et `ps.rs` n'utilisent actuellement pas `task::blocking`/`task::future` et ne participent donc pas à ce chemin d'annulation.\n\n## Cycle de vie de l'annulation et transitions d'état\n\n### Cycle de vie de `CancelToken`\n\n`CancelToken` est coopératif et à état :\n\n```text\nCréé\n  ├─ pas de signal + pas de délai d'expiration  -> jeton passif (n'annule jamais sauf placement externe)\n  ├─ signal enregistré                           -> attend le rappel AbortSignal\n  └─ échéance définie                            -> la vérification du délai d'expiration devient active\n\nEn cours\n  ├─ heartbeat()/wait() détecte le signal   -> AbortReason::Signal\n  ├─ heartbeat()/wait() détecte l'échéance  -> AbortReason::Timeout\n  ├─ wait() détecte Ctrl-C                  -> AbortReason::User\n  └─ pas d'annulation                       -> continuer\n\nAnnulé (terminal)\n  └─ la première raison d'annulation l'emporte (drapeau atomique + notificateur)\n```\n\n### Annulation avant démarrage vs en cours d'exécution\n\n- **Avant le démarrage / avant la première vérification d'annulation** :\n  - Les utilisateurs de `task::future` qui font une course sur `ct.wait()` peuvent résoudre l'annulation immédiatement dès qu'ils entrent dans `select!`.\n  - Les utilisateurs de `task::blocking` n'observent l'annulation que lorsque le code de la fermeture atteint `heartbeat()`. Si la fermeture n'effectue pas de heartbeat tôt, l'annulation est retardée.\n\n- **En cours d'exécution** :\n  - `blocking` : le prochain `heartbeat()` retourne `Err(\"Aborted: ...\")`.\n  - `future` : la branche `ct.wait()` remporte le `select!`, puis le code annule la machinerie asynchrone subordonnée (pour shell : annule le jeton Tokio, attend jusqu'à 2s, puis abandonne la tâche).\n\n## Exigences de heartbeat pour les boucles longues\n\n`heartbeat()` doit s'exécuter à une cadence prévisible dans les boucles avec des ensembles de travail illimités ou importants.\n\nPatterns observés :\n\n- `glob::filter_entries` : vérification de chaque entrée avant filtrage/correspondance.\n- `fd::score_entries` : vérification de chaque candidat analysé.\n- `grep_sync` : vérification d'annulation explicite avant la phase de recherche intensive, ainsi que les appels au cache fs qui reçoivent également le jeton.\n- `run_pty_sync` : vérification à chaque tick de boucle (cadence de sommeil ~16ms) et destruction du processus enfant en cas d'annulation.\n\nRègle pratique : aucune boucle sur une entrée de taille externe ne doit dépasser un court intervalle délimité sans heartbeat.\n\n## Comportement en cas d'échec et propagation des erreurs vers JS\n\n### Tâches bloquantes\n\nChemin d'erreur :\n\n1. La fermeture retourne `Err(napi::Error)` (y compris l'abandon par `heartbeat()`).\n2. `Task::compute()` retourne `Err`.\n3. `AsyncTask` rejette la promesse JS.\n\nChaînes d'erreur typiques :\n\n- `Aborted: Timeout`\n- `Aborted: Signal`\n- erreurs de domaine (`Failed to decode image: ...`, `Conversion error: ...`, etc.)\n\n### Tâches futures\n\nChemin d'erreur :\n\n1. Le corps asynchrone retourne `Err(napi::Error)` ou l'échec de jointure est mappé (`... task failed: {err}`).\n2. La promesse générée par `task::future` est rejetée.\n3. Certaines API retournent intentionnellement des résultats d'annulation structurés au lieu d'un rejet (`ShellRunResult`/`ShellExecuteResult` avec les drapeaux `cancelled`/`timed_out` et `exit_code: None`).\n\n### Séparation du rapport d'annulation\n\n- **Annulation comme erreur** : la plupart des exports bloquants utilisant `heartbeat()?`.\n- **Annulation comme résultat typé** : API de commandes de style shell/pty qui modélisent l'annulation dans des structures de résultat.\n\nChoisir un seul modèle par API et le documenter explicitement.\n\n## Pièges courants\n\n1. **Heartbeat manquant dans les boucles bloquantes**\n   - Symptôme : le délai d'expiration/signal semble ignoré jusqu'à la fin de la boucle.\n   - Correction : ajouter `ct.heartbeat()?` en tête de boucle et avant les étapes coûteuses par élément.\n\n2. **Longues sections non annulables**\n   - Symptôme : pics de latence d'annulation lors d'un seul appel volumineux (décodage, tri, compression, etc.).\n   - Correction : diviser le travail en blocs avec des points de heartbeat ; si impossible, documenter la latence.\n\n3. **Blocage de l'exécuteur asynchrone**\n   - Symptôme : l'API asynchrone se bloque lorsque du code intensif en synchrone s'exécute directement dans un future.\n   - Correction : déplacer les blocs CPU/synchrones vers `task::blocking` ou `tokio::task::spawn_blocking`.\n\n4. **Sémantiques d'annulation incohérentes**\n   - Symptôme : une API rejette en cas d'annulation, une autre résout avec des drapeaux, ce qui perturbe les appelants.\n   - Correction : standardiser par domaine et maintenir l'alignement de la documentation des wrappers.\n\n5. **Oubli du pont d'annulation dans les tâches asynchrones imbriquées**\n   - Symptôme : le jeton externe est annulé mais les lecteurs/tâches de sous-processus internes continuent de fonctionner.\n   - Correction : relier l'annulation au jeton/signal interne et appliquer un délai de grâce avec repli sur abandon forcé.\n\n## Liste de contrôle pour les nouveaux exports annulables\n\n1. Classifier correctement le travail :\n   - Lié au CPU ou blocage synchrone -> `task::blocking`\n   - I/O asynchrone / orchestration `await` -> `task::future`\n\n2. Exposer les entrées d'annulation si nécessaire :\n   - inclure `timeoutMs` et `signal` dans les options `#[napi(object)]`\n   - créer `let ct = task::CancelToken::new(timeout_ms, signal);`\n\n3. Relier l'annulation à travers toutes les couches :\n   - boucles bloquantes : `ct.heartbeat()?` à intervalles stables\n   - orchestration asynchrone : course avec `ct.wait()` et annulation des sous-tâches/jetons\n\n4. Définir le contrat d'annulation :\n   - rejeter la promesse avec une erreur d'annulation, ou\n   - résoudre un type structuré `{ cancelled, timedOut, ... }`\n   - maintenir ce contrat cohérent pour la famille d'API\n\n5. Propager les échecs avec contexte :\n   - mapper les erreurs via `Error::from_reason(format!(\"...: {err}\"))`\n   - inclure des préfixes spécifiques à l'étape (`spawn`, `decode`, `wait`, etc.)\n\n6. Gérer l'annulation avant démarrage et en cours d'exécution :\n   - la vérification/attente d'annulation doit avoir lieu avant le corps coûteux et durant une longue exécution\n\n7. Valider l'absence d'utilisation incorrecte de l'exécuteur :\n   - pas de long travail synchrone directement dans des futures asynchrones sans `spawn_blocking`/wrapper de tâche bloquante\n",
	"fr/natives/natives-shell-pty-process.md": "---\ntitle: 'Natifs Shell, PTY, Processus et Gestion des touches internes'\ndescription: >-\n  Exécution Shell, gestion PTY, cycle de vie des processus et gestion des\n  événements clavier dans la couche native.\nsidebar:\n  order: 4\n  label: 'Shell, PTY & processus'\ni18n:\n  sourceHash: 00ea95614c6a\n  translator: machine\n---\n\n# Natifs Shell, PTY, Processus et Gestion des touches internes\n\nCe document couvre les **primitives d'exécution/processus/terminal** dans `@f5-sales-demo/pi-natives` : `shell`, `pty`, `ps`, et `keys`, en utilisant les termes architecturaux de `docs/natives-architecture.md`.\n\n## Fichiers d'implémentation\n\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/shell/windows.rs` (Windows uniquement)\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/ps.rs`\n- `crates/pi-natives/src/keys.rs`\n- `crates/pi-natives/src/task.rs` (comportement d'annulation partagé utilisé par shell/pty)\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/bindings.ts`\n\n## Responsabilité des couches\n\n- **Couche wrapper/API TS** (`packages/natives/src/*`) : points d'entrée typés, surface d'annulation (`timeoutMs`, `AbortSignal`), et ergonomie JS.\n- **Couche module Rust N-API** (`crates/pi-natives/src/*`) : exécution des processus shell/PTY, traversée/terminaison de l'arborescence de processus, et analyse des séquences de touches.\n- **Porte de validation** (`native.ts`, niveau architectural) : garantit que les exports requis (`Shell`, `executeShell`, `PtySession`, `killTree`, `listDescendants`, helpers de touches) existent avant l'utilisation des wrappers.\n\n## Sous-système Shell (`shell`)\n\n### Modèle d'API\n\nDeux modes d'exécution sont exposés :\n\n1. **One-shot** via `executeShell(options, onChunk?)`.\n2. **Session persistante** via `new Shell(options?)` puis `shell.run(...)` de manière répétée.\n\nLes deux diffusent la sortie via un callback threadsafe et retournent `{ exitCode?, cancelled, timedOut }`.\n\n### Création de session et modèle d'environnement\n\nRust crée `brush_core::Shell` avec :\n\n- mode non-interactif,\n- `do_not_inherit_env: true`,\n- reconstruction explicite de l'environnement à partir de l'env hôte,\n- liste d'exclusion pour les variables sensibles au shell (`PS1`, `PWD`, `SHLVL`, exports de fonctions bash, etc.).\n\nComportement de l'env de session :\n\n- `ShellOptions.sessionEnv` est appliqué une fois à la création de la session.\n- `ShellRunOptions.env` est limité à la commande (`EnvironmentScope::Command`) et retiré après chaque exécution.\n- `PATH` est fusionné de manière spéciale sous Windows avec une déduplication insensible à la casse.\n\nEnrichissement de chemin spécifique à Windows (`shell/windows.rs`) : les chemins Git-for-Windows découverts (`cmd`, `bin`, `usr/bin`) sont ajoutés s'ils sont présents et non déjà inclus.\n\n### Cycle de vie d'exécution et transitions d'état\n\nLe shell persistant (`Shell.run`) utilise cette machine à états :\n\n- **Inactif/Non initialisé** : `session: None`.\n- **En cours d'exécution** : le premier `run()` crée la session paresseusement, stocke le token `current_abort`, et exécute la commande.\n- **Terminé + maintien actif** : si le flux de contrôle d'exécution est `Normal`, `current_abort` est effacé et la session est réutilisée.\n- **Terminé + arrêt** : si le flux de contrôle est lié à une boucle/script/sortie du shell (`BreakLoop`, `ContinueLoop`, `ReturnFromFunctionOrScript`, `ExitShell`), la session est abandonnée (`session: None`).\n- **Annulé/Expiré** : la tâche d'exécution est annulée, attente gracieuse (2s), puis abandon forcé ; la session est abandonnée.\n- **Erreur** : la session est abandonnée.\n\nLe shell one-shot (`executeShell`) crée et abandonne toujours une nouvelle session à chaque appel.\n\n### Comportement de streaming/sortie\n\n- Stdout/stderr sont acheminés dans un pipe partagé et lus de manière concurrente.\n- Le lecteur décode l'UTF-8 de manière incrémentielle ; les séquences d'octets invalides émettent des chunks de remplacement `U+FFFD`.\n- Après la complétion du processus, le drain de sortie dispose de gardes d'inactivité/maximum (`250ms` d'inactivité, `2s` maximum) pour éviter les blocages dus aux tâches de fond maintenant les descripteurs ouverts.\n\n### Annulation, expiration et tâches de fond\n\n- `CancelToken` est construit à partir de `timeoutMs` et d'un `AbortSignal` optionnel.\n- En cas d'annulation/expiration, le token d'annulation du shell est déclenché, puis la tâche dispose d'une fenêtre gracieuse de 2s avant l'abandon forcé.\n- Si l'annulation se produit, les tâches de fond sont terminées (`TERM`, puis `KILL` différé) en utilisant les métadonnées de job de brush.\n\nComportement de `Shell.abort()` :\n\n- annule uniquement la commande en cours d'exécution pour cette instance `Shell`,\n- opération nulle en cas de succès lorsque rien n'est en cours d'exécution.\n\n### Comportement en cas d'échec\n\nLes erreurs courantes incluent :\n\n- échecs d'initialisation de session (`Failed to initialize shell`),\n- erreurs de répertoire courant (`Failed to set cwd`),\n- échecs de définition/retrait d'env,\n- échecs de source de snapshot,\n- échecs de création/clonage de pipe,\n- échec d'exécution (`Shell execution failed: ...`),\n- échecs du wrapper de tâche (`Shell execution task failed: ...`).\n\nIndicateurs d'annulation au niveau du résultat :\n\n- expiration -> `exitCode: undefined`, `timedOut: true`.\n- signal d'abandon -> `exitCode: undefined`, `cancelled: true`.\n\n## Sous-système PTY (`pty`)\n\n### Modèle d'API\n\n`new PtySession()` expose :\n\n- `start(options, onChunk?) -> Promise<{ exitCode?, cancelled, timedOut }>`\n- `write(data)`\n- `resize(cols, rows)`\n- `kill()`\n\n### Cycle de vie d'exécution et transitions d'état\n\nMachine à états de `PtySession` :\n\n- **Inactif** : `core: None`.\n- **Réservé** : `start()` installe le canal de contrôle de manière synchrone (`core: Some`) avant que le travail asynchrone ne commence, de sorte que `write/resize/kill` deviennent immédiatement valides.\n- **En cours d'exécution** : la boucle PTY bloquante gère l'état du processus enfant, les événements du lecteur, le battement de cœur d'annulation, et les messages de contrôle.\n- **Terminal fermé** : sortie du processus enfant + complétion du lecteur.\n- **Finalisé** : `core` est toujours réinitialisé à `None` après la complétion de la tâche de démarrage (succès ou erreur).\n\nGarde de concurrence :\n\n- démarrer alors qu'une session est déjà en cours retourne `PTY session already running`.\n\n### Patterns de spawn/attach/write/read/terminate\n\n- PTY ouvert via `portable_pty::native_pty_system().openpty(...)`.\n- La commande s'exécute actuellement comme `sh -lc <command>` avec `cwd` et des substitutions d'env optionnels.\n- `write()` envoie des octets bruts vers stdin du PTY.\n- `resize()` limite les dimensions (`cols 20..400`, `rows 5..200`) et appelle le redimensionnement du maître.\n- `kill()` marque l'exécution comme annulée et tue le processus enfant.\n\nChemin de sortie :\n\n- un thread lecteur dédié lit le flux maître,\n- décodage UTF-8 incrémentiel avec remplacement `U+FFFD` sur les octets invalides,\n- chunks transmis via le callback threadsafe N-API.\n\n### Sémantique d'annulation et d'expiration\n\n- `timeoutMs` et `AbortSignal` alimentent un `CancelToken`.\n- la boucle appelle `ct.heartbeat()` périodiquement ; l'abandon déclenche le kill du processus enfant.\n- la classification de l'expiration est basée sur une chaîne (sous-chaîne `\"Timeout\"` dans l'erreur de battement de cœur).\n\n### Comportement en cas d'échec\n\nLes surfaces d'erreur comprennent :\n\n- échec d'allocation/ouverture PTY,\n- échec de spawn PTY,\n- échec d'acquisition du writer/reader,\n- échecs de statut/attente du processus enfant,\n- empoisonnement de verrou,\n- déconnexion du canal de contrôle (`PTY session is no longer available`).\n\nÉchecs d'appels de contrôle lorsqu'inactif :\n\n- `write/resize/kill` retournent `PTY session is not running`.\n\n## Sous-système d'arborescence de processus (`ps`)\n\n### Modèle d'API\n\n- `killTree(pid, signal) -> number`\n- `listDescendants(pid) -> number[]`\n\nLe wrapper TS enregistre également l'intégration native de kill-tree dans les utils partagés via `setNativeKillTree(native.killTree)`.\n\n### Implémentation spécifique à la plateforme\n\n- **Linux** : lit récursivement `/proc/<pid>/task/<pid>/children`.\n- **macOS** : utilise `libproc` `proc_listchildpids`.\n- **Windows** : capture l'état de la table des processus avec `CreateToolhelp32Snapshot`, construit une carte parent->enfants, termine avec `OpenProcess(PROCESS_TERMINATE)` + `TerminateProcess`.\n\n### Comportement de kill-tree\n\n- Les descendants sont collectés récursivement.\n- L'ordre de kill est de bas en haut (descendants les plus profonds en premier) pour réduire le re-parentage des orphelins.\n- Le pid racine est tué en dernier.\n- La valeur de retour est le nombre de terminaisons réussies.\n\nComportement des signaux :\n\n- POSIX : le `signal` fourni est passé à `kill`.\n- Windows : `signal` est ignoré ; la terminaison est un arrêt de processus inconditionnel.\n\n### Comportement en cas d'échec\n\nCe module est intentionnellement non-lançant au niveau de la surface API :\n\n- les branches d'arborescence de processus manquantes/inaccessibles sont ignorées,\n- les échecs de kill par pid sont comptés comme non réussis (pas comme des erreurs),\n- un échec de recherche retourne typiquement `[]` de `listDescendants` et `0` de `killTree`.\n\n## Sous-système d'analyse des touches (`keys`)\n\n### Modèle d'API\n\nHelpers exposés :\n\n- `parseKey(data, kittyProtocolActive)`\n- `matchesKey(data, keyId, kittyProtocolActive)`\n- `parseKittySequence(data)`\n- `matchesKittySequence(data, expectedCodepoint, expectedModifier)`\n- `matchesLegacySequence(data, keyName)`\n\n### Modèle d'analyse\n\nL'analyseur combine :\n\n- mappages directs sur un seul octet (`enter`, `tab`, `ctrl+<lettre>`, ASCII imprimable),\n- recherche O(1) de séquences d'échappement legacy (carte PHF),\n- analyse de `modifyOtherKeys` xterm,\n- analyse du protocole Kitty (`CSI u`, `CSI ~`, `CSI 1;...<lettre>`),\n- normalisation vers des identifiants de touches (`ctrl+c`, `shift+tab`, `pageUp`, `f5`, etc.).\n\nGestion des modificateurs :\n\n- seuls les bits shift/alt/ctrl sont comparés pour la correspondance de touches,\n- les bits de verrouillage sont masqués avant les comparaisons.\n\nComportement de disposition :\n\n- le repli sur la disposition de base est intentionnellement limité afin que les dispositions remappées ne créent pas de fausses correspondances pour les lettres/symboles ASCII.\n\n### Comportement en cas d'échec\n\n- Les séquences non reconnues ou invalides produisent `null` depuis les fonctions d'analyse.\n- Les fonctions de correspondance retournent `false` en cas d'échec d'analyse ou de non-correspondance.\n- Aucune surface d'erreur levée pour les entrées de touches malformées.\n\n## Correspondance API wrapper JS ↔ export Rust\n\n### Shell + PTY + Processus\n\n| API wrapper TS | Export Rust N-API | Notes |\n|---|---|---|\n| `executeShell(options, onChunk?)` | `executeShell` (`execute_shell`) | Exécution shell one-shot |\n| `new Shell(options?)` | classe `Shell` | Session shell persistante |\n| `shell.run(options, onChunk?)` | `Shell::run` | Réutilise la session lors d'un maintien actif du flux de contrôle |\n| `shell.abort()` | `Shell::abort` | Annule l'exécution active pour cette instance shell |\n| `new PtySession()` | classe `PtySession` | Session PTY avec état |\n| `pty.start(options, onChunk?)` | `PtySession::start` | Exécution PTY interactive |\n| `pty.write(data)` | `PtySession::write` | Transmission brute vers stdin |\n| `pty.resize(cols, rows)` | `PtySession::resize` | Dimensions du terminal limitées |\n| `pty.kill()` | `PtySession::kill` | Force la terminaison du processus enfant PTY actif |\n| `killTree(pid, signal)` | `killTree` (`kill_tree`) | Terminaison de l'arborescence de processus en commençant par les enfants |\n| `listDescendants(pid)` | `listDescendants` (`list_descendants`) | Liste récursive des descendants |\n\n### Touches\n\n| API wrapper TS | Export Rust N-API | Notes |\n|---|---|---|\n| `matchesKittySequence(data, cp, mod)` | `matchesKittySequence` (`matches_kitty_sequence`) | Correspondance Kitty codepoint+modificateur |\n| `parseKey(data, kittyProtocolActive)` | `parseKey` (`parse_key`) | Analyseur d'identifiant de touche normalisé |\n| `matchesLegacySequence(data, keyName)` | `matchesLegacySequence` (`matches_legacy_sequence`) | Vérification exacte dans la carte de séquences legacy |\n| `parseKittySequence(data)` | `parseKittySequence` (`parse_kitty_sequence`) | Résultat d'analyse Kitty structuré |\n| `matchesKey(data, keyId, kittyProtocolActive)` | `matchesKey` (`matches_key`) | Correspondant de touches de haut niveau |\n\n## Notes sur le nettoyage des sessions abandonnées et la finalisation\n\n- **Session shell persistante** : si une exécution est annulée/expirée/en erreur/hors flux de maintien actif, Rust abandonne explicitement l'état interne de la session. Les exécutions normales réussies conservent la session pour réutilisation.\n- **Session PTY** : `core` est toujours effacé après la fin de `start()`, y compris sur les chemins d'échec.\n- **Aucun contrat de kill piloté par un finaliseur JS explicite** n'est exposé par les wrappers ; le nettoyage est principalement lié aux chemins de complétion/annulation d'exécution. Les appelants doivent utiliser `timeoutMs`, `AbortSignal`, `shell.abort()`, ou `pty.kill()` pour un arrêt déterministe.\n",
	"fr/natives/natives-text-search-pipeline.md": "---\ntitle: Pipeline natif de texte et de recherche\ndescription: >-\n  Pipeline de recherche de texte natif avec indexation du contenu de fichiers\n  basée sur grep, glob et ripgrep.\nsidebar:\n  order: 6\n  label: Pipeline texte et recherche\ni18n:\n  sourceHash: 0e93462fdd12\n  translator: machine\n---\n\n# Pipeline natif de texte/recherche\n\nCe document décrit la surface de texte/recherche de `@f5-sales-demo/pi-natives` (`grep`, `glob`, `text`, `highlight`), depuis les wrappers TypeScript jusqu'aux exports N-API Rust et aux objets de résultats JS.\n\nLa terminologie suit `docs/natives-architecture.md` :\n\n- **Wrapper** : API TS dans `packages/natives/src/*`\n- **Couche module Rust** : exports N-API dans `crates/pi-natives/src/*`\n- **Cache de scan partagé** : cache d'entrées de répertoire géré par `fs_cache`, utilisé par les flux de découverte/recherche\n\n## Fichiers d'implémentation\n\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/glob_util.rs`\n- `crates/pi-natives/src/fs_cache.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/highlight.rs`\n- `crates/pi-natives/src/fd.rs`\n\n## Correspondance API JS ↔ export Rust\n\n| API wrapper JS | Export Rust (`#[napi]`, snake_case -> camelCase) | Module Rust |\n| --- | --- | --- |\n| `grep(options, onMatch?)` | `grep` | `grep.rs` |\n| `searchContent(content, options)` | `search` | `grep.rs` |\n| `hasMatch(content, pattern, options?)` | `hasMatch` | `grep.rs` |\n| `fuzzyFind(options)` | `fuzzyFind` | `fd.rs` |\n| `glob(options, onMatch?)` | `glob` | `glob.rs` |\n| `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `fs_cache.rs` |\n| `wrapTextWithAnsi(text, width)` | `wrapTextWithAnsi` | `text.rs` |\n| `truncateToWidth(text, maxWidth, ellipsis, pad)` | `truncateToWidth` | `text.rs` |\n| `sliceWithWidth(line, startCol, length, strict?)` | `sliceWithWidth` | `text.rs` |\n| `extractSegments(line, beforeEnd, afterStart, afterLen, strictAfter)` | `extractSegments` | `text.rs` |\n| `sanitizeText(text)` | `sanitizeText` | `text.rs` |\n| `visibleWidth(text)` | `visibleWidth` | `text.rs` |\n| `highlightCode(code, lang, colors)` | `highlightCode` | `highlight.rs` |\n| `supportsLanguage(lang)` | `supportsLanguage` | `highlight.rs` |\n| `getSupportedLanguages()` | `getSupportedLanguages` | `highlight.rs` |\n\n## Vue d'ensemble du pipeline par sous-système\n\n## 1) Recherche par expression régulière (`grep`, `searchContent`, `hasMatch`)\n\n### Flux d'entrée/options\n\n1. Le wrapper TS transmet les options au module natif :\n   - `grep/index.ts` transmet `options` pratiquement sans modification et encapsule le callback de la forme `(match) => void` vers la forme de callback threadsafe napi `(err, match)`.\n   - `searchContent` et `hasMatch` transmettent directement une chaîne ou un `Uint8Array`.\n2. Les structures d'options Rust dans `grep.rs` désérialisent les champs en camelCase (`ignoreCase`, `maxCount`, `contextBefore`, `contextAfter`, `maxColumns`, `timeoutMs`).\n3. `grep` crée un `CancelToken` à partir de `timeoutMs` + `AbortSignal` et s'exécute à l'intérieur de `task::blocking(\"grep\", ...)`.\n\n### Branches d'exécution\n\n- **Branche en mémoire (utilitaire pur)**\n  - `search` → `search_sync` → `run_search` sur les octets de contenu fournis.\n  - Pas d'accès au système de fichiers, pas de `fs_cache`.\n- **Branche fichier unique (dépendante du système de fichiers)**\n  - `grep_sync` résout le chemin, vérifie que les métadonnées correspondent à un fichier, puis lit jusqu'à `MAX_FILE_BYTES` par fichier (`4 Mio`) à travers le matcher ripgrep.\n- **Branche répertoire (dépendante du système de fichiers)**\n  - Consultation optionnelle du cache via `fs_cache::get_or_scan` quand `cache: true`.\n  - Scan frais via `fs_cache::force_rescan` quand `cache: false`.\n  - Vérification optionnelle des résultats vides lorsque l'ancienneté du cache dépasse `empty_recheck_ms()`.\n  - Filtrage des entrées : fichiers uniquement + filtre glob optionnel (`glob_util`) + filtre de type optionnel (`js`, `ts`, `rust`, etc.).\n\n### Sémantiques de recherche/collecte\n\n- Moteur de regex : `grep_regex::RegexMatcherBuilder` avec `ignoreCase` et `multiline`.\n- Résolution du contexte :\n  - `contextBefore/contextAfter` remplacent le champ `context` hérité.\n  - Les modes sans contenu remettent à zéro la collecte de contexte.\n- Modes de sortie :\n  - `content` => un `GrepMatch` par correspondance.\n  - `count` et `filesWithMatches` correspondent tous deux à des entrées de type comptage (`lineNumber=0`, `line=\"\"`, `matchCount` défini).\n- Limites :\n  - Les paramètres globaux `offset` et `maxCount` sont appliqués sur l'ensemble des fichiers.\n  - Le chemin parallèle n'est utilisé que lorsque `maxCount` n'est pas défini et que `offset == 0` ; sinon, le chemin séquentiel préserve la sémantique déterministe de décalage/limite global.\n\n### Mise en forme des résultats vers JS\n\n- Les champs Rust `SearchResult`/`GrepResult` sont mappés vers les types TS via la conversion de champs d'objets N-API.\n- Les compteurs sont limités à `u32` avant de franchir la frontière N-API.\n- Les booléens optionnels sont omis sauf s'ils sont vrais dans certains chemins (`limitReached`).\n- Le callback de streaming reçoit chaque `GrepMatch` mis en forme (entrée de contenu ou de comptage).\n\n### Comportement en cas d'échec\n\n- `searchContent` retourne `SearchResult.error` en cas d'échec de regex/recherche au lieu de lever une exception.\n- `grep` rejette sur les erreurs fatales (chemin invalide, glob/regex invalide, annulation par timeout/abandon).\n- `hasMatch` retourne `Result<bool>` et lève une exception en cas de motif invalide ou d'erreurs de décodage UTF-8.\n- Les erreurs d'ouverture/de recherche de fichiers dans les scans multi-fichiers sont ignorées pour chaque fichier ; le scan continue.\n\n### Gestion des expressions régulières malformées\n\n`grep.rs` assainit les accolades avant la compilation de la regex :\n\n- Les accolades ressemblant à des répétitions invalides sont échappées (`{`/`}` -> `\\{`/`\\}`) lorsqu'elles ne peuvent pas former `{N}`, `{N,}`, `{N,M}`.\n- Cela empêche les fragments de gabarit littéraux courants (par exemple `${platform}`) d'échouer en tant que répétitions malformées.\n- La syntaxe de regex invalide restante retourne quand même une erreur de regex.\n\n## 2) Découverte de fichiers (`glob`) et recherche floue de chemins (`fuzzyFind`)\n\n`glob` et `fuzzyFind` partagent les scans `fs_cache` ; la logique de correspondance diffère.\n\n### Flux `glob`\n\n1. Wrapper TS (`glob/index.ts`) :\n   - `path.resolve(options.path)`.\n   - Valeurs par défaut : `pattern=\"*\"`, `hidden=false`, `gitignore=true`, `recursive=true`.\n2. Rust `glob` construit `GlobConfig` et compile le motif via `glob_util::compile_glob`.\n3. Source d'entrées :\n   - `cache=true` => `get_or_scan` + `force_rescan` optionnel en cas de cache vide périmé.\n   - `cache=false` => `force_rescan(..., store=false)` (scan frais uniquement).\n4. Filtrage :\n   - Ignorer `.git` systématiquement.\n   - Ignorer `node_modules` sauf si demandé (`includeNodeModules` ou motif mentionnant node_modules).\n   - Appliquer la correspondance glob.\n   - Appliquer le filtre de type de fichier ; les filtres `file/dir` sur les liens symboliques résolvent les métadonnées de la cible.\n5. Tri optionnel par mtime décroissant (`sortByMtime`) avant la troncature à `maxResults`.\n\n### Flux `fuzzyFind` (implémenté dans `fd.rs`)\n\n1. Le wrapper TS est exporté depuis le module `grep`, mais l'implémentation Rust se trouve dans `fd.rs`.\n2. Source de scan partagée depuis `fs_cache` avec le même découpage cache/sans-cache et la même politique de revérification en cas de cache vide périmé.\n3. Scoring :\n   - score flou basé sur exact / commence-par / contient / sous-séquence\n   - chemin de scoring normalisé par séparateurs/ponctuation\n   - bonus de répertoire et départage déterministe (`score desc`, puis `path asc`)\n4. Les entrées de liens symboliques sont exclues des résultats fuzzy.\n\n### Comportement en cas d'échec\n\n- Motif glob invalide => erreur provenant de `glob_util::compile_glob`.\n- La racine de recherche doit être un répertoire existant (`resolve_search_path`), sinon erreur.\n- Les annulations/timeouts se propagent comme des erreurs d'abandon via les vérifications `CancelToken::heartbeat()` dans les boucles.\n\n### Gestion des globs malformés\n\n`glob_util::build_glob_pattern` est tolérant :\n\n- Normalise `\\` en `/`.\n- Préfixe automatiquement les motifs récursifs simples avec `**/` quand `recursive=true`.\n- Ferme automatiquement les groupes d'alternance `{...` non équilibrés avant la compilation.\n\n## 3) Cycle de vie du scan/cache partagé (`fs_cache`)\n\n`fs_cache` stocke les résultats de scan sous forme d'entrées relatives normalisées (`path`, `fileType`, `mtime` optionnel) indexées par :\n\n- racine de recherche canonique\n- `include_hidden`\n- `use_gitignore`\n\n### Transitions d'état du cache\n\n1. **Absence / désactivé**\n   - TTL est `0` ou la clé est absente/expirée -> `collect_entries` frais.\n2. **Présence**\n   - Ancienneté de l'entrée `< cache_ttl_ms()` -> retourner les entrées mises en cache + `cache_age_ms`.\n3. **Revérification en cas de cache vide périmé** (politique de l'appelant dans `glob`/`grep`/`fd`)\n   - Si la requête produit zéro correspondance et `cache_age_ms >= empty_recheck_ms()`, forcer un nouveau scan.\n4. **Invalidation**\n   - `invalidateFsScanCache(path?)` :\n     - sans argument : effacer toutes les clés\n     - avec un chemin : supprimer les clés dont la racine préfixe ce chemin cible\n\n### Compromis liés aux résultats périmés\n\n- Le cache favorise les scans répétés à faible latence plutôt que la cohérence immédiate.\n- La fenêtre TTL peut retourner des résultats positifs/négatifs périmés.\n- La revérification en cas de résultat vide réduit les faux négatifs périmés pour les scans anciens en cache, au coût d'un scan supplémentaire.\n- L'invalidation explicite est le mécanisme de correction prévu après les mutations de fichiers.\n\n## 4) Utilitaires de texte ANSI (`text`)\n\nCe sont des utilitaires purs, en mémoire (sans accès au système de fichiers).\n\n### Périmètre et responsabilités\n\n- **`text.rs` gère la sémantique des cellules de terminal** :\n  - analyse des séquences ANSI\n  - largeur et découpage prenant en compte les graphèmes\n  - comportements de retour à la ligne, troncature et assainissement\n- **La troncature de ligne de `grep.rs` (`maxColumns`) est séparée** :\n  - troncature simple à la limite des caractères des lignes correspondantes avec `...`\n  - ne préserve pas l'état ANSI et ne tient pas compte de la largeur en cellules de terminal\n\n### Comportements clés\n\n- `wrapTextWithAnsi` : effectue le retour à la ligne par largeur visible, transporte les codes SGR actifs sur les lignes encapsulées.\n- `truncateToWidth` : troncature par cellules visibles avec politique d'ellipse (`Unicode`, `Ascii`, `Omit`), rembourrage optionnel à droite, et chemin rapide retournant la chaîne JS originale si elle est inchangée.\n- `sliceWithWidth` : découpage par colonne avec application optionnelle stricte de la largeur.\n- `extractSegments` : extrait les segments avant/après autour d'une superposition tout en restaurant l'état ANSI pour le segment `after`.\n- `sanitizeText` : supprime les séquences ANSI et les caractères de contrôle, rejette les surrogats isolés, normalise CR/LF en supprimant `\\r`.\n- `visibleWidth` : compte les cellules de terminal visibles (les tabulations utilisent `TAB_WIDTH` fixe défini dans l'implémentation Rust).\n\n### Comportement en cas d'échec\n\nLes fonctions texte retournent généralement une sortie transformée déterministe ; les erreurs se limitent aux frontières de conversion des chaînes JS (échecs de conversion d'arguments N-API).\n\n## 5) Coloration syntaxique (`highlight`)\n\n`highlight.rs` est une transformation pure (pas de système de fichiers, pas de cache).\n\n### Flux\n\n1. Le wrapper transmet `code`, le `lang` optionnel et la palette de couleurs ANSI.\n2. Rust résout la syntaxe par :\n   - recherche par jeton/nom\n   - recherche par extension\n   - repli sur la table d'alias (`ts/tsx/js -> JavaScript`, etc.)\n   - repli sur la syntaxe texte brut si non résolu\n3. Analyser chaque ligne avec `ParseState` et la pile de portées de syntect.\n4. Mapper les portées vers 11 catégories de couleurs sémantiques et injecter/réinitialiser les codes de couleur ANSI.\n\n### Comportement en cas d'échec\n\n- Un échec d'analyse par ligne n'échoue pas l'appel : cette ligne est ajoutée sans coloration et le traitement continue.\n- Un langage inconnu/non pris en charge replie sur la syntaxe texte brut.\n\n## Flux utilitaires purs vs dépendants du système de fichiers\n\n| Flux | Accès au système de fichiers | Cache partagé | Notes |\n| --- | --- | --- | --- |\n| `searchContent` / `hasMatch` | Non | Non | regex sur les octets/chaîne fournis uniquement |\n| Fonctions du module `text` | Non | Non | ANSI/largeur/assainissement uniquement |\n| Fonctions du module `highlight` | Non | Non | coloration syntaxique + ANSI uniquement |\n| `glob` | Oui | Optionnel | scans de répertoires + filtrage glob |\n| `fuzzyFind` | Oui | Optionnel | scans de répertoires + scoring flou |\n| `grep` (chemin fichier/répertoire) | Oui | Optionnel (mode répertoire) | ripgrep sur les fichiers, filtres/callback optionnels |\n\n## Résumé du cycle de vie de bout en bout\n\n1. L'appelant invoque le wrapper TS avec des options typées.\n2. Le wrapper normalise les valeurs par défaut (notamment pour `glob`) et transmet à l'export `native.*`.\n3. Rust valide/normalise les options et construit la configuration du matcher/de recherche.\n4. Pour les flux liés au système de fichiers, les entrées sont scannées (présence/absence en cache/nouveau scan) puis filtrées/scorées.\n5. Les boucles de travail appellent périodiquement le heartbeat d'annulation ; le timeout/abandon peut mettre fin à l'exécution.\n6. Rust met en forme les sorties en objets N-API (`lineNumber`, `matchCount`, `limitReached`, etc.).\n7. Le wrapper TS retourne des objets JS typés (et des callbacks optionnels par correspondance pour `grep`/`glob`).\n",
	"fr/natives/porting-to-natives.md": "---\ntitle: Portage vers pi-natives (N-API) — Notes de terrain\ndescription: >-\n  Notes de terrain pour la migration du code Node.js child_process et shell vers\n  la couche native Rust N-API.\nsidebar:\n  order: 9\n  label: Portage vers pi-natives\ni18n:\n  sourceHash: 4f5150286535\n  translator: machine\n---\n\n# Portage vers pi-natives (N-API) — Notes de terrain\n\nCeci est un guide pratique pour déplacer les chemins critiques dans `crates/pi-natives` et les connecter via les bindings JS. Il existe pour éviter que les mêmes erreurs se reproduisent.\n\n## Quand effectuer un portage\n\nPortez lorsque l'une de ces conditions est vraie :\n\n- Le chemin critique s'exécute dans des boucles de rendu, des mises à jour UI fréquentes ou des traitements par lots volumineux.\n- Les allocations JS dominent (rotation de chaînes, retour en arrière de regex, grands tableaux).\n- Vous disposez déjà d'une référence JS et pouvez comparer les deux versions côte à côte.\n- Le travail est limité par le CPU ou du I/O bloquant qui peut s'exécuter sur le pool de threads libuv.\n- Le travail est du I/O asynchrone qui peut s'exécuter sur le runtime Tokio (par ex., exécution shell).\n\nÉvitez les portages qui dépendent d'un état uniquement JS ou d'imports dynamiques. Les exports N-API doivent être purs, données en entrée/données en sortie. Le travail de longue durée doit passer par `task::blocking` (limité par le CPU/I/O bloquant) ou `task::future` (I/O asynchrone) avec annulation.\n\n## Anatomie d'un export natif\n\n**Côté Rust :**\n\n- L'implémentation se trouve dans `crates/pi-natives/src/<module>.rs`. Si vous ajoutez un nouveau module, enregistrez-le dans `crates/pi-natives/src/lib.rs`.\n- Exportez avec `#[napi]` ; les exports en snake_case sont convertis automatiquement en camelCase. Utilisez `js_name` explicite uniquement pour les vrais alias/noms non par défaut. Utilisez `#[napi(object)]` pour les structs.\n- Utilisez `task::blocking(tag, cancel_token, work)` (voir `crates/pi-natives/src/task.rs`) pour le travail limité par le CPU ou bloquant. Utilisez `task::future(env, tag, work)` pour le travail asynchrone nécessitant Tokio (par ex., sessions shell). Passez un `CancelToken` lorsque vous exposez `timeoutMs` ou `AbortSignal`.\n\n**Côté JS :**\n\n- `packages/natives/src/bindings.ts` contient l'interface de base `NativeBindings`.\n- `packages/natives/src/<module>/types.ts` définit les types TS et augmente `NativeBindings` via la fusion de déclarations.\n- `packages/natives/src/native.ts` importe chaque fichier `<module>/types.ts` pour activer les déclarations.\n- `packages/natives/src/<module>/index.ts` encapsule le binding `native` depuis `packages/natives/src/native.ts`.\n- `packages/natives/src/native.ts` charge l'addon et `validateNative` vérifie les exports requis.\n- `packages/natives/src/index.ts` réexporte le wrapper pour les appelants dans `packages/*`.\n\n## Liste de contrôle du portage\n\n1. **Ajouter l'implémentation Rust**\n\n- Placez la logique principale dans une fonction Rust simple.\n- S'il s'agit d'un nouveau module, ajoutez-le à `crates/pi-natives/src/lib.rs`.\n- Exposez-le avec `#[napi]` pour que le mapping par défaut snake_case -> camelCase reste cohérent.\n- Gardez les signatures possédées et simples : `String`, `Vec<String>`, `Uint8Array`, ou `Either<JsString, Uint8Array>` pour les entrées volumineuses de chaînes/octets.\n- Pour le travail limité par le CPU ou bloquant, utilisez `task::blocking` ; pour le travail asynchrone, utilisez `task::future`. Passez un `CancelToken` et appelez `heartbeat()` à l'intérieur des boucles longues.\n\n2. **Connecter les bindings JS**\n\n- Ajoutez les types et l'augmentation `NativeBindings` dans `packages/natives/src/<module>/types.ts`.\n- Importez `./<module>/types` dans `packages/natives/src/native.ts` pour déclencher la fusion de déclarations.\n- Ajoutez un wrapper dans `packages/natives/src/<module>/index.ts` qui appelle `native`.\n- Réexportez depuis `packages/natives/src/index.ts`.\n\n3. **Mettre à jour la validation native**\n\n- Ajoutez `checkFn(\"newExport\")` dans `validateNative` (`packages/natives/src/native.ts`).\n\n4. **Ajouter des benchmarks**\n\n- Placez les benchmarks à côté du package propriétaire (`packages/tui/bench`, `packages/natives/bench`, ou `packages/coding-agent/bench`).\n- Incluez une référence JS et une version native dans la même exécution.\n- Utilisez `Bun.nanoseconds()` et un nombre d'itérations fixe.\n- Gardez les entrées du benchmark petites et réalistes (données réelles observées dans le chemin critique).\n\n5. **Compiler le binaire natif**\n\n- `bun --cwd=packages/natives run build`\n- Utilisez `bun --cwd=packages/natives run build` et définissez `PI_DEV=1` si vous souhaitez des diagnostics du loader pendant les tests.\n\n6. **Exécuter le benchmark**\n\n- `bun run packages/<pkg>/bench/<bench>.ts` (ou `bun --cwd=packages/natives run bench`)\n\n7. **Décider de l'utilisation**\n\n- Si le natif est plus lent, **gardez JS** et laissez l'export natif inutilisé.\n- Si le natif est plus rapide, basculez les sites d'appel vers le wrapper natif.\n\n## Points de friction et comment les éviter\n\n### 1) Un `pi_natives.node` obsolète empêche les nouveaux exports\n\nLe loader préfère le binaire taggé par plateforme dans `packages/natives/native` (`pi_natives.<platform>-<arch>.node`). `PI_DEV=1` n'active désormais que les diagnostics du loader ; il ne bascule plus vers un nom de fichier d'addon dev séparé. Il existe également un fallback `pi_natives.node`. Les binaires compilés s'extraient vers `~/.xcsh/natives/<version>/pi_natives.<platform>-<arch>.node`. Si l'un d'entre eux est obsolète, les exports ne se mettront pas à jour.\n\n**Correctif :** supprimez le fichier obsolète avant de reconstruire.\n\n```bash\nrm packages/natives/native/pi_natives.linux-x64.node\nrm packages/natives/native/pi_natives.node\nbun --cwd=packages/natives run build\n```\n\nSi vous exécutez un binaire compilé, supprimez le répertoire d'addon en cache :\n\n```bash\nrm -rf ~/.xcsh/natives/<version>\n```\n\nPuis vérifiez que l'export existe dans le binaire :\n\n```bash\nbun -e 'const tag = `${process.platform}-${process.arch}`; const mod = require(`./packages/natives/native/pi_natives.${tag}.node`); console.log(Object.keys(mod).includes(\"newExport\"));'\n```\n\n### 2) Erreurs \"Missing exports\" de `validateNative`\n\nC'est **positif** — cela empêche les incohérences silencieuses. Lorsque vous voyez ceci :\n\n```\nNative addon missing exports ... Missing: visibleWidth\n```\n\ncela signifie que votre binaire est obsolète, que le nom de l'export Rust (ou l'alias explicite lorsqu'il est utilisé) ne correspond pas au nom JS, ou que l'export n'a jamais été compilé. Corrigez le build et l'incohérence de nommage, n'affaiblissez pas la validation.\n\n### 3) Incompatibilité de signature Rust\n\nGardez-la simple et possédée. `String`, `Vec<String>`, et `Uint8Array` fonctionnent. Évitez les références comme `&str` dans les exports publics. Si vous avez besoin de données structurées, encapsulez-les dans des structs `#[napi(object)]`.\n\n### 4) Erreurs de benchmarking\n\n- Ne comparez pas des entrées ou allocations différentes.\n- Gardez JS et natif utilisant des tableaux d'entrée identiques.\n- Exécutez les deux dans le même fichier de benchmark pour éviter le biais.\n\n## Modèle de benchmark\n\n```ts\nconst ITERATIONS = 2000;\n\nfunction bench(name: string, fn: () => void): number {\n const start = Bun.nanoseconds();\n for (let i = 0; i < ITERATIONS; i++) fn();\n const elapsed = (Bun.nanoseconds() - start) / 1e6;\n console.log(`${name}: ${elapsed.toFixed(2)}ms total (${(elapsed / ITERATIONS).toFixed(6)}ms/op)`);\n return elapsed;\n}\n\nbench(\"feature/js\", () => {\n jsImpl(sample);\n});\n\nbench(\"feature/native\", () => {\n nativeImpl(sample);\n});\n```\n\n## Liste de contrôle de vérification\n\n- `validateNative` passe (aucun export manquant).\n- `NativeBindings` est augmenté dans `packages/natives/src/<module>/types.ts` et le wrapper est réexporté dans `packages/natives/src/index.ts`.\n- `Object.keys(require(...))` inclut votre nouvel export.\n- Les chiffres de benchmark sont enregistrés dans la PR/notes.\n- Le site d'appel est mis à jour **uniquement si** le natif est plus rapide ou équivalent.\n\n## Règle générale\n\n- Si le natif est plus lent, **ne basculez pas**. Gardez l'export pour un travail futur, mais le TUI doit rester sur le chemin le plus rapide.\n- Si le natif est plus rapide, basculez le site d'appel et gardez le benchmark en place pour détecter les régressions.\n",
	"fr/providers/models.md": "---\ntitle: Configuration des modèles et des fournisseurs\ndescription: >-\n  Registre de modèles et configuration des fournisseurs via models.yml avec\n  routage, fallback et tarification.\nsidebar:\n  order: 1\n  label: Modèles et fournisseurs\ni18n:\n  sourceHash: 8053df967ff6\n  translator: machine\n---\n\n# Configuration des modèles et des fournisseurs (`models.yml`)\n\nCe document décrit comment le coding-agent charge actuellement les modèles, applique les surcharges, résout les identifiants et choisit les modèles à l'exécution.\n\n## Ce qui contrôle le comportement des modèles\n\nFichiers d'implémentation principaux :\n\n- `src/config/model-registry.ts` — charge les modèles intégrés + personnalisés, les surcharges de fournisseurs, la découverte à l'exécution, l'intégration de l'authentification\n- `src/config/model-resolver.ts` — analyse les patterns de modèles et sélectionne les modèles initial/smol/slow\n- `src/config/settings-schema.ts` — paramètres liés aux modèles (`modelRoles`, préférences de transport des fournisseurs)\n- `src/session/auth-storage.ts` — ordre de résolution des clés API + OAuth\n- `packages/ai/src/models.ts` et `packages/ai/src/types.ts` — fournisseurs/modèles intégrés et types `Model`/`compat`\n\n## Emplacement du fichier de configuration et comportement hérité\n\nChemin de configuration par défaut :\n\n- `~/.xcsh/agent/models.yml`\n\nComportement hérité toujours présent :\n\n- Si `models.yml` est absent et que `models.json` existe au même emplacement, il est migré vers `models.yml`.\n- Les chemins de configuration explicites en `.json` / `.jsonc` sont toujours pris en charge lorsqu'ils sont passés programmatiquement à `ModelRegistry`.\n\n## Structure de `models.yml`\n\n```yaml\nconfigVersion: 1  # optional — written by auto-config, used for migration detection\nproviders:\n  <provider-id>:\n    # provider-level config\nequivalence:\n  overrides:\n    <provider-id>/<model-id>: <canonical-model-id>\n  exclude:\n    - <provider-id>/<model-id>\n```\n\n`configVersion` est un entier optionnel écrit par le système de configuration automatique. Lorsqu'il est présent, xcsh l'utilise pour détecter les configurations obsolètes et les mettre à jour automatiquement.\n\n`provider-id` est la clé canonique du fournisseur utilisée pour la sélection et la recherche d'authentification.\n\n`equivalence` est optionnel et configure le regroupement canonique des modèles au-dessus des modèles concrets des fournisseurs :\n\n- `overrides` associe un sélecteur concret exact (`provider/modelId`) à un identifiant canonique officiel en amont\n- `exclude` exclut un sélecteur concret du regroupement canonique\n\n## Champs au niveau du fournisseur\n\n```yaml\nproviders:\n  my-provider:\n    baseUrl: https://api.example.com/v1\n    apiKey: MY_PROVIDER_API_KEY\n    api: openai-completions\n    headers:\n      X-Team: platform\n    authHeader: true\n    auth: apiKey\n    discovery:\n      type: ollama\n    modelOverrides:\n      some-model-id:\n        name: Renamed model\n    models:\n      - id: some-model-id\n        name: Some Model\n        api: openai-completions\n        reasoning: false\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 128000\n        maxTokens: 16384\n        headers:\n          X-Model: value\n        compat:\n          supportsStore: true\n          supportsDeveloperRole: true\n          supportsReasoningEffort: true\n          maxTokensField: max_completion_tokens\n          openRouterRouting:\n            only: [anthropic]\n          vercelGatewayRouting:\n            order: [anthropic, openai]\n          extraBody:\n            gateway: m1-01\n            controller: mlx\n```\n\n### Valeurs `api` autorisées pour les fournisseurs/modèles\n\n- `openai-completions`\n- `openai-responses`\n- `openai-codex-responses`\n- `azure-openai-responses`\n- `anthropic-messages`\n- `google-generative-ai`\n- `google-vertex`\n\n### Valeurs autorisées pour auth/discovery\n\n- `auth` : `apiKey` (par défaut) ou `none`\n- `discovery.type` : `ollama`\n\n## Règles de validation (actuelles)\n\n### Fournisseur personnalisé complet (`models` non vide)\n\nRequis :\n\n- `baseUrl`\n- `apiKey` sauf si `auth: none`\n- `api` au niveau du fournisseur ou pour chaque modèle\n\n### Fournisseur en surcharge uniquement (`models` absent ou vide)\n\nDoit définir au moins l'un des éléments suivants :\n\n- `baseUrl`\n- `modelOverrides`\n- `discovery`\n\n### Découverte\n\n- `discovery` nécessite `api` au niveau du fournisseur.\n\n### Vérifications des valeurs des modèles\n\n- `id` requis\n- `contextWindow` et `maxTokens` doivent être positifs s'ils sont fournis\n\n## Ordre de fusion et de surcharge\n\nPipeline de ModelRegistry (lors du rafraîchissement) :\n\n1. Charger les fournisseurs/modèles intégrés depuis `@f5-sales-demo/pi-ai`.\n2. Charger la configuration personnalisée `models.yml`.\n3. Appliquer les surcharges de fournisseurs (`baseUrl`, `headers`) aux modèles intégrés.\n4. Appliquer les `modelOverrides` (par fournisseur + identifiant de modèle).\n5. Fusionner les `models` personnalisés :\n   - même `provider + id` remplace l'existant\n   - sinon, ajouter à la suite\n6. Appliquer les modèles découverts à l'exécution (actuellement Ollama et LM Studio), puis réappliquer les surcharges de modèles.\n\n## Équivalence canonique des modèles et coalescence\n\nLe registre conserve chaque modèle concret de fournisseur puis construit une couche canonique au-dessus.\n\nLes identifiants canoniques sont uniquement des identifiants officiels en amont, par exemple :\n\n- `claude-opus-4-6`\n- `claude-haiku-4-5`\n- `gpt-5.3-codex`\n\n### Configuration d'équivalence dans `models.yml`\n\nExemple :\n\n```yaml\nproviders:\n  zenmux:\n    baseUrl: https://api.zenmux.example/v1\n    apiKey: ZENMUX_API_KEY\n    api: openai-codex-responses\n    models:\n      - id: codex\n        name: Zenmux Codex\n        reasoning: true\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 200000\n        maxTokens: 32768\n\nequivalence:\n  overrides:\n    zenmux/codex: gpt-5.3-codex\n    p-codex/codex: gpt-5.3-codex\n  exclude:\n    - demo/codex-preview\n```\n\nOrdre de construction pour le regroupement canonique :\n\n1. surcharge utilisateur exacte depuis `equivalence.overrides`\n2. correspondances d'identifiants officiels groupés depuis les métadonnées des modèles intégrés\n3. normalisation heuristique conservatrice pour les variantes de passerelle/fournisseur\n4. repli sur l'identifiant propre du modèle concret\n\nLes heuristiques actuelles sont intentionnellement restrictives :\n\n- les préfixes amont intégrés peuvent être supprimés lorsqu'ils sont présents, par exemple `anthropic/...` ou `openai/...`\n- les variantes de version avec points et tirets ne peuvent être normalisées que lorsqu'elles correspondent à un identifiant officiel existant, par exemple `4.6 -> 4-6`\n- les familles ou versions ambiguës ne sont pas fusionnées sans correspondance groupée ou surcharge explicite\n\n### Comportement de la résolution canonique\n\nLorsque plusieurs variantes concrètes partagent un identifiant canonique, la résolution utilise :\n\n1. disponibilité et authentification\n2. `modelProviderOrder` dans `config.yml`\n3. ordre existant du registre/fournisseur si `modelProviderOrder` n'est pas défini\n\nLes fournisseurs désactivés ou non authentifiés sont ignorés.\n\nL'état de session et les transcriptions continuent d'enregistrer le fournisseur/modèle concret qui a réellement exécuté le tour.\n\nValeurs par défaut du fournisseur vs surcharges par modèle :\n\n- Les `headers` du fournisseur constituent la base.\n- Les `headers` du modèle surchargent les clés d'en-tête du fournisseur.\n- Les `modelOverrides` peuvent surcharger les métadonnées du modèle (`name`, `reasoning`, `input`, `cost`, `contextWindow`, `maxTokens`, `headers`, `compat`, `contextPromotionTarget`).\n- `compat` est fusionné en profondeur pour les blocs de routage imbriqués (`openRouterRouting`, `vercelGatewayRouting`, `extraBody`).\n\n## Intégration de la découverte à l'exécution\n\n### Découverte implicite d'Ollama\n\nSi `ollama` n'est pas explicitement configuré, le registre ajoute un fournisseur découvrable implicite :\n\n- fournisseur : `ollama`\n- api : `openai-completions`\n- URL de base : `OLLAMA_BASE_URL` ou `http://127.0.0.1:11434`\n- mode d'authentification : sans clé (comportement `auth: none`)\n\nLa découverte à l'exécution appelle `GET /api/tags` sur Ollama et synthétise des entrées de modèle avec les valeurs par défaut locales.\n\n### Découverte implicite de llama.cpp\n\nSi `llama.cpp` n'est pas explicitement configuré, le registre ajoute un fournisseur découvrable implicite :\nNote : il utilise la nouvelle API anthropic messages au lieu de openai-completions.\n\n- fournisseur : `llama.cpp`\n- api : `openai-responses`\n- URL de base : `LLAMA_CPP_BASE_URL` ou `http://127.0.0.1:8080`\n- mode d'authentification : sans clé (comportement `auth: none`)\n\nLa découverte à l'exécution appelle `GET models` sur llama.cpp et synthétise des entrées de modèle avec les valeurs par défaut locales.\n\n### Découverte implicite de LM Studio\n\nSi `lm-studio` n'est pas explicitement configuré, le registre ajoute un fournisseur découvrable implicite :\n\n- fournisseur : `lm-studio`\n- api : `openai-completions`\n- URL de base : `LM_STUDIO_BASE_URL` ou `http://127.0.0.1:1234/v1`\n- mode d'authentification : sans clé (comportement `auth: none`)\n\nLa découverte à l'exécution récupère les modèles (`GET /models`) et synthétise des entrées de modèle avec les valeurs par défaut locales.\n\n### Découverte explicite de fournisseur\n\nVous pouvez configurer la découverte vous-même :\n\n```yaml\nproviders:\n  ollama:\n    baseUrl: http://127.0.0.1:11434\n    api: openai-completions\n    auth: none\n    discovery:\n      type: ollama\n      \n  llama.cpp:\n    baseUrl: http://127.0.0.1:8080\n    api: openai-responses\n    auth: none\n    discovery:\n      type: llama.cpp\n```\n\n### Enregistrement de fournisseur par extension\n\nLes extensions peuvent enregistrer des fournisseurs à l'exécution (`pi.registerProvider(...)`), incluant :\n\n- remplacement/ajout de modèle pour un fournisseur\n- enregistrement de gestionnaire de flux personnalisé pour de nouveaux identifiants d'API\n- enregistrement de fournisseur OAuth personnalisé\n\n## Ordre de résolution de l'authentification et des clés API\n\nLors de la demande d'une clé pour un fournisseur, l'ordre effectif est :\n\n1. Surcharge à l'exécution (CLI `--api-key`)\n2. Identifiant de clé API stocké dans `agent.db`\n3. Identifiant OAuth stocké dans `agent.db` (avec rafraîchissement)\n4. Correspondance de variable d'environnement (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc.)\n5. Résolveur de repli de ModelRegistry (`apiKey` du fournisseur depuis `models.yml`, sémantique nom-d'env-ou-littéral)\n\nComportement de `apiKey` dans `models.yml` :\n\n- La valeur est d'abord traitée comme un nom de variable d'environnement.\n- Si aucune variable d'environnement n'existe, la chaîne littérale est utilisée comme jeton.\n\nSi `authHeader: true` et que `apiKey` du fournisseur est défini, les modèles reçoivent :\n\n- Un en-tête `Authorization: Bearer <clé-résolue>` injecté.\n\nFournisseurs sans clé :\n\n- Les fournisseurs marqués `auth: none` sont considérés comme disponibles sans identifiants.\n- `getApiKey*` retourne `kNoAuth` pour ceux-ci.\n\n## Disponibilité des modèles vs tous les modèles\n\n- `getAll()` retourne le registre de modèles chargé (intégrés + personnalisés fusionnés + découverts).\n- `getAvailable()` filtre pour ne garder que les modèles sans clé ou avec une authentification résolvable.\n\nAinsi, un modèle peut exister dans le registre mais ne pas être sélectionnable tant que l'authentification n'est pas disponible.\n\n## Résolution des modèles à l'exécution\n\n### CLI et analyse de patterns\n\n`model-resolver.ts` prend en charge :\n\n- `provider/modelId` exact\n- identifiant canonique de modèle exact\n- identifiant de modèle exact (fournisseur déduit)\n- correspondance floue/par sous-chaîne\n- patterns glob dans `--models` (par ex. `openai/*`, `*sonnet*`)\n- suffixe optionnel `:thinkingLevel` (`off|minimal|low|medium|high|xhigh`)\n\n`--provider` est hérité ; `--model` est préféré.\n\nOrdre de précédence pour les sélecteurs exacts :\n\n1. `provider/modelId` exact contourne la coalescence\n2. l'identifiant canonique exact est résolu via l'index canonique\n3. l'identifiant concret nu exact fonctionne toujours\n4. la correspondance floue et glob s'exécute après les chemins exacts\n\n### Priorité de sélection du modèle initial\n\n`findInitialModel(...)` utilise cet ordre :\n\n1. fournisseur+modèle explicite en CLI\n2. premier modèle dans la portée (si pas en reprise de session)\n3. fournisseur/modèle par défaut sauvegardé\n4. valeurs par défaut de fournisseurs connus (par ex. OpenAI/Anthropic/etc.) parmi les modèles disponibles\n5. premier modèle disponible\n\n### Alias de rôles et paramètres\n\nRôles de modèle pris en charge :\n\n- `default`, `smol`, `slow`, `plan`, `commit`\n\nLes alias de rôle comme `pi/smol` sont développés via `settings.modelRoles`. Chaque valeur de rôle peut également ajouter un sélecteur de réflexion tel que `:minimal`, `:low`, `:medium` ou `:high`.\n\nSi un rôle pointe vers un autre rôle, le modèle cible hérite normalement et tout suffixe explicite sur le rôle référent l'emporte pour cette utilisation spécifique au rôle.\n\nParamètres associés :\n\n- `modelRoles` (enregistrement)\n- `enabledModels` (liste de patterns avec portée)\n- `modelProviderOrder` (précédence globale fournisseur canonique)\n- `providers.kimiApiFormat` (format de requête `openai` ou `anthropic`)\n- `providers.openaiWebsockets` (préférence websocket `auto|off|on` pour le transport OpenAI Codex)\n\n`modelRoles` peut stocker soit :\n\n- `provider/modelId` pour épingler une variante de fournisseur concrète\n- un identifiant canonique tel que `gpt-5.3-codex` pour permettre la coalescence des fournisseurs\n\nPour `enabledModels` et `--models` en CLI :\n\n- les identifiants canoniques exacts sont développés vers toutes les variantes concrètes de ce groupe canonique\n- les entrées explicites `provider/modelId` restent exactes\n- les globs et correspondances floues opèrent toujours sur les modèles concrets\n\n## `/model` et `--list-models`\n\nLes deux interfaces gardent les modèles préfixés par le fournisseur visibles et sélectionnables.\n\nElles exposent désormais également les modèles canoniques/coalescés :\n\n- `/model` inclut une vue canonique aux côtés des onglets par fournisseur\n- `--list-models` affiche une section canonique plus les lignes concrètes par fournisseur\n\nSélectionner une entrée canonique stocke le sélecteur canonique. Sélectionner une ligne de fournisseur stocke le `provider/modelId` explicite.\n\n## Promotion de contexte (chaînes de repli au niveau du modèle)\n\nLa promotion de contexte est un mécanisme de récupération de dépassement pour les variantes à petit contexte (par exemple `*-spark`) qui promeut automatiquement vers un modèle frère à plus grand contexte lorsque l'API rejette une requête avec une erreur de longueur de contexte.\n\n### Déclenchement et ordre\n\nLorsqu'un tour échoue avec une erreur de dépassement de contexte (par ex. `context_length_exceeded`), `AgentSession` tente la promotion **avant** de recourir à la compaction :\n\n1. Si `contextPromotion.enabled` est vrai, résoudre une cible de promotion (voir ci-dessous).\n2. Si une cible est trouvée, basculer vers elle et réessayer la requête — aucune compaction nécessaire.\n3. Si aucune cible n'est disponible, passer à la compaction automatique sur le modèle actuel.\n\n### Sélection de la cible\n\nLa sélection est pilotée par le modèle, pas par le rôle :\n\n1. `currentModel.contextPromotionTarget` (si configuré)\n2. plus petit modèle à plus grand contexte sur le même fournisseur + API\n\nLes candidats sont ignorés à moins que les identifiants ne soient résolus (`ModelRegistry.getApiKey(...)`).\n\n### Transfert websocket OpenAI Codex\n\nSi le basculement se fait depuis/vers `openai-codex-responses`, la clé d'état du fournisseur de session `openai-codex-responses` est fermée avant le changement de modèle. Cela supprime l'état du transport websocket afin que le prochain tour démarre proprement sur le modèle promu.\n\n### Comportement de persistance\n\nLa promotion utilise un basculement temporaire (`setModelTemporary`) :\n\n- enregistré comme un `model_change` temporaire dans l'historique de session\n- ne réécrit pas la correspondance de rôle sauvegardée\n\n### Configuration de chaînes de repli explicites\n\nConfigurez le repli directement dans les métadonnées du modèle via `contextPromotionTarget`.\n\n`contextPromotionTarget` accepte soit :\n\n- `provider/model-id` (explicite)\n- `model-id` (résolu au sein du fournisseur actuel)\n\nExemple (`models.yml`) pour Spark -> non-Spark sur le même fournisseur :\n\n```yaml\nproviders:\n  openai-codex:\n    modelOverrides:\n      gpt-5.3-codex-spark:\n        contextPromotionTarget: openai-codex/gpt-5.3-codex\n```\n\nLe générateur de modèles intégré attribue également ceci automatiquement pour les modèles `*-spark` lorsqu'un modèle de base du même fournisseur existe.\n\n## Champs de compatibilité et de routage\n\n`models.yml` prend en charge ce sous-ensemble `compat` :\n\n- `supportsStore`\n- `supportsDeveloperRole`\n- `supportsReasoningEffort`\n- `maxTokensField` (`max_completion_tokens` ou `max_tokens`)\n- `openRouterRouting.only` / `openRouterRouting.order`\n- `vercelGatewayRouting.only` / `vercelGatewayRouting.order`\n\nCeux-ci sont consommés par la logique de transport OpenAI-completions et combinés avec la détection automatique basée sur l'URL.\n\n## Exemples pratiques\n\n### Point de terminaison local compatible OpenAI (sans authentification)\n\n```yaml\nproviders:\n  local-openai:\n    baseUrl: http://127.0.0.1:8000/v1\n    auth: none\n    api: openai-completions\n    models:\n      - id: Qwen/Qwen2.5-Coder-32B-Instruct\n        name: Qwen 2.5 Coder 32B (local)\n```\n\n### Proxy hébergé avec clé basée sur une variable d'environnement\n\n```yaml\nproviders:\n  anthropic-proxy:\n    baseUrl: https://proxy.example.com/anthropic\n    apiKey: ANTHROPIC_PROXY_API_KEY\n    api: anthropic-messages\n    authHeader: true\n    models:\n      - id: claude-sonnet-4-20250514\n        name: Claude Sonnet 4 (Proxy)\n        reasoning: true\n        input: [text, image]\n```\n\n### Surcharger la route d'un fournisseur intégré + métadonnées du modèle\n\n```yaml\nproviders:\n  openrouter:\n    baseUrl: https://my-proxy.example.com/v1\n    headers:\n      X-Team: platform\n    modelOverrides:\n      anthropic/claude-sonnet-4:\n        name: Sonnet 4 (Corp)\n        compat:\n          openRouterRouting:\n            only: [anthropic]\n```\n\n## Configuration automatique du proxy LiteLLM\n\nLorsque les deux variables d'environnement `LITELLM_BASE_URL` et `LITELLM_API_KEY` sont définies, xcsh gère automatiquement la configuration de `models.yml` pour le proxy LiteLLM.\n\n### Génération automatique au premier lancement\n\nSi `models.yml` n'existe pas et que les variables d'environnement LiteLLM sont détectées, xcsh le génère automatiquement :\n\n```yaml\n# Auto-generated by xcsh for LiteLLM proxy\n# API key resolved from LITELLM_API_KEY env var at runtime\nconfigVersion: 1\nproviders:\n  anthropic:\n    baseUrl: \"https://your-litellm-proxy.example.com/anthropic\"\n    apiKey: LITELLM_API_KEY\n```\n\nUn `config.yml` par défaut est également généré avec des paramètres de fournisseur d'images appropriés.\n\n### Auto-réparation au démarrage\n\nÀ chaque démarrage, `startupHealthCheck()` dans le registre des modèles effectue les vérifications suivantes :\n\n| Condition | Action |\n|-----------|--------|\n| `models.yml` absent | Génération automatique à partir des variables d'environnement |\n| `models.yml` corrompu ou non analysable | Sauvegarde en `.bak`, régénération |\n| `baseUrl` ne correspond pas à `LITELLM_BASE_URL` | Sauvegarde en `.bak`, régénération avec la nouvelle URL |\n| `configVersion` absent ou obsolète | Sauvegarde en `.bak`, régénération avec la version actuelle |\n| Configuration saine | Aucune action |\n\nToutes les réparations créent des sauvegardes `.bak` avant l'écrasement. Toutes les opérations sont idempotentes.\n\n### Commande CLI\n\n```bash\nxcsh setup litellm              # Generate or fix LiteLLM config\nxcsh setup litellm --check      # Validate without writing\nxcsh setup litellm --check --json  # Machine-readable validation output\n```\n\n### Variables d'environnement requises\n\n| Variable | Objectif |\n|----------|---------|\n| `LITELLM_BASE_URL` | URL du proxy LiteLLM (par ex. `https://your-proxy.example.com`). Doit commencer par `http://` ou `https://`. |\n| `LITELLM_API_KEY` | Clé API pour le proxy. Référencée par nom dans la configuration générée, résolue à l'exécution. |\n\nSi l'une des variables n'est pas définie, la configuration automatique est silencieusement ignorée.\n\n### Versionnement de la configuration\n\nLes configurations générées incluent un champ `configVersion`. Lorsque le format généré change dans les versions futures, xcsh détecte les configurations obsolètes et les met à jour automatiquement (avec sauvegarde).\n\n## Avertissement concernant les consommateurs hérités\n\nLa plupart de la configuration des modèles passe maintenant par `models.yml` via `ModelRegistry`.\n\nUn chemin hérité notable subsiste : la résolution d'authentification Anthropic pour la recherche web lit toujours `~/.xcsh/agent/models.json` directement dans `src/web/search/auth.ts`.\n\nSi vous dépendez de ce chemin spécifique, gardez la compatibilité JSON à l'esprit jusqu'à ce que ce module soit migré.\n\n## Mode d'échec\n\nSi `models.yml` échoue aux vérifications de schéma ou de validation :\n\n- Si `LITELLM_BASE_URL` et `LITELLM_API_KEY` sont définies, la vérification de santé au démarrage tente une auto-réparation (sauvegarde du fichier corrompu, régénération à partir des variables d'environnement). Si la réparation réussit, le registre recharge la configuration corrigée.\n- Si l'auto-réparation n'est pas possible (variables d'environnement non définies, échec d'écriture), le registre continue de fonctionner avec les modèles intégrés.\n- L'erreur est exposée via `ModelRegistry.getError()` et affichée dans l'interface/les notifications.\n",
	"fr/providers/provider-streaming-internals.md": "---\ntitle: Fonctionnement interne du streaming des fournisseurs\ndescription: >-\n  Implémentation du streaming des fournisseurs avec analyse SSE, comptage de\n  tokens et gestion de la contre-pression.\nsidebar:\n  order: 2\n  label: Fonctionnement interne du streaming\ni18n:\n  sourceHash: a32ffa769c4d\n  translator: machine\n---\n\n# Fonctionnement interne du streaming des fournisseurs\n\nCe document explique comment le streaming de tokens/outils est normalisé dans `@f5-sales-demo/pi-ai`, puis propagé à travers les événements de session `@f5-sales-demo/pi-agent-core` et `coding-agent`.\n\n## Flux de bout en bout\n\n1. `streamSimple()` (`packages/ai/src/stream.ts`) mappe les options génériques et les distribue vers une fonction de flux de fournisseur.\n2. Les fonctions de flux de fournisseur (`anthropic.ts`, `openai-responses.ts`, `google.ts`) traduisent les événements de flux natifs du fournisseur en séquence `AssistantMessageEvent` unifiée.\n3. Chaque fournisseur pousse des événements dans `AssistantMessageEventStream` (`packages/ai/src/utils/event-stream.ts`), qui régule les événements delta et expose :\n   - l'itération asynchrone pour les mises à jour incrémentielles\n   - `result()` pour le `AssistantMessage` final\n4. `agentLoop` (`packages/agent/src/agent-loop.ts`) consomme ces événements, fait muter l'état de l'assistant en cours et émet des événements `message_update` portant le `assistantMessageEvent` brut.\n5. `AgentSession` (`packages/coding-agent/src/session/agent-session.ts`) s'abonne aux événements de l'agent, persiste les messages, pilote les hooks d'extension et applique les comportements de session (nouvelle tentative, compaction, TTSR, vérifications d'abandon de modification en streaming).\n\n## Contrat de flux unifié dans `@f5-sales-demo/pi-ai`\n\nTous les fournisseurs émettent la même forme (`AssistantMessageEvent` dans `packages/ai/src/types.ts`) :\n\n- `start`\n- triplets de cycle de vie de blocs de contenu :\n  - texte : `text_start` → `text_delta`* → `text_end`\n  - réflexion : `thinking_start` → `thinking_delta`* → `thinking_end`\n  - appel d'outil : `toolcall_start` → `toolcall_delta`* → `toolcall_end`\n- événement terminal :\n  - `done` avec `reason: \"stop\" | \"length\" | \"toolUse\"`\n  - ou `error` avec `reason: \"aborted\" | \"error\"`\n\n`AssistantMessageEventStream` garantit :\n\n- le résultat final est résolu par l'événement terminal (`done` ou `error`)\n- les deltas sont regroupés/régulés (~50 ms)\n- les deltas mis en tampon sont vidés avant les événements non-delta et avant la complétion\n\n## Comportement de régulation et d'harmonisation des deltas\n\n`AssistantMessageEventStream` traite `text_delta`, `thinking_delta` et `toolcall_delta` comme des événements fusionnables :\n\n- les deltas mis en tampon ne sont fusionnés que lorsque **type + contentIndex** correspondent\n- la fusion conserve le dernier instantané `partial`\n- les événements non-delta forcent un vidage immédiat\n\nCela lisse les flux de fournisseur à haute fréquence pour les consommateurs TUI/événements, mais ne constitue pas une contre-pression du fournisseur : les fournisseurs produisent toujours à pleine vitesse, tandis que le flux local met en tampon.\n\n## Détails de normalisation par fournisseur\n\n## Anthropic (`anthropic-messages`)\n\nSource : `packages/ai/src/providers/anthropic.ts`\n\nPoints de normalisation :\n\n- `message_start` initialise l'utilisation (tokens d'entrée/sortie/cache)\n- `content_block_start` correspond aux débuts de texte/réflexion/appel d'outil\n- `content_block_delta` mappe :\n  - `text_delta` → `text_delta`\n  - `thinking_delta` → `thinking_delta`\n  - `input_json_delta` → `toolcall_delta`\n  - `signature_delta` met à jour `thinkingSignature` uniquement (pas d'événement)\n- `content_block_stop` émet le `*_end` correspondant\n- `message_delta.stop_reason` est mappé via `mapStopReason()`\n\nStreaming des arguments d'appel d'outil :\n\n- chaque bloc d'outil porte un `partialJson` interne\n- chaque delta JSON s'ajoute à `partialJson`\n- les `arguments` sont réanalysés à chaque delta via `parseStreamingJson()`\n- `toolcall_end` réanalyse une dernière fois, puis supprime `partialJson`\n\n## Réponses OpenAI (`openai-responses`)\n\nSource : `packages/ai/src/providers/openai-responses.ts`\n\nPoints de normalisation :\n\n- `response.output_item.added` démarre les blocs de raisonnement/texte/appel de fonction\n- les événements de résumé de raisonnement (`response.reasoning_summary_text.delta`) deviennent `thinking_delta`\n- les deltas de sortie/refus deviennent `text_delta`\n- `response.function_call_arguments.delta` devient `toolcall_delta`\n- `response.output_item.done` émet `thinking_end` / `text_end` / `toolcall_end`\n- `response.completed` mappe le statut en raison d'arrêt et l'utilisation\n\nStreaming des arguments d'appel d'outil :\n\n- même schéma d'accumulation `partialJson` qu'Anthropic\n- les fournisseurs qui n'envoient que `response.function_call_arguments.done` alimentent quand même les arguments finaux\n- les identifiants d'appel d'outil sont normalisés sous la forme `\"<call_id>|<item_id>\"`\n\n## Google Generative AI (`google-generative-ai`)\n\nSource : `packages/ai/src/providers/google.ts`\n\nPoints de normalisation :\n\n- itère sur `candidate.content.parts`\n- les parties texte sont réparties en réflexion ou texte par `isThinkingPart(part)`\n- les transitions de bloc ferment le bloc précédent avant d'en démarrer un nouveau\n- `part.functionCall` est traité comme un appel d'outil complet (start/delta/end émis immédiatement)\n- la raison de fin est mappée par `mapStopReason()` depuis `google-shared.ts`\n\nStreaming des arguments d'appel d'outil :\n\n- les arguments d'appel de fonction arrivent sous forme d'objet structuré, et non de texte JSON incrémentiel\n- l'implémentation émet un `toolcall_delta` synthétique contenant `JSON.stringify(arguments)`\n- aucun analyseur JSON partiel n'est nécessaire pour Google dans ce chemin\n\n## Accumulation et récupération du JSON partiel des appels d'outil\n\nLe comportement partagé pour Anthropic/OpenAI Responses utilise `parseStreamingJson()` (`packages/ai/src/utils/json-parse.ts`) :\n\n1. essayer `JSON.parse`\n2. recours à l'analyseur `partial-json` pour les fragments incomplets\n3. si les deux échouent, retourner `{}`\n\nImplications :\n\n- les deltas d'arguments mal formés ou tronqués ne font pas immédiatement planter le traitement du flux\n- les `arguments` en cours peuvent temporairement être `{}`\n- les deltas valides ultérieurs peuvent récupérer les arguments structurés car l'analyse est réessayée à chaque ajout\n- le `toolcall_end` final effectue une dernière tentative d'analyse avant l'émission\n\n## Raisons d'arrêt versus erreurs de transport/exécution\n\nLes raisons d'arrêt du fournisseur sont mappées vers un `stopReason` normalisé :\n\n- Anthropic : `end_turn`→`stop`, `max_tokens`→`length`, `tool_use`→`toolUse`, cas de sécurité/refus→`error`\n- Réponses OpenAI : `completed`→`stop`, `incomplete`→`length`, `failed/cancelled`→`error`\n- Google : `STOP`→`stop`, `MAX_TOKENS`→`length`, classes de sécurité/interdit/appel de fonction mal formé→`error`\n\nLa sémantique des erreurs est divisée en deux étapes :\n\n1. **Sémantique de complétion du modèle** (raison de fin/statut rapporté par le fournisseur)\n2. **Échec de transport/exécution** (exceptions réseau/client/analyseur/abandon)\n\nSi le flux du fournisseur génère une exception ou signale un échec, chaque enveloppe de fournisseur intercepte et émet un événement `error` terminal avec :\n\n- `stopReason = \"aborted\"` lorsque le signal d'abandon est activé\n- sinon `stopReason = \"error\"`\n- `errorMessage = formatErrorMessageWithRetryAfter(error)`\n\n## Comportement en cas de chunk mal formé / échec d'analyse SSE\n\nPour ces chemins de fournisseur, le cadrage chunk/SSE est géré par les flux du SDK vendeur (SDK Anthropic, SDK OpenAI, SDK Google). Ce code n'implémente pas ici de décodeur SSE personnalisé.\n\nComportement observé dans l'implémentation actuelle :\n\n- l'analyse de chunk/SSE mal formé au niveau du SDK se manifeste sous forme d'exception ou d'événement `error` de flux\n- l'enveloppe du fournisseur convertit cela en événement `error` terminal unifié\n- aucune reprise/nouvelle tentative spécifique au fournisseur à l'intérieur de la fonction de flux elle-même\n- les nouvelles tentatives de niveau supérieur sont gérées dans la logique de nouvelle tentative automatique de `AgentSession` (nouvelle tentative au niveau du message, pas de rejeu de chunk de flux)\n\n## Limites d'annulation\n\nL'annulation est structurée en couches :\n\n- Requête du fournisseur IA : `options.signal` est transmis à l'appel de flux du client fournisseur.\n- Enveloppe du fournisseur : après la boucle de flux, un signal abandonné force le chemin d'erreur (`\"Request was aborted\"`).\n- Boucle d'agent : vérifie `signal.aborted` avant de traiter chaque événement du fournisseur et peut synthétiser un message d'assistant abandonné à partir du dernier partiel.\n- Contrôles de session/agent : `AgentSession.abort()` -> `agent.abort()` -> annulation du contrôleur d'abandon partagé.\n\nL'annulation de l'exécution des outils est distincte de l'annulation du flux du modèle :\n\n- les exécuteurs d'outils utilisent `AbortSignal.any([agentSignal, steeringAbortSignal])`\n- les interruptions de pilotage peuvent abandonner l'exécution des outils restants tout en préservant les résultats d'outils déjà produits\n\n## Limites de contre-pression\n\nIl n'existe pas de mécanisme de contre-pression strict entre le flux du SDK fournisseur et les consommateurs en aval :\n\n- `EventStream` utilise des files d'attente en mémoire sans taille maximale\n- la régulation réduit le taux de mise à jour de l'interface utilisateur mais ne ralentit pas l'absorption du fournisseur\n- si les consommateurs prennent du retard de manière significative, les événements mis en file d'attente peuvent croître jusqu'à la complétion\n\nLa conception actuelle privilégie la réactivité et un ordonnancement simple plutôt qu'un contrôle de flux à tampon borné.\n\n## Comment les événements de flux remontent en tant qu'événements agent/session\n\n`agentLoop.streamAssistantResponse()` fait le pont entre `AssistantMessageEvent` et `AgentEvent` :\n\n- sur `start` : pousse un message d'assistant fictif et émet `message_start`\n- sur les événements de bloc (`text_*`, `thinking_*`, `toolcall_*`) : met à jour le dernier message d'assistant, émet `message_update` avec le `assistantMessageEvent` brut\n- sur le terminal (`done`/`error`) : résout le message final depuis `response.result()`, émet `message_end`\n\n`AgentSession` consomme ensuite ces événements pour les comportements au niveau de la session :\n\n- TTSR surveille `message_update.assistantMessageEvent` pour `text_delta` et `toolcall_delta`\n- le garde de modification en streaming inspecte `toolcall_delta`/`toolcall_end` sur les appels `edit` et peut abandonner prématurément\n- la persistance écrit les messages finalisés à `message_end`\n- la nouvelle tentative automatique examine `stopReason === \"error\"` de l'assistant ainsi que les heuristiques de `errorMessage`\n\n## Responsabilités unifiées versus spécifiques au fournisseur\n\nUnifiées (contrat commun) :\n\n- forme des événements (`AssistantMessageEvent`)\n- extraction du résultat final (`done`/`error`)\n- règles de régulation et de fusion des deltas\n- modèle de propagation des événements agent/session\n\nSpécifiques au fournisseur (non entièrement abstraites) :\n\n- taxonomies d'événements en amont et logique de mappage\n- tables de traduction des raisons d'arrêt\n- conventions d'identifiants d'appel d'outil\n- sémantique des blocs de raisonnement/réflexion et signatures\n- sémantique des tokens d'utilisation et calendrier de disponibilité\n- contraintes de conversion de messages par API\n\n## Fichiers d'implémentation\n\n- [`../../ai/src/stream.ts`](../../packages/ai/src/stream.ts) — distribution des fournisseurs, mappage des options, plomberie des clés API/sessions.\n- [`../../ai/src/utils/event-stream.ts`](../../packages/ai/src/utils/event-stream.ts) — file d'attente de flux générique + régulation des deltas de l'assistant.\n- [`../../ai/src/utils/json-parse.ts`](../../packages/ai/src/utils/json-parse.ts) — analyse JSON partielle pour les arguments d'outil en streaming.\n- [`../../ai/src/providers/anthropic.ts`](../../packages/ai/src/providers/anthropic.ts) — traduction des événements Anthropic et accumulation des deltas JSON d'outil.\n- [`../../ai/src/providers/openai-responses.ts`](../../packages/ai/src/providers/openai-responses.ts) — traduction des événements Réponses OpenAI et mappage des statuts.\n- [`../../ai/src/providers/google.ts`](../../packages/ai/src/providers/google.ts) — traduction chunk-vers-bloc du flux Gemini.\n- [`../../ai/src/providers/google-shared.ts`](../../packages/ai/src/providers/google-shared.ts) — mappage des raisons de fin Gemini et règles de conversion partagées.\n- [`../../agent/src/agent-loop.ts`](../../packages/agent/src/agent-loop.ts) — consommation du flux du fournisseur et pont `message_update`.\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — gestion au niveau de la session des mises à jour en streaming, de l'abandon, des nouvelles tentatives et de la persistance.\n",
	"fr/providers/python-repl.md": "---\ntitle: Outil Python et Runtime IPython\ndescription: >-\n  Runtime de l'outil Python REPL avec gestion du noyau IPython, exécution et\n  capture des sorties.\nsidebar:\n  order: 3\n  label: Python & IPython\ni18n:\n  sourceHash: 70f0a034ecef\n  translator: machine\n---\n\n# Outil Python et Runtime IPython\n\nCe document décrit la pile d'exécution Python actuelle dans `packages/coding-agent`.\nIl couvre le comportement de l'outil, le cycle de vie du noyau/passerelle, la gestion de l'environnement, la sémantique d'exécution, le rendu des sorties et les modes de défaillance opérationnels.\n\n## Périmètre et fichiers clés\n\n- Surface de l'outil : `src/tools/python.ts`\n- Orchestration du noyau par session/appel : `src/ipy/executor.ts`\n- Protocole du noyau + intégration de la passerelle : `src/ipy/kernel.ts`\n- Coordinateur de passerelle locale partagée : `src/ipy/gateway-coordinator.ts`\n- Renderer en mode interactif pour les exécutions Python déclenchées par l'utilisateur : `src/modes/components/python-execution.ts`\n- Filtrage du runtime/environnement et résolution Python : `src/ipy/runtime.ts`\n\n## Description de l'outil Python\n\nL'outil `python` exécute une ou plusieurs cellules Python via un noyau adossé à un Jupyter Kernel Gateway (et non en lançant `python -c` directement par cellule).\n\nParamètres de l'outil :\n\n```ts\n{\n  cells: Array<{ code: string; title?: string }>;\n  timeout?: number; // secondes, limité à 1..600, défaut 30\n  cwd?: string;\n  reset?: boolean; // réinitialise le noyau avant la première cellule uniquement\n}\n```\n\nL'outil est `concurrency = \"exclusive\"` pour une session, les appels ne se chevauchent donc pas.\n\n## Cycle de vie de la passerelle\n\n### Modes\n\nIl existe deux chemins de passerelle :\n\n1. **Passerelle externe** (`PI_PYTHON_GATEWAY_URL` défini)\n   - Utilise directement l'URL configurée.\n   - Authentification optionnelle avec `PI_PYTHON_GATEWAY_TOKEN`.\n   - Aucun processus de passerelle local n'est lancé ni géré.\n\n2. **Passerelle locale partagée** (chemin par défaut)\n   - Utilise un processus partagé unique coordonné sous `~/.xcsh/agent/python-gateway`.\n   - Fichier de métadonnées : `gateway.json`\n   - Fichier de verrouillage : `gateway.lock`\n   - Commande de lancement :\n     - `python -m kernel_gateway`\n     - liée à `127.0.0.1:<port-alloué>`\n     - vérification de démarrage : `GET /api/kernelspecs`\n\n### Coordination de la passerelle locale partagée\n\n`acquireSharedGateway()` :\n\n- Acquiert un verrou de fichier (`gateway.lock`) avec battement de cœur.\n- Réutilise `gateway.json` si le PID est actif et que la vérification de santé réussit.\n- Nettoie les informations/PID périmés si nécessaire.\n- Démarre une nouvelle passerelle si aucune passerelle saine n'existe.\n\n`releaseSharedGateway()` est actuellement une opération nulle (l'arrêt du noyau ne démonte pas la passerelle partagée).\n\n`shutdownSharedGateway()` termine explicitement le processus partagé et efface les métadonnées de la passerelle.\n\n### Contrainte importante\n\n`python.sharedGateway=false` est rejeté au démarrage du noyau :\n\n- Erreur : `Shared Python gateway required; local gateways are disabled`\n- Il n'existe pas de mode de passerelle locale non partagée par processus.\n\n## Cycle de vie du noyau\n\nChaque exécution utilise un noyau créé via `POST /api/kernels` sur la passerelle sélectionnée.\n\nSéquence de démarrage du noyau :\n\n1. Vérification de disponibilité (`checkPythonKernelAvailability`)\n2. Création du noyau (`/api/kernels`)\n3. Ouverture du websocket (`/api/kernels/:id/channels`)\n4. Initialisation de l'environnement du noyau (`cwd`, variables d'environnement, `sys.path`)\n5. Exécution de `PYTHON_PRELUDE`\n6. Chargement des modules d'extension depuis :\n   - utilisateur : `~/.xcsh/agent/modules/*.py`\n   - projet : `<cwd>/.xcsh/modules/*.py` (remplace le module utilisateur de même nom)\n\nArrêt du noyau :\n\n- Supprime le noyau distant via `DELETE /api/kernels/:id`\n- Ferme le websocket\n- Appelle le hook de libération de la passerelle partagée (opération nulle actuellement)\n\n## Sémantique de persistance de session\n\n`python.kernelMode` contrôle la réutilisation du noyau :\n\n- `session` (par défaut)\n  - Réutilise les sessions de noyau indexées par identité de session + cwd.\n  - L'exécution est sérialisée par session via une file d'attente.\n  - Les sessions inactives sont évincées après 5 minutes.\n  - Maximum 4 sessions ; la plus ancienne est évincée en cas de dépassement.\n  - Les vérifications de battement de cœur détectent les noyaux défaillants.\n  - Un redémarrage automatique est autorisé une fois ; un crash répété entraîne un échec définitif.\n\n- `per-call`\n  - Crée un nouveau noyau pour chaque requête d'exécution.\n  - Arrête le noyau après la requête.\n  - Aucune persistance d'état entre les appels.\n\n### Comportement multi-cellules lors d'un seul appel d'outil\n\nLes cellules s'exécutent séquentiellement dans la même instance de noyau pour cet appel d'outil.\n\nEn cas d'échec d'une cellule intermédiaire :\n\n- L'état des cellules précédentes reste en mémoire.\n- L'outil retourne une erreur ciblée indiquant quelle cellule a échoué.\n- Les cellules suivantes ne sont pas exécutées.\n\n`reset=true` s'applique uniquement à la première exécution de cellule dans cet appel.\n\n## Filtrage de l'environnement et résolution du runtime\n\nL'environnement est filtré avant le lancement du runtime passerelle/noyau :\n\n- La liste d'autorisation inclut les variables essentielles telles que `PATH`, `HOME`, les variables de locale, `VIRTUAL_ENV`, `PYTHONPATH`, etc.\n- Préfixes autorisés : `LC_`, `XDG_`, `PI_`\n- La liste de blocage supprime les clés API courantes (OpenAI/Anthropic/Gemini/etc.)\n\nOrdre de sélection du runtime :\n\n1. Venv actif/localisé (`VIRTUAL_ENV`, puis `<cwd>/.venv`, `<cwd>/venv`)\n2. Venv géré dans `~/.xcsh/python-env`\n3. `python` ou `python3` dans le PATH\n\nLorsqu'un venv est sélectionné, son chemin bin/Scripts est ajouté en tête du `PATH`.\n\nL'initialisation de l'environnement du noyau dans Python effectue également :\n\n- `os.chdir(cwd)`\n- injection de la carte d'environnement fournie dans `os.environ`\n- s'assure que cwd est dans `sys.path`\n\n## Disponibilité de l'outil et sélection du mode\n\n`python.toolMode` (par défaut `both`) + le remplacement optionnel `PI_PY` contrôle l'exposition :\n\n- `ipy-only`\n- `bash-only`\n- `both`\n\nValeurs acceptées pour `PI_PY` :\n\n- `0` / `bash` -> `bash-only`\n- `1` / `py` -> `ipy-only`\n- `mix` / `both` -> `both`\n\nEn cas d'échec du prévol Python, la création de l'outil se dégrade en bash-only pour cette session.\n\n## Flux d'exécution et annulation/timeout\n\n### Timeout au niveau de l'outil\n\nLe timeout de l'outil `python` est exprimé en secondes, par défaut 30, limité à `1..600`.\n\nL'outil combine :\n\n- le signal d'abandon de l'appelant\n- le signal d'abandon du timeout\n\navec `AbortSignal.any(...)`.\n\n### Annulation de l'exécution du noyau\n\nEn cas d'abandon/timeout :\n\n- L'exécution est marquée comme annulée.\n- Une interruption du noyau est tentée via REST (`POST /interrupt`) et le canal de contrôle `interrupt_request`.\n- Le résultat inclut `cancelled=true`.\n- Le chemin de timeout annote la sortie avec `Command timed out after <n> seconds`.\n\n### Comportement de stdin\n\nLe stdin interactif n'est pas pris en charge.\n\nSi le noyau émet une `input_request` :\n\n- L'outil enregistre `stdinRequested=true`\n- Émet un texte explicatif\n- Envoie une `input_reply` vide\n- L'exécution est traitée comme un échec au niveau de l'exécuteur\n\n## Capture et rendu des sorties\n\n### Classes de sorties capturées\n\nDepuis les messages du noyau :\n\n- `stream` -> fragments de texte brut\n- `display_data`/`execute_result` -> gestion de l'affichage enrichi\n- `error` -> texte de traceback\n- MIME personnalisé `application/x-xcsh-status` -> événements de statut structurés\n\nPrécédence MIME pour l'affichage :\n\n1. `text/markdown`\n2. `text/plain`\n3. `text/html` (converti en markdown basique)\n\nÉgalement capturés comme sorties structurées :\n\n- `application/json` -> données d'arbre JSON\n- `image/png` -> charges utiles d'image\n- `application/x-xcsh-status` -> événements de statut\n\n### Stockage et troncature\n\nLa sortie est diffusée via `OutputSink` et peut être persistée dans le stockage d'artefacts.\n\nLes résultats de l'outil peuvent inclure des métadonnées de troncature et `artifact://<id>` pour la récupération de la sortie complète.\n\n### Comportement du renderer\n\n- Renderer de l'outil (`python.ts`) :\n  - affiche des blocs de cellules de code avec le statut par cellule\n  - l'aperçu réduit affiche par défaut 10 lignes\n  - prend en charge le mode étendu pour la sortie complète et un détail de statut plus riche\n- Renderer interactif (`python-execution.ts`) :\n  - utilisé pour les exécutions Python déclenchées par l'utilisateur dans le TUI\n  - l'aperçu réduit affiche par défaut 20 lignes\n  - limite les lignes individuelles très longues à 4000 caractères pour la sécurité d'affichage\n  - affiche les notices d'annulation/erreur/troncature\n\n## Prise en charge de la passerelle externe\n\nDéfinir :\n\n```bash\nexport PI_PYTHON_GATEWAY_URL=\"http://127.0.0.1:8888\"\n# Optionnel :\nexport PI_PYTHON_GATEWAY_TOKEN=\"...\"\n```\n\nDifférences de comportement par rapport à la passerelle locale partagée :\n\n- Pas de fichiers de verrouillage/info de passerelle locale\n- Pas de lancement/arrêt de processus local\n- Les vérifications de santé et les opérations CRUD du noyau s'exécutent contre le point de terminaison externe\n- Les échecs d'authentification sont signalés avec des indications explicites sur le jeton\n\n## Dépannage opérationnel (modes de défaillance actuels)\n\n- **Outil Python non disponible**\n  - Vérifier `python.toolMode` / `PI_PY`.\n  - En cas d'échec du prévol, le runtime bascule en bash-only.\n\n- **Erreurs de disponibilité du noyau**\n  - Le mode local requiert que `kernel_gateway` et `ipykernel` soient importables dans le runtime Python résolu.\n  - Installer avec :\n\n    ```bash\n    python -m pip install jupyter_kernel_gateway ipykernel\n    ```\n\n- **`python.sharedGateway=false` provoque un échec au démarrage**\n  - Ce comportement est attendu avec l'implémentation actuelle.\n\n- **Échecs d'authentification/d'accessibilité de la passerelle externe**\n  - 401/403 -> définir `PI_PYTHON_GATEWAY_TOKEN`.\n  - timeout/inaccessible -> vérifier l'URL/réseau et la santé de la passerelle.\n\n- **L'exécution se bloque puis expire**\n  - Augmenter le `timeout` de l'outil (max 600s) si la charge de travail est légitime.\n  - Pour du code bloqué, l'annulation déclenche une interruption du noyau, mais le code utilisateur peut nécessiter une refactorisation.\n\n- **Invites stdin/input dans le code Python**\n  - `input()` n'est pas pris en charge de manière interactive dans ce chemin de runtime ; transmettre les données de manière programmatique.\n\n- **Épuisement des ressources (`EMFILE` / trop de fichiers ouverts)**\n  - Le gestionnaire de sessions déclenche la récupération de la passerelle partagée (démontage de session + redémarrage de la passerelle partagée).\n\n- **Erreurs de répertoire de travail**\n  - L'outil valide que `cwd` existe et est un répertoire avant l'exécution.\n\n## Variables d'environnement pertinentes\n\n- `PI_PY` — remplacement de l'exposition de l'outil (correspondance `bash-only`/`ipy-only`/`both` ci-dessus)\n- `PI_PYTHON_GATEWAY_URL` — utiliser une passerelle externe\n- `PI_PYTHON_GATEWAY_TOKEN` — jeton d'authentification optionnel pour la passerelle externe\n- `PI_PYTHON_SKIP_CHECK=1` — contourner les vérifications de prévol/préchauffage Python\n- `PI_PYTHON_IPC_TRACE=1` — journaliser les traces d'envoi/réception IPC du noyau\n- `PI_DEBUG_STARTUP=1` — émettre des marqueurs de débogage de phase de démarrage\n",
	"fr/runtime-tools/bash-tool-runtime.md": "---\ntitle: Environnement d'exécution de l'outil Bash\ndescription: >-\n  Environnement d'exécution de l'outil Bash avec gestion des processus shell,\n  sandboxing, délai d'expiration et diffusion de la sortie.\nsidebar:\n  order: 1\n  label: Outil Bash\ni18n:\n  sourceHash: 18b12aa5dbd5\n  translator: machine\n---\n\n# Environnement d'exécution de l'outil Bash\n\nCe document décrit le chemin d'exécution de l'**outil `bash`** utilisé par les appels d'outils de l'agent, depuis la normalisation des commandes jusqu'à l'exécution, la troncature/les artefacts et le rendu.\n\nIl indique également où le comportement diverge entre le mode TUI interactif, le mode impression, le mode RPC et l'exécution shell bang (`!`) initiée par l'utilisateur.\n\n## Périmètre et surfaces d'exécution\n\nIl existe deux surfaces d'exécution bash distinctes dans l'agent de codage :\n\n1. **Surface d'appel d'outil** (`toolName: \"bash\"`) : utilisée lorsque le modèle appelle l'outil bash.\n   - Point d'entrée : `BashTool.execute()`.\n2. **Surface de commande bang utilisateur** (`!cmd` depuis une entrée interactive ou la commande RPC `bash`) : chemin d'assistance au niveau de la session.\n   - Point d'entrée : `AgentSession.executeBash()`.\n\nLes deux utilisent finalement `executeBash()` dans `src/exec/bash-executor.ts` pour l'exécution sans PTY, mais seul le chemin d'appel d'outil exécute la logique de normalisation/interception et de rendu de l'outil.\n\n## Pipeline d'appel d'outil de bout en bout\n\n## 1) Normalisation des entrées et fusion des paramètres\n\n`BashTool.execute()` normalise d'abord la commande brute via `normalizeBashCommand()` :\n\n- extrait les `| head -n N`, `| head -N`, `| tail -n N`, `| tail -N` en fin de chaîne sous forme de limites structurées,\n- supprime les espaces blancs en début et en fin de chaîne,\n- conserve intacts les espaces blancs internes.\n\nEnsuite, il fusionne les limites extraites avec les arguments explicites de l'outil :\n\n- les arguments `head`/`tail` explicites remplacent les valeurs extraites,\n- les valeurs extraites ne servent que de valeurs de repli.\n\n### Mise en garde\n\nLes commentaires de `bash-normalize.ts` mentionnent la suppression de `2>&1`, mais l'implémentation actuelle ne le retire pas. Le comportement à l'exécution reste correct (stdout/stderr sont déjà fusionnés), mais le comportement de normalisation est plus restreint que ce que les commentaires suggèrent.\n\n## 2) Interception optionnelle (chemin de commande bloquée)\n\nSi `bashInterceptor.enabled` est vrai, `BashTool` charge les règles depuis les paramètres et exécute `checkBashInterception()` sur la commande normalisée.\n\nComportement de l'interception :\n\n- la commande est bloquée **uniquement** lorsque :\n  - une règle regex correspond, et\n  - l'outil suggéré est présent dans `ctx.toolNames`.\n- les règles regex invalides sont silencieusement ignorées.\n- lors d'un blocage, `BashTool` lève une `ToolError` avec le message :\n  - `Blocked: ...`\n  - commande originale incluse.\n\nLes motifs de règles par défaut (définis dans le code) ciblent les utilisations abusives courantes :\n\n- lecteurs de fichiers (`cat`, `head`, `tail`, ...),\n- outils de recherche (`grep`, `rg`, ...),\n- outils de recherche de fichiers (`find`, `fd`, ...),\n- éditeurs sur place (`sed -i`, `perl -i`, `awk -i inplace`),\n- redirections d'écriture shell (`echo ... > file`, redirection heredoc).\n\n### Mise en garde\n\n`InterceptionResult` inclut `suggestedTool`, mais `BashTool` ne fait actuellement apparaître que le texte du message (aucun champ d'outil suggéré structuré dans `details`).\n\n## 3) Validation du répertoire de travail et limitation du délai d'expiration\n\n`cwd` est résolu relativement au répertoire de travail de la session (`resolveToCwd`), puis validé via `stat` :\n\n- chemin manquant -> `ToolError(\"Working directory does not exist: ...\")`\n- non-répertoire -> `ToolError(\"Working directory is not a directory: ...\")`\n\nLe délai d'expiration est limité à `[1, 3600]` secondes et converti en millisecondes.\n\n## 4) Allocation des artefacts\n\nAvant l'exécution, l'outil alloue un chemin/identifiant d'artefact (au mieux) pour le stockage de la sortie tronquée.\n\n- l'échec d'allocation d'artefact n'est pas fatal (l'exécution continue sans fichier de déversement d'artefact),\n- l'identifiant/chemin de l'artefact est transmis au chemin d'exécution pour la persistance complète de la sortie lors d'une troncature.\n\n## 5) Sélection de l'exécution PTY ou non-PTY\n\n`BashTool` choisit l'exécution PTY uniquement lorsque toutes les conditions suivantes sont vraies :\n\n- `bash.virtualTerminal === \"on\"`\n- `PI_NO_PTY !== \"1\"`\n- le contexte de l'outil dispose d'une interface utilisateur (`ctx.hasUI === true` et `ctx.ui` défini)\n\nSinon, il utilise `executeBash()` non interactif.\n\nCela signifie que le mode impression et les contextes RPC/outils sans interface utilisateur utilisent toujours le mode non-PTY.\n\n## Moteur d'exécution non interactif (`executeBash`)\n\n## Modèle de réutilisation des sessions shell\n\n`executeBash()` met en cache les instances `Shell` natives dans une table de correspondance globale au processus, indexée par :\n\n- chemin du shell,\n- préfixe de commande configuré,\n- chemin du snapshot,\n- environnement shell sérialisé,\n- clé de session d'agent optionnelle.\n\nPour les exécutions au niveau de la session, `AgentSession.executeBash()` transmet `sessionKey: this.sessionId`, isolant la réutilisation par session.\n\nLe chemin d'appel d'outil ne transmet **pas** `sessionKey`, de sorte que la portée de réutilisation est basée sur la configuration du shell/snapshot/env.\n\n## Configuration du shell et comportement des snapshots\n\nÀ chaque appel, l'exécuteur charge la configuration du shell depuis les paramètres (`shell`, `env`, `prefix` optionnel).\n\nSi le shell sélectionné inclut `bash`, il tente `getOrCreateSnapshot()` :\n\n- le snapshot capture les alias/fonctions/options du rc utilisateur,\n- la création du snapshot est au mieux,\n- en cas d'échec, le repli se fait sans snapshot.\n\nSi un `prefix` est configuré, la commande devient :\n\n```text\n<prefix> <command>\n```\n\n## Diffusion et annulation\n\n`Shell.run()` diffuse des fragments vers un rappel. L'exécuteur achemine chaque fragment vers `OutputSink` et le rappel optionnel `onChunk`.\n\nAnnulation :\n\n- un signal d'abandon déclenche `shellSession.abort(...)`,\n- le délai d'expiration issu du résultat natif est mappé vers `cancelled: true` + texte d'annotation,\n- une annulation explicite retourne de même `cancelled: true` + annotation.\n\nAucune exception n'est levée à l'intérieur de l'exécuteur pour les délais d'expiration/annulations ; il retourne un `BashResult` structuré et laisse l'appelant gérer la sémantique des erreurs.\n\n## Chemin PTY interactif (`runInteractiveBashPty`)\n\nLorsque PTY est activé, l'outil exécute `runInteractiveBashPty()` qui ouvre un composant de console en superposition et pilote une `PtySession` native.\n\nPoints saillants du comportement :\n\n- le terminal virtuel xterm-headless restitue le viewport dans la superposition,\n- l'entrée clavier est normalisée (y compris les séquences Kitty et la gestion du mode curseur applicatif),\n- `esc` lors de l'exécution tue la session PTY,\n- le redimensionnement du terminal se propage au PTY (`session.resize(cols, rows)`).\n\nDes valeurs par défaut de renforcement de l'environnement sont injectées pour les exécutions non surveillées :\n\n- paginateurs désactivés (`PAGER=cat`, `GIT_PAGER=cat`, etc.),\n- invites d'éditeur désactivées (`GIT_EDITOR=true`, `EDITOR=true`, ...),\n- invites de terminal/authentification réduites (`GIT_TERMINAL_PROMPT=0`, `SSH_ASKPASS=/usr/bin/false`, `CI=1`),\n- indicateurs d'Automatisation des gestionnaires de paquets/outils pour un comportement non interactif.\n\nLa sortie PTY est normalisée (`CRLF`/`CR` vers `LF`, `sanitizeText`) et écrite dans `OutputSink`, avec prise en charge du déversement d'artefact.\n\nEn cas d'erreur de démarrage/exécution PTY, le collecteur reçoit une ligne `PTY error: ...` et la commande se finalise avec un code de sortie indéfini.\n\n## Gestion de la sortie : diffusion, troncature, déversement d'artefact\n\nLes chemins PTY et non-PTY utilisent tous deux `OutputSink`.\n\n## Sémantique d'OutputSink\n\n- conserve un tampon de fin en mémoire encodé en UTF-8 (`DEFAULT_MAX_BYTES`, actuellement 50 Ko),\n- suit le nombre total d'octets/lignes observés,\n- si un chemin d'artefact existe et que la sortie déborde (ou si le fichier est déjà actif), écrit le flux complet dans le fichier d'artefact,\n- lorsque le seuil mémoire est dépassé, réduit le tampon en mémoire à la fin (avec respect des limites UTF-8),\n- marque `truncated` lorsqu'un débordement/déversement de fichier se produit.\n\n`dump()` retourne :\n\n- `output` (avec éventuel préfixe annoté),\n- `truncated`,\n- `totalLines/totalBytes`,\n- `outputLines/outputBytes`,\n- `artifactId` si un fichier d'artefact était actif.\n\n### Mise en garde sur les longues sorties\n\nLa troncature à l'exécution est basée sur un seuil en octets dans `OutputSink` (50 Ko par défaut). Elle n'impose pas de limite stricte de 2 000 lignes dans ce chemin de code.\n\n## Mises à jour en direct de l'outil\n\nPour l'exécution non-PTY, `BashTool` utilise un `TailBuffer` séparé pour les mises à jour partielles et émet des snapshots `onUpdate` pendant l'exécution de la commande.\n\nPour l'exécution PTY, le rendu en direct est géré par l'interface utilisateur de superposition personnalisée, et non par des fragments textuels `onUpdate`.\n\n## Mise en forme des résultats, métadonnées et mappage des erreurs\n\nAprès l'exécution :\n\n1. Gestion de `cancelled` :\n   - si le signal d'abandon est déclenché -> lève `ToolAbortError` (sémantique d'abandon),\n   - sinon -> lève `ToolError` (traité comme un échec de l'outil).\n2. PTY `timedOut` -> lève `ToolError`.\n3. applique les filtres head/tail au texte de sortie final (`applyHeadTail`, head puis tail).\n4. une sortie vide devient `(no output)`.\n5. attache les métadonnées de troncature via `toolResult(...).truncationFromSummary(result, { direction: \"tail\" })`.\n6. mappage du code de sortie :\n   - code de sortie manquant -> `ToolError(\"... missing exit status\")`\n   - sortie non nulle -> `ToolError(\"... Command exited with code N\")`\n   - sortie nulle -> résultat de succès.\n\nStructure de la charge utile de succès :\n\n- `content` : texte de sortie,\n- `details.meta.truncation` lorsque tronqué, incluant :\n  - `direction`, `truncatedBy`, nombre total/de sortie de lignes et d'octets,\n  - `shownRange`,\n  - `artifactId` lorsque disponible.\n\nÉtant donné que les outils intégrés sont encapsulés avec `wrapToolWithMetaNotice()`, le texte de notification de troncature est automatiquement ajouté au contenu textuel final (par exemple : `Full: artifact://<id>`).\n\n## Chemins de rendu\n\n## Rendu d'appel d'outil (`bashToolRenderer`)\n\n`bashToolRenderer` est utilisé pour les messages d'appel d'outil (`toolCall` / `toolResult`) :\n\n- le mode réduit affiche un aperçu tronqué à la ligne visuelle,\n- le mode développé affiche tout le texte de sortie actuellement disponible,\n- la ligne d'avertissement inclut la raison de la troncature et `artifact://<id>` lorsque tronqué,\n- la valeur de délai d'expiration (issue des arguments) est affichée dans la ligne de métadonnées du pied de page.\n\n### Mise en garde : expansion complète de l'artefact\n\n`BashRenderContext` possède `isFullOutput`, mais le constructeur de contexte de rendu actuel ne le définit pas pour les résultats de l'outil bash. La vue développée utilise toujours le texte déjà présent dans le contenu du résultat (sortie de fin/tronquée), sauf si un autre appelant fournit le contenu complet de l'artefact.\n\n## Composant de commande bang utilisateur (`BashExecutionComponent`)\n\n`BashExecutionComponent` est destiné aux commandes `!` utilisateur en mode interactif (pas aux appels d'outils du modèle) :\n\n- diffuse les fragments en direct,\n- l'aperçu réduit conserve les 20 dernières lignes logiques,\n- limitation à 4 000 caractères par ligne,\n- affiche les avertissements de troncature et d'artefact lorsque les métadonnées sont présentes,\n- marque séparément l'état annulé/erreur/sortie.\n\nCe composant est connecté par `CommandController.handleBashCommand()` et alimenté par `AgentSession.executeBash()`.\n\n## Différences de comportement selon le mode\n\n| Surface                             | Chemin d'entrée                                       | PTY éligible                                                                       | Interface utilisateur de sortie en direct                                                        | Remontée des erreurs                                         |\n| ----------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------ |\n| Appel d'outil interactif            | `BashTool.execute`                                    | Oui, si `bash.virtualTerminal=on`, interface utilisateur existante et `PI_NO_PTY!=1` | Superposition PTY (interactif) ou mises à jour de fin diffusées                                 | Les erreurs d'outil deviennent `toolResult.isError`          |\n| Appel d'outil en mode impression    | `BashTool.execute`                                    | Non (pas de contexte d'interface utilisateur)                                      | Pas de superposition TUI ; la sortie apparaît dans le flux d'événements/flux de texte assistant final | Même mappage d'erreur d'outil                               |\n| Appel d'outil RPC (outillage agent) | `BashTool.execute`                                    | Généralement pas d'interface utilisateur -> non-PTY                                | Événements/résultats d'outil structurés                                                          | Même mappage d'erreur d'outil                               |\n| Commande bang interactive (`!`)     | `AgentSession.executeBash` + `BashExecutionComponent` | Non (utilise directement l'exécuteur)                                              | Composant d'exécution bash dédié                                                                  | Le contrôleur intercepte les exceptions et affiche une erreur d'interface utilisateur |\n| Commande RPC `bash`                 | `rpc-mode` -> `session.executeBash`                   | Non                                                                                | Retourne directement `BashResult`                                                                | Le consommateur gère les champs retournés                    |\n\n## Mises en garde opérationnelles\n\n- L'intercepteur ne bloque les commandes que lorsque l'outil suggéré est actuellement disponible dans le contexte.\n- Si l'allocation d'artefact échoue, la troncature se produit quand même mais aucune référence arrière `artifact://` n'est disponible.\n- Le cache de sessions shell ne dispose d'aucune éviction explicite dans ce module ; la durée de vie est limitée au processus.\n- Les surfaces de délai d'expiration PTY et non-PTY diffèrent :\n  - PTY expose un champ de résultat `timedOut` explicite,\n  - non-PTY mappe le délai d'expiration vers un résumé `cancelled + annotation`.\n\n## Fichiers d'implémentation\n\n- [`src/tools/bash.ts`](../../packages/coding-agent/src/tools/bash.ts) — point d'entrée de l'outil, normalisation/interception, sélection PTY/non-PTY, mappage des résultats/erreurs, rendu de l'outil bash.\n- [`src/tools/bash-normalize.ts`](../../packages/coding-agent/src/tools/bash-normalize.ts) — normalisation des commandes et filtrage head/tail post-exécution.\n- [`src/tools/bash-interceptor.ts`](../../packages/coding-agent/src/tools/bash-interceptor.ts) — correspondance des règles d'interception et messages de commande bloquée.\n- [`src/exec/bash-executor.ts`](../../packages/coding-agent/src/exec/bash-executor.ts) — exécuteur non-PTY, réutilisation des sessions shell, câblage de l'annulation, intégration du collecteur de sortie.\n- [`src/tools/bash-interactive.ts`](../../packages/coding-agent/src/tools/bash-interactive.ts) — environnement d'exécution PTY, interface utilisateur de superposition, normalisation des entrées, valeurs par défaut d'environnement non interactif.\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — troncature/déversement d'artefact `OutputSink` et métadonnées de résumé.\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — assistants d'allocation d'artefacts et tampon de fin en diffusion.\n- [`src/tools/output-meta.ts`](../../packages/coding-agent/src/tools/output-meta.ts) — forme des métadonnées de troncature + encapsuleur d'injection de notification.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — `executeBash` au niveau de la session, enregistrement des messages, cycle de vie de l'abandon.\n- [`src/modes/components/bash-execution.ts`](../../packages/coding-agent/src/modes/components/bash-execution.ts) — composant d'exécution de commande `!` interactive.\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts) — câblage pour la complétion du flux/mise à jour de l'interface utilisateur de commande `!` interactive.\n- [`src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts) — surface de commande RPC `bash` et `abort_bash`.\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — résolution de `artifact://<id>`.\n",
	"fr/runtime-tools/context-command.md": "---\ntitle: Contextes F5 XC\ndescription: >-\n  Connectez xcsh aux tenants F5 Distributed Cloud -- créez, basculez et gérez\n  les contextes d'authentification.\nsidebar:\n  order: 1\n  label: Contextes F5 XC\ni18n:\n  sourceHash: a9cccbc338f0\n  translator: machine\n---\n\n# Contextes F5 XC\n\nxcsh se connecte à F5 Distributed Cloud via des **contextes** -- des jeux d'identifiants nommés qui associent une URL de tenant, un jeton API et un namespace. Si vous avez déjà utilisé `kubectl config use-context` ou `kubectx`, le flux de travail est identique : créez un contexte, basculez entre eux par nom, et utilisez `-` pour revenir au précédent.\n\n## Pour commencer\n\n### 1. Créez votre premier contexte\n\nVous avez besoin de trois éléments depuis votre console F5 XC : l'URL du tenant, un jeton API, et optionnellement un namespace.\n\n```\n/context create production https://acme.console.ves.volterra.io p12k3-your-api-token\n```\n\n```\nContext 'production' created. Use /context activate production to switch to it.\n```\n\nOu utilisez l'assistant guidé si vous préférez des invites étape par étape :\n\n```\n/context wizard\n```\n\n### 2. Activez-le\n\n```\n/context production\n```\n\n```\n╭─ production ─────────────────────────────────────────────────╮\n│ XCSH_TENANT     acme                                         │\n│ XCSH_API_URL    https://acme.console.ves.volterra.io         │\n│ XCSH_API_TOKEN  ...oken                                      │\n│ Status          Connected (312ms)                            │\n├─ Environment ────────────────────────────────────────────────┤\n│ XCSH_NAMESPACE  default                                      │\n╰──────────────────────────────────────────────────────────────╯\n```\n\nUne fois activé, xcsh injecte les identifiants du tenant dans votre session. L'agent peut désormais effectuer des appels à l'API F5 XC, et la barre d'état affiche le contexte actif.\n\n### 3. Ajoutez d'autres contextes et basculez entre eux\n\n```\n/context create staging https://staging.console.ves.volterra.io p12k3-staging-token\n```\n\nBasculez par nom -- aucun verbe de sous-commande nécessaire :\n\n```\n/context staging\n```\n\nRevenez au contexte précédent (style `cd -`) :\n\n```\n/context -\n```\n\nAppeler `/context -` deux fois vous ramène à votre point de départ.\n\n### 4. Voyez ce que vous avez\n\n```\n/context\n```\n\n```\n  production           https://acme.console.ves.volterra.io\n* staging              https://staging.console.ves.volterra.io\n```\n\nLe `*` marque le contexte actif.\n\n## Commandes courantes\n\n| Commande | Ce qu'elle fait |\n|---|---|\n| `/context` | Lister tous les contextes |\n| `/context <name>` | Basculer vers un contexte |\n| `/context -` | Basculer vers le contexte précédent |\n| `/context show` | Afficher les détails du contexte actif (jetons masqués) |\n| `/context status` | Afficher l'état actuel de l'authentification |\n\n## Cycle de vie des contextes\n\n| Commande | Ce qu'elle fait |\n|---|---|\n| `/context create <name> <url> <token> [namespace]` | Créer un contexte |\n| `/context delete <name> --confirm` | Supprimer un contexte (nécessite `--confirm`) |\n| `/context rename <old> <new>` | Renommer un contexte |\n| `/context validate <name>` | Tester les identifiants sans basculer |\n| `/context export [name] [--include-token]` | Exporter en JSON (jetons masqués par défaut) |\n| `/context import <path-or-json> [--overwrite]` | Importer depuis un fichier ou du JSON en ligne |\n| `/context wizard` | Configuration interactive guidée |\n\n## Changer de namespace\n\nChaque contexte possède un namespace par défaut. Changez-le sans modifier le contexte :\n\n```\n/context namespace system\n```\n\nL'autocomplétion par tabulation propose les noms de namespace du tenant actif.\n\n## Variables d'environnement sur les contextes\n\nLes contextes peuvent contenir des variables d'environnement supplémentaires qui sont injectées dans votre session lors de l'activation. Utile pour une configuration propre à chaque tenant qui ne fait pas partie du jeu d'identifiants.\n\n```\n/context set CUSTOM_HEADER=x-acme-trace\n/context set LOG_LEVEL=debug\n/context env list\n/context unset LOG_LEVEL\n```\n\nAlias : `add` = `set`, `remove`/`clear` = `unset`.\n\n## Autocomplétion par tabulation\n\nTapez `/context ` et appuyez sur Tab. Le menu déroulant affiche :\n\n1. **Noms de contextes** -- avec des indications d'URL de tenant, pour distinguer les tenants\n2. **`-`** -- apparaît lorsque vous avez déjà basculé, indique vers quel contexte vous reviendriez\n3. **Sous-commandes** -- `list`, `create`, `delete`, etc.\n\nLes noms de contextes apparaissent en premier car le basculement est l'action la plus courante.\n\nLes complétions au niveau des sous-commandes fonctionnent également : `/context activate <Tab>` complète les noms de contextes, `/context namespace <Tab>` complète les namespaces, `/context unset <Tab>` complète les clés de variables d'environnement connues.\n\n## Règles de nommage\n\nLes noms de contextes doivent comporter entre 1 et 64 caractères : lettres, chiffres, tirets, underscores.\n\nLes noms qui entrent en conflit avec les sous-commandes sont rejetés :\n\n```\n/context create list https://example.com tok\n```\n\n```\nError: Context name 'list' conflicts with a /context subcommand. Choose a different name.\n```\n\nL'ensemble complet des noms réservés : `list`, `show`, `status`, `create`, `delete`, `rename`, `namespace`, `env`, `set`, `unset`, `add`, `remove`, `clear`, `activate`, `validate`, `export`, `import`, `wizard`, `help`. La comparaison est insensible à la casse.\n\n## Substitution par variables d'environnement\n\nSi `XCSH_API_URL` et `XCSH_API_TOKEN` sont définies dans votre environnement shell avant de lancer xcsh, elles prennent le pas sur tout contexte. Ceci est utile pour les pipelines CI/CD ou les sessions ponctuelles où vous ne souhaitez pas créer un contexte persistant.\n\nDans ce mode, `/context` affiche les identifiants issus de l'environnement avec l'étiquette `(via env vars)`.\n\n## Comportement du contexte précédent\n\n- **Portée de session** : le contexte précédent est réinitialisé au redémarrage de xcsh. Il n'est pas persisté sur disque.\n- **Ping-pong** : `/context -` deux fois vous ramène à votre point de départ.\n- **Sûr face aux mutations** : si vous supprimez le contexte précédent, le pointeur est effacé. Si vous le renommez, le pointeur suit le nouveau nom.\n- **La réactivation est un no-op** : `/context production` lorsque vous êtes déjà sur `production` ne réinitialise pas le pointeur précédent.\n\n## Conventions de conception\n\nL'expérience utilisateur de `/context` s'inspire de :\n\n- **kubectx** : `kubectx <name>` pour basculer, `kubectx -` pour le précédent, `kubectx` seul pour lister\n- **kubectl** : `kubectl config use-context` pour la forme explicite\n- **Shell** : `cd -` / `OLDPWD` pour le suivi du répertoire précédent\n",
	"fr/runtime-tools/custom-tools.md": "---\ntitle: Outils personnalisés\ndescription: >-\n  Enregistrement des outils personnalisés, définition de schéma et pipeline\n  d'exécution pour étendre l'agent.\nsidebar:\n  order: 4\n  label: Outils personnalisés\ni18n:\n  sourceHash: 5f4a441fc2e2\n  translator: machine\n---\n\n# Outils personnalisés\n\nLes outils personnalisés sont des fonctions appelables par le modèle qui s'intègrent dans le même pipeline d'exécution que les outils intégrés.\n\nUn outil personnalisé est un module TypeScript/JavaScript qui exporte une fabrique. La fabrique reçoit une API hôte (`CustomToolAPI`) et retourne un outil ou un tableau d'outils.\n\n## Ce que c'est (et ce que ce n'est pas)\n\n- **Outil personnalisé** : appelable par le modèle durant un tour (`execute` + schéma TypeBox).\n- **Extension** : cadre de cycle de vie/événements pouvant enregistrer des outils et intercepter/modifier des événements.\n- **Hook** : scripts externes pré/post-commande.\n- **Skill** : paquet de guidage/contexte statique, pas de code d'outil exécutable.\n\nSi vous avez besoin que le modèle appelle du code directement, utilisez un outil personnalisé.\n\n## Chemins d'intégration dans le code actuel\n\nIl existe deux styles d'intégration actifs :\n\n1. **Outils personnalisés fournis par le SDK** (`options.customTools`)\n   - Encapsulés dans des outils agent via `CustomToolAdapter` ou des wrappers d'extension.\n   - Toujours inclus dans l'ensemble d'outils actifs initial lors du démarrage du SDK.\n\n2. **Modules découverts via le système de fichiers par l'API de chargement** (`discoverAndLoadCustomTools` / `loadCustomTools`)\n   - Exposés en tant qu'API de bibliothèque dans `src/extensibility/custom-tools/loader.ts`.\n   - Le code hôte peut les appeler pour découvrir et charger des modules d'outils depuis les chemins de configuration/fournisseur/plugin.\n\n```text\nFlux d'appel d'outil du modèle\n\nAppel d'outil LLM\n   │\n   ▼\nRegistre d'outils (outils intégrés + adaptateurs d'outils personnalisés)\n   │\n   ▼\nCustomTool.execute(toolCallId, params, onUpdate, ctx, signal)\n   │\n   ├─ onUpdate(...)  -> résultat partiel en streaming\n   └─ return result  -> contenu/détails de l'outil final\n```\n\n## Emplacements de découverte (API de chargement)\n\n`discoverAndLoadCustomTools(configuredPaths, cwd, builtInToolNames)` fusionne :\n\n1. Les fournisseurs de capacités (`toolCapability`), notamment :\n   - Configuration OMP native (`~/.xcsh/agent/tools`, `.xcsh/tools`)\n   - Configuration Claude (`~/.claude/tools`, `.claude/tools`)\n   - Configuration Codex (`~/.codex/tools`, `.codex/tools`)\n   - Fournisseur de cache de plugin marketplace Claude\n2. Les manifestes de plugins installés (`~/.xcsh/plugins/node_modules/*` via le chargeur de plugins)\n3. Les chemins configurés explicitement passés au chargeur\n\n### Comportement important\n\n- Les chemins résolus en double sont dédupliqués.\n- Les conflits de noms d'outils sont rejetés par rapport aux outils intégrés et aux outils personnalisés déjà chargés.\n- Les fichiers `.md` et `.json` sont découverts comme métadonnées d'outils par certains fournisseurs, mais le chargeur de modules exécutables les rejette en tant qu'outils exécutables.\n- Les chemins configurés relatifs sont résolus depuis `cwd` ; `~` est développé.\n\n## Contrat de module\n\nUn module d'outil personnalisé doit exporter une fonction (export par défaut préféré) :\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = (pi) => ({\n name: \"repo_stats\",\n label: \"Repo Stats\",\n description: \"Counts tracked TypeScript files\",\n parameters: pi.typebox.Type.Object({\n  glob: pi.typebox.Type.Optional(pi.typebox.Type.String({ default: \"**/*.ts\" })),\n }),\n\n async execute(toolCallId, params, onUpdate, ctx, signal) {\n  onUpdate?.({\n   content: [{ type: \"text\", text: \"Scanning files...\" }],\n   details: { phase: \"scan\" },\n  });\n\n  const result = await pi.exec(\"git\", [\"ls-files\", params.glob ?? \"**/*.ts\"], { signal, cwd: pi.cwd });\n  if (result.killed) {\n   throw new Error(\"Scan was cancelled\");\n  }\n  if (result.code !== 0) {\n   throw new Error(result.stderr || \"git ls-files failed\");\n  }\n\n  const files = result.stdout.split(\"\\n\").filter(Boolean);\n  return {\n   content: [{ type: \"text\", text: `Found ${files.length} files` }],\n   details: { count: files.length, sample: files.slice(0, 10) },\n  };\n },\n\n onSession(event) {\n  if (event.reason === \"shutdown\") {\n   // cleanup resources if needed\n  }\n },\n});\n\nexport default factory;\n```\n\nType de retour de la fabrique :\n\n- `CustomTool`\n- `CustomTool[]`\n- `Promise<CustomTool | CustomTool[]>`\n\n## Surface d'API transmise aux fabriques (`CustomToolAPI`)\n\nDepuis `types.ts` et `loader.ts` :\n\n- `cwd` : répertoire de travail hôte\n- `exec(command, args, options?)` : assistant d'exécution de processus\n- `ui` : contexte d'interface utilisateur (peut être sans effet dans les modes sans interface)\n- `hasUI` : `false` dans les flux non interactifs\n- `logger` : journaliseur de fichiers partagé\n- `typebox` : `@sinclair/typebox` injecté\n- `pi` : exports de `@f5-sales-demo/xcsh` injectés\n- `pushPendingAction(action)` : enregistre une action de prévisualisation pour l'outil `resolve` masqué (`docs/resolve-tool-runtime.md`)\n\nLe chargeur démarre avec un contexte d'interface utilisateur sans effet et nécessite que le code hôte appelle `setUIContext(...)` lorsque la véritable interface est prête.\n\n## Contrat d'exécution et typage\n\nSignature de `CustomTool.execute` :\n\n```ts\nexecute(toolCallId, params, onUpdate, ctx, signal)\n```\n\n- `params` est typé statiquement depuis votre schéma TypeBox via `Static<TParams>`.\n- La validation des arguments à l'exécution se produit avant l'exécution dans la boucle agent.\n- `onUpdate` émet des résultats partiels pour le streaming de l'interface utilisateur.\n- `ctx` inclut l'état de session/modèle et un assistant `abort()`.\n- `signal` transporte l'annulation.\n\n`CustomToolAdapter` fait le pont avec l'interface d'outil agent et transmet les appels dans le bon ordre d'arguments.\n\n## Comment les outils sont exposés au modèle\n\n- Les outils sont encapsulés dans des instances `AgentTool` (`CustomToolAdapter` ou wrappers d'extension).\n- Ils sont insérés dans le registre d'outils de session par nom.\n- Lors du démarrage du SDK, les outils personnalisés et enregistrés par extension sont inclus de force dans l'ensemble actif initial.\n- L'option CLI `--tools` valide actuellement uniquement les noms d'outils intégrés ; l'inclusion des outils personnalisés est gérée via les chemins de découverte/enregistrement et les options du SDK.\n\n## Hooks de rendu\n\nHooks de rendu optionnels :\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\nComportement à l'exécution dans TUI :\n\n- Si des hooks existent, la sortie de l'outil est rendue dans un conteneur `Box`.\n- `renderResult` reçoit `{ expanded, isPartial, spinnerFrame? }`.\n- Les erreurs de rendu sont interceptées et journalisées ; l'interface revient au rendu de texte par défaut.\n\n## Gestion de session/état\n\nLe hook optionnel `onSession(event, ctx)` reçoit les événements de cycle de vie de session, notamment :\n\n- `start`, `switch`, `branch`, `tree`, `shutdown`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`, `todo_reminder`\n\nUtilisez `ctx.sessionManager` pour reconstruire l'état depuis l'historique lorsque le contexte de branche/session change.\n\n## Échecs et sémantiques d'annulation\n\n### Échecs synchrones/asynchrones\n\n- Lever une exception (ou les promesses rejetées) dans `execute` est traité comme un échec d'outil.\n- Le runtime agent convertit les échecs en messages de résultat d'outil avec `isError: true` et un contenu texte d'erreur.\n- Avec les wrappers d'extension, les gestionnaires `tool_result` peuvent réécrire davantage le contenu/les détails et même remplacer le statut d'erreur.\n\n### Annulation\n\n- L'abandon de l'agent se propage via `AbortSignal` jusqu'à `execute`.\n- Transmettez `signal` aux travaux de sous-processus (`pi.exec(..., { signal })`) pour une annulation coopérative.\n- `ctx.abort()` permet à un outil de demander l'abandon de l'opération agent en cours.\n\n### Erreurs onSession\n\n- Les erreurs `onSession` sont interceptées et journalisées en tant qu'avertissements ; elles ne font pas planter la session.\n\n## Contraintes réelles à prendre en compte\n\n- Les noms d'outils doivent être globalement uniques dans le registre actif.\n- Privilégiez des sorties déterministes et structurées selon le schéma dans `details` pour la reconstruction du rendu/état.\n- Protégez l'utilisation de l'interface avec `pi.hasUI`.\n- Traitez les fichiers `.md`/`.json` dans les répertoires d'outils comme des métadonnées, pas comme des modules exécutables.\n",
	"fr/runtime-tools/notebook-tool-runtime.md": "---\ntitle: Internes du runtime de l'outil Notebook\ndescription: >-\n  Runtime de l'outil Notebook Jupyter avec exécution de cellules, cycle de vie\n  du noyau et rendu des sorties.\nsidebar:\n  order: 2\n  label: Outil Notebook\ni18n:\n  sourceHash: c1bafcb245e4\n  translator: machine\n---\n\n# Internes du runtime de l'outil Notebook\n\nCe document décrit l'implémentation actuelle de l'outil `notebook` et sa relation avec le runtime Python adossé à un noyau.\n\nLa distinction essentielle : **`notebook` est un éditeur JSON de notebooks, pas un exécuteur de notebooks**. Il modifie directement les sources des cellules `.ipynb` ; il ne démarre pas de noyau Python et ne communique pas avec lui.\n\n## Fichiers d'implémentation\n\n- [`src/tools/notebook.ts`](../../packages/coding-agent/src/tools/notebook.ts)\n- [`src/ipy/executor.ts`](../../packages/coding-agent/src/ipy/executor.ts)\n- [`src/ipy/kernel.ts`](../../packages/coding-agent/src/ipy/kernel.ts)\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts)\n- [`src/tools/python.ts`](../../packages/coding-agent/src/tools/python.ts)\n\n## 1) Frontière du runtime : édition vs exécution\n\n## Outil `notebook` (`src/tools/notebook.ts`)\n\n- Prend en charge `action: edit | insert | delete` sur un fichier `.ipynb`.\n- Résout le chemin relatif au CWD de la session (`resolveToCwd`).\n- Charge le JSON du notebook, valide le tableau `cells`, valide les limites de `cell_index`.\n- Applique les modifications de source en mémoire et réécrit l'intégralité du JSON du notebook avec `JSON.stringify(notebook, null, 1)`.\n- Retourne un résumé textuel + des `details` structurés (`action`, `cellIndex`, `cellType`, `totalCells`, `cellSource`).\n\nAucun cycle de vie de noyau n'existe dans cet outil :\n\n- pas d'acquisition de passerelle\n- pas d'ID de session de noyau\n- pas d'`execute_request`\n- pas de fragments de flux provenant des canaux du noyau\n- pas de capture d'affichage enrichi (`image/png`, affichage JSON, MIME de statut)\n\n## Chemin d'exécution de type notebook (`src/tools/python.ts` + `src/ipy/*`)\n\nLorsque l'agent doit exécuter du code Python en style cellule (cellules séquentielles, état persistant, affichages enrichis), cela passe par l'outil **`python`**, et non par `notebook`.\n\nC'est dans ce chemin que résident les modes de noyau, le comportement de redémarrage/annulation, le streaming par fragments et la troncature des artefacts de sortie.\n\n## 2) Sémantique de gestion des cellules du notebook (outil `notebook`)\n\n## Normalisation de la source\n\n`content` est divisé en `source: string[]` avec préservation des sauts de ligne :\n\n- chaque ligne non finale conserve le `\\n` de fin\n- la ligne finale n'a pas de saut de ligne forcé en fin\n\nCela respecte les conventions JSON des notebooks et évite la concaténation accidentelle de lignes lors d'éditions ultérieures.\n\n## Comportement des actions\n\n- `edit`\n  - remplace `cells[cell_index].source`\n  - préserve le `cell_type` existant\n- `insert`\n  - insère à `[0..cellCount]`\n  - `cell_type` prend par défaut la valeur `code`\n  - les cellules de code initialisent `execution_count: null` et `outputs: []`\n  - les cellules markdown n'initialisent que `metadata` + `source`\n- `delete`\n  - supprime `cells[cell_index]`\n  - retourne la `source` supprimée dans les détails pour l'aperçu du rendu\n\n## Surfaces d'erreur\n\nLes échecs critiques sont levés pour :\n\n- fichier notebook manquant\n- JSON invalide\n- `cells` manquant ou non-tableau\n- index hors limites (les plages valides diffèrent pour l'insertion et les autres opérations)\n- `content` manquant pour `edit`/`insert`\n\nCes erreurs deviennent des réponses d'outil `Error:` en amont ; le rendu utilise le chemin du notebook + le texte d'erreur formaté.\n\n## 3) Sémantique des sessions de noyau (là où elles existent réellement)\n\nLa sémantique des noyaux est implémentée dans `executePython` / `PythonKernel` et s'applique à l'outil `python`.\n\n## Modes\n\n`PythonKernelMode` :\n\n- `session` (par défaut)\n  - noyaux mis en cache dans la map `kernelSessions`\n  - maximum 4 sessions ; la plus ancienne est évincée en cas de dépassement\n  - nettoyage des sessions inactives/mortes toutes les 30s, expiration après 5 minutes\n  - la file d'attente par session sérialise l'exécution (`session.queue`)\n- `per-call`\n  - crée un noyau pour la requête\n  - exécute\n  - arrête toujours le noyau dans le bloc `finally`\n\n## Comportement de réinitialisation\n\nL'outil `python` passe `reset` uniquement pour la première cellule d'un appel multi-cellules ; les cellules suivantes s'exécutent toujours avec `reset: false`.\n\n## Mort du noyau / redémarrage / nouvelle tentative\n\nEn mode session (`withKernelSession`) :\n\n- un noyau mort est détecté par battement de cœur (vérification `kernel.isAlive()` toutes les 5s) ou par échec d'exécution.\n- un état mort avant exécution déclenche `restartKernelSession`.\n- le chemin de plantage au moment de l'exécution effectue une nouvelle tentative : redémarre le noyau, réexécute le gestionnaire.\n- `restartCount > 1` dans la même session lève l'erreur `Python kernel restarted too many times in this session`.\n\nComportement de nouvelle tentative au démarrage :\n\n- la création de noyau sur passerelle partagée effectue une nouvelle tentative sur `SharedGatewayCreateError` avec un HTTP 5xx.\n\nRécupération en cas d'épuisement des ressources :\n\n- détecte les échecs de type `EMFILE`/`ENFILE`/\"Too many open files\"\n- efface les sessions suivies\n- appelle `shutdownSharedGateway()`\n- effectue une nouvelle tentative de création de session de noyau\n\n## 4) Injection de variables d'environnement/session\n\nLe démarrage du noyau reçoit une map d'environnement optionnelle de l'exécuteur :\n\n- `PI_SESSION_FILE` (chemin du fichier d'état de session)\n- `ARTIFACTS` (répertoire des artefacts)\n\n`PythonKernel.#initializeKernelEnvironment(...)` exécute ensuite le script d'initialisation dans le noyau pour :\n\n- `os.chdir(cwd)`\n- injecter les entrées d'environnement dans `os.environ`\n- ajouter cwd en tête de `sys.path` s'il est absent\n\nImplication :\n\n- les helpers de préambule qui lisent le contexte de session ou d'artefact s'appuient sur ces variables d'environnement dans l'état du processus Python.\n\n## 5) Gestion du streaming/fragments et des affichages (chemin adossé au noyau)\n\nLe client du noyau traite les messages du protocole Jupyter par exécution :\n\n- `stream` -> fragment de texte vers `onChunk`\n- `execute_result` / `display_data` ->\n  - texte d'affichage choisi par priorité MIME : `text/markdown` > `text/plain` > `text/html` converti\n  - sorties structurées capturées séparément :\n    - `application/json` -> `{ type: \"json\" }`\n    - `image/png` -> `{ type: \"image\" }`\n    - `application/x-xcsh-status` -> `{ type: \"status\" }` (pas d'émission de texte)\n- `error` -> texte de traceback poussé vers le flux de fragments + métadonnées d'erreur structurées\n- `input_request` -> émet un texte d'avertissement stdin, envoie une réponse `input_reply` vide, marque stdin comme demandé\n- la complétion attend à la fois `execute_reply` et le `status=idle` du noyau\n\nAnnulation/expiration :\n\n- le signal d'abandon déclenche `interrupt()` (REST `/interrupt` + `interrupt_request` sur le canal de contrôle)\n- le résultat est marqué `cancelled=true`\n- le chemin d'expiration annote la sortie avec `Command timed out after <n> seconds`\n\n## 6) Comportement de troncature et d'artefacts\n\n`OutputSink` dans `src/session/streaming-output.ts` est utilisé par les chemins d'exécution du noyau (`executeWithKernel`) :\n\n- assainit chaque fragment (`sanitizeText`)\n- suit le total des lignes/octets en sortie\n- fichier de déversement d'artefact optionnel (`artifactPath`, `artifactId`)\n- lorsque le tampon en mémoire dépasse le seuil (`DEFAULT_MAX_BYTES` sauf dérogation) :\n  - marqué comme tronqué\n  - conserve les octets de fin en mémoire (limite sûre UTF-8)\n  - peut déverser le flux complet vers un récepteur d'artefact\n\n`dump()` retourne :\n\n- texte de sortie visible (éventuellement tronqué en fin)\n- indicateur de troncature + compteurs\n- ID d'artefact (pour les références `artifact://<id>`)\n\nL'outil `python` convertit ces métadonnées en avis de troncature de résultat et avertissements TUI.\n\nL'outil `notebook` n'utilise **pas** `OutputSink` ; il ne dispose pas de pipeline de troncature de flux/artefact car il n'exécute pas de code.\n\n## 7) Hypothèses du rendu et formatage\n\n## Rendu de notebook (`notebookToolRenderer`)\n\n- vue d'appel : ligne de statut avec action + chemin du notebook + métadonnées de cellule/type\n- vue de résultat :\n  - résumé de succès dérivé de `details`\n  - `cellSource` rendu via `renderCodeCell`\n  - les cellules markdown définissent l'indication de langage `markdown` ; les autres cellules n'ont pas de dérogation de langage explicite\n  - la limite d'aperçu de code réduit est `PREVIEW_LIMITS.COLLAPSED_LINES * 2`\n  - prend en charge le mode développé via les options de rendu partagées\n  - utilise un cache de rendu indexé par largeur + état développé\n\nHypothèse de rendu d'erreur :\n\n- si le premier contenu textuel commence par `Error:`, le rendu formate un bloc d'erreur de notebook.\n\n## Rendu Python (pour la sortie d'exécution réelle)\n\nLe rendu d'exécution adossé au noyau attend :\n\n- des transitions d'état par cellule (`pending/running/complete/error`)\n- une section optionnelle d'événements de statut structurés\n- des arborescences de sortie JSON optionnelles\n- des avertissements de troncature + un pointeur `artifact://<id>` optionnel\n\nCe comportement du rendu est sans lien avec les résultats de l'édition JSON de `notebook`, si ce n'est que les deux réutilisent des primitives TUI partagées.\n\n## 8) Divergence par rapport au comportement de l'outil Python simple\n\nSi « outil Python simple » désigne le chemin d'exécution `python` :\n\n- `python` exécute du code dans un noyau, persiste l'état selon le mode, stream les fragments, capture les affichages enrichis, gère les interruptions/expirations et prend en charge la troncature de sortie/artefacts.\n- `notebook` effectue uniquement des mutations déterministes du JSON de notebook ; pas d'exécution, pas d'état de noyau, pas de flux de fragments, pas de sorties d'affichage, pas de pipeline d'artefacts.\n\nSi un flux de travail nécessite les deux :\n\n1. modifier la source du notebook avec `notebook`\n2. exécuter les cellules de code via `python` (en passant le code manuellement), et non via `notebook`\n\nL'implémentation actuelle ne fournit pas d'outil unique qui à la fois mute le `.ipynb` et exécute les cellules du notebook via un contexte de noyau.\n",
	"fr/runtime-tools/resolve-tool-runtime.md": "---\ntitle: Composants internes du moteur d'exécution de l'outil Resolve\ndescription: >-\n  Moteur d'exécution de l'outil Resolve pour la résolution des chemins de\n  fichiers, la récupération de contenu et l'accès aux ressources via URL.\nsidebar:\n  order: 3\n  label: Outil Resolve\ni18n:\n  sourceHash: 06e8be8c5a3c\n  translator: machine\n---\n\n# Composants internes du moteur d'exécution de l'outil Resolve\n\nCe document explique comment les workflows de prévisualisation/application sont modélisés dans l'agent de codage et comment les outils personnalisés peuvent y participer via `pushPendingAction`.\n\n## Périmètre et fichiers clés\n\n- [`src/tools/resolve.ts`](../../packages/coding-agent/src/tools/resolve.ts)\n- [`src/tools/pending-action.ts`](../../packages/coding-agent/src/tools/pending-action.ts)\n- [`src/tools/ast-edit.ts`](../../packages/coding-agent/src/tools/ast-edit.ts)\n- [`src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts)\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n\n## Fonctionnement de `resolve`\n\n`resolve` est un outil masqué qui finalise une action de prévisualisation en attente.\n\n- `action: \"apply\"` exécute `apply(reason)` sur l'action en attente et rend les modifications persistantes.\n- `action: \"discard\"` invoque `reject(reason)` si cette fonction est fournie ; dans le cas contraire, l'action est abandonnée avec le message par défaut « Discarded ».\n\nSi aucune action en attente n'existe, `resolve` échoue avec le message :\n\n- `No pending action to resolve. Nothing to apply or discard.`\n\n## Les actions en attente forment une pile (LIFO)\n\nLes actions en attente sont stockées dans `PendingActionStore` sous la forme d'une pile push/pop :\n\n- `push(action)` ajoute une nouvelle action en attente au sommet de la pile.\n- `peek()` inspecte l'action actuellement au sommet.\n- `pop()` retire et retourne l'action au sommet.\n- `hasPending` indique si la pile est non vide.\n\n`resolve` consomme toujours l'action **la plus haute** en premier (`pop()`), de sorte que plusieurs outils produisant des prévisualisations sont résolus dans l'ordre inverse de leur enregistrement.\n\n## Exemple de producteur intégré (`ast_edit`)\n\n`ast_edit` prévisualise d'abord les remplacements structurels. Lorsque la prévisualisation contient des remplacements et n'a pas encore été appliquée, il enregistre une action en attente contenant :\n\n- un libellé (résumé lisible par un humain)\n- `sourceToolName` (`ast_edit`)\n- le callback `apply(reason: string)` qui réexécute la modification AST avec `dryRun: false`\n\n`resolve(action=\"apply\", reason=\"...\")` transmet `reason` à ce callback.\n\n## Outils personnalisés : `pushPendingAction`\n\nLes outils personnalisés peuvent enregistrer des actions en attente compatibles avec resolve via `CustomToolAPI.pushPendingAction(...)`.\n\n`CustomToolPendingAction` :\n\n- `label: string` (obligatoire)\n- `apply(reason: string): Promise<AgentToolResult<unknown>>` (obligatoire) — invoqué lors de l'application ; `reason` est la chaîne transmise à `resolve`\n- `reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>` (optionnel) — invoqué lors de l'abandon ; la valeur retournée remplace le message par défaut « Discarded » si elle est fournie\n- `details?: unknown` (optionnel)\n- `sourceToolName?: string` (optionnel, valeur par défaut : `\"custom_tool\"`)\n\n### Exemple d'utilisation minimal\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = pi => ({\n name: \"batch_rename_preview\",\n label: \"Batch Rename Preview\",\n description: \"Previews renames and defers commit to resolve\",\n parameters: pi.typebox.Type.Object({\n  files: pi.typebox.Type.Array(pi.typebox.Type.String()),\n }),\n\n async execute(_toolCallId, params) {\n  const previewSummary = `Prepared rename plan for ${params.files.length} files`;\n\n  pi.pushPendingAction({\n   label: `Batch rename: ${params.files.length} files`,\n   sourceToolName: \"batch_rename_preview\",\n   apply: async (reason) => {\n    // apply writes here\n    return {\n     content: [{ type: \"text\", text: `Applied batch rename. Reason: ${reason}` }],\n    };\n   },\n   reject: async (reason) => {\n    // optional: cleanup or notify on discard\n    return {\n     content: [{ type: \"text\", text: `Discarded batch rename. Reason: ${reason}` }],\n    };\n   },\n  });\n\n  return {\n   content: [{ type: \"text\", text: `${previewSummary}. Call resolve to apply or discard.` }],\n  };\n },\n});\n\nexport default factory;\n```\n\n## Disponibilité du moteur d'exécution et erreurs\n\n`pushPendingAction` est câblé par le chargeur d'outils personnalisés en utilisant le `PendingActionStore` de la session active.\n\nSi le moteur d'exécution ne dispose pas de stockage d'actions en attente, `pushPendingAction` lève l'exception :\n\n- `Pending action store unavailable for custom tools in this runtime.`\n\n## Comportement de sélection d'outil\n\nLorsque `PendingActionStore.hasPending` est à `true`, le moteur d'exécution de l'agent oriente la sélection d'outil vers `resolve` afin que les prévisualisations en attente soient explicitement finalisées avant que le flux d'outils normal ne reprenne.\n\n## Recommandations pour les développeurs\n\n- Utilisez les actions en attente uniquement pour les opérations destructives ou à fort impact qui doivent prendre en charge une application ou un abandon explicite.\n- Gardez `label` concis et précis ; il est affiché dans la sortie du rendu de resolve.\n- Assurez-vous que `apply(reason)` est suffisamment déterministe et idempotent pour une exécution en une seule passe ; `reason` est informatif et ne doit pas modifier le comportement.\n- Implémentez `reject(reason)` lorsque l'abandon nécessite un nettoyage (état temporaire, verrous, notifications) ; omettez-le pour les prévisualisations sans état pour lesquelles le message par défaut suffit.\n- Si votre outil peut mettre en attente plusieurs prévisualisations, gardez à l'esprit la sémantique LIFO : la dernière action enregistrée est résolue en premier.\n",
	"fr/runtime-tools/slash-command-internals.md": "---\ntitle: Fonctionnement interne des commandes slash\ndescription: >-\n  Fonctionnement interne du système de commandes slash avec l'enregistrement,\n  l'analyse des arguments et la répartition de l'exécution.\nsidebar:\n  order: 5\n  label: Commandes slash\ni18n:\n  sourceHash: 2cbd44a3de87\n  translator: machine\n---\n\n# Fonctionnement interne des commandes slash\n\nCe document décrit comment les commandes slash sont découvertes, dédupliquées, présentées en mode interactif, et développées au moment de l'invite dans `coding-agent`.\n\n## Fichiers d'implémentation\n\n- [`src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n- [`src/capability/slash-command.ts`](../../packages/coding-agent/src/capability/slash-command.ts)\n- [`src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`src/discovery/claude.ts`](../../packages/coding-agent/src/discovery/claude.ts)\n- [`src/discovery/codex.ts`](../../packages/coding-agent/src/discovery/codex.ts)\n- [`src/discovery/claude-plugins.ts`](../../packages/coding-agent/src/discovery/claude-plugins.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n\n## 1) Modèle de découverte\n\nLes commandes slash constituent une capacité (`id: \"slash-commands\"`) indexée par nom de commande (`key: cmd => cmd.name`).\n\nLe registre des capacités charge tous les fournisseurs enregistrés, triés par priorité décroissante, et déduplique par clé selon la sémantique **premier arrivé, premier servi**.\n\n### Ordre de priorité des fournisseurs\n\nFournisseurs de commandes slash actuels et leurs priorités :\n\n1. `native` (OMP) — priorité `100`\n2. `claude` — priorité `80`\n3. `claude-plugins` — priorité `70`\n4. `codex` — priorité `70`\n\nComportement en cas d'égalité : les fournisseurs de même priorité conservent l'ordre d'enregistrement. L'ordre d'importation actuel enregistre `claude-plugins` avant `codex`, ainsi les commandes de plugin l'emportent sur les commandes codex en cas de collision de noms.\n\n### Comportement en cas de collision de noms\n\nPour `slash-commands`, les collisions sont résolues strictement par déduplication de capacité :\n\n- l'élément de plus haute priorité est conservé dans `result.items`\n- les doublons de priorité inférieure ne figurent que dans `result.all` et sont marqués `_shadowed = true`\n\nCela s'applique entre les fournisseurs, mais aussi au sein d'un même fournisseur s'il retourne des noms en double.\n\n### Comportement d'analyse des fichiers\n\nLes fournisseurs utilisent majoritairement `loadFilesFromDir(...)`, qui actuellement :\n\n- utilise par défaut une correspondance non récursive (`*.md`)\n- emploie le glob natif avec `gitignore: true`, `hidden: false`\n- lit chaque fichier correspondant et le transforme en `SlashCommand`\n\nAinsi, les fichiers et répertoires cachés ne sont pas chargés, et les chemins ignorés sont exclus.\n\n## 2) Chemins source spécifiques aux fournisseurs et priorité locale\n\n## Fournisseur `native` (`builtin.ts`)\n\nLes racines de recherche proviennent des répertoires `.xcsh` :\n\n- projet : `<cwd>/.xcsh/commands/*.md`\n- utilisateur : `~/.xcsh/agent/commands/*.md`\n\n`getConfigDirs()` retourne d'abord le projet, puis l'utilisateur ; ainsi **les commandes natives du projet l'emportent sur les commandes natives de l'utilisateur** en cas de collision de noms.\n\n## Fournisseur `claude` (`claude.ts`)\n\nCharge :\n\n- utilisateur : `~/.claude/commands/*.md`\n- projet : `<cwd>/.claude/commands/*.md`\n\nLe fournisseur place les éléments utilisateur avant les éléments projet, de sorte que **les commandes Claude de l'utilisateur l'emportent sur les commandes Claude du projet** en cas de collision de noms dans ce fournisseur.\n\n## Fournisseur `codex` (`codex.ts`)\n\nCharge :\n\n- utilisateur : `~/.codex/commands/*.md`\n- projet : `<cwd>/.codex/commands/*.md`\n\nLes deux côtés sont chargés puis aplatis dans l'ordre utilisateur en premier, ainsi **les commandes Codex de l'utilisateur l'emportent sur les commandes Codex du projet** en cas de collision.\n\nLe contenu des commandes Codex est analysé avec suppression du frontmatter (`parseFrontmatter`), et le nom de la commande peut être remplacé par le frontmatter `name` ; sinon le nom du fichier est utilisé.\n\n## Fournisseur `claude-plugins` (`claude-plugins.ts`)\n\nCharge les racines de commandes des plugins depuis `~/.claude/plugins/installed_plugins.json`, puis analyse `<pluginRoot>/commands/*.md`.\n\nL'ordonnancement suit l'ordre d'itération du registre et l'ordre des entrées par plugin dans ces données JSON. Il n'y a pas d'étape de tri supplémentaire.\n\n## 3) Matérialisation vers le `FileSlashCommand` d'exécution\n\n`loadSlashCommands()` dans `src/extensibility/slash-commands.ts` convertit les éléments de capacité en objets `FileSlashCommand` utilisés au moment de l'invite.\n\nPour chaque commande :\n\n1. analyser le frontmatter et le corps (`parseFrontmatter`)\n2. source de la description :\n   - `frontmatter.description` si présent\n   - sinon la première ligne de corps non vide (rognée, 60 caractères max avec `...`)\n3. conserver le corps analysé comme contenu de gabarit exécutable\n4. calculer une chaîne d'affichage de la source, par exemple `via Claude Code Project`\n\nLa sévérité de l'analyse du frontmatter dépend de la source :\n\n- niveau `native` -> les erreurs d'analyse sont `fatal`\n- niveaux `user`/`project` -> les erreurs d'analyse sont `warn` avec analyse de secours\n\n### Commandes de repli intégrées\n\nAprès les commandes provenant du système de fichiers et des fournisseurs, des gabarits de commandes intégrés sont ajoutés (`EMBEDDED_COMMAND_TEMPLATES`) si leurs noms ne sont pas déjà présents.\n\nL'ensemble intégré actuel provient de `src/task/commands.ts` et est utilisé comme repli (`source: \"bundled\"`).\n\n## 4) Mode interactif : origine des listes de commandes\n\nLe mode interactif combine plusieurs sources de commandes pour la complétion automatique et le routage des commandes.\n\nÀ la construction, il constitue une liste de commandes en attente à partir de :\n\n- les commandes intégrées (`BUILTIN_SLASH_COMMANDS`, incluant la complétion des arguments et les indications en ligne pour certaines commandes)\n- les commandes slash enregistrées par les extensions (`extensionRunner.getRegisteredCommands(...)`)\n- les commandes personnalisées TypeScript (`session.customCommands`), mappées vers des libellés de commandes slash\n- les commandes de compétences optionnelles (`/skill:<name>`) lorsque `skills.enableSkillCommands` est activé\n\nPuis `init()` appelle `refreshSlashCommandState(...)` pour charger les commandes basées sur des fichiers et installer un `CombinedAutocompleteProvider` contenant :\n\n- les commandes en attente mentionnées ci-dessus\n- les commandes basées sur des fichiers découvertes\n\n`refreshSlashCommandState(...)` met également à jour `session.setSlashCommands(...)` afin que l'expansion des invites utilise le même ensemble de commandes de fichiers découvertes.\n\n### Cycle de vie du rafraîchissement\n\nL'état des commandes slash est rafraîchi :\n\n- lors de l'initialisation interactive\n- après qu'une commande `/move` change le répertoire de travail (`handleMoveCommand` appelle `resetCapabilities()` puis `refreshSlashCommandState(newCwd)`)\n\nIl n'y a pas de surveillance continue des répertoires de commandes par observateur de fichiers.\n\n### Autres points d'exposition\n\nLe tableau de bord des extensions charge également la capacité `slash-commands` et affiche les entrées de commandes actives et occultées, y compris les doublons `_shadowed`.\n\n## 5) Placement dans le pipeline d'invites\n\nOrdre de traitement des commandes slash par `AgentSession.prompt(...)` (lorsque `expandPromptTemplates !== false`) :\n\n1. **Commandes d'extension** (`#tryExecuteExtensionCommand`)  \n   Si `/name` correspond à une commande enregistrée par une extension, le gestionnaire s'exécute immédiatement et l'invite retourne.\n2. **Commandes personnalisées TypeScript** (`#tryExecuteCustomCommand`)  \n   Frontière uniquement : si une correspondance est trouvée, elle s'exécute et peut retourner :\n   - `string` -> remplace le texte de l'invite par cette chaîne\n   - `void/undefined` -> traité comme géré ; aucune invite LLM\n3. **Commandes slash basées sur des fichiers** (`expandSlashCommand`)  \n   Si le texte commence toujours par `/`, tentative d'expansion de la commande markdown.\n4. **Gabarits d'invite** (`expandPromptTemplate`)  \n   Appliqués après le traitement slash/personnalisé.\n5. **Livraison**\n   - inactif : l'invite est envoyée immédiatement à l'agent\n   - en streaming : l'invite est mise en file d'attente comme steer/follow-up selon `streamingBehavior`\n\nC'est pourquoi l'expansion des commandes slash se situe avant l'expansion des gabarits d'invite, et pourquoi les commandes personnalisées peuvent transformer le slash initial avant la correspondance avec les commandes de fichiers.\n\n## 6) Sémantique d'expansion pour les commandes slash basées sur des fichiers\n\nComportement de `expandSlashCommand(text, fileCommands)` :\n\n- ne s'exécute que lorsque le texte commence par `/`\n- extrait le nom de la commande du premier jeton après `/`\n- extrait les arguments du reste du texte via `parseCommandArgs`\n- recherche une correspondance exacte de nom dans les `fileCommands` chargées\n- en cas de correspondance, applique :\n  - remplacement positionnel : `$1`, `$2`, ...\n  - remplacement agrégé : `$ARGUMENTS` et `$@`\n  - puis rendu du gabarit via `prompt.render` avec `{ args, ARGUMENTS, arguments }`\n- en l'absence de correspondance, retourne le texte original inchangé\n\n### Mises en garde concernant `parseCommandArgs`\n\nL'analyseur est un découpage simple tenant compte des guillemets :\n\n- prend en charge les guillemets `'simples'` et `\"doubles\"` pour conserver les espaces\n- supprime les délimiteurs de guillemets\n- n'implémente pas les règles d'échappement par barre oblique inverse\n- un guillemet non fermé n'est pas une erreur ; l'analyseur consomme jusqu'à la fin\n\n## 7) Comportement pour les entrées `/...` inconnues\n\nLes entrées slash inconnues **ne sont pas rejetées** par la logique slash centrale.\n\nSi la commande n'est pas gérée par les couches extension/personnalisée/fichier, `expandSlashCommand` retourne le texte original, et l'invite littérale `/...` poursuit normalement l'expansion du gabarit et la livraison au LLM.\n\nLe mode interactif gère séparément de nombreuses commandes intégrées dans `InputController` (par exemple `/settings`, `/model`, `/mcp`, `/move`, `/exit`). Celles-ci sont consommées avant `session.prompt(...)` et n'atteignent donc jamais l'expansion des commandes de fichiers dans ce chemin.\n\n## 8) Différences en mode streaming par rapport au mode inactif\n\n## Chemin inactif\n\n- `session.prompt(\"/x ...\")` exécute le pipeline de commandes et soit exécute la commande immédiatement, soit envoie le texte développé directement.\n\n## Chemin streaming (`session.isStreaming === true`)\n\n- `prompt(...)` exécute quand même les transformations extension/personnalisée/fichier/gabarit en premier\n- puis requiert `streamingBehavior` :\n  - `\"steer\"` -> met en file d'attente un message d'interruption (`agent.steer`)\n  - `\"followUp\"` -> met en file d'attente un message post-tour (`agent.followUp`)\n- si `streamingBehavior` est omis, l'invite lève une erreur\n\n### Comportement de streaming spécifique aux commandes\n\n- Les commandes d'extension sont exécutées immédiatement, même pendant le streaming (non mises en file d'attente sous forme de texte).\n- Les méthodes d'aide `steer(...)`/`followUp(...)` rejettent les commandes d'extension (`#throwIfExtensionCommand`) pour éviter de mettre en file d'attente le texte de commande pour des gestionnaires devant s'exécuter de manière synchrone.\n- La relecture de la file d'attente de compaction utilise `isKnownSlashCommand(...)` pour décider si les entrées mises en file d'attente doivent être relues via `session.prompt(...)` (pour les commandes slash connues) ou via les méthodes brutes steer/follow-up.\n\n## 9) Gestion des erreurs et surfaces d'échec\n\n- Les échecs de chargement des fournisseurs sont isolés ; le registre collecte les avertissements et continue avec les autres fournisseurs.\n- Les éléments de commandes slash invalides (nom, chemin ou contenu manquant, ou niveau invalide) sont rejetés par la validation des capacités.\n- Échecs d'analyse du frontmatter :\n  - commandes natives : l'erreur d'analyse fatale remonte\n  - commandes non natives : avertissement + analyse de secours clé/valeur\n- Les exceptions des gestionnaires de commandes extension/personnalisées sont interceptées et signalées via le canal d'erreur des extensions (ou via le logger de repli pour les commandes personnalisées sans exécuteur d'extension), et traitées comme gérées (aucune exécution de secours non souhaitée).\n",
	"fr/runtime-tools/task-agent-discovery.md": "---\ntitle: Découverte et sélection des agents de tâches\ndescription: >-\n  Logique de découverte et de sélection des agents de tâches pour le routage du\n  travail vers des types de sous-agents spécialisés.\nsidebar:\n  order: 6\n  label: Découverte des agents de tâches\ni18n:\n  sourceHash: 8cf42457c672\n  translator: machine\n---\n\n# Découverte et sélection des agents de tâches\n\nCe document décrit comment le sous-système de tâches découvre les définitions d'agents, fusionne plusieurs sources et résout un agent demandé au moment de l'exécution.\n\nIl couvre le comportement à l'exécution tel qu'implémenté aujourd'hui, y compris la priorité, la gestion des définitions invalides et les contraintes de création/profondeur qui peuvent rendre un agent effectivement indisponible.\n\n## Fichiers d'implémentation\n\n- [`src/task/discovery.ts`](../../packages/coding-agent/src/task/discovery.ts)\n- [`src/task/agents.ts`](../../packages/coding-agent/src/task/agents.ts)\n- [`src/task/types.ts`](../../packages/coding-agent/src/task/types.ts)\n- [`src/task/index.ts`](../../packages/coding-agent/src/task/index.ts)\n- [`src/task/commands.ts`](../../packages/coding-agent/src/task/commands.ts)\n- [`src/prompts/agents/task.md`](../../packages/coding-agent/src/prompts/agents/task.md)\n- [`src/prompts/tools/task.md`](../../packages/coding-agent/src/prompts/tools/task.md)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/config.ts`](../../packages/coding-agent/src/config.ts)\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts)\n\n---\n\n## Forme de la définition d'agent\n\nLes agents de tâches se normalisent en `AgentDefinition` (`src/task/types.ts`) :\n\n- `name`, `description`, `systemPrompt` (requis pour un agent chargé valide)\n- optionnels : `tools`, `spawns`, `model`, `thinkingLevel`, `output`\n- `source` : `\"bundled\" | \"user\" | \"project\"`\n- optionnel : `filePath`\n\nL'analyse provient du frontmatter via `parseAgentFields()` (`src/discovery/helpers.ts`) :\n\n- `name` ou `description` manquant => invalide (`null`), l'appelant traite comme un échec d'analyse\n- `tools` accepte CSV ou tableau ; si fourni, `submit_result` est automatiquement ajouté\n- `spawns` accepte `*`, CSV ou tableau\n- comportement de rétrocompatibilité : si `spawns` est manquant mais que `tools` inclut `task`, `spawns` devient `*`\n- `output` est transmis tel quel comme données de schéma opaques\n\n## Agents intégrés\n\nLes agents intégrés sont embarqués au moment de la compilation (`src/task/agents.ts`) en utilisant des imports texte.\n\n`EMBEDDED_AGENT_DEFS` définit :\n\n- `explore`, `plan`, `designer`, `reviewer` à partir de fichiers de prompts\n- `task` et `quick_task` à partir du corps partagé `task.md` plus le frontmatter injecté\n\nChemin de chargement :\n\n1. `loadBundledAgents()` analyse le markdown embarqué avec `parseAgent(..., \"bundled\", \"fatal\")`\n2. les résultats sont mis en cache en mémoire (`bundledAgentsCache`)\n3. `clearBundledAgentsCache()` est une réinitialisation du cache réservée aux tests\n\nParce que l'analyse des agents intégrés utilise `level: \"fatal\"`, un frontmatter mal formé dans les agents intégrés lève une exception et peut faire échouer entièrement la découverte.\n\n## Découverte par système de fichiers et plugins\n\n`discoverAgents(cwd, home)` (`src/task/discovery.ts`) fusionne les agents provenant de plusieurs emplacements avant d'ajouter les définitions intégrées.\n\n### Entrées de découverte\n\n1. Répertoires d'agents de la configuration utilisateur via `getConfigDirs(\"agents\", { project: false })`\n2. Répertoires d'agents du projet le plus proche via `findAllNearestProjectConfigDirs(\"agents\", cwd)`\n3. Racines de plugins Claude (`listClaudePluginRoots(home)`) avec les sous-répertoires `agents/`\n4. Agents intégrés (`loadBundledAgents()`)\n\n### Ordre réel des sources\n\nL'ordre des familles de sources provient de `getConfigDirs(\"\", { project: false })`, qui est dérivé de `priorityList` dans `src/config.ts` :\n\n1. `.xcsh`\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nPour chaque famille de sources, l'ordre de découverte est :\n\n1. répertoire du projet le plus proche pour cette source (si trouvé)\n2. répertoire utilisateur pour cette source\n\nAprès tous les répertoires de familles de sources, les répertoires `agents/` des plugins sont ajoutés (les plugins de portée projet d'abord, puis ceux de portée utilisateur).\n\nLes agents intégrés sont ajoutés en dernier.\n\n### Avertissement important : commentaires obsolètes vs code actuel\n\nLes commentaires d'en-tête de `discovery.ts` mentionnent encore `.pi` et ne mentionnent pas `.codex`/`.gemini`. L'ordre réel à l'exécution est piloté par `src/config.ts` et utilise actuellement `.xcsh`, `.claude`, `.codex`, `.gemini`.\n\n## Règles de fusion et de collision\n\nLa découverte utilise une déduplication premier-arrivé-premier-servi par `agent.name` exact :\n\n- Un `Set<string>` suit les noms déjà vus.\n- Les agents chargés sont aplatis dans l'ordre des répertoires et conservés uniquement si le nom n'a pas été vu.\n- Les agents intégrés sont filtrés par rapport au même ensemble et ajoutés uniquement s'ils n'ont pas encore été vus.\n\nImplications :\n\n- Le projet prend le pas sur l'utilisateur pour la même famille de sources.\n- Une famille de sources de priorité supérieure prend le pas sur une inférieure (`.xcsh` avant `.claude`, etc.).\n- Les agents non intégrés prennent le pas sur les agents intégrés ayant le même nom.\n- La correspondance de nom est sensible à la casse (`Task` et `task` sont distincts).\n- Au sein d'un même répertoire, les fichiers markdown sont lus dans l'ordre lexicographique des noms de fichiers avant la déduplication.\n\n## Comportement en cas de fichier d'agent invalide ou manquant\n\nPar répertoire (`loadAgentsFromDir`) :\n\n- répertoire illisible/manquant : traité comme vide (`readdir(...).catch(() => [])`)\n- échec de lecture ou d'analyse du fichier : avertissement journalisé, fichier ignoré\n- le chemin d'analyse utilise `parseAgent(..., level: \"warn\")`\n\nLe comportement en cas d'échec du frontmatter provient de `parseFrontmatter` :\n\n- une erreur d'analyse au niveau `warn` journalise un avertissement\n- l'analyseur se rabat sur un analyseur simple ligne par ligne `key: value`\n- si les champs requis sont toujours manquants, `parseAgentFields` échoue, puis `AgentParsingError` est levée et interceptée par l'appelant (fichier ignoré)\n\nEffet net : un seul fichier d'agent personnalisé défectueux n'interrompt pas la découverte des autres fichiers.\n\n## Recherche et sélection d'agent\n\nLa recherche est une recherche linéaire par nom exact :\n\n- `getAgent(agents, name)` => `agents.find(a => a.name === name)`\n\nLors de l'exécution des tâches (`TaskTool.execute`) :\n\n1. les agents sont redécouverts au moment de l'appel (`discoverAgents(this.session.cwd)`)\n2. le `params.agent` demandé est résolu via `getAgent`\n3. un agent manquant retourne une réponse d'outil immédiate :\n   - `Unknown agent \"...\". Available: ...`\n   - aucun sous-processus n'est lancé\n\n### Description vs découverte au moment de l'exécution\n\n`TaskTool.create()` construit la description de l'outil à partir des résultats de découverte au moment de l'initialisation (`buildDescription`).\n\n`execute()` redécouvre les agents à nouveau. Ainsi l'ensemble à l'exécution peut différer de ce qui était listé dans la description d'outil précédente si les fichiers d'agents ont changé en cours de session.\n\n## Garde-fous de sortie structurée et priorité des schémas\n\nPriorité du schéma de sortie à l'exécution dans `TaskTool.execute` :\n\n1. frontmatter de l'agent `output`\n2. `params.schema` de l'appel de tâche\n3. `outputSchema` de la session parente\n\n(`effectiveOutputSchema = effectiveAgent.output ?? outputSchema ?? this.session.outputSchema`)\n\nLe texte de garde-fou au moment du prompt dans `src/prompts/tools/task.md` met en garde contre le comportement de discordance pour les agents à sortie structurée (`explore`, `reviewer`) : les instructions de format de sortie en prose peuvent entrer en conflit avec le schéma intégré et produire des sorties `null`.\n\nIl s'agit de recommandations, pas de logique de validation stricte à l'exécution dans `discoverAgents`.\n\n## Interaction avec la découverte de commandes\n\n`src/task/commands.ts` est une infrastructure parallèle pour les commandes de workflow (pas les définitions d'agents), mais elle suit le même schéma global :\n\n- découverte d'abord à partir des fournisseurs de capacités\n- déduplication par nom avec premier-arrivé-premier-servi\n- ajout des commandes intégrées si pas encore vues\n- recherche par nom exact via `getCommand`\n\nDans `src/task/index.ts`, les helpers de commandes sont réexportés avec les helpers de découverte d'agents. La découverte d'agents elle-même ne dépend pas de la découverte de commandes à l'exécution.\n\n## Contraintes de disponibilité au-delà de la découverte\n\nUn agent peut être découvrable mais néanmoins indisponible à l'exécution en raison de garde-fous d'exécution.\n\n### Politique de création du parent\n\n`TaskTool.execute` vérifie `session.getSessionSpawns()` :\n\n- `\"*\"` => autoriser tout\n- `\"\"` => refuser tout\n- liste CSV => autoriser uniquement les noms listés\n\nSi refusé : réponse immédiate `Cannot spawn '...'. Allowed: ...`.\n\n### Garde de protection contre l'auto-récursion par variable d'environnement\n\n`PI_BLOCKED_AGENT` est lu lors de la construction de l'outil. Si la demande correspond, l'exécution est rejetée avec un message de prévention de récursion.\n\n### Contrôle de la profondeur de récursion (disponibilité de l'outil task dans les sessions enfants)\n\nDans `runSubprocess` (`src/task/executor.ts`) :\n\n- la profondeur est calculée à partir de `taskDepth`\n- `task.maxRecursionDepth` contrôle le seuil\n- à la profondeur maximale :\n  - l'outil `task` est retiré de la liste d'outils de l'enfant\n  - l'env `spawns` de l'enfant est défini comme vide\n\nAinsi les niveaux plus profonds ne peuvent pas créer d'autres tâches même si la définition de l'agent inclut `spawns`.\n\n## Avertissement concernant le mode plan (implémentation actuelle)\n\n`TaskTool.execute` calcule un `effectiveAgent` pour le mode plan (préfixe le prompt du mode plan, force un sous-ensemble d'outils en lecture seule, vide les spawns), mais `runSubprocess` est appelé avec `agent` plutôt qu'`effectiveAgent`.\n\nEffet actuel :\n\n- le remplacement de modèle / niveau de réflexion / schéma de sortie sont dérivés de `effectiveAgent`\n- le prompt système et les restrictions d'outils/spawns de `effectiveAgent` ne sont pas transmis dans ce chemin d'appel\n\nIl s'agit d'un avertissement d'implémentation qu'il est important de connaître lors de la lecture des attentes de comportement en mode plan.\n",
	"fr/sessions/compaction.md": "---\ntitle: Compaction et résumés de branches\ndescription: >-\n  Compaction de la fenêtre de contexte et génération de résumés de branches pour\n  les sessions de longue durée.\nsidebar:\n  order: 5\n  label: Compaction\ni18n:\n  sourceHash: dae425a900d8\n  translator: machine\n---\n\n# Compaction et résumés de branches\n\nLa compaction et les résumés de branches sont les deux mécanismes qui permettent de maintenir l'utilisabilité des sessions longues sans perdre le contexte des travaux antérieurs.\n\n- **La compaction** réécrit l'historique ancien sous forme de résumé sur la branche courante.\n- **Le résumé de branche** capture le contexte d'une branche abandonnée lors de la navigation `/tree`.\n\nLes deux sont persistés sous forme d'entrées de session et reconvertis en messages de contexte utilisateur lors de la reconstruction de l'entrée LLM.\n\n## Fichiers d'implémentation clés\n\n- `src/session/compaction/compaction.ts`\n- `src/session/compaction/branch-summarization.ts`\n- `src/session/compaction/pruning.ts`\n- `src/session/compaction/utils.ts`\n- `src/session/session-manager.ts`\n- `src/session/agent-session.ts`\n- `src/session/messages.ts`\n- `src/extensibility/hooks/types.ts`\n- `src/config/settings-schema.ts`\n\n## Modèle d'entrée de session\n\nLa compaction et les résumés de branches sont des entrées de session de premier ordre, et non de simples messages assistant/utilisateur.\n\n- `CompactionEntry`\n  - `type: \"compaction\"`\n  - `summary`, `shortSummary` optionnel\n  - `firstKeptEntryId` (limite de compaction)\n  - `tokensBefore`\n  - `details`, `preserveData`, `fromExtension` optionnels\n- `BranchSummaryEntry`\n  - `type: \"branch_summary\"`\n  - `fromId`, `summary`\n  - `details`, `fromExtension` optionnels\n\nLors de la reconstruction du contexte (`buildSessionContext`) :\n\n1. La dernière compaction sur le chemin actif est convertie en un message `compactionSummary`.\n2. Les entrées conservées depuis `firstKeptEntryId` jusqu'au point de compaction sont réintégrées.\n3. Les entrées ultérieures sur le chemin sont ajoutées.\n4. Les entrées `branch_summary` sont converties en messages `branchSummary`.\n5. Les entrées `custom_message` sont converties en messages `custom`.\n\nCes rôles personnalisés sont ensuite transformés en messages utilisateur destinés au LLM dans `convertToLlm()` à l'aide des modèles statiques :\n\n- `prompts/compaction/compaction-summary-context.md`\n- `prompts/compaction/branch-summary-context.md`\n\n## Pipeline de compaction\n\n### Déclencheurs\n\nLa compaction peut s'exécuter de trois façons :\n\n1. **Manuelle** : `/compact [instructions]` appelle `AgentSession.compact(...)`.\n2. **Récupération automatique de dépassement** : après une erreur assistant correspondant à un dépassement de contexte.\n3. **Compaction automatique par seuil** : après un tour réussi lorsque le contexte dépasse le seuil.\n\n### Forme de la compaction (visuelle)\n\n```text\nAvant compaction :\n\n  entrée:  0     1     2     3      4     5     6      7      8     9\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┘\n                └────────┬───────┘ └──────────────┬──────────────┘\n               messagesToSummarize            messages conservés\n                                   ↑\n                          firstKeptEntryId (entrée 4)\n\nAprès compaction (nouvelle entrée ajoutée) :\n\n  entrée:  0     1     2     3      4     5     6      7      8     9      10\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┬─────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │ cmp │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┴─────┘\n               └──────────┬──────┘ └──────────────────────┬───────────────────┘\n                 non envoyé au LLM                    envoyé au LLM\n                                                         ↑\n                                              commence depuis firstKeptEntryId\n\nCe que voit le LLM :\n\n  ┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐\n  │ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │\n  └────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘\n       ↑         ↑      └─────────────────┬────────────────┘\n    prompt   from cmp          messages depuis firstKeptEntryId\n```\n\n### Compaction par dépassement avec reprise vs compaction par seuil\n\nLes deux chemins automatiques sont intentionnellement différents :\n\n- **Compaction par dépassement avec reprise**\n  - Déclencheur : une erreur assistant du modèle courant est détectée comme dépassement de contexte.\n  - Le message d'erreur assistant défaillant est retiré de l'état actif de l'agent avant la reprise.\n  - La compaction automatique s'exécute avec `reason: \"overflow\"` et `willRetry: true`.\n  - En cas de succès, l'agent continue automatiquement (`agent.continue()`) après la compaction.\n\n- **Compaction par seuil**\n  - Déclencheur : `contextTokens > contextWindow - compaction.reserveTokens`.\n  - S'exécute avec `reason: \"threshold\"` et `willRetry: false`.\n  - En cas de succès, si `compaction.autoContinue !== false`, injecte une invite synthétique :\n    - `\"Continue if you have next steps.\"`\n\n### Élagage avant compaction\n\nAvant les vérifications de compaction, un élagage des résultats d'outils peut s'exécuter (`pruneToolOutputs`).\n\nPolitique d'élagage par défaut :\n\n- Protéger les `40 000` tokens de sortie d'outils les plus récents.\n- Exiger au moins `20 000` tokens d'économies totales estimées.\n- Ne jamais élaguer les résultats d'outils provenant de `skill` ou `read`.\n\nLes résultats d'outils élagués sont remplacés par :\n\n- `[Output truncated - N tokens]`\n\nSi l'élagage modifie des entrées, le stockage de session est réécrit et l'état des messages de l'agent est actualisé avant les décisions de compaction.\n\n### Logique de limite et de point de coupure\n\n`prepareCompaction()` ne considère que les entrées depuis la dernière entrée de compaction (le cas échéant).\n\n1. Trouver l'index de compaction précédent.\n2. Calculer `boundaryStart = prevCompactionIndex + 1`.\n3. Adapter `keepRecentTokens` en utilisant le ratio d'utilisation mesuré lorsqu'il est disponible.\n4. Exécuter `findCutPoint()` sur la fenêtre de limite.\n\nLes points de coupure valides incluent :\n\n- les entrées de message avec les rôles : `user`, `assistant`, `bashExecution`, `hookMessage`, `branchSummary`, `compactionSummary`\n- les entrées `custom_message`\n- les entrées `branch_summary`\n\nRègle absolue : ne jamais couper à `toolResult`.\n\nSi des entrées de métadonnées non-message se trouvent immédiatement avant le point de coupure (`model_change`, `thinking_level_change`, libellés, etc.), elles sont intégrées dans la région conservée en déplaçant l'index de coupure vers l'arrière jusqu'à atteindre un message ou une limite de compaction.\n\n### Gestion des tours fractionnés\n\nSi le point de coupure ne se trouve pas au début d'un tour utilisateur, la compaction le traite comme un tour fractionné.\n\nLa détection du début de tour considère ces éléments comme des limites de tour utilisateur :\n\n- `message.role === \"user\"`\n- `message.role === \"bashExecution\"`\n- entrée `custom_message`\n- entrée `branch_summary`\n\nLa compaction de tour fractionné génère deux résumés :\n\n1. Résumé d'historique (`messagesToSummarize`)\n2. Résumé de préfixe de tour (`turnPrefixMessages`)\n\nLe résumé final stocké est fusionné comme suit :\n\n```markdown\n<history summary>\n\n---\n\n**Turn Context (split turn):**\n\n<turn prefix summary>\n```\n\n### Génération de résumés\n\n`compact(...)` construit des résumés à partir du texte de conversation sérialisé :\n\n1. Convertir les messages via `convertToLlm()`.\n2. Sérialiser avec `serializeConversation()`.\n3. Envelopper dans `<conversation>...</conversation>`.\n4. Inclure optionnellement `<previous-summary>...</previous-summary>`.\n5. Injecter optionnellement le contexte de hook sous forme de liste `<additional-context>`.\n6. Exécuter l'invite de résumé avec `SUMMARIZATION_SYSTEM_PROMPT`.\n\nSélection de l'invite :\n\n- première compaction : `compaction-summary.md`\n- compaction itérative avec résumé antérieur : `compaction-update-summary.md`\n- deuxième passe de tour fractionné : `compaction-turn-prefix.md`\n- résumé court pour l'interface : `compaction-short-summary.md`\n\nMode de résumé distant :\n\n- Si `compaction.remoteEndpoint` est défini, la compaction envoie une requête POST :\n  - `{ systemPrompt, prompt }`\n- Attend du JSON contenant au moins `{ summary }`.\n\n### Contexte des opérations sur fichiers dans les résumés\n\nLa compaction suit l'activité cumulée sur les fichiers à l'aide des appels d'outils assistant :\n\n- `read(path)` → ensemble lu\n- `write(path)` → ensemble modifié\n- `edit(path)` → ensemble modifié\n\nComportement cumulatif :\n\n- Inclut les détails de compaction antérieurs uniquement si l'entrée précédente est générée par pi (`fromExtension !== true`).\n- Dans les tours fractionnés, inclut également les opérations sur fichiers du préfixe de tour.\n- `readFiles` exclut les fichiers également modifiés.\n\nDes balises de fichiers sont ajoutées au texte du résumé via le modèle d'invite :\n\n```xml\n<read-files>\n...\n</read-files>\n<modified-files>\n...\n</modified-files>\n```\n\n### Persistance et rechargement\n\nAprès la génération du résumé (ou un résumé fourni par hook), la session agent :\n\n1. Ajoute `CompactionEntry` avec `appendCompaction(...)`.\n2. Reconstruit le contexte via `buildSessionContext()`.\n3. Remplace les messages agent actifs par le contexte reconstruit.\n4. Émet l'événement hook `session_compact`.\n\n## Pipeline de résumé de branche\n\nLe résumé de branche est lié à la navigation dans l'arbre, et non au dépassement de tokens.\n\n### Déclencheur\n\nLors de `navigateTree(...)` :\n\n1. Calculer les entrées abandonnées depuis l'ancienne feuille jusqu'à l'ancêtre commun à l'aide de `collectEntriesForBranchSummary(...)`.\n2. Si l'appelant a demandé un résumé (`options.summarize`), générer le résumé avant de changer de feuille.\n3. Si un résumé existe, l'attacher à la cible de navigation avec `branchWithSummary(...)`.\n\nEn pratique, ceci est généralement déclenché par le flux `/tree` lorsque `branchSummary.enabled` est activé.\n\n### Forme du changement de branche (visuelle)\n\n```text\nArbre avant navigation :\n\n         ┌─ B ─ C ─ D (ancienne feuille, abandonnée)\n    A ───┤\n         └─ E ─ F (cible)\n\nAncêtre commun : A\nEntrées à résumer : B, C, D\n\nAprès navigation avec résumé :\n\n         ┌─ B ─ C ─ D ─ [résumé de B,C,D]\n    A ───┤\n         └─ E ─ F (nouvelle feuille)\n```\n\n### Préparation et budget de tokens\n\n`generateBranchSummary(...)` calcule le budget comme suit :\n\n- `tokenBudget = model.contextWindow - branchSummary.reserveTokens`\n\n`prepareBranchEntries(...)` effectue ensuite :\n\n1. Premier passage : collecter les opérations cumulées sur les fichiers depuis toutes les entrées résumées, y compris les détails `branch_summary` antérieurs générés par pi.\n2. Deuxième passage : parcourir du plus récent au plus ancien, en ajoutant des messages jusqu'à atteindre le budget de tokens.\n3. Préférer la préservation du contexte récent.\n4. Peut tout de même inclure de grandes entrées de résumé proches de la limite du budget pour la continuité.\n\nLes entrées de compaction sont incluses sous forme de messages (`compactionSummary`) lors de l'entrée en résumé de branche.\n\n### Génération et persistance du résumé\n\nLe résumé de branche :\n\n1. Convertit et sérialise les messages sélectionnés.\n2. Enveloppe dans `<conversation>`.\n3. Utilise des instructions personnalisées si fournies, sinon `branch-summary.md`.\n4. Appelle le modèle de résumé avec `SUMMARIZATION_SYSTEM_PROMPT`.\n5. Préfixe avec `branch-summary-preamble.md`.\n6. Ajoute les balises d'opérations sur fichiers.\n\nLe résultat est stocké sous forme de `BranchSummaryEntry` avec des détails optionnels (`readFiles`, `modifiedFiles`).\n\n## Points de contact avec les extensions et les hooks\n\n### `session_before_compact`\n\nHook de pré-compaction.\n\nPeut :\n\n- annuler la compaction (`{ cancel: true }`)\n- fournir un payload de compaction personnalisé complet (`{ compaction: CompactionResult }`)\n\n### `session.compacting`\n\nHook de personnalisation de l'invite/contexte pour la compaction par défaut.\n\nPeut retourner :\n\n- `prompt` (remplace l'invite de résumé de base)\n- `context` (lignes de contexte supplémentaires injectées dans `<additional-context>`)\n- `preserveData` (stocké sur l'entrée de compaction)\n\n### `session_compact`\n\nNotification post-compaction avec `compactionEntry` sauvegardé et indicateur `fromExtension`.\n\n### `session_before_tree`\n\nS'exécute lors de la navigation dans l'arbre avant la génération du résumé de branche par défaut.\n\nPeut :\n\n- annuler la navigation\n- fournir un `{ summary: { summary, details } }` personnalisé utilisé lorsque l'utilisateur a demandé un résumé\n\n### `session_tree`\n\nÉvénement post-navigation exposant la nouvelle/ancienne feuille et l'entrée de résumé optionnelle.\n\n## Comportement à l'exécution et sémantique des erreurs\n\n- La compaction manuelle interrompt d'abord l'opération agent en cours.\n- `abortCompaction()` annule les contrôleurs de compaction manuelle et automatique.\n- La compaction automatique émet des événements de session de début/fin pour les mises à jour de l'interface/état.\n- La compaction automatique peut essayer plusieurs modèles candidats et relancer en cas d'échec transitoire.\n- Les erreurs de dépassement sont exclues du chemin de reprise générique car elles sont gérées par la compaction.\n- En cas d'échec de la compaction automatique :\n  - le chemin de dépassement émet `Context overflow recovery failed: ...`\n  - le chemin par seuil émet `Auto-compaction failed: ...`\n- Le résumé de branche peut être annulé via un signal d'abandon (par ex. Échap), retournant un résultat de navigation annulé/abandonné.\n\n## Paramètres et valeurs par défaut\n\nDepuis `settings-schema.ts` :\n\n- `compaction.enabled` = `true`\n- `compaction.reserveTokens` = `16384`\n- `compaction.keepRecentTokens` = `20000`\n- `compaction.autoContinue` = `true`\n- `compaction.remoteEndpoint` = `undefined`\n- `branchSummary.enabled` = `false`\n- `branchSummary.reserveTokens` = `16384`\n\nCes valeurs sont consommées à l'exécution par `AgentSession` et les modules de compaction/résumé de branches.\n",
	"fr/sessions/handoff-generation-pipeline.md": "---\ntitle: Pipeline de génération de transfert\ndescription: >-\n  Pipeline de génération de transfert pour créer des résumés de session\n  portables pour la collaboration en équipe.\nsidebar:\n  order: 8\n  label: Pipeline de transfert\ni18n:\n  sourceHash: 03666084b5ac\n  translator: machine\n---\n\n# Pipeline de génération `/handoff`\n\nCe document décrit comment l'agent de codage implémente `/handoff` aujourd'hui : chemin de déclenchement, invite de génération, capture de la complétion, changement de session et réinjection du contexte.\n\n## Périmètre\n\nCouvre :\n\n- La distribution de la commande interactive `/handoff`\n- Le cycle de vie et les transitions d'état de `AgentSession.handoff()`\n- La façon dont la sortie du transfert est capturée depuis la sortie de l'assistant\n- La façon dont les anciennes et nouvelles sessions persistent les données de transfert différemment\n- Le comportement de l'interface utilisateur en cas de succès, d'annulation et d'échec\n\nNe couvre pas :\n\n- La navigation générique dans l'arbre / les mécanismes internes des branches\n- Les commandes de session autres que le transfert (`/new`, `/fork`, `/resume`)\n\n## Fichiers d'implémentation\n\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n\n## Chemin de déclenchement\n\n1. `/handoff` est déclaré dans les métadonnées des commandes slash intégrées (`slash-commands.ts`) avec une indication en ligne optionnelle : `[focus instructions]`.\n2. Dans la gestion des entrées interactives (`InputController`), le texte de soumission correspondant à `/handoff` ou `/handoff ...` est intercepté avant la soumission normale de l'invite.\n3. L'éditeur est effacé et `handleHandoffCommand(customInstructions?)` est appelé.\n4. `CommandController.handleHandoffCommand` effectue une vérification préalable en utilisant les entrées courantes :\n   - Compte les entrées `type === \"message\"`.\n   - Si `< 2`, un avertissement est émis : `Nothing to hand off (no messages yet)` et la fonction retourne.\n\nLa même vérification de contenu minimal existe à nouveau dans `AgentSession.handoff()` et lève une exception si la condition n'est pas satisfaite. Cette mesure de sécurité est dupliquée à la fois au niveau de l'interface utilisateur et au niveau de la session.\n\n## Cycle de vie de bout en bout\n\n### 1) Démarrage de la génération du transfert\n\n`AgentSession.handoff(customInstructions?)` :\n\n- Lit les entrées de la branche courante (`sessionManager.getBranch()`)\n- Valide le nombre minimal de messages (`>= 2`)\n- Crée `#handoffAbortController`\n- Construit une invite fixe et intégrée demandant un document de transfert structuré (`Goal`, `Constraints & Preferences`, `Progress`, `Key Decisions`, `Critical Context`, `Next Steps`)\n- Ajoute `Additional focus: ...` si des instructions personnalisées sont fournies\n\nL'invite est envoyée via :\n\n```ts\nawait this.prompt(handoffPrompt, { expandPromptTemplates: false });\n```\n\n`expandPromptTemplates: false` empêche l'expansion des modèles de slash/invite de cette charge utile d'instruction interne.\n\n### 2) Capture de la complétion\n\nAvant l'envoi de l'invite, `handoff()` s'abonne aux événements de session et attend `agent_end`.\n\nÀ la réception de `agent_end`, il extrait le texte du transfert depuis l'état de l'agent en parcourant à rebours pour trouver le message `assistant` le plus récent, puis en concaténant tous les blocs `content` où `type === \"text\"` avec `\\n`.\n\nHypothèses importantes concernant l'extraction :\n\n- Seuls les blocs de texte sont utilisés ; le contenu non textuel est ignoré.\n- On suppose que le dernier message de l'assistant correspond à la génération du transfert.\n- Il n'analyse pas les sections markdown ni ne valide la conformité au format.\n- Si la sortie de l'assistant ne contient pas de blocs de texte, le transfert est considéré comme manquant.\n\n### 3) Vérifications d'annulation\n\n`handoff()` retourne `undefined` lorsque l'une ou l'autre des conditions suivantes est vérifiée :\n\n- aucun texte de transfert capturé, ou\n- `#handoffAbortController.signal.aborted` est vrai\n\nIl efface toujours `#handoffAbortController` dans `finally`.\n\n### 4) Création d'une nouvelle session\n\nSi du texte a été capturé et que l'opération n'a pas été abandonnée :\n\n1. Vider l'enregistreur de la session courante (`sessionManager.flush()`)\n2. Démarrer une toute nouvelle session (`sessionManager.newSession()`)\n3. Réinitialiser l'état de l'agent en mémoire (`agent.reset()`)\n4. Relier `agent.sessionId` au nouvel identifiant de session\n5. Vider les tableaux de contexte en attente (`#steeringMessages`, `#followUpMessages`, `#pendingNextTurnMessages`)\n6. Réinitialiser le compteur de rappel des tâches\n\n`newSession()` crée un nouvel en-tête et une liste d'entrées vide (la feuille est réinitialisée à `null`). Dans le chemin de transfert, aucun `parentSession` n'est passé.\n\n### 5) Injection du contexte de transfert\n\nLe document de transfert généré est encapsulé et ajouté à la nouvelle session en tant qu'entrée `custom_message` :\n\n```text\n<handoff-context>\n...handoff text...\n</handoff-context>\n\nThe above is a handoff document from a previous session. Use this context to continue the work seamlessly.\n```\n\nAppel d'insertion :\n\n```ts\nthis.sessionManager.appendCustomMessageEntry(\"handoff\", handoffContent, true);\n```\n\nSémantique :\n\n- `customType` : `\"handoff\"`\n- `display` : `true` (visible lors de la reconstruction TUI)\n- Type d'entrée : `custom_message` (participe au contexte LLM)\n\n### 6) Reconstruction du contexte actif de l'agent\n\nAprès l'injection :\n\n1. `sessionManager.buildSessionContext()` résout la liste des messages pour la feuille courante\n2. `agent.replaceMessages(sessionContext.messages)` rend le message de transfert injecté actif dans le contexte\n3. La méthode retourne `{ document: handoffText }`\n\nÀ ce stade, le contexte LLM actif dans la nouvelle session contient le message de transfert injecté, et non l'ancienne transcription.\n\n## Modèle de persistance : ancienne session vs nouvelle session\n\n### Ancienne session\n\nDurant la génération, la persistance normale des messages reste active. La réponse de transfert de l'assistant est persistée en tant qu'entrée `message` ordinaire lors de `message_end`.\n\nRésultat : la session d'origine contient le transfert généré visible dans la transcription historique.\n\n### Nouvelle session\n\nAprès la réinitialisation de la session, le transfert est persisté en tant que `custom_message` avec `customType: \"handoff\"`.\n\n`buildSessionContext()` convertit cette entrée en un message de contexte personnalisé/utilisateur à l'exécution via `createCustomMessage(...)`, afin qu'il soit inclus dans les futures invites de la nouvelle session.\n\n## Comportement du contrôleur / interface utilisateur\n\nComportement de `CommandController.handleHandoffCommand` :\n\n- Appelle `await session.handoff(customInstructions)`\n- Si le résultat est `undefined` : `showError(\"Handoff cancelled\")`\n- En cas de succès :\n  - `rebuildChatFromMessages()` (charge le nouveau contexte de session, incluant le transfert injecté)\n  - invalide la barre de statut et la bordure supérieure de l'éditeur\n  - recharge les tâches\n  - ajoute une ligne de chat de succès : `New session started with handoff context`\n- En cas d'exception :\n  - si le message est `\"Handoff cancelled\"` ou si le nom de l'erreur est `AbortError` : `showError(\"Handoff cancelled\")`\n  - sinon : `showError(\"Handoff failed: <message>\")`\n- Demande un rendu à la fin\n\n## Sémantique d'annulation (comportement actuel)\n\n### Primitive d'annulation au niveau de la session\n\n`AgentSession` expose :\n\n- `abortHandoff()` → abandonne `#handoffAbortController`\n- `isGeneratingHandoff` → vrai tant que le contrôleur existe\n\nLorsque ce chemin d'abandon est utilisé, l'abonné au transfert rejette avec `Error(\"Handoff cancelled\")`, et le contrôleur de commande le mappe vers l'interface utilisateur d'annulation.\n\n### Limitation du chemin interactif `/handoff`\n\nDans le câblage actuel du contrôleur interactif, `/handoff` n'installe pas de gestionnaire Escape dédié qui appelle `abortHandoff()` (contrairement aux chemins de compactage/résumé de branche qui remplacent temporairement `editor.onEscape`).\n\nImpact pratique :\n\n- Il existe une prise en charge de l'annulation au niveau de la session, mais aucun raccourci clavier spécifique au transfert dans le chemin de la commande `/handoff`.\n- L'interruption par l'utilisateur peut toujours se produire via des chemins d'abandon d'agent plus larges, mais ce n'est pas le même canal d'annulation explicite utilisé par `abortHandoff()`.\n\n## Transfert abandonné vs transfert échoué\n\nClassification actuelle dans l'interface utilisateur :\n\n- **Abandonné/annulé**\n  - Le chemin `abortHandoff()` déclenche `\"Handoff cancelled\"`, ou\n  - une `AbortError` est levée\n  - L'interface utilisateur affiche `Handoff cancelled`\n\n- **Échoué**\n  - toute autre erreur levée par `handoff()` / le pipeline d'invite (erreurs de validation de modèle/API, exceptions à l'exécution, etc.)\n  - L'interface utilisateur affiche `Handoff failed: ...`\n\nNuance supplémentaire : si la génération se termine mais qu'aucun texte n'est extrait, `handoff()` retourne `undefined` et le contrôleur signale actuellement **annulé**, et non **échoué**.\n\n## Protections pour les sessions courtes et le contenu minimal\n\nDeux protections empêchent les transferts à faible signal :\n\n- Couche interface utilisateur (`handleHandoffCommand`) : avertit et retourne prématurément si `< 2` entrées de message\n- Couche session (`handoff()`) : lève la même condition en tant qu'erreur\n\nCela évite de créer une nouvelle session avec un contexte de transfert vide ou quasi-vide.\n\n## Résumé des transitions d'état\n\nFlux d'état de haut niveau :\n\n1. Commande slash interactive interceptée\n2. Vérification préalable du nombre de messages\n3. `#handoffAbortController` créé (`isGeneratingHandoff = true`)\n4. Invite de transfert interne soumise (visible dans le chat comme une génération normale de l'assistant)\n5. À la réception de `agent_end`, le dernier texte de l'assistant est extrait\n6. Si manquant/abandonné → retourner `undefined` ou chemin d'erreur d'annulation\n7. Si présent :\n   - vider l'ancienne session\n   - créer une nouvelle session vide\n   - réinitialiser les files d'attente/compteurs à l'exécution\n   - ajouter `custom_message(handoff)`\n   - reconstruire et remplacer les messages actifs de l'agent\n8. Le contrôleur reconstruit l'interface de chat et annonce le succès\n9. `#handoffAbortController` effacé (`isGeneratingHandoff = false`)\n\n## Hypothèses et limitations connues\n\n- L'extraction du transfert est heuristique : « derniers blocs de texte de l'assistant » ; aucune validation structurelle.\n- Aucune vérification stricte que le markdown généré suit le format de section demandé.\n- Le texte extrait manquant est signalé comme une annulation dans l'expérience utilisateur du contrôleur.\n- Le flux interactif `/handoff` manque actuellement d'une liaison Escape→`abortHandoff()` dédiée.\n- Les métadonnées de lignée de la nouvelle session (`parentSession`) ne sont pas définies par ce chemin.\n",
	"fr/sessions/memory.md": "---\ntitle: Mémoire autonome\ndescription: >-\n  Système de mémoire autonome pour la persistance des préférences utilisateur,\n  du contexte de projet et des retours d'expérience entre les sessions.\nsidebar:\n  order: 7\n  label: Mémoire autonome\ni18n:\n  sourceHash: 2aa9f516aa1e\n  translator: machine\n---\n\n# Mémoire autonome\n\nLorsqu'elle est activée, l'agent extrait automatiquement les connaissances durables des sessions passées et injecte un résumé compact dans chaque nouvelle session. Au fil du temps, il construit un magasin de mémoire à l'échelle du projet — décisions techniques, flux de travail récurrents, pièges — qui se perpétue sans effort manuel.\n\nDésactivée par défaut. Activez-la via `/settings` ou `config.yml` :\n\n```yaml\nmemories:\n  enabled: true\n```\n\n## Utilisation\n\n### Ce qui est injecté\n\nAu démarrage de la session, si un résumé de mémoire existe pour le projet en cours, il est injecté dans le prompt système sous forme de bloc **Memory Guidance**. L'agent reçoit l'instruction de :\n\n- Traiter la mémoire comme un contexte heuristique — utile pour les processus et les décisions antérieures, mais ne faisant pas autorité sur l'état actuel du dépôt.\n- Citer le chemin de l'artefact mémoire lorsque la mémoire modifie le plan, et l'associer à des preuves issues du dépôt actuel avant d'agir.\n- Privilégier l'état du dépôt et les instructions de l'utilisateur en cas de conflit avec la mémoire ; traiter la mémoire conflictuelle comme obsolète.\n\n### Lecture des artefacts mémoire\n\nL'agent peut lire directement les fichiers mémoire en utilisant les URL `memory://` avec l'outil `read` :\n\n| URL | Contenu |\n|---|---|\n| `memory://root` | Résumé compact injecté au démarrage |\n| `memory://root/MEMORY.md` | Document complet de mémoire à long terme |\n| `memory://root/skills/<name>/SKILL.md` | Un guide procédural de compétence généré |\n\n### Commande slash `/memory`\n\n| Sous-commande | Effet |\n|---|---|\n| `view` | Afficher le contenu d'injection mémoire actuel |\n| `clear` / `reset` | Supprimer toutes les données mémoire et les artefacts générés |\n| `enqueue` / `rebuild` | Forcer la consolidation à s'exécuter au prochain démarrage |\n\n## Fonctionnement\n\nLes mémoires sont construites par un pipeline en arrière-plan qui s'exécute au démarrage ou est déclenché manuellement via une commande slash.\n\n**Phase 1 — extraction par session :** Pour chaque session passée ayant changé depuis son dernier traitement, un modèle lit l'historique de la session et extrait le signal durable : décisions techniques, contraintes, échecs résolus, flux de travail récurrents. Les sessions trop récentes, trop anciennes ou actuellement actives sont ignorées. Chaque extraction produit un bloc de mémoire brut et un court synopsis pour cette session.\n\n**Phase 2 — consolidation :** Après l'extraction, une seconde passe de modèle lit toutes les extractions par session et produit trois sorties écrites sur le disque :\n\n- `MEMORY.md` — un document de mémoire à long terme organisé\n- `memory_summary.md` — le texte compact injecté au démarrage de la session\n- `skills/` — des guides procéduraux réutilisables, chacun dans son propre sous-répertoire\n\nLa Phase 2 utilise un bail pour empêcher les doubles exécutions lorsque plusieurs processus démarrent simultanément. Les répertoires de compétences obsolètes issus d'exécutions précédentes sont automatiquement élagués.\n\nToutes les sorties sont analysées pour détecter les secrets avant d'être écrites sur le disque.\n\n### Comportement de l'extraction\n\nLe comportement d'extraction et de consolidation de la mémoire est entièrement piloté par des fichiers de prompts statiques dans `src/prompts/memories/`.\n\n| Fichier | Objectif | Variables |\n|---|---|---|\n| `stage_one_system.md` | Prompt système pour l'extraction par session | — |\n| `stage_one_input.md` | Modèle de tour utilisateur encapsulant le contenu de la session | `{{thread_id}}`, `{{response_items_json}}` |\n| `consolidation.md` | Prompt pour la consolidation inter-sessions | `{{raw_memories}}`, `{{rollout_summaries}}` |\n| `read_path.md` | Guidance mémoire injectée dans les sessions actives | `{{memory_summary}}` |\n\n### Sélection du modèle\n\nLa mémoire s'appuie sur le système de rôles de modèle.\n\n| Phase | Rôle | Objectif |\n|---|---|---|\n| Phase 1 (extraction) | `default` | Extraction de connaissances par session |\n| Phase 2 (consolidation) | `smol` | Synthèse inter-sessions |\n\nSi `smol` n'est pas configuré, la Phase 2 se rabat sur le rôle `default`.\n\n## Configuration\n\n| Paramètre | Défaut | Description |\n|---|---|---|\n| `memories.enabled` | `false` | Interrupteur principal |\n| `memories.maxRolloutAgeDays` | `30` | Les sessions plus anciennes que cette valeur ne sont pas traitées |\n| `memories.minRolloutIdleHours` | `12` | Les sessions actives plus récemment que cette valeur sont ignorées |\n| `memories.maxRolloutsPerStartup` | `64` | Plafond de sessions traitées lors d'un seul démarrage |\n| `memories.summaryInjectionTokenLimit` | `5000` | Nombre maximal de tokens du résumé injecté dans le prompt système |\n\nDes paramètres de réglage supplémentaires (concurrence, durées de bail, budgets de tokens) sont disponibles dans la configuration pour un usage avancé.\n\n## Fichiers clés\n\n- `src/memories/index.ts` — orchestration du pipeline, injection, gestion des commandes slash\n- `src/memories/storage.ts` — file d'attente de tâches et registre de threads basés sur SQLite\n- `src/prompts/memories/` — modèles de prompts mémoire\n- `src/internal-urls/memory-protocol.ts` — gestionnaire d'URL `memory://`\n",
	"fr/sessions/non-compaction-retry-policy.md": "---\ntitle: Politique de nouvelle tentative automatique hors compaction\ndescription: >-\n  Politique de nouvelle tentative automatique pour les échecs d'API transitoires\n  hors du chemin de compaction.\nsidebar:\n  order: 6\n  label: Politique de nouvelle tentative\ni18n:\n  sourceHash: 8999a0258dd8\n  translator: machine\n---\n\n# Politique de nouvelle tentative automatique hors compaction\n\nCe document décrit le chemin standard de nouvelle tentative en cas d'erreur API dans `AgentSession`.\n\nIl exclut explicitement la récupération en cas de dépassement de contexte via la compaction automatique. Le dépassement est géré par la logique de compaction et est documenté séparément dans [`compaction.md`](./compaction.md).\n\n## Fichiers d'implémentation\n\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/config/settings-schema.ts`](../../packages/coding-agent/src/config/settings-schema.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts)\n- [`../src/modes/rpc/rpc-client.ts`](../../packages/coding-agent/src/modes/rpc/rpc-client.ts)\n- [`../src/modes/rpc/rpc-types.ts`](../../packages/coding-agent/src/modes/rpc/rpc-types.ts)\n\n## Délimitation du périmètre par rapport à la compaction\n\nLa nouvelle tentative et la compaction sont vérifiées depuis le même chemin `agent_end`, mais sont intentionnellement séparées :\n\n1. `agent_end` inspecte le dernier message de l'assistant.\n2. `#isRetryableError(...)` s'exécute en premier.\n3. Si une nouvelle tentative est initiée, les vérifications de compaction sont ignorées pour ce tour.\n4. Les erreurs de dépassement de contexte sont strictement exclues de la classification de nouvelle tentative (`isContextOverflow(...)` court-circuite la nouvelle tentative).\n5. Le dépassement tombe donc dans `#checkCompaction(...)` au lieu de la nouvelle tentative standard.\n\nEn résumé : les échecs de type surcharge, limitation de débit, serveur ou réseau utilisent cette politique de nouvelle tentative ; le dépassement de fenêtre de contexte utilise la récupération par compaction.\n\n## Classification des nouvelles tentatives\n\n`#isRetryableError(...)` exige que toutes les conditions suivantes soient réunies :\n\n- le `stopReason` de l'assistant est `\"error\"`\n- `errorMessage` existe\n- le message **n'est pas** un dépassement de contexte\n- `errorMessage` correspond à `#isRetryableErrorMessage(...)`\n\nEnsemble de motifs éligibles actuels (basés sur des expressions régulières) :\n\n- overloaded\n- rate limit / usage limit / too many requests\n- classes de serveurs de type HTTP : 429, 500, 502, 503, 504\n- service unavailable / server error / internal error\n- connection error / fetch failed\n- formulation `retry delay`\n\nIl s'agit d'une classification par motifs de chaînes de caractères, et non de codes d'erreur typés spécifiques au fournisseur.\n\n## Cycle de vie de la nouvelle tentative et transitions d'état\n\nÉtat de session utilisé par la nouvelle tentative :\n\n- `#retryAttempt: number` (`0` signifie inactif)\n- `#retryPromise: Promise<void> | undefined` (suit le cycle de vie de la nouvelle tentative en cours)\n- `#retryResolve: (() => void) | undefined` (résout `#retryPromise`)\n- `#retryAbortController: AbortController | undefined` (annule la pause du délai d'attente exponentiel)\n\nFlux (`#handleRetryableError`) :\n\n1. Lire le groupe de paramètres `retry`.\n2. Si `retry.enabled === false`, s'arrêter immédiatement (`false`, aucune nouvelle tentative démarrée).\n3. Incrémenter `#retryAttempt`.\n4. Créer `#retryPromise` une seule fois (première tentative d'une chaîne).\n5. Si la tentative dépasse `retry.maxRetries`, émettre l'événement d'échec final et s'arrêter.\n6. Calculer le délai : `retry.baseDelayMs * 2^(tentative-1)`.\n7. Pour les erreurs de limite d'utilisation, analyser les indications de nouvelle tentative et appeler le stockage d'authentification (`markUsageLimitReached(...)`) ; si le changement de fournisseur/modèle réussit, forcer le délai à `0`.\n8. Émettre `auto_retry_start`.\n9. Supprimer le message d'erreur de l'assistant en fin de liste de l'état d'exécution de l'agent (conservé dans l'historique de session persisté).\n10. Mettre en pause avec prise en charge de l'abandon.\n11. Au réveil, planifier `agent.continue()` via `setTimeout(..., 0)`.\n\n### Ce qui réinitialise les compteurs de nouvelles tentatives\n\n`#retryAttempt` est réinitialisé à `0` dans ces cas :\n\n- premier message d'assistant réussi sans erreur ni abandon après le démarrage des nouvelles tentatives (émet `auto_retry_end { success: true }`)\n- annulation de la nouvelle tentative pendant la pause du délai d'attente exponentiel\n- chemin de dépassement du nombre maximum de tentatives\n\n`#retryPromise` se résout et s'efface lorsque la chaîne de nouvelles tentatives se termine (succès, annulation ou dépassement du maximum), via `#resolveRetry()`.\n\n## Délai d'attente exponentiel et sémantique du nombre maximum de tentatives\n\nParamètres :\n\n- `retry.enabled` (valeur par défaut : `true`)\n- `retry.maxRetries` (valeur par défaut : `3`)\n- `retry.baseDelayMs` (valeur par défaut : `2000`)\n\nNumérotation des tentatives :\n\n- le compteur de tentatives est incrémenté avant la vérification du maximum\n- les événements de démarrage utilisent la tentative courante (base 1)\n- l'événement de fin pour dépassement du maximum rapporte `attempt: this.#retryAttempt - 1` (dernier nombre de nouvelles tentatives effectuées)\n\nSéquence de délai d'attente avec les paramètres par défaut :\n\n- tentative 1 : 2000 ms\n- tentative 2 : 4000 ms\n- tentative 3 : 8000 ms\n\nLes entrées de remplacement du délai ne sont utilisées que dans le chemin de gestion des limites d'utilisation, et uniquement pour influencer la décision de changement de modèle/compte dans le stockage d'authentification. Dans le chemin principal de nouvelle tentative hors compaction, le délai d'attente reste un délai exponentiel local, sauf si un changement réussit (`delayMs = 0`).\n\n## Mécanismes d'abandon\n\n### Abandon explicite de la nouvelle tentative\n\n`abortRetry()` :\n\n- abandonne `#retryAbortController` (si présent)\n- résout la promesse de nouvelle tentative (`#resolveRetry()`) afin de débloquer les entités en attente\n\nSi l'abandon survient pendant la pause, le chemin de capture émet :\n\n- `auto_retry_end { success: false, finalError: \"Retry cancelled\" }`\n- réinitialise la tentative et le contrôleur\n\n### Interaction avec l'abandon global d'opération\n\n`abort()` appelle `abortRetry()` avant d'abandonner le flux d'agent actif. Cela garantit l'annulation du délai d'attente de nouvelle tentative lorsque l'utilisateur émet un abandon général.\n\n### Interaction avec l'interface TUI\n\nSur `auto_retry_start`, EventController :\n\n- remplace le gestionnaire de la touche `Esc` par `session.abortRetry()`\n- affiche le texte de chargement : `Retrying (attempt/maxAttempts) in Ns… (esc to cancel)`\n\nSur `auto_retry_end`, il restaure le gestionnaire `Esc` précédent et efface l'état du chargeur.\n\n## Comportement du streaming et de la complétion des invites\n\n`prompt()` attend finalement `#waitForRetry()` après le retour de `agent.prompt(...)`.\n\nEffet :\n\n- un appel de prompt ne se résout pas complètement tant que toute chaîne de nouvelles tentatives démarrée n'est pas terminée (succès/échec/annulation)\n- le cycle de vie de la nouvelle tentative fait partie d'une limite d'exécution logique d'une invite\n\nCela empêche les appelants de considérer un tour en cours de nouvelle tentative comme terminé prématurément.\n\n## Contrôles : paramètres et RPC\n\n### Boutons de configuration\n\nDéfinis dans le schéma de paramètres sous le groupe retry :\n\n- `retry.enabled`\n- `retry.maxRetries`\n- `retry.baseDelayMs`\n\nBascules programmatiques dans la session :\n\n- `setAutoRetryEnabled(enabled)` écrit `retry.enabled`\n- `autoRetryEnabled` lit `retry.enabled`\n- `isRetrying` indique si la promesse du cycle de vie de nouvelle tentative est active\n\n### Contrôles RPC\n\nSurface de commandes RPC :\n\n- `set_auto_retry` → `session.setAutoRetryEnabled(command.enabled)`\n- `abort_retry` → `session.abortRetry()`\n\nAssistants client :\n\n- `RpcClient.setAutoRetry(enabled)`\n- `RpcClient.abortRetry()`\n\nLes deux commandes retournent des réponses de succès ; les détails de progression ou d'échec de la nouvelle tentative proviennent des événements de session diffusés en continu, et non des charges utiles de réponse aux commandes.\n\n## Émission d'événements et remontée des échecs\n\nÉvénements de nouvelle tentative au niveau de la session :\n\n- `auto_retry_start { attempt, maxAttempts, delayMs, errorMessage }`\n- `auto_retry_end { success, attempt, finalError? }`\n\nPropagation :\n\n- émis via `AgentSession.subscribe(...)`\n- transmis au lanceur d'extension en tant qu'événements d'extension\n- en mode RPC, transmis directement en tant qu'objets d'événements JSON (`session.subscribe(event => output(event))`)\n- en mode TUI, consommés par `EventController` pour l'interface utilisateur de chargement/erreur\n\nRemontée des échecs finaux :\n\n- En cas de dépassement du maximum ou d'annulation, `auto_retry_end.success === false`\n- L'interface TUI affiche : `Retry failed after N attempts: <finalError>`\n- Les extensions et hooks reçoivent `auto_retry_end` avec les mêmes champs\n- Les consommateurs RPC reçoivent le même objet d'événement sur le flux stdout\n\n## Conditions d'arrêt permanent\n\nLa nouvelle tentative s'arrête et ne continuera pas automatiquement lorsque l'une des conditions suivantes se produit :\n\n- `retry.enabled` est false\n- l'erreur n'est pas classifiée comme eligible à une nouvelle tentative\n- l'erreur est un dépassement de contexte (délégué au chemin de compaction)\n- le nombre maximum de nouvelles tentatives est dépassé\n- l'utilisateur annule la nouvelle tentative (`abort_retry` ou `Esc` pendant le chargeur de nouvelle tentative)\n- un abandon global (`abort`) annule d'abord la nouvelle tentative\n\nUne nouvelle chaîne de nouvelles tentatives peut encore démarrer ultérieurement lors d'une future erreur éligible, une fois les compteurs réinitialisés.\n\n## Mises en garde opérationnelles\n\n- La classification repose sur la correspondance de texte par expressions régulières ; les erreurs structurées spécifiques au fournisseur ne sont pas utilisées ici.\n- La nouvelle tentative supprime l'erreur de l'assistant en échec du **contexte d'exécution** avant de reprendre, mais l'historique de session conserve tout de même cette entrée d'erreur.\n- `RpcSessionState` expose actuellement `autoCompactionEnabled` mais pas de champ `autoRetryEnabled` ; les appelants RPC doivent gérer leur propre état de bascule ou interroger les paramètres via d'autres API.\n",
	"fr/sessions/session-operations-export-share-fork-resume.md": "---\ntitle: 'Opérations de session : Exporter, Vider, Partager, Dupliquer, Reprendre'\ndescription: >-\n  Opérations de session pour exporter, partager, dupliquer et reprendre des\n  conversations.\nsidebar:\n  order: 3\n  label: Opérations\ni18n:\n  sourceHash: e3c210b29c3e\n  translator: machine\n---\n\n# Opérations de session : export, dump, share, fork, resume/continue\n\nCe document décrit le comportement visible par l'opérateur pour les opérations d'exportation, de partage, de duplication et de reprise de session telles qu'elles sont actuellement implémentées.\n\n## Fichiers d'implémentation\n\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/export/html/index.ts`](../../packages/coding-agent/src/export/html/index.ts)\n- [`../src/export/custom-share.ts`](../../packages/coding-agent/src/export/custom-share.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n\n## Matrice des opérations\n\n| Opération | Chemin d'entrée | Mutation de session | Création/changement de fichier de session | Artefact de sortie |\n|---|---|---|---|---|\n| `/dump` | Commande slash interactive | Non | Non | Texte dans le presse-papiers |\n| `/export [path]` | Commande slash interactive | Non | Non | Fichier HTML |\n| `--export <session.jsonl> [outputPath]` | Chemin rapide au démarrage CLI | Pas de mutation de session active | Pas de session active ; lit le fichier cible | Fichier HTML |\n| `/share` | Commande slash interactive | Non | Non | HTML temporaire + URL de partage/gist |\n| `/fork` | Commande slash interactive | Oui (l'identité de la session active change) | Crée un nouveau fichier de session et bascule la session courante vers celui-ci (mode persistant uniquement) | Copie le répertoire d'artefacts vers le nouveau espace de noms de session si présent |\n| `/resume` | Commande slash interactive | Oui (l'état en mémoire actif est remplacé) | Bascule vers le fichier de session existant sélectionné | Aucun |\n| `--resume` | Démarrage CLI (sélecteur) | Oui après création de session | Ouvre le fichier de session existant sélectionné | Aucun |\n| `--resume <id\\|path>` | Démarrage CLI | Oui après création de session | Ouvre une session existante ; le cas multi-projet peut créer une duplication dans le projet courant | Aucun |\n| `--continue` | Démarrage CLI | Oui après création de session | Ouvre le fil de progression du terminal ou la session la plus récente ; en crée une nouvelle si aucune n'existe | Aucun |\n\n## Exportation et vidage\n\n### `/export [outputPath]` (interactif)\n\nFlux :\n\n1. `InputController` route `/export...` vers `CommandController.handleExportCommand`.\n2. La commande divise sur les espaces blancs et n'utilise que le premier argument après `/export` comme `outputPath`.\n3. `AgentSession.exportToHtml()` appelle `exportSessionToHtml(sessionManager, state, { outputPath, themeName })`.\n4. En cas de succès, l'interface affiche le chemin et ouvre le fichier dans le navigateur.\n\nDétails de comportement :\n\n- Les arguments `--copy`, `clipboard` et `copy` sont explicitement rejetés avec un avertissement invitant à utiliser `/dump`.\n- L'exportation intègre l'en-tête de session, les entrées, la feuille ainsi que le `systemPrompt` courant et les descriptions d'outils issues de l'état de l'agent.\n- Aucune entrée de session n'est ajoutée pendant l'exportation.\n\nMise en garde :\n\n- L'analyse des arguments est basée sur les espaces (`text.split(/\\s+/)`), donc les chemins entre guillemets contenant des espaces ne sont pas préservés comme un seul chemin par ce chemin de commande.\n\n### `--export <inputSessionFile> [outputPath]` (CLI)\n\nFlux dans `main.ts` :\n\n1. Traité en amont (avant le démarrage interactif/de session).\n2. Appelle `exportFromFile(inputPath, outputPath?)`.\n3. `SessionManager.open(inputPath)` charge les entrées, puis le HTML est généré et écrit.\n4. Le processus affiche `Exported to: ...` et se termine.\n\nDétails de comportement :\n\n- Un fichier d'entrée manquant génère `File not found: <path>`.\n- Ce chemin ne crée pas d'`AgentSession` et ne mute aucune session en cours d'exécution.\n\n### `/dump` (exportation interactive vers le presse-papiers)\n\nFlux :\n\n1. `CommandController.handleDumpCommand()` appelle `session.formatSessionAsText()`.\n2. Si la chaîne est vide, signale `No messages to dump yet.`\n3. Sinon, copie dans le presse-papiers via le `copyToClipboard` natif.\n\nLe contenu du vidage inclut :\n\n- Le prompt système\n- Le modèle actif et le niveau de réflexion\n- Les définitions d'outils et leurs paramètres\n- Les messages utilisateur/assistant\n- Les blocs de réflexion et les appels d'outils\n- Les résultats d'outils et les blocs d'exécution (sauf les entrées bash/python `excludeFromContext`)\n- Les entrées de type personnalisé, hook, mention de fichier, résumé de branche et résumé de compaction\n\nAucun changement de persistance de session n'est effectué par le vidage.\n\n## Partage\n\n`/share` est uniquement interactif et commence toujours par exporter la session courante vers un fichier HTML temporaire.\n\n### Phase 1 : export temporaire\n\n- Chemin du fichier temporaire : `${os.tmpdir()}/${Snowflake.next()}.html`\n- Utilise `session.exportToHtml(tmpFile)`\n- Si l'exportation échoue (notamment pour les sessions en mémoire), le partage se termine avec une erreur.\n\n### Phase 2 : gestionnaire de partage personnalisé (si présent)\n\n`loadCustomShare()` vérifie `~/.xcsh/agent` pour le premier candidat existant :\n\n- `share.ts`\n- `share.js`\n- `share.mjs`\n\nPrérequis :\n\n- Le module doit exporter par défaut une fonction `(htmlPath) => Promise<CustomShareResult | string | undefined>`.\n\nSi présent et valide :\n\n- L'interface passe à l'état de chargement `Sharing...`.\n- Interprétation du résultat du gestionnaire :\n  - chaîne => traitée comme URL, affichée et ouverte\n  - objet => `url` et/ou `message` affichés ; `url` ouverte\n  - `undefined`/falsy => `Session shared` générique\n- Le fichier temporaire est supprimé après l'exécution.\n\nComportement de repli critique :\n\n- Si le gestionnaire personnalisé existe mais que son chargement échoue, la commande génère une erreur et retourne.\n- Si le gestionnaire personnalisé s'exécute et lève une exception, la commande génère une erreur et retourne.\n- Dans les deux cas d'échec, il **ne se rabat pas** sur le gist GitHub.\n- Le repli sur le gist n'intervient que lorsqu'aucun script de partage personnalisé n'est trouvé.\n\n### Phase 3 : repli par défaut sur le gist\n\nUniquement lorsqu'aucun gestionnaire de partage personnalisé n'est trouvé :\n\n1. Valide `gh auth status`.\n2. Affiche le chargement `Creating gist...`.\n3. Exécute `gh gist create --public=false <tmpFile>`.\n4. Analyse l'URL du gist, en dérive l'identifiant, construit l'URL de prévisualisation `https://gistpreview.github.io/?<id>`.\n5. Affiche à la fois les URLs de prévisualisation et du gist ; ouvre la prévisualisation.\n\nSémantique d'annulation/abandon dans le partage :\n\n- Le chargeur dispose d'un hook `onAbort` qui restaure l'interface de l'éditeur et signale `Share cancelled`.\n- La commande `gh gist create` sous-jacente ne reçoit pas de signal d'abandon dans ce chemin de code ; l'annulation est au niveau de l'interface et est vérifiée après le retour de la commande.\n\n## Duplication (Fork)\n\n`/fork` crée une nouvelle session à partir de la session courante et change l'identité de la session active.\n\n### Préconditions et vérifications immédiates\n\n- Si l'agent est en train de diffuser, `/fork` est rejeté avec un avertissement.\n- Les indicateurs d'état/chargement de l'interface sont effacés avant l'opération.\n\n### Flux au niveau de la session\n\n`AgentSession.fork()` :\n\n1. Émet `session_before_switch` avec `reason: \"fork\"` (annulable).\n2. Vide les écritures en attente.\n3. Appelle `SessionManager.fork()`.\n4. Copie le répertoire d'artefacts de l'ancien espace de noms de session vers le nouveau (au mieux ; les échecs de copie autres que ENOENT sont journalisés, pas fatals).\n5. Met à jour `agent.sessionId`.\n6. Émet `session_switch` avec `reason: \"fork\"`.\n\nComportement de `SessionManager.fork()` :\n\n- Requiert le mode persistant et un fichier de session existant.\n- Crée un nouvel identifiant de session et un nouveau chemin de fichier JSONL.\n- Réécrit l'en-tête avec :\n  - nouveau `id`\n  - nouvel horodatage\n  - `cwd` inchangé\n  - `parentSession` défini sur l'identifiant de la session précédente\n- Conserve toutes les entrées non-en-tête inchangées dans le nouveau fichier.\n\n### Comportement non persistant\n\n- Le gestionnaire de session en mémoire retourne `undefined` depuis `fork()`.\n- `AgentSession.fork()` retourne `false`.\n- L'interface signale `Fork failed (session not persisted or cancelled)`.\n\n## Reprise et continuation\n\n## `/resume` interactif\n\nFlux :\n\n1. Ouvre le sélecteur de session alimenté via `SessionManager.list(currentCwd, currentSessionDir)`.\n2. Lors de la sélection, `SelectorController.handleResumeSession(sessionPath)` appelle `session.switchSession(sessionPath)`.\n3. L'interface efface/reconstruit le chat et les tâches, puis signale `Resumed session`.\n\nNotes :\n\n- Ce sélecteur ne liste que les sessions dans la portée du répertoire de session courant.\n- Il n'utilise pas la recherche globale multi-projet.\n\n## CLI `--resume`\n\n### `--resume` (sans valeur)\n\n- `main.ts` liste les sessions pour le répertoire de travail/session courant et ouvre le sélecteur.\n- Le chemin sélectionné est ouvert avec `SessionManager.open(selectedPath)` avant la création de session.\n\n### `--resume <value>`\n\nOrdre de résolution dans `createSessionManager()` :\n\n1. Si la valeur ressemble à un chemin (`/`, `\\`, ou `.jsonl`), ouvrir directement.\n2. Sinon, traiter comme préfixe d'identifiant :\n   - recherche dans la portée courante (`SessionManager.list(cwd, sessionDir)`)\n   - si non trouvé et sans `sessionDir` explicite, recherche globale (`SessionManager.listAll()`)\n\nComportement de correspondance d'identifiant multi-projet :\n\n- Si le répertoire de travail de la session trouvée diffère du répertoire de travail courant, la CLI demande :\n  - `Session found in different project ... Fork into current directory? [y/N]`\n- En cas de oui : `SessionManager.forkFrom(match.path, cwd, sessionDir)` crée un nouveau fichier dupliqué local.\n- En cas de non/sans TTY par défaut : la commande génère une erreur.\n\n## CLI `--continue`\n\n`SessionManager.continueRecent(cwd, sessionDir)` :\n\n1. Résout le répertoire de session pour le répertoire de travail courant.\n2. Lit d'abord le fil de progression étendu au terminal.\n3. Se rabat sur le fichier de session modifié le plus récemment.\n4. Ouvre la session trouvée ; si aucune n'existe, crée une nouvelle session.\n\nIl s'agit d'un comportement au démarrage uniquement ; il n'existe pas de commande slash interactive `/continue`.\n\n## Comment le changement de session mute réellement l'état d'exécution\n\n`AgentSession.switchSession(sessionPath)` effectue la transition d'exécution utilisée par les opérations de type reprise :\n\n1. Émet `session_before_switch` avec `reason: \"resume\"` et `targetSessionFile` (annulable).\n2. Déconnecte l'abonnement aux événements de l'agent et abandonne le travail en cours.\n3. Efface les messages de pilotage, de suivi et de tour suivant en file d'attente.\n4. Vide les écritures du gestionnaire de session courant.\n5. `sessionManager.setSessionFile(sessionPath)` et met à jour `agent.sessionId`.\n6. Construit le contexte de session à partir des entrées chargées.\n7. Émet `session_switch` avec `reason: \"resume\"`.\n8. Remplace les messages de l'agent à partir du contexte.\n9. Restaure le modèle (s'il est disponible dans le registre courant).\n10. Restaure ou initialise le niveau de réflexion.\n11. Reconnecte l'abonnement aux événements de l'agent.\n\nAucun nouveau fichier de session n'est créé par `switchSession()` lui-même.\n\n## Émissions d'événements et points d'annulation\n\n### Hooks du cycle de vie de changement/duplication\n\nPour `newSession`, `fork` et `switchSession` :\n\n- Événement avant : `session_before_switch`\n  - raisons : `new`, `fork`, `resume`\n  - annulable en retournant `{ cancel: true }`\n- Événement après : `session_switch`\n  - même ensemble de raisons\n  - inclut `previousSessionFile`\n\n`ExtensionRunner.emit()` retourne tôt au premier résultat d'événement avant annulant.\n\n### Comportement `onSession` des outils personnalisés\n\nLe pont SDK transmet les événements de session d'extension aux callbacks `onSession` des outils personnalisés :\n\n- `session_switch` -> `onSession({ reason: \"switch\", previousSessionFile })`\n- `session_branch` -> `reason: \"branch\"`\n- `session_start` -> `reason: \"start\"`\n- `session_tree` -> `reason: \"tree\"`\n- `session_shutdown` -> `reason: \"shutdown\"`\n\nCes callbacks sont observationnels ; ils n'annulent pas le changement/la duplication.\n\n### Autres surfaces d'annulation pertinentes pour ce document\n\n- `/fork` est bloqué pendant la diffusion (l'utilisateur doit attendre ou abandonner la réponse courante en premier).\n- Le sélecteur `/resume` peut être annulé par l'utilisateur en fermant le sélecteur.\n- `--resume <id>` multi-projet peut être annulé en refusant l'invite de duplication.\n- `/share` dispose d'un chemin d'abandon dans l'interface (`Share cancelled`) pour le flux gist ; il ne câble pas la sémantique de fin de processus pour `gh gist create` dans ce chemin de code.\n\n## Comportement des sessions non persistantes (en mémoire)\n\nLorsque le gestionnaire de session est créé avec `SessionManager.inMemory()` (`--no-session`) :\n\n- Le chemin du fichier de session est absent.\n- `/export` et `/share` échouent avec `Cannot export in-memory session to HTML` (propagé vers l'interface d'erreur de commande).\n- `/fork` échoue car `SessionManager.fork()` requiert la persistance.\n- `/dump` fonctionne toujours car il sérialise l'état de l'agent en mémoire.\n- Les sémantiques de reprise/continuation CLI sont contournées si `--no-session` est défini, car la création du gestionnaire retourne immédiatement une instance en mémoire.\n\n## Mises en garde d'implémentation connues (selon le code actuel)\n\n- `SelectorController.handleResumeSession()` ne vérifie pas le résultat booléen de `session.switchSession(...)` ; un changement annulé par un hook peut tout de même progresser vers le chemin de repeint/statut de l'interface « Resumed session ».\n- Les échecs de partage personnalisé dans `/share` ne se dégradent pas vers le repli par défaut sur le gist ; ils terminent la commande avec une erreur.\n- La tokenisation des arguments de `/export` est simpliste et ne préserve pas les chemins entre guillemets contenant des espaces.\n",
	"fr/sessions/session-switching-and-recent-listing.md": "---\ntitle: Changement de session et liste des sessions récentes\ndescription: >-\n  Mécanismes de changement de session et liste des sessions récentes avec\n  recherche et filtrage.\nsidebar:\n  order: 4\n  label: Changement & récentes\ni18n:\n  sourceHash: aae56130b508\n  translator: machine\n---\n\n# Changement de session et liste des sessions récentes\n\nCe document décrit comment coding-agent découvre les sessions récentes, résout les cibles `--resume`, présente les sélecteurs de session et change la session active en cours d'exécution.\n\nIl se concentre sur le comportement de l'implémentation actuelle, y compris les chemins de repli et les mises en garde.\n\n## Fichiers d'implémentation\n\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/cli/session-picker.ts`](../../packages/coding-agent/src/cli/session-picker.ts)\n- [`../src/modes/components/session-selector.ts`](../../packages/coding-agent/src/modes/components/session-selector.ts)\n- [`../src/modes/controllers/selector-controller.ts`](../../packages/coding-agent/src/modes/controllers/selector-controller.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n\n## Découverte des sessions récentes\n\n### Portée du répertoire\n\n`SessionManager` stocke les sessions dans un répertoire scopé au cwd par défaut :\n\n- `~/.xcsh/agent/sessions/--<cwd-encoded>--/*.jsonl`\n\n`SessionManager.list(cwd, sessionDir?)` ne lit que ce répertoire sauf si un `sessionDir` explicite est fourni.\n\n### Deux chemins de listage avec des charges utiles différentes\n\nIl existe deux pipelines de listage différents :\n\n1. `getRecentSessions(sessionDir, limit)` (vue d'accueil/résumé)\n   - Ne lit qu'un préfixe de 4 Ko (`readTextPrefix(..., 4096)`) de chaque fichier.\n   - Parse l'en-tête + l'aperçu du premier texte utilisateur.\n   - Retourne des `RecentSessionInfo` légers avec des getters paresseux `name` et `timeAgo`.\n   - Trie par `mtime` du fichier en ordre décroissant.\n\n2. `SessionManager.list(...)` / `SessionManager.listAll()` (sélecteurs de reprise et correspondance par ID)\n   - Lit les fichiers de session complets.\n   - Construit des objets `SessionInfo` (`id`, `cwd`, `title`, `messageCount`, `firstMessage`, `allMessagesText`, horodatages).\n   - Exclut les sessions avec zéro entrées `message`.\n   - Trie par `modified` en ordre décroissant.\n\n### Comportement de repli des métadonnées\n\nPour les résumés récents (`RecentSessionInfo`) :\n\n- préférence du nom d'affichage : `header.title` -> premier prompt utilisateur -> `header.id` -> nom de fichier\n- le nom est tronqué à 40 caractères pour les affichages compacts\n- les caractères de contrôle/retours à la ligne sont supprimés/assainis des noms dérivés du titre\n\nPour les entrées de liste `SessionInfo` :\n\n- `title` est `header.title` ou le dernier `shortSummary` de compaction\n- `firstMessage` est le texte du premier message utilisateur ou `\"(no messages)\"`\n\n## Résolution de `--continue` et préférence du breadcrumb terminal\n\n`SessionManager.continueRecent(cwd, sessionDir?)` résout la cible dans cet ordre :\n\n1. Lire le breadcrumb scopé au terminal (`~/.xcsh/agent/terminal-sessions/<terminal-id>`)\n2. Valider le breadcrumb :\n   - le terminal courant peut être identifié\n   - le cwd du breadcrumb correspond au cwd courant (comparaison de chemin résolu)\n   - le fichier référencé existe toujours\n3. Si le breadcrumb est invalide/manquant, se replier sur le fichier le plus récent par mtime dans le répertoire de session (`findMostRecentSession`)\n4. Si aucun trouvé, créer une nouvelle session\n\nLa dérivation de l'ID terminal préfère le chemin TTY et se replie sur des identifiants basés sur l'environnement (`KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION`).\n\nLes écritures de breadcrumb sont en best-effort et non fatales.\n\n## Résolution de la cible de reprise au démarrage (`main.ts`)\n\n### `--resume <value>`\n\n`createSessionManager(...)` gère les `--resume` à valeur chaîne dans deux modes :\n\n1. Valeur de type chemin (contient `/`, `\\\\`, ou se termine par `.jsonl`)\n   - `SessionManager.open(sessionArg, parsed.sessionDir)` direct\n\n2. Valeur de préfixe d'ID\n   - trouver une correspondance dans `SessionManager.list(cwd, sessionDir)` par `id.startsWith(sessionArg)`\n   - si pas de correspondance locale et que `sessionDir` n'est pas forcé, essayer `SessionManager.listAll()`\n   - la première correspondance est utilisée (pas d'invite d'ambiguïté)\n\nComportement de correspondance inter-projets :\n\n- si le cwd de la session correspondante diffère du cwd courant, le CLI demande s'il faut forker dans le projet courant\n- oui -> `SessionManager.forkFrom(...)`\n- non -> lance une erreur (`Session \"...\" is in another project (...)`)\n\nAucune correspondance -> lance une erreur (`Session \"...\" not found.`).\n\n### `--resume` (sans valeur)\n\nGéré après la construction initiale du session-manager :\n\n1. lister les sessions locales avec `SessionManager.list(cwd, parsed.sessionDir)`\n2. si vide : afficher `No sessions found` et sortir prématurément\n3. ouvrir le sélecteur TUI (`selectSession`)\n4. si annulé : afficher `No session selected` et sortir prématurément\n5. si sélectionné : `SessionManager.open(selectedPath)`\n\n### `--continue`\n\nUtilise `SessionManager.continueRecent(...)` directement (comportement breadcrumb-first ci-dessus).\n\n## Mécanismes internes de la sélection par sélecteur\n\n## Sélecteur CLI (`src/cli/session-picker.ts`)\n\n`selectSession(sessions)` crée une TUI autonome avec `SessionSelectorComponent` et se résout exactement une fois :\n\n- sélection -> résout le chemin sélectionné\n- annulation (Esc) -> résout `null`\n- sortie forcée (chemin Ctrl+C) -> arrête la TUI et `process.exit(0)`\n\n## Sélecteur interactif en session (`SelectorController.showSessionSelector`)\n\nFlux :\n\n1. récupérer les sessions depuis le répertoire de session courant via `SessionManager.list(currentCwd, currentSessionDir)`\n2. monter `SessionSelectorComponent` dans la zone éditeur en utilisant `showSelector(...)`\n3. callbacks :\n   - sélection -> fermer le sélecteur et appeler `handleResumeSession(sessionPath)`\n   - annulation -> restaurer l'éditeur et re-rendre\n   - sortie -> `ctx.shutdown()`\n\n## Comportement du composant sélecteur de session\n\n`SessionList` supporte :\n\n- navigation par flèches/page\n- Entrée pour sélectionner\n- Échap pour annuler\n- Ctrl+C pour quitter\n- recherche floue à travers l'id/titre/cwd/premier message/tous les messages/chemin de la session\n\nComportement de rendu avec liste vide :\n\n- affiche un message au lieu de planter\n- Entrée sur une liste vide ne fait rien (pas de callback)\n- Échap/Ctrl+C fonctionnent toujours\n\nMise en garde : le texte de l'interface indique `Press Tab to view all`, mais ce composant n'a actuellement pas de gestionnaire Tab et le câblage actuel ne liste que les sessions du scope courant.\n\n## Exécution du changement en cours d'exécution (`AgentSession.switchSession`)\n\n`switchSession(sessionPath)` est le chemin principal de changement en cours de processus.\n\nCycle de vie/transition d'état :\n\n1. capturer `previousSessionFile`\n2. émettre l'événement hook `session_before_switch` (`reason: \"resume\"`, annulable)\n3. si annulé -> retourner `false` sans changement\n4. se déconnecter du flux d'événements de l'agent courant\n5. abandonner la génération/le flux d'outils actifs\n6. vider les tampons de messages en file d'attente (steering/follow-up/next-turn)\n7. vider l'écrivain de session (`sessionManager.flush()`) pour persister les écritures en attente\n8. `sessionManager.setSessionFile(sessionPath)`\n   - met à jour le pointeur de fichier de session\n   - écrit le breadcrumb terminal\n   - charge les entrées / migre / résout les blobs / réindexe\n   - si données de fichier manquantes/invalides : initialise une nouvelle session à ce chemin et réécrit l'en-tête\n9. mettre à jour `agent.sessionId`\n10. reconstruire le contexte via `buildSessionContext()`\n11. émettre l'événement hook `session_switch` (`reason: \"resume\"`, `previousSessionFile`)\n12. remplacer les messages de l'agent avec le contexte reconstruit\n13. restaurer le modèle par défaut depuis `sessionContext.models.default` s'il est disponible et présent dans le registre de modèles\n14. restaurer le niveau de réflexion :\n    - si la branche a déjà un `thinking_level_change`, appliquer le niveau de session sauvegardé\n    - sinon dériver le niveau de réflexion par défaut depuis les paramètres, limiter aux capacités du modèle, le définir, et ajouter une nouvelle entrée `thinking_level_change`\n15. reconnecter les écouteurs de l'agent et retourner `true`\n\n## Reconstruction de l'état de l'interface après un changement interactif\n\n`SelectorController.handleResumeSession` effectue une réinitialisation de l'interface autour de `switchSession` :\n\n- arrêter l'animation de chargement\n- vider le conteneur de statut\n- vider l'interface des messages en attente et la map des outils en attente\n- réinitialiser les références du composant de streaming/message\n- appeler `session.switchSession(...)`\n- vider le conteneur de chat et re-rendre depuis le contexte de session (`renderInitialMessages`)\n- recharger les todos depuis les artefacts de la nouvelle session\n- afficher `Resumed session`\n\nAinsi l'état visible de la conversation/todos est reconstruit depuis le nouveau fichier de session.\n\n## Reprise au démarrage vs changement en session\n\n### Reprise au démarrage (`--continue`, `--resume`, ouverture directe)\n\n- Le fichier de session est choisi avant `createAgentSession(...)`.\n- `sdk.ts` construit `existingSession = sessionManager.buildSessionContext()`.\n- Les messages de l'agent sont restaurés une seule fois lors de la création de la session.\n- Le modèle/la réflexion sont sélectionnés lors de la création (y compris la logique de restauration/repli).\n- Le mode interactif exécute ensuite `#restoreModeFromSession()` pour ré-entrer dans l'état de mode persisté (actuellement plan/plan_paused).\n\n### Changement en session (chemin sélecteur de type `/resume`)\n\n- Utilise `AgentSession.switchSession(...)` sur un `AgentSession` déjà en cours d'exécution.\n- Les messages/modèle/réflexion sont reconstruits immédiatement en place.\n- Les événements hook `session_before_switch`/`session_switch` sont émis.\n- Le chat/les todos de l'interface sont rafraîchis.\n- Aucun appel dédié de restauration de mode post-changement n'est fait dans le flux du sélecteur ; le comportement de ré-entrée de mode n'est pas symétrique avec le `#restoreModeFromSession()` du démarrage.\n\n## Comportement en cas d'échec et cas limites\n\n### Chemins d'annulation\n\n- Annulation du sélecteur CLI -> retourne `null`, l'appelant affiche `No session selected`, le processus sort prématurément.\n- Annulation du sélecteur interactif -> l'éditeur est restauré, pas de changement de session.\n- Annulation par hook (`session_before_switch`) -> `switchSession()` retourne `false`.\n\n### Chemins avec liste vide\n\n- CLI `--resume` (sans valeur) : liste vide affiche `No sessions found` et sort.\n- Sélecteur interactif : liste vide affiche un message et reste annulable.\n\n### Fichier de session cible manquant/invalide\n\nLors de l'ouverture/du changement vers un chemin spécifique (`setSessionFile`) :\n\n- ENOENT -> traité comme vide -> nouvelle session initialisée à ce chemin exact et persistée.\n- en-tête malformé/invalide (ou entrées parsées effectivement illisibles) -> traité comme vide -> nouvelle session initialisée et persistée.\n\nC'est un comportement de récupération, pas un échec dur.\n\n### Échecs durs\n\nLe changement/l'ouverture peut toujours lever une exception en cas de véritables échecs d'E/S (erreurs de permission, échecs de réécriture, etc.), qui sont propagés aux appelants.\n\n### Mises en garde sur la correspondance par préfixe d'ID\n\n- La correspondance d'ID utilise `startsWith` et prend la première correspondance dans la liste triée.\n- Pas d'interface d'ambiguïté si plusieurs sessions partagent le même préfixe.\n- `SessionManager.list(...)` exclut les sessions avec zéro message, donc ces sessions ne sont pas reprenables via la correspondance d'ID/le sélecteur de liste.\n",
	"fr/sessions/session-tree-plan.md": "---\ntitle: Architecture de l'arbre de session\ndescription: >-\n  Session tree architecture with branching, navigation, and parent-child\n  conversation relationships.\nsidebar:\n  order: 2\n  label: Architecture de l'arbre\ni18n:\n  sourceHash: bd8b78d6c33a\n  translator: machine\n---\n\n# Architecture de l'arbre de session (actuelle)\n\nRéférence : [session.md](./session.md)\n\nCe document décrit le fonctionnement actuel de la navigation dans l'arbre de session : modèle d'arbre en mémoire, règles de déplacement des feuilles, comportement de branchement et intégration des extensions/événements.\n\n## En quoi consiste ce sous-système\n\nLa session est stockée sous forme de journal d'entrées en ajout seul, mais le comportement à l'exécution est basé sur un arbre :\n\n- Chaque entrée non-en-tête possède un `id` et un `parentId`.\n- La position active est `leafId` dans `SessionManager`.\n- L'ajout d'une entrée crée toujours un enfant de la feuille courante.\n- Le branchement ne **réécrit pas** l'historique ; il change uniquement l'endroit où la feuille pointe avant le prochain ajout.\n\nFichiers clés :\n\n- `src/session/session-manager.ts` — modèle de données de l'arbre, parcours, déplacement des feuilles, extraction de branche/session\n- `src/session/agent-session.ts` — flux de navigation `/tree`, résumé, émission de hooks/événements\n- `src/modes/components/tree-selector.ts` — comportement interactif de l'interface arborescente et filtrage\n- `src/modes/controllers/selector-controller.ts` — orchestration du sélecteur pour `/tree` et `/branch`\n- `src/modes/controllers/input-controller.ts` — routage des commandes (`/tree`, `/branch`, comportement du double-échap)\n- `src/session/messages.ts` — conversion des entrées `branch_summary`, `compaction` et `custom_message` en messages de contexte LLM\n\n## Modèle de données de l'arbre dans `SessionManager`\n\nIndex à l'exécution :\n\n- `#byId: Map<string, SessionEntry>` — recherche rapide pour toute entrée\n- `#leafId: string | null` — position actuelle dans l'arbre\n- `#labelsById: Map<string, string>` — étiquettes résolues par identifiant d'entrée cible\n\nAPI de l'arbre :\n\n- `getBranch(fromId?)` remonte les liens parents jusqu'à la racine et renvoie le chemin racine→nœud\n- `getTree()` renvoie `SessionTreeNode[]` (`entry`, `children`, `label`)\n  - les liens parents deviennent des tableaux d'enfants\n  - les entrées dont le parent est manquant sont traitées comme des racines\n  - les enfants sont triés du plus ancien au plus récent par horodatage\n- `getChildren(parentId)` renvoie les enfants directs\n- `getLabel(id)` résout l'étiquette actuelle depuis `labelsById`\n\n`getTree()` est une projection à l'exécution ; la persistance reste sous forme d'entrées JSONL en ajout seul.\n\n## Sémantique du déplacement des feuilles\n\nIl existe trois primitives de déplacement des feuilles :\n\n1. `branch(entryId)`\n   - Valide que l'entrée existe\n   - Définit `leafId = entryId`\n   - Aucune nouvelle entrée n'est écrite\n\n2. `resetLeaf()`\n   - Définit `leafId = null`\n   - Le prochain ajout crée une nouvelle entrée racine (`parentId = null`)\n\n3. `branchWithSummary(branchFromId, summary, details?, fromExtension?)`\n   - Accepte `branchFromId: string | null`\n   - Définit `leafId = branchFromId`\n   - Ajoute une entrée `branch_summary` comme enfant de cette feuille\n   - Quand `branchFromId` est `null`, `fromId` est persisté comme `\"root\"`\n\n## Comportement de navigation `/tree` (même fichier de session)\n\n`AgentSession.navigateTree()` est de la navigation, pas un fork de fichier.\n\nFlux :\n\n1. Valider la cible et calculer le chemin abandonné (`collectEntriesForBranchSummary`)\n2. Émettre `session_before_tree` avec `TreePreparation`\n3. Optionnellement résumer les entrées abandonnées (résumé fourni par un hook ou résumeur intégré)\n4. Calculer la nouvelle cible de feuille :\n   - sélection d'un message **utilisateur** : la feuille se déplace vers son parent, et le texte du message est renvoyé pour pré-remplir l'éditeur\n   - sélection d'un **custom_message** : même règle que pour un message utilisateur (feuille = parent, le texte pré-remplit l'éditeur)\n   - sélection de toute autre entrée : feuille = identifiant de l'entrée sélectionnée\n5. Appliquer le déplacement de feuille :\n   - avec résumé : `branchWithSummary(newLeafId, ...)`\n   - sans résumé et `newLeafId === null` : `resetLeaf()`\n   - sinon : `branch(newLeafId)`\n6. Reconstruire le contexte de l'agent à partir de la nouvelle feuille et émettre `session_tree`\n\nImportant : les entrées de résumé sont attachées à la **nouvelle position de navigation**, pas à la fin de la branche abandonnée.\n\n## Comportement de `/branch` (nouveau fichier de session)\n\n`/branch` et `/tree` sont intentionnellement différents :\n\n- `/tree` navigue à l'intérieur du fichier de session actuel.\n- `/branch` crée un nouveau fichier de branche de session (ou un remplacement en mémoire pour le mode non-persistant).\n\nFlux utilisateur de `/branch` (`SelectorController.showUserMessageSelector` → `AgentSession.branch`) :\n\n- La source de branchement doit être un **message utilisateur**.\n- Le texte utilisateur sélectionné est extrait pour pré-remplir l'éditeur.\n- Si le message utilisateur sélectionné est racine (`parentId === null`) : démarrer une nouvelle session via `newSession({ parentSession: previousSessionFile })`.\n- Sinon : `createBranchedSession(selectedEntry.parentId)` pour forker l'historique jusqu'à la limite du prompt sélectionné.\n\nSpécificités de `SessionManager.createBranchedSession(leafId)` :\n\n- Construit le chemin racine→feuille via `getBranch(leafId)` ; lève une exception si absent.\n- Exclut les entrées `label` existantes du chemin copié.\n- Reconstruit des entrées d'étiquettes fraîches à partir de `labelsById` résolu pour les entrées qui restent dans le chemin.\n- Mode persistant : écrit un nouveau fichier JSONL et bascule le gestionnaire dessus ; renvoie le nouveau chemin de fichier.\n- Mode en mémoire : remplace les entrées en mémoire ; renvoie `undefined`.\n\n## Reconstruction du contexte et intégration résumé/personnalisé\n\n`buildSessionContext()` (dans `session-manager.ts`) résout le chemin actif racine→feuille et construit l'état effectif du contexte LLM :\n\n- Suit le dernier état thinking/model/mode/ttsr sur le chemin.\n- Gère la dernière compaction sur le chemin :\n  - émet d'abord le résumé de compaction\n  - rejoue les messages conservés depuis `firstKeptEntryId` jusqu'au point de compaction\n  - puis rejoue les messages post-compaction\n- Inclut les entrées `branch_summary` et `custom_message` en tant qu'objets `AgentMessage`.\n\n`session/messages.ts` mappe ensuite ces types de messages pour l'entrée du modèle :\n\n- `branchSummary` et `compactionSummary` deviennent des messages de contexte modélisés avec le rôle utilisateur\n- `custom`/`hookMessage` deviennent des messages de contenu avec le rôle utilisateur\n\nAinsi, le déplacement dans l'arbre modifie le contexte en changeant le chemin de la feuille active, et non en mutant les anciennes entrées.\n\n## Étiquettes et comportement de l'interface arborescente\n\nPersistance des étiquettes :\n\n- `appendLabelChange(targetId, label?)` écrit des entrées `label` sur la chaîne de feuille courante.\n- `labelsById` est mis à jour immédiatement (ajout ou suppression).\n- `getTree()` résout l'étiquette actuelle sur chaque nœud retourné.\n\nComportement du sélecteur d'arbre (`tree-selector.ts`) :\n\n- Aplatit l'arbre pour la navigation, conserve la mise en surbrillance du chemin actif et priorise l'affichage de la branche active en premier.\n- Prend en charge les modes de filtrage : `default`, `no-tools`, `user-only`, `labeled-only`, `all`.\n- Prend en charge la recherche en texte libre sur le contenu sémantique rendu.\n- `Shift+L` ouvre l'édition d'étiquette en ligne et écrit via `appendLabelChange`.\n\nRoutage des commandes :\n\n- `/tree` ouvre toujours le sélecteur d'arbre.\n- `/branch` ouvre le sélecteur de messages utilisateur sauf si `doubleEscapeAction=tree`, auquel cas il utilise également l'interface du sélecteur d'arbre.\n\n## Points d'extension et hooks pour les opérations sur l'arbre\n\nAPI d'extension au moment de la commande (`ExtensionCommandContext`) :\n\n- `branch(entryId)` — créer un fichier de session branché\n- `navigateTree(targetId, { summarize? })` — se déplacer dans l'arbre/fichier courant\n\nÉvénements autour de la navigation dans l'arbre :\n\n- `session_before_tree`\n  - reçoit `TreePreparation` :\n    - `targetId`\n    - `oldLeafId`\n    - `commonAncestorId`\n    - `entriesToSummarize`\n    - `userWantsSummary`\n  - peut annuler la navigation\n  - peut fournir un payload de résumé utilisé à la place du résumeur intégré\n  - reçoit un `signal` d'abandon (chemin d'annulation par Échap)\n- `session_tree`\n  - émet `newLeafId`, `oldLeafId`\n  - inclut `summaryEntry` lorsqu'un résumé a été créé\n  - `fromExtension` indique l'origine du résumé\n\nHooks de cycle de vie adjacents mais liés :\n\n- `session_before_branch` / `session_branch` pour le flux `/branch`\n- `session_before_compact`, `session.compacting`, `session_compact` pour les entrées de compaction qui affectent ensuite la reconstruction du contexte de l'arbre\n\n## Contraintes réelles et conditions limites\n\n- `branch()` ne peut pas cibler `null` ; utilisez `resetLeaf()` pour l'état racine-avant-première-entrée.\n- `branchWithSummary()` prend en charge la cible `null` et enregistre `fromId: \"root\"`.\n- Sélectionner la feuille courante dans le sélecteur d'arbre est une opération sans effet.\n- Le résumé nécessite un modèle actif ; en son absence, la navigation avec résumé échoue immédiatement.\n- Si le résumé est abandonné, la navigation est annulée et la feuille reste inchangée.\n- Les sessions en mémoire ne renvoient jamais de chemin de fichier de branche depuis `createBranchedSession`.\n\n## Compatibilité héritée encore présente\n\nLes migrations de session s'exécutent toujours au chargement :\n\n- v1→v2 ajoute `id`/`parentId` et convertit l'ancre d'index de compaction en ancre d'identifiant\n- v2→v3 migre le rôle hérité `hookMessage` vers `custom`\n\nLe comportement actuel à l'exécution utilise la sémantique d'arbre version 3 après migration.\n",
	"fr/sessions/session.md": "---\ntitle: Stockage de session et modèle d'entrée\ndescription: >-\n  Append-only session storage model with entry types, persistence, and migration\n  between formats.\nsidebar:\n  order: 1\n  label: Stockage et modèle d'entrée\ni18n:\n  sourceHash: 42fe17549e00\n  translator: machine\n---\n\n# Stockage de session et modèle d'entrée\n\nCe document est la source de vérité concernant la manière dont les sessions de l'agent de codage sont représentées, persistées, migrées et reconstruites à l'exécution.\n\n## Portée\n\nCouvre :\n\n- Le format JSONL des sessions et le versionnage\n- La taxonomie des entrées et la sémantique arborescente (`id`/`parentId` + pointeur de feuille)\n- Le comportement de migration/compatibilité lors du chargement de fichiers anciens ou malformés\n- La reconstruction du contexte (`buildSessionContext`)\n- Les garanties de persistance, le comportement en cas d'échec, la troncature/externalisation des blobs\n- Les abstractions de stockage (`FileSessionStorage`, `MemorySessionStorage`) et les utilitaires associés\n\nNe couvre pas le comportement de rendu de l'interface `/tree` au-delà des sémantiques qui affectent les données de session.\n\n## Fichiers d'implémentation\n\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`src/session/messages.ts`](../../packages/coding-agent/src/session/messages.ts)\n- [`src/session/session-storage.ts`](../../packages/coding-agent/src/session/session-storage.ts)\n- [`src/session/history-storage.ts`](../../packages/coding-agent/src/session/history-storage.ts)\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts)\n\n## Organisation sur le disque\n\nEmplacement par défaut du fichier de session :\n\n```text\n~/.xcsh/agent/sessions/--<cwd-encoded>--/<timestamp>_<sessionId>.jsonl\n```\n\n`<cwd-encoded>` est dérivé du répertoire de travail en supprimant le slash initial et en remplaçant `/`, `\\\\` et `:` par `-`.\n\nEmplacement du magasin de blobs :\n\n```text\n~/.xcsh/agent/blobs/<sha256>\n```\n\nLes fichiers de fil d'Ariane du terminal sont écrits sous :\n\n```text\n~/.xcsh/agent/terminal-sessions/<terminal-id>\n```\n\nLe contenu du fil d'Ariane se compose de deux lignes : le répertoire de travail original, puis le chemin du fichier de session. `continueRecent()` privilégie ce pointeur limité au terminal avant de scanner le mtime le plus récent.\n\n## Format de fichier\n\nLes fichiers de session sont en JSONL : un objet JSON par ligne.\n\n- La ligne 1 est toujours l'en-tête de session (`type: \"session\"`).\n- Les lignes restantes sont des valeurs `SessionEntry`.\n- Les entrées sont en ajout uniquement à l'exécution ; la navigation entre branches déplace un pointeur (`leafId`) plutôt que de modifier les entrées existantes.\n\n### En-tête (`SessionHeader`)\n\n```json\n{\n  \"type\": \"session\",\n  \"version\": 3,\n  \"id\": \"1f9d2a6b9c0d1234\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\",\n  \"cwd\": \"/work/pi\",\n  \"title\": \"optional session title\",\n  \"parentSession\": \"optional lineage marker\"\n}\n```\n\nNotes :\n\n- `version` est optionnel dans les fichiers v1 ; son absence signifie v1.\n- `parentSession` est une chaîne de lignage opaque. Le code actuel écrit soit un identifiant de session, soit un chemin de session selon le flux (`fork`, `forkFrom`, `createBranchedSession`, ou `newSession({ parentSession })` explicite). À traiter comme des métadonnées, pas comme une clé étrangère typée.\n\n### Base d'entrée (`SessionEntryBase`)\n\nToutes les entrées non-en-tête incluent :\n\n```json\n{\n  \"type\": \"...\",\n  \"id\": \"8-char-id\",\n  \"parentId\": \"previous-or-branch-parent\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\"\n}\n```\n\n`parentId` peut être `null` pour une entrée racine (premier ajout, ou après `resetLeaf()`).\n\n## Taxonomie des entrées\n\n`SessionEntry` est l'union de :\n\n- `message`\n- `thinking_level_change`\n- `model_change`\n- `compaction`\n- `branch_summary`\n- `custom`\n- `custom_message`\n- `label`\n- `ttsr_injection`\n- `session_init`\n- `mode_change`\n\n### `message`\n\nStocke directement un `AgentMessage`.\n\n```json\n{\n  \"type\": \"message\",\n  \"id\": \"a1b2c3d4\",\n  \"parentId\": null,\n  \"timestamp\": \"2026-02-16T10:21:00.000Z\",\n  \"message\": {\n    \"role\": \"assistant\",\n    \"provider\": \"anthropic\",\n    \"model\": \"claude-sonnet-4-5\",\n    \"content\": [{ \"type\": \"text\", \"text\": \"Done.\" }],\n    \"usage\": { \"input\": 100, \"output\": 20, \"cacheRead\": 0, \"cacheWrite\": 0, \"cost\": { \"input\": 0, \"output\": 0, \"cacheRead\": 0, \"cacheWrite\": 0, \"total\": 0 } },\n    \"timestamp\": 1760000000000\n  }\n}\n```\n\n### `model_change`\n\n```json\n{\n  \"type\": \"model_change\",\n  \"id\": \"b1c2d3e4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:21:30.000Z\",\n  \"model\": \"openai/gpt-4o\",\n  \"role\": \"default\"\n}\n```\n\n`role` est optionnel ; son absence est traitée comme `default` lors de la reconstruction du contexte.\n\n### `thinking_level_change`\n\n```json\n{\n  \"type\": \"thinking_level_change\",\n  \"id\": \"c1d2e3f4\",\n  \"parentId\": \"b1c2d3e4\",\n  \"timestamp\": \"2026-02-16T10:22:00.000Z\",\n  \"thinkingLevel\": \"high\"\n}\n```\n\n### `compaction`\n\n```json\n{\n  \"type\": \"compaction\",\n  \"id\": \"d1e2f3a4\",\n  \"parentId\": \"c1d2e3f4\",\n  \"timestamp\": \"2026-02-16T10:23:00.000Z\",\n  \"summary\": \"Conversation summary\",\n  \"shortSummary\": \"Short recap\",\n  \"firstKeptEntryId\": \"a1b2c3d4\",\n  \"tokensBefore\": 42000,\n  \"details\": { \"readFiles\": [\"src/a.ts\"] },\n  \"preserveData\": { \"hookState\": true },\n  \"fromExtension\": false\n}\n```\n\n### `branch_summary`\n\n```json\n{\n  \"type\": \"branch_summary\",\n  \"id\": \"e1f2a3b4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:24:00.000Z\",\n  \"fromId\": \"a1b2c3d4\",\n  \"summary\": \"Summary of abandoned path\",\n  \"details\": { \"note\": \"optional\" },\n  \"fromExtension\": true\n}\n```\n\nSi le branchement se fait depuis la racine (`branchFromId === null`), `fromId` est la chaîne littérale `\"root\"`.\n\n### `custom`\n\nPersistance de l'état des extensions ; ignoré par `buildSessionContext`.\n\n```json\n{\n  \"type\": \"custom\",\n  \"id\": \"f1a2b3c4\",\n  \"parentId\": \"e1f2a3b4\",\n  \"timestamp\": \"2026-02-16T10:25:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"data\": { \"state\": 1 }\n}\n```\n\n### `custom_message`\n\nMessage fourni par une extension qui participe au contexte LLM.\n\n```json\n{\n  \"type\": \"custom_message\",\n  \"id\": \"a2b3c4d5\",\n  \"parentId\": \"f1a2b3c4\",\n  \"timestamp\": \"2026-02-16T10:26:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"content\": \"Injected context\",\n  \"display\": true,\n  \"details\": { \"debug\": false }\n}\n```\n\n### `label`\n\n```json\n{\n  \"type\": \"label\",\n  \"id\": \"b2c3d4e5\",\n  \"parentId\": \"a2b3c4d5\",\n  \"timestamp\": \"2026-02-16T10:27:00.000Z\",\n  \"targetId\": \"a1b2c3d4\",\n  \"label\": \"checkpoint\"\n}\n```\n\n`label: undefined` efface un label pour `targetId`.\n\n### `ttsr_injection`\n\n```json\n{\n  \"type\": \"ttsr_injection\",\n  \"id\": \"c2d3e4f5\",\n  \"parentId\": \"b2c3d4e5\",\n  \"timestamp\": \"2026-02-16T10:28:00.000Z\",\n  \"injectedRules\": [\"ruleA\", \"ruleB\"]\n}\n```\n\n### `session_init`\n\n```json\n{\n  \"type\": \"session_init\",\n  \"id\": \"d2e3f4a5\",\n  \"parentId\": \"c2d3e4f5\",\n  \"timestamp\": \"2026-02-16T10:29:00.000Z\",\n  \"systemPrompt\": \"...\",\n  \"task\": \"...\",\n  \"tools\": [\"read\", \"edit\"],\n  \"outputSchema\": { \"type\": \"object\" }\n}\n```\n\n### `mode_change`\n\n```json\n{\n  \"type\": \"mode_change\",\n  \"id\": \"e2f3a4b5\",\n  \"parentId\": \"d2e3f4a5\",\n  \"timestamp\": \"2026-02-16T10:30:00.000Z\",\n  \"mode\": \"plan\",\n  \"data\": { \"planFile\": \"/tmp/plan.md\" }\n}\n```\n\n## Versionnage et migration\n\nVersion actuelle de session : `3`.\n\n### v1 -> v2\n\nAppliquée lorsque le `version` de l'en-tête est absent ou `< 2` :\n\n- Ajoute `id` et `parentId` à chaque entrée non-en-tête.\n- Reconstruit une chaîne parentale linéaire en utilisant l'ordre du fichier.\n- Migre le champ de compaction `firstKeptEntryIndex` -> `firstKeptEntryId` lorsqu'il est présent.\n- Définit `version = 2` dans l'en-tête.\n\n### v2 -> v3\n\nAppliquée lorsque le `version` de l'en-tête est `< 3` :\n\n- Pour les entrées `message` : réécrit l'ancien `message.role === \"hookMessage\"` en `\"custom\"`.\n- Définit `version = 3` dans l'en-tête.\n\n### Déclencheur de migration et persistance\n\n- Les migrations s'exécutent lors du chargement de session (`setSessionFile`).\n- Si une migration a été exécutée, le fichier entier est réécrit sur le disque immédiatement.\n- La migration modifie d'abord les entrées en mémoire, puis persiste le JSONL réécrit.\n\n## Comportement de chargement et compatibilité\n\nComportement de `loadEntriesFromFile(path)` :\n\n- Fichier manquant (`ENOENT`) -> retourne `[]`.\n- Les lignes non analysables sont gérées par le parseur JSONL tolérant (`parseJsonlLenient`).\n- Si la première entrée analysée n'est pas un en-tête de session valide (`type !== \"session\"` ou `id` de type chaîne manquant) -> retourne `[]`.\n\nComportement de `SessionManager.setSessionFile()` :\n\n- `[]` retourné par le chargeur est traité comme une session vide/inexistante et remplacé par un nouveau fichier de session initialisé à ce chemin.\n- Les fichiers valides sont chargés, migrés si nécessaire, les références de blobs sont résolues, puis indexés.\n\n## Sémantique de l'arbre et des feuilles\n\nLe modèle sous-jacent est un arbre en ajout uniquement + un pointeur de feuille mutable :\n\n- Chaque méthode d'ajout crée exactement une nouvelle entrée dont le `parentId` est le `leafId` actuel.\n- La nouvelle entrée devient le nouveau `leafId`.\n- `branch(entryId)` déplace uniquement `leafId` ; les entrées existantes restent inchangées.\n- `resetLeaf()` définit `leafId = null` ; le prochain ajout crée une nouvelle entrée racine (`parentId: null`).\n- `branchWithSummary()` définit la feuille sur la cible de branchement et ajoute une entrée `branch_summary`.\n\n`getEntries()` retourne toutes les entrées non-en-tête dans l'ordre d'insertion. Les entrées existantes ne sont pas supprimées en fonctionnement normal ; les réécritures préservent l'historique logique tout en mettant à jour la représentation (migrations, déplacements, utilitaires de réécriture ciblée).\n\n## Reconstruction du contexte (`buildSessionContext`)\n\n`buildSessionContext(entries, leafId, byId?)` détermine ce qui est envoyé au modèle.\n\nAlgorithme :\n\n1. Déterminer la feuille :\n   - `leafId === null` -> retourner un contexte vide.\n   - `leafId` explicite -> utiliser cette entrée si trouvée.\n   - sinon, repli sur la dernière entrée.\n2. Remonter la chaîne `parentId` de la feuille à la racine et inverser pour obtenir le chemin racine->feuille.\n3. Dériver l'état à l'exécution à travers le chemin :\n   - `thinkingLevel` depuis le dernier `thinking_level_change` (par défaut `\"off\"`)\n   - carte des modèles depuis les entrées `model_change` (`role ?? \"default\"`)\n   - `models.default` de repli depuis le provider/modèle du message assistant s'il n'y a pas de changement de modèle explicite\n   - `injectedTtsrRules` dédupliquées depuis toutes les entrées `ttsr_injection`\n   - mode/modeData depuis le dernier `mode_change` (mode par défaut `\"none\"`)\n4. Construire la liste des messages :\n   - Les entrées `message` passent directement\n   - Les entrées `custom_message` deviennent des AgentMessages `custom` via `createCustomMessage`\n   - Les entrées `branch_summary` deviennent des AgentMessages `branchSummary` via `createBranchSummaryMessage`\n   - Si une `compaction` existe sur le chemin :\n     - Émettre d'abord le résumé de compaction (`createCompactionSummaryMessage`)\n     - Émettre les entrées du chemin à partir de `firstKeptEntryId` jusqu'à la limite de compaction\n     - Émettre les entrées après la limite de compaction\n\nLes entrées `custom` et `session_init` n'injectent pas directement de contexte dans le modèle.\n\n## Garanties de persistance et modèle de défaillance\n\n### Persistance vs en mémoire\n\n- `SessionManager.create/open/continueRecent/forkFrom` -> mode persistant (`persist = true`).\n- `SessionManager.inMemory` -> mode non persistant (`persist = false`) avec `MemorySessionStorage`.\n\n### Pipeline d'écriture\n\nLes écritures sont sérialisées via une chaîne de promesses interne (`#persistChain`) et `NdjsonFileWriter`.\n\n- `append*` met à jour l'état en mémoire immédiatement.\n- La persistance est différée jusqu'à ce qu'au moins un message assistant existe.\n  - Avant le premier assistant : les entrées sont conservées en mémoire ; aucun ajout au fichier ne se produit.\n  - Lorsque le premier assistant existe : l'intégralité de la session en mémoire est vidée dans le fichier.\n  - Par la suite : les nouvelles entrées sont ajoutées de manière incrémentale.\n\nJustification dans le code : éviter de persister des sessions qui n'ont jamais produit de réponse assistant.\n\n### Opérations de durabilité\n\n- `flush()` vide l'écrivain et appelle `fsync()`.\n- Les réécritures atomiques complètes (`#rewriteFile`) écrivent dans un fichier temporaire, effectuent flush+fsync, ferment, puis renomment par-dessus la cible.\n- Utilisées pour les migrations, `setSessionName`, `rewriteEntries`, les opérations de déplacement et les réécritures d'arguments d'appels d'outils.\n\n### Comportement en cas d'erreur\n\n- Les erreurs de persistance sont verrouillées (`#persistError`) et relancées lors des opérations suivantes.\n- La première erreur est journalisée une seule fois avec le contexte du fichier de session.\n- La fermeture de l'écrivain est en meilleur effort mais propage la première erreur significative.\n\n## Contrôles de taille des données et externalisation des blobs\n\nAvant de persister les entrées :\n\n- Les grandes chaînes sont tronquées à `MAX_PERSIST_CHARS` (500 000 caractères) avec notification :\n  - `\"[Session persistence truncated large content]\"`\n- Les champs transitoires `partialJson` et `jsonlEvents` sont supprimés.\n- Si l'objet a à la fois `content` et `lineCount`, le nombre de lignes est recalculé après troncature.\n- Les blocs d'images dans les tableaux `content` avec une longueur base64 >= 1024 sont externalisés en références de blob :\n  - stockés sous la forme `blob:sha256:<hash>`\n  - les octets bruts sont écrits dans le magasin de blobs (`BlobStore.put`)\n\nAu chargement, les références de blob sont résolues en base64 pour les blocs d'images des message/custom_message.\n\n## Abstractions de stockage\n\nL'interface `SessionStorage` fournit toutes les opérations du système de fichiers utilisées par `SessionManager` :\n\n- synchrones : `ensureDirSync`, `existsSync`, `writeTextSync`, `statSync`, `listFilesSync`\n- asynchrones : `exists`, `readText`, `readTextPrefix`, `writeText`, `rename`, `unlink`, `openWriter`\n\nImplémentations :\n\n- `FileSessionStorage` : système de fichiers réel (Bun + node fs)\n- `MemorySessionStorage` : implémentation en mémoire basée sur une map pour les tests/sessions non persistantes\n\n`SessionStorageWriter` expose `writeLine`, `flush`, `fsync`, `close`, `getError`.\n\n## Utilitaires de découverte de sessions\n\nDéfinis dans `session-manager.ts` :\n\n- `getRecentSessions(sessionDir, limit)` -> métadonnées légères pour l'interface/sélecteur de session\n- `findMostRecentSession(sessionDir)` -> la plus récente par mtime\n- `list(cwd, sessionDir?)` -> sessions dans le périmètre d'un projet\n- `listAll()` -> sessions à travers tous les périmètres de projets sous `~/.xcsh/agent/sessions`\n\nL'extraction des métadonnées ne lit qu'un préfixe (`readTextPrefix(..., 4096)`) lorsque c'est possible.\n\n## Connexe mais distinct : stockage de l'historique des prompts\n\n`HistoryStorage` (`history-storage.ts`) est un sous-système SQLite séparé pour le rappel/la recherche de prompts, pas pour la relecture de sessions.\n\n- Base de données : `~/.xcsh/agent/history.db`\n- Table : `history(id, prompt, created_at, cwd)`\n- Index FTS5 : `history_fts` avec synchronisation maintenue par déclencheurs\n- Déduplique les prompts identiques consécutifs en utilisant un cache en mémoire du dernier prompt\n- Insertion asynchrone (`setImmediate`) pour que la capture de prompt ne bloque pas l'exécution du tour\n\nUtilisez les fichiers de session pour le graphe de conversation/la relecture d'état ; utilisez `HistoryStorage` pour l'expérience utilisateur de l'historique des prompts.\n",
	"fr/sessions/ttsr-injection-lifecycle.md": "---\ntitle: Cycle de vie de l'injection TTSR\ndescription: >-\n  Cycle de vie de l'injection TTSR (outil utilisé, résultat d'outil, rappel\n  système) pour la gestion du contexte.\nsidebar:\n  order: 9\n  label: Injection TTSR\ni18n:\n  sourceHash: d6179a286584\n  translator: machine\n---\n\n# Cycle de vie de l'injection TTSR\n\nCe document couvre le chemin d'exécution actuel des Time Traveling Stream Rules (TTSR), depuis la découverte des règles jusqu'à l'interruption du flux, l'injection de nouvelle tentative, les notifications d'extension et la gestion de l'état de session.\n\n## Fichiers d'implémentation\n\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/export/ttsr.ts`](../../packages/coding-agent/src/export/ttsr.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/prompts/system/ttsr-interrupt.md`](../../packages/coding-agent/src/prompts/system/ttsr-interrupt.md)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/types.ts`](../../packages/coding-agent/src/extensibility/extensions/types.ts)\n- [`../src/extensibility/hooks/types.ts`](../../packages/coding-agent/src/extensibility/hooks/types.ts)\n- [`../src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n\n## 1. Flux de découverte et enregistrement des règles\n\nÀ la création de la session, `createAgentSession()` charge toutes les règles découvertes et construit un `TtsrManager` :\n\n```ts\nconst ttsrSettings = settings.getGroup(\"ttsr\");\nconst ttsrManager = new TtsrManager(ttsrSettings);\nconst rulesResult = await loadCapability<Rule>(ruleCapability.id, { cwd });\nfor (const rule of rulesResult.items) {\n  if (rule.ttsrTrigger) ttsrManager.addRule(rule);\n}\n```\n\n### Comportement de déduplication avant enregistrement\n\n`loadCapability(\"rules\")` effectue une déduplication par `rule.name` selon la sémantique « le premier l'emporte » (la priorité de fournisseur la plus élevée est traitée en premier). Les doublons masqués sont supprimés avant l'enregistrement TTSR.\n\n### Comportement de `TtsrManager.addRule()`\n\nL'enregistrement est ignoré lorsque :\n\n- `rule.ttsrTrigger` est absent\n- une règle portant le même `rule.name` a déjà été enregistrée dans ce gestionnaire\n- la compilation de l'expression régulière échoue (`new RegExp(rule.ttsrTrigger)` lève une exception)\n\nLes déclencheurs d'expression régulière invalides sont consignés comme avertissements et ignorés ; le démarrage de la session se poursuit.\n\n### Mise en garde concernant les paramètres\n\n`TtsrSettings.enabled` est chargé dans le gestionnaire, mais n'est actuellement pas vérifié lors du contrôle d'accès à l'exécution. Si des règles existent, la correspondance s'exécute tout de même.\n\n## 2. Cycle de vie du moniteur de flux\n\nLa détection TTSR s'exécute dans `AgentSession.#handleAgentEvent`.\n\n### Début de tour\n\nSur `turn_start`, le tampon de flux est réinitialisé :\n\n- `ttsrManager.resetBuffer()`\n\n### Pendant le flux (`message_update`)\n\nLorsque des mises à jour de l'assistant arrivent et que des règles existent :\n\n- surveiller `text_delta` et `toolcall_delta`\n- ajouter le delta dans le tampon du gestionnaire\n- appeler `check(buffer)`\n\n`check()` itère sur les règles enregistrées et retourne toutes les règles correspondantes qui passent la politique de répétition (`#canTrigger`).\n\n## 3. Décision de déclenchement et chemin d'abandon immédiat\n\nLorsqu'une ou plusieurs règles correspondent :\n\n1. `markInjected(matches)` enregistre les noms de règles dans l'état d'injection du gestionnaire.\n2. les règles correspondantes sont mises en file d'attente dans `#pendingTtsrInjections`.\n3. `#ttsrAbortPending = true`.\n4. `agent.abort()` est appelé immédiatement.\n5. l'événement `ttsr_triggered` est émis de manière asynchrone (fire-and-forget).\n6. la nouvelle tentative est planifiée via `setTimeout(..., 50)`.\n\nL'abandon n'est pas bloqué par les rappels d'extension.\n\n## 4. Planification de la nouvelle tentative, mode de contexte et injection de rappel\n\nAprès le délai de 50 ms :\n\n1. `#ttsrAbortPending = false`\n2. lecture de `ttsrManager.getSettings().contextMode`\n3. si `contextMode === \"discard\"`, suppression de la sortie partielle de l'assistant avec `agent.popMessage()`\n4. construction du contenu d'injection à partir des règles en attente en utilisant le modèle `ttsr-interrupt.md`\n5. ajout d'un message utilisateur synthétique contenant un bloc `<system-interrupt ...>` par règle\n6. appel de `agent.continue()` pour relancer la génération\n\nLe contenu du modèle est :\n\n```xml\n<system-interrupt reason=\"rule_violation\" rule=\"{{name}}\" path=\"{{path}}\">\n...\n{{content}}\n</system-interrupt>\n```\n\nLes injections en attente sont effacées après la génération du contenu.\n\n### Comportement de `contextMode` sur la sortie partielle\n\n- `discard` : le message d'assistant partiel/abandonné est supprimé avant la nouvelle tentative.\n- `keep` : la sortie partielle de l'assistant reste dans l'état de conversation ; le rappel est ajouté après celui-ci.\n\n## 5. Politique de répétition et logique d'écart\n\n`TtsrManager` suit `#messageCount` et `lastInjectedAt` par règle.\n\n### `repeatMode: \"once\"`\n\nUne règle ne peut se déclencher qu'une seule fois après avoir un enregistrement d'injection.\n\n### `repeatMode: \"after-gap\"`\n\nUne règle peut se redéclencher uniquement lorsque :\n\n- `messageCount - lastInjectedAt >= repeatGap`\n\n`messageCount` s'incrémente sur `turn_end`, donc l'écart est mesuré en tours complétés, non en fragments de flux.\n\n## 6. Émission d'événements et surfaces d'extension/hook\n\n### Événement de session\n\n`AgentSessionEvent` inclut :\n\n```ts\n{ type: \"ttsr_triggered\"; rules: Rule[] }\n```\n\n### Exécuteur d'extension\n\n`#emitSessionEvent()` route l'événement vers :\n\n- les écouteurs d'extension (`ExtensionRunner.emit({ type: \"ttsr_triggered\", rules })`)\n- les abonnés de session locaux\n\n### Typage des hooks et des outils personnalisés\n\n- l'API d'extension expose `on(\"ttsr_triggered\", ...)`\n- l'API de hook expose `on(\"ttsr_triggered\", ...)`\n- les outils personnalisés reçoivent `onSession({ reason: \"ttsr_triggered\", rules })`\n\n### Différence de rendu en mode interactif\n\nLe mode interactif utilise `session.isTtsrAbortPending` pour supprimer l'affichage de la raison d'arrêt de l'assistant abandonné comme un échec visible lors d'une interruption TTSR, et affiche un `TtsrNotificationComponent` lorsque l'événement arrive.\n\n## 7. État de persistance et de reprise (implémentation actuelle)\n\n`SessionManager` dispose d'une prise en charge complète du schéma pour la persistance des règles injectées :\n\n- type d'entrée : `ttsr_injection`\n- API d'ajout : `appendTtsrInjection(ruleNames)`\n- API de requête : `getInjectedTtsrRules()`\n- la reconstruction du contexte inclut `SessionContext.injectedTtsrRules`\n\n`TtsrManager` prend également en charge la restauration via `restoreInjected(ruleNames)`.\n\n### État du câblage actuel\n\nDans le chemin d'exécution actuel :\n\n- `AgentSession` n'ajoute pas d'entrées `ttsr_injection` lorsque TTSR se déclenche.\n- `createAgentSession()` ne restaure pas `existingSession.injectedTtsrRules` dans `ttsrManager`.\n\nEffet net : la suppression des règles injectées est appliquée en mémoire pour le processus en cours, mais n'est pas actuellement persistée/restaurée lors d'un rechargement/reprise de session par ce chemin.\n\n## 8. Limites de concurrence et garanties d'ordre\n\n### Rappel d'abandon et de nouvelle tentative\n\n- l'abandon est synchrone du point de vue du gestionnaire TTSR (`agent.abort()` est appelé immédiatement)\n- la nouvelle tentative est différée par un minuteur (50 ms)\n- la notification d'extension est asynchrone et intentionnellement non attendue avant la planification de l'abandon/nouvelle tentative\n\n### Correspondances multiples dans la même fenêtre de flux\n\n`check()` retourne toutes les règles éligibles correspondantes actuellement. Elles sont injectées en lot dans le prochain message de nouvelle tentative.\n\n### Entre l'abandon et la continuation\n\nPendant la fenêtre du minuteur, l'état peut changer (interruption de l'utilisateur, actions de mode, événements supplémentaires). L'appel de nouvelle tentative est au mieux : `agent.continue().catch(() => {})` absorbe les erreurs de suivi.\n\n## 9. Résumé des cas limites\n\n- Expression régulière `ttsr_trigger` invalide : ignorée avec avertissement ; les autres règles continuent.\n- Noms de règles en double au niveau de la couche de capacité : les doublons de priorité inférieure sont masqués avant l'enregistrement.\n- Noms en double au niveau du gestionnaire : le second enregistrement est ignoré.\n- `contextMode: \"keep\"` : la sortie partielle en violation peut rester dans le contexte avant la nouvelle tentative avec rappel.\n- La répétition après écart dépend des incréments du compteur de tours sur `turn_end` ; les fragments en cours de tour ne font pas avancer les compteurs d'écart.\n",
	"fr/tui/theme.md": "---\ntitle: Référence de la thématisation\ndescription: >-\n  Référence de la thématisation TUI avec les jetons de couleur, les paramètres\n  de police et la personnalisation des thèmes.\nsidebar:\n  order: 3\n  label: Thématisation\ni18n:\n  sourceHash: 7e962a7da157\n  translator: machine\n---\n\n# Référence de la thématisation\n\nCe document décrit le fonctionnement de la thématisation dans le coding-agent aujourd'hui : schéma, chargement, comportement à l'exécution et modes de défaillance.\n\n## Ce que le système de thèmes contrôle\n\nLe système de thèmes pilote :\n\n- les jetons de couleur premier plan/arrière-plan utilisés dans l'ensemble du TUI\n- les adaptateurs de style markdown (`getMarkdownTheme()`)\n- les adaptateurs de sélecteur/éditeur/liste de paramètres (`getSelectListTheme()`, `getEditorTheme()`, `getSettingsListTheme()`)\n- le préréglage de symboles + les surcharges de symboles (`unicode`, `nerd`, `ascii`)\n- les couleurs de coloration syntaxique utilisées par le surligneur natif (`@f5-sales-demo/pi-natives`)\n- les couleurs des segments de la barre d'état\n\nImplémentation principale : `src/modes/theme/theme.ts`.\n\n## Structure JSON du thème\n\nLes fichiers de thème sont des objets JSON validés par rapport au schéma d'exécution dans `theme.ts` (`ThemeJsonSchema`) et reflétés par `src/modes/theme/theme-schema.json`.\n\nChamps de niveau supérieur :\n\n- `name` (requis)\n- `colors` (requis ; tous les jetons de couleur sont requis)\n- `vars` (optionnel ; variables de couleur réutilisables)\n- `export` (optionnel ; couleurs d'export HTML)\n- `symbols` (optionnel)\n  - `preset` (optionnel : `unicode | nerd | ascii`)\n  - `overrides` (optionnel : surcharges clé/valeur pour `SymbolKey`)\n\nLes valeurs de couleur acceptent :\n\n- chaîne hexadécimale (`\"#RRGGBB\"`)\n- index de couleur 256 (`0..255`)\n- chaîne de référence de variable (résolue via `vars`)\n- chaîne vide (`\"\"`) signifiant la valeur par défaut du terminal (`\\x1b[39m` premier plan, `\\x1b[49m` arrière-plan)\n\n## Jetons de couleur requis (actuels)\n\nTous les jetons ci-dessous sont requis dans `colors`.\n\n### Texte principal et bordures (11)\n\n`accent`, `border`, `borderAccent`, `borderMuted`, `success`, `error`, `warning`, `muted`, `dim`, `text`, `thinkingText`\n\n### Blocs d'arrière-plan (7)\n\n`selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`, `statusLineBg`\n\n### Texte des messages/outils (5)\n\n`userMessageText`, `customMessageText`, `customMessageLabel`, `toolTitle`, `toolOutput`\n\n### Markdown (10)\n\n`mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet`\n\n### Diff d'outils + coloration syntaxique (12)\n\n`toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext`,\n`syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation`\n\n### Bordures de mode/réflexion (8)\n\n`thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh`, `bashMode`, `pythonMode`\n\n### Couleurs des segments de la barre d'état (14)\n\n`statusLineSep`, `statusLineModel`, `statusLinePath`, `statusLineGitClean`, `statusLineGitDirty`, `statusLineContext`, `statusLineSpend`, `statusLineStaged`, `statusLineDirty`, `statusLineUntracked`, `statusLineOutput`, `statusLineCost`, `statusLineSubagents`\n\n## Jetons optionnels\n\n### Section `export` (optionnelle)\n\nUtilisée pour les aides à la thématisation de l'export HTML :\n\n- `export.pageBg`\n- `export.cardBg`\n- `export.infoBg`\n\nSi omis, le code d'export dérive les valeurs par défaut à partir des couleurs résolues du thème.\n\n### Section `symbols` (optionnelle)\n\n- `symbols.preset` définit un jeu de symboles par défaut au niveau du thème.\n- `symbols.overrides` peut surcharger des valeurs individuelles de `SymbolKey`.\n\nOrdre de priorité à l'exécution :\n\n1. surcharge `symbolPreset` des paramètres (si définie)\n2. `symbols.preset` du JSON du thème\n3. valeur de repli `\"unicode\"`\n\nLes clés de surcharge invalides sont ignorées et journalisées (`logger.debug`).\n\n## Sources de thèmes intégrés vs personnalisés\n\nOrdre de recherche des thèmes (`loadThemeJson`) :\n\n1. thèmes intégrés embarqués (`defaults/xcsh-dark.json` et `defaults/xcsh-light.json` compilés dans `defaultThemes`)\n2. fichier de thème personnalisé : `<customThemesDir>/<name>.json`\n\nLe répertoire des thèmes personnalisés provient de `getCustomThemesDir()` :\n\n- par défaut : `~/.xcsh/agent/themes`\n- surchargé par `PI_CODING_AGENT_DIR` (`$PI_CODING_AGENT_DIR/themes`)\n\n`getAvailableThemes()` retourne les noms fusionnés intégrés + personnalisés, triés, avec priorité aux intégrés en cas de collision de noms.\n\n## Chargement, validation et résolution\n\nPour les fichiers de thèmes personnalisés :\n\n1. lecture du JSON\n2. analyse du JSON\n3. validation par rapport au `ThemeJsonSchema`\n4. résolution récursive des références `vars`\n5. conversion des valeurs résolues en ANSI selon le mode de capacité du terminal\n\nComportement de validation :\n\n- jetons de couleur requis manquants : message d'erreur groupé explicite\n- types/valeurs de jetons incorrects : erreurs de validation avec chemin JSON\n- fichier de thème inconnu : `Theme not found: <name>`\n\nComportement des références de variables :\n\n- prend en charge les références imbriquées\n- lève une exception en cas de référence de variable manquante\n- lève une exception en cas de références circulaires\n\n## Comportement du mode de couleur du terminal\n\nDétection du mode de couleur (`detectColorMode`) :\n\n- `COLORTERM=truecolor|24bit` => truecolor\n- `WT_SESSION` => truecolor\n- `TERM` dans `dumb`, `linux`, ou vide => 256color\n- sinon => truecolor\n\nComportement de conversion :\n\n- hex -> `Bun.color(..., \"ansi-16m\" | \"ansi-256\")`\n- numérique -> ANSI `38;5` / `48;5`\n- `\"\"` -> réinitialisation premier plan/arrière-plan par défaut\n\n## Comportement de changement à l'exécution\n\n### Thème initial (`initTheme`)\n\n`main.ts` initialise le thème avec les paramètres :\n\n- `symbolPreset`\n- `colorBlindMode`\n- `theme.dark`\n- `theme.light`\n\nLa sélection automatique du créneau de thème utilise la détection de l'arrière-plan via `COLORFGBG` :\n\n- analyse de l'index d'arrière-plan depuis `COLORFGBG`\n- `< 8` => créneau sombre (`theme.dark`)\n- `>= 8` => créneau clair (`theme.light`)\n- échec de l'analyse => créneau sombre\n\nValeurs par défaut actuelles du schéma de paramètres :\n\n- `theme.dark = \"xcsh-dark\"`\n- `theme.light = \"xcsh-light\"`\n- `symbolPreset = \"unicode\"`\n- `colorBlindMode = false`\n\n### Changement explicite (`setTheme`)\n\n- charge le thème sélectionné\n- met à jour le singleton global `theme`\n- démarre optionnellement l'observateur\n- déclenche le callback `onThemeChange`\n\nEn cas d'échec :\n\n- retombe sur le thème intégré `dark`\n- retourne `{ success: false, error }`\n\n### Changement de prévisualisation (`previewTheme`)\n\n- applique temporairement le thème de prévisualisation au `theme` global\n- ne modifie **pas** les paramètres persistés en lui-même\n- retourne succès/erreur sans remplacement de repli\n\nL'interface des paramètres utilise ceci pour la prévisualisation en direct et restaure le thème précédent en cas d'annulation.\n\n## Observateurs et rechargement en direct\n\nLorsque l'observateur est activé (`setTheme(..., true)` / initialisation interactive) :\n\n- observe uniquement le chemin du fichier personnalisé `<customThemesDir>/<currentTheme>.json`\n- les thèmes intégrés ne sont effectivement pas observés\n- `change` du fichier : tente un rechargement (avec anti-rebond)\n- `rename`/suppression du fichier : retombe sur `dark`, ferme l'observateur\n\nLe mode automatique installe également un écouteur `SIGWINCH` et peut réévaluer le mappage du créneau sombre/clair lorsque l'état du terminal change.\n\n## Comportement du mode daltonien\n\n`colorBlindMode` ne modifie qu'un seul jeton à l'exécution :\n\n- `toolDiffAdded` est ajusté en HSV (le vert est décalé vers le bleu)\n- l'ajustement n'est appliqué que lorsque la valeur résolue est une chaîne hexadécimale\n\nLes autres jetons restent inchangés.\n\n## Où les paramètres de thème sont persistés\n\nLes paramètres liés aux thèmes sont persistés par `Settings` dans le fichier YAML de configuration globale :\n\n- chemin : `<agentDir>/config.yml`\n- répertoire agent par défaut : `~/.xcsh/agent`\n- fichier effectif par défaut : `~/.xcsh/agent/config.yml`\n\nClés persistées :\n\n- `theme.dark`\n- `theme.light`\n- `symbolPreset`\n- `colorBlindMode`\n\nUne migration héritée existe : l'ancien format plat `theme: \"name\"` est migré vers le format imbriqué `theme.dark` ou `theme.light` basé sur la détection de luminance.\n\n## Créer un thème personnalisé (pratique)\n\n1. Créez un fichier dans le répertoire des thèmes personnalisés, par exemple `~/.xcsh/agent/themes/my-theme.json`.\n2. Incluez `name`, des `vars` optionnels, et **tous les** jetons `colors` requis.\n3. Incluez optionnellement `symbols` et `export`.\n4. Sélectionnez le thème dans les Paramètres (`Affichage -> Thème sombre` ou `Affichage -> Thème clair`) selon le créneau automatique souhaité.\n\nSquelette minimal. Chaque clé dans `colors` est requise — le validateur à l'exécution\n(`additionalProperties: false`) rejette à la fois les clés manquantes et les clés inconnues.\nPour les implémentations de référence fournies, consultez\n[`packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json)\net [`xcsh-light.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-light.json).\n\nLa barre d'état possède deux systèmes de couleurs parallèles documentés dans l'issue #242 :\n\n- Les couleurs de texte hexadécimales (`statusLinePath`, `statusLineGitClean`, `statusLineGitDirty`,\n  `statusLineStaged`, `statusLineDirty`, `statusLineUntracked`) pilotent le\n  rendu non-powerline.\n- Les indices de palette 256 couleurs (`statusLine<Segment>Bg` / `statusLine<Segment>Fg`)\n  pilotent le remplissage des segments powerline. Ils sont indépendants des clés hexadécimales ci-dessus —\n  les deux doivent être définis.\n\n```json\n{\n  \"name\": \"my-theme\",\n  \"vars\": {\n    \"accent\": \"#7aa2f7\",\n    \"muted\": 244\n  },\n  \"colors\": {\n    \"accent\": \"accent\",\n    \"chromeAccent\": \"accent\",\n    \"spinnerAccent\": \"accent\",\n    \"contentAccent\": \"muted\",\n    \"border\": \"#4c566a\",\n    \"borderAccent\": \"accent\",\n    \"borderMuted\": \"muted\",\n    \"success\": \"#9ece6a\",\n    \"error\": \"#f7768e\",\n    \"warning\": \"#e0af68\",\n    \"muted\": \"muted\",\n    \"dim\": 240,\n    \"gutterSuccess\": \"#7dcfff\",\n    \"gutterWarning\": \"#e0af68\",\n    \"text\": \"\",\n    \"thinkingText\": \"muted\",\n\n    \"selectedBg\": \"#2a2f45\",\n    \"userMessageBg\": \"#1f2335\",\n    \"userMessageText\": \"\",\n    \"customMessageBg\": \"#24283b\",\n    \"customMessageText\": \"\",\n    \"customMessageLabel\": \"accent\",\n    \"toolPendingBg\": \"#1f2335\",\n    \"toolSuccessBg\": \"#1f2d2a\",\n    \"toolErrorBg\": \"#2d1f2a\",\n    \"toolTitle\": \"\",\n    \"toolOutput\": \"muted\",\n\n    \"mdHeading\": \"accent\",\n    \"mdLink\": \"accent\",\n    \"mdLinkUrl\": \"muted\",\n    \"mdCode\": \"#c0caf5\",\n    \"mdCodeBlock\": \"#c0caf5\",\n    \"mdCodeBlockBorder\": \"muted\",\n    \"mdQuote\": \"muted\",\n    \"mdQuoteBorder\": \"muted\",\n    \"mdHr\": \"muted\",\n    \"mdListBullet\": \"accent\",\n\n    \"toolDiffAdded\": \"#9ece6a\",\n    \"toolDiffRemoved\": \"#f7768e\",\n    \"toolDiffContext\": \"muted\",\n\n    \"syntaxComment\": \"#565f89\",\n    \"syntaxKeyword\": \"#bb9af7\",\n    \"syntaxFunction\": \"#7aa2f7\",\n    \"syntaxVariable\": \"#c0caf5\",\n    \"syntaxString\": \"#9ece6a\",\n    \"syntaxNumber\": \"#ff9e64\",\n    \"syntaxType\": \"#2ac3de\",\n    \"syntaxOperator\": \"#89ddff\",\n    \"syntaxPunctuation\": \"#9aa5ce\",\n    \"syntaxControl\": \"#bb9af7\",\n\n    \"thinkingOff\": 240,\n    \"thinkingMinimal\": 244,\n    \"thinkingLow\": \"#7aa2f7\",\n    \"thinkingMedium\": \"#2ac3de\",\n    \"thinkingHigh\": \"#bb9af7\",\n    \"thinkingXhigh\": \"#f7768e\",\n\n    \"bashMode\": \"#2ac3de\",\n    \"pythonMode\": \"#bb9af7\",\n\n    \"statusLineBg\": \"#16161e\",\n    \"statusLineSep\": 240,\n    \"statusLineModel\": \"#bb9af7\",\n    \"statusLinePath\": \"#7aa2f7\",\n    \"statusLineGitClean\": \"#9ece6a\",\n    \"statusLineGitDirty\": \"#e0af68\",\n    \"statusLineContext\": \"#2ac3de\",\n    \"statusLineSpend\": \"#7dcfff\",\n    \"statusLineStaged\": \"#9ece6a\",\n    \"statusLineDirty\": \"#e0af68\",\n    \"statusLineUntracked\": \"#f7768e\",\n    \"statusLineOutput\": \"#c0caf5\",\n    \"statusLineCost\": \"#ff9e64\",\n    \"statusLineSubagents\": \"#bb9af7\",\n\n    \"statusLineOsIconBg\": 7,\n    \"statusLineOsIconFg\": 232,\n    \"statusLinePathBg\": 4,\n    \"statusLinePathFg\": 254,\n    \"statusLineGitCleanBg\": 2,\n    \"statusLineGitCleanFg\": 0,\n    \"statusLineGitDirtyBg\": 3,\n    \"statusLineGitDirtyFg\": 0,\n    \"statusLineGitStagedBg\": 64,\n    \"statusLineGitStagedFg\": 0,\n    \"statusLineGitUntrackedBg\": 39,\n    \"statusLineGitUntrackedFg\": 0,\n    \"statusLineGitConflictBg\": 1,\n    \"statusLineGitConflictFg\": 7,\n    \"statusLinePlanModeBg\": 236,\n    \"statusLinePlanModeFg\": 117,\n    \"statusLineProfileXcshBg\": \"accent\",\n    \"statusLineProfileXcshFg\": 231\n  }\n}\n```\n\n## Tester les thèmes personnalisés\n\nUtilisez ce flux de travail :\n\n1. Démarrez le mode interactif (l'observateur est activé dès le démarrage).\n2. Ouvrez les paramètres et prévisualisez les valeurs du thème (`previewTheme` en direct).\n3. Pour les fichiers de thèmes personnalisés, modifiez le JSON pendant l'exécution et confirmez le rechargement automatique à la sauvegarde.\n4. Exercez les surfaces critiques :\n   - rendu markdown\n   - blocs d'outils (en attente/succès/erreur)\n   - rendu des diffs (ajouté/supprimé/contexte)\n   - lisibilité de la barre d'état\n   - changements de bordure selon le niveau de réflexion\n   - couleurs de bordure des modes bash/python\n5. Validez les deux préréglages de symboles si votre thème dépend de la largeur/apparence des glyphes.\n\n## Contraintes réelles et mises en garde\n\n- Tous les jetons `colors` sont requis pour les thèmes personnalisés.\n- `export` et `symbols` sont optionnels.\n- `$schema` dans le JSON du thème est informatif ; la validation à l'exécution est imposée par le schéma TypeBox compilé dans le code.\n- L'échec de `setTheme` retombe sur `dark` ; l'échec de `previewTheme` ne remplace pas le thème actuel.\n- Les erreurs de rechargement de l'observateur de fichiers conservent le thème actuellement chargé jusqu'à un rechargement réussi ou le déclenchement du chemin de repli.\n",
	"fr/tui/tree.md": "---\ntitle: Référence de la commande Tree\ndescription: >-\n  /tree command reference for visualizing session history and conversation\n  branches.\nsidebar:\n  order: 4\n  label: Commande /tree\ni18n:\n  sourceHash: ee0e412fe993\n  translator: machine\n---\n\n# Référence de la commande `/tree`\n\n`/tree` ouvre le navigateur interactif **Session Tree**. Il vous permet de naviguer vers n'importe quelle entrée dans le fichier de session actuel et de continuer à partir de ce point.\n\nIl s'agit d'un déplacement de feuille dans le fichier, pas d'un export vers une nouvelle session.\n\n## Ce que fait `/tree`\n\n- Construit un arbre à partir des entrées de la session actuelle (`SessionManager.getTree()`)\n- Ouvre `TreeSelectorComponent` avec navigation au clavier, filtres et recherche\n- Lors de la sélection, appelle `AgentSession.navigateTree(targetId, { summarize, customInstructions })`\n- Reconstruit le chat visible à partir du nouveau chemin de feuille\n- Pré-remplit optionnellement le texte de l'éditeur lors de la sélection d'un message utilisateur/personnalisé\n\nImplémentation principale :\n\n- `src/modes/controllers/input-controller.ts` (`/tree`, liaison des raccourcis clavier, comportement du double-échap)\n- `src/modes/controllers/selector-controller.ts` (lancement de l'interface arbre + flux de prompt de résumé)\n- `src/modes/components/tree-selector.ts` (navigation, filtres, recherche, étiquettes, rendu)\n- `src/session/agent-session.ts` (`navigateTree` changement de feuille + résumé optionnel)\n- `src/session/session-manager.ts` (`getTree`, `branch`, `branchWithSummary`, `resetLeaf`, persistance des étiquettes)\n\n## Comment l'ouvrir\n\nChacune des méthodes suivantes ouvre le même sélecteur :\n\n- `/tree`\n- action de raccourci clavier configurée `tree`\n- double-échap sur un éditeur vide lorsque `doubleEscapeAction = \"tree\"` (par défaut)\n- `/branch` lorsque `doubleEscapeAction = \"tree\"` (redirige vers le sélecteur d'arbre au lieu du sélecteur de branches utilisateur uniquement)\n\n## Modèle d'interface de l'arbre\n\nL'arbre est rendu à partir des pointeurs parent des entrées de session (`id` / `parentId`).\n\n- Les enfants sont triés par horodatage croissant (les plus anciens en premier, les plus récents en bas)\n- La branche active (chemin de la racine à la feuille actuelle) est marquée d'une puce\n- Les étiquettes (si présentes) sont affichées sous la forme `[label]` avant le texte du nœud\n- Si plusieurs racines existent (chaînes de parents orphelines/cassées), elles sont affichées sous une racine de branchement virtuelle\n\n```text\nExample tree view (active path marked with •):\n\n├─ user: \"Start task\"\n│  └─ assistant: \"Plan\"\n│     ├─ • user: \"Try approach A\"\n│     │  └─ • assistant: \"A result\"\n│     │     └─ • [milestone] user: \"Continue A\"\n│     └─ user: \"Try approach B\"\n│        └─ assistant: \"B result\"\n```\n\nLe sélecteur se recentre autour de la sélection actuelle et affiche jusqu'à :\n\n- `max(5, floor(terminalHeight / 2))` lignes\n\n## Raccourcis clavier dans le sélecteur d'arbre\n\n- `Up` / `Down` : déplacer la sélection (avec bouclage)\n- `Left` / `Right` : page précédente / page suivante\n- `Enter` : sélectionner le nœud\n- `Esc` : effacer la recherche si active ; sinon fermer le sélecteur\n- `Ctrl+C` : fermer le sélecteur\n- `Type` : ajouter à la requête de recherche\n- `Backspace` : supprimer un caractère de recherche\n- `Shift+L` : modifier/effacer l'étiquette de l'entrée sélectionnée\n- `Ctrl+O` : parcourir les filtres vers l'avant\n- `Shift+Ctrl+O` : parcourir les filtres vers l'arrière\n- `Alt+D/T/U/L/A` : aller directement à un mode de filtre spécifique\n\n## Sémantique des filtres et de la recherche\n\nModes de filtre (`TreeList`) :\n\n1. `default`\n2. `no-tools`\n3. `user-only`\n4. `labeled-only`\n5. `all`\n\n### `default`\n\nAffiche la plupart des nœuds conversationnels, mais masque les types d'entrées de gestion interne :\n\n- `label`\n- `custom`\n- `model_change`\n- `thinking_level_change`\n\n### `no-tools`\n\nIdentique à `default`, mais masque en plus les messages `toolResult`.\n\n### `user-only`\n\nUniquement les entrées `message` dont le rôle est `user`.\n\n### `labeled-only`\n\nUniquement les entrées qui possèdent actuellement une étiquette résolue.\n\n### `all`\n\nTout ce qui se trouve dans l'arbre de session, y compris les entrées de gestion interne/personnalisées.\n\n### Comportement des nœuds assistant contenant uniquement des outils\n\nLes messages assistant qui ne contiennent **que des appels d'outils** (sans texte) sont masqués par défaut dans toutes les vues filtrées, sauf si :\n\n- le message est en erreur/interrompu (`stopReason` différent de `stop`/`toolUse`), ou\n- il s'agit de la feuille actuelle (toujours maintenue visible)\n\n### Comportement de la recherche\n\n- La requête est tokenisée par les espaces\n- La correspondance est insensible à la casse\n- Tous les tokens doivent correspondre (sémantique ET)\n- Le texte recherchable inclut l'étiquette, le rôle et le contenu spécifique au type (texte du message, texte de résumé de branche, type personnalisé, extraits de commandes d'outils, etc.)\n\n## Résultats de la sélection (important)\n\n`navigateTree` calcule le nouveau comportement de feuille à partir du type d'entrée sélectionné :\n\n### Sélection d'un message `user`\n\n- La nouvelle feuille devient le `parentId` de l'entrée sélectionnée\n- Si le parent est `null` (message utilisateur racine), la feuille est réinitialisée à la racine (`resetLeaf()`)\n- Le texte du message sélectionné est copié dans l'éditeur pour modification/renvoi\n\n### Sélection d'un `custom_message`\n\n- Même règle de feuille que pour les messages utilisateur (`parentId`)\n- Le contenu textuel est extrait et copié dans l'éditeur\n\n### Sélection d'un nœud non-utilisateur (assistant/outil/résumé/compaction/gestion interne personnalisée/etc.)\n\n- La nouvelle feuille devient l'identifiant du nœud sélectionné\n- L'éditeur n'est pas pré-rempli\n\n### Sélection de la feuille actuelle\n\n- Aucune opération ; le sélecteur se ferme avec « Already at this point »\n\n```text\nSelection decision (simplified):\n\nselected node\n   │\n   ├─ is current leaf? ── yes ──> close selector (no-op)\n   │\n   ├─ is user/custom_message? ── yes ──> leaf := parentId (or resetLeaf for root)\n   │                                     + prefill editor text\n   │\n   └─ otherwise ──> leaf := selected node id\n                    + no editor prefill\n```\n\n## Flux de résumé lors du changement\n\nLe prompt de résumé est contrôlé par `branchSummary.enabled` (par défaut : `false`).\n\nLorsqu'il est activé, après avoir choisi un nœud, l'interface demande :\n\n- `No summary`\n- `Summarize`\n- `Summarize with custom prompt`\n\nDétails du flux :\n\n- Échap dans le prompt de résumé rouvre le sélecteur d'arbre\n- L'annulation du prompt personnalisé retourne à la boucle de choix de résumé\n- Pendant la génération du résumé, l'interface affiche un indicateur de chargement et lie `Esc` à `abortBranchSummary()`\n- Si la génération du résumé est interrompue, le sélecteur d'arbre se rouvre et aucun déplacement n'est appliqué\n\nFonctionnement interne de `navigateTree` :\n\n- Collecte les entrées de la branche abandonnée depuis l'ancienne feuille jusqu'à l'ancêtre commun\n- Émet `session_before_tree` (les extensions peuvent annuler ou injecter un résumé)\n- Utilise le résumeur par défaut uniquement si demandé et nécessaire\n- Applique le déplacement avec :\n  - `branchWithSummary(...)` lorsqu'un résumé existe\n  - `branch(newLeafId)` pour un déplacement non-racine sans résumé\n  - `resetLeaf()` pour un déplacement vers la racine sans résumé\n- Remplace la conversation de l'agent par le contexte de session reconstruit\n- Émet `session_tree`\n\nNote : si l'utilisateur demande un résumé mais qu'il n'y a rien à résumer, la navigation se poursuit sans créer d'entrée de résumé.\n\n## Étiquettes\n\nLes modifications d'étiquettes dans l'interface d'arbre appellent `appendLabelChange(targetId, label)`.\n\n- une étiquette non vide définit/met à jour l'étiquette résolue\n- une étiquette vide la supprime\n- les étiquettes sont stockées sous forme d'entrées `label` en ajout seul\n- les nœuds de l'arbre affichent l'état résolu de l'étiquette, pas l'historique brut des entrées d'étiquettes\n\n## `/tree` vs opérations adjacentes\n\n| Opération | Portée | Résultat |\n|---|---|---|\n| `/tree` | Fichier de session actuel | Déplace la feuille vers le point sélectionné (même fichier) |\n| `/branch` | Généralement fichier de session actuel -> nouveau fichier de session | Par défaut, crée une branche à partir du message **utilisateur** sélectionné dans un nouveau fichier de session ; si `doubleEscapeAction = \"tree\"`, `/branch` ouvre l'interface de navigation par arbre à la place |\n| `/fork` | Session actuelle complète | Duplique la session dans un nouveau fichier de session persisté |\n| `/resume` | Liste des sessions | Bascule vers un autre fichier de session |\n\nDistinction clé : `/tree` est un outil de navigation/repositionnement au sein d'un seul fichier de session. `/branch`, `/fork` et `/resume` changent tous le contexte du fichier de session.\n\n## Flux de travail opérateur\n\n### Relancer à partir d'un prompt utilisateur antérieur sans perdre la branche actuelle\n\n1. `/tree`\n2. rechercher/sélectionner un message utilisateur antérieur\n3. choisir `No summary` (ou résumer si nécessaire)\n4. modifier le texte pré-rempli dans l'éditeur\n5. soumettre\n\nEffet : une nouvelle branche se développe à partir du point sélectionné dans le même fichier de session.\n\n### Quitter la branche actuelle avec un repère contextuel\n\n1. activer `branchSummary.enabled`\n2. `/tree` et sélectionner le nœud cible\n3. choisir `Summarize` (ou prompt personnalisé)\n\nEffet : une entrée `branch_summary` est ajoutée à la position cible avant de continuer.\n\n### Examiner les entrées de gestion interne masquées\n\n1. `/tree`\n2. appuyer sur `Alt+A` (all)\n3. rechercher `model`, `thinking`, `custom` ou des étiquettes\n\nEffet : inspecter la chronologie interne complète, pas seulement les nœuds conversationnels.\n\n### Marquer des points de pivot pour des sauts ultérieurs\n\n1. `/tree`\n2. naviguer vers l'entrée\n3. `Shift+L` et définir une étiquette\n4. utiliser ensuite `Alt+L` (`labeled-only`) pour naviguer rapidement\n\nEffet : navigation rapide entre des points de repère durables dans les branches.\n",
	"fr/tui/tui-runtime-internals.md": "---\ntitle: Composants internes du runtime TUI\ndescription: >-\n  Composants internes du runtime de l'interface utilisateur en terminal couvrant\n  le pipeline de rendu, la gestion des entrées et la gestion d'état.\nsidebar:\n  order: 2\n  label: Composants internes du runtime\ni18n:\n  sourceHash: cc8f7dcce46a\n  translator: machine\n---\n\n# Composants internes du runtime TUI\n\nCe document décrit le chemin d'exécution non-thème depuis l'entrée terminal jusqu'à la sortie rendue en mode interactif. Il se concentre sur le comportement dans `packages/tui` et son intégration depuis les contrôleurs de `packages/coding-agent`.\n\n## Couches du runtime et propriété\n\n- **Moteur `packages/tui`** : cycle de vie du terminal, normalisation de stdin, routage du focus, planification du rendu, peinture différentielle, composition des superpositions, positionnement matériel du curseur.\n- **Mode interactif de `packages/coding-agent`** : construit l'arbre des composants, lie les rappels de l'éditeur et les mappages de touches, réagit aux événements agent/session, et traduit l'état du domaine (streaming, exécution d'outils, nouvelles tentatives, mode plan) en composants UI.\n\nRègle de délimitation : le moteur TUI est indépendant des messages. Il ne connaît que `Component.render(width)`, `handleInput(data)`, le focus et les superpositions. La sémantique de l'agent reste dans les contrôleurs interactifs.\n\n## Fichiers d'implémentation\n\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/components/custom-editor.ts`](../../packages/coding-agent/src/modes/components/custom-editor.ts)\n- [`../../tui/src/tui.ts`](../../packages/tui/src/tui.ts)\n- [`../../tui/src/terminal.ts`](../../packages/tui/src/terminal.ts)\n- [`../../tui/src/editor-component.ts`](../../packages/tui/src/editor-component.ts)\n- [`../../tui/src/stdin-buffer.ts`](../../packages/tui/src/stdin-buffer.ts)\n- [`../../tui/src/components/loader.ts`](../../packages/tui/src/components/loader.ts)\n\n## Démarrage et assemblage de l'arbre de composants\n\n`InteractiveMode` construit `TUI(new ProcessTerminal(), showHardwareCursor)` et crée des conteneurs persistants :\n\n- `chatContainer`\n- `pendingMessagesContainer`\n- `statusContainer`\n- `todoContainer`\n- `statusLine`\n- `editorContainer` (contient `CustomEditor`)\n\n`init()` câble l'arbre dans cet ordre, donne le focus à l'éditeur, enregistre les gestionnaires d'entrée via `InputController`, démarre le TUI et demande un rendu forcé.\n\nUn rendu forcé (`requestRender(true)`) réinitialise les caches de lignes précédentes et la gestion du curseur avant le repeint.\n\n## Cycle de vie du terminal et normalisation de stdin\n\n`ProcessTerminal.start()` :\n\n1. Active le mode brut et le collage entre crochets.\n2. Attache le gestionnaire de redimensionnement.\n3. Crée un `StdinBuffer` pour découper les fragments d'échappement partiels en séquences complètes.\n4. Interroge la prise en charge du protocole clavier Kitty (`CSI ? u`), puis active les indicateurs de protocole si pris en charge.\n5. Sous Windows, tente l'activation de l'entrée VT via les indicateurs de mode `kernel32`.\n\nComportement de `StdinBuffer` :\n\n- Tamponne les séquences d'échappement fragmentées (CSI/OSC/DCS/APC/SS3).\n- Émet `data` uniquement lorsqu'une séquence est complète ou vidée par délai d'expiration.\n- Détecte le collage entre crochets et émet un événement `paste` avec le texte collé brut.\n\nCela empêche les fragments d'échappement partiels d'être mal interprétés comme des touches normales.\n\n## Routage des entrées et modèle de focus\n\nChemin d'entrée :\n\n`stdin -> ProcessTerminal -> StdinBuffer -> TUI.#handleInput -> focusedComponent.handleInput`\n\nDétails du routage :\n\n1. Le TUI exécute d'abord les écouteurs d'entrée enregistrés (`addInputListener`), permettant un comportement de consommation/transformation.\n2. Le TUI gère le raccourci de débogage global (`shift+ctrl+d`) avant la distribution aux composants.\n3. Si le composant en focus appartient à une superposition désormais masquée/invisible, le TUI réassigne le focus à la prochaine superposition visible ou au focus pré-superposition sauvegardé.\n4. Les événements de relâchement de touche sont filtrés, sauf si le composant en focus définit `wantsKeyRelease = true`.\n5. Après la distribution, le TUI planifie le rendu.\n\n`setFocus()` active/désactive également `Focusable.focused`, ce qui contrôle si les composants émettent `CURSOR_MARKER` pour le positionnement matériel du curseur.\n\n## Répartition de la gestion des touches : éditeur vs contrôleur\n\n`CustomEditor` intercepte d'abord les combinaisons à haute priorité (échappement, ctrl-c/d/z, ctrl-v, variantes ctrl-p, ctrl-t, alt-haut, touches personnalisées d'extension) et délègue le reste au comportement de base de `Editor` (édition de texte, historique, autocomplétion, déplacement du curseur).\n\n`InputController.setupKeyHandlers()` lie ensuite les rappels de l'éditeur aux actions du mode :\n\n- annulation / sorties de mode sur `Escape`\n- arrêt sur double `Ctrl+C` ou `Ctrl+D` avec éditeur vide\n- suspension/reprise sur `Ctrl+Z`\n- raccourcis de commande slash et sélecteur\n- bascules de suivi/défilement et bascules d'expansion\n\nCela maintient l'analyse des touches et la mécanique de l'éditeur dans `packages/tui` et la sémantique du mode dans les contrôleurs de coding-agent.\n\n## Boucle de rendu et stratégie de diff\n\n`TUI.requestRender()` est soumis à un anti-rebond pour limiter à un rendu par tick en utilisant `process.nextTick`. Plusieurs changements d'état dans le même tour sont fusionnés.\n\nPipeline de `#doRender()` :\n\n1. Rendu de l'arbre de composants racine vers `newLines`.\n2. Composition des superpositions visibles (le cas échéant).\n3. Extraction et suppression de `CURSOR_MARKER` des lignes visibles de la fenêtre d'affichage.\n4. Ajout de suffixes de réinitialisation de segment pour les lignes sans image.\n5. Choix entre repeint complet ou correction différentielle :\n   - première image\n   - changement de largeur\n   - rétrécissement avec `clearOnShrink` activé et sans superpositions\n   - modifications au-dessus de la fenêtre d'affichage précédente\n6. Pour les mises à jour différentielles, correction uniquement de la plage de lignes modifiées et effacement des lignes finales obsolètes si nécessaire.\n7. Repositionnement du curseur matériel pour la prise en charge de l'IME.\n\nLes écritures de rendu utilisent le mode de sortie synchronisée (`CSI ? 2026 h/l`) pour réduire le scintillement/déchirement.\n\n## Contraintes de sûreté du rendu\n\nVérifications de sûreté critiques dans `TUI` :\n\n- Les lignes rendues sans image ne doivent pas dépasser la largeur du terminal ; un dépassement lève une exception et écrit des diagnostics d'incident.\n- La composition des superpositions inclut une troncature défensive et une vérification de largeur post-composition.\n- Les changements de largeur forcent un redessin complet car la sémantique du retour à la ligne change.\n- La position du curseur est contrainte avant le déplacement.\n\nCes contraintes constituent une application à l'exécution, et non de simples conventions.\n\n## Gestion du redimensionnement\n\nLes événements de redimensionnement sont pilotés par événements depuis `ProcessTerminal` vers `TUI.requestRender()`.\n\nEffets :\n\n- Tout changement de largeur déclenche un redessin complet.\n- Le suivi de la fenêtre d'affichage/du haut (`#previousViewportTop`, `#maxLinesRendered`) évite les calculs de curseur relatifs invalides lors de changements de contenu ou de taille du terminal.\n- La visibilité des superpositions peut dépendre des dimensions du terminal (`OverlayOptions.visible`) ; le focus est corrigé lorsque les superpositions deviennent non visibles après redimensionnement.\n\n## Streaming et mises à jour UI incrémentales\n\n`EventController` s'abonne aux `AgentSessionEvent` et met à jour l'UI de manière incrémentale :\n\n- `agent_start` : démarre le chargeur dans `statusContainer`.\n- `message_start` assistant : crée `streamingComponent` et le monte.\n- `message_update` : met à jour le contenu assistant en streaming ; crée/met à jour les composants d'exécution d'outils au fur et à mesure que les appels d'outils apparaissent.\n- `tool_execution_update/end` : met à jour les composants de résultat d'outil et l'état de complétion.\n- `message_end` : finalise le flux assistant, gère les annotations abandonnées/erreur, marque les arguments d'outil en attente comme complets lors d'un arrêt normal.\n- `agent_end` : arrête les chargeurs, efface l'état du flux transitoire, vide le changement de modèle différé, émet une notification de complétion si mis en arrière-plan.\n\nLe regroupement des outils de lecture est intentionnellement avec état (`#lastReadGroup`) pour fusionner les appels d'outils de lecture consécutifs en un seul bloc visuel jusqu'à ce qu'une interruption non-lecture se produise.\n\n## Orchestration du statut et du chargeur\n\nPropriété de la voie de statut :\n\n- `statusContainer` contient les chargeurs transitoires (`loadingAnimation`, `autoCompactionLoader`, `retryLoader`).\n- `statusLine` affiche les indicateurs de statut/hooks/plan persistants et pilote les mises à jour de bordure supérieure de l'éditeur.\n\nComportement du chargeur :\n\n- `Loader` se met à jour toutes les 80 ms via un intervalle et demande un rendu à chaque image.\n- Les gestionnaires d'échappement sont temporairement remplacés pendant la compaction automatique et la nouvelle tentative automatique pour annuler ces opérations.\n- Sur les chemins de fin/annulation, les contrôleurs restaurent les gestionnaires d'échappement précédents et arrêtent/effacent les composants du chargeur.\n\n## Transitions de mode et mise en arrière-plan\n\n### Modes d'entrée Bash/Python\n\nLes préfixes de texte d'entrée basculent les indicateurs de mode de bordure de l'éditeur :\n\n- `!` -> mode bash\n- `$` (préfixe non-littéral de template) -> mode python\n\nL'échappement quitte le mode inactif en effaçant le texte de l'éditeur et en restaurant la couleur de la bordure ; lorsque l'exécution est active, l'échappement abandonne la tâche en cours à la place.\n\n### Mode plan\n\n`InteractiveMode` suit les indicateurs de mode plan, l'état de la ligne de statut, les outils actifs et le changement de modèle. L'entrée/sortie met à jour les entrées de mode de session et l'état du statut/UI, y compris le changement de modèle différé si le streaming est actif.\n\n### Suspension/reprise (`Ctrl+Z`)\n\n`InputController.handleCtrlZ()` :\n\n1. Enregistre un gestionnaire `SIGCONT` à usage unique pour redémarrer le TUI et forcer le rendu.\n2. Arrête le TUI avant la suspension.\n3. Envoie `SIGTSTP` au groupe de processus.\n\n### Mode arrière-plan (`/background` ou `/bg`)\n\n`handleBackgroundCommand()` :\n\n- Rejette lorsqu'inactif.\n- Bascule le contexte UI des outils vers non-interactif (`hasUI=false`) afin que les outils UI interactifs échouent rapidement.\n- Arrête les chargeurs/la ligne de statut et se désabonne du gestionnaire d'événements au premier plan.\n- S'abonne au gestionnaire d'événements en arrière-plan (attend principalement `agent_end`).\n- Arrête le TUI et envoie `SIGTSTP` (chemin de contrôle de tâche POSIX).\n\nSur `agent_end` en arrière-plan sans travail en file d'attente, le contrôleur envoie une notification de complétion et s'arrête.\n\n## Chemins d'annulation\n\nEntrées d'annulation principales :\n\n- `Escape` pendant le chargeur de flux actif : restaure les messages en file d'attente dans l'éditeur et abandonne l'agent.\n- `Escape` pendant l'exécution bash/python : abandonne la commande en cours.\n- `Escape` pendant la compaction automatique/nouvelle tentative : invoque des méthodes d'abandon dédiées via des gestionnaires d'échappement temporaires.\n- `Ctrl+C` pression unique : effacer l'éditeur ; double pression dans les 500 ms : arrêt.\n\nL'annulation est conditionnelle à l'état ; la même touche peut signifier abandon, sortie de mode, déclencheur de sélecteur ou aucune action selon l'état du runtime.\n\n## Comportement piloté par événements vs comportement à débit limité\n\nMises à jour pilotées par événements :\n\n- Événements de session agent (`EventController`)\n- Rappels d'entrée de touches (`InputController`)\n- Rappel de redimensionnement du terminal\n- Observateurs de thème/branche dans `InteractiveMode`\n\nChemins à débit limité/anti-rebond :\n\n- Le rendu TUI est soumis à un anti-rebond par tick (fusion de `requestRender`).\n- L'animation du chargeur est à intervalle fixe (80 ms), chaque image demandant un rendu.\n- Les mises à jour d'autocomplétion de l'éditeur (dans `Editor`) utilisent des minuteries anti-rebond, réduisant le recalcul excessif lors de la frappe.\n\nLe runtime combine donc des transitions d'état pilotées par événements avec une cadence de rendu bornée pour maintenir l'interactivité réactive sans tempêtes de repeint.\n",
	"fr/tui/tui.md": "---\ntitle: Intégration TUI pour les extensions et les outils personnalisés\ndescription: >-\n  Contrat d'intégration TUI pour les extensions, les outils personnalisés et les\n  rendus personnalisés.\nsidebar:\n  order: 1\n  label: Intégration des extensions\ni18n:\n  sourceHash: 47f8f2b2045e\n  translator: machine\n---\n\n# Intégration TUI pour les extensions et les outils personnalisés\n\nCe document couvre le contrat TUI **actuel** utilisé par `packages/coding-agent` et `packages/tui` pour l'interface utilisateur des extensions, l'interface utilisateur des outils personnalisés et les rendus personnalisés.\n\n## Ce qu'est ce sous-système\n\nLe runtime comporte deux couches :\n\n- **Moteur de rendu (`packages/tui`)** : rendu terminal différentiel, dispatch des entrées, focus, overlays, positionnement du curseur.\n- **Couche d'intégration (`packages/coding-agent`)** : monte les composants d'extension/outil personnalisé, connecte les raccourcis clavier/thème et restaure l'état de l'éditeur.\n\n## Comportement du runtime par mode\n\n| Mode | Disponibilité de `ctx.ui.custom(...)` | Notes |\n| --- | --- | --- |\n| TUI interactif | Supporté | Le composant est monté dans la zone de l'éditeur, reçoit le focus, et doit appeler `done(result)` pour résoudre. |\n| Arrière-plan/headless | Non interactif | Le contexte UI est un no-op (`hasUI === false`). |\n| Mode RPC | Non supporté | `custom()` retourne `Promise<never>` et ne monte pas de composants TUI. |\n\nSi votre extension/outil peut fonctionner en mode non interactif, protégez-vous avec `ctx.hasUI` / `pi.hasUI`.\n\n## Contrat de composant principal (`@f5-sales-demo/pi-tui`)\n\n`packages/tui/src/tui.ts` définit :\n\n```ts\nexport interface Component {\n  render(width: number): string[];\n  handleInput?(data: string): void;\n  wantsKeyRelease?: boolean;\n  invalidate(): void;\n}\n```\n\n`Focusable` est séparé :\n\n```ts\nexport interface Focusable {\n  focused: boolean;\n}\n```\n\nLe comportement du curseur utilise `CURSOR_MARKER` (pas `getCursorPosition`). Les composants ayant le focus émettent le marqueur dans le texte rendu ; `TUI` l'extrait et positionne le curseur matériel.\n\n## Contraintes de rendu (sécurité terminale)\n\nLa sortie de votre `render(width)` doit être compatible avec le terminal :\n\n1. **Ne jamais dépasser `width` sur aucune ligne**. Le moteur de rendu lève une erreur si une ligne non-image déborde.\n2. **Mesurez la largeur visuelle**, pas la longueur de la chaîne : utilisez `visibleWidth()`.\n3. **Tronquez/encapsulez le texte compatible ANSI** avec `truncateToWidth()` / `wrapTextWithAnsi()`.\n4. **Assainissez les tabulations/contenus** provenant de sources externes en utilisant `replaceTabs()` (et les assainisseurs de plus haut niveau dans les chemins de rendu de coding-agent).\n\nPatron minimal :\n\n```ts\nimport { replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\n\nrender(width: number): string[] {\n  return this.lines.map(line => truncateToWidth(replaceTabs(line), width));\n}\n```\n\n## Gestion des entrées et raccourcis clavier\n\n### Correspondance brute des touches\n\nUtilisez `matchesKey(data, \"...\")` pour les touches de navigation et les combinaisons.\n\n### Respectez les raccourcis clavier configurés par l'utilisateur\n\nLes factories d'interface utilisateur des extensions reçoivent un `KeybindingsManager` (mode interactif) afin que vous puissiez honorer les actions mappées au lieu de coder en dur les touches :\n\n```ts\nif (keybindings.matches(data, \"interrupt\")) {\n  done(undefined);\n  return;\n}\n```\n\n### Événements de relâchement/répétition de touches\n\nLes événements de relâchement de touches sont filtrés sauf si votre composant définit :\n\n```ts\nwantsKeyRelease = true;\n```\n\nUtilisez ensuite `isKeyRelease()` / `isKeyRepeat()` si nécessaire.\n\n## Focus, overlays et curseur\n\n- `TUI.setFocus(component)` route les entrées vers ce composant.\n- Les API d'overlay existent dans `TUI` (`showOverlay`, `OverlayHandle`), mais le montage `ctx.ui.custom` des extensions en mode interactif remplace actuellement directement la zone du composant éditeur.\n- L'option `custom(..., options?: { overlay?: boolean })` existe dans les types d'extension ; le montage interactif des extensions ignore actuellement cette option.\n\n## Points de montage et contrats de retour\n\n## 1) Interface utilisateur d'extension (`ExtensionUIContext`)\n\nSignature actuelle (`extensibility/extensions/types.ts`) :\n\n```ts\ncustom<T>(\n  factory: (\n    tui: TUI,\n    theme: Theme,\n    keybindings: KeybindingsManager,\n    done: (result: T) => void,\n  ) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n  options?: { overlay?: boolean },\n): Promise<T>\n```\n\nComportement en mode interactif (`extension-ui-controller.ts`) :\n\n- Sauvegarde le texte de l'éditeur.\n- Remplace le composant éditeur par votre composant.\n- Donne le focus à votre composant.\n- À l'appel de `done(result)` : appelle `component.dispose?.()`, restaure l'éditeur + le texte, donne le focus à l'éditeur, résout la promesse.\n\nDonc `done(...)` est obligatoire pour la complétion.\n\n## 2) Contexte UI hook/outil personnalisé (typage legacy)\n\n`HookUIContext.custom` est typé comme `(tui, theme, done)` dans les types de hook/outil personnalisé.\nL'implémentation interactive sous-jacente appelle les factories avec `(tui, theme, keybindings, done)`. Les consommateurs JS peuvent utiliser l'argument supplémentaire ; la compatibilité au niveau des types reflète encore la signature legacy à 3 arguments.\n\nLes outils personnalisés utilisent typiquement le même point d'entrée UI via l'objet `pi.ui` avec portée factory, puis retournent la valeur sélectionnée dans le contenu normal de l'outil :\n\n```ts\nasync execute(toolCallId, params, onUpdate, ctx, signal) {\n  if (!pi.hasUI) {\n    return { content: [{ type: \"text\", text: \"UI unavailable\" }] };\n  }\n\n  const picked = await pi.ui.custom<string | undefined>((tui, theme, done) => {\n    const component = new MyPickerComponent(done, signal);\n    return component;\n  });\n\n  return { content: [{ type: \"text\", text: picked ? `Picked: ${picked}` : \"Cancelled\" }] };\n}\n```\n\n## 3) Rendus personnalisés d'appel/résultat d'outil\n\nLes outils personnalisés et les outils d'extension peuvent retourner des composants depuis :\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\n`options` inclut actuellement :\n\n- `expanded: boolean`\n- `isPartial: boolean`\n- `spinnerFrame?: number`\n\nCes rendus sont montés par `ToolExecutionComponent`.\n\n## Cycle de vie et annulation\n\n- `dispose()` est optionnel au niveau des types mais devrait être implémenté lorsque vous possédez des timers, sous-processus, watchers, sockets ou overlays.\n- `done(...)` devrait être appelé exactement une fois depuis le flux de votre composant.\n- Pour une interface utilisateur longue durée annulable, associez `CancellableLoader` avec `AbortSignal` et appelez `done(...)` depuis `onAbort`.\n\nExemple de patron d'annulation :\n\n```ts\nconst loader = new CancellableLoader(tui, theme.fg(\"accent\"), theme.fg(\"muted\"), \"Working...\");\nloader.onAbort = () => done(undefined);\nvoid doWork(loader.signal).then(result => done(result));\nreturn loader;\n```\n\n## Exemple réaliste de composant personnalisé (commande d'extension)\n\n```ts\nimport type { Component } from \"@f5-sales-demo/pi-tui\";\nimport { SelectList, matchesKey, replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\nimport { getSelectListTheme, type ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nclass Picker implements Component {\n  list: SelectList;\n  keybindings: any;\n  done: (value: string | undefined) => void;\n\n  constructor(\n    items: Array<{ value: string; label: string }>,\n    keybindings: any,\n    done: (value: string | undefined) => void,\n  ) {\n    this.list = new SelectList(items, 8, getSelectListTheme());\n    this.keybindings = keybindings;\n    this.done = done;\n    this.list.onSelect = item => this.done(item.value);\n    this.list.onCancel = () => this.done(undefined);\n  }\n\n  handleInput(data: string): void {\n    if (this.keybindings.matches(data, \"interrupt\")) {\n      this.done(undefined);\n      return;\n    }\n    this.list.handleInput(data);\n  }\n\n  render(width: number): string[] {\n    return this.list.render(width).map(line => truncateToWidth(replaceTabs(line), width));\n  }\n\n  invalidate(): void {\n    this.list.invalidate();\n  }\n}\n\nexport default function extension(pi: ExtensionAPI): void {\n  pi.registerCommand(\"pick-model\", {\n    description: \"Pick a model profile\",\n    handler: async (_args, ctx) => {\n      if (!ctx.hasUI) return;\n\n      const selected = await ctx.ui.custom<string | undefined>((tui, theme, keybindings, done) => {\n        const items = [\n          { value: \"fast\", label: theme.fg(\"accent\", \"Fast\") },\n          { value: \"balanced\", label: \"Balanced\" },\n          { value: \"quality\", label: \"Quality\" },\n        ];\n        return new Picker(items, keybindings, done);\n      });\n\n      if (selected) ctx.ui.notify(`Selected profile: ${selected}`, \"info\");\n    },\n  });\n}\n```\n\n## Fichiers d'implémentation clés\n\n- `packages/tui/src/tui.ts` — `Component`, `Focusable`, marqueur de curseur, focus, overlay, dispatch des entrées.\n- `packages/tui/src/utils.ts` — primitives de largeur/troncature/assainissement.\n- `packages/tui/src/keys.ts` / `keybindings.ts` — analyse des touches et mappage configurable des actions.\n- `packages/coding-agent/src/modes/controllers/extension-ui-controller.ts` — montage/démontage interactif pour l'interface utilisateur des extensions/hooks/outils personnalisés.\n- `packages/coding-agent/src/extensibility/extensions/types.ts` — contrats d'interface utilisateur et de rendu des extensions.\n- `packages/coding-agent/src/extensibility/hooks/types.ts` — contrat d'interface utilisateur des hooks (signature custom legacy).\n- `packages/coding-agent/src/extensibility/custom-tools/types.ts` — contrats d'exécution/rendu des outils personnalisés.\n- `packages/coding-agent/src/modes/components/tool-execution.ts` — montage des composants `renderCall`/`renderResult` et options d'état partiel.\n- `packages/coding-agent/src/tools/context.ts` — propagation du contexte UI des outils (`hasUI`, `ui`).\n",
	"hi/configuration/blob-artifact-architecture.md": "---\ntitle: Blob और Artifact स्टोरेज आर्किटेक्चर\ndescription: >-\n  सेशन मीडिया, स्क्रीनशॉट, और टूल आउटपुट के लिए content-addressable blob स्टोर\n  और artifact रजिस्ट्री।\nsidebar:\n  order: 7\n  label: Blob और artifact स्टोरेज\ni18n:\n  sourceHash: 70d255f48d5b\n  translator: machine\n---\n\n# Blob और artifact स्टोरेज आर्किटेक्चर\n\nयह दस्तावेज़ बताता है कि coding-agent बड़े/बाइनरी पेलोड को सेशन JSONL के बाहर कैसे स्टोर करता है, ट्रंकेटेड टूल आउटपुट कैसे पर्सिस्ट किया जाता है, और आंतरिक URL (`artifact://`, `agent://`) स्टोर किए गए डेटा में कैसे रिज़ॉल्व होते हैं।\n\n## दो स्टोरेज सिस्टम क्यों मौजूद हैं\n\nरनटाइम विभिन्न डेटा आकारों के लिए दो अलग-अलग पर्सिस्टेंस मैकेनिज़्म का उपयोग करता है:\n\n- **Content-addressed blobs** (`blob:sha256:<hash>`): ग्लोबल, बाइनरी-ओरिएंटेड स्टोरेज जो पर्सिस्टेड सेशन एंट्रीज़ से बड़े इमेज base64 पेलोड को एक्सटर्नलाइज़ करने के लिए उपयोग किया जाता है।\n- **सेशन-स्कोप्ड artifacts** (`<sessionFile-without-.jsonl>/` के अंतर्गत फ़ाइलें): पूर्ण टूल आउटपुट और सबएजेंट आउटपुट के लिए प्रति-सेशन टेक्स्ट फ़ाइलें।\n\nये जानबूझकर अलग हैं:\n\n- blob स्टोरेज कंटेंट हैश द्वारा डीडुप्लिकेशन और स्थिर संदर्भों को ऑप्टिमाइज़ करता है,\n- artifact स्टोरेज append-only सेशन टूलिंग और ह्यूमन/टूल रिट्रीवल को लोकल ID द्वारा ऑप्टिमाइज़ करता है।\n\n## स्टोरेज सीमाएँ और ऑन-डिस्क लेआउट\n\n## Blob स्टोर सीमा (ग्लोबल)\n\n`SessionManager` `BlobStore(getBlobsDir())` बनाता है, इसलिए blob फ़ाइलें एक साझा ग्लोबल blob डायरेक्टरी में रहती हैं (सेशन फ़ोल्डर में नहीं)।\n\nBlob फ़ाइल नामकरण:\n\n- फ़ाइल पथ: `<blobsDir>/<sha256-hex>`\n- कोई एक्सटेंशन नहीं\n- एंट्रीज़ में स्टोर किया गया संदर्भ स्ट्रिंग: `blob:sha256:<sha256-hex>`\n\nनिहितार्थ:\n\n- सेशनों में समान बाइनरी कंटेंट उसी हैश/पथ पर रिज़ॉल्व होता है,\n- कंटेंट स्तर पर राइट्स इडेम्पोटेंट हैं,\n- blobs किसी भी व्यक्तिगत सेशन फ़ाइल से अधिक समय तक जीवित रह सकते हैं।\n\n## Artifact सीमा (सेशन-लोकल)\n\n`ArtifactManager` सेशन फ़ाइल पथ से artifact डायरेक्टरी प्राप्त करता है:\n\n- सेशन फ़ाइल: `.../<timestamp>_<sessionId>.jsonl`\n- artifacts डायरेक्टरी: `.../<timestamp>_<sessionId>/` (`.jsonl` हटाकर)\n\nArtifact प्रकार इस डायरेक्टरी को साझा करते हैं:\n\n- ट्रंकेटेड टूल आउटपुट फ़ाइलें: `<numericId>.<toolType>.log` (`artifact://` के लिए)\n- सबएजेंट आउटपुट फ़ाइलें: `<outputId>.md` (`agent://` के लिए)\n\n## ID और नाम आवंटन योजनाएँ\n\n## Blob ID: कंटेंट हैश\n\n`BlobStore.put()` रॉ बाइनरी बाइट्स पर SHA-256 कम्प्यूट करता है और लौटाता है:\n\n- `hash`: हेक्स डाइजेस्ट,\n- `path`: `<blobsDir>/<hash>`,\n- `ref`: `blob:sha256:<hash>`।\n\nकोई सेशन-लोकल काउंटर उपयोग नहीं किया जाता।\n\n## Artifact ID: सेशन-लोकल मोनोटोनिक इंटीजर\n\n`ArtifactManager` पहले उपयोग पर मौजूदा `*.log` artifact फ़ाइलों को स्कैन करता है ताकि अधिकतम मौजूदा न्यूमेरिक ID मिले और `nextId = max + 1` सेट करे।\n\nआवंटन व्यवहार:\n\n- फ़ाइल फ़ॉर्मेट: `{id}.{toolType}.log`\n- ID अनुक्रमिक स्ट्रिंग हैं (`\"0\"`, `\"1\"`, ...)\n- रिज़्यूम मौजूदा artifacts को ओवरराइट नहीं करता क्योंकि स्कैन आवंटन से पहले होता है।\n\nयदि artifact डायरेक्टरी गायब है, तो स्कैनिंग खाली सूची देती है और आवंटन `0` से शुरू होता है।\n\n## एजेंट आउटपुट ID (`agent://`)\n\n`AgentOutputManager` सबएजेंट आउटपुट के लिए ID `<index>-<requestedId>` के रूप में आवंटित करता है (वैकल्पिक रूप से पैरेंट प्रीफ़िक्स के अंतर्गत नेस्टेड, जैसे `0-Parent.1-Child`)। यह रिज़्यूम पर अगले इंडेक्स से जारी रखने के लिए इनिशियलाइज़ेशन पर मौजूदा `.md` फ़ाइलों को स्कैन करता है।\n\n## पर्सिस्टेंस डेटाफ़्लो\n\n## 1) सेशन एंट्री पर्सिस्टेंस रीराइट पथ\n\nसेशन एंट्रीज़ लिखे जाने से पहले (`#rewriteFile` / इंक्रीमेंटल पर्सिस्ट), `SessionManager` `prepareEntryForPersistence()` (`truncateForPersistence` के माध्यम से) कॉल करता है।\n\nमुख्य व्यवहार:\n\n1. **बड़ी स्ट्रिंग ट्रंकेशन**: ओवरसाइज़्ड स्ट्रिंग्स को काटा जाता है और `\"[Session persistence truncated large content]\"` सफ़िक्स किया जाता है।\n2. **ट्रांसिएंट फ़ील्ड स्ट्रिपिंग**: `partialJson` और `jsonlEvents` पर्सिस्टेड एंट्रीज़ से हटा दिए जाते हैं।\n3. **इमेज एक्सटर्नलाइज़ेशन blobs में**:\n   - केवल `content` ऐरे में इमेज ब्लॉक्स पर लागू होता है,\n   - केवल जब `data` पहले से blob ref नहीं है,\n   - केवल जब base64 लंबाई कम से कम थ्रेशोल्ड (`BLOB_EXTERNALIZE_THRESHOLD = 1024`) है,\n   - इनलाइन base64 को `blob:sha256:<hash>` से बदलता है।\n\nयह सेशन JSONL को कॉम्पैक्ट रखता है जबकि रिकवरेबिलिटी बनाए रखता है।\n\n## 2) सेशन लोड रीहाइड्रेशन पथ\n\nसेशन खोलते समय (`setSessionFile`), माइग्रेशन के बाद, `SessionManager` `resolveBlobRefsInEntries()` चलाता है।\n\nप्रत्येक message/custom-message इमेज ब्लॉक के लिए जिसमें `blob:sha256:<hash>` है:\n\n- blob स्टोर से blob बाइट्स पढ़ता है,\n- बाइट्स को वापस base64 में कनवर्ट करता है,\n- रनटाइम कंज़्यूमर्स के लिए इन-मेमोरी एंट्री को इनलाइन base64 में म्यूटेट करता है।\n\nयदि blob गायब है:\n\n- `resolveImageData()` चेतावनी लॉग करता है,\n- मूल ref स्ट्रिंग अपरिवर्तित लौटाता है,\n- लोड जारी रहता है (कोई हार्ड क्रैश नहीं)।\n\n## 3) टूल आउटपुट स्पिल/ट्रंकेशन पथ\n\n`OutputSink` bash/python/ssh और संबंधित एक्ज़ीक्यूटर्स में स्ट्रीमिंग आउटपुट को पावर करता है।\n\nव्यवहार:\n\n1. प्रत्येक चंक को सैनिटाइज़ किया जाता है और इन-मेमोरी टेल बफ़र में जोड़ा जाता है।\n2. जब इन-मेमोरी बाइट्स स्पिल थ्रेशोल्ड (`DEFAULT_MAX_BYTES`, 50KB) से अधिक हो जाते हैं, तो सिंक आउटपुट को ट्रंकेटेड मार्क करता है।\n3. यदि artifact पथ उपलब्ध है, तो सिंक एक फ़ाइल राइटर खोलता है और लिखता है:\n   - मौजूदा बफ़र्ड कंटेंट एक बार,\n   - सभी बाद के चंक।\n4. इन-मेमोरी बफ़र हमेशा डिस्प्ले के लिए टेल विंडो तक ट्रिम किया जाता है।\n5. `dump()` `artifactId` सहित सारांश लौटाता है केवल जब फ़ाइल सिंक सफलतापूर्वक बनाया गया हो।\n\nव्यावहारिक प्रभाव:\n\n- UI/टूल रिटर्न ट्रंकेटेड टेल दिखाता है,\n- पूर्ण आउटपुट artifact फ़ाइल में संरक्षित है और `artifact://<id>` के रूप में संदर्भित है।\n\nयदि फ़ाइल सिंक निर्माण विफल होता है (I/O एरर, मिसिंग पथ, आदि), तो सिंक चुपचाप केवल इन-मेमोरी ट्रंकेशन पर फ़ॉलबैक करता है; पूर्ण आउटपुट पर्सिस्ट नहीं होता।\n\n## URL एक्सेस मॉडल\n\n## `blob:` संदर्भ\n\n`blob:sha256:<hash>` सेशन एंट्री पेलोड के अंदर एक पर्सिस्टेंस संदर्भ है, राउटर द्वारा हैंडल किया जाने वाला आंतरिक URL स्कीम नहीं। रिज़ॉल्यूशन सेशन लोड के दौरान `SessionManager` द्वारा किया जाता है।\n\n## `artifact://<id>`\n\n`ArtifactProtocolHandler` द्वारा हैंडल किया जाता है:\n\n- सक्रिय सेशन artifact डायरेक्टरी की आवश्यकता है,\n- ID न्यूमेरिक होना चाहिए,\n- फ़ाइलनाम प्रीफ़िक्स `<id>.` मैच करके रिज़ॉल्व करता है,\n- मैच की गई `.log` फ़ाइल से रॉ टेक्स्ट (`text/plain`) लौटाता है,\n- गायब होने पर, एरर में उपलब्ध artifact ID की सूची शामिल होती है।\n\nगायब डायरेक्टरी का व्यवहार:\n\n- यदि artifacts डायरेक्टरी मौजूद नहीं है, तो `No artifacts directory found` थ्रो करता है।\n\n## `agent://<id>`\n\n`AgentProtocolHandler` द्वारा `<artifactsDir>/<id>.md` पर हैंडल किया जाता है:\n\n- सादा फ़ॉर्म मार्कडाउन टेक्स्ट लौटाता है,\n- `/path` या `?q=` फ़ॉर्म JSON एक्सट्रैक्शन करते हैं,\n- path और query एक्सट्रैक्शन को संयोजित नहीं किया जा सकता,\n- यदि एक्सट्रैक्शन अनुरोधित है, तो फ़ाइल कंटेंट को JSON के रूप में पार्स होना चाहिए।\n\nगायब डायरेक्टरी का व्यवहार:\n\n- `No artifacts directory found` थ्रो करता है।\n\nगायब आउटपुट का व्यवहार:\n\n- मौजूदा `.md` फ़ाइलों से उपलब्ध ID के साथ `Not found: <id>` थ्रो करता है।\n\nRead टूल एकीकरण:\n\n- `read` नॉन-एक्सट्रैक्शन आंतरिक URL रीड्स के लिए offset/limit पेजिनेशन का समर्थन करता है,\n- जब `agent://` एक्सट्रैक्शन उपयोग किया जाता है तो `offset/limit` को अस्वीकार करता है।\n\n## रिज़्यूम, फ़ोर्क, और मूव सिमेंटिक्स\n\n## रिज़्यूम\n\n- `ArtifactManager` पहले आवंटन पर मौजूदा `{id}.*.log` फ़ाइलों को स्कैन करता है और नंबरिंग जारी रखता है।\n- `AgentOutputManager` मौजूदा `.md` आउटपुट ID को स्कैन करता है और नंबरिंग जारी रखता है।\n- `SessionManager` लोड पर blob refs को base64 में रीहाइड्रेट करता है।\n\n## फ़ोर्क\n\n`SessionManager.fork()` नई सेशन ID और `parentSession` लिंक के साथ एक नई सेशन फ़ाइल बनाता है, फिर पुरानी/नई फ़ाइल पथ लौटाता है। Artifact कॉपीइंग `AgentSession.fork()` द्वारा हैंडल की जाती है:\n\n- पुरानी artifact डायरेक्टरी को नई artifact डायरेक्टरी में रिकर्सिव कॉपी का प्रयास करता है,\n- गायब पुरानी डायरेक्टरी सहन की जाती है,\n- नॉन-ENOENT कॉपी एरर चेतावनी के रूप में लॉग किए जाते हैं और फ़ोर्क फिर भी पूरा होता है।\n\nफ़ोर्क के बाद ID निहितार्थ:\n\n- यदि कॉपी सफल हुई, तो नए सेशन में artifact काउंटर अधिकतम कॉपी किए गए ID के बाद से जारी रहते हैं,\n- यदि कॉपी विफल/स्किप हुई, तो नए सेशन artifact ID `0` से शुरू होते हैं।\n\nफ़ोर्क के बाद blob निहितार्थ:\n\n- blobs ग्लोबल और content-addressed हैं, इसलिए blob डायरेक्टरी कॉपी की आवश्यकता नहीं है।\n\n## नए cwd में मूव\n\n`SessionManager.moveTo()` सेशन फ़ाइल और artifact डायरेक्टरी दोनों को नई डिफ़ॉल्ट सेशन डायरेक्टरी में रीनेम करता है, यदि बाद का कोई चरण विफल होता है तो रोलबैक लॉजिक के साथ। यह सेशन स्कोप को स्थानांतरित करते हुए artifact पहचान को संरक्षित करता है।\n\n## विफलता हैंडलिंग और फ़ॉलबैक पथ\n\n| मामला | व्यवहार |\n| --- | --- |\n| रीहाइड्रेशन के दौरान Blob फ़ाइल गायब | चेतावनी और `blob:sha256:` ref स्ट्रिंग इन-मेमोरी रखें |\n| `BlobStore.get` के माध्यम से Blob रीड ENOENT | `null` लौटाता है |\n| Artifact डायरेक्टरी गायब (`ArtifactManager.listFiles`) | खाली सूची लौटाता है (आवंटन नए सिरे से शुरू हो सकता है) |\n| Artifact डायरेक्टरी गायब (`artifact://` / `agent://`) | स्पष्ट `No artifacts directory found` थ्रो करता है |\n| Artifact ID नहीं मिला | उपलब्ध ID सूची के साथ थ्रो करता है |\n| OutputSink artifact राइटर इनिट विफल | केवल टेल-ओनली ट्रंकेशन के साथ जारी रहता है (कोई पूर्ण-आउटपुट artifact नहीं) |\n| कोई सेशन फ़ाइल नहीं (कुछ टास्क पथ) | टास्क टूल सबएजेंट आउटपुट के लिए अस्थायी artifacts डायरेक्टरी पर फ़ॉलबैक करता है |\n\n## बाइनरी blob एक्सटर्नलाइज़ेशन बनाम टेक्स्ट-आउटपुट artifacts\n\n- **Blob एक्सटर्नलाइज़ेशन** पर्सिस्टेड सेशन एंट्री कंटेंट के अंदर बाइनरी इमेज पेलोड के लिए है; यह JSONL में इनलाइन base64 को स्थिर कंटेंट refs से बदलता है।\n- **Artifacts** एक्ज़ीक्यूशन आउटपुट और सबएजेंट आउटपुट के लिए सादी टेक्स्ट फ़ाइलें हैं; ये आंतरिक URL के माध्यम से सेशन-लोकल ID द्वारा एड्रेसेबल हैं।\n\nदोनों सिस्टम केवल अप्रत्यक्ष रूप से इंटरसेक्ट करते हैं (दोनों सेशन JSONL ब्लोट को कम करते हैं) लेकिन उनकी पहचान, जीवनकाल, और रिट्रीवल पथ अलग-अलग हैं।\n\n## कार्यान्वयन फ़ाइलें\n\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts) — blob संदर्भ फ़ॉर्मेट, हैशिंग, put/get, externalize/resolve हेल्पर्स।\n- [`src/session/artifacts.ts`](../../packages/coding-agent/src/session/artifacts.ts) — सेशन artifact डायरेक्टरी मॉडल और न्यूमेरिक artifact ID आवंटन।\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — `OutputSink` ट्रंकेशन/स्पिल-टू-फ़ाइल व्यवहार और सारांश मेटाडेटा।\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts) — पर्सिस्टेंस ट्रांसफ़ॉर्म, लोड पर blob रीहाइड्रेशन, सेशन फ़ोर्क/मूव इंटरैक्शन।\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — इंटरैक्टिव फ़ोर्क के दौरान artifact डायरेक्टरी कॉपी।\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — टूल artifact मैनेजर बूटस्ट्रैप और प्रति-टूल artifact पथ आवंटन।\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — `artifact://` रिज़ॉल्वर।\n- [`src/internal-urls/agent-protocol.ts`](../../packages/coding-agent/src/internal-urls/agent-protocol.ts) — `agent://` रिज़ॉल्वर + JSON एक्सट्रैक्शन।\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — आंतरिक URL राउटर वायरिंग और artifacts-dir रिज़ॉल्वर।\n- [`src/task/output-manager.ts`](../../packages/coding-agent/src/task/output-manager.ts) — `agent://` के लिए सेशन-स्कोप्ड एजेंट आउटपुट ID आवंटन।\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — सबएजेंट आउटपुट artifact राइट्स (`<id>.md`) और अस्थायी artifact डायरेक्टरी फ़ॉलबैक।\n",
	"hi/configuration/config-usage.md": "---\ntitle: कॉन्फ़िगरेशन डिस्कवरी और रिज़ॉल्यूशन\ndescription: >-\n  xcsh कैसे प्रोजेक्ट, उपयोगकर्ता, और एंटरप्राइज़ रूट्स से कॉन्फ़िगरेशन खोजता,\n  हल करता, और परतों में व्यवस्थित करता है।\nsidebar:\n  order: 1\n  label: कॉन्फ़िगरेशन\ni18n:\n  sourceHash: e38bd9792499\n  translator: machine\n---\n\n# कॉन्फ़िगरेशन डिस्कवरी और रिज़ॉल्यूशन\n\nयह दस्तावेज़ वर्णन करता है कि coding-agent आज कॉन्फ़िगरेशन को कैसे हल करता है: कौन से रूट्स स्कैन किए जाते हैं, प्राथमिकता कैसे काम करती है, और हल किया गया कॉन्फ़िग settings, skills, hooks, tools, और extensions द्वारा कैसे उपभोग किया जाता है।\n\n## दायरा\n\nप्राथमिक कार्यान्वयन:\n\n- `src/config.ts`\n- `src/config/settings.ts`\n- `src/config/settings-schema.ts`\n- `src/discovery/builtin.ts`\n- `src/discovery/helpers.ts`\n\nमुख्य एकीकरण बिंदु:\n\n- `src/capability/index.ts`\n- `src/discovery/index.ts`\n- `src/extensibility/skills.ts`\n- `src/extensibility/hooks/loader.ts`\n- `src/extensibility/custom-tools/loader.ts`\n- `src/extensibility/extensions/loader.ts`\n\n---\n\n## रिज़ॉल्यूशन प्रवाह (दृश्य)\n\n```text\n         Config roots (ordered)\n┌───────────────────────────────────────┐\n│ 1) ~/.xcsh/agent + <cwd>/.xcsh          │\n│ 2) ~/.claude   + <cwd>/.claude        │\n│ 3) ~/.codex    + <cwd>/.codex         │\n│ 4) ~/.gemini   + <cwd>/.gemini        │\n└───────────────────────────────────────┘\n                    │\n                    ▼\n        config.ts helper resolution\n  (getConfigDirs/findConfigFile/findNearest...)\n                    │\n                    ▼\n       capability providers enumerate items\n (native, claude, codex, gemini, agents, etc.)\n                    │\n                    ▼\n      priority sort + per-capability dedup\n                    │\n                    ▼\n          subsystem-specific consumption\n   (settings, skills, hooks, tools, extensions)\n```\n\n## 1) कॉन्फ़िग रूट्स और स्रोत क्रम\n\n## कैनोनिकल रूट्स\n\n`src/config.ts` एक निश्चित स्रोत प्राथमिकता सूची परिभाषित करता है:\n\n1. `.xcsh` (नेटिव)\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nउपयोगकर्ता-स्तर के आधार:\n\n- `~/.xcsh/agent`\n- `~/.claude`\n- `~/.codex`\n- `~/.gemini`\n\nप्रोजेक्ट-स्तर के आधार:\n\n- `<cwd>/.xcsh`\n- `<cwd>/.claude`\n- `<cwd>/.codex`\n- `<cwd>/.gemini`\n\n`CONFIG_DIR_NAME` `.xcsh` है (`packages/utils/src/dirs.ts`)।\n\n## महत्वपूर्ण प्रतिबंध\n\n`src/config.ts` में जेनेरिक हेल्पर्स स्रोत डिस्कवरी क्रम में `.pi` को शामिल **नहीं** करते हैं।\n\n---\n\n## 2) कोर डिस्कवरी हेल्पर्स (`src/config.ts`)\n\n## `getConfigDirs(subpath, options)`\n\nक्रमबद्ध प्रविष्टियाँ लौटाता है:\n\n- पहले उपयोगकर्ता-स्तर की प्रविष्टियाँ (स्रोत प्राथमिकता के अनुसार)\n- फिर प्रोजेक्ट-स्तर की प्रविष्टियाँ (उसी स्रोत प्राथमिकता के अनुसार)\n\nविकल्प:\n\n- `user` (डिफ़ॉल्ट `true`)\n- `project` (डिफ़ॉल्ट `true`)\n- `cwd` (डिफ़ॉल्ट `getProjectDir()`)\n- `existingOnly` (डिफ़ॉल्ट `false`)\n\nयह API डायरेक्टरी-आधारित कॉन्फ़िग लुकअप (commands, hooks, tools, agents, आदि) के लिए उपयोग किया जाता है।\n\n## `findConfigFile(subpath, options)` / `findConfigFileWithMeta(...)`\n\nक्रमबद्ध आधारों में पहली मौजूद फ़ाइल खोजता है, पहला मिलान लौटाता है (केवल-पथ या पथ+मेटाडेटा)।\n\n## `findAllNearestProjectConfigDirs(subpath, cwd)`\n\nपैरेंट डायरेक्टरीज़ में ऊपर की ओर चलता है और **प्रत्येक स्रोत आधार** (`.xcsh`, `.claude`, `.codex`, `.gemini`) के लिए **निकटतम मौजूद डायरेक्टरी** लौटाता है, फिर परिणामों को स्रोत प्राथमिकता के अनुसार क्रमबद्ध करता है।\n\nइसका उपयोग तब करें जब प्रोजेक्ट कॉन्फ़िग को पूर्वज डायरेक्टरीज़ से इनहेरिट किया जाना चाहिए (मोनोरेपो/नेस्टेड वर्कस्पेस व्यवहार)।\n\n---\n\n## 3) फ़ाइल कॉन्फ़िग रैपर (`ConfigFile<T>` `src/config.ts` में)\n\n`ConfigFile<T>` एकल कॉन्फ़िग फ़ाइलों के लिए स्कीमा-सत्यापित लोडर है।\n\nसमर्थित प्रारूप:\n\n- `.yml` / `.yaml`\n- `.json` / `.jsonc`\n\nव्यवहार:\n\n- AJV के साथ प्रदान किए गए TypeBox स्कीमा के विरुद्ध पार्स किए गए डेटा को सत्यापित करता है।\n- `invalidate()` तक लोड परिणाम को कैश करता है।\n- `tryLoad()` के माध्यम से त्रि-स्थिति परिणाम लौटाता है:\n  - `ok`\n  - `not-found`\n  - `error` (स्कीमा/पार्स संदर्भ के साथ `ConfigError`)\n\nलेगेसी माइग्रेशन अभी भी समर्थित:\n\n- यदि लक्ष्य पथ `.yml`/`.yaml` है, तो एक सहोदर `.json` एक बार स्वचालित रूप से माइग्रेट किया जाता है (`migrateJsonToYml`)।\n\n---\n\n## 4) सेटिंग्स रिज़ॉल्यूशन मॉडल (`src/config/settings.ts`)\n\nरनटाइम सेटिंग्स मॉडल परतों में है:\n\n1. वैश्विक सेटिंग्स: `~/.xcsh/agent/config.yml`\n2. प्रोजेक्ट सेटिंग्स: settings capability के माध्यम से खोजी गई (प्रदाताओं से `settings.json`)\n3. रनटाइम ओवरराइड: इन-मेमोरी, गैर-स्थायी\n4. स्कीमा डिफ़ॉल्ट: `SETTINGS_SCHEMA` से\n\nप्रभावी रीड पथ:\n\n`defaults <- global <- project <- overrides`\n\nराइट व्यवहार:\n\n- `settings.set(...)` **वैश्विक** परत (`config.yml`) में लिखता है और बैकग्राउंड सेव कतारबद्ध करता है।\n- प्रोजेक्ट सेटिंग्स capability discovery से केवल-पठनीय हैं।\n\n## माइग्रेशन व्यवहार अभी भी सक्रिय\n\nस्टार्टअप पर, यदि `config.yml` अनुपस्थित है:\n\n1. `~/.xcsh/agent/settings.json` से माइग्रेट करें (सफलता पर `.bak` में नाम बदला जाता है)\n2. `agent.db` से लेगेसी DB सेटिंग्स के साथ मर्ज करें\n3. मर्ज किए गए परिणाम को `config.yml` में लिखें\n\n`#migrateRawSettings` में फ़ील्ड-स्तर माइग्रेशन:\n\n- `queueMode` -> `steeringMode`\n- `ask.timeout` मिलीसेकंड -> सेकंड जब पुराना मान ms जैसा लगता है (`> 1000`)\n- लेगेसी फ्लैट `theme: \"...\"` -> `theme.dark/theme.light` संरचना\n\n---\n\n## 5) Capability/डिस्कवरी एकीकरण\n\nअधिकांश गैर-कोर कॉन्फ़िग लोडिंग capability रजिस्ट्री (`src/capability/index.ts` + `src/discovery/index.ts`) के माध्यम से होती है।\n\n## प्रदाता क्रम\n\nप्रदाता संख्यात्मक प्राथमिकता (उच्चतर पहले) के अनुसार क्रमबद्ध किए जाते हैं। उदाहरण प्राथमिकताएँ:\n\n- नेटिव OMP (`builtin.ts`): `100`\n- Claude: `80`\n- Codex / agents / Claude marketplace: `70`\n- Gemini: `60`\n\n```text\nProvider precedence (higher wins)\n\nnative (.xcsh)          priority 100\nclaude                 priority  80\ncodex / agents / ...   priority  70\ngemini                 priority  60\n```\n\n## डीडुप अर्थविज्ञान\n\nCapabilities एक `key(item)` परिभाषित करती हैं:\n\n- समान key => पहला आइटम जीतता है (उच्च-प्राथमिकता/पहले-लोड किया गया आइटम)\n- कोई key नहीं (`undefined`) => कोई डीडुप नहीं, सभी आइटम बनाए रखे जाते हैं\n\nप्रासंगिक keys:\n\n- skills: `name`\n- tools: `name`\n- hooks: `${type}:${tool}:${name}`\n- extension modules: `name`\n- extensions: `name`\n- settings: कोई डीडुप नहीं (सभी आइटम संरक्षित)\n\n---\n\n## 6) नेटिव `.xcsh` प्रदाता व्यवहार (`src/discovery/builtin.ts`)\n\nनेटिव प्रदाता (`id: native`) इनसे पढ़ता है:\n\n- प्रोजेक्ट: `<cwd>/.xcsh/...`\n- उपयोगकर्ता: `~/.xcsh/agent/...`\n\n### डायरेक्टरी प्रवेश नियम\n\n`builtin.ts` केवल तभी कॉन्फ़िग रूट शामिल करता है जब डायरेक्टरी मौजूद हो **और गैर-रिक्त हो** (`ifNonEmptyDir`)।\n\n### स्कोप-विशिष्ट लोडिंग\n\n- Skills: `skills/*/SKILL.md`\n- Slash commands: `commands/*.md`\n- Rules: `rules/*.{md,mdc}`\n- Prompts: `prompts/*.md`\n- Instructions: `instructions/*.md`\n- Hooks: `hooks/pre/*`, `hooks/post/*`\n- Tools: `tools/*.json|*.md` और `tools/<name>/index.ts`\n- Extension modules: `extensions/` के अंतर्गत खोजे गए (+ लेगेसी `settings.json.extensions` स्ट्रिंग ऐरे)\n- Extensions: `extensions/<name>/gemini-extension.json`\n- Settings capability: `settings.json`\n\n### निकटतम-प्रोजेक्ट लुकअप बारीकी\n\n`SYSTEM.md` और `XCSH.md` के लिए, नेटिव प्रदाता निकटतम-पूर्वज प्रोजेक्ट `.xcsh` डायरेक्टरी खोज (ऊपर की ओर चलना) का उपयोग करता है लेकिन फिर भी `.xcsh` dir का गैर-रिक्त होना आवश्यक है।\n\n---\n\n## 7) प्रमुख उपप्रणालियाँ कॉन्फ़िग का उपभोग कैसे करती हैं\n\n## सेटिंग्स उपप्रणाली\n\n- `Settings.init()` वैश्विक `config.yml` + खोजी गई प्रोजेक्ट `settings.json` capability आइटम्स लोड करता है।\n- केवल `level === \"project\"` वाले capability आइटम्स प्रोजेक्ट परत में मर्ज किए जाते हैं।\n\n## Skills उपप्रणाली\n\n- `extensibility/skills.ts` `loadCapability(skillCapability.id, { cwd })` के माध्यम से लोड करता है।\n- स्रोत टॉगल और फ़िल्टर लागू करता है (`ignoredSkills`, `includeSkills`, कस्टम dirs)।\n- लेगेसी-नामित टॉगल अभी भी मौजूद हैं (`skills.enablePiUser`, `skills.enablePiProject`) लेकिन वे नेटिव प्रदाता (`provider === \"native\"`) को गेट करते हैं।\n\n## Hooks उपप्रणाली\n\n- `discoverAndLoadHooks()` hook capability + स्पष्ट रूप से कॉन्फ़िगर किए गए पथों से hook पथ हल करता है।\n- फिर Bun import के माध्यम से मॉड्यूल लोड करता है।\n\n## Tools उपप्रणाली\n\n- `discoverAndLoadCustomTools()` tool capability + प्लगइन tool पथ + स्पष्ट रूप से कॉन्फ़िगर किए गए पथों से tool पथ हल करता है।\n- डिक्लेरेटिव `.md/.json` tool फ़ाइलें केवल मेटाडेटा हैं; निष्पादन योग्य लोडिंग कोड मॉड्यूल की अपेक्षा करता है।\n\n## Extensions उपप्रणाली\n\n- `discoverAndLoadExtensions()` extension-module capability प्लस स्पष्ट पथों से extension मॉड्यूल हल करता है।\n- वर्तमान कार्यान्वयन जानबूझकर लोडिंग से पहले केवल `_source.provider === \"native\"` वाले capability आइटम्स रखता है।\n\n---\n\n## 8) भरोसा करने योग्य प्राथमिकता नियम\n\nइस मानसिक मॉडल का उपयोग करें:\n\n1. `config.ts` से स्रोत डायरेक्टरी क्रम उम्मीदवार पथ क्रम निर्धारित करता है।\n2. Capability प्रदाता प्राथमिकता क्रॉस-प्रदाता प्राथमिकता निर्धारित करती है।\n3. Capability key डीडुप टक्कर व्यवहार निर्धारित करता है (कुंजीबद्ध capabilities के लिए पहला जीतता है)।\n4. उपप्रणाली-विशिष्ट मर्ज तर्क प्रभावी प्राथमिकता को और बदल सकता है (विशेषकर settings)।\n\n### Settings-विशिष्ट चेतावनी\n\nSettings capability आइटम्स डीडुप्लिकेट नहीं किए जाते; `Settings.#loadProjectSettings()` लौटाए गए क्रम में प्रोजेक्ट आइटम्स को डीप-मर्ज करता है। क्योंकि मर्ज बाद के आइटम मानों को पहले के मानों पर लागू करता है, प्रभावी ओवरराइड व्यवहार प्रदाता उत्सर्जन क्रम पर निर्भर करता है, न कि केवल capability key अर्थविज्ञान पर।\n\n---\n\n## 9) लेगेसी/संगतता व्यवहार अभी भी मौजूद\n\n- YAML-लक्षित फ़ाइलों के लिए `ConfigFile` JSON -> YAML माइग्रेशन।\n- `settings.json` और `agent.db` से `config.yml` में Settings माइग्रेशन।\n- Settings key माइग्रेशन (`queueMode`, `ask.timeout`, फ्लैट `theme`)।\n- Extension manifest संगतता: लोडर `package.json.xcsh` और `package.json.pi` दोनों manifest अनुभाग स्वीकार करता है।\n- लेगेसी सेटिंग नाम `skills.enablePiUser` / `skills.enablePiProject` अभी भी नेटिव skill स्रोत के लिए सक्रिय गेट हैं।\n\nयदि ये संगतता पथ कोड में हटा दिए जाते हैं, तो इस दस्तावेज़ को तुरंत अपडेट करें; कई रनटाइम व्यवहार आज भी इन पर निर्भर हैं।\n",
	"hi/configuration/environment-variables.md": "---\ntitle: पर्यावरण चर\ndescription: xcsh कॉन्फ़िगरेशन और व्यवहार नियंत्रण के लिए रनटाइम पर्यावरण चर संदर्भ।\nsidebar:\n  order: 2\n  label: पर्यावरण चर\ni18n:\n  sourceHash: e2890f963c02\n  translator: machine\n---\n\n# पर्यावरण चर (वर्तमान रनटाइम संदर्भ)\n\nयह संदर्भ वर्तमान कोड पथों से प्राप्त है:\n\n- `packages/coding-agent/src/**`\n- `packages/ai/src/**` (coding-agent द्वारा उपयोग किया जाने वाला प्रदाता/प्रमाणीकरण रिज़ॉल्यूशन)\n- `packages/utils/src/**` और `packages/tui/src/**` जहाँ ये चर सीधे coding-agent रनटाइम को प्रभावित करते हैं\n\nयह केवल सक्रिय व्यवहार का दस्तावेज़ीकरण करता है।\n\n## रिज़ॉल्यूशन मॉडल और प्राथमिकता\n\nअधिकांश रनटाइम लुकअप `@f5-sales-demo/pi-utils` (`packages/utils/src/env.ts`) से `$env` का उपयोग करते हैं।\n\n`$env` लोडिंग क्रम:\n\n1. मौजूदा प्रोसेस पर्यावरण (`Bun.env`)\n2. प्रोजेक्ट `.env` (`$PWD/.env`) उन कुंजियों के लिए जो पहले से सेट नहीं हैं\n3. होम `.env` (`~/.env`) उन कुंजियों के लिए जो पहले से सेट नहीं हैं\n\n`.env` फ़ाइलों में अतिरिक्त नियम: पार्स के दौरान `XCSH_*` कुंजियों को `PI_*` कुंजियों में मिरर किया जाता है।\n\n---\n\n## 1) मॉडल/प्रदाता प्रमाणीकरण\n\nये `getEnvApiKey()` (`packages/ai/src/stream.ts`) के माध्यम से उपभोग किए जाते हैं जब तक कि अन्यथा उल्लेख न किया गया हो।\n\n### मुख्य प्रदाता क्रेडेंशियल\n\n| चर                        | उपयोग | कब आवश्यक                                                 | नोट्स / प्राथमिकता                                                                                  |\n|---------------------------------|---|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|\n| `ANTHROPIC_OAUTH_TOKEN`         | Anthropic API प्रमाणीकरण | OAuth टोकन प्रमाणीकरण के साथ Anthropic का उपयोग करते समय                         | प्रदाता प्रमाणीकरण रिज़ॉल्यूशन के लिए `ANTHROPIC_API_KEY` पर प्राथमिकता लेता है                              |\n| `ANTHROPIC_API_KEY`             | Anthropic API प्रमाणीकरण | OAuth टोकन के बिना Anthropic का उपयोग करते समय                           | `ANTHROPIC_OAUTH_TOKEN` के बाद फ़ॉलबैक                                                              |\n| `ANTHROPIC_FOUNDRY_API_KEY`     | Azure Foundry / एंटरप्राइज़ गेटवे के माध्यम से Anthropic | `CLAUDE_CODE_USE_FOUNDRY` सक्षम होने पर                             | Foundry मोड सक्षम होने पर `ANTHROPIC_OAUTH_TOKEN` और `ANTHROPIC_API_KEY` पर प्राथमिकता लेता है  |\n| `OPENAI_API_KEY`                | OpenAI प्रमाणीकरण | स्पष्ट apiKey तर्क के बिना OpenAI-परिवार प्रदाताओं का उपयोग करते समय | OpenAI Completions/Responses प्रदाताओं द्वारा उपयोग किया जाता है                                                      |\n| `GEMINI_API_KEY`                | Google Gemini प्रमाणीकरण | `google` प्रदाता मॉडल का उपयोग करते समय                                | Gemini प्रदाता मैपिंग के लिए प्राथमिक कुंजी                                                             |\n| `GOOGLE_API_KEY`                | Gemini इमेज टूल प्रमाणीकरण फ़ॉलबैक | `GEMINI_API_KEY` के बिना `gemini_image` टूल का उपयोग करते समय            | coding-agent इमेज टूल फ़ॉलबैक पथ द्वारा उपयोग किया जाता है                                                       |\n| `GROQ_API_KEY`                  | Groq प्रमाणीकरण | Groq मॉडल का उपयोग करते समय                                             |                                                                                                     |\n| `CEREBRAS_API_KEY`              | Cerebras प्रमाणीकरण | Cerebras मॉडल का उपयोग करते समय                                         |                                                                                                     |\n| `TOGETHER_API_KEY`              | Together प्रमाणीकरण | `together` प्रदाता का उपयोग करते समय                                     |                                                                                                     |\n| `HUGGINGFACE_HUB_TOKEN`         | Hugging Face प्रमाणीकरण | `huggingface` प्रदाता का उपयोग करते समय                                  | प्राथमिक Hugging Face टोकन env चर                                                                  |\n| `HF_TOKEN`                      | Hugging Face प्रमाणीकरण | `huggingface` प्रदाता का उपयोग करते समय                                  | `HUGGINGFACE_HUB_TOKEN` अनसेट होने पर फ़ॉलबैक                                                      |\n| `SYNTHETIC_API_KEY`             | Synthetic प्रमाणीकरण | Synthetic मॉडल का उपयोग करते समय                                        |                                                                                                     |\n| `NVIDIA_API_KEY`                | NVIDIA प्रमाणीकरण | `nvidia` प्रदाता का उपयोग करते समय                                       |                                                                                                     |\n| `NANO_GPT_API_KEY`              | NanoGPT प्रमाणीकरण | `nanogpt` प्रदाता का उपयोग करते समय                                      |                                                                                                     |\n| `VENICE_API_KEY`                | Venice प्रमाणीकरण | `venice` प्रदाता का उपयोग करते समय                                       |                                                                                                     |\n| `LITELLM_API_KEY`               | LiteLLM प्रमाणीकरण | `litellm` प्रदाता का उपयोग करते समय                                      | OpenAI-संगत LiteLLM प्रॉक्सी कुंजी। `LITELLM_BASE_URL` के साथ सेट होने पर, `models.yml` का ऑटो-कॉन्फ़िग सक्षम करता है |\n| `LM_STUDIO_API_KEY`             | LM Studio प्रमाणीकरण (वैकल्पिक) | प्रमाणीकृत होस्ट के साथ `lm-studio` प्रदाता का उपयोग करते समय           | स्थानीय LM Studio आमतौर पर प्रमाणीकरण के बिना चलता है; जब कुंजी आवश्यक हो तो कोई भी गैर-रिक्त टोकन काम करता है         |\n| `OLLAMA_API_KEY`                | Ollama प्रमाणीकरण (वैकल्पिक) | प्रमाणीकृत होस्ट के साथ `ollama` प्रदाता का उपयोग करते समय              | स्थानीय Ollama आमतौर पर प्रमाणीकरण के बिना चलता है; जब कुंजी आवश्यक हो तो कोई भी गैर-रिक्त टोकन काम करता है            |\n| `LLAMA_CPP_API_KEY`             | Ollama प्रमाणीकरण (वैकल्पिक) | `--api-key` पैरामीटर के साथ `llama-server` का उपयोग करते समय              | स्थानीय llama.cpp आमतौर पर प्रमाणीकरण के बिना चलता है; जब कुंजी कॉन्फ़िगर हो तो कोई भी गैर-रिक्त टोकन काम करता है       |\n| `XIAOMI_API_KEY`                | Xiaomi MiMo प्रमाणीकरण | `xiaomi` प्रदाता का उपयोग करते समय                                       |                                                                                                     |\n| `MOONSHOT_API_KEY`              | Moonshot प्रमाणीकरण | `moonshot` प्रदाता का उपयोग करते समय                                     |                                                                                                     |\n| `XAI_API_KEY`                   | xAI प्रमाणीकरण | xAI मॉडल का उपयोग करते समय                                              |                                                                                                     |\n| `OPENROUTER_API_KEY`            | OpenRouter प्रमाणीकरण | OpenRouter मॉडल का उपयोग करते समय                                       | जब पसंदीदा/ऑटो प्रदाता OpenRouter हो तो इमेज टूल द्वारा भी उपयोग किया जाता है                                  |\n| `MISTRAL_API_KEY`               | Mistral प्रमाणीकरण | Mistral मॉडल का उपयोग करते समय                                          |                                                                                                     |\n| `ZAI_API_KEY`                   | z.ai प्रमाणीकरण | z.ai मॉडल का उपयोग करते समय                                             | z.ai वेब खोज प्रदाता द्वारा भी उपयोग किया जाता है                                                               |\n| `MINIMAX_API_KEY`               | MiniMax प्रमाणीकरण | `minimax` प्रदाता का उपयोग करते समय                                      |                                                                                                     |\n| `MINIMAX_CODE_API_KEY`          | MiniMax Code प्रमाणीकरण | `minimax-code` प्रदाता का उपयोग करते समय                                 |                                                                                                     |\n| `MINIMAX_CODE_CN_API_KEY`       | MiniMax Code CN प्रमाणीकरण | `minimax-code-cn` प्रदाता का उपयोग करते समय                              |                                                                                                     |\n| `OPENCODE_API_KEY`              | OpenCode प्रमाणीकरण | OpenCode मॉडल का उपयोग करते समय                                         |                                                                                                     |\n| `QIANFAN_API_KEY`               | Qianfan प्रमाणीकरण | `qianfan` प्रदाता का उपयोग करते समय                                      |                                                                                                     |\n| `QWEN_OAUTH_TOKEN`              | Qwen Portal प्रमाणीकरण | OAuth टोकन के साथ `qwen-portal` का उपयोग करते समय                          | `QWEN_PORTAL_API_KEY` पर प्राथमिकता लेता है                                                         |\n| `QWEN_PORTAL_API_KEY`           | Qwen Portal प्रमाणीकरण | API कुंजी के साथ `qwen-portal` का उपयोग करते समय                              | `QWEN_OAUTH_TOKEN` के बाद फ़ॉलबैक                                                                   |\n| `ZENMUX_API_KEY`                | ZenMux प्रमाणीकरण | `zenmux` प्रदाता का उपयोग करते समय                                       | ZenMux OpenAI और Anthropic-संगत मार्गों के लिए उपयोग किया जाता है                                              |\n| `VLLM_API_KEY`                  | vLLM प्रमाणीकरण/खोज ऑप्ट-इन | `vllm` प्रदाता (स्थानीय OpenAI-संगत सर्वर) का उपयोग करते समय       | बिना प्रमाणीकरण वाले स्थानीय सर्वरों के लिए कोई भी गैर-रिक्त मान काम करता है                                                 |\n| `CURSOR_ACCESS_TOKEN`           | Cursor प्रदाता प्रमाणीकरण | Cursor प्रदाता का उपयोग करते समय                                         |                                                                                                     |\n| `AI_GATEWAY_API_KEY`            | Vercel AI Gateway प्रमाणीकरण | `vercel-ai-gateway` प्रदाता का उपयोग करते समय                            |                                                                                                     |\n| `CLOUDFLARE_AI_GATEWAY_API_KEY` | Cloudflare AI Gateway प्रमाणीकरण | `cloudflare-ai-gateway` प्रदाता का उपयोग करते समय                        | बेस URL को `https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic` के रूप में कॉन्फ़िगर किया जाना चाहिए |\n\n### GitHub/Copilot टोकन श्रृंखलाएँ\n\n| चर | उपयोग | श्रृंखला |\n|---|---|---|\n| `COPILOT_GITHUB_TOKEN` | GitHub Copilot प्रदाता प्रमाणीकरण | `COPILOT_GITHUB_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN` |\n| `GH_TOKEN` | Copilot फ़ॉलबैक; वेब स्क्रैपर में GitHub API प्रमाणीकरण | वेब स्क्रैपर में: `GITHUB_TOKEN` → `GH_TOKEN` |\n| `GITHUB_TOKEN` | Copilot फ़ॉलबैक; वेब स्क्रैपर में GitHub API प्रमाणीकरण | वेब स्क्रैपर में: `GH_TOKEN` से पहले जाँचा जाता है |\n\n---\n\n## 2) प्रदाता-विशिष्ट रनटाइम कॉन्फ़िगरेशन\n\n### Anthropic Foundry गेटवे (Azure / एंटरप्राइज़ प्रॉक्सी)\n\nजब `CLAUDE_CODE_USE_FOUNDRY` सक्षम होता है, Anthropic अनुरोध Foundry मोड में स्विच हो जाते हैं:\n\n- बेस URL `FOUNDRY_BASE_URL` से रिज़ॉल्व होता है (अनसेट होने पर फ़ॉलबैक मॉडल/डिफ़ॉल्ट बेस URL रहता है)।\n- प्रदाता `anthropic` के लिए API कुंजी रिज़ॉल्यूशन बन जाता है:\n  `ANTHROPIC_FOUNDRY_API_KEY` → `ANTHROPIC_OAUTH_TOKEN` → `ANTHROPIC_API_KEY`।\n- `ANTHROPIC_CUSTOM_HEADERS` को कॉमा/न्यूलाइन-पृथक `key: value` जोड़ों के रूप में पार्स किया जाता है और अनुरोध हेडर में मर्ज किया जाता है।\n- TLS क्लाइंट/सर्वर सामग्री env मानों से इंजेक्ट की जा सकती है:\n  `NODE_EXTRA_CA_CERTS`, `CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`।\n  प्रत्येक निम्नलिखित में से एक स्वीकार करता है:\n  - PEM सामग्री का फ़ाइलसिस्टम पथ, या\n  - इनलाइन PEM (एस्केप्ड `\\n` अनुक्रमों सहित)।\n\n| चर | मान प्रकार | व्यवहार |\n|---|---|---|\n| `CLAUDE_CODE_USE_FOUNDRY` | बूलियन-जैसी स्ट्रिंग (`1`, `true`, `yes`, `on`) | Anthropic प्रदाता के लिए Foundry मोड सक्षम करता है |\n| `FOUNDRY_BASE_URL` | URL स्ट्रिंग | Foundry मोड में Anthropic एंडपॉइंट बेस URL |\n| `ANTHROPIC_FOUNDRY_API_KEY` | टोकन स्ट्रिंग | `Authorization: Bearer <token>` के लिए उपयोग किया जाता है |\n| `ANTHROPIC_CUSTOM_HEADERS` | हेडर सूची स्ट्रिंग | अतिरिक्त हेडर; प्रारूप `header-a: value, header-b: value` या न्यूलाइन-पृथक |\n| `NODE_EXTRA_CA_CERTS` | PEM पथ या इनलाइन PEM | सर्वर प्रमाणपत्र सत्यापन के लिए अतिरिक्त CA श्रृंखला |\n| `CLAUDE_CODE_CLIENT_CERT` | PEM पथ या इनलाइन PEM | mTLS क्लाइंट प्रमाणपत्र |\n| `CLAUDE_CODE_CLIENT_KEY` | PEM पथ या इनलाइन PEM | mTLS क्लाइंट निजी कुंजी (प्रमाणपत्र के साथ जोड़ी होनी चाहिए) |\n\n### Amazon Bedrock\n\n| चर | डिफ़ॉल्ट / व्यवहार |\n|---|---|\n| `AWS_REGION` | प्राथमिक क्षेत्र स्रोत |\n| `AWS_DEFAULT_REGION` | `AWS_REGION` अनसेट होने पर फ़ॉलबैक |\n| `AWS_PROFILE` | नामित प्रोफ़ाइल प्रमाणीकरण पथ सक्षम करता है |\n| `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` | IAM कुंजी प्रमाणीकरण पथ सक्षम करता है |\n| `AWS_BEARER_TOKEN_BEDROCK` | बेयरर टोकन प्रमाणीकरण पथ सक्षम करता है |\n| `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` / `AWS_CONTAINER_CREDENTIALS_FULL_URI` | ECS टास्क क्रेडेंशियल पथ सक्षम करता है |\n| `AWS_WEB_IDENTITY_TOKEN_FILE` + `AWS_ROLE_ARN` | वेब आइडेंटिटी प्रमाणीकरण पथ सक्षम करता है |\n| `AWS_BEDROCK_SKIP_AUTH` | यदि `1`, डमी क्रेडेंशियल इंजेक्ट करता है (प्रॉक्सी/गैर-प्रमाणीकरण परिदृश्य) |\n| `AWS_BEDROCK_FORCE_HTTP1` | यदि `1`, Node HTTP/1 अनुरोध हैंडलर को बाध्य करता है |\n\nप्रदाता कोड में क्षेत्र फ़ॉलबैक: `options.region` → `AWS_REGION` → `AWS_DEFAULT_REGION` → `us-east-1`।\n\n### Azure OpenAI Responses\n\n| चर | डिफ़ॉल्ट / व्यवहार |\n|---|---|\n| `AZURE_OPENAI_API_KEY` | आवश्यक जब तक कि API कुंजी विकल्प के रूप में पास न की गई हो |\n| `AZURE_OPENAI_API_VERSION` | डिफ़ॉल्ट `v1` |\n| `AZURE_OPENAI_BASE_URL` | प्रत्यक्ष बेस URL ओवरराइड |\n| `AZURE_OPENAI_RESOURCE_NAME` | बेस URL बनाने के लिए उपयोग किया जाता है: `https://<resource>.openai.azure.com/openai/v1` |\n| `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` | वैकल्पिक मैपिंग स्ट्रिंग: `modelId=deploymentName,model2=deployment2` |\n\nबेस URL रिज़ॉल्यूशन: विकल्प `azureBaseUrl` → env `AZURE_OPENAI_BASE_URL` → विकल्प/env संसाधन नाम → `model.baseUrl`।\n\n### Google Vertex AI\n\n| चर | आवश्यक? | नोट्स |\n|---|---|---|\n| `GOOGLE_CLOUD_PROJECT` | हाँ (जब तक विकल्पों में पास न किया गया हो) | फ़ॉलबैक: `GCLOUD_PROJECT` |\n| `GCLOUD_PROJECT` | फ़ॉलबैक | वैकल्पिक प्रोजेक्ट ID स्रोत के रूप में उपयोग किया जाता है |\n| `GOOGLE_CLOUD_LOCATION` | हाँ (जब तक विकल्पों में पास न किया गया हो) | प्रदाता में कोई डिफ़ॉल्ट नहीं |\n| `GOOGLE_APPLICATION_CREDENTIALS` | सशर्त | यदि सेट है, तो फ़ाइल मौजूद होनी चाहिए; अन्यथा ADC फ़ॉलबैक पथ जाँचा जाता है (`~/.config/gcloud/application_default_credentials.json`) |\n\n### Kimi\n\n| चर | डिफ़ॉल्ट / व्यवहार |\n|---|---|\n| `KIMI_CODE_OAUTH_HOST` | प्राथमिक OAuth होस्ट ओवरराइड |\n| `KIMI_OAUTH_HOST` | फ़ॉलबैक OAuth होस्ट ओवरराइड |\n| `KIMI_CODE_BASE_URL` | Kimi उपयोग एंडपॉइंट बेस URL को ओवरराइड करता है (`usage/kimi.ts`) |\n\nOAuth होस्ट श्रृंखला: `KIMI_CODE_OAUTH_HOST` → `KIMI_OAUTH_HOST` → `https://auth.kimi.com`।\n\n### Antigravity/Gemini इमेज संगतता\n\n| चर | डिफ़ॉल्ट / व्यवहार |\n|---|---|\n| `PI_AI_ANTIGRAVITY_VERSION` | Gemini CLI प्रदाता में Antigravity user-agent संस्करण टैग को ओवरराइड करता है |\n\n### OpenAI Codex responses (फ़ीचर/डीबग नियंत्रण)\n\n| चर | व्यवहार |\n|---|---|\n| `PI_CODEX_DEBUG` | `1`/`true` Codex प्रदाता डीबग लॉगिंग सक्षम करता है |\n| `PI_CODEX_WEBSOCKET` | `1`/`true` वेबसॉकेट ट्रांसपोर्ट प्राथमिकता सक्षम करता है |\n| `PI_CODEX_WEBSOCKET_V2` | `1`/`true` वेबसॉकेट v2 पथ सक्षम करता है |\n| `PI_CODEX_WEBSOCKET_IDLE_TIMEOUT_MS` | धनात्मक पूर्णांक ओवरराइड (डिफ़ॉल्ट 300000) |\n| `PI_CODEX_WEBSOCKET_RETRY_BUDGET` | गैर-ऋणात्मक पूर्णांक ओवरराइड (डिफ़ॉल्ट 5) |\n| `PI_CODEX_WEBSOCKET_RETRY_DELAY_MS` | धनात्मक पूर्णांक आधार बैकऑफ़ ओवरराइड (डिफ़ॉल्ट 500) |\n\n### Cursor प्रदाता डीबग\n\n| चर | व्यवहार |\n|---|---|\n| `DEBUG_CURSOR` | प्रदाता डीबग लॉग सक्षम करता है; विस्तृत पेलोड स्निपेट के लिए `2`/`verbose` |\n| `DEBUG_CURSOR_LOG` | JSONL डीबग लॉग आउटपुट के लिए वैकल्पिक फ़ाइल पथ |\n\n### प्रॉम्प्ट कैश संगतता स्विच\n\n| चर | व्यवहार |\n|---|---|\n| `PI_CACHE_RETENTION` | यदि `long`, जहाँ समर्थित हो वहाँ लंबी अवधारण सक्षम करता है (`anthropic`, `openai-responses`, Bedrock अवधारण रिज़ॉल्यूशन) |\n\n---\n\n## 3) वेब खोज उपप्रणाली\n\n### खोज प्रदाता क्रेडेंशियल\n\n| चर | किसके द्वारा उपयोग |\n|---|---|\n| `EXA_API_KEY` | Exa खोज प्रदाता और Exa MCP टूल |\n| `BRAVE_API_KEY` | Brave खोज प्रदाता |\n| `PERPLEXITY_API_KEY` | Perplexity खोज प्रदाता API-कुंजी मोड |\n| `TAVILY_API_KEY` | Tavily खोज प्रदाता |\n| `ZAI_API_KEY` | z.ai खोज प्रदाता (`agent.db` में संग्रहीत OAuth भी जाँचता है) |\n| `OPENAI_API_KEY` / DB में Codex OAuth | Codex खोज प्रदाता उपलब्धता/प्रमाणीकरण |\n\n### Anthropic वेब खोज प्रमाणीकरण श्रृंखला\n\n`packages/coding-agent/src/web/search/auth.ts` इस क्रम में Anthropic वेब-खोज क्रेडेंशियल रिज़ॉल्व करता है:\n\n1. `ANTHROPIC_SEARCH_API_KEY` (+ वैकल्पिक `ANTHROPIC_SEARCH_BASE_URL`)\n2. `api: \"anthropic-messages\"` के साथ `models.json` प्रदाता प्रविष्टि\n3. `agent.db` से Anthropic OAuth क्रेडेंशियल (5-मिनट बफ़र के भीतर समाप्त नहीं होने चाहिए)\n4. सामान्य Anthropic env फ़ॉलबैक: प्रदाता कुंजी (`ANTHROPIC_FOUNDRY_API_KEY`/`ANTHROPIC_OAUTH_TOKEN`/`ANTHROPIC_API_KEY`) + वैकल्पिक `ANTHROPIC_BASE_URL` (Foundry मोड सक्षम होने पर `FOUNDRY_BASE_URL`)\n\nसंबंधित चर:\n\n| चर | डिफ़ॉल्ट / व्यवहार |\n|---|---|\n| `ANTHROPIC_SEARCH_API_KEY` | सर्वोच्च-प्राथमिकता स्पष्ट खोज कुंजी |\n| `ANTHROPIC_SEARCH_BASE_URL` | छोड़े जाने पर `https://api.anthropic.com` पर डिफ़ॉल्ट |\n| `ANTHROPIC_SEARCH_MODEL` | `claude-haiku-4-5` पर डिफ़ॉल्ट |\n| `ANTHROPIC_BASE_URL` | टियर-4 प्रमाणीकरण पथ के लिए सामान्य फ़ॉलबैक बेस URL |\n\n### Perplexity OAuth फ़्लो व्यवहार फ़्लैग\n\n| चर | व्यवहार |\n|---|---|\n| `PI_AUTH_NO_BORROW` | यदि सेट है, Perplexity लॉगिन फ़्लो में macOS नेटिव-ऐप टोकन बॉरोइंग पथ अक्षम करता है |\n\n---\n\n## 4) Python टूलिंग और कर्नेल रनटाइम\n\n| चर | डिफ़ॉल्ट / व्यवहार |\n|---|---|\n| `PI_PY` | Python टूल मोड ओवरराइड: `0`/`bash`=`bash-only`, `1`/`py`=`ipy-only`, `mix`/`both`=`both`; अमान्य मान अनदेखे किए जाते हैं |\n| `PI_PYTHON_SKIP_CHECK` | यदि `1`, Python कर्नेल उपलब्धता जाँच/वॉर्म जाँच छोड़ता है |\n| `PI_PYTHON_GATEWAY_URL` | यदि सेट है, स्थानीय साझा गेटवे के बजाय बाहरी कर्नेल गेटवे का उपयोग करता है |\n| `PI_PYTHON_GATEWAY_TOKEN` | बाहरी गेटवे के लिए वैकल्पिक प्रमाणीकरण टोकन (`Authorization: token <value>`) |\n| `PI_PYTHON_IPC_TRACE` | यदि `1`, कर्नेल मॉड्यूल में निम्न-स्तरीय IPC ट्रेस पथ सक्षम करता है |\n| `VIRTUAL_ENV` | Python रनटाइम रिज़ॉल्यूशन के लिए सर्वोच्च-प्राथमिकता venv पथ |\n\nअतिरिक्त सशर्त व्यवहार:\n\n- यदि `BUN_ENV=test` या `NODE_ENV=test`, Python उपलब्धता जाँच को ठीक माना जाता है और वॉर्मिंग छोड़ दी जाती है।\n- Python env फ़िल्टरिंग सामान्य API कुंजियों को अस्वीकार करता है और सुरक्षित आधार चर + `LC_`, `XDG_`, `PI_` उपसर्गों को अनुमति देता है।\n\n---\n\n## 5) एजेंट/रनटाइम व्यवहार टॉगल\n\n| चर                   | डिफ़ॉल्ट / व्यवहार                                                                           |\n|----------------------------|----------------------------------------------------------------------------------------------|\n| `PI_SMOL_MODEL`            | `smol` के लिए अस्थायी मॉडल-भूमिका ओवरराइड (CLI `--smol` प्राथमिकता लेता है)                     |\n| `PI_SLOW_MODEL`            | `slow` के लिए अस्थायी मॉडल-भूमिका ओवरराइड (CLI `--slow` प्राथमिकता लेता है)                     |\n| `PI_PLAN_MODEL`            | `plan` के लिए अस्थायी मॉडल-भूमिका ओवरराइड (CLI `--plan` प्राथमिकता लेता है)                     |\n| `PI_NO_TITLE`              | यदि सेट (कोई भी गैर-रिक्त मान), पहले उपयोगकर्ता संदेश पर ऑटो सत्र शीर्षक जनरेशन अक्षम करता है   |\n| `NULL_PROMPT`              | यदि `true`, सिस्टम प्रॉम्प्ट बिल्डर खाली स्ट्रिंग लौटाता है                                        |\n| `PI_BLOCKED_AGENT`         | टास्क टूल में एक विशिष्ट सबएजेंट प्रकार को ब्लॉक करता है                                                 |\n| `PI_SUBPROCESS_CMD`        | सबएजेंट स्पॉन कमांड को ओवरराइड करता है (`xcsh` / `xcsh.cmd` रिज़ॉल्यूशन बाईपास)                       |\n| `PI_TASK_MAX_OUTPUT_BYTES` | प्रति सबएजेंट अधिकतम कैप्चर्ड आउटपुट बाइट्स (डिफ़ॉल्ट `500000`)                                    |\n| `PI_TASK_MAX_OUTPUT_LINES` | प्रति सबएजेंट अधिकतम कैप्चर्ड आउटपुट लाइनें (डिफ़ॉल्ट `5000`)                                      |\n| `PI_TIMING`                | यदि `1`, स्टार्टअप/टूल टाइमिंग इंस्ट्रूमेंटेशन लॉग सक्षम करता है                                     |\n| `PI_DEBUG_STARTUP`         | कई स्टार्टअप पथों में stderr पर स्टार्टअप स्टेज डीबग प्रिंट सक्षम करता है                       |\n| `PI_PACKAGE_DIR`           | पैकेज एसेट बेस dir रिज़ॉल्यूशन को ओवरराइड करता है (docs/examples/changelog पथ लुकअप)            |\n| `PI_DISABLE_LSPMUX`        | यदि `1`, lspmux डिटेक्शन/इंटीग्रेशन अक्षम करता है और प्रत्यक्ष LSP सर्वर स्पॉनिंग को बाध्य करता है          |\n| `LITELLM_BASE_URL`         | LiteLLM प्रॉक्सी बेस URL। `LITELLM_API_KEY` के साथ सेट होने पर, पहले रन पर `models.yml` का ऑटो-जनरेशन और हर स्टार्टअप पर स्वयं-सुधार ट्रिगर करता है |\n| `LM_STUDIO_BASE_URL`       | डिफ़ॉल्ट अंतर्निहित LM Studio खोज बेस URL ओवरराइड (अनसेट होने पर `http://127.0.0.1:1234/v1`) |\n| `OLLAMA_BASE_URL`          | डिफ़ॉल्ट अंतर्निहित Ollama खोज बेस URL ओवरराइड (अनसेट होने पर `http://127.0.0.1:11434`)      |\n| `LLAMA_CPP_BASE_URL`       | डिफ़ॉल्ट अंतर्निहित Llama.cpp खोज बेस URL ओवरराइड (अनसेट होने पर `http://127.0.0.1:8080`)    |\n| `PI_EDIT_VARIANT`          | यदि `hashline`, जब एडिट टूल उपलब्ध हो तो hashline read/grep डिस्प्ले मोड को बाध्य करता है               |\n| `PI_NO_PTY`                | यदि `1`, bash टूल के लिए इंटरैक्टिव PTY पथ अक्षम करता है                                          |\n\n`PI_NO_PTY` तब भी आंतरिक रूप से सेट किया जाता है जब CLI `--no-pty` का उपयोग किया जाता है।\n\n---\n\n## 6) स्टोरेज और कॉन्फ़िग रूट पथ\n\nये `@f5-sales-demo/pi-utils/dirs` के माध्यम से उपभोग किए जाते हैं और प्रभावित करते हैं कि coding-agent डेटा कहाँ संग्रहीत करता है।\n\n| चर | डिफ़ॉल्ट / व्यवहार |\n|---|---|\n| `PI_CONFIG_DIR` | होम के अंतर्गत कॉन्फ़िग रूट dirname (डिफ़ॉल्ट `.xcsh`) |\n| `PI_CODING_AGENT_DIR` | एजेंट निर्देशिका के लिए पूर्ण ओवरराइड (डिफ़ॉल्ट `~/<PI_CONFIG_DIR or .xcsh>/agent`) |\n| `PWD` | पथ सहायकों में कैनोनिकल वर्तमान कार्य निर्देशिका का मिलान करते समय उपयोग किया जाता है |\n\n---\n\n## 7) शेल/टूल निष्पादन पर्यावरण\n\n(`packages/utils/src/procmgr.ts` और coding-agent bash टूल इंटीग्रेशन से।)\n\n| चर | व्यवहार |\n|---|---|\n| `PI_BASH_NO_CI` | स्पॉन किए गए शेल env में स्वचालित `CI=true` इंजेक्शन दबाता है |\n| `CLAUDE_BASH_NO_CI` | `PI_BASH_NO_CI` के लिए लेगेसी उपनाम फ़ॉलबैक |\n| `PI_BASH_NO_LOGIN` | लॉगिन शेल मोड अक्षम करने के लिए अभीष्ट |\n| `CLAUDE_BASH_NO_LOGIN` | `PI_BASH_NO_LOGIN` के लिए लेगेसी उपनाम फ़ॉलबैक |\n| `PI_SHELL_PREFIX` | वैकल्पिक कमांड उपसर्ग रैपर |\n| `CLAUDE_CODE_SHELL_PREFIX` | `PI_SHELL_PREFIX` के लिए लेगेसी उपनाम फ़ॉलबैक |\n| `VISUAL` | पसंदीदा बाहरी संपादक कमांड |\n| `EDITOR` | फ़ॉलबैक बाहरी संपादक कमांड |\n\nवर्तमान कार्यान्वयन नोट: `PI_BASH_NO_LOGIN`/`CLAUDE_BASH_NO_LOGIN` पढ़े जाते हैं, लेकिन वर्तमान `getShellArgs()` दोनों शाखाओं में `['-l','-c']` लौटाता है (आज प्रभावी रूप से नो-ऑप)।\n\n---\n\n## 8) UI/थीम/सत्र पहचान (स्वतः-पहचाना गया env)\n\nये रनटाइम सिग्नल के रूप में पढ़े जाते हैं; ये आमतौर पर मैन्युअल रूप से कॉन्फ़िगर किए जाने के बजाय टर्मिनल/OS द्वारा सेट किए जाते हैं।\n\n| चर | उपयोग |\n|---|---|\n| `COLORTERM`, `TERM`, `WT_SESSION` | रंग क्षमता पहचान (थीम रंग मोड) |\n| `COLORFGBG` | टर्मिनल पृष्ठभूमि लाइट/डार्क स्वतः-पहचान |\n| `TERM_PROGRAM`, `TERM_PROGRAM_VERSION`, `TERMINAL_EMULATOR` | सिस्टम प्रॉम्प्ट/संदर्भ में टर्मिनल पहचान |\n| `KDE_FULL_SESSION`, `XDG_CURRENT_DESKTOP`, `DESKTOP_SESSION`, `XDG_SESSION_DESKTOP`, `GDMSESSION`, `WINDOWMANAGER` | सिस्टम प्रॉम्प्ट/संदर्भ में डेस्कटॉप/विंडो-मैनेजर पहचान |\n| `KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION` | स्थिर प्रति-टर्मिनल सत्र ब्रेडक्रम्ब ID |\n| `SHELL`, `ComSpec`, `TERM_PROGRAM`, `TERM` | सिस्टम जानकारी डायग्नोस्टिक्स |\n| `APPDATA`, `XDG_CONFIG_HOME` | lspmux कॉन्फ़िग पथ रिज़ॉल्यूशन |\n| `HOME` | MCP कमांड UI में पथ संक्षिप्तीकरण |\n\n---\n\n## 9) नेटिव लोडर/डीबग फ़्लैग\n\n| चर | व्यवहार |\n|---|---|\n| `PI_DEV` | `packages/natives` में वर्बोस नेटिव ऐडऑन लोड डायग्नोस्टिक्स सक्षम करता है |\n\n## 10) TUI रनटाइम फ़्लैग (साझा पैकेज, coding-agent UX को प्रभावित करता है)\n\n| चर | व्यवहार |\n|---|---|\n| `PI_NOTIFICATIONS` | `off` / `0` / `false` डेस्कटॉप नोटिफ़िकेशन दबाते हैं |\n| `PI_TUI_WRITE_LOG` | यदि सेट है, TUI लेखन को फ़ाइल में लॉग करता है |\n| `PI_HARDWARE_CURSOR` | यदि `1`, हार्डवेयर कर्सर मोड सक्षम करता है |\n| `PI_CLEAR_ON_SHRINK` | यदि `1`, जब सामग्री सिकुड़ती है तो खाली पंक्तियों को साफ़ करता है |\n| `PI_DEBUG_REDRAW` | यदि `1`, रीड्रॉ डीबग लॉगिंग सक्षम करता है |\n| `PI_TUI_DEBUG` | यदि `1`, गहन TUI डीबग डंप पथ सक्षम करता है |\n\n---\n\n## 11) कमिट जनरेशन नियंत्रण\n\n| चर | व्यवहार |\n|---|---|\n| `PI_COMMIT_TEST_FALLBACK` | यदि `true` (केस-असंवेदनशील), कमिट फ़ॉलबैक जनरेशन पथ को बाध्य करता है |\n| `PI_COMMIT_NO_FALLBACK` | यदि `true`, जब एजेंट कोई प्रस्ताव नहीं लौटाता तो फ़ॉलबैक अक्षम करता है |\n| `PI_COMMIT_MAP_REDUCE` | यदि `false`, मैप-रिड्यूस कमिट विश्लेषण पथ अक्षम करता है |\n| `DEBUG` | यदि सेट है, कमिट एजेंट त्रुटि स्टैक ट्रेस प्रिंट किए जाते हैं |\n\n---\n\n## सुरक्षा-संवेदनशील चर\n\nइन्हें गोपनीय के रूप में मानें; इन्हें लॉग या कमिट न करें:\n\n- प्रदाता/API कुंजियाँ और OAuth/बेयरर क्रेडेंशियल (सभी `*_API_KEY`, `*_TOKEN`, OAuth एक्सेस/रिफ़्रेश टोकन)\n- क्लाउड क्रेडेंशियल (`AWS_*`, `GOOGLE_APPLICATION_CREDENTIALS` पथ सर्विस-अकाउंट सामग्री उजागर कर सकता है)\n- खोज/प्रदाता प्रमाणीकरण चर (`EXA_API_KEY`, `BRAVE_API_KEY`, `PERPLEXITY_API_KEY`, Anthropic खोज कुंजियाँ)\n- Foundry mTLS सामग्री (`CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`, `NODE_EXTRA_CA_CERTS` जब यह निजी CA बंडल की ओर इंगित करता है)\n\nPython रनटाइम कर्नेल उपप्रक्रियाओं को स्पॉन करने से पहले कई सामान्य कुंजी चर को स्पष्ट रूप से हटाता है (`packages/coding-agent/src/ipy/runtime.ts`)।\n",
	"hi/configuration/fs-scan-cache-architecture.md": "---\ntitle: फ़ाइलसिस्टम स्कैन कैश आर्किटेक्चर\ndescription: >-\n  तेज़ फ़ाइल खोज के लिए फ़ाइलसिस्टम स्कैन कैश अनुबंध, जिसमें\n  stale-while-revalidate सेमेंटिक्स शामिल हैं।\nsidebar:\n  order: 8\n  label: फ़ाइलसिस्टम स्कैन कैश\ni18n:\n  sourceHash: 2a2bde1726ac\n  translator: machine\n---\n\n# फ़ाइलसिस्टम स्कैन कैश आर्किटेक्चर अनुबंध\n\nयह दस्तावेज़ Rust में लागू किए गए साझा फ़ाइलसिस्टम स्कैन कैश (`crates/pi-natives/src/fs_cache.rs`) के वर्तमान अनुबंध को परिभाषित करता है, जिसे `packages/coding-agent` को एक्सपोज़ की गई नेटिव discovery/search APIs द्वारा उपयोग किया जाता है।\n\n## यह कैश क्या है\n\nकैश में स्कैन स्कोप और ट्रैवर्सल पॉलिसी के आधार पर कुंजीबद्ध पूर्ण डायरेक्टरी-स्कैन एंट्री सूचियाँ (`GlobMatch[]`) संग्रहीत की जाती हैं, फिर उच्च-स्तरीय ऑपरेशन (glob फ़िल्टरिंग, fuzzy स्कोरिंग, grep फ़ाइल चयन) उन कैश्ड एंट्री के विरुद्ध चलाए जाते हैं।\n\nप्राथमिक लक्ष्य:\n\n- बार-बार discovery/search कॉल के लिए बार-बार फ़ाइलसिस्टम वॉक से बचना\n- `glob`, `fuzzyFind`, और `grep` के बीच एकरूपता बनाए रखना जब वे एक ही स्कैन पॉलिसी साझा करते हों\n- खाली परिणामों के लिए स्पष्ट staleness पुनर्प्राप्ति और फ़ाइल म्यूटेशन के बाद स्पष्ट invalidation की अनुमति देना\n\n## स्वामित्व और सार्वजनिक सतह\n\n- कैश कार्यान्वयन और पॉलिसी: `crates/pi-natives/src/fs_cache.rs`\n- नेटिव उपभोक्ता:\n  - `crates/pi-natives/src/glob.rs`\n  - `crates/pi-natives/src/fd.rs` (`fuzzyFind`)\n  - `crates/pi-natives/src/grep.rs`\n- JS बाइंडिंग/एक्सपोर्ट:\n  - `packages/natives/src/glob/index.ts` (`invalidateFsScanCache`)\n  - `packages/natives/src/glob/types.ts`\n  - `packages/natives/src/grep/types.ts`\n- Coding-agent म्यूटेशन invalidation हेल्पर:\n  - `packages/coding-agent/src/tools/fs-cache-invalidation.ts`\n\n## कैश कुंजी विभाजन (हार्ड अनुबंध)\n\nप्रत्येक एंट्री निम्न द्वारा कुंजीबद्ध होती है:\n\n- canonicalized `root` डायरेक्टरी पाथ\n- `include_hidden` बूलियन\n- `use_gitignore` बूलियन\n\nनिहितार्थ:\n\n- हिडन और नॉन-हिडन स्कैन एंट्री साझा **नहीं** करते।\n- Gitignore-respecting और ignore-disabled स्कैन एंट्री साझा **नहीं** करते।\n- उपभोक्ताओं को hidden/gitignore व्यवहार के लिए स्थिर सेमेंटिक्स पास करना होगा; किसी भी फ्लैग को बदलने से एक अलग कैश पार्टीशन बनता है।\n\n`node_modules` समावेश कैश कुंजी में **नहीं** है। कैश में `node_modules` सहित एंट्री संग्रहीत होती हैं; प्रति-उपभोक्ता फ़िल्टरिंग पुनर्प्राप्ति के बाद लागू होती है।\n\n## स्कैन संग्रह व्यवहार\n\nकैश पॉप्युलेशन एक निर्धारक वॉकर (`ignore::WalkBuilder`) का उपयोग करती है जो `include_hidden` और `use_gitignore` द्वारा कॉन्फ़िगर होता है:\n\n- `follow_links(false)`\n- फ़ाइल पाथ के अनुसार क्रमबद्ध\n- `.git` हमेशा छोड़ा जाता है\n- `node_modules` हमेशा कैश-स्कैन समय पर एकत्र किया जाता है (और वैकल्पिक रूप से बाद में फ़िल्टर किया जाता है)\n- एंट्री फ़ाइल प्रकार + `mtime` `symlink_metadata` के माध्यम से कैप्चर किए जाते हैं\n\nखोज रूट `resolve_search_path` द्वारा रिज़ॉल्व किए जाते हैं:\n\n- सापेक्ष पाथ वर्तमान cwd के विरुद्ध रिज़ॉल्व होते हैं\n- लक्ष्य एक मौजूदा डायरेक्टरी होनी चाहिए\n- संभव होने पर रूट canonicalize किया जाता है\n\n## ताज़गी और निष्कासन नीति\n\nवैश्विक नीति (environment-overridable):\n\n- `FS_SCAN_CACHE_TTL_MS` (डिफ़ॉल्ट `1000`)\n- `FS_SCAN_EMPTY_RECHECK_MS` (डिफ़ॉल्ट `200`)\n- `FS_SCAN_CACHE_MAX_ENTRIES` (डिफ़ॉल्ट `16`)\n\nव्यवहार:\n\n- `get_or_scan(...)`\n  - यदि TTL `0` है: कैश को पूरी तरह बायपास करें, हमेशा ताज़ा स्कैन (`cache_age_ms = 0`)\n  - TTL के भीतर कैश हिट पर: कैश्ड एंट्री + नॉन-ज़ीरो `cache_age_ms` लौटाएं\n  - एक्सपायर्ड हिट पर: कुंजी evict करें, रिस्कैन करें, ताज़ा एंट्री स्टोर करें\n- अधिकतम एंट्री प्रवर्तन `created_at` के अनुसार oldest-first eviction है\n\n## खाली-परिणाम फास्ट रीचेक (सामान्य हिट से अलग)\n\nसामान्य कैश हिट:\n\n- TTL के भीतर कैश हिट कैश्ड एंट्री लौटाती है और कुछ नहीं करती।\n\nखाली-परिणाम फास्ट रीचेक:\n\n- यह `ScanResult.cache_age_ms` का उपयोग करने वाली एक **caller-side** नीति है\n- यदि फ़िल्टर्ड/क्वेरी परिणाम खाली है और कैश्ड स्कैन आयु कम से कम `empty_recheck_ms()` है, तो कॉलर एक `force_rescan(...)` करता है और पुनः प्रयास करता है\n- यह तब stale-negative परिणामों को कम करने के लिए है जब फ़ाइलें हाल ही में जोड़ी गई हों लेकिन कैश अभी भी TTL के भीतर हो\n\nवर्तमान उपभोक्ता:\n\n- `glob`: जब फ़िल्टर्ड मैच खाली हों और स्कैन आयु सीमा से अधिक हो तो रीचेक करता है\n- `fuzzyFind` (`fd.rs`): केवल तब रीचेक करता है जब क्वेरी नॉन-एम्प्टी हो और स्कोर्ड मैच खाली हों\n- `grep`: जब चयनित candidate फ़ाइल सूची खाली हो तब रीचेक करता है\n\n## उपभोक्ता डिफ़ॉल्ट और कैश उपयोग\n\nकैश सभी एक्सपोज़्ड APIs पर opt-in है (`cache?: boolean`, डिफ़ॉल्ट `false`)।\n\nनेटिव APIs में वर्तमान डिफ़ॉल्ट:\n\n- `glob`: `hidden=false`, `gitignore=true`, `cache=false`\n- `fuzzyFind`: `hidden=false`, `gitignore=true`, `cache=false`\n- `grep`: `hidden=true`, `cache=false`, और कैश स्कैन हमेशा `use_gitignore=true` का उपयोग करता है\n\nCoding-agent कॉलर आज:\n\n- हाई-वॉल्यूम mention candidate discovery कैश सक्षम करती है:\n  - `packages/coding-agent/src/utils/file-mentions.ts`\n  - प्रोफ़ाइल: `hidden=true`, `gitignore=true`, `includeNodeModules=true`, `cache=true`\n- टूल-स्तरीय `grep` इंटीग्रेशन वर्तमान में स्कैन कैश अक्षम करता है (`cache: false`):\n  - `packages/coding-agent/src/tools/grep.ts`\n\n## Invalidation अनुबंध\n\nनेटिव invalidation एंट्रीपॉइंट:\n\n- `invalidateFsScanCache(path?: string)`\n  - `path` के साथ: उन कैश एंट्री हटाएं जिनका रूट लक्ष्य पाथ का उपसर्ग है\n  - पाथ के बिना: सभी स्कैन कैश एंट्री साफ़ करें\n\nपाथ हैंडलिंग विवरण:\n\n- सापेक्ष invalidation पाथ cwd के विरुद्ध रिज़ॉल्व होते हैं\n- invalidation canonicalization का प्रयास करती है\n- यदि लक्ष्य मौजूद नहीं है (जैसे, डिलीट), तो fallback parent को canonicalize करता है और संभव होने पर फ़ाइलनाम पुनः संलग्न करता है\n- यह create/delete/rename के लिए invalidation व्यवहार सुरक्षित रखता है जहाँ एक पक्ष मौजूद नहीं हो सकता\n\n## Coding-agent म्यूटेशन फ्लो ज़िम्मेदारियाँ\n\nCoding-agent कोड को सफल फ़ाइलसिस्टम म्यूटेशन के बाद invalidate करना होगा।\n\nकेंद्रीय हेल्पर:\n\n- `invalidateFsScanAfterWrite(path)`\n- `invalidateFsScanAfterDelete(path)`\n- `invalidateFsScanAfterRename(oldPath, newPath)` (जब पाथ अलग हों तो दोनों पक्षों को invalidate करता है)\n\nवर्तमान म्यूटेशन टूल callsite:\n\n- `packages/coding-agent/src/tools/write.ts`\n- `packages/coding-agent/src/patch/index.ts` (hashline/patch/replace फ्लो)\n\nनियम: यदि कोई फ्लो फ़ाइलसिस्टम सामग्री या स्थान को म्यूटेट करता है और इन हेल्पर को बायपास करता है, तो कैश staleness बग अपेक्षित हैं।\n\n## नया कैश उपभोक्ता सुरक्षित रूप से जोड़ना\n\nनए scanner/search पाथ में कैश उपयोग पेश करते समय:\n\n1. **स्थिर स्कैन पॉलिसी इनपुट का उपयोग करें**\n   - पहले hidden/gitignore सेमेंटिक्स तय करें\n   - उन्हें `get_or_scan`/`force_rescan` में लगातार पास करें ताकि कैश पार्टीशन जानबूझकर हों\n\n2. **कैश डेटा को केवल ट्रैवर्सल पॉलिसी द्वारा प्री-फ़िल्टर्ड मानें**\n   - पुनर्प्राप्ति के बाद टूल-विशिष्ट फ़िल्टरिंग (glob पैटर्न, टाइप फ़िल्टर, node_modules नियम) लागू करें\n   - यह कभी न मानें कि कैश्ड एंट्री पहले से आपके उच्च-स्तरीय फ़िल्टर दर्शाती हैं\n\n3. **खाली-परिणाम फास्ट रीचेक केवल stale-negative जोखिम के लिए लागू करें**\n   - `scan.cache_age_ms >= empty_recheck_ms()` का उपयोग करें\n   - `force_rescan(..., store=true, ...)` के साथ एक बार पुनः प्रयास करें\n   - इस पाथ को सामान्य कैश-हिट लॉजिक से अलग रखें\n\n4. **नो-कैश मोड का स्पष्ट रूप से सम्मान करें**\n   - जब कॉलर कैश अक्षम करे, `force_rescan(..., store=false, ...)` कॉल करें\n   - नो-कैश रिक्वेस्ट पाथ में साझा कैश पॉप्युलेट न करें\n\n5. **किसी भी नए राइट पाथ के लिए म्यूटेशन invalidation वायर करें**\n   - सफल write/edit/delete/rename के बाद, coding-agent invalidation हेल्पर कॉल करें\n   - rename/move के लिए, पुराने और नए दोनों पाथ invalidate करें\n\n6. **प्रति-कॉल TTL नॉब न जोड़ें**\n   - वर्तमान अनुबंध केवल वैश्विक नीति (env-configured) है, कोई per-request TTL ओवरराइड नहीं\n\n## ज्ञात सीमाएँ\n\n- कैश स्कोप प्रक्रिया-स्थानीय इन-मेमोरी (`DashMap`) है, प्रक्रिया पुनरारंभ के दौरान संरक्षित नहीं।\n- कैश स्कैन एंट्री संग्रहीत करता है, अंतिम टूल परिणाम नहीं।\n- `glob`/`fuzzyFind`/`grep` स्कैन एंट्री केवल तब साझा करते हैं जब कुंजी आयाम (`root`, `hidden`, `gitignore`) मेल खाते हों।\n- `.git` हमेशा कॉलर विकल्पों की परवाह किए बिना स्कैन संग्रह समय पर बाहर रखा जाता है।\n",
	"hi/configuration/hooks.md": "---\ntitle: हुक्स\ndescription: कोडिंग एजेंट जीवनचक्र में प्री/पोस्ट इवेंट स्वचालन के लिए हुक सिस्टम।\nsidebar:\n  order: 4\n  label: हुक्स\ni18n:\n  sourceHash: cdbec10bc405\n  translator: machine\n---\n\n# हुक्स\n\nयह दस्तावेज़ `src/extensibility/hooks/*` में **वर्तमान हुक सबसिस्टम कोड** का वर्णन करता है।\n\n## रनटाइम में वर्तमान स्थिति\n\nहुक पैकेज (`src/extensibility/hooks/`) अभी भी एक API सतह के रूप में निर्यातित और उपयोग योग्य है, लेकिन डिफ़ॉल्ट CLI रनटाइम अब **एक्सटेंशन रनर** पथ को प्रारंभ करता है। वर्तमान स्टार्टअप प्रवाह में:\n\n- `--hook` को `--extension` के लिए एक उपनाम के रूप में माना जाता है (CLI पथ `additionalExtensionPaths` में मर्ज किए जाते हैं)\n- उपकरण `HookToolWrapper` से नहीं, बल्कि `ExtensionToolWrapper` द्वारा लपेटे जाते हैं\n- संदर्भ रूपांतरण और जीवनचक्र उत्सर्जन `ExtensionRunner` के माध्यम से होते हैं\n\nअतः यह फ़ाइल हुक सबसिस्टम कार्यान्वयन (types/loader/runner/wrapper) का दस्तावेज़ीकरण करती है, जिसमें लीगेसी व्यवहार और बाधाएं शामिल हैं।\n\n## मुख्य फ़ाइलें\n\n- `src/extensibility/hooks/types.ts` — हुक संदर्भ, इवेंट प्रकार, और परिणाम अनुबंध\n- `src/extensibility/hooks/loader.ts` — मॉड्यूल लोडिंग और हुक डिस्कवरी ब्रिज\n- `src/extensibility/hooks/runner.ts` — इवेंट डिस्पैच, कमांड लुकअप, एरर सिग्नलिंग\n- `src/extensibility/hooks/tool-wrapper.ts` — प्री/पोस्ट टूल इंटरसेप्शन रैपर\n- `src/extensibility/hooks/index.ts` — निर्यात/पुनः-निर्यात\n\n## हुक मॉड्यूल क्या होता है\n\nएक हुक मॉड्यूल को एक फ़ैक्टरी डिफ़ॉल्ट-एक्सपोर्ट करनी होती है:\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function hook(pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName === \"bash\" && String(event.input.command ?? \"\").includes(\"rm -rf\")) {\n   return { block: true, reason: \"blocked by policy\" };\n  }\n });\n}\n```\n\nफ़ैक्टरी यह कर सकती है:\n\n- `pi.on(...)` के साथ इवेंट हैंडलर पंजीकृत करना\n- `pi.sendMessage(...)` से स्थायी कस्टम संदेश भेजना\n- `pi.appendEntry(...)` से गैर-LLM स्थिति बनाए रखना\n- `pi.registerCommand(...)` के माध्यम से स्लैश कमांड पंजीकृत करना\n- `pi.registerMessageRenderer(...)` के माध्यम से कस्टम संदेश रेंडरर पंजीकृत करना\n- `pi.exec(...)` के माध्यम से शेल कमांड चलाना\n\n## डिस्कवरी और लोडिंग\n\n`discoverAndLoadHooks(configuredPaths, cwd)` निम्नलिखित करता है:\n\n1. क्षमता रजिस्ट्री से खोजे गए हुक लोड करता है (`loadCapability(\"hooks\")`)\n2. स्पष्ट रूप से कॉन्फ़िगर किए गए पथ जोड़ता है (पूर्ण पथ द्वारा डीडुप्ड)\n3. `loadHooks(allPaths, cwd)` को कॉल करता है\n\n`loadHooks` फिर प्रत्येक पथ को आयात करता है और एक `default` फ़ंक्शन की अपेक्षा करता है।\n\n### पथ समाधान\n\n`loader.ts` हुक पथों को इस प्रकार हल करता है:\n\n- पूर्ण पथ: जैसा है वैसा उपयोग किया जाता है\n- `~` पथ: विस्तारित किया जाता है\n- सापेक्ष पथ: `cwd` के सापेक्ष हल किया जाता है\n\n### महत्वपूर्ण लीगेसी बेमेल\n\n`hookCapability` के लिए डिस्कवरी प्रोवाइडर अभी भी प्री/पोस्ट शेल-स्टाइल हुक फ़ाइलों को मॉडल करते हैं (उदाहरण के लिए `.claude/hooks/pre/*`, `.xcsh/.../hooks/pre/*`)।\n\nयहाँ हुक लोडर डायनामिक मॉड्यूल इम्पोर्ट का उपयोग करता है और एक डिफ़ॉल्ट JS/TS हुक फ़ैक्टरी की आवश्यकता होती है। यदि कोई खोजा गया हुक पथ मॉड्यूल के रूप में आयात योग्य नहीं है, तो लोड विफल हो जाता है और `LoadHooksResult.errors` में रिपोर्ट किया जाता है।\n\n## इवेंट सतहें\n\nहुक इवेंट `types.ts` में दृढ़ता से टाइप किए गए हैं।\n\n### सत्र इवेंट\n\n- `session_start`\n- `session_before_switch` → `{ cancel?: boolean }` लौटा सकता है\n- `session_switch`\n- `session_before_branch` → `{ cancel?: boolean; skipConversationRestore?: boolean }` लौटा सकता है\n- `session_branch`\n- `session_before_compact` → `{ cancel?: boolean; compaction?: CompactionResult }` लौटा सकता है\n- `session.compacting` → `{ context?: string[]; prompt?: string; preserveData?: Record<string, unknown> }` लौटा सकता है\n- `session_compact`\n- `session_before_tree` → `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }` लौटा सकता है\n- `session_tree`\n- `session_shutdown`\n\n### एजेंट/संदर्भ इवेंट\n\n- `context` → `{ messages?: Message[] }` लौटा सकता है\n- `before_agent_start` → `{ message?: { customType; content; display; details } }` लौटा सकता है\n- `agent_start`\n- `agent_end`\n- `turn_start`\n- `turn_end`\n- `auto_compaction_start`\n- `auto_compaction_end`\n- `auto_retry_start`\n- `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### टूल इवेंट (प्री/पोस्ट मॉडल)\n\n- `tool_call` (पूर्व-निष्पादन) → `{ block?: boolean; reason?: string }` लौटा सकता है\n- `tool_result` (पश्च-निष्पादन) → `{ content?; details?; isError? }` लौटा सकता है\n\nयह हुक सबसिस्टम का मूल प्री/पोस्ट इंटरसेप्शन मॉडल है।\n\n```text\nHook tool interception flow\n\ntool_call handlers\n   │\n   ├─ any { block: true }? ── yes ──> throw (tool blocked)\n   │\n   └─ no\n      │\n      ▼\n   execute underlying tool\n      │\n      ├─ success ──> tool_result handlers can override { content, details }\n      │\n      └─ error   ──> emit tool_result(isError=true) then rethrow original error\n```\n\n## निष्पादन मॉडल और म्यूटेशन सिमेंटिक्स\n\n### 1) पूर्व-निष्पादन: `tool_call`\n\n`HookToolWrapper.execute()` टूल निष्पादन से पहले `tool_call` उत्सर्जित करता है।\n\n- यदि कोई भी हैंडलर `{ block: true }` लौटाता है, तो निष्पादन रुक जाता है\n- यदि हैंडलर थ्रो करता है, तो रैपर विफल हो जाता है और निष्पादन को ब्लॉक करता है\n- लौटाया गया `reason` थ्रो किए गए एरर टेक्स्ट बन जाता है\n\n### 2) टूल निष्पादन\n\nयदि ब्लॉक नहीं किया गया है तो अंतर्निहित टूल सामान्य रूप से निष्पादित होता है।\n\n### 3) पश्च-निष्पादन: `tool_result`\n\nसफलता के बाद, रैपर `tool_result` उत्सर्जित करता है:\n\n- `toolName`, `toolCallId`, `input`\n- `content`\n- `details`\n- `isError: false`\n\nयदि हैंडलर ओवरराइड लौटाता है:\n\n- `content` परिणाम सामग्री को प्रतिस्थापित कर सकता है\n- `details` परिणाम विवरण को प्रतिस्थापित कर सकता है\n\nटूल विफलता पर, रैपर `isError: true` और एरर टेक्स्ट सामग्री के साथ `tool_result` उत्सर्जित करता है, फिर मूल एरर को पुनः थ्रो करता है।\n\n### हुक क्या म्यूटेट कर सकते हैं\n\n- `context` के माध्यम से एकल कॉल के लिए LLM संदर्भ (`messages` प्रतिस्थापन श्रृंखला)\n- सफल टूल कॉल पर टूल आउटपुट content/details (`tool_result` पथ)\n- `before_agent_start` के माध्यम से प्री-एजेंट इंजेक्टेड संदेश\n- `session_before_*` और `session.compacting` के माध्यम से रद्दीकरण/कस्टम कॉम्पैक्शन/ट्री व्यवहार\n\n### इस कार्यान्वयन में हुक क्या म्यूटेट नहीं कर सकते\n\n- कच्चे टूल इनपुट पैरामीटर इन-प्लेस (केवल `tool_call` पर ब्लॉक/अनुमति)\n- थ्रो किए गए टूल एरर के बाद निष्पादन जारी रखना (एरर पथ पुनः थ्रो करता है)\n- रैपर व्यवहार में अंतिम सफलता/एरर स्थिति (लौटाया गया `isError` टाइप किया गया है लेकिन `HookToolWrapper` द्वारा लागू नहीं किया जाता)\n\n## क्रम और टकराव व्यवहार\n\n### डिस्कवरी-स्तर क्रम\n\nक्षमता प्रोवाइडर प्राथमिकता-क्रमबद्ध होते हैं (पहले उच्च)। डीडुप्लीकेशन क्षमता कुंजी द्वारा होती है, पहले वाला जीतता है।\n\n`hooks` के लिए, क्षमता कुंजी `${type}:${tool}:${name}` है। निम्न-प्राथमिकता प्रोवाइडरों से छाया किए गए डुप्लीकेट चिह्नित किए जाते हैं और प्रभावी खोजी गई सूची से बाहर किए जाते हैं।\n\n### लोड क्रम\n\n`discoverAndLoadHooks` एक फ्लैट `allPaths` सूची बनाता है, हल किए गए पूर्ण पथ द्वारा डीडुप्ड, फिर `loadHooks` उस क्रम में पुनरावृत्त करता है।\nप्रत्येक खोजी गई डायरेक्टरी के भीतर फ़ाइल क्रम `readdir` आउटपुट पर निर्भर करता है; हुक लोडर अतिरिक्त सॉर्टिंग नहीं करता।\n\n### रनटाइम हैंडलर क्रम\n\n`HookRunner` के अंदर, क्रम पंजीकरण अनुक्रम द्वारा निर्धारक होता है:\n\n1. हुक्स सरणी क्रम\n2. हुक/इवेंट प्रति हैंडलर पंजीकरण क्रम\n\nइवेंट प्रकार के अनुसार टकराव व्यवहार:\n\n- `tool_call`: अंतिम लौटाया गया परिणाम जीतता है जब तक कोई हैंडलर ब्लॉक न करे; पहला ब्लॉक शॉर्ट-सर्किट करता है\n- `tool_result`: अंतिम लौटाया गया ओवरराइड जीतता है (कोई शॉर्ट-सर्किट नहीं)\n- `context`: श्रृंखलाबद्ध; प्रत्येक हैंडलर पूर्व हैंडलर का संदेश आउटपुट प्राप्त करता है\n- `before_agent_start`: पहला लौटाया गया संदेश रखा जाता है; बाद के संदेश अनदेखे किए जाते हैं\n- `session_before_*`: नवीनतम लौटाया गया परिणाम ट्रैक किया जाता है; `cancel: true` तुरंत शॉर्ट-सर्किट करता है\n- `session.compacting`: नवीनतम लौटाया गया परिणाम जीतता है\n\nकमांड/रेंडरर टकराव:\n\n- `getCommand(name)` हुक्स में पहला मेल लौटाता है (पहले लोड किया गया जीतता है)\n- `getMessageRenderer(customType)` पहला मेल लौटाता है\n- `getRegisteredCommands()` सभी कमांड लौटाता है (कोई डीडुप्लीकेशन नहीं)\n\n## UI इंटरेक्शन (`HookContext.ui`)\n\n`HookUIContext` में शामिल हैं:\n\n- `select`, `confirm`, `input`, `editor`\n- `notify`\n- `setStatus`\n- `custom`\n- `setEditorText`, `getEditorText`\n- `theme` गेटर\n\n`ctx.hasUI` इंगित करता है कि इंटरैक्टिव UI उपलब्ध है या नहीं।\n\nबिना UI के चलाते समय, डिफ़ॉल्ट नो-ऑप संदर्भ व्यवहार है:\n\n- `select/input/editor` `undefined` लौटाते हैं\n- `confirm` `false` लौटाता है\n- `notify`, `setStatus`, `setEditorText` नो-ऑप हैं\n- `getEditorText` `\"\"` लौटाता है\n\n### स्टेटस लाइन व्यवहार\n\n`ctx.ui.setStatus(key, text)` के माध्यम से सेट किया गया हुक स्टेटस टेक्स्ट:\n\n- प्रति कुंजी संग्रहीत होता है\n- कुंजी नाम द्वारा क्रमबद्ध होता है\n- सैनिटाइज़ किया जाता है (`\\r`, `\\n`, `\\t` → स्पेस; बार-बार आने वाले स्पेस संकुचित होते हैं)\n- प्रदर्शन के लिए जोड़ा और चौड़ाई-छंटनी किया जाता है\n\n## एरर प्रसार और फ़ॉलबैक\n\n### लोड-समय\n\n- अमान्य मॉड्यूल या गुम डिफ़ॉल्ट एक्सपोर्ट → `LoadHooksResult.errors` में कैप्चर किया जाता है\n- अन्य हुक्स के लिए लोडिंग जारी रहती है\n\n### इवेंट-समय\n\n`HookRunner.emit(...)` अधिकांश इवेंट के लिए हैंडलर एरर को पकड़ता है और श्रोताओं को `HookError` (`hookPath`, `event`, `error`) उत्सर्जित करता है, फिर जारी रहता है।\n\n`emitToolCall(...)` अधिक सख्त है: हैंडलर एरर वहाँ निगले नहीं जाते; वे कॉलर तक प्रसारित होते हैं। `HookToolWrapper` में, यह टूल कॉल को ब्लॉक करता है (फेल-सेफ)।\n\n## वास्तविक API उदाहरण\n\n### असुरक्षित बैश कमांड ब्लॉक करें\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName !== \"bash\") return;\n  const cmd = String(event.input.command ?? \"\");\n  if (!cmd.includes(\"rm -rf\")) return;\n\n  if (!ctx.hasUI) return { block: true, reason: \"rm -rf blocked (no UI)\" };\n  const ok = await ctx.ui.confirm(\"Dangerous command\", `Allow: ${cmd}`);\n  if (!ok) return { block: true, reason: \"user denied command\" };\n });\n}\n```\n\n### पश्च-निष्पादन पर टूल आउटपुट रिडैक्ट करें\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_result\", async event => {\n  if (event.toolName !== \"read\" || event.isError) return;\n\n  const redacted = event.content.map(chunk => {\n   if (chunk.type !== \"text\") return chunk;\n   return { ...chunk, text: chunk.text.replaceAll(/API_KEY=\\S+/g, \"API_KEY=[REDACTED]\") };\n  });\n\n  return { content: redacted };\n });\n}\n```\n\n### प्रति LLM कॉल मॉडल संदर्भ संशोधित करें\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"context\", async event => {\n  const filtered = event.messages.filter(msg => !(msg.role === \"custom\" && msg.customType === \"debug-only\"));\n  return { messages: filtered };\n });\n}\n```\n\n### कमांड-सेफ संदर्भ विधियों के साथ स्लैश कमांड पंजीकृत करें\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.registerCommand(\"handoff\", {\n  description: \"Create a new session with setup message\",\n  handler: async (_args, ctx) => {\n   await ctx.waitForIdle();\n   await ctx.newSession({\n    parentSession: ctx.sessionManager.getSessionFile(),\n    setup: async sm => {\n     sm.appendMessage({\n      role: \"user\",\n      content: [{ type: \"text\", text: \"Continue from prior session summary.\" }],\n      timestamp: Date.now(),\n     });\n    },\n   });\n  },\n });\n}\n```\n\n## निर्यात सतह\n\n`src/extensibility/hooks/index.ts` निर्यात करता है:\n\n- लोडिंग API (`discoverAndLoadHooks`, `loadHooks`)\n- रनर और रैपर (`HookRunner`, `HookToolWrapper`)\n- सभी हुक प्रकार\n- `execCommand` पुनः-निर्यात\n\nऔर पैकेज रूट (`src/index.ts`) लीगेसी संगतता सतह के रूप में हुक **प्रकार** पुनः-निर्यात करता है।\n",
	"hi/configuration/porting-from-pi-mono.md": "---\ntitle: 'pi-mono से पोर्टिंग: एक व्यावहारिक मर्ज गाइड'\ndescription: pi-mono मोनोरेपो से xcsh कोडबेस में कोड माइग्रेट करने के लिए व्यावहारिक गाइड।\nsidebar:\n  order: 9\n  label: pi-mono से पोर्टिंग\ni18n:\n  sourceHash: fd4e8c09303d\n  translator: machine\n---\n\n# pi-mono से पोर्टिंग: एक व्यावहारिक मर्ज गाइड\n\nयह गाइड pi-mono से इस रेपो में परिवर्तन पोर्ट करने के लिए एक दोहराने योग्य चेकलिस्ट है।\nइसे किसी भी मर्ज के लिए उपयोग करें: एकल फ़ाइल, फ़ीचर ब्रांच, या पूर्ण रिलीज़ सिंक।\n\n## अंतिम सिंक पॉइंट\n\n**कमिट:** `b21b42d032919de2f2e6920a76fa9a37c3920c0a`\n**तिथि:** 2026-03-22\n\nप्रत्येक सिंक के बाद इस अनुभाग को अपडेट करें; पिछली रेंज का पुनः उपयोग न करें।\n\nनया सिंक शुरू करते समय, इस कमिट से आगे के पैचेस जेनरेट करें:\n\n```bash\ngit format-patch b21b42d032919de2f2e6920a76fa9a37c3920c0a..HEAD --stdout > changes.patch\n```\n\n## 0) स्कोप परिभाषित करें\n\n- अपस्ट्रीम संदर्भ (कमिट, टैग, या PR) की पहचान करें।\n- उन पैकेजेस या फ़ोल्डरों को सूचीबद्ध करें जिन्हें आप बदलने की योजना बना रहे हैं।\n- तय करें कि कौन सी सुविधाएँ स्कोप में हैं और कौन सी जानबूझकर छोड़ी जा रही हैं।\n\n## 1) कोड को सुरक्षित रूप से लाएँ\n\n- थोक कॉपी के बजाय एक स्वच्छ, केंद्रित diff को प्राथमिकता दें।\n- बिल्ट आर्टिफैक्ट्स या जेनरेट की गई फ़ाइलों को कॉपी करने से बचें।\n- यदि अपस्ट्रीम ने नई फ़ाइलें जोड़ीं, तो उन्हें स्पष्ट रूप से जोड़ें और सामग्री की समीक्षा करें।\n\n## 2) इम्पोर्ट एक्सटेंशन कन्वेंशन का मिलान करें\n\nअधिकांश रनटाइम TypeScript सोर्सेस इंटरनल इम्पोर्ट्स में `.js` को छोड़ देते हैं, लेकिन कुछ test/bench एंट्रीपॉइंट्स ESM\nरनटाइम संगतता के लिए `.js` रखते हैं। लोकल पैकेज की मौजूदा शैली का पालन करें; एक्सटेंशन को बड़े पैमाने पर न हटाएँ।\n\n- `packages/coding-agent` रनटाइम सोर्सेस में, इंटरनल इम्पोर्ट्स को बिना एक्सटेंशन के रखें जब तक कि नॉन-TS एसेट्स इम्पोर्ट न कर रहे हों।\n- `packages/tui/test` और `packages/natives/bench` में, `.js` वहाँ रखें जहाँ आसपास की फ़ाइलें पहले से इसका उपयोग करती हैं।\n- जब टूलिंग द्वारा आवश्यक हो तो वास्तविक फ़ाइल एक्सटेंशन रखें (जैसे, `.json`, `.css`, `.md` टेक्स्ट एम्बेड्स)।\n- उदाहरण: `import { x } from \"./foo.js\";` → `import { x } from \"./foo\";` (केवल जब पैकेज कन्वेंशन एक्सटेंशन-रहित हो)।\n\n## 3) इम्पोर्ट स्कोप बदलें\n\nअपस्ट्रीम अलग-अलग पैकेज स्कोप का उपयोग करता है। उन्हें लगातार बदलें।\n\n- पुराने स्कोप को यहाँ उपयोग किए जाने वाले लोकल स्कोप से बदलें।\n- उदाहरण (उन वास्तविक पैकेजेस के अनुसार समायोजित करें जिन्हें आप पोर्ट कर रहे हैं):\n  - `@mariozechner/pi-coding-agent` → `@f5-sales-demo/xcsh`\n  - `@mariozechner/pi-agent-core` → `@f5-sales-demo/pi-agent-core`\n  - `@mariozechner/pi-tui` → `@f5-sales-demo/pi-tui`\n  - `@mariozechner/pi-ai` → `@f5-sales-demo/pi-ai`\n\n## 4) जहाँ Bun APIs Node से बेहतर हों, वहाँ उपयोग करें\n\nहम Bun पर चलते हैं। Node APIs को केवल तभी बदलें जब Bun बेहतर विकल्प प्रदान करता हो।\n\n**बदलें:**\n\n- प्रोसेस स्पॉनिंग: `child_process.spawn` → सरल कमांड्स के लिए Bun Shell `$`, स्ट्रीमिंग या लंबे समय तक चलने वाले कार्य के लिए `Bun.spawn`/`Bun.spawnSync`\n- फ़ाइल I/O: `fs.readFileSync` → `Bun.file().text()` / `Bun.write()`\n- HTTP क्लाइंट्स: `node-fetch`, `axios` → नेटिव `fetch`\n- क्रिप्टो हैशिंग: `node:crypto` → Web Crypto या `Bun.hash`\n- SQLite: `better-sqlite3` → `bun:sqlite`\n- Env लोडिंग: `dotenv` → Bun स्वचालित रूप से `.env` लोड करता है\n\n**न बदलें (ये Bun में ठीक काम करते हैं):**\n\n- `os.homedir()` — `Bun.env.HOME`, `Bun.env.HOME`, या शाब्दिक `\"~\"` से न बदलें\n- `os.tmpdir()` — `Bun.env.TMPDIR || \"/tmp\"` या हार्डकोडेड पाथ से न बदलें\n- `fs.mkdtempSync()` — मैन्युअल पाथ कंस्ट्रक्शन से न बदलें\n- `path.join()`, `path.resolve()`, आदि — ये ठीक हैं\n\n**इम्पोर्ट शैली:** `node:` प्रीफ़िक्स का उपयोग केवल नेमस्पेस इम्पोर्ट्स के साथ करें (कोई नेम्ड इम्पोर्ट्स `node:fs` या `node:path` से नहीं)।\n\n**अतिरिक्त Bun कन्वेंशन:**\n\n- छोटे, नॉन-स्ट्रीमिंग कमांड्स के लिए Bun Shell `$` को प्राथमिकता दें; `Bun.spawn` का उपयोग केवल तभी करें जब आपको स्ट्रीमिंग I/O या प्रोसेस कंट्रोल की आवश्यकता हो।\n- फ़ाइलों के लिए `Bun.file()`/`Bun.write()` और डायरेक्टरीज़ के लिए `node:fs/promises` का उपयोग करें।\n- `Bun.file().exists()` जाँचों से बचें; try/catch में `isEnoent` हैंडलिंग का उपयोग करें।\n- `setTimeout` रैपर्स पर `Bun.sleep(ms)` को प्राथमिकता दें।\n\n**गलत:**\n\n```typescript\n// BROKEN: env vars may be undefined, \"~\" is not expanded\nconst home = Bun.env.HOME || \"~\";\nconst tmp = Bun.env.TMPDIR || \"/tmp\";\n```\n\n**सही:**\n\n```typescript\nimport * as os from \"node:os\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst configDir = path.join(os.homedir(), \".config\", \"myapp\");\nconst tempDir = fs.mkdtempSync(path.join(os.tmpdir(), \"myapp-\"));\n```\n\n## 5) Bun एम्बेड्स को प्राथमिकता दें (कॉपी नहीं)\n\nबिल्ड समय पर रनटाइम एसेट्स या वेंडर फ़ाइलें कॉपी न करें।\n\n- यदि अपस्ट्रीम एसेट्स को dist फ़ोल्डर में कॉपी करता है, तो Bun-अनुकूल एम्बेड्स से बदलें।\n- प्रॉम्प्ट्स स्टैटिक `.md` फ़ाइलें हैं; इनलाइन प्रॉम्प्ट स्ट्रिंग्स के बजाय Bun टेक्स्ट इम्पोर्ट्स (`with { type: \"text\" }`) और Handlebars का उपयोग करें।\n- आस-पास के नॉन-टेक्स्ट रिसोर्सेस लोड करने के लिए `import.meta.dir` + `Bun.file` का उपयोग करें।\n- एसेट्स को इन-रेपो रखें और बंडलर को उन्हें शामिल करने दें।\n- कॉपी स्क्रिप्ट्स को समाप्त करें जब तक कि उपयोगकर्ता स्पष्ट रूप से उनका अनुरोध न करे।\n- यदि अपस्ट्रीम रनटाइम पर एक बंडल्ड फ़ॉलबैक फ़ाइल पढ़ता है, तो फ़ाइलसिस्टम रीड्स को Bun टेक्स्ट एम्बेड इम्पोर्ट से बदलें।\n  - उदाहरण (Codex instructions फ़ॉलबैक):\n    - `const FALLBACK_PROMPT_PATH = join(import.meta.dir, \"codex-instructions.md\");` -> हटाया गया\n    - `import FALLBACK_INSTRUCTIONS from \"./codex-instructions.md\" with { type: \"text\" };`\n    - `readFileSync(FALLBACK_PROMPT_PATH, \"utf8\")` के बजाय `return FALLBACK_INSTRUCTIONS;` का उपयोग करें\n\n## 6) `package.json` को सावधानी से पोर्ट करें\n\n`package.json` को एक अनुबंध मानें। जानबूझकर मर्ज करें।\n\n- मौजूदा `name`, `version`, `type`, `exports`, और `bin` को रखें जब तक कि पोर्ट में परिवर्तन की आवश्यकता न हो।\n- npm/node स्क्रिप्ट्स को Bun समकक्षों से बदलें (जैसे, `bun check`, `bun test`)।\n- सुनिश्चित करें कि डिपेंडेंसीज़ सही स्कोप का उपयोग करती हैं।\n- टाइप एरर ठीक करने के लिए डिपेंडेंसीज़ को डाउनग्रेड न करें; इसके बजाय अपग्रेड करें।\n- वर्कस्पेस पैकेज लिंक्स और `peerDependencies` को वेलिडेट करें।\n\n## 7) कोड स्टाइल और टूलिंग का मिलान करें\n\n- मौजूदा फ़ॉर्मेटिंग कन्वेंशन को बनाए रखें।\n- जब तक आवश्यक न हो `any` का परिचय न दें।\n- डायनामिक इम्पोर्ट्स और इनलाइन टाइप इम्पोर्ट्स से बचें; केवल टॉप-लेवल इम्पोर्ट्स का उपयोग करें।\n- कोड में कभी भी प्रॉम्प्ट्स न बनाएँ; प्रॉम्प्ट्स स्टैटिक `.md` फ़ाइलें हैं जो Handlebars से रेंडर होती हैं।\n- coding-agent में, कभी भी `console.log`/`console.warn`/`console.error` का उपयोग न करें; `@f5-sales-demo/pi-utils` से `logger` का उपयोग करें।\n- `new Promise((resolve, reject) => ...)` के बजाय `Promise.withResolvers()` का उपयोग करें।\n- **क्लास फ़ील्ड्स या मेथड्स पर `private`/`protected`/`public` कीवर्ड्स नहीं।** एनकैप्सुलेशन के लिए ES `#` प्राइवेट फ़ील्ड्स का उपयोग करें; एक्सेसिबल सदस्यों को बिना कीवर्ड के रखें। एकमात्र अपवाद कंस्ट्रक्टर पैरामीटर प्रॉपर्टीज़ (`constructor(private readonly x: T)`) है, जहाँ कीवर्ड TypeScript द्वारा आवश्यक है। जब अपस्ट्रीम कोड पोर्ट कर रहे हों जो `private foo` या `protected bar` का उपयोग करता है, तो `#foo` (प्राइवेट) या बेयर `bar` (एक्सेसिबल) में बदलें।\n- नए एड-हॉक कोड पर मौजूदा हेल्पर्स और यूटिलिटीज़ को प्राथमिकता दें।\n- इस रेपो में पहले से किए गए Bun-फ़र्स्ट इंफ्रास्ट्रक्चर परिवर्तनों को संरक्षित रखें:\n  - रनटाइम Bun है (कोई Node एंट्री पॉइंट्स नहीं)।\n  - पैकेज मैनेजर Bun है (कोई npm लॉकफ़ाइल्स नहीं)।\n  - भारी Node APIs (`child_process`, `readline`) को Bun समकक्षों से बदला गया है।\n  - हल्के Node APIs (`os.homedir`, `os.tmpdir`, `fs.mkdtempSync`, `path.*`) को रखा गया है।\n  - CLI shebangs `bun` का उपयोग करते हैं (`node` नहीं, `tsx` नहीं)।\n  - पैकेजेस सीधे सोर्स फ़ाइलों का उपयोग करते हैं (कोई TypeScript बिल्ड स्टेप नहीं)।\n  - CI वर्कफ़्लो install/check/test के लिए Bun चलाते हैं।\n\n## 8) पुरानी संगतता परतें हटाएँ\n\nजब तक अनुरोध न किया जाए, अपस्ट्रीम संगतता शिम्स हटाएँ।\n\n- पुराने APIs को हटाएँ जो बदले गए थे।\n- सभी कॉल साइट्स को सीधे नई API पर अपडेट करें।\n- `*_v2` या समानांतर संस्करण न रखें।\n\n## 9) डॉक्स और संदर्भ अपडेट करें\n\n- जहाँ उचित हो pi-mono रेपो लिंक्स को बदलें।\n- उदाहरणों को Bun और सही पैकेज स्कोप का उपयोग करने के लिए अपडेट करें।\n- सुनिश्चित करें कि README निर्देश अभी भी वर्तमान रेपो व्यवहार से मेल खाते हैं।\n\n## 10) पोर्ट को वेलिडेट करें\n\nपरिवर्तनों के बाद मानक जाँचें चलाएँ:\n\n- `bun check`\n\nयदि रेपो में पहले से ऐसी जाँचें विफल हो रही हैं जो आपके परिवर्तनों से संबंधित नहीं हैं, तो इसे स्पष्ट करें।\nटेस्ट Bun के रनर (Vitest नहीं) का उपयोग करते हैं, लेकिन `bun test` केवल तभी चलाएँ जब स्पष्ट रूप से अनुरोध किया गया हो।\n\n## 11) सुधारी गई सुविधाओं की सुरक्षा करें (रिग्रेशन ट्रैप सूची)\n\nयदि आपने पहले से स्थानीय रूप से व्यवहार में सुधार किया है, तो उन्हें **अपरिवर्तनीय** मानें। पोर्ट करने से पहले,\nसुधारों को लिखें और स्पष्ट जाँचें जोड़ें ताकि वे मर्ज में खो न जाएँ।\n\n- **अपेक्षित व्यवहार को फ़्रीज़ करें**: प्रत्येक सुधार के लिए एक संक्षिप्त \"पहले/बाद\" नोट जोड़ें (इनपुट, आउटपुट,\n  डिफ़ॉल्ट, एज केसेस)। यह मूक रोलबैक को रोकता है।\n- **पुराने → नए APIs का मैपिंग करें**: यदि अपस्ट्रीम ने अवधारणाओं का नाम बदला (hooks → extensions, custom tools → tools, आदि),\n  सुनिश्चित करें कि हर पुराना एंट्री पॉइंट अभी भी वायर है। एक छूटा हुआ फ़्लैग या एक्सपोर्ट = खोई हुई कार्यक्षमता।\n- **एक्सपोर्ट्स सत्यापित करें**: `package.json` `exports`, पब्लिक टाइप्स, और बैरल फ़ाइलों की जाँच करें। अपस्ट्रीम पोर्ट्स अक्सर\n  लोकल एडिशन्स को री-एक्सपोर्ट करना भूल जाते हैं।\n- **नॉन-हैप्पी पाथ कवर करें**: यदि आपने एरर हैंडलिंग, टाइमआउट्स, या फ़ॉलबैक लॉजिक ठीक किया है, तो एक टेस्ट या\n  कम से कम एक मैन्युअल चेकलिस्ट जोड़ें जो उन पाथ्स को एक्सरसाइज़ करे।\n- **डिफ़ॉल्ट्स और कॉन्फ़िग मर्ज ऑर्डर जाँचें**: सुधार अक्सर डिफ़ॉल्ट्स में रहते हैं। पुष्टि करें कि नए डिफ़ॉल्ट्स\n  रिवर्ट नहीं हुए (जैसे, नई कॉन्फ़िग प्राथमिकता, अक्षम सुविधाएँ, टूल सूचियाँ)।\n- **env/shell व्यवहार का ऑडिट करें**: यदि आपने एक्ज़ीक्यूशन या सैंडबॉक्सिंग ठीक की है, तो सत्यापित करें कि नया पाथ अभी भी आपके\n  सैनिटाइज़्ड env का उपयोग करता है और alias/function ओवरराइड्स को फिर से पेश नहीं करता।\n- **लक्षित सैंपल्स फिर से चलाएँ**: \"ज्ञात अच्छे\" उदाहरणों का एक न्यूनतम सेट रखें और पोर्ट के बाद उन्हें चलाएँ\n  (CLI फ़्लैग्स, एक्सटेंशन रजिस्ट्रेशन, टूल एक्ज़ीक्यूशन)।\n\n## 12) रीवर्क किए गए कोड का पता लगाएँ और उसे संभालें\n\nकिसी फ़ाइल को पोर्ट करने से पहले, जाँचें कि क्या अपस्ट्रीम ने इसे महत्वपूर्ण रूप से रीफ़ैक्टर किया है:\n\n```bash\n# Compare the file you're about to port against what you have locally\ngit diff HEAD upstream/main -- path/to/file.ts\n```\n\nयदि diff दिखाता है कि फ़ाइल **रीवर्क** की गई थी (केवल पैच नहीं):\n\n- नई एब्स्ट्रैक्शन्स, नाम बदली गई अवधारणाएँ, मर्ज किए गए मॉड्यूल, बदला हुआ डेटा फ़्लो\n\nतो आपको पोर्ट करने से पहले **नई इम्प्लीमेंटेशन को अच्छी तरह पढ़ना होगा**। रीवर्क किए गए कोड का अंधा मर्जिंग कार्यक्षमता खो देता है क्योंकि:\n\nनोट: इंटरैक्टिव मोड को हाल ही में controllers/utils/types में विभाजित किया गया था। संबंधित परिवर्तनों को बैकपोर्ट करते समय, अपडेट्स को उन व्यक्तिगत फ़ाइलों में पोर्ट करें जो हमने बनाई हैं और सुनिश्चित करें कि `interactive-mode.ts` वायरिंग सिंक में रहे।\n\n1. **डिफ़ॉल्ट्स चुपचाप बदल जाते हैं** - एक नया वेरिएबल `defaultFoo = [a, b]` पुराने `getAllFoo()` को बदल सकता है जो `[a, b, c, d, e]` लौटाता था।\n\n2. **API विकल्प छूट जाते हैं** - जब सिस्टम मर्ज होते हैं (जैसे, `hooks` + `customTools` → `extensions`), पुराने विकल्प नई इम्प्लीमेंटेशन में वायर नहीं हो सकते।\n\n3. **कोड पाथ्स स्टेल हो जाते हैं** - एक नाम बदली गई अवधारणा (जैसे, `hookMessage` → `custom`) को हर switch स्टेटमेंट, टाइप गार्ड, और हैंडलर में अपडेट की आवश्यकता होती है—केवल परिभाषा में नहीं।\n\n4. **कॉन्टेक्स्ट/कैपेबिलिटीज़ सिकुड़ती हैं** - पुरानी APIs ने `{ logger, typebox, pi }` एक्सपोज़ किया हो सकता है जिसे नई APIs ने शामिल करना भूल दिया।\n\n### सिमेंटिक पोर्टिंग प्रक्रिया\n\nजब अपस्ट्रीम ने एक मॉड्यूल रीवर्क किया हो:\n\n1. **पुरानी इम्प्लीमेंटेशन पढ़ें** - समझें कि यह क्या करती थी, कौन से विकल्प स्वीकार करती थी, क्या एक्सपोज़ करती थी।\n\n2. **नई इम्प्लीमेंटेशन पढ़ें** - नई एब्स्ट्रैक्शन्स और वे पुराने व्यवहार से कैसे मैप होती हैं, समझें।\n\n3. **फ़ीचर पैरिटी सत्यापित करें** - पुराने कोड की प्रत्येक क्षमता के लिए, पुष्टि करें कि नया कोड इसे संरक्षित करता है या स्पष्ट रूप से हटाता है।\n\n4. **बचे हुओं के लिए grep करें** - पुराने नामों/अवधारणाओं को खोजें जो switch स्टेटमेंट्स, हैंडलर्स, UI कंपोनेंट्स में छूट गए हो सकते हैं।\n\n5. **सीमाओं का परीक्षण करें** - CLI फ़्लैग्स, SDK विकल्प, इवेंट हैंडलर्स, डिफ़ॉल्ट वैल्यूज़—ये वहाँ हैं जहाँ रिग्रेशन छिपते हैं।\n\n### त्वरित जाँचें\n\n```bash\n# Find all uses of an old concept that may need updating\nrg \"oldConceptName\" --type ts\n\n# Compare default values between versions\ngit show upstream/main:path/to/file.ts | rg \"default|DEFAULT\"\n\n# Check if all enum/union values have handlers\nrg \"case \\\"\" path/to/file.ts\n```\n\n## 13) त्वरित ऑडिट चेकलिस्ट\n\nसमाप्त करने से पहले इसे अंतिम पास के रूप में उपयोग करें:\n\n- [ ] इम्पोर्ट एक्सटेंशन लोकल पैकेज कन्वेंशन का पालन करते हैं (बड़े पैमाने पर `.js` स्ट्रिपिंग नहीं)\n- [ ] नए/पोर्ट किए गए कोड में कोई Node-ओनली APIs नहीं\n- [ ] सभी पैकेज स्कोप अपडेट किए गए\n- [ ] `package.json` स्क्रिप्ट्स Bun का उपयोग करती हैं\n- [ ] प्रॉम्प्ट्स `.md` टेक्स्ट इम्पोर्ट्स हैं (कोई इनलाइन प्रॉम्प्ट स्ट्रिंग्स नहीं)\n- [ ] coding-agent में कोई `console.*` नहीं (`logger` का उपयोग करें)\n- [ ] एसेट्स Bun एम्बेड पैटर्न से लोड होती हैं (कोई कॉपी स्क्रिप्ट्स नहीं)\n- [ ] टेस्ट या जाँचें चलती हैं (या स्पष्ट रूप से ब्लॉक्ड के रूप में नोट किया गया)\n- [ ] कोई कार्यक्षमता रिग्रेशन नहीं (अनुभाग 11-12 देखें)\n\n## 14) कमिट मैसेज फ़ॉर्मेट\n\nबैकपोर्ट कमिट करते समय, रेपो फ़ॉर्मेट `<type>(scope): <past-tense description>` का पालन करें और कमिट\nरेंज को शीर्षक में रखें।\n\n```\nfix(coding-agent): backported pi-mono changes (<from>..<to>)\n\npackages/<package>:\n- <type>: <description>\n- <type>: <description> (#<issue> by @<contributor>)\n\npackages/<other-package>:\n- <type>: <description>\n```\n\n**उदाहरण:**\n\n```\nfix(coding-agent): backported pi-mono changes (9f3eef65f..52532c7c0)\n\npackages/ai:\n- fix: handle \"sensitive\" stop reason from Anthropic API\n- fix: normalize tool call IDs with special characters for Responses API\n- fix: add overflow detection for Bedrock, MiniMax, Kimi providers\n- fix: 429 status is rate limiting, not context overflow\n\npackages/tui:\n- fix: refactored autocomplete state tracking\n- fix: file autocomplete should not trigger on empty text\n- fix: configurable autocomplete max visible items\n- fix: improved table column width calculation with word-aware wrapping\n\npackages/coding-agent:\n- fix: preserve external config.yml edits on save (#1046 by @nicobailonMD)\n- fix: resolve macOS NFD and curly quote variants in file paths\n```\n\n**नियम:**\n\n- परिवर्तनों को पैकेज के अनुसार समूहित करें\n- कन्वेंशनल कमिट टाइप्स का उपयोग करें (`fix`, `feat`, `refactor`, `perf`, `docs`)\n- बाहरी योगदानों के लिए अपस्ट्रीम issue/PR नंबर और कंट्रीब्यूटर एट्रिब्यूशन शामिल करें\n- शीर्षक में कमिट रेंज सिंक पॉइंट्स को ट्रैक करने में मदद करती है\n\n## 15) जानबूझकर किए गए विचलन\n\nहमारे फ़ोर्क में वास्तुशिल्प निर्णय हैं जो अपस्ट्रीम से भिन्न हैं। **इन अपस्ट्रीम पैटर्न को पोर्ट न करें:**\n\n### UI आर्किटेक्चर\n\n| अपस्ट्रीम                                    | हमारा फ़ोर्क                                                  | कारण                                                                |\n| ------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------- |\n| `FooterDataProvider` क्लास                  | `StatusLineComponent`                                     | सरल, एकीकृत स्टेटस लाइन                                       |\n| `ctx.ui.setHeader()` / `ctx.ui.setFooter()` | नॉन-TUI मोड्स में स्टब                                     | TUI में लागू, अन्यथा no-op                                   |\n| `ctx.ui.setEditorComponent()`               | नॉन-TUI मोड्स में स्टब                                     | TUI में लागू, अन्यथा no-op                                   |\n| `InteractiveModeOptions` ऑप्शन्स ऑब्जेक्ट     | पोज़िशनल कंस्ट्रक्टर args (ऑप्शन्स टाइप अभी भी एक्सपोर्टेड) | कंस्ट्रक्टर सिग्नेचर रखें; जब अपस्ट्रीम फ़ील्ड्स जोड़े तो टाइप अपडेट करें |\n\n### कंपोनेंट नेमिंग\n\n| अपस्ट्रीम                     | हमारा फ़ोर्क                |\n| ---------------------------- | ----------------------- |\n| `extension-input.ts`         | `hook-input.ts`         |\n| `extension-selector.ts`      | `hook-selector.ts`      |\n| `ExtensionInputComponent`    | `HookInputComponent`    |\n| `ExtensionSelectorComponent` | `HookSelectorComponent` |\n\n### API नेमिंग\n\n| अपस्ट्रीम                                 | हमारा फ़ोर्क                                 | नोट्स                                     |\n| ---------------------------------------- | ---------------------------------------- | ----------------------------------------- |\n| `sessionManager.appendSessionInfo(name)` | `sessionManager.setSessionName(name)`    | हम पूरे कोड में `sessionName` का उपयोग करते हैं           |\n| `sessionManager.getSessionName()`        | `sessionManager.getSessionName()`        | समान (हमने अपस्ट्रीम के RPC से मिलान के लिए एकीकृत किया) |\n| `agent.sessionName` / `setSessionName()` | `agent.sessionName` / `setSessionName()` | समान                                      |\n\n### फ़ाइल कंसोलिडेशन\n\n| अपस्ट्रीम                                           | हमारा फ़ोर्क                                | कारण                                  |\n| -------------------------------------------------- | --------------------------------------- | --------------------------------------- |\n| `clipboard.ts` + `clipboard-image.ts` (टूल फ़ाइलें) | `@f5-sales-demo/pi-natives` क्लिपबोर्ड मॉड्यूल | N-API नेटिव इम्प्लीमेंटेशन में मर्ज किया गया |\n\n### टेस्ट फ़्रेमवर्क\n\n| अपस्ट्रीम                  | हमारा फ़ोर्क                      |\n| ------------------------- | ----------------------------- |\n| `vitest` with `vi.mock()` | `bun:test` with `vi` from bun |\n| `node:test` assertions    | `expect()` matchers           |\n\n### टूल आर्किटेक्चर\n\n| अपस्ट्रीम                            | हमारा फ़ोर्क                                                          | नोट्स                                                     |\n| ----------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------- |\n| `createTool(cwd: string, options?)` | `createTools(session: ToolSession)` via `BUILTIN_TOOLS` रजिस्ट्री  | टूल फ़ैक्ट्रीज़ `ToolSession` स्वीकार करती हैं और `null` लौटा सकती हैं |\n| प्रति-टूल `*Operations` इंटरफ़ेसेज़   | प्रति-टूल इंटरफ़ेसेज़ बने रहते हैं (`FindOperations`, `GrepOperations`)   | SSH/रिमोट ओवरराइड्स के लिए उपयोग किया जाता है                             |\n| हर जगह Node.js `fs/promises`    | फ़ाइलों के लिए `Bun.file()`/`Bun.write()`; dirs के लिए `node:fs/promises` | जब Bun APIs सरल बनाएँ तो उन्हें प्राथमिकता दें                        |\n\n### Auth स्टोरेज\n\n| अपस्ट्रीम                        | हमारा फ़ोर्क                                    | नोट्स                                        |\n| ------------------------------- | ------------------------------------------- | -------------------------------------------- |\n| `proper-lockfile` + `auth.json` | `agent.db` (bun:sqlite)                     | क्रेडेंशियल्स विशेष रूप से `agent.db` में संग्रहीत |\n| प्रति प्रोवाइडर एकल क्रेडेंशियल  | राउंड-रॉबिन चयन के साथ मल्टी-क्रेडेंशियल | सेशन एफ़िनिटी और बैकऑफ़ लॉजिक संरक्षित |\n\n### एक्सटेंशन्स\n\n| अपस्ट्रीम                      | हमारा फ़ोर्क                                   |\n| ----------------------------- | ------------------------------------------ |\n| TypeScript लोडिंग के लिए `jiti` | नेटिव Bun `import()`                      |\n| `pkg.pi` मैनिफ़ेस्ट फ़ील्ड       | `pkg.xcsh ?? pkg.pi` (हमारे नेमस्पेस को प्राथमिकता) |\n\n### इन अपस्ट्रीम सुविधाओं को छोड़ें\n\nपोर्ट करते समय, इन फ़ाइलों/सुविधाओं को पूरी तरह **छोड़ें**:\n\n- `footer-data-provider.ts` — हम StatusLineComponent का उपयोग करते हैं\n- `clipboard-image.ts` — क्लिपबोर्ड `@f5-sales-demo/pi-natives` N-API मॉड्यूल में है\n- GitHub वर्कफ़्लो फ़ाइलें — हमारी अपनी CI है\n- `models.generated.ts` — ऑटो-जेनरेटेड, स्थानीय रूप से रीजेनरेट करें (models.json के रूप में)\n\n### हमने जोड़ी गई सुविधाएँ (इन्हें संरक्षित रखें)\n\nये हमारे फ़ोर्क में मौजूद हैं लेकिन अपस्ट्रीम में नहीं। **कभी ओवरराइट न करें:**\n\n- इंटरैक्टिव मोड में `StatusLineComponent`\n- सेशन एफ़िनिटी के साथ मल्टी-क्रेडेंशियल auth\n- कैपेबिलिटी-बेस्ड डिस्कवरी सिस्टम (`defineCapability`, `registerProvider`, `loadCapability`, `skillCapability`, आदि)\n- MCP/Exa/SSH इंटीग्रेशन्स\n- फ़ॉर्मेट-ऑन-सेव के लिए LSP राइट-थ्रू\n- Bash इंटरसेप्शन (`checkBashInterception`)\n- रीड टूल में फ़ज़ी पाथ सजेशन्स\n",
	"hi/configuration/rpc.md": "---\ntitle: RPC प्रोटोकॉल संदर्भ\ndescription: xcsh घटकों के बीच अंतर-प्रक्रिया संचार के लिए JSON-RPC प्रोटोकॉल संदर्भ।\nsidebar:\n  order: 5\n  label: RPC प्रोटोकॉल\ni18n:\n  sourceHash: b4a3ddaf08ab\n  translator: machine\n---\n\n# RPC प्रोटोकॉल संदर्भ\n\nRPC मोड कोडिंग एजेंट को stdio पर न्यूलाइन-सीमांकित JSON प्रोटोकॉल के रूप में चलाता है।\n\n- **stdin**: कमांड (`RpcCommand`) और एक्सटेंशन UI प्रतिक्रियाएँ\n- **stdout**: कमांड प्रतिक्रियाएँ (`RpcResponse`), सत्र/एजेंट इवेंट, एक्सटेंशन UI अनुरोध\n\nप्राथमिक कार्यान्वयन:\n\n- `src/modes/rpc/rpc-mode.ts`\n- `src/modes/rpc/rpc-types.ts`\n- `src/session/agent-session.ts`\n- `packages/agent/src/agent.ts`\n- `packages/agent/src/agent-loop.ts`\n\n## स्टार्टअप\n\n```bash\nxcsh --mode rpc [regular CLI options]\n```\n\nव्यवहार संबंधी नोट्स:\n\n- RPC मोड में `@file` CLI आर्गुमेंट अस्वीकार किए जाते हैं।\n- RPC मोड अतिरिक्त मॉडल कॉल से बचने के लिए डिफ़ॉल्ट रूप से स्वचालित सत्र शीर्षक जनरेशन को अक्षम करता है।\n- RPC मोड वर्कफ़्लो-परिवर्तनकारी `todo.*`, `task.*`, और `async.*` सेटिंग्स को उपयोगकर्ता ओवरराइड इनहेरिट करने के बजाय उनके बिल्ट-इन डिफ़ॉल्ट पर रीसेट करता है।\n- प्रक्रिया stdin को JSONL (`readJsonl(Bun.stdin.stream())`) के रूप में पढ़ती है।\n- जब stdin बंद होता है, तो प्रक्रिया कोड `0` के साथ बाहर निकलती है।\n- प्रतिक्रियाएँ/इवेंट प्रति पंक्ति एक JSON ऑब्जेक्ट के रूप में लिखे जाते हैं।\n\n## ट्रांसपोर्ट और फ़्रेमिंग\n\nप्रत्येक फ़्रेम एक एकल JSON ऑब्जेक्ट होता है जिसके बाद `\\n` आता है।\n\nऑब्जेक्ट आकार के अतिरिक्त कोई एनवेलप नहीं है।\n\n### आउटबाउंड फ़्रेम श्रेणियाँ (stdout)\n\n1. `RpcResponse` (`{ type: \"response\", ... }`)\n2. `AgentSessionEvent` ऑब्जेक्ट (`agent_start`, `message_update`, आदि)\n3. `RpcExtensionUIRequest` (`{ type: \"extension_ui_request\", ... }`)\n4. एक्सटेंशन त्रुटियाँ (`{ type: \"extension_error\", extensionPath, event, error }`)\n\n### इनबाउंड फ़्रेम श्रेणियाँ (stdin)\n\n1. `RpcCommand`\n2. `RpcExtensionUIResponse` (`{ type: \"extension_ui_response\", ... }`)\n\n## अनुरोध/प्रतिक्रिया सहसंबंध\n\nसभी कमांड वैकल्पिक `id?: string` स्वीकार करते हैं।\n\n- यदि प्रदान किया गया है, तो सामान्य कमांड प्रतिक्रियाएँ उसी `id` को इको करती हैं।\n- `RpcClient` लंबित-अनुरोध समाधान के लिए इस पर निर्भर करता है।\n\nरनटाइम से महत्वपूर्ण एज व्यवहार:\n\n- अज्ञात कमांड प्रतिक्रियाएँ `id: undefined` के साथ उत्सर्जित की जाती हैं (भले ही अनुरोध में `id` हो)।\n- इनपुट लूप में पार्स/हैंडलर अपवाद `command: \"parse\"` के साथ `id: undefined` उत्सर्जित करते हैं।\n- `prompt` और `abort_and_prompt` तत्काल सफलता लौटाते हैं, फिर यदि एसिंक्रोनस प्रॉम्प्ट शेड्यूलिंग विफल होती है तो **उसी** id के साथ बाद में एक त्रुटि प्रतिक्रिया उत्सर्जित कर सकते हैं।\n\n## कमांड स्कीमा (कैनोनिकल)\n\n`RpcCommand` `src/modes/rpc/rpc-types.ts` में परिभाषित है:\n\n### प्रॉम्प्टिंग\n\n- `{ id?, type: \"prompt\", message: string, images?: ImageContent[], streamingBehavior?: \"steer\" | \"followUp\" }`\n- `{ id?, type: \"steer\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"follow_up\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"abort\" }`\n- `{ id?, type: \"abort_and_prompt\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"new_session\", parentSession?: string }`\n\n### स्थिति\n\n- `{ id?, type: \"get_state\" }`\n- `{ id?, type: \"set_todos\", phases: TodoPhase[] }`\n- `{ id?, type: \"set_host_tools\", tools: RpcHostToolDefinition[] }`\n\n### मॉडल\n\n- `{ id?, type: \"set_model\", provider: string, modelId: string }`\n- `{ id?, type: \"cycle_model\" }`\n- `{ id?, type: \"get_available_models\" }`\n\n### थिंकिंग\n\n- `{ id?, type: \"set_thinking_level\", level: ThinkingLevel }`\n- `{ id?, type: \"cycle_thinking_level\" }`\n\n### क्यू मोड\n\n- `{ id?, type: \"set_steering_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_follow_up_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_interrupt_mode\", mode: \"immediate\" | \"wait\" }`\n\n### कॉम्पैक्शन\n\n- `{ id?, type: \"compact\", customInstructions?: string }`\n- `{ id?, type: \"set_auto_compaction\", enabled: boolean }`\n\n### रिट्राई\n\n- `{ id?, type: \"set_auto_retry\", enabled: boolean }`\n- `{ id?, type: \"abort_retry\" }`\n\n### Bash\n\n- `{ id?, type: \"bash\", command: string }`\n- `{ id?, type: \"abort_bash\" }`\n\n### सत्र\n\n- `{ id?, type: \"get_session_stats\" }`\n- `{ id?, type: \"export_html\", outputPath?: string }`\n- `{ id?, type: \"switch_session\", sessionPath: string }`\n- `{ id?, type: \"branch\", entryId: string }`\n- `{ id?, type: \"get_branch_messages\" }`\n- `{ id?, type: \"get_last_assistant_text\" }`\n- `{ id?, type: \"set_session_name\", name: string }`\n\n### संदेश\n\n- `{ id?, type: \"get_messages\" }`\n\n## प्रतिक्रिया स्कीमा\n\nसभी कमांड परिणाम `RpcResponse` का उपयोग करते हैं:\n\n- सफलता: `{ id?, type: \"response\", command: <command>, success: true, data?: ... }`\n- विफलता: `{ id?, type: \"response\", command: string, success: false, error: string }`\n\nडेटा पेलोड कमांड-विशिष्ट हैं और `rpc-types.ts` में परिभाषित हैं।\n\n### `get_state` पेलोड\n\n```json\n{\n  \"model\": { \"provider\": \"...\", \"id\": \"...\" },\n  \"thinkingLevel\": \"off|minimal|low|medium|high|xhigh\",\n  \"isStreaming\": false,\n  \"isCompacting\": false,\n  \"steeringMode\": \"all|one-at-a-time\",\n  \"followUpMode\": \"all|one-at-a-time\",\n  \"interruptMode\": \"immediate|wait\",\n  \"sessionFile\": \"...\",\n  \"sessionId\": \"...\",\n  \"sessionName\": \"...\",\n  \"autoCompactionEnabled\": true,\n  \"messageCount\": 0,\n  \"queuedMessageCount\": 0,\n  \"todoPhases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Todos\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the tool surface\",\n          \"status\": \"in_progress\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n### `set_todos` पेलोड\n\nवर्तमान सत्र के लिए इन-मेमोरी todo स्थिति को प्रतिस्थापित करता है और सामान्यीकृत चरण सूची लौटाता है:\n\n```json\n{\n  \"id\": \"req_2\",\n  \"type\": \"set_todos\",\n  \"phases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Evaluation\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the read tool surface\",\n          \"status\": \"in_progress\"\n        },\n        {\n          \"id\": \"task-2\",\n          \"content\": \"Exercise edit operations\",\n          \"status\": \"pending\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nयह उन होस्ट के लिए उपयोगी है जो पहले प्रॉम्प्ट से पहले एक योजना को प्री-सीड करना चाहते हैं।\n\n### `set_host_tools` पेलोड\n\nहोस्ट-स्वामित्व वाले टूल्स के वर्तमान सेट को प्रतिस्थापित करता है जिन्हें RPC सर्वर\nstdio पर वापस कॉल कर सकता है:\n\n```json\n{\n  \"id\": \"req_3\",\n  \"type\": \"set_host_tools\",\n  \"tools\": [\n    {\n      \"name\": \"echo_host\",\n      \"label\": \"Echo Host\",\n      \"description\": \"Echo a value from the embedding host\",\n      \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"message\": { \"type\": \"string\" }\n        },\n        \"required\": [\"message\"],\n        \"additionalProperties\": false\n      }\n    }\n  ]\n}\n```\n\nप्रतिक्रिया पेलोड है:\n\n```json\n{\n  \"toolNames\": [\"echo_host\"]\n}\n```\n\nये टूल्स अगले मॉडल कॉल से पहले सक्रिय सत्र टूल रजिस्ट्री में जोड़े जाते हैं। `set_host_tools` को पुनः भेजने से पिछला होस्ट-स्वामित्व वाला सेट प्रतिस्थापित हो जाता है।\n\n## इवेंट स्ट्रीम स्कीमा\n\nRPC मोड `AgentSession.subscribe(...)` से `AgentSessionEvent` ऑब्जेक्ट को फ़ॉरवर्ड करता है।\n\nसामान्य इवेंट प्रकार:\n\n- `agent_start`, `agent_end`\n- `turn_start`, `turn_end`\n- `message_start`, `message_update`, `message_end`\n- `tool_execution_start`, `tool_execution_update`, `tool_execution_end`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n- `todo_auto_clear`\n\nएक्सटेंशन रनर त्रुटियाँ अलग से इस प्रकार उत्सर्जित की जाती हैं:\n\n```json\n{ \"type\": \"extension_error\", \"extensionPath\": \"...\", \"event\": \"...\", \"error\": \"...\" }\n```\n\n`message_update` में `assistantMessageEvent` (text/thinking/toolcall deltas) में स्ट्रीमिंग डेल्टा शामिल होते हैं।\n\n## प्रॉम्प्ट/क्यू समवर्तिता और क्रम\n\nयह सबसे महत्वपूर्ण परिचालन व्यवहार है।\n\n### तत्काल पुष्टि बनाम समापन\n\n`prompt` और `abort_and_prompt` **तत्काल पुष्टि** किए जाते हैं:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n```\n\nइसका अर्थ है:\n\n- कमांड स्वीकृति != रन समापन\n- अंतिम समापन `agent_end` के माध्यम से देखा जाता है\n\n### स्ट्रीमिंग के दौरान\n\n`AgentSession.prompt()` सक्रिय स्ट्रीमिंग के दौरान `streamingBehavior` की आवश्यकता रखता है:\n\n- `\"steer\"` => कतारबद्ध स्टीयरिंग संदेश (इंटरप्ट पथ)\n- `\"followUp\"` => कतारबद्ध फ़ॉलो-अप संदेश (पोस्ट-टर्न पथ)\n\nयदि स्ट्रीमिंग के दौरान छोड़ दिया जाता है, तो प्रॉम्प्ट विफल हो जाता है।\n\n### क्यू डिफ़ॉल्ट\n\nकोडिंग-एजेंट सेटिंग्स स्कीमा (`packages/coding-agent/src/config/settings-schema.ts`) से:\n\n- `steeringMode`: `\"one-at-a-time\"`\n- `followUpMode`: `\"one-at-a-time\"`\n- `interruptMode`: `\"wait\"`\n\n### मोड शब्दार्थ\n\n- `set_steering_mode` / `set_follow_up_mode`\n  - `\"one-at-a-time\"`: प्रति टर्न एक कतारबद्ध संदेश डीक्यू करें\n  - `\"all\"`: पूरी कतार एक साथ डीक्यू करें\n- `set_interrupt_mode`\n  - `\"immediate\"`: टूल निष्पादन टूल कॉल के बीच स्टीयरिंग की जाँच करता है; लंबित स्टीयरिंग टर्न में शेष टूल कॉल को निरस्त कर सकता है\n  - `\"wait\"`: टर्न समापन तक स्टीयरिंग को स्थगित करें\n\n## एक्सटेंशन UI उप-प्रोटोकॉल\n\nRPC मोड में एक्सटेंशन अनुरोध/प्रतिक्रिया UI फ़्रेम का उपयोग करते हैं।\n\n### आउटबाउंड अनुरोध\n\n`RpcExtensionUIRequest` (`type: \"extension_ui_request\"`) मेथड:\n\n- `select`, `confirm`, `input`, `editor`\n- `notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text`\n\nरनटाइम नोट:\n\n- RPC मोड में स्वचालित सत्र शीर्षक जनरेशन अक्षम है, और `setTitle` UI\n  अनुरोध भी डिफ़ॉल्ट रूप से दबाए जाते हैं क्योंकि अधिकांश होस्ट के पास एक\n  सार्थक टर्मिनल-शीर्षक सतह नहीं होती। केवल UI इवेंट में वापस ऑप्ट-इन\n  करने के लिए `PI_RPC_EMIT_TITLE=1` सेट करें।\n\nउदाहरण:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"123\", \"method\": \"confirm\", \"title\": \"Confirm\", \"message\": \"Continue?\", \"timeout\": 30000 }\n```\n\n### इनबाउंड प्रतिक्रिया\n\n`RpcExtensionUIResponse` (`type: \"extension_ui_response\"`):\n\n- `{ type: \"extension_ui_response\", id: string, value: string }`\n- `{ type: \"extension_ui_response\", id: string, confirmed: boolean }`\n- `{ type: \"extension_ui_response\", id: string, cancelled: true }`\n\nयदि किसी डायलॉग में टाइमआउट है, तो जब टाइमआउट/एबॉर्ट सक्रिय होता है तब RPC मोड एक डिफ़ॉल्ट मान पर हल करता है।\n\n## होस्ट टूल उप-प्रोटोकॉल\n\nRPC होस्ट `set_host_tools` भेजकर एजेंट को कस्टम टूल्स एक्सपोज़ कर सकते हैं, फिर\nउसी ट्रांसपोर्ट पर निष्पादन अनुरोधों को सेवा प्रदान कर सकते हैं।\n\n### आउटबाउंड अनुरोध\n\nजब एजेंट चाहता है कि होस्ट उन टूल्स में से एक को निष्पादित करे, तो RPC मोड उत्सर्जित करता है:\n\n```json\n{\n  \"type\": \"host_tool_call\",\n  \"id\": \"host_1\",\n  \"toolCallId\": \"toolu_123\",\n  \"toolName\": \"echo_host\",\n  \"arguments\": { \"message\": \"hello\" }\n}\n```\n\nयदि टूल निष्पादन बाद में निरस्त कर दिया जाता है, तो RPC मोड उत्सर्जित करता है:\n\n```json\n{\n  \"type\": \"host_tool_cancel\",\n  \"id\": \"host_cancel_1\",\n  \"targetId\": \"host_1\"\n}\n```\n\n### इनबाउंड अपडेट और समापन\n\nहोस्ट वैकल्पिक रूप से प्रगति स्ट्रीम कर सकते हैं:\n\n```json\n{\n  \"type\": \"host_tool_update\",\n  \"id\": \"host_1\",\n  \"partialResult\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"working\" }]\n  }\n}\n```\n\nसमापन इसका उपयोग करता है:\n\n```json\n{\n  \"type\": \"host_tool_result\",\n  \"id\": \"host_1\",\n  \"result\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"done\" }]\n  }\n}\n```\n\nलौटाई गई सामग्री को टूल त्रुटि के रूप में प्रदर्शित करने के लिए `host_tool_result` पर `isError: true` सेट करें।\n\n## त्रुटि मॉडल और पुनर्प्राप्ति योग्यता\n\n### कमांड-स्तरीय विफलताएँ\n\nविफलताएँ स्ट्रिंग `error` के साथ `success: false` होती हैं।\n\n```json\n{ \"id\": \"req_2\", \"type\": \"response\", \"command\": \"set_model\", \"success\": false, \"error\": \"Model not found: provider/model\" }\n```\n\n### पुनर्प्राप्ति योग्यता अपेक्षाएँ\n\n- अधिकांश कमांड विफलताएँ पुनर्प्राप्ति योग्य हैं; प्रक्रिया जीवित रहती है।\n- विकृत JSONL / पार्स-लूप अपवाद एक `parse` त्रुटि प्रतिक्रिया उत्सर्जित करते हैं और बाद की पंक्तियाँ पढ़ना जारी रखते हैं।\n- खाली `set_session_name` अस्वीकार किया जाता है (`Session name cannot be empty`)।\n- अज्ञात `id` वाली एक्सटेंशन UI प्रतिक्रियाएँ अनदेखी की जाती हैं।\n- प्रक्रिया समाप्ति की शर्तें stdin बंद होना या स्पष्ट एक्सटेंशन-ट्रिगर्ड शटडाउन हैं।\n\n## संक्षिप्त कमांड प्रवाह\n\n### 1) प्रॉम्प्ट और स्ट्रीम\n\nstdin:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"prompt\", \"message\": \"Summarize this repo\" }\n```\n\nstdout अनुक्रम (विशिष्ट):\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n{ \"type\": \"agent_start\" }\n{ \"type\": \"message_update\", \"assistantMessageEvent\": { \"type\": \"text_delta\", \"delta\": \"...\" }, \"message\": { \"role\": \"assistant\", \"content\": [] } }\n{ \"type\": \"agent_end\", \"messages\": [] }\n```\n\n### 2) स्पष्ट क्यू नीति के साथ स्ट्रीमिंग के दौरान प्रॉम्प्ट\n\nstdin:\n\n```json\n{ \"id\": \"req_2\", \"type\": \"prompt\", \"message\": \"Also include risks\", \"streamingBehavior\": \"followUp\" }\n```\n\n### 3) क्यू व्यवहार का निरीक्षण और ट्यूनिंग\n\nstdin:\n\n```json\n{ \"id\": \"q1\", \"type\": \"get_state\" }\n{ \"id\": \"q2\", \"type\": \"set_steering_mode\", \"mode\": \"all\" }\n{ \"id\": \"q3\", \"type\": \"set_interrupt_mode\", \"mode\": \"wait\" }\n```\n\n### 4) एक्सटेंशन UI राउंड ट्रिप\n\nstdout:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"ui_7\", \"method\": \"input\", \"title\": \"Branch name\", \"placeholder\": \"feature/...\" }\n```\n\nstdin:\n\n```json\n{ \"type\": \"extension_ui_response\", \"id\": \"ui_7\", \"value\": \"feature/rpc-host\" }\n```\n\n## `RpcClient` हेल्पर पर नोट्स\n\n`src/modes/rpc/rpc-client.ts` एक सुविधा रैपर है, प्रोटोकॉल परिभाषा नहीं।\n\nवर्तमान हेल्पर विशेषताएँ:\n\n- `bun <cliPath> --mode rpc` स्पॉन करता है\n- जनरेट किए गए `req_<n>` ids द्वारा प्रतिक्रियाओं को सहसंबद्ध करता है\n- केवल मान्यता प्राप्त `AgentEvent` प्रकारों को श्रोताओं को डिस्पैच करता है\n- `setCustomTools()` और `host_tool_call` / `host_tool_cancel` के स्वचालित प्रबंधन के माध्यम से होस्ट-स्वामित्व वाले कस्टम टूल्स का समर्थन करता है\n- प्रत्येक प्रोटोकॉल कमांड के लिए हेल्पर मेथड एक्सपोज़ **नहीं** करता (उदाहरण के लिए, `set_interrupt_mode` और `set_session_name` प्रोटोकॉल प्रकारों में हैं लेकिन समर्पित मेथड के रूप में रैप नहीं किए गए हैं)\n\nयदि आपको पूर्ण सतह कवरेज की आवश्यकता है तो कच्चे प्रोटोकॉल फ़्रेम का उपयोग करें।\n",
	"hi/configuration/sdk.md": "---\ntitle: SDK\ndescription: कस्टम एजेंट और इंटीग्रेशन बनाने के लिए xcsh कोडिंग एजेंट रनटाइम के ऊपर SDK।\nsidebar:\n  order: 6\n  label: SDK\ni18n:\n  sourceHash: 80f3a4374241\n  translator: machine\n---\n\n# SDK\n\nSDK, `@f5-sales-demo/xcsh` के लिए इन-प्रोसेस इंटीग्रेशन सरफेस है।\nइसका उपयोग तब करें जब आप अपने Bun/Node प्रोसेस से एजेंट स्टेट, इवेंट स्ट्रीमिंग, टूल वायरिंग और सेशन कंट्रोल तक सीधी पहुँच चाहते हों।\n\nयदि आपको क्रॉस-लैंग्वेज/प्रोसेस आइसोलेशन की आवश्यकता है, तो इसके बजाय RPC मोड का उपयोग करें।\n\n## इंस्टॉलेशन\n\n```bash\nbun add @f5-sales-demo/xcsh\n```\n\n## एंट्री पॉइंट\n\n`@f5-sales-demo/xcsh` पैकेज रूट से SDK APIs एक्सपोर्ट करता है (और `@f5-sales-demo/xcsh/sdk` के माध्यम से भी)।\n\nएम्बेडर्स के लिए कोर एक्सपोर्ट:\n\n- `createAgentSession`\n- `SessionManager`\n- `Settings`\n- `AuthStorage`\n- `ModelRegistry`\n- `discoverAuthStorage`\n- डिस्कवरी हेल्पर्स (`discoverExtensions`, `discoverSkills`, `discoverContextFiles`, `discoverPromptTemplates`, `discoverSlashCommands`, `discoverCustomTSCommands`, `discoverMCPServers`)\n- टूल फैक्ट्री सरफेस (`createTools`, `BUILTIN_TOOLS`, टूल क्लासेस)\n\n## क्विक स्टार्ट (ऑटो-डिस्कवरी डिफ़ॉल्ट)\n\n```ts\nimport { createAgentSession } from \"@f5-sales-demo/xcsh\";\n\nconst { session, modelFallbackMessage } = await createAgentSession();\n\nif (modelFallbackMessage) {\n process.stderr.write(`${modelFallbackMessage}\\n`);\n}\n\nconst unsubscribe = session.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Summarize this repository in 3 bullets.\");\nunsubscribe();\nawait session.dispose();\n```\n\n## `createAgentSession()` डिफ़ॉल्ट रूप से क्या डिस्कवर करता है\n\n`createAgentSession()` \"प्रदान करें तो ओवरराइड करें, छोड़ें तो डिस्कवर करें\" का पालन करता है।\n\nयदि छोड़ा जाए, तो यह निम्नलिखित रिज़ॉल्व करता है:\n\n- `cwd`: `getProjectDir()`\n- `agentDir`: `~/.xcsh/agent` (`getAgentDir()` के माध्यम से)\n- `authStorage`: `discoverAuthStorage(agentDir)`\n- `modelRegistry`: `new ModelRegistry(authStorage)` + `await refresh()`\n- `settings`: `await Settings.init({ cwd, agentDir })`\n- `sessionManager`: `SessionManager.create(cwd)` (फ़ाइल-बैक्ड)\n- स्किल्स/कॉन्टेक्स्ट फ़ाइलें/प्रॉम्प्ट टेम्पलेट/स्लैश कमांड/एक्सटेंशन/कस्टम TS कमांड\n- `createTools(...)` के माध्यम से बिल्ट-इन टूल्स\n- MCP टूल्स (डिफ़ॉल्ट रूप से सक्षम)\n- LSP इंटीग्रेशन (डिफ़ॉल्ट रूप से सक्षम)\n\n### आवश्यक बनाम वैकल्पिक इनपुट\n\nआमतौर पर आपको केवल वही प्रदान करना होता है जिसे आप नियंत्रित करना चाहते हैं:\n\n- **प्रदान करना आवश्यक**: न्यूनतम सेशन के लिए कुछ भी नहीं\n- **एम्बेडर्स में आमतौर पर स्पष्ट रूप से प्रदान करें**:\n    - `sessionManager` (यदि आपको इन-मेमोरी या कस्टम लोकेशन चाहिए)\n    - `authStorage` + `modelRegistry` (यदि आप क्रेडेंशियल/मॉडल लाइफसाइकल के स्वामी हैं)\n    - `model` या `modelPattern` (यदि निर्धारित मॉडल चयन आवश्यक है)\n    - `settings` (यदि आपको आइसोलेटेड/टेस्ट कॉन्फ़िग चाहिए)\n\n## सेशन मैनेजर व्यवहार (परसिस्टेंट बनाम इन-मेमोरी)\n\n`AgentSession` हमेशा `SessionManager` का उपयोग करता है; व्यवहार इस बात पर निर्भर करता है कि आप कौन सी फैक्ट्री उपयोग करते हैं।\n\n### फ़ाइल-बैक्ड (डिफ़ॉल्ट)\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.create(process.cwd()),\n});\n\nconsole.log(session.sessionFile); // absolute .jsonl path\n```\n\n- कन्वर्सेशन/मैसेज/स्टेट डेल्टा को सेशन फ़ाइलों में परसिस्ट करता है।\n- resume/open/list/fork वर्कफ़्लो को सपोर्ट करता है।\n- `session.sessionFile` परिभाषित है।\n\n### इन-मेमोरी\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.inMemory(),\n});\n\nconsole.log(session.sessionFile); // undefined\n```\n\n- कोई फाइलसिस्टम परसिस्टेंस नहीं।\n- टेस्ट, एफेमेरल वर्कर्स, रिक्वेस्ट-स्कोप्ड एजेंट्स के लिए उपयोगी।\n- सेशन मेथड्स अभी भी काम करते हैं, लेकिन परसिस्टेंस-विशिष्ट व्यवहार (फ़ाइल resume/fork पाथ) स्वाभाविक रूप से सीमित हैं।\n\n### Resume/open/list हेल्पर्स\n\n```ts\nimport { SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst recent = await SessionManager.continueRecent(process.cwd());\nconst listed = await SessionManager.list(process.cwd());\nconst opened = listed[0] ? await SessionManager.open(listed[0].path) : null;\n```\n\n## मॉडल और ऑथ वायरिंग\n\n`createAgentSession()` मॉडल चयन और API की रिज़ॉल्यूशन के लिए `ModelRegistry` + `AuthStorage` का उपयोग करता है।\n\n### स्पष्ट वायरिंग\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst available = modelRegistry.getAvailable();\nif (available.length === 0) throw new Error(\"No authenticated models available\");\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n model: available[0],\n thinkingLevel: \"medium\",\n sessionManager: SessionManager.inMemory(),\n});\n```\n\n### `model` छोड़े जाने पर चयन क्रम\n\nजब कोई स्पष्ट `model`/`modelPattern` प्रदान नहीं किया जाता:\n\n1. मौजूदा सेशन से मॉडल रिस्टोर करें (यदि रिस्टोरेबल + की उपलब्ध है)\n2. सेटिंग्स डिफ़ॉल्ट मॉडल रोल (`default`)\n3. वैलिड ऑथ के साथ पहला उपलब्ध मॉडल\n\nयदि रिस्टोर विफल होता है, तो `modelFallbackMessage` फॉलबैक की व्याख्या करता है।\n\n### ऑथ प्राथमिकता\n\n`AuthStorage.getApiKey(...)` इस क्रम में रिज़ॉल्व करता है:\n\n1. रनटाइम ओवरराइड (`setRuntimeApiKey`)\n2. `agent.db` में स्टोर्ड क्रेडेंशियल\n3. प्रोवाइडर एनवायरनमेंट वेरिएबल्स\n4. कस्टम-प्रोवाइडर रिज़ॉल्वर फॉलबैक (यदि कॉन्फ़िगर किया गया हो)\n\n## इवेंट सब्सक्रिप्शन मॉडल\n\n`session.subscribe(listener)` से सब्सक्राइब करें; यह एक अनसब्सक्राइब फ़ंक्शन रिटर्न करता है।\n\n```ts\nconst unsubscribe = session.subscribe(event => {\n switch (event.type) {\n  case \"agent_start\":\n  case \"turn_start\":\n  case \"tool_execution_start\":\n   break;\n  case \"message_update\":\n   if (event.assistantMessageEvent.type === \"text_delta\") {\n    process.stdout.write(event.assistantMessageEvent.delta);\n   }\n   break;\n }\n});\n```\n\n`AgentSessionEvent` में कोर `AgentEvent` के साथ-साथ सेशन-लेवल इवेंट शामिल हैं:\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n## प्रॉम्प्ट लाइफसाइकल\n\n`session.prompt(text, options?)` प्राथमिक एंट्री पॉइंट है।\n\nव्यवहार:\n\n1. वैकल्पिक कमांड/टेम्पलेट एक्सपेंशन (`/` कमांड, कस्टम कमांड, फ़ाइल स्लैश कमांड, प्रॉम्प्ट टेम्पलेट)\n2. यदि वर्तमान में स्ट्रीमिंग हो:\n    - `streamingBehavior: \"steer\" | \"followUp\"` की आवश्यकता है\n    - काम छोड़ने के बजाय क्यू करता है\n3. यदि आइडल हो:\n    - मॉडल + API की वैलिडेट करता है\n    - यूज़र मैसेज जोड़ता है\n    - एजेंट टर्न शुरू करता है\n\nसंबंधित APIs:\n\n- `sendUserMessage(content, { deliverAs? })`\n- `steer(text, images?)`\n- `followUp(text, images?)`\n- `sendCustomMessage({ customType, content, ... }, { deliverAs?, triggerTurn? })`\n- `abort()`\n\n## उपकरण और एक्सटेंशन इंटीग्रेशन\n\n### बिल्ट-इन और फ़िल्टरिंग\n\n- बिल्ट-इन `createTools(...)` और `BUILTIN_TOOLS` से आते हैं।\n- `toolNames` बिल्ट-इन के लिए allowlist का काम करता है।\n- `customTools` और एक्सटेंशन-रजिस्टर्ड उपकरण अभी भी शामिल हैं।\n- हिडन टूल्स (उदाहरण के लिए `submit_result`) ऑप्ट-इन हैं जब तक कि विकल्पों द्वारा आवश्यक न हो।\n\n```ts\nconst { session } = await createAgentSession({\n toolNames: [\"read\", \"grep\", \"find\", \"write\"],\n requireSubmitResultTool: true,\n});\n```\n\n### एक्सटेंशन\n\n- `extensions`: इनलाइन `ExtensionFactory[]`\n- `additionalExtensionPaths`: अतिरिक्त एक्सटेंशन फ़ाइलें लोड करें\n- `disableExtensionDiscovery`: स्वचालित एक्सटेंशन स्कैनिंग अक्षम करें\n- `preloadedExtensions`: पहले से लोड किए गए एक्सटेंशन सेट का पुनः उपयोग करें\n\n### रनटाइम टूल सेट परिवर्तन\n\n`AgentSession` रनटाइम एक्टिवेशन अपडेट सपोर्ट करता है:\n\n- `getActiveToolNames()`\n- `getAllToolNames()`\n- `setActiveToolsByName(names)`\n- `refreshMCPTools(mcpTools)`\n\nसक्रिय टूल परिवर्तनों को दर्शाने के लिए सिस्टम प्रॉम्प्ट पुनर्निर्मित किया जाता है।\n\n## डिस्कवरी हेल्पर्स\n\nइनका उपयोग तब करें जब आप आंतरिक डिस्कवरी लॉजिक को फिर से बनाए बिना आंशिक नियंत्रण चाहते हों:\n\n- `discoverAuthStorage(agentDir?)`\n- `discoverExtensions(cwd?)`\n- `discoverSkills(cwd?, _agentDir?, settings?)`\n- `discoverContextFiles(cwd?, _agentDir?)`\n- `discoverPromptTemplates(cwd?, agentDir?)`\n- `discoverSlashCommands(cwd?)`\n- `discoverCustomTSCommands(cwd?, agentDir?)`\n- `discoverMCPServers(cwd?)`\n- `buildSystemPrompt(options?)`\n\n## सबएजेंट-ओरिएंटेड विकल्प\n\nSDK उपभोक्ताओं के लिए जो ऑर्केस्ट्रेटर बना रहे हैं (टास्क एग्जीक्यूटर फ्लो के समान):\n\n- `outputSchema`: संरचित आउटपुट अपेक्षा को टूल कॉन्टेक्स्ट में पास करता है\n- `requireSubmitResultTool`: `submit_result` टूल इंक्लूज़न को अनिवार्य करता है\n- `taskDepth`: नेस्टेड टास्क सेशन के लिए रिकर्सन-डेप्थ कॉन्टेक्स्ट\n- `parentTaskPrefix`: नेस्टेड टास्क आउटपुट के लिए आर्टिफैक्ट नेमिंग प्रीफिक्स\n\nये सामान्य सिंगल-एजेंट एम्बेडिंग के लिए वैकल्पिक हैं।\n\n## `createAgentSession()` रिटर्न वैल्यू\n\n```ts\ntype CreateAgentSessionResult = {\n session: AgentSession;\n extensionsResult: LoadExtensionsResult;\n setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;\n mcpManager?: MCPManager;\n modelFallbackMessage?: string;\n lspServers?: Array<{ name: string; status: \"ready\" | \"error\"; fileTypes: string[]; error?: string }>;\n};\n```\n\n`setToolUIContext(...)` का उपयोग केवल तभी करें जब आपका एम्बेडर UI क्षमताएं प्रदान करता है जिन्हें टूल्स/एक्सटेंशन कॉल करें।\n\n## न्यूनतम नियंत्रित एम्बेड उदाहरण\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n Settings,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst settings = Settings.isolated({\n \"compaction.enabled\": true,\n \"retry.enabled\": true,\n});\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n settings,\n sessionManager: SessionManager.inMemory(),\n toolNames: [\"read\", \"grep\", \"find\", \"edit\", \"write\"],\n enableMCP: false,\n enableLsp: true,\n});\n\nsession.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Find all TODO comments in this repo and propose fixes.\");\nawait session.dispose();\n```\n",
	"hi/configuration/secrets.md": "---\ntitle: सीक्रेट ऑब्फस्केशन\ndescription: >-\n  सीक्रेट ऑब्फस्केशन पाइपलाइन जो सेशन लॉग और आउटपुट से संवेदनशील मानों को हटा\n  देती है।\nsidebar:\n  order: 3\n  label: सीक्रेट\ni18n:\n  sourceHash: 1d9dc101c614\n  translator: machine\n---\n\n# सीक्रेट ऑब्फस्केशन\n\nसंवेदनशील मानों (API कुंजियाँ, टोकन, पासवर्ड) को LLM प्रदाताओं को भेजे जाने से रोकता है। जब सक्षम होता है, तो प्रक्रिया से बाहर जाने से पहले सीक्रेट को निर्धारक प्लेसहोल्डर से बदल दिया जाता है, और मॉडल द्वारा लौटाए गए टूल कॉल आर्गुमेंट में पुनर्स्थापित कर दिया जाता है।\n\n## सक्षम करना\n\nडिफ़ॉल्ट रूप से सक्षम है। `/settings` UI के माध्यम से या सीधे `config.yml` में टॉगल करें:\n\n```yaml\nsecrets:\n  enabled: false\n```\n\n## यह कैसे काम करता है\n\n1. सेशन स्टार्टअप पर, सीक्रेट दो स्रोतों से एकत्र किए जाते हैं:\n   - **एनवायरनमेंट वेरिएबल** जो सामान्य सीक्रेट पैटर्न (`*_KEY`, `*_SECRET`, `*_TOKEN`, `*_PASSWORD`, आदि) से मेल खाते हों और जिनका मान >= 8 अक्षर हो\n   - **`secrets.yml` फ़ाइलें** (नीचे देखें)\n\n2. LLM को भेजे जाने वाले आउटबाउंड संदेशों में सभी सीक्रेट मानों को `<<$env:S0>>`, `<<$env:S1>>`, आदि जैसे प्लेसहोल्डर से बदल दिया जाता है।\n\n3. मॉडल द्वारा लौटाए गए टूल कॉल आर्गुमेंट को डीप-वॉक किया जाता है और निष्पादन से पहले प्लेसहोल्डर को मूल मानों में पुनर्स्थापित किया जाता है।\n\nदो मोड नियंत्रित करते हैं कि प्रत्येक सीक्रेट के साथ क्या होता है:\n\n| मोड | व्यवहार | उलटनीय |\n|---|---|---|\n| `obfuscate` (डिफ़ॉल्ट) | इंडेक्स्ड प्लेसहोल्डर `<<$env:SN>>` से बदला जाता है | हाँ (टूल आर्गुमेंट में डी-ऑब्फस्केट होता है) |\n| `replace` | निर्धारक समान-लंबाई वाली स्ट्रिंग से बदला जाता है | नहीं (एकतरफा) |\n\n## secrets.yml\n\nYAML में कस्टम सीक्रेट एंट्री परिभाषित करें। दो स्थानों की जाँच की जाती है:\n\n| स्तर | पथ | उद्देश्य |\n|---|---|---|\n| वैश्विक | `~/.xcsh/agent/secrets.yml` | सभी प्रोजेक्ट में सीक्रेट |\n| प्रोजेक्ट | `<cwd>/.xcsh/secrets.yml` | प्रोजेक्ट-विशिष्ट सीक्रेट |\n\nप्रोजेक्ट एंट्री मिलान करने वाले `content` वाली वैश्विक एंट्री को ओवरराइड करती हैं।\n\n### स्कीमा\n\nऐरे में प्रत्येक एंट्री के ये फ़ील्ड होते हैं:\n\n| फ़ील्ड | प्रकार | आवश्यक | विवरण |\n|---|---|---|---|\n| `type` | `\"plain\"` या `\"regex\"` | हाँ | मिलान रणनीति |\n| `content` | स्ट्रिंग | हाँ | सीक्रेट मान (plain) या regex पैटर्न (regex) |\n| `mode` | `\"obfuscate\"` या `\"replace\"` | नहीं | डिफ़ॉल्ट: `\"obfuscate\"` |\n| `replacement` | स्ट्रिंग | नहीं | कस्टम प्रतिस्थापन (केवल replace मोड) |\n| `flags` | स्ट्रिंग | नहीं | Regex फ़्लैग (केवल regex प्रकार) |\n\n### उदाहरण\n\n#### Plain सीक्रेट\n\n```yaml\n# एक विशिष्ट API कुंजी को ऑब्फस्केट करें (डिफ़ॉल्ट मोड)\n- type: plain\n  content: sk-proj-abc123def456\n\n# एक डेटाबेस पासवर्ड को निश्चित स्ट्रिंग से बदलें\n- type: plain\n  content: hunter2\n  mode: replace\n  replacement: \"********\"\n```\n\n#### Regex सीक्रेट\n\n```yaml\n# किसी भी AWS-स्टाइल कुंजी को ऑब्फस्केट करें\n- type: regex\n  content: \"AKIA[0-9A-Z]{16}\"\n\n# स्पष्ट फ़्लैग के साथ केस-असंवेदनशील मिलान\n- type: regex\n  content: \"api[_-]?key\\\\s*=\\\\s*\\\\w+\"\n  flags: \"i\"\n\n# Regex लिटरल सिंटैक्स (एक स्ट्रिंग में पैटर्न और फ़्लैग)\n- type: regex\n  content: \"/bearer\\\\s+[a-zA-Z0-9._~+\\\\/=-]+/i\"\n```\n\nRegex एंट्री हमेशा वैश्विक रूप से स्कैन करती हैं (`g` फ़्लैग स्वचालित रूप से लागू होता है)। Regex लिटरल सिंटैक्स `/pattern/flags` को अलग `content` + `flags` फ़ील्ड के विकल्प के रूप में समर्थित है। पैटर्न के भीतर एस्केप्ड स्लैश (`\\\\/`) को सही ढंग से संभाला जाता है।\n\n#### Replace मोड के साथ Regex\n\n```yaml\n# कनेक्शन स्ट्रिंग को एकतरफा बदलें (उलटनीय नहीं)\n- type: regex\n  content: \"postgres://[^\\\\s]+\"\n  mode: replace\n  replacement: \"postgres://***\"\n```\n\n## एनवायरनमेंट वेरिएबल डिटेक्शन के साथ इंटरेक्शन\n\nएनवायरनमेंट वेरिएबल हमेशा पहले एकत्र किए जाते हैं। फ़ाइल-परिभाषित एंट्री बाद में जोड़ी जाती हैं, इसलिए फ़ाइल एंट्री उन सीक्रेट को कवर कर सकती हैं जो एनवायरनमेंट वेरिएबल में नहीं हैं (कॉन्फ़िग फ़ाइलें, हार्डकोडेड मान, आदि)। यदि एक ही मान दोनों में दिखाई देता है, तो फ़ाइल एंट्री का मोड प्राथमिकता लेता है।\n\n## प्रमुख फ़ाइलें\n\n- `src/secrets/index.ts` -- लोडिंग, मर्जिंग, एनवायरनमेंट वेरिएबल संग्रह\n- `src/secrets/obfuscator.ts` -- `SecretObfuscator` क्लास, प्लेसहोल्डर जनरेशन, मैसेज ऑब्फस्केशन\n- `src/secrets/regex.ts` -- regex लिटरल पार्सिंग और कम्पाइलेशन\n- `src/config/settings-schema.ts` -- `secrets.enabled` सेटिंग परिभाषा\n",
	"hi/extensions/extension-loading.md": "---\ntitle: एक्सटेंशन लोडिंग (TypeScript/JavaScript मॉड्यूल)\ndescription: >-\n  एक्सटेंशन के लिए TypeScript और JavaScript मॉड्यूल लोडिंग पाइपलाइन जिसमें\n  रिज़ॉल्यूशन, वैलिडेशन और कैशिंग शामिल है।\nsidebar:\n  order: 2\n  label: एक्सटेंशन लोडिंग\ni18n:\n  sourceHash: a8cea231c660\n  translator: machine\n---\n\n# एक्सटेंशन लोडिंग (TypeScript/JavaScript मॉड्यूल)\n\nयह दस्तावेज़ इस बात को कवर करता है कि कोडिंग एजेंट स्टार्टअप पर **एक्सटेंशन मॉड्यूल** (`.ts`/`.js`) को कैसे खोजता और लोड करता है।\n\nयह `gemini-extension.json` मैनिफ़ेस्ट एक्सटेंशन को कवर **नहीं** करता (जो अलग से प्रलेखित हैं)।\n\n## यह सबसिस्टम क्या करता है\n\nएक्सटेंशन लोडिंग मॉड्यूल एंट्री फ़ाइलों की एक सूची बनाता है, Bun के साथ प्रत्येक मॉड्यूल को इम्पोर्ट करता है, उसकी फ़ैक्ट्री को निष्पादित करता है, और लौटाता है:\n\n- लोड किए गए एक्सटेंशन परिभाषाएँ\n- प्रति-पथ लोड त्रुटियाँ (पूरे लोड को रोके बिना)\n- एक साझा एक्सटेंशन रनटाइम ऑब्जेक्ट जो बाद में `ExtensionRunner` द्वारा उपयोग किया जाता है\n\n## प्राथमिक कार्यान्वयन फ़ाइलें\n\n- `src/extensibility/extensions/loader.ts` — पथ खोज + इम्पोर्ट/निष्पादन\n- `src/extensibility/extensions/index.ts` — सार्वजनिक एक्सपोर्ट\n- `src/extensibility/extensions/runner.ts` — लोड के बाद रनटाइम/इवेंट निष्पादन\n- `src/discovery/builtin.ts` — एक्सटेंशन मॉड्यूल के लिए नेटिव ऑटो-डिस्कवरी प्रोवाइडर\n- `src/config/settings.ts` — मर्ज किए गए `extensions` / `disabledExtensions` सेटिंग्स लोड करता है\n\n---\n\n## एक्सटेंशन लोडिंग के इनपुट\n\n### 1) ऑटो-डिस्कवर किए गए नेटिव एक्सटेंशन मॉड्यूल\n\n`discoverAndLoadExtensions()` पहले डिस्कवरी प्रोवाइडर्स से `extension-module` कैपेबिलिटी आइटम माँगता है, फिर केवल प्रोवाइडर `native` आइटम रखता है।\n\nप्रभावी नेटिव स्थान:\n\n- प्रोजेक्ट: `<cwd>/.xcsh/extensions`\n- उपयोगकर्ता: `~/.xcsh/agent/extensions`\n\nपथ रूट नेटिव प्रोवाइडर (`SOURCE_PATHS.native`) से आते हैं।\n\nनोट्स:\n\n- नेटिव ऑटो-डिस्कवरी वर्तमान में `.xcsh` आधारित है।\n- लेगेसी `.pi` अभी भी `package.json` मैनिफ़ेस्ट कीज़ (`pi.extensions`) में स्वीकृत है, लेकिन यहाँ नेटिव रूट के रूप में नहीं।\n\n### 2) स्पष्ट रूप से कॉन्फ़िगर किए गए पथ\n\nऑटो-डिस्कवरी के बाद, कॉन्फ़िगर किए गए पथ जोड़े और रिज़ॉल्व किए जाते हैं।\n\nमुख्य सेशन स्टार्टअप पथ (`sdk.ts`) में कॉन्फ़िगर किए गए पथ स्रोत:\n\n1. CLI-प्रदत्त पथ (`--extension/-e`, और `--hook` को भी एक्सटेंशन पथ के रूप में माना जाता है)\n2. सेटिंग्स `extensions` ऐरे (मर्ज किए गए ग्लोबल + प्रोजेक्ट सेटिंग्स)\n\nग्लोबल सेटिंग्स फ़ाइल:\n\n- `~/.xcsh/agent/config.yml` (या `PI_CODING_AGENT_DIR` के माध्यम से कस्टम एजेंट डायरेक्टरी)\n\nप्रोजेक्ट सेटिंग्स फ़ाइल:\n\n- `<cwd>/.xcsh/settings.json`\n\nउदाहरण:\n\n```yaml\n# ~/.xcsh/agent/config.yml\nextensions:\n  - ~/my-exts/safety.ts\n  - ./local/ext-pack\n```\n\n```json\n{\n  \"extensions\": [\"./.xcsh/extensions/my-extra\"]\n}\n```\n\n---\n\n## सक्षम/अक्षम नियंत्रण\n\n### डिस्कवरी अक्षम करना\n\n- CLI: `--no-extensions`\n- SDK विकल्प: `disableExtensionDiscovery`\n\nव्यवहार विभाजन:\n\n- SDK: जब `disableExtensionDiscovery=true` हो, तब भी यह `loadExtensions()` के माध्यम से `additionalExtensionPaths` लोड करता है।\n- CLI पथ निर्माण (`main.ts`) वर्तमान में `--no-extensions` सेट होने पर CLI एक्सटेंशन पथ साफ़ कर देता है, इसलिए उस मोड में स्पष्ट `-e/--hook` अग्रेषित नहीं किए जाते।\n\n### विशिष्ट एक्सटेंशन मॉड्यूल अक्षम करना\n\n`disabledExtensions` सेटिंग एक्सटेंशन id प्रारूप द्वारा फ़िल्टर करती है:\n\n- `extension-module:<derivedName>`\n\n`derivedName` एंट्री पथ (`getExtensionNameFromPath`) पर आधारित है, उदाहरण के लिए:\n\n- `/x/foo.ts` -> `foo`\n- `/x/bar/index.ts` -> `bar`\n\nउदाहरण:\n\n```yaml\ndisabledExtensions:\n  - extension-module:foo\n```\n\n---\n\n## पथ और एंट्री रिज़ॉल्यूशन\n\n### पथ सामान्यीकरण\n\nकॉन्फ़िगर किए गए पथों के लिए:\n\n1. यूनिकोड स्पेस सामान्यीकरण\n2. `~` का विस्तार\n3. यदि सापेक्ष है, तो वर्तमान `cwd` के विरुद्ध रिज़ॉल्व करें\n\n### यदि कॉन्फ़िगर किया गया पथ एक फ़ाइल है\n\nइसे सीधे मॉड्यूल एंट्री उम्मीदवार के रूप में उपयोग किया जाता है।\n\n### यदि कॉन्फ़िगर किया गया पथ एक डायरेक्टरी है\n\nरिज़ॉल्यूशन क्रम:\n\n1. उस डायरेक्टरी में `package.json` जिसमें `xcsh.extensions` (या लेगेसी `pi.extensions`) हो -> घोषित एंट्रीज़ का उपयोग करें\n2. `index.ts`\n3. `index.js`\n4. अन्यथा एक स्तर तक एक्सटेंशन एंट्रीज़ के लिए स्कैन करें:\n   - सीधी `*.ts` / `*.js`\n   - सबडायरेक्टरी `index.ts` / `index.js`\n   - सबडायरेक्टरी `package.json` जिसमें `xcsh.extensions` / `pi.extensions` हो\n\nनियम और प्रतिबंध:\n\n- एक सबडायरेक्टरी स्तर से परे कोई पुनरावर्ती खोज नहीं\n- घोषित `extensions` मैनिफ़ेस्ट एंट्रीज़ उस पैकेज डायरेक्टरी के सापेक्ष रिज़ॉल्व की जाती हैं\n- घोषित एंट्रीज़ केवल तभी शामिल की जाती हैं जब फ़ाइल मौजूद हो/एक्सेस की अनुमति हो\n- `*/index.{ts,js}` जोड़ों में, TypeScript को JavaScript पर प्राथमिकता दी जाती है\n- सिमलिंक को पात्र फ़ाइलों/डायरेक्टरियों के रूप में माना जाता है\n\n### इग्नोर व्यवहार स्रोत के अनुसार भिन्न होता है\n\n- नेटिव ऑटो-डिस्कवरी (डिस्कवरी हेल्पर्स में `discoverExtensionModulePaths`) नेटिव glob का उपयोग करती है जिसमें `gitignore: true` और `hidden: false` हो।\n- `loader.ts` में स्पष्ट कॉन्फ़िगर्ड डायरेक्टरी स्कैनिंग `readdir` नियमों का उपयोग करती है और gitignore फ़िल्टरिंग लागू **नहीं** करती।\n\n---\n\n## लोड क्रम और प्राथमिकता\n\n`discoverAndLoadExtensions()` एक क्रमबद्ध सूची बनाता है और फिर `loadExtensions()` को कॉल करता है।\n\nक्रम:\n\n1. नेटिव ऑटो-डिस्कवर किए गए मॉड्यूल\n2. स्पष्ट कॉन्फ़िगर किए गए पथ (प्रदान किए गए क्रम में)\n\n`sdk.ts` में, कॉन्फ़िगर्ड क्रम है:\n\n1. CLI अतिरिक्त पथ\n2. सेटिंग्स `extensions`\n\nडी-डुप्लिकेशन:\n\n- एब्सोल्यूट पथ आधारित\n- पहले देखा गया पथ जीतता है\n- बाद के डुप्लिकेट अनदेखे किए जाते हैं\n\nनिहितार्थ: यदि एक ही मॉड्यूल पथ ऑटो-डिस्कवर और स्पष्ट रूप से कॉन्फ़िगर दोनों है, तो यह पहली स्थिति (ऑटो-डिस्कवर चरण) पर एक बार लोड होता है।\n\n---\n\n## मॉड्यूल इम्पोर्ट और फ़ैक्ट्री अनुबंध\n\nप्रत्येक उम्मीदवार पथ को डायनेमिक इम्पोर्ट से लोड किया जाता है:\n\n- `await import(resolvedPath)`\n- फ़ैक्ट्री `module.default ?? module` है\n- फ़ैक्ट्री एक फ़ंक्शन (`ExtensionFactory`) होनी चाहिए\n\nयदि एक्सपोर्ट एक फ़ंक्शन नहीं है, तो वह पथ एक संरचित त्रुटि के साथ विफल होता है और लोडिंग जारी रहती है।\n\n---\n\n## विफलता हैंडलिंग और आइसोलेशन\n\n### लोडिंग के दौरान\n\nप्रति एक्सटेंशन पथ, विफलताओं को `{ path, error }` के रूप में कैप्चर किया जाता है और अन्य पथों को लोड होने से नहीं रोकता।\n\nसामान्य मामले:\n\n- इम्पोर्ट विफलता / अनुपस्थित फ़ाइल\n- अमान्य फ़ैक्ट्री एक्सपोर्ट (नॉन-फ़ंक्शन)\n- फ़ैक्ट्री निष्पादित करते समय अपवाद फेंका गया\n\n### रनटाइम आइसोलेशन मॉडल\n\n- एक्सटेंशन **सैंडबॉक्स्ड नहीं** हैं (समान प्रोसेस/रनटाइम)।\n- वे एक `EventBus` और एक `ExtensionRuntime` इंस्टेंस साझा करते हैं।\n- लोड के दौरान, रनटाइम एक्शन मेथड जानबूझकर `ExtensionRuntimeNotInitializedError` फेंकते हैं; एक्शन वायरिंग बाद में `ExtensionRunner.initialize()` में होती है।\n\n### लोडिंग के बाद\n\nजब इवेंट `ExtensionRunner` के माध्यम से चलते हैं, तो हैंडलर अपवादों को पकड़ा जाता है और रनर लूप को क्रैश करने के बजाय एक्सटेंशन त्रुटियों के रूप में उत्सर्जित किया जाता है।\n\n---\n\n## न्यूनतम उपयोगकर्ता/प्रोजेक्ट लेआउट उदाहरण\n\n### उपयोगकर्ता-स्तर\n\n```text\n~/.xcsh/agent/\n  config.yml\n  extensions/\n    guardrails.ts\n    audit/\n      index.ts\n```\n\n### प्रोजेक्ट-स्तर\n\n```text\n<repo>/\n  .xcsh/\n    settings.json\n    extensions/\n      checks/\n        package.json\n      lint-gates.ts\n```\n\n`checks/package.json`:\n\n```json\n{\n  \"xcsh\": {\n    \"extensions\": [\"./src/check-a.ts\", \"./src/check-b.js\"]\n  }\n}\n```\n\nलेगेसी मैनिफ़ेस्ट कुंजी अभी भी स्वीकृत:\n\n```json\n{\n  \"pi\": {\n    \"extensions\": [\"./index.ts\"]\n  }\n}\n```\n",
	"hi/extensions/extensions.md": "---\ntitle: एक्सटेंशन\ndescription: >-\n  एक्सटेंशन रनटाइम का अवलोकन जिसमें प्रकार, रनर लाइफसाइकिल, पंजीकरण और डिस्कवरी\n  शामिल हैं।\nsidebar:\n  order: 1\n  label: अवलोकन\ni18n:\n  sourceHash: 14cc16dbd98b\n  translator: machine\n---\n\n# एक्सटेंशन\n\n`packages/coding-agent` में रनटाइम एक्सटेंशन लिखने के लिए प्राथमिक मार्गदर्शिका।\n\nयह दस्तावेज़ वर्तमान एक्सटेंशन रनटाइम को कवर करता है:\n\n- `src/extensibility/extensions/types.ts`\n- `src/extensibility/extensions/runner.ts`\n- `src/extensibility/extensions/wrapper.ts`\n- `src/extensibility/extensions/index.ts`\n- `src/modes/controllers/extension-ui-controller.ts`\n\nडिस्कवरी पथों और फाइलसिस्टम लोडिंग नियमों के लिए, `docs/extension-loading.md` देखें।\n\n## एक्सटेंशन क्या है\n\nएक एक्सटेंशन एक TS/JS मॉड्यूल है जो एक डिफ़ॉल्ट फ़ैक्टरी निर्यात करता है:\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nexport default function myExtension(pi: ExtensionAPI) {\n // register handlers/tools/commands/renderers\n}\n```\n\nएक्सटेंशन एक ही मॉड्यूल में निम्नलिखित सभी को संयोजित कर सकते हैं:\n\n- इवेंट हैंडलर (`pi.on(...)`)\n- LLM-callable उपकरण (`pi.registerTool(...)`)\n- स्लैश कमांड (`pi.registerCommand(...)`)\n- कीबोर्ड शॉर्टकट और फ्लैग\n- कस्टम मैसेज रेंडरिंग\n- सेशन/मैसेज इंजेक्शन API (`sendMessage`, `sendUserMessage`, `appendEntry`)\n\n## रनटाइम मॉडल\n\n1. एक्सटेंशन इम्पोर्ट किए जाते हैं और उनके फ़ैक्टरी फ़ंक्शन चलाए जाते हैं।\n2. उस लोड फ़ेज़ के दौरान, पंजीकरण विधियाँ मान्य हैं; रनटाइम एक्शन विधियाँ अभी तक प्रारंभ नहीं हुई हैं।\n3. `ExtensionRunner.initialize(...)` सक्रिय मोड के लिए लाइव एक्शन/कॉन्टेक्स्ट को जोड़ता है।\n4. सेशन/एजेंट/टूल लाइफसाइकिल इवेंट हैंडलर को भेजे जाते हैं।\n5. हर टूल एक्जीक्यूशन को एक्सटेंशन इंटरसेप्शन के साथ लपेटा जाता है (`tool_call` / `tool_result`)।\n\n```text\nExtension lifecycle (simplified)\n\nload paths\n   │\n   ▼\nimport module + run factory (registration only)\n   │\n   ▼\nExtensionRunner.initialize(mode/session/tool registry)\n   │\n   ├─ emit session/agent events to handlers\n   ├─ wrap tool execution (tool_call/tool_result)\n   └─ expose runtime actions (sendMessage, setActiveTools, ...)\n```\n\n`loader.ts` से महत्वपूर्ण बाधा:\n\n- एक्सटेंशन लोड के दौरान `pi.sendMessage()` जैसी एक्शन विधियों को कॉल करने पर `ExtensionRuntimeNotInitializedError` फेंकता है\n- पहले पंजीकृत करें; इवेंट/कमांड/टूल से रनटाइम व्यवहार करें\n\n## त्वरित प्रारंभ\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\nimport { Type } from \"@sinclair/typebox\";\n\nexport default function (pi: ExtensionAPI) {\n pi.setLabel(\"Safety + Utilities\");\n\n pi.on(\"session_start\", async (_event, ctx) => {\n  ctx.ui.notify(`Extension loaded in ${ctx.cwd}`, \"info\");\n });\n\n pi.on(\"tool_call\", async (event) => {\n  if (event.toolName === \"bash\" && event.input.command?.includes(\"rm -rf\")) {\n   return { block: true, reason: \"Blocked by extension policy\" };\n  }\n });\n\n pi.registerTool({\n  name: \"hello_extension\",\n  label: \"Hello Extension\",\n  description: \"Return a greeting\",\n  parameters: Type.Object({ name: Type.String() }),\n  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {\n   return {\n    content: [{ type: \"text\", text: `Hello, ${params.name}` }],\n    details: { greeted: params.name },\n   };\n  },\n });\n\n pi.registerCommand(\"hello-ext\", {\n  description: \"Show queue state\",\n  handler: async (_args, ctx) => {\n   ctx.ui.notify(`pending=${ctx.hasPendingMessages()}`, \"info\");\n  },\n });\n}\n```\n\n## एक्सटेंशन API सतहें\n\n## 1) पंजीकरण और एक्शन (`ExtensionAPI`)\n\nमुख्य विधियाँ:\n\n- `on(event, handler)`\n- `registerTool`, `registerCommand`, `registerShortcut`, `registerFlag`\n- `registerMessageRenderer`\n- `sendMessage`, `sendUserMessage`, `appendEntry`\n- `getActiveTools`, `getAllTools`, `setActiveTools`\n- `getSessionName`, `setSessionName`\n- `setModel`, `getThinkingLevel`, `setThinkingLevel`\n- `registerProvider`\n- `events` (साझा इवेंट बस)\n\nइंटरेक्टिव मोड में, `input` हैंडलर बिल्ट-इन पहले-मैसेज ऑटो-टाइटल जाँच से पहले चलते हैं। एक्सटेंशन जो `input` से `await pi.setSessionName(...)` कॉल करते हैं, वे सतत सेशन नाम सेट कर सकते हैं और उस सेशन के लिए डिफ़ॉल्ट ऑटो-जेनरेटेड टाइटल को चलने से रोक सकते हैं।\n\nयह भी उजागर है:\n\n- `pi.logger`\n- `pi.typebox`\n- `pi.pi` (पैकेज निर्यात)\n\n### मैसेज डिलीवरी सेमेंटिक्स\n\n`pi.sendMessage(message, options)` समर्थन करता है:\n\n- `deliverAs: \"steer\"` (डिफ़ॉल्ट) — वर्तमान रन को बाधित करता है\n- `deliverAs: \"followUp\"` — वर्तमान रन के बाद चलने के लिए कतारबद्ध\n- `deliverAs: \"nextTurn\"` — संग्रहीत और अगले उपयोगकर्ता प्रॉम्प्ट पर इंजेक्ट किया गया\n- `triggerTurn: true` — निष्क्रिय होने पर एक टर्न शुरू करता है (`nextTurn` इसे अनदेखा करता है)\n\n`pi.sendUserMessage(content, { deliverAs })` हमेशा प्रॉम्प्ट प्रवाह से गुजरता है; स्ट्रीमिंग के दौरान यह steer/follow-up के रूप में कतारबद्ध होता है।\n\n## 2) हैंडलर कॉन्टेक्स्ट (`ExtensionContext`)\n\nहैंडलर और टूल `execute` को `ctx` मिलता है जिसमें:\n\n- `ui`\n- `hasUI`\n- `cwd`\n- `sessionManager` (केवल-पढ़ने योग्य)\n- `modelRegistry`, `model`\n- `getContextUsage()`\n- `compact(...)`\n- `isIdle()`, `hasPendingMessages()`, `abort()`\n- `shutdown()`\n- `getSystemPrompt()`\n\n## 3) कमांड कॉन्टेक्स्ट (`ExtensionCommandContext`)\n\nकमांड हैंडलर को अतिरिक्त मिलता है:\n\n- `waitForIdle()`\n- `newSession(...)`\n- `switchSession(...)`\n- `branch(entryId)`\n- `navigateTree(targetId, { summarize })`\n- `reload()`\n\nसेशन-नियंत्रण प्रवाह के लिए कमांड कॉन्टेक्स्ट का उपयोग करें; ये विधियाँ जानबूझकर सामान्य इवेंट हैंडलर से अलग की गई हैं।\n\n## इवेंट सतह (वर्तमान नाम और व्यवहार)\n\nकैनोनिकल इवेंट यूनियन और पेलोड प्रकार `types.ts` में हैं।\n\n### सेशन लाइफसाइकिल\n\n- `session_start`\n- `session_before_switch` / `session_switch`\n- `session_before_branch` / `session_branch`\n- `session_before_compact` / `session.compacting` / `session_compact`\n- `session_before_tree` / `session_tree`\n- `session_shutdown`\n\nरद्द करने योग्य प्री-इवेंट:\n\n- `session_before_switch` → `{ cancel?: boolean }`\n- `session_before_branch` → `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_before_compact` → `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session_before_tree` → `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n\n### प्रॉम्प्ट और टर्न लाइफसाइकिल\n\n- `input`\n- `before_agent_start`\n- `context`\n- `agent_start` / `agent_end`\n- `turn_start` / `turn_end`\n- `message_start` / `message_update` / `message_end`\n\n### टूल लाइफसाइकिल\n\n- `tool_call` (पूर्व-निष्पादन, ब्लॉक कर सकता है)\n- `tool_result` (पश्च-निष्पादन, content/details/isError को पैच कर सकता है)\n- `tool_execution_start` / `tool_execution_update` / `tool_execution_end` (अवलोकनीयता)\n\n`tool_result` मिडलवेयर-स्टाइल है: हैंडलर एक्सटेंशन क्रम में चलते हैं और हर एक पूर्व संशोधन देखता है।\n\n### विश्वसनीयता/रनटाइम संकेत\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### उपयोगकर्ता कमांड इंटरसेप्शन\n\n- `user_bash` (`{ result }` के साथ ओवरराइड करें)\n- `user_python` (`{ result }` के साथ ओवरराइड करें)\n\n### `resources_discover`\n\n`resources_discover` एक्सटेंशन प्रकारों और `ExtensionRunner` में मौजूद है।\nवर्तमान रनटाइम नोट: `ExtensionRunner.emitResourcesDiscover(...)` लागू किया गया है, लेकिन वर्तमान कोडबेस में इसे कॉल करने वाला कोई `AgentSession` कॉलसाइट नहीं है।\n\n## टूल लेखन विवरण\n\n`registerTool` `types.ts` से `ToolDefinition` का उपयोग करता है।\n\nवर्तमान `execute` हस्ताक्षर:\n\n```ts\nexecute(\n toolCallId,\n params,\n signal,\n onUpdate,\n ctx,\n): Promise<AgentToolResult>\n```\n\nटेम्पलेट:\n\n```ts\npi.registerTool({\n name: \"my_tool\",\n label: \"My Tool\",\n description: \"...\",\n parameters: Type.Object({}),\n async execute(_id, _params, signal, onUpdate, ctx) {\n  if (signal?.aborted) {\n   return { content: [{ type: \"text\", text: \"Cancelled\" }] };\n  }\n  onUpdate?.({ content: [{ type: \"text\", text: \"Working...\" }] });\n  return { content: [{ type: \"text\", text: \"Done\" }], details: {} };\n },\n onSession(event, ctx) {\n  // reason: start|switch|branch|tree|shutdown\n },\n renderCall(args, theme) {\n  // optional TUI render\n },\n renderResult(result, options, theme, args) {\n  // optional TUI render\n },\n});\n```\n\n`tool_call`/`tool_result` `sdk.ts` में रजिस्ट्री को रैप किए जाने के बाद सभी उपकरणों को इंटरसेप्ट करता है, जिसमें बिल्ट-इन और एक्सटेंशन/कस्टम उपकरण शामिल हैं।\n\n## UI एकीकरण बिंदु\n\n`ctx.ui` `ExtensionUIContext` इंटरफेस को लागू करता है। समर्थन मोड के अनुसार भिन्न होता है।\n\n### इंटरेक्टिव मोड (`extension-ui-controller.ts`)\n\nसमर्थित:\n\n- डायलॉग: `select`, `confirm`, `input`, `editor`\n- नोटिफिकेशन/स्टेटस/एडिटर टेक्स्ट/टर्मिनल इनपुट/कस्टम ओवरले\n- नाम से थीम लिस्टिंग/लोडिंग (`setTheme` स्ट्रिंग नाम समर्थन करता है)\n- उपकरण विस्तारित टॉगल\n\nइस कंट्रोलर में वर्तमान नो-ऑप विधियाँ:\n\n- `setFooter`\n- `setHeader`\n- `setEditorComponent`\n\nयह भी नोट करें: `setWidget` वर्तमान में `setHookWidget(...)` के माध्यम से स्टेटस-लाइन टेक्स्ट पर रूट करता है।\n\n### RPC मोड (`rpc-mode.ts`)\n\n`ctx.ui` RPC `extension_ui_request` इवेंट द्वारा समर्थित है:\n\n- डायलॉग विधियाँ (`select`, `confirm`, `input`, `editor`) क्लाइंट प्रतिक्रियाओं तक राउंड-ट्रिप करती हैं\n- फायर-एंड-फॉरगेट विधियाँ अनुरोध भेजती हैं (`notify`, `setStatus`, `setWidget` स्ट्रिंग सरणियों के लिए, `setTitle`, `setEditorText`)\n\nRPC कार्यान्वयन में असमर्थित/नो-ऑप:\n\n- `onTerminalInput`\n- `custom`\n- `setFooter`, `setHeader`, `setEditorComponent`\n- `setWorkingMessage`\n- थीम स्विचिंग/लोडिंग (`setTheme` विफलता लौटाता है)\n- टूल विस्तार नियंत्रण निष्क्रिय हैं\n\n### प्रिंट/हेडलेस/सबएजेंट पथ\n\nजब रनर init को कोई UI कॉन्टेक्स्ट नहीं दिया जाता, तो `ctx.hasUI` `false` है और विधियाँ नो-ऑप/डिफ़ॉल्ट-रिटर्निंग हैं।\n\n### बैकग्राउंड इंटरेक्टिव मोड\n\nबैकग्राउंड मोड एक गैर-इंटरेक्टिव UI कॉन्टेक्स्ट ऑब्जेक्ट स्थापित करता है। वर्तमान कार्यान्वयन में, `ctx.hasUI` अभी भी `true` हो सकता है जबकि इंटरेक्टिव डायलॉग डिफ़ॉल्ट/नो-ऑप व्यवहार लौटाते हैं।\n\n## सेशन और स्थिति पैटर्न\n\nटिकाऊ एक्सटेंशन स्थिति के लिए:\n\n1. `pi.appendEntry(customType, data)` के साथ सतत रखें।\n2. `session_start`, `session_branch`, `session_tree` पर `ctx.sessionManager.getBranch()` से स्थिति पुनर्निर्माण करें।\n3. टूल रिजल्ट `details` को संरचित रखें जब स्थिति टूल रिजल्ट इतिहास से दृश्यमान/पुनर्निर्माण योग्य होनी चाहिए।\n\nउदाहरण पुनर्निर्माण पैटर्न:\n\n```ts\npi.on(\"session_start\", async (_event, ctx) => {\n let latest;\n for (const entry of ctx.sessionManager.getBranch()) {\n  if (entry.type === \"custom\" && entry.customType === \"my-state\") {\n   latest = entry.data;\n  }\n }\n // restore from latest\n});\n```\n\n## रेंडरिंग एक्सटेंशन बिंदु\n\n## कस्टम मैसेज रेंडरर\n\n```ts\npi.registerMessageRenderer(\"my-type\", (message, { expanded }, theme) => {\n // return pi-tui Component\n});\n```\n\nकस्टम मैसेज प्रदर्शित होने पर इंटरेक्टिव रेंडरिंग द्वारा उपयोग किया जाता है।\n\n## टूल कॉल/रिजल्ट रेंडरर\n\nTUI में कस्टम टूल विज़ुअलाइज़ेशन के लिए `registerTool` परिभाषाओं पर `renderCall` / `renderResult` प्रदान करें।\n\n## बाधाएँ और नुकसान\n\n- एक्सटेंशन लोड के दौरान रनटाइम एक्शन अनुपलब्ध हैं।\n- `tool_call` त्रुटियाँ निष्पादन को ब्लॉक करती हैं (fail-closed)।\n- बिल्ट-इन के साथ कमांड नाम संघर्ष डायग्नोस्टिक्स के साथ छोड़ दिए जाते हैं।\n- आरक्षित शॉर्टकट अनदेखे किए जाते हैं (`ctrl+c`, `ctrl+d`, `ctrl+z`, `ctrl+k`, `ctrl+p`, `ctrl+l`, `ctrl+o`, `ctrl+t`, `ctrl+g`, `shift+tab`, `shift+ctrl+p`, `alt+enter`, `escape`, `enter`)।\n- `ctx.reload()` को वर्तमान कमांड हैंडलर फ्रेम के लिए टर्मिनल मानें।\n\n## एक्सटेंशन बनाम हुक बनाम कस्टम-टूल\n\nसही सतह का उपयोग करें:\n\n- **एक्सटेंशन** (`src/extensibility/extensions/*`): एकीकृत प्रणाली (इवेंट + उपकरण + कमांड + रेंडरर + प्रोवाइडर पंजीकरण)।\n- **हुक** (`src/extensibility/hooks/*`): अलग लेगेसी इवेंट API।\n- **कस्टम-टूल** (`src/extensibility/custom-tools/*`): टूल-केंद्रित मॉड्यूल; एक्सटेंशन के साथ लोड होने पर वे अनुकूलित होते हैं और एक्सटेंशन इंटरसेप्शन रैपर से गुजरते हैं।\n\nयदि आपको एक ऐसे पैकेज की आवश्यकता है जो नीति, उपकरण, कमांड UX और रेंडरिंग को एक साथ संभाले, तो एक्सटेंशन का उपयोग करें।\n",
	"hi/extensions/gemini-manifest-extensions.md": "---\ntitle: Gemini मैनिफेस्ट एक्सटेंशन\ndescription: >-\n  क्रॉस-प्लेटफ़ॉर्म स्किल और एजेंट संगतता के लिए Gemini मैनिफेस्ट एक्सटेंशन\n  प्रारूप।\nsidebar:\n  order: 7\n  label: Gemini मैनिफेस्ट\ni18n:\n  sourceHash: 7134165a5f6d\n  translator: machine\n---\n\n# Gemini मैनिफेस्ट एक्सटेंशन (`gemini-extension.json`)\n\nयह दस्तावेज़ बताता है कि कोडिंग-एजेंट Gemini-स्टाइल मैनिफेस्ट एक्सटेंशन (`gemini-extension.json`) को कैसे खोजता और पार्स करता है, और उन्हें `extensions` क्षमता में कैसे रूपांतरित करता है।\n\nयह TypeScript/JavaScript एक्सटेंशन मॉड्यूल लोडिंग (`extensions/*.ts`, `index.ts`, `package.json xcsh.extensions`) को **नहीं** कवर करता, जिसे `extension-loading.md` में प्रलेखित किया गया है।\n\n## कार्यान्वयन फ़ाइलें\n\n- [`../src/discovery/gemini.ts`](../../packages/coding-agent/src/discovery/gemini.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/capability/extension.ts`](../../packages/coding-agent/src/capability/extension.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/loader.ts`](../../packages/coding-agent/src/extensibility/extensions/loader.ts)\n\n---\n\n## क्या खोजा जाता है\n\nGemini प्रोवाइडर (`id: gemini`, प्राथमिकता `60`) एक `extensions` लोडर पंजीकृत करता है जो दो निश्चित रूट्स को स्कैन करता है:\n\n- उपयोगकर्ता: `~/.gemini/extensions`\n- प्रोजेक्ट: `<cwd>/.gemini/extensions`\n\nपाथ रेज़ॉल्यूशन `ctx.home` और `ctx.cwd` से `getUserPath()` / `getProjectPath()` के माध्यम से सीधे होता है।\n\nमहत्वपूर्ण स्कोप नियम: प्रोजेक्ट लुकअप **केवल cwd** तक सीमित है। यह पैरेंट डायरेक्ट्री में नहीं जाता।\n\n---\n\n## डायरेक्ट्री स्कैन नियम\n\nप्रत्येक रूट (`~/.gemini/extensions` और `<cwd>/.gemini/extensions`) के लिए, डिस्कवरी निम्नलिखित करती है:\n\n1. `readDirEntries(root)`\n2. केवल प्रत्यक्ष चाइल्ड डायरेक्ट्री रखें (`entry.isDirectory()`)\n3. प्रत्येक चाइल्ड `<name>` के लिए, ठीक यही पढ़ने का प्रयास करें:\n   - `<root>/<name>/gemini-extension.json`\n\nएक डायरेक्ट्री स्तर से आगे कोई पुनरावर्ती स्कैन नहीं होती।\n\n### छुपी हुई डायरेक्ट्री\n\nGemini मैनिफेस्ट डिस्कवरी डॉट-प्रीफिक्स्ड डायरेक्ट्री नामों को **फ़िल्टर नहीं** करती। यदि कोई छुपी हुई चाइल्ड डायरेक्ट्री मौजूद है और उसमें `gemini-extension.json` है, तो उसे भी शामिल किया जाता है।\n\n### अनुपस्थित/अपठनीय फ़ाइलें\n\nयदि `gemini-extension.json` अनुपस्थित या अपठनीय है, तो उस डायरेक्ट्री को चुपचाप छोड़ दिया जाता है (कोई चेतावनी नहीं)।\n\n---\n\n## मैनिफेस्ट आकार (जैसा कार्यान्वित किया गया है)\n\nक्षमता प्रकार इस मैनिफेस्ट आकार को परिभाषित करता है:\n\n```ts\ninterface ExtensionManifest {\n name?: string;\n description?: string;\n mcpServers?: Record<string, Omit<MCPServer, \"name\" | \"_source\">>;\n tools?: unknown[];\n context?: unknown;\n}\n```\n\nडिस्कवरी-टाइम व्यवहार जानबूझकर शिथिल है:\n\n- JSON पार्स सफलता आवश्यक है।\n- JSON सिंटैक्स से परे फ़ील्ड प्रकार/सामग्री के लिए कोई रनटाइम स्कीमा सत्यापन नहीं है।\n- पार्स किया गया ऑब्जेक्ट क्षमता आइटम पर `manifest` के रूप में संग्रहीत होता है।\n\n### नाम सामान्यीकरण\n\n`Extension.name` इस प्रकार सेट होता है:\n\n1. `manifest.name` यदि यह `null`/`undefined` नहीं है\n2. अन्यथा एक्सटेंशन डायरेक्ट्री का नाम\n\nयहाँ कोई स्ट्रिंग-प्रकार प्रवर्तन लागू नहीं किया जाता।\n\n---\n\n## क्षमता आइटम में मटेरियलाइज़ेशन\n\nएक वैध पार्स किया गया मैनिफेस्ट एक `Extension` क्षमता आइटम बनाता है:\n\n```ts\n{\n name: manifest.name ?? <directory-name>,\n path: <extension-directory>,\n manifest: <parsed-json>,\n level: \"user\" | \"project\",\n _source: {\n  provider: \"gemini\",\n  providerName: \"Gemini CLI\" // capability registry द्वारा संलग्न\n  path: <absolute-manifest-path>,\n  level: \"user\" | \"project\"\n }\n}\n```\n\nटिप्पणियाँ:\n\n- `_source.path` को `createSourceMeta()` द्वारा एब्सोल्यूट पाथ में सामान्यीकृत किया जाता है।\n- `extensions` के लिए रजिस्ट्री-स्तरीय क्षमता सत्यापन केवल `name` और `path` की उपस्थिति जाँचता है।\n- मैनिफेस्ट इंटर्नल (`mcpServers`, `tools`, `context`) डिस्कवरी के दौरान सत्यापित नहीं होते।\n\n---\n\n## एरर हैंडलिंग और चेतावनी अर्थशास्त्र\n\n### चेतावनी दी जाती है\n\n- मैनिफेस्ट फ़ाइल में अमान्य JSON:\n  - चेतावनी प्रारूप: `Invalid JSON in <manifestPath>`\n\n### चेतावनी नहीं दी जाती (मूक स्किप)\n\n- `extensions` डायरेक्ट्री अनुपस्थित\n- चाइल्ड डायरेक्ट्री में `gemini-extension.json` नहीं है\n- अपठनीय मैनिफेस्ट फ़ाइल\n- मैनिफेस्ट JSON वाक्यात्मक रूप से वैध लेकिन अर्थात्मक रूप से अजीब/अपूर्ण\n\nइसका अर्थ है कि आंशिक वैधता स्वीकार की जाती है: केवल वाक्यात्मक JSON विफलता पर चेतावनी उत्सर्जित होती है।\n\n---\n\n## अन्य स्रोतों के साथ प्राथमिकता और डुप्लीकेशन निष्कासन\n\n`extensions` क्षमता को क्षमता रजिस्ट्री द्वारा प्रोवाइडरों में एकत्रित किया जाता है।\n\nइस क्षमता के लिए वर्तमान प्रोवाइडर:\n\n- `native` (`packages/coding-agent/src/discovery/builtin.ts`) प्राथमिकता `100`\n- `gemini` (`packages/coding-agent/src/discovery/gemini.ts`) प्राथमिकता `60`\n\nडुप्लीकेशन निष्कासन कुंजी `ext.name` है (`extensionCapability.key = ext => ext.name`)।\n\n### क्रॉस-प्रोवाइडर प्राथमिकता\n\nडुप्लीकेट एक्सटेंशन नामों पर उच्च-प्राथमिकता प्रोवाइडर जीतता है।\n\n- यदि `native` और `gemini` दोनों एक्सटेंशन नाम `foo` उत्सर्जित करते हैं, तो native आइटम रखा जाता है।\n- निम्न-प्राथमिकता डुप्लीकेट केवल `result.all` में `_shadowed = true` के साथ बरकरार रहता है।\n\n### इंट्रा-प्रोवाइडर क्रम प्रभाव\n\nचूँकि डुप्लीकेशन निष्कासन \"पहले दिखा वह जीता\" के आधार पर है, प्रोवाइडर-स्थानीय आइटम क्रम महत्वपूर्ण है।\n\n- Gemini लोडर **पहले उपयोगकर्ता**, फिर **प्रोजेक्ट** जोड़ता है।\n- इसलिए, `~/.gemini/extensions` और `<cwd>/.gemini/extensions` के बीच डुप्लीकेट नाम उपयोगकर्ता प्रविष्टि को बनाए रखते हैं और प्रोजेक्ट प्रविष्टि को शैडो करते हैं।\n\nइसके विपरीत, native प्रोवाइडर `getConfigDirs()` में भिन्न क्रम से config dir बनाता है (`project` फिर `user`), इसलिए native इंट्रा-प्रोवाइडर शैडोइंग विपरीत दिशा में होती है।\n\n---\n\n## उपयोगकर्ता बनाम प्रोजेक्ट व्यवहार सारांश\n\nGemini मैनिफेस्ट के लिए विशेष रूप से:\n\n- प्रत्येक लोड पर दोनों उपयोगकर्ता और प्रोजेक्ट रूट्स स्कैन किए जाते हैं।\n- प्रोजेक्ट रूट `<cwd>/.gemini/extensions` तक निश्चित है (कोई पूर्वज वॉक नहीं)।\n- Gemini स्रोत के भीतर डुप्लीकेट नाम उपयोगकर्ता-प्रथम के आधार पर हल होते हैं।\n- उच्च-प्राथमिकता प्रोवाइडरों (विशेष रूप से native) के विरुद्ध डुप्लीकेट नाम प्राथमिकता से हार जाते हैं।\n\n---\n\n## सीमा: डिस्कवरी मेटाडेटा बनाम रनटाइम एक्सटेंशन लोडिंग\n\n`gemini-extension.json` डिस्कवरी वर्तमान में क्षमता मेटाडेटा (`Extension` आइटम) को फ़ीड करती है। यह सीधे रन करने योग्य TS/JS एक्सटेंशन मॉड्यूल **लोड नहीं करती**।\n\nरनटाइम मॉड्यूल लोडिंग (`discoverAndLoadExtensions()` / `loadExtensions()`) `extension-modules` और स्पष्ट पाथ का उपयोग करती है, और वर्तमान में ऑटो-डिस्कवर्ड मॉड्यूल को केवल प्रोवाइडर `native` तक फ़िल्टर करती है।\n\nव्यावहारिक निहितार्थ:\n\n- Gemini मैनिफेस्ट एक्सटेंशन क्षमता रिकॉर्ड के रूप में खोजे जा सकते हैं।\n- वे स्वयं एक्सटेंशन लोडर पाइपलाइन द्वारा रनटाइम एक्सटेंशन मॉड्यूल के रूप में निष्पादित नहीं होते।\n\nयह सीमा वर्तमान कार्यान्वयन में जानबूझकर है और बताती है कि मैनिफेस्ट डिस्कवरी और निष्पादन योग्य मॉड्यूल लोडिंग अलग क्यों हो सकती है।\n",
	"hi/extensions/marketplace.md": "---\ntitle: मार्केटप्लेस प्लगइन सिस्टम\ndescription: >-\n  क्यूरेटेड प्लगइन संग्रह की खोज, इंस्टॉलेशन और प्रबंधन के लिए मार्केटप्लेस\n  प्लगइन सिस्टम।\nsidebar:\n  order: 4\n  label: मार्केटप्लेस\ni18n:\n  sourceHash: 71d9f8f93a81\n  translator: machine\n---\n\n# मार्केटप्लेस प्लगइन सिस्टम\n\nमार्केटप्लेस सिस्टम आपको Git-होस्टेड कैटलॉग से प्लगइन खोजने, इंस्टॉल करने और प्रबंधित करने की सुविधा देता है। यह Claude Code प्लगइन रजिस्ट्री फ़ॉर्मेट के साथ संगत है।\n\n## त्वरित शुरुआत\n\n```\n/marketplace add anthropics/f5-sales-demo-marketplace\n/marketplace install wordpress.com@f5-sales-demo-marketplace\n```\n\nया बिना किसी तर्क के बस `/marketplace` टाइप करें और इंटरेक्टिव प्लगइन ब्राउज़र खोलें।\n\n## अवधारणाएँ\n\nएक **मार्केटप्लेस** एक Git रिपॉज़िटरी (या स्थानीय डायरेक्टरी) होती है जिसमें `.xcsh-plugin/marketplace.json` पर एक कैटलॉग फ़ाइल होती है। कैटलॉग उपलब्ध प्लगइन को उनके स्रोत, विवरण और मेटाडेटा के साथ सूचीबद्ध करता है।\n\nएक **प्लगइन** एक डायरेक्टरी है जिसमें स्किल्स, कमांड, हुक, MCP सर्वर, या LSP सर्वर होते हैं। प्लगइन को `name@marketplace` (जैसे `code-review@f5-sales-demo-marketplace`) द्वारा पहचाना जाता है।\n\n**स्कोप**: प्लगइन को दो स्कोप पर इंस्टॉल किया जा सकता है:\n\n- **user** (डिफ़ॉल्ट) -- सभी प्रोजेक्ट में उपलब्ध, `~/.xcsh/plugins/installed_plugins.json` में संग्रहीत\n- **project** -- केवल वर्तमान प्रोजेक्ट में उपलब्ध, `.xcsh/installed_plugins.json` में संग्रहीत\n\nप्रोजेक्ट-स्कोप्ड इंस्टॉल, उसी प्लगइन के user-स्कोप्ड इंस्टॉल को ओवरराइड करते हैं।\n\n## कमांड\n\n### इंटरेक्टिव मोड\n\n| कमांड | प्रभाव |\n|---|---|\n| `/marketplace` | इंटरेक्टिव प्लगइन ब्राउज़र खोलें (इंस्टॉल) |\n\n### मार्केटप्लेस प्रबंधन\n\n| कमांड | प्रभाव |\n|---|---|\n| `/marketplace add <source>` | एक मार्केटप्लेस स्रोत जोड़ें |\n| `/marketplace remove <name>` | एक मार्केटप्लेस हटाएँ |\n| `/marketplace update [name]` | कैटलॉग पुनः प्राप्त करें; सभी अपडेट करने के लिए नाम छोड़ें |\n| `/marketplace list` | कॉन्फ़िगर किए गए मार्केटप्लेस सूचीबद्ध करें |\n\n### प्लगइन संचालन\n\n| कमांड | प्रभाव |\n|---|---|\n| `/marketplace discover [marketplace]` | उपलब्ध प्लगइन ब्राउज़ करें |\n| `/marketplace install [--force] [--scope user\\|project] name@marketplace` | एक प्लगइन इंस्टॉल करें |\n| `/marketplace uninstall [--scope user\\|project] name@marketplace` | एक प्लगइन अनइंस्टॉल करें |\n| `/marketplace installed` | इंस्टॉल किए गए मार्केटप्लेस प्लगइन सूचीबद्ध करें |\n| `/marketplace upgrade [--scope user\\|project] [name@marketplace]` | एक या सभी प्लगइन अपग्रेड करें |\n\n### CLI समकक्ष\n\nवही संचालन कमांड लाइन से भी उपलब्ध हैं:\n\n```\nxcsh plugin marketplace add <source>\nxcsh plugin marketplace remove <name>\nxcsh plugin marketplace update [name]\nxcsh plugin marketplace list\nxcsh plugin discover [marketplace]\nxcsh plugin install --scope project name@marketplace\n```\n\n## मार्केटप्लेस स्रोत\n\nजब आप `/marketplace add <source>` चलाते हैं, तो सिस्टम स्रोत को वर्गीकृत करता है:\n\n| स्रोत फ़ॉर्मेट | प्रकार | उदाहरण |\n|---|---|---|\n| `owner/repo` | GitHub शॉर्टहैंड | `anthropics/f5-sales-demo-marketplace` |\n| `https://...*.json` | डायरेक्ट कैटलॉग URL | `https://example.com/marketplace.json` |\n| `https://...*.git` या `git@...` | Git रिपॉज़िटरी | `https://github.com/org/repo.git` |\n| `./path` या `~/path` या `/path` | स्थानीय डायरेक्टरी | `./my-marketplace` |\n\nसिस्टम रिपॉज़िटरी क्लोन करता है (या स्थानीय डायरेक्टरी पढ़ता है), `.xcsh-plugin/marketplace.json` ढूँढता है, उसे सत्यापित करता है, और कैटलॉग को स्थानीय रूप से कैश करता है।\n\n## कैटलॉग फ़ॉर्मेट (marketplace.json)\n\nएक मार्केटप्लेस कैटलॉग रिपॉज़िटरी रूट में `.xcsh-plugin/marketplace.json` पर स्थित होता है:\n\n```json\n{\n  \"$schema\": \"https://anthropic.com/claude-code/marketplace.schema.json\",\n  \"name\": \"my-marketplace\",\n  \"owner\": {\n    \"name\": \"Your Name\",\n    \"email\": \"you@example.com\"\n  },\n  \"description\": \"A collection of plugins\",\n  \"plugins\": [\n    {\n      \"name\": \"my-plugin\",\n      \"description\": \"What this plugin does\",\n      \"source\": \"./plugins/my-plugin\",\n      \"category\": \"development\",\n      \"homepage\": \"https://github.com/you/my-plugin\"\n    }\n  ]\n}\n```\n\n### आवश्यक फ़ील्ड\n\n| फ़ील्ड | विवरण |\n|---|---|\n| `name` | मार्केटप्लेस का नाम। लोअरकेस अल्फ़ान्यूमेरिक, हाइफ़न और डॉट। अल्फ़ान्यूमेरिक से शुरू और समाप्त होना चाहिए। अधिकतम 64 अक्षर। |\n| `owner.name` | मार्केटप्लेस स्वामी का नाम |\n| `plugins` | प्लगइन एंट्री की सरणी |\n\n### प्लगइन एंट्री फ़ील्ड\n\n| फ़ील्ड | आवश्यक | विवरण |\n|---|---|---|\n| `name` | हाँ | प्लगइन का नाम (मार्केटप्लेस नाम के समान नियम) |\n| `source` | हाँ | प्लगइन कहाँ खोजें (नीचे देखें) |\n| `description` | नहीं | संक्षिप्त विवरण |\n| `version` | नहीं | संस्करण स्ट्रिंग |\n| `author` | नहीं | `{ name, email? }` |\n| `homepage` | नहीं | URL |\n| `category` | नहीं | श्रेणी स्ट्रिंग (जैसे `development`, `productivity`, `security`) |\n| `tags` | नहीं | स्ट्रिंग टैग की सरणी |\n| `strict` | नहीं | बूलियन |\n| `commands` | नहीं | प्रदान किए गए स्लैश कमांड |\n| `agents` | नहीं | प्रदान किए गए एजेंट |\n| `hooks` | नहीं | हुक परिभाषाएँ |\n| `mcpServers` | नहीं | MCP सर्वर परिभाषाएँ |\n| `lspServers` | नहीं | LSP सर्वर परिभाषाएँ |\n\n### प्लगइन स्रोत फ़ॉर्मेट\n\n`source` फ़ील्ड कई फ़ॉर्मेट का समर्थन करता है:\n\n**सापेक्ष पथ** (मार्केटप्लेस रेपो के भीतर):\n\n```json\n\"source\": \"./plugins/my-plugin\"\n```\n\n**Git रिपॉज़िटरी URL**:\n\n```json\n\"source\": {\n  \"source\": \"url\",\n  \"url\": \"https://github.com/org/repo.git\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**GitHub शॉर्टहैंड**:\n\n```json\n\"source\": {\n  \"source\": \"github\",\n  \"repo\": \"org/repo\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Git सबडायरेक्टरी** (मोनोरेपो):\n\n```json\n\"source\": {\n  \"source\": \"git-subdir\",\n  \"url\": \"https://github.com/org/monorepo.git\",\n  \"path\": \"plugins/my-plugin\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**npm पैकेज**:\n\n```json\n\"source\": {\n  \"source\": \"npm\",\n  \"package\": \"@scope/my-plugin\",\n  \"version\": \"1.0.0\"\n}\n```\n\n## डिस्क पर लेआउट\n\n```\n~/.xcsh/\n  config/\n    marketplaces.json          # जोड़े गए मार्केटप्लेस की रजिस्ट्री\n  plugins/\n    installed_plugins.json     # User-स्कोप्ड इंस्टॉल किए गए प्लगइन\n    cache/\n      marketplaces/            # कैश किए गए मार्केटप्लेस कैटलॉग\n      plugins/                 # कैश की गई प्लगइन डायरेक्टरी\n\n<project>/.xcsh/\n  installed_plugins.json       # Project-स्कोप्ड इंस्टॉल किए गए प्लगइन\n```\n\n## नामकरण नियम\n\nमार्केटप्लेस और प्लगइन नाम:\n\n- लोअरकेस अक्षर या अंक से शुरू और समाप्त होने चाहिए\n- केवल लोअरकेस अक्षर, अंक, हाइफ़न और डॉट हो सकते हैं\n- अधिकतम 64 अक्षर के होने चाहिए\n\nप्लगइन ID (`name@marketplace`) कुल अधिकतम 128 अक्षर होने चाहिए।\n\nमान्य उदाहरण: `my-plugin`, `code-review`, `wordpress.com`, `ai-firstify`\nअमान्य उदाहरण: `-bad`, `bad-`, `.bad`, `Bad`, `under_score`\n",
	"hi/extensions/plugin-manager-installer-plumbing.md": "---\ntitle: प्लगइन मैनेजर और इंस्टॉलर प्लंबिंग\ndescription: >-\n  प्लगइन मैनेजर की आंतरिक कार्यप्रणाली जिसमें इंस्टॉलेशन, वैलिडेशन, डिपेंडेंसी\n  रिज़ॉल्यूशन और लाइफसाइकल प्रबंधन शामिल हैं।\nsidebar:\n  order: 5\n  label: प्लगइन मैनेजर\ni18n:\n  sourceHash: 9c33e5a2c22a\n  translator: machine\n---\n\n# प्लगइन मैनेजर और इंस्टॉलर प्लंबिंग\n\nयह दस्तावेज़ बताता है कि `xcsh plugin` ऑपरेशन डिस्क पर प्लगइन स्थिति को किस प्रकार बदलते हैं और इंस्टॉल किए गए प्लगइन किस प्रकार रनटाइम क्षमताएं बनते हैं (आज उपकरण, हुक/कमांड पाथ रिज़ॉल्यूशन उपलब्ध)।\n\n## स्कोप और आर्किटेक्चर\n\nकोडबेस में प्लगइन-प्रबंधन के दो कार्यान्वयन हैं:\n\n1. **CLI कमांड द्वारा उपयोग किया जाने वाला सक्रिय पाथ**: `PluginManager` (`src/extensibility/plugins/manager.ts`)\n2. **लेगेसी हेल्पर मॉड्यूल**: इंस्टॉलर फ़ंक्शन (`src/extensibility/plugins/installer.ts`)\n\n`xcsh plugin ...` कमांड का निष्पादन `PluginManager` से होकर जाता है।\n\n`installer.ts` अभी भी महत्वपूर्ण सुरक्षा जाँच और फ़ाइलसिस्टम व्यवहार को दस्तावेज़ीकृत करता है, लेकिन यह `src/commands/plugin.ts` + `src/cli/plugin-cli.ts` द्वारा उपयोग किया जाने वाला पाथ नहीं है।\n\n## लाइफसाइकल: CLI इनवोकेशन से रनटाइम उपलब्धता तक\n\n```text\nxcsh plugin <action> ...\n  -> src/commands/plugin.ts\n  -> runPluginCommand(...) in src/cli/plugin-cli.ts\n  -> PluginManager method (install/list/uninstall/link/...) \n  -> mutate ~/.xcsh/plugins/{package.json,node_modules,xcsh-plugins.lock.json}\n  -> runtime discovery: discoverAndLoadCustomTools(...)\n  -> getAllPluginToolPaths(cwd)\n  -> custom tool loader imports tool modules\n```\n\n### कमांड एंट्रीपॉइंट\n\n- `src/commands/plugin.ts` कमांड/फ़्लैग परिभाषित करता है और `runPluginCommand` को अग्रेषित करता है।\n- `src/cli/plugin-cli.ts` सबकमांड को `PluginManager` मेथड से मैप करता है:\n  - `install`, `uninstall`, `list`, `link`, `doctor`, `features`, `config`, `enable`, `disable`\n- कोई स्पष्ट `update` एक्शन मौजूद नहीं है; अपडेट नए पैकेज/वर्जन स्पेक के साथ `install` पुनः चलाकर किया जाता है।\n\n## ऑन-डिस्क मॉडल\n\nग्लोबल प्लगइन स्थिति `~/.xcsh/plugins` के अंतर्गत रहती है:\n\n- `package.json` — `bun install`/`bun uninstall` द्वारा उपयोग किया जाने वाला डिपेंडेंसी मैनिफेस्ट\n- `node_modules/` — इंस्टॉल किए गए प्लगइन पैकेज या सिमलिंक\n- `xcsh-plugins.lock.json` — रनटाइम स्थिति:\n  - प्रति प्लगइन सक्षम/अक्षम\n  - प्रति प्लगइन चयनित फीचर सेट\n  - स्थायी प्लगइन सेटिंग्स\n\nप्रोजेक्ट-लोकल ओवरराइड यहाँ रहते हैं:\n\n- `<cwd>/.xcsh/plugin-overrides.json`\n\nओवरराइड मैनेजर/लोडर दृष्टिकोण से केवल पढ़ने योग्य हैं (यहाँ कोई राइट पाथ नहीं है) और इस प्रोजेक्ट के लिए प्लगइन को अक्षम कर सकते हैं या फीचर/सेटिंग्स को ओवरराइड कर सकते हैं।\n\n## प्लगइन स्पेक पार्सिंग और मेटाडेटा इंटरप्रिटेशन\n\n## इंस्टॉल स्पेक ग्रामर\n\n`parsePluginSpec` (`parser.ts`) इनका समर्थन करता है:\n\n- `pkg` -> `features: null` (डिफॉल्ट व्यवहार)\n- `pkg[*]` -> सभी मैनिफेस्ट फीचर सक्षम करें\n- `pkg[]` -> कोई वैकल्पिक फीचर सक्षम न करें\n- `pkg[a,b]` -> नामित फीचर सक्षम करें\n- `@scope/pkg@1.2.3[feat]` -> स्पष्ट फीचर चयन के साथ स्कोप्ड + वर्जन्ड पैकेज\n\n`extractPackageName` इंस्टॉल के बाद ऑन-डिस्क पाथ लुकअप के लिए वर्जन सफिक्स हटाता है।\n\n## मैनिफेस्ट स्रोत और आवश्यक फील्ड\n\nमैनिफेस्ट इस प्रकार रिज़ॉल्व होता है:\n\n1. `package.json.xcsh`\n2. फॉलबैक `package.json.pi`\n3. फॉलबैक `{ version: package.version }`\n\nनिहितार्थ:\n\n- मैनेजर/लोडर में कोई सख्त स्कीमा वैलिडेशन नहीं है।\n- `xcsh`/`pi` से रहित पैकेज अभी भी इंस्टॉल करने योग्य और सूचीबद्ध करने योग्य है।\n- रनटाइम प्लगइन लोडिंग (`getEnabledPlugins`) `xcsh`/`pi` मैनिफेस्ट के बिना पैकेज को छोड़ देती है।\n- `manifest.version` हमेशा पैकेज `version` से ओवरराइट होता है।\n\nपढ़ने के समय खराब `package.json` JSON एक हार्ड फेलियर है; खराब मैनिफेस्ट शेप बाद में तभी फेल हो सकता है जब विशिष्ट फील्ड उपयोग किए जाएं।\n\n## इंस्टॉल/अपडेट फ्लो (`PluginManager.install`)\n\n1. इंस्टॉल स्पेक से फीचर ब्रैकेट सिंटैक्स पार्स करें।\n2. रेगेक्स + शेल-मेटाकैरेक्टर डेनीलिस्ट के विरुद्ध पैकेज नाम वैलिडेट करें।\n3. सुनिश्चित करें कि प्लगइन `package.json` मौजूद है (`xcsh-plugins`, प्राइवेट डिपेंडेंसी मैप)।\n4. `~/.xcsh/plugins` में `bun install <packageSpec>` चलाएं।\n5. इंस्टॉल किया गया पैकेज `node_modules/<name>/package.json` पढ़ें।\n6. मैनिफेस्ट रिज़ॉल्व करें और `enabledFeatures` कंप्यूट करें:\n   - `[*]`: सभी घोषित फीचर (या `null` यदि कोई फीचर मैप नहीं है)\n   - `[a,b]`: मैनिफेस्ट फीचर मैप में प्रत्येक फीचर की जाँच करता है\n   - `[]`: खाली फीचर सूची\n   - बेयर स्पेक: `null` (बाद में लोडर में डिफॉल्ट पॉलिसी उपयोग करें)\n7. लॉकफाइल रनटाइम स्थिति अपसर्ट करें: `{ version, enabledFeatures, enabled: true }`।\n\n### अपडेट सेमेंटिक्स\n\nक्योंकि अपडेट इंस्टॉल-संचालित है:\n\n- `xcsh plugin install pkg@newVersion` डिपेंडेंसी और लॉकफाइल वर्जन अपडेट करता है।\n- मौजूदा सेटिंग्स संरक्षित रहती हैं; स्टेट एंट्री version/features/enabled के लिए ओवरराइट होती है।\n- कोई अलग \"अपडेट जाँचें\" या ट्रांजेक्शनल माइग्रेशन लॉजिक मौजूद नहीं है।\n\n## रिमूव फ्लो (`PluginManager.uninstall`)\n\n1. पैकेज नाम वैलिडेट करें।\n2. प्लगइन डायरेक्टरी में `bun uninstall <name>` चलाएं।\n3. लॉकफाइल से प्लगइन रनटाइम स्थिति हटाएं:\n   - `config.plugins[name]`\n   - `config.settings[name]`\n\nयदि अनइंस्टॉल कमांड विफल होती है, तो रनटाइम स्थिति नहीं बदलती।\n\n## लिस्ट फ्लो (`PluginManager.list`)\n\n1. `~/.xcsh/plugins/package.json` से प्लगइन डिपेंडेंसी मैप पढ़ें।\n2. लॉकफाइल रनटाइम कॉन्फिग लोड करें (फाइल नहीं मिली -> खाली डिफॉल्ट)।\n3. प्रोजेक्ट ओवरराइड लोड करें (`<cwd>/.xcsh/plugin-overrides.json`, पार्स/पढ़ने की त्रुटियां -> चेतावनी के साथ खाली ऑब्जेक्ट)।\n4. रिज़ॉल्व करने योग्य package.json वाली प्रत्येक डिपेंडेंसी के लिए:\n   - `InstalledPlugin` रिकॉर्ड बनाएं\n   - फीचर/इनेबल स्थिति मर्ज करें:\n     - लॉकफाइल से बेस (या डिफॉल्ट)\n     - प्रोजेक्ट ओवरराइड फीचर चयन को बदल सकते हैं\n     - प्रोजेक्ट `disabled` सूची प्लगइन को अक्षम के रूप में मास्क करती है\n\nयह CLI स्टेटस आउटपुट और settings/features ऑपरेशन द्वारा उपयोग की जाने वाली प्रभावी स्थिति है।\n\n## लिंक फ्लो (`PluginManager.link`)\n\n`link` लोकल पैकेज को `~/.xcsh/plugins/node_modules/<pkg.name>` में सिमलिंक करके लोकल प्लगइन डेवलपमेंट का समर्थन करता है।\n\nव्यवहार:\n\n1. मैनेजर cwd के विरुद्ध `localPath` रिज़ॉल्व करें।\n2. लोकल `package.json` और `name` फील्ड की आवश्यकता है।\n3. प्लगइन डायरेक्टरी सुनिश्चित करें।\n4. स्कोप्ड नामों के लिए, स्कोप डायरेक्टरी बनाएं।\n5. टार्गेट लिंक लोकेशन पर मौजूदा पाथ हटाएं।\n6. सिमलिंक बनाएं।\n7. डिफॉल्ट फीचर (`null`) के साथ सक्षम रनटाइम लॉकफाइल एंट्री जोड़ें।\n\nचेतावनी: वर्तमान `PluginManager.link` लेगेसी `installer.ts` (`normalizedPath.startsWith(normalizedCwd)`) में मौजूद `cwd` पाथ-बाउंड्री जाँच को लागू नहीं करता, इसलिए विश्वास कॉलर की जिम्मेदारी है।\n\n## रनटाइम लोडिंग: इंस्टॉल किए गए प्लगइन से कॉल करने योग्य क्षमताओं तक\n\n## डिस्कवरी गेट\n\n`getEnabledPlugins(cwd)` (`plugins/loader.ts`) पढ़ता है:\n\n- प्लगइन डिपेंडेंसी मैनिफेस्ट (`package.json`)\n- लॉकफाइल रनटाइम स्थिति\n- `getConfigDirPaths(\"plugin-overrides.json\", { user: false, cwd })` के माध्यम से प्रोजेक्ट ओवरराइड\n\nफ़िल्टरिंग:\n\n- यदि कोई प्लगइन package.json नहीं है तो छोड़ें\n- यदि मैनिफेस्ट (`xcsh`/`pi`) अनुपस्थित है तो छोड़ें\n- यदि लॉकफाइल में ग्लोबली अक्षम है तो छोड़ें\n- यदि प्रोजेक्ट-अक्षम है तो छोड़ें\n\n## क्षमता पाथ रिज़ॉल्यूशन\n\nप्रत्येक सक्षम प्लगइन के लिए:\n\n- `resolvePluginToolPaths(plugin)`\n- `resolvePluginHookPaths(plugin)`\n- `resolvePluginCommandPaths(plugin)`\n\nप्रत्येक रिज़ॉल्वर में बेस एंट्री के साथ फीचर एंट्री शामिल हैं:\n\n- स्पष्ट फीचर सूची -> केवल चयनित फीचर\n- `enabledFeatures === null` -> `default: true` चिह्नित फीचर सक्षम करें\n\nगुम फाइलें चुपचाप छोड़ दी जाती हैं (`existsSync` गार्ड)।\n\n## वर्तमान रनटाइम वायरिंग अंतर\n\n- **उपकरण आज रनटाइम में वायर्ड हैं** `discoverAndLoadCustomTools` (`custom-tools/loader.ts`) के माध्यम से, जो `getAllPluginToolPaths(cwd)` कॉल करता है।\n- पाथ को कस्टम टूल डिस्कवरी में रिज़ॉल्व किए गए एब्सोल्यूट पाथ द्वारा डी-डुप्लिकेट किया जाता है (`seen` सेट, पहला पाथ जीतता है)।\n- **हुक/कमांड रिज़ॉल्वर मौजूद हैं** और एक्सपोर्ट किए गए हैं, लेकिन यह कोड पाथ वर्तमान में उन्हें उसी तरह रनटाइम रजिस्ट्री में वायर नहीं करता जैसे उपकरण वायर किए जाते हैं।\n\n## लॉक/स्टेट प्रबंधन विवरण\n\n`PluginManager` प्रति इंस्टेंस मेमोरी में रनटाइम कॉन्फिग कैश करता है (`#runtimeConfig`) और एक बार लेजिली लोड करता है।\n\nलोड व्यवहार:\n\n- लॉकफाइल नहीं मिली -> `{ plugins: {}, settings: {} }`\n- लॉकफाइल पढ़ने/पार्स करने की विफलता -> चेतावनी + समान खाली डिफॉल्ट\n\nसेव व्यवहार:\n\n- प्रत्येक म्यूटेशन पर पूरी लॉकफाइल JSON प्रिटी-प्रिंटेड लिखता है\n\nकोई क्रॉस-प्रोसेस लॉकिंग या मर्ज रणनीति मौजूद नहीं है; समवर्ती राइटर एक-दूसरे को ओवरराइट कर सकते हैं।\n\n## सुरक्षा जाँच और विश्वास सीमाएं\n\n## इनपुट/पैकेज वैलिडेशन\n\nसक्रिय मैनेजर पाथ पैकेज-नाम वैलिडेशन लागू करता है:\n\n- स्कोप्ड/अनस्कोप्ड पैकेज स्पेक के लिए रेगेक्स (वैकल्पिक रूप से वर्जन के साथ)\n- स्पष्ट शेल मेटाकैरेक्टर डेनीलिस्ट (`[;&|`$(){}[]<>\\\\]`)\n\nयह `bun install/uninstall` इनवोक करते समय कमांड-इंजेक्शन जोखिम को सीमित करता है।\n\n## फ़ाइलसिस्टम विश्वास सीमा\n\n- कस्टम टूल मॉड्यूल इंपोर्ट होने पर प्लगइन कोड इन-प्रोसेस निष्पादित होता है; कोई सैंडबॉक्सिंग नहीं।\n- मैनिफेस्ट रिलेटिव पाथ प्लगइन पैकेज डायरेक्टरी के विरुद्ध जॉइन किए जाते हैं और केवल अस्तित्व-जाँच किए जाते हैं।\n- प्लगइन पैकेज स्वयं इंस्टॉल होने के बाद विश्वसनीय कोड है।\n\n## लेगेसी इंस्टॉलर-केवल जाँच\n\n`installer.ts` में अतिरिक्त लिंक-टाइम जाँच हैं जो `PluginManager.link` में नहीं हैं:\n\n- लोकल पाथ प्रोजेक्ट cwd के अंदर रिज़ॉल्व होना चाहिए\n- सिमलिंक टार्गेट नामकरण के लिए अतिरिक्त पैकेज नाम/पाथ ट्रैवर्सल गार्ड\n\nक्योंकि CLI `PluginManager` उपयोग करता है, ये सख्त लिंक गार्ड वर्तमान में मुख्य पाथ पर नहीं हैं।\n\n## विफलता, आंशिक सफलता, और रोलबैक व्यवहार\n\nप्लगइन मैनेजर ट्रांजेक्शनल नहीं है।\n\n| ऑपरेशन चरण | विफलता व्यवहार | रोलबैक |\n| --- | --- | --- |\n| `bun install` विफल | stderr के साथ इंस्टॉल रद्द | N/A (अभी तक कोई स्टेट राइट नहीं) |\n| इंस्टॉल सफल, फिर मैनिफेस्ट/फीचर वैलिडेशन विफल | कमांड विफल | कोई अनइंस्टॉल रोलबैक नहीं; डिपेंडेंसी `node_modules`/`package.json` में बनी रह सकती है |\n| इंस्टॉल सफल, फिर लॉकफाइल राइट विफल | कमांड विफल | इंस्टॉल किए गए पैकेज का कोई रोलबैक नहीं |\n| `bun uninstall` सफल, लॉकफाइल राइट विफल | कमांड विफल | पैकेज हटाया गया, स्टेल रनटाइम स्थिति बनी रह सकती है |\n| `link` पुराना टार्गेट हटाता है फिर सिमलिंक निर्माण विफल | कमांड विफल | पिछले लिंक/डायरेक्टरी की कोई रिस्टोरेशन नहीं |\n\nसंचालन में, `doctor --fix` कुछ ड्रिफ्ट की मरम्मत कर सकता है (`bun install`, अनाथ कॉन्फिग क्लीनअप, अमान्य-फीचर क्लीनअप), लेकिन यह बेस्ट-एफर्ट है।\n\n## खराब/अनुपस्थित मैनिफेस्ट व्यवहार सारांश\n\n- अनुपस्थित `xcsh`/`pi` फील्ड:\n  - इंस्टॉल/लिस्ट: सहन किया जाता है (न्यूनतम मैनिफेस्ट)\n  - रनटाइम enabled-plugin डिस्कवरी: नॉन-प्लगइन के रूप में छोड़ा जाता है\n- इंस्टॉल स्पेक या `features --set/--enable` द्वारा संदर्भित अनुपस्थित फीचर: उपलब्ध फीचर सूची के साथ हार्ड एरर\n- अमान्य `plugin-overrides.json`: मैनेजर और लोडर दोनों पाथ में `{}` के फॉलबैक के साथ अनदेखा किया जाता है\n- मैनिफेस्ट द्वारा संदर्भित अनुपस्थित टूल/हुक/कमांड फाइल पाथ: रिज़ॉल्वर विस्तार के दौरान चुपचाप अनदेखा; केवल `doctor` द्वारा त्रुटि के रूप में चिह्नित\n\n## मोड अंतर और प्राथमिकता\n\n- `--dry-run` (install): सिंथेटिक इंस्टॉल परिणाम लौटाता है, कोई फ़ाइलसिस्टम/नेटवर्क/स्टेट राइट नहीं।\n- `--json`: केवल आउटपुट फॉर्मेटिंग, कोई व्यवहार परिवर्तन नहीं।\n- प्रोजेक्ट ओवरराइड हमेशा फीचर/सेटिंग्स दृश्य के लिए ग्लोबल लॉकफाइल पर प्राथमिकता लेते हैं।\n- प्रभावी सक्षमता `runtimeEnabled && !projectDisabled` है।\n\n## कार्यान्वयन फाइलें\n\n- [`src/commands/plugin.ts`](../../packages/coding-agent/src/commands/plugin.ts) — CLI कमांड घोषणा और फ्लैग मैपिंग\n- [`src/cli/plugin-cli.ts`](../../packages/coding-agent/src/cli/plugin-cli.ts) — एक्शन डिस्पैच, उपयोगकर्ता-सामना कमांड हैंडलर\n- [`src/extensibility/plugins/manager.ts`](../../packages/coding-agent/src/extensibility/plugins/manager.ts) — सक्रिय install/remove/list/link/state/doctor कार्यान्वयन\n- [`src/extensibility/plugins/installer.ts`](../../packages/coding-agent/src/extensibility/plugins/installer.ts) — लेगेसी इंस्टॉलर हेल्पर और अतिरिक्त लिंक सुरक्षा जाँच\n- [`src/extensibility/plugins/loader.ts`](../../packages/coding-agent/src/extensibility/plugins/loader.ts) — enabled-plugin डिस्कवरी और टूल/हुक/कमांड पाथ रिज़ॉल्यूशन\n- [`src/extensibility/plugins/parser.ts`](../../packages/coding-agent/src/extensibility/plugins/parser.ts) — इंस्टॉल स्पेक और पैकेज-नाम पार्सिंग हेल्पर\n- [`src/extensibility/plugins/types.ts`](../../packages/coding-agent/src/extensibility/plugins/types.ts) — मैनिफेस्ट/रनटाइम/ओवरराइड टाइप कॉन्ट्रैक्ट\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts) — प्लगइन-प्रदत्त टूल मॉड्यूल के लिए रनटाइम वायरिंग\n",
	"hi/extensions/rulebook-matching-pipeline.md": "---\ntitle: रूलबुक मैचिंग पाइपलाइन\ndescription: >-\n  एजेंट सेशन के लिए संदर्भ-विशिष्ट निर्देश सेट चुनने और लागू करने हेतु रूलबुक\n  मैचिंग पाइपलाइन।\nsidebar:\n  order: 6\n  label: रूलबुक मैचिंग\ni18n:\n  sourceHash: a16a9c565053\n  translator: machine\n---\n\n# रूलबुक मैचिंग पाइपलाइन\n\nयह दस्तावेज़ बताता है कि coding-agent किस प्रकार समर्थित config फॉर्मेट से नियमों की खोज करता है, उन्हें एकल `Rule` आकार में सामान्यीकृत करता है, प्राथमिकता संघर्षों को हल करता है, और परिणाम को निम्नलिखित में विभाजित करता है:\n\n- **रूलबुक नियम** (सिस्टम प्रॉम्प्ट + `rule://` URL के माध्यम से मॉडल को उपलब्ध)\n- **TTSR नियम** (time-travel stream interruption नियम)\n\nयह वर्तमान कार्यान्वयन को दर्शाता है, जिसमें आंशिक सिमेंटिक्स और मेटाडेटा शामिल है जो पार्स तो किया जाता है लेकिन लागू नहीं होता।\n\n## कार्यान्वयन फ़ाइलें\n\n- [`../src/capability/rule.ts`](../../packages/coding-agent/src/capability/rule.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/discovery/index.ts`](../../packages/coding-agent/src/discovery/index.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/cursor.ts`](../../packages/coding-agent/src/discovery/cursor.ts)\n- [`../src/discovery/windsurf.ts`](../../packages/coding-agent/src/discovery/windsurf.ts)\n- [`../src/discovery/cline.ts`](../../packages/coding-agent/src/discovery/cline.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/system-prompt.ts`](../../packages/coding-agent/src/system-prompt.ts)\n- [`../src/internal-urls/rule-protocol.ts`](../../packages/coding-agent/src/internal-urls/rule-protocol.ts)\n- [`../src/utils/frontmatter.ts`](../../packages/coding-agent/src/utils/frontmatter.ts)\n\n## 1. कैनोनिकल नियम आकार\n\nसभी प्रोवाइडर स्रोत फ़ाइलों को `Rule` में सामान्यीकृत करते हैं:\n\n```ts\ninterface Rule {\n  name: string;\n  path: string;\n  content: string;\n  globs?: string[];\n  alwaysApply?: boolean;\n  description?: string;\n  ttsrTrigger?: string;\n  _source: SourceMeta;\n}\n```\n\nCapability पहचान `rule.name` है (`ruleCapability.key = rule => rule.name`)।\n\nपरिणाम: प्राथमिकता और डिडुप्लिकेशन **केवल नाम-आधारित** हैं। एक ही `name` वाली दो अलग-अलग फ़ाइलें एक ही तार्किक नियम मानी जाती हैं।\n\n## 2. डिस्कवरी स्रोत और सामान्यीकरण\n\n`src/discovery/index.ts` प्रोवाइडर को स्वतः पंजीकृत करता है। `rules` के लिए वर्तमान प्रोवाइडर हैं:\n\n- `native` (प्राथमिकता `100`)\n- `cursor` (प्राथमिकता `50`)\n- `windsurf` (प्राथमिकता `50`)\n- `cline` (प्राथमिकता `40`)\n\n### Native प्रोवाइडर (`builtin.ts`)\n\n`.xcsh` नियमों को निम्न से लोड करता है:\n\n- प्रोजेक्ट: `<cwd>/.xcsh/rules/*.{md,mdc}`\n- उपयोगकर्ता: `~/.xcsh/agent/rules/*.{md,mdc}`\n\nसामान्यीकरण:\n\n- `name` = `.md`/`.mdc` के बिना फ़ाइलनाम\n- frontmatter `parseFrontmatter` के माध्यम से पार्स किया गया\n- `content` = बॉडी (frontmatter हटाया गया)\n- `globs`, `alwaysApply`, `description`, `ttsr_trigger` सीधे मैप किए गए\n\nमहत्वपूर्ण चेतावनी: इस प्रोवाइडर में `globs` को `string[] | undefined` के रूप में कास्ट किया जाता है, लेकिन कोई element filtering नहीं होती।\n\n### Cursor प्रोवाइडर (`cursor.ts`)\n\nनिम्न से लोड करता है:\n\n- उपयोगकर्ता: `~/.cursor/rules/*.{mdc,md}`\n- प्रोजेक्ट: `<cwd>/.cursor/rules/*.{mdc,md}`\n\nसामान्यीकरण (`transformMDCRule`):\n\n- `description`: केवल तभी रखा जाता है जब string हो\n- `alwaysApply`: केवल `true` संरक्षित होता है (`false` `undefined` बन जाता है)\n- `globs`: array (केवल string elements) या single string स्वीकार करता है\n- `ttsr_trigger`: केवल string\n- `name` फ़ाइलनाम से बिना extension के\n\n### Windsurf प्रोवाइडर (`windsurf.ts`)\n\nनिम्न से लोड करता है:\n\n- उपयोगकर्ता: `~/.codeium/windsurf/memories/global_rules.md` (निश्चित नियम नाम `global_rules`)\n- प्रोजेक्ट: `<cwd>/.windsurf/rules/*.md`\n\nसामान्यीकरण:\n\n- `globs`: array-of-string या single string\n- `alwaysApply`, `description` frontmatter से कास्ट किया गया\n- `ttsr_trigger`: केवल string\n- `name` प्रोजेक्ट नियमों के लिए फ़ाइलनाम से\n\n### Cline प्रोवाइडर (`cline.ts`)\n\nनिकटतम `.clinerules` के लिए `cwd` से ऊपर की ओर खोज करता है:\n\n- यदि directory है: उसके अंदर `*.md` लोड करता है\n- यदि file है: एकल फ़ाइल को `clinerules` नाम के नियम के रूप में लोड करता है\n\nसामान्यीकरण:\n\n- `globs`: array-of-string या single string\n- `alwaysApply`: केवल यदि boolean हो\n- `description`: केवल string\n- `ttsr_trigger`: केवल string\n\n## 3. Frontmatter पार्सिंग व्यवहार और अस्पष्टता\n\nसभी प्रोवाइडर `parseFrontmatter` (`utils/frontmatter.ts`) का उपयोग इन सिमेंटिक्स के साथ करते हैं:\n\n1. Frontmatter तभी पार्स होता है जब content `---` से शुरू हो और एक closing `\\n---` हो।\n2. Frontmatter निष्कर्षण के बाद बॉडी trimmed होती है।\n3. यदि YAML पार्स विफल हो:\n   - चेतावनी लॉग की जाती है,\n   - पार्सर simple `key: value` लाइन पार्सिंग (`^(\\w+):\\s*(.*)$`) पर fallback करता है।\n\nअस्पष्टता के परिणाम:\n\n- Fallback पार्सर arrays, nested objects, quoting rules, या हाइफ़नेटेड keys को समर्थन नहीं करता।\n- Fallback मान strings बन जाते हैं (उदाहरण के लिए `alwaysApply: true` string `\"true\"` बन जाता है), इसलिए boolean/string types की आवश्यकता वाले प्रोवाइडर मेटाडेटा छोड़ सकते हैं।\n- `ttsr_trigger` fallback में काम करता है (underscore key); `thinking-level` जैसी keys नहीं करतीं।\n- Valid frontmatter के बिना फ़ाइलें अभी भी खाली मेटाडेटा और पूर्ण content body के साथ नियमों के रूप में लोड होती हैं।\n\n## 4. प्रोवाइडर प्राथमिकता और डिडुप्लिकेशन\n\n`loadCapability(\"rules\")` (`capability/index.ts`) प्रोवाइडर आउटपुट को merge करता है और फिर `rule.name` द्वारा deduplicate करता है।\n\n### प्राथमिकता मॉडल\n\n- प्रोवाइडर को प्राथमिकता अवरोही क्रम में क्रमित किया जाता है।\n- समान प्राथमिकता पर पंजीकरण क्रम रखा जाता है (`discovery/index.ts` से `cursor` `windsurf` से पहले)।\n- Dedup first-wins है: पहले आने वाला नियम नाम रखा जाता है; बाद के समान-नाम वाले items `all` में `_shadowed` चिह्नित होते हैं और `items` से बाहर रखे जाते हैं।\n\nवर्तमान में प्रभावी नियम प्रोवाइडर क्रम है:\n\n1. `native` (100)\n2. `cursor` (50)\n3. `windsurf` (50)\n4. `cline` (40)\n\n### Intra-provider ordering चेतावनी\n\nकिसी प्रोवाइडर के भीतर, item क्रम `loadFilesFromDir` glob परिणाम क्रम और स्पष्ट push क्रम से आता है। यह सामान्य उपयोग के लिए पर्याप्त deterministic है लेकिन code में स्पष्ट रूप से sorted नहीं है।\n\nउल्लेखनीय source-order अंतर:\n\n- `native` प्रोजेक्ट फिर user config dirs जोड़ता है।\n- `cursor` user फिर प्रोजेक्ट परिणाम जोड़ता है।\n- `windsurf` user `global_rules` पहले जोड़ता है, फिर प्रोजेक्ट नियम।\n- `cline` केवल निकटतम `.clinerules` स्रोत लोड करता है।\n\n## 5. Rulebook, Always-Apply, और TTSR buckets में विभाजन\n\n`createAgentSession` (`sdk.ts`) में नियम खोज के बाद:\n\n1. सभी खोजे गए नियमों को स्कैन किया जाता है।\n2. `condition` (frontmatter key; `ttsr_trigger` / `ttsrTrigger` fallback के रूप में स्वीकृत) वाले नियमों को `TtsrManager` में पंजीकृत किया जाता है।\n3. एक अलग `rulebookRules` सूची इस predicate के साथ बनाई जाती है:\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && !rule.alwaysApply && !!rule.description\n```\n\n4. एक `alwaysApplyRules` सूची बनाई जाती है:\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && rule.alwaysApply === true\n```\n\n### Bucket व्यवहार\n\n- **TTSR bucket**: `condition` वाला कोई भी नियम (description आवश्यक नहीं)। अन्य buckets पर प्राथमिकता लेता है।\n- **Always-apply bucket**: `alwaysApply === true`, TTSR नहीं। पूर्ण content सिस्टम प्रॉम्प्ट में inject होती है। `rule://` के माध्यम से resolve करने योग्य।\n- **Rulebook bucket**: description होनी चाहिए, TTSR नहीं होना चाहिए, `alwaysApply` नहीं होना चाहिए। सिस्टम प्रॉम्प्ट में name+description द्वारा सूचीबद्ध; content `rule://` के माध्यम से मांग पर पढ़ी जाती है।\n- `condition` और `alwaysApply` दोनों वाला नियम केवल TTSR में जाता है (TTSR प्राथमिकता लेता है)।\n- `alwaysApply` और `description` दोनों वाला नियम केवल always-apply में जाता है (rulebook में नहीं)।\n\n## 6. मेटाडेटा runtime surfaces को कैसे प्रभावित करता है\n\n### `description`\n\n- Rulebook में शामिल करने के लिए आवश्यक।\n- सिस्टम प्रॉम्प्ट `<rules>` ब्लॉक में rendered।\n- Description न होने का अर्थ है नियम `rule://` के माध्यम से उपलब्ध नहीं है और सिस्टम प्रॉम्प्ट नियमों में सूचीबद्ध नहीं है।\n\n### `globs`\n\n- `Rule` पर carry through होता है।\n- सिस्टम प्रॉम्प्ट rules block में `<glob>...</glob>` entries के रूप में rendered।\n- Rules UI state (`extensions` mode list) में exposed।\n- **इस पाइपलाइन में स्वचालित मैचिंग के लिए लागू नहीं।** कोई runtime glob matcher नहीं है जो वर्तमान file/tool target द्वारा नियमों का चयन करे।\n\n### `alwaysApply`\n\n- प्रोवाइडर द्वारा पार्स और संरक्षित किया जाता है।\n- UI display में उपयोग (`\"always\"` trigger label extensions state manager में)।\n- `rulebookRules` से exclusion condition के रूप में उपयोग।\n- **पूर्ण नियम content सिस्टम प्रॉम्प्ट में स्वतः inject होती है** (rulebook rules section से पहले)।\n- नियम को re-reading के लिए `rule://<name>` के माध्यम से भी address किया जा सकता है।\n\n### `ttsr_trigger`\n\n- `rule.ttsrTrigger` पर मैप किया गया।\n- यदि उपस्थित हो, नियम को TTSR manager की ओर route किया जाता है, rulebook में नहीं।\n\n## 7. सिस्टम प्रॉम्प्ट inclusion path\n\n`buildSystemPromptInternal` को `rules` (rulebook) और `alwaysApplyRules` दोनों प्राप्त होते हैं।\n\nAlways-apply नियम पहले rendered होते हैं, उनकी raw content सीधे प्रॉम्प्ट में inject होती है।\n\nRulebook नियम एक `# Rules` section में rendered होते हैं:\n\n- `Read rule://<name> when working in matching domain`\n- प्रत्येक नियम का `name`, `description`, और वैकल्पिक `<glob>` सूची\n\nयह advisory/contextual है: प्रॉम्प्ट टेक्स्ट मॉडल से लागू नियमों को पढ़ने के लिए कहता है, लेकिन code glob applicability को enforce नहीं करता।\n\n## 8. `rule://` internal URL व्यवहार\n\n`RuleProtocolHandler` निम्न के साथ पंजीकृत है:\n\n```ts\nnew RuleProtocolHandler({ getRules: () => [...rulebookRules, ...alwaysApplyRules] })\n```\n\nनिहितार्थ:\n\n- `rule://<name>` **rulebookRules** और **alwaysApplyRules** दोनों के विरुद्ध resolve करता है।\n- केवल TTSR वाले नियम और बिना description और बिना `alwaysApply` के नियम `rule://` के माध्यम से address करने योग्य नहीं हैं।\n- Resolution exact name match है।\n- अज्ञात नाम उपलब्ध नियम नामों की सूची देते हुए error लौटाते हैं।\n- लौटाई गई content raw `rule.content` है (frontmatter हटाया गया), content type `text/markdown`।\n\n## 9. ज्ञात आंशिक / non-enforced सिमेंटिक्स\n\n1. प्रोवाइडर descriptions legacy फ़ाइलों (`.cursorrules`, `.windsurfrules`) का उल्लेख करती हैं, लेकिन वर्तमान loader code paths वास्तव में उन फ़ाइलों को नहीं पढ़ते।\n2. `globs` मेटाडेटा prompt/UI को surfaced है लेकिन rule selection logic द्वारा enforce नहीं किया जाता।\n3. `rule://` के लिए नियम चयन में rulebook और always-apply नियम शामिल हैं, लेकिन केवल TTSR नियम नहीं।\n4. Discovery warnings (`loadCapability(\"rules\").warnings`) उत्पन्न होती हैं लेकिन `createAgentSession` वर्तमान में इस path में उन्हें surface/log नहीं करता।\n",
	"hi/extensions/skills.md": "---\ntitle: कौशल\ndescription: >-\n  कोडिंग एजेंट में विशेष क्षमताओं को पंजीकृत करने, खोजने और आह्वान करने के लिए\n  कौशल प्रणाली।\nsidebar:\n  order: 3\n  label: कौशल\ni18n:\n  sourceHash: 3e062cc13851\n  translator: machine\n---\n\n# कौशल\n\nकौशल (Skills) फ़ाइल-आधारित क्षमता पैक हैं जो स्टार्टअप पर खोजे जाते हैं और मॉडल को निम्नलिखित रूप में उपलब्ध कराए जाते हैं:\n\n- सिस्टम प्रॉम्प्ट में हल्का मेटाडेटा (नाम + विवरण)\n- `read skill://...` के माध्यम से मांग पर सामग्री\n- वैकल्पिक इंटरैक्टिव `/skill:<name>` कमांड\n\nयह दस्तावेज़ `src/extensibility/skills.ts`, `src/discovery/builtin.ts`, `src/internal-urls/skill-protocol.ts`, और `src/discovery/agents-md.ts` में वर्तमान रनटाइम व्यवहार को कवर करता है।\n\n## इस कोडबेस में एक कौशल क्या है\n\nएक खोजा गया कौशल इस प्रकार प्रस्तुत किया जाता है:\n\n- `name`\n- `description`\n- `filePath` (`SKILL.md` पथ)\n- `baseDir` (कौशल डायरेक्टरी)\n- स्रोत मेटाडेटा (`provider`, `level`, पथ)\n\nरनटाइम को वैधता के लिए केवल `name` और `path` की आवश्यकता होती है। व्यवहार में, मिलान गुणवत्ता इस बात पर निर्भर करती है कि `description` सार्थक हो।\n\n## आवश्यक लेआउट और SKILL.md अपेक्षाएं\n\n### डायरेक्टरी लेआउट\n\nप्रोवाइडर-आधारित खोज (native/Claude/Codex/Agents/plugin प्रोवाइडर) के लिए, कौशल **`skills/` के एक स्तर नीचे** खोजे जाते हैं:\n\n- `<skills-root>/<skill-name>/SKILL.md`\n\n`<skills-root>/group/<skill>/SKILL.md` जैसे नेस्टेड पैटर्न प्रोवाइडर लोडर द्वारा खोजे नहीं जाते।\n\n`skills.customDirectories` के लिए, स्कैनिंग उसी गैर-पुनरावर्ती लेआउट (`*/SKILL.md`) का उपयोग करती है।\n\n```text\nProvider-discovered layout (non-recursive under skills/):\n\n<root>/skills/\n  ├─ postgres/\n  │   └─ SKILL.md      ✅ discovered\n  ├─ pdf/\n  │   └─ SKILL.md      ✅ discovered\n  └─ team/\n      └─ internal/\n          └─ SKILL.md  ❌ not discovered by provider loaders\n\nCustom-directory scanning is also non-recursive, so nested paths are ignored unless you point `customDirectories` at that nested parent.\n```\n\n### `SKILL.md` फ्रंटमैटर\n\nकौशल प्रकार पर समर्थित फ्रंटमैटर फ़ील्ड:\n\n- `name?: string`\n- `description?: string`\n- `globs?: string[]`\n- `alwaysApply?: boolean`\n- अतिरिक्त कुंजियाँ अज्ञात मेटाडेटा के रूप में संरक्षित की जाती हैं\n\nवर्तमान रनटाइम व्यवहार:\n\n- `name` डिफ़ॉल्ट रूप से कौशल डायरेक्टरी का नाम होता है\n- `description` निम्नलिखित के लिए आवश्यक है:\n  - native `.xcsh` प्रोवाइडर कौशल खोज (`requireDescription: true`)\n  - `src/discovery/helpers.ts` में `scanSkillsFromDir` के माध्यम से `skills.customDirectories` स्कैन (गैर-पुनरावर्ती)\n- गैर-native प्रोवाइडर विवरण के बिना कौशल लोड कर सकते हैं\n\n## खोज पाइपलाइन\n\n`src/extensibility/skills.ts` में `discoverSkills()` दो पास करता है:\n\n1. **क्षमता प्रोवाइडर** `loadCapability(\"skills\")` के माध्यम से\n2. **कस्टम डायरेक्टरी** `scanSkillsFromDir(..., { requireDescription: true })` के माध्यम से (एक-स्तरीय डायरेक्टरी गणना)\n\nयदि `skills.enabled` `false` है, तो खोज कोई कौशल नहीं लौटाती।\n\n### अंतर्निहित कौशल प्रोवाइडर और प्राथमिकता\n\nप्रोवाइडर क्रम पहले प्राथमिकता-आधारित है (उच्चतर जीतता है), फिर बराबरी के लिए पंजीकरण क्रम।\n\nवर्तमान में पंजीकृत कौशल प्रोवाइडर:\n\n1. `native` (प्राथमिकता 100) — `src/discovery/builtin.ts` के माध्यम से `.xcsh` user/project कौशल\n2. `claude` (प्राथमिकता 80)\n3. प्राथमिकता 70 समूह (पंजीकरण क्रम में):\n   - `claude-plugins`\n   - `agents`\n   - `codex`\n\nडिडुप कुंजी कौशल नाम है। किसी दिए गए नाम वाला पहला आइटम जीतता है।\n\n### स्रोत टॉगल और फ़िल्टरिंग\n\n`discoverSkills()` ये नियंत्रण लागू करता है:\n\n- स्रोत टॉगल: `enableCodexUser`, `enableClaudeUser`, `enableClaudeProject`, `enablePiUser`, `enablePiProject`\n- कौशल नाम पर glob फ़िल्टर:\n  - `ignoredSkills` (बाहर करें)\n  - `includeSkills` (allowlist शामिल करें; खाली का अर्थ है सब शामिल करें)\n\nफ़िल्टर क्रम है:\n\n1. स्रोत सक्षम\n2. अनदेखा नहीं किया गया\n3. शामिल (यदि include सूची मौजूद है)\n\ncodex/claude/native के अलावा अन्य प्रोवाइडर के लिए (उदाहरण के लिए `agents`, `claude-plugins`), सक्षमता वर्तमान में इस पर वापस जाती है: सक्षम यदि **कोई भी** अंतर्निहित स्रोत टॉगल सक्षम है।\n\n### टकराव और डुप्लिकेट प्रबंधन\n\n- क्षमता डिडुप पहले से ही प्रति नाम पहला कौशल रखती है (उच्चतम-प्राथमिकता प्रोवाइडर)\n- `extensibility/skills.ts` अतिरिक्त रूप से:\n  - `realpath` द्वारा समान फ़ाइलों को डी-डुप्लिकेट करता है (symlink-safe)\n  - बाद में कौशल नाम टकराने पर टकराव चेतावनियाँ उत्सर्जित करता है\n  - `scanSkillsFromDir` पर एक पतले अडैप्टर के रूप में सुविधा `discoverSkillsFromDir({ dir, source })` API रखता है\n- कस्टम-डायरेक्टरी कौशल प्रोवाइडर कौशल के बाद मर्ज किए जाते हैं और उसी टकराव व्यवहार का पालन करते हैं\n\n## रनटाइम उपयोग व्यवहार\n\n### सिस्टम प्रॉम्प्ट एक्सपोज़र\n\nसिस्टम प्रॉम्प्ट निर्माण (`src/system-prompt.ts`) खोजे गए कौशल का उपयोग इस प्रकार करता है:\n\n- यदि `read` टूल उपलब्ध है:\n  - प्रॉम्प्ट में खोजे गए कौशल सूची शामिल करें\n- अन्यथा:\n  - खोजी गई सूची छोड़ें\n\nTask tool subagents को सामान्य session निर्माण के माध्यम से session की खोजी गई/प्रदत्त कौशल सूची प्राप्त होती है; कोई per-task कौशल pinning ओवरराइड नहीं है।\n\n### इंटरैक्टिव `/skill:<name>` कमांड\n\nयदि `skills.enableSkillCommands` true है, तो इंटरैक्टिव मोड प्रत्येक खोजे गए कौशल के लिए एक slash कमांड पंजीकृत करता है।\n\n`/skill:<name> [args]` व्यवहार:\n\n- `filePath` से सीधे कौशल फ़ाइल पढ़ता है\n- फ्रंटमैटर हटाता है\n- कौशल बॉडी को follow-up कस्टम संदेश के रूप में इंजेक्ट करता है\n- मेटाडेटा जोड़ता है (`Skill: <path>`, वैकल्पिक `User: <args>`)\n\n## `skill://` URL व्यवहार\n\n`src/internal-urls/skill-protocol.ts` समर्थन करता है:\n\n- `skill://<name>` → उस कौशल के `SKILL.md` पर resolve होता है\n- `skill://<name>/<relative-path>` → उस कौशल डायरेक्टरी के अंदर resolve होता है\n\n```text\nskill:// URL resolution\n\nskill://pdf\n  -> <pdf-base>/SKILL.md\n\nskill://pdf/references/tables.md\n  -> <pdf-base>/references/tables.md\n\nGuards:\n- reject absolute paths\n- reject `..` traversal\n- reject any resolved path escaping <pdf-base>\n```\n\nResolution विवरण:\n\n- कौशल नाम बिल्कुल मेल खाना चाहिए\n- सापेक्ष पथ URL-decoded हैं\n- absolute पथ अस्वीकृत हैं\n- पथ traversal (`..`) अस्वीकृत है\n- resolved पथ `baseDir` के भीतर ही रहना चाहिए\n- गायब फ़ाइलें एक स्पष्ट `File not found` त्रुटि लौटाती हैं\n\nकंटेंट प्रकार:\n\n- `.md` => `text/markdown`\n- बाकी सब => `text/plain`\n\nगायब assets के लिए कोई fallback खोज नहीं की जाती।\n\n## कौशल बनाम XCSH.md, कमांड, उपकरण, hooks\n\n### कौशल बनाम XCSH.md\n\n- **कौशल**: नामित, वैकल्पिक क्षमता पैक जो कार्य संदर्भ द्वारा चुने जाते हैं या स्पष्ट रूप से अनुरोध किए जाते हैं\n- **XCSH.md/context फ़ाइलें**: स्थायी निर्देश फ़ाइलें जो context-file क्षमता के रूप में लोड होती हैं और level/depth नियमों द्वारा मर्ज की जाती हैं\n\n`src/discovery/agents-md.ts` विशेष रूप से standalone `XCSH.md` फ़ाइलें खोजने के लिए `cwd` से पूर्वज डायरेक्टरी में चलता है (depth 20 तक), hidden-directory segments को छोड़कर।\n\n### कौशल बनाम slash कमांड\n\n- **कौशल**: मॉडल-पठनीय ज्ञान/वर्कफ़्लो सामग्री\n- **Slash कमांड**: उपयोगकर्ता-आह्वानित कमांड एंट्री पॉइंट\n- `/skill:<name>` एक सुविधा wrapper है जो कौशल टेक्स्ट इंजेक्ट करता है; यह कौशल खोज semantics नहीं बदलता\n\n### कौशल बनाम कस्टम उपकरण\n\n- **कौशल**: प्रॉम्प्ट संदर्भ और `read` के माध्यम से लोड की गई दस्तावेज़ीकरण/वर्कफ़्लो सामग्री\n- **कस्टम उपकरण**: मॉडल द्वारा schemas और रनटाइम side effects के साथ callable executable tool API\n\n### कौशल बनाम hooks\n\n- **कौशल**: निष्क्रिय सामग्री\n- **Hooks**: event-driven रनटाइम interceptors जो निष्पादन के दौरान व्यवहार को block/modify कर सकते हैं\n\n## खोज तर्क से जुड़ा व्यावहारिक authoring मार्गदर्शन\n\n- प्रत्येक कौशल को अपनी डायरेक्टरी में रखें: `<skills-root>/<skill-name>/SKILL.md`\n- हमेशा स्पष्ट `name` और `description` फ्रंटमैटर शामिल करें\n- संदर्भित assets को उसी कौशल डायरेक्टरी के अंतर्गत रखें और `skill://<name>/...` से एक्सेस करें\n- नेस्टेड taxonomy (`team/domain/skill`) के लिए, `skills.customDirectories` को नेस्टेड parent डायरेक्टरी पर point करें; स्कैनिंग स्वयं गैर-पुनरावर्ती रहती है\n- स्रोतों में डुप्लिकेट कौशल नाम से बचें; प्रोवाइडर प्राथमिकता द्वारा पहला मेल जीतता है\n",
	"hi/index.md": "---\ntitle: xcsh दस्तावेज़ीकरण\ndescription: >-\n  TypeScript कोडिंग एजेंट और Rust नेटिव लेयर के साथ AI-संचालित डेवलपमेंट CLI, जो\n  लंबे समय तक चलने वाले सेशन, MCP सहायता और प्लेटफ़ॉर्म पैकेजिंग के लिए तैयार\n  है।\nsidebar:\n  order: 0\n  label: अवलोकन\ni18n:\n  sourceHash: b9288f42bf46\n  translator: machine\n---\n\nxcsh एक AI-संचालित डेवलपमेंट CLI है जिसमें एक TypeScript कोडिंग एजेंट और एक\nRust नेटिव लेयर (`pi-natives`) शामिल है। यह ओपन-सोर्स\n[`badlogic/pi-mono`](https://github.com/badlogic/pi-mono) लाइन को एक\nहार्डन्ड रनटाइम, ट्री नेविगेशन और कॉम्पैक्शन के साथ लंबे समय तक चलने वाले सेशन,\nएक Python IPython उपकरण, पूर्ण MCP सहायता, एक स्किल्स सिस्टम, और Linux, macOS\nतथा Windows को लक्षित प्लेटफ़ॉर्म पैकेजिंग के साथ विस्तारित करता है।\n\n## शुरू कहाँ से करें\n\n- **[F5 XC कंटेक्स्ट](/runtime-tools/context-command)** — F5 Distributed Cloud\n  टेनेंट से कनेक्ट करें। कंटेक्स्ट बनाएँ, उनके बीच स्विच करें, नेमस्पेस और क्रेडेंशियल प्रबंधित करें।\n- **कॉन्फ़िगरेशन** — xcsh किस प्रकार कॉन्फ़िगरेशन को खोजता, हल करता और लेयर करता है।\n- **रनटाइम और उपकरण** — bash / नोटबुक / रिज़ॉल्व उपकरण रनटाइम और\n  स्लैश-कमांड इंटरफ़ेस।\n- **सेशन** — अपेंड-ओनली एंट्री लॉग, ट्री नेविगेशन, कॉम्पैक्शन, और\n  ऑटोनॉमस मेमोरी सिस्टम।\n- **नेटिव्स (Rust)** — `pi-natives` N-API एडऑन की आर्किटेक्चर जो\n  शेल / PTY / मीडिया / सर्च को संचालित करती है।\n- **MCP** — कॉन्फ़िगरेशन, प्रोटोकॉल इंटर्नल्स, रनटाइम लाइफ़साइकिल, और सर्वर तथा उपकरण कैसे बनाएँ।\n- **एक्सटेंशन, स्किल्स और प्लगइन** — ऑथरिंग, लोडिंग, मिलान नियम, मार्केटप्लेस और प्लगइन इंस्टॉलर।\n- **प्रोवाइडर और मॉडल** — मॉडल कॉन्फ़िगरेशन, स्ट्रीमिंग इंटर्नल्स, और Python / IPython रनटाइम।\n- **TUI** — थीमिंग, `/tree` कमांड, और एक्सटेंशन तथा कस्टम उपकरण के लिए इंटीग्रेशन हुक।\n\n## यह दस्तावेज़ सेट कैसे व्यवस्थित है\n\nसाइडबार में प्रत्येक शीर्ष-स्तरीय समूह एजेंट के किसी उप-सिस्टम से संबंधित है। किसी\nसमूह के भीतर, पृष्ठ \"अवलोकन\" से \"इंटर्नल्स\" तक क्रम में होते हैं, ताकि आप जब\nकिसी कार्य के लिए पर्याप्त संदर्भ प्राप्त हो जाए तो पढ़ना बंद कर सकें।\n",
	"hi/mcp/mcp-config.md": "---\ntitle: MCP कॉन्फ़िगरेशन\ndescription: 'कोडिंग एजेंट रनटाइम के लिए MCP सर्वर कॉन्फ़िगरेशन, वैलिडेशन और प्रबंधन।'\nsidebar:\n  order: 1\n  label: कॉन्फ़िगरेशन\ni18n:\n  sourceHash: ef8b49458ce9\n  translator: machine\n---\n\n# OMP में MCP कॉन्फ़िगरेशन\n\nयह गाइड बताती है कि OMP कोडिंग एजेंट के लिए MCP सर्वर कैसे जोड़ें, संपादित करें और वैलिडेट करें।\n\nकोड में सत्य का स्रोत:\n\n- रनटाइम कॉन्फ़िग टाइप्स: `packages/coding-agent/src/mcp/types.ts`\n- कॉन्फ़िग राइटर: `packages/coding-agent/src/mcp/config-writer.ts`\n- लोडर + वैलिडेशन: `packages/coding-agent/src/mcp/config.ts`\n- स्टैंडअलोन `mcp.json` डिस्कवरी: `packages/coding-agent/src/discovery/mcp-json.ts`\n- स्कीमा: `packages/coding-agent/src/config/mcp-schema.json`\n\n## पसंदीदा कॉन्फ़िग स्थान\n\nOMP कई टूल्स (`.claude/`, `.cursor/`, `.vscode/`, `opencode.json`, और अन्य) से MCP सर्वर खोज सकता है, लेकिन OMP-नेटिव कॉन्फ़िगरेशन के लिए आपको आमतौर पर इन फ़ाइलों में से किसी एक का उपयोग करना चाहिए:\n\n- प्रोजेक्ट: `.xcsh/mcp.json`\n- उपयोगकर्ता: `~/.xcsh/mcp.json`\n\nOMP प्रोजेक्ट रूट में फ़ॉलबैक स्टैंडअलोन फ़ाइलें भी स्वीकार करता है:\n\n- `mcp.json`\n- `.mcp.json`\n\n`.xcsh/mcp.json` का उपयोग करें जब आप चाहते हैं कि OMP कॉन्फ़िगरेशन का स्वामी हो। रूट `mcp.json` / `.mcp.json` का उपयोग केवल तब करें जब आप एक पोर्टेबल फ़ॉलबैक फ़ाइल चाहते हैं जिसे अन्य MCP क्लाइंट भी पढ़ सकें।\n\n## स्कीमा संदर्भ जोड़ें\n\nएडिटर ऑटोकम्प्लीट और वैलिडेशन के लिए फ़ाइल के शीर्ष पर यह लाइन जोड़ें:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {}\n}\n```\n\nOMP अब इसे स्वचालित रूप से लिखता है जब `/mcp add`, `/mcp enable`, `/mcp disable`, `/mcp reauth`, या अन्य कॉन्फ़िग-लेखन प्रवाह OMP-प्रबंधित MCP फ़ाइल बनाते या अपडेट करते हैं।\n\n## फ़ाइल संरचना\n\nOMP इस शीर्ष-स्तरीय संरचना का समर्थन करता है:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"server-name\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"some-mcp-server\"]\n    }\n  },\n  \"disabledServers\": [\"server-name\"]\n}\n```\n\nशीर्ष-स्तरीय कुंजियाँ:\n\n- `$schema` — टूलिंग के लिए वैकल्पिक JSON Schema URL\n- `mcpServers` — सर्वर नाम से सर्वर कॉन्फ़िग का मैप\n- `disabledServers` — उपयोगकर्ता-स्तरीय डिनाइलिस्ट जो खोजे गए सर्वर को नाम से बंद करने के लिए उपयोग की जाती है\n\nसर्वर नाम `^[a-zA-Z0-9_.-]{1,100}$` से मेल खाने चाहिए।\n\n## समर्थित सर्वर फ़ील्ड\n\nप्रत्येक ट्रांसपोर्ट के लिए साझा फ़ील्ड:\n\n- `enabled?: boolean` — `false` होने पर इस सर्वर को छोड़ दें\n- `timeout?: number` — मिलीसेकंड में कनेक्शन टाइमआउट\n- `auth?: { ... }` — OMP द्वारा OAuth/API-key प्रवाह के लिए उपयोग किया जाने वाला प्रमाणीकरण मेटाडेटा\n- `oauth?: { ... }` — प्रमाणीकरण/पुनः प्रमाणीकरण के दौरान उपयोग की जाने वाली स्पष्ट OAuth क्लाइंट सेटिंग्स\n\n### `stdio` ट्रांसपोर्ट\n\nजब `type` छोड़ दिया जाता है तो `stdio` डिफ़ॉल्ट होता है।\n\nआवश्यक:\n\n- `command: string`\n\nवैकल्पिक:\n\n- `type?: \"stdio\"`\n- `args?: string[]`\n- `env?: Record<string, string>`\n- `cwd?: string`\n\nउदाहरण:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/Users/alice/projects\",\n        \"/Users/alice/Documents\"\n      ]\n    }\n  }\n}\n```\n\nयह आधिकारिक Filesystem MCP सर्वर पैकेज (`@modelcontextprotocol/server-filesystem`) का अनुसरण करता है।\n\n### `http` ट्रांसपोर्ट\n\nआवश्यक:\n\n- `type: \"http\"`\n- `url: string`\n\nवैकल्पिक:\n\n- `headers?: Record<string, string>`\n\nउदाहरण:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\nयह GitHub के होस्टेड GitHub MCP सर्वर एंडपॉइंट से मेल खाता है।\n\n### `sse` ट्रांसपोर्ट\n\nआवश्यक:\n\n- `type: \"sse\"`\n- `url: string`\n\nवैकल्पिक:\n\n- `headers?: Record<string, string>`\n\nउदाहरण:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"legacy-remote\": {\n      \"type\": \"sse\",\n      \"url\": \"https://example.com/mcp/sse\"\n    }\n  }\n}\n```\n\n`sse` अभी भी संगतता के लिए समर्थित है, लेकिन MCP विनिर्देश अब नए सर्वर के लिए Streamable HTTP (`type: \"http\"`) को प्राथमिकता देता है।\n\n## प्रमाणीकरण फ़ील्ड\n\nOMP दो प्रमाणीकरण-संबंधित ऑब्जेक्ट समझता है।\n\n### `auth`\n\n```json\n{\n  \"type\": \"oauth\" | \"apikey\",\n  \"credentialId\": \"optional-stored-credential-id\",\n  \"tokenUrl\": \"optional-token-endpoint\",\n  \"clientId\": \"optional-client-id\",\n  \"clientSecret\": \"optional-client-secret\"\n}\n```\n\nइसका उपयोग करें जब OMP को सर्वर के लिए क्रेडेंशियल्स को पुनर्स्थापित करने का तरीका याद रखना चाहिए।\n\n### `oauth`\n\n```json\n{\n  \"clientId\": \"...\",\n  \"clientSecret\": \"...\",\n  \"redirectUri\": \"...\",\n  \"callbackPort\": 3334,\n  \"callbackPath\": \"/oauth/callback\"\n}\n```\n\nइसका उपयोग करें जब MCP सर्वर को स्पष्ट OAuth क्लाइंट सेटिंग्स की आवश्यकता हो।\n\nSlack वर्तमान में इसका सबसे स्पष्ट उदाहरण है। Slack का MCP सर्वर `https://mcp.slack.com/mcp` पर होस्ट किया गया है, Streamable HTTP का उपयोग करता है, और आपके Slack ऐप के क्लाइंट क्रेडेंशियल्स के साथ गोपनीय OAuth की आवश्यकता होती है।\n\nउदाहरण:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\nSlack के दस्तावेज़ों से प्रासंगिक Slack एंडपॉइंट:\n\n- MCP एंडपॉइंट: `https://mcp.slack.com/mcp`\n- प्राधिकरण एंडपॉइंट: `https://slack.com/oauth/v2_user/authorize`\n- टोकन एंडपॉइंट: `https://slack.com/api/oauth.v2.user.access`\n\n## सामान्य कॉपी-पेस्ट उदाहरण\n\n### stdio के माध्यम से Filesystem सर्वर\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/absolute/path/one\",\n        \"/absolute/path/two\"\n      ]\n    }\n  }\n}\n```\n\n### HTTP के माध्यम से GitHub होस्टेड सर्वर\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\n### Docker के माध्यम से GitHub लोकल सर्वर\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"-i\",\n        \"--rm\",\n        \"-e\",\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\",\n        \"ghcr.io/github/github-mcp-server\"\n      ],\n      \"env\": {\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n      }\n    }\n  }\n}\n```\n\nयह GitHub की आधिकारिक लोकल Docker इमेज `ghcr.io/github/github-mcp-server` से मेल खाता है।\n\n### OAuth के माध्यम से Slack होस्टेड सर्वर\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\n## सीक्रेट्स और वेरिएबल रिज़ॉल्यूशन\n\nयह वह हिस्सा है जो आमतौर पर लोगों को भ्रमित करता है।\n\n### `.xcsh/mcp.json` और `~/.xcsh/mcp.json` में\n\nOMP किसी सर्वर को लॉन्च करने या HTTP अनुरोध करने से पहले, `env` और `headers` मानों को इस प्रकार हल करता है:\n\n1. यदि कोई मान `!` से शुरू होता है, तो OMP इसे शेल कमांड के रूप में चलाता है और ट्रिम किए गए stdout का उपयोग करता है।\n2. अन्यथा OMP पहले जाँचता है कि क्या मान किसी एनवायरनमेंट वेरिएबल नाम से मेल खाता है।\n3. यदि वह एनवायरनमेंट वेरिएबल सेट नहीं है, तो OMP स्ट्रिंग का शाब्दिक उपयोग करता है।\n\nउदाहरण:\n\n```json\n{\n  \"env\": {\n    \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n  },\n  \"headers\": {\n    \"X-MCP-Insiders\": \"true\"\n  }\n}\n```\n\nइसका मतलब है कि यह लोकल सीक्रेट्स के लिए मान्य और सुविधाजनक है:\n\n- `\"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"` → वर्तमान शेल एनवायरनमेंट से कॉपी करें\n- `\"Authorization\": \"Bearer hardcoded-token\"` → शाब्दिक मान का उपयोग करें\n- `\"Authorization\": \"!printf 'Bearer %s' \\\"$GITHUB_TOKEN\\\"\"` → कमांड से हेडर बनाएं\n\n### रूट `mcp.json` और `.mcp.json` में\n\nस्टैंडअलोन फ़ॉलबैक लोडर भी डिस्कवरी के दौरान स्ट्रिंग्स के अंदर `${VAR}` और `${VAR:-default}` का विस्तार करता है।\n\nउदाहरण:\n\n```json\n{\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\",\n      \"headers\": {\n        \"Authorization\": \"Bearer ${GITHUB_TOKEN}\"\n      }\n    }\n  }\n}\n```\n\nयदि आप OMP का सबसे कम आश्चर्यजनक व्यवहार चाहते हैं, तो `.xcsh/mcp.json` को प्राथमिकता दें और स्पष्ट env/header मानों का उपयोग करें।\n\n## `disabledServers`\n\n`disabledServers` मुख्य रूप से उपयोगकर्ता कॉन्फ़िग फ़ाइल (`~/.xcsh/mcp.json`) में तब उपयोगी है जब कोई सर्वर किसी अन्य स्रोत से खोजा जाता है और आप चाहते हैं कि OMP उस अन्य टूल की कॉन्फ़िग को संपादित किए बिना उसे अनदेखा करे।\n\nउदाहरण:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"disabledServers\": [\"github\", \"slack\"]\n}\n```\n\n## `/mcp add` बनाम सीधे JSON संपादन\n\n`/mcp add` का उपयोग करें जब आप गाइडेड सेटअप चाहते हैं।\n\nसीधे JSON संपादन का उपयोग करें जब:\n\n- आपको किसी ऐसे ट्रांसपोर्ट या प्रमाणीकरण विकल्प की आवश्यकता है जिसके लिए विज़ार्ड अभी तक प्रॉम्प्ट नहीं करता\n- आप किसी अन्य MCP क्लाइंट से सर्वर परिभाषा पेस्ट करना चाहते हैं\n- आप अपने एडिटर में स्कीमा-समर्थित वैलिडेशन चाहते हैं\n\nसंपादन के बाद, उपयोग करें:\n\n- `/mcp reload` वर्तमान सत्र में सर्वर को पुनः खोजने और पुनः कनेक्ट करने के लिए\n- `/mcp list` यह देखने के लिए कि कोई सर्वर किस कॉन्फ़िग फ़ाइल से आया है\n- `/mcp test <name>` एकल सर्वर का परीक्षण करने के लिए\n\n## OMP द्वारा लागू किए जाने वाले वैलिडेशन नियम\n\n`packages/coding-agent/src/mcp/config.ts` में `validateServerConfig()` से:\n\n- `stdio` के लिए `command` आवश्यक है\n- `http` और `sse` के लिए `url` आवश्यक है\n- एक सर्वर `command` और `url` दोनों सेट नहीं कर सकता\n- अज्ञात `type` मान अस्वीकार किए जाते हैं\n\nव्यावहारिक प्रभाव:\n\n- `type` छोड़ने का मतलब `stdio` है\n- यदि आप कोई रिमोट सर्वर कॉन्फ़िग पेस्ट करते हैं और `\"type\": \"http\"` भूल जाते हैं, तो OMP इसे `stdio` के रूप में मानेगा और शिकायत करेगा कि `command` गायब है\n- `sse` संगतता के लिए मान्य रहता है, लेकिन नए होस्टेड सर्वर को आमतौर पर `http` के रूप में कॉन्फ़िगर किया जाना चाहिए\n\n## डिस्कवरी और प्राथमिकता\n\nOMP फ़ाइलों में डुप्लिकेट सर्वर परिभाषाओं को मर्ज नहीं करता। डिस्कवरी प्रदाताओं को प्राथमिकता दी जाती है, और उच्च-प्राथमिकता वाली परिभाषा जीतती है।\n\nव्यवहार में:\n\n- `.xcsh/mcp.json` या `~/.xcsh/mcp.json` को प्राथमिकता दें जब आप OMP-विशिष्ट ओवरराइड चाहते हैं\n- जब संभव हो तो टूल्स में सर्वर नाम अद्वितीय रखें\n- उपयोगकर्ता कॉन्फ़िग में `disabledServers` का उपयोग करें जब कोई तृतीय-पक्ष कॉन्फ़िग किसी ऐसे सर्वर को बार-बार पुनः प्रस्तुत करता रहे जो आप नहीं चाहते\n\n## समस्या निवारण\n\n### `Server \"name\": stdio server requires \"command\" field`\n\nआपने संभवतः किसी रिमोट सर्वर पर `type: \"http\"` छोड़ दिया है।\n\n### `Server \"name\": both \"command\" and \"url\" are set`\n\nएक ट्रांसपोर्ट चुनें। OMP `command` को stdio और `url` को http/sse के रूप में मानता है।\n\n### `/mcp add` ने काम किया लेकिन सर्वर अभी भी कनेक्ट नहीं होता\n\nJSON मान्य है, लेकिन सर्वर अभी भी अगम्य हो सकता है। `/mcp test <name>` का उपयोग करें और जाँचें कि:\n\n- बाइनरी या Docker इमेज मौजूद है\n- आवश्यक एनवायरनमेंट वेरिएबल सेट हैं\n- रिमोट URL पहुँच योग्य है\n- OAuth या API टोकन मान्य है\n\n### सर्वर किसी अन्य टूल की कॉन्फ़िग में मौजूद है लेकिन OMP में नहीं\n\n`/mcp list` चलाएं। OMP कई तृतीय-पक्ष MCP फ़ाइलें खोजता है, लेकिन प्रोजेक्ट-स्तरीय लोडिंग `mcp.enableProjectConfig` सेटिंग के माध्यम से भी अक्षम की जा सकती है।\n\n## संदर्भ\n\n- MCP ट्रांसपोर्ट विनिर्देश: <https://modelcontextprotocol.io/specification/2025-03-26/basic/transports>\n- Filesystem सर्वर पैकेज: <https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem>\n- GitHub MCP सर्वर: <https://github.com/github/github-mcp-server>\n- Slack MCP सर्वर दस्तावेज़: <https://docs.slack.dev/ai/slack-mcp-server/>\n",
	"hi/mcp/mcp-protocol-transports.md": "---\ntitle: MCP प्रोटोकॉल और ट्रांसपोर्ट आंतरिक संरचना\ndescription: >-\n  MCP protocol implementation with stdio, SSE, and streamable HTTP transport\n  layers.\nsidebar:\n  order: 2\n  label: प्रोटोकॉल और ट्रांसपोर्ट\ni18n:\n  sourceHash: 48632064dd00\n  translator: machine\n---\n\n# MCP प्रोटोकॉल और ट्रांसपोर्ट आंतरिक संरचना\n\nयह दस्तावेज़ वर्णन करता है कि coding-agent MCP JSON-RPC मैसेजिंग को कैसे कार्यान्वित करता है और प्रोटोकॉल संबंधी चिंताओं को ट्रांसपोर्ट संबंधी चिंताओं से कैसे अलग किया जाता है।\n\n## कवरेज\n\nइसमें शामिल है:\n\n- JSON-RPC अनुरोध/प्रतिक्रिया और अधिसूचना प्रवाह\n- stdio और HTTP/SSE ट्रांसपोर्ट के लिए अनुरोध सहसंबंध और जीवनचक्र\n- टाइमआउट और रद्दीकरण व्यवहार\n- त्रुटि प्रसारण और विकृत पेलोड हैंडलिंग\n- ट्रांसपोर्ट चयन सीमाएं (`stdio` बनाम `http`/`sse`)\n- कौन सी पुनर्कनेक्ट/पुनर्प्रयास जिम्मेदारियां ट्रांसपोर्ट-स्तरीय हैं बनाम मैनेजर-स्तरीय\n\nइसमें एक्सटेंशन ऑथरिंग UX या कमांड UI शामिल नहीं है।\n\n## कार्यान्वयन फ़ाइलें\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/transports/stdio.ts`](../../packages/coding-agent/src/mcp/transports/stdio.ts)\n- [`src/mcp/transports/http.ts`](../../packages/coding-agent/src/mcp/transports/http.ts)\n- [`src/mcp/transports/index.ts`](../../packages/coding-agent/src/mcp/transports/index.ts)\n- [`src/mcp/json-rpc.ts`](../../packages/coding-agent/src/mcp/json-rpc.ts)\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n\n## परत सीमाएं\n\n### प्रोटोकॉल परत (JSON-RPC + MCP विधियाँ)\n\n- संदेश आकार `types.ts` में परिभाषित हैं (`JsonRpcRequest`, `JsonRpcNotification`, `JsonRpcResponse`, `JsonRpcMessage`)।\n- MCP क्लाइंट लॉजिक (`client.ts`) विधि क्रम और सत्र हैंडशेक निर्धारित करता है:\n  1. `initialize` अनुरोध\n  2. `notifications/initialized` अधिसूचना\n  3. `tools/list`, `tools/call` जैसी विधि कॉल\n\n### ट्रांसपोर्ट परत (`MCPTransport`)\n\n`MCPTransport` डिलीवरी और जीवनचक्र को अमूर्त करता है:\n\n- `request(method, params, options?) -> Promise<T>`\n- `notify(method, params?) -> Promise<void>`\n- `close()`\n- `connected`\n- वैकल्पिक कॉलबैक: `onClose`, `onError`, `onNotification`\n\nट्रांसपोर्ट कार्यान्वयन फ्रेमिंग और I/O विवरणों के स्वामी हैं:\n\n- `StdioTransport`: सबप्रोसेस stdio पर न्यूलाइन-सीमांकित JSON\n- `HttpTransport`: HTTP POST पर JSON-RPC, वैकल्पिक SSE प्रतिक्रियाओं/श्रवण के साथ\n\n### महत्वपूर्ण वर्तमान चेतावनी\n\nट्रांसपोर्ट कॉलबैक (`onClose`, `onError`, `onNotification`) कार्यान्वित हैं, लेकिन वर्तमान `MCPClient`/`MCPManager` प्रवाह इन कॉलबैक में पुनर्कनेक्शन लॉजिक को जोड़ते नहीं हैं। अधिसूचनाएं केवल तभी उपभोग की जाती हैं जब कॉलर हैंडलर पंजीकृत करता है।\n\n## ट्रांसपोर्ट चयन\n\n`client.ts:createTransport()` कॉन्फ़िगरेशन से ट्रांसपोर्ट चुनता है:\n\n- `type` छोड़ दिया गया या `\"stdio\"` -> `createStdioTransport`\n- `\"http\"` या `\"sse\"` -> `createHttpTransport`\n\n`\"sse\"` को HTTP ट्रांसपोर्ट वेरिएंट (समान क्लास) के रूप में माना जाता है, एक अलग ट्रांसपोर्ट कार्यान्वयन के रूप में नहीं।\n\n## JSON-RPC संदेश प्रवाह और सहसंबंध\n\n## अनुरोध ID\n\nप्रत्येक ट्रांसपोर्ट प्रति-अनुरोध ID उत्पन्न करता है (`Math.random` + टाइमस्टैम्प स्ट्रिंग)। ID ट्रांसपोर्ट-स्थानीय सहसंबंध टोकन हैं।\n\n## Stdio सहसंबंध पथ\n\n- आउटबाउंड अनुरोध एक JSON ऑब्जेक्ट + `\\n` के रूप में क्रमबद्ध किया जाता है।\n- `#pendingRequests: Map<id, {resolve,reject}>` इन-फ्लाइट अनुरोधों को संग्रहीत करता है।\n- रीड लूप stdout से JSONL पार्स करता है और `#handleMessage` को कॉल करता है।\n- यदि इनबाउंड संदेश में मेल खाता `id` है, तो अनुरोध resolve/reject होता है।\n- यदि इनबाउंड संदेश में `method` है और कोई `id` नहीं है, तो इसे अधिसूचना के रूप में माना जाता है और `onNotification` को भेजा जाता है।\n\nअज्ञात ID को अनदेखा किया जाता है (कोई rejection नहीं, कोई error कॉलबैक नहीं)।\n\n## HTTP सहसंबंध पथ\n\n- आउटबाउंड अनुरोध JSON बॉडी और उत्पन्न `id` के साथ HTTP `POST` है।\n- गैर-SSE प्रतिक्रिया पथ: एक JSON-RPC प्रतिक्रिया पार्स करता है और `result` लौटाता है / `error` पर throw करता है।\n- SSE प्रतिक्रिया पथ (`Content-Type: text/event-stream`): इवेंट स्ट्रीम करता है, पहले संदेश को लौटाता है जिसका `id` अपेक्षित अनुरोध ID से मेल खाता है और जिसमें `result` या `error` है।\n- `method` वाले और बिना `id` वाले SSE संदेशों को अधिसूचना के रूप में माना जाता है।\n\nयदि SSE स्ट्रीम मेल खाती प्रतिक्रिया से पहले समाप्त हो जाती है, तो अनुरोध `No response received for request ID ...` के साथ विफल होता है।\n\n## अधिसूचनाएं\n\nक्लाइंट `transport.notify(...)` के माध्यम से JSON-RPC अधिसूचनाएं उत्सर्जित करता है।\n\n- Stdio: stdin में अधिसूचना फ्रेम (`jsonrpc`, `method`, वैकल्पिक `params`) प्लस न्यूलाइन लिखता है।\n- HTTP: बिना `id` के POST बॉडी भेजता है; सफलता `2xx` या `202 Accepted` स्वीकार करती है।\n\nसर्वर-आरंभित अधिसूचनाएं केवल ट्रांसपोर्ट `onNotification` के माध्यम से प्रदर्शित की जाती हैं; मैनेजर/क्लाइंट में कोई डिफ़ॉल्ट वैश्विक सब्सक्राइबर नहीं है।\n\n## Stdio ट्रांसपोर्ट आंतरिक संरचना\n\n## जीवनचक्र और स्थिति संक्रमण\n\n- प्रारंभिक: `connected=false`, `process=null`, pending map खाली\n- `connect()`:\n  - कॉन्फ़िगर किए गए command/args/env/cwd के साथ सबप्रोसेस spawn करें\n  - connected चिह्नित करें\n  - stdout रीड लूप शुरू करें (`readJsonl`)\n  - stderr लूप शुरू करें (read/discard; वर्तमान में मूक)\n- `close()`:\n  - disconnected चिह्नित करें\n  - सभी pending अनुरोधों को reject करें (`Transport closed`)\n  - सबप्रोसेस kill करें\n  - रीड लूप शटडाउन की प्रतीक्षा करें\n  - `onClose` उत्सर्जित करें\n\nयदि रीड लूप अप्रत्याशित रूप से बाहर निकलता है, तो `finally` `#handleClose()` को ट्रिगर करता है जो समान pending-request rejection और close कॉलबैक करता है।\n\n## टाइमआउट और रद्दीकरण\n\nप्रति अनुरोध:\n\n- टाइमआउट डिफ़ॉल्ट `config.timeout ?? 30000` है\n- कॉलर से वैकल्पिक `AbortSignal`\n- abort और timeout दोनों pending promise को reject करते हैं और map entry साफ़ करते हैं\n\nरद्दीकरण केवल स्थानीय है: ट्रांसपोर्ट सर्वर को प्रोटोकॉल-स्तरीय रद्दीकरण अधिसूचना नहीं भेजता।\n\n## विकृत पेलोड हैंडलिंग\n\nरीड लूप में:\n\n- प्रत्येक पार्स की गई JSONL लाइन `try/catch` में `#handleMessage` को पास की जाती है\n- विकृत/अमान्य संदेश हैंडलिंग अपवाद छोड़ दिए जाते हैं (`Skip malformed lines` टिप्पणी)\n- लूप जारी रहता है, इसलिए एक खराब संदेश कनेक्शन को समाप्त नहीं करता\n\nयदि अंतर्निहित स्ट्रीम पार्सर throw करता है, तो `onError` invoked होता है (जब अभी भी connected हो), फिर कनेक्शन बंद हो जाता है।\n\n## डिस्कनेक्ट/विफलता व्यवहार\n\nजब प्रक्रिया बाहर निकलती है या स्ट्रीम बंद होती है:\n\n- सभी इन-फ्लाइट अनुरोध `Transport closed` के साथ reject होते हैं\n- कोई स्वचालित पुनरारंभ या पुनर्कनेक्ट नहीं\n- उच्च परतों को एक नया ट्रांसपोर्ट बनाकर पुनर्कनेक्ट करना होगा\n\n## बैकप्रेशर/स्ट्रीमिंग नोट्स\n\n- आउटबाउंड राइट drain सेमांटिक्स की प्रतीक्षा किए बिना `stdin.write()` + `flush()` का उपयोग करते हैं।\n- ट्रांसपोर्ट में कोई स्पष्ट कतार या high-watermark प्रबंधन नहीं है।\n- इनबाउंड प्रोसेसिंग स्ट्रीम-संचालित है (`readJsonl` पर `for await`), एक समय में एक पार्स किया गया संदेश।\n\n## HTTP/SSE ट्रांसपोर्ट आंतरिक संरचना\n\n## जीवनचक्र और कनेक्शन सेमांटिक्स\n\nHTTP ट्रांसपोर्ट में तार्किक कनेक्शन स्थिति है, लेकिन अनुरोध पथ प्रति HTTP कॉल स्टेटलेस है:\n\n- `connect()` `connected=true` सेट करता है (कोई सॉकेट/सत्र हैंडशेक नहीं)\n- `Mcp-Session-Id` हेडर के माध्यम से वैकल्पिक सर्वर सत्र ट्रैकिंग\n- `close()` वैकल्पिक रूप से `Mcp-Session-Id` के साथ `DELETE` भेजता है, SSE लिसनर को abort करता है, `onClose` उत्सर्जित करता है\n\nइसलिए `connected` का अर्थ है \"ट्रांसपोर्ट उपयोग योग्य है\", न कि \"स्थायी स्ट्रीम स्थापित है\"।\n\n## सत्र हेडर व्यवहार\n\n- POST प्रतिक्रिया पर, यदि `Mcp-Session-Id` हेडर उपस्थित है, तो ट्रांसपोर्ट इसे संग्रहीत करता है।\n- बाद के अनुरोध/अधिसूचनाएं `Mcp-Session-Id` शामिल करती हैं।\n- `close()` HTTP DELETE के साथ सर्वर सत्र समाप्त करने का प्रयास करता है; समाप्ति विफलताओं को अनदेखा किया जाता है।\n\n## टाइमआउट और रद्दीकरण\n\n`request()` और `notify()` दोनों के लिए:\n\n- टाइमआउट `AbortController` (`config.timeout ?? 30000`) का उपयोग करता है\n- बाहरी सिग्नल, यदि प्रदान किया गया हो, `AbortSignal.any([...])` के माध्यम से मर्ज किया जाता है\n- AbortError हैंडलिंग कॉलर abort बनाम timeout को अलग करती है\n\nफेंकी गई त्रुटियां:\n\n- टाइमआउट: `Request timeout after ...ms` (या `SSE response timeout ...`, `Notify timeout ...`)\n- कॉलर abort: जब बाहरी सिग्नल पहले से abort हो तो मूल AbortError पुनः फेंका जाता है\n\n## HTTP त्रुटि प्रसारण\n\nगैर-OK प्रतिक्रिया पर:\n\n- प्रतिक्रिया टेक्स्ट फेंकी गई त्रुटि में शामिल है (`HTTP <status>: <text>`)\n- यदि उपस्थित हो, `WWW-Authenticate` और `Mcp-Auth-Server` से auth संकेत जोड़े जाते हैं\n\nJSON-RPC error ऑब्जेक्ट पर:\n\n- `MCP error <code>: <message>` throw करता है\n\nविकृत JSON बॉडी (`response.json()` विफलता) parse अपवाद के रूप में प्रसारित होती है।\n\n## SSE व्यवहार और मोड\n\nदो SSE पथ मौजूद हैं:\n\n1. **प्रति-अनुरोध SSE प्रतिक्रिया** (`#parseSSEResponse`)\n   - जब POST प्रतिक्रिया content type `text/event-stream` हो तब उपयोग किया जाता है\n   - मेल खाते response id मिलने तक स्ट्रीम का उपभोग करता है\n   - समान स्ट्रीम के दौरान इंटरलीव्ड अधिसूचनाओं को संसाधित कर सकता है\n\n2. **पृष्ठभूमि SSE लिसनर** (`startSSEListener()`)\n   - सर्वर-आरंभित अधिसूचनाओं के लिए वैकल्पिक GET लिसनर\n   - वर्तमान में MCP मैनेजर/क्लाइंट द्वारा स्वचालित रूप से शुरू नहीं किया जाता\n   - यदि GET `405` लौटाता है, तो लिसनर चुपचाप स्वयं को अक्षम कर देता है (सर्वर इस मोड का समर्थन नहीं करता)\n\n## विकृत पेलोड और डिस्कनेक्ट हैंडलिंग\n\nSSE JSON पार्सिंग त्रुटियां `readSseJson` से बाहर निकलती हैं और अनुरोध/लिसनर को reject करती हैं।\n\n- अनुरोध SSE पार्स त्रुटियां सक्रिय अनुरोध को reject करती हैं।\n- पृष्ठभूमि लिसनर त्रुटियां `onError` ट्रिगर करती हैं (AbortError को छोड़कर)।\n- पृष्ठभूमि लिसनर के लिए कोई ऑटो-रिकनेक्ट नहीं।\n\n## `json-rpc.ts` उपयोगिता बनाम ट्रांसपोर्ट अमूर्तता\n\n`src/mcp/json-rpc.ts` प्रत्यक्ष HTTP MCP कॉल के लिए `callMCP()` और `parseSSE()` हेल्पर प्रदान करता है (Exa एकीकरण द्वारा उपयोग किया जाता है), `MCPClient`/`MCPManager` द्वारा उपयोग की जाने वाली `MCPTransport` अमूर्तता नहीं।\n\n`HttpTransport` से उल्लेखनीय अंतर:\n\n- पहले संपूर्ण प्रतिक्रिया टेक्स्ट पार्स करता है, फिर पहली `data:` लाइन निकालता है (`parseSSE`), JSON फ़ॉलबैक के साथ\n- कोई अनुरोध टाइमआउट प्रबंधन नहीं, कोई abort API नहीं, कोई session-id हैंडलिंग नहीं, कोई ट्रांसपोर्ट जीवनचक्र नहीं\n- कच्चा JSON-RPC envelope ऑब्जेक्ट लौटाता है\n\nयह पथ हल्का है लेकिन पूर्ण ट्रांसपोर्ट कार्यान्वयन से कम मजबूत है।\n\n## पुनर्प्रयास/पुनर्कनेक्ट जिम्मेदारियां\n\n## ट्रांसपोर्ट-स्तरीय\n\nवर्तमान ट्रांसपोर्ट कार्यान्वयन निम्नलिखित **नहीं** करते:\n\n- विफल अनुरोधों का पुनर्प्रयास\n- stdio प्रक्रिया बाहर निकलने के बाद पुनर्कनेक्ट\n- SSE लिसनर को पुनर्कनेक्ट\n- डिस्कनेक्ट के बाद इन-फ्लाइट अनुरोध पुनः भेजना\n\nवे तेजी से विफल होते हैं और त्रुटियां प्रसारित करते हैं।\n\n## मैनेजर/क्लाइंट-स्तरीय\n\n`MCPManager` खोज/प्रारंभिक कनेक्शन ऑर्केस्ट्रेशन को संभालता है और केवल कनेक्ट प्रवाह (`connectToServer`/`discoverAndConnect` पथ) फिर से चलाकर पुनर्कनेक्ट कर सकता है। यह रनटाइम विफलता कॉलबैक पर पहले से जुड़े ट्रांसपोर्ट को ऑटो-हील नहीं करता।\n\n`MCPManager` में धीमे सर्वरों के लिए स्टार्टअप फ़ॉलबैक व्यवहार है (कैश से स्थगित उपकरण), लेकिन यह उपकरण उपलब्धता फ़ॉलबैक है, ट्रांसपोर्ट पुनर्प्रयास नहीं।\n\n## विफलता परिदृश्य सारांश\n\n- **विकृत stdio संदेश लाइन**: छोड़ दी गई; स्ट्रीम जारी रहती है।\n- **Stdio स्ट्रीम/प्रक्रिया समाप्त**: ट्रांसपोर्ट बंद; pending अनुरोध `Transport closed` के रूप में reject।\n- **HTTP गैर-2xx**: अनुरोध/notify HTTP त्रुटि throw करता है।\n- **अमान्य JSON प्रतिक्रिया**: parse अपवाद प्रसारित।\n- **SSE मेल खाते id के बिना समाप्त**: अनुरोध `No response received for request ID ...` के साथ विफल।\n- **टाइमआउट**: ट्रांसपोर्ट-विशिष्ट टाइमआउट त्रुटि।\n- **कॉलर abort**: कॉलर सिग्नल से AbortError/कारण प्रसारित।\n\n## व्यावहारिक सीमा नियम\n\nयदि चिंता संदेश आकार, id सहसंबंध, या MCP विधि क्रम से संबंधित है, तो यह प्रोटोकॉल/क्लाइंट लॉजिक में आती है।\n\nयदि चिंता फ्रेमिंग (JSONL बनाम HTTP/SSE), स्ट्रीम पार्सिंग, fetch/spawn जीवनचक्र, टाइमआउट क्लॉक, या कनेक्शन टियरडाउन से संबंधित है, तो यह ट्रांसपोर्ट कार्यान्वयन में आती है।\n",
	"hi/mcp/mcp-runtime-lifecycle.md": "---\ntitle: MCP रनटाइम जीवनचक्र\ndescription: >-\n  MCP सर्वर प्रक्रिया जीवनचक्र - इनिशियलाइज़ेशन से लेकर टूल रजिस्ट्रेशन, हेल्थ\n  मॉनिटरिंग और शटडाउन तक।\nsidebar:\n  order: 3\n  label: रनटाइम जीवनचक्र\ni18n:\n  sourceHash: d04cefaf38f8\n  translator: machine\n---\n\n# MCP रनटाइम जीवनचक्र\n\nयह दस्तावेज़ वर्णन करता है कि MCP सर्वर coding-agent रनटाइम में कैसे खोजे, कनेक्ट किए, टूल्स के रूप में एक्सपोज़ किए, रिफ्रेश किए और बंद किए जाते हैं।\n\n## जीवनचक्र एक नज़र में\n\n1. **SDK स्टार्टअप** `discoverAndLoadMCPTools()` कॉल करता है (जब तक MCP अक्षम न हो)।\n2. **डिस्कवरी** (`loadAllMCPConfigs`) कैपेबिलिटी स्रोतों से MCP सर्वर कॉन्फ़िग रिज़ॉल्व करता है, अक्षम/प्रोजेक्ट/Exa एंट्रीज़ को फ़िल्टर करता है, और स्रोत मेटाडेटा संरक्षित रखता है।\n3. **मैनेजर कनेक्ट चरण** (`MCPManager.connectServers`) प्रति-सर्वर कनेक्ट + `tools/list` को समानांतर में शुरू करता है।\n4. **फास्ट स्टार्टअप गेट** 250ms तक प्रतीक्षा करता है, फिर लौटा सकता है:\n   - पूरी तरह लोड हुए `MCPTool`s,\n   - प्रति सर्वर विफलताएँ,\n   - या अभी भी लंबित सर्वरों के लिए कैश्ड `DeferredMCPTool`s।\n5. **SDK वायरिंग** MCP टूल्स को सेशन के लिए रनटाइम टूल रजिस्ट्री में मर्ज करता है।\n6. **लाइव सेशन** `/mcp` फ्लो (`disconnectAll` + पुनः खोज + `session.refreshMCPTools`) के माध्यम से MCP टूल्स को रिफ्रेश कर सकता है।\n7. **टियरडाउन** तब होता है जब कॉलर `disconnectServer`/`disconnectAll` इनवोक करते हैं; मैनेजर डिस्कनेक्ट किए गए सर्वरों के लिए MCP टूल रजिस्ट्रेशन भी हटाता है।\n\n## डिस्कवरी और लोड चरण\n\n### SDK से एंट्री पथ\n\n`createAgentSession()` (`src/sdk.ts` में) जब `enableMCP` true होता है (डिफ़ॉल्ट) तब MCP स्टार्टअप करता है:\n\n- `discoverAndLoadMCPTools(cwd, { ... })` कॉल करता है,\n- `authStorage`, कैश स्टोरेज, और `mcp.enableProjectConfig` सेटिंग पास करता है,\n- हमेशा `filterExa: true` सेट करता है,\n- प्रति-सर्वर लोड/कनेक्ट त्रुटियाँ लॉग करता है,\n- लौटाए गए मैनेजर को `toolSession.mcpManager` और सेशन रिज़ल्ट में संग्रहीत करता है।\n\nयदि `enableMCP` false है, तो MCP डिस्कवरी पूरी तरह स्किप हो जाती है।\n\n### कॉन्फ़िग डिस्कवरी और फ़िल्टरिंग\n\n`loadAllMCPConfigs()` (`src/mcp/config.ts`) कैपेबिलिटी डिस्कवरी के माध्यम से कैनोनिकल MCP सर्वर आइटम लोड करता है, फिर लिगेसी `MCPServerConfig` में कन्वर्ट करता है।\n\nफ़िल्टरिंग व्यवहार:\n\n- `enableProjectConfig: false` प्रोजेक्ट-स्तरीय एंट्रीज़ (`_source.level === \"project\"`) को हटाता है।\n- `enabled: false` सर्वर कनेक्ट प्रयासों से पहले स्किप किए जाते हैं।\n- Exa सर्वर डिफ़ॉल्ट रूप से फ़िल्टर किए जाते हैं और API कुंजियाँ नेटिव Exa टूल इंटीग्रेशन के लिए निकाली जाती हैं।\n\nपरिणाम में `configs` और `sources` (मेटाडेटा जो बाद में प्रोवाइडर लेबलिंग के लिए उपयोग होता है) दोनों शामिल हैं।\n\n### डिस्कवरी-स्तरीय विफलता व्यवहार\n\n`discoverAndLoadMCPTools()` दो विफलता वर्गों में अंतर करता है:\n\n- **डिस्कवरी हार्ड विफलता** (`manager.discoverAndConnect` से अपवाद, आमतौर पर कॉन्फ़िग डिस्कवरी से): एक खाली टूल सेट और एक सिंथेटिक त्रुटि `{ path: \".mcp.json\", error }` लौटाता है।\n- **प्रति-सर्वर रनटाइम/कनेक्ट विफलता**: मैनेजर `errors` मैप के साथ आंशिक सफलता लौटाता है; अन्य सर्वर जारी रहते हैं।\n\nअतः जब व्यक्तिगत MCP सर्वर विफल होते हैं तो स्टार्टअप पूरे एजेंट सेशन को विफल नहीं करता।\n\n## मैनेजर स्टेट मॉडल\n\n`MCPManager` अलग-अलग रजिस्ट्रीज़ के साथ रनटाइम जीवनचक्र ट्रैक करता है:\n\n- `#connections: Map<string, MCPServerConnection>` — पूरी तरह कनेक्टेड सर्वर।\n- `#pendingConnections: Map<string, Promise<MCPServerConnection>>` — हैंडशेक प्रगति में।\n- `#pendingToolLoads: Map<string, Promise<{ connection, serverTools }>>` — कनेक्टेड लेकिन टूल्स अभी लोड हो रहे हैं।\n- `#tools: CustomTool[]` — कॉलर्स को एक्सपोज़ किया गया वर्तमान MCP टूल व्यू।\n- `#sources: Map<string, SourceMeta>` — कनेक्ट पूरा होने से पहले भी प्रोवाइडर/स्रोत मेटाडेटा।\n\n`getConnectionStatus(name)` इन मैप्स से स्टेटस प्राप्त करता है:\n\n- `connected` यदि `#connections` में है,\n- `connecting` यदि पेंडिंग कनेक्ट या पेंडिंग टूल लोड में है,\n- `disconnected` अन्यथा।\n\n## कनेक्शन स्थापना और स्टार्टअप टाइमिंग\n\n## प्रति-सर्वर कनेक्ट पाइपलाइन\n\n`connectServers()` में प्रत्येक खोजे गए सर्वर के लिए:\n\n1. स्रोत मेटाडेटा स्टोर/अपडेट करें,\n2. यदि पहले से कनेक्टेड/पेंडिंग है तो स्किप करें,\n3. ट्रांसपोर्ट फ़ील्ड्स वैलिडेट करें (`validateServerConfig`),\n4. ऑथ/शेल प्रतिस्थापन रिज़ॉल्व करें (`#resolveAuthConfig`),\n5. `connectToServer(name, resolvedConfig)` कॉल करें,\n6. `listTools(connection)` कॉल करें,\n7. टूल परिभाषाएँ कैश करें (`MCPToolCache.set`) सर्वोत्तम-प्रयास।\n\n`connectToServer()` व्यवहार (`src/mcp/client.ts`):\n\n- stdio या HTTP/SSE ट्रांसपोर्ट बनाता है,\n- MCP `initialize` + `notifications/initialized` करता है,\n- टाइमआउट उपयोग करता है (`config.timeout` या 30s डिफ़ॉल्ट),\n- init विफलता पर ट्रांसपोर्ट बंद करता है।\n\n### फास्ट स्टार्टअप गेट + डिफ़र्ड फ़ॉलबैक\n\n`connectServers()` इनके बीच एक रेस पर प्रतीक्षा करता है:\n\n- सभी कनेक्ट/टूल-लोड कार्य सेटल हो जाएँ, और\n- `STARTUP_TIMEOUT_MS = 250`।\n\n250ms के बाद:\n\n- पूर्ण हुए कार्य लाइव `MCPTool`s बन जाते हैं,\n- अस्वीकृत कार्य प्रति-सर्वर त्रुटियाँ उत्पन्न करते हैं,\n- अभी भी लंबित कार्य:\n  - यदि उपलब्ध हो तो कैश्ड टूल परिभाषाओं (`MCPToolCache.get`) का उपयोग करके `DeferredMCPTool`s बनाते हैं,\n  - अन्यथा उन लंबित कार्यों के सेटल होने तक प्रतीक्षा करते हैं।\n\nयह एक हाइब्रिड स्टार्टअप मॉडल है: कैश उपलब्ध होने पर तेज़ रिटर्न, कैश न होने पर शुद्धता के लिए प्रतीक्षा।\n\n### बैकग्राउंड पूर्णता व्यवहार\n\nप्रत्येक लंबित `toolsPromise` की एक बैकग्राउंड कंटिन्यूएशन भी होती है जो अंततः:\n\n- `#replaceServerTools` के माध्यम से मैनेजर स्टेट में उस सर्वर के टूल स्लाइस को बदलती है,\n- कैश लिखती है,\n- स्टार्टअप के बाद ही विलंबित विफलताएँ लॉग करती है (`allowBackgroundLogging`)।\n\n## टूल एक्सपोज़र और लाइव-सेशन उपलब्धता\n\n### स्टार्टअप रजिस्ट्रेशन\n\n`discoverAndLoadMCPTools()` मैनेजर टूल्स को `LoadedCustomTool[]` में कन्वर्ट करता है और पथों को सजाता है (`mcp:<server> via <providerName>` जब ज्ञात हो)।\n\n`createAgentSession()` फिर इन टूल्स को `customTools` में पुश करता है, जो `mcp_<server>_<tool>` जैसे नामों के साथ रैप और रनटाइम टूल रजिस्ट्री में जोड़े जाते हैं।\n\n### टूल कॉल\n\n- `MCPTool` पहले से कनेक्टेड `MCPServerConnection` के माध्यम से टूल्स कॉल करता है।\n- `DeferredMCPTool` कॉल करने से पहले `waitForConnection(server)` की प्रतीक्षा करता है; यह कैश्ड टूल्स को कनेक्शन तैयार होने से पहले अस्तित्व में रहने देता है।\n\nदोनों संरचित टूल आउटपुट लौटाते हैं और ट्रांसपोर्ट/टूल त्रुटियों को `MCP error: ...` टूल कंटेंट में कन्वर्ट करते हैं (abort वैसा ही रहता है)।\n\n## रिफ्रेश/रीलोड पथ (स्टार्टअप बनाम लाइव रीलोड)\n\n### प्रारंभिक स्टार्टअप पथ\n\n- `sdk.ts` में एकबारगी डिस्कवरी/लोड,\n- टूल्स प्रारंभिक सेशन टूल रजिस्ट्री में रजिस्टर किए जाते हैं।\n\n### इंटरैक्टिव रीलोड पथ\n\n`/mcp reload` पथ (`src/modes/controllers/mcp-command-controller.ts`) करता है:\n\n1. `mcpManager.disconnectAll()`,\n2. `mcpManager.discoverAndConnect()`,\n3. `session.refreshMCPTools(mcpManager.getTools())`।\n\n`session.refreshMCPTools()` (`src/session/agent-session.ts`) सभी `mcp_` टूल्स हटाता है, नवीनतम MCP टूल्स को पुनः रैप करता है, और टूल सेट को पुनः सक्रिय करता है ताकि MCP परिवर्तन सेशन पुनरारंभ किए बिना लागू हों।\n\nविलंबित कनेक्शनों के लिए एक फॉलो-अप पथ भी है: किसी विशिष्ट सर्वर की प्रतीक्षा के बाद, यदि स्टेटस `connected` हो जाता है, तो यह `session.refreshMCPTools(...)` पुनः चलाता है ताकि नए उपलब्ध टूल्स इन-सेशन पुनः बाउंड हों।\n\n## हेल्थ, रीकनेक्ट, और आंशिक विफलता व्यवहार\n\nवर्तमान रनटाइम व्यवहार जानबूझकर न्यूनतम है:\n\n- मैनेजर/क्लाइंट में **कोई स्वायत्त हेल्थ मॉनिटर नहीं**।\n- जब ट्रांसपोर्ट ड्रॉप होता है तो **कोई स्वचालित रीकनेक्ट लूप नहीं**।\n- मैनेजर ट्रांसपोर्ट `onClose`/`onError` को सब्सक्राइब नहीं करता; स्टेटस रजिस्ट्री-आधारित है।\n- रीकनेक्ट स्पष्ट है: रीलोड फ्लो या सीधा `connectServers()` इनवोकेशन।\n\nसंचालन की दृष्टि से:\n\n- एक सर्वर विफल होने से स्वस्थ सर्वरों के टूल्स नहीं हटते,\n- कनेक्ट/लिस्ट विफलताएँ प्रति सर्वर पृथक हैं,\n- टूल कैश और बैकग्राउंड अपडेट सर्वोत्तम-प्रयास हैं (चेतावनियाँ/त्रुटियाँ लॉग होती हैं, कोई हार्ड स्टॉप नहीं)।\n\n## टियरडाउन सेमेंटिक्स\n\n### सर्वर-स्तरीय टियरडाउन\n\n`disconnectServer(name)`:\n\n- पेंडिंग एंट्रीज़/स्रोत मेटाडेटा हटाता है,\n- कनेक्टेड होने पर ट्रांसपोर्ट बंद करता है,\n- मैनेजर स्टेट से उस सर्वर के `mcp_` टूल्स हटाता है।\n\n### ग्लोबल टियरडाउन\n\n`disconnectAll()`:\n\n- `Promise.allSettled` के साथ सभी सक्रिय ट्रांसपोर्ट बंद करता है,\n- पेंडिंग मैप्स, स्रोत, कनेक्शन, और मैनेजर टूल सूची साफ़ करता है।\n\nवर्तमान वायरिंग में, स्पष्ट टियरडाउन MCP कमांड फ्लो (रीलोड/रिमूव/डिसेबल के लिए) में उपयोग किया जाता है। स्टार्टअप पथ में कोई अलग स्वचालित मैनेजर डिस्पोज़ल हुक नहीं है; जब कॉलर्स को निर्धारक MCP शटडाउन की आवश्यकता होती है तो वे मैनेजर डिस्कनेक्ट विधियों को इनवोक करने के लिए ज़िम्मेदार हैं।\n\n## विफलता मोड और गारंटी\n\n| परिदृश्य | व्यवहार | हार्ड फेल बनाम सर्वोत्तम-प्रयास |\n| --- | --- | --- |\n| डिस्कवरी थ्रो करती है (कैपेबिलिटी/कॉन्फ़िग लोड पथ) | लोडर खाली टूल्स + सिंथेटिक `.mcp.json` त्रुटि लौटाता है | सर्वोत्तम-प्रयास सेशन स्टार्टअप |\n| अमान्य सर्वर कॉन्फ़िग | सर्वर वैलिडेशन त्रुटि एंट्री के साथ स्किप | सर्वोत्तम-प्रयास प्रति सर्वर |\n| कनेक्ट टाइमआउट/init विफलता | सर्वर त्रुटि रिकॉर्ड; अन्य जारी | सर्वोत्तम-प्रयास प्रति सर्वर |\n| स्टार्टअप पर `tools/list` अभी भी लंबित, कैश हिट के साथ | डिफ़र्ड टूल्स तुरंत लौटाए जाते हैं | सर्वोत्तम-प्रयास तेज़ स्टार्टअप |\n| स्टार्टअप पर `tools/list` अभी भी लंबित, कैश के बिना | स्टार्टअप लंबित के सेटल होने तक प्रतीक्षा करता है | शुद्धता के लिए हार्ड वेट |\n| विलंबित बैकग्राउंड टूल-लोड विफलता | स्टार्टअप गेट के बाद लॉग | सर्वोत्तम-प्रयास लॉगिंग |\n| रनटाइम ड्रॉप्ड ट्रांसपोर्ट | कोई स्वचालित रीकनेक्ट नहीं; रीकनेक्ट/रीलोड तक भविष्य के कॉल विफल | मैनुअल कार्रवाई द्वारा सर्वोत्तम-प्रयास रिकवरी |\n\n## सार्वजनिक API सतह\n\n`src/mcp/index.ts` बाहरी कॉलर्स के लिए लोडर/मैनेजर/क्लाइंट APIs को पुनः निर्यात करता है। `src/sdk.ts` एक सुविधा रैपर के रूप में `discoverMCPServers()` एक्सपोज़ करता है जो समान लोडर रिज़ल्ट शेप लौटाता है।\n\n## कार्यान्वयन फ़ाइलें\n\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts) — लोडर फ़साड, डिस्कवरी त्रुटि सामान्यीकरण, `LoadedCustomTool` रूपांतरण।\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts) — जीवनचक्र स्टेट रजिस्ट्रीज़, समानांतर कनेक्ट/लिस्ट फ्लो, रिफ्रेश/डिस्कनेक्ट।\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts) — ट्रांसपोर्ट सेटअप, इनिशियलाइज़ हैंडशेक, लिस्ट/कॉल/डिस्कनेक्ट।\n- [`src/mcp/index.ts`](../../packages/coding-agent/src/mcp/index.ts) — MCP मॉड्यूल API निर्यात।\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — सेशन/टूल रजिस्ट्री में स्टार्टअप वायरिंग।\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts) — मैनेजर द्वारा उपयोग किया जाने वाला कॉन्फ़िग डिस्कवरी/फ़िल्टरिंग/वैलिडेशन।\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts) — `MCPTool` और `DeferredMCPTool` रनटाइम व्यवहार।\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — `refreshMCPTools` लाइव रीबाइंडिंग।\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts) — इंटरैक्टिव रीलोड/रीकनेक्ट फ्लो।\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — पैरेंट मैनेजर कनेक्शनों के माध्यम से सबएजेंट MCP प्रॉक्सिंग।\n",
	"hi/mcp/mcp-server-tool-authoring.md": "---\ntitle: MCP सर्वर और टूल ऑथरिंग\ndescription: कोडिंग एजेंट के लिए कस्टम MCP सर्वर बनाने और टूल्स पंजीकृत करने की गाइड।\nsidebar:\n  order: 4\n  label: सर्वर और टूल ऑथरिंग\ni18n:\n  sourceHash: 160e7560ef1f\n  translator: machine\n---\n\n# MCP सर्वर और टूल ऑथरिंग\n\nयह दस्तावेज़ बताता है कि MCP सर्वर परिभाषाएँ कैसे coding-agent में कॉल करने योग्य `mcp_*` टूल्स बनती हैं, और जब कॉन्फ़िग अमान्य, डुप्लिकेट, अक्षम, या auth-gated होते हैं तो ऑपरेटर्स को क्या अपेक्षा रखनी चाहिए।\n\n## संक्षिप्त आर्किटेक्चर\n\n```text\nConfig sources (.xcsh/.claude/.cursor/.vscode/mcp.json, mcp.json, etc.)\n  -> discovery providers normalize to canonical MCPServer\n  -> capability loader dedupes by server name (higher provider priority wins)\n  -> loadAllMCPConfigs converts to MCPServerConfig + skips enabled:false\n  -> MCPManager connects/listTools (with auth/header/env resolution)\n  -> MCPTool/DeferredMCPTool bridge exposes tools as mcp_<server>_<tool>\n  -> AgentSession.refreshMCPTools replaces live MCP tools immediately\n```\n\n## 1) सर्वर कॉन्फ़िग मॉडल और सत्यापन\n\n`src/mcp/types.ts` MCP कॉन्फ़िग लेखकों और रनटाइम द्वारा उपयोग की जाने वाली ऑथरिंग शेप को परिभाषित करता है:\n\n- `stdio` (डिफ़ॉल्ट जब `type` अनुपस्थित हो): `command` आवश्यक है, `args`, `env`, `cwd` वैकल्पिक हैं\n- `http`: `url` आवश्यक है, `headers` वैकल्पिक है\n- `sse`: `url` आवश्यक है, `headers` वैकल्पिक है (संगतता के लिए रखा गया)\n- साझा फ़ील्ड: `enabled`, `timeout`, `auth`\n\n`validateServerConfig()` (`src/mcp/config.ts`) ट्रांसपोर्ट मूल बातों को लागू करता है:\n\n- उन कॉन्फ़िग को अस्वीकार करता है जो `command` और `url` दोनों सेट करते हैं\n- stdio के लिए `command` आवश्यक है\n- http/sse के लिए `url` आवश्यक है\n- अज्ञात `type` को अस्वीकार करता है\n\n`config-writer.ts` add/update ऑपरेशन के लिए यह सत्यापन लागू करता है और सर्वर नामों को भी मान्य करता है:\n\n- गैर-रिक्त\n- अधिकतम 100 अक्षर\n- केवल `[a-zA-Z0-9_.-]`\n\n### ट्रांसपोर्ट संबंधी समस्याएँ\n\n- `type` छोड़ने का अर्थ stdio है। यदि आपका इरादा HTTP/SSE था लेकिन `type` छोड़ दिया, तो `command` अनिवार्य हो जाता है।\n- `sse` अभी भी स्वीकार किया जाता है लेकिन आंतरिक रूप से HTTP ट्रांसपोर्ट (`createHttpTransport`) के रूप में व्यवहार किया जाता है।\n- सत्यापन संरचनात्मक है, पहुँच-योग्यता नहीं: एक वाक्यात्मक रूप से मान्य URL अभी भी कनेक्ट समय पर विफल हो सकता है।\n\n## 2) डिस्कवरी, सामान्यीकरण, और प्राथमिकता\n\n### क्षमता-आधारित डिस्कवरी\n\n`loadAllMCPConfigs()` (`src/mcp/config.ts`) `loadCapability(mcpCapability.id)` के माध्यम से कैनोनिकल `MCPServer` आइटम लोड करता है।\n\nक्षमता परत (`src/capability/index.ts`) तब:\n\n1. प्राथमिकता क्रम में प्रदाताओं को लोड करती है\n2. `server.name` द्वारा डिडूप करती है (पहली जीत = सर्वोच्च प्राथमिकता)\n3. डिडूप किए गए आइटम को मान्य करती है\n\nपरिणाम: स्रोतों में डुप्लिकेट सर्वर नाम मर्ज नहीं होते। एक परिभाषा जीतती है; कम-प्राथमिकता वाले डुप्लिकेट छाया में रह जाते हैं।\n\n### `.mcp.json` और संबंधित फ़ाइलें\n\n`src/discovery/mcp-json.ts` में समर्पित फ़ॉलबैक प्रदाता प्रोजेक्ट-रूट `mcp.json` और `.mcp.json` (कम प्राथमिकता) पढ़ता है।\n\nव्यवहार में MCP सर्वर उच्च-प्राथमिकता प्रदाताओं (उदाहरण के लिए नेटिव `.xcsh/...` और टूल-विशिष्ट कॉन्फ़िग निर्देशिकाएँ) से भी आते हैं। ऑथरिंग मार्गदर्शन:\n\n- स्पष्ट नियंत्रण के लिए `.xcsh/mcp.json` (प्रोजेक्ट) या `~/.xcsh/mcp.json` (उपयोगकर्ता) को प्राथमिकता दें।\n- फ़ॉलबैक संगतता की आवश्यकता होने पर रूट `mcp.json` / `.mcp.json` का उपयोग करें।\n- एक ही सर्वर नाम को कई स्रोतों में पुन: उपयोग करने से मर्ज नहीं, प्राथमिकता शैडोइंग होती है।\n\n### सामान्यीकरण व्यवहार\n\n`convertToLegacyConfig()` (`src/mcp/config.ts`) कैनोनिकल `MCPServer` को रनटाइम `MCPServerConfig` में मैप करता है।\n\nमुख्य व्यवहार:\n\n- ट्रांसपोर्ट `server.transport ?? (command ? \"stdio\" : url ? \"http\" : \"stdio\")` के रूप में अनुमानित\n- अक्षम सर्वर (`enabled === false`) कनेक्शन से पहले हटा दिए जाते हैं\n- उपस्थित होने पर वैकल्पिक फ़ील्ड संरक्षित रहते हैं\n\n### डिस्कवरी के दौरान एनवायरनमेंट विस्तार\n\n`mcp-json.ts` `expandEnvVarsDeep()` के साथ स्ट्रिंग फ़ील्ड में env प्लेसहोल्डर्स का विस्तार करता है:\n\n- `${VAR}` और `${VAR:-default}` का समर्थन करता है\n- अनसुलझे मान शाब्दिक `${VAR}` स्ट्रिंग्स बने रहते हैं\n\n`mcp-json.ts` उपयोगकर्ता JSON के लिए रनटाइम टाइप जाँच भी करता है और पूरी फ़ाइल को विफल करने के बजाय अमान्य `enabled`/`timeout` मानों के लिए चेतावनी लॉग करता है।\n\n## 3) Auth और रनटाइम मान समाधान\n\n`MCPManager.prepareConfig()`/`#resolveAuthConfig()` (`src/mcp/manager.ts`) अंतिम प्री-कनेक्ट पास है।\n\n### OAuth क्रेडेंशियल इंजेक्शन\n\nयदि कॉन्फ़िग में:\n\n```ts\nauth: { type: \"oauth\", credentialId: \"...\" }\n```\n\nऔर auth स्टोरेज में क्रेडेंशियल मौजूद है:\n\n- `http`/`sse`: `Authorization: Bearer <access_token>` हेडर इंजेक्ट करता है\n- `stdio`: `OAUTH_ACCESS_TOKEN` env वेरिएबल इंजेक्ट करता है\n\nयदि क्रेडेंशियल लुकअप विफल होता है, तो मैनेजर एक चेतावनी लॉग करता है और अनसुलझे auth के साथ जारी रहता है।\n\n### हेडर/env मान समाधान\n\nकनेक्ट से पहले, मैनेजर `resolveConfigValue()` (`src/config/resolve-config-value.ts`) के माध्यम से प्रत्येक हेडर/env मान को रिज़ॉल्व करता है:\n\n- `!` से शुरू होने वाला मान => शेल कमांड निष्पादित करें, ट्रिम किए गए stdout का उपयोग करें (कैश्ड)\n- अन्यथा, पहले मान को एनवायरनमेंट वेरिएबल नाम के रूप में मानें (`process.env[name]`), शाब्दिक मान पर फ़ॉलबैक\n- अनसुलझे कमांड/env मान अंतिम हेडर/env मैप से हटा दिए जाते हैं\n\nपरिचालन चेतावनी: इसका अर्थ है कि गलत टाइप की गई सीक्रेट कमांड/env कुंजी चुपचाप उस हेडर/env प्रविष्टि को हटा सकती है, जिससे डाउनस्ट्रीम 401/403 या सर्वर स्टार्टअप विफलताएँ हो सकती हैं।\n\n## 4) टूल ब्रिज: MCP -> एजेंट-कॉल करने योग्य टूल्स\n\n`src/mcp/tool-bridge.ts` MCP टूल परिभाषाओं को `CustomTool`s में रूपांतरित करता है।\n\n### नामकरण और टकराव डोमेन\n\nटूल नाम इस प्रकार उत्पन्न होते हैं:\n\n```text\nmcp_<sanitized_server_name>_<sanitized_tool_name>\n```\n\nनियम:\n\n- लोअरकेस में बदलता है\n- गैर-`[a-z_]` अक्षर `_` बन जाते हैं\n- बार-बार आने वाले अंडरस्कोर संकुचित होते हैं\n- टूल नाम में अनावश्यक `<server>_` उपसर्ग एक बार हटा दिया जाता है\n\nयह कई टकरावों से बचाता है, लेकिन सभी से नहीं। विभिन्न कच्चे नाम अभी भी एक ही पहचानकर्ता में सैनिटाइज़ हो सकते हैं (उदाहरण के लिए `my-server` और `my.server` दोनों समान रूप से सैनिटाइज़ होते हैं), और रजिस्ट्री प्रविष्टि अंतिम-लिखा-जीता के आधार पर होती है।\n\n### स्कीमा मैपिंग\n\n`convertSchema()` MCP JSON Schema को अधिकतर यथावत रखता है लेकिन प्रदाता संगतता के लिए `properties` अनुपस्थित ऑब्जेक्ट स्कीमा को `{}` से पैच करता है।\n\n### निष्पादन मैपिंग\n\n`MCPTool.execute()` / `DeferredMCPTool.execute()`:\n\n- MCP `tools/call` कॉल करता है\n- MCP कंटेंट को प्रदर्शन योग्य टेक्स्ट में फ़्लैटन करता है\n- संरचित विवरण लौटाता है (`serverName`, `mcpToolName`, प्रदाता मेटाडेटा)\n- सर्वर-रिपोर्टेड `isError` को `Error: ...` टेक्स्ट परिणाम में मैप करता है\n- थ्रोन ट्रांसपोर्ट/रनटाइम विफलताओं को `MCP error: ...` में मैप करता है\n- AbortError को `ToolAbortError` में अनुवादित करके abort सिमेंटिक्स को संरक्षित करता है\n\n## 5) ऑपरेटर जीवनचक्र: जोड़ें/संपादित करें/हटाएँ और लाइव अपडेट\n\nइंटरैक्टिव मोड `src/modes/controllers/mcp-command-controller.ts` में `/mcp` प्रदान करता है।\n\nसमर्थित ऑपरेशन:\n\n- `add` (विज़ार्ड या क्विक-ऐड)\n- `remove` / `rm`\n- `enable` / `disable`\n- `test`\n- `reauth` / `unauth`\n- `reload`\n\nकॉन्फ़िग लेखन एटॉमिक है (`writeMCPConfigFile`: अस्थायी फ़ाइल + नाम बदलें)।\n\nपरिवर्तनों के बाद, कंट्रोलर `#reloadMCP()` कॉल करता है:\n\n1. `mcpManager.disconnectAll()`\n2. `mcpManager.discoverAndConnect()`\n3. `session.refreshMCPTools(mcpManager.getTools())`\n\n`refreshMCPTools()` सभी `mcp_` रजिस्ट्री प्रविष्टियों को बदलता है और तुरंत नवीनतम MCP टूल सेट को पुनः सक्रिय करता है, इसलिए परिवर्तन सत्र को पुनरारंभ किए बिना प्रभावी होते हैं।\n\n### मोड अंतर\n\n- **इंटरैक्टिव/TUI मोड**: `/mcp` इन-ऐप UX देता है (विज़ार्ड, OAuth प्रवाह, कनेक्शन स्थिति टेक्स्ट, तत्काल रनटाइम रीबाइंडिंग)।\n- **SDK/हेडलेस इंटीग्रेशन**: `discoverAndLoadMCPTools()` (`src/mcp/loader.ts`) लोड किए गए टूल्स + प्रति-सर्वर त्रुटियाँ लौटाता है; कोई `/mcp` कमांड UX नहीं।\n\n## 6) उपयोगकर्ता-दृश्यमान त्रुटि सतहें\n\nसामान्य त्रुटि स्ट्रिंग्स जो उपयोगकर्ता/ऑपरेटर देखते हैं:\n\n- add/update सत्यापन विफलताएँ:\n  - `Invalid server config: ...`\n  - `Server \"<name>\" already exists in <path>`\n- क्विक-ऐड तर्क समस्याएँ:\n  - `Use either --url or -- <command...>, not both.`\n  - `--token requires --url (HTTP/SSE transport).`\n- कनेक्ट/टेस्ट विफलताएँ:\n  - `Failed to connect to \"<name>\": <message>`\n  - टाइमआउट सहायता टेक्स्ट टाइमआउट बढ़ाने का सुझाव देता है\n  - `401/403` के लिए auth सहायता टेक्स्ट\n- auth/OAuth प्रवाह:\n  - `Authentication required ... OAuth endpoints could not be discovered`\n  - `OAuth flow timed out. Please try again.`\n  - `OAuth authentication failed: ...`\n- अक्षम सर्वर उपयोग:\n  - `Server \"<name>\" is disabled. Run /mcp enable <name> first.`\n\nडिस्कवरी में खराब स्रोत JSON को आम तौर पर चेतावनियों/लॉग के रूप में संभाला जाता है; config-writer पथ स्पष्ट त्रुटियाँ फेंकते हैं।\n\n## 7) व्यावहारिक ऑथरिंग मार्गदर्शन\n\nइस कोडबेस में मजबूत MCP ऑथरिंग के लिए:\n\n1. सभी MCP-सक्षम कॉन्फ़िग स्रोतों में सर्वर नाम विश्व स्तर पर अद्वितीय रखें।\n2. उत्पन्न `mcp_*` टूल नामों में सैनिटाइज़्ड-नाम टकराव से बचने के लिए अल्फ़ान्यूमेरिक/अंडरस्कोर नाम पसंद करें।\n3. आकस्मिक stdio डिफ़ॉल्ट से बचने के लिए स्पष्ट `type` का उपयोग करें।\n4. `enabled: false` को हार्ड-ऑफ़ मानें: सर्वर रनटाइम कनेक्ट सेट से बाहर हो जाता है।\n5. OAuth कॉन्फ़िग के लिए, एक मान्य `credentialId` स्टोर करें; अन्यथा auth इंजेक्शन छोड़ दिया जाता है।\n6. यदि कमांड-आधारित सीक्रेट रिज़ॉल्यूशन (`!cmd`) का उपयोग कर रहे हैं, तो सत्यापित करें कि कमांड आउटपुट स्थिर और गैर-रिक्त है।\n\n## कार्यान्वयन फ़ाइलें\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts)\n- [`src/mcp/config-writer.ts`](../../packages/coding-agent/src/mcp/config-writer.ts)\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts)\n- [`src/discovery/mcp-json.ts`](../../packages/coding-agent/src/discovery/mcp-json.ts)\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/config/resolve-config-value.ts`](../../packages/coding-agent/src/config/resolve-config-value.ts)\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts)\n",
	"hi/natives/natives-addon-loader-runtime.md": "---\ntitle: नेटिव्स एडऑन लोडर रनटाइम\ndescription: >-\n  N-API एडऑन लोडर रनटाइम जिसमें प्लेटफ़ॉर्म डिटेक्शन, फॉलबैक स्ट्रैटेजी और\n  मॉड्यूल रिज़ॉल्यूशन शामिल है।\nsidebar:\n  order: 3\n  label: एडऑन लोडर\ni18n:\n  sourceHash: 743ea3e32c7c\n  translator: machine\n---\n\n# नेटिव्स एडऑन लोडर रनटाइम\n\nयह दस्तावेज़ `@f5-sales-demo/pi-natives` में एडऑन लोडिंग/वैलिडेशन लेयर का गहराई से विश्लेषण करता है: `native.ts` यह कैसे तय करता है कि कौन सी `.node` फ़ाइल लोड करनी है, एम्बेडेड पेलोड एक्सट्रैक्शन कब चलता है, और स्टार्टअप विफलताओं की रिपोर्ट कैसे की जाती है।\n\n## इम्प्लीमेंटेशन फ़ाइलें\n\n- `packages/natives/src/native.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/package.json`\n\n## दायरा और जिम्मेदारी\n\nलोडर/रनटाइम की जिम्मेदारियाँ जानबूझकर सीमित रखी गई हैं:\n\n- एडऑन फ़ाइलनाम और डायरेक्टरी के लिए प्लेटफ़ॉर्म/CPU-अवेयर कैंडिडेट सूची बनाना।\n- वैकल्पिक रूप से एक एम्बेडेड एडऑन को वर्शन्ड पर-यूज़र कैश डायरेक्टरी में मटीरियलाइज़ करना।\n- निर्धारित क्रम में कैंडिडेट्स का प्रयास करना।\n- बाइंडिंग्स एक्सपोज़ करने से पहले `validateNative` के माध्यम से पुराने या असंगत एडऑन को रिजेक्ट करना।\n\nयहाँ दायरे से बाहर: मॉड्यूल-स्पेसिफिक grep/text/highlight व्यवहार।\n\n## रनटाइम इनपुट और व्युत्पन्न स्थिति\n\nमॉड्यूल इनिशियलाइज़ेशन (`export const native = loadNative();`) पर, `native.ts` स्टैटिक कॉन्टेक्स्ट कंप्यूट करता है:\n\n- **प्लेटफ़ॉर्म टैग**: ``${process.platform}-${process.arch}`` (उदाहरण के लिए `darwin-arm64`)।\n- **पैकेज वर्शन**: `packages/natives/package.json` से (`version` फ़ील्ड)।\n- **कोर डायरेक्टरीज़**:\n  - `nativeDir`: पैकेज-लोकल `packages/natives/native`।\n  - `execDir`: `process.execPath` वाली डायरेक्टरी।\n  - `versionedDir`: `<getNativesDir()>/<packageVersion>`।\n  - `userDataDir` फॉलबैक:\n    - Windows: `%LOCALAPPDATA%/xcsh` (या `%USERPROFILE%/AppData/Local/xcsh`)।\n    - नॉन-Windows: `~/.local/bin`।\n- **कम्पाइल्ड-बाइनरी मोड** (`isCompiledBinary`): true अगर इनमें से कोई भी हो:\n  - `PI_COMPILED` env var सेट हो, या\n  - `import.meta.url` में Bun-एम्बेडेड मार्कर्स हों (`$bunfs`, `~BUN`, `%7EBUN`)।\n- **वेरिएंट ओवरराइड**: `PI_NATIVE_VARIANT` (केवल `modern`/`baseline`; अमान्य मान अनदेखे)।\n- **चयनित वेरिएंट**: स्पष्ट ओवरराइड, अन्यथा x64 पर रनटाइम AVX2 डिटेक्शन (AVX2 हो तो `modern`, नहीं तो `baseline`)।\n\n## प्लेटफ़ॉर्म सपोर्ट और टैग रिज़ॉल्यूशन\n\n`SUPPORTED_PLATFORMS` निम्न तक सीमित है:\n\n- `linux-x64`\n- `linux-arm64`\n- `darwin-x64`\n- `darwin-arm64`\n- `win32-x64`\n\nव्यवहार विवरण:\n\n- असमर्थित प्लेटफ़ॉर्म्स को शुरुआत में रिजेक्ट नहीं किया जाता।\n- लोडर फिर भी सभी कम्प्यूटेड कैंडिडेट्स पहले आज़माता है।\n- यदि कुछ भी लोड नहीं होता, तो यह समर्थित टैग्स की सूची के साथ स्पष्ट असमर्थित-प्लेटफ़ॉर्म एरर थ्रो करता है।\n\nयह नज़दीकी-मिस केस के लिए उपयोगी डायग्नोस्टिक्स बनाए रखता है, जबकि वास्तव में असमर्थित टारगेट्स के लिए पूरी तरह विफल होता है।\n\n## वेरिएंट चयन (`modern` / `baseline` / डिफ़ॉल्ट)\n\n### x64 व्यवहार\n\n1. यदि `PI_NATIVE_VARIANT` `modern` या `baseline` है, तो वह मान जीतता है।\n2. अन्यथा AVX2 सपोर्ट डिटेक्ट करें:\n   - Linux: `avx2` के लिए `/proc/cpuinfo` स्कैन करें।\n   - macOS: `sysctl` क्वेरी करें (`machdep.cpu.leaf7_features`, फॉलबैक `machdep.cpu.features`)।\n   - Windows: PowerShell `[System.Runtime.Intrinsics.X86.Avx2]::IsSupported` चलाएं।\n3. परिणाम:\n   - AVX2 उपलब्ध -> `modern`\n   - AVX2 अनुपलब्ध/अनडिटेक्टेबल -> `baseline`\n\n### नॉन-x64 व्यवहार\n\n- कोई वेरिएंट उपयोग नहीं होता; लोडर डिफ़ॉल्ट फ़ाइलनाम (`pi_natives.<platform>-<arch>.node`) पर रहता है।\n\n### फ़ाइलनाम कंस्ट्रक्शन\n\n`tag = <platform>-<arch>` मानते हुए:\n\n- नॉन-x64 या कोई वेरिएंट नहीं: `pi_natives.<tag>.node`\n- x64 + `modern`: क्रम में प्रयास करें\n  1. `pi_natives.<tag>-modern.node`\n  2. `pi_natives.<tag>-baseline.node` (जानबूझकर फॉलबैक)\n- x64 + `baseline`: केवल `pi_natives.<tag>-baseline.node`\n\n`addonLabel` जो अंतिम एरर मैसेज में उपयोग होता है वह `<tag>` या `<tag> (<variant>)` है।\n\n## कैंडिडेट पाथ कंस्ट्रक्शन और फॉलबैक ऑर्डरिंग\n\n`native.ts` किसी भी `require(...)` कॉल से पहले कैंडिडेट पूल बनाता है।\n\n### रिलीज़ कैंडिडेट्स\n\nवेरिएंट-रिज़ॉल्व्ड फ़ाइलनाम सूची से बने और इस क्रम में खोजे गए:\n\n- **नॉन-कम्पाइल्ड रनटाइम**:\n  1. `<nativeDir>/<filename>`\n  2. `<execDir>/<filename>`\n\n- **कम्पाइल्ड रनटाइम** (`PI_COMPILED` या Bun एम्बेडेड मार्कर्स):\n  1. `<versionedDir>/<filename>`\n  2. `<userDataDir>/<filename>`\n  3. `<nativeDir>/<filename>`\n  4. `<execDir>/<filename>`\n\n`dedupedCandidates` पहली घटना के क्रम को बनाए रखते हुए डुप्लिकेट हटाता है।\n\n### अंतिम रनटाइम सीक्वेंस\n\nलोड टाइम पर:\n\n1. वैकल्पिक एम्बेडेड एक्सट्रैक्शन कैंडिडेट (यदि उत्पन्न हो) सबसे आगे डाला जाता है।\n2. शेष डिडुप्लिकेटेड कैंडिडेट्स क्रम में आज़माए जाते हैं।\n3. पहला कैंडिडेट जो `require(...)` और `validateNative(...)` दोनों पास करे, जीतता है।\n\n## एम्बेडेड एडऑन एक्सट्रैक्शन लाइफसाइकिल\n\n`embedded-addon.ts` एक जेनरेटेड मैनिफेस्ट शेप परिभाषित करता है:\n\n- `platformTag`\n- `version`\n- `files[]` जहाँ प्रत्येक एंट्री में `variant`, `filename`, `filePath` हो\n\nवर्तमान चेक-इन डिफ़ॉल्ट `embeddedAddon: null` है; कम्पाइल्ड आर्टिफैक्ट्स इसे वास्तविक मेटाडेटा से बदल सकते हैं।\n\n### एक्सट्रैक्शन स्टेट मशीन\n\nएक्सट्रैक्शन (`maybeExtractEmbeddedAddon`) केवल तब चलता है जब सभी गेट्स पास हों:\n\n1. `isCompiledBinary === true`\n2. `embeddedAddon !== null`\n3. `embeddedAddon.platformTag === platformTag`\n4. `embeddedAddon.version === packageVersion`\n5. एक वेरिएंट-उपयुक्त एम्बेडेड फ़ाइल मिले\n\nवेरिएंट फ़ाइल चयन रनटाइम वेरिएंट इंटेंट को दर्शाता है:\n\n- नॉन-x64: `default` को प्राथमिकता दें, फिर पहली उपलब्ध फ़ाइल।\n- x64 + `modern`: `modern` को प्राथमिकता दें, `baseline` पर फॉलबैक।\n- x64 + `baseline`: `baseline` आवश्यक।\n\nमटीरियलाइज़ेशन व्यवहार:\n\n1. `<versionedDir>` सुनिश्चित करें कि मौजूद हो (`mkdirSync(..., { recursive: true })`)।\n2. यदि `<versionedDir>/<selected filename>` पहले से मौजूद है, तो उसे पुनः उपयोग करें (कोई री-राइट नहीं)।\n3. अन्यथा एम्बेडेड सोर्स `filePath` पढ़ें और टारगेट फ़ाइल लिखें।\n4. उच्चतम-प्राथमिकता लोड प्रयास के लिए टारगेट पाथ रिटर्न करें।\n\nविफलता पर, एक्सट्रैक्शन तुरंत क्रैश नहीं होता; यह एक एरर एंट्री (डायरेक्टरी क्रिएशन या राइट विफलता) जोड़ता है और लोडर सामान्य कैंडिडेट प्रोबिंग पर आगे बढ़ता है।\n\n## लाइफसाइकिल और स्टेट ट्रांज़िशन\n\n```text\nInit\n  -> Compute platform/version/variant/candidate lists\n  -> (Compiled + embedded manifest matches?)\n       yes -> Try extract embedded to versionedDir (record errors, continue)\n       no  -> Skip extraction\n  -> For each runtime candidate in order:\n       require(candidate)\n       -> success: validateNative\n            -> pass: return bindings (READY)\n            -> fail: record error, continue\n       -> failure: record error, continue\n  -> none loaded:\n       if unsupported platform tag -> throw Unsupported platform\n       else -> throw Failed to load (full tried-path diagnostics + hints)\n```\n\n## `validateNative` कॉन्ट्रैक्ट चेक्स\n\n`validateNative(bindings, source)` स्टार्टअप पर `NativeBindings` पर फंक्शन-ओनली कॉन्ट्रैक्ट लागू करता है।\n\nमैकेनिक्स:\n\n- प्रत्येक आवश्यक एक्सपोर्ट नाम के लिए, यह जाँचता है कि `typeof bindings[name] === \"function\"`।\n- गायब नाम एकत्रित किए जाते हैं।\n- यदि कोई गायब हों, तो लोडर थ्रो करता है:\n  - सोर्स एडऑन पाथ,\n  - गायब एक्सपोर्ट सूची,\n  - रीबिल्ड कमांड हिंट।\n\nयह पुराने बाइनरी, आंशिक बिल्ड और सिम्बल/नाम ड्रिफ्ट के विरुद्ध एक हार्ड कम्पैटिबिलिटी गेट है।\n\n### JS API ↔ नेटिव एक्सपोर्ट मैपिंग (वैलिडेशन गेट)\n\n| `validateNative` में चेक किया गया JS बाइंडिंग नाम | अपेक्षित नेटिव एक्सपोर्ट नाम |\n| --- | --- |\n| `grep` | `grep` |\n| `glob` | `glob` |\n| `highlightCode` | `highlightCode` |\n| `executeShell` | `executeShell` |\n| `PtySession` | `PtySession` |\n| `Shell` | `Shell` |\n| `visibleWidth` | `visibleWidth` |\n| `getSystemInfo` | `getSystemInfo` |\n| `getWorkProfile` | `getWorkProfile` |\n| `invalidateFsScanCache` | `invalidateFsScanCache` |\n\nनोट: `bindings.ts` केवल बेस `cancelWork(id)` मेम्बर डिक्लेयर करता है; मॉड्यूल `types.ts` फ़ाइलें अतिरिक्त सिम्बल्स को डिक्लेरेशन-मर्ज करती हैं जो `validateNative` लागू करता है।\n\n## विफलता व्यवहार और डायग्नोस्टिक्स\n\n## असमर्थित प्लेटफ़ॉर्म\n\nयदि सभी कैंडिडेट्स विफल हों और `platformTag` `SUPPORTED_PLATFORMS` में न हो, तो लोडर थ्रो करता है:\n\n- `Unsupported platform: <tag>`\n- पूर्ण समर्थित-प्लेटफ़ॉर्म सूची\n- स्पष्ट इश्यू-रिपोर्टिंग मार्गदर्शन\n\n## पुराना बाइनरी / मिसमैच लक्षण\n\nविशिष्ट पुराना मिसमैच सिग्नल:\n\n- `Native addon missing exports (<candidate>). Missing: ...`\n\nसामान्य कारण:\n\n- पिछले पैकेज वर्शन/API शेप से पुरानी `.node` बाइनरी।\n- गलत वेरिएंट आर्टिफैक्ट चयनित (x64 के लिए)।\n- लोड किए गए आर्टिफैक्ट में नया Rust एक्सपोर्ट मौजूद नहीं।\n\nलोडर व्यवहार:\n\n- प्रति-कैंडिडेट मिसिंग-एक्सपोर्ट विफलताएँ रिकॉर्ड करता है।\n- शेष कैंडिडेट्स की प्रोबिंग जारी रखता है।\n- यदि कोई कैंडिडेट वैलिडेट न हो, तो अंतिम एरर में प्रत्येक विफलता मैसेज के साथ हर आज़माया गया पाथ शामिल होता है।\n\n## कम्पाइल्ड-बाइनरी स्टार्टअप विफलताएँ\n\nकम्पाइल्ड मोड में अंतिम डायग्नोस्टिक्स में शामिल हैं:\n\n- अपेक्षित वर्शन्ड कैश टारगेट पाथ (`<versionedDir>/<filename>`),\n- पुराने `<versionedDir>` को हटाने और पुनः चलाने का समाधान,\n- प्रत्येक अपेक्षित फ़ाइलनाम के लिए डायरेक्ट रिलीज़ डाउनलोड `curl` कमांड।\n\n## नॉन-कम्पाइल्ड स्टार्टअप विफलताएँ\n\nसामान्य पैकेज/रनटाइम मोड में अंतिम डायग्नोस्टिक्स में शामिल हैं:\n\n- रीइंस्टॉल हिंट (`bun install @f5-sales-demo/pi-natives`),\n- लोकल रीबिल्ड कमांड (`bun --cwd=packages/natives run build`),\n- वैकल्पिक x64 वेरिएंट बिल्ड हिंट (`TARGET_VARIANT=baseline|modern ...`)।\n\n## रनटाइम व्यवहार\n\n- लोडर हमेशा रिलीज़ कैंडिडेट चेन उपयोग करता है।\n- `PI_DEV` सेट करने से केवल प्रति-कैंडिडेट कंसोल डायग्नोस्टिक्स सक्षम होते हैं (`Loaded native addon...` और लोड एरर)।\n",
	"hi/natives/natives-architecture.md": "---\ntitle: Natives आर्किटेक्चर\ndescription: >-\n  Rust N-API native addon आर्किटेक्चर जो TypeScript और प्लेटफ़ॉर्म-विशिष्ट\n  ऑपरेशन्स के बीच सेतु का काम करता है।\nsidebar:\n  order: 1\n  label: आर्किटेक्चर\ni18n:\n  sourceHash: d38ed2437bb7\n  translator: machine\n---\n\n# Natives आर्किटेक्चर\n\n`@f5-sales-demo/pi-natives` एक तीन-स्तरीय स्टैक है:\n\n1. **TypeScript wrapper/API लेयर** स्थिर JS/TS एंट्रीपॉइंट्स को एक्सपोज़ करती है।\n2. **Addon लोडिंग/वैलिडेशन लेयर** वर्तमान रनटाइम के लिए `.node` बाइनरी को रिज़ॉल्व और वैलिडेट करती है।\n3. **Rust N-API मॉड्यूल लेयर** JS को एक्सपोर्ट किए जाने वाले परफ़ॉर्मेंस-क्रिटिकल प्रिमिटिव्स को इम्प्लीमेंट करती है।\n\nयह दस्तावेज़ गहन मॉड्यूल-स्तरीय डॉक्स के लिए आधार है।\n\n## इम्प्लीमेंटेशन फ़ाइलें\n\n- `packages/natives/src/index.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `crates/pi-natives/src/lib.rs`\n\n## लेयर 1: TypeScript wrapper/API लेयर\n\n`packages/natives/src/index.ts` पब्लिक बैरल है। यह एक्सपोर्ट्स को क्षमता डोमेन के अनुसार समूहित करता है और रॉ N-API बाइंडिंग्स को सीधे एक्सपोज़ करने के बजाय टाइप्ड रैपर्स को री-एक्सपोर्ट करता है।\n\nवर्तमान शीर्ष-स्तरीय समूह:\n\n- **Search/text प्रिमिटिव्स**: `grep`, `glob`, `text`, `highlight`\n- **Execution/process/terminal प्रिमिटिव्स**: `shell`, `pty`, `ps`, `keys`\n- **System/media/conversion प्रिमिटिव्स**: `image`, `html`, `clipboard`, `system-info`, `work`\n\n`packages/natives/src/bindings.ts` बेस इंटरफ़ेस कॉन्ट्रैक्ट को परिभाषित करता है:\n\n- `NativeBindings` शेयर्ड सदस्यों (`cancelWork(id: number)`) से शुरू होता है\n- मॉड्यूल-विशिष्ट बाइंडिंग्स प्रत्येक मॉड्यूल की `types.ts` से डिक्लेरेशन मर्जिंग द्वारा जोड़ी जाती हैं\n- `Cancellable` उन रैपर्स के लिए timeout और abort-signal ऑप्शन्स को मानकीकृत करता है जो कैंसिलेशन एक्सपोज़ करते हैं\n\n**गारंटीड कॉन्ट्रैक्ट (API-फेसिंग):** उपभोक्ता `@f5-sales-demo/pi-natives` से इम्पोर्ट करते हैं और टाइप्ड रैपर्स का उपयोग करते हैं।\n\n**इम्प्लीमेंटेशन डिटेल (बदल सकता है):** डिक्लेरेशन मर्जिंग और आंतरिक रैपर लेआउट (`src/<module>/index.ts`, `src/<module>/types.ts`)।\n\n## लेयर 2: Addon लोडिंग और वैलिडेशन\n\n`packages/natives/src/native.ts` रनटाइम addon चयन, वैकल्पिक एक्सट्रैक्शन, और एक्सपोर्ट वैलिडेशन का स्वामित्व रखता है।\n\n### कैंडिडेट रिज़ॉल्यूशन मॉडल\n\n- प्लेटफ़ॉर्म टैग `\"${process.platform}-${process.arch}\"` है।\n- वर्तमान में समर्थित टैग हैं:\n  - `linux-x64`\n  - `linux-arm64`\n  - `darwin-x64`\n  - `darwin-arm64`\n  - `win32-x64`\n- x64 CPU वेरिएंट्स का उपयोग कर सकता है:\n  - `modern` (AVX2-सक्षम)\n  - `baseline` (फ़ॉलबैक)\n- गैर-x64 डिफ़ॉल्ट फ़ाइलनाम का उपयोग करता है (कोई वेरिएंट सफ़िक्स नहीं)।\n\nफ़ाइलनाम रणनीति:\n\n- रिलीज़: `pi_natives.<platform>-<arch>.node`\n- x64 वेरिएंट रिलीज़: `pi_natives.<platform>-<arch>-modern.node` और/या `...-baseline.node`\n- `PI_DEV` लोडर डायग्नोस्टिक्स सक्षम करता है लेकिन addon फ़ाइलनाम नहीं बदलता\n\n### प्लेटफ़ॉर्म-विशिष्ट वेरिएंट डिटेक्शन\n\nx64 के लिए, वेरिएंट चयन उपयोग करता है:\n\n- **Linux**: `/proc/cpuinfo`\n- **macOS**: `sysctl machdep.cpu.leaf7_features` / `machdep.cpu.features`\n- **Windows**: `System.Runtime.Intrinsics.X86.Avx2` के लिए PowerShell जाँच\n\n`PI_NATIVE_VARIANT` स्पष्ट रूप से `modern` या `baseline` को बाध्य कर सकता है।\n\n### बाइनरी वितरण और एक्सट्रैक्शन मॉडल\n\n`packages/natives/package.json` प्रकाशित फ़ाइलों में `src` और `native` दोनों शामिल करता है। `native/` डायरेक्टरी प्रीबिल्ट प्लेटफ़ॉर्म आर्टिफ़ैक्ट्स स्टोर करती है।\n\nकंपाइल्ड बाइनरीज़ (`PI_COMPILED` या Bun एम्बेडेड रनटाइम मार्कर्स) के लिए, लोडर व्यवहार है:\n\n1. वर्शन्ड यूज़र कैश पथ जाँचें: `<getNativesDir()>/<packageVersion>/...`\n2. लीगेसी कंपाइल्ड-बाइनरी लोकेशन जाँचें:\n   - Windows: `%LOCALAPPDATA%/xcsh` (फ़ॉलबैक `%USERPROFILE%/AppData/Local/xcsh`)\n   - गैर-Windows: `~/.local/bin`\n3. पैकेज्ड `native/` और एक्ज़ीक्यूटेबल डायरेक्टरी कैंडिडेट्स पर फ़ॉलबैक करें\n\nयदि एक एम्बेडेड addon मैनिफ़ेस्ट मौजूद है (`embedded-addon.ts` जो `scripts/embed-native.ts` द्वारा जेनरेट किया गया), तो `native.ts` लोडिंग से पहले मैचिंग एम्बेडेड बाइनरी को वर्शन्ड कैश डायरेक्टरी में मटीरियलाइज़ कर सकता है।\n\n### वैलिडेशन और विफलता मोड\n\n`require(candidate)` के बाद, `validateNative(...)` आवश्यक एक्सपोर्ट्स की पुष्टि करता है (उदाहरण के लिए `grep`, `glob`, `highlightCode`, `PtySession`, `Shell`, `getSystemInfo`, `getWorkProfile`, `invalidateFsScanCache`)।\n\nविफलता पथ स्पष्ट हैं:\n\n- **असमर्थित प्लेटफ़ॉर्म टैग**: समर्थित प्लेटफ़ॉर्म सूची के साथ त्रुटि फेंकता है\n- **कोई लोड करने योग्य कैंडिडेट नहीं**: सभी प्रयास किए गए पथों और सुधार संकेतों के साथ त्रुटि फेंकता है\n- **गायब एक्सपोर्ट्स**: सटीक गायब नामों और रीबिल्ड कमांड के साथ त्रुटि फेंकता है\n- **एम्बेडेड एक्सट्रैक्शन त्रुटियाँ**: डायरेक्टरी/राइट विफलताओं को रिकॉर्ड करता है और उन्हें अंतिम लोड डायग्नोस्टिक्स में शामिल करता है\n\n**गारंटीड कॉन्ट्रैक्ट (API-फेसिंग):** addon लोड या तो वैलिडेटेड बाइंडिंग सेट के साथ सफल होता है या कार्रवाई योग्य त्रुटि टेक्स्ट के साथ तुरंत विफल होता है।\n\n**इम्प्लीमेंटेशन डिटेल (बदल सकता है):** सटीक कैंडिडेट खोज क्रम और कंपाइल्ड-बाइनरी फ़ॉलबैक पथ क्रम।\n\n## लेयर 3: Rust N-API मॉड्यूल लेयर\n\n`crates/pi-natives/src/lib.rs` Rust एंट्री मॉड्यूल है जो एक्सपोर्टेड मॉड्यूल स्वामित्व घोषित करता है:\n\n- `clipboard`\n- `fd`\n- `fs_cache`\n- `glob`\n- `glob_util`\n- `grep`\n- `highlight`\n- `html`\n- `image`\n- `keys`\n- `prof`\n- `ps`\n- `pty`\n- `shell`\n- `system_info`\n- `task`\n- `text`\n\nये मॉड्यूल `native.ts` द्वारा उपभोग और वैलिडेट किए जाने वाले N-API सिम्बल्स को इम्प्लीमेंट करते हैं। JS-स्तरीय नाम `packages/natives/src` में TS रैपर्स के माध्यम से प्रदर्शित किए जाते हैं।\n\n**गारंटीड कॉन्ट्रैक्ट (API-फेसिंग):** Rust मॉड्यूल एक्सपोर्ट्स को `validateNative` और रैपर मॉड्यूल्स द्वारा अपेक्षित बाइंडिंग नामों से मेल खाना चाहिए।\n\n**इम्प्लीमेंटेशन डिटेल (बदल सकता है):** आंतरिक Rust मॉड्यूल विभाजन और हेल्पर मॉड्यूल सीमाएँ (`glob_util`, `task`, आदि)।\n\n## स्वामित्व सीमाएँ\n\nआर्किटेक्चर स्तर पर, स्वामित्व निम्नानुसार विभाजित है:\n\n- **TS wrapper/API स्वामित्व (`packages/natives/src`)**\n  - पब्लिक API ग्रुपिंग, ऑप्शन टाइपिंग, और स्थिर JS एर्गोनॉमिक्स\n  - कॉलर्स को एक्सपोज़ किया गया कैंसिलेशन सरफ़ेस (`timeoutMs`, `AbortSignal`)\n- **लोडर स्वामित्व (`packages/natives/src/native.ts`)**\n  - रनटाइम बाइनरी चयन\n  - CPU वेरिएंट चयन और ओवरराइड हैंडलिंग\n  - कंपाइल्ड-बाइनरी एक्सट्रैक्शन और कैंडिडेट प्रोबिंग\n  - आवश्यक नेटिव एक्सपोर्ट्स की कठोर वैलिडेशन\n- **Rust स्वामित्व (`crates/pi-natives/src`)**\n  - एल्गोरिदमिक और सिस्टम-स्तरीय इम्प्लीमेंटेशन\n  - प्लेटफ़ॉर्म-नेटिव व्यवहार और परफ़ॉर्मेंस-संवेदनशील लॉजिक\n  - N-API सिम्बल इम्प्लीमेंटेशन जिसे TS रैपर्स उपभोग करते हैं\n\n## रनटाइम फ़्लो (उच्च स्तरीय)\n\n1. उपभोक्ता `@f5-sales-demo/pi-natives` से इम्पोर्ट करता है।\n2. रैपर मॉड्यूल सिंगलटन `native` बाइंडिंग में कॉल करता है।\n3. `native.ts` प्लेटफ़ॉर्म/आर्च/वेरिएंट के लिए कैंडिडेट बाइनरी चुनता है।\n4. कंपाइल्ड डिस्ट्रीब्यूशन के लिए वैकल्पिक एम्बेडेड बाइनरी एक्सट्रैक्शन होता है।\n5. Addon लोड किया जाता है और एक्सपोर्ट सेट वैलिडेट किया जाता है।\n6. रैपर कॉलर को टाइप्ड रिज़ल्ट्स लौटाता है।\n\n## शब्दावली\n\n- **Native addon**: एक `.node` बाइनरी जो Node-API (N-API) के माध्यम से लोड की जाती है।\n- **प्लेटफ़ॉर्म टैग**: रनटाइम ट्यूपल `platform-arch` (उदाहरण के लिए `darwin-arm64`)।\n- **वेरिएंट**: x64 CPU-विशिष्ट बिल्ड फ़्लेवर (`modern` AVX2, `baseline` फ़ॉलबैक)।\n- **रैपर**: TS फ़ंक्शन/क्लास जो रॉ नेटिव एक्सपोर्ट्स पर टाइप्ड API प्रदान करता है।\n- **डिक्लेरेशन मर्जिंग**: TS तकनीक जो मॉड्यूल `types.ts` फ़ाइलों द्वारा `NativeBindings` को विस्तारित करने के लिए उपयोग की जाती है।\n- **कंपाइल्ड बाइनरी मोड**: रनटाइम मोड जहाँ CLI बंडल किया जाता है और नेटिव addons केवल पैकेज-लोकल पथों के बजाय एक्सट्रैक्टेड/कैश पथों से रिज़ॉल्व किए जाते हैं।\n- **एम्बेडेड addon**: बिल्ड आर्टिफ़ैक्ट मेटाडेटा और फ़ाइल संदर्भ जो `embedded-addon.ts` में जेनरेट किए जाते हैं ताकि कंपाइल्ड बाइनरीज़ मैचिंग `.node` पेलोड्स एक्सट्रैक्ट कर सकें।\n- **वैलिडेशन गेट**: `validateNative(...)` जाँच जो आवश्यक एक्सपोर्ट्स गायब होने पर पुरानी/बेमेल बाइनरीज़ को अस्वीकार करती है।\n",
	"hi/natives/natives-binding-contract.md": "---\ntitle: नेटिव्स बाइंडिंग कॉन्ट्रैक्ट (TypeScript पक्ष)\ndescription: >-\n  Rust नेटिव फ़ंक्शन में N-API के माध्यम से कॉल करने के लिए TypeScript-पक्षीय\n  बाइंडिंग कॉन्ट्रैक्ट।\nsidebar:\n  order: 2\n  label: बाइंडिंग कॉन्ट्रैक्ट\ni18n:\n  sourceHash: 36dc5fed1f0a\n  translator: machine\n---\n\n# नेटिव्स बाइंडिंग कॉन्ट्रैक्ट (TypeScript पक्ष)\n\nयह दस्तावेज़ TypeScript-पक्षीय कॉन्ट्रैक्ट को परिभाषित करता है जो `@f5-sales-demo/pi-natives` कॉलर्स और लोड किए गए N-API addon के बीच स्थित होता है।\n\nयह तीन भागों पर केंद्रित है:\n\n1. कॉन्ट्रैक्ट आकार (`NativeBindings` + मॉड्यूल ऑगमेंटेशन),\n2. रैपर व्यवहार (`src/<module>/index.ts`),\n3. पब्लिक एक्सपोर्ट सतह (`src/index.ts`)।\n\n## इम्प्लीमेंटेशन फ़ाइलें\n\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/system-info/types.ts`\n- `packages/natives/src/system-info/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/work/types.ts`\n- `packages/natives/src/work/index.ts`\n\n## कॉन्ट्रैक्ट मॉडल\n\n`packages/natives/src/bindings.ts` बेस कॉन्ट्रैक्ट को परिभाषित करता है:\n\n- `NativeBindings` (बेस इंटरफेस, वर्तमान में `cancelWork(id: number): void` शामिल है)\n- `Cancellable` (`timeoutMs?: number`, `signal?: AbortSignal`)\n- `TsFunc<T>` कॉलबैक आकार जो N-API थ्रेडसेफ कॉलबैक द्वारा उपयोग किया जाता है\n\nप्रत्येक मॉड्यूल डिक्लेरेशन मर्जिंग द्वारा अपने स्वयं के फ़ील्ड जोड़ता है:\n\n```ts\n// packages/natives/src/<module>/types.ts\ndeclare module \"../bindings\" {\n interface NativeBindings {\n  grep(options: GrepOptions, onMatch?: TsFunc<GrepMatch>): Promise<GrepResult>;\n }\n}\n```\n\nयह एक मोनोलिथिक केंद्रीय टाइप फ़ाइल के बिना एकल समग्र बाइंडिंग इंटरफेस बनाए रखता है।\n\n## डिक्लेरेशन-मर्जिंग लाइफसाइकिल और स्टेट ट्रांज़िशन\n\n### 1) कम्पाइल-टाइम टाइप असेंबली\n\n- `bindings.ts` बेस `NativeBindings` सिंबल प्रदान करता है।\n- प्रत्येक `src/<module>/types.ts` `NativeBindings` को ऑगमेंट करता है।\n- `src/native.ts` सभी `./<module>/types` फ़ाइलों को साइड इफेक्ट्स के लिए इम्पोर्ट करता है ताकि मर्ज किया गया कॉन्ट्रैक्ट वहाँ स्कोप में हो जहाँ `NativeBindings` का उपयोग किया जाता है।\n\nस्टेट ट्रांज़िशन: **बेस कॉन्ट्रैक्ट** → **मर्ज किया गया कॉन्ट्रैक्ट**।\n\n### 2) रनटाइम addon लोड और वैलिडेशन गेट\n\n- `src/native.ts` कैंडिडेट `.node` बाइनरी लोड करता है।\n- लोड किए गए ऑब्जेक्ट को `NativeBindings` के रूप में माना जाता है और तुरंत `validateNative(...)` के माध्यम से पास किया जाता है।\n- `validateNative` `typeof bindings[name] === \"function\"` द्वारा आवश्यक एक्सपोर्ट कीज़ को वेरीफाई करता है।\n\nस्टेट ट्रांज़िशन: **अनट्रस्टेड addon ऑब्जेक्ट** → **वैलिडेटेड नेटिव बाइंडिंग ऑब्जेक्ट** (या हार्ड फेलियर)।\n\n### 3) रैपर इनवोकेशन\n\n- `src/<module>/index.ts` में मॉड्यूल रैपर `native.<export>` को कॉल करते हैं।\n- रैपर डिफ़ॉल्ट और कॉलबैक आकार को अनुकूलित करते हैं (JS APIs में `(err, value)` से केवल-वैल्यू कॉलबैक पैटर्न)।\n- `src/index.ts` मॉड्यूल रैपर/टाइप्स को पब्लिक पैकेज API के रूप में री-एक्सपोर्ट करता है।\n\nस्टेट ट्रांज़िशन: **वैलिडेटेड रॉ बाइंडिंग** → **एर्गोनोमिक पब्लिक API**।\n\n## रैपर की जिम्मेदारियाँ\n\nरैपर जानबूझकर पतले होते हैं; वे नेटिव लॉजिक को फिर से इम्प्लीमेंट नहीं करते।\n\nप्राथमिक जिम्मेदारियाँ:\n\n- **आर्ग्युमेंट नॉर्मलाइज़ेशन/डिफ़ॉल्टिंग**\n  - `glob()` `options.path` को ऐब्सोल्यूट पाथ पर रिज़ॉल्व करता है और `hidden`, `gitignore`, `recursive` को डिफ़ॉल्ट करता है।\n  - `hasMatch()` नेटिव कॉल से पहले डिफ़ॉल्ट फ्लैग्स (`ignoreCase`, `multiline`) भरता है।\n- **कॉलबैक अडैप्टेशन**\n  - `grep()`, `glob()`, `executeShell()` `TsFunc<T>` (`error, value`) को केवल सफल मान प्राप्त करने वाले यूज़र कॉलबैक में कनवर्ट करते हैं।\n- **नेटिव कॉल के आसपास एनवायरनमेंट या पॉलिसी व्यवहार**\n  - क्लिपबोर्ड रैपर OSC52/Termux/हेडलेस हैंडलिंग जोड़ता है और कॉपी को बेस्ट एफर्ट मानता है।\n- **पब्लिक नामकरण और री-एक्सपोर्ट क्यूरेशन**\n  - `searchContent()` नेटिव एक्सपोर्ट `search` से मैप होता है।\n\n## पब्लिक एक्सपोर्ट सतह संगठन\n\n`packages/natives/src/index.ts` कैनोनिकल पब्लिक बैरल है। यह एक्सपोर्ट को क्षमता डोमेन के अनुसार समूहित करता है:\n\n- सर्च/टेक्स्ट: `grep`, `glob`, `text`, `highlight`\n- एक्ज़ीक्यूशन/प्रोसेस/टर्मिनल: `shell`, `pty`, `ps`, `keys`\n- सिस्टम/मीडिया/कनवर्ज़न: `image`, `html`, `clipboard`, `system-info`, `work`\n\nमेंटेनर नियम: यदि कोई रैपर `src/index.ts` से री-एक्सपोर्ट नहीं किया गया है, तो वह इच्छित पब्लिक पैकेज सतह का हिस्सा नहीं है।\n\n## JS API ↔ नेटिव एक्सपोर्ट मैपिंग (प्रतिनिधि)\n\nRust पक्ष N-API एक्सपोर्ट नामों का उपयोग करता है (आमतौर पर `#[napi]` snake_case -> camelCase रूपांतरण से, और कभी-कभी स्पष्ट एलियास के साथ) जो इन बाइंडिंग कीज़ से मेल खाने चाहिए।\n\n| श्रेणी | पब्लिक JS API (रैपर) | नेटिव बाइंडिंग की | रिटर्न टाइप | Async? |\n|---|---|---|---|---|\n| Grep | `grep(options, onMatch?)` | `grep` | `Promise<GrepResult>` | हाँ |\n| Grep | `searchContent(content, options)` | `search` | `SearchResult` | नहीं |\n| Grep | `hasMatch(content, pattern, opts?)` | `hasMatch` | `boolean` | नहीं |\n| Grep | `fuzzyFind(options)` | `fuzzyFind` | `Promise<FuzzyFindResult>` | हाँ |\n| Glob | `glob(options, onMatch?)` | `glob` | `Promise<GlobResult>` | हाँ |\n| Glob | `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `void` | नहीं |\n| Shell | `executeShell(options, onChunk?)` | `executeShell` | `Promise<ShellExecuteResult>` | हाँ |\n| Shell | `Shell` | `Shell` | क्लास कंस्ट्रक्टर | लागू नहीं |\n| PTY | `PtySession` | `PtySession` | क्लास कंस्ट्रक्टर | लागू नहीं |\n| Text | `truncateToWidth(...)` | `truncateToWidth` | `string` | नहीं |\n| Text | `sliceWithWidth(...)` | `sliceWithWidth` | `SliceWithWidthResult` | नहीं |\n| Text | `visibleWidth(text)` | `visibleWidth` | `number` | नहीं |\n| Highlight | `highlightCode(code, lang, colors)` | `highlightCode` | `string` | नहीं |\n| HTML | `htmlToMarkdown(html, options?)` | `htmlToMarkdown` | `Promise<string>` | हाँ |\n| System | `getSystemInfo()` | `getSystemInfo` | `SystemInfo` | नहीं |\n| Work | `getWorkProfile(lastSeconds)` | `getWorkProfile` | `WorkProfile` | नहीं |\n| Process | `killTree(pid, signal)` | `killTree` | `number` | नहीं |\n| Process | `listDescendants(pid)` | `listDescendants` | `number[]` | नहीं |\n| Clipboard | `copyToClipboard(text)` | `copyToClipboard` | `Promise<void>` (बेस्ट एफर्ट रैपर व्यवहार) | हाँ |\n| Clipboard | `readImageFromClipboard()` | `readImageFromClipboard` | `Promise<ClipboardImage \\| null>` | हाँ |\n| Keys | `parseKey(data, kittyProtocolActive)` | `parseKey` | `string \\| null` | नहीं |\n\n## Sync बनाम async कॉन्ट्रैक्ट अंतर\n\nकॉन्ट्रैक्ट sync और async APIs को मिश्रित करता है; रैपर एक मॉडल थोपने के बजाय नेटिव कॉल स्टाइल को संरक्षित करते हैं:\n\n- **Promise-आधारित async एक्सपोर्ट** I/O या लंबे समय तक चलने वाले कार्य के लिए (`grep`, `glob`, `htmlToMarkdown`, `executeShell`, क्लिपबोर्ड, इमेज ऑपरेशन)।\n- **सिंक्रोनस एक्सपोर्ट** निर्धारक इन-मेमोरी ट्रांसफॉर्म/पार्सर के लिए (`search`, `hasMatch`, हाइलाइटिंग, टेक्स्ट विड्थ/स्लाइसिंग, की पार्सिंग, प्रोसेस क्वेरी)।\n- **कंस्ट्रक्टर एक्सपोर्ट** स्टेटफुल रनटाइम ऑब्जेक्ट के लिए (`Shell`, `PtySession`, `PhotonImage`)।\n\nमेंटेनर के लिए निहितार्थ: किसी मौजूदा एक्सपोर्ट के लिए sync ↔ async बदलना रैपर और कॉलर्स में एक ब्रेकिंग API और कॉन्ट्रैक्ट परिवर्तन है।\n\n## ऑब्जेक्ट और enum टाइपिंग पैटर्न\n\n### ऑब्जेक्ट पैटर्न (`#[napi(object)]`-स्टाइल JS ऑब्जेक्ट)\n\nTS ऑब्जेक्ट-आकार के नेटिव मानों को इंटरफेस के रूप में मॉडल करता है, उदाहरण के लिए:\n\n- `GrepResult`, `SearchResult`, `GlobResult`\n- `SystemInfo`, `WorkProfile`\n- `ClipboardImage`, `ParsedKittyResult`\n\nये कम्पाइल टाइम पर स्ट्रक्चरल कॉन्ट्रैक्ट हैं; रनटाइम आकार की शुद्धता नेटिव इम्प्लीमेंटेशन की जिम्मेदारी है।\n\n### Enum पैटर्न\n\nन्यूमेरिक नेटिव enum TS में `const enum` मानों के रूप में प्रदर्शित किए जाते हैं:\n\n- `FileType` (`1=file`, `2=dir`, `3=symlink`)\n- `ImageFormat` (`0=PNG`, `1=JPEG`, `2=WEBP`, `3=GIF`)\n- `SamplingFilter`, `Ellipsis`, `KeyEventType`\n\nकॉलर नामित enum सदस्य देखते हैं; बाइंडिंग बाउंड्री संख्याएँ पास करती है।\n\n## मिसमैच कैसे पकड़े जाते हैं\n\nमिसमैच डिटेक्शन दो परतों पर होती है:\n\n1. **कम्पाइल-टाइम TypeScript कॉन्ट्रैक्ट चेक**\n   - रैपर मर्ज किए गए `NativeBindings` के विरुद्ध `native.<name>` को कॉल करते हैं।\n   - गायब/नाम बदले हुए बाइंडिंग कीज़ रैपर में TS टाइप-चेकिंग को तोड़ते हैं।\n\n2. **`validateNative` में रनटाइम वैलिडेशन**\n   - लोड के बाद, `native.ts` आवश्यक एक्सपोर्ट की जाँच करता है और यदि कोई गायब हो तो थ्रो करता है।\n   - एरर मैसेज में गायब कीज़ और रिबिल्ड निर्देश शामिल हैं।\n\nयह सामान्य स्टेल-बाइनरी ड्रिफ्ट को पकड़ता है: रैपर/टाइप मौजूद है लेकिन लोड किए गए `.node` में एक्सपोर्ट का अभाव है।\n\n## फेलियर व्यवहार और चेतावनियाँ\n\n### लोड/वैलिडेशन फेलियर (हार्ड फेलियर)\n\n- Addon लोड फेलियर या असमर्थित प्लेटफ़ॉर्म `native.ts` में मॉड्यूल इनिट के दौरान थ्रो करता है।\n- गायब आवश्यक एक्सपोर्ट रैपर उपयोग योग्य होने से पहले थ्रो करते हैं।\n\nप्रभाव: पैकेज पहली कॉल तक फेलियर को स्थगित करने के बजाय तुरंत विफल होता है।\n\n### रैपर-स्तर व्यवहार अंतर\n\n- कुछ रैपर जानबूझकर फेलियर को नरम करते हैं (`copyToClipboard` बेस्ट एफर्ट है और नेटिव फेलियर को निगल लेता है)।\n- स्ट्रीमिंग कॉलबैक कॉलबैक एरर पेलोड को नजरअंदाज करते हैं और केवल सफल वैल्यू इवेंट फॉरवर्ड करते हैं।\n\n### टाइप-स्तर चेतावनियाँ (रनटाइम TS से अधिक सख्त)\n\n- TS के ऑप्शनल फ़ील्ड सेमेंटिक वैलिडिटी की गारंटी नहीं देते; नेटिव परत अभी भी खराब रूप से गठित मानों को अस्वीकार कर सकती है।\n- `const enum` टाइपिंग रनटाइम पर अनटाइप्ड कॉलर्स से रेंज से बाहर न्यूमेरिक मानों को नहीं रोकती।\n- `validateNative` केवल आवश्यक एक्सपोर्ट की उपस्थिति/फ़ंक्शन-नेस की जाँच करता है, न कि गहरे आर्ग्युमेंट/रिटर्न-शेप कम्पैटिबिलिटी की।\n- `bindings.ts` बेस इंटरफेस में `cancelWork(id)` शामिल करता है, लेकिन वर्तमान रनटाइम वैलिडेशन लिस्ट उस की को एनफोर्स नहीं करती।\n\n## बाइंडिंग परिवर्तनों के लिए मेंटेनर चेकलिस्ट\n\nकोई एक्सपोर्ट जोड़ते/बदलते समय, इन सभी को अपडेट करें:\n\n1. `src/<module>/types.ts` (ऑगमेंटेशन + कॉन्ट्रैक्ट टाइप्स)\n2. `src/<module>/index.ts` (रैपर व्यवहार)\n3. मॉड्यूल टाइप्स के लिए `src/native.ts` इम्पोर्ट (यदि नया मॉड्यूल है)\n4. `validateNative` आवश्यक एक्सपोर्ट चेक\n5. `src/index.ts` पब्लिक री-एक्सपोर्ट\n\nकिसी भी चरण को छोड़ने से कम्पाइल-टाइम ड्रिफ्ट या रनटाइम लोड-टाइम फेलियर होता है।\n",
	"hi/natives/natives-build-release-debugging.md": "---\ntitle: 'नेटिव्स बिल्ड, रिलीज़ और डिबगिंग रनबुक'\ndescription: 'Rust नेटिव एडऑन के लिए प्लेटफ़ॉर्म-अनुसार बिल्ड, रिलीज़ और डिबगिंग रनबुक।'\nsidebar:\n  order: 8\n  label: 'बिल्ड, रिलीज़ और डिबगिंग'\ni18n:\n  sourceHash: efe47aa5b466\n  translator: machine\n---\n\n# नेटिव्स बिल्ड, रिलीज़ और डिबगिंग रनबुक\n\nयह रनबुक वर्णन करता है कि `@f5-sales-demo/pi-natives` बिल्ड पाइपलाइन `.node` एडऑन कैसे उत्पन्न करती है, संकलित वितरण उन्हें कैसे लोड करते हैं, और लोडर/बिल्ड विफलताओं को कैसे डिबग किया जाए।\n\nयह `docs/natives-architecture.md` की आर्किटेक्चर शब्दावली का अनुसरण करता है:\n\n- **बिल्ड-टाइम आर्टिफ़ैक्ट उत्पादन** (`scripts/build-native.ts`)\n- **एम्बेडेड एडऑन मैनिफेस्ट जनरेशन** (`scripts/embed-native.ts`)\n- **रनटाइम एडऑन लोडिंग + वैलिडेशन गेट** (`src/native.ts`)\n\n## कार्यान्वयन फ़ाइलें\n\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `packages/natives/src/native.ts`\n- `crates/pi-natives/Cargo.toml`\n\n## बिल्ड पाइपलाइन अवलोकन\n\n### 1) बिल्ड एंट्रीपॉइंट्स\n\n`packages/natives/package.json` स्क्रिप्ट्स:\n\n- `bun scripts/build-native.ts` (`build`) → रिलीज़ बिल्ड\n- `bun scripts/build-native.ts --dev` (`dev:native`) → डिबग/डेव प्रोफ़ाइल बिल्ड (समान आउटपुट नामकरण)\n- `bun scripts/embed-native.ts` (`embed:native`) → बिल्ट फ़ाइलों से `src/embedded-addon.ts` उत्पन्न करें\n\n### 2) Rust आर्टिफ़ैक्ट बिल्ड\n\n`build-native.ts` `crates/pi-natives` में Cargo चलाता है:\n\n- बेस कमांड: `cargo build`\n- रिलीज़ मोड में `--dev` पास न होने पर `--release` जुड़ता है\n- क्रॉस टार्गेट `--target <CROSS_TARGET>` जोड़ता है\n\n`crates/pi-natives/Cargo.toml` `crate-type = [\"cdylib\"]` घोषित करता है, इसलिए Cargo एक शेयर्ड लाइब्रेरी (`.so`/`.dylib`/`.dll`) उत्पन्न करता है जिसे फिर `.node` एडऑन फ़ाइलनाम में कॉपी/रीनेम किया जाता है।\n\n### 3) आर्टिफ़ैक्ट खोज और इंस्टॉल\n\nCargo पूर्ण होने के बाद, `build-native.ts` क्रम में उम्मीदवार आउटपुट डायरेक्ट्री स्कैन करता है:\n\n1. `${CARGO_TARGET_DIR}` (यदि सेट हो)\n2. `<repo>/target`\n3. `crates/pi-natives/target`\n\nप्रत्येक रूट के लिए यह प्रोफ़ाइल डायरेक्ट्री जाँचता है:\n\n- क्रॉस बिल्ड: `<root>/<crossTarget>/<profile>` फिर `<root>/<profile>`\n- नेटिव बिल्ड: `<root>/<profile>`\n\nफिर यह निम्न में से एक ढूँढता है:\n\n- `libpi_natives.so`\n- `libpi_natives.dylib`\n- `pi_natives.dll`\n- `libpi_natives.dll`\n\nमिलने पर, यह टेम्प-फ़ाइल + रीनेम सेमेंटिक्स के साथ `packages/natives/native/` में अटॉमिकली इंस्टॉल करता है (Windows फ़ॉलबैक लॉक्ड DLL प्रतिस्थापन विफलताओं को स्पष्ट रूप से संभालता है)।\n\n## टार्गेट/वेरिएंट मॉडल और नामकरण परंपराएँ\n\n## प्लेटफ़ॉर्म टैग\n\nबिल्ड और रनटाइम दोनों प्लेटफ़ॉर्म टैग उपयोग करते हैं:\n\n`<platform>-<arch>` (उदाहरण: `darwin-arm64`, `linux-x64`)\n\n## वेरिएंट मॉडल (केवल x64)\n\nx64 CPU वेरिएंट्स का समर्थन करता है:\n\n- `modern` (AVX2-सक्षम पथ)\n- `baseline` (फ़ॉलबैक)\n\nगैर-x64 एकल डिफ़ॉल्ट आर्टिफ़ैक्ट उपयोग करता है (कोई वेरिएंट प्रत्यय नहीं)।\n\n### आउटपुट फ़ाइलनाम\n\nरिलीज़ बिल्ड:\n\n- x64: `pi_natives.<platform>-<arch>-modern.node` या `...-baseline.node`\n- गैर-x64: `pi_natives.<platform>-<arch>.node`\n\nडेव बिल्ड (`--dev`):\n\n- डिबग प्रोफ़ाइल फ़्लैग उपयोग करता है लेकिन मानक प्लेटफ़ॉर्म-टैग्ड आउटपुट नामकरण बनाए रखता है\n\n`native.ts` में रनटाइम लोडर उम्मीदवार क्रम:\n\n- रिलीज़ उम्मीदवार\n- कंपाइल्ड मोड पैकेज-लोकल फ़ाइलों से पहले निकाले गए/कैश उम्मीदवार जोड़ता है\n\n## एनवायरनमेंट फ़्लैग और बिल्ड विकल्प\n\n## रनटाइम फ़्लैग\n\n- `PI_DEV` (लोडर व्यवहार): लोडर डायग्नोस्टिक्स सक्षम करें\n- `PI_NATIVE_VARIANT` (लोडर व्यवहार, केवल x64): रनटाइम पर `modern` या `baseline` चयन बाध्य करें\n- `PI_COMPILED` (लोडर व्यवहार): कंपाइल्ड-बाइनरी उम्मीदवार/निष्कर्षण व्यवहार सक्षम करें\n\n## बिल्ड-टाइम फ़्लैग/विकल्प\n\n- `--dev` (स्क्रिप्ट आर्ग): डिबग प्रोफ़ाइल बिल्ड\n- `CROSS_TARGET`: Cargo `--target` को पास किया जाता है\n- `TARGET_PLATFORM`: आउटपुट प्लेटफ़ॉर्म टैग नामकरण ओवरराइड करें\n- `TARGET_ARCH`: आउटपुट आर्क नामकरण ओवरराइड करें\n- `TARGET_VARIANT` (केवल x64): आउटपुट फ़ाइलनाम और RUSTFLAGS नीति के लिए `modern` या `baseline` बाध्य करें\n- `CARGO_TARGET_DIR`: Cargo आउटपुट खोजते समय अतिरिक्त रूट\n- `RUSTFLAGS`:\n  - यदि अनसेट हो और क्रॉस-कंपाइलिंग न हो, स्क्रिप्ट सेट करती है:\n    - modern: `-C target-cpu=x86-64-v3`\n    - baseline: `-C target-cpu=x86-64-v2`\n    - गैर-x64 / कोई वेरिएंट नहीं: `-C target-cpu=native`\n  - यदि पहले से सेट हो, स्क्रिप्ट ओवरराइड नहीं करती\n\n## बिल्ड स्टेट/लाइफसाइकिल ट्रांज़िशन\n\n### बिल्ड लाइफसाइकिल (`build-native.ts`)\n\n1. **Init**: आर्ग/env पार्स करें (`--dev`, टार्गेट ओवरराइड, क्रॉस फ़्लैग)\n2. **वेरिएंट रिज़ॉल्व**:\n   - गैर-x64 → कोई वेरिएंट नहीं\n   - x64 + `TARGET_VARIANT` → स्पष्ट वेरिएंट\n   - x64 क्रॉस-बिल्ड बिना `TARGET_VARIANT` → हार्ड एरर\n   - x64 लोकल बिल्ड बिना ओवरराइड → होस्ट AVX2 डिटेक्ट करें\n3. **कंपाइल**: रिज़ॉल्व्ड प्रोफ़ाइल/टार्गेट के साथ Cargo चलाएँ\n4. **आर्टिफ़ैक्ट लोकेट**: टार्गेट रूट/प्रोफ़ाइल डायरेक्ट्री/लाइब्रेरी नाम स्कैन करें\n5. **इंस्टॉल**: `packages/natives/native` में कॉपी + अटॉमिक रीनेम\n6. **पूर्ण**: एडऑन लोडर उम्मीदवारों के लिए तैयार\n\nकिसी भी चरण में विफलता होने पर स्पष्ट एरर टेक्स्ट के साथ एग्ज़िट होता है (अमान्य वेरिएंट, विफल cargo बिल्ड, गायब आउटपुट लाइब्रेरी, इंस्टॉल/रीनेम विफलता)।\n\n### एम्बेड लाइफसाइकिल (`embed-native.ts`)\n\n1. **Init**: `TARGET_PLATFORM`/`TARGET_ARCH` या होस्ट वैल्यू से प्लेटफ़ॉर्म टैग कंप्यूट करें\n2. **उम्मीदवार सेट**:\n   - x64 दोनों `modern` और `baseline` की अपेक्षा करता है\n   - गैर-x64 एक डिफ़ॉल्ट फ़ाइल की अपेक्षा करता है\n3. `packages/natives/native` में **उपलब्धता वैलिडेट** करें\n4. Bun `file` इम्पोर्ट और पैकेज वर्शन के साथ **मैनिफेस्ट जनरेट** करें (`src/embedded-addon.ts`)\n5. कंपाइल्ड मोड के लिए **रनटाइम निष्कर्षण तैयार**\n\n`--reset` वैलिडेशन बायपास करता है और एक null मैनिफेस्ट स्टब (`embeddedAddon = null`) लिखता है।\n\n## डेव वर्कफ़्लो बनाम शिप्ड/कंपाइल्ड व्यवहार\n\n## लोकल डेवलपमेंट वर्कफ़्लो\n\nसामान्य लोकल लूप:\n\n1. एडऑन बिल्ड करें:\n   - रिलीज़: `bun --cwd=packages/natives run build`\n   - डिबग प्रोफ़ाइल: `bun --cwd=packages/natives run dev:native`\n2. लोडर डायग्नोस्टिक्स परीक्षण करते समय `PI_DEV=1` सेट करें\n3. `native.ts` में लोडर पैकेज-लोकल `native/` (और एग्ज़ीक्यूटेबल-डायर फ़ॉलबैक) उम्मीदवार रिज़ॉल्व करता है\n4. `validateNative` रैपर बाइंडिंग उपयोग करने से पहले एक्सपोर्ट संगतता लागू करता है\n\n## शिप्ड/कंपाइल्ड बाइनरी वर्कफ़्लो\n\nकंपाइल्ड मोड में (`PI_COMPILED` या Bun एम्बेडेड मार्कर):\n\n1. लोडर वर्शन्ड कैश डायरेक्ट्री कंप्यूट करता है: `<getNativesDir()>/<packageVersion>` (परिचालन रूप से `~/.xcsh/natives/<version>`)\n2. यदि एम्बेडेड मैनिफेस्ट वर्तमान प्लेटफ़ॉर्म+वर्शन से मेल खाता है, लोडर उस वर्शन्ड डायरेक्ट्री में चयनित एम्बेडेड फ़ाइल निकाल सकता है\n3. रनटाइम उम्मीदवार क्रम में शामिल हैं:\n   - वर्शन्ड कैश डायरेक्ट्री\n   - लेगेसी कंपाइल्ड-बाइनरी डायरेक्ट्री (Windows पर `%LOCALAPPDATA%/xcsh`, अन्यत्र `~/.local/bin`)\n   - पैकेज/एग्ज़ीक्यूटेबल डायरेक्ट्री\n4. सफलतापूर्वक लोड हुए पहले एडऑन को भी `validateNative` पास करना होगा\n\nइसीलिए पैकेजिंग + रनटाइम लोडर अपेक्षाएँ संरेखित होनी चाहिए: फ़ाइलनाम, प्लेटफ़ॉर्म टैग, और एक्सपोर्ट किए गए सिंबल को `native.ts` जाँचता और वैलिडेट करता है, उनसे मेल खाना चाहिए।\n\n## JS API ↔ Rust एक्सपोर्ट मैपिंग (वैलिडेशन गेट सबसेट)\n\n`native.ts` के लिए लोड किए गए एडऑन पर ये JS-दृश्यमान एक्सपोर्ट मौजूद होना आवश्यक है। वे `crates/pi-natives/src` में Rust N-API एक्सपोर्ट से मैप होते हैं:\n\n| `validateNative` द्वारा आवश्यक JS नाम | Rust एक्सपोर्ट घोषणा | Rust सोर्स फ़ाइल |\n| --- | --- | --- |\n| `glob` | `#[napi] pub fn glob(...)` | `crates/pi-natives/src/glob.rs` |\n| `grep` | `#[napi] pub fn grep(...)` | `crates/pi-natives/src/grep.rs` |\n| `search` | `#[napi] pub fn search(...)` | `crates/pi-natives/src/grep.rs` |\n| `highlightCode` | `#[napi] pub fn highlight_code(...)` | `crates/pi-natives/src/highlight.rs` |\n| `getSystemInfo` | `#[napi] pub fn get_system_info(...)` | `crates/pi-natives/src/system_info.rs` |\n| `getWorkProfile` | `#[napi] pub fn get_work_profile(...)` (कैमल-केस्ड एक्सपोर्ट) | `crates/pi-natives/src/prof.rs` |\n| `invalidateFsScanCache` | `#[napi] pub fn invalidate_fs_scan_cache(...)` | `crates/pi-natives/src/fs_cache.rs` |\n\nयदि कोई आवश्यक सिंबल गायब हो, तो लोडर रिबिल्ड संकेत के साथ तत्काल विफल हो जाता है।\n\n## विफलता व्यवहार और डायग्नोस्टिक्स\n\n## बिल्ड-टाइम विफलताएँ\n\n- अमान्य वेरिएंट कॉन्फ़िगरेशन:\n  - `TARGET_VARIANT` गैर-x64 पर सेट → तत्काल एरर\n  - स्पष्ट `TARGET_VARIANT` के बिना x64 क्रॉस-बिल्ड → तत्काल एरर\n- Cargo बिल्ड विफलता:\n  - स्क्रिप्ट नॉन-ज़ीरो एग्ज़िट और stderr दिखाती है\n- आर्टिफ़ैक्ट नहीं मिला:\n  - स्क्रिप्ट हर जाँची गई प्रोफ़ाइल डायरेक्ट्री प्रिंट करती है\n- इंस्टॉल विफलता:\n  - स्पष्ट संदेश; Windows में लॉक्ड-फ़ाइल संकेत शामिल\n\n## रनटाइम लोडर विफलताएँ (`native.ts`)\n\n- असमर्थित प्लेटफ़ॉर्म टैग:\n  - समर्थित प्लेटफ़ॉर्म सूची के साथ थ्रो\n- कोई उम्मीदवार लोड नहीं हो सका:\n  - पूर्ण उम्मीदवार एरर सूची और मोड-विशिष्ट उपचार संकेतों के साथ थ्रो\n- गायब एक्सपोर्ट:\n  - सटीक गायब सिंबल नाम और रिबिल्ड कमांड के साथ थ्रो\n- एम्बेडेड निष्कर्षण समस्याएँ:\n  - निष्कर्षण mkdir/write एरर रिकॉर्ड किए जाते हैं और अंतिम डायग्नोस्टिक्स में शामिल होते हैं\n\n## समस्या निवारण मैट्रिक्स\n\n| लक्षण | संभावित कारण | जाँच करें | सुधार |\n| --- | --- | --- | --- |\n| `Native addon missing exports ... Missing: <name>` | पुरानी `.node` बाइनरी, Rust एक्सपोर्ट नाम मिसमैच, या गलत बाइनरी लोड हुई | लोड पथ देखने के लिए `PI_DEV=1` के साथ चलाएँ; उस फ़ाइल की एक्सपोर्ट सूची जाँचें | `build` रिबिल्ड करें; सुनिश्चित करें कि Rust `#[napi]` एक्सपोर्ट नाम (या ज़रूरत होने पर स्पष्ट उपनाम) JS की से मेल खाता हो; पुरानी कैश/वर्शन्ड फ़ाइलें हटाएँ |\n| x64 मशीन modern अपेक्षित होने पर baseline लोड करती है | `PI_NATIVE_VARIANT=baseline`, कोई AVX2 डिटेक्ट नहीं, या केवल baseline फ़ाइल मौजूद | `PI_NATIVE_VARIANT` जाँचें; `-modern` फ़ाइल के लिए `native/` जाँचें | modern वेरिएंट बिल्ड करें (`TARGET_VARIANT=modern ... build`) और सुनिश्चित करें फ़ाइल शिप हो |\n| क्रॉस-बिल्ड अनुपयोगी/गलत-लेबल्ड बाइनरी उत्पन्न करता है | `CROSS_TARGET` और `TARGET_PLATFORM`/`TARGET_ARCH` के बीच मिसमैच, या x64 के लिए `TARGET_VARIANT` गायब | env टपल और आउटपुट फ़ाइलनाम की पुष्टि करें | संगत env वैल्यू और स्पष्ट x64 `TARGET_VARIANT` के साथ पुनः चलाएँ |\n| अपग्रेड के बाद कंपाइल्ड बाइनरी विफल | पुराना निकाला गया कैश (`~/.xcsh/natives/<old-or-mismatched-version>`) या एम्बेडेड मैनिफेस्ट मिसमैच | वर्शन्ड नेटिव्स डायरेक्ट्री और लोडर एरर सूची जाँचें | पैकेज वर्शन के लिए वर्शन्ड नेटिव्स कैश डिलीट करें और पुनः चलाएँ; पैकेजिंग के दौरान एम्बेडेड मैनिफेस्ट पुनः उत्पन्न करें |\n| लोडर कई पथ जाँचता है और कोई काम नहीं करता | प्लेटफ़ॉर्म मिसमैच या पैकेज `native/` में रिलीज़ आर्टिफ़ैक्ट गायब | वास्तविक फ़ाइलनाम(ों) के विरुद्ध `platformTag` जाँचें | सुनिश्चित करें कि बिल्ट फ़ाइलनाम ठीक `pi_natives.<platform>-<arch>(-variant).node` परंपरा से मेल खाए और पैकेज में `native/` शामिल हो |\n| `embed:native` \"Incomplete native addons\" के साथ विफल | एम्बेडिंग से पहले आवश्यक वेरिएंट फ़ाइलें नहीं बनीं | एरर टेक्स्ट में अपेक्षित बनाम मिली सूची जाँचें | पहले आवश्यक फ़ाइलें बिल्ड करें (x64: दोनों modern+baseline; गैर-x64: डिफ़ॉल्ट), फिर `embed:native` पुनः चलाएँ |\n\n## परिचालन कमांड\n\n```bash\n# वर्तमान होस्ट के लिए रिलीज़ आर्टिफ़ैक्ट\nbun --cwd=packages/natives run build\n\n# डिबग प्रोफ़ाइल आर्टिफ़ैक्ट बिल्ड\nbun --cwd=packages/natives run dev:native\n\n# स्पष्ट x64 वेरिएंट बिल्ड करें\nTARGET_VARIANT=modern bun --cwd=packages/natives run build\nTARGET_VARIANT=baseline bun --cwd=packages/natives run build\n\n# बिल्ट नेटिव फ़ाइलों से एम्बेडेड एडऑन मैनिफेस्ट जनरेट करें\nbun --cwd=packages/natives run embed:native\n\n# एम्बेडेड मैनिफेस्ट को null स्टब पर रीसेट करें\nbun --cwd=packages/natives run embed:native -- --reset\n```\n",
	"hi/natives/natives-media-system-utils.md": "---\ntitle: नेटिव मीडिया और सिस्टम उपयोगिताएँ\ndescription: >-\n  स्क्रीनशॉट, इमेज हैंडलिंग और सिस्टम जानकारी के लिए नेटिव मीडिया प्रोसेसिंग\n  उपयोगिताएँ।\nsidebar:\n  order: 7\n  label: मीडिया और सिस्टम उपयोगिताएँ\ni18n:\n  sourceHash: 430898c177bc\n  translator: machine\n---\n\n# नेटिव मीडिया + सिस्टम उपयोगिताएँ\n\nयह दस्तावेज़ [`docs/natives-architecture.md`](./natives-architecture.md) में वर्णित **system/media/conversion primitives** परत का एक सबसिस्टम गहन-विश्लेषण है: `image`, `html`, `clipboard`, और `work` प्रोफाइलिंग।\n\n## इम्प्लीमेंटेशन फ़ाइलें\n\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/prof.rs`\n- `crates/pi-natives/src/task.rs`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/work/index.ts`\n- `packages/natives/src/work/types.ts`\n\n> नोट: कोई `crates/pi-natives/src/work.rs` नहीं है; work प्रोफाइलिंग `prof.rs` में इम्प्लीमेंट की गई है और `task.rs` में इंस्ट्रूमेंटेशन द्वारा फीड की जाती है।\n\n## TS API ↔ Rust एक्सपोर्ट/मॉड्यूल मैपिंग\n\n| TS एक्सपोर्ट (packages/natives)             | Rust N-API एक्सपोर्ट                                                    | Rust मॉड्यूल                          |\n| ------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------- |\n| `PhotonImage.parse(bytes)`                  | `PhotonImage::parse`                                                     | `image.rs`                            |\n| `PhotonImage#resize(width, height, filter)` | `PhotonImage::resize`                                                    | `image.rs`                            |\n| `PhotonImage#encode(format, quality)`       | `PhotonImage::encode`                                                    | `image.rs`                            |\n| `htmlToMarkdown(html, options)`             | `html_to_markdown`                                                       | `html.rs`                             |\n| `copyToClipboard(text)`                     | `copy_to_clipboard` + TS फ़ॉलबैक लॉजिक                                  | `clipboard.rs` + `clipboard/index.ts` |\n| `readImageFromClipboard()`                  | `read_image_from_clipboard`                                              | `clipboard.rs`                        |\n| `getWorkProfile(lastSeconds)`               | `get_work_profile`                                                      | `prof.rs`                             |\n\n## डेटा फ़ॉर्मेट सीमाएँ और रूपांतरण\n\n### इमेज (`image`)\n\n- **JS इनपुट सीमा**: `Uint8Array` एन्कोडेड इमेज बाइट्स।\n- **Rust डिकोड सीमा**: बाइट्स को `Vec<u8>` में कॉपी किया जाता है, फ़ॉर्मेट का अनुमान `ImageReader::with_guessed_format()` से लगाया जाता है, फिर `DynamicImage` में डिकोड किया जाता है।\n- **इन-मेमोरी स्टेट**: `PhotonImage` में `Arc<DynamicImage>` स्टोर होता है।\n- **आउटपुट सीमा**: `encode(format, quality)` `Promise<Uint8Array>` (Rust `Vec<u8>`) लौटाता है।\n\nफ़ॉर्मेट ID न्यूमेरिक हैं:\n\n- `0`: PNG\n- `1`: JPEG\n- `2`: WebP (lossless एनकोडर)\n- `3`: GIF\n\nबाधाएँ:\n\n- `quality` केवल JPEG के लिए उपयोग होती है।\n- PNG/WebP/GIF `quality` को अनदेखा करते हैं।\n- असमर्थित फ़ॉर्मेट ID विफल होते हैं (`Invalid image format: <id>`)।\n\n### HTML रूपांतरण (`html`)\n\n- **JS इनपुट सीमा**: HTML `string` + वैकल्पिक ऑब्जेक्ट `{ cleanContent?: boolean; skipImages?: boolean }`।\n- **Rust रूपांतरण सीमा**: `String` इनपुट को `html_to_markdown_rs::convert` द्वारा रूपांतरित किया जाता है।\n- **आउटपुट सीमा**: Markdown `string`।\n\nरूपांतरण व्यवहार:\n\n- `cleanContent` डिफ़ॉल्ट रूप से `false` है।\n- जब `cleanContent=true` हो, तो `PreprocessingPreset::Aggressive` और नेविगेशन/फॉर्म के लिए हार्ड-रिमूवल फ़्लैग के साथ प्रीप्रोसेसिंग सक्षम होती है।\n- `skipImages` डिफ़ॉल्ट रूप से `false` है।\n\n### क्लिपबोर्ड (`clipboard`)\n\n- **टेक्स्ट पथ**:\n  - TS पहले OSC 52 (`\\x1b]52;c;<base64>\\x07`) उत्सर्जित करता है जब stdout एक TTY हो।\n  - फिर उसी टेक्स्ट को नेटिव क्लिपबोर्ड API (`native.copyToClipboard`) के माध्यम से बेस्ट-एफ़र्ट प्रयास किया जाता है।\n  - Termux पर, TS पहले `termux-clipboard-set` का प्रयास करता है।\n- **इमेज रीड पथ**:\n  - Rust `arboard` से रॉ इमेज पढ़ता है।\n  - Rust इसे PNG बाइट्स (`image` crate) में पुनः एन्कोड करता है, `{ data: Uint8Array, mimeType: \"image/png\" }` लौटाता है।\n  - TS Termux पर या बिना डिस्प्ले सर्वर (`DISPLAY`/`WAYLAND_DISPLAY` अनुपस्थित) वाले Linux सेशन पर जल्दी `null` लौटाता है।\n\n### Work प्रोफाइलिंग (`work`)\n\n- **संग्रह सीमा**: प्रोफाइलिंग सैंपल `task::blocking` और `task::future` में `profile_region(tag)` गार्ड द्वारा उत्पन्न होते हैं।\n- **स्टोरेज फ़ॉर्मेट**: निश्चित आकार का सर्कुलर बफ़र (`MAX_SAMPLES = 10_000`) जो स्टैक पाथ + अवधि (`μs`) + टाइमस्टैम्प (`μs since process start`) स्टोर करता है।\n- **आउटपुट सीमा**: `getWorkProfile(lastSeconds)` ऑब्जेक्ट लौटाता है:\n  - `folded`: फ़ोल्डेड-स्टैक टेक्स्ट (flamegraph इनपुट)\n  - `summary`: markdown टेबल सारांश\n  - `svg`: वैकल्पिक flamegraph SVG\n  - `totalMs`, `sampleCount`\n\n## जीवनचक्र और स्टेट ट्रांज़िशन\n\n### इमेज जीवनचक्र\n\n1. `PhotonImage.parse(bytes)` एक ब्लॉकिंग डिकोड टास्क (`image.decode`) शेड्यूल करता है।\n2. सफलता पर, JS में एक नेटिव `PhotonImage` हैंडल मौजूद होता है।\n3. `resize(...)` एक नया नेटिव हैंडल (`image.resize`) बनाता है, पुराने और नए हैंडल साथ-साथ रह सकते हैं।\n4. `encode(...)` इमेज डाइमेंशन को म्यूटेट किए बिना बाइट्स (`image.encode`) मटेरियलाइज़ करता है।\n\nविफलता ट्रांज़िशन:\n\n- फ़ॉर्मेट डिटेक्शन/डिकोड विफलता parse promise को रिजेक्ट करती है।\n- एन्कोड विफलता encode promise को रिजेक्ट करती है।\n- अमान्य फ़ॉर्मेट ID encode promise को रिजेक्ट करती है।\n\n### HTML जीवनचक्र\n\n1. `htmlToMarkdown(html, options)` एक ब्लॉकिंग रूपांतरण टास्क शेड्यूल करता है।\n2. रूपांतरण डिफ़ॉल्ट विकल्पों (`cleanContent=false`, `skipImages=false`) के साथ चलता है जब तक निर्दिष्ट न हो।\n3. markdown स्ट्रिंग लौटाता है या रिजेक्ट करता है।\n\nविफलता ट्रांज़िशन:\n\n- कनवर्टर विफलता रिजेक्टेड promise लौटाती है (`Conversion error: ...`)।\n\n### क्लिपबोर्ड जीवनचक्र\n\n`copyToClipboard(text)` जानबूझकर बेस्ट-एफ़र्ट और मल्टी-पाथ है:\n\n1. यदि TTY: OSC 52 राइट का प्रयास करें (base64 पेलोड)।\n2. `TERMUX_VERSION` सेट होने पर Termux कमांड आज़माएँ।\n3. नेटिव `arboard` टेक्स्ट कॉपी आज़माएँ।\n4. TS लेयर पर एरर को निगल लें।\n\n`readImageFromClipboard()` की सख्ती चरण के अनुसार भिन्न होती है:\n\n1. TS असमर्थित रनटाइम संदर्भों (Termux/headless Linux) को `null` पर हार्ड-गेट करता है।\n2. Rust `arboard` रीड केवल तभी चलती है जब TS इसकी अनुमति दे।\n3. `ContentNotAvailable` को `null` पर मैप किया जाता है।\n4. अन्य Rust एरर रिजेक्ट करते हैं।\n\n### Work प्रोफाइलिंग जीवनचक्र\n\n1. कोई स्पष्ट शुरुआत नहीं: जब टास्क हेल्पर्स निष्पादित होते हैं तो प्रोफाइलिंग हमेशा चालू रहती है।\n2. प्रत्येक इंस्ट्रूमेंटेड टास्क स्कोप गार्ड ड्रॉप पर एक सैंपल रिकॉर्ड करता है।\n3. बफ़र क्षमता पहुँचने के बाद सैंपल सबसे पुरानी एंट्री को ओवरराइट करते हैं।\n4. `getWorkProfile(lastSeconds)` एक टाइम विंडो पढ़ता है और folded/summary/svg आर्टिफैक्ट निकालता है।\n\nविफलता ट्रांज़िशन:\n\n- SVG जनरेशन विफलता सॉफ्ट-फेल है (`svg: null`), जबकि folded और summary फिर भी लौटते हैं।\n- खाली सैंपल विंडो खाली folded डेटा और `svg: null` लौटाती है, न कि एरर।\n\n## असमर्थित ऑपरेशन और एरर प्रोपेगेशन\n\n### इमेज\n\n- असमर्थित डिकोड इनपुट या दूषित बाइट्स: सख्त विफलता (promise रिजेक्शन)।\n- असमर्थित एन्कोड फ़ॉर्मेट ID: सख्त विफलता।\n- TS रैपर में कोई बेस्ट-एफ़र्ट फ़ॉलबैक पाथ नहीं।\n\n### HTML\n\n- रूपांतरण एरर सख्त विफलताएँ हैं (रिजेक्शन)।\n- विकल्प छोड़ना बेस्ट-एफ़र्ट डिफ़ॉल्टिंग है, विफलता नहीं।\n\n### क्लिपबोर्ड\n\n- टेक्स्ट कॉपी TS लेयर पर बेस्ट-एफ़र्ट है: ऑपरेशनल विफलताएँ दबाई जाती हैं।\n- इमेज रीड \"कोई इमेज नहीं\" (`null`) और ऑपरेशनल विफलता (रिजेक्शन) के बीच अंतर करता है।\n- Termux/headless Linux को इमेज रीड के लिए असमर्थित संदर्भ माना जाता है (`null`)।\n\n### Work प्रोफाइलिंग\n\n- फ़ंक्शन कॉल के लिए रिट्रीवल सख्त है, लेकिन आर्टिफैक्ट जनरेशन आंशिक रूप से बेस्ट-एफ़र्ट है (`svg` nullable)।\n- बफ़र ट्रंकेशन अपेक्षित व्यवहार है (ring buffer), डेटा हानि बग नहीं।\n\n## प्लेटफ़ॉर्म संबंधी चेतावनियाँ\n\n- **क्लिपबोर्ड टेक्स्ट**: OSC 52 टर्मिनल समर्थन पर निर्भर करता है; नेटिव क्लिपबोर्ड एक्सेस डेस्कटॉप वातावरण/सेशन पर निर्भर करती है।\n- **क्लिपबोर्ड इमेज रीड**: Termux और बिना डिस्प्ले सर्वर वाले Linux के लिए TS में ब्लॉक है।\n",
	"hi/natives/natives-rust-task-cancellation.md": "---\ntitle: नेटिव Rust टास्क निष्पादन और रद्दीकरण\ndescription: सहकारी रद्दीकरण और क्लीनअप सिमेंटिक्स के साथ Rust async टास्क निष्पादन मॉडल।\nsidebar:\n  order: 5\n  label: टास्क रद्दीकरण\ni18n:\n  sourceHash: 0fbf45c6d463\n  translator: machine\n---\n\n# नेटिव Rust टास्क निष्पादन और रद्दीकरण (`pi-natives`)\n\nयह दस्तावेज़ बताता है कि `crates/pi-natives` नेटिव कार्य को कैसे शेड्यूल करता है और रद्दीकरण JS विकल्पों (`timeoutMs`, `AbortSignal`) से Rust निष्पादन तक कैसे प्रवाहित होता है।\n\n## कार्यान्वयन फ़ाइलें\n\n- `crates/pi-natives/src/task.rs`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/fd.rs`\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/ps.rs`\n\n## कोर प्रिमिटिव्स (`task.rs`)\n\n`task.rs` तीन कोर घटक परिभाषित करता है:\n\n1. `task::blocking(tag, cancel_token, work)`\n   - `napi::AsyncTask` / `Task` को रैप करता है।\n   - `compute()` libuv वर्कर थ्रेड्स पर चलता है (CPU-बाउंड या ब्लॉकिंग/सिंक सिस्टम कॉल के लिए)।\n   - JS `Promise<T>` लौटाता है।\n\n2. `task::future(env, tag, work)`\n   - `env.spawn_future(...)` को रैप करता है।\n   - Tokio रनटाइम पर async कार्य चलाता है।\n   - `PromiseRaw<'env, T>` लौटाता है।\n\n3. `CancelToken` / `AbortToken` / `AbortReason`\n   - `CancelToken::new(timeout_ms, signal)` डेडलाइन + वैकल्पिक `AbortSignal` को संयुक्त करता है।\n   - `CancelToken::heartbeat()` ब्लॉकिंग लूप के लिए सहकारी रद्दीकरण है।\n   - `CancelToken::wait()` async रद्दीकरण प्रतीक्षा है (`Signal` / `Timeout` / `User` Ctrl-C)।\n   - `AbortToken` बाहरी कोड को abort अनुरोध करने देता है (`abort(reason)`)।\n\n## `blocking` बनाम `future`: निष्पादन मॉडल और चयन\n\n### `task::blocking` का उपयोग करें\n\nजब कार्य CPU-भारी या मूल रूप से सिंक्रोनस/ब्लॉकिंग हो:\n\n- regex/फ़ाइल स्कैनिंग (`grep`, `glob`, `fuzzy_find`)\n- सिंक्रोनस PTY लूप इंटर्नल्स (`spawn_blocking` के माध्यम से `run_pty_sync`)\n- clipboard/image/html रूपांतरण\n\nव्यवहार:\n\n- वर्क क्लोज़र एक क्लोन किया हुआ `CancelToken` प्राप्त करता है।\n- रद्दीकरण केवल वहीं देखा जाता है जहाँ कोड `ct.heartbeat()?` जाँचता है।\n- क्लोज़र `Err(...)` JS promise को अस्वीकार करता है।\n\n### `task::future` का उपयोग करें\n\nजब कार्य को async ऑपरेशन `await` करना हो:\n\n- shell सत्र ऑर्केस्ट्रेशन (`shell.run`, `executeShell`)\n- टास्क रेसिंग (`tokio::select!`) पूर्णता और रद्दीकरण के बीच\n\nव्यवहार:\n\n- Future, `ct.wait()` के विरुद्ध सामान्य पूर्णता को रेस कर सकता है।\n- कैंसल पाथ पर, async कार्यान्वयन आमतौर पर आंतरिक सबसिस्टम को रद्दीकरण प्रसारित करते हैं (जैसे, `tokio_util::CancellationToken`) और वैकल्पिक रूप से ग्रेस टाइमआउट पर जबरदस्ती abort करते हैं।\n\n## JS API ↔ Rust एक्सपोर्ट मैपिंग (टास्क/कैंसल प्रासंगिक)\n\n| JS-facing API | Rust export (`#[napi]`) | Scheduler | Cancellation hookup |\n|---|---|---|---|\n| `grep(options, onMatch?)` | `grep` | `task::blocking(\"grep\", ct, ...)` | `CancelToken::new(options.timeoutMs, options.signal)` + `ct.heartbeat()` |\n| `glob(options, onMatch?)` | `glob` | `task::blocking(\"glob\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` in filter loop |\n| `fuzzyFind(options)` | `fuzzy_find` | `task::blocking(\"fuzzy_find\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` in scoring loop |\n| `shell.run(options, onChunk?)` | `Shell::run` | `task::future(env, \"shell.run\", ...)` | `ct.wait()` raced against run task; bridges to Tokio `CancellationToken` |\n| `executeShell(options, onChunk?)` | `execute_shell` | `task::future(env, \"shell.execute\", ...)` | same as above |\n| `pty.start(options, onChunk?)` | `PtySession::start` | `task::future(env, \"pty.start\", ...)` + inner `spawn_blocking` | `CancelToken` checked in sync PTY loop via `heartbeat()` |\n| `htmlToMarkdown(html, options?)` | `html_to_markdown` | `task::blocking(\"html_to_markdown\", (), ...)` | none (`()` token) |\n| `PhotonImage.parse/encode/resize` | `PhotonImage::{parse,encode,resize}` | `task::blocking(...)` | none (`()` token) |\n| `copyToClipboard/readImageFromClipboard` | `copy_to_clipboard` / `read_image_from_clipboard` | `task::blocking(...)` | none (`()` token) |\n\n`text.rs` और `ps.rs` वर्तमान में `task::blocking`/`task::future` का उपयोग नहीं करते और इसलिए इस रद्दीकरण पाथ में भाग नहीं लेते।\n\n## रद्दीकरण जीवनचक्र और अवस्था संक्रमण\n\n### `CancelToken` जीवनचक्र\n\n`CancelToken` सहकारी और स्टेटफुल है:\n\n```text\nCreated\n  ├─ no signal + no timeout  -> passive token (never aborts unless externally emplaced)\n  ├─ signal registered        -> waits for AbortSignal callback\n  └─ deadline set             -> timeout check becomes active\n\nRunning\n  ├─ heartbeat()/wait() sees signal   -> AbortReason::Signal\n  ├─ heartbeat()/wait() sees deadline -> AbortReason::Timeout\n  ├─ wait() sees Ctrl-C               -> AbortReason::User\n  └─ no abort                         -> continue\n\nAborted (terminal)\n  └─ first abort reason wins (atomic flag + notifier)\n```\n\n### शुरू से पहले बनाम मध्य-निष्पादन रद्दीकरण\n\n- **शुरू से पहले / पहली रद्दीकरण जाँच से पहले**:\n  - `task::future` उपयोगकर्ता जो `ct.wait()` पर रेस करते हैं, एक बार `select!` में प्रवेश करने पर कैंसल को तुरंत हल कर सकते हैं।\n  - `task::blocking` उपयोगकर्ता रद्दीकरण तभी देखते हैं जब क्लोज़र कोड `heartbeat()` तक पहुँचता है। यदि क्लोज़र जल्दी हार्टबीट नहीं करता, तो रद्दीकरण देरी से होगा।\n\n- **मध्य-निष्पादन**:\n  - `blocking`: अगला `heartbeat()` `Err(\"Aborted: ...\")` लौटाता है।\n  - `future`: `ct.wait()` ब्रांच `select!` जीतती है, फिर कोड अधीनस्थ async मशीनरी को रद्द करता है (shell के लिए: Tokio टोकन रद्द करता है, 2s तक प्रतीक्षा करता है, फिर टास्क abort करता है)।\n\n## लंबे समय तक चलने वाले लूप के लिए हार्टबीट अपेक्षाएँ\n\n`heartbeat()` को असीमित या बड़े वर्क सेट वाले लूप में अनुमानित गति से चलना चाहिए।\n\nदेखे गए पैटर्न:\n\n- `glob::filter_entries`: फ़िल्टरिंग/मिलान से पहले प्रत्येक एंट्री की जाँच करें।\n- `fd::score_entries`: प्रत्येक स्कैन किए गए उम्मीदवार की जाँच करें।\n- `grep_sync`: भारी सर्च चरण से पहले स्पष्ट रद्दीकरण जाँच, साथ ही fs-cache कॉल जो टोकन भी प्राप्त करते हैं।\n- `run_pty_sync`: प्रत्येक लूप टिक (~16ms स्लीप गति) पर जाँच और रद्दीकरण पर child को kill करना।\n\nव्यावहारिक नियम: बाहरी-आकार इनपुट पर कोई भी लूप हार्टबीट के बिना एक छोटी बाउंडेड अंतराल से अधिक नहीं होना चाहिए।\n\n## JS को विफलता व्यवहार और त्रुटि प्रसार\n\n### ब्लॉकिंग टास्क\n\nत्रुटि पाथ:\n\n1. क्लोज़र `Err(napi::Error)` लौटाता है (`heartbeat()` abort सहित)।\n2. `Task::compute()` `Err` लौटाता है।\n3. `AsyncTask` JS promise को अस्वीकार करता है।\n\nसामान्य त्रुटि स्ट्रिंग्स:\n\n- `Aborted: Timeout`\n- `Aborted: Signal`\n- डोमेन त्रुटियाँ (`Failed to decode image: ...`, `Conversion error: ...`, आदि)\n\n### Future टास्क\n\nत्रुटि पाथ:\n\n1. Async बॉडी `Err(napi::Error)` लौटाती है या join विफलता को मैप किया जाता है (`... task failed: {err}`)।\n2. `task::future`-स्पॉन्ड promise अस्वीकार होता है।\n3. कुछ APIs जानबूझकर अस्वीकृति के बजाय संरचित रद्दीकरण परिणाम लौटाते हैं (`ShellRunResult`/`ShellExecuteResult` के साथ `cancelled`/`timed_out` फ्लैग और `exit_code: None`)।\n\n### रद्दीकरण रिपोर्टिंग विभाजन\n\n- **त्रुटि के रूप में Abort**: `heartbeat()?` का उपयोग करने वाले अधिकांश ब्लॉकिंग एक्सपोर्ट।\n- **टाइप्ड परिणाम के रूप में Abort**: shell/pty स्टाइल कमांड APIs जो रिजल्ट स्ट्रक्चर में रद्दीकरण को मॉडल करते हैं।\n\nप्रति API एक मॉडल चुनें और उसे स्पष्ट रूप से दस्तावेज़ीकृत करें।\n\n## सामान्य समस्याएँ\n\n1. **ब्लॉकिंग लूप में हार्टबीट का अभाव**\n   - लक्षण: टाइमआउट/सिग्नल लूप समाप्त होने तक अनदेखा प्रतीत होता है।\n   - समाधान: लूप के शीर्ष और महंगे प्रति-आइटम चरणों से पहले `ct.heartbeat()?` जोड़ें।\n\n2. **लंबे अरद्द करने योग्य खंड**\n   - लक्षण: एकल बड़े कॉल (decode, sort, compression, आदि) के दौरान रद्दीकरण विलंब में वृद्धि।\n   - समाधान: कार्य को हार्टबीट सीमाओं के साथ खंडों में विभाजित करें; यदि असंभव हो, तो विलंब को दस्तावेज़ीकृत करें।\n\n3. **Blocking async executor**\n   - लक्षण: async API रुक जाती है जब sync-भारी कोड सीधे future में चलता है।\n   - समाधान: CPU/sync ब्लॉक को `task::blocking` या `tokio::task::spawn_blocking` में ले जाएँ।\n\n4. **असंगत कैंसल सिमेंटिक्स**\n   - लक्षण: एक API कैंसल पर reject करती है, दूसरी फ्लैग के साथ resolve करती है, जो callers को भ्रमित करता है।\n   - समाधान: प्रति डोमेन मानकीकृत करें और रैपर दस्तावेज़ को संरेखित रखें।\n\n5. **नेस्टेड async टास्क में रद्दीकरण ब्रिज भूलना**\n   - लक्षण: बाहरी टोकन रद्द होता है लेकिन आंतरिक readers/subprocess टास्क चलते रहते हैं।\n   - समाधान: रद्दीकरण को आंतरिक टोकन/सिग्नल से जोड़ें और ग्रेस टाइमआउट + जबरदस्ती abort फॉलबैक लागू करें।\n\n## नए रद्द करने योग्य एक्सपोर्ट के लिए चेकलिस्ट\n\n1. कार्य को सही ढंग से वर्गीकृत करें:\n   - CPU-बाउंड या sync ब्लॉकिंग -> `task::blocking`\n   - async I/O / `await` ऑर्केस्ट्रेशन -> `task::future`\n\n2. आवश्यकता पड़ने पर कैंसल इनपुट उजागर करें:\n   - `#[napi(object)]` विकल्पों में `timeoutMs` और `signal` शामिल करें\n   - `let ct = task::CancelToken::new(timeout_ms, signal);` बनाएँ\n\n3. सभी परतों में रद्दीकरण जोड़ें:\n   - ब्लॉकिंग लूप: स्थिर अंतराल पर `ct.heartbeat()?`\n   - async ऑर्केस्ट्रेशन: `ct.wait()` के साथ रेस करें और sub-tasks/tokens रद्द करें\n\n4. रद्दीकरण अनुबंध तय करें:\n   - abort त्रुटि के साथ promise अस्वीकार करें, या\n   - टाइप्ड `{ cancelled, timedOut, ... }` resolve करें\n   - API परिवार के लिए यह अनुबंध सुसंगत रखें\n\n5. संदर्भ के साथ विफलताएँ प्रसारित करें:\n   - `Error::from_reason(format!(\"...: {err}\"))` के माध्यम से त्रुटियाँ मैप करें\n   - चरण-विशिष्ट उपसर्ग शामिल करें (`spawn`, `decode`, `wait`, आदि)\n\n6. शुरू से पहले और मध्य-उड़ान रद्दीकरण संभालें:\n   - महंगे बॉडी से पहले और लंबे निष्पादन के दौरान रद्दीकरण जाँच/प्रतीक्षा होनी चाहिए\n\n7. executor के दुरुपयोग की जाँच करें:\n   - `spawn_blocking`/blocking टास्क रैपर के बिना async futures के अंदर सीधे कोई लंबा sync कार्य नहीं\n",
	"hi/natives/natives-shell-pty-process.md": "---\ntitle: 'नेटिव्स Shell, PTY, प्रक्रिया और Key आंतरिक तंत्र'\ndescription: >-\n  नेटिव परत में Shell निष्पादन, PTY प्रबंधन, प्रक्रिया जीवनचक्र, और key इवेंट\n  हैंडलिंग।\nsidebar:\n  order: 4\n  label: 'Shell, PTY और प्रक्रिया'\ni18n:\n  sourceHash: 00ea95614c6a\n  translator: machine\n---\n\n# नेटिव्स Shell, PTY, प्रक्रिया और Key आंतरिक तंत्र\n\nयह दस्तावेज़ `@f5-sales-demo/pi-natives` में **निष्पादन/प्रक्रिया/टर्मिनल प्रिमिटिव्स** को कवर करता है: `shell`, `pty`, `ps`, और `keys`, जो `docs/natives-architecture.md` से आर्किटेक्चर शब्दावली का उपयोग करते हैं।\n\n## कार्यान्वयन फ़ाइलें\n\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/shell/windows.rs` (केवल Windows)\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/ps.rs`\n- `crates/pi-natives/src/keys.rs`\n- `crates/pi-natives/src/task.rs` (shell/pty द्वारा उपयोग किया जाने वाला साझा रद्दीकरण व्यवहार)\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/bindings.ts`\n\n## परत स्वामित्व\n\n- **TS रैपर/API परत** (`packages/natives/src/*`): टाइप्ड एंट्रीपॉइंट, रद्दीकरण सतह (`timeoutMs`, `AbortSignal`), और JS एर्गोनॉमिक्स।\n- **Rust N-API मॉड्यूल परत** (`crates/pi-natives/src/*`): shell/PTY प्रक्रिया निष्पादन, प्रक्रिया-ट्री ट्रैवर्सल/समाप्ति, और key-सीक्वेंस पार्सिंग।\n- **सत्यापन गेट** (`native.ts`, आर्किटेक्चर-स्तर): सुनिश्चित करता है कि रैपर उपयोग से पहले आवश्यक exports (`Shell`, `executeShell`, `PtySession`, `killTree`, `listDescendants`, key हेल्पर) मौजूद हैं।\n\n## Shell उपतंत्र (`shell`)\n\n### API मॉडल\n\nदो निष्पादन मोड उजागर किए गए हैं:\n\n1. **एक-बार** `executeShell(options, onChunk?)` के माध्यम से।\n2. **स्थायी सत्र** `new Shell(options?)` फिर `shell.run(...)` बार-बार के माध्यम से।\n\nदोनों एक थ्रेडसेफ कॉलबैक के माध्यम से आउटपुट स्ट्रीम करते हैं और `{ exitCode?, cancelled, timedOut }` लौटाते हैं।\n\n### सत्र निर्माण और पर्यावरण मॉडल\n\nRust इन के साथ `brush_core::Shell` बनाता है:\n\n- गैर-इंटरेक्टिव मोड,\n- `do_not_inherit_env: true`,\n- होस्ट env से स्पष्ट पर्यावरण पुनर्निर्माण,\n- shell-संवेदनशील vars के लिए skip-list (`PS1`, `PWD`, `SHLVL`, bash फ़ंक्शन exports, आदि)।\n\nसत्र env व्यवहार:\n\n- `ShellOptions.sessionEnv` सत्र निर्माण पर एक बार लागू होता है।\n- `ShellRunOptions.env` कमांड-स्कोप्ड (`EnvironmentScope::Command`) है और प्रत्येक रन के बाद pop होता है।\n- `PATH` को Windows पर case-insensitive dedupe के साथ विशेष रूप से मर्ज किया जाता है।\n\nकेवल Windows पर path संवर्धन (`shell/windows.rs`): खोजे गए Git-for-Windows paths (`cmd`, `bin`, `usr/bin`) को जोड़ा जाता है यदि मौजूद हों और पहले से शामिल न हों।\n\n### रनटाइम जीवनचक्र और स्थिति संक्रमण\n\nस्थायी shell (`Shell.run`) इस स्थिति मशीन का उपयोग करता है:\n\n- **Idle/अनारंभीकृत**: `session: None`।\n- **चल रहा**: पहला `run()` lazily सत्र बनाता है, `current_abort` टोकन संग्रहीत करता है, कमांड निष्पादित करता है।\n- **पूर्ण + keepalive**: यदि निष्पादन नियंत्रण प्रवाह `Normal` है, `current_abort` साफ़ होता है और सत्र पुनः उपयोग होता है।\n- **पूर्ण + teardown**: यदि नियंत्रण प्रवाह loop/script/shell-exit संबंधित है (`BreakLoop`, `ContinueLoop`, `ReturnFromFunctionOrScript`, `ExitShell`), सत्र drop होता है (`session: None`)।\n- **रद्द/टाइम आउट**: रन टास्क रद्द होता है, grace wait (2s), फिर force-abort; सत्र drop होता है।\n- **त्रुटि**: सत्र drop होता है।\n\nएक-बार shell (`executeShell`) प्रत्येक कॉल पर हमेशा एक नया सत्र बनाता और drop करता है।\n\n### स्ट्रीमिंग/आउटपुट व्यवहार\n\n- Stdout/stderr को एक साझा pipe में रूट किया जाता है और एक साथ पढ़ा जाता है।\n- रीडर UTF-8 को incrementally decode करता है; अमान्य byte सीक्वेंस `U+FFFD` रिप्लेसमेंट chunks emit करते हैं।\n- प्रक्रिया पूर्ण होने के बाद, आउटपुट drain में idle/max guards (`250ms` idle, `2s` max) होते हैं ताकि descriptors खुला रखने वाली बैकग्राउंड jobs पर hanging से बचा जा सके।\n\n### रद्दीकरण, टाइमआउट, और बैकग्राउंड jobs\n\n- `CancelToken` `timeoutMs` और वैकल्पिक `AbortSignal` से बनाया जाता है।\n- रद्दीकरण/टाइमआउट पर, shell रद्दीकरण टोकन ट्रिगर होता है, फिर forced abort से पहले टास्क को 2s graceful window मिलती है।\n- यदि रद्दीकरण होता है, बैकग्राउंड jobs brush job metadata का उपयोग करके समाप्त की जाती हैं (`TERM`, फिर delayed `KILL`)।\n\n`Shell.abort()` व्यवहार:\n\n- केवल उस `Shell` इंस्टेंस के लिए वर्तमान चल रहे कमांड को abort करता है,\n- जब कुछ नहीं चल रहा हो तो no-op success।\n\n### विफलता व्यवहार\n\nसामान्य उजागर त्रुटियों में शामिल हैं:\n\n- सत्र init विफलताएं (`Failed to initialize shell`),\n- cwd त्रुटियां (`Failed to set cwd`),\n- env set/pop विफलताएं,\n- snapshot source विफलताएं,\n- pipe creation/clone विफलताएं,\n- निष्पादन विफलता (`Shell execution failed: ...`),\n- टास्क रैपर विफलताएं (`Shell execution task failed: ...`)।\n\nपरिणाम-स्तर रद्दीकरण flags:\n\n- timeout -> `exitCode: undefined`, `timedOut: true`।\n- abort signal -> `exitCode: undefined`, `cancelled: true`।\n\n## PTY उपतंत्र (`pty`)\n\n### API मॉडल\n\n`new PtySession()` उजागर करता है:\n\n- `start(options, onChunk?) -> Promise<{ exitCode?, cancelled, timedOut }>`\n- `write(data)`\n- `resize(cols, rows)`\n- `kill()`\n\n### रनटाइम जीवनचक्र और स्थिति संक्रमण\n\n`PtySession` स्थिति मशीन:\n\n- **Idle**: `core: None`।\n- **Reserved**: `start()` async काम शुरू होने से पहले synchronously कंट्रोल चैनल install करता है (`core: Some`), ताकि `write/resize/kill` तुरंत valid हो जाएं।\n- **चल रहा**: blocking PTY loop child स्थिति, reader events, रद्दीकरण heartbeat, और control messages हैंडल करता है।\n- **टर्मिनल बंद**: child exit + reader completion।\n- **Finalized**: start टास्क पूर्ण होने के बाद (success या error) `core` हमेशा `None` पर reset होता है।\n\nसंगामिता गार्ड:\n\n- पहले से चल रहे होने पर start करने पर `PTY session already running` लौटाता है।\n\n### Spawn/attach/write/read/terminate पैटर्न\n\n- PTY `portable_pty::native_pty_system().openpty(...)` के माध्यम से खुलता है।\n- कमांड वर्तमान में वैकल्पिक `cwd` और env overrides के साथ `sh -lc <command>` के रूप में चलता है।\n- `write()` PTY stdin को raw bytes भेजता है।\n- `resize()` dimensions को clamp करता है (`cols 20..400`, `rows 5..200`) और master resize कॉल करता है।\n- `kill()` run को cancelled के रूप में चिह्नित करता है और child प्रक्रिया को kill करता है।\n\nआउटपुट पथ:\n\n- समर्पित reader thread master stream पढ़ता है,\n- `U+FFFD` रिप्लेसमेंट के साथ अमान्य bytes पर incremental UTF-8 decode,\n- N-API threadsafe callback के माध्यम से chunks forward किए जाते हैं।\n\n### रद्दीकरण और टाइमआउट सिमेंटिक्स\n\n- `timeoutMs` और `AbortSignal` एक `CancelToken` को फ़ीड करते हैं।\n- loop आवधिक रूप से `ct.heartbeat()` कॉल करता है; abort child kill ट्रिगर करता है।\n- टाइमआउट वर्गीकरण string-based है (heartbeat error में `\"Timeout\"` substring)।\n\n### विफलता व्यवहार\n\nत्रुटि surfaces में शामिल हैं:\n\n- PTY allocation/open विफलता,\n- PTY spawn विफलता,\n- writer/reader acquisition विफलता,\n- child status/wait विफलताएं,\n- lock poisoning,\n- control-channel disconnection (`PTY session is no longer available`)।\n\nचल न रहे होने पर control call विफलताएं:\n\n- `write/resize/kill` `PTY session is not running` लौटाते हैं।\n\n## प्रक्रिया-ट्री उपतंत्र (`ps`)\n\n### API मॉडल\n\n- `killTree(pid, signal) -> number`\n- `listDescendants(pid) -> number[]`\n\nTS रैपर `setNativeKillTree(native.killTree)` के माध्यम से native kill-tree integration को साझा utils में भी रजिस्टर करता है।\n\n### प्लेटफ़ॉर्म-विशिष्ट कार्यान्वयन\n\n- **Linux**: `/proc/<pid>/task/<pid>/children` को recursively पढ़ता है।\n- **macOS**: `libproc` `proc_listchildpids` का उपयोग करता है।\n- **Windows**: `CreateToolhelp32Snapshot` से प्रक्रिया तालिका snapshot करता है, parent->children map बनाता है, `OpenProcess(PROCESS_TERMINATE)` + `TerminateProcess` से समाप्त करता है।\n\n### Kill-tree व्यवहार\n\n- Descendants recursively एकत्र किए जाते हैं।\n- Kill क्रम bottom-up (पहले सबसे गहरे descendants) है ताकि orphan re-parenting कम हो।\n- Root pid अंत में kill होता है।\n- Return value सफल terminations की गिनती है।\n\nSignal व्यवहार:\n\n- POSIX: प्रदान किया गया `signal` `kill` को पास होता है।\n- Windows: `signal` को ignore किया जाता है; termination बिना शर्त process terminate है।\n\n### विफलता व्यवहार\n\nयह मॉड्यूल API surface पर जानबूझकर non-throwing है:\n\n- गुम/अनुपलब्ध प्रक्रिया ट्री शाखाएं skip की जाती हैं,\n- per-pid kill विफलताएं असफल के रूप में गिनी जाती हैं (त्रुटियां नहीं),\n- lookup miss आमतौर पर `listDescendants` से `[]` और `killTree` से `0` देता है।\n\n## Key पार्सिंग उपतंत्र (`keys`)\n\n### API मॉडल\n\nउजागर हेल्पर:\n\n- `parseKey(data, kittyProtocolActive)`\n- `matchesKey(data, keyId, kittyProtocolActive)`\n- `parseKittySequence(data)`\n- `matchesKittySequence(data, expectedCodepoint, expectedModifier)`\n- `matchesLegacySequence(data, keyName)`\n\n### पार्सिंग मॉडल\n\nपार्सर को जोड़ता है:\n\n- सीधे single-byte mappings (`enter`, `tab`, `ctrl+<letter>`, printable ASCII),\n- O(1) legacy escape-sequence lookup (PHF map),\n- xterm `modifyOtherKeys` पार्सिंग,\n- Kitty protocol पार्सिंग (`CSI u`, `CSI ~`, `CSI 1;...<letter>`),\n- key IDs में normalization (`ctrl+c`, `shift+tab`, `pageUp`, `f5`, आदि)।\n\nModifier हैंडलिंग:\n\n- key matching के लिए केवल shift/alt/ctrl bits की तुलना की जाती है,\n- lock bits को comparisons से पहले masked out किया जाता है।\n\nLayout व्यवहार:\n\n- base-layout fallback जानबूझकर constrained है ताकि remapped layouts ASCII letters/symbols के लिए false matches न बनाएं।\n\n### विफलता व्यवहार\n\n- अज्ञात या अमान्य sequences parse functions से `null` देते हैं।\n- Match functions parse विफलता या mismatch पर `false` लौटाते हैं।\n- विकृत key input के लिए कोई thrown error surface नहीं है।\n\n## JS रैपर API ↔ Rust export मैपिंग\n\n### Shell + PTY + प्रक्रिया\n\n| TS रैपर API | Rust N-API export | नोट्स |\n|---|---|---|\n| `executeShell(options, onChunk?)` | `executeShell` (`execute_shell`) | एक-बार shell निष्पादन |\n| `new Shell(options?)` | `Shell` class | स्थायी shell सत्र |\n| `shell.run(options, onChunk?)` | `Shell::run` | keepalive control flow पर सत्र पुनः उपयोग करता है |\n| `shell.abort()` | `Shell::abort` | उस shell इंस्टेंस के लिए सक्रिय run को abort करता है |\n| `new PtySession()` | `PtySession` class | Stateful PTY सत्र |\n| `pty.start(options, onChunk?)` | `PtySession::start` | इंटरेक्टिव PTY run |\n| `pty.write(data)` | `PtySession::write` | Raw stdin passthrough |\n| `pty.resize(cols, rows)` | `PtySession::resize` | Clamped टर्मिनल dimensions |\n| `pty.kill()` | `PtySession::kill` | सक्रिय PTY child को force-kill करता है |\n| `killTree(pid, signal)` | `killTree` (`kill_tree`) | Children-first प्रक्रिया ट्री समाप्ति |\n| `listDescendants(pid)` | `listDescendants` (`list_descendants`) | Recursive descendants listing |\n\n### Keys\n\n| TS रैपर API | Rust N-API export | नोट्स |\n|---|---|---|\n| `matchesKittySequence(data, cp, mod)` | `matchesKittySequence` (`matches_kitty_sequence`) | Kitty codepoint+modifier match |\n| `parseKey(data, kittyProtocolActive)` | `parseKey` (`parse_key`) | Normalized key-id पार्सर |\n| `matchesLegacySequence(data, keyName)` | `matchesLegacySequence` (`matches_legacy_sequence`) | Exact legacy sequence map check |\n| `parseKittySequence(data)` | `parseKittySequence` (`parse_kitty_sequence`) | Structured Kitty parse परिणाम |\n| `matchesKey(data, keyId, kittyProtocolActive)` | `matchesKey` (`matches_key`) | High-level key matcher |\n\n## परित्यक्त सत्र cleanup और finalization नोट्स\n\n- **Shell स्थायी सत्र**: यदि कोई run रद्द/टाइम आउट/त्रुटि/non-keepalive control flow है, Rust स्पष्ट रूप से आंतरिक सत्र स्थिति drop करता है। सफल सामान्य runs पुनः उपयोग के लिए सत्र रखते हैं।\n- **PTY सत्र**: विफलता paths सहित `start()` समाप्त होने के बाद `core` हमेशा साफ़ होता है।\n- **कोई स्पष्ट JS finalizer-driven kill अनुबंध** रैपर द्वारा उजागर नहीं किया जाता; cleanup मुख्य रूप से run completion/cancellation paths से जुड़ी है। कॉलर्स को निर्धारक teardown के लिए `timeoutMs`, `AbortSignal`, `shell.abort()`, या `pty.kill()` का उपयोग करना चाहिए।\n",
	"hi/natives/natives-text-search-pipeline.md": "---\ntitle: नेटिव टेक्स्ट और खोज पाइपलाइन\ndescription: >-\n  grep, glob, और ripgrep-आधारित फ़ाइल सामग्री इंडेक्सिंग के साथ नेटिव टेक्स्ट\n  खोज पाइपलाइन।\nsidebar:\n  order: 6\n  label: टेक्स्ट और खोज पाइपलाइन\ni18n:\n  sourceHash: 0e93462fdd12\n  translator: machine\n---\n\n# नेटिव टेक्स्ट/खोज पाइपलाइन\n\nयह दस्तावेज़ `@f5-sales-demo/pi-natives` टेक्स्ट/खोज सतह (`grep`, `glob`, `text`, `highlight`) को TypeScript रैपर से Rust N-API एक्सपोर्ट तक और वापस JS परिणाम ऑब्जेक्ट में मैप करता है।\n\nशब्दावली `docs/natives-architecture.md` का अनुसरण करती है:\n\n- **रैपर**: `packages/natives/src/*` में TS API\n- **Rust मॉड्यूल लेयर**: `crates/pi-natives/src/*` में N-API एक्सपोर्ट\n- **साझा स्कैन कैश**: `fs_cache`-समर्थित डायरेक्टरी-एंट्री कैश जो डिस्कवरी/खोज प्रवाह द्वारा उपयोग किया जाता है\n\n## कार्यान्वयन फ़ाइलें\n\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/glob_util.rs`\n- `crates/pi-natives/src/fs_cache.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/highlight.rs`\n- `crates/pi-natives/src/fd.rs`\n\n## JS API ↔ Rust एक्सपोर्ट मैपिंग\n\n| JS रैपर API | Rust एक्सपोर्ट (`#[napi]`, snake_case -> camelCase) | Rust मॉड्यूल |\n| --- | --- | --- |\n| `grep(options, onMatch?)` | `grep` | `grep.rs` |\n| `searchContent(content, options)` | `search` | `grep.rs` |\n| `hasMatch(content, pattern, options?)` | `hasMatch` | `grep.rs` |\n| `fuzzyFind(options)` | `fuzzyFind` | `fd.rs` |\n| `glob(options, onMatch?)` | `glob` | `glob.rs` |\n| `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `fs_cache.rs` |\n| `wrapTextWithAnsi(text, width)` | `wrapTextWithAnsi` | `text.rs` |\n| `truncateToWidth(text, maxWidth, ellipsis, pad)` | `truncateToWidth` | `text.rs` |\n| `sliceWithWidth(line, startCol, length, strict?)` | `sliceWithWidth` | `text.rs` |\n| `extractSegments(line, beforeEnd, afterStart, afterLen, strictAfter)` | `extractSegments` | `text.rs` |\n| `sanitizeText(text)` | `sanitizeText` | `text.rs` |\n| `visibleWidth(text)` | `visibleWidth` | `text.rs` |\n| `highlightCode(code, lang, colors)` | `highlightCode` | `highlight.rs` |\n| `supportsLanguage(lang)` | `supportsLanguage` | `highlight.rs` |\n| `getSupportedLanguages()` | `getSupportedLanguages` | `highlight.rs` |\n\n## उपतंत्र द्वारा पाइपलाइन अवलोकन\n\n## 1) रेजेक्स खोज (`grep`, `searchContent`, `hasMatch`)\n\n### इनपुट/विकल्प प्रवाह\n\n1. TS रैपर विकल्पों को नेटिव को फॉरवर्ड करता है:\n   - `grep/index.ts` `options` को अधिकांशतः अपरिवर्तित पास करता है और कॉलबैक को `(match) => void` से napi threadsafe कॉलबैक शेप `(err, match)` में रैप करता है।\n   - `searchContent` और `hasMatch` स्ट्रिंग/`Uint8Array` सीधे पास करते हैं।\n2. `grep.rs` में Rust विकल्प स्ट्रक्चर camelCase फ़ील्ड डिसीरियलाइज़ करते हैं (`ignoreCase`, `maxCount`, `contextBefore`, `contextAfter`, `maxColumns`, `timeoutMs`)।\n3. `grep` `timeoutMs` + `AbortSignal` से `CancelToken` बनाता है और `task::blocking(\"grep\", ...)` के अंदर चलता है।\n\n### निष्पादन शाखाएँ\n\n- **इन-मेमोरी शाखा (शुद्ध उपयोगिता)**\n  - `search` → `search_sync` → प्रदान की गई सामग्री बाइट्स पर `run_search`।\n  - कोई फ़ाइलसिस्टम स्कैन नहीं, कोई `fs_cache` नहीं।\n- **एकल-फ़ाइल शाखा (फ़ाइलसिस्टम-निर्भर)**\n  - `grep_sync` पथ रिज़ॉल्व करता है, मेटाडेटा की जाँच करता है कि फ़ाइल है, प्रति फ़ाइल `MAX_FILE_BYTES` (`4 MiB`) तक ripgrep मैचर के माध्यम से स्ट्रीम करता है।\n- **डायरेक्टरी शाखा (फ़ाइलसिस्टम-निर्भर)**\n  - `cache: true` होने पर `fs_cache::get_or_scan` के माध्यम से वैकल्पिक कैश लुकअप।\n  - `cache: false` होने पर `fs_cache::force_rescan` के माध्यम से नया स्कैन।\n  - कैश आयु `empty_recheck_ms()` से अधिक होने पर वैकल्पिक खाली-परिणाम पुनःजाँच।\n  - एंट्री फ़िल्टरिंग: केवल-फ़ाइल + वैकल्पिक glob फ़िल्टर (`glob_util`) + वैकल्पिक प्रकार फ़िल्टर मैपिंग (`js`, `ts`, `rust`, आदि)।\n\n### खोज/संग्रह सिमेंटिक्स\n\n- रेजेक्स इंजन: `ignoreCase` और `multiline` के साथ `grep_regex::RegexMatcherBuilder`।\n- संदर्भ रिज़ॉल्यूशन:\n  - `contextBefore/contextAfter` पुरानी `context` को ओवरराइड करते हैं।\n  - गैर-सामग्री मोड संदर्भ संग्रह को शून्य करते हैं।\n- आउटपुट मोड:\n  - `content` => प्रति हिट एक `GrepMatch`।\n  - `count` और `filesWithMatches` दोनों काउंट-स्टाइल एंट्री में मैप होते हैं (`lineNumber=0`, `line=\"\"`, `matchCount` सेट)।\n- सीमाएँ:\n  - वैश्विक `offset` और `maxCount` फ़ाइलों में लागू।\n  - समानांतर पथ तभी उपयोग होता है जब `maxCount` अनसेट हो और `offset == 0`; अन्यथा क्रमिक पथ नियतात्मक वैश्विक offset/limit सिमेंटिक्स संरक्षित करता है।\n\n### JS को वापस परिणाम आकार देना\n\n- Rust `SearchResult`/`GrepResult` फ़ील्ड N-API ऑब्जेक्ट फ़ील्ड रूपांतरण के माध्यम से TS प्रकारों में मैप होते हैं।\n- N-API पार करने से पहले काउंटर `u32` तक क्लैंप किए जाते हैं।\n- वैकल्पिक बूलियन कुछ पथों में केवल तब शामिल होते हैं जब true हों (`limitReached`)।\n- स्ट्रीमिंग कॉलबैक प्रत्येक आकार दिए गए `GrepMatch` (सामग्री या काउंट एंट्री) प्राप्त करता है।\n\n### विफलता व्यवहार\n\n- `searchContent` थ्रो करने के बजाय regex/खोज विफलताओं के लिए `SearchResult.error` लौटाता है।\n- `grep` कठिन त्रुटियों पर रिजेक्ट करता है (अमान्य पथ, अमान्य glob/regex, रद्दीकरण timeout/abort)।\n- `hasMatch` `Result<bool>` लौटाता है और अमान्य पैटर्न/UTF-8 डिकोडिंग त्रुटियों पर थ्रो करता है।\n- मल्टी-फ़ाइल स्कैन में फ़ाइल खोलने/खोज त्रुटियाँ प्रति-फ़ाइल छोड़ी जाती हैं; स्कैन जारी रहता है।\n\n### विकृत रेजेक्स हैंडलिंग\n\n`grep.rs` रेजेक्स कंपाइल से पहले ब्रेसेज़ को साफ़ करता है:\n\n- अमान्य रिपीटिशन-जैसे ब्रेसेज़ एस्केप किए जाते हैं (`{`/`}` -> `\\{`/`\\}`) जब वे `{N}`, `{N,}`, `{N,M}` नहीं बना सकते।\n- यह सामान्य लिटरल-टेम्पलेट फ्रैगमेंट (उदाहरण के लिए `${platform}`) को विकृत रिपीटिशन के रूप में विफल होने से रोकता है।\n- शेष अमान्य रेजेक्स सिंटैक्स अभी भी रेजेक्स त्रुटि लौटाता है।\n\n## 2) फ़ाइल डिस्कवरी (`glob`) और फ़ज़ी पथ खोज (`fuzzyFind`)\n\n`glob` और `fuzzyFind` `fs_cache` स्कैन साझा करते हैं; मिलान तर्क अलग होता है।\n\n### `glob` प्रवाह\n\n1. TS रैपर (`glob/index.ts`):\n   - `path.resolve(options.path)`।\n   - डिफ़ॉल्ट: `pattern=\"*\"`, `hidden=false`, `gitignore=true`, `recursive=true`।\n2. Rust `glob` `GlobConfig` बनाता है और `glob_util::compile_glob` के माध्यम से पैटर्न कंपाइल करता है।\n3. एंट्री स्रोत:\n   - `cache=true` => `get_or_scan` + वैकल्पिक stale-empty `force_rescan`।\n   - `cache=false` => `force_rescan(..., store=false)` (केवल नया)।\n4. फ़िल्टरिंग:\n   - `.git` हमेशा छोड़ें।\n   - `node_modules` छोड़ें जब तक अनुरोध न किया जाए (`includeNodeModules` या node_modules का उल्लेख करने वाला पैटर्न)।\n   - glob मिलान लागू करें।\n   - फ़ाइल-प्रकार फ़िल्टर लागू करें; symlink `file/dir` फ़िल्टर लक्ष्य मेटाडेटा रिज़ॉल्व करते हैं।\n5. `maxResults` तक छोटा करने से पहले mtime desc द्वारा वैकल्पिक सॉर्ट (`sortByMtime`)।\n\n### `fuzzyFind` प्रवाह (`fd.rs` में कार्यान्वित)\n\n1. TS रैपर `grep` मॉड्यूल से एक्सपोर्ट है, लेकिन Rust कार्यान्वयन `fd.rs` में रहता है।\n2. `fs_cache` से साझा स्कैन स्रोत, समान cache/no-cache विभाजन और stale-empty recheck नीति के साथ।\n3. स्कोरिंग:\n   - exact / starts-with / contains / subsequence-आधारित fuzzy स्कोर\n   - separator/punctuation-नॉर्मलाइज़्ड स्कोरिंग पथ\n   - डायरेक्टरी बोनस और नियतात्मक टाई-ब्रेक (`score desc`, फिर `path asc`)\n4. Symlink एंट्री fuzzy परिणामों से बाहर रखी जाती हैं।\n\n### विफलता व्यवहार\n\n- अमान्य glob पैटर्न => `glob_util::compile_glob` से त्रुटि।\n- खोज रूट एक मौजूदा डायरेक्टरी होनी चाहिए (`resolve_search_path`), अन्यथा त्रुटि।\n- रद्दीकरण/timeout लूप में `CancelToken::heartbeat()` जाँच के माध्यम से abort त्रुटियों के रूप में प्रसारित होते हैं।\n\n### विकृत glob हैंडलिंग\n\n`glob_util::build_glob_pattern` सहनशील है:\n\n- `\\` को `/` में नॉर्मलाइज़ करता है।\n- `recursive=true` होने पर सरल पुनरावर्ती पैटर्न को स्वतः `**/` से उपसर्गित करता है।\n- कंपाइल से पहले असंतुलित `{...` ऑल्टर्नेशन ग्रुप को स्वतः बंद करता है।\n\n## 3) साझा स्कैन/कैश जीवनचक्र (`fs_cache`)\n\n`fs_cache` स्कैन परिणामों को नॉर्मलाइज़्ड सापेक्ष एंट्री (`path`, `fileType`, वैकल्पिक `mtime`) के रूप में संग्रहीत करता है, जो इनके द्वारा कुंजीकृत हैं:\n\n- कैनोनिकल खोज रूट\n- `include_hidden`\n- `use_gitignore`\n\n### कैश स्थिति संक्रमण\n\n1. **मिस / अक्षम**\n   - TTL `0` है या कुंजी अनुपस्थित/समाप्त है -> नया `collect_entries`।\n2. **हिट**\n   - एंट्री आयु `< cache_ttl_ms()` -> कैश्ड एंट्री + `cache_age_ms` लौटाएँ।\n3. **Stale-empty recheck** (`glob`/`grep`/`fd` में कॉलर नीति)\n   - यदि क्वेरी शून्य मिलान देती है और `cache_age_ms >= empty_recheck_ms()`, तो एक बार पुनः स्कैन बाध्य करें।\n4. **अमान्यकरण**\n   - `invalidateFsScanCache(path?)`:\n     - कोई आर्ग नहीं: सभी कुंजियाँ साफ़ करें\n     - path आर्ग: उन कुंजियाँ हटाएँ जिनका रूट उस लक्ष्य पथ को उपसर्गित करता है\n\n### Stale-परिणाम ट्रेडऑफ\n\n- कैश तत्काल संगतता पर कम-विलंबता बार-बार स्कैन को प्राथमिकता देता है।\n- TTL विंडो stale सकारात्मक/नकारात्मक परिणाम लौटा सकती है।\n- खाली-परिणाम recheck एक अतिरिक्त स्कैन की कीमत पर पुराने कैश्ड स्कैन के लिए stale नकारात्मक परिणाम कम करता है।\n- फ़ाइल म्यूटेशन के बाद स्पष्ट अमान्यकरण इच्छित सटीकता हुक है।\n\n## 4) ANSI टेक्स्ट उपयोगिताएँ (`text`)\n\nये शुद्ध, इन-मेमोरी उपयोगिताएँ हैं (कोई फ़ाइलसिस्टम स्कैनिंग नहीं)।\n\n### सीमाएँ और जिम्मेदारियाँ\n\n- **`text.rs` टर्मिनल-सेल सिमेंटिक्स का स्वामी है**:\n  - ANSI सीक्वेंस पार्सिंग\n  - ग्राफीम-अवेयर चौड़ाई और स्लाइसिंग\n  - wrap/truncate/sanitize व्यवहार\n- **`grep.rs` लाइन ट्रंकेशन (`maxColumns`) अलग है**:\n  - `...` के साथ मिलान की गई लाइनों का सरल कैरेक्टर-बाउंड्री ट्रंकेशन\n  - ANSI-state-preserving नहीं और टर्मिनल-सेल चौड़ाई जागरूक नहीं\n\n### मुख्य व्यवहार\n\n- `wrapTextWithAnsi`: दृश्यमान चौड़ाई से रैप करता है, रैप्ड लाइनों में सक्रिय SGR कोड वहन करता है।\n- `truncateToWidth`: ellipsis नीति (`Unicode`, `Ascii`, `Omit`) के साथ दृश्यमान-सेल ट्रंकेशन, वैकल्पिक दाईं पैडिंग, और अपरिवर्तित होने पर मूल JS स्ट्रिंग लौटाने का fast-path।\n- `sliceWithWidth`: वैकल्पिक strict चौड़ाई प्रवर्तन के साथ कॉलम स्लाइसिंग।\n- `extractSegments`: `after` सेगमेंट के लिए ANSI स्थिति पुनर्स्थापित करते हुए ओवरले के आसपास before/after सेगमेंट निकालता है।\n- `sanitizeText`: ANSI एस्केप + कंट्रोल कैरेक्टर हटाता है, अकेले surrogates छोड़ता है, `\\r` हटाकर CR/LF नॉर्मलाइज़ करता है।\n- `visibleWidth`: दृश्यमान टर्मिनल सेल गिनता है (टैब Rust कार्यान्वयन से निश्चित `TAB_WIDTH` उपयोग करते हैं)।\n\n### विफलता व्यवहार\n\nटेक्स्ट फ़ंक्शन आमतौर पर नियतात्मक रूपांतरित आउटपुट लौटाते हैं; त्रुटियाँ JS स्ट्रिंग रूपांतरण सीमाओं (N-API आर्ग्युमेंट रूपांतरण विफलताओं) तक सीमित हैं।\n\n## 5) सिंटैक्स हाइलाइटिंग (`highlight`)\n\n`highlight.rs` शुद्ध परिवर्तन है (कोई FS नहीं, कोई कैश नहीं)।\n\n### प्रवाह\n\n1. रैपर `code`, वैकल्पिक `lang`, और ANSI रंग पैलेट फॉरवर्ड करता है।\n2. Rust सिंटैक्स रिज़ॉल्व करता है:\n   - टोकन/नाम लुकअप\n   - एक्सटेंशन लुकअप\n   - उपनाम तालिका फॉलबैक (`ts/tsx/js -> JavaScript`, आदि)\n   - अनरिज़ॉल्व्ड होने पर सादे टेक्स्ट सिंटैक्स में फॉलबैक\n3. syntect `ParseState` और स्कोप स्टैक के साथ प्रत्येक लाइन पार्स करें।\n4. स्कोप को 11 सिमेंटिक रंग श्रेणियों में मैप करें और ANSI रंग कोड इंजेक्ट/रीसेट करें।\n\n### विफलता व्यवहार\n\n- प्रति-लाइन पार्स विफलता कॉल को विफल नहीं करती: वह लाइन बिना हाइलाइट के जोड़ी जाती है और प्रोसेसिंग जारी रहती है।\n- अज्ञात/असमर्थित भाषा सादे टेक्स्ट सिंटैक्स पर फॉलबैक करती है।\n\n## शुद्ध उपयोगिता बनाम फ़ाइलसिस्टम-निर्भर प्रवाह\n\n| प्रवाह | फ़ाइलसिस्टम एक्सेस | साझा कैश | टिप्पणियाँ |\n| --- | --- | --- | --- |\n| `searchContent` / `hasMatch` | नहीं | नहीं | केवल प्रदान की गई बाइट्स/स्ट्रिंग पर रेजेक्स |\n| `text` मॉड्यूल फ़ंक्शन | नहीं | नहीं | केवल ANSI/चौड़ाई/sanitization |\n| `highlight` मॉड्यूल फ़ंक्शन | नहीं | नहीं | केवल सिंटैक्स + ANSI रंगीकरण |\n| `glob` | हाँ | वैकल्पिक | डायरेक्टरी स्कैन + glob फ़िल्टरिंग |\n| `fuzzyFind` | हाँ | वैकल्पिक | डायरेक्टरी स्कैन + fuzzy स्कोरिंग |\n| `grep` (फ़ाइल/dir पथ) | हाँ | वैकल्पिक (dir मोड) | फ़ाइलों पर ripgrep, वैकल्पिक फ़िल्टर/कॉलबैक |\n\n## एंड-टू-एंड जीवनचक्र सारांश\n\n1. कॉलर टाइप्ड विकल्पों के साथ TS रैपर को आमंत्रित करता है।\n2. रैपर डिफ़ॉल्ट नॉर्मलाइज़ करता है (विशेष रूप से `glob`) और `native.*` एक्सपोर्ट को फॉरवर्ड करता है।\n3. Rust विकल्प सत्यापित/नॉर्मलाइज़ करता है और मैचर/खोज कॉन्फ़िगरेशन बनाता है।\n4. फ़ाइलसिस्टम प्रवाह के लिए, एंट्री स्कैन (कैश हिट/मिस/रीस्कैन) की जाती हैं और फिर फ़िल्टर/स्कोर की जाती हैं।\n5. वर्कर लूप समय-समय पर cancel heartbeat कॉल करते हैं; timeout/abort निष्पादन समाप्त कर सकते हैं।\n6. Rust आउटपुट को N-API ऑब्जेक्ट (`lineNumber`, `matchCount`, `limitReached`, आदि) में आकार देता है।\n7. TS रैपर टाइप्ड JS ऑब्जेक्ट लौटाता है (और `grep`/`glob` के लिए वैकल्पिक प्रति-मिलान कॉलबैक)।\n",
	"hi/natives/porting-to-natives.md": "---\ntitle: pi-natives (N-API) में पोर्टिंग — फील्ड नोट्स\ndescription: >-\n  Node.js child_process और shell कोड को Rust N-API native layer में माइग्रेट\n  करने के लिए फील्ड नोट्स।\nsidebar:\n  order: 9\n  label: pi-natives में पोर्टिंग\ni18n:\n  sourceHash: 4f5150286535\n  translator: machine\n---\n\n# pi-natives (N-API) में पोर्टिंग — फील्ड नोट्स\n\nयह हॉट पाथ्स को `crates/pi-natives` में ले जाने और उन्हें JS बाइंडिंग्स के माध्यम से जोड़ने के लिए एक व्यावहारिक गाइड है। यह इसलिए मौजूद है ताकि एक ही विफलताएँ दोबारा न हों।\n\n## कब पोर्ट करें\n\nजब इनमें से कोई भी सत्य हो तो पोर्ट करें:\n\n- हॉट पाथ रेंडर लूप्स, तेज UI अपडेट्स, या बड़े बैचेस में चलता है।\n- JS आवंटन प्रमुख हैं (स्ट्रिंग चर्न, regex बैकट्रैकिंग, बड़े arrays)।\n- आपके पास पहले से JS बेसलाइन है और आप दोनों संस्करणों को साथ-साथ बेंचमार्क कर सकते हैं।\n- कार्य CPU-बाउंड है या ब्लॉकिंग I/O है जो libuv थ्रेड पूल पर चल सकता है।\n- कार्य async I/O है जो Tokio के रनटाइम पर चल सकता है (जैसे, shell execution)।\n\nऐसे पोर्ट्स से बचें जो JS-only स्टेट या डायनामिक imports पर निर्भर करते हैं। N-API exports शुद्ध, data-in/data-out होने चाहिए। लंबे समय तक चलने वाले कार्य `task::blocking` (CPU-बाउंड/ब्लॉकिंग I/O) या `task::future` (async I/O) के माध्यम से cancellation के साथ जाने चाहिए।\n\n## एक native export की संरचना\n\n**Rust पक्ष:**\n\n- कार्यान्वयन `crates/pi-natives/src/<module>.rs` में होता है। यदि आप एक नया मॉड्यूल जोड़ते हैं, तो इसे `crates/pi-natives/src/lib.rs` में रजिस्टर करें।\n- `#[napi]` के साथ एक्सपोर्ट करें; snake_case exports स्वचालित रूप से camelCase में बदल जाते हैं। स्पष्ट `js_name` का उपयोग केवल वास्तविक aliases/गैर-डिफ़ॉल्ट नामों के लिए करें। structs के लिए `#[napi(object)]` का उपयोग करें।\n- CPU-बाउंड या ब्लॉकिंग कार्य के लिए `task::blocking(tag, cancel_token, work)` (देखें `crates/pi-natives/src/task.rs`) का उपयोग करें। async कार्य के लिए जिसे Tokio की आवश्यकता है (जैसे, shell sessions) `task::future(env, tag, work)` का उपयोग करें। जब आप `timeoutMs` या `AbortSignal` एक्सपोज़ करते हैं तो `CancelToken` पास करें।\n\n**JS पक्ष:**\n\n- `packages/natives/src/bindings.ts` में बेस `NativeBindings` इंटरफ़ेस होता है।\n- `packages/natives/src/<module>/types.ts` TS प्रकार परिभाषित करता है और declaration merging के माध्यम से `NativeBindings` को augment करता है।\n- `packages/natives/src/native.ts` declarations को सक्रिय करने के लिए प्रत्येक `<module>/types.ts` फाइल को import करता है।\n- `packages/natives/src/<module>/index.ts` `packages/natives/src/native.ts` से `native` बाइंडिंग को wrap करता है।\n- `packages/natives/src/native.ts` addon लोड करता है और `validateNative` आवश्यक exports को enforce करता है।\n- `packages/natives/src/index.ts` `packages/*` में कॉलर्स के लिए wrapper को re-export करता है।\n\n## पोर्टिंग चेकलिस्ट\n\n1. **Rust कार्यान्वयन जोड़ें**\n\n- मुख्य लॉजिक को एक सामान्य Rust फंक्शन में रखें।\n- यदि यह एक नया मॉड्यूल है, तो इसे `crates/pi-natives/src/lib.rs` में जोड़ें।\n- इसे `#[napi]` के साथ एक्सपोज़ करें ताकि डिफ़ॉल्ट snake_case -> camelCase मैपिंग सुसंगत रहे।\n- सिग्नेचर owned और सरल रखें: `String`, `Vec<String>`, `Uint8Array`, या बड़े string/byte inputs के लिए `Either<JsString, Uint8Array>`।\n- CPU-बाउंड या ब्लॉकिंग कार्य के लिए `task::blocking` का उपयोग करें; async कार्य के लिए `task::future` का उपयोग करें। एक `CancelToken` पास करें और लंबे लूप्स के अंदर `heartbeat()` कॉल करें।\n\n2. **JS बाइंडिंग्स जोड़ें**\n\n- `packages/natives/src/<module>/types.ts` में types और `NativeBindings` augmentation जोड़ें।\n- Declaration merging ट्रिगर करने के लिए `packages/natives/src/native.ts` में `./<module>/types` import करें।\n- `packages/natives/src/<module>/index.ts` में एक wrapper जोड़ें जो `native` को कॉल करता है।\n- `packages/natives/src/index.ts` से re-export करें।\n\n3. **Native validation अपडेट करें**\n\n- `validateNative` (`packages/natives/src/native.ts`) में `checkFn(\"newExport\")` जोड़ें।\n\n4. **बेंचमार्क जोड़ें**\n\n- बेंचमार्क को स्वामी पैकेज के पास रखें (`packages/tui/bench`, `packages/natives/bench`, या `packages/coding-agent/bench`)।\n- एक ही रन में JS बेसलाइन और native संस्करण दोनों शामिल करें।\n- `Bun.nanoseconds()` और एक निश्चित iteration count का उपयोग करें।\n- बेंचमार्क inputs छोटे और यथार्थवादी रखें (हॉट पाथ में दिखने वाला वास्तविक डेटा)।\n\n5. **Native बाइनरी बिल्ड करें**\n\n- `bun --cwd=packages/natives run build`\n- `bun --cwd=packages/natives run build` का उपयोग करें और यदि आप टेस्टिंग के दौरान loader diagnostics चाहते हैं तो `PI_DEV=1` सेट करें।\n\n6. **बेंचमार्क चलाएं**\n\n- `bun run packages/<pkg>/bench/<bench>.ts` (या `bun --cwd=packages/natives run bench`)\n\n7. **उपयोग पर निर्णय लें**\n\n- यदि native धीमा है, तो **JS रखें** और native export को अप्रयुक्त छोड़ दें।\n- यदि native तेज है, तो कॉल साइट्स को native wrapper पर स्विच करें।\n\n## समस्या बिंदु और उनसे बचने के तरीके\n\n### 1) पुराना `pi_natives.node` नए exports को रोकता है\n\nलोडर `packages/natives/native` में प्लेटफॉर्म-टैग्ड बाइनरी (`pi_natives.<platform>-<arch>.node`) को प्राथमिकता देता है। `PI_DEV=1` अब केवल loader diagnostics सक्षम करता है; यह अब एक अलग dev addon फ़ाइलनाम पर स्विच नहीं करता। एक फ़ॉलबैक `pi_natives.node` भी है। संकलित बाइनरी `~/.xcsh/natives/<version>/pi_natives.<platform>-<arch>.node` में extract होती हैं। यदि इनमें से कोई भी पुरानी है, तो exports अपडेट नहीं होंगे।\n\n**समाधान:** रीबिल्ड करने से पहले पुरानी फ़ाइल हटाएं।\n\n```bash\nrm packages/natives/native/pi_natives.linux-x64.node\nrm packages/natives/native/pi_natives.node\nbun --cwd=packages/natives run build\n```\n\nयदि आप एक संकलित बाइनरी चला रहे हैं, तो कैश्ड addon डायरेक्टरी हटाएं:\n\n```bash\nrm -rf ~/.xcsh/natives/<version>\n```\n\nफिर सत्यापित करें कि export बाइनरी में मौजूद है:\n\n```bash\nbun -e 'const tag = `${process.platform}-${process.arch}`; const mod = require(`./packages/natives/native/pi_natives.${tag}.node`); console.log(Object.keys(mod).includes(\"newExport\"));'\n```\n\n### 2) `validateNative` से \"Missing exports\" त्रुटियाँ\n\nयह **अच्छी बात है** — यह मूक बेमेलों को रोकता है। जब आप यह देखें:\n\n```\nNative addon missing exports ... Missing: visibleWidth\n```\n\nइसका मतलब है कि आपकी बाइनरी पुरानी है, Rust export नाम (या उपयोग किए जाने पर स्पष्ट alias) JS नाम से मेल नहीं खाता, या export कभी compile नहीं हुआ। बिल्ड और नामकरण बेमेल को ठीक करें, validation को कमजोर न करें।\n\n### 3) Rust सिग्नेचर बेमेल\n\nइसे सरल और owned रखें। `String`, `Vec<String>`, और `Uint8Array` काम करते हैं। पब्लिक exports में `&str` जैसे references से बचें। यदि आपको स्ट्रक्चर्ड डेटा चाहिए, तो इसे `#[napi(object)]` structs में wrap करें।\n\n### 4) बेंचमार्किंग गलतियाँ\n\n- विभिन्न inputs या allocations की तुलना न करें।\n- JS और native दोनों को समान input arrays का उपयोग करने दें।\n- दोनों को एक ही बेंचमार्क फ़ाइल में चलाएं ताकि विषमता से बचा जा सके।\n\n## बेंचमार्क टेम्पलेट\n\n```ts\nconst ITERATIONS = 2000;\n\nfunction bench(name: string, fn: () => void): number {\n const start = Bun.nanoseconds();\n for (let i = 0; i < ITERATIONS; i++) fn();\n const elapsed = (Bun.nanoseconds() - start) / 1e6;\n console.log(`${name}: ${elapsed.toFixed(2)}ms total (${(elapsed / ITERATIONS).toFixed(6)}ms/op)`);\n return elapsed;\n}\n\nbench(\"feature/js\", () => {\n jsImpl(sample);\n});\n\nbench(\"feature/native\", () => {\n nativeImpl(sample);\n});\n```\n\n## सत्यापन चेकलिस्ट\n\n- `validateNative` पास होता है (कोई missing exports नहीं)।\n- `NativeBindings` `packages/natives/src/<module>/types.ts` में augment किया गया है और wrapper `packages/natives/src/index.ts` में re-export किया गया है।\n- `Object.keys(require(...))` में आपका नया export शामिल है।\n- PR/नोट्स में बेंच नंबर दर्ज हैं।\n- कॉल साइट **केवल तभी** अपडेट किया गया जब native तेज या बराबर हो।\n\n## अंगूठे का नियम\n\n- यदि native धीमा है, तो **स्विच न करें**। भविष्य के कार्य के लिए export रखें, लेकिन TUI को तेज पाथ पर रहना चाहिए।\n- यदि native तेज है, तो कॉल साइट स्विच करें और regressions पकड़ने के लिए बेंचमार्क बनाए रखें।\n",
	"hi/providers/models.md": "---\ntitle: मॉडल और प्रोवाइडर कॉन्फ़िगरेशन\ndescription: >-\n  रूटिंग, फ़ॉलबैक और मूल्य निर्धारण के साथ models.yml के माध्यम से मॉडल\n  रजिस्ट्री और प्रोवाइडर कॉन्फ़िगरेशन।\nsidebar:\n  order: 1\n  label: मॉडल और प्रोवाइडर\ni18n:\n  sourceHash: 8053df967ff6\n  translator: machine\n---\n\n# मॉडल और प्रोवाइडर कॉन्फ़िगरेशन (`models.yml`)\n\nयह दस्तावेज़ बताता है कि coding-agent वर्तमान में मॉडल कैसे लोड करता है, ओवरराइड लागू करता है, क्रेडेंशियल रिज़ॉल्व करता है, और रनटाइम पर मॉडल चुनता है।\n\n## मॉडल व्यवहार को क्या नियंत्रित करता है\n\nप्राथमिक कार्यान्वयन फ़ाइलें:\n\n- `src/config/model-registry.ts` — बिल्ट-इन + कस्टम मॉडल, प्रोवाइडर ओवरराइड, रनटाइम डिस्कवरी, auth इंटीग्रेशन लोड करता है\n- `src/config/model-resolver.ts` — मॉडल पैटर्न पार्स करता है और initial/smol/slow मॉडल चुनता है\n- `src/config/settings-schema.ts` — मॉडल-संबंधित सेटिंग्स (`modelRoles`, प्रोवाइडर ट्रांसपोर्ट प्राथमिकताएं)\n- `src/session/auth-storage.ts` — API key + OAuth रिज़ॉल्यूशन क्रम\n- `packages/ai/src/models.ts` और `packages/ai/src/types.ts` — बिल्ट-इन प्रोवाइडर/मॉडल और `Model`/`compat` टाइप\n\n## कॉन्फ़िग फ़ाइल स्थान और लेगेसी व्यवहार\n\nडिफ़ॉल्ट कॉन्फ़िग पाथ:\n\n- `~/.xcsh/agent/models.yml`\n\nलेगेसी व्यवहार अभी भी मौजूद है:\n\n- यदि `models.yml` अनुपस्थित है और उसी स्थान पर `models.json` मौजूद है, तो इसे `models.yml` में माइग्रेट किया जाता है।\n- स्पष्ट `.json` / `.jsonc` कॉन्फ़िग पाथ अभी भी समर्थित हैं जब `ModelRegistry` को प्रोग्रामेटिक रूप से पास किए जाएं।\n\n## `models.yml` संरचना\n\n```yaml\nconfigVersion: 1  # optional — written by auto-config, used for migration detection\nproviders:\n  <provider-id>:\n    # provider-level config\nequivalence:\n  overrides:\n    <provider-id>/<model-id>: <canonical-model-id>\n  exclude:\n    - <provider-id>/<model-id>\n```\n\n`configVersion` एक वैकल्पिक integer है जो auto-config सिस्टम द्वारा लिखा जाता है। जब मौजूद हो, xcsh इसका उपयोग पुराने कॉन्फ़िग का पता लगाने और उन्हें ऑटो-अपग्रेड करने के लिए करता है।\n\n`provider-id` वह canonical प्रोवाइडर की है जो चयन और auth लुकअप में उपयोग होती है।\n\n`equivalence` वैकल्पिक है और concrete प्रोवाइडर मॉडल के ऊपर canonical मॉडल ग्रुपिंग कॉन्फ़िगर करता है:\n\n- `overrides` एक exact concrete selector (`provider/modelId`) को आधिकारिक upstream canonical id से मैप करता है\n- `exclude` एक concrete selector को canonical ग्रुपिंग से बाहर करता है\n\n## प्रोवाइडर-स्तरीय फ़ील्ड\n\n```yaml\nproviders:\n  my-provider:\n    baseUrl: https://api.example.com/v1\n    apiKey: MY_PROVIDER_API_KEY\n    api: openai-completions\n    headers:\n      X-Team: platform\n    authHeader: true\n    auth: apiKey\n    discovery:\n      type: ollama\n    modelOverrides:\n      some-model-id:\n        name: Renamed model\n    models:\n      - id: some-model-id\n        name: Some Model\n        api: openai-completions\n        reasoning: false\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 128000\n        maxTokens: 16384\n        headers:\n          X-Model: value\n        compat:\n          supportsStore: true\n          supportsDeveloperRole: true\n          supportsReasoningEffort: true\n          maxTokensField: max_completion_tokens\n          openRouterRouting:\n            only: [anthropic]\n          vercelGatewayRouting:\n            order: [anthropic, openai]\n          extraBody:\n            gateway: m1-01\n            controller: mlx\n```\n\n### अनुमत प्रोवाइडर/मॉडल `api` मान\n\n- `openai-completions`\n- `openai-responses`\n- `openai-codex-responses`\n- `azure-openai-responses`\n- `anthropic-messages`\n- `google-generative-ai`\n- `google-vertex`\n\n### अनुमत auth/discovery मान\n\n- `auth`: `apiKey` (डिफ़ॉल्ट) या `none`\n- `discovery.type`: `ollama`\n\n## सत्यापन नियम (वर्तमान)\n\n### पूर्ण कस्टम प्रोवाइडर (`models` non-empty है)\n\nआवश्यक:\n\n- `baseUrl`\n- `apiKey` जब तक `auth: none` न हो\n- `api` प्रोवाइडर स्तर पर या प्रत्येक मॉडल में\n\n### केवल-ओवरराइड प्रोवाइडर (`models` अनुपस्थित या empty)\n\nनिम्न में से कम से कम एक परिभाषित होना चाहिए:\n\n- `baseUrl`\n- `modelOverrides`\n- `discovery`\n\n### डिस्कवरी\n\n- `discovery` के लिए प्रोवाइडर-स्तरीय `api` आवश्यक है।\n\n### मॉडल मान जांच\n\n- `id` आवश्यक है\n- `contextWindow` और `maxTokens` यदि प्रदान किए गए हों तो सकारात्मक होने चाहिए\n\n## मर्ज और ओवरराइड क्रम\n\nModelRegistry पाइपलाइन (रिफ्रेश पर):\n\n1. `@f5-sales-demo/pi-ai` से बिल्ट-इन प्रोवाइडर/मॉडल लोड करें।\n2. `models.yml` कस्टम कॉन्फ़िग लोड करें।\n3. बिल्ट-इन मॉडलों पर प्रोवाइडर ओवरराइड (`baseUrl`, `headers`) लागू करें।\n4. `modelOverrides` (प्रति प्रोवाइडर + मॉडल id) लागू करें।\n5. कस्टम `models` मर्ज करें:\n   - समान `provider + id` मौजूदा को बदलता है\n   - अन्यथा जोड़ें\n6. रनटाइम-डिस्कवर्ड मॉडल (वर्तमान में Ollama और LM Studio) लागू करें, फिर मॉडल ओवरराइड पुनः लागू करें।\n\n## Canonical मॉडल समतुल्यता और कोलेसिंग\n\nरजिस्ट्री हर concrete प्रोवाइडर मॉडल को रखती है और फिर उनके ऊपर एक canonical लेयर बनाती है।\n\nCanonical ids केवल आधिकारिक upstream ids हैं, उदाहरण के लिए:\n\n- `claude-opus-4-6`\n- `claude-haiku-4-5`\n- `gpt-5.3-codex`\n\n### `models.yml` equivalence कॉन्फ़िग\n\nउदाहरण:\n\n```yaml\nproviders:\n  zenmux:\n    baseUrl: https://api.zenmux.example/v1\n    apiKey: ZENMUX_API_KEY\n    api: openai-codex-responses\n    models:\n      - id: codex\n        name: Zenmux Codex\n        reasoning: true\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 200000\n        maxTokens: 32768\n\nequivalence:\n  overrides:\n    zenmux/codex: gpt-5.3-codex\n    p-codex/codex: gpt-5.3-codex\n  exclude:\n    - demo/codex-preview\n```\n\nCanonical ग्रुपिंग के लिए बिल्ड क्रम:\n\n1. `equivalence.overrides` से exact यूज़र ओवरराइड\n2. बिल्ट-इन मॉडल मेटाडेटा से बंडल्ड official-id मिलान\n3. gateway/provider वेरिएंट के लिए conservative heuristic normalization\n4. concrete मॉडल की अपनी id पर फ़ॉलबैक\n\nवर्तमान heuristics जानबूझकर संकीर्ण हैं:\n\n- एम्बेडेड upstream प्रीफ़िक्स जब मौजूद हों तो हटाए जा सकते हैं, उदाहरण के लिए `anthropic/...` या `openai/...`\n- डॉटेड और डैश्ड वर्शन वेरिएंट केवल तब normalize हो सकते हैं जब वे किसी मौजूदा official id से मैप होते हों, उदाहरण के लिए `4.6 -> 4-6`\n- अस्पष्ट families या versions को बंडल्ड मिलान या explicit ओवरराइड के बिना मर्ज नहीं किया जाता\n\n### Canonical रिज़ॉल्यूशन व्यवहार\n\nजब कई concrete वेरिएंट एक canonical id साझा करते हैं, तो रिज़ॉल्यूशन उपयोग करता है:\n\n1. उपलब्धता और auth\n2. `config.yml` `modelProviderOrder`\n3. यदि `modelProviderOrder` अनसेट है तो मौजूदा रजिस्ट्री/प्रोवाइडर क्रम\n\nअक्षम या अनअथेंटिकेटेड प्रोवाइडर को छोड़ दिया जाता है।\n\nसेशन स्थिति और ट्रांसक्रिप्ट concrete प्रोवाइडर/मॉडल को रिकॉर्ड करते रहते हैं जिसने वास्तव में टर्न निष्पादित किया।\n\nप्रोवाइडर डिफ़ॉल्ट बनाम प्रति-मॉडल ओवरराइड:\n\n- प्रोवाइडर `headers` बेसलाइन हैं।\n- मॉडल `headers` प्रोवाइडर हेडर की को ओवरराइड करते हैं।\n- `modelOverrides` मॉडल मेटाडेटा (`name`, `reasoning`, `input`, `cost`, `contextWindow`, `maxTokens`, `headers`, `compat`, `contextPromotionTarget`) ओवरराइड कर सकते हैं।\n- `compat` nested रूटिंग ब्लॉक (`openRouterRouting`, `vercelGatewayRouting`, `extraBody`) के लिए deep-merged होता है।\n\n## रनटाइम डिस्कवरी इंटीग्रेशन\n\n### Implicit Ollama डिस्कवरी\n\nयदि `ollama` स्पष्ट रूप से कॉन्फ़िगर नहीं है, तो रजिस्ट्री एक implicit discoverable प्रोवाइडर जोड़ती है:\n\n- प्रोवाइडर: `ollama`\n- api: `openai-completions`\n- बेस URL: `OLLAMA_BASE_URL` या `http://127.0.0.1:11434`\n- auth मोड: keyless (`auth: none` व्यवहार)\n\nरनटाइम डिस्कवरी Ollama पर `GET /api/tags` कॉल करती है और local डिफ़ॉल्ट के साथ मॉडल एंट्री synthesize करती है।\n\n### Implicit llama.cpp डिस्कवरी\n\nयदि `llama.cpp` स्पष्ट रूप से कॉन्फ़िगर नहीं है, तो रजिस्ट्री एक implicit discoverable प्रोवाइडर जोड़ती है:\nनोट: यह openai-completions के बजाय नई anthropic messages api का उपयोग कर रहा है।\n\n- प्रोवाइडर: `llama.cpp`\n- api: `openai-responses`\n- बेस URL: `LLAMA_CPP_BASE_URL` या `http://127.0.0.1:8080`\n- auth मोड: keyless (`auth: none` व्यवहार)\n\nरनटाइम डिस्कवरी llama.cpp पर `GET models` कॉल करती है और local डिफ़ॉल्ट के साथ मॉडल एंट्री synthesize करती है।\n\n### Implicit LM Studio डिस्कवरी\n\nयदि `lm-studio` स्पष्ट रूप से कॉन्फ़िगर नहीं है, तो रजिस्ट्री एक implicit discoverable प्रोवाइडर जोड़ती है:\n\n- प्रोवाइडर: `lm-studio`\n- api: `openai-completions`\n- बेस URL: `LM_STUDIO_BASE_URL` या `http://127.0.0.1:1234/v1`\n- auth मोड: keyless (`auth: none` व्यवहार)\n\nरनटाइम डिस्कवरी मॉडल फ़ेच करती है (`GET /models`) और local डिफ़ॉल्ट के साथ मॉडल एंट्री synthesize करती है।\n\n### स्पष्ट प्रोवाइडर डिस्कवरी\n\nआप डिस्कवरी स्वयं कॉन्फ़िगर कर सकते हैं:\n\n```yaml\nproviders:\n  ollama:\n    baseUrl: http://127.0.0.1:11434\n    api: openai-completions\n    auth: none\n    discovery:\n      type: ollama\n      \n  llama.cpp:\n    baseUrl: http://127.0.0.1:8080\n    api: openai-responses\n    auth: none\n    discovery:\n      type: llama.cpp\n```\n\n### एक्सटेंशन प्रोवाइडर पंजीकरण\n\nएक्सटेंशन रनटाइम पर प्रोवाइडर रजिस्टर कर सकते हैं (`pi.registerProvider(...)`), जिनमें शामिल हैं:\n\n- किसी प्रोवाइडर के लिए मॉडल प्रतिस्थापन/जोड़\n- नए API IDs के लिए कस्टम स्ट्रीम हैंडलर पंजीकरण\n- कस्टम OAuth प्रोवाइडर पंजीकरण\n\n## Auth और API key रिज़ॉल्यूशन क्रम\n\nकिसी प्रोवाइडर के लिए key अनुरोध करते समय, प्रभावी क्रम है:\n\n1. रनटाइम ओवरराइड (CLI `--api-key`)\n2. `agent.db` में स्टोर्ड API key क्रेडेंशियल\n3. `agent.db` में स्टोर्ड OAuth क्रेडेंशियल (रिफ्रेश के साथ)\n4. एनवायरनमेंट वेरिएबल मैपिंग (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, आदि)\n5. ModelRegistry फ़ॉलबैक रिज़ॉल्वर (प्रोवाइडर `apiKey` `models.yml` से, env-name-or-literal semantics)\n\n`models.yml` `apiKey` व्यवहार:\n\n- मान को पहले एनवायरनमेंट वेरिएबल नाम के रूप में ट्रीट किया जाता है।\n- यदि कोई env var मौजूद नहीं है, तो literal string को टोकन के रूप में उपयोग किया जाता है।\n\nयदि `authHeader: true` और प्रोवाइडर `apiKey` सेट है, तो मॉडलों को मिलता है:\n\n- `Authorization: Bearer <resolved-key>` हेडर इंजेक्ट।\n\nKeyless प्रोवाइडर:\n\n- `auth: none` चिह्नित प्रोवाइडर को बिना क्रेडेंशियल के उपलब्ध माना जाता है।\n- `getApiKey*` उनके लिए `kNoAuth` लौटाता है।\n\n## मॉडल उपलब्धता बनाम सभी मॉडल\n\n- `getAll()` लोडेड मॉडल रजिस्ट्री (बिल्ट-इन + मर्ज्ड कस्टम + डिस्कवर्ड) लौटाता है।\n- `getAvailable()` उन मॉडलों तक फ़िल्टर करता है जो keyless हैं या जिनका auth रिज़ॉल्व हो सकता है।\n\nइसलिए एक मॉडल रजिस्ट्री में मौजूद हो सकता है लेकिन auth उपलब्ध होने तक चयन योग्य नहीं होगा।\n\n## रनटाइम मॉडल रिज़ॉल्यूशन\n\n### CLI और पैटर्न पार्सिंग\n\n`model-resolver.ts` समर्थन करता है:\n\n- exact `provider/modelId`\n- exact canonical मॉडल id\n- exact मॉडल id (प्रोवाइडर अनुमानित)\n- fuzzy/substring मिलान\n- `--models` में glob स्कोप पैटर्न (जैसे `openai/*`, `*sonnet*`)\n- वैकल्पिक `:thinkingLevel` suffix (`off|minimal|low|medium|high|xhigh`)\n\n`--provider` लेगेसी है; `--model` प्राथमिक है।\n\nExact selectors के लिए रिज़ॉल्यूशन प्राथमिकता:\n\n1. exact `provider/modelId` coalescing को bypass करता है\n2. exact canonical id canonical इंडेक्स के माध्यम से रिज़ॉल्व होता है\n3. exact bare concrete id अभी भी काम करता है\n4. fuzzy और glob मिलान exact पाथ के बाद चलते हैं\n\n### Initial मॉडल चयन प्राथमिकता\n\n`findInitialModel(...)` इस क्रम का उपयोग करता है:\n\n1. explicit CLI provider+model\n2. पहला scoped मॉडल (यदि resume नहीं हो रहा)\n3. सेव्ड डिफ़ॉल्ट provider/model\n4. उपलब्ध मॉडलों में ज्ञात प्रोवाइडर डिफ़ॉल्ट (जैसे OpenAI/Anthropic/आदि)\n5. पहला उपलब्ध मॉडल\n\n### Role aliases और सेटिंग्स\n\nसमर्थित मॉडल roles:\n\n- `default`, `smol`, `slow`, `plan`, `commit`\n\n`pi/smol` जैसे Role aliases `settings.modelRoles` के माध्यम से expand होते हैं। प्रत्येक role मान `:minimal`, `:low`, `:medium`, या `:high` जैसा thinking selector भी जोड़ सकता है।\n\nयदि कोई role किसी अन्य role की ओर इंगित करता है, तो लक्ष्य मॉडल सामान्य रूप से inherit करता है और referring role पर कोई explicit suffix उस role-specific उपयोग के लिए जीतता है।\n\nसंबंधित सेटिंग्स:\n\n- `modelRoles` (record)\n- `enabledModels` (scoped pattern list)\n- `modelProviderOrder` (global canonical-provider precedence)\n- `providers.kimiApiFormat` (`openai` या `anthropic` request format)\n- `providers.openaiWebsockets` (OpenAI Codex transport के लिए `auto|off|on` websocket प्राथमिकता)\n\n`modelRoles` निम्न में से कोई एक स्टोर कर सकता है:\n\n- `provider/modelId` किसी concrete प्रोवाइडर वेरिएंट को पिन करने के लिए\n- `gpt-5.3-codex` जैसी canonical id जो provider coalescing की अनुमति देती है\n\n`enabledModels` और CLI `--models` के लिए:\n\n- exact canonical ids उस canonical समूह में सभी concrete वेरिएंट तक expand होती हैं\n- explicit `provider/modelId` एंट्री exact रहती हैं\n- globs और fuzzy मिलान अभी भी concrete मॉडलों पर काम करते हैं\n\n## `/model` और `--list-models`\n\nदोनों सतहें provider-prefixed मॉडलों को दृश्यमान और चयन योग्य रखती हैं।\n\nअब वे canonical/coalesced मॉडलों को भी expose करती हैं:\n\n- `/model` प्रोवाइडर tabs के साथ canonical दृश्य शामिल करता है\n- `--list-models` concrete प्रोवाइडर rows के साथ एक canonical section प्रिंट करता है\n\nCanonical एंट्री चुनने पर canonical selector स्टोर होता है। प्रोवाइडर row चुनने पर explicit `provider/modelId` स्टोर होता है।\n\n## Context promotion (मॉडल-स्तरीय फ़ॉलबैक चेन)\n\nContext promotion छोटे-context वेरिएंट (उदाहरण के लिए `*-spark`) के लिए एक overflow recovery mechanism है जो स्वचालित रूप से एक बड़े-context sibling पर promote करता है जब API context length error के साथ request reject करता है।\n\n### ट्रिगर और क्रम\n\nजब कोई टर्न context overflow error (जैसे `context_length_exceeded`) के साथ विफल होता है, `AgentSession` compaction से पहले promotion का प्रयास करता है **पहले**:\n\n1. यदि `contextPromotion.enabled` true है, तो promotion target रिज़ॉल्व करें (नीचे देखें)।\n2. यदि target मिलता है, उस पर switch करें और request retry करें — कोई compaction की आवश्यकता नहीं।\n3. यदि कोई target उपलब्ध नहीं है, तो current मॉडल पर auto-compaction पर fall through करें।\n\n### Target चयन\n\nचयन मॉडल-driven है, role-driven नहीं:\n\n1. `currentModel.contextPromotionTarget` (यदि कॉन्फ़िगर किया गया हो)\n2. समान प्रोवाइडर + API पर सबसे छोटा बड़े-context वाला मॉडल\n\nउम्मीदवारों को नज़रअंदाज़ किया जाता है जब तक क्रेडेंशियल रिज़ॉल्व न हों (`ModelRegistry.getApiKey(...)`)।\n\n### OpenAI Codex websocket handoff\n\nयदि `openai-codex-responses` से/से switch हो रहा है, तो मॉडल switch से पहले session provider state key `openai-codex-responses` बंद हो जाती है। यह websocket transport state को drop करता है ताकि अगला टर्न promoted मॉडल पर clean शुरू हो।\n\n### Persistence व्यवहार\n\nPromotion temporary switching (`setModelTemporary`) का उपयोग करता है:\n\n- session history में temporary `model_change` के रूप में रिकॉर्ड\n- सेव्ड role mapping को पुनर्लेखित नहीं करता\n\n### Explicit फ़ॉलबैक चेन कॉन्फ़िगर करना\n\n`contextPromotionTarget` के माध्यम से मॉडल मेटाडेटा में सीधे फ़ॉलबैक कॉन्फ़िगर करें।\n\n`contextPromotionTarget` निम्न में से कोई एक स्वीकार करता है:\n\n- `provider/model-id` (explicit)\n- `model-id` (current प्रोवाइडर के भीतर रिज़ॉल्व)\n\nउदाहरण (`models.yml`) समान प्रोवाइडर पर Spark -> non-Spark के लिए:\n\n```yaml\nproviders:\n  openai-codex:\n    modelOverrides:\n      gpt-5.3-codex-spark:\n        contextPromotionTarget: openai-codex/gpt-5.3-codex\n```\n\nबिल्ट-इन मॉडल generator `*-spark` मॉडलों के लिए इसे स्वचालित रूप से तब assign करता है जब same-provider base मॉडल मौजूद हो।\n\n## Compatibility और routing फ़ील्ड\n\n`models.yml` यह `compat` subset समर्थन करता है:\n\n- `supportsStore`\n- `supportsDeveloperRole`\n- `supportsReasoningEffort`\n- `maxTokensField` (`max_completion_tokens` या `max_tokens`)\n- `openRouterRouting.only` / `openRouterRouting.order`\n- `vercelGatewayRouting.only` / `vercelGatewayRouting.order`\n\nइन्हें OpenAI-completions transport logic द्वारा उपभोग किया जाता है और URL-based auto-detection के साथ संयुक्त किया जाता है।\n\n## व्यावहारिक उदाहरण\n\n### Local OpenAI-compatible endpoint (बिना auth)\n\n```yaml\nproviders:\n  local-openai:\n    baseUrl: http://127.0.0.1:8000/v1\n    auth: none\n    api: openai-completions\n    models:\n      - id: Qwen/Qwen2.5-Coder-32B-Instruct\n        name: Qwen 2.5 Coder 32B (local)\n```\n\n### Env-based key के साथ hosted proxy\n\n```yaml\nproviders:\n  anthropic-proxy:\n    baseUrl: https://proxy.example.com/anthropic\n    apiKey: ANTHROPIC_PROXY_API_KEY\n    api: anthropic-messages\n    authHeader: true\n    models:\n      - id: claude-sonnet-4-20250514\n        name: Claude Sonnet 4 (Proxy)\n        reasoning: true\n        input: [text, image]\n```\n\n### बिल्ट-इन प्रोवाइडर route + मॉडल मेटाडेटा ओवरराइड\n\n```yaml\nproviders:\n  openrouter:\n    baseUrl: https://my-proxy.example.com/v1\n    headers:\n      X-Team: platform\n    modelOverrides:\n      anthropic/claude-sonnet-4:\n        name: Sonnet 4 (Corp)\n        compat:\n          openRouterRouting:\n            only: [anthropic]\n```\n\n## LiteLLM proxy ऑटो-कॉन्फ़िगरेशन\n\nजब `LITELLM_BASE_URL` और `LITELLM_API_KEY` दोनों एनवायरनमेंट वेरिएबल सेट हों, xcsh LiteLLM proxy के लिए `models.yml` कॉन्फ़िगरेशन स्वचालित रूप से प्रबंधित करता है।\n\n### पहली-बार ऑटो-जनरेशन\n\nयदि `models.yml` मौजूद नहीं है और LiteLLM env vars का पता चलता है, xcsh इसे स्वचालित रूप से generate करता है:\n\n```yaml\n# Auto-generated by xcsh for LiteLLM proxy\n# API key resolved from LITELLM_API_KEY env var at runtime\nconfigVersion: 1\nproviders:\n  anthropic:\n    baseUrl: \"https://your-litellm-proxy.example.com/anthropic\"\n    apiKey: LITELLM_API_KEY\n```\n\nएक डिफ़ॉल्ट `config.yml` भी sensible image provider settings के साथ generate होता है।\n\n### Startup self-healing\n\nहर startup पर, model registry में `startupHealthCheck()` निम्न जांचें चलाता है:\n\n| शर्त | कार्रवाई |\n|-----------|--------|\n| `models.yml` अनुपस्थित | env vars से ऑटो-generate करें |\n| `models.yml` corrupt या parse न हो सके | `.bak` पर backup, regenerate |\n| `baseUrl` `LITELLM_BASE_URL` से मेल नहीं खाता | `.bak` पर backup, नए URL के साथ regenerate |\n| `configVersion` अनुपस्थित या पुराना | `.bak` पर backup, current version के साथ regenerate |\n| कॉन्फ़िग healthy है | कोई कार्रवाई नहीं |\n\nसभी मरम्मत ओवरराइट से पहले `.bak` backup बनाते हैं। सभी ऑपरेशन idempotent हैं।\n\n### CLI कमांड\n\n```bash\nxcsh setup litellm              # Generate or fix LiteLLM config\nxcsh setup litellm --check      # Validate without writing\nxcsh setup litellm --check --json  # Machine-readable validation output\n```\n\n### आवश्यक एनवायरनमेंट वेरिएबल\n\n| वेरिएबल | उद्देश्य |\n|----------|---------|\n| `LITELLM_BASE_URL` | LiteLLM proxy URL (जैसे `https://your-proxy.example.com`)। `http://` या `https://` से शुरू होना चाहिए। |\n| `LITELLM_API_KEY` | proxy के लिए API key। generated config में नाम से referenced, रनटाइम पर रिज़ॉल्व। |\n\nयदि कोई भी वेरिएबल unset है, तो ऑटो-कॉन्फ़िगरेशन silently skip हो जाता है।\n\n### कॉन्फ़िग versioning\n\nGenerated configs में `configVersion` फ़ील्ड शामिल होता है। जब भविष्य के releases में generated format बदलता है, xcsh पुराने configs का पता लगाता है और उन्हें स्वचालित रूप से upgrade करता है (backup के साथ)।\n\n## लेगेसी consumer सावधानी\n\nअधिकांश मॉडल कॉन्फ़िगरेशन अब `ModelRegistry` के माध्यम से `models.yml` से होकर जाता है।\n\nएक उल्लेखनीय लेगेसी पाथ बना हुआ है: web-search Anthropic auth resolution अभी भी `src/web/search/auth.ts` में सीधे `~/.xcsh/agent/models.json` पढ़ता है।\n\nयदि आप उस specific पाथ पर निर्भर हैं, तो JSON compatibility को ध्यान में रखें जब तक वह module माइग्रेट नहीं हो जाता।\n\n## विफलता मोड\n\nयदि `models.yml` schema या validation जांच में विफल होता है:\n\n- यदि `LITELLM_BASE_URL` और `LITELLM_API_KEY` सेट हैं, तो startup health check ऑटो-मरम्मत का प्रयास करता है (corrupt फ़ाइल backup करें, env vars से regenerate करें)। यदि मरम्मत सफल हो, तो रजिस्ट्री fixed config को reload करती है।\n- यदि ऑटो-मरम्मत संभव नहीं है (env vars unset, write विफलता), तो रजिस्ट्री बिल्ट-इन मॉडलों के साथ काम करती रहती है।\n- Error `ModelRegistry.getError()` के माध्यम से expose होती है और UI/notifications में दिखाई जाती है।\n",
	"hi/providers/provider-streaming-internals.md": "---\ntitle: प्रोवाइडर स्ट्रीमिंग आंतरिक संरचना\ndescription: >-\n  SSE पार्सिंग, टोकन काउंटिंग और बैकप्रेशर हैंडलिंग के साथ प्रोवाइडर स्ट्रीमिंग\n  कार्यान्वयन।\nsidebar:\n  order: 2\n  label: स्ट्रीमिंग आंतरिक संरचना\ni18n:\n  sourceHash: a32ffa769c4d\n  translator: machine\n---\n\n# प्रोवाइडर स्ट्रीमिंग आंतरिक संरचना\n\nयह दस्तावेज़ बताता है कि `@f5-sales-demo/pi-ai` में टोकन/टूल स्ट्रीमिंग को कैसे सामान्यीकृत किया जाता है, फिर `@f5-sales-demo/pi-agent-core` और `coding-agent` सत्र इवेंट्स के माध्यम से कैसे प्रसारित किया जाता है।\n\n## एंड-टू-एंड प्रवाह\n\n1. `streamSimple()` (`packages/ai/src/stream.ts`) सामान्य विकल्पों को मैप करता है और किसी प्रोवाइडर स्ट्रीम फ़ंक्शन को डिस्पैच करता है।\n2. प्रोवाइडर स्ट्रीम फ़ंक्शन (`anthropic.ts`, `openai-responses.ts`, `google.ts`) प्रोवाइडर-नेटिव स्ट्रीम इवेंट्स को एकीकृत `AssistantMessageEvent` अनुक्रम में रूपांतरित करते हैं।\n3. प्रत्येक प्रोवाइडर `AssistantMessageEventStream` (`packages/ai/src/utils/event-stream.ts`) में इवेंट्स पुश करता है, जो डेल्टा इवेंट्स को थ्रॉटल करता है और निम्नलिखित उजागर करता है:\n   - वृद्धिशील अपडेट के लिए async पुनरावृत्ति\n   - अंतिम `AssistantMessage` के लिए `result()`\n4. `agentLoop` (`packages/agent/src/agent-loop.ts`) उन इवेंट्स का उपभोग करता है, इन-फ़्लाइट असिस्टेंट स्थिति को संशोधित करता है, और कच्चे `assistantMessageEvent` वहन करने वाले `message_update` इवेंट्स उत्सर्जित करता है।\n5. `AgentSession` (`packages/coding-agent/src/session/agent-session.ts`) एजेंट इवेंट्स की सदस्यता लेता है, संदेशों को संग्रहीत करता है, एक्सटेंशन हुक्स चलाता है, और सत्र व्यवहार लागू करता है (retry, compaction, TTSR, streaming-edit abort जाँच)।\n\n## `@f5-sales-demo/pi-ai` में एकीकृत स्ट्रीम अनुबंध\n\nसभी प्रोवाइडर एक ही आकार उत्सर्जित करते हैं (`AssistantMessageEvent` in `packages/ai/src/types.ts`):\n\n- `start`\n- कंटेंट ब्लॉक लाइफसाइकिल ट्रिपलेट्स:\n  - टेक्स्ट: `text_start` → `text_delta`* → `text_end`\n  - थिंकिंग: `thinking_start` → `thinking_delta`* → `thinking_end`\n  - टूल कॉल: `toolcall_start` → `toolcall_delta`* → `toolcall_end`\n- टर्मिनल इवेंट:\n  - `done` with `reason: \"stop\" | \"length\" | \"toolUse\"`\n  - या `error` with `reason: \"aborted\" | \"error\"`\n\n`AssistantMessageEventStream` की गारंटी:\n\n- अंतिम परिणाम टर्मिनल इवेंट (`done` या `error`) द्वारा हल किया जाता है\n- डेल्टा को बैच/थ्रॉटल किया जाता है (~50ms)\n- बफर्ड डेल्टा को नॉन-डेल्टा इवेंट्स से पहले और पूर्णता से पहले फ्लश किया जाता है\n\n## डेल्टा थ्रॉटलिंग और हार्मोनाइज़ेशन व्यवहार\n\n`AssistantMessageEventStream` `text_delta`, `thinking_delta`, और `toolcall_delta` को विलयनीय इवेंट्स मानता है:\n\n- बफर्ड डेल्टा केवल तभी विलीन होते हैं जब **type + contentIndex** मेल खाएं\n- विलय नवीनतम `partial` स्नैपशॉट रखता है\n- नॉन-डेल्टा इवेंट्स तत्काल फ्लश को बाध्य करते हैं\n\nयह TUI/इवेंट उपभोक्ताओं के लिए उच्च-आवृत्ति प्रोवाइडर स्ट्रीम को सुचारू बनाता है, लेकिन यह प्रोवाइडर बैकप्रेशर नहीं है: प्रोवाइडर अभी भी पूरी गति से उत्पादन करते हैं, जबकि स्थानीय स्ट्रीम बफर करती है।\n\n## प्रोवाइडर सामान्यीकरण विवरण\n\n## Anthropic (`anthropic-messages`)\n\nस्रोत: `packages/ai/src/providers/anthropic.ts`\n\nसामान्यीकरण बिंदु:\n\n- `message_start` उपयोग (input/output/cache टोकन) को प्रारंभ करता है\n- `content_block_start` text/thinking/toolcall स्टार्ट्स पर मैप होता है\n- `content_block_delta` मैप करता है:\n  - `text_delta` → `text_delta`\n  - `thinking_delta` → `thinking_delta`\n  - `input_json_delta` → `toolcall_delta`\n  - `signature_delta` केवल `thinkingSignature` अपडेट करता है (कोई इवेंट नहीं)\n- `content_block_stop` संबंधित `*_end` उत्सर्जित करता है\n- `message_delta.stop_reason` को `mapStopReason()` के माध्यम से मैप किया जाता है\n\nटूल-कॉल आर्गुमेंट स्ट्रीमिंग:\n\n- प्रत्येक टूल ब्लॉक में आंतरिक `partialJson` होता है\n- प्रत्येक JSON डेल्टा `partialJson` में जोड़ा जाता है\n- `arguments` को प्रत्येक डेल्टा पर `parseStreamingJson()` के माध्यम से पुनः पार्स किया जाता है\n- `toolcall_end` एक बार और पार्स करता है, फिर `partialJson` हटाता है\n\n## OpenAI Responses (`openai-responses`)\n\nस्रोत: `packages/ai/src/providers/openai-responses.ts`\n\nसामान्यीकरण बिंदु:\n\n- `response.output_item.added` reasoning/text/function-call ब्लॉक शुरू करता है\n- reasoning summary इवेंट्स (`response.reasoning_summary_text.delta`) `thinking_delta` बनते हैं\n- output/refusal डेल्टा `text_delta` बनते हैं\n- `response.function_call_arguments.delta` `toolcall_delta` बनता है\n- `response.output_item.done` `thinking_end` / `text_end` / `toolcall_end` उत्सर्जित करता है\n- `response.completed` स्टेटस को stop reason और usage पर मैप करता है\n\nटूल-कॉल आर्गुमेंट स्ट्रीमिंग:\n\n- Anthropic जैसा ही `partialJson` संचय पैटर्न\n- ऐसे प्रोवाइडर जो केवल `response.function_call_arguments.done` भेजते हैं, अंतिम args को फिर भी पॉप्युलेट करते हैं\n- टूल कॉल IDs को `\"<call_id>|<item_id>\"` के रूप में सामान्यीकृत किया जाता है\n\n## Google Generative AI (`google-generative-ai`)\n\nस्रोत: `packages/ai/src/providers/google.ts`\n\nसामान्यीकरण बिंदु:\n\n- `candidate.content.parts` को इटरेट करता है\n- टेक्स्ट पार्ट्स को `isThinkingPart(part)` द्वारा thinking बनाम text में विभाजित किया जाता है\n- ब्लॉक ट्रांजिशन नया ब्लॉक शुरू करने से पहले पिछले को बंद करते हैं\n- `part.functionCall` को एक पूर्ण टूल कॉल माना जाता है (start/delta/end तुरंत उत्सर्जित)\n- finish reason को `google-shared.ts` से `mapStopReason()` द्वारा मैप किया जाता है\n\nटूल-कॉल आर्गुमेंट स्ट्रीमिंग:\n\n- function call args संरचित ऑब्जेक्ट के रूप में आते हैं, न कि वृद्धिशील JSON टेक्स्ट के रूप में\n- कार्यान्वयन `JSON.stringify(arguments)` युक्त एक सिंथेटिक `toolcall_delta` उत्सर्जित करता है\n- इस पथ में Google के लिए partial JSON पार्सर की आवश्यकता नहीं है\n\n## आंशिक टूल-कॉल JSON संचय और पुनर्प्राप्ति\n\nAnthropic/OpenAI Responses के लिए साझा व्यवहार `parseStreamingJson()` (`packages/ai/src/utils/json-parse.ts`) का उपयोग करता है:\n\n1. `JSON.parse` का प्रयास\n2. अधूरे खंडों के लिए `partial-json` पार्सर पर फ़ॉलबैक\n3. यदि दोनों विफल हों, तो `{}` लौटाएं\n\nनिहितार्थ:\n\n- खराब या काटे गए आर्गुमेंट डेल्टा स्ट्रीम प्रोसेसिंग को तुरंत क्रैश नहीं करते\n- प्रगतिशील `arguments` अस्थायी रूप से `{}` हो सकते हैं\n- बाद के वैध डेल्टा संरचित आर्गुमेंट पुनर्प्राप्त कर सकते हैं क्योंकि प्रत्येक append पर पार्सिंग पुनः प्रयास होता है\n- अंतिम `toolcall_end` उत्सर्जन से पहले एक और पार्स प्रयास करता है\n\n## Stop Reasons बनाम ट्रांसपोर्ट/रनटाइम त्रुटियां\n\nप्रोवाइडर stop reasons को सामान्यीकृत `stopReason` पर मैप किया जाता है:\n\n- Anthropic: `end_turn`→`stop`, `max_tokens`→`length`, `tool_use`→`toolUse`, safety/refusal cases→`error`\n- OpenAI Responses: `completed`→`stop`, `incomplete`→`length`, `failed/cancelled`→`error`\n- Google: `STOP`→`stop`, `MAX_TOKENS`→`length`, safety/prohibited/malformed-function-call classes→`error`\n\nत्रुटि सेमेंटिक्स दो चरणों में विभाजित हैं:\n\n1. **मॉडल कम्पलीशन सेमेंटिक्स** (प्रोवाइडर द्वारा रिपोर्ट किया गया finish reason/status)\n2. **ट्रांसपोर्ट/रनटाइम विफलता** (नेटवर्क/क्लाइंट/पार्सर/abort अपवाद)\n\nयदि प्रोवाइडर स्ट्रीम थ्रो करता है या विफलता का संकेत देता है, तो प्रत्येक प्रोवाइडर रैपर पकड़ता है और टर्मिनल `error` इवेंट उत्सर्जित करता है:\n\n- `stopReason = \"aborted\"` जब abort सिग्नल सेट हो\n- अन्यथा `stopReason = \"error\"`\n- `errorMessage = formatErrorMessageWithRetryAfter(error)`\n\n## खराब चंक / SSE पार्स विफलता व्यवहार\n\nइन प्रोवाइडर पथों के लिए, चंक/SSE फ्रेमिंग वेंडर SDK स्ट्रीम (Anthropic SDK, OpenAI SDK, Google SDK) द्वारा संभाली जाती है। यह कोड यहाँ कस्टम SSE डिकोडर कार्यान्वित नहीं करता।\n\nवर्तमान कार्यान्वयन में अवलोकित व्यवहार:\n\n- SDK स्तर पर खराब चंक/SSE पार्सिंग एक अपवाद या स्ट्रीम `error` इवेंट के रूप में सामने आती है\n- प्रोवाइडर रैपर इसे एकीकृत टर्मिनल `error` इवेंट में बदलता है\n- स्ट्रीम फ़ंक्शन के अंदर कोई प्रोवाइडर-विशिष्ट resume/retry नहीं\n- उच्च-स्तरीय retries `AgentSession` auto-retry लॉजिक में संभाले जाते हैं (संदेश-स्तर retry, स्ट्रीम-चंक रिप्ले नहीं)\n\n## रद्दीकरण सीमाएं\n\nरद्दीकरण स्तरबद्ध है:\n\n- AI प्रोवाइडर अनुरोध: `options.signal` को प्रोवाइडर क्लाइंट स्ट्रीम कॉल में पास किया जाता है।\n- प्रोवाइडर रैपर: स्ट्रीम लूप के बाद, aborted सिग्नल error पथ (`\"Request was aborted\"`) को बाध्य करता है।\n- एजेंट लूप: प्रत्येक प्रोवाइडर इवेंट को संभालने से पहले `signal.aborted` जाँचता है और नवीनतम partial से aborted असिस्टेंट संदेश संश्लेषित कर सकता है।\n- Session/agent नियंत्रण: `AgentSession.abort()` -> `agent.abort()` -> साझा abort controller रद्दीकरण।\n\nटूल execution रद्दीकरण मॉडल स्ट्रीम रद्दीकरण से अलग है:\n\n- टूल रनर `AbortSignal.any([agentSignal, steeringAbortSignal])` का उपयोग करते हैं\n- steering interrupts पहले से उत्पादित टूल परिणामों को संरक्षित करते हुए शेष टूल execution को abort कर सकते हैं\n\n## बैकप्रेशर सीमाएं\n\nप्रोवाइडर SDK स्ट्रीम और डाउनस्ट्रीम उपभोक्ताओं के बीच कोई कठोर बैकप्रेशर तंत्र नहीं है:\n\n- `EventStream` बिना अधिकतम आकार के इन-मेमोरी कतारों का उपयोग करता है\n- थ्रॉटलिंग UI अपडेट दर कम करता है लेकिन प्रोवाइडर इनटेक को धीमा नहीं करता\n- यदि उपभोक्ता काफी पीछे हों, तो कतारबद्ध इवेंट्स पूर्णता तक बढ़ सकते हैं\n\nवर्तमान डिज़ाइन bounded-buffer flow control पर जवाबदेही और सरल क्रमण को प्राथमिकता देता है।\n\n## स्ट्रीम इवेंट्स एजेंट/सत्र इवेंट्स के रूप में कैसे सामने आते हैं\n\n`agentLoop.streamAssistantResponse()` `AssistantMessageEvent` को `AgentEvent` से जोड़ता है:\n\n- `start` पर: placeholder असिस्टेंट संदेश पुश करता है और `message_start` उत्सर्जित करता है\n- ब्लॉक इवेंट्स पर (`text_*`, `thinking_*`, `toolcall_*`): अंतिम असिस्टेंट संदेश अपडेट करता है, कच्चे `assistantMessageEvent` के साथ `message_update` उत्सर्जित करता है\n- टर्मिनल पर (`done`/`error`): `response.result()` से अंतिम संदेश हल करता है, `message_end` उत्सर्जित करता है\n\n`AgentSession` फिर सत्र-स्तरीय व्यवहारों के लिए उन इवेंट्स का उपभोग करता है:\n\n- TTSR `text_delta` और `toolcall_delta` के लिए `message_update.assistantMessageEvent` देखता है\n- streaming edit guard `edit` कॉल पर `toolcall_delta`/`toolcall_end` की जाँच करता है और जल्दी abort कर सकता है\n- persistence `message_end` पर अंतिम संदेश लिखता है\n- auto-retry असिस्टेंट `stopReason === \"error\"` और `errorMessage` heuristics की जाँच करता है\n\n## एकीकृत बनाम प्रोवाइडर-विशिष्ट जिम्मेदारियां\n\nएकीकृत (सामान्य अनुबंध):\n\n- इवेंट आकार (`AssistantMessageEvent`)\n- अंतिम परिणाम निष्कर्षण (`done`/`error`)\n- डेल्टा थ्रॉटलिंग + विलय नियम\n- एजेंट/सत्र इवेंट प्रसार मॉडल\n\nप्रोवाइडर-विशिष्ट (पूरी तरह अमूर्त नहीं):\n\n- upstream इवेंट वर्गीकरण और मैपिंग लॉजिक\n- stop-reason अनुवाद तालिकाएं\n- टूल-कॉल ID परंपराएं\n- reasoning/thinking ब्लॉक सेमेंटिक्स और signatures\n- usage टोकन सेमेंटिक्स और उपलब्धता समय\n- प्रति API संदेश रूपांतरण बाधाएं\n\n## कार्यान्वयन फ़ाइलें\n\n- [`../../ai/src/stream.ts`](../../packages/ai/src/stream.ts) — प्रोवाइडर dispatch, option mapping, API key/session plumbing।\n- [`../../ai/src/utils/event-stream.ts`](../../packages/ai/src/utils/event-stream.ts) — सामान्य स्ट्रीम कतार + असिस्टेंट डेल्टा थ्रॉटलिंग।\n- [`../../ai/src/utils/json-parse.ts`](../../packages/ai/src/utils/json-parse.ts) — स्ट्रीम किए गए टूल आर्गुमेंट के लिए partial JSON पार्सिंग।\n- [`../../ai/src/providers/anthropic.ts`](../../packages/ai/src/providers/anthropic.ts) — Anthropic इवेंट अनुवाद और टूल JSON डेल्टा संचय।\n- [`../../ai/src/providers/openai-responses.ts`](../../packages/ai/src/providers/openai-responses.ts) — OpenAI Responses इवेंट अनुवाद और status mapping।\n- [`../../ai/src/providers/google.ts`](../../packages/ai/src/providers/google.ts) — Gemini स्ट्रीम chunk-to-block अनुवाद।\n- [`../../ai/src/providers/google-shared.ts`](../../packages/ai/src/providers/google-shared.ts) — Gemini finish-reason mapping और साझा रूपांतरण नियम।\n- [`../../agent/src/agent-loop.ts`](../../packages/agent/src/agent-loop.ts) — प्रोवाइडर स्ट्रीम उपभोग और `message_update` ब्रिजिंग।\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — स्ट्रीमिंग अपडेट, abort, retry और persistence का सत्र-स्तरीय प्रबंधन।\n",
	"hi/providers/python-repl.md": "---\ntitle: Python उपकरण और IPython रनटाइम\ndescription: >-\n  IPython कर्नेल प्रबंधन, निष्पादन और आउटपुट कैप्चर के साथ Python REPL उपकरण\n  रनटाइम।\nsidebar:\n  order: 3\n  label: Python और IPython\ni18n:\n  sourceHash: 70f0a034ecef\n  translator: machine\n---\n\n# Python उपकरण और IPython रनटाइम\n\nयह दस्तावेज़ `packages/coding-agent` में वर्तमान Python निष्पादन स्टैक का वर्णन करता है।\nइसमें उपकरण व्यवहार, कर्नेल/गेटवे लाइफ़साइकिल, एनवायरनमेंट हैंडलिंग, निष्पादन सेमेंटिक्स, आउटपुट रेंडरिंग और परिचालन विफलता मोड शामिल हैं।\n\n## दायरा और मुख्य फ़ाइलें\n\n- उपकरण सतह: `src/tools/python.ts`\n- सेशन/प्रति-कॉल कर्नेल ऑर्केस्ट्रेशन: `src/ipy/executor.ts`\n- कर्नेल प्रोटोकॉल + गेटवे इंटीग्रेशन: `src/ipy/kernel.ts`\n- साझा लोकल गेटवे समन्वयक: `src/ipy/gateway-coordinator.ts`\n- उपयोगकर्ता-ट्रिगर Python रन के लिए इंटरएक्टिव-मोड रेंडरर: `src/modes/components/python-execution.ts`\n- रनटाइम/एनव फ़िल्टरिंग और Python रेज़ोल्यूशन: `src/ipy/runtime.ts`\n\n## Python उपकरण क्या है\n\n`python` उपकरण एक या अधिक Python सेल को Jupyter Kernel Gateway-समर्थित कर्नेल के माध्यम से निष्पादित करता है (प्रति सेल सीधे `python -c` स्पॉन करके नहीं)।\n\nउपकरण पैरामीटर:\n\n```ts\n{\n  cells: Array<{ code: string; title?: string }>;\n  timeout?: number; // सेकंड, 1..600 तक क्लैंप, डिफ़ॉल्ट 30\n  cwd?: string;\n  reset?: boolean; // केवल पहले सेल से पहले कर्नेल रीसेट करें\n}\n```\n\nउपकरण एक सेशन के लिए `concurrency = \"exclusive\"` है, इसलिए कॉल ओवरलैप नहीं होते।\n\n## गेटवे लाइफ़साइकिल\n\n### मोड\n\nदो गेटवे पथ हैं:\n\n1. **बाहरी गेटवे** (`PI_PYTHON_GATEWAY_URL` सेट होने पर)\n   - कॉन्फ़िगर किए गए URL का सीधे उपयोग करता है।\n   - `PI_PYTHON_GATEWAY_TOKEN` के साथ वैकल्पिक ऑथ।\n   - कोई लोकल गेटवे प्रक्रिया स्पॉन या प्रबंधित नहीं की जाती।\n\n2. **लोकल साझा गेटवे** (डिफ़ॉल्ट पथ)\n   - `~/.xcsh/agent/python-gateway` के अंतर्गत समन्वित एकल साझा प्रक्रिया का उपयोग करता है।\n   - मेटाडेटा फ़ाइल: `gateway.json`\n   - लॉक फ़ाइल: `gateway.lock`\n   - स्पॉन कमांड:\n     - `python -m kernel_gateway`\n     - `127.0.0.1:<allocated-port>` से बाइंड\n     - स्टार्टअप हेल्थ चेक: `GET /api/kernelspecs`\n\n### लोकल साझा गेटवे समन्वय\n\n`acquireSharedGateway()`:\n\n- हार्टबीट के साथ फ़ाइल लॉक (`gateway.lock`) लेता है।\n- यदि PID जीवित है और हेल्थ चेक पास होता है तो `gateway.json` पुनः उपयोग करता है।\n- आवश्यकता पड़ने पर पुरानी जानकारी/PID साफ़ करता है।\n- जब कोई स्वस्थ गेटवे मौजूद नहीं होता तो नया गेटवे शुरू करता है।\n\n`releaseSharedGateway()` वर्तमान में एक no-op है (कर्नेल शटडाउन साझा गेटवे को बंद नहीं करता)।\n\n`shutdownSharedGateway()` साझा प्रक्रिया को स्पष्ट रूप से समाप्त करता है और गेटवे मेटाडेटा साफ़ करता है।\n\n### महत्वपूर्ण बाधा\n\n`python.sharedGateway=false` को कर्नेल स्टार्ट पर अस्वीकार किया जाता है:\n\n- त्रुटि: `Shared Python gateway required; local gateways are disabled`\n- प्रति-प्रक्रिया गैर-साझा लोकल गेटवे मोड उपलब्ध नहीं है।\n\n## कर्नेल लाइफ़साइकिल\n\nप्रत्येक निष्पादन चयनित गेटवे पर `POST /api/kernels` के माध्यम से बनाए गए कर्नेल का उपयोग करता है।\n\nकर्नेल स्टार्टअप अनुक्रम:\n\n1. उपलब्धता जाँच (`checkPythonKernelAvailability`)\n2. कर्नेल बनाएँ (`/api/kernels`)\n3. वेबसॉकेट खोलें (`/api/kernels/:id/channels`)\n4. कर्नेल एनव आरंभ करें (`cwd`, एनव वेरिएबल, `sys.path`)\n5. `PYTHON_PRELUDE` निष्पादित करें\n6. एक्सटेंशन मॉड्यूल लोड करें:\n   - उपयोगकर्ता: `~/.xcsh/agent/modules/*.py`\n   - प्रोजेक्ट: `<cwd>/.xcsh/modules/*.py` (समान-नाम उपयोगकर्ता मॉड्यूल को ओवरराइड करता है)\n\nकर्नेल शटडाउन:\n\n- `DELETE /api/kernels/:id` के माध्यम से रिमोट कर्नेल हटाता है\n- वेबसॉकेट बंद करता है\n- साझा गेटवे रिलीज़ हुक कॉल करता है (आज no-op)\n\n## सेशन पर्सिस्टेंस सेमेंटिक्स\n\n`python.kernelMode` कर्नेल पुनः उपयोग को नियंत्रित करता है:\n\n- `session` (डिफ़ॉल्ट)\n  - सेशन आइडेंटिटी + cwd द्वारा कुंजीबद्ध कर्नेल सेशन पुनः उपयोग करता है।\n  - निष्पादन एक क्यू के माध्यम से प्रति सेशन क्रमबद्ध है।\n  - निष्क्रिय सेशन 5 मिनट बाद बाहर कर दिए जाते हैं।\n  - अधिकतम 4 सेशन; ओवरफ़्लो पर सबसे पुराना बाहर किया जाता है।\n  - हार्टबीट चेक मृत कर्नेल का पता लगाते हैं।\n  - एक बार ऑटो-रीस्टार्ट की अनुमति; बार-बार क्रैश => कठोर विफलता।\n\n- `per-call`\n  - प्रत्येक निष्पादन अनुरोध के लिए एक नया कर्नेल बनाता है।\n  - अनुरोध के बाद कर्नेल बंद करता है।\n  - क्रॉस-कॉल स्टेट पर्सिस्टेंस नहीं।\n\n### एकल उपकरण कॉल में मल्टी-सेल व्यवहार\n\nउस उपकरण कॉल के लिए सेल उसी कर्नेल इंस्टेंस में क्रमिक रूप से चलते हैं।\n\nयदि कोई मध्यवर्ती सेल विफल होता है:\n\n- पहले के सेल की स्थिति मेमोरी में बनी रहती है।\n- उपकरण एक लक्षित त्रुटि लौटाता है जो बताती है कि कौन सा सेल विफल हुआ।\n- बाद के सेल निष्पादित नहीं होते।\n\n`reset=true` केवल उस कॉल में पहले सेल निष्पादन पर लागू होता है।\n\n## एनवायरनमेंट फ़िल्टरिंग और रनटाइम रेज़ोल्यूशन\n\nगेटवे/कर्नेल रनटाइम लॉन्च करने से पहले एनवायरनमेंट फ़िल्टर किया जाता है:\n\n- अनुमति सूची में `PATH`, `HOME`, लोकेल वेरिएबल, `VIRTUAL_ENV`, `PYTHONPATH` आदि जैसे मुख्य वेरिएबल शामिल हैं।\n- अनुमति-उपसर्ग: `LC_`, `XDG_`, `PI_`\n- अस्वीकृति सूची सामान्य API कुंजियाँ हटाती है (OpenAI/Anthropic/Gemini/आदि)\n\nरनटाइम चयन क्रम:\n\n1. सक्रिय/स्थित venv (`VIRTUAL_ENV`, फिर `<cwd>/.venv`, `<cwd>/venv`)\n2. `~/.xcsh/python-env` पर प्रबंधित venv\n3. PATH पर `python` या `python3`\n\nजब कोई venv चुना जाता है, तो उसका bin/Scripts पथ `PATH` में आगे जोड़ा जाता है।\n\nPython के अंदर कर्नेल एनव आरंभीकरण भी:\n\n- `os.chdir(cwd)`\n- प्रदान किए गए एनव मैप को `os.environ` में इंजेक्ट करता है\n- सुनिश्चित करता है कि cwd `sys.path` में है\n\n## उपकरण उपलब्धता और मोड चयन\n\n`python.toolMode` (डिफ़ॉल्ट `both`) + वैकल्पिक `PI_PY` ओवरराइड एक्सपोज़र को नियंत्रित करता है:\n\n- `ipy-only`\n- `bash-only`\n- `both`\n\n`PI_PY` स्वीकृत मान:\n\n- `0` / `bash` -> `bash-only`\n- `1` / `py` -> `ipy-only`\n- `mix` / `both` -> `both`\n\nयदि Python प्रीफ़्लाइट विफल होता है, तो उस सेशन के लिए उपकरण निर्माण bash-only तक डिग्रेड हो जाता है।\n\n## निष्पादन प्रवाह और रद्दीकरण/टाइमआउट\n\n### उपकरण-स्तर टाइमआउट\n\n`python` उपकरण टाइमआउट सेकंड में है, डिफ़ॉल्ट 30, `1..600` तक क्लैंप।\n\nउपकरण इन्हें मिलाता है:\n\n- कॉलर अबॉर्ट सिग्नल\n- टाइमआउट अबॉर्ट सिग्नल\n\n`AbortSignal.any(...)` के साथ।\n\n### कर्नेल निष्पादन रद्दीकरण\n\nअबॉर्ट/टाइमआउट पर:\n\n- निष्पादन रद्द के रूप में चिह्नित किया जाता है।\n- REST (`POST /interrupt`) और कंट्रोल-चैनल `interrupt_request` के माध्यम से कर्नेल इंटरप्ट का प्रयास किया जाता है।\n- परिणाम में `cancelled=true` शामिल है।\n- टाइमआउट पथ आउटपुट को `Command timed out after <n> seconds` के रूप में एनोटेट करता है।\n\n### stdin व्यवहार\n\nइंटरएक्टिव stdin समर्थित नहीं है।\n\nयदि कर्नेल `input_request` उत्सर्जित करता है:\n\n- उपकरण `stdinRequested=true` रिकॉर्ड करता है\n- व्याख्यात्मक टेक्स्ट उत्सर्जित करता है\n- खाली `input_reply` भेजता है\n- निष्पादन को एग्ज़ीक्यूटर परत पर विफलता के रूप में माना जाता है\n\n## आउटपुट कैप्चर और रेंडरिंग\n\n### कैप्चर किए गए आउटपुट वर्ग\n\nकर्नेल संदेशों से:\n\n- `stream` -> सादा टेक्स्ट खंड\n- `display_data`/`execute_result` -> रिच डिस्प्ले हैंडलिंग\n- `error` -> ट्रेसबैक टेक्स्ट\n- कस्टम MIME `application/x-xcsh-status` -> संरचित स्टेटस इवेंट\n\nडिस्प्ले MIME प्राथमिकता:\n\n1. `text/markdown`\n2. `text/plain`\n3. `text/html` (बेसिक मार्कडाउन में परिवर्तित)\n\nइसके अतिरिक्त संरचित आउटपुट के रूप में कैप्चर किया गया:\n\n- `application/json` -> JSON ट्री डेटा\n- `image/png` -> छवि पेलोड\n- `application/x-xcsh-status` -> स्टेटस इवेंट\n\n### स्टोरेज और ट्रंकेशन\n\nआउटपुट `OutputSink` के माध्यम से स्ट्रीम किया जाता है और आर्टिफ़ैक्ट स्टोरेज में सहेजा जा सकता है।\n\nउपकरण परिणामों में ट्रंकेशन मेटाडेटा और पूर्ण आउटपुट पुनर्प्राप्ति के लिए `artifact://<id>` शामिल हो सकता है।\n\n### रेंडरर व्यवहार\n\n- उपकरण रेंडरर (`python.ts`):\n  - प्रति-सेल स्टेटस के साथ कोड-सेल ब्लॉक दिखाता है\n  - संक्षिप्त पूर्वावलोकन डिफ़ॉल्ट 10 पंक्तियाँ\n  - पूर्ण आउटपुट और समृद्ध स्टेटस विवरण के लिए विस्तारित मोड का समर्थन करता है\n- इंटरएक्टिव रेंडरर (`python-execution.ts`):\n  - TUI में उपयोगकर्ता-ट्रिगर Python निष्पादन के लिए उपयोग किया जाता है\n  - संक्षिप्त पूर्वावलोकन डिफ़ॉल्ट 20 पंक्तियाँ\n  - डिस्प्ले सुरक्षा के लिए बहुत लंबी व्यक्तिगत पंक्तियों को 4000 वर्णों तक क्लैंप करता है\n  - रद्दीकरण/त्रुटि/ट्रंकेशन नोटिस दिखाता है\n\n## बाहरी गेटवे समर्थन\n\nसेट करें:\n\n```bash\nexport PI_PYTHON_GATEWAY_URL=\"http://127.0.0.1:8888\"\n# वैकल्पिक:\nexport PI_PYTHON_GATEWAY_TOKEN=\"...\"\n```\n\nलोकल साझा गेटवे से व्यवहार अंतर:\n\n- कोई लोकल गेटवे लॉक/जानकारी फ़ाइलें नहीं\n- कोई लोकल प्रक्रिया स्पॉन/समाप्ति नहीं\n- हेल्थ चेक और कर्नेल CRUD बाहरी एंडपॉइंट के विरुद्ध चलते हैं\n- ऑथ विफलताएँ स्पष्ट टोकन मार्गदर्शन के साथ प्रदर्शित की जाती हैं\n\n## परिचालन समस्या निवारण (वर्तमान विफलता मोड)\n\n- **Python उपकरण उपलब्ध नहीं**\n  - `python.toolMode` / `PI_PY` जाँचें।\n  - यदि प्रीफ़्लाइट विफल होता है, तो रनटाइम bash-only पर फ़ॉलबैक करता है।\n\n- **कर्नेल उपलब्धता त्रुटियाँ**\n  - लोकल मोड के लिए रेज़ॉल्व किए गए Python रनटाइम में `kernel_gateway` और `ipykernel` दोनों इम्पोर्ट योग्य होने चाहिए।\n  - इंस्टॉल करें:\n\n    ```bash\n    python -m pip install jupyter_kernel_gateway ipykernel\n    ```\n\n- **`python.sharedGateway=false` से स्टार्टअप विफलता**\n  - यह वर्तमान कार्यान्वयन के साथ अपेक्षित है।\n\n- **बाहरी गेटवे ऑथ/पहुँच विफलताएँ**\n  - 401/403 -> `PI_PYTHON_GATEWAY_TOKEN` सेट करें।\n  - टाइमआउट/अनुपलब्ध -> URL/नेटवर्क और गेटवे हेल्थ सत्यापित करें।\n\n- **निष्पादन रुकता है फिर टाइमआउट होता है**\n  - यदि वर्कलोड वैध है तो उपकरण `timeout` बढ़ाएँ (अधिकतम 600s)।\n  - अटके कोड के लिए, रद्दीकरण कर्नेल इंटरप्ट ट्रिगर करता है लेकिन उपयोगकर्ता कोड को अभी भी रिफ़ैक्टर करने की आवश्यकता हो सकती है।\n\n- **Python कोड में stdin/input प्रॉम्प्ट**\n  - `input()` इस रनटाइम पथ में इंटरएक्टिव रूप से समर्थित नहीं है; डेटा प्रोग्रामेटिक रूप से पास करें।\n\n- **संसाधन समाप्ति (`EMFILE` / बहुत अधिक खुली फ़ाइलें)**\n  - सेशन मैनेजर साझा-गेटवे पुनर्प्राप्ति ट्रिगर करता है (सेशन टियरडाउन + साझा गेटवे रीस्टार्ट)।\n\n- **कार्यशील निर्देशिका त्रुटियाँ**\n  - उपकरण निष्पादन से पहले सत्यापित करता है कि `cwd` मौजूद है और एक निर्देशिका है।\n\n## प्रासंगिक एनवायरनमेंट वेरिएबल\n\n- `PI_PY` — उपकरण एक्सपोज़र ओवरराइड (ऊपर दिया गया `bash-only`/`ipy-only`/`both` मैपिंग)\n- `PI_PYTHON_GATEWAY_URL` — बाहरी गेटवे का उपयोग करें\n- `PI_PYTHON_GATEWAY_TOKEN` — वैकल्पिक बाहरी गेटवे ऑथ टोकन\n- `PI_PYTHON_SKIP_CHECK=1` — Python प्रीफ़्लाइट/वार्म चेक बाईपास करें\n- `PI_PYTHON_IPC_TRACE=1` — कर्नेल IPC भेजने/प्राप्त करने के ट्रेस लॉग करें\n- `PI_DEBUG_STARTUP=1` — स्टार्टअप-स्टेज डीबग मार्कर उत्सर्जित करें\n",
	"hi/runtime-tools/bash-tool-runtime.md": "---\ntitle: Bash टूल रनटाइम\ndescription: >-\n  शेल प्रोसेस प्रबंधन, सैंडबॉक्सिंग, टाइमआउट और आउटपुट स्ट्रीमिंग के साथ Bash\n  टूल रनटाइम।\nsidebar:\n  order: 1\n  label: Bash टूल\ni18n:\n  sourceHash: 18b12aa5dbd5\n  translator: machine\n---\n\n# Bash टूल रनटाइम\n\nयह दस्तावेज़ एजेंट टूल कॉल द्वारा उपयोग किए जाने वाले **`bash` टूल** रनटाइम पाथ का वर्णन करता है — कमांड नॉर्मलाइज़ेशन से लेकर निष्पादन, ट्रंकेशन/आर्टिफैक्ट और रेंडरिंग तक।\n\nयह यह भी बताता है कि इंटरेक्टिव TUI, प्रिंट मोड, RPC मोड और यूज़र-इनिशिएटेड बैंग (`!`) शेल निष्पादन में व्यवहार कहाँ भिन्न होता है।\n\n## स्कोप और रनटाइम सर्फेस\n\ncoding-agent में दो अलग-अलग bash निष्पादन सर्फेस हैं:\n\n1. **टूल-कॉल सर्फेस** (`toolName: \"bash\"`): जब मॉडल bash टूल को कॉल करता है तब उपयोग किया जाता है।\n   - एंट्री पॉइंट: `BashTool.execute()`।\n2. **यूज़र बैंग-कमांड सर्फेस** (इंटरेक्टिव इनपुट या RPC `bash` कमांड से `!cmd`): सेशन-लेवल हेल्पर पाथ।\n   - एंट्री पॉइंट: `AgentSession.executeBash()`।\n\nदोनों अंततः नॉन-PTY निष्पादन के लिए `src/exec/bash-executor.ts` में `executeBash()` का उपयोग करते हैं, लेकिन केवल टूल-कॉल पाथ नॉर्मलाइज़ेशन/इंटरसेप्शन और टूल रेंडरर लॉजिक चलाता है।\n\n## एंड-टू-एंड टूल-कॉल पाइपलाइन\n\n## 1) इनपुट नॉर्मलाइज़ेशन और पैरामीटर मर्ज\n\n`BashTool.execute()` पहले `normalizeBashCommand()` के माध्यम से रॉ कमांड को नॉर्मलाइज़ करता है:\n\n- अनुगामी `| head -n N`, `| head -N`, `| tail -n N`, `| tail -N` को स्ट्रक्चर्ड लिमिट में निकालता है,\n- अनुगामी/अग्रणी व्हाइटस्पेस को ट्रिम करता है,\n- आंतरिक व्हाइटस्पेस को बरकरार रखता है।\n\nफिर यह निकाली गई लिमिट को स्पष्ट टूल आर्ग्स के साथ मर्ज करता है:\n\n- स्पष्ट `head`/`tail` आर्ग्स निकाले गए मानों को ओवरराइड करते हैं,\n- निकाले गए मान केवल फ़ॉलबैक हैं।\n\n### चेतावनी\n\n`bash-normalize.ts` टिप्पणियों में `2>&1` हटाने का उल्लेख है, लेकिन वर्तमान कार्यान्वयन इसे नहीं हटाता। रनटाइम व्यवहार अभी भी सही है (stdout/stderr पहले से मर्ज हैं), लेकिन नॉर्मलाइज़ेशन व्यवहार टिप्पणियों की तुलना में संकीर्ण है।\n\n## 2) वैकल्पिक इंटरसेप्शन (ब्लॉक्ड-कमांड पाथ)\n\nयदि `bashInterceptor.enabled` true है, तो `BashTool` सेटिंग्स से नियम लोड करता है और नॉर्मलाइज़ किए गए कमांड के विरुद्ध `checkBashInterception()` चलाता है।\n\nइंटरसेप्शन व्यवहार:\n\n- कमांड **केवल तभी** ब्लॉक होती है जब:\n  - regex नियम मेल खाता है, और\n  - सुझाया गया टूल `ctx.toolNames` में मौजूद है।\n- अमान्य regex नियम चुपचाप छोड़ दिए जाते हैं।\n- ब्लॉक होने पर, `BashTool` संदेश के साथ `ToolError` थ्रो करता है:\n  - `Blocked: ...`\n  - मूल कमांड शामिल।\n\nडिफ़ॉल्ट नियम पैटर्न (कोड में परिभाषित) सामान्य दुरुपयोगों को लक्षित करते हैं:\n\n- फ़ाइल रीडर (`cat`, `head`, `tail`, ...)\n- सर्च उपकरण (`grep`, `rg`, ...)\n- फ़ाइल फाइंडर (`find`, `fd`, ...)\n- इन-प्लेस एडिटर (`sed -i`, `perl -i`, `awk -i inplace`)\n- शेल रीडायरेक्शन राइट्स (`echo ... > file`, heredoc रीडायरेक्शन)\n\n### चेतावनी\n\n`InterceptionResult` में `suggestedTool` शामिल है, लेकिन `BashTool` वर्तमान में केवल संदेश टेक्स्ट दिखाता है (`details` में कोई स्ट्रक्चर्ड suggested-tool फ़ील्ड नहीं)।\n\n## 3) CWD वैलिडेशन और टाइमआउट क्लैंपिंग\n\n`cwd` को सेशन cwd (`resolveToCwd`) के सापेक्ष रिज़ॉल्व किया जाता है, फिर `stat` के माध्यम से वैलिडेट किया जाता है:\n\n- गायब पाथ -> `ToolError(\"Working directory does not exist: ...\")`\n- गैर-डायरेक्टरी -> `ToolError(\"Working directory is not a directory: ...\")`\n\nटाइमआउट को `[1, 3600]` सेकंड तक क्लैंप किया जाता है और मिलीसेकंड में कनवर्ट किया जाता है।\n\n## 4) आर्टिफैक्ट आवंटन\n\nनिष्पादन से पहले, टूल ट्रंकेटेड आउटपुट स्टोरेज के लिए एक आर्टिफैक्ट पाथ/आईडी (बेस्ट-एफर्ट) आवंटित करता है।\n\n- आर्टिफैक्ट आवंटन विफलता नॉन-फेटल है (निष्पादन आर्टिफैक्ट स्पिल फ़ाइल के बिना जारी रहता है),\n- ट्रंकेशन पर पूर्ण-आउटपुट परसिस्टेंस के लिए आर्टिफैक्ट id/path निष्पादन पाथ में पास किए जाते हैं।\n\n## 5) PTY बनाम नॉन-PTY निष्पादन चयन\n\n`BashTool` PTY निष्पादन तभी चुनता है जब सभी शर्तें सत्य हों:\n\n- `bash.virtualTerminal === \"on\"`\n- `PI_NO_PTY !== \"1\"`\n- टूल संदर्भ में UI है (`ctx.hasUI === true` और `ctx.ui` सेट)\n\nअन्यथा यह नॉन-इंटरेक्टिव `executeBash()` का उपयोग करता है।\n\nइसका मतलब है कि प्रिंट मोड और नॉन-UI RPC/टूल संदर्भ हमेशा नॉन-PTY का उपयोग करते हैं।\n\n## नॉन-इंटरेक्टिव निष्पादन इंजन (`executeBash`)\n\n## शेल सेशन पुनः उपयोग मॉडल\n\n`executeBash()` एक प्रोसेस-ग्लोबल मैप में नेटिव `Shell` इंस्टेंस को कैश करता है जो निम्न द्वारा की-ड होता है:\n\n- शेल पाथ,\n- कॉन्फ़िगर किया गया कमांड प्रीफ़िक्स,\n- स्नैपशॉट पाथ,\n- सीरियलाइज़ किया गया शेल env,\n- वैकल्पिक एजेंट सेशन की।\n\nसेशन-लेवल निष्पादनों के लिए, `AgentSession.executeBash()` `sessionKey: this.sessionId` पास करता है, जो प्रति सेशन पुनः उपयोग को अलग करता है।\n\nटूल-कॉल पाथ `sessionKey` **पास नहीं करता**, इसलिए पुनः उपयोग स्कोप शेल config/snapshot/env पर आधारित है।\n\n## शेल कॉन्फिग और स्नैपशॉट व्यवहार\n\nप्रत्येक कॉल पर, executor सेटिंग्स शेल config (`shell`, `env`, वैकल्पिक `prefix`) लोड करता है।\n\nयदि चुने गए शेल में `bash` शामिल है, तो यह `getOrCreateSnapshot()` का प्रयास करता है:\n\n- स्नैपशॉट यूज़र rc से aliases/functions/options कैप्चर करता है,\n- स्नैपशॉट निर्माण बेस्ट-एफर्ट है,\n- विफलता नो-स्नैपशॉट पर फ़ॉलबैक करती है।\n\nयदि `prefix` कॉन्फ़िगर किया गया है, तो कमांड बन जाता है:\n\n```text\n<prefix> <command>\n```\n\n## स्ट्रीमिंग और कैंसलेशन\n\n`Shell.run()` कॉलबैक पर चंक स्ट्रीम करता है। Executor प्रत्येक चंक को `OutputSink` और वैकल्पिक `onChunk` कॉलबैक में पाइप करता है।\n\nकैंसलेशन:\n\n- अबॉर्टेड सिग्नल `shellSession.abort(...)` ट्रिगर करता है,\n- नेटिव रिज़ल्ट से टाइमआउट को `cancelled: true` + एनोटेशन टेक्स्ट में मैप किया जाता है,\n- स्पष्ट कैंसलेशन भी `cancelled: true` + एनोटेशन रिटर्न करता है।\n\nटाइमआउट/कैंसल के लिए executor के अंदर कोई एक्सेप्शन नहीं थ्रो किया जाता; यह स्ट्रक्चर्ड `BashResult` रिटर्न करता है और कॉलर को एरर सेमेंटिक्स मैप करने देता है।\n\n## इंटरेक्टिव PTY पाथ (`runInteractiveBashPty`)\n\nजब PTY सक्षम होता है, टूल `runInteractiveBashPty()` चलाता है जो एक ओवरले कंसोल घटक खोलता है और एक नेटिव `PtySession` चलाता है।\n\nव्यवहार की मुख्य विशेषताएं:\n\n- xterm-headless वर्चुअल टर्मिनल ओवरले में व्यूपोर्ट रेंडर करता है,\n- कीबोर्ड इनपुट नॉर्मलाइज़ किया जाता है (Kitty सीक्वेंस और एप्लिकेशन कर्सर मोड हैंडलिंग सहित),\n- चलते समय `esc` PTY सेशन को किल करता है,\n- टर्मिनल रीसाइज़ PTY में प्रोपेगेट होता है (`session.resize(cols, rows)`)।\n\nअनअटेंडेड रन के लिए एनवायरनमेंट हार्डनिंग डिफ़ॉल्ट इंजेक्ट किए जाते हैं:\n\n- पेजर अक्षम (`PAGER=cat`, `GIT_PAGER=cat`, आदि),\n- एडिटर प्रॉम्प्ट अक्षम (`GIT_EDITOR=true`, `EDITOR=true`, ...),\n- टर्मिनल/ऑथ प्रॉम्प्ट कम (`GIT_TERMINAL_PROMPT=0`, `SSH_ASKPASS=/usr/bin/false`, `CI=1`),\n- नॉन-इंटरेक्टिव व्यवहार के लिए पैकेज-मैनेजर/टूल ऑटोमेशन फ्लैग।\n\nPTY आउटपुट नॉर्मलाइज़ किया जाता है (`CRLF`/`CR` से `LF`, `sanitizeText`) और `OutputSink` में लिखा जाता है, जिसमें आर्टिफैक्ट स्पिल सपोर्ट शामिल है।\n\nPTY स्टार्टअप/रनटाइम एरर पर, sink को `PTY error: ...` लाइन मिलती है और कमांड undefined exit code के साथ समाप्त होता है।\n\n## आउटपुट हैंडलिंग: स्ट्रीमिंग, ट्रंकेशन, आर्टिफैक्ट स्पिल\n\nPTY और नॉन-PTY दोनों पाथ `OutputSink` का उपयोग करते हैं।\n\n## OutputSink सेमेंटिक्स\n\n- इन-मेमोरी UTF-8-सेफ टेल बफ़र रखता है (`DEFAULT_MAX_BYTES`, वर्तमान में 50KB),\n- देखे गए कुल बाइट्स/लाइनें ट्रैक करता है,\n- यदि आर्टिफैक्ट पाथ मौजूद है और आउटपुट ओवरफ्लो होता है (या फ़ाइल पहले से सक्रिय है), तो पूरी स्ट्रीम को आर्टिफैक्ट फ़ाइल में लिखता है,\n- जब मेमोरी थ्रेशोल्ड ओवरफ्लो होता है, इन-मेमोरी बफ़र को टेल तक ट्रिम करता है (UTF-8 बाउंड्री सेफ),\n- ओवरफ्लो/फ़ाइल स्पिल होने पर `truncated` मार्क करता है।\n\n`dump()` रिटर्न करता है:\n\n- `output` (संभवतः एनोटेटेड प्रीफ़िक्स),\n- `truncated`,\n- `totalLines/totalBytes`,\n- `outputLines/outputBytes`,\n- `artifactId` यदि आर्टिफैक्ट फ़ाइल सक्रिय थी।\n\n### लॉन्ग-आउटपुट चेतावनी\n\n`OutputSink` में रनटाइम ट्रंकेशन बाइट-थ्रेशोल्ड आधारित है (50KB डिफ़ॉल्ट)। यह इस कोड पाथ में एक हार्ड 2000-लाइन कैप लागू नहीं करता।\n\n## लाइव टूल अपडेट\n\nनॉन-PTY निष्पादन के लिए, `BashTool` पार्शियल अपडेट के लिए एक अलग `TailBuffer` का उपयोग करता है और कमांड चलते समय `onUpdate` स्नैपशॉट एमिट करता है।\n\nPTY निष्पादन के लिए, लाइव रेंडरिंग कस्टम UI ओवरले द्वारा हैंडल की जाती है, न कि `onUpdate` टेक्स्ट चंक द्वारा।\n\n## रिज़ल्ट शेपिंग, मेटाडेटा और एरर मैपिंग\n\nनिष्पादन के बाद:\n\n1. `cancelled` हैंडलिंग:\n   - यदि अबॉर्ट सिग्नल अबॉर्टेड है -> `ToolAbortError` थ्रो करें (अबॉर्ट सेमेंटिक्स),\n   - अन्यथा -> `ToolError` थ्रो करें (टूल विफलता के रूप में माना जाता है)।\n2. PTY `timedOut` -> `ToolError` थ्रो करें।\n3. अंतिम आउटपुट टेक्स्ट पर head/tail फ़िल्टर लागू करें (`applyHeadTail`, head फिर tail)।\n4. खाली आउटपुट `(no output)` बन जाता है।\n5. `toolResult(...).truncationFromSummary(result, { direction: \"tail\" })` के माध्यम से ट्रंकेशन मेटाडेटा अटैच करें।\n6. exit-code मैपिंग:\n   - गायब exit code -> `ToolError(\"... missing exit status\")`\n   - नॉन-ज़ीरो exit -> `ToolError(\"... Command exited with code N\")`\n   - ज़ीरो exit -> सफलता रिज़ल्ट।\n\nसफलता पेलोड संरचना:\n\n- `content`: टेक्स्ट आउटपुट,\n- `details.meta.truncation` जब ट्रंकेटेड हो, जिसमें शामिल हैं:\n  - `direction`, `truncatedBy`, कुल/आउटपुट लाइन+बाइट काउंट,\n  - `shownRange`,\n  - `artifactId` जब उपलब्ध हो।\n\nक्योंकि बिल्ट-इन टूल `wrapToolWithMetaNotice()` से रैप होते हैं, ट्रंकेशन नोटिस टेक्स्ट स्वचालित रूप से अंतिम टेक्स्ट कंटेंट में जोड़ा जाता है (उदाहरण के लिए: `Full: artifact://<id>`)।\n\n## रेंडरिंग पाथ\n\n## टूल-कॉल रेंडरर (`bashToolRenderer`)\n\n`bashToolRenderer` टूल-कॉल मैसेज (`toolCall` / `toolResult`) के लिए उपयोग किया जाता है:\n\n- कोलैप्स्ड मोड विज़ुअल-लाइन-ट्रंकेटेड प्रीव्यू दिखाता है,\n- एक्सपैंडेड मोड वर्तमान में उपलब्ध सभी आउटपुट टेक्स्ट दिखाता है,\n- वार्निंग लाइन ट्रंकेशन कारण और ट्रंकेटेड होने पर `artifact://<id>` शामिल करती है,\n- टाइमआउट मान (आर्ग्स से) फुटर मेटाडेटा लाइन में दिखाया जाता है।\n\n### चेतावनी: पूर्ण आर्टिफैक्ट एक्सपैंशन\n\n`BashRenderContext` में `isFullOutput` है, लेकिन वर्तमान रेंडरर कॉन्टेक्स्ट बिल्डर bash टूल रिज़ल्ट के लिए इसे सेट नहीं करता। एक्सपैंडेड व्यू अभी भी रिज़ल्ट कंटेंट (tail/ट्रंकेटेड आउटपुट) में पहले से मौजूद टेक्स्ट का उपयोग करता है जब तक कि कोई अन्य कॉलर पूर्ण आर्टिफैक्ट कंटेंट प्रदान न करे।\n\n## यूज़र बैंग-कमांड घटक (`BashExecutionComponent`)\n\n`BashExecutionComponent` इंटरेक्टिव मोड में यूज़र `!` कमांड के लिए है (मॉडल टूल कॉल नहीं):\n\n- चंक को लाइव स्ट्रीम करता है,\n- कोलैप्स्ड प्रीव्यू अंतिम 20 लॉजिकल लाइनें रखता है,\n- प्रति लाइन 4000 कैरेक्टर पर लाइन क्लैंप,\n- मेटाडेटा मौजूद होने पर ट्रंकेशन + आर्टिफैक्ट वार्निंग दिखाता है,\n- कैंसल/एरर/exit स्टेट को अलग-अलग मार्क करता है।\n\nयह घटक `CommandController.handleBashCommand()` द्वारा वायर्ड है और `AgentSession.executeBash()` से फीड होता है।\n\n## मोड-विशिष्ट व्यवहार अंतर\n\n| सर्फेस | एंट्री पाथ | PTY योग्य | लाइव आउटपुट UX | एरर सर्फेसिंग |\n| ------------------------------ | ----------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------ |\n| इंटरेक्टिव टूल कॉल | `BashTool.execute` | हाँ, जब `bash.virtualTerminal=on` और UI मौजूद हो और `PI_NO_PTY!=1` | PTY ओवरले (इंटरेक्टिव) या स्ट्रीम्ड टेल अपडेट | टूल एरर `toolResult.isError` बन जाते हैं |\n| प्रिंट मोड टूल कॉल | `BashTool.execute` | नहीं (कोई UI संदर्भ नहीं) | कोई TUI ओवरले नहीं; आउटपुट इवेंट स्ट्रीम/अंतिम असिस्टेंट टेक्स्ट फ्लो में दिखता है | समान टूल एरर मैपिंग |\n| RPC टूल कॉल (एजेंट टूलिंग) | `BashTool.execute` | आमतौर पर कोई UI नहीं -> नॉन-PTY | स्ट्रक्चर्ड टूल इवेंट/रिज़ल्ट | समान टूल एरर मैपिंग |\n| इंटरेक्टिव बैंग कमांड (`!`) | `AgentSession.executeBash` + `BashExecutionComponent` | नहीं (executor सीधे उपयोग करता है) | समर्पित bash निष्पादन घटक | Controller एक्सेप्शन कैच करता है और UI एरर दिखाता है |\n| RPC `bash` कमांड | `rpc-mode` -> `session.executeBash` | नहीं | `BashResult` सीधे रिटर्न करता है | Consumer रिटर्न किए गए फ़ील्ड हैंडल करता है |\n\n## ऑपरेशनल चेतावनियाँ\n\n- Interceptor केवल तभी कमांड ब्लॉक करता है जब सुझाया गया टूल वर्तमान में संदर्भ में उपलब्ध हो।\n- यदि आर्टिफैक्ट आवंटन विफल होता है, ट्रंकेशन अभी भी होता है लेकिन कोई `artifact://` बैक-रेफ़रेंस उपलब्ध नहीं होता।\n- शेल सेशन कैश में इस मॉड्यूल में कोई स्पष्ट इवेक्शन नहीं है; लाइफटाइम प्रोसेस-स्कोप्ड है।\n- PTY और नॉन-PTY टाइमआउट सर्फेस अलग हैं:\n  - PTY स्पष्ट `timedOut` रिज़ल्ट फ़ील्ड एक्सपोज़ करता है,\n  - नॉन-PTY टाइमआउट को `cancelled + annotation` सारांश में मैप करता है।\n\n## कार्यान्वयन फ़ाइलें\n\n- [`src/tools/bash.ts`](../../packages/coding-agent/src/tools/bash.ts) — टूल एंट्रीपॉइंट, नॉर्मलाइज़ेशन/इंटरसेप्शन, PTY/नॉन-PTY चयन, रिज़ल्ट/एरर मैपिंग, bash टूल रेंडरर।\n- [`src/tools/bash-normalize.ts`](../../packages/coding-agent/src/tools/bash-normalize.ts) — कमांड नॉर्मलाइज़ेशन और पोस्ट-रन head/tail फ़िल्टरिंग।\n- [`src/tools/bash-interceptor.ts`](../../packages/coding-agent/src/tools/bash-interceptor.ts) — interceptor नियम मिलान और ब्लॉक्ड-कमांड मैसेज।\n- [`src/exec/bash-executor.ts`](../../packages/coding-agent/src/exec/bash-executor.ts) — नॉन-PTY executor, शेल सेशन पुनः उपयोग, कैंसलेशन वायरिंग, आउटपुट sink इंटीग्रेशन।\n- [`src/tools/bash-interactive.ts`](../../packages/coding-agent/src/tools/bash-interactive.ts) — PTY रनटाइम, ओवरले UI, इनपुट नॉर्मलाइज़ेशन, नॉन-इंटरेक्टिव env डिफ़ॉल्ट।\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — `OutputSink` ट्रंकेशन/आर्टिफैक्ट स्पिल और सारांश मेटाडेटा।\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — आर्टिफैक्ट आवंटन हेल्पर और स्ट्रीमिंग टेल बफ़र।\n- [`src/tools/output-meta.ts`](../../packages/coding-agent/src/tools/output-meta.ts) — ट्रंकेशन मेटाडेटा शेप + नोटिस इंजेक्शन रैपर।\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — सेशन-लेवल `executeBash`, मैसेज रिकॉर्डिंग, अबॉर्ट लाइफसाइकल।\n- [`src/modes/components/bash-execution.ts`](../../packages/coding-agent/src/modes/components/bash-execution.ts) — इंटरेक्टिव `!` कमांड निष्पादन घटक।\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts) — इंटरेक्टिव `!` कमांड UI स्ट्रीम/अपडेट कम्पलीशन के लिए वायरिंग।\n- [`src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts) — RPC `bash` और `abort_bash` कमांड सर्फेस।\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — `artifact://<id>` रिज़ॉल्यूशन।\n",
	"hi/runtime-tools/context-command.md": "---\ntitle: F5 XC संदर्भ\ndescription: >-\n  xcsh को F5 Distributed Cloud टेनेंट से कनेक्ट करें -- प्रमाणीकरण संदर्भ बनाएं,\n  स्विच करें और प्रबंधित करें।\nsidebar:\n  order: 1\n  label: F5 XC संदर्भ\ni18n:\n  sourceHash: a9cccbc338f0\n  translator: machine\n---\n\n# F5 XC संदर्भ\n\nxcsh **संदर्भों (contexts)** के माध्यम से F5 Distributed Cloud से जुड़ता है -- ये नामित क्रेडेंशियल सेट हैं जो एक टेनेंट URL, API टोकन और नेमस्पेस को बाइंड करते हैं। यदि आपने `kubectl config use-context` या `kubectx` का उपयोग किया है, तो कार्यप्रवाह समान है: एक संदर्भ बनाएं, नाम से उनके बीच स्विच करें, और वापस पलटने के लिए `-` का उपयोग करें।\n\n## शुरुआत करना\n\n### 1. अपना पहला संदर्भ बनाएं\n\nआपको अपने F5 XC कंसोल से तीन चीज़ों की आवश्यकता है: टेनेंट URL, एक API टोकन, और वैकल्पिक रूप से एक नेमस्पेस।\n\n```\n/context create production https://acme.console.ves.volterra.io p12k3-your-api-token\n```\n\n```\nContext 'production' created. Use /context activate production to switch to it.\n```\n\nया यदि आप चरण-दर-चरण प्रॉम्प्ट पसंद करते हैं तो गाइडेड विज़ार्ड का उपयोग करें:\n\n```\n/context wizard\n```\n\n### 2. इसे सक्रिय करें\n\n```\n/context production\n```\n\n```\n╭─ production ─────────────────────────────────────────────────╮\n│ XCSH_TENANT     acme                                         │\n│ XCSH_API_URL    https://acme.console.ves.volterra.io         │\n│ XCSH_API_TOKEN  ...oken                                      │\n│ Status          Connected (312ms)                            │\n├─ Environment ────────────────────────────────────────────────┤\n│ XCSH_NAMESPACE  default                                      │\n╰──────────────────────────────────────────────────────────────╯\n```\n\nसक्रिय होने पर, xcsh आपके सत्र में टेनेंट क्रेडेंशियल्स इंजेक्ट करता है। एजेंट अब F5 XC API कॉल कर सकता है, और स्टेटस लाइन सक्रिय संदर्भ दिखाती है।\n\n### 3. और संदर्भ जोड़ें और उनके बीच स्विच करें\n\n```\n/context create staging https://staging.console.ves.volterra.io p12k3-staging-token\n```\n\nनाम से स्विच करें -- किसी सबकमांड क्रिया की आवश्यकता नहीं:\n\n```\n/context staging\n```\n\nपिछले संदर्भ पर वापस जाएं (`cd -` शैली):\n\n```\n/context -\n```\n\n`/context -` को दो बार कॉल करने से आप वापस वहीं पहुंच जाते हैं जहां से आपने शुरू किया था।\n\n### 4. देखें आपके पास क्या है\n\n```\n/context\n```\n\n```\n  production           https://acme.console.ves.volterra.io\n* staging              https://staging.console.ves.volterra.io\n```\n\n`*` सक्रिय संदर्भ को चिह्नित करता है।\n\n## दैनिक कमांड\n\n| कमांड | यह क्या करता है |\n|---|---|\n| `/context` | सभी संदर्भों की सूची दिखाएं |\n| `/context <name>` | किसी संदर्भ पर स्विच करें |\n| `/context -` | पिछले संदर्भ पर स्विच करें |\n| `/context show` | सक्रिय संदर्भ का विवरण दिखाएं (टोकन छिपे हुए) |\n| `/context status` | वर्तमान प्रमाणीकरण स्थिति दिखाएं |\n\n## संदर्भ जीवनचक्र\n\n| कमांड | यह क्या करता है |\n|---|---|\n| `/context create <name> <url> <token> [namespace]` | एक संदर्भ बनाएं |\n| `/context delete <name> --confirm` | एक संदर्भ हटाएं (`--confirm` आवश्यक) |\n| `/context rename <old> <new>` | एक संदर्भ का नाम बदलें |\n| `/context validate <name>` | स्विच किए बिना क्रेडेंशियल्स का परीक्षण करें |\n| `/context export [name] [--include-token]` | JSON के रूप में निर्यात करें (टोकन डिफ़ॉल्ट रूप से छिपे हुए) |\n| `/context import <path-or-json> [--overwrite]` | फ़ाइल या इनलाइन JSON से आयात करें |\n| `/context wizard` | गाइडेड इंटरैक्टिव सेटअप |\n\n## नेमस्पेस स्विच करना\n\nप्रत्येक संदर्भ में एक डिफ़ॉल्ट नेमस्पेस होता है। संदर्भ बदले बिना इसे स्विच करें:\n\n```\n/context namespace system\n```\n\nटैब कंप्लीशन सक्रिय टेनेंट से नेमस्पेस नाम प्रदान करता है।\n\n## संदर्भों पर एनवायरनमेंट वेरिएबल\n\nसंदर्भ अतिरिक्त एनवायरनमेंट वेरिएबल ले जा सकते हैं जो सक्रियण पर आपके सत्र में इंजेक्ट किए जाते हैं। प्रति-टेनेंट कॉन्फ़िगरेशन के लिए उपयोगी जो क्रेडेंशियल सेट का हिस्सा नहीं है।\n\n```\n/context set CUSTOM_HEADER=x-acme-trace\n/context set LOG_LEVEL=debug\n/context env list\n/context unset LOG_LEVEL\n```\n\nउपनाम: `add` = `set`, `remove`/`clear` = `unset`।\n\n## टैब कंप्लीशन\n\n`/context ` टाइप करें और Tab दबाएं। ड्रॉपडाउन दिखाता है:\n\n1. **संदर्भ नाम** -- टेनेंट URL संकेतों के साथ, ताकि आप टेनेंट्स को अलग-अलग पहचान सकें\n2. **`-`** -- तब दिखाई देता है जब आपने पहले स्विच किया हो, दिखाता है कि आप किस संदर्भ पर पलटेंगे\n3. **सबकमांड** -- `list`, `create`, `delete`, आदि।\n\nसंदर्भ नाम पहले दिखाई देते हैं क्योंकि स्विच करना सबसे आम क्रिया है।\n\nसबकमांड-स्तरीय कंप्लीशन भी काम करते हैं: `/context activate <Tab>` संदर्भ नाम पूर्ण करता है, `/context namespace <Tab>` नेमस्पेस पूर्ण करता है, `/context unset <Tab>` ज्ञात env var कुंजियों को पूर्ण करता है।\n\n## नामकरण नियम\n\nसंदर्भ नाम 1-64 अक्षरों के होने चाहिए: अक्षर, अंक, हाइफ़न, अंडरस्कोर।\n\nसबकमांड से टकराने वाले नाम अस्वीकार कर दिए जाते हैं:\n\n```\n/context create list https://example.com tok\n```\n\n```\nError: Context name 'list' conflicts with a /context subcommand. Choose a different name.\n```\n\nपूर्ण आरक्षित सेट: `list`, `show`, `status`, `create`, `delete`, `rename`, `namespace`, `env`, `set`, `unset`, `add`, `remove`, `clear`, `activate`, `validate`, `export`, `import`, `wizard`, `help`। तुलना केस-असंवेदनशील है।\n\n## एनवायरनमेंट वेरिएबल ओवरराइड\n\nयदि xcsh लॉन्च करने से पहले आपके शेल एनवायरनमेंट में `XCSH_API_URL` और `XCSH_API_TOKEN` सेट हैं, तो वे किसी भी संदर्भ पर प्राथमिकता लेते हैं। यह CI/CD पाइपलाइनों या एकबारगी सत्रों के लिए उपयोगी है जहां आप स्थायी संदर्भ बनाना नहीं चाहते।\n\nइस मोड में चलते समय, `/context` एनवायरनमेंट-स्रोत क्रेडेंशियल्स को `(via env vars)` लेबल के साथ दिखाता है।\n\n## पिछले संदर्भ का व्यवहार\n\n- **सत्र-स्कोप्ड**: जब आप xcsh पुनः आरंभ करते हैं तो पिछला संदर्भ रीसेट हो जाता है। यह डिस्क पर स्थायी नहीं होता।\n- **पिंग-पोंग**: `/context -` दो बार करने से आप वापस वहीं पहुंच जाते हैं जहां से शुरू किया था।\n- **म्यूटेशन के प्रति सुरक्षित**: यदि आप पिछले संदर्भ को हटाते हैं, तो पॉइंटर साफ़ हो जाता है। यदि आप इसका नाम बदलते हैं, तो पॉइंटर नए नाम का अनुसरण करता है।\n- **पुनः-सक्रियण एक नो-ऑप है**: जब आप पहले से `production` पर हैं तो `/context production` पिछले पॉइंटर को रीसेट नहीं करता।\n\n## डिज़ाइन सम्मेलन\n\n`/context` UX इनका अनुसरण करता है:\n\n- **kubectx**: स्विच करने के लिए `kubectx <name>`, पिछले के लिए `kubectx -`, सूचीबद्ध करने के लिए बिना तर्क `kubectx`\n- **kubectl**: स्पष्ट रूप के लिए `kubectl config use-context`\n- **शेल**: पिछली-निर्देशिका ट्रैकिंग के लिए `cd -` / `OLDPWD`\n",
	"hi/runtime-tools/custom-tools.md": "---\ntitle: कस्टम उपकरण\ndescription: >-\n  एजेंट को विस्तारित करने के लिए कस्टम टूल पंजीकरण, स्कीमा परिभाषा और निष्पादन\n  पाइपलाइन।\nsidebar:\n  order: 4\n  label: कस्टम उपकरण\ni18n:\n  sourceHash: 5f4a441fc2e2\n  translator: machine\n---\n\n# कस्टम उपकरण\n\nकस्टम उपकरण मॉडल-कॉल करने योग्य फ़ंक्शन हैं जो बिल्ट-इन उपकरणों की तरह उसी टूल निष्पादन पाइपलाइन में जुड़ते हैं।\n\nएक कस्टम टूल एक TypeScript/JavaScript मॉड्यूल है जो एक फ़ैक्टरी एक्सपोर्ट करता है। फ़ैक्टरी को एक होस्ट API (`CustomToolAPI`) प्राप्त होती है और वह एक टूल या टूल्स की एक सरणी लौटाती है।\n\n## यह क्या है (और क्या नहीं है)\n\n- **कस्टम टूल**: एक टर्न के दौरान मॉडल द्वारा कॉल करने योग्य (`execute` + TypeBox स्कीमा)।\n- **एक्सटेंशन**: लाइफसाइकिल/इवेंट फ्रेमवर्क जो उपकरण पंजीकृत कर सकता है और इवेंट को इंटरसेप्ट/संशोधित कर सकता है।\n- **हुक**: बाहरी प्री/पोस्ट कमांड स्क्रिप्ट।\n- **स्किल**: स्थिर मार्गदर्शन/संदर्भ पैकेज, निष्पादन योग्य टूल कोड नहीं।\n\nयदि आपको मॉडल को सीधे कोड कॉल करने की आवश्यकता है, तो कस्टम टूल का उपयोग करें।\n\n## वर्तमान कोड में एकीकरण पथ\n\nदो सक्रिय एकीकरण शैलियाँ हैं:\n\n1. **SDK-प्रदत्त कस्टम उपकरण** (`options.customTools`)\n   - `CustomToolAdapter` या एक्सटेंशन रैपर के माध्यम से एजेंट टूल्स में लपेटे जाते हैं।\n   - SDK बूटस्ट्रैप में हमेशा प्रारंभिक सक्रिय टूल सेट में शामिल।\n\n2. **लोडर API के माध्यम से फ़ाइलसिस्टम-खोजे गए मॉड्यूल** (`discoverAndLoadCustomTools` / `loadCustomTools`)\n   - `src/extensibility/custom-tools/loader.ts` में लाइब्रेरी API के रूप में उपलब्ध।\n   - होस्ट कोड config/provider/plugin पथों से टूल मॉड्यूल खोजने और लोड करने के लिए इन्हें कॉल कर सकता है।\n\n```text\nModel tool call flow\n\nLLM tool call\n   │\n   ▼\nTool registry (built-ins + custom tool adapters)\n   │\n   ▼\nCustomTool.execute(toolCallId, params, onUpdate, ctx, signal)\n   │\n   ├─ onUpdate(...)  -> streamed partial result\n   └─ return result  -> final tool content/details\n```\n\n## खोज स्थान (लोडर API)\n\n`discoverAndLoadCustomTools(configuredPaths, cwd, builtInToolNames)` निम्नलिखित को मर्ज करता है:\n\n1. क्षमता प्रदाता (`toolCapability`), जिनमें शामिल हैं:\n   - नेटिव OMP config (`~/.xcsh/agent/tools`, `.xcsh/tools`)\n   - Claude config (`~/.claude/tools`, `.claude/tools`)\n   - Codex config (`~/.codex/tools`, `.codex/tools`)\n   - Claude मार्केटप्लेस प्लगइन कैश प्रदाता\n2. इंस्टॉल किए गए प्लगइन मेनिफेस्ट (`~/.xcsh/plugins/node_modules/*` प्लगइन लोडर के माध्यम से)\n3. लोडर को पास किए गए स्पष्ट कॉन्फ़िगर किए गए पथ\n\n### महत्वपूर्ण व्यवहार\n\n- डुप्लीकेट हल किए गए पथ डीडुप्लीकेट किए जाते हैं।\n- टूल नाम संघर्षों को बिल्ट-इन और पहले से लोड किए गए कस्टम टूल्स के विरुद्ध अस्वीकार किया जाता है।\n- `.md` और `.json` फ़ाइलें कुछ प्रदाताओं द्वारा टूल मेटाडेटा के रूप में खोजी जाती हैं, लेकिन निष्पादन योग्य मॉड्यूल लोडर उन्हें चलाने योग्य टूल के रूप में अस्वीकार करता है।\n- सापेक्ष कॉन्फ़िगर किए गए पथ `cwd` से हल किए जाते हैं; `~` का विस्तार किया जाता है।\n\n## मॉड्यूल अनुबंध\n\nएक कस्टम टूल मॉड्यूल को एक फ़ंक्शन एक्सपोर्ट करना होगा (डिफ़ॉल्ट एक्सपोर्ट प्राथमिक):\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = (pi) => ({\n name: \"repo_stats\",\n label: \"Repo Stats\",\n description: \"Counts tracked TypeScript files\",\n parameters: pi.typebox.Type.Object({\n  glob: pi.typebox.Type.Optional(pi.typebox.Type.String({ default: \"**/*.ts\" })),\n }),\n\n async execute(toolCallId, params, onUpdate, ctx, signal) {\n  onUpdate?.({\n   content: [{ type: \"text\", text: \"Scanning files...\" }],\n   details: { phase: \"scan\" },\n  });\n\n  const result = await pi.exec(\"git\", [\"ls-files\", params.glob ?? \"**/*.ts\"], { signal, cwd: pi.cwd });\n  if (result.killed) {\n   throw new Error(\"Scan was cancelled\");\n  }\n  if (result.code !== 0) {\n   throw new Error(result.stderr || \"git ls-files failed\");\n  }\n\n  const files = result.stdout.split(\"\\n\").filter(Boolean);\n  return {\n   content: [{ type: \"text\", text: `Found ${files.length} files` }],\n   details: { count: files.length, sample: files.slice(0, 10) },\n  };\n },\n\n onSession(event) {\n  if (event.reason === \"shutdown\") {\n   // cleanup resources if needed\n  }\n },\n});\n\nexport default factory;\n```\n\nफ़ैक्टरी रिटर्न प्रकार:\n\n- `CustomTool`\n- `CustomTool[]`\n- `Promise<CustomTool | CustomTool[]>`\n\n## फ़ैक्टरियों को पास किया गया API सरफेस (`CustomToolAPI`)\n\n`types.ts` और `loader.ts` से:\n\n- `cwd`: होस्ट कार्यशील निर्देशिका\n- `exec(command, args, options?)`: प्रोसेस निष्पादन सहायक\n- `ui`: UI संदर्भ (हेडलेस मोड में no-op हो सकता है)\n- `hasUI`: गैर-इंटरैक्टिव प्रवाह में `false`\n- `logger`: साझा फ़ाइल लॉगर\n- `typebox`: इंजेक्टेड `@sinclair/typebox`\n- `pi`: इंजेक्टेड `@f5-sales-demo/xcsh` एक्सपोर्ट\n- `pushPendingAction(action)`: छुपे हुए `resolve` टूल के लिए प्रीव्यू एक्शन पंजीकृत करें (`docs/resolve-tool-runtime.md`)\n\nलोडर नो-ऑप UI संदर्भ के साथ शुरू होता है और होस्ट कोड को वास्तविक UI तैयार होने पर `setUIContext(...)` कॉल करने की आवश्यकता होती है।\n\n## निष्पादन अनुबंध और टाइपिंग\n\n`CustomTool.execute` सिग्नेचर:\n\n```ts\nexecute(toolCallId, params, onUpdate, ctx, signal)\n```\n\n- `params` TypeBox स्कीमा से `Static<TParams>` के माध्यम से स्टेटिकली टाइप किया गया है।\n- रनटाइम तर्क सत्यापन एजेंट लूप में निष्पादन से पहले होता है।\n- `onUpdate` UI स्ट्रीमिंग के लिए आंशिक परिणाम उत्सर्जित करता है।\n- `ctx` में सेशन/मॉडल स्थिति और एक `abort()` सहायक शामिल है।\n- `signal` रद्दीकरण वहन करता है।\n\n`CustomToolAdapter` इसे एजेंट टूल इंटरफेस से जोड़ता है और सही तर्क क्रम में कॉल अग्रेषित करता है।\n\n## मॉडल को उपकरण कैसे उजागर किए जाते हैं\n\n- उपकरणों को `AgentTool` इंस्टेंस (`CustomToolAdapter` या एक्सटेंशन रैपर) में लपेटा जाता है।\n- उन्हें नाम के अनुसार सेशन टूल रजिस्ट्री में डाला जाता है।\n- SDK बूटस्ट्रैप में, कस्टम और एक्सटेंशन-पंजीकृत उपकरण प्रारंभिक सक्रिय सेट में बलपूर्वक शामिल किए जाते हैं।\n- CLI `--tools` वर्तमान में केवल बिल्ट-इन टूल नामों को सत्यापित करता है; कस्टम टूल समावेश खोज/पंजीकरण पथों और SDK विकल्पों के माध्यम से संभाला जाता है।\n\n## रेंडरिंग हुक\n\nवैकल्पिक रेंडरिंग हुक:\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\nTUI में रनटाइम व्यवहार:\n\n- यदि हुक मौजूद हैं, तो टूल आउटपुट एक `Box` कंटेनर के अंदर रेंडर किया जाता है।\n- `renderResult` को `{ expanded, isPartial, spinnerFrame? }` प्राप्त होता है।\n- रेंडरर त्रुटियाँ पकड़ी जाती हैं और लॉग की जाती हैं; UI डिफ़ॉल्ट टेक्स्ट रेंडरिंग पर वापस जाता है।\n\n## सेशन/स्थिति प्रबंधन\n\nवैकल्पिक `onSession(event, ctx)` सेशन लाइफसाइकिल इवेंट प्राप्त करता है, जिनमें शामिल हैं:\n\n- `start`, `switch`, `branch`, `tree`, `shutdown`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`, `todo_reminder`\n\nब्रांच/सेशन संदर्भ बदलने पर इतिहास से स्थिति पुनर्निर्माण के लिए `ctx.sessionManager` का उपयोग करें।\n\n## विफलताएँ और रद्दीकरण शब्दार्थ\n\n### सिंक्रोनस/एसिंक्रोनस विफलताएँ\n\n- `execute` में थ्रो करना (या अस्वीकृत प्रॉमिस) टूल विफलता के रूप में माना जाता है।\n- एजेंट रनटाइम विफलताओं को `isError: true` और त्रुटि टेक्स्ट सामग्री के साथ टूल परिणाम संदेशों में परिवर्तित करता है।\n- एक्सटेंशन रैपर के साथ, `tool_result` हैंडलर सामग्री/विवरण को पुनः लिख सकते हैं और यहाँ तक कि त्रुटि स्थिति को ओवरराइड कर सकते हैं।\n\n### रद्दीकरण\n\n- एजेंट अबॉर्ट `AbortSignal` के माध्यम से `execute` तक प्रसारित होता है।\n- सहकारी रद्दीकरण के लिए सबप्रोसेस कार्य को `signal` अग्रेषित करें (`pi.exec(..., { signal })`)।\n- `ctx.abort()` एक टूल को वर्तमान एजेंट ऑपरेशन के अबॉर्ट का अनुरोध करने देता है।\n\n### onSession त्रुटियाँ\n\n- `onSession` त्रुटियाँ पकड़ी जाती हैं और चेतावनी के रूप में लॉग की जाती हैं; वे सेशन को क्रैश नहीं करती हैं।\n\n## डिज़ाइन के लिए वास्तविक बाधाएँ\n\n- टूल नाम सक्रिय रजिस्ट्री में वैश्विक रूप से अद्वितीय होने चाहिए।\n- रेंडरर/स्थिति पुनर्निर्माण के लिए `details` में निर्धारक, स्कीमा-आकारित आउटपुट को प्राथमिकता दें।\n- `pi.hasUI` के साथ UI उपयोग की सुरक्षा करें।\n- टूल निर्देशिकाओं में `.md`/`.json` को मेटाडेटा मानें, निष्पादन योग्य मॉड्यूल नहीं।\n",
	"hi/runtime-tools/notebook-tool-runtime.md": "---\ntitle: नोटबुक टूल रनटाइम आंतरिक संरचना\ndescription: >-\n  Jupyter नोटबुक टूल रनटाइम जिसमें सेल एक्जीक्यूशन, कर्नेल लाइफसाइकल और आउटपुट\n  रेंडरिंग शामिल है।\nsidebar:\n  order: 2\n  label: नोटबुक टूल\ni18n:\n  sourceHash: c1bafcb245e4\n  translator: machine\n---\n\n# नोटबुक टूल रनटाइम आंतरिक संरचना\n\nयह दस्तावेज़ वर्तमान `notebook` टूल के कार्यान्वयन और कर्नेल-समर्थित Python रनटाइम के साथ उसके संबंध का वर्णन करता है।\n\nमहत्वपूर्ण अंतर: **`notebook` एक JSON नोटबुक संपादक है, न कि नोटबुक एक्जीक्यूटर**। यह `.ipynb` सेल सोर्स को सीधे संपादित करता है; यह Python कर्नेल को प्रारंभ नहीं करता और न ही उससे संवाद करता है।\n\n## कार्यान्वयन फ़ाइलें\n\n- [`src/tools/notebook.ts`](../../packages/coding-agent/src/tools/notebook.ts)\n- [`src/ipy/executor.ts`](../../packages/coding-agent/src/ipy/executor.ts)\n- [`src/ipy/kernel.ts`](../../packages/coding-agent/src/ipy/kernel.ts)\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts)\n- [`src/tools/python.ts`](../../packages/coding-agent/src/tools/python.ts)\n\n## 1) रनटाइम सीमा: संपादन बनाम एक्जीक्यूशन\n\n## `notebook` टूल (`src/tools/notebook.ts`)\n\n- `.ipynb` फ़ाइल पर `action: edit | insert | delete` का समर्थन करता है।\n- सेशन CWD के सापेक्ष पथ को रिज़ॉल्व करता है (`resolveToCwd`)।\n- नोटबुक JSON लोड करता है, `cells` एरे को मान्य करता है, `cell_index` सीमाओं को मान्य करता है।\n- मेमोरी में सोर्स एडिट्स लागू करता है और `JSON.stringify(notebook, null, 1)` के साथ पूरा नोटबुक JSON वापस लिखता है।\n- पाठ्य सारांश + संरचित `details` (`action`, `cellIndex`, `cellType`, `totalCells`, `cellSource`) लौटाता है।\n\nइस टूल में कोई कर्नेल लाइफसाइकल मौजूद नहीं है:\n\n- कोई गेटवे अधिग्रहण नहीं\n- कोई कर्नेल सेशन ID नहीं\n- कोई `execute_request` नहीं\n- कर्नेल चैनलों से कोई स्ट्रीम चंक्स नहीं\n- कोई रिच डिस्प्ले कैप्चर नहीं (`image/png`, JSON display, status MIME)\n\n## नोटबुक-जैसा एक्जीक्यूशन पथ (`src/tools/python.ts` + `src/ipy/*`)\n\nजब एजेंट को सेल-स्टाइल Python कोड चलाने की आवश्यकता होती है (अनुक्रमिक सेल, स्थायी अवस्था, रिच डिस्प्ले), तो वह **`python` टूल** के माध्यम से जाता है, न कि `notebook` के।\n\nवह पथ वहाँ है जहाँ कर्नेल मोड्स, restart/cancel व्यवहार, चंक स्ट्रीमिंग और आउटपुट आर्टिफैक्ट ट्रंकेशन रहते हैं।\n\n## 2) नोटबुक सेल हैंडलिंग सेमेंटिक्स (`notebook` टूल)\n\n## सोर्स नॉर्मलाइज़ेशन\n\n`content` को न्यूलाइन संरक्षण के साथ `source: string[]` में विभाजित किया जाता है:\n\n- प्रत्येक गैर-अंतिम पंक्ति अनुगामी `\\n` रखती है\n- अंतिम पंक्ति में कोई जबरन अनुगामी न्यूलाइन नहीं होती\n\nयह नोटबुक JSON परंपराओं को प्रतिबिंबित करता है और बाद के संपादनों पर अनपेक्षित पंक्ति संयोजन से बचाता है।\n\n## एक्शन व्यवहार\n\n- `edit`\n  - `cells[cell_index].source` को बदलता है\n  - मौजूदा `cell_type` को संरक्षित रखता है\n- `insert`\n  - `[0..cellCount]` पर सम्मिलित करता है\n  - `cell_type` डिफ़ॉल्ट रूप से `code` होता है\n  - कोड सेल `execution_count: null` और `outputs: []` के साथ आरंभ होते हैं\n  - मार्कडाउन सेल केवल `metadata` + `source` के साथ आरंभ होते हैं\n- `delete`\n  - `cells[cell_index]` को हटाता है\n  - रेंडरर पूर्वावलोकन के लिए details में हटाए गए `source` को लौटाता है\n\n## त्रुटि सतहें\n\nनिम्नलिखित के लिए हार्ड विफलताएँ थ्रो की जाती हैं:\n\n- नोटबुक फ़ाइल अनुपस्थित होने पर\n- अमान्य JSON\n- अनुपस्थित/गैर-एरे `cells`\n- रेंज से बाहर का इंडेक्स (insert और non-insert के लिए अलग-अलग वैध रेंज होते हैं)\n- `edit`/`insert` के लिए `content` अनुपस्थित होने पर\n\nये अपस्ट्रीम `Error:` टूल प्रतिक्रियाएँ बनती हैं; रेंडरर नोटबुक पथ + स्वरूपित त्रुटि पाठ का उपयोग करता है।\n\n## 3) कर्नेल सेशन सेमेंटिक्स (जहाँ वे वास्तव में मौजूद हैं)\n\nकर्नेल सेमेंटिक्स `executePython` / `PythonKernel` में कार्यान्वित हैं और `python` टूल पर लागू होते हैं।\n\n## मोड्स\n\n`PythonKernelMode`:\n\n- `session` (डिफ़ॉल्ट)\n  - कर्नेल `kernelSessions` मैप में कैश्ड होते हैं\n  - अधिकतम 4 सेशन; ओवरफ्लो पर सबसे पुराना बाहर निकाला जाता है\n  - हर 30 सेकंड में idle/dead क्लीनअप, 5 मिनट बाद टाइमआउट\n  - प्रति-सेशन क्यू एक्जीक्यूशन को सीरियलाइज़ करता है (`session.queue`)\n- `per-call`\n  - अनुरोध के लिए कर्नेल बनाता है\n  - एक्जीक्यूट करता है\n  - `finally` में हमेशा कर्नेल बंद करता है\n\n## रीसेट व्यवहार\n\n`python` टूल केवल मल्टी-सेल कॉल की पहली सेल के लिए `reset` पास करता है; बाद की सेल हमेशा `reset: false` के साथ चलती हैं।\n\n## कर्नेल की मृत्यु / रीस्टार्ट / रिट्री\n\nसेशन मोड में (`withKernelSession`):\n\n- मृत कर्नेल का पता हार्टबीट (`kernel.isAlive()` हर 5 सेकंड में जाँच) या एक्जीक्यूट विफलता से चलता है।\n- पूर्व-रन मृत अवस्था `restartKernelSession` को ट्रिगर करती है।\n- एक्जीक्यूट-टाइम क्रैश पथ एक बार पुनः प्रयास करता है: कर्नेल रीस्टार्ट करता है, हैंडलर पुनः चलाता है।\n- उसी सेशन में `restartCount > 1` से `Python kernel restarted too many times in this session` थ्रो होता है।\n\nस्टार्टअप रिट्री व्यवहार:\n\n- शेयर्ड गेटवे कर्नेल निर्माण HTTP 5xx के साथ `SharedGatewayCreateError` पर एक बार पुनः प्रयास करता है।\n\nसंसाधन समाप्ति पुनर्प्राप्ति:\n\n- `EMFILE`/`ENFILE`/\"Too many open files\" शैली की विफलताओं का पता लगाता है\n- ट्रैक किए गए सेशन साफ़ करता है\n- `shutdownSharedGateway()` को कॉल करता है\n- कर्नेल सेशन निर्माण एक बार पुनः प्रयास करता है\n\n## 4) एनवायरनमेंट/सेशन वेरिएबल इंजेक्शन\n\nकर्नेल स्टार्टअप को executor से वैकल्पिक env मैप मिलता है:\n\n- `PI_SESSION_FILE` (सेशन स्टेट फ़ाइल पथ)\n- `ARTIFACTS` (आर्टिफैक्ट डायरेक्टरी)\n\n`PythonKernel.#initializeKernelEnvironment(...)` फिर कर्नेल के अंदर init स्क्रिप्ट चलाता है:\n\n- `os.chdir(cwd)`\n- `os.environ` में env एंट्रियाँ इंजेक्ट करता है\n- अनुपस्थित होने पर cwd को `sys.path` के आगे जोड़ता है\n\nनिहितार्थ:\n\n- प्रस्तावना हेल्पर जो सेशन या आर्टिफैक्ट संदर्भ पढ़ते हैं, वे Python प्रोसेस स्टेट में इन env vars पर निर्भर करते हैं।\n\n## 5) स्ट्रीमिंग/चंक और डिस्प्ले हैंडलिंग (कर्नेल-समर्थित पथ)\n\nकर्नेल क्लाइंट प्रत्येक एक्जीक्यूशन के लिए Jupyter प्रोटोकॉल संदेशों को प्रोसेस करता है:\n\n- `stream` -> `onChunk` को टेक्स्ट चंक\n- `execute_result` / `display_data` ->\n  - MIME प्राथमिकता द्वारा चुना गया डिस्प्ले टेक्स्ट: `text/markdown` > `text/plain` > रूपांतरित `text/html`\n  - संरचित आउटपुट अलग से कैप्चर किए जाते हैं:\n    - `application/json` -> `{ type: \"json\" }`\n    - `image/png` -> `{ type: \"image\" }`\n    - `application/x-xcsh-status` -> `{ type: \"status\" }` (कोई टेक्स्ट उत्सर्जन नहीं)\n- `error` -> ट्रेसबैक टेक्स्ट चंक स्ट्रीम में पुश + संरचित त्रुटि मेटाडेटा\n- `input_request` -> stdin चेतावनी टेक्स्ट उत्सर्जित करता है, खाली `input_reply` भेजता है, stdin अनुरोधित चिह्नित करता है\n- पूर्णता `execute_reply` और कर्नेल `status=idle` दोनों की प्रतीक्षा करती है\n\nरद्दीकरण/टाइमआउट:\n\n- abort सिग्नल `interrupt()` को ट्रिगर करता है (REST `/interrupt` + control-channel `interrupt_request`)\n- परिणाम `cancelled=true` चिह्नित करता है\n- टाइमआउट पथ आउटपुट को `Command timed out after <n> seconds` के साथ एनोटेट करता है\n\n## 6) ट्रंकेशन और आर्टिफैक्ट व्यवहार\n\n`src/session/streaming-output.ts` में `OutputSink` का उपयोग कर्नेल एक्जीक्यूशन पथों (`executeWithKernel`) द्वारा किया जाता है:\n\n- प्रत्येक चंक को सैनिटाइज़ करता है (`sanitizeText`)\n- कुल/आउटपुट पंक्तियाँ और बाइट्स ट्रैक करता है\n- वैकल्पिक आर्टिफैक्ट स्पिल फ़ाइल (`artifactPath`, `artifactId`)\n- जब इन-मेमोरी बफर सीमा से अधिक हो जाता है (`DEFAULT_MAX_BYTES` जब तक ओवरराइड न हो):\n  - ट्रंकेटेड चिह्नित करता है\n  - मेमोरी में टेल बाइट्स रखता है (UTF-8 सुरक्षित सीमा)\n  - आर्टिफैक्ट सिंक में पूरी स्ट्रीम स्पिल कर सकता है\n\n`dump()` लौटाता है:\n\n- दृश्यमान आउटपुट टेक्स्ट (संभवतः टेल-ट्रंकेटेड)\n- ट्रंकेशन फ्लैग + काउंट\n- आर्टिफैक्ट ID (`artifact://<id>` संदर्भों के लिए)\n\n`python` टूल इस मेटाडेटा को परिणाम ट्रंकेशन सूचनाओं और TUI चेतावनियों में परिवर्तित करता है।\n\n`notebook` टूल `OutputSink` का उपयोग **नहीं** करता; इसमें कोई स्ट्रीम/आर्टिफैक्ट ट्रंकेशन पाइपलाइन नहीं है क्योंकि यह कोड एक्जीक्यूट नहीं करता।\n\n## 7) रेंडरर अनुमान और स्वरूपण\n\n## नोटबुक रेंडरर (`notebookToolRenderer`)\n\n- कॉल व्यू: action + नोटबुक पथ + cell/type मेटाडेटा के साथ स्टेटस लाइन\n- परिणाम व्यू:\n  - `details` से प्राप्त सफलता सारांश\n  - `cellSource` को `renderCodeCell` के माध्यम से रेंडर किया गया\n  - मार्कडाउन सेल `markdown` लैंग्वेज हिंट सेट करते हैं; अन्य सेल में कोई स्पष्ट लैंग्वेज ओवरराइड नहीं\n  - संक्षिप्त कोड पूर्वावलोकन सीमा `PREVIEW_LIMITS.COLLAPSED_LINES * 2` है\n  - शेयर्ड रेंडर विकल्पों के माध्यम से विस्तारित मोड का समर्थन करता है\n  - width + expanded state द्वारा कीड रेंडर कैश का उपयोग करता है\n\nत्रुटि रेंडरिंग अनुमान:\n\n- यदि पहली टेक्स्ट सामग्री `Error:` से शुरू होती है, तो रेंडरर नोटबुक त्रुटि ब्लॉक के रूप में स्वरूपित करता है।\n\n## Python रेंडरर (वास्तविक एक्जीक्यूशन आउटपुट के लिए)\n\nकर्नेल-समर्थित एक्जीक्यूशन रेंडरिंग की अपेक्षाएँ:\n\n- प्रति-सेल स्टेटस ट्रांजिशन (`pending/running/complete/error`)\n- वैकल्पिक संरचित स्टेटस इवेंट अनुभाग\n- वैकल्पिक JSON आउटपुट ट्री\n- ट्रंकेशन चेतावनियाँ + वैकल्पिक `artifact://<id>` पॉइंटर\n\nयह रेंडरर व्यवहार `notebook` JSON संपादन परिणामों से असंबंधित है, सिवाय इसके कि दोनों शेयर्ड TUI प्रिमिटिव का पुनः उपयोग करते हैं।\n\n## 8) सादे Python टूल व्यवहार से विचलन\n\nयदि \"सादे Python टूल\" का अर्थ `python` एक्जीक्यूशन पथ है:\n\n- `python` कर्नेल में कोड एक्जीक्यूट करता है, मोड द्वारा स्टेट बनाए रखता है, चंक स्ट्रीम करता है, रिच डिस्प्ले कैप्चर करता है, interrupt/timeout संभालता है, और आउटपुट ट्रंकेशन/आर्टिफैक्ट का समर्थन करता है।\n- `notebook` केवल निर्धारित नोटबुक JSON म्यूटेशन करता है; कोई एक्जीक्यूशन नहीं, कोई कर्नेल स्टेट नहीं, कोई चंक स्ट्रीम नहीं, कोई डिस्प्ले आउटपुट नहीं, कोई आर्टिफैक्ट पाइपलाइन नहीं।\n\nयदि किसी वर्कफ़्लो को दोनों की आवश्यकता है:\n\n1. `notebook` के साथ नोटबुक सोर्स संपादित करें\n2. कोड सेल को `python` के माध्यम से एक्जीक्यूट करें (कोड को मैन्युअल रूप से पास करके), `notebook` के माध्यम से नहीं\n\nवर्तमान कार्यान्वयन एक एकल टूल प्रदान नहीं करता जो `.ipynb` को म्यूटेट करे और कर्नेल संदर्भ के माध्यम से नोटबुक सेल एक्जीक्यूट भी करे।\n",
	"hi/runtime-tools/resolve-tool-runtime.md": "---\ntitle: Resolve टूल रनटाइम इंटर्नल्स\ndescription: >-\n  फ़ाइल पथ समाधान, कंटेंट फेचिंग और URL-आधारित संसाधन एक्सेस के लिए Resolve टूल\n  रनटाइम।\nsidebar:\n  order: 3\n  label: Resolve टूल\ni18n:\n  sourceHash: 06e8be8c5a3c\n  translator: machine\n---\n\n# Resolve टूल रनटाइम इंटर्नल्स\n\nयह दस्तावेज़ बताता है कि coding-agent में preview/apply वर्कफ़्लो को कैसे मॉडल किया जाता है और कस्टम टूल `pushPendingAction` के माध्यम से कैसे भाग ले सकते हैं।\n\n## स्कोप और मुख्य फ़ाइलें\n\n- [`src/tools/resolve.ts`](../../packages/coding-agent/src/tools/resolve.ts)\n- [`src/tools/pending-action.ts`](../../packages/coding-agent/src/tools/pending-action.ts)\n- [`src/tools/ast-edit.ts`](../../packages/coding-agent/src/tools/ast-edit.ts)\n- [`src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts)\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n\n## `resolve` क्या करता है\n\n`resolve` एक छिपा हुआ टूल है जो एक pending preview action को अंतिम रूप देता है।\n\n- `action: \"apply\"` pending action पर `apply(reason)` को निष्पादित करता है और परिवर्तनों को सुरक्षित करता है।\n- `action: \"discard\"` यदि प्रदान किया गया हो तो `reject(reason)` को आमंत्रित करता है; अन्यथा एक डिफ़ॉल्ट \"Discarded\" संदेश के साथ action को छोड़ देता है।\n\nयदि कोई pending action मौजूद नहीं है, तो `resolve` निम्न त्रुटि के साथ विफल होता है:\n\n- `No pending action to resolve. Nothing to apply or discard.`\n\n## Pending actions एक स्टैक (LIFO) हैं\n\nPending actions को `PendingActionStore` में push/pop स्टैक के रूप में संग्रहीत किया जाता है:\n\n- `push(action)` शीर्ष पर एक नया pending action जोड़ता है।\n- `peek()` वर्तमान शीर्ष action का निरीक्षण करता है।\n- `pop()` शीर्ष action को हटाता है और लौटाता है।\n- `hasPending` इंगित करता है कि स्टैक खाली नहीं है।\n\n`resolve` हमेशा पहले **सबसे ऊपर के** pending action को उपभोग करता है (`pop()`), इसलिए एकाधिक preview-उत्पन्न करने वाले टूल पंजीकरण के विपरीत क्रम में resolve होते हैं।\n\n## अंतर्निहित producer उदाहरण (`ast_edit`)\n\n`ast_edit` पहले structural replacements का preview करता है। जब preview में replacements होते हैं और वह अभी तक apply नहीं हुआ है, तो यह एक pending action push करता है जिसमें शामिल हैं:\n\n- label (मानव-पठनीय सारांश)\n- `sourceToolName` (`ast_edit`)\n- `apply(reason: string)` callback जो `dryRun: false` के साथ AST edit को पुनः चलाता है\n\n`resolve(action=\"apply\", reason=\"...\")` इस callback में `reason` पास करता है।\n\n## कस्टम टूल: `pushPendingAction`\n\nकस्टम टूल `CustomToolAPI.pushPendingAction(...)` के माध्यम से resolve-संगत pending actions पंजीकृत कर सकते हैं।\n\n`CustomToolPendingAction`:\n\n- `label: string` (आवश्यक)\n- `apply(reason: string): Promise<AgentToolResult<unknown>>` (आवश्यक) — apply पर आमंत्रित होता है; `reason` वह string है जो `resolve` को पास की जाती है\n- `reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>` (वैकल्पिक) — discard पर आमंत्रित होता है; यदि प्रदान किया गया हो तो return value डिफ़ॉल्ट \"Discarded\" संदेश को प्रतिस्थापित करती है\n- `details?: unknown` (वैकल्पिक)\n- `sourceToolName?: string` (वैकल्पिक, डिफ़ॉल्ट `\"custom_tool\"`)\n\n### न्यूनतम उपयोग उदाहरण\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = pi => ({\n name: \"batch_rename_preview\",\n label: \"Batch Rename Preview\",\n description: \"Previews renames and defers commit to resolve\",\n parameters: pi.typebox.Type.Object({\n  files: pi.typebox.Type.Array(pi.typebox.Type.String()),\n }),\n\n async execute(_toolCallId, params) {\n  const previewSummary = `Prepared rename plan for ${params.files.length} files`;\n\n  pi.pushPendingAction({\n   label: `Batch rename: ${params.files.length} files`,\n   sourceToolName: \"batch_rename_preview\",\n   apply: async (reason) => {\n    // apply writes here\n    return {\n     content: [{ type: \"text\", text: `Applied batch rename. Reason: ${reason}` }],\n    };\n   },\n   reject: async (reason) => {\n    // optional: cleanup or notify on discard\n    return {\n     content: [{ type: \"text\", text: `Discarded batch rename. Reason: ${reason}` }],\n    };\n   },\n  });\n\n  return {\n   content: [{ type: \"text\", text: `${previewSummary}. Call resolve to apply or discard.` }],\n  };\n },\n});\n\nexport default factory;\n```\n\n## रनटाइम उपलब्धता और विफलताएं\n\n`pushPendingAction` को कस्टम टूल लोडर द्वारा सक्रिय session `PendingActionStore` का उपयोग करके जोड़ा जाता है।\n\nयदि रनटाइम में कोई pending-action store नहीं है, तो `pushPendingAction` निम्न त्रुटि फेंकता है:\n\n- `Pending action store unavailable for custom tools in this runtime.`\n\n## टूल-चॉइस व्यवहार\n\nजब `PendingActionStore.hasPending` true होता है, तो agent runtime टूल चॉइस को `resolve` की ओर प्राथमिकता देता है ताकि सामान्य टूल प्रवाह जारी रखने से पहले pending previews को स्पष्ट रूप से अंतिम रूप दिया जा सके।\n\n## डेवलपर मार्गदर्शन\n\n- Pending actions का उपयोग केवल विनाशकारी या उच्च-प्रभाव वाले ऑपरेशन के लिए करें जिन्हें स्पष्ट apply/discard का समर्थन करना चाहिए।\n- `label` को संक्षिप्त और विशिष्ट रखें; यह resolve renderer आउटपुट में दिखाया जाता है।\n- सुनिश्चित करें कि `apply(reason)` एकल निष्पादन के लिए पर्याप्त रूप से निर्धारणवादी और idempotent हो; `reason` सूचनात्मक है और व्यवहार को नहीं बदलना चाहिए।\n- `reject(reason)` तब लागू करें जब discard को cleanup की आवश्यकता हो (अस्थायी state, locks, notifications); उन stateless previews के लिए इसे छोड़ दें जहाँ डिफ़ॉल्ट संदेश पर्याप्त हो।\n- यदि आपका टूल एकाधिक previews स्टेज कर सकता है, तो LIFO semantics याद रखें: सबसे बाद में push किया गया action पहले resolve होता है।\n",
	"hi/runtime-tools/slash-command-internals.md": "---\ntitle: स्लैश कमांड आंतरिक संरचना\ndescription: >-\n  पंजीकरण, तर्क पार्सिंग और निष्पादन डिस्पैच के साथ स्लैश कमांड सिस्टम की आंतरिक\n  संरचना।\nsidebar:\n  order: 5\n  label: स्लैश कमांड\ni18n:\n  sourceHash: 2cbd44a3de87\n  translator: machine\n---\n\n# स्लैश कमांड आंतरिक संरचना\n\nयह दस्तावेज़ बताता है कि `coding-agent` में स्लैश कमांड किस प्रकार खोजे जाते हैं, डुप्लीकेट हटाए जाते हैं, इंटरएक्टिव मोड में प्रस्तुत किए जाते हैं, और प्रॉम्प्ट समय पर विस्तारित किए जाते हैं।\n\n## कार्यान्वयन फ़ाइलें\n\n- [`src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n- [`src/capability/slash-command.ts`](../../packages/coding-agent/src/capability/slash-command.ts)\n- [`src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`src/discovery/claude.ts`](../../packages/coding-agent/src/discovery/claude.ts)\n- [`src/discovery/codex.ts`](../../packages/coding-agent/src/discovery/codex.ts)\n- [`src/discovery/claude-plugins.ts`](../../packages/coding-agent/src/discovery/claude-plugins.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n\n## 1) डिस्कवरी मॉडल\n\nस्लैश कमांड एक क्षमता (`id: \"slash-commands\"`) हैं जो कमांड नाम द्वारा कुंजीबद्ध हैं (`key: cmd => cmd.name`)।\n\nक्षमता रजिस्ट्री सभी पंजीकृत प्रदाताओं को लोड करती है, प्रदाता प्राथमिकता के अनुसार अवरोही क्रम में क्रमबद्ध करती है, और **पहले आने वाले को प्राथमिकता** (first wins) के नियम के साथ कुंजी द्वारा डुप्लीकेट हटाती है।\n\n### प्रदाता प्राथमिकता\n\nवर्तमान स्लैश-कमांड प्रदाता और उनकी प्राथमिकताएँ:\n\n1. `native` (OMP) — प्राथमिकता `100`\n2. `claude` — प्राथमिकता `80`\n3. `claude-plugins` — प्राथमिकता `70`\n4. `codex` — प्राथमिकता `70`\n\nटाई व्यवहार: समान-प्राथमिकता वाले प्रदाता पंजीकरण क्रम बनाए रखते हैं। वर्तमान इम्पोर्ट क्रम `codex` से पहले `claude-plugins` को पंजीकृत करता है, इसलिए नाम टकराव होने पर प्लगइन कमांड codex कमांड पर जीतते हैं।\n\n### नाम-टकराव व्यवहार\n\n`slash-commands` के लिए, टकराव सख्त रूप से क्षमता डिडुप द्वारा हल किए जाते हैं:\n\n- उच्चतम-प्राथमिकता वाला आइटम `result.items` में रखा जाता है\n- कम-प्राथमिकता वाले डुप्लीकेट केवल `result.all` में रहते हैं और `_shadowed = true` चिह्नित होते हैं\n\nयह प्रदाताओं के पार और एक प्रदाता के भीतर भी लागू होता है यदि वह डुप्लीकेट नाम लौटाता है।\n\n### फ़ाइल स्कैनिंग व्यवहार\n\nप्रदाता अधिकतर `loadFilesFromDir(...)` का उपयोग करते हैं, जो वर्तमान में:\n\n- गैर-पुनरावर्ती मिलान (`*.md`) पर डिफ़ॉल्ट होता है\n- `gitignore: true`, `hidden: false` के साथ नेटिव glob का उपयोग करता है\n- प्रत्येक मिलान की गई फ़ाइल को पढ़ता है और उसे `SlashCommand` में रूपांतरित करता है\n\nइसलिए छुपी हुई फ़ाइलें/डायरेक्टरी लोड नहीं होती हैं, और अनदेखे पथ छोड़ दिए जाते हैं।\n\n## 2) प्रदाता-विशिष्ट स्रोत पथ और स्थानीय प्राथमिकता\n\n## `native` प्रदाता (`builtin.ts`)\n\nखोज मूल `.xcsh` डायरेक्टरी से आते हैं:\n\n- प्रोजेक्ट: `<cwd>/.xcsh/commands/*.md`\n- उपयोगकर्ता: `~/.xcsh/agent/commands/*.md`\n\n`getConfigDirs()` पहले प्रोजेक्ट लौटाता है, फिर उपयोगकर्ता, इसलिए नाम टकराव होने पर **प्रोजेक्ट नेटिव कमांड उपयोगकर्ता नेटिव कमांड से जीतते हैं**।\n\n## `claude` प्रदाता (`claude.ts`)\n\nलोड करता है:\n\n- उपयोगकर्ता: `~/.claude/commands/*.md`\n- प्रोजेक्ट: `<cwd>/.claude/commands/*.md`\n\nप्रदाता प्रोजेक्ट आइटम से पहले उपयोगकर्ता आइटम पुश करता है, इसलिए इस प्रदाता के भीतर समान-नाम टकराव पर **उपयोगकर्ता Claude कमांड प्रोजेक्ट Claude कमांड से जीतते हैं**।\n\n## `codex` प्रदाता (`codex.ts`)\n\nलोड करता है:\n\n- उपयोगकर्ता: `~/.codex/commands/*.md`\n- प्रोजेक्ट: `<cwd>/.codex/commands/*.md`\n\nदोनों पक्ष लोड किए जाते हैं फिर उपयोगकर्ता-प्रथम क्रम में समतल किए जाते हैं, इसलिए टकराव पर **उपयोगकर्ता Codex कमांड प्रोजेक्ट Codex कमांड से जीतते हैं**।\n\nCodex कमांड सामग्री फ्रंटमैटर स्ट्रिपिंग (`parseFrontmatter`) के साथ पार्स की जाती है, और कमांड नाम फ्रंटमैटर `name` द्वारा ओवरराइड किया जा सकता है; अन्यथा फ़ाइलनाम का उपयोग किया जाता है।\n\n## `claude-plugins` प्रदाता (`claude-plugins.ts`)\n\n`~/.claude/plugins/installed_plugins.json` से प्लगइन कमांड मूल लोड करता है, फिर `<pluginRoot>/commands/*.md` स्कैन करता है।\n\nक्रम उस JSON डेटा से रजिस्ट्री पुनरावृत्ति क्रम और प्रति-प्लगइन एंट्री क्रम का अनुसरण करता है। कोई अतिरिक्त सॉर्ट चरण नहीं है।\n\n## 3) रनटाइम `FileSlashCommand` में मटेरियलाइज़ेशन\n\n`src/extensibility/slash-commands.ts` में `loadSlashCommands()` क्षमता आइटम को प्रॉम्प्ट समय पर उपयोग किए जाने वाले `FileSlashCommand` ऑब्जेक्ट में परिवर्तित करता है।\n\nप्रत्येक कमांड के लिए:\n\n1. फ्रंटमैटर/बॉडी पार्स करें (`parseFrontmatter`)\n2. विवरण स्रोत:\n   - `frontmatter.description` यदि मौजूद हो\n   - अन्यथा पहली गैर-रिक्त बॉडी लाइन (ट्रिम की गई, अधिकतम 60 वर्ण `...` के साथ)\n3. पार्स की गई बॉडी को निष्पादन योग्य टेम्पलेट सामग्री के रूप में रखें\n4. `via Claude Code Project` जैसी प्रदर्शन स्रोत स्ट्रिंग की गणना करें\n\nफ्रंटमैटर पार्स गंभीरता स्रोत-निर्भर है:\n\n- `native` स्तर -> पार्स त्रुटियाँ `fatal` हैं\n- `user`/`project` स्तर -> पार्स त्रुटियाँ `warn` हैं जिनमें फ़ॉलबैक पार्सिंग होती है\n\n### बंडल किए गए फ़ॉलबैक कमांड\n\nफाइलसिस्टम/प्रदाता कमांड के बाद, एम्बेडेड कमांड टेम्पलेट (`EMBEDDED_COMMAND_TEMPLATES`) जोड़े जाते हैं यदि उनके नाम पहले से मौजूद नहीं हैं।\n\nवर्तमान एम्बेडेड सेट `src/task/commands.ts` से आता है और फ़ॉलबैक के रूप में उपयोग किया जाता है (`source: \"bundled\"`)।\n\n## 4) इंटरएक्टिव मोड: कमांड सूचियाँ कहाँ से आती हैं\n\nइंटरएक्टिव मोड ऑटोकम्पलीट और कमांड रूटिंग के लिए कई कमांड स्रोतों को संयोजित करता है।\n\nनिर्माण समय पर यह एक पेंडिंग कमांड सूची बनाता है:\n\n- बिल्ट-इन (`BUILTIN_SLASH_COMMANDS`, जिसमें चयनित कमांड के लिए तर्क पूर्णता और इनलाइन संकेत शामिल हैं)\n- एक्सटेंशन-पंजीकृत स्लैश कमांड (`extensionRunner.getRegisteredCommands(...)`)\n- TypeScript कस्टम कमांड (`session.customCommands`), स्लैश कमांड लेबल में मैप किए गए\n- वैकल्पिक स्किल कमांड (`/skill:<name>`) जब `skills.enableSkillCommands` सक्षम हो\n\nफिर `init()` फ़ाइल-आधारित कमांड लोड करने और एक `CombinedAutocompleteProvider` इंस्टॉल करने के लिए `refreshSlashCommandState(...)` को कॉल करता है जिसमें शामिल है:\n\n- ऊपर की पेंडिंग कमांड\n- खोजे गए फ़ाइल-आधारित कमांड\n\n`refreshSlashCommandState(...)` `session.setSlashCommands(...)` भी अपडेट करता है ताकि प्रॉम्प्ट विस्तार उसी खोजे गए फ़ाइल कमांड सेट का उपयोग करे।\n\n### रिफ्रेश जीवनचक्र\n\nस्लैश कमांड स्थिति रिफ्रेश होती है:\n\n- इंटरएक्टिव इनिट के दौरान\n- `/move` द्वारा कार्यशील डायरेक्टरी बदलने के बाद (`handleMoveCommand` `resetCapabilities()` फिर `refreshSlashCommandState(newCwd)` कॉल करता है)\n\nकमांड डायरेक्टरी के लिए कोई निरंतर फ़ाइल वॉचर नहीं है।\n\n### अन्य प्रदर्शन\n\nExtensions डैशबोर्ड `slash-commands` क्षमता भी लोड करता है और सक्रिय/छाया किए गए कमांड प्रविष्टियाँ प्रदर्शित करता है, जिसमें `_shadowed` डुप्लीकेट शामिल हैं।\n\n## 5) प्रॉम्प्ट पाइपलाइन स्थान\n\n`AgentSession.prompt(...)` स्लैश हैंडलिंग क्रम (जब `expandPromptTemplates !== false`):\n\n1. **एक्सटेंशन कमांड** (`#tryExecuteExtensionCommand`)  \n   यदि `/name` एक्सटेंशन-पंजीकृत कमांड से मेल खाता है, हैंडलर तुरंत निष्पादित होता है और प्रॉम्प्ट वापस आता है।\n2. **TypeScript कस्टम कमांड** (`#tryExecuteCustomCommand`)  \n   केवल सीमा: यदि मेल खाता है, यह निष्पादित होता है और वापस कर सकता है:\n   - `string` -> प्रॉम्प्ट टेक्स्ट को उस स्ट्रिंग से बदलें\n   - `void/undefined` -> हैंडल किए गए के रूप में माना जाता है; कोई LLM प्रॉम्प्ट नहीं\n3. **फ़ाइल-आधारित स्लैश कमांड** (`expandSlashCommand`)  \n   यदि टेक्स्ट अभी भी `/` से शुरू होता है, तो markdown कमांड विस्तार का प्रयास करें।\n4. **प्रॉम्प्ट टेम्पलेट** (`expandPromptTemplate`)  \n   स्लैश/कस्टम प्रसंस्करण के बाद लागू।\n5. **डिलीवरी**\n   - idle: प्रॉम्प्ट तुरंत एजेंट को भेजा जाता है\n   - streaming: प्रॉम्प्ट `streamingBehavior` के आधार पर steer/follow-up के रूप में क्यूड किया जाता है\n\nइसीलिए स्लैश कमांड विस्तार प्रॉम्प्ट-टेम्पलेट विस्तार से पहले होता है, और इसीलिए कस्टम कमांड फ़ाइल-कमांड मिलान से पहले प्रमुख स्लैश को रूपांतरित कर सकते हैं।\n\n## 6) फ़ाइल-आधारित स्लैश कमांड के लिए विस्तार सिमेंटिक्स\n\n`expandSlashCommand(text, fileCommands)` व्यवहार:\n\n- केवल तब चलता है जब टेक्स्ट `/` से शुरू होता है\n- `/` के बाद पहले टोकन से कमांड नाम पार्स करता है\n- `parseCommandArgs` के माध्यम से शेष टेक्स्ट से args पार्स करता है\n- लोड किए गए `fileCommands` में सटीक नाम मिलान खोजता है\n- यदि मेल खाया, लागू करता है:\n  - स्थितिगत प्रतिस्थापन: `$1`, `$2`, ...\n  - समेकित प्रतिस्थापन: `$ARGUMENTS` और `$@`\n  - फिर `{ args, ARGUMENTS, arguments }` के साथ `prompt.render` के माध्यम से टेम्पलेट रेंडरिंग\n- यदि कोई मेल नहीं, मूल टेक्स्ट अपरिवर्तित लौटाता है\n\n### `parseCommandArgs` की सावधानियाँ\n\nपार्सर एक सरल quote-aware विभाजन है:\n\n- स्पेस रखने के लिए `'single'` और `\"double\"` quoting का समर्थन करता है\n- quote सीमांकक हटाता है\n- backslash escaping नियम लागू नहीं करता\n- अपूर्ण quote कोई त्रुटि नहीं है; पार्सर अंत तक उपभोग करता है\n\n## 7) अज्ञात `/...` व्यवहार\n\nअज्ञात स्लैश इनपुट को मुख्य स्लैश लॉजिक द्वारा **अस्वीकार नहीं किया जाता**।\n\nयदि कमांड एक्सटेंशन/कस्टम/फ़ाइल परतों द्वारा हैंडल नहीं किया जाता, `expandSlashCommand` मूल टेक्स्ट लौटाता है, और लिटरल `/...` प्रॉम्प्ट सामान्य प्रॉम्प्ट-टेम्पलेट विस्तार और LLM डिलीवरी के माध्यम से आगे बढ़ता है।\n\nइंटरएक्टिव मोड `InputController` में कई बिल्ट-इन को अलग से कठोरता से हैंडल करता है (उदाहरण के लिए `/settings`, `/model`, `/mcp`, `/move`, `/exit`)। ये `session.prompt(...)` से पहले उपभोग किए जाते हैं और इसलिए उस पथ में फ़ाइल-कमांड विस्तार तक कभी नहीं पहुँचते।\n\n## 8) idle की तुलना में streaming-time अंतर\n\n## Idle पथ\n\n- `session.prompt(\"/x ...\")` कमांड पाइपलाइन चलाता है और या तो कमांड तुरंत निष्पादित करता है या विस्तारित टेक्स्ट सीधे भेजता है।\n\n## Streaming पथ (`session.isStreaming === true`)\n\n- `prompt(...)` अभी भी पहले एक्सटेंशन/कस्टम/फ़ाइल/टेम्पलेट रूपांतरण चलाता है\n- फिर `streamingBehavior` की आवश्यकता होती है:\n  - `\"steer\"` -> इंटरप्ट संदेश क्यू करें (`agent.steer`)\n  - `\"followUp\"` -> पोस्ट-टर्न संदेश क्यू करें (`agent.followUp`)\n- यदि `streamingBehavior` छोड़ा गया है, प्रॉम्प्ट एक त्रुटि फेंकता है\n\n### महत्वपूर्ण कमांड-विशिष्ट streaming व्यवहार\n\n- एक्सटेंशन कमांड streaming के दौरान भी तुरंत निष्पादित होते हैं (टेक्स्ट के रूप में क्यूड नहीं)।\n- `steer(...)`/`followUp(...)` हेल्पर मेथड एक्सटेंशन कमांड को अस्वीकार करती हैं (`#throwIfExtensionCommand`) ताकि हैंडलर के लिए कमांड टेक्स्ट क्यूइंग से बचा जा सके जिन्हें synchronously चलना चाहिए।\n- Compaction queue रिप्ले `isKnownSlashCommand(...)` का उपयोग करता है यह तय करने के लिए कि क्यूड प्रविष्टियों को `session.prompt(...)` के माध्यम से रिप्ले किया जाना चाहिए (ज्ञात स्लैश कमांड के लिए) बनाम raw steer/follow-up मेथड।\n\n## 9) त्रुटि प्रबंधन और विफलता सतह\n\n- प्रदाता लोड विफलताएँ अलग होती हैं; रजिस्ट्री चेतावनियाँ एकत्र करती है और अन्य प्रदाताओं के साथ जारी रहती है।\n- अमान्य स्लैश कमांड आइटम (गायब नाम/पथ/सामग्री या अमान्य स्तर) क्षमता सत्यापन द्वारा हटा दिए जाते हैं।\n- फ्रंटमैटर पार्स विफलताएँ:\n  - native कमांड: fatal पार्स त्रुटि बुलबुले उठती है\n  - non-native कमांड: चेतावनी + फ़ॉलबैक key/value पार्स\n- एक्सटेंशन/कस्टम कमांड हैंडलर अपवाद पकड़े जाते हैं और एक्सटेंशन त्रुटि चैनल के माध्यम से रिपोर्ट किए जाते हैं (या एक्सटेंशन रनर के बिना कस्टम कमांड के लिए logger फ़ॉलबैक), और हैंडल किए गए के रूप में माना जाता है (कोई अनपेक्षित फ़ॉलबैक निष्पादन नहीं)।\n",
	"hi/runtime-tools/task-agent-discovery.md": "---\ntitle: टास्क एजेंट डिस्कवरी और सिलेक्शन\ndescription: >-\n  विशेषज्ञ सबएजेंट प्रकारों को कार्य रूट करने के लिए टास्क एजेंट डिस्कवरी और\n  सिलेक्शन लॉजिक।\nsidebar:\n  order: 6\n  label: टास्क एजेंट डिस्कवरी\ni18n:\n  sourceHash: 8cf42457c672\n  translator: machine\n---\n\n# टास्क एजेंट डिस्कवरी और सिलेक्शन\n\nयह दस्तावेज़ बताता है कि टास्क सबसिस्टम कैसे एजेंट डेफिनिशन्स की खोज करता है, कई स्रोतों को मर्ज करता है, और एक्सीक्यूशन समय पर अनुरोधित एजेंट को रिज़ॉल्व करता है।\n\nयह आज के कार्यान्वित रनटाइम व्यवहार को कवर करता है, जिसमें प्रेसीडेंस, अमान्य-डेफिनिशन हैंडलिंग, और spawn/depth बाधाएँ शामिल हैं जो किसी एजेंट को प्रभावी रूप से अनुपलब्ध बना सकती हैं।\n\n## इम्प्लीमेंटेशन फ़ाइलें\n\n- [`src/task/discovery.ts`](../../packages/coding-agent/src/task/discovery.ts)\n- [`src/task/agents.ts`](../../packages/coding-agent/src/task/agents.ts)\n- [`src/task/types.ts`](../../packages/coding-agent/src/task/types.ts)\n- [`src/task/index.ts`](../../packages/coding-agent/src/task/index.ts)\n- [`src/task/commands.ts`](../../packages/coding-agent/src/task/commands.ts)\n- [`src/prompts/agents/task.md`](../../packages/coding-agent/src/prompts/agents/task.md)\n- [`src/prompts/tools/task.md`](../../packages/coding-agent/src/prompts/tools/task.md)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/config.ts`](../../packages/coding-agent/src/config.ts)\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts)\n\n---\n\n## एजेंट डेफिनिशन का आकार\n\nटास्क एजेंट्स `AgentDefinition` (`src/task/types.ts`) में नॉर्मलाइज़ होते हैं:\n\n- `name`, `description`, `systemPrompt` (एक वैध लोडेड एजेंट के लिए आवश्यक)\n- वैकल्पिक `tools`, `spawns`, `model`, `thinkingLevel`, `output`\n- `source`: `\"bundled\" | \"user\" | \"project\"`\n- वैकल्पिक `filePath`\n\nपार्सिंग `parseAgentFields()` (`src/discovery/helpers.ts`) के माध्यम से frontmatter से आती है:\n\n- `name` या `description` गायब होने पर => अमान्य (`null`), कॉलर इसे पार्स विफलता मानता है\n- `tools` CSV या array स्वीकार करता है; यदि प्रदान किया गया हो, तो `submit_result` स्वतः जोड़ा जाता है\n- `spawns` `*`, CSV, या array स्वीकार करता है\n- बैकवर्ड-कम्पैट व्यवहार: यदि `spawns` गायब है लेकिन `tools` में `task` शामिल है, तो `spawns` `*` बन जाता है\n- `output` अपारदर्शी स्कीमा डेटा के रूप में पास किया जाता है\n\n## बंडल्ड एजेंट्स\n\nबंडल्ड एजेंट्स बिल्ड समय पर (`src/task/agents.ts`) टेक्स्ट इम्पोर्ट्स का उपयोग करके एम्बेड किए जाते हैं।\n\n`EMBEDDED_AGENT_DEFS` परिभाषित करता है:\n\n- प्रॉम्प्ट फ़ाइलों से `explore`, `plan`, `designer`, `reviewer`\n- साझा `task.md` बॉडी प्लस इंजेक्टेड frontmatter से `task` और `quick_task`\n\nलोडिंग पथ:\n\n1. `loadBundledAgents()` एम्बेडेड मार्कडाउन को `parseAgent(..., \"bundled\", \"fatal\")` से पार्स करता है\n2. परिणाम इन-मेमोरी कैश किए जाते हैं (`bundledAgentsCache`)\n3. `clearBundledAgentsCache()` केवल टेस्ट के लिए कैश रीसेट है\n\nचूंकि बंडल्ड पार्सिंग `level: \"fatal\"` का उपयोग करती है, विकृत बंडल्ड frontmatter थ्रो करता है और पूरी डिस्कवरी विफल कर सकता है।\n\n## फ़ाइलसिस्टम और प्लगइन डिस्कवरी\n\n`discoverAgents(cwd, home)` (`src/task/discovery.ts`) बंडल्ड डेफिनिशन्स जोड़ने से पहले कई स्थानों से एजेंट्स को मर्ज करता है।\n\n### डिस्कवरी इनपुट्स\n\n1. `getConfigDirs(\"agents\", { project: false })` से उपयोगकर्ता कॉन्फ़िग एजेंट डायरेक्टरीज़\n2. `findAllNearestProjectConfigDirs(\"agents\", cwd)` से निकटतम प्रोजेक्ट एजेंट डायरेक्टरीज़\n3. `agents/` सबडायरेक्टरीज़ के साथ Claude प्लगइन रूट्स (`listClaudePluginRoots(home)`)\n4. बंडल्ड एजेंट्स (`loadBundledAgents()`)\n\n### वास्तविक स्रोत क्रम\n\nस्रोत-परिवार का क्रम `getConfigDirs(\"\", { project: false })` से आता है, जो `src/config.ts` में `priorityList` से प्राप्त होता है:\n\n1. `.xcsh`\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nप्रत्येक स्रोत परिवार के लिए, डिस्कवरी क्रम है:\n\n1. उस स्रोत के लिए निकटतम प्रोजेक्ट डायरेक्टरी (यदि मिली)\n2. उस स्रोत के लिए उपयोगकर्ता डायरेक्टरी\n\nसभी स्रोत-परिवार डायरेक्टरीज़ के बाद, प्लगइन `agents/` डायरेक्टरीज़ जोड़ी जाती हैं (पहले प्रोजेक्ट-स्कोप प्लगइन्स, फिर उपयोगकर्ता-स्कोप)।\n\nबंडल्ड एजेंट्स सबसे अंत में जोड़े जाते हैं।\n\n### महत्वपूर्ण चेतावनी: पुरानी टिप्पणियाँ बनाम वर्तमान कोड\n\n`discovery.ts` हेडर टिप्पणियाँ अभी भी `.pi` का उल्लेख करती हैं और `.codex`/`.gemini` का उल्लेख नहीं करतीं। वास्तविक रनटाइम क्रम `src/config.ts` द्वारा संचालित है और वर्तमान में `.xcsh`, `.claude`, `.codex`, `.gemini` का उपयोग करता है।\n\n## मर्ज और कोलिज़न नियम\n\nडिस्कवरी सटीक `agent.name` द्वारा first-wins डीडुप का उपयोग करती है:\n\n- एक `Set<string>` देखे गए नामों को ट्रैक करता है।\n- लोड किए गए एजेंट्स डायरेक्टरी क्रम में फ़्लैटन किए जाते हैं और केवल तभी रखे जाते हैं जब नाम नहीं देखा गया हो।\n- बंडल्ड एजेंट्स उसी सेट के विरुद्ध फ़िल्टर किए जाते हैं और केवल तभी जोड़े जाते हैं जब अभी भी नहीं देखे गए हों।\n\nनिहितार्थ:\n\n- समान स्रोत परिवार के लिए प्रोजेक्ट उपयोगकर्ता को ओवरराइड करता है।\n- उच्च-प्राथमिकता स्रोत परिवार निम्न को ओवरराइड करता है (`.xcsh` `.claude` से पहले, आदि)।\n- गैर-बंडल्ड एजेंट्स समान नाम वाले बंडल्ड एजेंट्स को ओवरराइड करते हैं।\n- नाम मिलान केस-सेंसिटिव है (`Task` और `task` अलग-अलग हैं)।\n- एक डायरेक्टरी के भीतर, मार्कडाउन फ़ाइलें डीडुप से पहले लेक्सिकोग्राफ़िक फ़ाइलनाम क्रम में पढ़ी जाती हैं।\n\n## अमान्य/गायब एजेंट फ़ाइल व्यवहार\n\nप्रति डायरेक्टरी (`loadAgentsFromDir`):\n\n- अपठनीय/गायब डायरेक्टरी: खाली माना जाता है (`readdir(...).catch(() => [])`)\n- फ़ाइल पढ़ने या पार्स विफलता: चेतावनी लॉग की जाती है, फ़ाइल छोड़ दी जाती है\n- पार्स पथ `parseAgent(..., level: \"warn\")` का उपयोग करता है\n\nFrontmatter विफलता व्यवहार `parseFrontmatter` से आता है:\n\n- `warn` स्तर पर पार्स त्रुटि चेतावनी लॉग करती है\n- पार्सर एक सरल `key: value` लाइन पार्सर पर फ़ॉलबैक करता है\n- यदि आवश्यक फ़ील्ड अभी भी गायब हैं, तो `parseAgentFields` विफल हो जाता है, फिर `AgentParsingError` थ्रो किया जाता है और कॉलर द्वारा पकड़ा जाता है (फ़ाइल छोड़ दी जाती है)\n\nकुल प्रभाव: एक खराब कस्टम एजेंट फ़ाइल अन्य फ़ाइलों की डिस्कवरी को रोकती नहीं है।\n\n## एजेंट लुकअप और सिलेक्शन\n\nलुकअप सटीक-नाम लीनियर सर्च है:\n\n- `getAgent(agents, name)` => `agents.find(a => a.name === name)`\n\nटास्क एक्सीक्यूशन में (`TaskTool.execute`):\n\n1. कॉल समय पर एजेंट्स फिर से खोजे जाते हैं (`discoverAgents(this.session.cwd)`)\n2. अनुरोधित `params.agent` को `getAgent` के माध्यम से रिज़ॉल्व किया जाता है\n3. गायब एजेंट तत्काल टूल प्रतिक्रिया लौटाता है:\n   - `Unknown agent \"...\". Available: ...`\n   - कोई सबप्रोसेस नहीं चलता\n\n### विवरण बनाम एक्सीक्यूशन-समय डिस्कवरी\n\n`TaskTool.create()` इनिशियलाइज़ेशन समय पर डिस्कवरी परिणामों से टूल विवरण बनाता है (`buildDescription`)।\n\n`execute()` फिर से एजेंट्स खोजता है। इसलिए रनटाइम सेट पहले के टूल विवरण में सूचीबद्ध से भिन्न हो सकता है यदि सत्र के बीच एजेंट फ़ाइलें बदल गई हों।\n\n## स्ट्रक्चर्ड-आउटपुट गार्डरेल्स और स्कीमा प्रेसीडेंस\n\n`TaskTool.execute` में रनटाइम आउटपुट स्कीमा प्रेसीडेंस:\n\n1. एजेंट frontmatter `output`\n2. टास्क कॉल `params.schema`\n3. पैरेंट सेशन `outputSchema`\n\n(`effectiveOutputSchema = effectiveAgent.output ?? outputSchema ?? this.session.outputSchema`)\n\n`src/prompts/tools/task.md` में प्रॉम्प्ट-समय गार्डरेल टेक्स्ट स्ट्रक्चर्ड-आउटपुट एजेंट्स (`explore`, `reviewer`) के लिए मिसमैच व्यवहार के बारे में चेतावनी देता है: प्रोज़ में आउटपुट-फ़ॉर्मेट निर्देश बिल्ट-इन स्कीमा के साथ विरोध कर सकते हैं और `null` आउटपुट उत्पन्न कर सकते हैं।\n\nयह मार्गदर्शन है, `discoverAgents` में कठोर रनटाइम वैलिडेशन लॉजिक नहीं।\n\n## कमांड डिस्कवरी इंटरैक्शन\n\n`src/task/commands.ts` वर्कफ़्लो कमांड्स (एजेंट डेफिनिशन्स नहीं) के लिए समानांतर इन्फ्रास्ट्रक्चर है, लेकिन यह समग्र रूप से समान पैटर्न का अनुसरण करता है:\n\n- पहले कैपेबिलिटी प्रोवाइडर्स से खोजें\n- first-wins के साथ नाम द्वारा डीडुप्लिकेट करें\n- यदि अभी भी नहीं देखे गए हों तो बंडल्ड कमांड्स जोड़ें\n- `getCommand` के माध्यम से सटीक-नाम लुकअप\n\n`src/task/index.ts` में, कमांड हेल्पर्स एजेंट डिस्कवरी हेल्पर्स के साथ री-एक्सपोर्ट किए जाते हैं। एजेंट डिस्कवरी स्वयं रनटाइम पर कमांड डिस्कवरी पर निर्भर नहीं करती।\n\n## डिस्कवरी से परे उपलब्धता बाधाएँ\n\nएक एजेंट खोजने योग्य हो सकता है लेकिन एक्सीक्यूशन गार्डरेल्स के कारण चलाने के लिए अभी भी अनुपलब्ध हो सकता है।\n\n### पैरेंट spawn पॉलिसी\n\n`TaskTool.execute` `session.getSessionSpawns()` की जाँच करता है:\n\n- `\"*\"` => किसी भी को अनुमति दें\n- `\"\"` => सभी को अस्वीकार करें\n- CSV सूची => केवल सूचीबद्ध नामों को अनुमति दें\n\nयदि अस्वीकृत: तत्काल `Cannot spawn '...'. Allowed: ...` प्रतिक्रिया।\n\n### ब्लॉक्ड सेल्फ-रिकर्सन env गार्ड\n\n`PI_BLOCKED_AGENT` टूल कंस्ट्रक्शन पर पढ़ा जाता है। यदि अनुरोध मेल खाता है, तो रिकर्सन-रोकथाम संदेश के साथ एक्सीक्यूशन अस्वीकार किया जाता है।\n\n### रिकर्सन-डेप्थ गेटिंग (चाइल्ड सेशन्स के अंदर टास्क टूल उपलब्धता)\n\n`runSubprocess` (`src/task/executor.ts`) में:\n\n- डेप्थ `taskDepth` से गणना की जाती है\n- `task.maxRecursionDepth` कटऑफ़ नियंत्रित करता है\n- अधिकतम डेप्थ पर:\n  - `task` टूल चाइल्ड टूल सूची से हटा दिया जाता है\n  - चाइल्ड `spawns` env खाली पर सेट किया जाता है\n\nइसलिए गहरे स्तर आगे टास्क स्पॉन नहीं कर सकते, भले ही एजेंट डेफिनिशन में `spawns` शामिल हो।\n\n## प्लान मोड चेतावनी (वर्तमान इम्प्लीमेंटेशन)\n\n`TaskTool.execute` प्लान मोड के लिए एक `effectiveAgent` की गणना करता है (प्लान-मोड प्रॉम्प्ट प्रीपेंड करता है, रीड-ओनली टूल सबसेट फ़ोर्स करता है, spawns क्लियर करता है), लेकिन `runSubprocess` को `effectiveAgent` के बजाय `agent` के साथ कॉल किया जाता है।\n\nवर्तमान प्रभाव:\n\n- मॉडल ओवरराइड / थिंकिंग लेवल / आउटपुट स्कीमा `effectiveAgent` से प्राप्त होते हैं\n- `effectiveAgent` से सिस्टम प्रॉम्प्ट और टूल/spawn प्रतिबंध इस कॉल पथ में पास नहीं किए जाते\n\nयह एक इम्प्लीमेंटेशन चेतावनी है जिसे प्लान-मोड व्यवहार अपेक्षाओं को पढ़ते समय जानना उपयोगी है।\n",
	"hi/sessions/compaction.md": "---\ntitle: संक्षेपण और शाखा सारांश\ndescription: लंबे सत्रों के लिए संदर्भ विंडो संक्षेपण और शाखा सारांश निर्माण।\nsidebar:\n  order: 5\n  label: संक्षेपण\ni18n:\n  sourceHash: dae425a900d8\n  translator: machine\n---\n\n# संक्षेपण और शाखा सारांश\n\nसंक्षेपण और शाखा सारांश दो ऐसे तंत्र हैं जो लंबे सत्रों को पूर्व कार्य संदर्भ खोए बिना उपयोग योग्य बनाए रखते हैं।\n\n- **संक्षेपण** वर्तमान शाखा पर पुराने इतिहास को एक सारांश में पुनर्लेखित करता है।\n- **शाखा सारांश** `/tree` नेविगेशन के दौरान परित्यक्त शाखा संदर्भ को कैप्चर करता है।\n\nदोनों को सत्र प्रविष्टियों के रूप में संरक्षित किया जाता है और LLM इनपुट पुनर्निर्माण के दौरान उपयोगकर्ता-संदर्भ संदेशों में वापस परिवर्तित किया जाता है।\n\n## मुख्य कार्यान्वयन फ़ाइलें\n\n- `src/session/compaction/compaction.ts`\n- `src/session/compaction/branch-summarization.ts`\n- `src/session/compaction/pruning.ts`\n- `src/session/compaction/utils.ts`\n- `src/session/session-manager.ts`\n- `src/session/agent-session.ts`\n- `src/session/messages.ts`\n- `src/extensibility/hooks/types.ts`\n- `src/config/settings-schema.ts`\n\n## सत्र प्रविष्टि मॉडल\n\nसंक्षेपण और शाखा सारांश प्रथम-श्रेणी की सत्र प्रविष्टियाँ हैं, न कि सामान्य असिस्टेंट/उपयोगकर्ता संदेश।\n\n- `CompactionEntry`\n  - `type: \"compaction\"`\n  - `summary`, वैकल्पिक `shortSummary`\n  - `firstKeptEntryId` (संक्षेपण सीमा)\n  - `tokensBefore`\n  - वैकल्पिक `details`, `preserveData`, `fromExtension`\n- `BranchSummaryEntry`\n  - `type: \"branch_summary\"`\n  - `fromId`, `summary`\n  - वैकल्पिक `details`, `fromExtension`\n\nजब संदर्भ पुनर्निर्मित होता है (`buildSessionContext`):\n\n1. सक्रिय पथ पर नवीनतम संक्षेपण एक `compactionSummary` संदेश में परिवर्तित होता है।\n2. `firstKeptEntryId` से संक्षेपण बिंदु तक की रखी गई प्रविष्टियाँ पुनः शामिल की जाती हैं।\n3. पथ पर बाद की प्रविष्टियाँ जोड़ी जाती हैं।\n4. `branch_summary` प्रविष्टियाँ `branchSummary` संदेशों में परिवर्तित होती हैं।\n5. `custom_message` प्रविष्टियाँ `custom` संदेशों में परिवर्तित होती हैं।\n\nवे कस्टम रोल्स तब `convertToLlm()` में स्थैतिक टेम्पलेट्स का उपयोग करके LLM-सामना करने वाले उपयोगकर्ता संदेशों में रूपांतरित होते हैं:\n\n- `prompts/compaction/compaction-summary-context.md`\n- `prompts/compaction/branch-summary-context.md`\n\n## संक्षेपण पाइपलाइन\n\n### ट्रिगर\n\nसंक्षेपण तीन तरीकों से चल सकता है:\n\n1. **मैन्युअल**: `/compact [instructions]` `AgentSession.compact(...)` को कॉल करता है।\n2. **स्वचालित ओवरफ़्लो रिकवरी**: एक असिस्टेंट त्रुटि के बाद जो संदर्भ ओवरफ़्लो से मेल खाती है।\n3. **स्वचालित थ्रेशोल्ड संक्षेपण**: एक सफल टर्न के बाद जब संदर्भ थ्रेशोल्ड से अधिक हो जाता है।\n\n### संक्षेपण आकार (दृश्य)\n\n```text\nBefore compaction:\n\n  entry:  0     1     2     3      4     5     6      7      8     9\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┘\n                └────────┬───────┘ └──────────────┬──────────────┘\n               messagesToSummarize            kept messages\n                                   ↑\n                          firstKeptEntryId (entry 4)\n\nAfter compaction (new entry appended):\n\n  entry:  0     1     2     3      4     5     6      7      8     9      10\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┬─────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │ cmp │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┴─────┘\n               └──────────┬──────┘ └──────────────────────┬───────────────────┘\n                 not sent to LLM                    sent to LLM\n                                                         ↑\n                                              starts from firstKeptEntryId\n\nWhat the LLM sees:\n\n  ┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐\n  │ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │\n  └────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘\n       ↑         ↑      └─────────────────┬────────────────┘\n    prompt   from cmp          messages from firstKeptEntryId\n```\n\n### ओवरफ़्लो-रिट्री बनाम थ्रेशोल्ड संक्षेपण\n\nदोनों स्वचालित पथ जानबूझकर अलग हैं:\n\n- **ओवरफ़्लो-रिट्री संक्षेपण**\n  - ट्रिगर: वर्तमान-मॉडल असिस्टेंट त्रुटि संदर्भ ओवरफ़्लो के रूप में पहचानी जाती है।\n  - विफल असिस्टेंट त्रुटि संदेश रिट्री से पहले सक्रिय एजेंट स्थिति से हटा दिया जाता है।\n  - ऑटो संक्षेपण `reason: \"overflow\"` और `willRetry: true` के साथ चलता है।\n  - सफलता पर, एजेंट संक्षेपण के बाद स्वतः जारी रहता है (`agent.continue()`)।\n\n- **थ्रेशोल्ड संक्षेपण**\n  - ट्रिगर: `contextTokens > contextWindow - compaction.reserveTokens`।\n  - `reason: \"threshold\"` और `willRetry: false` के साथ चलता है।\n  - सफलता पर, यदि `compaction.autoContinue !== false`, एक सिंथेटिक प्रॉम्प्ट इंजेक्ट करता है:\n    - `\"Continue if you have next steps.\"`\n\n### पूर्व-संक्षेपण प्रूनिंग\n\nसंक्षेपण जाँच से पहले, टूल-रिज़ल्ट प्रूनिंग चल सकती है (`pruneToolOutputs`)।\n\nडिफ़ॉल्ट प्रून नीति:\n\n- नवीनतम `40_000` टूल-आउटपुट टोकन सुरक्षित रखें।\n- कम से कम `20_000` कुल अनुमानित बचत की आवश्यकता।\n- `skill` या `read` से टूल परिणाम कभी न प्रून करें।\n\nप्रून किए गए टूल परिणाम इससे प्रतिस्थापित होते हैं:\n\n- `[Output truncated - N tokens]`\n\nयदि प्रूनिंग प्रविष्टियों को बदलती है, तो संक्षेपण निर्णयों से पहले सत्र स्टोरेज पुनर्लिखित होता है और एजेंट संदेश स्थिति ताज़ा होती है।\n\n### सीमा और कट-पॉइंट लॉजिक\n\n`prepareCompaction()` केवल अंतिम संक्षेपण प्रविष्टि (यदि कोई हो) के बाद से प्रविष्टियों पर विचार करता है।\n\n1. पिछला संक्षेपण इंडेक्स खोजें।\n2. `boundaryStart = prevCompactionIndex + 1` की गणना करें।\n3. उपलब्ध उपयोग अनुपात का उपयोग करके `keepRecentTokens` को अनुकूलित करें।\n4. सीमा विंडो पर `findCutPoint()` चलाएँ।\n\nमान्य कट पॉइंट में शामिल हैं:\n\n- रोल्स वाले संदेश प्रविष्टियाँ: `user`, `assistant`, `bashExecution`, `hookMessage`, `branchSummary`, `compactionSummary`\n- `custom_message` प्रविष्टियाँ\n- `branch_summary` प्रविष्टियाँ\n\nकठोर नियम: कभी भी `toolResult` पर न काटें।\n\nयदि कट पॉइंट से तुरंत पहले गैर-संदेश मेटाडेटा प्रविष्टियाँ हैं (`model_change`, `thinking_level_change`, लेबल आदि), तो उन्हें कट इंडेक्स को पीछे ले जाकर रखे गए क्षेत्र में खींचा जाता है जब तक कि कोई संदेश या संक्षेपण सीमा नहीं मिलती।\n\n### स्प्लिट-टर्न हैंडलिंग\n\nयदि कट पॉइंट यूज़र-टर्न स्टार्ट पर नहीं है, तो संक्षेपण इसे स्प्लिट टर्न के रूप में मानता है।\n\nटर्न स्टार्ट पहचान इन्हें यूज़र-टर्न सीमाओं के रूप में मानती है:\n\n- `message.role === \"user\"`\n- `message.role === \"bashExecution\"`\n- `custom_message` प्रविष्टि\n- `branch_summary` प्रविष्टि\n\nस्प्लिट-टर्न संक्षेपण दो सारांश उत्पन्न करता है:\n\n1. इतिहास सारांश (`messagesToSummarize`)\n2. टर्न-प्रीफ़िक्स सारांश (`turnPrefixMessages`)\n\nअंतिम संग्रहीत सारांश इस प्रकार मर्ज किया जाता है:\n\n```markdown\n<history summary>\n\n---\n\n**Turn Context (split turn):**\n\n<turn prefix summary>\n```\n\n### सारांश निर्माण\n\n`compact(...)` क्रमबद्ध वार्तालाप पाठ से सारांश बनाता है:\n\n1. `convertToLlm()` के माध्यम से संदेश परिवर्तित करें।\n2. `serializeConversation()` के साथ क्रमबद्ध करें।\n3. `<conversation>...</conversation>` में लपेटें।\n4. वैकल्पिक रूप से `<previous-summary>...</previous-summary>` शामिल करें।\n5. वैकल्पिक रूप से हुक संदर्भ को `<additional-context>` सूची के रूप में इंजेक्ट करें।\n6. `SUMMARIZATION_SYSTEM_PROMPT` के साथ सारांशीकरण प्रॉम्प्ट निष्पादित करें।\n\nप्रॉम्प्ट चयन:\n\n- पहला संक्षेपण: `compaction-summary.md`\n- पूर्व सारांश के साथ पुनरावृत्त संक्षेपण: `compaction-update-summary.md`\n- स्प्लिट-टर्न दूसरा पास: `compaction-turn-prefix.md`\n- संक्षिप्त UI सारांश: `compaction-short-summary.md`\n\nरिमोट सारांशीकरण मोड:\n\n- यदि `compaction.remoteEndpoint` सेट है, तो संक्षेपण POST करता है:\n  - `{ systemPrompt, prompt }`\n- कम से कम `{ summary }` युक्त JSON की अपेक्षा करता है।\n\n### सारांशों में फ़ाइल-ऑपरेशन संदर्भ\n\nसंक्षेपण असिस्टेंट टूल कॉल का उपयोग करके संचयी फ़ाइल गतिविधि ट्रैक करता है:\n\n- `read(path)` → रीड सेट\n- `write(path)` → संशोधित सेट\n- `edit(path)` → संशोधित सेट\n\nसंचयी व्यवहार:\n\n- पूर्व संक्षेपण विवरण केवल तब शामिल करता है जब पूर्व प्रविष्टि pi-जनरेटेड हो (`fromExtension !== true`)।\n- स्प्लिट टर्न में, टर्न-प्रीफ़िक्स फ़ाइल ऑपरेशन भी शामिल होते हैं।\n- `readFiles` में संशोधित फ़ाइलें शामिल नहीं होतीं।\n\nसारांश पाठ में प्रॉम्प्ट टेम्पलेट के माध्यम से फ़ाइल टैग जोड़े जाते हैं:\n\n```xml\n<read-files>\n...\n</read-files>\n<modified-files>\n...\n</modified-files>\n```\n\n### संरक्षित करना और पुनः लोड करना\n\nसारांश निर्माण (या हुक-प्रदत्त सारांश) के बाद, एजेंट सत्र:\n\n1. `appendCompaction(...)` के साथ `CompactionEntry` जोड़ता है।\n2. `buildSessionContext()` के माध्यम से संदर्भ पुनर्निर्मित करता है।\n3. लाइव एजेंट संदेशों को पुनर्निर्मित संदर्भ से प्रतिस्थापित करता है।\n4. `session_compact` हुक इवेंट उत्सर्जित करता है।\n\n## शाखा सारांशीकरण पाइपलाइन\n\nशाखा सारांशीकरण ट्री नेविगेशन से जुड़ा है, टोकन ओवरफ़्लो से नहीं।\n\n### ट्रिगर\n\n`navigateTree(...)` के दौरान:\n\n1. `collectEntriesForBranchSummary(...)` का उपयोग करके पुराने लीफ से सामान्य पूर्वज तक परित्यक्त प्रविष्टियाँ गणना करें।\n2. यदि कॉलर ने सारांश अनुरोध किया (`options.summarize`), लीफ स्विच करने से पहले सारांश उत्पन्न करें।\n3. यदि सारांश मौजूद है, `branchWithSummary(...)` का उपयोग करके इसे नेविगेशन लक्ष्य पर संलग्न करें।\n\nसंचालनात्मक रूप से यह आमतौर पर `/tree` प्रवाह द्वारा संचालित होता है जब `branchSummary.enabled` सक्षम हो।\n\n### शाखा स्विच आकार (दृश्य)\n\n```text\nTree before navigation:\n\n         ┌─ B ─ C ─ D (old leaf, being abandoned)\n    A ───┤\n         └─ E ─ F (target)\n\nCommon ancestor: A\nEntries to summarize: B, C, D\n\nAfter navigation with summary:\n\n         ┌─ B ─ C ─ D ─ [summary of B,C,D]\n    A ───┤\n         └─ E ─ F (new leaf)\n```\n\n### तैयारी और टोकन बजट\n\n`generateBranchSummary(...)` बजट की गणना इस प्रकार करता है:\n\n- `tokenBudget = model.contextWindow - branchSummary.reserveTokens`\n\n`prepareBranchEntries(...)` तब:\n\n1. पहला पास: सभी सारांशित प्रविष्टियों से संचयी फ़ाइल ऑपरेशन एकत्र करें, पूर्व pi-जनरेटेड `branch_summary` विवरण सहित।\n2. दूसरा पास: नवीनतम → पुरानी की ओर चलते हुए टोकन बजट पहुँचने तक संदेश जोड़ें।\n3. हालिया संदर्भ संरक्षित करना प्राथमिकता दें।\n4. निरंतरता के लिए बजट सीमा के पास बड़ी सारांश प्रविष्टियाँ अभी भी शामिल हो सकती हैं।\n\nसंक्षेपण प्रविष्टियाँ शाखा सारांशीकरण इनपुट के दौरान संदेशों (`compactionSummary`) के रूप में शामिल होती हैं।\n\n### सारांश निर्माण और दृढ़ता\n\nशाखा सारांशीकरण:\n\n1. चयनित संदेशों को परिवर्तित और क्रमबद्ध करता है।\n2. `<conversation>` में लपेटता है।\n3. आपूर्ति किए गए कस्टम निर्देश उपयोग करता है, अन्यथा `branch-summary.md`।\n4. `SUMMARIZATION_SYSTEM_PROMPT` के साथ सारांशीकरण मॉडल को कॉल करता है।\n5. `branch-summary-preamble.md` प्रीपेंड करता है।\n6. फ़ाइल-ऑपरेशन टैग जोड़ता है।\n\nपरिणाम वैकल्पिक विवरण (`readFiles`, `modifiedFiles`) के साथ `BranchSummaryEntry` के रूप में संग्रहीत होता है।\n\n## एक्सटेंशन और हुक टचपॉइंट\n\n### `session_before_compact`\n\nपूर्व-संक्षेपण हुक।\n\nकर सकता है:\n\n- संक्षेपण रद्द करना (`{ cancel: true }`)\n- पूर्ण कस्टम संक्षेपण पेलोड प्रदान करना (`{ compaction: CompactionResult }`)\n\n### `session.compacting`\n\nडिफ़ॉल्ट संक्षेपण के लिए प्रॉम्प्ट/संदर्भ अनुकूलन हुक।\n\nवापस कर सकता है:\n\n- `prompt` (बेस सारांश प्रॉम्प्ट ओवरराइड)\n- `context` (`<additional-context>` में इंजेक्ट की गई अतिरिक्त संदर्भ पंक्तियाँ)\n- `preserveData` (संक्षेपण प्रविष्टि पर संग्रहीत)\n\n### `session_compact`\n\nसहेजी गई `compactionEntry` और `fromExtension` फ्लैग के साथ पोस्ट-संक्षेपण अधिसूचना।\n\n### `session_before_tree`\n\nडिफ़ॉल्ट शाखा सारांश निर्माण से पहले ट्री नेविगेशन पर चलता है।\n\nकर सकता है:\n\n- नेविगेशन रद्द करना\n- कस्टम `{ summary: { summary, details } }` प्रदान करना जो उपयोगकर्ता द्वारा सारांशीकरण अनुरोध पर उपयोग होता है\n\n### `session_tree`\n\nनए/पुराने लीफ और वैकल्पिक सारांश प्रविष्टि को उजागर करने वाला पोस्ट-नेविगेशन इवेंट।\n\n## रनटाइम व्यवहार और विफलता सिमेंटिक्स\n\n- मैन्युअल संक्षेपण पहले वर्तमान एजेंट ऑपरेशन को निरस्त करता है।\n- `abortCompaction()` मैन्युअल और ऑटो-संक्षेपण दोनों कंट्रोलर्स को रद्द करता है।\n- ऑटो संक्षेपण UI/स्थिति अपडेट के लिए स्टार्ट/एंड सत्र इवेंट उत्सर्जित करता है।\n- ऑटो संक्षेपण कई मॉडल उम्मीदवारों का प्रयास कर सकता है और क्षणिक विफलताओं को पुनः प्रयास कर सकता है।\n- ओवरफ़्लो त्रुटियाँ सामान्य रिट्री पथ से बाहर हैं क्योंकि उन्हें संक्षेपण द्वारा संभाला जाता है।\n- यदि ऑटो-संक्षेपण विफल होता है:\n  - ओवरफ़्लो पथ `Context overflow recovery failed: ...` उत्सर्जित करता है\n  - थ्रेशोल्ड पथ `Auto-compaction failed: ...` उत्सर्जित करता है\n- शाखा सारांशीकरण को अबॉर्ट सिग्नल (जैसे, Escape) के माध्यम से रद्द किया जा सकता है, रद्द/निरस्त नेविगेशन परिणाम लौटाते हुए।\n\n## सेटिंग्स और डिफ़ॉल्ट\n\n`settings-schema.ts` से:\n\n- `compaction.enabled` = `true`\n- `compaction.reserveTokens` = `16384`\n- `compaction.keepRecentTokens` = `20000`\n- `compaction.autoContinue` = `true`\n- `compaction.remoteEndpoint` = `undefined`\n- `branchSummary.enabled` = `false`\n- `branchSummary.reserveTokens` = `16384`\n\nये मान रनटाइम पर `AgentSession` और संक्षेपण/शाखा सारांशीकरण मॉड्यूल द्वारा उपभोग किए जाते हैं।\n",
	"hi/sessions/handoff-generation-pipeline.md": "---\ntitle: हैंडऑफ़ जनरेशन पाइपलाइन\ndescription: टीम सहयोग के लिए पोर्टेबल सेशन सारांश बनाने हेतु हैंडऑफ़ जनरेशन पाइपलाइन।\nsidebar:\n  order: 8\n  label: हैंडऑफ़ पाइपलाइन\ni18n:\n  sourceHash: 03666084b5ac\n  translator: machine\n---\n\n# `/handoff` जनरेशन पाइपलाइन\n\nयह दस्तावेज़ बताता है कि coding-agent आज `/handoff` को कैसे लागू करता है: ट्रिगर पथ, जनरेशन प्रॉम्प्ट, कम्पलीशन कैप्चर, सेशन स्विच, और संदर्भ पुनःइंजेक्शन।\n\n## कार्यक्षेत्र\n\nइसमें शामिल है:\n\n- इंटरेक्टिव `/handoff` कमांड डिस्पैच\n- `AgentSession.handoff()` लाइफसाइकल और स्टेट ट्रांजिशन\n- हैंडऑफ़ आउटपुट को असिस्टेंट आउटपुट से कैसे कैप्चर किया जाता है\n- पुराने/नए सेशन हैंडऑफ़ डेटा को अलग-अलग तरह से कैसे पर्सिस्ट करते हैं\n- सफलता, रद्दीकरण, और विफलता के लिए UI व्यवहार\n\nइसमें शामिल नहीं है:\n\n- सामान्य ट्री नेविगेशन/ब्रांच इंटर्नल\n- गैर-हैंडऑफ़ सेशन कमांड (`/new`, `/fork`, `/resume`)\n\n## इम्प्लीमेंटेशन फ़ाइलें\n\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n\n## ट्रिगर पथ\n\n1. `/handoff` को बिल्टइन स्लैश कमांड मेटाडेटा (`slash-commands.ts`) में वैकल्पिक इनलाइन हिंट के साथ घोषित किया गया है: `[focus instructions]`।\n2. इंटरेक्टिव इनपुट हैंडलिंग (`InputController`) में, `/handoff` या `/handoff ...` से मेल खाने वाला सबमिट टेक्स्ट सामान्य प्रॉम्प्ट सबमिशन से पहले इंटरसेप्ट किया जाता है।\n3. एडिटर क्लियर किया जाता है और `handleHandoffCommand(customInstructions?)` को कॉल किया जाता है।\n4. `CommandController.handleHandoffCommand` वर्तमान एंट्री का उपयोग करके प्रीफ्लाइट गार्ड करता है:\n   - `type === \"message\"` एंट्री की गिनती करता है।\n   - यदि `< 2` है, तो चेतावनी देता है: `Nothing to hand off (no messages yet)` और वापस लौट जाता है।\n\nयही न्यूनतम-सामग्री गार्ड `AgentSession.handoff()` के अंदर फिर से मौजूद है और उल्लंघन होने पर एरर थ्रो करता है। यह UI और सेशन दोनों परतों पर सुरक्षा को डुप्लिकेट करता है।\n\n## एंड-टू-एंड लाइफसाइकल\n\n### 1) हैंडऑफ़ जनरेशन शुरू करना\n\n`AgentSession.handoff(customInstructions?)`:\n\n- वर्तमान ब्रांच एंट्री पढ़ता है (`sessionManager.getBranch()`)\n- न्यूनतम मैसेज काउंट सत्यापित करता है (`>= 2`)\n- `#handoffAbortController` बनाता है\n- एक संरचित हैंडऑफ़ दस्तावेज़ का अनुरोध करने वाला फिक्स्ड, इनलाइन प्रॉम्प्ट बनाता है (`Goal`, `Constraints & Preferences`, `Progress`, `Key Decisions`, `Critical Context`, `Next Steps`)\n- यदि कस्टम निर्देश प्रदान किए गए हैं तो `Additional focus: ...` जोड़ता है\n\nप्रॉम्प्ट इस प्रकार भेजा जाता है:\n\n```ts\nawait this.prompt(handoffPrompt, { expandPromptTemplates: false });\n```\n\n`expandPromptTemplates: false` इस आंतरिक इंस्ट्रक्शन पेलोड के स्लैश/प्रॉम्प्ट-टेम्पलेट विस्तार को रोकता है।\n\n### 2) कम्पलीशन कैप्चर करना\n\nप्रॉम्प्ट भेजने से पहले, `handoff()` सेशन इवेंट की सदस्यता लेता है और `agent_end` की प्रतीक्षा करता है।\n\n`agent_end` पर, यह सबसे हालिया `assistant` मैसेज के लिए पीछे स्कैन करके एजेंट स्टेट से हैंडऑफ़ टेक्स्ट निकालता है, फिर सभी `content` ब्लॉक जहाँ `type === \"text\"` हैं उन्हें `\\n` के साथ जोड़ता है।\n\nमहत्वपूर्ण निष्कर्षण मान्यताएँ:\n\n- केवल टेक्स्ट ब्लॉक का उपयोग किया जाता है; गैर-टेक्स्ट सामग्री को नज़रअंदाज़ किया जाता है।\n- यह मानता है कि नवीनतम असिस्टेंट मैसेज हैंडऑफ़ जनरेशन से संबंधित है।\n- यह मार्कडाउन सेक्शन को पार्स नहीं करता या फ़ॉर्मेट अनुपालन को सत्यापित नहीं करता।\n- यदि असिस्टेंट आउटपुट में कोई टेक्स्ट ब्लॉक नहीं है, तो हैंडऑफ़ को अनुपस्थित माना जाता है।\n\n### 3) रद्दीकरण जाँच\n\n`handoff()` `undefined` लौटाता है जब कोई भी शर्त पूरी होती है:\n\n- कोई हैंडऑफ़ टेक्स्ट कैप्चर नहीं हुआ, या\n- `#handoffAbortController.signal.aborted` सत्य है\n\nयह हमेशा `finally` में `#handoffAbortController` को क्लियर करता है।\n\n### 4) नया सेशन बनाना\n\nयदि टेक्स्ट कैप्चर हुआ और अबोर्ट नहीं हुआ:\n\n1. वर्तमान सेशन राइटर को फ्लश करें (`sessionManager.flush()`)\n2. एक बिल्कुल नया सेशन शुरू करें (`sessionManager.newSession()`)\n3. इन-मेमोरी एजेंट स्टेट रीसेट करें (`agent.reset()`)\n4. `agent.sessionId` को नए सेशन id से रीबाइंड करें\n5. क्यूड कॉन्टेक्स्ट एरे क्लियर करें (`#steeringMessages`, `#followUpMessages`, `#pendingNextTurnMessages`)\n6. टोडो रिमाइंडर काउंटर रीसेट करें\n\n`newSession()` एक नया हेडर और खाली एंट्री लिस्ट बनाता है (लीफ `null` पर रीसेट)। हैंडऑफ़ पथ में, कोई `parentSession` पास नहीं किया जाता।\n\n### 5) हैंडऑफ़-कॉन्टेक्स्ट इंजेक्शन\n\nजेनरेट किए गए हैंडऑफ़ दस्तावेज़ को रैप करके नए सेशन में `custom_message` एंट्री के रूप में जोड़ा जाता है:\n\n```text\n<handoff-context>\n...handoff text...\n</handoff-context>\n\nThe above is a handoff document from a previous session. Use this context to continue the work seamlessly.\n```\n\nइंसर्शन कॉल:\n\n```ts\nthis.sessionManager.appendCustomMessageEntry(\"handoff\", handoffContent, true);\n```\n\nसेमेंटिक्स:\n\n- `customType`: `\"handoff\"`\n- `display`: `true` (TUI रीबिल्ड में दृश्यमान)\n- एंट्री टाइप: `custom_message` (LLM कॉन्टेक्स्ट में भाग लेता है)\n\n### 6) सक्रिय एजेंट कॉन्टेक्स्ट रीबिल्ड करना\n\nइंजेक्शन के बाद:\n\n1. `sessionManager.buildSessionContext()` वर्तमान लीफ के लिए मैसेज लिस्ट रिज़ॉल्व करता है\n2. `agent.replaceMessages(sessionContext.messages)` इंजेक्टेड हैंडऑफ़ मैसेज को सक्रिय कॉन्टेक्स्ट बनाता है\n3. मेथड `{ document: handoffText }` लौटाता है\n\nइस बिंदु पर, नए सेशन में सक्रिय LLM कॉन्टेक्स्ट में पुराने ट्रांस्क्रिप्ट के बजाय इंजेक्टेड हैंडऑफ़ मैसेज होता है।\n\n## पर्सिस्टेंस मॉडल: पुराना सेशन बनाम नया सेशन\n\n### पुराना सेशन\n\nजनरेशन के दौरान, सामान्य मैसेज पर्सिस्टेंस सक्रिय रहती है। असिस्टेंट हैंडऑफ़ रिस्पॉन्स `message_end` पर एक नियमित `message` एंट्री के रूप में पर्सिस्ट किया जाता है।\n\nपरिणाम: मूल सेशन में दिखाई देने वाला जेनरेटेड हैंडऑफ़ ऐतिहासिक ट्रांस्क्रिप्ट के भाग के रूप में शामिल होता है।\n\n### नया सेशन\n\nसेशन रीसेट के बाद, हैंडऑफ़ को `customType: \"handoff\"` के साथ `custom_message` के रूप में पर्सिस्ट किया जाता है।\n\n`buildSessionContext()` इस एंट्री को `createCustomMessage(...)` के माध्यम से रनटाइम कस्टम/यूज़र-कॉन्टेक्स्ट मैसेज में कन्वर्ट करता है, इसलिए यह नए सेशन से भविष्य के प्रॉम्प्ट में शामिल होता है।\n\n## कंट्रोलर/UI व्यवहार\n\n`CommandController.handleHandoffCommand` व्यवहार:\n\n- `await session.handoff(customInstructions)` को कॉल करता है\n- यदि परिणाम `undefined` है: `showError(\"Handoff cancelled\")`\n- सफलता पर:\n  - `rebuildChatFromMessages()` (नया सेशन कॉन्टेक्स्ट लोड करता है, इंजेक्टेड हैंडऑफ़ सहित)\n  - स्टेटस लाइन और एडिटर टॉप बॉर्डर को इनवैलिडेट करता है\n  - टोडो रीलोड करता है\n  - सफलता चैट लाइन जोड़ता है: `New session started with handoff context`\n- एक्सेप्शन पर:\n  - यदि मैसेज `\"Handoff cancelled\"` है या एरर नाम `AbortError` है: `showError(\"Handoff cancelled\")`\n  - अन्यथा: `showError(\"Handoff failed: <message>\")`\n- अंत में रेंडर का अनुरोध करता है\n\n## रद्दीकरण सेमेंटिक्स (वर्तमान व्यवहार)\n\n### सेशन-स्तर रद्दीकरण प्रिमिटिव\n\n`AgentSession` एक्सपोज़ करता है:\n\n- `abortHandoff()` → `#handoffAbortController` को अबोर्ट करता है\n- `isGeneratingHandoff` → कंट्रोलर मौजूद रहने के दौरान true\n\nजब इस अबोर्ट पथ का उपयोग किया जाता है, तो हैंडऑफ़ सब्सक्राइबर `Error(\"Handoff cancelled\")` के साथ रिजेक्ट करता है, और कमांड कंट्रोलर इसे रद्दीकरण UI में मैप करता है।\n\n### इंटरेक्टिव `/handoff` पथ सीमा\n\nवर्तमान इंटरेक्टिव कंट्रोलर वायरिंग में, `/handoff` एक समर्पित Escape हैंडलर इंस्टॉल नहीं करता जो `abortHandoff()` को कॉल करे (कॉम्पेक्शन/ब्रांच-सारांश पथों के विपरीत जो अस्थायी रूप से `editor.onEscape` को ओवरराइड करते हैं)।\n\nव्यावहारिक प्रभाव:\n\n- सेशन-स्तर रद्दीकरण सहायता मौजूद है, लेकिन `/handoff` कमांड पथ में कोई हैंडऑफ़-विशिष्ट की-बाइंडिंग हुक नहीं है।\n- व्यापक एजेंट अबोर्ट पथों के माध्यम से उपयोगकर्ता व्यवधान अभी भी हो सकता है, लेकिन वह `abortHandoff()` द्वारा उपयोग किया जाने वाला वही स्पष्ट रद्दीकरण चैनल नहीं है।\n\n## अबोर्टेड बनाम विफल हैंडऑफ़\n\nवर्तमान UI वर्गीकरण:\n\n- **अबोर्टेड/रद्द**\n  - `abortHandoff()` पथ `\"Handoff cancelled\"` ट्रिगर करता है, या\n  - `AbortError` थ्रो होता है\n  - UI दिखाता है `Handoff cancelled`\n\n- **विफल**\n  - `handoff()` / प्रॉम्प्ट पाइपलाइन से कोई भी अन्य थ्रो किया गया एरर (मॉडल/API वैलिडेशन एरर, रनटाइम एक्सेप्शन, आदि)\n  - UI दिखाता है `Handoff failed: ...`\n\nअतिरिक्त बारीकियाँ: यदि जनरेशन पूरी होती है लेकिन कोई टेक्स्ट नहीं निकाला जाता, तो `handoff()` `undefined` लौटाता है और कंट्रोलर वर्तमान में **रद्द** रिपोर्ट करता है, **विफल** नहीं।\n\n## शॉर्ट-सेशन और न्यूनतम-सामग्री गार्डरेल\n\nदो गार्ड कम-सिग्नल हैंडऑफ़ को रोकते हैं:\n\n- UI परत (`handleHandoffCommand`): `< 2` मैसेज एंट्री के लिए चेतावनी देता है और जल्दी वापस लौटता है\n- सेशन परत (`handoff()`): उसी शर्त को एरर के रूप में थ्रो करता है\n\nयह खाली/लगभग-खाली हैंडऑफ़ कॉन्टेक्स्ट के साथ नया सेशन बनाने से बचाता है।\n\n## स्टेट ट्रांजिशन सारांश\n\nउच्च-स्तरीय स्टेट प्रवाह:\n\n1. इंटरेक्टिव स्लैश कमांड इंटरसेप्ट किया गया\n2. प्रीफ्लाइट मैसेज-काउंट गार्ड\n3. `#handoffAbortController` बनाया गया (`isGeneratingHandoff = true`)\n4. आंतरिक हैंडऑफ़ प्रॉम्प्ट सबमिट किया गया (चैट में सामान्य असिस्टेंट जनरेशन के रूप में दृश्यमान)\n5. `agent_end` पर, अंतिम असिस्टेंट टेक्स्ट निकाला गया\n6. यदि अनुपस्थित/अबोर्टेड → `undefined` लौटाएं या रद्दीकरण एरर पथ\n7. यदि उपस्थित:\n   - पुराना सेशन फ्लश करें\n   - नया खाली सेशन बनाएं\n   - रनटाइम क्यू/काउंटर रीसेट करें\n   - `custom_message(handoff)` जोड़ें\n   - सक्रिय एजेंट मैसेज रीबिल्ड और रिप्लेस करें\n8. कंट्रोलर चैट UI रीबिल्ड करता है और सफलता की घोषणा करता है\n9. `#handoffAbortController` क्लियर किया गया (`isGeneratingHandoff = false`)\n\n## ज्ञात मान्यताएँ और सीमाएँ\n\n- हैंडऑफ़ निष्कर्षण अनुमानिक है: \"अंतिम असिस्टेंट टेक्स्ट ब्लॉक\"; कोई संरचनात्मक सत्यापन नहीं।\n- कोई कठोर जाँच नहीं कि जेनरेट किया गया मार्कडाउन अनुरोधित सेक्शन फ़ॉर्मेट का पालन करता है।\n- अनुपस्थित निकाले गए टेक्स्ट को कंट्रोलर UX में रद्दीकरण के रूप में रिपोर्ट किया जाता है।\n- `/handoff` इंटरेक्टिव प्रवाह में वर्तमान में एक समर्पित Escape→`abortHandoff()` बाइंडिंग का अभाव है।\n- इस पथ द्वारा नया सेशन वंशावली मेटाडेटा (`parentSession`) सेट नहीं किया जाता।\n",
	"hi/sessions/memory.md": "---\ntitle: स्वायत्त मेमोरी\ndescription: >-\n  सत्रों के बीच उपयोगकर्ता प्राथमिकताओं, प्रोजेक्ट संदर्भ और फीडबैक को बनाए रखने\n  के लिए स्वायत्त मेमोरी प्रणाली।\nsidebar:\n  order: 7\n  label: स्वायत्त मेमोरी\ni18n:\n  sourceHash: 2aa9f516aa1e\n  translator: machine\n---\n\n# स्वायत्त मेमोरी\n\nसक्षम होने पर, एजेंट स्वचालित रूप से पिछले सत्रों से स्थायी ज्ञान निकालता है और प्रत्येक नए सत्र में एक संक्षिप्त सारांश इंजेक्ट करता है। समय के साथ यह एक प्रोजेक्ट-स्कोप्ड मेमोरी स्टोर बनाता है — तकनीकी निर्णय, बार-बार आने वाले वर्कफ़्लो, कठिनाइयाँ — जो बिना मैन्युअल प्रयास के आगे बढ़ता रहता है।\n\nडिफ़ॉल्ट रूप से अक्षम। `/settings` या `config.yml` के माध्यम से सक्षम करें:\n\n```yaml\nmemories:\n  enabled: true\n```\n\n## उपयोग\n\n### क्या इंजेक्ट होता है\n\nसत्र शुरू होने पर, यदि वर्तमान प्रोजेक्ट के लिए मेमोरी सारांश मौजूद है, तो इसे सिस्टम प्रॉम्प्ट में **Memory Guidance** ब्लॉक के रूप में इंजेक्ट किया जाता है। एजेंट को निर्देश दिया जाता है कि:\n\n- मेमोरी को अनुमानी संदर्भ के रूप में मानें — प्रक्रिया और पूर्व निर्णयों के लिए उपयोगी, वर्तमान रिपो स्थिति पर आधिकारिक नहीं।\n- जब मेमोरी योजना बदलती है तो मेमोरी आर्टिफैक्ट पथ का हवाला दें, और कार्य करने से पहले इसे वर्तमान-रिपो साक्ष्य के साथ जोड़ें।\n- जब रिपो स्थिति और उपयोगकर्ता निर्देश मेमोरी से विरोध करें तो उन्हें प्राथमिकता दें; विरोधी मेमोरी को पुराना मानें।\n\n### मेमोरी आर्टिफैक्ट्स पढ़ना\n\nएजेंट `read` टूल के साथ `memory://` URL का उपयोग करके सीधे मेमोरी फाइलें पढ़ सकता है:\n\n| URL | सामग्री |\n|---|---|\n| `memory://root` | स्टार्टअप पर इंजेक्ट किया गया संक्षिप्त सारांश |\n| `memory://root/MEMORY.md` | पूर्ण दीर्घकालिक मेमोरी दस्तावेज़ |\n| `memory://root/skills/<name>/SKILL.md` | एक जनरेट किया गया स्किल प्लेबुक |\n\n### `/memory` स्लैश कमांड\n\n| सबकमांड | प्रभाव |\n|---|---|\n| `view` | वर्तमान मेमोरी इंजेक्शन पेलोड दिखाएं |\n| `clear` / `reset` | सभी मेमोरी डेटा और जनरेट किए गए आर्टिफैक्ट्स हटाएं |\n| `enqueue` / `rebuild` | अगले स्टार्टअप पर कंसोलिडेशन चलाने के लिए बाध्य करें |\n\n## यह कैसे काम करता है\n\nमेमोरी एक बैकग्राउंड पाइपलाइन द्वारा बनाई जाती है जो स्टार्टअप पर या स्लैश कमांड के माध्यम से मैन्युअल रूप से ट्रिगर होती है।\n\n**चरण 1 — प्रति-सत्र निष्कर्षण:** प्रत्येक पिछले सत्र के लिए जो अंतिम प्रोसेसिंग के बाद से बदला है, एक मॉडल सत्र इतिहास पढ़ता है और स्थायी सिग्नल निकालता है: तकनीकी निर्णय, बाधाएं, हल की गई विफलताएं, बार-बार आने वाले वर्कफ़्लो। जो सत्र बहुत हाल के, बहुत पुराने, या वर्तमान में सक्रिय हैं, उन्हें छोड़ दिया जाता है। प्रत्येक निष्कर्षण एक रॉ मेमोरी ब्लॉक और उस सत्र के लिए एक संक्षिप्त सारांश उत्पन्न करता है।\n\n**चरण 2 — कंसोलिडेशन:** निष्कर्षण के बाद, एक दूसरा मॉडल पास सभी प्रति-सत्र निष्कर्षणों को पढ़ता है और तीन आउटपुट उत्पन्न करता है जो डिस्क पर लिखे जाते हैं:\n\n- `MEMORY.md` — एक क्यूरेटेड दीर्घकालिक मेमोरी दस्तावेज़\n- `memory_summary.md` — सत्र शुरू होने पर इंजेक्ट किया जाने वाला संक्षिप्त पाठ\n- `skills/` — पुन: प्रयोज्य प्रक्रियात्मक प्लेबुक, प्रत्येक अपनी उपनिर्देशिका में\n\nचरण 2 एक लीज़ का उपयोग करता है ताकि कई प्रक्रियाएं एक साथ शुरू होने पर डबल-रनिंग न हो। पिछले रन से बची पुरानी स्किल निर्देशिकाएं स्वचालित रूप से हटा दी जाती हैं।\n\nसभी आउटपुट डिस्क पर लिखने से पहले सीक्रेट्स के लिए स्कैन किए जाते हैं।\n\n### निष्कर्षण व्यवहार\n\nमेमोरी निष्कर्षण और कंसोलिडेशन व्यवहार पूरी तरह से `src/prompts/memories/` में स्थिर प्रॉम्प्ट फाइलों द्वारा संचालित होता है।\n\n| फाइल | उद्देश्य | वेरिएबल्स |\n|---|---|---|\n| `stage_one_system.md` | प्रति-सत्र निष्कर्षण के लिए सिस्टम प्रॉम्प्ट | — |\n| `stage_one_input.md` | सत्र सामग्री को रैप करने वाला यूज़र-टर्न टेम्पलेट | `{{thread_id}}`, `{{response_items_json}}` |\n| `consolidation.md` | क्रॉस-सत्र कंसोलिडेशन के लिए प्रॉम्प्ट | `{{raw_memories}}`, `{{rollout_summaries}}` |\n| `read_path.md` | लाइव सत्रों में इंजेक्ट की जाने वाली मेमोरी गाइडेंस | `{{memory_summary}}` |\n\n### मॉडल चयन\n\nमेमोरी मॉडल रोल सिस्टम का उपयोग करती है।\n\n| चरण | रोल | उद्देश्य |\n|---|---|---|\n| चरण 1 (निष्कर्षण) | `default` | प्रति-सत्र ज्ञान निष्कर्षण |\n| चरण 2 (कंसोलिडेशन) | `smol` | क्रॉस-सत्र संश्लेषण |\n\nयदि `smol` कॉन्फ़िगर नहीं है, तो चरण 2 `default` रोल पर फॉलबैक करता है।\n\n## कॉन्फ़िगरेशन\n\n| सेटिंग | डिफ़ॉल्ट | विवरण |\n|---|---|---|\n| `memories.enabled` | `false` | मास्टर स्विच |\n| `memories.maxRolloutAgeDays` | `30` | इससे पुराने सत्र प्रोसेस नहीं किए जाते |\n| `memories.minRolloutIdleHours` | `12` | इससे अधिक हाल में सक्रिय सत्र छोड़ दिए जाते हैं |\n| `memories.maxRolloutsPerStartup` | `64` | एक स्टार्टअप में प्रोसेस किए जाने वाले सत्रों की सीमा |\n| `memories.summaryInjectionTokenLimit` | `5000` | सिस्टम प्रॉम्प्ट में इंजेक्ट किए जाने वाले सारांश के अधिकतम टोकन |\n\nउन्नत उपयोग के लिए कॉन्फ़िग में अतिरिक्त ट्यूनिंग नॉब्स (कंकरेंसी, लीज़ अवधि, टोकन बजट) उपलब्ध हैं।\n\n## मुख्य फाइलें\n\n- `src/memories/index.ts` — पाइपलाइन ऑर्केस्ट्रेशन, इंजेक्शन, स्लैश कमांड हैंडलिंग\n- `src/memories/storage.ts` — SQLite-समर्थित जॉब क्यू और थ्रेड रजिस्ट्री\n- `src/prompts/memories/` — मेमोरी प्रॉम्प्ट टेम्पलेट्स\n- `src/internal-urls/memory-protocol.ts` — `memory://` URL हैंडलर\n",
	"hi/sessions/non-compaction-retry-policy.md": "---\ntitle: गैर-कॉम्पैक्शन ऑटो-रिट्राई नीति\ndescription: कॉम्पैक्शन पथ के बाहर क्षणिक API विफलताओं के लिए ऑटो-रिट्राई नीति।\nsidebar:\n  order: 6\n  label: रिट्राई नीति\ni18n:\n  sourceHash: 8999a0258dd8\n  translator: machine\n---\n\n# गैर-कॉम्पैक्शन ऑटो-रिट्राई नीति\n\nयह दस्तावेज़ `AgentSession` में मानक API-त्रुटि रिट्राई पथ का वर्णन करता है।\n\nयह स्पष्ट रूप से ऑटो-कॉम्पैक्शन के माध्यम से कॉन्टेक्स्ट-ओवरफ्लो पुनर्प्राप्ति को बाहर करता है। ओवरफ्लो को कॉम्पैक्शन लॉजिक द्वारा संभाला जाता है और इसे अलग से [`compaction.md`](./compaction.md) में दस्तावेज़ीकृत किया गया है।\n\n## कार्यान्वयन फ़ाइलें\n\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/config/settings-schema.ts`](../../packages/coding-agent/src/config/settings-schema.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts)\n- [`../src/modes/rpc/rpc-client.ts`](../../packages/coding-agent/src/modes/rpc/rpc-client.ts)\n- [`../src/modes/rpc/rpc-types.ts`](../../packages/coding-agent/src/modes/rpc/rpc-types.ts)\n\n## कॉम्पैक्शन के साथ दायरे की सीमा\n\nरिट्राई और कॉम्पैक्शन दोनों को एक ही `agent_end` पथ से जाँचा जाता है, लेकिन इन्हें जानबूझकर अलग रखा गया है:\n\n1. `agent_end` अंतिम असिस्टेंट संदेश का निरीक्षण करता है।\n2. `#isRetryableError(...)` पहले चलता है।\n3. यदि रिट्राई शुरू किया जाता है, तो उस टर्न के लिए कॉम्पैक्शन जाँच छोड़ दी जाती है।\n4. कॉन्टेक्स्ट-ओवरफ्लो त्रुटियाँ रिट्राई वर्गीकरण से पूरी तरह बाहर हैं (`isContextOverflow(...)` रिट्राई को शॉर्ट-सर्किट करता है)।\n5. ओवरफ्लो इसलिए मानक रिट्राई के बजाय `#checkCompaction(...)` तक पहुँचता है।\n\nअर्थात्: ओवरलोड/रेट/सर्वर/नेटवर्क-शैली की विफलताएँ इस रिट्राई नीति का उपयोग करती हैं; कॉन्टेक्स्ट-विंडो ओवरफ्लो कॉम्पैक्शन पुनर्प्राप्ति का उपयोग करता है।\n\n## रिट्राई वर्गीकरण\n\n`#isRetryableError(...)` के लिए निम्नलिखित सभी शर्तें आवश्यक हैं:\n\n- असिस्टेंट का `stopReason === \"error\"`\n- `errorMessage` मौजूद हो\n- संदेश **कॉन्टेक्स्ट ओवरफ्लो नहीं** होना चाहिए\n- `errorMessage` `#isRetryableErrorMessage(...)` से मेल खाता हो\n\nवर्तमान रिट्राई-योग्य पैटर्न सेट (regex-आधारित):\n\n- overloaded\n- rate limit / usage limit / too many requests\n- HTTP-जैसे सर्वर वर्ग: 429, 500, 502, 503, 504\n- service unavailable / server error / internal error\n- connection error / fetch failed\n- `retry delay` शब्दावली\n\nयह स्ट्रिंग-पैटर्न वर्गीकरण है, न कि टाइप किए गए प्रोवाइडर त्रुटि कोड।\n\n## रिट्राई जीवनचक्र और स्थिति परिवर्तन\n\nरिट्राई द्वारा उपयोग की जाने वाली सेशन स्थिति:\n\n- `#retryAttempt: number` (`0` का अर्थ है निष्क्रिय)\n- `#retryPromise: Promise<void> | undefined` (इन-प्रोग्रेस रिट्राई जीवनचक्र को ट्रैक करता है)\n- `#retryResolve: (() => void) | undefined` (`#retryPromise` को रिज़ॉल्व करता है)\n- `#retryAbortController: AbortController | undefined` (बैकऑफ़ स्लीप को रद्द करता है)\n\nप्रवाह (`#handleRetryableError`):\n\n1. `retry` सेटिंग्स समूह पढ़ें।\n2. यदि `retry.enabled === false`, तुरंत रुकें (`false`, कोई रिट्राई शुरू नहीं)।\n3. `#retryAttempt` बढ़ाएँ।\n4. एक बार `#retryPromise` बनाएँ (एक चेन में पहला प्रयास)।\n5. यदि प्रयास `retry.maxRetries` से अधिक हो गया, अंतिम विफलता इवेंट भेजें और रुकें।\n6. देरी की गणना करें: `retry.baseDelayMs * 2^(attempt-1)`।\n7. usage-limit त्रुटियों के लिए, रिट्राई हिंट्स पार्स करें और auth storage (`markUsageLimitReached(...)`) को कॉल करें; यदि प्रोवाइडर/मॉडल स्विच सफल होता है, तो देरी को `0` पर सेट करें।\n8. `auto_retry_start` भेजें।\n9. एजेंट रनटाइम स्थिति से पिछला असिस्टेंट त्रुटि संदेश हटाएँ (persisted सेशन इतिहास में रखा जाता है)।\n10. abort सपोर्ट के साथ स्लीप करें।\n11. जागने पर, `setTimeout(..., 0)` के माध्यम से `agent.continue()` शेड्यूल करें।\n\n### रिट्राई काउंटर क्या रीसेट करता है\n\n`#retryAttempt` इन मामलों में `0` पर रीसेट होता है:\n\n- रिट्राई शुरू होने के बाद पहला सफल गैर-त्रुटि, गैर-अबोर्टेड असिस्टेंट संदेश (`auto_retry_end { success: true }` भेजता है)\n- बैकऑफ़ स्लीप के दौरान रिट्राई रद्द करना\n- अधिकतम रिट्राई अधिक होने का पथ\n\n`#retryPromise` रिट्राई चेन समाप्त होने पर (सफलता, रद्दीकरण, या अधिकतम-अधिक) `#resolveRetry()` के माध्यम से रिज़ॉल्व/क्लियर होता है।\n\n## बैकऑफ़ और अधिकतम-प्रयास अर्थशास्त्र\n\nसेटिंग्स:\n\n- `retry.enabled` (डिफ़ॉल्ट `true`)\n- `retry.maxRetries` (डिफ़ॉल्ट `3`)\n- `retry.baseDelayMs` (डिफ़ॉल्ट `2000`)\n\nप्रयास क्रमांकन:\n\n- अधिकतम-जाँच से पहले प्रयास काउंटर बढ़ाया जाता है\n- प्रारंभ इवेंट वर्तमान प्रयास (1-आधारित) का उपयोग करते हैं\n- अधिकतम-अधिक अंत इवेंट `attempt: this.#retryAttempt - 1` रिपोर्ट करता है (अंतिम प्रयास किया गया रिट्राई काउंट)\n\nडिफ़ॉल्ट सेटिंग्स के साथ बैकऑफ़ अनुक्रम:\n\n- प्रयास 1: 2000 ms\n- प्रयास 2: 4000 ms\n- प्रयास 3: 8000 ms\n\nदेरी ओवरराइड इनपुट केवल usage-limit हैंडलिंग पथ में उपयोग किए जाते हैं, और केवल auth-storage मॉडल/अकाउंट स्विचिंग निर्णय को प्रभावित करने के लिए। मुख्य गैर-कॉम्पैक्शन रिट्राई पथ में, बैकऑफ़ स्थानीय एक्सपोनेंशियल देरी रहती है जब तक स्विचिंग सफल न हो (`delayMs = 0`)।\n\n## अबोर्ट मैकेनिक्स\n\n### स्पष्ट रिट्राई अबोर्ट\n\n`abortRetry()`:\n\n- `#retryAbortController` को अबोर्ट करता है (यदि मौजूद हो)\n- रिट्राई प्रॉमिस को रिज़ॉल्व करता है (`#resolveRetry()`) ताकि अवेटर्स अनब्लॉक हों\n\nयदि स्लीप के दौरान अबोर्ट होता है, तो catch पथ भेजता है:\n\n- `auto_retry_end { success: false, finalError: \"Retry cancelled\" }`\n- प्रयास/कंट्रोलर रीसेट करता है\n\n### वैश्विक ऑपरेशन अबोर्ट इंटरैक्शन\n\n`abort()` सक्रिय एजेंट स्ट्रीम को अबोर्ट करने से पहले `abortRetry()` को कॉल करता है। यह सुनिश्चित करता है कि जब उपयोगकर्ता सामान्य अबोर्ट जारी करता है तो रिट्राई बैकऑफ़ रद्द हो जाता है।\n\n### TUI इंटरैक्शन\n\n`auto_retry_start` पर, EventController:\n\n- `Esc` हैंडलर को `session.abortRetry()` में बदलता है\n- लोडर टेक्स्ट रेंडर करता है: `Retrying (attempt/maxAttempts) in Ns… (esc to cancel)`\n\n`auto_retry_end` पर, यह पूर्व `Esc` हैंडलर पुनर्स्थापित करता है और लोडर स्थिति साफ़ करता है।\n\n## स्ट्रीमिंग और प्रॉम्प्ट पूर्णता व्यवहार\n\n`prompt()` अंततः `agent.prompt(...)` वापस आने के बाद `#waitForRetry()` पर प्रतीक्षा करता है।\n\nप्रभाव:\n\n- एक प्रॉम्प्ट कॉल तब तक पूरी तरह से रिज़ॉल्व नहीं होती जब तक कोई भी शुरू की गई रिट्राई चेन समाप्त न हो (सफलता/विफलता/रद्दीकरण)\n- रिट्राई जीवनचक्र एक तार्किक प्रॉम्प्ट निष्पादन सीमा का हिस्सा है\n\nयह कॉलर्स को रिट्राई हो रहे टर्न को बहुत जल्दी पूर्ण मानने से रोकता है।\n\n## नियंत्रण: सेटिंग्स और RPC\n\n### कॉन्फ़िगरेशन नॉब्स\n\nretry समूह के तहत सेटिंग्स स्कीमा में परिभाषित:\n\n- `retry.enabled`\n- `retry.maxRetries`\n- `retry.baseDelayMs`\n\nसेशन में प्रोग्रामेटिक टॉगल:\n\n- `setAutoRetryEnabled(enabled)` `retry.enabled` लिखता है\n- `autoRetryEnabled` `retry.enabled` पढ़ता है\n- `isRetrying` रिपोर्ट करता है कि रिट्राई जीवनचक्र प्रॉमिस सक्रिय है या नहीं\n\n### RPC नियंत्रण\n\nRPC कमांड सर्फेस:\n\n- `set_auto_retry` → `session.setAutoRetryEnabled(command.enabled)`\n- `abort_retry` → `session.abortRetry()`\n\nक्लाइंट हेल्पर:\n\n- `RpcClient.setAutoRetry(enabled)`\n- `RpcClient.abortRetry()`\n\nदोनों कमांड सफलता प्रतिक्रियाएँ लौटाते हैं; रिट्राई प्रगति/विफलता विवरण स्ट्रीम किए गए सेशन इवेंट से आते हैं, कमांड प्रतिक्रिया पेलोड से नहीं।\n\n## इवेंट उत्सर्जन और विफलता सर्फेसिंग\n\nसेशन-स्तरीय रिट्राई इवेंट:\n\n- `auto_retry_start { attempt, maxAttempts, delayMs, errorMessage }`\n- `auto_retry_end { success, attempt, finalError? }`\n\nप्रचार:\n\n- `AgentSession.subscribe(...)` के माध्यम से भेजे गए\n- एक्सटेंशन रनर को एक्सटेंशन इवेंट के रूप में अग्रेषित\n- RPC मोड में, सीधे JSON इवेंट ऑब्जेक्ट के रूप में अग्रेषित (`session.subscribe(event => output(event))`)\n- TUI में, लोडर/त्रुटि UI के लिए `EventController` द्वारा उपयोग\n\nअंतिम विफलता सर्फेसिंग:\n\n- अधिकतम-अधिक या रद्दीकरण पर, `auto_retry_end.success === false`\n- TUI दिखाता है: `Retry failed after N attempts: <finalError>`\n- एक्सटेंशन/हुक उन्हीं फ़ील्ड के साथ `auto_retry_end` प्राप्त करते हैं\n- RPC उपभोक्ता stdout स्ट्रीम पर वही इवेंट ऑब्जेक्ट प्राप्त करते हैं\n\n## स्थायी रोक की शर्तें\n\nरिट्राई रुक जाता है और इनमें से कोई भी होने पर स्वतः जारी नहीं रहेगा:\n\n- `retry.enabled` false है\n- त्रुटि रिट्राई-वर्गीकृत नहीं है\n- त्रुटि कॉन्टेक्स्ट ओवरफ्लो है (कॉम्पैक्शन पथ को सौंपी गई)\n- अधिकतम रिट्राई अधिक हो गया\n- उपयोगकर्ता रिट्राई रद्द करता है (`abort_retry` या रिट्राई लोडर के दौरान `Esc`)\n- वैश्विक अबोर्ट (`abort`) पहले रिट्राई रद्द करता है\n\nकाउंटर रीसेट होने के बाद भविष्य की रिट्राई-योग्य त्रुटि पर एक नई रिट्राई चेन अभी भी शुरू हो सकती है।\n\n## परिचालन चेतावनियाँ\n\n- वर्गीकरण regex टेक्स्ट मिलान है; प्रोवाइडर-विशिष्ट संरचित त्रुटियाँ यहाँ उपयोग नहीं की जाती हैं।\n- रिट्राई **रनटाइम कॉन्टेक्स्ट** से विफल असिस्टेंट त्रुटि को re-continue से पहले हटा देता है, लेकिन सेशन इतिहास में वह त्रुटि प्रविष्टि बनी रहती है।\n- `RpcSessionState` वर्तमान में `autoCompactionEnabled` को उजागर करता है लेकिन `autoRetryEnabled` फ़ील्ड नहीं; RPC कॉलर को अपनी टॉगल स्थिति स्वयं ट्रैक करनी होगी या अन्य API के माध्यम से सेटिंग्स क्वेरी करनी होगी।\n",
	"hi/sessions/session-operations-export-share-fork-resume.md": "---\ntitle: 'सत्र संचालन: Export, Dump, Share, Fork, Resume'\ndescription: 'वार्तालापों को निर्यात, साझा, फोर्क और पुनः आरंभ करने के लिए सत्र संचालन।'\nsidebar:\n  order: 3\n  label: संचालन\ni18n:\n  sourceHash: e3c210b29c3e\n  translator: machine\n---\n\n# सत्र संचालन: export, dump, share, fork, resume/continue\n\nयह दस्तावेज़ वर्तमान में कार्यान्वित सत्र export/share/fork/resume संचालनों के लिए ऑपरेटर-दृश्य व्यवहार का वर्णन करता है।\n\n## कार्यान्वयन फ़ाइलें\n\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/export/html/index.ts`](../../packages/coding-agent/src/export/html/index.ts)\n- [`../src/export/custom-share.ts`](../../packages/coding-agent/src/export/custom-share.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n\n## संचालन मैट्रिक्स\n\n| संचालन | प्रवेश पथ | सत्र परिवर्तन | सत्र फ़ाइल निर्माण/स्विच | आउटपुट आर्टिफैक्ट |\n|---|---|---|---|---|\n| `/dump` | इंटरैक्टिव स्लैश कमांड | नहीं | नहीं | क्लिपबोर्ड टेक्स्ट |\n| `/export [path]` | इंटरैक्टिव स्लैश कमांड | नहीं | नहीं | HTML फ़ाइल |\n| `--export <session.jsonl> [outputPath]` | CLI स्टार्टअप फास्ट-पाथ | कोई रनटाइम सत्र परिवर्तन नहीं | कोई सक्रिय सत्र नहीं; लक्ष्य फ़ाइल पढ़ता है | HTML फ़ाइल |\n| `/share` | इंटरैक्टिव स्लैश कमांड | नहीं | नहीं | अस्थायी HTML + share URL/gist |\n| `/fork` | इंटरैक्टिव स्लैश कमांड | हाँ (सक्रिय सत्र पहचान बदलती है) | नई सत्र फ़ाइल बनाता है और वर्तमान सत्र को उसमें स्विच करता है (केवल persistent मोड) | जब मौजूद हो तो आर्टिफैक्ट डायरेक्टरी को नए सत्र नेमस्पेस में कॉपी करता है |\n| `/resume` | इंटरैक्टिव स्लैश कमांड | हाँ (सक्रिय इन-मेमोरी स्थिति प्रतिस्थापित) | चयनित मौजूदा सत्र फ़ाइल पर स्विच करता है | कोई नहीं |\n| `--resume` | CLI स्टार्टअप (पिकर) | सत्र निर्माण के बाद हाँ | चयनित मौजूदा सत्र फ़ाइल खोलता है | कोई नहीं |\n| `--resume <id\\|path>` | CLI स्टार्टअप | सत्र निर्माण के बाद हाँ | मौजूदा सत्र खोलता है; क्रॉस-प्रोजेक्ट केस वर्तमान प्रोजेक्ट में फोर्क कर सकता है | कोई नहीं |\n| `--continue` | CLI स्टार्टअप | सत्र निर्माण के बाद हाँ | टर्मिनल ब्रेडक्रम्ब या सबसे हालिया सत्र खोलता है; यदि कोई मौजूद नहीं है तो नया बनाता है | कोई नहीं |\n\n## Export और dump\n\n### `/export [outputPath]` (इंटरैक्टिव)\n\nप्रवाह:\n\n1. `InputController` `/export...` को `CommandController.handleExportCommand` पर रूट करता है।\n2. कमांड व्हाइटस्पेस पर विभाजित होता है और `/export` के बाद केवल पहले आर्गुमेंट को `outputPath` के रूप में उपयोग करता है।\n3. `AgentSession.exportToHtml()` `exportSessionToHtml(sessionManager, state, { outputPath, themeName })` को कॉल करता है।\n4. सफलता पर, UI पथ दिखाता है और फ़ाइल को ब्राउज़र में खोलता है।\n\nव्यवहार विवरण:\n\n- `--copy`, `clipboard`, और `copy` आर्गुमेंट स्पष्ट रूप से `/dump` उपयोग करने की चेतावनी के साथ अस्वीकृत किए जाते हैं।\n- Export सत्र हेडर/एंट्री/लीफ के साथ-साथ वर्तमान `systemPrompt` और एजेंट स्थिति से टूल विवरण एम्बेड करता है।\n- Export के दौरान कोई सत्र एंट्री जोड़ी नहीं जाती।\n\nचेतावनी:\n\n- आर्गुमेंट पार्सिंग व्हाइटस्पेस-आधारित (`text.split(/\\s+/)`) है, इसलिए स्पेस वाले उद्धृत पथ इस कमांड पथ द्वारा एकल पथ के रूप में संरक्षित नहीं होते।\n\n### `--export <inputSessionFile> [outputPath]` (CLI)\n\n`main.ts` में प्रवाह:\n\n1. जल्दी संभाला जाता है (इंटरैक्टिव/सत्र स्टार्टअप से पहले)।\n2. `exportFromFile(inputPath, outputPath?)` को कॉल करता है।\n3. `SessionManager.open(inputPath)` एंट्रीज़ लोड करता है, फिर HTML उत्पन्न और लिखा जाता है।\n4. प्रक्रिया `Exported to: ...` प्रिंट करती है और बाहर निकलती है।\n\nव्यवहार विवरण:\n\n- अनुपस्थित इनपुट फ़ाइल `File not found: <path>` के रूप में प्रकट होती है।\n- यह पथ `AgentSession` नहीं बनाता और किसी चल रहे सत्र को परिवर्तित नहीं करता।\n\n### `/dump` (इंटरैक्टिव क्लिपबोर्ड export)\n\nप्रवाह:\n\n1. `CommandController.handleDumpCommand()` `session.formatSessionAsText()` को कॉल करता है।\n2. यदि खाली स्ट्रिंग है, तो `No messages to dump yet.` रिपोर्ट करता है।\n3. अन्यथा नेटिव `copyToClipboard` के माध्यम से क्लिपबोर्ड पर कॉपी करता है।\n\nDump सामग्री में शामिल है:\n\n- सिस्टम प्रॉम्प्ट\n- सक्रिय मॉडल/थिंकिंग स्तर\n- टूल परिभाषाएँ + पैरामीटर\n- उपयोगकर्ता/सहायक संदेश\n- थिंकिंग ब्लॉक और टूल कॉल\n- टूल परिणाम और निष्पादन ब्लॉक (`excludeFromContext` bash/python एंट्रीज़ को छोड़कर)\n- कस्टम/हुक/फ़ाइल मेंशन/ब्रांच सारांश/कॉम्पैक्शन सारांश एंट्रीज़\n\nडंपिंग द्वारा कोई सत्र स्थायित्व परिवर्तन नहीं किए जाते।\n\n## Share\n\n`/share` केवल इंटरैक्टिव है और हमेशा वर्तमान सत्र को एक अस्थायी HTML फ़ाइल में निर्यात करके शुरू होता है।\n\n### चरण 1: अस्थायी export\n\n- अस्थायी फ़ाइल पथ: `${os.tmpdir()}/${Snowflake.next()}.html`\n- `session.exportToHtml(tmpFile)` का उपयोग करता है\n- यदि export विफल होता है (विशेष रूप से इन-मेमोरी सत्रों में), share त्रुटि के साथ समाप्त होता है।\n\n### चरण 2: कस्टम share हैंडलर (यदि मौजूद हो)\n\n`loadCustomShare()` `~/.xcsh/agent` में पहले मौजूद उम्मीदवार की जाँच करता है:\n\n- `share.ts`\n- `share.js`\n- `share.mjs`\n\nआवश्यकताएँ:\n\n- मॉड्यूल को एक फ़ंक्शन `(htmlPath) => Promise<CustomShareResult | string | undefined>` डिफ़ॉल्ट-एक्सपोर्ट करना चाहिए।\n\nयदि मौजूद और वैध हो:\n\n- UI `Sharing...` लोडर स्थिति में प्रवेश करता है।\n- हैंडलर परिणाम व्याख्या:\n  - string => URL के रूप में माना जाता है, दिखाया और खोला जाता है\n  - object => `url` और/या `message` दिखाया जाता है; `url` खोला जाता है\n  - `undefined`/falsy => सामान्य `Session shared`\n- पूर्ण होने के बाद अस्थायी फ़ाइल हटा दी जाती है।\n\nमहत्वपूर्ण फ़ॉलबैक व्यवहार:\n\n- यदि कस्टम हैंडलर मौजूद है लेकिन लोडिंग विफल होती है, तो कमांड त्रुटि देता है और लौटता है।\n- यदि कस्टम हैंडलर निष्पादित होता है और थ्रो करता है, तो कमांड त्रुटि देता है और लौटता है।\n- दोनों विफलता मामलों में, यह GitHub gist पर **फ़ॉलबैक नहीं** करता।\n- Gist फ़ॉलबैक केवल तब होता है जब कोई कस्टम share स्क्रिप्ट मौजूद नहीं होती।\n\n### चरण 3: डिफ़ॉल्ट gist फ़ॉलबैक\n\nकेवल जब कोई कस्टम share हैंडलर नहीं मिलता:\n\n1. `gh auth status` को मान्य करता है।\n2. `Creating gist...` लोडर दिखाता है।\n3. `gh gist create --public=false <tmpFile>` चलाता है।\n4. Gist URL पार्स करता है, gist id प्राप्त करता है, पूर्वावलोकन URL `https://gistpreview.github.io/?<id>` बनाता है।\n5. पूर्वावलोकन और gist दोनों URL दिखाता है; पूर्वावलोकन खोलता है।\n\nShare में रद्दीकरण/abort सिमेंटिक्स:\n\n- लोडर में `onAbort` हुक होता है जो एडिटर UI को पुनर्स्थापित करता है और `Share cancelled` रिपोर्ट करता है।\n- अंतर्निहित `gh gist create` कमांड को इस कोड पथ में abort सिग्नल नहीं दिया जाता; रद्दीकरण UI-स्तर पर होता है और कमांड लौटने के बाद जाँचा जाता है।\n\n## Fork\n\n`/fork` वर्तमान सत्र से एक नया सत्र बनाता है और सक्रिय सत्र पहचान को स्विच करता है।\n\n### पूर्वशर्तें और तत्काल गार्ड\n\n- यदि एजेंट स्ट्रीमिंग कर रहा है, तो `/fork` चेतावनी के साथ अस्वीकृत किया जाता है।\n- संचालन से पहले UI स्थिति/लोडिंग संकेतक साफ़ किए जाते हैं।\n\n### सत्र-स्तरीय प्रवाह\n\n`AgentSession.fork()`:\n\n1. `reason: \"fork\"` के साथ `session_before_switch` उत्सर्जित करता है (रद्द करने योग्य)।\n2. लंबित लेखन फ्लश करता है।\n3. `SessionManager.fork()` कॉल करता है।\n4. पुराने सत्र नेमस्पेस से नए नेमस्पेस में आर्टिफैक्ट्स डायरेक्टरी कॉपी करता है (सर्वोत्तम प्रयास; गैर-ENOENT कॉपी विफलताएँ लॉग की जाती हैं, घातक नहीं)।\n5. `agent.sessionId` अपडेट करता है।\n6. `reason: \"fork\"` के साथ `session_switch` उत्सर्जित करता है।\n\n`SessionManager.fork()` व्यवहार:\n\n- Persistent मोड और मौजूदा सत्र फ़ाइल की आवश्यकता होती है।\n- नई सत्र id और नई JSONL फ़ाइल पथ बनाता है।\n- हेडर को फिर से लिखता है:\n  - नई `id`\n  - नया टाइमस्टैम्प\n  - `cwd` अपरिवर्तित\n  - `parentSession` पिछली सत्र id पर सेट\n- नई फ़ाइल में सभी गैर-हेडर एंट्रीज़ अपरिवर्तित रखता है।\n\n### गैर-persistent व्यवहार\n\n- इन-मेमोरी सत्र प्रबंधक `fork()` से `undefined` लौटाता है।\n- `AgentSession.fork()` `false` लौटाता है।\n- UI `Fork failed (session not persisted or cancelled)` रिपोर्ट करता है।\n\n## Resume और continue\n\n## इंटरैक्टिव `/resume`\n\nप्रवाह:\n\n1. `SessionManager.list(currentCwd, currentSessionDir)` के माध्यम से भरे गए सत्र सेलेक्टर को खोलता है।\n2. चयन पर, `SelectorController.handleResumeSession(sessionPath)` `session.switchSession(sessionPath)` कॉल करता है।\n3. UI चैट और todos को साफ़/पुनर्निर्माण करता है, फिर `Resumed session` रिपोर्ट करता है।\n\nनोट्स:\n\n- यह पिकर केवल वर्तमान सत्र डायरेक्टरी स्कोप में सत्रों को सूचीबद्ध करता है।\n- यह वैश्विक क्रॉस-प्रोजेक्ट खोज का उपयोग नहीं करता।\n\n## CLI `--resume`\n\n### `--resume` (बिना मान)\n\n- `main.ts` वर्तमान cwd/sessionDir के लिए सत्रों को सूचीबद्ध करता है और पिकर खोलता है।\n- चयनित पथ सत्र निर्माण से पहले `SessionManager.open(selectedPath)` के साथ खोला जाता है।\n\n### `--resume <value>`\n\n`createSessionManager()` रिज़ॉल्यूशन क्रम:\n\n1. यदि मान पथ जैसा दिखता है (`/`, `\\`, या `.jsonl`), सीधे खोलें।\n2. अन्यथा id उपसर्ग के रूप में माना जाता है:\n   - वर्तमान स्कोप में खोजें (`SessionManager.list(cwd, sessionDir)`)\n   - यदि नहीं मिला और कोई स्पष्ट `sessionDir` नहीं, वैश्विक में खोजें (`SessionManager.listAll()`)\n\nक्रॉस-प्रोजेक्ट id मैच व्यवहार:\n\n- यदि मिलान किए गए सत्र का cwd वर्तमान cwd से भिन्न है, CLI पूछता है:\n  - `Session found in different project ... Fork into current directory? [y/N]`\n- हाँ पर: `SessionManager.forkFrom(match.path, cwd, sessionDir)` एक नई स्थानीय फोर्क फ़ाइल बनाता है।\n- नहीं/गैर-TTY डिफ़ॉल्ट पर: कमांड त्रुटि देता है।\n\n## CLI `--continue`\n\n`SessionManager.continueRecent(cwd, sessionDir)`:\n\n1. वर्तमान cwd के लिए सत्र डायरेक्टरी रिज़ॉल्व करता है।\n2. पहले टर्मिनल-स्कोप्ड ब्रेडक्रम्ब पढ़ता है।\n3. सबसे हाल में संशोधित सत्र फ़ाइल पर फ़ॉलबैक करता है।\n4. पाया गया सत्र खोलता है; यदि कोई मौजूद नहीं है, तो नया सत्र बनाता है।\n\nयह केवल स्टार्टअप व्यवहार है; कोई इंटरैक्टिव `/continue` स्लैश कमांड नहीं है।\n\n## सत्र स्विचिंग वास्तव में रनटाइम स्थिति को कैसे बदलती है\n\n`AgentSession.switchSession(sessionPath)` resume-जैसे संचालनों द्वारा उपयोग किया जाने वाला रनटाइम संक्रमण करता है:\n\n1. `reason: \"resume\"` और `targetSessionFile` के साथ `session_before_switch` उत्सर्जित करता है (रद्द करने योग्य)।\n2. एजेंट इवेंट सदस्यता को डिस्कनेक्ट करता है और चल रहे कार्य को abort करता है।\n3. कतारबद्ध steering/follow-up/next-turn संदेशों को साफ़ करता है।\n4. वर्तमान सत्र प्रबंधक लेखन फ्लश करता है।\n5. `sessionManager.setSessionFile(sessionPath)` और `agent.sessionId` अपडेट करता है।\n6. लोड की गई एंट्रीज़ से सत्र संदर्भ बनाता है।\n7. `reason: \"resume\"` के साथ `session_switch` उत्सर्जित करता है।\n8. संदर्भ से एजेंट संदेशों को प्रतिस्थापित करता है।\n9. मॉडल को पुनर्स्थापित करता है (यदि वर्तमान रजिस्ट्री में उपलब्ध हो)।\n10. थिंकिंग स्तर को पुनर्स्थापित या आरंभ करता है।\n11. एजेंट इवेंट सदस्यता को पुनः कनेक्ट करता है।\n\n`switchSession()` स्वयं कोई नई सत्र फ़ाइल नहीं बनाता।\n\n## इवेंट उत्सर्जन और रद्दीकरण बिंदु\n\n### Switch/fork जीवनचक्र हुक\n\n`newSession`, `fork`, और `switchSession` के लिए:\n\n- पहले का इवेंट: `session_before_switch`\n  - कारण: `new`, `fork`, `resume`\n  - `{ cancel: true }` लौटाकर रद्द करने योग्य\n- बाद का इवेंट: `session_switch`\n  - समान कारण सेट\n  - `previousSessionFile` शामिल करता है\n\n`ExtensionRunner.emit()` पहले रद्द करने वाले before-event परिणाम पर जल्दी लौटता है।\n\n### कस्टम टूल `onSession` व्यवहार\n\nSDK ब्रिज एक्सटेंशन सत्र इवेंट को कस्टम टूल `onSession` कॉलबैक में:\n\n- `session_switch` -> `onSession({ reason: \"switch\", previousSessionFile })`\n- `session_branch` -> `reason: \"branch\"`\n- `session_start` -> `reason: \"start\"`\n- `session_tree` -> `reason: \"tree\"`\n- `session_shutdown` -> `reason: \"shutdown\"`\n\nये कॉलबैक अवलोकनात्मक हैं; ये switch/fork को रद्द नहीं करते।\n\n### इस दस्तावेज़ से संबंधित अन्य रद्दीकरण सतहें\n\n- `/fork` स्ट्रीमिंग के दौरान अवरुद्ध होता है (उपयोगकर्ता को पहले वर्तमान प्रतिक्रिया की प्रतीक्षा/abort करना होगा)।\n- `/resume` सेलेक्टर उपयोगकर्ता द्वारा सेलेक्टर बंद करने पर रद्द किया जा सकता है।\n- क्रॉस-प्रोजेक्ट `--resume <id>` फोर्क प्रॉम्प्ट अस्वीकार करके रद्द किया जा सकता है।\n- `/share` में gist प्रवाह के लिए UI abort पथ (`Share cancelled`) है; यह इस कोड पथ में `gh gist create` के लिए प्रोसेस-किल सिमेंटिक्स को वायर नहीं करता।\n\n## गैर-persistent (इन-मेमोरी) सत्र व्यवहार\n\nजब सत्र प्रबंधक `SessionManager.inMemory()` (`--no-session`) के साथ बनाया जाता है:\n\n- सत्र फ़ाइल पथ अनुपस्थित होता है।\n- `/export` और `/share` `Cannot export in-memory session to HTML` के साथ विफल होते हैं (कमांड त्रुटि UI में प्रचारित)।\n- `/fork` विफल होता है क्योंकि `SessionManager.fork()` को persistence की आवश्यकता होती है।\n- `/dump` अभी भी काम करता है क्योंकि यह इन-मेमोरी एजेंट स्थिति को क्रमबद्ध करता है।\n- CLI resume/continue सिमेंटिक्स बायपास होते हैं यदि `--no-session` सेट है, क्योंकि प्रबंधक निर्माण तुरंत इन-मेमोरी लौटाता है।\n\n## ज्ञात कार्यान्वयन चेतावनियाँ (वर्तमान कोड के अनुसार)\n\n- `SelectorController.handleResumeSession()` `session.switchSession(...)` से बूलियन परिणाम की जाँच नहीं करता; हुक-रद्द स्विच अभी भी UI \"Resumed session\" रीपेंट/स्थिति पथ के माध्यम से आगे बढ़ सकता है।\n- `/share` कस्टम-share विफलताएँ डिफ़ॉल्ट gist फ़ॉलबैक पर नहीं जातीं; वे कमांड को त्रुटि के साथ समाप्त करती हैं।\n- `/export` आर्गुमेंट टोकनाइज़ेशन सरलीकृत है और स्पेस वाले उद्धृत पथों को संरक्षित नहीं करता।\n",
	"hi/sessions/session-switching-and-recent-listing.md": "---\ntitle: सत्र स्विचिंग और हालिया सत्र सूची\ndescription: सत्र स्विचिंग तंत्र और खोज तथा फ़िल्टरिंग के साथ हालिया सत्र सूची।\nsidebar:\n  order: 4\n  label: स्विचिंग और हालिया\ni18n:\n  sourceHash: aae56130b508\n  translator: machine\n---\n\n# सत्र स्विचिंग और हालिया सत्र सूची\n\nयह दस्तावेज़ वर्णन करता है कि coding-agent हालिया सत्रों की खोज कैसे करता है, `--resume` लक्ष्यों को कैसे हल करता है, सत्र पिकर कैसे प्रस्तुत करता है, और सक्रिय रनटाइम सत्र कैसे स्विच करता है।\n\nयह वर्तमान कार्यान्वयन व्यवहार पर केंद्रित है, जिसमें फ़ॉलबैक पथ और चेतावनियाँ शामिल हैं।\n\n## कार्यान्वयन फ़ाइलें\n\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/cli/session-picker.ts`](../../packages/coding-agent/src/cli/session-picker.ts)\n- [`../src/modes/components/session-selector.ts`](../../packages/coding-agent/src/modes/components/session-selector.ts)\n- [`../src/modes/controllers/selector-controller.ts`](../../packages/coding-agent/src/modes/controllers/selector-controller.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n\n## हालिया सत्र खोज\n\n### डायरेक्टरी स्कोप\n\n`SessionManager` डिफ़ॉल्ट रूप से सत्रों को cwd-स्कोप्ड डायरेक्टरी में संग्रहीत करता है:\n\n- `~/.xcsh/agent/sessions/--<cwd-encoded>--/*.jsonl`\n\n`SessionManager.list(cwd, sessionDir?)` केवल उसी डायरेक्टरी को पढ़ता है जब तक कि एक स्पष्ट `sessionDir` प्रदान न किया जाए।\n\n### अलग-अलग पेलोड वाले दो सूचीकरण पथ\n\nदो अलग-अलग सूचीकरण पाइपलाइन हैं:\n\n1. `getRecentSessions(sessionDir, limit)` (स्वागत/सारांश दृश्य)\n   - प्रत्येक फ़ाइल से केवल 4KB प्रीफ़िक्स (`readTextPrefix(..., 4096)`) पढ़ता है।\n   - हेडर + सबसे पहले उपयोगकर्ता टेक्स्ट पूर्वावलोकन को पार्स करता है।\n   - लेज़ी `name` और `timeAgo` गेटर्स के साथ हल्का `RecentSessionInfo` लौटाता है।\n   - फ़ाइल `mtime` के अनुसार अवरोही क्रम में सॉर्ट करता है।\n\n2. `SessionManager.list(...)` / `SessionManager.listAll()` (रिज़्यूम पिकर और ID मिलान)\n   - पूरी सत्र फ़ाइलें पढ़ता है।\n   - `SessionInfo` ऑब्जेक्ट बनाता है (`id`, `cwd`, `title`, `messageCount`, `firstMessage`, `allMessagesText`, टाइमस्टैम्प)।\n   - शून्य `message` प्रविष्टियों वाले सत्रों को छोड़ देता है।\n   - `modified` के अनुसार अवरोही क्रम में सॉर्ट करता है।\n\n### मेटाडेटा फ़ॉलबैक व्यवहार\n\nहालिया सारांशों (`RecentSessionInfo`) के लिए:\n\n- प्रदर्शन नाम प्राथमिकता: `header.title` -> पहला उपयोगकर्ता प्रॉम्प्ट -> `header.id` -> फ़ाइलनाम\n- कॉम्पैक्ट प्रदर्शन के लिए नाम 40 अक्षरों तक काटा जाता है\n- शीर्षक-व्युत्पन्न नामों से नियंत्रण अक्षर/नई पंक्तियाँ हटा दी जाती हैं/सैनिटाइज़ की जाती हैं\n\n`SessionInfo` सूची प्रविष्टियों के लिए:\n\n- `title` है `header.title` या नवीनतम कंपैक्शन `shortSummary`\n- `firstMessage` पहला उपयोगकर्ता संदेश टेक्स्ट है या `\"(no messages)\"`\n\n## `--continue` रिज़ॉल्यूशन और टर्मिनल ब्रेडक्रम्ब प्राथमिकता\n\n`SessionManager.continueRecent(cwd, sessionDir?)` इस क्रम में लक्ष्य को हल करता है:\n\n1. टर्मिनल-स्कोप्ड ब्रेडक्रम्ब पढ़ें (`~/.xcsh/agent/terminal-sessions/<terminal-id>`)\n2. ब्रेडक्रम्ब सत्यापित करें:\n   - वर्तमान टर्मिनल की पहचान की जा सकती है\n   - ब्रेडक्रम्ब cwd वर्तमान cwd से मेल खाता है (रिज़ॉल्व्ड पथ तुलना)\n   - संदर्भित फ़ाइल अभी भी मौजूद है\n3. यदि ब्रेडक्रम्ब अमान्य/अनुपस्थित है, तो सत्र डायरेक्टरी में mtime के अनुसार सबसे नई फ़ाइल पर फ़ॉलबैक करें (`findMostRecentSession`)\n4. यदि कोई नहीं मिला, तो नया सत्र बनाएँ\n\nटर्मिनल ID व्युत्पन्न TTY पथ को प्राथमिकता देता है और env-आधारित पहचानकर्ताओं (`KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION`) पर फ़ॉलबैक करता है।\n\nब्रेडक्रम्ब लेखन सर्वोत्तम-प्रयास है और गैर-घातक है।\n\n## स्टार्टअप-टाइम रिज़्यूम लक्ष्य रिज़ॉल्यूशन (`main.ts`)\n\n### `--resume <value>`\n\n`createSessionManager(...)` स्ट्रिंग-मूल्य वाले `--resume` को दो मोड में संभालता है:\n\n1. पथ-जैसा मान (`/`, `\\\\` शामिल है, या `.jsonl` में समाप्त होता है)\n   - सीधे `SessionManager.open(sessionArg, parsed.sessionDir)`\n\n2. ID प्रीफ़िक्स मान\n   - `SessionManager.list(cwd, sessionDir)` में `id.startsWith(sessionArg)` द्वारा मिलान खोजें\n   - यदि कोई स्थानीय मिलान नहीं और `sessionDir` बाध्य नहीं है, तो `SessionManager.listAll()` आज़माएँ\n   - पहला मिलान उपयोग किया जाता है (कोई अस्पष्टता प्रॉम्प्ट नहीं)\n\nक्रॉस-प्रोजेक्ट मिलान व्यवहार:\n\n- यदि मिलान किए गए सत्र का cwd वर्तमान cwd से भिन्न है, तो CLI पूछता है कि वर्तमान प्रोजेक्ट में फ़ॉर्क करना है या नहीं\n- हाँ -> `SessionManager.forkFrom(...)`\n- नहीं -> त्रुटि फेंकता है (`Session \"...\" is in another project (...)`)\n\nकोई मिलान नहीं -> त्रुटि फेंकता है (`Session \"...\" not found.`)।\n\n### `--resume` (बिना मान)\n\nप्रारंभिक सत्र-प्रबंधक निर्माण के बाद संभाला जाता है:\n\n1. `SessionManager.list(cwd, parsed.sessionDir)` के साथ स्थानीय सत्रों की सूची बनाएँ\n2. यदि खाली: `No sessions found` प्रिंट करें और जल्दी बाहर निकलें\n3. TUI पिकर खोलें (`selectSession`)\n4. यदि रद्द किया: `No session selected` प्रिंट करें और जल्दी बाहर निकलें\n5. यदि चयनित: `SessionManager.open(selectedPath)`\n\n### `--continue`\n\nसीधे `SessionManager.continueRecent(...)` का उपयोग करता है (ऊपर ब्रेडक्रम्ब-प्रथम व्यवहार)।\n\n## पिकर-आधारित चयन आंतरिक विवरण\n\n## CLI पिकर (`src/cli/session-picker.ts`)\n\n`selectSession(sessions)` `SessionSelectorComponent` के साथ एक स्टैंडअलोन TUI बनाता है और ठीक एक बार हल करता है:\n\n- चयन -> चयनित पथ हल करता है\n- रद्द (Esc) -> `null` हल करता है\n- हार्ड एग्ज़िट (Ctrl+C पथ) -> TUI रोकता है और `process.exit(0)`\n\n## इंटरैक्टिव इन-सत्र पिकर (`SelectorController.showSessionSelector`)\n\nप्रवाह:\n\n1. `SessionManager.list(currentCwd, currentSessionDir)` के माध्यम से वर्तमान सत्र डायरेक्टरी से सत्र प्राप्त करें\n2. `showSelector(...)` का उपयोग करके एडिटर क्षेत्र में `SessionSelectorComponent` माउंट करें\n3. कॉलबैक:\n   - चयन -> सेलेक्टर बंद करें और `handleResumeSession(sessionPath)` कॉल करें\n   - रद्द -> एडिटर पुनर्स्थापित करें और रीरेंडर करें\n   - बाहर निकलें -> `ctx.shutdown()`\n\n## सत्र सेलेक्टर कम्पोनेंट व्यवहार\n\n`SessionList` समर्थन करता है:\n\n- ऐरो/पेज नेविगेशन\n- Enter चयन के लिए\n- Esc रद्द करने के लिए\n- Ctrl+C बाहर निकलने के लिए\n- सत्र id/title/cwd/first message/all messages/path में फ़ज़ी खोज\n\nखाली-सूची रेंडर व्यवहार:\n\n- क्रैश होने के बजाय एक संदेश रेंडर करता है\n- खाली पर Enter कुछ नहीं करता (कोई कॉलबैक नहीं)\n- Esc/Ctrl+C अभी भी काम करते हैं\n\nचेतावनी: UI टेक्स्ट कहता है `Press Tab to view all`, लेकिन इस कम्पोनेंट में वर्तमान में कोई Tab हैंडलर नहीं है और वर्तमान वायरिंग केवल वर्तमान-स्कोप सत्रों की सूची बनाती है।\n\n## रनटाइम स्विच निष्पादन (`AgentSession.switchSession`)\n\n`switchSession(sessionPath)` कोर इन-प्रोसेस स्विच पथ है।\n\nजीवनचक्र/स्थिति संक्रमण:\n\n1. `previousSessionFile` कैप्चर करें\n2. `session_before_switch` हुक इवेंट एमिट करें (`reason: \"resume\"`, रद्द करने योग्य)\n3. यदि रद्द किया गया -> बिना स्विच के `false` लौटाएँ\n4. वर्तमान एजेंट इवेंट स्ट्रीम से डिस्कनेक्ट करें\n5. सक्रिय जनरेशन/टूल फ़्लो एबॉर्ट करें\n6. कतारबद्ध स्टीयरिंग/फ़ॉलो-अप/अगला-टर्न संदेश बफ़र साफ़ करें\n7. लंबित लेखन को स्थायी करने के लिए सत्र राइटर फ़्लश करें (`sessionManager.flush()`)\n8. `sessionManager.setSessionFile(sessionPath)`\n   - सत्र फ़ाइल पॉइंटर अपडेट करता है\n   - टर्मिनल ब्रेडक्रम्ब लिखता है\n   - प्रविष्टियाँ लोड करता है / माइग्रेट करता है / ब्लॉब-रिज़ॉल्व करता है / रीइंडेक्स करता है\n   - यदि फ़ाइल डेटा अनुपस्थित/अमान्य है: उस पथ पर एक नया सत्र आरंभ करता है और हेडर पुनर्लिखित करता है\n9. `agent.sessionId` अपडेट करें\n10. `buildSessionContext()` के माध्यम से संदर्भ पुनर्निर्माण करें\n11. `session_switch` हुक इवेंट एमिट करें (`reason: \"resume\"`, `previousSessionFile`)\n12. पुनर्निर्मित संदर्भ के साथ एजेंट संदेश बदलें\n13. यदि उपलब्ध और मॉडल रजिस्ट्री में मौजूद है तो `sessionContext.models.default` से डिफ़ॉल्ट मॉडल पुनर्स्थापित करें\n14. थिंकिंग स्तर पुनर्स्थापित करें:\n    - यदि ब्रांच में पहले से `thinking_level_change` है, तो सहेजा गया सत्र स्तर लागू करें\n    - अन्यथा सेटिंग्स से डिफ़ॉल्ट थिंकिंग स्तर प्राप्त करें, मॉडल क्षमता तक सीमित करें, सेट करें, और एक नई `thinking_level_change` प्रविष्टि जोड़ें\n15. एजेंट श्रोताओं को पुनः कनेक्ट करें और `true` लौटाएँ\n\n## इंटरैक्टिव स्विच के बाद UI स्थिति पुनर्निर्माण\n\n`SelectorController.handleResumeSession` `switchSession` के आसपास UI रीसेट करता है:\n\n- लोडिंग एनिमेशन रोकें\n- स्थिति कंटेनर साफ़ करें\n- लंबित-संदेश UI और लंबित टूल मैप साफ़ करें\n- स्ट्रीमिंग कम्पोनेंट/संदेश संदर्भ रीसेट करें\n- `session.switchSession(...)` कॉल करें\n- चैट कंटेनर साफ़ करें और सत्र संदर्भ से रीरेंडर करें (`renderInitialMessages`)\n- नए सत्र आर्टिफ़ैक्ट्स से टूडू पुनः लोड करें\n- `Resumed session` दिखाएँ\n\nइसलिए दृश्य वार्तालाप/टूडू स्थिति नई सत्र फ़ाइल से पुनर्निर्मित होती है।\n\n## स्टार्टअप रिज़्यूम बनाम इन-सत्र स्विच\n\n### स्टार्टअप रिज़्यूम (`--continue`, `--resume`, सीधे खोलना)\n\n- `createAgentSession(...)` से पहले सत्र फ़ाइल चुनी जाती है।\n- `sdk.ts` `existingSession = sessionManager.buildSessionContext()` बनाता है।\n- सत्र निर्माण के दौरान एजेंट संदेश एक बार पुनर्स्थापित किए जाते हैं।\n- निर्माण के दौरान मॉडल/थिंकिंग का चयन किया जाता है (पुनर्स्थापना/फ़ॉलबैक तर्क सहित)।\n- इंटरैक्टिव मोड फिर स्थायी मोड स्थिति (वर्तमान में plan/plan_paused) में पुनः प्रवेश करने के लिए `#restoreModeFromSession()` चलाता है।\n\n### इन-सत्र स्विच (`/resume`-शैली सेलेक्टर पथ)\n\n- पहले से चल रहे `AgentSession` पर `AgentSession.switchSession(...)` का उपयोग करता है।\n- संदेश/मॉडल/थिंकिंग तुरंत यथास्थान पुनर्निर्मित किए जाते हैं।\n- हुक `session_before_switch`/`session_switch` इवेंट एमिट किए जाते हैं।\n- UI चैट/टूडू ताज़ा किए जाते हैं।\n- सेलेक्टर फ़्लो में कोई समर्पित पोस्ट-स्विच मोड पुनर्स्थापना कॉल नहीं किया जाता; मोड पुनः-प्रवेश व्यवहार स्टार्टअप `#restoreModeFromSession()` के साथ सममित नहीं है।\n\n## विफलता और एज-केस व्यवहार\n\n### रद्दीकरण पथ\n\n- CLI पिकर रद्द -> `null` लौटाता है, कॉलर `No session selected` प्रिंट करता है, प्रक्रिया जल्दी बाहर निकलती है।\n- इंटरैक्टिव पिकर रद्द -> एडिटर पुनर्स्थापित, कोई सत्र परिवर्तन नहीं।\n- हुक रद्दीकरण (`session_before_switch`) -> `switchSession()` `false` लौटाता है।\n\n### खाली सूची पथ\n\n- CLI `--resume` (बिना मान): खाली सूची `No sessions found` प्रिंट करती है और बाहर निकलती है।\n- इंटरैक्टिव सेलेक्टर: खाली सूची संदेश रेंडर करती है और रद्द करने योग्य रहती है।\n\n### अनुपस्थित/अमान्य लक्ष्य सत्र फ़ाइल\n\nकिसी विशिष्ट पथ पर खोलने/स्विच करने पर (`setSessionFile`):\n\n- ENOENT -> खाली माना जाता है -> उसी पथ पर नया सत्र आरंभ और स्थायी किया जाता है।\n- विकृत/अमान्य हेडर (या प्रभावी रूप से अपठनीय पार्स की गई प्रविष्टियाँ) -> खाली माना जाता है -> नया सत्र आरंभ और स्थायी किया जाता है।\n\nयह पुनर्प्राप्ति व्यवहार है, कठोर विफलता नहीं।\n\n### कठोर विफलताएँ\n\nस्विच/ओपन वास्तविक I/O विफलताओं (अनुमति त्रुटियाँ, पुनर्लेखन विफलताएँ, आदि) पर अभी भी त्रुटि फेंक सकता है, जो कॉलर्स तक प्रसारित होती हैं।\n\n### ID प्रीफ़िक्स मिलान चेतावनियाँ\n\n- ID मिलान `startsWith` का उपयोग करता है और सॉर्ट की गई सूची में पहला मिलान लेता है।\n- यदि एकाधिक सत्र प्रीफ़िक्स साझा करते हैं तो कोई अस्पष्टता UI नहीं।\n- `SessionManager.list(...)` शून्य संदेशों वाले सत्रों को बाहर कर देता है, इसलिए वे सत्र ID मिलान/सूची पिकर के माध्यम से पुनः प्रारंभ योग्य नहीं हैं।\n",
	"hi/sessions/session-tree-plan.md": "---\ntitle: सेशन ट्री आर्किटेक्चर\ndescription: >-\n  ब्रांचिंग, नेविगेशन, और पैरेंट-चाइल्ड कन्वर्सेशन संबंधों के साथ सेशन ट्री\n  आर्किटेक्चर।\nsidebar:\n  order: 2\n  label: ट्री आर्किटेक्चर\ni18n:\n  sourceHash: bd8b78d6c33a\n  translator: machine\n---\n\n# सेशन ट्री आर्किटेक्चर (वर्तमान)\n\nसंदर्भ: [session.md](./session.md)\n\nयह दस्तावेज़ बताता है कि सेशन ट्री नेविगेशन आज कैसे काम करता है: इन-मेमोरी ट्री मॉडल, लीफ मूवमेंट नियम, ब्रांचिंग व्यवहार, और एक्सटेंशन/इवेंट इंटीग्रेशन।\n\n## यह सबसिस्टम क्या है\n\nसेशन को एक append-only एंट्री लॉग के रूप में संग्रहीत किया जाता है, लेकिन रनटाइम व्यवहार ट्री-आधारित है:\n\n- प्रत्येक non-header एंट्री में `id` और `parentId` होता है।\n- सक्रिय स्थिति `SessionManager` में `leafId` है।\n- किसी एंट्री को append करना हमेशा वर्तमान लीफ का एक चाइल्ड बनाता है।\n- ब्रांचिंग इतिहास को **पुनर्लेखित नहीं** करती; यह केवल बदलती है कि अगले append से पहले लीफ कहाँ इंगित करती है।\n\nमुख्य फ़ाइलें:\n\n- `src/session/session-manager.ts` — ट्री डेटा मॉडल, ट्रैवर्सल, लीफ मूवमेंट, ब्रांच/सेशन एक्सट्रैक्शन\n- `src/session/agent-session.ts` — `/tree` नेविगेशन फ्लो, सारांशीकरण, हुक/इवेंट एमिशन\n- `src/modes/components/tree-selector.ts` — इंटरेक्टिव ट्री UI व्यवहार और फ़िल्टरिंग\n- `src/modes/controllers/selector-controller.ts` — `/tree` और `/branch` के लिए सेलेक्टर ऑर्केस्ट्रेशन\n- `src/modes/controllers/input-controller.ts` — कमांड रूटिंग (`/tree`, `/branch`, double-escape व्यवहार)\n- `src/session/messages.ts` — `branch_summary`, `compaction`, और `custom_message` एंट्रियों का LLM कॉन्टेक्स्ट मैसेजेस में रूपांतरण\n\n## `SessionManager` में ट्री डेटा मॉडल\n\nरनटाइम इंडेक्स:\n\n- `#byId: Map<string, SessionEntry>` — किसी भी एंट्री के लिए त्वरित लुकअप\n- `#leafId: string | null` — ट्री में वर्तमान स्थिति\n- `#labelsById: Map<string, string>` — टार्गेट एंट्री id द्वारा रिज़ॉल्व्ड लेबल\n\nट्री APIs:\n\n- `getBranch(fromId?)` पैरेंट लिंक से रूट तक चलता है और रूट→नोड पाथ लौटाता है\n- `getTree()` `SessionTreeNode[]` (`entry`, `children`, `label`) लौटाता है\n  - पैरेंट लिंक चिल्ड्रेन ऐरे बन जाते हैं\n  - गुम पैरेंट वाली एंट्रियों को रूट माना जाता है\n  - चिल्ड्रेन टाइमस्टैम्प के अनुसार oldest→newest क्रमबद्ध होते हैं\n- `getChildren(parentId)` प्रत्यक्ष चिल्ड्रेन लौटाता है\n- `getLabel(id)` `labelsById` से वर्तमान लेबल रिज़ॉल्व करता है\n\n`getTree()` एक रनटाइम प्रोजेक्शन है; परसिस्टेंस append-only JSONL एंट्रियाँ बनी रहती हैं।\n\n## लीफ मूवमेंट सिमेंटिक्स\n\nतीन लीफ मूवमेंट प्रिमिटिव हैं:\n\n1. `branch(entryId)`\n   - एंट्री के अस्तित्व को वैलिडेट करता है\n   - `leafId = entryId` सेट करता है\n   - कोई नई एंट्री नहीं लिखी जाती\n\n2. `resetLeaf()`\n   - `leafId = null` सेट करता है\n   - अगला append एक नई रूट एंट्री बनाता है (`parentId = null`)\n\n3. `branchWithSummary(branchFromId, summary, details?, fromExtension?)`\n   - `branchFromId: string | null` स्वीकार करता है\n   - `leafId = branchFromId` सेट करता है\n   - उस लीफ के चाइल्ड के रूप में एक `branch_summary` एंट्री append करता है\n   - जब `branchFromId` `null` हो, `fromId` को `\"root\"` के रूप में persisted किया जाता है\n\n## `/tree` नेविगेशन व्यवहार (समान सेशन फ़ाइल)\n\n`AgentSession.navigateTree()` नेविगेशन है, फ़ाइल फोर्किंग नहीं।\n\nफ्लो:\n\n1. टार्गेट को वैलिडेट करें और abandoned पाथ की गणना करें (`collectEntriesForBranchSummary`)\n2. `TreePreparation` के साथ `session_before_tree` एमिट करें\n3. वैकल्पिक रूप से abandoned एंट्रियों का सारांश बनाएं (हुक-प्रदत्त सारांश या बिल्ट-इन समराइज़र)\n4. नया लीफ टार्गेट कंप्यूट करें:\n   - एक **user** मैसेज चुनना: लीफ उसके पैरेंट पर जाती है, और मैसेज टेक्स्ट एडिटर प्रीफिल के लिए लौटाया जाता है\n   - एक **custom_message** चुनना: user मैसेज के समान नियम (लीफ = पैरेंट, टेक्स्ट एडिटर को प्रीफिल करता है)\n   - कोई अन्य एंट्री चुनना: लीफ = चयनित एंट्री id\n5. लीफ मूव लागू करें:\n   - सारांश के साथ: `branchWithSummary(newLeafId, ...)`\n   - सारांश के बिना और `newLeafId === null`: `resetLeaf()`\n   - अन्यथा: `branch(newLeafId)`\n6. नई लीफ से एजेंट कॉन्टेक्स्ट पुनर्निर्मित करें और `session_tree` एमिट करें\n\nमहत्वपूर्ण: सारांश एंट्रियाँ **नई नेविगेशन स्थिति** पर संलग्न होती हैं, abandoned ब्रांच टेल पर नहीं।\n\n## `/branch` व्यवहार (नई सेशन फ़ाइल)\n\n`/branch` और `/tree` जानबूझकर अलग हैं:\n\n- `/tree` वर्तमान सेशन फ़ाइल के भीतर नेविगेट करता है।\n- `/branch` एक नई सेशन ब्रांच फ़ाइल बनाता है (या non-persistent मोड के लिए इन-मेमोरी प्रतिस्थापन)।\n\nयूज़र-फेसिंग `/branch` फ्लो (`SelectorController.showUserMessageSelector` → `AgentSession.branch`):\n\n- ब्रांच सोर्स एक **user message** होना चाहिए।\n- चयनित user टेक्स्ट एडिटर प्रीफिल के लिए एक्सट्रैक्ट किया जाता है।\n- यदि चयनित user मैसेज रूट है (`parentId === null`): `newSession({ parentSession: previousSessionFile })` के माध्यम से एक नया सेशन शुरू करें।\n- अन्यथा: चयनित प्रॉम्प्ट बाउंड्री तक इतिहास फोर्क करने के लिए `createBranchedSession(selectedEntry.parentId)`।\n\n`SessionManager.createBranchedSession(leafId)` विशेषताएँ:\n\n- `getBranch(leafId)` के माध्यम से रूट→लीफ पाथ बनाता है; गुम होने पर throws करता है।\n- कॉपी किए गए पाथ से मौजूदा `label` एंट्रियाँ बाहर करता है।\n- उन एंट्रियों के लिए resolved `labelsById` से ताज़ा लेबल एंट्रियाँ पुनर्निर्मित करता है जो पाथ में बनी रहती हैं।\n- Persistent मोड: नई JSONL फ़ाइल लिखता है और मैनेजर को उस पर स्विच करता है; नया फ़ाइल पाथ लौटाता है।\n- इन-मेमोरी मोड: इन-मेमोरी एंट्रियाँ बदलता है; `undefined` लौटाता है।\n\n## कॉन्टेक्स्ट पुनर्निर्माण और सारांश/कस्टम इंटीग्रेशन\n\n`buildSessionContext()` (`session-manager.ts` में) सक्रिय रूट→लीफ पाथ रिज़ॉल्व करता है और प्रभावी LLM कॉन्टेक्स्ट स्टेट बनाता है:\n\n- पाथ पर नवीनतम thinking/model/mode/ttsr स्टेट ट्रैक करता है।\n- पाथ पर नवीनतम compaction को हैंडल करता है:\n  - पहले compaction सारांश एमिट करता है\n  - `firstKeptEntryId` से compaction पॉइंट तक kept मैसेजेस रीप्ले करता है\n  - फिर post-compaction मैसेजेस रीप्ले करता है\n- `branch_summary` और `custom_message` एंट्रियों को `AgentMessage` ऑब्जेक्ट के रूप में शामिल करता है।\n\n`session/messages.ts` फिर मॉडल इनपुट के लिए इन मैसेज टाइप को मैप करता है:\n\n- `branchSummary` और `compactionSummary` user-role templated कॉन्टेक्स्ट मैसेजेस बन जाते हैं\n- `custom`/`hookMessage` user-role कंटेंट मैसेजेस बन जाते हैं\n\nइस प्रकार ट्री मूवमेंट सक्रिय लीफ पाथ बदलकर कॉन्टेक्स्ट बदलता है, पुरानी एंट्रियों को म्यूटेट करके नहीं।\n\n## लेबल और ट्री UI व्यवहार\n\nलेबल परसिस्टेंस:\n\n- `appendLabelChange(targetId, label?)` वर्तमान लीफ चेन पर `label` एंट्रियाँ लिखता है।\n- `labelsById` तुरंत अपडेट होता है (set या delete)।\n- `getTree()` प्रत्येक लौटाए गए नोड पर वर्तमान लेबल रिज़ॉल्व करता है।\n\nट्री सेलेक्टर व्यवहार (`tree-selector.ts`):\n\n- नेविगेशन के लिए ट्री को फ्लैट करता है, active-path हाइलाइटिंग रखता है, और पहले सक्रिय ब्रांच प्रदर्शित करने को प्राथमिकता देता है।\n- फ़िल्टर मोड सपोर्ट करता है: `default`, `no-tools`, `user-only`, `labeled-only`, `all`।\n- रेंडर किए गए सिमेंटिक कंटेंट पर फ्री-टेक्स्ट सर्च सपोर्ट करता है।\n- `Shift+L` इनलाइन लेबल एडिटिंग खोलता है और `appendLabelChange` के माध्यम से लिखता है।\n\nकमांड रूटिंग:\n\n- `/tree` हमेशा ट्री सेलेक्टर खोलता है।\n- `/branch` user-message सेलेक्टर खोलता है जब तक `doubleEscapeAction=tree` न हो, उस स्थिति में यह ट्री सेलेक्टर UX भी उपयोग करता है।\n\n## ट्री ऑपरेशन के लिए एक्सटेंशन और हुक टचपॉइंट\n\nकमांड-टाइम एक्सटेंशन API (`ExtensionCommandContext`):\n\n- `branch(entryId)` — branched सेशन फ़ाइल बनाएं\n- `navigateTree(targetId, { summarize? })` — वर्तमान ट्री/फ़ाइल के भीतर जाएं\n\nट्री नेविगेशन के आसपास इवेंट:\n\n- `session_before_tree`\n  - `TreePreparation` प्राप्त करता है:\n    - `targetId`\n    - `oldLeafId`\n    - `commonAncestorId`\n    - `entriesToSummarize`\n    - `userWantsSummary`\n  - नेविगेशन रद्द कर सकता है\n  - बिल्ट-इन समराइज़र के बजाय उपयोग किया जाने वाला सारांश पेलोड प्रदान कर सकता है\n  - abort `signal` प्राप्त करता है (Escape cancellation पाथ)\n- `session_tree`\n  - `newLeafId`, `oldLeafId` एमिट करता है\n  - सारांश बनाए जाने पर `summaryEntry` शामिल करता है\n  - `fromExtension` सारांश उत्पत्ति दर्शाता है\n\nआसन्न लेकिन संबंधित लाइफसाइकिल हुक:\n\n- `/branch` फ्लो के लिए `session_before_branch` / `session_branch`\n- compaction एंट्रियों के लिए `session_before_compact`, `session.compacting`, `session_compact` जो बाद में ट्री-कॉन्टेक्स्ट पुनर्निर्माण को प्रभावित करती हैं\n\n## वास्तविक बाधाएँ और एज कंडीशन\n\n- `branch()` `null` को टार्गेट नहीं कर सकता; root-before-first-entry स्टेट के लिए `resetLeaf()` उपयोग करें।\n- `branchWithSummary()` `null` टार्गेट सपोर्ट करता है और `fromId: \"root\"` रिकॉर्ड करता है।\n- ट्री सेलेक्टर में वर्तमान लीफ का चयन करना एक no-op है।\n- सारांशीकरण के लिए एक सक्रिय मॉडल आवश्यक है; अनुपस्थित होने पर, सारांश नेविगेशन तेज़ी से विफल होता है।\n- यदि सारांशीकरण abort किया जाता है, तो नेविगेशन रद्द हो जाता है और लीफ अपरिवर्तित रहती है।\n- इन-मेमोरी सेशन `createBranchedSession` से कभी भी ब्रांच फ़ाइल पाथ नहीं लौटाते।\n\n## अभी भी मौजूद लेगेसी संगतता\n\nसेशन माइग्रेशन लोड पर चलती हैं:\n\n- v1→v2 `id`/`parentId` जोड़ता है और compaction इंडेक्स एंकर को id एंकर में बदलता है\n- v2→v3 legacy `hookMessage` रोल को `custom` में माइग्रेट करता है\n\nमाइग्रेशन के बाद वर्तमान रनटाइम व्यवहार version-3 ट्री सिमेंटिक्स है।\n",
	"hi/sessions/session.md": "---\ntitle: सत्र संग्रहण और प्रविष्टि मॉडल\ndescription: >-\n  प्रविष्टि प्रकारों, स्थायित्व, और प्रारूपों के बीच माइग्रेशन के साथ\n  केवल-जोड़ने योग्य सत्र संग्रहण मॉडल।\nsidebar:\n  order: 1\n  label: संग्रहण और प्रविष्टि मॉडल\ni18n:\n  sourceHash: 42fe17549e00\n  translator: machine\n---\n\n# सत्र संग्रहण और प्रविष्टि मॉडल\n\nयह दस्तावेज़ इस बात का प्रामाणिक स्रोत है कि coding-agent सत्रों को रनटाइम पर कैसे प्रस्तुत, स्थायी, माइग्रेट और पुनर्निर्मित किया जाता है।\n\n## दायरा\n\nइसमें शामिल है:\n\n- सत्र JSONL प्रारूप और संस्करण\n- प्रविष्टि वर्गीकरण और ट्री सिमैंटिक्स (`id`/`parentId` + लीफ पॉइंटर)\n- पुरानी या विकृत फ़ाइलें लोड करते समय माइग्रेशन/संगतता व्यवहार\n- संदर्भ पुनर्निर्माण (`buildSessionContext`)\n- स्थायित्व गारंटी, विफलता व्यवहार, ट्रंकेशन/ब्लॉब बाह्यकरण\n- संग्रहण अमूर्तन (`FileSessionStorage`, `MemorySessionStorage`) और संबंधित उपयोगिताएँ\n\nइसमें शामिल नहीं है: `/tree` UI रेंडरिंग व्यवहार, सत्र डेटा को प्रभावित करने वाले सिमैंटिक्स से परे।\n\n## कार्यान्वयन फ़ाइलें\n\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`src/session/messages.ts`](../../packages/coding-agent/src/session/messages.ts)\n- [`src/session/session-storage.ts`](../../packages/coding-agent/src/session/session-storage.ts)\n- [`src/session/history-storage.ts`](../../packages/coding-agent/src/session/history-storage.ts)\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts)\n\n## डिस्क पर लेआउट\n\nडिफ़ॉल्ट सत्र फ़ाइल स्थान:\n\n```text\n~/.xcsh/agent/sessions/--<cwd-encoded>--/<timestamp>_<sessionId>.jsonl\n```\n\n`<cwd-encoded>` कार्यशील निर्देशिका से अग्रणी स्लैश हटाकर और `/`, `\\\\`, तथा `:` को `-` से बदलकर प्राप्त किया जाता है।\n\nब्लॉब स्टोर स्थान:\n\n```text\n~/.xcsh/agent/blobs/<sha256>\n```\n\nटर्मिनल ब्रेडक्रम्ब फ़ाइलें यहाँ लिखी जाती हैं:\n\n```text\n~/.xcsh/agent/terminal-sessions/<terminal-id>\n```\n\nब्रेडक्रम्ब सामग्री दो पंक्तियाँ हैं: मूल cwd, फिर सत्र फ़ाइल पथ। `continueRecent()` सबसे हाल के mtime को स्कैन करने से पहले इस टर्मिनल-स्कोप्ड पॉइंटर को प्राथमिकता देता है।\n\n## फ़ाइल प्रारूप\n\nसत्र फ़ाइलें JSONL हैं: प्रति पंक्ति एक JSON ऑब्जेक्ट।\n\n- पंक्ति 1 हमेशा सत्र हेडर (`type: \"session\"`) होती है।\n- शेष पंक्तियाँ `SessionEntry` मान हैं।\n- रनटाइम पर प्रविष्टियाँ केवल-जोड़ने योग्य हैं; शाखा नेविगेशन मौजूदा प्रविष्टियों को बदलने के बजाय एक पॉइंटर (`leafId`) को स्थानांतरित करता है।\n\n### हेडर (`SessionHeader`)\n\n```json\n{\n  \"type\": \"session\",\n  \"version\": 3,\n  \"id\": \"1f9d2a6b9c0d1234\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\",\n  \"cwd\": \"/work/pi\",\n  \"title\": \"optional session title\",\n  \"parentSession\": \"optional lineage marker\"\n}\n```\n\nनोट:\n\n- v1 फ़ाइलों में `version` वैकल्पिक है; अनुपस्थिति का अर्थ v1 है।\n- `parentSession` एक अपारदर्शी वंशावली स्ट्रिंग है। वर्तमान कोड प्रवाह (`fork`, `forkFrom`, `createBranchedSession`, या स्पष्ट `newSession({ parentSession })`) के आधार पर या तो सत्र id या सत्र पथ लिखता है। इसे मेटाडेटा मानें, टाइप्ड विदेशी कुंजी नहीं।\n\n### प्रविष्टि आधार (`SessionEntryBase`)\n\nसभी गैर-हेडर प्रविष्टियों में शामिल है:\n\n```json\n{\n  \"type\": \"...\",\n  \"id\": \"8-char-id\",\n  \"parentId\": \"previous-or-branch-parent\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\"\n}\n```\n\n`parentId` मूल प्रविष्टि (पहला जोड़, या `resetLeaf()` के बाद) के लिए `null` हो सकता है।\n\n## प्रविष्टि वर्गीकरण\n\n`SessionEntry` इनका संघ है:\n\n- `message`\n- `thinking_level_change`\n- `model_change`\n- `compaction`\n- `branch_summary`\n- `custom`\n- `custom_message`\n- `label`\n- `ttsr_injection`\n- `session_init`\n- `mode_change`\n\n### `message`\n\nसीधे एक `AgentMessage` संग्रहीत करता है।\n\n```json\n{\n  \"type\": \"message\",\n  \"id\": \"a1b2c3d4\",\n  \"parentId\": null,\n  \"timestamp\": \"2026-02-16T10:21:00.000Z\",\n  \"message\": {\n    \"role\": \"assistant\",\n    \"provider\": \"anthropic\",\n    \"model\": \"claude-sonnet-4-5\",\n    \"content\": [{ \"type\": \"text\", \"text\": \"Done.\" }],\n    \"usage\": { \"input\": 100, \"output\": 20, \"cacheRead\": 0, \"cacheWrite\": 0, \"cost\": { \"input\": 0, \"output\": 0, \"cacheRead\": 0, \"cacheWrite\": 0, \"total\": 0 } },\n    \"timestamp\": 1760000000000\n  }\n}\n```\n\n### `model_change`\n\n```json\n{\n  \"type\": \"model_change\",\n  \"id\": \"b1c2d3e4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:21:30.000Z\",\n  \"model\": \"openai/gpt-4o\",\n  \"role\": \"default\"\n}\n```\n\n`role` वैकल्पिक है; संदर्भ पुनर्निर्माण में अनुपस्थित को `default` माना जाता है।\n\n### `thinking_level_change`\n\n```json\n{\n  \"type\": \"thinking_level_change\",\n  \"id\": \"c1d2e3f4\",\n  \"parentId\": \"b1c2d3e4\",\n  \"timestamp\": \"2026-02-16T10:22:00.000Z\",\n  \"thinkingLevel\": \"high\"\n}\n```\n\n### `compaction`\n\n```json\n{\n  \"type\": \"compaction\",\n  \"id\": \"d1e2f3a4\",\n  \"parentId\": \"c1d2e3f4\",\n  \"timestamp\": \"2026-02-16T10:23:00.000Z\",\n  \"summary\": \"Conversation summary\",\n  \"shortSummary\": \"Short recap\",\n  \"firstKeptEntryId\": \"a1b2c3d4\",\n  \"tokensBefore\": 42000,\n  \"details\": { \"readFiles\": [\"src/a.ts\"] },\n  \"preserveData\": { \"hookState\": true },\n  \"fromExtension\": false\n}\n```\n\n### `branch_summary`\n\n```json\n{\n  \"type\": \"branch_summary\",\n  \"id\": \"e1f2a3b4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:24:00.000Z\",\n  \"fromId\": \"a1b2c3d4\",\n  \"summary\": \"Summary of abandoned path\",\n  \"details\": { \"note\": \"optional\" },\n  \"fromExtension\": true\n}\n```\n\nयदि मूल से शाखा बना रहे हैं (`branchFromId === null`), तो `fromId` शाब्दिक स्ट्रिंग `\"root\"` होता है।\n\n### `custom`\n\nएक्सटेंशन स्थिति स्थायित्व; `buildSessionContext` द्वारा अनदेखा किया जाता है।\n\n```json\n{\n  \"type\": \"custom\",\n  \"id\": \"f1a2b3c4\",\n  \"parentId\": \"e1f2a3b4\",\n  \"timestamp\": \"2026-02-16T10:25:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"data\": { \"state\": 1 }\n}\n```\n\n### `custom_message`\n\nएक्सटेंशन-प्रदत्त संदेश जो LLM संदर्भ में भाग लेता है।\n\n```json\n{\n  \"type\": \"custom_message\",\n  \"id\": \"a2b3c4d5\",\n  \"parentId\": \"f1a2b3c4\",\n  \"timestamp\": \"2026-02-16T10:26:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"content\": \"Injected context\",\n  \"display\": true,\n  \"details\": { \"debug\": false }\n}\n```\n\n### `label`\n\n```json\n{\n  \"type\": \"label\",\n  \"id\": \"b2c3d4e5\",\n  \"parentId\": \"a2b3c4d5\",\n  \"timestamp\": \"2026-02-16T10:27:00.000Z\",\n  \"targetId\": \"a1b2c3d4\",\n  \"label\": \"checkpoint\"\n}\n```\n\n`label: undefined` `targetId` के लिए एक लेबल साफ़ करता है।\n\n### `ttsr_injection`\n\n```json\n{\n  \"type\": \"ttsr_injection\",\n  \"id\": \"c2d3e4f5\",\n  \"parentId\": \"b2c3d4e5\",\n  \"timestamp\": \"2026-02-16T10:28:00.000Z\",\n  \"injectedRules\": [\"ruleA\", \"ruleB\"]\n}\n```\n\n### `session_init`\n\n```json\n{\n  \"type\": \"session_init\",\n  \"id\": \"d2e3f4a5\",\n  \"parentId\": \"c2d3e4f5\",\n  \"timestamp\": \"2026-02-16T10:29:00.000Z\",\n  \"systemPrompt\": \"...\",\n  \"task\": \"...\",\n  \"tools\": [\"read\", \"edit\"],\n  \"outputSchema\": { \"type\": \"object\" }\n}\n```\n\n### `mode_change`\n\n```json\n{\n  \"type\": \"mode_change\",\n  \"id\": \"e2f3a4b5\",\n  \"parentId\": \"d2e3f4a5\",\n  \"timestamp\": \"2026-02-16T10:30:00.000Z\",\n  \"mode\": \"plan\",\n  \"data\": { \"planFile\": \"/tmp/plan.md\" }\n}\n```\n\n## संस्करण और माइग्रेशन\n\nवर्तमान सत्र संस्करण: `3`।\n\n### v1 -> v2\n\nतब लागू होता है जब हेडर `version` अनुपस्थित हो या `< 2`:\n\n- प्रत्येक गैर-हेडर प्रविष्टि में `id` और `parentId` जोड़ता है।\n- फ़ाइल क्रम का उपयोग करके एक रैखिक पैरेंट श्रृंखला पुनर्निर्मित करता है।\n- जब मौजूद हो तो संघनन फ़ील्ड `firstKeptEntryIndex` -> `firstKeptEntryId` माइग्रेट करता है।\n- हेडर `version = 2` सेट करता है।\n\n### v2 -> v3\n\nतब लागू होता है जब हेडर `version < 3`:\n\n- `message` प्रविष्टियों के लिए: विरासत `message.role === \"hookMessage\"` को `\"custom\"` में पुनर्लिखित करता है।\n- हेडर `version = 3` सेट करता है।\n\n### माइग्रेशन ट्रिगर और स्थायित्व\n\n- माइग्रेशन सत्र लोड (`setSessionFile`) के दौरान चलते हैं।\n- यदि कोई माइग्रेशन चला, तो पूरी फ़ाइल तुरंत डिस्क पर पुनर्लिखित की जाती है।\n- माइग्रेशन पहले इन-मेमोरी प्रविष्टियों को बदलता है, फिर पुनर्लिखित JSONL को स्थायी करता है।\n\n## लोड और संगतता व्यवहार\n\n`loadEntriesFromFile(path)` व्यवहार:\n\n- अनुपस्थित फ़ाइल (`ENOENT`) -> `[]` लौटाता है।\n- गैर-पार्स करने योग्य पंक्तियों को उदार JSONL पार्सर (`parseJsonlLenient`) द्वारा संभाला जाता है।\n- यदि पहली पार्स की गई प्रविष्टि एक मान्य सत्र हेडर नहीं है (`type !== \"session\"` या स्ट्रिंग `id` अनुपस्थित) -> `[]` लौटाता है।\n\n`SessionManager.setSessionFile()` व्यवहार:\n\n- लोडर से `[]` को खाली/अस्तित्वहीन सत्र माना जाता है और उस पथ पर एक नई आरंभीकृत सत्र फ़ाइल से बदल दिया जाता है।\n- मान्य फ़ाइलें लोड की जाती हैं, आवश्यकता होने पर माइग्रेट की जाती हैं, ब्लॉब रेफ़ हल किए जाते हैं, फिर अनुक्रमित किए जाते हैं।\n\n## ट्री और लीफ सिमैंटिक्स\n\nअंतर्निहित मॉडल केवल-जोड़ने योग्य ट्री + परिवर्तनशील लीफ पॉइंटर है:\n\n- प्रत्येक जोड़ने की विधि ठीक एक नई प्रविष्टि बनाती है जिसका `parentId` वर्तमान `leafId` होता है।\n- नई प्रविष्टि नया `leafId` बन जाती है।\n- `branch(entryId)` केवल `leafId` को स्थानांतरित करता है; मौजूदा प्रविष्टियाँ अपरिवर्तित रहती हैं।\n- `resetLeaf()` `leafId = null` सेट करता है; अगला जोड़ एक नई मूल प्रविष्टि (`parentId: null`) बनाता है।\n- `branchWithSummary()` लीफ को शाखा लक्ष्य पर सेट करता है और एक `branch_summary` प्रविष्टि जोड़ता है।\n\n`getEntries()` सभी गैर-हेडर प्रविष्टियों को सम्मिलन क्रम में लौटाता है। सामान्य संचालन में मौजूदा प्रविष्टियाँ हटाई नहीं जातीं; पुनर्लेखन प्रतिनिधित्व को अपडेट करते हुए तार्किक इतिहास को संरक्षित करते हैं (माइग्रेशन, स्थानांतरण, लक्षित पुनर्लेखन सहायक)।\n\n## संदर्भ पुनर्निर्माण (`buildSessionContext`)\n\n`buildSessionContext(entries, leafId, byId?)` यह निर्धारित करता है कि मॉडल को क्या भेजा जाता है।\n\nएल्गोरिदम:\n\n1. लीफ निर्धारित करें:\n   - `leafId === null` -> खाली संदर्भ लौटाएँ।\n   - स्पष्ट `leafId` -> यदि मिले तो उस प्रविष्टि का उपयोग करें।\n   - अन्यथा अंतिम प्रविष्टि पर फ़ॉलबैक।\n2. लीफ से मूल तक `parentId` श्रृंखला पर चलें और मूल->लीफ पथ के लिए उलटें।\n3. पथ पर रनटाइम स्थिति प्राप्त करें:\n   - नवीनतम `thinking_level_change` से `thinkingLevel` (डिफ़ॉल्ट `\"off\"`)\n   - `model_change` प्रविष्टियों से मॉडल मैप (`role ?? \"default\"`)\n   - यदि कोई स्पष्ट मॉडल परिवर्तन नहीं है तो सहायक संदेश provider/model से फ़ॉलबैक `models.default`\n   - सभी `ttsr_injection` प्रविष्टियों से डिडुप्लिकेटेड `injectedTtsrRules`\n   - नवीनतम `mode_change` से mode/modeData (डिफ़ॉल्ट मोड `\"none\"`)\n4. संदेश सूची बनाएँ:\n   - `message` प्रविष्टियाँ सीधे पास होती हैं\n   - `custom_message` प्रविष्टियाँ `createCustomMessage` के माध्यम से `custom` AgentMessages बनती हैं\n   - `branch_summary` प्रविष्टियाँ `createBranchSummaryMessage` के माध्यम से `branchSummary` AgentMessages बनती हैं\n   - यदि पथ पर `compaction` मौजूद है:\n     - पहले संघनन सारांश प्रकाशित करें (`createCompactionSummaryMessage`)\n     - संघनन सीमा तक `firstKeptEntryId` से शुरू होने वाली पथ प्रविष्टियाँ प्रकाशित करें\n     - संघनन सीमा के बाद की प्रविष्टियाँ प्रकाशित करें\n\n`custom` और `session_init` प्रविष्टियाँ सीधे मॉडल संदर्भ में इंजेक्ट नहीं करतीं।\n\n## स्थायित्व गारंटी और विफलता मॉडल\n\n### स्थायी बनाम इन-मेमोरी\n\n- `SessionManager.create/open/continueRecent/forkFrom` -> स्थायी मोड (`persist = true`)।\n- `SessionManager.inMemory` -> गैर-स्थायी मोड (`persist = false`) `MemorySessionStorage` के साथ।\n\n### लेखन पाइपलाइन\n\nलेखन एक आंतरिक प्रॉमिस श्रृंखला (`#persistChain`) और `NdjsonFileWriter` के माध्यम से क्रमबद्ध होते हैं।\n\n- `append*` इन-मेमोरी स्थिति को तुरंत अपडेट करता है।\n- कम से कम एक सहायक संदेश मौजूद होने तक स्थायित्व स्थगित रहता है।\n  - पहले सहायक से पहले: प्रविष्टियाँ मेमोरी में रखी जाती हैं; कोई फ़ाइल जोड़ नहीं होता।\n  - जब पहला सहायक मौजूद हो: पूर्ण इन-मेमोरी सत्र फ़ाइल में फ़्लश किया जाता है।\n  - उसके बाद: नई प्रविष्टियाँ क्रमिक रूप से जोड़ी जाती हैं।\n\nकोड में तर्काधार: ऐसे सत्रों को स्थायी करने से बचें जिन्होंने कभी सहायक प्रतिक्रिया उत्पन्न नहीं की।\n\n### स्थायित्व संचालन\n\n- `flush()` राइटर को फ़्लश करता है और `fsync()` कॉल करता है।\n- परमाणु पूर्ण पुनर्लेखन (`#rewriteFile`) अस्थायी फ़ाइल में लिखते हैं, flush+fsync करते हैं, बंद करते हैं, फिर लक्ष्य पर नाम बदलते हैं।\n- माइग्रेशन, `setSessionName`, `rewriteEntries`, स्थानांतरण संचालन, और टूल-कॉल आर्ग पुनर्लेखन के लिए उपयोग किया जाता है।\n\n### त्रुटि व्यवहार\n\n- स्थायित्व त्रुटियाँ लैच की जाती हैं (`#persistError`) और बाद के संचालनों पर पुनः फेंकी जाती हैं।\n- पहली त्रुटि सत्र फ़ाइल संदर्भ के साथ एक बार लॉग की जाती है।\n- राइटर बंद करना सर्वोत्तम-प्रयास है लेकिन पहली सार्थक त्रुटि को प्रसारित करता है।\n\n## डेटा आकार नियंत्रण और ब्लॉब बाह्यकरण\n\nप्रविष्टियों को स्थायी करने से पहले:\n\n- बड़ी स्ट्रिंग्स को `MAX_PERSIST_CHARS` (500,000 वर्ण) तक सूचना के साथ ट्रंकेट किया जाता है:\n  - `\"[Session persistence truncated large content]\"`\n- क्षणिक फ़ील्ड `partialJson` और `jsonlEvents` हटा दिए जाते हैं।\n- यदि ऑब्जेक्ट में `content` और `lineCount` दोनों हैं, तो ट्रंकेशन के बाद लाइन काउंट पुनर्गणना किया जाता है।\n- `content` सरणियों में base64 लंबाई >= 1024 वाले इमेज ब्लॉक ब्लॉब रेफ़ में बाह्यकृत किए जाते हैं:\n  - `blob:sha256:<hash>` के रूप में संग्रहीत\n  - कच्चे बाइट ब्लॉब स्टोर (`BlobStore.put`) में लिखे जाते हैं\n\nलोड पर, message/custom_message इमेज ब्लॉक के लिए ब्लॉब रेफ़ वापस base64 में हल किए जाते हैं।\n\n## संग्रहण अमूर्तन\n\n`SessionStorage` इंटरफ़ेस `SessionManager` द्वारा उपयोग किए जाने वाले सभी फ़ाइलसिस्टम संचालन प्रदान करता है:\n\n- तुल्यकालिक: `ensureDirSync`, `existsSync`, `writeTextSync`, `statSync`, `listFilesSync`\n- अतुल्यकालिक: `exists`, `readText`, `readTextPrefix`, `writeText`, `rename`, `unlink`, `openWriter`\n\nकार्यान्वयन:\n\n- `FileSessionStorage`: वास्तविक फ़ाइलसिस्टम (Bun + node fs)\n- `MemorySessionStorage`: परीक्षणों/गैर-स्थायी सत्रों के लिए मैप-समर्थित इन-मेमोरी कार्यान्वयन\n\n`SessionStorageWriter` `writeLine`, `flush`, `fsync`, `close`, `getError` प्रकट करता है।\n\n## सत्र खोज उपयोगिताएँ\n\n`session-manager.ts` में परिभाषित:\n\n- `getRecentSessions(sessionDir, limit)` -> UI/सत्र चयनकर्ता के लिए हल्का मेटाडेटा\n- `findMostRecentSession(sessionDir)` -> mtime द्वारा नवीनतम\n- `list(cwd, sessionDir?)` -> एक प्रोजेक्ट दायरे में सत्र\n- `listAll()` -> `~/.xcsh/agent/sessions` के अंतर्गत सभी प्रोजेक्ट दायरों में सत्र\n\nमेटाडेटा निष्कर्षण जहाँ संभव हो केवल एक उपसर्ग (`readTextPrefix(..., 4096)`) पढ़ता है।\n\n## संबंधित लेकिन भिन्न: प्रॉम्प्ट इतिहास संग्रहण\n\n`HistoryStorage` (`history-storage.ts`) प्रॉम्प्ट स्मरण/खोज के लिए एक अलग SQLite उपप्रणाली है, सत्र पुनर्चालन के लिए नहीं।\n\n- DB: `~/.xcsh/agent/history.db`\n- तालिका: `history(id, prompt, created_at, cwd)`\n- FTS5 अनुक्रमणिका: ट्रिगर-अनुरक्षित सिंक के साथ `history_fts`\n- इन-मेमोरी अंतिम-प्रॉम्प्ट कैश का उपयोग करके लगातार समान प्रॉम्प्ट्स को डिडुप्लिकेट करता है\n- अतुल्यकालिक सम्मिलन (`setImmediate`) ताकि प्रॉम्प्ट कैप्चर टर्न निष्पादन को अवरुद्ध न करे\n\nवार्तालाप ग्राफ/स्थिति पुनर्चालन के लिए सत्र फ़ाइलों का उपयोग करें; प्रॉम्प्ट इतिहास UX के लिए `HistoryStorage` का उपयोग करें।\n",
	"hi/sessions/ttsr-injection-lifecycle.md": "---\ntitle: TTSR इंजेक्शन जीवनचक्र\ndescription: >-\n  संदर्भ प्रबंधन के लिए TTSR (tool-use, tool-result, system-reminder) इंजेक्शन\n  जीवनचक्र।\nsidebar:\n  order: 9\n  label: TTSR इंजेक्शन\ni18n:\n  sourceHash: d6179a286584\n  translator: machine\n---\n\n# TTSR इंजेक्शन जीवनचक्र\n\nयह दस्तावेज़ वर्तमान Time Traveling Stream Rules (TTSR) रनटाइम पथ को कवर करता है, जिसमें नियम खोज से लेकर स्ट्रीम रुकावट, पुनः प्रयास इंजेक्शन, एक्सटेंशन सूचनाएं, और सत्र-स्थिति प्रबंधन शामिल हैं।\n\n## कार्यान्वयन फ़ाइलें\n\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/export/ttsr.ts`](../../packages/coding-agent/src/export/ttsr.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/prompts/system/ttsr-interrupt.md`](../../packages/coding-agent/src/prompts/system/ttsr-interrupt.md)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/types.ts`](../../packages/coding-agent/src/extensibility/extensions/types.ts)\n- [`../src/extensibility/hooks/types.ts`](../../packages/coding-agent/src/extensibility/hooks/types.ts)\n- [`../src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n\n## 1. खोज फ़ीड और नियम पंजीकरण\n\nसत्र निर्माण के समय, `createAgentSession()` सभी खोजे गए नियमों को लोड करता है और एक `TtsrManager` बनाता है:\n\n```ts\nconst ttsrSettings = settings.getGroup(\"ttsr\");\nconst ttsrManager = new TtsrManager(ttsrSettings);\nconst rulesResult = await loadCapability<Rule>(ruleCapability.id, { cwd });\nfor (const rule of rulesResult.items) {\n  if (rule.ttsrTrigger) ttsrManager.addRule(rule);\n}\n```\n\n### पूर्व-पंजीकरण डीडुप्लिकेशन व्यवहार\n\n`loadCapability(\"rules\")` `rule.name` के आधार पर डीडुप्लिकेट करता है, जिसमें पहला-जीतता-है (first-wins) शब्दार्थ होता है (उच्च प्रदाता प्राथमिकता पहले)। छायांकित डुप्लिकेट TTSR पंजीकरण से पहले हटा दिए जाते हैं।\n\n### `TtsrManager.addRule()` व्यवहार\n\nपंजीकरण छोड़ दिया जाता है जब:\n\n- `rule.ttsrTrigger` अनुपस्थित हो\n- समान `rule.name` वाला नियम पहले से इस प्रबंधक में पंजीकृत हो\n- regex संकलित होने में विफल हो (`new RegExp(rule.ttsrTrigger)` त्रुटि फेंके)\n\nअमान्य regex ट्रिगर चेतावनी के रूप में लॉग किए जाते हैं और अनदेखा कर दिए जाते हैं; सत्र स्टार्टअप जारी रहता है।\n\n### सेटिंग चेतावनी\n\n`TtsrSettings.enabled` को प्रबंधक में लोड किया जाता है लेकिन वर्तमान में रनटाइम गेटिंग में जाँचा नहीं जाता। यदि नियम मौजूद हैं, तो मिलान फिर भी चलता है।\n\n## 2. स्ट्रीमिंग मॉनिटर जीवनचक्र\n\nTTSR पहचान `AgentSession.#handleAgentEvent` के अंदर चलती है।\n\n### टर्न प्रारंभ\n\n`turn_start` पर, स्ट्रीम बफ़र रीसेट होता है:\n\n- `ttsrManager.resetBuffer()`\n\n### स्ट्रीम के दौरान (`message_update`)\n\nजब सहायक अपडेट आते हैं और नियम मौजूद होते हैं:\n\n- `text_delta` और `toolcall_delta` की निगरानी करें\n- डेल्टा को प्रबंधक बफ़र में जोड़ें\n- `check(buffer)` को कॉल करें\n\n`check()` पंजीकृत नियमों पर पुनरावृत्ति करता है और उन सभी मिलान करने वाले नियमों को लौटाता है जो दोहराव नीति (`#canTrigger`) को पास करते हैं।\n\n## 3. ट्रिगर निर्णय और तत्काल निरस्तीकरण पथ\n\nजब एक या अधिक नियम मेल खाते हैं:\n\n1. `markInjected(matches)` प्रबंधक इंजेक्शन स्थिति में नियम नामों को रिकॉर्ड करता है।\n2. मेल खाने वाले नियम `#pendingTtsrInjections` में कतारबद्ध होते हैं।\n3. `#ttsrAbortPending = true`।\n4. `agent.abort()` तुरंत कॉल किया जाता है।\n5. `ttsr_triggered` इवेंट असिंक्रोनस रूप से उत्सर्जित होता है (फ़ायर-एंड-फ़ॉरगेट)।\n6. पुनः प्रयास कार्य `setTimeout(..., 50)` के माध्यम से शेड्यूल किया जाता है।\n\nनिरस्तीकरण एक्सटेंशन कॉलबैक पर अवरुद्ध नहीं होता।\n\n## 4. पुनः प्रयास शेड्यूलिंग, संदर्भ मोड, और रिमाइंडर इंजेक्शन\n\n50ms टाइमआउट के बाद:\n\n1. `#ttsrAbortPending = false`\n2. `ttsrManager.getSettings().contextMode` पढ़ें\n3. यदि `contextMode === \"discard\"` है, तो `agent.popMessage()` के साथ आंशिक सहायक आउटपुट को छोड़ दें\n4. `ttsr-interrupt.md` टेम्पलेट का उपयोग करके लंबित नियमों से इंजेक्शन सामग्री बनाएं\n5. प्रति नियम एक `<system-interrupt ...>` ब्लॉक वाला एक सिंथेटिक उपयोगकर्ता संदेश जोड़ें\n6. पुनः जनरेशन के लिए `agent.continue()` कॉल करें\n\nटेम्पलेट पेलोड है:\n\n```xml\n<system-interrupt reason=\"rule_violation\" rule=\"{{name}}\" path=\"{{path}}\">\n...\n{{content}}\n</system-interrupt>\n```\n\nसामग्री जनरेशन के बाद लंबित इंजेक्शन साफ़ कर दिए जाते हैं।\n\n### आंशिक आउटपुट पर `contextMode` व्यवहार\n\n- `discard`: पुनः प्रयास से पहले आंशिक/निरस्त सहायक संदेश हटा दिया जाता है।\n- `keep`: आंशिक सहायक आउटपुट वार्तालाप स्थिति में बना रहता है; रिमाइंडर उसके बाद जोड़ा जाता है।\n\n## 5. दोहराव नीति और अंतराल तर्क\n\n`TtsrManager` `#messageCount` और प्रति-नियम `lastInjectedAt` को ट्रैक करता है।\n\n### `repeatMode: \"once\"`\n\nएक नियम इंजेक्शन रिकॉर्ड होने के बाद केवल एक बार ट्रिगर हो सकता है।\n\n### `repeatMode: \"after-gap\"`\n\nएक नियम तभी पुनः ट्रिगर हो सकता है जब:\n\n- `messageCount - lastInjectedAt >= repeatGap`\n\n`messageCount` `turn_end` पर बढ़ता है, इसलिए अंतराल पूर्ण टर्न में मापा जाता है, स्ट्रीम चंक में नहीं।\n\n## 6. इवेंट उत्सर्जन और एक्सटेंशन/हुक सतहें\n\n### सत्र इवेंट\n\n`AgentSessionEvent` में शामिल है:\n\n```ts\n{ type: \"ttsr_triggered\"; rules: Rule[] }\n```\n\n### एक्सटेंशन रनर\n\n`#emitSessionEvent()` इवेंट को यहाँ रूट करता है:\n\n- एक्सटेंशन श्रोता (`ExtensionRunner.emit({ type: \"ttsr_triggered\", rules })`)\n- स्थानीय सत्र सदस्य\n\n### हुक और कस्टम-टूल टाइपिंग\n\n- एक्सटेंशन API `on(\"ttsr_triggered\", ...)` को उजागर करता है\n- हुक API `on(\"ttsr_triggered\", ...)` को उजागर करता है\n- कस्टम टूल `onSession({ reason: \"ttsr_triggered\", rules })` प्राप्त करते हैं\n\n### इंटरैक्टिव-मोड रेंडरिंग अंतर\n\nइंटरैक्टिव मोड `session.isTtsrAbortPending` का उपयोग करता है ताकि TTSR रुकावट के दौरान निरस्त सहायक स्टॉप कारण को दृश्य विफलता के रूप में दिखाने से रोका जा सके, और जब इवेंट आता है तब `TtsrNotificationComponent` रेंडर करता है।\n\n## 7. स्थायित्व और पुनः प्रारंभ स्थिति (वर्तमान कार्यान्वयन)\n\n`SessionManager` में इंजेक्ट किए गए नियम स्थायित्व के लिए पूर्ण स्कीमा समर्थन है:\n\n- प्रविष्टि प्रकार: `ttsr_injection`\n- जोड़ने का API: `appendTtsrInjection(ruleNames)`\n- क्वेरी API: `getInjectedTtsrRules()`\n- संदर्भ पुनर्निर्माण में `SessionContext.injectedTtsrRules` शामिल है\n\n`TtsrManager` `restoreInjected(ruleNames)` के माध्यम से पुनर्स्थापन का भी समर्थन करता है।\n\n### वर्तमान वायरिंग स्थिति\n\nवर्तमान रनटाइम पथ में:\n\n- `AgentSession` TTSR ट्रिगर होने पर `ttsr_injection` प्रविष्टियाँ नहीं जोड़ता।\n- `createAgentSession()` `existingSession.injectedTtsrRules` को वापस `ttsrManager` में पुनर्स्थापित नहीं करता।\n\nशुद्ध प्रभाव: इंजेक्ट किए गए नियम का दमन लाइव प्रक्रिया के लिए इन-मेमोरी लागू होता है, लेकिन वर्तमान में इस पथ द्वारा सत्र पुनः लोड/पुनः प्रारंभ के दौरान स्थायी/पुनर्स्थापित नहीं होता।\n\n## 8. रेस सीमाएं और क्रम गारंटी\n\n### निरस्तीकरण बनाम पुनः प्रयास कॉलबैक\n\n- TTSR हैंडलर के दृष्टिकोण से निरस्तीकरण सिंक्रोनस है (`agent.abort()` तुरंत कॉल किया जाता है)\n- पुनः प्रयास टाइमर द्वारा विलंबित होता है (`50ms`)\n- एक्सटेंशन सूचना असिंक्रोनस है और जानबूझकर निरस्तीकरण/पुनः प्रयास शेड्यूलिंग से पहले प्रतीक्षित नहीं की जाती\n\n### एक ही स्ट्रीम विंडो में कई मिलान\n\n`check()` सभी वर्तमान में मेल खाने वाले पात्र नियम लौटाता है। वे अगले पुनः प्रयास संदेश पर एक बैच के रूप में इंजेक्ट किए जाते हैं।\n\n### निरस्तीकरण और जारी रखने के बीच\n\nटाइमर विंडो के दौरान, स्थिति बदल सकती है (उपयोगकर्ता रुकावट, मोड क्रियाएं, अतिरिक्त इवेंट)। पुनः प्रयास कॉल सर्वोत्तम-प्रयास है: `agent.continue().catch(() => {})` अनुवर्ती त्रुटियों को निगल लेता है।\n\n## 9. किनारे के मामलों का सारांश\n\n- अमान्य `ttsr_trigger` regex: चेतावनी के साथ छोड़ दिया जाता है; अन्य नियम जारी रहते हैं।\n- क्षमता परत पर डुप्लिकेट नियम नाम: निम्न-प्राथमिकता वाले डुप्लिकेट पंजीकरण से पहले छायांकित कर दिए जाते हैं।\n- प्रबंधक परत पर डुप्लिकेट नाम: दूसरा पंजीकरण अनदेखा कर दिया जाता है।\n- `contextMode: \"keep\"`: रिमाइंडर पुनः प्रयास से पहले आंशिक उल्लंघनकारी आउटपुट संदर्भ में बना रह सकता है।\n- दोहराव-अंतराल-के-बाद `turn_end` पर टर्न गणना वृद्धि पर निर्भर करता है; मध्य-टर्न चंक अंतराल काउंटर को आगे नहीं बढ़ाते।\n",
	"hi/tui/theme.md": "---\ntitle: थीमिंग संदर्भ\ndescription: 'रंग टोकन, फ़ॉन्ट सेटिंग और थीम अनुकूलन के साथ TUI थीमिंग संदर्भ।'\nsidebar:\n  order: 3\n  label: थीमिंग\ni18n:\n  sourceHash: 7e962a7da157\n  translator: machine\n---\n\n# थीमिंग संदर्भ\n\nयह दस्तावेज़ बताता है कि आज कोडिंग-एजेंट में थीमिंग कैसे काम करती है: स्कीमा, लोडिंग, रनटाइम व्यवहार, और विफलता के तरीके।\n\n## थीम सिस्टम क्या नियंत्रित करता है\n\nथीम सिस्टम निम्नलिखित को संचालित करता है:\n\n- TUI भर में उपयोग किए जाने वाले फ़ोरग्राउंड/बैकग्राउंड रंग टोकन\n- मार्कडाउन स्टाइलिंग अडैप्टर (`getMarkdownTheme()`)\n- सेलेक्टर/एडिटर/सेटिंग्स लिस्ट अडैप्टर (`getSelectListTheme()`, `getEditorTheme()`, `getSettingsListTheme()`)\n- सिंबल प्रीसेट + सिंबल ओवरराइड (`unicode`, `nerd`, `ascii`)\n- नेटिव हाइलाइटर द्वारा उपयोग किए जाने वाले सिंटैक्स हाइलाइटिंग रंग (`@f5-sales-demo/pi-natives`)\n- स्टेटस लाइन सेगमेंट रंग\n\nप्राथमिक इम्प्लीमेंटेशन: `src/modes/theme/theme.ts`।\n\n## थीम JSON आकार\n\nथीम फ़ाइलें JSON ऑब्जेक्ट हैं जिन्हें `theme.ts` (`ThemeJsonSchema`) में रनटाइम स्कीमा के विरुद्ध सत्यापित किया जाता है और `src/modes/theme/theme-schema.json` द्वारा मिरर किया जाता है।\n\nशीर्ष-स्तरीय फ़ील्ड:\n\n- `name` (आवश्यक)\n- `colors` (आवश्यक; सभी रंग टोकन आवश्यक)\n- `vars` (वैकल्पिक; पुन: उपयोग योग्य रंग वेरिएबल)\n- `export` (वैकल्पिक; HTML एक्सपोर्ट रंग)\n- `symbols` (वैकल्पिक)\n  - `preset` (वैकल्पिक: `unicode | nerd | ascii`)\n  - `overrides` (वैकल्पिक: `SymbolKey` के लिए key/value ओवरराइड)\n\nरंग मान स्वीकार करते हैं:\n\n- hex स्ट्रिंग (`\"#RRGGBB\"`)\n- 256-रंग इंडेक्स (`0..255`)\n- वेरिएबल संदर्भ स्ट्रिंग (`vars` के माध्यम से रिज़ॉल्व किया गया)\n- रिक्त स्ट्रिंग (`\"\"`) जिसका अर्थ है टर्मिनल डिफ़ॉल्ट (`\\x1b[39m` fg, `\\x1b[49m` bg)\n\n## आवश्यक रंग टोकन (वर्तमान)\n\nनीचे सभी टोकन `colors` में आवश्यक हैं।\n\n### मुख्य टेक्स्ट और बॉर्डर (11)\n\n`accent`, `border`, `borderAccent`, `borderMuted`, `success`, `error`, `warning`, `muted`, `dim`, `text`, `thinkingText`\n\n### बैकग्राउंड ब्लॉक (7)\n\n`selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`, `statusLineBg`\n\n### मैसेज/टूल टेक्स्ट (5)\n\n`userMessageText`, `customMessageText`, `customMessageLabel`, `toolTitle`, `toolOutput`\n\n### मार्कडाउन (10)\n\n`mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet`\n\n### टूल diff + सिंटैक्स हाइलाइटिंग (12)\n\n`toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext`,\n`syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation`\n\n### मोड/थिंकिंग बॉर्डर (8)\n\n`thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh`, `bashMode`, `pythonMode`\n\n### स्टेटस लाइन सेगमेंट रंग (14)\n\n`statusLineSep`, `statusLineModel`, `statusLinePath`, `statusLineGitClean`, `statusLineGitDirty`, `statusLineContext`, `statusLineSpend`, `statusLineStaged`, `statusLineDirty`, `statusLineUntracked`, `statusLineOutput`, `statusLineCost`, `statusLineSubagents`\n\n## वैकल्पिक टोकन\n\n### `export` अनुभाग (वैकल्पिक)\n\nHTML एक्सपोर्ट थीमिंग हेल्पर के लिए उपयोग किया जाता है:\n\n- `export.pageBg`\n- `export.cardBg`\n- `export.infoBg`\n\nयदि छोड़ा गया, तो एक्सपोर्ट कोड रिज़ॉल्व किए गए थीम रंगों से डिफ़ॉल्ट प्राप्त करता है।\n\n### `symbols` अनुभाग (वैकल्पिक)\n\n- `symbols.preset` एक थीम-स्तरीय डिफ़ॉल्ट सिंबल सेट सेट करता है।\n- `symbols.overrides` व्यक्तिगत `SymbolKey` मानों को ओवरराइड कर सकता है।\n\nरनटाइम प्राथमिकता:\n\n1. सेटिंग्स `symbolPreset` ओवरराइड (यदि सेट है)\n2. थीम JSON `symbols.preset`\n3. फ़ॉलबैक `\"unicode\"`\n\nअमान्य ओवरराइड keys को अनदेखा किया जाता है और लॉग किया जाता है (`logger.debug`)।\n\n## बिल्ट-इन बनाम कस्टम थीम स्रोत\n\nथीम लुकअप क्रम (`loadThemeJson`):\n\n1. बिल्ट-इन एम्बेडेड थीम (`defaults/xcsh-dark.json` और `defaults/xcsh-light.json` जो `defaultThemes` में संकलित हैं)\n2. कस्टम थीम फ़ाइल: `<customThemesDir>/<name>.json`\n\nकस्टम थीम डायरेक्टरी `getCustomThemesDir()` से आती है:\n\n- डिफ़ॉल्ट: `~/.xcsh/agent/themes`\n- `PI_CODING_AGENT_DIR` द्वारा ओवरराइड किया गया (`$PI_CODING_AGENT_DIR/themes`)\n\n`getAvailableThemes()` मर्ज किए गए बिल्ट-इन + कस्टम नाम लौटाता है, सॉर्ट किए गए, नाम टकराव पर बिल्ट-इन को प्राथमिकता मिलती है।\n\n## लोडिंग, सत्यापन, और रिज़ॉल्यूशन\n\nकस्टम थीम फ़ाइलों के लिए:\n\n1. JSON पढ़ें\n2. JSON पार्स करें\n3. `ThemeJsonSchema` के विरुद्ध सत्यापित करें\n4. `vars` संदर्भों को पुनरावर्ती रूप से रिज़ॉल्व करें\n5. रिज़ॉल्व किए गए मानों को टर्मिनल क्षमता मोड द्वारा ANSI में परिवर्तित करें\n\nसत्यापन व्यवहार:\n\n- अनुपस्थित आवश्यक रंग टोकन: स्पष्ट समूहीकृत त्रुटि संदेश\n- गलत टोकन प्रकार/मान: JSON पथ के साथ सत्यापन त्रुटियाँ\n- अज्ञात थीम फ़ाइल: `Theme not found: <name>`\n\nVar संदर्भ व्यवहार:\n\n- नेस्टेड संदर्भों का समर्थन करता है\n- अनुपस्थित वेरिएबल संदर्भ पर त्रुटि फेंकता है\n- गोलाकार संदर्भों पर त्रुटि फेंकता है\n\n## टर्मिनल रंग मोड व्यवहार\n\nरंग मोड डिटेक्शन (`detectColorMode`):\n\n- `COLORTERM=truecolor|24bit` => truecolor\n- `WT_SESSION` => truecolor\n- `TERM` में `dumb`, `linux`, या खाली => 256color\n- अन्यथा => truecolor\n\nरूपांतरण व्यवहार:\n\n- hex -> `Bun.color(..., \"ansi-16m\" | \"ansi-256\")`\n- numeric -> `38;5` / `48;5` ANSI\n- `\"\"` -> डिफ़ॉल्ट fg/bg रीसेट\n\n## रनटाइम स्विचिंग व्यवहार\n\n### प्रारंभिक थीम (`initTheme`)\n\n`main.ts` सेटिंग्स के साथ थीम इनिशियलाइज़ करता है:\n\n- `symbolPreset`\n- `colorBlindMode`\n- `theme.dark`\n- `theme.light`\n\nऑटो थीम स्लॉट चयन `COLORFGBG` बैकग्राउंड डिटेक्शन का उपयोग करता है:\n\n- `COLORFGBG` से बैकग्राउंड इंडेक्स पार्स करें\n- `< 8` => डार्क स्लॉट (`theme.dark`)\n- `>= 8` => लाइट स्लॉट (`theme.light`)\n- पार्स विफलता => डार्क स्लॉट\n\nसेटिंग्स स्कीमा से वर्तमान डिफ़ॉल्ट:\n\n- `theme.dark = \"xcsh-dark\"`\n- `theme.light = \"xcsh-light\"`\n- `symbolPreset = \"unicode\"`\n- `colorBlindMode = false`\n\n### स्पष्ट स्विचिंग (`setTheme`)\n\n- चयनित थीम लोड करता है\n- ग्लोबल `theme` सिंगलटन अपडेट करता है\n- वैकल्पिक रूप से वॉचर शुरू करता है\n- `onThemeChange` कॉलबैक ट्रिगर करता है\n\nविफलता पर:\n\n- बिल्ट-इन `dark` पर फ़ॉलबैक करता है\n- `{ success: false, error }` लौटाता है\n\n### प्रीव्यू स्विचिंग (`previewTheme`)\n\n- ग्लोबल `theme` पर अस्थायी प्रीव्यू थीम लागू करता है\n- अपने आप सहेजी गई सेटिंग्स **नहीं** बदलता\n- फ़ॉलबैक प्रतिस्थापन के बिना success/error लौटाता है\n\nसेटिंग्स UI इसे लाइव प्रीव्यू के लिए उपयोग करता है और रद्द करने पर पूर्व थीम पुनर्स्थापित करता है।\n\n## वॉचर और लाइव रीलोड\n\nजब वॉचर सक्षम हो (`setTheme(..., true)` / इंटरएक्टिव इनिट):\n\n- केवल कस्टम फ़ाइल पथ `<customThemesDir>/<currentTheme>.json` देखता है\n- बिल्ट-इन प्रभावी रूप से नहीं देखे जाते\n- फ़ाइल `change`: रीलोड का प्रयास करता है (debounced)\n- फ़ाइल `rename`/delete: `dark` पर फ़ॉलबैक करता है, वॉचर बंद करता है\n\nऑटो मोड एक `SIGWINCH` लिस्टनर भी इंस्टॉल करता है और टर्मिनल स्थिति बदलने पर डार्क/लाइट स्लॉट मैपिंग का पुनर्मूल्यांकन कर सकता है।\n\n## कलर-ब्लाइंड मोड व्यवहार\n\n`colorBlindMode` रनटाइम पर केवल एक टोकन बदलता है:\n\n- `toolDiffAdded` को HSV-समायोजित किया जाता है (हरा रंग नीले की ओर स्थानांतरित)\n- समायोजन केवल तब लागू होता है जब रिज़ॉल्व किया गया मान hex स्ट्रिंग हो\n\nअन्य टोकन अपरिवर्तित रहते हैं।\n\n## थीम सेटिंग्स कहाँ सहेजी जाती हैं\n\nथीम-संबंधित सेटिंग्स `Settings` द्वारा ग्लोबल config YAML में सहेजी जाती हैं:\n\n- पथ: `<agentDir>/config.yml`\n- डिफ़ॉल्ट एजेंट डायरेक्टरी: `~/.xcsh/agent`\n- प्रभावी डिफ़ॉल्ट फ़ाइल: `~/.xcsh/agent/config.yml`\n\nसहेजी गई keys:\n\n- `theme.dark`\n- `theme.light`\n- `symbolPreset`\n- `colorBlindMode`\n\nलेगेसी माइग्रेशन मौजूद है: पुराने फ्लैट `theme: \"name\"` को luminance डिटेक्शन के आधार पर नेस्टेड `theme.dark` या `theme.light` में माइग्रेट किया जाता है।\n\n## कस्टम थीम बनाना (व्यावहारिक)\n\n1. कस्टम थीम डायरेक्टरी में फ़ाइल बनाएँ, जैसे `~/.xcsh/agent/themes/my-theme.json`।\n2. `name`, वैकल्पिक `vars`, और **सभी आवश्यक** `colors` टोकन शामिल करें।\n3. वैकल्पिक रूप से `symbols` और `export` शामिल करें।\n4. Settings में थीम चुनें (`Display -> Dark theme` या `Display -> Light theme`) इस पर निर्भर करते हुए कि आप कौन सा ऑटो स्लॉट चाहते हैं।\n\nन्यूनतम स्केलेटन। `colors` में हर key आवश्यक है — रनटाइम वैलिडेटर\n(`additionalProperties: false`) अनुपस्थित keys और अज्ञात keys दोनों को अस्वीकार करता है।\nशिप किए गए संदर्भ इम्प्लीमेंटेशन के लिए देखें\n[`packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json)\nऔर [`xcsh-light.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-light.json)।\n\nस्टेटस लाइन में दो समानांतर रंग प्रणालियाँ हैं जो issue #242 में दस्तावेज़ीकृत हैं:\n\n- Hex टेक्स्ट रंग (`statusLinePath`, `statusLineGitClean`, `statusLineGitDirty`,\n  `statusLineStaged`, `statusLineDirty`, `statusLineUntracked`) नॉन-पावरलाइन\n  रेंडरिंग को संचालित करते हैं।\n- 256-रंग पैलेट इंडेक्स (`statusLine<Segment>Bg` / `statusLine<Segment>Fg`)\n  पावरलाइन सेगमेंट फिल को संचालित करते हैं। वे ऊपर की hex keys से स्वतंत्र हैं —\n  दोनों सेट होने चाहिए।\n\n```json\n{\n  \"name\": \"my-theme\",\n  \"vars\": {\n    \"accent\": \"#7aa2f7\",\n    \"muted\": 244\n  },\n  \"colors\": {\n    \"accent\": \"accent\",\n    \"chromeAccent\": \"accent\",\n    \"spinnerAccent\": \"accent\",\n    \"contentAccent\": \"muted\",\n    \"border\": \"#4c566a\",\n    \"borderAccent\": \"accent\",\n    \"borderMuted\": \"muted\",\n    \"success\": \"#9ece6a\",\n    \"error\": \"#f7768e\",\n    \"warning\": \"#e0af68\",\n    \"muted\": \"muted\",\n    \"dim\": 240,\n    \"gutterSuccess\": \"#7dcfff\",\n    \"gutterWarning\": \"#e0af68\",\n    \"text\": \"\",\n    \"thinkingText\": \"muted\",\n\n    \"selectedBg\": \"#2a2f45\",\n    \"userMessageBg\": \"#1f2335\",\n    \"userMessageText\": \"\",\n    \"customMessageBg\": \"#24283b\",\n    \"customMessageText\": \"\",\n    \"customMessageLabel\": \"accent\",\n    \"toolPendingBg\": \"#1f2335\",\n    \"toolSuccessBg\": \"#1f2d2a\",\n    \"toolErrorBg\": \"#2d1f2a\",\n    \"toolTitle\": \"\",\n    \"toolOutput\": \"muted\",\n\n    \"mdHeading\": \"accent\",\n    \"mdLink\": \"accent\",\n    \"mdLinkUrl\": \"muted\",\n    \"mdCode\": \"#c0caf5\",\n    \"mdCodeBlock\": \"#c0caf5\",\n    \"mdCodeBlockBorder\": \"muted\",\n    \"mdQuote\": \"muted\",\n    \"mdQuoteBorder\": \"muted\",\n    \"mdHr\": \"muted\",\n    \"mdListBullet\": \"accent\",\n\n    \"toolDiffAdded\": \"#9ece6a\",\n    \"toolDiffRemoved\": \"#f7768e\",\n    \"toolDiffContext\": \"muted\",\n\n    \"syntaxComment\": \"#565f89\",\n    \"syntaxKeyword\": \"#bb9af7\",\n    \"syntaxFunction\": \"#7aa2f7\",\n    \"syntaxVariable\": \"#c0caf5\",\n    \"syntaxString\": \"#9ece6a\",\n    \"syntaxNumber\": \"#ff9e64\",\n    \"syntaxType\": \"#2ac3de\",\n    \"syntaxOperator\": \"#89ddff\",\n    \"syntaxPunctuation\": \"#9aa5ce\",\n    \"syntaxControl\": \"#bb9af7\",\n\n    \"thinkingOff\": 240,\n    \"thinkingMinimal\": 244,\n    \"thinkingLow\": \"#7aa2f7\",\n    \"thinkingMedium\": \"#2ac3de\",\n    \"thinkingHigh\": \"#bb9af7\",\n    \"thinkingXhigh\": \"#f7768e\",\n\n    \"bashMode\": \"#2ac3de\",\n    \"pythonMode\": \"#bb9af7\",\n\n    \"statusLineBg\": \"#16161e\",\n    \"statusLineSep\": 240,\n    \"statusLineModel\": \"#bb9af7\",\n    \"statusLinePath\": \"#7aa2f7\",\n    \"statusLineGitClean\": \"#9ece6a\",\n    \"statusLineGitDirty\": \"#e0af68\",\n    \"statusLineContext\": \"#2ac3de\",\n    \"statusLineSpend\": \"#7dcfff\",\n    \"statusLineStaged\": \"#9ece6a\",\n    \"statusLineDirty\": \"#e0af68\",\n    \"statusLineUntracked\": \"#f7768e\",\n    \"statusLineOutput\": \"#c0caf5\",\n    \"statusLineCost\": \"#ff9e64\",\n    \"statusLineSubagents\": \"#bb9af7\",\n\n    \"statusLineOsIconBg\": 7,\n    \"statusLineOsIconFg\": 232,\n    \"statusLinePathBg\": 4,\n    \"statusLinePathFg\": 254,\n    \"statusLineGitCleanBg\": 2,\n    \"statusLineGitCleanFg\": 0,\n    \"statusLineGitDirtyBg\": 3,\n    \"statusLineGitDirtyFg\": 0,\n    \"statusLineGitStagedBg\": 64,\n    \"statusLineGitStagedFg\": 0,\n    \"statusLineGitUntrackedBg\": 39,\n    \"statusLineGitUntrackedFg\": 0,\n    \"statusLineGitConflictBg\": 1,\n    \"statusLineGitConflictFg\": 7,\n    \"statusLinePlanModeBg\": 236,\n    \"statusLinePlanModeFg\": 117,\n    \"statusLineProfileXcshBg\": \"accent\",\n    \"statusLineProfileXcshFg\": 231\n  }\n}\n```\n\n## कस्टम थीम का परीक्षण\n\nइस वर्कफ़्लो का उपयोग करें:\n\n1. इंटरएक्टिव मोड शुरू करें (स्टार्टअप से वॉचर सक्षम)।\n2. सेटिंग्स खोलें और थीम मान प्रीव्यू करें (लाइव `previewTheme`)।\n3. कस्टम थीम फ़ाइलों के लिए, चलते समय JSON संपादित करें और सेव पर ऑटो-रीलोड की पुष्टि करें।\n4. महत्वपूर्ण सतहों का परीक्षण करें:\n   - मार्कडाउन रेंडरिंग\n   - टूल ब्लॉक (pending/success/error)\n   - diff रेंडरिंग (added/removed/context)\n   - स्टेटस लाइन पठनीयता\n   - थिंकिंग स्तर बॉर्डर परिवर्तन\n   - bash/python मोड बॉर्डर रंग\n5. यदि आपकी थीम glyph चौड़ाई/दिखावट पर निर्भर करती है तो दोनों सिंबल प्रीसेट सत्यापित करें।\n\n## वास्तविक बाधाएँ और सावधानियाँ\n\n- कस्टम थीम के लिए सभी `colors` टोकन आवश्यक हैं।\n- `export` और `symbols` वैकल्पिक हैं।\n- थीम JSON में `$schema` सूचनात्मक है; रनटाइम सत्यापन कोड में संकलित TypeBox स्कीमा द्वारा लागू किया जाता है।\n- `setTheme` विफलता `dark` पर फ़ॉलबैक करती है; `previewTheme` विफलता वर्तमान थीम को प्रतिस्थापित नहीं करती।\n- फ़ाइल वॉचर रीलोड त्रुटियाँ वर्तमान लोड की गई थीम को तब तक बनाए रखती हैं जब तक कि सफल रीलोड या फ़ॉलबैक पथ ट्रिगर न हो जाए।\n",
	"hi/tui/tree.md": "---\ntitle: Tree कमांड संदर्भ\ndescription: सत्र इतिहास और वार्तालाप शाखाओं को विज़ुअलाइज़ करने के लिए /tree कमांड संदर्भ।\nsidebar:\n  order: 4\n  label: /tree कमांड\ni18n:\n  sourceHash: ee0e412fe993\n  translator: machine\n---\n\n# `/tree` कमांड संदर्भ\n\n`/tree` इंटरएक्टिव **Session Tree** नेविगेटर खोलता है। यह आपको वर्तमान सत्र फ़ाइल में किसी भी प्रविष्टि पर जाने और उस बिंदु से जारी रखने की सुविधा देता है।\n\nयह एक इन-फ़ाइल लीफ़ मूव है, न कि कोई नया सत्र एक्सपोर्ट।\n\n## `/tree` क्या करता है\n\n- वर्तमान सत्र प्रविष्टियों से एक ट्री बनाता है (`SessionManager.getTree()`)\n- `TreeSelectorComponent` को कीबोर्ड नेविगेशन, फ़िल्टर और सर्च के साथ खोलता है\n- चयन पर, `AgentSession.navigateTree(targetId, { summarize, customInstructions })` को कॉल करता है\n- नए लीफ़ पाथ से दृश्यमान चैट को पुनर्निर्मित करता है\n- user/custom संदेश चुनते समय वैकल्पिक रूप से एडिटर टेक्स्ट प्रीफ़िल करता है\n\nमुख्य इम्प्लीमेंटेशन:\n\n- `src/modes/controllers/input-controller.ts` (`/tree`, keybinding वायरिंग, double-escape व्यवहार)\n- `src/modes/controllers/selector-controller.ts` (tree UI लॉन्च + summary prompt फ्लो)\n- `src/modes/components/tree-selector.ts` (नेविगेशन, फ़िल्टर, सर्च, लेबल, रेंडरिंग)\n- `src/session/agent-session.ts` (`navigateTree` लीफ़ स्विचिंग + वैकल्पिक सारांश)\n- `src/session/session-manager.ts` (`getTree`, `branch`, `branchWithSummary`, `resetLeaf`, लेबल पर्सिस्टेंस)\n\n## इसे कैसे खोलें\n\nनिम्नलिखित में से कोई भी एक ही सेलेक्टर खोलता है:\n\n- `/tree`\n- कॉन्फ़िगर किया गया keybinding एक्शन `tree`\n- खाली एडिटर पर double-escape जब `doubleEscapeAction = \"tree\"` (डिफ़ॉल्ट)\n- `/branch` जब `doubleEscapeAction = \"tree\"` (tree सेलेक्टर की ओर रूट करता है, न कि user-only branch picker की ओर)\n\n## Tree UI मॉडल\n\nट्री को सत्र प्रविष्टि पेरेंट पॉइंटर्स (`id` / `parentId`) से रेंडर किया जाता है।\n\n- बच्चों को टाइमस्टैम्प के अनुसार आरोही क्रम में क्रमबद्ध किया जाता है (पुराने पहले, नए नीचे)\n- सक्रिय शाखा (रूट से वर्तमान लीफ़ तक का पाथ) एक बुलेट के साथ चिह्नित होती है\n- लेबल (यदि मौजूद हों) नोड टेक्स्ट से पहले `[label]` के रूप में रेंडर होते हैं\n- यदि एकाधिक रूट मौजूद हों (अनाथ/टूटी हुई पेरेंट चेन), तो उन्हें एक वर्चुअल ब्रांचिंग रूट के अंतर्गत दिखाया जाता है\n\n```text\nExample tree view (active path marked with •):\n\n├─ user: \"Start task\"\n│  └─ assistant: \"Plan\"\n│     ├─ • user: \"Try approach A\"\n│     │  └─ • assistant: \"A result\"\n│     │     └─ • [milestone] user: \"Continue A\"\n│     └─ user: \"Try approach B\"\n│        └─ assistant: \"B result\"\n```\n\nसेलेक्टर वर्तमान चयन के आसपास पुनः केंद्रित होता है और अधिकतम निम्नलिखित दिखाता है:\n\n- `max(5, floor(terminalHeight / 2))` पंक्तियाँ\n\n## Tree सेलेक्टर के अंदर कीबाइंडिंग\n\n- `Up` / `Down`: चयन को आगे/पीछे ले जाएं (रैप करता है)\n- `Left` / `Right`: पेज अप / पेज डाउन\n- `Enter`: नोड चुनें\n- `Esc`: सर्च सक्रिय होने पर उसे साफ़ करें; अन्यथा सेलेक्टर बंद करें\n- `Ctrl+C`: सेलेक्टर बंद करें\n- `Type`: सर्च क्वेरी में जोड़ें\n- `Backspace`: सर्च कैरेक्टर हटाएं\n- `Shift+L`: चयनित प्रविष्टि पर लेबल संपादित करें/साफ़ करें\n- `Ctrl+O`: फ़िल्टर को आगे चक्र करें\n- `Shift+Ctrl+O`: फ़िल्टर को पीछे चक्र करें\n- `Alt+D/T/U/L/A`: सीधे किसी विशिष्ट फ़िल्टर मोड पर जाएं\n\n## फ़िल्टर और सर्च सेमेंटिक्स\n\nफ़िल्टर मोड (`TreeList`):\n\n1. `default`\n2. `no-tools`\n3. `user-only`\n4. `labeled-only`\n5. `all`\n\n### `default`\n\nअधिकांश वार्तालापात्मक नोड दिखाता है, लेकिन बुककीपिंग प्रविष्टि प्रकार छुपाता है:\n\n- `label`\n- `custom`\n- `model_change`\n- `thinking_level_change`\n\n### `no-tools`\n\n`default` के समान, साथ ही `toolResult` संदेश भी छुपाता है।\n\n### `user-only`\n\nकेवल `message` प्रविष्टियाँ जहाँ role `user` है।\n\n### `labeled-only`\n\nकेवल वे प्रविष्टियाँ जो वर्तमान में किसी लेबल को रिज़ॉल्व करती हैं।\n\n### `all`\n\nसत्र ट्री में सब कुछ, जिसमें बुककीपिंग/custom प्रविष्टियाँ शामिल हैं।\n\n### Tool-only assistant नोड व्यवहार\n\nAssistant संदेश जिनमें **केवल tool calls** हों (कोई टेक्स्ट नहीं) डिफ़ॉल्ट रूप से सभी फ़िल्टर किए गए व्यूज़ में छुपे रहते हैं, जब तक कि:\n\n- संदेश error/aborted न हो (`stopReason` `stop`/`toolUse` न हो), या\n- यह वर्तमान लीफ़ न हो (हमेशा दृश्यमान रखा जाता है)\n\n### सर्च व्यवहार\n\n- क्वेरी को स्पेस द्वारा टोकनाइज़ किया जाता है\n- मिलान case-insensitive होता है\n- सभी टोकन मिलने चाहिए (AND सेमेंटिक्स)\n- खोजने योग्य टेक्स्ट में लेबल, role और प्रकार-विशिष्ट कंटेंट शामिल है (संदेश टेक्स्ट, branch summary टेक्स्ट, custom type, tool command स्निपेट, आदि)\n\n## चयन परिणाम (महत्वपूर्ण)\n\n`navigateTree` चयनित प्रविष्टि प्रकार से नए लीफ़ व्यवहार की गणना करता है:\n\n### `user` संदेश चुनना\n\n- नया लीफ़ चयनित प्रविष्टि का `parentId` बन जाता है\n- यदि parent `null` है (रूट user संदेश), तो लीफ़ रूट पर रीसेट होता है (`resetLeaf()`)\n- चयनित संदेश टेक्स्ट संपादन/पुनः सबमिट के लिए एडिटर में कॉपी हो जाता है\n\n### `custom_message` चुनना\n\n- user संदेशों जैसा ही लीफ़ नियम (`parentId`)\n- टेक्स्ट कंटेंट निकाला जाता है और एडिटर में कॉपी होता है\n\n### non-user नोड चुनना (assistant/tool/summary/compaction/custom bookkeeping/आदि)\n\n- नया लीफ़ चयनित नोड id बन जाता है\n- एडिटर प्रीफ़िल नहीं होता\n\n### वर्तमान लीफ़ चुनना\n\n- कोई क्रिया नहीं; सेलेक्टर \"Already at this point\" के साथ बंद होता है\n\n```text\nSelection decision (simplified):\n\nselected node\n   │\n   ├─ is current leaf? ── yes ──> close selector (no-op)\n   │\n   ├─ is user/custom_message? ── yes ──> leaf := parentId (or resetLeaf for root)\n   │                                     + prefill editor text\n   │\n   └─ otherwise ──> leaf := selected node id\n                    + no editor prefill\n```\n\n## स्विच पर सारांश फ्लो\n\nSummary prompt `branchSummary.enabled` (डिफ़ॉल्ट: `false`) द्वारा नियंत्रित होता है।\n\nसक्षम होने पर, नोड चुनने के बाद UI पूछता है:\n\n- `No summary`\n- `Summarize`\n- `Summarize with custom prompt`\n\nफ्लो विवरण:\n\n- Summary prompt में Escape tree सेलेक्टर को पुनः खोलता है\n- Custom prompt रद्द करने पर summary choice loop पर वापस लौटता है\n- सारांश के दौरान, UI लोडर दिखाता है और `Esc` को `abortBranchSummary()` से बाइंड करता है\n- यदि सारांश बीच में रद्द हो, तो tree सेलेक्टर पुनः खुलता है और कोई मूव लागू नहीं होता\n\n`navigateTree` आंतरिक विवरण:\n\n- पुराने लीफ़ से सामान्य पूर्वज तक abandoned-branch प्रविष्टियाँ एकत्र करता है\n- `session_before_tree` emit करता है (एक्सटेंशन रद्द कर सकते हैं या सारांश inject कर सकते हैं)\n- डिफ़ॉल्ट summarizer का उपयोग केवल तब करता है जब अनुरोध हो और आवश्यक हो\n- निम्नलिखित के साथ मूव लागू करता है:\n  - `branchWithSummary(...)` जब सारांश मौजूद हो\n  - `branch(newLeafId)` सारांश के बिना non-root मूव के लिए\n  - `resetLeaf()` सारांश के बिना root मूव के लिए\n- पुनर्निर्मित सत्र संदर्भ के साथ agent conversation को बदलता है\n- `session_tree` emit करता है\n\nनोट: यदि user सारांश का अनुरोध करता है लेकिन सारांश बनाने के लिए कुछ नहीं है, तो नेविगेशन summary प्रविष्टि बनाए बिना आगे बढ़ता है।\n\n## लेबल\n\nTree UI में लेबल संपादन `appendLabelChange(targetId, label)` को कॉल करता है।\n\n- non-empty लेबल resolved लेबल सेट/अपडेट करता है\n- empty लेबल उसे साफ़ करता है\n- लेबल append-only `label` प्रविष्टियों के रूप में संग्रहीत होते हैं\n- tree नोड्स resolved लेबल स्थिति प्रदर्शित करते हैं, न कि raw label-entry इतिहास\n\n## `/tree` और संबंधित ऑपरेशन\n\n| ऑपरेशन | स्कोप | परिणाम |\n|---|---|---|\n| `/tree` | वर्तमान सत्र फ़ाइल | चयनित बिंदु पर लीफ़ मूव करता है (वही फ़ाइल) |\n| `/branch` | आमतौर पर वर्तमान सत्र फ़ाइल -> नई सत्र फ़ाइल | डिफ़ॉल्ट रूप से चयनित **user** संदेश से नई सत्र फ़ाइल में ब्रांच करता है; यदि `doubleEscapeAction = \"tree\"` हो, तो `/branch` tree navigation UI खोलता है |\n| `/fork` | पूरा वर्तमान सत्र | सत्र को एक नई persisted सत्र फ़ाइल में डुप्लीकेट करता है |\n| `/resume` | सत्र सूची | किसी अन्य सत्र फ़ाइल पर स्विच करता है |\n\nमुख्य अंतर: `/tree` एक सत्र फ़ाइल के अंदर नेविगेशन/पुनःस्थिति उपकरण है। `/branch`, `/fork`, और `/resume` सभी सत्र-फ़ाइल संदर्भ बदलते हैं।\n\n## ऑपरेटर वर्कफ़्लो\n\n### वर्तमान शाखा खोए बिना पहले के user prompt से पुनः चलाएं\n\n1. `/tree`\n2. पहले के user संदेश को खोजें/चुनें\n3. `No summary` चुनें (या यदि आवश्यक हो तो सारांश करें)\n4. एडिटर में प्रीफ़िल्ड टेक्स्ट संपादित करें\n5. सबमिट करें\n\nप्रभाव: उसी सत्र फ़ाइल के अंदर चयनित बिंदु से नई शाखा बढ़ती है।\n\n### संदर्भ ब्रेडक्रम्ब के साथ वर्तमान शाखा छोड़ें\n\n1. `branchSummary.enabled` सक्षम करें\n2. `/tree` और लक्ष्य नोड चुनें\n3. `Summarize` (या custom prompt) चुनें\n\nप्रभाव: जारी रखने से पहले लक्ष्य स्थिति पर एक `branch_summary` प्रविष्टि जोड़ी जाती है।\n\n### छुपी हुई बुककीपिंग प्रविष्टियाँ जाँचें\n\n1. `/tree`\n2. `Alt+A` दबाएं (all)\n3. `model`, `thinking`, `custom`, या लेबल खोजें\n\nप्रभाव: केवल वार्तालापात्मक नोड्स नहीं, बल्कि पूरी आंतरिक टाइमलाइन देखें।\n\n### बाद की जंप के लिए पिवट पॉइंट बुकमार्क करें\n\n1. `/tree`\n2. प्रविष्टि पर जाएं\n3. `Shift+L` और लेबल सेट करें\n4. बाद में त्वरित जंप के लिए `Alt+L` (`labeled-only`) का उपयोग करें\n\nप्रभाव: टिकाऊ शाखा मील के पत्थरों के बीच तेज़ नेविगेशन।\n",
	"hi/tui/tui-runtime-internals.md": "---\ntitle: TUI रनटाइम इंटर्नल्स\ndescription: >-\n  टर्मिनल UI रनटाइम इंटर्नल्स जिसमें रेंडरिंग पाइपलाइन, इनपुट हैंडलिंग और स्टेट\n  मैनेजमेंट शामिल हैं।\nsidebar:\n  order: 2\n  label: रनटाइम इंटर्नल्स\ni18n:\n  sourceHash: cc8f7dcce46a\n  translator: machine\n---\n\n# TUI रनटाइम इंटर्नल्स\n\nयह दस्तावेज़ इंटरैक्टिव मोड में टर्मिनल इनपुट से रेंडर किए गए आउटपुट तक के नॉन-थीम रनटाइम पाथ का मानचित्रण करता है। यह `packages/tui` में व्यवहार और `packages/coding-agent` कंट्रोलर्स से इसके इंटीग्रेशन पर केंद्रित है।\n\n## रनटाइम परतें और स्वामित्व\n\n- **`packages/tui` इंजन**: टर्मिनल लाइफसाइकिल, stdin नॉर्मलाइज़ेशन, फोकस रूटिंग, रेंडर शेड्यूलिंग, डिफरेंशियल पेंटिंग, ओवरले कंपोज़िशन, हार्डवेयर कर्सर प्लेसमेंट।\n- **`packages/coding-agent` इंटरैक्टिव मोड**: कम्पोनेंट ट्री बनाता है, एडिटर कॉलबैक और कीमैप बाइंड करता है, एजेंट/सेशन इवेंट्स पर प्रतिक्रिया देता है, और डोमेन स्टेट (स्ट्रीमिंग, टूल एक्जीक्यूशन, रिट्रीज़, प्लान मोड) को UI घटकों में अनुवादित करता है।\n\nबाउंड्री नियम: TUI इंजन मैसेज-अज्ञेय है। यह केवल `Component.render(width)`, `handleInput(data)`, फोकस और ओवरले जानता है। एजेंट सेमांटिक्स इंटरैक्टिव कंट्रोलर्स में रहते हैं।\n\n## इम्प्लीमेंटेशन फ़ाइलें\n\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/components/custom-editor.ts`](../../packages/coding-agent/src/modes/components/custom-editor.ts)\n- [`../../tui/src/tui.ts`](../../packages/tui/src/tui.ts)\n- [`../../tui/src/terminal.ts`](../../packages/tui/src/terminal.ts)\n- [`../../tui/src/editor-component.ts`](../../packages/tui/src/editor-component.ts)\n- [`../../tui/src/stdin-buffer.ts`](../../packages/tui/src/stdin-buffer.ts)\n- [`../../tui/src/components/loader.ts`](../../packages/tui/src/components/loader.ts)\n\n## बूट और कम्पोनेंट ट्री असेंबली\n\n`InteractiveMode` `TUI(new ProcessTerminal(), showHardwareCursor)` का निर्माण करता है और परसिस्टेंट कंटेनर बनाता है:\n\n- `chatContainer`\n- `pendingMessagesContainer`\n- `statusContainer`\n- `todoContainer`\n- `statusLine`\n- `editorContainer` (`CustomEditor` रखता है)\n\n`init()` उस क्रम में ट्री को वायर करता है, एडिटर पर फोकस करता है, `InputController` के माध्यम से इनपुट हैंडलर रजिस्टर करता है, TUI शुरू करता है, और एक फोर्स्ड रेंडर का अनुरोध करता है।\n\nएक फोर्स्ड रेंडर (`requestRender(true)`) रिपेंटिंग से पहले पिछली-लाइन कैश और कर्सर बुककीपिंग को रीसेट करता है।\n\n## टर्मिनल लाइफसाइकिल और stdin नॉर्मलाइज़ेशन\n\n`ProcessTerminal.start()`:\n\n1. रॉ मोड और ब्रैकेटेड पेस्ट सक्षम करता है।\n2. रिसाइज़ हैंडलर अटैच करता है।\n3. आंशिक एस्केप चंक्स को पूर्ण सीक्वेंस में विभाजित करने के लिए `StdinBuffer` बनाता है।\n4. Kitty कीबोर्ड प्रोटोकॉल सपोर्ट (`CSI ? u`) क्वेरी करता है, फिर सपोर्टेड होने पर प्रोटोकॉल फ्लैग सक्षम करता है।\n5. Windows पर, `kernel32` मोड फ्लैग के माध्यम से VT इनपुट एनेबलमेंट का प्रयास करता है।\n\n`StdinBuffer` व्यवहार:\n\n- खंडित एस्केप सीक्वेंस (CSI/OSC/DCS/APC/SS3) बफर करता है।\n- `data` केवल तभी एमिट करता है जब कोई सीक्वेंस पूर्ण हो या टाइमआउट-फ्लश हो।\n- ब्रैकेटेड पेस्ट डिटेक्ट करता है और रॉ पेस्ट किए गए टेक्स्ट के साथ `paste` इवेंट एमिट करता है।\n\nयह आंशिक एस्केप चंक्स को सामान्य कीप्रेस के रूप में गलत व्याख्यायित होने से रोकता है।\n\n## इनपुट रूटिंग और फोकस मॉडल\n\nइनपुट पाथ:\n\n`stdin -> ProcessTerminal -> StdinBuffer -> TUI.#handleInput -> focusedComponent.handleInput`\n\nरूटिंग विवरण:\n\n1. TUI पहले रजिस्टर्ड इनपुट लिसनर चलाता है (`addInputListener`), जो consume/transform व्यवहार की अनुमति देता है।\n2. TUI कम्पोनेंट डिस्पैच से पहले ग्लोबल डीबग शॉर्टकट (`shift+ctrl+d`) हैंडल करता है।\n3. यदि फोकस्ड कम्पोनेंट किसी ओवरले से संबंधित है जो अब hidden/invisible है, तो TUI अगले visible ओवरले या सेव किए गए pre-overlay फोकस को फोकस रीअसाइन करता है।\n4. कीमुक्ति इवेंट्स फ़िल्टर किए जाते हैं जब तक फोकस्ड कम्पोनेंट `wantsKeyRelease = true` सेट नहीं करता।\n5. डिस्पैच के बाद, TUI रेंडर शेड्यूल करता है।\n\n`setFocus()` `Focusable.focused` को भी टॉगल करता है, जो नियंत्रित करता है कि घटक हार्डवेयर कर्सर प्लेसमेंट के लिए `CURSOR_MARKER` एमिट करते हैं या नहीं।\n\n## की हैंडलिंग विभाजन: एडिटर बनाम कंट्रोलर\n\n`CustomEditor` पहले उच्च-प्राथमिकता कॉम्बो इंटरसेप्ट करता है (escape, ctrl-c/d/z, ctrl-v, ctrl-p वेरिएंट, ctrl-t, alt-up, एक्सटेंशन कस्टम कीज़) और बाकी को बेस `Editor` व्यवहार (टेक्स्ट एडिटिंग, हिस्ट्री, ऑटोकम्पलीट, कर्सर मूवमेंट) को डेलीगेट करता है।\n\n`InputController.setupKeyHandlers()` फिर एडिटर कॉलबैक को मोड एक्शन से बाइंड करता है:\n\n- `Escape` पर कैंसेलेशन / मोड एग्जिट\n- डबल `Ctrl+C` या खाली-एडिटर `Ctrl+D` पर शटडाउन\n- `Ctrl+Z` पर सस्पेंड/रिज्यूम\n- स्लैश-कमांड और सेलेक्टर हॉटकीज़\n- फॉलो-अप/डीक्यू टॉगल और एक्सपेंशन टॉगल\n\nयह की पार्सिंग/एडिटर मेकैनिक्स को `packages/tui` में और मोड सेमांटिक्स को coding-agent कंट्रोलर्स में रखता है।\n\n## रेंडर लूप और डिफिंग रणनीति\n\n`TUI.requestRender()` को `process.nextTick` का उपयोग करके प्रति टिक एक रेंडर तक डिबाउंस किया जाता है। एक ही टर्न में कई स्टेट परिवर्तन कोलेस होते हैं।\n\n`#doRender()` पाइपलाइन:\n\n1. रूट कम्पोनेंट ट्री को `newLines` में रेंडर करें।\n2. दृश्यमान ओवरले कम्पोज़िट करें (यदि कोई हो)।\n3. दृश्यमान व्यूपोर्ट लाइनों से `CURSOR_MARKER` एक्सट्रैक्ट और स्ट्रिप करें।\n4. नॉन-इमेज लाइनों के लिए सेगमेंट रीसेट सफिक्स अपेंड करें।\n5. फुल रिपेंट बनाम डिफरेंशियल पैच चुनें:\n   - पहला फ्रेम\n   - चौड़ाई परिवर्तन\n   - `clearOnShrink` सक्षम और कोई ओवरले नहीं के साथ श्रिंक\n   - पिछले व्यूपोर्ट के ऊपर एडिट\n6. डिफरेंशियल अपडेट के लिए, केवल बदली हुई लाइन रेंज पैच करें और आवश्यक होने पर पुरानी ट्रेलिंग लाइनें क्लियर करें।\n7. IME सपोर्ट के लिए हार्डवेयर कर्सर रिपोज़िशन करें।\n\nरेंडर राइट्स फ्लिकर/टियरिंग को कम करने के लिए सिंक्रोनाइज़्ड आउटपुट मोड (`CSI ? 2026 h/l`) का उपयोग करते हैं।\n\n## रेंडर सेफ्टी कॉन्स्ट्रेंट्स\n\n`TUI` में महत्वपूर्ण सेफ्टी चेक:\n\n- नॉन-इमेज रेंडर की गई लाइनें टर्मिनल चौड़ाई से अधिक नहीं होनी चाहिए; ओवरफ्लो थ्रो करता है और क्रैश डायग्नोस्टिक्स लिखता है।\n- ओवरले कम्पोज़िटिंग में डिफेंसिव ट्रंकेशन और पोस्ट-कम्पोज़िट चौड़ाई वेरिफिकेशन शामिल है।\n- चौड़ाई परिवर्तन फुल रीड्रॉ को बाध्य करता है क्योंकि रैपिंग सेमांटिक्स बदलती हैं।\n- मूवमेंट से पहले कर्सर पोज़िशन क्लैम्प की जाती है।\n\nये कॉन्स्ट्रेंट्स रनटाइम एनफोर्समेंट हैं, केवल कन्वेंशन नहीं।\n\n## रिसाइज़ हैंडलिंग\n\nरिसाइज़ इवेंट्स `ProcessTerminal` से `TUI.requestRender()` तक इवेंट-ड्रिवन हैं।\n\nप्रभाव:\n\n- किसी भी चौड़ाई परिवर्तन से फुल रीड्रॉ ट्रिगर होता है।\n- व्यूपोर्ट/टॉप ट्रैकिंग (`#previousViewportTop`, `#maxLinesRendered`) कंटेंट या टर्मिनल साइज़ बदलने पर अमान्य रिलेटिव कर्सर मैथ से बचती है।\n- ओवरले विज़िबिलिटी टर्मिनल डायमेंशन पर निर्भर हो सकती है (`OverlayOptions.visible`); रिसाइज़ के बाद ओवरले non-visible हो जाने पर फोकस करेक्ट किया जाता है।\n\n## स्ट्रीमिंग और इंक्रीमेंटल UI अपडेट\n\n`EventController` `AgentSessionEvent` को सब्सक्राइब करता है और UI को इंक्रीमेंटली अपडेट करता है:\n\n- `agent_start`: `statusContainer` में लोडर शुरू करता है।\n- `message_start` असिस्टेंट: `streamingComponent` बनाता है और माउंट करता है।\n- `message_update`: स्ट्रीमिंग असिस्टेंट कंटेंट अपडेट करता है; टूल कॉल्स आने पर टूल एक्जीक्यूशन घटक बनाता/अपडेट करता है।\n- `tool_execution_update/end`: टूल रिज़ल्ट घटक और कम्पलीशन स्टेट अपडेट करता है।\n- `message_end`: असिस्टेंट स्ट्रीम को फाइनलाइज़ करता है, aborted/error एनोटेशन हैंडल करता है, नॉर्मल स्टॉप पर पेंडिंग टूल args को कम्पलीट मार्क करता है।\n- `agent_end`: लोडर्स बंद करता है, ट्रांजिएंट स्ट्रीम स्टेट क्लियर करता है, डिफर्ड मॉडल स्विच फ्लश करता है, बैकग्राउंड होने पर कम्पलीशन नोटिफिकेशन जारी करता है।\n\nRead-tool ग्रुपिंग जानबूझकर स्टेटफुल है (`#lastReadGroup`) ताकि नॉन-रीड ब्रेक होने तक लगातार रीड टूल कॉल्स को एक विज़ुअल ब्लॉक में कोलेस किया जा सके।\n\n## स्टेटस और लोडर ऑर्केस्ट्रेशन\n\nस्टेटस लेन स्वामित्व:\n\n- `statusContainer` ट्रांजिएंट लोडर रखता है (`loadingAnimation`, `autoCompactionLoader`, `retryLoader`)।\n- `statusLine` परसिस्टेंट स्टेटस/हुक्स/प्लान इंडिकेटर रेंडर करता है और एडिटर टॉप बॉर्डर अपडेट ड्राइव करता है।\n\nलोडर व्यवहार:\n\n- `Loader` इंटरवल के माध्यम से हर 80ms अपडेट होता है और प्रत्येक फ्रेम रेंडर का अनुरोध करता है।\n- ऑटो-कॉम्पेक्शन और ऑटो-रिट्री के दौरान उन ऑपरेशन को कैंसल करने के लिए एस्केप हैंडलर अस्थायी रूप से ओवरराइड किए जाते हैं।\n- end/cancel पाथ पर, कंट्रोलर्स पिछले एस्केप हैंडलर रिस्टोर करते हैं और लोडर घटक बंद/क्लियर करते हैं।\n\n## मोड ट्रांजिशन और बैकग्राउंडिंग\n\n### Bash/Python इनपुट मोड\n\nइनपुट टेक्स्ट प्रीफिक्स एडिटर बॉर्डर मोड फ्लैग टॉगल करते हैं:\n\n- `!` -> bash मोड\n- `$` (नॉन-टेम्पलेट लिटरल प्रीफिक्स) -> python मोड\n\nEscape इनएक्टिव मोड से एडिटर टेक्स्ट क्लियर करके और बॉर्डर कलर रिस्टोर करके बाहर निकलता है; जब एक्जीक्यूशन एक्टिव हो, escape चल रहे टास्क को एबॉर्ट करता है।\n\n### प्लान मोड\n\n`InteractiveMode` प्लान मोड फ्लैग, स्टेटस-लाइन स्टेट, एक्टिव टूल्स और मॉडल स्विचिंग ट्रैक करता है। Enter/exit सेशन मोड एंट्री और स्टेटस/UI स्टेट अपडेट करता है, जिसमें स्ट्रीमिंग एक्टिव होने पर डिफर्ड मॉडल स्विच शामिल है।\n\n### सस्पेंड/रिज्यूम (`Ctrl+Z`)\n\n`InputController.handleCtrlZ()`:\n\n1. TUI रिस्टार्ट करने और फोर्स रेंडर के लिए वन-शॉट `SIGCONT` हैंडलर रजिस्टर करता है।\n2. सस्पेंड से पहले TUI बंद करता है।\n3. प्रोसेस ग्रुप को `SIGTSTP` भेजता है।\n\n### बैकग्राउंड मोड (`/background` या `/bg`)\n\n`handleBackgroundCommand()`:\n\n- आइडल होने पर रिजेक्ट करता है।\n- टूल UI कंटेक्स्ट को नॉन-इंटरैक्टिव (`hasUI=false`) पर स्विच करता है ताकि इंटरैक्टिव UI टूल्स जल्दी फेल हों।\n- लोडर/स्टेटस लाइन बंद करता है और फोरग्राउंड इवेंट हैंडलर अनसब्सक्राइब करता है।\n- बैकग्राउंड इवेंट हैंडलर सब्सक्राइब करता है (मुख्यतः `agent_end` की प्रतीक्षा करता है)।\n- TUI बंद करता है और `SIGTSTP` भेजता है (POSIX जॉब कंट्रोल पाथ)।\n\nबैकग्राउंड में `agent_end` पर बिना क्यूड वर्क के, कंट्रोलर कम्पलीशन नोटिफिकेशन भेजता है और शटडाउन करता है।\n\n## कैंसेलेशन पाथ\n\nप्राथमिक कैंसेलेशन इनपुट:\n\n- एक्टिव स्ट्रीम लोडर के दौरान `Escape`: क्यूड मैसेज एडिटर में रिस्टोर करता है और एजेंट एबॉर्ट करता है।\n- bash/python एक्जीक्यूशन के दौरान `Escape`: चल रहे कमांड को एबॉर्ट करता है।\n- ऑटो-कॉम्पेक्शन/रिट्री के दौरान `Escape`: अस्थायी एस्केप हैंडलर के माध्यम से डेडिकेटेड एबॉर्ट मेथड इनवोक करता है।\n- `Ctrl+C` सिंगल प्रेस: एडिटर क्लियर; 500ms के भीतर डबल प्रेस: शटडाउन।\n\nकैंसेलेशन स्टेट-कंडिशनल है; एक ही की रनटाइम स्टेट के आधार पर abort, mode-exit, selector trigger, या no-op का अर्थ दे सकती है।\n\n## इवेंट-ड्रिवन बनाम थ्रॉटल्ड व्यवहार\n\nइवेंट-ड्रिवन अपडेट:\n\n- एजेंट सेशन इवेंट्स (`EventController`)\n- की इनपुट कॉलबैक (`InputController`)\n- टर्मिनल रिसाइज़ कॉलबैक\n- `InteractiveMode` में थीम/ब्रांच वॉचर\n\nथ्रॉटल्ड/डिबाउंस्ड पाथ:\n\n- TUI रेंडरिंग टिक-डिबाउंस्ड है (`requestRender` कोलेसिंग)।\n- लोडर एनिमेशन फिक्स्ड-इंटरवल (80ms) है, प्रत्येक फ्रेम रेंडर का अनुरोध करता है।\n- एडिटर ऑटोकम्पलीट अपडेट (`Editor` के अंदर) डिबाउंस टाइमर का उपयोग करते हैं, टाइपिंग के दौरान रिकम्प्यूट चर्न कम करते हैं।\n\nरनटाइम इसलिए बाउंडेड रेंडर कैडेंस के साथ इवेंट-ड्रिवन स्टेट ट्रांजिशन को मिलाता है ताकि रिपेंट स्टॉर्म के बिना इंटरैक्टिविटी रिस्पॉन्सिव बनी रहे।\n",
	"hi/tui/tui.md": "---\ntitle: एक्सटेंशन और कस्टम टूल्स के लिए TUI इंटीग्रेशन\ndescription: 'एक्सटेंशन, कस्टम टूल्स और कस्टम रेंडरर्स के लिए TUI इंटीग्रेशन कॉन्ट्रैक्ट।'\nsidebar:\n  order: 1\n  label: एक्सटेंशन इंटीग्रेशन\ni18n:\n  sourceHash: 47f8f2b2045e\n  translator: machine\n---\n\n# एक्सटेंशन और कस्टम टूल्स के लिए TUI इंटीग्रेशन\n\nयह दस्तावेज़ **वर्तमान** TUI कॉन्ट्रैक्ट को कवर करता है जो `packages/coding-agent` और `packages/tui` द्वारा एक्सटेंशन UI, कस्टम टूल UI, और कस्टम रेंडरर्स के लिए उपयोग किया जाता है।\n\n## यह सबसिस्टम क्या है\n\nरनटाइम में दो लेयर हैं:\n\n- **रेंडरिंग इंजन (`packages/tui`)**: डिफरेंशियल टर्मिनल रेंडरर, इनपुट डिस्पैच, फोकस, ओवरले, कर्सर प्लेसमेंट।\n- **इंटीग्रेशन लेयर (`packages/coding-agent`)**: एक्सटेंशन/कस्टम-टूल कंपोनेंट्स को माउंट करता है, कीबाइंडिंग्स/थीम को वायर करता है, और एडिटर स्टेट को रिस्टोर करता है।\n\n## मोड के अनुसार रनटाइम व्यवहार\n\n| मोड | `ctx.ui.custom(...)` उपलब्धता | नोट्स |\n| --- | --- | --- |\n| इंटरैक्टिव TUI | समर्थित | कंपोनेंट एडिटर एरिया में माउंट होता है, फोकस होता है, और रिज़ॉल्व करने के लिए `done(result)` कॉल करना अनिवार्य है। |\n| बैकग्राउंड/हेडलेस | इंटरैक्टिव नहीं | UI कॉन्टेक्स्ट नो-ऑप है (`hasUI === false`)। |\n| RPC मोड | समर्थित नहीं | `custom()` `Promise<never>` रिटर्न करता है और TUI कंपोनेंट्स माउंट नहीं करता। |\n\nयदि आपका एक्सटेंशन/टूल नॉन-इंटरैक्टिव मोड में चल सकता है, तो `ctx.hasUI` / `pi.hasUI` से गार्ड करें।\n\n## कोर कंपोनेंट कॉन्ट्रैक्ट (`@f5-sales-demo/pi-tui`)\n\n`packages/tui/src/tui.ts` में परिभाषित है:\n\n```ts\nexport interface Component {\n  render(width: number): string[];\n  handleInput?(data: string): void;\n  wantsKeyRelease?: boolean;\n  invalidate(): void;\n}\n```\n\n`Focusable` अलग है:\n\n```ts\nexport interface Focusable {\n  focused: boolean;\n}\n```\n\nकर्सर व्यवहार `CURSOR_MARKER` का उपयोग करता है (`getCursorPosition` का नहीं)। फोकस्ड कंपोनेंट्स रेंडर किए गए टेक्स्ट में मार्कर एमिट करते हैं; `TUI` इसे एक्सट्रैक्ट करता है और हार्डवेयर कर्सर को पोज़िशन करता है।\n\n## रेंडरिंग बाधाएँ (टर्मिनल सेफ्टी)\n\nआपका `render(width)` आउटपुट टर्मिनल-सेफ होना चाहिए:\n\n1. **किसी भी लाइन पर `width` से अधिक न हो**। रेंडरर एरर थ्रो करता है यदि कोई नॉन-इमेज लाइन ओवरफ्लो होती है।\n2. **विज़ुअल विड्थ मापें**, स्ट्रिंग लेंथ नहीं: `visibleWidth()` का उपयोग करें।\n3. **ANSI-अवेयर टेक्स्ट को ट्रंकेट/रैप करें** `truncateToWidth()` / `wrapTextWithAnsi()` से।\n4. **बाहरी स्रोतों से टैब्स/कंटेंट को सैनिटाइज़ करें** `replaceTabs()` का उपयोग करके (और coding-agent रेंडर पाथ्स में हायर-लेवल सैनिटाइज़र्स)।\n\nन्यूनतम पैटर्न:\n\n```ts\nimport { replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\n\nrender(width: number): string[] {\n  return this.lines.map(line => truncateToWidth(replaceTabs(line), width));\n}\n```\n\n## इनपुट हैंडलिंग और कीबाइंडिंग्स\n\n### रॉ की मैचिंग\n\nनेविगेशन कीज़ और कॉम्बोस के लिए `matchesKey(data, \"...\")` का उपयोग करें।\n\n### यूज़र-कॉन्फ़िगर्ड ऐप कीबाइंडिंग्स का सम्मान करें\n\nएक्सटेंशन UI फैक्ट्रीज़ को एक `KeybindingsManager` (इंटरैक्टिव मोड) प्राप्त होता है ताकि आप कीज़ को हार्डकोड करने के बजाय मैप्ड एक्शन्स का सम्मान कर सकें:\n\n```ts\nif (keybindings.matches(data, \"interrupt\")) {\n  done(undefined);\n  return;\n}\n```\n\n### की रिलीज़/रिपीट इवेंट्स\n\nकी रिलीज़ इवेंट्स फ़िल्टर हो जाते हैं जब तक आपका कंपोनेंट यह सेट नहीं करता:\n\n```ts\nwantsKeyRelease = true;\n```\n\nफिर आवश्यकता होने पर `isKeyRelease()` / `isKeyRepeat()` का उपयोग करें।\n\n## फोकस, ओवरले, और कर्सर\n\n- `TUI.setFocus(component)` उस कंपोनेंट को इनपुट रूट करता है।\n- ओवरले API `TUI` में मौजूद हैं (`showOverlay`, `OverlayHandle`), लेकिन इंटरैक्टिव मोड में एक्सटेंशन `ctx.ui.custom` माउंटिंग वर्तमान में एडिटर कंपोनेंट एरिया को सीधे रिप्लेस करती है।\n- `custom(..., options?: { overlay?: boolean })` विकल्प एक्सटेंशन टाइप्स में मौजूद है; इंटरैक्टिव एक्सटेंशन माउंटिंग वर्तमान में इस विकल्प को अनदेखा करती है।\n\n## माउंट पॉइंट्स और रिटर्न कॉन्ट्रैक्ट्स\n\n## 1) एक्सटेंशन UI (`ExtensionUIContext`)\n\nवर्तमान सिग्नेचर (`extensibility/extensions/types.ts`):\n\n```ts\ncustom<T>(\n  factory: (\n    tui: TUI,\n    theme: Theme,\n    keybindings: KeybindingsManager,\n    done: (result: T) => void,\n  ) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n  options?: { overlay?: boolean },\n): Promise<T>\n```\n\nइंटरैक्टिव मोड में व्यवहार (`extension-ui-controller.ts`):\n\n- एडिटर टेक्स्ट सेव करता है।\n- एडिटर कंपोनेंट को आपके कंपोनेंट से रिप्लेस करता है।\n- आपके कंपोनेंट को फोकस करता है।\n- `done(result)` पर: `component.dispose?.()` कॉल करता है, एडिटर + टेक्स्ट रिस्टोर करता है, एडिटर को फोकस करता है, प्रॉमिस रिज़ॉल्व करता है।\n\nइसलिए `done(...)` पूर्णता के लिए अनिवार्य है।\n\n## 2) हुक/कस्टम-टूल UI कॉन्टेक्स्ट (लेगेसी टाइपिंग)\n\n`HookUIContext.custom` को हुक/कस्टम-टूल टाइप्स में `(tui, theme, done)` के रूप में टाइप किया गया है।\nअंतर्निहित इंटरैक्टिव इम्प्लीमेंटेशन फैक्ट्रीज़ को `(tui, theme, keybindings, done)` के साथ कॉल करता है। JS कंज़्यूमर्स अतिरिक्त आर्ग का उपयोग कर सकते हैं; टाइप-लेवल कम्पैटिबिलिटी अभी भी 3-आर्ग लेगेसी सिग्नेचर को रिफ्लेक्ट करती है।\n\nकस्टम टूल्स आमतौर पर फैक्ट्री-स्कोप्ड `pi.ui` ऑब्जेक्ट के माध्यम से उसी UI एंट्रीपॉइंट का उपयोग करते हैं, फिर सामान्य टूल कंटेंट में चयनित मान रिटर्न करते हैं:\n\n```ts\nasync execute(toolCallId, params, onUpdate, ctx, signal) {\n  if (!pi.hasUI) {\n    return { content: [{ type: \"text\", text: \"UI unavailable\" }] };\n  }\n\n  const picked = await pi.ui.custom<string | undefined>((tui, theme, done) => {\n    const component = new MyPickerComponent(done, signal);\n    return component;\n  });\n\n  return { content: [{ type: \"text\", text: picked ? `Picked: ${picked}` : \"Cancelled\" }] };\n}\n```\n\n## 3) कस्टम टूल कॉल/रिज़ल्ट रेंडरर्स\n\nकस्टम टूल्स और एक्सटेंशन टूल्स इनसे कंपोनेंट्स रिटर्न कर सकते हैं:\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\n`options` में वर्तमान में शामिल है:\n\n- `expanded: boolean`\n- `isPartial: boolean`\n- `spinnerFrame?: number`\n\nये रेंडरर्स `ToolExecutionComponent` द्वारा माउंट किए जाते हैं।\n\n## लाइफसाइकल और कैंसिलेशन\n\n- `dispose()` टाइप लेवल पर वैकल्पिक है लेकिन जब आपके पास टाइमर्स, सबप्रोसेसेज़, वॉचर्स, सॉकेट्स, या ओवरले हों तो इसे इम्प्लीमेंट करना चाहिए।\n- `done(...)` को आपके कंपोनेंट फ्लो से ठीक एक बार कॉल किया जाना चाहिए।\n- कैंसिल करने योग्य लंबे समय तक चलने वाले UI के लिए, `CancellableLoader` को `AbortSignal` के साथ जोड़ें और `onAbort` से `done(...)` कॉल करें।\n\nकैंसिलेशन पैटर्न का उदाहरण:\n\n```ts\nconst loader = new CancellableLoader(tui, theme.fg(\"accent\"), theme.fg(\"muted\"), \"Working...\");\nloader.onAbort = () => done(undefined);\nvoid doWork(loader.signal).then(result => done(result));\nreturn loader;\n```\n\n## यथार्थवादी कस्टम कंपोनेंट उदाहरण (एक्सटेंशन कमांड)\n\n```ts\nimport type { Component } from \"@f5-sales-demo/pi-tui\";\nimport { SelectList, matchesKey, replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\nimport { getSelectListTheme, type ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nclass Picker implements Component {\n  list: SelectList;\n  keybindings: any;\n  done: (value: string | undefined) => void;\n\n  constructor(\n    items: Array<{ value: string; label: string }>,\n    keybindings: any,\n    done: (value: string | undefined) => void,\n  ) {\n    this.list = new SelectList(items, 8, getSelectListTheme());\n    this.keybindings = keybindings;\n    this.done = done;\n    this.list.onSelect = item => this.done(item.value);\n    this.list.onCancel = () => this.done(undefined);\n  }\n\n  handleInput(data: string): void {\n    if (this.keybindings.matches(data, \"interrupt\")) {\n      this.done(undefined);\n      return;\n    }\n    this.list.handleInput(data);\n  }\n\n  render(width: number): string[] {\n    return this.list.render(width).map(line => truncateToWidth(replaceTabs(line), width));\n  }\n\n  invalidate(): void {\n    this.list.invalidate();\n  }\n}\n\nexport default function extension(pi: ExtensionAPI): void {\n  pi.registerCommand(\"pick-model\", {\n    description: \"Pick a model profile\",\n    handler: async (_args, ctx) => {\n      if (!ctx.hasUI) return;\n\n      const selected = await ctx.ui.custom<string | undefined>((tui, theme, keybindings, done) => {\n        const items = [\n          { value: \"fast\", label: theme.fg(\"accent\", \"Fast\") },\n          { value: \"balanced\", label: \"Balanced\" },\n          { value: \"quality\", label: \"Quality\" },\n        ];\n        return new Picker(items, keybindings, done);\n      });\n\n      if (selected) ctx.ui.notify(`Selected profile: ${selected}`, \"info\");\n    },\n  });\n}\n```\n\n## प्रमुख इम्प्लीमेंटेशन फ़ाइलें\n\n- `packages/tui/src/tui.ts` — `Component`, `Focusable`, कर्सर मार्कर, फोकस, ओवरले, इनपुट डिस्पैच।\n- `packages/tui/src/utils.ts` — विड्थ/ट्रंकेशन/सैनिटाइज़ेशन प्रिमिटिव्स।\n- `packages/tui/src/keys.ts` / `keybindings.ts` — की पार्सिंग और कॉन्फ़िगर करने योग्य एक्शन मैपिंग।\n- `packages/coding-agent/src/modes/controllers/extension-ui-controller.ts` — एक्सटेंशन/हुक/कस्टम-टूल UI के लिए इंटरैक्टिव माउंटिंग/अनमाउंटिंग।\n- `packages/coding-agent/src/extensibility/extensions/types.ts` — एक्सटेंशन UI और रेंडरर कॉन्ट्रैक्ट्स।\n- `packages/coding-agent/src/extensibility/hooks/types.ts` — हुक UI कॉन्ट्रैक्ट (लेगेसी कस्टम सिग्नेचर)।\n- `packages/coding-agent/src/extensibility/custom-tools/types.ts` — कस्टम टूल execute/render कॉन्ट्रैक्ट्स।\n- `packages/coding-agent/src/modes/components/tool-execution.ts` — `renderCall`/`renderResult` कंपोनेंट्स और पार्शियल-स्टेट विकल्पों को माउंट करना।\n- `packages/coding-agent/src/tools/context.ts` — टूल UI कॉन्टेक्स्ट प्रोपेगेशन (`hasUI`, `ui`)।\n",
	"it/configuration/blob-artifact-architecture.md": "---\ntitle: Architettura dello storage di blob e artefatti\ndescription: >-\n  Content-addressable blob store e registro degli artefatti per media di\n  sessione, screenshot e output degli strumenti.\nsidebar:\n  order: 7\n  label: Storage blob e artefatti\ni18n:\n  sourceHash: 70d255f48d5b\n  translator: machine\n---\n\n# Architettura dello storage di blob e artefatti\n\nQuesto documento descrive come coding-agent archivia payload grandi/binari al di fuori del JSONL di sessione, come viene persistito l'output troncato degli strumenti e come gli URL interni (`artifact://`, `agent://`) vengono risolti ai dati archiviati.\n\n## Perché esistono due sistemi di storage\n\nIl runtime utilizza due meccanismi di persistenza diversi per forme di dati differenti:\n\n- **Blob content-addressed** (`blob:sha256:<hash>`): storage globale, orientato al binario, utilizzato per esternalizzare payload base64 di immagini di grandi dimensioni dalle entry di sessione persistite.\n- **Artefatti con ambito di sessione** (file sotto `<sessionFile-without-.jsonl>/`): file di testo per sessione utilizzati per gli output completi degli strumenti e gli output dei subagent.\n\nSono intenzionalmente separati:\n\n- lo storage blob ottimizza la deduplicazione e i riferimenti stabili tramite hash del contenuto,\n- lo storage degli artefatti ottimizza gli strumenti append-only di sessione e il recupero da parte di umani/strumenti tramite ID locali.\n\n## Confini dello storage e layout su disco\n\n## Confine del blob store (globale)\n\n`SessionManager` costruisce `BlobStore(getBlobsDir())`, quindi i file blob risiedono in una directory blob globale condivisa (non in una cartella di sessione).\n\nNomenclatura dei file blob:\n\n- percorso file: `<blobsDir>/<sha256-hex>`\n- nessuna estensione\n- stringa di riferimento archiviata nelle entry: `blob:sha256:<sha256-hex>`\n\nImplicazioni:\n\n- lo stesso contenuto binario tra sessioni diverse si risolve nello stesso hash/percorso,\n- le scritture sono idempotenti a livello di contenuto,\n- i blob possono sopravvivere a qualsiasi singolo file di sessione.\n\n## Confine degli artefatti (locale alla sessione)\n\n`ArtifactManager` deriva la directory degli artefatti dal percorso del file di sessione:\n\n- file di sessione: `.../<timestamp>_<sessionId>.jsonl`\n- directory degli artefatti: `.../<timestamp>_<sessionId>/` (rimozione di `.jsonl`)\n\nI tipi di artefatto condividono questa directory:\n\n- file di output troncato degli strumenti: `<numericId>.<toolType>.log` (per `artifact://`)\n- file di output dei subagent: `<outputId>.md` (per `agent://`)\n\n## Schemi di allocazione di ID e nomi\n\n## ID dei blob: hash del contenuto\n\n`BlobStore.put()` calcola SHA-256 sui byte binari grezzi e restituisce:\n\n- `hash`: digest esadecimale,\n- `path`: `<blobsDir>/<hash>`,\n- `ref`: `blob:sha256:<hash>`.\n\nNon viene utilizzato alcun contatore locale alla sessione.\n\n## ID degli artefatti: intero monotonico locale alla sessione\n\n`ArtifactManager` scansiona i file artefatto `*.log` esistenti al primo utilizzo per trovare l'ID numerico massimo esistente e imposta `nextId = max + 1`.\n\nComportamento di allocazione:\n\n- formato file: `{id}.{toolType}.log`\n- gli ID sono stringhe sequenziali (`\"0\"`, `\"1\"`, ...)\n- il ripristino non sovrascrive gli artefatti esistenti perché la scansione avviene prima dell'allocazione.\n\nSe la directory degli artefatti è mancante, la scansione restituisce una lista vuota e l'allocazione parte da `0`.\n\n## ID degli output dell'agente (`agent://`)\n\n`AgentOutputManager` alloca gli ID per gli output dei subagent come `<index>-<requestedId>` (opzionalmente annidati sotto un prefisso genitore, ad es. `0-Parent.1-Child`). Scansiona i file `.md` esistenti all'inizializzazione per continuare dall'indice successivo al ripristino.\n\n## Flusso dati di persistenza\n\n## 1) Percorso di riscrittura della persistenza delle entry di sessione\n\nPrima che le entry di sessione vengano scritte (`#rewriteFile` / persistenza incrementale), `SessionManager` chiama `prepareEntryForPersistence()` (tramite `truncateForPersistence`).\n\nComportamenti chiave:\n\n1. **Troncamento di stringhe grandi**: le stringhe sovradimensionate vengono tagliate e suffisse con `\"[Session persistence truncated large content]\"`.\n2. **Rimozione dei campi transienti**: `partialJson` e `jsonlEvents` vengono rimossi dalle entry persistite.\n3. **Esternalizzazione delle immagini in blob**:\n   - si applica solo ai blocchi immagine negli array `content`,\n   - solo quando `data` non è già un riferimento blob,\n   - solo quando la lunghezza del base64 è almeno pari alla soglia (`BLOB_EXTERNALIZE_THRESHOLD = 1024`),\n   - sostituisce il base64 inline con `blob:sha256:<hash>`.\n\nQuesto mantiene il JSONL di sessione compatto preservando la recuperabilità.\n\n## 2) Percorso di reidratazione al caricamento della sessione\n\nQuando si apre una sessione (`setSessionFile`), dopo le migrazioni, `SessionManager` esegue `resolveBlobRefsInEntries()`.\n\nPer ogni blocco immagine di messaggio/messaggio-personalizzato con `blob:sha256:<hash>`:\n\n- legge i byte del blob dal blob store,\n- converte i byte in base64,\n- modifica l'entry in memoria per inserire il base64 inline per i consumatori runtime.\n\nSe il blob è mancante:\n\n- `resolveImageData()` registra un warning,\n- restituisce la stringa di riferimento originale invariata,\n- il caricamento continua (nessun crash critico).\n\n## 3) Percorso di riversamento/troncamento dell'output degli strumenti\n\n`OutputSink` gestisce l'output in streaming nei tool bash/python/ssh e negli executor correlati.\n\nComportamento:\n\n1. Ogni chunk viene sanitizzato e aggiunto al buffer tail in memoria.\n2. Quando i byte in memoria superano la soglia di riversamento (`DEFAULT_MAX_BYTES`, 50KB), il sink segna l'output come troncato.\n3. Se è disponibile un percorso artefatto, il sink apre un file writer e scrive:\n   - il contenuto bufferizzato esistente una sola volta,\n   - tutti i chunk successivi.\n4. Il buffer in memoria viene sempre ridotto alla finestra tail per la visualizzazione.\n5. `dump()` restituisce un riepilogo che include `artifactId` solo quando il file sink è stato creato con successo.\n\nEffetto pratico:\n\n- l'UI/il ritorno dello strumento mostra il tail troncato,\n- l'output completo è preservato nel file artefatto e referenziato come `artifact://<id>`.\n\nSe la creazione del file sink fallisce (errore I/O, percorso mancante, ecc.), il sink ricade silenziosamente al solo troncamento in memoria; l'output completo non viene persistito.\n\n## Modello di accesso agli URL\n\n## Riferimenti `blob:`\n\n`blob:sha256:<hash>` è un riferimento di persistenza all'interno dei payload delle entry di sessione, non uno schema URL interno gestito dal router. La risoluzione viene effettuata da `SessionManager` durante il caricamento della sessione.\n\n## `artifact://<id>`\n\nGestito da `ArtifactProtocolHandler`:\n\n- richiede una directory artefatti di sessione attiva,\n- l'ID deve essere numerico,\n- risolve cercando una corrispondenza con il prefisso del nome file `<id>.`,\n- restituisce testo grezzo (`text/plain`) dal file `.log` corrispondente,\n- quando mancante, l'errore include la lista degli ID artefatto disponibili.\n\nComportamento con directory mancante:\n\n- se la directory degli artefatti non esiste, lancia `No artifacts directory found`.\n\n## `agent://<id>`\n\nGestito da `AgentProtocolHandler` su `<artifactsDir>/<id>.md`:\n\n- nella forma semplice restituisce testo markdown,\n- le forme `/path` o `?q=` eseguono estrazione JSON,\n- l'estrazione per percorso e per query non possono essere combinate,\n- se viene richiesta l'estrazione, il contenuto del file deve essere parsabile come JSON.\n\nComportamento con directory mancante:\n\n- lancia `No artifacts directory found`.\n\nComportamento con output mancante:\n\n- lancia `Not found: <id>` con gli ID disponibili dai file `.md` esistenti.\n\nIntegrazione con lo strumento read:\n\n- `read` supporta la paginazione offset/limit per le letture di URL interni senza estrazione,\n- rifiuta `offset/limit` quando viene utilizzata l'estrazione con `agent://`.\n\n## Semantica di ripristino, fork e spostamento\n\n## Ripristino\n\n- `ArtifactManager` scansiona i file `{id}.*.log` esistenti alla prima allocazione e continua la numerazione.\n- `AgentOutputManager` scansiona gli ID di output `.md` esistenti e continua la numerazione.\n- `SessionManager` reidrata i riferimenti blob in base64 al caricamento.\n\n## Fork\n\n`SessionManager.fork()` crea un nuovo file di sessione con un nuovo ID di sessione e un collegamento `parentSession`, quindi restituisce i percorsi file vecchio/nuovo. La copia degli artefatti è gestita da `AgentSession.fork()`:\n\n- tenta la copia ricorsiva della vecchia directory artefatti nella nuova directory artefatti,\n- la mancanza della vecchia directory è tollerata,\n- gli errori di copia diversi da ENOENT vengono registrati come warning e il fork si completa comunque.\n\nImplicazioni sugli ID dopo il fork:\n\n- se la copia ha avuto successo, i contatori degli artefatti nella nuova sessione continuano dopo l'ID massimo copiato,\n- se la copia è fallita/saltata, gli ID degli artefatti della nuova sessione partono da `0`.\n\nImplicazioni sui blob dopo il fork:\n\n- i blob sono globali e content-addressed, quindi non è necessaria alcuna copia della directory blob.\n\n## Spostamento in una nuova cwd\n\n`SessionManager.moveTo()` rinomina sia il file di sessione che la directory degli artefatti nella nuova directory di sessione predefinita, con logica di rollback se un passaggio successivo fallisce. Questo preserva l'identità degli artefatti mentre si riloca l'ambito della sessione.\n\n## Gestione degli errori e percorsi di fallback\n\n| Caso | Comportamento |\n| --- | --- |\n| File blob mancante durante la reidratazione | Warning e mantenimento della stringa di riferimento `blob:sha256:` in memoria |\n| Blob read ENOENT tramite `BlobStore.get` | Restituisce `null` |\n| Directory artefatti mancante (`ArtifactManager.listFiles`) | Restituisce lista vuota (l'allocazione può partire da zero) |\n| Directory artefatti mancante (`artifact://` / `agent://`) | Lancia esplicitamente `No artifacts directory found` |\n| ID artefatto non trovato | Lancia con elenco degli ID disponibili |\n| Inizializzazione del writer artefatto di OutputSink fallita | Continua con il solo troncamento tail (nessun artefatto con output completo) |\n| Nessun file di sessione (alcuni percorsi task) | Lo strumento task ricade su una directory artefatti temporanea per gli output dei subagent |\n\n## Esternalizzazione blob binari vs artefatti di output testuale\n\n- L'**esternalizzazione blob** è per payload di immagini binarie all'interno del contenuto delle entry di sessione persistite; sostituisce il base64 inline nel JSONL con riferimenti al contenuto stabili.\n- Gli **artefatti** sono file di testo semplice per l'output di esecuzione e l'output dei subagent; sono indirizzabili tramite ID locali alla sessione attraverso URL interni.\n\nI due sistemi si intersecano solo indirettamente (entrambi riducono il bloat del JSONL di sessione) ma hanno percorsi di identità, durata e recupero differenti.\n\n## File di implementazione\n\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts) — formato dei riferimenti blob, hashing, put/get, helper per esternalizzazione/risoluzione.\n- [`src/session/artifacts.ts`](../../packages/coding-agent/src/session/artifacts.ts) — modello della directory artefatti di sessione e allocazione degli ID artefatto numerici.\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — comportamento di troncamento/riversamento su file di `OutputSink` e metadati di riepilogo.\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts) — trasformazioni di persistenza, reidratazione blob al caricamento, interazioni fork/spostamento di sessione.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — copia della directory artefatti durante il fork interattivo.\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — bootstrap dell'artifact manager degli strumenti e allocazione del percorso artefatto per strumento.\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — resolver `artifact://`.\n- [`src/internal-urls/agent-protocol.ts`](../../packages/coding-agent/src/internal-urls/agent-protocol.ts) — resolver `agent://` + estrazione JSON.\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — cablaggio del router URL interni e resolver della directory artefatti.\n- [`src/task/output-manager.ts`](../../packages/coding-agent/src/task/output-manager.ts) — allocazione degli ID di output dell'agente con ambito di sessione per `agent://`.\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — scritture degli artefatti di output dei subagent (`<id>.md`) e fallback su directory artefatti temporanea.\n",
	"it/configuration/config-usage.md": "---\ntitle: Scoperta e risoluzione della configurazione\ndescription: >-\n  Come xcsh scopre, risolve e stratifica la configurazione dalle radici di\n  progetto, utente e aziendali.\nsidebar:\n  order: 1\n  label: Configurazione\ni18n:\n  sourceHash: e38bd9792499\n  translator: machine\n---\n\n# Scoperta e risoluzione della configurazione\n\nQuesto documento descrive come il coding-agent risolve la configurazione oggi: quali radici vengono analizzate, come funziona la precedenza e come la configurazione risolta viene consumata da impostazioni, skill, hook, strumenti ed estensioni.\n\n## Ambito\n\nImplementazione principale:\n\n- `src/config.ts`\n- `src/config/settings.ts`\n- `src/config/settings-schema.ts`\n- `src/discovery/builtin.ts`\n- `src/discovery/helpers.ts`\n\nPunti di integrazione chiave:\n\n- `src/capability/index.ts`\n- `src/discovery/index.ts`\n- `src/extensibility/skills.ts`\n- `src/extensibility/hooks/loader.ts`\n- `src/extensibility/custom-tools/loader.ts`\n- `src/extensibility/extensions/loader.ts`\n\n---\n\n## Flusso di risoluzione (visuale)\n\n```text\n         Config roots (ordered)\n┌───────────────────────────────────────┐\n│ 1) ~/.xcsh/agent + <cwd>/.xcsh          │\n│ 2) ~/.claude   + <cwd>/.claude        │\n│ 3) ~/.codex    + <cwd>/.codex         │\n│ 4) ~/.gemini   + <cwd>/.gemini        │\n└───────────────────────────────────────┘\n                    │\n                    ▼\n        config.ts helper resolution\n  (getConfigDirs/findConfigFile/findNearest...)\n                    │\n                    ▼\n       capability providers enumerate items\n (native, claude, codex, gemini, agents, etc.)\n                    │\n                    ▼\n      priority sort + per-capability dedup\n                    │\n                    ▼\n          subsystem-specific consumption\n   (settings, skills, hooks, tools, extensions)\n```\n\n## 1) Radici di configurazione e ordine delle sorgenti\n\n## Radici canoniche\n\n`src/config.ts` definisce una lista di priorità delle sorgenti fissa:\n\n1. `.xcsh` (nativo)\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nBasi a livello utente:\n\n- `~/.xcsh/agent`\n- `~/.claude`\n- `~/.codex`\n- `~/.gemini`\n\nBasi a livello progetto:\n\n- `<cwd>/.xcsh`\n- `<cwd>/.claude`\n- `<cwd>/.codex`\n- `<cwd>/.gemini`\n\n`CONFIG_DIR_NAME` è `.xcsh` (`packages/utils/src/dirs.ts`).\n\n## Vincolo importante\n\nGli helper generici in `src/config.ts` **non** includono `.pi` nell'ordine di scoperta delle sorgenti.\n\n---\n\n## 2) Helper di scoperta principali (`src/config.ts`)\n\n## `getConfigDirs(subpath, options)`\n\nRestituisce voci ordinate:\n\n- Prima le voci a livello utente (per priorità della sorgente)\n- Poi le voci a livello progetto (per la stessa priorità della sorgente)\n\nOpzioni:\n\n- `user` (predefinito `true`)\n- `project` (predefinito `true`)\n- `cwd` (predefinito `getProjectDir()`)\n- `existingOnly` (predefinito `false`)\n\nQuesta API è utilizzata per le ricerche di configurazione basate su directory (comandi, hook, strumenti, agenti, ecc.).\n\n## `findConfigFile(subpath, options)` / `findConfigFileWithMeta(...)`\n\nCerca il primo file esistente attraverso le basi ordinate, restituisce la prima corrispondenza (solo percorso o percorso+metadati).\n\n## `findAllNearestProjectConfigDirs(subpath, cwd)`\n\nRisale le directory padre verso l'alto e restituisce la **directory esistente più vicina per ogni base sorgente** (`.xcsh`, `.claude`, `.codex`, `.gemini`), poi ordina i risultati per priorità della sorgente.\n\nUtilizzare questa funzione quando la configurazione del progetto deve essere ereditata dalle directory antenate (comportamento monorepo/workspace annidato).\n\n---\n\n## 3) Wrapper per file di configurazione (`ConfigFile<T>` in `src/config.ts`)\n\n`ConfigFile<T>` è il loader con validazione dello schema per singoli file di configurazione.\n\nFormati supportati:\n\n- `.yml` / `.yaml`\n- `.json` / `.jsonc`\n\nComportamento:\n\n- Valida i dati analizzati con AJV rispetto a uno schema TypeBox fornito.\n- Mette in cache il risultato del caricamento fino a `invalidate()`.\n- Restituisce un risultato a tre stati tramite `tryLoad()`:\n  - `ok`\n  - `not-found`\n  - `error` (`ConfigError` con contesto schema/parsing)\n\nMigrazione legacy ancora supportata:\n\n- Se il percorso di destinazione è `.yml`/`.yaml`, un file `.json` adiacente viene auto-migrato una sola volta (`migrateJsonToYml`).\n\n---\n\n## 4) Modello di risoluzione delle impostazioni (`src/config/settings.ts`)\n\nIl modello delle impostazioni a runtime è stratificato:\n\n1. Impostazioni globali: `~/.xcsh/agent/config.yml`\n2. Impostazioni di progetto: scoperte tramite la capability settings (`settings.json` dai provider)\n3. Override a runtime: in memoria, non persistenti\n4. Valori predefiniti dello schema: da `SETTINGS_SCHEMA`\n\nPercorso di lettura effettivo:\n\n`defaults <- global <- project <- overrides`\n\nComportamento di scrittura:\n\n- `settings.set(...)` scrive nel livello **globale** (`config.yml`) e accoda il salvataggio in background.\n- Le impostazioni di progetto sono in sola lettura dalla scoperta delle capability.\n\n## Comportamento di migrazione ancora attivo\n\nAll'avvio, se `config.yml` è assente:\n\n1. Migrazione da `~/.xcsh/agent/settings.json` (rinominato in `.bak` in caso di successo)\n2. Merge con le impostazioni legacy del DB da `agent.db`\n3. Scrittura del risultato unificato in `config.yml`\n\nMigrazioni a livello di campo in `#migrateRawSettings`:\n\n- `queueMode` -> `steeringMode`\n- `ask.timeout` millisecondi -> secondi quando il vecchio valore sembra essere in ms (`> 1000`)\n- Struttura legacy piatta `theme: \"...\"` -> struttura `theme.dark/theme.light`\n\n---\n\n## 5) Integrazione capability/discovery\n\nLa maggior parte dei flussi di caricamento della configurazione non-core passa attraverso il registro delle capability (`src/capability/index.ts` + `src/discovery/index.ts`).\n\n## Ordinamento dei provider\n\nI provider sono ordinati per priorità numerica (il più alto prima). Esempi di priorità:\n\n- OMP nativo (`builtin.ts`): `100`\n- Claude: `80`\n- Codex / agents / Claude marketplace: `70`\n- Gemini: `60`\n\n```text\nProvider precedence (higher wins)\n\nnative (.xcsh)          priority 100\nclaude                 priority  80\ncodex / agents / ...   priority  70\ngemini                 priority  60\n```\n\n## Semantica di deduplicazione\n\nLe capability definiscono una `key(item)`:\n\n- stessa chiave => il primo elemento vince (elemento con priorità più alta/caricato prima)\n- nessuna chiave (`undefined`) => nessuna deduplicazione, tutti gli elementi vengono mantenuti\n\nChiavi rilevanti:\n\n- skill: `name`\n- strumenti: `name`\n- hook: `${type}:${tool}:${name}`\n- moduli di estensione: `name`\n- estensioni: `name`\n- impostazioni: nessuna deduplicazione (tutti gli elementi preservati)\n\n---\n\n## 6) Comportamento del provider nativo `.xcsh` (`src/discovery/builtin.ts`)\n\nIl provider nativo (`id: native`) legge da:\n\n- progetto: `<cwd>/.xcsh/...`\n- utente: `~/.xcsh/agent/...`\n\n### Regola di ammissione delle directory\n\n`builtin.ts` include una radice di configurazione solo se la directory esiste **ed è non vuota** (`ifNonEmptyDir`).\n\n### Caricamento specifico per ambito\n\n- Skill: `skills/*/SKILL.md`\n- Comandi slash: `commands/*.md`\n- Regole: `rules/*.{md,mdc}`\n- Prompt: `prompts/*.md`\n- Istruzioni: `instructions/*.md`\n- Hook: `hooks/pre/*`, `hooks/post/*`\n- Strumenti: `tools/*.json|*.md` e `tools/<name>/index.ts`\n- Moduli di estensione: scoperti sotto `extensions/` (+ array di stringhe legacy `settings.json.extensions`)\n- Estensioni: `extensions/<name>/gemini-extension.json`\n- Capability impostazioni: `settings.json`\n\n### Sfumatura della ricerca nearest-project\n\nPer `SYSTEM.md` e `XCSH.md`, il provider nativo utilizza la ricerca della directory `.xcsh` di progetto nell'antenato più vicino (risalita) ma richiede comunque che la directory `.xcsh` sia non vuota.\n\n---\n\n## 7) Come i principali sottosistemi consumano la configurazione\n\n## Sottosistema impostazioni\n\n- `Settings.init()` carica il `config.yml` globale + gli elementi della capability `settings.json` del progetto scoperti.\n- Solo gli elementi della capability con `level === \"project\"` vengono uniti nel livello progetto.\n\n## Sottosistema skill\n\n- `extensibility/skills.ts` carica tramite `loadCapability(skillCapability.id, { cwd })`.\n- Applica toggle e filtri sulle sorgenti (`ignoredSkills`, `includeSkills`, directory personalizzate).\n- I toggle con nomi legacy esistono ancora (`skills.enablePiUser`, `skills.enablePiProject`) ma controllano il provider nativo (`provider === \"native\"`).\n\n## Sottosistema hook\n\n- `discoverAndLoadHooks()` risolve i percorsi degli hook dalla capability hook + percorsi configurati esplicitamente.\n- Poi carica i moduli tramite import Bun.\n\n## Sottosistema strumenti\n\n- `discoverAndLoadCustomTools()` risolve i percorsi degli strumenti dalla capability strumenti + percorsi degli strumenti dei plugin + percorsi configurati esplicitamente.\n- I file strumento dichiarativi `.md/.json` contengono solo metadati; il caricamento eseguibile si aspetta moduli di codice.\n\n## Sottosistema estensioni\n\n- `discoverAndLoadExtensions()` risolve i moduli di estensione dalla capability extension-module più percorsi espliciti.\n- L'implementazione attuale mantiene intenzionalmente solo gli elementi della capability con `_source.provider === \"native\"` prima del caricamento.\n\n---\n\n## 8) Regole di precedenza su cui fare affidamento\n\nUtilizzare questo modello mentale:\n\n1. L'ordinamento delle directory sorgente da `config.ts` determina l'ordine dei percorsi candidati.\n2. La priorità del provider di capability determina la precedenza tra provider.\n3. La deduplicazione tramite chiave della capability determina il comportamento in caso di collisione (il primo vince per le capability con chiave).\n4. La logica di merge specifica del sottosistema può modificare ulteriormente la precedenza effettiva (specialmente per le impostazioni).\n\n### Avvertenza specifica per le impostazioni\n\nGli elementi della capability impostazioni non sono deduplicati; `Settings.#loadProjectSettings()` esegue un deep-merge degli elementi di progetto nell'ordine restituito. Poiché il merge applica i valori degli elementi successivi sopra quelli precedenti, il comportamento effettivo di override dipende dall'ordine di emissione del provider, non solo dalla semantica delle chiavi della capability.\n\n---\n\n## 9) Comportamenti legacy/di compatibilità ancora presenti\n\n- Migrazione `ConfigFile` da JSON a YAML per i file destinati a YAML.\n- Migrazione delle impostazioni da `settings.json` e `agent.db` a `config.yml`.\n- Migrazioni delle chiavi delle impostazioni (`queueMode`, `ask.timeout`, `theme` piatto).\n- Compatibilità del manifesto delle estensioni: il loader accetta sia le sezioni del manifesto `package.json.xcsh` che `package.json.pi`.\n- I nomi di impostazioni legacy `skills.enablePiUser` / `skills.enablePiProject` sono ancora gate attivi per la sorgente skill nativa.\n\nSe questi percorsi di compatibilità vengono rimossi nel codice, aggiornare immediatamente questo documento; diversi comportamenti a runtime dipendono ancora da essi oggi.\n",
	"it/configuration/environment-variables.md": "---\ntitle: Variabili d'ambiente\ndescription: >-\n  Riferimento delle variabili d'ambiente runtime per la configurazione e il\n  controllo del comportamento di xcsh.\nsidebar:\n  order: 2\n  label: Variabili d'ambiente\ni18n:\n  sourceHash: e2890f963c02\n  translator: machine\n---\n\n# Variabili d'ambiente (Riferimento runtime corrente)\n\nQuesto riferimento è derivato dai percorsi di codice correnti in:\n\n- `packages/coding-agent/src/**`\n- `packages/ai/src/**` (risoluzione provider/autenticazione usata da coding-agent)\n- `packages/utils/src/**` e `packages/tui/src/**` dove tali variabili influenzano direttamente il runtime di coding-agent\n\nDocumenta solo il comportamento attivo.\n\n## Modello di risoluzione e precedenza\n\nLa maggior parte delle ricerche runtime utilizza `$env` da `@f5-sales-demo/pi-utils` (`packages/utils/src/env.ts`).\n\nOrdine di caricamento di `$env`:\n\n1. Ambiente di processo esistente (`Bun.env`)\n2. `.env` del progetto (`$PWD/.env`) per le chiavi non già impostate\n3. `.env` della home (`~/.env`) per le chiavi non già impostate\n\nRegola aggiuntiva nei file `.env`: le chiavi `XCSH_*` vengono replicate nelle chiavi `PI_*` durante il parsing.\n\n---\n\n## 1) Autenticazione modello/provider\n\nQueste sono consumate tramite `getEnvApiKey()` (`packages/ai/src/stream.ts`) salvo diversa indicazione.\n\n### Credenziali provider principali\n\n| Variabile                       | Utilizzata per | Richiesta quando                                              | Note / precedenza                                                                                   |\n|---------------------------------|---|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|\n| `ANTHROPIC_OAUTH_TOKEN`         | Autenticazione API Anthropic | Si usa Anthropic con autenticazione token OAuth               | Ha precedenza su `ANTHROPIC_API_KEY` per la risoluzione dell'autenticazione del provider            |\n| `ANTHROPIC_API_KEY`             | Autenticazione API Anthropic | Si usa Anthropic senza token OAuth                            | Fallback dopo `ANTHROPIC_OAUTH_TOKEN`                                                               |\n| `ANTHROPIC_FOUNDRY_API_KEY`     | Anthropic tramite Azure Foundry / gateway enterprise | `CLAUDE_CODE_USE_FOUNDRY` abilitato                           | Ha precedenza su `ANTHROPIC_OAUTH_TOKEN` e `ANTHROPIC_API_KEY` quando la modalità Foundry è attiva |\n| `OPENAI_API_KEY`                | Autenticazione OpenAI | Si usano provider della famiglia OpenAI senza argomento apiKey esplicito | Usata dai provider OpenAI Completions/Responses                                                    |\n| `GEMINI_API_KEY`                | Autenticazione Google Gemini | Si usano modelli del provider `google`                        | Chiave primaria per la mappatura del provider Gemini                                                |\n| `GOOGLE_API_KEY`                | Fallback autenticazione tool immagini Gemini | Si usa il tool `gemini_image` senza `GEMINI_API_KEY`          | Usata dal percorso fallback del tool immagini di coding-agent                                       |\n| `GROQ_API_KEY`                  | Autenticazione Groq | Si usano modelli Groq                                         |                                                                                                     |\n| `CEREBRAS_API_KEY`              | Autenticazione Cerebras | Si usano modelli Cerebras                                     |                                                                                                     |\n| `TOGETHER_API_KEY`              | Autenticazione Together | Si usa il provider `together`                                 |                                                                                                     |\n| `HUGGINGFACE_HUB_TOKEN`         | Autenticazione Hugging Face | Si usa il provider `huggingface`                              | Variabile d'ambiente primaria per il token Hugging Face                                             |\n| `HF_TOKEN`                      | Autenticazione Hugging Face | Si usa il provider `huggingface`                              | Fallback quando `HUGGINGFACE_HUB_TOKEN` non è impostato                                            |\n| `SYNTHETIC_API_KEY`             | Autenticazione Synthetic | Si usano modelli Synthetic                                    |                                                                                                     |\n| `NVIDIA_API_KEY`                | Autenticazione NVIDIA | Si usa il provider `nvidia`                                   |                                                                                                     |\n| `NANO_GPT_API_KEY`              | Autenticazione NanoGPT | Si usa il provider `nanogpt`                                  |                                                                                                     |\n| `VENICE_API_KEY`                | Autenticazione Venice | Si usa il provider `venice`                                   |                                                                                                     |\n| `LITELLM_API_KEY`               | Autenticazione LiteLLM | Si usa il provider `litellm`                                  | Chiave proxy LiteLLM compatibile con OpenAI. Quando impostata con `LITELLM_BASE_URL`, abilita la configurazione automatica di `models.yml` |\n| `LM_STUDIO_API_KEY`             | Autenticazione LM Studio (opzionale) | Si usa il provider `lm-studio` con host autenticati           | LM Studio locale di solito funziona senza autenticazione; qualsiasi token non vuoto funziona quando è richiesta una chiave |\n| `OLLAMA_API_KEY`                | Autenticazione Ollama (opzionale) | Si usa il provider `ollama` con host autenticati              | Ollama locale di solito funziona senza autenticazione; qualsiasi token non vuoto funziona quando è richiesta una chiave |\n| `LLAMA_CPP_API_KEY`             | Autenticazione Ollama (opzionale) | Si usa `llama-server` con il parametro `--api-key`            | llama.cpp locale di solito funziona senza autenticazione; qualsiasi token non vuoto funziona quando è configurata una chiave |\n| `XIAOMI_API_KEY`                | Autenticazione Xiaomi MiMo | Si usa il provider `xiaomi`                                   |                                                                                                     |\n| `MOONSHOT_API_KEY`              | Autenticazione Moonshot | Si usa il provider `moonshot`                                 |                                                                                                     |\n| `XAI_API_KEY`                   | Autenticazione xAI | Si usano modelli xAI                                          |                                                                                                     |\n| `OPENROUTER_API_KEY`            | Autenticazione OpenRouter | Si usano modelli OpenRouter                                   | Usata anche dal tool immagini quando il provider preferito/auto è OpenRouter                        |\n| `MISTRAL_API_KEY`               | Autenticazione Mistral | Si usano modelli Mistral                                      |                                                                                                     |\n| `ZAI_API_KEY`                   | Autenticazione z.ai | Si usano modelli z.ai                                         | Usata anche dal provider di ricerca web z.ai                                                        |\n| `MINIMAX_API_KEY`               | Autenticazione MiniMax | Si usa il provider `minimax`                                  |                                                                                                     |\n| `MINIMAX_CODE_API_KEY`          | Autenticazione MiniMax Code | Si usa il provider `minimax-code`                             |                                                                                                     |\n| `MINIMAX_CODE_CN_API_KEY`       | Autenticazione MiniMax Code CN | Si usa il provider `minimax-code-cn`                          |                                                                                                     |\n| `OPENCODE_API_KEY`              | Autenticazione OpenCode | Si usano modelli OpenCode                                     |                                                                                                     |\n| `QIANFAN_API_KEY`               | Autenticazione Qianfan | Si usa il provider `qianfan`                                  |                                                                                                     |\n| `QWEN_OAUTH_TOKEN`              | Autenticazione Qwen Portal | Si usa `qwen-portal` con token OAuth                          | Ha precedenza su `QWEN_PORTAL_API_KEY`                                                              |\n| `QWEN_PORTAL_API_KEY`           | Autenticazione Qwen Portal | Si usa `qwen-portal` con chiave API                           | Fallback dopo `QWEN_OAUTH_TOKEN`                                                                    |\n| `ZENMUX_API_KEY`                | Autenticazione ZenMux | Si usa il provider `zenmux`                                   | Usata per le rotte ZenMux compatibili con OpenAI e Anthropic                                        |\n| `VLLM_API_KEY`                  | Autenticazione/scoperta vLLM opt-in | Si usa il provider `vllm` (server locali compatibili con OpenAI) | Qualsiasi valore non vuoto funziona per server locali senza autenticazione                         |\n| `CURSOR_ACCESS_TOKEN`           | Autenticazione provider Cursor | Si usa il provider Cursor                                     |                                                                                                     |\n| `AI_GATEWAY_API_KEY`            | Autenticazione Vercel AI Gateway | Si usa il provider `vercel-ai-gateway`                        |                                                                                                     |\n| `CLOUDFLARE_AI_GATEWAY_API_KEY` | Autenticazione Cloudflare AI Gateway | Si usa il provider `cloudflare-ai-gateway`                    | L'URL base deve essere configurato come `https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic` |\n\n### Catene di token GitHub/Copilot\n\n| Variabile | Utilizzata per | Catena |\n|---|---|---|\n| `COPILOT_GITHUB_TOKEN` | Autenticazione provider GitHub Copilot | `COPILOT_GITHUB_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN` |\n| `GH_TOKEN` | Fallback Copilot; autenticazione API GitHub nel web scraper | Nel web scraper: `GITHUB_TOKEN` → `GH_TOKEN` |\n| `GITHUB_TOKEN` | Fallback Copilot; autenticazione API GitHub nel web scraper | Nel web scraper: verificata prima di `GH_TOKEN` |\n\n---\n\n## 2) Configurazione runtime specifica per provider\n\n### Anthropic Foundry Gateway (Azure / proxy enterprise)\n\nQuando `CLAUDE_CODE_USE_FOUNDRY` è abilitato, le richieste Anthropic passano alla modalità Foundry:\n\n- L'URL base viene risolto da `FOUNDRY_BASE_URL` (il fallback rimane l'URL base del modello/predefinito se non impostato).\n- La risoluzione della chiave API per il provider `anthropic` diventa:\n  `ANTHROPIC_FOUNDRY_API_KEY` → `ANTHROPIC_OAUTH_TOKEN` → `ANTHROPIC_API_KEY`.\n- `ANTHROPIC_CUSTOM_HEADERS` viene analizzato come coppie `chiave: valore` separate da virgola/a capo e unito agli header della richiesta.\n- Il materiale TLS client/server può essere iniettato dai valori d'ambiente:\n  `NODE_EXTRA_CA_CERTS`, `CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`.\n  Ciascuno accetta:\n  - un percorso del filesystem al contenuto PEM, oppure\n  - PEM inline (incluse le sequenze `\\n` con escape).\n\n| Variabile | Tipo di valore | Comportamento |\n|---|---|---|\n| `CLAUDE_CODE_USE_FOUNDRY` | Stringa di tipo booleano (`1`, `true`, `yes`, `on`) | Abilita la modalità Foundry per il provider Anthropic |\n| `FOUNDRY_BASE_URL` | Stringa URL | URL base dell'endpoint Anthropic in modalità Foundry |\n| `ANTHROPIC_FOUNDRY_API_KEY` | Stringa token | Usata per `Authorization: Bearer <token>` |\n| `ANTHROPIC_CUSTOM_HEADERS` | Stringa lista di header | Header extra; formato `header-a: valore, header-b: valore` o separati da a capo |\n| `NODE_EXTRA_CA_CERTS` | Percorso PEM o PEM inline | Catena CA aggiuntiva per la validazione del certificato del server |\n| `CLAUDE_CODE_CLIENT_CERT` | Percorso PEM o PEM inline | Certificato client mTLS |\n| `CLAUDE_CODE_CLIENT_KEY` | Percorso PEM o PEM inline | Chiave privata client mTLS (deve essere abbinata al certificato) |\n\n### Amazon Bedrock\n\n| Variabile | Predefinito / comportamento |\n|---|---|\n| `AWS_REGION` | Fonte primaria della regione |\n| `AWS_DEFAULT_REGION` | Fallback se `AWS_REGION` non è impostato |\n| `AWS_PROFILE` | Abilita il percorso di autenticazione con profilo nominato |\n| `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` | Abilita il percorso di autenticazione con chiave IAM |\n| `AWS_BEARER_TOKEN_BEDROCK` | Abilita il percorso di autenticazione con bearer token |\n| `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` / `AWS_CONTAINER_CREDENTIALS_FULL_URI` | Abilita il percorso delle credenziali del task ECS |\n| `AWS_WEB_IDENTITY_TOKEN_FILE` + `AWS_ROLE_ARN` | Abilita il percorso di autenticazione con web identity |\n| `AWS_BEDROCK_SKIP_AUTH` | Se `1`, inietta credenziali fittizie (scenari proxy/senza autenticazione) |\n| `AWS_BEDROCK_FORCE_HTTP1` | Se `1`, forza il gestore di richieste Node HTTP/1 |\n\nFallback della regione nel codice del provider: `options.region` → `AWS_REGION` → `AWS_DEFAULT_REGION` → `us-east-1`.\n\n### Azure OpenAI Responses\n\n| Variabile | Predefinito / comportamento |\n|---|---|\n| `AZURE_OPENAI_API_KEY` | Richiesta a meno che la chiave API non sia passata come opzione |\n| `AZURE_OPENAI_API_VERSION` | Predefinito `v1` |\n| `AZURE_OPENAI_BASE_URL` | Override diretto dell'URL base |\n| `AZURE_OPENAI_RESOURCE_NAME` | Usata per costruire l'URL base: `https://<resource>.openai.azure.com/openai/v1` |\n| `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` | Stringa di mappatura opzionale: `modelId=deploymentName,model2=deployment2` |\n\nRisoluzione dell'URL base: opzione `azureBaseUrl` → env `AZURE_OPENAI_BASE_URL` → opzione/env resource name → `model.baseUrl`.\n\n### Google Vertex AI\n\n| Variabile | Richiesta? | Note |\n|---|---|---|\n| `GOOGLE_CLOUD_PROJECT` | Sì (a meno che non sia passata nelle opzioni) | Fallback: `GCLOUD_PROJECT` |\n| `GCLOUD_PROJECT` | Fallback | Usata come fonte alternativa dell'ID progetto |\n| `GOOGLE_CLOUD_LOCATION` | Sì (a meno che non sia passata nelle opzioni) | Nessun valore predefinito nel provider |\n| `GOOGLE_APPLICATION_CREDENTIALS` | Condizionale | Se impostata, il file deve esistere; altrimenti viene verificato il percorso di fallback ADC (`~/.config/gcloud/application_default_credentials.json`) |\n\n### Kimi\n\n| Variabile | Predefinito / comportamento |\n|---|---|\n| `KIMI_CODE_OAUTH_HOST` | Override primario dell'host OAuth |\n| `KIMI_OAUTH_HOST` | Override di fallback dell'host OAuth |\n| `KIMI_CODE_BASE_URL` | Sovrascrive l'URL base dell'endpoint di utilizzo Kimi (`usage/kimi.ts`) |\n\nCatena dell'host OAuth: `KIMI_CODE_OAUTH_HOST` → `KIMI_OAUTH_HOST` → `https://auth.kimi.com`.\n\n### Compatibilità Antigravity/immagini Gemini\n\n| Variabile | Predefinito / comportamento |\n|---|---|\n| `PI_AI_ANTIGRAVITY_VERSION` | Sovrascrive il tag versione dell'user-agent Antigravity nel provider Gemini CLI |\n\n### Risposte OpenAI Codex (controlli funzionalità/debug)\n\n| Variabile | Comportamento |\n|---|---|\n| `PI_CODEX_DEBUG` | `1`/`true` abilita il logging di debug del provider Codex |\n| `PI_CODEX_WEBSOCKET` | `1`/`true` abilita la preferenza per il trasporto websocket |\n| `PI_CODEX_WEBSOCKET_V2` | `1`/`true` abilita il percorso websocket v2 |\n| `PI_CODEX_WEBSOCKET_IDLE_TIMEOUT_MS` | Override con intero positivo (predefinito 300000) |\n| `PI_CODEX_WEBSOCKET_RETRY_BUDGET` | Override con intero non negativo (predefinito 5) |\n| `PI_CODEX_WEBSOCKET_RETRY_DELAY_MS` | Override del backoff base con intero positivo (predefinito 500) |\n\n### Debug provider Cursor\n\n| Variabile | Comportamento |\n|---|---|\n| `DEBUG_CURSOR` | Abilita i log di debug del provider; `2`/`verbose` per frammenti dettagliati del payload |\n| `DEBUG_CURSOR_LOG` | Percorso file opzionale per l'output del log di debug in formato JSONL |\n\n### Switch compatibilità cache prompt\n\n| Variabile | Comportamento |\n|---|---|\n| `PI_CACHE_RETENTION` | Se `long`, abilita la retention estesa dove supportata (`anthropic`, `openai-responses`, risoluzione della retention Bedrock) |\n\n---\n\n## 3) Sottosistema di ricerca web\n\n### Credenziali provider di ricerca\n\n| Variabile | Utilizzata da |\n|---|---|\n| `EXA_API_KEY` | Provider di ricerca Exa e tool MCP Exa |\n| `BRAVE_API_KEY` | Provider di ricerca Brave |\n| `PERPLEXITY_API_KEY` | Provider di ricerca Perplexity in modalità chiave API |\n| `TAVILY_API_KEY` | Provider di ricerca Tavily |\n| `ZAI_API_KEY` | Provider di ricerca z.ai (controlla anche l'OAuth memorizzato in `agent.db`) |\n| `OPENAI_API_KEY` / OAuth Codex nel DB | Disponibilità/autenticazione del provider di ricerca Codex |\n\n### Catena di autenticazione ricerca web Anthropic\n\n`packages/coding-agent/src/web/search/auth.ts` risolve le credenziali di ricerca web Anthropic in questo ordine:\n\n1. `ANTHROPIC_SEARCH_API_KEY` (+ opzionale `ANTHROPIC_SEARCH_BASE_URL`)\n2. Voce del provider in `models.json` con `api: \"anthropic-messages\"`\n3. Credenziali OAuth Anthropic da `agent.db` (non devono scadere entro un buffer di 5 minuti)\n4. Fallback generico env Anthropic: chiave del provider (`ANTHROPIC_FOUNDRY_API_KEY`/`ANTHROPIC_OAUTH_TOKEN`/`ANTHROPIC_API_KEY`) + opzionale `ANTHROPIC_BASE_URL` (`FOUNDRY_BASE_URL` quando la modalità Foundry è abilitata)\n\nVariabili correlate:\n\n| Variabile | Predefinito / comportamento |\n|---|---|\n| `ANTHROPIC_SEARCH_API_KEY` | Chiave di ricerca esplicita con la priorità più alta |\n| `ANTHROPIC_SEARCH_BASE_URL` | Predefinito `https://api.anthropic.com` quando omesso |\n| `ANTHROPIC_SEARCH_MODEL` | Predefinito `claude-haiku-4-5` |\n| `ANTHROPIC_BASE_URL` | URL base generico di fallback per il percorso di autenticazione di livello 4 |\n\n### Flag di comportamento del flusso OAuth Perplexity\n\n| Variabile | Comportamento |\n|---|---|\n| `PI_AUTH_NO_BORROW` | Se impostata, disabilita il percorso di prestito token dell'app nativa macOS nel flusso di login Perplexity |\n\n---\n\n## 4) Strumenti Python e runtime del kernel\n\n| Variabile | Predefinito / comportamento |\n|---|---|\n| `PI_PY` | Override della modalità tool Python: `0`/`bash`=`bash-only`, `1`/`py`=`ipy-only`, `mix`/`both`=`both`; valori non validi ignorati |\n| `PI_PYTHON_SKIP_CHECK` | Se `1`, salta i controlli di disponibilità/riscaldamento del kernel Python |\n| `PI_PYTHON_GATEWAY_URL` | Se impostata, usa un gateway kernel esterno invece del gateway condiviso locale |\n| `PI_PYTHON_GATEWAY_TOKEN` | Token di autenticazione opzionale per il gateway esterno (`Authorization: token <value>`) |\n| `PI_PYTHON_IPC_TRACE` | Se `1`, abilita il percorso di traccia IPC a basso livello nel modulo kernel |\n| `VIRTUAL_ENV` | Percorso venv con la priorità più alta per la risoluzione del runtime Python |\n\nComportamento condizionale aggiuntivo:\n\n- Se `BUN_ENV=test` o `NODE_ENV=test`, i controlli di disponibilità Python sono trattati come OK e il riscaldamento viene saltato.\n- Il filtraggio dell'ambiente Python nega le chiavi API comuni e consente variabili base sicure + prefissi `LC_`, `XDG_`, `PI_`.\n\n---\n\n## 5) Toggle di comportamento dell'agente/runtime\n\n| Variabile                  | Predefinito / comportamento                                                                  |\n|----------------------------|----------------------------------------------------------------------------------------------|\n| `PI_SMOL_MODEL`            | Override effimero del ruolo modello per `smol` (il CLI `--smol` ha precedenza)               |\n| `PI_SLOW_MODEL`            | Override effimero del ruolo modello per `slow` (il CLI `--slow` ha precedenza)               |\n| `PI_PLAN_MODEL`            | Override effimero del ruolo modello per `plan` (il CLI `--plan` ha precedenza)               |\n| `PI_NO_TITLE`              | Se impostata (qualsiasi valore non vuoto), disabilita la generazione automatica del titolo della sessione al primo messaggio utente |\n| `NULL_PROMPT`              | Se `true`, il costruttore del prompt di sistema restituisce una stringa vuota                 |\n| `PI_BLOCKED_AGENT`         | Blocca un tipo specifico di sotto-agente nel tool task                                       |\n| `PI_SUBPROCESS_CMD`        | Sovrascrive il comando di spawn del sotto-agente (bypass della risoluzione `xcsh` / `xcsh.cmd`) |\n| `PI_TASK_MAX_OUTPUT_BYTES` | Massimo di byte dell'output catturato per sotto-agente (predefinito `500000`)                |\n| `PI_TASK_MAX_OUTPUT_LINES` | Massimo di righe dell'output catturato per sotto-agente (predefinito `5000`)                 |\n| `PI_TIMING`                | Se `1`, abilita i log di strumentazione dei tempi di avvio/tool                              |\n| `PI_DEBUG_STARTUP`         | Abilita le stampe di debug delle fasi di avvio su stderr in più percorsi di avvio            |\n| `PI_PACKAGE_DIR`           | Sovrascrive la risoluzione della directory base degli asset del pacchetto (ricerca percorsi docs/examples/changelog) |\n| `PI_DISABLE_LSPMUX`        | Se `1`, disabilita il rilevamento/integrazione di lspmux e forza lo spawn diretto del server LSP |\n| `LITELLM_BASE_URL`         | URL base del proxy LiteLLM. Quando impostato con `LITELLM_API_KEY`, attiva la generazione automatica di `models.yml` al primo avvio e l'auto-riparazione ad ogni avvio |\n| `LM_STUDIO_BASE_URL`       | Override dell'URL base predefinito per la scoperta implicita di LM Studio (`http://127.0.0.1:1234/v1` se non impostato) |\n| `OLLAMA_BASE_URL`          | Override dell'URL base predefinito per la scoperta implicita di Ollama (`http://127.0.0.1:11434` se non impostato) |\n| `LLAMA_CPP_BASE_URL`       | Override dell'URL base predefinito per la scoperta implicita di Llama.cpp (`http://127.0.0.1:8080` se non impostato) |\n| `PI_EDIT_VARIANT`          | Se `hashline`, forza la modalità di visualizzazione hashline per read/grep quando il tool edit è disponibile |\n| `PI_NO_PTY`                | Se `1`, disabilita il percorso PTY interattivo per il tool bash                              |\n\n`PI_NO_PTY` viene anche impostata internamente quando viene usato il CLI `--no-pty`.\n\n---\n\n## 6) Percorsi root di storage e configurazione\n\nQueste sono consumate tramite `@f5-sales-demo/pi-utils/dirs` e influenzano dove coding-agent memorizza i dati.\n\n| Variabile | Predefinito / comportamento |\n|---|---|\n| `PI_CONFIG_DIR` | Nome della directory root di configurazione sotto home (predefinito `.xcsh`) |\n| `PI_CODING_AGENT_DIR` | Override completo per la directory dell'agente (predefinito `~/<PI_CONFIG_DIR o .xcsh>/agent`) |\n| `PWD` | Usata per la corrispondenza della directory di lavoro corrente canonica negli helper dei percorsi |\n\n---\n\n## 7) Ambiente di esecuzione shell/tool\n\n(Da `packages/utils/src/procmgr.ts` e integrazione del tool bash di coding-agent.)\n\n| Variabile | Comportamento |\n|---|---|\n| `PI_BASH_NO_CI` | Sopprime l'iniezione automatica di `CI=true` nell'ambiente della shell generata |\n| `CLAUDE_BASH_NO_CI` | Alias legacy di fallback per `PI_BASH_NO_CI` |\n| `PI_BASH_NO_LOGIN` | Destinata a disabilitare la modalità shell di login |\n| `CLAUDE_BASH_NO_LOGIN` | Alias legacy di fallback per `PI_BASH_NO_LOGIN` |\n| `PI_SHELL_PREFIX` | Wrapper prefisso comando opzionale |\n| `CLAUDE_CODE_SHELL_PREFIX` | Alias legacy di fallback per `PI_SHELL_PREFIX` |\n| `VISUAL` | Comando dell'editor esterno preferito |\n| `EDITOR` | Comando dell'editor esterno di fallback |\n\nNota sull'implementazione corrente: `PI_BASH_NO_LOGIN`/`CLAUDE_BASH_NO_LOGIN` vengono lette, ma l'attuale `getShellArgs()` restituisce `['-l','-c']` in entrambi i rami (effettivamente senza effetto oggi).\n\n---\n\n## 8) Rilevamento UI/tema/sessione (env auto-rilevato)\n\nQueste vengono lette come segnali runtime; sono solitamente impostate dal terminale/SO piuttosto che configurate manualmente.\n\n| Variabile | Utilizzata per |\n|---|---|\n| `COLORTERM`, `TERM`, `WT_SESSION` | Rilevamento delle capacità colore (modalità colore del tema) |\n| `COLORFGBG` | Auto-rilevamento sfondo chiaro/scuro del terminale |\n| `TERM_PROGRAM`, `TERM_PROGRAM_VERSION`, `TERMINAL_EMULATOR` | Identità del terminale nel prompt/contesto di sistema |\n| `KDE_FULL_SESSION`, `XDG_CURRENT_DESKTOP`, `DESKTOP_SESSION`, `XDG_SESSION_DESKTOP`, `GDMSESSION`, `WINDOWMANAGER` | Rilevamento desktop/window-manager nel prompt/contesto di sistema |\n| `KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION` | ID breadcrumb stabili per sessione del terminale |\n| `SHELL`, `ComSpec`, `TERM_PROGRAM`, `TERM` | Diagnostica informazioni di sistema |\n| `APPDATA`, `XDG_CONFIG_HOME` | Risoluzione del percorso di configurazione lspmux |\n| `HOME` | Abbreviazione dei percorsi nell'interfaccia dei comandi MCP |\n\n---\n\n## 9) Flag del loader nativo/debug\n\n| Variabile | Comportamento |\n|---|---|\n| `PI_DEV` | Abilita la diagnostica verbosa del caricamento degli addon nativi in `packages/natives` |\n\n## 10) Flag runtime TUI (pacchetto condiviso, influenza l'UX di coding-agent)\n\n| Variabile | Comportamento |\n|---|---|\n| `PI_NOTIFICATIONS` | `off` / `0` / `false` sopprimono le notifiche desktop |\n| `PI_TUI_WRITE_LOG` | Se impostata, registra le scritture TUI su file |\n| `PI_HARDWARE_CURSOR` | Se `1`, abilita la modalità cursore hardware |\n| `PI_CLEAR_ON_SHRINK` | Se `1`, cancella le righe vuote quando il contenuto si riduce |\n| `PI_DEBUG_REDRAW` | Se `1`, abilita il logging di debug del ridisegno |\n| `PI_TUI_DEBUG` | Se `1`, abilita il percorso di dump di debug approfondito della TUI |\n\n---\n\n## 11) Controlli generazione commit\n\n| Variabile | Comportamento |\n|---|---|\n| `PI_COMMIT_TEST_FALLBACK` | Se `true` (case-insensitive), forza il percorso di generazione commit di fallback |\n| `PI_COMMIT_NO_FALLBACK` | Se `true`, disabilita il fallback quando l'agente non restituisce alcuna proposta |\n| `PI_COMMIT_MAP_REDUCE` | Se `false`, disabilita il percorso di analisi commit map-reduce |\n| `DEBUG` | Se impostata, vengono stampati gli stack trace degli errori dell'agente commit |\n\n---\n\n## Variabili sensibili per la sicurezza\n\nTrattare queste come segreti; non registrarle nei log né includerle nei commit:\n\n- Chiavi API/provider e credenziali OAuth/bearer (tutte le `*_API_KEY`, `*_TOKEN`, token di accesso/refresh OAuth)\n- Credenziali cloud (`AWS_*`, il percorso `GOOGLE_APPLICATION_CREDENTIALS` può esporre materiale dell'account di servizio)\n- Variabili di autenticazione ricerca/provider (`EXA_API_KEY`, `BRAVE_API_KEY`, `PERPLEXITY_API_KEY`, chiavi di ricerca Anthropic)\n- Materiale mTLS Foundry (`CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`, `NODE_EXTRA_CA_CERTS` quando punta a bundle CA privati)\n\nIl runtime Python inoltre elimina esplicitamente molte variabili chiave comuni prima di generare i sottoprocessi del kernel (`packages/coding-agent/src/ipy/runtime.ts`).\n",
	"it/configuration/fs-scan-cache-architecture.md": "---\ntitle: Architettura della cache per la scansione del filesystem\ndescription: >-\n  Filesystem scan cache contract for fast file discovery with\n  stale-while-revalidate semantics.\nsidebar:\n  order: 8\n  label: Cache per la scansione del filesystem\ni18n:\n  sourceHash: 2a2bde1726ac\n  translator: machine\n---\n\n# Contratto architetturale della cache per la scansione del filesystem\n\nQuesto documento definisce il contratto attuale per la cache condivisa di scansione del filesystem implementata in Rust (`crates/pi-natives/src/fs_cache.rs`) e utilizzata dalle API native di discovery/search esposte a `packages/coding-agent`.\n\n## Cos'è questa cache\n\nLa cache memorizza liste complete di entry di scansione delle directory (`GlobMatch[]`) indicizzate per ambito di scansione e politica di attraversamento, consentendo poi alle operazioni di livello superiore (filtraggio glob, scoring fuzzy, selezione file per grep) di operare su tali entry memorizzate.\n\nObiettivi principali:\n\n- evitare attraversamenti ripetuti del filesystem per chiamate di discovery/search ripetute\n- mantenere la coerenza tra `glob`, `fuzzyFind` e `grep` quando condividono la stessa politica di scansione\n- consentire il recupero esplicito dalla obsolescenza per risultati vuoti e l'invalidazione esplicita dopo mutazioni dei file\n\n## Proprietà e superficie pubblica\n\n- Implementazione e politica della cache: `crates/pi-natives/src/fs_cache.rs`\n- Consumer nativi:\n  - `crates/pi-natives/src/glob.rs`\n  - `crates/pi-natives/src/fd.rs` (`fuzzyFind`)\n  - `crates/pi-natives/src/grep.rs`\n- Binding/export JS:\n  - `packages/natives/src/glob/index.ts` (`invalidateFsScanCache`)\n  - `packages/natives/src/glob/types.ts`\n  - `packages/natives/src/grep/types.ts`\n- Helper di invalidazione per le mutazioni del coding-agent:\n  - `packages/coding-agent/src/tools/fs-cache-invalidation.ts`\n\n## Partizionamento della chiave di cache (contratto rigido)\n\nOgni entry è indicizzata da:\n\n- percorso della directory `root` canonicalizzato\n- booleano `include_hidden`\n- booleano `use_gitignore`\n\nImplicazioni:\n\n- Le scansioni con e senza file nascosti **non** condividono le entry.\n- Le scansioni che rispettano gitignore e quelle con ignore disabilitato **non** condividono le entry.\n- I consumer devono passare semantiche stabili per il comportamento hidden/gitignore; modificare uno dei due flag crea una partizione di cache diversa.\n\nL'inclusione di `node_modules` **non** è nella chiave di cache. La cache memorizza le entry con `node_modules` incluso; il filtraggio per consumer viene applicato dopo il recupero.\n\n## Comportamento della raccolta di scansione\n\nIl popolamento della cache utilizza un walker deterministico (`ignore::WalkBuilder`) configurato tramite `include_hidden` e `use_gitignore`:\n\n- `follow_links(false)`\n- ordinato per percorso file\n- `.git` viene sempre saltato\n- `node_modules` viene sempre raccolto al momento della scansione per la cache (e opzionalmente filtrato successivamente)\n- il tipo di file dell'entry e `mtime` vengono catturati tramite `symlink_metadata`\n\nLe radici di ricerca vengono risolte da `resolve_search_path`:\n\n- i percorsi relativi vengono risolti rispetto alla cwd corrente\n- il target deve essere una directory esistente\n- la radice viene canonicalizzata quando possibile\n\n## Politica di freschezza ed eviction\n\nPolitica globale (sovrascrivibile tramite variabili d'ambiente):\n\n- `FS_SCAN_CACHE_TTL_MS` (default `1000`)\n- `FS_SCAN_EMPTY_RECHECK_MS` (default `200`)\n- `FS_SCAN_CACHE_MAX_ENTRIES` (default `16`)\n\nComportamento:\n\n- `get_or_scan(...)`\n  - se il TTL è `0`: bypass completo della cache, scansione sempre fresca (`cache_age_ms = 0`)\n  - in caso di cache hit entro il TTL: restituisce le entry memorizzate + `cache_age_ms` diverso da zero\n  - in caso di hit scaduto: rimuove la chiave, riesegue la scansione, memorizza una entry fresca\n- l'applicazione del limite massimo di entry avviene tramite eviction dei più vecchi per `created_at`\n\n## Ricontrollo rapido per risultati vuoti (separato dagli hit normali)\n\nCache hit normale:\n\n- un cache hit entro il TTL restituisce le entry memorizzate e non fa altro.\n\nRicontrollo rapido per risultati vuoti:\n\n- questa è una politica **lato chiamante** che utilizza `ScanResult.cache_age_ms`\n- se il risultato filtrato/query è vuoto e l'età della scansione in cache è almeno `empty_recheck_ms()`, il chiamante esegue un `force_rescan(...)` e riprova\n- destinato a ridurre i risultati falsi negativi obsoleti quando i file sono stati aggiunti di recente ma la cache è ancora entro il TTL\n\nConsumer attuali:\n\n- `glob`: ricontrolla quando i match filtrati sono vuoti e l'età della scansione supera la soglia\n- `fuzzyFind` (`fd.rs`): ricontrolla solo quando la query non è vuota e i match con scoring sono vuoti\n- `grep`: ricontrolla quando la lista dei file candidati selezionati è vuota\n\n## Valori predefiniti dei consumer e utilizzo della cache\n\nLa cache è opt-in su tutte le API esposte (`cache?: boolean`, default `false`).\n\nValori predefiniti attuali nelle API native:\n\n- `glob`: `hidden=false`, `gitignore=true`, `cache=false`\n- `fuzzyFind`: `hidden=false`, `gitignore=true`, `cache=false`\n- `grep`: `hidden=true`, `cache=false`, e la scansione della cache utilizza sempre `use_gitignore=true`\n\nChiamanti del coding-agent attualmente:\n\n- Il discovery dei candidati per mention ad alto volume abilita la cache:\n  - `packages/coding-agent/src/utils/file-mentions.ts`\n  - profilo: `hidden=true`, `gitignore=true`, `includeNodeModules=true`, `cache=true`\n- L'integrazione di `grep` a livello tool attualmente disabilita la scan cache (`cache: false`):\n  - `packages/coding-agent/src/tools/grep.ts`\n\n## Contratto di invalidazione\n\nPunto di ingresso nativo per l'invalidazione:\n\n- `invalidateFsScanCache(path?: string)`\n  - con `path`: rimuove le entry di cache la cui radice è un prefisso del percorso target\n  - senza path: cancella tutte le entry della scan cache\n\nDettagli sulla gestione dei percorsi:\n\n- i percorsi di invalidazione relativi vengono risolti rispetto alla cwd\n- l'invalidazione tenta la canonicalizzazione\n- se il target non esiste (ad esempio, dopo una cancellazione), il fallback canonicalizza il parent e riattacca il nome file quando possibile\n- questo preserva il comportamento di invalidazione per creazione/cancellazione/rinomina dove uno dei lati potrebbe non esistere\n\n## Responsabilità del flusso di mutazione del coding-agent\n\nIl codice del coding-agent deve invalidare dopo mutazioni del filesystem avvenute con successo.\n\nHelper centrali:\n\n- `invalidateFsScanAfterWrite(path)`\n- `invalidateFsScanAfterDelete(path)`\n- `invalidateFsScanAfterRename(oldPath, newPath)` (invalida entrambi i lati quando i percorsi differiscono)\n\nCallsite attuali dei tool di mutazione:\n\n- `packages/coding-agent/src/tools/write.ts`\n- `packages/coding-agent/src/patch/index.ts` (flussi hashline/patch/replace)\n\nRegola: se un flusso muta il contenuto o la posizione nel filesystem e bypassa questi helper, sono prevedibili bug di obsolescenza della cache.\n\n## Aggiungere un nuovo consumer della cache in sicurezza\n\nQuando si introduce l'uso della cache in un nuovo percorso di scanner/search:\n\n1. **Usare input stabili per la politica di scansione**\n   - decidere prima la semantica hidden/gitignore\n   - passarli in modo coerente a `get_or_scan`/`force_rescan` affinché le partizioni di cache siano intenzionali\n\n2. **Trattare i dati della cache come pre-filtrati solo dalla politica di attraversamento**\n   - applicare il filtraggio specifico del tool (pattern glob, filtri di tipo, regole node_modules) dopo il recupero\n   - non assumere mai che le entry memorizzate riflettano già i filtri di livello superiore\n\n3. **Implementare il ricontrollo rapido per risultati vuoti solo per il rischio di falsi negativi obsoleti**\n   - usare `scan.cache_age_ms >= empty_recheck_ms()`\n   - riprovare una volta con `force_rescan(..., store=true, ...)`\n   - mantenere questo percorso separato dalla logica di cache-hit normale\n\n4. **Rispettare esplicitamente la modalità no-cache**\n   - quando il chiamante disabilita la cache, chiamare `force_rescan(..., store=false, ...)`\n   - non popolare la cache condivisa in un percorso di richiesta no-cache\n\n5. **Collegare l'invalidazione per le mutazioni per qualsiasi nuovo percorso di scrittura**\n   - dopo una scrittura/modifica/cancellazione/rinomina riuscita, chiamare l'helper di invalidazione del coding-agent\n   - per rinomina/spostamento, invalidare sia il vecchio che il nuovo percorso\n\n6. **Non aggiungere parametri TTL per singola chiamata**\n   - il contratto attuale prevede solo una politica globale (configurata via variabili d'ambiente), nessun override del TTL per singola richiesta\n\n## Limiti noti\n\n- L'ambito della cache è in-memory e locale al processo (`DashMap`), non persistito tra i riavvii del processo.\n- La cache memorizza le entry di scansione, non i risultati finali dei tool.\n- `glob`/`fuzzyFind`/`grep` condividono le entry di scansione solo quando le dimensioni della chiave (`root`, `hidden`, `gitignore`) corrispondono.\n- `.git` viene sempre escluso al momento della raccolta di scansione indipendentemente dalle opzioni del chiamante.\n",
	"it/configuration/hooks.md": "---\ntitle: Hook\ndescription: >-\n  Sistema di hook per l'automazione di eventi pre/post nel ciclo di vita\n  dell'agente di codifica.\nsidebar:\n  order: 4\n  label: Hook\ni18n:\n  sourceHash: cdbec10bc405\n  translator: machine\n---\n\n# Hook\n\nQuesto documento descrive il **codice corrente del sottosistema hook** in `src/extensibility/hooks/*`.\n\n## Stato attuale nel runtime\n\nIl pacchetto hook (`src/extensibility/hooks/`) è ancora esportato e utilizzabile come superficie API, ma il runtime CLI predefinito ora inizializza il percorso dell'**extension runner**. Nel flusso di avvio attuale:\n\n- `--hook` è trattato come alias di `--extension` (i percorsi CLI vengono uniti in `additionalExtensionPaths`)\n- gli strumenti sono avvolti da `ExtensionToolWrapper`, non da `HookToolWrapper`\n- le trasformazioni di contesto e le emissioni del ciclo di vita passano attraverso `ExtensionRunner`\n\nQuesto file documenta quindi l'implementazione del sottosistema hook (tipi/loader/runner/wrapper), inclusi il comportamento legacy e i vincoli.\n\n## File principali\n\n- `src/extensibility/hooks/types.ts` — contesto hook, tipi di evento e contratti di risultato\n- `src/extensibility/hooks/loader.ts` — caricamento dei moduli e bridge per la scoperta degli hook\n- `src/extensibility/hooks/runner.ts` — dispatch degli eventi, ricerca dei comandi e segnalazione degli errori\n- `src/extensibility/hooks/tool-wrapper.ts` — wrapper di intercettazione pre/post degli strumenti\n- `src/extensibility/hooks/index.ts` — esportazioni/riesportazioni\n\n## Cos'è un modulo hook\n\nUn modulo hook deve esportare di default una factory:\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function hook(pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName === \"bash\" && String(event.input.command ?? \"\").includes(\"rm -rf\")) {\n   return { block: true, reason: \"blocked by policy\" };\n  }\n });\n}\n```\n\nLa factory può:\n\n- registrare gestori di eventi con `pi.on(...)`\n- inviare messaggi personalizzati persistenti con `pi.sendMessage(...)`\n- persistere stato non-LLM con `pi.appendEntry(...)`\n- registrare comandi slash tramite `pi.registerCommand(...)`\n- registrare renderer personalizzati dei messaggi tramite `pi.registerMessageRenderer(...)`\n- eseguire comandi shell tramite `pi.exec(...)`\n\n## Scoperta e caricamento\n\n`discoverAndLoadHooks(configuredPaths, cwd)` esegue:\n\n1. Carica gli hook scoperti dal registro delle capability (`loadCapability(\"hooks\")`)\n2. Aggiunge i percorsi configurati esplicitamente (deduplicati per percorso assoluto)\n3. Chiama `loadHooks(allPaths, cwd)`\n\n`loadHooks` importa quindi ciascun percorso e si aspetta una funzione `default`.\n\n### Risoluzione dei percorsi\n\n`loader.ts` risolve i percorsi degli hook come segue:\n\n- percorso assoluto: utilizzato così com'è\n- percorso con `~`: espanso\n- percorso relativo: risolto rispetto a `cwd`\n\n### Importante discrepanza legacy\n\nI provider di scoperta per `hookCapability` modellano ancora file hook shell-style pre/post (ad esempio `.claude/hooks/pre/*`, `.xcsh/.../hooks/pre/*`).\n\nIl loader degli hook qui utilizza l'importazione dinamica dei moduli e richiede una factory hook JS/TS di default. Se un percorso hook scoperto non è importabile come modulo, il caricamento fallisce e viene riportato in `LoadHooksResult.errors`.\n\n## Superfici degli eventi\n\nGli eventi hook sono fortemente tipizzati in `types.ts`.\n\n### Eventi di sessione\n\n- `session_start`\n- `session_before_switch` → può restituire `{ cancel?: boolean }`\n- `session_switch`\n- `session_before_branch` → può restituire `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_branch`\n- `session_before_compact` → può restituire `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session.compacting` → può restituire `{ context?: string[]; prompt?: string; preserveData?: Record<string, unknown> }`\n- `session_compact`\n- `session_before_tree` → può restituire `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n- `session_tree`\n- `session_shutdown`\n\n### Eventi agente/contesto\n\n- `context` → può restituire `{ messages?: Message[] }`\n- `before_agent_start` → può restituire `{ message?: { customType; content; display; details } }`\n- `agent_start`\n- `agent_end`\n- `turn_start`\n- `turn_end`\n- `auto_compaction_start`\n- `auto_compaction_end`\n- `auto_retry_start`\n- `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### Eventi degli strumenti (modello pre/post)\n\n- `tool_call` (pre-esecuzione) → può restituire `{ block?: boolean; reason?: string }`\n- `tool_result` (post-esecuzione) → può restituire `{ content?; details?; isError? }`\n\nQuesto è il modello di intercettazione pre/post centrale del sottosistema hook.\n\n```text\nFlusso di intercettazione degli strumenti hook\n\ngestori tool_call\n   │\n   ├─ qualcuno restituisce { block: true }? ── sì ──> throw (strumento bloccato)\n   │\n   └─ no\n      │\n      ▼\n   esecuzione dello strumento sottostante\n      │\n      ├─ successo ──> i gestori tool_result possono sovrascrivere { content, details }\n      │\n      └─ errore   ──> emette tool_result(isError=true) poi rilancia l'errore originale\n```\n\n## Modello di esecuzione e semantica delle mutazioni\n\n### 1) Pre-esecuzione: `tool_call`\n\n`HookToolWrapper.execute()` emette `tool_call` prima dell'esecuzione dello strumento.\n\n- se un qualsiasi gestore restituisce `{ block: true }`, l'esecuzione si interrompe\n- se un gestore lancia un'eccezione, il wrapper fallisce in modo sicuro e blocca l'esecuzione\n- il `reason` restituito diventa il testo dell'errore lanciato\n\n### 2) Esecuzione dello strumento\n\nLo strumento sottostante viene eseguito normalmente se non bloccato.\n\n### 3) Post-esecuzione: `tool_result`\n\nDopo il successo, il wrapper emette `tool_result` con:\n\n- `toolName`, `toolCallId`, `input`\n- `content`\n- `details`\n- `isError: false`\n\nSe un gestore restituisce delle sovrascritture:\n\n- `content` può sostituire il contenuto del risultato\n- `details` può sostituire i dettagli del risultato\n\nIn caso di fallimento dello strumento, il wrapper emette `tool_result` con `isError: true` e il contenuto del testo di errore, poi rilancia l'errore originale.\n\n### Cosa possono mutare gli hook\n\n- il contesto LLM per una singola chiamata tramite `context` (catena di sostituzione dei `messages`)\n- il contenuto/i dettagli dell'output degli strumenti per le chiamate riuscite (percorso `tool_result`)\n- il messaggio iniettato prima dell'agente tramite `before_agent_start`\n- il comportamento di cancellazione/compattazione personalizzata/tree tramite `session_before_*` e `session.compacting`\n\n### Cosa non possono mutare gli hook in questa implementazione\n\n- i parametri di input raw degli strumenti in-place (solo block/allow su `tool_call`)\n- la continuazione dell'esecuzione dopo errori degli strumenti lanciati (il percorso di errore rilancia)\n- lo stato finale di successo/errore nel comportamento del wrapper (il `isError` restituito è tipizzato ma non applicato da `HookToolWrapper`)\n\n## Ordinamento e comportamento in caso di conflitto\n\n### Ordinamento a livello di scoperta\n\nI provider di capability sono ordinati per priorità (dalla più alta). La deduplicazione avviene per chiave di capability, vince il primo.\n\nPer `hooks`, la chiave di capability è `${type}:${tool}:${name}`. I duplicati oscurati da provider a priorità inferiore vengono contrassegnati ed esclusi dalla lista di scoperta effettiva.\n\n### Ordine di caricamento\n\n`discoverAndLoadHooks` costruisce una lista `allPaths` piatta, deduplicata per percorso assoluto risolto, poi `loadHooks` itera in quell'ordine.\nL'ordine dei file all'interno di ciascuna directory scoperta dipende dall'output di `readdir`; il loader degli hook non esegue un ordinamento aggiuntivo.\n\n### Ordine dei gestori a runtime\n\nAll'interno di `HookRunner`, l'ordine è deterministico per sequenza di registrazione:\n\n1. ordine dell'array degli hook\n2. ordine di registrazione dei gestori per hook/evento\n\nComportamento in caso di conflitto per tipo di evento:\n\n- `tool_call`: vince l'ultimo risultato restituito a meno che un gestore blocchi; il primo blocco causa un cortocircuito\n- `tool_result`: vince l'ultima sovrascrittura restituita (nessun cortocircuito)\n- `context`: concatenato; ciascun gestore riceve l'output dei messaggi del gestore precedente\n- `before_agent_start`: viene mantenuto il primo messaggio restituito; i messaggi successivi vengono ignorati\n- `session_before_*`: viene tracciato l'ultimo risultato restituito; `cancel: true` causa un cortocircuito immediato\n- `session.compacting`: vince l'ultimo risultato restituito\n\nConflitti di comandi/renderer:\n\n- `getCommand(name)` restituisce la prima corrispondenza tra gli hook (vince il primo caricato)\n- `getMessageRenderer(customType)` restituisce la prima corrispondenza\n- `getRegisteredCommands()` restituisce tutti i comandi (senza deduplicazione)\n\n## Interazioni con l'interfaccia utente (`HookContext.ui`)\n\n`HookUIContext` include:\n\n- `select`, `confirm`, `input`, `editor`\n- `notify`\n- `setStatus`\n- `custom`\n- `setEditorText`, `getEditorText`\n- getter `theme`\n\n`ctx.hasUI` indica se l'interfaccia utente interattiva è disponibile.\n\nQuando si esegue senza interfaccia utente, il comportamento predefinito del contesto no-op è:\n\n- `select/input/editor` restituiscono `undefined`\n- `confirm` restituisce `false`\n- `notify`, `setStatus`, `setEditorText` sono no-op\n- `getEditorText` restituisce `\"\"`\n\n### Comportamento della riga di stato\n\nIl testo di stato hook impostato tramite `ctx.ui.setStatus(key, text)` è:\n\n- memorizzato per chiave\n- ordinato per nome della chiave\n- sanificato (`\\r`, `\\n`, `\\t` → spazi; spazi ripetuti compressi)\n- unito e troncato in larghezza per la visualizzazione\n\n## Propagazione degli errori e fallback\n\n### In fase di caricamento\n\n- modulo non valido o export default mancante → catturato in `LoadHooksResult.errors`\n- il caricamento continua per gli altri hook\n\n### In fase di evento\n\n`HookRunner.emit(...)` cattura gli errori dei gestori per la maggior parte degli eventi ed emette `HookError` ai listener (`hookPath`, `event`, `error`), poi continua.\n\n`emitToolCall(...)` è più rigoroso: gli errori dei gestori non vengono inghiottiti; si propagano al chiamante. In `HookToolWrapper`, questo blocca la chiamata allo strumento (fail-safe).\n\n## Esempi API realistici\n\n### Bloccare comandi bash non sicuri\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName !== \"bash\") return;\n  const cmd = String(event.input.command ?? \"\");\n  if (!cmd.includes(\"rm -rf\")) return;\n\n  if (!ctx.hasUI) return { block: true, reason: \"rm -rf blocked (no UI)\" };\n  const ok = await ctx.ui.confirm(\"Dangerous command\", `Allow: ${cmd}`);\n  if (!ok) return { block: true, reason: \"user denied command\" };\n });\n}\n```\n\n### Oscurare l'output degli strumenti in post-esecuzione\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_result\", async event => {\n  if (event.toolName !== \"read\" || event.isError) return;\n\n  const redacted = event.content.map(chunk => {\n   if (chunk.type !== \"text\") return chunk;\n   return { ...chunk, text: chunk.text.replaceAll(/API_KEY=\\S+/g, \"API_KEY=[REDACTED]\") };\n  });\n\n  return { content: redacted };\n });\n}\n```\n\n### Modificare il contesto del modello per ogni chiamata LLM\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"context\", async event => {\n  const filtered = event.messages.filter(msg => !(msg.role === \"custom\" && msg.customType === \"debug-only\"));\n  return { messages: filtered };\n });\n}\n```\n\n### Registrare un comando slash con metodi di contesto sicuri per i comandi\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.registerCommand(\"handoff\", {\n  description: \"Create a new session with setup message\",\n  handler: async (_args, ctx) => {\n   await ctx.waitForIdle();\n   await ctx.newSession({\n    parentSession: ctx.sessionManager.getSessionFile(),\n    setup: async sm => {\n     sm.appendMessage({\n      role: \"user\",\n      content: [{ type: \"text\", text: \"Continue from prior session summary.\" }],\n      timestamp: Date.now(),\n     });\n    },\n   });\n  },\n });\n}\n```\n\n## Superficie di esportazione\n\n`src/extensibility/hooks/index.ts` esporta:\n\n- API di caricamento (`discoverAndLoadHooks`, `loadHooks`)\n- runner e wrapper (`HookRunner`, `HookToolWrapper`)\n- tutti i tipi hook\n- riesportazione di `execCommand`\n\nE il root del pacchetto (`src/index.ts`) riesporta i **tipi** hook come superficie di compatibilità legacy.\n",
	"it/configuration/porting-from-pi-mono.md": "---\ntitle: 'Migrazione da pi-mono: Guida Pratica al Merge'\ndescription: Guida pratica per migrare il codice dal monorepo pi-mono nel codebase xcsh.\nsidebar:\n  order: 9\n  label: Migrazione da pi-mono\ni18n:\n  sourceHash: fd4e8c09303d\n  translator: machine\n---\n\n# Migrazione da pi-mono: Guida Pratica al Merge\n\nQuesta guida è una checklist ripetibile per il porting delle modifiche da pi-mono in questo repository.\nUsala per qualsiasi merge: singolo file, feature branch o sincronizzazione completa di una release.\n\n## Ultimo Punto di Sincronizzazione\n\n**Commit:** `b21b42d032919de2f2e6920a76fa9a37c3920c0a`\n**Data:** 2026-03-22\n\nAggiorna questa sezione dopo ogni sincronizzazione; non riutilizzare l'intervallo precedente.\n\nQuando avvii una nuova sincronizzazione, genera le patch da questo commit in avanti:\n\n```bash\ngit format-patch b21b42d032919de2f2e6920a76fa9a37c3920c0a..HEAD --stdout > changes.patch\n```\n\n## 0) Definire l'ambito\n\n- Identifica il riferimento upstream (commit, tag o PR).\n- Elenca i pacchetti o le cartelle che intendi modificare.\n- Decidi quali funzionalità sono nell'ambito e quali sono intenzionalmente escluse.\n\n## 1) Portare il codice in sicurezza\n\n- Preferisci un diff pulito e mirato piuttosto che una copia all'ingrosso.\n- Evita di copiare artefatti compilati o file generati.\n- Se upstream ha aggiunto nuovi file, aggiungili esplicitamente e revisiona il contenuto.\n\n## 2) Rispettare le convenzioni delle estensioni negli import\n\nLa maggior parte dei sorgenti TypeScript di runtime omette `.js` negli import interni, ma alcuni entrypoint di test/bench mantengono `.js` per la compatibilità runtime ESM. Segui lo stile esistente del pacchetto locale; non rimuovere le estensioni indiscriminatamente.\n\n- In `packages/coding-agent` nei sorgenti runtime, mantieni gli import interni senza estensione a meno che non si importino asset non-TS.\n- In `packages/tui/test` e `packages/natives/bench`, mantieni `.js` dove i file circostanti lo utilizzano già.\n- Mantieni le estensioni reali dei file quando richiesto dagli strumenti (es. `.json`, `.css`, embed di testo `.md`).\n- Esempio: `import { x } from \"./foo.js\";` → `import { x } from \"./foo\";` (solo quando la convenzione del pacchetto è senza estensione).\n\n## 3) Sostituire gli scope degli import\n\nUpstream utilizza scope di pacchetto diversi. Sostituiscili in modo coerente.\n\n- Sostituisci i vecchi scope con lo scope locale utilizzato qui.\n- Esempi (adatta in base ai pacchetti effettivi che stai portando):\n  - `@mariozechner/pi-coding-agent` → `@f5-sales-demo/xcsh`\n  - `@mariozechner/pi-agent-core` → `@f5-sales-demo/pi-agent-core`\n  - `@mariozechner/pi-tui` → `@f5-sales-demo/pi-tui`\n  - `@mariozechner/pi-ai` → `@f5-sales-demo/pi-ai`\n\n## 4) Usare le API Bun dove migliorano rispetto a Node\n\nEseguiamo su Bun. Sostituisci le API Node solo quando Bun fornisce un'alternativa migliore.\n\n**SOSTITUISCI:**\n\n- Spawning di processi: `child_process.spawn` → Bun Shell `$` per comandi semplici, `Bun.spawn`/`Bun.spawnSync` per streaming o lavoro di lunga durata\n- I/O su file: `fs.readFileSync` → `Bun.file().text()` / `Bun.write()`\n- Client HTTP: `node-fetch`, `axios` → `fetch` nativo\n- Hashing crittografico: `node:crypto` → Web Crypto o `Bun.hash`\n- SQLite: `better-sqlite3` → `bun:sqlite`\n- Caricamento env: `dotenv` → Bun carica `.env` automaticamente\n\n**NON SOSTITUIRE (funzionano correttamente in Bun):**\n\n- `os.homedir()` — NON sostituire con `Bun.env.HOME`, `Bun.env.HOME`, o il letterale `\"~\"`\n- `os.tmpdir()` — NON sostituire con `Bun.env.TMPDIR || \"/tmp\"` o percorsi hardcoded\n- `fs.mkdtempSync()` — NON sostituire con costruzione manuale del percorso\n- `path.join()`, `path.resolve()`, ecc. — vanno bene così\n\n**Stile degli import:** Usa il prefisso `node:` solo con import di namespace (nessun import nominato da `node:fs` o `node:path`).\n\n**Convenzioni Bun aggiuntive:**\n\n- Preferisci Bun Shell `$` per comandi brevi e non-streaming; usa `Bun.spawn` solo quando hai bisogno di I/O streaming o controllo del processo.\n- Usa `Bun.file()`/`Bun.write()` per i file e `node:fs/promises` per le directory.\n- Evita i controlli `Bun.file().exists()`; usa la gestione `isEnoent` in try/catch.\n- Preferisci `Bun.sleep(ms)` rispetto ai wrapper `setTimeout`.\n\n**Sbagliato:**\n\n```typescript\n// BROKEN: env vars may be undefined, \"~\" is not expanded\nconst home = Bun.env.HOME || \"~\";\nconst tmp = Bun.env.TMPDIR || \"/tmp\";\n```\n\n**Corretto:**\n\n```typescript\nimport * as os from \"node:os\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst configDir = path.join(os.homedir(), \".config\", \"myapp\");\nconst tempDir = fs.mkdtempSync(path.join(os.tmpdir(), \"myapp-\"));\n```\n\n## 5) Preferire gli embed Bun (niente copie)\n\nNon copiare asset runtime o file vendor al momento del build.\n\n- Se upstream copia asset in una cartella dist, sostituisci con embed compatibili con Bun.\n- I prompt sono file `.md` statici; usa gli import di testo Bun (`with { type: \"text\" }`) e Handlebars invece di stringhe di prompt inline.\n- Usa `import.meta.dir` + `Bun.file` per caricare risorse non-testuali adiacenti.\n- Mantieni gli asset nel repository e lascia che il bundler li includa.\n- Elimina gli script di copia a meno che l'utente non li richieda esplicitamente.\n- Se upstream legge un file di fallback incluso nel bundle a runtime, sostituisci le letture del filesystem con un import embed di testo Bun.\n  - Esempio (fallback istruzioni Codex):\n    - `const FALLBACK_PROMPT_PATH = join(import.meta.dir, \"codex-instructions.md\");` -> rimosso\n    - `import FALLBACK_INSTRUCTIONS from \"./codex-instructions.md\" with { type: \"text\" };`\n    - Usa `return FALLBACK_INSTRUCTIONS;` invece di `readFileSync(FALLBACK_PROMPT_PATH, \"utf8\")`\n\n## 6) Portare `package.json` con attenzione\n\nTratta `package.json` come un contratto. Effettua il merge intenzionalmente.\n\n- Mantieni `name`, `version`, `type`, `exports` e `bin` esistenti a meno che il porting non richieda modifiche.\n- Sostituisci gli script npm/node con equivalenti Bun (es. `bun check`, `bun test`).\n- Assicurati che le dipendenze usino lo scope corretto.\n- Non fare downgrade delle dipendenze per correggere errori di tipo; fai upgrade invece.\n- Verifica i link dei pacchetti workspace e le `peerDependencies`.\n\n## 7) Allineare lo stile del codice e gli strumenti\n\n- Mantieni le convenzioni di formattazione esistenti.\n- Non introdurre `any` a meno che non sia necessario.\n- Evita import dinamici e import di tipo inline; usa solo import di primo livello.\n- Non costruire mai prompt nel codice; i prompt sono file `.md` statici renderizzati con Handlebars.\n- In coding-agent, non usare mai `console.log`/`console.warn`/`console.error`; usa `logger` da `@f5-sales-demo/pi-utils`.\n- Usa `Promise.withResolvers()` invece di `new Promise((resolve, reject) => ...)`.\n- **Nessuna keyword `private`/`protected`/`public` sui campi o metodi delle classi.** Usa i campi privati ES `#` per l'incapsulamento; lascia i membri accessibili senza keyword. L'unica eccezione sono le proprietà dei parametri del costruttore (`constructor(private readonly x: T)`), dove la keyword è richiesta da TypeScript. Quando porti codice upstream che usa `private foo` o `protected bar`, converti in `#foo` (privato) o `bar` senza keyword (accessibile).\n- Preferisci helper e utilità esistenti rispetto a nuovo codice ad-hoc.\n- Preserva le modifiche infrastrutturali Bun-first già presenti in questo repository:\n  - Il runtime è Bun (nessun entry point Node).\n  - Il package manager è Bun (nessun lockfile npm).\n  - Le API Node pesanti (`child_process`, `readline`) sono sostituite con equivalenti Bun.\n  - Le API Node leggere (`os.homedir`, `os.tmpdir`, `fs.mkdtempSync`, `path.*`) sono mantenute.\n  - Gli shebang CLI usano `bun` (non `node`, non `tsx`).\n  - I pacchetti usano direttamente i file sorgente (nessun step di build TypeScript).\n  - I workflow CI eseguono Bun per install/check/test.\n\n## 8) Rimuovere i vecchi layer di compatibilità\n\nA meno che non sia richiesto, rimuovi gli shim di compatibilità upstream.\n\n- Elimina le vecchie API che sono state sostituite.\n- Aggiorna tutti i punti di chiamata alla nuova API direttamente.\n- Non mantenere versioni `*_v2` o parallele.\n\n## 9) Aggiornare documentazione e riferimenti\n\n- Sostituisci i link al repository pi-mono dove appropriato.\n- Aggiorna gli esempi per usare Bun e gli scope di pacchetto corretti.\n- Assicurati che le istruzioni del README corrispondano ancora al comportamento attuale del repository.\n\n## 10) Validare il porting\n\nEsegui i controlli standard dopo le modifiche:\n\n- `bun check`\n\nSe il repository ha già controlli che falliscono non correlati alle tue modifiche, segnalalo.\nI test usano il runner di Bun (non Vitest), ma esegui `bun test` solo quando esplicitamente richiesto.\n\n## 11) Proteggere le funzionalità migliorate (lista trappole di regressione)\n\nSe hai già migliorato il comportamento localmente, tratta quei miglioramenti come **non negoziabili**. Prima del porting, annota i miglioramenti e aggiungi controlli espliciti affinché non vadano persi nel merge.\n\n- **Blocca il comportamento atteso**: aggiungi una breve nota \"prima/dopo\" per ogni miglioramento (input, output, valori predefiniti, casi limite). Questo previene rollback silenziosi.\n- **Mappa vecchie → nuove API**: se upstream ha rinominato concetti (hooks → extensions, custom tools → tools, ecc.), assicurati che ogni vecchio punto di ingresso sia ancora collegato. Un flag o un'esportazione mancante equivale a funzionalità persa.\n- **Verifica le esportazioni**: controlla gli `exports` del `package.json`, i tipi pubblici e i file barrel. I porting da upstream spesso dimenticano di ri-esportare le aggiunte locali.\n- **Copri i percorsi non-happy**: se hai corretto la gestione degli errori, i timeout o la logica di fallback, aggiungi un test o almeno una checklist manuale che eserciti quei percorsi.\n- **Controlla i valori predefiniti e l'ordine di merge della configurazione**: i miglioramenti spesso risiedono nei valori predefiniti. Conferma che i nuovi valori predefiniti non siano tornati indietro (es. nuova precedenza di configurazione, funzionalità disabilitate, liste di tool).\n- **Audita il comportamento env/shell**: se hai corretto l'esecuzione o il sandboxing, verifica che il nuovo percorso utilizzi ancora il tuo env sanitizzato e non reintroduca override di alias/funzioni.\n- **Riesegui esempi mirati**: mantieni un set minimale di esempi \"noti come funzionanti\" e eseguili dopo il porting (flag CLI, registrazione estensioni, esecuzione tool).\n\n## 12) Rilevare e gestire il codice rielaborato\n\nPrima di portare un file, controlla se upstream lo ha significativamente ristrutturato:\n\n```bash\n# Compare the file you're about to port against what you have locally\ngit diff HEAD upstream/main -- path/to/file.ts\n```\n\nSe il diff mostra che il file è stato **rielaborato** (non solo patchato):\n\n- Nuove astrazioni, concetti rinominati, moduli fusi, flusso di dati modificato\n\nAllora devi **leggere approfonditamente la nuova implementazione** prima del porting. Il merge alla cieca di codice rielaborato perde funzionalità perché:\n\nNota: la modalità interattiva è stata recentemente suddivisa in controller/utils/types. Quando fai backport di modifiche correlate, porta gli aggiornamenti nei singoli file che abbiamo creato e assicurati che il cablaggio di `interactive-mode.ts` rimanga sincronizzato.\n\n1. **I valori predefiniti cambiano silenziosamente** - Una nuova variabile `defaultFoo = [a, b]` potrebbe sostituire un vecchio `getAllFoo()` che restituiva `[a, b, c, d, e]`.\n\n2. **Le opzioni API vengono eliminate** - Quando i sistemi si fondono (es. `hooks` + `customTools` → `extensions`), le vecchie opzioni potrebbero non essere collegate alla nuova implementazione.\n\n3. **I percorsi di codice diventano obsoleti** - Un concetto rinominato (es. `hookMessage` → `custom`) necessita di aggiornamenti in ogni statement switch, type guard e handler — non solo nella definizione.\n\n4. **Contesto/capacità si riducono** - Le vecchie API potrebbero aver esposto `{ logger, typebox, pi }` che le nuove API hanno dimenticato di includere.\n\n### Processo di porting semantico\n\nQuando upstream ha rielaborato un modulo:\n\n1. **Leggi la vecchia implementazione** - Comprendi cosa faceva, quali opzioni accettava, cosa esponeva.\n\n2. **Leggi la nuova implementazione** - Comprendi le nuove astrazioni e come si mappano al vecchio comportamento.\n\n3. **Verifica la parità di funzionalità** - Per ogni capacità nel vecchio codice, conferma che il nuovo codice la preservi o la rimuova esplicitamente.\n\n4. **Cerca i residui** - Cerca vecchi nomi/concetti che potrebbero essere stati dimenticati negli statement switch, handler, componenti UI.\n\n5. **Testa i confini** - Flag CLI, opzioni SDK, gestori di eventi, valori predefiniti — è qui che si nascondono le regressioni.\n\n### Controlli rapidi\n\n```bash\n# Find all uses of an old concept that may need updating\nrg \"oldConceptName\" --type ts\n\n# Compare default values between versions\ngit show upstream/main:path/to/file.ts | rg \"default|DEFAULT\"\n\n# Check if all enum/union values have handlers\nrg \"case \\\"\" path/to/file.ts\n```\n\n## 13) Checklist rapida di audit\n\nUsa questa come passaggio finale prima di concludere:\n\n- [ ] Le estensioni degli import seguono la convenzione del pacchetto locale (nessuna rimozione indiscriminata di `.js`)\n- [ ] Nessuna API solo-Node nel codice nuovo/portato\n- [ ] Tutti gli scope dei pacchetti aggiornati\n- [ ] Gli script di `package.json` usano Bun\n- [ ] I prompt sono import di testo `.md` (nessuna stringa di prompt inline)\n- [ ] Nessun `console.*` in coding-agent (usa `logger`)\n- [ ] Gli asset vengono caricati tramite pattern di embed Bun (nessun script di copia)\n- [ ] Test o controlli eseguiti (o esplicitamente segnalati come bloccati)\n- [ ] Nessuna regressione di funzionalità (vedi sezioni 11-12)\n\n## 14) Formato del messaggio di commit\n\nQuando fai commit di un backport, segui il formato del repository `<type>(scope): <descrizione al passato>` e mantieni l'intervallo di commit nel titolo.\n\n```\nfix(coding-agent): backported pi-mono changes (<from>..<to>)\n\npackages/<package>:\n- <type>: <description>\n- <type>: <description> (#<issue> by @<contributor>)\n\npackages/<other-package>:\n- <type>: <description>\n```\n\n**Esempio:**\n\n```\nfix(coding-agent): backported pi-mono changes (9f3eef65f..52532c7c0)\n\npackages/ai:\n- fix: handle \"sensitive\" stop reason from Anthropic API\n- fix: normalize tool call IDs with special characters for Responses API\n- fix: add overflow detection for Bedrock, MiniMax, Kimi providers\n- fix: 429 status is rate limiting, not context overflow\n\npackages/tui:\n- fix: refactored autocomplete state tracking\n- fix: file autocomplete should not trigger on empty text\n- fix: configurable autocomplete max visible items\n- fix: improved table column width calculation with word-aware wrapping\n\npackages/coding-agent:\n- fix: preserve external config.yml edits on save (#1046 by @nicobailonMD)\n- fix: resolve macOS NFD and curly quote variants in file paths\n```\n\n**Regole:**\n\n- Raggruppa le modifiche per pacchetto\n- Usa i tipi di commit convenzionali (`fix`, `feat`, `refactor`, `perf`, `docs`)\n- Includi i numeri di issue/PR upstream e l'attribuzione del contributore per i contributi esterni\n- L'intervallo di commit nel titolo aiuta a tracciare i punti di sincronizzazione\n\n## 15) Divergenze Intenzionali\n\nIl nostro fork ha decisioni architetturali che differiscono da upstream. **Non portare questi pattern upstream:**\n\n### Architettura UI\n\n| Upstream                                    | Il Nostro Fork                                            | Motivo                                                                |\n| ------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------- |\n| Classe `FooterDataProvider`                 | `StatusLineComponent`                                     | Status line più semplice e integrata                                  |\n| `ctx.ui.setHeader()` / `ctx.ui.setFooter()` | Stub nelle modalità non-TUI                               | Implementato in TUI, no-op altrove                                    |\n| `ctx.ui.setEditorComponent()`               | Stub nelle modalità non-TUI                               | Implementato in TUI, no-op altrove                                    |\n| Oggetto opzioni `InteractiveModeOptions`    | Argomenti posizionali del costruttore (il tipo opzioni è ancora esportato) | Mantieni la firma del costruttore; aggiorna il tipo quando upstream aggiunge campi |\n\n### Denominazione dei Componenti\n\n| Upstream                     | Il Nostro Fork            |\n| ---------------------------- | ----------------------- |\n| `extension-input.ts`         | `hook-input.ts`         |\n| `extension-selector.ts`      | `hook-selector.ts`      |\n| `ExtensionInputComponent`    | `HookInputComponent`    |\n| `ExtensionSelectorComponent` | `HookSelectorComponent` |\n\n### Denominazione delle API\n\n| Upstream                                 | Il Nostro Fork                           | Note                                      |\n| ---------------------------------------- | ---------------------------------------- | ----------------------------------------- |\n| `sessionManager.appendSessionInfo(name)` | `sessionManager.setSessionName(name)`    | Usiamo `sessionName` ovunque              |\n| `sessionManager.getSessionName()`        | `sessionManager.getSessionName()`        | Uguale (abbiamo unificato per corrispondere all'RPC di upstream) |\n| `agent.sessionName` / `setSessionName()` | `agent.sessionName` / `setSessionName()` | Uguale                                    |\n\n### Consolidamento dei File\n\n| Upstream                                           | Il Nostro Fork                          | Motivo                                  |\n| -------------------------------------------------- | --------------------------------------- | --------------------------------------- |\n| `clipboard.ts` + `clipboard-image.ts` (file tool)  | Modulo clipboard `@f5-sales-demo/pi-natives` | Fuso nell'implementazione nativa N-API  |\n\n### Framework di Test\n\n| Upstream                  | Il Nostro Fork                |\n| ------------------------- | ----------------------------- |\n| `vitest` con `vi.mock()`  | `bun:test` con `vi` da bun   |\n| Asserzioni `node:test`    | Matcher `expect()`            |\n\n### Architettura dei Tool\n\n| Upstream                            | Il Nostro Fork                                                            | Note                                                      |\n| ----------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------- |\n| `createTool(cwd: string, options?)` | `createTools(session: ToolSession)` tramite registro `BUILTIN_TOOLS`  | Le factory dei tool accettano `ToolSession` e possono restituire `null` |\n| Interfacce `*Operations` per tool   | Le interfacce per tool rimangono (`FindOperations`, `GrepOperations`)   | Usate per override SSH/remoti                             |\n| `fs/promises` di Node.js ovunque    | `Bun.file()`/`Bun.write()` per i file; `node:fs/promises` per le directory | Preferisci le API Bun quando semplificano                 |\n\n### Storage dell'Autenticazione\n\n| Upstream                        | Il Nostro Fork                              | Note                                         |\n| ------------------------------- | ------------------------------------------- | -------------------------------------------- |\n| `proper-lockfile` + `auth.json` | `agent.db` (bun:sqlite)                     | Le credenziali sono memorizzate esclusivamente in `agent.db` |\n| Singola credenziale per provider | Multi-credenziale con selezione round-robin | Logica di affinità di sessione e backoff preservata |\n\n### Estensioni\n\n| Upstream                      | Il Nostro Fork                             |\n| ----------------------------- | ------------------------------------------ |\n| `jiti` per il caricamento TypeScript | `import()` nativo di Bun                   |\n| Campo manifest `pkg.pi`       | `pkg.xcsh ?? pkg.pi` (preferisci il nostro namespace) |\n\n### Salta Queste Funzionalità Upstream\n\nQuando porti, **salta** interamente questi file/funzionalità:\n\n- `footer-data-provider.ts` — usiamo StatusLineComponent\n- `clipboard-image.ts` — il clipboard è nel modulo N-API `@f5-sales-demo/pi-natives`\n- File di workflow GitHub — abbiamo la nostra CI\n- `models.generated.ts` — auto-generato, rigeneralo localmente (come models.json invece)\n\n### Funzionalità Aggiunte da Noi (Preservale)\n\nQueste esistono nel nostro fork ma non in upstream. **Non sovrascrivere mai:**\n\n- `StatusLineComponent` nella modalità interattiva\n- Autenticazione multi-credenziale con affinità di sessione\n- Sistema di discovery basato su capability (`defineCapability`, `registerProvider`, `loadCapability`, `skillCapability`, ecc.)\n- Integrazioni MCP/Exa/SSH\n- Writethrough LSP per format-on-save\n- Intercettazione Bash (`checkBashInterception`)\n- Suggerimenti fuzzy dei percorsi nel tool di lettura\n",
	"it/configuration/rpc.md": "---\ntitle: Riferimento al protocollo RPC\ndescription: >-\n  Riferimento al protocollo JSON-RPC per la comunicazione tra processi tra i\n  componenti xcsh.\nsidebar:\n  order: 5\n  label: Protocollo RPC\ni18n:\n  sourceHash: b4a3ddaf08ab\n  translator: machine\n---\n\n# Riferimento al protocollo RPC\n\nLa modalità RPC esegue l'agente di codifica come protocollo JSON delimitato da newline su stdio.\n\n- **stdin**: comandi (`RpcCommand`) e risposte UI dell'estensione\n- **stdout**: risposte ai comandi (`RpcResponse`), eventi di sessione/agente, richieste UI dell'estensione\n\nImplementazione principale:\n\n- `src/modes/rpc/rpc-mode.ts`\n- `src/modes/rpc/rpc-types.ts`\n- `src/session/agent-session.ts`\n- `packages/agent/src/agent.ts`\n- `packages/agent/src/agent-loop.ts`\n\n## Avvio\n\n```bash\nxcsh --mode rpc [regular CLI options]\n```\n\nNote sul comportamento:\n\n- Gli argomenti CLI `@file` vengono rifiutati in modalità RPC.\n- La modalità RPC disabilita per impostazione predefinita la generazione automatica del titolo di sessione per evitare una chiamata aggiuntiva al modello.\n- La modalità RPC reimposta le impostazioni `todo.*`, `task.*` e `async.*` che alterano il flusso di lavoro ai valori predefiniti incorporati, invece di ereditare le sostituzioni dell'utente.\n- Il processo legge stdin come JSONL (`readJsonl(Bun.stdin.stream())`).\n- Quando stdin si chiude, il processo termina con codice `0`.\n- Le risposte/eventi vengono scritti come un oggetto JSON per riga.\n\n## Trasporto e framing\n\nOgni frame è un singolo oggetto JSON seguito da `\\n`.\n\nNon esiste alcun involucro oltre alla forma dell'oggetto stesso.\n\n### Categorie di frame in uscita (stdout)\n\n1. `RpcResponse` (`{ type: \"response\", ... }`)\n2. Oggetti `AgentSessionEvent` (`agent_start`, `message_update`, ecc.)\n3. `RpcExtensionUIRequest` (`{ type: \"extension_ui_request\", ... }`)\n4. Errori dell'estensione (`{ type: \"extension_error\", extensionPath, event, error }`)\n\n### Categorie di frame in entrata (stdin)\n\n1. `RpcCommand`\n2. `RpcExtensionUIResponse` (`{ type: \"extension_ui_response\", ... }`)\n\n## Correlazione richiesta/risposta\n\nTutti i comandi accettano un `id?: string` opzionale.\n\n- Se fornito, le normali risposte ai comandi ripetono lo stesso `id`.\n- `RpcClient` si basa su questo per la risoluzione delle richieste in attesa.\n\nComportamenti limite importanti dal runtime:\n\n- Le risposte a comandi sconosciuti vengono emesse con `id: undefined` (anche se la richiesta aveva un `id`).\n- Le eccezioni di parsing/gestore nel ciclo di input emettono `command: \"parse\"` con `id: undefined`.\n- `prompt` e `abort_and_prompt` restituiscono un successo immediato, ma possono emettere in seguito una risposta di errore con lo **stesso** id se la pianificazione asincrona del prompt fallisce.\n\n## Schema dei comandi (canonico)\n\n`RpcCommand` è definito in `src/modes/rpc/rpc-types.ts`:\n\n### Prompting\n\n- `{ id?, type: \"prompt\", message: string, images?: ImageContent[], streamingBehavior?: \"steer\" | \"followUp\" }`\n- `{ id?, type: \"steer\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"follow_up\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"abort\" }`\n- `{ id?, type: \"abort_and_prompt\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"new_session\", parentSession?: string }`\n\n### Stato\n\n- `{ id?, type: \"get_state\" }`\n- `{ id?, type: \"set_todos\", phases: TodoPhase[] }`\n- `{ id?, type: \"set_host_tools\", tools: RpcHostToolDefinition[] }`\n\n### Modello\n\n- `{ id?, type: \"set_model\", provider: string, modelId: string }`\n- `{ id?, type: \"cycle_model\" }`\n- `{ id?, type: \"get_available_models\" }`\n\n### Pensiero\n\n- `{ id?, type: \"set_thinking_level\", level: ThinkingLevel }`\n- `{ id?, type: \"cycle_thinking_level\" }`\n\n### Modalità coda\n\n- `{ id?, type: \"set_steering_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_follow_up_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_interrupt_mode\", mode: \"immediate\" | \"wait\" }`\n\n### Compattazione\n\n- `{ id?, type: \"compact\", customInstructions?: string }`\n- `{ id?, type: \"set_auto_compaction\", enabled: boolean }`\n\n### Ripetizione\n\n- `{ id?, type: \"set_auto_retry\", enabled: boolean }`\n- `{ id?, type: \"abort_retry\" }`\n\n### Bash\n\n- `{ id?, type: \"bash\", command: string }`\n- `{ id?, type: \"abort_bash\" }`\n\n### Sessione\n\n- `{ id?, type: \"get_session_stats\" }`\n- `{ id?, type: \"export_html\", outputPath?: string }`\n- `{ id?, type: \"switch_session\", sessionPath: string }`\n- `{ id?, type: \"branch\", entryId: string }`\n- `{ id?, type: \"get_branch_messages\" }`\n- `{ id?, type: \"get_last_assistant_text\" }`\n- `{ id?, type: \"set_session_name\", name: string }`\n\n### Messaggi\n\n- `{ id?, type: \"get_messages\" }`\n\n## Schema delle risposte\n\nTutti i risultati dei comandi utilizzano `RpcResponse`:\n\n- Successo: `{ id?, type: \"response\", command: <command>, success: true, data?: ... }`\n- Fallimento: `{ id?, type: \"response\", command: string, success: false, error: string }`\n\nI payload dei dati sono specifici per ogni comando e definiti in `rpc-types.ts`.\n\n### Payload di `get_state`\n\n```json\n{\n  \"model\": { \"provider\": \"...\", \"id\": \"...\" },\n  \"thinkingLevel\": \"off|minimal|low|medium|high|xhigh\",\n  \"isStreaming\": false,\n  \"isCompacting\": false,\n  \"steeringMode\": \"all|one-at-a-time\",\n  \"followUpMode\": \"all|one-at-a-time\",\n  \"interruptMode\": \"immediate|wait\",\n  \"sessionFile\": \"...\",\n  \"sessionId\": \"...\",\n  \"sessionName\": \"...\",\n  \"autoCompactionEnabled\": true,\n  \"messageCount\": 0,\n  \"queuedMessageCount\": 0,\n  \"todoPhases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Todos\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the tool surface\",\n          \"status\": \"in_progress\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n### Payload di `set_todos`\n\nSostituisce lo stato todo in memoria per la sessione corrente e restituisce l'elenco delle fasi normalizzate:\n\n```json\n{\n  \"id\": \"req_2\",\n  \"type\": \"set_todos\",\n  \"phases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Evaluation\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the read tool surface\",\n          \"status\": \"in_progress\"\n        },\n        {\n          \"id\": \"task-2\",\n          \"content\": \"Exercise edit operations\",\n          \"status\": \"pending\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nQuesto è utile per gli host che desiderano pre-caricare un piano prima del primo prompt.\n\n### Payload di `set_host_tools`\n\nSostituisce l'insieme corrente di strumenti di proprietà dell'host che il server RPC può richiamare\ntramite stdio:\n\n```json\n{\n  \"id\": \"req_3\",\n  \"type\": \"set_host_tools\",\n  \"tools\": [\n    {\n      \"name\": \"echo_host\",\n      \"label\": \"Echo Host\",\n      \"description\": \"Echo a value from the embedding host\",\n      \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"message\": { \"type\": \"string\" }\n        },\n        \"required\": [\"message\"],\n        \"additionalProperties\": false\n      }\n    }\n  ]\n}\n```\n\nIl payload della risposta è:\n\n```json\n{\n  \"toolNames\": [\"echo_host\"]\n}\n```\n\nQuesti strumenti vengono aggiunti al registro degli strumenti della sessione attiva prima della successiva\nchiamata al modello. Inviare nuovamente `set_host_tools` sostituisce il precedente insieme di proprietà dell'host.\n\n## Schema del flusso di eventi\n\nLa modalità RPC inoltra gli oggetti `AgentSessionEvent` da `AgentSession.subscribe(...)`.\n\nTipi di evento comuni:\n\n- `agent_start`, `agent_end`\n- `turn_start`, `turn_end`\n- `message_start`, `message_update`, `message_end`\n- `tool_execution_start`, `tool_execution_update`, `tool_execution_end`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n- `todo_auto_clear`\n\nGli errori del runner dell'estensione vengono emessi separatamente come:\n\n```json\n{ \"type\": \"extension_error\", \"extensionPath\": \"...\", \"event\": \"...\", \"error\": \"...\" }\n```\n\n`message_update` include delta di streaming in `assistantMessageEvent` (delta di testo/pensiero/toolcall).\n\n## Concorrenza e ordinamento di prompt/coda\n\nQuesto è il comportamento operativo più importante.\n\n### Ack immediato vs completamento\n\n`prompt` e `abort_and_prompt` vengono **riconosciuti immediatamente**:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n```\n\nCiò significa:\n\n- accettazione del comando != completamento dell'esecuzione\n- il completamento finale viene osservato tramite `agent_end`\n\n### Durante lo streaming\n\n`AgentSession.prompt()` richiede `streamingBehavior` durante lo streaming attivo:\n\n- `\"steer\"` => messaggio di steering in coda (percorso di interruzione)\n- `\"followUp\"` => messaggio di follow-up in coda (percorso post-turno)\n\nSe omesso durante lo streaming, il prompt fallisce.\n\n### Valori predefiniti della coda\n\nDallo schema delle impostazioni dell'agente di codifica (`packages/coding-agent/src/config/settings-schema.ts`):\n\n- `steeringMode`: `\"one-at-a-time\"`\n- `followUpMode`: `\"one-at-a-time\"`\n- `interruptMode`: `\"wait\"`\n\n### Semantica delle modalità\n\n- `set_steering_mode` / `set_follow_up_mode`\n  - `\"one-at-a-time\"`: rimuove dalla coda un messaggio per turno\n  - `\"all\"`: rimuove l'intera coda in una volta\n- `set_interrupt_mode`\n  - `\"immediate\"`: l'esecuzione degli strumenti controlla lo steering tra le chiamate agli strumenti; lo steering in attesa può interrompere le chiamate agli strumenti rimanenti nel turno\n  - `\"wait\"`: rimanda lo steering fino al completamento del turno\n\n## Sotto-protocollo UI dell'estensione\n\nLe estensioni in modalità RPC utilizzano frame UI richiesta/risposta.\n\n### Richiesta in uscita\n\nMetodi di `RpcExtensionUIRequest` (`type: \"extension_ui_request\"`):\n\n- `select`, `confirm`, `input`, `editor`\n- `notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text`\n\nNota sul runtime:\n\n- La generazione automatica del titolo di sessione è disabilitata in modalità RPC, e le richieste UI\n  `setTitle` vengono anch'esse soppresse per impostazione predefinita perché la maggior parte degli host non dispone di\n  una superficie di titolo terminale significativa. Impostare `PI_RPC_EMIT_TITLE=1` per riattivare solo l'evento UI.\n\nEsempio:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"123\", \"method\": \"confirm\", \"title\": \"Confirm\", \"message\": \"Continue?\", \"timeout\": 30000 }\n```\n\n### Risposta in entrata\n\n`RpcExtensionUIResponse` (`type: \"extension_ui_response\"`):\n\n- `{ type: \"extension_ui_response\", id: string, value: string }`\n- `{ type: \"extension_ui_response\", id: string, confirmed: boolean }`\n- `{ type: \"extension_ui_response\", id: string, cancelled: true }`\n\nSe un dialogo ha un timeout, la modalità RPC risolve con un valore predefinito quando scatta il timeout/abort.\n\n## Sotto-protocollo degli strumenti host\n\nGli host RPC possono esporre strumenti personalizzati all'agente inviando `set_host_tools`, quindi\nservendo le richieste di esecuzione sullo stesso trasporto.\n\n### Richiesta in uscita\n\nQuando l'agente vuole che l'host esegua uno di questi strumenti, la modalità RPC emette:\n\n```json\n{\n  \"type\": \"host_tool_call\",\n  \"id\": \"host_1\",\n  \"toolCallId\": \"toolu_123\",\n  \"toolName\": \"echo_host\",\n  \"arguments\": { \"message\": \"hello\" }\n}\n```\n\nSe l'esecuzione dello strumento viene successivamente interrotta, la modalità RPC emette:\n\n```json\n{\n  \"type\": \"host_tool_cancel\",\n  \"id\": \"host_cancel_1\",\n  \"targetId\": \"host_1\"\n}\n```\n\n### Aggiornamenti in entrata e completamento\n\nGli host possono facoltativamente inviare in streaming il progresso:\n\n```json\n{\n  \"type\": \"host_tool_update\",\n  \"id\": \"host_1\",\n  \"partialResult\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"working\" }]\n  }\n}\n```\n\nIl completamento utilizza:\n\n```json\n{\n  \"type\": \"host_tool_result\",\n  \"id\": \"host_1\",\n  \"result\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"done\" }]\n  }\n}\n```\n\nImpostare `isError: true` su `host_tool_result` per presentare il contenuto restituito come\nerrore dello strumento.\n\n## Modello degli errori e recuperabilità\n\n### Fallimenti a livello di comando\n\nI fallimenti hanno `success: false` con `error` stringa.\n\n```json\n{ \"id\": \"req_2\", \"type\": \"response\", \"command\": \"set_model\", \"success\": false, \"error\": \"Model not found: provider/model\" }\n```\n\n### Aspettative di recuperabilità\n\n- La maggior parte dei fallimenti dei comandi è recuperabile; il processo rimane attivo.\n- Le eccezioni JSONL malformato / del ciclo di parsing emettono una risposta di errore `parse` e continuano a leggere le righe successive.\n- Un `set_session_name` vuoto viene rifiutato (`Session name cannot be empty`).\n- Le risposte UI dell'estensione con `id` sconosciuto vengono ignorate.\n- Le condizioni di terminazione del processo sono la chiusura di stdin o uno shutdown esplicito attivato dall'estensione.\n\n## Flussi di comandi compatti\n\n### 1) Prompt e streaming\n\nstdin:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"prompt\", \"message\": \"Summarize this repo\" }\n```\n\nSequenza stdout (tipica):\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n{ \"type\": \"agent_start\" }\n{ \"type\": \"message_update\", \"assistantMessageEvent\": { \"type\": \"text_delta\", \"delta\": \"...\" }, \"message\": { \"role\": \"assistant\", \"content\": [] } }\n{ \"type\": \"agent_end\", \"messages\": [] }\n```\n\n### 2) Prompt durante lo streaming con politica di coda esplicita\n\nstdin:\n\n```json\n{ \"id\": \"req_2\", \"type\": \"prompt\", \"message\": \"Also include risks\", \"streamingBehavior\": \"followUp\" }\n```\n\n### 3) Ispezione e regolazione del comportamento della coda\n\nstdin:\n\n```json\n{ \"id\": \"q1\", \"type\": \"get_state\" }\n{ \"id\": \"q2\", \"type\": \"set_steering_mode\", \"mode\": \"all\" }\n{ \"id\": \"q3\", \"type\": \"set_interrupt_mode\", \"mode\": \"wait\" }\n```\n\n### 4) Round trip UI dell'estensione\n\nstdout:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"ui_7\", \"method\": \"input\", \"title\": \"Branch name\", \"placeholder\": \"feature/...\" }\n```\n\nstdin:\n\n```json\n{ \"type\": \"extension_ui_response\", \"id\": \"ui_7\", \"value\": \"feature/rpc-host\" }\n```\n\n## Note sull'helper `RpcClient`\n\n`src/modes/rpc/rpc-client.ts` è un wrapper di comodità, non la definizione del protocollo.\n\nCaratteristiche attuali dell'helper:\n\n- Avvia `bun <cliPath> --mode rpc`\n- Correla le risposte tramite id `req_<n>` generati\n- Invia solo i tipi `AgentEvent` riconosciuti ai listener\n- Supporta strumenti personalizzati di proprietà dell'host tramite `setCustomTools()` e la gestione automatica di `host_tool_call` / `host_tool_cancel`\n- **Non** espone metodi helper per ogni comando del protocollo (ad esempio, `set_interrupt_mode` e `set_session_name` sono nei tipi del protocollo ma non sono racchiusi come metodi dedicati)\n\nUtilizzare frame di protocollo raw se si necessita di una copertura completa della superficie.\n",
	"it/configuration/sdk.md": "---\ntitle: SDK\ndescription: >-\n  SDK per la creazione di agenti personalizzati e integrazioni sul runtime\n  dell'agente di codifica xcsh.\nsidebar:\n  order: 6\n  label: SDK\ni18n:\n  sourceHash: 80f3a4374241\n  translator: machine\n---\n\n# SDK\n\nL'SDK è la superficie di integrazione in-process per `@f5-sales-demo/xcsh`.\nUtilizzarlo quando si desidera accesso diretto allo stato dell'agente, allo streaming di eventi, al collegamento degli strumenti e al controllo della sessione dal proprio processo Bun/Node.\n\nSe è necessario l'isolamento cross-linguaggio/processo, utilizzare invece la modalità RPC.\n\n## Installazione\n\n```bash\nbun add @f5-sales-demo/xcsh\n```\n\n## Punti di ingresso\n\n`@f5-sales-demo/xcsh` esporta le API dell'SDK dalla radice del pacchetto (e anche tramite `@f5-sales-demo/xcsh/sdk`).\n\nEsportazioni principali per gli embedder:\n\n- `createAgentSession`\n- `SessionManager`\n- `Settings`\n- `AuthStorage`\n- `ModelRegistry`\n- `discoverAuthStorage`\n- Helper di discovery (`discoverExtensions`, `discoverSkills`, `discoverContextFiles`, `discoverPromptTemplates`, `discoverSlashCommands`, `discoverCustomTSCommands`, `discoverMCPServers`)\n- Superficie factory degli strumenti (`createTools`, `BUILTIN_TOOLS`, classi di strumenti)\n\n## Avvio rapido (impostazioni predefinite di auto-discovery)\n\n```ts\nimport { createAgentSession } from \"@f5-sales-demo/xcsh\";\n\nconst { session, modelFallbackMessage } = await createAgentSession();\n\nif (modelFallbackMessage) {\n process.stderr.write(`${modelFallbackMessage}\\n`);\n}\n\nconst unsubscribe = session.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Summarize this repository in 3 bullets.\");\nunsubscribe();\nawait session.dispose();\n```\n\n## Cosa scopre `createAgentSession()` per impostazione predefinita\n\n`createAgentSession()` segue il principio \"fornire per sovrascrivere, omettere per scoprire\".\n\nSe omesso, risolve:\n\n- `cwd`: `getProjectDir()`\n- `agentDir`: `~/.xcsh/agent` (tramite `getAgentDir()`)\n- `authStorage`: `discoverAuthStorage(agentDir)`\n- `modelRegistry`: `new ModelRegistry(authStorage)` + `await refresh()`\n- `settings`: `await Settings.init({ cwd, agentDir })`\n- `sessionManager`: `SessionManager.create(cwd)` (basato su file)\n- skills/file di contesto/template di prompt/comandi slash/estensioni/comandi TS personalizzati\n- strumenti integrati tramite `createTools(...)`\n- strumenti MCP (abilitati per impostazione predefinita)\n- integrazione LSP (abilitata per impostazione predefinita)\n\n### Input obbligatori vs opzionali\n\nIn genere è necessario fornire solo ciò che si desidera controllare:\n\n- **Da fornire obbligatoriamente**: niente per una sessione minimale\n- **Di solito forniti esplicitamente** negli embedder:\n    - `sessionManager` (se si necessita di in-memory o di una posizione personalizzata)\n    - `authStorage` + `modelRegistry` (se si gestisce il ciclo di vita delle credenziali/modelli)\n    - `model` o `modelPattern` (se la selezione deterministica del modello è importante)\n    - `settings` (se si necessita di una configurazione isolata/di test)\n\n## Comportamento del gestore di sessione (persistente vs in-memory)\n\n`AgentSession` utilizza sempre un `SessionManager`; il comportamento dipende dalla factory utilizzata.\n\n### Basato su file (predefinito)\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.create(process.cwd()),\n});\n\nconsole.log(session.sessionFile); // percorso assoluto .jsonl\n```\n\n- Persiste conversazioni/messaggi/delta di stato nei file di sessione.\n- Supporta i flussi di lavoro di ripristino/apertura/elenco/fork.\n- `session.sessionFile` è definito.\n\n### In-memory\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.inMemory(),\n});\n\nconsole.log(session.sessionFile); // undefined\n```\n\n- Nessuna persistenza nel filesystem.\n- Utile per test, worker effimeri, agenti con scope di richiesta.\n- I metodi di sessione funzionano ancora, ma i comportamenti specifici della persistenza (percorsi di ripristino/fork su file) sono naturalmente limitati.\n\n### Helper di ripristino/apertura/elenco\n\n```ts\nimport { SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst recent = await SessionManager.continueRecent(process.cwd());\nconst listed = await SessionManager.list(process.cwd());\nconst opened = listed[0] ? await SessionManager.open(listed[0].path) : null;\n```\n\n## Collegamento di modello e autenticazione\n\n`createAgentSession()` utilizza `ModelRegistry` + `AuthStorage` per la selezione del modello e la risoluzione della chiave API.\n\n### Collegamento esplicito\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst available = modelRegistry.getAvailable();\nif (available.length === 0) throw new Error(\"No authenticated models available\");\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n model: available[0],\n thinkingLevel: \"medium\",\n sessionManager: SessionManager.inMemory(),\n});\n```\n\n### Ordine di selezione quando `model` è omesso\n\nQuando non viene fornito un `model`/`modelPattern` esplicito:\n\n1. ripristino del modello dalla sessione esistente (se ripristinabile + chiave disponibile)\n2. ruolo del modello predefinito nelle impostazioni (`default`)\n3. primo modello disponibile con autenticazione valida\n\nSe il ripristino fallisce, `modelFallbackMessage` spiega il fallback.\n\n### Priorità di autenticazione\n\n`AuthStorage.getApiKey(...)` risolve in questo ordine:\n\n1. override di runtime (`setRuntimeApiKey`)\n2. credenziali memorizzate in `agent.db`\n3. variabili d'ambiente del provider\n4. fallback del resolver custom-provider (se configurato)\n\n## Modello di sottoscrizione agli eventi\n\nSottoscrivere con `session.subscribe(listener)`; restituisce una funzione di annullamento della sottoscrizione.\n\n```ts\nconst unsubscribe = session.subscribe(event => {\n switch (event.type) {\n  case \"agent_start\":\n  case \"turn_start\":\n  case \"tool_execution_start\":\n   break;\n  case \"message_update\":\n   if (event.assistantMessageEvent.type === \"text_delta\") {\n    process.stdout.write(event.assistantMessageEvent.delta);\n   }\n   break;\n }\n});\n```\n\n`AgentSessionEvent` include gli `AgentEvent` principali più gli eventi a livello di sessione:\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n## Ciclo di vita del prompt\n\n`session.prompt(text, options?)` è il punto di ingresso principale.\n\nComportamento:\n\n1. espansione opzionale di comandi/template (comandi `/`, comandi personalizzati, comandi slash da file, template di prompt)\n2. se attualmente in streaming:\n    - richiede `streamingBehavior: \"steer\" | \"followUp\"`\n    - mette in coda invece di scartare il lavoro\n3. se inattivo:\n    - valida il modello + la chiave API\n    - aggiunge il messaggio utente\n    - avvia il turno dell'agente\n\nAPI correlate:\n\n- `sendUserMessage(content, { deliverAs? })`\n- `steer(text, images?)`\n- `followUp(text, images?)`\n- `sendCustomMessage({ customType, content, ... }, { deliverAs?, triggerTurn? })`\n- `abort()`\n\n## Strumenti e integrazione delle estensioni\n\n### Integrati e filtraggio\n\n- Gli integrati provengono da `createTools(...)` e `BUILTIN_TOOLS`.\n- `toolNames` funge da allowlist per gli integrati.\n- Gli strumenti `customTools` e quelli registrati dalle estensioni sono comunque inclusi.\n- Gli strumenti nascosti (ad esempio `submit_result`) sono opt-in a meno che non siano richiesti dalle opzioni.\n\n```ts\nconst { session } = await createAgentSession({\n toolNames: [\"read\", \"grep\", \"find\", \"write\"],\n requireSubmitResultTool: true,\n});\n```\n\n### Estensioni\n\n- `extensions`: `ExtensionFactory[]` inline\n- `additionalExtensionPaths`: carica file di estensione aggiuntivi\n- `disableExtensionDiscovery`: disabilita la scansione automatica delle estensioni\n- `preloadedExtensions`: riutilizza un set di estensioni già caricato\n\n### Modifiche al set di strumenti a runtime\n\n`AgentSession` supporta aggiornamenti di attivazione a runtime:\n\n- `getActiveToolNames()`\n- `getAllToolNames()`\n- `setActiveToolsByName(names)`\n- `refreshMCPTools(mcpTools)`\n\nIl prompt di sistema viene ricostruito per riflettere le modifiche agli strumenti attivi.\n\n## Helper di discovery\n\nUtilizzarli quando si desidera un controllo parziale senza ricreare la logica di discovery interna:\n\n- `discoverAuthStorage(agentDir?)`\n- `discoverExtensions(cwd?)`\n- `discoverSkills(cwd?, _agentDir?, settings?)`\n- `discoverContextFiles(cwd?, _agentDir?)`\n- `discoverPromptTemplates(cwd?, agentDir?)`\n- `discoverSlashCommands(cwd?)`\n- `discoverCustomTSCommands(cwd?, agentDir?)`\n- `discoverMCPServers(cwd?)`\n- `buildSystemPrompt(options?)`\n\n## Opzioni orientate ai subagent\n\nPer i consumatori dell'SDK che costruiscono orchestratori (simile al flusso dell'esecutore di task):\n\n- `outputSchema`: trasmette l'aspettativa di output strutturato nel contesto dello strumento\n- `requireSubmitResultTool`: forza l'inclusione dello strumento `submit_result`\n- `taskDepth`: contesto di profondità di ricorsione per sessioni di task annidate\n- `parentTaskPrefix`: prefisso di denominazione degli artefatti per gli output di task annidate\n\nQuesti sono opzionali per il normale embedding a singolo agente.\n\n## Valore di ritorno di `createAgentSession()`\n\n```ts\ntype CreateAgentSessionResult = {\n session: AgentSession;\n extensionsResult: LoadExtensionsResult;\n setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;\n mcpManager?: MCPManager;\n modelFallbackMessage?: string;\n lspServers?: Array<{ name: string; status: \"ready\" | \"error\"; fileTypes: string[]; error?: string }>;\n};\n```\n\nUtilizzare `setToolUIContext(...)` solo se il proprio embedder fornisce capacità UI che gli strumenti/le estensioni devono richiamare.\n\n## Esempio minimale di embed controllato\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n Settings,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst settings = Settings.isolated({\n \"compaction.enabled\": true,\n \"retry.enabled\": true,\n});\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n settings,\n sessionManager: SessionManager.inMemory(),\n toolNames: [\"read\", \"grep\", \"find\", \"edit\", \"write\"],\n enableMCP: false,\n enableLsp: true,\n});\n\nsession.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Find all TODO comments in this repo and propose fixes.\");\nawait session.dispose();\n```\n",
	"it/configuration/secrets.md": "---\ntitle: Offuscamento dei segreti\ndescription: >-\n  Pipeline di offuscamento dei segreti che oscura i valori sensibili dai log di\n  sessione e dagli output.\nsidebar:\n  order: 3\n  label: Segreti\ni18n:\n  sourceHash: 1d9dc101c614\n  translator: machine\n---\n\n# Offuscamento dei segreti\n\nImpedisce che valori sensibili (chiavi API, token, password) vengano inviati ai provider LLM. Quando abilitato, i segreti vengono sostituiti con segnaposto deterministici prima di lasciare il processo, e ripristinati negli argomenti delle chiamate agli strumenti restituiti dal modello.\n\n## Abilitazione\n\nAbilitato per impostazione predefinita. Attivare/disattivare tramite l'interfaccia `/settings` o direttamente in `config.yml`:\n\n```yaml\nsecrets:\n  enabled: false\n```\n\n## Come funziona\n\n1. All'avvio della sessione, i segreti vengono raccolti da due fonti:\n   - **Variabili d'ambiente** che corrispondono a pattern comuni di segreti (`*_KEY`, `*_SECRET`, `*_TOKEN`, `*_PASSWORD`, ecc.) con valori >= 8 caratteri\n   - **File `secrets.yml`** (vedi sotto)\n\n2. I messaggi in uscita verso l'LLM hanno tutti i valori segreti sostituiti con segnaposto come `<<$env:S0>>`, `<<$env:S1>>`, ecc.\n\n3. Gli argomenti delle chiamate agli strumenti restituiti dal modello vengono analizzati in profondità e i segnaposto vengono ripristinati ai valori originali prima dell'esecuzione.\n\nDue modalità controllano il comportamento di ciascun segreto:\n\n| Modalità | Comportamento | Reversibile |\n|---|---|---|\n| `obfuscate` (predefinita) | Sostituito con segnaposto indicizzato `<<$env:SN>>` | Sì (de-offuscato negli argomenti degli strumenti) |\n| `replace` | Sostituito con una stringa deterministica della stessa lunghezza | No (unidirezionale) |\n\n## secrets.yml\n\nDefinisce voci di segreti personalizzate in YAML. Vengono controllate due posizioni:\n\n| Livello | Percorso | Scopo |\n|---|---|---|\n| Globale | `~/.xcsh/agent/secrets.yml` | Segreti per tutti i progetti |\n| Progetto | `<cwd>/.xcsh/secrets.yml` | Segreti specifici del progetto |\n\nLe voci del progetto sovrascrivono le voci globali con `content` corrispondente.\n\n### Schema\n\nOgni voce nell'array ha i seguenti campi:\n\n| Campo | Tipo | Obbligatorio | Descrizione |\n|---|---|---|---|\n| `type` | `\"plain\"` o `\"regex\"` | Sì | Strategia di corrispondenza |\n| `content` | stringa | Sì | Il valore segreto (plain) o il pattern regex (regex) |\n| `mode` | `\"obfuscate\"` o `\"replace\"` | No | Predefinito: `\"obfuscate\"` |\n| `replacement` | stringa | No | Sostituzione personalizzata (solo modalità replace) |\n| `flags` | stringa | No | Flag regex (solo tipo regex) |\n\n### Esempi\n\n#### Segreti in chiaro\n\n```yaml\n# Offusca una chiave API specifica (modalità predefinita)\n- type: plain\n  content: sk-proj-abc123def456\n\n# Sostituisce una password del database con una stringa fissa\n- type: plain\n  content: hunter2\n  mode: replace\n  replacement: \"********\"\n```\n\n#### Segreti regex\n\n```yaml\n# Offusca qualsiasi chiave in stile AWS\n- type: regex\n  content: \"AKIA[0-9A-Z]{16}\"\n\n# Corrispondenza senza distinzione tra maiuscole e minuscole con flag espliciti\n- type: regex\n  content: \"api[_-]?key\\\\s*=\\\\s*\\\\w+\"\n  flags: \"i\"\n\n# Sintassi letterale regex (pattern e flag in un'unica stringa)\n- type: regex\n  content: \"/bearer\\\\s+[a-zA-Z0-9._~+\\\\/=-]+/i\"\n```\n\nLe voci regex eseguono sempre la scansione globalmente (il flag `g` viene applicato automaticamente). La sintassi letterale regex `/pattern/flags` è supportata come alternativa ai campi separati `content` + `flags`. Le barre oblique con escape all'interno del pattern (`\\\\/`) vengono gestite correttamente.\n\n#### Modalità replace con regex\n\n```yaml\n# Sostituzione unidirezionale delle stringhe di connessione (non reversibile)\n- type: regex\n  content: \"postgres://[^\\\\s]+\"\n  mode: replace\n  replacement: \"postgres://***\"\n```\n\n## Interazione con il rilevamento delle variabili d'ambiente\n\nLe variabili d'ambiente vengono sempre raccolte per prime. Le voci definite nei file vengono aggiunte successivamente, in modo che le voci dei file possano coprire segreti che non si trovano nelle variabili d'ambiente (file di configurazione, valori hardcoded, ecc.). Se lo stesso valore compare in entrambe, la modalità della voce del file ha la precedenza.\n\n## File chiave\n\n- `src/secrets/index.ts` -- caricamento, unione, raccolta delle variabili d'ambiente\n- `src/secrets/obfuscator.ts` -- classe `SecretObfuscator`, generazione dei segnaposto, offuscamento dei messaggi\n- `src/secrets/regex.ts` -- analisi e compilazione dei letterali regex\n- `src/config/settings-schema.ts` -- definizione dell'impostazione `secrets.enabled`\n",
	"it/extensions/extension-loading.md": "---\ntitle: Caricamento delle estensioni (Moduli TypeScript/JavaScript)\ndescription: >-\n  Pipeline di caricamento dei moduli TypeScript e JavaScript per le estensioni\n  con risoluzione, validazione e caching.\nsidebar:\n  order: 2\n  label: Caricamento delle estensioni\ni18n:\n  sourceHash: a8cea231c660\n  translator: machine\n---\n\n# Caricamento delle estensioni (Moduli TypeScript/JavaScript)\n\nQuesto documento tratta come l'agente di codifica individua e carica i **moduli di estensione** (`.ts`/`.js`) all'avvio.\n\n**Non** copre le estensioni con manifesto `gemini-extension.json` (documentate separatamente).\n\n## Cosa fa questo sottosistema\n\nIl caricamento delle estensioni costruisce un elenco di file entry dei moduli, importa ciascun modulo con Bun, esegue la sua factory e restituisce:\n\n- le definizioni delle estensioni caricate\n- gli errori di caricamento per ciascun percorso (senza interrompere l'intero caricamento)\n- un oggetto runtime di estensione condiviso, utilizzato successivamente da `ExtensionRunner`\n\n## File di implementazione principali\n\n- `src/extensibility/extensions/loader.ts` — scoperta dei percorsi + importazione/esecuzione\n- `src/extensibility/extensions/index.ts` — esportazioni pubbliche\n- `src/extensibility/extensions/runner.ts` — esecuzione runtime/eventi dopo il caricamento\n- `src/discovery/builtin.ts` — provider di auto-scoperta nativo per i moduli di estensione\n- `src/config/settings.ts` — carica le impostazioni unite `extensions` / `disabledExtensions`\n\n---\n\n## Input per il caricamento delle estensioni\n\n### 1) Moduli di estensione nativi scoperti automaticamente\n\n`discoverAndLoadExtensions()` chiede prima ai provider di scoperta gli elementi con capability `extension-module`, poi mantiene solo gli elementi del provider `native`.\n\nPosizioni native effettive:\n\n- Progetto: `<cwd>/.xcsh/extensions`\n- Utente: `~/.xcsh/agent/extensions`\n\nLe radici dei percorsi provengono dal provider nativo (`SOURCE_PATHS.native`).\n\nNote:\n\n- L'auto-scoperta nativa è attualmente basata su `.xcsh`.\n- Il legacy `.pi` è ancora accettato nelle chiavi del manifesto `package.json` (`pi.extensions`), ma non come radice nativa in questo contesto.\n\n### 2) Percorsi configurati esplicitamente\n\nDopo l'auto-scoperta, i percorsi configurati vengono aggiunti e risolti.\n\nFonti dei percorsi configurati nel percorso di avvio della sessione principale (`sdk.ts`):\n\n1. Percorsi forniti via CLI (`--extension/-e`, e `--hook` è anch'esso trattato come percorso di estensione)\n2. Array `extensions` nelle impostazioni (impostazioni globali + progetto unite)\n\nFile delle impostazioni globali:\n\n- `~/.xcsh/agent/config.yml` (o directory agente personalizzata tramite `PI_CODING_AGENT_DIR`)\n\nFile delle impostazioni di progetto:\n\n- `<cwd>/.xcsh/settings.json`\n\nEsempi:\n\n```yaml\n# ~/.xcsh/agent/config.yml\nextensions:\n  - ~/my-exts/safety.ts\n  - ./local/ext-pack\n```\n\n```json\n{\n  \"extensions\": [\"./.xcsh/extensions/my-extra\"]\n}\n```\n\n---\n\n## Controlli di abilitazione/disabilitazione\n\n### Disabilitare la scoperta\n\n- CLI: `--no-extensions`\n- Opzione SDK: `disableExtensionDiscovery`\n\nDifferenza di comportamento:\n\n- SDK: quando `disableExtensionDiscovery=true`, carica comunque `additionalExtensionPaths` tramite `loadExtensions()`.\n- La costruzione dei percorsi CLI (`main.ts`) attualmente svuota i percorsi di estensione CLI quando `--no-extensions` è impostato, quindi i percorsi espliciti `-e/--hook` non vengono inoltrati in quella modalità.\n\n### Disabilitare moduli di estensione specifici\n\nL'impostazione `disabledExtensions` filtra per formato id dell'estensione:\n\n- `extension-module:<derivedName>`\n\n`derivedName` è basato sul percorso entry (`getExtensionNameFromPath`), ad esempio:\n\n- `/x/foo.ts` -> `foo`\n- `/x/bar/index.ts` -> `bar`\n\nEsempio:\n\n```yaml\ndisabledExtensions:\n  - extension-module:foo\n```\n\n---\n\n## Risoluzione di percorsi ed entry\n\n### Normalizzazione dei percorsi\n\nPer i percorsi configurati:\n\n1. Normalizzazione degli spazi unicode\n2. Espansione di `~`\n3. Se relativo, risoluzione rispetto al `cwd` corrente\n\n### Se il percorso configurato è un file\n\nViene utilizzato direttamente come candidato entry del modulo.\n\n### Se il percorso configurato è una directory\n\nOrdine di risoluzione:\n\n1. `package.json` in quella directory con `xcsh.extensions` (o legacy `pi.extensions`) -> utilizza le entry dichiarate\n2. `index.ts`\n3. `index.js`\n4. Altrimenti scansiona un livello per le entry di estensione:\n   - file diretti `*.ts` / `*.js`\n   - sottodirectory `index.ts` / `index.js`\n   - sottodirectory `package.json` con `xcsh.extensions` / `pi.extensions`\n\nRegole e vincoli:\n\n- nessuna scoperta ricorsiva oltre un livello di sottodirectory\n- le entry di manifesto dichiarate con `extensions` sono risolte relativamente alla directory del pacchetto\n- le entry dichiarate sono incluse solo se il file esiste/l'accesso è consentito\n- nelle coppie `*/index.{ts,js}`, TypeScript è preferito rispetto a JavaScript\n- i link simbolici sono trattati come file/directory eleggibili\n\n### Il comportamento di ignore differisce per fonte\n\n- L'auto-scoperta nativa (`discoverExtensionModulePaths` negli helper di scoperta) utilizza glob nativo con `gitignore: true` e `hidden: false`.\n- La scansione esplicita di directory configurate in `loader.ts` utilizza regole `readdir` e **non** applica il filtraggio gitignore.\n\n---\n\n## Ordine di caricamento e precedenza\n\n`discoverAndLoadExtensions()` costruisce un'unica lista ordinata e poi chiama `loadExtensions()`.\n\nOrdine:\n\n1. Moduli scoperti automaticamente in modo nativo\n2. Percorsi configurati esplicitamente (nell'ordine fornito)\n\nIn `sdk.ts`, l'ordine configurato è:\n\n1. Percorsi aggiuntivi CLI\n2. `extensions` dalle impostazioni\n\nDe-duplicazione:\n\n- basata sul percorso assoluto\n- il primo percorso trovato ha la precedenza\n- i duplicati successivi vengono ignorati\n\nImplicazione: se lo stesso percorso di modulo è sia scoperto automaticamente che configurato esplicitamente, viene caricato una sola volta nella prima posizione (fase di auto-scoperta).\n\n---\n\n## Importazione del modulo e contratto della factory\n\nOgni percorso candidato viene caricato con importazione dinamica:\n\n- `await import(resolvedPath)`\n- la factory è `module.default ?? module`\n- la factory deve essere una funzione (`ExtensionFactory`)\n\nSe l'export non è una funzione, quel percorso fallisce con un errore strutturato e il caricamento continua.\n\n---\n\n## Gestione dei fallimenti e isolamento\n\n### Durante il caricamento\n\nPer ciascun percorso di estensione, i fallimenti vengono catturati come `{ path, error }` e non impediscono il caricamento degli altri percorsi.\n\nCasi comuni:\n\n- fallimento dell'importazione / file mancante\n- export factory non valido (non-funzione)\n- eccezione lanciata durante l'esecuzione della factory\n\n### Modello di isolamento a runtime\n\n- Le estensioni **non sono sandboxate** (stesso processo/runtime).\n- Condividono un unico `EventBus` e un'unica istanza `ExtensionRuntime`.\n- Durante il caricamento, i metodi di azione del runtime lanciano intenzionalmente `ExtensionRuntimeNotInitializedError`; il collegamento delle azioni avviene successivamente in `ExtensionRunner.initialize()`.\n\n### Dopo il caricamento\n\nQuando gli eventi vengono eseguiti attraverso `ExtensionRunner`, le eccezioni degli handler vengono catturate ed emesse come errori dell'estensione invece di far crashare il ciclo del runner.\n\n---\n\n## Esempi minimi di layout utente/progetto\n\n### Livello utente\n\n```text\n~/.xcsh/agent/\n  config.yml\n  extensions/\n    guardrails.ts\n    audit/\n      index.ts\n```\n\n### Livello progetto\n\n```text\n<repo>/\n  .xcsh/\n    settings.json\n    extensions/\n      checks/\n        package.json\n      lint-gates.ts\n```\n\n`checks/package.json`:\n\n```json\n{\n  \"xcsh\": {\n    \"extensions\": [\"./src/check-a.ts\", \"./src/check-b.js\"]\n  }\n}\n```\n\nChiave di manifesto legacy ancora accettata:\n\n```json\n{\n  \"pi\": {\n    \"extensions\": [\"./index.ts\"]\n  }\n}\n```\n",
	"it/extensions/extensions.md": "---\ntitle: Estensioni\ndescription: >-\n  Panoramica del runtime delle estensioni che copre tipi, ciclo di vita del\n  runner, registrazione e discovery.\nsidebar:\n  order: 1\n  label: Panoramica\ni18n:\n  sourceHash: 14cc16dbd98b\n  translator: machine\n---\n\n# Estensioni\n\nGuida principale per la creazione di estensioni runtime in `packages/coding-agent`.\n\nQuesto documento illustra il runtime delle estensioni corrente in:\n\n- `src/extensibility/extensions/types.ts`\n- `src/extensibility/extensions/runner.ts`\n- `src/extensibility/extensions/wrapper.ts`\n- `src/extensibility/extensions/index.ts`\n- `src/modes/controllers/extension-ui-controller.ts`\n\nPer i percorsi di discovery e le regole di caricamento dal filesystem, consultare `docs/extension-loading.md`.\n\n## Cos'è un'estensione\n\nUn'estensione è un modulo TS/JS che esporta una factory predefinita:\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nexport default function myExtension(pi: ExtensionAPI) {\n // register handlers/tools/commands/renderers\n}\n```\n\nLe estensioni possono combinare tutti gli elementi seguenti in un unico modulo:\n\n- gestori di eventi (`pi.on(...)`)\n- strumenti richiamabili dall'LLM (`pi.registerTool(...)`)\n- comandi slash (`pi.registerCommand(...)`)\n- scorciatoie da tastiera e flag\n- rendering personalizzato dei messaggi\n- API di iniezione sessione/messaggio (`sendMessage`, `sendUserMessage`, `appendEntry`)\n\n## Modello di runtime\n\n1. Le estensioni vengono importate e le loro funzioni factory eseguite.\n2. Durante la fase di caricamento, i metodi di registrazione sono validi; i metodi di azione runtime non sono ancora inizializzati.\n3. `ExtensionRunner.initialize(...)` collega azioni/contesti attivi per la modalità corrente.\n4. Gli eventi del ciclo di vita di sessione/agente/strumento vengono emessi ai gestori.\n5. Ogni esecuzione di strumenti è avvolta con l'intercettazione delle estensioni (`tool_call` / `tool_result`).\n\n```text\nExtension lifecycle (simplified)\n\nload paths\n   │\n   ▼\nimport module + run factory (registration only)\n   │\n   ▼\nExtensionRunner.initialize(mode/session/tool registry)\n   │\n   ├─ emit session/agent events to handlers\n   ├─ wrap tool execution (tool_call/tool_result)\n   └─ expose runtime actions (sendMessage, setActiveTools, ...)\n```\n\nVincolo importante da `loader.ts`:\n\n- la chiamata di metodi di azione come `pi.sendMessage()` durante il caricamento dell'estensione genera `ExtensionRuntimeNotInitializedError`\n- registrare prima; eseguire il comportamento runtime dagli eventi/comandi/strumenti\n\n## Avvio rapido\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\nimport { Type } from \"@sinclair/typebox\";\n\nexport default function (pi: ExtensionAPI) {\n pi.setLabel(\"Safety + Utilities\");\n\n pi.on(\"session_start\", async (_event, ctx) => {\n  ctx.ui.notify(`Extension loaded in ${ctx.cwd}`, \"info\");\n });\n\n pi.on(\"tool_call\", async (event) => {\n  if (event.toolName === \"bash\" && event.input.command?.includes(\"rm -rf\")) {\n   return { block: true, reason: \"Blocked by extension policy\" };\n  }\n });\n\n pi.registerTool({\n  name: \"hello_extension\",\n  label: \"Hello Extension\",\n  description: \"Return a greeting\",\n  parameters: Type.Object({ name: Type.String() }),\n  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {\n   return {\n    content: [{ type: \"text\", text: `Hello, ${params.name}` }],\n    details: { greeted: params.name },\n   };\n  },\n });\n\n pi.registerCommand(\"hello-ext\", {\n  description: \"Show queue state\",\n  handler: async (_args, ctx) => {\n   ctx.ui.notify(`pending=${ctx.hasPendingMessages()}`, \"info\");\n  },\n });\n}\n```\n\n## Superfici dell'API delle estensioni\n\n## 1) Registrazione e azioni (`ExtensionAPI`)\n\nMetodi principali:\n\n- `on(event, handler)`\n- `registerTool`, `registerCommand`, `registerShortcut`, `registerFlag`\n- `registerMessageRenderer`\n- `sendMessage`, `sendUserMessage`, `appendEntry`\n- `getActiveTools`, `getAllTools`, `setActiveTools`\n- `getSessionName`, `setSessionName`\n- `setModel`, `getThinkingLevel`, `setThinkingLevel`\n- `registerProvider`\n- `events` (bus eventi condiviso)\n\nIn modalità interattiva, i gestori `input` vengono eseguiti prima del controllo automatico del titolo del primo messaggio integrato. Le estensioni che chiamano `await pi.setSessionName(...)` da `input` possono impostare il nome della sessione persistente e impedire l'esecuzione del titolo generato automaticamente per tale sessione.\n\nEsposti anche:\n\n- `pi.logger`\n- `pi.typebox`\n- `pi.pi` (esportazioni del pacchetto)\n\n### Semantica di consegna dei messaggi\n\n`pi.sendMessage(message, options)` supporta:\n\n- `deliverAs: \"steer\"` (predefinito) — interrompe l'esecuzione corrente\n- `deliverAs: \"followUp\"` — messo in coda per l'esecuzione dopo quella corrente\n- `deliverAs: \"nextTurn\"` — memorizzato e iniettato al prossimo prompt utente\n- `triggerTurn: true` — avvia un turno quando inattivo (`nextTurn` ignora questo)\n\n`pi.sendUserMessage(content, { deliverAs })` passa sempre attraverso il flusso di prompt; durante lo streaming viene messo in coda come steer/follow-up.\n\n## 2) Contesto del gestore (`ExtensionContext`)\n\nI gestori e il metodo `execute` degli strumenti ricevono `ctx` con:\n\n- `ui`\n- `hasUI`\n- `cwd`\n- `sessionManager` (sola lettura)\n- `modelRegistry`, `model`\n- `getContextUsage()`\n- `compact(...)`\n- `isIdle()`, `hasPendingMessages()`, `abort()`\n- `shutdown()`\n- `getSystemPrompt()`\n\n## 3) Contesto del comando (`ExtensionCommandContext`)\n\nI gestori di comandi ricevono in aggiunta:\n\n- `waitForIdle()`\n- `newSession(...)`\n- `switchSession(...)`\n- `branch(entryId)`\n- `navigateTree(targetId, { summarize })`\n- `reload()`\n\nUtilizzare il contesto del comando per i flussi di controllo della sessione; questi metodi sono intenzionalmente separati dai gestori di eventi generali.\n\n## Superficie degli eventi (nomi e comportamenti correnti)\n\nLe union canoniche degli eventi e i tipi di payload si trovano in `types.ts`.\n\n### Ciclo di vita della sessione\n\n- `session_start`\n- `session_before_switch` / `session_switch`\n- `session_before_branch` / `session_branch`\n- `session_before_compact` / `session.compacting` / `session_compact`\n- `session_before_tree` / `session_tree`\n- `session_shutdown`\n\nPre-eventi annullabili:\n\n- `session_before_switch` → `{ cancel?: boolean }`\n- `session_before_branch` → `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_before_compact` → `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session_before_tree` → `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n\n### Ciclo di vita del prompt e del turno\n\n- `input`\n- `before_agent_start`\n- `context`\n- `agent_start` / `agent_end`\n- `turn_start` / `turn_end`\n- `message_start` / `message_update` / `message_end`\n\n### Ciclo di vita degli strumenti\n\n- `tool_call` (pre-esecuzione, può bloccare)\n- `tool_result` (post-esecuzione, può modificare content/details/isError)\n- `tool_execution_start` / `tool_execution_update` / `tool_execution_end` (osservabilità)\n\n`tool_result` ha stile middleware: i gestori vengono eseguiti nell'ordine delle estensioni e ciascuno vede le modifiche precedenti.\n\n### Segnali di affidabilità/runtime\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### Intercettazione dei comandi utente\n\n- `user_bash` (sovrascrivibile con `{ result }`)\n- `user_python` (sovrascrivibile con `{ result }`)\n\n### `resources_discover`\n\n`resources_discover` esiste nei tipi delle estensioni e in `ExtensionRunner`.\nNota sul runtime corrente: `ExtensionRunner.emitResourcesDiscover(...)` è implementato, ma nel codebase corrente non esistono callsite di `AgentSession` che lo invocano.\n\n## Dettagli per la creazione di strumenti\n\n`registerTool` utilizza `ToolDefinition` da `types.ts`.\n\nFirma corrente di `execute`:\n\n```ts\nexecute(\n toolCallId,\n params,\n signal,\n onUpdate,\n ctx,\n): Promise<AgentToolResult>\n```\n\nTemplate:\n\n```ts\npi.registerTool({\n name: \"my_tool\",\n label: \"My Tool\",\n description: \"...\",\n parameters: Type.Object({}),\n async execute(_id, _params, signal, onUpdate, ctx) {\n  if (signal?.aborted) {\n   return { content: [{ type: \"text\", text: \"Cancelled\" }] };\n  }\n  onUpdate?.({ content: [{ type: \"text\", text: \"Working...\" }] });\n  return { content: [{ type: \"text\", text: \"Done\" }], details: {} };\n },\n onSession(event, ctx) {\n  // reason: start|switch|branch|tree|shutdown\n },\n renderCall(args, theme) {\n  // optional TUI render\n },\n renderResult(result, options, theme, args) {\n  // optional TUI render\n },\n});\n```\n\n`tool_call`/`tool_result` intercettano tutti gli strumenti una volta che il registro è incapsulato in `sdk.ts`, inclusi quelli integrati e quelli personalizzati/delle estensioni.\n\n## Punti di integrazione dell'interfaccia utente\n\n`ctx.ui` implementa l'interfaccia `ExtensionUIContext`. Il supporto varia in base alla modalità.\n\n### Modalità interattiva (`extension-ui-controller.ts`)\n\nSupportato:\n\n- finestre di dialogo: `select`, `confirm`, `input`, `editor`\n- notifiche/stato/testo editor/input terminale/overlay personalizzati\n- elenco/caricamento dei temi per nome (`setTheme` supporta nomi stringa)\n- toggle espansione strumenti\n\nMetodi attualmente non operativi in questo controller:\n\n- `setFooter`\n- `setHeader`\n- `setEditorComponent`\n\nNota: `setWidget` attualmente instrada verso il testo della barra di stato tramite `setHookWidget(...)`.\n\n### Modalità RPC (`rpc-mode.ts`)\n\n`ctx.ui` è supportato da eventi RPC `extension_ui_request`:\n\n- i metodi di dialogo (`select`, `confirm`, `input`, `editor`) eseguono round-trip verso le risposte del client\n- i metodi fire-and-forget emettono richieste (`notify`, `setStatus`, `setWidget` per array di stringhe, `setTitle`, `setEditorText`)\n\nNon supportati/non operativi nell'implementazione RPC:\n\n- `onTerminalInput`\n- `custom`\n- `setFooter`, `setHeader`, `setEditorComponent`\n- `setWorkingMessage`\n- cambio/caricamento del tema (`setTheme` restituisce un errore)\n- i controlli di espansione degli strumenti sono inerti\n\n### Percorsi print/headless/subagent\n\nQuando al runner init non viene fornito alcun contesto UI, `ctx.hasUI` è `false` e i metodi sono no-op/restituiscono valori predefiniti.\n\n### Modalità interattiva in background\n\nLa modalità background installa un oggetto di contesto UI non interattivo. Nell'implementazione corrente, `ctx.hasUI` può comunque essere `true` mentre le finestre di dialogo interattive restituiscono valori predefiniti/comportamento no-op.\n\n## Sessione e pattern di stato\n\nPer lo stato durevole delle estensioni:\n\n1. Persistere con `pi.appendEntry(customType, data)`.\n2. Ricostruire lo stato da `ctx.sessionManager.getBranch()` su `session_start`, `session_branch`, `session_tree`.\n3. Mantenere i `details` del risultato degli strumenti strutturati quando lo stato deve essere visibile/ricostruibile dalla cronologia dei risultati degli strumenti.\n\nEsempio di pattern di ricostruzione:\n\n```ts\npi.on(\"session_start\", async (_event, ctx) => {\n let latest;\n for (const entry of ctx.sessionManager.getBranch()) {\n  if (entry.type === \"custom\" && entry.customType === \"my-state\") {\n   latest = entry.data;\n  }\n }\n // restore from latest\n});\n```\n\n## Punti di estensione del rendering\n\n## Renderer personalizzato per i messaggi\n\n```ts\npi.registerMessageRenderer(\"my-type\", (message, { expanded }, theme) => {\n // return pi-tui Component\n});\n```\n\nUtilizzato dal rendering interattivo quando vengono visualizzati messaggi personalizzati.\n\n## Renderer di chiamata/risultato degli strumenti\n\nFornire `renderCall` / `renderResult` nelle definizioni `registerTool` per la visualizzazione personalizzata degli strumenti nel TUI.\n\n## Vincoli e insidie\n\n- Le azioni runtime non sono disponibili durante il caricamento dell'estensione.\n- Gli errori di `tool_call` bloccano l'esecuzione (fail-closed).\n- I conflitti di nome dei comandi con quelli integrati vengono ignorati con diagnostiche.\n- Le scorciatoie riservate vengono ignorate (`ctrl+c`, `ctrl+d`, `ctrl+z`, `ctrl+k`, `ctrl+p`, `ctrl+l`, `ctrl+o`, `ctrl+t`, `ctrl+g`, `shift+tab`, `shift+ctrl+p`, `alt+enter`, `escape`, `enter`).\n- Trattare `ctx.reload()` come terminale per il frame del gestore del comando corrente.\n\n## Estensioni vs hook vs strumenti personalizzati\n\nUtilizzare la superficie appropriata:\n\n- **Estensioni** (`src/extensibility/extensions/*`): sistema unificato (eventi + strumenti + comandi + renderer + registrazione provider).\n- **Hook** (`src/extensibility/hooks/*`): API eventi legacy separata.\n- **Strumenti personalizzati** (`src/extensibility/custom-tools/*`): moduli orientati agli strumenti; quando caricati insieme alle estensioni vengono adattati e passano comunque attraverso i wrapper di intercettazione delle estensioni.\n\nSe si necessita di un unico pacchetto che gestisca policy, strumenti, UX dei comandi e rendering insieme, utilizzare le estensioni.\n",
	"it/extensions/gemini-manifest-extensions.md": "---\ntitle: Estensioni del manifest Gemini\ndescription: >-\n  Formato delle estensioni del manifest Gemini per la compatibilità di skill e\n  agenti multipiattaforma.\nsidebar:\n  order: 7\n  label: Manifest Gemini\ni18n:\n  sourceHash: 7134165a5f6d\n  translator: machine\n---\n\n# Estensioni del manifest Gemini (`gemini-extension.json`)\n\nQuesto documento illustra come il coding-agent scopre e analizza le estensioni del manifest in stile Gemini (`gemini-extension.json`) nella capability `extensions`.\n\n**Non** tratta il caricamento dei moduli di estensione TypeScript/JavaScript (`extensions/*.ts`, `index.ts`, `package.json xcsh.extensions`), documentato in `extension-loading.md`.\n\n## File di implementazione\n\n- [`../src/discovery/gemini.ts`](../../packages/coding-agent/src/discovery/gemini.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/capability/extension.ts`](../../packages/coding-agent/src/capability/extension.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/loader.ts`](../../packages/coding-agent/src/extensibility/extensions/loader.ts)\n\n---\n\n## Cosa viene scoperto\n\nIl provider Gemini (`id: gemini`, priorità `60`) registra un loader `extensions` che esegue la scansione di due radici fisse:\n\n- Utente: `~/.gemini/extensions`\n- Progetto: `<cwd>/.gemini/extensions`\n\nLa risoluzione dei percorsi avviene direttamente da `ctx.home` e `ctx.cwd` tramite `getUserPath()` / `getProjectPath()`.\n\nRegola importante sull'ambito: la ricerca nel progetto è **limitata alla cwd**. Non risale le directory padre.\n\n---\n\n## Regole di scansione delle directory\n\nPer ciascuna radice (`~/.gemini/extensions` e `<cwd>/.gemini/extensions`), il processo di discovery esegue:\n\n1. `readDirEntries(root)`\n2. mantiene solo le directory figlie dirette (`entry.isDirectory()`)\n3. per ogni figlio `<name>`, tenta di leggere esattamente:\n   - `<root>/<name>/gemini-extension.json`\n\nNon è prevista alcuna scansione ricorsiva oltre un livello di directory.\n\n### Directory nascoste\n\nIl processo di discovery del manifest Gemini **non** filtra i nomi di directory con prefisso punto. Se esiste una directory figlia nascosta contenente `gemini-extension.json`, viene presa in considerazione.\n\n### File mancanti o non leggibili\n\nSe `gemini-extension.json` è assente o non leggibile, quella directory viene ignorata silenziosamente (nessun avviso).\n\n---\n\n## Struttura del manifest (come implementata)\n\nIl tipo di capability definisce questa struttura del manifest:\n\n```ts\ninterface ExtensionManifest {\n name?: string;\n description?: string;\n mcpServers?: Record<string, Omit<MCPServer, \"name\" | \"_source\">>;\n tools?: unknown[];\n context?: unknown;\n}\n```\n\nIl comportamento durante il discovery è volutamente permissivo:\n\n- È richiesto il successo dell'analisi JSON.\n- Non è prevista la validazione dello schema a runtime per i tipi/contenuti dei campi, al di là della sintassi JSON.\n- L'oggetto analizzato viene memorizzato come `manifest` nell'elemento di capability.\n\n### Normalizzazione del nome\n\n`Extension.name` viene impostato su:\n\n1. `manifest.name` se non è `null`/`undefined`\n2. altrimenti il nome della directory dell'estensione\n\nQui non viene applicata alcuna verifica del tipo stringa.\n\n---\n\n## Materializzazione in elementi di capability\n\nUn manifest analizzato correttamente crea un elemento di capability `Extension`:\n\n```ts\n{\n name: manifest.name ?? <directory-name>,\n path: <extension-directory>,\n manifest: <parsed-json>,\n level: \"user\" | \"project\",\n _source: {\n  provider: \"gemini\",\n  providerName: \"Gemini CLI\" // allegato dal registro delle capability\n  path: <absolute-manifest-path>,\n  level: \"user\" | \"project\"\n }\n}\n```\n\nNote:\n\n- `_source.path` viene normalizzato come percorso assoluto da `createSourceMeta()`.\n- La validazione delle capability a livello di registro per `extensions` verifica solo la presenza di `name` e `path`.\n- I contenuti interni del manifest (`mcpServers`, `tools`, `context`) non vengono validati durante il discovery.\n\n---\n\n## Gestione degli errori e semantica degli avvisi\n\n### Con avviso\n\n- JSON non valido in un file manifest:\n  - formato dell'avviso: `Invalid JSON in <manifestPath>`\n\n### Senza avviso (ignorato silenziosamente)\n\n- directory `extensions` assente\n- la directory figlia non ha `gemini-extension.json`\n- file manifest non leggibile\n- il JSON del manifest è sintatticamente valido ma semanticamente anomalo/incompleto\n\nCiò significa che la validità parziale è accettata: solo il fallimento sintattico del JSON genera un avviso.\n\n---\n\n## Precedenza e deduplicazione con altre sorgenti\n\nLa capability `extensions` viene aggregata tra i provider dal registro delle capability.\n\nProvider attuali per questa capability:\n\n- `native` (`packages/coding-agent/src/discovery/builtin.ts`) priorità `100`\n- `gemini` (`packages/coding-agent/src/discovery/gemini.ts`) priorità `60`\n\nLa chiave di deduplicazione è `ext.name` (`extensionCapability.key = ext => ext.name`).\n\n### Precedenza tra provider\n\nIl provider con priorità più alta vince in caso di nomi di estensione duplicati.\n\n- Se sia `native` che `gemini` emettono il nome di estensione `foo`, viene mantenuto l'elemento nativo.\n- Il duplicato a priorità inferiore viene conservato solo in `result.all` con `_shadowed = true`.\n\n### Effetti dell'ordine all'interno del provider\n\nPoiché la deduplicazione segue il criterio \"vince il primo trovato\", l'ordine degli elementi locali al provider è rilevante.\n\n- Il loader Gemini aggiunge prima gli elementi **utente**, poi quelli di **progetto**.\n- Pertanto, i nomi duplicati tra `~/.gemini/extensions` e `<cwd>/.gemini/extensions` mantengono l'elemento utente e oscurano quello di progetto.\n\nAl contrario, il provider nativo costruisce l'ordine delle directory di configurazione in modo diverso (`project` prima di `user` in `getConfigDirs()`), quindi la direzione di oscuramento intra-provider nativo è opposta.\n\n---\n\n## Riepilogo del comportamento utente vs progetto\n\nPer i manifest Gemini in particolare:\n\n- Entrambe le radici utente e progetto vengono scansionate a ogni caricamento.\n- La radice del progetto è fissa su `<cwd>/.gemini/extensions` (nessuna risalita agli antenati).\n- I nomi duplicati all'interno della sorgente Gemini vengono risolti dando precedenza all'utente.\n- I nomi duplicati rispetto a provider con priorità più alta (in particolare nativo) perdono per priorità.\n\n---\n\n## Confine: metadati di discovery vs caricamento delle estensioni a runtime\n\nIl discovery di `gemini-extension.json` attualmente alimenta i metadati di capability (elementi `Extension`). **Non** carica direttamente moduli di estensione TS/JS eseguibili.\n\nIl caricamento dei moduli a runtime (`discoverAndLoadExtensions()` / `loadExtensions()`) utilizza `extension-modules` e percorsi espliciti, e attualmente filtra i moduli auto-scoperti limitandosi al provider `native`.\n\nImplicazione pratica:\n\n- Le estensioni del manifest Gemini sono individuabili come record di capability.\n- Non vengono, di per sé, eseguite come moduli di estensione a runtime dalla pipeline del loader delle estensioni.\n\nQuesto confine è intenzionale nell'implementazione attuale e spiega perché il discovery del manifest e il caricamento dei moduli eseguibili possono divergere.\n",
	"it/extensions/marketplace.md": "---\ntitle: Sistema di plugin del Marketplace\ndescription: >-\n  Sistema di plugin del marketplace per scoprire, installare e gestire raccolte\n  curate di plugin.\nsidebar:\n  order: 4\n  label: Marketplace\ni18n:\n  sourceHash: 71d9f8f93a81\n  translator: machine\n---\n\n# Sistema di plugin del Marketplace\n\nIl sistema marketplace consente di scoprire, installare e gestire plugin da cataloghi ospitati su Git. È compatibile con il formato del registro plugin di Claude Code.\n\n## Avvio rapido\n\n```\n/marketplace add anthropics/f5-sales-demo-marketplace\n/marketplace install wordpress.com@f5-sales-demo-marketplace\n```\n\nOppure digita semplicemente `/marketplace` senza argomenti per aprire il browser interattivo dei plugin.\n\n## Concetti\n\nUn **marketplace** è un repository Git (o una directory locale) contenente un file di catalogo in `.xcsh-plugin/marketplace.json`. Il catalogo elenca i plugin disponibili con le relative sorgenti, descrizioni e metadati.\n\nUn **plugin** è una directory contenente skill, comandi, hook, server MCP o server LSP. I plugin sono identificati da `nome@marketplace` (es. `code-review@f5-sales-demo-marketplace`).\n\n**Ambiti**: i plugin possono essere installati in due ambiti:\n\n- **user** (predefinito) -- disponibile in tutti i progetti, memorizzato in `~/.xcsh/plugins/installed_plugins.json`\n- **project** -- disponibile solo nel progetto corrente, memorizzato in `.xcsh/installed_plugins.json`\n\nLe installazioni con ambito project hanno la precedenza sulle installazioni con ambito user dello stesso plugin.\n\n## Comandi\n\n### Modalità interattiva\n\n| Comando | Effetto |\n|---|---|\n| `/marketplace` | Apre il browser interattivo dei plugin (installa) |\n\n### Gestione del marketplace\n\n| Comando | Effetto |\n|---|---|\n| `/marketplace add <source>` | Aggiunge una sorgente marketplace |\n| `/marketplace remove <name>` | Rimuove un marketplace |\n| `/marketplace update [name]` | Recupera nuovamente i cataloghi; omettere il nome per aggiornare tutti |\n| `/marketplace list` | Elenca i marketplace configurati |\n\n### Operazioni sui plugin\n\n| Comando | Effetto |\n|---|---|\n| `/marketplace discover [marketplace]` | Sfoglia i plugin disponibili |\n| `/marketplace install [--force] [--scope user\\|project] name@marketplace` | Installa un plugin |\n| `/marketplace uninstall [--scope user\\|project] name@marketplace` | Disinstalla un plugin |\n| `/marketplace installed` | Elenca i plugin del marketplace installati |\n| `/marketplace upgrade [--scope user\\|project] [name@marketplace]` | Aggiorna uno o tutti i plugin |\n\n### Equivalenti da riga di comando\n\nLe stesse operazioni sono disponibili dalla riga di comando:\n\n```\nxcsh plugin marketplace add <source>\nxcsh plugin marketplace remove <name>\nxcsh plugin marketplace update [name]\nxcsh plugin marketplace list\nxcsh plugin discover [marketplace]\nxcsh plugin install --scope project name@marketplace\n```\n\n## Sorgenti del marketplace\n\nQuando si esegue `/marketplace add <source>`, il sistema classifica la sorgente:\n\n| Formato della sorgente | Tipo | Esempio |\n|---|---|---|\n| `owner/repo` | Scorciatoia GitHub | `anthropics/f5-sales-demo-marketplace` |\n| `https://...*.json` | URL diretto del catalogo | `https://example.com/marketplace.json` |\n| `https://...*.git` o `git@...` | Repository Git | `https://github.com/org/repo.git` |\n| `./path` o `~/path` o `/path` | Directory locale | `./my-marketplace` |\n\nIl sistema clona il repository (o legge la directory locale), individua `.xcsh-plugin/marketplace.json`, lo valida e memorizza il catalogo nella cache locale.\n\n## Formato del catalogo (marketplace.json)\n\nUn catalogo marketplace risiede in `.xcsh-plugin/marketplace.json` nella root del repository:\n\n```json\n{\n  \"$schema\": \"https://anthropic.com/claude-code/marketplace.schema.json\",\n  \"name\": \"my-marketplace\",\n  \"owner\": {\n    \"name\": \"Your Name\",\n    \"email\": \"you@example.com\"\n  },\n  \"description\": \"A collection of plugins\",\n  \"plugins\": [\n    {\n      \"name\": \"my-plugin\",\n      \"description\": \"What this plugin does\",\n      \"source\": \"./plugins/my-plugin\",\n      \"category\": \"development\",\n      \"homepage\": \"https://github.com/you/my-plugin\"\n    }\n  ]\n}\n```\n\n### Campi obbligatori\n\n| Campo | Descrizione |\n|---|---|\n| `name` | Nome del marketplace. Alfanumerico minuscolo, trattini e punti. Deve iniziare e terminare con un carattere alfanumerico. Massimo 64 caratteri. |\n| `owner.name` | Nome del proprietario del marketplace |\n| `plugins` | Array di voci di plugin |\n\n### Campi delle voci dei plugin\n\n| Campo | Obbligatorio | Descrizione |\n|---|---|---|\n| `name` | sì | Nome del plugin (stesse regole del nome del marketplace) |\n| `source` | sì | Dove trovare il plugin (vedi sotto) |\n| `description` | no | Breve descrizione |\n| `version` | no | Stringa di versione |\n| `author` | no | `{ name, email? }` |\n| `homepage` | no | URL |\n| `category` | no | Stringa di categoria (es. `development`, `productivity`, `security`) |\n| `tags` | no | Array di tag stringa |\n| `strict` | no | Booleano |\n| `commands` | no | Comandi slash forniti |\n| `agents` | no | Agenti forniti |\n| `hooks` | no | Definizioni di hook |\n| `mcpServers` | no | Definizioni di server MCP |\n| `lspServers` | no | Definizioni di server LSP |\n\n### Formati della sorgente del plugin\n\nIl campo `source` supporta diversi formati:\n\n**Percorso relativo** (all'interno del repository del marketplace):\n\n```json\n\"source\": \"./plugins/my-plugin\"\n```\n\n**URL repository Git**:\n\n```json\n\"source\": {\n  \"source\": \"url\",\n  \"url\": \"https://github.com/org/repo.git\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Scorciatoia GitHub**:\n\n```json\n\"source\": {\n  \"source\": \"github\",\n  \"repo\": \"org/repo\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Sottodirectory Git** (monorepo):\n\n```json\n\"source\": {\n  \"source\": \"git-subdir\",\n  \"url\": \"https://github.com/org/monorepo.git\",\n  \"path\": \"plugins/my-plugin\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Pacchetto npm**:\n\n```json\n\"source\": {\n  \"source\": \"npm\",\n  \"package\": \"@scope/my-plugin\",\n  \"version\": \"1.0.0\"\n}\n```\n\n## Struttura su disco\n\n```\n~/.xcsh/\n  config/\n    marketplaces.json          # Registro dei marketplace aggiunti\n  plugins/\n    installed_plugins.json     # Plugin installati con ambito user\n    cache/\n      marketplaces/            # Cataloghi marketplace memorizzati nella cache\n      plugins/                 # Directory dei plugin memorizzate nella cache\n\n<project>/.xcsh/\n  installed_plugins.json       # Plugin installati con ambito project\n```\n\n## Regole di denominazione\n\nI nomi di marketplace e plugin devono:\n\n- Iniziare e terminare con una lettera minuscola o una cifra\n- Contenere solo lettere minuscole, cifre, trattini e punti\n- Avere al massimo 64 caratteri\n\nGli ID dei plugin (`nome@marketplace`) devono avere al massimo 128 caratteri in totale.\n\nEsempi validi: `my-plugin`, `code-review`, `wordpress.com`, `ai-firstify`\nEsempi non validi: `-bad`, `bad-`, `.bad`, `Bad`, `under_score`\n",
	"it/extensions/plugin-manager-installer-plumbing.md": "---\ntitle: Gestione dei plugin e meccanismi di installazione\ndescription: >-\n  Meccanismi interni del gestore di plugin che coprono installazione,\n  validazione, risoluzione delle dipendenze e gestione del ciclo di vita.\nsidebar:\n  order: 5\n  label: Gestore dei plugin\ni18n:\n  sourceHash: 9c33e5a2c22a\n  translator: machine\n---\n\n# Gestore dei plugin e meccanismi di installazione\n\nQuesto documento descrive come le operazioni `xcsh plugin` modificano lo stato dei plugin su disco e come i plugin installati diventano funzionalità disponibili a runtime (oggi come strumenti, con risoluzione del percorso per hook/comandi disponibile).\n\n## Ambito e architettura\n\nNel codice sono presenti due implementazioni di gestione dei plugin:\n\n1. **Percorso attivo utilizzato dai comandi CLI**: `PluginManager` (`src/extensibility/plugins/manager.ts`)\n2. **Modulo helper legacy**: funzioni di installazione (`src/extensibility/plugins/installer.ts`)\n\nL'esecuzione del comando `xcsh plugin ...` passa attraverso `PluginManager`.\n\n`installer.ts` documenta ancora importanti controlli di sicurezza e comportamenti del filesystem, ma non è il percorso utilizzato da `src/commands/plugin.ts` + `src/cli/plugin-cli.ts`.\n\n## Ciclo di vita: dall'invocazione CLI alla disponibilità a runtime\n\n```text\nxcsh plugin <action> ...\n  -> src/commands/plugin.ts\n  -> runPluginCommand(...) in src/cli/plugin-cli.ts\n  -> PluginManager method (install/list/uninstall/link/...) \n  -> mutate ~/.xcsh/plugins/{package.json,node_modules,xcsh-plugins.lock.json}\n  -> runtime discovery: discoverAndLoadCustomTools(...)\n  -> getAllPluginToolPaths(cwd)\n  -> custom tool loader imports tool modules\n```\n\n### Punti di ingresso dei comandi\n\n- `src/commands/plugin.ts` definisce i comandi/flag e li inoltra a `runPluginCommand`.\n- `src/cli/plugin-cli.ts` mappa i sottocomandi ai metodi di `PluginManager`:\n  - `install`, `uninstall`, `list`, `link`, `doctor`, `features`, `config`, `enable`, `disable`\n- Non esiste un'azione esplicita `update`; l'aggiornamento avviene rieseguendo `install` con una nuova specifica di pacchetto/versione.\n\n## Modello su disco\n\nLo stato globale dei plugin risiede in `~/.xcsh/plugins`:\n\n- `package.json` — manifesto delle dipendenze utilizzato da `bun install`/`bun uninstall`\n- `node_modules/` — pacchetti plugin installati o symlink\n- `xcsh-plugins.lock.json` — stato a runtime:\n  - abilitato/disabilitato per ciascun plugin\n  - set di funzionalità selezionato per ciascun plugin\n  - impostazioni persistite del plugin\n\nLe sovrascritture locali al progetto si trovano in:\n\n- `<cwd>/.xcsh/plugin-overrides.json`\n\nLe sovrascritture sono in sola lettura dal punto di vista del gestore/caricatore (nessun percorso di scrittura qui) e possono disabilitare plugin o sovrascrivere funzionalità/impostazioni per questo progetto.\n\n## Analisi delle specifiche dei plugin e interpretazione dei metadati\n\n## Grammatica delle specifiche di installazione\n\n`parsePluginSpec` (`parser.ts`) supporta:\n\n- `pkg` -> `features: null` (comportamento predefinito)\n- `pkg[*]` -> abilita tutte le funzionalità del manifesto\n- `pkg[]` -> non abilitare funzionalità opzionali\n- `pkg[a,b]` -> abilita le funzionalità specificate per nome\n- `@scope/pkg@1.2.3[feat]` -> pacchetto con scope e versione con selezione esplicita delle funzionalità\n\n`extractPackageName` rimuove il suffisso di versione per la ricerca del percorso su disco dopo l'installazione.\n\n## Sorgente del manifesto e campi obbligatori\n\nIl manifesto viene risolto come segue:\n\n1. `package.json.xcsh`\n2. fallback `package.json.pi`\n3. fallback `{ version: package.version }`\n\nImplicazioni:\n\n- Non esiste una validazione strict dello schema nel gestore/caricatore.\n- Un pacchetto privo di manifesto `xcsh`/`pi` è comunque installabile ed elencabile.\n- Il caricamento del plugin a runtime (`getEnabledPlugins`) salta i pacchetti privi di manifesto `xcsh`/`pi`.\n- `manifest.version` viene sempre sovrascritto dalla `version` del pacchetto.\n\nUn JSON `package.json` malformato è un errore bloccante al momento della lettura; una struttura del manifesto malformata potrebbe fallire in seguito solo quando vengono consumati campi specifici.\n\n## Flusso di installazione/aggiornamento (`PluginManager.install`)\n\n1. Analizzare la sintassi delle parentesi per le funzionalità dalla specifica di installazione.\n2. Validare il nome del pacchetto tramite regex + lista di esclusione dei metacaratteri shell.\n3. Verificare che esista il `package.json` del plugin (`xcsh-plugins`, mappa delle dipendenze private).\n4. Eseguire `bun install <packageSpec>` in `~/.xcsh/plugins`.\n5. Leggere il `package.json` del pacchetto installato in `node_modules/<name>/package.json`.\n6. Risolvere il manifesto e calcolare `enabledFeatures`:\n   - `[*]`: tutte le funzionalità dichiarate (o `null` se non è presente una mappa di funzionalità)\n   - `[a,b]`: valida che ogni funzionalità esista nella mappa delle funzionalità del manifesto\n   - `[]`: lista di funzionalità vuota\n   - specifica senza parentesi: `null` (utilizzare in seguito la policy dei valori predefiniti nel caricatore)\n7. Aggiornare o inserire lo stato a runtime nel lockfile: `{ version, enabledFeatures, enabled: true }`.\n\n### Semantica degli aggiornamenti\n\nPoiché l'aggiornamento è guidato dall'installazione:\n\n- `xcsh plugin install pkg@newVersion` aggiorna la dipendenza e la versione nel lockfile.\n- Le impostazioni esistenti vengono preservate; la voce dello stato viene sovrascritta per versione/funzionalità/abilitazione.\n- Non esiste logica separata per \"verificare aggiornamenti\" o per la migrazione transazionale.\n\n## Flusso di rimozione (`PluginManager.uninstall`)\n\n1. Validare il nome del pacchetto.\n2. Eseguire `bun uninstall <name>` nella directory dei plugin.\n3. Rimuovere lo stato a runtime del plugin dal lockfile:\n   - `config.plugins[name]`\n   - `config.settings[name]`\n\nSe il comando di disinstallazione fallisce, lo stato a runtime non viene modificato.\n\n## Flusso di elenco (`PluginManager.list`)\n\n1. Leggere la mappa delle dipendenze dei plugin da `~/.xcsh/plugins/package.json`.\n2. Caricare la configurazione a runtime dal lockfile (file mancante -> valori predefiniti vuoti).\n3. Caricare le sovrascritture del progetto (`<cwd>/.xcsh/plugin-overrides.json`, errori di analisi/lettura -> oggetto vuoto con avviso).\n4. Per ogni dipendenza con un `package.json` risolvibile:\n   - costruire il record `InstalledPlugin`\n   - unire lo stato di funzionalità/abilitazione:\n     - base dal lockfile (o valori predefiniti)\n     - le sovrascritture del progetto possono sostituire la selezione delle funzionalità\n     - la lista `disabled` del progetto maschera il plugin come disabilitato\n\nQuesto è lo stato effettivo utilizzato dall'output dello stato CLI e dalle operazioni su impostazioni/funzionalità.\n\n## Flusso di collegamento (`PluginManager.link`)\n\n`link` supporta lo sviluppo locale di plugin creando un symlink di un pacchetto locale in `~/.xcsh/plugins/node_modules/<pkg.name>`.\n\nComportamento:\n\n1. Risolvere `localPath` rispetto alla directory di lavoro del gestore.\n2. Richiedere la presenza di `package.json` e del campo `name` locali.\n3. Verificare che le directory dei plugin esistano.\n4. Per nomi con scope, creare la directory dello scope.\n5. Rimuovere il percorso esistente nella posizione del link di destinazione.\n6. Creare il symlink.\n7. Aggiungere la voce nel lockfile a runtime abilitata con le funzionalità predefinite (`null`).\n\nAvvertenza: l'attuale `PluginManager.link` non applica il controllo dei limiti del percorso `cwd` presente nel legacy `installer.ts` (`normalizedPath.startsWith(normalizedCwd)`), pertanto la fiducia è responsabilità del chiamante.\n\n## Caricamento a runtime: dal plugin installato alle funzionalità disponibili\n\n## Gate di scoperta\n\n`getEnabledPlugins(cwd)` (`plugins/loader.ts`) legge:\n\n- il manifesto delle dipendenze del plugin (`package.json`)\n- lo stato a runtime del lockfile\n- le sovrascritture del progetto tramite `getConfigDirPaths(\"plugin-overrides.json\", { user: false, cwd })`\n\nFiltraggio:\n\n- salta se non è presente il package.json del plugin\n- salta se il manifesto (`xcsh`/`pi`) è assente\n- salta se globalmente disabilitato nel lockfile\n- salta se disabilitato dal progetto\n\n## Risoluzione del percorso delle funzionalità\n\nPer ogni plugin abilitato:\n\n- `resolvePluginToolPaths(plugin)`\n- `resolvePluginHookPaths(plugin)`\n- `resolvePluginCommandPaths(plugin)`\n\nOgni resolver include voci base più voci delle funzionalità:\n\n- lista di funzionalità esplicita -> solo le funzionalità selezionate\n- `enabledFeatures === null` -> abilita le funzionalità contrassegnate con `default: true`\n\nI file mancanti vengono ignorati silenziosamente (guardia `existsSync`).\n\n## Differenze attuali nel cablaggio a runtime\n\n- **Gli strumenti sono cablati nel runtime oggi** tramite `discoverAndLoadCustomTools` (`custom-tools/loader.ts`), che chiama `getAllPluginToolPaths(cwd)`.\n- I percorsi vengono deduplicati in base al percorso assoluto risolto nella scoperta degli strumenti personalizzati (insieme `seen`, vince il primo percorso).\n- **I resolver per hook/comandi esistono** e sono esportati, ma questo percorso di codice non li cablature attualmente in un registro a runtime nello stesso modo in cui vengono cablati gli strumenti.\n\n## Dettagli di gestione del lock/stato\n\n`PluginManager` memorizza nella cache la configurazione a runtime in memoria per istanza (`#runtimeConfig`) e la carica in modo lazy una sola volta.\n\nComportamento di caricamento:\n\n- lockfile mancante -> `{ plugins: {}, settings: {} }`\n- errore di lettura/analisi del lockfile -> avviso + stessi valori predefiniti vuoti\n\nComportamento di salvataggio:\n\n- scrive l'intero JSON del lockfile con formattazione pretty-print ad ogni mutazione\n\nNon esiste alcun meccanismo di blocco tra processi o strategia di merge; scrittori concorrenti possono sovrascriversi a vicenda.\n\n## Controlli di sicurezza e limiti di fiducia\n\n## Validazione dell'input/pacchetto\n\nIl percorso del gestore attivo applica la validazione del nome del pacchetto:\n\n- regex per specifiche di pacchetto con e senza scope (opzionalmente con versione)\n- lista esplicita di esclusione dei metacaratteri shell (`[;&|`$(){}[]<>\\\\]`)\n\nQuesto limita il rischio di command injection quando si invoca `bun install/uninstall`.\n\n## Limite di fiducia del filesystem\n\n- Il codice del plugin viene eseguito in-process quando i moduli degli strumenti personalizzati vengono importati; nessun sandboxing.\n- I percorsi relativi del manifesto vengono congiunti alla directory del pacchetto plugin e viene verificata solo la loro esistenza.\n- Il pacchetto plugin stesso è codice fidato una volta installato.\n\n## Controlli esclusivi del legacy installer\n\n`installer.ts` include controlli aggiuntivi al momento del collegamento non presenti in `PluginManager.link`:\n\n- il percorso locale deve essere risolto all'interno della directory di lavoro del progetto\n- guardie aggiuntive per il nome del pacchetto e la traversata del percorso per la denominazione del target del symlink\n\nPoiché la CLI utilizza `PluginManager`, queste guardie di collegamento più restrittive non sono attualmente nel percorso principale.\n\n## Comportamento in caso di errore, successo parziale e rollback\n\nIl gestore dei plugin non è transazionale.\n\n| Fase dell'operazione | Comportamento in caso di errore | Rollback |\n| --- | --- | --- |\n| `bun install` fallisce | l'installazione si interrompe con stderr | N/A (nessuna scrittura di stato ancora) |\n| Installazione riuscita, poi la validazione del manifesto/funzionalità fallisce | il comando fallisce | Nessun rollback della disinstallazione; la dipendenza potrebbe rimanere in `node_modules`/`package.json` |\n| Installazione riuscita, poi la scrittura del lockfile fallisce | il comando fallisce | Nessun rollback del pacchetto installato |\n| `bun uninstall` riuscito, scrittura del lockfile fallisce | il comando fallisce | Pacchetto rimosso, lo stato a runtime obsoleto potrebbe rimanere |\n| `link` rimuove il target precedente poi la creazione del symlink fallisce | il comando fallisce | Nessun ripristino del link/directory precedente |\n\nDal punto di vista operativo, `doctor --fix` può riparare alcune discrepanze (`bun install`, pulizia delle configurazioni orfane, pulizia delle funzionalità non valide), ma è un tentativo best-effort.\n\n## Riepilogo del comportamento con manifesto malformato/mancante\n\n- Campo `xcsh`/`pi` mancante:\n  - installazione/elenco: tollerato (manifesto minimale)\n  - scoperta dei plugin abilitati a runtime: ignorato come non-plugin\n- Funzionalità mancante referenziata dalla specifica di installazione o da `features --set/--enable`: errore bloccante con lista delle funzionalità disponibili\n- `plugin-overrides.json` non valido: ignorato con fallback a `{}` sia nel percorso del gestore che del caricatore\n- Percorsi di file strumenti/hook/comandi mancanti referenziati dal manifesto: ignorati silenziosamente durante l'espansione del resolver; segnalati come errori solo da `doctor`\n\n## Differenze di modalità e precedenza\n\n- `--dry-run` (install): restituisce un risultato di installazione sintetico, nessuna scrittura su filesystem/rete/stato.\n- `--json`: solo formattazione dell'output, nessun cambiamento di comportamento.\n- Le sovrascritture del progetto hanno sempre la precedenza sul lockfile globale per la visualizzazione di funzionalità/impostazioni.\n- L'abilitazione effettiva è `runtimeEnabled && !projectDisabled`.\n\n## File di implementazione\n\n- [`src/commands/plugin.ts`](../../packages/coding-agent/src/commands/plugin.ts) — dichiarazione del comando CLI e mappatura dei flag\n- [`src/cli/plugin-cli.ts`](../../packages/coding-agent/src/cli/plugin-cli.ts) — dispatching delle azioni, gestori dei comandi rivolti all'utente\n- [`src/extensibility/plugins/manager.ts`](../../packages/coding-agent/src/extensibility/plugins/manager.ts) — implementazione attiva di install/remove/list/link/state/doctor\n- [`src/extensibility/plugins/installer.ts`](../../packages/coding-agent/src/extensibility/plugins/installer.ts) — helper legacy del installer e controlli di sicurezza aggiuntivi per il collegamento\n- [`src/extensibility/plugins/loader.ts`](../../packages/coding-agent/src/extensibility/plugins/loader.ts) — scoperta dei plugin abilitati e risoluzione dei percorsi di strumenti/hook/comandi\n- [`src/extensibility/plugins/parser.ts`](../../packages/coding-agent/src/extensibility/plugins/parser.ts) — helper per l'analisi delle specifiche di installazione e dei nomi dei pacchetti\n- [`src/extensibility/plugins/types.ts`](../../packages/coding-agent/src/extensibility/plugins/types.ts) — contratti di tipo per manifesto/runtime/sovrascritture\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts) — cablaggio a runtime per i moduli degli strumenti forniti dai plugin\n",
	"it/extensions/rulebook-matching-pipeline.md": "---\ntitle: Pipeline di corrispondenza del Rulebook\ndescription: >-\n  Pipeline di corrispondenza del rulebook per la selezione e l'applicazione di\n  set di istruzioni specifici per contesto alle sessioni dell'agente.\nsidebar:\n  order: 6\n  label: Corrispondenza del rulebook\ni18n:\n  sourceHash: a16a9c565053\n  translator: machine\n---\n\n# Pipeline di corrispondenza del Rulebook\n\nQuesto documento descrive come il coding-agent rileva le regole dai formati di configurazione supportati, le normalizza in un'unica struttura `Rule`, risolve i conflitti di precedenza e suddivide il risultato in:\n\n- **Regole del rulebook** (disponibili per il modello tramite prompt di sistema + URL `rule://`)\n- **Regole TTSR** (regole di interruzione del flusso time-travel)\n\nRiflette l'implementazione corrente, incluse semantiche parziali e metadati analizzati ma non applicati.\n\n## File di implementazione\n\n- [`../src/capability/rule.ts`](../../packages/coding-agent/src/capability/rule.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/discovery/index.ts`](../../packages/coding-agent/src/discovery/index.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/cursor.ts`](../../packages/coding-agent/src/discovery/cursor.ts)\n- [`../src/discovery/windsurf.ts`](../../packages/coding-agent/src/discovery/windsurf.ts)\n- [`../src/discovery/cline.ts`](../../packages/coding-agent/src/discovery/cline.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/system-prompt.ts`](../../packages/coding-agent/src/system-prompt.ts)\n- [`../src/internal-urls/rule-protocol.ts`](../../packages/coding-agent/src/internal-urls/rule-protocol.ts)\n- [`../src/utils/frontmatter.ts`](../../packages/coding-agent/src/utils/frontmatter.ts)\n\n## 1. Struttura canonica della regola\n\nTutti i provider normalizzano i file sorgente in `Rule`:\n\n```ts\ninterface Rule {\n  name: string;\n  path: string;\n  content: string;\n  globs?: string[];\n  alwaysApply?: boolean;\n  description?: string;\n  ttsrTrigger?: string;\n  _source: SourceMeta;\n}\n```\n\nL'identità della capability è `rule.name` (`ruleCapability.key = rule => rule.name`).\n\nConseguenza: la precedenza e la deduplicazione si basano **esclusivamente sul nome**. Due file diversi con lo stesso `name` sono considerati la stessa regola logica.\n\n## 2. Sorgenti di rilevamento e normalizzazione\n\n`src/discovery/index.ts` registra automaticamente i provider. Per `rules`, i provider correnti sono:\n\n- `native` (priorità `100`)\n- `cursor` (priorità `50`)\n- `windsurf` (priorità `50`)\n- `cline` (priorità `40`)\n\n### Provider nativo (`builtin.ts`)\n\nCarica le regole `.xcsh` da:\n\n- progetto: `<cwd>/.xcsh/rules/*.{md,mdc}`\n- utente: `~/.xcsh/agent/rules/*.{md,mdc}`\n\nNormalizzazione:\n\n- `name` = nome del file senza `.md`/`.mdc`\n- frontmatter analizzato tramite `parseFrontmatter`\n- `content` = corpo (frontmatter rimosso)\n- `globs`, `alwaysApply`, `description`, `ttsr_trigger` mappati direttamente\n\nNota importante: `globs` viene convertito come `string[] | undefined` senza filtraggio degli elementi in questo provider.\n\n### Provider Cursor (`cursor.ts`)\n\nCarica da:\n\n- utente: `~/.cursor/rules/*.{mdc,md}`\n- progetto: `<cwd>/.cursor/rules/*.{mdc,md}`\n\nNormalizzazione (`transformMDCRule`):\n\n- `description`: mantenuto solo se stringa\n- `alwaysApply`: viene preservato solo `true` (`false` diventa `undefined`)\n- `globs`: accetta array (solo elementi stringa) o singola stringa\n- `ttsr_trigger`: solo stringa\n- `name` dal nome del file senza estensione\n\n### Provider Windsurf (`windsurf.ts`)\n\nCarica da:\n\n- utente: `~/.codeium/windsurf/memories/global_rules.md` (nome regola fisso `global_rules`)\n- progetto: `<cwd>/.windsurf/rules/*.md`\n\nNormalizzazione:\n\n- `globs`: array di stringhe o singola stringa\n- `alwaysApply`, `description` convertiti dal frontmatter\n- `ttsr_trigger`: solo stringa\n- `name` dal nome del file per le regole di progetto\n\n### Provider Cline (`cline.ts`)\n\nRicerca verso l'alto a partire da `cwd` per il `.clinerules` più vicino:\n\n- se directory: carica i file `*.md` al suo interno\n- se file: carica il singolo file come regola denominata `clinerules`\n\nNormalizzazione:\n\n- `globs`: array di stringhe o singola stringa\n- `alwaysApply`: solo se booleano\n- `description`: solo stringa\n- `ttsr_trigger`: solo stringa\n\n## 3. Comportamento di analisi del frontmatter e ambiguità\n\nTutti i provider utilizzano `parseFrontmatter` (`utils/frontmatter.ts`) con le seguenti semantiche:\n\n1. Il frontmatter viene analizzato solo quando il contenuto inizia con `---` e ha una chiusura `\\n---`.\n2. Il corpo viene rimosso degli spazi dopo l'estrazione del frontmatter.\n3. Se l'analisi YAML fallisce:\n   - viene registrato un avviso,\n   - il parser ricorre all'analisi semplice per riga `key: value` (`^(\\w+):\\s*(.*)$`).\n\nConseguenze dell'ambiguità:\n\n- Il parser di fallback non supporta array, oggetti annidati, regole di quotatura o chiavi con trattino.\n- I valori di fallback diventano stringhe (ad esempio `alwaysApply: true` diventa la stringa `\"true\"`), pertanto i provider che richiedono tipi booleani/stringa potrebbero eliminare i metadati.\n- `ttsr_trigger` funziona nel fallback (chiave con underscore); chiavi come `thinking-level` non funzionerebbero.\n- I file senza frontmatter valido vengono comunque caricati come regole con metadati vuoti e corpo del contenuto completo.\n\n## 4. Precedenza dei provider e deduplicazione\n\n`loadCapability(\"rules\")` (`capability/index.ts`) unisce gli output dei provider e poi deduplicata per `rule.name`.\n\n### Modello di precedenza\n\n- I provider sono ordinati per priorità decrescente.\n- A parità di priorità, viene mantenuto l'ordine di registrazione (`cursor` prima di `windsurf` da `discovery/index.ts`).\n- La deduplicazione segue il criterio \"primo vince\": il primo nome di regola incontrato viene mantenuto; gli elementi successivi con lo stesso nome vengono contrassegnati come `_shadowed` in `all` ed esclusi da `items`.\n\nL'ordine effettivo dei provider di regole è attualmente:\n\n1. `native` (100)\n2. `cursor` (50)\n3. `windsurf` (50)\n4. `cline` (40)\n\n### Avvertenza sull'ordinamento intra-provider\n\nAll'interno di un provider, l'ordine degli elementi deriva dall'ordinamento dei risultati glob di `loadFilesFromDir` più l'ordine esplicito di inserimento. Questo è sufficientemente deterministico per l'uso normale, ma non è ordinato esplicitamente nel codice.\n\nDifferenze notevoli nell'ordine delle sorgenti:\n\n- `native` aggiunge prima la directory di configurazione del progetto, poi quella dell'utente.\n- `cursor` aggiunge prima i risultati dell'utente, poi quelli del progetto.\n- `windsurf` aggiunge prima `global_rules` dell'utente, poi le regole del progetto.\n- `cline` carica solo la sorgente `.clinerules` più vicina.\n\n## 5. Suddivisione nei bucket Rulebook, Always-Apply e TTSR\n\nDopo il rilevamento delle regole in `createAgentSession` (`sdk.ts`):\n\n1. Tutte le regole rilevate vengono analizzate.\n2. Le regole con `condition` (chiave frontmatter; `ttsr_trigger` / `ttsrTrigger` accettati come fallback) vengono registrate nel `TtsrManager`.\n3. Una lista separata `rulebookRules` viene costruita con questo predicato:\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && !rule.alwaysApply && !!rule.description\n```\n\n4. Una lista `alwaysApplyRules` viene costruita:\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && rule.alwaysApply === true\n```\n\n### Comportamento dei bucket\n\n- **Bucket TTSR**: qualsiasi regola con `condition` (description non richiesta). Ha priorità sugli altri bucket.\n- **Bucket always-apply**: `alwaysApply === true`, non TTSR. Il contenuto completo viene iniettato nel prompt di sistema. Accessibile tramite `rule://`.\n- **Bucket rulebook**: deve avere description, non deve essere TTSR, non deve essere `alwaysApply`. Elencato nel prompt di sistema per nome + description; il contenuto viene letto su richiesta tramite `rule://`.\n- Una regola con sia `condition` che `alwaysApply` va solo al TTSR (il TTSR ha la priorità).\n- Una regola con sia `alwaysApply` che `description` va solo al bucket always-apply (non al rulebook).\n\n## 6. Come i metadati influenzano le superfici di runtime\n\n### `description`\n\n- Obbligatoria per l'inclusione nel rulebook.\n- Visualizzata nel blocco `<rules>` del prompt di sistema.\n- L'assenza di description significa che la regola non è disponibile tramite `rule://` e non è elencata nelle regole del prompt di sistema.\n\n### `globs`\n\n- Propagata attraverso `Rule`.\n- Visualizzata come voci `<glob>...</glob>` nel blocco delle regole del prompt di sistema.\n- Esposta nello stato UI delle regole (lista modalità `extensions`).\n- **Non applicata per la corrispondenza automatica in questa pipeline.** Non esiste un matcher glob in fase di runtime che selezioni le regole in base al file corrente o al target dello strumento.\n\n### `alwaysApply`\n\n- Analizzato e preservato dai provider.\n- Utilizzato nella visualizzazione UI (etichetta trigger `\"always\"` nel gestore dello stato extensions).\n- Utilizzato come condizione di esclusione da `rulebookRules`.\n- **Il contenuto completo della regola viene iniettato automaticamente nel prompt di sistema** (prima della sezione delle regole del rulebook).\n- La regola è anche indirizzabile tramite `rule://<name>` per la rilettura.\n\n### `ttsr_trigger`\n\n- Mappato a `rule.ttsrTrigger`.\n- Se presente, la regola viene instradata al gestore TTSR, non al rulebook.\n\n## 7. Percorso di inclusione nel prompt di sistema\n\n`buildSystemPromptInternal` riceve sia `rules` (rulebook) che `alwaysApplyRules`.\n\nLe regole always-apply vengono visualizzate per prime, iniettando il loro contenuto grezzo direttamente nel prompt.\n\nLe regole del rulebook vengono visualizzate in una sezione `# Rules` con:\n\n- `Read rule://<name> when working in matching domain`\n- Per ogni regola: `name`, `description` e lista `<glob>` opzionale\n\nQuesto è di natura consultiva/contestuale: il testo del prompt chiede al modello di leggere le regole applicabili, ma il codice non applica l'applicabilità dei glob.\n\n## 8. Comportamento dell'URL interno `rule://`\n\n`RuleProtocolHandler` è registrato con:\n\n```ts\nnew RuleProtocolHandler({ getRules: () => [...rulebookRules, ...alwaysApplyRules] })\n```\n\nImplicazioni:\n\n- `rule://<name>` viene risolto rispetto a **rulebookRules** e **alwaysApplyRules**.\n- Le regole solo TTSR e le regole senza description e senza `alwaysApply` non sono indirizzabili tramite `rule://`.\n- La risoluzione avviene per corrispondenza esatta del nome.\n- I nomi sconosciuti restituiscono un errore con l'elenco dei nomi di regole disponibili.\n- Il contenuto restituito è il `rule.content` grezzo (frontmatter rimosso), tipo di contenuto `text/markdown`.\n\n## 9. Semantiche parziali / non applicate note\n\n1. Le descrizioni dei provider menzionano file legacy (`.cursorrules`, `.windsurfrules`), ma i percorsi di caricamento del codice corrente non leggono effettivamente quei file.\n2. I metadati `globs` vengono esposti al prompt/UI ma non sono applicati dalla logica di selezione delle regole.\n3. La selezione delle regole per `rule://` include le regole del rulebook e quelle always-apply, ma non le regole solo TTSR.\n4. Gli avvisi di rilevamento (`loadCapability(\"rules\").warnings`) vengono prodotti, ma `createAgentSession` non li espone/registra attualmente in questo percorso.\n",
	"it/extensions/skills.md": "---\ntitle: Competenze\ndescription: >-\n  Sistema di competenze per la registrazione, il rilevamento e l'invocazione di\n  capacità specializzate nell'agente di codifica.\nsidebar:\n  order: 3\n  label: Competenze\ni18n:\n  sourceHash: 3e062cc13851\n  translator: machine\n---\n\n# Competenze\n\nLe competenze sono pacchetti di capacità basati su file, rilevati all'avvio ed esposti al modello come:\n\n- metadati leggeri nel prompt di sistema (nome + descrizione)\n- contenuto su richiesta tramite `read skill://...`\n- comandi interattivi facoltativi `/skill:<name>`\n\nQuesto documento descrive il comportamento attuale del runtime in `src/extensibility/skills.ts`, `src/discovery/builtin.ts`, `src/internal-urls/skill-protocol.ts` e `src/discovery/agents-md.ts`.\n\n## Cosa rappresenta una competenza in questo codebase\n\nUna competenza rilevata è rappresentata come:\n\n- `name`\n- `description`\n- `filePath` (il percorso `SKILL.md`)\n- `baseDir` (directory della competenza)\n- metadati di origine (`provider`, `level`, percorso)\n\nIl runtime richiede solo `name` e `path` per la validità. In pratica, la qualità della corrispondenza dipende dal fatto che `description` sia significativa.\n\n## Layout richiesto e aspettative su SKILL.md\n\n### Layout della directory\n\nPer il rilevamento basato su provider (provider nativi/Claude/Codex/Agents/plugin), le competenze vengono rilevate a **un livello sotto `skills/`**:\n\n- `<skills-root>/<skill-name>/SKILL.md`\n\nI pattern annidati come `<skills-root>/group/<skill>/SKILL.md` non vengono rilevati dai loader dei provider.\n\nPer `skills.customDirectories`, la scansione utilizza lo stesso layout non ricorsivo (`*/SKILL.md`).\n\n```text\nProvider-discovered layout (non-recursive under skills/):\n\n<root>/skills/\n  ├─ postgres/\n  │   └─ SKILL.md      ✅ discovered\n  ├─ pdf/\n  │   └─ SKILL.md      ✅ discovered\n  └─ team/\n      └─ internal/\n          └─ SKILL.md  ❌ not discovered by provider loaders\n\nCustom-directory scanning is also non-recursive, so nested paths are ignored unless you point `customDirectories` at that nested parent.\n```\n\n### Frontmatter di `SKILL.md`\n\nCampi frontmatter supportati nel tipo della competenza:\n\n- `name?: string`\n- `description?: string`\n- `globs?: string[]`\n- `alwaysApply?: boolean`\n- le chiavi aggiuntive vengono mantenute come metadati sconosciuti\n\nComportamento attuale del runtime:\n\n- `name` utilizza per impostazione predefinita il nome della directory della competenza\n- `description` è obbligatoria per:\n  - il rilevamento delle competenze del provider nativo `.xcsh` (`requireDescription: true`)\n  - le scansioni di `skills.customDirectories` tramite `scanSkillsFromDir` in `src/discovery/helpers.ts` (non ricorsivo)\n- i provider non nativi possono caricare competenze senza descrizione\n\n## Pipeline di rilevamento\n\n`discoverSkills()` in `src/extensibility/skills.ts` esegue due passaggi:\n\n1. **Provider di capacità** tramite `loadCapability(\"skills\")`\n2. **Directory personalizzate** tramite `scanSkillsFromDir(..., { requireDescription: true })` (enumerazione di directory a un livello)\n\nSe `skills.enabled` è `false`, il rilevamento non restituisce alcuna competenza.\n\n### Provider di competenze predefiniti e precedenza\n\nL'ordinamento dei provider è basato sulla priorità (la più alta prevale), poi sull'ordine di registrazione in caso di parità.\n\nProvider di competenze attualmente registrati:\n\n1. `native` (priorità 100) — competenze utente/progetto `.xcsh` tramite `src/discovery/builtin.ts`\n2. `claude` (priorità 80)\n3. gruppo priorità 70 (in ordine di registrazione):\n   - `claude-plugins`\n   - `agents`\n   - `codex`\n\nLa chiave di deduplicazione è il nome della competenza. Vince il primo elemento con un determinato nome.\n\n### Controlli di origine e filtraggio\n\n`discoverSkills()` applica questi controlli:\n\n- controlli di origine: `enableCodexUser`, `enableClaudeUser`, `enableClaudeProject`, `enablePiUser`, `enablePiProject`\n- filtri glob sul nome della competenza:\n  - `ignoredSkills` (escludi)\n  - `includeSkills` (lista di inclusione consentita; vuota significa includi tutto)\n\nL'ordine dei filtri è:\n\n1. origine abilitata\n2. non ignorata\n3. inclusa (se è presente una lista di inclusione)\n\nPer i provider diversi da codex/claude/native (ad esempio `agents`, `claude-plugins`), l'abilitazione ricade attualmente su: abilitato se **almeno** un controllo di origine predefinito è abilitato.\n\n### Gestione di collisioni e duplicati\n\n- La deduplicazione delle capacità mantiene già la prima competenza per nome (provider con precedenza più alta)\n- `extensibility/skills.ts` inoltre:\n  - deduplica i file identici tramite `realpath` (sicuro per i symlink)\n  - emette avvisi di collisione quando il nome di una competenza successiva è in conflitto\n  - mantiene l'API `discoverSkillsFromDir({ dir, source })` come adattatore sottile su `scanSkillsFromDir`\n- Le competenze delle directory personalizzate vengono unite dopo le competenze dei provider e seguono lo stesso comportamento di collisione\n\n## Comportamento di utilizzo del runtime\n\n### Esposizione nel prompt di sistema\n\nLa costruzione del prompt di sistema (`src/system-prompt.ts`) utilizza le competenze rilevate come segue:\n\n- se lo strumento `read` è disponibile:\n  - include la lista delle competenze rilevate nel prompt\n- altrimenti:\n  - omette la lista rilevata\n\nI sottoagenti dello strumento Task ricevono la lista delle competenze rilevate/fornite della sessione tramite la normale creazione della sessione; non esiste un override di blocco delle competenze per attività.\n\n### Comandi interattivi `/skill:<name>`\n\nSe `skills.enableSkillCommands` è true, la modalità interattiva registra un comando slash per ogni competenza rilevata.\n\nComportamento di `/skill:<name> [args]`:\n\n- legge il file della competenza direttamente da `filePath`\n- rimuove il frontmatter\n- inietta il corpo della competenza come messaggio personalizzato di follow-up\n- aggiunge metadati (`Skill: <path>`, `User: <args>` opzionale)\n\n## Comportamento degli URL `skill://`\n\n`src/internal-urls/skill-protocol.ts` supporta:\n\n- `skill://<name>` → risolve nel `SKILL.md` di quella competenza\n- `skill://<name>/<relative-path>` → risolve all'interno della directory di quella competenza\n\n```text\nskill:// URL resolution\n\nskill://pdf\n  -> <pdf-base>/SKILL.md\n\nskill://pdf/references/tables.md\n  -> <pdf-base>/references/tables.md\n\nGuards:\n- reject absolute paths\n- reject `..` traversal\n- reject any resolved path escaping <pdf-base>\n```\n\nDettagli di risoluzione:\n\n- il nome della competenza deve corrispondere esattamente\n- i percorsi relativi vengono decodificati dall'URL\n- i percorsi assoluti vengono rifiutati\n- l'attraversamento del percorso (`..`) viene rifiutato\n- il percorso risolto deve rimanere all'interno di `baseDir`\n- i file mancanti restituiscono un errore esplicito `File not found`\n\nTipo di contenuto:\n\n- `.md` => `text/markdown`\n- tutto il resto => `text/plain`\n\nNon viene eseguita alcuna ricerca alternativa per gli asset mancanti.\n\n## Competenze vs XCSH.md, comandi, strumenti, hook\n\n### Competenze vs XCSH.md\n\n- **Competenze**: pacchetti di capacità denominati e facoltativi, selezionati in base al contesto dell'attività o richiesti esplicitamente\n- **XCSH.md/file di contesto**: file di istruzioni persistenti caricati come capacità di file di contesto e uniti in base alle regole di livello/profondità\n\n`src/discovery/agents-md.ts` naviga specificamente nelle directory antenate da `cwd` per rilevare file `XCSH.md` standalone (fino a una profondità di 20), escludendo i segmenti di directory nascosti.\n\n### Competenze vs comandi slash\n\n- **Competenze**: contenuto di conoscenza/flusso di lavoro leggibile dal modello\n- **Comandi slash**: punti di ingresso dei comandi invocati dall'utente\n- `/skill:<name>` è un wrapper di convenienza che inietta il testo della competenza; non modifica la semantica del rilevamento delle competenze\n\n### Competenze vs strumenti personalizzati\n\n- **Competenze**: contenuto di documentazione/flusso di lavoro caricato tramite il contesto del prompt e `read`\n- **Strumenti personalizzati**: API di strumenti eseguibili richiamabili dal modello con schemi ed effetti collaterali del runtime\n\n### Competenze vs hook\n\n- **Competenze**: contenuto passivo\n- **Hook**: intercettori del runtime guidati dagli eventi che possono bloccare/modificare il comportamento durante l'esecuzione\n\n## Guida pratica alla creazione legata alla logica di rilevamento\n\n- Inserire ogni competenza nella propria directory: `<skills-root>/<skill-name>/SKILL.md`\n- Includere sempre il frontmatter esplicito `name` e `description`\n- Mantenere gli asset di riferimento sotto la stessa directory della competenza e accedervi con `skill://<name>/...`\n- Per la tassonomia annidata (`team/domain/skill`), puntare `skills.customDirectories` alla directory padre annidata; la scansione stessa rimane non ricorsiva\n- Evitare nomi di competenze duplicati tra le origini; vince la prima corrispondenza per precedenza del provider\n",
	"it/index.md": "---\ntitle: Documentazione xcsh\ndescription: >-\n  CLI di sviluppo basata su AI con agente di codifica TypeScript e livello\n  nativo Rust per sessioni a lunga durata, supporto MCP e packaging\n  multipiattaforma.\nsidebar:\n  order: 0\n  label: Panoramica\ni18n:\n  sourceHash: b9288f42bf46\n  translator: machine\n---\n\nxcsh è una CLI di sviluppo basata su AI con un agente di codifica TypeScript e un\nlivello nativo Rust (`pi-natives`). Estende la linea open-source\n[`badlogic/pi-mono`](https://github.com/badlogic/pi-mono) con un runtime\nrinforzato, sessioni a lunga durata con navigazione ad albero e compattazione,\nuno strumento Python IPython, supporto MCP completo, un sistema di skills e\npackaging multipiattaforma per Linux, macOS e Windows.\n\n## Da dove iniziare\n\n- **[Contesti F5 XC](/runtime-tools/context-command)** — connessione ai tenant F5 Distributed Cloud.\n  Creazione di contesti, passaggio tra di essi, gestione di namespace e credenziali.\n- **Configurazione** — come xcsh individua, risolve e stratifica la configurazione.\n- **Runtime e Strumenti** — i runtime degli strumenti bash / notebook / resolve e la\n  superficie dei comandi slash.\n- **Sessioni** — log di voci in append-only, navigazione ad albero, compattazione e il\n  sistema di memoria autonomo.\n- **Natives (Rust)** — architettura dell'addon N-API `pi-natives` che\n  alimenta shell / PTY / media / ricerca.\n- **MCP** — configurazione, dettagli del protocollo, ciclo di vita del runtime e come\n  creare server e strumenti.\n- **Estensioni, Skills e Plugin** — creazione, caricamento, regole di matching, il\n  marketplace e l'installer dei plugin.\n- **Provider e Modelli** — configurazione dei modelli, dettagli dello streaming e il\n  runtime Python / IPython.\n- **TUI** — temi, il comando `/tree` e hook di integrazione per\n  estensioni e strumenti personalizzati.\n\n## Come è organizzato questo set di documentazione\n\nOgni gruppo di primo livello nella barra laterale corrisponde a un sottosistema dell'agente. All'interno\ndi un gruppo, le pagine procedono da \"panoramica\" a \"dettagli interni\", così da poter interrompere la lettura\nquando si ha abbastanza contesto per il compito in questione.\n",
	"it/mcp/mcp-config.md": "---\ntitle: Configurazione MCP\ndescription: >-\n  Configurazione, validazione e gestione dei server MCP per il runtime\n  dell'agente di codifica.\nsidebar:\n  order: 1\n  label: Configurazione\ni18n:\n  sourceHash: ef8b49458ce9\n  translator: machine\n---\n\n# Configurazione MCP in OMP\n\nQuesta guida spiega come aggiungere, modificare e validare i server MCP per l'agente di codifica OMP.\n\nFonti di riferimento nel codice:\n\n- Tipi di configurazione runtime: `packages/coding-agent/src/mcp/types.ts`\n- Writer della configurazione: `packages/coding-agent/src/mcp/config-writer.ts`\n- Loader + validazione: `packages/coding-agent/src/mcp/config.ts`\n- Discovery standalone di `mcp.json`: `packages/coding-agent/src/discovery/mcp-json.ts`\n- Schema: `packages/coding-agent/src/config/mcp-schema.json`\n\n## Posizioni di configurazione preferite\n\nOMP può scoprire i server MCP da molteplici strumenti (`.claude/`, `.cursor/`, `.vscode/`, `opencode.json` e altri), ma per la configurazione nativa di OMP è consigliabile utilizzare uno di questi file:\n\n- Progetto: `.xcsh/mcp.json`\n- Utente: `~/.xcsh/mcp.json`\n\nOMP accetta anche file standalone di fallback nella radice del progetto:\n\n- `mcp.json`\n- `.mcp.json`\n\nUtilizzare `.xcsh/mcp.json` quando si desidera che OMP gestisca la configurazione. Utilizzare `mcp.json` / `.mcp.json` nella radice solo quando si desidera un file di fallback portabile che anche altri client MCP possano leggere.\n\n## Aggiungere un riferimento allo schema\n\nAggiungere questa riga all'inizio del file per l'autocompletamento e la validazione nell'editor:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {}\n}\n```\n\nOMP ora scrive questo automaticamente quando `/mcp add`, `/mcp enable`, `/mcp disable`, `/mcp reauth` o altri flussi di scrittura della configurazione creano o aggiornano un file MCP gestito da OMP.\n\n## Struttura del file\n\nOMP supporta questa struttura di primo livello:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"server-name\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"some-mcp-server\"]\n    }\n  },\n  \"disabledServers\": [\"server-name\"]\n}\n```\n\nChiavi di primo livello:\n\n- `$schema` — URL opzionale dello JSON Schema per gli strumenti\n- `mcpServers` — mappa del nome del server alla configurazione del server\n- `disabledServers` — denylist a livello utente utilizzata per disattivare i server scoperti per nome\n\nI nomi dei server devono corrispondere a `^[a-zA-Z0-9_.-]{1,100}$`.\n\n## Campi del server supportati\n\nCampi condivisi per ogni trasporto:\n\n- `enabled?: boolean` — salta questo server quando è `false`\n- `timeout?: number` — timeout di connessione in millisecondi\n- `auth?: { ... }` — metadati di autenticazione utilizzati da OMP per i flussi OAuth/API-key\n- `oauth?: { ... }` — impostazioni esplicite del client OAuth utilizzate durante l'autenticazione/riautenticazione\n\n### Trasporto `stdio`\n\n`stdio` è il valore predefinito quando `type` è omesso.\n\nObbligatorio:\n\n- `command: string`\n\nOpzionale:\n\n- `type?: \"stdio\"`\n- `args?: string[]`\n- `env?: Record<string, string>`\n- `cwd?: string`\n\nEsempio:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/Users/alice/projects\",\n        \"/Users/alice/Documents\"\n      ]\n    }\n  }\n}\n```\n\nQuesto segue il pacchetto ufficiale del server MCP Filesystem (`@modelcontextprotocol/server-filesystem`).\n\n### Trasporto `http`\n\nObbligatorio:\n\n- `type: \"http\"`\n- `url: string`\n\nOpzionale:\n\n- `headers?: Record<string, string>`\n\nEsempio:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\nQuesto corrisponde all'endpoint del server MCP GitHub ospitato da GitHub.\n\n### Trasporto `sse`\n\nObbligatorio:\n\n- `type: \"sse\"`\n- `url: string`\n\nOpzionale:\n\n- `headers?: Record<string, string>`\n\nEsempio:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"legacy-remote\": {\n      \"type\": \"sse\",\n      \"url\": \"https://example.com/mcp/sse\"\n    }\n  }\n}\n```\n\n`sse` è ancora supportato per compatibilità, ma la specifica MCP ora preferisce lo Streamable HTTP (`type: \"http\"`) per i nuovi server.\n\n## Campi di autenticazione\n\nOMP comprende due oggetti relativi all'autenticazione.\n\n### `auth`\n\n```json\n{\n  \"type\": \"oauth\" | \"apikey\",\n  \"credentialId\": \"optional-stored-credential-id\",\n  \"tokenUrl\": \"optional-token-endpoint\",\n  \"clientId\": \"optional-client-id\",\n  \"clientSecret\": \"optional-client-secret\"\n}\n```\n\nUtilizzare questo quando OMP deve ricordare come reidratare le credenziali per un server.\n\n### `oauth`\n\n```json\n{\n  \"clientId\": \"...\",\n  \"clientSecret\": \"...\",\n  \"redirectUri\": \"...\",\n  \"callbackPort\": 3334,\n  \"callbackPath\": \"/oauth/callback\"\n}\n```\n\nUtilizzare questo quando il server MCP richiede impostazioni esplicite del client OAuth.\n\nSlack è l'esempio attuale più chiaro. Il server MCP di Slack è ospitato su `https://mcp.slack.com/mcp`, utilizza Streamable HTTP e richiede OAuth confidenziale con le credenziali client della propria app Slack.\n\nEsempio:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\nEndpoint Slack rilevanti dalla documentazione di Slack:\n\n- Endpoint MCP: `https://mcp.slack.com/mcp`\n- Endpoint di autorizzazione: `https://slack.com/oauth/v2_user/authorize`\n- Endpoint del token: `https://slack.com/api/oauth.v2.user.access`\n\n## Esempi comuni copia-incolla\n\n### Server Filesystem via stdio\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/absolute/path/one\",\n        \"/absolute/path/two\"\n      ]\n    }\n  }\n}\n```\n\n### Server GitHub ospitato via HTTP\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\n### Server GitHub locale via Docker\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"-i\",\n        \"--rm\",\n        \"-e\",\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\",\n        \"ghcr.io/github/github-mcp-server\"\n      ],\n      \"env\": {\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n      }\n    }\n  }\n}\n```\n\nQuesto corrisponde all'immagine Docker locale ufficiale di GitHub `ghcr.io/github/github-mcp-server`.\n\n### Server Slack ospitato via OAuth\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\n## Segreti e risoluzione delle variabili\n\nQuesta è la parte che di solito crea più confusione.\n\n### In `.xcsh/mcp.json` e `~/.xcsh/mcp.json`\n\nPrima che OMP avvii un server o effettui una richiesta HTTP, risolve i valori di `env` e `headers` in questo modo:\n\n1. Se un valore inizia con `!`, OMP lo esegue come comando shell e utilizza lo stdout trimmato.\n2. Altrimenti OMP verifica prima se il valore corrisponde al nome di una variabile d'ambiente.\n3. Se quella variabile d'ambiente non è impostata, OMP utilizza la stringa letteralmente.\n\nEsempi:\n\n```json\n{\n  \"env\": {\n    \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n  },\n  \"headers\": {\n    \"X-MCP-Insiders\": \"true\"\n  }\n}\n```\n\nCiò significa che quanto segue è valido e comodo per i segreti locali:\n\n- `\"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"` → copia dall'ambiente shell corrente\n- `\"Authorization\": \"Bearer hardcoded-token\"` → utilizza il valore letterale\n- `\"Authorization\": \"!printf 'Bearer %s' \\\"$GITHUB_TOKEN\\\"\"` → costruisce l'header da un comando\n\n### In `mcp.json` e `.mcp.json` nella radice\n\nIl loader standalone di fallback espande anche `${VAR}` e `${VAR:-default}` all'interno delle stringhe durante la discovery.\n\nEsempio:\n\n```json\n{\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\",\n      \"headers\": {\n        \"Authorization\": \"Bearer ${GITHUB_TOKEN}\"\n      }\n    }\n  }\n}\n```\n\nSe si desidera il comportamento OMP meno sorprendente, preferire `.xcsh/mcp.json` e utilizzare valori espliciti per env/header.\n\n## `disabledServers`\n\n`disabledServers` è utile principalmente nel file di configurazione utente (`~/.xcsh/mcp.json`) quando un server viene scoperto da un'altra fonte e si desidera che OMP lo ignori senza modificare la configurazione dell'altro strumento.\n\nEsempio:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"disabledServers\": [\"github\", \"slack\"]\n}\n```\n\n## `/mcp add` vs modifica diretta del JSON\n\nUtilizzare `/mcp add` quando si desidera una configurazione guidata.\n\nUtilizzare la modifica diretta del JSON quando:\n\n- si necessita di un'opzione di trasporto o autenticazione che la procedura guidata non richiede ancora\n- si desidera incollare una definizione di server da un altro client MCP\n- si desidera la validazione basata sullo schema nel proprio editor\n\nDopo la modifica, utilizzare:\n\n- `/mcp reload` per riscoprire e riconnettere i server nella sessione corrente\n- `/mcp list` per vedere da quale file di configurazione proviene un server\n- `/mcp test <name>` per testare un singolo server\n\n## Regole di validazione applicate da OMP\n\nDa `validateServerConfig()` in `packages/coding-agent/src/mcp/config.ts`:\n\n- `stdio` richiede `command`\n- `http` e `sse` richiedono `url`\n- un server non può impostare sia `command` che `url`\n- i valori di `type` sconosciuti vengono rifiutati\n\nImplicazioni pratiche:\n\n- Omettere `type` significa `stdio`\n- Se si incolla la configurazione di un server remoto e si dimentica `\"type\": \"http\"`, OMP la tratterà come `stdio` e segnalerà che `command` è mancante\n- `sse` rimane valido per compatibilità, ma i nuovi server ospitati dovrebbero generalmente essere configurati come `http`\n\n## Discovery e precedenza\n\nOMP non unisce le definizioni di server duplicate tra i file. I provider di discovery hanno una priorità, e la definizione con priorità più alta prevale.\n\nIn pratica:\n\n- preferire `.xcsh/mcp.json` o `~/.xcsh/mcp.json` quando si desidera un override specifico per OMP\n- mantenere i nomi dei server univoci tra gli strumenti quando possibile\n- utilizzare `disabledServers` nella configurazione utente quando una configurazione di terze parti continua a reintrodurre un server che non si desidera\n\n## Risoluzione dei problemi\n\n### `Server \"name\": stdio server requires \"command\" field`\n\nProbabilmente è stato omesso `type: \"http\"` su un server remoto.\n\n### `Server \"name\": both \"command\" and \"url\" are set`\n\nScegliere un trasporto. OMP tratta `command` come stdio e `url` come http/sse.\n\n### `/mcp add` ha funzionato ma il server ancora non si connette\n\nIl JSON è valido, ma il server potrebbe comunque essere irraggiungibile. Utilizzare `/mcp test <name>` e verificare se:\n\n- il binario o l'immagine Docker esiste\n- le variabili d'ambiente richieste sono impostate\n- l'URL remoto è raggiungibile\n- il token OAuth o API è valido\n\n### Il server esiste nella configurazione di un altro strumento ma non in OMP\n\nEseguire `/mcp list`. OMP scopre molti file MCP di terze parti, ma il caricamento a livello di progetto può anche essere disabilitato tramite l'impostazione `mcp.enableProjectConfig`.\n\n## Riferimenti\n\n- Specifica dei trasporti MCP: <https://modelcontextprotocol.io/specification/2025-03-26/basic/transports>\n- Pacchetto del server Filesystem: <https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem>\n- Server MCP GitHub: <https://github.com/github/github-mcp-server>\n- Documentazione del server MCP Slack: <https://docs.slack.dev/ai/slack-mcp-server/>\n",
	"it/mcp/mcp-protocol-transports.md": "---\ntitle: Protocollo MCP e meccanismi interni di trasporto\ndescription: >-\n  Implementazione del protocollo MCP con livelli di trasporto stdio, SSE e HTTP\n  streamable.\nsidebar:\n  order: 2\n  label: Protocollo e trasporti\ni18n:\n  sourceHash: 48632064dd00\n  translator: machine\n---\n\n# Protocollo MCP e meccanismi interni di trasporto\n\nQuesto documento descrive come coding-agent implementa la messaggistica MCP JSON-RPC e come le problematiche di protocollo sono separate dalle problematiche di trasporto.\n\n## Ambito\n\nTratta:\n\n- Flusso di richiesta/risposta e notifiche JSON-RPC\n- Correlazione delle richieste e ciclo di vita per i trasporti stdio e HTTP/SSE\n- Comportamento di timeout e cancellazione\n- Propagazione degli errori e gestione dei payload malformati\n- Limiti di selezione del trasporto (`stdio` vs `http`/`sse`)\n- Quali responsabilità di riconnessione/ripetizione sono a livello di trasporto rispetto a livello di manager\n\nNon tratta l'UX di creazione delle estensioni o l'interfaccia utente dei comandi.\n\n## File di implementazione\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/transports/stdio.ts`](../../packages/coding-agent/src/mcp/transports/stdio.ts)\n- [`src/mcp/transports/http.ts`](../../packages/coding-agent/src/mcp/transports/http.ts)\n- [`src/mcp/transports/index.ts`](../../packages/coding-agent/src/mcp/transports/index.ts)\n- [`src/mcp/json-rpc.ts`](../../packages/coding-agent/src/mcp/json-rpc.ts)\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n\n## Confini dei livelli\n\n### Livello di protocollo (JSON-RPC + metodi MCP)\n\n- Le strutture dei messaggi sono definite in `types.ts` (`JsonRpcRequest`, `JsonRpcNotification`, `JsonRpcResponse`, `JsonRpcMessage`).\n- La logica del client MCP (`client.ts`) determina l'ordine dei metodi e l'handshake di sessione:\n  1. Richiesta `initialize`\n  2. Notifica `notifications/initialized`\n  3. Chiamate a metodi come `tools/list`, `tools/call`\n\n### Livello di trasporto (`MCPTransport`)\n\n`MCPTransport` astrae la consegna e il ciclo di vita:\n\n- `request(method, params, options?) -> Promise<T>`\n- `notify(method, params?) -> Promise<void>`\n- `close()`\n- `connected`\n- callback opzionali: `onClose`, `onError`, `onNotification`\n\nLe implementazioni di trasporto gestiscono il framing e i dettagli di I/O:\n\n- `StdioTransport`: JSON delimitato da newline su stdio del sottoprocesso\n- `HttpTransport`: JSON-RPC su HTTP POST, con risposte/ascolto SSE opzionali\n\n### Avvertenza importante attuale\n\nLe callback di trasporto (`onClose`, `onError`, `onNotification`) sono implementate, ma i flussi correnti di `MCPClient`/`MCPManager` non collegano la logica di riconnessione a queste callback. Le notifiche vengono consumate solo se il chiamante registra i relativi handler.\n\n## Selezione del trasporto\n\n`client.ts:createTransport()` sceglie il trasporto dalla configurazione:\n\n- `type` omesso o `\"stdio\"` -> `createStdioTransport`\n- `\"http\"` o `\"sse\"` -> `createHttpTransport`\n\n`\"sse\"` è trattato come una variante del trasporto HTTP (stessa classe), non come un'implementazione di trasporto separata.\n\n## Flusso dei messaggi JSON-RPC e correlazione\n\n## ID di richiesta\n\nOgni trasporto genera ID per richiesta (`Math.random` + stringa timestamp). Gli ID sono token di correlazione locali al trasporto.\n\n## Percorso di correlazione stdio\n\n- La richiesta in uscita viene serializzata come un oggetto JSON + `\\n`.\n- `#pendingRequests: Map<id, {resolve,reject}>` memorizza le richieste in corso.\n- Il ciclo di lettura analizza il JSONL dallo stdout e chiama `#handleMessage`.\n- Se il messaggio in entrata ha un `id` corrispondente, la richiesta viene risolta/rifiutata.\n- Se il messaggio in entrata ha `method` e nessun `id`, viene trattato come notifica e inviato a `onNotification`.\n\nGli ID sconosciuti vengono ignorati (nessun rifiuto, nessuna callback di errore).\n\n## Percorso di correlazione HTTP\n\n- La richiesta in uscita è un HTTP `POST` con body JSON e `id` generato.\n- Percorso di risposta non SSE: analizza una risposta JSON-RPC e restituisce `result`/lancia un'eccezione su `error`.\n- Percorso di risposta SSE (`Content-Type: text/event-stream`): trasmette eventi in streaming, restituisce il primo messaggio il cui `id` corrisponde all'ID di richiesta atteso e contiene `result` o `error`.\n- I messaggi SSE con `method` e senza `id` vengono trattati come notifiche.\n\nSe lo stream SSE termina prima della risposta corrispondente, la richiesta fallisce con `No response received for request ID ...`.\n\n## Notifiche\n\nIl client emette notifiche JSON-RPC tramite `transport.notify(...)`.\n\n- Stdio: scrive il frame di notifica su stdin (`jsonrpc`, `method`, `params` opzionali) più newline.\n- HTTP: invia il body POST senza `id`; il successo accetta `2xx` o `202 Accepted`.\n\nLe notifiche avviate dal server vengono esposte solo attraverso `onNotification` del trasporto; non esiste un sottoscrittore globale predefinito nel manager/client.\n\n## Meccanismi interni del trasporto stdio\n\n## Ciclo di vita e transizioni di stato\n\n- Iniziale: `connected=false`, `process=null`, mappa pending vuota\n- `connect()`:\n  - avvia il sottoprocesso con comando/args/env/cwd configurati\n  - contrassegna come connesso\n  - avvia il ciclo di lettura stdout (`readJsonl`)\n  - avvia il ciclo stderr (legge/scarta; attualmente silenzioso)\n- `close()`:\n  - contrassegna come disconnesso\n  - rifiuta tutte le richieste pending (`Transport closed`)\n  - termina il sottoprocesso\n  - attende la chiusura del ciclo di lettura\n  - emette `onClose`\n\nSe il ciclo di lettura termina inaspettatamente, `finally` attiva `#handleClose()` che esegue lo stesso rifiuto delle richieste pending e la callback di chiusura.\n\n## Timeout e cancellazione\n\nPer ogni richiesta:\n\n- il timeout predefinito è `config.timeout ?? 30000`\n- `AbortSignal` opzionale dal chiamante\n- abort e timeout rifiutano entrambi la promise pending e puliscono la voce nella mappa\n\nLa cancellazione è solo locale: il trasporto non invia notifiche di cancellazione a livello di protocollo al server.\n\n## Gestione dei payload malformati\n\nNel ciclo di lettura:\n\n- ogni riga JSONL analizzata viene passata a `#handleMessage` in un blocco `try/catch`\n- le eccezioni nella gestione dei messaggi malformati/non validi vengono scartate (commento `Skip malformed lines`)\n- il ciclo continua, quindi un messaggio errato non interrompe la connessione\n\nSe il parser dello stream sottostante lancia un'eccezione, viene invocato `onError` (quando ancora connesso), poi la connessione si chiude.\n\n## Comportamento in caso di disconnessione/errore\n\nQuando il processo termina o lo stream si chiude:\n\n- tutte le richieste in corso vengono rifiutate con `Transport closed`\n- nessun riavvio o riconnessione automatica\n- i livelli superiori devono riconnettersi creando un nuovo trasporto\n\n## Note su backpressure/streaming\n\n- Le scritture in uscita utilizzano `stdin.write()` + `flush()` senza attendere la semantica di drain.\n- Non esiste una gestione esplicita della coda o del high-watermark nel trasporto.\n- L'elaborazione in entrata è guidata dallo stream (`for await` su `readJsonl`), un messaggio analizzato alla volta.\n\n## Meccanismi interni del trasporto HTTP/SSE\n\n## Ciclo di vita e semantica della connessione\n\nIl trasporto HTTP ha uno stato di connessione logico, ma il percorso delle richieste è stateless per ogni chiamata HTTP:\n\n- `connect()` imposta `connected=true` (nessun handshake socket/sessione)\n- tracciamento opzionale della sessione server tramite header `Mcp-Session-Id`\n- `close()` invia opzionalmente `DELETE` con `Mcp-Session-Id`, interrompe il listener SSE, emette `onClose`\n\nQuindi `connected` significa \"trasporto utilizzabile\", non \"stream persistente stabilito\".\n\n## Comportamento dell'header di sessione\n\n- Alla risposta POST, se l'header `Mcp-Session-Id` è presente, il trasporto lo memorizza.\n- Le richieste/notifiche successive includono `Mcp-Session-Id`.\n- `close()` tenta di terminare la sessione server con HTTP DELETE; i fallimenti di terminazione vengono ignorati.\n\n## Timeout e cancellazione\n\nPer `request()` e `notify()`:\n\n- il timeout utilizza `AbortController` (`config.timeout ?? 30000`)\n- il segnale esterno, se fornito, viene unito tramite `AbortSignal.any([...])`\n- la gestione di AbortError distingue l'abort del chiamante dal timeout\n\nErrori generati:\n\n- timeout: `Request timeout after ...ms` (o `SSE response timeout ...`, `Notify timeout ...`)\n- abort del chiamante: l'AbortError originale viene rilancato quando il segnale esterno è già abortito\n\n## Propagazione degli errori HTTP\n\nSu risposta non OK:\n\n- il testo della risposta è incluso nell'errore generato (`HTTP <status>: <text>`)\n- se presenti, gli hint di autenticazione da `WWW-Authenticate` e `Mcp-Auth-Server` vengono aggiunti\n\nSu oggetto di errore JSON-RPC:\n\n- lancia `MCP error <code>: <message>`\n\nIl fallimento del parsing del body JSON (`response.json()`) si propaga come eccezione di analisi.\n\n## Comportamento SSE e modalità\n\nEsistono due percorsi SSE:\n\n1. **Risposta SSE per richiesta** (`#parseSSEResponse`)\n   - utilizzato quando il content type della risposta POST è `text/event-stream`\n   - consuma lo stream fino a trovare l'id di risposta corrispondente\n   - può elaborare notifiche interleaved durante lo stesso stream\n\n2. **Listener SSE in background** (`startSSEListener()`)\n   - listener GET opzionale per le notifiche avviate dal server\n   - attualmente non avviato automaticamente da MCP manager/client\n   - se GET restituisce `405`, il listener si disabilita silenziosamente (il server non supporta questa modalità)\n\n## Gestione di payload malformati e disconnessione\n\nGli errori di parsing JSON SSE emergono da `readSseJson` e rifiutano la richiesta/il listener.\n\n- Gli errori di parsing SSE delle richieste rifiutano la richiesta attiva.\n- Gli errori del listener in background attivano `onError` (eccetto AbortError).\n- Nessuna riconnessione automatica per il listener in background.\n\n## Utilità `json-rpc.ts` vs astrazione di trasporto\n\n`src/mcp/json-rpc.ts` fornisce gli helper `callMCP()` e `parseSSE()` per chiamate HTTP MCP dirette (utilizzate dall'integrazione Exa), non l'astrazione `MCPTransport` usata da `MCPClient`/`MCPManager`.\n\nDifferenze rilevanti rispetto a `HttpTransport`:\n\n- analizza prima l'intero testo della risposta, poi estrae la prima riga `data:` (`parseSSE`), con fallback JSON\n- nessuna gestione del timeout di richiesta, nessuna API di abort, nessuna gestione di session-id, nessun ciclo di vita del trasporto\n- restituisce l'oggetto envelope JSON-RPC grezzo\n\nQuesto percorso è leggero ma meno robusto rispetto all'implementazione completa del trasporto.\n\n## Responsabilità di ripetizione/riconnessione\n\n## A livello di trasporto\n\nLe implementazioni di trasporto attuali **non**:\n\n- ripetono le richieste fallite\n- si riconnettono dopo la chiusura del processo stdio\n- riconnettono i listener SSE\n- reinviano le richieste in corso dopo la disconnessione\n\nFalliscono rapidamente e propagano gli errori.\n\n## A livello di manager/client\n\n`MCPManager` gestisce l'orchestrazione di discovery/connessione iniziale e può riconnettersi solo eseguendo nuovamente i flussi di connessione (`connectToServer`/percorsi `discoverAndConnect`). Non ripristina automaticamente un trasporto già connesso in caso di errori a runtime tramite le callback.\n\n`MCPManager` ha un comportamento di fallback all'avvio per i server lenti (strumenti differiti dalla cache), ma si tratta di un fallback sulla disponibilità degli strumenti, non di un ripetizione del trasporto.\n\n## Riepilogo degli scenari di errore\n\n- **Riga del messaggio stdio malformata**: scartata; lo stream continua.\n- **Stream/processo stdio termina**: il trasporto si chiude; le richieste pending vengono rifiutate con `Transport closed`.\n- **HTTP non-2xx**: la richiesta/notifica genera un errore HTTP.\n- **Risposta JSON non valida**: l'eccezione di parsing viene propagata.\n- **SSE termina senza id corrispondente**: la richiesta fallisce con `No response received for request ID ...`.\n- **Timeout**: errore di timeout specifico del trasporto.\n- **Abort del chiamante**: AbortError/motivo propagato dal segnale del chiamante.\n\n## Regola pratica sui confini\n\nSe la problematica riguarda la struttura dei messaggi, la correlazione degli id o l'ordine dei metodi MCP, appartiene alla logica di protocollo/client.\n\nSe la problematica riguarda il framing (JSONL vs HTTP/SSE), il parsing dello stream, il ciclo di vita di fetch/spawn, i clock di timeout o la chiusura della connessione, appartiene all'implementazione del trasporto.\n",
	"it/mcp/mcp-runtime-lifecycle.md": "---\ntitle: Ciclo di vita del runtime MCP\ndescription: >-\n  Ciclo di vita del processo server MCP dall'inizializzazione alla registrazione\n  degli strumenti, monitoraggio dello stato e arresto.\nsidebar:\n  order: 3\n  label: Ciclo di vita del runtime\ni18n:\n  sourceHash: d04cefaf38f8\n  translator: machine\n---\n\n# Ciclo di vita del runtime MCP\n\nQuesto documento descrive come i server MCP vengono scoperti, connessi, esposti come strumenti, aggiornati e terminati nel runtime del coding-agent.\n\n## Panoramica del ciclo di vita\n\n1. **L'avvio dell'SDK** chiama `discoverAndLoadMCPTools()` (a meno che MCP non sia disabilitato).\n2. **La discovery** (`loadAllMCPConfigs`) risolve le configurazioni dei server MCP dalle sorgenti di capability, filtra le voci disabilitate/progetto/Exa e preserva i metadati della sorgente.\n3. **La fase di connessione del Manager** (`MCPManager.connectServers`) avvia la connessione per server + `tools/list` in parallelo.\n4. **Il gate di avvio rapido** attende fino a 250ms, poi può restituire:\n   - `MCPTool` completamente caricati,\n   - errori per server,\n   - o `DeferredMCPTool` dalla cache per i server ancora in attesa.\n5. **Il wiring dell'SDK** unisce gli strumenti MCP nel registro degli strumenti del runtime per la sessione.\n6. **La sessione attiva** può aggiornare gli strumenti MCP tramite i flussi `/mcp` (`disconnectAll` + ri-discovery + `session.refreshMCPTools`).\n7. **La terminazione** avviene quando i chiamanti invocano `disconnectServer`/`disconnectAll`; il manager cancella anche le registrazioni degli strumenti MCP per i server disconnessi.\n\n## Fase di discovery e caricamento\n\n### Percorso di ingresso dall'SDK\n\n`createAgentSession()` in `src/sdk.ts` esegue l'avvio MCP quando `enableMCP` è true (predefinito):\n\n- chiama `discoverAndLoadMCPTools(cwd, { ... })`,\n- passa `authStorage`, lo storage della cache e l'impostazione `mcp.enableProjectConfig`,\n- imposta sempre `filterExa: true`,\n- registra nei log gli errori di caricamento/connessione per server,\n- memorizza il manager restituito in `toolSession.mcpManager` e nel risultato della sessione.\n\nSe `enableMCP` è false, la discovery MCP viene completamente saltata.\n\n### Discovery e filtraggio della configurazione\n\n`loadAllMCPConfigs()` (`src/mcp/config.ts`) carica gli elementi canonici del server MCP attraverso la discovery delle capability, poi li converte nella `MCPServerConfig` legacy.\n\nComportamento del filtraggio:\n\n- `enableProjectConfig: false` rimuove le voci a livello di progetto (`_source.level === \"project\"`).\n- I server con `enabled: false` vengono saltati prima dei tentativi di connessione.\n- I server Exa vengono filtrati per impostazione predefinita e le chiavi API vengono estratte per l'integrazione nativa dello strumento Exa.\n\nIl risultato include sia `configs` che `sources` (metadati usati successivamente per l'etichettatura del provider).\n\n### Comportamento in caso di errore a livello di discovery\n\n`discoverAndLoadMCPTools()` distingue due classi di errore:\n\n- **Errore grave della discovery** (eccezione da `manager.discoverAndConnect`, tipicamente dalla discovery della configurazione): restituisce un set di strumenti vuoto e un errore sintetico `{ path: \".mcp.json\", error }`.\n- **Errore di runtime/connessione per server**: il manager restituisce un successo parziale con la mappa `errors`; gli altri server continuano.\n\nQuindi l'avvio non fa fallire l'intera sessione dell'agente quando singoli server MCP falliscono.\n\n## Modello di stato del Manager\n\n`MCPManager` traccia il ciclo di vita del runtime con registri separati:\n\n- `#connections: Map<string, MCPServerConnection>` — server completamente connessi.\n- `#pendingConnections: Map<string, Promise<MCPServerConnection>>` — handshake in corso.\n- `#pendingToolLoads: Map<string, Promise<{ connection, serverTools }>>` — connessi ma strumenti ancora in caricamento.\n- `#tools: CustomTool[]` — vista corrente degli strumenti MCP esposta ai chiamanti.\n- `#sources: Map<string, SourceMeta>` — metadati del provider/sorgente anche prima del completamento della connessione.\n\n`getConnectionStatus(name)` deriva lo stato da queste mappe:\n\n- `connected` se presente in `#connections`,\n- `connecting` se in connessione pendente o caricamento strumenti pendente,\n- `disconnected` altrimenti.\n\n## Stabilimento della connessione e tempistica di avvio\n\n## Pipeline di connessione per server\n\nPer ogni server scoperto in `connectServers()`:\n\n1. memorizzare/aggiornare i metadati della sorgente,\n2. saltare se già connesso/pendente,\n3. validare i campi di trasporto (`validateServerConfig`),\n4. risolvere le sostituzioni di auth/shell (`#resolveAuthConfig`),\n5. chiamare `connectToServer(name, resolvedConfig)`,\n6. chiamare `listTools(connection)`,\n7. mettere in cache le definizioni degli strumenti (`MCPToolCache.set`) in modalità best-effort.\n\nComportamento di `connectToServer()` (`src/mcp/client.ts`):\n\n- crea un trasporto stdio o HTTP/SSE,\n- esegue `initialize` MCP + `notifications/initialized`,\n- utilizza un timeout (`config.timeout` o 30s predefinito),\n- chiude il trasporto in caso di errore di inizializzazione.\n\n### Gate di avvio rapido + fallback differito\n\n`connectServers()` attende una gara tra:\n\n- tutti i task di connessione/caricamento strumenti completati, e\n- `STARTUP_TIMEOUT_MS = 250`.\n\nDopo 250ms:\n\n- i task completati con successo diventano `MCPTool` attivi,\n- i task rifiutati producono errori per server,\n- i task ancora pendenti:\n  - usano le definizioni degli strumenti dalla cache se disponibili (`MCPToolCache.get`) per creare `DeferredMCPTool`,\n  - altrimenti attendono fino al completamento di quei task pendenti.\n\nQuesto è un modello di avvio ibrido: ritorno rapido quando la cache è disponibile, attesa per correttezza quando la cache non è disponibile.\n\n### Comportamento di completamento in background\n\nOgni `toolsPromise` pendente ha anche una continuazione in background che alla fine:\n\n- sostituisce la porzione di strumenti di quel server nello stato del manager tramite `#replaceServerTools`,\n- scrive la cache,\n- registra nei log gli errori tardivi solo dopo l'avvio (`allowBackgroundLogging`).\n\n## Esposizione degli strumenti e disponibilità nella sessione attiva\n\n### Registrazione all'avvio\n\n`discoverAndLoadMCPTools()` converte gli strumenti del manager in `LoadedCustomTool[]` e decora i percorsi (`mcp:<server> via <providerName>` quando noto).\n\n`createAgentSession()` poi inserisce questi strumenti in `customTools`, che vengono wrappati e aggiunti al registro degli strumenti del runtime con nomi come `mcp_<server>_<tool>`.\n\n### Chiamate agli strumenti\n\n- `MCPTool` chiama gli strumenti attraverso una `MCPServerConnection` già connessa.\n- `DeferredMCPTool` attende `waitForConnection(server)` prima di chiamare; questo permette agli strumenti dalla cache di esistere prima che la connessione sia pronta.\n\nEntrambi restituiscono output strutturato dello strumento e convertono gli errori di trasporto/strumento in contenuto dello strumento `MCP error: ...` (l'abort rimane abort).\n\n## Percorsi di aggiornamento/ricaricamento (avvio vs ricaricamento dal vivo)\n\n### Percorso di avvio iniziale\n\n- discovery/caricamento una tantum in `sdk.ts`,\n- gli strumenti vengono registrati nel registro degli strumenti della sessione iniziale.\n\n### Percorso di ricaricamento interattivo\n\nIl percorso `/mcp reload` (`src/modes/controllers/mcp-command-controller.ts`) esegue:\n\n1. `mcpManager.disconnectAll()`,\n2. `mcpManager.discoverAndConnect()`,\n3. `session.refreshMCPTools(mcpManager.getTools())`.\n\n`session.refreshMCPTools()` (`src/session/agent-session.ts`) rimuove tutti gli strumenti `mcp_`, ri-wrappa gli ultimi strumenti MCP e ri-attiva il set di strumenti in modo che le modifiche MCP si applichino senza riavviare la sessione.\n\nEsiste anche un percorso di follow-up per le connessioni tardive: dopo aver atteso un server specifico, se lo stato diventa `connected`, ri-esegue `session.refreshMCPTools(...)` in modo che gli strumenti appena disponibili vengano riassociati nella sessione.\n\n## Salute, riconnessione e comportamento in caso di errore parziale\n\nIl comportamento attuale del runtime è intenzionalmente minimale:\n\n- **Nessun monitor autonomo della salute** nel manager/client.\n- **Nessun ciclo di riconnessione automatica** quando un trasporto si interrompe.\n- Il manager non si iscrive a `onClose`/`onError` del trasporto; lo stato è guidato dal registro.\n- La riconnessione è esplicita: flusso di ricaricamento o invocazione diretta di `connectServers()`.\n\nOperativamente:\n\n- il fallimento di un server non rimuove gli strumenti dai server sani,\n- gli errori di connessione/lista sono isolati per server,\n- la cache degli strumenti e gli aggiornamenti in background sono best-effort (warning/errori registrati nei log, nessun arresto forzato).\n\n## Semantica della terminazione\n\n### Terminazione a livello di server\n\n`disconnectServer(name)`:\n\n- rimuove le voci pendenti/metadati della sorgente,\n- chiude il trasporto se connesso,\n- rimuove gli strumenti `mcp_` di quel server dallo stato del manager.\n\n### Terminazione globale\n\n`disconnectAll()`:\n\n- chiude tutti i trasporti attivi con `Promise.allSettled`,\n- cancella le mappe pendenti, le sorgenti, le connessioni e la lista degli strumenti del manager.\n\nNel wiring attuale, la terminazione esplicita è usata nei flussi dei comandi MCP (per ricaricamento/rimozione/disabilitazione). Non esiste un hook separato di disposal automatico del manager nel percorso di avvio stesso; i chiamanti sono responsabili di invocare i metodi di disconnessione del manager quando necessitano di un arresto MCP deterministico.\n\n## Modalità di errore e garanzie\n\n| Scenario | Comportamento | Errore grave vs best-effort |\n| --- | --- | --- |\n| La discovery lancia un'eccezione (percorso di caricamento capability/config) | Il loader restituisce strumenti vuoti + errore sintetico `.mcp.json` | Avvio sessione best-effort |\n| Configurazione server non valida | Server saltato con voce di errore di validazione | Best-effort per server |\n| Timeout di connessione/errore di inizializzazione | Errore del server registrato; gli altri continuano | Best-effort per server |\n| `tools/list` ancora pendente all'avvio con cache disponibile | Strumenti differiti restituiti immediatamente | Avvio rapido best-effort |\n| `tools/list` ancora pendente all'avvio senza cache | L'avvio attende il completamento dei pendenti | Attesa forzata per correttezza |\n| Errore tardivo di caricamento strumenti in background | Registrato nei log dopo il gate di avvio | Logging best-effort |\n| Trasporto interrotto a runtime | Nessuna riconnessione automatica; le chiamate future falliscono fino a riconnessione/ricaricamento | Recupero best-effort tramite azione manuale |\n\n## Superficie dell'API pubblica\n\n`src/mcp/index.ts` ri-esporta le API del loader/manager/client per i chiamanti esterni. `src/sdk.ts` espone `discoverMCPServers()` come wrapper di convenienza che restituisce la stessa forma di risultato del loader.\n\n## File di implementazione\n\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts) — facciata del loader, normalizzazione degli errori della discovery, conversione `LoadedCustomTool`.\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts) — registri di stato del ciclo di vita, flusso parallelo di connessione/lista, aggiornamento/disconnessione.\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts) — configurazione del trasporto, handshake di inizializzazione, lista/chiamata/disconnessione.\n- [`src/mcp/index.ts`](../../packages/coding-agent/src/mcp/index.ts) — esportazioni dell'API del modulo MCP.\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — wiring di avvio nel registro sessione/strumenti.\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts) — discovery/filtraggio/validazione della configurazione usata dal manager.\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts) — comportamento runtime di `MCPTool` e `DeferredMCPTool`.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — riassociazione dal vivo `refreshMCPTools`.\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts) — flussi interattivi di ricaricamento/riconnessione.\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — proxy MCP del subagent tramite connessioni del manager padre.\n",
	"it/mcp/mcp-server-tool-authoring.md": "---\ntitle: Creazione di server e strumenti MCP\ndescription: >-\n  Guida alla creazione di server MCP personalizzati e alla registrazione di\n  strumenti per l'agente di codifica.\nsidebar:\n  order: 4\n  label: Creazione di server e strumenti\ni18n:\n  sourceHash: 160e7560ef1f\n  translator: machine\n---\n\n# Creazione di server e strumenti MCP\n\nQuesto documento spiega come le definizioni di server MCP diventano strumenti `mcp_*` richiamabili nell'agente di codifica, e cosa devono aspettarsi gli operatori quando le configurazioni sono non valide, duplicate, disabilitate o protette da autenticazione.\n\n## Architettura in sintesi\n\n```text\nConfig sources (.xcsh/.claude/.cursor/.vscode/mcp.json, mcp.json, etc.)\n  -> discovery providers normalize to canonical MCPServer\n  -> capability loader dedupes by server name (higher provider priority wins)\n  -> loadAllMCPConfigs converts to MCPServerConfig + skips enabled:false\n  -> MCPManager connects/listTools (with auth/header/env resolution)\n  -> MCPTool/DeferredMCPTool bridge exposes tools as mcp_<server>_<tool>\n  -> AgentSession.refreshMCPTools replaces live MCP tools immediately\n```\n\n## 1) Modello di configurazione del server e validazione\n\n`src/mcp/types.ts` definisce la struttura di creazione utilizzata dagli autori di configurazioni MCP e dal runtime:\n\n- `stdio` (predefinito quando manca `type`): richiede `command`, opzionalmente `args`, `env`, `cwd`\n- `http`: richiede `url`, opzionalmente `headers`\n- `sse`: richiede `url`, opzionalmente `headers` (mantenuto per compatibilità)\n- campi condivisi: `enabled`, `timeout`, `auth`\n\n`validateServerConfig()` (`src/mcp/config.ts`) applica le regole fondamentali di trasporto:\n\n- rifiuta le configurazioni che impostano sia `command` che `url`\n- richiede `command` per stdio\n- richiede `url` per http/sse\n- rifiuta valori `type` sconosciuti\n\n`config-writer.ts` applica questa validazione per le operazioni di aggiunta/aggiornamento e verifica anche i nomi dei server:\n\n- non vuoti\n- massimo 100 caratteri\n- solo `[a-zA-Z0-9_.-]`\n\n### Problematiche di trasporto\n\n- L'omissione di `type` implica stdio. Se si intende HTTP/SSE ma si omette `type`, `command` diventa obbligatorio.\n- `sse` è ancora accettato ma trattato internamente come trasporto HTTP (`createHttpTransport`).\n- La validazione è strutturale, non verifica la raggiungibilità: un URL sintatticamente valido può comunque fallire al momento della connessione.\n\n## 2) Scoperta, normalizzazione e precedenza\n\n### Scoperta basata sulle capacità\n\n`loadAllMCPConfigs()` (`src/mcp/config.ts`) carica gli elementi canonici `MCPServer` tramite `loadCapability(mcpCapability.id)`.\n\nIl livello di capacità (`src/capability/index.ts`) quindi:\n\n1. carica i provider in ordine di priorità\n2. deduplica per `server.name` (il primo vince = priorità più alta)\n3. valida gli elementi deduplicati\n\nRisultato: i nomi di server duplicati tra le sorgenti non vengono uniti. Una sola definizione vince; i duplicati con priorità inferiore vengono oscurati.\n\n### `.mcp.json` e file correlati\n\nIl provider di fallback dedicato in `src/discovery/mcp-json.ts` legge `mcp.json` e `.mcp.json` nella root del progetto (bassa priorità).\n\nIn pratica, i server MCP provengono anche da provider con priorità più alta (ad esempio i file nativi `.xcsh/...` e le directory di configurazione specifiche degli strumenti). Linee guida per la creazione:\n\n- Preferire `.xcsh/mcp.json` (progetto) o `~/.xcsh/mcp.json` (utente) per un controllo esplicito.\n- Usare `mcp.json` / `.mcp.json` nella root quando si necessita di compatibilità di fallback.\n- Il riutilizzo dello stesso nome di server in più sorgenti causa un oscuramento per precedenza, non una fusione.\n\n### Comportamento di normalizzazione\n\n`convertToLegacyConfig()` (`src/mcp/config.ts`) mappa il `MCPServer` canonico nel `MCPServerConfig` di runtime.\n\nComportamento chiave:\n\n- il trasporto viene dedotto come `server.transport ?? (command ? \"stdio\" : url ? \"http\" : \"stdio\")`\n- i server disabilitati (`enabled === false`) vengono esclusi prima della connessione\n- i campi opzionali vengono preservati quando presenti\n\n### Espansione delle variabili d'ambiente durante la scoperta\n\n`mcp-json.ts` espande i segnaposto di ambiente nei campi stringa con `expandEnvVarsDeep()`:\n\n- supporta `${VAR}` e `${VAR:-default}`\n- i valori non risolti rimangono come stringhe letterali `${VAR}`\n\n`mcp-json.ts` esegue inoltre verifiche del tipo a runtime per il JSON dell'utente e registra avvisi per valori `enabled`/`timeout` non validi invece di far fallire l'intero file.\n\n## 3) Risoluzione dei valori di autenticazione e runtime\n\n`MCPManager.prepareConfig()`/`#resolveAuthConfig()` (`src/mcp/manager.ts`) è il passaggio finale prima della connessione.\n\n### Iniezione delle credenziali OAuth\n\nSe la configurazione contiene:\n\n```ts\nauth: { type: \"oauth\", credentialId: \"...\" }\n```\n\ne la credenziale esiste nell'archivio di autenticazione:\n\n- `http`/`sse`: inietta l'intestazione `Authorization: Bearer <access_token>`\n- `stdio`: inietta la variabile d'ambiente `OAUTH_ACCESS_TOKEN`\n\nSe la ricerca delle credenziali fallisce, il manager registra un avviso e continua con l'autenticazione non risolta.\n\n### Risoluzione dei valori di intestazione/ambiente\n\nPrima della connessione, il manager risolve ogni valore di intestazione/ambiente tramite `resolveConfigValue()` (`src/config/resolve-config-value.ts`):\n\n- valore che inizia con `!` => esegue il comando shell, usa lo stdout pulito (memorizzato nella cache)\n- altrimenti, tratta il valore prima come nome di variabile d'ambiente (`process.env[name]`), con fallback al valore letterale\n- i valori di comando/ambiente non risolti vengono omessi dalla mappa finale di intestazioni/ambiente\n\nAvvertenza operativa: ciò significa che una chiave di comando/ambiente per il segreto scritta in modo errato può rimuovere silenziosamente quella voce di intestazione/ambiente, producendo errori 401/403 a valle o errori di avvio del server.\n\n## 4) Bridge degli strumenti: MCP -> strumenti richiamabili dall'agente\n\n`src/mcp/tool-bridge.ts` converte le definizioni degli strumenti MCP in `CustomTool`.\n\n### Denominazione e dominio delle collisioni\n\nI nomi degli strumenti vengono generati come:\n\n```text\nmcp_<sanitized_server_name>_<sanitized_tool_name>\n```\n\nRegole:\n\n- conversione in minuscolo\n- i caratteri non conformi a `[a-z_]` diventano `_`\n- i trattini bassi ripetuti vengono compressi\n- il prefisso ridondante `<server>_` nel nome dello strumento viene rimosso una volta\n\nQuesto evita molte collisioni, ma non tutte. Nomi raw diversi possono comunque produrre lo stesso identificatore dopo la sanitizzazione (ad esempio `my-server` e `my.server` producono risultati simili), e l'inserimento nel registro è last-write-wins.\n\n### Mappatura dello schema\n\n`convertSchema()` mantiene lo schema JSON MCP per lo più invariato, ma corregge gli schemi oggetto privi di `properties` aggiungendo `{}` per la compatibilità con i provider.\n\n### Mappatura dell'esecuzione\n\n`MCPTool.execute()` / `DeferredMCPTool.execute()`:\n\n- chiama MCP `tools/call`\n- appiattisce il contenuto MCP in testo visualizzabile\n- restituisce dettagli strutturati (`serverName`, `mcpToolName`, metadati del provider)\n- mappa `isError` segnalato dal server in un risultato testuale `Error: ...`\n- mappa i fallimenti di trasporto/runtime lanciati in `MCP error: ...`\n- preserva la semantica di interruzione traducendo AbortError in `ToolAbortError`\n\n## 5) Ciclo di vita dell'operatore: aggiunta/modifica/rimozione e aggiornamenti in tempo reale\n\nLa modalità interattiva espone `/mcp` in `src/modes/controllers/mcp-command-controller.ts`.\n\nOperazioni supportate:\n\n- `add` (procedura guidata o aggiunta rapida)\n- `remove` / `rm`\n- `enable` / `disable`\n- `test`\n- `reauth` / `unauth`\n- `reload`\n\nLe scritture di configurazione sono atomiche (`writeMCPConfigFile`: file temporaneo + rinomina).\n\nDopo le modifiche, il controller chiama `#reloadMCP()`:\n\n1. `mcpManager.disconnectAll()`\n2. `mcpManager.discoverAndConnect()`\n3. `session.refreshMCPTools(mcpManager.getTools())`\n\n`refreshMCPTools()` sostituisce tutte le voci `mcp_` nel registro e riattiva immediatamente il set più recente di strumenti MCP, quindi le modifiche hanno effetto senza riavviare la sessione.\n\n### Differenze tra modalità\n\n- **Modalità interattiva/TUI**: `/mcp` fornisce un'interfaccia utente in-app (procedura guidata, flusso OAuth, testo dello stato della connessione, ricollegamento immediato al runtime).\n- **Integrazione SDK/headless**: `discoverAndLoadMCPTools()` (`src/mcp/loader.ts`) restituisce gli strumenti caricati e gli errori per server; nessuna interfaccia utente per il comando `/mcp`.\n\n## 6) Superfici di errore visibili all'utente\n\nStringhe di errore comuni che utenti/operatori vedono:\n\n- errori di validazione in aggiunta/aggiornamento:\n  - `Invalid server config: ...`\n  - `Server \"<name>\" already exists in <path>`\n- problemi con gli argomenti di aggiunta rapida:\n  - `Use either --url or -- <command...>, not both.`\n  - `--token requires --url (HTTP/SSE transport).`\n- errori di connessione/test:\n  - `Failed to connect to \"<name>\": <message>`\n  - il testo di aiuto per il timeout suggerisce di aumentare il valore\n  - il testo di aiuto per l'autenticazione per `401/403`\n- flussi di autenticazione/OAuth:\n  - `Authentication required ... OAuth endpoints could not be discovered`\n  - `OAuth flow timed out. Please try again.`\n  - `OAuth authentication failed: ...`\n- utilizzo di un server disabilitato:\n  - `Server \"<name>\" is disabled. Run /mcp enable <name> first.`\n\nIl JSON sorgente non valido durante la scoperta viene generalmente gestito come avvisi/log; i percorsi di config-writer generano errori espliciti.\n\n## 7) Linee guida pratiche per la creazione\n\nPer una creazione robusta di MCP in questa codebase:\n\n1. Mantenere i nomi dei server globalmente univoci in tutte le sorgenti di configurazione compatibili con MCP.\n2. Preferire nomi alfanumerici/con trattino basso per evitare collisioni di nomi sanitizzati nei nomi degli strumenti `mcp_*` generati.\n3. Usare `type` esplicito per evitare impostazioni predefinite stdio accidentali.\n4. Trattare `enabled: false` come disattivazione completa: il server viene omesso dal set di connessione al runtime.\n5. Per le configurazioni OAuth, memorizzare un `credentialId` valido; altrimenti l'iniezione dell'autenticazione viene saltata.\n6. Se si utilizza la risoluzione dei segreti basata su comandi (`!cmd`), verificare che l'output del comando sia stabile e non vuoto.\n\n## File di implementazione\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts)\n- [`src/mcp/config-writer.ts`](../../packages/coding-agent/src/mcp/config-writer.ts)\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts)\n- [`src/discovery/mcp-json.ts`](../../packages/coding-agent/src/discovery/mcp-json.ts)\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/config/resolve-config-value.ts`](../../packages/coding-agent/src/config/resolve-config-value.ts)\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts)\n",
	"it/natives/natives-addon-loader-runtime.md": "---\ntitle: Runtime del Loader dei Natives Addon\ndescription: >-\n  N-API addon loader runtime with platform detection, fallback strategies, and\n  module resolution.\nsidebar:\n  order: 3\n  label: Loader degli addon\ni18n:\n  sourceHash: 743ea3e32c7c\n  translator: machine\n---\n\n# Runtime del Loader dei Natives Addon\n\nQuesto documento approfondisce il livello di caricamento/validazione degli addon in `@f5-sales-demo/pi-natives`: come `native.ts` decide quale file `.node` caricare, quando viene eseguita l'estrazione del payload incorporato e come vengono segnalati gli errori all'avvio.\n\n## File di implementazione\n\n- `packages/natives/src/native.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/package.json`\n\n## Ambito e responsabilità\n\nLe responsabilità del loader/runtime sono intenzionalmente limitate:\n\n- Costruire un elenco di candidati per nomi di file e directory degli addon, basato su piattaforma e CPU.\n- Opzionalmente materializzare un addon incorporato in una directory cache versionata per utente.\n- Tentare i candidati in ordine deterministico.\n- Rifiutare addon obsoleti o incompatibili tramite `validateNative` prima di esporre i binding.\n\nFuori dall'ambito di questo documento: comportamento specifico dei moduli per grep/text/highlight.\n\n## Input di runtime e stato derivato\n\nAll'inizializzazione del modulo (`export const native = loadNative();`), `native.ts` calcola il contesto statico:\n\n- **Tag piattaforma**: ``${process.platform}-${process.arch}`` (ad esempio `darwin-arm64`).\n- **Versione del pacchetto**: da `packages/natives/package.json` (campo `version`).\n- **Directory principali**:\n  - `nativeDir`: locale al pacchetto `packages/natives/native`.\n  - `execDir`: directory contenente `process.execPath`.\n  - `versionedDir`: `<getNativesDir()>/<packageVersion>`.\n  - Fallback `userDataDir`:\n    - Windows: `%LOCALAPPDATA%/xcsh` (oppure `%USERPROFILE%/AppData/Local/xcsh`).\n    - Non-Windows: `~/.local/bin`.\n- **Modalità binario compilato** (`isCompiledBinary`): true se una qualsiasi delle seguenti condizioni è vera:\n  - La variabile d'ambiente `PI_COMPILED` è impostata, oppure\n  - `import.meta.url` contiene marcatori incorporati di Bun (`$bunfs`, `~BUN`, `%7EBUN`).\n- **Override della variante**: `PI_NATIVE_VARIANT` (solo `modern`/`baseline`; valori non validi vengono ignorati).\n- **Variante selezionata**: override esplicito, altrimenti rilevamento AVX2 a runtime su x64 (`modern` se AVX2 disponibile, altrimenti `baseline`).\n\n## Supporto piattaforme e risoluzione dei tag\n\n`SUPPORTED_PLATFORMS` è fissato a:\n\n- `linux-x64`\n- `linux-arm64`\n- `darwin-x64`\n- `darwin-arm64`\n- `win32-x64`\n\nDettagli di comportamento:\n\n- Le piattaforme non supportate non vengono rifiutate anticipatamente.\n- Il loader tenta comunque prima tutti i candidati calcolati.\n- Se nulla viene caricato, viene lanciato un errore esplicito di piattaforma non supportata con l'elenco dei tag supportati.\n\nQuesto preserva diagnostiche utili per i casi quasi corrispondenti, continuando comunque a fallire in modo definitivo per i target realmente non supportati.\n\n## Selezione della variante (`modern` / `baseline` / default)\n\n### Comportamento su x64\n\n1. Se `PI_NATIVE_VARIANT` è `modern` o `baseline`, quel valore ha la precedenza.\n2. Altrimenti rileva il supporto AVX2:\n   - Linux: scansiona `/proc/cpuinfo` cercando `avx2`.\n   - macOS: interroga `sysctl` (`machdep.cpu.leaf7_features`, fallback `machdep.cpu.features`).\n   - Windows: esegue PowerShell `[System.Runtime.Intrinsics.X86.Avx2]::IsSupported`.\n3. Risultato:\n   - AVX2 disponibile -> `modern`\n   - AVX2 non disponibile/non rilevabile -> `baseline`\n\n### Comportamento su non-x64\n\n- Non viene utilizzata alcuna variante; il loader usa il nome file predefinito (`pi_natives.<platform>-<arch>.node`).\n\n### Costruzione del nome file\n\nDato `tag = <platform>-<arch>`:\n\n- Non-x64 o nessuna variante: `pi_natives.<tag>.node`\n- x64 + `modern`: prova in ordine\n  1. `pi_natives.<tag>-modern.node`\n  2. `pi_natives.<tag>-baseline.node` (fallback intenzionale)\n- x64 + `baseline`: solo `pi_natives.<tag>-baseline.node`\n\nL'`addonLabel` utilizzato nei messaggi di errore finali è `<tag>` oppure `<tag> (<variant>)`.\n\n## Costruzione dei percorsi candidati e ordine di fallback\n\n`native.ts` costruisce i pool di candidati prima di qualsiasi chiamata `require(...)`.\n\n### Candidati per il rilascio\n\nCostruiti dall'elenco dei nomi file risolti per variante e cercati in questo ordine:\n\n- **Runtime non compilato**:\n  1. `<nativeDir>/<filename>`\n  2. `<execDir>/<filename>`\n\n- **Runtime compilato** (`PI_COMPILED` o marcatori incorporati di Bun):\n  1. `<versionedDir>/<filename>`\n  2. `<userDataDir>/<filename>`\n  3. `<nativeDir>/<filename>`\n  4. `<execDir>/<filename>`\n\n`dedupedCandidates` rimuove i duplicati preservando l'ordine della prima occorrenza.\n\n### Sequenza finale a runtime\n\nAl momento del caricamento:\n\n1. Il candidato di estrazione incorporato opzionale (se prodotto) viene inserito in testa.\n2. I candidati deduplicati rimanenti vengono provati in ordine.\n3. Il primo candidato che supera sia `require(...)` che `validateNative(...)` vince.\n\n## Ciclo di vita dell'estrazione dell'addon incorporato\n\n`embedded-addon.ts` definisce una struttura del manifesto generato:\n\n- `platformTag`\n- `version`\n- `files[]` dove ogni voce ha `variant`, `filename`, `filePath`\n\nIl valore predefinito attualmente registrato è `embeddedAddon: null`; gli artefatti compilati possono sostituirlo con metadati reali.\n\n### Macchina a stati dell'estrazione\n\nL'estrazione (`maybeExtractEmbeddedAddon`) viene eseguita solo quando tutti i gate sono superati:\n\n1. `isCompiledBinary === true`\n2. `embeddedAddon !== null`\n3. `embeddedAddon.platformTag === platformTag`\n4. `embeddedAddon.version === packageVersion`\n5. Un file incorporato appropriato per la variante viene trovato\n\nLa selezione del file per variante rispecchia l'intento di variante a runtime:\n\n- Non-x64: preferisce `default`, poi il primo file disponibile.\n- x64 + `modern`: preferisce `modern`, fallback a `baseline`.\n- x64 + `baseline`: richiede `baseline`.\n\nComportamento della materializzazione:\n\n1. Assicura che `<versionedDir>` esista (`mkdirSync(..., { recursive: true })`).\n2. Se `<versionedDir>/<selected filename>` esiste già, lo riutilizza (nessuna riscrittura).\n3. Altrimenti legge il `filePath` sorgente incorporato e scrive il file di destinazione.\n4. Restituisce il percorso di destinazione per il tentativo di caricamento a priorità più alta.\n\nIn caso di fallimento, l'estrazione non provoca un crash immediato; aggiunge una voce di errore (fallimento nella creazione della directory o nella scrittura) e il loader procede con il probing normale dei candidati.\n\n## Ciclo di vita e transizioni di stato\n\n```text\nInit\n  -> Calcola piattaforma/versione/variante/elenchi candidati\n  -> (Compilato + manifesto incorporato corrisponde?)\n       sì -> Tenta estrazione incorporata in versionedDir (registra errori, continua)\n       no  -> Salta estrazione\n  -> Per ogni candidato runtime in ordine:\n       require(candidato)\n       -> successo: validateNative\n            -> superato: restituisci binding (PRONTO)\n            -> fallito: registra errore, continua\n       -> fallimento: registra errore, continua\n  -> nessuno caricato:\n       se tag piattaforma non supportato -> lancia Piattaforma non supportata\n       altrimenti -> lancia Caricamento fallito (diagnostiche complete dei percorsi tentati + suggerimenti)\n```\n\n## Controlli contrattuali di `validateNative`\n\n`validateNative(bindings, source)` applica un contratto basato esclusivamente su funzioni su `NativeBindings` all'avvio.\n\nMeccanismo:\n\n- Per ogni nome di export richiesto, verifica `typeof bindings[name] === \"function\"`.\n- I nomi mancanti vengono aggregati.\n- Se qualcuno è mancante, il loader lancia:\n  - percorso dell'addon sorgente,\n  - elenco degli export mancanti,\n  - suggerimento del comando di rebuild.\n\nQuesto è un gate di compatibilità rigido contro binari obsoleti, build parziali e derive di simboli/nomi.\n\n### Mappatura API JS ↔ export nativi (gate di validazione)\n\n| Nome binding JS verificato in `validateNative` | Nome export nativo atteso |\n| --- | --- |\n| `grep` | `grep` |\n| `glob` | `glob` |\n| `highlightCode` | `highlightCode` |\n| `executeShell` | `executeShell` |\n| `PtySession` | `PtySession` |\n| `Shell` | `Shell` |\n| `visibleWidth` | `visibleWidth` |\n| `getSystemInfo` | `getSystemInfo` |\n| `getWorkProfile` | `getWorkProfile` |\n| `invalidateFsScanCache` | `invalidateFsScanCache` |\n\nNota: `bindings.ts` dichiara solo il membro base `cancelWork(id)`; i file `types.ts` dei moduli effettuano declaration-merge di simboli aggiuntivi che `validateNative` impone.\n\n## Comportamento in caso di errore e diagnostiche\n\n## Piattaforma non supportata\n\nSe tutti i candidati falliscono e `platformTag` non è in `SUPPORTED_PLATFORMS`, il loader lancia:\n\n- `Unsupported platform: <tag>`\n- Elenco completo delle piattaforme supportate\n- Guida esplicita per la segnalazione del problema\n\n## Sintomi di binario obsoleto / mismatch\n\nSegnale tipico di mismatch obsoleto:\n\n- `Native addon missing exports (<candidate>). Missing: ...`\n\nCause comuni:\n\n- Vecchio binario `.node` da una versione/forma API precedente del pacchetto.\n- Artefatto della variante errata selezionato (per x64).\n- Nuovo export Rust non presente nell'artefatto caricato.\n\nComportamento del loader:\n\n- Registra i fallimenti di export mancanti per ogni candidato.\n- Continua a provare i candidati rimanenti.\n- Se nessun candidato supera la validazione, l'errore finale include ogni percorso tentato con il relativo messaggio di errore.\n\n## Errori all'avvio in modalità binario compilato\n\nIn modalità compilata le diagnostiche finali includono:\n\n- percorsi attesi della cache versionata (`<versionedDir>/<filename>`),\n- rimedio per eliminare il `<versionedDir>` obsoleto e rieseguire,\n- comandi `curl` per il download diretto dalla release per ogni nome file atteso.\n\n## Errori all'avvio in modalità non compilata\n\nIn modalità normale pacchetto/runtime le diagnostiche finali includono:\n\n- suggerimento di reinstallazione (`bun install @f5-sales-demo/pi-natives`),\n- comando di rebuild locale (`bun --cwd=packages/natives run build`),\n- suggerimento opzionale di build della variante x64 (`TARGET_VARIANT=baseline|modern ...`).\n\n## Comportamento a runtime\n\n- Il loader utilizza sempre la catena di candidati per il rilascio.\n- L'impostazione di `PI_DEV` abilita solo le diagnostiche per candidato nella console (`Loaded native addon...` e errori di caricamento).\n",
	"it/natives/natives-architecture.md": "---\ntitle: Architettura dei Nativi\ndescription: >-\n  Architettura degli addon nativi Rust N-API che collega TypeScript e operazioni\n  specifiche della piattaforma.\nsidebar:\n  order: 1\n  label: Architettura\ni18n:\n  sourceHash: d38ed2437bb7\n  translator: machine\n---\n\n# Architettura dei Nativi\n\n`@f5-sales-demo/pi-natives` è uno stack a tre livelli:\n\n1. **Livello wrapper/API TypeScript** espone punti di accesso JS/TS stabili.\n2. **Livello di caricamento/validazione dell'addon** risolve e valida il binario `.node` per il runtime corrente.\n3. **Livello modulo Rust N-API** implementa le primitive critiche per le prestazioni esportate verso JS.\n\nQuesto documento è la base per la documentazione più approfondita a livello di modulo.\n\n## File di implementazione\n\n- `packages/natives/src/index.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `crates/pi-natives/src/lib.rs`\n\n## Livello 1: Livello wrapper/API TypeScript\n\n`packages/natives/src/index.ts` è il barrel pubblico. Raggruppa le esportazioni per dominio funzionale e ri-esporta wrapper tipizzati anziché esporre direttamente i binding N-API grezzi.\n\nGruppi di primo livello attuali:\n\n- **Primitive di ricerca/testo**: `grep`, `glob`, `text`, `highlight`\n- **Primitive di esecuzione/processo/terminale**: `shell`, `pty`, `ps`, `keys`\n- **Primitive di sistema/media/conversione**: `image`, `html`, `clipboard`, `system-info`, `work`\n\n`packages/natives/src/bindings.ts` definisce il contratto di interfaccia base:\n\n- `NativeBindings` inizia con i membri condivisi (`cancelWork(id: number)`)\n- I binding specifici del modulo vengono aggiunti tramite declaration merging dal file `types.ts` di ciascun modulo\n- `Cancellable` standardizza le opzioni di timeout e abort-signal per i wrapper che espongono la cancellazione\n\n**Contratto garantito (lato API):** i consumatori importano da `@f5-sales-demo/pi-natives` e utilizzano wrapper tipizzati.\n\n**Dettaglio implementativo (soggetto a modifiche):** declaration merging e layout interno dei wrapper (`src/<module>/index.ts`, `src/<module>/types.ts`).\n\n## Livello 2: Caricamento e validazione dell'addon\n\n`packages/natives/src/native.ts` gestisce la selezione dell'addon a runtime, l'estrazione opzionale e la validazione delle esportazioni.\n\n### Modello di risoluzione dei candidati\n\n- Il tag della piattaforma è `\"${process.platform}-${process.arch}\"`.\n- I tag attualmente supportati sono:\n  - `linux-x64`\n  - `linux-arm64`\n  - `darwin-x64`\n  - `darwin-arm64`\n  - `win32-x64`\n- x64 può utilizzare varianti CPU:\n  - `modern` (con supporto AVX2)\n  - `baseline` (fallback)\n- Le architetture non-x64 utilizzano il nome file predefinito (senza suffisso di variante).\n\nStrategia dei nomi file:\n\n- Release: `pi_natives.<platform>-<arch>.node`\n- Release con variante x64: `pi_natives.<platform>-<arch>-modern.node` e/o `...-baseline.node`\n- `PI_DEV` abilita la diagnostica del loader ma non modifica i nomi file dell'addon\n\n### Rilevamento della variante specifica per piattaforma\n\nPer x64, la selezione della variante utilizza:\n\n- **Linux**: `/proc/cpuinfo`\n- **macOS**: `sysctl machdep.cpu.leaf7_features` / `machdep.cpu.features`\n- **Windows**: verifica PowerShell per `System.Runtime.Intrinsics.X86.Avx2`\n\n`PI_NATIVE_VARIANT` può forzare esplicitamente `modern` o `baseline`.\n\n### Modello di distribuzione ed estrazione dei binari\n\n`packages/natives/package.json` include sia `src` che `native` nei file pubblicati. La directory `native/` contiene gli artefatti precompilati per la piattaforma.\n\nPer i binari compilati (marcatori `PI_COMPILED` o runtime embedded Bun), il comportamento del loader è:\n\n1. Verificare il percorso cache utente con versione: `<getNativesDir()>/<packageVersion>/...`\n2. Verificare la posizione legacy del binario compilato:\n   - Windows: `%LOCALAPPDATA%/xcsh` (fallback `%USERPROFILE%/AppData/Local/xcsh`)\n   - non-Windows: `~/.local/bin`\n3. Ripiegare sulla directory `native/` del pacchetto e sui candidati nella directory dell'eseguibile\n\nSe è presente un manifesto dell'addon embedded (`embedded-addon.ts` generato da `scripts/embed-native.ts`), `native.ts` può materializzare il binario embedded corrispondente nella directory cache con versione prima del caricamento.\n\n### Validazione e modalità di errore\n\nDopo `require(candidate)`, `validateNative(...)` verifica le esportazioni richieste (ad esempio `grep`, `glob`, `highlightCode`, `PtySession`, `Shell`, `getSystemInfo`, `getWorkProfile`, `invalidateFsScanCache`).\n\nI percorsi di errore sono espliciti:\n\n- **Tag piattaforma non supportato**: lancia un'eccezione con l'elenco delle piattaforme supportate\n- **Nessun candidato caricabile**: lancia un'eccezione con tutti i percorsi tentati e suggerimenti di rimedio\n- **Esportazioni mancanti**: lancia un'eccezione con i nomi esatti mancanti e il comando di rebuild\n- **Errori di estrazione embedded**: registra gli errori di directory/scrittura e li include nella diagnostica finale di caricamento\n\n**Contratto garantito (lato API):** il caricamento dell'addon riesce con un set di binding validato oppure fallisce immediatamente con un messaggio di errore utilizzabile.\n\n**Dettaglio implementativo (soggetto a modifiche):** ordine esatto di ricerca dei candidati e ordinamento del percorso di fallback dei binari compilati.\n\n## Livello 3: Livello modulo Rust N-API\n\n`crates/pi-natives/src/lib.rs` è il modulo Rust di ingresso che dichiara la proprietà dei moduli esportati:\n\n- `clipboard`\n- `fd`\n- `fs_cache`\n- `glob`\n- `glob_util`\n- `grep`\n- `highlight`\n- `html`\n- `image`\n- `keys`\n- `prof`\n- `ps`\n- `pty`\n- `shell`\n- `system_info`\n- `task`\n- `text`\n\nQuesti moduli implementano i simboli N-API consumati e validati da `native.ts`. I nomi lato JS sono esposti attraverso i wrapper TS in `packages/natives/src`.\n\n**Contratto garantito (lato API):** le esportazioni dei moduli Rust devono corrispondere ai nomi dei binding attesi da `validateNative` e dai moduli wrapper.\n\n**Dettaglio implementativo (soggetto a modifiche):** decomposizione interna dei moduli Rust e confini dei moduli ausiliari (`glob_util`, `task`, ecc.).\n\n## Confini di responsabilità\n\nA livello architetturale, la responsabilità è suddivisa come segue:\n\n- **Responsabilità del wrapper/API TS (`packages/natives/src`)**\n  - raggruppamento dell'API pubblica, tipizzazione delle opzioni ed ergonomia JS stabile\n  - superficie di cancellazione (`timeoutMs`, `AbortSignal`) esposta ai chiamanti\n- **Responsabilità del loader (`packages/natives/src/native.ts`)**\n  - selezione del binario a runtime\n  - selezione della variante CPU e gestione dell'override\n  - estrazione del binario compilato e probing dei candidati\n  - validazione rigorosa delle esportazioni native richieste\n- **Responsabilità di Rust (`crates/pi-natives/src`)**\n  - implementazione algoritmica e a livello di sistema\n  - comportamento nativo della piattaforma e logica sensibile alle prestazioni\n  - implementazione dei simboli N-API consumati dai wrapper TS\n\n## Flusso a runtime (alto livello)\n\n1. Il consumatore importa da `@f5-sales-demo/pi-natives`.\n2. Il modulo wrapper chiama il binding singleton `native`.\n3. `native.ts` seleziona il binario candidato per piattaforma/architettura/variante.\n4. L'estrazione opzionale del binario embedded avviene per le distribuzioni compilate.\n5. L'addon viene caricato e il set di esportazioni viene validato.\n6. Il wrapper restituisce risultati tipizzati al chiamante.\n\n## Glossario\n\n- **Addon nativo**: Un binario `.node` caricato tramite Node-API (N-API).\n- **Tag piattaforma**: Tupla runtime `platform-arch` (ad esempio `darwin-arm64`).\n- **Variante**: Flavor di build specifico per CPU x64 (`modern` AVX2, `baseline` fallback).\n- **Wrapper**: Funzione/classe TS che fornisce un'API tipizzata sulle esportazioni native grezze.\n- **Declaration merging**: Tecnica TS utilizzata dai file `types.ts` dei moduli per estendere `NativeBindings`.\n- **Modalità binario compilato**: Modalità runtime in cui la CLI è integrata e gli addon nativi vengono risolti da percorsi estratti/cache anziché solo da percorsi locali al pacchetto.\n- **Addon embedded**: Metadati degli artefatti di build e riferimenti ai file generati in `embedded-addon.ts` affinché i binari compilati possano estrarre i payload `.node` corrispondenti.\n- **Gate di validazione**: Verifica `validateNative(...)` che rifiuta i binari obsoleti/non corrispondenti a cui mancano le esportazioni richieste.\n",
	"it/natives/natives-binding-contract.md": "---\ntitle: Contratto di Binding Nativo (Lato TypeScript)\ndescription: >-\n  Contratto di binding lato TypeScript per la chiamata alle funzioni native Rust\n  tramite N-API.\nsidebar:\n  order: 2\n  label: Contratto di binding\ni18n:\n  sourceHash: 36dc5fed1f0a\n  translator: machine\n---\n\n# Contratto di Binding Nativo (Lato TypeScript)\n\nQuesto documento definisce il contratto lato TypeScript che si interpone tra i chiamanti di `@f5-sales-demo/pi-natives` e l'addon N-API caricato.\n\nSi concentra su tre elementi:\n\n1. forma del contratto (`NativeBindings` + module augmentation),\n2. comportamento dei wrapper (`src/<module>/index.ts`),\n3. superficie di esportazione pubblica (`src/index.ts`).\n\n## File di implementazione\n\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/system-info/types.ts`\n- `packages/natives/src/system-info/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/work/types.ts`\n- `packages/natives/src/work/index.ts`\n\n## Modello del contratto\n\n`packages/natives/src/bindings.ts` definisce il contratto base:\n\n- `NativeBindings` (interfaccia base, attualmente include `cancelWork(id: number): void`)\n- `Cancellable` (`timeoutMs?: number`, `signal?: AbortSignal`)\n- `TsFunc<T>` forma della callback utilizzata dalle callback threadsafe di N-API\n\nOgni modulo aggiunge i propri campi tramite declaration merging:\n\n```ts\n// packages/natives/src/<module>/types.ts\ndeclare module \"../bindings\" {\n interface NativeBindings {\n  grep(options: GrepOptions, onMatch?: TsFunc<GrepMatch>): Promise<GrepResult>;\n }\n}\n```\n\nQuesto mantiene un'unica interfaccia di binding aggregata senza un file di tipi centrale monolitico.\n\n## Ciclo di vita del declaration-merging e transizioni di stato\n\n### 1) Assemblaggio dei tipi a tempo di compilazione\n\n- `bindings.ts` fornisce il simbolo base `NativeBindings`.\n- Ogni `src/<module>/types.ts` estende `NativeBindings`.\n- `src/native.ts` importa tutti i file `./<module>/types` per i side effect, così che il contratto unificato sia nello scope dove `NativeBindings` viene utilizzato.\n\nTransizione di stato: **Contratto base** → **Contratto unificato**.\n\n### 2) Caricamento dell'addon a runtime e gate di validazione\n\n- `src/native.ts` carica i binari `.node` candidati.\n- L'oggetto caricato viene trattato come `NativeBindings` e immediatamente passato attraverso `validateNative(...)`.\n- `validateNative` verifica le chiavi di esportazione richieste tramite `typeof bindings[name] === \"function\"`.\n\nTransizione di stato: **Oggetto addon non attendibile** → **Oggetto di binding nativo validato** (o fallimento critico).\n\n### 3) Invocazione dei wrapper\n\n- I wrapper dei moduli in `src/<module>/index.ts` chiamano `native.<export>`.\n- I wrapper adattano i valori predefiniti e la forma delle callback (da `(err, value)` a pattern callback solo-valore nelle API JS).\n- `src/index.ts` ri-esporta wrapper/tipi dei moduli come API pubblica del pacchetto.\n\nTransizione di stato: **Binding grezzi validati** → **API pubblica ergonomica**.\n\n## Responsabilità dei wrapper\n\nI wrapper sono intenzionalmente sottili; non re-implementano la logica nativa.\n\nResponsabilità principali:\n\n- **Normalizzazione/impostazione predefinita degli argomenti**\n  - `glob()` risolve `options.path` in un percorso assoluto e imposta i valori predefiniti per `hidden`, `gitignore`, `recursive`.\n  - `hasMatch()` compila i flag predefiniti (`ignoreCase`, `multiline`) prima della chiamata nativa.\n- **Adattamento delle callback**\n  - `grep()`, `glob()`, `executeShell()` convertono `TsFunc<T>` (`error, value`) in callback utente che ricevono solo valori di successo.\n- **Comportamento di ambiente o policy attorno alle chiamate native**\n  - Il wrapper della clipboard aggiunge la gestione OSC52/Termux/headless e tratta la copia come best effort.\n- **Naming pubblico e curazione delle ri-esportazioni**\n  - `searchContent()` mappa all'esportazione nativa `search`.\n\n## Organizzazione della superficie di esportazione pubblica\n\n`packages/natives/src/index.ts` è il barrel pubblico canonico. Raggruppa le esportazioni per dominio funzionale:\n\n- Ricerca/testo: `grep`, `glob`, `text`, `highlight`\n- Esecuzione/processi/terminale: `shell`, `pty`, `ps`, `keys`\n- Sistema/media/conversione: `image`, `html`, `clipboard`, `system-info`, `work`\n\nRegola per i maintainer: se un wrapper non è ri-esportato da `src/index.ts`, non fa parte della superficie pubblica prevista del pacchetto.\n\n## Mappatura API JS ↔ esportazione nativa (rappresentativa)\n\nIl lato Rust utilizza nomi di esportazione N-API (tipicamente dalla conversione `#[napi]` snake_case -> camelCase, con occasionali alias espliciti) che devono corrispondere a queste chiavi di binding.\n\n| Categoria | API JS pubblica (wrapper) | Chiave di binding nativa | Tipo di ritorno | Asincrona? |\n|---|---|---|---|---|\n| Grep | `grep(options, onMatch?)` | `grep` | `Promise<GrepResult>` | Sì |\n| Grep | `searchContent(content, options)` | `search` | `SearchResult` | No |\n| Grep | `hasMatch(content, pattern, opts?)` | `hasMatch` | `boolean` | No |\n| Grep | `fuzzyFind(options)` | `fuzzyFind` | `Promise<FuzzyFindResult>` | Sì |\n| Glob | `glob(options, onMatch?)` | `glob` | `Promise<GlobResult>` | Sì |\n| Glob | `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `void` | No |\n| Shell | `executeShell(options, onChunk?)` | `executeShell` | `Promise<ShellExecuteResult>` | Sì |\n| Shell | `Shell` | `Shell` | costruttore di classe | N/D |\n| PTY | `PtySession` | `PtySession` | costruttore di classe | N/D |\n| Text | `truncateToWidth(...)` | `truncateToWidth` | `string` | No |\n| Text | `sliceWithWidth(...)` | `sliceWithWidth` | `SliceWithWidthResult` | No |\n| Text | `visibleWidth(text)` | `visibleWidth` | `number` | No |\n| Highlight | `highlightCode(code, lang, colors)` | `highlightCode` | `string` | No |\n| HTML | `htmlToMarkdown(html, options?)` | `htmlToMarkdown` | `Promise<string>` | Sì |\n| System | `getSystemInfo()` | `getSystemInfo` | `SystemInfo` | No |\n| Work | `getWorkProfile(lastSeconds)` | `getWorkProfile` | `WorkProfile` | No |\n| Process | `killTree(pid, signal)` | `killTree` | `number` | No |\n| Process | `listDescendants(pid)` | `listDescendants` | `number[]` | No |\n| Clipboard | `copyToClipboard(text)` | `copyToClipboard` | `Promise<void>` (comportamento wrapper best effort) | Sì |\n| Clipboard | `readImageFromClipboard()` | `readImageFromClipboard` | `Promise<ClipboardImage \\| null>` | Sì |\n| Keys | `parseKey(data, kittyProtocolActive)` | `parseKey` | `string \\| null` | No |\n\n## Differenze contrattuali tra sincrono e asincrono\n\nIl contratto mescola API sincrone e asincrone; i wrapper preservano lo stile di chiamata nativa piuttosto che forzare un unico modello:\n\n- **Esportazioni asincrone basate su Promise** per I/O o lavori di lunga durata (`grep`, `glob`, `htmlToMarkdown`, `executeShell`, clipboard, operazioni su immagini).\n- **Esportazioni sincrone** per trasformazioni/parser deterministici in memoria (`search`, `hasMatch`, highlighting, larghezza/slicing del testo, parsing dei tasti, query sui processi).\n- **Esportazioni di costruttori** per oggetti runtime con stato (`Shell`, `PtySession`, `PhotonImage`).\n\nImplicazione per i maintainer: cambiare sincrono ↔ asincrono per un'esportazione esistente è un cambiamento breaking dell'API e del contratto attraverso wrapper e chiamanti.\n\n## Pattern di tipizzazione per oggetti ed enum\n\n### Pattern oggetto (oggetti JS in stile `#[napi(object)]`)\n\nI modelli TS rappresentano i valori nativi a forma di oggetto come interfacce, ad esempio:\n\n- `GrepResult`, `SearchResult`, `GlobResult`\n- `SystemInfo`, `WorkProfile`\n- `ClipboardImage`, `ParsedKittyResult`\n\nQuesti sono contratti strutturali a tempo di compilazione; la correttezza della forma a runtime è di responsabilità dell'implementazione nativa.\n\n### Pattern enum\n\nGli enum nativi numerici sono rappresentati come valori `const enum` in TS:\n\n- `FileType` (`1=file`, `2=dir`, `3=symlink`)\n- `ImageFormat` (`0=PNG`, `1=JPEG`, `2=WEBP`, `3=GIF`)\n- `SamplingFilter`, `Ellipsis`, `KeyEventType`\n\nI chiamanti vedono membri enum con nome; al confine del binding vengono passati numeri.\n\n## Come vengono rilevate le discrepanze\n\nIl rilevamento delle discrepanze avviene su due livelli:\n\n1. **Controlli del contratto TypeScript a tempo di compilazione**\n   - I wrapper chiamano `native.<name>` contro il `NativeBindings` unificato.\n   - Chiavi di binding mancanti/rinominate interrompono il type-checking di TS nei wrapper.\n\n2. **Validazione a runtime in `validateNative`**\n   - Dopo il caricamento, `native.ts` verifica le esportazioni richieste e lancia un'eccezione se ne mancano.\n   - Il messaggio di errore include le chiavi mancanti e le istruzioni per la ricompilazione.\n\nQuesto intercetta il comune drift da binario obsoleto: il wrapper/tipo esiste ma il `.node` caricato non ha l'esportazione.\n\n## Comportamento in caso di fallimento e avvertenze\n\n### Fallimenti di caricamento/validazione (fallimenti critici)\n\n- Il fallimento del caricamento dell'addon o una piattaforma non supportata lanciano un'eccezione durante l'inizializzazione del modulo in `native.ts`.\n- Esportazioni richieste mancanti lanciano un'eccezione prima che i wrapper siano utilizzabili.\n\nEffetto: il pacchetto fallisce rapidamente piuttosto che rinviare il fallimento alla prima chiamata.\n\n### Differenze di comportamento a livello di wrapper\n\n- Alcuni wrapper attenuano intenzionalmente i fallimenti (`copyToClipboard` è best effort e assorbe i fallimenti nativi).\n- Le callback di streaming ignorano i payload di errore delle callback e inoltrano solo eventi con valori di successo.\n\n### Avvertenze a livello di tipo (il runtime è più rigoroso del TS)\n\n- I campi opzionali in TS non garantiscono la validità semantica; il livello nativo può comunque rifiutare valori malformati.\n- La tipizzazione `const enum` non impedisce valori numerici fuori range da chiamanti non tipizzati a runtime.\n- `validateNative` controlla solo la presenza e la natura di funzione delle esportazioni richieste, non la compatibilità profonda della forma argomenti/ritorno.\n- `bindings.ts` include `cancelWork(id)` nell'interfaccia base, ma l'attuale lista di validazione a runtime non applica quella chiave.\n\n## Checklist per i maintainer per le modifiche ai binding\n\nQuando si aggiunge/modifica un'esportazione, aggiornare tutti i seguenti:\n\n1. `src/<module>/types.ts` (augmentation + tipi del contratto)\n2. `src/<module>/index.ts` (comportamento del wrapper)\n3. Import di `src/native.ts` per i tipi del modulo (se nuovo modulo)\n4. Controlli delle esportazioni richieste in `validateNative`\n5. Ri-esportazioni pubbliche di `src/index.ts`\n\nSaltare qualsiasi passaggio crea drift a tempo di compilazione o fallimento a tempo di caricamento a runtime.\n",
	"it/natives/natives-build-release-debugging.md": "---\ntitle: 'Runbook di build, rilascio e debug dei moduli nativi'\ndescription: 'Runbook di build, rilascio e debug per l''addon nativo Rust su più piattaforme.'\nsidebar:\n  order: 8\n  label: 'Build, rilascio e debug'\ni18n:\n  sourceHash: efe47aa5b466\n  translator: machine\n---\n\n# Runbook di build, rilascio e debug dei moduli nativi\n\nQuesto runbook descrive come la pipeline di build di `@f5-sales-demo/pi-natives` produce addon `.node`, come le distribuzioni compilate li caricano e come eseguire il debug degli errori del loader/build.\n\nSegue i termini architetturali da `docs/natives-architecture.md`:\n\n- **produzione di artefatti in fase di build** (`scripts/build-native.ts`)\n- **generazione del manifesto addon incorporato** (`scripts/embed-native.ts`)\n- **caricamento addon a runtime + gate di validazione** (`src/native.ts`)\n\n## File di implementazione\n\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `packages/natives/src/native.ts`\n- `crates/pi-natives/Cargo.toml`\n\n## Panoramica della pipeline di build\n\n### 1) Entry point di build\n\nScript di `packages/natives/package.json`:\n\n- `bun scripts/build-native.ts` (`build`) → build in modalità release\n- `bun scripts/build-native.ts --dev` (`dev:native`) → build con profilo debug/dev (stessa denominazione dell'output)\n- `bun scripts/embed-native.ts` (`embed:native`) → genera `src/embedded-addon.ts` dai file compilati\n\n### 2) Build dell'artefatto Rust\n\n`build-native.ts` esegue Cargo in `crates/pi-natives`:\n\n- comando base: `cargo build`\n- la modalità release aggiunge `--release` a meno che non venga passato `--dev`\n- il target cross aggiunge `--target <CROSS_TARGET>`\n\n`crates/pi-natives/Cargo.toml` dichiara `crate-type = [\"cdylib\"]`, quindi Cargo emette una libreria condivisa (`.so`/`.dylib`/`.dll`) che viene poi copiata/rinominata in un nome file addon `.node`.\n\n### 3) Individuazione e installazione degli artefatti\n\nDopo il completamento di Cargo, `build-native.ts` scansiona le directory di output candidate nell'ordine seguente:\n\n1. `${CARGO_TARGET_DIR}` (se impostato)\n2. `<repo>/target`\n3. `crates/pi-natives/target`\n\nPer ogni root controlla le directory del profilo:\n\n- build cross: `<root>/<crossTarget>/<profile>` poi `<root>/<profile>`\n- build nativa: `<root>/<profile>`\n\nQuindi cerca uno dei seguenti file:\n\n- `libpi_natives.so`\n- `libpi_natives.dylib`\n- `pi_natives.dll`\n- `libpi_natives.dll`\n\nUna volta trovato, viene installato atomicamente in `packages/natives/native/` con semantica temp-file + rename (il fallback Windows gestisce esplicitamente i fallimenti di sostituzione delle DLL bloccate).\n\n## Modello target/variante e convenzioni di denominazione\n\n## Tag di piattaforma\n\nSia la build che il runtime utilizzano il tag di piattaforma:\n\n`<platform>-<arch>` (esempio: `darwin-arm64`, `linux-x64`)\n\n## Modello di variante (solo x64)\n\nx64 supporta varianti CPU:\n\n- `modern` (percorso con supporto AVX2)\n- `baseline` (fallback)\n\nLe piattaforme non-x64 utilizzano un singolo artefatto predefinito (senza suffisso di variante).\n\n### Nomi dei file di output\n\nBuild release:\n\n- x64: `pi_natives.<platform>-<arch>-modern.node` oppure `...-baseline.node`\n- non-x64: `pi_natives.<platform>-<arch>.node`\n\nBuild dev (`--dev`):\n\n- Utilizza i flag del profilo debug ma mantiene la denominazione dell'output standard con tag di piattaforma\n\nOrdine dei candidati nel loader a runtime in `native.ts`:\n\n- candidati release\n- la modalità compilata antepone i candidati estratti/dalla cache prima dei file locali del pacchetto\n\n## Flag di ambiente e opzioni di build\n\n## Flag a runtime\n\n- `PI_DEV` (comportamento del loader): abilita la diagnostica del loader\n- `PI_NATIVE_VARIANT` (comportamento del loader, solo x64): forza la selezione di `modern` o `baseline` a runtime\n- `PI_COMPILED` (comportamento del loader): abilita il comportamento di candidato/estrazione per i binari compilati\n\n## Flag/opzioni in fase di build\n\n- `--dev` (argomento script): build con profilo debug\n- `CROSS_TARGET`: passato a Cargo come `--target`\n- `TARGET_PLATFORM`: sovrascrive la denominazione del tag di piattaforma nell'output\n- `TARGET_ARCH`: sovrascrive la denominazione dell'arch nell'output\n- `TARGET_VARIANT` (solo x64): forza `modern` o `baseline` per il nome del file di output e la policy di RUSTFLAGS\n- `CARGO_TARGET_DIR`: root aggiuntiva durante la ricerca degli output di Cargo\n- `RUSTFLAGS`:\n  - se non impostato e non si effettua cross-compiling, lo script imposta:\n    - modern: `-C target-cpu=x86-64-v3`\n    - baseline: `-C target-cpu=x86-64-v2`\n    - non-x64 / nessuna variante: `-C target-cpu=native`\n  - se già impostato, lo script non lo sovrascrive\n\n## Transizioni di stato/ciclo di vita della build\n\n### Ciclo di vita della build (`build-native.ts`)\n\n1. **Init**: analisi degli argomenti/variabili d'ambiente (`--dev`, override target, flag cross)\n2. **Risoluzione variante**:\n   - non-x64 → nessuna variante\n   - x64 + `TARGET_VARIANT` → variante esplicita\n   - x64 cross-build senza `TARGET_VARIANT` → errore hard\n   - x64 build locale senza override → rilevamento AVX2 dell'host\n3. **Compilazione**: esecuzione di Cargo con profilo/target risolti\n4. **Individuazione artefatto**: scansione root target/directory profilo/nomi libreria\n5. **Installazione**: copia + rename atomico in `packages/natives/native`\n6. **Completamento**: addon pronto per i candidati del loader\n\nIn caso di errore, l'esecuzione termina in qualsiasi fase con un testo di errore esplicito (variante non valida, build cargo fallita, libreria di output mancante, errore di installazione/rename).\n\n### Ciclo di vita dell'embedding (`embed-native.ts`)\n\n1. **Init**: calcolo del tag di piattaforma da `TARGET_PLATFORM`/`TARGET_ARCH` o dai valori dell'host\n2. **Set di candidati**:\n   - x64 si aspetta sia `modern` che `baseline`\n   - non-x64 si aspetta un file predefinito\n3. **Validazione disponibilità** in `packages/natives/native`\n4. **Generazione manifesto** (`src/embedded-addon.ts`) con import `file` di Bun e versione del pacchetto\n5. **Estrazione a runtime pronta** per la modalità compilata\n\n`--reset` bypassa la validazione e scrive uno stub manifesto null (`embeddedAddon = null`).\n\n## Workflow di sviluppo locale vs comportamento shipped/compilato\n\n## Workflow di sviluppo locale\n\nCiclo locale tipico:\n\n1. Build dell'addon:\n   - release: `bun --cwd=packages/natives run build`\n   - profilo debug: `bun --cwd=packages/natives run dev:native`\n2. Impostare `PI_DEV=1` durante il test della diagnostica del loader\n3. Il loader in `native.ts` risolve i candidati nella directory `native/` locale del pacchetto (e il fallback nella directory dell'eseguibile)\n4. `validateNative` impone la compatibilità degli export prima che i wrapper utilizzino il binding\n\n## Workflow per binari shipped/compilati\n\nIn modalità compilata (`PI_COMPILED` o marker embedded di Bun):\n\n1. Il loader calcola la directory cache versionata: `<getNativesDir()>/<packageVersion>` (operativamente `~/.xcsh/natives/<version>`)\n2. Se il manifesto incorporato corrisponde alla piattaforma+versione corrente, il loader può estrarre il file incorporato selezionato in quella directory versionata\n3. L'ordine dei candidati a runtime include:\n   - directory cache versionata\n   - directory legacy per binari compilati (`%LOCALAPPDATA%/xcsh` su Windows, `~/.local/bin` altrove)\n   - directory del pacchetto/eseguibile\n4. Il primo addon caricato con successo deve comunque superare `validateNative`\n\nPer questo motivo il packaging e le aspettative del loader a runtime devono essere allineati: i nomi dei file, i tag di piattaforma e i simboli esportati devono corrispondere a ciò che `native.ts` sonda e valida.\n\n## Mappatura API JS ↔ export Rust (sottoinsieme del gate di validazione)\n\n`native.ts` richiede che questi export visibili in JS esistano sull'addon caricato. Sono mappati agli export N-API Rust in `crates/pi-natives/src`:\n\n| Nome JS richiesto da `validateNative` | Dichiarazione export Rust | File sorgente Rust |\n| --- | --- | --- |\n| `glob` | `#[napi] pub fn glob(...)` | `crates/pi-natives/src/glob.rs` |\n| `grep` | `#[napi] pub fn grep(...)` | `crates/pi-natives/src/grep.rs` |\n| `search` | `#[napi] pub fn search(...)` | `crates/pi-natives/src/grep.rs` |\n| `highlightCode` | `#[napi] pub fn highlight_code(...)` | `crates/pi-natives/src/highlight.rs` |\n| `getSystemInfo` | `#[napi] pub fn get_system_info(...)` | `crates/pi-natives/src/system_info.rs` |\n| `getWorkProfile` | `#[napi] pub fn get_work_profile(...)` (export in camelCase) | `crates/pi-natives/src/prof.rs` |\n| `invalidateFsScanCache` | `#[napi] pub fn invalidate_fs_scan_cache(...)` | `crates/pi-natives/src/fs_cache.rs` |\n\nSe un simbolo richiesto è assente, il loader fallisce immediatamente con un suggerimento di rebuild.\n\n## Comportamento in caso di errore e diagnostica\n\n## Errori in fase di build\n\n- Configurazione variante non valida:\n  - `TARGET_VARIANT` impostato su non-x64 → errore immediato\n  - x64 cross-build senza `TARGET_VARIANT` esplicito → errore immediato\n- Errore di build Cargo:\n  - lo script riporta il codice di uscita diverso da zero e stderr\n- Artefatto non trovato:\n  - lo script stampa ogni directory del profilo controllata\n- Errore di installazione:\n  - messaggio esplicito; su Windows include un suggerimento relativo ai file bloccati\n\n## Errori del loader a runtime (`native.ts`)\n\n- Tag di piattaforma non supportato:\n  - lancia un'eccezione con la lista delle piattaforme supportate\n- Nessun candidato è stato caricato con successo:\n  - lancia un'eccezione con la lista completa degli errori dei candidati e suggerimenti di rimedio specifici per la modalità\n- Export mancanti:\n  - lancia un'eccezione con i nomi esatti dei simboli mancanti e il comando di rebuild\n- Problemi di estrazione incorporata:\n  - gli errori di mkdir/write durante l'estrazione vengono registrati e inclusi nella diagnostica finale\n\n## Matrice di risoluzione dei problemi\n\n| Sintomo | Causa probabile | Verifica | Soluzione |\n| --- | --- | --- | --- |\n| `Native addon missing exports ... Missing: <name>` | Binario `.node` non aggiornato, mancata corrispondenza del nome export Rust, o binario errato caricato | Eseguire con `PI_DEV=1` per vedere il percorso caricato; ispezionare la lista degli export per quel file | Ricompilare con `build`; assicurarsi che il nome dell'export Rust `#[napi]` (o l'alias esplicito se necessario) corrisponda alla chiave JS; rimuovere i file cache/versionati non aggiornati |\n| La macchina x64 carica baseline quando si aspetta modern | `PI_NATIVE_VARIANT=baseline`, AVX2 non rilevato, o solo il file baseline presente | Controllare `PI_NATIVE_VARIANT`; ispezionare `native/` per il file `-modern` | Compilare la variante modern (`TARGET_VARIANT=modern ... build`) e assicurarsi che il file sia distribuito |\n| La cross-build produce un binario inutilizzabile/etichettato erroneamente | Mancata corrispondenza tra `CROSS_TARGET` e `TARGET_PLATFORM`/`TARGET_ARCH`, o `TARGET_VARIANT` mancante per x64 | Verificare la tupla delle variabili d'ambiente e il nome del file di output | Rieseguire con valori di ambiente coerenti e `TARGET_VARIANT` x64 esplicito |\n| Il binario compilato fallisce dopo un aggiornamento | Cache estratta non aggiornata (`~/.xcsh/natives/<versione-vecchia-o-non-corrispondente>`) o mancata corrispondenza del manifesto incorporato | Ispezionare la directory natives versionata e la lista degli errori del loader | Eliminare la cache natives versionata per la versione del pacchetto e rieseguire; rigenerare il manifesto incorporato durante il packaging |\n| Il loader sonda molti percorsi e nessuno funziona | Mancata corrispondenza di piattaforma o artefatto release mancante in `native/` del pacchetto | Controllare `platformTag` rispetto ai nomi dei file effettivi | Assicurarsi che il nome del file compilato corrisponda esattamente alla convenzione `pi_natives.<platform>-<arch>(-variant).node` e che il pacchetto includa `native/` |\n| `embed:native` fallisce con \"Incomplete native addons\" | I file di variante richiesti non sono stati compilati prima dell'embedding | Controllare la lista expected vs found nel testo dell'errore | Compilare prima i file richiesti (x64: sia modern che baseline; non-x64: predefinito), poi rieseguire `embed:native` |\n\n## Comandi operativi\n\n```bash\n# Artefatto release per l'host corrente\nbun --cwd=packages/natives run build\n\n# Build artefatto con profilo debug\nbun --cwd=packages/natives run dev:native\n\n# Compilazione varianti x64 esplicite\nTARGET_VARIANT=modern bun --cwd=packages/natives run build\nTARGET_VARIANT=baseline bun --cwd=packages/natives run build\n\n# Generazione del manifesto addon incorporato dai file nativi compilati\nbun --cwd=packages/natives run embed:native\n\n# Reset del manifesto incorporato a stub null\nbun --cwd=packages/natives run embed:native -- --reset\n```\n",
	"it/natives/natives-media-system-utils.md": "---\ntitle: Utilità native per media e sistema\ndescription: >-\n  Utilità native per l'elaborazione di media, la gestione delle immagini e le\n  informazioni di sistema.\nsidebar:\n  order: 7\n  label: Utilità media e sistema\ni18n:\n  sourceHash: 430898c177bc\n  translator: machine\n---\n\n# Utilità native per media e sistema\n\nQuesto documento è un'analisi approfondita del sottosistema per il livello dei **primitivi di sistema/media/conversione** descritto in [`docs/natives-architecture.md`](./natives-architecture.md): profilazione di `image`, `html`, `clipboard` e `work`.\n\n## File di implementazione\n\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/prof.rs`\n- `crates/pi-natives/src/task.rs`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/work/index.ts`\n- `packages/natives/src/work/types.ts`\n\n> Nota: non esiste `crates/pi-natives/src/work.rs`; la profilazione del lavoro è implementata in `prof.rs` e alimentata dalla strumentazione in `task.rs`.\n\n## Mappatura API TS ↔ export/modulo Rust\n\n| Export TS (packages/natives)                | Export Rust N-API                                                       | Modulo Rust                           |\n| ------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------- |\n| `PhotonImage.parse(bytes)`                  | `PhotonImage::parse`                                                     | `image.rs`                            |\n| `PhotonImage#resize(width, height, filter)` | `PhotonImage::resize`                                                    | `image.rs`                            |\n| `PhotonImage#encode(format, quality)`       | `PhotonImage::encode`                                                    | `image.rs`                            |\n| `htmlToMarkdown(html, options)`             | `html_to_markdown`                                                       | `html.rs`                             |\n| `copyToClipboard(text)`                     | `copy_to_clipboard` + logica di fallback TS                              | `clipboard.rs` + `clipboard/index.ts` |\n| `readImageFromClipboard()`                  | `read_image_from_clipboard`                                              | `clipboard.rs`                        |\n| `getWorkProfile(lastSeconds)`               | `get_work_profile`                                                      | `prof.rs`                             |\n\n## Confini di formato dei dati e conversioni\n\n### Immagine (`image`)\n\n- **Confine di input JS**: byte dell'immagine codificati come `Uint8Array`.\n- **Confine di decodifica Rust**: i byte vengono copiati in `Vec<u8>`, il formato viene rilevato con `ImageReader::with_guessed_format()`, quindi decodificato in `DynamicImage`.\n- **Stato in memoria**: `PhotonImage` memorizza `Arc<DynamicImage>`.\n- **Confine di output**: `encode(format, quality)` restituisce `Promise<Uint8Array>` (Rust `Vec<u8>`).\n\nGli ID di formato sono numerici:\n\n- `0`: PNG\n- `1`: JPEG\n- `2`: WebP (encoder lossless)\n- `3`: GIF\n\nVincoli:\n\n- `quality` viene utilizzato solo per JPEG.\n- PNG/WebP/GIF ignorano `quality`.\n- Gli ID di formato non supportati generano un errore (`Invalid image format: <id>`).\n\n### Conversione HTML (`html`)\n\n- **Confine di input JS**: `string` HTML + oggetto opzionale `{ cleanContent?: boolean; skipImages?: boolean }`.\n- **Confine di conversione Rust**: l'input `String` viene convertito da `html_to_markdown_rs::convert`.\n- **Confine di output**: `string` Markdown.\n\nComportamento della conversione:\n\n- `cleanContent` ha valore predefinito `false`.\n- Quando `cleanContent=true`, il preprocessamento è abilitato con `PreprocessingPreset::Aggressive` e flag di rimozione rigida per navigazione/moduli.\n- `skipImages` ha valore predefinito `false`.\n\n### Appunti (`clipboard`)\n\n- **Percorso testo**:\n  - TS emette prima OSC 52 (`\\x1b]52;c;<base64>\\x07`) quando stdout è un TTY.\n  - Lo stesso testo viene quindi tentato tramite l'API degli appunti nativa (`native.copyToClipboard`) come tentativo ottimistico.\n  - Su Termux, TS tenta prima `termux-clipboard-set`.\n- **Percorso di lettura immagine**:\n  - Rust legge l'immagine grezza da `arboard`.\n  - Rust la ricodifica in byte PNG (`crate image`), restituisce `{ data: Uint8Array, mimeType: \"image/png\" }`.\n  - TS restituisce `null` preventivamente su Termux o sessioni Linux senza server di visualizzazione (variabili `DISPLAY`/`WAYLAND_DISPLAY` assenti).\n\n### Profilazione del lavoro (`work`)\n\n- **Confine di raccolta**: i campioni di profilazione vengono prodotti dalle guardie `profile_region(tag)` in `task::blocking` e `task::future`.\n- **Formato di archiviazione**: buffer circolare a dimensione fissa (`MAX_SAMPLES = 10_000`) che memorizza il percorso dello stack + durata (`μs`) + timestamp (`μs dall'avvio del processo`).\n- **Confine di output**: `getWorkProfile(lastSeconds)` restituisce un oggetto:\n  - `folded`: testo con stack compresso (input per flamegraph)\n  - `summary`: tabella riassuntiva in markdown\n  - `svg`: SVG del flamegraph opzionale\n  - `totalMs`, `sampleCount`\n\n## Ciclo di vita e transizioni di stato\n\n### Ciclo di vita dell'immagine\n\n1. `PhotonImage.parse(bytes)` pianifica un'attività di decodifica bloccante (`image.decode`).\n2. In caso di successo, in JS esiste un handle nativo `PhotonImage`.\n3. `resize(...)` crea un nuovo handle nativo (`image.resize`); il vecchio e il nuovo handle possono coesistere.\n4. `encode(...)` materializza i byte (`image.encode`) senza mutare le dimensioni dell'immagine.\n\nTransizioni di errore:\n\n- Il rilevamento del formato o la decodifica fallita rifiuta la promise di parse.\n- Il fallimento della codifica rifiuta la promise di encode.\n- Un ID di formato non valido rifiuta la promise di encode.\n\n### Ciclo di vita HTML\n\n1. `htmlToMarkdown(html, options)` pianifica un'attività di conversione bloccante.\n2. La conversione viene eseguita con le opzioni predefinite (`cleanContent=false`, `skipImages=false`) salvo diversa indicazione.\n3. Restituisce la stringa markdown o rifiuta.\n\nTransizioni di errore:\n\n- Il fallimento del convertitore restituisce una promise rifiutata (`Conversion error: ...`).\n\n### Ciclo di vita degli appunti\n\n`copyToClipboard(text)` è intenzionalmente ottimistico e multi-percorso:\n\n1. Se TTY: tentativo di scrittura OSC 52 (payload base64).\n2. Tentativo del comando Termux quando `TERMUX_VERSION` è impostato.\n3. Tentativo di copia testo nativa tramite `arboard`.\n4. Gli errori vengono soppressi a livello TS.\n\nLa severità di `readImageFromClipboard()` varia per fase:\n\n1. TS blocca rigidamente i contesti di runtime non supportati (Termux/Linux headless) restituendo `null`.\n2. La lettura Rust tramite `arboard` viene eseguita solo quando TS lo consente.\n3. `ContentNotAvailable` viene mappato a `null`.\n4. Gli altri errori Rust causano un rifiuto.\n\n### Ciclo di vita della profilazione del lavoro\n\n1. Nessun avvio esplicito: la profilazione è sempre attiva durante l'esecuzione dei task helper.\n2. Ogni ambito di task strumentato registra un campione al rilascio della guardia.\n3. I campioni sovrascrivono le voci più vecchie una volta raggiunta la capacità del buffer.\n4. `getWorkProfile(lastSeconds)` legge una finestra temporale e produce gli artefatti folded/summary/svg.\n\nTransizioni di errore:\n\n- Il fallimento nella generazione SVG è un soft-fail (`svg: null`), mentre folded e summary vengono comunque restituiti.\n- Una finestra di campioni vuota restituisce dati folded vuoti e `svg: null`, non un errore.\n\n## Operazioni non supportate e propagazione degli errori\n\n### Immagine\n\n- Input di decodifica non supportato o byte corrotti: errore rigido (rifiuto della promise).\n- ID di formato di codifica non supportato: errore rigido.\n- Nessun percorso di fallback ottimistico nel wrapper TS.\n\n### HTML\n\n- Gli errori di conversione sono errori rigidi (rifiuto).\n- L'omissione delle opzioni applica i valori predefiniti in modo ottimistico, senza generare errori.\n\n### Appunti\n\n- La copia del testo è ottimistica a livello TS: i fallimenti operativi vengono soppressi.\n- La lettura dell'immagine distingue \"nessuna immagine\" (`null`) da un fallimento operativo (rifiuto).\n- Termux/Linux headless sono trattati come contesti non supportati per la lettura delle immagini (`null`).\n\n### Profilazione del lavoro\n\n- Il recupero è rigido per la chiamata alla funzione stessa, ma la generazione degli artefatti è parzialmente ottimistica (`svg` nullable).\n- La troncatura del buffer è un comportamento atteso (ring buffer), non un bug di perdita di dati.\n\n## Avvertenze di Piattaforma\n\n- **Testo negli appunti**: OSC 52 dipende dal supporto del terminale; l'accesso nativo agli appunti dipende dall'ambiente desktop/sessione.\n- **Lettura immagine dagli appunti**: bloccata in TS per Termux e Linux senza server di visualizzazione.\n",
	"it/natives/natives-rust-task-cancellation.md": "---\ntitle: Esecuzione e cancellazione nativa dei task Rust\ndescription: >-\n  Modello di esecuzione dei task asincroni Rust con cancellazione cooperativa e\n  semantica di pulizia.\nsidebar:\n  order: 5\n  label: Cancellazione dei task\ni18n:\n  sourceHash: 0fbf45c6d463\n  translator: machine\n---\n\n# Esecuzione e cancellazione nativa dei task Rust (`pi-natives`)\n\nQuesto documento descrive come `crates/pi-natives` pianifica il lavoro nativo e come la cancellazione fluisce dalle opzioni JS (`timeoutMs`, `AbortSignal`) all'esecuzione Rust.\n\n## File di implementazione\n\n- `crates/pi-natives/src/task.rs`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/fd.rs`\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/ps.rs`\n\n## Primitive fondamentali (`task.rs`)\n\n`task.rs` definisce tre elementi fondamentali:\n\n1. `task::blocking(tag, cancel_token, work)`\n   - Avvolge `napi::AsyncTask` / `Task`.\n   - `compute()` viene eseguito sui thread worker di libuv (per chiamate di sistema CPU-bound o bloccanti/sincrone).\n   - Restituisce una JS `Promise<T>`.\n\n2. `task::future(env, tag, work)`\n   - Avvolge `env.spawn_future(...)`.\n   - Esegue lavoro asincrono sul runtime Tokio.\n   - Restituisce `PromiseRaw<'env, T>`.\n\n3. `CancelToken` / `AbortToken` / `AbortReason`\n   - `CancelToken::new(timeout_ms, signal)` combina deadline + `AbortSignal` opzionale.\n   - `CancelToken::heartbeat()` è la cancellazione cooperativa per i loop bloccanti.\n   - `CancelToken::wait()` è l'attesa di cancellazione asincrona (`Signal` / `Timeout` / `User` Ctrl-C).\n   - `AbortToken` consente a codice esterno di richiedere l'interruzione (`abort(reason)`).\n\n## `blocking` vs `future`: modello di esecuzione e selezione\n\n### Usare `task::blocking`\n\nDa usare quando il lavoro è CPU-intensive o fondamentalmente sincrono/bloccante:\n\n- scansione regex/file (`grep`, `glob`, `fuzzy_find`)\n- logica interna del loop PTY sincrono (`run_pty_sync` tramite `spawn_blocking`)\n- conversioni clipboard/immagini/html\n\nComportamento:\n\n- La closure del lavoro riceve un `CancelToken` clonato.\n- La cancellazione viene osservata solo dove il codice verifica `ct.heartbeat()?`.\n- `Err(...)` nella closure rifiuta la promise JS.\n\n### Usare `task::future`\n\nDa usare quando il lavoro deve eseguire `await` su operazioni asincrone:\n\n- orchestrazione sessioni shell (`shell.run`, `executeShell`)\n- racing di task (`tokio::select!`) tra completamento e cancellazione\n\nComportamento:\n\n- Il future può mettere in competizione il completamento normale contro `ct.wait()`.\n- Nel percorso di cancellazione, le implementazioni asincrone tipicamente propagano la cancellazione ai sottosistemi interni (ad es., `tokio_util::CancellationToken`) e opzionalmente forzano l'interruzione al timeout di grazia.\n\n## Mappatura API JS ↔ export Rust (rilevanti per task/cancellazione)\n\n| API lato JS | Export Rust (`#[napi]`) | Scheduler | Collegamento cancellazione |\n|---|---|---|---|\n| `grep(options, onMatch?)` | `grep` | `task::blocking(\"grep\", ct, ...)` | `CancelToken::new(options.timeoutMs, options.signal)` + `ct.heartbeat()` |\n| `glob(options, onMatch?)` | `glob` | `task::blocking(\"glob\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` nel loop di filtraggio |\n| `fuzzyFind(options)` | `fuzzy_find` | `task::blocking(\"fuzzy_find\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` nel loop di scoring |\n| `shell.run(options, onChunk?)` | `Shell::run` | `task::future(env, \"shell.run\", ...)` | `ct.wait()` in competizione con il task di esecuzione; collegato a Tokio `CancellationToken` |\n| `executeShell(options, onChunk?)` | `execute_shell` | `task::future(env, \"shell.execute\", ...)` | come sopra |\n| `pty.start(options, onChunk?)` | `PtySession::start` | `task::future(env, \"pty.start\", ...)` + `spawn_blocking` interno | `CancelToken` verificato nel loop PTY sincrono tramite `heartbeat()` |\n| `htmlToMarkdown(html, options?)` | `html_to_markdown` | `task::blocking(\"html_to_markdown\", (), ...)` | nessuno (token `()`) |\n| `PhotonImage.parse/encode/resize` | `PhotonImage::{parse,encode,resize}` | `task::blocking(...)` | nessuno (token `()`) |\n| `copyToClipboard/readImageFromClipboard` | `copy_to_clipboard` / `read_image_from_clipboard` | `task::blocking(...)` | nessuno (token `()`) |\n\n`text.rs` e `ps.rs` attualmente non usano `task::blocking`/`task::future` e pertanto non partecipano a questo percorso di cancellazione.\n\n## Ciclo di vita della cancellazione e transizioni di stato\n\n### Ciclo di vita di `CancelToken`\n\n`CancelToken` è cooperativo e con stato:\n\n```text\nCreated\n  ├─ no signal + no timeout  -> passive token (never aborts unless externally emplaced)\n  ├─ signal registered        -> waits for AbortSignal callback\n  └─ deadline set             -> timeout check becomes active\n\nRunning\n  ├─ heartbeat()/wait() sees signal   -> AbortReason::Signal\n  ├─ heartbeat()/wait() sees deadline -> AbortReason::Timeout\n  ├─ wait() sees Ctrl-C               -> AbortReason::User\n  └─ no abort                         -> continue\n\nAborted (terminal)\n  └─ first abort reason wins (atomic flag + notifier)\n```\n\n### Cancellazione prima dell'avvio vs durante l'esecuzione\n\n- **Prima dell'avvio / prima del primo controllo di cancellazione**:\n  - Gli utenti di `task::future` che competono su `ct.wait()` possono risolvere la cancellazione immediatamente una volta entrati nel `select!`.\n  - Gli utenti di `task::blocking` osservano la cancellazione solo quando il codice della closure raggiunge `heartbeat()`. Se la closure non esegue un heartbeat precocemente, la cancellazione è ritardata.\n\n- **Durante l'esecuzione**:\n  - `blocking`: il prossimo `heartbeat()` restituisce `Err(\"Aborted: ...\")`.\n  - `future`: il ramo `ct.wait()` vince il `select!`, quindi il codice cancella il meccanismo asincrono subordinato (per shell: cancella il token Tokio, attende fino a 2s, poi interrompe forzatamente il task).\n\n## Aspettative di heartbeat per loop di lunga durata\n\n`heartbeat()` deve essere eseguito a cadenza prevedibile nei loop con set di lavoro illimitati o grandi.\n\nPattern osservati:\n\n- `glob::filter_entries`: controllo su ogni voce prima del filtraggio/matching.\n- `fd::score_entries`: controllo su ogni candidato analizzato.\n- `grep_sync`: controllo esplicito di cancellazione prima della fase di ricerca pesante, più chiamate alla cache del filesystem che ricevono anch'esse il token.\n- `run_pty_sync`: controllo ad ogni tick del loop (cadenza di sleep ~16ms) e terminazione del processo figlio alla cancellazione.\n\nRegola pratica: nessun loop su input di dimensione esterna dovrebbe superare un breve intervallo limitato senza un heartbeat.\n\n## Comportamento in caso di errore e propagazione degli errori a JS\n\n### Task bloccanti\n\nPercorso degli errori:\n\n1. La closure restituisce `Err(napi::Error)` (incluso l'abort di `heartbeat()`).\n2. `Task::compute()` restituisce `Err`.\n3. `AsyncTask` rifiuta la promise JS.\n\nStringhe di errore tipiche:\n\n- `Aborted: Timeout`\n- `Aborted: Signal`\n- errori di dominio (`Failed to decode image: ...`, `Conversion error: ...`, ecc.)\n\n### Task future\n\nPercorso degli errori:\n\n1. Il corpo asincrono restituisce `Err(napi::Error)` oppure il fallimento del join viene mappato (`... task failed: {err}`).\n2. La promise generata da `task::future` viene rifiutata.\n3. Alcune API restituiscono intenzionalmente risultati strutturati di cancellazione invece del rifiuto (`ShellRunResult`/`ShellExecuteResult` con flag `cancelled`/`timed_out` e `exit_code: None`).\n\n### Suddivisione della segnalazione di cancellazione\n\n- **Abort come errore**: la maggior parte degli export bloccanti che utilizzano `heartbeat()?`.\n- **Abort come risultato tipizzato**: API stile shell/pty per comandi che modellano la cancellazione nelle struct di risultato.\n\nScegliere un modello per API e documentarlo esplicitamente.\n\n## Insidie comuni\n\n1. **Heartbeat mancante nei loop bloccanti**\n   - Sintomo: timeout/signal sembra ignorato fino al termine del loop.\n   - Correzione: aggiungere `ct.heartbeat()?` all'inizio del loop e prima di passaggi costosi per elemento.\n\n2. **Sezioni lunghe non cancellabili**\n   - Sintomo: picchi di latenza nella cancellazione durante una singola chiamata pesante (decodifica, ordinamento, compressione, ecc.).\n   - Correzione: suddividere il lavoro in blocchi con confini di heartbeat; se impossibile, documentare la latenza.\n\n3. **Blocco dell'executor asincrono**\n   - Sintomo: l'API asincrona si blocca quando codice pesantemente sincrono viene eseguito direttamente nel future.\n   - Correzione: spostare i blocchi CPU/sincroni in `task::blocking` o `tokio::task::spawn_blocking`.\n\n4. **Semantica di cancellazione inconsistente**\n   - Sintomo: un'API rifiuta alla cancellazione, un'altra risolve con flag, confondendo i chiamanti.\n   - Correzione: standardizzare per dominio e mantenere allineata la documentazione dei wrapper.\n\n5. **Dimenticanza del bridge di cancellazione nei task asincroni nidificati**\n   - Sintomo: il token esterno è cancellato ma i reader/task di sottoprocesso interni continuano a funzionare.\n   - Correzione: collegare la cancellazione al token/signal interno e applicare timeout di grazia + fallback di interruzione forzata.\n\n## Checklist per nuovi export cancellabili\n\n1. Classificare correttamente il lavoro:\n   - CPU-bound o bloccante sincrono -> `task::blocking`\n   - I/O asincrono / orchestrazione con `await` -> `task::future`\n\n2. Esporre gli input di cancellazione quando necessario:\n   - includere `timeoutMs` e `signal` nelle opzioni `#[napi(object)]`\n   - creare `let ct = task::CancelToken::new(timeout_ms, signal);`\n\n3. Collegare la cancellazione attraverso tutti i livelli:\n   - loop bloccanti: `ct.heartbeat()?` a intervalli stabili\n   - orchestrazione asincrona: competizione con `ct.wait()` e cancellazione di sub-task/token\n\n4. Decidere il contratto di cancellazione:\n   - rifiutare la promise con errore di abort, oppure\n   - risolvere con risultato tipizzato `{ cancelled, timedOut, ... }`\n   - mantenere questo contratto coerente per la famiglia di API\n\n5. Propagare gli errori con contesto:\n   - mappare gli errori tramite `Error::from_reason(format!(\"...: {err}\"))`\n   - includere prefissi specifici per fase (`spawn`, `decode`, `wait`, ecc.)\n\n6. Gestire la cancellazione prima dell'avvio e durante l'esecuzione:\n   - il controllo/attesa di cancellazione deve avvenire prima del corpo costoso e durante l'esecuzione prolungata\n\n7. Verificare l'assenza di uso improprio dell'executor:\n   - nessun lavoro sincrono prolungato direttamente dentro future asincroni senza wrapper `spawn_blocking`/task bloccante\n",
	"it/natives/natives-shell-pty-process.md": "---\ntitle: 'Internals nativi di Shell, PTY, Process e Key'\ndescription: >-\n  Esecuzione shell, gestione PTY, ciclo di vita dei processi e gestione degli\n  eventi chiave nel livello nativo.\nsidebar:\n  order: 4\n  label: 'Shell, PTY e processo'\ni18n:\n  sourceHash: 00ea95614c6a\n  translator: machine\n---\n\n# Internals nativi di Shell, PTY, Process e Key\n\nQuesto documento tratta le **primitive di esecuzione/processo/terminale** in `@f5-sales-demo/pi-natives`: `shell`, `pty`, `ps` e `keys`, utilizzando i termini architetturali di `docs/natives-architecture.md`.\n\n## File di implementazione\n\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/shell/windows.rs` (solo Windows)\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/ps.rs`\n- `crates/pi-natives/src/keys.rs`\n- `crates/pi-natives/src/task.rs` (comportamento di cancellazione condiviso utilizzato da shell/pty)\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/bindings.ts`\n\n## Proprietà dei livelli\n\n- **Livello wrapper/API TS** (`packages/natives/src/*`): entrypoint tipizzati, superficie di cancellazione (`timeoutMs`, `AbortSignal`) ed ergonomia JS.\n- **Livello modulo Rust N-API** (`crates/pi-natives/src/*`): esecuzione di processi shell/PTY, attraversamento/terminazione dell'albero di processi e analisi delle sequenze di tasti.\n- **Gate di validazione** (`native.ts`, a livello architetturale): verifica che le esportazioni richieste (`Shell`, `executeShell`, `PtySession`, `killTree`, `listDescendants`, helper per i tasti) esistano prima che i wrapper vengano utilizzati.\n\n## Sottosistema Shell (`shell`)\n\n### Modello API\n\nSono esposti due modalità di esecuzione:\n\n1. **Esecuzione singola** tramite `executeShell(options, onChunk?)`.\n2. **Sessione persistente** tramite `new Shell(options?)` e poi `shell.run(...)` ripetutamente.\n\nEntrambe trasmettono l'output tramite una callback threadsafe e restituiscono `{ exitCode?, cancelled, timedOut }`.\n\n### Creazione della sessione e modello di ambiente\n\nRust crea `brush_core::Shell` con:\n\n- modalità non interattiva,\n- `do_not_inherit_env: true`,\n- ricostruzione esplicita dell'ambiente dall'env dell'host,\n- lista di esclusione per le variabili sensibili alla shell (`PS1`, `PWD`, `SHLVL`, esportazioni di funzioni bash, ecc.).\n\nComportamento dell'env di sessione:\n\n- `ShellOptions.sessionEnv` viene applicato una volta alla creazione della sessione.\n- `ShellRunOptions.env` è a scope di comando (`EnvironmentScope::Command`) e viene rimosso dopo ogni esecuzione.\n- `PATH` viene unito in modo speciale su Windows con deduplicazione case-insensitive.\n\nArricchimento del percorso solo per Windows (`shell/windows.rs`): i percorsi Git-for-Windows rilevati (`cmd`, `bin`, `usr/bin`) vengono aggiunti se presenti e non già inclusi.\n\n### Ciclo di vita in esecuzione e transizioni di stato\n\nLa shell persistente (`Shell.run`) utilizza questa macchina a stati:\n\n- **Idle/Non inizializzata**: `session: None`.\n- **In esecuzione**: la prima `run()` crea la sessione in modo lazy, memorizza il token `current_abort`, esegue il comando.\n- **Completata + keepalive**: se il flusso di controllo dell'esecuzione è `Normal`, `current_abort` viene azzerato e la sessione viene riutilizzata.\n- **Completata + teardown**: se il flusso di controllo è correlato a loop/script/uscita dalla shell (`BreakLoop`, `ContinueLoop`, `ReturnFromFunctionOrScript`, `ExitShell`), la sessione viene eliminata (`session: None`).\n- **Cancellata/Scaduta per timeout**: il task di esecuzione viene cancellato, attesa di tolleranza (2s), poi interruzione forzata; la sessione viene eliminata.\n- **Errore**: la sessione viene eliminata.\n\nLa shell monouso (`executeShell`) crea e distrugge sempre una nuova sessione per ogni chiamata.\n\n### Comportamento di streaming/output\n\n- Stdout/stderr vengono instradati in una pipe condivisa e letti in modo concorrente.\n- Il lettore decodifica UTF-8 in modo incrementale; le sequenze di byte non valide emettono chunk di sostituzione `U+FFFD`.\n- Dopo il completamento del processo, lo svuotamento dell'output ha limiti di inattività/massimo (`250ms` di inattività, `2s` massimo) per evitare blocchi causati da job in background che mantengono aperto i descrittori.\n\n### Cancellazione, timeout e job in background\n\n- `CancelToken` viene costruito da `timeoutMs` e da un eventuale `AbortSignal`.\n- In caso di cancellazione/timeout, viene attivato il token di cancellazione della shell, poi il task riceve una finestra di tolleranza di 2s prima dell'interruzione forzata.\n- Se si verifica la cancellazione, i job in background vengono terminati (`TERM`, poi `KILL` con ritardo) utilizzando i metadati dei job di brush.\n\nComportamento di `Shell.abort()`:\n\n- interrompe solo il comando attualmente in esecuzione per quella istanza di `Shell`,\n- è un no-op con successo quando non è in esecuzione nulla.\n\n### Comportamento in caso di errore\n\nGli errori comuni esposti includono:\n\n- errori di inizializzazione della sessione (`Failed to initialize shell`),\n- errori di directory di lavoro (`Failed to set cwd`),\n- errori di impostazione/rimozione dell'env,\n- errori di recupero dello snapshot sorgente,\n- errori di creazione/clonazione della pipe,\n- errore di esecuzione (`Shell execution failed: ...`),\n- errori del wrapper del task (`Shell execution task failed: ...`).\n\nFlag di cancellazione a livello di risultato:\n\n- timeout -> `exitCode: undefined`, `timedOut: true`.\n- segnale di abort -> `exitCode: undefined`, `cancelled: true`.\n\n## Sottosistema PTY (`pty`)\n\n### Modello API\n\n`new PtySession()` espone:\n\n- `start(options, onChunk?) -> Promise<{ exitCode?, cancelled, timedOut }>`\n- `write(data)`\n- `resize(cols, rows)`\n- `kill()`\n\n### Ciclo di vita in esecuzione e transizioni di stato\n\nMacchina a stati di `PtySession`:\n\n- **Idle**: `core: None`.\n- **Riservata**: `start()` installa il canale di controllo in modo sincrono (`core: Some`) prima che inizi il lavoro asincrono, rendendo immediatamente validi `write/resize/kill`.\n- **In esecuzione**: il loop PTY bloccante gestisce lo stato del processo figlio, gli eventi del lettore, il heartbeat di cancellazione e i messaggi di controllo.\n- **Terminale chiuso**: uscita del figlio + completamento del lettore.\n- **Finalizzata**: `core` viene sempre reimpostato a `None` dopo il completamento del task di start (successo o errore).\n\nGuard di concorrenza:\n\n- avviare una sessione già in esecuzione restituisce `PTY session already running`.\n\n### Pattern di spawn/attach/write/read/terminate\n\n- PTY aperto tramite `portable_pty::native_pty_system().openpty(...)`.\n- Il comando viene attualmente eseguito come `sh -lc <command>` con override opzionali di `cwd` e env.\n- `write()` invia byte raw allo stdin del PTY.\n- `resize()` limita le dimensioni (`cols 20..400`, `rows 5..200`) e chiama il resize del master.\n- `kill()` segna l'esecuzione come cancellata e termina il processo figlio.\n\nPercorso di output:\n\n- un thread dedicato al lettore legge lo stream del master,\n- decodifica UTF-8 incrementale con sostituzione `U+FFFD` per i byte non validi,\n- i chunk vengono inoltrati tramite callback threadsafe N-API.\n\n### Semantica di cancellazione e timeout\n\n- `timeoutMs` e `AbortSignal` alimentano un `CancelToken`.\n- il loop chiama `ct.heartbeat()` periodicamente; l'abort attiva la terminazione del figlio.\n- la classificazione del timeout è basata su stringa (sottostringa `\"Timeout\"` nell'errore del heartbeat).\n\n### Comportamento in caso di errore\n\nLe superfici di errore includono:\n\n- errore di allocazione/apertura PTY,\n- errore di spawn PTY,\n- errore di acquisizione writer/reader,\n- errori di stato/attesa del figlio,\n- avvelenamento del lock,\n- disconnessione del canale di controllo (`PTY session is no longer available`).\n\nErrori nelle chiamate di controllo quando non in esecuzione:\n\n- `write/resize/kill` restituiscono `PTY session is not running`.\n\n## Sottosistema dell'albero di processi (`ps`)\n\n### Modello API\n\n- `killTree(pid, signal) -> number`\n- `listDescendants(pid) -> number[]`\n\nIl wrapper TS registra anche l'integrazione nativa del kill-tree nelle utilità condivise tramite `setNativeKillTree(native.killTree)`.\n\n### Implementazione specifica per piattaforma\n\n- **Linux**: legge ricorsivamente `/proc/<pid>/task/<pid>/children`.\n- **macOS**: utilizza `libproc` `proc_listchildpids`.\n- **Windows**: cattura la tabella dei processi con `CreateToolhelp32Snapshot`, costruisce una mappa parent->children, termina con `OpenProcess(PROCESS_TERMINATE)` + `TerminateProcess`.\n\n### Comportamento di kill-tree\n\n- I discendenti vengono raccolti ricorsivamente.\n- L'ordine di terminazione è dal basso verso l'alto (prima i discendenti più profondi) per ridurre il ri-parenting degli orfani.\n- Il pid radice viene terminato per ultimo.\n- Il valore restituito è il conteggio delle terminazioni riuscite.\n\nComportamento dei segnali:\n\n- POSIX: il `signal` fornito viene passato a `kill`.\n- Windows: `signal` viene ignorato; la terminazione è un processo terminate incondizionato.\n\n### Comportamento in caso di errore\n\nQuesto modulo è intenzionalmente non-throwing a livello di superficie API:\n\n- i rami dell'albero di processi mancanti/inaccessibili vengono saltati,\n- i fallimenti di kill per singolo pid vengono contati come non riusciti (non come errori),\n- una ricerca mancante restituisce tipicamente `[]` da `listDescendants` e `0` da `killTree`.\n\n## Sottosistema di analisi dei tasti (`keys`)\n\n### Modello API\n\nHelper esposti:\n\n- `parseKey(data, kittyProtocolActive)`\n- `matchesKey(data, keyId, kittyProtocolActive)`\n- `parseKittySequence(data)`\n- `matchesKittySequence(data, expectedCodepoint, expectedModifier)`\n- `matchesLegacySequence(data, keyName)`\n\n### Modello di analisi\n\nIl parser combina:\n\n- mappature dirette a singolo byte (`enter`, `tab`, `ctrl+<lettera>`, ASCII stampabile),\n- lookup O(1) di sequenze escape legacy (mappa PHF),\n- analisi di `modifyOtherKeys` xterm,\n- analisi del protocollo Kitty (`CSI u`, `CSI ~`, `CSI 1;...<lettera>`),\n- normalizzazione agli ID dei tasti (`ctrl+c`, `shift+tab`, `pageUp`, `f5`, ecc.).\n\nGestione dei modificatori:\n\n- per la corrispondenza dei tasti vengono confrontati solo i bit shift/alt/ctrl,\n- i bit di lock vengono mascherati prima dei confronti.\n\nComportamento del layout:\n\n- il fallback al layout di base è intenzionalmente limitato in modo che i layout rimappati non creino false corrispondenze per lettere/simboli ASCII.\n\n### Comportamento in caso di errore\n\n- Le sequenze non riconosciute o non valide producono `null` dalle funzioni di analisi.\n- Le funzioni di corrispondenza restituiscono `false` in caso di errore di analisi o mancata corrispondenza.\n- Nessuna superficie di errore generata per input di tasti malformati.\n\n## Mappatura API wrapper JS ↔ esportazioni Rust\n\n### Shell + PTY + Process\n\n| API wrapper TS | Esportazione Rust N-API | Note |\n|---|---|---|\n| `executeShell(options, onChunk?)` | `executeShell` (`execute_shell`) | Esecuzione shell monouso |\n| `new Shell(options?)` | classe `Shell` | Sessione shell persistente |\n| `shell.run(options, onChunk?)` | `Shell::run` | Riutilizza la sessione con flusso di controllo keepalive |\n| `shell.abort()` | `Shell::abort` | Interrompe l'esecuzione attiva per quella istanza shell |\n| `new PtySession()` | classe `PtySession` | Sessione PTY con stato |\n| `pty.start(options, onChunk?)` | `PtySession::start` | Esecuzione PTY interattiva |\n| `pty.write(data)` | `PtySession::write` | Passthrough raw stdin |\n| `pty.resize(cols, rows)` | `PtySession::resize` | Dimensioni terminale limitate |\n| `pty.kill()` | `PtySession::kill` | Termina forzatamente il processo figlio PTY attivo |\n| `killTree(pid, signal)` | `killTree` (`kill_tree`) | Terminazione dell'albero di processi partendo dai figli |\n| `listDescendants(pid)` | `listDescendants` (`list_descendants`) | Elenco ricorsivo dei discendenti |\n\n### Tasti\n\n| API wrapper TS | Esportazione Rust N-API | Note |\n|---|---|---|\n| `matchesKittySequence(data, cp, mod)` | `matchesKittySequence` (`matches_kitty_sequence`) | Corrispondenza Kitty codepoint+modificatore |\n| `parseKey(data, kittyProtocolActive)` | `parseKey` (`parse_key`) | Parser di key-id normalizzato |\n| `matchesLegacySequence(data, keyName)` | `matchesLegacySequence` (`matches_legacy_sequence`) | Controllo esatto sulla mappa delle sequenze legacy |\n| `parseKittySequence(data)` | `parseKittySequence` (`parse_kitty_sequence`) | Risultato strutturato dell'analisi Kitty |\n| `matchesKey(data, keyId, kittyProtocolActive)` | `matchesKey` (`matches_key`) | Matcher di tasti ad alto livello |\n\n## Note sulla pulizia delle sessioni abbandonate e sulla finalizzazione\n\n- **Sessione shell persistente**: se un'esecuzione viene cancellata/scaduta per timeout/in errore/con flusso di controllo non-keepalive, Rust elimina esplicitamente lo stato della sessione interna. Le esecuzioni normali con successo mantengono la sessione per il riutilizzo.\n- **Sessione PTY**: `core` viene sempre azzerato dopo il completamento di `start()`, inclusi i percorsi di errore.\n- **Nessun contratto di kill guidato da finalizzatore JS esplicito** è esposto dai wrapper; la pulizia è principalmente legata ai percorsi di completamento/cancellazione dell'esecuzione. I chiamanti dovrebbero utilizzare `timeoutMs`, `AbortSignal`, `shell.abort()` o `pty.kill()` per un teardown deterministico.\n",
	"it/natives/natives-text-search-pipeline.md": "---\ntitle: Pipeline nativa per testo e ricerca\ndescription: >-\n  Pipeline di ricerca testuale nativa con indicizzazione del contenuto dei file\n  basata su grep, glob e ripgrep.\nsidebar:\n  order: 6\n  label: Pipeline testo e ricerca\ni18n:\n  sourceHash: 0e93462fdd12\n  translator: machine\n---\n\n# Pipeline nativa per testo/ricerca\n\nQuesto documento descrive la superficie testo/ricerca di `@f5-sales-demo/pi-natives` (`grep`, `glob`, `text`, `highlight`), dai wrapper TypeScript agli export N-API Rust e viceversa verso gli oggetti risultato JS.\n\nLa terminologia segue `docs/natives-architecture.md`:\n\n- **Wrapper**: API TS in `packages/natives/src/*`\n- **Livello modulo Rust**: export N-API in `crates/pi-natives/src/*`\n- **Cache di scansione condivisa**: cache di voci directory basata su `fs_cache` usata dai flussi di discovery/ricerca\n\n## File di implementazione\n\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/glob_util.rs`\n- `crates/pi-natives/src/fs_cache.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/highlight.rs`\n- `crates/pi-natives/src/fd.rs`\n\n## Mappatura API JS ↔ export Rust\n\n| API wrapper JS | Export Rust (`#[napi]`, snake_case -> camelCase) | Modulo Rust |\n| --- | --- | --- |\n| `grep(options, onMatch?)` | `grep` | `grep.rs` |\n| `searchContent(content, options)` | `search` | `grep.rs` |\n| `hasMatch(content, pattern, options?)` | `hasMatch` | `grep.rs` |\n| `fuzzyFind(options)` | `fuzzyFind` | `fd.rs` |\n| `glob(options, onMatch?)` | `glob` | `glob.rs` |\n| `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `fs_cache.rs` |\n| `wrapTextWithAnsi(text, width)` | `wrapTextWithAnsi` | `text.rs` |\n| `truncateToWidth(text, maxWidth, ellipsis, pad)` | `truncateToWidth` | `text.rs` |\n| `sliceWithWidth(line, startCol, length, strict?)` | `sliceWithWidth` | `text.rs` |\n| `extractSegments(line, beforeEnd, afterStart, afterLen, strictAfter)` | `extractSegments` | `text.rs` |\n| `sanitizeText(text)` | `sanitizeText` | `text.rs` |\n| `visibleWidth(text)` | `visibleWidth` | `text.rs` |\n| `highlightCode(code, lang, colors)` | `highlightCode` | `highlight.rs` |\n| `supportsLanguage(lang)` | `supportsLanguage` | `highlight.rs` |\n| `getSupportedLanguages()` | `getSupportedLanguages` | `highlight.rs` |\n\n## Panoramica della pipeline per sottosistema\n\n## 1) Ricerca regex (`grep`, `searchContent`, `hasMatch`)\n\n### Flusso di input/opzioni\n\n1. Il wrapper TS inoltra le opzioni al modulo nativo:\n   - `grep/index.ts` passa `options` quasi invariato e avvolge il callback da `(match) => void` nella forma di callback napi threadsafe `(err, match)`.\n   - `searchContent` e `hasMatch` passano direttamente stringa/`Uint8Array`.\n2. Le struct di opzioni Rust in `grep.rs` deserializzano i campi camelCase (`ignoreCase`, `maxCount`, `contextBefore`, `contextAfter`, `maxColumns`, `timeoutMs`).\n3. `grep` crea un `CancelToken` da `timeoutMs` + `AbortSignal` ed esegue all'interno di `task::blocking(\"grep\", ...)`.\n\n### Rami di esecuzione\n\n- **Ramo in-memory (utilità pura)**\n  - `search` → `search_sync` → `run_search` sui byte di contenuto forniti.\n  - Nessuna scansione del filesystem, nessun `fs_cache`.\n- **Ramo file singolo (dipendente dal filesystem)**\n  - `grep_sync` risolve il percorso, verifica che i metadati siano di tipo file, legge fino a `MAX_FILE_BYTES` per file (`4 MiB`) attraverso il matcher ripgrep.\n- **Ramo directory (dipendente dal filesystem)**\n  - Ricerca facoltativa nella cache tramite `fs_cache::get_or_scan` quando `cache: true`.\n  - Scansione aggiornata tramite `fs_cache::force_rescan` quando `cache: false`.\n  - Ricontrollo facoltativo del risultato vuoto quando l'età della cache supera `empty_recheck_ms()`.\n  - Filtraggio delle voci: solo file + filtro glob opzionale (`glob_util`) + mappatura filtro tipo opzionale (`js`, `ts`, `rust`, ecc.).\n\n### Semantica di ricerca/raccolta\n\n- Motore regex: `grep_regex::RegexMatcherBuilder` con `ignoreCase` e `multiline`.\n- Risoluzione del contesto:\n  - `contextBefore/contextAfter` sovrascrivono il `context` legacy.\n  - Le modalità non-content azzerano la raccolta del contesto.\n- Modalità di output:\n  - `content` => un `GrepMatch` per corrispondenza.\n  - `count` e `filesWithMatches` mappano entrambi su voci in stile count (`lineNumber=0`, `line=\"\"`, `matchCount` impostato).\n- Limiti:\n  - `offset` globale e `maxCount` applicati tra i file.\n  - Il percorso parallelo viene usato solo quando `maxCount` non è impostato e `offset == 0`; altrimenti il percorso sequenziale preserva la semantica deterministica di offset/limite globale.\n\n### Formattazione del risultato verso JS\n\n- I campi Rust `SearchResult`/`GrepResult` mappano sui tipi TS tramite conversione dei campi oggetto N-API.\n- I contatori vengono limitati a `u32` prima di attraversare N-API.\n- I booleani opzionali vengono omessi a meno che non siano veri in alcuni percorsi (`limitReached`).\n- Il callback in streaming riceve ogni `GrepMatch` formattato (voce di contenuto o conteggio).\n\n### Comportamento in caso di errore\n\n- `searchContent` restituisce `SearchResult.error` per errori di regex/ricerca invece di lanciare eccezioni.\n- `grep` rifiuta in caso di errori gravi (percorso non valido, glob/regex non valido, timeout di cancellazione/interruzione).\n- `hasMatch` restituisce `Result<bool>` e lancia eccezioni per errori di pattern non valido/decodifica UTF-8.\n- Gli errori di apertura/ricerca file nelle scansioni multi-file vengono ignorati per ogni file; la scansione continua.\n\n### Gestione di regex malformata\n\n`grep.rs` sanifica le parentesi graffe prima della compilazione regex:\n\n- Le parentesi graffe simili a ripetizioni non valide vengono escape (`{`/`}` -> `\\{`/`\\}`) quando non possono formare `{N}`, `{N,}`, `{N,M}`.\n- Questo impedisce ai frammenti comuni di template letterale (ad esempio `${platform}`) di fallire come ripetizione malformata.\n- La sintassi regex non valida rimanente restituisce ancora un errore regex.\n\n## 2) Discovery dei file (`glob`) e ricerca fuzzy dei percorsi (`fuzzyFind`)\n\n`glob` e `fuzzyFind` condividono le scansioni `fs_cache`; la logica di corrispondenza è diversa.\n\n### Flusso `glob`\n\n1. Wrapper TS (`glob/index.ts`):\n   - `path.resolve(options.path)`.\n   - Valori predefiniti: `pattern=\"*\"`, `hidden=false`, `gitignore=true`, `recursive=true`.\n2. Rust `glob` costruisce `GlobConfig` e compila il pattern tramite `glob_util::compile_glob`.\n3. Sorgente delle voci:\n   - `cache=true` => `get_or_scan` + eventuale `force_rescan` per voci vuote obsolete.\n   - `cache=false` => `force_rescan(..., store=false)` (solo aggiornamento).\n4. Filtraggio:\n   - Salta sempre `.git`.\n   - Salta `node_modules` a meno che non richiesto (`includeNodeModules` o pattern che menziona node_modules).\n   - Applica la corrispondenza glob.\n   - Applica il filtro per tipo di file; i filtri symlink `file/dir` risolvono i metadati del target.\n5. Ordinamento opzionale per mtime decrescente (`sortByMtime`) prima di troncare a `maxResults`.\n\n### Flusso `fuzzyFind` (implementato in `fd.rs`)\n\n1. Il wrapper TS è esportato dal modulo `grep`, ma l'implementazione Rust si trova in `fd.rs`.\n2. Sorgente di scansione condivisa da `fs_cache` con la stessa logica cache/no-cache e policy di ricontrollo per voci vuote obsolete.\n3. Punteggio:\n   - punteggio fuzzy basato su corrispondenza esatta / starts-with / contains / sottosequenza\n   - percorso di punteggio normalizzato per separatori/punteggiatura\n   - bonus directory e tie-break deterministico (`punteggio desc`, poi `percorso asc`)\n4. Le voci symlink sono escluse dai risultati fuzzy.\n\n### Comportamento in caso di errore\n\n- Pattern glob non valido => errore da `glob_util::compile_glob`.\n- La radice di ricerca deve essere una directory esistente (`resolve_search_path`), altrimenti errore.\n- Cancellazione/timeout si propagano come errori di interruzione tramite controlli `CancelToken::heartbeat()` nei cicli.\n\n### Gestione di glob malformato\n\n`glob_util::build_glob_pattern` è tollerante:\n\n- Normalizza `\\` in `/`.\n- Aggiunge automaticamente il prefisso `**/` ai pattern ricorsivi semplici quando `recursive=true`.\n- Chiude automaticamente i gruppi di alternazione `{...` non bilanciati prima della compilazione.\n\n## 3) Ciclo di vita della scansione/cache condivisa (`fs_cache`)\n\n`fs_cache` memorizza i risultati di scansione come voci relative normalizzate (`path`, `fileType`, `mtime` opzionale) indicizzate per:\n\n- radice di ricerca canonica\n- `include_hidden`\n- `use_gitignore`\n\n### Transizioni di stato della cache\n\n1. **Miss / disabilitata**\n   - TTL è `0` o chiave assente/scaduta -> `collect_entries` aggiornato.\n2. **Hit**\n   - Età voce `< cache_ttl_ms()` -> restituisce le voci in cache + `cache_age_ms`.\n3. **Ricontrollo per voci vuote obsolete** (policy chiamante in `glob`/`grep`/`fd`)\n   - Se la query produce zero corrispondenze e `cache_age_ms >= empty_recheck_ms()`, forza una nuova scansione.\n4. **Invalidazione**\n   - `invalidateFsScanCache(path?)`:\n     - nessun argomento: cancella tutte le chiavi\n     - argomento path: rimuove le chiavi la cui radice è prefisso del percorso target\n\n### Compromesso per risultati obsoleti\n\n- La cache privilegia le scansioni ripetute a bassa latenza rispetto alla coerenza immediata.\n- La finestra TTL può restituire positivi/negativi obsoleti.\n- Il ricontrollo per risultati vuoti riduce i falsi negativi obsoleti per le scansioni in cache più vecchie, al costo di una scansione extra.\n- L'invalidazione esplicita è il meccanismo di correttezza previsto dopo le mutazioni dei file.\n\n## 4) Utilità per testo ANSI (`text`)\n\nSi tratta di utilità pure in-memory (nessuna scansione del filesystem).\n\n### Confini e responsabilità\n\n- **`text.rs` gestisce la semantica delle celle terminale**:\n  - Analisi delle sequenze ANSI\n  - larghezza e slicing con consapevolezza dei grafemi\n  - comportamento di wrap/truncate/sanitize\n- **La troncatura delle righe in `grep.rs` (`maxColumns`) è separata**:\n  - semplice troncatura ai confini dei caratteri delle righe corrispondenti con `...`\n  - non preserva lo stato ANSI e non è consapevole della larghezza delle celle terminale\n\n### Comportamenti principali\n\n- `wrapTextWithAnsi`: esegue il wrap in base alla larghezza visibile, riportando i codici SGR attivi sulle righe mandate a capo.\n- `truncateToWidth`: troncatura a celle visibili con policy per i puntini di sospensione (`Unicode`, `Ascii`, `Omit`), padding opzionale a destra e percorso veloce che restituisce la stringa JS originale se invariata.\n- `sliceWithWidth`: slicing per colonna con applicazione opzionale della larghezza esatta.\n- `extractSegments`: estrae i segmenti prima/dopo attorno a un overlay ripristinando lo stato ANSI per il segmento `after`.\n- `sanitizeText`: rimuove escape ANSI e caratteri di controllo, elimina i surrogate isolati, normalizza CR/LF rimuovendo `\\r`.\n- `visibleWidth`: conta le celle terminale visibili (i tab usano `TAB_WIDTH` fisso dall'implementazione Rust).\n\n### Comportamento in caso di errore\n\nLe funzioni di testo restituiscono generalmente output trasformato deterministico; gli errori sono limitati ai confini di conversione delle stringhe JS (errori di conversione degli argomenti N-API).\n\n## 5) Evidenziazione della sintassi (`highlight`)\n\n`highlight.rs` è una trasformazione pura (nessun FS, nessuna cache).\n\n### Flusso\n\n1. Il wrapper inoltra `code`, `lang` opzionale e palette di colori ANSI.\n2. Rust risolve la sintassi tramite:\n   - ricerca per token/nome\n   - ricerca per estensione\n   - tabella alias di fallback (`ts/tsx/js -> JavaScript`, ecc.)\n   - fallback alla sintassi plain text quando non risolta\n3. Analizza ogni riga con `ParseState` di syntect e stack degli scope.\n4. Mappa gli scope su 11 categorie semantiche di colore e inietta/ripristina i codici colore ANSI.\n\n### Comportamento in caso di errore\n\n- L'errore di analisi per riga non causa il fallimento della chiamata: quella riga viene aggiunta senza evidenziazione e l'elaborazione continua.\n- Il linguaggio sconosciuto/non supportato torna alla sintassi plain text.\n\n## Flussi di utilità pura vs dipendenti dal filesystem\n\n| Flusso | Accesso al filesystem | Cache condivisa | Note |\n| --- | --- | --- | --- |\n| `searchContent` / `hasMatch` | No | No | regex solo sui byte/stringa forniti |\n| Funzioni del modulo `text` | No | No | solo ANSI/larghezza/sanificazione |\n| Funzioni del modulo `highlight` | No | No | solo sintassi + colorazione ANSI |\n| `glob` | Sì | Opzionale | scansioni directory + filtraggio glob |\n| `fuzzyFind` | Sì | Opzionale | scansioni directory + punteggio fuzzy |\n| `grep` (percorso file/dir) | Sì | Opzionale (modalità dir) | ripgrep sui file, filtri/callback opzionali |\n\n## Riepilogo del ciclo di vita end-to-end\n\n1. Il chiamante invoca il wrapper TS con opzioni tipizzate.\n2. Il wrapper normalizza i valori predefiniti (in particolare `glob`) e li inoltra all'export `native.*`.\n3. Rust valida/normalizza le opzioni e costruisce la configurazione del matcher/ricerca.\n4. Per i flussi filesystem, le voci vengono scansionate (cache hit/miss/rescan) poi filtrate/valorizzate.\n5. I cicli worker chiamano periodicamente il heartbeat di cancellazione; timeout/interruzione possono terminare l'esecuzione.\n6. Rust formatta gli output in oggetti N-API (`lineNumber`, `matchCount`, `limitReached`, ecc.).\n7. Il wrapper TS restituisce oggetti JS tipizzati (e callback opzionali per corrispondenza per `grep`/`glob`).\n",
	"it/natives/porting-to-natives.md": "---\ntitle: Portare a pi-natives (N-API) — Note sul campo\ndescription: >-\n  Note sul campo per la migrazione del codice Node.js child_process e shell al\n  layer nativo Rust N-API.\nsidebar:\n  order: 9\n  label: Portare a pi-natives\ni18n:\n  sourceHash: 4f5150286535\n  translator: machine\n---\n\n# Portare a pi-natives (N-API) — Note sul campo\n\nQuesta è una guida pratica per spostare i percorsi critici in `crates/pi-natives` e collegarli attraverso i binding JS. Esiste per evitare che gli stessi errori si ripetano.\n\n## Quando effettuare il porting\n\nEffettuare il porting quando una qualsiasi di queste condizioni è vera:\n\n- Il percorso critico viene eseguito nei cicli di rendering, negli aggiornamenti rapidi dell'UI o in elaborazioni batch di grandi dimensioni.\n- Le allocazioni JS dominano (creazione continua di stringhe, backtracking delle regex, array di grandi dimensioni).\n- Si dispone già di un baseline JS e si possono confrontare entrambe le versioni fianco a fianco.\n- Il lavoro è CPU-bound o I/O bloccante che può essere eseguito sul thread pool di libuv.\n- Il lavoro è I/O asincrono che può essere eseguito sul runtime di Tokio (ad esempio, esecuzione shell).\n\nEvitare i porting che dipendono da stato esclusivamente JS o da import dinamici. Le esportazioni N-API dovrebbero essere pure, con dati in ingresso e dati in uscita. Il lavoro di lunga durata dovrebbe passare attraverso `task::blocking` (CPU-bound/I/O bloccante) o `task::future` (I/O asincrono) con cancellazione.\n\n## Anatomia di un'esportazione nativa\n\n**Lato Rust:**\n\n- L'implementazione risiede in `crates/pi-natives/src/<module>.rs`. Se si aggiunge un nuovo modulo, registrarlo in `crates/pi-natives/src/lib.rs`.\n- Esportare con `#[napi]`; le esportazioni in snake_case vengono convertite automaticamente in camelCase. Usare `js_name` esplicito solo per veri alias/nomi non predefiniti. Usare `#[napi(object)]` per le struct.\n- Usare `task::blocking(tag, cancel_token, work)` (vedere `crates/pi-natives/src/task.rs`) per lavoro CPU-bound o bloccante. Usare `task::future(env, tag, work)` per lavoro asincrono che necessita di Tokio (ad esempio, sessioni shell). Passare un `CancelToken` quando si espone `timeoutMs` o `AbortSignal`.\n\n**Lato JS:**\n\n- `packages/natives/src/bindings.ts` contiene l'interfaccia base `NativeBindings`.\n- `packages/natives/src/<module>/types.ts` definisce i tipi TS e augmenta `NativeBindings` tramite declaration merging.\n- `packages/natives/src/native.ts` importa ciascun file `<module>/types.ts` per attivare le dichiarazioni.\n- `packages/natives/src/<module>/index.ts` wrappa il binding `native` da `packages/natives/src/native.ts`.\n- `packages/natives/src/native.ts` carica l'addon e `validateNative` verifica le esportazioni richieste.\n- `packages/natives/src/index.ts` ri-esporta il wrapper per i chiamanti in `packages/*`.\n\n## Checklist per il porting\n\n1. **Aggiungere l'implementazione Rust**\n\n- Inserire la logica principale in una funzione Rust pura.\n- Se è un nuovo modulo, aggiungerlo a `crates/pi-natives/src/lib.rs`.\n- Esporlo con `#[napi]` in modo che la mappatura predefinita snake_case -> camelCase rimanga consistente.\n- Mantenere le firme owned e semplici: `String`, `Vec<String>`, `Uint8Array` o `Either<JsString, Uint8Array>` per input di stringhe/byte di grandi dimensioni.\n- Per lavoro CPU-bound o bloccante, usare `task::blocking`; per lavoro asincrono, usare `task::future`. Passare un `CancelToken` e chiamare `heartbeat()` all'interno dei cicli lunghi.\n\n2. **Collegare i binding JS**\n\n- Aggiungere i tipi e l'augmentation di `NativeBindings` in `packages/natives/src/<module>/types.ts`.\n- Importare `./<module>/types` in `packages/natives/src/native.ts` per attivare il declaration merging.\n- Aggiungere un wrapper in `packages/natives/src/<module>/index.ts` che chiama `native`.\n- Ri-esportare da `packages/natives/src/index.ts`.\n\n3. **Aggiornare la validazione nativa**\n\n- Aggiungere `checkFn(\"newExport\")` in `validateNative` (`packages/natives/src/native.ts`).\n\n4. **Aggiungere benchmark**\n\n- Posizionare i benchmark accanto al pacchetto proprietario (`packages/tui/bench`, `packages/natives/bench` o `packages/coding-agent/bench`).\n- Includere una versione baseline JS e una versione nativa nella stessa esecuzione.\n- Usare `Bun.nanoseconds()` e un conteggio di iterazioni fisso.\n- Mantenere gli input del benchmark piccoli e realistici (dati effettivi osservati nel percorso critico).\n\n5. **Compilare il binario nativo**\n\n- `bun --cwd=packages/natives run build`\n- Usare `bun --cwd=packages/natives run build` e impostare `PI_DEV=1` se si desiderano diagnostiche del loader durante i test.\n\n6. **Eseguire il benchmark**\n\n- `bun run packages/<pkg>/bench/<bench>.ts` (oppure `bun --cwd=packages/natives run bench`)\n\n7. **Decidere sull'utilizzo**\n\n- Se il nativo è più lento, **mantenere JS** e lasciare l'esportazione nativa inutilizzata.\n- Se il nativo è più veloce, passare i punti di chiamata al wrapper nativo.\n\n## Punti critici e come evitarli\n\n### 1) `pi_natives.node` obsoleto impedisce le nuove esportazioni\n\nIl loader preferisce il binario con tag della piattaforma in `packages/natives/native` (`pi_natives.<platform>-<arch>.node`). `PI_DEV=1` ora abilita solo le diagnostiche del loader; non passa più a un nome file addon di sviluppo separato. Esiste anche un fallback `pi_natives.node`. I binari compilati vengono estratti in `~/.xcsh/natives/<version>/pi_natives.<platform>-<arch>.node`. Se uno qualsiasi di questi è obsoleto, le esportazioni non si aggiorneranno.\n\n**Soluzione:** rimuovere il file obsoleto prima di ricompilare.\n\n```bash\nrm packages/natives/native/pi_natives.linux-x64.node\nrm packages/natives/native/pi_natives.node\nbun --cwd=packages/natives run build\n```\n\nSe si sta eseguendo un binario compilato, eliminare la directory dell'addon in cache:\n\n```bash\nrm -rf ~/.xcsh/natives/<version>\n```\n\nQuindi verificare che l'esportazione esista nel binario:\n\n```bash\nbun -e 'const tag = `${process.platform}-${process.arch}`; const mod = require(`./packages/natives/native/pi_natives.${tag}.node`); console.log(Object.keys(mod).includes(\"newExport\"));'\n```\n\n### 2) Errori \"Missing exports\" da `validateNative`\n\nQuesto è **positivo** — previene disallineamenti silenti. Quando si vede questo:\n\n```\nNative addon missing exports ... Missing: visibleWidth\n```\n\nsignifica che il binario è obsoleto, il nome dell'esportazione Rust (o l'alias esplicito quando usato) non corrisponde al nome JS, oppure l'esportazione non è mai stata compilata. Correggere la build e il disallineamento dei nomi, non indebolire la validazione.\n\n### 3) Mismatch della firma Rust\n\nMantenerla semplice e owned. `String`, `Vec<String>` e `Uint8Array` funzionano. Evitare riferimenti come `&str` nelle esportazioni pubbliche. Se si necessita di dati strutturati, avvolgerli in struct `#[napi(object)]`.\n\n### 4) Errori nei benchmark\n\n- Non confrontare input o allocazioni diverse.\n- Mantenere JS e nativo con array di input identici.\n- Eseguire entrambi nello stesso file di benchmark per evitare scostamenti.\n\n## Template per benchmark\n\n```ts\nconst ITERATIONS = 2000;\n\nfunction bench(name: string, fn: () => void): number {\n const start = Bun.nanoseconds();\n for (let i = 0; i < ITERATIONS; i++) fn();\n const elapsed = (Bun.nanoseconds() - start) / 1e6;\n console.log(`${name}: ${elapsed.toFixed(2)}ms total (${(elapsed / ITERATIONS).toFixed(6)}ms/op)`);\n return elapsed;\n}\n\nbench(\"feature/js\", () => {\n jsImpl(sample);\n});\n\nbench(\"feature/native\", () => {\n nativeImpl(sample);\n});\n```\n\n## Checklist di verifica\n\n- `validateNative` passa (nessuna esportazione mancante).\n- `NativeBindings` è augmentato in `packages/natives/src/<module>/types.ts` e il wrapper è ri-esportato in `packages/natives/src/index.ts`.\n- `Object.keys(require(...))` include la nuova esportazione.\n- Numeri dei benchmark registrati nella PR/note.\n- Punto di chiamata aggiornato **solo se** il nativo è più veloce o equivalente.\n\n## Regola generale\n\n- Se il nativo è più lento, **non effettuare il passaggio**. Mantenere l'esportazione per lavoro futuro, ma la TUI dovrebbe rimanere sul percorso più veloce.\n- Se il nativo è più veloce, passare al punto di chiamata nativo e mantenere il benchmark attivo per intercettare regressioni.\n",
	"it/providers/models.md": "---\ntitle: Configurazione di modelli e provider\ndescription: >-\n  Registro dei modelli e configurazione del provider tramite models.yml con\n  routing, fallback e prezzi.\nsidebar:\n  order: 1\n  label: Modelli e provider\ni18n:\n  sourceHash: 8053df967ff6\n  translator: machine\n---\n\n# Configurazione di modelli e provider (`models.yml`)\n\nQuesto documento descrive come il coding-agent carica attualmente i modelli, applica le sostituzioni, risolve le credenziali e sceglie i modelli in fase di esecuzione.\n\n## Cosa controlla il comportamento dei modelli\n\nFile di implementazione principali:\n\n- `src/config/model-registry.ts` — carica i modelli integrati e personalizzati, le sostituzioni del provider, il rilevamento in fase di esecuzione, l'integrazione con l'autenticazione\n- `src/config/model-resolver.ts` — analizza i pattern dei modelli e seleziona i modelli iniziali/smol/slow\n- `src/config/settings-schema.ts` — impostazioni relative ai modelli (`modelRoles`, preferenze di trasporto del provider)\n- `src/session/auth-storage.ts` — ordine di risoluzione per chiavi API e OAuth\n- `packages/ai/src/models.ts` e `packages/ai/src/types.ts` — provider/modelli integrati e tipi `Model`/`compat`\n\n## Posizione del file di configurazione e comportamento legacy\n\nPercorso di configurazione predefinito:\n\n- `~/.xcsh/agent/models.yml`\n\nComportamento legacy ancora presente:\n\n- Se `models.yml` non è presente e `models.json` esiste nella stessa posizione, viene migrato in `models.yml`.\n- I percorsi di configurazione `.json` / `.jsonc` espliciti sono ancora supportati quando passati programmaticamente a `ModelRegistry`.\n\n## Struttura di `models.yml`\n\n```yaml\nconfigVersion: 1  # optional — written by auto-config, used for migration detection\nproviders:\n  <provider-id>:\n    # provider-level config\nequivalence:\n  overrides:\n    <provider-id>/<model-id>: <canonical-model-id>\n  exclude:\n    - <provider-id>/<model-id>\n```\n\n`configVersion` è un intero opzionale scritto dal sistema di configurazione automatica. Quando presente, xcsh lo utilizza per rilevare configurazioni obsolete e aggiornarle automaticamente.\n\n`provider-id` è la chiave canonica del provider utilizzata per la selezione e la ricerca dell'autenticazione.\n\n`equivalence` è opzionale e configura il raggruppamento canonico dei modelli al di sopra dei modelli concreti del provider:\n\n- `overrides` mappa un selettore concreto esatto (`provider/modelId`) a un id canonico ufficiale upstream\n- `exclude` esclude un selettore concreto dal raggruppamento canonico\n\n## Campi a livello di provider\n\n```yaml\nproviders:\n  my-provider:\n    baseUrl: https://api.example.com/v1\n    apiKey: MY_PROVIDER_API_KEY\n    api: openai-completions\n    headers:\n      X-Team: platform\n    authHeader: true\n    auth: apiKey\n    discovery:\n      type: ollama\n    modelOverrides:\n      some-model-id:\n        name: Renamed model\n    models:\n      - id: some-model-id\n        name: Some Model\n        api: openai-completions\n        reasoning: false\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 128000\n        maxTokens: 16384\n        headers:\n          X-Model: value\n        compat:\n          supportsStore: true\n          supportsDeveloperRole: true\n          supportsReasoningEffort: true\n          maxTokensField: max_completion_tokens\n          openRouterRouting:\n            only: [anthropic]\n          vercelGatewayRouting:\n            order: [anthropic, openai]\n          extraBody:\n            gateway: m1-01\n            controller: mlx\n```\n\n### Valori `api` consentiti per provider/modello\n\n- `openai-completions`\n- `openai-responses`\n- `openai-codex-responses`\n- `azure-openai-responses`\n- `anthropic-messages`\n- `google-generative-ai`\n- `google-vertex`\n\n### Valori consentiti per auth/discovery\n\n- `auth`: `apiKey` (predefinito) o `none`\n- `discovery.type`: `ollama`\n\n## Regole di validazione (attuali)\n\n### Provider personalizzato completo (`models` non vuoto)\n\nObbligatori:\n\n- `baseUrl`\n- `apiKey` a meno che non sia impostato `auth: none`\n- `api` a livello di provider o per ciascun modello\n\n### Provider solo per sostituzione (`models` assente o vuoto)\n\nDeve definire almeno uno dei seguenti:\n\n- `baseUrl`\n- `modelOverrides`\n- `discovery`\n\n### Discovery\n\n- `discovery` richiede `api` a livello di provider.\n\n### Controlli sui valori del modello\n\n- `id` obbligatorio\n- `contextWindow` e `maxTokens` devono essere positivi se forniti\n\n## Ordine di unione e sostituzione\n\nPipeline di ModelRegistry (durante l'aggiornamento):\n\n1. Carica provider/modelli integrati da `@f5-sales-demo/pi-ai`.\n2. Carica la configurazione personalizzata da `models.yml`.\n3. Applica le sostituzioni del provider (`baseUrl`, `headers`) ai modelli integrati.\n4. Applica `modelOverrides` (per provider + id modello).\n5. Unisce i `models` personalizzati:\n   - lo stesso `provider + id` sostituisce quello esistente\n   - altrimenti viene aggiunto in coda\n6. Applica i modelli scoperti in fase di esecuzione (attualmente Ollama e LM Studio), quindi riapplica le sostituzioni del modello.\n\n## Equivalenza canonica dei modelli e coalescenza\n\nIl registro mantiene ogni modello concreto del provider e poi costruisce un livello canonico al di sopra di essi.\n\nGli id canonici sono id ufficiali upstream, ad esempio:\n\n- `claude-opus-4-6`\n- `claude-haiku-4-5`\n- `gpt-5.3-codex`\n\n### Configurazione dell'equivalenza in `models.yml`\n\nEsempio:\n\n```yaml\nproviders:\n  zenmux:\n    baseUrl: https://api.zenmux.example/v1\n    apiKey: ZENMUX_API_KEY\n    api: openai-codex-responses\n    models:\n      - id: codex\n        name: Zenmux Codex\n        reasoning: true\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 200000\n        maxTokens: 32768\n\nequivalence:\n  overrides:\n    zenmux/codex: gpt-5.3-codex\n    p-codex/codex: gpt-5.3-codex\n  exclude:\n    - demo/codex-preview\n```\n\nOrdine di costruzione per il raggruppamento canonico:\n\n1. sostituzione utente esatta da `equivalence.overrides`\n2. corrispondenze di id ufficiali incluse nei metadati del modello integrato\n3. normalizzazione euristica conservativa per varianti gateway/provider\n4. fallback all'id proprio del modello concreto\n\nLe euristiche attuali sono intenzionalmente limitate:\n\n- i prefissi upstream incorporati possono essere rimossi quando presenti, ad esempio `anthropic/...` o `openai/...`\n- le varianti di versione con punti e trattini possono essere normalizzate solo quando mappano a un id ufficiale esistente, ad esempio `4.6 -> 4-6`\n- le famiglie o versioni ambigue non vengono unite senza una corrispondenza inclusa o una sostituzione esplicita\n\n### Comportamento della risoluzione canonica\n\nQuando più varianti concrete condividono un id canonico, la risoluzione utilizza:\n\n1. disponibilità e autenticazione\n2. `modelProviderOrder` in `config.yml`\n3. ordine del registro/provider esistente se `modelProviderOrder` non è impostato\n\nI provider disabilitati o non autenticati vengono ignorati.\n\nLo stato della sessione e le trascrizioni continuano a registrare il provider/modello concreto che ha effettivamente eseguito il turno.\n\nValori predefiniti del provider rispetto alle sostituzioni per modello:\n\n- Gli `headers` del provider costituiscono la base.\n- Gli `headers` del modello sovrascrivono le chiavi degli header del provider.\n- `modelOverrides` può sovrascrivere i metadati del modello (`name`, `reasoning`, `input`, `cost`, `contextWindow`, `maxTokens`, `headers`, `compat`, `contextPromotionTarget`).\n- `compat` viene unito in profondità per i blocchi di routing annidati (`openRouterRouting`, `vercelGatewayRouting`, `extraBody`).\n\n## Integrazione con il rilevamento in fase di esecuzione\n\n### Rilevamento implicito di Ollama\n\nSe `ollama` non è configurato esplicitamente, il registro aggiunge un provider rilevabile in modo implicito:\n\n- provider: `ollama`\n- api: `openai-completions`\n- URL base: `OLLAMA_BASE_URL` o `http://127.0.0.1:11434`\n- modalità di autenticazione: senza chiave (comportamento `auth: none`)\n\nIl rilevamento in fase di esecuzione chiama `GET /api/tags` su Ollama e sintetizza le voci del modello con valori predefiniti locali.\n\n### Rilevamento implicito di llama.cpp\n\nSe `llama.cpp` non è configurato esplicitamente, il registro aggiunge un provider rilevabile in modo implicito:\nNota: utilizza la nuova API anthropic messages invece di openai-completions.\n\n- provider: `llama.cpp`\n- api: `openai-responses`\n- URL base: `LLAMA_CPP_BASE_URL` o `http://127.0.0.1:8080`\n- modalità di autenticazione: senza chiave (comportamento `auth: none`)\n\nIl rilevamento in fase di esecuzione chiama `GET models` su llama.cpp e sintetizza le voci del modello con valori predefiniti locali.\n\n### Rilevamento implicito di LM Studio\n\nSe `lm-studio` non è configurato esplicitamente, il registro aggiunge un provider rilevabile in modo implicito:\n\n- provider: `lm-studio`\n- api: `openai-completions`\n- URL base: `LM_STUDIO_BASE_URL` o `http://127.0.0.1:1234/v1`\n- modalità di autenticazione: senza chiave (comportamento `auth: none`)\n\nIl rilevamento in fase di esecuzione recupera i modelli (`GET /models`) e sintetizza le voci del modello con valori predefiniti locali.\n\n### Rilevamento esplicito del provider\n\nÈ possibile configurare il rilevamento manualmente:\n\n```yaml\nproviders:\n  ollama:\n    baseUrl: http://127.0.0.1:11434\n    api: openai-completions\n    auth: none\n    discovery:\n      type: ollama\n      \n  llama.cpp:\n    baseUrl: http://127.0.0.1:8080\n    api: openai-responses\n    auth: none\n    discovery:\n      type: llama.cpp\n```\n\n### Registrazione di provider tramite estensioni\n\nLe estensioni possono registrare provider in fase di esecuzione (`pi.registerProvider(...)`), inclusi:\n\n- sostituzione/aggiunta di modelli per un provider\n- registrazione di gestori di stream personalizzati per nuovi ID API\n- registrazione di provider OAuth personalizzati\n\n## Ordine di risoluzione per autenticazione e chiavi API\n\nQuando si richiede una chiave per un provider, l'ordine effettivo è:\n\n1. Sostituzione in fase di esecuzione (CLI `--api-key`)\n2. Credenziale chiave API memorizzata in `agent.db`\n3. Credenziale OAuth memorizzata in `agent.db` (con aggiornamento)\n4. Mappatura tramite variabile d'ambiente (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, ecc.)\n5. Resolver di fallback di ModelRegistry (provider `apiKey` da `models.yml`, semantica nome-env-o-letterale)\n\nComportamento di `apiKey` in `models.yml`:\n\n- Il valore viene prima trattato come nome di variabile d'ambiente.\n- Se non esiste alcuna variabile d'ambiente, la stringa letterale viene utilizzata come token.\n\nSe `authHeader: true` e il provider `apiKey` è impostato, i modelli ricevono:\n\n- header `Authorization: Bearer <chiave-risolta>` iniettato.\n\nProvider senza chiave:\n\n- I provider contrassegnati con `auth: none` vengono considerati disponibili senza credenziali.\n- `getApiKey*` restituisce `kNoAuth` per essi.\n\n## Disponibilità dei modelli vs tutti i modelli\n\n- `getAll()` restituisce il registro dei modelli caricati (integrati + personalizzati uniti + scoperti).\n- `getAvailable()` filtra i modelli che sono senza chiave o hanno un'autenticazione risolvibile.\n\nPertanto un modello può esistere nel registro ma non essere selezionabile finché non è disponibile l'autenticazione.\n\n## Risoluzione dei modelli in fase di esecuzione\n\n### Analisi di CLI e pattern\n\n`model-resolver.ts` supporta:\n\n- `provider/modelId` esatto\n- id di modello canonico esatto\n- id di modello esatto (provider dedotto)\n- corrispondenza fuzzy/per sottostringa\n- pattern glob nell'ambito di `--models` (ad es. `openai/*`, `*sonnet*`)\n- suffisso opzionale `:thinkingLevel` (`off|minimal|low|medium|high|xhigh`)\n\n`--provider` è legacy; è preferibile usare `--model`.\n\nPrecedenza della risoluzione per selettori esatti:\n\n1. `provider/modelId` esatto bypassa la coalescenza\n2. id canonico esatto risolve tramite l'indice canonico\n3. id concreto semplice esatto funziona comunque\n4. la corrispondenza fuzzy e glob viene eseguita dopo i percorsi esatti\n\n### Priorità di selezione del modello iniziale\n\n`findInitialModel(...)` utilizza questo ordine:\n\n1. provider+modello esplicito da CLI\n2. primo modello nell'ambito (se non si riprende una sessione)\n3. provider/modello predefinito salvato\n4. valori predefiniti di provider noti (ad es. OpenAI/Anthropic/ecc.) tra i modelli disponibili\n5. primo modello disponibile\n\n### Alias di ruolo e impostazioni\n\nRuoli dei modelli supportati:\n\n- `default`, `smol`, `slow`, `plan`, `commit`\n\nGli alias di ruolo come `pi/smol` si espandono tramite `settings.modelRoles`. Ogni valore di ruolo può anche aggiungere un selettore di pensiero come `:minimal`, `:low`, `:medium` o `:high`.\n\nSe un ruolo punta a un altro ruolo, il modello di destinazione eredita normalmente e qualsiasi suffisso esplicito sul ruolo di riferimento prevale per quell'uso specifico del ruolo.\n\nImpostazioni correlate:\n\n- `modelRoles` (record)\n- `enabledModels` (elenco di pattern nell'ambito)\n- `modelProviderOrder` (precedenza globale del provider canonico)\n- `providers.kimiApiFormat` (formato della richiesta `openai` o `anthropic`)\n- `providers.openaiWebsockets` (preferenza websocket `auto|off|on` per il trasporto OpenAI Codex)\n\n`modelRoles` può memorizzare:\n\n- `provider/modelId` per fissare una variante concreta del provider\n- un id canonico come `gpt-5.3-codex` per consentire la coalescenza del provider\n\nPer `enabledModels` e CLI `--models`:\n\n- gli id canonici esatti si espandono a tutte le varianti concrete in quel gruppo canonico\n- le voci esplicite `provider/modelId` rimangono esatte\n- i glob e le corrispondenze fuzzy operano comunque sui modelli concreti\n\n## `/model` e `--list-models`\n\nEntrambe le superfici mantengono visibili e selezionabili i modelli con prefisso del provider.\n\nOra espongono anche i modelli canonici/coalescenti:\n\n- `/model` include una vista canonica insieme alle schede del provider\n- `--list-models` stampa una sezione canonica più le righe concrete del provider\n\nLa selezione di una voce canonica memorizza il selettore canonico. La selezione di una riga del provider memorizza l'`provider/modelId` esplicito.\n\n## Promozione del contesto (catene di fallback a livello di modello)\n\nLa promozione del contesto è un meccanismo di recupero da overflow per varianti con contesto ridotto (ad esempio `*-spark`) che promuove automaticamente a un elemento gemello con contesto più ampio quando l'API rifiuta una richiesta con un errore di lunghezza del contesto.\n\n### Attivazione e ordine\n\nQuando un turno fallisce con un errore di overflow del contesto (ad es. `context_length_exceeded`), `AgentSession` tenta la promozione **prima** di ricorrere alla compattazione:\n\n1. Se `contextPromotion.enabled` è true, risolve un target di promozione (vedere di seguito).\n2. Se viene trovato un target, lo sostituisce e riprova la richiesta — senza necessità di compattazione.\n3. Se non è disponibile alcun target, prosegue con la compattazione automatica sul modello corrente.\n\n### Selezione del target\n\nLa selezione è guidata dal modello, non dal ruolo:\n\n1. `currentModel.contextPromotionTarget` (se configurato)\n2. modello con contesto più ampio più piccolo sullo stesso provider + API\n\nI candidati vengono ignorati a meno che le credenziali non si risolvano (`ModelRegistry.getApiKey(...)`).\n\n### Handoff del websocket OpenAI Codex\n\nIn caso di passaggio da/a `openai-codex-responses`, la chiave di stato del provider di sessione `openai-codex-responses` viene chiusa prima del cambio di modello. Questo elimina lo stato del trasporto websocket in modo che il turno successivo parta pulito sul modello promosso.\n\n### Comportamento della persistenza\n\nLa promozione utilizza il cambio temporaneo (`setModelTemporary`):\n\n- registrata come `model_change` temporaneo nella cronologia della sessione\n- non riscrive la mappatura dei ruoli salvata\n\n### Configurazione di catene di fallback esplicite\n\nConfigurare il fallback direttamente nei metadati del modello tramite `contextPromotionTarget`.\n\n`contextPromotionTarget` accetta:\n\n- `provider/model-id` (esplicito)\n- `model-id` (risolto nel provider corrente)\n\nEsempio (`models.yml`) per Spark -> non-Spark sullo stesso provider:\n\n```yaml\nproviders:\n  openai-codex:\n    modelOverrides:\n      gpt-5.3-codex-spark:\n        contextPromotionTarget: openai-codex/gpt-5.3-codex\n```\n\nIl generatore di modelli integrato assegna anche questo automaticamente per i modelli `*-spark` quando esiste un modello base sullo stesso provider.\n\n## Campi di compatibilità e routing\n\n`models.yml` supporta questo sottoinsieme di `compat`:\n\n- `supportsStore`\n- `supportsDeveloperRole`\n- `supportsReasoningEffort`\n- `maxTokensField` (`max_completion_tokens` o `max_tokens`)\n- `openRouterRouting.only` / `openRouterRouting.order`\n- `vercelGatewayRouting.only` / `vercelGatewayRouting.order`\n\nQuesti vengono utilizzati dalla logica di trasporto OpenAI-completions e combinati con il rilevamento automatico basato su URL.\n\n## Esempi pratici\n\n### Endpoint compatibile con OpenAI locale (senza autenticazione)\n\n```yaml\nproviders:\n  local-openai:\n    baseUrl: http://127.0.0.1:8000/v1\n    auth: none\n    api: openai-completions\n    models:\n      - id: Qwen/Qwen2.5-Coder-32B-Instruct\n        name: Qwen 2.5 Coder 32B (local)\n```\n\n### Proxy ospitato con chiave basata su variabile d'ambiente\n\n```yaml\nproviders:\n  anthropic-proxy:\n    baseUrl: https://proxy.example.com/anthropic\n    apiKey: ANTHROPIC_PROXY_API_KEY\n    api: anthropic-messages\n    authHeader: true\n    models:\n      - id: claude-sonnet-4-20250514\n        name: Claude Sonnet 4 (Proxy)\n        reasoning: true\n        input: [text, image]\n```\n\n### Sostituzione del routing del provider integrato e metadati del modello\n\n```yaml\nproviders:\n  openrouter:\n    baseUrl: https://my-proxy.example.com/v1\n    headers:\n      X-Team: platform\n    modelOverrides:\n      anthropic/claude-sonnet-4:\n        name: Sonnet 4 (Corp)\n        compat:\n          openRouterRouting:\n            only: [anthropic]\n```\n\n## Configurazione automatica del proxy LiteLLM\n\nQuando le variabili d'ambiente `LITELLM_BASE_URL` e `LITELLM_API_KEY` sono entrambe impostate, xcsh gestisce automaticamente la configurazione di `models.yml` per il proxy LiteLLM.\n\n### Generazione automatica al primo avvio\n\nSe `models.yml` non esiste e vengono rilevate le variabili d'ambiente LiteLLM, xcsh lo genera automaticamente:\n\n```yaml\n# Auto-generated by xcsh for LiteLLM proxy\n# API key resolved from LITELLM_API_KEY env var at runtime\nconfigVersion: 1\nproviders:\n  anthropic:\n    baseUrl: \"https://your-litellm-proxy.example.com/anthropic\"\n    apiKey: LITELLM_API_KEY\n```\n\nViene generato anche un `config.yml` predefinito con impostazioni sensate per il provider di immagini.\n\n### Autoripristino all'avvio\n\nAd ogni avvio, `startupHealthCheck()` nel registro dei modelli esegue i seguenti controlli:\n\n| Condizione | Azione |\n|-----------|--------|\n| `models.yml` mancante | Genera automaticamente dalle variabili d'ambiente |\n| `models.yml` corrotto o non analizzabile | Backup in `.bak`, rigenera |\n| `baseUrl` non corrisponde a `LITELLM_BASE_URL` | Backup in `.bak`, rigenera con nuovo URL |\n| `configVersion` mancante o obsoleto | Backup in `.bak`, rigenera con versione corrente |\n| Configurazione integra | Nessuna azione |\n\nTutte le riparazioni creano backup `.bak` prima di sovrascrivere. Tutte le operazioni sono idempotenti.\n\n### Comando CLI\n\n```bash\nxcsh setup litellm              # Generate or fix LiteLLM config\nxcsh setup litellm --check      # Validate without writing\nxcsh setup litellm --check --json  # Machine-readable validation output\n```\n\n### Variabili d'ambiente richieste\n\n| Variabile | Scopo |\n|----------|---------|\n| `LITELLM_BASE_URL` | URL del proxy LiteLLM (ad es. `https://your-proxy.example.com`). Deve iniziare con `http://` o `https://`. |\n| `LITELLM_API_KEY` | Chiave API per il proxy. Referenziata per nome nella configurazione generata, risolta in fase di esecuzione. |\n\nSe una delle variabili non è impostata, la configurazione automatica viene ignorata silenziosamente.\n\n### Versioning della configurazione\n\nLe configurazioni generate includono un campo `configVersion`. Quando il formato generato cambia nelle versioni future, xcsh rileva le configurazioni obsolete e le aggiorna automaticamente (con backup).\n\n## Avvertenza per i consumer legacy\n\nLa maggior parte della configurazione dei modelli ora fluisce tramite `models.yml` attraverso `ModelRegistry`.\n\nRimane un percorso legacy degno di nota: la risoluzione dell'autenticazione Anthropic per la ricerca web legge ancora direttamente `~/.xcsh/agent/models.json` in `src/web/search/auth.ts`.\n\nSe si fa affidamento su quel percorso specifico, tenere presente la compatibilità JSON finché tale modulo non viene migrato.\n\n## Modalità di errore\n\nSe `models.yml` non supera i controlli di schema o validazione:\n\n- Se `LITELLM_BASE_URL` e `LITELLM_API_KEY` sono impostati, il controllo di integrità all'avvio tenta la riparazione automatica (backup del file corrotto, rigenerazione dalle variabili d'ambiente). Se la riparazione ha esito positivo, il registro ricarica la configurazione corretta.\n- Se la riparazione automatica non è possibile (variabili d'ambiente non impostate, errore di scrittura), il registro continua a operare con i modelli integrati.\n- L'errore viene esposto tramite `ModelRegistry.getError()` e visualizzato nell'interfaccia utente/notifiche.\n",
	"it/providers/provider-streaming-internals.md": "---\ntitle: Dettagli interni dello streaming del provider\ndescription: >-\n  Implementazione dello streaming del provider con parsing SSE, conteggio dei\n  token e gestione del backpressure.\nsidebar:\n  order: 2\n  label: Dettagli interni dello streaming\ni18n:\n  sourceHash: a32ffa769c4d\n  translator: machine\n---\n\n# Dettagli interni dello streaming del provider\n\nQuesto documento spiega come lo streaming di token/strumenti viene normalizzato in `@f5-sales-demo/pi-ai`, quindi propagato attraverso gli eventi di sessione di `@f5-sales-demo/pi-agent-core` e `coding-agent`.\n\n## Flusso end-to-end\n\n1. `streamSimple()` (`packages/ai/src/stream.ts`) mappa le opzioni generiche e invia a una funzione di stream del provider.\n2. Le funzioni di stream del provider (`anthropic.ts`, `openai-responses.ts`, `google.ts`) traducono gli eventi di stream nativi del provider nella sequenza unificata `AssistantMessageEvent`.\n3. Ogni provider inserisce gli eventi in `AssistantMessageEventStream` (`packages/ai/src/utils/event-stream.ts`), che limita gli eventi delta ed espone:\n   - iterazione asincrona per aggiornamenti incrementali\n   - `result()` per il messaggio `AssistantMessage` finale\n4. `agentLoop` (`packages/agent/src/agent-loop.ts`) consuma tali eventi, modifica lo stato dell'assistente in elaborazione ed emette eventi `message_update` che trasportano il `assistantMessageEvent` grezzo.\n5. `AgentSession` (`packages/coding-agent/src/session/agent-session.ts`) si iscrive agli eventi dell'agente, persiste i messaggi, gestisce i hook delle estensioni e applica i comportamenti di sessione (retry, compaction, TTSR, controlli di interruzione della modifica in streaming).\n\n## Contratto di stream unificato in `@f5-sales-demo/pi-ai`\n\nTutti i provider emettono la stessa struttura (`AssistantMessageEvent` in `packages/ai/src/types.ts`):\n\n- `start`\n- triplette del ciclo di vita del blocco di contenuto:\n  - testo: `text_start` → `text_delta`* → `text_end`\n  - pensiero: `thinking_start` → `thinking_delta`* → `thinking_end`\n  - chiamata strumento: `toolcall_start` → `toolcall_delta`* → `toolcall_end`\n- evento terminale:\n  - `done` con `reason: \"stop\" | \"length\" | \"toolUse\"`\n  - oppure `error` con `reason: \"aborted\" | \"error\"`\n\n`AssistantMessageEventStream` garantisce:\n\n- il risultato finale viene risolto dall'evento terminale (`done` o `error`)\n- i delta vengono raggruppati/limitati (~50ms)\n- i delta bufferizzati vengono scaricati prima degli eventi non-delta e prima del completamento\n\n## Comportamento di limitazione e armonizzazione dei delta\n\n`AssistantMessageEventStream` tratta `text_delta`, `thinking_delta` e `toolcall_delta` come eventi unibili:\n\n- i delta bufferizzati vengono uniti solo quando **type + contentIndex** corrispondono\n- l'unione mantiene lo snapshot `partial` più recente\n- gli eventi non-delta forzano lo scaricamento immediato\n\nQuesto uniforma gli stream ad alta frequenza dei provider per i consumatori TUI/eventi, ma non costituisce backpressure del provider: i provider continuano a produrre alla massima velocità, mentre lo stream locale bufferizza.\n\n## Dettagli di normalizzazione del provider\n\n## Anthropic (`anthropic-messages`)\n\nSorgente: `packages/ai/src/providers/anthropic.ts`\n\nPunti di normalizzazione:\n\n- `message_start` inizializza l'utilizzo (token di input/output/cache)\n- `content_block_start` mappa a inizi di testo/pensiero/chiamata strumento\n- `content_block_delta` mappa:\n  - `text_delta` → `text_delta`\n  - `thinking_delta` → `thinking_delta`\n  - `input_json_delta` → `toolcall_delta`\n  - `signature_delta` aggiorna solo `thinkingSignature` (nessun evento)\n- `content_block_stop` emette il corrispondente `*_end`\n- `message_delta.stop_reason` mappa tramite `mapStopReason()`\n\nStreaming degli argomenti delle chiamate strumento:\n\n- ogni blocco strumento contiene un `partialJson` interno\n- ogni delta JSON viene aggiunto a `partialJson`\n- gli `arguments` vengono rianalizzati ad ogni delta tramite `parseStreamingJson()`\n- `toolcall_end` esegue un'ulteriore rianalisi, quindi rimuove `partialJson`\n\n## OpenAI Responses (`openai-responses`)\n\nSorgente: `packages/ai/src/providers/openai-responses.ts`\n\nPunti di normalizzazione:\n\n- `response.output_item.added` avvia blocchi di ragionamento/testo/chiamata a funzione\n- gli eventi di riepilogo del ragionamento (`response.reasoning_summary_text.delta`) diventano `thinking_delta`\n- i delta di output/rifiuto diventano `text_delta`\n- `response.function_call_arguments.delta` diventa `toolcall_delta`\n- `response.output_item.done` emette `thinking_end` / `text_end` / `toolcall_end`\n- `response.completed` mappa lo stato al motivo di arresto e all'utilizzo\n\nStreaming degli argomenti delle chiamate strumento:\n\n- stesso schema di accumulazione `partialJson` di Anthropic\n- i provider che inviano solo `response.function_call_arguments.done` popolano comunque gli argomenti finali\n- gli ID delle chiamate strumento vengono normalizzati come `\"<call_id>|<item_id>\"`\n\n## Google Generative AI (`google-generative-ai`)\n\nSorgente: `packages/ai/src/providers/google.ts`\n\nPunti di normalizzazione:\n\n- itera su `candidate.content.parts`\n- le parti di testo vengono suddivise in pensiero vs testo tramite `isThinkingPart(part)`\n- le transizioni di blocco chiudono il blocco precedente prima di avviarne uno nuovo\n- `part.functionCall` viene trattato come una chiamata strumento completa (start/delta/end emessi immediatamente)\n- il motivo di fine viene mappato da `mapStopReason()` in `google-shared.ts`\n\nStreaming degli argomenti delle chiamate strumento:\n\n- gli argomenti delle chiamate a funzione arrivano come oggetto strutturato, non come testo JSON incrementale\n- l'implementazione emette un `toolcall_delta` sintetico contenente `JSON.stringify(arguments)`\n- non è necessario alcun parser JSON parziale per Google in questo percorso\n\n## Accumulazione e recupero del JSON parziale delle chiamate strumento\n\nIl comportamento condiviso per Anthropic/OpenAI Responses utilizza `parseStreamingJson()` (`packages/ai/src/utils/json-parse.ts`):\n\n1. si tenta `JSON.parse`\n2. fallback al parser `partial-json` per frammenti incompleti\n3. se entrambi falliscono, viene restituito `{}`\n\nImplicazioni:\n\n- i delta di argomenti malformati o troncati non interrompono immediatamente l'elaborazione dello stream\n- gli `arguments` in corso possono essere temporaneamente `{}`\n- delta validi successivi possono recuperare argomenti strutturati perché il parsing viene ritentato ad ogni aggiunta\n- il `toolcall_end` finale esegue un ulteriore tentativo di parsing prima dell'emissione\n\n## Motivi di arresto vs errori di trasporto/runtime\n\nI motivi di arresto del provider vengono mappati al `stopReason` normalizzato:\n\n- Anthropic: `end_turn`→`stop`, `max_tokens`→`length`, `tool_use`→`toolUse`, casi di sicurezza/rifiuto→`error`\n- OpenAI Responses: `completed`→`stop`, `incomplete`→`length`, `failed/cancelled`→`error`\n- Google: `STOP`→`stop`, `MAX_TOKENS`→`length`, classi di sicurezza/vietato/chiamata a funzione malformata→`error`\n\nLa semantica degli errori è divisa in due fasi:\n\n1. **Semantica del completamento del modello** (motivo/stato di fine riportato dal provider)\n2. **Errore di trasporto/runtime** (eccezioni di rete/client/parser/abort)\n\nSe lo stream del provider genera un'eccezione o segnala un errore, ogni wrapper del provider cattura ed emette un evento `error` terminale con:\n\n- `stopReason = \"aborted\"` quando il segnale di abort è impostato\n- altrimenti `stopReason = \"error\"`\n- `errorMessage = formatErrorMessageWithRetryAfter(error)`\n\n## Comportamento in caso di chunk malformato / errore di parsing SSE\n\nPer questi percorsi del provider, il framing chunk/SSE è gestito dagli stream degli SDK vendor (Anthropic SDK, OpenAI SDK, Google SDK). Questo codice non implementa un decoder SSE personalizzato.\n\nComportamento osservato nell'implementazione attuale:\n\n- il parsing chunk/SSE malformato a livello SDK emerge come eccezione o evento `error` dello stream\n- il wrapper del provider converte ciò in un evento `error` terminale unificato\n- nessun resume/retry specifico del provider all'interno della funzione di stream stessa\n- i retry di livello superiore sono gestiti dalla logica di auto-retry di `AgentSession` (retry a livello di messaggio, non replay di chunk dello stream)\n\n## Confini di cancellazione\n\nLa cancellazione è stratificata:\n\n- Richiesta al provider IA: `options.signal` viene passato nella chiamata allo stream del client provider.\n- Wrapper del provider: dopo il ciclo dello stream, il segnale abortito forza il percorso di errore (`\"Request was aborted\"`).\n- Agent loop: controlla `signal.aborted` prima di gestire ogni evento del provider e può sintetizzare un messaggio dell'assistente abortito dal parziale più recente.\n- Controlli di sessione/agente: `AgentSession.abort()` -> `agent.abort()` -> cancellazione del controller di abort condiviso.\n\nLa cancellazione dell'esecuzione degli strumenti è separata dalla cancellazione dello stream del modello:\n\n- i runner degli strumenti usano `AbortSignal.any([agentSignal, steeringAbortSignal])`\n- le interruzioni di steering possono interrompere l'esecuzione degli strumenti rimanente preservando i risultati degli strumenti già prodotti\n\n## Confini di backpressure\n\nNon esiste un meccanismo di backpressure rigido tra lo stream dell'SDK del provider e i consumatori a valle:\n\n- `EventStream` utilizza code in memoria senza dimensione massima\n- la limitazione riduce la frequenza degli aggiornamenti dell'interfaccia utente ma non rallenta l'acquisizione dal provider\n- se i consumatori restano significativamente indietro, gli eventi in coda possono crescere fino al completamento\n\nIl design attuale privilegia la reattività e la semplicità dell'ordinamento rispetto al controllo del flusso con buffer limitato.\n\n## Come gli eventi di stream emergono come eventi agente/sessione\n\n`agentLoop.streamAssistantResponse()` collega `AssistantMessageEvent` ad `AgentEvent`:\n\n- su `start`: inserisce un messaggio placeholder dell'assistente ed emette `message_start`\n- sugli eventi di blocco (`text_*`, `thinking_*`, `toolcall_*`): aggiorna l'ultimo messaggio dell'assistente, emette `message_update` con il `assistantMessageEvent` grezzo\n- sul terminale (`done`/`error`): risolve il messaggio finale da `response.result()`, emette `message_end`\n\n`AgentSession` consuma quindi tali eventi per i comportamenti a livello di sessione:\n\n- TTSR osserva `message_update.assistantMessageEvent` per `text_delta` e `toolcall_delta`\n- il guard delle modifiche in streaming ispeziona `toolcall_delta`/`toolcall_end` sulle chiamate `edit` e può interrompersi anticipatamente\n- la persistenza scrive i messaggi finalizzati su `message_end`\n- l'auto-retry esamina il `stopReason === \"error\"` dell'assistente più le euristiche di `errorMessage`\n\n## Responsabilità unificate vs specifiche del provider\n\nUnificate (contratto comune):\n\n- forma dell'evento (`AssistantMessageEvent`)\n- estrazione del risultato finale (`done`/`error`)\n- regole di limitazione e unione dei delta\n- modello di propagazione degli eventi agente/sessione\n\nSpecifiche del provider (non completamente astratte):\n\n- tassonomie degli eventi upstream e logica di mappatura\n- tabelle di traduzione dei motivi di arresto\n- convenzioni sugli ID delle chiamate strumento\n- semantica e firme dei blocchi di ragionamento/pensiero\n- semantica dei token di utilizzo e tempistica di disponibilità\n- vincoli di conversione dei messaggi per API\n\n## File di implementazione\n\n- [`../../ai/src/stream.ts`](../../packages/ai/src/stream.ts) — dispatch del provider, mappatura delle opzioni, collegamento di chiavi API/sessione.\n- [`../../ai/src/utils/event-stream.ts`](../../packages/ai/src/utils/event-stream.ts) — coda di stream generica + limitazione dei delta dell'assistente.\n- [`../../ai/src/utils/json-parse.ts`](../../packages/ai/src/utils/json-parse.ts) — parsing JSON parziale per gli argomenti degli strumenti in streaming.\n- [`../../ai/src/providers/anthropic.ts`](../../packages/ai/src/providers/anthropic.ts) — traduzione degli eventi Anthropic e accumulazione dei delta JSON degli strumenti.\n- [`../../ai/src/providers/openai-responses.ts`](../../packages/ai/src/providers/openai-responses.ts) — traduzione degli eventi OpenAI Responses e mappatura degli stati.\n- [`../../ai/src/providers/google.ts`](../../packages/ai/src/providers/google.ts) — traduzione chunk-to-block dello stream Gemini.\n- [`../../ai/src/providers/google-shared.ts`](../../packages/ai/src/providers/google-shared.ts) — mappatura del motivo di fine Gemini e regole di conversione condivise.\n- [`../../agent/src/agent-loop.ts`](../../packages/agent/src/agent-loop.ts) — consumo dello stream del provider e bridging di `message_update`.\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — gestione a livello di sessione degli aggiornamenti in streaming, abort, retry e persistenza.\n",
	"it/providers/python-repl.md": "---\ntitle: Strumento Python e Runtime IPython\ndescription: >-\n  Runtime dello strumento Python REPL con gestione del kernel IPython,\n  esecuzione e acquisizione dell'output.\nsidebar:\n  order: 3\n  label: Python e IPython\ni18n:\n  sourceHash: 70f0a034ecef\n  translator: machine\n---\n\n# Strumento Python e Runtime IPython\n\nQuesto documento descrive l'attuale stack di esecuzione Python in `packages/coding-agent`.\nCopre il comportamento degli strumenti, il ciclo di vita del kernel/gateway, la gestione dell'ambiente, la semantica di esecuzione, il rendering dell'output e le modalità di errore operative.\n\n## Ambito e file principali\n\n- Superficie dello strumento: `src/tools/python.ts`\n- Orchestrazione del kernel per sessione/chiamata: `src/ipy/executor.ts`\n- Protocollo kernel + integrazione gateway: `src/ipy/kernel.ts`\n- Coordinatore gateway locale condiviso: `src/ipy/gateway-coordinator.ts`\n- Renderer in modalità interattiva per esecuzioni Python avviate dall'utente: `src/modes/components/python-execution.ts`\n- Filtraggio del runtime/ambiente e risoluzione di Python: `src/ipy/runtime.ts`\n\n## Cos'è lo strumento Python\n\nLo strumento `python` esegue una o più celle Python tramite un kernel supportato da Jupyter Kernel Gateway (e non avviando `python -c` direttamente per ogni cella).\n\nParametri dello strumento:\n\n```ts\n{\n  cells: Array<{ code: string; title?: string }>;\n  timeout?: number; // secondi, limitato a 1..600, predefinito 30\n  cwd?: string;\n  reset?: boolean; // reimposta il kernel prima della prima cella soltanto\n}\n```\n\nLo strumento è `concurrency = \"exclusive\"` per una sessione, quindi le chiamate non si sovrappongono.\n\n## Ciclo di vita del gateway\n\n### Modalità\n\nEsistono due percorsi di gateway:\n\n1. **Gateway esterno** (`PI_PYTHON_GATEWAY_URL` impostato)\n   - Utilizza direttamente l'URL configurato.\n   - Autenticazione opzionale con `PI_PYTHON_GATEWAY_TOKEN`.\n   - Nessun processo gateway locale viene avviato o gestito.\n\n2. **Gateway locale condiviso** (percorso predefinito)\n   - Utilizza un singolo processo condiviso coordinato sotto `~/.xcsh/agent/python-gateway`.\n   - File di metadati: `gateway.json`\n   - File di blocco: `gateway.lock`\n   - Comando di avvio:\n     - `python -m kernel_gateway`\n     - associato a `127.0.0.1:<porta-allocata>`\n     - controllo di integrità all'avvio: `GET /api/kernelspecs`\n\n### Coordinamento del gateway locale condiviso\n\n`acquireSharedGateway()`:\n\n- Acquisisce un blocco file (`gateway.lock`) con heartbeat.\n- Riutilizza `gateway.json` se il PID è attivo e il controllo di integrità viene superato.\n- Pulisce le informazioni/PID non aggiornati quando necessario.\n- Avvia un nuovo gateway quando non ne esiste uno integro.\n\n`releaseSharedGateway()` è attualmente un'operazione vuota (l'arresto del kernel non smonta il gateway condiviso).\n\n`shutdownSharedGateway()` termina esplicitamente il processo condiviso e cancella i metadati del gateway.\n\n### Vincolo importante\n\n`python.sharedGateway=false` viene rifiutato all'avvio del kernel:\n\n- Errore: `Shared Python gateway required; local gateways are disabled`\n- Non esiste una modalità gateway locale non condivisa per processo.\n\n## Ciclo di vita del kernel\n\nOgni esecuzione utilizza un kernel creato tramite `POST /api/kernels` sul gateway selezionato.\n\nSequenza di avvio del kernel:\n\n1. Controllo di disponibilità (`checkPythonKernelAvailability`)\n2. Creazione del kernel (`/api/kernels`)\n3. Apertura del websocket (`/api/kernels/:id/channels`)\n4. Inizializzazione dell'ambiente del kernel (`cwd`, variabili d'ambiente, `sys.path`)\n5. Esecuzione di `PYTHON_PRELUDE`\n6. Caricamento dei moduli di estensione da:\n   - utente: `~/.xcsh/agent/modules/*.py`\n   - progetto: `<cwd>/.xcsh/modules/*.py` (sovrascrive i moduli utente con lo stesso nome)\n\nArresto del kernel:\n\n- Elimina il kernel remoto tramite `DELETE /api/kernels/:id`\n- Chiude il websocket\n- Richiama l'hook di rilascio del gateway condiviso (oggi un'operazione vuota)\n\n## Semantica di persistenza della sessione\n\n`python.kernelMode` controlla il riutilizzo del kernel:\n\n- `session` (predefinito)\n  - Riutilizza le sessioni del kernel identificate da identità di sessione + cwd.\n  - L'esecuzione è serializzata per sessione tramite una coda.\n  - Le sessioni inattive vengono eliminate dopo 5 minuti.\n  - Al massimo 4 sessioni; la più vecchia viene eliminata in caso di overflow.\n  - I controlli heartbeat rilevano i kernel non più attivi.\n  - Il riavvio automatico è consentito una volta; crash ripetuti => errore definitivo.\n\n- `per-call`\n  - Crea un kernel nuovo per ogni richiesta di esecuzione.\n  - Arresta il kernel al termine della richiesta.\n  - Nessuna persistenza dello stato tra chiamate diverse.\n\n### Comportamento multi-cella in una singola chiamata allo strumento\n\nLe celle vengono eseguite sequenzialmente nella stessa istanza del kernel per quella chiamata allo strumento.\n\nSe una cella intermedia fallisce:\n\n- Lo stato delle celle precedenti rimane in memoria.\n- Lo strumento restituisce un errore mirato che indica quale cella ha avuto esito negativo.\n- Le celle successive non vengono eseguite.\n\n`reset=true` si applica solo alla prima esecuzione di cella in quella chiamata.\n\n## Filtraggio dell'ambiente e risoluzione del runtime\n\nL'ambiente viene filtrato prima di avviare il runtime gateway/kernel:\n\n- La lista di permessi include variabili fondamentali come `PATH`, `HOME`, variabili di localizzazione, `VIRTUAL_ENV`, `PYTHONPATH`, ecc.\n- Prefissi consentiti: `LC_`, `XDG_`, `PI_`\n- La lista di negazione rimuove le chiavi API comuni (OpenAI/Anthropic/Gemini/ecc.)\n\nOrdine di selezione del runtime:\n\n1. Venv attivo/localizzato (`VIRTUAL_ENV`, poi `<cwd>/.venv`, `<cwd>/venv`)\n2. Venv gestito in `~/.xcsh/python-env`\n3. `python` o `python3` nel PATH\n\nQuando viene selezionato un venv, il suo percorso bin/Scripts viene preposto a `PATH`.\n\nL'inizializzazione dell'ambiente del kernel in Python esegue anche:\n\n- `os.chdir(cwd)`\n- inietta la mappa di ambiente fornita in `os.environ`\n- garantisce che cwd sia in `sys.path`\n\n## Disponibilità dello strumento e selezione della modalità\n\n`python.toolMode` (predefinito `both`) + eventuale override `PI_PY` controlla l'esposizione:\n\n- `ipy-only`\n- `bash-only`\n- `both`\n\nValori accettati da `PI_PY`:\n\n- `0` / `bash` -> `bash-only`\n- `1` / `py` -> `ipy-only`\n- `mix` / `both` -> `both`\n\nSe il preflight di Python fallisce, la creazione dello strumento si degrada a bash-only per quella sessione.\n\n## Flusso di esecuzione e cancellazione/timeout\n\n### Timeout a livello di strumento\n\nIl timeout dello strumento `python` è in secondi, predefinito 30, limitato a `1..600`.\n\nLo strumento combina:\n\n- segnale di interruzione del chiamante\n- segnale di interruzione per timeout\n\ncon `AbortSignal.any(...)`.\n\n### Cancellazione dell'esecuzione del kernel\n\nIn caso di interruzione/timeout:\n\n- L'esecuzione viene contrassegnata come annullata.\n- L'interruzione del kernel viene tentata tramite REST (`POST /interrupt`) e il canale di controllo `interrupt_request`.\n- Il risultato include `cancelled=true`.\n- Il percorso di timeout annota l'output come `Command timed out after <n> seconds`.\n\n### Comportamento di stdin\n\nLo stdin interattivo non è supportato.\n\nSe il kernel emette `input_request`:\n\n- Lo strumento registra `stdinRequested=true`\n- Emette un testo esplicativo\n- Invia una `input_reply` vuota\n- L'esecuzione viene trattata come un errore a livello di executor\n\n## Acquisizione e rendering dell'output\n\n### Classi di output acquisite\n\nDai messaggi del kernel:\n\n- `stream` -> porzioni di testo semplice\n- `display_data`/`execute_result` -> gestione della visualizzazione ricca\n- `error` -> testo del traceback\n- MIME personalizzato `application/x-xcsh-status` -> eventi di stato strutturati\n\nPrecedenza MIME per la visualizzazione:\n\n1. `text/markdown`\n2. `text/plain`\n3. `text/html` (convertito in markdown di base)\n\nAcquisiti inoltre come output strutturati:\n\n- `application/json` -> dati ad albero JSON\n- `image/png` -> payload di immagini\n- `application/x-xcsh-status` -> eventi di stato\n\n### Archiviazione e troncamento\n\nL'output viene trasmesso in streaming tramite `OutputSink` e può essere salvato nell'archiviazione degli artefatti.\n\nI risultati degli strumenti possono includere metadati di troncamento e `artifact://<id>` per il recupero dell'output completo.\n\n### Comportamento del renderer\n\n- Renderer dello strumento (`python.ts`):\n  - mostra blocchi di celle di codice con stato per cella\n  - l'anteprima compressa mostra per impostazione predefinita 10 righe\n  - supporta la modalità espansa per l'output completo e dettagli di stato più ricchi\n- Renderer interattivo (`python-execution.ts`):\n  - utilizzato per l'esecuzione Python avviata dall'utente nell'interfaccia TUI\n  - l'anteprima compressa mostra per impostazione predefinita 20 righe\n  - limita le singole righe molto lunghe a 4000 caratteri per la sicurezza della visualizzazione\n  - mostra avvisi di cancellazione/errore/troncamento\n\n## Supporto gateway esterno\n\nImpostare:\n\n```bash\nexport PI_PYTHON_GATEWAY_URL=\"http://127.0.0.1:8888\"\n# Opzionale:\nexport PI_PYTHON_GATEWAY_TOKEN=\"...\"\n```\n\nDifferenze di comportamento rispetto al gateway locale condiviso:\n\n- Nessun file di blocco/informazioni gateway locale\n- Nessun avvio/terminazione di processi locali\n- I controlli di integrità e le operazioni CRUD del kernel vengono eseguiti sull'endpoint esterno\n- Gli errori di autenticazione vengono visualizzati con una guida esplicita sul token\n\n## Risoluzione dei problemi operativi (modalità di errore attuali)\n\n- **Strumento Python non disponibile**\n  - Verificare `python.toolMode` / `PI_PY`.\n  - Se il preflight fallisce, il runtime torna a bash-only.\n\n- **Errori di disponibilità del kernel**\n  - La modalità locale richiede che sia `kernel_gateway` che `ipykernel` siano importabili nel runtime Python risolto.\n  - Installare con:\n\n    ```bash\n    python -m pip install jupyter_kernel_gateway ipykernel\n    ```\n\n- **`python.sharedGateway=false` causa un errore di avvio**\n  - Questo è il comportamento previsto con l'implementazione attuale.\n\n- **Errori di autenticazione/raggiungibilità del gateway esterno**\n  - 401/403 -> impostare `PI_PYTHON_GATEWAY_TOKEN`.\n  - timeout/non raggiungibile -> verificare URL/rete e integrità del gateway.\n\n- **L'esecuzione si blocca e va in timeout**\n  - Aumentare il `timeout` dello strumento (max 600s) se il carico di lavoro è legittimo.\n  - Per il codice bloccato, la cancellazione attiva l'interruzione del kernel, ma il codice utente potrebbe comunque richiedere un refactoring.\n\n- **Prompt stdin/input nel codice Python**\n  - `input()` non è supportato in modo interattivo in questo percorso di runtime; passare i dati in modo programmatico.\n\n- **Esaurimento delle risorse (`EMFILE` / troppi file aperti)**\n  - Il gestore delle sessioni avvia il ripristino del gateway condiviso (smontaggio della sessione + riavvio del gateway condiviso).\n\n- **Errori della directory di lavoro**\n  - Lo strumento verifica che `cwd` esista e sia una directory prima dell'esecuzione.\n\n## Variabili d'ambiente pertinenti\n\n- `PI_PY` — override dell'esposizione dello strumento (mappatura `bash-only`/`ipy-only`/`both` descritta sopra)\n- `PI_PYTHON_GATEWAY_URL` — utilizza un gateway esterno\n- `PI_PYTHON_GATEWAY_TOKEN` — token di autenticazione opzionale per il gateway esterno\n- `PI_PYTHON_SKIP_CHECK=1` — ignora i controlli di preflight/riscaldamento di Python\n- `PI_PYTHON_IPC_TRACE=1` — registra le tracce di invio/ricezione IPC del kernel\n- `PI_DEBUG_STARTUP=1` — emette marcatori di debug della fase di avvio\n",
	"it/runtime-tools/bash-tool-runtime.md": "---\ntitle: Runtime dello strumento Bash\ndescription: >-\n  Runtime dello strumento Bash con gestione dei processi shell, sandboxing,\n  timeout e streaming dell'output.\nsidebar:\n  order: 1\n  label: Strumento Bash\ni18n:\n  sourceHash: 18b12aa5dbd5\n  translator: machine\n---\n\n# Runtime dello strumento Bash\n\nQuesto documento descrive il percorso di runtime dello **strumento `bash`** utilizzato dalle chiamate agli strumenti dell'agente, dalla normalizzazione dei comandi all'esecuzione, alla troncatura/artefatti e al rendering.\n\nViene inoltre indicato dove il comportamento diverge in modalità TUI interattiva, modalità di stampa, modalità RPC ed esecuzione shell bang (`!`) avviata dall'utente.\n\n## Ambito e superfici di runtime\n\nEsistono due diverse superfici di esecuzione bash nel coding-agent:\n\n1. **Superficie di chiamata agli strumenti** (`toolName: \"bash\"`): utilizzata quando il modello chiama lo strumento bash.\n   - Punto di ingresso: `BashTool.execute()`.\n2. **Superficie di comando bang utente** (`!cmd` dall'input interattivo o comando RPC `bash`): percorso helper a livello di sessione.\n   - Punto di ingresso: `AgentSession.executeBash()`.\n\nEntrambe alla fine utilizzano `executeBash()` in `src/exec/bash-executor.ts` per l'esecuzione non-PTY, ma solo il percorso di chiamata agli strumenti esegue la logica di normalizzazione/intercettazione e del renderer degli strumenti.\n\n## Pipeline di chiamata agli strumenti end-to-end\n\n## 1) Normalizzazione dell'input e merge dei parametri\n\n`BashTool.execute()` normalizza prima il comando grezzo tramite `normalizeBashCommand()`:\n\n- estrae i `| head -n N`, `| head -N`, `| tail -n N`, `| tail -N` finali in limiti strutturati,\n- rimuove gli spazi bianchi finali/iniziali,\n- mantiene intatti gli spazi bianchi interni.\n\nQuindi unisce i limiti estratti con gli argomenti espliciti dello strumento:\n\n- gli argomenti espliciti `head`/`tail` sovrascrivono i valori estratti,\n- i valori estratti sono solo un fallback.\n\n### Avvertenza\n\nI commenti in `bash-normalize.ts` menzionano la rimozione di `2>&1`, ma l'implementazione attuale non lo rimuove. Il comportamento di runtime è comunque corretto (stdout/stderr sono già unificati), ma il comportamento di normalizzazione è più limitato di quanto suggeriscano i commenti.\n\n## 2) Intercettazione opzionale (percorso di comando bloccato)\n\nSe `bashInterceptor.enabled` è true, `BashTool` carica le regole dalle impostazioni ed esegue `checkBashInterception()` sul comando normalizzato.\n\nComportamento di intercettazione:\n\n- il comando è bloccato **solo** quando:\n  - la regola regex corrisponde, e\n  - lo strumento suggerito è presente in `ctx.toolNames`.\n- le regole regex non valide vengono ignorate silenziosamente.\n- in caso di blocco, `BashTool` genera un `ToolError` con il messaggio:\n  - `Blocked: ...`\n  - comando originale incluso.\n\nI pattern delle regole predefinite (definiti nel codice) si rivolgono agli utilizzi impropri più comuni:\n\n- lettori di file (`cat`, `head`, `tail`, ...)\n- strumenti di ricerca (`grep`, `rg`, ...)\n- cercatori di file (`find`, `fd`, ...)\n- editor in-place (`sed -i`, `perl -i`, `awk -i inplace`)\n- scritture con reindirizzamento shell (`echo ... > file`, reindirizzamento heredoc)\n\n### Avvertenza\n\n`InterceptionResult` include `suggestedTool`, ma `BashTool` attualmente espone solo il testo del messaggio (nessun campo structured suggested-tool in `details`).\n\n## 3) Validazione CWD e limitazione del timeout\n\n`cwd` viene risolto relativamente al cwd della sessione (`resolveToCwd`), quindi validato tramite `stat`:\n\n- percorso mancante -> `ToolError(\"Working directory does not exist: ...\")`\n- non-directory -> `ToolError(\"Working directory is not a directory: ...\")`\n\nIl timeout è limitato all'intervallo `[1, 3600]` secondi e convertito in millisecondi.\n\n## 4) Allocazione degli artefatti\n\nPrima dell'esecuzione, lo strumento alloca un percorso/id di artefatto (best-effort) per l'archiviazione dell'output troncato.\n\n- il fallimento dell'allocazione dell'artefatto non è fatale (l'esecuzione continua senza file di spill dell'artefatto),\n- id/percorso dell'artefatto vengono passati nel percorso di esecuzione per la persistenza dell'output completo in caso di troncatura.\n\n## 5) Selezione dell'esecuzione PTY vs non-PTY\n\n`BashTool` sceglie l'esecuzione PTY solo quando tutte le condizioni sono vere:\n\n- `bash.virtualTerminal === \"on\"`\n- `PI_NO_PTY !== \"1\"`\n- il contesto dello strumento ha UI (`ctx.hasUI === true` e `ctx.ui` impostato)\n\nAltrimenti utilizza `executeBash()` non interattivo.\n\nCiò significa che la modalità di stampa e i contesti RPC/strumenti non-UI utilizzano sempre non-PTY.\n\n## Motore di esecuzione non interattivo (`executeBash`)\n\n## Modello di riutilizzo delle sessioni shell\n\n`executeBash()` memorizza nella cache le istanze `Shell` native in una mappa globale del processo con chiave basata su:\n\n- percorso della shell,\n- prefisso di comando configurato,\n- percorso dello snapshot,\n- env della shell serializzata,\n- chiave di sessione agente opzionale.\n\nPer le esecuzioni a livello di sessione, `AgentSession.executeBash()` passa `sessionKey: this.sessionId`, isolando il riutilizzo per sessione.\n\nIl percorso di chiamata agli strumenti **non** passa `sessionKey`, quindi l'ambito di riutilizzo è basato sulla configurazione shell/snapshot/env.\n\n## Configurazione della shell e comportamento degli snapshot\n\nAd ogni chiamata, l'executor carica la configurazione della shell dalle impostazioni (`shell`, `env`, `prefix` opzionale).\n\nSe la shell selezionata include `bash`, tenta `getOrCreateSnapshot()`:\n\n- lo snapshot cattura alias/funzioni/opzioni dal rc dell'utente,\n- la creazione dello snapshot è best-effort,\n- in caso di fallimento, si ricade all'assenza di snapshot.\n\nSe `prefix` è configurato, il comando diventa:\n\n```text\n<prefix> <command>\n```\n\n## Streaming e cancellazione\n\n`Shell.run()` trasmette chunk al callback. L'executor instrada ogni chunk in `OutputSink` e nel callback opzionale `onChunk`.\n\nCancellazione:\n\n- il segnale interrotto attiva `shellSession.abort(...)`,\n- il timeout del risultato nativo viene mappato su `cancelled: true` + testo di annotazione,\n- la cancellazione esplicita restituisce analogamente `cancelled: true` + annotazione.\n\nNessuna eccezione viene generata all'interno dell'executor per timeout/cancellazione; restituisce un `BashResult` strutturato e lascia che il chiamante mappi la semantica degli errori.\n\n## Percorso PTY interattivo (`runInteractiveBashPty`)\n\nQuando PTY è abilitato, lo strumento esegue `runInteractiveBashPty()` che apre un componente console in overlay e gestisce una `PtySession` nativa.\n\nCaratteristiche principali del comportamento:\n\n- il terminale virtuale xterm-headless renderizza il viewport nell'overlay,\n- l'input da tastiera è normalizzato (inclusa la gestione delle sequenze Kitty e della modalità cursore applicazione),\n- `esc` durante l'esecuzione termina la sessione PTY,\n- il ridimensionamento del terminale si propaga al PTY (`session.resize(cols, rows)`).\n\nLe impostazioni predefinite di hardening dell'ambiente vengono iniettate per le esecuzioni non presidiate:\n\n- pager disabilitati (`PAGER=cat`, `GIT_PAGER=cat`, ecc.),\n- prompt dell'editor disabilitati (`GIT_EDITOR=true`, `EDITOR=true`, ...),\n- prompt terminale/autenticazione ridotti (`GIT_TERMINAL_PROMPT=0`, `SSH_ASKPASS=/usr/bin/false`, `CI=1`),\n- flag di automazione package-manager/strumento per il comportamento non interattivo.\n\nL'output PTY è normalizzato (`CRLF`/`CR` in `LF`, `sanitizeText`) e scritto in `OutputSink`, incluso il supporto allo spill degli artefatti.\n\nIn caso di errore di avvio/runtime PTY, il sink riceve la riga `PTY error: ...` e il comando si finalizza con codice di uscita indefinito.\n\n## Gestione dell'output: streaming, troncatura, spill degli artefatti\n\nSia il percorso PTY che quello non-PTY utilizzano `OutputSink`.\n\n## Semantica di OutputSink\n\n- mantiene un buffer tail in memoria con codifica UTF-8 sicura (`DEFAULT_MAX_BYTES`, attualmente 50KB),\n- tiene traccia del totale di byte/righe visti,\n- se esiste un percorso di artefatto e l'output supera il limite (o il file è già attivo), scrive lo stream completo nel file dell'artefatto,\n- quando la soglia di memoria viene superata, riduce il buffer in memoria al tail (sicuro ai limiti UTF-8),\n- segna `truncated` quando si verifica overflow/spill su file.\n\n`dump()` restituisce:\n\n- `output` (possibilmente con prefisso annotato),\n- `truncated`,\n- `totalLines/totalBytes`,\n- `outputLines/outputBytes`,\n- `artifactId` se il file dell'artefatto era attivo.\n\n### Avvertenza sull'output lungo\n\nLa troncatura di runtime si basa sulla soglia in byte in `OutputSink` (default 50KB). Non impone un limite rigido di 2000 righe in questo percorso di codice.\n\n## Aggiornamenti live degli strumenti\n\nPer l'esecuzione non-PTY, `BashTool` utilizza un `TailBuffer` separato per gli aggiornamenti parziali e invia snapshot `onUpdate` mentre il comando è in esecuzione.\n\nPer l'esecuzione PTY, il rendering live è gestito dall'overlay UI personalizzato, non dai chunk di testo `onUpdate`.\n\n## Formazione del risultato, metadati e mappatura degli errori\n\nDopo l'esecuzione:\n\n1. Gestione di `cancelled`:\n   - se il segnale di interruzione è interrotto -> genera `ToolAbortError` (semantica di interruzione),\n   - altrimenti -> genera `ToolError` (trattato come fallimento dello strumento).\n2. PTY `timedOut` -> genera `ToolError`.\n3. applica i filtri head/tail al testo di output finale (`applyHeadTail`, head poi tail).\n4. l'output vuoto diventa `(no output)`.\n5. allega i metadati di troncatura tramite `toolResult(...).truncationFromSummary(result, { direction: \"tail\" })`.\n6. mappatura del codice di uscita:\n   - codice di uscita mancante -> `ToolError(\"... missing exit status\")`\n   - uscita non zero -> `ToolError(\"... Command exited with code N\")`\n   - uscita zero -> risultato di successo.\n\nStruttura del payload di successo:\n\n- `content`: testo di output,\n- `details.meta.truncation` quando troncato, inclusi:\n  - `direction`, `truncatedBy`, conteggi totali/output di righe+byte,\n  - `shownRange`,\n  - `artifactId` quando disponibile.\n\nPoiché gli strumenti integrati sono avvolti con `wrapToolWithMetaNotice()`, il testo di avviso di troncatura viene aggiunto automaticamente al contenuto testuale finale (ad esempio: `Full: artifact://<id>`).\n\n## Percorsi di rendering\n\n## Renderer di chiamata agli strumenti (`bashToolRenderer`)\n\n`bashToolRenderer` è utilizzato per i messaggi di chiamata agli strumenti (`toolCall` / `toolResult`):\n\n- la modalità compressa mostra un'anteprima troncata a livello di righe visive,\n- la modalità espansa mostra tutto il testo di output attualmente disponibile,\n- la riga di avviso include il motivo della troncatura e `artifact://<id>` quando troncato,\n- il valore di timeout (dagli argomenti) è mostrato nella riga dei metadati del piè di pagina.\n\n### Avvertenza: espansione completa dell'artefatto\n\n`BashRenderContext` ha `isFullOutput`, ma il builder del contesto del renderer attuale non lo imposta per i risultati dello strumento bash. La vista espansa utilizza ancora il testo già presente nel contenuto del risultato (output tail/troncato) a meno che un altro chiamante non fornisca il contenuto completo dell'artefatto.\n\n## Componente di comando bang utente (`BashExecutionComponent`)\n\n`BashExecutionComponent` è per i comandi `!` dell'utente in modalità interattiva (non per le chiamate agli strumenti del modello):\n\n- trasmette chunk in tempo reale,\n- l'anteprima compressa mantiene le ultime 20 righe logiche,\n- limite di 4000 caratteri per riga,\n- mostra avvisi di troncatura + artefatto quando i metadati sono presenti,\n- segna separatamente lo stato cancellato/errore/uscita.\n\nQuesto componente è collegato da `CommandController.handleBashCommand()` e alimentato da `AgentSession.executeBash()`.\n\n## Differenze di comportamento specifiche per modalità\n\n| Superficie                            | Percorso di ingresso                                  | Idoneità PTY                                                          | UX di output live                                                                  | Gestione degli errori                                    |\n| ------------------------------------- | ----------------------------------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------------- |\n| Chiamata agli strumenti interattiva   | `BashTool.execute`                                    | Sì, quando `bash.virtualTerminal=on` e UI esistente e `PI_NO_PTY!=1` | Overlay PTY (interattivo) o aggiornamenti tail in streaming                        | Gli errori dello strumento diventano `toolResult.isError` |\n| Chiamata agli strumenti in stampa     | `BashTool.execute`                                    | No (nessun contesto UI)                                               | Nessun overlay TUI; l'output appare nel flusso di eventi/testo finale dell'assistente | Stessa mappatura degli errori dello strumento            |\n| Chiamata agli strumenti RPC (tooling agente) | `BashTool.execute`                             | Di solito nessuna UI -> non-PTY                                       | Eventi/risultati strutturati degli strumenti                                        | Stessa mappatura degli errori dello strumento            |\n| Comando bang interattivo (`!`)        | `AgentSession.executeBash` + `BashExecutionComponent` | No (usa l'executor direttamente)                                      | Componente di esecuzione bash dedicato                                              | Il controller cattura le eccezioni e mostra l'errore UI  |\n| Comando `bash` RPC                    | `rpc-mode` -> `session.executeBash`                   | No                                                                    | Restituisce `BashResult` direttamente                                               | Il consumatore gestisce i campi restituiti               |\n\n## Avvertenze operative\n\n- L'interceptor blocca i comandi solo quando lo strumento suggerito è attualmente disponibile nel contesto.\n- Se l'allocazione dell'artefatto fallisce, la troncatura si verifica comunque ma non è disponibile alcun back-reference `artifact://`.\n- La cache delle sessioni shell non ha un'evizione esplicita in questo modulo; la durata è limitata al processo.\n- Le superfici di timeout PTY e non-PTY differiscono:\n  - PTY espone il campo di risultato esplicito `timedOut`,\n  - non-PTY mappa il timeout in `cancelled + annotation` summary.\n\n## File di implementazione\n\n- [`src/tools/bash.ts`](../../packages/coding-agent/src/tools/bash.ts) — punto di ingresso dello strumento, normalizzazione/intercettazione, selezione PTY/non-PTY, mappatura risultati/errori, renderer dello strumento bash.\n- [`src/tools/bash-normalize.ts`](../../packages/coding-agent/src/tools/bash-normalize.ts) — normalizzazione dei comandi e filtraggio head/tail post-esecuzione.\n- [`src/tools/bash-interceptor.ts`](../../packages/coding-agent/src/tools/bash-interceptor.ts) — corrispondenza delle regole dell'interceptor e messaggi di comando bloccato.\n- [`src/exec/bash-executor.ts`](../../packages/coding-agent/src/exec/bash-executor.ts) — executor non-PTY, riutilizzo delle sessioni shell, collegamento della cancellazione, integrazione del sink di output.\n- [`src/tools/bash-interactive.ts`](../../packages/coding-agent/src/tools/bash-interactive.ts) — runtime PTY, overlay UI, normalizzazione dell'input, impostazioni predefinite env non interattive.\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — troncatura/spill degli artefatti `OutputSink` e metadati di riepilogo.\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — helper per l'allocazione degli artefatti e buffer tail in streaming.\n- [`src/tools/output-meta.ts`](../../packages/coding-agent/src/tools/output-meta.ts) — forma dei metadati di troncatura + wrapper di iniezione degli avvisi.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — `executeBash` a livello di sessione, registrazione dei messaggi, ciclo di vita dell'interruzione.\n- [`src/modes/components/bash-execution.ts`](../../packages/coding-agent/src/modes/components/bash-execution.ts) — componente di esecuzione del comando `!` interattivo.\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts) — collegamento per il completamento del flusso/aggiornamento UI del comando `!` interattivo.\n- [`src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts) — superficie dei comandi `bash` e `abort_bash` RPC.\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — risoluzione di `artifact://<id>`.\n",
	"it/runtime-tools/context-command.md": "---\ntitle: Contesti F5 XC\ndescription: >-\n  Connettere xcsh ai tenant F5 Distributed Cloud -- creare, cambiare e gestire i\n  contesti di autenticazione.\nsidebar:\n  order: 1\n  label: Contesti F5 XC\ni18n:\n  sourceHash: a9cccbc338f0\n  translator: machine\n---\n\n# Contesti F5 XC\n\nxcsh si connette a F5 Distributed Cloud tramite i **contesti** -- set di credenziali con nome che associano un URL del tenant, un token API e un namespace. Se avete utilizzato `kubectl config use-context` o `kubectx`, il flusso di lavoro è identico: creare un contesto, passare dall'uno all'altro per nome e usare `-` per tornare al precedente.\n\n## Per iniziare\n\n### 1. Creare il primo contesto\n\nServono tre informazioni dalla console F5 XC: l'URL del tenant, un token API e opzionalmente un namespace.\n\n```\n/context create production https://acme.console.ves.volterra.io p12k3-your-api-token\n```\n\n```\nContext 'production' created. Use /context activate production to switch to it.\n```\n\nOppure utilizzare la procedura guidata se si preferiscono i prompt passo-passo:\n\n```\n/context wizard\n```\n\n### 2. Attivarlo\n\n```\n/context production\n```\n\n```\n╭─ production ─────────────────────────────────────────────────╮\n│ XCSH_TENANT     acme                                         │\n│ XCSH_API_URL    https://acme.console.ves.volterra.io         │\n│ XCSH_API_TOKEN  ...oken                                      │\n│ Status          Connected (312ms)                            │\n├─ Environment ────────────────────────────────────────────────┤\n│ XCSH_NAMESPACE  default                                      │\n╰──────────────────────────────────────────────────────────────╯\n```\n\nUna volta attivato, xcsh inietta le credenziali del tenant nella sessione. L'agente può ora effettuare chiamate API F5 XC e la barra di stato mostra il contesto attivo.\n\n### 3. Aggiungere altri contesti e passare dall'uno all'altro\n\n```\n/context create staging https://staging.console.ves.volterra.io p12k3-staging-token\n```\n\nPassare per nome -- non è necessario alcun verbo di sottocomando:\n\n```\n/context staging\n```\n\nTornare al contesto precedente (stile `cd -`):\n\n```\n/context -\n```\n\nEseguire `/context -` due volte riporta al punto di partenza.\n\n### 4. Visualizzare i contesti disponibili\n\n```\n/context\n```\n\n```\n  production           https://acme.console.ves.volterra.io\n* staging              https://staging.console.ves.volterra.io\n```\n\nIl simbolo `*` indica il contesto attivo.\n\n## Comandi di uso quotidiano\n\n| Comando | Funzione |\n|---|---|\n| `/context` | Elencare tutti i contesti |\n| `/context <name>` | Passare a un contesto |\n| `/context -` | Passare al contesto precedente |\n| `/context show` | Mostrare i dettagli del contesto attivo (token mascherati) |\n| `/context status` | Mostrare lo stato corrente dell'autenticazione |\n\n## Ciclo di vita del contesto\n\n| Comando | Funzione |\n|---|---|\n| `/context create <name> <url> <token> [namespace]` | Creare un contesto |\n| `/context delete <name> --confirm` | Eliminare un contesto (richiede `--confirm`) |\n| `/context rename <old> <new>` | Rinominare un contesto |\n| `/context validate <name>` | Testare le credenziali senza cambiare contesto |\n| `/context export [name] [--include-token]` | Esportare come JSON (token mascherati per impostazione predefinita) |\n| `/context import <path-or-json> [--overwrite]` | Importare da file o JSON inline |\n| `/context wizard` | Configurazione interattiva guidata |\n\n## Cambio di namespace\n\nOgni contesto ha un namespace predefinito. È possibile cambiarlo senza modificare il contesto:\n\n```\n/context namespace system\n```\n\nIl completamento automatico con Tab suggerisce i nomi dei namespace dal tenant attivo.\n\n## Variabili d'ambiente nei contesti\n\nI contesti possono contenere variabili d'ambiente aggiuntive che vengono iniettate nella sessione al momento dell'attivazione. Utile per configurazioni specifiche del tenant che non fanno parte del set di credenziali.\n\n```\n/context set CUSTOM_HEADER=x-acme-trace\n/context set LOG_LEVEL=debug\n/context env list\n/context unset LOG_LEVEL\n```\n\nAlias: `add` = `set`, `remove`/`clear` = `unset`.\n\n## Completamento con Tab\n\nDigitare `/context ` e premere Tab. Il menu a discesa mostra:\n\n1. **Nomi dei contesti** -- con indicazioni sull'URL del tenant, per distinguere i diversi tenant\n2. **`-`** -- appare quando si è già effettuato un cambio, mostra verso quale contesto si tornerebbe\n3. **Sottocomandi** -- `list`, `create`, `delete`, ecc.\n\nI nomi dei contesti appaiono per primi perché il cambio di contesto è l'azione più comune.\n\nIl completamento funziona anche a livello di sottocomando: `/context activate <Tab>` completa i nomi dei contesti, `/context namespace <Tab>` completa i namespace, `/context unset <Tab>` completa le chiavi delle variabili d'ambiente conosciute.\n\n## Regole di denominazione\n\nI nomi dei contesti devono essere composti da 1-64 caratteri: lettere, cifre, trattini, underscore.\n\nI nomi che collidono con i sottocomandi vengono rifiutati:\n\n```\n/context create list https://example.com tok\n```\n\n```\nError: Context name 'list' conflicts with a /context subcommand. Choose a different name.\n```\n\nL'insieme completo dei nomi riservati: `list`, `show`, `status`, `create`, `delete`, `rename`, `namespace`, `env`, `set`, `unset`, `add`, `remove`, `clear`, `activate`, `validate`, `export`, `import`, `wizard`, `help`. Il confronto è case-insensitive.\n\n## Override tramite variabili d'ambiente\n\nSe `XCSH_API_URL` e `XCSH_API_TOKEN` sono impostate nell'ambiente della shell prima di avviare xcsh, hanno la precedenza su qualsiasi contesto. Questo è utile per pipeline CI/CD o sessioni occasionali in cui non si desidera creare un contesto persistente.\n\nQuando si opera in questa modalità, `/context` mostra le credenziali provenienti dall'ambiente con l'etichetta `(via env vars)`.\n\n## Comportamento del contesto precedente\n\n- **Ambito di sessione**: il contesto precedente viene azzerato al riavvio di xcsh. Non viene persistito su disco.\n- **Ping-pong**: `/context -` eseguito due volte riporta al punto di partenza.\n- **Sicuro rispetto alle modifiche**: se si elimina il contesto precedente, il puntatore viene cancellato. Se lo si rinomina, il puntatore segue il nuovo nome.\n- **La riattivazione è un no-op**: `/context production` quando si è già su `production` non azzera il puntatore al contesto precedente.\n\n## Convenzioni di design\n\nL'esperienza utente di `/context` segue:\n\n- **kubectx**: `kubectx <name>` per il cambio, `kubectx -` per il precedente, `kubectx` senza argomenti per l'elenco\n- **kubectl**: `kubectl config use-context` per la forma esplicita\n- **Shell**: `cd -` / `OLDPWD` per il tracciamento della directory precedente\n",
	"it/runtime-tools/custom-tools.md": "---\ntitle: Strumenti personalizzati\ndescription: >-\n  Registrazione di strumenti personalizzati, definizione dello schema e pipeline\n  di esecuzione per estendere l'agente.\nsidebar:\n  order: 4\n  label: Strumenti personalizzati\ni18n:\n  sourceHash: 5f4a441fc2e2\n  translator: machine\n---\n\n# Strumenti personalizzati\n\nGli strumenti personalizzati sono funzioni richiamabili dal modello che si integrano nella stessa pipeline di esecuzione degli strumenti integrati.\n\nUno strumento personalizzato è un modulo TypeScript/JavaScript che esporta una factory. La factory riceve un'API host (`CustomToolAPI`) e restituisce uno strumento o un array di strumenti.\n\n## Cosa è (e cosa non è)\n\n- **Strumento personalizzato**: richiamabile dal modello durante un turno (`execute` + schema TypeBox).\n- **Estensione**: framework di ciclo di vita/eventi che può registrare strumenti e intercettare/modificare eventi.\n- **Hook**: script esterni pre/post comando.\n- **Skill**: pacchetto statico di guida/contesto, non codice di strumento eseguibile.\n\nSe è necessario che il modello richiami codice direttamente, utilizzare uno strumento personalizzato.\n\n## Percorsi di integrazione nel codice corrente\n\nEsistono due stili di integrazione attivi:\n\n1. **Strumenti personalizzati forniti dall'SDK** (`options.customTools`)\n   - Incapsulati in strumenti agente tramite `CustomToolAdapter` o wrapper di estensione.\n   - Sempre inclusi nel set di strumenti attivi iniziale nel bootstrap dell'SDK.\n\n2. **Moduli rilevati dal filesystem tramite API loader** (`discoverAndLoadCustomTools` / `loadCustomTools`)\n   - Esposti come API di libreria in `src/extensibility/custom-tools/loader.ts`.\n   - Il codice host può chiamarli per rilevare e caricare moduli di strumenti dai percorsi di configurazione/provider/plugin.\n\n```text\nFlusso chiamata strumento modello\n\nChiamata strumento LLM\n   │\n   ▼\nRegistro strumenti (integrati + adattatori strumenti personalizzati)\n   │\n   ▼\nCustomTool.execute(toolCallId, params, onUpdate, ctx, signal)\n   │\n   ├─ onUpdate(...)  -> risultato parziale in streaming\n   └─ return result  -> contenuto/dettagli strumento finale\n```\n\n## Posizioni di rilevamento (API loader)\n\n`discoverAndLoadCustomTools(configuredPaths, cwd, builtInToolNames)` combina:\n\n1. Provider di capacità (`toolCapability`), inclusi:\n   - Configurazione OMP nativa (`~/.xcsh/agent/tools`, `.xcsh/tools`)\n   - Configurazione Claude (`~/.claude/tools`, `.claude/tools`)\n   - Configurazione Codex (`~/.codex/tools`, `.codex/tools`)\n   - Provider cache plugin marketplace Claude\n2. Manifesti plugin installati (`~/.xcsh/plugins/node_modules/*` tramite plugin loader)\n3. Percorsi configurati esplicitamente passati al loader\n\n### Comportamento importante\n\n- I percorsi risolti duplicati vengono deduplicati.\n- I conflitti di nomi degli strumenti vengono rifiutati rispetto agli strumenti integrati e agli strumenti personalizzati già caricati.\n- I file `.md` e `.json` vengono rilevati come metadati degli strumenti da alcuni provider, ma il loader di moduli eseguibili li rifiuta come strumenti eseguibili.\n- I percorsi configurati relativi vengono risolti a partire da `cwd`; `~` viene espanso.\n\n## Contratto del modulo\n\nUn modulo di strumento personalizzato deve esportare una funzione (preferibilmente export default):\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = (pi) => ({\n name: \"repo_stats\",\n label: \"Repo Stats\",\n description: \"Counts tracked TypeScript files\",\n parameters: pi.typebox.Type.Object({\n  glob: pi.typebox.Type.Optional(pi.typebox.Type.String({ default: \"**/*.ts\" })),\n }),\n\n async execute(toolCallId, params, onUpdate, ctx, signal) {\n  onUpdate?.({\n   content: [{ type: \"text\", text: \"Scanning files...\" }],\n   details: { phase: \"scan\" },\n  });\n\n  const result = await pi.exec(\"git\", [\"ls-files\", params.glob ?? \"**/*.ts\"], { signal, cwd: pi.cwd });\n  if (result.killed) {\n   throw new Error(\"Scan was cancelled\");\n  }\n  if (result.code !== 0) {\n   throw new Error(result.stderr || \"git ls-files failed\");\n  }\n\n  const files = result.stdout.split(\"\\n\").filter(Boolean);\n  return {\n   content: [{ type: \"text\", text: `Found ${files.length} files` }],\n   details: { count: files.length, sample: files.slice(0, 10) },\n  };\n },\n\n onSession(event) {\n  if (event.reason === \"shutdown\") {\n   // cleanup resources if needed\n  }\n },\n});\n\nexport default factory;\n```\n\nTipo restituito dalla factory:\n\n- `CustomTool`\n- `CustomTool[]`\n- `Promise<CustomTool | CustomTool[]>`\n\n## Superficie API passata alle factory (`CustomToolAPI`)\n\nDa `types.ts` e `loader.ts`:\n\n- `cwd`: directory di lavoro dell'host\n- `exec(command, args, options?)`: helper per l'esecuzione di processi\n- `ui`: contesto UI (può essere no-op nelle modalità headless)\n- `hasUI`: `false` nei flussi non interattivi\n- `logger`: logger su file condiviso\n- `typebox`: `@sinclair/typebox` iniettato\n- `pi`: export di `@f5-sales-demo/xcsh` iniettati\n- `pushPendingAction(action)`: registra un'azione di anteprima per lo strumento nascosto `resolve` (`docs/resolve-tool-runtime.md`)\n\nIl loader avvia con un contesto UI no-op e richiede che il codice host chiami `setUIContext(...)` quando la UI reale è pronta.\n\n## Contratto di esecuzione e tipizzazione\n\nFirma di `CustomTool.execute`:\n\n```ts\nexecute(toolCallId, params, onUpdate, ctx, signal)\n```\n\n- `params` è tipizzato staticamente dallo schema TypeBox tramite `Static<TParams>`.\n- La validazione degli argomenti a runtime avviene prima dell'esecuzione nel ciclo agente.\n- `onUpdate` emette risultati parziali per lo streaming UI.\n- `ctx` include lo stato sessione/modello e un helper `abort()`.\n- `signal` gestisce la cancellazione.\n\n`CustomToolAdapter` collega questo all'interfaccia dello strumento agente e inoltra le chiamate nell'ordine corretto degli argomenti.\n\n## Come gli strumenti vengono esposti al modello\n\n- Gli strumenti vengono incapsulati in istanze `AgentTool` (`CustomToolAdapter` o wrapper di estensione).\n- Vengono inseriti nel registro degli strumenti di sessione per nome.\n- Nel bootstrap dell'SDK, gli strumenti personalizzati e quelli registrati tramite estensione vengono forzatamente inclusi nel set attivo iniziale.\n- L'opzione CLI `--tools` al momento valida solo i nomi degli strumenti integrati; l'inclusione degli strumenti personalizzati è gestita tramite i percorsi di rilevamento/registrazione e le opzioni dell'SDK.\n\n## Hook di rendering\n\nHook di rendering facoltativi:\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\nComportamento a runtime nella TUI:\n\n- Se gli hook esistono, l'output dello strumento viene renderizzato all'interno di un contenitore `Box`.\n- `renderResult` riceve `{ expanded, isPartial, spinnerFrame? }`.\n- Gli errori del renderer vengono intercettati e registrati; la UI torna al rendering testuale predefinito.\n\n## Gestione sessione/stato\n\nL'hook facoltativo `onSession(event, ctx)` riceve gli eventi del ciclo di vita della sessione, inclusi:\n\n- `start`, `switch`, `branch`, `tree`, `shutdown`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`, `todo_reminder`\n\nUtilizzare `ctx.sessionManager` per ricostruire lo stato dalla cronologia quando il contesto branch/sessione cambia.\n\n## Semantica di fallimenti e cancellazione\n\n### Fallimenti sincroni/asincroni\n\n- Sollevare un'eccezione (o promise rifiutate) in `execute` viene trattato come fallimento dello strumento.\n- Il runtime agente converte i fallimenti in messaggi di risultato dello strumento con `isError: true` e contenuto testuale dell'errore.\n- Con i wrapper di estensione, i gestori `tool_result` possono ulteriormente riscrivere contenuto/dettagli e persino sovrascrivere lo stato di errore.\n\n### Cancellazione\n\n- L'abort dell'agente si propaga tramite `AbortSignal` a `execute`.\n- Inoltrare `signal` ai processi secondari (`pi.exec(..., { signal })`) per la cancellazione cooperativa.\n- `ctx.abort()` consente a uno strumento di richiedere l'abort dell'operazione agente corrente.\n\n### Errori onSession\n\n- Gli errori di `onSession` vengono intercettati e registrati come avvisi; non provocano il crash della sessione.\n\n## Vincoli reali da considerare in fase di progettazione\n\n- I nomi degli strumenti devono essere globalmente univoci nel registro attivo.\n- Preferire output deterministici a forma di schema in `details` per la ricostruzione renderer/stato.\n- Proteggere l'utilizzo dell'UI con `pi.hasUI`.\n- Trattare i file `.md`/`.json` nelle directory degli strumenti come metadati, non come moduli eseguibili.\n",
	"it/runtime-tools/notebook-tool-runtime.md": "---\ntitle: Componenti interni del runtime dello strumento Notebook\ndescription: >-\n  Runtime dello strumento Jupyter notebook con esecuzione delle celle, ciclo di\n  vita del kernel e rendering dell'output.\nsidebar:\n  order: 2\n  label: Strumento Notebook\ni18n:\n  sourceHash: c1bafcb245e4\n  translator: machine\n---\n\n# Componenti interni del runtime dello strumento Notebook\n\nQuesto documento descrive l'implementazione corrente dello strumento `notebook` e la sua relazione con il runtime Python supportato dal kernel.\n\nLa distinzione fondamentale: **`notebook` è un editor JSON di notebook, non un esecutore di notebook**. Modifica direttamente i sorgenti delle celle `.ipynb`; non avvia né comunica con un kernel Python.\n\n## File di implementazione\n\n- [`src/tools/notebook.ts`](../../packages/coding-agent/src/tools/notebook.ts)\n- [`src/ipy/executor.ts`](../../packages/coding-agent/src/ipy/executor.ts)\n- [`src/ipy/kernel.ts`](../../packages/coding-agent/src/ipy/kernel.ts)\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts)\n- [`src/tools/python.ts`](../../packages/coding-agent/src/tools/python.ts)\n\n## 1) Confine di runtime: modifica vs esecuzione\n\n## Strumento `notebook` (`src/tools/notebook.ts`)\n\n- Supporta `action: edit | insert | delete` su un file `.ipynb`.\n- Risolve il percorso relativo alla CWD della sessione (`resolveToCwd`).\n- Carica il JSON del notebook, valida l'array `cells`, valida i limiti di `cell_index`.\n- Applica le modifiche ai sorgenti in memoria e riscrive il JSON completo del notebook con `JSON.stringify(notebook, null, 1)`.\n- Restituisce un riepilogo testuale e `details` strutturati (`action`, `cellIndex`, `cellType`, `totalCells`, `cellSource`).\n\nIn questo strumento non esiste alcun ciclo di vita del kernel:\n\n- nessuna acquisizione del gateway\n- nessun ID di sessione del kernel\n- nessun `execute_request`\n- nessun chunk di stream dai canali del kernel\n- nessuna cattura di display rich (`image/png`, display JSON, status MIME)\n\n## Percorso di esecuzione simile a notebook (`src/tools/python.ts` + `src/ipy/*`)\n\nQuando l'agente deve eseguire codice Python in stile celle (celle sequenziali, stato persistente, display rich), ciò avviene attraverso lo strumento **`python`**, non `notebook`.\n\nÈ in quel percorso che risiedono le modalità del kernel, il comportamento di restart/cancel, lo streaming dei chunk e il troncamento degli artefatti di output.\n\n## 2) Semantica di gestione delle celle del notebook (strumento `notebook`)\n\n## Normalizzazione dei sorgenti\n\n`content` viene suddiviso in `source: string[]` con preservazione delle interruzioni di riga:\n\n- ogni riga non finale mantiene il `\\n` finale\n- l'ultima riga non ha una newline finale forzata\n\nQuesto rispecchia le convenzioni JSON dei notebook ed evita la concatenazione accidentale di righe nelle modifiche successive.\n\n## Comportamento delle azioni\n\n- `edit`\n  - sostituisce `cells[cell_index].source`\n  - preserva il `cell_type` esistente\n- `insert`\n  - inserisce in `[0..cellCount]`\n  - `cell_type` predefinito è `code`\n  - le celle di codice inizializzano `execution_count: null` e `outputs: []`\n  - le celle markdown inizializzano solo `metadata` + `source`\n- `delete`\n  - rimuove `cells[cell_index]`\n  - restituisce il `source` rimosso in details per l'anteprima del renderer\n\n## Superfici di errore\n\nVengono sollevati errori critici per:\n\n- file notebook mancante\n- JSON non valido\n- `cells` mancante o non array\n- indice fuori intervallo (inserimento e non inserimento hanno intervalli validi diversi)\n- `content` mancante per `edit`/`insert`\n\nQuesti diventano risposte di strumento `Error:` a monte; il renderer utilizza il percorso del notebook e il testo dell'errore formattato.\n\n## 3) Semantica delle sessioni del kernel (dove esistono effettivamente)\n\nLa semantica del kernel è implementata in `executePython` / `PythonKernel` e si applica allo strumento `python`.\n\n## Modalità\n\n`PythonKernelMode`:\n\n- `session` (predefinita)\n  - kernel memorizzati nella mappa `kernelSessions`\n  - massimo 4 sessioni; quella più vecchia viene eliminata in caso di overflow\n  - pulizia idle/dead ogni 30 secondi, timeout dopo 5 minuti\n  - la coda per sessione serializza l'esecuzione (`session.queue`)\n- `per-call`\n  - crea il kernel per la richiesta\n  - esegue\n  - arresta sempre il kernel in `finally`\n\n## Comportamento di reset\n\nLo strumento `python` passa `reset` solo per la prima cella in una chiamata multi-cella; le celle successive vengono sempre eseguite con `reset: false`.\n\n## Morte del kernel / restart / retry\n\nIn modalità sessione (`withKernelSession`):\n\n- il kernel morto viene rilevato tramite heartbeat (controllo `kernel.isAlive()` ogni 5 secondi) o da un errore di esecuzione.\n- lo stato morto pre-esecuzione attiva `restartKernelSession`.\n- il percorso di crash durante l'esecuzione riprova una volta: riavvia il kernel, riesegue l'handler.\n- `restartCount > 1` nella stessa sessione genera `Python kernel restarted too many times in this session`.\n\nComportamento di retry all'avvio:\n\n- la creazione del kernel sul gateway condiviso riprova una volta su `SharedGatewayCreateError` con HTTP 5xx.\n\nRecupero da esaurimento delle risorse:\n\n- rileva errori di tipo `EMFILE`/`ENFILE`/\"Too many open files\"\n- cancella le sessioni tracciate\n- chiama `shutdownSharedGateway()`\n- riprova la creazione della sessione del kernel una volta\n\n## 4) Iniezione di variabili di ambiente/sessione\n\nAll'avvio del kernel viene passata una mappa env opzionale dall'esecutore:\n\n- `PI_SESSION_FILE` (percorso del file di stato della sessione)\n- `ARTIFACTS` (directory degli artefatti)\n\n`PythonKernel.#initializeKernelEnvironment(...)` esegue quindi uno script di inizializzazione all'interno del kernel per:\n\n- `os.chdir(cwd)`\n- iniettare le voci env in `os.environ`\n- anteporre cwd a `sys.path` se mancante\n\nImplicazione:\n\n- gli helper di preludio che leggono il contesto di sessione o artefatto si basano su queste variabili env nello stato del processo Python.\n\n## 5) Gestione dello streaming/chunk e dei display (percorso supportato dal kernel)\n\nIl client del kernel elabora i messaggi del protocollo Jupyter per ogni esecuzione:\n\n- `stream` -> chunk di testo verso `onChunk`\n- `execute_result` / `display_data` ->\n  - testo del display scelto per precedenza MIME: `text/markdown` > `text/plain` > `text/html` convertito\n  - output strutturati catturati separatamente:\n    - `application/json` -> `{ type: \"json\" }`\n    - `image/png` -> `{ type: \"image\" }`\n    - `application/x-xcsh-status` -> `{ type: \"status\" }` (nessuna emissione di testo)\n- `error` -> testo del traceback inviato allo stream di chunk + metadati di errore strutturati\n- `input_request` -> emette testo di avviso stdin, invia `input_reply` vuoto, contrassegna stdin come richiesto\n- il completamento attende sia `execute_reply` che `status=idle` del kernel\n\nCancellazione/timeout:\n\n- il segnale di interruzione attiva `interrupt()` (REST `/interrupt` + `interrupt_request` sul canale di controllo)\n- il risultato è contrassegnato con `cancelled=true`\n- il percorso di timeout annota l'output con `Command timed out after <n> seconds`\n\n## 6) Comportamento di troncamento e artefatti\n\n`OutputSink` in `src/session/streaming-output.ts` è utilizzato dai percorsi di esecuzione del kernel (`executeWithKernel`):\n\n- sanifica ogni chunk (`sanitizeText`)\n- tiene traccia del totale di righe, righe di output e byte\n- file di spill opzionale per artefatti (`artifactPath`, `artifactId`)\n- quando il buffer in memoria supera la soglia (`DEFAULT_MAX_BYTES` a meno che non sia sovrascritto):\n  - contrassegna come troncato\n  - mantiene in memoria i byte finali (confine UTF-8 sicuro)\n  - può riversare lo stream completo nel sink degli artefatti\n\n`dump()` restituisce:\n\n- testo dell'output visibile (possibilmente troncato dalla coda)\n- flag di troncamento + conteggi\n- ID artefatto (per riferimenti `artifact://<id>`)\n\nLo strumento `python` converte questi metadati in avvisi di troncamento del risultato e avvisi TUI.\n\nLo strumento `notebook` **non** utilizza `OutputSink`; non dispone di pipeline di troncamento stream/artefatto perché non esegue codice.\n\n## 7) Assunzioni del renderer e formattazione\n\n## Renderer del notebook (`notebookToolRenderer`)\n\n- vista della chiamata: riga di stato con azione + percorso del notebook + metadati cella/tipo\n- vista del risultato:\n  - riepilogo del successo derivato da `details`\n  - `cellSource` reso tramite `renderCodeCell`\n  - le celle markdown impostano il suggerimento di linguaggio `markdown`; le altre celle non hanno override di linguaggio esplicito\n  - il limite di anteprima del codice compresso è `PREVIEW_LIMITS.COLLAPSED_LINES * 2`\n  - supporta la modalità espansa tramite opzioni di rendering condivise\n  - utilizza la cache di rendering con chiave per larghezza + stato espanso\n\nAssunzione del rendering degli errori:\n\n- se il primo contenuto testuale inizia con `Error:`, il renderer lo formatta come blocco di errore del notebook.\n\n## Renderer Python (per l'output dell'esecuzione effettiva)\n\nIl rendering dell'esecuzione supportata dal kernel prevede:\n\n- transizioni di stato per cella (`pending/running/complete/error`)\n- sezione opzionale di eventi di stato strutturati\n- alberi di output JSON opzionali\n- avvisi di troncamento + puntatore `artifact://<id>` opzionale\n\nIl comportamento di questo renderer non è correlato ai risultati delle modifiche JSON di `notebook`, tranne per il fatto che entrambi riutilizzano primitive TUI condivise.\n\n## 8) Divergenza dal comportamento dello strumento Python semplice\n\nSe per \"strumento Python semplice\" si intende il percorso di esecuzione `python`:\n\n- `python` esegue il codice in un kernel, persiste lo stato in base alla modalità, effettua lo streaming dei chunk, cattura i display rich, gestisce interrupt/timeout e supporta il troncamento dell'output/artefatti.\n- `notebook` esegue solo mutazioni deterministiche del JSON del notebook; nessuna esecuzione, nessuno stato del kernel, nessun stream di chunk, nessun output di display, nessuna pipeline di artefatti.\n\nSe un flusso di lavoro richiede entrambi:\n\n1. modificare i sorgenti del notebook con `notebook`\n2. eseguire le celle di codice tramite `python` (passando manualmente il codice), non attraverso `notebook`\n\nL'implementazione attuale non fornisce un singolo strumento che sia in grado di mutare `.ipynb` ed eseguire celle del notebook attraverso il contesto del kernel.\n",
	"it/runtime-tools/resolve-tool-runtime.md": "---\ntitle: Dettagli interni del runtime dello strumento Resolve\ndescription: >-\n  Runtime dello strumento Resolve per la risoluzione dei percorsi di file, il\n  recupero dei contenuti e l'accesso alle risorse basato su URL.\nsidebar:\n  order: 3\n  label: Strumento Resolve\ni18n:\n  sourceHash: 06e8be8c5a3c\n  translator: machine\n---\n\n# Dettagli interni del runtime dello strumento Resolve\n\nQuesto documento spiega come i flussi di lavoro preview/apply sono modellati in coding-agent e come gli strumenti personalizzati possono partecipare tramite `pushPendingAction`.\n\n## Ambito e file principali\n\n- [`src/tools/resolve.ts`](../../packages/coding-agent/src/tools/resolve.ts)\n- [`src/tools/pending-action.ts`](../../packages/coding-agent/src/tools/pending-action.ts)\n- [`src/tools/ast-edit.ts`](../../packages/coding-agent/src/tools/ast-edit.ts)\n- [`src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts)\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n\n## Cosa fa `resolve`\n\n`resolve` è uno strumento nascosto che finalizza un'azione di anteprima in sospeso.\n\n- `action: \"apply\"` esegue `apply(reason)` sull'azione in sospeso e persiste le modifiche.\n- `action: \"discard\"` invoca `reject(reason)` se fornito; altrimenti scarta l'azione con un messaggio predefinito \"Discarded\".\n\nSe non esiste alcuna azione in sospeso, `resolve` fallisce con:\n\n- `No pending action to resolve. Nothing to apply or discard.`\n\n## Le azioni in sospeso sono uno stack (LIFO)\n\nLe azioni in sospeso sono memorizzate in `PendingActionStore` come uno stack push/pop:\n\n- `push(action)` aggiunge una nuova azione in sospeso in cima.\n- `peek()` ispeziona l'azione corrente in cima.\n- `pop()` rimuove e restituisce l'azione in cima.\n- `hasPending` indica se lo stack è non vuoto.\n\n`resolve` consuma sempre l'azione in sospeso **più in alto** per prima (`pop()`), quindi più strumenti che producono anteprime vengono risolti in ordine inverso di registrazione.\n\n## Esempio di produttore integrato (`ast_edit`)\n\n`ast_edit` visualizza in anteprima le sostituzioni strutturali prima. Quando l'anteprima contiene sostituzioni e non è ancora stata applicata, inserisce un'azione in sospeso che contiene:\n\n- label (riepilogo leggibile dall'utente)\n- `sourceToolName` (`ast_edit`)\n- callback `apply(reason: string)` che riesegue la modifica AST con `dryRun: false`\n\n`resolve(action=\"apply\", reason=\"...\")` passa `reason` a questo callback.\n\n## Strumenti personalizzati: `pushPendingAction`\n\nGli strumenti personalizzati possono registrare azioni in sospeso compatibili con resolve tramite `CustomToolAPI.pushPendingAction(...)`.\n\n`CustomToolPendingAction`:\n\n- `label: string` (obbligatorio)\n- `apply(reason: string): Promise<AgentToolResult<unknown>>` (obbligatorio) — invocato all'applicazione; `reason` è la stringa passata a `resolve`\n- `reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>` (facoltativo) — invocato al momento dello scarto; il valore restituito sostituisce il messaggio predefinito \"Discarded\" se fornito\n- `details?: unknown` (facoltativo)\n- `sourceToolName?: string` (facoltativo, il valore predefinito è `\"custom_tool\"`)\n\n### Esempio di utilizzo minimale\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = pi => ({\n name: \"batch_rename_preview\",\n label: \"Batch Rename Preview\",\n description: \"Previews renames and defers commit to resolve\",\n parameters: pi.typebox.Type.Object({\n  files: pi.typebox.Type.Array(pi.typebox.Type.String()),\n }),\n\n async execute(_toolCallId, params) {\n  const previewSummary = `Prepared rename plan for ${params.files.length} files`;\n\n  pi.pushPendingAction({\n   label: `Batch rename: ${params.files.length} files`,\n   sourceToolName: \"batch_rename_preview\",\n   apply: async (reason) => {\n    // apply writes here\n    return {\n     content: [{ type: \"text\", text: `Applied batch rename. Reason: ${reason}` }],\n    };\n   },\n   reject: async (reason) => {\n    // optional: cleanup or notify on discard\n    return {\n     content: [{ type: \"text\", text: `Discarded batch rename. Reason: ${reason}` }],\n    };\n   },\n  });\n\n  return {\n   content: [{ type: \"text\", text: `${previewSummary}. Call resolve to apply or discard.` }],\n  };\n },\n});\n\nexport default factory;\n```\n\n## Disponibilità del runtime e malfunzionamenti\n\n`pushPendingAction` è collegato dal loader degli strumenti personalizzati utilizzando il `PendingActionStore` della sessione attiva.\n\nSe il runtime non dispone di un archivio delle azioni in sospeso, `pushPendingAction` genera un'eccezione:\n\n- `Pending action store unavailable for custom tools in this runtime.`\n\n## Comportamento della scelta dello strumento\n\nQuando `PendingActionStore.hasPending` è true, il runtime dell'agente orienta la scelta dello strumento verso `resolve`, in modo che le anteprime in sospeso vengano esplicitamente finalizzate prima che il normale flusso degli strumenti continui.\n\n## Indicazioni per gli sviluppatori\n\n- Utilizzare le azioni in sospeso solo per operazioni distruttive o ad alto impatto che dovrebbero supportare un'applicazione/scarto espliciti.\n- Mantenere `label` conciso e specifico; viene visualizzato nell'output del renderer di resolve.\n- Assicurarsi che `apply(reason)` sia sufficientemente deterministico e idempotente per un'esecuzione una tantum; `reason` è informativo e non dovrebbe modificare il comportamento.\n- Implementare `reject(reason)` quando lo scarto richiede una pulizia (stato temporaneo, lock, notifiche); ometterlo per le anteprime senza stato in cui il messaggio predefinito è sufficiente.\n- Se lo strumento può gestire più anteprime in fase di staging, ricordare la semantica LIFO: l'ultima azione inserita viene risolta per prima.\n",
	"it/runtime-tools/slash-command-internals.md": "---\ntitle: Meccanismi interni dei comandi Slash\ndescription: >-\n  Meccanismi interni del sistema di comandi slash con registrazione, analisi\n  degli argomenti e dispatch dell'esecuzione.\nsidebar:\n  order: 5\n  label: Comandi slash\ni18n:\n  sourceHash: 2cbd44a3de87\n  translator: machine\n---\n\n# Meccanismi interni dei comandi slash\n\nQuesto documento descrive come i comandi slash vengono individuati, deduplicati, esposti nella modalità interattiva ed espansi al momento della richiesta in `coding-agent`.\n\n## File di implementazione\n\n- [`src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n- [`src/capability/slash-command.ts`](../../packages/coding-agent/src/capability/slash-command.ts)\n- [`src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`src/discovery/claude.ts`](../../packages/coding-agent/src/discovery/claude.ts)\n- [`src/discovery/codex.ts`](../../packages/coding-agent/src/discovery/codex.ts)\n- [`src/discovery/claude-plugins.ts`](../../packages/coding-agent/src/discovery/claude-plugins.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n\n## 1) Modello di individuazione\n\nI comandi slash sono una capacità (`id: \"slash-commands\"`) indicizzata per nome di comando (`key: cmd => cmd.name`).\n\nIl registro delle capacità carica tutti i provider registrati, ordinati per priorità del provider in ordine decrescente, e deduplica per chiave con semantica **il primo vince**.\n\n### Precedenza dei provider\n\nProvider di comandi slash attuali e relative priorità:\n\n1. `native` (OMP) — priorità `100`\n2. `claude` — priorità `80`\n3. `claude-plugins` — priorità `70`\n4. `codex` — priorità `70`\n\nComportamento in caso di parità: i provider con uguale priorità mantengono l'ordine di registrazione. L'ordine di importazione corrente registra `claude-plugins` prima di `codex`, pertanto i comandi dei plugin hanno la precedenza sui comandi codex in caso di collisioni di nomi.\n\n### Comportamento in caso di collisioni di nomi\n\nPer `slash-commands`, le collisioni vengono risolte esclusivamente tramite deduplicazione delle capacità:\n\n- l'elemento con la precedenza più alta viene mantenuto in `result.items`\n- i duplicati con precedenza inferiore rimangono solo in `result.all` e vengono contrassegnati con `_shadowed = true`\n\nQuesto si applica tra i provider e anche all'interno di un singolo provider qualora restituisca nomi duplicati.\n\n### Comportamento della scansione dei file\n\nI provider utilizzano principalmente `loadFilesFromDir(...)`, che attualmente:\n\n- utilizza per impostazione predefinita la corrispondenza non ricorsiva (`*.md`)\n- usa glob nativo con `gitignore: true`, `hidden: false`\n- legge ogni file corrispondente e lo trasforma in uno `SlashCommand`\n\nPertanto i file/directory nascosti non vengono caricati e i percorsi ignorati vengono saltati.\n\n## 2) Percorsi sorgente specifici per provider e precedenza locale\n\n## Provider `native` (`builtin.ts`)\n\nLe radici di ricerca provengono dalle directory `.xcsh`:\n\n- progetto: `<cwd>/.xcsh/commands/*.md`\n- utente: `~/.xcsh/agent/commands/*.md`\n\n`getConfigDirs()` restituisce prima il progetto, poi l'utente, quindi **i comandi nativi del progetto hanno la precedenza sui comandi nativi dell'utente** in caso di collisioni di nomi.\n\n## Provider `claude` (`claude.ts`)\n\nCarica:\n\n- utente: `~/.claude/commands/*.md`\n- progetto: `<cwd>/.claude/commands/*.md`\n\nIl provider inserisce gli elementi dell'utente prima di quelli del progetto, quindi **i comandi Claude dell'utente hanno la precedenza sui comandi Claude del progetto** in caso di collisioni con lo stesso nome all'interno di questo provider.\n\n## Provider `codex` (`codex.ts`)\n\nCarica:\n\n- utente: `~/.codex/commands/*.md`\n- progetto: `<cwd>/.codex/commands/*.md`\n\nEntrambi i lati vengono caricati e quindi appiattiti in ordine utente-prima, pertanto **i comandi Codex dell'utente hanno la precedenza sui comandi Codex del progetto** in caso di collisioni.\n\nIl contenuto dei comandi Codex viene analizzato con rimozione del frontmatter (`parseFrontmatter`), e il nome del comando può essere sovrascritto dal `name` nel frontmatter; in caso contrario viene utilizzato il nome del file.\n\n## Provider `claude-plugins` (`claude-plugins.ts`)\n\nCarica le radici dei comandi dei plugin da `~/.claude/plugins/installed_plugins.json`, quindi esegue la scansione di `<pluginRoot>/commands/*.md`.\n\nL'ordinamento segue l'ordine di iterazione del registro e l'ordine degli elementi per plugin presenti in quel file JSON. Non è previsto alcun passaggio di ordinamento aggiuntivo.\n\n## 3) Materializzazione in `FileSlashCommand` a runtime\n\n`loadSlashCommands()` in `src/extensibility/slash-commands.ts` converte gli elementi delle capacità in oggetti `FileSlashCommand` utilizzati al momento della richiesta.\n\nPer ciascun comando:\n\n1. analisi del frontmatter/corpo (`parseFrontmatter`)\n2. sorgente della descrizione:\n   - `frontmatter.description` se presente\n   - altrimenti la prima riga non vuota del corpo (trimmed, max 60 caratteri con `...`)\n3. mantenimento del corpo analizzato come contenuto del template eseguibile\n4. calcolo di una stringa sorgente visualizzata come `via Claude Code Project`\n\nLa severità dell'analisi del frontmatter dipende dalla sorgente:\n\n- livello `native` -> gli errori di analisi sono `fatal`\n- livelli `user`/`project` -> gli errori di analisi sono `warn` con analisi di fallback\n\n### Comandi di fallback incorporati\n\nDopo i comandi da filesystem/provider, vengono aggiunti i template di comando incorporati (`EMBEDDED_COMMAND_TEMPLATES`) se i relativi nomi non sono già presenti.\n\nL'insieme incorporato attuale proviene da `src/task/commands.ts` e viene utilizzato come fallback (`source: \"bundled\"`).\n\n## 4) Modalità interattiva: provenienza degli elenchi di comandi\n\nLa modalità interattiva combina più sorgenti di comandi per il completamento automatico e il routing dei comandi.\n\nAl momento della costruzione, viene creato un elenco di comandi in attesa composto da:\n\n- built-in (`BUILTIN_SLASH_COMMANDS`, include il completamento degli argomenti e i suggerimenti inline per i comandi selezionati)\n- comandi slash registrati tramite estensione (`extensionRunner.getRegisteredCommands(...)`)\n- comandi personalizzati TypeScript (`session.customCommands`), mappati come etichette di comandi slash\n- comandi skill opzionali (`/skill:<name>`) quando `skills.enableSkillCommands` è abilitato\n\nPoi `init()` chiama `refreshSlashCommandState(...)` per caricare i comandi basati su file e installare un `CombinedAutocompleteProvider` contenente:\n\n- i comandi in attesa sopra indicati\n- i comandi basati su file individuati\n\n`refreshSlashCommandState(...)` aggiorna anche `session.setSlashCommands(...)` affinché l'espansione della richiesta utilizzi lo stesso insieme di comandi basati su file individuato.\n\n### Ciclo di vita dell'aggiornamento\n\nLo stato dei comandi slash viene aggiornato:\n\n- durante l'inizializzazione interattiva\n- dopo che `/move` cambia la directory di lavoro (`handleMoveCommand` chiama `resetCapabilities()` e poi `refreshSlashCommandState(newCwd)`)\n\nNon è presente alcun file watcher continuo per le directory dei comandi.\n\n### Altre modalità di esposizione\n\nIl pannello delle Estensioni carica anch'esso la capacità `slash-commands` e visualizza le voci dei comandi attivi/oscurati, inclusi i duplicati `_shadowed`.\n\n## 5) Posizionamento nella pipeline delle richieste\n\nOrdine di gestione slash di `AgentSession.prompt(...)` (quando `expandPromptTemplates !== false`):\n\n1. **Comandi delle estensioni** (`#tryExecuteExtensionCommand`)  \n   Se `/name` corrisponde a un comando registrato dall'estensione, il gestore viene eseguito immediatamente e la richiesta viene restituita.\n2. **Comandi personalizzati TypeScript** (`#tryExecuteCustomCommand`)  \n   Solo come limite: se corrisponde, viene eseguito e può restituire:\n   - `string` -> sostituisce il testo della richiesta con quella stringa\n   - `void/undefined` -> trattato come gestito; nessuna richiesta al modello linguistico\n3. **Comandi slash basati su file** (`expandSlashCommand`)  \n   Se il testo inizia ancora con `/`, viene tentata l'espansione del comando markdown.\n4. **Template di richiesta** (`expandPromptTemplate`)  \n   Applicati dopo l'elaborazione slash/personalizzata.\n5. **Consegna**\n   - idle: la richiesta viene inviata immediatamente all'agente\n   - in streaming: la richiesta viene accodata come steer/follow-up in base a `streamingBehavior`\n\nQuesto spiega perché l'espansione dei comandi slash precede l'espansione dei template di richiesta, e perché i comandi personalizzati possono trasformare la barra iniziale prima della corrispondenza con i comandi basati su file.\n\n## 6) Semantica dell'espansione per i comandi slash basati su file\n\nComportamento di `expandSlashCommand(text, fileCommands)`:\n\n- viene eseguito solo quando il testo inizia con `/`\n- analizza il nome del comando dal primo token dopo `/`\n- analizza gli argomenti dal testo rimanente tramite `parseCommandArgs`\n- cerca una corrispondenza esatta per nome nei `fileCommands` caricati\n- se corrisponde, applica:\n  - sostituzione posizionale: `$1`, `$2`, ...\n  - sostituzione aggregata: `$ARGUMENTS` e `$@`\n  - quindi rendering del template tramite `prompt.render` con `{ args, ARGUMENTS, arguments }`\n- se non c'è corrispondenza, restituisce il testo originale invariato\n\n### Avvertenze su `parseCommandArgs`\n\nIl parser è una suddivisione semplice con supporto alle virgolette:\n\n- supporta le virgolette `'singole'` e `\"doppie\"` per mantenere gli spazi\n- rimuove i delimitatori delle virgolette\n- non implementa regole di escape con backslash\n- una virgoletta non corrispondente non è un errore; il parser consuma fino alla fine\n\n## 7) Comportamento per input `/...` sconosciuti\n\nL'input slash sconosciuto **non viene rifiutato** dalla logica slash principale.\n\nSe il comando non viene gestito dai livelli estensione/personalizzato/file, `expandSlashCommand` restituisce il testo originale e la richiesta letterale `/...` prosegue attraverso la normale espansione dei template e la consegna al modello linguistico.\n\nLa modalità interattiva gestisce separatamente e in modo diretto molti built-in in `InputController` (ad esempio `/settings`, `/model`, `/mcp`, `/move`, `/exit`). Questi vengono consumati prima di `session.prompt(...)` e pertanto non raggiungono mai l'espansione dei comandi basati su file in quel percorso.\n\n## 8) Differenze tra streaming e idle\n\n## Percorso idle\n\n- `session.prompt(\"/x ...\")` esegue la pipeline dei comandi ed esegue immediatamente il comando oppure invia direttamente il testo espanso.\n\n## Percorso streaming (`session.isStreaming === true`)\n\n- `prompt(...)` esegue comunque prima le trasformazioni estensione/personalizzato/file/template\n- quindi richiede `streamingBehavior`:\n  - `\"steer\"` -> accoda un messaggio di interruzione (`agent.steer`)\n  - `\"followUp\"` -> accoda un messaggio post-turno (`agent.followUp`)\n- se `streamingBehavior` viene omesso, la richiesta genera un errore\n\n### Comportamento di streaming specifico per comando\n\n- I comandi delle estensioni vengono eseguiti immediatamente anche durante lo streaming (non vengono accodati come testo).\n- I metodi helper `steer(...)`/`followUp(...)` rifiutano i comandi delle estensioni (`#throwIfExtensionCommand`) per evitare di accodare testo di comandi per gestori che devono essere eseguiti in modo sincrono.\n- La riproduzione della coda di compattazione utilizza `isKnownSlashCommand(...)` per decidere se le voci accodate debbano essere riprodotte tramite `session.prompt(...)` (per i comandi slash noti) o tramite i metodi raw steer/follow-up.\n\n## 9) Gestione degli errori e superfici di fallimento\n\n- I fallimenti nel caricamento del provider sono isolati; il registro raccoglie i warning e continua con gli altri provider.\n- Gli elementi di comandi slash non validi (nome/percorso/contenuto mancante o livello non valido) vengono scartati dalla validazione delle capacità.\n- Fallimenti nell'analisi del frontmatter:\n  - comandi nativi: l'errore di analisi fatale viene propagato\n  - comandi non nativi: warning + analisi chiave/valore di fallback\n- Le eccezioni nei gestori dei comandi estensione/personalizzati vengono catturate e segnalate tramite il canale di errori dell'estensione (o il logger di fallback per i comandi personalizzati senza extension runner), e vengono trattate come gestite (nessuna esecuzione di fallback indesiderata).\n",
	"it/runtime-tools/task-agent-discovery.md": "---\ntitle: Scoperta e selezione degli agenti di task\ndescription: >-\n  Logica di scoperta e selezione degli agenti di task per instradare il lavoro\n  verso tipi di sottoagenti specializzati.\nsidebar:\n  order: 6\n  label: Scoperta degli agenti di task\ni18n:\n  sourceHash: 8cf42457c672\n  translator: machine\n---\n\n# Scoperta e selezione degli agenti di task\n\nQuesto documento descrive come il sottosistema di task individua le definizioni degli agenti, unisce più sorgenti e risolve un agente richiesto al momento dell'esecuzione.\n\nCopre il comportamento a runtime come implementato attualmente, inclusi la precedenza, la gestione delle definizioni non valide e i vincoli di spawn/profondità che possono rendere un agente effettivamente non disponibile.\n\n## File di implementazione\n\n- [`src/task/discovery.ts`](../../packages/coding-agent/src/task/discovery.ts)\n- [`src/task/agents.ts`](../../packages/coding-agent/src/task/agents.ts)\n- [`src/task/types.ts`](../../packages/coding-agent/src/task/types.ts)\n- [`src/task/index.ts`](../../packages/coding-agent/src/task/index.ts)\n- [`src/task/commands.ts`](../../packages/coding-agent/src/task/commands.ts)\n- [`src/prompts/agents/task.md`](../../packages/coding-agent/src/prompts/agents/task.md)\n- [`src/prompts/tools/task.md`](../../packages/coding-agent/src/prompts/tools/task.md)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/config.ts`](../../packages/coding-agent/src/config.ts)\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts)\n\n---\n\n## Struttura della definizione dell'agente\n\nGli agenti di task si normalizzano in `AgentDefinition` (`src/task/types.ts`):\n\n- `name`, `description`, `systemPrompt` (obbligatori per un agente caricato valido)\n- facoltativi: `tools`, `spawns`, `model`, `thinkingLevel`, `output`\n- `source`: `\"bundled\" | \"user\" | \"project\"`\n- facoltativo: `filePath`\n\nIl parsing proviene dal frontmatter tramite `parseAgentFields()` (`src/discovery/helpers.ts`):\n\n- `name` o `description` mancanti => non valido (`null`), il chiamante lo tratta come errore di parsing\n- `tools` accetta CSV o array; se fornito, `submit_result` viene aggiunto automaticamente\n- `spawns` accetta `*`, CSV o array\n- comportamento di compatibilità con versioni precedenti: se `spawns` è assente ma `tools` include `task`, `spawns` diventa `*`\n- `output` viene trasmesso come dato di schema opaco\n\n## Agenti integrati\n\nGli agenti integrati sono incorporati al momento della build (`src/task/agents.ts`) tramite import testuali.\n\n`EMBEDDED_AGENT_DEFS` definisce:\n\n- `explore`, `plan`, `designer`, `reviewer` dai file di prompt\n- `task` e `quick_task` dal corpo condiviso `task.md` con frontmatter iniettato\n\nPercorso di caricamento:\n\n1. `loadBundledAgents()` effettua il parsing del markdown incorporato con `parseAgent(..., \"bundled\", \"fatal\")`\n2. i risultati vengono memorizzati nella cache in memoria (`bundledAgentsCache`)\n3. `clearBundledAgentsCache()` è un reset della cache solo per i test\n\nPoiché il parsing integrato usa `level: \"fatal\"`, il frontmatter integrato malformato genera un'eccezione e può causare il fallimento della scoperta.\n\n## Scoperta tramite filesystem e plugin\n\n`discoverAgents(cwd, home)` (`src/task/discovery.ts`) unisce gli agenti da più posizioni prima di aggiungere le definizioni integrate.\n\n### Input di scoperta\n\n1. Directory degli agenti dalla configurazione utente tramite `getConfigDirs(\"agents\", { project: false })`\n2. Directory di configurazione del progetto più vicine tramite `findAllNearestProjectConfigDirs(\"agents\", cwd)`\n3. Root dei plugin Claude (`listClaudePluginRoots(home)`) con sottodirectory `agents/`\n4. Agenti integrati (`loadBundledAgents()`)\n\n### Ordine effettivo delle sorgenti\n\nL'ordine delle famiglie di sorgenti proviene da `getConfigDirs(\"\", { project: false })`, derivato da `priorityList` in `src/config.ts`:\n\n1. `.xcsh`\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nPer ogni famiglia di sorgenti, l'ordine di scoperta è:\n\n1. directory del progetto più vicina per quella sorgente (se trovata)\n2. directory utente per quella sorgente\n\nDopo tutte le directory delle famiglie di sorgenti, le directory `agents/` dei plugin vengono aggiunte in fondo (prima i plugin con scope progetto, poi quelli con scope utente).\n\nGli agenti integrati vengono aggiunti per ultimi.\n\n### Avvertenza importante: commenti obsoleti vs codice attuale\n\nI commenti nell'intestazione di `discovery.ts` menzionano ancora `.pi` e non menzionano `.codex`/`.gemini`. L'ordine effettivo a runtime è determinato da `src/config.ts` e attualmente usa `.xcsh`, `.claude`, `.codex`, `.gemini`.\n\n## Regole di unione e collisione\n\nLa scoperta utilizza la deduplicazione \"primo-vince\" per `agent.name` esatto:\n\n- Un `Set<string>` tiene traccia dei nomi già visti.\n- Gli agenti caricati vengono appiattiti nell'ordine delle directory e mantenuti solo se il nome non è già stato visto.\n- Gli agenti integrati vengono filtrati rispetto allo stesso insieme e aggiunti solo se ancora non presenti.\n\nImplicazioni:\n\n- Il progetto sovrascrive l'utente per la stessa famiglia di sorgenti.\n- La famiglia di sorgenti con priorità più alta sovrascrive quella con priorità più bassa (`.xcsh` prima di `.claude`, ecc.).\n- Gli agenti non integrati sovrascrivono gli agenti integrati con lo stesso nome.\n- La corrispondenza dei nomi è sensibile alle maiuscole (`Task` e `task` sono distinti).\n- All'interno di una directory, i file markdown vengono letti in ordine lessicografico del nome file prima della deduplicazione.\n\n## Comportamento in caso di file agente non valido o mancante\n\nPer directory (`loadAgentsFromDir`):\n\n- directory illeggibile/mancante: trattata come vuota (`readdir(...).catch(() => [])`)\n- errore di lettura o parsing del file: viene registrato un avviso, il file viene saltato\n- il percorso di parsing usa `parseAgent(..., level: \"warn\")`\n\nIl comportamento in caso di errore del frontmatter proviene da `parseFrontmatter`:\n\n- un errore di parsing al livello `warn` registra un avviso\n- il parser torna a un semplice parser di righe `key: value`\n- se i campi obbligatori sono ancora mancanti, `parseAgentFields` fallisce, quindi `AgentParsingError` viene generato e catturato dal chiamante (il file viene saltato)\n\nEffetto netto: un file agente personalizzato non valido non interrompe la scoperta degli altri file.\n\n## Ricerca e selezione dell'agente\n\nLa ricerca è una ricerca lineare per nome esatto:\n\n- `getAgent(agents, name)` => `agents.find(a => a.name === name)`\n\nNell'esecuzione del task (`TaskTool.execute`):\n\n1. gli agenti vengono riscoperti al momento della chiamata (`discoverAgents(this.session.cwd)`)\n2. il `params.agent` richiesto viene risolto tramite `getAgent`\n3. un agente mancante restituisce una risposta immediata dello strumento:\n   - `Unknown agent \"...\". Available: ...`\n   - nessun sottoprocesso viene avviato\n\n### Descrizione vs scoperta al momento dell'esecuzione\n\n`TaskTool.create()` costruisce la descrizione dello strumento dai risultati della scoperta al momento dell'inizializzazione (`buildDescription`).\n\n`execute()` riscopre nuovamente gli agenti. Pertanto, l'insieme a runtime può differire da quello elencato nella descrizione dello strumento precedente se i file degli agenti sono cambiati durante la sessione.\n\n## Guardrail dell'output strutturato e precedenza dello schema\n\nPrecedenza dello schema di output a runtime in `TaskTool.execute`:\n\n1. `output` del frontmatter dell'agente\n2. `params.schema` della chiamata al task\n3. `outputSchema` della sessione padre\n\n(`effectiveOutputSchema = effectiveAgent.output ?? outputSchema ?? this.session.outputSchema`)\n\nIl testo di guardrail nel prompt in `src/prompts/tools/task.md` avverte del comportamento in caso di mancata corrispondenza per gli agenti con output strutturato (`explore`, `reviewer`): le istruzioni sul formato dell'output in prosa possono entrare in conflitto con lo schema integrato e produrre output `null`.\n\nQuesta è una guida, non una logica di validazione rigida a runtime in `discoverAgents`.\n\n## Interazione con la scoperta dei comandi\n\n`src/task/commands.ts` è un'infrastruttura parallela per i comandi del flusso di lavoro (non le definizioni degli agenti), ma segue lo stesso schema generale:\n\n- scoperta prima dai provider di capacità\n- deduplicazione per nome con primo-vince\n- aggiunta dei comandi integrati se ancora non presenti\n- ricerca per nome esatto tramite `getCommand`\n\nIn `src/task/index.ts`, i helper dei comandi vengono riesportati insieme agli helper della scoperta degli agenti. La scoperta degli agenti non dipende dalla scoperta dei comandi a runtime.\n\n## Vincoli di disponibilità oltre la scoperta\n\nUn agente può essere individuabile ma comunque non disponibile per l'esecuzione a causa dei guardrail di esecuzione.\n\n### Policy di spawn del padre\n\n`TaskTool.execute` controlla `session.getSessionSpawns()`:\n\n- `\"*\"` => consenti qualsiasi\n- `\"\"` => nega tutto\n- lista CSV => consenti solo i nomi elencati\n\nSe negato: risposta immediata `Cannot spawn '...'. Allowed: ...`.\n\n### Guardia ambientale contro la ricorsione su se stesso\n\n`PI_BLOCKED_AGENT` viene letto alla costruzione dello strumento. Se la richiesta corrisponde, l'esecuzione viene rifiutata con un messaggio di prevenzione della ricorsione.\n\n### Limitazione della profondità di ricorsione (disponibilità dello strumento task nelle sessioni figlio)\n\nIn `runSubprocess` (`src/task/executor.ts`):\n\n- la profondità viene calcolata da `taskDepth`\n- `task.maxRecursionDepth` controlla il limite\n- quando alla profondità massima:\n  - lo strumento `task` viene rimosso dall'elenco degli strumenti del figlio\n  - l'env `spawns` del figlio viene impostato su vuoto\n\nPertanto, i livelli più profondi non possono generare ulteriori task anche se la definizione dell'agente include `spawns`.\n\n## Avvertenza sulla modalità piano (implementazione attuale)\n\n`TaskTool.execute` calcola un `effectiveAgent` per la modalità piano (antepone il prompt della modalità piano, forza un sottoinsieme di strumenti in sola lettura, azzera gli spawn), ma `runSubprocess` viene chiamato con `agent` anziché `effectiveAgent`.\n\nEffetto attuale:\n\n- l'override del modello / il livello di pensiero / lo schema di output sono derivati da `effectiveAgent`\n- il prompt di sistema e le restrizioni su strumenti/spawn di `effectiveAgent` non vengono trasmessi in questo percorso di chiamata\n\nQuesta è un'avvertenza implementativa da tenere a mente quando si analizzano le aspettative sul comportamento della modalità piano.\n",
	"it/sessions/compaction.md": "---\ntitle: Compattazione e riepiloghi dei rami\ndescription: >-\n  Compattazione della finestra di contesto e generazione di riepiloghi dei rami\n  per sessioni di lunga durata.\nsidebar:\n  order: 5\n  label: Compattazione\ni18n:\n  sourceHash: dae425a900d8\n  translator: machine\n---\n\n# Compattazione e riepiloghi dei rami\n\nLa compattazione e i riepiloghi dei rami sono i due meccanismi che mantengono utilizzabili le sessioni lunghe senza perdere il contesto del lavoro precedente.\n\n- La **compattazione** riscrive la cronologia precedente in un riepilogo sul ramo corrente.\n- Il **riepilogo del ramo** cattura il contesto dei rami abbandonati durante la navigazione con `/tree`.\n\nEntrambi vengono persistiti come voci di sessione e riconvertiti in messaggi di contesto utente durante la ricostruzione dell'input per l'LLM.\n\n## File di implementazione principali\n\n- `src/session/compaction/compaction.ts`\n- `src/session/compaction/branch-summarization.ts`\n- `src/session/compaction/pruning.ts`\n- `src/session/compaction/utils.ts`\n- `src/session/session-manager.ts`\n- `src/session/agent-session.ts`\n- `src/session/messages.ts`\n- `src/extensibility/hooks/types.ts`\n- `src/config/settings-schema.ts`\n\n## Modello delle voci di sessione\n\nLa compattazione e i riepiloghi dei rami sono voci di sessione di prima classe, non semplici messaggi assistant/user.\n\n- `CompactionEntry`\n  - `type: \"compaction\"`\n  - `summary`, opzionale `shortSummary`\n  - `firstKeptEntryId` (confine di compattazione)\n  - `tokensBefore`\n  - opzionali `details`, `preserveData`, `fromExtension`\n- `BranchSummaryEntry`\n  - `type: \"branch_summary\"`\n  - `fromId`, `summary`\n  - opzionali `details`, `fromExtension`\n\nQuando il contesto viene ricostruito (`buildSessionContext`):\n\n1. L'ultima compattazione sul percorso attivo viene convertita in un messaggio `compactionSummary`.\n2. Le voci mantenute da `firstKeptEntryId` al punto di compattazione vengono re-incluse.\n3. Le voci successive sul percorso vengono aggiunte in coda.\n4. Le voci `branch_summary` vengono convertite in messaggi `branchSummary`.\n5. Le voci `custom_message` vengono convertite in messaggi `custom`.\n\nQuei ruoli personalizzati vengono poi trasformati in messaggi utente destinati all'LLM in `convertToLlm()` utilizzando i template statici:\n\n- `prompts/compaction/compaction-summary-context.md`\n- `prompts/compaction/branch-summary-context.md`\n\n## Pipeline di compattazione\n\n### Attivazioni\n\nLa compattazione può essere eseguita in tre modi:\n\n1. **Manuale**: `/compact [istruzioni]` chiama `AgentSession.compact(...)`.\n2. **Recupero automatico da overflow**: dopo un errore dell'assistente che corrisponde a un overflow del contesto.\n3. **Compattazione automatica a soglia**: dopo un turno riuscito quando il contesto supera la soglia.\n\n### Forma della compattazione (visuale)\n\n```text\nBefore compaction:\n\n  entry:  0     1     2     3      4     5     6      7      8     9\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┘\n                └────────┬───────┘ └──────────────┬──────────────┘\n               messagesToSummarize            kept messages\n                                   ↑\n                          firstKeptEntryId (entry 4)\n\nAfter compaction (new entry appended):\n\n  entry:  0     1     2     3      4     5     6      7      8     9      10\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┬─────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │ cmp │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┴─────┘\n               └──────────┬──────┘ └──────────────────────┬───────────────────┘\n                 not sent to LLM                    sent to LLM\n                                                         ↑\n                                              starts from firstKeptEntryId\n\nWhat the LLM sees:\n\n  ┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐\n  │ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │\n  └────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘\n       ↑         ↑      └─────────────────┬────────────────┘\n    prompt   from cmp          messages from firstKeptEntryId\n```\n\n### Compattazione da overflow-retry vs compattazione a soglia\n\nI due percorsi automatici sono intenzionalmente diversi:\n\n- **Compattazione da overflow-retry**\n  - Attivazione: l'errore dell'assistente del modello corrente viene rilevato come overflow del contesto.\n  - Il messaggio di errore dell'assistente fallito viene rimosso dallo stato attivo dell'agente prima del retry.\n  - La compattazione automatica viene eseguita con `reason: \"overflow\"` e `willRetry: true`.\n  - In caso di successo, l'agente prosegue automaticamente (`agent.continue()`) dopo la compattazione.\n\n- **Compattazione a soglia**\n  - Attivazione: `contextTokens > contextWindow - compaction.reserveTokens`.\n  - Viene eseguita con `reason: \"threshold\"` e `willRetry: false`.\n  - In caso di successo, se `compaction.autoContinue !== false`, inietta un prompt sintetico:\n    - `\"Continue if you have next steps.\"`\n\n### Potatura pre-compattazione\n\nPrima dei controlli di compattazione, può essere eseguita la potatura dei risultati degli strumenti (`pruneToolOutputs`).\n\nPolitica di potatura predefinita:\n\n- Proteggere i più recenti `40_000` token di output degli strumenti.\n- Richiedere almeno `20_000` token di risparmio stimato totale.\n- Non potare mai i risultati degli strumenti da `skill` o `read`.\n\nI risultati degli strumenti potati vengono sostituiti con:\n\n- `[Output truncated - N tokens]`\n\nSe la potatura modifica le voci, lo storage della sessione viene riscritto e lo stato dei messaggi dell'agente viene aggiornato prima delle decisioni di compattazione.\n\n### Logica del confine e del punto di taglio\n\n`prepareCompaction()` considera solo le voci successive all'ultima voce di compattazione (se presente).\n\n1. Trova l'indice della compattazione precedente.\n2. Calcola `boundaryStart = prevCompactionIndex + 1`.\n3. Adatta `keepRecentTokens` usando il rapporto di utilizzo misurato quando disponibile.\n4. Esegue `findCutPoint()` sulla finestra di confine.\n\nI punti di taglio validi includono:\n\n- voci messaggio con ruoli: `user`, `assistant`, `bashExecution`, `hookMessage`, `branchSummary`, `compactionSummary`\n- voci `custom_message`\n- voci `branch_summary`\n\nRegola ferrea: non tagliare mai su `toolResult`.\n\nSe ci sono voci di metadati non-messaggio immediatamente prima del punto di taglio (`model_change`, `thinking_level_change`, etichette, ecc.), queste vengono spostate nella regione mantenuta arretrando l'indice di taglio fino a raggiungere un messaggio o un confine di compattazione.\n\n### Gestione dei turni divisi\n\nSe il punto di taglio non si trova all'inizio di un turno utente, la compattazione lo tratta come un turno diviso.\n\nIl rilevamento dell'inizio del turno considera questi come confini di turno utente:\n\n- `message.role === \"user\"`\n- `message.role === \"bashExecution\"`\n- voce `custom_message`\n- voce `branch_summary`\n\nLa compattazione di un turno diviso genera due riepiloghi:\n\n1. Riepilogo della cronologia (`messagesToSummarize`)\n2. Riepilogo del prefisso del turno (`turnPrefixMessages`)\n\nIl riepilogo finale memorizzato viene unito come:\n\n```markdown\n<history summary>\n\n---\n\n**Turn Context (split turn):**\n\n<turn prefix summary>\n```\n\n### Generazione del riepilogo\n\n`compact(...)` costruisce i riepiloghi dal testo serializzato della conversazione:\n\n1. Converte i messaggi tramite `convertToLlm()`.\n2. Serializza con `serializeConversation()`.\n3. Racchiude in `<conversation>...</conversation>`.\n4. Include opzionalmente `<previous-summary>...</previous-summary>`.\n5. Inietta opzionalmente il contesto dell'hook come lista `<additional-context>`.\n6. Esegue il prompt di riepilogazione con `SUMMARIZATION_SYSTEM_PROMPT`.\n\nSelezione del prompt:\n\n- prima compattazione: `compaction-summary.md`\n- compattazione iterativa con riepilogo precedente: `compaction-update-summary.md`\n- secondo passaggio del turno diviso: `compaction-turn-prefix.md`\n- riepilogo breve per l'interfaccia: `compaction-short-summary.md`\n\nModalità di riepilogazione remota:\n\n- Se `compaction.remoteEndpoint` è impostato, la compattazione invia una richiesta POST con:\n  - `{ systemPrompt, prompt }`\n- Si aspetta un JSON contenente almeno `{ summary }`.\n\n### Contesto delle operazioni sui file nei riepiloghi\n\nLa compattazione traccia l'attività cumulativa sui file utilizzando le chiamate agli strumenti dell'assistente:\n\n- `read(path)` → insieme dei file letti\n- `write(path)` → insieme dei file modificati\n- `edit(path)` → insieme dei file modificati\n\nComportamento cumulativo:\n\n- Include i dettagli della compattazione precedente solo quando la voce precedente è generata internamente (`fromExtension !== true`).\n- Nei turni divisi, include anche le operazioni sui file del prefisso del turno.\n- `readFiles` esclude i file che sono stati anche modificati.\n\nAl testo del riepilogo vengono aggiunti i tag delle operazioni sui file tramite il template del prompt:\n\n```xml\n<read-files>\n...\n</read-files>\n<modified-files>\n...\n</modified-files>\n```\n\n### Persistenza e ricaricamento\n\nDopo la generazione del riepilogo (o il riepilogo fornito dall'hook), la sessione dell'agente:\n\n1. Aggiunge una `CompactionEntry` con `appendCompaction(...)`.\n2. Ricostruisce il contesto tramite `buildSessionContext()`.\n3. Sostituisce i messaggi live dell'agente con il contesto ricostruito.\n4. Emette l'evento hook `session_compact`.\n\n## Pipeline di riepilogazione dei rami\n\nLa riepilogazione dei rami è legata alla navigazione dell'albero, non all'overflow dei token.\n\n### Attivazione\n\nDurante `navigateTree(...)`:\n\n1. Calcola le voci abbandonate dalla vecchia foglia all'antenato comune usando `collectEntriesForBranchSummary(...)`.\n2. Se il chiamante ha richiesto il riepilogo (`options.summarize`), genera il riepilogo prima di cambiare foglia.\n3. Se il riepilogo esiste, lo allega al target della navigazione usando `branchWithSummary(...)`.\n\nOperativamente questo è comunemente guidato dal flusso `/tree` quando `branchSummary.enabled` è abilitato.\n\n### Forma del cambio di ramo (visuale)\n\n```text\nTree before navigation:\n\n         ┌─ B ─ C ─ D (old leaf, being abandoned)\n    A ───┤\n         └─ E ─ F (target)\n\nCommon ancestor: A\nEntries to summarize: B, C, D\n\nAfter navigation with summary:\n\n         ┌─ B ─ C ─ D ─ [summary of B,C,D]\n    A ───┤\n         └─ E ─ F (new leaf)\n```\n\n### Preparazione e budget di token\n\n`generateBranchSummary(...)` calcola il budget come:\n\n- `tokenBudget = model.contextWindow - branchSummary.reserveTokens`\n\n`prepareBranchEntries(...)` quindi:\n\n1. Primo passaggio: raccoglie le operazioni sui file cumulative da tutte le voci riepilogate, inclusi i dettagli dei `branch_summary` precedenti generati internamente.\n2. Secondo passaggio: percorre dalla più recente alla più vecchia, aggiungendo messaggi fino al raggiungimento del budget di token.\n3. Preferisce preservare il contesto recente.\n4. Può comunque includere voci di riepilogo di grandi dimensioni vicine al limite del budget per continuità.\n\nLe voci di compattazione vengono incluse come messaggi (`compactionSummary`) durante l'input della riepilogazione dei rami.\n\n### Generazione del riepilogo e persistenza\n\nLa riepilogazione dei rami:\n\n1. Converte e serializza i messaggi selezionati.\n2. Racchiude in `<conversation>`.\n3. Utilizza istruzioni personalizzate se fornite, altrimenti `branch-summary.md`.\n4. Chiama il modello di riepilogazione con `SUMMARIZATION_SYSTEM_PROMPT`.\n5. Antepone `branch-summary-preamble.md`.\n6. Aggiunge i tag delle operazioni sui file.\n\nIl risultato viene memorizzato come `BranchSummaryEntry` con dettagli opzionali (`readFiles`, `modifiedFiles`).\n\n## Punti di contatto per estensioni e hook\n\n### `session_before_compact`\n\nHook pre-compattazione.\n\nPuò:\n\n- annullare la compattazione (`{ cancel: true }`)\n- fornire un payload di compattazione personalizzato completo (`{ compaction: CompactionResult }`)\n\n### `session.compacting`\n\nHook di personalizzazione del prompt/contesto per la compattazione predefinita.\n\nPuò restituire:\n\n- `prompt` (sovrascrive il prompt di riepilogo base)\n- `context` (righe di contesto aggiuntive iniettate in `<additional-context>`)\n- `preserveData` (memorizzato nella voce di compattazione)\n\n### `session_compact`\n\nNotifica post-compattazione con la `compactionEntry` salvata e il flag `fromExtension`.\n\n### `session_before_tree`\n\nViene eseguito durante la navigazione dell'albero prima della generazione predefinita del riepilogo del ramo.\n\nPuò:\n\n- annullare la navigazione\n- fornire un `{ summary: { summary, details } }` personalizzato utilizzato quando l'utente ha richiesto la riepilogazione\n\n### `session_tree`\n\nEvento post-navigazione che espone la foglia nuova/vecchia e la voce di riepilogo opzionale.\n\n## Comportamento a runtime e semantica dei fallimenti\n\n- La compattazione manuale interrompe prima l'operazione corrente dell'agente.\n- `abortCompaction()` annulla i controller sia della compattazione manuale che automatica.\n- La compattazione automatica emette eventi di sessione start/end per aggiornamenti dell'interfaccia/stato.\n- La compattazione automatica può provare più modelli candidati e riprovare in caso di fallimenti transitori.\n- Gli errori di overflow sono esclusi dal percorso di retry generico perché vengono gestiti dalla compattazione.\n- Se la compattazione automatica fallisce:\n  - il percorso overflow emette `Context overflow recovery failed: ...`\n  - il percorso a soglia emette `Auto-compaction failed: ...`\n- La riepilogazione dei rami può essere annullata tramite segnale di abort (ad es. Escape), restituendo un risultato di navigazione cancellata/interrotta.\n\n## Impostazioni e valori predefiniti\n\nDa `settings-schema.ts`:\n\n- `compaction.enabled` = `true`\n- `compaction.reserveTokens` = `16384`\n- `compaction.keepRecentTokens` = `20000`\n- `compaction.autoContinue` = `true`\n- `compaction.remoteEndpoint` = `undefined`\n- `branchSummary.enabled` = `false`\n- `branchSummary.reserveTokens` = `16384`\n\nQuesti valori vengono consumati a runtime da `AgentSession` e dai moduli di compattazione/riepilogazione dei rami.\n",
	"it/sessions/handoff-generation-pipeline.md": "---\ntitle: Pipeline di generazione handoff\ndescription: >-\n  Pipeline di generazione handoff per la creazione di riepiloghi di sessione\n  portabili per la collaborazione in team.\nsidebar:\n  order: 8\n  label: Pipeline handoff\ni18n:\n  sourceHash: 03666084b5ac\n  translator: machine\n---\n\n# Pipeline di generazione `/handoff`\n\nQuesto documento descrive come l'agente di codifica implementa `/handoff` oggi: percorso di attivazione, prompt di generazione, acquisizione del completamento, cambio di sessione e reiniezione del contesto.\n\n## Ambito\n\nCopre:\n\n- Invio del comando interattivo `/handoff`\n- Ciclo di vita e transizioni di stato di `AgentSession.handoff()`\n- Come l'output dell'handoff viene acquisito dall'output dell'assistente\n- Come le sessioni vecchie/nuove persistono i dati di handoff in modo diverso\n- Comportamento dell'interfaccia utente per successo, annullamento e fallimento\n\nNon copre:\n\n- Navigazione generica dell'albero/internals dei rami\n- Comandi di sessione non correlati all'handoff (`/new`, `/fork`, `/resume`)\n\n## File di implementazione\n\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n\n## Percorso di attivazione\n\n1. `/handoff` è dichiarato nei metadati dei comandi slash integrati (`slash-commands.ts`) con un suggerimento inline opzionale: `[focus instructions]`.\n2. Nella gestione dell'input interattivo (`InputController`), il testo inviato corrispondente a `/handoff` o `/handoff ...` viene intercettato prima della normale invio del prompt.\n3. L'editor viene svuotato e viene chiamato `handleHandoffCommand(customInstructions?)`.\n4. `CommandController.handleHandoffCommand` esegue un controllo preliminare utilizzando le voci correnti:\n   - Conta le voci `type === \"message\"`.\n   - Se `< 2`, avvisa: `Nothing to hand off (no messages yet)` e ritorna.\n\nLo stesso controllo sul contenuto minimo esiste anche all'interno di `AgentSession.handoff()` e genera un errore se violato. Questo duplica la sicurezza sia a livello di interfaccia utente che a livello di sessione.\n\n## Ciclo di vita end-to-end\n\n### 1) Avvio della generazione dell'handoff\n\n`AgentSession.handoff(customInstructions?)`:\n\n- Legge le voci del ramo corrente (`sessionManager.getBranch()`)\n- Valida il numero minimo di messaggi (`>= 2`)\n- Crea `#handoffAbortController`\n- Costruisce un prompt fisso e inline che richiede un documento di handoff strutturato (`Goal`, `Constraints & Preferences`, `Progress`, `Key Decisions`, `Critical Context`, `Next Steps`)\n- Aggiunge `Additional focus: ...` se vengono fornite istruzioni personalizzate\n\nIl prompt viene inviato tramite:\n\n```ts\nawait this.prompt(handoffPrompt, { expandPromptTemplates: false });\n```\n\n`expandPromptTemplates: false` impedisce l'espansione di slash/template di prompt su questo payload di istruzioni interne.\n\n### 2) Acquisizione del completamento\n\nPrima di inviare il prompt, `handoff()` si iscrive agli eventi di sessione e attende `agent_end`.\n\nAll'evento `agent_end`, estrae il testo dell'handoff dallo stato dell'agente scansionando all'indietro per trovare il messaggio `assistant` più recente, quindi concatena tutti i blocchi `content` in cui `type === \"text\"` con `\\n`.\n\nAssunzioni importanti sull'estrazione:\n\n- Vengono utilizzati solo i blocchi di testo; il contenuto non testuale viene ignorato.\n- Si presuppone che l'ultimo messaggio dell'assistente corrisponda alla generazione dell'handoff.\n- Non analizza le sezioni markdown né convalida la conformità al formato.\n- Se l'output dell'assistente non contiene blocchi di testo, l'handoff viene considerato mancante.\n\n### 3) Controlli di annullamento\n\n`handoff()` restituisce `undefined` quando si verifica una delle seguenti condizioni:\n\n- nessun testo di handoff acquisito, oppure\n- `#handoffAbortController.signal.aborted` è true\n\nIn ogni caso, il metodo svuota `#handoffAbortController` nel blocco `finally`.\n\n### 4) Creazione di una nuova sessione\n\nSe il testo è stato acquisito e non è stato interrotto:\n\n1. Svuota il writer della sessione corrente (`sessionManager.flush()`)\n2. Avvia una sessione completamente nuova (`sessionManager.newSession()`)\n3. Reimposta lo stato dell'agente in memoria (`agent.reset()`)\n4. Riassocia `agent.sessionId` al nuovo ID di sessione\n5. Svuota gli array di contesto in coda (`#steeringMessages`, `#followUpMessages`, `#pendingNextTurnMessages`)\n6. Reimposta il contatore del promemoria todo\n\n`newSession()` crea un nuovo header e un elenco di voci vuoto (il leaf viene reimpostato a `null`). Nel percorso di handoff, non viene passato alcun `parentSession`.\n\n### 5) Iniezione del contesto di handoff\n\nIl documento di handoff generato viene racchiuso e aggiunto alla nuova sessione come voce `custom_message`:\n\n```text\n<handoff-context>\n...handoff text...\n</handoff-context>\n\nThe above is a handoff document from a previous session. Use this context to continue the work seamlessly.\n```\n\nChiamata di inserimento:\n\n```ts\nthis.sessionManager.appendCustomMessageEntry(\"handoff\", handoffContent, true);\n```\n\nSemantica:\n\n- `customType`: `\"handoff\"`\n- `display`: `true` (visibile nella ricostruzione TUI)\n- Tipo di voce: `custom_message` (partecipa al contesto LLM)\n\n### 6) Ricostruzione del contesto attivo dell'agente\n\nDopo l'iniezione:\n\n1. `sessionManager.buildSessionContext()` risolve l'elenco dei messaggi per il leaf corrente\n2. `agent.replaceMessages(sessionContext.messages)` rende il messaggio di handoff iniettato il contesto attivo\n3. Il metodo restituisce `{ document: handoffText }`\n\nA questo punto, il contesto LLM attivo nella nuova sessione contiene il messaggio di handoff iniettato, non la trascrizione precedente.\n\n## Modello di persistenza: sessione vecchia vs sessione nuova\n\n### Sessione vecchia\n\nDurante la generazione, la persistenza normale dei messaggi rimane attiva. La risposta di handoff dell'assistente viene persistita come voce `message` regolare all'evento `message_end`.\n\nRisultato: la sessione originale contiene l'handoff generato visibile come parte della trascrizione storica.\n\n### Sessione nuova\n\nDopo il reset della sessione, l'handoff viene persistito come `custom_message` con `customType: \"handoff\"`.\n\n`buildSessionContext()` converte questa voce in un messaggio di contesto personalizzato/utente a runtime tramite `createCustomMessage(...)`, in modo che venga incluso nei prompt futuri della nuova sessione.\n\n## Comportamento del controller/interfaccia utente\n\nComportamento di `CommandController.handleHandoffCommand`:\n\n- Chiama `await session.handoff(customInstructions)`\n- Se il risultato è `undefined`: `showError(\"Handoff cancelled\")`\n- In caso di successo:\n  - `rebuildChatFromMessages()` (carica il contesto della nuova sessione, incluso l'handoff iniettato)\n  - invalida la barra di stato e il bordo superiore dell'editor\n  - ricarica i todo\n  - aggiunge una riga di chat di successo: `New session started with handoff context`\n- In caso di eccezione:\n  - se il messaggio è `\"Handoff cancelled\"` o il nome dell'errore è `AbortError`: `showError(\"Handoff cancelled\")`\n  - altrimenti: `showError(\"Handoff failed: <message>\")`\n- Richiede il rendering al termine\n\n## Semantica di annullamento (comportamento attuale)\n\n### Primitiva di annullamento a livello di sessione\n\n`AgentSession` espone:\n\n- `abortHandoff()` → interrompe `#handoffAbortController`\n- `isGeneratingHandoff` → true mentre il controller esiste\n\nQuando viene utilizzato questo percorso di interruzione, il sottoscrittore dell'handoff rifiuta con `Error(\"Handoff cancelled\")` e il controller dei comandi lo mappa all'interfaccia utente di annullamento.\n\n### Limitazione del percorso `/handoff` interattivo\n\nNell'attuale cablaggio del controller interattivo, `/handoff` non installa un gestore Escape dedicato che chiami `abortHandoff()` (a differenza dei percorsi di compattazione/riepilogo-ramo che sovrascrivono temporaneamente `editor.onEscape`).\n\nImpatto pratico:\n\n- Esiste il supporto all'annullamento a livello di sessione, ma nessun hook di keybinding specifico per l'handoff nel percorso del comando `/handoff`.\n- L'interruzione dell'utente può comunque avvenire tramite percorsi di interruzione più ampi dell'agente, ma non si tratta dello stesso canale di annullamento esplicito utilizzato da `abortHandoff()`.\n\n## Handoff interrotto vs handoff fallito\n\nClassificazione attuale dell'interfaccia utente:\n\n- **Interrotto/annullato**\n  - Il percorso `abortHandoff()` genera `\"Handoff cancelled\"`, oppure\n  - viene generato un `AbortError`\n  - L'interfaccia utente mostra `Handoff cancelled`\n\n- **Fallito**\n  - qualsiasi altro errore generato da `handoff()` / dalla pipeline dei prompt (errori di validazione del modello/API, eccezioni di runtime, ecc.)\n  - L'interfaccia utente mostra `Handoff failed: ...`\n\nSfumatura aggiuntiva: se la generazione viene completata ma non viene estratto alcun testo, `handoff()` restituisce `undefined` e il controller attualmente segnala **annullato**, non **fallito**.\n\n## Salvaguardie per sessioni brevi e contenuto minimo\n\nDue controlli prevengono handoff a basso segnale:\n\n- Livello interfaccia utente (`handleHandoffCommand`): avvisa e ritorna anticipatamente per voci di messaggio `< 2`\n- Livello sessione (`handoff()`): genera la stessa condizione come errore\n\nQuesto evita la creazione di una nuova sessione con contesto di handoff vuoto o quasi vuoto.\n\n## Riepilogo delle transizioni di stato\n\nFlusso di stato ad alto livello:\n\n1. Comando slash interattivo intercettato\n2. Controllo preliminare sul numero di messaggi\n3. `#handoffAbortController` creato (`isGeneratingHandoff = true`)\n4. Prompt di handoff interno inviato (visibile nella chat come normale generazione dell'assistente)\n5. All'evento `agent_end`, viene estratto l'ultimo testo dell'assistente\n6. Se mancante/interrotto → restituisce `undefined` o percorso di errore di annullamento\n7. Se presente:\n   - svuota la sessione vecchia\n   - crea una nuova sessione vuota\n   - reimposta le code/i contatori di runtime\n   - aggiunge `custom_message(handoff)`\n   - ricostruisce e sostituisce i messaggi attivi dell'agente\n8. Il controller ricostruisce l'interfaccia utente della chat e annuncia il successo\n9. `#handoffAbortController` svuotato (`isGeneratingHandoff = false`)\n\n## Assunzioni e limitazioni note\n\n- L'estrazione dell'handoff è euristica: \"ultimi blocchi di testo dell'assistente\"; nessuna validazione strutturale.\n- Nessun controllo rigido che il markdown generato segua il formato delle sezioni richiesto.\n- Il testo estratto mancante viene segnalato come annullamento nell'esperienza utente del controller.\n- Il flusso interattivo di `/handoff` attualmente non dispone di un binding Escape→`abortHandoff()` dedicato.\n- I metadati di lignaggio della nuova sessione (`parentSession`) non vengono impostati da questo percorso.\n",
	"it/sessions/memory.md": "---\ntitle: Memoria Autonoma\ndescription: >-\n  Sistema di memoria autonoma per la persistenza delle preferenze utente, del\n  contesto di progetto e del feedback tra sessioni.\nsidebar:\n  order: 7\n  label: Memoria autonoma\ni18n:\n  sourceHash: 2aa9f516aa1e\n  translator: machine\n---\n\n# Memoria Autonoma\n\nQuando abilitato, l'agente estrae automaticamente la conoscenza duratura dalle sessioni passate e inietta un riepilogo compatto in ogni nuova sessione. Nel tempo costruisce un archivio di memoria con ambito di progetto — decisioni tecniche, workflow ricorrenti, insidie — che viene mantenuto senza sforzo manuale.\n\nDisabilitato per impostazione predefinita. Si abilita tramite `/settings` o `config.yml`:\n\n```yaml\nmemories:\n  enabled: true\n```\n\n## Utilizzo\n\n### Cosa viene iniettato\n\nAll'avvio della sessione, se esiste un riepilogo di memoria per il progetto corrente, viene iniettato nel system prompt come blocco **Memory Guidance**. L'agente è istruito a:\n\n- Trattare la memoria come contesto euristico — utile per processi e decisioni precedenti, non autorevole sullo stato attuale del repository.\n- Citare il percorso dell'artefatto di memoria quando la memoria modifica il piano, e abbinarlo a evidenze dal repository corrente prima di agire.\n- Preferire lo stato del repository e le istruzioni dell'utente quando sono in conflitto con la memoria; trattare la memoria in conflitto come obsoleta.\n\n### Lettura degli artefatti di memoria\n\nL'agente può leggere i file di memoria direttamente utilizzando URL `memory://` con lo strumento `read`:\n\n| URL | Contenuto |\n|---|---|\n| `memory://root` | Riepilogo compatto iniettato all'avvio |\n| `memory://root/MEMORY.md` | Documento completo di memoria a lungo termine |\n| `memory://root/skills/<name>/SKILL.md` | Un playbook di competenze generato |\n\n### Comando slash `/memory`\n\n| Sottocomando | Effetto |\n|---|---|\n| `view` | Mostra il payload di iniezione della memoria corrente |\n| `clear` / `reset` | Elimina tutti i dati di memoria e gli artefatti generati |\n| `enqueue` / `rebuild` | Forza l'esecuzione della consolidazione al prossimo avvio |\n\n## Come funziona\n\nLe memorie vengono costruite da una pipeline in background che si esegue all'avvio o manualmente tramite comando slash.\n\n**Fase 1 — estrazione per sessione:** Per ogni sessione passata che è cambiata dall'ultima elaborazione, un modello legge la cronologia della sessione ed estrae segnali duraturi: decisioni tecniche, vincoli, errori risolti, workflow ricorrenti. Le sessioni troppo recenti, troppo vecchie o attualmente attive vengono saltate. Ogni estrazione produce un blocco di memoria grezzo e una breve sinossi per quella sessione.\n\n**Fase 2 — consolidamento:** Dopo l'estrazione, un secondo passaggio del modello legge tutte le estrazioni per sessione e produce tre output scritti su disco:\n\n- `MEMORY.md` — un documento di memoria a lungo termine curato\n- `memory_summary.md` — il testo compatto iniettato all'avvio della sessione\n- `skills/` — playbook procedurali riutilizzabili, ciascuno nella propria sottodirectory\n\nLa Fase 2 utilizza un lease per evitare la doppia esecuzione quando più processi si avviano simultaneamente. Le directory di competenze obsolete dalle esecuzioni precedenti vengono eliminate automaticamente.\n\nTutti gli output vengono scansionati alla ricerca di segreti prima di essere scritti su disco.\n\n### Comportamento di estrazione\n\nIl comportamento di estrazione e consolidamento della memoria è interamente guidato da file di prompt statici in `src/prompts/memories/`.\n\n| File | Scopo | Variabili |\n|---|---|---|\n| `stage_one_system.md` | System prompt per l'estrazione per sessione | — |\n| `stage_one_input.md` | Template del turno utente che racchiude il contenuto della sessione | `{{thread_id}}`, `{{response_items_json}}` |\n| `consolidation.md` | Prompt per il consolidamento tra sessioni | `{{raw_memories}}`, `{{rollout_summaries}}` |\n| `read_path.md` | Guida di memoria iniettata nelle sessioni attive | `{{memory_summary}}` |\n\n### Selezione del modello\n\nLa memoria si appoggia al sistema di ruoli del modello.\n\n| Fase | Ruolo | Scopo |\n|---|---|---|\n| Fase 1 (estrazione) | `default` | Estrazione della conoscenza per sessione |\n| Fase 2 (consolidamento) | `smol` | Sintesi tra sessioni |\n\nSe `smol` non è configurato, la Fase 2 ricade sul ruolo `default`.\n\n## Configurazione\n\n| Impostazione | Predefinito | Descrizione |\n|---|---|---|\n| `memories.enabled` | `false` | Interruttore principale |\n| `memories.maxRolloutAgeDays` | `30` | Le sessioni più vecchie di questo valore non vengono elaborate |\n| `memories.minRolloutIdleHours` | `12` | Le sessioni attive più di recente rispetto a questo valore vengono saltate |\n| `memories.maxRolloutsPerStartup` | `64` | Limite di sessioni elaborate in un singolo avvio |\n| `memories.summaryInjectionTokenLimit` | `5000` | Massimo di token del riepilogo iniettato nel system prompt |\n\nParametri di regolazione aggiuntivi (concorrenza, durate dei lease, budget di token) sono disponibili nella configurazione per un uso avanzato.\n\n## File principali\n\n- `src/memories/index.ts` — orchestrazione della pipeline, iniezione, gestione dei comandi slash\n- `src/memories/storage.ts` — coda di lavori basata su SQLite e registro dei thread\n- `src/prompts/memories/` — template dei prompt di memoria\n- `src/internal-urls/memory-protocol.ts` — gestore degli URL `memory://`\n",
	"it/sessions/non-compaction-retry-policy.md": "---\ntitle: Politica di ripetizione automatica per errori non legati alla compattazione\ndescription: >-\n  Politica di ripetizione automatica per errori API transitori al di fuori del\n  percorso di compattazione.\nsidebar:\n  order: 6\n  label: Politica di ripetizione\ni18n:\n  sourceHash: 8999a0258dd8\n  translator: machine\n---\n\n# Politica di ripetizione automatica per errori non legati alla compattazione\n\nQuesto documento descrive il percorso standard di ripetizione in caso di errori API in `AgentSession`.\n\nEsclude esplicitamente il ripristino in caso di overflow del contesto tramite compattazione automatica. L'overflow è gestito dalla logica di compattazione ed è documentato separatamente in [`compaction.md`](./compaction.md).\n\n## File di implementazione\n\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/config/settings-schema.ts`](../../packages/coding-agent/src/config/settings-schema.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts)\n- [`../src/modes/rpc/rpc-client.ts`](../../packages/coding-agent/src/modes/rpc/rpc-client.ts)\n- [`../src/modes/rpc/rpc-types.ts`](../../packages/coding-agent/src/modes/rpc/rpc-types.ts)\n\n## Limite di ambito rispetto alla compattazione\n\nLa ripetizione e la compattazione vengono verificate dallo stesso percorso `agent_end`, ma sono intenzionalmente separate:\n\n1. `agent_end` ispeziona l'ultimo messaggio dell'assistente.\n2. `#isRetryableError(...)` viene eseguito per primo.\n3. Se viene avviata la ripetizione, i controlli di compattazione vengono saltati per quel turno.\n4. Gli errori di overflow del contesto sono esclusi in modo definitivo dalla classificazione delle ripetizioni (`isContextOverflow(...)` interrompe anticipatamente la ripetizione).\n5. L'overflow ricade quindi su `#checkCompaction(...)` anziché sulla ripetizione standard.\n\nPertanto: i fallimenti di tipo overload/rate/server/rete utilizzano questa politica di ripetizione; l'overflow della finestra di contesto utilizza il ripristino tramite compattazione.\n\n## Classificazione delle ripetizioni\n\n`#isRetryableError(...)` richiede che siano soddisfatte tutte le seguenti condizioni:\n\n- `stopReason === \"error\"` dell'assistente\n- `errorMessage` esiste\n- il messaggio **non** è un overflow del contesto\n- `errorMessage` corrisponde a `#isRetryableErrorMessage(...)`\n\nInsieme di pattern ripetibili correnti (basati su espressioni regolari):\n\n- overloaded\n- rate limit / usage limit / too many requests\n- classi server di tipo HTTP: 429, 500, 502, 503, 504\n- service unavailable / server error / internal error\n- connection error / fetch failed\n- formulazione `retry delay`\n\nSi tratta di classificazione tramite pattern su stringhe, non di codici di errore tipizzati del provider.\n\n## Ciclo di vita della ripetizione e transizioni di stato\n\nStato della sessione utilizzato dalla ripetizione:\n\n- `#retryAttempt: number` (`0` indica inattivo)\n- `#retryPromise: Promise<void> | undefined` (tiene traccia del ciclo di vita della ripetizione in corso)\n- `#retryResolve: (() => void) | undefined` (risolve `#retryPromise`)\n- `#retryAbortController: AbortController | undefined` (annulla il ritardo di backoff)\n\nFlusso (`#handleRetryableError`):\n\n1. Legge il gruppo di impostazioni `retry`.\n2. Se `retry.enabled === false`, si ferma immediatamente (`false`, nessuna ripetizione avviata).\n3. Incrementa `#retryAttempt`.\n4. Crea `#retryPromise` una sola volta (primo tentativo in una catena).\n5. Se il tentativo supera `retry.maxRetries`, emette l'evento di fallimento finale e si ferma.\n6. Calcola il ritardo: `retry.baseDelayMs * 2^(tentativo-1)`.\n7. Per gli errori di limite di utilizzo, analizza i suggerimenti di ripetizione e chiama l'archiviazione di autenticazione (`markUsageLimitReached(...)`); se il cambio di provider/modello ha successo, forza il ritardo a `0`.\n8. Emette `auto_retry_start`.\n9. Rimuove il messaggio di errore dell'assistente in coda dallo stato di runtime dell'agente (mantenuto nella cronologia della sessione persistita).\n10. Attende con supporto all'interruzione.\n11. Al risveglio, pianifica `agent.continue()` tramite `setTimeout(..., 0)`.\n\n### Cosa reimposta i contatori di ripetizione\n\n`#retryAttempt` viene reimpostato a `0` nei seguenti casi:\n\n- primo messaggio dell'assistente riuscito, senza errori e non interrotto, dopo l'avvio delle ripetizioni (emette `auto_retry_end { success: true }`)\n- annullamento della ripetizione durante il ritardo di backoff\n- percorso di superamento del numero massimo di ripetizioni\n\n`#retryPromise` viene risolto/cancellato al termine della catena di ripetizioni (successo, annullamento o superamento del massimo), tramite `#resolveRetry()`.\n\n## Semantica del backoff e del numero massimo di tentativi\n\nImpostazioni:\n\n- `retry.enabled` (valore predefinito `true`)\n- `retry.maxRetries` (valore predefinito `3`)\n- `retry.baseDelayMs` (valore predefinito `2000`)\n\nNumerazione dei tentativi:\n\n- il contatore dei tentativi viene incrementato prima del controllo del massimo\n- gli eventi di avvio utilizzano il tentativo corrente (basato su 1)\n- l'evento di fine per superamento del massimo riporta `attempt: this.#retryAttempt - 1` (ultimo conteggio di ripetizione tentato)\n\nSequenza di backoff con le impostazioni predefinite:\n\n- tentativo 1: 2000 ms\n- tentativo 2: 4000 ms\n- tentativo 3: 8000 ms\n\nGli input di override del ritardo vengono utilizzati esclusivamente nel percorso di gestione del limite di utilizzo, e solo per influenzare la decisione di cambio modello/account nell'archiviazione di autenticazione. Nel percorso principale di ripetizione non legato alla compattazione, il backoff rimane un ritardo esponenziale locale, a meno che il cambio non abbia successo (`delayMs = 0`).\n\n## Meccanismi di interruzione\n\n### Interruzione esplicita della ripetizione\n\n`abortRetry()`:\n\n- interrompe `#retryAbortController` (se presente)\n- risolve la promise di ripetizione (`#resolveRetry()`) in modo da sbloccare i chiamanti in attesa\n\nSe l'interruzione avviene durante l'attesa, il percorso di cattura emette:\n\n- `auto_retry_end { success: false, finalError: \"Retry cancelled\" }`\n- reimposta il tentativo e il controller\n\n### Interazione con l'interruzione globale dell'operazione\n\n`abort()` chiama `abortRetry()` prima di interrompere il flusso dell'agente attivo. Questo garantisce l'annullamento del backoff di ripetizione quando l'utente emette un'interruzione generale.\n\n### Interazione con l'interfaccia TUI\n\nAll'evento `auto_retry_start`, EventController:\n\n- sostituisce il gestore `Esc` con `session.abortRetry()`\n- mostra il testo del loader: `Retrying (attempt/maxAttempts) in Ns… (esc to cancel)`\n\nAll'evento `auto_retry_end`, ripristina il gestore `Esc` precedente e cancella lo stato del loader.\n\n## Comportamento dello streaming e del completamento del prompt\n\n`prompt()` attende infine su `#waitForRetry()` dopo che `agent.prompt(...)` ha restituito il controllo.\n\nEffetto:\n\n- una chiamata a prompt non si risolve completamente finché non termina qualsiasi catena di ripetizione avviata (successo/fallimento/annullamento)\n- il ciclo di vita della ripetizione fa parte di un unico confine logico di esecuzione del prompt\n\nCiò impedisce ai chiamanti di considerare concluso un turno in fase di ripetizione troppo presto.\n\n## Controlli: impostazioni e RPC\n\n### Parametri di configurazione\n\nDefiniti nello schema delle impostazioni nel gruppo retry:\n\n- `retry.enabled`\n- `retry.maxRetries`\n- `retry.baseDelayMs`\n\nControlli programmatici nella sessione:\n\n- `setAutoRetryEnabled(enabled)` scrive `retry.enabled`\n- `autoRetryEnabled` legge `retry.enabled`\n- `isRetrying` indica se la promise del ciclo di vita della ripetizione è attiva\n\n### Controlli RPC\n\nSuperficie dei comandi RPC:\n\n- `set_auto_retry` → `session.setAutoRetryEnabled(command.enabled)`\n- `abort_retry` → `session.abortRetry()`\n\nHelper del client:\n\n- `RpcClient.setAutoRetry(enabled)`\n- `RpcClient.abortRetry()`\n\nEntrambi i comandi restituiscono risposte di successo; i dettagli sull'avanzamento/fallimento della ripetizione provengono dagli eventi di sessione in streaming, non dai payload delle risposte ai comandi.\n\n## Emissione di eventi e rilevamento dei fallimenti\n\nEventi di ripetizione a livello di sessione:\n\n- `auto_retry_start { attempt, maxAttempts, delayMs, errorMessage }`\n- `auto_retry_end { success, attempt, finalError? }`\n\nPropagazione:\n\n- emessi tramite `AgentSession.subscribe(...)`\n- inoltrati al runner dell'estensione come eventi dell'estensione\n- in modalità RPC, inoltrati direttamente come oggetti evento JSON (`session.subscribe(event => output(event))`)\n- nell'interfaccia TUI, consumati da `EventController` per la UI di loader/errore\n\nRilevamento del fallimento finale:\n\n- In caso di superamento del massimo o annullamento, `auto_retry_end.success === false`\n- L'interfaccia TUI mostra: `Retry failed after N attempts: <finalError>`\n- Le estensioni/hook ricevono `auto_retry_end` con gli stessi campi\n- I consumatori RPC ricevono lo stesso oggetto evento sullo stream stdout\n\n## Condizioni di arresto permanente\n\nLa ripetizione si interrompe e non prosegue automaticamente quando si verifica una delle seguenti condizioni:\n\n- `retry.enabled` è false\n- l'errore non è classificato come ripetibile\n- l'errore è un overflow del contesto (delegato al percorso di compattazione)\n- è stato superato il numero massimo di ripetizioni\n- l'utente annulla la ripetizione (`abort_retry` oppure `Esc` durante il loader di ripetizione)\n- l'interruzione globale (`abort`) annulla prima la ripetizione\n\nUna nuova catena di ripetizione può comunque avviarsi successivamente in seguito a un nuovo errore ripetibile, dopo la reimpostazione dei contatori.\n\n## Avvertenze operative\n\n- La classificazione è basata su corrispondenza di pattern testuali tramite espressioni regolari; gli errori strutturati specifici del provider non vengono utilizzati qui.\n- La ripetizione rimuove l'errore dell'assistente dal **contesto di runtime** prima di continuare, ma la cronologia della sessione mantiene comunque quella voce di errore.\n- `RpcSessionState` attualmente espone `autoCompactionEnabled` ma non un campo `autoRetryEnabled`; i chiamanti RPC devono tenere traccia del proprio stato di attivazione o interrogare le impostazioni tramite altre API.\n",
	"it/sessions/session-operations-export-share-fork-resume.md": "---\ntitle: 'Operazioni di sessione: esportazione, dump, condivisione, fork, ripristino'\ndescription: >-\n  Operazioni di sessione per l'esportazione, la condivisione, il fork e il\n  ripristino delle conversazioni.\nsidebar:\n  order: 3\n  label: Operazioni\ni18n:\n  sourceHash: e3c210b29c3e\n  translator: machine\n---\n\n# Operazioni di sessione: export, dump, share, fork, resume/continue\n\nQuesto documento descrive il comportamento visibile dall'operatore per le operazioni di esportazione/condivisione/fork/ripristino della sessione così come sono attualmente implementate.\n\n## File di implementazione\n\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/export/html/index.ts`](../../packages/coding-agent/src/export/html/index.ts)\n- [`../src/export/custom-share.ts`](../../packages/coding-agent/src/export/custom-share.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n\n## Matrice delle operazioni\n\n| Operazione | Percorso di accesso | Mutazione della sessione | Creazione/cambio file di sessione | Artefatto di output |\n|---|---|---|---|---|\n| `/dump` | Comando slash interattivo | No | No | Testo negli appunti |\n| `/export [path]` | Comando slash interattivo | No | No | File HTML |\n| `--export <session.jsonl> [outputPath]` | Percorso rapido di avvio CLI | Nessuna mutazione di sessione a runtime | Nessuna sessione attiva; legge il file di destinazione | File HTML |\n| `/share` | Comando slash interattivo | No | No | HTML temporaneo + URL di condivisione/gist |\n| `/fork` | Comando slash interattivo | Sì (l'identità della sessione attiva cambia) | Crea un nuovo file di sessione e passa la sessione corrente a quest'ultimo (solo in modalità persistente) | Copia la directory degli artefatti nel nuovo namespace di sessione, se presente |\n| `/resume` | Comando slash interattivo | Sì (lo stato in-memory attivo viene sostituito) | Passa al file di sessione esistente selezionato | Nessuno |\n| `--resume` | Avvio CLI (selettore) | Sì dopo la creazione della sessione | Apre il file di sessione esistente selezionato | Nessuno |\n| `--resume <id\\|path>` | Avvio CLI | Sì dopo la creazione della sessione | Apre la sessione esistente; nel caso cross-project può effettuare il fork nel progetto corrente | Nessuno |\n| `--continue` | Avvio CLI | Sì dopo la creazione della sessione | Apre il breadcrumb del terminale o la sessione più recente; ne crea una nuova se non ne esiste nessuna | Nessuno |\n\n## Esportazione e dump\n\n### `/export [outputPath]` (interattivo)\n\nFlusso:\n\n1. `InputController` instrada `/export...` verso `CommandController.handleExportCommand`.\n2. Il comando divide per spazi bianchi e utilizza solo il primo argomento dopo `/export` come `outputPath`.\n3. `AgentSession.exportToHtml()` chiama `exportSessionToHtml(sessionManager, state, { outputPath, themeName })`.\n4. In caso di successo, l'interfaccia utente mostra il percorso e apre il file nel browser.\n\nDettagli del comportamento:\n\n- Gli argomenti `--copy`, `clipboard` e `copy` vengono esplicitamente rifiutati con un avviso che invita a utilizzare `/dump`.\n- L'esportazione incorpora l'intestazione/le voci/la foglia della sessione più il `systemPrompt` corrente e le descrizioni degli strumenti dallo stato dell'agente.\n- Nessuna voce di sessione viene aggiunta durante l'esportazione.\n\nAvvertenza:\n\n- L'analisi degli argomenti è basata sugli spazi bianchi (`text.split(/\\s+/)`), pertanto i percorsi tra virgolette con spazi non vengono preservati come percorso singolo in questo percorso di comando.\n\n### `--export <inputSessionFile> [outputPath]` (CLI)\n\nFlusso in `main.ts`:\n\n1. Gestito anticipatamente (prima dell'avvio interattivo/della sessione).\n2. Chiama `exportFromFile(inputPath, outputPath?)`.\n3. `SessionManager.open(inputPath)` carica le voci, quindi l'HTML viene generato e scritto.\n4. Il processo stampa `Exported to: ...` ed esce.\n\nDettagli del comportamento:\n\n- Un file di input mancante viene segnalato come `File not found: <path>`.\n- Questo percorso non crea un `AgentSession` e non muta alcuna sessione in esecuzione.\n\n### `/dump` (esportazione interattiva negli appunti)\n\nFlusso:\n\n1. `CommandController.handleDumpCommand()` chiama `session.formatSessionAsText()`.\n2. Se la stringa è vuota, riporta `No messages to dump yet.`\n3. Altrimenti copia negli appunti tramite `copyToClipboard` nativo.\n\nIl contenuto del dump include:\n\n- Prompt di sistema\n- Modello attivo/livello di riflessione\n- Definizioni degli strumenti e parametri\n- Messaggi utente/assistente\n- Blocchi di riflessione e chiamate agli strumenti\n- Risultati degli strumenti e blocchi di esecuzione (ad eccezione delle voci bash/python con `excludeFromContext`)\n- Voci personalizzate/hook/menzione di file/riepilogo branch/riepilogo di compattazione\n\nNessuna modifica alla persistenza della sessione viene effettuata dal dump.\n\n## Condivisione\n\n`/share` è solo interattivo e inizia sempre esportando la sessione corrente in un file HTML temporaneo.\n\n### Fase 1: esportazione temporanea\n\n- Percorso del file temporaneo: `${os.tmpdir()}/${Snowflake.next()}.html`\n- Utilizza `session.exportToHtml(tmpFile)`\n- Se l'esportazione fallisce (in particolare per le sessioni in-memory), la condivisione termina con un errore.\n\n### Fase 2: gestore di condivisione personalizzato (se presente)\n\n`loadCustomShare()` verifica `~/.xcsh/agent` per il primo candidato esistente:\n\n- `share.ts`\n- `share.js`\n- `share.mjs`\n\nRequisiti:\n\n- Il modulo deve esportare come default una funzione `(htmlPath) => Promise<CustomShareResult | string | undefined>`.\n\nSe presente e valido:\n\n- L'interfaccia utente entra nello stato di caricamento `Sharing...`.\n- Interpretazione del risultato del gestore:\n  - stringa => trattata come URL, mostrata e aperta\n  - oggetto => `url` e/o `message` mostrati; `url` aperto\n  - `undefined`/falsy => `Session shared` generico\n- Il file temporaneo viene rimosso al termine.\n\nComportamento di fallback critico:\n\n- Se il gestore personalizzato esiste ma il caricamento fallisce, il comando genera un errore e termina.\n- Se il gestore personalizzato viene eseguito e genera un'eccezione, il comando genera un errore e termina.\n- In entrambi i casi di errore, **non** viene eseguito il fallback al gist di GitHub.\n- Il fallback al gist avviene solo quando non esiste nessuno script di condivisione personalizzato.\n\n### Fase 3: fallback predefinito al gist\n\nSolo quando non viene trovato nessun gestore di condivisione personalizzato:\n\n1. Valida `gh auth status`.\n2. Mostra il loader `Creating gist...`.\n3. Esegue `gh gist create --public=false <tmpFile>`.\n4. Analizza l'URL del gist, ricava l'id del gist, costruisce l'URL di anteprima `https://gistpreview.github.io/?<id>`.\n5. Mostra sia l'URL di anteprima che quello del gist; apre l'anteprima.\n\nSemantica di annullamento/interruzione nella condivisione:\n\n- Il loader dispone di un hook `onAbort` che ripristina l'interfaccia utente dell'editor e riporta `Share cancelled`.\n- Il comando sottostante `gh gist create` non riceve un segnale di interruzione in questo percorso di codice; l'annullamento è a livello di interfaccia utente e viene verificato dopo il ritorno del comando.\n\n## Fork\n\n`/fork` crea una nuova sessione da quella corrente e cambia l'identità della sessione attiva.\n\n### Precondizioni e controlli immediati\n\n- Se l'agente sta eseguendo lo streaming, `/fork` viene rifiutato con un avviso.\n- Gli indicatori di stato/caricamento dell'interfaccia utente vengono azzerati prima dell'operazione.\n\n### Flusso a livello di sessione\n\n`AgentSession.fork()`:\n\n1. Emette `session_before_switch` con `reason: \"fork\"` (annullabile).\n2. Svuota le scritture in sospeso.\n3. Chiama `SessionManager.fork()`.\n4. Copia la directory degli artefatti dal namespace della vecchia sessione a quello nuovo (best-effort; i fallimenti di copia non ENOENT vengono registrati, non sono fatali).\n5. Aggiorna `agent.sessionId`.\n6. Emette `session_switch` con `reason: \"fork\"`.\n\nComportamento di `SessionManager.fork()`:\n\n- Richiede la modalità persistente e un file di sessione esistente.\n- Crea un nuovo id di sessione e un nuovo percorso file JSONL.\n- Riscrive l'intestazione con:\n  - nuovo `id`\n  - nuovo timestamp\n  - `cwd` invariato\n  - `parentSession` impostato sull'id della sessione precedente\n- Mantiene invariate tutte le voci non di intestazione nel nuovo file.\n\n### Comportamento non persistente\n\n- Il gestore di sessione in-memory restituisce `undefined` da `fork()`.\n- `AgentSession.fork()` restituisce `false`.\n- L'interfaccia utente riporta `Fork failed (session not persisted or cancelled)`.\n\n## Ripristino e continuazione\n\n## `/resume` interattivo\n\nFlusso:\n\n1. Apre il selettore di sessione popolato tramite `SessionManager.list(currentCwd, currentSessionDir)`.\n2. Alla selezione, `SelectorController.handleResumeSession(sessionPath)` chiama `session.switchSession(sessionPath)`.\n3. L'interfaccia utente cancella/ricostruisce la chat e i todo, quindi riporta `Resumed session`.\n\nNote:\n\n- Questo selettore elenca solo le sessioni nell'ambito della directory di sessione corrente.\n- Non utilizza la ricerca globale cross-project.\n\n## CLI `--resume`\n\n### `--resume` (nessun valore)\n\n- `main.ts` elenca le sessioni per cwd/sessionDir correnti e apre il selettore.\n- Il percorso selezionato viene aperto con `SessionManager.open(selectedPath)` prima della creazione della sessione.\n\n### `--resume <value>`\n\nOrdine di risoluzione di `createSessionManager()`:\n\n1. Se il valore sembra un percorso (`/`, `\\` o `.jsonl`), apertura diretta.\n2. Altrimenti trattato come prefisso id:\n   - ricerca nell'ambito corrente (`SessionManager.list(cwd, sessionDir)`)\n   - se non trovato e nessuna `sessionDir` esplicita, ricerca globale (`SessionManager.listAll()`)\n\nComportamento in caso di corrispondenza id cross-project:\n\n- Se il cwd della sessione trovata differisce dal cwd corrente, la CLI chiede:\n  - `Session found in different project ... Fork into current directory? [y/N]`\n- In caso affermativo: `SessionManager.forkFrom(match.path, cwd, sessionDir)` crea un nuovo file forked locale.\n- In caso negativo/TTY non predefinito: il comando genera un errore.\n\n## CLI `--continue`\n\n`SessionManager.continueRecent(cwd, sessionDir)`:\n\n1. Risolve la directory di sessione per il cwd corrente.\n2. Legge prima il breadcrumb con scope al terminale.\n3. Ricorre al file di sessione modificato più di recente.\n4. Apre la sessione trovata; se non ne esiste nessuna, crea una nuova sessione.\n\nQuesto è un comportamento solo di avvio; non esiste un comando slash interattivo `/continue`.\n\n## Come il cambio di sessione muta effettivamente lo stato a runtime\n\n`AgentSession.switchSession(sessionPath)` esegue la transizione a runtime utilizzata dalle operazioni di tipo resume:\n\n1. Emette `session_before_switch` con `reason: \"resume\"` e `targetSessionFile` (annullabile).\n2. Disconnette la sottoscrizione agli eventi dell'agente e interrompe il lavoro in corso.\n3. Cancella i messaggi di steering/follow-up/next-turn in coda.\n4. Svuota le scritture del gestore di sessione corrente.\n5. `sessionManager.setSessionFile(sessionPath)` e aggiorna `agent.sessionId`.\n6. Costruisce il contesto di sessione dalle voci caricate.\n7. Emette `session_switch` con `reason: \"resume\"`.\n8. Sostituisce i messaggi dell'agente dal contesto.\n9. Ripristina il modello (se disponibile nel registro corrente).\n10. Ripristina o inizializza il livello di riflessione.\n11. Riconnette la sottoscrizione agli eventi dell'agente.\n\nNessun nuovo file di sessione viene creato da `switchSession()` stesso.\n\n## Emissioni di eventi e punti di annullamento\n\n### Hook del ciclo di vita switch/fork\n\nPer `newSession`, `fork` e `switchSession`:\n\n- Evento precedente: `session_before_switch`\n  - motivazioni: `new`, `fork`, `resume`\n  - annullabile restituendo `{ cancel: true }`\n- Evento successivo: `session_switch`\n  - stesso insieme di motivazioni\n  - include `previousSessionFile`\n\n`ExtensionRunner.emit()` termina anticipatamente al primo risultato di un evento precedente che annulla.\n\n### Comportamento `onSession` degli strumenti personalizzati\n\nL'SDK collega gli eventi di sessione delle estensioni ai callback `onSession` degli strumenti personalizzati:\n\n- `session_switch` -> `onSession({ reason: \"switch\", previousSessionFile })`\n- `session_branch` -> `reason: \"branch\"`\n- `session_start` -> `reason: \"start\"`\n- `session_tree` -> `reason: \"tree\"`\n- `session_shutdown` -> `reason: \"shutdown\"`\n\nQuesti callback sono osservativi; non annullano switch/fork.\n\n### Altre superfici di annullamento rilevanti per questo documento\n\n- `/fork` viene bloccato durante lo streaming (l'utente deve attendere/interrompere la risposta corrente prima).\n- Il selettore `/resume` può essere annullato dall'utente chiudendo il selettore.\n- `--resume <id>` cross-project può essere annullato rifiutando il prompt di fork.\n- `/share` dispone di un percorso di interruzione nell'interfaccia utente (`Share cancelled`) per il flusso gist; non implementa la semantica di kill del processo per `gh gist create` in questo percorso di codice.\n\n## Comportamento della sessione non persistente (in-memory)\n\nQuando il gestore di sessione viene creato con `SessionManager.inMemory()` (`--no-session`):\n\n- Il percorso del file di sessione è assente.\n- `/export` e `/share` falliscono con `Cannot export in-memory session to HTML` (propagato all'interfaccia utente degli errori di comando).\n- `/fork` fallisce perché `SessionManager.fork()` richiede la persistenza.\n- `/dump` funziona ancora perché serializza lo stato dell'agente in-memory.\n- La semantica di resume/continue da CLI viene ignorata se `--no-session` è impostato, poiché la creazione del gestore restituisce immediatamente in-memory.\n\n## Avvertenze note sull'implementazione (nel codice corrente)\n\n- `SelectorController.handleResumeSession()` non verifica il risultato booleano di `session.switchSession(...)`; un cambio annullato da un hook può comunque procedere attraverso il percorso di ridisegno/stato \"Resumed session\" dell'interfaccia utente.\n- I fallimenti di condivisione personalizzata in `/share` non degradano al fallback gist predefinito; terminano il comando con un errore.\n- La tokenizzazione degli argomenti di `/export` è semplicistica e non preserva i percorsi tra virgolette con spazi.\n",
	"it/sessions/session-switching-and-recent-listing.md": "---\ntitle: Cambio di sessione e lista delle sessioni recenti\ndescription: >-\n  Meccaniche di cambio sessione e lista delle sessioni recenti con ricerca e\n  filtraggio.\nsidebar:\n  order: 4\n  label: Cambio e recenti\ni18n:\n  sourceHash: aae56130b508\n  translator: machine\n---\n\n# Cambio di sessione e lista delle sessioni recenti\n\nQuesto documento descrive come coding-agent scopre le sessioni recenti, risolve i target di `--resume`, presenta i selettori di sessione e cambia la sessione runtime attiva.\n\nSi concentra sul comportamento dell'implementazione attuale, inclusi i percorsi di fallback e le avvertenze.\n\n## File di implementazione\n\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/cli/session-picker.ts`](../../packages/coding-agent/src/cli/session-picker.ts)\n- [`../src/modes/components/session-selector.ts`](../../packages/coding-agent/src/modes/components/session-selector.ts)\n- [`../src/modes/controllers/selector-controller.ts`](../../packages/coding-agent/src/modes/controllers/selector-controller.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n\n## Scoperta delle sessioni recenti\n\n### Ambito della directory\n\n`SessionManager` salva le sessioni in una directory con ambito cwd per impostazione predefinita:\n\n- `~/.xcsh/agent/sessions/--<cwd-encoded>--/*.jsonl`\n\n`SessionManager.list(cwd, sessionDir?)` legge solo quella directory a meno che non venga fornita una `sessionDir` esplicita.\n\n### Due percorsi di listing con payload differenti\n\nEsistono due pipeline di listing differenti:\n\n1. `getRecentSessions(sessionDir, limit)` (vista di benvenuto/riepilogo)\n   - Legge solo un prefisso di 4KB (`readTextPrefix(..., 4096)`) da ciascun file.\n   - Analizza l'header + anteprima del primo testo utente.\n   - Restituisce un `RecentSessionInfo` leggero con getter lazy per `name` e `timeAgo`.\n   - Ordina per `mtime` del file in ordine decrescente.\n\n2. `SessionManager.list(...)` / `SessionManager.listAll()` (selettori di ripresa e corrispondenza ID)\n   - Legge i file di sessione completi.\n   - Costruisce oggetti `SessionInfo` (`id`, `cwd`, `title`, `messageCount`, `firstMessage`, `allMessagesText`, timestamp).\n   - Esclude le sessioni con zero voci `message`.\n   - Ordina per `modified` in ordine decrescente.\n\n### Comportamento di fallback dei metadati\n\nPer i riepiloghi recenti (`RecentSessionInfo`):\n\n- preferenza del nome visualizzato: `header.title` -> primo prompt utente -> `header.id` -> nome file\n- il nome è troncato a 40 caratteri per le visualizzazioni compatte\n- i caratteri di controllo/newline vengono rimossi/sanificati dai nomi derivati dal titolo\n\nPer le voci della lista `SessionInfo`:\n\n- `title` è `header.title` o l'ultimo `shortSummary` di compattazione\n- `firstMessage` è il testo del primo messaggio utente o `\"(no messages)\"`\n\n## Risoluzione di `--continue` e preferenza del breadcrumb del terminale\n\n`SessionManager.continueRecent(cwd, sessionDir?)` risolve il target in questo ordine:\n\n1. Legge il breadcrumb con ambito terminale (`~/.xcsh/agent/terminal-sessions/<terminal-id>`)\n2. Valida il breadcrumb:\n   - il terminale corrente può essere identificato\n   - il cwd del breadcrumb corrisponde al cwd corrente (confronto di percorsi risolti)\n   - il file referenziato esiste ancora\n3. Se il breadcrumb è invalido/mancante, ripiegamento sul file più recente per mtime nella directory di sessione (`findMostRecentSession`)\n4. Se nessuno trovato, crea una nuova sessione\n\nLa derivazione dell'ID del terminale preferisce il percorso TTY e ripiega su identificatori basati su variabili d'ambiente (`KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION`).\n\nLe scritture del breadcrumb sono best-effort e non fatali.\n\n## Risoluzione del target di ripresa all'avvio (`main.ts`)\n\n### `--resume <valore>`\n\n`createSessionManager(...)` gestisce `--resume` con valore stringa in due modalità:\n\n1. Valore tipo percorso (contiene `/`, `\\\\`, o termina con `.jsonl`)\n   - direttamente `SessionManager.open(sessionArg, parsed.sessionDir)`\n\n2. Valore prefisso ID\n   - cerca corrispondenza in `SessionManager.list(cwd, sessionDir)` tramite `id.startsWith(sessionArg)`\n   - se nessuna corrispondenza locale e `sessionDir` non è forzata, prova `SessionManager.listAll()`\n   - viene usata la prima corrispondenza (nessun prompt di ambiguità)\n\nComportamento di corrispondenza cross-progetto:\n\n- se il cwd della sessione trovata differisce dal cwd corrente, il CLI chiede se effettuare un fork nel progetto corrente\n- sì -> `SessionManager.forkFrom(...)`\n- no -> lancia un errore (`Session \"...\" is in another project (...)`)\n\nNessuna corrispondenza -> lancia un errore (`Session \"...\" not found.`).\n\n### `--resume` (senza valore)\n\nGestito dopo la costruzione iniziale del session-manager:\n\n1. elenca le sessioni locali con `SessionManager.list(cwd, parsed.sessionDir)`\n2. se vuoto: stampa `No sessions found` ed esce anticipatamente\n3. apre il selettore TUI (`selectSession`)\n4. se annullato: stampa `No session selected` ed esce anticipatamente\n5. se selezionato: `SessionManager.open(selectedPath)`\n\n### `--continue`\n\nUsa direttamente `SessionManager.continueRecent(...)` (comportamento breadcrumb-first descritto sopra).\n\n## Dettagli interni della selezione tramite picker\n\n## Picker CLI (`src/cli/session-picker.ts`)\n\n`selectSession(sessions)` crea una TUI standalone con `SessionSelectorComponent` e si risolve esattamente una volta:\n\n- selezione -> risolve il percorso selezionato\n- annullamento (Esc) -> risolve `null`\n- uscita forzata (percorso Ctrl+C) -> ferma la TUI e `process.exit(0)`\n\n## Picker interattivo in-sessione (`SelectorController.showSessionSelector`)\n\nFlusso:\n\n1. recupera le sessioni dalla directory di sessione corrente tramite `SessionManager.list(currentCwd, currentSessionDir)`\n2. monta `SessionSelectorComponent` nell'area editor usando `showSelector(...)`\n3. callback:\n   - selezione -> chiude il selettore e chiama `handleResumeSession(sessionPath)`\n   - annullamento -> ripristina l'editor e ri-renderizza\n   - uscita -> `ctx.shutdown()`\n\n## Comportamento del componente selettore di sessione\n\n`SessionList` supporta:\n\n- navigazione con frecce/pagina\n- Invio per selezionare\n- Esc per annullare\n- Ctrl+C per uscire\n- ricerca fuzzy su id sessione/titolo/cwd/primo messaggio/tutti i messaggi/percorso\n\nComportamento di rendering con lista vuota:\n\n- renderizza un messaggio invece di andare in crash\n- Invio su lista vuota non fa nulla (nessun callback)\n- Esc/Ctrl+C funzionano comunque\n\nAvvertenza: il testo dell'UI dice `Press Tab to view all`, ma questo componente attualmente non ha un gestore Tab e il wiring corrente elenca solo le sessioni dell'ambito corrente.\n\n## Esecuzione del cambio runtime (`AgentSession.switchSession`)\n\n`switchSession(sessionPath)` è il percorso principale di cambio in-process.\n\nCiclo di vita/transizione di stato:\n\n1. cattura `previousSessionFile`\n2. emette l'evento hook `session_before_switch` (`reason: \"resume\"`, cancellabile)\n3. se annullato -> restituisce `false` senza cambio\n4. si disconnette dallo stream di eventi dell'agente corrente\n5. interrompe la generazione/flusso di tool attivo\n6. svuota i buffer dei messaggi di steering/follow-up/next-turn in coda\n7. scarica il session writer (`sessionManager.flush()`) per persistere le scritture in sospeso\n8. `sessionManager.setSessionFile(sessionPath)`\n   - aggiorna il puntatore al file di sessione\n   - scrive il breadcrumb del terminale\n   - carica le voci / migra / risolve blob / reindicizza\n   - se dati del file mancanti/invalidi: inizializza una nuova sessione a quel percorso e riscrive l'header\n9. aggiorna `agent.sessionId`\n10. ricostruisce il contesto tramite `buildSessionContext()`\n11. emette l'evento hook `session_switch` (`reason: \"resume\"`, `previousSessionFile`)\n12. sostituisce i messaggi dell'agente con il contesto ricostruito\n13. ripristina il modello predefinito da `sessionContext.models.default` se disponibile e presente nel registro dei modelli\n14. ripristina il livello di thinking:\n    - se il branch ha già `thinking_level_change`, applica il livello salvato della sessione\n    - altrimenti deriva il livello di thinking predefinito dalle impostazioni, lo limita alla capacità del modello, lo imposta e aggiunge una nuova voce `thinking_level_change`\n15. riconnette i listener dell'agente e restituisce `true`\n\n## Ricostruzione dello stato UI dopo il cambio interattivo\n\n`SelectorController.handleResumeSession` esegue il reset dell'UI attorno a `switchSession`:\n\n- ferma l'animazione di caricamento\n- svuota il container di stato\n- svuota l'UI dei messaggi in sospeso e la mappa dei tool in sospeso\n- resetta il componente di streaming/riferimenti ai messaggi\n- chiama `session.switchSession(...)`\n- svuota il container della chat e ri-renderizza dal contesto della sessione (`renderInitialMessages`)\n- ricarica i todo dagli artefatti della nuova sessione\n- mostra `Resumed session`\n\nQuindi lo stato visibile della conversazione/todo viene ricostruito dal nuovo file di sessione.\n\n## Ripresa all'avvio vs cambio in-sessione\n\n### Ripresa all'avvio (`--continue`, `--resume`, apertura diretta)\n\n- Il file di sessione viene scelto prima di `createAgentSession(...)`.\n- `sdk.ts` costruisce `existingSession = sessionManager.buildSessionContext()`.\n- I messaggi dell'agente vengono ripristinati una volta durante la creazione della sessione.\n- Modello/thinking vengono selezionati durante la creazione (inclusa la logica di ripristino/fallback).\n- La modalità interattiva poi esegue `#restoreModeFromSession()` per ri-entrare nello stato di modalità persistito (attualmente plan/plan_paused).\n\n### Cambio in-sessione (percorso selettore stile `/resume`)\n\n- Usa `AgentSession.switchSession(...)` su un `AgentSession` già in esecuzione.\n- Messaggi/modello/thinking vengono ricostruiti immediatamente sul posto.\n- Vengono emessi gli eventi hook `session_before_switch`/`session_switch`.\n- Chat/todo dell'UI vengono aggiornati.\n- Nessuna chiamata dedicata di ripristino della modalità post-cambio viene effettuata nel flusso del selettore; il comportamento di ri-entrata nella modalità non è simmetrico con `#restoreModeFromSession()` all'avvio.\n\n## Comportamento in caso di errore e casi limite\n\n### Percorsi di annullamento\n\n- Annullamento picker CLI -> restituisce `null`, il chiamante stampa `No session selected`, il processo esce anticipatamente.\n- Annullamento picker interattivo -> editor ripristinato, nessun cambio di sessione.\n- Annullamento hook (`session_before_switch`) -> `switchSession()` restituisce `false`.\n\n### Percorsi con lista vuota\n\n- CLI `--resume` (senza valore): lista vuota stampa `No sessions found` ed esce.\n- Selettore interattivo: lista vuota renderizza un messaggio e rimane annullabile.\n\n### File di sessione target mancante/invalido\n\nQuando si apre/cambia verso un percorso specifico (`setSessionFile`):\n\n- ENOENT -> trattato come vuoto -> nuova sessione inizializzata a quel percorso esatto e persistita.\n- header malformato/invalido (o voci analizzate effettivamente illeggibili) -> trattato come vuoto -> nuova sessione inizializzata e persistita.\n\nQuesto è un comportamento di recupero, non un fallimento hard.\n\n### Fallimenti hard\n\nIl cambio/apertura può comunque lanciare eccezioni su veri errori di I/O (errori di permessi, errori di riscrittura, ecc.), che vengono propagati ai chiamanti.\n\n### Avvertenze sulla corrispondenza per prefisso ID\n\n- La corrispondenza ID usa `startsWith` e prende la prima corrispondenza nella lista ordinata.\n- Nessuna UI di disambiguazione se più sessioni condividono lo stesso prefisso.\n- `SessionManager.list(...)` esclude le sessioni con zero messaggi, quindi quelle sessioni non sono ripristinabili tramite corrispondenza ID/selettore lista.\n",
	"it/sessions/session-tree-plan.md": "---\ntitle: Architettura ad albero delle sessioni\ndescription: >-\n  Architettura ad albero delle sessioni con diramazione, navigazione e relazioni\n  di conversazione padre-figlio.\nsidebar:\n  order: 2\n  label: Architettura ad albero\ni18n:\n  sourceHash: bd8b78d6c33a\n  translator: machine\n---\n\n# Architettura ad albero delle sessioni (corrente)\n\nRiferimento: [session.md](./session.md)\n\nQuesto documento descrive il funzionamento attuale della navigazione ad albero delle sessioni: modello ad albero in memoria, regole di movimento delle foglie, comportamento di diramazione e integrazione con estensioni/eventi.\n\n## Cosa rappresenta questo sottosistema\n\nLa sessione è memorizzata come un log di voci append-only, ma il comportamento a runtime è basato su albero:\n\n- Ogni voce non di intestazione ha `id` e `parentId`.\n- La posizione attiva è `leafId` in `SessionManager`.\n- L'aggiunta di una voce crea sempre un figlio della foglia corrente.\n- La diramazione **non** riscrive la cronologia; modifica solo il punto a cui la foglia punta prima del successivo inserimento.\n\nFile chiave:\n\n- `src/session/session-manager.ts` — modello dati ad albero, attraversamento, movimento delle foglie, estrazione di rami/sessioni\n- `src/session/agent-session.ts` — flusso di navigazione `/tree`, riepilogo, emissione di hook/eventi\n- `src/modes/components/tree-selector.ts` — comportamento dell'interfaccia ad albero interattiva e filtraggio\n- `src/modes/controllers/selector-controller.ts` — orchestrazione del selettore per `/tree` e `/branch`\n- `src/modes/controllers/input-controller.ts` — instradamento dei comandi (`/tree`, `/branch`, comportamento del doppio Escape)\n- `src/session/messages.ts` — conversione delle voci `branch_summary`, `compaction` e `custom_message` in messaggi di contesto per il modello LLM\n\n## Modello dati ad albero in `SessionManager`\n\nIndici a runtime:\n\n- `#byId: Map<string, SessionEntry>` — ricerca rapida per qualsiasi voce\n- `#leafId: string | null` — posizione corrente nell'albero\n- `#labelsById: Map<string, string>` — etichette risolte per id della voce di destinazione\n\nAPI dell'albero:\n\n- `getBranch(fromId?)` percorre i collegamenti al nodo padre fino alla radice e restituisce il percorso radice→nodo\n- `getTree()` restituisce `SessionTreeNode[]` (`entry`, `children`, `label`)\n  - i collegamenti al nodo padre diventano array di figli\n  - le voci con nodi padre mancanti sono trattate come radici\n  - i figli sono ordinati dal più vecchio al più recente per timestamp\n- `getChildren(parentId)` restituisce i figli diretti\n- `getLabel(id)` risolve l'etichetta corrente da `labelsById`\n\n`getTree()` è una proiezione a runtime; la persistenza rimane come voci JSONL append-only.\n\n## Semantica del movimento delle foglie\n\nEsistono tre primitive di movimento delle foglie:\n\n1. `branch(entryId)`\n   - Valida l'esistenza della voce\n   - Imposta `leafId = entryId`\n   - Non viene scritta alcuna nuova voce\n\n2. `resetLeaf()`\n   - Imposta `leafId = null`\n   - Il successivo inserimento crea una nuova voce radice (`parentId = null`)\n\n3. `branchWithSummary(branchFromId, summary, details?, fromExtension?)`\n   - Accetta `branchFromId: string | null`\n   - Imposta `leafId = branchFromId`\n   - Aggiunge una voce `branch_summary` come figlio di quella foglia\n   - Quando `branchFromId` è `null`, `fromId` viene persistito come `\"root\"`\n\n## Comportamento della navigazione `/tree` (stesso file di sessione)\n\n`AgentSession.navigateTree()` è navigazione, non biforcazione di file.\n\nFlusso:\n\n1. Validare la destinazione e calcolare il percorso abbandonato (`collectEntriesForBranchSummary`)\n2. Emettere `session_before_tree` con `TreePreparation`\n3. Riepilogare facoltativamente le voci abbandonate (riepilogo fornito dall'hook o riepilogatore integrato)\n4. Calcolare la nuova destinazione della foglia:\n   - selezionando un messaggio **utente**: la foglia si sposta al suo nodo padre e il testo del messaggio viene restituito per la precompilazione dell'editor\n   - selezionando un **custom_message**: stessa regola del messaggio utente (foglia = nodo padre, il testo precompila l'editor)\n   - selezionando qualsiasi altra voce: foglia = id della voce selezionata\n5. Applicare lo spostamento della foglia:\n   - con riepilogo: `branchWithSummary(newLeafId, ...)`\n   - senza riepilogo e `newLeafId === null`: `resetLeaf()`\n   - altrimenti: `branch(newLeafId)`\n6. Ricostruire il contesto dell'agente dalla nuova foglia ed emettere `session_tree`\n\nImportante: le voci di riepilogo sono collegate alla **nuova posizione di navigazione**, non alla coda del ramo abbandonato.\n\n## Comportamento di `/branch` (nuovo file di sessione)\n\n`/branch` e `/tree` sono intenzionalmente diversi:\n\n- `/tree` naviga all'interno del file di sessione corrente.\n- `/branch` crea un nuovo file di ramo di sessione (o una sostituzione in memoria per la modalità non persistente).\n\nFlusso `/branch` rivolto all'utente (`SelectorController.showUserMessageSelector` → `AgentSession.branch`):\n\n- L'origine del ramo deve essere un **messaggio utente**.\n- Il testo utente selezionato viene estratto per la precompilazione dell'editor.\n- Se il messaggio utente selezionato è la radice (`parentId === null`): avviare una nuova sessione tramite `newSession({ parentSession: previousSessionFile })`.\n- Altrimenti: `createBranchedSession(selectedEntry.parentId)` per biforcre la cronologia fino al limite del prompt selezionato.\n\nSpecifiche di `SessionManager.createBranchedSession(leafId)`:\n\n- Costruisce il percorso radice→foglia tramite `getBranch(leafId)`; genera un errore se mancante.\n- Esclude le voci `label` esistenti dal percorso copiato.\n- Ricostruisce nuove voci etichetta dagli `labelsById` risolti per le voci che rimangono nel percorso.\n- Modalità persistente: scrive un nuovo file JSONL e trasferisce il manager su di esso; restituisce il nuovo percorso del file.\n- Modalità in memoria: sostituisce le voci in memoria; restituisce `undefined`.\n\n## Ricostruzione del contesto e integrazione di riepilogo/custom\n\n`buildSessionContext()` (in `session-manager.ts`) risolve il percorso radice→foglia attivo e costruisce lo stato di contesto LLM effettivo:\n\n- Traccia l'ultimo stato di thinking/model/mode/ttsr sul percorso.\n- Gestisce l'ultima compattazione sul percorso:\n  - emette prima il riepilogo della compattazione\n  - riproduce i messaggi mantenuti da `firstKeptEntryId` al punto di compattazione\n  - poi riproduce i messaggi successivi alla compattazione\n- Include le voci `branch_summary` e `custom_message` come oggetti `AgentMessage`.\n\n`session/messages.ts` mappa poi questi tipi di messaggi per l'input del modello:\n\n- `branchSummary` e `compactionSummary` diventano messaggi di contesto con template ruolo-utente\n- `custom`/`hookMessage` diventano messaggi di contenuto ruolo-utente\n\nPertanto, lo spostamento nell'albero modifica il contesto cambiando il percorso della foglia attiva, senza mutare le voci precedenti.\n\n## Etichette e comportamento dell'interfaccia ad albero\n\nPersistenza delle etichette:\n\n- `appendLabelChange(targetId, label?)` scrive voci `label` sulla catena della foglia corrente.\n- `labelsById` viene aggiornato immediatamente (impostazione o eliminazione).\n- `getTree()` risolve l'etichetta corrente su ciascun nodo restituito.\n\nComportamento del selettore ad albero (`tree-selector.ts`):\n\n- Appiattisce l'albero per la navigazione, mantiene l'evidenziazione del percorso attivo e dà priorità alla visualizzazione del ramo attivo.\n- Supporta modalità di filtro: `default`, `no-tools`, `user-only`, `labeled-only`, `all`.\n- Supporta la ricerca di testo libero sul contenuto semantico visualizzato.\n- `Shift+L` apre la modifica inline delle etichette e scrive tramite `appendLabelChange`.\n\nInstradamento dei comandi:\n\n- `/tree` apre sempre il selettore ad albero.\n- `/branch` apre il selettore di messaggi utente a meno che `doubleEscapeAction=tree`, nel qual caso usa anche l'interfaccia del selettore ad albero.\n\n## Punti di integrazione con estensioni e hook per le operazioni ad albero\n\nAPI delle estensioni per i comandi (`ExtensionCommandContext`):\n\n- `branch(entryId)` — crea un file di sessione ramificato\n- `navigateTree(targetId, { summarize? })` — sposta all'interno dell'albero/file corrente\n\nEventi relativi alla navigazione ad albero:\n\n- `session_before_tree`\n  - riceve `TreePreparation`:\n    - `targetId`\n    - `oldLeafId`\n    - `commonAncestorId`\n    - `entriesToSummarize`\n    - `userWantsSummary`\n  - può annullare la navigazione\n  - può fornire il payload di riepilogo usato al posto del riepilogatore integrato\n  - riceve il segnale di interruzione `signal` (percorso di annullamento tramite Escape)\n- `session_tree`\n  - emette `newLeafId`, `oldLeafId`\n  - include `summaryEntry` quando è stato creato un riepilogo\n  - `fromExtension` indica l'origine del riepilogo\n\nHook del ciclo di vita adiacenti ma correlati:\n\n- `session_before_branch` / `session_branch` per il flusso `/branch`\n- `session_before_compact`, `session.compacting`, `session_compact` per le voci di compattazione che influenzano successivamente la ricostruzione del contesto ad albero\n\n## Vincoli reali e condizioni limite\n\n- `branch()` non può avere `null` come destinazione; usare `resetLeaf()` per lo stato precedente alla prima voce radice.\n- `branchWithSummary()` supporta la destinazione `null` e registra `fromId: \"root\"`.\n- La selezione della foglia corrente nel selettore ad albero è un'operazione nulla.\n- Il riepilogo richiede un modello attivo; in sua assenza, la navigazione con riepilogo fallisce immediatamente.\n- Se il riepilogo viene interrotto, la navigazione viene annullata e la foglia rimane invariata.\n- Le sessioni in memoria non restituiscono mai un percorso del file di ramo da `createBranchedSession`.\n\n## Compatibilità con le versioni precedenti ancora presente\n\nLe migrazioni di sessione vengono ancora eseguite al caricamento:\n\n- v1→v2 aggiunge `id`/`parentId` e converte l'ancora dell'indice di compattazione in ancora id\n- v2→v3 migra il ruolo legacy `hookMessage` a `custom`\n\nIl comportamento a runtime corrente è basato sulla semantica ad albero versione 3 dopo la migrazione.\n",
	"it/sessions/session.md": "---\ntitle: Archiviazione delle sessioni e modello delle entry\ndescription: >-\n  Append-only session storage model with entry types, persistence, and migration\n  between formats.\nsidebar:\n  order: 1\n  label: Archiviazione e modello entry\ni18n:\n  sourceHash: 42fe17549e00\n  translator: machine\n---\n\n# Archiviazione delle sessioni e modello delle entry\n\nQuesto documento è la fonte di verità per il modo in cui le sessioni del coding-agent vengono rappresentate, persistite, migrate e ricostruite a runtime.\n\n## Ambito\n\nCopre:\n\n- Formato JSONL delle sessioni e versionamento\n- Tassonomia delle entry e semantica ad albero (`id`/`parentId` + puntatore foglia)\n- Comportamento di migrazione/compatibilità durante il caricamento di file vecchi o malformati\n- Ricostruzione del contesto (`buildSessionContext`)\n- Garanzie di persistenza, comportamento in caso di errore, troncamento/esternalizzazione blob\n- Astrazioni di archiviazione (`FileSessionStorage`, `MemorySessionStorage`) e utility correlate\n\nNon copre il comportamento di rendering dell'interfaccia `/tree` oltre alle semantiche che influenzano i dati di sessione.\n\n## File di implementazione\n\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`src/session/messages.ts`](../../packages/coding-agent/src/session/messages.ts)\n- [`src/session/session-storage.ts`](../../packages/coding-agent/src/session/session-storage.ts)\n- [`src/session/history-storage.ts`](../../packages/coding-agent/src/session/history-storage.ts)\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts)\n\n## Layout su disco\n\nPosizione predefinita del file di sessione:\n\n```text\n~/.xcsh/agent/sessions/--<cwd-encoded>--/<timestamp>_<sessionId>.jsonl\n```\n\n`<cwd-encoded>` è derivato dalla directory di lavoro rimuovendo lo slash iniziale e sostituendo `/`, `\\\\` e `:` con `-`.\n\nPosizione del blob store:\n\n```text\n~/.xcsh/agent/blobs/<sha256>\n```\n\nI file breadcrumb del terminale vengono scritti in:\n\n```text\n~/.xcsh/agent/terminal-sessions/<terminal-id>\n```\n\nIl contenuto del breadcrumb è composto da due righe: la cwd originale, seguita dal percorso del file di sessione. `continueRecent()` preferisce questo puntatore con scope sul terminale prima di cercare il file con l'mtime più recente.\n\n## Formato del file\n\nI file di sessione sono JSONL: un oggetto JSON per riga.\n\n- La riga 1 è sempre l'header di sessione (`type: \"session\"`).\n- Le righe rimanenti sono valori `SessionEntry`.\n- Le entry sono append-only a runtime; la navigazione tra branch sposta un puntatore (`leafId`) anziché modificare le entry esistenti.\n\n### Header (`SessionHeader`)\n\n```json\n{\n  \"type\": \"session\",\n  \"version\": 3,\n  \"id\": \"1f9d2a6b9c0d1234\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\",\n  \"cwd\": \"/work/pi\",\n  \"title\": \"optional session title\",\n  \"parentSession\": \"optional lineage marker\"\n}\n```\n\nNote:\n\n- `version` è opzionale nei file v1; l'assenza indica v1.\n- `parentSession` è una stringa opaca di lineage. Il codice attuale scrive un id di sessione o un percorso di sessione a seconda del flusso (`fork`, `forkFrom`, `createBranchedSession`, o `newSession({ parentSession })` esplicito). Va trattato come metadato, non come una foreign key tipizzata.\n\n### Base delle entry (`SessionEntryBase`)\n\nTutte le entry non-header includono:\n\n```json\n{\n  \"type\": \"...\",\n  \"id\": \"8-char-id\",\n  \"parentId\": \"previous-or-branch-parent\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\"\n}\n```\n\n`parentId` può essere `null` per una entry radice (primo append, o dopo `resetLeaf()`).\n\n## Tassonomia delle entry\n\n`SessionEntry` è l'unione di:\n\n- `message`\n- `thinking_level_change`\n- `model_change`\n- `compaction`\n- `branch_summary`\n- `custom`\n- `custom_message`\n- `label`\n- `ttsr_injection`\n- `session_init`\n- `mode_change`\n\n### `message`\n\nMemorizza un `AgentMessage` direttamente.\n\n```json\n{\n  \"type\": \"message\",\n  \"id\": \"a1b2c3d4\",\n  \"parentId\": null,\n  \"timestamp\": \"2026-02-16T10:21:00.000Z\",\n  \"message\": {\n    \"role\": \"assistant\",\n    \"provider\": \"anthropic\",\n    \"model\": \"claude-sonnet-4-5\",\n    \"content\": [{ \"type\": \"text\", \"text\": \"Done.\" }],\n    \"usage\": { \"input\": 100, \"output\": 20, \"cacheRead\": 0, \"cacheWrite\": 0, \"cost\": { \"input\": 0, \"output\": 0, \"cacheRead\": 0, \"cacheWrite\": 0, \"total\": 0 } },\n    \"timestamp\": 1760000000000\n  }\n}\n```\n\n### `model_change`\n\n```json\n{\n  \"type\": \"model_change\",\n  \"id\": \"b1c2d3e4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:21:30.000Z\",\n  \"model\": \"openai/gpt-4o\",\n  \"role\": \"default\"\n}\n```\n\n`role` è opzionale; se mancante viene trattato come `default` nella ricostruzione del contesto.\n\n### `thinking_level_change`\n\n```json\n{\n  \"type\": \"thinking_level_change\",\n  \"id\": \"c1d2e3f4\",\n  \"parentId\": \"b1c2d3e4\",\n  \"timestamp\": \"2026-02-16T10:22:00.000Z\",\n  \"thinkingLevel\": \"high\"\n}\n```\n\n### `compaction`\n\n```json\n{\n  \"type\": \"compaction\",\n  \"id\": \"d1e2f3a4\",\n  \"parentId\": \"c1d2e3f4\",\n  \"timestamp\": \"2026-02-16T10:23:00.000Z\",\n  \"summary\": \"Conversation summary\",\n  \"shortSummary\": \"Short recap\",\n  \"firstKeptEntryId\": \"a1b2c3d4\",\n  \"tokensBefore\": 42000,\n  \"details\": { \"readFiles\": [\"src/a.ts\"] },\n  \"preserveData\": { \"hookState\": true },\n  \"fromExtension\": false\n}\n```\n\n### `branch_summary`\n\n```json\n{\n  \"type\": \"branch_summary\",\n  \"id\": \"e1f2a3b4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:24:00.000Z\",\n  \"fromId\": \"a1b2c3d4\",\n  \"summary\": \"Summary of abandoned path\",\n  \"details\": { \"note\": \"optional\" },\n  \"fromExtension\": true\n}\n```\n\nSe si effettua il branch dalla radice (`branchFromId === null`), `fromId` è la stringa letterale `\"root\"`.\n\n### `custom`\n\nPersistenza dello stato delle estensioni; ignorato da `buildSessionContext`.\n\n```json\n{\n  \"type\": \"custom\",\n  \"id\": \"f1a2b3c4\",\n  \"parentId\": \"e1f2a3b4\",\n  \"timestamp\": \"2026-02-16T10:25:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"data\": { \"state\": 1 }\n}\n```\n\n### `custom_message`\n\nMessaggio fornito da un'estensione che partecipa al contesto LLM.\n\n```json\n{\n  \"type\": \"custom_message\",\n  \"id\": \"a2b3c4d5\",\n  \"parentId\": \"f1a2b3c4\",\n  \"timestamp\": \"2026-02-16T10:26:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"content\": \"Injected context\",\n  \"display\": true,\n  \"details\": { \"debug\": false }\n}\n```\n\n### `label`\n\n```json\n{\n  \"type\": \"label\",\n  \"id\": \"b2c3d4e5\",\n  \"parentId\": \"a2b3c4d5\",\n  \"timestamp\": \"2026-02-16T10:27:00.000Z\",\n  \"targetId\": \"a1b2c3d4\",\n  \"label\": \"checkpoint\"\n}\n```\n\n`label: undefined` cancella un'etichetta per `targetId`.\n\n### `ttsr_injection`\n\n```json\n{\n  \"type\": \"ttsr_injection\",\n  \"id\": \"c2d3e4f5\",\n  \"parentId\": \"b2c3d4e5\",\n  \"timestamp\": \"2026-02-16T10:28:00.000Z\",\n  \"injectedRules\": [\"ruleA\", \"ruleB\"]\n}\n```\n\n### `session_init`\n\n```json\n{\n  \"type\": \"session_init\",\n  \"id\": \"d2e3f4a5\",\n  \"parentId\": \"c2d3e4f5\",\n  \"timestamp\": \"2026-02-16T10:29:00.000Z\",\n  \"systemPrompt\": \"...\",\n  \"task\": \"...\",\n  \"tools\": [\"read\", \"edit\"],\n  \"outputSchema\": { \"type\": \"object\" }\n}\n```\n\n### `mode_change`\n\n```json\n{\n  \"type\": \"mode_change\",\n  \"id\": \"e2f3a4b5\",\n  \"parentId\": \"d2e3f4a5\",\n  \"timestamp\": \"2026-02-16T10:30:00.000Z\",\n  \"mode\": \"plan\",\n  \"data\": { \"planFile\": \"/tmp/plan.md\" }\n}\n```\n\n## Versionamento e migrazione\n\nVersione corrente della sessione: `3`.\n\n### v1 -> v2\n\nApplicata quando la `version` dell'header è assente o `< 2`:\n\n- Aggiunge `id` e `parentId` a ogni entry non-header.\n- Ricostruisce una catena di parent lineare usando l'ordine del file.\n- Migra il campo di compaction `firstKeptEntryIndex` -> `firstKeptEntryId` quando presente.\n- Imposta `version = 2` nell'header.\n\n### v2 -> v3\n\nApplicata quando `version` dell'header `< 3`:\n\n- Per le entry `message`: riscrive il legacy `message.role === \"hookMessage\"` in `\"custom\"`.\n- Imposta `version = 3` nell'header.\n\n### Trigger di migrazione e persistenza\n\n- Le migrazioni vengono eseguite durante il caricamento della sessione (`setSessionFile`).\n- Se una qualsiasi migrazione è stata eseguita, l'intero file viene riscritto su disco immediatamente.\n- La migrazione modifica le entry in memoria prima, poi persiste il JSONL riscritto.\n\n## Comportamento di caricamento e compatibilità\n\nComportamento di `loadEntriesFromFile(path)`:\n\n- File mancante (`ENOENT`) -> restituisce `[]`.\n- Le righe non analizzabili sono gestite dal parser JSONL lenient (`parseJsonlLenient`).\n- Se la prima entry analizzata non è un header di sessione valido (`type !== \"session\"` o `id` stringa mancante) -> restituisce `[]`.\n\nComportamento di `SessionManager.setSessionFile()`:\n\n- `[]` dal loader è trattato come sessione vuota/inesistente e viene sostituito con un nuovo file di sessione inizializzato in quel percorso.\n- I file validi vengono caricati, migrati se necessario, i riferimenti blob risolti, quindi indicizzati.\n\n## Semantica dell'albero e della foglia\n\nIl modello sottostante è un albero append-only + puntatore foglia mutabile:\n\n- Ogni metodo di append crea esattamente una nuova entry il cui `parentId` è il `leafId` corrente.\n- La nuova entry diventa il nuovo `leafId`.\n- `branch(entryId)` sposta solo il `leafId`; le entry esistenti rimangono invariate.\n- `resetLeaf()` imposta `leafId = null`; il prossimo append crea una nuova entry radice (`parentId: null`).\n- `branchWithSummary()` imposta la foglia sul target del branch e aggiunge una entry `branch_summary`.\n\n`getEntries()` restituisce tutte le entry non-header in ordine di inserimento. Le entry esistenti non vengono eliminate durante il funzionamento normale; le riscritture preservano la cronologia logica aggiornando la rappresentazione (migrazioni, spostamenti, helper di riscrittura mirata).\n\n## Ricostruzione del contesto (`buildSessionContext`)\n\n`buildSessionContext(entries, leafId, byId?)` risolve ciò che viene inviato al modello.\n\nAlgoritmo:\n\n1. Determinare la foglia:\n   - `leafId === null` -> restituisce contesto vuoto.\n   - `leafId` esplicito -> usa quella entry se trovata.\n   - altrimenti fallback all'ultima entry.\n2. Percorrere la catena `parentId` dalla foglia alla radice e invertire in percorso radice->foglia.\n3. Derivare lo stato runtime lungo il percorso:\n   - `thinkingLevel` dall'ultimo `thinking_level_change` (predefinito `\"off\"`)\n   - mappa dei modelli dalle entry `model_change` (`role ?? \"default\"`)\n   - fallback `models.default` dal provider/modello del messaggio assistente se nessun cambio modello esplicito\n   - `injectedTtsrRules` deduplicati da tutte le entry `ttsr_injection`\n   - mode/modeData dall'ultimo `mode_change` (modalità predefinita `\"none\"`)\n4. Costruire la lista dei messaggi:\n   - le entry `message` passano direttamente\n   - le entry `custom_message` diventano `AgentMessages` `custom` tramite `createCustomMessage`\n   - le entry `branch_summary` diventano `AgentMessages` `branchSummary` tramite `createBranchSummaryMessage`\n   - se esiste un `compaction` nel percorso:\n     - emettere prima il sommario di compaction (`createCompactionSummaryMessage`)\n     - emettere le entry del percorso a partire da `firstKeptEntryId` fino al confine di compaction\n     - emettere le entry dopo il confine di compaction\n\nLe entry `custom` e `session_init` non iniettano contesto nel modello direttamente.\n\n## Garanzie di persistenza e modello di errore\n\n### Persistenza vs in-memory\n\n- `SessionManager.create/open/continueRecent/forkFrom` -> modalità persistente (`persist = true`).\n- `SessionManager.inMemory` -> modalità non persistente (`persist = false`) con `MemorySessionStorage`.\n\n### Pipeline di scrittura\n\nLe scritture sono serializzate attraverso una catena di promise interna (`#persistChain`) e `NdjsonFileWriter`.\n\n- `append*` aggiorna lo stato in memoria immediatamente.\n- La persistenza è differita fino a quando non esiste almeno un messaggio assistente.\n  - Prima del primo assistente: le entry sono mantenute in memoria; nessun append su file avviene.\n  - Quando esiste il primo assistente: l'intera sessione in memoria viene scaricata su file.\n  - Successivamente: le nuove entry vengono aggiunte in modo incrementale.\n\nMotivazione nel codice: evitare di persistere sessioni che non hanno mai prodotto una risposta dell'assistente.\n\n### Operazioni di durabilità\n\n- `flush()` scarica il writer e chiama `fsync()`.\n- Le riscritture atomiche complete (`#rewriteFile`) scrivono su un file temporaneo, flush+fsync, chiudono, poi rinominano sopra il target.\n- Usate per migrazioni, `setSessionName`, `rewriteEntries`, operazioni di spostamento e riscritture degli argomenti delle tool-call.\n\n### Comportamento in caso di errore\n\n- Gli errori di persistenza vengono memorizzati (`#persistError`) e rilanciati nelle operazioni successive.\n- Il primo errore viene registrato una sola volta con il contesto del file di sessione.\n- La chiusura del writer è best-effort ma propaga il primo errore significativo.\n\n## Controlli sulla dimensione dei dati e esternalizzazione blob\n\nPrima di persistere le entry:\n\n- Le stringhe di grandi dimensioni vengono troncate a `MAX_PERSIST_CHARS` (500.000 caratteri) con avviso:\n  - `\"[Session persistence truncated large content]\"`\n- I campi transitori `partialJson` e `jsonlEvents` vengono rimossi.\n- Se l'oggetto ha sia `content` che `lineCount`, il conteggio delle righe viene ricalcolato dopo il troncamento.\n- I blocchi immagine negli array `content` con lunghezza base64 >= 1024 vengono esternalizzati in riferimenti blob:\n  - memorizzati come `blob:sha256:<hash>`\n  - i byte grezzi vengono scritti nel blob store (`BlobStore.put`)\n\nAl caricamento, i riferimenti blob vengono risolti nuovamente in base64 per i blocchi immagine di message/custom_message.\n\n## Astrazioni di archiviazione\n\nL'interfaccia `SessionStorage` fornisce tutte le operazioni del filesystem utilizzate da `SessionManager`:\n\n- sync: `ensureDirSync`, `existsSync`, `writeTextSync`, `statSync`, `listFilesSync`\n- async: `exists`, `readText`, `readTextPrefix`, `writeText`, `rename`, `unlink`, `openWriter`\n\nImplementazioni:\n\n- `FileSessionStorage`: filesystem reale (Bun + node fs)\n- `MemorySessionStorage`: implementazione in-memory basata su map per test/sessioni non persistenti\n\n`SessionStorageWriter` espone `writeLine`, `flush`, `fsync`, `close`, `getError`.\n\n## Utility di scoperta delle sessioni\n\nDefinite in `session-manager.ts`:\n\n- `getRecentSessions(sessionDir, limit)` -> metadati leggeri per UI/selettore sessione\n- `findMostRecentSession(sessionDir)` -> la più recente per mtime\n- `list(cwd, sessionDir?)` -> sessioni in uno scope di progetto\n- `listAll()` -> sessioni in tutti gli scope di progetto sotto `~/.xcsh/agent/sessions`\n\nL'estrazione dei metadati legge solo un prefisso (`readTextPrefix(..., 4096)`) quando possibile.\n\n## Correlato ma distinto: archiviazione della cronologia dei prompt\n\n`HistoryStorage` (`history-storage.ts`) è un sottosistema SQLite separato per il richiamo/ricerca dei prompt, non per il replay delle sessioni.\n\n- DB: `~/.xcsh/agent/history.db`\n- Tabella: `history(id, prompt, created_at, cwd)`\n- Indice FTS5: `history_fts` con sincronizzazione mantenuta da trigger\n- Deduplica i prompt identici consecutivi usando una cache in-memory dell'ultimo prompt\n- Inserimento asincrono (`setImmediate`) in modo che la cattura del prompt non blocchi l'esecuzione del turno\n\nUsare i file di sessione per il replay del grafo/stato della conversazione; usare `HistoryStorage` per la UX della cronologia dei prompt.\n",
	"it/sessions/ttsr-injection-lifecycle.md": "---\ntitle: Ciclo di vita dell'iniezione TTSR\ndescription: >-\n  Ciclo di vita dell'iniezione TTSR (tool-use, tool-result, system-reminder) per\n  la gestione del contesto.\nsidebar:\n  order: 9\n  label: Iniezione TTSR\ni18n:\n  sourceHash: d6179a286584\n  translator: machine\n---\n\n# Ciclo di vita dell'iniezione TTSR\n\nQuesto documento descrive l'attuale percorso runtime delle Time Traveling Stream Rules (TTSR), dalla scoperta delle regole all'interruzione dello stream, all'iniezione di retry, alle notifiche delle estensioni e alla gestione dello stato della sessione.\n\n## File di implementazione\n\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/export/ttsr.ts`](../../packages/coding-agent/src/export/ttsr.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/prompts/system/ttsr-interrupt.md`](../../packages/coding-agent/src/prompts/system/ttsr-interrupt.md)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/types.ts`](../../packages/coding-agent/src/extensibility/extensions/types.ts)\n- [`../src/extensibility/hooks/types.ts`](../../packages/coding-agent/src/extensibility/hooks/types.ts)\n- [`../src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n\n## 1. Feed di scoperta e registrazione delle regole\n\nAlla creazione della sessione, `createAgentSession()` carica tutte le regole scoperte e costruisce un `TtsrManager`:\n\n```ts\nconst ttsrSettings = settings.getGroup(\"ttsr\");\nconst ttsrManager = new TtsrManager(ttsrSettings);\nconst rulesResult = await loadCapability<Rule>(ruleCapability.id, { cwd });\nfor (const rule of rulesResult.items) {\n  if (rule.ttsrTrigger) ttsrManager.addRule(rule);\n}\n```\n\n### Comportamento di deduplicazione pre-registrazione\n\n`loadCapability(\"rules\")` deduplica per `rule.name` con semantica first-wins (priorità del provider più alta per prima). I duplicati oscurati vengono rimossi prima della registrazione TTSR.\n\n### Comportamento di `TtsrManager.addRule()`\n\nLa registrazione viene saltata quando:\n\n- `rule.ttsrTrigger` è assente\n- una regola con lo stesso `rule.name` era già stata registrata in questo manager\n- la regex non riesce a compilarsi (`new RegExp(rule.ttsrTrigger)` genera un'eccezione)\n\nI trigger con regex non valide vengono registrati come warning e ignorati; l'avvio della sessione prosegue.\n\n### Avvertenza sulle impostazioni\n\n`TtsrSettings.enabled` viene caricato nel manager ma attualmente non viene verificato nel gating runtime. Se esistono regole, il matching viene comunque eseguito.\n\n## 2. Ciclo di vita del monitor dello streaming\n\nIl rilevamento TTSR viene eseguito all'interno di `AgentSession.#handleAgentEvent`.\n\n### Inizio del turno\n\nAl `turn_start`, il buffer dello stream viene resettato:\n\n- `ttsrManager.resetBuffer()`\n\n### Durante lo stream (`message_update`)\n\nQuando arrivano aggiornamenti dall'assistente e le regole esistono:\n\n- monitora `text_delta` e `toolcall_delta`\n- accoda il delta nel buffer del manager\n- chiama `check(buffer)`\n\n`check()` itera le regole registrate e restituisce tutte le regole corrispondenti che superano la politica di ripetizione (`#canTrigger`).\n\n## 3. Decisione di trigger e percorso di abort immediato\n\nQuando una o più regole corrispondono:\n\n1. `markInjected(matches)` registra i nomi delle regole nello stato di iniezione del manager.\n2. le regole corrispondenti vengono accodate in `#pendingTtsrInjections`.\n3. `#ttsrAbortPending = true`.\n4. `agent.abort()` viene chiamato immediatamente.\n5. l'evento `ttsr_triggered` viene emesso in modo asincrono (fire-and-forget).\n6. il lavoro di retry viene pianificato tramite `setTimeout(..., 50)`.\n\nL'abort non è bloccato sui callback delle estensioni.\n\n## 4. Pianificazione del retry, modalità contesto e iniezione del promemoria\n\nDopo il timeout di 50ms:\n\n1. `#ttsrAbortPending = false`\n2. legge `ttsrManager.getSettings().contextMode`\n3. se `contextMode === \"discard\"`, scarta l'output parziale dell'assistente con `agent.popMessage()`\n4. costruisce il contenuto dell'iniezione dalle regole in attesa usando il template `ttsr-interrupt.md`\n5. aggiunge un messaggio utente sintetico contenente un blocco `<system-interrupt ...>` per ogni regola\n6. chiama `agent.continue()` per riprovare la generazione\n\nIl payload del template è:\n\n```xml\n<system-interrupt reason=\"rule_violation\" rule=\"{{name}}\" path=\"{{path}}\">\n...\n{{content}}\n</system-interrupt>\n```\n\nLe iniezioni in attesa vengono svuotate dopo la generazione del contenuto.\n\n### Comportamento di `contextMode` sull'output parziale\n\n- `discard`: il messaggio parziale/abortito dell'assistente viene rimosso prima del retry.\n- `keep`: l'output parziale dell'assistente rimane nello stato della conversazione; il promemoria viene aggiunto dopo di esso.\n\n## 5. Politica di ripetizione e logica di gap\n\n`TtsrManager` tiene traccia di `#messageCount` e di `lastInjectedAt` per ogni regola.\n\n### `repeatMode: \"once\"`\n\nUna regola può essere attivata solo una volta dopo che ha un record di iniezione.\n\n### `repeatMode: \"after-gap\"`\n\nUna regola può essere riattivata solo quando:\n\n- `messageCount - lastInjectedAt >= repeatGap`\n\n`messageCount` viene incrementato al `turn_end`, quindi il gap viene misurato in turni completati, non in chunk dello stream.\n\n## 6. Emissione degli eventi e superfici di estensione/hook\n\n### Evento di sessione\n\n`AgentSessionEvent` include:\n\n```ts\n{ type: \"ttsr_triggered\"; rules: Rule[] }\n```\n\n### Runner delle estensioni\n\n`#emitSessionEvent()` instrada l'evento verso:\n\n- listener delle estensioni (`ExtensionRunner.emit({ type: \"ttsr_triggered\", rules })`)\n- sottoscrittori locali della sessione\n\n### Tipizzazione di hook e custom-tool\n\n- l'API delle estensioni espone `on(\"ttsr_triggered\", ...)`\n- l'API degli hook espone `on(\"ttsr_triggered\", ...)`\n- i custom tool ricevono `onSession({ reason: \"ttsr_triggered\", rules })`\n\n### Differenza di rendering nella modalità interattiva\n\nLa modalità interattiva utilizza `session.isTtsrAbortPending` per sopprimere la visualizzazione del motivo di arresto dell'assistente abortito come errore visibile durante l'interruzione TTSR, e renderizza un `TtsrNotificationComponent` quando l'evento arriva.\n\n## 7. Persistenza e stato di ripristino (implementazione attuale)\n\n`SessionManager` ha pieno supporto dello schema per la persistenza delle regole iniettate:\n\n- tipo di entry: `ttsr_injection`\n- API di aggiunta: `appendTtsrInjection(ruleNames)`\n- API di query: `getInjectedTtsrRules()`\n- la ricostruzione del contesto include `SessionContext.injectedTtsrRules`\n\n`TtsrManager` supporta anche il ripristino tramite `restoreInjected(ruleNames)`.\n\n### Stato attuale del collegamento\n\nNel percorso runtime attuale:\n\n- `AgentSession` non aggiunge entry `ttsr_injection` quando il TTSR si attiva.\n- `createAgentSession()` non ripristina `existingSession.injectedTtsrRules` nel `ttsrManager`.\n\nEffetto netto: la soppressione delle regole iniettate viene applicata in memoria per il processo in esecuzione, ma attualmente non viene persistita/ripristinata attraverso il ricaricamento/ripristino della sessione tramite questo percorso.\n\n## 8. Limiti di race condition e garanzie di ordinamento\n\n### Abort vs callback di retry\n\n- l'abort è sincrono dal punto di vista del gestore TTSR (`agent.abort()` viene chiamato immediatamente)\n- il retry è differito tramite timer (`50ms`)\n- la notifica alle estensioni è asincrona e intenzionalmente non attesa prima della pianificazione di abort/retry\n\n### Match multipli nella stessa finestra di stream\n\n`check()` restituisce tutte le regole idonee attualmente corrispondenti. Vengono iniettate come batch nel successivo messaggio di retry.\n\n### Tra abort e continue\n\nDurante la finestra del timer, lo stato può cambiare (interruzione dell'utente, azioni di modalità, eventi aggiuntivi). La chiamata di retry è best-effort: `agent.continue().catch(() => {})` sopprime gli errori successivi.\n\n## 9. Riepilogo dei casi limite\n\n- Regex `ttsr_trigger` non valida: saltata con warning; le altre regole continuano.\n- Nomi di regole duplicati a livello di capability: i duplicati con priorità inferiore vengono oscurati prima della registrazione.\n- Nomi duplicati a livello di manager: la seconda registrazione viene ignorata.\n- `contextMode: \"keep\"`: l'output parziale in violazione può rimanere nel contesto prima del retry con promemoria.\n- Il repeat-after-gap dipende dagli incrementi del contatore di turni al `turn_end`; i chunk a metà turno non avanzano i contatori di gap.\n",
	"it/tui/theme.md": "---\ntitle: Riferimento al tema\ndescription: >-\n  Riferimento al tema TUI con token colore, impostazioni dei font e\n  personalizzazione del tema.\nsidebar:\n  order: 3\n  label: Tema\ni18n:\n  sourceHash: 7e962a7da157\n  translator: machine\n---\n\n# Riferimento al tema\n\nQuesto documento descrive il funzionamento del sistema di temi nell'agente di codifica: schema, caricamento, comportamento a runtime e modalità di errore.\n\n## Cosa controlla il sistema di temi\n\nIl sistema di temi gestisce:\n\n- token colore per primo piano/sfondo utilizzati nell'intera TUI\n- adattatori di stile markdown (`getMarkdownTheme()`)\n- adattatori per selettore/editor/elenco impostazioni (`getSelectListTheme()`, `getEditorTheme()`, `getSettingsListTheme()`)\n- preset di simboli + override dei simboli (`unicode`, `nerd`, `ascii`)\n- colori di evidenziazione della sintassi utilizzati dall'evidenziatore nativo (`@f5-sales-demo/pi-natives`)\n- colori dei segmenti della barra di stato\n\nImplementazione principale: `src/modes/theme/theme.ts`.\n\n## Struttura JSON del tema\n\nI file di tema sono oggetti JSON validati rispetto allo schema runtime in `theme.ts` (`ThemeJsonSchema`) e rispecchiati da `src/modes/theme/theme-schema.json`.\n\nCampi di primo livello:\n\n- `name` (obbligatorio)\n- `colors` (obbligatorio; tutti i token colore sono obbligatori)\n- `vars` (facoltativo; variabili colore riutilizzabili)\n- `export` (facoltativo; colori per l'esportazione HTML)\n- `symbols` (facoltativo)\n  - `preset` (facoltativo: `unicode | nerd | ascii`)\n  - `overrides` (facoltativo: override chiave/valore per `SymbolKey`)\n\nI valori colore accettano:\n\n- stringa esadecimale (`\"#RRGGBB\"`)\n- indice a 256 colori (`0..255`)\n- stringa di riferimento a variabile (risolta tramite `vars`)\n- stringa vuota (`\"\"`) che indica il valore predefinito del terminale (`\\x1b[39m` fg, `\\x1b[49m` bg)\n\n## Token colore obbligatori (correnti)\n\nTutti i token riportati di seguito sono obbligatori in `colors`.\n\n### Testo e bordi principali (11)\n\n`accent`, `border`, `borderAccent`, `borderMuted`, `success`, `error`, `warning`, `muted`, `dim`, `text`, `thinkingText`\n\n### Blocchi di sfondo (7)\n\n`selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`, `statusLineBg`\n\n### Testo messaggi/strumenti (5)\n\n`userMessageText`, `customMessageText`, `customMessageLabel`, `toolTitle`, `toolOutput`\n\n### Markdown (10)\n\n`mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet`\n\n### Diff strumenti + evidenziazione sintassi (12)\n\n`toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext`,\n`syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation`\n\n### Bordi modalità/pensiero (8)\n\n`thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh`, `bashMode`, `pythonMode`\n\n### Colori dei segmenti della barra di stato (14)\n\n`statusLineSep`, `statusLineModel`, `statusLinePath`, `statusLineGitClean`, `statusLineGitDirty`, `statusLineContext`, `statusLineSpend`, `statusLineStaged`, `statusLineDirty`, `statusLineUntracked`, `statusLineOutput`, `statusLineCost`, `statusLineSubagents`\n\n## Token facoltativi\n\n### Sezione `export` (facoltativa)\n\nUtilizzata per gli helper di tema dell'esportazione HTML:\n\n- `export.pageBg`\n- `export.cardBg`\n- `export.infoBg`\n\nSe omessa, il codice di esportazione ricava i valori predefiniti dai colori del tema risolti.\n\n### Sezione `symbols` (facoltativa)\n\n- `symbols.preset` imposta un set di simboli predefinito a livello di tema.\n- `symbols.overrides` può sovrascrivere singoli valori `SymbolKey`.\n\nPrecedenza a runtime:\n\n1. override `symbolPreset` nelle impostazioni (se impostato)\n2. `symbols.preset` nel JSON del tema\n3. fallback `\"unicode\"`\n\nLe chiavi di override non valide vengono ignorate e registrate (`logger.debug`).\n\n## Sorgenti di temi predefiniti e personalizzati\n\nOrdine di ricerca del tema (`loadThemeJson`):\n\n1. temi predefiniti incorporati (`defaults/xcsh-dark.json` e `defaults/xcsh-light.json` compilati in `defaultThemes`)\n2. file di tema personalizzato: `<customThemesDir>/<name>.json`\n\nLa directory dei temi personalizzati proviene da `getCustomThemesDir()`:\n\n- predefinita: `~/.xcsh/agent/themes`\n- sovrascritta da `PI_CODING_AGENT_DIR` (`$PI_CODING_AGENT_DIR/themes`)\n\n`getAvailableThemes()` restituisce i nomi predefiniti + personalizzati uniti, ordinati, con i predefiniti che hanno la precedenza in caso di collisione di nomi.\n\n## Caricamento, validazione e risoluzione\n\nPer i file di tema personalizzati:\n\n1. lettura JSON\n2. analisi JSON\n3. validazione rispetto a `ThemeJsonSchema`\n4. risoluzione ricorsiva dei riferimenti `vars`\n5. conversione dei valori risolti in ANSI in base alla modalità di capacità del terminale\n\nComportamento di validazione:\n\n- token colore obbligatori mancanti: messaggio di errore raggruppato esplicito\n- tipi/valori di token errati: errori di validazione con percorso JSON\n- file di tema sconosciuto: `Theme not found: <name>`\n\nComportamento dei riferimenti a variabili:\n\n- supporta riferimenti annidati\n- genera un'eccezione per riferimenti a variabili mancanti\n- genera un'eccezione per riferimenti circolari\n\n## Comportamento della modalità colore del terminale\n\nRilevamento della modalità colore (`detectColorMode`):\n\n- `COLORTERM=truecolor|24bit` => truecolor\n- `WT_SESSION` => truecolor\n- `TERM` in `dumb`, `linux`, o vuoto => 256color\n- altrimenti => truecolor\n\nComportamento di conversione:\n\n- hex -> `Bun.color(..., \"ansi-16m\" | \"ansi-256\")`\n- numerico -> ANSI `38;5` / `48;5`\n- `\"\"` -> reset predefinito fg/bg\n\n## Comportamento del cambio tema a runtime\n\n### Tema iniziale (`initTheme`)\n\n`main.ts` inizializza il tema con le impostazioni:\n\n- `symbolPreset`\n- `colorBlindMode`\n- `theme.dark`\n- `theme.light`\n\nLa selezione automatica dello slot del tema utilizza il rilevamento dello sfondo `COLORFGBG`:\n\n- analisi dell'indice di sfondo da `COLORFGBG`\n- `< 8` => slot scuro (`theme.dark`)\n- `>= 8` => slot chiaro (`theme.light`)\n- errore di analisi => slot scuro\n\nValori predefiniti correnti dallo schema delle impostazioni:\n\n- `theme.dark = \"xcsh-dark\"`\n- `theme.light = \"xcsh-light\"`\n- `symbolPreset = \"unicode\"`\n- `colorBlindMode = false`\n\n### Cambio esplicito (`setTheme`)\n\n- carica il tema selezionato\n- aggiorna il singleton globale `theme`\n- avvia facoltativamente il watcher\n- attiva il callback `onThemeChange`\n\nIn caso di errore:\n\n- effettua il fallback al tema predefinito `dark`\n- restituisce `{ success: false, error }`\n\n### Cambio in anteprima (`previewTheme`)\n\n- applica un tema di anteprima temporaneo al `theme` globale\n- **non** modifica di per sé le impostazioni persistenti\n- restituisce successo/errore senza sostituzione di fallback\n\nL'interfaccia delle impostazioni utilizza questa funzione per l'anteprima in tempo reale e ripristina il tema precedente in caso di annullamento.\n\n## Watcher e ricaricamento live\n\nQuando il watcher è abilitato (`setTheme(..., true)` / inizializzazione interattiva):\n\n- monitora solo il percorso del file personalizzato `<customThemesDir>/<currentTheme>.json`\n- i temi predefiniti non vengono monitorati\n- evento `change` del file: tenta il ricaricamento (con debounce)\n- evento `rename`/eliminazione del file: effettua il fallback a `dark`, chiude il watcher\n\nLa modalità automatica installa anche un listener `SIGWINCH` e può rivalutare la mappatura degli slot scuro/chiaro quando lo stato del terminale cambia.\n\n## Comportamento della modalità daltonismo\n\n`colorBlindMode` modifica a runtime solo un token:\n\n- `toolDiffAdded` viene regolato in HSV (il verde viene spostato verso il blu)\n- la regolazione viene applicata solo quando il valore risolto è una stringa esadecimale\n\nGli altri token rimangono invariati.\n\n## Dove vengono persistite le impostazioni del tema\n\nLe impostazioni relative al tema vengono persistite da `Settings` nel file YAML di configurazione globale:\n\n- percorso: `<agentDir>/config.yml`\n- directory agente predefinita: `~/.xcsh/agent`\n- file predefinito effettivo: `~/.xcsh/agent/config.yml`\n\nChiavi persistite:\n\n- `theme.dark`\n- `theme.light`\n- `symbolPreset`\n- `colorBlindMode`\n\nEsiste una migrazione legacy: il vecchio `theme: \"name\"` flat viene migrato a `theme.dark` o `theme.light` annidati in base al rilevamento della luminanza.\n\n## Creazione di un tema personalizzato (pratica)\n\n1. Creare il file nella directory dei temi personalizzati, ad esempio `~/.xcsh/agent/themes/my-theme.json`.\n2. Includere `name`, `vars` facoltativo e **tutti i** token `colors` obbligatori.\n3. Includere facoltativamente `symbols` ed `export`.\n4. Selezionare il tema nelle Impostazioni (`Display -> Dark theme` o `Display -> Light theme`) a seconda dello slot automatico desiderato.\n\nStruttura minimale. Ogni chiave in `colors` è obbligatoria — il validatore runtime\n(`additionalProperties: false`) rifiuta sia le chiavi mancanti che quelle sconosciute.\nPer le implementazioni di riferimento fornite consultare\n[`packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json)\ne [`xcsh-light.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-light.json).\n\nLa barra di stato dispone di due sistemi di colori paralleli documentati nella issue #242:\n\n- Colori testo esadecimali (`statusLinePath`, `statusLineGitClean`, `statusLineGitDirty`,\n  `statusLineStaged`, `statusLineDirty`, `statusLineUntracked`) gestiscono il rendering non-powerline.\n- Indici della palette a 256 colori (`statusLine<Segment>Bg` / `statusLine<Segment>Fg`)\n  gestiscono i riempimenti dei segmenti powerline. Sono indipendenti dalle chiavi esadecimali di cui sopra —\n  entrambi devono essere impostati.\n\n```json\n{\n  \"name\": \"my-theme\",\n  \"vars\": {\n    \"accent\": \"#7aa2f7\",\n    \"muted\": 244\n  },\n  \"colors\": {\n    \"accent\": \"accent\",\n    \"chromeAccent\": \"accent\",\n    \"spinnerAccent\": \"accent\",\n    \"contentAccent\": \"muted\",\n    \"border\": \"#4c566a\",\n    \"borderAccent\": \"accent\",\n    \"borderMuted\": \"muted\",\n    \"success\": \"#9ece6a\",\n    \"error\": \"#f7768e\",\n    \"warning\": \"#e0af68\",\n    \"muted\": \"muted\",\n    \"dim\": 240,\n    \"gutterSuccess\": \"#7dcfff\",\n    \"gutterWarning\": \"#e0af68\",\n    \"text\": \"\",\n    \"thinkingText\": \"muted\",\n\n    \"selectedBg\": \"#2a2f45\",\n    \"userMessageBg\": \"#1f2335\",\n    \"userMessageText\": \"\",\n    \"customMessageBg\": \"#24283b\",\n    \"customMessageText\": \"\",\n    \"customMessageLabel\": \"accent\",\n    \"toolPendingBg\": \"#1f2335\",\n    \"toolSuccessBg\": \"#1f2d2a\",\n    \"toolErrorBg\": \"#2d1f2a\",\n    \"toolTitle\": \"\",\n    \"toolOutput\": \"muted\",\n\n    \"mdHeading\": \"accent\",\n    \"mdLink\": \"accent\",\n    \"mdLinkUrl\": \"muted\",\n    \"mdCode\": \"#c0caf5\",\n    \"mdCodeBlock\": \"#c0caf5\",\n    \"mdCodeBlockBorder\": \"muted\",\n    \"mdQuote\": \"muted\",\n    \"mdQuoteBorder\": \"muted\",\n    \"mdHr\": \"muted\",\n    \"mdListBullet\": \"accent\",\n\n    \"toolDiffAdded\": \"#9ece6a\",\n    \"toolDiffRemoved\": \"#f7768e\",\n    \"toolDiffContext\": \"muted\",\n\n    \"syntaxComment\": \"#565f89\",\n    \"syntaxKeyword\": \"#bb9af7\",\n    \"syntaxFunction\": \"#7aa2f7\",\n    \"syntaxVariable\": \"#c0caf5\",\n    \"syntaxString\": \"#9ece6a\",\n    \"syntaxNumber\": \"#ff9e64\",\n    \"syntaxType\": \"#2ac3de\",\n    \"syntaxOperator\": \"#89ddff\",\n    \"syntaxPunctuation\": \"#9aa5ce\",\n    \"syntaxControl\": \"#bb9af7\",\n\n    \"thinkingOff\": 240,\n    \"thinkingMinimal\": 244,\n    \"thinkingLow\": \"#7aa2f7\",\n    \"thinkingMedium\": \"#2ac3de\",\n    \"thinkingHigh\": \"#bb9af7\",\n    \"thinkingXhigh\": \"#f7768e\",\n\n    \"bashMode\": \"#2ac3de\",\n    \"pythonMode\": \"#bb9af7\",\n\n    \"statusLineBg\": \"#16161e\",\n    \"statusLineSep\": 240,\n    \"statusLineModel\": \"#bb9af7\",\n    \"statusLinePath\": \"#7aa2f7\",\n    \"statusLineGitClean\": \"#9ece6a\",\n    \"statusLineGitDirty\": \"#e0af68\",\n    \"statusLineContext\": \"#2ac3de\",\n    \"statusLineSpend\": \"#7dcfff\",\n    \"statusLineStaged\": \"#9ece6a\",\n    \"statusLineDirty\": \"#e0af68\",\n    \"statusLineUntracked\": \"#f7768e\",\n    \"statusLineOutput\": \"#c0caf5\",\n    \"statusLineCost\": \"#ff9e64\",\n    \"statusLineSubagents\": \"#bb9af7\",\n\n    \"statusLineOsIconBg\": 7,\n    \"statusLineOsIconFg\": 232,\n    \"statusLinePathBg\": 4,\n    \"statusLinePathFg\": 254,\n    \"statusLineGitCleanBg\": 2,\n    \"statusLineGitCleanFg\": 0,\n    \"statusLineGitDirtyBg\": 3,\n    \"statusLineGitDirtyFg\": 0,\n    \"statusLineGitStagedBg\": 64,\n    \"statusLineGitStagedFg\": 0,\n    \"statusLineGitUntrackedBg\": 39,\n    \"statusLineGitUntrackedFg\": 0,\n    \"statusLineGitConflictBg\": 1,\n    \"statusLineGitConflictFg\": 7,\n    \"statusLinePlanModeBg\": 236,\n    \"statusLinePlanModeFg\": 117,\n    \"statusLineProfileXcshBg\": \"accent\",\n    \"statusLineProfileXcshFg\": 231\n  }\n}\n```\n\n## Test dei temi personalizzati\n\nUtilizzare questo flusso di lavoro:\n\n1. Avviare la modalità interattiva (watcher abilitato all'avvio).\n2. Aprire le impostazioni e visualizzare in anteprima i valori del tema (live `previewTheme`).\n3. Per i file di tema personalizzati, modificare il JSON durante l'esecuzione e verificare il ricaricamento automatico al salvataggio.\n4. Verificare le superfici critiche:\n   - rendering markdown\n   - blocchi strumenti (pending/success/error)\n   - rendering diff (added/removed/context)\n   - leggibilità della barra di stato\n   - cambiamenti dei bordi al livello di pensiero\n   - colori dei bordi in modalità bash/python\n5. Validare entrambi i preset di simboli se il tema dipende dalla larghezza/aspetto dei glifi.\n\n## Vincoli reali e avvertenze\n\n- Tutti i token `colors` sono obbligatori per i temi personalizzati.\n- `export` e `symbols` sono facoltativi.\n- `$schema` nel JSON del tema è informativo; la validazione runtime è applicata dallo schema TypeBox compilato nel codice.\n- Il fallback di `setTheme` in caso di errore è `dark`; il fallback di `previewTheme` in caso di errore non sostituisce il tema corrente.\n- Gli errori di ricaricamento del watcher mantengono il tema attualmente caricato fino a quando non viene eseguito con successo un ricaricamento o viene attivato un percorso di fallback.\n",
	"it/tui/tree.md": "---\ntitle: Riferimento al comando Tree\ndescription: >-\n  Riferimento al comando /tree per visualizzare la cronologia delle sessioni e i\n  rami delle conversazioni.\nsidebar:\n  order: 4\n  label: Comando /tree\ni18n:\n  sourceHash: ee0e412fe993\n  translator: machine\n---\n\n# Riferimento al comando `/tree`\n\n`/tree` apre il navigatore interattivo **Session Tree**. Consente di passare a qualsiasi voce nel file di sessione corrente e di continuare da quel punto.\n\nSi tratta di uno spostamento a foglia nel file, non di un'esportazione in una nuova sessione.\n\n## Cosa fa `/tree`\n\n- Costruisce un albero dalle voci della sessione corrente (`SessionManager.getTree()`)\n- Apre `TreeSelectorComponent` con navigazione da tastiera, filtri e ricerca\n- Alla selezione, chiama `AgentSession.navigateTree(targetId, { summarize, customInstructions })`\n- Ricostruisce la chat visibile dal nuovo percorso foglia\n- Opzionalmente precompila il testo dell'editor quando si seleziona un messaggio utente/personalizzato\n\nImplementazione principale:\n\n- `src/modes/controllers/input-controller.ts` (cablaggio di `/tree`, tasti di scelta rapida, comportamento doppio escape)\n- `src/modes/controllers/selector-controller.ts` (avvio dell'interfaccia ad albero + flusso di richiesta sommario)\n- `src/modes/components/tree-selector.ts` (navigazione, filtri, ricerca, etichette, rendering)\n- `src/session/agent-session.ts` (commutazione di foglia con `navigateTree` + sommario opzionale)\n- `src/session/session-manager.ts` (`getTree`, `branch`, `branchWithSummary`, `resetLeaf`, persistenza etichette)\n\n## Come aprirlo\n\nOgnuno dei seguenti comandi apre lo stesso selettore:\n\n- `/tree`\n- azione tasto di scelta rapida configurata `tree`\n- doppio escape su editor vuoto quando `doubleEscapeAction = \"tree\"` (predefinito)\n- `/branch` quando `doubleEscapeAction = \"tree\"` (reindirizza al selettore ad albero invece che al selettore di rami solo-utente)\n\n## Modello di interfaccia utente ad albero\n\nL'albero viene renderizzato dai puntatori ai genitori delle voci di sessione (`id` / `parentId`).\n\n- I figli sono ordinati per timestamp in modo crescente (i più vecchi prima, i più recenti in basso)\n- Il ramo attivo (percorso dalla radice alla foglia corrente) è contrassegnato con un punto elenco\n- Le etichette (se presenti) vengono visualizzate come `[etichetta]` prima del testo del nodo\n- Se esistono più radici (catene di genitori orfane/interrotte), vengono mostrate sotto una radice ramificata virtuale\n\n```text\nExample tree view (active path marked with •):\n\n├─ user: \"Start task\"\n│  └─ assistant: \"Plan\"\n│     ├─ • user: \"Try approach A\"\n│     │  └─ • assistant: \"A result\"\n│     │     └─ • [milestone] user: \"Continue A\"\n│     └─ user: \"Try approach B\"\n│        └─ assistant: \"B result\"\n```\n\nIl selettore si ricentra attorno alla selezione corrente e mostra fino a:\n\n- `max(5, floor(terminalHeight / 2))` righe\n\n## Tasti di scelta rapida nel selettore ad albero\n\n- `Su` / `Giù`: sposta la selezione (con avvolgimento)\n- `Sinistra` / `Destra`: pagina su / pagina giù\n- `Invio`: seleziona nodo\n- `Esc`: cancella la ricerca se attiva; altrimenti chiude il selettore\n- `Ctrl+C`: chiude il selettore\n- `Digita`: aggiunge alla query di ricerca\n- `Backspace`: elimina un carattere dalla ricerca\n- `Shift+L`: modifica/cancella l'etichetta sulla voce selezionata\n- `Ctrl+O`: cicla il filtro in avanti\n- `Shift+Ctrl+O`: cicla il filtro all'indietro\n- `Alt+D/T/U/L/A`: passa direttamente a una specifica modalità filtro\n\n## Filtri e semantica della ricerca\n\nModalità filtro (`TreeList`):\n\n1. `default`\n2. `no-tools`\n3. `user-only`\n4. `labeled-only`\n5. `all`\n\n### `default`\n\nMostra la maggior parte dei nodi conversazionali, ma nasconde i tipi di voci di contabilità:\n\n- `label`\n- `custom`\n- `model_change`\n- `thinking_level_change`\n\n### `no-tools`\n\nUguale a `default`, ma nasconde anche i messaggi `toolResult`.\n\n### `user-only`\n\nSolo voci `message` con ruolo `user`.\n\n### `labeled-only`\n\nSolo voci che attualmente risolvono a un'etichetta.\n\n### `all`\n\nTutto nell'albero della sessione, incluse le voci di contabilità/personalizzate.\n\n### Comportamento dei nodi assistente solo-strumenti\n\nI messaggi dell'assistente che contengono **solo chiamate agli strumenti** (nessun testo) sono nascosti per impostazione predefinita in tutte le viste filtrate a meno che:\n\n- il messaggio sia in errore/interrotto (`stopReason` diverso da `stop`/`toolUse`), oppure\n- sia la foglia corrente (sempre mantenuta visibile)\n\n### Comportamento della ricerca\n\n- La query viene tokenizzata per spazi\n- La corrispondenza non distingue maiuscole/minuscole\n- Tutti i token devono corrispondere (semantica AND)\n- Il testo ricercabile include etichetta, ruolo e contenuto specifico per tipo (testo del messaggio, testo del sommario del ramo, tipo personalizzato, frammenti di comandi degli strumenti, ecc.)\n\n## Esiti della selezione (importante)\n\n`navigateTree` calcola il nuovo comportamento della foglia dal tipo di voce selezionata:\n\n### Selezione di un messaggio `user`\n\n- La nuova foglia diventa il `parentId` della voce selezionata\n- Se il genitore è `null` (messaggio utente radice), la foglia viene reimpostata alla radice (`resetLeaf()`)\n- Il testo del messaggio selezionato viene copiato nell'editor per la modifica/reinvio\n\n### Selezione di un `custom_message`\n\n- Stessa regola di foglia dei messaggi utente (`parentId`)\n- Il contenuto testuale viene estratto e copiato nell'editor\n\n### Selezione di un nodo non-utente (assistente/strumento/sommario/compattazione/contabilità personalizzata/ecc.)\n\n- La nuova foglia diventa l'id del nodo selezionato\n- L'editor non viene precompilato\n\n### Selezione della foglia corrente\n\n- Nessuna operazione; il selettore si chiude con \"Already at this point\"\n\n```text\nSelection decision (simplified):\n\nselected node\n   │\n   ├─ is current leaf? ── yes ──> close selector (no-op)\n   │\n   ├─ is user/custom_message? ── yes ──> leaf := parentId (or resetLeaf for root)\n   │                                     + prefill editor text\n   │\n   └─ otherwise ──> leaf := selected node id\n                    + no editor prefill\n```\n\n## Flusso sommario alla commutazione\n\nIl prompt del sommario è controllato da `branchSummary.enabled` (predefinito: `false`).\n\nQuando abilitato, dopo aver selezionato un nodo l'interfaccia chiede:\n\n- `No summary`\n- `Summarize`\n- `Summarize with custom prompt`\n\nDettagli del flusso:\n\n- Escape nel prompt del sommario riapre il selettore ad albero\n- La cancellazione del prompt personalizzato ritorna al ciclo di scelta del sommario\n- Durante la riepilogazione, l'interfaccia mostra un indicatore di caricamento e associa `Esc` a `abortBranchSummary()`\n- Se la riepilogazione viene interrotta, il selettore ad albero si riapre e nessuno spostamento viene applicato\n\nFunzionamento interno di `navigateTree`:\n\n- Raccoglie le voci del ramo abbandonato dalla vecchia foglia all'antenato comune\n- Emette `session_before_tree` (le estensioni possono annullare o iniettare un sommario)\n- Usa il riepilogatore predefinito solo se richiesto e necessario\n- Applica lo spostamento con:\n  - `branchWithSummary(...)` quando esiste un sommario\n  - `branch(newLeafId)` per uno spostamento non-radice senza sommario\n  - `resetLeaf()` per uno spostamento radice senza sommario\n- Sostituisce la conversazione dell'agente con il contesto della sessione ricostruito\n- Emette `session_tree`\n\nNota: se l'utente richiede un sommario ma non c'è nulla da riepilogare, la navigazione procede senza creare una voce di sommario.\n\n## Etichette\n\nLe modifiche alle etichette nell'interfaccia ad albero chiamano `appendLabelChange(targetId, label)`.\n\n- un'etichetta non vuota imposta/aggiorna l'etichetta risolta\n- un'etichetta vuota la cancella\n- le etichette sono archiviate come voci `label` in sola aggiunta\n- i nodi dell'albero mostrano lo stato dell'etichetta risolta, non la cronologia grezza delle voci di etichetta\n\n## `/tree` vs operazioni adiacenti\n\n| Operazione | Ambito | Risultato |\n|---|---|---|\n| `/tree` | File di sessione corrente | Sposta la foglia al punto selezionato (stesso file) |\n| `/branch` | Di solito file di sessione corrente -> nuovo file di sessione | Per impostazione predefinita crea un ramo dal messaggio **utente** selezionato in un nuovo file di sessione; se `doubleEscapeAction = \"tree\"`, `/branch` apre l'interfaccia di navigazione ad albero |\n| `/fork` | Intera sessione corrente | Duplica la sessione in un nuovo file di sessione persistente |\n| `/resume` | Elenco sessioni | Passa a un altro file di sessione |\n\nDistinzione fondamentale: `/tree` è uno strumento di navigazione/riposizionamento all'interno di un file di sessione. `/branch`, `/fork` e `/resume` cambiano tutti il contesto del file di sessione.\n\n## Flussi di lavoro operatore\n\n### Rieseguire da un prompt utente precedente senza perdere il ramo corrente\n\n1. `/tree`\n2. cerca/seleziona il messaggio utente precedente\n3. scegli `No summary` (o riepilogare se necessario)\n4. modifica il testo precompilato nell'editor\n5. invia\n\nEffetto: un nuovo ramo cresce dal punto selezionato all'interno dello stesso file di sessione.\n\n### Lasciare il ramo corrente con un riferimento contestuale\n\n1. abilita `branchSummary.enabled`\n2. `/tree` e seleziona il nodo di destinazione\n3. scegli `Summarize` (o un prompt personalizzato)\n\nEffetto: una voce `branch_summary` viene aggiunta alla posizione di destinazione prima di continuare.\n\n### Ispezionare le voci di contabilità nascoste\n\n1. `/tree`\n2. premi `Alt+A` (all)\n3. cerca `model`, `thinking`, `custom`, o le etichette\n\nEffetto: ispeziona la cronologia interna completa, non solo i nodi conversazionali.\n\n### Aggiungere segnalibri ai punti cardine per salti successivi\n\n1. `/tree`\n2. spostati sulla voce\n3. `Shift+L` e imposta l'etichetta\n4. in seguito usa `Alt+L` (`labeled-only`) per navigare rapidamente\n\nEffetto: navigazione rapida tra punti di riferimento duraturi del ramo.\n",
	"it/tui/tui-runtime-internals.md": "---\ntitle: Componenti interni del runtime TUI\ndescription: >-\n  Componenti interni del runtime dell'interfaccia utente terminale, con\n  copertura della pipeline di rendering, gestione degli input e gestione dello\n  stato.\nsidebar:\n  order: 2\n  label: Componenti interni del runtime\ni18n:\n  sourceHash: cc8f7dcce46a\n  translator: machine\n---\n\n# Componenti interni del runtime TUI\n\nQuesto documento mappa il percorso del runtime non-tema dall'input del terminale all'output renderizzato in modalità interattiva. Si concentra sul comportamento in `packages/tui` e sulla sua integrazione dai controller di `packages/coding-agent`.\n\n## Livelli del runtime e responsabilità\n\n- **Engine `packages/tui`**: ciclo di vita del terminale, normalizzazione stdin, instradamento del focus, pianificazione del rendering, pittura differenziale, composizione degli overlay, posizionamento hardware del cursore.\n- **Modalità interattiva di `packages/coding-agent`**: costruisce l'albero dei componenti, associa callback dell'editor e keymaps, reagisce agli eventi agente/sessione e traduce lo stato del dominio (streaming, esecuzione degli strumenti, tentativi, modalità piano) in componenti UI.\n\nRegola di confine: l'engine TUI è agnostico rispetto ai messaggi. Conosce solo `Component.render(width)`, `handleInput(data)`, il focus e gli overlay. La semantica dell'agente rimane nei controller interattivi.\n\n## File di implementazione\n\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/components/custom-editor.ts`](../../packages/coding-agent/src/modes/components/custom-editor.ts)\n- [`../../tui/src/tui.ts`](../../packages/tui/src/tui.ts)\n- [`../../tui/src/terminal.ts`](../../packages/tui/src/terminal.ts)\n- [`../../tui/src/editor-component.ts`](../../packages/tui/src/editor-component.ts)\n- [`../../tui/src/stdin-buffer.ts`](../../packages/tui/src/stdin-buffer.ts)\n- [`../../tui/src/components/loader.ts`](../../packages/tui/src/components/loader.ts)\n\n## Avvio e assemblaggio dell'albero dei componenti\n\n`InteractiveMode` costruisce `TUI(new ProcessTerminal(), showHardwareCursor)` e crea contenitori persistenti:\n\n- `chatContainer`\n- `pendingMessagesContainer`\n- `statusContainer`\n- `todoContainer`\n- `statusLine`\n- `editorContainer` (contiene `CustomEditor`)\n\n`init()` collega l'albero in quest'ordine, mette a fuoco l'editor, registra i gestori di input tramite `InputController`, avvia TUI e richiede un rendering forzato.\n\nUn rendering forzato (`requestRender(true)`) azzera le cache delle righe precedenti e la contabilità del cursore prima di ridipingere.\n\n## Ciclo di vita del terminale e normalizzazione stdin\n\n`ProcessTerminal.start()`:\n\n1. Abilita la modalità raw e il paste tra parentesi.\n2. Associa il gestore di ridimensionamento.\n3. Crea uno `StdinBuffer` per suddividere i blocchi di escape parziali in sequenze complete.\n4. Interroga il supporto del protocollo tastiera Kitty (`CSI ? u`), quindi abilita i flag di protocollo se supportati.\n5. Su Windows, tenta l'abilitazione dell'input VT tramite i flag di modalità `kernel32`.\n\nComportamento di `StdinBuffer`:\n\n- Bufferizza le sequenze di escape frammentate (CSI/OSC/DCS/APC/SS3).\n- Emette `data` solo quando una sequenza è completa o viene svuotata per timeout.\n- Rileva il paste tra parentesi ed emette un evento `paste` con il testo incollato grezzo.\n\nCiò impedisce che i blocchi di escape parziali vengano interpretati erroneamente come normali pressioni di tasti.\n\n## Instradamento dell'input e modello di focus\n\nPercorso dell'input:\n\n`stdin -> ProcessTerminal -> StdinBuffer -> TUI.#handleInput -> focusedComponent.handleInput`\n\nDettagli dell'instradamento:\n\n1. TUI esegue prima i listener di input registrati (`addInputListener`), consentendo comportamenti di consumo/trasformazione.\n2. TUI gestisce la scorciatoia di debug globale (`shift+ctrl+d`) prima del dispatch al componente.\n3. Se il componente con focus appartiene a un overlay ora nascosto/invisibile, TUI riassegna il focus al prossimo overlay visibile o al focus pre-overlay salvato.\n4. Gli eventi di rilascio tasto vengono filtrati a meno che il componente con focus non imposti `wantsKeyRelease = true`.\n5. Dopo il dispatch, TUI pianifica il rendering.\n\n`setFocus()` alterna anche `Focusable.focused`, che controlla se i componenti emettono `CURSOR_MARKER` per il posizionamento hardware del cursore.\n\n## Suddivisione della gestione dei tasti: editor vs controller\n\n`CustomEditor` intercetta prima le combinazioni ad alta priorità (escape, ctrl-c/d/z, ctrl-v, varianti ctrl-p, ctrl-t, alt-su, tasti personalizzati delle estensioni) e delega il resto al comportamento base di `Editor` (modifica del testo, cronologia, completamento automatico, movimento del cursore).\n\n`InputController.setupKeyHandlers()` associa quindi i callback dell'editor alle azioni di modalità:\n\n- annullamento / uscita dalla modalità con `Escape`\n- arresto con doppio `Ctrl+C` o `Ctrl+D` con editor vuoto\n- sospensione/ripresa con `Ctrl+Z`\n- hotkey di comandi slash e selettori\n- attivazione/disattivazione dei toggle follow-up/dequeue e toggle di espansione\n\nQuesto mantiene l'analisi dei tasti/meccaniche dell'editor in `packages/tui` e la semantica della modalità nei controller di coding-agent.\n\n## Loop di rendering e strategia di diffing\n\n`TUI.requestRender()` è de-rimbalzato a un rendering per tick usando `process.nextTick`. Più cambiamenti di stato nello stesso turno si fondono insieme.\n\nPipeline `#doRender()`:\n\n1. Renderizza l'albero dei componenti radice in `newLines`.\n2. Compone gli overlay visibili (se presenti).\n3. Estrae e rimuove `CURSOR_MARKER` dalle righe del viewport visibile.\n4. Aggiunge suffissi di reset del segmento per le righe non-immagine.\n5. Sceglie tra ridipintura completa e patch differenziale:\n   - primo frame\n   - cambio di larghezza\n   - riduzione con `clearOnShrink` abilitato e nessun overlay\n   - modifiche sopra il viewport precedente\n6. Per gli aggiornamenti differenziali, corregge solo l'intervallo di righe modificate e cancella le righe finali obsolete quando necessario.\n7. Riposiziona il cursore hardware per il supporto IME.\n\nLe scritture di rendering utilizzano la modalità di output sincronizzato (`CSI ? 2026 h/l`) per ridurre lo sfarfallio/tearing.\n\n## Vincoli di sicurezza del rendering\n\nControlli di sicurezza critici in `TUI`:\n\n- Le righe renderizzate non-immagine non devono superare la larghezza del terminale; l'overflow genera un'eccezione e scrive diagnostiche di crash.\n- La composizione degli overlay include troncamento difensivo e verifica della larghezza post-composizione.\n- I cambiamenti di larghezza forzano un ridisegno completo perché la semantica del wrapping cambia.\n- La posizione del cursore viene limitata prima del movimento.\n\nQuesti vincoli sono applicazioni a runtime, non semplici convenzioni.\n\n## Gestione del ridimensionamento\n\nGli eventi di ridimensionamento sono guidati dagli eventi da `ProcessTerminal` a `TUI.requestRender()`.\n\nEffetti:\n\n- Qualsiasi cambio di larghezza attiva un ridisegno completo.\n- Il tracciamento del viewport/top (`#previousViewportTop`, `#maxLinesRendered`) evita calcoli relativi del cursore non validi quando il contenuto o le dimensioni del terminale cambiano.\n- La visibilità degli overlay può dipendere dalle dimensioni del terminale (`OverlayOptions.visible`); il focus viene corretto quando gli overlay diventano non visibili dopo il ridimensionamento.\n\n## Aggiornamenti UI in streaming e incrementali\n\n`EventController` si iscrive a `AgentSessionEvent` e aggiorna l'UI in modo incrementale:\n\n- `agent_start`: avvia il loader in `statusContainer`.\n- `message_start` assistant: crea `streamingComponent` e lo monta.\n- `message_update`: aggiorna il contenuto dell'assistant in streaming; crea/aggiorna i componenti di esecuzione degli strumenti man mano che appaiono le chiamate agli strumenti.\n- `tool_execution_update/end`: aggiorna i componenti del risultato degli strumenti e lo stato di completamento.\n- `message_end`: finalizza lo stream dell'assistant, gestisce le annotazioni di interruzione/errore, contrassegna gli argomenti degli strumenti in sospeso come completi all'arresto normale.\n- `agent_end`: arresta i loader, cancella lo stato di stream transitorio, svuota il cambio di modello differito, emette la notifica di completamento se in background.\n\nIl raggruppamento degli strumenti di lettura è intenzionalmente stateful (`#lastReadGroup`) per unire chiamate consecutive agli strumenti di lettura in un unico blocco visivo fino a quando non si verifica un'interruzione non-read.\n\n## Orchestrazione di stato e loader\n\nProprietà della corsia di stato:\n\n- `statusContainer` contiene i loader transitori (`loadingAnimation`, `autoCompactionLoader`, `retryLoader`).\n- `statusLine` renderizza gli indicatori persistenti di stato/hook/piano e guida gli aggiornamenti del bordo superiore dell'editor.\n\nComportamento del loader:\n\n- `Loader` si aggiorna ogni 80ms tramite intervallo e richiede il rendering a ogni frame.\n- I gestori Escape vengono temporaneamente sovrascritti durante la compattazione automatica e il tentativo automatico per annullare tali operazioni.\n- Nei percorsi di fine/annullamento, i controller ripristinano i gestori escape precedenti e arrestano/cancellano i componenti loader.\n\n## Transizioni di modalità e background\n\n### Modalità di input Bash/Python\n\nI prefissi del testo di input attivano i flag di modalità del bordo dell'editor:\n\n- `!` -> modalità bash\n- `$` (prefisso non-template literal) -> modalità python\n\nEscape esce dalla modalità inattiva cancellando il testo dell'editor e ripristinando il colore del bordo; quando l'esecuzione è attiva, escape interrompe invece l'attività in corso.\n\n### Modalità piano\n\n`InteractiveMode` tiene traccia dei flag della modalità piano, dello stato della riga di stato, degli strumenti attivi e del cambio di modello. L'entrata/uscita aggiorna le voci della modalità sessione e lo stato UI/stato, incluso il cambio di modello differito se lo streaming è attivo.\n\n### Sospensione/ripresa (`Ctrl+Z`)\n\n`InputController.handleCtrlZ()`:\n\n1. Registra un gestore `SIGCONT` one-shot per riavviare TUI e forzare il rendering.\n2. Arresta TUI prima della sospensione.\n3. Invia `SIGTSTP` al gruppo di processi.\n\n### Modalità background (`/background` o `/bg`)\n\n`handleBackgroundCommand()`:\n\n- Rifiuta quando inattivo.\n- Cambia il contesto UI degli strumenti a non-interattivo (`hasUI=false`) in modo che gli strumenti UI interattivi falliscano rapidamente.\n- Arresta i loader/la riga di stato e annulla l'iscrizione al gestore di eventi in primo piano.\n- Si iscrive al gestore di eventi in background (principalmente in attesa di `agent_end`).\n- Arresta TUI e invia `SIGTSTP` (percorso di controllo lavori POSIX).\n\nAll'evento `agent_end` in background senza lavoro in coda, il controller invia la notifica di completamento e si arresta.\n\n## Percorsi di annullamento\n\nInput primari di annullamento:\n\n- `Escape` durante il loader di stream attivo: ripristina i messaggi in coda nell'editor e interrompe l'agente.\n- `Escape` durante l'esecuzione bash/python: interrompe il comando in esecuzione.\n- `Escape` durante la compattazione automatica/tentativo: richiama metodi di interruzione dedicati tramite gestori escape temporanei.\n- `Ctrl+C` pressione singola: cancella l'editor; doppia pressione entro 500ms: arresto.\n\nL'annullamento è condizionato dallo stato; la stessa chiave può significare interruzione, uscita dalla modalità, attivazione del selettore o nessuna operazione a seconda dello stato a runtime.\n\n## Comportamento guidato dagli eventi vs a limitazione di frequenza\n\nAggiornamenti guidati dagli eventi:\n\n- Eventi della sessione agente (`EventController`)\n- Callback di input dei tasti (`InputController`)\n- Callback di ridimensionamento del terminale\n- Osservatori di tema/branch in `InteractiveMode`\n\nPercorsi a limitazione di frequenza/de-rimbalzo:\n\n- Il rendering TUI è de-rimbalzato per tick (fusione di `requestRender`).\n- L'animazione del loader è a intervallo fisso (80ms), con ogni frame che richiede il rendering.\n- Gli aggiornamenti di completamento automatico dell'editor (all'interno di `Editor`) utilizzano timer de-rimbalzo, riducendo il ricalcolo durante la digitazione.\n\nIl runtime combina quindi transizioni di stato guidate dagli eventi con una cadenza di rendering limitata per mantenere la reattività dell'interazione senza tempeste di ridipintura.\n",
	"it/tui/tui.md": "---\ntitle: Integrazione TUI per estensioni e strumenti personalizzati\ndescription: >-\n  Contratto di integrazione TUI per estensioni, strumenti personalizzati e\n  renderer personalizzati.\nsidebar:\n  order: 1\n  label: Integrazione estensioni\ni18n:\n  sourceHash: 47f8f2b2045e\n  translator: machine\n---\n\n# Integrazione TUI per estensioni e strumenti personalizzati\n\nQuesto documento tratta il contratto TUI **attuale** utilizzato da `packages/coding-agent` e `packages/tui` per l'interfaccia utente delle estensioni, l'interfaccia utente degli strumenti personalizzati e i renderer personalizzati.\n\n## Cos'è questo sottosistema\n\nIl runtime ha due livelli:\n\n- **Motore di rendering (`packages/tui`)**: renderer terminale differenziale, dispatch degli input, focus, overlay, posizionamento del cursore.\n- **Livello di integrazione (`packages/coding-agent`)**: monta i componenti di estensioni/strumenti personalizzati, collega le associazioni di tasti/tema e ripristina lo stato dell'editor.\n\n## Comportamento del runtime per modalità\n\n| Modalità | Disponibilità di `ctx.ui.custom(...)` | Note |\n| --- | --- | --- |\n| TUI interattiva | Supportata | Il componente viene montato nell'area dell'editor, riceve il focus e deve chiamare `done(result)` per risolvere. |\n| Background/headless | Non interattiva | Il contesto UI è no-op (`hasUI === false`). |\n| Modalità RPC | Non supportata | `custom()` restituisce `Promise<never>` e non monta componenti TUI. |\n\nSe la vostra estensione/strumento può funzionare in modalità non interattiva, verificate con `ctx.hasUI` / `pi.hasUI`.\n\n## Contratto del componente principale (`@f5-sales-demo/pi-tui`)\n\n`packages/tui/src/tui.ts` definisce:\n\n```ts\nexport interface Component {\n  render(width: number): string[];\n  handleInput?(data: string): void;\n  wantsKeyRelease?: boolean;\n  invalidate(): void;\n}\n```\n\n`Focusable` è separato:\n\n```ts\nexport interface Focusable {\n  focused: boolean;\n}\n```\n\nIl comportamento del cursore utilizza `CURSOR_MARKER` (non `getCursorPosition`). I componenti con focus emettono il marker nel testo renderizzato; `TUI` lo estrae e posiziona il cursore hardware.\n\n## Vincoli di rendering (sicurezza terminale)\n\nL'output di `render(width)` deve essere sicuro per il terminale:\n\n1. **Non superare mai `width` su nessuna riga**. Il renderer lancia un errore se una riga non-immagine eccede la larghezza.\n2. **Misurare la larghezza visiva**, non la lunghezza della stringa: utilizzare `visibleWidth()`.\n3. **Troncare/mandare a capo testo con consapevolezza ANSI** con `truncateToWidth()` / `wrapTextWithAnsi()`.\n4. **Sanitizzare tabulazioni/contenuto** da fonti esterne utilizzando `replaceTabs()` (e sanitizzatori di livello superiore nei percorsi di rendering di coding-agent).\n\nPattern minimale:\n\n```ts\nimport { replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\n\nrender(width: number): string[] {\n  return this.lines.map(line => truncateToWidth(replaceTabs(line), width));\n}\n```\n\n## Gestione degli input e associazioni di tasti\n\n### Corrispondenza diretta dei tasti\n\nUtilizzare `matchesKey(data, \"...\")` per i tasti di navigazione e le combinazioni.\n\n### Rispettare le associazioni di tasti dell'applicazione configurate dall'utente\n\nLe factory dell'interfaccia utente delle estensioni ricevono un `KeybindingsManager` (in modalità interattiva) così potete rispettare le azioni mappate invece di codificare i tasti direttamente:\n\n```ts\nif (keybindings.matches(data, \"interrupt\")) {\n  done(undefined);\n  return;\n}\n```\n\n### Eventi di rilascio/ripetizione dei tasti\n\nGli eventi di rilascio dei tasti vengono filtrati a meno che il vostro componente non imposti:\n\n```ts\nwantsKeyRelease = true;\n```\n\nQuindi utilizzate `isKeyRelease()` / `isKeyRepeat()` se necessario.\n\n## Focus, overlay e cursore\n\n- `TUI.setFocus(component)` indirizza l'input verso quel componente.\n- Le API per gli overlay esistono in `TUI` (`showOverlay`, `OverlayHandle`), ma il montaggio di `ctx.ui.custom` delle estensioni in modalità interattiva attualmente sostituisce direttamente l'area del componente editor.\n- L'opzione `custom(..., options?: { overlay?: boolean })` esiste nei tipi delle estensioni; il montaggio interattivo delle estensioni attualmente ignora questa opzione.\n\n## Punti di montaggio e contratti di ritorno\n\n## 1) Interfaccia utente delle estensioni (`ExtensionUIContext`)\n\nFirma attuale (`extensibility/extensions/types.ts`):\n\n```ts\ncustom<T>(\n  factory: (\n    tui: TUI,\n    theme: Theme,\n    keybindings: KeybindingsManager,\n    done: (result: T) => void,\n  ) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n  options?: { overlay?: boolean },\n): Promise<T>\n```\n\nComportamento in modalità interattiva (`extension-ui-controller.ts`):\n\n- Salva il testo dell'editor.\n- Sostituisce il componente editor con il vostro componente.\n- Assegna il focus al vostro componente.\n- Al `done(result)`: chiama `component.dispose?.()`, ripristina l'editor + il testo, assegna il focus all'editor, risolve la promise.\n\nQuindi `done(...)` è obbligatorio per il completamento.\n\n## 2) Contesto UI per hook/strumenti personalizzati (tipizzazione legacy)\n\n`HookUIContext.custom` è tipizzato come `(tui, theme, done)` nei tipi di hook/strumenti personalizzati.\nL'implementazione interattiva sottostante chiama le factory con `(tui, theme, keybindings, done)`. I consumatori JS possono utilizzare l'argomento aggiuntivo; la compatibilità a livello di tipo riflette ancora la firma legacy a 3 argomenti.\n\nGli strumenti personalizzati tipicamente utilizzano lo stesso punto di accesso UI tramite l'oggetto `pi.ui` nell'ambito della factory, quindi restituiscono il valore selezionato nel contenuto normale dello strumento:\n\n```ts\nasync execute(toolCallId, params, onUpdate, ctx, signal) {\n  if (!pi.hasUI) {\n    return { content: [{ type: \"text\", text: \"UI unavailable\" }] };\n  }\n\n  const picked = await pi.ui.custom<string | undefined>((tui, theme, done) => {\n    const component = new MyPickerComponent(done, signal);\n    return component;\n  });\n\n  return { content: [{ type: \"text\", text: picked ? `Picked: ${picked}` : \"Cancelled\" }] };\n}\n```\n\n## 3) Renderer personalizzati per chiamate/risultati degli strumenti\n\nGli strumenti personalizzati e gli strumenti delle estensioni possono restituire componenti da:\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\n`options` attualmente include:\n\n- `expanded: boolean`\n- `isPartial: boolean`\n- `spinnerFrame?: number`\n\nQuesti renderer vengono montati da `ToolExecutionComponent`.\n\n## Ciclo di vita e cancellazione\n\n- `dispose()` è opzionale a livello di tipo ma dovrebbe essere implementato quando si possiedono timer, sottoprocessi, watcher, socket o overlay.\n- `done(...)` dovrebbe essere chiamato esattamente una volta dal flusso del vostro componente.\n- Per interfacce utente a lunga esecuzione cancellabili, abbinate `CancellableLoader` con `AbortSignal` e chiamate `done(...)` da `onAbort`.\n\nEsempio di pattern di cancellazione:\n\n```ts\nconst loader = new CancellableLoader(tui, theme.fg(\"accent\"), theme.fg(\"muted\"), \"Working...\");\nloader.onAbort = () => done(undefined);\nvoid doWork(loader.signal).then(result => done(result));\nreturn loader;\n```\n\n## Esempio realistico di componente personalizzato (comando di estensione)\n\n```ts\nimport type { Component } from \"@f5-sales-demo/pi-tui\";\nimport { SelectList, matchesKey, replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\nimport { getSelectListTheme, type ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nclass Picker implements Component {\n  list: SelectList;\n  keybindings: any;\n  done: (value: string | undefined) => void;\n\n  constructor(\n    items: Array<{ value: string; label: string }>,\n    keybindings: any,\n    done: (value: string | undefined) => void,\n  ) {\n    this.list = new SelectList(items, 8, getSelectListTheme());\n    this.keybindings = keybindings;\n    this.done = done;\n    this.list.onSelect = item => this.done(item.value);\n    this.list.onCancel = () => this.done(undefined);\n  }\n\n  handleInput(data: string): void {\n    if (this.keybindings.matches(data, \"interrupt\")) {\n      this.done(undefined);\n      return;\n    }\n    this.list.handleInput(data);\n  }\n\n  render(width: number): string[] {\n    return this.list.render(width).map(line => truncateToWidth(replaceTabs(line), width));\n  }\n\n  invalidate(): void {\n    this.list.invalidate();\n  }\n}\n\nexport default function extension(pi: ExtensionAPI): void {\n  pi.registerCommand(\"pick-model\", {\n    description: \"Pick a model profile\",\n    handler: async (_args, ctx) => {\n      if (!ctx.hasUI) return;\n\n      const selected = await ctx.ui.custom<string | undefined>((tui, theme, keybindings, done) => {\n        const items = [\n          { value: \"fast\", label: theme.fg(\"accent\", \"Fast\") },\n          { value: \"balanced\", label: \"Balanced\" },\n          { value: \"quality\", label: \"Quality\" },\n        ];\n        return new Picker(items, keybindings, done);\n      });\n\n      if (selected) ctx.ui.notify(`Selected profile: ${selected}`, \"info\");\n    },\n  });\n}\n```\n\n## File di implementazione principali\n\n- `packages/tui/src/tui.ts` — `Component`, `Focusable`, marker del cursore, focus, overlay, dispatch degli input.\n- `packages/tui/src/utils.ts` — primitive per larghezza/troncamento/sanitizzazione.\n- `packages/tui/src/keys.ts` / `keybindings.ts` — parsing dei tasti e mappatura configurabile delle azioni.\n- `packages/coding-agent/src/modes/controllers/extension-ui-controller.ts` — montaggio/smontaggio interattivo per l'interfaccia utente di estensioni/hook/strumenti personalizzati.\n- `packages/coding-agent/src/extensibility/extensions/types.ts` — contratti per l'interfaccia utente e i renderer delle estensioni.\n- `packages/coding-agent/src/extensibility/hooks/types.ts` — contratto UI per gli hook (firma custom legacy).\n- `packages/coding-agent/src/extensibility/custom-tools/types.ts` — contratti di esecuzione/rendering per strumenti personalizzati.\n- `packages/coding-agent/src/modes/components/tool-execution.ts` — montaggio dei componenti `renderCall`/`renderResult` e opzioni per stato parziale.\n- `packages/coding-agent/src/tools/context.ts` — propagazione del contesto UI degli strumenti (`hasUI`, `ui`).\n",
	"ja/configuration/blob-artifact-architecture.md": "---\ntitle: Blob およびアーティファクトストレージアーキテクチャ\ndescription: セッションメディア、スクリーンショット、ツール出力のためのコンテンツアドレス指定可能な Blob ストアおよびアーティファクトレジストリ。\nsidebar:\n  order: 7\n  label: Blob & アーティファクトストレージ\ni18n:\n  sourceHash: 70d255f48d5b\n  translator: machine\n---\n\n# Blob およびアーティファクトストレージアーキテクチャ\n\nこのドキュメントでは、coding-agent がセッション JSONL の外部に大きな/バイナリペイロードをどのように格納するか、切り詰められたツール出力がどのように永続化されるか、そして内部 URL（`artifact://`、`agent://`）が格納されたデータにどのように解決されるかを説明します。\n\n## 2つのストレージシステムが存在する理由\n\nランタイムは異なるデータ形状に対して2つの異なる永続化メカニズムを使用します：\n\n- **コンテンツアドレス指定 Blob**（`blob:sha256:<hash>`）：永続化されたセッションエントリから大きな画像 base64 ペイロードを外部化するために使用される、グローバルなバイナリ指向ストレージ。\n- **セッションスコープのアーティファクト**（`<sessionFile-without-.jsonl>/` 配下のファイル）：完全なツール出力およびサブエージェント出力に使用される、セッションごとのテキストファイル。\n\nこれらは意図的に分離されています：\n\n- Blob ストレージはコンテンツハッシュによる重複排除と安定した参照を最適化し、\n- アーティファクトストレージはローカル ID による追記専用のセッションツーリングとヒューマン/ツールによる検索を最適化します。\n\n## ストレージ境界とディスク上のレイアウト\n\n## Blob ストア境界（グローバル）\n\n`SessionManager` は `BlobStore(getBlobsDir())` を構築するため、Blob ファイルは共有グローバル Blob ディレクトリに配置されます（セッションフォルダ内ではありません）。\n\nBlob ファイルの命名：\n\n- ファイルパス：`<blobsDir>/<sha256-hex>`\n- 拡張子なし\n- エントリに格納される参照文字列：`blob:sha256:<sha256-hex>`\n\n含意：\n\n- 異なるセッション間で同一のバイナリコンテンツは同じハッシュ/パスに解決される、\n- 書き込みはコンテンツレベルで冪等である、\n- Blob は個々のセッションファイルよりも長く存続できる。\n\n## アーティファクト境界（セッションローカル）\n\n`ArtifactManager` はセッションファイルパスからアーティファクトディレクトリを導出します：\n\n- セッションファイル：`.../<timestamp>_<sessionId>.jsonl`\n- アーティファクトディレクトリ：`.../<timestamp>_<sessionId>/`（`.jsonl` を除去）\n\nアーティファクトの種類はこのディレクトリを共有します：\n\n- 切り詰められたツール出力ファイル：`<numericId>.<toolType>.log`（`artifact://` 用）\n- サブエージェント出力ファイル：`<outputId>.md`（`agent://` 用）\n\n## ID と名前の割り当てスキーム\n\n## Blob ID：コンテンツハッシュ\n\n`BlobStore.put()` は生のバイナリバイトに対して SHA-256 を計算し、以下を返します：\n\n- `hash`：16進ダイジェスト、\n- `path`：`<blobsDir>/<hash>`、\n- `ref`：`blob:sha256:<hash>`。\n\nセッションローカルなカウンターは使用されません。\n\n## アーティファクト ID：セッションローカルな単調増加整数\n\n`ArtifactManager` は初回使用時に既存の `*.log` アーティファクトファイルをスキャンして最大の既存数値 ID を見つけ、`nextId = max + 1` を設定します。\n\n割り当て動作：\n\n- ファイル形式：`{id}.{toolType}.log`\n- ID は連番の文字列（`\"0\"`、`\"1\"`、...）\n- スキャンは割り当て前に行われるため、再開時に既存のアーティファクトを上書きしない。\n\nアーティファクトディレクトリが存在しない場合、スキャンは空のリストを返し、割り当ては `0` から開始されます。\n\n## エージェント出力 ID（`agent://`）\n\n`AgentOutputManager` はサブエージェント出力の ID を `<index>-<requestedId>` として割り当てます（オプションで親プレフィックスの下にネスト、例：`0-Parent.1-Child`）。初期化時に既存の `.md` ファイルをスキャンし、再開時に次のインデックスから継続します。\n\n## 永続化データフロー\n\n## 1) セッションエントリの永続化書き換えパス\n\nセッションエントリが書き込まれる前（`#rewriteFile` / インクリメンタル永続化）、`SessionManager` は `prepareEntryForPersistence()`（`truncateForPersistence` 経由）を呼び出します。\n\n主要な動作：\n\n1. **大きな文字列の切り詰め**：過大な文字列はカットされ、`\"[Session persistence truncated large content]\"` がサフィックスとして追加される。\n2. **一時フィールドの除去**：`partialJson` と `jsonlEvents` が永続化エントリから削除される。\n3. **画像の Blob への外部化**：\n   - `content` 配列内の画像ブロックにのみ適用、\n   - `data` がすでに Blob 参照でない場合のみ、\n   - base64 の長さが閾値以上の場合のみ（`BLOB_EXTERNALIZE_THRESHOLD = 1024`）、\n   - インライン base64 を `blob:sha256:<hash>` に置換。\n\nこれによりセッション JSONL をコンパクトに保ちつつ、復元可能性を維持します。\n\n## 2) セッションロードの再水和パス\n\nセッションを開く際（`setSessionFile`）、マイグレーション後に `SessionManager` は `resolveBlobRefsInEntries()` を実行します。\n\n`blob:sha256:<hash>` を持つ各メッセージ/カスタムメッセージの画像ブロックについて：\n\n- Blob ストアから Blob バイトを読み取り、\n- バイトを base64 に変換し直し、\n- ランタイムコンシューマー向けにインメモリエントリを base64 インラインに変更。\n\nBlob が存在しない場合：\n\n- `resolveImageData()` が警告をログ出力、\n- 元の参照文字列をそのまま返す、\n- ロードは継続（ハードクラッシュなし）。\n\n## 3) ツール出力のスピル/切り詰めパス\n\n`OutputSink` は bash/python/ssh および関連エグゼキュータのストリーミング出力を駆動します。\n\n動作：\n\n1. すべてのチャンクはサニタイズされ、インメモリのテールバッファに追加される。\n2. インメモリバイトがスピル閾値（`DEFAULT_MAX_BYTES`、50KB）を超えると、シンクは出力を切り詰め済みとしてマークする。\n3. アーティファクトパスが利用可能な場合、シンクはファイルライターを開き以下を書き込む：\n   - 既存のバッファ済みコンテンツを1回、\n   - 以降のすべてのチャンク。\n4. インメモリバッファは表示用に常にテールウィンドウにトリミングされる。\n5. `dump()` はファイルシンクが正常に作成された場合のみ `artifactId` を含むサマリーを返す。\n\n実際の効果：\n\n- UI/ツールの戻り値は切り詰められたテールを表示、\n- 完全な出力はアーティファクトファイルに保存され、`artifact://<id>` として参照される。\n\nファイルシンクの作成に失敗した場合（I/O エラー、パスの欠落など）、シンクはサイレントにインメモリ切り詰めのみにフォールバックし、完全な出力は永続化されません。\n\n## URL アクセスモデル\n\n## `blob:` 参照\n\n`blob:sha256:<hash>` は永続化されたセッションエントリペイロード内の永続化参照であり、ルーターが処理する内部 URL スキームではありません。解決はセッションロード時に `SessionManager` によって行われます。\n\n## `artifact://<id>`\n\n`ArtifactProtocolHandler` によって処理されます：\n\n- アクティブなセッションアーティファクトディレクトリが必要、\n- ID は数値でなければならない、\n- ファイル名プレフィックス `<id>.` のマッチングによって解決、\n- マッチした `.log` ファイルから生テキスト（`text/plain`）を返す、\n- 見つからない場合、利用可能なアーティファクト ID のリストを含むエラーを返す。\n\nディレクトリが存在しない場合の動作：\n\n- アーティファクトディレクトリが存在しない場合、`No artifacts directory found` をスローする。\n\n## `agent://<id>`\n\n`AgentProtocolHandler` が `<artifactsDir>/<id>.md` を処理します：\n\n- プレーン形式ではマークダウンテキストを返す、\n- `/path` または `?q=` 形式では JSON 抽出を実行、\n- パスとクエリの抽出は組み合わせて使用できない、\n- 抽出が要求された場合、ファイルコンテンツは JSON としてパースできなければならない。\n\nディレクトリが存在しない場合の動作：\n\n- `No artifacts directory found` をスローする。\n\n出力が存在しない場合の動作：\n\n- 既存の `.md` ファイルから利用可能な ID とともに `Not found: <id>` をスローする。\n\n読み取りツール統合：\n\n- `read` は非抽出型の内部 URL 読み取りに対して offset/limit ページネーションをサポートする、\n- `agent://` 抽出が使用される場合は `offset/limit` を拒否する。\n\n## 再開、フォーク、および移動のセマンティクス\n\n## 再開\n\n- `ArtifactManager` は最初の割り当て時に既存の `{id}.*.log` ファイルをスキャンし、番号付けを継続する。\n- `AgentOutputManager` は既存の `.md` 出力 ID をスキャンし、番号付けを継続する。\n- `SessionManager` はロード時に Blob 参照を base64 に再水和する。\n\n## フォーク\n\n`SessionManager.fork()` は新しいセッション ID と `parentSession` リンクを持つ新しいセッションファイルを作成し、旧/新のファイルパスを返します。アーティファクトのコピーは `AgentSession.fork()` によって処理されます：\n\n- 旧アーティファクトディレクトリから新アーティファクトディレクトリへの再帰コピーを試行、\n- 旧ディレクトリが存在しない場合は許容、\n- ENOENT 以外のコピーエラーは警告としてログ出力され、フォークは完了する。\n\nフォーク後の ID への影響：\n\n- コピーが成功した場合、新セッションのアーティファクトカウンターはコピーされた最大 ID の次から継続、\n- コピーが失敗/スキップされた場合、新セッションのアーティファクト ID は `0` から開始。\n\nフォーク後の Blob への影響：\n\n- Blob はグローバルでコンテンツアドレス指定されているため、Blob ディレクトリのコピーは不要。\n\n## 新しい cwd への移動\n\n`SessionManager.moveTo()` はセッションファイルとアーティファクトディレクトリの両方を新しいデフォルトセッションディレクトリにリネームし、後のステップが失敗した場合のロールバックロジックを備えています。これにより、セッションスコープを再配置しつつアーティファクトの同一性を保持します。\n\n## 障害処理とフォールバックパス\n\n| ケース | 動作 |\n| --- | --- |\n| 再水和時に Blob ファイルが存在しない | 警告を出し、`blob:sha256:` 参照文字列をインメモリに保持 |\n| `BlobStore.get` 経由の Blob 読み取り ENOENT | `null` を返す |\n| アーティファクトディレクトリが存在しない（`ArtifactManager.listFiles`） | 空のリストを返す（割り当てを最初から開始可能） |\n| アーティファクトディレクトリが存在しない（`artifact://` / `agent://`） | 明示的に `No artifacts directory found` をスロー |\n| アーティファクト ID が見つからない | 利用可能な ID リストとともにスロー |\n| OutputSink アーティファクトライターの初期化に失敗 | テールのみの切り詰めで継続（完全出力アーティファクトなし） |\n| セッションファイルなし（一部のタスクパス） | タスクツールがサブエージェント出力用に一時アーティファクトディレクトリにフォールバック |\n\n## バイナリ Blob 外部化とテキスト出力アーティファクト\n\n- **Blob 外部化**は、永続化されたセッションエントリコンテンツ内のバイナリ画像ペイロード用であり、JSONL 内のインライン base64 を安定したコンテンツ参照に置換します。\n- **アーティファクト**は実行出力およびサブエージェント出力用のプレーンテキストファイルであり、内部 URL を通じてセッションローカルな ID でアドレス指定可能です。\n\n2つのシステムは間接的にのみ交差し（両方ともセッション JSONL の肥大化を軽減）、ID、ライフタイム、および検索パスは異なります。\n\n## 実装ファイル\n\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts) — Blob 参照フォーマット、ハッシュ化、put/get、外部化/解決ヘルパー。\n- [`src/session/artifacts.ts`](../../packages/coding-agent/src/session/artifacts.ts) — セッションアーティファクトディレクトリモデルと数値アーティファクト ID 割り当て。\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — `OutputSink` の切り詰め/ファイルへのスピル動作とサマリーメタデータ。\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts) — 永続化変換、ロード時の Blob 再水和、セッションフォーク/移動のインタラクション。\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — インタラクティブフォーク時のアーティファクトディレクトリコピー。\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — ツールアーティファクトマネージャーのブートストラップとツールごとのアーティファクトパス割り当て。\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — `artifact://` リゾルバー。\n- [`src/internal-urls/agent-protocol.ts`](../../packages/coding-agent/src/internal-urls/agent-protocol.ts) — `agent://` リゾルバー + JSON 抽出。\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — 内部 URL ルーターのワイヤリングとアーティファクトディレクトリリゾルバー。\n- [`src/task/output-manager.ts`](../../packages/coding-agent/src/task/output-manager.ts) — `agent://` 用のセッションスコープのエージェント出力 ID 割り当て。\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — サブエージェント出力アーティファクトの書き込み（`<id>.md`）と一時アーティファクトディレクトリのフォールバック。\n",
	"ja/configuration/config-usage.md": "---\ntitle: 設定の探索と解決\ndescription: xcsh がプロジェクト、ユーザー、およびエンタープライズルートから設定を探索、解決、および階層化する方法。\nsidebar:\n  order: 1\n  label: 設定\ni18n:\n  sourceHash: e38bd9792499\n  translator: machine\n---\n\n# 設定の探索と解決\n\nこのドキュメントでは、coding-agent が現在どのように設定を解決しているかについて説明します。スキャンされるルート、優先順位の仕組み、そして解決された設定が settings、skills、hooks、tools、および extensions によってどのように使用されるかを記載しています。\n\n## スコープ\n\n主要な実装:\n\n- `src/config.ts`\n- `src/config/settings.ts`\n- `src/config/settings-schema.ts`\n- `src/discovery/builtin.ts`\n- `src/discovery/helpers.ts`\n\n主要な統合ポイント:\n\n- `src/capability/index.ts`\n- `src/discovery/index.ts`\n- `src/extensibility/skills.ts`\n- `src/extensibility/hooks/loader.ts`\n- `src/extensibility/custom-tools/loader.ts`\n- `src/extensibility/extensions/loader.ts`\n\n---\n\n## 解決フロー（視覚的表現）\n\n```text\n         Config roots (ordered)\n┌───────────────────────────────────────┐\n│ 1) ~/.xcsh/agent + <cwd>/.xcsh          │\n│ 2) ~/.claude   + <cwd>/.claude        │\n│ 3) ~/.codex    + <cwd>/.codex         │\n│ 4) ~/.gemini   + <cwd>/.gemini        │\n└───────────────────────────────────────┘\n                    │\n                    ▼\n        config.ts helper resolution\n  (getConfigDirs/findConfigFile/findNearest...)\n                    │\n                    ▼\n       capability providers enumerate items\n (native, claude, codex, gemini, agents, etc.)\n                    │\n                    ▼\n      priority sort + per-capability dedup\n                    │\n                    ▼\n          subsystem-specific consumption\n   (settings, skills, hooks, tools, extensions)\n```\n\n## 1) 設定ルートとソース順序\n\n## 正規ルート\n\n`src/config.ts` は固定のソース優先順位リストを定義しています:\n\n1. `.xcsh` (ネイティブ)\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nユーザーレベルのベース:\n\n- `~/.xcsh/agent`\n- `~/.claude`\n- `~/.codex`\n- `~/.gemini`\n\nプロジェクトレベルのベース:\n\n- `<cwd>/.xcsh`\n- `<cwd>/.claude`\n- `<cwd>/.codex`\n- `<cwd>/.gemini`\n\n`CONFIG_DIR_NAME` は `.xcsh` です（`packages/utils/src/dirs.ts`）。\n\n## 重要な制約\n\n`src/config.ts` の汎用ヘルパーは、ソース探索順序に `.pi` を含み**ません**。\n\n---\n\n## 2) コア探索ヘルパー（`src/config.ts`）\n\n## `getConfigDirs(subpath, options)`\n\n順序付きエントリを返します:\n\n- まずユーザーレベルのエントリ（ソース優先順位順）\n- 次にプロジェクトレベルのエントリ（同じソース優先順位順）\n\nオプション:\n\n- `user`（デフォルト `true`）\n- `project`（デフォルト `true`）\n- `cwd`（デフォルト `getProjectDir()`）\n- `existingOnly`（デフォルト `false`）\n\nこの API は、ディレクトリベースの設定検索（commands、hooks、tools、agents など）に使用されます。\n\n## `findConfigFile(subpath, options)` / `findConfigFileWithMeta(...)`\n\n順序付きベース全体で最初に存在するファイルを検索し、最初にマッチしたもの（パスのみ、またはパス+メタデータ）を返します。\n\n## `findAllNearestProjectConfigDirs(subpath, cwd)`\n\n親ディレクトリを上方向にたどり、**ソースベースごとに最も近い既存ディレクトリ**（`.xcsh`、`.claude`、`.codex`、`.gemini`）を返し、結果をソース優先順位でソートします。\n\nプロジェクト設定が祖先ディレクトリから継承されるべき場合（モノレポ/ネストされたワークスペースの動作）に使用します。\n\n---\n\n## 3) ファイル設定ラッパー（`src/config.ts` の `ConfigFile<T>`）\n\n`ConfigFile<T>` は、単一設定ファイルのスキーマ検証付きローダーです。\n\nサポートされるフォーマット:\n\n- `.yml` / `.yaml`\n- `.json` / `.jsonc`\n\n動作:\n\n- 提供された TypeBox スキーマに対して AJV でパースされたデータを検証します。\n- `invalidate()` が呼ばれるまでロード結果をキャッシュします。\n- `tryLoad()` 経由で三状態の結果を返します:\n  - `ok`\n  - `not-found`\n  - `error`（スキーマ/パースコンテキスト付きの `ConfigError`）\n\nレガシーマイグレーションは引き続きサポートされています:\n\n- ターゲットパスが `.yml`/`.yaml` の場合、兄弟の `.json` が一度だけ自動マイグレーションされます（`migrateJsonToYml`）。\n\n---\n\n## 4) Settings 解決モデル（`src/config/settings.ts`）\n\nランタイム設定モデルは階層化されています:\n\n1. グローバル設定: `~/.xcsh/agent/config.yml`\n2. プロジェクト設定: settings ケーパビリティ経由で探索（プロバイダーからの `settings.json`）\n3. ランタイムオーバーライド: インメモリ、非永続\n4. スキーマデフォルト: `SETTINGS_SCHEMA` から\n\n実効的な読み取りパス:\n\n`defaults <- global <- project <- overrides`\n\n書き込みの動作:\n\n- `settings.set(...)` は**グローバル**レイヤー（`config.yml`）に書き込み、バックグラウンド保存をキューに入れます。\n- プロジェクト設定はケーパビリティ探索からの読み取り専用です。\n\n## マイグレーション動作は引き続きアクティブ\n\n起動時に `config.yml` が存在しない場合:\n\n1. `~/.xcsh/agent/settings.json` からマイグレーション（成功時に `.bak` にリネーム）\n2. `agent.db` からのレガシー DB 設定とマージ\n3. マージ結果を `config.yml` に書き込み\n\n`#migrateRawSettings` のフィールドレベルマイグレーション:\n\n- `queueMode` -> `steeringMode`\n- `ask.timeout` ミリ秒 -> 秒（古い値がミリ秒のように見える場合 (`> 1000`)）\n- レガシーのフラット `theme: \"...\"` -> `theme.dark/theme.light` 構造\n\n---\n\n## 5) ケーパビリティ/探索の統合\n\nコア以外のほとんどの設定ロードフローは、ケーパビリティレジストリ（`src/capability/index.ts` + `src/discovery/index.ts`）を通じて行われます。\n\n## プロバイダー順序\n\nプロバイダーは数値の優先度でソートされます（高い方が優先）。優先度の例:\n\n- ネイティブ OMP（`builtin.ts`）: `100`\n- Claude: `80`\n- Codex / agents / Claude marketplace: `70`\n- Gemini: `60`\n\n```text\nProvider precedence (higher wins)\n\nnative (.xcsh)          priority 100\nclaude                 priority  80\ncodex / agents / ...   priority  70\ngemini                 priority  60\n```\n\n## 重複排除のセマンティクス\n\nケーパビリティは `key(item)` を定義します:\n\n- 同じキー => 最初のアイテムが優先（より高い優先度/先にロードされたアイテム）\n- キーなし（`undefined`）=> 重複排除なし、すべてのアイテムが保持される\n\n関連するキー:\n\n- skills: `name`\n- tools: `name`\n- hooks: `${type}:${tool}:${name}`\n- extension modules: `name`\n- extensions: `name`\n- settings: 重複排除なし（すべてのアイテムが保持される）\n\n---\n\n## 6) ネイティブ `.xcsh` プロバイダーの動作（`src/discovery/builtin.ts`）\n\nネイティブプロバイダー（`id: native`）は以下から読み取ります:\n\n- プロジェクト: `<cwd>/.xcsh/...`\n- ユーザー: `~/.xcsh/agent/...`\n\n### ディレクトリ許可ルール\n\n`builtin.ts` は、ディレクトリが存在し**かつ空でない**場合（`ifNonEmptyDir`）のみ設定ルートを含めます。\n\n### スコープ固有のロード\n\n- Skills: `skills/*/SKILL.md`\n- スラッシュコマンド: `commands/*.md`\n- Rules: `rules/*.{md,mdc}`\n- Prompts: `prompts/*.md`\n- Instructions: `instructions/*.md`\n- Hooks: `hooks/pre/*`, `hooks/post/*`\n- Tools: `tools/*.json|*.md` および `tools/<name>/index.ts`\n- Extension modules: `extensions/` 配下で探索（+ レガシー `settings.json.extensions` 文字列配列）\n- Extensions: `extensions/<name>/gemini-extension.json`\n- Settings ケーパビリティ: `settings.json`\n\n### nearest-project 検索のニュアンス\n\n`SYSTEM.md` と `XCSH.md` について、ネイティブプロバイダーは最も近い祖先のプロジェクト `.xcsh` ディレクトリ検索（上方向へのたどり）を使用しますが、`.xcsh` ディレクトリが空でないことを引き続き要求します。\n\n---\n\n## 7) 主要サブシステムが設定を使用する方法\n\n## Settings サブシステム\n\n- `Settings.init()` はグローバル `config.yml` + 探索されたプロジェクト `settings.json` ケーパビリティアイテムをロードします。\n- `level === \"project\"` のケーパビリティアイテムのみがプロジェクトレイヤーにマージされます。\n\n## Skills サブシステム\n\n- `extensibility/skills.ts` は `loadCapability(skillCapability.id, { cwd })` 経由でロードします。\n- ソーストグルとフィルター（`ignoredSkills`、`includeSkills`、カスタムディレクトリ）を適用します。\n- レガシー名のトグルがまだ存在します（`skills.enablePiUser`、`skills.enablePiProject`）が、これらはネイティブプロバイダー（`provider === \"native\"`）をゲートします。\n\n## Hooks サブシステム\n\n- `discoverAndLoadHooks()` は hook ケーパビリティ + 明示的に設定されたパスからフックパスを解決します。\n- その後、Bun import 経由でモジュールをロードします。\n\n## Tools サブシステム\n\n- `discoverAndLoadCustomTools()` は tool ケーパビリティ + プラグインツールパス + 明示的に設定されたパスからツールパスを解決します。\n- 宣言的な `.md/.json` ツールファイルはメタデータのみです。実行可能なロードはコードモジュールを期待します。\n\n## Extensions サブシステム\n\n- `discoverAndLoadExtensions()` は extension-module ケーパビリティ + 明示的なパスからエクステンションモジュールを解決します。\n- 現在の実装は、ロード前に `_source.provider === \"native\"` のケーパビリティアイテムのみを意図的に保持します。\n\n---\n\n## 8) 依拠すべき優先順位ルール\n\n以下のメンタルモデルを使用してください:\n\n1. `config.ts` のソースディレクトリ順序が候補パスの順序を決定します。\n2. ケーパビリティプロバイダーの優先度がプロバイダー間の優先順位を決定します。\n3. ケーパビリティキーの重複排除が衝突時の動作を決定します（キー付きケーパビリティでは最初のものが優先）。\n4. サブシステム固有のマージロジックが実効的な優先順位をさらに変更する場合があります（特に settings）。\n\n### Settings 固有の注意事項\n\nSettings ケーパビリティアイテムは重複排除されません。`Settings.#loadProjectSettings()` は返された順序でプロジェクトアイテムをディープマージします。マージは後のアイテムの値を前のアイテムの値に上書き適用するため、実効的なオーバーライド動作はケーパビリティキーのセマンティクスだけでなく、プロバイダーの出力順序に依存します。\n\n---\n\n## 9) 現在も存在するレガシー/互換性動作\n\n- `ConfigFile` の YAML 対象ファイルに対する JSON -> YAML マイグレーション。\n- `settings.json` および `agent.db` から `config.yml` への Settings マイグレーション。\n- Settings キーのマイグレーション（`queueMode`、`ask.timeout`、フラット `theme`）。\n- エクステンションマニフェストの互換性: ローダーは `package.json.xcsh` と `package.json.pi` の両方のマニフェストセクションを受け入れます。\n- レガシー設定名 `skills.enablePiUser` / `skills.enablePiProject` は、ネイティブスキルソースのアクティブなゲートとして引き続き機能しています。\n\nこれらの互換性パスがコードから削除された場合は、このドキュメントを直ちに更新してください。現在、いくつかのランタイム動作がこれらに依存しています。\n",
	"ja/configuration/environment-variables.md": "---\ntitle: 環境変数\ndescription: xcsh の設定と動作制御のためのランタイム環境変数リファレンス。\nsidebar:\n  order: 2\n  label: 環境変数\ni18n:\n  sourceHash: e2890f963c02\n  translator: machine\n---\n\n# 環境変数（現在のランタイムリファレンス）\n\nこのリファレンスは、以下の現在のコードパスから導出されています：\n\n- `packages/coding-agent/src/**`\n- `packages/ai/src/**`（coding-agent が使用するプロバイダー/認証解決）\n- `packages/utils/src/**` および `packages/tui/src/**`（これらの変数が coding-agent のランタイムに直接影響する場合）\n\nアクティブな動作のみを文書化しています。\n\n## 解決モデルと優先順位\n\nほとんどのランタイムルックアップは、`@f5-sales-demo/pi-utils`（`packages/utils/src/env.ts`）の `$env` を使用します。\n\n`$env` の読み込み順序：\n\n1. 既存のプロセス環境（`Bun.env`）\n2. プロジェクトの `.env`（`$PWD/.env`）- まだ設定されていないキーのみ\n3. ホームの `.env`（`~/.env`）- まだ設定されていないキーのみ\n\n`.env` ファイルの追加ルール：解析時に `XCSH_*` キーは `PI_*` キーにミラーリングされます。\n\n---\n\n## 1) モデル/プロバイダー認証\n\n特に記載がない限り、`getEnvApiKey()`（`packages/ai/src/stream.ts`）を介して使用されます。\n\n### コアプロバイダー資格情報\n\n| 変数                        | 用途 | 必要な場合                                                 | 備考 / 優先順位                                                                                  |\n|---------------------------------|---|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|\n| `ANTHROPIC_OAUTH_TOKEN`         | Anthropic API 認証 | OAuth トークン認証で Anthropic を使用する場合                         | プロバイダー認証解決において `ANTHROPIC_API_KEY` より優先                              |\n| `ANTHROPIC_API_KEY`             | Anthropic API 認証 | OAuth トークンなしで Anthropic を使用する場合                           | `ANTHROPIC_OAUTH_TOKEN` の後のフォールバック                                                              |\n| `ANTHROPIC_FOUNDRY_API_KEY`     | Azure Foundry / エンタープライズゲートウェイ経由の Anthropic | `CLAUDE_CODE_USE_FOUNDRY` が有効な場合                             | Foundry モード有効時は `ANTHROPIC_OAUTH_TOKEN` および `ANTHROPIC_API_KEY` より優先  |\n| `OPENAI_API_KEY`                | OpenAI 認証 | 明示的な apiKey 引数なしで OpenAI ファミリープロバイダーを使用する場合 | OpenAI Completions/Responses プロバイダーで使用                                                      |\n| `GEMINI_API_KEY`                | Google Gemini 認証 | `google` プロバイダーモデルを使用する場合                                | Gemini プロバイダーマッピングの主キー                                                             |\n| `GOOGLE_API_KEY`                | Gemini 画像ツール認証フォールバック | `GEMINI_API_KEY` なしで `gemini_image` ツールを使用する場合            | coding-agent 画像ツールのフォールバックパスで使用                                                       |\n| `GROQ_API_KEY`                  | Groq 認証 | Groq モデルを使用する場合                                             |                                                                                                     |\n| `CEREBRAS_API_KEY`              | Cerebras 認証 | Cerebras モデルを使用する場合                                         |                                                                                                     |\n| `TOGETHER_API_KEY`              | Together 認証 | `together` プロバイダーを使用する場合                                     |                                                                                                     |\n| `HUGGINGFACE_HUB_TOKEN`         | Hugging Face 認証 | `huggingface` プロバイダーを使用する場合                                  | Hugging Face の主要トークン環境変数                                                                  |\n| `HF_TOKEN`                      | Hugging Face 認証 | `huggingface` プロバイダーを使用する場合                                  | `HUGGINGFACE_HUB_TOKEN` が未設定の場合のフォールバック                                                      |\n| `SYNTHETIC_API_KEY`             | Synthetic 認証 | Synthetic モデルを使用する場合                                        |                                                                                                     |\n| `NVIDIA_API_KEY`                | NVIDIA 認証 | `nvidia` プロバイダーを使用する場合                                       |                                                                                                     |\n| `NANO_GPT_API_KEY`              | NanoGPT 認証 | `nanogpt` プロバイダーを使用する場合                                      |                                                                                                     |\n| `VENICE_API_KEY`                | Venice 認証 | `venice` プロバイダーを使用する場合                                       |                                                                                                     |\n| `LITELLM_API_KEY`               | LiteLLM 認証 | `litellm` プロバイダーを使用する場合                                      | OpenAI 互換 LiteLLM プロキシキー。`LITELLM_BASE_URL` と併用すると `models.yml` の自動設定が有効になります |\n| `LM_STUDIO_API_KEY`             | LM Studio 認証（オプション） | 認証付きホストで `lm-studio` プロバイダーを使用する場合           | ローカルの LM Studio は通常認証なしで動作します。キーが必要な場合は空でない任意のトークンが機能します         |\n| `OLLAMA_API_KEY`                | Ollama 認証（オプション） | 認証付きホストで `ollama` プロバイダーを使用する場合              | ローカルの Ollama は通常認証なしで動作します。キーが必要な場合は空でない任意のトークンが機能します            |\n| `LLAMA_CPP_API_KEY`             | Ollama 認証（オプション） | `--api-key` パラメータ付きで `llama-server` を使用する場合              | ローカルの llama.cpp は通常認証なしで動作します。キーが設定されている場合は空でない任意のトークンが機能します       |\n| `XIAOMI_API_KEY`                | Xiaomi MiMo 認証 | `xiaomi` プロバイダーを使用する場合                                       |                                                                                                     |\n| `MOONSHOT_API_KEY`              | Moonshot 認証 | `moonshot` プロバイダーを使用する場合                                     |                                                                                                     |\n| `XAI_API_KEY`                   | xAI 認証 | xAI モデルを使用する場合                                              |                                                                                                     |\n| `OPENROUTER_API_KEY`            | OpenRouter 認証 | OpenRouter モデルを使用する場合                                       | 優先/自動プロバイダーが OpenRouter の場合、画像ツールでも使用                                  |\n| `MISTRAL_API_KEY`               | Mistral 認証 | Mistral モデルを使用する場合                                          |                                                                                                     |\n| `ZAI_API_KEY`                   | z.ai 認証 | z.ai モデルを使用する場合                                             | z.ai ウェブ検索プロバイダーでも使用                                                               |\n| `MINIMAX_API_KEY`               | MiniMax 認証 | `minimax` プロバイダーを使用する場合                                      |                                                                                                     |\n| `MINIMAX_CODE_API_KEY`          | MiniMax Code 認証 | `minimax-code` プロバイダーを使用する場合                                 |                                                                                                     |\n| `MINIMAX_CODE_CN_API_KEY`       | MiniMax Code CN 認証 | `minimax-code-cn` プロバイダーを使用する場合                              |                                                                                                     |\n| `OPENCODE_API_KEY`              | OpenCode 認証 | OpenCode モデルを使用する場合                                         |                                                                                                     |\n| `QIANFAN_API_KEY`               | Qianfan 認証 | `qianfan` プロバイダーを使用する場合                                      |                                                                                                     |\n| `QWEN_OAUTH_TOKEN`              | Qwen Portal 認証 | OAuth トークンで `qwen-portal` を使用する場合                          | `QWEN_PORTAL_API_KEY` より優先                                                         |\n| `QWEN_PORTAL_API_KEY`           | Qwen Portal 認証 | API キーで `qwen-portal` を使用する場合                              | `QWEN_OAUTH_TOKEN` の後のフォールバック                                                                   |\n| `ZENMUX_API_KEY`                | ZenMux 認証 | `zenmux` プロバイダーを使用する場合                                       | ZenMux の OpenAI および Anthropic 互換ルートに使用                                              |\n| `VLLM_API_KEY`                  | vLLM 認証/検出オプトイン | `vllm` プロバイダー（ローカル OpenAI 互換サーバー）を使用する場合       | 認証なしのローカルサーバーでは空でない任意の値が機能します                                                 |\n| `CURSOR_ACCESS_TOKEN`           | Cursor プロバイダー認証 | Cursor プロバイダーを使用する場合                                         |                                                                                                     |\n| `AI_GATEWAY_API_KEY`            | Vercel AI Gateway 認証 | `vercel-ai-gateway` プロバイダーを使用する場合                            |                                                                                                     |\n| `CLOUDFLARE_AI_GATEWAY_API_KEY` | Cloudflare AI Gateway 認証 | `cloudflare-ai-gateway` プロバイダーを使用する場合                        | ベース URL は `https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic` として設定する必要があります |\n\n### GitHub/Copilot トークンチェーン\n\n| 変数 | 用途 | チェーン |\n|---|---|---|\n| `COPILOT_GITHUB_TOKEN` | GitHub Copilot プロバイダー認証 | `COPILOT_GITHUB_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN` |\n| `GH_TOKEN` | Copilot フォールバック; ウェブスクレイパーでの GitHub API 認証 | ウェブスクレイパー内: `GITHUB_TOKEN` → `GH_TOKEN` |\n| `GITHUB_TOKEN` | Copilot フォールバック; ウェブスクレイパーでの GitHub API 認証 | ウェブスクレイパー内: `GH_TOKEN` より先にチェック |\n\n---\n\n## 2) プロバイダー固有のランタイム設定\n\n### Anthropic Foundry Gateway（Azure / エンタープライズプロキシ）\n\n`CLAUDE_CODE_USE_FOUNDRY` が有効な場合、Anthropic リクエストは Foundry モードに切り替わります：\n\n- ベース URL は `FOUNDRY_BASE_URL` から解決されます（未設定の場合はモデル/デフォルトのベース URL がフォールバックとして残ります）。\n- プロバイダー `anthropic` の API キー解決は以下の順になります：\n  `ANTHROPIC_FOUNDRY_API_KEY` → `ANTHROPIC_OAUTH_TOKEN` → `ANTHROPIC_API_KEY`。\n- `ANTHROPIC_CUSTOM_HEADERS` はカンマ/改行区切りの `key: value` ペアとして解析され、リクエストヘッダーにマージされます。\n- TLS クライアント/サーバーマテリアルは環境変数値から注入できます：\n  `NODE_EXTRA_CA_CERTS`、`CLAUDE_CODE_CLIENT_CERT`、`CLAUDE_CODE_CLIENT_KEY`。\n  それぞれ以下のいずれかを受け入れます：\n  - PEM コンテンツへのファイルシステムパス、または\n  - インライン PEM（エスケープされた `\\n` シーケンスを含む）。\n\n| 変数 | 値の型 | 動作 |\n|---|---|---|\n| `CLAUDE_CODE_USE_FOUNDRY` | ブーリアン風の文字列（`1`、`true`、`yes`、`on`） | Anthropic プロバイダーの Foundry モードを有効にする |\n| `FOUNDRY_BASE_URL` | URL 文字列 | Foundry モードでの Anthropic エンドポイントベース URL |\n| `ANTHROPIC_FOUNDRY_API_KEY` | トークン文字列 | `Authorization: Bearer <token>` に使用 |\n| `ANTHROPIC_CUSTOM_HEADERS` | ヘッダーリスト文字列 | 追加ヘッダー; 形式は `header-a: value, header-b: value` または改行区切り |\n| `NODE_EXTRA_CA_CERTS` | PEM パスまたはインライン PEM | サーバー証明書検証用の追加 CA チェーン |\n| `CLAUDE_CODE_CLIENT_CERT` | PEM パスまたはインライン PEM | mTLS クライアント証明書 |\n| `CLAUDE_CODE_CLIENT_KEY` | PEM パスまたはインライン PEM | mTLS クライアント秘密鍵（証明書とペアにする必要があります） |\n\n### Amazon Bedrock\n\n| 変数 | デフォルト / 動作 |\n|---|---|\n| `AWS_REGION` | プライマリリージョンソース |\n| `AWS_DEFAULT_REGION` | `AWS_REGION` が未設定の場合のフォールバック |\n| `AWS_PROFILE` | 名前付きプロファイル認証パスを有効にする |\n| `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` | IAM キー認証パスを有効にする |\n| `AWS_BEARER_TOKEN_BEDROCK` | ベアラートークン認証パスを有効にする |\n| `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` / `AWS_CONTAINER_CREDENTIALS_FULL_URI` | ECS タスク資格情報パスを有効にする |\n| `AWS_WEB_IDENTITY_TOKEN_FILE` + `AWS_ROLE_ARN` | ウェブアイデンティティ認証パスを有効にする |\n| `AWS_BEDROCK_SKIP_AUTH` | `1` の場合、ダミー資格情報を注入（プロキシ/非認証シナリオ） |\n| `AWS_BEDROCK_FORCE_HTTP1` | `1` の場合、Node HTTP/1 リクエストハンドラーを強制 |\n\nプロバイダーコードでのリージョンフォールバック: `options.region` → `AWS_REGION` → `AWS_DEFAULT_REGION` → `us-east-1`。\n\n### Azure OpenAI Responses\n\n| 変数 | デフォルト / 動作 |\n|---|---|\n| `AZURE_OPENAI_API_KEY` | オプションとして API キーが渡されない限り必須 |\n| `AZURE_OPENAI_API_VERSION` | デフォルト `v1` |\n| `AZURE_OPENAI_BASE_URL` | 直接ベース URL オーバーライド |\n| `AZURE_OPENAI_RESOURCE_NAME` | ベース URL の構築に使用: `https://<resource>.openai.azure.com/openai/v1` |\n| `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` | オプションのマッピング文字列: `modelId=deploymentName,model2=deployment2` |\n\nベース URL の解決: オプション `azureBaseUrl` → 環境変数 `AZURE_OPENAI_BASE_URL` → オプション/環境変数のリソース名 → `model.baseUrl`。\n\n### Google Vertex AI\n\n| 変数 | 必須? | 備考 |\n|---|---|---|\n| `GOOGLE_CLOUD_PROJECT` | はい（オプションで渡されない場合） | フォールバック: `GCLOUD_PROJECT` |\n| `GCLOUD_PROJECT` | フォールバック | 代替プロジェクト ID ソースとして使用 |\n| `GOOGLE_CLOUD_LOCATION` | はい（オプションで渡されない場合） | プロバイダーにデフォルトなし |\n| `GOOGLE_APPLICATION_CREDENTIALS` | 条件付き | 設定されている場合、ファイルが存在する必要があります。それ以外の場合は ADC フォールバックパスがチェックされます（`~/.config/gcloud/application_default_credentials.json`） |\n\n### Kimi\n\n| 変数 | デフォルト / 動作 |\n|---|---|\n| `KIMI_CODE_OAUTH_HOST` | プライマリ OAuth ホストオーバーライド |\n| `KIMI_OAUTH_HOST` | フォールバック OAuth ホストオーバーライド |\n| `KIMI_CODE_BASE_URL` | Kimi 使用エンドポイントベース URL をオーバーライド（`usage/kimi.ts`） |\n\nOAuth ホストチェーン: `KIMI_CODE_OAUTH_HOST` → `KIMI_OAUTH_HOST` → `https://auth.kimi.com`。\n\n### Antigravity/Gemini 画像互換性\n\n| 変数 | デフォルト / 動作 |\n|---|---|\n| `PI_AI_ANTIGRAVITY_VERSION` | Gemini CLI プロバイダーの Antigravity ユーザーエージェントバージョンタグをオーバーライド |\n\n### OpenAI Codex responses（機能/デバッグ制御）\n\n| 変数 | 動作 |\n|---|---|\n| `PI_CODEX_DEBUG` | `1`/`true` で Codex プロバイダーのデバッグログを有効にする |\n| `PI_CODEX_WEBSOCKET` | `1`/`true` で WebSocket トランスポート優先を有効にする |\n| `PI_CODEX_WEBSOCKET_V2` | `1`/`true` で WebSocket v2 パスを有効にする |\n| `PI_CODEX_WEBSOCKET_IDLE_TIMEOUT_MS` | 正の整数オーバーライド（デフォルト 300000） |\n| `PI_CODEX_WEBSOCKET_RETRY_BUDGET` | 非負の整数オーバーライド（デフォルト 5） |\n| `PI_CODEX_WEBSOCKET_RETRY_DELAY_MS` | 正の整数ベースバックオフオーバーライド（デフォルト 500） |\n\n### Cursor プロバイダーデバッグ\n\n| 変数 | 動作 |\n|---|---|\n| `DEBUG_CURSOR` | プロバイダーデバッグログを有効にする; `2`/`verbose` で詳細なペイロードスニペット表示 |\n| `DEBUG_CURSOR_LOG` | JSONL デバッグログ出力のオプションファイルパス |\n\n### プロンプトキャッシュ互換性スイッチ\n\n| 変数 | 動作 |\n|---|---|\n| `PI_CACHE_RETENTION` | `long` の場合、サポートされている場所で長期保持を有効にする（`anthropic`、`openai-responses`、Bedrock 保持解決） |\n\n---\n\n## 3) ウェブ検索サブシステム\n\n### 検索プロバイダー資格情報\n\n| 変数 | 使用元 |\n|---|---|\n| `EXA_API_KEY` | Exa 検索プロバイダーおよび Exa MCP ツール |\n| `BRAVE_API_KEY` | Brave 検索プロバイダー |\n| `PERPLEXITY_API_KEY` | Perplexity 検索プロバイダー API キーモード |\n| `TAVILY_API_KEY` | Tavily 検索プロバイダー |\n| `ZAI_API_KEY` | z.ai 検索プロバイダー（`agent.db` の保存済み OAuth もチェック） |\n| `OPENAI_API_KEY` / DB 内の Codex OAuth | Codex 検索プロバイダーの利用可能性/認証 |\n\n### Anthropic ウェブ検索認証チェーン\n\n`packages/coding-agent/src/web/search/auth.ts` は、以下の順序で Anthropic ウェブ検索資格情報を解決します：\n\n1. `ANTHROPIC_SEARCH_API_KEY`（+ オプションの `ANTHROPIC_SEARCH_BASE_URL`）\n2. `api: \"anthropic-messages\"` を持つ `models.json` プロバイダーエントリ\n3. `agent.db` からの Anthropic OAuth 資格情報（5 分のバッファ内に期限切れしない必要があります）\n4. 汎用 Anthropic 環境変数フォールバック: プロバイダーキー（`ANTHROPIC_FOUNDRY_API_KEY`/`ANTHROPIC_OAUTH_TOKEN`/`ANTHROPIC_API_KEY`）+ オプションの `ANTHROPIC_BASE_URL`（Foundry モード有効時は `FOUNDRY_BASE_URL`）\n\n関連変数：\n\n| 変数 | デフォルト / 動作 |\n|---|---|\n| `ANTHROPIC_SEARCH_API_KEY` | 最優先の明示的検索キー |\n| `ANTHROPIC_SEARCH_BASE_URL` | 省略時は `https://api.anthropic.com` がデフォルト |\n| `ANTHROPIC_SEARCH_MODEL` | デフォルトは `claude-haiku-4-5` |\n| `ANTHROPIC_BASE_URL` | ティア 4 認証パスの汎用フォールバックベース URL |\n\n### Perplexity OAuth フロー動作フラグ\n\n| 変数 | 動作 |\n|---|---|\n| `PI_AUTH_NO_BORROW` | 設定されている場合、Perplexity ログインフローでの macOS ネイティブアプリトークン借用パスを無効にする |\n\n---\n\n## 4) Python ツーリングとカーネルランタイム\n\n| 変数 | デフォルト / 動作 |\n|---|---|\n| `PI_PY` | Python ツールモードオーバーライド: `0`/`bash`=`bash-only`、`1`/`py`=`ipy-only`、`mix`/`both`=`both`; 無効な値は無視 |\n| `PI_PYTHON_SKIP_CHECK` | `1` の場合、Python カーネル可用性チェック/ウォームチェックをスキップ |\n| `PI_PYTHON_GATEWAY_URL` | 設定されている場合、ローカル共有ゲートウェイの代わりに外部カーネルゲートウェイを使用 |\n| `PI_PYTHON_GATEWAY_TOKEN` | 外部ゲートウェイ用のオプション認証トークン（`Authorization: token <value>`） |\n| `PI_PYTHON_IPC_TRACE` | `1` の場合、カーネルモジュールの低レベル IPC トレースパスを有効にする |\n| `VIRTUAL_ENV` | Python ランタイム解決用の最優先 venv パス |\n\n追加の条件付き動作：\n\n- `BUN_ENV=test` または `NODE_ENV=test` の場合、Python 可用性チェックは OK として扱われ、ウォーミングはスキップされます。\n- Python 環境フィルタリングは一般的な API キーを拒否し、安全な基本変数 + `LC_`、`XDG_`、`PI_` プレフィックスを許可します。\n\n---\n\n## 5) エージェント/ランタイム動作トグル\n\n| 変数                   | デフォルト / 動作                                                                           |\n|----------------------------|----------------------------------------------------------------------------------------------|\n| `PI_SMOL_MODEL`            | `smol` の一時的なモデルロールオーバーライド（CLI `--smol` が優先）                     |\n| `PI_SLOW_MODEL`            | `slow` の一時的なモデルロールオーバーライド（CLI `--slow` が優先）                     |\n| `PI_PLAN_MODEL`            | `plan` の一時的なモデルロールオーバーライド（CLI `--plan` が優先）                     |\n| `PI_NO_TITLE`              | 設定されている場合（空でない任意の値）、最初のユーザーメッセージでの自動セッションタイトル生成を無効にする   |\n| `NULL_PROMPT`              | `true` の場合、システムプロンプトビルダーが空文字列を返す                                        |\n| `PI_BLOCKED_AGENT`         | タスクツールで特定のサブエージェントタイプをブロック                                                 |\n| `PI_SUBPROCESS_CMD`        | サブエージェントスポーンコマンドをオーバーライド（`xcsh` / `xcsh.cmd` 解決バイパス）                       |\n| `PI_TASK_MAX_OUTPUT_BYTES` | サブエージェントあたりの最大キャプチャ出力バイト数（デフォルト `500000`）                                    |\n| `PI_TASK_MAX_OUTPUT_LINES` | サブエージェントあたりの最大キャプチャ出力行数（デフォルト `5000`）                                      |\n| `PI_TIMING`                | `1` の場合、起動/ツールタイミング計測ログを有効にする                                     |\n| `PI_DEBUG_STARTUP`         | 複数の起動パスで stderr への起動ステージデバッグ出力を有効にする                       |\n| `PI_PACKAGE_DIR`           | パッケージアセットベースディレクトリの解決をオーバーライド（docs/examples/changelog パスルックアップ）            |\n| `PI_DISABLE_LSPMUX`        | `1` の場合、lspmux 検出/統合を無効にし、直接 LSP サーバースポーンを強制          |\n| `LITELLM_BASE_URL`         | LiteLLM プロキシベース URL。`LITELLM_API_KEY` と併用すると、初回実行時に `models.yml` の自動生成をトリガーし、起動ごとに自己修復を行う |\n| `LM_STUDIO_BASE_URL`       | デフォルトの暗黙的 LM Studio 検出ベース URL オーバーライド（未設定の場合は `http://127.0.0.1:1234/v1`） |\n| `OLLAMA_BASE_URL`          | デフォルトの暗黙的 Ollama 検出ベース URL オーバーライド（未設定の場合は `http://127.0.0.1:11434`）      |\n| `LLAMA_CPP_BASE_URL`       | デフォルトの暗黙的 Llama.cpp 検出ベース URL オーバーライド（未設定の場合は `http://127.0.0.1:8080`）    |\n| `PI_EDIT_VARIANT`          | `hashline` の場合、編集ツールが利用可能な場合にハッシュライン読み取り/grep 表示モードを強制               |\n| `PI_NO_PTY`                | `1` の場合、bash ツールのインタラクティブ PTY パスを無効にする                                          |\n\n`PI_NO_PTY` は CLI `--no-pty` が使用された場合にも内部的に設定されます。\n\n---\n\n## 6) ストレージと設定ルートパス\n\nこれらは `@f5-sales-demo/pi-utils/dirs` を介して使用され、coding-agent がデータを保存する場所に影響します。\n\n| 変数 | デフォルト / 動作 |\n|---|---|\n| `PI_CONFIG_DIR` | ホームディレクトリ下の設定ルートディレクトリ名（デフォルト `.xcsh`） |\n| `PI_CODING_AGENT_DIR` | エージェントディレクトリの完全オーバーライド（デフォルト `~/<PI_CONFIG_DIR or .xcsh>/agent`） |\n| `PWD` | パスヘルパーでの正規カレントワーキングディレクトリのマッチングに使用 |\n\n---\n\n## 7) シェル/ツール実行環境\n\n（`packages/utils/src/procmgr.ts` および coding-agent bash ツール統合より。）\n\n| 変数 | 動作 |\n|---|---|\n| `PI_BASH_NO_CI` | スポーンされたシェル環境への自動 `CI=true` 注入を抑制 |\n| `CLAUDE_BASH_NO_CI` | `PI_BASH_NO_CI` のレガシーエイリアスフォールバック |\n| `PI_BASH_NO_LOGIN` | ログインシェルモードを無効にすることを意図 |\n| `CLAUDE_BASH_NO_LOGIN` | `PI_BASH_NO_LOGIN` のレガシーエイリアスフォールバック |\n| `PI_SHELL_PREFIX` | オプションのコマンドプレフィックスラッパー |\n| `CLAUDE_CODE_SHELL_PREFIX` | `PI_SHELL_PREFIX` のレガシーエイリアスフォールバック |\n| `VISUAL` | 優先外部エディターコマンド |\n| `EDITOR` | フォールバック外部エディターコマンド |\n\n現在の実装に関する注記: `PI_BASH_NO_LOGIN`/`CLAUDE_BASH_NO_LOGIN` は読み取られますが、現在の `getShellArgs()` は両方のブランチで `['-l','-c']` を返します（実質的に現在は no-op）。\n\n---\n\n## 8) UI/テーマ/セッション検出（自動検出される環境変数）\n\nこれらはランタイムシグナルとして読み取られます。通常、手動設定ではなくターミナル/OS によって設定されます。\n\n| 変数 | 用途 |\n|---|---|\n| `COLORTERM`, `TERM`, `WT_SESSION` | カラー機能検出（テーマカラーモード） |\n| `COLORFGBG` | ターミナル背景のライト/ダーク自動検出 |\n| `TERM_PROGRAM`, `TERM_PROGRAM_VERSION`, `TERMINAL_EMULATOR` | システムプロンプト/コンテキストでのターミナルアイデンティティ |\n| `KDE_FULL_SESSION`, `XDG_CURRENT_DESKTOP`, `DESKTOP_SESSION`, `XDG_SESSION_DESKTOP`, `GDMSESSION`, `WINDOWMANAGER` | システムプロンプト/コンテキストでのデスクトップ/ウィンドウマネージャー検出 |\n| `KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION` | ターミナルごとの安定したセッションブレッドクラム ID |\n| `SHELL`, `ComSpec`, `TERM_PROGRAM`, `TERM` | システム情報診断 |\n| `APPDATA`, `XDG_CONFIG_HOME` | lspmux 設定パス解決 |\n| `HOME` | MCP コマンド UI でのパス短縮 |\n\n---\n\n## 9) ネイティブローダー/デバッグフラグ\n\n| 変数 | 動作 |\n|---|---|\n| `PI_DEV` | `packages/natives` での詳細なネイティブアドオンロード診断を有効にする |\n\n## 10) TUI ランタイムフラグ（共有パッケージ、coding-agent の UX に影響）\n\n| 変数 | 動作 |\n|---|---|\n| `PI_NOTIFICATIONS` | `off` / `0` / `false` でデスクトップ通知を抑制 |\n| `PI_TUI_WRITE_LOG` | 設定されている場合、TUI 書き込みをファイルにログ記録 |\n| `PI_HARDWARE_CURSOR` | `1` の場合、ハードウェアカーソルモードを有効にする |\n| `PI_CLEAR_ON_SHRINK` | `1` の場合、コンテンツが縮小した際に空の行をクリア |\n| `PI_DEBUG_REDRAW` | `1` の場合、再描画デバッグログを有効にする |\n| `PI_TUI_DEBUG` | `1` の場合、詳細な TUI デバッグダンプパスを有効にする |\n\n---\n\n## 11) コミット生成制御\n\n| 変数 | 動作 |\n|---|---|\n| `PI_COMMIT_TEST_FALLBACK` | `true`（大文字小文字不問）の場合、コミットフォールバック生成パスを強制 |\n| `PI_COMMIT_NO_FALLBACK` | `true` の場合、エージェントが提案を返さなかった場合のフォールバックを無効にする |\n| `PI_COMMIT_MAP_REDUCE` | `false` の場合、マップリデュースコミット分析パスを無効にする |\n| `DEBUG` | 設定されている場合、コミットエージェントのエラースタックトレースが出力される |\n\n---\n\n## セキュリティに敏感な変数\n\nこれらはシークレットとして扱ってください。ログに記録したりコミットしたりしないでください：\n\n- プロバイダー/API キーおよび OAuth/ベアラー資格情報（すべての `*_API_KEY`、`*_TOKEN`、OAuth アクセス/リフレッシュトークン）\n- クラウド資格情報（`AWS_*`、`GOOGLE_APPLICATION_CREDENTIALS` パスはサービスアカウントマテリアルを露出する可能性があります）\n- 検索/プロバイダー認証変数（`EXA_API_KEY`、`BRAVE_API_KEY`、`PERPLEXITY_API_KEY`、Anthropic 検索キー）\n- Foundry mTLS マテリアル（`CLAUDE_CODE_CLIENT_CERT`、`CLAUDE_CODE_CLIENT_KEY`、プライベート CA バンドルを指す場合の `NODE_EXTRA_CA_CERTS`）\n\nPython ランタイムも、カーネルサブプロセスをスポーンする前に多くの一般的なキー変数を明示的にストリップします（`packages/coding-agent/src/ipy/runtime.ts`）。\n",
	"ja/configuration/fs-scan-cache-architecture.md": "---\ntitle: ファイルシステムスキャンキャッシュアーキテクチャ\ndescription: >-\n  Filesystem scan cache contract for fast file discovery with\n  stale-while-revalidate semantics.\nsidebar:\n  order: 8\n  label: ファイルシステムスキャンキャッシュ\ni18n:\n  sourceHash: 2a2bde1726ac\n  translator: machine\n---\n\n# ファイルシステムスキャンキャッシュ アーキテクチャ契約\n\nこのドキュメントは、Rust（`crates/pi-natives/src/fs_cache.rs`）で実装され、`packages/coding-agent` に公開されるネイティブのディスカバリ/検索 API が利用する共有ファイルシステムスキャンキャッシュの現行契約を定義します。\n\n## このキャッシュとは\n\nキャッシュは、スキャンスコープとトラバーサルポリシーをキーとして、完全なディレクトリスキャンエントリリスト（`GlobMatch[]`）を保存し、上位レベルの操作（globフィルタリング、ファジースコアリング、grepファイル選択）がこれらのキャッシュされたエントリに対して実行できるようにします。\n\n主な目標：\n\n- 繰り返しのディスカバリ/検索呼び出しに対する重複したファイルシステムウォークの回避\n- `glob`、`fuzzyFind`、`grep` が同じスキャンポリシーを共有する場合の一貫性の維持\n- 空の結果に対する明示的な陳腐化回復と、ファイル変更後の明示的な無効化の許可\n\n## 所有権と公開サーフェス\n\n- キャッシュの実装とポリシー: `crates/pi-natives/src/fs_cache.rs`\n- ネイティブコンシューマー:\n  - `crates/pi-natives/src/glob.rs`\n  - `crates/pi-natives/src/fd.rs`（`fuzzyFind`）\n  - `crates/pi-natives/src/grep.rs`\n- JSバインディング/エクスポート:\n  - `packages/natives/src/glob/index.ts`（`invalidateFsScanCache`）\n  - `packages/natives/src/glob/types.ts`\n  - `packages/natives/src/grep/types.ts`\n- Coding-agent の変更時無効化ヘルパー:\n  - `packages/coding-agent/src/tools/fs-cache-invalidation.ts`\n\n## キャッシュキーのパーティショニング（厳格な契約）\n\n各エントリは以下をキーとします：\n\n- 正規化された `root` ディレクトリパス\n- `include_hidden` ブール値\n- `use_gitignore` ブール値\n\n影響：\n\n- 隠しファイル対象と非対象のスキャンはエントリを **共有しません**。\n- gitignore 準拠と無視無効のスキャンはエントリを **共有しません**。\n- コンシューマーは hidden/gitignore の動作について安定したセマンティクスを渡す必要があります。いずれかのフラグを変更すると、異なるキャッシュパーティションが作成されます。\n\n`node_modules` の包含はキャッシュキーに **含まれません**。キャッシュは `node_modules` を含むエントリを保存し、コンシューマーごとのフィルタリングは取得後に適用されます。\n\n## スキャン収集の動作\n\nキャッシュの作成には、`include_hidden` と `use_gitignore` で設定される決定論的ウォーカー（`ignore::WalkBuilder`）を使用します：\n\n- `follow_links(false)`\n- ファイルパスでソート\n- `.git` は常にスキップ\n- `node_modules` はキャッシュスキャン時に常に収集（後でオプションでフィルタリング）\n- エントリのファイルタイプ + `mtime` は `symlink_metadata` を介して取得\n\n検索ルートは `resolve_search_path` で解決されます：\n\n- 相対パスは現在の cwd に対して解決\n- ターゲットは既存のディレクトリである必要がある\n- ルートは可能な場合に正規化される\n\n## 鮮度とエビクションポリシー\n\nグローバルポリシー（環境変数でオーバーライド可能）：\n\n- `FS_SCAN_CACHE_TTL_MS`（デフォルト `1000`）\n- `FS_SCAN_EMPTY_RECHECK_MS`（デフォルト `200`）\n- `FS_SCAN_CACHE_MAX_ENTRIES`（デフォルト `16`）\n\n動作：\n\n- `get_or_scan(...)`\n  - TTL が `0` の場合：キャッシュを完全にバイパスし、常にフレッシュスキャン（`cache_age_ms = 0`）\n  - TTL 内のキャッシュヒット時：キャッシュされたエントリ + 非ゼロの `cache_age_ms` を返す\n  - 期限切れのヒット時：キーをエビクト、再スキャン、フレッシュエントリを保存\n- 最大エントリ数の強制は `created_at` による最古優先エビクション\n\n## 空の結果の高速再チェック（通常のヒットとは別）\n\n通常のキャッシュヒット：\n\n- TTL 内のキャッシュヒットはキャッシュされたエントリを返し、他に何もしません。\n\n空の結果の高速再チェック：\n\n- これは `ScanResult.cache_age_ms` を使用する **呼び出し側** のポリシーです\n- フィルタ/クエリ結果が空で、キャッシュスキャンの経過時間が少なくとも `empty_recheck_ms()` の場合、呼び出し側は1回の `force_rescan(...)` を実行してリトライします\n- ファイルが最近追加されたがキャッシュがまだ TTL 内である場合の、陳腐化したネガティブ結果を減らすことを目的としています\n\n現在のコンシューマー：\n\n- `glob`：フィルタされたマッチが空でスキャン経過時間がしきい値を超えた場合に再チェック\n- `fuzzyFind`（`fd.rs`）：クエリが空でなく、スコアリングされたマッチが空の場合のみ再チェック\n- `grep`：選択された候補ファイルリストが空の場合に再チェック\n\n## コンシューマーのデフォルトとキャッシュの使用\n\nキャッシュは公開されるすべての API でオプトイン方式です（`cache?: boolean`、デフォルト `false`）。\n\nネイティブ API の現在のデフォルト：\n\n- `glob`：`hidden=false`、`gitignore=true`、`cache=false`\n- `fuzzyFind`：`hidden=false`、`gitignore=true`、`cache=false`\n- `grep`：`hidden=true`、`cache=false`、キャッシュスキャンは常に `use_gitignore=true` を使用\n\n現在の Coding-agent の呼び出し側：\n\n- 大量のメンション候補ディスカバリではキャッシュを有効化：\n  - `packages/coding-agent/src/utils/file-mentions.ts`\n  - プロファイル：`hidden=true`、`gitignore=true`、`includeNodeModules=true`、`cache=true`\n- ツールレベルの `grep` 統合は現在スキャンキャッシュを無効化（`cache: false`）：\n  - `packages/coding-agent/src/tools/grep.ts`\n\n## 無効化の契約\n\nネイティブの無効化エントリポイント：\n\n- `invalidateFsScanCache(path?: string)`\n  - `path` あり：ルートがターゲットパスのプレフィックスであるキャッシュエントリを削除\n  - `path` なし：すべてのスキャンキャッシュエントリをクリア\n\nパス処理の詳細：\n\n- 相対的な無効化パスは cwd に対して解決\n- 無効化は正規化を試行\n- ターゲットが存在しない場合（例：削除）、フォールバックとして親を正規化し、可能な場合はファイル名を再付加\n- これにより、一方が存在しない可能性のある作成/削除/リネームでの無効化動作が保持される\n\n## Coding-agent の変更フローの責任\n\nCoding-agent のコードは、ファイルシステムの変更が成功した後にキャッシュを無効化する必要があります。\n\n中央ヘルパー：\n\n- `invalidateFsScanAfterWrite(path)`\n- `invalidateFsScanAfterDelete(path)`\n- `invalidateFsScanAfterRename(oldPath, newPath)`（パスが異なる場合は両方を無効化）\n\n現在の変更ツールの呼び出し箇所：\n\n- `packages/coding-agent/src/tools/write.ts`\n- `packages/coding-agent/src/patch/index.ts`（hashline/patch/replace フロー）\n\nルール：フローがファイルシステムのコンテンツまたは場所を変更し、これらのヘルパーをバイパスする場合、キャッシュの陳腐化バグが発生することが想定されます。\n\n## 新しいキャッシュコンシューマーを安全に追加する\n\n新しいスキャナー/検索パスでキャッシュの使用を導入する場合：\n\n1. **安定したスキャンポリシー入力を使用する**\n   - まず hidden/gitignore のセマンティクスを決定する\n   - キャッシュパーティションが意図的になるよう、`get_or_scan`/`force_rescan` に一貫して渡す\n\n2. **キャッシュデータはトラバーサルポリシーによるプレフィルタリングのみと扱う**\n   - ツール固有のフィルタリング（globパターン、タイプフィルタ、node_modulesルール）は取得後に適用する\n   - キャッシュされたエントリが上位レベルのフィルタをすでに反映していると想定しない\n\n3. **陳腐化したネガティブリスクに対してのみ空の結果の高速再チェックを実装する**\n   - `scan.cache_age_ms >= empty_recheck_ms()` を使用する\n   - `force_rescan(..., store=true, ...)` で1回リトライする\n   - このパスは通常のキャッシュヒットロジックとは分離する\n\n4. **キャッシュ無効モードを明示的に尊重する**\n   - 呼び出し側がキャッシュを無効にした場合、`force_rescan(..., store=false, ...)` を呼び出す\n   - キャッシュ無効のリクエストパスで共有キャッシュを作成しない\n\n5. **新しい書き込みパスに対して変更時無効化を接続する**\n   - 書き込み/編集/削除/リネームが成功した後、coding-agent の無効化ヘルパーを呼び出す\n   - リネーム/移動の場合、古いパスと新しいパスの両方を無効化する\n\n6. **呼び出しごとの TTL 調整を追加しない**\n   - 現在の契約はグローバルポリシーのみ（環境変数で設定）、リクエストごとの TTL オーバーライドなし\n\n## 既知の境界\n\n- キャッシュスコープはプロセスローカルのインメモリ（`DashMap`）であり、プロセスの再起動をまたいで永続化されません。\n- キャッシュはスキャンエントリを保存し、最終的なツールの結果は保存しません。\n- `glob`/`fuzzyFind`/`grep` は、キーの次元（`root`、`hidden`、`gitignore`）が一致する場合のみスキャンエントリを共有します。\n- `.git` は呼び出し側のオプションに関係なく、スキャン収集時に常に除外されます。\n",
	"ja/configuration/hooks.md": "---\ntitle: フック\ndescription: コーディングエージェントライフサイクルにおけるイベント前後の自動化のためのフックシステム。\nsidebar:\n  order: 4\n  label: フック\ni18n:\n  sourceHash: cdbec10bc405\n  translator: machine\n---\n\n# フック\n\nこのドキュメントでは、`src/extensibility/hooks/*` にある**現在のフックサブシステムのコード**について説明します。\n\n## ランタイムにおける現在の状態\n\nフックパッケージ（`src/extensibility/hooks/`）はAPIサーフェスとして引き続きエクスポートされ使用可能ですが、デフォルトのCLIランタイムは現在**拡張ランナー**のパスを初期化します。現在の起動フローでは：\n\n- `--hook` は `--extension` のエイリアスとして扱われます（CLIパスは `additionalExtensionPaths` にマージされます）\n- ツールは `HookToolWrapper` ではなく `ExtensionToolWrapper` によってラップされます\n- コンテキスト変換とライフサイクルのエミッションは `ExtensionRunner` を通じて処理されます\n\nこのため、このファイルは、レガシーの動作と制約を含む、フックサブシステムの実装自体（型/ローダー/ランナー/ラッパー）についてドキュメント化します。\n\n## 主要なファイル\n\n- `src/extensibility/hooks/types.ts` — フックコンテキスト、イベント型、および結果コントラクト\n- `src/extensibility/hooks/loader.ts` — モジュールの読み込みとフック検出ブリッジ\n- `src/extensibility/hooks/runner.ts` — イベントディスパッチ、コマンドルックアップ、エラーシグナリング\n- `src/extensibility/hooks/tool-wrapper.ts` — ツールの前後インターセプトラッパー\n- `src/extensibility/hooks/index.ts` — エクスポート/再エクスポート\n\n## フックモジュールとは\n\nフックモジュールはファクトリーをデフォルトエクスポートする必要があります：\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function hook(pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName === \"bash\" && String(event.input.command ?? \"\").includes(\"rm -rf\")) {\n   return { block: true, reason: \"blocked by policy\" };\n  }\n });\n}\n```\n\nファクトリーは以下のことが可能です：\n\n- `pi.on(...)` でイベントハンドラーを登録する\n- `pi.sendMessage(...)` で永続的なカスタムメッセージを送信する\n- `pi.appendEntry(...)` で非LLM状態を永続化する\n- `pi.registerCommand(...)` でスラッシュコマンドを登録する\n- `pi.registerMessageRenderer(...)` でカスタムメッセージレンダラーを登録する\n- `pi.exec(...)` でシェルコマンドを実行する\n\n## 検出と読み込み\n\n`discoverAndLoadHooks(configuredPaths, cwd)` は以下を実行します：\n\n1. ケイパビリティレジストリからフックを検出して読み込む（`loadCapability(\"hooks\")`）\n2. 明示的に設定されたパスを追加する（絶対パスで重複排除）\n3. `loadHooks(allPaths, cwd)` を呼び出す\n\nその後、`loadHooks` は各パスをインポートし、`default` 関数を期待します。\n\n### パス解決\n\n`loader.ts` はフックパスを以下のように解決します：\n\n- 絶対パス：そのまま使用\n- `~` パス：展開される\n- 相対パス：`cwd` に対して解決される\n\n### 重要なレガシーの不一致\n\n`hookCapability` の検出プロバイダーは、依然として前後のシェルスタイルのフックファイル（例：`.claude/hooks/pre/*`、`.xcsh/.../hooks/pre/*`）をモデル化しています。\n\nここのフックローダーは動的モジュールインポートを使用し、デフォルトのJS/TSフックファクトリーを必要とします。検出されたフックパスがモジュールとしてインポートできない場合、読み込みは失敗し `LoadHooksResult.errors` に報告されます。\n\n## イベントサーフェス\n\nフックイベントは `types.ts` で厳密に型付けされています。\n\n### セッションイベント\n\n- `session_start`\n- `session_before_switch` → `{ cancel?: boolean }` を返すことができる\n- `session_switch`\n- `session_before_branch` → `{ cancel?: boolean; skipConversationRestore?: boolean }` を返すことができる\n- `session_branch`\n- `session_before_compact` → `{ cancel?: boolean; compaction?: CompactionResult }` を返すことができる\n- `session.compacting` → `{ context?: string[]; prompt?: string; preserveData?: Record<string, unknown> }` を返すことができる\n- `session_compact`\n- `session_before_tree` → `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }` を返すことができる\n- `session_tree`\n- `session_shutdown`\n\n### エージェント/コンテキストイベント\n\n- `context` → `{ messages?: Message[] }` を返すことができる\n- `before_agent_start` → `{ message?: { customType; content; display; details } }` を返すことができる\n- `agent_start`\n- `agent_end`\n- `turn_start`\n- `turn_end`\n- `auto_compaction_start`\n- `auto_compaction_end`\n- `auto_retry_start`\n- `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### ツールイベント（前後モデル）\n\n- `tool_call`（実行前）→ `{ block?: boolean; reason?: string }` を返すことができる\n- `tool_result`（実行後）→ `{ content?; details?; isError? }` を返すことができる\n\nこれはフックサブシステムのコアとなる前後インターセプトモデルです。\n\n```text\nフックツールインターセプトフロー\n\ntool_call ハンドラー\n   │\n   ├─ { block: true } が返された場合? ── はい ──> スロー（ツールブロック）\n   │\n   └─ いいえ\n      │\n      ▼\n   基盤となるツールを実行\n      │\n      ├─ 成功 ──> tool_result ハンドラーが { content, details } をオーバーライド可能\n      │\n      └─ エラー   ──> tool_result(isError=true) をエミットし、元のエラーを再スロー\n```\n\n## 実行モデルとミューテーションのセマンティクス\n\n### 1) 実行前：`tool_call`\n\n`HookToolWrapper.execute()` はツール実行前に `tool_call` をエミットします。\n\n- いずれかのハンドラーが `{ block: true }` を返すと、実行が停止する\n- ハンドラーがスローした場合、ラッパーはフェイルクローズで実行をブロックする\n- 返された `reason` がスローされたエラーテキストになる\n\n### 2) ツール実行\n\nブロックされていない場合、基盤となるツールが通常通り実行されます。\n\n### 3) 実行後：`tool_result`\n\n成功後、ラッパーは以下を含む `tool_result` をエミットします：\n\n- `toolName`、`toolCallId`、`input`\n- `content`\n- `details`\n- `isError: false`\n\nハンドラーがオーバーライドを返した場合：\n\n- `content` は結果コンテンツを置き換えることができる\n- `details` は結果の詳細を置き換えることができる\n\nツールの失敗時、ラッパーは `isError: true` とエラーテキストコンテンツを含む `tool_result` をエミットし、元のエラーを再スローします。\n\n### フックがミューテート可能なもの\n\n- `context` による単一呼び出しのLLMコンテキスト（`messages` 置換チェーン）\n- 成功したツール呼び出しのツール出力コンテンツ/詳細（`tool_result` パス）\n- `before_agent_start` によるエージェント起動前の注入メッセージ\n- `session_before_*` および `session.compacting` によるキャンセル/カスタムコンパクション/ツリー動作\n\n### この実装においてフックがミューテート不可能なもの\n\n- ツールの入力パラメーターをインプレースで変更（`tool_call` ではブロック/許可のみ）\n- スローされたツールエラー後の実行継続（エラーパスは再スローする）\n- ラッパー動作における最終的な成功/エラーステータス（返された `isError` は型付けされているが `HookToolWrapper` では適用されない）\n\n## 順序と競合の動作\n\n### 検出レベルの順序\n\nケイパビリティプロバイダーは優先度順にソートされます（高いものが優先）。重複排除はケイパビリティキーで行われ、最初のものが優先されます。\n\n`hooks` の場合、ケイパビリティキーは `${type}:${tool}:${name}` です。低優先度のプロバイダーからの重複は、シャドーされたものとしてマークされ、有効な検出リストから除外されます。\n\n### 読み込み順序\n\n`discoverAndLoadHooks` は解決された絶対パスで重複排除されたフラットな `allPaths` リストを作成し、その後 `loadHooks` がその順序で繰り返し処理します。\n各検出ディレクトリ内のファイル順は `readdir` の出力に依存し、フックローダーは追加のソートを実行しません。\n\n### ランタイムハンドラーの順序\n\n`HookRunner` 内では、順序は登録シーケンスによって決定論的に決まります：\n\n1. フック配列の順序\n2. フック/イベントごとのハンドラー登録順序\n\nイベント型による競合の動作：\n\n- `tool_call`：ハンドラーがブロックしない限り最後に返された結果が優先され、最初のブロックで短絡する\n- `tool_result`：最後に返されたオーバーライドが優先（短絡なし）\n- `context`：チェーン化され、各ハンドラーは前のハンドラーのメッセージ出力を受け取る\n- `before_agent_start`：最初に返されたメッセージが保持され、以降のメッセージは無視される\n- `session_before_*`：最後に返された結果が追跡され、`cancel: true` は即座に短絡する\n- `session.compacting`：最後に返された結果が優先\n\nコマンド/レンダラーの競合：\n\n- `getCommand(name)` はフック全体で最初の一致を返す（最初に読み込まれたものが優先）\n- `getMessageRenderer(customType)` は最初の一致を返す\n- `getRegisteredCommands()` はすべてのコマンドを返す（重複排除なし）\n\n## UIインタラクション（`HookContext.ui`）\n\n`HookUIContext` には以下が含まれます：\n\n- `select`、`confirm`、`input`、`editor`\n- `notify`\n- `setStatus`\n- `custom`\n- `setEditorText`、`getEditorText`\n- `theme` ゲッター\n\n`ctx.hasUI` はインタラクティブなUIが使用可能かどうかを示します。\n\nUIなしで実行する場合、デフォルトのno-opコンテキスト動作は以下の通りです：\n\n- `select/input/editor` は `undefined` を返す\n- `confirm` は `false` を返す\n- `notify`、`setStatus`、`setEditorText` はno-opである\n- `getEditorText` は `\"\"` を返す\n\n### ステータスラインの動作\n\n`ctx.ui.setStatus(key, text)` で設定されたフックステータステキストは：\n\n- キーごとに保存される\n- キー名でソートされる\n- サニタイズされる（`\\r`、`\\n`、`\\t` → スペース；連続するスペースは縮小される）\n- 表示のために結合され幅が切り詰められる\n\n## エラーの伝播とフォールバック\n\n### 読み込み時\n\n- 無効なモジュールまたはデフォルトエクスポートの欠如 → `LoadHooksResult.errors` に記録される\n- 他のフックの読み込みは継続される\n\n### イベント時\n\n`HookRunner.emit(...)` はほとんどのイベントのハンドラーエラーをキャッチし、`HookError` をリスナーにエミット（`hookPath`、`event`、`error`）してから継続します。\n\n`emitToolCall(...)` はより厳格です：ハンドラーエラーはそこでは飲み込まれず、呼び出し元に伝播します。`HookToolWrapper` では、これによりツール呼び出しがブロックされます（フェイルセーフ）。\n\n## 実際のAPIの例\n\n### 安全でないbashコマンドをブロックする\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName !== \"bash\") return;\n  const cmd = String(event.input.command ?? \"\");\n  if (!cmd.includes(\"rm -rf\")) return;\n\n  if (!ctx.hasUI) return { block: true, reason: \"rm -rf blocked (no UI)\" };\n  const ok = await ctx.ui.confirm(\"Dangerous command\", `Allow: ${cmd}`);\n  if (!ok) return { block: true, reason: \"user denied command\" };\n });\n}\n```\n\n### 実行後にツール出力を編集する\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_result\", async event => {\n  if (event.toolName !== \"read\" || event.isError) return;\n\n  const redacted = event.content.map(chunk => {\n   if (chunk.type !== \"text\") return chunk;\n   return { ...chunk, text: chunk.text.replaceAll(/API_KEY=\\S+/g, \"API_KEY=[REDACTED]\") };\n  });\n\n  return { content: redacted };\n });\n}\n```\n\n### LLM呼び出しごとにモデルコンテキストを変更する\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"context\", async event => {\n  const filtered = event.messages.filter(msg => !(msg.role === \"custom\" && msg.customType === \"debug-only\"));\n  return { messages: filtered };\n });\n}\n```\n\n### コマンドセーフなコンテキストメソッドでスラッシュコマンドを登録する\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.registerCommand(\"handoff\", {\n  description: \"Create a new session with setup message\",\n  handler: async (_args, ctx) => {\n   await ctx.waitForIdle();\n   await ctx.newSession({\n    parentSession: ctx.sessionManager.getSessionFile(),\n    setup: async sm => {\n     sm.appendMessage({\n      role: \"user\",\n      content: [{ type: \"text\", text: \"Continue from prior session summary.\" }],\n      timestamp: Date.now(),\n     });\n    },\n   });\n  },\n });\n}\n```\n\n## エクスポートサーフェス\n\n`src/extensibility/hooks/index.ts` がエクスポートするもの：\n\n- 読み込みAPI（`discoverAndLoadHooks`、`loadHooks`）\n- ランナーとラッパー（`HookRunner`、`HookToolWrapper`）\n- すべてのフック型\n- `execCommand` の再エクスポート\n\nパッケージルート（`src/index.ts`）はレガシー互換性サーフェスとしてフック**型**を再エクスポートします。\n",
	"ja/configuration/porting-from-pi-mono.md": "---\ntitle: pi-mono からの移植：実践的マージガイド\ndescription: pi-mono モノリポからの xcsh コードベースへのコード移行に関する実践的ガイドです。\nsidebar:\n  order: 9\n  label: pi-mono からの移植\ni18n:\n  sourceHash: fd4e8c09303d\n  translator: machine\n---\n\n# pi-mono からの移植：実践的マージガイド\n\nこのガイドは、pi-mono からこのリポジトリへ変更を移植するための再利用可能なチェックリストです。\n単一ファイル、フィーチャーブランチ、フルリリース同期など、あらゆるマージに使用してください。\n\n## 最終同期ポイント\n\n**コミット:** `b21b42d032919de2f2e6920a76fa9a37c3920c0a`\n**日付:** 2026-03-22\n\n各同期の後にこのセクションを更新してください。前回の範囲を再利用しないでください。\n\n新しい同期を開始する際は、このコミットから先のパッチを生成します：\n\n```bash\ngit format-patch b21b42d032919de2f2e6920a76fa9a37c3920c0a..HEAD --stdout > changes.patch\n```\n\n## 0) スコープを定義する\n\n- アップストリームの参照（コミット、タグ、または PR）を特定する。\n- 変更を予定しているパッケージやフォルダをリストアップする。\n- どの機能がスコープ内で、どの機能を意図的にスキップするかを決定する。\n\n## 1) コードを安全に持ち込む\n\n- 丸ごとコピーではなく、クリーンで焦点を絞った diff を優先する。\n- ビルド成果物や生成ファイルのコピーを避ける。\n- アップストリームが新しいファイルを追加した場合は、明示的に追加し内容をレビューする。\n\n## 2) インポート拡張子の規約に合わせる\n\nほとんどのランタイム TypeScript ソースは内部インポートで `.js` を省略しますが、一部の test/bench エントリーポイントは ESM ランタイム互換性のために `.js` を保持しています。ローカルパッケージの既存スタイルに従い、拡張子を一括で除去しないでください。\n\n- `packages/coding-agent` のランタイムソースでは、非 TS アセットのインポートでない限り、内部インポートは拡張子なしにする。\n- `packages/tui/test` と `packages/natives/bench` では、周囲のファイルが既に使用している場合は `.js` を保持する。\n- ツールが要求する場合は実際のファイル拡張子を保持する（例：`.json`、`.css`、`.md` テキスト埋め込み）。\n- 例：`import { x } from \"./foo.js\";` → `import { x } from \"./foo\";`（パッケージの規約が拡張子なしの場合のみ）。\n\n## 3) インポートスコープを置換する\n\nアップストリームは異なるパッケージスコープを使用しています。一貫して置換してください。\n\n- 古いスコープをここで使用されているローカルスコープに置換する。\n- 例（移植する実際のパッケージに合わせて調整してください）：\n  - `@mariozechner/pi-coding-agent` → `@f5-sales-demo/xcsh`\n  - `@mariozechner/pi-agent-core` → `@f5-sales-demo/pi-agent-core`\n  - `@mariozechner/pi-tui` → `@f5-sales-demo/pi-tui`\n  - `@mariozechner/pi-ai` → `@f5-sales-demo/pi-ai`\n\n## 4) Bun API が Node より優れている場合は Bun API を使用する\n\nBun 上で実行します。Bun がより良い代替手段を提供する場合にのみ Node API を置換してください。\n\n**置換するもの：**\n\n- プロセス生成：`child_process.spawn` → 簡単なコマンドには Bun Shell `$`、ストリーミングや長時間実行には `Bun.spawn`/`Bun.spawnSync`\n- ファイル I/O：`fs.readFileSync` → `Bun.file().text()` / `Bun.write()`\n- HTTP クライアント：`node-fetch`、`axios` → ネイティブ `fetch`\n- 暗号ハッシュ：`node:crypto` → Web Crypto または `Bun.hash`\n- SQLite：`better-sqlite3` → `bun:sqlite`\n- Env 読み込み：`dotenv` → Bun は `.env` を自動的に読み込む\n\n**置換しないもの（Bun でも問題なく動作します）：**\n\n- `os.homedir()` — `Bun.env.HOME`、`Bun.env.HOME`、またはリテラル `\"~\"` に置換しない\n- `os.tmpdir()` — `Bun.env.TMPDIR || \"/tmp\"` やハードコードされたパスに置換しない\n- `fs.mkdtempSync()` — 手動パス構築に置換しない\n- `path.join()`、`path.resolve()` など — これらは問題ない\n\n**インポートスタイル：** `node:` プレフィックスを名前空間インポートでのみ使用する（`node:fs` や `node:path` からの名前付きインポートは使わない）。\n\n**追加の Bun 規約：**\n\n- 短い非ストリーミングコマンドには Bun Shell `$` を優先する。ストリーミング I/O やプロセス制御が必要な場合にのみ `Bun.spawn` を使用する。\n- ファイルには `Bun.file()`/`Bun.write()` を、ディレクトリには `node:fs/promises` を使用する。\n- `Bun.file().exists()` チェックを避ける。try/catch で `isEnoent` ハンドリングを使用する。\n- `setTimeout` ラッパーより `Bun.sleep(ms)` を優先する。\n\n**誤り：**\n\n```typescript\n// BROKEN: env vars may be undefined, \"~\" is not expanded\nconst home = Bun.env.HOME || \"~\";\nconst tmp = Bun.env.TMPDIR || \"/tmp\";\n```\n\n**正しい：**\n\n```typescript\nimport * as os from \"node:os\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst configDir = path.join(os.homedir(), \".config\", \"myapp\");\nconst tempDir = fs.mkdtempSync(path.join(os.tmpdir(), \"myapp-\"));\n```\n\n## 5) Bun エンベッドを優先する（コピーしない）\n\nビルド時にランタイムアセットやベンダーファイルをコピーしないでください。\n\n- アップストリームがアセットを dist フォルダにコピーしている場合、Bun フレンドリーなエンベッドに置換する。\n- プロンプトは静的な `.md` ファイルです。インラインプロンプト文字列の代わりに Bun テキストインポート（`with { type: \"text\" }`）と Handlebars を使用する。\n- 隣接する非テキストリソースの読み込みには `import.meta.dir` + `Bun.file` を使用する。\n- アセットをリポジトリ内に保持し、バンドラーに含めさせる。\n- ユーザーが明示的に要求しない限り、コピースクリプトを排除する。\n- アップストリームが実行時にバンドルされたフォールバックファイルを読み込んでいる場合、ファイルシステム読み込みを Bun テキストエンベッドインポートに置換する。\n  - 例（Codex instructions フォールバック）：\n    - `const FALLBACK_PROMPT_PATH = join(import.meta.dir, \"codex-instructions.md\");` → 削除\n    - `import FALLBACK_INSTRUCTIONS from \"./codex-instructions.md\" with { type: \"text\" };`\n    - `readFileSync(FALLBACK_PROMPT_PATH, \"utf8\")` の代わりに `return FALLBACK_INSTRUCTIONS;` を使用\n\n## 6) `package.json` を慎重に移植する\n\n`package.json` はコントラクトとして扱います。意図的にマージしてください。\n\n- 移植で変更が必要な場合を除き、既存の `name`、`version`、`type`、`exports`、`bin` を保持する。\n- npm/node スクリプトを Bun 同等のものに置換する（例：`bun check`、`bun test`）。\n- 依存関係が正しいスコープを使用していることを確認する。\n- 型エラーを修正するために依存関係をダウングレードしない。代わりにアップグレードする。\n- ワークスペースパッケージリンクと `peerDependencies` を検証する。\n\n## 7) コードスタイルとツールを揃える\n\n- 既存のフォーマット規約を保持する。\n- 必要でない限り `any` を導入しない。\n- 動的インポートやインライン型インポートを避ける。トップレベルインポートのみを使用する。\n- コード内でプロンプトを構築しない。プロンプトは Handlebars でレンダリングされる静的な `.md` ファイルです。\n- coding-agent では `console.log`/`console.warn`/`console.error` を使用しない。`@f5-sales-demo/pi-utils` の `logger` を使用する。\n- `new Promise((resolve, reject) => ...)` の代わりに `Promise.withResolvers()` を使用する。\n- **クラスフィールドやメソッドに `private`/`protected`/`public` キーワードを使用しない。** カプセル化には ES `#` プライベートフィールドを使用し、アクセス可能なメンバーはキーワードなし（bare）にする。唯一の例外はコンストラクタパラメータプロパティ（`constructor(private readonly x: T)`）で、TypeScript が要求するためキーワードが必要です。アップストリームコードで `private foo` や `protected bar` を使用している場合、`#foo`（プライベート）または bare `bar`（アクセス可能）に変換する。\n- 新しいアドホックコードよりも既存のヘルパーやユーティリティを優先する。\n- このリポジトリで既に行われた Bun ファーストのインフラ変更を維持する：\n  - ランタイムは Bun（Node エントリーポイントなし）。\n  - パッケージマネージャーは Bun（npm ロックファイルなし）。\n  - 重い Node API（`child_process`、`readline`）は Bun 同等のものに置換済み。\n  - 軽量な Node API（`os.homedir`、`os.tmpdir`、`fs.mkdtempSync`、`path.*`）は保持。\n  - CLI の shebang は `bun` を使用（`node` でも `tsx` でもない）。\n  - パッケージはソースファイルを直接使用（TypeScript ビルドステップなし）。\n  - CI ワークフローはインストール/チェック/テストに Bun を実行。\n\n## 8) 古い互換性レイヤーを削除する\n\n要求されない限り、アップストリームの互換性シムを削除してください。\n\n- 置換された古い API を削除する。\n- すべての呼び出し箇所を新しい API に直接更新する。\n- `*_v2` や並列バージョンを保持しない。\n\n## 9) ドキュメントと参照を更新する\n\n- 適切な箇所で pi-mono リポジトリのリンクを置換する。\n- サンプルを Bun と正しいパッケージスコープを使用するように更新する。\n- README の手順が現在のリポジトリの動作と一致していることを確認する。\n\n## 10) 移植を検証する\n\n変更後に標準チェックを実行する：\n\n- `bun check`\n\n変更に関係のない既存の失敗チェックがある場合は、それを明示的に指摘してください。\nテストは Bun のランナーを使用します（Vitest ではありません）が、明示的に要求された場合にのみ `bun test` を実行してください。\n\n## 11) 改善された機能を保護する（リグレッショントラップリスト）\n\nローカルで既に動作を改善している場合、それらを**交渉不可**として扱ってください。移植前に改善点を書き出し、マージで失われないように明示的なチェックを追加してください。\n\n- **期待される動作を凍結する**：各改善点について短い「移植前/移植後」のメモを追加する（入力、出力、デフォルト値、エッジケース）。これにより暗黙のロールバックを防止します。\n- **旧 → 新 API をマッピングする**：アップストリームが概念名を変更した場合（hooks → extensions、custom tools → tools など）、すべての古いエントリーポイントが正しく接続されていることを確認する。1 つのフラグやエクスポートの見落としが機能喪失を意味します。\n- **エクスポートを検証する**：`package.json` の `exports`、公開型、バレルファイルを確認する。アップストリームからの移植では、ローカルの追加機能の再エクスポートを忘れがちです。\n- **非ハッピーパスをカバーする**：エラーハンドリング、タイムアウト、フォールバックロジックを修正した場合、テストまたは少なくともそれらのパスを実行する手動チェックリストを追加する。\n- **デフォルト値と設定マージ順序を確認する**：改善はデフォルト値に存在することが多い。新しいデフォルトが元に戻っていないことを確認する（例：新しい設定の優先順位、無効化された機能、ツールリスト）。\n- **env/shell 動作を監査する**：実行やサンドボックスを修正した場合、新しいパスが依然としてサニタイズされた env を使用し、エイリアス/関数オーバーライドを再導入していないことを確認する。\n- **対象サンプルを再実行する**：「正常動作確認済み」のサンプルの最小セットを保持し、移植後にそれらを実行する（CLI フラグ、拡張機能の登録、ツールの実行）。\n\n## 12) リワークされたコードを検出して対処する\n\nファイルを移植する前に、アップストリームが大幅にリファクタリングしたかどうかを確認してください：\n\n```bash\n# Compare the file you're about to port against what you have locally\ngit diff HEAD upstream/main -- path/to/file.ts\n```\n\ndiff がファイルが**リワーク**された（単なるパッチではない）ことを示している場合：\n\n- 新しい抽象化、名前変更された概念、統合されたモジュール、変更されたデータフロー\n\n移植前に**新しい実装を徹底的に読む**必要があります。リワークされたコードのブラインドマージは機能を失います。理由は以下の通りです：\n\n注意：インタラクティブモードは最近 controllers/utils/types に分割されました。関連する変更をバックポートする際は、作成した個別ファイルに更新を移植し、`interactive-mode.ts` の配線が同期していることを確認してください。\n\n1. **デフォルトが暗黙的に変更される** - 新しい変数 `defaultFoo = [a, b]` が、`[a, b, c, d, e]` を返していた古い `getAllFoo()` を置換する可能性があります。\n\n2. **API オプションが欠落する** - システムが統合される場合（例：`hooks` + `customTools` → `extensions`）、古いオプションが新しい実装に接続されない可能性があります。\n\n3. **コードパスが陳腐化する** - 名前変更された概念（例：`hookMessage` → `custom`）は、定義だけでなく、すべての switch 文、型ガード、ハンドラーでの更新が必要です。\n\n4. **コンテキスト/ケイパビリティが縮小する** - 古い API が公開していた `{ logger, typebox, pi }` を新しい API が含め忘れている可能性があります。\n\n### セマンティック移植プロセス\n\nアップストリームがモジュールをリワークした場合：\n\n1. **古い実装を読む** - 何をしていたか、どのオプションを受け入れていたか、何を公開していたかを理解する。\n\n2. **新しい実装を読む** - 新しい抽象化と、それが古い動作にどのようにマッピングされるかを理解する。\n\n3. **機能パリティを検証する** - 古いコードの各ケイパビリティについて、新しいコードがそれを保持しているか、明示的に削除しているかを確認する。\n\n4. **残存を grep する** - switch 文、ハンドラー、UI コンポーネントで見落とされた可能性のある古い名前/概念を検索する。\n\n5. **境界をテストする** - CLI フラグ、SDK オプション、イベントハンドラー、デフォルト値 — これらがリグレッションの潜伏場所です。\n\n### クイックチェック\n\n```bash\n# Find all uses of an old concept that may need updating\nrg \"oldConceptName\" --type ts\n\n# Compare default values between versions\ngit show upstream/main:path/to/file.ts | rg \"default|DEFAULT\"\n\n# Check if all enum/union values have handlers\nrg \"case \\\"\" path/to/file.ts\n```\n\n## 13) クイック監査チェックリスト\n\n完了前の最終パスとして使用してください：\n\n- [ ] インポート拡張子がローカルパッケージの規約に従っている（`.js` の一括除去なし）\n- [ ] 新規/移植コードに Node 専用 API がない\n- [ ] すべてのパッケージスコープが更新されている\n- [ ] `package.json` スクリプトが Bun を使用している\n- [ ] プロンプトが `.md` テキストインポートである（インラインプロンプト文字列なし）\n- [ ] coding-agent に `console.*` がない（`logger` を使用）\n- [ ] アセットが Bun エンベッドパターンで読み込まれている（コピースクリプトなし）\n- [ ] テストまたはチェックが実行される（またはブロックされていることが明示的に記載されている）\n- [ ] 機能リグレッションがない（セクション 11-12 を参照）\n\n## 14) コミットメッセージフォーマット\n\nバックポートをコミットする際は、リポジトリフォーマット `<type>(scope): <past-tense description>` に従い、コミット範囲をタイトルに含めてください。\n\n```\nfix(coding-agent): backported pi-mono changes (<from>..<to>)\n\npackages/<package>:\n- <type>: <description>\n- <type>: <description> (#<issue> by @<contributor>)\n\npackages/<other-package>:\n- <type>: <description>\n```\n\n**例：**\n\n```\nfix(coding-agent): backported pi-mono changes (9f3eef65f..52532c7c0)\n\npackages/ai:\n- fix: handle \"sensitive\" stop reason from Anthropic API\n- fix: normalize tool call IDs with special characters for Responses API\n- fix: add overflow detection for Bedrock, MiniMax, Kimi providers\n- fix: 429 status is rate limiting, not context overflow\n\npackages/tui:\n- fix: refactored autocomplete state tracking\n- fix: file autocomplete should not trigger on empty text\n- fix: configurable autocomplete max visible items\n- fix: improved table column width calculation with word-aware wrapping\n\npackages/coding-agent:\n- fix: preserve external config.yml edits on save (#1046 by @nicobailonMD)\n- fix: resolve macOS NFD and curly quote variants in file paths\n```\n\n**ルール：**\n\n- パッケージごとに変更をグループ化する\n- Conventional Commit のタイプを使用する（`fix`、`feat`、`refactor`、`perf`、`docs`）\n- 外部コントリビューションにはアップストリームの issue/PR 番号とコントリビューターの帰属を含める\n- タイトルのコミット範囲は同期ポイントの追跡に役立つ\n\n## 15) 意図的な分岐\n\n当フォークにはアップストリームと異なるアーキテクチャ上の決定があります。**以下のアップストリームパターンは移植しないでください：**\n\n### UI アーキテクチャ\n\n| アップストリーム                                    | 当フォーク                                                  | 理由                                                                |\n| ------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------- |\n| `FooterDataProvider` クラス                  | `StatusLineComponent`                                     | よりシンプルで統合されたステータスライン                                       |\n| `ctx.ui.setHeader()` / `ctx.ui.setFooter()` | 非 TUI モードではスタブ                                     | TUI で実装済み、それ以外では no-op                                   |\n| `ctx.ui.setEditorComponent()`               | 非 TUI モードではスタブ                                     | TUI で実装済み、それ以外では no-op                                   |\n| `InteractiveModeOptions` オプションオブジェクト     | 位置引数コンストラクタ（options 型はエクスポート維持） | コンストラクタシグネチャを維持。アップストリームがフィールド追加時に型を更新 |\n\n### コンポーネント命名\n\n| アップストリーム                     | 当フォーク                |\n| ---------------------------- | ----------------------- |\n| `extension-input.ts`         | `hook-input.ts`         |\n| `extension-selector.ts`      | `hook-selector.ts`      |\n| `ExtensionInputComponent`    | `HookInputComponent`    |\n| `ExtensionSelectorComponent` | `HookSelectorComponent` |\n\n### API 命名\n\n| アップストリーム                                 | 当フォーク                                 | 備考                                     |\n| ---------------------------------------- | ---------------------------------------- | ----------------------------------------- |\n| `sessionManager.appendSessionInfo(name)` | `sessionManager.setSessionName(name)`    | 全体で `sessionName` を使用           |\n| `sessionManager.getSessionName()`        | `sessionManager.getSessionName()`        | 同じ（アップストリームの RPC に合わせて統一済み） |\n| `agent.sessionName` / `setSessionName()` | `agent.sessionName` / `setSessionName()` | 同じ                                      |\n\n### ファイル統合\n\n| アップストリーム                                           | 当フォーク                                | 理由                                  |\n| -------------------------------------------------- | --------------------------------------- | --------------------------------------- |\n| `clipboard.ts` + `clipboard-image.ts`（ツールファイル） | `@f5-sales-demo/pi-natives` clipboard モジュール | N-API ネイティブ実装に統合 |\n\n### テストフレームワーク\n\n| アップストリーム                  | 当フォーク                      |\n| ------------------------- | ----------------------------- |\n| `vitest` と `vi.mock()` | `bun:test` と bun の `vi` |\n| `node:test` アサーション    | `expect()` マッチャー           |\n\n### ツールアーキテクチャ\n\n| アップストリーム                            | 当フォーク                                                          | 備考                                                     |\n| ----------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------- |\n| `createTool(cwd: string, options?)` | `createTools(session: ToolSession)` via `BUILTIN_TOOLS` レジストリ  | ツールファクトリは `ToolSession` を受け取り `null` を返せる |\n| ツールごとの `*Operations` インターフェース   | ツールごとのインターフェースは維持（`FindOperations`、`GrepOperations`）   | SSH/リモートオーバーライドに使用                             |\n| Node.js `fs/promises` を全面使用    | ファイルには `Bun.file()`/`Bun.write()`、ディレクトリには `node:fs/promises` | 簡素化できる場合は Bun API を優先                        |\n\n### 認証ストレージ\n\n| アップストリーム                        | 当フォーク                                    | 備考                                        |\n| ------------------------------- | ------------------------------------------- | -------------------------------------------- |\n| `proper-lockfile` + `auth.json` | `agent.db` (bun:sqlite)                     | 認証情報は `agent.db` に排他的に保存 |\n| プロバイダーごとに単一認証情報  | ラウンドロビン選択による複数認証情報 | セッションアフィニティとバックオフロジックは維持 |\n\n### エクステンション\n\n| アップストリーム                      | 当フォーク                                   |\n| ----------------------------- | ------------------------------------------ |\n| TypeScript 読み込みに `jiti` | ネイティブ Bun `import()`                      |\n| `pkg.pi` マニフェストフィールド       | `pkg.xcsh ?? pkg.pi`（当名前空間を優先） |\n\n### スキップすべきアップストリーム機能\n\n移植時に、以下のファイル/機能は**完全にスキップ**してください：\n\n- `footer-data-provider.ts` — StatusLineComponent を使用しています\n- `clipboard-image.ts` — clipboard は `@f5-sales-demo/pi-natives` N-API モジュールにあります\n- GitHub ワークフローファイル — 独自の CI があります\n- `models.generated.ts` — 自動生成のため、ローカルで再生成（代わりに models.json として）\n\n### 当フォークで追加した機能（これらを保持する）\n\n以下は当フォークに存在しますがアップストリームには存在しません。**絶対に上書きしないでください：**\n\n- インタラクティブモードの `StatusLineComponent`\n- セッションアフィニティ付き複数認証情報\n- ケイパビリティベースのディスカバリシステム（`defineCapability`、`registerProvider`、`loadCapability`、`skillCapability` など）\n- MCP/Exa/SSH 統合\n- フォーマット・オン・セーブの LSP ライトスルー\n- Bash インターセプション（`checkBashInterception`）\n- read ツールでのファジーパスサジェスチョン\n",
	"ja/configuration/rpc.md": "---\ntitle: RPCプロトコルリファレンス\ndescription: xcshコンポーネント間のプロセス間通信のためのJSON-RPCプロトコルリファレンス。\nsidebar:\n  order: 5\n  label: RPCプロトコル\ni18n:\n  sourceHash: b4a3ddaf08ab\n  translator: machine\n---\n\n# RPCプロトコルリファレンス\n\nRPCモードは、stdio上の改行区切りJSONプロトコルとしてコーディングエージェントを実行します。\n\n- **stdin**: コマンド（`RpcCommand`）および拡張UIレスポンス\n- **stdout**: コマンドレスポンス（`RpcResponse`）、セッション/エージェントイベント、拡張UIリクエスト\n\n主要な実装:\n\n- `src/modes/rpc/rpc-mode.ts`\n- `src/modes/rpc/rpc-types.ts`\n- `src/session/agent-session.ts`\n- `packages/agent/src/agent.ts`\n- `packages/agent/src/agent-loop.ts`\n\n## 起動\n\n```bash\nxcsh --mode rpc [regular CLI options]\n```\n\n動作に関する注意事項:\n\n- `@file` CLI引数はRPCモードでは拒否されます。\n- RPCモードでは、余分なモデル呼び出しを避けるため、デフォルトで自動セッションタイトル生成が無効になっています。\n- RPCモードでは、ワークフローに影響する `todo.*`、`task.*`、および `async.*` の設定を、ユーザーのオーバーライドを継承する代わりに組み込みのデフォルト値にリセットします。\n- プロセスはstdinをJSONLとして読み取ります（`readJsonl(Bun.stdin.stream())`）。\n- stdinが閉じられると、プロセスは終了コード `0` で終了します。\n- レスポンス/イベントは1行に1つのJSONオブジェクトとして書き込まれます。\n\n## トランスポートとフレーミング\n\n各フレームは、`\\n` が後続する単一のJSONオブジェクトです。\n\nオブジェクトの形状自体以外にエンベロープはありません。\n\n### 送信フレームカテゴリ（stdout）\n\n1. `RpcResponse`（`{ type: \"response\", ... }`）\n2. `AgentSessionEvent` オブジェクト（`agent_start`、`message_update` など）\n3. `RpcExtensionUIRequest`（`{ type: \"extension_ui_request\", ... }`）\n4. 拡張エラー（`{ type: \"extension_error\", extensionPath, event, error }`）\n\n### 受信フレームカテゴリ（stdin）\n\n1. `RpcCommand`\n2. `RpcExtensionUIResponse`（`{ type: \"extension_ui_response\", ... }`）\n\n## リクエスト/レスポンスの相関\n\nすべてのコマンドはオプションの `id?: string` を受け付けます。\n\n- 指定された場合、通常のコマンドレスポンスは同じ `id` をエコーバックします。\n- `RpcClient` はこれを利用して保留中のリクエストを解決します。\n\nランタイムにおける重要なエッジケースの動作:\n\n- 不明なコマンドのレスポンスは `id: undefined` で送信されます（リクエストに `id` があった場合でも）。\n- 入力ループでのパース/ハンドラー例外は `command: \"parse\"` で `id: undefined` として送信されます。\n- `prompt` と `abort_and_prompt` は即座に成功を返しますが、非同期プロンプトのスケジューリングが失敗した場合、**同じ** idで後続のエラーレスポンスを送信する場合があります。\n\n## コマンドスキーマ（正規）\n\n`RpcCommand` は `src/modes/rpc/rpc-types.ts` で定義されています:\n\n### プロンプト\n\n- `{ id?, type: \"prompt\", message: string, images?: ImageContent[], streamingBehavior?: \"steer\" | \"followUp\" }`\n- `{ id?, type: \"steer\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"follow_up\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"abort\" }`\n- `{ id?, type: \"abort_and_prompt\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"new_session\", parentSession?: string }`\n\n### 状態\n\n- `{ id?, type: \"get_state\" }`\n- `{ id?, type: \"set_todos\", phases: TodoPhase[] }`\n- `{ id?, type: \"set_host_tools\", tools: RpcHostToolDefinition[] }`\n\n### モデル\n\n- `{ id?, type: \"set_model\", provider: string, modelId: string }`\n- `{ id?, type: \"cycle_model\" }`\n- `{ id?, type: \"get_available_models\" }`\n\n### 思考\n\n- `{ id?, type: \"set_thinking_level\", level: ThinkingLevel }`\n- `{ id?, type: \"cycle_thinking_level\" }`\n\n### キューモード\n\n- `{ id?, type: \"set_steering_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_follow_up_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_interrupt_mode\", mode: \"immediate\" | \"wait\" }`\n\n### コンパクション\n\n- `{ id?, type: \"compact\", customInstructions?: string }`\n- `{ id?, type: \"set_auto_compaction\", enabled: boolean }`\n\n### リトライ\n\n- `{ id?, type: \"set_auto_retry\", enabled: boolean }`\n- `{ id?, type: \"abort_retry\" }`\n\n### Bash\n\n- `{ id?, type: \"bash\", command: string }`\n- `{ id?, type: \"abort_bash\" }`\n\n### セッション\n\n- `{ id?, type: \"get_session_stats\" }`\n- `{ id?, type: \"export_html\", outputPath?: string }`\n- `{ id?, type: \"switch_session\", sessionPath: string }`\n- `{ id?, type: \"branch\", entryId: string }`\n- `{ id?, type: \"get_branch_messages\" }`\n- `{ id?, type: \"get_last_assistant_text\" }`\n- `{ id?, type: \"set_session_name\", name: string }`\n\n### メッセージ\n\n- `{ id?, type: \"get_messages\" }`\n\n## レスポンススキーマ\n\nすべてのコマンド結果は `RpcResponse` を使用します:\n\n- 成功: `{ id?, type: \"response\", command: <command>, success: true, data?: ... }`\n- 失敗: `{ id?, type: \"response\", command: string, success: false, error: string }`\n\nデータペイロードはコマンド固有であり、`rpc-types.ts` で定義されています。\n\n### `get_state` ペイロード\n\n```json\n{\n  \"model\": { \"provider\": \"...\", \"id\": \"...\" },\n  \"thinkingLevel\": \"off|minimal|low|medium|high|xhigh\",\n  \"isStreaming\": false,\n  \"isCompacting\": false,\n  \"steeringMode\": \"all|one-at-a-time\",\n  \"followUpMode\": \"all|one-at-a-time\",\n  \"interruptMode\": \"immediate|wait\",\n  \"sessionFile\": \"...\",\n  \"sessionId\": \"...\",\n  \"sessionName\": \"...\",\n  \"autoCompactionEnabled\": true,\n  \"messageCount\": 0,\n  \"queuedMessageCount\": 0,\n  \"todoPhases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Todos\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the tool surface\",\n          \"status\": \"in_progress\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n### `set_todos` ペイロード\n\n現在のセッションのインメモリTodo状態を置き換え、正規化されたフェーズリストを返します:\n\n```json\n{\n  \"id\": \"req_2\",\n  \"type\": \"set_todos\",\n  \"phases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Evaluation\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the read tool surface\",\n          \"status\": \"in_progress\"\n        },\n        {\n          \"id\": \"task-2\",\n          \"content\": \"Exercise edit operations\",\n          \"status\": \"pending\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nこれは、最初のプロンプトの前にプランを事前設定したいホストに便利です。\n\n### `set_host_tools` ペイロード\n\nRPCサーバーがstdio経由でコールバックできるホスト所有ツールの現在のセットを置き換えます:\n\n```json\n{\n  \"id\": \"req_3\",\n  \"type\": \"set_host_tools\",\n  \"tools\": [\n    {\n      \"name\": \"echo_host\",\n      \"label\": \"Echo Host\",\n      \"description\": \"Echo a value from the embedding host\",\n      \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"message\": { \"type\": \"string\" }\n        },\n        \"required\": [\"message\"],\n        \"additionalProperties\": false\n      }\n    }\n  ]\n}\n```\n\nレスポンスペイロードは以下の通りです:\n\n```json\n{\n  \"toolNames\": [\"echo_host\"]\n}\n```\n\nこれらのツールは、次のモデル呼び出しの前にアクティブセッションのツールレジストリに追加されます。`set_host_tools` を再送信すると、以前のホスト所有セットが置き換えられます。\n\n## イベントストリームスキーマ\n\nRPCモードは、`AgentSession.subscribe(...)` から `AgentSessionEvent` オブジェクトを転送します。\n\n一般的なイベントタイプ:\n\n- `agent_start`、`agent_end`\n- `turn_start`、`turn_end`\n- `message_start`、`message_update`、`message_end`\n- `tool_execution_start`、`tool_execution_update`、`tool_execution_end`\n- `auto_compaction_start`、`auto_compaction_end`\n- `auto_retry_start`、`auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n- `todo_auto_clear`\n\n拡張ランナーのエラーは以下のように個別に送信されます:\n\n```json\n{ \"type\": \"extension_error\", \"extensionPath\": \"...\", \"event\": \"...\", \"error\": \"...\" }\n```\n\n`message_update` には `assistantMessageEvent` にストリーミングデルタ（テキスト/思考/ツールコールのデルタ）が含まれます。\n\n## プロンプト/キューの並行性と順序\n\nこれは最も重要な運用上の動作です。\n\n### 即時確認応答 vs 完了\n\n`prompt` と `abort_and_prompt` は**即座に確認応答**されます:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n```\n\nこれは以下を意味します:\n\n- コマンドの受け入れ != 実行の完了\n- 最終的な完了は `agent_end` を通じて観察されます\n\n### ストリーミング中\n\n`AgentSession.prompt()` はアクティブなストリーミング中に `streamingBehavior` を必要とします:\n\n- `\"steer\"` => キューに入れられたステアリングメッセージ（割り込みパス）\n- `\"followUp\"` => キューに入れられたフォローアップメッセージ（ターン後パス）\n\nストリーミング中に省略すると、プロンプトは失敗します。\n\n### キューのデフォルト\n\nコーディングエージェント設定スキーマ（`packages/coding-agent/src/config/settings-schema.ts`）より:\n\n- `steeringMode`: `\"one-at-a-time\"`\n- `followUpMode`: `\"one-at-a-time\"`\n- `interruptMode`: `\"wait\"`\n\n### モードのセマンティクス\n\n- `set_steering_mode` / `set_follow_up_mode`\n  - `\"one-at-a-time\"`: ターンごとにキューから1つのメッセージをデキュー\n  - `\"all\"`: キュー全体を一度にデキュー\n- `set_interrupt_mode`\n  - `\"immediate\"`: ツール実行がツールコール間でステアリングをチェックし、保留中のステアリングがターン内の残りのツールコールを中止できます\n  - `\"wait\"`: ターン完了までステアリングを遅延\n\n## 拡張UIサブプロトコル\n\nRPCモードの拡張機能は、リクエスト/レスポンスUIフレームを使用します。\n\n### 送信リクエスト\n\n`RpcExtensionUIRequest`（`type: \"extension_ui_request\"`）メソッド:\n\n- `select`、`confirm`、`input`、`editor`\n- `notify`、`setStatus`、`setWidget`、`setTitle`、`set_editor_text`\n\nランタイムに関する注意:\n\n- RPCモードでは自動セッションタイトル生成が無効になっており、`setTitle` UIリクエストもデフォルトで抑制されます。これは、ほとんどのホストが意味のあるターミナルタイトルサーフェスを持っていないためです。UIイベントのみをオプトインするには `PI_RPC_EMIT_TITLE=1` を設定してください。\n\n例:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"123\", \"method\": \"confirm\", \"title\": \"Confirm\", \"message\": \"Continue?\", \"timeout\": 30000 }\n```\n\n### 受信レスポンス\n\n`RpcExtensionUIResponse`（`type: \"extension_ui_response\"`）:\n\n- `{ type: \"extension_ui_response\", id: string, value: string }`\n- `{ type: \"extension_ui_response\", id: string, confirmed: boolean }`\n- `{ type: \"extension_ui_response\", id: string, cancelled: true }`\n\nダイアログにタイムアウトがある場合、RPCモードはタイムアウト/中止が発生するとデフォルト値に解決します。\n\n## ホストツールサブプロトコル\n\nRPCホストは `set_host_tools` を送信してエージェントにカスタムツールを公開し、同じトランスポート上で実行リクエストを処理できます。\n\n### 送信リクエスト\n\nエージェントがホストにこれらのツールの1つを実行してほしい場合、RPCモードは以下を送信します:\n\n```json\n{\n  \"type\": \"host_tool_call\",\n  \"id\": \"host_1\",\n  \"toolCallId\": \"toolu_123\",\n  \"toolName\": \"echo_host\",\n  \"arguments\": { \"message\": \"hello\" }\n}\n```\n\nツール実行が後に中止された場合、RPCモードは以下を送信します:\n\n```json\n{\n  \"type\": \"host_tool_cancel\",\n  \"id\": \"host_cancel_1\",\n  \"targetId\": \"host_1\"\n}\n```\n\n### 受信アップデートと完了\n\nホストはオプションで進捗をストリーミングできます:\n\n```json\n{\n  \"type\": \"host_tool_update\",\n  \"id\": \"host_1\",\n  \"partialResult\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"working\" }]\n  }\n}\n```\n\n完了には以下を使用します:\n\n```json\n{\n  \"type\": \"host_tool_result\",\n  \"id\": \"host_1\",\n  \"result\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"done\" }]\n  }\n}\n```\n\n返されたコンテンツをツールエラーとして表示するには、`host_tool_result` に `isError: true` を設定してください。\n\n## エラーモデルとリカバリ可能性\n\n### コマンドレベルの失敗\n\n失敗は `success: false` と文字列の `error` で表されます。\n\n```json\n{ \"id\": \"req_2\", \"type\": \"response\", \"command\": \"set_model\", \"success\": false, \"error\": \"Model not found: provider/model\" }\n```\n\n### リカバリ可能性の期待値\n\n- ほとんどのコマンドの失敗はリカバリ可能であり、プロセスは存続します。\n- 不正なJSONL / パースループの例外は `parse` エラーレスポンスを送信し、後続の行の読み取りを続行します。\n- 空の `set_session_name` は拒否されます（`Session name cannot be empty`）。\n- 不明な `id` を持つ拡張UIレスポンスは無視されます。\n- プロセスの終了条件は、stdinの閉鎖または拡張機能によるシャットダウンのトリガーです。\n\n## コンパクトなコマンドフロー\n\n### 1) プロンプトとストリーム\n\nstdin:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"prompt\", \"message\": \"Summarize this repo\" }\n```\n\nstdout シーケンス（典型的）:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n{ \"type\": \"agent_start\" }\n{ \"type\": \"message_update\", \"assistantMessageEvent\": { \"type\": \"text_delta\", \"delta\": \"...\" }, \"message\": { \"role\": \"assistant\", \"content\": [] } }\n{ \"type\": \"agent_end\", \"messages\": [] }\n```\n\n### 2) 明示的なキューポリシーによるストリーミング中のプロンプト\n\nstdin:\n\n```json\n{ \"id\": \"req_2\", \"type\": \"prompt\", \"message\": \"Also include risks\", \"streamingBehavior\": \"followUp\" }\n```\n\n### 3) キュー動作の確認と調整\n\nstdin:\n\n```json\n{ \"id\": \"q1\", \"type\": \"get_state\" }\n{ \"id\": \"q2\", \"type\": \"set_steering_mode\", \"mode\": \"all\" }\n{ \"id\": \"q3\", \"type\": \"set_interrupt_mode\", \"mode\": \"wait\" }\n```\n\n### 4) 拡張UIラウンドトリップ\n\nstdout:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"ui_7\", \"method\": \"input\", \"title\": \"Branch name\", \"placeholder\": \"feature/...\" }\n```\n\nstdin:\n\n```json\n{ \"type\": \"extension_ui_response\", \"id\": \"ui_7\", \"value\": \"feature/rpc-host\" }\n```\n\n## `RpcClient` ヘルパーに関する注意事項\n\n`src/modes/rpc/rpc-client.ts` は便利なラッパーであり、プロトコル定義ではありません。\n\n現在のヘルパーの特性:\n\n- `bun <cliPath> --mode rpc` を起動します\n- 生成された `req_<n>` IDでレスポンスを相関させます\n- 認識された `AgentEvent` タイプのみをリスナーにディスパッチします\n- `setCustomTools()` およびホスト所有カスタムツールの `host_tool_call` / `host_tool_cancel` の自動処理をサポートします\n- すべてのプロトコルコマンドに対するヘルパーメソッドを公開して**いません**（例えば、`set_interrupt_mode` と `set_session_name` はプロトコル型にありますが、専用メソッドとしてラップされていません）\n\n完全なサーフェスカバレッジが必要な場合は、生のプロトコルフレームを使用してください。\n",
	"ja/configuration/sdk.md": "---\ntitle: SDK\ndescription: xcshコーディングエージェントランタイム上にカスタムエージェントおよびインテグレーションを構築するためのSDK。\nsidebar:\n  order: 6\n  label: SDK\ni18n:\n  sourceHash: 80f3a4374241\n  translator: machine\n---\n\n# SDK\n\nSDKは `@f5-sales-demo/xcsh` のインプロセスインテグレーションサーフェスです。\n自身のBun/Nodeプロセスからエージェントの状態、イベントストリーミング、ツールの接続、セッション制御に直接アクセスしたい場合に使用します。\n\n言語をまたいだ分離やプロセス分離が必要な場合は、RPCモードを使用してください。\n\n## インストール\n\n```bash\nbun add @f5-sales-demo/xcsh\n```\n\n## エントリポイント\n\n`@f5-sales-demo/xcsh` はパッケージルート（および `@f5-sales-demo/xcsh/sdk` 経由）からSDK APIをエクスポートします。\n\nエンベッダー向けのコアエクスポート:\n\n- `createAgentSession`\n- `SessionManager`\n- `Settings`\n- `AuthStorage`\n- `ModelRegistry`\n- `discoverAuthStorage`\n- ディスカバリーヘルパー（`discoverExtensions`、`discoverSkills`、`discoverContextFiles`、`discoverPromptTemplates`、`discoverSlashCommands`、`discoverCustomTSCommands`、`discoverMCPServers`）\n- ツールファクトリーサーフェス（`createTools`、`BUILTIN_TOOLS`、ツールクラス）\n\n## クイックスタート（自動ディスカバリーのデフォルト）\n\n```ts\nimport { createAgentSession } from \"@f5-sales-demo/xcsh\";\n\nconst { session, modelFallbackMessage } = await createAgentSession();\n\nif (modelFallbackMessage) {\n process.stderr.write(`${modelFallbackMessage}\\n`);\n}\n\nconst unsubscribe = session.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Summarize this repository in 3 bullets.\");\nunsubscribe();\nawait session.dispose();\n```\n\n## `createAgentSession()` がデフォルトで検出するもの\n\n`createAgentSession()` は「指定すれば上書き、省略すれば自動検出」の方針に従います。\n\n省略した場合、以下が解決されます:\n\n- `cwd`: `getProjectDir()`\n- `agentDir`: `~/.xcsh/agent`（`getAgentDir()` 経由）\n- `authStorage`: `discoverAuthStorage(agentDir)`\n- `modelRegistry`: `new ModelRegistry(authStorage)` + `await refresh()`\n- `settings`: `await Settings.init({ cwd, agentDir })`\n- `sessionManager`: `SessionManager.create(cwd)`（ファイルバック）\n- スキル / コンテキストファイル / プロンプトテンプレート / スラッシュコマンド / 拡張機能 / カスタムTSコマンド\n- `createTools(...)` 経由の組み込みツール\n- MCPツール（デフォルトで有効）\n- LSPインテグレーション（デフォルトで有効）\n\n### 必須入力とオプション入力\n\n通常、制御したいものだけを指定すれば十分です:\n\n- **必須**: 最小限のセッションには何も不要\n- **エンベッダーで明示的に指定することが多いもの**:\n    - `sessionManager`（インメモリまたはカスタムロケーションが必要な場合）\n    - `authStorage` + `modelRegistry`（認証情報やモデルのライフサイクルを自分で管理する場合）\n    - `model` または `modelPattern`（決定論的なモデル選択が重要な場合）\n    - `settings`（分離された設定やテスト用設定が必要な場合）\n\n## セッションマネージャーの動作（永続化 vs インメモリ）\n\n`AgentSession` は常に `SessionManager` を使用します。動作は使用するファクトリーによって異なります。\n\n### ファイルバック（デフォルト）\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.create(process.cwd()),\n});\n\nconsole.log(session.sessionFile); // 絶対パスの .jsonl ファイル\n```\n\n- 会話 / メッセージ / 状態デルタをセッションファイルに永続化します。\n- 再開 / オープン / 一覧 / フォークのワークフローをサポートします。\n- `session.sessionFile` が定義されます。\n\n### インメモリ\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.inMemory(),\n});\n\nconsole.log(session.sessionFile); // undefined\n```\n\n- ファイルシステムへの永続化なし。\n- テスト、エフェメラルワーカー、リクエストスコープのエージェントに有用です。\n- セッションメソッドは引き続き動作しますが、永続化固有の動作（ファイルの再開 / フォークパス）は当然ながら制限されます。\n\n### 再開 / オープン / 一覧ヘルパー\n\n```ts\nimport { SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst recent = await SessionManager.continueRecent(process.cwd());\nconst listed = await SessionManager.list(process.cwd());\nconst opened = listed[0] ? await SessionManager.open(listed[0].path) : null;\n```\n\n## モデルと認証の接続\n\n`createAgentSession()` はモデル選択とAPIキーの解決に `ModelRegistry` と `AuthStorage` を使用します。\n\n### 明示的な接続\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst available = modelRegistry.getAvailable();\nif (available.length === 0) throw new Error(\"No authenticated models available\");\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n model: available[0],\n thinkingLevel: \"medium\",\n sessionManager: SessionManager.inMemory(),\n});\n```\n\n### `model` が省略された場合の選択順序\n\n`model` / `modelPattern` が明示的に指定されていない場合:\n\n1. 既存セッションからモデルを復元（復元可能かつキーが利用可能な場合）\n2. 設定のデフォルトモデルロール（`default`）\n3. 有効な認証を持つ最初の利用可能なモデル\n\n復元に失敗した場合、`modelFallbackMessage` がフォールバック理由を説明します。\n\n### 認証の優先順位\n\n`AuthStorage.getApiKey(...)` は以下の順序で解決します:\n\n1. ランタイムオーバーライド（`setRuntimeApiKey`）\n2. `agent.db` に保存された認証情報\n3. プロバイダー環境変数\n4. カスタムプロバイダーリゾルバーのフォールバック（設定されている場合）\n\n## イベントサブスクリプションモデル\n\n`session.subscribe(listener)` でサブスクライブします。戻り値はアンサブスクライブ関数です。\n\n```ts\nconst unsubscribe = session.subscribe(event => {\n switch (event.type) {\n  case \"agent_start\":\n  case \"turn_start\":\n  case \"tool_execution_start\":\n   break;\n  case \"message_update\":\n   if (event.assistantMessageEvent.type === \"text_delta\") {\n    process.stdout.write(event.assistantMessageEvent.delta);\n   }\n   break;\n }\n});\n```\n\n`AgentSessionEvent` にはコアの `AgentEvent` に加えてセッションレベルのイベントが含まれます:\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n## プロンプトライフサイクル\n\n`session.prompt(text, options?)` が主要なエントリポイントです。\n\n動作:\n\n1. オプションのコマンド / テンプレート展開（`/` コマンド、カスタムコマンド、ファイルスラッシュコマンド、プロンプトテンプレート）\n2. 現在ストリーミング中の場合:\n    - `streamingBehavior: \"steer\" | \"followUp\"` が必要\n    - 処理を破棄せずにキューに追加\n3. アイドル状態の場合:\n    - モデルとAPIキーを検証\n    - ユーザーメッセージを追加\n    - エージェントターンを開始\n\n関連API:\n\n- `sendUserMessage(content, { deliverAs? })`\n- `steer(text, images?)`\n- `followUp(text, images?)`\n- `sendCustomMessage({ customType, content, ... }, { deliverAs?, triggerTurn? })`\n- `abort()`\n\n## ツールと拡張機能のインテグレーション\n\n### 組み込みとフィルタリング\n\n- 組み込みツールは `createTools(...)` と `BUILTIN_TOOLS` から提供されます。\n- `toolNames` は組み込みツールのアローリストとして機能します。\n- `customTools` および拡張機能で登録されたツールは引き続き含まれます。\n- 隠しツール（例: `submit_result`）は、オプションで必要とされない限りオプトインです。\n\n```ts\nconst { session } = await createAgentSession({\n toolNames: [\"read\", \"grep\", \"find\", \"write\"],\n requireSubmitResultTool: true,\n});\n```\n\n### 拡張機能\n\n- `extensions`: インライン `ExtensionFactory[]`\n- `additionalExtensionPaths`: 追加の拡張機能ファイルを読み込む\n- `disableExtensionDiscovery`: 自動拡張機能スキャンを無効化\n- `preloadedExtensions`: すでに読み込まれた拡張機能セットを再利用\n\n### ランタイムツールセットの変更\n\n`AgentSession` はランタイムアクティベーションの更新をサポートします:\n\n- `getActiveToolNames()`\n- `getAllToolNames()`\n- `setActiveToolsByName(names)`\n- `refreshMCPTools(mcpTools)`\n\nアクティブツールの変更を反映するためにシステムプロンプトが再構築されます。\n\n## ディスカバリーヘルパー\n\n内部ディスカバリーロジックを再実装せずに部分的な制御が必要な場合に使用します:\n\n- `discoverAuthStorage(agentDir?)`\n- `discoverExtensions(cwd?)`\n- `discoverSkills(cwd?, _agentDir?, settings?)`\n- `discoverContextFiles(cwd?, _agentDir?)`\n- `discoverPromptTemplates(cwd?, agentDir?)`\n- `discoverSlashCommands(cwd?)`\n- `discoverCustomTSCommands(cwd?, agentDir?)`\n- `discoverMCPServers(cwd?)`\n- `buildSystemPrompt(options?)`\n\n## サブエージェント向けオプション\n\nオーケストレーターを構築するSDKコンシューマー向け（タスクエグゼキューターフローに類似）:\n\n- `outputSchema`: 構造化出力の期待値をツールコンテキストに渡す\n- `requireSubmitResultTool`: `submit_result` ツールの組み込みを強制する\n- `taskDepth`: ネストされたタスクセッションの再帰深度コンテキスト\n- `parentTaskPrefix`: ネストされたタスク出力のアーティファクト命名プレフィックス\n\nこれらは通常の単一エージェントへの組み込みでは任意です。\n\n## `createAgentSession()` の戻り値\n\n```ts\ntype CreateAgentSessionResult = {\n session: AgentSession;\n extensionsResult: LoadExtensionsResult;\n setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;\n mcpManager?: MCPManager;\n modelFallbackMessage?: string;\n lspServers?: Array<{ name: string; status: \"ready\" | \"error\"; fileTypes: string[]; error?: string }>;\n};\n```\n\n`setToolUIContext(...)` は、エンベッダーがツールや拡張機能から呼び出されるUI機能を提供する場合にのみ使用してください。\n\n## 最小限の制御されたエンベッド例\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n Settings,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst settings = Settings.isolated({\n \"compaction.enabled\": true,\n \"retry.enabled\": true,\n});\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n settings,\n sessionManager: SessionManager.inMemory(),\n toolNames: [\"read\", \"grep\", \"find\", \"edit\", \"write\"],\n enableMCP: false,\n enableLsp: true,\n});\n\nsession.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Find all TODO comments in this repo and propose fixes.\");\nawait session.dispose();\n```\n",
	"ja/configuration/secrets.md": "---\ntitle: シークレットの難読化\ndescription: セッションログと出力から機密値を秘匿するシークレット難読化パイプライン。\nsidebar:\n  order: 3\n  label: シークレット\ni18n:\n  sourceHash: 1d9dc101c614\n  translator: machine\n---\n\n# シークレットの難読化\n\n機密値（APIキー、トークン、パスワード）がLLMプロバイダーに送信されるのを防ぎます。有効にすると、シークレットはプロセスを離れる前に決定論的なプレースホルダーに置換され、モデルが返すツール呼び出し引数では元の値に復元されます。\n\n## 有効化\n\nデフォルトで有効です。`/settings` UIまたは `config.yml` で直接切り替えできます：\n\n```yaml\nsecrets:\n  enabled: false\n```\n\n## 仕組み\n\n1. セッション開始時に、2つのソースからシークレットが収集されます：\n   - **環境変数** — 一般的なシークレットパターン（`*_KEY`、`*_SECRET`、`*_TOKEN`、`*_PASSWORD` など）に一致し、値が8文字以上のもの\n   - **`secrets.yml` ファイル**（下記参照）\n\n2. LLMへの送信メッセージでは、すべてのシークレット値が `<<$env:S0>>`、`<<$env:S1>>` などのプレースホルダーに置換されます。\n\n3. モデルが返すツール呼び出し引数は深く走査され、実行前にプレースホルダーが元の値に復元されます。\n\n各シークレットの処理方法を制御する2つのモードがあります：\n\n| モード | 動作 | 可逆性 |\n|---|---|---|\n| `obfuscate`（デフォルト） | インデックス付きプレースホルダー `<<$env:SN>>` に置換 | あり（ツール引数で難読化解除） |\n| `replace` | 決定論的な同じ長さの文字列に置換 | なし（一方向） |\n\n## secrets.yml\n\nYAMLでカスタムシークレットエントリを定義します。2つの場所がチェックされます：\n\n| レベル | パス | 用途 |\n|---|---|---|\n| グローバル | `~/.xcsh/agent/secrets.yml` | すべてのプロジェクト共通のシークレット |\n| プロジェクト | `<cwd>/.xcsh/secrets.yml` | プロジェクト固有のシークレット |\n\nプロジェクトエントリは、`content` が一致するグローバルエントリを上書きします。\n\n### スキーマ\n\n配列の各エントリには以下のフィールドがあります：\n\n| フィールド | 型 | 必須 | 説明 |\n|---|---|---|---|\n| `type` | `\"plain\"` または `\"regex\"` | はい | マッチ戦略 |\n| `content` | string | はい | シークレット値（plain）または正規表現パターン（regex） |\n| `mode` | `\"obfuscate\"` または `\"replace\"` | いいえ | デフォルト：`\"obfuscate\"` |\n| `replacement` | string | いいえ | カスタム置換文字列（replaceモードのみ） |\n| `flags` | string | いいえ | 正規表現フラグ（regexタイプのみ） |\n\n### 例\n\n#### プレーンシークレット\n\n```yaml\n# 特定のAPIキーを難読化（デフォルトモード）\n- type: plain\n  content: sk-proj-abc123def456\n\n# データベースパスワードを固定文字列に置換\n- type: plain\n  content: hunter2\n  mode: replace\n  replacement: \"********\"\n```\n\n#### 正規表現シークレット\n\n```yaml\n# AWS形式のキーをすべて難読化\n- type: regex\n  content: \"AKIA[0-9A-Z]{16}\"\n\n# 明示的なフラグによる大文字小文字を区別しないマッチ\n- type: regex\n  content: \"api[_-]?key\\\\s*=\\\\s*\\\\w+\"\n  flags: \"i\"\n\n# 正規表現リテラル構文（パターンとフラグを1つの文字列で指定）\n- type: regex\n  content: \"/bearer\\\\s+[a-zA-Z0-9._~+\\\\/=-]+/i\"\n```\n\n正規表現エントリは常にグローバルにスキャンされます（`g` フラグは自動的に適用されます）。正規表現リテラル構文 `/pattern/flags` は、`content` + `flags` を別々に指定する代替手段としてサポートされています。パターン内のエスケープされたスラッシュ（`\\\\/`）は正しく処理されます。\n\n#### replaceモードと正規表現の組み合わせ\n\n```yaml\n# 接続文字列を一方向で置換（不可逆）\n- type: regex\n  content: \"postgres://[^\\\\s]+\"\n  mode: replace\n  replacement: \"postgres://***\"\n```\n\n## 環境変数検出との相互作用\n\n環境変数は常に最初に収集されます。ファイルで定義されたエントリはその後に追加されるため、ファイルエントリは環境変数にないシークレット（設定ファイル、ハードコードされた値など）もカバーできます。同じ値が両方に存在する場合、ファイルエントリのモードが優先されます。\n\n## 主要ファイル\n\n- `src/secrets/index.ts` -- 読み込み、マージ、環境変数収集\n- `src/secrets/obfuscator.ts` -- `SecretObfuscator` クラス、プレースホルダー生成、メッセージ難読化\n- `src/secrets/regex.ts` -- 正規表現リテラルの解析とコンパイル\n- `src/config/settings-schema.ts` -- `secrets.enabled` 設定の定義\n",
	"ja/extensions/extension-loading.md": "---\ntitle: 拡張機能の読み込み（TypeScript/JavaScript モジュール）\ndescription: 解決、検証、キャッシュを備えた拡張機能向けの TypeScript および JavaScript モジュール読み込みパイプライン。\nsidebar:\n  order: 2\n  label: 拡張機能の読み込み\ni18n:\n  sourceHash: a8cea231c660\n  translator: machine\n---\n\n# 拡張機能の読み込み（TypeScript/JavaScript モジュール）\n\nこのドキュメントでは、コーディングエージェントが起動時に**拡張機能モジュール**（`.ts`/`.js`）を検出し読み込む方法について説明します。\n\n`gemini-extension.json` マニフェスト拡張機能については**対象外**です（別途ドキュメント化されています）。\n\n## このサブシステムの機能\n\n拡張機能の読み込みは、モジュールエントリファイルのリストを構築し、各モジュールを Bun でインポートし、そのファクトリを実行して、以下を返します：\n\n- 読み込まれた拡張機能の定義\n- パスごとの読み込みエラー（全体の読み込みを中断しない）\n- 後で `ExtensionRunner` が使用する共有の拡張機能ランタイムオブジェクト\n\n## 主要な実装ファイル\n\n- `src/extensibility/extensions/loader.ts` — パス検出 + インポート/実行\n- `src/extensibility/extensions/index.ts` — 公開エクスポート\n- `src/extensibility/extensions/runner.ts` — 読み込み後のランタイム/イベント実行\n- `src/discovery/builtin.ts` — 拡張機能モジュール用のネイティブ自動検出プロバイダー\n- `src/config/settings.ts` — マージされた `extensions` / `disabledExtensions` 設定の読み込み\n\n---\n\n## 拡張機能読み込みへの入力\n\n### 1) 自動検出されたネイティブ拡張機能モジュール\n\n`discoverAndLoadExtensions()` は、まず検出プロバイダーに `extension-module` ケイパビリティ項目を問い合わせ、次にプロバイダー `native` の項目のみを保持します。\n\n有効なネイティブの場所：\n\n- プロジェクト: `<cwd>/.xcsh/extensions`\n- ユーザー: `~/.xcsh/agent/extensions`\n\nパスルートはネイティブプロバイダー（`SOURCE_PATHS.native`）から取得されます。\n\n注意事項：\n\n- ネイティブの自動検出は現在 `.xcsh` ベースです。\n- レガシー `.pi` は `package.json` マニフェストキー（`pi.extensions`）では引き続き受け入れられますが、ここではネイティブルートとしては使用されません。\n\n### 2) 明示的に設定されたパス\n\n自動検出の後、設定されたパスが追加・解決されます。\n\nメインセッション起動パス（`sdk.ts`）における設定パスのソース：\n\n1. CLI で指定されたパス（`--extension/-e`、および `--hook` も拡張機能パスとして扱われます）\n2. 設定の `extensions` 配列（グローバル + プロジェクト設定のマージ）\n\nグローバル設定ファイル：\n\n- `~/.xcsh/agent/config.yml`（または `PI_CODING_AGENT_DIR` によるカスタムエージェントディレクトリ）\n\nプロジェクト設定ファイル：\n\n- `<cwd>/.xcsh/settings.json`\n\n例：\n\n```yaml\n# ~/.xcsh/agent/config.yml\nextensions:\n  - ~/my-exts/safety.ts\n  - ./local/ext-pack\n```\n\n```json\n{\n  \"extensions\": [\"./.xcsh/extensions/my-extra\"]\n}\n```\n\n---\n\n## 有効化/無効化の制御\n\n### 検出の無効化\n\n- CLI: `--no-extensions`\n- SDK オプション: `disableExtensionDiscovery`\n\n動作の違い：\n\n- SDK: `disableExtensionDiscovery=true` の場合でも、`loadExtensions()` を通じて `additionalExtensionPaths` は読み込まれます。\n- CLI パスの構築（`main.ts`）では、`--no-extensions` が設定されると CLI 拡張機能パスがクリアされるため、そのモードでは明示的な `-e/--hook` は転送されません。\n\n### 特定の拡張機能モジュールの無効化\n\n`disabledExtensions` 設定は拡張機能 ID 形式でフィルタリングします：\n\n- `extension-module:<derivedName>`\n\n`derivedName` はエントリパスに基づきます（`getExtensionNameFromPath`）。例：\n\n- `/x/foo.ts` -> `foo`\n- `/x/bar/index.ts` -> `bar`\n\n例：\n\n```yaml\ndisabledExtensions:\n  - extension-module:foo\n```\n\n---\n\n## パスとエントリの解決\n\n### パスの正規化\n\n設定されたパスの場合：\n\n1. Unicode スペースの正規化\n2. `~` の展開\n3. 相対パスの場合、現在の `cwd` に対して解決\n\n### 設定されたパスがファイルの場合\n\nモジュールエントリ候補として直接使用されます。\n\n### 設定されたパスがディレクトリの場合\n\n解決順序：\n\n1. そのディレクトリ内の `package.json` に `xcsh.extensions`（またはレガシー `pi.extensions`）がある場合 -> 宣言されたエントリを使用\n2. `index.ts`\n3. `index.js`\n4. それ以外の場合、1 レベルをスキャンして拡張機能エントリを検索：\n   - 直接の `*.ts` / `*.js`\n   - サブディレクトリの `index.ts` / `index.js`\n   - サブディレクトリの `package.json` に `xcsh.extensions` / `pi.extensions`\n\nルールと制約：\n\n- 1 つのサブディレクトリレベルを超える再帰的な検出は行われない\n- 宣言された `extensions` マニフェストエントリは、そのパッケージディレクトリを基準に解決される\n- 宣言されたエントリは、ファイルが存在し/アクセスが許可されている場合のみ含まれる\n- `*/index.{ts,js}` のペアでは、TypeScript が JavaScript より優先される\n- シンボリックリンクは有効なファイル/ディレクトリとして扱われる\n\n### 無視の動作はソースによって異なる\n\n- ネイティブの自動検出（検出ヘルパー内の `discoverExtensionModulePaths`）は、`gitignore: true` および `hidden: false` でネイティブ glob を使用します。\n- `loader.ts` における明示的に設定されたディレクトリスキャンは `readdir` ルールを使用し、gitignore フィルタリングは**適用しません**。\n\n---\n\n## 読み込み順序と優先順位\n\n`discoverAndLoadExtensions()` は 1 つの順序付きリストを構築し、`loadExtensions()` を呼び出します。\n\n順序：\n\n1. ネイティブで自動検出されたモジュール\n2. 明示的に設定されたパス（指定された順序で）\n\n`sdk.ts` における設定順序：\n\n1. CLI の追加パス\n2. 設定の `extensions`\n\n重複排除：\n\n- 絶対パスベース\n- 最初に見つかったパスが優先\n- 後の重複は無視される\n\n含意：同じモジュールパスが自動検出と明示的な設定の両方に存在する場合、最初の位置（自動検出段階）で 1 回だけ読み込まれます。\n\n---\n\n## モジュールのインポートとファクトリ契約\n\n各候補パスは動的インポートで読み込まれます：\n\n- `await import(resolvedPath)`\n- ファクトリは `module.default ?? module`\n- ファクトリは関数（`ExtensionFactory`）でなければならない\n\nエクスポートが関数でない場合、そのパスは構造化エラーで失敗し、読み込みは継続されます。\n\n---\n\n## 障害処理と分離\n\n### 読み込み中\n\n拡張機能パスごとに、障害は `{ path, error }` としてキャプチャされ、他のパスの読み込みを停止しません。\n\n一般的なケース：\n\n- インポート失敗 / ファイルが見つからない\n- 無効なファクトリエクスポート（非関数）\n- ファクトリ実行中にスローされた例外\n\n### ランタイム分離モデル\n\n- 拡張機能は**サンドボックス化されていません**（同一プロセス/ランタイム）。\n- 1 つの `EventBus` と 1 つの `ExtensionRuntime` インスタンスを共有します。\n- 読み込み中、ランタイムのアクションメソッドは意図的に `ExtensionRuntimeNotInitializedError` をスローします。アクションの配線は後で `ExtensionRunner.initialize()` で行われます。\n\n### 読み込み後\n\n`ExtensionRunner` を通じてイベントが実行される際、ハンドラーの例外はキャッチされ、ランナーループをクラッシュさせるのではなく拡張機能エラーとして発行されます。\n\n---\n\n## 最小限のユーザー/プロジェクトレイアウト例\n\n### ユーザーレベル\n\n```text\n~/.xcsh/agent/\n  config.yml\n  extensions/\n    guardrails.ts\n    audit/\n      index.ts\n```\n\n### プロジェクトレベル\n\n```text\n<repo>/\n  .xcsh/\n    settings.json\n    extensions/\n      checks/\n        package.json\n      lint-gates.ts\n```\n\n`checks/package.json`：\n\n```json\n{\n  \"xcsh\": {\n    \"extensions\": [\"./src/check-a.ts\", \"./src/check-b.js\"]\n  }\n}\n```\n\nレガシーマニフェストキーも引き続き受け入れられます：\n\n```json\n{\n  \"pi\": {\n    \"extensions\": [\"./index.ts\"]\n  }\n}\n```\n",
	"ja/extensions/extensions.md": "---\ntitle: 拡張機能\ndescription: 拡張機能ランタイムの概要：タイプ、ランナーライフサイクル、登録、および検出について説明します。\nsidebar:\n  order: 1\n  label: 概要\ni18n:\n  sourceHash: 14cc16dbd98b\n  translator: machine\n---\n\n# 拡張機能\n\n`packages/coding-agent` におけるランタイム拡張機能の作成に関する主要ガイドです。\n\nこのドキュメントでは、以下に含まれる現在の拡張機能ランタイムについて説明します：\n\n- `src/extensibility/extensions/types.ts`\n- `src/extensibility/extensions/runner.ts`\n- `src/extensibility/extensions/wrapper.ts`\n- `src/extensibility/extensions/index.ts`\n- `src/modes/controllers/extension-ui-controller.ts`\n\n検出パスおよびファイルシステムの読み込みルールについては、`docs/extension-loading.md` を参照してください。\n\n## 拡張機能とは\n\n拡張機能とは、デフォルトファクトリーをエクスポートする TS/JS モジュールです：\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nexport default function myExtension(pi: ExtensionAPI) {\n // handlers/tools/commands/renderers を登録する\n}\n```\n\n拡張機能は、以下のすべてを1つのモジュールに組み合わせることができます：\n\n- イベントハンドラー（`pi.on(...)`）\n- LLM 呼び出し可能ツール（`pi.registerTool(...)`）\n- スラッシュコマンド（`pi.registerCommand(...)`）\n- キーボードショートカットおよびフラグ\n- カスタムメッセージレンダリング\n- セッション/メッセージ注入 API（`sendMessage`、`sendUserMessage`、`appendEntry`）\n\n## ランタイムモデル\n\n1. 拡張機能がインポートされ、ファクトリー関数が実行されます。\n2. このロードフェーズ中、登録メソッドは有効ですが、ランタイムアクションメソッドはまだ初期化されていません。\n3. `ExtensionRunner.initialize(...)` が、アクティブモードのライブアクション/コンテキストを接続します。\n4. セッション/エージェント/ツールのライフサイクルイベントがハンドラーに送出されます。\n5. すべてのツール実行は、拡張機能インターセプション（`tool_call` / `tool_result`）でラップされます。\n\n```text\n拡張機能ライフサイクル（簡略版）\n\n読み込みパス\n   │\n   ▼\nモジュールのインポート + ファクトリー実行（登録のみ）\n   │\n   ▼\nExtensionRunner.initialize(mode/session/tool registry)\n   │\n   ├─ セッション/エージェントイベントをハンドラーに送出\n   ├─ ツール実行をラップ（tool_call/tool_result）\n   └─ ランタイムアクションを公開（sendMessage, setActiveTools, ...）\n```\n\n`loader.ts` の重要な制約：\n\n- 拡張機能のロード中に `pi.sendMessage()` などのアクションメソッドを呼び出すと、`ExtensionRuntimeNotInitializedError` がスローされます\n- まず登録を行い、ランタイムの動作はイベント/コマンド/ツールから実行してください\n\n## クイックスタート\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\nimport { Type } from \"@sinclair/typebox\";\n\nexport default function (pi: ExtensionAPI) {\n pi.setLabel(\"Safety + Utilities\");\n\n pi.on(\"session_start\", async (_event, ctx) => {\n  ctx.ui.notify(`Extension loaded in ${ctx.cwd}`, \"info\");\n });\n\n pi.on(\"tool_call\", async (event) => {\n  if (event.toolName === \"bash\" && event.input.command?.includes(\"rm -rf\")) {\n   return { block: true, reason: \"Blocked by extension policy\" };\n  }\n });\n\n pi.registerTool({\n  name: \"hello_extension\",\n  label: \"Hello Extension\",\n  description: \"Return a greeting\",\n  parameters: Type.Object({ name: Type.String() }),\n  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {\n   return {\n    content: [{ type: \"text\", text: `Hello, ${params.name}` }],\n    details: { greeted: params.name },\n   };\n  },\n });\n\n pi.registerCommand(\"hello-ext\", {\n  description: \"Show queue state\",\n  handler: async (_args, ctx) => {\n   ctx.ui.notify(`pending=${ctx.hasPendingMessages()}`, \"info\");\n  },\n });\n}\n```\n\n## 拡張機能 API サーフェス\n\n## 1) 登録とアクション（`ExtensionAPI`）\n\nコアメソッド：\n\n- `on(event, handler)`\n- `registerTool`、`registerCommand`、`registerShortcut`、`registerFlag`\n- `registerMessageRenderer`\n- `sendMessage`、`sendUserMessage`、`appendEntry`\n- `getActiveTools`、`getAllTools`、`setActiveTools`\n- `getSessionName`、`setSessionName`\n- `setModel`、`getThinkingLevel`、`setThinkingLevel`\n- `registerProvider`\n- `events`（共有イベントバス）\n\nインタラクティブモードでは、`input` ハンドラーは組み込みの最初のメッセージ自動タイトルチェックよりも前に実行されます。`input` から `await pi.setSessionName(...)` を呼び出す拡張機能は、永続化されたセッション名を設定し、そのセッションに対してデフォルトの自動生成タイトルが実行されないようにすることができます。\n\nまた、以下も公開されています：\n\n- `pi.logger`\n- `pi.typebox`\n- `pi.pi`（パッケージエクスポート）\n\n### メッセージ配信セマンティクス\n\n`pi.sendMessage(message, options)` は以下をサポートします：\n\n- `deliverAs: \"steer\"`（デフォルト）— 現在の実行を中断する\n- `deliverAs: \"followUp\"` — 現在の実行後にキューに追加される\n- `deliverAs: \"nextTurn\"` — 次のユーザープロンプト時に保存して注入される\n- `triggerTurn: true` — アイドル時にターンを開始する（`nextTurn` はこれを無視する）\n\n`pi.sendUserMessage(content, { deliverAs })` は常にプロンプトフローを経由します。ストリーミング中は steer/follow-up としてキューに追加されます。\n\n## 2) ハンドラーコンテキスト（`ExtensionContext`）\n\nハンドラーおよびツールの `execute` は、以下を含む `ctx` を受け取ります：\n\n- `ui`\n- `hasUI`\n- `cwd`\n- `sessionManager`（読み取り専用）\n- `modelRegistry`、`model`\n- `getContextUsage()`\n- `compact(...)`\n- `isIdle()`、`hasPendingMessages()`、`abort()`\n- `shutdown()`\n- `getSystemPrompt()`\n\n## 3) コマンドコンテキスト（`ExtensionCommandContext`）\n\nコマンドハンドラーはさらに以下を取得します：\n\n- `waitForIdle()`\n- `newSession(...)`\n- `switchSession(...)`\n- `branch(entryId)`\n- `navigateTree(targetId, { summarize })`\n- `reload()`\n\nセッション制御フローにはコマンドコンテキストを使用してください。これらのメソッドは意図的に一般的なイベントハンドラーから分離されています。\n\n## イベントサーフェス（現在の名前と動作）\n\n標準的なイベントユニオンとペイロードタイプは `types.ts` に記載されています。\n\n### セッションライフサイクル\n\n- `session_start`\n- `session_before_switch` / `session_switch`\n- `session_before_branch` / `session_branch`\n- `session_before_compact` / `session.compacting` / `session_compact`\n- `session_before_tree` / `session_tree`\n- `session_shutdown`\n\nキャンセル可能な事前イベント：\n\n- `session_before_switch` → `{ cancel?: boolean }`\n- `session_before_branch` → `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_before_compact` → `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session_before_tree` → `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n\n### プロンプトおよびターンライフサイクル\n\n- `input`\n- `before_agent_start`\n- `context`\n- `agent_start` / `agent_end`\n- `turn_start` / `turn_end`\n- `message_start` / `message_update` / `message_end`\n\n### ツールライフサイクル\n\n- `tool_call`（実行前、ブロック可能）\n- `tool_result`（実行後、content/details/isError のパッチ適用可能）\n- `tool_execution_start` / `tool_execution_update` / `tool_execution_end`（オブザーバビリティ）\n\n`tool_result` はミドルウェアスタイルです：ハンドラーは拡張機能の順序で実行され、それぞれが以前の変更を参照します。\n\n### 信頼性/ランタイムシグナル\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### ユーザーコマンドインターセプション\n\n- `user_bash`（`{ result }` でオーバーライド）\n- `user_python`（`{ result }` でオーバーライド）\n\n### `resources_discover`\n\n`resources_discover` は拡張機能タイプおよび `ExtensionRunner` に存在します。\n現在のランタイム注記：`ExtensionRunner.emitResourcesDiscover(...)` は実装されていますが、現在のコードベースにはそれを呼び出す `AgentSession` のコールサイトが存在しません。\n\n## ツール作成の詳細\n\n`registerTool` は `types.ts` の `ToolDefinition` を使用します。\n\n現在の `execute` シグネチャ：\n\n```ts\nexecute(\n toolCallId,\n params,\n signal,\n onUpdate,\n ctx,\n): Promise<AgentToolResult>\n```\n\nテンプレート：\n\n```ts\npi.registerTool({\n name: \"my_tool\",\n label: \"My Tool\",\n description: \"...\",\n parameters: Type.Object({}),\n async execute(_id, _params, signal, onUpdate, ctx) {\n  if (signal?.aborted) {\n   return { content: [{ type: \"text\", text: \"Cancelled\" }] };\n  }\n  onUpdate?.({ content: [{ type: \"text\", text: \"Working...\" }] });\n  return { content: [{ type: \"text\", text: \"Done\" }], details: {} };\n },\n onSession(event, ctx) {\n  // reason: start|switch|branch|tree|shutdown\n },\n renderCall(args, theme) {\n  // オプションの TUI レンダリング\n },\n renderResult(result, options, theme, args) {\n  // オプションの TUI レンダリング\n },\n});\n```\n\n`tool_call`/`tool_result` は、`sdk.ts` でレジストリがラップされると、組み込みおよび拡張機能/カスタムツールを含むすべてのツールをインターセプトします。\n\n## UI 統合ポイント\n\n`ctx.ui` は `ExtensionUIContext` インターフェースを実装します。サポート状況はモードによって異なります。\n\n### インタラクティブモード（`extension-ui-controller.ts`）\n\nサポート対象：\n\n- ダイアログ：`select`、`confirm`、`input`、`editor`\n- 通知/ステータス/エディターテキスト/ターミナル入力/カスタムオーバーレイ\n- 名前によるテーマ一覧/読み込み（`setTheme` は文字列名をサポート）\n- ツール展開トグル\n\nこのコントローラーで現在 no-op となっているメソッド：\n\n- `setFooter`\n- `setHeader`\n- `setEditorComponent`\n\nまた、`setWidget` は現在 `setHookWidget(...)` 経由でステータスライン テキストにルーティングされます。\n\n### RPC モード（`rpc-mode.ts`）\n\n`ctx.ui` は RPC `extension_ui_request` イベントでバックアップされます：\n\n- ダイアログメソッド（`select`、`confirm`、`input`、`editor`）はクライアントレスポンスへのラウンドトリップを実行\n- Fire-and-forget メソッドはリクエストを送出（`notify`、`setStatus`、文字列配列の `setWidget`、`setTitle`、`setEditorText`）\n\nRPC 実装でサポートされていない/no-op：\n\n- `onTerminalInput`\n- `custom`\n- `setFooter`、`setHeader`、`setEditorComponent`\n- `setWorkingMessage`\n- テーマの切り替え/読み込み（`setTheme` は失敗を返す）\n- ツール展開コントロールは無効\n\n### Print/ヘッドレス/サブエージェントパス\n\nランナーの初期化に UI コンテキストが提供されない場合、`ctx.hasUI` は `false` となり、メソッドは no-op/デフォルト返却となります。\n\n### バックグラウンドインタラクティブモード\n\nバックグラウンドモードは非インタラクティブな UI コンテキストオブジェクトをインストールします。現在の実装では、インタラクティブなダイアログがデフォルト/no-op の動作を返す一方、`ctx.hasUI` が `true` のままになる場合があります。\n\n## セッションと状態のパターン\n\n拡張機能の永続的な状態のために：\n\n1. `pi.appendEntry(customType, data)` を使用して永続化します。\n2. `session_start`、`session_branch`、`session_tree` で `ctx.sessionManager.getBranch()` から状態を再構築します。\n3. ツール結果の `details` は、状態がツール結果履歴から参照/再構築可能である必要がある場合に構造化して保持します。\n\n再構築パターンの例：\n\n```ts\npi.on(\"session_start\", async (_event, ctx) => {\n let latest;\n for (const entry of ctx.sessionManager.getBranch()) {\n  if (entry.type === \"custom\" && entry.customType === \"my-state\") {\n   latest = entry.data;\n  }\n }\n // latest から復元する\n});\n```\n\n## レンダリング拡張ポイント\n\n## カスタムメッセージレンダラー\n\n```ts\npi.registerMessageRenderer(\"my-type\", (message, { expanded }, theme) => {\n // pi-tui Component を返す\n});\n```\n\nカスタムメッセージが表示される際のインタラクティブレンダリングで使用されます。\n\n## ツールコール/結果レンダラー\n\nTUI でのカスタムツール可視化のために、`registerTool` 定義に `renderCall` / `renderResult` を指定します。\n\n## 制約と落とし穴\n\n- ランタイムアクションは拡張機能のロード中は使用できません。\n- `tool_call` のエラーは実行をブロックします（フェールクローズド）。\n- 組み込みとのコマンド名の競合は、診断とともにスキップされます。\n- 予約済みショートカットは無視されます（`ctrl+c`、`ctrl+d`、`ctrl+z`、`ctrl+k`、`ctrl+p`、`ctrl+l`、`ctrl+o`、`ctrl+t`、`ctrl+g`、`shift+tab`、`shift+ctrl+p`、`alt+enter`、`escape`、`enter`）。\n- `ctx.reload()` は、現在のコマンドハンドラーフレームの終端として扱ってください。\n\n## 拡張機能 vs フック vs カスタムツール\n\n適切なサーフェスを使用してください：\n\n- **拡張機能**（`src/extensibility/extensions/*`）：統合システム（イベント + ツール + コマンド + レンダラー + プロバイダー登録）。\n- **フック**（`src/extensibility/hooks/*`）：別個のレガシーイベント API。\n- **カスタムツール**（`src/extensibility/custom-tools/*`）：ツール中心のモジュール。拡張機能と共に読み込まれる場合、適合されて拡張機能インターセプションラッパーを通過します。\n\nポリシー、ツール、コマンド UX、およびレンダリングを一括して管理する1つのパッケージが必要な場合は、拡張機能を使用してください。\n",
	"ja/extensions/gemini-manifest-extensions.md": "---\ntitle: Gemini マニフェスト拡張\ndescription: クロスプラットフォームのスキルおよびエージェント互換性のための Gemini マニフェスト拡張フォーマット。\nsidebar:\n  order: 7\n  label: Gemini マニフェスト\ni18n:\n  sourceHash: 7134165a5f6d\n  translator: machine\n---\n\n# Gemini マニフェスト拡張 (`gemini-extension.json`)\n\nこのドキュメントでは、コーディングエージェントが Gemini スタイルのマニフェスト拡張 (`gemini-extension.json`) を検出し、`extensions` ケーパビリティとしてパースする方法について説明します。\n\nTypeScript/JavaScript 拡張モジュールのロード（`extensions/*.ts`、`index.ts`、`package.json xcsh.extensions`）については、`extension-loading.md` に記載されており、本ドキュメントでは扱いません。\n\n## 実装ファイル\n\n- [`../src/discovery/gemini.ts`](../../packages/coding-agent/src/discovery/gemini.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/capability/extension.ts`](../../packages/coding-agent/src/capability/extension.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/loader.ts`](../../packages/coding-agent/src/extensibility/extensions/loader.ts)\n\n---\n\n## 検出対象\n\nGemini プロバイダー（`id: gemini`、優先度 `60`）は `extensions` ローダーを登録し、2 つの固定ルートをスキャンします。\n\n- ユーザー: `~/.gemini/extensions`\n- プロジェクト: `<cwd>/.gemini/extensions`\n\nパス解決は `getUserPath()` / `getProjectPath()` を通じて `ctx.home` および `ctx.cwd` から直接行われます。\n\n重要なスコープルール: プロジェクトのルックアップは **cwd のみ** です。親ディレクトリをたどることはありません。\n\n---\n\n## ディレクトリスキャンのルール\n\n各ルート（`~/.gemini/extensions` および `<cwd>/.gemini/extensions`）に対して、検出処理は以下を行います。\n\n1. `readDirEntries(root)` を実行\n2. 直接の子ディレクトリのみを保持（`entry.isDirectory()`）\n3. 各子 `<name>` に対して、正確に以下のみを読み取ろうとする:\n   - `<root>/<name>/gemini-extension.json`\n\n1 ディレクトリレベルを超えた再帰的スキャンは行いません。\n\n### 隠しディレクトリ\n\nGemini マニフェスト検出では、ドットプレフィックスのディレクトリ名をフィルタリング**しません**。隠し子ディレクトリが存在し `gemini-extension.json` を含む場合、そのディレクトリは対象として扱われます。\n\n### 欠落または読み取り不能なファイル\n\n`gemini-extension.json` が欠落しているか読み取り不能な場合、そのディレクトリは警告なしにスキップされます（警告なし）。\n\n---\n\n## マニフェストの形式（実装ベース）\n\nケーパビリティ型はこのマニフェスト形式を定義します。\n\n```ts\ninterface ExtensionManifest {\n name?: string;\n description?: string;\n mcpServers?: Record<string, Omit<MCPServer, \"name\" | \"_source\">>;\n tools?: unknown[];\n context?: unknown;\n}\n```\n\n検出時の動作は意図的に緩やかです。\n\n- JSON のパース成功が必須です。\n- JSON の構文を超えたフィールドの型や内容に対する実行時スキーマ検証は行いません。\n- パースされたオブジェクトはケーパビリティアイテムの `manifest` として保存されます。\n\n### 名前の正規化\n\n`Extension.name` は以下のように設定されます。\n\n1. `manifest.name` が `null`/`undefined` でない場合はその値を使用\n2. それ以外の場合は拡張ディレクトリ名を使用\n\nここでは文字列型の強制は適用されません。\n\n---\n\n## ケーパビリティアイテムへのマテリアライズ\n\n有効にパースされたマニフェストは 1 つの `Extension` ケーパビリティアイテムを生成します。\n\n```ts\n{\n name: manifest.name ?? <directory-name>,\n path: <extension-directory>,\n manifest: <parsed-json>,\n level: \"user\" | \"project\",\n _source: {\n  provider: \"gemini\",\n  providerName: \"Gemini CLI\" // ケーパビリティレジストリによって付与\n  path: <absolute-manifest-path>,\n  level: \"user\" | \"project\"\n }\n}\n```\n\n注意事項:\n\n- `_source.path` は `createSourceMeta()` によって絶対パスに正規化されます。\n- `extensions` に対するレジストリレベルのケーパビリティ検証では、`name` と `path` の存在のみを確認します。\n- マニフェストの内部要素（`mcpServers`、`tools`、`context`）は検出時には検証されません。\n\n---\n\n## エラー処理と警告のセマンティクス\n\n### 警告が発出される場合\n\n- マニフェストファイルに無効な JSON が含まれる場合:\n  - 警告フォーマット: `Invalid JSON in <manifestPath>`\n\n### 警告が発出されない場合（サイレントスキップ）\n\n- `extensions` ディレクトリが存在しない\n- 子ディレクトリに `gemini-extension.json` がない\n- マニフェストファイルが読み取り不能\n- マニフェストの JSON が構文的には有効だが意味的に不完全または不規則\n\nつまり、部分的な有効性は受け入れられ、JSON の構文エラーのみが警告を発出します。\n\n---\n\n## 他のソースとの優先順位と重複排除\n\n`extensions` ケーパビリティはケーパビリティレジストリによってプロバイダー横断で集約されます。\n\nこのケーパビリティの現在のプロバイダー:\n\n- `native`（`packages/coding-agent/src/discovery/builtin.ts`）優先度 `100`\n- `gemini`（`packages/coding-agent/src/discovery/gemini.ts`）優先度 `60`\n\n重複排除キーは `ext.name`（`extensionCapability.key = ext => ext.name`）です。\n\n### クロスプロバイダーの優先順位\n\n重複する拡張名については、優先度の高いプロバイダーが勝ちます。\n\n- `native` と `gemini` の両方が拡張名 `foo` を出力する場合、native のアイテムが保持されます。\n- 優先度の低い重複は `_shadowed = true` の状態で `result.all` にのみ保持されます。\n\n### プロバイダー内の順序の影響\n\n重複排除は「最初に見つかったものが優先」であるため、プロバイダーローカルのアイテム順序が重要です。\n\n- Gemini ローダーは **ユーザーを先**に、次に**プロジェクト**を追加します。\n- そのため、`~/.gemini/extensions` と `<cwd>/.gemini/extensions` の間で名前が重複する場合、ユーザーのエントリが保持され、プロジェクトのエントリがシャドウされます。\n\n対照的に、native プロバイダーは `getConfigDirs()` において異なる順序（`project` を先に、次に `user`）でコンフィグディレクトリを構築するため、native プロバイダー内でのシャドウイングの方向は逆になります。\n\n---\n\n## ユーザーとプロジェクトの動作まとめ\n\nGemini マニフェスト固有の動作として:\n\n- ユーザーおよびプロジェクトの両ルートがロードのたびにスキャンされます。\n- プロジェクトルートは `<cwd>/.gemini/extensions` に固定されます（祖先ディレクトリへのウォークなし）。\n- Gemini ソース内での名前の重複はユーザー優先で解決されます。\n- 優先度の高いプロバイダー（特に native）との名前の重複は優先度によって失われます。\n\n---\n\n## 境界: 検出メタデータとランタイム拡張ロード\n\n`gemini-extension.json` の検出は現在、ケーパビリティメタデータ（`Extension` アイテム）にフィードされます。実行可能な TS/JS 拡張モジュールを直接ロードするものでは**ありません**。\n\nランタイムモジュールロード（`discoverAndLoadExtensions()` / `loadExtensions()`）は `extension-modules` と明示的なパスを使用し、現在は自動検出されたモジュールをプロバイダー `native` のみにフィルタリングしています。\n\n実際的な意味合い:\n\n- Gemini マニフェスト拡張はケーパビリティレコードとして検出可能です。\n- それ自体では、拡張ローダーパイプラインによってランタイム拡張モジュールとして実行されることはありません。\n\nこの境界は現在の実装において意図的なものであり、マニフェスト検出と実行可能モジュールのロードが乖離する可能性がある理由を説明しています。\n",
	"ja/extensions/marketplace.md": "---\ntitle: マーケットプレイスプラグインシステム\ndescription: 厳選されたプラグインコレクションの検出、インストール、管理のためのマーケットプレイスプラグインシステム。\nsidebar:\n  order: 4\n  label: マーケットプレイス\ni18n:\n  sourceHash: 71d9f8f93a81\n  translator: machine\n---\n\n# マーケットプレイスプラグインシステム\n\nマーケットプレイスシステムを使用すると、Gitでホストされたカタログからプラグインを検出、インストール、管理できます。Claude Codeプラグインレジストリ形式と互換性があります。\n\n## クイックスタート\n\n```\n/marketplace add anthropics/f5-sales-demo-marketplace\n/marketplace install wordpress.com@f5-sales-demo-marketplace\n```\n\nまたは、引数なしで `/marketplace` と入力するだけで、インタラクティブなプラグインブラウザが開きます。\n\n## コンセプト\n\n**マーケットプレイス**とは、`.xcsh-plugin/marketplace.json` にカタログファイルを含むGitリポジトリ（またはローカルディレクトリ）です。カタログには、利用可能なプラグインとそのソース、説明、メタデータが一覧されています。\n\n**プラグイン**とは、スキル、コマンド、フック、MCPサーバー、またはLSPサーバーを含むディレクトリです。プラグインは `name@marketplace` で識別されます（例: `code-review@f5-sales-demo-marketplace`）。\n\n**スコープ**: プラグインは2つのスコープでインストールできます:\n\n- **user**（デフォルト）-- すべてのプロジェクトで利用可能、`~/.xcsh/plugins/installed_plugins.json` に保存\n- **project** -- 現在のプロジェクトでのみ利用可能、`.xcsh/installed_plugins.json` に保存\n\nプロジェクトスコープのインストールは、同じプラグインのユーザースコープのインストールをシャドウ（上書き）します。\n\n## コマンド\n\n### インタラクティブモード\n\n| コマンド | 効果 |\n|---|---|\n| `/marketplace` | インタラクティブなプラグインブラウザを開く（インストール） |\n\n### マーケットプレイス管理\n\n| コマンド | 効果 |\n|---|---|\n| `/marketplace add <source>` | マーケットプレイスソースを追加 |\n| `/marketplace remove <name>` | マーケットプレイスを削除 |\n| `/marketplace update [name]` | カタログを再取得。名前を省略するとすべて更新 |\n| `/marketplace list` | 設定済みのマーケットプレイスを一覧表示 |\n\n### プラグイン操作\n\n| コマンド | 効果 |\n|---|---|\n| `/marketplace discover [marketplace]` | 利用可能なプラグインを閲覧 |\n| `/marketplace install [--force] [--scope user\\|project] name@marketplace` | プラグインをインストール |\n| `/marketplace uninstall [--scope user\\|project] name@marketplace` | プラグインをアンインストール |\n| `/marketplace installed` | インストール済みのマーケットプレイスプラグインを一覧表示 |\n| `/marketplace upgrade [--scope user\\|project] [name@marketplace]` | 1つまたはすべてのプラグインをアップグレード |\n\n### CLI相当コマンド\n\n同じ操作はコマンドラインからも利用できます:\n\n```\nxcsh plugin marketplace add <source>\nxcsh plugin marketplace remove <name>\nxcsh plugin marketplace update [name]\nxcsh plugin marketplace list\nxcsh plugin discover [marketplace]\nxcsh plugin install --scope project name@marketplace\n```\n\n## マーケットプレイスソース\n\n`/marketplace add <source>` を実行すると、システムがソースを分類します:\n\n| ソース形式 | タイプ | 例 |\n|---|---|---|\n| `owner/repo` | GitHub省略形 | `anthropics/f5-sales-demo-marketplace` |\n| `https://...*.json` | カタログ直接URL | `https://example.com/marketplace.json` |\n| `https://...*.git` or `git@...` | Gitリポジトリ | `https://github.com/org/repo.git` |\n| `./path` or `~/path` or `/path` | ローカルディレクトリ | `./my-marketplace` |\n\nシステムはリポジトリをクローン（またはローカルディレクトリを読み取り）し、`.xcsh-plugin/marketplace.json` を見つけて検証し、カタログをローカルにキャッシュします。\n\n## カタログ形式（marketplace.json）\n\nマーケットプレイスカタログは、リポジトリルートの `.xcsh-plugin/marketplace.json` に配置されます:\n\n```json\n{\n  \"$schema\": \"https://anthropic.com/claude-code/marketplace.schema.json\",\n  \"name\": \"my-marketplace\",\n  \"owner\": {\n    \"name\": \"Your Name\",\n    \"email\": \"you@example.com\"\n  },\n  \"description\": \"A collection of plugins\",\n  \"plugins\": [\n    {\n      \"name\": \"my-plugin\",\n      \"description\": \"What this plugin does\",\n      \"source\": \"./plugins/my-plugin\",\n      \"category\": \"development\",\n      \"homepage\": \"https://github.com/you/my-plugin\"\n    }\n  ]\n}\n```\n\n### 必須フィールド\n\n| フィールド | 説明 |\n|---|---|\n| `name` | マーケットプレイス名。小文字の英数字、ハイフン、ドットが使用可能。英数字で開始・終了する必要があります。最大64文字。 |\n| `owner.name` | マーケットプレイスオーナー名 |\n| `plugins` | プラグインエントリの配列 |\n\n### プラグインエントリフィールド\n\n| フィールド | 必須 | 説明 |\n|---|---|---|\n| `name` | はい | プラグイン名（マーケットプレイス名と同じルール） |\n| `source` | はい | プラグインの取得元（下記参照） |\n| `description` | いいえ | 短い説明 |\n| `version` | いいえ | バージョン文字列 |\n| `author` | いいえ | `{ name, email? }` |\n| `homepage` | いいえ | URL |\n| `category` | いいえ | カテゴリ文字列（例: `development`、`productivity`、`security`） |\n| `tags` | いいえ | 文字列タグの配列 |\n| `strict` | いいえ | ブール値 |\n| `commands` | いいえ | 提供するスラッシュコマンド |\n| `agents` | いいえ | 提供するエージェント |\n| `hooks` | いいえ | フック定義 |\n| `mcpServers` | いいえ | MCPサーバー定義 |\n| `lspServers` | いいえ | LSPサーバー定義 |\n\n### プラグインソース形式\n\n`source` フィールドはいくつかの形式をサポートしています:\n\n**相対パス**（マーケットプレイスリポジトリ内）:\n\n```json\n\"source\": \"./plugins/my-plugin\"\n```\n\n**GitリポジトリURL**:\n\n```json\n\"source\": {\n  \"source\": \"url\",\n  \"url\": \"https://github.com/org/repo.git\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**GitHub省略形**:\n\n```json\n\"source\": {\n  \"source\": \"github\",\n  \"repo\": \"org/repo\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Gitサブディレクトリ**（モノレポ）:\n\n```json\n\"source\": {\n  \"source\": \"git-subdir\",\n  \"url\": \"https://github.com/org/monorepo.git\",\n  \"path\": \"plugins/my-plugin\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**npmパッケージ**:\n\n```json\n\"source\": {\n  \"source\": \"npm\",\n  \"package\": \"@scope/my-plugin\",\n  \"version\": \"1.0.0\"\n}\n```\n\n## ディスク上のレイアウト\n\n```\n~/.xcsh/\n  config/\n    marketplaces.json          # 追加されたマーケットプレイスのレジストリ\n  plugins/\n    installed_plugins.json     # ユーザースコープのインストール済みプラグイン\n    cache/\n      marketplaces/            # キャッシュされたマーケットプレイスカタログ\n      plugins/                 # キャッシュされたプラグインディレクトリ\n\n<project>/.xcsh/\n  installed_plugins.json       # プロジェクトスコープのインストール済みプラグイン\n```\n\n## 命名規則\n\nマーケットプレイス名とプラグイン名は以下の条件を満たす必要があります:\n\n- 小文字の英字または数字で開始・終了すること\n- 小文字の英字、数字、ハイフン、ドットのみを含むこと\n- 最大64文字であること\n\nプラグインID（`name@marketplace`）は合計で最大128文字です。\n\n有効な例: `my-plugin`、`code-review`、`wordpress.com`、`ai-firstify`\n無効な例: `-bad`、`bad-`、`.bad`、`Bad`、`under_score`\n",
	"ja/extensions/plugin-manager-installer-plumbing.md": "---\ntitle: プラグインマネージャーとインストーラーの内部構造\ndescription: インストール、検証、依存関係の解決、ライフサイクル管理を含むプラグインマネージャーの内部仕様。\nsidebar:\n  order: 5\n  label: プラグインマネージャー\ni18n:\n  sourceHash: 9c33e5a2c22a\n  translator: machine\n---\n\n# プラグインマネージャーとインストーラーの内部構造\n\nこのドキュメントでは、`xcsh plugin` 操作がディスク上のプラグイン状態をどのように変更するか、およびインストールされたプラグインがランタイム機能（現在はツール、フック/コマンドのパス解決も利用可能）としてどのように機能するかについて説明します。\n\n## スコープとアーキテクチャ\n\nコードベースには2つのプラグイン管理実装があります。\n\n1. **CLI コマンドで使用されるアクティブパス**: `PluginManager`（`src/extensibility/plugins/manager.ts`）\n2. **レガシーヘルパーモジュール**: インストーラー関数（`src/extensibility/plugins/installer.ts`）\n\n`xcsh plugin ...` コマンドの実行は `PluginManager` を経由します。\n\n`installer.ts` には重要なセキュリティチェックとファイルシステムの動作が記述されていますが、`src/commands/plugin.ts` + `src/cli/plugin-cli.ts` が使用するパスではありません。\n\n## ライフサイクル: CLI 呼び出しからランタイム利用可能までの流れ\n\n```text\nxcsh plugin <action> ...\n  -> src/commands/plugin.ts\n  -> runPluginCommand(...) in src/cli/plugin-cli.ts\n  -> PluginManager method (install/list/uninstall/link/...) \n  -> mutate ~/.xcsh/plugins/{package.json,node_modules,xcsh-plugins.lock.json}\n  -> runtime discovery: discoverAndLoadCustomTools(...)\n  -> getAllPluginToolPaths(cwd)\n  -> custom tool loader imports tool modules\n```\n\n### コマンドエントリーポイント\n\n- `src/commands/plugin.ts` はコマンド/フラグを定義し、`runPluginCommand` に転送します。\n- `src/cli/plugin-cli.ts` はサブコマンドを `PluginManager` のメソッドにマッピングします:\n  - `install`、`uninstall`、`list`、`link`、`doctor`、`features`、`config`、`enable`、`disable`\n- 明示的な `update` アクションは存在しません。更新は新しいパッケージ/バージョン指定で `install` を再実行することで行います。\n\n## ディスク上のモデル\n\nグローバルなプラグイン状態は `~/.xcsh/plugins` 以下に保存されます:\n\n- `package.json` — `bun install`/`bun uninstall` で使用する依存関係マニフェスト\n- `node_modules/` — インストール済みプラグインパッケージまたはシンボリックリンク\n- `xcsh-plugins.lock.json` — ランタイム状態:\n  - プラグインごとの有効/無効状態\n  - プラグインごとの選択済みフィーチャーセット\n  - 永続化されたプラグイン設定\n\nプロジェクトローカルのオーバーライドは以下に保存されます:\n\n- `<cwd>/.xcsh/plugin-overrides.json`\n\nオーバーライドはマネージャー/ローダーの観点から読み取り専用（書き込みパスなし）であり、このプロジェクトに対してプラグインを無効化したり、フィーチャー/設定をオーバーライドしたりできます。\n\n## プラグイン仕様の解析とメタデータの解釈\n\n## インストール仕様の文法\n\n`parsePluginSpec`（`parser.ts`）がサポートする記法:\n\n- `pkg` -> `features: null`（デフォルト動作）\n- `pkg[*]` -> マニフェストの全フィーチャーを有効化\n- `pkg[]` -> オプションフィーチャーを有効化しない\n- `pkg[a,b]` -> 指定したフィーチャーを有効化\n- `@scope/pkg@1.2.3[feat]` -> スコープ付き＋バージョン指定パッケージに明示的なフィーチャー選択\n\n`extractPackageName` はインストール後のディスク上パス検索のためにバージョンサフィックスを除去します。\n\n## マニフェストのソースと必須フィールド\n\nマニフェストは以下の順序で解決されます:\n\n1. `package.json.xcsh`\n2. フォールバック `package.json.pi`\n3. フォールバック `{ version: package.version }`\n\n影響:\n\n- マネージャー/ローダーには厳密なスキーマ検証が存在しません。\n- `xcsh`/`pi` がないパッケージでもインストールおよびリスト表示は可能です。\n- ランタイムプラグインローディング（`getEnabledPlugins`）では `xcsh`/`pi` マニフェストがないパッケージはスキップされます。\n- `manifest.version` は常にパッケージの `version` で上書きされます。\n\n`package.json` の JSON が不正な場合は読み込み時にハードエラーとなります。マニフェストの構造が不正な場合は、特定のフィールドが参照されたときにのみ失敗する場合があります。\n\n## インストール/更新フロー（`PluginManager.install`）\n\n1. インストール仕様からフィーチャーブラケット構文を解析します。\n2. パッケージ名を正規表現＋シェルメタキャラクター拒否リストに対して検証します。\n3. プラグインの `package.json` が存在することを確認します（`xcsh-plugins`、プライベート依存関係マップ）。\n4. `~/.xcsh/plugins` で `bun install <packageSpec>` を実行します。\n5. インストール済みパッケージの `node_modules/<name>/package.json` を読み込みます。\n6. マニフェストを解決し `enabledFeatures` を計算します:\n   - `[*]`: 宣言済みの全フィーチャー（フィーチャーマップがない場合は `null`）\n   - `[a,b]`: マニフェストフィーチャーマップに各フィーチャーが存在することを検証\n   - `[]`: 空のフィーチャーリスト\n   - ベア仕様: `null`（後でローダーのデフォルトポリシーを使用）\n7. ロックファイルのランタイム状態をアップサート: `{ version, enabledFeatures, enabled: true }`\n\n### 更新のセマンティクス\n\n更新はインストールによって行われるため:\n\n- `xcsh plugin install pkg@newVersion` は依存関係とロックファイルのバージョンを更新します。\n- 既存の設定は保持されます。バージョン/フィーチャー/有効状態のエントリーは上書きされます。\n- 個別の「更新チェック」やトランザクション型マイグレーションロジックは存在しません。\n\n## 削除フロー（`PluginManager.uninstall`）\n\n1. パッケージ名を検証します。\n2. プラグインディレクトリで `bun uninstall <name>` を実行します。\n3. ロックファイルからプラグインのランタイム状態を削除します:\n   - `config.plugins[name]`\n   - `config.settings[name]`\n\nアンインストールコマンドが失敗した場合、ランタイム状態は変更されません。\n\n## リストフロー（`PluginManager.list`）\n\n1. `~/.xcsh/plugins/package.json` からプラグイン依存関係マップを読み込みます。\n2. ロックファイルのランタイム設定を読み込みます（ファイルが存在しない場合は空のデフォルト値）。\n3. プロジェクトオーバーライドを読み込みます（`<cwd>/.xcsh/plugin-overrides.json`、解析/読み取りエラーの場合は警告を出して空オブジェクト）。\n4. `package.json` が解決可能な各依存関係に対して:\n   - `InstalledPlugin` レコードを構築\n   - フィーチャー/有効状態をマージ:\n     - ベースはロックファイルから（またはデフォルト値）\n     - プロジェクトオーバーライドでフィーチャー選択を置き換え可能\n     - プロジェクトの `disabled` リストによりプラグインが無効としてマスクされる\n\nこれが CLI のステータス出力および設定/フィーチャー操作で使用される実効状態です。\n\n## リンクフロー（`PluginManager.link`）\n\n`link` はローカルパッケージを `~/.xcsh/plugins/node_modules/<pkg.name>` にシンボリックリンクすることで、ローカルプラグイン開発をサポートします。\n\n動作:\n\n1. マネージャーの cwd に対して `localPath` を解決します。\n2. ローカルの `package.json` と `name` フィールドの存在を要求します。\n3. プラグインディレクトリが存在することを確認します。\n4. スコープ付き名の場合、スコープディレクトリを作成します。\n5. リンクターゲットの既存パスを削除します。\n6. シンボリックリンクを作成します。\n7. デフォルトフィーチャー（`null`）で有効なロックファイルエントリーを追加します。\n\n注意点: 現在の `PluginManager.link` は、レガシーの `installer.ts` に存在する `cwd` パス境界チェック（`normalizedPath.startsWith(normalizedCwd)`）を強制しないため、信頼は呼び出し元の責任となります。\n\n## ランタイムローディング: インストール済みプラグインから呼び出し可能な機能まで\n\n## ディスカバリーゲート\n\n`getEnabledPlugins(cwd)`（`plugins/loader.ts`）が読み込む内容:\n\n- プラグイン依存関係マニフェスト（`package.json`）\n- ロックファイルのランタイム状態\n- `getConfigDirPaths(\"plugin-overrides.json\", { user: false, cwd })` 経由のプロジェクトオーバーライド\n\nフィルタリング:\n\n- プラグインの `package.json` がない場合はスキップ\n- マニフェスト（`xcsh`/`pi`）がない場合はスキップ\n- ロックファイルでグローバルに無効化されている場合はスキップ\n- プロジェクトで無効化されている場合はスキップ\n\n## 機能パスの解決\n\n有効な各プラグインに対して:\n\n- `resolvePluginToolPaths(plugin)`\n- `resolvePluginHookPaths(plugin)`\n- `resolvePluginCommandPaths(plugin)`\n\n各リゾルバーはベースエントリーとフィーチャーエントリーを含みます:\n\n- 明示的なフィーチャーリスト -> 選択したフィーチャーのみ\n- `enabledFeatures === null` -> `default: true` とマークされたフィーチャーを有効化\n\n存在しないファイルはサイレントにスキップされます（`existsSync` ガード）。\n\n## 現在のランタイム配線の違い\n\n- **ツールは現在ランタイムに配線されています**: `discoverAndLoadCustomTools`（`custom-tools/loader.ts`）経由で、`getAllPluginToolPaths(cwd)` を呼び出します。\n- パスはカスタムツールディスカバリー内で解決済み絶対パスによって重複排除されます（`seen` セット、最初のパスが優先）。\n- **フック/コマンドリゾルバーは存在**してエクスポートされていますが、このコードパスは現在ツールと同じ方法でランタイムレジストリに配線されていません。\n\n## ロック/状態管理の詳細\n\n`PluginManager` はランタイム設定をインスタンスごとにメモリ内（`#runtimeConfig`）にキャッシュし、初回アクセス時に遅延読み込みします。\n\n読み込み動作:\n\n- ロックファイルが存在しない場合 -> `{ plugins: {}, settings: {} }`\n- ロックファイルの読み込み/解析に失敗した場合 -> 警告＋同じ空のデフォルト値\n\n保存動作:\n\n- ミューテーションのたびにロックファイル全体を JSON プリティプリント形式で書き込みます\n\nクロスプロセスのロックやマージ戦略は存在しません。並行した書き込みは互いに上書きする可能性があります。\n\n## セキュリティチェックと信頼境界\n\n## 入力/パッケージの検証\n\nアクティブなマネージャーパスではパッケージ名の検証を強制します:\n\n- スコープ付き/スコープなしパッケージ仕様（オプションのバージョン付き）の正規表現\n- 明示的なシェルメタキャラクター拒否リスト（`[;&|`$(){}[]<>\\\\]`）\n\nこれにより、`bun install/uninstall` を呼び出す際のコマンドインジェクションリスクが制限されます。\n\n## ファイルシステムの信頼境界\n\n- プラグインコードはカスタムツールモジュールのインポート時にインプロセスで実行されます。サンドボックス化はありません。\n- マニフェストの相対パスはプラグインパッケージディレクトリに対して結合され、存在チェックのみが行われます。\n- プラグインパッケージ自体はインストール後は信頼されたコードとして扱われます。\n\n## レガシーインストーラー専用のチェック\n\n`installer.ts` には `PluginManager.link` に反映されていない追加のリンク時チェックが含まれます:\n\n- ローカルパスはプロジェクトの cwd 内に解決される必要があります\n- シンボリックリンクターゲットの命名に関する追加のパッケージ名/パストラバーサルガード\n\nCLI は `PluginManager` を使用しているため、これらの厳格なリンクガードは現在メインパスには存在しません。\n\n## 失敗、部分的成功、ロールバックの動作\n\nプラグインマネージャーはトランザクション処理を行いません。\n\n| 操作ステージ | 失敗時の動作 | ロールバック |\n| --- | --- | --- |\n| `bun install` が失敗 | stderr でインストール中断 | 該当なし（状態の書き込みはまだ行われていない） |\n| インストール成功後、マニフェスト/フィーチャー検証が失敗 | コマンド失敗 | アンインストールのロールバックなし。依存関係が `node_modules`/`package.json` に残る場合あり |\n| インストール成功後、ロックファイルの書き込みが失敗 | コマンド失敗 | インストール済みパッケージのロールバックなし |\n| `bun uninstall` 成功後、ロックファイルの書き込みが失敗 | コマンド失敗 | パッケージは削除済み、古いランタイム状態が残る場合あり |\n| `link` が古いターゲットを削除後、シンボリックリンクの作成に失敗 | コマンド失敗 | 以前のリンク/ディレクトリの復元なし |\n\n運用上、`doctor --fix` は一部のドリフトを修復できます（`bun install`、孤立した設定のクリーンアップ、無効なフィーチャーのクリーンアップ）が、ベストエフォートです。\n\n## 不正/欠損マニフェストの動作まとめ\n\n- `xcsh`/`pi` フィールドが欠損している場合:\n  - インストール/リスト: 許容（最小限のマニフェスト）\n  - ランタイムの有効プラグインディスカバリー: 非プラグインとしてスキップ\n- インストール仕様または `features --set/--enable` で参照されているフィーチャーが存在しない場合: 利用可能なフィーチャーリストとともにハードエラー\n- `plugin-overrides.json` が不正な場合: マネージャーとローダーの両パスで `{}` にフォールバックして無視（警告あり）\n- マニフェストで参照されているツール/フック/コマンドのファイルパスが存在しない場合: リゾルバーの展開時にサイレントに無視。`doctor` によってのみエラーとして報告\n\n## モードの違いと優先順位\n\n- `--dry-run`（インストール）: 合成されたインストール結果を返し、ファイルシステム/ネットワーク/状態への書き込みは行いません。\n- `--json`: 出力フォーマットのみ、動作の変更なし。\n- プロジェクトオーバーライドは常にグローバルロックファイルよりフィーチャー/設定の表示で優先されます。\n- 実効的な有効状態は `runtimeEnabled && !projectDisabled` です。\n\n## 実装ファイル\n\n- [`src/commands/plugin.ts`](../../packages/coding-agent/src/commands/plugin.ts) — CLI コマンド宣言とフラグマッピング\n- [`src/cli/plugin-cli.ts`](../../packages/coding-agent/src/cli/plugin-cli.ts) — アクションディスパッチ、ユーザー向けコマンドハンドラー\n- [`src/extensibility/plugins/manager.ts`](../../packages/coding-agent/src/extensibility/plugins/manager.ts) — アクティブなインストール/削除/リスト/リンク/状態/doctor 実装\n- [`src/extensibility/plugins/installer.ts`](../../packages/coding-agent/src/extensibility/plugins/installer.ts) — レガシーインストーラーヘルパーと追加のリンクセキュリティチェック\n- [`src/extensibility/plugins/loader.ts`](../../packages/coding-agent/src/extensibility/plugins/loader.ts) — 有効プラグインのディスカバリーとツール/フック/コマンドのパス解決\n- [`src/extensibility/plugins/parser.ts`](../../packages/coding-agent/src/extensibility/plugins/parser.ts) — インストール仕様とパッケージ名の解析ヘルパー\n- [`src/extensibility/plugins/types.ts`](../../packages/coding-agent/src/extensibility/plugins/types.ts) — マニフェスト/ランタイム/オーバーライドの型定義\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts) — プラグイン提供のツールモジュールのランタイム配線\n",
	"ja/extensions/rulebook-matching-pipeline.md": "---\ntitle: ルールブックマッチングパイプライン\ndescription: エージェントセッションにコンテキスト固有の命令セットを選択・適用するためのルールブックマッチングパイプライン。\nsidebar:\n  order: 6\n  label: ルールブックマッチング\ni18n:\n  sourceHash: a16a9c565053\n  translator: machine\n---\n\n# ルールブックマッチングパイプライン\n\nこのドキュメントでは、coding-agent がサポートされている設定フォーマットからルールを検出し、単一の `Rule` 形式に正規化し、優先順位の競合を解決し、結果を以下に分割する方法について説明します：\n\n- **ルールブックルール**（システムプロンプト + `rule://` URL を介してモデルに利用可能）\n- **TTSR ルール**（タイムトラベルストリーム中断ルール）\n\nこれは現在の実装を反映しており、パースされるが強制されない部分的なセマンティクスやメタデータを含みます。\n\n## 実装ファイル\n\n- [`../src/capability/rule.ts`](../../packages/coding-agent/src/capability/rule.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/discovery/index.ts`](../../packages/coding-agent/src/discovery/index.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/cursor.ts`](../../packages/coding-agent/src/discovery/cursor.ts)\n- [`../src/discovery/windsurf.ts`](../../packages/coding-agent/src/discovery/windsurf.ts)\n- [`../src/discovery/cline.ts`](../../packages/coding-agent/src/discovery/cline.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/system-prompt.ts`](../../packages/coding-agent/src/system-prompt.ts)\n- [`../src/internal-urls/rule-protocol.ts`](../../packages/coding-agent/src/internal-urls/rule-protocol.ts)\n- [`../src/utils/frontmatter.ts`](../../packages/coding-agent/src/utils/frontmatter.ts)\n\n## 1. 正規ルール形式\n\nすべてのプロバイダーはソースファイルを `Rule` に正規化します：\n\n```ts\ninterface Rule {\n  name: string;\n  path: string;\n  content: string;\n  globs?: string[];\n  alwaysApply?: boolean;\n  description?: string;\n  ttsrTrigger?: string;\n  _source: SourceMeta;\n}\n```\n\nケイパビリティの識別子は `rule.name` です（`ruleCapability.key = rule => rule.name`）。\n\n結果として、優先順位と重複排除は **名前ベースのみ** で行われます。同じ `name` を持つ2つの異なるファイルは、同一の論理ルールとみなされます。\n\n## 2. 検出ソースと正規化\n\n`src/discovery/index.ts` はプロバイダーを自動登録します。`rules` の現在のプロバイダーは以下の通りです：\n\n- `native`（優先度 `100`）\n- `cursor`（優先度 `50`）\n- `windsurf`（優先度 `50`）\n- `cline`（優先度 `40`）\n\n### Native プロバイダー（`builtin.ts`）\n\n`.xcsh` ルールを以下から読み込みます：\n\n- プロジェクト: `<cwd>/.xcsh/rules/*.{md,mdc}`\n- ユーザー: `~/.xcsh/agent/rules/*.{md,mdc}`\n\n正規化：\n\n- `name` = `.md`/`.mdc` を除いたファイル名\n- フロントマターは `parseFrontmatter` で解析\n- `content` = 本文（フロントマター除去後）\n- `globs`、`alwaysApply`、`description`、`ttsr_trigger` は直接マッピング\n\n重要な注意点: `globs` はこのプロバイダーでは要素のフィルタリングなしに `string[] | undefined` としてキャストされます。\n\n### Cursor プロバイダー（`cursor.ts`）\n\n以下から読み込みます：\n\n- ユーザー: `~/.cursor/rules/*.{mdc,md}`\n- プロジェクト: `<cwd>/.cursor/rules/*.{mdc,md}`\n\n正規化（`transformMDCRule`）：\n\n- `description`: 文字列の場合のみ保持\n- `alwaysApply`: `true` のみ保持（`false` は `undefined` になる）\n- `globs`: 配列（文字列要素のみ）または単一文字列を受け付ける\n- `ttsr_trigger`: 文字列のみ\n- `name` は拡張子を除いたファイル名\n\n### Windsurf プロバイダー（`windsurf.ts`）\n\n以下から読み込みます：\n\n- ユーザー: `~/.codeium/windsurf/memories/global_rules.md`（固定ルール名 `global_rules`）\n- プロジェクト: `<cwd>/.windsurf/rules/*.md`\n\n正規化：\n\n- `globs`: 文字列の配列または単一文字列\n- `alwaysApply`、`description` はフロントマターからキャスト\n- `ttsr_trigger`: 文字列のみ\n- `name` はプロジェクトルールの場合ファイル名から取得\n\n### Cline プロバイダー（`cline.ts`）\n\n`cwd` から上方向に最も近い `.clinerules` を検索します：\n\n- ディレクトリの場合: その中の `*.md` を読み込む\n- ファイルの場合: `clinerules` という名前のルールとして単一ファイルを読み込む\n\n正規化：\n\n- `globs`: 文字列の配列または単一文字列\n- `alwaysApply`: ブール値の場合のみ\n- `description`: 文字列のみ\n- `ttsr_trigger`: 文字列のみ\n\n## 3. フロントマター解析の動作と曖昧性\n\nすべてのプロバイダーは以下のセマンティクスで `parseFrontmatter`（`utils/frontmatter.ts`）を使用します：\n\n1. フロントマターは、コンテンツが `---` で始まり、閉じの `\\n---` がある場合のみ解析されます。\n2. フロントマター抽出後、本文はトリミングされます。\n3. YAML 解析が失敗した場合：\n   - 警告がログに記録され、\n   - パーサーは単純な `key: value` 行解析（`^(\\w+):\\s*(.*)$`）にフォールバックします。\n\n曖昧性の影響：\n\n- フォールバックパーサーは、配列、ネストされたオブジェクト、クォーティングルール、ハイフン付きキーをサポートしません。\n- フォールバック値は文字列になります（例えば `alwaysApply: true` は文字列 `\"true\"` になる）ため、ブール値/文字列型を要求するプロバイダーではメタデータが失われる可能性があります。\n- `ttsr_trigger` はフォールバックで動作します（アンダースコアキー）。`thinking-level` のようなキーは動作しません。\n- 有効なフロントマターのないファイルも、空のメタデータと完全なコンテンツ本文を持つルールとして読み込まれます。\n\n## 4. プロバイダーの優先順位と重複排除\n\n`loadCapability(\"rules\")`（`capability/index.ts`）はプロバイダーの出力をマージし、`rule.name` で重複排除します。\n\n### 優先順位モデル\n\n- プロバイダーは優先度の降順で順序付けられます。\n- 同じ優先度の場合は登録順が維持されます（`discovery/index.ts` から `cursor` が `windsurf` より先）。\n- 重複排除は先勝ち方式です：最初に見つかったルール名が保持され、後から同名のアイテムは `all` で `_shadowed` としてマークされ、`items` からは除外されます。\n\n現在の実効的なルールプロバイダーの順序は以下の通りです：\n\n1. `native`（100）\n2. `cursor`（50）\n3. `windsurf`（50）\n4. `cline`（40）\n\n### プロバイダー内の順序に関する注意点\n\nプロバイダー内では、アイテムの順序は `loadFilesFromDir` の glob 結果の順序と明示的な push 順序に由来します。通常の使用では十分に決定論的ですが、コード上で明示的にソートされているわけではありません。\n\nソース順序の主な違い：\n\n- `native` はプロジェクトの後にユーザー設定ディレクトリを追加します。\n- `cursor` はユーザーの後にプロジェクトの結果を追加します。\n- `windsurf` はユーザーの `global_rules` を最初に追加し、次にプロジェクトルールを追加します。\n- `cline` は最も近い `.clinerules` ソースのみを読み込みます。\n\n## 5. ルールブック、常時適用、TTSR バケットへの分割\n\n`createAgentSession`（`sdk.ts`）でのルール検出後：\n\n1. 検出されたすべてのルールがスキャンされます。\n2. `condition`（フロントマターキー、フォールバックとして `ttsr_trigger` / `ttsrTrigger` も受け付ける）を持つルールは `TtsrManager` に登録されます。\n3. 以下の述語で別途 `rulebookRules` リストが構築されます：\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && !rule.alwaysApply && !!rule.description\n```\n\n4. `alwaysApplyRules` リストが構築されます：\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && rule.alwaysApply === true\n```\n\n### バケットの動作\n\n- **TTSR バケット**: `condition` を持つ任意のルール（description は不要）。他のバケットより優先されます。\n- **常時適用バケット**: `alwaysApply === true` で、TTSR ではないもの。コンテンツ全体がシステムプロンプトに注入されます。`rule://` で解決可能です。\n- **ルールブックバケット**: description が必須、TTSR でなく、`alwaysApply` でないもの。システムプロンプトに名前と説明で一覧表示され、コンテンツは `rule://` を介してオンデマンドで読み取られます。\n- `condition` と `alwaysApply` の両方を持つルールは TTSR のみに振り分けられます（TTSR が優先）。\n- `alwaysApply` と `description` の両方を持つルールは常時適用のみに振り分けられます（ルールブックには含まれません）。\n\n## 6. メタデータがランタイムサーフェスに与える影響\n\n### `description`\n\n- ルールブックへの包含に必須です。\n- システムプロンプトの `<rules>` ブロックにレンダリングされます。\n- description がない場合、ルールは `rule://` で利用できず、システムプロンプトのルール一覧にも表示されません。\n\n### `globs`\n\n- `Rule` に引き継がれます。\n- システムプロンプトのルールブロックに `<glob>...</glob>` エントリとしてレンダリングされます。\n- ルール UI 状態（`extensions` モードリスト）に公開されます。\n- **このパイプラインでは自動マッチングは強制されません。** 現在のファイル/ツールターゲットによってルールを選択するランタイム glob マッチャーは存在しません。\n\n### `alwaysApply`\n\n- プロバイダーによって解析・保持されます。\n- UI 表示で使用されます（拡張機能状態マネージャーでの `\"always\"` トリガーラベル）。\n- `rulebookRules` からの除外条件として使用されます。\n- **ルールのコンテンツ全体がシステムプロンプトに自動注入されます**（ルールブックルールセクションの前に）。\n- ルールは再読み取りのために `rule://<name>` でもアドレス指定可能です。\n\n### `ttsr_trigger`\n\n- `rule.ttsrTrigger` にマッピングされます。\n- 存在する場合、ルールはルールブックではなく TTSR マネージャーにルーティングされます。\n\n## 7. システムプロンプトへの組み込みパス\n\n`buildSystemPromptInternal` は `rules`（ルールブック）と `alwaysApplyRules` の両方を受け取ります。\n\n常時適用ルールが最初にレンダリングされ、その生のコンテンツがプロンプトに直接注入されます。\n\nルールブックルールは `# Rules` セクションに以下の形式でレンダリングされます：\n\n- `Read rule://<name> when working in matching domain`\n- 各ルールの `name`、`description`、およびオプションの `<glob>` リスト\n\nこれは助言的/コンテキスト的なものです：プロンプトテキストはモデルに適用可能なルールを読むよう求めますが、コードは glob の適用可能性を強制しません。\n\n## 8. `rule://` 内部 URL の動作\n\n`RuleProtocolHandler` は以下で登録されます：\n\n```ts\nnew RuleProtocolHandler({ getRules: () => [...rulebookRules, ...alwaysApplyRules] })\n```\n\n影響：\n\n- `rule://<name>` は **rulebookRules** と **alwaysApplyRules** の両方に対して解決されます。\n- TTSR のみのルール、および description も `alwaysApply` もないルールは `rule://` でアドレス指定できません。\n- 解決は正確な名前一致です。\n- 不明な名前の場合、利用可能なルール名のリストを含むエラーが返されます。\n- 返されるコンテンツは生の `rule.content`（フロントマター除去済み）で、コンテンツタイプは `text/markdown` です。\n\n## 9. 既知の部分的/未強制のセマンティクス\n\n1. プロバイダーの説明ではレガシーファイル（`.cursorrules`、`.windsurfrules`）に言及していますが、現在のローダーコードパスでは実際にはそれらのファイルを読み取りません。\n2. `globs` メタデータはプロンプト/UI に表示されますが、ルール選択ロジックでは強制されません。\n3. `rule://` のルール選択にはルールブックと常時適用ルールが含まれますが、TTSR のみのルールは含まれません。\n4. 検出警告（`loadCapability(\"rules\").warnings`）は生成されますが、`createAgentSession` は現在このパスでそれらを表示/ログ出力しません。\n",
	"ja/extensions/skills.md": "---\ntitle: スキル\ndescription: コーディングエージェントにおける特化した機能の登録、検出、呼び出しのためのスキルシステム。\nsidebar:\n  order: 3\n  label: スキル\ni18n:\n  sourceHash: 3e062cc13851\n  translator: machine\n---\n\n# スキル\n\nスキルはファイルベースの機能パックであり、起動時に検出され、以下の形式でモデルに公開されます：\n\n- システムプロンプト内の軽量メタデータ（名前 + 説明）\n- `read skill://...` によるオンデマンドコンテンツ\n- オプションのインタラクティブ `/skill:<name>` コマンド\n\nこのドキュメントでは、`src/extensibility/skills.ts`、`src/discovery/builtin.ts`、`src/internal-urls/skill-protocol.ts`、および `src/discovery/agents-md.ts` における現在のランタイム動作を説明します。\n\n## このコードベースにおけるスキルとは\n\n検出されたスキルは以下で表現されます：\n\n- `name`\n- `description`\n- `filePath`（`SKILL.md` のパス）\n- `baseDir`（スキルディレクトリ）\n- ソースメタデータ（`provider`、`level`、パス）\n\nランタイムが有効性のために必要とするのは `name` と `path` のみです。実際には、マッチングの品質は `description` が意味のあるものであるかどうかに依存します。\n\n## 必須のレイアウトと SKILL.md の要件\n\n### ディレクトリレイアウト\n\nプロバイダーベースの検出（native/Claude/Codex/Agents/plugin プロバイダー）では、スキルは **`skills/` の1階層下** として検出されます：\n\n- `<skills-root>/<skill-name>/SKILL.md`\n\n`<skills-root>/group/<skill>/SKILL.md` のようなネストされたパターンは、プロバイダーローダーでは検出されません。\n\n`skills.customDirectories` の場合、スキャンは同じ非再帰的なレイアウト（`*/SKILL.md`）を使用します。\n\n```text\nProvider-discovered layout (non-recursive under skills/):\n\n<root>/skills/\n  ├─ postgres/\n  │   └─ SKILL.md      ✅ discovered\n  ├─ pdf/\n  │   └─ SKILL.md      ✅ discovered\n  └─ team/\n      └─ internal/\n          └─ SKILL.md  ❌ not discovered by provider loaders\n\nCustom-directory scanning is also non-recursive, so nested paths are ignored unless you point `customDirectories` at that nested parent.\n```\n\n### `SKILL.md` フロントマター\n\nスキル型でサポートされるフロントマターフィールド：\n\n- `name?: string`\n- `description?: string`\n- `globs?: string[]`\n- `alwaysApply?: boolean`\n- 追加のキーは不明なメタデータとして保持されます\n\n現在のランタイム動作：\n\n- `name` はデフォルトでスキルディレクトリ名になります\n- `description` は以下の場合に必須です：\n  - ネイティブ `.xcsh` プロバイダーのスキル検出（`requireDescription: true`）\n  - `src/discovery/helpers.ts` の `scanSkillsFromDir` 経由の `skills.customDirectories` スキャン（非再帰的）\n- ネイティブ以外のプロバイダーは説明なしでスキルをロードできます\n\n## 検出パイプライン\n\n`src/extensibility/skills.ts` の `discoverSkills()` は2つのパスを実行します：\n\n1. `loadCapability(\"skills\")` 経由の **機能プロバイダー**\n2. `scanSkillsFromDir(..., { requireDescription: true })` 経由の **カスタムディレクトリ**（1階層のディレクトリ列挙）\n\n`skills.enabled` が `false` の場合、検出はスキルを返しません。\n\n### 組み込みスキルプロバイダーと優先順位\n\nプロバイダーの順序は優先度優先（高い方が勝つ）で、同順位の場合は登録順です。\n\n現在登録されているスキルプロバイダー：\n\n1. `native`（優先度 100）— `src/discovery/builtin.ts` 経由の `.xcsh` ユーザー/プロジェクトスキル\n2. `claude`（優先度 80）\n3. 優先度 70 グループ（登録順）：\n   - `claude-plugins`\n   - `agents`\n   - `codex`\n\n重複排除キーはスキル名です。指定された名前の最初のアイテムが優先されます。\n\n### ソーストグルとフィルタリング\n\n`discoverSkills()` は以下の制御を適用します：\n\n- ソーストグル：`enableCodexUser`、`enableClaudeUser`、`enableClaudeProject`、`enablePiUser`、`enablePiProject`\n- スキル名に対する glob フィルター：\n  - `ignoredSkills`（除外）\n  - `includeSkills`（許可リストへの包含；空の場合はすべてを含む）\n\nフィルター順序：\n\n1. ソースが有効\n2. 無視されていない\n3. 包含されている（包含リストがある場合）\n\ncodex/claude/native 以外のプロバイダー（例：`agents`、`claude-plugins`）では、有効化は現在のところ次のフォールバックに従います：**いずれかの**組み込みソーストグルが有効であれば有効。\n\n### 衝突と重複の処理\n\n- 機能の重複排除は、名前ごとに最初のスキルを保持します（最高優先度のプロバイダー）\n- `extensibility/skills.ts` はさらに：\n  - `realpath` による同一ファイルの重複排除（シンボリックリンク対応）\n  - 後続のスキル名が競合する場合に衝突警告を発行\n  - `scanSkillsFromDir` のシンアダプターとして便利な `discoverSkillsFromDir({ dir, source })` API を保持\n- カスタムディレクトリのスキルはプロバイダースキルの後にマージされ、同じ衝突動作に従います\n\n## ランタイム使用動作\n\n### システムプロンプトへの公開\n\nシステムプロンプトの構築（`src/system-prompt.ts`）は、検出されたスキルを以下のように使用します：\n\n- `read` ツールが利用可能な場合：\n  - 検出されたスキルリストをプロンプトに含める\n- それ以外の場合：\n  - 検出されたリストを省略\n\nTask ツールのサブエージェントは、通常のセッション作成を通じてセッションの検出/提供されたスキルリストを受け取ります。タスクごとのスキル固定オーバーライドはありません。\n\n### インタラクティブ `/skill:<name>` コマンド\n\n`skills.enableSkillCommands` が true の場合、インタラクティブモードは検出されたスキルごとに1つのスラッシュコマンドを登録します。\n\n`/skill:<name> [args]` の動作：\n\n- `filePath` からスキルファイルを直接読み取る\n- フロントマターを除去\n- スキル本文をフォローアップカスタムメッセージとして挿入\n- メタデータを追加（`Skill: <path>`、オプションで `User: <args>`）\n\n## `skill://` URL の動作\n\n`src/internal-urls/skill-protocol.ts` は以下をサポートします：\n\n- `skill://<name>` → そのスキルの `SKILL.md` に解決\n- `skill://<name>/<relative-path>` → そのスキルディレクトリ内に解決\n\n```text\nskill:// URL resolution\n\nskill://pdf\n  -> <pdf-base>/SKILL.md\n\nskill://pdf/references/tables.md\n  -> <pdf-base>/references/tables.md\n\nGuards:\n- reject absolute paths\n- reject `..` traversal\n- reject any resolved path escaping <pdf-base>\n```\n\n解決の詳細：\n\n- スキル名は完全一致する必要があります\n- 相対パスは URL デコードされます\n- 絶対パスは拒否されます\n- パストラバーサル（`..`）は拒否されます\n- 解決されたパスは `baseDir` 内に留まる必要があります\n- 存在しないファイルは明示的な `File not found` エラーを返します\n\nコンテンツタイプ：\n\n- `.md` => `text/markdown`\n- その他すべて => `text/plain`\n\n存在しないアセットに対するフォールバック検索は実行されません。\n\n## スキル vs XCSH.md、コマンド、ツール、フック\n\n### スキル vs XCSH.md\n\n- **スキル**：タスクコンテキストによって選択されるか、明示的に要求される名前付きのオプション機能パック\n- **XCSH.md/コンテキストファイル**：コンテキストファイル機能としてロードされ、レベル/深度ルールによってマージされる永続的な指示ファイル\n\n`src/discovery/agents-md.ts` は、`cwd` から親ディレクトリを上方にたどり、スタンドアロンの `XCSH.md` ファイルを検出します（深度 20 まで）。隠しディレクトリセグメントは除外されます。\n\n### スキル vs スラッシュコマンド\n\n- **スキル**：モデルが読み取り可能なナレッジ/ワークフローコンテンツ\n- **スラッシュコマンド**：ユーザーが呼び出すコマンドエントリポイント\n- `/skill:<name>` はスキルテキストを挿入する便利なラッパーです。スキル検出のセマンティクスは変更しません\n\n### スキル vs カスタムツール\n\n- **スキル**：プロンプトコンテキストと `read` を通じてロードされるドキュメント/ワークフローコンテンツ\n- **カスタムツール**：スキーマとランタイム副作用を持つ、モデルが呼び出し可能な実行可能ツール API\n\n### スキル vs フック\n\n- **スキル**：パッシブコンテンツ\n- **フック**：実行中に動作をブロック/変更できるイベント駆動のランタイムインターセプター\n\n## 検出ロジックに基づく実践的なオーサリングガイダンス\n\n- 各スキルを独自のディレクトリに配置する：`<skills-root>/<skill-name>/SKILL.md`\n- 常に明示的な `name` と `description` フロントマターを含める\n- 参照されるアセットは同じスキルディレクトリ下に置き、`skill://<name>/...` でアクセスする\n- ネストされた分類体系（`team/domain/skill`）の場合は、`skills.customDirectories` をネストされた親ディレクトリに指定する。スキャン自体は非再帰的のまま\n- ソース間でスキル名が重複しないようにする。プロバイダーの優先順位により最初のマッチが優先されます\n",
	"ja/index.md": "---\ntitle: xcsh ドキュメント\ndescription: >-\n  AI駆動の開発CLIで、TypeScriptコーディングエージェントとRustネイティブレイヤーを備え、長期セッション、MCPサポート、プラットフォームパッケージングに対応しています。\nsidebar:\n  order: 0\n  label: 概要\ni18n:\n  sourceHash: b9288f42bf46\n  translator: machine\n---\n\nxcshは、TypeScriptコーディングエージェントとRustネイティブレイヤー（`pi-natives`）を備えたAI駆動の開発CLIです。オープンソースの[`badlogic/pi-mono`](https://github.com/badlogic/pi-mono)ラインを拡張し、堅牢なランタイム、ツリーナビゲーションとコンパクションを備えた長期セッション、Python IPythonツール、完全なMCPサポート、スキルシステム、およびLinux、macOS、Windowsを対象としたプラットフォームパッケージングを提供します。\n\n## はじめに\n\n- **[F5 XC コンテキスト](/runtime-tools/context-command)** — F5 Distributed Cloudテナントに接続します。コンテキストの作成、切り替え、名前空間と資格情報の管理を行います。\n- **設定** — xcshが設定を検出、解決、およびレイヤー化する方法。\n- **ランタイムとツール** — bash / notebook / resolveツールランタイムとスラッシュコマンドの表面。\n- **セッション** — 追記専用エントリログ、ツリーナビゲーション、コンパクション、および自律メモリシステム。\n- **ネイティブ（Rust）** — シェル / PTY / メディア / 検索を支える`pi-natives` N-APIアドオンのアーキテクチャ。\n- **MCP** — 設定、プロトコル内部、ランタイムライフサイクル、およびサーバーとツールの作成方法。\n- **拡張機能、スキル、プラグイン** — 作成、読み込み、マッチングルール、マーケットプレイス、およびプラグインインストーラー。\n- **プロバイダーとモデル** — モデル設定、ストリーミング内部、およびPython / IPythonランタイム。\n- **TUI** — テーマ設定、`/tree`コマンド、および拡張機能やカスタムツール向けの統合フック。\n\n## このドキュメントセットの構成\n\nサイドバーの各トップレベルグループは、エージェントのサブシステムに対応しています。グループ内では、ページが「概要」から「内部構造」へと進むため、手元のタスクに必要な十分なコンテキストを得た時点で読み進めることを止められます。\n",
	"ja/mcp/mcp-config.md": "---\ntitle: MCP設定\ndescription: コーディングエージェントランタイムのMCPサーバー設定、バリデーション、および管理。\nsidebar:\n  order: 1\n  label: 設定\ni18n:\n  sourceHash: ef8b49458ce9\n  translator: machine\n---\n\n# OMPにおけるMCP設定\n\nこのガイドでは、OMPコーディングエージェントのMCPサーバーの追加、編集、およびバリデーションの方法を説明します。\n\nコードにおける情報源:\n\n- ランタイム設定型: `packages/coding-agent/src/mcp/types.ts`\n- 設定ライター: `packages/coding-agent/src/mcp/config-writer.ts`\n- ローダー + バリデーション: `packages/coding-agent/src/mcp/config.ts`\n- スタンドアロン `mcp.json` ディスカバリー: `packages/coding-agent/src/discovery/mcp-json.ts`\n- スキーマ: `packages/coding-agent/src/config/mcp-schema.json`\n\n## 推奨される設定ファイルの配置場所\n\nOMPは複数のツール（`.claude/`、`.cursor/`、`.vscode/`、`opencode.json`など）からMCPサーバーを検出できますが、OMPネイティブの設定には通常以下のファイルのいずれかを使用してください:\n\n- プロジェクト: `.xcsh/mcp.json`\n- ユーザー: `~/.xcsh/mcp.json`\n\nOMPはプロジェクトルートにあるフォールバック用のスタンドアロンファイルも受け付けます:\n\n- `mcp.json`\n- `.mcp.json`\n\nOMPに設定を管理させたい場合は `.xcsh/mcp.json` を使用してください。他のMCPクライアントも読み取れるポータブルなフォールバックファイルが必要な場合のみ、ルートの `mcp.json` / `.mcp.json` を使用してください。\n\n## スキーマ参照の追加\n\nエディターの自動補完とバリデーションのために、ファイルの先頭に以下の行を追加してください:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {}\n}\n```\n\nOMPは `/mcp add`、`/mcp enable`、`/mcp disable`、`/mcp reauth`、その他の設定書き込みフローがOMP管理のMCPファイルを作成または更新する際に、これを自動的に書き込むようになりました。\n\n## ファイル構造\n\nOMPは以下のトップレベル構造をサポートしています:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"server-name\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"some-mcp-server\"]\n    }\n  },\n  \"disabledServers\": [\"server-name\"]\n}\n```\n\nトップレベルキー:\n\n- `$schema` — ツール用のオプションのJSON Schema URL\n- `mcpServers` — サーバー名からサーバー設定へのマップ\n- `disabledServers` — 検出されたサーバーを名前で無効にするためのユーザーレベルの拒否リスト\n\nサーバー名は `^[a-zA-Z0-9_.-]{1,100}$` に一致する必要があります。\n\n## サポートされるサーバーフィールド\n\nすべてのトランスポートに共通のフィールド:\n\n- `enabled?: boolean` — `false` の場合、このサーバーをスキップ\n- `timeout?: number` — ミリ秒単位の接続タイムアウト\n- `auth?: { ... }` — OMPがOAuth/APIキーフローで使用する認証メタデータ\n- `oauth?: { ... }` — 認証/再認証時に使用する明示的なOAuthクライアント設定\n\n### `stdio` トランスポート\n\n`type` が省略された場合、`stdio` がデフォルトです。\n\n必須:\n\n- `command: string`\n\nオプション:\n\n- `type?: \"stdio\"`\n- `args?: string[]`\n- `env?: Record<string, string>`\n- `cwd?: string`\n\n例:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/Users/alice/projects\",\n        \"/Users/alice/Documents\"\n      ]\n    }\n  }\n}\n```\n\nこれは公式のFilesystem MCPサーバーパッケージ（`@modelcontextprotocol/server-filesystem`）に従っています。\n\n### `http` トランスポート\n\n必須:\n\n- `type: \"http\"`\n- `url: string`\n\nオプション:\n\n- `headers?: Record<string, string>`\n\n例:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\nこれはGitHubのホスト型GitHub MCPサーバーエンドポイントに対応しています。\n\n### `sse` トランスポート\n\n必須:\n\n- `type: \"sse\"`\n- `url: string`\n\nオプション:\n\n- `headers?: Record<string, string>`\n\n例:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"legacy-remote\": {\n      \"type\": \"sse\",\n      \"url\": \"https://example.com/mcp/sse\"\n    }\n  }\n}\n```\n\n`sse` は互換性のためにまだサポートされていますが、MCP仕様では新しいサーバーにはStreamable HTTP（`type: \"http\"`）を推奨しています。\n\n## 認証フィールド\n\nOMPは2つの認証関連オブジェクトを理解します。\n\n### `auth`\n\n```json\n{\n  \"type\": \"oauth\" | \"apikey\",\n  \"credentialId\": \"optional-stored-credential-id\",\n  \"tokenUrl\": \"optional-token-endpoint\",\n  \"clientId\": \"optional-client-id\",\n  \"clientSecret\": \"optional-client-secret\"\n}\n```\n\nOMPがサーバーの資格情報を復元する方法を記憶する必要がある場合に使用します。\n\n### `oauth`\n\n```json\n{\n  \"clientId\": \"...\",\n  \"clientSecret\": \"...\",\n  \"redirectUri\": \"...\",\n  \"callbackPort\": 3334,\n  \"callbackPath\": \"/oauth/callback\"\n}\n```\n\nMCPサーバーが明示的なOAuthクライアント設定を必要とする場合に使用します。\n\nSlackが現在最もわかりやすい例です。SlackのMCPサーバーは `https://mcp.slack.com/mcp` でホストされており、Streamable HTTPを使用し、SlackアプリのクライアントクレデンシャルによるConfidential OAuthを必要とします。\n\n例:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\nSlackのドキュメントに記載されている関連エンドポイント:\n\n- MCPエンドポイント: `https://mcp.slack.com/mcp`\n- 認可エンドポイント: `https://slack.com/oauth/v2_user/authorize`\n- トークンエンドポイント: `https://slack.com/api/oauth.v2.user.access`\n\n## よく使われるコピー＆ペースト用の例\n\n### stdioによるFilesystemサーバー\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/absolute/path/one\",\n        \"/absolute/path/two\"\n      ]\n    }\n  }\n}\n```\n\n### HTTP経由のGitHubホスト型サーバー\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\n### Docker経由のGitHubローカルサーバー\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"-i\",\n        \"--rm\",\n        \"-e\",\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\",\n        \"ghcr.io/github/github-mcp-server\"\n      ],\n      \"env\": {\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n      }\n    }\n  }\n}\n```\n\nこれはGitHubの公式ローカルDockerイメージ `ghcr.io/github/github-mcp-server` に対応しています。\n\n### OAuth経由のSlackホスト型サーバー\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\n## シークレットと変数の解決\n\nこの部分は多くの人がつまずくところです。\n\n### `.xcsh/mcp.json` と `~/.xcsh/mcp.json` の場合\n\nOMPがサーバーを起動したりHTTPリクエストを行う前に、`env` と `headers` の値を以下のように解決します:\n\n1. 値が `!` で始まる場合、OMPはそれをシェルコマンドとして実行し、トリムされたstdoutを使用します。\n2. それ以外の場合、OMPはまず値が環境変数名に一致するかどうかを確認します。\n3. その環境変数が設定されていない場合、OMPは文字列をそのまま使用します。\n\n例:\n\n```json\n{\n  \"env\": {\n    \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n  },\n  \"headers\": {\n    \"X-MCP-Insiders\": \"true\"\n  }\n}\n```\n\nつまり、以下のようにローカルシークレットに対して有効かつ便利です:\n\n- `\"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"` → 現在のシェル環境からコピー\n- `\"Authorization\": \"Bearer hardcoded-token\"` → リテラル値を使用\n- `\"Authorization\": \"!printf 'Bearer %s' \\\"$GITHUB_TOKEN\\\"\"` → コマンドからヘッダーを構築\n\n### ルートの `mcp.json` と `.mcp.json` の場合\n\nスタンドアロンのフォールバックローダーは、ディスカバリー時に文字列内の `${VAR}` と `${VAR:-default}` も展開します。\n\n例:\n\n```json\n{\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\",\n      \"headers\": {\n        \"Authorization\": \"Bearer ${GITHUB_TOKEN}\"\n      }\n    }\n  }\n}\n```\n\n最も予想外の動作が少ないOMPの挙動を望む場合は、`.xcsh/mcp.json` を優先し、明示的なenv/header値を使用してください。\n\n## `disabledServers`\n\n`disabledServers` は、主にユーザー設定ファイル（`~/.xcsh/mcp.json`）において、他のソースから検出されたサーバーを、そのツールの設定を編集せずにOMPに無視させたい場合に便利です。\n\n例:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"disabledServers\": [\"github\", \"slack\"]\n}\n```\n\n## `/mcp add` とJSON直接編集の比較\n\nガイド付きセットアップが必要な場合は `/mcp add` を使用してください。\n\n以下の場合はJSON直接編集を使用してください:\n\n- ウィザードがまだプロンプトしないトランスポートまたは認証オプションが必要な場合\n- 別のMCPクライアントからサーバー定義を貼り付けたい場合\n- エディターでスキーマベースのバリデーションを使用したい場合\n\n編集後は以下を使用してください:\n\n- `/mcp reload` で現在のセッションでサーバーを再検出して再接続\n- `/mcp list` でサーバーがどの設定ファイルから来たかを確認\n- `/mcp test <name>` で単一のサーバーをテスト\n\n## OMPが適用するバリデーションルール\n\n`packages/coding-agent/src/mcp/config.ts` の `validateServerConfig()` より:\n\n- `stdio` は `command` を必要とする\n- `http` と `sse` は `url` を必要とする\n- サーバーは `command` と `url` の両方を設定できない\n- 不明な `type` 値は拒否される\n\n実際の影響:\n\n- `type` を省略すると `stdio` になる\n- リモートサーバーの設定を貼り付けて `\"type\": \"http\"` を忘れると、OMPはそれを `stdio` として扱い、`command` がないと警告する\n- `sse` は互換性のために有効だが、新しいホスト型サーバーは通常 `http` として設定すべき\n\n## ディスカバリーと優先順位\n\nOMPはファイル間で重複するサーバー定義をマージしません。ディスカバリープロバイダーには優先順位があり、より高い優先順位の定義が優先されます。\n\n実際の運用では:\n\n- OMP固有のオーバーライドが必要な場合は `.xcsh/mcp.json` または `~/.xcsh/mcp.json` を優先\n- 可能であればツール間でサーバー名を一意に保つ\n- サードパーティの設定が不要なサーバーを再度追加し続ける場合は、ユーザー設定で `disabledServers` を使用\n\n## トラブルシューティング\n\n### `Server \"name\": stdio server requires \"command\" field`\n\nリモートサーバーに `type: \"http\"` を指定し忘れている可能性があります。\n\n### `Server \"name\": both \"command\" and \"url\" are set`\n\nトランスポートを1つ選択してください。OMPは `command` をstdioとして、`url` をhttp/sseとして扱います。\n\n### `/mcp add` は成功したがサーバーが接続されない\n\nJSONは有効ですが、サーバーに到達できない可能性があります。`/mcp test <name>` を使用して以下を確認してください:\n\n- バイナリまたはDockerイメージが存在するか\n- 必要な環境変数が設定されているか\n- リモートURLに到達可能か\n- OAuthまたはAPIトークンが有効か\n\n### サーバーが他のツールの設定には存在するがOMPにはない\n\n`/mcp list` を実行してください。OMPは多くのサードパーティMCPファイルを検出しますが、プロジェクトレベルの読み込みは `mcp.enableProjectConfig` 設定で無効にすることもできます。\n\n## 参考資料\n\n- MCPトランスポート仕様: <https://modelcontextprotocol.io/specification/2025-03-26/basic/transports>\n- Filesystemサーバーパッケージ: <https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem>\n- GitHub MCPサーバー: <https://github.com/github/github-mcp-server>\n- Slack MCPサーバードキュメント: <https://docs.slack.dev/ai/slack-mcp-server/>\n",
	"ja/mcp/mcp-protocol-transports.md": "---\ntitle: MCPプロトコルとトランスポート内部構造\ndescription: >-\n  MCP protocol implementation with stdio, SSE, and streamable HTTP transport\n  layers.\nsidebar:\n  order: 2\n  label: プロトコルとトランスポート\ni18n:\n  sourceHash: 48632064dd00\n  translator: machine\n---\n\n# MCPプロトコルとトランスポート内部構造\n\nこのドキュメントでは、coding-agentがMCP JSON-RPCメッセージングをどのように実装しているか、またプロトコルの関心事がトランスポートの関心事からどのように分離されているかについて説明します。\n\n## スコープ\n\n対象範囲：\n\n- JSON-RPCのリクエスト/レスポンスおよび通知フロー\n- stdioおよびHTTP/SSEトランスポートにおけるリクエストの相関とライフサイクル\n- タイムアウトとキャンセルの動作\n- エラーの伝搬と不正なペイロードの処理\n- トランスポート選択の境界（`stdio` vs `http`/`sse`）\n- 再接続/リトライの責任がトランスポートレベルかマネージャーレベルか\n\nエクステンション作成のUXやコマンドUIは対象外です。\n\n## 実装ファイル\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/transports/stdio.ts`](../../packages/coding-agent/src/mcp/transports/stdio.ts)\n- [`src/mcp/transports/http.ts`](../../packages/coding-agent/src/mcp/transports/http.ts)\n- [`src/mcp/transports/index.ts`](../../packages/coding-agent/src/mcp/transports/index.ts)\n- [`src/mcp/json-rpc.ts`](../../packages/coding-agent/src/mcp/json-rpc.ts)\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n\n## レイヤー境界\n\n### プロトコルレイヤー（JSON-RPC + MCPメソッド）\n\n- メッセージの形状は`types.ts`で定義されています（`JsonRpcRequest`、`JsonRpcNotification`、`JsonRpcResponse`、`JsonRpcMessage`）。\n- MCPクライアントロジック（`client.ts`）がメソッドの順序とセッションハンドシェイクを決定します：\n  1. `initialize` リクエスト\n  2. `notifications/initialized` 通知\n  3. `tools/list`、`tools/call` などのメソッド呼び出し\n\n### トランスポートレイヤー（`MCPTransport`）\n\n`MCPTransport`は配信とライフサイクルを抽象化します：\n\n- `request(method, params, options?) -> Promise<T>`\n- `notify(method, params?) -> Promise<void>`\n- `close()`\n- `connected`\n- オプションのコールバック：`onClose`、`onError`、`onNotification`\n\nトランスポート実装はフレーミングとI/Oの詳細を担当します：\n\n- `StdioTransport`：サブプロセスのstdioを介した改行区切りJSON\n- `HttpTransport`：HTTP POSTを介したJSON-RPC、オプションのSSEレスポンス/リスニング付き\n\n### 現在の重要な注意事項\n\nトランスポートコールバック（`onClose`、`onError`、`onNotification`）は実装されていますが、現在の`MCPClient`/`MCPManager`フローではこれらのコールバックに再接続ロジックが接続されていません。通知は呼び出し元がハンドラーを登録した場合のみ消費されます。\n\n## トランスポートの選択\n\n`client.ts:createTransport()`が設定からトランスポートを選択します：\n\n- `type`が省略または`\"stdio\"` -> `createStdioTransport`\n- `\"http\"`または`\"sse\"` -> `createHttpTransport`\n\n`\"sse\"`はHTTPトランスポートのバリアント（同じクラス）として扱われ、別個のトランスポート実装ではありません。\n\n## JSON-RPCメッセージフローと相関\n\n## リクエストID\n\n各トランスポートはリクエストごとにIDを生成します（`Math.random` + タイムスタンプ文字列）。IDはトランスポートローカルの相関トークンです。\n\n## Stdioの相関パス\n\n- 送信リクエストは1つのJSONオブジェクト + `\\n`としてシリアライズされます。\n- `#pendingRequests: Map<id, {resolve,reject}>`がインフライトリクエストを格納します。\n- 読み取りループがstdoutからJSONLをパースし、`#handleMessage`を呼び出します。\n- 受信メッセージに一致する`id`がある場合、リクエストはresolve/rejectされます。\n- 受信メッセージに`method`があり`id`がない場合、通知として扱われ`onNotification`に送信されます。\n\n不明なIDは無視されます（rejectもエラーコールバックもありません）。\n\n## HTTPの相関パス\n\n- 送信リクエストは生成された`id`を含むJSONボディのHTTP `POST`です。\n- 非SSEレスポンスパス：1つのJSON-RPCレスポンスをパースし、`result`を返すか`error`でスローします。\n- SSEレスポンスパス（`Content-Type: text/event-stream`）：イベントをストリーミングし、期待するリクエストIDに一致し`result`または`error`を持つ最初のメッセージを返します。\n- `method`があり`id`がないSSEメッセージは通知として扱われます。\n\n一致するレスポンスの前にSSEストリームが終了した場合、リクエストは`No response received for request ID ...`で失敗します。\n\n## 通知\n\nクライアントは`transport.notify(...)`を介してJSON-RPC通知を送信します。\n\n- Stdio：通知フレーム（`jsonrpc`、`method`、オプションの`params`）と改行をstdinに書き込みます。\n- HTTP：`id`なしのPOSTボディを送信します。成功は`2xx`または`202 Accepted`を受け入れます。\n\nサーバー起点の通知はトランスポートの`onNotification`を通じてのみ表面化されます。マネージャー/クライアントにはデフォルトのグローバルサブスクライバーはありません。\n\n## Stdioトランスポートの内部構造\n\n## ライフサイクルと状態遷移\n\n- 初期状態：`connected=false`、`process=null`、pendingマップは空\n- `connect()`：\n  - 設定されたcommand/args/env/cwdでサブプロセスを生成\n  - connectedをマーク\n  - stdoutの読み取りループを開始（`readJsonl`）\n  - stderrのループを開始（読み取り/破棄；現在はサイレント）\n- `close()`：\n  - disconnectedをマーク\n  - すべての保留中リクエストをreject（`Transport closed`）\n  - サブプロセスをkill\n  - 読み取りループのシャットダウンを待機\n  - `onClose`を発行\n\n読み取りループが予期せず終了した場合、`finally`が`#handleClose()`をトリガーし、同じ保留中リクエストのrejectとcloseコールバックを実行します。\n\n## タイムアウトとキャンセル\n\nリクエストごとに：\n\n- タイムアウトのデフォルトは`config.timeout ?? 30000`\n- 呼び出し元からのオプションの`AbortSignal`\n- abortとtimeoutの両方が保留中のPromiseをrejectしマップエントリをクリーンアップ\n\nキャンセルはローカルのみです：トランスポートはプロトコルレベルのキャンセル通知をサーバーに送信しません。\n\n## 不正なペイロードの処理\n\n読み取りループ内：\n\n- パースされた各JSONL行は`try/catch`内の`#handleMessage`に渡されます\n- 不正/無効なメッセージ処理の例外は破棄されます（`Skip malformed lines`コメント）\n- ループは継続するため、1つの不正メッセージで接続が切断されることはありません\n\n基盤となるストリームパーサーがスローした場合、`onError`が呼び出され（まだ接続中の場合）、その後接続が閉じます。\n\n## 切断/障害時の動作\n\nプロセスの終了またはストリームの閉鎖時：\n\n- すべてのインフライトリクエストは`Transport closed`でrejectされます\n- 自動再起動や再接続はありません\n- 上位レイヤーが新しいトランスポートを作成して再接続する必要があります\n\n## バックプレッシャー/ストリーミングに関する注意事項\n\n- 送信書き込みは`stdin.write()` + `flush()`を使用し、drainセマンティクスを待機しません。\n- トランスポートには明示的なキューやhigh-watermark管理がありません。\n- 受信処理は（`readJsonl`に対する`for await`による）ストリーム駆動で、パースされたメッセージを1つずつ処理します。\n\n## HTTP/SSEトランスポートの内部構造\n\n## ライフサイクルと接続セマンティクス\n\nHTTPトランスポートは論理的な接続状態を持ちますが、リクエストパスはHTTP呼び出しごとにステートレスです：\n\n- `connect()`は`connected=true`を設定（ソケット/セッションハンドシェイクなし）\n- `Mcp-Session-Id`ヘッダーによるオプションのサーバーセッション追跡\n- `close()`はオプションで`Mcp-Session-Id`付きの`DELETE`を送信し、SSEリスナーを中断し、`onClose`を発行\n\nしたがって`connected`は「トランスポートが使用可能」を意味し、「永続ストリームが確立済み」を意味しません。\n\n## セッションヘッダーの動作\n\n- POSTレスポンスで`Mcp-Session-Id`ヘッダーが存在する場合、トランスポートがそれを保存します。\n- 後続のリクエスト/通知に`Mcp-Session-Id`が含まれます。\n- `close()`はHTTP DELETEでサーバーセッションの終了を試みます。終了の失敗は無視されます。\n\n## タイムアウトとキャンセル\n\n`request()`と`notify()`の両方について：\n\n- タイムアウトは`AbortController`を使用（`config.timeout ?? 30000`）\n- 外部シグナルが提供された場合、`AbortSignal.any([...])`でマージされます\n- AbortErrorの処理は呼び出し元のabortとタイムアウトを区別します\n\nスローされるエラー：\n\n- タイムアウト：`Request timeout after ...ms`（または`SSE response timeout ...`、`Notify timeout ...`）\n- 呼び出し元のabort：外部シグナルが既にabortされている場合、元のAbortErrorが再スローされます\n\n## HTTPエラーの伝搬\n\n非OKレスポンスの場合：\n\n- レスポンステキストがスローされるエラーに含まれます（`HTTP <status>: <text>`）\n- 存在する場合、`WWW-Authenticate`と`Mcp-Auth-Server`からの認証ヒントが追加されます\n\nJSON-RPCエラーオブジェクトの場合：\n\n- `MCP error <code>: <message>`をスローします\n\n不正なJSONボディ（`response.json()`の失敗）はパース例外として伝搬します。\n\n## SSEの動作とモード\n\n2つのSSEパスが存在します：\n\n1. **リクエストごとのSSEレスポンス**（`#parseSSEResponse`）\n   - POSTレスポンスのContent Typeが`text/event-stream`の場合に使用\n   - 一致するレスポンスIDが見つかるまでストリームを消費\n   - 同じストリーム内のインターリーブされた通知を処理可能\n\n2. **バックグラウンドSSEリスナー**（`startSSEListener()`）\n   - サーバー起点の通知用のオプションのGETリスナー\n   - 現在MCPマネージャー/クライアントによって自動的に開始されません\n   - GETが`405`を返した場合、リスナーはサイレントに自身を無効化します（サーバーがこのモードをサポートしていません）\n\n## 不正なペイロードと切断の処理\n\nSSE JSONパースエラーは`readSseJson`から浮上し、リクエスト/リスナーをrejectします。\n\n- リクエストSSEのパースエラーはアクティブなリクエストをrejectします。\n- バックグラウンドリスナーのエラーは`onError`をトリガーします（AbortErrorを除く）。\n- バックグラウンドリスナーの自動再接続はありません。\n\n## `json-rpc.ts`ユーティリティとトランスポート抽象化\n\n`src/mcp/json-rpc.ts`は、`MCPClient`/`MCPManager`が使用する`MCPTransport`抽象化ではなく、直接的なHTTP MCP呼び出し（Exa統合で使用）のための`callMCP()`と`parseSSE()`ヘルパーを提供します。\n\n`HttpTransport`との主な違い：\n\n- まずレスポンステキスト全体をパースし、次に最初の`data:`行を抽出（`parseSSE`）、JSONフォールバック付き\n- リクエストタイムアウト管理、abort API、session-id処理、トランスポートライフサイクルなし\n- 生のJSON-RPCエンベロープオブジェクトを返す\n\nこのパスは軽量ですが、完全なトランスポート実装ほど堅牢ではありません。\n\n## リトライ/再接続の責任\n\n## トランスポートレベル\n\n現在のトランスポート実装は以下を**行いません**：\n\n- 失敗したリクエストのリトライ\n- stdioプロセス終了後の再接続\n- SSEリスナーの再接続\n- 切断後のインフライトリクエストの再送信\n\n即座に失敗し、エラーを伝搬します。\n\n## マネージャー/クライアントレベル\n\n`MCPManager`はディスカバリー/初期接続のオーケストレーションを処理し、接続フローを再実行することでのみ再接続できます（`connectToServer`/`discoverAndConnect`パス）。ランタイム障害コールバック時に既に接続済みのトランスポートを自動修復することはしません。\n\n`MCPManager`には遅いサーバー向けの起動時フォールバック動作（キャッシュからの遅延ツール）がありますが、これはツール可用性のフォールバックであり、トランスポートのリトライではありません。\n\n## 障害シナリオまとめ\n\n- **不正なstdioメッセージ行**：破棄されます。ストリームは継続します。\n- **stdioストリーム/プロセスの終了**：トランスポートが閉じます。保留中のリクエストは`Transport closed`でrejectされます。\n- **HTTP非2xx**：リクエスト/notifyがHTTPエラーをスローします。\n- **無効なJSONレスポンス**：パース例外が伝搬されます。\n- **一致するIDなしでSSEが終了**：リクエストが`No response received for request ID ...`で失敗します。\n- **タイムアウト**：トランスポート固有のタイムアウトエラー。\n- **呼び出し元のabort**：呼び出し元のシグナルからAbortError/reasonが伝搬されます。\n\n## 実用的な境界ルール\n\n関心事がメッセージの形状、ID相関、またはMCPメソッドの順序であれば、プロトコル/クライアントロジックに属します。\n\n関心事がフレーミング（JSONL vs HTTP/SSE）、ストリームパース、fetch/spawnのライフサイクル、タイムアウトクロック、または接続のティアダウンであれば、トランスポート実装に属します。\n",
	"ja/mcp/mcp-runtime-lifecycle.md": "---\ntitle: MCPランタイムライフサイクル\ndescription: 初期化からツール登録、ヘルスモニタリング、シャットダウンまでのMCPサーバープロセスのライフサイクル。\nsidebar:\n  order: 3\n  label: ランタイムライフサイクル\ni18n:\n  sourceHash: d04cefaf38f8\n  translator: machine\n---\n\n# MCPランタイムライフサイクル\n\nこのドキュメントでは、MCPサーバーがcoding-agentランタイムにおいてどのように検出、接続、ツールとして公開、更新、および終了されるかを説明します。\n\n## ライフサイクルの概要\n\n1. **SDK起動時**に `discoverAndLoadMCPTools()` を呼び出します（MCPが無効でない場合）。\n2. **ディスカバリ**（`loadAllMCPConfigs`）がケーパビリティソースからMCPサーバー設定を解決し、無効化されたエントリ/プロジェクトエントリ/Exaエントリをフィルタリングし、ソースメタデータを保持します。\n3. **マネージャー接続フェーズ**（`MCPManager.connectServers`）がサーバーごとの接続と `tools/list` を並列で開始します。\n4. **高速起動ゲート**が最大250msまで待機し、以下を返す可能性があります：\n   - 完全にロードされた `MCPTool`、\n   - サーバーごとの失敗情報、\n   - またはまだ保留中のサーバーに対するキャッシュ済み `DeferredMCPTool`。\n5. **SDKワイヤリング**がMCPツールをセッションのランタイムツールレジストリに統合します。\n6. **ライブセッション**は `/mcp` フロー（`disconnectAll` + 再ディスカバリ + `session.refreshMCPTools`）を介してMCPツールを更新できます。\n7. **ティアダウン**は呼び出し元が `disconnectServer`/`disconnectAll` を実行した際に発生します。マネージャーは切断されたサーバーのMCPツール登録もクリアします。\n\n## ディスカバリとロードフェーズ\n\n### SDKからのエントリパス\n\n`src/sdk.ts` の `createAgentSession()` は、`enableMCP` が true（デフォルト）の場合にMCP起動を実行します：\n\n- `discoverAndLoadMCPTools(cwd, { ... })` を呼び出し、\n- `authStorage`、キャッシュストレージ、および `mcp.enableProjectConfig` 設定を渡し、\n- 常に `filterExa: true` を設定し、\n- サーバーごとのロード/接続エラーをログに記録し、\n- 返されたマネージャーを `toolSession.mcpManager` とセッション結果に格納します。\n\n`enableMCP` が false の場合、MCPディスカバリは完全にスキップされます。\n\n### 設定のディスカバリとフィルタリング\n\n`loadAllMCPConfigs()`（`src/mcp/config.ts`）はケーパビリティディスカバリを通じて正規のMCPサーバーアイテムをロードし、レガシー `MCPServerConfig` に変換します。\n\nフィルタリング動作：\n\n- `enableProjectConfig: false` はプロジェクトレベルのエントリ（`_source.level === \"project\"`）を除外します。\n- `enabled: false` のサーバーは接続試行前にスキップされます。\n- Exaサーバーはデフォルトでフィルタリングされ、APIキーはネイティブExaツール統合用に抽出されます。\n\n結果には `configs` と `sources`（後でプロバイダーラベリングに使用されるメタデータ）の両方が含まれます。\n\n### ディスカバリレベルの失敗動作\n\n`discoverAndLoadMCPTools()` は2つの失敗クラスを区別します：\n\n- **ディスカバリのハード失敗**（`manager.discoverAndConnect` からの例外、通常は設定ディスカバリに起因）：空のツールセットと1つの合成エラー `{ path: \".mcp.json\", error }` を返します。\n- **サーバーごとのランタイム/接続失敗**：マネージャーが `errors` マップ付きの部分的な成功を返し、他のサーバーは継続します。\n\nそのため、個々のMCPサーバーが失敗してもエージェントセッション全体は失敗しません。\n\n## マネージャーの状態モデル\n\n`MCPManager` は個別のレジストリでランタイムライフサイクルを追跡します：\n\n- `#connections: Map<string, MCPServerConnection>` — 完全に接続されたサーバー。\n- `#pendingConnections: Map<string, Promise<MCPServerConnection>>` — ハンドシェイク進行中。\n- `#pendingToolLoads: Map<string, Promise<{ connection, serverTools }>>` — 接続済みだがツールがまだロード中。\n- `#tools: CustomTool[]` — 呼び出し元に公開される現在のMCPツールビュー。\n- `#sources: Map<string, SourceMeta>` — 接続完了前でもプロバイダー/ソースのメタデータ。\n\n`getConnectionStatus(name)` はこれらのマップからステータスを導出します：\n\n- `#connections` にある場合は `connected`、\n- 保留中の接続または保留中のツールロードがある場合は `connecting`、\n- それ以外は `disconnected`。\n\n## 接続確立と起動タイミング\n\n## サーバーごとの接続パイプライン\n\n`connectServers()` で検出されたサーバーごとに：\n\n1. ソースメタデータを保存/更新、\n2. すでに接続済み/保留中の場合はスキップ、\n3. トランスポートフィールドの検証（`validateServerConfig`）、\n4. 認証/シェル置換の解決（`#resolveAuthConfig`）、\n5. `connectToServer(name, resolvedConfig)` を呼び出し、\n6. `listTools(connection)` を呼び出し、\n7. ツール定義をベストエフォートでキャッシュ（`MCPToolCache.set`）。\n\n`connectToServer()` の動作（`src/mcp/client.ts`）：\n\n- stdioまたはHTTP/SSEトランスポートを作成、\n- MCP `initialize` + `notifications/initialized` を実行、\n- タイムアウトを使用（`config.timeout` または30秒のデフォルト）、\n- 初期化失敗時にトランスポートを閉じる。\n\n### 高速起動ゲート + 遅延フォールバック\n\n`connectServers()` は以下のレースを待機します：\n\n- すべての接続/ツールロードタスクの完了、\n- `STARTUP_TIMEOUT_MS = 250`。\n\n250ms後：\n\n- 完了したタスクはライブ `MCPTool` になり、\n- 拒否されたタスクはサーバーごとのエラーを生成し、\n- まだ保留中のタスクは：\n  - キャッシュされたツール定義が利用可能な場合（`MCPToolCache.get`）、`DeferredMCPTool` を作成、\n  - それ以外の場合、保留中のタスクが完了するまでブロック。\n\nこれはハイブリッド起動モデルです：キャッシュが利用可能な場合は高速リターン、キャッシュがない場合は正確性のための待機。\n\n### バックグラウンド完了動作\n\n保留中の各 `toolsPromise` にはバックグラウンド継続もあり、最終的に：\n\n- `#replaceServerTools` を介してマネージャー状態のそのサーバーのツールスライスを置換、\n- キャッシュを書き込み、\n- 起動後にのみ遅延失敗をログ記録（`allowBackgroundLogging`）。\n\n## ツールの公開とライブセッションでの利用可能性\n\n### 起動時の登録\n\n`discoverAndLoadMCPTools()` はマネージャーのツールを `LoadedCustomTool[]` に変換し、パスを装飾します（既知の場合 `mcp:<server> via <providerName>`）。\n\n`createAgentSession()` はこれらのツールを `customTools` にプッシュし、`mcp_<server>_<tool>` のような名前でラップしてランタイムツールレジストリに追加します。\n\n### ツール呼び出し\n\n- `MCPTool` はすでに接続された `MCPServerConnection` を通じてツールを呼び出します。\n- `DeferredMCPTool` は呼び出し前に `waitForConnection(server)` を待機します。これにより、接続が準備できる前にキャッシュされたツールを存在させることができます。\n\nどちらも構造化されたツール出力を返し、トランスポート/ツールエラーを `MCP error: ...` ツールコンテンツに変換します（アボートはアボートのまま）。\n\n## リフレッシュ/リロードパス（起動時 vs ライブリロード）\n\n### 初期起動パス\n\n- `sdk.ts` での一回限りのディスカバリ/ロード、\n- ツールは初期セッションツールレジストリに登録。\n\n### インタラクティブリロードパス\n\n`/mcp reload` パス（`src/modes/controllers/mcp-command-controller.ts`）は以下を実行します：\n\n1. `mcpManager.disconnectAll()`、\n2. `mcpManager.discoverAndConnect()`、\n3. `session.refreshMCPTools(mcpManager.getTools())`。\n\n`session.refreshMCPTools()`（`src/session/agent-session.ts`）はすべての `mcp_` ツールを削除し、最新のMCPツールを再ラップし、ツールセットを再アクティベートして、セッションを再起動せずにMCPの変更を適用します。\n\n遅延接続のためのフォローアップパスもあります：特定のサーバーを待機した後、ステータスが `connected` になった場合、`session.refreshMCPTools(...)` を再実行して、新しく利用可能になったツールをセッション内で再バインドします。\n\n## ヘルス、再接続、および部分的な失敗動作\n\n現在のランタイム動作は意図的にミニマルです：\n\n- マネージャー/クライアントに**自律的なヘルスモニターはありません**。\n- トランスポートが切断された際の**自動再接続ループはありません**。\n- マネージャーはトランスポートの `onClose`/`onError` をサブスクライブしません。ステータスはレジストリ駆動です。\n- 再接続は明示的です：リロードフローまたは直接の `connectServers()` 呼び出し。\n\n運用上：\n\n- 1つのサーバーの失敗が正常なサーバーからツールを削除することはありません、\n- 接続/リスト失敗はサーバーごとに分離されます、\n- ツールキャッシュとバックグラウンド更新はベストエフォートです（警告/エラーはログ記録、ハードストップなし）。\n\n## ティアダウンのセマンティクス\n\n### サーバーレベルのティアダウン\n\n`disconnectServer(name)`:\n\n- 保留中のエントリ/ソースメタデータを削除、\n- 接続されている場合はトランスポートを閉じる、\n- マネージャー状態からそのサーバーの `mcp_` ツールを削除。\n\n### グローバルティアダウン\n\n`disconnectAll()`:\n\n- `Promise.allSettled` ですべてのアクティブなトランスポートを閉じる、\n- 保留中のマップ、ソース、接続、およびマネージャーのツールリストをクリア。\n\n現在のワイヤリングでは、明示的なティアダウンはMCPコマンドフロー（リロード/削除/無効化）で使用されます。起動パス自体に別の自動マネージャー破棄フックはありません。呼び出し元は、確定的なMCPシャットダウンが必要な場合にマネージャーの切断メソッドを呼び出す責任があります。\n\n## 失敗モードと保証\n\n| シナリオ | 動作 | ハード失敗 vs ベストエフォート |\n| --- | --- | --- |\n| ディスカバリがスロー（ケーパビリティ/設定ロードパス） | ローダーが空のツール + 合成 `.mcp.json` エラーを返す | ベストエフォートでのセッション起動 |\n| 無効なサーバー設定 | バリデーションエラーエントリでサーバーをスキップ | サーバーごとのベストエフォート |\n| 接続タイムアウト/初期化失敗 | サーバーエラーを記録、他は継続 | サーバーごとのベストエフォート |\n| 起動時にキャッシュヒットで `tools/list` がまだ保留中 | 遅延ツールを即座に返す | ベストエフォートの高速起動 |\n| 起動時にキャッシュなしで `tools/list` がまだ保留中 | 起動は保留中の完了を待機 | 正確性のためのハード待機 |\n| 遅延バックグラウンドツールロード失敗 | 起動ゲート後にログ記録 | ベストエフォートのログ記録 |\n| ランタイムでのトランスポート切断 | 自動再接続なし、再接続/リロードまで以降の呼び出しは失敗 | 手動アクションによるベストエフォートのリカバリ |\n\n## パブリックAPIサーフェス\n\n`src/mcp/index.ts` はローダー/マネージャー/クライアントAPIを外部呼び出し元向けに再エクスポートします。`src/sdk.ts` は同じローダー結果のシェイプを返すコンビニエンスラッパーとして `discoverMCPServers()` を公開します。\n\n## 実装ファイル\n\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts) — ローダーファサード、ディスカバリエラーの正規化、`LoadedCustomTool` 変換。\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts) — ライフサイクル状態レジストリ、並列接続/リストフロー、リフレッシュ/切断。\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts) — トランスポートセットアップ、初期化ハンドシェイク、リスト/呼び出し/切断。\n- [`src/mcp/index.ts`](../../packages/coding-agent/src/mcp/index.ts) — MCPモジュールAPIエクスポート。\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — セッション/ツールレジストリへの起動ワイヤリング。\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts) — マネージャーが使用する設定のディスカバリ/フィルタリング/バリデーション。\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts) — `MCPTool` と `DeferredMCPTool` のランタイム動作。\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — `refreshMCPTools` ライブ再バインディング。\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts) — インタラクティブリロード/再接続フロー。\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — 親マネージャー接続を介したサブエージェントMCPプロキシ。\n",
	"ja/mcp/mcp-server-tool-authoring.md": "---\ntitle: MCPサーバーとツールのオーサリング\ndescription: カスタムMCPサーバーの構築とコーディングエージェント用ツールの登録に関するガイド。\nsidebar:\n  order: 4\n  label: サーバーとツールのオーサリング\ni18n:\n  sourceHash: 160e7560ef1f\n  translator: machine\n---\n\n# MCPサーバーとツールのオーサリング\n\nこのドキュメントでは、MCPサーバー定義がcoding-agentで呼び出し可能な`mcp_*`ツールになる仕組み、および設定が無効・重複・無効化・認証ゲート付きの場合にオペレーターが想定すべき動作について説明します。\n\n## アーキテクチャの概要\n\n```text\nConfig sources (.xcsh/.claude/.cursor/.vscode/mcp.json, mcp.json, etc.)\n  -> discovery providers normalize to canonical MCPServer\n  -> capability loader dedupes by server name (higher provider priority wins)\n  -> loadAllMCPConfigs converts to MCPServerConfig + skips enabled:false\n  -> MCPManager connects/listTools (with auth/header/env resolution)\n  -> MCPTool/DeferredMCPTool bridge exposes tools as mcp_<server>_<tool>\n  -> AgentSession.refreshMCPTools replaces live MCP tools immediately\n```\n\n## 1) サーバー設定モデルとバリデーション\n\n`src/mcp/types.ts`は、MCP設定作成者とランタイムが使用するオーサリング形式を定義しています：\n\n- `stdio`（`type`が省略された場合のデフォルト）：`command`が必須、`args`、`env`、`cwd`はオプション\n- `http`：`url`が必須、`headers`はオプション\n- `sse`：`url`が必須、`headers`はオプション（互換性のために維持）\n- 共通フィールド：`enabled`、`timeout`、`auth`\n\n`validateServerConfig()`（`src/mcp/config.ts`）はトランスポートの基本を検証します：\n\n- `command`と`url`の両方が設定されている場合は拒否\n- stdioでは`command`が必須\n- http/sseでは`url`が必須\n- 不明な`type`は拒否\n\n`config-writer.ts`は追加/更新操作にこのバリデーションを適用し、サーバー名も検証します：\n\n- 空でないこと\n- 最大100文字\n- `[a-zA-Z0-9_.-]`のみ使用可能\n\n### トランスポートの注意点\n\n- `type`を省略するとstdioになります。HTTP/SSEを意図していたのに`type`を省略した場合、`command`が必須になります。\n- `sse`は引き続き受け入れられますが、内部的にはHTTPトランスポート（`createHttpTransport`）として扱われます。\n- バリデーションは構造的なものであり、到達可能性の検証ではありません：構文的に有効なURLでも接続時に失敗する可能性があります。\n\n## 2) ディスカバリ、正規化、優先順位\n\n### ケイパビリティベースのディスカバリ\n\n`loadAllMCPConfigs()`（`src/mcp/config.ts`）は、`loadCapability(mcpCapability.id)`を通じて正規の`MCPServer`アイテムをロードします。\n\nケイパビリティレイヤー（`src/capability/index.ts`）は以下を実行します：\n\n1. 優先順位に従ってプロバイダーをロード\n2. `server.name`で重複排除（最初の一致 = 最高優先順位が勝つ）\n3. 重複排除されたアイテムを検証\n\n結果：ソース間で重複するサーバー名はマージされません。1つの定義が勝ち、優先順位の低い重複はシャドウされます。\n\n### `.mcp.json`と関連ファイル\n\n`src/discovery/mcp-json.ts`の専用フォールバックプロバイダーは、プロジェクトルートの`mcp.json`および`.mcp.json`（低優先順位）を読み取ります。\n\n実際には、MCPサーバーはより高い優先順位のプロバイダー（例：ネイティブの`.xcsh/...`やツール固有の設定ディレクトリ）からも取得されます。オーサリングのガイダンス：\n\n- 明示的な制御には`.xcsh/mcp.json`（プロジェクト）または`~/.xcsh/mcp.json`（ユーザー）を推奨します。\n- フォールバック互換性が必要な場合はルートの`mcp.json` / `.mcp.json`を使用します。\n- 複数のソースで同じサーバー名を再利用すると、マージではなく優先順位によるシャドウが発生します。\n\n### 正規化の動作\n\n`convertToLegacyConfig()`（`src/mcp/config.ts`）は正規の`MCPServer`をランタイムの`MCPServerConfig`にマッピングします。\n\n主な動作：\n\n- トランスポートは`server.transport ?? (command ? \"stdio\" : url ? \"http\" : \"stdio\")`として推論される\n- 無効化されたサーバー（`enabled === false`）は接続前に除外される\n- オプションフィールドは存在する場合に保持される\n\n### ディスカバリ中の環境変数展開\n\n`mcp-json.ts`は`expandEnvVarsDeep()`を使用して文字列フィールド内の環境変数プレースホルダーを展開します：\n\n- `${VAR}`と`${VAR:-default}`をサポート\n- 未解決の値はリテラルの`${VAR}`文字列のまま残る\n\n`mcp-json.ts`はユーザーJSONに対してランタイム型チェックも実行し、無効な`enabled`/`timeout`値に対してはファイル全体をハードフェイルさせるのではなく警告をログ出力します。\n\n## 3) 認証とランタイム値の解決\n\n`MCPManager.prepareConfig()`/`#resolveAuthConfig()`（`src/mcp/manager.ts`）は、接続前の最終パスです。\n\n### OAuth資格情報の注入\n\n設定に以下がある場合：\n\n```ts\nauth: { type: \"oauth\", credentialId: \"...\" }\n```\n\nかつ認証ストレージに資格情報が存在する場合：\n\n- `http`/`sse`：`Authorization: Bearer <access_token>`ヘッダーを注入\n- `stdio`：`OAUTH_ACCESS_TOKEN`環境変数を注入\n\n資格情報のルックアップが失敗した場合、マネージャーは警告をログ出力し、未解決の認証のまま続行します。\n\n### ヘッダー/環境変数値の解決\n\n接続前に、マネージャーは`resolveConfigValue()`（`src/config/resolve-config-value.ts`）を通じて各ヘッダー/環境変数値を解決します：\n\n- `!`で始まる値 => シェルコマンドを実行し、トリムされたstdoutを使用（キャッシュあり）\n- それ以外の場合、まず環境変数名として扱い（`process.env[name]`）、フォールバックとしてリテラル値を使用\n- 未解決のコマンド/環境変数値は最終的なヘッダー/環境変数マップから除外される\n\n運用上の注意：これは、シークレットのコマンド/環境変数キーを誤入力すると、そのヘッダー/環境変数エントリがサイレントに削除され、ダウンストリームで401/403やサーバー起動失敗が発生する可能性があることを意味します。\n\n## 4) ツールブリッジ：MCP -> エージェントから呼び出し可能なツール\n\n`src/mcp/tool-bridge.ts`はMCPツール定義を`CustomTool`に変換します。\n\n### 命名と衝突のドメイン\n\nツール名は以下の形式で生成されます：\n\n```text\nmcp_<sanitized_server_name>_<sanitized_tool_name>\n```\n\nルール：\n\n- 小文字化\n- `[a-z_]`以外の文字は`_`に変換\n- 連続するアンダースコアは圧縮\n- ツール名における冗長な`<server>_`プレフィックスは1回だけ除去\n\nこれにより多くの衝突は回避されますが、すべてではありません。異なる生の名前が同じ識別子にサニタイズされる可能性があり（例：`my-server`と`my.server`は同様にサニタイズされる）、レジストリへの挿入は最後の書き込みが優先されます。\n\n### スキーママッピング\n\n`convertSchema()`はMCP JSON Schemaをほぼそのまま保持しますが、プロバイダー互換性のために`properties`が欠落しているオブジェクトスキーマには`{}`をパッチします。\n\n### 実行マッピング\n\n`MCPTool.execute()` / `DeferredMCPTool.execute()`：\n\n- MCP `tools/call`を呼び出す\n- MCPコンテンツを表示可能なテキストにフラット化する\n- 構造化された詳細（`serverName`、`mcpToolName`、プロバイダーメタデータ）を返す\n- サーバーが報告する`isError`を`Error: ...`テキスト結果にマッピングする\n- スローされたトランスポート/ランタイム障害を`MCP error: ...`にマッピングする\n- AbortErrorを`ToolAbortError`に変換してアボートセマンティクスを保持する\n\n## 5) オペレーターのライフサイクル：追加/編集/削除とライブ更新\n\nインタラクティブモードでは、`src/modes/controllers/mcp-command-controller.ts`で`/mcp`が公開されます。\n\nサポートされる操作：\n\n- `add`（ウィザードまたはクイック追加）\n- `remove` / `rm`\n- `enable` / `disable`\n- `test`\n- `reauth` / `unauth`\n- `reload`\n\n設定の書き込みはアトミックです（`writeMCPConfigFile`：一時ファイル + リネーム）。\n\n変更後、コントローラーは`#reloadMCP()`を呼び出します：\n\n1. `mcpManager.disconnectAll()`\n2. `mcpManager.discoverAndConnect()`\n3. `session.refreshMCPTools(mcpManager.getTools())`\n\n`refreshMCPTools()`はすべての`mcp_`レジストリエントリを置換し、最新のMCPツールセットを直ちに再アクティブ化するため、セッションを再起動することなく変更が反映されます。\n\n### モードの違い\n\n- **インタラクティブ/TUIモード**：`/mcp`がアプリ内UXを提供（ウィザード、OAuthフロー、接続ステータステキスト、即時ランタイムリバインディング）。\n- **SDK/ヘッドレス統合**：`discoverAndLoadMCPTools()`（`src/mcp/loader.ts`）がロードされたツール + サーバーごとのエラーを返す。`/mcp`コマンドUXなし。\n\n## 6) ユーザーに表示されるエラーサーフェス\n\nユーザー/オペレーターに表示される一般的なエラー文字列：\n\n- 追加/更新のバリデーション失敗：\n  - `Invalid server config: ...`\n  - `Server \"<name>\" already exists in <path>`\n- クイック追加の引数の問題：\n  - `Use either --url or -- <command...>, not both.`\n  - `--token requires --url (HTTP/SSE transport).`\n- 接続/テストの失敗：\n  - `Failed to connect to \"<name>\": <message>`\n  - タイムアウトのヘルプテキストでタイムアウト値の増加を提案\n  - `401/403`に対する認証ヘルプテキスト\n- 認証/OAuthフロー：\n  - `Authentication required ... OAuth endpoints could not be discovered`\n  - `OAuth flow timed out. Please try again.`\n  - `OAuth authentication failed: ...`\n- 無効化されたサーバーの使用：\n  - `Server \"<name>\" is disabled. Run /mcp enable <name> first.`\n\nディスカバリにおける不正なソースJSONは通常、警告/ログとして処理されます。config-writerのパスは明示的なエラーをスローします。\n\n## 7) 実践的なオーサリングガイダンス\n\nこのコードベースで堅牢なMCPオーサリングを行うために：\n\n1. すべてのMCP対応設定ソース間でサーバー名をグローバルに一意に保つ。\n2. 生成される`mcp_*`ツール名でのサニタイズ名の衝突を避けるため、英数字/アンダースコアの名前を推奨。\n3. 意図しないstdioデフォルトを避けるため、明示的な`type`を使用する。\n4. `enabled: false`はハードオフとして扱う：サーバーはランタイム接続セットから除外される。\n5. OAuth設定では、有効な`credentialId`を保存する。そうしないと認証注入がスキップされる。\n6. コマンドベースのシークレット解決（`!cmd`）を使用する場合、コマンド出力が安定しており空でないことを確認する。\n\n## 実装ファイル\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts)\n- [`src/mcp/config-writer.ts`](../../packages/coding-agent/src/mcp/config-writer.ts)\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts)\n- [`src/discovery/mcp-json.ts`](../../packages/coding-agent/src/discovery/mcp-json.ts)\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/config/resolve-config-value.ts`](../../packages/coding-agent/src/config/resolve-config-value.ts)\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts)\n",
	"ja/natives/natives-addon-loader-runtime.md": "---\ntitle: ネイティブ アドオンローダー ランタイム\ndescription: プラットフォーム検出、フォールバック戦略、モジュール解決を備えた N-API アドオンローダーランタイム。\nsidebar:\n  order: 3\n  label: アドオンローダー\ni18n:\n  sourceHash: 743ea3e32c7c\n  translator: machine\n---\n\n# ネイティブ アドオンローダー ランタイム\n\n本ドキュメントでは、`@f5-sales-demo/pi-natives` におけるアドオンのロード・検証レイヤーを詳しく解説します。具体的には、`native.ts` がどの `.node` ファイルをロードするかを決定する方法、埋め込みペイロードの展開が実行されるタイミング、および起動時の障害がどのように報告されるかを説明します。\n\n## 実装ファイル\n\n- `packages/natives/src/native.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/package.json`\n\n## スコープと責任範囲\n\nローダー/ランタイムの責任範囲は意図的に限定されています：\n\n- プラットフォーム/CPU を考慮したアドオンファイル名およびディレクトリの候補リストを構築する。\n- オプションとして、埋め込みアドオンをバージョン管理されたユーザーごとのキャッシュディレクトリに展開する。\n- 候補を決定論的な順序で試行する。\n- バインディングを公開する前に `validateNative` を介して古いまたは互換性のないアドオンを拒否する。\n\nモジュール固有の grep/テキスト/ハイライト動作については本ドキュメントの対象外です。\n\n## ランタイム入力と導出状態\n\nモジュール初期化時（`export const native = loadNative();`）に、`native.ts` は静的コンテキストを計算します：\n\n- **プラットフォームタグ**: ``${process.platform}-${process.arch}``（例：`darwin-arm64`）。\n- **パッケージバージョン**: `packages/natives/package.json`（`version` フィールド）から取得。\n- **コアディレクトリ**:\n  - `nativeDir`: パッケージローカルの `packages/natives/native`。\n  - `execDir`: `process.execPath` を含むディレクトリ。\n  - `versionedDir`: `<getNativesDir()>/<packageVersion>`。\n  - `userDataDir` フォールバック:\n    - Windows: `%LOCALAPPDATA%/xcsh`（または `%USERPROFILE%/AppData/Local/xcsh`）。\n    - 非 Windows: `~/.local/bin`。\n- **コンパイル済みバイナリモード**（`isCompiledBinary`）: 以下のいずれかが true の場合に true:\n  - `PI_COMPILED` 環境変数が設定されている、または\n  - `import.meta.url` に Bun 埋め込みマーカー（`$bunfs`、`~BUN`、`%7EBUN`）が含まれる。\n- **バリアントオーバーライド**: `PI_NATIVE_VARIANT`（`modern`/`baseline` のみ有効；無効な値は無視される）。\n- **選択されたバリアント**: 明示的なオーバーライドがある場合はそれを使用、それ以外は x64 での実行時 AVX2 検出（AVX2 の場合は `modern`、そうでない場合は `baseline`）。\n\n## プラットフォームサポートとタグ解決\n\n`SUPPORTED_PLATFORMS` は以下に固定されています：\n\n- `linux-x64`\n- `linux-arm64`\n- `darwin-x64`\n- `darwin-arm64`\n- `win32-x64`\n\n動作の詳細：\n\n- サポートされていないプラットフォームは最初の段階で拒否されません。\n- ローダーは計算されたすべての候補を最初に試行します。\n- 何もロードできない場合、サポートされているタグの一覧を含む明示的なサポート外プラットフォームエラーをスローします。\n\nこれにより、真にサポートされていないターゲットではハード失敗しながら、ニアミスのケースに対して有用な診断情報を提供します。\n\n## バリアント選択（`modern` / `baseline` / デフォルト）\n\n### x64 の動作\n\n1. `PI_NATIVE_VARIANT` が `modern` または `baseline` の場合、その値が優先されます。\n2. それ以外は AVX2 サポートを検出します：\n   - Linux: `/proc/cpuinfo` で `avx2` をスキャン。\n   - macOS: `sysctl` を照会（`machdep.cpu.leaf7_features`、フォールバックとして `machdep.cpu.features`）。\n   - Windows: PowerShell で `[System.Runtime.Intrinsics.X86.Avx2]::IsSupported` を実行。\n3. 結果:\n   - AVX2 利用可能 -> `modern`\n   - AVX2 利用不可/検出不可 -> `baseline`\n\n### 非 x64 の動作\n\n- バリアントは使用されません。ローダーはデフォルトのファイル名（`pi_natives.<platform>-<arch>.node`）を使用します。\n\n### ファイル名の構築\n\n`tag = <platform>-<arch>` とした場合：\n\n- 非 x64 またはバリアントなし: `pi_natives.<tag>.node`\n- x64 + `modern`: 以下の順で試行\n  1. `pi_natives.<tag>-modern.node`\n  2. `pi_natives.<tag>-baseline.node`（意図的なフォールバック）\n- x64 + `baseline`: `pi_natives.<tag>-baseline.node` のみ\n\n最終エラーメッセージで使用される `addonLabel` は `<tag>` または `<tag> (<variant>)` のいずれかです。\n\n## 候補パスの構築とフォールバック順序\n\n`native.ts` は `require(...)` 呼び出しの前に候補プールを構築します。\n\n### リリース候補\n\nバリアント解決済みのファイル名リストから構築され、以下の順で検索されます：\n\n- **非コンパイルランタイム**:\n  1. `<nativeDir>/<filename>`\n  2. `<execDir>/<filename>`\n\n- **コンパイル済みランタイム**（`PI_COMPILED` または Bun 埋め込みマーカー）:\n  1. `<versionedDir>/<filename>`\n  2. `<userDataDir>/<filename>`\n  3. `<nativeDir>/<filename>`\n  4. `<execDir>/<filename>`\n\n`dedupedCandidates` は最初の出現順を保持しながら重複を除去します。\n\n### 最終ランタイムシーケンス\n\nロード時：\n\n1. オプションの埋め込み展開候補（生成された場合）が先頭に挿入されます。\n2. 残りの重複除去済み候補が順番に試行されます。\n3. `require(...)` に成功し、かつ `validateNative(...)` を通過した最初の候補が採用されます。\n\n## 埋め込みアドオン展開のライフサイクル\n\n`embedded-addon.ts` は以下の生成済みマニフェスト形状を定義します：\n\n- `platformTag`\n- `version`\n- `files[]`（各エントリには `variant`、`filename`、`filePath` がある）\n\nチェックイン済みのデフォルトは `embeddedAddon: null` です。コンパイル済みアーティファクトはこれを実際のメタデータに置き換える場合があります。\n\n### 展開ステートマシン\n\n展開（`maybeExtractEmbeddedAddon`）はすべてのゲートが通過した場合にのみ実行されます：\n\n1. `isCompiledBinary === true`\n2. `embeddedAddon !== null`\n3. `embeddedAddon.platformTag === platformTag`\n4. `embeddedAddon.version === packageVersion`\n5. バリアントに適した埋め込みファイルが見つかる\n\nバリアントファイルの選択はランタイムのバリアント意図を反映します：\n\n- 非 x64: `default` を優先し、次に最初に利用可能なファイル。\n- x64 + `modern`: `modern` を優先し、`baseline` にフォールバック。\n- x64 + `baseline`: `baseline` を必須とする。\n\n展開動作：\n\n1. `<versionedDir>` が存在することを確認（`mkdirSync(..., { recursive: true })`）。\n2. `<versionedDir>/<selected filename>` が既に存在する場合は再利用（再書き込みなし）。\n3. 存在しない場合は埋め込みソース `filePath` を読み込み、ターゲットファイルに書き込む。\n4. 最優先ロード試行のためにターゲットパスを返す。\n\n失敗時、展開は即座にクラッシュしません。ディレクトリ作成または書き込みの失敗をエラーエントリとして追加し、ローダーは通常の候補探索を続行します。\n\n## ライフサイクルと状態遷移\n\n```text\nInit\n  -> プラットフォーム/バージョン/バリアント/候補リストを計算\n  -> (コンパイル済み + 埋め込みマニフェストが一致するか？)\n       yes -> 埋め込みを versionedDir に展開を試みる（エラーを記録し続行）\n       no  -> 展開をスキップ\n  -> 順番に各ランタイム候補について:\n       require(candidate)\n       -> 成功: validateNative\n            -> 通過: バインディングを返す（READY）\n            -> 失敗: エラーを記録し続行\n       -> 失敗: エラーを記録し続行\n  -> 何もロードされなかった場合:\n       プラットフォームタグが未サポートの場合 -> Unsupported platform をスロー\n       それ以外 -> Failed to load をスロー（全試行パスの診断情報 + ヒント付き）\n```\n\n## `validateNative` コントラクトチェック\n\n`validateNative(bindings, source)` は起動時に `NativeBindings` に対して関数のみのコントラクトを強制します。\n\n仕組み：\n\n- 必須エクスポート名それぞれについて、`typeof bindings[name] === \"function\"` をチェックします。\n- 欠落している名前は集約されます。\n- 欠落がある場合、ローダーは以下をスローします：\n  - ソースアドオンパス、\n  - 欠落エクスポートの一覧、\n  - 再ビルドコマンドのヒント。\n\nこれは古いバイナリ、不完全なビルド、シンボル/名前のドリフトに対するハード互換性ゲートです。\n\n### JS API ↔ ネイティブエクスポートマッピング（検証ゲート）\n\n| `validateNative` でチェックされる JS バインディング名 | 期待されるネイティブエクスポート名 |\n| --- | --- |\n| `grep` | `grep` |\n| `glob` | `glob` |\n| `highlightCode` | `highlightCode` |\n| `executeShell` | `executeShell` |\n| `PtySession` | `PtySession` |\n| `Shell` | `Shell` |\n| `visibleWidth` | `visibleWidth` |\n| `getSystemInfo` | `getSystemInfo` |\n| `getWorkProfile` | `getWorkProfile` |\n| `invalidateFsScanCache` | `invalidateFsScanCache` |\n\n注意: `bindings.ts` はベースの `cancelWork(id)` メンバーのみを宣言しています。モジュールの `types.ts` ファイルは `validateNative` が強制する追加シンボルを宣言マージします。\n\n## 障害動作と診断\n\n## サポートされていないプラットフォーム\n\nすべての候補が失敗し、`platformTag` が `SUPPORTED_PLATFORMS` に含まれていない場合、ローダーは以下をスローします：\n\n- `Unsupported platform: <tag>`\n- サポートされているプラットフォームの完全な一覧\n- 明示的な問題報告ガイダンス\n\n## 古いバイナリ / 不一致の症状\n\n典型的な古いバイナリの不一致シグナル：\n\n- `Native addon missing exports (<candidate>). Missing: ...`\n\nよくある原因：\n\n- 以前のパッケージバージョン/API 形状の古い `.node` バイナリ。\n- 誤ったバリアントアーティファクトが選択された（x64 の場合）。\n- ロードされたアーティファクトに新しい Rust エクスポートが存在しない。\n\nローダーの動作：\n\n- 候補ごとの欠落エクスポート失敗を記録します。\n- 残りの候補の探索を続行します。\n- 候補が検証を通過しない場合、最終エラーには試行されたすべてのパスと各失敗メッセージが含まれます。\n\n## コンパイル済みバイナリの起動失敗\n\nコンパイル済みモードの最終診断には以下が含まれます：\n\n- 期待されるバージョン管理キャッシュのターゲットパス（`<versionedDir>/<filename>`）、\n- 古い `<versionedDir>` を削除して再実行するための修復手順、\n- 期待される各ファイル名の直接リリースダウンロード `curl` コマンド。\n\n## 非コンパイルの起動失敗\n\n通常のパッケージ/ランタイムモードの最終診断には以下が含まれます：\n\n- 再インストールのヒント（`bun install @f5-sales-demo/pi-natives`）、\n- ローカル再ビルドコマンド（`bun --cwd=packages/natives run build`）、\n- オプションの x64 バリアントビルドヒント（`TARGET_VARIANT=baseline|modern ...`）。\n\n## ランタイム動作\n\n- ローダーは常にリリース候補チェーンを使用します。\n- `PI_DEV` を設定すると、候補ごとのコンソール診断（`Loaded native addon...` およびロードエラー）のみが有効になります。\n",
	"ja/natives/natives-architecture.md": "---\ntitle: Natives アーキテクチャ\ndescription: TypeScript とプラットフォーム固有の操作を橋渡しする Rust N-API ネイティブアドオンアーキテクチャ。\nsidebar:\n  order: 1\n  label: アーキテクチャ\ni18n:\n  sourceHash: d38ed2437bb7\n  translator: machine\n---\n\n# Natives アーキテクチャ\n\n`@f5-sales-demo/pi-natives` は3層のスタックで構成されています：\n\n1. **TypeScript ラッパー/API レイヤー** は安定した JS/TS エントリポイントを公開します。\n2. **アドオン読み込み/検証レイヤー** は現在のランタイムに対応する `.node` バイナリを解決・検証します。\n3. **Rust N-API モジュールレイヤー** はパフォーマンスが重要なプリミティブを実装し、JS にエクスポートします。\n\nこのドキュメントは、より詳細なモジュールレベルのドキュメントの基盤となります。\n\n## 実装ファイル\n\n- `packages/natives/src/index.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `crates/pi-natives/src/lib.rs`\n\n## レイヤー 1: TypeScript ラッパー/API レイヤー\n\n`packages/natives/src/index.ts` はパブリックバレルファイルです。機能ドメインごとにエクスポートをグループ化し、生の N-API バインディングを直接公開するのではなく、型付きラッパーを再エクスポートします。\n\n現在のトップレベルグループ：\n\n- **検索/テキストプリミティブ**: `grep`, `glob`, `text`, `highlight`\n- **実行/プロセス/ターミナルプリミティブ**: `shell`, `pty`, `ps`, `keys`\n- **システム/メディア/変換プリミティブ**: `image`, `html`, `clipboard`, `system-info`, `work`\n\n`packages/natives/src/bindings.ts` は基本的なインターフェースコントラクトを定義します：\n\n- `NativeBindings` は共有メンバー（`cancelWork(id: number)`）から始まります\n- モジュール固有のバインディングは各モジュールの `types.ts` からの宣言マージによって追加されます\n- `Cancellable` はキャンセル機能を公開するラッパーのタイムアウトおよびアボートシグナルオプションを標準化します\n\n**保証されたコントラクト（API 向け）:** コンシューマーは `@f5-sales-demo/pi-natives` からインポートし、型付きラッパーを使用します。\n\n**実装の詳細（変更の可能性あり）:** 宣言マージおよび内部ラッパーレイアウト（`src/<module>/index.ts`、`src/<module>/types.ts`）。\n\n## レイヤー 2: アドオンの読み込みと検証\n\n`packages/natives/src/native.ts` はランタイムでのアドオン選択、オプションの抽出、およびエクスポートの検証を担当します。\n\n### 候補解決モデル\n\n- プラットフォームタグは `\"${process.platform}-${process.arch}\"` です。\n- 現在サポートされているタグ：\n  - `linux-x64`\n  - `linux-arm64`\n  - `darwin-x64`\n  - `darwin-arm64`\n  - `win32-x64`\n- x64 は CPU バリアントを使用できます：\n  - `modern`（AVX2 対応）\n  - `baseline`（フォールバック）\n- x64 以外はデフォルトのファイル名を使用します（バリアントサフィックスなし）。\n\nファイル名戦略：\n\n- リリース: `pi_natives.<platform>-<arch>.node`\n- x64 バリアントリリース: `pi_natives.<platform>-<arch>-modern.node` および/または `...-baseline.node`\n- `PI_DEV` はローダー診断を有効にしますが、アドオンのファイル名は変更しません\n\n### プラットフォーム固有のバリアント検出\n\nx64 の場合、バリアント選択には以下を使用します：\n\n- **Linux**: `/proc/cpuinfo`\n- **macOS**: `sysctl machdep.cpu.leaf7_features` / `machdep.cpu.features`\n- **Windows**: PowerShell による `System.Runtime.Intrinsics.X86.Avx2` のチェック\n\n`PI_NATIVE_VARIANT` で `modern` または `baseline` を明示的に強制できます。\n\n### バイナリ配布と抽出モデル\n\n`packages/natives/package.json` は公開ファイルに `src` と `native` の両方を含みます。`native/` ディレクトリにはビルド済みのプラットフォームアーティファクトが格納されます。\n\nコンパイル済みバイナリ（`PI_COMPILED` または Bun 埋め込みランタイムマーカー）の場合、ローダーの動作は以下の通りです：\n\n1. バージョン付きユーザーキャッシュパスを確認: `<getNativesDir()>/<packageVersion>/...`\n2. レガシーのコンパイル済みバイナリの場所を確認：\n   - Windows: `%LOCALAPPDATA%/xcsh`（フォールバック `%USERPROFILE%/AppData/Local/xcsh`）\n   - Windows 以外: `~/.local/bin`\n3. パッケージ内の `native/` および実行ファイルディレクトリの候補にフォールバック\n\n埋め込みアドオンマニフェストが存在する場合（`scripts/embed-native.ts` によって生成された `embedded-addon.ts`）、`native.ts` は読み込み前に一致する埋め込みバイナリをバージョン付きキャッシュディレクトリに展開できます。\n\n### 検証と失敗モード\n\n`require(candidate)` の後、`validateNative(...)` は必要なエクスポート（例: `grep`、`glob`、`highlightCode`、`PtySession`、`Shell`、`getSystemInfo`、`getWorkProfile`、`invalidateFsScanCache`）を検証します。\n\n失敗パスは明示的です：\n\n- **サポートされていないプラットフォームタグ**: サポートされているプラットフォームのリストと共にスローします\n- **読み込み可能な候補なし**: 試行したすべてのパスと改善のヒントと共にスローします\n- **エクスポートの欠落**: 正確な欠落名とリビルドコマンドと共にスローします\n- **埋め込み抽出エラー**: ディレクトリ/書き込みの失敗を記録し、最終的な読み込み診断に含めます\n\n**保証されたコントラクト（API 向け）:** アドオンの読み込みは、検証済みのバインディングセットで成功するか、実行可能なエラーテキストと共に即座に失敗します。\n\n**実装の詳細（変更の可能性あり）:** 正確な候補検索順序およびコンパイル済みバイナリのフォールバックパスの順序。\n\n## レイヤー 3: Rust N-API モジュールレイヤー\n\n`crates/pi-natives/src/lib.rs` はエクスポートされたモジュールの所有権を宣言する Rust エントリモジュールです：\n\n- `clipboard`\n- `fd`\n- `fs_cache`\n- `glob`\n- `glob_util`\n- `grep`\n- `highlight`\n- `html`\n- `image`\n- `keys`\n- `prof`\n- `ps`\n- `pty`\n- `shell`\n- `system_info`\n- `task`\n- `text`\n\nこれらのモジュールは、`native.ts` によって消費・検証される N-API シンボルを実装します。JS レベルの名前は `packages/natives/src` 内の TS ラッパーを通じて公開されます。\n\n**保証されたコントラクト（API 向け）:** Rust モジュールのエクスポートは、`validateNative` およびラッパーモジュールが期待するバインディング名と一致する必要があります。\n\n**実装の詳細（変更の可能性あり）:** 内部の Rust モジュール分解およびヘルパーモジュールの境界（`glob_util`、`task` など）。\n\n## 所有権の境界\n\nアーキテクチャレベルでは、所有権は以下のように分割されています：\n\n- **TS ラッパー/API の所有権 (`packages/natives/src`)**\n  - パブリック API のグループ化、オプションの型付け、安定した JS エルゴノミクス\n  - 呼び出し元に公開されるキャンセルサーフェス（`timeoutMs`、`AbortSignal`）\n- **ローダーの所有権 (`packages/natives/src/native.ts`)**\n  - ランタイムバイナリの選択\n  - CPU バリアントの選択とオーバーライド処理\n  - コンパイル済みバイナリの抽出と候補の探索\n  - 必要なネイティブエクスポートの厳密な検証\n- **Rust の所有権 (`crates/pi-natives/src`)**\n  - アルゴリズムおよびシステムレベルの実装\n  - プラットフォームネイティブな動作とパフォーマンスが重要なロジック\n  - TS ラッパーが消費する N-API シンボルの実装\n\n## ランタイムフロー（概要）\n\n1. コンシューマーが `@f5-sales-demo/pi-natives` からインポートします。\n2. ラッパーモジュールがシングルトンの `native` バインディングを呼び出します。\n3. `native.ts` がプラットフォーム/アーキテクチャ/バリアントに対応する候補バイナリを選択します。\n4. コンパイル済みディストリビューションの場合、オプションの埋め込みバイナリ抽出が行われます。\n5. アドオンが読み込まれ、エクスポートセットが検証されます。\n6. ラッパーが型付きの結果を呼び出し元に返します。\n\n## 用語集\n\n- **ネイティブアドオン**: Node-API（N-API）を介して読み込まれる `.node` バイナリ。\n- **プラットフォームタグ**: ランタイムタプル `platform-arch`（例: `darwin-arm64`）。\n- **バリアント**: x64 CPU 固有のビルドフレーバー（`modern` AVX2、`baseline` フォールバック）。\n- **ラッパー**: 生のネイティブエクスポートに対して型付き API を提供する TS 関数/クラス。\n- **宣言マージ**: モジュールの `types.ts` ファイルが `NativeBindings` を拡張するために使用する TS テクニック。\n- **コンパイル済みバイナリモード**: CLI がバンドルされ、ネイティブアドオンがパッケージローカルパスのみではなく抽出/キャッシュパスから解決されるランタイムモード。\n- **埋め込みアドオン**: コンパイル済みバイナリが一致する `.node` ペイロードを抽出できるように `embedded-addon.ts` に生成されるビルドアーティファクトのメタデータおよびファイル参照。\n- **検証ゲート**: 必要なエクスポートが欠落している古い/不一致のバイナリを拒否する `validateNative(...)` チェック。\n",
	"ja/natives/natives-binding-contract.md": "---\ntitle: ネイティブバインディングコントラクト（TypeScript側）\ndescription: N-API経由でRustネイティブ関数を呼び出すためのTypeScript側バインディングコントラクト。\nsidebar:\n  order: 2\n  label: バインディングコントラクト\ni18n:\n  sourceHash: 36dc5fed1f0a\n  translator: machine\n---\n\n# ネイティブバインディングコントラクト（TypeScript側）\n\nこのドキュメントでは、`@f5-sales-demo/pi-natives` の呼び出し元とロードされたN-APIアドオンの間に位置するTypeScript側のコントラクトを定義します。\n\n以下の3つの要素に焦点を当てます：\n\n1. コントラクトの形状（`NativeBindings` + モジュール拡張）\n2. ラッパーの振る舞い（`src/<module>/index.ts`）\n3. パブリックエクスポートサーフェス（`src/index.ts`）\n\n## 実装ファイル\n\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/system-info/types.ts`\n- `packages/natives/src/system-info/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/work/types.ts`\n- `packages/natives/src/work/index.ts`\n\n## コントラクトモデル\n\n`packages/natives/src/bindings.ts` はベースコントラクトを定義します：\n\n- `NativeBindings`（ベースインターフェース、現在は `cancelWork(id: number): void` を含む）\n- `Cancellable`（`timeoutMs?: number`、`signal?: AbortSignal`）\n- `TsFunc<T>` N-APIスレッドセーフコールバックで使用されるコールバック形状\n\n各モジュールは宣言マージによって独自のフィールドを追加します：\n\n```ts\n// packages/natives/src/<module>/types.ts\ndeclare module \"../bindings\" {\n interface NativeBindings {\n  grep(options: GrepOptions, onMatch?: TsFunc<GrepMatch>): Promise<GrepResult>;\n }\n}\n```\n\nこれにより、モノリシックな中央型ファイルを必要とせず、1つの集約されたバインディングインターフェースを維持できます。\n\n## 宣言マージのライフサイクルと状態遷移\n\n### 1) コンパイル時の型アセンブリ\n\n- `bindings.ts` がベースとなる `NativeBindings` シンボルを提供します。\n- すべての `src/<module>/types.ts` が `NativeBindings` を拡張します。\n- `src/native.ts` がすべての `./<module>/types` ファイルを副作用のためにインポートし、`NativeBindings` が使用される場所でマージされたコントラクトがスコープ内に入るようにします。\n\n状態遷移: **ベースコントラクト** → **マージ済みコントラクト**\n\n### 2) ランタイムでのアドオンロードとバリデーションゲート\n\n- `src/native.ts` が候補の `.node` バイナリをロードします。\n- ロードされたオブジェクトは `NativeBindings` として扱われ、直ちに `validateNative(...)` に渡されます。\n- `validateNative` は `typeof bindings[name] === \"function\"` により必要なエクスポートキーを検証します。\n\n状態遷移: **信頼されていないアドオンオブジェクト** → **検証済みネイティブバインディングオブジェクト**（またはハードフェイラー）\n\n### 3) ラッパー呼び出し\n\n- `src/<module>/index.ts` のモジュールラッパーが `native.<export>` を呼び出します。\n- ラッパーはデフォルト値とコールバック形状を適応させます（`(err, value)` をJS APIでの値のみのコールバックパターンに変換）。\n- `src/index.ts` がモジュールラッパー/型をパブリックパッケージAPIとして再エクスポートします。\n\n状態遷移: **検証済みの生バインディング** → **人間工学的なパブリックAPI**\n\n## ラッパーの責務\n\nラッパーは意図的に薄く設計されており、ネイティブロジックを再実装しません。\n\n主な責務：\n\n- **引数の正規化/デフォルト設定**\n  - `glob()` は `options.path` を絶対パスに解決し、`hidden`、`gitignore`、`recursive` のデフォルト値を設定します。\n  - `hasMatch()` はネイティブ呼び出し前にデフォルトフラグ（`ignoreCase`、`multiline`）を設定します。\n- **コールバックの適応**\n  - `grep()`、`glob()`、`executeShell()` は `TsFunc<T>`（`error, value`）を成功した値のみを受け取るユーザーコールバックに変換します。\n- **ネイティブ呼び出しに関する環境またはポリシーの振る舞い**\n  - クリップボードラッパーはOSC52/Termux/ヘッドレス処理を追加し、コピーをベストエフォートとして扱います。\n- **パブリック命名と再エクスポートの管理**\n  - `searchContent()` はネイティブエクスポート `search` にマッピングされます。\n\n## パブリックエクスポートサーフェスの構成\n\n`packages/natives/src/index.ts` は正規のパブリックバレルファイルです。機能ドメインごとにエクスポートをグループ化しています：\n\n- 検索/テキスト: `grep`、`glob`、`text`、`highlight`\n- 実行/プロセス/ターミナル: `shell`、`pty`、`ps`、`keys`\n- システム/メディア/変換: `image`、`html`、`clipboard`、`system-info`、`work`\n\nメンテナールール: ラッパーが `src/index.ts` から再エクスポートされていない場合、それは意図されたパブリックパッケージサーフェスの一部ではありません。\n\n## JS API ↔ ネイティブエクスポートマッピング（代表例）\n\nRust側はN-APIエクスポート名（通常は `#[napi]` のsnake_case → camelCase変換から、時折明示的なエイリアスを使用）を使用し、これらのバインディングキーと一致する必要があります。\n\n| カテゴリ | パブリックJS API（ラッパー） | ネイティブバインディングキー | 戻り値の型 | 非同期？ |\n|---|---|---|---|---|\n| Grep | `grep(options, onMatch?)` | `grep` | `Promise<GrepResult>` | はい |\n| Grep | `searchContent(content, options)` | `search` | `SearchResult` | いいえ |\n| Grep | `hasMatch(content, pattern, opts?)` | `hasMatch` | `boolean` | いいえ |\n| Grep | `fuzzyFind(options)` | `fuzzyFind` | `Promise<FuzzyFindResult>` | はい |\n| Glob | `glob(options, onMatch?)` | `glob` | `Promise<GlobResult>` | はい |\n| Glob | `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `void` | いいえ |\n| Shell | `executeShell(options, onChunk?)` | `executeShell` | `Promise<ShellExecuteResult>` | はい |\n| Shell | `Shell` | `Shell` | クラスコンストラクタ | N/A |\n| PTY | `PtySession` | `PtySession` | クラスコンストラクタ | N/A |\n| Text | `truncateToWidth(...)` | `truncateToWidth` | `string` | いいえ |\n| Text | `sliceWithWidth(...)` | `sliceWithWidth` | `SliceWithWidthResult` | いいえ |\n| Text | `visibleWidth(text)` | `visibleWidth` | `number` | いいえ |\n| Highlight | `highlightCode(code, lang, colors)` | `highlightCode` | `string` | いいえ |\n| HTML | `htmlToMarkdown(html, options?)` | `htmlToMarkdown` | `Promise<string>` | はい |\n| System | `getSystemInfo()` | `getSystemInfo` | `SystemInfo` | いいえ |\n| Work | `getWorkProfile(lastSeconds)` | `getWorkProfile` | `WorkProfile` | いいえ |\n| Process | `killTree(pid, signal)` | `killTree` | `number` | いいえ |\n| Process | `listDescendants(pid)` | `listDescendants` | `number[]` | いいえ |\n| Clipboard | `copyToClipboard(text)` | `copyToClipboard` | `Promise<void>`（ベストエフォートラッパー動作） | はい |\n| Clipboard | `readImageFromClipboard()` | `readImageFromClipboard` | `Promise<ClipboardImage \\| null>` | はい |\n| Keys | `parseKey(data, kittyProtocolActive)` | `parseKey` | `string \\| null` | いいえ |\n\n## 同期 vs 非同期のコントラクトの違い\n\nコントラクトは同期と非同期のAPIを混在させています。ラッパーは1つのモデルを強制するのではなく、ネイティブの呼び出しスタイルを維持します：\n\n- **Promiseベースの非同期エクスポート** — I/Oまたは長時間実行の処理向け（`grep`、`glob`、`htmlToMarkdown`、`executeShell`、クリップボード、画像操作）。\n- **同期エクスポート** — 決定論的なインメモリ変換/パーサー向け（`search`、`hasMatch`、ハイライト、テキスト幅/スライス、キーパース、プロセスクエリ）。\n- **コンストラクタエクスポート** — ステートフルなランタイムオブジェクト向け（`Shell`、`PtySession`、`PhotonImage`）。\n\nメンテナーへの注意: 既存のエクスポートの同期↔非同期を変更することは、ラッパーと呼び出し元全体にわたる破壊的なAPIおよびコントラクトの変更となります。\n\n## オブジェクトと列挙型の型付けパターン\n\n### オブジェクトパターン（`#[napi(object)]` スタイルのJSオブジェクト）\n\nTSはオブジェクト形状のネイティブ値をインターフェースとしてモデル化します。例：\n\n- `GrepResult`、`SearchResult`、`GlobResult`\n- `SystemInfo`、`WorkProfile`\n- `ClipboardImage`、`ParsedKittyResult`\n\nこれらはコンパイル時の構造的コントラクトであり、ランタイムの形状の正しさはネイティブ実装が担保します。\n\n### 列挙型パターン\n\n数値のネイティブ列挙型はTSでは `const enum` 値として表現されます：\n\n- `FileType`（`1=file`、`2=dir`、`3=symlink`）\n- `ImageFormat`（`0=PNG`、`1=JPEG`、`2=WEBP`、`3=GIF`）\n- `SamplingFilter`、`Ellipsis`、`KeyEventType`\n\n呼び出し元は名前付きの列挙型メンバーを参照しますが、バインディング境界では数値が渡されます。\n\n## 不一致の検出方法\n\n不一致の検出は2つのレイヤーで行われます：\n\n1. **コンパイル時のTypeScriptコントラクトチェック**\n   - ラッパーはマージ済みの `NativeBindings` に対して `native.<name>` を呼び出します。\n   - バインディングキーが欠落/名前変更されるとラッパーのTS型チェックが失敗します。\n\n2. **`validateNative` によるランタイムバリデーション**\n   - ロード後、`native.ts` が必要なエクスポートをチェックし、欠落があればスローします。\n   - エラーメッセージには欠落しているキーとリビルド手順が含まれます。\n\nこれにより、ラッパー/型は存在するがロードされた `.node` にエクスポートがないという、よくある古いバイナリのドリフトを検出できます。\n\n## 失敗時の振る舞いと注意事項\n\n### ロード/バリデーション失敗（ハードフェイラー）\n\n- アドオンのロード失敗またはサポートされていないプラットフォームの場合、`native.ts` のモジュール初期化時にスローされます。\n- 必要なエクスポートの欠落は、ラッパーが使用可能になる前にスローされます。\n\n効果: パッケージは最初の呼び出しまで失敗を遅延させるのではなく、即座に失敗します。\n\n### ラッパーレベルの動作の違い\n\n- 一部のラッパーは意図的に失敗を軽減します（`copyToClipboard` はベストエフォートであり、ネイティブの失敗を握りつぶします）。\n- ストリーミングコールバックはコールバックのエラーペイロードを無視し、成功した値イベントのみを転送します。\n\n### 型レベルの注意事項（ランタイムはTSより厳格）\n\n- TSのオプショナルフィールドはセマンティックな妥当性を保証しません。ネイティブレイヤーは不正な値を拒否する可能性があります。\n- `const enum` の型付けは、ランタイムで型付けされていない呼び出し元からの範囲外の数値を防止しません。\n- `validateNative` は必要なエクスポートの存在/関数であることのみをチェックし、引数/戻り値の形状の深い互換性はチェックしません。\n- `bindings.ts` はベースインターフェースに `cancelWork(id)` を含んでいますが、現在のランタイムバリデーションリストはそのキーを強制していません。\n\n## バインディング変更時のメンテナーチェックリスト\n\nエクスポートを追加/変更する際は、以下のすべてを更新してください：\n\n1. `src/<module>/types.ts`（拡張 + コントラクト型）\n2. `src/<module>/index.ts`（ラッパーの振る舞い）\n3. `src/native.ts` のモジュール型のインポート（新規モジュールの場合）\n4. `validateNative` の必要なエクスポートチェック\n5. `src/index.ts` のパブリック再エクスポート\n\nいずれかのステップをスキップすると、コンパイル時のドリフトまたはランタイムのロード時失敗が発生します。\n",
	"ja/natives/natives-build-release-debugging.md": "---\ntitle: ネイティブアドオンのビルド、リリース、およびデバッグ ランブック\ndescription: Rustネイティブアドオンのクロスプラットフォーム対応のビルド、リリース、およびデバッグに関するランブック。\nsidebar:\n  order: 8\n  label: ビルド、リリース、デバッグ\ni18n:\n  sourceHash: efe47aa5b466\n  translator: machine\n---\n\n# ネイティブアドオンのビルド、リリース、およびデバッグ ランブック\n\nこのランブックでは、`@f5-sales-demo/pi-natives` ビルドパイプラインが `.node` アドオンを生成する方法、コンパイル済みディストリビューションがそれらを読み込む方法、およびローダー/ビルドの障害をデバッグする方法について説明します。\n\n`docs/natives-architecture.md` のアーキテクチャ用語に従います：\n\n- **ビルド時のアーティファクト生成** (`scripts/build-native.ts`)\n- **組み込みアドオンマニフェスト生成** (`scripts/embed-native.ts`)\n- **ランタイムのアドオン読み込み + 検証ゲート** (`src/native.ts`)\n\n## 実装ファイル\n\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `packages/natives/src/native.ts`\n- `crates/pi-natives/Cargo.toml`\n\n## ビルドパイプラインの概要\n\n### 1) ビルドエントリポイント\n\n`packages/natives/package.json` のスクリプト：\n\n- `bun scripts/build-native.ts` (`build`) → リリースビルド\n- `bun scripts/build-native.ts --dev` (`dev:native`) → デバッグ/開発プロファイルビルド（同じ出力ファイル名）\n- `bun scripts/embed-native.ts` (`embed:native`) → ビルドされたファイルから `src/embedded-addon.ts` を生成\n\n### 2) Rust アーティファクトのビルド\n\n`build-native.ts` は `crates/pi-natives` で Cargo を実行します：\n\n- 基本コマンド: `cargo build`\n- `--dev` が渡されない限り、リリースモードでは `--release` を追加\n- クロスターゲットの場合は `--target <CROSS_TARGET>` を追加\n\n`crates/pi-natives/Cargo.toml` は `crate-type = [\"cdylib\"]` を宣言しているため、Cargo は共有ライブラリ (`.so`/`.dylib`/`.dll`) を出力し、その後 `.node` アドオンファイル名にコピー/リネームされます。\n\n### 3) アーティファクトの検出とインストール\n\nCargo が完了した後、`build-native.ts` は以下の順序で候補出力ディレクトリをスキャンします：\n\n1. `${CARGO_TARGET_DIR}` （設定されている場合）\n2. `<repo>/target`\n3. `crates/pi-natives/target`\n\n各ルートに対してプロファイルディレクトリを確認します：\n\n- クロスビルド: `<root>/<crossTarget>/<profile>` → `<root>/<profile>`\n- ネイティブビルド: `<root>/<profile>`\n\nその後、以下のいずれかを探します：\n\n- `libpi_natives.so`\n- `libpi_natives.dylib`\n- `pi_natives.dll`\n- `libpi_natives.dll`\n\n見つかった場合、一時ファイル + リネームのセマンティクスで `packages/natives/native/` にアトミックにインストールします（Windows のフォールバックは、ロックされた DLL の置き換え失敗を明示的に処理します）。\n\n## ターゲット/バリアントモデルと命名規則\n\n## プラットフォームタグ\n\nビルドとランタイムの両方でプラットフォームタグを使用します：\n\n`<platform>-<arch>` （例: `darwin-arm64`、`linux-x64`）\n\n## バリアントモデル（x64 のみ）\n\nx64 は CPU バリアントをサポートします：\n\n- `modern` （AVX2 対応パス）\n- `baseline` （フォールバック）\n\n非 x64 は単一のデフォルトアーティファクトを使用します（バリアントサフィックスなし）。\n\n### 出力ファイル名\n\nリリースビルド：\n\n- x64: `pi_natives.<platform>-<arch>-modern.node` または `...-baseline.node`\n- 非 x64: `pi_natives.<platform>-<arch>.node`\n\n開発ビルド (`--dev`)：\n\n- デバッグプロファイルフラグを使用しますが、標準のプラットフォームタグ付き出力命名を維持します\n\n`native.ts` のランタイムローダー候補順序：\n\n- リリース候補\n- コンパイルモードでは、パッケージローカルファイルの前に抽出/キャッシュ候補を追加します\n\n## 環境フラグとビルドオプション\n\n## ランタイムフラグ\n\n- `PI_DEV` （ローダー動作）: ローダー診断を有効化\n- `PI_NATIVE_VARIANT` （ローダー動作、x64 のみ）: ランタイム時に `modern` または `baseline` の選択を強制\n- `PI_COMPILED` （ローダー動作）: コンパイル済みバイナリの候補/抽出動作を有効化\n\n## ビルド時フラグ/オプション\n\n- `--dev` （スクリプト引数）: デバッグプロファイルでビルド\n- `CROSS_TARGET`: Cargo の `--target` に渡される\n- `TARGET_PLATFORM`: 出力プラットフォームタグの命名をオーバーライド\n- `TARGET_ARCH`: 出力アーキテクチャの命名をオーバーライド\n- `TARGET_VARIANT` （x64 のみ）: 出力ファイル名と RUSTFLAGS ポリシーに対して `modern` または `baseline` を強制\n- `CARGO_TARGET_DIR`: Cargo 出力を検索する際の追加ルート\n- `RUSTFLAGS`:\n  - 未設定かつクロスコンパイルでない場合、スクリプトは以下を設定:\n    - modern: `-C target-cpu=x86-64-v3`\n    - baseline: `-C target-cpu=x86-64-v2`\n    - 非 x64 / バリアントなし: `-C target-cpu=native`\n  - すでに設定されている場合、スクリプトはオーバーライドしません\n\n## ビルドの状態/ライフサイクル遷移\n\n### ビルドライフサイクル (`build-native.ts`)\n\n1. **初期化**: 引数/環境変数の解析（`--dev`、ターゲットオーバーライド、クロスフラグ）\n2. **バリアント解決**:\n   - 非 x64 → バリアントなし\n   - x64 + `TARGET_VARIANT` → 明示的バリアント\n   - x64 クロスビルドで `TARGET_VARIANT` なし → ハードエラー\n   - x64 ローカルビルドでオーバーライドなし → ホストの AVX2 を検出\n3. **コンパイル**: 解決されたプロファイル/ターゲットで Cargo を実行\n4. **アーティファクト検出**: ターゲットルート/プロファイルディレクトリ/ライブラリ名をスキャン\n5. **インストール**: `packages/natives/native` にコピー + アトミックリネーム\n6. **完了**: ローダー候補として準備されたアドオンを出力\n\n障害発生時は、明示的なエラーテキスト（無効なバリアント、Cargo ビルド失敗、出力ライブラリの欠落、インストール/リネーム失敗）とともにいずれのステージでも終了します。\n\n### 組み込みライフサイクル (`embed-native.ts`)\n\n1. **初期化**: `TARGET_PLATFORM`/`TARGET_ARCH` またはホスト値からプラットフォームタグを計算\n2. **候補セット**:\n   - x64 は `modern` と `baseline` の両方を期待\n   - 非 x64 は 1 つのデフォルトファイルを期待\n3. `packages/natives/native` での**可用性を検証**\n4. Bun の `file` インポートとパッケージバージョンを含む**マニフェストを生成** (`src/embedded-addon.ts`)\n5. コンパイルモード用の**ランタイム抽出準備完了**\n\n`--reset` は検証をバイパスし、null マニフェストスタブ (`embeddedAddon = null`) を書き込みます。\n\n## 開発ワークフローと出荷/コンパイル済み動作\n\n## ローカル開発ワークフロー\n\n典型的なローカルループ：\n\n1. アドオンをビルド:\n   - リリース: `bun --cwd=packages/natives run build`\n   - デバッグプロファイル: `bun --cwd=packages/natives run dev:native`\n2. ローダー診断をテストする場合は `PI_DEV=1` を設定\n3. `native.ts` のローダーがパッケージローカルの `native/`（および実行ファイルディレクトリのフォールバック）候補を解決\n4. `validateNative` がラッパーがバインディングを使用する前にエクスポートの互換性を検証\n\n## 出荷/コンパイル済みバイナリワークフロー\n\nコンパイルモード（`PI_COMPILED` または Bun 組み込みマーカー）の場合：\n\n1. ローダーがバージョン付きキャッシュディレクトリを計算: `<getNativesDir()>/<packageVersion>` （運用上は `~/.xcsh/natives/<version>`）\n2. 組み込みマニフェストが現在のプラットフォーム + バージョンと一致する場合、ローダーは選択された組み込みファイルをそのバージョン付きディレクトリに抽出する可能性あり\n3. ランタイム候補の順序は以下を含む:\n   - バージョン付きキャッシュディレクトリ\n   - レガシーコンパイル済みバイナリディレクトリ（Windows では `%LOCALAPPDATA%/xcsh`、その他では `~/.local/bin`）\n   - パッケージ/実行ファイルディレクトリ\n4. 最初に正常に読み込まれたアドオンは引き続き `validateNative` を通過する必要あり\n\nこれが、パッケージングとランタイムローダーの期待値が一致する必要がある理由です：ファイル名、プラットフォームタグ、およびエクスポートされたシンボルは、`native.ts` がプローブおよび検証するものと一致する必要があります。\n\n## JS API ↔ Rust エクスポートマッピング（検証ゲートのサブセット）\n\n`native.ts` は、読み込まれたアドオンにこれらの JS 可視エクスポートが存在することを要求します。これらは `crates/pi-natives/src` の Rust N-API エクスポートにマッピングされます：\n\n| `validateNative` が要求する JS 名 | Rust エクスポート宣言 | Rust ソースファイル |\n| --- | --- | --- |\n| `glob` | `#[napi] pub fn glob(...)` | `crates/pi-natives/src/glob.rs` |\n| `grep` | `#[napi] pub fn grep(...)` | `crates/pi-natives/src/grep.rs` |\n| `search` | `#[napi] pub fn search(...)` | `crates/pi-natives/src/grep.rs` |\n| `highlightCode` | `#[napi] pub fn highlight_code(...)` | `crates/pi-natives/src/highlight.rs` |\n| `getSystemInfo` | `#[napi] pub fn get_system_info(...)` | `crates/pi-natives/src/system_info.rs` |\n| `getWorkProfile` | `#[napi] pub fn get_work_profile(...)` （キャメルケースエクスポート） | `crates/pi-natives/src/prof.rs` |\n| `invalidateFsScanCache` | `#[napi] pub fn invalidate_fs_scan_cache(...)` | `crates/pi-natives/src/fs_cache.rs` |\n\n必要なシンボルが欠落している場合、ローダーはリビルドヒントとともに即座に失敗します。\n\n## 障害動作と診断\n\n## ビルド時の障害\n\n- 無効なバリアント設定:\n  - 非 x64 で `TARGET_VARIANT` が設定されている → 即座にエラー\n  - x64 クロスビルドで明示的な `TARGET_VARIANT` がない → 即座にエラー\n- Cargo ビルドの失敗:\n  - スクリプトが非ゼロ終了と stderr を表示\n- アーティファクトが見つからない:\n  - スクリプトがチェックしたすべてのプロファイルディレクトリを出力\n- インストールの失敗:\n  - 明示的なメッセージ; Windows ではロックされたファイルのヒントを含む\n\n## ランタイムローダーの障害 (`native.ts`)\n\n- サポートされていないプラットフォームタグ:\n  - サポートされているプラットフォームリストとともにスロー\n- どの候補も読み込めない:\n  - 完全な候補エラーリストとモード固有の修正ヒントとともにスロー\n- エクスポートの欠落:\n  - 正確な欠落シンボル名とリビルドコマンドとともにスロー\n- 組み込み抽出の問題:\n  - 抽出の mkdir/write エラーが記録され、最終診断に含まれる\n\n## トラブルシューティングマトリクス\n\n| 症状 | 考えられる原因 | 確認方法 | 修正方法 |\n| --- | --- | --- | --- |\n| `Native addon missing exports ... Missing: <name>` | 古い `.node` バイナリ、Rust エクスポート名の不一致、または間違ったバイナリが読み込まれた | `PI_DEV=1` で実行して読み込まれたパスを確認; そのファイルのエクスポートリストを検査 | `build` を再ビルド; Rust の `#[napi]` エクスポート名（または必要に応じて明示的エイリアス）が JS キーと一致することを確認; 古いキャッシュ/バージョン付きファイルを削除 |\n| x64 マシンで modern が期待されるのに baseline が読み込まれる | `PI_NATIVE_VARIANT=baseline`、AVX2 が検出されない、または baseline ファイルのみ存在 | `PI_NATIVE_VARIANT` を確認; `native/` の `-modern` ファイルを検査 | modern バリアントをビルド（`TARGET_VARIANT=modern ... build`）し、ファイルが同梱されていることを確認 |\n| クロスビルドが使用不可/ラベル不一致のバイナリを生成 | `CROSS_TARGET` と `TARGET_PLATFORM`/`TARGET_ARCH` の不一致、または x64 で `TARGET_VARIANT` が欠落 | 環境変数のタプルと出力ファイル名を確認 | 一貫した環境変数値と明示的な x64 `TARGET_VARIANT` で再実行 |\n| アップグレード後にコンパイル済みバイナリが失敗 | 古い抽出キャッシュ（`~/.xcsh/natives/<old-or-mismatched-version>`）または組み込みマニフェストの不一致 | バージョン付き natives ディレクトリとローダーエラーリストを検査 | パッケージバージョンのバージョン付き natives キャッシュを削除して再実行; パッケージング時に組み込みマニフェストを再生成 |\n| ローダーが多くのパスをプローブするがどれも動作しない | プラットフォームの不一致またはパッケージの `native/` にリリースアーティファクトが欠落 | `platformTag` と実際のファイル名を確認 | ビルドされたファイル名が `pi_natives.<platform>-<arch>(-variant).node` の規則と正確に一致し、パッケージに `native/` が含まれていることを確認 |\n| `embed:native` が \"Incomplete native addons\" で失敗 | 組み込み前に必要なバリアントファイルがビルドされていない | エラーテキストの期待値と検出リストを確認 | 必要なファイルを先にビルド（x64: modern+baseline の両方; 非 x64: デフォルト）してから `embed:native` を再実行 |\n\n## 運用コマンド\n\n```bash\n# 現在のホスト用リリースアーティファクト\nbun --cwd=packages/natives run build\n\n# デバッグプロファイルのアーティファクトビルド\nbun --cwd=packages/natives run dev:native\n\n# 明示的な x64 バリアントをビルド\nTARGET_VARIANT=modern bun --cwd=packages/natives run build\nTARGET_VARIANT=baseline bun --cwd=packages/natives run build\n\n# ビルドされたネイティブファイルから組み込みアドオンマニフェストを生成\nbun --cwd=packages/natives run embed:native\n\n# 組み込みマニフェストを null スタブにリセット\nbun --cwd=packages/natives run embed:native -- --reset\n```\n",
	"ja/natives/natives-media-system-utils.md": "---\ntitle: ネイティブメディアおよびシステムユーティリティ\ndescription: スクリーンショット、画像処理、システム情報のためのネイティブメディア処理ユーティリティ。\nsidebar:\n  order: 7\n  label: メディア & システムユーティリティ\ni18n:\n  sourceHash: 430898c177bc\n  translator: machine\n---\n\n# ネイティブメディア + システムユーティリティ\n\nこのドキュメントは、[`docs/natives-architecture.md`](./natives-architecture.md) で説明されている**システム/メディア/変換プリミティブ**レイヤーのサブシステム詳細解説です: `image`、`html`、`clipboard`、および `work` プロファイリング。\n\n## 実装ファイル\n\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/prof.rs`\n- `crates/pi-natives/src/task.rs`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/work/index.ts`\n- `packages/natives/src/work/types.ts`\n\n> 注: `crates/pi-natives/src/work.rs` は存在しません。ワークプロファイリングは `prof.rs` で実装され、`task.rs` のインストルメンテーションからデータが供給されます。\n\n## TS API ↔ Rust エクスポート/モジュールマッピング\n\n| TS エクスポート (packages/natives)           | Rust N-API エクスポート                                                  | Rust モジュール                        |\n| ------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------- |\n| `PhotonImage.parse(bytes)`                  | `PhotonImage::parse`                                                     | `image.rs`                            |\n| `PhotonImage#resize(width, height, filter)` | `PhotonImage::resize`                                                    | `image.rs`                            |\n| `PhotonImage#encode(format, quality)`       | `PhotonImage::encode`                                                    | `image.rs`                            |\n| `htmlToMarkdown(html, options)`             | `html_to_markdown`                                                       | `html.rs`                             |\n| `copyToClipboard(text)`                     | `copy_to_clipboard` + TS フォールバックロジック                            | `clipboard.rs` + `clipboard/index.ts` |\n| `readImageFromClipboard()`                  | `read_image_from_clipboard`                                              | `clipboard.rs`                        |\n| `getWorkProfile(lastSeconds)`               | `get_work_profile`                                                      | `prof.rs`                             |\n\n## データフォーマット境界と変換\n\n### 画像 (`image`)\n\n- **JS 入力境界**: `Uint8Array` エンコード済み画像バイト。\n- **Rust デコード境界**: バイトは `Vec<u8>` にコピーされ、`ImageReader::with_guessed_format()` でフォーマットが推測された後、`DynamicImage` にデコードされます。\n- **インメモリ状態**: `PhotonImage` は `Arc<DynamicImage>` を保持します。\n- **出力境界**: `encode(format, quality)` は `Promise<Uint8Array>` (Rust `Vec<u8>`) を返します。\n\nフォーマット ID は数値です:\n\n- `0`: PNG\n- `1`: JPEG\n- `2`: WebP (ロスレスエンコーダー)\n- `3`: GIF\n\n制約:\n\n- `quality` は JPEG でのみ使用されます。\n- PNG/WebP/GIF は `quality` を無視します。\n- サポートされていないフォーマット ID は失敗します (`Invalid image format: <id>`)。\n\n### HTML 変換 (`html`)\n\n- **JS 入力境界**: HTML `string` + オプションのオブジェクト `{ cleanContent?: boolean; skipImages?: boolean }`。\n- **Rust 変換境界**: `String` 入力は `html_to_markdown_rs::convert` によって変換されます。\n- **出力境界**: Markdown `string`。\n\n変換の動作:\n\n- `cleanContent` のデフォルトは `false` です。\n- `cleanContent=true` の場合、`PreprocessingPreset::Aggressive` による前処理とナビゲーション/フォームのハード削除フラグが有効になります。\n- `skipImages` のデフォルトは `false` です。\n\n### クリップボード (`clipboard`)\n\n- **テキストパス**:\n  - TS はまず stdout が TTY の場合に OSC 52 (`\\x1b]52;c;<base64>\\x07`) を出力します。\n  - 同じテキストがベストエフォートとしてネイティブクリップボード API (`native.copyToClipboard`) 経由でも試行されます。\n  - Termux では、TS はまず `termux-clipboard-set` を試行します。\n- **画像読み取りパス**:\n  - Rust は `arboard` から生の画像を読み取ります。\n  - Rust はそれを PNG バイトに再エンコードし (`image` クレート)、`{ data: Uint8Array, mimeType: \"image/png\" }` を返します。\n  - TS は Termux またはディスプレイサーバーのない Linux セッション (`DISPLAY`/`WAYLAND_DISPLAY` が未設定) では早期に `null` を返します。\n\n### ワークプロファイリング (`work`)\n\n- **収集境界**: プロファイリングサンプルは `task::blocking` と `task::future` 内の `profile_region(tag)` ガードによって生成されます。\n- **ストレージフォーマット**: スタックパス + 期間 (`μs`) + タイムスタンプ (`プロセス開始からの μs`) を格納する固定サイズのリングバッファ (`MAX_SAMPLES = 10_000`)。\n- **出力境界**: `getWorkProfile(lastSeconds)` はオブジェクトを返します:\n  - `folded`: フォールドスタックテキスト (フレームグラフ入力)\n  - `summary`: マークダウンテーブルのサマリー\n  - `svg`: オプションのフレームグラフ SVG\n  - `totalMs`、`sampleCount`\n\n## ライフサイクルと状態遷移\n\n### 画像のライフサイクル\n\n1. `PhotonImage.parse(bytes)` はブロッキングデコードタスク (`image.decode`) をスケジュールします。\n2. 成功時、ネイティブの `PhotonImage` ハンドルが JS に存在します。\n3. `resize(...)` は新しいネイティブハンドル (`image.resize`) を作成し、古いハンドルと新しいハンドルは共存できます。\n4. `encode(...)` は画像の寸法を変更せずにバイトを具体化します (`image.encode`)。\n\n失敗時の遷移:\n\n- フォーマット検出/デコードの失敗は parse プロミスを拒否します。\n- エンコードの失敗は encode プロミスを拒否します。\n- 無効なフォーマット ID は encode プロミスを拒否します。\n\n### HTML のライフサイクル\n\n1. `htmlToMarkdown(html, options)` はブロッキング変換タスクをスケジュールします。\n2. 変換は指定がない限りデフォルトのオプション (`cleanContent=false`、`skipImages=false`) で実行されます。\n3. マークダウン文字列を返すか、拒否します。\n\n失敗時の遷移:\n\n- コンバーターの失敗は拒否されたプロミスを返します (`Conversion error: ...`)。\n\n### クリップボードのライフサイクル\n\n`copyToClipboard(text)` は意図的にベストエフォートかつマルチパスです:\n\n1. TTY の場合: OSC 52 書き込み (base64 ペイロード) を試行します。\n2. `TERMUX_VERSION` が設定されている場合、Termux コマンドを試行します。\n3. ネイティブの `arboard` テキストコピーを試行します。\n4. TS レイヤーでエラーを吸収します。\n\n`readImageFromClipboard()` はステージによって厳格さが異なります:\n\n1. TS はサポートされていないランタイムコンテキスト (Termux/ヘッドレス Linux) を `null` にハードゲートします。\n2. Rust の `arboard` 読み取りは TS が許可した場合のみ実行されます。\n3. `ContentNotAvailable` は `null` にマップされます。\n4. その他の Rust エラーは拒否されます。\n\n### ワークプロファイリングのライフサイクル\n\n1. 明示的な開始はありません: タスクヘルパーが実行される際にプロファイリングは常にオンです。\n2. インストルメンテーションされたすべてのタスクスコープは、ガードのドロップ時に1つのサンプルを記録します。\n3. バッファ容量に達した後、サンプルは最も古いエントリを上書きします。\n4. `getWorkProfile(lastSeconds)` は時間ウィンドウを読み取り、フォールド/サマリー/SVG アーティファクトを生成します。\n\n失敗時の遷移:\n\n- SVG 生成の失敗はソフトフェイルです (`svg: null`)。フォールドとサマリーは引き続き返されます。\n- 空のサンプルウィンドウは空のフォールドデータと `svg: null` を返し、エラーにはなりません。\n\n## サポートされていない操作とエラー伝播\n\n### 画像\n\n- サポートされていないデコード入力または破損したバイト: 厳格な失敗 (プロミスの拒否)。\n- サポートされていないエンコードフォーマット ID: 厳格な失敗。\n- TS ラッパーにベストエフォートのフォールバックパスはありません。\n\n### HTML\n\n- 変換エラーは厳格な失敗 (拒否) です。\n- オプションの省略はベストエフォートのデフォルト設定であり、失敗ではありません。\n\n### クリップボード\n\n- テキストコピーは TS レイヤーでベストエフォートです: 操作上の失敗は抑制されます。\n- 画像読み取りは「画像なし」(`null`) と操作上の失敗 (拒否) を区別します。\n- Termux/ヘッドレス Linux は画像読み取りのサポートされていないコンテキストとして扱われます (`null`)。\n\n### ワークプロファイリング\n\n- 取得は関数呼び出し自体に対しては厳格ですが、アーティファクト生成は部分的にベストエフォートです (`svg` は null 許容)。\n- バッファの切り捨ては期待される動作 (リングバッファ) であり、データ損失のバグではありません。\n\n## プラットフォームの注意事項\n\n- **クリップボードテキスト**: OSC 52 はターミナルのサポートに依存します。ネイティブクリップボードアクセスはデスクトップ環境/セッションに依存します。\n- **クリップボード画像読み取り**: Termux およびディスプレイサーバーのない Linux では TS でブロックされます。\n",
	"ja/natives/natives-rust-task-cancellation.md": "---\ntitle: ネイティブRustタスクの実行とキャンセル\ndescription: 協調的キャンセルとクリーンアップセマンティクスを持つRust非同期タスク実行モデル。\nsidebar:\n  order: 5\n  label: タスクキャンセル\ni18n:\n  sourceHash: 0fbf45c6d463\n  translator: machine\n---\n\n# ネイティブRustタスクの実行とキャンセル (`pi-natives`)\n\nこのドキュメントでは、`crates/pi-natives` がネイティブワークをどのようにスケジューリングし、JSオプション（`timeoutMs`、`AbortSignal`）からRust実行へキャンセルがどのように伝播するかを説明します。\n\n## 実装ファイル\n\n- `crates/pi-natives/src/task.rs`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/fd.rs`\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/ps.rs`\n\n## コアプリミティブ (`task.rs`)\n\n`task.rs` は3つのコア要素を定義しています：\n\n1. `task::blocking(tag, cancel_token, work)`\n   - `napi::AsyncTask` / `Task` をラップします。\n   - `compute()` はlibuvワーカースレッド上で実行されます（CPU負荷の高い処理やブロッキング/同期システムコール向け）。\n   - JS `Promise<T>` を返します。\n\n2. `task::future(env, tag, work)`\n   - `env.spawn_future(...)` をラップします。\n   - 非同期ワークをTokioランタイム上で実行します。\n   - `PromiseRaw<'env, T>` を返します。\n\n3. `CancelToken` / `AbortToken` / `AbortReason`\n   - `CancelToken::new(timeout_ms, signal)` はデッドラインとオプションの `AbortSignal` を組み合わせます。\n   - `CancelToken::heartbeat()` はブロッキングループのための協調的キャンセルです。\n   - `CancelToken::wait()` は非同期キャンセル待機です（`Signal` / `Timeout` / `User` Ctrl-C）。\n   - `AbortToken` は外部コードがアボートを要求できるようにします（`abort(reason)`）。\n\n## `blocking` vs `future`：実行モデルと選択基準\n\n### `task::blocking` を使用する場合\n\nワークがCPU負荷が高い、または根本的に同期的/ブロッキングである場合に使用します：\n\n- 正規表現/ファイルスキャン（`grep`、`glob`、`fuzzy_find`）\n- 同期PTYループの内部処理（`spawn_blocking` 経由の `run_pty_sync`）\n- クリップボード/画像/HTML変換\n\n動作：\n\n- ワーククロージャはクローンされた `CancelToken` を受け取ります。\n- キャンセルはコードが `ct.heartbeat()?` をチェックする箇所でのみ検知されます。\n- クロージャの `Err(...)` はJSのPromiseをリジェクトします。\n\n### `task::future` を使用する場合\n\nワークが非同期操作を `await` する必要がある場合に使用します：\n\n- シェルセッションのオーケストレーション（`shell.run`、`executeShell`）\n- タスクレーシング（`tokio::select!`）による完了とキャンセルの競合\n\n動作：\n\n- Futureは通常の完了と `ct.wait()` をレースさせることができます。\n- キャンセルパスでは、非同期実装は通常、内部サブシステム（例：`tokio_util::CancellationToken`）にキャンセルを伝播し、オプションでグレースタイムアウト後に強制アボートします。\n\n## JS API ↔ Rustエクスポートのマッピング（タスク/キャンセル関連）\n\n| JS向けAPI | Rustエクスポート (`#[napi]`) | スケジューラ | キャンセル接続 |\n|---|---|---|---|\n| `grep(options, onMatch?)` | `grep` | `task::blocking(\"grep\", ct, ...)` | `CancelToken::new(options.timeoutMs, options.signal)` + `ct.heartbeat()` |\n| `glob(options, onMatch?)` | `glob` | `task::blocking(\"glob\", ct, ...)` | `CancelToken::new(...)` + フィルタループ内の `ct.heartbeat()` |\n| `fuzzyFind(options)` | `fuzzy_find` | `task::blocking(\"fuzzy_find\", ct, ...)` | `CancelToken::new(...)` + スコアリングループ内の `ct.heartbeat()` |\n| `shell.run(options, onChunk?)` | `Shell::run` | `task::future(env, \"shell.run\", ...)` | 実行タスクに対して `ct.wait()` をレース；Tokio `CancellationToken` にブリッジ |\n| `executeShell(options, onChunk?)` | `execute_shell` | `task::future(env, \"shell.execute\", ...)` | 上記と同じ |\n| `pty.start(options, onChunk?)` | `PtySession::start` | `task::future(env, \"pty.start\", ...)` + 内部 `spawn_blocking` | 同期PTYループ内で `heartbeat()` 経由で `CancelToken` をチェック |\n| `htmlToMarkdown(html, options?)` | `html_to_markdown` | `task::blocking(\"html_to_markdown\", (), ...)` | なし（`()` トークン） |\n| `PhotonImage.parse/encode/resize` | `PhotonImage::{parse,encode,resize}` | `task::blocking(...)` | なし（`()` トークン） |\n| `copyToClipboard/readImageFromClipboard` | `copy_to_clipboard` / `read_image_from_clipboard` | `task::blocking(...)` | なし（`()` トークン） |\n\n`text.rs` と `ps.rs` は現在 `task::blocking`/`task::future` を使用しておらず、このキャンセルパスには参加しません。\n\n## キャンセルのライフサイクルと状態遷移\n\n### `CancelToken` のライフサイクル\n\n`CancelToken` は協調的かつステートフルです：\n\n```text\nCreated\n  ├─ no signal + no timeout  -> passive token (never aborts unless externally emplaced)\n  ├─ signal registered        -> waits for AbortSignal callback\n  └─ deadline set             -> timeout check becomes active\n\nRunning\n  ├─ heartbeat()/wait() sees signal   -> AbortReason::Signal\n  ├─ heartbeat()/wait() sees deadline -> AbortReason::Timeout\n  ├─ wait() sees Ctrl-C               -> AbortReason::User\n  └─ no abort                         -> continue\n\nAborted (terminal)\n  └─ first abort reason wins (atomic flag + notifier)\n```\n\n### 開始前 vs 実行中のキャンセル\n\n- **開始前 / 最初のキャンセルチェック前**：\n  - `task::future` のユーザーが `ct.wait()` でレースする場合、`select!` に入った時点で即座にキャンセルを解決できます。\n  - `task::blocking` のユーザーは、クロージャコードが `heartbeat()` に到達した時にのみキャンセルを検知します。クロージャが早い段階でハートビートしない場合、キャンセルは遅延します。\n\n- **実行中**：\n  - `blocking`：次の `heartbeat()` が `Err(\"Aborted: ...\")` を返します。\n  - `future`：`ct.wait()` ブランチが `select!` で勝利し、その後コードが従属する非同期機構をキャンセルします（シェルの場合：Tokioトークンをキャンセルし、最大2秒待機してからタスクをアボート）。\n\n## 長時間実行ループにおけるハートビートの期待値\n\n`heartbeat()` は、無制限または大規模なワークセットを持つループで予測可能な頻度で実行する必要があります。\n\n観察されるパターン：\n\n- `glob::filter_entries`：フィルタリング/マッチング前に各エントリをチェック。\n- `fd::score_entries`：スキャンされた各候補をチェック。\n- `grep_sync`：重い検索フェーズ前の明示的なキャンセルチェック、およびトークンを受け取るfs-cache呼び出し。\n- `run_pty_sync`：毎ループティック（約16msのスリープ間隔）でチェックし、キャンセル時に子プロセスをkill。\n\n実用的なルール：外部サイズの入力に対するループは、ハートビートなしに短い制限された間隔を超えてはなりません。\n\n## 失敗動作とJSへのエラー伝播\n\n### ブロッキングタスク\n\nエラーパス：\n\n1. クロージャが `Err(napi::Error)` を返す（`heartbeat()` のアボートを含む）。\n2. `Task::compute()` が `Err` を返す。\n3. `AsyncTask` がJSのPromiseをリジェクトする。\n\n典型的なエラー文字列：\n\n- `Aborted: Timeout`\n- `Aborted: Signal`\n- ドメインエラー（`Failed to decode image: ...`、`Conversion error: ...` など）\n\n### Futureタスク\n\nエラーパス：\n\n1. 非同期ボディが `Err(napi::Error)` を返すか、join失敗がマッピングされる（`... task failed: {err}`）。\n2. `task::future` で生成されたPromiseがリジェクトされる。\n3. 一部のAPIは、リジェクトではなく構造化されたキャンセル結果を意図的に返す（`ShellRunResult`/`ShellExecuteResult` の `cancelled`/`timed_out` フラグと `exit_code: None`）。\n\n### キャンセル報告の分類\n\n- **エラーとしてのアボート**：`heartbeat()?` を使用するほとんどのブロッキングエクスポート。\n- **型付き結果としてのアボート**：結果構造体でキャンセルをモデル化するシェル/PTYスタイルのコマンドAPI。\n\nAPI毎に1つのモデルを選択し、明示的にドキュメント化してください。\n\n## よくある落とし穴\n\n1. **ブロッキングループでのハートビートの欠落**\n   - 症状：ループが終了するまでtimeout/signalが無視されているように見える。\n   - 修正：ループの先頭と高コストなアイテムごとのステップ前に `ct.heartbeat()?` を追加する。\n\n2. **長いキャンセル不可セクション**\n   - 症状：単一の大きな呼び出し（デコード、ソート、圧縮など）中にキャンセルレイテンシが急増する。\n   - 修正：ハートビート境界でワークをチャンクに分割する。不可能な場合はレイテンシをドキュメント化する。\n\n3. **非同期エグゼキュータのブロッキング**\n   - 症状：同期的に重いコードがfuture内で直接実行されると非同期APIが停止する。\n   - 修正：CPU/同期ブロックを `task::blocking` または `tokio::task::spawn_blocking` に移動する。\n\n4. **一貫性のないキャンセルセマンティクス**\n   - 症状：あるAPIはキャンセル時にリジェクトし、別のAPIはフラグ付きでリゾルブするため、呼び出し側が混乱する。\n   - 修正：ドメインごとに標準化し、ラッパーのドキュメントを整合させる。\n\n5. **ネストされた非同期タスクでのキャンセルブリッジの忘れ**\n   - 症状：外部トークンがキャンセルされても内部リーダー/サブプロセスタスクが実行を継続する。\n   - 修正：内部トークン/シグナルにキャンセルをブリッジし、グレースタイムアウト + 強制アボートフォールバックを適用する。\n\n## 新しいキャンセル可能エクスポートのチェックリスト\n\n1. ワークを正しく分類する：\n   - CPU負荷が高いまたは同期ブロッキング -> `task::blocking`\n   - 非同期I/O / `await` オーケストレーション -> `task::future`\n\n2. 必要に応じてキャンセル入力を公開する：\n   - `#[napi(object)]` オプションに `timeoutMs` と `signal` を含める\n   - `let ct = task::CancelToken::new(timeout_ms, signal);` を作成する\n\n3. すべてのレイヤーにキャンセルを接続する：\n   - ブロッキングループ：安定した間隔で `ct.heartbeat()?` を実行\n   - 非同期オーケストレーション：`ct.wait()` とレースし、サブタスク/トークンをキャンセル\n\n4. キャンセルの契約を決定する：\n   - アボートエラーでPromiseをリジェクトする、または\n   - 型付き `{ cancelled, timedOut, ... }` でリゾルブする\n   - この契約をAPIファミリ全体で一貫させる\n\n5. コンテキスト付きで失敗を伝播する：\n   - `Error::from_reason(format!(\"...: {err}\"))` でエラーをマッピングする\n   - ステージ固有のプレフィックスを含める（`spawn`、`decode`、`wait` など）\n\n6. 開始前および実行中のキャンセルを処理する：\n   - キャンセルチェック/awaitは、高コストなボディの前と長時間実行中に行う必要がある\n\n7. エグゼキュータの誤用がないことを検証する：\n   - `spawn_blocking`/ブロッキングタスクラッパーなしで、非同期future内に長い同期ワークを直接配置しない\n",
	"ja/natives/natives-shell-pty-process.md": "---\ntitle: ネイティブ Shell、PTY、プロセス、およびキー内部構造\ndescription: ネイティブレイヤーにおけるシェル実行、PTY管理、プロセスライフサイクル、およびキーイベント処理。\nsidebar:\n  order: 4\n  label: Shell、PTY & プロセス\ni18n:\n  sourceHash: 00ea95614c6a\n  translator: machine\n---\n\n# ネイティブ Shell、PTY、プロセス、およびキー内部構造\n\nこのドキュメントでは、`@f5-sales-demo/pi-natives` における**実行/プロセス/ターミナルのプリミティブ**（`shell`、`pty`、`ps`、`keys`）について、`docs/natives-architecture.md` のアーキテクチャ用語を使用して説明します。\n\n## 実装ファイル\n\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/shell/windows.rs`（Windows のみ）\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/ps.rs`\n- `crates/pi-natives/src/keys.rs`\n- `crates/pi-natives/src/task.rs`（shell/pty で使用される共有キャンセル動作）\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/bindings.ts`\n\n## レイヤーの責務\n\n- **TS ラッパー/API レイヤー**（`packages/natives/src/*`）：型付きエントリポイント、キャンセルサーフェス（`timeoutMs`、`AbortSignal`）、および JS エルゴノミクス。\n- **Rust N-API モジュールレイヤー**（`crates/pi-natives/src/*`）：shell/PTY プロセス実行、プロセスツリーの走査/終了、およびキーシーケンスのパース。\n- **バリデーションゲート**（`native.ts`、アーキテクチャレベル）：ラッパーが使用される前に、必要なエクスポート（`Shell`、`executeShell`、`PtySession`、`killTree`、`listDescendants`、キーヘルパー）の存在を確認します。\n\n## Shell サブシステム（`shell`）\n\n### API モデル\n\n2つの実行モードが公開されています：\n\n1. **ワンショット** - `executeShell(options, onChunk?)` による実行。\n2. **永続セッション** - `new Shell(options?)` を作成し、`shell.run(...)` を繰り返し呼び出す。\n\n両方ともスレッドセーフなコールバックを通じて出力をストリーミングし、`{ exitCode?, cancelled, timedOut }` を返します。\n\n### セッションの作成と環境モデル\n\nRust は以下の設定で `brush_core::Shell` を作成します：\n\n- 非対話モード、\n- `do_not_inherit_env: true`、\n- ホスト環境からの明示的な環境再構築、\n- シェルに影響する変数のスキップリスト（`PS1`、`PWD`、`SHLVL`、bash 関数エクスポートなど）。\n\nセッション環境の動作：\n\n- `ShellOptions.sessionEnv` はセッション作成時に一度だけ適用されます。\n- `ShellRunOptions.env` はコマンドスコープ（`EnvironmentScope::Command`）であり、各実行後にポップされます。\n- `PATH` は Windows では大文字小文字を区別しない重複排除でマージされます。\n\nWindows 固有のパス拡充（`shell/windows.rs`）：検出された Git-for-Windows のパス（`cmd`、`bin`、`usr/bin`）が存在し、まだ含まれていない場合に追加されます。\n\n### ランタイムライフサイクルと状態遷移\n\n永続シェル（`Shell.run`）は以下のステートマシンを使用します：\n\n- **Idle/Uninitialized**：`session: None`。\n- **Running**：最初の `run()` がセッションを遅延作成し、`current_abort` トークンを保存してコマンドを実行します。\n- **Completed + keepalive**：実行制御フローが `Normal` の場合、`current_abort` がクリアされ、セッションが再利用されます。\n- **Completed + teardown**：制御フローがループ/スクリプト/シェル終了関連（`BreakLoop`、`ContinueLoop`、`ReturnFromFunctionOrScript`、`ExitShell`）の場合、セッションが破棄されます（`session: None`）。\n- **Cancelled/Timed out**：実行タスクがキャンセルされ、猶予待機（2秒）後に強制中断、セッションが破棄されます。\n- **Error**：セッションが破棄されます。\n\nワンショットシェル（`executeShell`）は呼び出しごとに常に新しいセッションを作成して破棄します。\n\n### ストリーミング/出力動作\n\n- stdout/stderr は共有パイプにルーティングされ、並行して読み取られます。\n- リーダーは UTF-8 をインクリメンタルにデコードし、無効なバイトシーケンスは `U+FFFD` 置換チャンクとして出力されます。\n- プロセス完了後、出力ドレインにはアイドル/最大ガード（アイドル `250ms`、最大 `2s`）があり、バックグラウンドジョブがディスクリプタを開いたままにしてハングすることを防ぎます。\n\n### キャンセル、タイムアウト、およびバックグラウンドジョブ\n\n- `CancelToken` は `timeoutMs` とオプションの `AbortSignal` から構築されます。\n- キャンセル/タイムアウト時にシェルのキャンセルトークンがトリガーされ、その後タスクは強制中断前に2秒の猶予期間を得ます。\n- キャンセルが発生した場合、バックグラウンドジョブは brush ジョブメタデータを使用して終了されます（`TERM`、その後遅延 `KILL`）。\n\n`Shell.abort()` の動作：\n\n- その `Shell` インスタンスの現在実行中のコマンドのみを中断します。\n- 何も実行されていない場合は成功のノーオペレーションになります。\n\n### 失敗時の動作\n\n一般的に表面化するエラーには以下が含まれます：\n\n- セッション初期化失敗（`Failed to initialize shell`）、\n- cwd エラー（`Failed to set cwd`）、\n- 環境変数の設定/ポップ失敗、\n- スナップショットソース失敗、\n- パイプ作成/クローン失敗、\n- 実行失敗（`Shell execution failed: ...`）、\n- タスクラッパー失敗（`Shell execution task failed: ...`）。\n\n結果レベルのキャンセルフラグ：\n\n- タイムアウト -> `exitCode: undefined`、`timedOut: true`。\n- 中断シグナル -> `exitCode: undefined`、`cancelled: true`。\n\n## PTY サブシステム（`pty`）\n\n### API モデル\n\n`new PtySession()` は以下を公開します：\n\n- `start(options, onChunk?) -> Promise<{ exitCode?, cancelled, timedOut }>`\n- `write(data)`\n- `resize(cols, rows)`\n- `kill()`\n\n### ランタイムライフサイクルと状態遷移\n\n`PtySession` のステートマシン：\n\n- **Idle**：`core: None`。\n- **Reserved**：`start()` は非同期処理の開始前に同期的にコントロールチャネルをインストール（`core: Some`）するため、`write/resize/kill` は即座に有効になります。\n- **Running**：ブロッキング PTY ループが子プロセスの状態、リーダーイベント、キャンセルハートビート、およびコントロールメッセージを処理します。\n- **Terminal closed**：子プロセスの終了 + リーダーの完了。\n- **Finalized**：start タスクの完了後（成功またはエラーに関わらず）、`core` は常に `None` にリセットされます。\n\n同時実行ガード：\n\n- 既に実行中に開始すると `PTY session already running` が返されます。\n\n### スポーン/アタッチ/ライト/リード/ターミネートパターン\n\n- PTY は `portable_pty::native_pty_system().openpty(...)` で開かれます。\n- コマンドは現在 `sh -lc <command>` として実行され、オプションの `cwd` と環境変数のオーバーライドがあります。\n- `write()` は生のバイトを PTY の stdin に送信します。\n- `resize()` はディメンションをクランプし（`cols 20..400`、`rows 5..200`）、マスターリサイズを呼び出します。\n- `kill()` は実行をキャンセル済みとしてマークし、子プロセスを強制終了します。\n\n出力パス：\n\n- 専用のリーダースレッドがマスターストリームを読み取り、\n- 無効なバイトに対して `U+FFFD` 置換を行うインクリメンタル UTF-8 デコード、\n- チャンクは N-API スレッドセーフコールバックを通じて転送されます。\n\n### キャンセルとタイムアウトのセマンティクス\n\n- `timeoutMs` と `AbortSignal` が `CancelToken` にフィードされます。\n- ループは定期的に `ct.heartbeat()` を呼び出し、中断時に子プロセスを強制終了します。\n- タイムアウトの分類は文字列ベースです（ハートビートエラーの `\"Timeout\"` 部分文字列）。\n\n### 失敗時の動作\n\nエラーサーフェスには以下が含まれます：\n\n- PTY 割り当て/オープン失敗、\n- PTY スポーン失敗、\n- ライター/リーダー取得失敗、\n- 子プロセスのステータス/待機失敗、\n- ロックポイズニング、\n- コントロールチャネル切断（`PTY session is no longer available`）。\n\n実行中でない場合のコントロール呼び出し失敗：\n\n- `write/resize/kill` は `PTY session is not running` を返します。\n\n## プロセスツリーサブシステム（`ps`）\n\n### API モデル\n\n- `killTree(pid, signal) -> number`\n- `listDescendants(pid) -> number[]`\n\nTS ラッパーは `setNativeKillTree(native.killTree)` を通じて、ネイティブの kill-tree 統合を共有ユーティリティに登録します。\n\n### プラットフォーム固有の実装\n\n- **Linux**：`/proc/<pid>/task/<pid>/children` を再帰的に読み取ります。\n- **macOS**：`libproc` の `proc_listchildpids` を使用します。\n- **Windows**：`CreateToolhelp32Snapshot` でプロセステーブルをスナップショットし、親->子マップを構築し、`OpenProcess(PROCESS_TERMINATE)` + `TerminateProcess` で終了します。\n\n### Kill-tree の動作\n\n- 子孫プロセスは再帰的に収集されます。\n- キルの順序はボトムアップ（最も深い子孫から先に）で、孤立プロセスの再ペアレンティングを軽減します。\n- ルート pid は最後にキルされます。\n- 戻り値は成功した終了の数です。\n\nシグナルの動作：\n\n- POSIX：指定された `signal` が `kill` に渡されます。\n- Windows：`signal` は無視され、終了は無条件のプロセスターミネートです。\n\n### 失敗時の動作\n\nこのモジュールは意図的に API サーフェスでスローしません：\n\n- 欠落/アクセス不能なプロセスツリーブランチはスキップされます、\n- pid ごとのキル失敗は不成功としてカウントされます（エラーではありません）、\n- ルックアップミスは通常、`listDescendants` から `[]`、`killTree` から `0` を返します。\n\n## キーパースサブシステム（`keys`）\n\n### API モデル\n\n公開されているヘルパー：\n\n- `parseKey(data, kittyProtocolActive)`\n- `matchesKey(data, keyId, kittyProtocolActive)`\n- `parseKittySequence(data)`\n- `matchesKittySequence(data, expectedCodepoint, expectedModifier)`\n- `matchesLegacySequence(data, keyName)`\n\n### パースモデル\n\nパーサーは以下を組み合わせます：\n\n- 直接的なシングルバイトマッピング（`enter`、`tab`、`ctrl+<letter>`、印刷可能 ASCII）、\n- O(1) レガシーエスケープシーケンスルックアップ（PHF マップ）、\n- xterm `modifyOtherKeys` パース、\n- Kitty プロトコルパース（`CSI u`、`CSI ~`、`CSI 1;...<letter>`）、\n- キー ID への正規化（`ctrl+c`、`shift+tab`、`pageUp`、`f5` など）。\n\n修飾キーの処理：\n\n- キーマッチングでは shift/alt/ctrl ビットのみが比較されます、\n- ロックビットは比較前にマスクアウトされます。\n\nレイアウトの動作：\n\n- ベースレイアウトフォールバックは意図的に制約されており、リマップされたレイアウトが ASCII 文字/記号に対して誤ったマッチを生成しないようにしています。\n\n### 失敗時の動作\n\n- 認識されないまたは無効なシーケンスはパース関数から `null` を生成します。\n- マッチ関数はパース失敗またはミスマッチ時に `false` を返します。\n- 不正なキー入力に対してスローされるエラーサーフェスはありません。\n\n## JS ラッパー API ↔ Rust エクスポートマッピング\n\n### Shell + PTY + プロセス\n\n| TS ラッパー API | Rust N-API エクスポート | 備考 |\n|---|---|---|\n| `executeShell(options, onChunk?)` | `executeShell` (`execute_shell`) | ワンショットシェル実行 |\n| `new Shell(options?)` | `Shell` クラス | 永続シェルセッション |\n| `shell.run(options, onChunk?)` | `Shell::run` | keepalive 制御フローでセッションを再利用 |\n| `shell.abort()` | `Shell::abort` | そのシェルインスタンスのアクティブな実行を中断 |\n| `new PtySession()` | `PtySession` クラス | ステートフル PTY セッション |\n| `pty.start(options, onChunk?)` | `PtySession::start` | インタラクティブ PTY 実行 |\n| `pty.write(data)` | `PtySession::write` | 生の stdin パススルー |\n| `pty.resize(cols, rows)` | `PtySession::resize` | クランプされたターミナルディメンション |\n| `pty.kill()` | `PtySession::kill` | アクティブな PTY 子プロセスを強制終了 |\n| `killTree(pid, signal)` | `killTree` (`kill_tree`) | 子プロセス優先のプロセスツリー終了 |\n| `listDescendants(pid)` | `listDescendants` (`list_descendants`) | 再帰的な子孫リスト取得 |\n\n### キー\n\n| TS ラッパー API | Rust N-API エクスポート | 備考 |\n|---|---|---|\n| `matchesKittySequence(data, cp, mod)` | `matchesKittySequence` (`matches_kitty_sequence`) | Kitty コードポイント+修飾キーマッチ |\n| `parseKey(data, kittyProtocolActive)` | `parseKey` (`parse_key`) | 正規化されたキー ID パーサー |\n| `matchesLegacySequence(data, keyName)` | `matchesLegacySequence` (`matches_legacy_sequence`) | 厳密なレガシーシーケンスマップチェック |\n| `parseKittySequence(data)` | `parseKittySequence` (`parse_kitty_sequence`) | 構造化された Kitty パース結果 |\n| `matchesKey(data, keyId, kittyProtocolActive)` | `matchesKey` (`matches_key`) | 高レベルキーマッチャー |\n\n## 放棄されたセッションのクリーンアップとファイナライゼーションに関する注意事項\n\n- **Shell 永続セッション**：実行がキャンセル/タイムアウト/エラー/非 keepalive 制御フローの場合、Rust は内部セッション状態を明示的に破棄します。正常な実行が成功した場合はセッションを再利用のために保持します。\n- **PTY セッション**：失敗パスを含め、`start()` の完了後に `core` は常にクリアされます。\n- ラッパーによって**明示的な JS ファイナライザー駆動のキルコントラクトは公開されていません**。クリーンアップは主に実行完了/キャンセルパスに紐づいています。確定的なティアダウンのために、呼び出し元は `timeoutMs`、`AbortSignal`、`shell.abort()`、または `pty.kill()` を使用すべきです。\n",
	"ja/natives/natives-text-search-pipeline.md": "---\ntitle: Natives テキスト＆検索パイプライン\ndescription: grep、glob、および ripgrep ベースのファイルコンテンツインデックスによるネイティブテキスト検索パイプライン。\nsidebar:\n  order: 6\n  label: テキスト＆検索パイプライン\ni18n:\n  sourceHash: 0e93462fdd12\n  translator: machine\n---\n\n# Natives テキスト/検索パイプライン\n\nこのドキュメントでは、`@f5-sales-demo/pi-natives` のテキスト/検索サーフェス（`grep`、`glob`、`text`、`highlight`）について、TypeScript ラッパーから Rust N-API エクスポート、そして JS 結果オブジェクトへのマッピングを説明します。\n\n用語は `docs/natives-architecture.md` に従います：\n\n- **Wrapper**: `packages/natives/src/*` 内の TS API\n- **Rust モジュールレイヤー**: `crates/pi-natives/src/*` 内の N-API エクスポート\n- **共有スキャンキャッシュ**: ディスカバリー/検索フローで使用される `fs_cache` ベースのディレクトリエントリキャッシュ\n\n## 実装ファイル\n\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/glob_util.rs`\n- `crates/pi-natives/src/fs_cache.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/highlight.rs`\n- `crates/pi-natives/src/fd.rs`\n\n## JS API ↔ Rust エクスポートマッピング\n\n| JS ラッパー API | Rust エクスポート (`#[napi]`, snake_case -> camelCase) | Rust モジュール |\n| --- | --- | --- |\n| `grep(options, onMatch?)` | `grep` | `grep.rs` |\n| `searchContent(content, options)` | `search` | `grep.rs` |\n| `hasMatch(content, pattern, options?)` | `hasMatch` | `grep.rs` |\n| `fuzzyFind(options)` | `fuzzyFind` | `fd.rs` |\n| `glob(options, onMatch?)` | `glob` | `glob.rs` |\n| `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `fs_cache.rs` |\n| `wrapTextWithAnsi(text, width)` | `wrapTextWithAnsi` | `text.rs` |\n| `truncateToWidth(text, maxWidth, ellipsis, pad)` | `truncateToWidth` | `text.rs` |\n| `sliceWithWidth(line, startCol, length, strict?)` | `sliceWithWidth` | `text.rs` |\n| `extractSegments(line, beforeEnd, afterStart, afterLen, strictAfter)` | `extractSegments` | `text.rs` |\n| `sanitizeText(text)` | `sanitizeText` | `text.rs` |\n| `visibleWidth(text)` | `visibleWidth` | `text.rs` |\n| `highlightCode(code, lang, colors)` | `highlightCode` | `highlight.rs` |\n| `supportsLanguage(lang)` | `supportsLanguage` | `highlight.rs` |\n| `getSupportedLanguages()` | `getSupportedLanguages` | `highlight.rs` |\n\n## サブシステム別パイプライン概要\n\n## 1) 正規表現検索 (`grep`、`searchContent`、`hasMatch`)\n\n### 入力/オプションフロー\n\n1. TS ラッパーがオプションをネイティブに転送します：\n   - `grep/index.ts` は `options` をほぼそのまま渡し、コールバックを `(match) => void` から napi スレッドセーフコールバック形式 `(err, match)` にラップします。\n   - `searchContent` と `hasMatch` は文字列/`Uint8Array` を直接渡します。\n2. `grep.rs` 内の Rust オプション構造体がキャメルケースフィールド（`ignoreCase`、`maxCount`、`contextBefore`、`contextAfter`、`maxColumns`、`timeoutMs`）をデシリアライズします。\n3. `grep` は `timeoutMs` + `AbortSignal` から `CancelToken` を作成し、`task::blocking(\"grep\", ...)` 内で実行します。\n\n### 実行ブランチ\n\n- **インメモリブランチ（純粋ユーティリティ）**\n  - `search` → `search_sync` → 提供されたコンテンツバイトに対して `run_search` を実行。\n  - ファイルシステムスキャンなし、`fs_cache` なし。\n- **単一ファイルブランチ（ファイルシステム依存）**\n  - `grep_sync` がパスを解決し、メタデータがファイルであることを確認し、ripgrep マッチャーを通じてファイルごとに最大 `MAX_FILE_BYTES`（`4 MiB`）までストリームします。\n- **ディレクトリブランチ（ファイルシステム依存）**\n  - `cache: true` の場合、`fs_cache::get_or_scan` によるオプションのキャッシュルックアップ。\n  - `cache: false` の場合、`fs_cache::force_rescan` による新規スキャン。\n  - キャッシュ経過時間が `empty_recheck_ms()` を超えた場合のオプションの空結果再チェック。\n  - エントリフィルタリング：ファイルのみ + オプションの glob フィルター（`glob_util`）+ オプションの型フィルターマッピング（`js`、`ts`、`rust` など）。\n\n### 検索/収集セマンティクス\n\n- 正規表現エンジン：`ignoreCase` と `multiline` を持つ `grep_regex::RegexMatcherBuilder`。\n- コンテキスト解決：\n  - `contextBefore/contextAfter` がレガシーの `context` をオーバーライド。\n  - 非コンテンツモードではコンテキスト収集をゼロに設定。\n- 出力モード：\n  - `content` => ヒットごとに1つの `GrepMatch`。\n  - `count` と `filesWithMatches` はともにカウントスタイルのエントリにマップ（`lineNumber=0`、`line=\"\"`、`matchCount` が設定）。\n- 制限：\n  - グローバルな `offset` と `maxCount` がファイル全体にわたって適用。\n  - `maxCount` が未設定で `offset == 0` の場合のみ並列パスが使用され、それ以外では決定的なグローバルオフセット/リミットセマンティクスを維持するために順次パスが使用されます。\n\n### JS への結果整形\n\n- Rust の `SearchResult`/`GrepResult` フィールドは N-API オブジェクトフィールド変換を通じて TS 型にマップされます。\n- カウンターは N-API を越える前に `u32` にクランプされます。\n- オプションのブール値は一部のパスで true の場合のみ含まれます（`limitReached`）。\n- ストリーミングコールバックは整形された各 `GrepMatch`（コンテンツまたはカウントエントリ）を受け取ります。\n\n### 失敗時の動作\n\n- `searchContent` は正規表現/検索の失敗に対してスローする代わりに `SearchResult.error` を返します。\n- `grep` はハードエラー（無効なパス、無効な glob/正規表現、キャンセルタイムアウト/アボート）で reject します。\n- `hasMatch` は `Result<bool>` を返し、無効なパターン/UTF-8 デコードエラーでスローします。\n- 複数ファイルスキャンでのファイルオープン/検索エラーはファイルごとにスキップされ、スキャンは続行します。\n\n### 不正な正規表現の処理\n\n`grep.rs` は正規表現コンパイル前に波括弧をサニタイズします：\n\n- 無効な繰り返しのような波括弧は、`{N}`、`{N,}`、`{N,M}` を形成できない場合にエスケープされます（`{`/`}` -> `\\{`/`\\}`）。\n- これにより、一般的なリテラルテンプレートフラグメント（例：`${platform}`）が不正な繰り返しとして失敗することを防ぎます。\n- 残りの無効な正規表現構文は正規表現エラーを返します。\n\n## 2) ファイルディスカバリー (`glob`) とファジーパス検索 (`fuzzyFind`)\n\n`glob` と `fuzzyFind` は `fs_cache` スキャンを共有しますが、マッチングロジックは異なります。\n\n### `glob` フロー\n\n1. TS ラッパー（`glob/index.ts`）：\n   - `path.resolve(options.path)`。\n   - デフォルト値：`pattern=\"*\"`、`hidden=false`、`gitignore=true`、`recursive=true`。\n2. Rust の `glob` が `GlobConfig` を構築し、`glob_util::compile_glob` を通じてパターンをコンパイル。\n3. エントリソース：\n   - `cache=true` => `get_or_scan` + オプションの stale-empty `force_rescan`。\n   - `cache=false` => `force_rescan(..., store=false)`（新規のみ）。\n4. フィルタリング：\n   - `.git` は常にスキップ。\n   - リクエストされない限り `node_modules` をスキップ（`includeNodeModules` または node_modules を含むパターン）。\n   - glob マッチを適用。\n   - ファイルタイプフィルターを適用；シンボリックリンクの `file/dir` フィルターはターゲットメタデータを解決。\n5. `maxResults` で切り捨てる前に、mtime 降順によるオプションのソート（`sortByMtime`）。\n\n### `fuzzyFind` フロー（`fd.rs` に実装）\n\n1. TS ラッパーは `grep` モジュールからエクスポートされますが、Rust の実装は `fd.rs` にあります。\n2. `fs_cache` からの共有スキャンソースで、同じキャッシュ/非キャッシュ分岐と stale-empty 再チェックポリシー。\n3. スコアリング：\n   - 完全一致 / 前方一致 / 部分一致 / サブシーケンスベースのファジースコア\n   - セパレーター/句読点で正規化されたスコアリングパス\n   - ディレクトリボーナスと決定的なタイブレーク（`score desc`、次に `path asc`）\n4. シンボリックリンクエントリはファジー結果から除外されます。\n\n### 失敗時の動作\n\n- 無効な glob パターン => `glob_util::compile_glob` からのエラー。\n- 検索ルートは既存のディレクトリである必要があります（`resolve_search_path`）。そうでない場合はエラー。\n- キャンセル/タイムアウトはループ内の `CancelToken::heartbeat()` チェックを通じてアボートエラーとして伝播します。\n\n### 不正な glob の処理\n\n`glob_util::build_glob_pattern` は寛容です：\n\n- `\\` を `/` に正規化。\n- `recursive=true` の場合、単純な再帰パターンに `**/` を自動プレフィックス。\n- コンパイル前に未閉じの `{...` 代替グループを自動的に閉じます。\n\n## 3) 共有スキャン/キャッシュライフサイクル (`fs_cache`)\n\n`fs_cache` はスキャン結果を正規化された相対エントリ（`path`、`fileType`、オプションの `mtime`）として以下のキーで保存します：\n\n- 正規化された検索ルート\n- `include_hidden`\n- `use_gitignore`\n\n### キャッシュ状態遷移\n\n1. **ミス / 無効**\n   - TTL が `0` またはキーが存在しない/期限切れ -> 新規 `collect_entries`。\n2. **ヒット**\n   - エントリ経過時間が `cache_ttl_ms()` 未満 -> キャッシュされたエントリ + `cache_age_ms` を返す。\n3. **Stale-empty 再チェック**（`glob`/`grep`/`fd` での呼び出し側ポリシー）\n   - クエリがゼロマッチで `cache_age_ms >= empty_recheck_ms()` の場合、1回の再スキャンを強制。\n4. **無効化**\n   - `invalidateFsScanCache(path?)`：\n     - 引数なし：すべてのキーをクリア\n     - パス引数：ルートがそのターゲットパスのプレフィックスであるキーを削除\n\n### Stale 結果のトレードオフ\n\n- キャッシュは即座の一貫性よりも低レイテンシの繰り返しスキャンを優先します。\n- TTL ウィンドウは stale な正/偽の結果を返す可能性があります。\n- 空結果の再チェックは、追加の1回のスキャンコストで古いキャッシュスキャンの stale な偽陰性を削減します。\n- 明示的な無効化は、ファイル変更後の正確性フックとして意図されています。\n\n## 4) ANSI テキストユーティリティ (`text`)\n\nこれらは純粋なインメモリユーティリティです（ファイルシステムスキャンなし）。\n\n### 境界と責任\n\n- **`text.rs` がターミナルセルセマンティクスを担当**：\n  - ANSI シーケンスの解析\n  - 書記素対応の幅とスライシング\n  - ラップ/切り捨て/サニタイズ動作\n- **`grep.rs` の行切り捨て（`maxColumns`）は別**：\n  - マッチした行の `...` を伴う単純な文字境界切り捨て\n  - ANSI 状態を維持せず、ターミナルセル幅を考慮しない\n\n### 主要な動作\n\n- `wrapTextWithAnsi`：可視幅で折り返し、アクティブな SGR コードを折り返された行に引き継ぎます。\n- `truncateToWidth`：省略記号ポリシー（`Unicode`、`Ascii`、`Omit`）、オプションの右パディング、変更がない場合に元の JS 文字列を返すファストパスを持つ可視セル切り捨て。\n- `sliceWithWidth`：オプションの厳密な幅制約を持つ列スライシング。\n- `extractSegments`：オーバーレイの前後のセグメントを抽出し、`after` セグメントの ANSI 状態を復元します。\n- `sanitizeText`：ANSI エスケープ + 制御文字を除去し、孤立サロゲートを削除し、`\\r` を除去して CR/LF を正規化します。\n- `visibleWidth`：可視ターミナルセルをカウントします（タブは Rust 実装の固定 `TAB_WIDTH` を使用）。\n\n### 失敗時の動作\n\nテキスト関数は一般的に決定的な変換された出力を返します。エラーは JS 文字列変換境界（N-API 引数変換の失敗）に限定されます。\n\n## 5) シンタックスハイライト (`highlight`)\n\n`highlight.rs` は純粋な変換です（FS なし、キャッシュなし）。\n\n### フロー\n\n1. ラッパーが `code`、オプションの `lang`、および ANSI カラーパレットを転送。\n2. Rust が以下の方法でシンタックスを解決：\n   - トークン/名前ルックアップ\n   - 拡張子ルックアップ\n   - エイリアステーブルフォールバック（`ts/tsx/js -> JavaScript` など）\n   - 未解決時はプレーンテキストシンタックスにフォールバック\n3. syntect の `ParseState` とスコープスタックで各行を解析。\n4. スコープを 11 のセマンティックカラーカテゴリにマップし、ANSI カラーコードを注入/リセット。\n\n### 失敗時の動作\n\n- 行ごとの解析失敗は呼び出しを失敗させません：その行はハイライトされずに追加され、処理は続行します。\n- 不明/サポートされていない言語はプレーンテキストシンタックスにフォールバックします。\n\n## 純粋ユーティリティ vs ファイルシステム依存フロー\n\n| フロー | ファイルシステムアクセス | 共有キャッシュ | 備考 |\n| --- | --- | --- | --- |\n| `searchContent` / `hasMatch` | なし | なし | 提供されたバイト/文字列に対する正規表現のみ |\n| `text` モジュール関数 | なし | なし | ANSI/幅/サニタイズのみ |\n| `highlight` モジュール関数 | なし | なし | シンタックス + ANSI カラーリングのみ |\n| `glob` | あり | オプション | ディレクトリスキャン + glob フィルタリング |\n| `fuzzyFind` | あり | オプション | ディレクトリスキャン + ファジースコアリング |\n| `grep`（ファイル/ディレクトリパス） | あり | オプション（ディレクトリモード） | ファイルに対する ripgrep、オプションのフィルター/コールバック |\n\n## エンドツーエンドライフサイクルサマリー\n\n1. 呼び出し元が型付きオプションで TS ラッパーを呼び出す。\n2. ラッパーがデフォルト値（特に `glob`）を正規化し、`native.*` エクスポートに転送。\n3. Rust がオプションを検証/正規化し、マッチャー/検索設定を構築。\n4. ファイルシステムフローでは、エントリがスキャンされ（キャッシュヒット/ミス/再スキャン）、フィルタリング/スコアリングされる。\n5. ワーカーループが定期的にキャンセルハートビートを呼び出し、タイムアウト/アボートで実行を終了可能。\n6. Rust が出力を N-API オブジェクト（`lineNumber`、`matchCount`、`limitReached` など）に整形。\n7. TS ラッパーが型付き JS オブジェクト（および `grep`/`glob` 用のオプションのマッチごとのコールバック）を返す。\n",
	"ja/natives/porting-to-natives.md": "---\ntitle: pi-natives (N-API) への移植 — フィールドノート\ndescription: Node.js の child_process とシェルコードを Rust N-API ネイティブレイヤーに移行するためのフィールドノート。\nsidebar:\n  order: 9\n  label: pi-natives への移植\ni18n:\n  sourceHash: 4f5150286535\n  translator: machine\n---\n\n# pi-natives (N-API) への移植 — フィールドノート\n\nこれは、ホットパスを `crates/pi-natives` に移動し、JS バインディングを通じて接続するための実践的なガイドです。同じ失敗を繰り返さないために存在します。\n\n## 移植すべきタイミング\n\n以下のいずれかに該当する場合に移植を行います：\n\n- ホットパスがレンダーループ、高頻度の UI 更新、または大量バッチで実行される。\n- JS のアロケーションが支配的（文字列の大量生成、正規表現のバックトラッキング、大きな配列）。\n- JS のベースラインが既にあり、両バージョンを並べてベンチマークできる。\n- 処理が CPU バウンドまたはブロッキング I/O で、libuv スレッドプール上で実行可能。\n- 処理が非同期 I/O で、Tokio のランタイム上で実行可能（例：シェル実行）。\n\nJS のみの状態や動的インポートに依存する移植は避けてください。N-API エクスポートは純粋な data-in/data-out であるべきです。長時間実行される処理は、`task::blocking`（CPU バウンド/ブロッキング I/O）または `task::future`（非同期 I/O）を通じてキャンセル機能付きで実行すべきです。\n\n## ネイティブエクスポートの構造\n\n**Rust 側：**\n\n- 実装は `crates/pi-natives/src/<module>.rs` に配置します。新しいモジュールを追加する場合は、`crates/pi-natives/src/lib.rs` に登録します。\n- `#[napi]` でエクスポートします。snake_case のエクスポートは自動的に camelCase に変換されます。明示的な `js_name` は、真のエイリアスやデフォルト以外の名前の場合にのみ使用します。構造体には `#[napi(object)]` を使用します。\n- CPU バウンドまたはブロッキング処理には `task::blocking(tag, cancel_token, work)`（`crates/pi-natives/src/task.rs` 参照）を使用します。Tokio が必要な非同期処理（例：シェルセッション）には `task::future(env, tag, work)` を使用します。`timeoutMs` や `AbortSignal` を公開する場合は `CancelToken` を渡します。\n\n**JS 側：**\n\n- `packages/natives/src/bindings.ts` にベースの `NativeBindings` インターフェースがあります。\n- `packages/natives/src/<module>/types.ts` で TS 型を定義し、宣言マージを通じて `NativeBindings` を拡張します。\n- `packages/natives/src/native.ts` は各 `<module>/types.ts` ファイルをインポートして宣言を有効化します。\n- `packages/natives/src/<module>/index.ts` は `packages/natives/src/native.ts` の `native` バインディングをラップします。\n- `packages/natives/src/native.ts` はアドオンをロードし、`validateNative` が必要なエクスポートを検証します。\n- `packages/natives/src/index.ts` は `packages/*` 内の呼び出し元向けにラッパーを再エクスポートします。\n\n## 移植チェックリスト\n\n1. **Rust 実装を追加する**\n\n- コアロジックをプレーンな Rust 関数に配置します。\n- 新しいモジュールの場合は、`crates/pi-natives/src/lib.rs` に追加します。\n- `#[napi]` でエクスポートし、デフォルトの snake_case -> camelCase マッピングの一貫性を保ちます。\n- シグネチャは所有型でシンプルに保ちます：`String`、`Vec<String>`、`Uint8Array`、または大きな文字列/バイト入力には `Either<JsString, Uint8Array>` を使用します。\n- CPU バウンドまたはブロッキング処理には `task::blocking` を、非同期処理には `task::future` を使用します。`CancelToken` を渡し、長いループ内で `heartbeat()` を呼び出します。\n\n2. **JS バインディングを接続する**\n\n- `packages/natives/src/<module>/types.ts` に型と `NativeBindings` の拡張を追加します。\n- `packages/natives/src/native.ts` で `./<module>/types` をインポートし、宣言マージを有効化します。\n- `packages/natives/src/<module>/index.ts` に `native` を呼び出すラッパーを追加します。\n- `packages/natives/src/index.ts` から再エクスポートします。\n\n3. **ネイティブバリデーションを更新する**\n\n- `validateNative`（`packages/natives/src/native.ts`）に `checkFn(\"newExport\")` を追加します。\n\n4. **ベンチマークを追加する**\n\n- ベンチマークは所有パッケージの隣に配置します（`packages/tui/bench`、`packages/natives/bench`、または `packages/coding-agent/bench`）。\n- 同一の実行で JS ベースラインとネイティブバージョンの両方を含めます。\n- `Bun.nanoseconds()` と固定のイテレーション回数を使用します。\n- ベンチマーク入力は小さく現実的に保ちます（ホットパスで実際に見られるデータ）。\n\n5. **ネイティブバイナリをビルドする**\n\n- `bun --cwd=packages/natives run build`\n- `bun --cwd=packages/natives run build` を使用し、テスト中にローダー診断が必要な場合は `PI_DEV=1` を設定します。\n\n6. **ベンチマークを実行する**\n\n- `bun run packages/<pkg>/bench/<bench>.ts`（または `bun --cwd=packages/natives run bench`）\n\n7. **使用を判断する**\n\n- ネイティブの方が遅い場合は、**JS を維持**し、ネイティブエクスポートは未使用のままにします。\n- ネイティブの方が速い場合は、呼び出し元をネイティブラッパーに切り替えます。\n\n## 問題点とその回避方法\n\n### 1) 古い `pi_natives.node` が新しいエクスポートを妨げる\n\nローダーは `packages/natives/native` 内のプラットフォームタグ付きバイナリ（`pi_natives.<platform>-<arch>.node`）を優先します。`PI_DEV=1` はローダー診断を有効にするだけで、別の dev アドオンファイル名への切り替えは行いません。フォールバックとして `pi_natives.node` もあります。コンパイル済みバイナリは `~/.xcsh/natives/<version>/pi_natives.<platform>-<arch>.node` に展開されます。これらのいずれかが古い場合、エクスポートは更新されません。\n\n**修正方法：** リビルド前に古いファイルを削除します。\n\n```bash\nrm packages/natives/native/pi_natives.linux-x64.node\nrm packages/natives/native/pi_natives.node\nbun --cwd=packages/natives run build\n```\n\nコンパイル済みバイナリを実行している場合は、キャッシュされたアドオンディレクトリを削除します：\n\n```bash\nrm -rf ~/.xcsh/natives/<version>\n```\n\n次に、バイナリにエクスポートが存在することを確認します：\n\n```bash\nbun -e 'const tag = `${process.platform}-${process.arch}`; const mod = require(`./packages/natives/native/pi_natives.${tag}.node`); console.log(Object.keys(mod).includes(\"newExport\"));'\n```\n\n### 2) `validateNative` からの「Missing exports」エラー\n\nこれは**正常な動作**です — サイレントな不整合を防ぎます。以下のようなメッセージが表示された場合：\n\n```\nNative addon missing exports ... Missing: visibleWidth\n```\n\nバイナリが古いか、Rust のエクスポート名（または使用時の明示的エイリアス）が JS の名前と一致しないか、エクスポートがコンパイルされていないことを意味します。ビルドと命名の不整合を修正してください。バリデーションを弱めてはいけません。\n\n### 3) Rust シグネチャの不整合\n\nシンプルで所有型に保ちます。`String`、`Vec<String>`、`Uint8Array` は動作します。パブリックエクスポートでは `&str` のような参照を避けてください。構造化データが必要な場合は、`#[napi(object)]` 構造体でラップします。\n\n### 4) ベンチマークの誤り\n\n- 異なる入力やアロケーションを比較しないでください。\n- JS とネイティブで同一の入力配列を使用します。\n- スキューを避けるため、両方を同じベンチマークファイルで実行します。\n\n## ベンチマークテンプレート\n\n```ts\nconst ITERATIONS = 2000;\n\nfunction bench(name: string, fn: () => void): number {\n const start = Bun.nanoseconds();\n for (let i = 0; i < ITERATIONS; i++) fn();\n const elapsed = (Bun.nanoseconds() - start) / 1e6;\n console.log(`${name}: ${elapsed.toFixed(2)}ms total (${(elapsed / ITERATIONS).toFixed(6)}ms/op)`);\n return elapsed;\n}\n\nbench(\"feature/js\", () => {\n jsImpl(sample);\n});\n\nbench(\"feature/native\", () => {\n nativeImpl(sample);\n});\n```\n\n## 検証チェックリスト\n\n- `validateNative` が通過する（エクスポートの欠落なし）。\n- `NativeBindings` が `packages/natives/src/<module>/types.ts` で拡張され、ラッパーが `packages/natives/src/index.ts` で再エクスポートされている。\n- `Object.keys(require(...))` に新しいエクスポートが含まれている。\n- ベンチマーク数値が PR/ノートに記録されている。\n- 呼び出し元の更新は、ネイティブの方が速いか同等の場合に**のみ**行われている。\n\n## 基本原則\n\n- ネイティブの方が遅い場合は、**切り替えないでください**。将来の作業のためにエクスポートは残しますが、TUI はより速いパスのままにすべきです。\n- ネイティブの方が速い場合は、呼び出し元を切り替え、リグレッションを検出するためにベンチマークを維持します。\n",
	"ja/providers/models.md": "---\ntitle: モデルおよびプロバイダー設定\ndescription: models.yml によるモデルレジストリとプロバイダー設定（ルーティング、フォールバック、料金を含む）。\nsidebar:\n  order: 1\n  label: モデルとプロバイダー\ni18n:\n  sourceHash: 8053df967ff6\n  translator: machine\n---\n\n# モデルおよびプロバイダー設定 (`models.yml`)\n\nこのドキュメントでは、コーディングエージェントが現在モデルを読み込む方法、オーバーライドを適用する方法、認証情報を解決する方法、および実行時にモデルを選択する方法について説明します。\n\n## モデルの動作を制御するもの\n\n主要な実装ファイル:\n\n- `src/config/model-registry.ts` — 組み込みモデルとカスタムモデルの読み込み、プロバイダーオーバーライド、実行時探索、認証統合\n- `src/config/model-resolver.ts` — モデルパターンの解析と initial/smol/slow モデルの選択\n- `src/config/settings-schema.ts` — モデル関連設定（`modelRoles`、プロバイダートランスポート設定）\n- `src/session/auth-storage.ts` — API キーと OAuth の解決順序\n- `packages/ai/src/models.ts` と `packages/ai/src/types.ts` — 組み込みプロバイダー/モデルと `Model`/`compat` 型\n\n## 設定ファイルの場所とレガシー動作\n\nデフォルトの設定パス:\n\n- `~/.xcsh/agent/models.yml`\n\n引き続き存在するレガシー動作:\n\n- `models.yml` が存在せず、同じ場所に `models.json` が存在する場合、`models.yml` に移行されます。\n- 明示的な `.json` / `.jsonc` 設定パスは、`ModelRegistry` にプログラム的に渡す場合も引き続きサポートされます。\n\n## `models.yml` の構造\n\n```yaml\nconfigVersion: 1  # optional — written by auto-config, used for migration detection\nproviders:\n  <provider-id>:\n    # provider-level config\nequivalence:\n  overrides:\n    <provider-id>/<model-id>: <canonical-model-id>\n  exclude:\n    - <provider-id>/<model-id>\n```\n\n`configVersion` は、自動設定システムによって書き込まれるオプションの整数です。存在する場合、xcsh はこれを使用して古い設定を検出し、自動的にアップグレードします。\n\n`provider-id` は、選択と認証ルックアップ全体で使用される標準的なプロバイダーキーです。\n\n`equivalence` はオプションであり、具体的なプロバイダーモデルの上に標準的なモデルグループを設定します:\n\n- `overrides` は正確な具体的セレクター（`provider/modelId`）を公式のアップストリーム標準 id にマッピングします\n- `exclude` は具体的なセレクターを標準グループから除外します\n\n## プロバイダーレベルのフィールド\n\n```yaml\nproviders:\n  my-provider:\n    baseUrl: https://api.example.com/v1\n    apiKey: MY_PROVIDER_API_KEY\n    api: openai-completions\n    headers:\n      X-Team: platform\n    authHeader: true\n    auth: apiKey\n    discovery:\n      type: ollama\n    modelOverrides:\n      some-model-id:\n        name: Renamed model\n    models:\n      - id: some-model-id\n        name: Some Model\n        api: openai-completions\n        reasoning: false\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 128000\n        maxTokens: 16384\n        headers:\n          X-Model: value\n        compat:\n          supportsStore: true\n          supportsDeveloperRole: true\n          supportsReasoningEffort: true\n          maxTokensField: max_completion_tokens\n          openRouterRouting:\n            only: [anthropic]\n          vercelGatewayRouting:\n            order: [anthropic, openai]\n          extraBody:\n            gateway: m1-01\n            controller: mlx\n```\n\n### 使用可能なプロバイダー/モデルの `api` 値\n\n- `openai-completions`\n- `openai-responses`\n- `openai-codex-responses`\n- `azure-openai-responses`\n- `anthropic-messages`\n- `google-generative-ai`\n- `google-vertex`\n\n### 使用可能な auth/discovery 値\n\n- `auth`: `apiKey`（デフォルト）または `none`\n- `discovery.type`: `ollama`\n\n## 検証ルール（現在）\n\n### 完全なカスタムプロバイダー（`models` が空でない場合）\n\n必須:\n\n- `baseUrl`\n- `auth: none` でない限り `apiKey`\n- プロバイダーレベルまたは各モデルに `api`\n\n### オーバーライドのみのプロバイダー（`models` が欠落または空の場合）\n\n以下のうち少なくとも1つを定義する必要があります:\n\n- `baseUrl`\n- `modelOverrides`\n- `discovery`\n\n### Discovery\n\n- `discovery` にはプロバイダーレベルの `api` が必要です。\n\n### モデル値のチェック\n\n- `id` は必須\n- `contextWindow` と `maxTokens` は、指定する場合は正の値である必要があります\n\n## マージとオーバーライドの順序\n\nModelRegistry パイプライン（更新時）:\n\n1. `@f5-sales-demo/pi-ai` から組み込みプロバイダー/モデルを読み込む。\n2. `models.yml` カスタム設定を読み込む。\n3. プロバイダーオーバーライド（`baseUrl`、`headers`）を組み込みモデルに適用する。\n4. `modelOverrides`（プロバイダー + モデル id ごと）を適用する。\n5. カスタム `models` をマージする:\n   - 同じ `provider + id` は既存のものを置き換える\n   - それ以外の場合は追加\n6. 実行時に発見されたモデル（現在は Ollama と LM Studio）を適用し、モデルオーバーライドを再適用する。\n\n## 標準モデルの等価性とまとめ\n\nレジストリはすべての具体的なプロバイダーモデルを保持し、その上に標準レイヤーを構築します。\n\n標準 id は公式のアップストリーム id のみです。例:\n\n- `claude-opus-4-6`\n- `claude-haiku-4-5`\n- `gpt-5.3-codex`\n\n### `models.yml` の equivalence 設定\n\n例:\n\n```yaml\nproviders:\n  zenmux:\n    baseUrl: https://api.zenmux.example/v1\n    apiKey: ZENMUX_API_KEY\n    api: openai-codex-responses\n    models:\n      - id: codex\n        name: Zenmux Codex\n        reasoning: true\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 200000\n        maxTokens: 32768\n\nequivalence:\n  overrides:\n    zenmux/codex: gpt-5.3-codex\n    p-codex/codex: gpt-5.3-codex\n  exclude:\n    - demo/codex-preview\n```\n\n標準グループのビルド順序:\n\n1. `equivalence.overrides` からの正確なユーザーオーバーライド\n2. 組み込みモデルメタデータからのバンドルされた公式 id との一致\n3. ゲートウェイ/プロバイダーバリアントの保守的なヒューリスティック正規化\n4. 具体的なモデル自身の id へのフォールバック\n\n現在のヒューリスティックは意図的に狭い範囲に限定されています:\n\n- 埋め込まれたアップストリームプレフィックスは、存在する場合に削除できます（例: `anthropic/...` または `openai/...`）\n- ドット区切りおよびダッシュ区切りのバージョンバリアントは、既存の公式 id にマッピングされる場合にのみ正規化できます（例: `4.6 -> 4-6`）\n- 曖昧なファミリーやバージョンは、バンドルされた一致または明示的なオーバーライドなしにはマージされません\n\n### 標準解決の動作\n\n複数の具体的なバリアントが標準 id を共有する場合、解決には以下が使用されます:\n\n1. 可用性と認証\n2. `config.yml` の `modelProviderOrder`\n3. `modelProviderOrder` が設定されていない場合は既存のレジストリ/プロバイダーの順序\n\n無効または未認証のプロバイダーはスキップされます。\n\nセッション状態とトランスクリプトは、実際にターンを実行した具体的なプロバイダー/モデルを引き続き記録します。\n\nプロバイダーデフォルトとモデルごとのオーバーライド:\n\n- プロバイダーの `headers` はベースラインです。\n- モデルの `headers` はプロバイダーのヘッダーキーをオーバーライドします。\n- `modelOverrides` はモデルメタデータ（`name`、`reasoning`、`input`、`cost`、`contextWindow`、`maxTokens`、`headers`、`compat`、`contextPromotionTarget`）をオーバーライドできます。\n- `compat` はネストされたルーティングブロック（`openRouterRouting`、`vercelGatewayRouting`、`extraBody`）に対してディープマージされます。\n\n## 実行時探索の統合\n\n### 暗黙的な Ollama 探索\n\n`ollama` が明示的に設定されていない場合、レジストリは暗黙的な探索可能プロバイダーを追加します:\n\n- プロバイダー: `ollama`\n- api: `openai-completions`\n- ベース URL: `OLLAMA_BASE_URL` または `http://127.0.0.1:11434`\n- 認証モード: キーなし（`auth: none` の動作）\n\n実行時探索は Ollama の `GET /api/tags` を呼び出し、ローカルのデフォルトでモデルエントリーを合成します。\n\n### 暗黙的な llama.cpp 探索\n\n`llama.cpp` が明示的に設定されていない場合、レジストリは暗黙的な探索可能プロバイダーを追加します:\n注意: openai-completions の代わりに新しい anthropic messages api を使用しています。\n\n- プロバイダー: `llama.cpp`\n- api: `openai-responses`\n- ベース URL: `LLAMA_CPP_BASE_URL` または `http://127.0.0.1:8080`\n- 認証モード: キーなし（`auth: none` の動作）\n\n実行時探索は llama.cpp の `GET models` を呼び出し、ローカルのデフォルトでモデルエントリーを合成します。\n\n### 暗黙的な LM Studio 探索\n\n`lm-studio` が明示的に設定されていない場合、レジストリは暗黙的な探索可能プロバイダーを追加します:\n\n- プロバイダー: `lm-studio`\n- api: `openai-completions`\n- ベース URL: `LM_STUDIO_BASE_URL` または `http://127.0.0.1:1234/v1`\n- 認証モード: キーなし（`auth: none` の動作）\n\n実行時探索はモデルを取得し（`GET /models`）、ローカルのデフォルトでモデルエントリーを合成します。\n\n### 明示的なプロバイダー探索\n\n探索を自分で設定できます:\n\n```yaml\nproviders:\n  ollama:\n    baseUrl: http://127.0.0.1:11434\n    api: openai-completions\n    auth: none\n    discovery:\n      type: ollama\n      \n  llama.cpp:\n    baseUrl: http://127.0.0.1:8080\n    api: openai-responses\n    auth: none\n    discovery:\n      type: llama.cpp\n```\n\n### 拡張プロバイダーの登録\n\n拡張機能は実行時にプロバイダーを登録できます（`pi.registerProvider(...)`）。以下を含みます:\n\n- プロバイダーのモデル置換/追加\n- 新しい API ID のカスタムストリームハンドラー登録\n- カスタム OAuth プロバイダーの登録\n\n## 認証と API キーの解決順序\n\nプロバイダーのキーを要求する際の有効な順序:\n\n1. 実行時オーバーライド（CLI `--api-key`）\n2. `agent.db` に保存された API キー認証情報\n3. `agent.db` に保存された OAuth 認証情報（更新あり）\n4. 環境変数マッピング（`OPENAI_API_KEY`、`ANTHROPIC_API_KEY` など）\n5. ModelRegistry フォールバックリゾルバー（`models.yml` のプロバイダー `apiKey`、環境変数名またはリテラルのセマンティクス）\n\n`models.yml` の `apiKey` の動作:\n\n- 値は最初に環境変数名として処理されます。\n- 環境変数が存在しない場合、リテラル文字列がトークンとして使用されます。\n\n`authHeader: true` でプロバイダーの `apiKey` が設定されている場合、モデルは以下を受け取ります:\n\n- `Authorization: Bearer <resolved-key>` ヘッダーが注入されます。\n\nキーなしプロバイダー:\n\n- `auth: none` でマークされたプロバイダーは、認証情報なしで利用可能として扱われます。\n- `getApiKey*` はそれらに対して `kNoAuth` を返します。\n\n## モデルの可用性とすべてのモデル\n\n- `getAll()` は読み込まれたモデルレジストリ（組み込み + マージされたカスタム + 探索済み）を返します。\n- `getAvailable()` は、キーなしまたは解決可能な認証を持つモデルにフィルタリングします。\n\nしたがって、モデルはレジストリに存在していても、認証が利用可能になるまで選択できない場合があります。\n\n## 実行時モデル解決\n\n### CLI とパターン解析\n\n`model-resolver.ts` がサポートするもの:\n\n- 正確な `provider/modelId`\n- 正確な標準モデル id\n- 正確なモデル id（プロバイダーは推論される）\n- ファジー/部分文字列マッチング\n- `--models` のグロブスコープパターン（例: `openai/*`、`*sonnet*`）\n- オプションの `:thinkingLevel` サフィックス（`off|minimal|low|medium|high|xhigh`）\n\n`--provider` はレガシーです。`--model` が推奨されます。\n\n正確なセレクターの解決優先度:\n\n1. 正確な `provider/modelId` はまとめをバイパスします\n2. 正確な標準 id は標準インデックスを通じて解決されます\n3. 正確なベアの具体的 id も機能します\n4. ファジーとグロブマッチングは正確なパスの後に実行されます\n\n### 初期モデル選択の優先度\n\n`findInitialModel(...)` は次の順序を使用します:\n\n1. 明示的な CLI プロバイダー + モデル\n2. 最初のスコープ付きモデル（再開しない場合）\n3. 保存されたデフォルトのプロバイダー/モデル\n4. 利用可能なモデルの中の既知のプロバイダーデフォルト（例: OpenAI/Anthropic など）\n5. 最初の利用可能なモデル\n\n### ロールエイリアスと設定\n\nサポートされているモデルロール:\n\n- `default`、`smol`、`slow`、`plan`、`commit`\n\n`pi/smol` などのロールエイリアスは `settings.modelRoles` を通じて展開されます。各ロール値には、`:minimal`、`:low`、`:medium`、`:high` などのシンキングセレクターを追加することもできます。\n\nロールが別のロールを指している場合、ターゲットモデルは通常通り継承され、参照するロールの明示的なサフィックスがそのロール固有の使用に勝ちます。\n\n関連する設定:\n\n- `modelRoles`（レコード）\n- `enabledModels`（スコープ付きパターンリスト）\n- `modelProviderOrder`（グローバルな標準プロバイダー優先度）\n- `providers.kimiApiFormat`（`openai` または `anthropic` リクエスト形式）\n- `providers.openaiWebsockets`（OpenAI Codex トランスポートの `auto|off|on` WebSocket 設定）\n\n`modelRoles` には以下のどちらかを格納できます:\n\n- 具体的なプロバイダーバリアントを固定する `provider/modelId`\n- プロバイダーのまとめを許可する `gpt-5.3-codex` などの標準 id\n\n`enabledModels` と CLI の `--models` の場合:\n\n- 正確な標準 id はその標準グループ内のすべての具体的なバリアントに展開されます\n- 明示的な `provider/modelId` エントリーは正確なまま維持されます\n- グロブとファジーマッチングは具体的なモデルに対して引き続き機能します\n\n## `/model` と `--list-models`\n\n両方のサーフェスでプロバイダープレフィックス付きモデルが表示可能で選択可能な状態を維持します。\n\nまた、標準/まとめられたモデルも公開されるようになりました:\n\n- `/model` はプロバイダータブの横に標準ビューを含みます\n- `--list-models` は標準セクションと具体的なプロバイダー行を印刷します\n\n標準エントリーを選択すると標準セレクターが保存されます。プロバイダー行を選択すると明示的な `provider/modelId` が保存されます。\n\n## コンテキストプロモーション（モデルレベルのフォールバックチェーン）\n\nコンテキストプロモーションは、API がコンテキスト長エラーでリクエストを拒否した場合に、より大きなコンテキストの兄弟モデルに自動的に昇格する、小さなコンテキストバリアント（例: `*-spark`）のオーバーフロー回復メカニズムです。\n\n### トリガーと順序\n\nコンテキストオーバーフローエラー（例: `context_length_exceeded`）でターンが失敗した場合、`AgentSession` はコンパクションにフォールバックする**前に**プロモーションを試みます:\n\n1. `contextPromotion.enabled` が true の場合、プロモーションターゲットを解決します（以下を参照）。\n2. ターゲットが見つかった場合、それに切り替えてリクエストを再試行します — コンパクションは不要です。\n3. ターゲットが利用できない場合、現在のモデルで自動コンパクションにフォールスルーします。\n\n### ターゲット選択\n\n選択はロール駆動ではなくモデル駆動です:\n\n1. `currentModel.contextPromotionTarget`（設定されている場合）\n2. 同じプロバイダー + API 上の最小の大きなコンテキストモデル\n\n認証情報が解決しない場合（`ModelRegistry.getApiKey(...)`）、候補は無視されます。\n\n### OpenAI Codex WebSocket ハンドオフ\n\n`openai-codex-responses` との間で切り替える場合、セッションプロバイダーの状態キー `openai-codex-responses` がモデル切り替え前に閉じられます。これにより WebSocket トランスポートの状態がドロップされ、次のターンがプロモートされたモデルでクリーンな状態で開始されます。\n\n### 永続性の動作\n\nプロモーションは一時的な切り替えを使用します（`setModelTemporary`）:\n\n- セッション履歴に一時的な `model_change` として記録されます\n- 保存されたロールマッピングは書き換えられません\n\n### 明示的なフォールバックチェーンの設定\n\n`contextPromotionTarget` を通じてモデルメタデータに直接フォールバックを設定します。\n\n`contextPromotionTarget` には以下のどちらかを指定できます:\n\n- `provider/model-id`（明示的）\n- `model-id`（現在のプロバイダー内で解決）\n\nSpark -> 同じプロバイダーの非 Spark への例（`models.yml`）:\n\n```yaml\nproviders:\n  openai-codex:\n    modelOverrides:\n      gpt-5.3-codex-spark:\n        contextPromotionTarget: openai-codex/gpt-5.3-codex\n```\n\n組み込みモデルジェネレーターも、同じプロバイダーのベースモデルが存在する場合、`*-spark` モデルに対して自動的にこれを割り当てます。\n\n## 互換性とルーティングフィールド\n\n`models.yml` は以下の `compat` サブセットをサポートしています:\n\n- `supportsStore`\n- `supportsDeveloperRole`\n- `supportsReasoningEffort`\n- `maxTokensField`（`max_completion_tokens` または `max_tokens`）\n- `openRouterRouting.only` / `openRouterRouting.order`\n- `vercelGatewayRouting.only` / `vercelGatewayRouting.order`\n\nこれらは OpenAI 完了トランスポートロジックによって消費され、URL ベースの自動検出と組み合わされます。\n\n## 実践的な例\n\n### ローカルの OpenAI 互換エンドポイント（認証なし）\n\n```yaml\nproviders:\n  local-openai:\n    baseUrl: http://127.0.0.1:8000/v1\n    auth: none\n    api: openai-completions\n    models:\n      - id: Qwen/Qwen2.5-Coder-32B-Instruct\n        name: Qwen 2.5 Coder 32B (local)\n```\n\n### 環境変数ベースのキーを持つホステッドプロキシ\n\n```yaml\nproviders:\n  anthropic-proxy:\n    baseUrl: https://proxy.example.com/anthropic\n    apiKey: ANTHROPIC_PROXY_API_KEY\n    api: anthropic-messages\n    authHeader: true\n    models:\n      - id: claude-sonnet-4-20250514\n        name: Claude Sonnet 4 (Proxy)\n        reasoning: true\n        input: [text, image]\n```\n\n### 組み込みプロバイダールート + モデルメタデータのオーバーライド\n\n```yaml\nproviders:\n  openrouter:\n    baseUrl: https://my-proxy.example.com/v1\n    headers:\n      X-Team: platform\n    modelOverrides:\n      anthropic/claude-sonnet-4:\n        name: Sonnet 4 (Corp)\n        compat:\n          openRouterRouting:\n            only: [anthropic]\n```\n\n## LiteLLM プロキシの自動設定\n\n`LITELLM_BASE_URL` と `LITELLM_API_KEY` の両方の環境変数が設定されている場合、xcsh は LiteLLM プロキシの `models.yml` 設定を自動的に管理します。\n\n### 初回実行時の自動生成\n\n`models.yml` が存在せず、LiteLLM 環境変数が検出された場合、xcsh は自動的に生成します:\n\n```yaml\n# Auto-generated by xcsh for LiteLLM proxy\n# API key resolved from LITELLM_API_KEY env var at runtime\nconfigVersion: 1\nproviders:\n  anthropic:\n    baseUrl: \"https://your-litellm-proxy.example.com/anthropic\"\n    apiKey: LITELLM_API_KEY\n```\n\nデフォルトの `config.yml` も適切なイメージプロバイダー設定で生成されます。\n\n### 起動時の自己修復\n\n起動ごとに、モデルレジストリの `startupHealthCheck()` が以下のチェックを実行します:\n\n| 条件 | アクション |\n|-----------|--------|\n| `models.yml` が存在しない | 環境変数から自動生成 |\n| `models.yml` が破損または解析不能 | `.bak` にバックアップし、再生成 |\n| `baseUrl` が `LITELLM_BASE_URL` と一致しない | `.bak` にバックアップし、新しい URL で再生成 |\n| `configVersion` が欠落または古い | `.bak` にバックアップし、現在のバージョンで再生成 |\n| 設定が正常 | アクションなし |\n\nすべての修復は上書き前に `.bak` バックアップを作成します。すべての操作は冪等です。\n\n### CLI コマンド\n\n```bash\nxcsh setup litellm              # Generate or fix LiteLLM config\nxcsh setup litellm --check      # Validate without writing\nxcsh setup litellm --check --json  # Machine-readable validation output\n```\n\n### 必須環境変数\n\n| 変数 | 目的 |\n|----------|---------|\n| `LITELLM_BASE_URL` | LiteLLM プロキシ URL（例: `https://your-proxy.example.com`）。`http://` または `https://` で始まる必要があります。 |\n| `LITELLM_API_KEY` | プロキシの API キー。生成された設定では名前で参照され、実行時に解決されます。 |\n\nいずれかの変数が設定されていない場合、自動設定は静かにスキップされます。\n\n### 設定バージョン管理\n\n生成された設定には `configVersion` フィールドが含まれます。将来のリリースで生成形式が変更された場合、xcsh は古い設定を検出し、自動的にアップグレードします（バックアップあり）。\n\n## レガシーコンシューマーの注意事項\n\nほとんどのモデル設定は、`ModelRegistry` を通じて `models.yml` を経由するようになりました。\n\n注目すべきレガシーパスが1つ残っています: Web 検索の Anthropic 認証解決は、`src/web/search/auth.ts` で `~/.xcsh/agent/models.json` を直接読み取ります。\n\nこの特定のパスに依存している場合は、そのモジュールが移行されるまで JSON 互換性を念頭に置いてください。\n\n## 失敗モード\n\n`models.yml` がスキーマまたは検証チェックに失敗した場合:\n\n- `LITELLM_BASE_URL` と `LITELLM_API_KEY` が設定されている場合、起動時ヘルスチェックが自動修復を試みます（破損したファイルをバックアップし、環境変数から再生成）。修復が成功した場合、レジストリは修正された設定を再読み込みします。\n- 自動修復が不可能な場合（環境変数が未設定、書き込み失敗）、レジストリは組み込みモデルで動作し続けます。\n- エラーは `ModelRegistry.getError()` を通じて公開され、UI/通知に表示されます。\n",
	"ja/providers/provider-streaming-internals.md": "---\ntitle: プロバイダーストリーミング内部実装\ndescription: SSEパース、トークンカウント、バックプレッシャー処理を含むプロバイダーストリーミング実装。\nsidebar:\n  order: 2\n  label: ストリーミング内部実装\ni18n:\n  sourceHash: a32ffa769c4d\n  translator: machine\n---\n\n# プロバイダーストリーミング内部実装\n\nこのドキュメントでは、`@f5-sales-demo/pi-ai` においてトークン/ツールストリーミングがどのように正規化され、`@f5-sales-demo/pi-agent-core` および `coding-agent` セッションイベントを通じて伝播されるかを説明します。\n\n## エンドツーエンドのフロー\n\n1. `streamSimple()`（`packages/ai/src/stream.ts`）は汎用オプションをマップし、プロバイダーストリーム関数にディスパッチします。\n2. プロバイダーストリーム関数（`anthropic.ts`、`openai-responses.ts`、`google.ts`）は、プロバイダーネイティブのストリームイベントを統一された `AssistantMessageEvent` シーケンスに変換します。\n3. 各プロバイダーはイベントを `AssistantMessageEventStream`（`packages/ai/src/utils/event-stream.ts`）にプッシュします。これはデルタイベントをスロットリングし、以下を公開します：\n   - インクリメンタル更新のための非同期イテレーション\n   - 最終的な `AssistantMessage` のための `result()`\n4. `agentLoop`（`packages/agent/src/agent-loop.ts`）はこれらのイベントを消費し、処理中のアシスタント状態を変更して、生の `assistantMessageEvent` を含む `message_update` イベントを発行します。\n5. `AgentSession`（`packages/coding-agent/src/session/agent-session.ts`）はエージェントイベントをサブスクライブし、メッセージを永続化し、拡張フックを駆動し、セッション動作（リトライ、コンパクション、TTSR、ストリーミング編集中断チェック）を適用します。\n\n## `@f5-sales-demo/pi-ai` における統一ストリームコントラクト\n\nすべてのプロバイダーは同一の形状（`packages/ai/src/types.ts` の `AssistantMessageEvent`）を出力します：\n\n- `start`\n- コンテンツブロックのライフサイクルトリプレット：\n  - テキスト：`text_start` → `text_delta`* → `text_end`\n  - シンキング：`thinking_start` → `thinking_delta`* → `thinking_end`\n  - ツールコール：`toolcall_start` → `toolcall_delta`* → `toolcall_end`\n- ターミナルイベント：\n  - `done`（`reason: \"stop\" | \"length\" | \"toolUse\"` を含む）\n  - または `error`（`reason: \"aborted\" | \"error\"` を含む）\n\n`AssistantMessageEventStream` が保証する内容：\n\n- 最終結果はターミナルイベント（`done` または `error`）によって解決される\n- デルタはバッチ処理/スロットリングされる（約50ms）\n- バッファリングされたデルタは非デルタイベントの前および完了前にフラッシュされる\n\n## デルタスロットリングと調和動作\n\n`AssistantMessageEventStream` は `text_delta`、`thinking_delta`、`toolcall_delta` をマージ可能なイベントとして扱います：\n\n- バッファリングされたデルタは **type + contentIndex** が一致する場合のみマージされる\n- マージでは最新の `partial` スナップショットが保持される\n- 非デルタイベントは即時フラッシュを強制する\n\nこれにより、TUI/イベントコンシューマーに対して高頻度のプロバイダーストリームが平滑化されますが、プロバイダーのバックプレッシャーではありません：プロバイダーは依然としてフルスピードで生産しており、ローカルストリームがバッファリングします。\n\n## プロバイダー正規化の詳細\n\n## Anthropic (`anthropic-messages`)\n\nソース：`packages/ai/src/providers/anthropic.ts`\n\n正規化ポイント：\n\n- `message_start` は使用量（入力/出力/キャッシュトークン）を初期化する\n- `content_block_start` はテキスト/シンキング/ツールコール開始にマップされる\n- `content_block_delta` のマッピング：\n  - `text_delta` → `text_delta`\n  - `thinking_delta` → `thinking_delta`\n  - `input_json_delta` → `toolcall_delta`\n  - `signature_delta` は `thinkingSignature` のみを更新する（イベントなし）\n- `content_block_stop` は対応する `*_end` を発行する\n- `message_delta.stop_reason` は `mapStopReason()` を介してマップされる\n\nツールコール引数ストリーミング：\n\n- 各ツールブロックは内部 `partialJson` を保持する\n- 各JSONデルタは `partialJson` に追記される\n- `arguments` はデルタごとに `parseStreamingJson()` を介して再パースされる\n- `toolcall_end` はもう一度パースしてから `partialJson` を除去する\n\n## OpenAI Responses (`openai-responses`)\n\nソース：`packages/ai/src/providers/openai-responses.ts`\n\n正規化ポイント：\n\n- `response.output_item.added` はリーズニング/テキスト/ファンクションコールブロックを開始する\n- リーズニングサマリーイベント（`response.reasoning_summary_text.delta`）は `thinking_delta` になる\n- 出力/拒否デルタは `text_delta` になる\n- `response.function_call_arguments.delta` は `toolcall_delta` になる\n- `response.output_item.done` は `thinking_end` / `text_end` / `toolcall_end` を発行する\n- `response.completed` はステータスをストップ理由と使用量にマップする\n\nツールコール引数ストリーミング：\n\n- Anthropic と同じ `partialJson` 蓄積パターン\n- `response.function_call_arguments.done` のみを送信するプロバイダーでも最終引数を設定できる\n- ツールコールIDは `\"<call_id>|<item_id>\"` として正規化される\n\n## Google Generative AI (`google-generative-ai`)\n\nソース：`packages/ai/src/providers/google.ts`\n\n正規化ポイント：\n\n- `candidate.content.parts` をイテレートする\n- テキストパーツは `isThinkingPart(part)` によってシンキングとテキストに分割される\n- ブロック遷移は新しいブロックを開始する前に前のブロックを閉じる\n- `part.functionCall` は完全なツールコールとして扱われる（start/delta/end が即座に発行される）\n- フィニッシュ理由は `google-shared.ts` の `mapStopReason()` によってマップされる\n\nツールコール引数ストリーミング：\n\n- ファンクションコール引数はインクリメンタルなJSONテキストではなく、構造化オブジェクトとして届く\n- 実装は `JSON.stringify(arguments)` を含む1つの合成 `toolcall_delta` を発行する\n- このパスではGoogleに対して部分的なJSONパーサーは不要\n\n## ツールコール部分JSONの蓄積とリカバリー\n\nAnthropic/OpenAI Responses の共通動作では `parseStreamingJson()`（`packages/ai/src/utils/json-parse.ts`）を使用します：\n\n1. `JSON.parse` を試みる\n2. 不完全なフラグメントに対して `partial-json` パーサーにフォールバックする\n3. 両方が失敗した場合、`{}` を返す\n\n影響：\n\n- 不正または切り捨てられた引数デルタは即座にストリーム処理をクラッシュさせない\n- 処理中の `arguments` は一時的に `{}` になる可能性がある\n- 後続の有効なデルタは、すべての追記でパースが再試行されるため、構造化された引数をリカバリーできる\n- 最終的な `toolcall_end` は発行前にもう一度パースを試みる\n\n## ストップ理由とトランスポート/ランタイムエラー\n\nプロバイダーのストップ理由は正規化された `stopReason` にマップされます：\n\n- Anthropic：`end_turn`→`stop`、`max_tokens`→`length`、`tool_use`→`toolUse`、安全性/拒否ケース→`error`\n- OpenAI Responses：`completed`→`stop`、`incomplete`→`length`、`failed/cancelled`→`error`\n- Google：`STOP`→`stop`、`MAX_TOKENS`→`length`、安全性/禁止/不正なファンクションコールクラス→`error`\n\nエラーセマンティクスは2段階に分かれています：\n\n1. **モデル完了セマンティクス**（プロバイダーが報告したフィニッシュ理由/ステータス）\n2. **トランスポート/ランタイム障害**（ネットワーク/クライアント/パーサー/中断例外）\n\nプロバイダーストリームがスローまたは障害を通知した場合、各プロバイダーラッパーはこれをキャッチし、以下を含むターミナル `error` イベントを発行します：\n\n- 中断シグナルが設定されている場合：`stopReason = \"aborted\"`\n- それ以外の場合：`stopReason = \"error\"`\n- `errorMessage = formatErrorMessageWithRetryAfter(error)`\n\n## 不正なチャンク / SSEパース失敗の動作\n\nこれらのプロバイダーパスでは、チャンク/SSEフレーミングはベンダーSDKストリーム（Anthropic SDK、OpenAI SDK、Google SDK）によって処理されます。このコードではカスタムSSEデコーダーは実装していません。\n\n現在の実装における観察された動作：\n\n- SDKレベルでの不正なチャンク/SSEパースは、例外またはストリームの `error` イベントとして表面化する\n- プロバイダーラッパーはそれを統一されたターミナル `error` イベントに変換する\n- ストリーム関数自体にはプロバイダー固有の再開/リトライは存在しない\n- より高いレベルのリトライは `AgentSession` の自動リトライロジックで処理される（メッセージレベルのリトライであり、ストリームチャンクの再生ではない）\n\n## キャンセルの境界\n\nキャンセルは階層化されています：\n\n- AIプロバイダーリクエスト：`options.signal` はプロバイダークライアントのストリームコールに渡される。\n- プロバイダーラッパー：ストリームループの後、中断されたシグナルはエラーパス（`\"Request was aborted\"`）を強制する。\n- エージェントループ：各プロバイダーイベントを処理する前に `signal.aborted` を確認し、最新のパーシャルから中断されたアシスタントメッセージを合成できる。\n- セッション/エージェントコントロール：`AgentSession.abort()` → `agent.abort()` → 共有中断コントローラーのキャンセル。\n\nツール実行のキャンセルはモデルストリームのキャンセルとは別です：\n\n- ツールランナーは `AbortSignal.any([agentSignal, steeringAbortSignal])` を使用する\n- ステアリング割り込みは、既に生成されたツール結果を保持しながら残りのツール実行を中断できる\n\n## バックプレッシャーの境界\n\nプロバイダーSDKストリームとダウンストリームコンシューマーの間にはハードなバックプレッシャーメカニズムはありません：\n\n- `EventStream` は最大サイズのないインメモリキューを使用する\n- スロットリングはUIの更新レートを低下させるが、プロバイダーの取り込みを遅くしない\n- コンシューマーが大幅に遅延した場合、キューに入れられたイベントは完了まで増加し続ける可能性がある\n\n現在の設計は、バウンデッドバッファのフロー制御よりも応答性とシンプルな順序付けを優先しています。\n\n## ストリームイベントがエージェント/セッションイベントとして表面化する方法\n\n`agentLoop.streamAssistantResponse()` は `AssistantMessageEvent` を `AgentEvent` にブリッジします：\n\n- `start` 時：プレースホルダーのアシスタントメッセージをプッシュし、`message_start` を発行する\n- ブロックイベント（`text_*`、`thinking_*`、`toolcall_*`）時：最後のアシスタントメッセージを更新し、生の `assistantMessageEvent` を含む `message_update` を発行する\n- ターミナル（`done`/`error`）時：`response.result()` から最終メッセージを解決し、`message_end` を発行する\n\n`AgentSession` はこれらのイベントをセッションレベルの動作のために消費します：\n\n- TTSRは `text_delta` と `toolcall_delta` のために `message_update.assistantMessageEvent` を監視する\n- ストリーミング編集ガードは `edit` コールでの `toolcall_delta`/`toolcall_end` を検査し、早期に中断できる\n- 永続化は `message_end` で完成したメッセージを書き込む\n- 自動リトライはアシスタントの `stopReason === \"error\"` と `errorMessage` ヒューリスティックを検査する\n\n## 統一とプロバイダー固有の責務\n\n統一（共通コントラクト）：\n\n- イベント形状（`AssistantMessageEvent`）\n- 最終結果の抽出（`done`/`error`）\n- デルタスロットリング + マージルール\n- エージェント/セッションイベント伝播モデル\n\nプロバイダー固有（完全には抽象化されていない）：\n\n- アップストリームのイベント分類とマッピングロジック\n- ストップ理由の変換テーブル\n- ツールコールIDの規約\n- リーズニング/シンキングブロックのセマンティクスとシグネチャ\n- 使用量トークンのセマンティクスと利用可能タイミング\n- APIごとのメッセージ変換制約\n\n## 実装ファイル\n\n- [`../../ai/src/stream.ts`](../../packages/ai/src/stream.ts) — プロバイダーディスパッチ、オプションマッピング、APIキー/セッションの配管。\n- [`../../ai/src/utils/event-stream.ts`](../../packages/ai/src/utils/event-stream.ts) — 汎用ストリームキューとアシスタントデルタスロットリング。\n- [`../../ai/src/utils/json-parse.ts`](../../packages/ai/src/utils/json-parse.ts) — ストリーミングされたツール引数の部分的なJSONパース。\n- [`../../ai/src/providers/anthropic.ts`](../../packages/ai/src/providers/anthropic.ts) — AnthropicイベントのTranslationとツールJSONデルタの蓄積。\n- [`../../ai/src/providers/openai-responses.ts`](../../packages/ai/src/providers/openai-responses.ts) — OpenAI Responsesイベントの変換とステータスマッピング。\n- [`../../ai/src/providers/google.ts`](../../packages/ai/src/providers/google.ts) — Geminiストリームチャンクからブロックへの変換。\n- [`../../ai/src/providers/google-shared.ts`](../../packages/ai/src/providers/google-shared.ts) — Geminiフィニッシュ理由マッピングと共有変換ルール。\n- [`../../agent/src/agent-loop.ts`](../../packages/agent/src/agent-loop.ts) — プロバイダーストリームの消費と `message_update` のブリッジ。\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — ストリーミング更新、中断、リトライ、永続化のセッションレベルの処理。\n",
	"ja/providers/python-repl.md": "---\ntitle: Python ツールと IPython ランタイム\ndescription: IPython カーネル管理、実行、および出力キャプチャを備えた Python REPL ツールランタイム。\nsidebar:\n  order: 3\n  label: Python と IPython\ni18n:\n  sourceHash: 70f0a034ecef\n  translator: machine\n---\n\n# Python ツールと IPython ランタイム\n\nこのドキュメントでは、`packages/coding-agent` における現在の Python 実行スタックについて説明します。\nツールの動作、カーネル/ゲートウェイのライフサイクル、環境処理、実行セマンティクス、出力レンダリング、および運用上の障害モードを扱います。\n\n## スコープと主要ファイル\n\n- ツールサーフェス: `src/tools/python.ts`\n- セッション/呼び出しごとのカーネルオーケストレーション: `src/ipy/executor.ts`\n- カーネルプロトコル + ゲートウェイ統合: `src/ipy/kernel.ts`\n- 共有ローカルゲートウェイコーディネーター: `src/ipy/gateway-coordinator.ts`\n- ユーザー起動の Python 実行向けインタラクティブモードレンダラー: `src/modes/components/python-execution.ts`\n- ランタイム/環境フィルタリングおよび Python 解決: `src/ipy/runtime.ts`\n\n## Python ツールとは\n\n`python` ツールは、1 つ以上の Python セルを Jupyter Kernel Gateway バックエンドのカーネル経由で実行します（セルごとに `python -c` を直接スポーンするわけではありません）。\n\nツールパラメーター:\n\n```ts\n{\n  cells: Array<{ code: string; title?: string }>;\n  timeout?: number; // 秒単位、1..600 にクランプ、デフォルト 30\n  cwd?: string;\n  reset?: boolean; // 最初のセルのみカーネルをリセット\n}\n```\n\nツールはセッションに対して `concurrency = \"exclusive\"` であるため、呼び出しは重複しません。\n\n## ゲートウェイのライフサイクル\n\n### モード\n\nゲートウェイのパスは 2 つあります:\n\n1. **外部ゲートウェイ** (`PI_PYTHON_GATEWAY_URL` が設定されている場合)\n   - 設定された URL を直接使用します。\n   - `PI_PYTHON_GATEWAY_TOKEN` によるオプション認証。\n   - ローカルゲートウェイプロセスはスポーンまたは管理されません。\n\n2. **ローカル共有ゲートウェイ** (デフォルトパス)\n   - `~/.xcsh/agent/python-gateway` 配下でコーディネートされる単一の共有プロセスを使用します。\n   - メタデータファイル: `gateway.json`\n   - ロックファイル: `gateway.lock`\n   - スポーンコマンド:\n     - `python -m kernel_gateway`\n     - `127.0.0.1:<allocated-port>` にバインド\n     - 起動ヘルスチェック: `GET /api/kernelspecs`\n\n### ローカル共有ゲートウェイの調整\n\n`acquireSharedGateway()`:\n\n- ハートビート付きのファイルロック (`gateway.lock`) を取得します。\n- PID が生存しておりヘルスチェックが通過する場合は `gateway.json` を再利用します。\n- 必要に応じて古い情報/PID をクリーンアップします。\n- 正常なゲートウェイが存在しない場合は新しいゲートウェイを起動します。\n\n`releaseSharedGateway()` は現在 no-op です（カーネルのシャットダウンは共有ゲートウェイを停止しません）。\n\n`shutdownSharedGateway()` は共有プロセスを明示的に終了し、ゲートウェイメタデータをクリアします。\n\n### 重要な制約\n\n`python.sharedGateway=false` はカーネル起動時に拒否されます:\n\n- エラー: `Shared Python gateway required; local gateways are disabled`\n- プロセスごとの非共有ローカルゲートウェイモードは存在しません。\n\n## カーネルのライフサイクル\n\n各実行は、選択したゲートウェイで `POST /api/kernels` を介して作成されたカーネルを使用します。\n\nカーネル起動シーケンス:\n\n1. 可用性チェック (`checkPythonKernelAvailability`)\n2. カーネル作成 (`/api/kernels`)\n3. WebSocket のオープン (`/api/kernels/:id/channels`)\n4. カーネル環境の初期化 (`cwd`、環境変数、`sys.path`)\n5. `PYTHON_PRELUDE` の実行\n6. 以下からの拡張モジュールの読み込み:\n   - ユーザー: `~/.xcsh/agent/modules/*.py`\n   - プロジェクト: `<cwd>/.xcsh/modules/*.py` (同名のユーザーモジュールを上書き)\n\nカーネルのシャットダウン:\n\n- `DELETE /api/kernels/:id` でリモートカーネルを削除\n- WebSocket を閉じる\n- 共有ゲートウェイのリリースフックを呼び出す (現在は no-op)\n\n## セッション永続化のセマンティクス\n\n`python.kernelMode` はカーネルの再利用を制御します:\n\n- `session` (デフォルト)\n  - セッション ID と cwd をキーとしてカーネルセッションを再利用します。\n  - 実行はキューを介してセッションごとに直列化されます。\n  - アイドルセッションは 5 分後に削除されます。\n  - 最大 4 セッション。オーバーフロー時は最も古いものが削除されます。\n  - ハートビートチェックにより死んだカーネルを検出します。\n  - 自動再起動は 1 回許可されます。繰り返しクラッシュするとハード障害になります。\n\n- `per-call`\n  - 各実行リクエストに対して新しいカーネルを作成します。\n  - リクエスト後にカーネルをシャットダウンします。\n  - 呼び出し間の状態の永続化はありません。\n\n### 単一ツール呼び出しにおけるマルチセルの動作\n\nセルは、そのツール呼び出しの同一カーネルインスタンス内で順次実行されます。\n\n中間セルが失敗した場合:\n\n- 以前のセルの状態はメモリに残ります。\n- ツールはどのセルが失敗したかを示す対象を絞ったエラーを返します。\n- 後続のセルは実行されません。\n\n`reset=true` はその呼び出しの最初のセル実行にのみ適用されます。\n\n## 環境フィルタリングとランタイム解決\n\nゲートウェイ/カーネルランタイムを起動する前に環境がフィルタリングされます:\n\n- 許可リストには `PATH`、`HOME`、ロケール変数、`VIRTUAL_ENV`、`PYTHONPATH` などのコア変数が含まれます。\n- 許可プレフィックス: `LC_`、`XDG_`、`PI_`\n- 拒否リストは一般的な API キー (OpenAI/Anthropic/Gemini など) を除外します。\n\nランタイム選択順序:\n\n1. アクティブ/検出済み venv (`VIRTUAL_ENV`、次に `<cwd>/.venv`、`<cwd>/venv`)\n2. `~/.xcsh/python-env` の管理済み venv\n3. PATH 上の `python` または `python3`\n\nvenv が選択された場合、その bin/Scripts パスが `PATH` の先頭に追加されます。\n\nPython 内部でのカーネル環境初期化も以下を行います:\n\n- `os.chdir(cwd)`\n- 提供された環境マップを `os.environ` に注入\n- cwd が `sys.path` に含まれることを確認\n\n## ツールの可用性とモード選択\n\n`python.toolMode` (デフォルト `both`) + オプションの `PI_PY` オーバーライドで公開を制御します:\n\n- `ipy-only`\n- `bash-only`\n- `both`\n\n`PI_PY` の受け付ける値:\n\n- `0` / `bash` -> `bash-only`\n- `1` / `py` -> `ipy-only`\n- `mix` / `both` -> `both`\n\nPython プリフライトが失敗した場合、そのセッションはツール作成が bash-only にデグレードします。\n\n## 実行フローとキャンセル/タイムアウト\n\n### ツールレベルのタイムアウト\n\n`python` ツールのタイムアウトは秒単位で、デフォルトは 30、`1..600` にクランプされます。\n\nツールは以下を組み合わせます:\n\n- 呼び出し元の中断シグナル\n- タイムアウト中断シグナル\n\n`AbortSignal.any(...)` で結合されます。\n\n### カーネル実行のキャンセル\n\n中断/タイムアウト時:\n\n- 実行はキャンセル済みとしてマークされます。\n- REST (`POST /interrupt`) およびコントロールチャンネルの `interrupt_request` を介してカーネル割り込みが試みられます。\n- 結果には `cancelled=true` が含まれます。\n- タイムアウトパスは出力に `Command timed out after <n> seconds` と注釈を付けます。\n\n### stdin の動作\n\nインタラクティブな stdin はサポートされていません。\n\nカーネルが `input_request` を発行した場合:\n\n- ツールは `stdinRequested=true` を記録します。\n- 説明テキストを出力します。\n- 空の `input_reply` を送信します。\n- 実行はエグゼキュータ層で失敗として扱われます。\n\n## 出力キャプチャとレンダリング\n\n### キャプチャされる出力クラス\n\nカーネルメッセージから:\n\n- `stream` -> プレーンテキストチャンク\n- `display_data`/`execute_result` -> リッチディスプレイ処理\n- `error` -> トレースバックテキスト\n- カスタム MIME `application/x-xcsh-status` -> 構造化ステータスイベント\n\nディスプレイ MIME の優先順位:\n\n1. `text/markdown`\n2. `text/plain`\n3. `text/html` (基本的な Markdown に変換)\n\n構造化出力として追加キャプチャ:\n\n- `application/json` -> JSON ツリーデータ\n- `image/png` -> 画像ペイロード\n- `application/x-xcsh-status` -> ステータスイベント\n\n### ストレージとトランケーション\n\n出力は `OutputSink` を通じてストリーミングされ、アーティファクトストレージに永続化される場合があります。\n\nツールの結果にはトランケーションメタデータと、完全な出力復元のための `artifact://<id>` が含まれる場合があります。\n\n### レンダラーの動作\n\n- ツールレンダラー (`python.ts`):\n  - セルごとのステータスを持つコードセルブロックを表示\n  - 折りたたみプレビューのデフォルトは 10 行\n  - 完全な出力とより詳細なステータス詳細のための展開モードをサポート\n- インタラクティブレンダラー (`python-execution.ts`):\n  - TUI でのユーザー起動の Python 実行に使用\n  - 折りたたみプレビューのデフォルトは 20 行\n  - 表示の安全のために非常に長い個々の行を 4000 文字にクランプ\n  - キャンセル/エラー/トランケーション通知を表示\n\n## 外部ゲートウェイのサポート\n\n以下を設定します:\n\n```bash\nexport PI_PYTHON_GATEWAY_URL=\"http://127.0.0.1:8888\"\n# オプション:\nexport PI_PYTHON_GATEWAY_TOKEN=\"...\"\n```\n\nローカル共有ゲートウェイとの動作の違い:\n\n- ローカルゲートウェイのロック/情報ファイルなし\n- ローカルプロセスのスポーン/終了なし\n- ヘルスチェックとカーネルの CRUD は外部エンドポイントに対して実行\n- 認証エラーは明示的なトークンガイダンスとともに表示\n\n## 運用上のトラブルシューティング (現在の障害モード)\n\n- **Python ツールが利用できない**\n  - `python.toolMode` / `PI_PY` を確認してください。\n  - プリフライトが失敗した場合、ランタイムは bash-only にフォールバックします。\n\n- **カーネル可用性エラー**\n  - ローカルモードでは、解決された Python ランタイムで `kernel_gateway` と `ipykernel` の両方がインポート可能である必要があります。\n  - 以下でインストールします:\n\n    ```bash\n    python -m pip install jupyter_kernel_gateway ipykernel\n    ```\n\n- **`python.sharedGateway=false` による起動失敗**\n  - 現在の実装では想定された動作です。\n\n- **外部ゲートウェイの認証/到達可能性エラー**\n  - 401/403 -> `PI_PYTHON_GATEWAY_TOKEN` を設定してください。\n  - タイムアウト/到達不能 -> URL/ネットワークとゲートウェイの健全性を確認してください。\n\n- **実行がハングしてタイムアウトになる**\n  - ワークロードが正当な場合はツールの `timeout` を増やします (最大 600 秒)。\n  - スタックしたコードの場合、キャンセルによりカーネル割り込みがトリガーされますが、ユーザーコードのリファクタリングが必要な場合もあります。\n\n- **Python コードの stdin/input プロンプト**\n  - `input()` はこのランタイムパスではインタラクティブにサポートされていません。データはプログラム的に渡してください。\n\n- **リソース枯渇 (`EMFILE` / オープンファイルが多すぎる)**\n  - セッションマネージャーは共有ゲートウェイの回復をトリガーします (セッションの解体 + 共有ゲートウェイの再起動)。\n\n- **作業ディレクトリエラー**\n  - ツールは実行前に `cwd` が存在し、ディレクトリであることを検証します。\n\n## 関連する環境変数\n\n- `PI_PY` — ツール公開オーバーライド (上記の `bash-only`/`ipy-only`/`both` マッピング)\n- `PI_PYTHON_GATEWAY_URL` — 外部ゲートウェイを使用\n- `PI_PYTHON_GATEWAY_TOKEN` — オプションの外部ゲートウェイ認証トークン\n- `PI_PYTHON_SKIP_CHECK=1` — Python プリフライト/ウォームチェックをバイパス\n- `PI_PYTHON_IPC_TRACE=1` — カーネル IPC の送受信トレースをログ記録\n- `PI_DEBUG_STARTUP=1` — 起動ステージのデバッグマーカーを出力\n",
	"ja/runtime-tools/bash-tool-runtime.md": "---\ntitle: Bash ツールランタイム\ndescription: >-\n  Bash tool runtime with shell process management, sandboxing, timeout, and\n  output streaming.\nsidebar:\n  order: 1\n  label: Bash ツール\ni18n:\n  sourceHash: 18b12aa5dbd5\n  translator: machine\n---\n\n# Bash ツールランタイム\n\nこのドキュメントでは、エージェントのツール呼び出しで使用される **`bash` ツール** のランタイムパスについて、コマンドの正規化から実行、切り詰め/アーティファクト、レンダリングまでを説明します。\n\nまた、インタラクティブ TUI、プリントモード、RPC モード、およびユーザーが開始するバン（`!`）シェル実行において動作が異なる箇所も示しています。\n\n## スコープとランタイムサーフェス\n\ncoding-agent には2つの異なる bash 実行サーフェスがあります：\n\n1. **ツール呼び出しサーフェス**（`toolName: \"bash\"`）：モデルが bash ツールを呼び出す際に使用されます。\n   - エントリポイント：`BashTool.execute()`\n2. **ユーザーバンコマンドサーフェス**（インタラクティブ入力からの `!cmd` または RPC `bash` コマンド）：セッションレベルのヘルパーパスです。\n   - エントリポイント：`AgentSession.executeBash()`\n\nどちらも最終的には `src/exec/bash-executor.ts` の `executeBash()` を非 PTY 実行に使用しますが、ツール呼び出しパスのみが正規化/インターセプションとツールレンダラーのロジックを実行します。\n\n## エンドツーエンドのツール呼び出しパイプライン\n\n## 1) 入力の正規化とパラメータマージ\n\n`BashTool.execute()` はまず `normalizeBashCommand()` を通じて生のコマンドを正規化します：\n\n- 末尾の `| head -n N`、`| head -N`、`| tail -n N`、`| tail -N` を構造化されたリミットとして抽出します\n- 末尾/先頭の空白をトリムします\n- 内部の空白はそのまま保持します\n\n次に、抽出されたリミットを明示的なツール引数とマージします：\n\n- 明示的な `head`/`tail` 引数は抽出された値をオーバーライドします\n- 抽出された値はフォールバックとしてのみ使用されます\n\n### 注意事項\n\n`bash-normalize.ts` のコメントには `2>&1` の除去について言及がありますが、現在の実装ではそれを削除しません。ランタイムの動作は依然として正しいですが（stdout/stderr は既にマージされています）、正規化の動作はコメントが示唆するよりも狭い範囲です。\n\n## 2) オプショナルインターセプション（ブロックコマンドパス）\n\n`bashInterceptor.enabled` が true の場合、`BashTool` は設定からルールを読み込み、正規化されたコマンドに対して `checkBashInterception()` を実行します。\n\nインターセプションの動作：\n\n- コマンドがブロックされるのは以下の **両方** が満たされた場合のみ：\n  - 正規表現ルールがマッチする、かつ\n  - 提案されたツールが `ctx.toolNames` に存在する\n- 無効な正規表現ルールは暗黙的にスキップされます\n- ブロック時、`BashTool` は以下のメッセージで `ToolError` をスローします：\n  - `Blocked: ...`\n  - 元のコマンドが含まれます\n\nデフォルトのルールパターン（コードで定義）は一般的な誤用を対象としています：\n\n- ファイルリーダー（`cat`、`head`、`tail`、...）\n- 検索ツール（`grep`、`rg`、...）\n- ファイルファインダー（`find`、`fd`、...）\n- インプレースエディタ（`sed -i`、`perl -i`、`awk -i inplace`）\n- シェルリダイレクト書き込み（`echo ... > file`、ヒアドキュメントリダイレクション）\n\n### 注意事項\n\n`InterceptionResult` には `suggestedTool` が含まれていますが、`BashTool` は現在メッセージテキストのみを表面化しています（`details` に構造化された提案ツールフィールドはありません）。\n\n## 3) CWD の検証とタイムアウトのクランプ\n\n`cwd` はセッションの cwd（`resolveToCwd`）に対して相対的に解決され、その後 `stat` で検証されます：\n\n- パスが存在しない -> `ToolError(\"Working directory does not exist: ...\")`\n- ディレクトリではない -> `ToolError(\"Working directory is not a directory: ...\")`\n\nタイムアウトは `[1, 3600]` 秒にクランプされ、ミリ秒に変換されます。\n\n## 4) アーティファクトの割り当て\n\n実行前に、ツールは切り詰められた出力の保存用にアーティファクトのパス/ID を（ベストエフォートで）割り当てます。\n\n- アーティファクトの割り当て失敗は致命的ではありません（アーティファクトスピルファイルなしで実行が続行されます）\n- アーティファクトの ID/パスは、切り詰め時の完全な出力の永続化のために実行パスに渡されます\n\n## 5) PTY vs 非 PTY 実行の選択\n\n`BashTool` は以下のすべてが true の場合にのみ PTY 実行を選択します：\n\n- `bash.virtualTerminal === \"on\"`\n- `PI_NO_PTY !== \"1\"`\n- ツールコンテキストに UI がある（`ctx.hasUI === true` かつ `ctx.ui` が設定されている）\n\nそれ以外の場合は非インタラクティブな `executeBash()` を使用します。\n\nつまり、プリントモードと非 UI の RPC/ツールコンテキストは常に非 PTY を使用します。\n\n## 非インタラクティブ実行エンジン（`executeBash`）\n\n## シェルセッション再利用モデル\n\n`executeBash()` はネイティブの `Shell` インスタンスを以下でキーイングされたプロセスグローバルマップにキャッシュします：\n\n- シェルパス\n- 設定されたコマンドプレフィックス\n- スナップショットパス\n- シリアライズされたシェル環境変数\n- オプショナルなエージェントセッションキー\n\nセッションレベルの実行では、`AgentSession.executeBash()` が `sessionKey: this.sessionId` を渡し、セッションごとに再利用を分離します。\n\nツール呼び出しパスは `sessionKey` を渡さ **ない** ため、再利用スコープはシェル設定/スナップショット/環境変数に基づきます。\n\n## シェル設定とスナップショットの動作\n\n各呼び出し時に、エグゼキュータは設定のシェル構成（`shell`、`env`、オプショナルな `prefix`）を読み込みます。\n\n選択されたシェルに `bash` が含まれる場合、`getOrCreateSnapshot()` を試行します：\n\n- スナップショットはユーザー rc からのエイリアス/関数/オプションをキャプチャします\n- スナップショットの作成はベストエフォートです\n- 失敗した場合はスナップショットなしにフォールバックします\n\n`prefix` が設定されている場合、コマンドは以下のようになります：\n\n```text\n<prefix> <command>\n```\n\n## ストリーミングとキャンセル\n\n`Shell.run()` はチャンクをコールバックにストリーミングします。エグゼキュータは各チャンクを `OutputSink` とオプショナルな `onChunk` コールバックにパイプします。\n\nキャンセル：\n\n- 中止シグナルは `shellSession.abort(...)` をトリガーします\n- ネイティブ結果からのタイムアウトは `cancelled: true` + アノテーションテキストにマッピングされます\n- 明示的なキャンセルも同様に `cancelled: true` + アノテーションを返します\n\nタイムアウト/キャンセル時にエグゼキュータ内で例外はスローされません。構造化された `BashResult` を返し、呼び出し元にエラーセマンティクスのマッピングを委ねます。\n\n## インタラクティブ PTY パス（`runInteractiveBashPty`）\n\nPTY が有効な場合、ツールは `runInteractiveBashPty()` を実行し、オーバーレイコンソールコンポーネントを開いてネイティブの `PtySession` を駆動します。\n\n動作のハイライト：\n\n- xterm-headless 仮想ターミナルがオーバーレイにビューポートをレンダリングします\n- キーボード入力は正規化されます（Kitty シーケンスやアプリケーションカーソルモードの処理を含む）\n- 実行中の `esc` は PTY セッションを終了します\n- ターミナルのリサイズは PTY に伝播されます（`session.resize(cols, rows)`）\n\n無人実行のための環境ハードニングデフォルトが注入されます：\n\n- ページャー無効化（`PAGER=cat`、`GIT_PAGER=cat` など）\n- エディタプロンプト無効化（`GIT_EDITOR=true`、`EDITOR=true`、...）\n- ターミナル/認証プロンプトの削減（`GIT_TERMINAL_PROMPT=0`、`SSH_ASKPASS=/usr/bin/false`、`CI=1`）\n- 非インタラクティブ動作のためのパッケージマネージャー/ツール自動化フラグ\n\nPTY 出力は正規化され（`CRLF`/`CR` を `LF` に、`sanitizeText`）、アーティファクトスピルサポートを含めて `OutputSink` に書き込まれます。\n\nPTY の起動/ランタイムエラー時、シンクは `PTY error: ...` 行を受け取り、コマンドは未定義の終了コードで完了します。\n\n## 出力処理：ストリーミング、切り詰め、アーティファクトスピル\n\nPTY パスと非 PTY パスの両方が `OutputSink` を使用します。\n\n## OutputSink のセマンティクス\n\n- インメモリの UTF-8 セーフなテールバッファを保持します（`DEFAULT_MAX_BYTES`、現在 50KB）\n- 総バイト数/行数を追跡します\n- アーティファクトパスが存在し、出力がオーバーフローした場合（またはファイルが既にアクティブな場合）、完全なストリームをアーティファクトファイルに書き込みます\n- メモリしきい値がオーバーフローした場合、インメモリバッファをテール（UTF-8 境界セーフ）にトリムします\n- オーバーフロー/ファイルスピルが発生した場合に `truncated` をマークします\n\n`dump()` は以下を返します：\n\n- `output`（アノテーション付きプレフィックスの可能性あり）\n- `truncated`\n- `totalLines/totalBytes`\n- `outputLines/outputBytes`\n- アーティファクトファイルがアクティブな場合は `artifactId`\n\n### 長い出力に関する注意事項\n\nランタイムの切り詰めは `OutputSink` のバイトしきい値ベースです（デフォルト 50KB）。このコードパスでは、ハードな 2000 行の制限は強制されません。\n\n## ライブツール更新\n\n非 PTY 実行の場合、`BashTool` は部分更新用に別の `TailBuffer` を使用し、コマンド実行中に `onUpdate` スナップショットを発行します。\n\nPTY 実行の場合、ライブレンダリングは `onUpdate` テキストチャンクではなく、カスタム UI オーバーレイによって処理されます。\n\n## 結果の整形、メタデータ、エラーマッピング\n\n実行後：\n\n1. `cancelled` の処理：\n   - 中止シグナルが中止されている場合 -> `ToolAbortError` をスロー（中止セマンティクス）\n   - それ以外 -> `ToolError` をスロー（ツール失敗として扱われます）\n2. PTY の `timedOut` -> `ToolError` をスロー\n3. 最終出力テキストに head/tail フィルターを適用（`applyHeadTail`、head が先で tail が後）\n4. 空の出力は `(no output)` になります\n5. `toolResult(...).truncationFromSummary(result, { direction: \"tail\" })` を通じて切り詰めメタデータを付加\n6. 終了コードのマッピング：\n   - 終了コードが欠落 -> `ToolError(\"... missing exit status\")`\n   - 非ゼロ終了 -> `ToolError(\"... Command exited with code N\")`\n   - ゼロ終了 -> 成功結果\n\n成功ペイロードの構造：\n\n- `content`：テキスト出力\n- 切り詰め時の `details.meta.truncation`。以下を含みます：\n  - `direction`、`truncatedBy`、総/出力の行数+バイト数\n  - `shownRange`\n  - 利用可能な場合は `artifactId`\n\nビルトインツールは `wrapToolWithMetaNotice()` でラップされているため、切り詰め通知テキストは最終テキストコンテンツに自動的に追加されます（例：`Full: artifact://<id>`）。\n\n## レンダリングパス\n\n## ツール呼び出しレンダラー（`bashToolRenderer`）\n\n`bashToolRenderer` はツール呼び出しメッセージ（`toolCall` / `toolResult`）に使用されます：\n\n- 折りたたみモードでは視覚的な行切り詰めプレビューを表示します\n- 展開モードでは現在利用可能なすべての出力テキストを表示します\n- 警告行には切り詰め理由と、切り詰め時の `artifact://<id>` が含まれます\n- タイムアウト値（引数から）はフッターのメタデータ行に表示されます\n\n### 注意事項：完全なアーティファクト展開\n\n`BashRenderContext` には `isFullOutput` がありますが、現在のレンダラーコンテキストビルダーは bash ツール結果に対してそれを設定しません。展開ビューは、別の呼び出し元が完全なアーティファクトコンテンツを提供しない限り、結果コンテンツ内のテキスト（テール/切り詰められた出力）を依然として使用します。\n\n## ユーザーバンコマンドコンポーネント（`BashExecutionComponent`）\n\n`BashExecutionComponent` はインタラクティブモードでのユーザーの `!` コマンド用です（モデルのツール呼び出しではありません）：\n\n- チャンクをライブストリーミングします\n- 折りたたみプレビューは最後の 20 論理行を保持します\n- 1行あたり 4000 文字で行クランプします\n- メタデータが存在する場合、切り詰め + アーティファクト警告を表示します\n- キャンセル/エラー/終了状態を個別にマークします\n\nこのコンポーネントは `CommandController.handleBashCommand()` によって配線され、`AgentSession.executeBash()` からデータが供給されます。\n\n## モード固有の動作の違い\n\n| サーフェス                        | エントリパス                                            | PTY 対象                                                         | ライブ出力 UX                                                           | エラーの表面化                                  |\n| ------------------------------ | ----------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------ |\n| インタラクティブツール呼び出し          | `BashTool.execute`                                    | はい、`bash.virtualTerminal=on` かつ UI が存在し `PI_NO_PTY!=1` の場合 | PTY オーバーレイ（インタラクティブ）またはストリーミングテール更新                       | ツールエラーは `toolResult.isError` になります          |\n| プリントモードツール呼び出し           | `BashTool.execute`                                    | いいえ（UI コンテキストなし）                                                   | TUI オーバーレイなし；出力はイベントストリーム/最終アシスタントテキストフローに表示 | 同じツールエラーマッピング                          |\n| RPC ツール呼び出し（エージェントツーリング）  | `BashTool.execute`                                    | 通常 UI なし -> 非 PTY                                             | 構造化されたツールイベント/結果                                           | 同じツールエラーマッピング                          |\n| インタラクティブバンコマンド（`!`） | `AgentSession.executeBash` + `BashExecutionComponent` | いいえ（エグゼキュータを直接使用）                                          | 専用の bash 実行コンポーネント                                       | コントローラーが例外をキャッチし UI エラーを表示 |\n| RPC `bash` コマンド             | `rpc-mode` -> `session.executeBash`                   | いいえ                                                                   | `BashResult` を直接返します                                            | コンシューマーが返されたフィールドを処理                 |\n\n## 運用上の注意事項\n\n- インターセプターは、提案されたツールが現在のコンテキストで利用可能な場合にのみコマンドをブロックします。\n- アーティファクトの割り当てが失敗した場合、切り詰めは依然として発生しますが、`artifact://` の逆参照は利用できません。\n- シェルセッションキャッシュはこのモジュールに明示的なエビクションがありません。ライフタイムはプロセススコープです。\n- PTY と非 PTY のタイムアウトサーフェスは異なります：\n  - PTY は明示的な `timedOut` 結果フィールドを公開します\n  - 非 PTY はタイムアウトを `cancelled + annotation` サマリーにマッピングします\n\n## 実装ファイル\n\n- [`src/tools/bash.ts`](../../packages/coding-agent/src/tools/bash.ts) — ツールエントリポイント、正規化/インターセプション、PTY/非 PTY 選択、結果/エラーマッピング、bash ツールレンダラー。\n- [`src/tools/bash-normalize.ts`](../../packages/coding-agent/src/tools/bash-normalize.ts) — コマンドの正規化と実行後の head/tail フィルタリング。\n- [`src/tools/bash-interceptor.ts`](../../packages/coding-agent/src/tools/bash-interceptor.ts) — インターセプタールールのマッチングとブロックコマンドメッセージ。\n- [`src/exec/bash-executor.ts`](../../packages/coding-agent/src/exec/bash-executor.ts) — 非 PTY エグゼキュータ、シェルセッション再利用、キャンセル配線、出力シンク統合。\n- [`src/tools/bash-interactive.ts`](../../packages/coding-agent/src/tools/bash-interactive.ts) — PTY ランタイム、オーバーレイ UI、入力正規化、非インタラクティブ環境デフォルト。\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — `OutputSink` 切り詰め/アーティファクトスピルとサマリーメタデータ。\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — アーティファクト割り当てヘルパーとストリーミングテールバッファ。\n- [`src/tools/output-meta.ts`](../../packages/coding-agent/src/tools/output-meta.ts) — 切り詰めメタデータの形状 + 通知注入ラッパー。\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — セッションレベルの `executeBash`、メッセージ記録、中止ライフサイクル。\n- [`src/modes/components/bash-execution.ts`](../../packages/coding-agent/src/modes/components/bash-execution.ts) — インタラクティブ `!` コマンド実行コンポーネント。\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts) — インタラクティブ `!` コマンド UI ストリーム/更新完了の配線。\n- [`src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts) — RPC `bash` および `abort_bash` コマンドサーフェス。\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — `artifact://<id>` の解決。\n",
	"ja/runtime-tools/context-command.md": "---\ntitle: F5 XC コンテキスト\ndescription: xcsh を F5 Distributed Cloud テナントに接続 -- 認証コンテキストの作成、切り替え、管理。\nsidebar:\n  order: 1\n  label: F5 XC コンテキスト\ni18n:\n  sourceHash: a9cccbc338f0\n  translator: machine\n---\n\n# F5 XC コンテキスト\n\nxcsh は **コンテキスト** を通じて F5 Distributed Cloud に接続します。コンテキストとは、テナント URL、API トークン、およびネームスペースを紐づけた名前付きの認証情報セットです。`kubectl config use-context` や `kubectx` を使ったことがあれば、ワークフローは同じです。コンテキストを作成し、名前で切り替え、`-` で前のコンテキストに戻ります。\n\n## はじめに\n\n### 1. 最初のコンテキストを作成する\n\nF5 XC コンソールから次の3つの情報が必要です：テナント URL、API トークン、およびオプションでネームスペース。\n\n```\n/context create production https://acme.console.ves.volterra.io p12k3-your-api-token\n```\n\n```\nContext 'production' created. Use /context activate production to switch to it.\n```\n\nステップバイステップのプロンプトを好む場合は、ガイド付きウィザードを使用できます：\n\n```\n/context wizard\n```\n\n### 2. コンテキストをアクティブにする\n\n```\n/context production\n```\n\n```\n╭─ production ─────────────────────────────────────────────────╮\n│ XCSH_TENANT     acme                                         │\n│ XCSH_API_URL    https://acme.console.ves.volterra.io         │\n│ XCSH_API_TOKEN  ...oken                                      │\n│ Status          Connected (312ms)                            │\n├─ Environment ────────────────────────────────────────────────┤\n│ XCSH_NAMESPACE  default                                      │\n╰──────────────────────────────────────────────────────────────╯\n```\n\nアクティブにすると、xcsh はテナントの認証情報をセッションに注入します。エージェントは F5 XC API 呼び出しを行えるようになり、ステータスラインにアクティブなコンテキストが表示されます。\n\n### 3. コンテキストを追加して切り替える\n\n```\n/context create staging https://staging.console.ves.volterra.io p12k3-staging-token\n```\n\n名前で切り替えます -- サブコマンド動詞は不要です：\n\n```\n/context staging\n```\n\n前のコンテキストに戻ります（`cd -` スタイル）：\n\n```\n/context -\n```\n\n`/context -` を2回呼び出すと、元の場所に戻ります。\n\n### 4. 現在の状態を確認する\n\n```\n/context\n```\n\n```\n  production           https://acme.console.ves.volterra.io\n* staging              https://staging.console.ves.volterra.io\n```\n\n`*` がアクティブなコンテキストを示します。\n\n## 日常的なコマンド\n\n| コマンド | 動作 |\n|---|---|\n| `/context` | すべてのコンテキストを一覧表示 |\n| `/context <name>` | コンテキストを切り替え |\n| `/context -` | 前のコンテキストに切り替え |\n| `/context show` | アクティブなコンテキストの詳細を表示（トークンはマスク） |\n| `/context status` | 現在の認証ステータスを表示 |\n\n## コンテキストのライフサイクル\n\n| コマンド | 動作 |\n|---|---|\n| `/context create <name> <url> <token> [namespace]` | コンテキストを作成 |\n| `/context delete <name> --confirm` | コンテキストを削除（`--confirm` が必要） |\n| `/context rename <old> <new>` | コンテキストの名前を変更 |\n| `/context validate <name>` | 切り替えずに認証情報をテスト |\n| `/context export [name] [--include-token]` | JSON としてエクスポート（デフォルトでトークンはマスク） |\n| `/context import <path-or-json> [--overwrite]` | ファイルまたはインライン JSON からインポート |\n| `/context wizard` | ガイド付きインタラクティブセットアップ |\n\n## ネームスペースの切り替え\n\n各コンテキストにはデフォルトのネームスペースがあります。コンテキストを変更せずにネームスペースを切り替えます：\n\n```\n/context namespace system\n```\n\nタブ補完でアクティブなテナントのネームスペース名が候補として表示されます。\n\n## コンテキストの環境変数\n\nコンテキストには追加の環境変数を設定でき、アクティブ化時にセッションに注入されます。認証情報セットには含まれないテナント固有の設定に便利です。\n\n```\n/context set CUSTOM_HEADER=x-acme-trace\n/context set LOG_LEVEL=debug\n/context env list\n/context unset LOG_LEVEL\n```\n\nエイリアス：`add` = `set`、`remove`/`clear` = `unset`。\n\n## タブ補完\n\n`/context ` と入力して Tab キーを押します。ドロップダウンに以下が表示されます：\n\n1. **コンテキスト名** -- テナント URL のヒント付きで、テナントを区別できます\n2. **`-`** -- 以前に切り替えたことがある場合に表示され、どのコンテキストに戻るかを示します\n3. **サブコマンド** -- `list`、`create`、`delete` など\n\n切り替えが最も一般的な操作であるため、コンテキスト名が最初に表示されます。\n\nサブコマンドレベルの補完も動作します：`/context activate <Tab>` でコンテキスト名を補完、`/context namespace <Tab>` でネームスペースを補完、`/context unset <Tab>` で既知の環境変数キーを補完します。\n\n## 命名規則\n\nコンテキスト名は1〜64文字で、英字、数字、ハイフン、アンダースコアが使用できます。\n\nサブコマンドと衝突する名前は拒否されます：\n\n```\n/context create list https://example.com tok\n```\n\n```\nError: Context name 'list' conflicts with a /context subcommand. Choose a different name.\n```\n\n予約語の完全なセット：`list`、`show`、`status`、`create`、`delete`、`rename`、`namespace`、`env`、`set`、`unset`、`add`、`remove`、`clear`、`activate`、`validate`、`export`、`import`、`wizard`、`help`。比較は大文字小文字を区別しません。\n\n## 環境変数によるオーバーライド\n\nxcsh 起動前にシェル環境で `XCSH_API_URL` と `XCSH_API_TOKEN` が設定されている場合、それらはすべてのコンテキストより優先されます。これは CI/CD パイプラインや、永続的なコンテキストを作成したくない一回限りのセッションに便利です。\n\nこのモードで実行している場合、`/context` は環境変数から取得した認証情報を `(via env vars)` ラベル付きで表示します。\n\n## 前のコンテキストの動作\n\n- **セッションスコープ**：前のコンテキストは xcsh の再起動時にリセットされます。ディスクには永続化されません。\n- **ピンポン**：`/context -` を2回実行すると、元の場所に戻ります。\n- **変更に対して安全**：前のコンテキストを削除すると、ポインタはクリアされます。名前を変更すると、ポインタは新しい名前に追従します。\n- **再アクティブ化はノーオペレーション**：すでに `production` にいるときに `/context production` を実行しても、前のポインタはリセットされません。\n\n## デザイン規約\n\n`/context` の UX は以下に準拠しています：\n\n- **kubectx**：`kubectx <name>` で切り替え、`kubectx -` で前に戻る、引数なしの `kubectx` で一覧表示\n- **kubectl**：`kubectl config use-context` による明示的な形式\n- **シェル**：`cd -` / `OLDPWD` による前のディレクトリ追跡\n",
	"ja/runtime-tools/custom-tools.md": "---\ntitle: カスタムツール\ndescription: エージェントを拡張するためのカスタムツール登録、スキーマ定義、および実行パイプライン。\nsidebar:\n  order: 4\n  label: カスタムツール\ni18n:\n  sourceHash: 5f4a441fc2e2\n  translator: machine\n---\n\n# カスタムツール\n\nカスタムツールは、組み込みツールと同じツール実行パイプラインに組み込まれる、モデルから呼び出し可能な関数です。\n\nカスタムツールは、ファクトリーをエクスポートする TypeScript/JavaScript モジュールです。ファクトリーはホスト API（`CustomToolAPI`）を受け取り、1 つのツールまたはツールの配列を返します。\n\n## これが何であるか（そして何でないか）\n\n- **カスタムツール**: ターン中にモデルから呼び出し可能（`execute` + TypeBox スキーマ）。\n- **Extension（拡張機能）**: ツールの登録やイベントのインターセプト/変更が可能なライフサイクル/イベントフレームワーク。\n- **Hook（フック）**: 外部のコマンド前後スクリプト。\n- **Skill（スキル）**: 静的なガイダンス/コンテキストパッケージ。実行可能なツールコードではない。\n\nモデルからコードを直接呼び出す必要がある場合は、カスタムツールを使用してください。\n\n## 現在のコードにおける統合パス\n\n2 つのアクティブな統合スタイルがあります：\n\n1. **SDK 提供のカスタムツール** (`options.customTools`)\n   - `CustomToolAdapter` または拡張ラッパーを介してエージェントツールにラップされます。\n   - SDK ブートストラップ時に常に初期アクティブツールセットに含まれます。\n\n2. **ローダー API 経由でファイルシステムから検出されるモジュール** (`discoverAndLoadCustomTools` / `loadCustomTools`)\n   - `src/extensibility/custom-tools/loader.ts` のライブラリ API として公開されています。\n   - ホストコードはこれらを呼び出して、config/provider/plugin パスからツールモジュールを検出・読み込みできます。\n\n```text\nモデルツール呼び出しフロー\n\nLLM ツール呼び出し\n   │\n   ▼\nツールレジストリ（組み込み + カスタムツールアダプター）\n   │\n   ▼\nCustomTool.execute(toolCallId, params, onUpdate, ctx, signal)\n   │\n   ├─ onUpdate(...)  -> ストリーミングされた部分的結果\n   └─ return result  -> 最終ツールコンテンツ/詳細\n```\n\n## 検出場所（ローダー API）\n\n`discoverAndLoadCustomTools(configuredPaths, cwd, builtInToolNames)` は以下をマージします：\n\n1. ケーパビリティプロバイダー（`toolCapability`）、以下を含む：\n   - ネイティブ OMP 設定（`~/.xcsh/agent/tools`、`.xcsh/tools`）\n   - Claude 設定（`~/.claude/tools`、`.claude/tools`）\n   - Codex 設定（`~/.codex/tools`、`.codex/tools`）\n   - Claude マーケットプレイスプラグインキャッシュプロバイダー\n2. インストール済みプラグインマニフェスト（プラグインローダー経由の `~/.xcsh/plugins/node_modules/*`）\n3. ローダーに渡された明示的に設定されたパス\n\n### 重要な動作\n\n- 解決されたパスの重複は除去されます。\n- ツール名の競合は、組み込みツールおよびすでに読み込まれたカスタムツールに対して拒否されます。\n- `.md` および `.json` ファイルは一部のプロバイダーによってツールメタデータとして検出されますが、実行可能モジュールローダーはこれらを実行可能ツールとして拒否します。\n- 相対設定パスは `cwd` から解決され、`~` は展開されます。\n\n## モジュールコントラクト\n\nカスタムツールモジュールは関数をエクスポートする必要があります（デフォルトエクスポートを推奨）：\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = (pi) => ({\n name: \"repo_stats\",\n label: \"Repo Stats\",\n description: \"Counts tracked TypeScript files\",\n parameters: pi.typebox.Type.Object({\n  glob: pi.typebox.Type.Optional(pi.typebox.Type.String({ default: \"**/*.ts\" })),\n }),\n\n async execute(toolCallId, params, onUpdate, ctx, signal) {\n  onUpdate?.({\n   content: [{ type: \"text\", text: \"Scanning files...\" }],\n   details: { phase: \"scan\" },\n  });\n\n  const result = await pi.exec(\"git\", [\"ls-files\", params.glob ?? \"**/*.ts\"], { signal, cwd: pi.cwd });\n  if (result.killed) {\n   throw new Error(\"Scan was cancelled\");\n  }\n  if (result.code !== 0) {\n   throw new Error(result.stderr || \"git ls-files failed\");\n  }\n\n  const files = result.stdout.split(\"\\n\").filter(Boolean);\n  return {\n   content: [{ type: \"text\", text: `Found ${files.length} files` }],\n   details: { count: files.length, sample: files.slice(0, 10) },\n  };\n },\n\n onSession(event) {\n  if (event.reason === \"shutdown\") {\n   // 必要に応じてリソースをクリーンアップ\n  }\n },\n});\n\nexport default factory;\n```\n\nファクトリーの戻り値の型：\n\n- `CustomTool`\n- `CustomTool[]`\n- `Promise<CustomTool | CustomTool[]>`\n\n## ファクトリーに渡される API サーフェス（`CustomToolAPI`）\n\n`types.ts` および `loader.ts` より：\n\n- `cwd`: ホストのワーキングディレクトリ\n- `exec(command, args, options?)`: プロセス実行ヘルパー\n- `ui`: UI コンテキスト（ヘッドレスモードでは no-op になる場合あり）\n- `hasUI`: 非インタラクティブフローでは `false`\n- `logger`: 共有ファイルロガー\n- `typebox`: 注入された `@sinclair/typebox`\n- `pi`: 注入された `@f5-sales-demo/xcsh` エクスポート\n- `pushPendingAction(action)`: 隠し `resolve` ツール用のプレビューアクションを登録する（`docs/resolve-tool-runtime.md`）\n\nローダーは no-op UI コンテキストで開始し、実際の UI が準備できた際にホストコードが `setUIContext(...)` を呼び出す必要があります。\n\n## 実行コントラクトと型付け\n\n`CustomTool.execute` のシグネチャ：\n\n```ts\nexecute(toolCallId, params, onUpdate, ctx, signal)\n```\n\n- `params` は、`Static<TParams>` を介して TypeBox スキーマから静的に型付けされます。\n- ランタイム引数の検証は、エージェントループでの実行前に行われます。\n- `onUpdate` は UI ストリーミング用の部分的な結果を送出します。\n- `ctx` にはセッション/モデルの状態と `abort()` ヘルパーが含まれます。\n- `signal` はキャンセルを伝達します。\n\n`CustomToolAdapter` はこれをエージェントツールインターフェースにブリッジし、正しい引数順序で呼び出しを転送します。\n\n## モデルへのツールの公開方法\n\n- ツールは `AgentTool` インスタンス（`CustomToolAdapter` または拡張ラッパー）にラップされます。\n- 名前によってセッションツールレジストリに挿入されます。\n- SDK ブートストラップでは、カスタムおよび拡張機能で登録されたツールは初期アクティブセットに強制的に含まれます。\n- CLI の `--tools` は現在、組み込みツール名のみを検証します。カスタムツールの組み込みは、検出/登録パスおよび SDK オプションを通じて処理されます。\n\n## レンダリングフック\n\nオプションのレンダリングフック：\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\nTUI でのランタイム動作：\n\n- フックが存在する場合、ツール出力は `Box` コンテナ内にレンダリングされます。\n- `renderResult` は `{ expanded, isPartial, spinnerFrame? }` を受け取ります。\n- レンダラーエラーはキャッチされてログに記録され、UI はデフォルトのテキストレンダリングにフォールバックします。\n\n## セッション/状態処理\n\nオプションの `onSession(event, ctx)` はセッションライフサイクルイベントを受け取ります。以下を含みます：\n\n- `start`、`switch`、`branch`、`tree`、`shutdown`\n- `auto_compaction_start`、`auto_compaction_end`\n- `auto_retry_start`、`auto_retry_end`\n- `ttsr_triggered`、`todo_reminder`\n\nブランチ/セッションコンテキストが変更された際に履歴から状態を再構築するには、`ctx.sessionManager` を使用してください。\n\n## 失敗とキャンセルのセマンティクス\n\n### 同期/非同期の失敗\n\n- `execute` でのスロー（または拒否された Promise）はツール失敗として扱われます。\n- エージェントランタイムは失敗を `isError: true` とエラーテキストコンテンツを含むツール結果メッセージに変換します。\n- 拡張ラッパーを使用する場合、`tool_result` ハンドラーはコンテンツ/詳細をさらに書き換え、エラーステータスを上書きすることもできます。\n\n### キャンセル\n\n- エージェントのアボートは `AbortSignal` を通じて `execute` に伝播されます。\n- 協調的なキャンセルのために、`signal` をサブプロセス処理（`pi.exec(..., { signal })`）に転送してください。\n- `ctx.abort()` により、ツールは現在のエージェント操作のアボートを要求できます。\n\n### onSession エラー\n\n- `onSession` エラーはキャッチされて警告としてログに記録されます。セッションはクラッシュしません。\n\n## 設計上の実際の制約\n\n- ツール名はアクティブなレジストリ内でグローバルに一意である必要があります。\n- レンダラー/状態の再構築のために、`details` には決定論的でスキーマ形式の出力を優先してください。\n- UI の使用は `pi.hasUI` でガードしてください。\n- ツールディレクトリ内の `.md`/`.json` はメタデータとして扱い、実行可能モジュールとして扱わないでください。\n",
	"ja/runtime-tools/notebook-tool-runtime.md": "---\ntitle: ノートブックツールランタイム内部構造\ndescription: セル実行、カーネルライフサイクル、出力レンダリングを備えた Jupyter ノートブックツールランタイム。\nsidebar:\n  order: 2\n  label: ノートブックツール\ni18n:\n  sourceHash: c1bafcb245e4\n  translator: machine\n---\n\n# ノートブックツールランタイム内部構造\n\n本ドキュメントでは、現在の `notebook` ツールの実装と、カーネルが基盤となる Python ランタイムとの関係について説明します。\n\n重要な区別として、**`notebook` は JSON ノートブックエディターであり、ノートブックの実行エンジンではありません**。`.ipynb` のセルソースを直接編集するものであり、Python カーネルの起動やカーネルとの通信は行いません。\n\n## 実装ファイル\n\n- [`src/tools/notebook.ts`](../../packages/coding-agent/src/tools/notebook.ts)\n- [`src/ipy/executor.ts`](../../packages/coding-agent/src/ipy/executor.ts)\n- [`src/ipy/kernel.ts`](../../packages/coding-agent/src/ipy/kernel.ts)\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts)\n- [`src/tools/python.ts`](../../packages/coding-agent/src/tools/python.ts)\n\n## 1) ランタイム境界：編集と実行\n\n## `notebook` ツール（`src/tools/notebook.ts`）\n\n- `.ipynb` ファイルに対して `action: edit | insert | delete` をサポートします。\n- セッションの CWD（`resolveToCwd`）を基準にパスを解決します。\n- ノートブック JSON を読み込み、`cells` 配列を検証し、`cell_index` の範囲を検証します。\n- ソースの編集をメモリ内で適用し、`JSON.stringify(notebook, null, 1)` を使用してノートブック JSON 全体を書き戻します。\n- テキスト形式のサマリーと構造化された `details`（`action`、`cellIndex`、`cellType`、`totalCells`、`cellSource`）を返します。\n\nこのツールにはカーネルライフサイクルが存在しません：\n\n- ゲートウェイの取得なし\n- カーネルセッション ID なし\n- `execute_request` なし\n- カーネルチャンネルからのストリームチャンクなし\n- リッチディスプレイのキャプチャなし（`image/png`、JSON ディスプレイ、ステータス MIME）\n\n## ノートブック的な実行パス（`src/tools/python.ts` + `src/ipy/*`）\n\nエージェントがセルスタイルの Python コード（シーケンシャルなセル、永続的な状態、リッチディスプレイ）を実行する必要がある場合、それは `notebook` ではなく **`python` ツール** を経由します。\n\nカーネルモード、リスタート・キャンセル動作、チャンクストリーミング、出力アーティファクトの切り捨てが存在するのは、このパスです。\n\n## 2) ノートブックセル処理のセマンティクス（`notebook` ツール）\n\n## ソースの正規化\n\n`content` は `source: string[]` に分割され、改行が保持されます：\n\n- 最終行以外の各行は末尾の `\\n` を保持します\n- 最終行には末尾の改行は強制されません\n\nこれはノートブック JSON の規則に従っており、後続の編集時における意図しない行の連結を防ぎます。\n\n## アクションの動作\n\n- `edit`\n  - `cells[cell_index].source` を置き換えます\n  - 既存の `cell_type` を保持します\n- `insert`\n  - `[0..cellCount]` の位置に挿入します\n  - `cell_type` のデフォルトは `code` です\n  - コードセルは `execution_count: null` と `outputs: []` で初期化されます\n  - マークダウンセルは `metadata` と `source` のみで初期化されます\n- `delete`\n  - `cells[cell_index]` を削除します\n  - レンダラーのプレビュー用に、削除された `source` を details に返します\n\n## エラーの発生条件\n\n以下の場合にハードエラーがスローされます：\n\n- ノートブックファイルが存在しない\n- 無効な JSON\n- `cells` が存在しないか配列でない\n- インデックスが範囲外（insert と非 insert では有効な範囲が異なります）\n- `edit`/`insert` に `content` がない\n\nこれらは上流で `Error:` ツールレスポンスとなり、レンダラーはノートブックパスとフォーマットされたエラーテキストを使用します。\n\n## 3) カーネルセッションのセマンティクス（実際に存在する場所）\n\nカーネルのセマンティクスは `executePython` / `PythonKernel` に実装されており、`python` ツールに適用されます。\n\n## モード\n\n`PythonKernelMode`：\n\n- `session`（デフォルト）\n  - カーネルは `kernelSessions` マップにキャッシュされます\n  - 最大 4 セッション；超過時は最も古いものが削除されます\n  - 30 秒ごとにアイドル・デッドのクリーンアップ、5 分後にタイムアウト\n  - セッションごとのキューが実行をシリアライズします（`session.queue`）\n- `per-call`\n  - リクエストごとにカーネルを作成します\n  - 実行します\n  - `finally` 内で常にカーネルをシャットダウンします\n\n## リセット動作\n\n`python` ツールは、複数セルの呼び出しにおける最初のセルにのみ `reset` を渡します。それ以降のセルは常に `reset: false` で実行されます。\n\n## カーネルの死亡・リスタート・リトライ\n\nセッションモード（`withKernelSession`）において：\n\n- デッドカーネルはハートビート（5 秒ごとの `kernel.isAlive()` チェック）または実行失敗によって検出されます。\n- 実行前のデッド状態は `restartKernelSession` をトリガーします。\n- 実行時のクラッシュパスは一度リトライします：カーネルをリスタートし、ハンドラーを再実行します。\n- 同一セッション内で `restartCount > 1` になると `Python kernel restarted too many times in this session` がスローされます。\n\n起動時のリトライ動作：\n\n- 共有ゲートウェイのカーネル作成は、HTTP 5xx を伴う `SharedGatewayCreateError` に対して一度リトライします。\n\nリソース枯渇からの回復：\n\n- `EMFILE`/`ENFILE`/\"Too many open files\" スタイルの失敗を検出します\n- 追跡中のセッションをクリアします\n- `shutdownSharedGateway()` を呼び出します\n- カーネルセッションの作成を一度リトライします\n\n## 4) 環境・セッション変数の注入\n\nカーネル起動時にエグゼキューターからオプションの環境マップを受け取ります：\n\n- `PI_SESSION_FILE`（セッション状態ファイルのパス）\n- `ARTIFACTS`（アーティファクトディレクトリ）\n\n`PythonKernel.#initializeKernelEnvironment(...)` は、カーネル内で初期化スクリプトを実行して以下を行います：\n\n- `os.chdir(cwd)`\n- 環境エントリを `os.environ` に注入します\n- cwd が存在しない場合、`sys.path` の先頭に追加します\n\n意味合いとして：\n\n- セッションやアーティファクトのコンテキストを読み取るプレリュードヘルパーは、Python プロセスの状態内のこれらの環境変数に依存します。\n\n## 5) ストリーミング・チャンクおよびディスプレイ処理（カーネルバックドパス）\n\nカーネルクライアントは実行ごとに Jupyter プロトコルメッセージを処理します：\n\n- `stream` -> テキストチャンクを `onChunk` へ\n- `execute_result` / `display_data` ->\n  - MIME の優先順位に基づいてディスプレイテキストを選択：`text/markdown` > `text/plain` > 変換済み `text/html`\n  - 構造化出力を個別にキャプチャ：\n    - `application/json` -> `{ type: \"json\" }`\n    - `image/png` -> `{ type: \"image\" }`\n    - `application/x-xcsh-status` -> `{ type: \"status\" }`（テキスト出力なし）\n- `error` -> トレースバックテキストをチャンクストリームにプッシュ + 構造化エラーメタデータ\n- `input_request` -> stdin 警告テキストを発行し、空の `input_reply` を送信し、stdin リクエスト済みとしてマーク\n- 完了は `execute_reply` とカーネルの `status=idle` の両方を待ちます\n\nキャンセル・タイムアウト：\n\n- 中止シグナルは `interrupt()` をトリガーします（REST `/interrupt` + コントロールチャンネルの `interrupt_request`）\n- 結果は `cancelled=true` でマークされます\n- タイムアウトパスは出力に `Command timed out after <n> seconds` のアノテーションを付けます\n\n## 6) 切り捨てとアーティファクトの動作\n\n`src/session/streaming-output.ts` の `OutputSink` は、カーネル実行パス（`executeWithKernel`）で使用されます：\n\n- すべてのチャンクをサニタイズします（`sanitizeText`）\n- 総行数・出力行数・バイト数を追跡します\n- オプションのアーティファクトスピルファイル（`artifactPath`、`artifactId`）\n- メモリ内バッファがしきい値（特に指定がない場合は `DEFAULT_MAX_BYTES`）を超えた場合：\n  - 切り捨て済みとしてマークします\n  - 末尾バイトをメモリに保持します（UTF-8 安全境界）\n  - フルストリームをアーティファクトシンクにスピルできます\n\n`dump()` は以下を返します：\n\n- 表示可能な出力テキスト（末尾が切り捨てられている場合があります）\n- 切り捨てフラグ + カウント\n- アーティファクト ID（`artifact://<id>` 参照用）\n\n`python` ツールはこのメタデータを結果の切り捨て通知と TUI 警告に変換します。\n\n`notebook` ツールは `OutputSink` を**使用しません**。コードを実行しないため、ストリーム・アーティファクトの切り捨てパイプラインがありません。\n\n## 7) レンダラーの前提条件とフォーマット\n\n## ノートブックレンダラー（`notebookToolRenderer`）\n\n- 呼び出しビュー：アクション + ノートブックパス + セル・タイプメタデータを含むステータス行\n- 結果ビュー：\n  - `details` から導出されたサクセスサマリー\n  - `renderCodeCell` を介してレンダリングされた `cellSource`\n  - マークダウンセルは言語ヒントに `markdown` を設定；その他のセルには明示的な言語オーバーライドなし\n  - 折り畳まれたコードプレビューの上限は `PREVIEW_LIMITS.COLLAPSED_LINES * 2`\n  - 共有レンダーオプションを介した展開モードをサポートします\n  - 幅 + 展開状態をキーとするレンダーキャッシュを使用します\n\nエラーレンダリングの前提：\n\n- 最初のテキストコンテンツが `Error:` で始まる場合、レンダラーはノートブックエラーブロックとしてフォーマットします。\n\n## Python レンダラー（実際の実行出力用）\n\nカーネルバックドの実行レンダリングは以下を期待します：\n\n- セルごとのステータス遷移（`pending/running/complete/error`）\n- オプションの構造化ステータスイベントセクション\n- オプションの JSON 出力ツリー\n- 切り捨て警告 + オプションの `artifact://<id>` ポインター\n\nこのレンダラーの動作は、両者が共有 TUI プリミティブを再利用することを除き、`notebook` JSON 編集結果とは無関係です。\n\n## 8) プレーン Python ツールの動作との相違点\n\n「プレーン Python ツール」が `python` 実行パスを意味する場合：\n\n- `python` はカーネル内でコードを実行し、モードによって状態を永続化し、チャンクをストリームし、リッチディスプレイをキャプチャし、割り込み・タイムアウトを処理し、出力の切り捨て・アーティファクトをサポートします。\n- `notebook` は確定的なノートブック JSON の変更のみを実行します；実行なし、カーネル状態なし、チャンクストリームなし、ディスプレイ出力なし、アーティファクトパイプラインなし。\n\nワークフローで両方が必要な場合：\n\n1. `notebook` でノートブックソースを編集します\n2. `notebook` を経由せず、`python` を通じてコードセルを実行します（コードを手動で渡します）\n\n現在の実装では、`.ipynb` の変更とカーネルコンテキストを通じたノートブックセルの実行の両方を行う単一のツールは提供されていません。\n",
	"ja/runtime-tools/resolve-tool-runtime.md": "---\ntitle: Resolve ツールのランタイム内部構造\ndescription: >-\n  Resolve tool runtime for file path resolution, content fetching, and URL-based\n  resource access.\nsidebar:\n  order: 3\n  label: Resolve ツール\ni18n:\n  sourceHash: 06e8be8c5a3c\n  translator: machine\n---\n\n# Resolve ツールのランタイム内部構造\n\nこのドキュメントでは、coding-agent におけるプレビュー/適用ワークフローのモデリング方法と、カスタムツールが `pushPendingAction` を通じて参加する方法について説明します。\n\n## スコープと主要ファイル\n\n- [`src/tools/resolve.ts`](../../packages/coding-agent/src/tools/resolve.ts)\n- [`src/tools/pending-action.ts`](../../packages/coding-agent/src/tools/pending-action.ts)\n- [`src/tools/ast-edit.ts`](../../packages/coding-agent/src/tools/ast-edit.ts)\n- [`src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts)\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n\n## `resolve` の機能\n\n`resolve` は、保留中のプレビューアクションを確定する隠しツールです。\n\n- `action: \"apply\"` は保留中のアクションに対して `apply(reason)` を実行し、変更を永続化します。\n- `action: \"discard\"` は、`reject(reason)` が提供されていればそれを呼び出し、提供されていなければデフォルトの「Discarded」メッセージでアクションを破棄します。\n\n保留中のアクションが存在しない場合、`resolve` は以下のエラーで失敗します：\n\n- `No pending action to resolve. Nothing to apply or discard.`\n\n## 保留アクションはスタック（LIFO）\n\n保留アクションは `PendingActionStore` にプッシュ/ポップのスタックとして格納されます：\n\n- `push(action)` は新しい保留アクションをスタックの最上部に追加します。\n- `peek()` は現在の最上部のアクションを確認します。\n- `pop()` は最上部のアクションを取り出して返します。\n- `hasPending` はスタックが空でないかどうかを示します。\n\n`resolve` は常に**最上部**の保留アクションを最初に消費します（`pop()`）。そのため、複数のプレビュー生成ツールは登録の逆順で解決されます。\n\n## 組み込みプロデューサーの例（`ast_edit`）\n\n`ast_edit` はまず構造的な置換をプレビューします。プレビューに置換が含まれ、まだ適用されていない場合、以下を含む保留アクションをプッシュします：\n\n- label（人間が読める要約）\n- `sourceToolName`（`ast_edit`）\n- `apply(reason: string)` コールバック — `dryRun: false` で AST 編集を再実行します\n\n`resolve(action=\"apply\", reason=\"...\")` はこのコールバックに `reason` を渡します。\n\n## カスタムツール：`pushPendingAction`\n\nカスタムツールは `CustomToolAPI.pushPendingAction(...)` を通じて resolve 互換の保留アクションを登録できます。\n\n`CustomToolPendingAction`：\n\n- `label: string`（必須）\n- `apply(reason: string): Promise<AgentToolResult<unknown>>`（必須）— 適用時に呼び出されます。`reason` は `resolve` に渡される文字列です\n- `reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>`（オプション）— 破棄時に呼び出されます。戻り値が提供された場合、デフォルトの「Discarded」メッセージを置き換えます\n- `details?: unknown`（オプション）\n- `sourceToolName?: string`（オプション、デフォルトは `\"custom_tool\"`）\n\n### 最小限の使用例\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = pi => ({\n name: \"batch_rename_preview\",\n label: \"Batch Rename Preview\",\n description: \"Previews renames and defers commit to resolve\",\n parameters: pi.typebox.Type.Object({\n  files: pi.typebox.Type.Array(pi.typebox.Type.String()),\n }),\n\n async execute(_toolCallId, params) {\n  const previewSummary = `Prepared rename plan for ${params.files.length} files`;\n\n  pi.pushPendingAction({\n   label: `Batch rename: ${params.files.length} files`,\n   sourceToolName: \"batch_rename_preview\",\n   apply: async (reason) => {\n    // apply writes here\n    return {\n     content: [{ type: \"text\", text: `Applied batch rename. Reason: ${reason}` }],\n    };\n   },\n   reject: async (reason) => {\n    // optional: cleanup or notify on discard\n    return {\n     content: [{ type: \"text\", text: `Discarded batch rename. Reason: ${reason}` }],\n    };\n   },\n  });\n\n  return {\n   content: [{ type: \"text\", text: `${previewSummary}. Call resolve to apply or discard.` }],\n  };\n },\n});\n\nexport default factory;\n```\n\n## ランタイムの利用可能性と障害\n\n`pushPendingAction` は、アクティブセッションの `PendingActionStore` を使用してカスタムツールローダーによって接続されます。\n\nランタイムに保留アクションストアがない場合、`pushPendingAction` は以下のエラーをスローします：\n\n- `Pending action store unavailable for custom tools in this runtime.`\n\n## ツール選択の動作\n\n`PendingActionStore.hasPending` が true の場合、エージェントランタイムはツール選択を `resolve` に偏向させ、通常のツールフローが続行される前に保留中のプレビューが明示的に確定されるようにします。\n\n## 開発者向けガイダンス\n\n- 保留アクションは、明示的な適用/破棄をサポートすべき破壊的または影響度の高い操作にのみ使用してください。\n- `label` は簡潔かつ具体的にしてください。resolve レンダラーの出力に表示されます。\n- `apply(reason)` は決定論的であり、ワンショット実行に十分な冪等性を持つようにしてください。`reason` は情報提供目的であり、動作を変更すべきではありません。\n- 破棄時にクリーンアップが必要な場合（一時的な状態、ロック、通知など）は `reject(reason)` を実装してください。デフォルトメッセージで十分なステートレスプレビューの場合は省略してください。\n- ツールが複数のプレビューをステージングできる場合、LIFO セマンティクスを意識してください：最後にプッシュされたアクションが最初に解決されます。\n",
	"ja/runtime-tools/slash-command-internals.md": "---\ntitle: スラッシュコマンドの内部構造\ndescription: 登録、引数解析、実行ディスパッチを含むスラッシュコマンドシステムの内部構造。\nsidebar:\n  order: 5\n  label: スラッシュコマンド\ni18n:\n  sourceHash: 2cbd44a3de87\n  translator: machine\n---\n\n# スラッシュコマンドの内部構造\n\n本ドキュメントでは、`coding-agent` においてスラッシュコマンドがどのように検出され、重複排除され、インタラクティブモードで表示され、プロンプト時に展開されるかを説明します。\n\n## 実装ファイル\n\n- [`src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n- [`src/capability/slash-command.ts`](../../packages/coding-agent/src/capability/slash-command.ts)\n- [`src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`src/discovery/claude.ts`](../../packages/coding-agent/src/discovery/claude.ts)\n- [`src/discovery/codex.ts`](../../packages/coding-agent/src/discovery/codex.ts)\n- [`src/discovery/claude-plugins.ts`](../../packages/coding-agent/src/discovery/claude-plugins.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n\n## 1) 検出モデル\n\nスラッシュコマンドはケイパビリティ（`id: \"slash-commands\"`）であり、コマンド名をキーとして（`key: cmd => cmd.name`）管理されます。\n\nケイパビリティレジストリは登録済みのすべてのプロバイダーを読み込み、プロバイダーの優先度の降順でソートし、**先着優先**のセマンティクスでキーの重複を排除します。\n\n### プロバイダーの優先順位\n\n現在のスラッシュコマンドプロバイダーと優先度：\n\n1. `native`（OMP）— 優先度 `100`\n2. `claude` — 優先度 `80`\n3. `claude-plugins` — 優先度 `70`\n4. `codex` — 優先度 `70`\n\n同一優先度の挙動：同じ優先度のプロバイダーは登録順を保持します。現在のインポート順では `claude-plugins` が `codex` より先に登録されるため、名前が衝突した場合はプラグインコマンドが codex コマンドより優先されます。\n\n### 名前衝突時の挙動\n\n`slash-commands` における衝突は、ケイパビリティの重複排除によって厳密に解決されます：\n\n- 最も優先度の高いアイテムが `result.items` に保持される\n- 低優先度の重複は `result.all` にのみ残り、`_shadowed = true` としてマークされる\n\nこれはプロバイダー間の衝突だけでなく、1 つのプロバイダーが重複する名前を返した場合にも適用されます。\n\n### ファイルスキャンの挙動\n\nプロバイダーはほとんどの場合 `loadFilesFromDir(...)` を使用し、現在の挙動は以下のとおりです：\n\n- デフォルトは非再帰的マッチング（`*.md`）\n- `gitignore: true`、`hidden: false` でネイティブ glob を使用\n- マッチした各ファイルを読み込み、`SlashCommand` に変換する\n\nそのため、隠しファイルやディレクトリは読み込まれず、無視パスはスキップされます。\n\n## 2) プロバイダー固有のソースパスとローカル優先度\n\n## `native` プロバイダー（`builtin.ts`）\n\n検索ルートは `.xcsh` ディレクトリに由来します：\n\n- プロジェクト: `<cwd>/.xcsh/commands/*.md`\n- ユーザー: `~/.xcsh/agent/commands/*.md`\n\n`getConfigDirs()` はプロジェクトを先に返し、次にユーザーを返すため、**名前が衝突した場合はプロジェクトのネイティブコマンドがユーザーのネイティブコマンドより優先**されます。\n\n## `claude` プロバイダー（`claude.ts`）\n\n以下を読み込みます：\n\n- ユーザー: `~/.claude/commands/*.md`\n- プロジェクト: `<cwd>/.claude/commands/*.md`\n\nプロバイダーはユーザーアイテムをプロジェクトアイテムより先に追加するため、このプロバイダー内での同名衝突では**ユーザーの Claude コマンドがプロジェクトの Claude コマンドより優先**されます。\n\n## `codex` プロバイダー（`codex.ts`）\n\n以下を読み込みます：\n\n- ユーザー: `~/.codex/commands/*.md`\n- プロジェクト: `<cwd>/.codex/commands/*.md`\n\n両側が読み込まれ、ユーザー優先の順序でフラット化されるため、衝突時は**ユーザーの Codex コマンドがプロジェクトの Codex コマンドより優先**されます。\n\nCodex コマンドのコンテンツはフロントマター除去（`parseFrontmatter`）によって解析され、コマンド名はフロントマターの `name` で上書きできます。指定がない場合はファイル名が使用されます。\n\n## `claude-plugins` プロバイダー（`claude-plugins.ts`）\n\n`~/.claude/plugins/installed_plugins.json` からプラグインコマンドのルートを読み込み、`<pluginRoot>/commands/*.md` をスキャンします。\n\n順序はレジストリの反復順と、その JSON データ内のプラグインエントリの順序に従います。追加のソートステップはありません。\n\n## 3) ランタイム `FileSlashCommand` へのマテリアライズ\n\n`src/extensibility/slash-commands.ts` の `loadSlashCommands()` は、ケイパビリティアイテムをプロンプト時に使用される `FileSlashCommand` オブジェクトに変換します。\n\n各コマンドに対して：\n\n1. フロントマター/ボディの解析（`parseFrontmatter`）\n2. 説明のソース：\n   - `frontmatter.description` が存在する場合はそれを使用\n   - それ以外の場合は最初の空でないボディ行（トリミング済み、最大 60 文字で `...`）\n3. 解析済みボディを実行可能テンプレートコンテンツとして保持\n4. `via Claude Code Project` のような表示ソース文字列を計算\n\nフロントマター解析の重大度はソースに依存します：\n\n- `native` レベル -> 解析エラーは `fatal`\n- `user`/`project` レベル -> 解析エラーは `warn` でフォールバック解析あり\n\n### バンドル済みフォールバックコマンド\n\nファイルシステム/プロバイダーコマンドの後、埋め込みコマンドテンプレート（`EMBEDDED_COMMAND_TEMPLATES`）がその名前がまだ存在しない場合に追加されます。\n\n現在の埋め込みセットは `src/task/commands.ts` に由来し、フォールバック（`source: \"bundled\"`）として使用されます。\n\n## 4) インタラクティブモード：コマンドリストの取得元\n\nインタラクティブモードは、オートコンプリートとコマンドルーティングのために複数のコマンドソースを組み合わせます。\n\n構築時に以下から保留中のコマンドリストを構築します：\n\n- 組み込みコマンド（`BUILTIN_SLASH_COMMANDS`。選択されたコマンドの引数補完とインラインヒントを含む）\n- 拡張登録済みスラッシュコマンド（`extensionRunner.getRegisteredCommands(...)`）\n- TypeScript カスタムコマンド（`session.customCommands`）。スラッシュコマンドラベルにマッピング\n- `skills.enableSkillCommands` が有効な場合のオプションのスキルコマンド（`/skill:<name>`）\n\nその後、`init()` は `refreshSlashCommandState(...)` を呼び出してファイルベースのコマンドを読み込み、以下を含む 1 つの `CombinedAutocompleteProvider` をインストールします：\n\n- 上記の保留中のコマンド\n- 検出されたファイルベースのコマンド\n\n`refreshSlashCommandState(...)` は `session.setSlashCommands(...)` も更新するため、プロンプト展開でも同じ検出済みファイルコマンドセットが使用されます。\n\n### リフレッシュのライフサイクル\n\nスラッシュコマンドの状態は以下のタイミングでリフレッシュされます：\n\n- インタラクティブ初期化中\n- `/move` で作業ディレクトリが変更された後（`handleMoveCommand` が `resetCapabilities()` を呼び出し、その後 `refreshSlashCommandState(newCwd)` を呼び出す）\n\nコマンドディレクトリの継続的なファイルウォッチャーはありません。\n\n### その他の表示箇所\n\nExtensions ダッシュボードも `slash-commands` ケイパビリティを読み込み、`_shadowed` の重複を含む、アクティブ/シャドウされたコマンドエントリを表示します。\n\n## 5) プロンプトパイプラインの配置\n\n`AgentSession.prompt(...)` のスラッシュ処理順序（`expandPromptTemplates !== false` の場合）：\n\n1. **拡張コマンド**（`#tryExecuteExtensionCommand`）  \n   `/name` が拡張登録済みコマンドに一致した場合、ハンドラーが即座に実行されプロンプトが返ります。\n2. **TypeScript カスタムコマンド**（`#tryExecuteCustomCommand`）  \n   境界のみ：一致した場合、実行されて以下を返す可能性があります：\n   - `string` -> プロンプトテキストをその文字列で置換\n   - `void/undefined` -> 処理済みとして扱われる。LLM プロンプトなし\n3. **ファイルベースのスラッシュコマンド**（`expandSlashCommand`）  \n   テキストがまだ `/` で始まる場合、マークダウンコマンド展開を試みる。\n4. **プロンプトテンプレート**（`expandPromptTemplate`）  \n   スラッシュ/カスタム処理の後に適用される。\n5. **デリバリー**\n   - アイドル: プロンプトは即座にエージェントに送信される\n   - ストリーミング: プロンプトは `streamingBehavior` に応じて steer/フォローアップとしてキューに入れられる\n\nこれがスラッシュコマンド展開がプロンプトテンプレート展開より前に位置する理由であり、カスタムコマンドがファイルコマンドマッチングの前に先頭のスラッシュを変換できる理由でもあります。\n\n## 6) ファイルベースのスラッシュコマンドの展開セマンティクス\n\n`expandSlashCommand(text, fileCommands)` の挙動：\n\n- テキストが `/` で始まる場合のみ実行\n- `/` の後の最初のトークンからコマンド名を解析\n- `parseCommandArgs` で残りのテキストから引数を解析\n- 読み込まれた `fileCommands` で完全一致を検索\n- 一致した場合、以下を適用：\n  - 位置指定置換: `$1`、`$2`、...\n  - 集約置換: `$ARGUMENTS` および `$@`\n  - その後 `{ args, ARGUMENTS, arguments }` で `prompt.render` によるテンプレートレンダリング\n- 一致しない場合、元のテキストをそのまま返す\n\n### `parseCommandArgs` の注意事項\n\nパーサーは単純なクォート対応の分割処理です：\n\n- スペースを保持するための `'シングル'` および `\"ダブル\"` クォートをサポート\n- クォートの区切り文字を除去\n- バックスラッシュエスケープルールは実装されていない\n- 未対応クォートはエラーにならず、パーサーは末尾まで消費する\n\n## 7) 未知の `/...` の挙動\n\n未知のスラッシュ入力はコアのスラッシュロジックによって**拒否されません**。\n\nコマンドが拡張/カスタム/ファイルの各レイヤーで処理されない場合、`expandSlashCommand` は元のテキストを返し、リテラルの `/...` プロンプトは通常のプロンプトテンプレート展開と LLM デリバリーを経由します。\n\nインタラクティブモードは `InputController` 内で多くの組み込みコマンドを個別に処理します（例: `/settings`、`/model`、`/mcp`、`/move`、`/exit`）。これらは `session.prompt(...)` の前に消費されるため、そのパスではファイルコマンド展開に到達しません。\n\n## 8) ストリーミング時とアイドル時の違い\n\n## アイドルパス\n\n- `session.prompt(\"/x ...\")` はコマンドパイプラインを実行し、コマンドを即座に実行するか、展開されたテキストを直接送信します。\n\n## ストリーミングパス（`session.isStreaming === true`）\n\n- `prompt(...)` は拡張/カスタム/ファイル/テンプレートの変換を先に実行\n- その後 `streamingBehavior` が必要：\n  - `\"steer\"` -> 割り込みメッセージをキューに追加（`agent.steer`）\n  - `\"followUp\"` -> ターン後メッセージをキューに追加（`agent.followUp`）\n- `streamingBehavior` が省略された場合、プロンプトはエラーをスロー\n\n### コマンド固有の重要なストリーミング挙動\n\n- 拡張コマンドはストリーミング中でも即座に実行されます（テキストとしてキューに入れられません）。\n- `steer(...)`/`followUp(...)` ヘルパーメソッドは拡張コマンドを拒否します（`#throwIfExtensionCommand`）。同期的に実行する必要があるハンドラーにコマンドテキストがキューイングされることを防ぐためです。\n- コンパクションキューのリプレイは `isKnownSlashCommand(...)` を使用して、キューに入ったエントリを `session.prompt(...)` 経由でリプレイするか（既知のスラッシュコマンドの場合）、生の steer/フォローアップメソッド経由でリプレイするかを判断します。\n\n## 9) エラー処理と障害の表面化\n\n- プロバイダーの読み込み失敗は分離されます。レジストリは警告を収集し、他のプロバイダーの処理を継続します。\n- 無効なスラッシュコマンドアイテム（name/path/content が欠如しているか、level が無効）はケイパビリティ検証によってドロップされます。\n- フロントマター解析の失敗：\n  - ネイティブコマンド: 致命的な解析エラーがバブルアップ\n  - 非ネイティブコマンド: 警告 + フォールバックのキー/値解析\n- 拡張/カスタムコマンドハンドラーの例外はキャッチされ、拡張エラーチャネル経由で報告されます（または拡張ランナーのないカスタムコマンドの場合はロガーフォールバック）。処理済みとして扱われ、意図しないフォールバック実行は行われません。\n",
	"ja/runtime-tools/task-agent-discovery.md": "---\ntitle: タスクエージェントの検出と選択\ndescription: 特化したサブエージェントタイプへの作業ルーティングのための、タスクエージェントの検出と選択ロジック。\nsidebar:\n  order: 6\n  label: タスクエージェントの検出\ni18n:\n  sourceHash: 8cf42457c672\n  translator: machine\n---\n\n# タスクエージェントの検出と選択\n\nこのドキュメントでは、タスクサブシステムがエージェント定義を検出し、複数のソースをマージし、実行時にリクエストされたエージェントを解決する方法について説明します。\n\n優先順位、無効な定義の処理、エージェントを実質的に利用不可にする可能性のあるスポーン/深度制約を含む、現在実装されているランタイムの動作について説明します。\n\n## 実装ファイル\n\n- [`src/task/discovery.ts`](../../packages/coding-agent/src/task/discovery.ts)\n- [`src/task/agents.ts`](../../packages/coding-agent/src/task/agents.ts)\n- [`src/task/types.ts`](../../packages/coding-agent/src/task/types.ts)\n- [`src/task/index.ts`](../../packages/coding-agent/src/task/index.ts)\n- [`src/task/commands.ts`](../../packages/coding-agent/src/task/commands.ts)\n- [`src/prompts/agents/task.md`](../../packages/coding-agent/src/prompts/agents/task.md)\n- [`src/prompts/tools/task.md`](../../packages/coding-agent/src/prompts/tools/task.md)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/config.ts`](../../packages/coding-agent/src/config.ts)\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts)\n\n---\n\n## エージェント定義の構造\n\nタスクエージェントは `AgentDefinition`（`src/task/types.ts`）に正規化されます：\n\n- `name`、`description`、`systemPrompt`（有効なロード済みエージェントに必須）\n- オプションの `tools`、`spawns`、`model`、`thinkingLevel`、`output`\n- `source`: `\"bundled\" | \"user\" | \"project\"`\n- オプションの `filePath`\n\nパースは `parseAgentFields()`（`src/discovery/helpers.ts`）を介したフロントマターから行われます：\n\n- `name` または `description` が欠落 => 無効（`null`）、呼び出し元はパース失敗として扱う\n- `tools` はCSVまたは配列を受け付け、指定された場合は `submit_result` が自動追加される\n- `spawns` は `*`、CSV、または配列を受け付ける\n- 後方互換動作：`spawns` が欠落しているが `tools` に `task` が含まれる場合、`spawns` は `*` になる\n- `output` は不透明なスキーマデータとしてそのまま渡される\n\n## バンドルエージェント\n\nバンドルエージェントはビルド時にテキストインポートを使用して埋め込まれます（`src/task/agents.ts`）。\n\n`EMBEDDED_AGENT_DEFS` は以下を定義します：\n\n- プロンプトファイルからの `explore`、`plan`、`designer`、`reviewer`\n- 共有の `task.md` 本体と注入されたフロントマターからの `task` と `quick_task`\n\nロードパス：\n\n1. `loadBundledAgents()` が `parseAgent(..., \"bundled\", \"fatal\")` で埋め込みマークダウンをパース\n2. 結果はメモリ内にキャッシュされる（`bundledAgentsCache`）\n3. `clearBundledAgentsCache()` はテスト専用のキャッシュリセット\n\nバンドルパースは `level: \"fatal\"` を使用するため、不正なバンドルフロントマターはスローし、検出全体が失敗する可能性があります。\n\n## ファイルシステムとプラグインの検出\n\n`discoverAgents(cwd, home)`（`src/task/discovery.ts`）は、バンドル定義を追加する前に複数のソースからエージェントをマージします。\n\n### 検出入力\n\n1. `getConfigDirs(\"agents\", { project: false })` からのユーザー設定エージェントディレクトリ\n2. `findAllNearestProjectConfigDirs(\"agents\", cwd)` からの最も近いプロジェクトエージェントディレクトリ\n3. `agents/` サブディレクトリを持つClaudeプラグインルート（`listClaudePluginRoots(home)`）\n4. バンドルエージェント（`loadBundledAgents()`）\n\n### 実際のソース順序\n\nソースファミリーの順序は `getConfigDirs(\"\", { project: false })` から取得され、`src/config.ts` の `priorityList` から導出されます：\n\n1. `.xcsh`\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\n各ソースファミリーについて、検出順序は：\n\n1. そのソースの最も近いプロジェクトディレクトリ（見つかった場合）\n2. そのソースのユーザーディレクトリ\n\nすべてのソースファミリーディレクトリの後に、プラグインの `agents/` ディレクトリが追加されます（プロジェクトスコープのプラグインが先、次にユーザースコープ）。\n\nバンドルエージェントは最後に追加されます。\n\n### 重要な注意事項：古いコメントと現在のコード\n\n`discovery.ts` のヘッダーコメントにはまだ `.pi` が記載されており、`.codex`/`.gemini` は記載されていません。実際のランタイム順序は `src/config.ts` によって駆動され、現在は `.xcsh`、`.claude`、`.codex`、`.gemini` を使用しています。\n\n## マージと衝突ルール\n\n検出はエージェントの正確な `agent.name` による先勝ち重複排除を使用します：\n\n- `Set<string>` が確認済みの名前を追跡\n- ロードされたエージェントはディレクトリ順にフラット化され、名前が未確認の場合のみ保持\n- バンドルエージェントは同じセットに対してフィルタリングされ、まだ未確認の場合のみ追加\n\n影響：\n\n- 同じソースファミリーでは、プロジェクトがユーザーをオーバーライド\n- 優先度の高いソースファミリーが低いものをオーバーライド（`.xcsh` が `.claude` より先、など）\n- 非バンドルエージェントは同名のバンドルエージェントをオーバーライド\n- 名前のマッチングは大文字小文字を区別（`Task` と `task` は別物）\n- 1つのディレクトリ内では、重複排除の前にマークダウンファイルがファイル名の辞書順で読み込まれる\n\n## 無効/欠落エージェントファイルの動作\n\nディレクトリごと（`loadAgentsFromDir`）：\n\n- 読み取り不可/存在しないディレクトリ：空として扱われる（`readdir(...).catch(() => [])`）\n- ファイル読み取りまたはパース失敗：警告がログに記録され、ファイルはスキップ\n- パースパスは `parseAgent(..., level: \"warn\")` を使用\n\nフロントマターの失敗動作は `parseFrontmatter` から来ます：\n\n- `warn` レベルのパースエラーは警告をログに記録\n- パーサーはシンプルな `key: value` 行パーサーにフォールバック\n- 必須フィールドがまだ欠落している場合、`parseAgentFields` が失敗し、`AgentParsingError` がスローされ呼び出し元でキャッチされる（ファイルはスキップ）\n\n最終的な効果：1つの不正なカスタムエージェントファイルが他のファイルの検出を中断しません。\n\n## エージェントの検索と選択\n\n検索は正確な名前による線形検索です：\n\n- `getAgent(agents, name)` => `agents.find(a => a.name === name)`\n\nタスク実行時（`TaskTool.execute`）：\n\n1. 呼び出し時にエージェントが再検出される（`discoverAgents(this.session.cwd)`）\n2. リクエストされた `params.agent` が `getAgent` で解決される\n3. エージェントが見つからない場合は即時ツールレスポンスを返す：\n   - `Unknown agent \"...\". Available: ...`\n   - サブプロセスは実行されない\n\n### 説明と実行時検出の違い\n\n`TaskTool.create()` は初期化時（`buildDescription`）の検出結果からツール説明を構築します。\n\n`execute()` はエージェントを再検出します。そのため、セッション中にエージェントファイルが変更された場合、ランタイムのセットは以前のツール説明にリストされていたものと異なる可能性があります。\n\n## 構造化出力のガードレールとスキーマの優先順位\n\n`TaskTool.execute` でのランタイム出力スキーマの優先順位：\n\n1. エージェントフロントマターの `output`\n2. タスク呼び出しの `params.schema`\n3. 親セッションの `outputSchema`\n\n（`effectiveOutputSchema = effectiveAgent.output ?? outputSchema ?? this.session.outputSchema`）\n\n`src/prompts/tools/task.md` のプロンプト時ガードレールテキストは、構造化出力エージェント（`explore`、`reviewer`）の不一致動作について警告します：散文中の出力フォーマット指示が組み込みスキーマと競合し、`null` 出力を生成する可能性があります。\n\nこれはガイダンスであり、`discoverAgents` 内のハードなランタイムバリデーションロジックではありません。\n\n## コマンド検出との関係\n\n`src/task/commands.ts` はワークフローコマンド（エージェント定義ではない）のための並行インフラストラクチャですが、全体的に同じパターンに従います：\n\n- まずケイパビリティプロバイダーから検出\n- 名前による先勝ちで重複排除\n- まだ未確認の場合はバンドルコマンドを追加\n- `getCommand` による正確な名前検索\n\n`src/task/index.ts` では、コマンドヘルパーがエージェント検出ヘルパーとともに再エクスポートされます。エージェント検出自体はランタイムでコマンド検出に依存しません。\n\n## 検出を超えた可用性制約\n\nエージェントは検出可能であっても、実行ガードレールのために実行不可能な場合があります。\n\n### 親のスポーンポリシー\n\n`TaskTool.execute` は `session.getSessionSpawns()` をチェックします：\n\n- `\"*\"` => すべて許可\n- `\"\"` => すべて拒否\n- CSVリスト => リストされた名前のみ許可\n\n拒否された場合：即時 `Cannot spawn '...'. Allowed: ...` レスポンス。\n\n### ブロックされた自己再帰の環境ガード\n\n`PI_BLOCKED_AGENT` はツール構築時に読み取られます。リクエストが一致する場合、再帰防止メッセージで実行が拒否されます。\n\n### 再帰深度ゲーティング（子セッション内のタスクツールの可用性）\n\n`runSubprocess`（`src/task/executor.ts`）にて：\n\n- 深度は `taskDepth` から計算\n- `task.maxRecursionDepth` がカットオフを制御\n- 最大深度に達した場合：\n  - `task` ツールが子ツールリストから削除\n  - 子の `spawns` 環境が空に設定\n\nそのため、エージェント定義に `spawns` が含まれていても、深い階層ではさらなるタスクをスポーンできません。\n\n## プランモードの注意事項（現在の実装）\n\n`TaskTool.execute` はプランモード用の `effectiveAgent` を計算します（プランモードプロンプトを先頭に追加、読み取り専用ツールサブセットを強制、スポーンをクリア）が、`runSubprocess` は `effectiveAgent` ではなく `agent` で呼び出されます。\n\n現在の影響：\n\n- モデルオーバーライド / 思考レベル / 出力スキーマは `effectiveAgent` から導出される\n- `effectiveAgent` からのシステムプロンプトとツール/スポーン制限はこの呼び出しパスでは渡されない\n\nこれはプランモードの動作期待値を読む際に知っておくべき実装上の注意事項です。\n",
	"ja/sessions/compaction.md": "---\ntitle: コンパクションとブランチサマリー\ndescription: 長時間セッションにおけるコンテキストウィンドウのコンパクションとブランチサマリー生成。\nsidebar:\n  order: 5\n  label: コンパクション\ni18n:\n  sourceHash: dae425a900d8\n  translator: machine\n---\n\n# コンパクションとブランチサマリー\n\nコンパクションとブランチサマリーは、過去の作業コンテキストを失うことなく長時間セッションを使い続けられるようにする2つのメカニズムです。\n\n- **コンパクション**は、現在のブランチ上で古い履歴をサマリーに書き換えます。\n- **ブランチサマリー**は、`/tree` ナビゲーション時に放棄されたブランチのコンテキストをキャプチャします。\n\nどちらもセッションエントリとして永続化され、LLM入力の再構築時にユーザーコンテキストメッセージに変換されます。\n\n## 主要な実装ファイル\n\n- `src/session/compaction/compaction.ts`\n- `src/session/compaction/branch-summarization.ts`\n- `src/session/compaction/pruning.ts`\n- `src/session/compaction/utils.ts`\n- `src/session/session-manager.ts`\n- `src/session/agent-session.ts`\n- `src/session/messages.ts`\n- `src/extensibility/hooks/types.ts`\n- `src/config/settings-schema.ts`\n\n## セッションエントリモデル\n\nコンパクションとブランチサマリーは、通常のassistant/userメッセージではなく、ファーストクラスのセッションエントリです。\n\n- `CompactionEntry`\n  - `type: \"compaction\"`\n  - `summary`、オプションの `shortSummary`\n  - `firstKeptEntryId`（コンパクション境界）\n  - `tokensBefore`\n  - オプションの `details`、`preserveData`、`fromExtension`\n- `BranchSummaryEntry`\n  - `type: \"branch_summary\"`\n  - `fromId`、`summary`\n  - オプションの `details`、`fromExtension`\n\nコンテキストが再構築される場合（`buildSessionContext`）：\n\n1. アクティブパス上の最新のコンパクションが1つの `compactionSummary` メッセージに変換されます。\n2. `firstKeptEntryId` からコンパクションポイントまでの保持されたエントリが再度含まれます。\n3. パス上のそれ以降のエントリが追加されます。\n4. `branch_summary` エントリが `branchSummary` メッセージに変換されます。\n5. `custom_message` エントリが `custom` メッセージに変換されます。\n\nこれらのカスタムロールは、`convertToLlm()` で静的テンプレートを使用してLLM向けのユーザーメッセージに変換されます：\n\n- `prompts/compaction/compaction-summary-context.md`\n- `prompts/compaction/branch-summary-context.md`\n\n## コンパクションパイプライン\n\n### トリガー\n\nコンパクションは3つの方法で実行できます：\n\n1. **手動**: `/compact [instructions]` が `AgentSession.compact(...)` を呼び出します。\n2. **自動オーバーフロー回復**: コンテキストオーバーフローに一致するアシスタントエラーの後。\n3. **自動閾値コンパクション**: コンテキストが閾値を超えた場合の成功ターンの後。\n\n### コンパクションの形状（ビジュアル）\n\n```text\nBefore compaction:\n\n  entry:  0     1     2     3      4     5     6      7      8     9\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┘\n                └────────┬───────┘ └──────────────┬──────────────┘\n               messagesToSummarize            kept messages\n                                   ↑\n                          firstKeptEntryId (entry 4)\n\nAfter compaction (new entry appended):\n\n  entry:  0     1     2     3      4     5     6      7      8     9      10\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┬─────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │ cmp │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┴─────┘\n               └──────────┬──────┘ └──────────────────────┬───────────────────┘\n                 not sent to LLM                    sent to LLM\n                                                         ↑\n                                              starts from firstKeptEntryId\n\nWhat the LLM sees:\n\n  ┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐\n  │ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │\n  └────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘\n       ↑         ↑      └─────────────────┬────────────────┘\n    prompt   from cmp          messages from firstKeptEntryId\n```\n\n### オーバーフロー再試行と閾値コンパクション\n\n2つの自動パスは意図的に異なります：\n\n- **オーバーフロー再試行コンパクション**\n  - トリガー: 現在のモデルのアシスタントエラーがコンテキストオーバーフローとして検出された場合。\n  - 失敗したアシスタントエラーメッセージは、再試行前にアクティブなエージェント状態から削除されます。\n  - 自動コンパクションが `reason: \"overflow\"` および `willRetry: true` で実行されます。\n  - 成功時、コンパクション後にエージェントが自動的に続行します（`agent.continue()`）。\n\n- **閾値コンパクション**\n  - トリガー: `contextTokens > contextWindow - compaction.reserveTokens`。\n  - `reason: \"threshold\"` および `willRetry: false` で実行されます。\n  - 成功時、`compaction.autoContinue !== false` の場合、合成プロンプトが注入されます：\n    - `\"Continue if you have next steps.\"`\n\n### コンパクション前のプルーニング\n\nコンパクションチェックの前に、ツール結果のプルーニングが実行される場合があります（`pruneToolOutputs`）。\n\nデフォルトのプルーニングポリシー：\n\n- 最新の `40_000` トークンのツール出力を保護します。\n- 合計で少なくとも `20_000` トークンの推定節約が必要です。\n- `skill` または `read` からのツール結果は絶対にプルーニングしません。\n\nプルーニングされたツール結果は以下に置き換えられます：\n\n- `[Output truncated - N tokens]`\n\nプルーニングによってエントリが変更された場合、セッションストレージが書き換えられ、コンパクション判断の前にエージェントのメッセージ状態がリフレッシュされます。\n\n### 境界とカットポイントのロジック\n\n`prepareCompaction()` は、最後のコンパクションエントリ（存在する場合）以降のエントリのみを考慮します。\n\n1. 前回のコンパクションインデックスを検索します。\n2. `boundaryStart = prevCompactionIndex + 1` を計算します。\n3. 利用可能な場合、測定された使用率を使用して `keepRecentTokens` を調整します。\n4. 境界ウィンドウに対して `findCutPoint()` を実行します。\n\n有効なカットポイントには以下が含まれます：\n\n- ロールが `user`、`assistant`、`bashExecution`、`hookMessage`、`branchSummary`、`compactionSummary` のメッセージエントリ\n- `custom_message` エントリ\n- `branch_summary` エントリ\n\nハードルール: `toolResult` でカットすることはありません。\n\nカットポイントの直前にメッセージ以外のメタデータエントリ（`model_change`、`thinking_level_change`、ラベルなど）がある場合、メッセージまたはコンパクション境界に到達するまでカットインデックスを後方に移動して、保持領域に含めます。\n\n### スプリットターン処理\n\nカットポイントがユーザーターンの開始位置でない場合、コンパクションはそれをスプリットターンとして扱います。\n\nターン開始の検出では、以下をユーザーターンの境界として扱います：\n\n- `message.role === \"user\"`\n- `message.role === \"bashExecution\"`\n- `custom_message` エントリ\n- `branch_summary` エントリ\n\nスプリットターンコンパクションは2つのサマリーを生成します：\n\n1. 履歴サマリー（`messagesToSummarize`）\n2. ターンプレフィックスサマリー（`turnPrefixMessages`）\n\n最終的に保存されるサマリーは以下のように統合されます：\n\n```markdown\n<history summary>\n\n---\n\n**Turn Context (split turn):**\n\n<turn prefix summary>\n```\n\n### サマリー生成\n\n`compact(...)` はシリアライズされた会話テキストからサマリーを構築します：\n\n1. `convertToLlm()` でメッセージを変換します。\n2. `serializeConversation()` でシリアライズします。\n3. `<conversation>...</conversation>` でラップします。\n4. オプションで `<previous-summary>...</previous-summary>` を含めます。\n5. オプションでフックコンテキストを `<additional-context>` リストとして注入します。\n6. `SUMMARIZATION_SYSTEM_PROMPT` で要約プロンプトを実行します。\n\nプロンプトの選択：\n\n- 初回コンパクション: `compaction-summary.md`\n- 前回のサマリーがある反復コンパクション: `compaction-update-summary.md`\n- スプリットターンの2回目パス: `compaction-turn-prefix.md`\n- 短いUIサマリー: `compaction-short-summary.md`\n\nリモート要約モード：\n\n- `compaction.remoteEndpoint` が設定されている場合、コンパクションは以下をPOSTします：\n  - `{ systemPrompt, prompt }`\n- 少なくとも `{ summary }` を含むJSONが期待されます。\n\n### サマリーにおけるファイル操作コンテキスト\n\nコンパクションは、アシスタントのツール呼び出しを使用して累積的なファイルアクティビティを追跡します：\n\n- `read(path)` → 読み取りセット\n- `write(path)` → 変更セット\n- `edit(path)` → 変更セット\n\n累積動作：\n\n- 前回のエントリがpi生成（`fromExtension !== true`）の場合のみ、前回のコンパクション詳細を含めます。\n- スプリットターンでは、ターンプレフィックスのファイル操作も含めます。\n- `readFiles` は変更されたファイルを除外します。\n\nサマリーテキストには、プロンプトテンプレート経由でファイルタグが追加されます：\n\n```xml\n<read-files>\n...\n</read-files>\n<modified-files>\n...\n</modified-files>\n```\n\n### 永続化と再読み込み\n\nサマリー生成（またはフック提供のサマリー）の後、エージェントセッションは：\n\n1. `appendCompaction(...)` で `CompactionEntry` を追加します。\n2. `buildSessionContext()` でコンテキストを再構築します。\n3. ライブエージェントメッセージを再構築されたコンテキストに置き換えます。\n4. `session_compact` フックイベントを発行します。\n\n## ブランチ要約パイプライン\n\nブランチ要約はトークンオーバーフローではなく、ツリーナビゲーションに関連付けられています。\n\n### トリガー\n\n`navigateTree(...)` 中：\n\n1. `collectEntriesForBranchSummary(...)` を使用して、古いリーフから共通祖先までの放棄されたエントリを計算します。\n2. 呼び出し元がサマリーを要求した場合（`options.summarize`）、リーフを切り替える前にサマリーを生成します。\n3. サマリーが存在する場合、`branchWithSummary(...)` を使用してナビゲーションターゲットに添付します。\n\nこれは通常、`branchSummary.enabled` が有効な場合に `/tree` フローによって駆動されます。\n\n### ブランチ切り替えの形状（ビジュアル）\n\n```text\nTree before navigation:\n\n         ┌─ B ─ C ─ D (old leaf, being abandoned)\n    A ───┤\n         └─ E ─ F (target)\n\nCommon ancestor: A\nEntries to summarize: B, C, D\n\nAfter navigation with summary:\n\n         ┌─ B ─ C ─ D ─ [summary of B,C,D]\n    A ───┤\n         └─ E ─ F (new leaf)\n```\n\n### 準備とトークンバジェット\n\n`generateBranchSummary(...)` は以下のようにバジェットを計算します：\n\n- `tokenBudget = model.contextWindow - branchSummary.reserveTokens`\n\n`prepareBranchEntries(...)` は以下を実行します：\n\n1. 最初のパス: 前回のpi生成 `branch_summary` の詳細を含む、すべての要約対象エントリから累積ファイル操作を収集します。\n2. 2回目のパス: 最新→最古の順にウォークし、トークンバジェットに達するまでメッセージを追加します。\n3. 最近のコンテキストの保持を優先します。\n4. 継続性のために、バジェット境界付近の大きなサマリーエントリを含める場合があります。\n\nコンパクションエントリは、ブランチ要約入力時にメッセージ（`compactionSummary`）として含まれます。\n\n### サマリー生成と永続化\n\nブランチ要約は：\n\n1. 選択されたメッセージを変換およびシリアライズします。\n2. `<conversation>` でラップします。\n3. カスタム指示が提供されている場合はそれを使用し、そうでなければ `branch-summary.md` を使用します。\n4. `SUMMARIZATION_SYSTEM_PROMPT` で要約モデルを呼び出します。\n5. `branch-summary-preamble.md` を先頭に追加します。\n6. ファイル操作タグを追加します。\n\n結果はオプションの詳細（`readFiles`、`modifiedFiles`）を持つ `BranchSummaryEntry` として保存されます。\n\n## 拡張機能とフックのタッチポイント\n\n### `session_before_compact`\n\nコンパクション前のフック。\n\n以下が可能です：\n\n- コンパクションのキャンセル（`{ cancel: true }`）\n- 完全なカスタムコンパクションペイロードの提供（`{ compaction: CompactionResult }`）\n\n### `session.compacting`\n\nデフォルトコンパクションのプロンプト/コンテキストカスタマイズフック。\n\n以下を返すことができます：\n\n- `prompt`（基本サマリープロンプトのオーバーライド）\n- `context`（`<additional-context>` に注入される追加コンテキスト行）\n- `preserveData`（コンパクションエントリに保存される）\n\n### `session_compact`\n\n保存された `compactionEntry` と `fromExtension` フラグを含むコンパクション後の通知。\n\n### `session_before_tree`\n\nデフォルトのブランチサマリー生成前に、ツリーナビゲーション時に実行されます。\n\n以下が可能です：\n\n- ナビゲーションのキャンセル\n- ユーザーが要約を要求した場合に使用されるカスタム `{ summary: { summary, details } }` の提供\n\n### `session_tree`\n\n新しい/古いリーフとオプションのサマリーエントリを公開するナビゲーション後のイベント。\n\n## ランタイムの動作と障害セマンティクス\n\n- 手動コンパクションは、最初に現在のエージェント操作を中止します。\n- `abortCompaction()` は、手動と自動の両方のコンパクションコントローラーをキャンセルします。\n- 自動コンパクションは、UI/状態更新のために開始/終了セッションイベントを発行します。\n- 自動コンパクションは、複数のモデル候補を試行し、一時的な障害を再試行できます。\n- オーバーフローエラーは、コンパクションによって処理されるため、汎用的な再試行パスから除外されます。\n- 自動コンパクションが失敗した場合：\n  - オーバーフローパスは `Context overflow recovery failed: ...` を発行します\n  - 閾値パスは `Auto-compaction failed: ...` を発行します\n- ブランチ要約は、中止シグナル（例：Escape）によりキャンセルでき、キャンセル/中止されたナビゲーション結果を返します。\n\n## 設定とデフォルト値\n\n`settings-schema.ts` より：\n\n- `compaction.enabled` = `true`\n- `compaction.reserveTokens` = `16384`\n- `compaction.keepRecentTokens` = `20000`\n- `compaction.autoContinue` = `true`\n- `compaction.remoteEndpoint` = `undefined`\n- `branchSummary.enabled` = `false`\n- `branchSummary.reserveTokens` = `16384`\n\nこれらの値は、`AgentSession` およびコンパクション/ブランチ要約モジュールによって実行時に使用されます。\n",
	"ja/sessions/handoff-generation-pipeline.md": "---\ntitle: ハンドオフ生成パイプライン\ndescription: チームコラボレーションのためのポータブルなセッションサマリーを作成するハンドオフ生成パイプライン。\nsidebar:\n  order: 8\n  label: ハンドオフパイプライン\ni18n:\n  sourceHash: 03666084b5ac\n  translator: machine\n---\n\n# `/handoff` 生成パイプライン\n\n本ドキュメントでは、コーディングエージェントが現時点で `/handoff` を実装する方法について説明します。トリガーパス、生成プロンプト、補完キャプチャ、セッション切り替え、コンテキスト再注入を対象とします。\n\n## 対象範囲\n\n対象:\n\n- インタラクティブな `/handoff` コマンドディスパッチ\n- `AgentSession.handoff()` のライフサイクルと状態遷移\n- ハンドオフ出力がアシスタント出力からキャプチャされる方法\n- 旧セッションと新セッションがハンドオフデータを永続化する際の違い\n- 成功・キャンセル・失敗時のUI動作\n\n対象外:\n\n- 汎用ツリーナビゲーション／ブランチ内部\n- ハンドオフ以外のセッションコマンド（`/new`、`/fork`、`/resume`）\n\n## 実装ファイル\n\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n\n## トリガーパス\n\n1. `/handoff` はビルトインのスラッシュコマンドメタデータ（`slash-commands.ts`）にオプションのインラインヒント `[focus instructions]` 付きで宣言されます。\n2. インタラクティブな入力処理（`InputController`）において、`/handoff` または `/handoff ...` に一致する送信テキストが通常のプロンプト送信前にインターセプトされます。\n3. エディターがクリアされ、`handleHandoffCommand(customInstructions?)` が呼び出されます。\n4. `CommandController.handleHandoffCommand` は現在のエントリーを使用してプリフライトガードを実行します:\n   - `type === \"message\"` のエントリー数をカウントします。\n   - `< 2` の場合、`Nothing to hand off (no messages yet)` と警告して返ります。\n\n同じ最小コンテンツガードが `AgentSession.handoff()` 内にも存在し、違反時はスローされます。これによりUIとセッション両レイヤーで安全性が二重化されます。\n\n## エンドツーエンドのライフサイクル\n\n### 1) ハンドオフ生成の開始\n\n`AgentSession.handoff(customInstructions?)`:\n\n- 現在のブランチエントリーを読み込む（`sessionManager.getBranch()`）\n- 最小メッセージ数を検証する（`>= 2`）\n- `#handoffAbortController` を作成する\n- 構造化されたハンドオフドキュメント（`Goal`、`Constraints & Preferences`、`Progress`、`Key Decisions`、`Critical Context`、`Next Steps`）を要求する固定のインラインプロンプトを構築する\n- カスタム指示が指定されている場合は `Additional focus: ...` を追加する\n\nプロンプトは以下の方法で送信されます:\n\n```ts\nawait this.prompt(handoffPrompt, { expandPromptTemplates: false });\n```\n\n`expandPromptTemplates: false` により、この内部指示ペイロードに対するスラッシュ／プロンプトテンプレート展開が防止されます。\n\n### 2) 補完のキャプチャ\n\nプロンプト送信前に、`handoff()` がセッションイベントをサブスクライブして `agent_end` を待機します。\n\n`agent_end` 時に、エージェントの状態から最新の `assistant` メッセージを逆順でスキャンし、`type === \"text\"` のすべての `content` ブロックを `\\n` で連結することでハンドオフテキストを抽出します。\n\n抽出に関する重要な前提条件:\n\n- テキストブロックのみが使用され、非テキストコンテンツは無視されます。\n- 最新のアシスタントメッセージがハンドオフ生成に対応していることを前提としています。\n- マークダウンセクションのパースやフォーマット適合性の検証は行いません。\n- アシスタント出力にテキストブロックがない場合、ハンドオフは欠損として扱われます。\n\n### 3) キャンセルチェック\n\n以下のいずれかの条件が成立した場合、`handoff()` は `undefined` を返します:\n\n- キャプチャされたハンドオフテキストがない、または\n- `#handoffAbortController.signal.aborted` が true である\n\n`finally` 内で常に `#handoffAbortController` がクリアされます。\n\n### 4) 新セッションの作成\n\nテキストがキャプチャされ、かつ中断されていない場合:\n\n1. 現在のセッションライターをフラッシュする（`sessionManager.flush()`）\n2. 新しいセッションを開始する（`sessionManager.newSession()`）\n3. インメモリのエージェント状態をリセットする（`agent.reset()`）\n4. `agent.sessionId` を新しいセッションIDに再バインドする\n5. キューに入ったコンテキスト配列をクリアする（`#steeringMessages`、`#followUpMessages`、`#pendingNextTurnMessages`）\n6. Todoリマインダーカウンターをリセットする\n\n`newSession()` は新しいヘッダーと空のエントリーリスト（リーフを `null` にリセット）を作成します。ハンドオフパスでは `parentSession` は渡されません。\n\n### 5) ハンドオフコンテキストの注入\n\n生成されたハンドオフドキュメントはラップされ、新しいセッションに `custom_message` エントリーとして追加されます:\n\n```text\n<handoff-context>\n...handoff text...\n</handoff-context>\n\nThe above is a handoff document from a previous session. Use this context to continue the work seamlessly.\n```\n\n挿入呼び出し:\n\n```ts\nthis.sessionManager.appendCustomMessageEntry(\"handoff\", handoffContent, true);\n```\n\nセマンティクス:\n\n- `customType`: `\"handoff\"`\n- `display`: `true`（TUIリビルドで表示される）\n- エントリータイプ: `custom_message`（LLMコンテキストに参加する）\n\n### 6) アクティブなエージェントコンテキストの再構築\n\n注入後:\n\n1. `sessionManager.buildSessionContext()` が現在のリーフのメッセージリストを解決する\n2. `agent.replaceMessages(sessionContext.messages)` により注入されたハンドオフメッセージがアクティブコンテキストになる\n3. メソッドが `{ document: handoffText }` を返す\n\nこの時点で、新しいセッションのアクティブなLLMコンテキストには、旧セッションのトランスクリプトではなく、注入されたハンドオフメッセージが含まれます。\n\n## 永続化モデル: 旧セッションと新セッション\n\n### 旧セッション\n\n生成中は通常のメッセージ永続化が有効なままです。アシスタントのハンドオフレスポンスは `message_end` 時に通常の `message` エントリーとして永続化されます。\n\n結果: 元のセッションには、履歴トランスクリプトの一部として生成されたハンドオフが表示されます。\n\n### 新セッション\n\nセッションリセット後、ハンドオフは `customType: \"handoff\"` の `custom_message` として永続化されます。\n\n`buildSessionContext()` はこのエントリーを `createCustomMessage(...)` 経由でランタイムのカスタム／ユーザーコンテキストメッセージに変換するため、新セッションの以降のプロンプトに含まれます。\n\n## コントローラー／UI動作\n\n`CommandController.handleHandoffCommand` の動作:\n\n- `await session.handoff(customInstructions)` を呼び出す\n- 結果が `undefined` の場合: `showError(\"Handoff cancelled\")`\n- 成功時:\n  - `rebuildChatFromMessages()`（注入されたハンドオフを含む新しいセッションコンテキストを読み込む）\n  - ステータスラインとエディタートップボーダーを無効化する\n  - Todoをリロードする\n  - 成功チャットラインを追加する: `New session started with handoff context`\n- 例外発生時:\n  - メッセージが `\"Handoff cancelled\"` またはエラー名が `AbortError` の場合: `showError(\"Handoff cancelled\")`\n  - それ以外: `showError(\"Handoff failed: <message>\")`\n- 最後にレンダリングを要求する\n\n## キャンセルセマンティクス（現在の動作）\n\n### セッションレベルのキャンセルプリミティブ\n\n`AgentSession` が公開するもの:\n\n- `abortHandoff()` → `#handoffAbortController` を中断する\n- `isGeneratingHandoff` → コントローラーが存在する間は true\n\nこの中断パスが使用された場合、ハンドオフサブスクライバーは `Error(\"Handoff cancelled\")` で拒否され、コマンドコントローラーがキャンセルUIにマッピングします。\n\n### インタラクティブな `/handoff` パスの制限\n\n現在のインタラクティブコントローラーの配線では、`/handoff` は `abortHandoff()` を呼び出す専用のEscapeハンドラーをインストールしません（コンパクション／ブランチサマリーパスが一時的に `editor.onEscape` をオーバーライドするのとは異なります）。\n\n実際の影響:\n\n- セッションレベルのキャンセルサポートは存在しますが、`/handoff` コマンドパスにはハンドオフ専用のキーバインドフックがありません。\n- ユーザーの中断は広範なエージェント中断パスを通じて発生する可能性がありますが、それは `abortHandoff()` が使用する明示的なキャンセルチャンネルとは異なります。\n\n## 中断と失敗の違い\n\n現在のUI分類:\n\n- **中断／キャンセル**\n  - `abortHandoff()` パスが `\"Handoff cancelled\"` をトリガーする、または\n  - `AbortError` がスローされる\n  - UIには `Handoff cancelled` と表示される\n\n- **失敗**\n  - `handoff()` ／プロンプトパイプライン（モデル／API検証エラー、ランタイム例外など）からスローされるその他のエラー\n  - UIには `Handoff failed: ...` と表示される\n\n追加の注意点: 生成が完了してもテキストが抽出されなかった場合、`handoff()` は `undefined` を返し、コントローラーは現在**失敗**ではなく**キャンセル**として報告します。\n\n## 短セッションおよび最小コンテンツのガードレール\n\n低シグナルなハンドオフを防ぐための2つのガード:\n\n- UIレイヤー（`handleHandoffCommand`）: `< 2` のメッセージエントリーに対して警告し早期リターンする\n- セッションレイヤー（`handoff()`）: 同じ条件をエラーとしてスローする\n\nこれにより、空または空に近いハンドオフコンテキストで新しいセッションが作成されることを防ぎます。\n\n## 状態遷移サマリー\n\n高レベルの状態フロー:\n\n1. インタラクティブなスラッシュコマンドがインターセプトされる\n2. プリフライトのメッセージ数ガード\n3. `#handoffAbortController` が作成される（`isGeneratingHandoff = true`）\n4. 内部ハンドオフプロンプトが送信される（通常のアシスタント生成としてチャットに表示される）\n5. `agent_end` 時に最新のアシスタントテキストが抽出される\n6. 欠損または中断の場合 → `undefined` を返すかキャンセルエラーパスへ\n7. 存在する場合:\n   - 旧セッションをフラッシュする\n   - 新しい空のセッションを作成する\n   - ランタイムキュー／カウンターをリセットする\n   - `custom_message(handoff)` を追加する\n   - アクティブなエージェントメッセージを再構築して置き換える\n8. コントローラーがチャットUIを再構築して成功を通知する\n9. `#handoffAbortController` がクリアされる（`isGeneratingHandoff = false`）\n\n## 既知の前提条件と制限事項\n\n- ハンドオフ抽出はヒューリスティック（「最後のアシスタントテキストブロック」）であり、構造的な検証は行われません。\n- 生成されたマークダウンが要求されたセクションフォーマットに従っているかどうかのハードチェックはありません。\n- 抽出されたテキストが欠損している場合、コントローラーのUXではキャンセルとして報告されます。\n- `/handoff` のインタラクティブフローには現在、専用のEscape→`abortHandoff()` バインディングがありません。\n- このパスでは新セッションの系譜メタデータ（`parentSession`）は設定されません。\n",
	"ja/sessions/memory.md": "---\ntitle: 自律型メモリ\ndescription: セッション間でユーザーの好み、プロジェクトコンテキスト、フィードバックを永続化する自律型メモリシステム。\nsidebar:\n  order: 7\n  label: 自律型メモリ\ni18n:\n  sourceHash: 2aa9f516aa1e\n  translator: machine\n---\n\n# 自律型メモリ\n\n有効にすると、エージェントは過去のセッションから永続的な知識を自動的に抽出し、各新規セッションにコンパクトな要約を注入します。時間の経過とともに、プロジェクトスコープのメモリストア（技術的な決定、繰り返し発生するワークフロー、落とし穴など）を構築し、手動の作業なしに引き継がれます。\n\nデフォルトでは無効です。`/settings` または `config.yml` で有効にしてください：\n\n```yaml\nmemories:\n  enabled: true\n```\n\n## 使用方法\n\n### 注入される内容\n\nセッション開始時に、現在のプロジェクトのメモリ要約が存在する場合、**Memory Guidance** ブロックとしてシステムプロンプトに注入されます。エージェントは以下のように指示されます：\n\n- メモリをヒューリスティックなコンテキストとして扱う — プロセスや過去の決定には有用だが、現在のリポジトリの状態に関して権威的ではない。\n- メモリが計画を変更する場合はメモリアーティファクトのパスを引用し、行動する前に現在のリポジトリの証拠と組み合わせる。\n- リポジトリの状態やユーザーの指示がメモリと矛盾する場合はそちらを優先し、矛盾するメモリは古いものとして扱う。\n\n### メモリアーティファクトの読み取り\n\nエージェントは `read` ツールで `memory://` URL を使用してメモリファイルを直接読み取ることができます：\n\n| URL | 内容 |\n|---|---|\n| `memory://root` | 起動時に注入されるコンパクトな要約 |\n| `memory://root/MEMORY.md` | 完全な長期メモリドキュメント |\n| `memory://root/skills/<name>/SKILL.md` | 生成されたスキルプレイブック |\n\n### `/memory` スラッシュコマンド\n\n| サブコマンド | 効果 |\n|---|---|\n| `view` | 現在のメモリ注入ペイロードを表示 |\n| `clear` / `reset` | すべてのメモリデータと生成されたアーティファクトを削除 |\n| `enqueue` / `rebuild` | 次回起動時に統合を強制実行 |\n\n## 仕組み\n\nメモリは、起動時またはスラッシュコマンドによる手動トリガーで実行されるバックグラウンドパイプラインによって構築されます。\n\n**フェーズ 1 — セッションごとの抽出：** 前回処理以降に変更があった各過去セッションについて、モデルがセッション履歴を読み取り、永続的なシグナル（技術的な決定、制約、解決された障害、繰り返し発生するワークフロー）を抽出します。直近すぎるセッション、古すぎるセッション、または現在アクティブなセッションはスキップされます。各抽出により、そのセッションの生のメモリブロックと短い概要が生成されます。\n\n**フェーズ 2 — 統合：** 抽出後、2回目のモデルパスがすべてのセッションごとの抽出を読み取り、ディスクに書き込まれる3つの出力を生成します：\n\n- `MEMORY.md` — キュレーションされた長期メモリドキュメント\n- `memory_summary.md` — セッション開始時に注入されるコンパクトなテキスト\n- `skills/` — 再利用可能な手順プレイブック、それぞれ独自のサブディレクトリに配置\n\nフェーズ 2 はリースを使用して、複数のプロセスが同時に開始された場合の二重実行を防止します。以前の実行からの古いスキルディレクトリは自動的に削除されます。\n\nすべての出力は、ディスクに書き込まれる前にシークレットのスキャンが行われます。\n\n### 抽出の動作\n\nメモリの抽出と統合の動作は、`src/prompts/memories/` にある静的プロンプトファイルによって完全に制御されます。\n\n| ファイル | 目的 | 変数 |\n|---|---|---|\n| `stage_one_system.md` | セッションごとの抽出のシステムプロンプト | — |\n| `stage_one_input.md` | セッション内容をラップするユーザーターンテンプレート | `{{thread_id}}`、`{{response_items_json}}` |\n| `consolidation.md` | クロスセッション統合のプロンプト | `{{raw_memories}}`、`{{rollout_summaries}}` |\n| `read_path.md` | ライブセッションに注入されるメモリガイダンス | `{{memory_summary}}` |\n\n### モデル選択\n\nメモリはモデルロールシステムを利用します。\n\n| フェーズ | ロール | 目的 |\n|---|---|---|\n| フェーズ 1（抽出） | `default` | セッションごとの知識抽出 |\n| フェーズ 2（統合） | `smol` | クロスセッション合成 |\n\n`smol` が設定されていない場合、フェーズ 2 は `default` ロールにフォールバックします。\n\n## 設定\n\n| 設定 | デフォルト | 説明 |\n|---|---|---|\n| `memories.enabled` | `false` | マスタースイッチ |\n| `memories.maxRolloutAgeDays` | `30` | これより古いセッションは処理されない |\n| `memories.minRolloutIdleHours` | `12` | これより最近アクティブだったセッションはスキップされる |\n| `memories.maxRolloutsPerStartup` | `64` | 1回の起動で処理されるセッション数の上限 |\n| `memories.summaryInjectionTokenLimit` | `5000` | システムプロンプトに注入される要約の最大トークン数 |\n\n追加のチューニングパラメータ（並行性、リース期間、トークンバジェット）は、上級者向けに設定で利用可能です。\n\n## 主要ファイル\n\n- `src/memories/index.ts` — パイプラインのオーケストレーション、注入、スラッシュコマンド処理\n- `src/memories/storage.ts` — SQLite ベースのジョブキューとスレッドレジストリ\n- `src/prompts/memories/` — メモリプロンプトテンプレート\n- `src/internal-urls/memory-protocol.ts` — `memory://` URL ハンドラー\n",
	"ja/sessions/non-compaction-retry-policy.md": "---\ntitle: 非コンパクションの自動リトライポリシー\ndescription: コンパクションパス外の一時的なAPIエラーに対する自動リトライポリシー。\nsidebar:\n  order: 6\n  label: リトライポリシー\ni18n:\n  sourceHash: 8999a0258dd8\n  translator: machine\n---\n\n# 非コンパクションの自動リトライポリシー\n\nこのドキュメントでは、`AgentSession` における標準的なAPIエラーのリトライパスについて説明します。\n\n自動コンパクションによるコンテキストオーバーフローの回復は明示的に対象外とします。オーバーフローはコンパクションロジックによって処理され、[`compaction.md`](./compaction.md) で別途ドキュメント化されています。\n\n## 実装ファイル\n\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/config/settings-schema.ts`](../../packages/coding-agent/src/config/settings-schema.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts)\n- [`../src/modes/rpc/rpc-client.ts`](../../packages/coding-agent/src/modes/rpc/rpc-client.ts)\n- [`../src/modes/rpc/rpc-types.ts`](../../packages/coding-agent/src/modes/rpc/rpc-types.ts)\n\n## コンパクションとのスコープ境界\n\nリトライとコンパクションは同じ `agent_end` パスから確認されますが、意図的に分離されています：\n\n1. `agent_end` が最後のアシスタントメッセージを検査します。\n2. `#isRetryableError(...)` が最初に実行されます。\n3. リトライが開始された場合、そのターンのコンパクション確認はスキップされます。\n4. コンテキストオーバーフローエラーはリトライ分類からハード除外されます（`isContextOverflow(...)` がリトライを短絡させます）。\n5. オーバーフローは標準リトライではなく `#checkCompaction(...)` にフォールスルーします。\n\nつまり、過負荷/レート制限/サーバー/ネットワーク系のエラーはこのリトライポリシーを使用し、コンテキストウィンドウのオーバーフローはコンパクション回復を使用します。\n\n## リトライ分類\n\n`#isRetryableError(...)` は以下のすべてを要件とします：\n\n- アシスタントの `stopReason === \"error\"`\n- `errorMessage` が存在する\n- メッセージが**コンテキストオーバーフローではない**\n- `errorMessage` が `#isRetryableErrorMessage(...)` と一致する\n\n現在のリトライ可能なパターンセット（正規表現ベース）：\n\n- overloaded\n- rate limit / usage limit / too many requests\n- HTTP系サーバークラス: 429、500、502、503、504\n- service unavailable / server error / internal error\n- connection error / fetch failed\n- `retry delay` の文言\n\nこれは型付きプロバイダーエラーコードではなく、文字列パターン分類です。\n\n## リトライのライフサイクルと状態遷移\n\nリトライで使用されるセッション状態：\n\n- `#retryAttempt: number`（`0` はアイドル状態を意味します）\n- `#retryPromise: Promise<void> | undefined`（進行中のリトライライフサイクルを追跡）\n- `#retryResolve: (() => void) | undefined`（`#retryPromise` を解決）\n- `#retryAbortController: AbortController | undefined`（バックオフスリープをキャンセル）\n\nフロー（`#handleRetryableError`）：\n\n1. `retry` 設定グループを読み取ります。\n2. `retry.enabled === false` の場合、即座に停止します（`false`、リトライは開始されません）。\n3. `#retryAttempt` をインクリメントします。\n4. `#retryPromise` を一度作成します（チェーン内の最初の試行）。\n5. 試行回数が `retry.maxRetries` を超えた場合、最終失敗イベントを発行して停止します。\n6. 遅延を計算します：`retry.baseDelayMs * 2^(attempt-1)`。\n7. usage-limit エラーの場合、リトライヒントを解析して認証ストレージ（`markUsageLimitReached(...)`）を呼び出します。プロバイダー/モデルの切り替えが成功した場合、遅延を `0` に強制します。\n8. `auto_retry_start` を発行します。\n9. エージェントランタイム状態から末尾のアシスタントエラーメッセージを削除します（永続化されたセッション履歴には保持されます）。\n10. 中止サポート付きでスリープします。\n11. 起床時に `setTimeout(..., 0)` を介して `agent.continue()` をスケジュールします。\n\n### リトライカウンターのリセット条件\n\n`#retryAttempt` は以下の場合に `0` にリセットされます：\n\n- リトライ開始後の最初の成功した非エラー・非中断アシスタントメッセージ（`auto_retry_end { success: true }` を発行）\n- バックオフスリープ中のリトライキャンセル\n- 最大リトライ回数超過パス\n\n`#retryPromise` は、リトライチェーンが終了したとき（成功、キャンセル、または最大回数超過）に `#resolveRetry()` を介して解決/クリアされます。\n\n## バックオフと最大試行回数のセマンティクス\n\n設定：\n\n- `retry.enabled`（デフォルト `true`）\n- `retry.maxRetries`（デフォルト `3`）\n- `retry.baseDelayMs`（デフォルト `2000`）\n\n試行回数の採番：\n\n- 試行カウンターは最大値チェックの前にインクリメントされます\n- 開始イベントは現在の試行回数を使用します（1始まり）\n- 最大回数超過の終了イベントは `attempt: this.#retryAttempt - 1` を報告します（最後に試みたリトライ回数）\n\nデフォルト設定でのバックオフシーケンス：\n\n- 試行1: 2000 ms\n- 試行2: 4000 ms\n- 試行3: 8000 ms\n\n遅延オーバーライドの入力は usage-limit 処理パスでのみ使用され、認証ストレージのモデル/アカウント切り替えの判断に影響を与えるためだけに使用されます。メインの非コンパクションリトライパスでは、切り替えが成功した場合（`delayMs = 0`）を除き、バックオフはローカルの指数遅延のままです。\n\n## 中止メカニクス\n\n### 明示的なリトライ中止\n\n`abortRetry()`：\n\n- `#retryAbortController` を中止します（存在する場合）\n- リトライPromiseを解決します（`#resolveRetry()`）。これにより待機者のブロックが解除されます。\n\nスリープ中に中止が発生した場合、catchパスは以下を発行します：\n\n- `auto_retry_end { success: false, finalError: \"Retry cancelled\" }`\n- 試行回数/コントローラーをリセットします\n\n### グローバルオペレーション中止との相互作用\n\n`abort()` はアクティブなエージェントストリームを中止する前に `abortRetry()` を呼び出します。これにより、ユーザーが全体的な中止を発行したときにリトライバックオフが確実にキャンセルされます。\n\n### TUIとの相互作用\n\n`auto_retry_start` 時、EventController は以下を行います：\n\n- `Esc` ハンドラーを `session.abortRetry()` に切り替えます\n- ローダーテキストをレンダリングします：`Retrying (attempt/maxAttempts) in Ns… (esc to cancel)`\n\n`auto_retry_end` 時、以前の `Esc` ハンドラーを復元してローダー状態をクリアします。\n\n## ストリーミングとプロンプト完了の動作\n\n`prompt()` は最終的に `agent.prompt(...)` が返った後に `#waitForRetry()` を待機します。\n\n効果：\n\n- プロンプト呼び出しは、開始されたリトライチェーンが終了するまで（成功/失敗/キャンセル）完全に解決されません\n- リトライのライフサイクルは1つの論理的なプロンプト実行境界の一部です\n\nこれにより、呼び出し元がリトライ中のターンを早まって完了と判断することを防ぎます。\n\n## 制御: 設定とRPC\n\n### 設定ノブ\n\n設定スキーマのリトライグループ下で定義されます：\n\n- `retry.enabled`\n- `retry.maxRetries`\n- `retry.baseDelayMs`\n\nセッションのプログラム的な切り替え：\n\n- `setAutoRetryEnabled(enabled)` は `retry.enabled` を書き込みます\n- `autoRetryEnabled` は `retry.enabled` を読み取ります\n- `isRetrying` はリトライライフサイクルのPromiseがアクティブかどうかを報告します\n\n### RPC制御\n\nRPCコマンドサーフェス：\n\n- `set_auto_retry` → `session.setAutoRetryEnabled(command.enabled)`\n- `abort_retry` → `session.abortRetry()`\n\nクライアントヘルパー：\n\n- `RpcClient.setAutoRetry(enabled)`\n- `RpcClient.abortRetry()`\n\nどちらのコマンドも成功レスポンスを返します。リトライの進捗/失敗の詳細はコマンドレスポンスのペイロードではなく、ストリーミングされたセッションイベントから取得されます。\n\n## イベント発行と失敗のサーフェシング\n\nセッションレベルのリトライイベント：\n\n- `auto_retry_start { attempt, maxAttempts, delayMs, errorMessage }`\n- `auto_retry_end { success, attempt, finalError? }`\n\n伝播：\n\n- `AgentSession.subscribe(...)` を通じて発行されます\n- 拡張機能イベントとして拡張機能ランナーに転送されます\n- RPCモードでは、JSONイベントオブジェクトとして直接転送されます（`session.subscribe(event => output(event))`）\n- TUIでは、ローダー/エラーUIのために `EventController` によって消費されます\n\n最終失敗のサーフェシング：\n\n- 最大回数超過またはキャンセル時、`auto_retry_end.success === false`\n- TUIに表示：`Retry failed after N attempts: <finalError>`\n- 拡張機能/フックは同じフィールドを持つ `auto_retry_end` を受信します\n- RPCコンシューマーはstdoutストリームで同じイベントオブジェクトを受信します\n\n## 永続的な停止条件\n\n以下のいずれかが発生した場合、リトライは停止し自動継続しません：\n\n- `retry.enabled` が false\n- エラーがリトライ分類されていない\n- エラーがコンテキストオーバーフロー（コンパクションパスに委譲）\n- 最大リトライ回数超過\n- ユーザーがリトライをキャンセル（リトライローダー中の `abort_retry` または `Esc`）\n- グローバル中止（`abort`）が最初にリトライをキャンセル\n\nカウンターがリセットされた後、将来のリトライ可能なエラーに対して新しいリトライチェーンを開始することができます。\n\n## 運用上の注意事項\n\n- 分類は正規表現によるテストマッチングです。プロバイダー固有の構造化エラーはここでは使用されません。\n- リトライは再継続前に**ランタイムコンテキスト**から失敗したアシスタントエラーを除去しますが、セッション履歴にはそのエラーエントリーが保持されます。\n- `RpcSessionState` は現在 `autoCompactionEnabled` を公開していますが、`autoRetryEnabled` フィールドは公開していません。RPCの呼び出し元は独自のトグル状態を追跡するか、他のAPIを通じて設定を照会する必要があります。\n",
	"ja/sessions/session-operations-export-share-fork-resume.md": "---\ntitle: 'セッション操作: エクスポート、ダンプ、共有、フォーク、再開'\ndescription: 会話のエクスポート、共有、フォーク、再開に関するセッション操作。\nsidebar:\n  order: 3\n  label: 操作\ni18n:\n  sourceHash: e3c210b29c3e\n  translator: machine\n---\n\n# セッション操作: export、dump、share、fork、resume/continue\n\nこのドキュメントでは、現在実装されているセッションのエクスポート/共有/フォーク/再開操作について、オペレーターから見える動作を説明します。\n\n## 実装ファイル\n\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/export/html/index.ts`](../../packages/coding-agent/src/export/html/index.ts)\n- [`../src/export/custom-share.ts`](../../packages/coding-agent/src/export/custom-share.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n\n## 操作マトリクス\n\n| 操作 | エントリパス | セッション変更 | セッションファイルの作成/切り替え | 出力アーティファクト |\n|---|---|---|---|---|\n| `/dump` | インタラクティブスラッシュコマンド | なし | なし | クリップボードテキスト |\n| `/export [path]` | インタラクティブスラッシュコマンド | なし | なし | HTMLファイル |\n| `--export <session.jsonl> [outputPath]` | CLIスタートアップファストパス | ランタイムセッション変更なし | アクティブセッションなし、対象ファイルを読み取り | HTMLファイル |\n| `/share` | インタラクティブスラッシュコマンド | なし | なし | 一時HTML + 共有URL/gist |\n| `/fork` | インタラクティブスラッシュコマンド | あり（アクティブセッションのIDが変更） | 新しいセッションファイルを作成し、現在のセッションをそれに切り替え（永続モードのみ） | アーティファクトディレクトリが存在する場合、新しいセッション名前空間にコピー |\n| `/resume` | インタラクティブスラッシュコマンド | あり（アクティブなインメモリ状態が置換） | 選択された既存セッションファイルに切り替え | なし |\n| `--resume` | CLIスタートアップ（ピッカー） | セッション作成後にあり | 選択された既存セッションファイルを開く | なし |\n| `--resume <id\\|path>` | CLIスタートアップ | セッション作成後にあり | 既存セッションを開く。クロスプロジェクトの場合は現在のプロジェクトにフォーク可能 | なし |\n| `--continue` | CLIスタートアップ | セッション作成後にあり | ターミナルのブレッドクラムまたは最新セッションを開く。存在しない場合は新規作成 | なし |\n\n## エクスポートとダンプ\n\n### `/export [outputPath]`（インタラクティブ）\n\nフロー:\n\n1. `InputController` が `/export...` を `CommandController.handleExportCommand` にルーティングします。\n2. コマンドはホワイトスペースで分割し、`/export` の後の最初の引数のみを `outputPath` として使用します。\n3. `AgentSession.exportToHtml()` が `exportSessionToHtml(sessionManager, state, { outputPath, themeName })` を呼び出します。\n4. 成功時、UIはパスを表示し、ブラウザでファイルを開きます。\n\n動作の詳細:\n\n- `--copy`、`clipboard`、`copy` 引数は明示的に拒否され、`/dump` を使用するよう警告が表示されます。\n- エクスポートにはセッションのヘッダー/エントリ/リーフと、現在の `systemPrompt` およびエージェント状態からのツール説明が埋め込まれます。\n- エクスポート中にセッションエントリは追加されません。\n\n注意点:\n\n- 引数の解析はホワイトスペースベース（`text.split(/\\s+/)`）であるため、スペースを含むクォートされたパスはこのコマンドパスでは単一のパスとして保持されません。\n\n### `--export <inputSessionFile> [outputPath]`（CLI）\n\n`main.ts` でのフロー:\n\n1. 早期に処理されます（インタラクティブ/セッションスタートアップの前）。\n2. `exportFromFile(inputPath, outputPath?)` を呼び出します。\n3. `SessionManager.open(inputPath)` がエントリを読み込み、その後HTMLが生成されて書き込まれます。\n4. プロセスは `Exported to: ...` と表示して終了します。\n\n動作の詳細:\n\n- 入力ファイルが存在しない場合、`File not found: <path>` としてエラーが表示されます。\n- このパスは `AgentSession` を作成せず、実行中のセッションを変更しません。\n\n### `/dump`（インタラクティブクリップボードエクスポート）\n\nフロー:\n\n1. `CommandController.handleDumpCommand()` が `session.formatSessionAsText()` を呼び出します。\n2. 空文字列の場合、`No messages to dump yet.` と報告します。\n3. それ以外の場合、ネイティブの `copyToClipboard` でクリップボードにコピーします。\n\nダンプの内容:\n\n- システムプロンプト\n- アクティブなモデル/思考レベル\n- ツール定義 + パラメータ\n- ユーザー/アシスタントメッセージ\n- 思考ブロックとツール呼び出し\n- ツール結果と実行ブロック（`excludeFromContext` のbash/pythonエントリを除く）\n- カスタム/フック/ファイルメンション/ブランチサマリー/コンパクションサマリーエントリ\n\nダンプによるセッション永続化の変更はありません。\n\n## 共有\n\n`/share` はインタラクティブ専用で、常に現在のセッションを一時HTMLファイルにエクスポートすることから開始します。\n\n### フェーズ1: 一時エクスポート\n\n- 一時ファイルパス: `${os.tmpdir()}/${Snowflake.next()}.html`\n- `session.exportToHtml(tmpFile)` を使用\n- エクスポートが失敗した場合（特にインメモリセッション）、共有はエラーで終了します。\n\n### フェーズ2: カスタム共有ハンドラー（存在する場合）\n\n`loadCustomShare()` は `~/.xcsh/agent` で最初に存在する候補をチェックします:\n\n- `share.ts`\n- `share.js`\n- `share.mjs`\n\n要件:\n\n- モジュールは `(htmlPath) => Promise<CustomShareResult | string | undefined>` 関数をデフォルトエクスポートする必要があります。\n\n存在し、有効な場合:\n\n- UIは `Sharing...` ローダー状態になります。\n- ハンドラー結果の解釈:\n  - 文字列 => URLとして扱われ、表示されて開かれる\n  - オブジェクト => `url` および/または `message` が表示される。`url` が開かれる\n  - `undefined`/falsy => 汎用的な `Session shared`\n- 完了後、一時ファイルは削除されます。\n\n重要なフォールバック動作:\n\n- カスタムハンドラーが存在するが読み込みに失敗した場合、コマンドはエラーで終了します。\n- カスタムハンドラーが実行されて例外をスローした場合、コマンドはエラーで終了します。\n- いずれの失敗ケースでも、GitHub gistへの**フォールバックは行われません**。\n- Gistフォールバックは、カスタム共有スクリプトが存在しない場合にのみ発生します。\n\n### フェーズ3: デフォルトgistフォールバック\n\nカスタム共有ハンドラーが見つからない場合のみ:\n\n1. `gh auth status` を検証します。\n2. `Creating gist...` ローダーを表示します。\n3. `gh gist create --public=false <tmpFile>` を実行します。\n4. gist URLを解析し、gist idを導出し、プレビューURL `https://gistpreview.github.io/?<id>` を構築します。\n5. プレビューURLとgist URLの両方を表示し、プレビューを開きます。\n\n共有でのキャンセル/中断セマンティクス:\n\n- ローダーには、エディターUIを復元して `Share cancelled` と報告する `onAbort` フックがあります。\n- このコードパスでは、基盤となる `gh gist create` コマンドにアボートシグナルは渡されません。キャンセルはUIレベルで、コマンドが返された後にチェックされます。\n\n## フォーク\n\n`/fork` は現在のセッションから新しいセッションを作成し、アクティブなセッションのIDを切り替えます。\n\n### 前提条件と即時ガード\n\n- エージェントがストリーミング中の場合、`/fork` は警告付きで拒否されます。\n- 操作前にUIステータス/ローディングインジケーターがクリアされます。\n\n### セッションレベルのフロー\n\n`AgentSession.fork()`:\n\n1. `reason: \"fork\"` で `session_before_switch` を発行します（キャンセル可能）。\n2. 保留中の書き込みをフラッシュします。\n3. `SessionManager.fork()` を呼び出します。\n4. 旧セッション名前空間から新しい名前空間にアーティファクトディレクトリをコピーします（ベストエフォート。ENOENT以外のコピー失敗はログに記録されますが、致命的ではありません）。\n5. `agent.sessionId` を更新します。\n6. `reason: \"fork\"` で `session_switch` を発行します。\n\n`SessionManager.fork()` の動作:\n\n- 永続モードと既存のセッションファイルが必要です。\n- 新しいセッションIDと新しいJSONLファイルパスを作成します。\n- ヘッダーを以下の内容で書き換えます:\n  - 新しい `id`\n  - 新しいタイムスタンプ\n  - `cwd` は変更なし\n  - `parentSession` に前のセッションIDを設定\n- 新しいファイル内のヘッダー以外のすべてのエントリは変更なし。\n\n### 非永続モードの動作\n\n- インメモリセッションマネージャーは `fork()` から `undefined` を返します。\n- `AgentSession.fork()` は `false` を返します。\n- UIは `Fork failed (session not persisted or cancelled)` と報告します。\n\n## 再開とコンティニュー\n\n## インタラクティブ `/resume`\n\nフロー:\n\n1. `SessionManager.list(currentCwd, currentSessionDir)` で取得したセッションセレクターを開きます。\n2. 選択時、`SelectorController.handleResumeSession(sessionPath)` が `session.switchSession(sessionPath)` を呼び出します。\n3. UIはチャットとTodoをクリア/再構築し、`Resumed session` と報告します。\n\n注意:\n\n- このピッカーは現在のセッションディレクトリスコープ内のセッションのみをリストします。\n- グローバルなクロスプロジェクト検索は使用しません。\n\n## CLI `--resume`\n\n### `--resume`（値なし）\n\n- `main.ts` が現在のcwd/sessionDirのセッションをリストし、ピッカーを開きます。\n- 選択されたパスは、セッション作成前に `SessionManager.open(selectedPath)` で開かれます。\n\n### `--resume <value>`\n\n`createSessionManager()` の解決順序:\n\n1. 値がパスのように見える場合（`/`、`\\`、または `.jsonl`）、直接開きます。\n2. そうでなければIDプレフィックスとして扱います:\n   - 現在のスコープを検索（`SessionManager.list(cwd, sessionDir)`）\n   - 見つからず、明示的な `sessionDir` がない場合、グローバル検索（`SessionManager.listAll()`）\n\nクロスプロジェクトID一致の動作:\n\n- 一致したセッションのcwdが現在のcwdと異なる場合、CLIは以下を確認します:\n  - `Session found in different project ... Fork into current directory? [y/N]`\n- yesの場合: `SessionManager.forkFrom(match.path, cwd, sessionDir)` が新しいローカルフォークファイルを作成します。\n- no/非TTYデフォルトの場合: コマンドはエラーになります。\n\n## CLI `--continue`\n\n`SessionManager.continueRecent(cwd, sessionDir)`:\n\n1. 現在のcwdのセッションディレクトリを解決します。\n2. まずターミナルスコープのブレッドクラムを読み取ります。\n3. 最も最近変更されたセッションファイルにフォールバックします。\n4. 見つかったセッションを開きます。存在しない場合は新しいセッションを作成します。\n\nこれはスタートアップ時のみの動作です。インタラクティブな `/continue` スラッシュコマンドはありません。\n\n## セッション切り替えが実際にランタイム状態を変更する方法\n\n`AgentSession.switchSession(sessionPath)` は、再開系の操作で使用されるランタイム遷移を実行します:\n\n1. `reason: \"resume\"` と `targetSessionFile` で `session_before_switch` を発行します（キャンセル可能）。\n2. エージェントイベントサブスクリプションを切断し、実行中の作業を中止します。\n3. キューに入っているステアリング/フォローアップ/次ターンメッセージをクリアします。\n4. 現在のセッションマネージャーの書き込みをフラッシュします。\n5. `sessionManager.setSessionFile(sessionPath)` を実行し、`agent.sessionId` を更新します。\n6. 読み込まれたエントリからセッションコンテキストを構築します。\n7. `reason: \"resume\"` で `session_switch` を発行します。\n8. コンテキストからエージェントメッセージを置換します。\n9. モデルを復元します（現在のレジストリで利用可能な場合）。\n10. 思考レベルを復元または初期化します。\n11. エージェントイベントサブスクリプションを再接続します。\n\n`switchSession()` 自体は新しいセッションファイルを作成しません。\n\n## イベント発行とキャンセルポイント\n\n### 切り替え/フォークのライフサイクルフック\n\n`newSession`、`fork`、`switchSession` の場合:\n\n- Beforeイベント: `session_before_switch`\n  - reason: `new`、`fork`、`resume`\n  - `{ cancel: true }` を返すことでキャンセル可能\n- Afterイベント: `session_switch`\n  - 同じreasonセット\n  - `previousSessionFile` を含む\n\n`ExtensionRunner.emit()` は、最初のキャンセルするbeforeイベント結果で早期リターンします。\n\n### カスタムツールの `onSession` 動作\n\nSDKブリッジが拡張セッションイベントをカスタムツールの `onSession` コールバックに接続します:\n\n- `session_switch` -> `onSession({ reason: \"switch\", previousSessionFile })`\n- `session_branch` -> `reason: \"branch\"`\n- `session_start` -> `reason: \"start\"`\n- `session_tree` -> `reason: \"tree\"`\n- `session_shutdown` -> `reason: \"shutdown\"`\n\nこれらのコールバックは監視用であり、切り替え/フォークをキャンセルしません。\n\n### このドキュメントに関連するその他のキャンセルサーフェス\n\n- `/fork` はストリーミング中はブロックされます（ユーザーは現在のレスポンスを待つか中止する必要があります）。\n- `/resume` セレクターはユーザーがセレクターを閉じることでキャンセルできます。\n- クロスプロジェクトの `--resume <id>` はフォークプロンプトを拒否することでキャンセルできます。\n- `/share` にはgistフローのUIアボートパス（`Share cancelled`）がありますが、このコードパスでは `gh gist create` に対するプロセスキルセマンティクスは接続されていません。\n\n## 非永続（インメモリ）セッションの動作\n\nセッションマネージャーが `SessionManager.inMemory()`（`--no-session`）で作成された場合:\n\n- セッションファイルパスは存在しません。\n- `/export` と `/share` は `Cannot export in-memory session to HTML` で失敗します（コマンドエラーUIに伝播）。\n- `/fork` は `SessionManager.fork()` が永続性を必要とするため失敗します。\n- `/dump` はインメモリのエージェント状態をシリアライズするため、引き続き動作します。\n- `--no-session` が設定されている場合、マネージャー作成が即座にインメモリを返すため、CLI resume/continueセマンティクスはバイパスされます。\n\n## 既知の実装上の注意点（現在のコード時点）\n\n- `SelectorController.handleResumeSession()` は `session.switchSession(...)` のブール値の結果をチェックしません。フックでキャンセルされた切り替えでも、UI上の「Resumed session」再描画/ステータスパスを通過する可能性があります。\n- `/share` のカスタム共有の失敗は、デフォルトのgistフォールバックには降格せず、エラーでコマンドを終了します。\n- `/export` の引数トークン化は簡素であり、スペースを含むクォートされたパスを保持しません。\n",
	"ja/sessions/session-switching-and-recent-listing.md": "---\ntitle: セッション切り替えと最近のセッション一覧\ndescription: セッション切り替えの仕組みと、検索・フィルタリング機能を備えた最近のセッション一覧表示。\nsidebar:\n  order: 4\n  label: 切り替えと最近のセッション\ni18n:\n  sourceHash: aae56130b508\n  translator: machine\n---\n\n# セッション切り替えと最近のセッション一覧\n\nこのドキュメントでは、coding-agent が最近のセッションを検出し、`--resume` ターゲットを解決し、セッションピッカーを表示し、アクティブなランタイムセッションを切り替える方法について説明します。\n\n現在の実装の動作に焦点を当てており、フォールバックパスや注意事項も含みます。\n\n## 実装ファイル\n\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/cli/session-picker.ts`](../../packages/coding-agent/src/cli/session-picker.ts)\n- [`../src/modes/components/session-selector.ts`](../../packages/coding-agent/src/modes/components/session-selector.ts)\n- [`../src/modes/controllers/selector-controller.ts`](../../packages/coding-agent/src/modes/controllers/selector-controller.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n\n## 最近のセッションの検出\n\n### ディレクトリスコープ\n\n`SessionManager` はデフォルトで cwd スコープのディレクトリにセッションを保存します：\n\n- `~/.xcsh/agent/sessions/--<cwd-encoded>--/*.jsonl`\n\n`SessionManager.list(cwd, sessionDir?)` は、明示的な `sessionDir` が提供されない限り、そのディレクトリのみを読み取ります。\n\n### ペイロードが異なる2つの一覧取得パイプライン\n\n2つの異なる一覧取得パイプラインがあります：\n\n1. `getRecentSessions(sessionDir, limit)`（ウェルカム/サマリービュー）\n   - 各ファイルから 4KB のプレフィックス（`readTextPrefix(..., 4096)`）のみを読み取ります。\n   - ヘッダーと最初のユーザーテキストプレビューを解析します。\n   - 遅延評価の `name` と `timeAgo` ゲッターを持つ軽量な `RecentSessionInfo` を返します。\n   - ファイルの `mtime` 降順でソートします。\n\n2. `SessionManager.list(...)` / `SessionManager.listAll()`（再開ピッカーと ID マッチング）\n   - セッションファイル全体を読み取ります。\n   - `SessionInfo` オブジェクト（`id`、`cwd`、`title`、`messageCount`、`firstMessage`、`allMessagesText`、タイムスタンプ）を構築します。\n   - `message` エントリがゼロのセッションは除外します。\n   - `modified` 降順でソートします。\n\n### メタデータのフォールバック動作\n\n最近のサマリー（`RecentSessionInfo`）の場合：\n\n- 表示名の優先順位：`header.title` -> 最初のユーザープロンプト -> `header.id` -> ファイル名\n- コンパクト表示では名前は 40 文字に切り詰められます\n- タイトル由来の名前から制御文字/改行は除去/サニタイズされます\n\n`SessionInfo` の一覧エントリの場合：\n\n- `title` は `header.title` または最新のコンパクション `shortSummary` です\n- `firstMessage` は最初のユーザーメッセージテキストまたは `\"(no messages)\"` です\n\n## `--continue` の解決とターミナルブレッドクラムの優先\n\n`SessionManager.continueRecent(cwd, sessionDir?)` は以下の順序でターゲットを解決します：\n\n1. ターミナルスコープのブレッドクラムを読み取る（`~/.xcsh/agent/terminal-sessions/<terminal-id>`）\n2. ブレッドクラムを検証する：\n   - 現在のターミナルが識別可能であること\n   - ブレッドクラムの cwd が現在の cwd と一致すること（解決済みパスの比較）\n   - 参照されているファイルがまだ存在すること\n3. ブレッドクラムが無効/存在しない場合、セッションディレクトリ内の mtime が最新のファイルにフォールバック（`findMostRecentSession`）\n4. 見つからない場合、新しいセッションを作成\n\nターミナル ID の導出は TTY パスを優先し、環境変数ベースの識別子（`KITTY_WINDOW_ID`、`TMUX_PANE`、`TERM_SESSION_ID`、`WT_SESSION`）にフォールバックします。\n\nブレッドクラムの書き込みはベストエフォートであり、致命的ではありません。\n\n## 起動時の再開ターゲット解決（`main.ts`）\n\n### `--resume <value>`\n\n`createSessionManager(...)` は文字列値の `--resume` を2つのモードで処理します：\n\n1. パス風の値（`/`、`\\\\` を含む、または `.jsonl` で終わる）\n   - `SessionManager.open(sessionArg, parsed.sessionDir)` で直接オープン\n\n2. ID プレフィックス値\n   - `SessionManager.list(cwd, sessionDir)` で `id.startsWith(sessionArg)` による一致を検索\n   - ローカルに一致がなく `sessionDir` が強制されていない場合、`SessionManager.listAll()` を試行\n   - 最初の一致が使用されます（曖昧さの解消プロンプトなし）\n\nクロスプロジェクトの一致動作：\n\n- 一致したセッションの cwd が現在の cwd と異なる場合、CLI は現在のプロジェクトにフォークするかどうかをプロンプトします\n- はい -> `SessionManager.forkFrom(...)`\n- いいえ -> エラーをスロー（`Session \"...\" is in another project (...)`）\n\n一致なし -> エラーをスロー（`Session \"...\" not found.`）。\n\n### `--resume`（値なし）\n\n初期のセッションマネージャー構築後に処理されます：\n\n1. `SessionManager.list(cwd, parsed.sessionDir)` でローカルセッションを一覧取得\n2. 空の場合：`No sessions found` を表示して早期終了\n3. TUI ピッカーを開く（`selectSession`）\n4. キャンセルされた場合：`No session selected` を表示して早期終了\n5. 選択された場合：`SessionManager.open(selectedPath)`\n\n### `--continue`\n\n`SessionManager.continueRecent(...)` を直接使用します（上記のブレッドクラム優先の動作）。\n\n## ピッカーベースの選択の内部構造\n\n## CLI ピッカー（`src/cli/session-picker.ts`）\n\n`selectSession(sessions)` は `SessionSelectorComponent` を持つスタンドアロン TUI を作成し、正確に1回だけ解決します：\n\n- 選択 -> 選択されたパスで解決\n- キャンセル（Esc）-> `null` で解決\n- 強制終了（Ctrl+C パス）-> TUI を停止して `process.exit(0)`\n\n## インタラクティブなセッション内ピッカー（`SelectorController.showSessionSelector`）\n\nフロー：\n\n1. `SessionManager.list(currentCwd, currentSessionDir)` で現在のセッションディレクトリからセッションを取得\n2. エディターエリアに `SessionSelectorComponent` をマウント（`showSelector(...)` を使用）\n3. コールバック：\n   - 選択 -> セレクターを閉じて `handleResumeSession(sessionPath)` を呼び出す\n   - キャンセル -> エディターを復元してリレンダリング\n   - 終了 -> `ctx.shutdown()`\n\n## セッションセレクターコンポーネントの動作\n\n`SessionList` がサポートする機能：\n\n- 矢印/ページナビゲーション\n- Enter で選択\n- Esc でキャンセル\n- Ctrl+C で終了\n- セッション id/title/cwd/最初のメッセージ/全メッセージ/パスを横断したファジー検索\n\n空のリストのレンダリング動作：\n\n- クラッシュする代わりにメッセージを表示\n- 空の状態で Enter を押しても何も起きない（コールバックなし）\n- Esc/Ctrl+C は引き続き動作\n\n注意事項：UI テキストには `Press Tab to view all` と表示されますが、このコンポーネントには現在 Tab ハンドラーがなく、現在の配線では現在のスコープのセッションのみを一覧表示します。\n\n## ランタイム切り替えの実行（`AgentSession.switchSession`）\n\n`switchSession(sessionPath)` はプロセス内切り替えの中核パスです。\n\nライフサイクル/状態遷移：\n\n1. `previousSessionFile` をキャプチャ\n2. `session_before_switch` フックイベントを発行（`reason: \"resume\"`、キャンセル可能）\n3. キャンセルされた場合 -> 切り替えなしで `false` を返す\n4. 現在のエージェントイベントストリームから切断\n5. アクティブな生成/ツールフローを中止\n6. キューに入っているステアリング/フォローアップ/次ターンのメッセージバッファをクリア\n7. セッションライターをフラッシュ（`sessionManager.flush()`）して保留中の書き込みを永続化\n8. `sessionManager.setSessionFile(sessionPath)`\n   - セッションファイルポインターを更新\n   - ターミナルブレッドクラムを書き込み\n   - エントリの読み込み / マイグレーション / blob 解決 / 再インデックスを実行\n   - ファイルデータが欠落/無効な場合：そのパスで新しいセッションを初期化してヘッダーを書き換え\n9. `agent.sessionId` を更新\n10. `buildSessionContext()` でコンテキストを再構築\n11. `session_switch` フックイベントを発行（`reason: \"resume\"`、`previousSessionFile`）\n12. エージェントメッセージを再構築されたコンテキストで置き換え\n13. `sessionContext.models.default` が利用可能でモデルレジストリに存在する場合、デフォルトモデルを復元\n14. thinking レベルを復元：\n    - ブランチに既に `thinking_level_change` がある場合、保存されたセッションレベルを適用\n    - それ以外の場合、設定からデフォルトの thinking レベルを導出し、モデルの機能に合わせてクランプして設定し、新しい `thinking_level_change` エントリを追加\n15. エージェントリスナーを再接続して `true` を返す\n\n## インタラクティブ切り替え後の UI 状態の再構築\n\n`SelectorController.handleResumeSession` は `switchSession` の前後で UI リセットを実行します：\n\n- ローディングアニメーションを停止\n- ステータスコンテナをクリア\n- 保留中のメッセージ UI と保留中のツールマップをクリア\n- ストリーミングコンポーネント/メッセージ参照をリセット\n- `session.switchSession(...)` を呼び出す\n- チャットコンテナをクリアしてセッションコンテキストからリレンダリング（`renderInitialMessages`）\n- 新しいセッションのアーティファクトから todo をリロード\n- `Resumed session` を表示\n\nしたがって、表示される会話/todo の状態は新しいセッションファイルから再構築されます。\n\n## 起動時の再開とセッション内切り替えの比較\n\n### 起動時の再開（`--continue`、`--resume`、直接オープン）\n\n- セッションファイルは `createAgentSession(...)` の前に選択されます。\n- `sdk.ts` が `existingSession = sessionManager.buildSessionContext()` を構築します。\n- エージェントメッセージはセッション作成時に一度だけ復元されます。\n- モデル/thinking は作成時に選択されます（復元/フォールバックロジックを含む）。\n- その後、インタラクティブモードが `#restoreModeFromSession()` を実行して永続化されたモード状態（現在は plan/plan_paused）に再入します。\n\n### セッション内切り替え（`/resume` スタイルのセレクターパス）\n\n- 既に実行中の `AgentSession` 上で `AgentSession.switchSession(...)` を使用します。\n- メッセージ/モデル/thinking はその場で即座に再構築されます。\n- フック `session_before_switch`/`session_switch` イベントが発行されます。\n- UI のチャット/todo がリフレッシュされます。\n- セレクターフローでは専用の切り替え後モード復元呼び出しは行われません。モード再入の動作は起動時の `#restoreModeFromSession()` と対称的ではありません。\n\n## 失敗およびエッジケースの動作\n\n### キャンセルパス\n\n- CLI ピッカーのキャンセル -> `null` を返し、呼び出し元が `No session selected` を表示、プロセスが早期終了。\n- インタラクティブピッカーのキャンセル -> エディターが復元され、セッション変更なし。\n- フックのキャンセル（`session_before_switch`）-> `switchSession()` が `false` を返す。\n\n### 空のリストパス\n\n- CLI `--resume`（値なし）：空のリストは `No sessions found` を表示して終了。\n- インタラクティブセレクター：空のリストはメッセージを表示し、キャンセル可能なまま維持。\n\n### ターゲットセッションファイルが存在しない/無効な場合\n\n特定のパスでオープン/切り替えする場合（`setSessionFile`）：\n\n- ENOENT -> 空として扱われる -> その正確なパスで新しいセッションが初期化され永続化される。\n- 不正/無効なヘッダー（または実質的に読み取り不能な解析済みエントリ）-> 空として扱われる -> 新しいセッションが初期化され永続化される。\n\nこれはリカバリー動作であり、ハードエラーではありません。\n\n### ハードエラー\n\n切り替え/オープンは、真の I/O エラー（権限エラー、書き換えエラーなど）の場合にスローする可能性があり、呼び出し元に伝播します。\n\n### ID プレフィックスマッチングの注意事項\n\n- ID マッチングは `startsWith` を使用し、ソート済みリストの最初の一致を取得します。\n- 複数のセッションがプレフィックスを共有している場合でも、曖昧さ解消の UI はありません。\n- `SessionManager.list(...)` はメッセージがゼロのセッションを除外するため、それらのセッションは ID マッチ/リストピッカーでは再開できません。\n",
	"ja/sessions/session-tree-plan.md": "---\ntitle: セッションツリーアーキテクチャ\ndescription: ブランチ、ナビゲーション、および親子会話関係を持つセッションツリーアーキテクチャ。\nsidebar:\n  order: 2\n  label: ツリーアーキテクチャ\ni18n:\n  sourceHash: bd8b78d6c33a\n  translator: machine\n---\n\n# セッションツリーアーキテクチャ（現行）\n\nリファレンス: [session.md](./session.md)\n\nこのドキュメントでは、セッションツリーナビゲーションの現在の仕組みについて説明します：インメモリツリーモデル、リーフ移動ルール、ブランチ動作、および拡張機能/イベント統合。\n\n## このサブシステムとは\n\nセッションは追記専用のエントリログとして保存されますが、ランタイムの動作はツリーベースです：\n\n- ヘッダー以外のすべてのエントリは `id` と `parentId` を持ちます。\n- アクティブな位置は `SessionManager` の `leafId` です。\n- エントリの追加は常に現在のリーフの子として作成されます。\n- ブランチは履歴を**書き換えません**。次の追加の前にリーフの指す先を変更するだけです。\n\n主要ファイル：\n\n- `src/session/session-manager.ts` — ツリーデータモデル、走査、リーフ移動、ブランチ/セッション抽出\n- `src/session/agent-session.ts` — `/tree` ナビゲーションフロー、要約、フック/イベント発行\n- `src/modes/components/tree-selector.ts` — インタラクティブなツリーUIの動作とフィルタリング\n- `src/modes/controllers/selector-controller.ts` — `/tree` と `/branch` のセレクターオーケストレーション\n- `src/modes/controllers/input-controller.ts` — コマンドルーティング（`/tree`、`/branch`、ダブルエスケープ動作）\n- `src/session/messages.ts` — `branch_summary`、`compaction`、および `custom_message` エントリのLLMコンテキストメッセージへの変換\n\n## `SessionManager` のツリーデータモデル\n\nランタイムインデックス：\n\n- `#byId: Map<string, SessionEntry>` — 任意のエントリの高速ルックアップ\n- `#leafId: string | null` — ツリー内の現在位置\n- `#labelsById: Map<string, string>` — ターゲットエントリIDによる解決済みラベル\n\nツリーAPI：\n\n- `getBranch(fromId?)` は親リンクをルートまでたどり、ルート→ノードのパスを返します\n- `getTree()` は `SessionTreeNode[]`（`entry`、`children`、`label`）を返します\n  - 親リンクが子配列に変換されます\n  - 親が見つからないエントリはルートとして扱われます\n  - 子はタイムスタンプの古い順→新しい順にソートされます\n- `getChildren(parentId)` は直接の子を返します\n- `getLabel(id)` は `labelsById` から現在のラベルを解決します\n\n`getTree()` はランタイムの射影であり、永続化は追記専用のJSONLエントリのままです。\n\n## リーフ移動のセマンティクス\n\n3つのリーフ移動プリミティブがあります：\n\n1. `branch(entryId)`\n   - エントリの存在を検証します\n   - `leafId = entryId` を設定します\n   - 新しいエントリは書き込まれません\n\n2. `resetLeaf()`\n   - `leafId = null` を設定します\n   - 次の追加で新しいルートエントリ（`parentId = null`）が作成されます\n\n3. `branchWithSummary(branchFromId, summary, details?, fromExtension?)`\n   - `branchFromId: string | null` を受け取ります\n   - `leafId = branchFromId` を設定します\n   - そのリーフの子として `branch_summary` エントリを追加します\n   - `branchFromId` が `null` の場合、`fromId` は `\"root\"` として永続化されます\n\n## `/tree` ナビゲーション動作（同一セッションファイル内）\n\n`AgentSession.navigateTree()` はナビゲーションであり、ファイルのフォークではありません。\n\nフロー：\n\n1. ターゲットを検証し、放棄されるパスを計算します（`collectEntriesForBranchSummary`）\n2. `TreePreparation` とともに `session_before_tree` を発行します\n3. オプションで放棄されるエントリを要約します（フック提供の要約または組み込みサマライザー）\n4. 新しいリーフターゲットを計算します：\n   - **user** メッセージを選択した場合：リーフはその親に移動し、メッセージテキストがエディターのプリフィルとして返されます\n   - **custom_message** を選択した場合：userメッセージと同じルール（リーフ = 親、テキストがエディターにプリフィル）\n   - その他のエントリを選択した場合：リーフ = 選択されたエントリのID\n5. リーフ移動を適用します：\n   - 要約あり：`branchWithSummary(newLeafId, ...)`\n   - 要約なしで `newLeafId === null`：`resetLeaf()`\n   - それ以外：`branch(newLeafId)`\n6. 新しいリーフからエージェントコンテキストを再構築し、`session_tree` を発行します\n\n重要：要約エントリは、放棄されたブランチの末尾ではなく、**新しいナビゲーション位置**に添付されます。\n\n## `/branch` 動作（新しいセッションファイル）\n\n`/branch` と `/tree` は意図的に異なります：\n\n- `/tree` は現在のセッションファイル内をナビゲートします。\n- `/branch` は新しいセッションブランチファイルを作成します（非永続モードではインメモリ置換）。\n\nユーザー向け `/branch` フロー（`SelectorController.showUserMessageSelector` → `AgentSession.branch`）：\n\n- ブランチソースは **user メッセージ** である必要があります。\n- 選択されたユーザーテキストがエディターのプリフィル用に抽出されます。\n- 選択されたユーザーメッセージがルート（`parentId === null`）の場合：`newSession({ parentSession: previousSessionFile })` で新しいセッションを開始します。\n- それ以外：`createBranchedSession(selectedEntry.parentId)` で選択されたプロンプト境界までの履歴をフォークします。\n\n`SessionManager.createBranchedSession(leafId)` の詳細：\n\n- `getBranch(leafId)` でルート→リーフのパスを構築します。見つからない場合はスローします。\n- コピーされるパスから既存の `label` エントリを除外します。\n- パスに残るエントリに対して、解決済みの `labelsById` から新しいラベルエントリを再構築します。\n- 永続モード：新しいJSONLファイルを書き込み、マネージャーをそちらに切り替えます。新しいファイルパスを返します。\n- インメモリモード：インメモリのエントリを置換します。`undefined` を返します。\n\n## コンテキスト再構築と要約/カスタム統合\n\n`buildSessionContext()`（`session-manager.ts` 内）はアクティブなルート→リーフのパスを解決し、有効なLLMコンテキスト状態を構築します：\n\n- パス上の最新のthinking/model/mode/ttsr状態を追跡します。\n- パス上の最新のコンパクションを処理します：\n  - まずコンパクション要約を出力します\n  - `firstKeptEntryId` からコンパクションポイントまでの保持メッセージを再生します\n  - その後、コンパクション後のメッセージを再生します\n- `branch_summary` と `custom_message` エントリを `AgentMessage` オブジェクトとして含めます。\n\n`session/messages.ts` はこれらのメッセージタイプをモデル入力用にマッピングします：\n\n- `branchSummary` と `compactionSummary` はuserロールのテンプレート化されたコンテキストメッセージになります\n- `custom`/`hookMessage` はuserロールのコンテンツメッセージになります\n\nつまり、ツリー移動は古いエントリを変更するのではなく、アクティブなリーフパスを変更することでコンテキストを変更します。\n\n## ラベルとツリーUIの動作\n\nラベルの永続化：\n\n- `appendLabelChange(targetId, label?)` は現在のリーフチェーン上に `label` エントリを書き込みます。\n- `labelsById` は即座に更新されます（設定または削除）。\n- `getTree()` は返される各ノードに現在のラベルを解決します。\n\nツリーセレクターの動作（`tree-selector.ts`）：\n\n- ツリーをフラット化してナビゲーションし、アクティブパスのハイライトを維持し、アクティブブランチの表示を優先します。\n- フィルターモードをサポート：`default`、`no-tools`、`user-only`、`labeled-only`、`all`。\n- レンダリングされたセマンティックコンテンツに対するフリーテキスト検索をサポートします。\n- `Shift+L` でインラインラベル編集を開き、`appendLabelChange` で書き込みます。\n\nコマンドルーティング：\n\n- `/tree` は常にツリーセレクターを開きます。\n- `/branch` は `doubleEscapeAction=tree` でない限りユーザーメッセージセレクターを開きます。その場合はツリーセレクターのUXも使用します。\n\n## ツリー操作に対する拡張機能とフックのタッチポイント\n\nコマンド時の拡張機能API（`ExtensionCommandContext`）：\n\n- `branch(entryId)` — ブランチセッションファイルを作成\n- `navigateTree(targetId, { summarize? })` — 現在のツリー/ファイル内を移動\n\nツリーナビゲーション周辺のイベント：\n\n- `session_before_tree`\n  - `TreePreparation` を受け取ります：\n    - `targetId`\n    - `oldLeafId`\n    - `commonAncestorId`\n    - `entriesToSummarize`\n    - `userWantsSummary`\n  - ナビゲーションをキャンセルできます\n  - 組み込みサマライザーの代わりに使用される要約ペイロードを提供できます\n  - abort `signal` を受け取ります（エスケープキャンセルパス）\n- `session_tree`\n  - `newLeafId`、`oldLeafId` を発行します\n  - 要約が作成された場合は `summaryEntry` を含みます\n  - `fromExtension` は要約の出所を示します\n\n隣接する関連ライフサイクルフック：\n\n- `session_before_branch` / `session_branch` — `/branch` フロー用\n- `session_before_compact`、`session.compacting`、`session_compact` — 後でツリーコンテキスト再構築に影響するコンパクションエントリ用\n\n## 実際の制約とエッジケース\n\n- `branch()` は `null` をターゲットにできません。最初のエントリ前のルート状態には `resetLeaf()` を使用してください。\n- `branchWithSummary()` は `null` ターゲットをサポートし、`fromId: \"root\"` を記録します。\n- ツリーセレクターで現在のリーフを選択するとノーオペレーションになります。\n- 要約にはアクティブなモデルが必要です。モデルがない場合、要約ナビゲーションは即座に失敗します。\n- 要約が中止された場合、ナビゲーションはキャンセルされ、リーフは変更されません。\n- インメモリセッションは `createBranchedSession` からブランチファイルパスを返しません。\n\n## 残存するレガシー互換性\n\nセッションマイグレーションはロード時に実行されます：\n\n- v1→v2 は `id`/`parentId` を追加し、コンパクションインデックスアンカーをIDアンカーに変換します\n- v2→v3 はレガシーの `hookMessage` ロールを `custom` に移行します\n\n現在のランタイム動作はマイグレーション後のバージョン3ツリーセマンティクスです。\n",
	"ja/sessions/session.md": "---\ntitle: セッションストレージとエントリモデル\ndescription: 追記専用のセッションストレージモデル。エントリタイプ、永続化、フォーマット間のマイグレーションについて。\nsidebar:\n  order: 1\n  label: ストレージとエントリモデル\ni18n:\n  sourceHash: 42fe17549e00\n  translator: machine\n---\n\n# セッションストレージとエントリモデル\n\nこのドキュメントは、コーディングエージェントのセッションがどのように表現、永続化、マイグレーション、およびランタイムで再構築されるかについての信頼できる情報源です。\n\n## スコープ\n\n対象範囲：\n\n- セッションJSONLフォーマットとバージョニング\n- エントリの分類とツリーセマンティクス（`id`/`parentId` + リーフポインター）\n- 古いファイルや不正なファイルを読み込む際のマイグレーション/互換性の動作\n- コンテキスト再構築（`buildSessionContext`）\n- 永続化の保証、障害時の動作、切り詰め/blobの外部化\n- ストレージ抽象化（`FileSessionStorage`、`MemorySessionStorage`）および関連ユーティリティ\n\nセッションデータに影響するセマンティクスを超えた `/tree` UIレンダリングの動作は対象外です。\n\n## 実装ファイル\n\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`src/session/messages.ts`](../../packages/coding-agent/src/session/messages.ts)\n- [`src/session/session-storage.ts`](../../packages/coding-agent/src/session/session-storage.ts)\n- [`src/session/history-storage.ts`](../../packages/coding-agent/src/session/history-storage.ts)\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts)\n\n## ディスク上のレイアウト\n\nデフォルトのセッションファイルの場所：\n\n```text\n~/.xcsh/agent/sessions/--<cwd-encoded>--/<timestamp>_<sessionId>.jsonl\n```\n\n`<cwd-encoded>` は作業ディレクトリから先頭のスラッシュを除去し、`/`、`\\\\`、`:` を `-` に置換して生成されます。\n\nblobストアの場所：\n\n```text\n~/.xcsh/agent/blobs/<sha256>\n```\n\nターミナルのブレッドクラムファイルは以下に書き込まれます：\n\n```text\n~/.xcsh/agent/terminal-sessions/<terminal-id>\n```\n\nブレッドクラムの内容は2行で構成されます：元のcwd、次にセッションファイルのパス。`continueRecent()` は最新のmtimeでスキャンする前に、このターミナルスコープのポインターを優先します。\n\n## ファイルフォーマット\n\nセッションファイルはJSONL形式で、1行につき1つのJSONオブジェクトです。\n\n- 1行目は常にセッションヘッダー（`type: \"session\"`）。\n- 残りの行は `SessionEntry` の値。\n- エントリはランタイムでは追記専用です。ブランチのナビゲーションは既存のエントリを変更するのではなく、ポインター（`leafId`）を移動します。\n\n### ヘッダー（`SessionHeader`）\n\n```json\n{\n  \"type\": \"session\",\n  \"version\": 3,\n  \"id\": \"1f9d2a6b9c0d1234\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\",\n  \"cwd\": \"/work/pi\",\n  \"title\": \"optional session title\",\n  \"parentSession\": \"optional lineage marker\"\n}\n```\n\n注意事項：\n\n- `version` はv1ファイルではオプションです。省略されている場合はv1を意味します。\n- `parentSession` は不透明な系統文字列です。現在のコードはフロー（`fork`、`forkFrom`、`createBranchedSession`、または明示的な `newSession({ parentSession })`）に応じてセッションIDまたはセッションパスのいずれかを書き込みます。型付けされた外部キーではなく、メタデータとして扱ってください。\n\n### エントリベース（`SessionEntryBase`）\n\nヘッダー以外のすべてのエントリには以下が含まれます：\n\n```json\n{\n  \"type\": \"...\",\n  \"id\": \"8-char-id\",\n  \"parentId\": \"previous-or-branch-parent\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\"\n}\n```\n\n`parentId` はルートエントリ（最初の追加、または `resetLeaf()` 後）の場合 `null` になり得ます。\n\n## エントリの分類\n\n`SessionEntry` は以下の共用体型です：\n\n- `message`\n- `thinking_level_change`\n- `model_change`\n- `compaction`\n- `branch_summary`\n- `custom`\n- `custom_message`\n- `label`\n- `ttsr_injection`\n- `session_init`\n- `mode_change`\n\n### `message`\n\n`AgentMessage` を直接格納します。\n\n```json\n{\n  \"type\": \"message\",\n  \"id\": \"a1b2c3d4\",\n  \"parentId\": null,\n  \"timestamp\": \"2026-02-16T10:21:00.000Z\",\n  \"message\": {\n    \"role\": \"assistant\",\n    \"provider\": \"anthropic\",\n    \"model\": \"claude-sonnet-4-5\",\n    \"content\": [{ \"type\": \"text\", \"text\": \"Done.\" }],\n    \"usage\": { \"input\": 100, \"output\": 20, \"cacheRead\": 0, \"cacheWrite\": 0, \"cost\": { \"input\": 0, \"output\": 0, \"cacheRead\": 0, \"cacheWrite\": 0, \"total\": 0 } },\n    \"timestamp\": 1760000000000\n  }\n}\n```\n\n### `model_change`\n\n```json\n{\n  \"type\": \"model_change\",\n  \"id\": \"b1c2d3e4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:21:30.000Z\",\n  \"model\": \"openai/gpt-4o\",\n  \"role\": \"default\"\n}\n```\n\n`role` はオプションです。省略された場合、コンテキスト再構築では `default` として扱われます。\n\n### `thinking_level_change`\n\n```json\n{\n  \"type\": \"thinking_level_change\",\n  \"id\": \"c1d2e3f4\",\n  \"parentId\": \"b1c2d3e4\",\n  \"timestamp\": \"2026-02-16T10:22:00.000Z\",\n  \"thinkingLevel\": \"high\"\n}\n```\n\n### `compaction`\n\n```json\n{\n  \"type\": \"compaction\",\n  \"id\": \"d1e2f3a4\",\n  \"parentId\": \"c1d2e3f4\",\n  \"timestamp\": \"2026-02-16T10:23:00.000Z\",\n  \"summary\": \"Conversation summary\",\n  \"shortSummary\": \"Short recap\",\n  \"firstKeptEntryId\": \"a1b2c3d4\",\n  \"tokensBefore\": 42000,\n  \"details\": { \"readFiles\": [\"src/a.ts\"] },\n  \"preserveData\": { \"hookState\": true },\n  \"fromExtension\": false\n}\n```\n\n### `branch_summary`\n\n```json\n{\n  \"type\": \"branch_summary\",\n  \"id\": \"e1f2a3b4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:24:00.000Z\",\n  \"fromId\": \"a1b2c3d4\",\n  \"summary\": \"Summary of abandoned path\",\n  \"details\": { \"note\": \"optional\" },\n  \"fromExtension\": true\n}\n```\n\nルートからの分岐（`branchFromId === null`）の場合、`fromId` はリテラル文字列 `\"root\"` になります。\n\n### `custom`\n\n拡張機能の状態永続化に使用されます。`buildSessionContext` では無視されます。\n\n```json\n{\n  \"type\": \"custom\",\n  \"id\": \"f1a2b3c4\",\n  \"parentId\": \"e1f2a3b4\",\n  \"timestamp\": \"2026-02-16T10:25:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"data\": { \"state\": 1 }\n}\n```\n\n### `custom_message`\n\n拡張機能が提供するメッセージで、LLMコンテキストに参加します。\n\n```json\n{\n  \"type\": \"custom_message\",\n  \"id\": \"a2b3c4d5\",\n  \"parentId\": \"f1a2b3c4\",\n  \"timestamp\": \"2026-02-16T10:26:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"content\": \"Injected context\",\n  \"display\": true,\n  \"details\": { \"debug\": false }\n}\n```\n\n### `label`\n\n```json\n{\n  \"type\": \"label\",\n  \"id\": \"b2c3d4e5\",\n  \"parentId\": \"a2b3c4d5\",\n  \"timestamp\": \"2026-02-16T10:27:00.000Z\",\n  \"targetId\": \"a1b2c3d4\",\n  \"label\": \"checkpoint\"\n}\n```\n\n`label: undefined` は `targetId` のラベルをクリアします。\n\n### `ttsr_injection`\n\n```json\n{\n  \"type\": \"ttsr_injection\",\n  \"id\": \"c2d3e4f5\",\n  \"parentId\": \"b2c3d4e5\",\n  \"timestamp\": \"2026-02-16T10:28:00.000Z\",\n  \"injectedRules\": [\"ruleA\", \"ruleB\"]\n}\n```\n\n### `session_init`\n\n```json\n{\n  \"type\": \"session_init\",\n  \"id\": \"d2e3f4a5\",\n  \"parentId\": \"c2d3e4f5\",\n  \"timestamp\": \"2026-02-16T10:29:00.000Z\",\n  \"systemPrompt\": \"...\",\n  \"task\": \"...\",\n  \"tools\": [\"read\", \"edit\"],\n  \"outputSchema\": { \"type\": \"object\" }\n}\n```\n\n### `mode_change`\n\n```json\n{\n  \"type\": \"mode_change\",\n  \"id\": \"e2f3a4b5\",\n  \"parentId\": \"d2e3f4a5\",\n  \"timestamp\": \"2026-02-16T10:30:00.000Z\",\n  \"mode\": \"plan\",\n  \"data\": { \"planFile\": \"/tmp/plan.md\" }\n}\n```\n\n## バージョニングとマイグレーション\n\n現在のセッションバージョン：`3`。\n\n### v1 -> v2\n\nヘッダーの `version` が未設定または `< 2` の場合に適用されます：\n\n- ヘッダー以外の各エントリに `id` と `parentId` を追加します。\n- ファイルの順序を使用して線形の親チェーンを再構築します。\n- compactionフィールド `firstKeptEntryIndex` が存在する場合、`firstKeptEntryId` にマイグレーションします。\n- ヘッダーの `version = 2` を設定します。\n\n### v2 -> v3\n\nヘッダーの `version < 3` の場合に適用されます：\n\n- `message` エントリ：レガシーの `message.role === \"hookMessage\"` を `\"custom\"` に書き換えます。\n- ヘッダーの `version = 3` を設定します。\n\n### マイグレーションのトリガーと永続化\n\n- マイグレーションはセッション読み込み時（`setSessionFile`）に実行されます。\n- いずれかのマイグレーションが実行された場合、ファイル全体が即座にディスクに書き直されます。\n- マイグレーションはまずメモリ内のエントリを変更し、その後書き直されたJSONLを永続化します。\n\n## 読み込みと互換性の動作\n\n`loadEntriesFromFile(path)` の動作：\n\n- ファイルが存在しない場合（`ENOENT`）-> `[]` を返します。\n- パースできない行は寛容なJSONLパーサー（`parseJsonlLenient`）で処理されます。\n- 最初にパースされたエントリが有効なセッションヘッダーでない場合（`type !== \"session\"` または文字列 `id` が欠落）-> `[]` を返します。\n\n`SessionManager.setSessionFile()` の動作：\n\n- ローダーからの `[]` は空/存在しないセッションとして扱われ、そのパスに新しく初期化されたセッションファイルで置き換えられます。\n- 有効なファイルは読み込まれ、必要に応じてマイグレーションされ、blob参照が解決された後、インデックスが作成されます。\n\n## ツリーとリーフのセマンティクス\n\n基盤となるモデルは追記専用ツリー + 可変リーフポインターです：\n\n- すべてのappendメソッドは、`parentId` が現在の `leafId` である新しいエントリを正確に1つ作成します。\n- 新しいエントリが新しい `leafId` になります。\n- `branch(entryId)` は `leafId` のみを移動します。既存のエントリは変更されません。\n- `resetLeaf()` は `leafId = null` を設定します。次のappendは新しいルートエントリ（`parentId: null`）を作成します。\n- `branchWithSummary()` はリーフをブランチターゲットに設定し、`branch_summary` エントリを追加します。\n\n`getEntries()` はヘッダー以外のすべてのエントリを挿入順で返します。通常の操作では既存のエントリは削除されません。書き換えは表現を更新しながら論理的な履歴を保持します（マイグレーション、移動、対象を絞った書き換えヘルパー）。\n\n## コンテキスト再構築（`buildSessionContext`）\n\n`buildSessionContext(entries, leafId, byId?)` はモデルに送信される内容を解決します。\n\nアルゴリズム：\n\n1. リーフの決定：\n   - `leafId === null` -> 空のコンテキストを返します。\n   - 明示的な `leafId` -> そのエントリが見つかればそれを使用します。\n   - それ以外の場合は最後のエントリにフォールバックします。\n2. リーフから `parentId` チェーンをルートまでたどり、ルート->リーフのパスに反転します。\n3. パス全体にわたってランタイム状態を導出します：\n   - `thinkingLevel` は最新の `thinking_level_change` から（デフォルトは `\"off\"`）\n   - `model_change` エントリからモデルマップ（`role ?? \"default\"`）\n   - 明示的なモデル変更がない場合、アシスタントメッセージのprovider/modelからフォールバック `models.default` を導出\n   - すべての `ttsr_injection` エントリから重複排除された `injectedTtsrRules`\n   - 最新の `mode_change` からmode/modeData（デフォルトモードは `\"none\"`）\n4. メッセージリストの構築：\n   - `message` エントリはそのまま通過\n   - `custom_message` エントリは `createCustomMessage` を通じて `custom` AgentMessagesになる\n   - `branch_summary` エントリは `createBranchSummaryMessage` を通じて `branchSummary` AgentMessagesになる\n   - パス上に `compaction` が存在する場合：\n     - 最初にcompactionサマリーを出力（`createCompactionSummaryMessage`）\n     - `firstKeptEntryId` からcompaction境界までのパスエントリを出力\n     - compaction境界以降のエントリを出力\n\n`custom` と `session_init` エントリはモデルコンテキストに直接注入しません。\n\n## 永続化の保証と障害モデル\n\n### 永続化とインメモリ\n\n- `SessionManager.create/open/continueRecent/forkFrom` -> 永続モード（`persist = true`）。\n- `SessionManager.inMemory` -> 非永続モード（`persist = false`）、`MemorySessionStorage` を使用。\n\n### 書き込みパイプライン\n\n書き込みは内部のプロミスチェーン（`#persistChain`）と `NdjsonFileWriter` を通じてシリアライズされます。\n\n- `append*` はインメモリの状態を即座に更新します。\n- 永続化は少なくとも1つのアシスタントメッセージが存在するまで遅延されます。\n  - 最初のアシスタント以前：エントリはメモリに保持され、ファイルへの追加は発生しません。\n  - 最初のアシスタントが存在する時点：インメモリの完全なセッションがファイルにフラッシュされます。\n  - それ以降：新しいエントリはインクリメンタルに追加されます。\n\nコード内の根拠：アシスタントの応答を生成しなかったセッションの永続化を回避するため。\n\n### 耐久性操作\n\n- `flush()` はライターをフラッシュし、`fsync()` を呼び出します。\n- アトミックな完全書き換え（`#rewriteFile`）は一時ファイルに書き込み、flush+fsync、close、その後ターゲットにrenameします。\n- マイグレーション、`setSessionName`、`rewriteEntries`、move操作、およびツールコール引数の書き換えで使用されます。\n\n### エラー動作\n\n- 永続化エラーはラッチされ（`#persistError`）、後続の操作で再スローされます。\n- 最初のエラーはセッションファイルのコンテキストとともに1回だけログに記録されます。\n- ライターのcloseはベストエフォートですが、最初の意味のあるエラーを伝播します。\n\n## データサイズの制御とBlobの外部化\n\nエントリの永続化前：\n\n- 大きな文字列は `MAX_PERSIST_CHARS`（500,000文字）に切り詰められ、通知が付加されます：\n  - `\"[Session persistence truncated large content]\"`\n- 一時フィールド `partialJson` と `jsonlEvents` は削除されます。\n- オブジェクトに `content` と `lineCount` の両方がある場合、切り詰め後に行数が再計算されます。\n- `content` 配列内のbase64長が1024以上の画像ブロックはblob参照に外部化されます：\n  - `blob:sha256:<hash>` として格納\n  - 生のバイトがblobストア（`BlobStore.put`）に書き込まれます\n\n読み込み時、blob参照はmessage/custom_messageの画像ブロック用にbase64に戻されます。\n\n## ストレージ抽象化\n\n`SessionStorage` インターフェースは `SessionManager` が使用するすべてのファイルシステム操作を提供します：\n\n- 同期：`ensureDirSync`、`existsSync`、`writeTextSync`、`statSync`、`listFilesSync`\n- 非同期：`exists`、`readText`、`readTextPrefix`、`writeText`、`rename`、`unlink`、`openWriter`\n\n実装：\n\n- `FileSessionStorage`：実際のファイルシステム（Bun + node fs）\n- `MemorySessionStorage`：テスト/非永続セッション用のマップベースのインメモリ実装\n\n`SessionStorageWriter` は `writeLine`、`flush`、`fsync`、`close`、`getError` を公開します。\n\n## セッション検出ユーティリティ\n\n`session-manager.ts` で定義されています：\n\n- `getRecentSessions(sessionDir, limit)` -> UI/セッションピッカー用の軽量メタデータ\n- `findMostRecentSession(sessionDir)` -> mtimeが最新のもの\n- `list(cwd, sessionDir?)` -> 1つのプロジェクトスコープ内のセッション\n- `listAll()` -> `~/.xcsh/agent/sessions` 配下のすべてのプロジェクトスコープにわたるセッション\n\nメタデータの抽出は可能な場合、プレフィックスのみを読み取ります（`readTextPrefix(..., 4096)`）。\n\n## 関連するが別のもの：プロンプト履歴ストレージ\n\n`HistoryStorage`（`history-storage.ts`）はプロンプトの呼び出し/検索用の別個のSQLiteサブシステムであり、セッションのリプレイ用ではありません。\n\n- DB：`~/.xcsh/agent/history.db`\n- テーブル：`history(id, prompt, created_at, cwd)`\n- FTS5インデックス：トリガーで同期が維持される `history_fts`\n- インメモリの最終プロンプトキャッシュを使用して連続する同一プロンプトを重複排除\n- 非同期挿入（`setImmediate`）により、プロンプトのキャプチャがターンの実行をブロックしない\n\n会話グラフ/状態のリプレイにはセッションファイルを使用し、プロンプト履歴のUXには `HistoryStorage` を使用してください。\n",
	"ja/sessions/ttsr-injection-lifecycle.md": "---\ntitle: TTSRインジェクションライフサイクル\ndescription: コンテキスト管理のためのTTSR（tool-use、tool-result、system-reminder）インジェクションライフサイクル。\nsidebar:\n  order: 9\n  label: TTSRインジェクション\ni18n:\n  sourceHash: d6179a286584\n  translator: machine\n---\n\n# TTSRインジェクションライフサイクル\n\nこのドキュメントでは、Time Traveling Stream Rules（TTSR）の現在のランタイムパスについて、ルールの検出からストリーム中断、リトライインジェクション、拡張通知、セッション状態の処理までを説明します。\n\n## 実装ファイル\n\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/export/ttsr.ts`](../../packages/coding-agent/src/export/ttsr.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/prompts/system/ttsr-interrupt.md`](../../packages/coding-agent/src/prompts/system/ttsr-interrupt.md)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/types.ts`](../../packages/coding-agent/src/extensibility/extensions/types.ts)\n- [`../src/extensibility/hooks/types.ts`](../../packages/coding-agent/src/extensibility/hooks/types.ts)\n- [`../src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n\n## 1. ディスカバリーフィードとルール登録\n\nセッション作成時に、`createAgentSession()` は検出されたすべてのルールを読み込み、`TtsrManager` を構築します：\n\n```ts\nconst ttsrSettings = settings.getGroup(\"ttsr\");\nconst ttsrManager = new TtsrManager(ttsrSettings);\nconst rulesResult = await loadCapability<Rule>(ruleCapability.id, { cwd });\nfor (const rule of rulesResult.items) {\n  if (rule.ttsrTrigger) ttsrManager.addRule(rule);\n}\n```\n\n### 登録前の重複排除動作\n\n`loadCapability(\"rules\")` は `rule.name` に基づいて重複排除を行い、先勝ちセマンティクス（優先度の高いプロバイダーが先）を適用します。シャドウされた重複はTTSR登録前に除去されます。\n\n### `TtsrManager.addRule()` の動作\n\n以下の場合、登録はスキップされます：\n\n- `rule.ttsrTrigger` が存在しない\n- 同じ `rule.name` を持つルールが既にこのマネージャーに登録されている\n- 正規表現のコンパイルに失敗する（`new RegExp(rule.ttsrTrigger)` がスローする）\n\n無効な正規表現トリガーは警告としてログに記録され、無視されます。セッションの起動は継続します。\n\n### 設定に関する注意事項\n\n`TtsrSettings.enabled` はマネージャーに読み込まれますが、現在ランタイムのゲーティングではチェックされていません。ルールが存在する場合、マッチングは引き続き実行されます。\n\n## 2. ストリーミングモニターのライフサイクル\n\nTTSR検出は `AgentSession.#handleAgentEvent` 内で実行されます。\n\n### ターン開始\n\n`turn_start` 時に、ストリームバッファがリセットされます：\n\n- `ttsrManager.resetBuffer()`\n\n### ストリーム中（`message_update`）\n\nアシスタントの更新が到着し、ルールが存在する場合：\n\n- `text_delta` と `toolcall_delta` を監視\n- デルタをマネージャーバッファに追加\n- `check(buffer)` を呼び出し\n\n`check()` は登録されたルールを反復し、リピートポリシー（`#canTrigger`）を通過するすべてのマッチルールを返します。\n\n## 3. トリガー判定と即時中断パス\n\n1つ以上のルールがマッチした場合：\n\n1. `markInjected(matches)` がマネージャーのインジェクション状態にルール名を記録\n2. マッチしたルールが `#pendingTtsrInjections` にキューイング\n3. `#ttsrAbortPending = true`\n4. `agent.abort()` が即座に呼び出される\n5. `ttsr_triggered` イベントが非同期で発行される（ファイア・アンド・フォーゲット）\n6. リトライ処理が `setTimeout(..., 50)` でスケジュールされる\n\n中断は拡張コールバックの完了を待ちません。\n\n## 4. リトライスケジューリング、コンテキストモード、リマインダーインジェクション\n\n50msタイムアウト後：\n\n1. `#ttsrAbortPending = false`\n2. `ttsrManager.getSettings().contextMode` を読み取り\n3. `contextMode === \"discard\"` の場合、`agent.popMessage()` で部分的なアシスタント出力を破棄\n4. 保留中のルールから `ttsr-interrupt.md` テンプレートを使用してインジェクションコンテンツを構築\n5. ルールごとに1つの `<system-interrupt ...>` ブロックを含む合成ユーザーメッセージを追加\n6. `agent.continue()` を呼び出して生成をリトライ\n\nテンプレートのペイロード：\n\n```xml\n<system-interrupt reason=\"rule_violation\" rule=\"{{name}}\" path=\"{{path}}\">\n...\n{{content}}\n</system-interrupt>\n```\n\n保留中のインジェクションはコンテンツ生成後にクリアされます。\n\n### 部分出力に対する `contextMode` の動作\n\n- `discard`: 部分的/中断されたアシスタントメッセージはリトライ前に除去されます。\n- `keep`: 部分的なアシスタント出力は会話状態に残り、リマインダーはその後に追加されます。\n\n## 5. リピートポリシーとギャップロジック\n\n`TtsrManager` は `#messageCount` とルールごとの `lastInjectedAt` を追跡します。\n\n### `repeatMode: \"once\"`\n\nルールはインジェクション記録が存在した後、1回のみトリガーできます。\n\n### `repeatMode: \"after-gap\"`\n\nルールは以下の条件でのみ再トリガーできます：\n\n- `messageCount - lastInjectedAt >= repeatGap`\n\n`messageCount` は `turn_end` 時にインクリメントされるため、ギャップはストリームチャンクではなく完了したターン数で測定されます。\n\n## 6. イベント発行と拡張/フックサーフェス\n\n### セッションイベント\n\n`AgentSessionEvent` には以下が含まれます：\n\n```ts\n{ type: \"ttsr_triggered\"; rules: Rule[] }\n```\n\n### 拡張ランナー\n\n`#emitSessionEvent()` はイベントを以下にルーティングします：\n\n- 拡張リスナー（`ExtensionRunner.emit({ type: \"ttsr_triggered\", rules })`）\n- ローカルセッションサブスクライバー\n\n### フックとカスタムツールの型定義\n\n- 拡張APIは `on(\"ttsr_triggered\", ...)` を公開\n- フックAPIは `on(\"ttsr_triggered\", ...)` を公開\n- カスタムツールは `onSession({ reason: \"ttsr_triggered\", rules })` を受信\n\n### インタラクティブモードのレンダリングの違い\n\nインタラクティブモードでは、`session.isTtsrAbortPending` を使用して、TTSR中断中に中断されたアシスタントの停止理由が可視的な失敗として表示されることを抑制し、イベント到着時に `TtsrNotificationComponent` をレンダリングします。\n\n## 7. 永続化と再開状態（現在の実装）\n\n`SessionManager` はインジェクトされたルールの永続化に対する完全なスキーマサポートを備えています：\n\n- エントリタイプ: `ttsr_injection`\n- 追加API: `appendTtsrInjection(ruleNames)`\n- クエリAPI: `getInjectedTtsrRules()`\n- コンテキスト再構築には `SessionContext.injectedTtsrRules` が含まれる\n\n`TtsrManager` も `restoreInjected(ruleNames)` による復元をサポートしています。\n\n### 現在の接続状態\n\n現在のランタイムパスでは：\n\n- `AgentSession` はTTSRトリガー時に `ttsr_injection` エントリを追加しません。\n- `createAgentSession()` は `existingSession.injectedTtsrRules` を `ttsrManager` に復元しません。\n\n結果として：インジェクトされたルールの抑制はライブプロセスのインメモリで適用されますが、このパスによるセッションのリロード/再開時の永続化/復元は現在行われていません。\n\n## 8. 競合境界と順序保証\n\n### 中断 vs リトライコールバック\n\n- 中断はTTSRハンドラーの観点から同期的（`agent.abort()` が即座に呼び出される）\n- リトライはタイマーにより遅延（`50ms`）\n- 拡張通知は非同期であり、中断/リトライのスケジューリング前に意図的にawaitされない\n\n### 同一ストリームウィンドウ内の複数マッチ\n\n`check()` は現在マッチしているすべての適格なルールを返します。それらは次のリトライメッセージでバッチとしてインジェクトされます。\n\n### 中断と続行の間\n\nタイマーウィンドウ中に状態が変化する可能性があります（ユーザー中断、モードアクション、追加イベント）。リトライ呼び出しはベストエフォートです：`agent.continue().catch(() => {})` は後続のエラーを飲み込みます。\n\n## 9. エッジケースの要約\n\n- 無効な `ttsr_trigger` 正規表現：警告付きでスキップされ、他のルールは継続します。\n- ケイパビリティレイヤーでのルール名の重複：優先度の低い重複は登録前にシャドウされます。\n- マネージャーレイヤーでの名前の重複：2番目の登録は無視されます。\n- `contextMode: \"keep\"`: リマインダーリトライの前に、部分的な違反出力がコンテキストに残る可能性があります。\n- after-gapリピートは `turn_end` でのターンカウントのインクリメントに依存します。ターン途中のチャンクはギャップカウンターを進めません。\n",
	"ja/tui/theme.md": "---\ntitle: テーマリファレンス\ndescription: カラートークン、フォント設定、テーマカスタマイズに関するTUIテーマリファレンス。\nsidebar:\n  order: 3\n  label: テーマ\ni18n:\n  sourceHash: 7e962a7da157\n  translator: machine\n---\n\n# テーマリファレンス\n\nこのドキュメントでは、現在のコーディングエージェントにおけるテーマの仕組みについて説明します：スキーマ、読み込み、ランタイムの動作、および障害モードについて記載しています。\n\n## テーマシステムが制御するもの\n\nテーマシステムは以下を制御します：\n\n- TUI全体で使用される前景色/背景色のカラートークン\n- Markdownスタイルアダプター（`getMarkdownTheme()`）\n- セレクター/エディター/設定リストアダプター（`getSelectListTheme()`、`getEditorTheme()`、`getSettingsListTheme()`）\n- シンボルプリセット＋シンボルオーバーライド（`unicode`、`nerd`、`ascii`）\n- ネイティブハイライター（`@f5-sales-demo/pi-natives`）が使用する構文ハイライトカラー\n- ステータスラインセグメントの色\n\n主な実装：`src/modes/theme/theme.ts`\n\n## テーマJSONの構造\n\nテーマファイルは、`theme.ts`のランタイムスキーマ（`ThemeJsonSchema`）に対して検証され、`src/modes/theme/theme-schema.json`にミラーリングされたJSONオブジェクトです。\n\nトップレベルフィールド：\n\n- `name`（必須）\n- `colors`（必須；すべてのカラートークンが必須）\n- `vars`（任意；再利用可能なカラー変数）\n- `export`（任意；HTMLエクスポートの色）\n- `symbols`（任意）\n  - `preset`（任意：`unicode | nerd | ascii`）\n  - `overrides`（任意：`SymbolKey`のキー/値オーバーライド）\n\nカラー値は以下を受け付けます：\n\n- 16進文字列（`\"#RRGGBB\"`）\n- 256色インデックス（`0..255`）\n- 変数参照文字列（`vars`を通じて解決）\n- 空文字列（`\"\"`）はターミナルデフォルト（前景は`\\x1b[39m`、背景は`\\x1b[49m`）を意味します\n\n## 必須カラートークン（現行）\n\n以下のトークンはすべて`colors`で必須です。\n\n### コアテキストとボーダー（11）\n\n`accent`、`border`、`borderAccent`、`borderMuted`、`success`、`error`、`warning`、`muted`、`dim`、`text`、`thinkingText`\n\n### 背景ブロック（7）\n\n`selectedBg`、`userMessageBg`、`customMessageBg`、`toolPendingBg`、`toolSuccessBg`、`toolErrorBg`、`statusLineBg`\n\n### メッセージ/ツールテキスト（5）\n\n`userMessageText`、`customMessageText`、`customMessageLabel`、`toolTitle`、`toolOutput`\n\n### Markdown（10）\n\n`mdHeading`、`mdLink`、`mdLinkUrl`、`mdCode`、`mdCodeBlock`、`mdCodeBlockBorder`、`mdQuote`、`mdQuoteBorder`、`mdHr`、`mdListBullet`\n\n### ツールdiff＋構文ハイライト（12）\n\n`toolDiffAdded`、`toolDiffRemoved`、`toolDiffContext`、\n`syntaxComment`、`syntaxKeyword`、`syntaxFunction`、`syntaxVariable`、`syntaxString`、`syntaxNumber`、`syntaxType`、`syntaxOperator`、`syntaxPunctuation`\n\n### モード/思考ボーダー（8）\n\n`thinkingOff`、`thinkingMinimal`、`thinkingLow`、`thinkingMedium`、`thinkingHigh`、`thinkingXhigh`、`bashMode`、`pythonMode`\n\n### ステータスラインセグメントの色（14）\n\n`statusLineSep`、`statusLineModel`、`statusLinePath`、`statusLineGitClean`、`statusLineGitDirty`、`statusLineContext`、`statusLineSpend`、`statusLineStaged`、`statusLineDirty`、`statusLineUntracked`、`statusLineOutput`、`statusLineCost`、`statusLineSubagents`\n\n## 任意トークン\n\n### `export`セクション（任意）\n\nHTMLエクスポートのテーマヘルパーに使用されます：\n\n- `export.pageBg`\n- `export.cardBg`\n- `export.infoBg`\n\n省略した場合、エクスポートコードは解決済みテーマカラーからデフォルトを導出します。\n\n### `symbols`セクション（任意）\n\n- `symbols.preset`はテーマレベルのデフォルトシンボルセットを設定します。\n- `symbols.overrides`は個別の`SymbolKey`値をオーバーライドできます。\n\nランタイムの優先順位：\n\n1. 設定の`symbolPreset`オーバーライド（設定されている場合）\n2. テーマJSONの`symbols.preset`\n3. フォールバック `\"unicode\"`\n\n無効なオーバーライドキーは無視され、ログに記録されます（`logger.debug`）。\n\n## ビルトインテーマとカスタムテーマのソース\n\nテーマの検索順序（`loadThemeJson`）：\n\n1. ビルトイン埋め込みテーマ（`defaults/xcsh-dark.json`と`defaults/xcsh-light.json`が`defaultThemes`にコンパイル済み）\n2. カスタムテーマファイル：`<customThemesDir>/<name>.json`\n\nカスタムテーマディレクトリは`getCustomThemesDir()`から取得されます：\n\n- デフォルト：`~/.xcsh/agent/themes`\n- `PI_CODING_AGENT_DIR`でオーバーライド（`$PI_CODING_AGENT_DIR/themes`）\n\n`getAvailableThemes()`はビルトイン＋カスタムのマージされた名前をソートして返し、名前が衝突した場合はビルトインが優先されます。\n\n## 読み込み、検証、および解決\n\nカスタムテーマファイルの場合：\n\n1. JSONを読み取り\n2. JSONをパース\n3. `ThemeJsonSchema`に対して検証\n4. `vars`参照を再帰的に解決\n5. 解決された値をターミナルのカラーモードに応じてANSIに変換\n\n検証の動作：\n\n- 必須カラートークンの欠落：明示的なグループ化されたエラーメッセージ\n- 不正なトークンの型/値：JSONパス付きの検証エラー\n- 不明なテーマファイル：`Theme not found: <name>`\n\n変数参照の動作：\n\n- ネストされた参照をサポート\n- 存在しない変数参照でスロー\n- 循環参照でスロー\n\n## ターミナルカラーモードの動作\n\nカラーモード検出（`detectColorMode`）：\n\n- `COLORTERM=truecolor|24bit` => truecolor\n- `WT_SESSION` => truecolor\n- `TERM`が`dumb`、`linux`、または空 => 256color\n- それ以外 => truecolor\n\n変換の動作：\n\n- hex -> `Bun.color(..., \"ansi-16m\" | \"ansi-256\")`\n- numeric -> `38;5` / `48;5` ANSI\n- `\"\"` -> デフォルトfg/bgリセット\n\n## ランタイム切り替えの動作\n\n### 初期テーマ（`initTheme`）\n\n`main.ts`は以下の設定でテーマを初期化します：\n\n- `symbolPreset`\n- `colorBlindMode`\n- `theme.dark`\n- `theme.light`\n\n自動テーマスロット選択は`COLORFGBG`の背景検出を使用します：\n\n- `COLORFGBG`から背景インデックスをパース\n- `< 8` => ダークスロット（`theme.dark`）\n- `>= 8` => ライトスロット（`theme.light`）\n- パース失敗 => ダークスロット\n\n設定スキーマの現在のデフォルト：\n\n- `theme.dark = \"xcsh-dark\"`\n- `theme.light = \"xcsh-light\"`\n- `symbolPreset = \"unicode\"`\n- `colorBlindMode = false`\n\n### 明示的な切り替え（`setTheme`）\n\n- 選択されたテーマを読み込み\n- グローバル`theme`シングルトンを更新\n- オプションでウォッチャーを開始\n- `onThemeChange`コールバックをトリガー\n\n失敗時：\n\n- ビルトイン`dark`にフォールバック\n- `{ success: false, error }`を返却\n\n### プレビュー切り替え（`previewTheme`）\n\n- 一時的なプレビューテーマをグローバル`theme`に適用\n- それ自体は永続化された設定を変更**しません**\n- フォールバック置換なしでsuccess/errorを返却\n\n設定UIはこれをライブプレビューに使用し、キャンセル時に以前のテーマを復元します。\n\n## ウォッチャーとライブリロード\n\nウォッチャーが有効な場合（`setTheme(..., true)` / インタラクティブ初期化）：\n\n- カスタムファイルパス`<customThemesDir>/<currentTheme>.json`のみを監視\n- ビルトインは実質的に監視されない\n- ファイル`change`：リロードを試行（デバウンス付き）\n- ファイル`rename`/削除：`dark`にフォールバックし、ウォッチャーを閉じる\n\n自動モードは`SIGWINCH`リスナーもインストールし、ターミナルの状態が変化したときにダーク/ライトスロットのマッピングを再評価できます。\n\n## 色覚多様性モードの動作\n\n`colorBlindMode`はランタイムで1つのトークンのみ変更します：\n\n- `toolDiffAdded`がHSV調整されます（緑が青方向にシフト）\n- 調整は解決された値が16進文字列の場合にのみ適用されます\n\nその他のトークンは変更されません。\n\n## テーマ設定の永続化場所\n\nテーマ関連の設定は`Settings`によってグローバル設定YAMLに永続化されます：\n\n- パス：`<agentDir>/config.yml`\n- デフォルトのエージェントディレクトリ：`~/.xcsh/agent`\n- 実効デフォルトファイル：`~/.xcsh/agent/config.yml`\n\n永続化されるキー：\n\n- `theme.dark`\n- `theme.light`\n- `symbolPreset`\n- `colorBlindMode`\n\nレガシー移行が存在します：古いフラットな`theme: \"name\"`は、輝度検出に基づいてネストされた`theme.dark`または`theme.light`に移行されます。\n\n## カスタムテーマの作成（実践編）\n\n1. カスタムテーマディレクトリにファイルを作成します（例：`~/.xcsh/agent/themes/my-theme.json`）。\n2. `name`、任意の`vars`、および**すべての必須**`colors`トークンを含めます。\n3. オプションで`symbols`と`export`を含めます。\n4. どの自動スロットに割り当てるかに応じて、設定（`Display -> Dark theme`または`Display -> Light theme`）でテーマを選択します。\n\n最小限のスケルトン。`colors`のすべてのキーが必須です — ランタイムバリデーター\n（`additionalProperties: false`）は、欠落キーと不明なキーの両方を拒否します。\n出荷済みのリファレンス実装については\n[`packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json)\nおよび[`xcsh-light.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-light.json)を参照してください。\n\nステータスラインには、issue #242で文書化された2つの並列カラーシステムがあります：\n\n- 16進テキストカラー（`statusLinePath`、`statusLineGitClean`、`statusLineGitDirty`、\n  `statusLineStaged`、`statusLineDirty`、`statusLineUntracked`）は非powerline\n  レンダリングを駆動します。\n- 256色パレットインデックス（`statusLine<Segment>Bg` / `statusLine<Segment>Fg`）は\n  powerlineセグメントの塗りつぶしを駆動します。これらは上記の16進キーとは独立しており、\n  両方を設定する必要があります。\n\n```json\n{\n  \"name\": \"my-theme\",\n  \"vars\": {\n    \"accent\": \"#7aa2f7\",\n    \"muted\": 244\n  },\n  \"colors\": {\n    \"accent\": \"accent\",\n    \"chromeAccent\": \"accent\",\n    \"spinnerAccent\": \"accent\",\n    \"contentAccent\": \"muted\",\n    \"border\": \"#4c566a\",\n    \"borderAccent\": \"accent\",\n    \"borderMuted\": \"muted\",\n    \"success\": \"#9ece6a\",\n    \"error\": \"#f7768e\",\n    \"warning\": \"#e0af68\",\n    \"muted\": \"muted\",\n    \"dim\": 240,\n    \"gutterSuccess\": \"#7dcfff\",\n    \"gutterWarning\": \"#e0af68\",\n    \"text\": \"\",\n    \"thinkingText\": \"muted\",\n\n    \"selectedBg\": \"#2a2f45\",\n    \"userMessageBg\": \"#1f2335\",\n    \"userMessageText\": \"\",\n    \"customMessageBg\": \"#24283b\",\n    \"customMessageText\": \"\",\n    \"customMessageLabel\": \"accent\",\n    \"toolPendingBg\": \"#1f2335\",\n    \"toolSuccessBg\": \"#1f2d2a\",\n    \"toolErrorBg\": \"#2d1f2a\",\n    \"toolTitle\": \"\",\n    \"toolOutput\": \"muted\",\n\n    \"mdHeading\": \"accent\",\n    \"mdLink\": \"accent\",\n    \"mdLinkUrl\": \"muted\",\n    \"mdCode\": \"#c0caf5\",\n    \"mdCodeBlock\": \"#c0caf5\",\n    \"mdCodeBlockBorder\": \"muted\",\n    \"mdQuote\": \"muted\",\n    \"mdQuoteBorder\": \"muted\",\n    \"mdHr\": \"muted\",\n    \"mdListBullet\": \"accent\",\n\n    \"toolDiffAdded\": \"#9ece6a\",\n    \"toolDiffRemoved\": \"#f7768e\",\n    \"toolDiffContext\": \"muted\",\n\n    \"syntaxComment\": \"#565f89\",\n    \"syntaxKeyword\": \"#bb9af7\",\n    \"syntaxFunction\": \"#7aa2f7\",\n    \"syntaxVariable\": \"#c0caf5\",\n    \"syntaxString\": \"#9ece6a\",\n    \"syntaxNumber\": \"#ff9e64\",\n    \"syntaxType\": \"#2ac3de\",\n    \"syntaxOperator\": \"#89ddff\",\n    \"syntaxPunctuation\": \"#9aa5ce\",\n    \"syntaxControl\": \"#bb9af7\",\n\n    \"thinkingOff\": 240,\n    \"thinkingMinimal\": 244,\n    \"thinkingLow\": \"#7aa2f7\",\n    \"thinkingMedium\": \"#2ac3de\",\n    \"thinkingHigh\": \"#bb9af7\",\n    \"thinkingXhigh\": \"#f7768e\",\n\n    \"bashMode\": \"#2ac3de\",\n    \"pythonMode\": \"#bb9af7\",\n\n    \"statusLineBg\": \"#16161e\",\n    \"statusLineSep\": 240,\n    \"statusLineModel\": \"#bb9af7\",\n    \"statusLinePath\": \"#7aa2f7\",\n    \"statusLineGitClean\": \"#9ece6a\",\n    \"statusLineGitDirty\": \"#e0af68\",\n    \"statusLineContext\": \"#2ac3de\",\n    \"statusLineSpend\": \"#7dcfff\",\n    \"statusLineStaged\": \"#9ece6a\",\n    \"statusLineDirty\": \"#e0af68\",\n    \"statusLineUntracked\": \"#f7768e\",\n    \"statusLineOutput\": \"#c0caf5\",\n    \"statusLineCost\": \"#ff9e64\",\n    \"statusLineSubagents\": \"#bb9af7\",\n\n    \"statusLineOsIconBg\": 7,\n    \"statusLineOsIconFg\": 232,\n    \"statusLinePathBg\": 4,\n    \"statusLinePathFg\": 254,\n    \"statusLineGitCleanBg\": 2,\n    \"statusLineGitCleanFg\": 0,\n    \"statusLineGitDirtyBg\": 3,\n    \"statusLineGitDirtyFg\": 0,\n    \"statusLineGitStagedBg\": 64,\n    \"statusLineGitStagedFg\": 0,\n    \"statusLineGitUntrackedBg\": 39,\n    \"statusLineGitUntrackedFg\": 0,\n    \"statusLineGitConflictBg\": 1,\n    \"statusLineGitConflictFg\": 7,\n    \"statusLinePlanModeBg\": 236,\n    \"statusLinePlanModeFg\": 117,\n    \"statusLineProfileXcshBg\": \"accent\",\n    \"statusLineProfileXcshFg\": 231\n  }\n}\n```\n\n## カスタムテーマのテスト\n\n以下のワークフローを使用してください：\n\n1. インタラクティブモードで起動します（起動時にウォッチャーが有効）。\n2. 設定を開き、テーマの値をプレビューします（ライブ`previewTheme`）。\n3. カスタムテーマファイルの場合、実行中にJSONを編集し、保存時の自動リロードを確認します。\n4. 重要な画面を検証します：\n   - Markdownレンダリング\n   - ツールブロック（保留中/成功/エラー）\n   - diffレンダリング（追加/削除/コンテキスト）\n   - ステータスラインの可読性\n   - 思考レベルのボーダー変更\n   - bash/pythonモードのボーダー色\n5. テーマがグリフの幅/外観に依存する場合は、両方のシンボルプリセットを検証してください。\n\n## 実際の制約と注意事項\n\n- カスタムテーマでは、すべての`colors`トークンが必須です。\n- `export`と`symbols`は任意です。\n- テーマJSON内の`$schema`は情報提供のみです。ランタイム検証はコード内のコンパイル済みTypeBoxスキーマによって実施されます。\n- `setTheme`の失敗は`dark`にフォールバックします。`previewTheme`の失敗は現在のテーマを置換しません。\n- ファイルウォッチャーのリロードエラーが発生した場合、リロードが成功するかフォールバックパスがトリガーされるまで、現在読み込まれているテーマが維持されます。\n",
	"ja/tui/tree.md": "---\ntitle: Tree コマンドリファレンス\ndescription: セッション履歴と会話ブランチを視覚化するための /tree コマンドリファレンス。\nsidebar:\n  order: 4\n  label: /tree コマンド\ni18n:\n  sourceHash: ee0e412fe993\n  translator: machine\n---\n\n# `/tree` コマンドリファレンス\n\n`/tree` はインタラクティブな**セッションツリー**ナビゲーターを開きます。現在のセッションファイル内の任意のエントリにジャンプし、そのポイントから続行できます。\n\nこれはファイル内のリーフ移動であり、新しいセッションのエクスポートではありません。\n\n## `/tree` の動作\n\n- 現在のセッションエントリからツリーを構築します（`SessionManager.getTree()`）\n- キーボードナビゲーション、フィルター、検索機能を備えた `TreeSelectorComponent` を開きます\n- 選択時に `AgentSession.navigateTree(targetId, { summarize, customInstructions })` を呼び出します\n- 新しいリーフパスから表示チャットを再構築します\n- ユーザー/カスタムメッセージを選択した場合、オプションでエディターテキストを事前入力します\n\n主要な実装：\n\n- `src/modes/controllers/input-controller.ts`（`/tree`、キーバインディングの接続、ダブルエスケープの動作）\n- `src/modes/controllers/selector-controller.ts`（ツリー UI の起動 + サマリープロンプトフロー）\n- `src/modes/components/tree-selector.ts`（ナビゲーション、フィルター、検索、ラベル、レンダリング）\n- `src/session/agent-session.ts`（`navigateTree` のリーフ切り替え + オプションのサマリー）\n- `src/session/session-manager.ts`（`getTree`、`branch`、`branchWithSummary`、`resetLeaf`、ラベルの永続化）\n\n## 開き方\n\n以下のいずれかで同じセレクターが開きます：\n\n- `/tree`\n- 設定されたキーバインディングアクション `tree`\n- エディターが空の状態でダブルエスケープ（`doubleEscapeAction = \"tree\"` の場合、デフォルト）\n- `/branch`（`doubleEscapeAction = \"tree\"` の場合、ユーザーのみのブランチピッカーではなくツリーセレクターにルーティングされます）\n\n## ツリー UI モデル\n\nツリーはセッションエントリの親ポインター（`id` / `parentId`）からレンダリングされます。\n\n- 子はタイムスタンプの昇順でソートされます（古いものが先、新しいものが下）\n- アクティブブランチ（ルートから現在のリーフまでのパス）はバレットでマークされます\n- ラベル（存在する場合）はノードテキストの前に `[label]` としてレンダリングされます\n- 複数のルートが存在する場合（孤立した/壊れた親チェーン）、仮想的な分岐ルートの下に表示されます\n\n```text\nExample tree view (active path marked with •):\n\n├─ user: \"Start task\"\n│  └─ assistant: \"Plan\"\n│     ├─ • user: \"Try approach A\"\n│     │  └─ • assistant: \"A result\"\n│     │     └─ • [milestone] user: \"Continue A\"\n│     └─ user: \"Try approach B\"\n│        └─ assistant: \"B result\"\n```\n\nセレクターは現在の選択を中心に再配置し、最大で以下の行数を表示します：\n\n- `max(5, floor(terminalHeight / 2))` 行\n\n## ツリーセレクター内のキーバインディング\n\n- `Up` / `Down`：選択を移動（ラップ）\n- `Left` / `Right`：ページアップ / ページダウン\n- `Enter`：ノードを選択\n- `Esc`：検索がアクティブな場合はクリア、それ以外はセレクターを閉じる\n- `Ctrl+C`：セレクターを閉じる\n- `Type`：検索クエリに追加\n- `Backspace`：検索文字を削除\n- `Shift+L`：選択したエントリのラベルを編集/クリア\n- `Ctrl+O`：フィルターを前方にサイクル\n- `Shift+Ctrl+O`：フィルターを後方にサイクル\n- `Alt+D/T/U/L/A`：特定のフィルターモードに直接ジャンプ\n\n## フィルターと検索のセマンティクス\n\nフィルターモード（`TreeList`）：\n\n1. `default`\n2. `no-tools`\n3. `user-only`\n4. `labeled-only`\n5. `all`\n\n### `default`\n\nほとんどの会話ノードを表示しますが、ブックキーピングエントリタイプは非表示にします：\n\n- `label`\n- `custom`\n- `model_change`\n- `thinking_level_change`\n\n### `no-tools`\n\n`default` と同じですが、さらに `toolResult` メッセージを非表示にします。\n\n### `user-only`\n\nロールが `user` の `message` エントリのみ表示します。\n\n### `labeled-only`\n\n現在ラベルに解決されるエントリのみ表示します。\n\n### `all`\n\nブックキーピング/カスタムエントリを含む、セッションツリー内のすべてを表示します。\n\n### ツールのみのアシスタントノードの動作\n\n**ツール呼び出しのみ**（テキストなし）を含むアシスタントメッセージは、以下の場合を除き、すべてのフィルタービューでデフォルトで非表示になります：\n\n- メッセージがエラー/中断（`stopReason` が `stop`/`toolUse` でない）の場合、または\n- 現在のリーフである場合（常に表示を維持）\n\n### 検索の動作\n\n- クエリはスペースでトークン化されます\n- マッチングは大文字小文字を区別しません\n- すべてのトークンが一致する必要があります（AND セマンティクス）\n- 検索可能なテキストにはラベル、ロール、タイプ固有のコンテンツ（メッセージテキスト、ブランチサマリーテキスト、カスタムタイプ、ツールコマンドスニペットなど）が含まれます\n\n## 選択結果（重要）\n\n`navigateTree` は選択されたエントリタイプから新しいリーフの動作を計算します：\n\n### `user` メッセージの選択\n\n- 新しいリーフは選択されたエントリの `parentId` になります\n- 親が `null`（ルートのユーザーメッセージ）の場合、リーフはルートにリセットされます（`resetLeaf()`）\n- 選択されたメッセージテキストは編集/再送信のためにエディターにコピーされます\n\n### `custom_message` の選択\n\n- ユーザーメッセージと同じリーフルール（`parentId`）\n- テキストコンテンツが抽出されエディターにコピーされます\n\n### ユーザー以外のノードの選択（アシスタント/ツール/サマリー/コンパクション/カスタムブックキーピングなど）\n\n- 新しいリーフは選択されたノードの ID になります\n- エディターは事前入力されません\n\n### 現在のリーフの選択\n\n- 何もしません。セレクターは「Already at this point」と表示して閉じます\n\n```text\nSelection decision (simplified):\n\nselected node\n   │\n   ├─ is current leaf? ── yes ──> close selector (no-op)\n   │\n   ├─ is user/custom_message? ── yes ──> leaf := parentId (or resetLeaf for root)\n   │                                     + prefill editor text\n   │\n   └─ otherwise ──> leaf := selected node id\n                    + no editor prefill\n```\n\n## 切り替え時のサマリーフロー\n\nサマリープロンプトは `branchSummary.enabled`（デフォルト：`false`）で制御されます。\n\n有効にすると、ノードを選択した後に UI が以下を尋ねます：\n\n- `No summary`\n- `Summarize`\n- `Summarize with custom prompt`\n\nフローの詳細：\n\n- サマリープロンプトでエスケープするとツリーセレクターが再度開きます\n- カスタムプロンプトのキャンセルはサマリー選択ループに戻ります\n- サマリー生成中、UI はローダーを表示し `Esc` を `abortBranchSummary()` にバインドします\n- サマリー生成が中断された場合、ツリーセレクターが再度開き、移動は適用されません\n\n`navigateTree` の内部：\n\n- 古いリーフから共通の祖先までの放棄されたブランチエントリを収集します\n- `session_before_tree` を発行します（拡張機能がキャンセルまたはサマリーを注入できます）\n- リクエストされ必要な場合のみデフォルトのサマライザーを使用します\n- 以下で移動を適用します：\n  - サマリーが存在する場合は `branchWithSummary(...)`\n  - サマリーなしの非ルート移動の場合は `branch(newLeafId)`\n  - サマリーなしのルート移動の場合は `resetLeaf()`\n- エージェントの会話を再構築されたセッションコンテキストに置き換えます\n- `session_tree` を発行します\n\n注意：ユーザーがサマリーをリクエストしても、サマリーする内容がない場合、サマリーエントリを作成せずにナビゲーションが進行します。\n\n## ラベル\n\nツリー UI でのラベル編集は `appendLabelChange(targetId, label)` を呼び出します。\n\n- 空でないラベルは解決済みラベルを設定/更新します\n- 空のラベルはクリアします\n- ラベルは追記のみの `label` エントリとして保存されます\n- ツリーノードは生のラベルエントリ履歴ではなく、解決済みのラベル状態を表示します\n\n## `/tree` と関連操作の比較\n\n| 操作 | スコープ | 結果 |\n|---|---|---|\n| `/tree` | 現在のセッションファイル | 選択したポイントにリーフを移動（同一ファイル内） |\n| `/branch` | 通常は現在のセッションファイル -> 新しいセッションファイル | デフォルトでは選択した**ユーザー**メッセージから新しいセッションファイルにブランチします。`doubleEscapeAction = \"tree\"` の場合、`/branch` は代わりにツリーナビゲーション UI を開きます |\n| `/fork` | 現在のセッション全体 | セッションを新しい永続化されたセッションファイルに複製します |\n| `/resume` | セッションリスト | 別のセッションファイルに切り替えます |\n\n重要な違い：`/tree` は1つのセッションファイル内のナビゲーション/再配置ツールです。`/branch`、`/fork`、`/resume` はすべてセッションファイルのコンテキストを変更します。\n\n## オペレーターのワークフロー\n\n### 現在のブランチを失わずに以前のユーザープロンプトから再実行する\n\n1. `/tree`\n2. 以前のユーザーメッセージを検索/選択\n3. `No summary` を選択（必要に応じてサマリーを作成）\n4. エディター内の事前入力されたテキストを編集\n5. 送信\n\n効果：同じセッションファイル内の選択したポイントから新しいブランチが成長します。\n\n### コンテキストのパンくずを残して現在のブランチを離れる\n\n1. `branchSummary.enabled` を有効にする\n2. `/tree` でターゲットノードを選択\n3. `Summarize`（またはカスタムプロンプト）を選択\n\n効果：続行する前にターゲット位置に `branch_summary` エントリが追加されます。\n\n### 非表示のブックキーピングエントリを調査する\n\n1. `/tree`\n2. `Alt+A`（all）を押す\n3. `model`、`thinking`、`custom`、またはラベルを検索\n\n効果：会話ノードだけでなく、完全な内部タイムラインを検査できます。\n\n### 後でジャンプするためにピボットポイントをブックマークする\n\n1. `/tree`\n2. エントリに移動\n3. `Shift+L` でラベルを設定\n4. 後で `Alt+L`（`labeled-only`）を使用して素早くジャンプ\n\n効果：永続的なブランチランドマーク間の高速ナビゲーション。\n",
	"ja/tui/tui-runtime-internals.md": "---\ntitle: TUI ランタイムの内部構造\ndescription: レンダリングパイプライン、入力処理、状態管理を含むターミナル UI ランタイムの内部構造。\nsidebar:\n  order: 2\n  label: ランタイムの内部構造\ni18n:\n  sourceHash: cc8f7dcce46a\n  translator: machine\n---\n\n# TUI ランタイムの内部構造\n\nこのドキュメントでは、インタラクティブモードにおけるターミナル入力からレンダリング出力までの、テーマ以外のランタイムパスをマッピングします。`packages/tui` での動作と、`packages/coding-agent` コントローラーからの統合に焦点を当てています。\n\n## ランタイムレイヤーと所有権\n\n- **`packages/tui` エンジン**: ターミナルライフサイクル、stdin の正規化、フォーカスルーティング、レンダースケジューリング、差分描画、オーバーレイ合成、ハードウェアカーソル配置。\n- **`packages/coding-agent` インタラクティブモード**: コンポーネントツリーの構築、エディタコールバックとキーマップのバインド、エージェント/セッションイベントへの反応、ドメイン状態（ストリーミング、ツール実行、リトライ、プランモード）の UI コンポーネントへの変換。\n\n境界ルール: TUI エンジンはメッセージに依存しません。`Component.render(width)`、`handleInput(data)`、フォーカス、オーバーレイのみを認識します。エージェントのセマンティクスはインタラクティブコントローラーに留まります。\n\n## 実装ファイル\n\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/components/custom-editor.ts`](../../packages/coding-agent/src/modes/components/custom-editor.ts)\n- [`../../tui/src/tui.ts`](../../packages/tui/src/tui.ts)\n- [`../../tui/src/terminal.ts`](../../packages/tui/src/terminal.ts)\n- [`../../tui/src/editor-component.ts`](../../packages/tui/src/editor-component.ts)\n- [`../../tui/src/stdin-buffer.ts`](../../packages/tui/src/stdin-buffer.ts)\n- [`../../tui/src/components/loader.ts`](../../packages/tui/src/components/loader.ts)\n\n## ブートとコンポーネントツリーの組み立て\n\n`InteractiveMode` は `TUI(new ProcessTerminal(), showHardwareCursor)` を構築し、永続的なコンテナを作成します：\n\n- `chatContainer`\n- `pendingMessagesContainer`\n- `statusContainer`\n- `todoContainer`\n- `statusLine`\n- `editorContainer`（`CustomEditor` を保持）\n\n`init()` はこの順序でツリーを配線し、エディタにフォーカスし、`InputController` を介して入力ハンドラを登録し、TUI を開始して、強制レンダリングを要求します。\n\n強制レンダリング（`requestRender(true)`）は、再描画前に前回の行キャッシュとカーソルのブックキーピングをリセットします。\n\n## ターミナルライフサイクルと stdin の正規化\n\n`ProcessTerminal.start()`:\n\n1. raw モードとブラケットペーストを有効化。\n2. リサイズハンドラをアタッチ。\n3. 部分的なエスケープチャンクを完全なシーケンスに分割する `StdinBuffer` を作成。\n4. Kitty キーボードプロトコルのサポートを照会（`CSI ? u`）し、サポートされている場合はプロトコルフラグを有効化。\n5. Windows では、`kernel32` モードフラグによる VT 入力の有効化を試行。\n\n`StdinBuffer` の動作：\n\n- 断片化されたエスケープシーケンス（CSI/OSC/DCS/APC/SS3）をバッファリング。\n- シーケンスが完了するかタイムアウトでフラッシュされた場合にのみ `data` を発行。\n- ブラケットペーストを検出し、生のペーストテキストを含む `paste` イベントを発行。\n\nこれにより、部分的なエスケープチャンクが通常のキー入力として誤解釈されることを防ぎます。\n\n## 入力ルーティングとフォーカスモデル\n\n入力パス：\n\n`stdin -> ProcessTerminal -> StdinBuffer -> TUI.#handleInput -> focusedComponent.handleInput`\n\nルーティングの詳細：\n\n1. TUI は最初に登録済みの入力リスナー（`addInputListener`）を実行し、消費/変換動作を可能にします。\n2. TUI はコンポーネントへのディスパッチ前にグローバルデバッグショートカット（`shift+ctrl+d`）を処理します。\n3. フォーカスされたコンポーネントが非表示/不可視になったオーバーレイに属している場合、TUI は次の可視オーバーレイまたは保存されたオーバーレイ前のフォーカスにフォーカスを再割り当てします。\n4. フォーカスされたコンポーネントが `wantsKeyRelease = true` を設定していない限り、キーリリースイベントはフィルタリングされます。\n5. ディスパッチ後、TUI はレンダリングをスケジュールします。\n\n`setFocus()` は `Focusable.focused` も切り替え、これによりコンポーネントがハードウェアカーソル配置のために `CURSOR_MARKER` を発行するかどうかが制御されます。\n\n## キー処理の分離：エディタ vs コントローラー\n\n`CustomEditor` は最初に高優先度のコンボ（escape、ctrl-c/d/z、ctrl-v、ctrl-p のバリアント、ctrl-t、alt-up、拡張カスタムキー）をインターセプトし、残りをベースの `Editor` 動作（テキスト編集、履歴、オートコンプリート、カーソル移動）に委譲します。\n\n`InputController.setupKeyHandlers()` はその後、エディタコールバックをモードアクションにバインドします：\n\n- `Escape` でのキャンセル / モード終了\n- ダブル `Ctrl+C` またはエディタ空状態での `Ctrl+D` によるシャットダウン\n- `Ctrl+Z` でのサスペンド/レジューム\n- スラッシュコマンドとセレクターのホットキー\n- フォローアップ/デキューの切り替えと展開の切り替え\n\nこれにより、キー解析/エディタメカニクスは `packages/tui` に、モードセマンティクスは coding-agent コントローラーに保持されます。\n\n## レンダーループと差分戦略\n\n`TUI.requestRender()` は `process.nextTick` を使用してティックごとに1回のレンダリングにデバウンスされます。同一ターン内の複数の状態変更は統合されます。\n\n`#doRender()` パイプライン：\n\n1. ルートコンポーネントツリーを `newLines` にレンダリング。\n2. 可視オーバーレイ（存在する場合）を合成。\n3. 可視ビューポート行から `CURSOR_MARKER` を抽出して除去。\n4. 非画像行にセグメントリセットサフィックスを追加。\n5. 完全再描画か差分パッチかを選択：\n   - 最初のフレーム\n   - 幅の変更\n   - `clearOnShrink` が有効でオーバーレイがない場合の縮小\n   - 前回のビューポート上方での編集\n6. 差分更新の場合、変更された行範囲のみをパッチし、必要に応じて古い末尾行をクリア。\n7. IME サポートのためにハードウェアカーソルを再配置。\n\nレンダリング書き込みは同期出力モード（`CSI ? 2026 h/l`）を使用してフリッカー/ティアリングを低減します。\n\n## レンダリングの安全性制約\n\n`TUI` における重要な安全性チェック：\n\n- 非画像のレンダリング行はターミナル幅を超えてはなりません。オーバーフローが発生すると例外がスローされ、クラッシュ診断が書き込まれます。\n- オーバーレイ合成には防御的な切り詰めと合成後の幅検証が含まれます。\n- 幅の変更は完全再描画を強制します。折り返しセマンティクスが変わるためです。\n- カーソル位置は移動前にクランプされます。\n\nこれらの制約はランタイムの強制であり、単なる慣例ではありません。\n\n## リサイズ処理\n\nリサイズイベントは `ProcessTerminal` から `TUI.requestRender()` へのイベント駆動です。\n\n効果：\n\n- 幅の変更は完全再描画をトリガーします。\n- ビューポート/トップトラッキング（`#previousViewportTop`、`#maxLinesRendered`）は、コンテンツやターミナルサイズが変更された際の無効な相対カーソル計算を回避します。\n- オーバーレイの可視性はターミナルの寸法に依存する場合があります（`OverlayOptions.visible`）。リサイズ後にオーバーレイが非可視になった場合、フォーカスは修正されます。\n\n## ストリーミングとインクリメンタル UI 更新\n\n`EventController` は `AgentSessionEvent` をサブスクライブし、UI をインクリメンタルに更新します：\n\n- `agent_start`: `statusContainer` でローダーを開始。\n- `message_start` assistant: `streamingComponent` を作成してマウント。\n- `message_update`: ストリーミング中のアシスタントコンテンツを更新。ツール呼び出しが現れるとツール実行コンポーネントを作成/更新。\n- `tool_execution_update/end`: ツール結果コンポーネントと完了状態を更新。\n- `message_end`: アシスタントストリームをファイナライズし、中断/エラーアノテーションを処理し、通常停止時に保留中のツール引数を完了としてマーク。\n- `agent_end`: ローダーを停止し、一時的なストリーム状態をクリアし、遅延モデル切り替えをフラッシュし、バックグラウンド化されている場合は完了通知を発行。\n\n読み取りツールのグループ化は意図的にステートフルです（`#lastReadGroup`）。連続する読み取りツール呼び出しは、非読み取りのブレークが発生するまで1つのビジュアルブロックに統合されます。\n\n## ステータスとローダーのオーケストレーション\n\nステータスレーンの所有権：\n\n- `statusContainer` は一時的なローダー（`loadingAnimation`、`autoCompactionLoader`、`retryLoader`）を保持。\n- `statusLine` は永続的なステータス/フック/プランインジケーターをレンダリングし、エディタの上部ボーダー更新を駆動。\n\nローダーの動作：\n\n- `Loader` はインターバルで 80ms ごとに更新し、各フレームでレンダリングを要求。\n- 自動コンパクションと自動リトライ中は、エスケープハンドラが一時的にオーバーライドされ、それらの操作をキャンセルできるようになります。\n- 終了/キャンセルパスでは、コントローラーは以前のエスケープハンドラを復元し、ローダーコンポーネントを停止/クリアします。\n\n## モード遷移とバックグラウンド化\n\n### Bash/Python 入力モード\n\n入力テキストのプレフィックスによりエディタボーダーのモードフラグが切り替わります：\n\n- `!` -> bash モード\n- `$`（テンプレートリテラルプレフィックス以外）-> python モード\n\nEscape は非アクティブモードを終了し、エディタテキストをクリアしてボーダーカラーを復元します。実行がアクティブな場合、escape は代わりに実行中のタスクを中断します。\n\n### プランモード\n\n`InteractiveMode` はプランモードフラグ、ステータスライン状態、アクティブなツール、モデル切り替えを追跡します。開始/終了はセッションモードエントリとステータス/UI 状態を更新し、ストリーミングがアクティブな場合は遅延モデル切り替えを含みます。\n\n### サスペンド/レジューム（`Ctrl+Z`）\n\n`InputController.handleCtrlZ()`:\n\n1. TUI を再起動して強制レンダリングするワンショット `SIGCONT` ハンドラを登録。\n2. サスペンド前に TUI を停止。\n3. プロセスグループに `SIGTSTP` を送信。\n\n### バックグラウンドモード（`/background` または `/bg`）\n\n`handleBackgroundCommand()`:\n\n- アイドル時は拒否。\n- ツール UI コンテキストを非インタラクティブ（`hasUI=false`）に切り替え、インタラクティブ UI ツールがフェイルファストするようにします。\n- ローダー/ステータスラインを停止し、フォアグラウンドイベントハンドラをアンサブスクライブ。\n- バックグラウンドイベントハンドラをサブスクライブ（主に `agent_end` を待機）。\n- TUI を停止し、`SIGTSTP` を送信（POSIX ジョブ制御パス）。\n\nキュー待ちの作業がない状態でバックグラウンドで `agent_end` が発生すると、コントローラーは完了通知を送信してシャットダウンします。\n\n## キャンセルパス\n\n主要なキャンセル入力：\n\n- アクティブなストリームローダー中の `Escape`: キュー待ちメッセージをエディタに復元し、エージェントを中断。\n- bash/python 実行中の `Escape`: 実行中のコマンドを中断。\n- 自動コンパクション/リトライ中の `Escape`: 一時的なエスケープハンドラを通じて専用の中断メソッドを呼び出し。\n- `Ctrl+C` 単押し: エディタをクリア。500ms 以内のダブル押し: シャットダウン。\n\nキャンセルは状態条件付きです。同じキーが、ランタイムの状態に応じて中断、モード終了、セレクタートリガー、または何もしないことを意味する場合があります。\n\n## イベント駆動 vs スロットル動作\n\nイベント駆動の更新：\n\n- エージェントセッションイベント（`EventController`）\n- キー入力コールバック（`InputController`）\n- ターミナルリサイズコールバック\n- `InteractiveMode` でのテーマ/ブランチウォッチャー\n\nスロットル/デバウンスパス：\n\n- TUI レンダリングはティックデバウンス（`requestRender` 統合）。\n- ローダーアニメーションは固定インターバル（80ms）で、各フレームがレンダリングを要求。\n- エディタのオートコンプリート更新（`Editor` 内部）はデバウンスタイマーを使用し、タイピング中の再計算チャーンを低減。\n\nしたがって、ランタイムはイベント駆動の状態遷移と制限付きレンダーケイデンスを混合し、再描画ストームなしにインタラクティビティの応答性を維持します。\n",
	"ja/tui/tui.md": "---\ntitle: 拡張機能とカスタムツールのTUI統合\ndescription: 拡張機能、カスタムツール、カスタムレンダラーのためのTUI統合コントラクト。\nsidebar:\n  order: 1\n  label: 拡張機能統合\ni18n:\n  sourceHash: 47f8f2b2045e\n  translator: machine\n---\n\n# 拡張機能とカスタムツールのTUI統合\n\nこのドキュメントでは、`packages/coding-agent` と `packages/tui` が拡張機能UI、カスタムツールUI、およびカスタムレンダラーに使用する**現在の** TUIコントラクトについて説明します。\n\n## このサブシステムとは\n\nランタイムには2つのレイヤーがあります：\n\n- **レンダリングエンジン (`packages/tui`)**: 差分ターミナルレンダラー、入力ディスパッチ、フォーカス、オーバーレイ、カーソル配置。\n- **統合レイヤー (`packages/coding-agent`)**: 拡張機能/カスタムツールコンポーネントのマウント、キーバインディング/テーマの接続、エディタ状態の復元。\n\n## モード別のランタイム動作\n\n| モード | `ctx.ui.custom(...)` の利用可否 | 備考 |\n| --- | --- | --- |\n| インタラクティブTUI | サポート | コンポーネントはエディタ領域にマウントされ、フォーカスされ、解決するために `done(result)` を呼び出す必要があります。 |\n| バックグラウンド/ヘッドレス | 非インタラクティブ | UIコンテキストはno-op（`hasUI === false`）。 |\n| RPCモード | 非サポート | `custom()` は `Promise<never>` を返し、TUIコンポーネントをマウントしません。 |\n\n拡張機能/ツールが非インタラクティブモードで動作可能な場合は、`ctx.hasUI` / `pi.hasUI` でガードしてください。\n\n## コアコンポーネントコントラクト (`@f5-sales-demo/pi-tui`)\n\n`packages/tui/src/tui.ts` で定義されています：\n\n```ts\nexport interface Component {\n  render(width: number): string[];\n  handleInput?(data: string): void;\n  wantsKeyRelease?: boolean;\n  invalidate(): void;\n}\n```\n\n`Focusable` は別インターフェースです：\n\n```ts\nexport interface Focusable {\n  focused: boolean;\n}\n```\n\nカーソルの動作は `CURSOR_MARKER`（`getCursorPosition` ではなく）を使用します。フォーカスされたコンポーネントはレンダリングされたテキストにマーカーを出力し、`TUI` がそれを抽出してハードウェアカーソルを配置します。\n\n## レンダリング制約（ターミナルの安全性）\n\n`render(width)` の出力はターミナルセーフでなければなりません：\n\n1. **どの行でも `width` を超えないこと**。レンダラーは画像以外の行がオーバーフローした場合にスローします。\n2. **文字列長ではなく、視覚的な幅を測定すること**: `visibleWidth()` を使用してください。\n3. **ANSIを考慮したテキストの切り詰め/折り返し**には `truncateToWidth()` / `wrapTextWithAnsi()` を使用してください。\n4. **外部ソースからのタブ/コンテンツのサニタイズ**には `replaceTabs()`（およびcoding-agentレンダーパスの上位レベルのサニタイザー）を使用してください。\n\n最小パターン：\n\n```ts\nimport { replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\n\nrender(width: number): string[] {\n  return this.lines.map(line => truncateToWidth(replaceTabs(line), width));\n}\n```\n\n## 入力処理とキーバインディング\n\n### 生のキーマッチング\n\nナビゲーションキーとコンビネーションには `matchesKey(data, \"...\")` を使用してください。\n\n### ユーザー設定のアプリキーバインディングの尊重\n\n拡張機能UIファクトリーは `KeybindingsManager`（インタラクティブモード）を受け取るため、キーをハードコーディングする代わりにマップされたアクションを尊重できます：\n\n```ts\nif (keybindings.matches(data, \"interrupt\")) {\n  done(undefined);\n  return;\n}\n```\n\n### キーリリース/リピートイベント\n\nキーリリースイベントは、コンポーネントが以下を設定しない限りフィルタリングされます：\n\n```ts\nwantsKeyRelease = true;\n```\n\n必要に応じて `isKeyRelease()` / `isKeyRepeat()` を使用してください。\n\n## フォーカス、オーバーレイ、カーソル\n\n- `TUI.setFocus(component)` は入力をそのコンポーネントにルーティングします。\n- オーバーレイAPIは `TUI`（`showOverlay`、`OverlayHandle`）に存在しますが、インタラクティブモードでの拡張機能 `ctx.ui.custom` マウントは現在、エディタコンポーネント領域を直接置き換えます。\n- `custom(..., options?: { overlay?: boolean })` オプションは拡張機能の型に存在しますが、インタラクティブな拡張機能マウントは現在このオプションを無視します。\n\n## マウントポイントと戻り値のコントラクト\n\n## 1) 拡張機能UI (`ExtensionUIContext`)\n\n現在のシグネチャ（`extensibility/extensions/types.ts`）：\n\n```ts\ncustom<T>(\n  factory: (\n    tui: TUI,\n    theme: Theme,\n    keybindings: KeybindingsManager,\n    done: (result: T) => void,\n  ) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n  options?: { overlay?: boolean },\n): Promise<T>\n```\n\nインタラクティブモードでの動作（`extension-ui-controller.ts`）：\n\n- エディタテキストを保存します。\n- エディタコンポーネントをあなたのコンポーネントに置き換えます。\n- あなたのコンポーネントにフォーカスします。\n- `done(result)` 時：`component.dispose?.()` を呼び出し、エディタ＋テキストを復元し、エディタにフォーカスし、Promiseを解決します。\n\nしたがって、`done(...)` は完了のために必須です。\n\n## 2) フック/カスタムツールUIコンテキスト（レガシー型定義）\n\n`HookUIContext.custom` はフック/カスタムツールの型で `(tui, theme, done)` として型定義されています。\n基盤となるインタラクティブ実装はファクトリーを `(tui, theme, keybindings, done)` で呼び出します。JSコンシューマーは追加の引数を使用できますが、型レベルの互換性は3引数のレガシーシグネチャを反映しています。\n\nカスタムツールは通常、ファクトリースコープの `pi.ui` オブジェクトを介して同じUIエントリーポイントを使用し、選択された値を通常のツールコンテンツで返します：\n\n```ts\nasync execute(toolCallId, params, onUpdate, ctx, signal) {\n  if (!pi.hasUI) {\n    return { content: [{ type: \"text\", text: \"UI unavailable\" }] };\n  }\n\n  const picked = await pi.ui.custom<string | undefined>((tui, theme, done) => {\n    const component = new MyPickerComponent(done, signal);\n    return component;\n  });\n\n  return { content: [{ type: \"text\", text: picked ? `Picked: ${picked}` : \"Cancelled\" }] };\n}\n```\n\n## 3) カスタムツールコール/結果レンダラー\n\nカスタムツールと拡張機能ツールは以下からコンポーネントを返すことができます：\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\n`options` には現在以下が含まれます：\n\n- `expanded: boolean`\n- `isPartial: boolean`\n- `spinnerFrame?: number`\n\nこれらのレンダラーは `ToolExecutionComponent` によってマウントされます。\n\n## ライフサイクルとキャンセル\n\n- `dispose()` は型レベルではオプションですが、タイマー、サブプロセス、ウォッチャー、ソケット、またはオーバーレイを所有している場合は実装すべきです。\n- `done(...)` はコンポーネントのフローから正確に1回呼び出されるべきです。\n- キャンセル可能な長時間実行UIの場合、`CancellableLoader` を `AbortSignal` とペアにし、`onAbort` から `done(...)` を呼び出してください。\n\nキャンセルパターンの例：\n\n```ts\nconst loader = new CancellableLoader(tui, theme.fg(\"accent\"), theme.fg(\"muted\"), \"Working...\");\nloader.onAbort = () => done(undefined);\nvoid doWork(loader.signal).then(result => done(result));\nreturn loader;\n```\n\n## 実用的なカスタムコンポーネントの例（拡張機能コマンド）\n\n```ts\nimport type { Component } from \"@f5-sales-demo/pi-tui\";\nimport { SelectList, matchesKey, replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\nimport { getSelectListTheme, type ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nclass Picker implements Component {\n  list: SelectList;\n  keybindings: any;\n  done: (value: string | undefined) => void;\n\n  constructor(\n    items: Array<{ value: string; label: string }>,\n    keybindings: any,\n    done: (value: string | undefined) => void,\n  ) {\n    this.list = new SelectList(items, 8, getSelectListTheme());\n    this.keybindings = keybindings;\n    this.done = done;\n    this.list.onSelect = item => this.done(item.value);\n    this.list.onCancel = () => this.done(undefined);\n  }\n\n  handleInput(data: string): void {\n    if (this.keybindings.matches(data, \"interrupt\")) {\n      this.done(undefined);\n      return;\n    }\n    this.list.handleInput(data);\n  }\n\n  render(width: number): string[] {\n    return this.list.render(width).map(line => truncateToWidth(replaceTabs(line), width));\n  }\n\n  invalidate(): void {\n    this.list.invalidate();\n  }\n}\n\nexport default function extension(pi: ExtensionAPI): void {\n  pi.registerCommand(\"pick-model\", {\n    description: \"Pick a model profile\",\n    handler: async (_args, ctx) => {\n      if (!ctx.hasUI) return;\n\n      const selected = await ctx.ui.custom<string | undefined>((tui, theme, keybindings, done) => {\n        const items = [\n          { value: \"fast\", label: theme.fg(\"accent\", \"Fast\") },\n          { value: \"balanced\", label: \"Balanced\" },\n          { value: \"quality\", label: \"Quality\" },\n        ];\n        return new Picker(items, keybindings, done);\n      });\n\n      if (selected) ctx.ui.notify(`Selected profile: ${selected}`, \"info\");\n    },\n  });\n}\n```\n\n## 主要な実装ファイル\n\n- `packages/tui/src/tui.ts` — `Component`、`Focusable`、カーソルマーカー、フォーカス、オーバーレイ、入力ディスパッチ。\n- `packages/tui/src/utils.ts` — 幅/切り詰め/サニタイズのプリミティブ。\n- `packages/tui/src/keys.ts` / `keybindings.ts` — キーパースおよび設定可能なアクションマッピング。\n- `packages/coding-agent/src/modes/controllers/extension-ui-controller.ts` — 拡張機能/フック/カスタムツールUIのインタラクティブなマウント/アンマウント。\n- `packages/coding-agent/src/extensibility/extensions/types.ts` — 拡張機能UIおよびレンダラーのコントラクト。\n- `packages/coding-agent/src/extensibility/hooks/types.ts` — フックUIコントラクト（レガシーカスタムシグネチャ）。\n- `packages/coding-agent/src/extensibility/custom-tools/types.ts` — カスタムツールのexecute/renderコントラクト。\n- `packages/coding-agent/src/modes/components/tool-execution.ts` — `renderCall`/`renderResult` コンポーネントのマウントおよび部分状態オプション。\n- `packages/coding-agent/src/tools/context.ts` — ツールUIコンテキストの伝播（`hasUI`、`ui`）。\n",
	"ko/configuration/blob-artifact-architecture.md": "---\ntitle: Blob 및 아티팩트 저장소 아키텍처\ndescription: '세션 미디어, 스크린샷 및 도구 출력을 위한 콘텐츠 주소 기반 blob 저장소와 아티팩트 레지스트리.'\nsidebar:\n  order: 7\n  label: Blob 및 아티팩트 저장소\ni18n:\n  sourceHash: 70d255f48d5b\n  translator: machine\n---\n\n# Blob 및 아티팩트 저장소 아키텍처\n\n이 문서는 coding-agent가 세션 JSONL 외부에 대용량/바이너리 페이로드를 저장하는 방법, 잘린 도구 출력이 어떻게 지속되는지, 그리고 내부 URL(`artifact://`, `agent://`)이 저장된 데이터로 어떻게 해석되는지를 설명합니다.\n\n## 두 가지 저장 시스템이 존재하는 이유\n\n런타임은 서로 다른 데이터 형태에 대해 두 가지 다른 영속성 메커니즘을 사용합니다:\n\n- **콘텐츠 주소 기반 blob** (`blob:sha256:<hash>`): 지속된 세션 항목에서 대용량 이미지 base64 페이로드를 외부화하는 데 사용되는 전역 바이너리 지향 저장소.\n- **세션 범위 아티팩트** (`<sessionFile-without-.jsonl>/` 하위 파일들): 전체 도구 출력 및 하위 에이전트 출력에 사용되는 세션별 텍스트 파일.\n\n이들은 의도적으로 분리되어 있습니다:\n\n- blob 저장소는 콘텐츠 해시를 통한 중복 제거와 안정적인 참조를 최적화하고,\n- 아티팩트 저장소는 추가 전용 세션 도구 사용과 로컬 ID를 통한 사람/도구의 검색을 최적화합니다.\n\n## 저장소 경계 및 디스크 레이아웃\n\n## Blob 저장소 경계 (전역)\n\n`SessionManager`는 `BlobStore(getBlobsDir())`를 생성하므로, blob 파일은 공유 전역 blob 디렉토리에 저장됩니다(세션 폴더가 아님).\n\nBlob 파일 명명 규칙:\n\n- 파일 경로: `<blobsDir>/<sha256-hex>`\n- 확장자 없음\n- 항목에 저장되는 참조 문자열: `blob:sha256:<sha256-hex>`\n\n시사점:\n\n- 세션 간 동일한 바이너리 콘텐츠는 동일한 해시/경로로 해석됩니다,\n- 쓰기는 콘텐츠 수준에서 멱등적입니다,\n- blob은 개별 세션 파일보다 오래 존속할 수 있습니다.\n\n## 아티팩트 경계 (세션 로컬)\n\n`ArtifactManager`는 세션 파일 경로에서 아티팩트 디렉토리를 유도합니다:\n\n- 세션 파일: `.../<timestamp>_<sessionId>.jsonl`\n- 아티팩트 디렉토리: `.../<timestamp>_<sessionId>/` (`.jsonl` 제거)\n\n아티팩트 유형은 이 디렉토리를 공유합니다:\n\n- 잘린 도구 출력 파일: `<numericId>.<toolType>.log` (`artifact://`용)\n- 하위 에이전트 출력 파일: `<outputId>.md` (`agent://`용)\n\n## ID 및 이름 할당 체계\n\n## Blob ID: 콘텐츠 해시\n\n`BlobStore.put()`은 원시 바이너리 바이트에 대해 SHA-256을 계산하고 다음을 반환합니다:\n\n- `hash`: 16진수 다이제스트,\n- `path`: `<blobsDir>/<hash>`,\n- `ref`: `blob:sha256:<hash>`.\n\n세션 로컬 카운터는 사용되지 않습니다.\n\n## 아티팩트 ID: 세션 로컬 단조 증가 정수\n\n`ArtifactManager`는 최초 사용 시 기존 `*.log` 아티팩트 파일을 스캔하여 최대 기존 숫자 ID를 찾고 `nextId = max + 1`을 설정합니다.\n\n할당 동작:\n\n- 파일 형식: `{id}.{toolType}.log`\n- ID는 순차적 문자열입니다 (`\"0\"`, `\"1\"`, ...)\n- 재개 시 스캔이 할당 전에 수행되므로 기존 아티팩트를 덮어쓰지 않습니다.\n\n아티팩트 디렉토리가 없는 경우, 스캔은 빈 목록을 반환하고 할당은 `0`부터 시작합니다.\n\n## 에이전트 출력 ID (`agent://`)\n\n`AgentOutputManager`는 하위 에이전트 출력의 ID를 `<index>-<requestedId>`로 할당합니다(선택적으로 상위 접두사 아래에 중첩, 예: `0-Parent.1-Child`). 초기화 시 기존 `.md` 파일을 스캔하여 재개 시 다음 인덱스부터 계속합니다.\n\n## 영속성 데이터 흐름\n\n## 1) 세션 항목 영속성 재작성 경로\n\n세션 항목이 작성되기 전(`#rewriteFile` / 증분 영속화), `SessionManager`는 (`truncateForPersistence`를 통해) `prepareEntryForPersistence()`를 호출합니다.\n\n주요 동작:\n\n1. **대용량 문자열 잘림**: 초과 크기 문자열이 잘리고 `\"[Session persistence truncated large content]\"` 접미사가 붙습니다.\n2. **임시 필드 제거**: `partialJson`과 `jsonlEvents`가 영속화된 항목에서 제거됩니다.\n3. **이미지의 blob 외부화**:\n   - `content` 배열의 이미지 블록에만 적용됩니다,\n   - `data`가 이미 blob 참조가 아닌 경우에만 적용됩니다,\n   - base64 길이가 최소 임계값 이상인 경우에만 적용됩니다 (`BLOB_EXTERNALIZE_THRESHOLD = 1024`),\n   - 인라인 base64를 `blob:sha256:<hash>`로 대체합니다.\n\n이를 통해 세션 JSONL을 컴팩트하게 유지하면서 복구 가능성을 보존합니다.\n\n## 2) 세션 로드 재수화 경로\n\n세션을 열 때(`setSessionFile`), 마이그레이션 후 `SessionManager`는 `resolveBlobRefsInEntries()`를 실행합니다.\n\n`blob:sha256:<hash>`를 가진 각 메시지/커스텀 메시지 이미지 블록에 대해:\n\n- blob 저장소에서 blob 바이트를 읽고,\n- 바이트를 다시 base64로 변환하고,\n- 런타임 소비자를 위해 인메모리 항목을 인라인 base64로 변경합니다.\n\nblob이 없는 경우:\n\n- `resolveImageData()`가 경고를 기록하고,\n- 원본 참조 문자열을 변경 없이 반환하고,\n- 로드가 계속됩니다(하드 크래시 없음).\n\n## 3) 도구 출력 스필/잘림 경로\n\n`OutputSink`는 bash/python/ssh 및 관련 실행기에서 스트리밍 출력을 구동합니다.\n\n동작:\n\n1. 모든 청크는 정제되어 인메모리 테일 버퍼에 추가됩니다.\n2. 인메모리 바이트가 스필 임계값(`DEFAULT_MAX_BYTES`, 50KB)을 초과하면, sink는 출력을 잘림으로 표시합니다.\n3. 아티팩트 경로가 사용 가능한 경우, sink는 파일 라이터를 열고 다음을 기록합니다:\n   - 기존 버퍼된 콘텐츠를 한 번,\n   - 이후 모든 청크.\n4. 인메모리 버퍼는 표시를 위해 항상 테일 윈도우로 잘립니다.\n5. `dump()`는 파일 sink가 성공적으로 생성된 경우에만 `artifactId`를 포함한 요약을 반환합니다.\n\n실질적 효과:\n\n- UI/도구 반환은 잘린 테일을 표시하고,\n- 전체 출력은 아티팩트 파일에 보존되며 `artifact://<id>`로 참조됩니다.\n\n파일 sink 생성이 실패하면(I/O 오류, 경로 누락 등), sink는 인메모리 잘림만으로 조용히 폴백합니다; 전체 출력은 영속화되지 않습니다.\n\n## URL 접근 모델\n\n## `blob:` 참조\n\n`blob:sha256:<hash>`는 세션 항목 페이로드 내부의 영속성 참조이며, 라우터가 처리하는 내부 URL 스킴이 아닙니다. 해석은 세션 로드 중 `SessionManager`에 의해 수행됩니다.\n\n## `artifact://<id>`\n\n`ArtifactProtocolHandler`에 의해 처리됩니다:\n\n- 활성 세션 아티팩트 디렉토리가 필요합니다,\n- ID는 숫자여야 합니다,\n- 파일명 접두사 `<id>.`를 매칭하여 해석합니다,\n- 매칭된 `.log` 파일에서 원시 텍스트(`text/plain`)를 반환합니다,\n- 없는 경우, 오류에 사용 가능한 아티팩트 ID 목록이 포함됩니다.\n\n디렉토리 누락 동작:\n\n- 아티팩트 디렉토리가 존재하지 않으면, `No artifacts directory found`를 발생시킵니다.\n\n## `agent://<id>`\n\n`AgentProtocolHandler`에 의해 `<artifactsDir>/<id>.md`를 통해 처리됩니다:\n\n- 기본 형태는 마크다운 텍스트를 반환합니다,\n- `/path` 또는 `?q=` 형태는 JSON 추출을 수행합니다,\n- 경로와 쿼리 추출은 결합할 수 없습니다,\n- 추출이 요청된 경우, 파일 콘텐츠는 JSON으로 파싱되어야 합니다.\n\n디렉토리 누락 동작:\n\n- `No artifacts directory found`를 발생시킵니다.\n\n출력 누락 동작:\n\n- 기존 `.md` 파일에서 사용 가능한 ID와 함께 `Not found: <id>`를 발생시킵니다.\n\n읽기 도구 통합:\n\n- `read`는 비추출 내부 URL 읽기에 대해 offset/limit 페이지네이션을 지원합니다,\n- `agent://` 추출이 사용될 때 `offset/limit`을 거부합니다.\n\n## 재개, 포크 및 이동 의미론\n\n## 재개\n\n- `ArtifactManager`는 최초 할당 시 기존 `{id}.*.log` 파일을 스캔하고 번호 매기기를 계속합니다.\n- `AgentOutputManager`는 기존 `.md` 출력 ID를 스캔하고 번호 매기기를 계속합니다.\n- `SessionManager`는 로드 시 blob 참조를 base64로 재수화합니다.\n\n## 포크\n\n`SessionManager.fork()`는 새 세션 ID와 `parentSession` 링크를 가진 새 세션 파일을 생성한 후, 이전/새 파일 경로를 반환합니다. 아티팩트 복사는 `AgentSession.fork()`에 의해 처리됩니다:\n\n- 이전 아티팩트 디렉토리를 새 아티팩트 디렉토리로 재귀 복사를 시도합니다,\n- 이전 디렉토리 누락은 허용됩니다,\n- ENOENT가 아닌 복사 오류는 경고로 기록되며 포크는 여전히 완료됩니다.\n\n포크 후 ID 시사점:\n\n- 복사가 성공하면, 새 세션의 아티팩트 카운터는 복사된 최대 ID 이후부터 계속됩니다,\n- 복사가 실패/건너뛰어진 경우, 새 세션 아티팩트 ID는 `0`부터 시작합니다.\n\n포크 후 Blob 시사점:\n\n- blob은 전역적이고 콘텐츠 주소 기반이므로, blob 디렉토리 복사가 필요하지 않습니다.\n\n## 새 cwd로 이동\n\n`SessionManager.moveTo()`는 세션 파일과 아티팩트 디렉토리를 모두 새 기본 세션 디렉토리로 이름을 변경하며, 이후 단계가 실패할 경우 롤백 로직을 포함합니다. 이는 세션 범위를 재배치하면서 아티팩트 식별성을 보존합니다.\n\n## 실패 처리 및 폴백 경로\n\n| 경우 | 동작 |\n| --- | --- |\n| 재수화 중 blob 파일 누락 | 경고하고 인메모리에 `blob:sha256:` 참조 문자열 유지 |\n| `BlobStore.get`을 통한 blob 읽기 ENOENT | `null` 반환 |\n| 아티팩트 디렉토리 누락 (`ArtifactManager.listFiles`) | 빈 목록 반환 (할당은 새로 시작 가능) |\n| 아티팩트 디렉토리 누락 (`artifact://` / `agent://`) | 명시적으로 `No artifacts directory found` 발생 |\n| 아티팩트 ID 미발견 | 사용 가능한 ID 목록과 함께 오류 발생 |\n| OutputSink 아티팩트 라이터 초기화 실패 | 테일 전용 잘림으로 계속 (전체 출력 아티팩트 없음) |\n| 세션 파일 없음 (일부 작업 경로) | 작업 도구가 하위 에이전트 출력을 위해 임시 아티팩트 디렉토리로 폴백 |\n\n## 바이너리 blob 외부화 vs 텍스트 출력 아티팩트\n\n- **Blob 외부화**는 영속화된 세션 항목 콘텐츠 내의 바이너리 이미지 페이로드를 위한 것입니다; JSONL의 인라인 base64를 안정적인 콘텐츠 참조로 대체합니다.\n- **아티팩트**는 실행 출력 및 하위 에이전트 출력을 위한 일반 텍스트 파일입니다; 내부 URL을 통해 세션 로컬 ID로 주소 지정할 수 있습니다.\n\n두 시스템은 간접적으로만 교차합니다(둘 다 세션 JSONL 비대화를 줄임). 그러나 식별, 수명 및 검색 경로가 다릅니다.\n\n## 구현 파일\n\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts) — blob 참조 형식, 해싱, put/get, 외부화/해석 헬퍼.\n- [`src/session/artifacts.ts`](../../packages/coding-agent/src/session/artifacts.ts) — 세션 아티팩트 디렉토리 모델 및 숫자 아티팩트 ID 할당.\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — `OutputSink` 잘림/파일 스필 동작 및 요약 메타데이터.\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts) — 영속성 변환, 로드 시 blob 재수화, 세션 포크/이동 상호작용.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — 대화형 포크 중 아티팩트 디렉토리 복사.\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — 도구 아티팩트 매니저 부트스트랩 및 도구별 아티팩트 경로 할당.\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — `artifact://` 리졸버.\n- [`src/internal-urls/agent-protocol.ts`](../../packages/coding-agent/src/internal-urls/agent-protocol.ts) — `agent://` 리졸버 + JSON 추출.\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — 내부 URL 라우터 연결 및 아티팩트 디렉토리 리졸버.\n- [`src/task/output-manager.ts`](../../packages/coding-agent/src/task/output-manager.ts) — `agent://`를 위한 세션 범위 에이전트 출력 ID 할당.\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — 하위 에이전트 출력 아티팩트 쓰기 (`<id>.md`) 및 임시 아티팩트 디렉토리 폴백.\n",
	"ko/configuration/config-usage.md": "---\ntitle: 구성 탐색 및 해석\ndescription: 'xcsh가 프로젝트, 사용자, 기업 루트에서 구성을 탐색하고 해석하며 계층화하는 방법.'\nsidebar:\n  order: 1\n  label: 구성\ni18n:\n  sourceHash: e38bd9792499\n  translator: machine\n---\n\n# 구성 탐색 및 해석\n\n이 문서는 코딩 에이전트가 현재 구성을 해석하는 방법을 설명합니다: 어떤 루트가 스캔되는지, 우선순위가 어떻게 동작하는지, 그리고 해석된 구성이 설정, 스킬, 훅, 도구, 확장에 의해 어떻게 소비되는지를 다룹니다.\n\n## 범위\n\n주요 구현:\n\n- `src/config.ts`\n- `src/config/settings.ts`\n- `src/config/settings-schema.ts`\n- `src/discovery/builtin.ts`\n- `src/discovery/helpers.ts`\n\n주요 통합 지점:\n\n- `src/capability/index.ts`\n- `src/discovery/index.ts`\n- `src/extensibility/skills.ts`\n- `src/extensibility/hooks/loader.ts`\n- `src/extensibility/custom-tools/loader.ts`\n- `src/extensibility/extensions/loader.ts`\n\n---\n\n## 해석 흐름 (시각화)\n\n```text\n         Config roots (ordered)\n┌───────────────────────────────────────┐\n│ 1) ~/.xcsh/agent + <cwd>/.xcsh          │\n│ 2) ~/.claude   + <cwd>/.claude        │\n│ 3) ~/.codex    + <cwd>/.codex         │\n│ 4) ~/.gemini   + <cwd>/.gemini        │\n└───────────────────────────────────────┘\n                    │\n                    ▼\n        config.ts helper resolution\n  (getConfigDirs/findConfigFile/findNearest...)\n                    │\n                    ▼\n       capability providers enumerate items\n (native, claude, codex, gemini, agents, etc.)\n                    │\n                    ▼\n      priority sort + per-capability dedup\n                    │\n                    ▼\n          subsystem-specific consumption\n   (settings, skills, hooks, tools, extensions)\n```\n\n## 1) 구성 루트와 소스 순서\n\n## 표준 루트\n\n`src/config.ts`는 고정된 소스 우선순위 목록을 정의합니다:\n\n1. `.xcsh` (네이티브)\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\n사용자 수준 기본 경로:\n\n- `~/.xcsh/agent`\n- `~/.claude`\n- `~/.codex`\n- `~/.gemini`\n\n프로젝트 수준 기본 경로:\n\n- `<cwd>/.xcsh`\n- `<cwd>/.claude`\n- `<cwd>/.codex`\n- `<cwd>/.gemini`\n\n`CONFIG_DIR_NAME`은 `.xcsh`입니다 (`packages/utils/src/dirs.ts`).\n\n## 중요한 제약 사항\n\n`src/config.ts`의 일반 헬퍼는 소스 탐색 순서에 `.pi`를 포함하지 **않습니다**.\n\n---\n\n## 2) 핵심 탐색 헬퍼 (`src/config.ts`)\n\n## `getConfigDirs(subpath, options)`\n\n정렬된 항목을 반환합니다:\n\n- 사용자 수준 항목이 먼저 (소스 우선순위순)\n- 이후 프로젝트 수준 항목 (동일한 소스 우선순위순)\n\n옵션:\n\n- `user` (기본값 `true`)\n- `project` (기본값 `true`)\n- `cwd` (기본값 `getProjectDir()`)\n- `existingOnly` (기본값 `false`)\n\n이 API는 디렉토리 기반 구성 조회(명령어, 훅, 도구, 에이전트 등)에 사용됩니다.\n\n## `findConfigFile(subpath, options)` / `findConfigFileWithMeta(...)`\n\n정렬된 기본 경로를 순회하며 첫 번째로 존재하는 파일을 검색하고, 첫 번째 일치 항목(경로만 또는 경로+메타데이터)을 반환합니다.\n\n## `findAllNearestProjectConfigDirs(subpath, cwd)`\n\n상위 디렉토리를 위로 탐색하며 **소스 기본 경로별(`.xcsh`, `.claude`, `.codex`, `.gemini`) 가장 가까운 존재하는 디렉토리**를 반환한 후, 소스 우선순위로 결과를 정렬합니다.\n\n프로젝트 구성이 상위 디렉토리에서 상속되어야 할 때 사용합니다 (모노레포/중첩 워크스페이스 동작).\n\n---\n\n## 3) 파일 구성 래퍼 (`src/config.ts`의 `ConfigFile<T>`)\n\n`ConfigFile<T>`는 단일 구성 파일을 위한 스키마 검증 로더입니다.\n\n지원 형식:\n\n- `.yml` / `.yaml`\n- `.json` / `.jsonc`\n\n동작:\n\n- 제공된 TypeBox 스키마에 대해 AJV로 파싱된 데이터를 검증합니다.\n- `invalidate()`가 호출될 때까지 로드 결과를 캐시합니다.\n- `tryLoad()`를 통해 3상태 결과를 반환합니다:\n  - `ok`\n  - `not-found`\n  - `error` (스키마/파싱 컨텍스트를 포함하는 `ConfigError`)\n\n레거시 마이그레이션이 여전히 지원됩니다:\n\n- 대상 경로가 `.yml`/`.yaml`인 경우, 형제 `.json` 파일이 한 번 자동 마이그레이션됩니다 (`migrateJsonToYml`).\n\n---\n\n## 4) 설정 해석 모델 (`src/config/settings.ts`)\n\n런타임 설정 모델은 계층화되어 있습니다:\n\n1. 전역 설정: `~/.xcsh/agent/config.yml`\n2. 프로젝트 설정: 설정 기능을 통해 탐색됨 (제공자로부터의 `settings.json`)\n3. 런타임 오버라이드: 인메모리, 비영구적\n4. 스키마 기본값: `SETTINGS_SCHEMA`에서 가져옴\n\n실효 읽기 경로:\n\n`defaults <- global <- project <- overrides`\n\n쓰기 동작:\n\n- `settings.set(...)`은 **전역** 계층(`config.yml`)에 기록하고 백그라운드 저장을 큐에 넣습니다.\n- 프로젝트 설정은 기능 탐색으로부터 읽기 전용입니다.\n\n## 마이그레이션 동작이 여전히 활성화되어 있음\n\n시작 시 `config.yml`이 없는 경우:\n\n1. `~/.xcsh/agent/settings.json`에서 마이그레이션 (성공 시 `.bak`으로 이름 변경)\n2. `agent.db`의 레거시 DB 설정과 병합\n3. 병합된 결과를 `config.yml`에 기록\n\n`#migrateRawSettings`의 필드 수준 마이그레이션:\n\n- `queueMode` -> `steeringMode`\n- `ask.timeout` 밀리초 -> 이전 값이 밀리초처럼 보일 때(`> 1000`) 초 단위로 변환\n- 레거시 단순 `theme: \"...\"` -> `theme.dark/theme.light` 구조\n\n---\n\n## 5) 기능/탐색 통합\n\n대부분의 비핵심 구성 로딩은 기능 레지스트리(`src/capability/index.ts` + `src/discovery/index.ts`)를 통해 흐릅니다.\n\n## 제공자 순서\n\n제공자는 숫자 우선순위(높을수록 먼저)로 정렬됩니다. 예시 우선순위:\n\n- 네이티브 OMP (`builtin.ts`): `100`\n- Claude: `80`\n- Codex / agents / Claude marketplace: `70`\n- Gemini: `60`\n\n```text\nProvider precedence (higher wins)\n\nnative (.xcsh)          priority 100\nclaude                 priority  80\ncodex / agents / ...   priority  70\ngemini                 priority  60\n```\n\n## 중복 제거 의미\n\n기능은 `key(item)`을 정의합니다:\n\n- 동일한 키 => 첫 번째 항목이 우선 (더 높은 우선순위/먼저 로드된 항목)\n- 키 없음 (`undefined`) => 중복 제거 없음, 모든 항목 유지\n\n관련 키:\n\n- 스킬: `name`\n- 도구: `name`\n- 훅: `${type}:${tool}:${name}`\n- 확장 모듈: `name`\n- 확장: `name`\n- 설정: 중복 제거 없음 (모든 항목 유지)\n\n---\n\n## 6) 네이티브 `.xcsh` 제공자 동작 (`src/discovery/builtin.ts`)\n\n네이티브 제공자(`id: native`)는 다음에서 읽습니다:\n\n- 프로젝트: `<cwd>/.xcsh/...`\n- 사용자: `~/.xcsh/agent/...`\n\n### 디렉토리 허용 규칙\n\n`builtin.ts`는 디렉토리가 존재하고 **비어 있지 않은 경우**(`ifNonEmptyDir`)에만 구성 루트를 포함합니다.\n\n### 범위별 로딩\n\n- 스킬: `skills/*/SKILL.md`\n- 슬래시 명령어: `commands/*.md`\n- 규칙: `rules/*.{md,mdc}`\n- 프롬프트: `prompts/*.md`\n- 지침: `instructions/*.md`\n- 훅: `hooks/pre/*`, `hooks/post/*`\n- 도구: `tools/*.json|*.md` 및 `tools/<name>/index.ts`\n- 확장 모듈: `extensions/` 아래에서 탐색 (+ 레거시 `settings.json.extensions` 문자열 배열)\n- 확장: `extensions/<name>/gemini-extension.json`\n- 설정 기능: `settings.json`\n\n### 가장 가까운 프로젝트 조회 뉘앙스\n\n`SYSTEM.md`와 `XCSH.md`의 경우, 네이티브 제공자는 가장 가까운 상위 프로젝트 `.xcsh` 디렉토리 검색(상위 탐색)을 사용하지만 여전히 `.xcsh` 디렉토리가 비어 있지 않아야 합니다.\n\n---\n\n## 7) 주요 서브시스템이 구성을 소비하는 방법\n\n## 설정 서브시스템\n\n- `Settings.init()`은 전역 `config.yml` + 탐색된 프로젝트 `settings.json` 기능 항목을 로드합니다.\n- `level === \"project\"`인 기능 항목만 프로젝트 계층에 병합됩니다.\n\n## 스킬 서브시스템\n\n- `extensibility/skills.ts`는 `loadCapability(skillCapability.id, { cwd })`를 통해 로드합니다.\n- 소스 토글 및 필터(`ignoredSkills`, `includeSkills`, 사용자 지정 디렉토리)를 적용합니다.\n- 레거시 명명 토글이 여전히 존재합니다(`skills.enablePiUser`, `skills.enablePiProject`), 이들은 네이티브 제공자(`provider === \"native\"`)를 게이트합니다.\n\n## 훅 서브시스템\n\n- `discoverAndLoadHooks()`는 훅 기능 + 명시적으로 구성된 경로에서 훅 경로를 해석합니다.\n- 이후 Bun import를 통해 모듈을 로드합니다.\n\n## 도구 서브시스템\n\n- `discoverAndLoadCustomTools()`는 도구 기능 + 플러그인 도구 경로 + 명시적으로 구성된 경로에서 도구 경로를 해석합니다.\n- 선언적 `.md/.json` 도구 파일은 메타데이터 전용이며, 실행 가능한 로딩은 코드 모듈을 기대합니다.\n\n## 확장 서브시스템\n\n- `discoverAndLoadExtensions()`는 확장 모듈 기능과 명시적 경로에서 확장 모듈을 해석합니다.\n- 현재 구현은 로딩 전에 의도적으로 `_source.provider === \"native\"`인 기능 항목만 유지합니다.\n\n---\n\n## 8) 신뢰할 수 있는 우선순위 규칙\n\n다음 멘탈 모델을 사용하세요:\n\n1. `config.ts`의 소스 디렉토리 순서가 후보 경로 순서를 결정합니다.\n2. 기능 제공자 우선순위가 교차 제공자 우선순위를 결정합니다.\n3. 기능 키 중복 제거가 충돌 동작을 결정합니다 (키가 있는 기능의 경우 첫 번째가 우선).\n4. 서브시스템별 병합 로직이 실효 우선순위를 추가로 변경할 수 있습니다 (특히 설정).\n\n### 설정 관련 주의사항\n\n설정 기능 항목은 중복 제거되지 않습니다. `Settings.#loadProjectSettings()`는 반환된 순서대로 프로젝트 항목을 깊은 병합합니다. 병합 시 이후 항목 값이 이전 항목 값을 덮어쓰므로, 실효 오버라이드 동작은 기능 키 의미만이 아닌 제공자 출력 순서에 따라 달라집니다.\n\n---\n\n## 9) 여전히 존재하는 레거시/호환성 동작\n\n- YAML 대상 파일에 대한 `ConfigFile` JSON -> YAML 마이그레이션.\n- `settings.json` 및 `agent.db`에서 `config.yml`로의 설정 마이그레이션.\n- 설정 키 마이그레이션 (`queueMode`, `ask.timeout`, 단순 `theme`).\n- 확장 매니페스트 호환성: 로더가 `package.json.xcsh`와 `package.json.pi` 매니페스트 섹션을 모두 허용합니다.\n- 레거시 설정 이름 `skills.enablePiUser` / `skills.enablePiProject`는 여전히 네이티브 스킬 소스의 활성 게이트입니다.\n\n이러한 호환성 경로가 코드에서 제거되면 이 문서를 즉시 업데이트하세요. 현재 여러 런타임 동작이 이에 의존하고 있습니다.\n",
	"ko/configuration/environment-variables.md": "---\ntitle: 환경 변수\ndescription: xcsh 구성 및 동작 제어를 위한 런타임 환경 변수 참조.\nsidebar:\n  order: 2\n  label: 환경 변수\ni18n:\n  sourceHash: e2890f963c02\n  translator: machine\n---\n\n# 환경 변수 (현재 런타임 참조)\n\n이 참조는 다음 경로의 현재 코드에서 도출되었습니다:\n\n- `packages/coding-agent/src/**`\n- `packages/ai/src/**` (coding-agent에서 사용하는 프로바이더/인증 해결)\n- `packages/utils/src/**` 및 `packages/tui/src/**` (해당 변수가 coding-agent 런타임에 직접 영향을 미치는 경우)\n\n활성 동작만 문서화합니다.\n\n## 해결 모델 및 우선순위\n\n대부분의 런타임 조회는 `@f5-sales-demo/pi-utils` (`packages/utils/src/env.ts`)의 `$env`를 사용합니다.\n\n`$env` 로딩 순서:\n\n1. 기존 프로세스 환경 (`Bun.env`)\n2. 아직 설정되지 않은 키에 대해 프로젝트 `.env` (`$PWD/.env`)\n3. 아직 설정되지 않은 키에 대해 홈 `.env` (`~/.env`)\n\n`.env` 파일의 추가 규칙: 파싱 중 `XCSH_*` 키는 `PI_*` 키로 미러링됩니다.\n\n---\n\n## 1) 모델/프로바이더 인증\n\n별도로 명시되지 않는 한 `getEnvApiKey()` (`packages/ai/src/stream.ts`)를 통해 사용됩니다.\n\n### 핵심 프로바이더 자격 증명\n\n| 변수                            | 사용 대상 | 필요 시점                                                     | 참고사항 / 우선순위                                                                                  |\n|---------------------------------|---|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|\n| `ANTHROPIC_OAUTH_TOKEN`         | Anthropic API 인증 | OAuth 토큰 인증으로 Anthropic 사용 시                         | 프로바이더 인증 해결 시 `ANTHROPIC_API_KEY`보다 우선함                                              |\n| `ANTHROPIC_API_KEY`             | Anthropic API 인증 | OAuth 토큰 없이 Anthropic 사용 시                           | `ANTHROPIC_OAUTH_TOKEN` 이후 폴백                                                              |\n| `ANTHROPIC_FOUNDRY_API_KEY`     | Azure Foundry / 엔터프라이즈 게이트웨이를 통한 Anthropic | `CLAUDE_CODE_USE_FOUNDRY` 활성화 시                             | Foundry 모드 활성화 시 `ANTHROPIC_OAUTH_TOKEN` 및 `ANTHROPIC_API_KEY`보다 우선함  |\n| `OPENAI_API_KEY`                | OpenAI 인증 | 명시적 apiKey 인수 없이 OpenAI 계열 프로바이더 사용 시 | OpenAI Completions/Responses 프로바이더에서 사용됨                                                      |\n| `GEMINI_API_KEY`                | Google Gemini 인증 | `google` 프로바이더 모델 사용 시                                | Gemini 프로바이더 매핑을 위한 기본 키                                                             |\n| `GOOGLE_API_KEY`                | Gemini 이미지 도구 인증 폴백 | `GEMINI_API_KEY` 없이 `gemini_image` 도구 사용 시            | coding-agent 이미지 도구 폴백 경로에서 사용됨                                                       |\n| `GROQ_API_KEY`                  | Groq 인증 | Groq 모델 사용 시                                             |                                                                                                     |\n| `CEREBRAS_API_KEY`              | Cerebras 인증 | Cerebras 모델 사용 시                                         |                                                                                                     |\n| `TOGETHER_API_KEY`              | Together 인증 | `together` 프로바이더 사용 시                                     |                                                                                                     |\n| `HUGGINGFACE_HUB_TOKEN`         | Hugging Face 인증 | `huggingface` 프로바이더 사용 시                                  | 기본 Hugging Face 토큰 환경 변수                                                                  |\n| `HF_TOKEN`                      | Hugging Face 인증 | `huggingface` 프로바이더 사용 시                                  | `HUGGINGFACE_HUB_TOKEN`이 설정되지 않은 경우 폴백                                                      |\n| `SYNTHETIC_API_KEY`             | Synthetic 인증 | Synthetic 모델 사용 시                                        |                                                                                                     |\n| `NVIDIA_API_KEY`                | NVIDIA 인증 | `nvidia` 프로바이더 사용 시                                       |                                                                                                     |\n| `NANO_GPT_API_KEY`              | NanoGPT 인증 | `nanogpt` 프로바이더 사용 시                                      |                                                                                                     |\n| `VENICE_API_KEY`                | Venice 인증 | `venice` 프로바이더 사용 시                                       |                                                                                                     |\n| `LITELLM_API_KEY`               | LiteLLM 인증 | `litellm` 프로바이더 사용 시                                      | OpenAI 호환 LiteLLM 프록시 키. `LITELLM_BASE_URL`과 함께 설정 시 `models.yml` 자동 구성 활성화 |\n| `LM_STUDIO_API_KEY`             | LM Studio 인증 (선택) | 인증된 호스트에서 `lm-studio` 프로바이더 사용 시           | 로컬 LM Studio는 일반적으로 인증 없이 실행됨; 키가 필요한 경우 비어 있지 않은 토큰이면 됨         |\n| `OLLAMA_API_KEY`                | Ollama 인증 (선택) | 인증된 호스트에서 `ollama` 프로바이더 사용 시              | 로컬 Ollama는 일반적으로 인증 없이 실행됨; 키가 필요한 경우 비어 있지 않은 토큰이면 됨            |\n| `LLAMA_CPP_API_KEY`             | Ollama 인증 (선택) | `--api-key` 파라미터와 함께 `llama-server` 사용 시              | 로컬 llama.cpp는 일반적으로 인증 없이 실행됨; 키가 구성된 경우 비어 있지 않은 토큰이면 됨       |\n| `XIAOMI_API_KEY`                | Xiaomi MiMo 인증 | `xiaomi` 프로바이더 사용 시                                       |                                                                                                     |\n| `MOONSHOT_API_KEY`              | Moonshot 인증 | `moonshot` 프로바이더 사용 시                                     |                                                                                                     |\n| `XAI_API_KEY`                   | xAI 인증 | xAI 모델 사용 시                                              |                                                                                                     |\n| `OPENROUTER_API_KEY`            | OpenRouter 인증 | OpenRouter 모델 사용 시                                       | 선호/자동 프로바이더가 OpenRouter인 경우 이미지 도구에서도 사용됨                                  |\n| `MISTRAL_API_KEY`               | Mistral 인증 | Mistral 모델 사용 시                                          |                                                                                                     |\n| `ZAI_API_KEY`                   | z.ai 인증 | z.ai 모델 사용 시                                             | z.ai 웹 검색 프로바이더에서도 사용됨                                                               |\n| `MINIMAX_API_KEY`               | MiniMax 인증 | `minimax` 프로바이더 사용 시                                      |                                                                                                     |\n| `MINIMAX_CODE_API_KEY`          | MiniMax Code 인증 | `minimax-code` 프로바이더 사용 시                                 |                                                                                                     |\n| `MINIMAX_CODE_CN_API_KEY`       | MiniMax Code CN 인증 | `minimax-code-cn` 프로바이더 사용 시                              |                                                                                                     |\n| `OPENCODE_API_KEY`              | OpenCode 인증 | OpenCode 모델 사용 시                                         |                                                                                                     |\n| `QIANFAN_API_KEY`               | Qianfan 인증 | `qianfan` 프로바이더 사용 시                                      |                                                                                                     |\n| `QWEN_OAUTH_TOKEN`              | Qwen 포털 인증 | OAuth 토큰으로 `qwen-portal` 사용 시                          | `QWEN_PORTAL_API_KEY`보다 우선함                                                                         |\n| `QWEN_PORTAL_API_KEY`           | Qwen 포털 인증 | API 키로 `qwen-portal` 사용 시                              | `QWEN_OAUTH_TOKEN` 이후 폴백                                                                   |\n| `ZENMUX_API_KEY`                | ZenMux 인증 | `zenmux` 프로바이더 사용 시                                       | ZenMux OpenAI 및 Anthropic 호환 라우트에서 사용됨                                              |\n| `VLLM_API_KEY`                  | vLLM 인증/검색 옵트인 | `vllm` 프로바이더 사용 시 (로컬 OpenAI 호환 서버)       | 인증 없는 로컬 서버의 경우 비어 있지 않은 값이면 됨                                                 |\n| `CURSOR_ACCESS_TOKEN`           | Cursor 프로바이더 인증 | Cursor 프로바이더 사용 시                                         |                                                                                                     |\n| `AI_GATEWAY_API_KEY`            | Vercel AI 게이트웨이 인증 | `vercel-ai-gateway` 프로바이더 사용 시                            |                                                                                                     |\n| `CLOUDFLARE_AI_GATEWAY_API_KEY` | Cloudflare AI 게이트웨이 인증 | `cloudflare-ai-gateway` 프로바이더 사용 시                        | 베이스 URL은 `https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic`으로 구성되어야 함 |\n\n### GitHub/Copilot 토큰 체인\n\n| 변수 | 사용 대상 | 체인 |\n|---|---|---|\n| `COPILOT_GITHUB_TOKEN` | GitHub Copilot 프로바이더 인증 | `COPILOT_GITHUB_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN` |\n| `GH_TOKEN` | Copilot 폴백; 웹 스크레이퍼에서 GitHub API 인증 | 웹 스크레이퍼: `GITHUB_TOKEN` → `GH_TOKEN` |\n| `GITHUB_TOKEN` | Copilot 폴백; 웹 스크레이퍼에서 GitHub API 인증 | 웹 스크레이퍼: `GH_TOKEN`보다 먼저 확인됨 |\n\n---\n\n## 2) 프로바이더별 런타임 구성\n\n### Anthropic Foundry 게이트웨이 (Azure / 엔터프라이즈 프록시)\n\n`CLAUDE_CODE_USE_FOUNDRY`가 활성화되면 Anthropic 요청이 Foundry 모드로 전환됩니다:\n\n- 베이스 URL은 `FOUNDRY_BASE_URL`에서 해결됩니다 (설정되지 않은 경우 모델/기본 베이스 URL이 폴백으로 유지됨).\n- `anthropic` 프로바이더의 API 키 해결 순서:\n  `ANTHROPIC_FOUNDRY_API_KEY` → `ANTHROPIC_OAUTH_TOKEN` → `ANTHROPIC_API_KEY`.\n- `ANTHROPIC_CUSTOM_HEADERS`는 쉼표/줄바꿈으로 구분된 `key: value` 쌍으로 파싱되어 요청 헤더에 병합됩니다.\n- 환경 변수 값에서 TLS 클라이언트/서버 자료를 주입할 수 있습니다:\n  `NODE_EXTRA_CA_CERTS`, `CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`.\n  각각 다음을 허용합니다:\n  - PEM 콘텐츠에 대한 파일시스템 경로, 또는\n  - 인라인 PEM (이스케이프된 `\\n` 시퀀스 포함).\n\n| 변수 | 값 유형 | 동작 |\n|---|---|---|\n| `CLAUDE_CODE_USE_FOUNDRY` | 부울형 문자열 (`1`, `true`, `yes`, `on`) | Anthropic 프로바이더에 대해 Foundry 모드 활성화 |\n| `FOUNDRY_BASE_URL` | URL 문자열 | Foundry 모드에서의 Anthropic 엔드포인트 베이스 URL |\n| `ANTHROPIC_FOUNDRY_API_KEY` | 토큰 문자열 | `Authorization: Bearer <token>`에 사용됨 |\n| `ANTHROPIC_CUSTOM_HEADERS` | 헤더 목록 문자열 | 추가 헤더; `header-a: value, header-b: value` 형식 또는 줄바꿈 구분 |\n| `NODE_EXTRA_CA_CERTS` | PEM 경로 또는 인라인 PEM | 서버 인증서 유효성 검사를 위한 추가 CA 체인 |\n| `CLAUDE_CODE_CLIENT_CERT` | PEM 경로 또는 인라인 PEM | mTLS 클라이언트 인증서 |\n| `CLAUDE_CODE_CLIENT_KEY` | PEM 경로 또는 인라인 PEM | mTLS 클라이언트 개인 키 (인증서와 쌍을 이루어야 함) |\n\n### Amazon Bedrock\n\n| 변수 | 기본값 / 동작 |\n|---|---|\n| `AWS_REGION` | 기본 리전 소스 |\n| `AWS_DEFAULT_REGION` | `AWS_REGION`이 설정되지 않은 경우 폴백 |\n| `AWS_PROFILE` | 명명된 프로필 인증 경로 활성화 |\n| `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` | IAM 키 인증 경로 활성화 |\n| `AWS_BEARER_TOKEN_BEDROCK` | 베어러 토큰 인증 경로 활성화 |\n| `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` / `AWS_CONTAINER_CREDENTIALS_FULL_URI` | ECS 태스크 자격 증명 경로 활성화 |\n| `AWS_WEB_IDENTITY_TOKEN_FILE` + `AWS_ROLE_ARN` | 웹 ID 인증 경로 활성화 |\n| `AWS_BEDROCK_SKIP_AUTH` | `1`이면 더미 자격 증명 주입 (프록시/비인증 시나리오) |\n| `AWS_BEDROCK_FORCE_HTTP1` | `1`이면 Node HTTP/1 요청 핸들러 강제 사용 |\n\n프로바이더 코드에서의 리전 폴백: `options.region` → `AWS_REGION` → `AWS_DEFAULT_REGION` → `us-east-1`.\n\n### Azure OpenAI Responses\n\n| 변수 | 기본값 / 동작 |\n|---|---|\n| `AZURE_OPENAI_API_KEY` | 옵션으로 API 키가 전달되지 않는 한 필수 |\n| `AZURE_OPENAI_API_VERSION` | 기본값 `v1` |\n| `AZURE_OPENAI_BASE_URL` | 직접 베이스 URL 재정의 |\n| `AZURE_OPENAI_RESOURCE_NAME` | 베이스 URL 구성에 사용됨: `https://<resource>.openai.azure.com/openai/v1` |\n| `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` | 선택적 매핑 문자열: `modelId=deploymentName,model2=deployment2` |\n\n베이스 URL 해결: 옵션 `azureBaseUrl` → 환경 변수 `AZURE_OPENAI_BASE_URL` → 옵션/환경 변수 리소스 이름 → `model.baseUrl`.\n\n### Google Vertex AI\n\n| 변수 | 필수 여부 | 참고사항 |\n|---|---|---|\n| `GOOGLE_CLOUD_PROJECT` | 예 (옵션으로 전달되지 않는 한) | 폴백: `GCLOUD_PROJECT` |\n| `GCLOUD_PROJECT` | 폴백 | 대체 프로젝트 ID 소스로 사용됨 |\n| `GOOGLE_CLOUD_LOCATION` | 예 (옵션으로 전달되지 않는 한) | 프로바이더에 기본값 없음 |\n| `GOOGLE_APPLICATION_CREDENTIALS` | 조건부 | 설정된 경우 파일이 존재해야 함; 그렇지 않으면 ADC 폴백 경로 확인 (`~/.config/gcloud/application_default_credentials.json`) |\n\n### Kimi\n\n| 변수 | 기본값 / 동작 |\n|---|---|\n| `KIMI_CODE_OAUTH_HOST` | 기본 OAuth 호스트 재정의 |\n| `KIMI_OAUTH_HOST` | 폴백 OAuth 호스트 재정의 |\n| `KIMI_CODE_BASE_URL` | Kimi 사용 엔드포인트 베이스 URL 재정의 (`usage/kimi.ts`) |\n\nOAuth 호스트 체인: `KIMI_CODE_OAUTH_HOST` → `KIMI_OAUTH_HOST` → `https://auth.kimi.com`.\n\n### Antigravity/Gemini 이미지 호환성\n\n| 변수 | 기본값 / 동작 |\n|---|---|\n| `PI_AI_ANTIGRAVITY_VERSION` | Gemini CLI 프로바이더에서 Antigravity 사용자 에이전트 버전 태그 재정의 |\n\n### OpenAI Codex responses (기능/디버그 제어)\n\n| 변수 | 동작 |\n|---|---|\n| `PI_CODEX_DEBUG` | `1`/`true`이면 Codex 프로바이더 디버그 로깅 활성화 |\n| `PI_CODEX_WEBSOCKET` | `1`/`true`이면 웹소켓 전송 선호 활성화 |\n| `PI_CODEX_WEBSOCKET_V2` | `1`/`true`이면 웹소켓 v2 경로 활성화 |\n| `PI_CODEX_WEBSOCKET_IDLE_TIMEOUT_MS` | 양의 정수 재정의 (기본값 300000) |\n| `PI_CODEX_WEBSOCKET_RETRY_BUDGET` | 음이 아닌 정수 재정의 (기본값 5) |\n| `PI_CODEX_WEBSOCKET_RETRY_DELAY_MS` | 양의 정수 기본 백오프 재정의 (기본값 500) |\n\n### Cursor 프로바이더 디버그\n\n| 변수 | 동작 |\n|---|---|\n| `DEBUG_CURSOR` | 프로바이더 디버그 로그 활성화; `2`/`verbose`이면 상세 페이로드 스니펫 출력 |\n| `DEBUG_CURSOR_LOG` | JSONL 디버그 로그 출력을 위한 선택적 파일 경로 |\n\n### 프롬프트 캐시 호환성 스위치\n\n| 변수 | 동작 |\n|---|---|\n| `PI_CACHE_RETENTION` | `long`이면 지원되는 경우 긴 보존 활성화 (`anthropic`, `openai-responses`, Bedrock 보존 해결) |\n\n---\n\n## 3) 웹 검색 서브시스템\n\n### 검색 프로바이더 자격 증명\n\n| 변수 | 사용 주체 |\n|---|---|\n| `EXA_API_KEY` | Exa 검색 프로바이더 및 Exa MCP 도구 |\n| `BRAVE_API_KEY` | Brave 검색 프로바이더 |\n| `PERPLEXITY_API_KEY` | Perplexity 검색 프로바이더 API 키 모드 |\n| `TAVILY_API_KEY` | Tavily 검색 프로바이더 |\n| `ZAI_API_KEY` | z.ai 검색 프로바이더 (`agent.db`에 저장된 OAuth도 확인) |\n| `OPENAI_API_KEY` / DB의 Codex OAuth | Codex 검색 프로바이더 가용성/인증 |\n\n### Anthropic 웹 검색 인증 체인\n\n`packages/coding-agent/src/web/search/auth.ts`는 다음 순서로 Anthropic 웹 검색 자격 증명을 해결합니다:\n\n1. `ANTHROPIC_SEARCH_API_KEY` (+ 선택적 `ANTHROPIC_SEARCH_BASE_URL`)\n2. `api: \"anthropic-messages\"`를 가진 `models.json` 프로바이더 항목\n3. `agent.db`의 Anthropic OAuth 자격 증명 (5분 버퍼 내에 만료되지 않아야 함)\n4. 일반 Anthropic 환경 변수 폴백: 프로바이더 키 (`ANTHROPIC_FOUNDRY_API_KEY`/`ANTHROPIC_OAUTH_TOKEN`/`ANTHROPIC_API_KEY`) + 선택적 `ANTHROPIC_BASE_URL` (Foundry 모드 활성화 시 `FOUNDRY_BASE_URL`)\n\n관련 변수:\n\n| 변수 | 기본값 / 동작 |\n|---|---|\n| `ANTHROPIC_SEARCH_API_KEY` | 최우선 명시적 검색 키 |\n| `ANTHROPIC_SEARCH_BASE_URL` | 생략 시 `https://api.anthropic.com`으로 기본 설정 |\n| `ANTHROPIC_SEARCH_MODEL` | 기본값 `claude-haiku-4-5` |\n| `ANTHROPIC_BASE_URL` | 4단계 인증 경로를 위한 일반 폴백 베이스 URL |\n\n### Perplexity OAuth 흐름 동작 플래그\n\n| 변수 | 동작 |\n|---|---|\n| `PI_AUTH_NO_BORROW` | 설정된 경우 Perplexity 로그인 흐름에서 macOS 네이티브 앱 토큰 차용 경로 비활성화 |\n\n---\n\n## 4) Python 도구 및 커널 런타임\n\n| 변수 | 기본값 / 동작 |\n|---|---|\n| `PI_PY` | Python 도구 모드 재정의: `0`/`bash`=`bash-only`, `1`/`py`=`ipy-only`, `mix`/`both`=`both`; 유효하지 않은 값은 무시됨 |\n| `PI_PYTHON_SKIP_CHECK` | `1`이면 Python 커널 가용성 검사/워밍 검사 건너뜀 |\n| `PI_PYTHON_GATEWAY_URL` | 설정된 경우 로컬 공유 게이트웨이 대신 외부 커널 게이트웨이 사용 |\n| `PI_PYTHON_GATEWAY_TOKEN` | 외부 게이트웨이를 위한 선택적 인증 토큰 (`Authorization: token <value>`) |\n| `PI_PYTHON_IPC_TRACE` | `1`이면 커널 모듈에서 저수준 IPC 추적 경로 활성화 |\n| `VIRTUAL_ENV` | Python 런타임 해결을 위한 최우선 venv 경로 |\n\n추가 조건부 동작:\n\n- `BUN_ENV=test` 또는 `NODE_ENV=test`이면 Python 가용성 검사가 OK로 처리되고 워밍이 건너뜀.\n- Python 환경 필터링은 일반적인 API 키 변수를 차단하고 안전한 기본 변수 및 `LC_`, `XDG_`, `PI_` 접두사를 허용합니다.\n\n---\n\n## 5) 에이전트/런타임 동작 토글\n\n| 변수                   | 기본값 / 동작                                                                           |\n|----------------------------|----------------------------------------------------------------------------------------------|\n| `PI_SMOL_MODEL`            | `smol` 역할에 대한 임시 모델 역할 재정의 (CLI `--smol`이 우선함)                     |\n| `PI_SLOW_MODEL`            | `slow` 역할에 대한 임시 모델 역할 재정의 (CLI `--slow`이 우선함)                     |\n| `PI_PLAN_MODEL`            | `plan` 역할에 대한 임시 모델 역할 재정의 (CLI `--plan`이 우선함)                     |\n| `PI_NO_TITLE`              | 설정된 경우 (비어 있지 않은 값), 첫 번째 사용자 메시지에서 자동 세션 제목 생성 비활성화   |\n| `NULL_PROMPT`              | `true`이면 시스템 프롬프트 빌더가 빈 문자열 반환                                        |\n| `PI_BLOCKED_AGENT`         | 태스크 도구에서 특정 서브에이전트 유형 차단                                 |\n| `PI_SUBPROCESS_CMD`        | 서브에이전트 스폰 명령 재정의 (`xcsh` / `xcsh.cmd` 해결 우회)                       |\n| `PI_TASK_MAX_OUTPUT_BYTES` | 서브에이전트당 최대 캡처 출력 바이트 (기본값 `500000`)                                    |\n| `PI_TASK_MAX_OUTPUT_LINES` | 서브에이전트당 최대 캡처 출력 라인 (기본값 `5000`)                                      |\n| `PI_TIMING`                | `1`이면 시작/도구 타이밍 계측 로그 활성화                                     |\n| `PI_DEBUG_STARTUP`         | 여러 시작 경로에서 stderr로 시작 단계 디버그 출력 활성화                       |\n| `PI_PACKAGE_DIR`           | 패키지 에셋 기본 디렉토리 해결 재정의 (문서/예제/변경로그 경로 조회)            |\n| `PI_DISABLE_LSPMUX`        | `1`이면 lspmux 감지/통합 비활성화 및 직접 LSP 서버 스폰 강제                          |\n| `LITELLM_BASE_URL`         | LiteLLM 프록시 베이스 URL. `LITELLM_API_KEY`와 함께 설정 시 최초 실행 시 `models.yml` 자동 생성 및 매 시작 시 자가 복구 트리거 |\n| `LM_STUDIO_BASE_URL`       | 기본 암묵적 LM Studio 검색 베이스 URL 재정의 (설정되지 않은 경우 `http://127.0.0.1:1234/v1`) |\n| `OLLAMA_BASE_URL`          | 기본 암묵적 Ollama 검색 베이스 URL 재정의 (설정되지 않은 경우 `http://127.0.0.1:11434`)      |\n| `LLAMA_CPP_BASE_URL`       | 기본 암묵적 Llama.cpp 검색 베이스 URL 재정의 (설정되지 않은 경우 `http://127.0.0.1:8080`)    |\n| `PI_EDIT_VARIANT`          | `hashline`이면 편집 도구 사용 가능 시 hashline 읽기/grep 표시 모드 강제               |\n| `PI_NO_PTY`                | `1`이면 bash 도구의 인터랙티브 PTY 경로 비활성화                                          |\n\n`PI_NO_PTY`는 CLI `--no-pty` 사용 시 내부적으로도 설정됩니다.\n\n---\n\n## 6) 스토리지 및 구성 루트 경로\n\n이것들은 `@f5-sales-demo/pi-utils/dirs`를 통해 사용되며 coding-agent가 데이터를 저장하는 위치에 영향을 미칩니다.\n\n| 변수 | 기본값 / 동작 |\n|---|---|\n| `PI_CONFIG_DIR` | 홈 디렉토리 아래 구성 루트 디렉토리명 (기본값 `.xcsh`) |\n| `PI_CODING_AGENT_DIR` | 에이전트 디렉토리의 전체 재정의 (기본값 `~/<PI_CONFIG_DIR or .xcsh>/agent`) |\n| `PWD` | 경로 헬퍼에서 표준 현재 작업 디렉토리 일치 시 사용됨 |\n\n---\n\n## 7) 셸/도구 실행 환경\n\n(`packages/utils/src/procmgr.ts` 및 coding-agent bash 도구 통합에서.)\n\n| 변수 | 동작 |\n|---|---|\n| `PI_BASH_NO_CI` | 스폰된 셸 환경에 자동 `CI=true` 주입 억제 |\n| `CLAUDE_BASH_NO_CI` | `PI_BASH_NO_CI`의 레거시 별칭 폴백 |\n| `PI_BASH_NO_LOGIN` | 로그인 셸 모드 비활성화 의도 |\n| `CLAUDE_BASH_NO_LOGIN` | `PI_BASH_NO_LOGIN`의 레거시 별칭 폴백 |\n| `PI_SHELL_PREFIX` | 선택적 명령 접두사 래퍼 |\n| `CLAUDE_CODE_SHELL_PREFIX` | `PI_SHELL_PREFIX`의 레거시 별칭 폴백 |\n| `VISUAL` | 선호하는 외부 편집기 명령 |\n| `EDITOR` | 폴백 외부 편집기 명령 |\n\n현재 구현 참고: `PI_BASH_NO_LOGIN`/`CLAUDE_BASH_NO_LOGIN`은 읽히지만 현재 `getShellArgs()`는 두 분기 모두에서 `['-l','-c']`를 반환합니다 (현재는 사실상 아무 동작도 하지 않음).\n\n---\n\n## 8) UI/테마/세션 감지 (자동 감지 환경 변수)\n\n이것들은 런타임 신호로 읽히며, 일반적으로 수동으로 구성하는 것이 아니라 터미널/OS에 의해 설정됩니다.\n\n| 변수 | 사용 대상 |\n|---|---|\n| `COLORTERM`, `TERM`, `WT_SESSION` | 색상 기능 감지 (테마 색상 모드) |\n| `COLORFGBG` | 터미널 배경 밝음/어두움 자동 감지 |\n| `TERM_PROGRAM`, `TERM_PROGRAM_VERSION`, `TERMINAL_EMULATOR` | 시스템 프롬프트/컨텍스트에서 터미널 식별 |\n| `KDE_FULL_SESSION`, `XDG_CURRENT_DESKTOP`, `DESKTOP_SESSION`, `XDG_SESSION_DESKTOP`, `GDMSESSION`, `WINDOWMANAGER` | 시스템 프롬프트/컨텍스트에서 데스크톱/윈도우 매니저 감지 |\n| `KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION` | 안정적인 터미널별 세션 브레드크럼 ID |\n| `SHELL`, `ComSpec`, `TERM_PROGRAM`, `TERM` | 시스템 정보 진단 |\n| `APPDATA`, `XDG_CONFIG_HOME` | lspmux 구성 경로 해결 |\n| `HOME` | MCP 명령 UI에서 경로 단축 |\n\n---\n\n## 9) 네이티브 로더/디버그 플래그\n\n| 변수 | 동작 |\n|---|---|\n| `PI_DEV` | `packages/natives`에서 자세한 네이티브 애드온 로드 진단 활성화 |\n\n## 10) TUI 런타임 플래그 (공유 패키지, coding-agent UX에 영향)\n\n| 변수 | 동작 |\n|---|---|\n| `PI_NOTIFICATIONS` | `off` / `0` / `false`이면 데스크톱 알림 억제 |\n| `PI_TUI_WRITE_LOG` | 설정된 경우 TUI 쓰기를 파일에 로깅 |\n| `PI_HARDWARE_CURSOR` | `1`이면 하드웨어 커서 모드 활성화 |\n| `PI_CLEAR_ON_SHRINK` | `1`이면 콘텐츠 축소 시 빈 행 지움 |\n| `PI_DEBUG_REDRAW` | `1`이면 리드로우 디버그 로깅 활성화 |\n| `PI_TUI_DEBUG` | `1`이면 심층 TUI 디버그 덤프 경로 활성화 |\n\n---\n\n## 11) 커밋 생성 제어\n\n| 변수 | 동작 |\n|---|---|\n| `PI_COMMIT_TEST_FALLBACK` | `true`이면 (대소문자 구분 없음) 커밋 폴백 생성 경로 강제 |\n| `PI_COMMIT_NO_FALLBACK` | `true`이면 에이전트가 제안을 반환하지 않을 때 폴백 비활성화 |\n| `PI_COMMIT_MAP_REDUCE` | `false`이면 맵-리듀스 커밋 분석 경로 비활성화 |\n| `DEBUG` | 설정된 경우 커밋 에이전트 오류 스택 트레이스 출력 |\n\n---\n\n## 보안에 민감한 변수\n\n이것들을 시크릿으로 취급하십시오; 로깅하거나 커밋하지 마십시오:\n\n- 프로바이더/API 키 및 OAuth/베어러 자격 증명 (모든 `*_API_KEY`, `*_TOKEN`, OAuth 액세스/갱신 토큰)\n- 클라우드 자격 증명 (`AWS_*`, `GOOGLE_APPLICATION_CREDENTIALS` 경로는 서비스 계정 자료를 노출할 수 있음)\n- 검색/프로바이더 인증 변수 (`EXA_API_KEY`, `BRAVE_API_KEY`, `PERPLEXITY_API_KEY`, Anthropic 검색 키)\n- Foundry mTLS 자료 (`CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`, 개인 CA 번들을 가리키는 경우 `NODE_EXTRA_CA_CERTS`)\n\nPython 런타임은 커널 서브프로세스 스폰 전에 많은 일반적인 키 변수를 명시적으로 제거합니다 (`packages/coding-agent/src/ipy/runtime.ts`).\n",
	"ko/configuration/fs-scan-cache-architecture.md": "---\ntitle: 파일시스템 스캔 캐시 아키텍처\ndescription: 빠른 파일 탐색을 위한 stale-while-revalidate 의미론을 가진 파일시스템 스캔 캐시 계약.\nsidebar:\n  order: 8\n  label: 파일시스템 스캔 캐시\ni18n:\n  sourceHash: 2a2bde1726ac\n  translator: machine\n---\n\n# 파일시스템 스캔 캐시 아키텍처 계약\n\n이 문서는 Rust(`crates/pi-natives/src/fs_cache.rs`)로 구현된 공유 파일시스템 스캔 캐시와 `packages/coding-agent`에 노출되는 네이티브 디스커버리/검색 API에서 소비되는 현재 계약을 정의합니다.\n\n## 이 캐시의 정의\n\n캐시는 스캔 범위와 탐색 정책을 키로 하는 전체 디렉토리 스캔 항목 목록(`GlobMatch[]`)을 저장하며, 상위 수준 작업(glob 필터링, 퍼지 스코어링, grep 파일 선택)이 이 캐시된 항목에 대해 실행될 수 있도록 합니다.\n\n주요 목표:\n\n- 반복적인 디스커버리/검색 호출에 대한 반복적인 파일시스템 워킹 방지\n- 동일한 스캔 정책을 공유하는 `glob`, `fuzzyFind`, `grep` 간의 일관성 유지\n- 빈 결과에 대한 명시적 비활성 복구 및 파일 변경 후 명시적 무효화 허용\n\n## 소유권 및 공개 인터페이스\n\n- 캐시 구현 및 정책: `crates/pi-natives/src/fs_cache.rs`\n- 네이티브 소비자:\n  - `crates/pi-natives/src/glob.rs`\n  - `crates/pi-natives/src/fd.rs` (`fuzzyFind`)\n  - `crates/pi-natives/src/grep.rs`\n- JS 바인딩/내보내기:\n  - `packages/natives/src/glob/index.ts` (`invalidateFsScanCache`)\n  - `packages/natives/src/glob/types.ts`\n  - `packages/natives/src/grep/types.ts`\n- 코딩 에이전트 변경 무효화 헬퍼:\n  - `packages/coding-agent/src/tools/fs-cache-invalidation.ts`\n\n## 캐시 키 파티셔닝 (하드 계약)\n\n각 항목은 다음을 키로 합니다:\n\n- 정규화된 `root` 디렉토리 경로\n- `include_hidden` 불리언\n- `use_gitignore` 불리언\n\n의미:\n\n- 숨김 파일 포함 스캔과 미포함 스캔은 항목을 **공유하지 않습니다**.\n- gitignore 준수 스캔과 무시 비활성화 스캔은 항목을 **공유하지 않습니다**.\n- 소비자는 hidden/gitignore 동작에 대해 안정적인 의미론을 전달해야 합니다. 어느 하나의 플래그를 변경하면 다른 캐시 파티션이 생성됩니다.\n\n`node_modules` 포함 여부는 캐시 키에 **포함되지 않습니다**. 캐시는 `node_modules`가 포함된 항목을 저장하며, 소비자별 필터링은 조회 후에 적용됩니다.\n\n## 스캔 수집 동작\n\n캐시 채우기는 `include_hidden`과 `use_gitignore`로 구성된 결정론적 워커(`ignore::WalkBuilder`)를 사용합니다:\n\n- `follow_links(false)`\n- 파일 경로 기준 정렬\n- `.git`은 항상 건너뜀\n- `node_modules`는 캐시 스캔 시점에 항상 수집됨 (이후 선택적으로 필터링)\n- 항목 파일 유형 + `mtime`은 `symlink_metadata`를 통해 캡처됨\n\n검색 루트는 `resolve_search_path`에 의해 해석됩니다:\n\n- 상대 경로는 현재 cwd 기준으로 해석됨\n- 대상은 기존 디렉토리여야 함\n- 루트는 가능한 경우 정규화됨\n\n## 신선도 및 제거 정책\n\n전역 정책 (환경 변수로 재정의 가능):\n\n- `FS_SCAN_CACHE_TTL_MS` (기본값 `1000`)\n- `FS_SCAN_EMPTY_RECHECK_MS` (기본값 `200`)\n- `FS_SCAN_CACHE_MAX_ENTRIES` (기본값 `16`)\n\n동작:\n\n- `get_or_scan(...)`\n  - TTL이 `0`인 경우: 캐시를 완전히 우회하고, 항상 새로운 스캔 수행 (`cache_age_ms = 0`)\n  - TTL 내의 캐시 히트: 캐시된 항목 + 0이 아닌 `cache_age_ms` 반환\n  - 만료된 히트: 키를 제거하고, 재스캔하여 새 항목 저장\n- 최대 항목 수 적용은 `created_at` 기준 가장 오래된 것부터 제거\n\n## 빈 결과 빠른 재확인 (일반 히트와 별도)\n\n일반 캐시 히트:\n\n- TTL 내의 캐시 히트는 캐시된 항목을 반환하며 다른 작업을 수행하지 않습니다.\n\n빈 결과 빠른 재확인:\n\n- 이것은 `ScanResult.cache_age_ms`를 사용하는 **호출자 측** 정책입니다\n- 필터링/쿼리 결과가 비어있고 캐시된 스캔 나이가 최소 `empty_recheck_ms()`인 경우, 호출자는 `force_rescan(...)`을 한 번 수행하고 재시도합니다\n- 파일이 최근에 추가되었지만 캐시가 아직 TTL 내에 있는 경우 stale-negative 결과를 줄이기 위한 것입니다\n\n현재 소비자:\n\n- `glob`: 필터링된 매치가 비어있고 스캔 나이가 임계값을 초과할 때 재확인\n- `fuzzyFind` (`fd.rs`): 쿼리가 비어있지 않고 스코어링된 매치가 비어있을 때만 재확인\n- `grep`: 선택된 후보 파일 목록이 비어있을 때 재확인\n\n## 소비자 기본값 및 캐시 사용\n\n캐시는 모든 노출된 API에서 옵트인입니다 (`cache?: boolean`, 기본값 `false`).\n\n네이티브 API의 현재 기본값:\n\n- `glob`: `hidden=false`, `gitignore=true`, `cache=false`\n- `fuzzyFind`: `hidden=false`, `gitignore=true`, `cache=false`\n- `grep`: `hidden=true`, `cache=false`, 캐시 스캔은 항상 `use_gitignore=true` 사용\n\n현재 코딩 에이전트 호출자:\n\n- 대량 멘션 후보 디스커버리는 캐시를 활성화:\n  - `packages/coding-agent/src/utils/file-mentions.ts`\n  - 프로필: `hidden=true`, `gitignore=true`, `includeNodeModules=true`, `cache=true`\n- 도구 수준 `grep` 통합은 현재 스캔 캐시를 비활성화 (`cache: false`):\n  - `packages/coding-agent/src/tools/grep.ts`\n\n## 무효화 계약\n\n네이티브 무효화 진입점:\n\n- `invalidateFsScanCache(path?: string)`\n  - `path` 제공 시: 루트가 대상 경로의 접두사인 캐시 항목 제거\n  - `path` 미제공 시: 모든 스캔 캐시 항목 정리\n\n경로 처리 세부사항:\n\n- 상대 무효화 경로는 cwd 기준으로 해석됨\n- 무효화 시 정규화를 시도함\n- 대상이 존재하지 않는 경우 (예: 삭제), 폴백으로 부모를 정규화하고 가능한 경우 파일명을 재연결함\n- 한쪽이 존재하지 않을 수 있는 생성/삭제/이름변경에 대한 무효화 동작을 보존함\n\n## 코딩 에이전트 변경 흐름 책임\n\n코딩 에이전트 코드는 성공적인 파일시스템 변경 후 반드시 무효화해야 합니다.\n\n중앙 헬퍼:\n\n- `invalidateFsScanAfterWrite(path)`\n- `invalidateFsScanAfterDelete(path)`\n- `invalidateFsScanAfterRename(oldPath, newPath)` (경로가 다를 때 양쪽 모두 무효화)\n\n현재 변경 도구 호출 지점:\n\n- `packages/coding-agent/src/tools/write.ts`\n- `packages/coding-agent/src/patch/index.ts` (hashline/patch/replace 흐름)\n\n규칙: 파일시스템 내용이나 위치를 변경하는 흐름이 이 헬퍼들을 우회하면, 캐시 비활성 버그가 예상됩니다.\n\n## 새로운 캐시 소비자를 안전하게 추가하기\n\n새로운 스캐너/검색 경로에 캐시 사용을 도입할 때:\n\n1. **안정적인 스캔 정책 입력 사용**\n   - hidden/gitignore 의미론을 먼저 결정\n   - 캐시 파티션이 의도적이 되도록 `get_or_scan`/`force_rescan`에 일관되게 전달\n\n2. **캐시 데이터를 탐색 정책에 의해서만 사전 필터링된 것으로 취급**\n   - 도구별 필터링(glob 패턴, 유형 필터, node_modules 규칙)은 조회 후 적용\n   - 캐시된 항목이 이미 상위 수준 필터를 반영한다고 절대 가정하지 않기\n\n3. **stale-negative 위험에 대해서만 빈 결과 빠른 재확인 구현**\n   - `scan.cache_age_ms >= empty_recheck_ms()` 사용\n   - `force_rescan(..., store=true, ...)`로 한 번 재시도\n   - 이 경로를 일반 캐시 히트 로직과 분리 유지\n\n4. **캐시 미사용 모드를 명시적으로 준수**\n   - 호출자가 캐시를 비활성화하면, `force_rescan(..., store=false, ...)`를 호출\n   - 캐시 미사용 요청 경로에서 공유 캐시를 채우지 않기\n\n5. **새로운 쓰기 경로에 대한 변경 무효화 연결**\n   - 성공적인 쓰기/편집/삭제/이름변경 후, 코딩 에이전트 무효화 헬퍼를 호출\n   - 이름변경/이동의 경우, 이전 경로와 새 경로 모두 무효화\n\n6. **호출별 TTL 조정 기능을 추가하지 않기**\n   - 현재 계약은 전역 정책만 지원 (환경 변수로 구성), 요청별 TTL 재정의 없음\n\n## 알려진 경계\n\n- 캐시 범위는 프로세스 로컬 인메모리(`DashMap`)이며, 프로세스 재시작 간에 유지되지 않습니다.\n- 캐시는 스캔 항목을 저장하며, 최종 도구 결과는 저장하지 않습니다.\n- `glob`/`fuzzyFind`/`grep`은 키 차원(`root`, `hidden`, `gitignore`)이 일치할 때만 스캔 항목을 공유합니다.\n- `.git`은 호출자 옵션에 관계없이 스캔 수집 시점에 항상 제외됩니다.\n",
	"ko/configuration/hooks.md": "---\ntitle: Hooks\ndescription: 코딩 에이전트 라이프사이클에서 사전/사후 이벤트 자동화를 위한 Hook 시스템.\nsidebar:\n  order: 4\n  label: Hooks\ni18n:\n  sourceHash: cdbec10bc405\n  translator: machine\n---\n\n# Hooks\n\n이 문서는 `src/extensibility/hooks/*`의 **현재 hook 서브시스템 코드**를 설명합니다.\n\n## 런타임 현재 상태\n\nhook 패키지(`src/extensibility/hooks/`)는 여전히 내보내지고 API 표면으로 사용 가능하지만, 기본 CLI 런타임은 이제 **확장 실행기** 경로를 초기화합니다. 현재 시작 흐름에서:\n\n- `--hook`은 `--extension`의 별칭으로 처리됩니다 (CLI 경로는 `additionalExtensionPaths`로 병합됨)\n- 도구는 `HookToolWrapper`가 아닌 `ExtensionToolWrapper`로 래핑됩니다\n- 컨텍스트 변환 및 라이프사이클 이벤트 발생은 `ExtensionRunner`를 통해 처리됩니다\n\n따라서 이 파일은 레거시 동작 및 제약 사항을 포함하여 hook 서브시스템 구현 자체(타입/로더/실행기/래퍼)를 문서화합니다.\n\n## 주요 파일\n\n- `src/extensibility/hooks/types.ts` — hook 컨텍스트, 이벤트 타입, 결과 계약\n- `src/extensibility/hooks/loader.ts` — 모듈 로딩 및 hook 탐색 브릿지\n- `src/extensibility/hooks/runner.ts` — 이벤트 디스패치, 명령 조회, 오류 신호\n- `src/extensibility/hooks/tool-wrapper.ts` — 사전/사후 도구 인터셉션 래퍼\n- `src/extensibility/hooks/index.ts` — 내보내기/재내보내기\n\n## Hook 모듈이란\n\nhook 모듈은 팩토리를 기본 내보내기해야 합니다:\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function hook(pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName === \"bash\" && String(event.input.command ?? \"\").includes(\"rm -rf\")) {\n   return { block: true, reason: \"blocked by policy\" };\n  }\n });\n}\n```\n\n팩토리는 다음을 수행할 수 있습니다:\n\n- `pi.on(...)`으로 이벤트 핸들러 등록\n- `pi.sendMessage(...)`로 영구적인 커스텀 메시지 전송\n- `pi.appendEntry(...)`로 비-LLM 상태 유지\n- `pi.registerCommand(...)`로 슬래시 명령 등록\n- `pi.registerMessageRenderer(...)`로 커스텀 메시지 렌더러 등록\n- `pi.exec(...)`로 셸 명령 실행\n\n## 탐색 및 로딩\n\n`discoverAndLoadHooks(configuredPaths, cwd)`는 다음을 수행합니다:\n\n1. 기능 레지스트리에서 탐색된 hook 로드 (`loadCapability(\"hooks\")`)\n2. 명시적으로 구성된 경로 추가 (절대 경로로 중복 제거)\n3. `loadHooks(allPaths, cwd)` 호출\n\n`loadHooks`는 각 경로를 가져와 `default` 함수를 기대합니다.\n\n### 경로 해석\n\n`loader.ts`는 hook 경로를 다음과 같이 해석합니다:\n\n- 절대 경로: 그대로 사용\n- `~` 경로: 확장됨\n- 상대 경로: `cwd` 기준으로 해석\n\n### 중요한 레거시 불일치\n\n`hookCapability`의 탐색 프로바이더는 여전히 사전/사후 셸 스타일 hook 파일(예: `.claude/hooks/pre/*`, `.xcsh/.../hooks/pre/*`)을 모델링합니다.\n\n여기서 hook 로더는 동적 모듈 임포트를 사용하며 기본 JS/TS hook 팩토리를 요구합니다. 탐색된 hook 경로가 모듈로 임포트할 수 없는 경우, 로드가 실패하고 `LoadHooksResult.errors`에 보고됩니다.\n\n## 이벤트 표면\n\nHook 이벤트는 `types.ts`에서 강타입으로 정의됩니다.\n\n### 세션 이벤트\n\n- `session_start`\n- `session_before_switch` → `{ cancel?: boolean }` 반환 가능\n- `session_switch`\n- `session_before_branch` → `{ cancel?: boolean; skipConversationRestore?: boolean }` 반환 가능\n- `session_branch`\n- `session_before_compact` → `{ cancel?: boolean; compaction?: CompactionResult }` 반환 가능\n- `session.compacting` → `{ context?: string[]; prompt?: string; preserveData?: Record<string, unknown> }` 반환 가능\n- `session_compact`\n- `session_before_tree` → `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }` 반환 가능\n- `session_tree`\n- `session_shutdown`\n\n### 에이전트/컨텍스트 이벤트\n\n- `context` → `{ messages?: Message[] }` 반환 가능\n- `before_agent_start` → `{ message?: { customType; content; display; details } }` 반환 가능\n- `agent_start`\n- `agent_end`\n- `turn_start`\n- `turn_end`\n- `auto_compaction_start`\n- `auto_compaction_end`\n- `auto_retry_start`\n- `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### 도구 이벤트 (사전/사후 모델)\n\n- `tool_call` (실행 전) → `{ block?: boolean; reason?: string }` 반환 가능\n- `tool_result` (실행 후) → `{ content?; details?; isError? }` 반환 가능\n\n이것이 hook 서브시스템의 핵심 사전/사후 인터셉션 모델입니다.\n\n```text\nHook 도구 인터셉션 흐름\n\ntool_call 핸들러\n   │\n   ├─ { block: true } 반환 있음? ── 예 ──> throw (도구 차단됨)\n   │\n   └─ 아니오\n      │\n      ▼\n   하위 도구 실행\n      │\n      ├─ 성공 ──> tool_result 핸들러가 { content, details } 재정의 가능\n      │\n      └─ 오류   ──> tool_result(isError=true) 발생 후 원래 오류 재throw\n```\n\n## 실행 모델 및 변경 의미론\n\n### 1) 사전 실행: `tool_call`\n\n`HookToolWrapper.execute()`는 도구 실행 전에 `tool_call`을 발생시킵니다.\n\n- 어떤 핸들러가 `{ block: true }`를 반환하면 실행이 중단됩니다\n- 핸들러가 throw하면 래퍼는 실패 안전 방식으로 실행을 차단합니다\n- 반환된 `reason`은 throw된 오류 텍스트가 됩니다\n\n### 2) 도구 실행\n\n차단되지 않은 경우 하위 도구가 정상적으로 실행됩니다.\n\n### 3) 사후 실행: `tool_result`\n\n성공 후 래퍼는 다음과 함께 `tool_result`를 발생시킵니다:\n\n- `toolName`, `toolCallId`, `input`\n- `content`\n- `details`\n- `isError: false`\n\n핸들러가 재정의를 반환하면:\n\n- `content`가 결과 내용을 교체할 수 있습니다\n- `details`가 결과 상세 정보를 교체할 수 있습니다\n\n도구 실패 시 래퍼는 `isError: true`와 오류 텍스트 내용으로 `tool_result`를 발생시킨 후 원래 오류를 재throw합니다.\n\n### Hook이 변경할 수 있는 것\n\n- `context`를 통한 단일 호출에 대한 LLM 컨텍스트 (`messages` 교체 체인)\n- 성공한 도구 호출의 도구 출력 내용/상세 정보 (`tool_result` 경로)\n- `before_agent_start`를 통한 사전 에이전트 주입 메시지\n- `session_before_*` 및 `session.compacting`을 통한 취소/커스텀 압축/트리 동작\n\n### 이 구현에서 Hook이 변경할 수 없는 것\n\n- 인플레이스 원시 도구 입력 매개변수 (`tool_call`에서는 차단/허용만 가능)\n- throw된 도구 오류 후 실행 계속 (오류 경로는 재throw)\n- 래퍼 동작에서 최종 성공/오류 상태 (반환된 `isError`는 타입이 지정되어 있으나 `HookToolWrapper`에 의해 적용되지 않음)\n\n## 순서 및 충돌 동작\n\n### 탐색 수준 순서\n\n기능 프로바이더는 우선순위로 정렬됩니다 (높은 것 먼저). 중복 제거는 기능 키 기준으로, 첫 번째가 우선합니다.\n\n`hooks`의 경우 기능 키는 `${type}:${tool}:${name}`입니다. 낮은 우선순위 프로바이더의 중복된 항목은 표시되고 유효한 탐색 목록에서 제외됩니다.\n\n### 로드 순서\n\n`discoverAndLoadHooks`는 해석된 절대 경로로 중복 제거된 평면 `allPaths` 목록을 구성한 다음 `loadHooks`가 해당 순서로 반복합니다.\n각 탐색 디렉토리 내의 파일 순서는 `readdir` 출력에 따라 달라지며, hook 로더는 추가 정렬을 수행하지 않습니다.\n\n### 런타임 핸들러 순서\n\n`HookRunner` 내에서 순서는 등록 순서에 따라 결정적입니다:\n\n1. hook 배열 순서\n2. hook/이벤트별 핸들러 등록 순서\n\n이벤트 타입별 충돌 동작:\n\n- `tool_call`: 핸들러가 차단하지 않는 한 마지막으로 반환된 결과가 우선; 첫 번째 차단이 단락\n- `tool_result`: 마지막으로 반환된 재정의가 우선 (단락 없음)\n- `context`: 체이닝됨; 각 핸들러는 이전 핸들러의 메시지 출력을 받음\n- `before_agent_start`: 첫 번째로 반환된 메시지가 유지됨; 이후 메시지는 무시됨\n- `session_before_*`: 마지막으로 반환된 결과가 추적됨; `cancel: true`가 즉시 단락\n- `session.compacting`: 마지막으로 반환된 결과가 우선\n\n명령/렌더러 충돌:\n\n- `getCommand(name)`은 hook 전체에서 첫 번째 일치를 반환 (먼저 로드된 것이 우선)\n- `getMessageRenderer(customType)`은 첫 번째 일치를 반환\n- `getRegisteredCommands()`는 모든 명령을 반환 (중복 제거 없음)\n\n## UI 상호작용 (`HookContext.ui`)\n\n`HookUIContext`에는 다음이 포함됩니다:\n\n- `select`, `confirm`, `input`, `editor`\n- `notify`\n- `setStatus`\n- `custom`\n- `setEditorText`, `getEditorText`\n- `theme` getter\n\n`ctx.hasUI`는 대화형 UI를 사용할 수 있는지 여부를 나타냅니다.\n\nUI 없이 실행할 때 기본 no-op 컨텍스트 동작은 다음과 같습니다:\n\n- `select/input/editor`는 `undefined`를 반환\n- `confirm`은 `false`를 반환\n- `notify`, `setStatus`, `setEditorText`는 no-op\n- `getEditorText`는 `\"\"`를 반환\n\n### 상태 표시줄 동작\n\n`ctx.ui.setStatus(key, text)`를 통해 설정된 hook 상태 텍스트는:\n\n- 키별로 저장됨\n- 키 이름으로 정렬됨\n- 정제됨 (`\\r`, `\\n`, `\\t` → 공백; 반복되는 공백 축소)\n- 표시를 위해 결합되고 너비 잘림\n\n## 오류 전파 및 폴백\n\n### 로드 시\n\n- 잘못된 모듈 또는 누락된 기본 내보내기 → `LoadHooksResult.errors`에 캡처됨\n- 다른 hook에 대한 로딩은 계속됨\n\n### 이벤트 시\n\n`HookRunner.emit(...)`은 대부분의 이벤트에 대한 핸들러 오류를 포착하고 `HookError`를 수신자(`hookPath`, `event`, `error`)에게 발생시킨 후 계속합니다.\n\n`emitToolCall(...)`은 더 엄격합니다: 핸들러 오류가 삼켜지지 않으며 호출자에게 전파됩니다. `HookToolWrapper`에서 이는 도구 호출을 차단합니다 (실패 안전).\n\n## 현실적인 API 예제\n\n### 안전하지 않은 bash 명령 차단\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName !== \"bash\") return;\n  const cmd = String(event.input.command ?? \"\");\n  if (!cmd.includes(\"rm -rf\")) return;\n\n  if (!ctx.hasUI) return { block: true, reason: \"rm -rf blocked (no UI)\" };\n  const ok = await ctx.ui.confirm(\"Dangerous command\", `Allow: ${cmd}`);\n  if (!ok) return { block: true, reason: \"user denied command\" };\n });\n}\n```\n\n### 실행 후 도구 출력 편집\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_result\", async event => {\n  if (event.toolName !== \"read\" || event.isError) return;\n\n  const redacted = event.content.map(chunk => {\n   if (chunk.type !== \"text\") return chunk;\n   return { ...chunk, text: chunk.text.replaceAll(/API_KEY=\\S+/g, \"API_KEY=[REDACTED]\") };\n  });\n\n  return { content: redacted };\n });\n}\n```\n\n### LLM 호출당 모델 컨텍스트 수정\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"context\", async event => {\n  const filtered = event.messages.filter(msg => !(msg.role === \"custom\" && msg.customType === \"debug-only\"));\n  return { messages: filtered };\n });\n}\n```\n\n### 명령 안전 컨텍스트 메서드로 슬래시 명령 등록\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.registerCommand(\"handoff\", {\n  description: \"Create a new session with setup message\",\n  handler: async (_args, ctx) => {\n   await ctx.waitForIdle();\n   await ctx.newSession({\n    parentSession: ctx.sessionManager.getSessionFile(),\n    setup: async sm => {\n     sm.appendMessage({\n      role: \"user\",\n      content: [{ type: \"text\", text: \"Continue from prior session summary.\" }],\n      timestamp: Date.now(),\n     });\n    },\n   });\n  },\n });\n}\n```\n\n## 내보내기 표면\n\n`src/extensibility/hooks/index.ts`에서 내보냅니다:\n\n- 로딩 API (`discoverAndLoadHooks`, `loadHooks`)\n- 실행기 및 래퍼 (`HookRunner`, `HookToolWrapper`)\n- 모든 hook 타입\n- `execCommand` 재내보내기\n\n패키지 루트(`src/index.ts`)는 레거시 호환성 표면으로 hook **타입**을 재내보냅니다.\n",
	"ko/configuration/porting-from-pi-mono.md": "---\ntitle: 'pi-mono에서 포팅하기: 실용적인 머지 가이드'\ndescription: pi-mono 모노레포에서 xcsh 코드베이스로 코드를 마이그레이션하기 위한 실용적인 가이드입니다.\nsidebar:\n  order: 9\n  label: pi-mono에서 포팅하기\ni18n:\n  sourceHash: fd4e8c09303d\n  translator: machine\n---\n\n# pi-mono에서 포팅하기: 실용적인 머지 가이드\n\n이 가이드는 pi-mono에서 이 저장소로 변경 사항을 포팅하기 위한 반복 가능한 체크리스트입니다.\n단일 파일, 기능 브랜치, 또는 전체 릴리스 동기화 등 모든 머지에 사용하십시오.\n\n## 마지막 동기화 지점\n\n**커밋:** `b21b42d032919de2f2e6920a76fa9a37c3920c0a`\n**날짜:** 2026-03-22\n\n각 동기화 후에 이 섹션을 업데이트하십시오. 이전 범위를 재사용하지 마십시오.\n\n새로운 동기화를 시작할 때, 이 커밋부터 패치를 생성합니다:\n\n```bash\ngit format-patch b21b42d032919de2f2e6920a76fa9a37c3920c0a..HEAD --stdout > changes.patch\n```\n\n## 0) 범위 정의\n\n- 업스트림 참조(커밋, 태그, 또는 PR)를 식별합니다.\n- 수정할 패키지 또는 폴더를 나열합니다.\n- 범위에 포함할 기능과 의도적으로 건너뛸 기능을 결정합니다.\n\n## 1) 코드를 안전하게 가져오기\n\n- 전체 복사보다는 깔끔하고 집중된 diff를 선호합니다.\n- 빌드 결과물이나 생성된 파일은 복사하지 않습니다.\n- 업스트림에서 새 파일을 추가한 경우, 명시적으로 추가하고 내용을 검토합니다.\n\n## 2) import 확장자 규칙 맞추기\n\n대부분의 런타임 TypeScript 소스는 내부 import에서 `.js`를 생략하지만, 일부 테스트/벤치마크 진입점은 ESM\n런타임 호환성을 위해 `.js`를 유지합니다. 로컬 패키지의 기존 스타일을 따르십시오. 확장자를 일괄적으로 제거하지 마십시오.\n\n- `packages/coding-agent` 런타임 소스에서는 비-TS 자산을 import하는 경우가 아니라면 내부 import에서 확장자를 생략합니다.\n- `packages/tui/test`와 `packages/natives/bench`에서는 주변 파일이 이미 사용하는 경우 `.js`를 유지합니다.\n- 도구에서 요구하는 경우 실제 파일 확장자를 유지합니다(예: `.json`, `.css`, `.md` 텍스트 임베드).\n- 예시: `import { x } from \"./foo.js\";` → `import { x } from \"./foo\";` (패키지 규칙이 확장자 생략인 경우에만).\n\n## 3) import 스코프 교체\n\n업스트림은 다른 패키지 스코프를 사용합니다. 일관되게 교체하십시오.\n\n- 이전 스코프를 여기에서 사용하는 로컬 스코프로 교체합니다.\n- 예시 (포팅하는 실제 패키지에 맞게 조정):\n  - `@mariozechner/pi-coding-agent` → `@f5-sales-demo/xcsh`\n  - `@mariozechner/pi-agent-core` → `@f5-sales-demo/pi-agent-core`\n  - `@mariozechner/pi-tui` → `@f5-sales-demo/pi-tui`\n  - `@mariozechner/pi-ai` → `@f5-sales-demo/pi-ai`\n\n## 4) Bun API가 Node보다 나은 경우 사용\n\n우리는 Bun에서 실행합니다. Bun이 더 나은 대안을 제공하는 경우에만 Node API를 교체합니다.\n\n**교체해야 하는 것:**\n\n- 프로세스 스폰: `child_process.spawn` → 간단한 명령에는 Bun Shell `$`, 스트리밍 또는 장시간 실행 작업에는 `Bun.spawn`/`Bun.spawnSync`\n- 파일 I/O: `fs.readFileSync` → `Bun.file().text()` / `Bun.write()`\n- HTTP 클라이언트: `node-fetch`, `axios` → 네이티브 `fetch`\n- 암호화 해싱: `node:crypto` → Web Crypto 또는 `Bun.hash`\n- SQLite: `better-sqlite3` → `bun:sqlite`\n- 환경 변수 로딩: `dotenv` → Bun이 `.env`를 자동으로 로드\n\n**교체하지 말아야 하는 것 (Bun에서 잘 동작):**\n\n- `os.homedir()` — `Bun.env.HOME`, `Bun.env.HOME`, 또는 리터럴 `\"~\"`로 교체하지 마십시오\n- `os.tmpdir()` — `Bun.env.TMPDIR || \"/tmp\"` 또는 하드코딩된 경로로 교체하지 마십시오\n- `fs.mkdtempSync()` — 수동 경로 구성으로 교체하지 마십시오\n- `path.join()`, `path.resolve()` 등 — 그대로 사용해도 됩니다\n\n**Import 스타일:** `node:` 접두사는 네임스페이스 import에서만 사용합니다(`node:fs`나 `node:path`에서 named import를 하지 마십시오).\n\n**추가 Bun 규칙:**\n\n- 짧은 비스트리밍 명령에는 Bun Shell `$`를 선호합니다. 스트리밍 I/O나 프로세스 제어가 필요한 경우에만 `Bun.spawn`을 사용합니다.\n- 파일에는 `Bun.file()`/`Bun.write()`를, 디렉토리에는 `node:fs/promises`를 사용합니다.\n- `Bun.file().exists()` 검사를 피하고, try/catch에서 `isEnoent` 처리를 사용합니다.\n- `setTimeout` 래퍼보다 `Bun.sleep(ms)`를 선호합니다.\n\n**잘못된 예:**\n\n```typescript\n// BROKEN: env vars may be undefined, \"~\" is not expanded\nconst home = Bun.env.HOME || \"~\";\nconst tmp = Bun.env.TMPDIR || \"/tmp\";\n```\n\n**올바른 예:**\n\n```typescript\nimport * as os from \"node:os\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst configDir = path.join(os.homedir(), \".config\", \"myapp\");\nconst tempDir = fs.mkdtempSync(path.join(os.tmpdir(), \"myapp-\"));\n```\n\n## 5) Bun 임베드 선호 (복사 금지)\n\n빌드 시 런타임 자산이나 벤더 파일을 복사하지 마십시오.\n\n- 업스트림이 자산을 dist 폴더에 복사하는 경우, Bun 친화적인 임베드로 교체합니다.\n- 프롬프트는 정적 `.md` 파일입니다. 인라인 프롬프트 문자열 대신 Bun 텍스트 import(`with { type: \"text\" }`)와 Handlebars를 사용합니다.\n- 인접한 비텍스트 리소스를 로드하려면 `import.meta.dir` + `Bun.file`을 사용합니다.\n- 자산을 저장소 내에 유지하고 번들러가 포함하도록 합니다.\n- 사용자가 명시적으로 요청하지 않는 한 복사 스크립트를 제거합니다.\n- 업스트림이 런타임에 번들된 폴백 파일을 읽는 경우, 파일시스템 읽기를 Bun 텍스트 임베드 import로 교체합니다.\n  - 예시 (Codex instructions 폴백):\n    - `const FALLBACK_PROMPT_PATH = join(import.meta.dir, \"codex-instructions.md\");` -> 제거\n    - `import FALLBACK_INSTRUCTIONS from \"./codex-instructions.md\" with { type: \"text\" };`\n    - `readFileSync(FALLBACK_PROMPT_PATH, \"utf8\")` 대신 `return FALLBACK_INSTRUCTIONS;`를 사용\n\n## 6) `package.json` 신중하게 포팅\n\n`package.json`을 계약으로 취급하십시오. 의도적으로 머지합니다.\n\n- 포팅에 변경이 필요하지 않는 한 기존 `name`, `version`, `type`, `exports`, `bin`을 유지합니다.\n- npm/node 스크립트를 Bun 동등물로 교체합니다(예: `bun check`, `bun test`).\n- 의존성이 올바른 스코프를 사용하는지 확인합니다.\n- 타입 오류를 수정하기 위해 의존성을 다운그레이드하지 마십시오. 대신 업그레이드합니다.\n- 워크스페이스 패키지 링크와 `peerDependencies`를 검증합니다.\n\n## 7) 코드 스타일 및 도구 정렬\n\n- 기존 포맷팅 규칙을 유지합니다.\n- 필요하지 않는 한 `any`를 도입하지 않습니다.\n- 동적 import와 인라인 타입 import를 피합니다. 최상위 import만 사용합니다.\n- 코드에서 프롬프트를 구성하지 않습니다. 프롬프트는 Handlebars로 렌더링되는 정적 `.md` 파일입니다.\n- coding-agent에서는 절대 `console.log`/`console.warn`/`console.error`를 사용하지 않습니다. `@f5-sales-demo/pi-utils`의 `logger`를 사용합니다.\n- `new Promise((resolve, reject) => ...)` 대신 `Promise.withResolvers()`를 사용합니다.\n- **클래스 필드나 메서드에 `private`/`protected`/`public` 키워드를 사용하지 않습니다.** 캡슐화에는 ES `#` 프라이빗 필드를 사용하고, 접근 가능한 멤버는 키워드 없이 그대로 둡니다. 유일한 예외는 생성자 매개변수 속성(`constructor(private readonly x: T)`)으로, TypeScript에서 키워드가 필수입니다. `private foo`나 `protected bar`를 사용하는 업스트림 코드를 포팅할 때는 `#foo`(프라이빗) 또는 `bar`(접근 가능)로 변환합니다.\n- 새로운 임시 코드보다 기존 헬퍼와 유틸리티를 선호합니다.\n- 이 저장소에서 이미 수행된 Bun 우선 인프라 변경 사항을 유지합니다:\n  - 런타임은 Bun입니다(Node 진입점 없음).\n  - 패키지 매니저는 Bun입니다(npm lockfile 없음).\n  - 무거운 Node API(`child_process`, `readline`)는 Bun 동등물로 교체되었습니다.\n  - 가벼운 Node API(`os.homedir`, `os.tmpdir`, `fs.mkdtempSync`, `path.*`)는 유지됩니다.\n  - CLI shebang은 `bun`을 사용합니다(`node`나 `tsx`가 아님).\n  - 패키지는 소스 파일을 직접 사용합니다(TypeScript 빌드 단계 없음).\n  - CI 워크플로우는 설치/검사/테스트에 Bun을 실행합니다.\n\n## 8) 이전 호환성 레이어 제거\n\n요청하지 않는 한 업스트림 호환성 심을 제거합니다.\n\n- 교체된 이전 API를 삭제합니다.\n- 모든 호출 지점을 새 API로 직접 업데이트합니다.\n- `*_v2` 또는 병렬 버전을 유지하지 않습니다.\n\n## 9) 문서 및 참조 업데이트\n\n- 적절한 경우 pi-mono 저장소 링크를 교체합니다.\n- 예시를 Bun과 올바른 패키지 스코프를 사용하도록 업데이트합니다.\n- README 지침이 현재 저장소 동작과 여전히 일치하는지 확인합니다.\n\n## 10) 포팅 검증\n\n변경 후 표준 검사를 실행합니다:\n\n- `bun check`\n\n저장소에 이미 변경 사항과 관련 없는 실패한 검사가 있는 경우, 이를 명시합니다.\n테스트는 Bun의 러너를 사용합니다(Vitest가 아님). 명시적으로 요청된 경우에만 `bun test`를 실행합니다.\n\n## 11) 개선된 기능 보호 (회귀 방지 목록)\n\n로컬에서 이미 동작을 개선한 경우, 이를 **양보 불가**로 취급합니다. 포팅 전에\n개선 사항을 기록하고 머지에서 유실되지 않도록 명시적인 검사를 추가합니다.\n\n- **예상 동작 동결**: 각 개선 사항에 대해 짧은 \"이전/이후\" 메모를 추가합니다(입력, 출력,\n  기본값, 엣지 케이스). 이는 무음 롤백을 방지합니다.\n- **이전 → 새 API 매핑**: 업스트림이 개념 이름을 변경한 경우(hooks → extensions, custom tools → tools 등),\n  모든 이전 진입점이 여전히 연결되는지 확인합니다. 하나의 누락된 플래그나 export는 기능 손실을 의미합니다.\n- **export 검증**: `package.json` `exports`, 공개 타입, 배럴 파일을 확인합니다. 업스트림 포팅 시\n  로컬 추가 사항을 다시 export하는 것을 잊는 경우가 많습니다.\n- **비정상 경로 확인**: 오류 처리, 타임아웃, 또는 폴백 로직을 수정한 경우, 해당 경로를 실행하는\n  테스트 또는 최소한 수동 체크리스트를 추가합니다.\n- **기본값 및 설정 머지 순서 확인**: 개선 사항은 종종 기본값에 있습니다. 새 기본값이\n  되돌아가지 않았는지 확인합니다(예: 새로운 설정 우선순위, 비활성화된 기능, 도구 목록).\n- **환경/셸 동작 감사**: 실행 또는 샌드박싱을 수정한 경우, 새 경로가 여전히 정제된\n  환경을 사용하고 별칭/함수 오버라이드를 다시 도입하지 않는지 확인합니다.\n- **대상 샘플 재실행**: 최소한의 \"알려진 양호\" 예시 세트를 유지하고 포팅 후 실행합니다\n  (CLI 플래그, 확장 등록, 도구 실행).\n\n## 12) 재작업된 코드 감지 및 처리\n\n파일을 포팅하기 전에 업스트림이 크게 리팩터링했는지 확인합니다:\n\n```bash\n# Compare the file you're about to port against what you have locally\ngit diff HEAD upstream/main -- path/to/file.ts\n```\n\ndiff에서 파일이 **재작업**되었음을 보여주는 경우 (단순 패치가 아닌):\n\n- 새로운 추상화, 이름 변경된 개념, 병합된 모듈, 변경된 데이터 흐름\n\n그렇다면 포팅하기 전에 **새 구현을 철저히 읽어야** 합니다. 재작업된 코드를 무분별하게 머지하면 다음과 같은 이유로 기능이 유실됩니다:\n\n참고: 인터랙티브 모드는 최근 controllers/utils/types로 분할되었습니다. 관련 변경 사항을 백포팅할 때, 우리가 생성한 개별 파일에 업데이트를 포팅하고 `interactive-mode.ts` 연결이 동기화 상태를 유지하는지 확인하십시오.\n\n1. **기본값이 무음으로 변경됨** - 새 변수 `defaultFoo = [a, b]`가 `[a, b, c, d, e]`를 반환하던 이전 `getAllFoo()`를 교체할 수 있습니다.\n\n2. **API 옵션이 삭제됨** - 시스템이 병합될 때(예: `hooks` + `customTools` → `extensions`), 이전 옵션이 새 구현에 연결되지 않을 수 있습니다.\n\n3. **코드 경로가 오래됨** - 이름이 변경된 개념(예: `hookMessage` → `custom`)은 정의뿐만 아니라 모든 switch 문, 타입 가드, 핸들러에서 업데이트가 필요합니다.\n\n4. **컨텍스트/기능이 축소됨** - 이전 API가 노출하던 `{ logger, typebox, pi }`를 새 API가 포함하지 않을 수 있습니다.\n\n### 의미론적 포팅 프로세스\n\n업스트림이 모듈을 재작업한 경우:\n\n1. **이전 구현 읽기** - 무엇을 했는지, 어떤 옵션을 받았는지, 무엇을 노출했는지 이해합니다.\n\n2. **새 구현 읽기** - 새로운 추상화와 이전 동작에 어떻게 매핑되는지 이해합니다.\n\n3. **기능 동등성 검증** - 이전 코드의 각 기능에 대해, 새 코드가 이를 보존하거나 명시적으로 제거했는지 확인합니다.\n\n4. **누락 검색** - switch 문, 핸들러, UI 컴포넌트에서 누락되었을 수 있는 이전 이름/개념을 검색합니다.\n\n5. **경계 테스트** - CLI 플래그, SDK 옵션, 이벤트 핸들러, 기본값 — 이곳에 회귀가 숨어 있습니다.\n\n### 빠른 검사\n\n```bash\n# Find all uses of an old concept that may need updating\nrg \"oldConceptName\" --type ts\n\n# Compare default values between versions\ngit show upstream/main:path/to/file.ts | rg \"default|DEFAULT\"\n\n# Check if all enum/union values have handlers\nrg \"case \\\"\" path/to/file.ts\n```\n\n## 13) 빠른 감사 체크리스트\n\n완료하기 전 최종 점검으로 사용합니다:\n\n- [ ] Import 확장자가 로컬 패키지 규칙을 따름 (일괄 `.js` 제거 없음)\n- [ ] 새로/포팅된 코드에 Node 전용 API 없음\n- [ ] 모든 패키지 스코프 업데이트 완료\n- [ ] `package.json` 스크립트가 Bun 사용\n- [ ] 프롬프트가 `.md` 텍스트 import임 (인라인 프롬프트 문자열 없음)\n- [ ] coding-agent에 `console.*` 없음 (`logger` 사용)\n- [ ] 자산이 Bun 임베드 패턴으로 로드됨 (복사 스크립트 없음)\n- [ ] 테스트 또는 검사가 실행됨 (또는 차단 상태로 명시적으로 기록됨)\n- [ ] 기능 회귀 없음 (섹션 11-12 참조)\n\n## 14) 커밋 메시지 형식\n\n백포트를 커밋할 때, 저장소 형식 `<type>(scope): <과거형 설명>`을 따르고 제목에 커밋\n범위를 유지합니다.\n\n```\nfix(coding-agent): backported pi-mono changes (<from>..<to>)\n\npackages/<package>:\n- <type>: <description>\n- <type>: <description> (#<issue> by @<contributor>)\n\npackages/<other-package>:\n- <type>: <description>\n```\n\n**예시:**\n\n```\nfix(coding-agent): backported pi-mono changes (9f3eef65f..52532c7c0)\n\npackages/ai:\n- fix: handle \"sensitive\" stop reason from Anthropic API\n- fix: normalize tool call IDs with special characters for Responses API\n- fix: add overflow detection for Bedrock, MiniMax, Kimi providers\n- fix: 429 status is rate limiting, not context overflow\n\npackages/tui:\n- fix: refactored autocomplete state tracking\n- fix: file autocomplete should not trigger on empty text\n- fix: configurable autocomplete max visible items\n- fix: improved table column width calculation with word-aware wrapping\n\npackages/coding-agent:\n- fix: preserve external config.yml edits on save (#1046 by @nicobailonMD)\n- fix: resolve macOS NFD and curly quote variants in file paths\n```\n\n**규칙:**\n\n- 패키지별로 변경 사항을 그룹화\n- 컨벤셔널 커밋 타입 사용 (`fix`, `feat`, `refactor`, `perf`, `docs`)\n- 외부 기여에 대해 업스트림 이슈/PR 번호 및 기여자 귀속 포함\n- 제목의 커밋 범위는 동기화 지점 추적에 도움\n\n## 15) 의도적 차이점\n\n우리 포크에는 업스트림과 다른 아키텍처 결정이 있습니다. **다음 업스트림 패턴을 포팅하지 마십시오:**\n\n### UI 아키텍처\n\n| 업스트림                                    | 우리 포크                                                  | 이유                                                                |\n| ------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------- |\n| `FooterDataProvider` 클래스                  | `StatusLineComponent`                                     | 더 간단하고 통합된 상태 표시줄                                       |\n| `ctx.ui.setHeader()` / `ctx.ui.setFooter()` | 비TUI 모드에서 스텁                                     | TUI에서 구현, 다른 곳에서는 무연산                                   |\n| `ctx.ui.setEditorComponent()`               | 비TUI 모드에서 스텁                                     | TUI에서 구현, 다른 곳에서는 무연산                                   |\n| `InteractiveModeOptions` 옵션 객체     | 위치 기반 생성자 인수 (옵션 타입은 여전히 export됨) | 생성자 시그니처 유지; 업스트림이 필드를 추가하면 타입 업데이트 |\n\n### 컴포넌트 네이밍\n\n| 업스트림                     | 우리 포크                |\n| ---------------------------- | ----------------------- |\n| `extension-input.ts`         | `hook-input.ts`         |\n| `extension-selector.ts`      | `hook-selector.ts`      |\n| `ExtensionInputComponent`    | `HookInputComponent`    |\n| `ExtensionSelectorComponent` | `HookSelectorComponent` |\n\n### API 네이밍\n\n| 업스트림                                 | 우리 포크                                 | 비고                                     |\n| ---------------------------------------- | ---------------------------------------- | ----------------------------------------- |\n| `sessionManager.appendSessionInfo(name)` | `sessionManager.setSessionName(name)`    | 우리는 전체적으로 `sessionName`을 사용           |\n| `sessionManager.getSessionName()`        | `sessionManager.getSessionName()`        | 동일 (업스트림의 RPC에 맞추어 통일) |\n| `agent.sessionName` / `setSessionName()` | `agent.sessionName` / `setSessionName()` | 동일                                      |\n\n### 파일 통합\n\n| 업스트림                                           | 우리 포크                                | 이유                                  |\n| -------------------------------------------------- | --------------------------------------- | --------------------------------------- |\n| `clipboard.ts` + `clipboard-image.ts` (도구 파일) | `@f5-sales-demo/pi-natives` clipboard 모듈 | N-API 네이티브 구현으로 병합 |\n\n### 테스트 프레임워크\n\n| 업스트림                  | 우리 포크                      |\n| ------------------------- | ----------------------------- |\n| `vitest`와 `vi.mock()` | `bun:test`와 bun의 `vi` |\n| `node:test` 어설션    | `expect()` 매처           |\n\n### 도구 아키텍처\n\n| 업스트림                            | 우리 포크                                                          | 비고                                                     |\n| ----------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------- |\n| `createTool(cwd: string, options?)` | `BUILTIN_TOOLS` 레지스트리를 통한 `createTools(session: ToolSession)`  | 도구 팩토리는 `ToolSession`을 받고 `null`을 반환할 수 있음 |\n| 도구별 `*Operations` 인터페이스   | 도구별 인터페이스 유지 (`FindOperations`, `GrepOperations`)   | SSH/원격 오버라이드에 사용                             |\n| 모든 곳에서 Node.js `fs/promises`    | 파일에는 `Bun.file()`/`Bun.write()`; 디렉토리에는 `node:fs/promises` | 간소화될 때 Bun API 선호                        |\n\n### 인증 저장소\n\n| 업스트림                        | 우리 포크                                    | 비고                                        |\n| ------------------------------- | ------------------------------------------- | -------------------------------------------- |\n| `proper-lockfile` + `auth.json` | `agent.db` (bun:sqlite)                     | 자격 증명은 `agent.db`에만 저장 |\n| 공급자당 단일 자격 증명  | 라운드 로빈 선택의 다중 자격 증명 | 세션 친화성 및 백오프 로직 보존 |\n\n### 확장\n\n| 업스트림                      | 우리 포크                                   |\n| ----------------------------- | ------------------------------------------ |\n| TypeScript 로딩을 위한 `jiti` | 네이티브 Bun `import()`                      |\n| `pkg.pi` 매니페스트 필드       | `pkg.xcsh ?? pkg.pi` (우리 네임스페이스 선호) |\n\n### 건너뛸 업스트림 기능\n\n포팅 시 다음 파일/기능을 **완전히 건너뜁니다**:\n\n- `footer-data-provider.ts` — 우리는 StatusLineComponent를 사용\n- `clipboard-image.ts` — 클립보드는 `@f5-sales-demo/pi-natives` N-API 모듈에 있음\n- GitHub 워크플로우 파일 — 우리만의 CI가 있음\n- `models.generated.ts` — 자동 생성됨, 로컬에서 재생성 (models.json으로 대신)\n\n### 우리가 추가한 기능 (보존 필수)\n\n이들은 우리 포크에 존재하지만 업스트림에는 없습니다. **절대 덮어쓰지 마십시오:**\n\n- 인터랙티브 모드의 `StatusLineComponent`\n- 세션 친화성이 있는 다중 자격 증명 인증\n- 기능 기반 디스커버리 시스템 (`defineCapability`, `registerProvider`, `loadCapability`, `skillCapability` 등)\n- MCP/Exa/SSH 통합\n- 저장 시 포맷을 위한 LSP writethrough\n- Bash 가로채기 (`checkBashInterception`)\n- 읽기 도구의 퍼지 경로 제안\n",
	"ko/configuration/rpc.md": "---\ntitle: RPC 프로토콜 레퍼런스\ndescription: xcsh 컴포넌트 간 프로세스 간 통신을 위한 JSON-RPC 프로토콜 레퍼런스.\nsidebar:\n  order: 5\n  label: RPC 프로토콜\ni18n:\n  sourceHash: b4a3ddaf08ab\n  translator: machine\n---\n\n# RPC 프로토콜 레퍼런스\n\nRPC 모드는 코딩 에이전트를 stdio를 통한 개행 구분 JSON 프로토콜로 실행합니다.\n\n- **stdin**: 명령(`RpcCommand`) 및 확장 UI 응답\n- **stdout**: 명령 응답(`RpcResponse`), 세션/에이전트 이벤트, 확장 UI 요청\n\n주요 구현:\n\n- `src/modes/rpc/rpc-mode.ts`\n- `src/modes/rpc/rpc-types.ts`\n- `src/session/agent-session.ts`\n- `packages/agent/src/agent.ts`\n- `packages/agent/src/agent-loop.ts`\n\n## 시작\n\n```bash\nxcsh --mode rpc [regular CLI options]\n```\n\n동작 참고사항:\n\n- `@file` CLI 인수는 RPC 모드에서 거부됩니다.\n- RPC 모드는 추가 모델 호출을 방지하기 위해 기본적으로 자동 세션 제목 생성을 비활성화합니다.\n- RPC 모드는 워크플로우를 변경하는 `todo.*`, `task.*`, `async.*` 설정을 사용자 재정의를 상속하는 대신 내장 기본값으로 재설정합니다.\n- 프로세스는 stdin을 JSONL로 읽습니다(`readJsonl(Bun.stdin.stream())`).\n- stdin이 닫히면 프로세스는 종료 코드 `0`으로 종료됩니다.\n- 응답/이벤트는 한 줄에 하나의 JSON 객체로 기록됩니다.\n\n## 전송 및 프레이밍\n\n각 프레임은 단일 JSON 객체 뒤에 `\\n`이 오는 형태입니다.\n\n객체 형태 자체 외에 추가적인 봉투(envelope)는 없습니다.\n\n### 아웃바운드 프레임 카테고리 (stdout)\n\n1. `RpcResponse` (`{ type: \"response\", ... }`)\n2. `AgentSessionEvent` 객체 (`agent_start`, `message_update` 등)\n3. `RpcExtensionUIRequest` (`{ type: \"extension_ui_request\", ... }`)\n4. 확장 오류 (`{ type: \"extension_error\", extensionPath, event, error }`)\n\n### 인바운드 프레임 카테고리 (stdin)\n\n1. `RpcCommand`\n2. `RpcExtensionUIResponse` (`{ type: \"extension_ui_response\", ... }`)\n\n## 요청/응답 상관관계\n\n모든 명령은 선택적 `id?: string`을 허용합니다.\n\n- 제공된 경우, 일반 명령 응답은 동일한 `id`를 에코합니다.\n- `RpcClient`는 대기 중인 요청 해결을 위해 이에 의존합니다.\n\n런타임의 중요한 엣지 동작:\n\n- 알 수 없는 명령 응답은 `id: undefined`로 전송됩니다(요청에 `id`가 있었더라도).\n- 입력 루프의 파싱/핸들러 예외는 `id: undefined`와 함께 `command: \"parse\"`를 전송합니다.\n- `prompt`와 `abort_and_prompt`는 즉시 성공을 반환한 후, 비동기 프롬프트 스케줄링이 실패하면 **동일한** id로 나중에 오류 응답을 전송할 수 있습니다.\n\n## 명령 스키마 (정규)\n\n`RpcCommand`는 `src/modes/rpc/rpc-types.ts`에 정의되어 있습니다:\n\n### 프롬프팅\n\n- `{ id?, type: \"prompt\", message: string, images?: ImageContent[], streamingBehavior?: \"steer\" | \"followUp\" }`\n- `{ id?, type: \"steer\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"follow_up\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"abort\" }`\n- `{ id?, type: \"abort_and_prompt\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"new_session\", parentSession?: string }`\n\n### 상태\n\n- `{ id?, type: \"get_state\" }`\n- `{ id?, type: \"set_todos\", phases: TodoPhase[] }`\n- `{ id?, type: \"set_host_tools\", tools: RpcHostToolDefinition[] }`\n\n### 모델\n\n- `{ id?, type: \"set_model\", provider: string, modelId: string }`\n- `{ id?, type: \"cycle_model\" }`\n- `{ id?, type: \"get_available_models\" }`\n\n### 사고\n\n- `{ id?, type: \"set_thinking_level\", level: ThinkingLevel }`\n- `{ id?, type: \"cycle_thinking_level\" }`\n\n### 큐 모드\n\n- `{ id?, type: \"set_steering_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_follow_up_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_interrupt_mode\", mode: \"immediate\" | \"wait\" }`\n\n### 압축\n\n- `{ id?, type: \"compact\", customInstructions?: string }`\n- `{ id?, type: \"set_auto_compaction\", enabled: boolean }`\n\n### 재시도\n\n- `{ id?, type: \"set_auto_retry\", enabled: boolean }`\n- `{ id?, type: \"abort_retry\" }`\n\n### Bash\n\n- `{ id?, type: \"bash\", command: string }`\n- `{ id?, type: \"abort_bash\" }`\n\n### 세션\n\n- `{ id?, type: \"get_session_stats\" }`\n- `{ id?, type: \"export_html\", outputPath?: string }`\n- `{ id?, type: \"switch_session\", sessionPath: string }`\n- `{ id?, type: \"branch\", entryId: string }`\n- `{ id?, type: \"get_branch_messages\" }`\n- `{ id?, type: \"get_last_assistant_text\" }`\n- `{ id?, type: \"set_session_name\", name: string }`\n\n### 메시지\n\n- `{ id?, type: \"get_messages\" }`\n\n## 응답 스키마\n\n모든 명령 결과는 `RpcResponse`를 사용합니다:\n\n- 성공: `{ id?, type: \"response\", command: <command>, success: true, data?: ... }`\n- 실패: `{ id?, type: \"response\", command: string, success: false, error: string }`\n\n데이터 페이로드는 명령별로 다르며 `rpc-types.ts`에 정의되어 있습니다.\n\n### `get_state` 페이로드\n\n```json\n{\n  \"model\": { \"provider\": \"...\", \"id\": \"...\" },\n  \"thinkingLevel\": \"off|minimal|low|medium|high|xhigh\",\n  \"isStreaming\": false,\n  \"isCompacting\": false,\n  \"steeringMode\": \"all|one-at-a-time\",\n  \"followUpMode\": \"all|one-at-a-time\",\n  \"interruptMode\": \"immediate|wait\",\n  \"sessionFile\": \"...\",\n  \"sessionId\": \"...\",\n  \"sessionName\": \"...\",\n  \"autoCompactionEnabled\": true,\n  \"messageCount\": 0,\n  \"queuedMessageCount\": 0,\n  \"todoPhases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Todos\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the tool surface\",\n          \"status\": \"in_progress\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n### `set_todos` 페이로드\n\n현재 세션의 인메모리 할 일 상태를 대체하고 정규화된 단계 목록을 반환합니다:\n\n```json\n{\n  \"id\": \"req_2\",\n  \"type\": \"set_todos\",\n  \"phases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Evaluation\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the read tool surface\",\n          \"status\": \"in_progress\"\n        },\n        {\n          \"id\": \"task-2\",\n          \"content\": \"Exercise edit operations\",\n          \"status\": \"pending\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n이는 첫 번째 프롬프트 전에 계획을 사전 설정하려는 호스트에 유용합니다.\n\n### `set_host_tools` 페이로드\n\nRPC 서버가 stdio를 통해 콜백할 수 있는 호스트 소유 도구의 현재 세트를 대체합니다:\n\n```json\n{\n  \"id\": \"req_3\",\n  \"type\": \"set_host_tools\",\n  \"tools\": [\n    {\n      \"name\": \"echo_host\",\n      \"label\": \"Echo Host\",\n      \"description\": \"Echo a value from the embedding host\",\n      \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"message\": { \"type\": \"string\" }\n        },\n        \"required\": [\"message\"],\n        \"additionalProperties\": false\n      }\n    }\n  ]\n}\n```\n\n응답 페이로드는 다음과 같습니다:\n\n```json\n{\n  \"toolNames\": [\"echo_host\"]\n}\n```\n\n이러한 도구는 다음 모델 호출 전에 활성 세션 도구 레지스트리에 추가됩니다. `set_host_tools`를 다시 보내면 이전 호스트 소유 세트가 대체됩니다.\n\n## 이벤트 스트림 스키마\n\nRPC 모드는 `AgentSession.subscribe(...)`로부터 `AgentSessionEvent` 객체를 전달합니다.\n\n일반적인 이벤트 유형:\n\n- `agent_start`, `agent_end`\n- `turn_start`, `turn_end`\n- `message_start`, `message_update`, `message_end`\n- `tool_execution_start`, `tool_execution_update`, `tool_execution_end`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n- `todo_auto_clear`\n\n확장 런너 오류는 별도로 전송됩니다:\n\n```json\n{ \"type\": \"extension_error\", \"extensionPath\": \"...\", \"event\": \"...\", \"error\": \"...\" }\n```\n\n`message_update`는 `assistantMessageEvent`에 스트리밍 델타(텍스트/사고/도구 호출 델타)를 포함합니다.\n\n## 프롬프트/큐 동시성 및 순서\n\n이것이 가장 중요한 운영 동작입니다.\n\n### 즉시 확인 vs 완료\n\n`prompt`와 `abort_and_prompt`는 **즉시 확인됩니다**:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n```\n\n이는 다음을 의미합니다:\n\n- 명령 수락 != 실행 완료\n- 최종 완료는 `agent_end`를 통해 관찰됩니다\n\n### 스트리밍 중\n\n`AgentSession.prompt()`는 활성 스트리밍 중에 `streamingBehavior`를 필요로 합니다:\n\n- `\"steer\"` => 큐에 넣은 조향 메시지 (인터럽트 경로)\n- `\"followUp\"` => 큐에 넣은 후속 메시지 (턴 이후 경로)\n\n스트리밍 중에 생략하면 프롬프트가 실패합니다.\n\n### 큐 기본값\n\n코딩 에이전트 설정 스키마(`packages/coding-agent/src/config/settings-schema.ts`)에서:\n\n- `steeringMode`: `\"one-at-a-time\"`\n- `followUpMode`: `\"one-at-a-time\"`\n- `interruptMode`: `\"wait\"`\n\n### 모드 의미\n\n- `set_steering_mode` / `set_follow_up_mode`\n  - `\"one-at-a-time\"`: 턴당 큐에서 하나의 메시지를 꺼냄\n  - `\"all\"`: 큐 전체를 한 번에 꺼냄\n- `set_interrupt_mode`\n  - `\"immediate\"`: 도구 실행이 도구 호출 사이에 조향을 확인함; 대기 중인 조향이 턴의 나머지 도구 호출을 중단할 수 있음\n  - `\"wait\"`: 턴 완료까지 조향을 지연\n\n## 확장 UI 서브 프로토콜\n\nRPC 모드의 확장은 요청/응답 UI 프레임을 사용합니다.\n\n### 아웃바운드 요청\n\n`RpcExtensionUIRequest` (`type: \"extension_ui_request\"`) 메서드:\n\n- `select`, `confirm`, `input`, `editor`\n- `notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text`\n\n런타임 참고사항:\n\n- 자동 세션 제목 생성은 RPC 모드에서 비활성화되며, 대부분의 호스트에 의미 있는 터미널 제목 표면이 없기 때문에 `setTitle` UI 요청도 기본적으로 억제됩니다. UI 이벤트를 다시 활성화하려면 `PI_RPC_EMIT_TITLE=1`을 설정하세요.\n\n예시:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"123\", \"method\": \"confirm\", \"title\": \"Confirm\", \"message\": \"Continue?\", \"timeout\": 30000 }\n```\n\n### 인바운드 응답\n\n`RpcExtensionUIResponse` (`type: \"extension_ui_response\"`):\n\n- `{ type: \"extension_ui_response\", id: string, value: string }`\n- `{ type: \"extension_ui_response\", id: string, confirmed: boolean }`\n- `{ type: \"extension_ui_response\", id: string, cancelled: true }`\n\n다이얼로그에 타임아웃이 있는 경우, RPC 모드는 타임아웃/중단이 발생하면 기본값으로 해결합니다.\n\n## 호스트 도구 서브 프로토콜\n\nRPC 호스트는 `set_host_tools`를 보낸 후 동일한 전송을 통해 실행 요청을 처리하여 에이전트에 커스텀 도구를 노출할 수 있습니다.\n\n### 아웃바운드 요청\n\n에이전트가 호스트에 해당 도구 중 하나를 실행하도록 요청할 때, RPC 모드는 다음을 전송합니다:\n\n```json\n{\n  \"type\": \"host_tool_call\",\n  \"id\": \"host_1\",\n  \"toolCallId\": \"toolu_123\",\n  \"toolName\": \"echo_host\",\n  \"arguments\": { \"message\": \"hello\" }\n}\n```\n\n도구 실행이 나중에 중단되면, RPC 모드는 다음을 전송합니다:\n\n```json\n{\n  \"type\": \"host_tool_cancel\",\n  \"id\": \"host_cancel_1\",\n  \"targetId\": \"host_1\"\n}\n```\n\n### 인바운드 업데이트 및 완료\n\n호스트는 선택적으로 진행 상황을 스트리밍할 수 있습니다:\n\n```json\n{\n  \"type\": \"host_tool_update\",\n  \"id\": \"host_1\",\n  \"partialResult\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"working\" }]\n  }\n}\n```\n\n완료는 다음을 사용합니다:\n\n```json\n{\n  \"type\": \"host_tool_result\",\n  \"id\": \"host_1\",\n  \"result\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"done\" }]\n  }\n}\n```\n\n반환된 내용을 도구 오류로 표시하려면 `host_tool_result`에 `isError: true`를 설정하세요.\n\n## 오류 모델 및 복구 가능성\n\n### 명령 수준 실패\n\n실패는 문자열 `error`와 함께 `success: false`입니다.\n\n```json\n{ \"id\": \"req_2\", \"type\": \"response\", \"command\": \"set_model\", \"success\": false, \"error\": \"Model not found: provider/model\" }\n```\n\n### 복구 가능성 기대사항\n\n- 대부분의 명령 실패는 복구 가능하며, 프로세스는 활성 상태를 유지합니다.\n- 잘못된 JSONL / 파싱 루프 예외는 `parse` 오류 응답을 전송하고 후속 줄을 계속 읽습니다.\n- 빈 `set_session_name`은 거부됩니다(`Session name cannot be empty`).\n- 알 수 없는 `id`를 가진 확장 UI 응답은 무시됩니다.\n- 프로세스 종료 조건은 stdin 닫힘 또는 확장에 의해 트리거된 명시적 종료입니다.\n\n## 간결한 명령 흐름\n\n### 1) 프롬프트 및 스트리밍\n\nstdin:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"prompt\", \"message\": \"Summarize this repo\" }\n```\n\nstdout 시퀀스 (일반적):\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n{ \"type\": \"agent_start\" }\n{ \"type\": \"message_update\", \"assistantMessageEvent\": { \"type\": \"text_delta\", \"delta\": \"...\" }, \"message\": { \"role\": \"assistant\", \"content\": [] } }\n{ \"type\": \"agent_end\", \"messages\": [] }\n```\n\n### 2) 명시적 큐 정책을 사용한 스트리밍 중 프롬프트\n\nstdin:\n\n```json\n{ \"id\": \"req_2\", \"type\": \"prompt\", \"message\": \"Also include risks\", \"streamingBehavior\": \"followUp\" }\n```\n\n### 3) 큐 동작 검사 및 조정\n\nstdin:\n\n```json\n{ \"id\": \"q1\", \"type\": \"get_state\" }\n{ \"id\": \"q2\", \"type\": \"set_steering_mode\", \"mode\": \"all\" }\n{ \"id\": \"q3\", \"type\": \"set_interrupt_mode\", \"mode\": \"wait\" }\n```\n\n### 4) 확장 UI 왕복\n\nstdout:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"ui_7\", \"method\": \"input\", \"title\": \"Branch name\", \"placeholder\": \"feature/...\" }\n```\n\nstdin:\n\n```json\n{ \"type\": \"extension_ui_response\", \"id\": \"ui_7\", \"value\": \"feature/rpc-host\" }\n```\n\n## `RpcClient` 헬퍼에 대한 참고사항\n\n`src/modes/rpc/rpc-client.ts`는 편의 래퍼이며, 프로토콜 정의가 아닙니다.\n\n현재 헬퍼 특성:\n\n- `bun <cliPath> --mode rpc`를 생성합니다\n- 생성된 `req_<n>` id로 응답을 상관시킵니다\n- 인식된 `AgentEvent` 유형만 리스너에 디스패치합니다\n- `setCustomTools()` 및 `host_tool_call` / `host_tool_cancel`의 자동 처리를 통해 호스트 소유 커스텀 도구를 지원합니다\n- 모든 프로토콜 명령에 대한 헬퍼 메서드를 노출하지는 **않습니다** (예를 들어, `set_interrupt_mode`와 `set_session_name`은 프로토콜 유형에 있지만 전용 메서드로 래핑되지 않음)\n\n완전한 표면 범위가 필요한 경우 원시 프로토콜 프레임을 사용하세요.\n",
	"ko/configuration/sdk.md": "---\ntitle: SDK\ndescription: xcsh 코딩 에이전트 런타임 위에 커스텀 에이전트 및 통합을 구축하기 위한 SDK입니다.\nsidebar:\n  order: 6\n  label: SDK\ni18n:\n  sourceHash: 80f3a4374241\n  translator: machine\n---\n\n# SDK\n\nSDK는 `@f5-sales-demo/xcsh`의 인프로세스 통합 인터페이스입니다.\n에이전트 상태, 이벤트 스트리밍, 도구 연결, 세션 제어에 자체 Bun/Node 프로세스에서 직접 접근하려는 경우에 사용하십시오.\n\n크로스 언어/프로세스 격리가 필요한 경우 RPC 모드를 대신 사용하십시오.\n\n## 설치\n\n```bash\nbun add @f5-sales-demo/xcsh\n```\n\n## 진입점\n\n`@f5-sales-demo/xcsh`는 패키지 루트(및 `@f5-sales-demo/xcsh/sdk`를 통해서도)에서 SDK API를 내보냅니다.\n\n임베더를 위한 핵심 내보내기:\n\n- `createAgentSession`\n- `SessionManager`\n- `Settings`\n- `AuthStorage`\n- `ModelRegistry`\n- `discoverAuthStorage`\n- 디스커버리 헬퍼 (`discoverExtensions`, `discoverSkills`, `discoverContextFiles`, `discoverPromptTemplates`, `discoverSlashCommands`, `discoverCustomTSCommands`, `discoverMCPServers`)\n- 도구 팩토리 인터페이스 (`createTools`, `BUILTIN_TOOLS`, 도구 클래스)\n\n## 빠른 시작 (자동 디스커버리 기본값)\n\n```ts\nimport { createAgentSession } from \"@f5-sales-demo/xcsh\";\n\nconst { session, modelFallbackMessage } = await createAgentSession();\n\nif (modelFallbackMessage) {\n process.stderr.write(`${modelFallbackMessage}\\n`);\n}\n\nconst unsubscribe = session.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Summarize this repository in 3 bullets.\");\nunsubscribe();\nawait session.dispose();\n```\n\n## `createAgentSession()`이 기본으로 디스커버리하는 항목\n\n`createAgentSession()`은 \"제공하면 재정의, 생략하면 디스커버리\" 원칙을 따릅니다.\n\n생략된 경우 다음을 해결합니다:\n\n- `cwd`: `getProjectDir()`\n- `agentDir`: `~/.xcsh/agent` (`getAgentDir()` 경유)\n- `authStorage`: `discoverAuthStorage(agentDir)`\n- `modelRegistry`: `new ModelRegistry(authStorage)` + `await refresh()`\n- `settings`: `await Settings.init({ cwd, agentDir })`\n- `sessionManager`: `SessionManager.create(cwd)` (파일 백업)\n- 스킬/컨텍스트 파일/프롬프트 템플릿/슬래시 명령어/익스텐션/커스텀 TS 명령어\n- `createTools(...)`를 통한 내장 도구\n- MCP 도구 (기본적으로 활성화)\n- LSP 통합 (기본적으로 활성화)\n\n### 필수 vs 선택적 입력\n\n일반적으로 제어하려는 항목만 제공하면 됩니다:\n\n- **반드시 제공**: 최소 세션에는 아무것도 필요 없음\n- **임베더에서 일반적으로 명시적으로 제공**:\n    - `sessionManager` (인메모리 또는 커스텀 위치가 필요한 경우)\n    - `authStorage` + `modelRegistry` (자격증명/모델 라이프사이클을 직접 관리하는 경우)\n    - `model` 또는 `modelPattern` (결정론적 모델 선택이 중요한 경우)\n    - `settings` (격리/테스트 설정이 필요한 경우)\n\n## 세션 매니저 동작 (지속적 vs 인메모리)\n\n`AgentSession`은 항상 `SessionManager`를 사용하며, 사용하는 팩토리에 따라 동작이 달라집니다.\n\n### 파일 백업 (기본값)\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.create(process.cwd()),\n});\n\nconsole.log(session.sessionFile); // absolute .jsonl path\n```\n\n- 대화/메시지/상태 델타를 세션 파일에 지속적으로 저장합니다.\n- 재개/열기/목록/포크 워크플로우를 지원합니다.\n- `session.sessionFile`이 정의됩니다.\n\n### 인메모리\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.inMemory(),\n});\n\nconsole.log(session.sessionFile); // undefined\n```\n\n- 파일 시스템 지속성이 없습니다.\n- 테스트, 임시 워커, 요청 범위 에이전트에 유용합니다.\n- 세션 메서드는 여전히 동작하지만, 지속성 관련 동작(파일 재개/포크 경로)은 자연스럽게 제한됩니다.\n\n### 재개/열기/목록 헬퍼\n\n```ts\nimport { SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst recent = await SessionManager.continueRecent(process.cwd());\nconst listed = await SessionManager.list(process.cwd());\nconst opened = listed[0] ? await SessionManager.open(listed[0].path) : null;\n```\n\n## 모델 및 인증 연결\n\n`createAgentSession()`은 모델 선택 및 API 키 해결을 위해 `ModelRegistry` + `AuthStorage`를 사용합니다.\n\n### 명시적 연결\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst available = modelRegistry.getAvailable();\nif (available.length === 0) throw new Error(\"No authenticated models available\");\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n model: available[0],\n thinkingLevel: \"medium\",\n sessionManager: SessionManager.inMemory(),\n});\n```\n\n### `model` 생략 시 선택 순서\n\n명시적인 `model`/`modelPattern`이 제공되지 않은 경우:\n\n1. 기존 세션에서 모델 복원 (복원 가능 + 키 사용 가능한 경우)\n2. 설정 기본 모델 역할 (`default`)\n3. 유효한 인증이 있는 첫 번째 사용 가능한 모델\n\n복원에 실패하면 `modelFallbackMessage`가 대체 이유를 설명합니다.\n\n### 인증 우선순위\n\n`AuthStorage.getApiKey(...)`는 다음 순서로 해결합니다:\n\n1. 런타임 재정의 (`setRuntimeApiKey`)\n2. `agent.db`에 저장된 자격증명\n3. 공급자 환경 변수\n4. 커스텀 공급자 리졸버 폴백 (설정된 경우)\n\n## 이벤트 구독 모델\n\n`session.subscribe(listener)`로 구독하면 구독 해제 함수가 반환됩니다.\n\n```ts\nconst unsubscribe = session.subscribe(event => {\n switch (event.type) {\n  case \"agent_start\":\n  case \"turn_start\":\n  case \"tool_execution_start\":\n   break;\n  case \"message_update\":\n   if (event.assistantMessageEvent.type === \"text_delta\") {\n    process.stdout.write(event.assistantMessageEvent.delta);\n   }\n   break;\n }\n});\n```\n\n`AgentSessionEvent`는 핵심 `AgentEvent`와 세션 레벨 이벤트를 포함합니다:\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n## 프롬프트 라이프사이클\n\n`session.prompt(text, options?)`가 기본 진입점입니다.\n\n동작:\n\n1. 선택적 명령어/템플릿 확장 (`/` 명령어, 커스텀 명령어, 파일 슬래시 명령어, 프롬프트 템플릿)\n2. 현재 스트리밍 중인 경우:\n    - `streamingBehavior: \"steer\" | \"followUp\"` 필요\n    - 작업을 버리지 않고 큐에 추가\n3. 유휴 상태인 경우:\n    - 모델 + API 키 유효성 검사\n    - 사용자 메시지 추가\n    - 에이전트 턴 시작\n\n관련 API:\n\n- `sendUserMessage(content, { deliverAs? })`\n- `steer(text, images?)`\n- `followUp(text, images?)`\n- `sendCustomMessage({ customType, content, ... }, { deliverAs?, triggerTurn? })`\n- `abort()`\n\n## 도구 및 익스텐션 통합\n\n### 내장 도구 및 필터링\n\n- 내장 도구는 `createTools(...)`와 `BUILTIN_TOOLS`에서 제공됩니다.\n- `toolNames`는 내장 도구의 허용 목록 역할을 합니다.\n- `customTools` 및 익스텐션 등록 도구는 여전히 포함됩니다.\n- 숨겨진 도구(예: `submit_result`)는 옵션에서 요구하지 않는 한 옵트인 방식입니다.\n\n```ts\nconst { session } = await createAgentSession({\n toolNames: [\"read\", \"grep\", \"find\", \"write\"],\n requireSubmitResultTool: true,\n});\n```\n\n### 익스텐션\n\n- `extensions`: 인라인 `ExtensionFactory[]`\n- `additionalExtensionPaths`: 추가 익스텐션 파일 로드\n- `disableExtensionDiscovery`: 자동 익스텐션 스캔 비활성화\n- `preloadedExtensions`: 이미 로드된 익스텐션 세트 재사용\n\n### 런타임 도구 세트 변경\n\n`AgentSession`은 런타임 활성화 업데이트를 지원합니다:\n\n- `getActiveToolNames()`\n- `getAllToolNames()`\n- `setActiveToolsByName(names)`\n- `refreshMCPTools(mcpTools)`\n\n활성 도구 변경 사항을 반영하기 위해 시스템 프롬프트가 재구성됩니다.\n\n## 디스커버리 헬퍼\n\n내부 디스커버리 로직을 재구현하지 않고 부분적인 제어가 필요할 때 사용하십시오:\n\n- `discoverAuthStorage(agentDir?)`\n- `discoverExtensions(cwd?)`\n- `discoverSkills(cwd?, _agentDir?, settings?)`\n- `discoverContextFiles(cwd?, _agentDir?)`\n- `discoverPromptTemplates(cwd?, agentDir?)`\n- `discoverSlashCommands(cwd?)`\n- `discoverCustomTSCommands(cwd?, agentDir?)`\n- `discoverMCPServers(cwd?)`\n- `buildSystemPrompt(options?)`\n\n## 서브에이전트 지향 옵션\n\n오케스트레이터를 구축하는 SDK 소비자를 위한 옵션입니다 (태스크 실행기 흐름과 유사):\n\n- `outputSchema`: 도구 컨텍스트에 구조화된 출력 기대값을 전달\n- `requireSubmitResultTool`: `submit_result` 도구 포함 강제\n- `taskDepth`: 중첩 태스크 세션의 재귀 깊이 컨텍스트\n- `parentTaskPrefix`: 중첩 태스크 출력을 위한 아티팩트 명명 접두사\n\n이 옵션들은 일반적인 단일 에이전트 임베딩에서는 선택 사항입니다.\n\n## `createAgentSession()` 반환값\n\n```ts\ntype CreateAgentSessionResult = {\n session: AgentSession;\n extensionsResult: LoadExtensionsResult;\n setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;\n mcpManager?: MCPManager;\n modelFallbackMessage?: string;\n lspServers?: Array<{ name: string; status: \"ready\" | \"error\"; fileTypes: string[]; error?: string }>;\n};\n```\n\n임베더가 도구/익스텐션에서 호출해야 하는 UI 기능을 제공하는 경우에만 `setToolUIContext(...)`를 사용하십시오.\n\n## 최소 제어 임베드 예제\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n Settings,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst settings = Settings.isolated({\n \"compaction.enabled\": true,\n \"retry.enabled\": true,\n});\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n settings,\n sessionManager: SessionManager.inMemory(),\n toolNames: [\"read\", \"grep\", \"find\", \"edit\", \"write\"],\n enableMCP: false,\n enableLsp: true,\n});\n\nsession.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Find all TODO comments in this repo and propose fixes.\");\nawait session.dispose();\n```\n",
	"ko/configuration/secrets.md": "---\ntitle: 시크릿 난독화\ndescription: 세션 로그 및 출력에서 민감한 값을 삭제하는 시크릿 난독화 파이프라인.\nsidebar:\n  order: 3\n  label: 시크릿\ni18n:\n  sourceHash: 1d9dc101c614\n  translator: machine\n---\n\n# 시크릿 난독화\n\n민감한 값(API 키, 토큰, 비밀번호)이 LLM 제공자에게 전송되는 것을 방지합니다. 활성화되면, 프로세스를 떠나기 전에 시크릿이 결정론적 플레이스홀더로 교체되고, 모델이 반환한 도구 호출 인수에서 복원됩니다.\n\n## 활성화\n\n기본적으로 활성화되어 있습니다. `/settings` UI를 통해 토글하거나 `config.yml`에서 직접 설정합니다:\n\n```yaml\nsecrets:\n  enabled: false\n```\n\n## 작동 방식\n\n1. 세션 시작 시, 두 가지 소스에서 시크릿이 수집됩니다:\n   - 값의 길이가 8자 이상인 일반적인 시크릿 패턴(`*_KEY`, `*_SECRET`, `*_TOKEN`, `*_PASSWORD` 등)과 일치하는 **환경 변수**\n   - **`secrets.yml` 파일** (아래 참조)\n\n2. LLM으로 전송되는 아웃바운드 메시지에서 모든 시크릿 값은 `<<$env:S0>>`, `<<$env:S1>>` 등과 같은 플레이스홀더로 교체됩니다.\n\n3. 모델이 반환한 도구 호출 인수는 실행 전에 깊이 탐색되어 플레이스홀더가 원래 값으로 복원됩니다.\n\n두 가지 모드가 각 시크릿에 대한 처리 방식을 제어합니다:\n\n| 모드 | 동작 | 복원 가능 여부 |\n|---|---|---|\n| `obfuscate` (기본값) | 인덱스 플레이스홀더 `<<$env:SN>>`으로 교체 | 예 (도구 인수에서 역난독화) |\n| `replace` | 결정론적 동일 길이 문자열로 교체 | 아니요 (단방향) |\n\n## secrets.yml\n\nYAML에서 사용자 정의 시크릿 항목을 정의합니다. 두 위치가 확인됩니다:\n\n| 수준 | 경로 | 목적 |\n|---|---|---|\n| 전역 | `~/.xcsh/agent/secrets.yml` | 모든 프로젝트에 걸친 시크릿 |\n| 프로젝트 | `<cwd>/.xcsh/secrets.yml` | 프로젝트별 시크릿 |\n\n프로젝트 항목은 일치하는 `content`를 가진 전역 항목을 재정의합니다.\n\n### 스키마\n\n배열의 각 항목은 다음 필드를 가집니다:\n\n| 필드 | 타입 | 필수 여부 | 설명 |\n|---|---|---|---|\n| `type` | `\"plain\"` 또는 `\"regex\"` | 예 | 매칭 전략 |\n| `content` | string | 예 | 시크릿 값(plain) 또는 정규식 패턴(regex) |\n| `mode` | `\"obfuscate\"` 또는 `\"replace\"` | 아니요 | 기본값: `\"obfuscate\"` |\n| `replacement` | string | 아니요 | 사용자 정의 교체 문자열 (replace 모드 전용) |\n| `flags` | string | 아니요 | 정규식 플래그 (regex 타입 전용) |\n\n### 예시\n\n#### 평문 시크릿\n\n```yaml\n# 특정 API 키 난독화 (기본 모드)\n- type: plain\n  content: sk-proj-abc123def456\n\n# 데이터베이스 비밀번호를 고정 문자열로 교체\n- type: plain\n  content: hunter2\n  mode: replace\n  replacement: \"********\"\n```\n\n#### 정규식 시크릿\n\n```yaml\n# AWS 스타일 키 난독화\n- type: regex\n  content: \"AKIA[0-9A-Z]{16}\"\n\n# 명시적 플래그를 사용한 대소문자 무시 매칭\n- type: regex\n  content: \"api[_-]?key\\\\s*=\\\\s*\\\\w+\"\n  flags: \"i\"\n\n# 정규식 리터럴 구문 (패턴과 플래그를 하나의 문자열로)\n- type: regex\n  content: \"/bearer\\\\s+[a-zA-Z0-9._~+\\\\/=-]+/i\"\n```\n\n정규식 항목은 항상 전역적으로 스캔됩니다(`g` 플래그가 자동으로 적용됨). 정규식 리터럴 구문 `/pattern/flags`는 별도의 `content` + `flags` 필드의 대안으로 지원됩니다. 패턴 내의 이스케이프된 슬래시(`\\\\/`)는 올바르게 처리됩니다.\n\n#### 정규식을 사용한 replace 모드\n\n```yaml\n# 연결 문자열 단방향 교체 (복원 불가)\n- type: regex\n  content: \"postgres://[^\\\\s]+\"\n  mode: replace\n  replacement: \"postgres://***\"\n```\n\n## 환경 변수 감지와의 상호작용\n\n환경 변수는 항상 먼저 수집됩니다. 파일에 정의된 항목은 이후에 추가되므로, 파일 항목은 환경 변수에 없는 시크릿(설정 파일, 하드코딩된 값 등)을 처리할 수 있습니다. 동일한 값이 두 곳에 모두 존재하는 경우, 파일 항목의 모드가 우선합니다.\n\n## 주요 파일\n\n- `src/secrets/index.ts` -- 로딩, 병합, 환경 변수 수집\n- `src/secrets/obfuscator.ts` -- `SecretObfuscator` 클래스, 플레이스홀더 생성, 메시지 난독화\n- `src/secrets/regex.ts` -- 정규식 리터럴 파싱 및 컴파일\n- `src/config/settings-schema.ts` -- `secrets.enabled` 설정 정의\n",
	"ko/extensions/extension-loading.md": "---\ntitle: 확장 기능 로딩 (TypeScript/JavaScript 모듈)\ndescription: '해석, 유효성 검증, 캐싱을 포함한 확장 기능용 TypeScript 및 JavaScript 모듈 로딩 파이프라인.'\nsidebar:\n  order: 2\n  label: 확장 기능 로딩\ni18n:\n  sourceHash: a8cea231c660\n  translator: machine\n---\n\n# 확장 기능 로딩 (TypeScript/JavaScript 모듈)\n\n이 문서는 코딩 에이전트가 시작 시 **확장 모듈**(`.ts`/`.js`)을 검색하고 로드하는 방법을 다룹니다.\n\n`gemini-extension.json` 매니페스트 확장은 다루지 **않으며**, 별도로 문서화되어 있습니다.\n\n## 이 하위 시스템의 역할\n\n확장 기능 로딩은 모듈 진입 파일 목록을 구성하고, Bun을 사용하여 각 모듈을 가져오고, 팩토리를 실행한 후 다음을 반환합니다:\n\n- 로드된 확장 기능 정의\n- 경로별 로드 오류 (전체 로드를 중단하지 않음)\n- 이후 `ExtensionRunner`에서 사용하는 공유 확장 런타임 객체\n\n## 주요 구현 파일\n\n- `src/extensibility/extensions/loader.ts` — 경로 검색 + 가져오기/실행\n- `src/extensibility/extensions/index.ts` — 공개 내보내기\n- `src/extensibility/extensions/runner.ts` — 로드 후 런타임/이벤트 실행\n- `src/discovery/builtin.ts` — 확장 모듈용 네이티브 자동 검색 프로바이더\n- `src/config/settings.ts` — 병합된 `extensions` / `disabledExtensions` 설정 로드\n\n---\n\n## 확장 기능 로딩 입력\n\n### 1) 자동 검색된 네이티브 확장 모듈\n\n`discoverAndLoadExtensions()`는 먼저 검색 프로바이더에 `extension-module` 기능 항목을 요청한 다음, 프로바이더 `native` 항목만 유지합니다.\n\n유효 네이티브 위치:\n\n- 프로젝트: `<cwd>/.xcsh/extensions`\n- 사용자: `~/.xcsh/agent/extensions`\n\n경로 루트는 네이티브 프로바이더(`SOURCE_PATHS.native`)에서 가져옵니다.\n\n참고:\n\n- 네이티브 자동 검색은 현재 `.xcsh` 기반입니다.\n- 레거시 `.pi`는 `package.json` 매니페스트 키(`pi.extensions`)에서 여전히 허용되지만, 여기서 네이티브 루트로는 사용되지 않습니다.\n\n### 2) 명시적으로 구성된 경로\n\n자동 검색 후, 구성된 경로가 추가되고 해석됩니다.\n\n주요 세션 시작 경로(`sdk.ts`)에서의 구성된 경로 소스:\n\n1. CLI 제공 경로 (`--extension/-e`, `--hook`도 확장 경로로 처리됨)\n2. 설정의 `extensions` 배열 (전역 + 프로젝트 설정 병합)\n\n전역 설정 파일:\n\n- `~/.xcsh/agent/config.yml` (또는 `PI_CODING_AGENT_DIR`을 통한 사용자 정의 에이전트 디렉터리)\n\n프로젝트 설정 파일:\n\n- `<cwd>/.xcsh/settings.json`\n\n예시:\n\n```yaml\n# ~/.xcsh/agent/config.yml\nextensions:\n  - ~/my-exts/safety.ts\n  - ./local/ext-pack\n```\n\n```json\n{\n  \"extensions\": [\"./.xcsh/extensions/my-extra\"]\n}\n```\n\n---\n\n## 활성화/비활성화 제어\n\n### 검색 비활성화\n\n- CLI: `--no-extensions`\n- SDK 옵션: `disableExtensionDiscovery`\n\n동작 분기:\n\n- SDK: `disableExtensionDiscovery=true`일 때에도 `loadExtensions()`를 통해 `additionalExtensionPaths`는 여전히 로드합니다.\n- CLI 경로 구성(`main.ts`)은 현재 `--no-extensions`가 설정되면 CLI 확장 경로를 초기화하므로, 해당 모드에서는 명시적 `-e/--hook`이 전달되지 않습니다.\n\n### 특정 확장 모듈 비활성화\n\n`disabledExtensions` 설정은 확장 ID 형식으로 필터링합니다:\n\n- `extension-module:<derivedName>`\n\n`derivedName`은 진입 경로를 기반으로 합니다(`getExtensionNameFromPath`). 예시:\n\n- `/x/foo.ts` -> `foo`\n- `/x/bar/index.ts` -> `bar`\n\n예시:\n\n```yaml\ndisabledExtensions:\n  - extension-module:foo\n```\n\n---\n\n## 경로 및 진입점 해석\n\n### 경로 정규화\n\n구성된 경로의 경우:\n\n1. 유니코드 공백 정규화\n2. `~` 확장\n3. 상대 경로인 경우, 현재 `cwd` 기준으로 해석\n\n### 구성된 경로가 파일인 경우\n\n모듈 진입 후보로 직접 사용됩니다.\n\n### 구성된 경로가 디렉터리인 경우\n\n해석 순서:\n\n1. 해당 디렉터리의 `package.json`에 `xcsh.extensions` (또는 레거시 `pi.extensions`)가 있는 경우 -> 선언된 진입점 사용\n2. `index.ts`\n3. `index.js`\n4. 그 외에는 한 단계 레벨에서 확장 진입점을 스캔:\n   - 직접 `*.ts` / `*.js`\n   - 하위 디렉터리 `index.ts` / `index.js`\n   - 하위 디렉터리 `package.json`에 `xcsh.extensions` / `pi.extensions`\n\n규칙 및 제약:\n\n- 하위 디렉터리 한 단계를 넘어서는 재귀적 검색 없음\n- 선언된 `extensions` 매니페스트 진입점은 해당 패키지 디렉터리 기준으로 해석됨\n- 선언된 진입점은 파일이 존재하고 접근이 허용된 경우에만 포함됨\n- `*/index.{ts,js}` 쌍에서는 TypeScript가 JavaScript보다 우선됨\n- 심볼릭 링크는 적격 파일/디렉터리로 처리됨\n\n### 무시 동작은 소스에 따라 다름\n\n- 네이티브 자동 검색(검색 헬퍼의 `discoverExtensionModulePaths`)은 `gitignore: true` 및 `hidden: false`로 네이티브 glob를 사용합니다.\n- `loader.ts`의 명시적 구성 디렉터리 스캔은 `readdir` 규칙을 사용하며, gitignore 필터링을 적용하지 **않습니다**.\n\n---\n\n## 로드 순서 및 우선순위\n\n`discoverAndLoadExtensions()`는 하나의 정렬된 목록을 구성한 후 `loadExtensions()`를 호출합니다.\n\n순서:\n\n1. 네이티브 자동 검색된 모듈\n2. 명시적으로 구성된 경로 (제공된 순서대로)\n\n`sdk.ts`에서 구성 순서는:\n\n1. CLI 추가 경로\n2. 설정의 `extensions`\n\n중복 제거:\n\n- 절대 경로 기반\n- 먼저 발견된 경로가 우선\n- 이후 중복은 무시됨\n\n의미: 동일한 모듈 경로가 자동 검색과 명시적 구성 모두에 존재하는 경우, 첫 번째 위치(자동 검색 단계)에서 한 번만 로드됩니다.\n\n---\n\n## 모듈 가져오기 및 팩토리 계약\n\n각 후보 경로는 동적 가져오기로 로드됩니다:\n\n- `await import(resolvedPath)`\n- 팩토리는 `module.default ?? module`\n- 팩토리는 함수여야 합니다 (`ExtensionFactory`)\n\n내보내기가 함수가 아닌 경우, 해당 경로는 구조화된 오류와 함께 실패하며 로딩은 계속됩니다.\n\n---\n\n## 실패 처리 및 격리\n\n### 로딩 중\n\n확장 경로별로 실패는 `{ path, error }`로 캡처되며, 다른 경로의 로딩을 중단하지 않습니다.\n\n일반적인 경우:\n\n- 가져오기 실패 / 파일 누락\n- 잘못된 팩토리 내보내기 (함수가 아닌 경우)\n- 팩토리 실행 중 예외 발생\n\n### 런타임 격리 모델\n\n- 확장 기능은 **샌드박스가 아닙니다** (동일한 프로세스/런타임).\n- 하나의 `EventBus`와 하나의 `ExtensionRuntime` 인스턴스를 공유합니다.\n- 로드 중에는 런타임 액션 메서드가 의도적으로 `ExtensionRuntimeNotInitializedError`를 발생시키며, 액션 연결은 이후 `ExtensionRunner.initialize()`에서 수행됩니다.\n\n### 로딩 후\n\n`ExtensionRunner`를 통해 이벤트가 실행될 때, 핸들러 예외는 포착되어 러너 루프를 중단시키는 대신 확장 오류로 발행됩니다.\n\n---\n\n## 최소 사용자/프로젝트 레이아웃 예시\n\n### 사용자 수준\n\n```text\n~/.xcsh/agent/\n  config.yml\n  extensions/\n    guardrails.ts\n    audit/\n      index.ts\n```\n\n### 프로젝트 수준\n\n```text\n<repo>/\n  .xcsh/\n    settings.json\n    extensions/\n      checks/\n        package.json\n      lint-gates.ts\n```\n\n`checks/package.json`:\n\n```json\n{\n  \"xcsh\": {\n    \"extensions\": [\"./src/check-a.ts\", \"./src/check-b.js\"]\n  }\n}\n```\n\n여전히 허용되는 레거시 매니페스트 키:\n\n```json\n{\n  \"pi\": {\n    \"extensions\": [\"./index.ts\"]\n  }\n}\n```\n",
	"ko/extensions/extensions.md": "---\ntitle: 확장 기능\ndescription: '유형, 러너 수명 주기, 등록 및 검색을 포함한 확장 기능 런타임 개요'\nsidebar:\n  order: 1\n  label: 개요\ni18n:\n  sourceHash: 14cc16dbd98b\n  translator: machine\n---\n\n# 확장 기능\n\n`packages/coding-agent`에서 런타임 확장 기능을 작성하기 위한 기본 가이드입니다.\n\n이 문서는 다음 파일의 현재 확장 기능 런타임을 다룹니다:\n\n- `src/extensibility/extensions/types.ts`\n- `src/extensibility/extensions/runner.ts`\n- `src/extensibility/extensions/wrapper.ts`\n- `src/extensibility/extensions/index.ts`\n- `src/modes/controllers/extension-ui-controller.ts`\n\n검색 경로 및 파일시스템 로딩 규칙에 대해서는 `docs/extension-loading.md`를 참조하십시오.\n\n## 확장 기능이란\n\n확장 기능은 기본 팩토리를 내보내는 TS/JS 모듈입니다:\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nexport default function myExtension(pi: ExtensionAPI) {\n // register handlers/tools/commands/renderers\n}\n```\n\n확장 기능은 하나의 모듈에서 다음 모든 기능을 조합할 수 있습니다:\n\n- 이벤트 핸들러 (`pi.on(...)`)\n- LLM 호출 가능 도구 (`pi.registerTool(...)`)\n- 슬래시 명령어 (`pi.registerCommand(...)`)\n- 키보드 단축키 및 플래그\n- 커스텀 메시지 렌더링\n- 세션/메시지 주입 API (`sendMessage`, `sendUserMessage`, `appendEntry`)\n\n## 런타임 모델\n\n1. 확장 기능이 임포트되고 팩토리 함수가 실행됩니다.\n2. 로드 단계에서 등록 메서드는 유효하지만, 런타임 액션 메서드는 아직 초기화되지 않습니다.\n3. `ExtensionRunner.initialize(...)`가 활성 모드에 대한 라이브 액션/컨텍스트를 연결합니다.\n4. 세션/에이전트/도구 수명 주기 이벤트가 핸들러에 전달됩니다.\n5. 모든 도구 실행은 확장 기능 인터셉션으로 래핑됩니다 (`tool_call` / `tool_result`).\n\n```text\nExtension lifecycle (simplified)\n\nload paths\n   │\n   ▼\nimport module + run factory (registration only)\n   │\n   ▼\nExtensionRunner.initialize(mode/session/tool registry)\n   │\n   ├─ emit session/agent events to handlers\n   ├─ wrap tool execution (tool_call/tool_result)\n   └─ expose runtime actions (sendMessage, setActiveTools, ...)\n```\n\n`loader.ts`의 중요한 제약 사항:\n\n- 확장 기능 로드 중 `pi.sendMessage()`와 같은 액션 메서드를 호출하면 `ExtensionRuntimeNotInitializedError`가 발생합니다.\n- 먼저 등록한 후, 이벤트/명령어/도구에서 런타임 동작을 수행하십시오.\n\n## 빠른 시작\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\nimport { Type } from \"@sinclair/typebox\";\n\nexport default function (pi: ExtensionAPI) {\n pi.setLabel(\"Safety + Utilities\");\n\n pi.on(\"session_start\", async (_event, ctx) => {\n  ctx.ui.notify(`Extension loaded in ${ctx.cwd}`, \"info\");\n });\n\n pi.on(\"tool_call\", async (event) => {\n  if (event.toolName === \"bash\" && event.input.command?.includes(\"rm -rf\")) {\n   return { block: true, reason: \"Blocked by extension policy\" };\n  }\n });\n\n pi.registerTool({\n  name: \"hello_extension\",\n  label: \"Hello Extension\",\n  description: \"Return a greeting\",\n  parameters: Type.Object({ name: Type.String() }),\n  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {\n   return {\n    content: [{ type: \"text\", text: `Hello, ${params.name}` }],\n    details: { greeted: params.name },\n   };\n  },\n });\n\n pi.registerCommand(\"hello-ext\", {\n  description: \"Show queue state\",\n  handler: async (_args, ctx) => {\n   ctx.ui.notify(`pending=${ctx.hasPendingMessages()}`, \"info\");\n  },\n });\n}\n```\n\n## 확장 기능 API 영역\n\n## 1) 등록 및 액션 (`ExtensionAPI`)\n\n핵심 메서드:\n\n- `on(event, handler)`\n- `registerTool`, `registerCommand`, `registerShortcut`, `registerFlag`\n- `registerMessageRenderer`\n- `sendMessage`, `sendUserMessage`, `appendEntry`\n- `getActiveTools`, `getAllTools`, `setActiveTools`\n- `getSessionName`, `setSessionName`\n- `setModel`, `getThinkingLevel`, `setThinkingLevel`\n- `registerProvider`\n- `events` (공유 이벤트 버스)\n\n인터랙티브 모드에서, `input` 핸들러는 내장된 첫 번째 메시지 자동 제목 검사 이전에 실행됩니다. `input`에서 `await pi.setSessionName(...)`을 호출하는 확장 기능은 지속되는 세션 이름을 설정할 수 있으며, 해당 세션에 대한 기본 자동 생성 제목 실행을 방지할 수 있습니다.\n\n또한 노출되는 항목:\n\n- `pi.logger`\n- `pi.typebox`\n- `pi.pi` (패키지 내보내기)\n\n### 메시지 전달 시맨틱\n\n`pi.sendMessage(message, options)`는 다음을 지원합니다:\n\n- `deliverAs: \"steer\"` (기본값) — 현재 실행을 중단합니다.\n- `deliverAs: \"followUp\"` — 현재 실행 이후 실행되도록 큐에 추가됩니다.\n- `deliverAs: \"nextTurn\"` — 저장되었다가 다음 사용자 프롬프트에 주입됩니다.\n- `triggerTurn: true` — 유휴 상태일 때 턴을 시작합니다 (`nextTurn`은 이를 무시합니다).\n\n`pi.sendUserMessage(content, { deliverAs })`는 항상 프롬프트 흐름을 통해 전달되며, 스트리밍 중에는 steer/follow-up으로 큐에 추가됩니다.\n\n## 2) 핸들러 컨텍스트 (`ExtensionContext`)\n\n핸들러와 도구 `execute`는 다음을 포함하는 `ctx`를 수신합니다:\n\n- `ui`\n- `hasUI`\n- `cwd`\n- `sessionManager` (읽기 전용)\n- `modelRegistry`, `model`\n- `getContextUsage()`\n- `compact(...)`\n- `isIdle()`, `hasPendingMessages()`, `abort()`\n- `shutdown()`\n- `getSystemPrompt()`\n\n## 3) 명령어 컨텍스트 (`ExtensionCommandContext`)\n\n명령어 핸들러는 추가로 다음을 제공받습니다:\n\n- `waitForIdle()`\n- `newSession(...)`\n- `switchSession(...)`\n- `branch(entryId)`\n- `navigateTree(targetId, { summarize })`\n- `reload()`\n\n세션 제어 흐름에는 명령어 컨텍스트를 사용하십시오. 이러한 메서드는 의도적으로 일반 이벤트 핸들러와 분리되어 있습니다.\n\n## 이벤트 영역 (현재 이름 및 동작)\n\n표준 이벤트 유니온 및 페이로드 유형은 `types.ts`에 있습니다.\n\n### 세션 수명 주기\n\n- `session_start`\n- `session_before_switch` / `session_switch`\n- `session_before_branch` / `session_branch`\n- `session_before_compact` / `session.compacting` / `session_compact`\n- `session_before_tree` / `session_tree`\n- `session_shutdown`\n\n취소 가능한 사전 이벤트:\n\n- `session_before_switch` → `{ cancel?: boolean }`\n- `session_before_branch` → `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_before_compact` → `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session_before_tree` → `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n\n### 프롬프트 및 턴 수명 주기\n\n- `input`\n- `before_agent_start`\n- `context`\n- `agent_start` / `agent_end`\n- `turn_start` / `turn_end`\n- `message_start` / `message_update` / `message_end`\n\n### 도구 수명 주기\n\n- `tool_call` (실행 전, 차단 가능)\n- `tool_result` (실행 후, 콘텐츠/세부 정보/isError 패치 가능)\n- `tool_execution_start` / `tool_execution_update` / `tool_execution_end` (관측 가능성)\n\n`tool_result`는 미들웨어 방식입니다: 핸들러는 확장 기능 순서대로 실행되며 각각 이전 수정 사항을 확인합니다.\n\n### 신뢰성/런타임 신호\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### 사용자 명령어 인터셉션\n\n- `user_bash` (`{ result }`로 재정의)\n- `user_python` (`{ result }`로 재정의)\n\n### `resources_discover`\n\n`resources_discover`는 확장 기능 유형과 `ExtensionRunner`에 존재합니다.\n현재 런타임 참고 사항: `ExtensionRunner.emitResourcesDiscover(...)`는 구현되어 있지만, 현재 코드베이스에서 이를 호출하는 `AgentSession` 호출 지점이 없습니다.\n\n## 도구 작성 세부 사항\n\n`registerTool`은 `types.ts`의 `ToolDefinition`을 사용합니다.\n\n현재 `execute` 서명:\n\n```ts\nexecute(\n toolCallId,\n params,\n signal,\n onUpdate,\n ctx,\n): Promise<AgentToolResult>\n```\n\n템플릿:\n\n```ts\npi.registerTool({\n name: \"my_tool\",\n label: \"My Tool\",\n description: \"...\",\n parameters: Type.Object({}),\n async execute(_id, _params, signal, onUpdate, ctx) {\n  if (signal?.aborted) {\n   return { content: [{ type: \"text\", text: \"Cancelled\" }] };\n  }\n  onUpdate?.({ content: [{ type: \"text\", text: \"Working...\" }] });\n  return { content: [{ type: \"text\", text: \"Done\" }], details: {} };\n },\n onSession(event, ctx) {\n  // reason: start|switch|branch|tree|shutdown\n },\n renderCall(args, theme) {\n  // optional TUI render\n },\n renderResult(result, options, theme, args) {\n  // optional TUI render\n },\n});\n```\n\n`tool_call`/`tool_result`는 `sdk.ts`에서 레지스트리가 래핑된 후 내장 도구 및 확장 기능/커스텀 도구를 포함한 모든 도구를 인터셉트합니다.\n\n## UI 통합 지점\n\n`ctx.ui`는 `ExtensionUIContext` 인터페이스를 구현합니다. 모드에 따라 지원 범위가 다릅니다.\n\n### 인터랙티브 모드 (`extension-ui-controller.ts`)\n\n지원 항목:\n\n- 다이얼로그: `select`, `confirm`, `input`, `editor`\n- 알림/상태/에디터 텍스트/터미널 입력/커스텀 오버레이\n- 이름으로 테마 목록 조회/로드 (`setTheme`은 문자열 이름을 지원합니다)\n- 도구 확장 토글\n\n이 컨트롤러의 현재 no-op 메서드:\n\n- `setFooter`\n- `setHeader`\n- `setEditorComponent`\n\n참고: `setWidget`은 현재 `setHookWidget(...)`을 통해 상태 표시줄 텍스트로 라우팅됩니다.\n\n### RPC 모드 (`rpc-mode.ts`)\n\n`ctx.ui`는 RPC `extension_ui_request` 이벤트로 지원됩니다:\n\n- 다이얼로그 메서드 (`select`, `confirm`, `input`, `editor`)는 클라이언트 응답으로 왕복합니다.\n- fire-and-forget 메서드는 요청을 내보냅니다 (`notify`, `setStatus`, 문자열 배열을 위한 `setWidget`, `setTitle`, `setEditorText`)\n\nRPC 구현에서 미지원/no-op:\n\n- `onTerminalInput`\n- `custom`\n- `setFooter`, `setHeader`, `setEditorComponent`\n- `setWorkingMessage`\n- 테마 전환/로드 (`setTheme`은 실패를 반환합니다)\n- 도구 확장 컨트롤은 비활성 상태입니다.\n\n### 프린트/헤드리스/서브에이전트 경로\n\n러너 초기화에 UI 컨텍스트가 제공되지 않으면 `ctx.hasUI`는 `false`이며 메서드는 no-op/기본값 반환입니다.\n\n### 백그라운드 인터랙티브 모드\n\n백그라운드 모드는 비인터랙티브 UI 컨텍스트 객체를 설치합니다. 현재 구현에서 `ctx.hasUI`는 여전히 `true`일 수 있지만, 인터랙티브 다이얼로그는 기본값/no-op 동작을 반환합니다.\n\n## 세션 및 상태 패턴\n\n지속적인 확장 기능 상태를 위해:\n\n1. `pi.appendEntry(customType, data)`로 지속합니다.\n2. `session_start`, `session_branch`, `session_tree`에서 `ctx.sessionManager.getBranch()`를 통해 상태를 재구성합니다.\n3. 도구 결과 히스토리에서 상태를 볼 수 있거나 재구성할 수 있어야 하는 경우, 도구 결과 `details`를 구조화된 형태로 유지합니다.\n\n재구성 패턴 예시:\n\n```ts\npi.on(\"session_start\", async (_event, ctx) => {\n let latest;\n for (const entry of ctx.sessionManager.getBranch()) {\n  if (entry.type === \"custom\" && entry.customType === \"my-state\") {\n   latest = entry.data;\n  }\n }\n // restore from latest\n});\n```\n\n## 렌더링 확장 지점\n\n## 커스텀 메시지 렌더러\n\n```ts\npi.registerMessageRenderer(\"my-type\", (message, { expanded }, theme) => {\n // return pi-tui Component\n});\n```\n\n커스텀 메시지가 표시될 때 인터랙티브 렌더링에서 사용됩니다.\n\n## 도구 호출/결과 렌더러\n\nTUI에서 커스텀 도구 시각화를 위해 `registerTool` 정의에 `renderCall` / `renderResult`를 제공하십시오.\n\n## 제약 사항 및 주의 사항\n\n- 런타임 액션은 확장 기능 로드 중에 사용할 수 없습니다.\n- `tool_call` 오류는 실행을 차단합니다 (fail-closed).\n- 내장 기능과 명령어 이름이 충돌하면 진단과 함께 건너뜁니다.\n- 예약된 단축키는 무시됩니다 (`ctrl+c`, `ctrl+d`, `ctrl+z`, `ctrl+k`, `ctrl+p`, `ctrl+l`, `ctrl+o`, `ctrl+t`, `ctrl+g`, `shift+tab`, `shift+ctrl+p`, `alt+enter`, `escape`, `enter`).\n- `ctx.reload()`는 현재 명령어 핸들러 프레임에서 종료로 처리하십시오.\n\n## 확장 기능 vs 훅 vs 커스텀 도구\n\n적절한 영역을 사용하십시오:\n\n- **확장 기능** (`src/extensibility/extensions/*`): 통합 시스템 (이벤트 + 도구 + 명령어 + 렌더러 + 프로바이더 등록).\n- **훅** (`src/extensibility/hooks/*`): 별도의 레거시 이벤트 API.\n- **커스텀 도구** (`src/extensibility/custom-tools/*`): 도구 중심 모듈. 확장 기능과 함께 로드될 때 적응되며 여전히 확장 기능 인터셉션 래퍼를 통과합니다.\n\n정책, 도구, 명령어 UX, 렌더링을 하나의 패키지로 소유해야 한다면 확장 기능을 사용하십시오.\n",
	"ko/extensions/gemini-manifest-extensions.md": "---\ntitle: Gemini 매니페스트 확장\ndescription: 크로스 플랫폼 스킬 및 에이전트 호환성을 위한 Gemini 매니페스트 확장 형식.\nsidebar:\n  order: 7\n  label: Gemini 매니페스트\ni18n:\n  sourceHash: 7134165a5f6d\n  translator: machine\n---\n\n# Gemini 매니페스트 확장 (`gemini-extension.json`)\n\n이 문서는 코딩 에이전트가 Gemini 스타일의 매니페스트 확장(`gemini-extension.json`)을 검색하고 `extensions` 기능으로 파싱하는 방법을 설명합니다.\n\nTypeScript/JavaScript 확장 모듈 로딩(`extensions/*.ts`, `index.ts`, `package.json xcsh.extensions`)은 다루지 **않으며**, 해당 내용은 `extension-loading.md`에 문서화되어 있습니다.\n\n## 구현 파일\n\n- [`../src/discovery/gemini.ts`](../../packages/coding-agent/src/discovery/gemini.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/capability/extension.ts`](../../packages/coding-agent/src/capability/extension.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/loader.ts`](../../packages/coding-agent/src/extensibility/extensions/loader.ts)\n\n---\n\n## 검색 대상\n\nGemini 프로바이더(`id: gemini`, 우선순위 `60`)는 두 개의 고정된 루트를 스캔하는 `extensions` 로더를 등록합니다.\n\n- 사용자: `~/.gemini/extensions`\n- 프로젝트: `<cwd>/.gemini/extensions`\n\n경로 확인은 `getUserPath()` / `getProjectPath()`를 통해 `ctx.home` 및 `ctx.cwd`에서 직접 수행됩니다.\n\n중요한 범위 규칙: 프로젝트 조회는 **cwd 전용**입니다. 상위 디렉토리를 탐색하지 않습니다.\n\n---\n\n## 디렉토리 스캔 규칙\n\n각 루트(`~/.gemini/extensions` 및 `<cwd>/.gemini/extensions`)에 대해 검색은 다음을 수행합니다.\n\n1. `readDirEntries(root)`\n2. 직접 하위 디렉토리만 유지(`entry.isDirectory()`)\n3. 각 하위 항목 `<name>`에 대해 정확히 다음을 읽으려고 시도:\n   - `<root>/<name>/gemini-extension.json`\n\n하나의 디렉토리 레벨을 초과하는 재귀적 스캔은 수행되지 않습니다.\n\n### 숨김 디렉토리\n\nGemini 매니페스트 검색은 점(`.`)으로 시작하는 디렉토리 이름을 필터링하지 **않습니다**. 숨김 하위 디렉토리가 존재하고 `gemini-extension.json`을 포함하는 경우 해당 항목이 처리됩니다.\n\n### 누락/읽기 불가 파일\n\n`gemini-extension.json`이 누락되거나 읽기 불가능한 경우, 해당 디렉토리는 경고 없이 자동으로 건너뜁니다.\n\n---\n\n## 매니페스트 형식 (구현 기준)\n\n기능 타입은 다음 매니페스트 형식을 정의합니다.\n\n```ts\ninterface ExtensionManifest {\n name?: string;\n description?: string;\n mcpServers?: Record<string, Omit<MCPServer, \"name\" | \"_source\">>;\n tools?: unknown[];\n context?: unknown;\n}\n```\n\n검색 시 동작은 의도적으로 느슨하게 처리됩니다.\n\n- JSON 파싱 성공이 필요합니다.\n- JSON 구문 외에 필드 타입/내용에 대한 런타임 스키마 유효성 검사는 수행되지 않습니다.\n- 파싱된 객체는 기능 항목의 `manifest`로 저장됩니다.\n\n### 이름 정규화\n\n`Extension.name`은 다음과 같이 설정됩니다.\n\n1. `manifest.name`이 `null`/`undefined`가 아닌 경우 해당 값 사용\n2. 그렇지 않으면 확장 디렉토리 이름 사용\n\n여기서는 문자열 타입 강제가 적용되지 않습니다.\n\n---\n\n## 기능 항목으로의 구체화\n\n유효하게 파싱된 매니페스트는 하나의 `Extension` 기능 항목을 생성합니다.\n\n```ts\n{\n name: manifest.name ?? <directory-name>,\n path: <extension-directory>,\n manifest: <parsed-json>,\n level: \"user\" | \"project\",\n _source: {\n  provider: \"gemini\",\n  providerName: \"Gemini CLI\" // 기능 레지스트리에 의해 첨부됨\n  path: <absolute-manifest-path>,\n  level: \"user\" | \"project\"\n }\n}\n```\n\n참고 사항:\n\n- `_source.path`는 `createSourceMeta()`에 의해 절대 경로로 정규화됩니다.\n- `extensions`에 대한 레지스트리 수준의 기능 유효성 검사는 `name`과 `path`의 존재 여부만 확인합니다.\n- 매니페스트 내부(`mcpServers`, `tools`, `context`)는 검색 중에 유효성이 검사되지 않습니다.\n\n---\n\n## 오류 처리 및 경고 의미\n\n### 경고 발생\n\n- 매니페스트 파일의 잘못된 JSON:\n  - 경고 형식: `Invalid JSON in <manifestPath>`\n\n### 경고 없음 (자동 건너뜀)\n\n- `extensions` 디렉토리 누락\n- 하위 디렉토리에 `gemini-extension.json` 없음\n- 읽기 불가능한 매니페스트 파일\n- 매니페스트 JSON이 구문적으로 유효하지만 의미상 불완전하거나 이상함\n\n즉, 부분적인 유효성이 허용됩니다. JSON 구문 오류가 발생하는 경우에만 경고가 발생합니다.\n\n---\n\n## 다른 소스와의 우선순위 및 중복 제거\n\n`extensions` 기능은 기능 레지스트리에 의해 프로바이더 간에 집계됩니다.\n\n이 기능의 현재 프로바이더:\n\n- `native` (`packages/coding-agent/src/discovery/builtin.ts`) 우선순위 `100`\n- `gemini` (`packages/coding-agent/src/discovery/gemini.ts`) 우선순위 `60`\n\n중복 제거 키는 `ext.name`(`extensionCapability.key = ext => ext.name`)입니다.\n\n### 프로바이더 간 우선순위\n\n중복 확장 이름에 대해 우선순위가 높은 프로바이더가 우선합니다.\n\n- `native`와 `gemini` 모두 확장 이름 `foo`를 발생시키는 경우, native 항목이 유지됩니다.\n- 낮은 우선순위의 중복 항목은 `_shadowed = true`와 함께 `result.all`에만 유지됩니다.\n\n### 프로바이더 내 순서 효과\n\n중복 제거 방식이 \"첫 번째 발견 우선\"이므로, 프로바이더 내의 항목 순서가 중요합니다.\n\n- Gemini 로더는 **사용자 항목을 먼저**, 그 다음 **프로젝트 항목**을 추가합니다.\n- 따라서 `~/.gemini/extensions`와 `<cwd>/.gemini/extensions` 간에 중복된 이름이 있으면 사용자 항목이 유지되고 프로젝트 항목은 숨겨집니다.\n\n반면, native 프로바이더는 `getConfigDirs()`에서 다른 순서(`project` 이후 `user`)로 설정 디렉토리를 구성하므로, native 프로바이더 내의 숨김 방향은 반대입니다.\n\n---\n\n## 사용자 vs 프로젝트 동작 요약\n\nGemini 매니페스트 전용:\n\n- 로드 시마다 사용자 및 프로젝트 루트가 모두 스캔됩니다.\n- 프로젝트 루트는 `<cwd>/.gemini/extensions`로 고정됩니다(상위 디렉토리 탐색 없음).\n- Gemini 소스 내의 중복 이름은 사용자 우선으로 처리됩니다.\n- 우선순위가 높은 프로바이더(특히 native)와의 중복 이름은 우선순위에 따라 패배합니다.\n\n---\n\n## 경계: 검색 메타데이터 vs 런타임 확장 로딩\n\n`gemini-extension.json` 검색은 현재 기능 메타데이터(`Extension` 항목)를 제공합니다. 실행 가능한 TS/JS 확장 모듈을 직접 로드하지는 **않습니다**.\n\n런타임 모듈 로딩(`discoverAndLoadExtensions()` / `loadExtensions()`)은 `extension-modules`와 명시적인 경로를 사용하며, 현재 자동 검색된 모듈을 `native` 프로바이더로만 필터링합니다.\n\n실질적인 의미:\n\n- Gemini 매니페스트 확장은 기능 레코드로 검색 가능합니다.\n- 확장 로더 파이프라인에 의해 런타임 확장 모듈로 자체적으로 실행되지는 않습니다.\n\n이 경계는 현재 구현에서 의도된 것으로, 매니페스트 검색과 실행 가능한 모듈 로딩이 왜 서로 다르게 동작할 수 있는지를 설명합니다.\n",
	"ko/extensions/marketplace.md": "---\ntitle: 마켓플레이스 플러그인 시스템\ndescription: '엄선된 플러그인 컬렉션을 검색, 설치 및 관리하기 위한 마켓플레이스 플러그인 시스템입니다.'\nsidebar:\n  order: 4\n  label: 마켓플레이스\ni18n:\n  sourceHash: 71d9f8f93a81\n  translator: machine\n---\n\n# 마켓플레이스 플러그인 시스템\n\n마켓플레이스 시스템을 사용하면 Git 호스팅 카탈로그에서 플러그인을 검색, 설치 및 관리할 수 있습니다. Claude Code 플러그인 레지스트리 형식과 호환됩니다.\n\n## 빠른 시작\n\n```\n/marketplace add anthropics/f5-sales-demo-marketplace\n/marketplace install wordpress.com@f5-sales-demo-marketplace\n```\n\n또는 인수 없이 `/marketplace`를 입력하면 대화형 플러그인 브라우저가 열립니다.\n\n## 개념\n\n**마켓플레이스**는 `.xcsh-plugin/marketplace.json` 위치에 카탈로그 파일이 포함된 Git 저장소(또는 로컬 디렉터리)입니다. 카탈로그는 소스, 설명 및 메타데이터와 함께 사용 가능한 플러그인을 나열합니다.\n\n**플러그인**은 스킬, 명령, 훅, MCP 서버 또는 LSP 서버가 포함된 디렉터리입니다. 플러그인은 `name@marketplace` 형식으로 식별됩니다(예: `code-review@f5-sales-demo-marketplace`).\n\n**범위**: 플러그인은 두 가지 범위에서 설치할 수 있습니다:\n\n- **user** (기본값) -- 모든 프로젝트에서 사용 가능하며, `~/.xcsh/plugins/installed_plugins.json`에 저장됩니다.\n- **project** -- 현재 프로젝트에서만 사용 가능하며, `.xcsh/installed_plugins.json`에 저장됩니다.\n\n프로젝트 범위 설치는 동일한 플러그인의 사용자 범위 설치를 덮어씁니다.\n\n## 명령어\n\n### 대화형 모드\n\n| 명령어 | 효과 |\n|---|---|\n| `/marketplace` | 대화형 플러그인 브라우저 열기 (설치) |\n\n### 마켓플레이스 관리\n\n| 명령어 | 효과 |\n|---|---|\n| `/marketplace add <source>` | 마켓플레이스 소스 추가 |\n| `/marketplace remove <name>` | 마켓플레이스 제거 |\n| `/marketplace update [name]` | 카탈로그 재가져오기; name을 생략하면 모두 업데이트 |\n| `/marketplace list` | 구성된 마켓플레이스 목록 표시 |\n\n### 플러그인 작업\n\n| 명령어 | 효과 |\n|---|---|\n| `/marketplace discover [marketplace]` | 사용 가능한 플러그인 탐색 |\n| `/marketplace install [--force] [--scope user\\|project] name@marketplace` | 플러그인 설치 |\n| `/marketplace uninstall [--scope user\\|project] name@marketplace` | 플러그인 제거 |\n| `/marketplace installed` | 설치된 마켓플레이스 플러그인 목록 표시 |\n| `/marketplace upgrade [--scope user\\|project] [name@marketplace]` | 하나 또는 모든 플러그인 업그레이드 |\n\n### CLI 동등 명령어\n\n동일한 작업을 명령줄에서도 수행할 수 있습니다:\n\n```\nxcsh plugin marketplace add <source>\nxcsh plugin marketplace remove <name>\nxcsh plugin marketplace update [name]\nxcsh plugin marketplace list\nxcsh plugin discover [marketplace]\nxcsh plugin install --scope project name@marketplace\n```\n\n## 마켓플레이스 소스\n\n`/marketplace add <source>`를 실행하면 시스템이 소스를 다음과 같이 분류합니다:\n\n| 소스 형식 | 유형 | 예시 |\n|---|---|---|\n| `owner/repo` | GitHub 단축 표기 | `anthropics/f5-sales-demo-marketplace` |\n| `https://...*.json` | 직접 카탈로그 URL | `https://example.com/marketplace.json` |\n| `https://...*.git` 또는 `git@...` | Git 저장소 | `https://github.com/org/repo.git` |\n| `./path` 또는 `~/path` 또는 `/path` | 로컬 디렉터리 | `./my-marketplace` |\n\n시스템은 저장소를 복제하거나(또는 로컬 디렉터리를 읽어) `.xcsh-plugin/marketplace.json`을 찾아 유효성을 검사한 후 카탈로그를 로컬에 캐시합니다.\n\n## 카탈로그 형식 (marketplace.json)\n\n마켓플레이스 카탈로그는 저장소 루트의 `.xcsh-plugin/marketplace.json`에 위치합니다:\n\n```json\n{\n  \"$schema\": \"https://anthropic.com/claude-code/marketplace.schema.json\",\n  \"name\": \"my-marketplace\",\n  \"owner\": {\n    \"name\": \"Your Name\",\n    \"email\": \"you@example.com\"\n  },\n  \"description\": \"A collection of plugins\",\n  \"plugins\": [\n    {\n      \"name\": \"my-plugin\",\n      \"description\": \"What this plugin does\",\n      \"source\": \"./plugins/my-plugin\",\n      \"category\": \"development\",\n      \"homepage\": \"https://github.com/you/my-plugin\"\n    }\n  ]\n}\n```\n\n### 필수 필드\n\n| 필드 | 설명 |\n|---|---|\n| `name` | 마켓플레이스 이름. 소문자 영숫자, 하이픈 및 점으로 구성. 영숫자로 시작하고 끝나야 함. 최대 64자. |\n| `owner.name` | 마켓플레이스 소유자 이름 |\n| `plugins` | 플러그인 항목 배열 |\n\n### 플러그인 항목 필드\n\n| 필드 | 필수 여부 | 설명 |\n|---|---|---|\n| `name` | 예 | 플러그인 이름 (마켓플레이스 이름과 동일한 규칙 적용) |\n| `source` | 예 | 플러그인을 찾을 위치 (아래 참조) |\n| `description` | 아니요 | 간단한 설명 |\n| `version` | 아니요 | 버전 문자열 |\n| `author` | 아니요 | `{ name, email? }` |\n| `homepage` | 아니요 | URL |\n| `category` | 아니요 | 카테고리 문자열 (예: `development`, `productivity`, `security`) |\n| `tags` | 아니요 | 문자열 태그 배열 |\n| `strict` | 아니요 | 불리언 |\n| `commands` | 아니요 | 제공되는 슬래시 명령어 |\n| `agents` | 아니요 | 제공되는 에이전트 |\n| `hooks` | 아니요 | 훅 정의 |\n| `mcpServers` | 아니요 | MCP 서버 정의 |\n| `lspServers` | 아니요 | LSP 서버 정의 |\n\n### 플러그인 소스 형식\n\n`source` 필드는 여러 형식을 지원합니다:\n\n**상대 경로** (마켓플레이스 저장소 내):\n\n```json\n\"source\": \"./plugins/my-plugin\"\n```\n\n**Git 저장소 URL**:\n\n```json\n\"source\": {\n  \"source\": \"url\",\n  \"url\": \"https://github.com/org/repo.git\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**GitHub 단축 표기**:\n\n```json\n\"source\": {\n  \"source\": \"github\",\n  \"repo\": \"org/repo\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Git 하위 디렉터리** (모노레포):\n\n```json\n\"source\": {\n  \"source\": \"git-subdir\",\n  \"url\": \"https://github.com/org/monorepo.git\",\n  \"path\": \"plugins/my-plugin\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**npm 패키지**:\n\n```json\n\"source\": {\n  \"source\": \"npm\",\n  \"package\": \"@scope/my-plugin\",\n  \"version\": \"1.0.0\"\n}\n```\n\n## 디스크 레이아웃\n\n```\n~/.xcsh/\n  config/\n    marketplaces.json          # 추가된 마켓플레이스 레지스트리\n  plugins/\n    installed_plugins.json     # 사용자 범위 설치된 플러그인\n    cache/\n      marketplaces/            # 캐시된 마켓플레이스 카탈로그\n      plugins/                 # 캐시된 플러그인 디렉터리\n\n<project>/.xcsh/\n  installed_plugins.json       # 프로젝트 범위 설치된 플러그인\n```\n\n## 명명 규칙\n\n마켓플레이스 및 플러그인 이름은 다음 조건을 충족해야 합니다:\n\n- 소문자 알파벳 또는 숫자로 시작하고 끝나야 합니다.\n- 소문자 알파벳, 숫자, 하이픈, 점만 포함해야 합니다.\n- 최대 64자를 초과하지 않아야 합니다.\n\n플러그인 ID(`name@marketplace`)는 총 최대 128자를 초과하지 않아야 합니다.\n\n유효한 예: `my-plugin`, `code-review`, `wordpress.com`, `ai-firstify`\n유효하지 않은 예: `-bad`, `bad-`, `.bad`, `Bad`, `under_score`\n",
	"ko/extensions/plugin-manager-installer-plumbing.md": "---\ntitle: 플러그인 매니저 및 인스톨러 내부 구조\ndescription: '설치, 검증, 의존성 해결, 라이프사이클 관리를 다루는 플러그인 매니저 내부 구조.'\nsidebar:\n  order: 5\n  label: 플러그인 매니저\ni18n:\n  sourceHash: 9c33e5a2c22a\n  translator: machine\n---\n\n# 플러그인 매니저 및 인스톨러 내부 구조\n\n이 문서는 `xcsh plugin` 작업이 디스크의 플러그인 상태를 어떻게 변경하는지, 그리고 설치된 플러그인이 런타임 기능(현재는 도구, 훅/명령어 경로 해석 가능)이 되는 방식을 설명합니다.\n\n## 범위 및 아키텍처\n\n코드베이스에는 두 가지 플러그인 관리 구현이 있습니다:\n\n1. **CLI 명령어에서 사용되는 활성 경로**: `PluginManager` (`src/extensibility/plugins/manager.ts`)\n2. **레거시 헬퍼 모듈**: 인스톨러 함수 (`src/extensibility/plugins/installer.ts`)\n\n`xcsh plugin ...` 명령어 실행은 `PluginManager`를 통해 이루어집니다.\n\n`installer.ts`는 여전히 중요한 안전 검사 및 파일시스템 동작을 문서화하지만, `src/commands/plugin.ts` + `src/cli/plugin-cli.ts`가 사용하는 경로는 아닙니다.\n\n## 라이프사이클: CLI 호출부터 런타임 가용성까지\n\n```text\nxcsh plugin <action> ...\n  -> src/commands/plugin.ts\n  -> runPluginCommand(...) in src/cli/plugin-cli.ts\n  -> PluginManager method (install/list/uninstall/link/...) \n  -> mutate ~/.xcsh/plugins/{package.json,node_modules,xcsh-plugins.lock.json}\n  -> runtime discovery: discoverAndLoadCustomTools(...)\n  -> getAllPluginToolPaths(cwd)\n  -> custom tool loader imports tool modules\n```\n\n### 명령어 진입점\n\n- `src/commands/plugin.ts`는 명령어/플래그를 정의하고 `runPluginCommand`로 전달합니다.\n- `src/cli/plugin-cli.ts`는 서브명령어를 `PluginManager` 메서드에 매핑합니다:\n  - `install`, `uninstall`, `list`, `link`, `doctor`, `features`, `config`, `enable`, `disable`\n- 명시적인 `update` 액션은 없으며, 업데이트는 새 패키지/버전 스펙으로 `install`을 재실행하여 수행합니다.\n\n## 디스크 모델\n\n전역 플러그인 상태는 `~/.xcsh/plugins` 아래에 저장됩니다:\n\n- `package.json` — `bun install`/`bun uninstall`이 사용하는 의존성 매니페스트\n- `node_modules/` — 설치된 플러그인 패키지 또는 심볼릭 링크\n- `xcsh-plugins.lock.json` — 런타임 상태:\n  - 플러그인별 활성화/비활성화 여부\n  - 플러그인별 선택된 기능 집합\n  - 지속되는 플러그인 설정\n\n프로젝트 로컬 오버라이드는 다음 위치에 저장됩니다:\n\n- `<cwd>/.xcsh/plugin-overrides.json`\n\n오버라이드는 매니저/로더 관점에서 읽기 전용(여기에는 쓰기 경로 없음)이며, 이 프로젝트에 대한 플러그인을 비활성화하거나 기능/설정을 오버라이드할 수 있습니다.\n\n## 플러그인 스펙 파싱 및 메타데이터 해석\n\n## 설치 스펙 문법\n\n`parsePluginSpec` (`parser.ts`)은 다음을 지원합니다:\n\n- `pkg` -> `features: null` (기본 동작)\n- `pkg[*]` -> 모든 매니페스트 기능 활성화\n- `pkg[]` -> 선택적 기능 없이 활성화\n- `pkg[a,b]` -> 명명된 기능 활성화\n- `@scope/pkg@1.2.3[feat]` -> 명시적 기능 선택이 포함된 스코프 + 버전 패키지\n\n`extractPackageName`은 설치 후 디스크 경로 조회를 위해 버전 접미사를 제거합니다.\n\n## 매니페스트 소스 및 필수 필드\n\n매니페스트는 다음 순서로 해석됩니다:\n\n1. `package.json.xcsh`\n2. 폴백 `package.json.pi`\n3. 폴백 `{ version: package.version }`\n\n시사점:\n\n- 매니저/로더에는 엄격한 스키마 검증이 없습니다.\n- `xcsh`/`pi`가 없는 패키지도 설치 및 목록 조회가 가능합니다.\n- 런타임 플러그인 로딩(`getEnabledPlugins`)은 `xcsh`/`pi` 매니페스트가 없는 패키지를 건너뜁니다.\n- `manifest.version`은 항상 패키지 `version`에서 덮어씌워집니다.\n\n`package.json` JSON이 잘못된 경우 읽기 시 하드 실패가 발생하며, 매니페스트 형태가 잘못된 경우 특정 필드를 사용할 때 나중에 실패할 수 있습니다.\n\n## 설치/업데이트 흐름 (`PluginManager.install`)\n\n1. 설치 스펙에서 기능 괄호 문법을 파싱합니다.\n2. 정규식 + 쉘 메타문자 거부 목록에 대해 패키지 이름을 검증합니다.\n3. 플러그인 `package.json`이 존재하는지 확인합니다 (`xcsh-plugins`, private 의존성 맵).\n4. `~/.xcsh/plugins`에서 `bun install <packageSpec>`을 실행합니다.\n5. 설치된 패키지 `node_modules/<name>/package.json`을 읽습니다.\n6. 매니페스트를 해석하고 `enabledFeatures`를 계산합니다:\n   - `[*]`: 선언된 모든 기능 (기능 맵이 없는 경우 `null`)\n   - `[a,b]`: 각 기능이 매니페스트 기능 맵에 존재하는지 검증\n   - `[]`: 빈 기능 목록\n   - 기본 스펙: `null` (로더에서 나중에 기본 정책 사용)\n7. 락파일 런타임 상태를 Upsert합니다: `{ version, enabledFeatures, enabled: true }`.\n\n### 업데이트 시맨틱\n\n업데이트는 설치 기반으로 이루어지므로:\n\n- `xcsh plugin install pkg@newVersion`은 의존성과 락파일 버전을 업데이트합니다.\n- 기존 설정은 보존되며, 버전/기능/활성화 여부에 대한 상태 항목이 덮어씌워집니다.\n- 별도의 \"업데이트 확인\" 또는 트랜잭션 마이그레이션 로직은 없습니다.\n\n## 제거 흐름 (`PluginManager.uninstall`)\n\n1. 패키지 이름을 검증합니다.\n2. 플러그인 디렉터리에서 `bun uninstall <name>`을 실행합니다.\n3. 락파일에서 플러그인 런타임 상태를 제거합니다:\n   - `config.plugins[name]`\n   - `config.settings[name]`\n\n언인스톨 명령어가 실패하면 런타임 상태는 변경되지 않습니다.\n\n## 목록 조회 흐름 (`PluginManager.list`)\n\n1. `~/.xcsh/plugins/package.json`에서 플러그인 의존성 맵을 읽습니다.\n2. 락파일 런타임 설정을 로드합니다 (파일 없음 -> 빈 기본값).\n3. 프로젝트 오버라이드를 로드합니다 (`<cwd>/.xcsh/plugin-overrides.json`, 파싱/읽기 오류 -> 경고와 함께 빈 객체).\n4. package.json을 해석할 수 있는 각 의존성에 대해:\n   - `InstalledPlugin` 레코드를 빌드합니다\n   - 기능/활성화 상태를 병합합니다:\n     - 락파일 기반 (또는 기본값)\n     - 프로젝트 오버라이드는 기능 선택을 대체할 수 있음\n     - 프로젝트 `disabled` 목록은 플러그인을 비활성화된 것으로 마스킹\n\n이것이 CLI 상태 출력 및 설정/기능 작업에서 사용되는 유효 상태입니다.\n\n## 링크 흐름 (`PluginManager.link`)\n\n`link`는 로컬 패키지를 `~/.xcsh/plugins/node_modules/<pkg.name>`에 심볼릭 링크하여 로컬 플러그인 개발을 지원합니다.\n\n동작:\n\n1. 매니저 cwd에 대해 `localPath`를 해석합니다.\n2. 로컬 `package.json` 및 `name` 필드를 요구합니다.\n3. 플러그인 디렉터리가 존재하는지 확인합니다.\n4. 스코프 이름의 경우 스코프 디렉터리를 생성합니다.\n5. 대상 링크 위치의 기존 경로를 제거합니다.\n6. 심볼릭 링크를 생성합니다.\n7. 기본 기능(`null`)으로 활성화된 런타임 락파일 항목을 추가합니다.\n\n주의사항: 현재 `PluginManager.link`는 레거시 `installer.ts`에 있는 `cwd` 경로 경계 검사(`normalizedPath.startsWith(normalizedCwd)`)를 적용하지 않으므로, 신뢰는 호출자의 책임입니다.\n\n## 런타임 로딩: 설치된 플러그인에서 호출 가능한 기능으로\n\n## 탐색 게이트\n\n`getEnabledPlugins(cwd)` (`plugins/loader.ts`)는 다음을 읽습니다:\n\n- 플러그인 의존성 매니페스트 (`package.json`)\n- 락파일 런타임 상태\n- `getConfigDirPaths(\"plugin-overrides.json\", { user: false, cwd })`를 통한 프로젝트 오버라이드\n\n필터링:\n\n- 플러그인 package.json이 없으면 건너뜀\n- 매니페스트(`xcsh`/`pi`)가 없으면 건너뜀\n- 락파일에서 전역 비활성화된 경우 건너뜀\n- 프로젝트에서 비활성화된 경우 건너뜀\n\n## 기능 경로 해석\n\n활성화된 각 플러그인에 대해:\n\n- `resolvePluginToolPaths(plugin)`\n- `resolvePluginHookPaths(plugin)`\n- `resolvePluginCommandPaths(plugin)`\n\n각 리졸버는 기본 항목과 기능 항목을 포함합니다:\n\n- 명시적 기능 목록 -> 선택된 기능만\n- `enabledFeatures === null` -> `default: true`로 표시된 기능 활성화\n\n파일이 없는 경우 자동으로 건너뜁니다 (`existsSync` 가드).\n\n## 현재 런타임 연결 차이점\n\n- **도구는 오늘 런타임에 연결됩니다** — `discoverAndLoadCustomTools` (`custom-tools/loader.ts`)를 통해 `getAllPluginToolPaths(cwd)`를 호출합니다.\n- 경로는 커스텀 도구 탐색에서 해석된 절대 경로로 중복 제거됩니다 (`seen` 집합, 첫 번째 경로 우선).\n- **훅/명령어 리졸버는 존재하고 내보내지지만**, 이 코드 경로는 현재 도구가 연결되는 것과 동일한 방식으로 런타임 레지스트리에 연결되지 않습니다.\n\n## 락/상태 관리 세부 사항\n\n`PluginManager`는 인스턴스당 메모리에 런타임 설정을 캐시하고(`#runtimeConfig`) 지연 로딩합니다.\n\n로드 동작:\n\n- 락파일 없음 -> `{ plugins: {}, settings: {} }`\n- 락파일 읽기/파싱 실패 -> 경고 + 동일한 빈 기본값\n\n저장 동작:\n\n- 각 변경 시 전체 락파일 JSON을 들여쓰기 형식으로 씁니다\n\n크로스 프로세스 잠금이나 병합 전략은 없으며, 동시 쓰기가 서로 덮어쓸 수 있습니다.\n\n## 안전 검사 및 신뢰 경계\n\n## 입력/패키지 검증\n\n활성 매니저 경로는 패키지 이름 검증을 적용합니다:\n\n- 스코프/비스코프 패키지 스펙을 위한 정규식 (선택적 버전 포함)\n- 명시적 쉘 메타문자 거부 목록 (`[;&|`$(){}[]<>\\\\]`)\n\n이는 `bun install/uninstall` 호출 시 명령어 인젝션 위험을 제한합니다.\n\n## 파일시스템 신뢰 경계\n\n- 플러그인 코드는 커스텀 도구 모듈을 임포트할 때 인프로세스로 실행되며, 샌드박싱은 없습니다.\n- 매니페스트 상대 경로는 플러그인 패키지 디렉터리에 대해 결합되며 존재 여부만 확인합니다.\n- 플러그인 패키지 자체는 설치 후 신뢰할 수 있는 코드입니다.\n\n## 레거시 인스톨러 전용 검사\n\n`installer.ts`에는 `PluginManager.link`에 반영되지 않은 추가적인 링크 시점 검사가 포함됩니다:\n\n- 로컬 경로는 프로젝트 cwd 내부에서 해석되어야 함\n- 심볼릭 링크 대상 이름 지정을 위한 추가 패키지 이름/경로 순회 가드\n\nCLI는 `PluginManager`를 사용하므로, 이 더 엄격한 링크 가드는 현재 메인 경로에 없습니다.\n\n## 실패, 부분 성공, 롤백 동작\n\n플러그인 매니저는 트랜잭션 방식이 아닙니다.\n\n| 작업 단계 | 실패 동작 | 롤백 |\n| --- | --- | --- |\n| `bun install` 실패 | stderr와 함께 설치 중단 | 해당 없음 (아직 상태 쓰기 없음) |\n| 설치 성공 후 매니페스트/기능 검증 실패 | 명령어 실패 | 언인스톨 롤백 없음; 의존성이 `node_modules`/`package.json`에 남을 수 있음 |\n| 설치 성공 후 락파일 쓰기 실패 | 명령어 실패 | 설치된 패키지 롤백 없음 |\n| `bun uninstall` 성공 후 락파일 쓰기 실패 | 명령어 실패 | 패키지 제거됨, 오래된 런타임 상태가 남을 수 있음 |\n| `link`가 이전 대상을 제거한 후 심볼릭 링크 생성 실패 | 명령어 실패 | 이전 링크/디렉터리 복원 없음 |\n\n운영상으로, `doctor --fix`는 일부 불일치를 복구할 수 있지만(`bun install`, 고아 설정 정리, 잘못된 기능 정리), 최선 노력 방식입니다.\n\n## 잘못된/누락된 매니페스트 동작 요약\n\n- 누락된 `xcsh`/`pi` 필드:\n  - 설치/목록 조회: 허용 (최소 매니페스트)\n  - 런타임 활성화 플러그인 탐색: 비플러그인으로 건너뜀\n- 설치 스펙 또는 `features --set/--enable`에서 참조된 누락된 기능: 사용 가능한 기능 목록과 함께 하드 오류\n- 잘못된 `plugin-overrides.json`: 매니저와 로더 경로 모두에서 `{}`로 폴백하여 무시\n- 매니페스트에서 참조된 누락된 도구/훅/명령어 파일 경로: 리졸버 확장 중 자동 무시; `doctor`에 의해서만 오류로 표시\n\n## 모드 차이점 및 우선순위\n\n- `--dry-run` (설치): 합성 설치 결과를 반환하며, 파일시스템/네트워크/상태 쓰기 없음.\n- `--json`: 출력 형식만 변경, 동작 변화 없음.\n- 프로젝트 오버라이드는 항상 기능/설정 보기에서 전역 락파일보다 우선합니다.\n- 유효 활성화는 `runtimeEnabled && !projectDisabled`입니다.\n\n## 구현 파일\n\n- [`src/commands/plugin.ts`](../../packages/coding-agent/src/commands/plugin.ts) — CLI 명령어 선언 및 플래그 매핑\n- [`src/cli/plugin-cli.ts`](../../packages/coding-agent/src/cli/plugin-cli.ts) — 액션 디스패치, 사용자 대면 명령어 핸들러\n- [`src/extensibility/plugins/manager.ts`](../../packages/coding-agent/src/extensibility/plugins/manager.ts) — 활성 설치/제거/목록/링크/상태/doctor 구현\n- [`src/extensibility/plugins/installer.ts`](../../packages/coding-agent/src/extensibility/plugins/installer.ts) — 레거시 인스톨러 헬퍼 및 추가 링크 안전 검사\n- [`src/extensibility/plugins/loader.ts`](../../packages/coding-agent/src/extensibility/plugins/loader.ts) — 활성화된 플러그인 탐색 및 도구/훅/명령어 경로 해석\n- [`src/extensibility/plugins/parser.ts`](../../packages/coding-agent/src/extensibility/plugins/parser.ts) — 설치 스펙 및 패키지 이름 파싱 헬퍼\n- [`src/extensibility/plugins/types.ts`](../../packages/coding-agent/src/extensibility/plugins/types.ts) — 매니페스트/런타임/오버라이드 타입 계약\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts) — 플러그인 제공 도구 모듈을 위한 런타임 연결\n",
	"ko/extensions/rulebook-matching-pipeline.md": "---\ntitle: 룰북 매칭 파이프라인\ndescription: 에이전트 세션에 컨텍스트별 명령 집합을 선택하고 적용하기 위한 룰북 매칭 파이프라인입니다.\nsidebar:\n  order: 6\n  label: 룰북 매칭\ni18n:\n  sourceHash: a16a9c565053\n  translator: machine\n---\n\n# 룰북 매칭 파이프라인\n\n이 문서는 코딩 에이전트가 지원되는 설정 형식에서 규칙을 검색하고, 이를 단일 `Rule` 형태로 정규화하며, 우선순위 충돌을 해결하고, 결과를 다음으로 분리하는 방법을 설명합니다:\n\n- **룰북 규칙** (시스템 프롬프트 + `rule://` URL을 통해 모델에서 사용 가능)\n- **TTSR 규칙** (시간 이동 스트림 중단 규칙)\n\n이 문서는 파싱되지만 적용되지 않는 부분적인 시맨틱 및 메타데이터를 포함하여 현재 구현을 반영합니다.\n\n## 구현 파일\n\n- [`../src/capability/rule.ts`](../../packages/coding-agent/src/capability/rule.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/discovery/index.ts`](../../packages/coding-agent/src/discovery/index.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/cursor.ts`](../../packages/coding-agent/src/discovery/cursor.ts)\n- [`../src/discovery/windsurf.ts`](../../packages/coding-agent/src/discovery/windsurf.ts)\n- [`../src/discovery/cline.ts`](../../packages/coding-agent/src/discovery/cline.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/system-prompt.ts`](../../packages/coding-agent/src/system-prompt.ts)\n- [`../src/internal-urls/rule-protocol.ts`](../../packages/coding-agent/src/internal-urls/rule-protocol.ts)\n- [`../src/utils/frontmatter.ts`](../../packages/coding-agent/src/utils/frontmatter.ts)\n\n## 1. 표준 규칙 형태\n\n모든 공급자는 소스 파일을 `Rule`로 정규화합니다:\n\n```ts\ninterface Rule {\n  name: string;\n  path: string;\n  content: string;\n  globs?: string[];\n  alwaysApply?: boolean;\n  description?: string;\n  ttsrTrigger?: string;\n  _source: SourceMeta;\n}\n```\n\n기능 식별자는 `rule.name`입니다 (`ruleCapability.key = rule => rule.name`).\n\n결과: 우선순위와 중복 제거는 **이름 기반으로만** 수행됩니다. 같은 `name`을 가진 두 개의 다른 파일은 동일한 논리적 규칙으로 간주됩니다.\n\n## 2. 검색 소스 및 정규화\n\n`src/discovery/index.ts`는 공급자를 자동으로 등록합니다. `rules`의 경우 현재 공급자는 다음과 같습니다:\n\n- `native` (우선순위 `100`)\n- `cursor` (우선순위 `50`)\n- `windsurf` (우선순위 `50`)\n- `cline` (우선순위 `40`)\n\n### 네이티브 공급자 (`builtin.ts`)\n\n다음 위치에서 `.xcsh` 규칙을 로드합니다:\n\n- 프로젝트: `<cwd>/.xcsh/rules/*.{md,mdc}`\n- 사용자: `~/.xcsh/agent/rules/*.{md,mdc}`\n\n정규화:\n\n- `name` = `.md`/`.mdc` 없는 파일명\n- 프론트매터는 `parseFrontmatter`를 통해 파싱됨\n- `content` = 본문 (프론트매터 제거됨)\n- `globs`, `alwaysApply`, `description`, `ttsr_trigger`는 직접 매핑됨\n\n중요한 주의 사항: `globs`는 이 공급자에서 요소 필터링 없이 `string[] | undefined`로 캐스팅됩니다.\n\n### Cursor 공급자 (`cursor.ts`)\n\n다음 위치에서 로드합니다:\n\n- 사용자: `~/.cursor/rules/*.{mdc,md}`\n- 프로젝트: `<cwd>/.cursor/rules/*.{mdc,md}`\n\n정규화 (`transformMDCRule`):\n\n- `description`: 문자열인 경우에만 유지됨\n- `alwaysApply`: `true`만 보존됨 (`false`는 `undefined`가 됨)\n- `globs`: 배열(문자열 요소만) 또는 단일 문자열 허용\n- `ttsr_trigger`: 문자열만\n- `name`은 확장자 없는 파일명에서 가져옴\n\n### Windsurf 공급자 (`windsurf.ts`)\n\n다음 위치에서 로드합니다:\n\n- 사용자: `~/.codeium/windsurf/memories/global_rules.md` (고정 규칙명 `global_rules`)\n- 프로젝트: `<cwd>/.windsurf/rules/*.md`\n\n정규화:\n\n- `globs`: 문자열 배열 또는 단일 문자열\n- `alwaysApply`, `description`은 프론트매터에서 캐스팅됨\n- `ttsr_trigger`: 문자열만\n- `name`은 프로젝트 규칙의 파일명에서 가져옴\n\n### Cline 공급자 (`cline.ts`)\n\n`cwd`에서 위쪽으로 가장 가까운 `.clinerules`를 검색합니다:\n\n- 디렉터리인 경우: 내부의 `*.md`를 로드함\n- 파일인 경우: `clinerules`라는 이름의 규칙으로 단일 파일을 로드함\n\n정규화:\n\n- `globs`: 문자열 배열 또는 단일 문자열\n- `alwaysApply`: 불리언인 경우에만\n- `description`: 문자열만\n- `ttsr_trigger`: 문자열만\n\n## 3. 프론트매터 파싱 동작 및 모호성\n\n모든 공급자는 다음 시맨틱으로 `parseFrontmatter` (`utils/frontmatter.ts`)를 사용합니다:\n\n1. 프론트매터는 콘텐츠가 `---`로 시작하고 닫는 `\\n---`가 있는 경우에만 파싱됩니다.\n2. 프론트매터 추출 후 본문이 트리밍됩니다.\n3. YAML 파싱이 실패하는 경우:\n   - 경고가 기록되고,\n   - 파서는 단순 `key: value` 줄 파싱(`^(\\w+):\\s*(.*)$`)으로 폴백합니다.\n\n모호성의 결과:\n\n- 폴백 파서는 배열, 중첩 객체, 인용 규칙 또는 하이픈이 포함된 키를 지원하지 않습니다.\n- 폴백 값은 문자열이 됩니다 (예: `alwaysApply: true`는 문자열 `\"true\"`가 됨). 따라서 불리언/문자열 타입을 요구하는 공급자는 메타데이터를 삭제할 수 있습니다.\n- `ttsr_trigger`는 폴백에서 작동합니다 (밑줄 키); `thinking-level`과 같은 키는 작동하지 않습니다.\n- 유효한 프론트매터가 없는 파일도 빈 메타데이터와 전체 콘텐츠 본문을 가진 규칙으로 로드됩니다.\n\n## 4. 공급자 우선순위 및 중복 제거\n\n`loadCapability(\"rules\")` (`capability/index.ts`)는 공급자 출력을 병합한 후 `rule.name`으로 중복을 제거합니다.\n\n### 우선순위 모델\n\n- 공급자는 우선순위 내림차순으로 정렬됩니다.\n- 동일한 우선순위에서는 등록 순서를 유지합니다 (`discovery/index.ts`에서 `cursor`가 `windsurf` 앞에 옴).\n- 중복 제거는 선착순입니다: 처음 발견된 규칙 이름이 유지되고, 이후 동일한 이름의 항목은 `all`에서 `_shadowed`로 표시되고 `items`에서 제외됩니다.\n\n현재 유효한 규칙 공급자 순서는 다음과 같습니다:\n\n1. `native` (100)\n2. `cursor` (50)\n3. `windsurf` (50)\n4. `cline` (40)\n\n### 공급자 내부 순서 주의 사항\n\n공급자 내에서 항목 순서는 `loadFilesFromDir` glob 결과 순서와 명시적 push 순서에서 옵니다. 이는 일반적인 사용에는 충분히 결정적이지만 코드에서 명시적으로 정렬되지는 않습니다.\n\n주목할 만한 소스 순서 차이:\n\n- `native`는 프로젝트를 추가한 후 사용자 설정 디렉터리를 추가합니다.\n- `cursor`는 사용자를 추가한 후 프로젝트 결과를 추가합니다.\n- `windsurf`는 사용자 `global_rules`를 먼저 추가한 후 프로젝트 규칙을 추가합니다.\n- `cline`은 가장 가까운 `.clinerules` 소스만 로드합니다.\n\n## 5. 룰북, 항상 적용, TTSR 버킷으로 분리\n\n`createAgentSession` (`sdk.ts`)에서 규칙 검색 후:\n\n1. 검색된 모든 규칙이 스캔됩니다.\n2. `condition` (프론트매터 키; 폴백으로 `ttsr_trigger` / `ttsrTrigger` 허용)이 있는 규칙은 `TtsrManager`에 등록됩니다.\n3. 별도의 `rulebookRules` 목록이 다음 조건으로 구성됩니다:\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && !rule.alwaysApply && !!rule.description\n```\n\n4. `alwaysApplyRules` 목록이 구성됩니다:\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && rule.alwaysApply === true\n```\n\n### 버킷 동작\n\n- **TTSR 버킷**: `condition`이 있는 모든 규칙 (description 불필요). 다른 버킷보다 우선순위가 높습니다.\n- **항상 적용 버킷**: `alwaysApply === true`, TTSR이 아님. 전체 콘텐츠가 시스템 프롬프트에 주입됨. `rule://`을 통해 확인 가능.\n- **룰북 버킷**: description이 있어야 하며, TTSR이 아니고, `alwaysApply`가 아니어야 함. 시스템 프롬프트에 이름+description으로 나열되며, 콘텐츠는 `rule://`을 통해 필요 시 읽힘.\n- `condition`과 `alwaysApply`를 모두 가진 규칙은 TTSR만으로 이동합니다 (TTSR이 우선).\n- `alwaysApply`와 `description`을 모두 가진 규칙은 항상 적용으로만 이동합니다 (룰북 아님).\n\n## 6. 메타데이터가 런타임 표면에 영향을 미치는 방법\n\n### `description`\n\n- 룰북에 포함되기 위해 필요합니다.\n- 시스템 프롬프트 `<rules>` 블록에 렌더링됩니다.\n- description이 없으면 규칙은 `rule://`을 통해 사용할 수 없으며 시스템 프롬프트 규칙에 나열되지 않습니다.\n\n### `globs`\n\n- `Rule`에서 전달됩니다.\n- 시스템 프롬프트 규칙 블록에 `<glob>...</glob>` 항목으로 렌더링됩니다.\n- 규칙 UI 상태에 노출됩니다 (`extensions` 모드 목록).\n- **이 파이프라인에서 자동 매칭을 위해 적용되지 않습니다.** 현재 파일/도구 대상으로 규칙을 선택하는 런타임 glob 매처가 없습니다.\n\n### `alwaysApply`\n\n- 공급자에 의해 파싱되고 보존됩니다.\n- UI 표시에 사용됩니다 (extensions 상태 관리자에서 `\"always\"` 트리거 레이블).\n- `rulebookRules`에서의 제외 조건으로 사용됩니다.\n- **전체 규칙 콘텐츠가 시스템 프롬프트에 자동 주입됩니다** (룰북 규칙 섹션 이전).\n- 규칙은 재읽기를 위해 `rule://<name>`을 통해서도 접근 가능합니다.\n\n### `ttsr_trigger`\n\n- `rule.ttsrTrigger`에 매핑됩니다.\n- 존재하는 경우 규칙은 TTSR 관리자로 라우팅되며 룰북에는 포함되지 않습니다.\n\n## 7. 시스템 프롬프트 포함 경로\n\n`buildSystemPromptInternal`은 `rules` (룰북)과 `alwaysApplyRules`를 모두 수신합니다.\n\n항상 적용 규칙은 먼저 렌더링되며, 원시 콘텐츠를 프롬프트에 직접 주입합니다.\n\n룰북 규칙은 `# Rules` 섹션에 다음과 함께 렌더링됩니다:\n\n- `Read rule://<name> when working in matching domain`\n- 각 규칙의 `name`, `description`, 및 선택적 `<glob>` 목록\n\n이는 권고적/맥락적입니다: 프롬프트 텍스트는 모델에게 적용 가능한 규칙을 읽도록 요청하지만, 코드는 glob 적용 가능성을 강제하지 않습니다.\n\n## 8. `rule://` 내부 URL 동작\n\n`RuleProtocolHandler`는 다음과 함께 등록됩니다:\n\n```ts\nnew RuleProtocolHandler({ getRules: () => [...rulebookRules, ...alwaysApplyRules] })\n```\n\n의미:\n\n- `rule://<name>`은 **rulebookRules**와 **alwaysApplyRules** 모두에 대해 확인됩니다.\n- TTSR 전용 규칙과 description 및 `alwaysApply`가 없는 규칙은 `rule://`을 통해 접근할 수 없습니다.\n- 해결은 정확한 이름 일치입니다.\n- 알 수 없는 이름은 사용 가능한 규칙 이름을 나열하는 오류를 반환합니다.\n- 반환된 콘텐츠는 원시 `rule.content` (프론트매터 제거됨)이며, 콘텐츠 타입은 `text/markdown`입니다.\n\n## 9. 알려진 부분적/미적용 시맨틱\n\n1. 공급자 설명에는 레거시 파일(`.cursorrules`, `.windsurfrules`)이 언급되어 있지만, 현재 로더 코드 경로는 실제로 해당 파일을 읽지 않습니다.\n2. `globs` 메타데이터는 프롬프트/UI에 표시되지만 규칙 선택 로직에서 적용되지 않습니다.\n3. `rule://`에 대한 규칙 선택에는 룰북과 항상 적용 규칙이 포함되지만 TTSR 전용 규칙은 포함되지 않습니다.\n4. 검색 경고(`loadCapability(\"rules\").warnings`)는 생성되지만 `createAgentSession`은 현재 이 경로에서 해당 경고를 표시/기록하지 않습니다.\n",
	"ko/extensions/skills.md": "---\ntitle: 스킬\ndescription: '코딩 에이전트에서 특화된 기능을 등록, 검색, 호출하기 위한 스킬 시스템.'\nsidebar:\n  order: 3\n  label: 스킬\ni18n:\n  sourceHash: 3e062cc13851\n  translator: machine\n---\n\n# 스킬\n\n스킬은 시작 시 검색되고 모델에 다음과 같이 노출되는 파일 기반 기능 팩입니다:\n\n- 시스템 프롬프트의 경량 메타데이터 (이름 + 설명)\n- `read skill://...`을 통한 온디맨드 콘텐츠\n- 선택적 대화형 `/skill:<name>` 명령어\n\n이 문서는 `src/extensibility/skills.ts`, `src/discovery/builtin.ts`, `src/internal-urls/skill-protocol.ts`, `src/discovery/agents-md.ts`의 현재 런타임 동작을 다룹니다.\n\n## 이 코드베이스에서 스킬의 정의\n\n검색된 스킬은 다음으로 표현됩니다:\n\n- `name`\n- `description`\n- `filePath` (`SKILL.md` 경로)\n- `baseDir` (스킬 디렉터리)\n- 소스 메타데이터 (`provider`, `level`, 경로)\n\n런타임은 유효성 검사를 위해 `name`과 `path`만 필요로 합니다. 실제로 매칭 품질은 `description`이 의미 있는 값인지에 따라 달라집니다.\n\n## 필수 레이아웃 및 SKILL.md 요구사항\n\n### 디렉터리 레이아웃\n\n프로바이더 기반 검색(native/Claude/Codex/Agents/플러그인 프로바이더)의 경우, 스킬은 **`skills/` 하위 한 단계**에서 검색됩니다:\n\n- `<skills-root>/<skill-name>/SKILL.md`\n\n`<skills-root>/group/<skill>/SKILL.md`와 같은 중첩 패턴은 프로바이더 로더에서 검색되지 않습니다.\n\n`skills.customDirectories`의 경우, 스캐닝은 동일한 비재귀적 레이아웃(`*/SKILL.md`)을 사용합니다.\n\n```text\nProvider-discovered layout (non-recursive under skills/):\n\n<root>/skills/\n  ├─ postgres/\n  │   └─ SKILL.md      ✅ discovered\n  ├─ pdf/\n  │   └─ SKILL.md      ✅ discovered\n  └─ team/\n      └─ internal/\n          └─ SKILL.md  ❌ not discovered by provider loaders\n\nCustom-directory scanning is also non-recursive, so nested paths are ignored unless you point `customDirectories` at that nested parent.\n```\n\n### `SKILL.md` 프론트매터\n\n스킬 타입에서 지원되는 프론트매터 필드:\n\n- `name?: string`\n- `description?: string`\n- `globs?: string[]`\n- `alwaysApply?: boolean`\n- 추가 키는 알 수 없는 메타데이터로 보존됨\n\n현재 런타임 동작:\n\n- `name`은 기본적으로 스킬 디렉터리 이름으로 설정됨\n- `description`은 다음 경우에 필수:\n  - native `.xcsh` 프로바이더 스킬 검색 (`requireDescription: true`)\n  - `src/discovery/helpers.ts`의 `scanSkillsFromDir`을 통한 `skills.customDirectories` 스캔 (비재귀적)\n- 비native 프로바이더는 설명 없이도 스킬을 로드할 수 있음\n\n## 검색 파이프라인\n\n`src/extensibility/skills.ts`의 `discoverSkills()`는 두 단계로 실행됩니다:\n\n1. `loadCapability(\"skills\")`를 통한 **기능 프로바이더**\n2. `scanSkillsFromDir(..., { requireDescription: true })`를 통한 **커스텀 디렉터리** (한 단계 디렉터리 열거)\n\n`skills.enabled`가 `false`이면, 검색은 스킬을 반환하지 않습니다.\n\n### 내장 스킬 프로바이더 및 우선순위\n\n프로바이더 순서는 우선순위 기준(높을수록 우선)이며, 동점일 경우 등록 순서를 따릅니다.\n\n현재 등록된 스킬 프로바이더:\n\n1. `native` (우선순위 100) — `src/discovery/builtin.ts`를 통한 `.xcsh` 사용자/프로젝트 스킬\n2. `claude` (우선순위 80)\n3. 우선순위 70 그룹 (등록 순서):\n   - `claude-plugins`\n   - `agents`\n   - `codex`\n\n중복 제거 키는 스킬 이름입니다. 동일한 이름의 첫 번째 항목이 우선합니다.\n\n### 소스 토글 및 필터링\n\n`discoverSkills()`는 다음 제어를 적용합니다:\n\n- 소스 토글: `enableCodexUser`, `enableClaudeUser`, `enableClaudeProject`, `enablePiUser`, `enablePiProject`\n- 스킬 이름에 대한 글로브 필터:\n  - `ignoredSkills` (제외)\n  - `includeSkills` (포함 허용 목록; 비어 있으면 모두 포함)\n\n필터 순서:\n\n1. 소스 활성화됨\n2. 무시되지 않음\n3. 포함됨 (포함 목록이 있는 경우)\n\ncodex/claude/native 이외의 프로바이더(예: `agents`, `claude-plugins`)의 경우, 활성화는 현재 **임의의** 내장 소스 토글이 활성화되어 있으면 활성화됨으로 폴백됩니다.\n\n### 충돌 및 중복 처리\n\n- 기능 중복 제거는 이미 이름당 첫 번째 스킬을 유지함 (가장 높은 우선순위 프로바이더)\n- `extensibility/skills.ts`는 추가로:\n  - `realpath`로 동일 파일을 중복 제거 (심볼릭 링크 안전)\n  - 나중 스킬 이름이 충돌할 때 충돌 경고 발생\n  - `discoverSkillsFromDir({ dir, source })` 편의 API를 `scanSkillsFromDir`의 씬 어댑터로 유지\n- 커스텀 디렉터리 스킬은 프로바이더 스킬 이후에 병합되며 동일한 충돌 동작을 따름\n\n## 런타임 사용 동작\n\n### 시스템 프롬프트 노출\n\n시스템 프롬프트 구성(`src/system-prompt.ts`)은 검색된 스킬을 다음과 같이 사용합니다:\n\n- `read` 도구를 사용할 수 있는 경우:\n  - 검색된 스킬 목록을 프롬프트에 포함\n- 그렇지 않은 경우:\n  - 검색된 목록 생략\n\n태스크 도구 서브에이전트는 일반 세션 생성을 통해 세션의 검색/제공된 스킬 목록을 수신합니다; 태스크별 스킬 고정 재정의는 없습니다.\n\n### 대화형 `/skill:<name>` 명령어\n\n`skills.enableSkillCommands`가 true이면, 대화형 모드는 검색된 스킬당 하나의 슬래시 명령어를 등록합니다.\n\n`/skill:<name> [args]` 동작:\n\n- `filePath`에서 직접 스킬 파일을 읽음\n- 프론트매터 제거\n- 스킬 본문을 후속 커스텀 메시지로 주입\n- 메타데이터 추가 (`Skill: <path>`, 선택적 `User: <args>`)\n\n## `skill://` URL 동작\n\n`src/internal-urls/skill-protocol.ts`는 다음을 지원합니다:\n\n- `skill://<name>` → 해당 스킬의 `SKILL.md`로 확인\n- `skill://<name>/<relative-path>` → 해당 스킬 디렉터리 내부로 확인\n\n```text\nskill:// URL resolution\n\nskill://pdf\n  -> <pdf-base>/SKILL.md\n\nskill://pdf/references/tables.md\n  -> <pdf-base>/references/tables.md\n\nGuards:\n- reject absolute paths\n- reject `..` traversal\n- reject any resolved path escaping <pdf-base>\n```\n\n확인 세부사항:\n\n- 스킬 이름은 정확히 일치해야 함\n- 상대 경로는 URL 디코딩됨\n- 절대 경로는 거부됨\n- 경로 순회(`..`)는 거부됨\n- 확인된 경로는 `baseDir` 내에 있어야 함\n- 파일이 없으면 명시적 `File not found` 오류 반환\n\n콘텐츠 유형:\n\n- `.md` => `text/markdown`\n- 그 외 모든 것 => `text/plain`\n\n누락된 애셋에 대한 폴백 검색은 수행되지 않습니다.\n\n## 스킬 vs XCSH.md, 명령어, 도구, 훅\n\n### 스킬 vs XCSH.md\n\n- **스킬**: 태스크 컨텍스트에 의해 선택되거나 명시적으로 요청되는 명명된 선택적 기능 팩\n- **XCSH.md/컨텍스트 파일**: 컨텍스트 파일 기능으로 로드되고 레벨/깊이 규칙에 따라 병합되는 영구 지시 파일\n\n`src/discovery/agents-md.ts`는 구체적으로 `cwd`에서 조상 디렉터리를 탐색하여 독립적인 `XCSH.md` 파일을 검색합니다 (최대 깊이 20, 숨겨진 디렉터리 세그먼트 제외).\n\n### 스킬 vs 슬래시 명령어\n\n- **스킬**: 모델이 읽을 수 있는 지식/워크플로우 콘텐츠\n- **슬래시 명령어**: 사용자가 호출하는 명령어 진입점\n- `/skill:<name>`은 스킬 텍스트를 주입하는 편의 래퍼이며, 스킬 검색 의미론을 변경하지 않음\n\n### 스킬 vs 커스텀 도구\n\n- **스킬**: 프롬프트 컨텍스트와 `read`를 통해 로드되는 문서/워크플로우 콘텐츠\n- **커스텀 도구**: 스키마와 런타임 사이드 이펙트를 가진 모델이 호출 가능한 실행 가능 도구 API\n\n### 스킬 vs 훅\n\n- **스킬**: 수동적 콘텐츠\n- **훅**: 실행 중에 동작을 차단/수정할 수 있는 이벤트 기반 런타임 인터셉터\n\n## 검색 로직과 연계된 실용적인 작성 지침\n\n- 각 스킬을 고유한 디렉터리에 배치: `<skills-root>/<skill-name>/SKILL.md`\n- 항상 명시적인 `name` 및 `description` 프론트매터 포함\n- 참조된 애셋은 동일한 스킬 디렉터리 아래에 두고 `skill://<name>/...`으로 접근\n- 중첩 분류(`team/domain/skill`)의 경우, `skills.customDirectories`를 중첩된 상위 디렉터리로 지정; 스캐닝 자체는 비재귀적으로 유지됨\n- 소스 간 중복된 스킬 이름을 피할 것; 프로바이더 우선순위에 따라 첫 번째 일치가 우선함\n",
	"ko/index.md": "---\ntitle: xcsh 문서\ndescription: >-\n  AI-powered development CLI with TypeScript coding agent and Rust native layer\n  for long-lived sessions, MCP support, and platform packaging.\nsidebar:\n  order: 0\n  label: 개요\ni18n:\n  sourceHash: b9288f42bf46\n  translator: machine\n---\n\nxcsh는 TypeScript 코딩 에이전트와 Rust 네이티브 레이어(`pi-natives`)를 갖춘 AI 기반 개발 CLI입니다. 오픈소스\n[`badlogic/pi-mono`](https://github.com/badlogic/pi-mono) 라인을 확장하여\n강화된 런타임, 트리 탐색 및 압축 기능을 갖춘 장기 세션,\nPython IPython 도구, 완전한 MCP 지원, 스킬 시스템, 그리고 Linux, macOS, Windows를 대상으로 하는 플랫폼 패키징을 제공합니다.\n\n## 시작하기\n\n- **[F5 XC 컨텍스트](/runtime-tools/context-command)** — F5 Distributed Cloud\n  테넌트에 연결합니다. 컨텍스트를 생성하고, 전환하며, 네임스페이스와 자격 증명을 관리합니다.\n- **구성** — xcsh가 구성을 탐색, 해석, 계층화하는 방법입니다.\n- **런타임 및 도구** — bash / 노트북 / resolve 도구 런타임과\n  슬래시 명령어 인터페이스입니다.\n- **세션** — 추가 전용 항목 로그, 트리 탐색, 압축, 그리고\n  자율 메모리 시스템입니다.\n- **네이티브 (Rust)** — 셸 / PTY / 미디어 / 검색을 지원하는\n  `pi-natives` N-API 애드온의 아키텍처입니다.\n- **MCP** — 구성, 프로토콜 내부 구조, 런타임 생명주기, 서버 및\n  도구 작성 방법입니다.\n- **확장, 스킬 및 플러그인** — 작성, 로딩, 매칭 규칙,\n  마켓플레이스, 플러그인 설치 프로그램입니다.\n- **프로바이더 및 모델** — 모델 구성, 스트리밍 내부 구조, Python / IPython\n  런타임입니다.\n- **TUI** — 테마 설정, `/tree` 명령어, 확장 및 커스텀 도구를 위한\n  통합 훅입니다.\n\n## 이 문서 세트의 구성 방식\n\n사이드바의 각 최상위 그룹은 에이전트의 하위 시스템에 대응합니다. 그룹 내에서\n페이지는 \"개요\"에서 \"내부 구조\" 순으로 진행되므로, 당면한 작업에\n충분한 맥락을 얻은 시점에서 읽기를 멈출 수 있습니다.\n",
	"ko/mcp/mcp-config.md": "---\ntitle: MCP 설정\ndescription: '코딩 에이전트 런타임을 위한 MCP 서버 설정, 유효성 검사 및 관리.'\nsidebar:\n  order: 1\n  label: 설정\ni18n:\n  sourceHash: ef8b49458ce9\n  translator: machine\n---\n\n# OMP의 MCP 설정\n\n이 가이드에서는 OMP 코딩 에이전트를 위한 MCP 서버를 추가, 편집 및 유효성 검사하는 방법을 설명합니다.\n\n코드 내 참조 소스:\n\n- 런타임 설정 타입: `packages/coding-agent/src/mcp/types.ts`\n- 설정 작성기: `packages/coding-agent/src/mcp/config-writer.ts`\n- 로더 + 유효성 검사: `packages/coding-agent/src/mcp/config.ts`\n- 독립형 `mcp.json` 탐색: `packages/coding-agent/src/discovery/mcp-json.ts`\n- 스키마: `packages/coding-agent/src/config/mcp-schema.json`\n\n## 권장 설정 파일 위치\n\nOMP는 여러 도구(`.claude/`, `.cursor/`, `.vscode/`, `opencode.json` 등)에서 MCP 서버를 탐색할 수 있지만, OMP 네이티브 설정에는 일반적으로 다음 파일 중 하나를 사용해야 합니다:\n\n- 프로젝트: `.xcsh/mcp.json`\n- 사용자: `~/.xcsh/mcp.json`\n\nOMP는 프로젝트 루트의 대체 독립형 파일도 허용합니다:\n\n- `mcp.json`\n- `.mcp.json`\n\nOMP가 설정을 소유하도록 하려면 `.xcsh/mcp.json`을 사용하세요. 다른 MCP 클라이언트도 읽을 수 있는 이식 가능한 대체 파일을 원하는 경우에만 루트 `mcp.json` / `.mcp.json`을 사용하세요.\n\n## 스키마 참조 추가\n\n에디터 자동완성 및 유효성 검사를 위해 파일 상단에 다음 줄을 추가하세요:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {}\n}\n```\n\nOMP는 이제 `/mcp add`, `/mcp enable`, `/mcp disable`, `/mcp reauth` 또는 기타 설정 작성 흐름이 OMP 관리 MCP 파일을 생성하거나 업데이트할 때 이를 자동으로 작성합니다.\n\n## 파일 구조\n\nOMP는 다음과 같은 최상위 구조를 지원합니다:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"server-name\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"some-mcp-server\"]\n    }\n  },\n  \"disabledServers\": [\"server-name\"]\n}\n```\n\n최상위 키:\n\n- `$schema` — 도구 지원을 위한 선택적 JSON Schema URL\n- `mcpServers` — 서버 이름에서 서버 설정으로의 맵\n- `disabledServers` — 탐색된 서버를 이름으로 비활성화하는 데 사용되는 사용자 수준 거부 목록\n\n서버 이름은 `^[a-zA-Z0-9_.-]{1,100}$` 패턴과 일치해야 합니다.\n\n## 지원되는 서버 필드\n\n모든 트랜스포트에 공통인 필드:\n\n- `enabled?: boolean` — `false`일 때 이 서버를 건너뜁니다\n- `timeout?: number` — 밀리초 단위의 연결 타임아웃\n- `auth?: { ... }` — OAuth/API-key 흐름을 위해 OMP에서 사용하는 인증 메타데이터\n- `oauth?: { ... }` — 인증/재인증 중에 사용되는 명시적 OAuth 클라이언트 설정\n\n### `stdio` 트랜스포트\n\n`type`이 생략되면 `stdio`가 기본값입니다.\n\n필수:\n\n- `command: string`\n\n선택:\n\n- `type?: \"stdio\"`\n- `args?: string[]`\n- `env?: Record<string, string>`\n- `cwd?: string`\n\n예시:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/Users/alice/projects\",\n        \"/Users/alice/Documents\"\n      ]\n    }\n  }\n}\n```\n\n이는 공식 Filesystem MCP 서버 패키지(`@modelcontextprotocol/server-filesystem`)를 따릅니다.\n\n### `http` 트랜스포트\n\n필수:\n\n- `type: \"http\"`\n- `url: string`\n\n선택:\n\n- `headers?: Record<string, string>`\n\n예시:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\n이는 GitHub의 호스팅된 GitHub MCP 서버 엔드포인트와 일치합니다.\n\n### `sse` 트랜스포트\n\n필수:\n\n- `type: \"sse\"`\n- `url: string`\n\n선택:\n\n- `headers?: Record<string, string>`\n\n예시:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"legacy-remote\": {\n      \"type\": \"sse\",\n      \"url\": \"https://example.com/mcp/sse\"\n    }\n  }\n}\n```\n\n`sse`는 호환성을 위해 여전히 지원되지만, MCP 사양에서는 이제 새로운 서버에 대해 Streamable HTTP(`type: \"http\"`)를 권장합니다.\n\n## 인증 필드\n\nOMP는 두 가지 인증 관련 객체를 인식합니다.\n\n### `auth`\n\n```json\n{\n  \"type\": \"oauth\" | \"apikey\",\n  \"credentialId\": \"optional-stored-credential-id\",\n  \"tokenUrl\": \"optional-token-endpoint\",\n  \"clientId\": \"optional-client-id\",\n  \"clientSecret\": \"optional-client-secret\"\n}\n```\n\nOMP가 서버의 자격 증명을 복원하는 방법을 기억해야 할 때 사용하세요.\n\n### `oauth`\n\n```json\n{\n  \"clientId\": \"...\",\n  \"clientSecret\": \"...\",\n  \"redirectUri\": \"...\",\n  \"callbackPort\": 3334,\n  \"callbackPath\": \"/oauth/callback\"\n}\n```\n\nMCP 서버가 명시적 OAuth 클라이언트 설정을 요구할 때 사용하세요.\n\nSlack이 현재 가장 명확한 예시입니다. Slack의 MCP 서버는 `https://mcp.slack.com/mcp`에서 호스팅되며, Streamable HTTP를 사용하고, Slack 앱의 클라이언트 자격 증명을 사용한 기밀 OAuth를 요구합니다.\n\n예시:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\nSlack 문서에서 제공하는 관련 Slack 엔드포인트:\n\n- MCP 엔드포인트: `https://mcp.slack.com/mcp`\n- 인가 엔드포인트: `https://slack.com/oauth/v2_user/authorize`\n- 토큰 엔드포인트: `https://slack.com/api/oauth.v2.user.access`\n\n## 일반적인 복사-붙여넣기 예시\n\n### stdio를 통한 파일시스템 서버\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/absolute/path/one\",\n        \"/absolute/path/two\"\n      ]\n    }\n  }\n}\n```\n\n### HTTP를 통한 GitHub 호스팅 서버\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\n### Docker를 통한 GitHub 로컬 서버\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"-i\",\n        \"--rm\",\n        \"-e\",\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\",\n        \"ghcr.io/github/github-mcp-server\"\n      ],\n      \"env\": {\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n      }\n    }\n  }\n}\n```\n\n이는 GitHub의 공식 로컬 Docker 이미지 `ghcr.io/github/github-mcp-server`와 일치합니다.\n\n### OAuth를 통한 Slack 호스팅 서버\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\n## 시크릿 및 변수 해석\n\n이 부분이 사람들이 가장 혼란스러워하는 부분입니다.\n\n### `.xcsh/mcp.json` 및 `~/.xcsh/mcp.json`에서\n\nOMP는 서버를 실행하거나 HTTP 요청을 보내기 전에 `env` 및 `headers` 값을 다음과 같이 해석합니다:\n\n1. 값이 `!`로 시작하면, OMP는 이를 쉘 명령으로 실행하고 트리밍된 stdout을 사용합니다.\n2. 그렇지 않으면 OMP는 먼저 값이 환경 변수 이름과 일치하는지 확인합니다.\n3. 해당 환경 변수가 설정되어 있지 않으면, OMP는 문자열을 그대로 사용합니다.\n\n예시:\n\n```json\n{\n  \"env\": {\n    \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n  },\n  \"headers\": {\n    \"X-MCP-Insiders\": \"true\"\n  }\n}\n```\n\n이는 로컬 시크릿에 대해 다음과 같이 유효하고 편리하다는 것을 의미합니다:\n\n- `\"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"` → 현재 쉘 환경에서 복사\n- `\"Authorization\": \"Bearer hardcoded-token\"` → 리터럴 값 사용\n- `\"Authorization\": \"!printf 'Bearer %s' \\\"$GITHUB_TOKEN\\\"\"` → 명령으로부터 헤더 생성\n\n### 루트 `mcp.json` 및 `.mcp.json`에서\n\n독립형 대체 로더는 탐색 중에 문자열 내의 `${VAR}` 및 `${VAR:-default}`도 확장합니다.\n\n예시:\n\n```json\n{\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\",\n      \"headers\": {\n        \"Authorization\": \"Bearer ${GITHUB_TOKEN}\"\n      }\n    }\n  }\n}\n```\n\n가장 예측 가능한 OMP 동작을 원한다면 `.xcsh/mcp.json`을 사용하고 명시적 env/header 값을 사용하세요.\n\n## `disabledServers`\n\n`disabledServers`는 주로 다른 소스에서 서버가 탐색되었고 해당 도구의 설정을 편집하지 않고 OMP가 이를 무시하도록 하고 싶을 때 사용자 설정 파일(`~/.xcsh/mcp.json`)에서 유용합니다.\n\n예시:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"disabledServers\": [\"github\", \"slack\"]\n}\n```\n\n## `/mcp add` vs JSON 직접 편집\n\n가이드 설정을 원할 때는 `/mcp add`를 사용하세요.\n\n다음과 같은 경우 직접 JSON 편집을 사용하세요:\n\n- 마법사가 아직 프롬프트하지 않는 트랜스포트 또는 인증 옵션이 필요한 경우\n- 다른 MCP 클라이언트의 서버 정의를 붙여넣고 싶은 경우\n- 에디터에서 스키마 기반 유효성 검사를 원하는 경우\n\n편집 후 다음을 사용하세요:\n\n- `/mcp reload` — 현재 세션에서 서버를 재탐색하고 재연결\n- `/mcp list` — 서버가 어떤 설정 파일에서 왔는지 확인\n- `/mcp test <name>` — 단일 서버 테스트\n\n## OMP가 적용하는 유효성 검사 규칙\n\n`packages/coding-agent/src/mcp/config.ts`의 `validateServerConfig()`에서:\n\n- `stdio`는 `command`를 필요로 합니다\n- `http` 및 `sse`는 `url`을 필요로 합니다\n- 서버는 `command`와 `url`을 동시에 설정할 수 없습니다\n- 알 수 없는 `type` 값은 거부됩니다\n\n실용적 의미:\n\n- `type`을 생략하면 `stdio`를 의미합니다\n- 원격 서버 설정을 붙여넣고 `\"type\": \"http\"`를 잊으면, OMP는 이를 `stdio`로 처리하고 `command`가 누락되었다고 경고합니다\n- `sse`는 호환성을 위해 여전히 유효하지만, 새로운 호스팅 서버는 일반적으로 `http`로 설정해야 합니다\n\n## 탐색 및 우선순위\n\nOMP는 파일 간에 중복된 서버 정의를 병합하지 않습니다. 탐색 프로바이더에 우선순위가 지정되며, 더 높은 우선순위의 정의가 우선합니다.\n\n실제로:\n\n- OMP 전용 오버라이드를 원할 때는 `.xcsh/mcp.json` 또는 `~/.xcsh/mcp.json`을 사용하세요\n- 가능하면 도구 간에 서버 이름을 고유하게 유지하세요\n- 서드파티 설정이 원하지 않는 서버를 계속 다시 도입하는 경우 사용자 설정에서 `disabledServers`를 사용하세요\n\n## 문제 해결\n\n### `Server \"name\": stdio server requires \"command\" field`\n\n원격 서버에 `type: \"http\"`를 생략했을 가능성이 높습니다.\n\n### `Server \"name\": both \"command\" and \"url\" are set`\n\n하나의 트랜스포트를 선택하세요. OMP는 `command`를 stdio로, `url`을 http/sse로 처리합니다.\n\n### `/mcp add`는 작동했지만 서버가 여전히 연결되지 않음\n\nJSON은 유효하지만 서버에 여전히 접근할 수 없을 수 있습니다. `/mcp test <name>`을 사용하고 다음을 확인하세요:\n\n- 바이너리 또는 Docker 이미지가 존재하는지\n- 필수 환경 변수가 설정되어 있는지\n- 원격 URL에 접근 가능한지\n- OAuth 또는 API 토큰이 유효한지\n\n### 서버가 다른 도구의 설정에는 있지만 OMP에는 없음\n\n`/mcp list`를 실행하세요. OMP는 많은 서드파티 MCP 파일을 탐색하지만, `mcp.enableProjectConfig` 설정을 통해 프로젝트 수준 로딩을 비활성화할 수도 있습니다.\n\n## 참고 자료\n\n- MCP 트랜스포트 사양: <https://modelcontextprotocol.io/specification/2025-03-26/basic/transports>\n- 파일시스템 서버 패키지: <https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem>\n- GitHub MCP 서버: <https://github.com/github/github-mcp-server>\n- Slack MCP 서버 문서: <https://docs.slack.dev/ai/slack-mcp-server/>\n",
	"ko/mcp/mcp-protocol-transports.md": "---\ntitle: MCP 프로토콜 및 전송 내부 구조\ndescription: 'stdio, SSE, 스트리밍 가능한 HTTP 전송 계층을 포함한 MCP 프로토콜 구현.'\nsidebar:\n  order: 2\n  label: 프로토콜 & 전송\ni18n:\n  sourceHash: 48632064dd00\n  translator: machine\n---\n\n# MCP 프로토콜 및 전송 내부 구조\n\n이 문서는 coding-agent가 MCP JSON-RPC 메시징을 구현하는 방식과 프로토콜 관심사가 전송 관심사로부터 어떻게 분리되는지를 설명합니다.\n\n## 범위\n\n다루는 내용:\n\n- JSON-RPC 요청/응답 및 알림 흐름\n- stdio 및 HTTP/SSE 전송에 대한 요청 상관관계 및 생명주기\n- 타임아웃 및 취소 동작\n- 오류 전파 및 잘못된 페이로드 처리\n- 전송 선택 경계 (`stdio` vs `http`/`sse`)\n- 재연결/재시도 책임이 전송 수준인지 관리자 수준인지\n\n확장 기능 작성 UX나 명령어 UI는 다루지 않습니다.\n\n## 구현 파일\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/transports/stdio.ts`](../../packages/coding-agent/src/mcp/transports/stdio.ts)\n- [`src/mcp/transports/http.ts`](../../packages/coding-agent/src/mcp/transports/http.ts)\n- [`src/mcp/transports/index.ts`](../../packages/coding-agent/src/mcp/transports/index.ts)\n- [`src/mcp/json-rpc.ts`](../../packages/coding-agent/src/mcp/json-rpc.ts)\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n\n## 계층 경계\n\n### 프로토콜 계층 (JSON-RPC + MCP 메서드)\n\n- 메시지 형태는 `types.ts`에 정의되어 있습니다 (`JsonRpcRequest`, `JsonRpcNotification`, `JsonRpcResponse`, `JsonRpcMessage`).\n- MCP 클라이언트 로직(`client.ts`)은 메서드 순서와 세션 핸드셰이크를 결정합니다:\n  1. `initialize` 요청\n  2. `notifications/initialized` 알림\n  3. `tools/list`, `tools/call`과 같은 메서드 호출\n\n### 전송 계층 (`MCPTransport`)\n\n`MCPTransport`는 전달 및 생명주기를 추상화합니다:\n\n- `request(method, params, options?) -> Promise<T>`\n- `notify(method, params?) -> Promise<void>`\n- `close()`\n- `connected`\n- 선택적 콜백: `onClose`, `onError`, `onNotification`\n\n전송 구현체는 프레이밍과 I/O 세부사항을 관리합니다:\n\n- `StdioTransport`: 하위 프로세스 stdio를 통한 줄바꿈 구분 JSON\n- `HttpTransport`: HTTP POST를 통한 JSON-RPC, 선택적 SSE 응답/수신 포함\n\n### 현재 주의사항\n\n전송 콜백(`onClose`, `onError`, `onNotification`)은 구현되어 있지만, 현재 `MCPClient`/`MCPManager` 흐름은 이러한 콜백에 재연결 로직을 연결하지 않습니다. 알림은 호출자가 핸들러를 등록한 경우에만 소비됩니다.\n\n## 전송 선택\n\n`client.ts:createTransport()`는 설정에서 전송을 선택합니다:\n\n- `type`이 생략되거나 `\"stdio\"` -> `createStdioTransport`\n- `\"http\"` 또는 `\"sse\"` -> `createHttpTransport`\n\n`\"sse\"`는 HTTP 전송 변형(동일한 클래스)으로 처리되며, 별도의 전송 구현체가 아닙니다.\n\n## JSON-RPC 메시지 흐름 및 상관관계\n\n## 요청 ID\n\n각 전송은 요청별 ID를 생성합니다 (`Math.random` + 타임스탬프 문자열). ID는 전송 로컬 상관관계 토큰입니다.\n\n## Stdio 상관관계 경로\n\n- 발신 요청은 하나의 JSON 객체 + `\\n`으로 직렬화됩니다.\n- `#pendingRequests: Map<id, {resolve,reject}>`가 진행 중인 요청을 저장합니다.\n- 읽기 루프는 stdout에서 JSONL을 파싱하고 `#handleMessage`를 호출합니다.\n- 인바운드 메시지에 일치하는 `id`가 있으면 요청이 resolve/reject됩니다.\n- 인바운드 메시지에 `method`가 있고 `id`가 없으면 알림으로 처리되어 `onNotification`에 전달됩니다.\n\n알 수 없는 ID는 무시됩니다 (거부 없음, 오류 콜백 없음).\n\n## HTTP 상관관계 경로\n\n- 발신 요청은 JSON 본문과 생성된 `id`가 포함된 HTTP `POST`입니다.\n- 비-SSE 응답 경로: 하나의 JSON-RPC 응답을 파싱하고 `result`를 반환하거나 `error` 시 throw합니다.\n- SSE 응답 경로 (`Content-Type: text/event-stream`): 이벤트를 스트리밍하고, 예상 요청 ID와 일치하며 `result` 또는 `error`가 있는 첫 번째 메시지를 반환합니다.\n- `method`가 있고 `id`가 없는 SSE 메시지는 알림으로 처리됩니다.\n\n일치하는 응답 없이 SSE 스트림이 종료되면 요청은 `No response received for request ID ...`로 실패합니다.\n\n## 알림\n\n클라이언트는 `transport.notify(...)`를 통해 JSON-RPC 알림을 발신합니다.\n\n- Stdio: 알림 프레임(`jsonrpc`, `method`, 선택적 `params`)과 줄바꿈을 stdin에 씁니다.\n- HTTP: `id` 없이 POST 본문을 전송합니다; 성공 시 `2xx` 또는 `202 Accepted`를 수락합니다.\n\n서버 시작 알림은 전송 `onNotification`을 통해서만 표시됩니다; 관리자/클라이언트에 기본 전역 구독자는 없습니다.\n\n## Stdio 전송 내부 구조\n\n## 생명주기 및 상태 전이\n\n- 초기: `connected=false`, `process=null`, pending 맵 비어있음\n- `connect()`:\n  - 구성된 command/args/env/cwd로 하위 프로세스 생성\n  - connected 표시\n  - stdout 읽기 루프 시작 (`readJsonl`)\n  - stderr 루프 시작 (읽기/폐기; 현재 무음)\n- `close()`:\n  - disconnected 표시\n  - 모든 대기 중인 요청 거부 (`Transport closed`)\n  - 하위 프로세스 종료\n  - 읽기 루프 종료 대기\n  - `onClose` 발신\n\n읽기 루프가 예기치 않게 종료되면, `finally`가 `#handleClose()`를 트리거하여 동일한 대기 중 요청 거부 및 닫기 콜백을 수행합니다.\n\n## 타임아웃 및 취소\n\n요청별:\n\n- 타임아웃 기본값은 `config.timeout ?? 30000`\n- 호출자의 선택적 `AbortSignal`\n- abort와 timeout 모두 대기 중인 promise를 거부하고 맵 항목을 정리합니다\n\n취소는 로컬에서만 이루어집니다: 전송은 서버에 프로토콜 수준의 취소 알림을 전송하지 않습니다.\n\n## 잘못된 페이로드 처리\n\n읽기 루프에서:\n\n- 파싱된 각 JSONL 라인은 `try/catch` 내의 `#handleMessage`에 전달됩니다\n- 잘못된/유효하지 않은 메시지 처리 예외는 무시됩니다 (`Skip malformed lines` 주석)\n- 루프가 계속되므로 하나의 잘못된 메시지가 연결을 종료하지 않습니다\n\n기본 스트림 파서가 throw하면, `onError`가 호출되고(여전히 연결 중인 경우), 연결이 닫힙니다.\n\n## 연결 끊김/실패 동작\n\n프로세스가 종료되거나 스트림이 닫힐 때:\n\n- 모든 진행 중인 요청은 `Transport closed`로 거부됩니다\n- 자동 재시작이나 재연결 없음\n- 상위 계층이 새 전송을 생성하여 재연결해야 합니다\n\n## 배압/스트리밍 참고사항\n\n- 발신 쓰기는 drain 시맨틱을 기다리지 않고 `stdin.write()` + `flush()`를 사용합니다.\n- 전송에 명시적인 큐나 high-watermark 관리가 없습니다.\n- 인바운드 처리는 스트림 기반(`readJsonl`에 대한 `for await`)이며, 한 번에 하나의 파싱된 메시지를 처리합니다.\n\n## HTTP/SSE 전송 내부 구조\n\n## 생명주기 및 연결 시맨틱\n\nHTTP 전송은 논리적 연결 상태를 가지지만, 요청 경로는 HTTP 호출별로 무상태입니다:\n\n- `connect()`는 `connected=true`를 설정합니다 (소켓/세션 핸드셰이크 없음)\n- `Mcp-Session-Id` 헤더를 통한 선택적 서버 세션 추적\n- `close()`는 선택적으로 `Mcp-Session-Id`와 함께 `DELETE`를 전송하고, SSE 리스너를 중단하며, `onClose`를 발신합니다\n\n따라서 `connected`는 \"전송 사용 가능\"을 의미하며, \"영구 스트림이 설정됨\"을 의미하지 않습니다.\n\n## 세션 헤더 동작\n\n- POST 응답 시 `Mcp-Session-Id` 헤더가 있으면 전송이 이를 저장합니다.\n- 후속 요청/알림에 `Mcp-Session-Id`가 포함됩니다.\n- `close()`는 HTTP DELETE로 서버 세션 종료를 시도합니다; 종료 실패는 무시됩니다.\n\n## 타임아웃 및 취소\n\n`request()`와 `notify()` 모두:\n\n- 타임아웃은 `AbortController`를 사용합니다 (`config.timeout ?? 30000`)\n- 제공된 경우 외부 signal은 `AbortSignal.any([...])`를 통해 병합됩니다\n- AbortError 처리는 호출자 abort와 타임아웃을 구분합니다\n\n발생하는 오류:\n\n- 타임아웃: `Request timeout after ...ms` (또는 `SSE response timeout ...`, `Notify timeout ...`)\n- 호출자 abort: 외부 signal이 이미 중단된 경우 원래 AbortError가 다시 throw됩니다\n\n## HTTP 오류 전파\n\n비-OK 응답 시:\n\n- 응답 텍스트가 throw된 오류에 포함됩니다 (`HTTP <status>: <text>`)\n- 있는 경우 `WWW-Authenticate` 및 `Mcp-Auth-Server`의 인증 힌트가 추가됩니다\n\nJSON-RPC 오류 객체인 경우:\n\n- `MCP error <code>: <message>`를 throw합니다\n\n잘못된 JSON 본문(`response.json()` 실패)은 파싱 예외로 전파됩니다.\n\n## SSE 동작 및 모드\n\n두 가지 SSE 경로가 존재합니다:\n\n1. **요청별 SSE 응답** (`#parseSSEResponse`)\n   - POST 응답 콘텐츠 타입이 `text/event-stream`일 때 사용됩니다\n   - 일치하는 응답 id가 발견될 때까지 스트림을 소비합니다\n   - 동일한 스트림에서 인터리브된 알림을 처리할 수 있습니다\n\n2. **백그라운드 SSE 리스너** (`startSSEListener()`)\n   - 서버 시작 알림을 위한 선택적 GET 리스너\n   - 현재 MCP 관리자/클라이언트에 의해 자동으로 시작되지 않습니다\n   - GET이 `405`를 반환하면 리스너는 조용히 비활성화됩니다 (서버가 이 모드를 지원하지 않음)\n\n## 잘못된 페이로드 및 연결 끊김 처리\n\nSSE JSON 파싱 오류는 `readSseJson`에서 발생하여 요청/리스너를 거부합니다.\n\n- 요청 SSE 파싱 오류는 활성 요청을 거부합니다.\n- 백그라운드 리스너 오류는 `onError`를 트리거합니다 (AbortError 제외).\n- 백그라운드 리스너에 대한 자동 재연결 없음.\n\n## `json-rpc.ts` 유틸리티 vs 전송 추상화\n\n`src/mcp/json-rpc.ts`는 `MCPClient`/`MCPManager`가 사용하는 `MCPTransport` 추상화가 아닌, 직접 HTTP MCP 호출(Exa 통합에서 사용)을 위한 `callMCP()` 및 `parseSSE()` 헬퍼를 제공합니다.\n\n`HttpTransport`와의 주요 차이점:\n\n- 전체 응답 텍스트를 먼저 파싱한 다음 첫 번째 `data:` 라인을 추출하며(`parseSSE`), JSON 폴백 포함\n- 요청 타임아웃 관리 없음, abort API 없음, session-id 처리 없음, 전송 생명주기 없음\n- 원시 JSON-RPC 봉투 객체를 반환합니다\n\n이 경로는 경량이지만 전체 전송 구현보다 덜 견고합니다.\n\n## 재시도/재연결 책임\n\n## 전송 수준\n\n현재 전송 구현체는 다음을 수행하지 **않습니다**:\n\n- 실패한 요청 재시도\n- stdio 프로세스 종료 후 재연결\n- SSE 리스너 재연결\n- 연결 끊김 후 진행 중인 요청 재전송\n\n빠르게 실패하고 오류를 전파합니다.\n\n## 관리자/클라이언트 수준\n\n`MCPManager`는 탐색/초기 연결 오케스트레이션을 처리하며, 연결 흐름을 다시 실행하여 재연결할 수 있습니다 (`connectToServer`/`discoverAndConnect` 경로). 런타임 실패 콜백에서 이미 연결된 전송을 자동 복구하지 않습니다.\n\n`MCPManager`는 느린 서버에 대한 시작 시 폴백 동작(캐시에서의 지연 도구)을 가지고 있지만, 이는 도구 가용성 폴백이지 전송 재시도가 아닙니다.\n\n## 실패 시나리오 요약\n\n- **잘못된 stdio 메시지 라인**: 무시됨; 스트림 계속됨.\n- **Stdio 스트림/프로세스 종료**: 전송 닫힘; 대기 중인 요청이 `Transport closed`로 거부됨.\n- **HTTP 비-2xx**: 요청/알림이 HTTP 오류를 throw함.\n- **유효하지 않은 JSON 응답**: 파싱 예외가 전파됨.\n- **일치하는 id 없이 SSE 종료**: 요청이 `No response received for request ID ...`로 실패함.\n- **타임아웃**: 전송별 타임아웃 오류.\n- **호출자 abort**: 호출자 signal에서 AbortError/reason이 전파됨.\n\n## 실용적 경계 규칙\n\n메시지 형태, id 상관관계, 또는 MCP 메서드 순서에 관한 사항은 프로토콜/클라이언트 로직에 속합니다.\n\n프레이밍(JSONL vs HTTP/SSE), 스트림 파싱, fetch/spawn 생명주기, 타임아웃 클럭, 또는 연결 해제에 관한 사항은 전송 구현체에 속합니다.\n",
	"ko/mcp/mcp-runtime-lifecycle.md": "---\ntitle: MCP 런타임 생명주기\ndescription: '초기화부터 도구 등록, 상태 모니터링, 종료까지의 MCP 서버 프로세스 생명주기.'\nsidebar:\n  order: 3\n  label: 런타임 생명주기\ni18n:\n  sourceHash: d04cefaf38f8\n  translator: machine\n---\n\n# MCP 런타임 생명주기\n\n이 문서는 코딩 에이전트 런타임에서 MCP 서버가 발견, 연결, 도구로 노출, 갱신 및 종료되는 방식을 설명합니다.\n\n## 생명주기 개요\n\n1. **SDK 시작** 시 `discoverAndLoadMCPTools()`를 호출합니다(MCP가 비활성화되지 않은 경우).\n2. **발견** (`loadAllMCPConfigs`)은 기능 소스에서 MCP 서버 설정을 확인하고, 비활성화된/프로젝트/Exa 항목을 필터링하며, 소스 메타데이터를 보존합니다.\n3. **매니저 연결 단계** (`MCPManager.connectServers`)는 서버별 연결 + `tools/list`를 병렬로 시작합니다.\n4. **빠른 시작 게이트**는 최대 250ms 대기 후 다음을 반환할 수 있습니다:\n   - 완전히 로드된 `MCPTool`,\n   - 서버별 실패,\n   - 또는 아직 대기 중인 서버에 대한 캐시된 `DeferredMCPTool`.\n5. **SDK 연결**은 MCP 도구를 세션의 런타임 도구 레지스트리에 병합합니다.\n6. **라이브 세션**은 `/mcp` 플로우(`disconnectAll` + 재발견 + `session.refreshMCPTools`)를 통해 MCP 도구를 갱신할 수 있습니다.\n7. **종료**는 호출자가 `disconnectServer`/`disconnectAll`을 호출할 때 발생하며, 매니저는 연결 해제된 서버의 MCP 도구 등록도 제거합니다.\n\n## 발견 및 로드 단계\n\n### SDK로부터의 진입 경로\n\n`src/sdk.ts`의 `createAgentSession()`은 `enableMCP`이 true(기본값)일 때 MCP 시작을 수행합니다:\n\n- `discoverAndLoadMCPTools(cwd, { ... })`를 호출하고,\n- `authStorage`, 캐시 저장소, `mcp.enableProjectConfig` 설정을 전달하며,\n- 항상 `filterExa: true`로 설정하고,\n- 서버별 로드/연결 오류를 로깅하며,\n- 반환된 매니저를 `toolSession.mcpManager`와 세션 결과에 저장합니다.\n\n`enableMCP`이 false이면 MCP 발견이 완전히 건너뛰어집니다.\n\n### 설정 발견 및 필터링\n\n`loadAllMCPConfigs()` (`src/mcp/config.ts`)은 기능 발견을 통해 정규 MCP 서버 항목을 로드한 후 레거시 `MCPServerConfig`로 변환합니다.\n\n필터링 동작:\n\n- `enableProjectConfig: false`는 프로젝트 수준 항목(`_source.level === \"project\"`)을 제거합니다.\n- `enabled: false`인 서버는 연결 시도 전에 건너뛰어집니다.\n- Exa 서버는 기본적으로 필터링되며, API 키는 네이티브 Exa 도구 통합을 위해 추출됩니다.\n\n결과에는 `configs`와 `sources`(나중에 프로바이더 레이블링에 사용되는 메타데이터) 모두 포함됩니다.\n\n### 발견 수준의 실패 동작\n\n`discoverAndLoadMCPTools()`는 두 가지 실패 유형을 구분합니다:\n\n- **발견 하드 실패** (`manager.discoverAndConnect`의 예외, 일반적으로 설정 발견에서 발생): 빈 도구 세트와 하나의 합성 오류 `{ path: \".mcp.json\", error }`를 반환합니다.\n- **서버별 런타임/연결 실패**: 매니저가 `errors` 맵과 함께 부분 성공을 반환하며, 다른 서버는 계속 진행됩니다.\n\n따라서 개별 MCP 서버가 실패해도 전체 에이전트 세션이 실패하지 않습니다.\n\n## 매니저 상태 모델\n\n`MCPManager`는 별도의 레지스트리로 런타임 생명주기를 추적합니다:\n\n- `#connections: Map<string, MCPServerConnection>` — 완전히 연결된 서버.\n- `#pendingConnections: Map<string, Promise<MCPServerConnection>>` — 핸드셰이크 진행 중.\n- `#pendingToolLoads: Map<string, Promise<{ connection, serverTools }>>` — 연결되었지만 도구가 아직 로딩 중.\n- `#tools: CustomTool[]` — 호출자에게 노출되는 현재 MCP 도구 뷰.\n- `#sources: Map<string, SourceMeta>` — 연결 완료 전의 프로바이더/소스 메타데이터.\n\n`getConnectionStatus(name)`는 이러한 맵에서 상태를 도출합니다:\n\n- `#connections`에 있으면 `connected`,\n- 대기 중인 연결 또는 대기 중인 도구 로드가 있으면 `connecting`,\n- 그 외에는 `disconnected`.\n\n## 연결 수립 및 시작 타이밍\n\n## 서버별 연결 파이프라인\n\n`connectServers()`에서 발견된 각 서버에 대해:\n\n1. 소스 메타데이터 저장/업데이트,\n2. 이미 연결됨/대기 중이면 건너뛰기,\n3. 전송 필드 검증 (`validateServerConfig`),\n4. 인증/셸 치환 해석 (`#resolveAuthConfig`),\n5. `connectToServer(name, resolvedConfig)` 호출,\n6. `listTools(connection)` 호출,\n7. 도구 정의 캐시 (`MCPToolCache.set`) 최선 노력.\n\n`connectToServer()` 동작 (`src/mcp/client.ts`):\n\n- stdio 또는 HTTP/SSE 전송을 생성하고,\n- MCP `initialize` + `notifications/initialized`를 수행하며,\n- 타임아웃 사용 (`config.timeout` 또는 기본 30초),\n- 초기화 실패 시 전송을 닫습니다.\n\n### 빠른 시작 게이트 + 지연 폴백\n\n`connectServers()`는 다음 사이의 경쟁을 대기합니다:\n\n- 모든 연결/도구 로드 작업 완료, 그리고\n- `STARTUP_TIMEOUT_MS = 250`.\n\n250ms 이후:\n\n- 완료된 작업은 라이브 `MCPTool`이 되고,\n- 거부된 작업은 서버별 오류를 생성하며,\n- 아직 대기 중인 작업은:\n  - 캐시된 도구 정의가 있으면 (`MCPToolCache.get`) `DeferredMCPTool`을 생성하고,\n  - 그렇지 않으면 대기 중인 작업이 완료될 때까지 블로킹합니다.\n\n이는 하이브리드 시작 모델입니다: 캐시가 있을 때 빠른 반환, 캐시가 없을 때 정확성을 위한 대기.\n\n### 백그라운드 완료 동작\n\n대기 중인 각 `toolsPromise`에는 최종적으로 다음을 수행하는 백그라운드 연속 작업도 있습니다:\n\n- `#replaceServerTools`를 통해 매니저 상태에서 해당 서버의 도구 슬라이스를 교체하고,\n- 캐시를 기록하며,\n- 시작 이후에만 지연 실패를 로깅합니다 (`allowBackgroundLogging`).\n\n## 도구 노출 및 라이브 세션 가용성\n\n### 시작 등록\n\n`discoverAndLoadMCPTools()`는 매니저 도구를 `LoadedCustomTool[]`로 변환하고 경로를 장식합니다(알려진 경우 `mcp:<server> via <providerName>`).\n\n`createAgentSession()`은 이 도구들을 `customTools`에 추가하며, 이들은 래핑되어 `mcp_<server>_<tool>`과 같은 이름으로 런타임 도구 레지스트리에 추가됩니다.\n\n### 도구 호출\n\n- `MCPTool`은 이미 연결된 `MCPServerConnection`을 통해 도구를 호출합니다.\n- `DeferredMCPTool`은 호출 전에 `waitForConnection(server)`를 대기합니다; 이를 통해 연결이 준비되기 전에 캐시된 도구가 존재할 수 있습니다.\n\n둘 다 구조화된 도구 출력을 반환하고 전송/도구 오류를 `MCP error: ...` 도구 콘텐츠로 변환합니다(중단은 중단으로 유지).\n\n## 갱신/리로드 경로 (시작 vs 라이브 리로드)\n\n### 초기 시작 경로\n\n- `sdk.ts`에서의 일회성 발견/로드,\n- 도구가 초기 세션 도구 레지스트리에 등록됩니다.\n\n### 대화형 리로드 경로\n\n`/mcp reload` 경로 (`src/modes/controllers/mcp-command-controller.ts`)는 다음을 수행합니다:\n\n1. `mcpManager.disconnectAll()`,\n2. `mcpManager.discoverAndConnect()`,\n3. `session.refreshMCPTools(mcpManager.getTools())`.\n\n`session.refreshMCPTools()` (`src/session/agent-session.ts`)는 모든 `mcp_` 도구를 제거하고, 최신 MCP 도구를 재래핑하며, 도구 세트를 재활성화하여 세션을 재시작하지 않고도 MCP 변경사항이 적용되도록 합니다.\n\n지연 연결을 위한 후속 경로도 있습니다: 특정 서버를 대기한 후 상태가 `connected`가 되면, `session.refreshMCPTools(...)`를 재실행하여 새로 사용 가능한 도구가 세션 내에서 재바인딩됩니다.\n\n## 상태 확인, 재연결 및 부분 실패 동작\n\n현재 런타임 동작은 의도적으로 최소화되어 있습니다:\n\n- 매니저/클라이언트에 **자율 상태 모니터가 없습니다**.\n- 전송이 끊어졌을 때 **자동 재연결 루프가 없습니다**.\n- 매니저는 전송의 `onClose`/`onError`를 구독하지 않으며, 상태는 레지스트리 기반입니다.\n- 재연결은 명시적입니다: 리로드 플로우 또는 직접 `connectServers()` 호출.\n\n운영적으로:\n\n- 하나의 서버 실패가 정상 서버의 도구를 제거하지 않으며,\n- 연결/목록 실패는 서버별로 격리되고,\n- 도구 캐시와 백그라운드 업데이트는 최선 노력입니다(경고/오류 로깅, 하드 중지 없음).\n\n## 종료 의미론\n\n### 서버 수준 종료\n\n`disconnectServer(name)`:\n\n- 대기 중인 항목/소스 메타데이터를 제거하고,\n- 연결된 경우 전송을 닫으며,\n- 매니저 상태에서 해당 서버의 `mcp_` 도구를 제거합니다.\n\n### 전역 종료\n\n`disconnectAll()`:\n\n- `Promise.allSettled`로 모든 활성 전송을 닫고,\n- 대기 맵, 소스, 연결 및 매니저 도구 목록을 초기화합니다.\n\n현재 연결에서 명시적 종료는 MCP 명령 플로우(리로드/제거/비활성화)에서 사용됩니다. 시작 경로 자체에는 별도의 자동 매니저 해제 훅이 없으며, 결정론적 MCP 종료가 필요한 경우 호출자가 매니저 연결 해제 메서드를 호출할 책임이 있습니다.\n\n## 실패 모드 및 보장\n\n| 시나리오 | 동작 | 하드 실패 vs 최선 노력 |\n| --- | --- | --- |\n| 발견 예외 발생 (기능/설정 로드 경로) | 로더가 빈 도구 + 합성 `.mcp.json` 오류 반환 | 최선 노력 세션 시작 |\n| 잘못된 서버 설정 | 검증 오류 항목으로 서버 건너뛰기 | 서버별 최선 노력 |\n| 연결 타임아웃/초기화 실패 | 서버 오류 기록; 다른 서버 계속 | 서버별 최선 노력 |\n| 시작 시 `tools/list`가 대기 중이고 캐시 적중 | 지연 도구 즉시 반환 | 최선 노력 빠른 시작 |\n| 시작 시 `tools/list`가 대기 중이고 캐시 없음 | 시작이 대기 완료까지 대기 | 정확성을 위한 하드 대기 |\n| 지연된 백그라운드 도구 로드 실패 | 시작 게이트 이후 로깅 | 최선 노력 로깅 |\n| 런타임 전송 끊김 | 자동 재연결 없음; 재연결/리로드까지 이후 호출 실패 | 수동 조치를 통한 최선 노력 복구 |\n\n## 공개 API 표면\n\n`src/mcp/index.ts`는 외부 호출자를 위해 로더/매니저/클라이언트 API를 재내보냅니다. `src/sdk.ts`는 동일한 로더 결과 형태를 반환하는 편의 래퍼로 `discoverMCPServers()`를 노출합니다.\n\n## 구현 파일\n\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts) — 로더 파사드, 발견 오류 정규화, `LoadedCustomTool` 변환.\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts) — 생명주기 상태 레지스트리, 병렬 연결/목록 플로우, 갱신/연결 해제.\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts) — 전송 설정, 초기화 핸드셰이크, 목록/호출/연결 해제.\n- [`src/mcp/index.ts`](../../packages/coding-agent/src/mcp/index.ts) — MCP 모듈 API 내보내기.\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — 세션/도구 레지스트리로의 시작 연결.\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts) — 매니저가 사용하는 설정 발견/필터링/검증.\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts) — `MCPTool` 및 `DeferredMCPTool` 런타임 동작.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — `refreshMCPTools` 라이브 재바인딩.\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts) — 대화형 리로드/재연결 플로우.\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — 부모 매니저 연결을 통한 서브에이전트 MCP 프록싱.\n",
	"ko/mcp/mcp-server-tool-authoring.md": "---\ntitle: MCP 서버 및 도구 작성\ndescription: 코딩 에이전트를 위한 커스텀 MCP 서버 구축 및 도구 등록 가이드.\nsidebar:\n  order: 4\n  label: 서버 및 도구 작성\ni18n:\n  sourceHash: 160e7560ef1f\n  translator: machine\n---\n\n# MCP 서버 및 도구 작성\n\n이 문서는 MCP 서버 정의가 코딩 에이전트에서 호출 가능한 `mcp_*` 도구로 변환되는 방식과, 설정이 유효하지 않거나, 중복되거나, 비활성화되거나, 인증이 필요한 경우 운영자가 예상해야 할 사항을 설명합니다.\n\n## 아키텍처 개요\n\n```text\nConfig sources (.xcsh/.claude/.cursor/.vscode/mcp.json, mcp.json, etc.)\n  -> discovery providers normalize to canonical MCPServer\n  -> capability loader dedupes by server name (higher provider priority wins)\n  -> loadAllMCPConfigs converts to MCPServerConfig + skips enabled:false\n  -> MCPManager connects/listTools (with auth/header/env resolution)\n  -> MCPTool/DeferredMCPTool bridge exposes tools as mcp_<server>_<tool>\n  -> AgentSession.refreshMCPTools replaces live MCP tools immediately\n```\n\n## 1) 서버 설정 모델 및 유효성 검사\n\n`src/mcp/types.ts`는 MCP 설정 작성자와 런타임에서 사용하는 작성 형식을 정의합니다:\n\n- `stdio` (`type`이 누락된 경우 기본값): `command` 필수, `args`, `env`, `cwd` 선택\n- `http`: `url` 필수, `headers` 선택\n- `sse`: `url` 필수, `headers` 선택 (호환성을 위해 유지)\n- 공유 필드: `enabled`, `timeout`, `auth`\n\n`validateServerConfig()` (`src/mcp/config.ts`)는 전송 계층 기본 사항을 강제합니다:\n\n- `command`와 `url`을 동시에 설정한 설정을 거부합니다\n- stdio에는 `command` 필수\n- http/sse에는 `url` 필수\n- 알 수 없는 `type`을 거부합니다\n\n`config-writer.ts`는 추가/업데이트 작업에 이 유효성 검사를 적용하며 서버 이름도 검증합니다:\n\n- 비어 있지 않아야 함\n- 최대 100자\n- `[a-zA-Z0-9_.-]`만 허용\n\n### 전송 계층 주의사항\n\n- `type`이 생략되면 stdio를 의미합니다. HTTP/SSE를 의도했지만 `type`을 생략한 경우 `command`가 필수가 됩니다.\n- `sse`는 여전히 허용되지만 내부적으로는 HTTP 전송(`createHttpTransport`)으로 처리됩니다.\n- 유효성 검사는 구조적이며 연결 가능성을 검사하지 않습니다: 구문적으로 유효한 URL도 연결 시점에 실패할 수 있습니다.\n\n## 2) 검색, 정규화 및 우선순위\n\n### 기능 기반 검색\n\n`loadAllMCPConfigs()` (`src/mcp/config.ts`)는 `loadCapability(mcpCapability.id)`를 통해 정규 `MCPServer` 항목을 로드합니다.\n\n기능 계층(`src/capability/index.ts`)은 다음을 수행합니다:\n\n1. 우선순위 순서로 프로바이더를 로드합니다\n2. `server.name` 기준으로 중복을 제거합니다 (먼저 발견된 것이 승리 = 가장 높은 우선순위)\n3. 중복 제거된 항목을 검증합니다\n\n결과: 여러 소스에 걸친 중복 서버 이름은 병합되지 않습니다. 하나의 정의만 적용되며, 낮은 우선순위의 중복은 가려집니다.\n\n### `.mcp.json` 및 관련 파일\n\n`src/discovery/mcp-json.ts`의 전용 폴백 프로바이더는 프로젝트 루트의 `mcp.json` 및 `.mcp.json`을 읽습니다 (낮은 우선순위).\n\n실제로 MCP 서버는 더 높은 우선순위의 프로바이더에서도 제공됩니다 (예: 네이티브 `.xcsh/...` 및 도구별 설정 디렉터리). 작성 지침:\n\n- 명시적 제어를 위해 `.xcsh/mcp.json` (프로젝트) 또는 `~/.xcsh/mcp.json` (사용자)를 선호하세요.\n- 폴백 호환성이 필요할 때 루트 `mcp.json` / `.mcp.json`을 사용하세요.\n- 여러 소스에서 동일한 서버 이름을 재사용하면 병합이 아닌 우선순위에 의한 가려짐이 발생합니다.\n\n### 정규화 동작\n\n`convertToLegacyConfig()` (`src/mcp/config.ts`)는 정규 `MCPServer`를 런타임 `MCPServerConfig`로 매핑합니다.\n\n주요 동작:\n\n- 전송 계층은 `server.transport ?? (command ? \"stdio\" : url ? \"http\" : \"stdio\")`로 추론됩니다\n- 비활성화된 서버(`enabled === false`)는 연결 전에 제거됩니다\n- 선택적 필드는 존재하는 경우 보존됩니다\n\n### 검색 중 환경 변수 확장\n\n`mcp-json.ts`는 `expandEnvVarsDeep()`를 사용하여 문자열 필드의 환경 변수 플레이스홀더를 확장합니다:\n\n- `${VAR}` 및 `${VAR:-default}`를 지원합니다\n- 해석되지 않은 값은 리터럴 `${VAR}` 문자열로 유지됩니다\n\n`mcp-json.ts`는 또한 사용자 JSON에 대해 런타임 타입 검사를 수행하며, 전체 파일을 실패 처리하는 대신 유효하지 않은 `enabled`/`timeout` 값에 대해 경고를 로그합니다.\n\n## 3) 인증 및 런타임 값 해석\n\n`MCPManager.prepareConfig()`/`#resolveAuthConfig()` (`src/mcp/manager.ts`)는 연결 전 최종 처리 단계입니다.\n\n### OAuth 자격 증명 주입\n\n설정에 다음이 있는 경우:\n\n```ts\nauth: { type: \"oauth\", credentialId: \"...\" }\n```\n\n인증 저장소에 자격 증명이 존재하면:\n\n- `http`/`sse`: `Authorization: Bearer <access_token>` 헤더를 주입합니다\n- `stdio`: `OAUTH_ACCESS_TOKEN` 환경 변수를 주입합니다\n\n자격 증명 조회가 실패하면 매니저는 경고를 로그하고 해석되지 않은 인증 상태로 계속 진행합니다.\n\n### 헤더/환경 변수 값 해석\n\n연결 전에 매니저는 `resolveConfigValue()` (`src/config/resolve-config-value.ts`)를 통해 각 헤더/환경 변수 값을 해석합니다:\n\n- `!`로 시작하는 값 => 셸 명령을 실행하고, 트리밍된 stdout을 사용합니다 (캐시됨)\n- 그 외에는 값을 환경 변수 이름으로 먼저 처리하고 (`process.env[name]`), 리터럴 값으로 폴백합니다\n- 해석되지 않은 명령/환경 변수 값은 최종 헤더/환경 변수 맵에서 제외됩니다\n\n운영상 주의사항: 잘못 입력된 시크릿 명령/환경 변수 키가 해당 헤더/환경 변수 항목을 조용히 제거하여 다운스트림에서 401/403 또는 서버 시작 실패를 유발할 수 있습니다.\n\n## 4) 도구 브릿지: MCP -> 에이전트 호출 가능 도구\n\n`src/mcp/tool-bridge.ts`는 MCP 도구 정의를 `CustomTool`로 변환합니다.\n\n### 이름 지정 및 충돌 도메인\n\n도구 이름은 다음과 같이 생성됩니다:\n\n```text\nmcp_<sanitized_server_name>_<sanitized_tool_name>\n```\n\n규칙:\n\n- 소문자로 변환\n- `[a-z_]`가 아닌 문자는 `_`로 변환\n- 반복되는 밑줄은 하나로 축약\n- 도구 이름에서 중복되는 `<server>_` 접두사는 한 번 제거\n\n이것은 많은 충돌을 방지하지만 전부는 아닙니다. 서로 다른 원본 이름이 동일한 식별자로 정제될 수 있으며 (예: `my-server`와 `my.server`는 유사하게 정제됨), 레지스트리 삽입은 마지막 쓰기가 우선합니다.\n\n### 스키마 매핑\n\n`convertSchema()`는 MCP JSON Schema를 대부분 그대로 유지하지만, `properties`가 누락된 객체 스키마에 프로바이더 호환성을 위해 `{}`를 패치합니다.\n\n### 실행 매핑\n\n`MCPTool.execute()` / `DeferredMCPTool.execute()`:\n\n- MCP `tools/call`을 호출합니다\n- MCP 콘텐츠를 표시 가능한 텍스트로 평탄화합니다\n- 구조화된 상세 정보를 반환합니다 (`serverName`, `mcpToolName`, 프로바이더 메타데이터)\n- 서버가 보고한 `isError`를 `Error: ...` 텍스트 결과로 매핑합니다\n- 발생한 전송/런타임 실패를 `MCP error: ...`로 매핑합니다\n- AbortError를 `ToolAbortError`로 변환하여 중단 시맨틱을 보존합니다\n\n## 5) 운영자 라이프사이클: 추가/편집/제거 및 실시간 업데이트\n\n인터랙티브 모드는 `src/modes/controllers/mcp-command-controller.ts`에서 `/mcp`를 노출합니다.\n\n지원되는 작업:\n\n- `add` (마법사 또는 빠른 추가)\n- `remove` / `rm`\n- `enable` / `disable`\n- `test`\n- `reauth` / `unauth`\n- `reload`\n\n설정 쓰기는 원자적입니다 (`writeMCPConfigFile`: 임시 파일 + 이름 변경).\n\n변경 후 컨트롤러는 `#reloadMCP()`를 호출합니다:\n\n1. `mcpManager.disconnectAll()`\n2. `mcpManager.discoverAndConnect()`\n3. `session.refreshMCPTools(mcpManager.getTools())`\n\n`refreshMCPTools()`는 모든 `mcp_` 레지스트리 항목을 교체하고 최신 MCP 도구 세트를 즉시 다시 활성화하므로, 세션을 재시작하지 않고도 변경 사항이 적용됩니다.\n\n### 모드 차이점\n\n- **인터랙티브/TUI 모드**: `/mcp`는 앱 내 UX를 제공합니다 (마법사, OAuth 흐름, 연결 상태 텍스트, 즉시 런타임 재바인딩).\n- **SDK/헤드리스 통합**: `discoverAndLoadMCPTools()` (`src/mcp/loader.ts`)는 로드된 도구 + 서버별 오류를 반환하며, `/mcp` 명령 UX는 없습니다.\n\n## 6) 사용자에게 표시되는 오류 화면\n\n사용자/운영자가 보게 되는 일반적인 오류 문자열:\n\n- 추가/업데이트 유효성 검사 실패:\n  - `Invalid server config: ...`\n  - `Server \"<name>\" already exists in <path>`\n- 빠른 추가 인수 문제:\n  - `Use either --url or -- <command...>, not both.`\n  - `--token requires --url (HTTP/SSE transport).`\n- 연결/테스트 실패:\n  - `Failed to connect to \"<name>\": <message>`\n  - 타임아웃 도움말 텍스트는 타임아웃 증가를 제안합니다\n  - `401/403`에 대한 인증 도움말 텍스트\n- 인증/OAuth 흐름:\n  - `Authentication required ... OAuth endpoints could not be discovered`\n  - `OAuth flow timed out. Please try again.`\n  - `OAuth authentication failed: ...`\n- 비활성화된 서버 사용:\n  - `Server \"<name>\" is disabled. Run /mcp enable <name> first.`\n\n검색 중 잘못된 소스 JSON은 일반적으로 경고/로그로 처리됩니다; config-writer 경로는 명시적 오류를 던집니다.\n\n## 7) 실용적 작성 지침\n\n이 코드베이스에서 견고한 MCP 작성을 위해:\n\n1. 모든 MCP 지원 설정 소스에서 서버 이름을 전역적으로 고유하게 유지하세요.\n2. 생성된 `mcp_*` 도구 이름에서 정제된 이름 충돌을 방지하기 위해 영숫자/밑줄 이름을 선호하세요.\n3. 우발적인 stdio 기본값을 방지하기 위해 명시적 `type`을 사용하세요.\n4. `enabled: false`는 완전 비활성화로 처리하세요: 서버가 런타임 연결 세트에서 제외됩니다.\n5. OAuth 설정의 경우 유효한 `credentialId`를 저장하세요; 그렇지 않으면 인증 주입이 건너뛰어집니다.\n6. 명령 기반 시크릿 해석(`!cmd`)을 사용하는 경우 명령 출력이 안정적이고 비어 있지 않은지 확인하세요.\n\n## 구현 파일\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts)\n- [`src/mcp/config-writer.ts`](../../packages/coding-agent/src/mcp/config-writer.ts)\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts)\n- [`src/discovery/mcp-json.ts`](../../packages/coding-agent/src/discovery/mcp-json.ts)\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/config/resolve-config-value.ts`](../../packages/coding-agent/src/config/resolve-config-value.ts)\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts)\n",
	"ko/natives/natives-addon-loader-runtime.md": "---\ntitle: 네이티브 애드온 로더 런타임\ndescription: >-\n  N-API addon loader runtime with platform detection, fallback strategies, and\n  module resolution.\nsidebar:\n  order: 3\n  label: 애드온 로더\ni18n:\n  sourceHash: 743ea3e32c7c\n  translator: machine\n---\n\n# 네이티브 애드온 로더 런타임\n\n이 문서는 `@f5-sales-demo/pi-natives`의 애드온 로딩/유효성 검증 레이어를 심층적으로 다룹니다: `native.ts`가 어떤 `.node` 파일을 로드할지 결정하는 방법, 임베디드 페이로드 추출이 실행되는 시점, 그리고 시작 실패가 보고되는 방식을 설명합니다.\n\n## 구현 파일\n\n- `packages/natives/src/native.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/package.json`\n\n## 범위 및 책임\n\n로더/런타임의 책임은 의도적으로 좁게 설정되어 있습니다:\n\n- 플랫폼/CPU 인식 기반의 애드온 파일명 및 디렉터리 후보 목록 구성.\n- 선택적으로 임베디드 애드온을 버전별 사용자 캐시 디렉터리에 구체화.\n- 결정론적 순서로 후보를 시도.\n- 바인딩을 노출하기 전에 `validateNative`를 통해 오래된 또는 호환되지 않는 애드온 거부.\n\n이 문서의 범위 밖: 모듈별 grep/text/highlight 동작.\n\n## 런타임 입력 및 파생 상태\n\n모듈 초기화 시(`export const native = loadNative();`), `native.ts`는 정적 컨텍스트를 계산합니다:\n\n- **플랫폼 태그**: ``${process.platform}-${process.arch}`` (예: `darwin-arm64`).\n- **패키지 버전**: `packages/natives/package.json`의 `version` 필드에서 가져옴.\n- **핵심 디렉터리**:\n  - `nativeDir`: 패키지 로컬 `packages/natives/native`.\n  - `execDir`: `process.execPath`를 포함하는 디렉터리.\n  - `versionedDir`: `<getNativesDir()>/<packageVersion>`.\n  - `userDataDir` 폴백:\n    - Windows: `%LOCALAPPDATA%/xcsh` (또는 `%USERPROFILE%/AppData/Local/xcsh`).\n    - Windows 이외: `~/.local/bin`.\n- **컴파일된 바이너리 모드** (`isCompiledBinary`): 다음 중 하나라도 해당하면 true:\n  - `PI_COMPILED` 환경 변수가 설정되었거나,\n  - `import.meta.url`에 Bun 임베디드 마커(`$bunfs`, `~BUN`, `%7EBUN`)가 포함된 경우.\n- **변형 오버라이드**: `PI_NATIVE_VARIANT` (`modern`/`baseline`만 허용; 유효하지 않은 값은 무시).\n- **선택된 변형**: 명시적 오버라이드가 있으면 해당 값, 그렇지 않으면 x64에서 런타임 AVX2 감지 (AVX2 지원 시 `modern`, 아니면 `baseline`).\n\n## 플랫폼 지원 및 태그 해석\n\n`SUPPORTED_PLATFORMS`는 다음으로 고정되어 있습니다:\n\n- `linux-x64`\n- `linux-arm64`\n- `darwin-x64`\n- `darwin-arm64`\n- `win32-x64`\n\n동작 상세:\n\n- 지원되지 않는 플랫폼은 사전에 거부되지 않습니다.\n- 로더는 여전히 모든 계산된 후보를 먼저 시도합니다.\n- 아무것도 로드되지 않으면, 지원되는 태그 목록과 함께 명시적 미지원 플랫폼 오류를 발생시킵니다.\n\n이는 근접 실패 사례에 대한 유용한 진단을 보존하면서 진정으로 지원되지 않는 대상에 대해서는 확실히 실패합니다.\n\n## 변형 선택 (`modern` / `baseline` / 기본값)\n\n### x64 동작\n\n1. `PI_NATIVE_VARIANT`가 `modern` 또는 `baseline`이면 해당 값이 우선합니다.\n2. 그렇지 않으면 AVX2 지원을 감지합니다:\n   - Linux: `/proc/cpuinfo`에서 `avx2`를 검색.\n   - macOS: `sysctl` 쿼리 (`machdep.cpu.leaf7_features`, 폴백 `machdep.cpu.features`).\n   - Windows: PowerShell `[System.Runtime.Intrinsics.X86.Avx2]::IsSupported` 실행.\n3. 결과:\n   - AVX2 사용 가능 -> `modern`\n   - AVX2 사용 불가/감지 불가 -> `baseline`\n\n### x64 이외 동작\n\n- 변형이 사용되지 않으며, 로더는 기본 파일명(`pi_natives.<platform>-<arch>.node`)을 유지합니다.\n\n### 파일명 구성\n\n`tag = <platform>-<arch>`가 주어진 경우:\n\n- x64 이외 또는 변형 없음: `pi_natives.<tag>.node`\n- x64 + `modern`: 다음 순서로 시도\n  1. `pi_natives.<tag>-modern.node`\n  2. `pi_natives.<tag>-baseline.node` (의도적 폴백)\n- x64 + `baseline`: `pi_natives.<tag>-baseline.node`만 시도\n\n최종 오류 메시지에 사용되는 `addonLabel`은 `<tag>` 또는 `<tag> (<variant>)`입니다.\n\n## 후보 경로 구성 및 폴백 순서\n\n`native.ts`는 `require(...)` 호출 전에 후보 풀을 구성합니다.\n\n### 릴리스 후보\n\n변형이 해석된 파일명 목록에서 구성되며 다음 순서로 검색됩니다:\n\n- **비컴파일 런타임**:\n  1. `<nativeDir>/<filename>`\n  2. `<execDir>/<filename>`\n\n- **컴파일 런타임** (`PI_COMPILED` 또는 Bun 임베디드 마커):\n  1. `<versionedDir>/<filename>`\n  2. `<userDataDir>/<filename>`\n  3. `<nativeDir>/<filename>`\n  4. `<execDir>/<filename>`\n\n`dedupedCandidates`는 첫 번째 출현 순서를 유지하면서 중복을 제거합니다.\n\n### 최종 런타임 시퀀스\n\n로드 시:\n\n1. 선택적 임베디드 추출 후보(생성된 경우)가 맨 앞에 삽입됩니다.\n2. 나머지 중복 제거된 후보가 순서대로 시도됩니다.\n3. `require(...)`와 `validateNative(...)`를 모두 통과하는 첫 번째 후보가 선택됩니다.\n\n## 임베디드 애드온 추출 생명주기\n\n`embedded-addon.ts`는 생성된 매니페스트 형태를 정의합니다:\n\n- `platformTag`\n- `version`\n- `files[]` - 각 항목에 `variant`, `filename`, `filePath` 포함\n\n현재 체크인된 기본값은 `embeddedAddon: null`이며, 컴파일된 아티팩트가 이를 실제 메타데이터로 대체할 수 있습니다.\n\n### 추출 상태 머신\n\n추출(`maybeExtractEmbeddedAddon`)은 모든 게이트가 통과할 때만 실행됩니다:\n\n1. `isCompiledBinary === true`\n2. `embeddedAddon !== null`\n3. `embeddedAddon.platformTag === platformTag`\n4. `embeddedAddon.version === packageVersion`\n5. 변형에 적합한 임베디드 파일이 발견됨\n\n변형 파일 선택은 런타임 변형 의도를 미러링합니다:\n\n- x64 이외: `default`를 선호, 그 다음 첫 번째 사용 가능한 파일.\n- x64 + `modern`: `modern`을 선호, `baseline`으로 폴백.\n- x64 + `baseline`: `baseline`을 요구.\n\n구체화 동작:\n\n1. `<versionedDir>`가 존재하는지 확인 (`mkdirSync(..., { recursive: true })`).\n2. `<versionedDir>/<선택된 파일명>`이 이미 존재하면 재사용 (재작성 없음).\n3. 그렇지 않으면 임베디드 소스 `filePath`를 읽고 대상 파일을 작성.\n4. 최우선 로드 시도를 위한 대상 경로를 반환.\n\n실패 시 추출은 즉시 충돌하지 않습니다. 오류 항목(디렉터리 생성 또는 쓰기 실패)을 추가하고 로더는 정상 후보 탐색을 계속합니다.\n\n## 생명주기 및 상태 전이\n\n```text\nInit\n  -> Compute platform/version/variant/candidate lists\n  -> (Compiled + embedded manifest matches?)\n       yes -> Try extract embedded to versionedDir (record errors, continue)\n       no  -> Skip extraction\n  -> For each runtime candidate in order:\n       require(candidate)\n       -> success: validateNative\n            -> pass: return bindings (READY)\n            -> fail: record error, continue\n       -> failure: record error, continue\n  -> none loaded:\n       if unsupported platform tag -> throw Unsupported platform\n       else -> throw Failed to load (full tried-path diagnostics + hints)\n```\n\n## `validateNative` 계약 검사\n\n`validateNative(bindings, source)`는 시작 시 `NativeBindings`에 대해 함수 전용 계약을 적용합니다.\n\n메커니즘:\n\n- 각 필수 내보내기 이름에 대해 `typeof bindings[name] === \"function\"`을 확인합니다.\n- 누락된 이름이 집계됩니다.\n- 누락된 항목이 있으면 로더가 다음을 포함하여 오류를 발생시킵니다:\n  - 소스 애드온 경로,\n  - 누락된 내보내기 목록,\n  - 재빌드 명령 힌트.\n\n이는 오래된 바이너리, 부분 빌드 및 심볼/이름 드리프트에 대한 강력한 호환성 게이트입니다.\n\n### JS API ↔ 네이티브 내보내기 매핑 (유효성 검증 게이트)\n\n| `validateNative`에서 확인되는 JS 바인딩 이름 | 예상 네이티브 내보내기 이름 |\n| --- | --- |\n| `grep` | `grep` |\n| `glob` | `glob` |\n| `highlightCode` | `highlightCode` |\n| `executeShell` | `executeShell` |\n| `PtySession` | `PtySession` |\n| `Shell` | `Shell` |\n| `visibleWidth` | `visibleWidth` |\n| `getSystemInfo` | `getSystemInfo` |\n| `getWorkProfile` | `getWorkProfile` |\n| `invalidateFsScanCache` | `invalidateFsScanCache` |\n\n참고: `bindings.ts`는 기본 `cancelWork(id)` 멤버만 선언합니다. 모듈 `types.ts` 파일이 `validateNative`가 적용하는 추가 심볼을 선언 병합합니다.\n\n## 실패 동작 및 진단\n\n## 지원되지 않는 플랫폼\n\n모든 후보가 실패하고 `platformTag`가 `SUPPORTED_PLATFORMS`에 없으면, 로더는 다음을 포함하여 오류를 발생시킵니다:\n\n- `Unsupported platform: <tag>`\n- 전체 지원 플랫폼 목록\n- 명시적 이슈 보고 안내\n\n## 오래된 바이너리 / 불일치 증상\n\n일반적인 오래된 불일치 신호:\n\n- `Native addon missing exports (<candidate>). Missing: ...`\n\n일반적인 원인:\n\n- 이전 패키지 버전/API 형태의 오래된 `.node` 바이너리.\n- 잘못된 변형 아티팩트 선택 (x64의 경우).\n- 로드된 아티팩트에 새로운 Rust 내보내기가 없음.\n\n로더 동작:\n\n- 후보별 누락 내보내기 실패를 기록합니다.\n- 나머지 후보 탐색을 계속합니다.\n- 유효성 검증을 통과하는 후보가 없으면, 최종 오류에 각 실패 메시지와 함께 시도된 모든 경로가 포함됩니다.\n\n## 컴파일된 바이너리 시작 실패\n\n컴파일 모드에서 최종 진단에는 다음이 포함됩니다:\n\n- 예상 버전별 캐시 대상 경로 (`<versionedDir>/<filename>`),\n- 오래된 `<versionedDir>`를 삭제하고 다시 실행하는 해결 방법,\n- 각 예상 파일명에 대한 직접 릴리스 다운로드 `curl` 명령.\n\n## 비컴파일 시작 실패\n\n일반 패키지/런타임 모드에서 최종 진단에는 다음이 포함됩니다:\n\n- 재설치 힌트 (`bun install @f5-sales-demo/pi-natives`),\n- 로컬 재빌드 명령 (`bun --cwd=packages/natives run build`),\n- 선택적 x64 변형 빌드 힌트 (`TARGET_VARIANT=baseline|modern ...`).\n\n## 런타임 동작\n\n- 로더는 항상 릴리스 후보 체인을 사용합니다.\n- `PI_DEV` 설정은 후보별 콘솔 진단(`Loaded native addon...` 및 로드 오류)만 활성화합니다.\n",
	"ko/natives/natives-architecture.md": "---\ntitle: Natives 아키텍처\ndescription: TypeScript와 플랫폼별 작업을 연결하는 Rust N-API 네이티브 애드온 아키텍처.\nsidebar:\n  order: 1\n  label: 아키텍처\ni18n:\n  sourceHash: d38ed2437bb7\n  translator: machine\n---\n\n# Natives 아키텍처\n\n`@f5-sales-demo/pi-natives`는 세 계층으로 구성된 스택입니다:\n\n1. **TypeScript 래퍼/API 계층**은 안정적인 JS/TS 진입점을 제공합니다.\n2. **애드온 로딩/검증 계층**은 현재 런타임에 맞는 `.node` 바이너리를 탐색하고 검증합니다.\n3. **Rust N-API 모듈 계층**은 JS로 내보내는 성능 핵심 원시 함수를 구현합니다.\n\n이 문서는 더 깊은 모듈 수준 문서의 기초입니다.\n\n## 구현 파일\n\n- `packages/natives/src/index.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `crates/pi-natives/src/lib.rs`\n\n## 계층 1: TypeScript 래퍼/API 계층\n\n`packages/natives/src/index.ts`는 공개 배럴 파일입니다. 기능 도메인별로 내보내기를 그룹화하고, 원시 N-API 바인딩을 직접 노출하지 않고 타입이 지정된 래퍼를 재내보내기합니다.\n\n현재 최상위 그룹:\n\n- **검색/텍스트 원시 함수**: `grep`, `glob`, `text`, `highlight`\n- **실행/프로세스/터미널 원시 함수**: `shell`, `pty`, `ps`, `keys`\n- **시스템/미디어/변환 원시 함수**: `image`, `html`, `clipboard`, `system-info`, `work`\n\n`packages/natives/src/bindings.ts`는 기본 인터페이스 계약을 정의합니다:\n\n- `NativeBindings`는 공유 멤버(`cancelWork(id: number)`)로 시작합니다\n- 모듈별 바인딩은 각 모듈의 `types.ts`에서 선언 병합을 통해 추가됩니다\n- `Cancellable`은 취소 기능을 제공하는 래퍼를 위해 타임아웃 및 abort-signal 옵션을 표준화합니다\n\n**보장된 계약 (API 측면):** 소비자는 `@f5-sales-demo/pi-natives`에서 가져오고 타입이 지정된 래퍼를 사용합니다.\n\n**구현 세부 사항 (변경 가능):** 선언 병합 및 내부 래퍼 레이아웃 (`src/<module>/index.ts`, `src/<module>/types.ts`).\n\n## 계층 2: 애드온 로딩 및 검증\n\n`packages/natives/src/native.ts`는 런타임 애드온 선택, 선택적 추출, 내보내기 검증을 담당합니다.\n\n### 후보 탐색 모델\n\n- 플랫폼 태그는 `\"${process.platform}-${process.arch}\"`입니다.\n- 현재 지원되는 태그:\n  - `linux-x64`\n  - `linux-arm64`\n  - `darwin-x64`\n  - `darwin-arm64`\n  - `win32-x64`\n- x64는 CPU 변형을 사용할 수 있습니다:\n  - `modern` (AVX2 지원)\n  - `baseline` (폴백)\n- 비 x64는 기본 파일명을 사용합니다 (변형 접미사 없음).\n\n파일명 전략:\n\n- 릴리스: `pi_natives.<platform>-<arch>.node`\n- x64 변형 릴리스: `pi_natives.<platform>-<arch>-modern.node` 및/또는 `...-baseline.node`\n- `PI_DEV`는 로더 진단을 활성화하지만 애드온 파일명은 변경하지 않습니다\n\n### 플랫폼별 변형 감지\n\nx64의 경우, 변형 선택은 다음을 사용합니다:\n\n- **Linux**: `/proc/cpuinfo`\n- **macOS**: `sysctl machdep.cpu.leaf7_features` / `machdep.cpu.features`\n- **Windows**: `System.Runtime.Intrinsics.X86.Avx2`에 대한 PowerShell 검사\n\n`PI_NATIVE_VARIANT`로 `modern` 또는 `baseline`을 명시적으로 강제할 수 있습니다.\n\n### 바이너리 배포 및 추출 모델\n\n`packages/natives/package.json`은 게시된 파일에 `src`와 `native`를 모두 포함합니다. `native/` 디렉터리는 사전 빌드된 플랫폼 아티팩트를 저장합니다.\n\n컴파일된 바이너리(`PI_COMPILED` 또는 Bun 임베디드 런타임 마커)의 경우, 로더 동작은 다음과 같습니다:\n\n1. 버전이 지정된 사용자 캐시 경로 확인: `<getNativesDir()>/<packageVersion>/...`\n2. 레거시 컴파일된 바이너리 위치 확인:\n   - Windows: `%LOCALAPPDATA%/xcsh` (폴백 `%USERPROFILE%/AppData/Local/xcsh`)\n   - 비 Windows: `~/.local/bin`\n3. 패키지에 포함된 `native/` 및 실행 파일 디렉터리 후보로 폴백\n\n임베디드 애드온 매니페스트가 존재하면 (`scripts/embed-native.ts`에 의해 생성된 `embedded-addon.ts`), `native.ts`는 로딩 전에 일치하는 임베디드 바이너리를 버전이 지정된 캐시 디렉터리에 구체화할 수 있습니다.\n\n### 검증 및 실패 모드\n\n`require(candidate)` 후, `validateNative(...)`는 필수 내보내기를 검증합니다 (예: `grep`, `glob`, `highlightCode`, `PtySession`, `Shell`, `getSystemInfo`, `getWorkProfile`, `invalidateFsScanCache`).\n\n실패 경로는 명시적입니다:\n\n- **지원되지 않는 플랫폼 태그**: 지원 플랫폼 목록과 함께 예외를 발생시킵니다\n- **로드 가능한 후보 없음**: 시도된 모든 경로와 해결 힌트와 함께 예외를 발생시킵니다\n- **누락된 내보내기**: 정확한 누락 이름과 재빌드 명령과 함께 예외를 발생시킵니다\n- **임베디드 추출 오류**: 디렉터리/쓰기 실패를 기록하고 최종 로드 진단에 포함합니다\n\n**보장된 계약 (API 측면):** 애드온 로드는 검증된 바인딩 세트로 성공하거나, 실행 가능한 오류 텍스트와 함께 즉시 실패합니다.\n\n**구현 세부 사항 (변경 가능):** 정확한 후보 탐색 순서 및 컴파일된 바이너리 폴백 경로 순서.\n\n## 계층 3: Rust N-API 모듈 계층\n\n`crates/pi-natives/src/lib.rs`는 내보내는 모듈 소유권을 선언하는 Rust 진입 모듈입니다:\n\n- `clipboard`\n- `fd`\n- `fs_cache`\n- `glob`\n- `glob_util`\n- `grep`\n- `highlight`\n- `html`\n- `image`\n- `keys`\n- `prof`\n- `ps`\n- `pty`\n- `shell`\n- `system_info`\n- `task`\n- `text`\n\n이 모듈들은 `native.ts`에 의해 소비되고 검증되는 N-API 심볼을 구현합니다. JS 수준 이름은 `packages/natives/src`의 TS 래퍼를 통해 표면화됩니다.\n\n**보장된 계약 (API 측면):** Rust 모듈 내보내기는 `validateNative` 및 래퍼 모듈이 기대하는 바인딩 이름과 일치해야 합니다.\n\n**구현 세부 사항 (변경 가능):** 내부 Rust 모듈 분해 및 헬퍼 모듈 경계 (`glob_util`, `task` 등).\n\n## 소유권 경계\n\n아키텍처 수준에서 소유권은 다음과 같이 분할됩니다:\n\n- **TS 래퍼/API 소유권 (`packages/natives/src`)**\n  - 공개 API 그룹화, 옵션 타이핑, 안정적인 JS 사용 편의성\n  - 호출자에게 노출되는 취소 표면 (`timeoutMs`, `AbortSignal`)\n- **로더 소유권 (`packages/natives/src/native.ts`)**\n  - 런타임 바이너리 선택\n  - CPU 변형 선택 및 오버라이드 처리\n  - 컴파일된 바이너리 추출 및 후보 탐색\n  - 필수 네이티브 내보내기의 엄격한 검증\n- **Rust 소유권 (`crates/pi-natives/src`)**\n  - 알고리즘 및 시스템 수준 구현\n  - 플랫폼 네이티브 동작 및 성능에 민감한 로직\n  - TS 래퍼가 소비하는 N-API 심볼 구현\n\n## 런타임 흐름 (고수준)\n\n1. 소비자가 `@f5-sales-demo/pi-natives`에서 가져옵니다.\n2. 래퍼 모듈이 싱글톤 `native` 바인딩을 호출합니다.\n3. `native.ts`가 플랫폼/아키텍처/변형에 맞는 후보 바이너리를 선택합니다.\n4. 컴파일된 배포판의 경우 선택적 임베디드 바이너리 추출이 발생합니다.\n5. 애드온이 로드되고 내보내기 세트가 검증됩니다.\n6. 래퍼가 호출자에게 타입이 지정된 결과를 반환합니다.\n\n## 용어집\n\n- **네이티브 애드온**: Node-API (N-API)를 통해 로드되는 `.node` 바이너리.\n- **플랫폼 태그**: 런타임 튜플 `platform-arch` (예: `darwin-arm64`).\n- **변형**: x64 CPU별 빌드 플레이버 (`modern` AVX2, `baseline` 폴백).\n- **래퍼**: 원시 네이티브 내보내기에 대해 타입이 지정된 API를 제공하는 TS 함수/클래스.\n- **선언 병합**: 모듈 `types.ts` 파일이 `NativeBindings`를 확장하기 위해 사용하는 TS 기법.\n- **컴파일된 바이너리 모드**: CLI가 번들되고 네이티브 애드온이 패키지 로컬 경로만이 아닌 추출/캐시 경로에서 탐색되는 런타임 모드.\n- **임베디드 애드온**: 컴파일된 바이너리가 일치하는 `.node` 페이로드를 추출할 수 있도록 `embedded-addon.ts`에 생성된 빌드 아티팩트 메타데이터 및 파일 참조.\n- **검증 게이트**: 필수 내보내기가 누락된 오래된/불일치 바이너리를 거부하는 `validateNative(...)` 검사.\n",
	"ko/natives/natives-binding-contract.md": "---\ntitle: Natives 바인딩 계약 (TypeScript 측)\ndescription: N-API를 통해 Rust 네이티브 함수를 호출하기 위한 TypeScript 측 바인딩 계약.\nsidebar:\n  order: 2\n  label: 바인딩 계약\ni18n:\n  sourceHash: 36dc5fed1f0a\n  translator: machine\n---\n\n# Natives 바인딩 계약 (TypeScript 측)\n\n이 문서는 `@f5-sales-demo/pi-natives` 호출자와 로드된 N-API 애드온 사이에 위치하는 TypeScript 측 계약을 정의합니다.\n\n세 가지 부분에 초점을 맞춥니다:\n\n1. 계약 형태 (`NativeBindings` + 모듈 보강),\n2. 래퍼 동작 (`src/<module>/index.ts`),\n3. 공개 내보내기 표면 (`src/index.ts`).\n\n## 구현 파일\n\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/system-info/types.ts`\n- `packages/natives/src/system-info/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/work/types.ts`\n- `packages/natives/src/work/index.ts`\n\n## 계약 모델\n\n`packages/natives/src/bindings.ts`는 기본 계약을 정의합니다:\n\n- `NativeBindings` (기본 인터페이스, 현재 `cancelWork(id: number): void` 포함)\n- `Cancellable` (`timeoutMs?: number`, `signal?: AbortSignal`)\n- `TsFunc<T>` N-API 스레드 안전 콜백에서 사용되는 콜백 형태\n\n각 모듈은 선언 병합을 통해 자체 필드를 추가합니다:\n\n```ts\n// packages/natives/src/<module>/types.ts\ndeclare module \"../bindings\" {\n interface NativeBindings {\n  grep(options: GrepOptions, onMatch?: TsFunc<GrepMatch>): Promise<GrepResult>;\n }\n}\n```\n\n이를 통해 단일 중앙 집중식 타입 파일 없이 하나의 통합 바인딩 인터페이스를 유지합니다.\n\n## 선언 병합 생명주기 및 상태 전환\n\n### 1) 컴파일 타임 타입 조립\n\n- `bindings.ts`는 기본 `NativeBindings` 심볼을 제공합니다.\n- 모든 `src/<module>/types.ts`가 `NativeBindings`를 보강합니다.\n- `src/native.ts`는 모든 `./<module>/types` 파일을 부수 효과를 위해 임포트하여 `NativeBindings`가 사용되는 곳에서 병합된 계약이 스코프 내에 있도록 합니다.\n\n상태 전환: **기본 계약** → **병합된 계약**.\n\n### 2) 런타임 애드온 로드 및 검증 게이트\n\n- `src/native.ts`는 후보 `.node` 바이너리를 로드합니다.\n- 로드된 객체는 `NativeBindings`로 취급되며 즉시 `validateNative(...)`를 통과합니다.\n- `validateNative`는 `typeof bindings[name] === \"function\"`으로 필수 내보내기 키를 확인합니다.\n\n상태 전환: **신뢰할 수 없는 애드온 객체** → **검증된 네이티브 바인딩 객체** (또는 하드 실패).\n\n### 3) 래퍼 호출\n\n- `src/<module>/index.ts`의 모듈 래퍼가 `native.<export>`를 호출합니다.\n- 래퍼는 기본값과 콜백 형태를 적응시킵니다 (`(err, value)`에서 JS API의 값 전용 콜백 패턴으로).\n- `src/index.ts`는 모듈 래퍼/타입을 공개 패키지 API로 다시 내보냅니다.\n\n상태 전환: **검증된 원시 바인딩** → **인체공학적 공개 API**.\n\n## 래퍼 책임\n\n래퍼는 의도적으로 얇습니다; 네이티브 로직을 다시 구현하지 않습니다.\n\n주요 책임:\n\n- **인수 정규화/기본값 설정**\n  - `glob()`은 `options.path`를 절대 경로로 해석하고 `hidden`, `gitignore`, `recursive`의 기본값을 설정합니다.\n  - `hasMatch()`는 네이티브 호출 전에 기본 플래그(`ignoreCase`, `multiline`)를 채웁니다.\n- **콜백 적응**\n  - `grep()`, `glob()`, `executeShell()`은 `TsFunc<T>` (`error, value`)를 성공적인 값만 수신하는 사용자 콜백으로 변환합니다.\n- **네이티브 호출 주변의 환경 또는 정책 동작**\n  - 클립보드 래퍼는 OSC52/Termux/헤드리스 처리를 추가하고 복사를 최선의 노력으로 취급합니다.\n- **공개 명명 및 재내보내기 큐레이션**\n  - `searchContent()`는 네이티브 내보내기 `search`에 매핑됩니다.\n\n## 공개 내보내기 표면 구성\n\n`packages/natives/src/index.ts`는 정식 공개 배럴 파일입니다. 기능 도메인별로 내보내기를 그룹화합니다:\n\n- 검색/텍스트: `grep`, `glob`, `text`, `highlight`\n- 실행/프로세스/터미널: `shell`, `pty`, `ps`, `keys`\n- 시스템/미디어/변환: `image`, `html`, `clipboard`, `system-info`, `work`\n\n유지관리자 규칙: 래퍼가 `src/index.ts`에서 다시 내보내지 않으면, 의도된 공개 패키지 표면의 일부가 아닙니다.\n\n## JS API ↔ 네이티브 내보내기 매핑 (대표적)\n\nRust 측은 N-API 내보내기 이름(일반적으로 `#[napi]` snake_case -> camelCase 변환, 때때로 명시적 별칭 사용)을 사용하며, 이는 이러한 바인딩 키와 일치해야 합니다.\n\n| 카테고리 | 공개 JS API (래퍼) | 네이티브 바인딩 키 | 반환 타입 | 비동기? |\n|---|---|---|---|---|\n| Grep | `grep(options, onMatch?)` | `grep` | `Promise<GrepResult>` | 예 |\n| Grep | `searchContent(content, options)` | `search` | `SearchResult` | 아니오 |\n| Grep | `hasMatch(content, pattern, opts?)` | `hasMatch` | `boolean` | 아니오 |\n| Grep | `fuzzyFind(options)` | `fuzzyFind` | `Promise<FuzzyFindResult>` | 예 |\n| Glob | `glob(options, onMatch?)` | `glob` | `Promise<GlobResult>` | 예 |\n| Glob | `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `void` | 아니오 |\n| Shell | `executeShell(options, onChunk?)` | `executeShell` | `Promise<ShellExecuteResult>` | 예 |\n| Shell | `Shell` | `Shell` | 클래스 생성자 | N/A |\n| PTY | `PtySession` | `PtySession` | 클래스 생성자 | N/A |\n| Text | `truncateToWidth(...)` | `truncateToWidth` | `string` | 아니오 |\n| Text | `sliceWithWidth(...)` | `sliceWithWidth` | `SliceWithWidthResult` | 아니오 |\n| Text | `visibleWidth(text)` | `visibleWidth` | `number` | 아니오 |\n| Highlight | `highlightCode(code, lang, colors)` | `highlightCode` | `string` | 아니오 |\n| HTML | `htmlToMarkdown(html, options?)` | `htmlToMarkdown` | `Promise<string>` | 예 |\n| System | `getSystemInfo()` | `getSystemInfo` | `SystemInfo` | 아니오 |\n| Work | `getWorkProfile(lastSeconds)` | `getWorkProfile` | `WorkProfile` | 아니오 |\n| Process | `killTree(pid, signal)` | `killTree` | `number` | 아니오 |\n| Process | `listDescendants(pid)` | `listDescendants` | `number[]` | 아니오 |\n| Clipboard | `copyToClipboard(text)` | `copyToClipboard` | `Promise<void>` (최선의 노력 래퍼 동작) | 예 |\n| Clipboard | `readImageFromClipboard()` | `readImageFromClipboard` | `Promise<ClipboardImage \\| null>` | 예 |\n| Keys | `parseKey(data, kittyProtocolActive)` | `parseKey` | `string \\| null` | 아니오 |\n\n## 동기 vs 비동기 계약 차이\n\n계약은 동기 및 비동기 API를 혼합합니다; 래퍼는 하나의 모델을 강제하기보다 네이티브 호출 스타일을 유지합니다:\n\n- **Promise 기반 비동기 내보내기**: I/O 또는 장시간 실행 작업용 (`grep`, `glob`, `htmlToMarkdown`, `executeShell`, 클립보드, 이미지 작업).\n- **동기 내보내기**: 결정론적 인메모리 변환/파서용 (`search`, `hasMatch`, 하이라이팅, 텍스트 너비/슬라이싱, 키 파싱, 프로세스 쿼리).\n- **생성자 내보내기**: 상태를 가진 런타임 객체용 (`Shell`, `PtySession`, `PhotonImage`).\n\n유지관리자를 위한 시사점: 기존 내보내기의 동기 ↔ 비동기를 변경하는 것은 래퍼와 호출자 전반에 걸친 파괴적 API 및 계약 변경입니다.\n\n## 객체 및 열거형 타이핑 패턴\n\n### 객체 패턴 (`#[napi(object)]` 스타일 JS 객체)\n\nTS는 객체 형태의 네이티브 값을 인터페이스로 모델링합니다. 예시:\n\n- `GrepResult`, `SearchResult`, `GlobResult`\n- `SystemInfo`, `WorkProfile`\n- `ClipboardImage`, `ParsedKittyResult`\n\n이들은 컴파일 타임의 구조적 계약이며, 런타임 형태 정확성은 네이티브 구현이 담당합니다.\n\n### 열거형 패턴\n\n숫자 네이티브 열거형은 TS에서 `const enum` 값으로 표현됩니다:\n\n- `FileType` (`1=file`, `2=dir`, `3=symlink`)\n- `ImageFormat` (`0=PNG`, `1=JPEG`, `2=WEBP`, `3=GIF`)\n- `SamplingFilter`, `Ellipsis`, `KeyEventType`\n\n호출자는 이름이 있는 열거형 멤버를 보며, 바인딩 경계에서는 숫자가 전달됩니다.\n\n## 불일치 감지 방법\n\n불일치 감지는 두 계층에서 이루어집니다:\n\n1. **컴파일 타임 TypeScript 계약 검사**\n   - 래퍼는 병합된 `NativeBindings`에 대해 `native.<name>`을 호출합니다.\n   - 누락되거나 이름이 변경된 바인딩 키는 래퍼에서 TS 타입 검사를 중단시킵니다.\n\n2. **`validateNative`에서의 런타임 검증**\n   - 로드 후 `native.ts`는 필수 내보내기를 확인하고 누락된 것이 있으면 예외를 던집니다.\n   - 오류 메시지에는 누락된 키와 재빌드 지침이 포함됩니다.\n\n이는 일반적인 오래된 바이너리 드리프트를 포착합니다: 래퍼/타입은 존재하지만 로드된 `.node`에 해당 내보내기가 없는 경우.\n\n## 실패 동작 및 주의사항\n\n### 로드/검증 실패 (하드 실패)\n\n- 애드온 로드 실패 또는 지원되지 않는 플랫폼은 `native.ts`에서 모듈 초기화 중에 예외를 던집니다.\n- 필수 내보내기 누락은 래퍼가 사용 가능해지기 전에 예외를 던집니다.\n\n효과: 패키지는 첫 번째 호출까지 실패를 미루지 않고 빠르게 실패합니다.\n\n### 래퍼 수준 동작 차이\n\n- 일부 래퍼는 의도적으로 실패를 완화합니다 (`copyToClipboard`는 최선의 노력이며 네이티브 실패를 무시합니다).\n- 스트리밍 콜백은 콜백 오류 페이로드를 무시하고 성공적인 값 이벤트만 전달합니다.\n\n### 타입 수준 주의사항 (런타임이 TS보다 엄격함)\n\n- TS 선택적 필드는 의미적 유효성을 보장하지 않습니다; 네이티브 계층은 여전히 잘못된 형태의 값을 거부할 수 있습니다.\n- `const enum` 타이핑은 런타임에서 타입이 지정되지 않은 호출자의 범위 밖 숫자 값을 방지하지 않습니다.\n- `validateNative`는 필수 내보내기의 존재/함수 여부만 확인하며, 깊은 인수/반환 형태 호환성은 확인하지 않습니다.\n- `bindings.ts`는 기본 인터페이스에 `cancelWork(id)`를 포함하지만, 현재 런타임 검증 목록은 해당 키를 강제하지 않습니다.\n\n## 바인딩 변경을 위한 유지관리자 체크리스트\n\n내보내기를 추가/변경할 때, 다음 모든 항목을 업데이트하세요:\n\n1. `src/<module>/types.ts` (보강 + 계약 타입)\n2. `src/<module>/index.ts` (래퍼 동작)\n3. `src/native.ts` 모듈 타입 임포트 (새 모듈인 경우)\n4. `validateNative` 필수 내보내기 검사\n5. `src/index.ts` 공개 재내보내기\n\n어떤 단계라도 건너뛰면 컴파일 타임 드리프트 또는 런타임 로드 타임 실패가 발생합니다.\n",
	"ko/natives/natives-build-release-debugging.md": "---\ntitle: 'Natives 빌드, 릴리스 및 디버깅 런북'\ndescription: '크로스 플랫폼 Rust 네이티브 애드온의 빌드, 릴리스 및 디버깅 런북.'\nsidebar:\n  order: 8\n  label: '빌드, 릴리스 및 디버깅'\ni18n:\n  sourceHash: efe47aa5b466\n  translator: machine\n---\n\n# Natives 빌드, 릴리스 및 디버깅 런북\n\n이 런북은 `@f5-sales-demo/pi-natives` 빌드 파이프라인이 `.node` 애드온을 생성하는 방법, 컴파일된 배포판이 이를 로드하는 방법, 그리고 로더/빌드 실패를 디버깅하는 방법을 설명합니다.\n\n`docs/natives-architecture.md`의 아키텍처 용어를 따릅니다:\n\n- **빌드 시점 아티팩트 생성** (`scripts/build-native.ts`)\n- **임베디드 애드온 매니페스트 생성** (`scripts/embed-native.ts`)\n- **런타임 애드온 로딩 + 유효성 검증 게이트** (`src/native.ts`)\n\n## 구현 파일\n\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `packages/natives/src/native.ts`\n- `crates/pi-natives/Cargo.toml`\n\n## 빌드 파이프라인 개요\n\n### 1) 빌드 진입점\n\n`packages/natives/package.json` 스크립트:\n\n- `bun scripts/build-native.ts` (`build`) → 릴리스 빌드\n- `bun scripts/build-native.ts --dev` (`dev:native`) → 디버그/개발 프로파일 빌드 (동일한 출력 네이밍)\n- `bun scripts/embed-native.ts` (`embed:native`) → 빌드된 파일로부터 `src/embedded-addon.ts` 생성\n\n### 2) Rust 아티팩트 빌드\n\n`build-native.ts`는 `crates/pi-natives`에서 Cargo를 실행합니다:\n\n- 기본 명령어: `cargo build`\n- `--dev`가 전달되지 않으면 릴리스 모드에서 `--release` 추가\n- 크로스 타겟은 `--target <CROSS_TARGET>` 추가\n\n`crates/pi-natives/Cargo.toml`은 `crate-type = [\"cdylib\"]`를 선언하므로, Cargo는 공유 라이브러리(`.so`/`.dylib`/`.dll`)를 생성하며 이후 `.node` 애드온 파일명으로 복사/이름 변경됩니다.\n\n### 3) 아티팩트 검색 및 설치\n\nCargo 완료 후, `build-native.ts`는 후보 출력 디렉토리를 순서대로 스캔합니다:\n\n1. `${CARGO_TARGET_DIR}` (설정된 경우)\n2. `<repo>/target`\n3. `crates/pi-natives/target`\n\n각 루트에 대해 프로파일 디렉토리를 확인합니다:\n\n- 크로스 빌드: `<root>/<crossTarget>/<profile>` 후 `<root>/<profile>`\n- 네이티브 빌드: `<root>/<profile>`\n\n그런 다음 다음 중 하나를 찾습니다:\n\n- `libpi_natives.so`\n- `libpi_natives.dylib`\n- `pi_natives.dll`\n- `libpi_natives.dll`\n\n발견되면, 임시 파일 + 이름 변경 시맨틱으로 `packages/natives/native/`에 원자적으로 설치됩니다 (Windows 폴백은 잠긴 DLL 교체 실패를 명시적으로 처리합니다).\n\n## 타겟/변형 모델 및 네이밍 규칙\n\n## 플랫폼 태그\n\n빌드와 런타임 모두 플랫폼 태그를 사용합니다:\n\n`<platform>-<arch>` (예: `darwin-arm64`, `linux-x64`)\n\n## 변형 모델 (x64 전용)\n\nx64는 CPU 변형을 지원합니다:\n\n- `modern` (AVX2 지원 경로)\n- `baseline` (폴백)\n\nx64가 아닌 경우 단일 기본 아티팩트를 사용합니다 (변형 접미사 없음).\n\n### 출력 파일명\n\n릴리스 빌드:\n\n- x64: `pi_natives.<platform>-<arch>-modern.node` 또는 `...-baseline.node`\n- 비-x64: `pi_natives.<platform>-<arch>.node`\n\n개발 빌드 (`--dev`):\n\n- 디버그 프로파일 플래그를 사용하지만 표준 플랫폼 태그 출력 네이밍을 유지\n\n`native.ts`에서의 런타임 로더 후보 순서:\n\n- 릴리스 후보\n- 컴파일 모드에서는 패키지 로컬 파일 앞에 추출/캐시 후보를 배치\n\n## 환경 플래그 및 빌드 옵션\n\n## 런타임 플래그\n\n- `PI_DEV` (로더 동작): 로더 진단 활성화\n- `PI_NATIVE_VARIANT` (로더 동작, x64 전용): 런타임에서 `modern` 또는 `baseline` 선택 강제\n- `PI_COMPILED` (로더 동작): 컴파일된 바이너리 후보/추출 동작 활성화\n\n## 빌드 시점 플래그/옵션\n\n- `--dev` (스크립트 인자): 디버그 프로파일 빌드\n- `CROSS_TARGET`: Cargo `--target`에 전달\n- `TARGET_PLATFORM`: 출력 플랫폼 태그 네이밍 재정의\n- `TARGET_ARCH`: 출력 아키텍처 네이밍 재정의\n- `TARGET_VARIANT` (x64 전용): 출력 파일명 및 RUSTFLAGS 정책에 `modern` 또는 `baseline` 강제\n- `CARGO_TARGET_DIR`: Cargo 출력 검색 시 추가 루트\n- `RUSTFLAGS`:\n  - 미설정이고 크로스 컴파일이 아닌 경우, 스크립트가 설정:\n    - modern: `-C target-cpu=x86-64-v3`\n    - baseline: `-C target-cpu=x86-64-v2`\n    - 비-x64 / 변형 없음: `-C target-cpu=native`\n  - 이미 설정된 경우, 스크립트가 재정의하지 않음\n\n## 빌드 상태/생명주기 전환\n\n### 빌드 생명주기 (`build-native.ts`)\n\n1. **초기화**: 인자/환경 파싱 (`--dev`, 타겟 재정의, 크로스 플래그)\n2. **변형 해석**:\n   - 비-x64 → 변형 없음\n   - x64 + `TARGET_VARIANT` → 명시적 변형\n   - `TARGET_VARIANT` 없는 x64 크로스 빌드 → 하드 에러\n   - 재정의 없는 x64 로컬 빌드 → 호스트 AVX2 감지\n3. **컴파일**: 해석된 프로파일/타겟으로 Cargo 실행\n4. **아티팩트 검색**: 타겟 루트/프로파일 디렉토리/라이브러리 이름 스캔\n5. **설치**: `packages/natives/native`에 복사 + 원자적 이름 변경\n6. **완료**: 로더 후보용 애드온 준비 완료\n\n실패 시 명시적 에러 텍스트와 함께 각 단계에서 종료됩니다 (잘못된 변형, 실패한 cargo 빌드, 누락된 출력 라이브러리, 설치/이름 변경 실패).\n\n### 임베드 생명주기 (`embed-native.ts`)\n\n1. **초기화**: `TARGET_PLATFORM`/`TARGET_ARCH` 또는 호스트 값으로 플랫폼 태그 계산\n2. **후보 집합**:\n   - x64는 `modern`과 `baseline` 모두 기대\n   - 비-x64는 하나의 기본 파일 기대\n3. `packages/natives/native`에서 **가용성 검증**\n4. Bun `file` 임포트와 패키지 버전으로 **매니페스트 생성** (`src/embedded-addon.ts`)\n5. 컴파일 모드를 위한 **런타임 추출 준비**\n\n`--reset`은 검증을 건너뛰고 null 매니페스트 스텁(`embeddedAddon = null`)을 작성합니다.\n\n## 개발 워크플로 vs 배포/컴파일 동작\n\n## 로컬 개발 워크플로\n\n일반적인 로컬 루프:\n\n1. 애드온 빌드:\n   - 릴리스: `bun --cwd=packages/natives run build`\n   - 디버그 프로파일: `bun --cwd=packages/natives run dev:native`\n2. 로더 진단 테스트 시 `PI_DEV=1` 설정\n3. `native.ts`의 로더가 패키지 로컬 `native/` (및 실행 파일 디렉토리 폴백) 후보를 해석\n4. `validateNative`가 래퍼가 바인딩을 사용하기 전에 내보내기 호환성을 강제\n\n## 배포/컴파일된 바이너리 워크플로\n\n컴파일 모드 (`PI_COMPILED` 또는 Bun 임베디드 마커):\n\n1. 로더가 버전별 캐시 디렉토리 계산: `<getNativesDir()>/<packageVersion>` (운영상 `~/.xcsh/natives/<version>`)\n2. 임베디드 매니페스트가 현재 플랫폼+버전과 일치하면, 로더가 선택된 임베디드 파일을 해당 버전별 디렉토리에 추출 가능\n3. 런타임 후보 순서:\n   - 버전별 캐시 디렉토리\n   - 레거시 컴파일된 바이너리 디렉토리 (Windows에서 `%LOCALAPPDATA%/xcsh`, 기타 플랫폼에서 `~/.local/bin`)\n   - 패키지/실행 파일 디렉토리\n4. 첫 번째로 성공적으로 로드된 애드온도 여전히 `validateNative`를 통과해야 함\n\n이것이 패키징과 런타임 로더 기대가 일치해야 하는 이유입니다: 파일명, 플랫폼 태그, 내보낸 심볼이 `native.ts`가 탐색하고 검증하는 것과 일치해야 합니다.\n\n## JS API ↔ Rust 내보내기 매핑 (유효성 검증 게이트 하위 집합)\n\n`native.ts`는 로드된 애드온에 다음 JS 가시 내보내기가 존재할 것을 요구합니다. 이들은 `crates/pi-natives/src`의 Rust N-API 내보내기에 매핑됩니다:\n\n| `validateNative`가 요구하는 JS 이름 | Rust 내보내기 선언 | Rust 소스 파일 |\n| --- | --- | --- |\n| `glob` | `#[napi] pub fn glob(...)` | `crates/pi-natives/src/glob.rs` |\n| `grep` | `#[napi] pub fn grep(...)` | `crates/pi-natives/src/grep.rs` |\n| `search` | `#[napi] pub fn search(...)` | `crates/pi-natives/src/grep.rs` |\n| `highlightCode` | `#[napi] pub fn highlight_code(...)` | `crates/pi-natives/src/highlight.rs` |\n| `getSystemInfo` | `#[napi] pub fn get_system_info(...)` | `crates/pi-natives/src/system_info.rs` |\n| `getWorkProfile` | `#[napi] pub fn get_work_profile(...)` (카멜 케이스 내보내기) | `crates/pi-natives/src/prof.rs` |\n| `invalidateFsScanCache` | `#[napi] pub fn invalidate_fs_scan_cache(...)` | `crates/pi-natives/src/fs_cache.rs` |\n\n필수 심볼이 누락되면, 로더는 재빌드 힌트와 함께 즉시 실패합니다.\n\n## 실패 동작 및 진단\n\n## 빌드 시점 실패\n\n- 잘못된 변형 구성:\n  - 비-x64에서 `TARGET_VARIANT` 설정 → 즉시 에러\n  - 명시적 `TARGET_VARIANT` 없는 x64 크로스 빌드 → 즉시 에러\n- Cargo 빌드 실패:\n  - 스크립트가 비-제로 종료 코드와 stderr을 표시\n- 아티팩트 미발견:\n  - 스크립트가 확인한 모든 프로파일 디렉토리를 출력\n- 설치 실패:\n  - 명시적 메시지; Windows는 잠긴 파일 힌트 포함\n\n## 런타임 로더 실패 (`native.ts`)\n\n- 지원되지 않는 플랫폼 태그:\n  - 지원 플랫폼 목록과 함께 예외 발생\n- 로드 가능한 후보 없음:\n  - 전체 후보 에러 목록과 모드별 수정 힌트와 함께 예외 발생\n- 누락된 내보내기:\n  - 정확한 누락 심볼 이름과 재빌드 명령어와 함께 예외 발생\n- 임베디드 추출 문제:\n  - 추출 mkdir/write 에러가 기록되어 최종 진단에 포함\n\n## 문제 해결 매트릭스\n\n| 증상 | 가능한 원인 | 확인 방법 | 수정 방법 |\n| --- | --- | --- | --- |\n| `Native addon missing exports ... Missing: <name>` | 오래된 `.node` 바이너리, Rust 내보내기 이름 불일치, 또는 잘못된 바이너리 로드 | `PI_DEV=1`로 실행하여 로드된 경로 확인; 해당 파일의 내보내기 목록 검사 | `build` 재빌드; Rust `#[napi]` 내보내기 이름(또는 필요 시 명시적 별칭)이 JS 키와 일치하는지 확인; 오래된 캐시/버전 파일 제거 |\n| x64 머신에서 modern이 예상되지만 baseline을 로드 | `PI_NATIVE_VARIANT=baseline`, AVX2 미감지, 또는 baseline 파일만 존재 | `PI_NATIVE_VARIANT` 확인; `native/`에서 `-modern` 파일 검사 | modern 변형 빌드 (`TARGET_VARIANT=modern ... build`) 후 파일이 배포에 포함되었는지 확인 |\n| 크로스 빌드가 사용 불가/잘못 레이블된 바이너리 생성 | `CROSS_TARGET`과 `TARGET_PLATFORM`/`TARGET_ARCH` 불일치, 또는 x64용 `TARGET_VARIANT` 누락 | 환경 튜플과 출력 파일명 확인 | 일관된 환경 값과 명시적 x64 `TARGET_VARIANT`로 재실행 |\n| 업그레이드 후 컴파일된 바이너리 실패 | 오래된 추출 캐시 (`~/.xcsh/natives/<이전-또는-불일치-버전>`) 또는 임베디드 매니페스트 불일치 | 버전별 natives 디렉토리와 로더 에러 목록 검사 | 해당 패키지 버전의 버전별 natives 캐시 삭제 후 재실행; 패키징 시 임베디드 매니페스트 재생성 |\n| 로더가 많은 경로를 탐색하지만 모두 실패 | 플랫폼 불일치 또는 패키지 `native/`에 릴리스 아티팩트 누락 | `platformTag`와 실제 파일명 비교 확인 | 빌드된 파일명이 `pi_natives.<platform>-<arch>(-variant).node` 규칙과 정확히 일치하고 패키지에 `native/`가 포함되었는지 확인 |\n| `embed:native`가 \"Incomplete native addons\"로 실패 | 임베딩 전에 필수 변형 파일이 빌드되지 않음 | 에러 텍스트에서 예상 vs 발견 목록 확인 | 필수 파일을 먼저 빌드 (x64: modern+baseline 모두; 비-x64: 기본), 후 `embed:native` 재실행 |\n\n## 운영 명령어\n\n```bash\n# 현재 호스트용 릴리스 아티팩트\nbun --cwd=packages/natives run build\n\n# 디버그 프로파일 아티팩트 빌드\nbun --cwd=packages/natives run dev:native\n\n# 명시적 x64 변형 빌드\nTARGET_VARIANT=modern bun --cwd=packages/natives run build\nTARGET_VARIANT=baseline bun --cwd=packages/natives run build\n\n# 빌드된 네이티브 파일로부터 임베디드 애드온 매니페스트 생성\nbun --cwd=packages/natives run embed:native\n\n# 임베디드 매니페스트를 null 스텁으로 초기화\nbun --cwd=packages/natives run embed:native -- --reset\n```\n",
	"ko/natives/natives-media-system-utils.md": "---\ntitle: 네이티브 미디어 및 시스템 유틸리티\ndescription: '스크린샷, 이미지 처리 및 시스템 정보를 위한 네이티브 미디어 처리 유틸리티.'\nsidebar:\n  order: 7\n  label: 미디어 & 시스템 유틸리티\ni18n:\n  sourceHash: 430898c177bc\n  translator: machine\n---\n\n# 네이티브 미디어 + 시스템 유틸리티\n\n이 문서는 [`docs/natives-architecture.md`](./natives-architecture.md)에 설명된 **system/media/conversion primitives** 계층에 대한 서브시스템 심층 분석입니다: `image`, `html`, `clipboard`, 그리고 `work` 프로파일링을 다룹니다.\n\n## 구현 파일\n\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/prof.rs`\n- `crates/pi-natives/src/task.rs`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/work/index.ts`\n- `packages/natives/src/work/types.ts`\n\n> 참고: `crates/pi-natives/src/work.rs`는 존재하지 않습니다. 작업 프로파일링은 `prof.rs`에서 구현되며 `task.rs`의 계측에 의해 데이터가 공급됩니다.\n\n## TS API ↔ Rust export/module 매핑\n\n| TS export (packages/natives)                | Rust N-API export                                                       | Rust 모듈                           |\n| ------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------- |\n| `PhotonImage.parse(bytes)`                  | `PhotonImage::parse`                                                     | `image.rs`                            |\n| `PhotonImage#resize(width, height, filter)` | `PhotonImage::resize`                                                    | `image.rs`                            |\n| `PhotonImage#encode(format, quality)`       | `PhotonImage::encode`                                                    | `image.rs`                            |\n| `htmlToMarkdown(html, options)`             | `html_to_markdown`                                                       | `html.rs`                             |\n| `copyToClipboard(text)`                     | `copy_to_clipboard` + TS 폴백 로직                                  | `clipboard.rs` + `clipboard/index.ts` |\n| `readImageFromClipboard()`                  | `read_image_from_clipboard`                                              | `clipboard.rs`                        |\n| `getWorkProfile(lastSeconds)`               | `get_work_profile`                                                      | `prof.rs`                             |\n\n## 데이터 형식 경계 및 변환\n\n### 이미지 (`image`)\n\n- **JS 입력 경계**: `Uint8Array` 인코딩된 이미지 바이트.\n- **Rust 디코드 경계**: 바이트가 `Vec<u8>`로 복사되고, `ImageReader::with_guessed_format()`으로 형식이 추측된 후 `DynamicImage`로 디코딩됩니다.\n- **메모리 내 상태**: `PhotonImage`는 `Arc<DynamicImage>`를 저장합니다.\n- **출력 경계**: `encode(format, quality)`는 `Promise<Uint8Array>` (Rust `Vec<u8>`)를 반환합니다.\n\n형식 ID는 숫자입니다:\n\n- `0`: PNG\n- `1`: JPEG\n- `2`: WebP (무손실 인코더)\n- `3`: GIF\n\n제약 조건:\n\n- `quality`는 JPEG에서만 사용됩니다.\n- PNG/WebP/GIF는 `quality`를 무시합니다.\n- 지원되지 않는 형식 ID는 실패합니다 (`Invalid image format: <id>`).\n\n### HTML 변환 (`html`)\n\n- **JS 입력 경계**: HTML `string` + 선택적 객체 `{ cleanContent?: boolean; skipImages?: boolean }`.\n- **Rust 변환 경계**: `String` 입력이 `html_to_markdown_rs::convert`에 의해 변환됩니다.\n- **출력 경계**: Markdown `string`.\n\n변환 동작:\n\n- `cleanContent`의 기본값은 `false`입니다.\n- `cleanContent=true`일 때, `PreprocessingPreset::Aggressive`와 네비게이션/폼에 대한 하드 제거 플래그가 포함된 전처리가 활성화됩니다.\n- `skipImages`의 기본값은 `false`입니다.\n\n### 클립보드 (`clipboard`)\n\n- **텍스트 경로**:\n  - TS는 stdout이 TTY일 때 먼저 OSC 52 (`\\x1b]52;c;<base64>\\x07`)를 출력합니다.\n  - 동일한 텍스트가 최선 노력 방식으로 네이티브 클립보드 API (`native.copyToClipboard`)를 통해 시도됩니다.\n  - Termux에서는 TS가 먼저 `termux-clipboard-set`을 시도합니다.\n- **이미지 읽기 경로**:\n  - Rust가 `arboard`에서 원시 이미지를 읽습니다.\n  - Rust가 이를 PNG 바이트로 재인코딩하고 (`image` 크레이트), `{ data: Uint8Array, mimeType: \"image/png\" }`를 반환합니다.\n  - TS는 Termux 또는 디스플레이 서버가 없는 Linux 세션(`DISPLAY`/`WAYLAND_DISPLAY` 누락)에서 `null`을 조기 반환합니다.\n\n### 작업 프로파일링 (`work`)\n\n- **수집 경계**: 프로파일링 샘플은 `task::blocking`과 `task::future`의 `profile_region(tag)` 가드에 의해 생성됩니다.\n- **저장 형식**: 스택 경로 + 지속 시간 (`μs`) + 타임스탬프 (`프로세스 시작 이후 μs`)를 저장하는 고정 크기 순환 버퍼 (`MAX_SAMPLES = 10_000`).\n- **출력 경계**: `getWorkProfile(lastSeconds)`는 다음 객체를 반환합니다:\n  - `folded`: 접힌 스택 텍스트 (플레임그래프 입력)\n  - `summary`: 마크다운 테이블 요약\n  - `svg`: 선택적 플레임그래프 SVG\n  - `totalMs`, `sampleCount`\n\n## 생명주기 및 상태 전이\n\n### 이미지 생명주기\n\n1. `PhotonImage.parse(bytes)`가 블로킹 디코드 작업을 스케줄링합니다 (`image.decode`).\n2. 성공 시, 네이티브 `PhotonImage` 핸들이 JS에 존재합니다.\n3. `resize(...)`는 새로운 네이티브 핸들을 생성하며 (`image.resize`), 이전 핸들과 새 핸들이 공존할 수 있습니다.\n4. `encode(...)`는 이미지 크기를 변경하지 않고 바이트를 구체화합니다 (`image.encode`).\n\n실패 전이:\n\n- 형식 감지/디코드 실패는 parse 프로미스를 거부합니다.\n- 인코드 실패는 encode 프로미스를 거부합니다.\n- 유효하지 않은 형식 ID는 encode 프로미스를 거부합니다.\n\n### HTML 생명주기\n\n1. `htmlToMarkdown(html, options)`가 블로킹 변환 작업을 스케줄링합니다.\n2. 변환은 지정되지 않은 경우 기본 옵션 (`cleanContent=false`, `skipImages=false`)으로 실행됩니다.\n3. 마크다운 문자열을 반환하거나 거부합니다.\n\n실패 전이:\n\n- 변환기 실패는 거부된 프로미스를 반환합니다 (`Conversion error: ...`).\n\n### 클립보드 생명주기\n\n`copyToClipboard(text)`는 의도적으로 최선 노력 방식이며 다중 경로입니다:\n\n1. TTY인 경우: OSC 52 쓰기를 시도합니다 (base64 페이로드).\n2. `TERMUX_VERSION`이 설정된 경우 Termux 명령을 시도합니다.\n3. 네이티브 `arboard` 텍스트 복사를 시도합니다.\n4. TS 계층에서 오류를 무시합니다.\n\n`readImageFromClipboard()`의 엄격성은 단계별로 다릅니다:\n\n1. TS가 지원되지 않는 런타임 컨텍스트(Termux/헤드리스 Linux)를 `null`로 하드 게이트합니다.\n2. Rust `arboard` 읽기는 TS가 허용한 경우에만 실행됩니다.\n3. `ContentNotAvailable`은 `null`로 매핑됩니다.\n4. 기타 Rust 오류는 거부합니다.\n\n### 작업 프로파일링 생명주기\n\n1. 명시적 시작이 없습니다: 작업 헬퍼가 실행될 때 프로파일링이 항상 켜져 있습니다.\n2. 모든 계측된 작업 스코프는 가드 드롭 시 하나의 샘플을 기록합니다.\n3. 버퍼 용량에 도달하면 샘플이 가장 오래된 항목을 덮어씁니다.\n4. `getWorkProfile(lastSeconds)`가 시간 윈도우를 읽고 접힌 스택/요약/SVG 아티팩트를 도출합니다.\n\n실패 전이:\n\n- SVG 생성 실패는 소프트 실패입니다 (`svg: null`), 접힌 스택과 요약은 여전히 반환됩니다.\n- 빈 샘플 윈도우는 빈 접힌 데이터와 `svg: null`을 반환하며, 오류가 아닙니다.\n\n## 지원되지 않는 작업 및 오류 전파\n\n### 이미지\n\n- 지원되지 않는 디코드 입력 또는 손상된 바이트: 엄격한 실패 (프로미스 거부).\n- 지원되지 않는 인코드 형식 ID: 엄격한 실패.\n- TS 래퍼에 최선 노력 폴백 경로가 없습니다.\n\n### HTML\n\n- 변환 오류는 엄격한 실패입니다 (거부).\n- 옵션 생략은 최선 노력 기본값 적용이며, 실패가 아닙니다.\n\n### 클립보드\n\n- 텍스트 복사는 TS 계층에서 최선 노력 방식입니다: 작동 실패가 억제됩니다.\n- 이미지 읽기는 \"이미지 없음\" (`null`)과 작동 실패 (거부)를 구분합니다.\n- Termux/헤드리스 Linux는 이미지 읽기에 대해 지원되지 않는 컨텍스트로 처리됩니다 (`null`).\n\n### 작업 프로파일링\n\n- 함수 호출 자체에 대한 검색은 엄격하지만, 아티팩트 생성은 부분적으로 최선 노력 방식입니다 (`svg`는 null 가능).\n- 버퍼 잘림은 예상되는 동작(링 버퍼)이며, 데이터 손실 버그가 아닙니다.\n\n## 플랫폼 주의사항\n\n- **클립보드 텍스트**: OSC 52는 터미널 지원에 의존하며, 네이티브 클립보드 접근은 데스크톱 환경/세션에 의존합니다.\n- **클립보드 이미지 읽기**: Termux 및 디스플레이 서버가 없는 Linux에서는 TS에서 차단됩니다.\n",
	"ko/natives/natives-rust-task-cancellation.md": "---\ntitle: 네이티브 Rust 태스크 실행 및 취소\ndescription: 협력적 취소 및 정리 시맨틱을 갖춘 Rust 비동기 태스크 실행 모델.\nsidebar:\n  order: 5\n  label: 태스크 취소\ni18n:\n  sourceHash: 0fbf45c6d463\n  translator: machine\n---\n\n# 네이티브 Rust 태스크 실행 및 취소 (`pi-natives`)\n\n이 문서는 `crates/pi-natives`가 네이티브 작업을 스케줄링하는 방법과 JS 옵션(`timeoutMs`, `AbortSignal`)에서 Rust 실행으로 취소가 전달되는 방식을 설명합니다.\n\n## 구현 파일\n\n- `crates/pi-natives/src/task.rs`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/fd.rs`\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/ps.rs`\n\n## 핵심 기본 요소 (`task.rs`)\n\n`task.rs`는 세 가지 핵심 구성 요소를 정의합니다:\n\n1. `task::blocking(tag, cancel_token, work)`\n   - `napi::AsyncTask` / `Task`를 래핑합니다.\n   - `compute()`는 libuv 워커 스레드에서 실행됩니다(CPU 집약적이거나 블로킹/동기 시스템 호출의 경우).\n   - JS `Promise<T>`를 반환합니다.\n\n2. `task::future(env, tag, work)`\n   - `env.spawn_future(...)`를 래핑합니다.\n   - Tokio 런타임에서 비동기 작업을 실행합니다.\n   - `PromiseRaw<'env, T>`를 반환합니다.\n\n3. `CancelToken` / `AbortToken` / `AbortReason`\n   - `CancelToken::new(timeout_ms, signal)`은 데드라인과 선택적 `AbortSignal`을 결합합니다.\n   - `CancelToken::heartbeat()`는 블로킹 루프를 위한 협력적 취소입니다.\n   - `CancelToken::wait()`는 비동기 취소 대기입니다(`Signal` / `Timeout` / `User` Ctrl-C).\n   - `AbortToken`은 외부 코드가 중단을 요청할 수 있게 합니다(`abort(reason)`).\n\n## `blocking` vs `future`: 실행 모델 및 선택 기준\n\n### `task::blocking` 사용 시\n\n작업이 CPU 집약적이거나 근본적으로 동기/블로킹인 경우 사용합니다:\n\n- 정규식/파일 스캐닝 (`grep`, `glob`, `fuzzy_find`)\n- 동기 PTY 루프 내부 (`spawn_blocking`을 통한 `run_pty_sync`)\n- 클립보드/이미지/html 변환\n\n동작 방식:\n\n- 작업 클로저는 클론된 `CancelToken`을 받습니다.\n- 취소는 코드가 `ct.heartbeat()?`를 확인하는 곳에서만 감지됩니다.\n- 클로저 `Err(...)`은 JS 프로미스를 거부합니다.\n\n### `task::future` 사용 시\n\n작업이 비동기 작업을 `await`해야 하는 경우 사용합니다:\n\n- 셸 세션 오케스트레이션 (`shell.run`, `executeShell`)\n- 완료와 취소 간의 태스크 경쟁 (`tokio::select!`)\n\n동작 방식:\n\n- Future는 일반 완료와 `ct.wait()` 간에 경쟁할 수 있습니다.\n- 취소 경로에서 비동기 구현은 일반적으로 내부 서브시스템(예: `tokio_util::CancellationToken`)에 취소를 전파하고 선택적으로 유예 타임아웃 후 강제 중단합니다.\n\n## JS API ↔ Rust 익스포트 매핑 (태스크/취소 관련)\n\n| JS facing API | Rust 익스포트 (`#[napi]`) | 스케줄러 | 취소 연결 |\n|---|---|---|---|\n| `grep(options, onMatch?)` | `grep` | `task::blocking(\"grep\", ct, ...)` | `CancelToken::new(options.timeoutMs, options.signal)` + `ct.heartbeat()` |\n| `glob(options, onMatch?)` | `glob` | `task::blocking(\"glob\", ct, ...)` | `CancelToken::new(...)` + 필터 루프에서 `ct.heartbeat()` |\n| `fuzzyFind(options)` | `fuzzy_find` | `task::blocking(\"fuzzy_find\", ct, ...)` | `CancelToken::new(...)` + 스코어링 루프에서 `ct.heartbeat()` |\n| `shell.run(options, onChunk?)` | `Shell::run` | `task::future(env, \"shell.run\", ...)` | `ct.wait()`가 실행 태스크와 경쟁; Tokio `CancellationToken`에 브리지 |\n| `executeShell(options, onChunk?)` | `execute_shell` | `task::future(env, \"shell.execute\", ...)` | 위와 동일 |\n| `pty.start(options, onChunk?)` | `PtySession::start` | `task::future(env, \"pty.start\", ...)` + 내부 `spawn_blocking` | 동기 PTY 루프에서 `heartbeat()`를 통해 `CancelToken` 확인 |\n| `htmlToMarkdown(html, options?)` | `html_to_markdown` | `task::blocking(\"html_to_markdown\", (), ...)` | 없음 (`()` 토큰) |\n| `PhotonImage.parse/encode/resize` | `PhotonImage::{parse,encode,resize}` | `task::blocking(...)` | 없음 (`()` 토큰) |\n| `copyToClipboard/readImageFromClipboard` | `copy_to_clipboard` / `read_image_from_clipboard` | `task::blocking(...)` | 없음 (`()` 토큰) |\n\n`text.rs`와 `ps.rs`는 현재 `task::blocking`/`task::future`를 사용하지 않으므로 이 취소 경로에 참여하지 않습니다.\n\n## 취소 생명주기 및 상태 전이\n\n### `CancelToken` 생명주기\n\n`CancelToken`은 협력적이며 상태를 가집니다:\n\n```text\nCreated\n  ├─ 신호 없음 + 타임아웃 없음  -> 수동 토큰 (외부에서 설정되지 않는 한 중단되지 않음)\n  ├─ 신호 등록됨               -> AbortSignal 콜백을 기다림\n  └─ 데드라인 설정됨            -> 타임아웃 확인이 활성화됨\n\nRunning\n  ├─ heartbeat()/wait()가 신호 감지    -> AbortReason::Signal\n  ├─ heartbeat()/wait()가 데드라인 감지 -> AbortReason::Timeout\n  ├─ wait()가 Ctrl-C 감지             -> AbortReason::User\n  └─ 중단 없음                        -> 계속\n\nAborted (종료 상태)\n  └─ 첫 번째 중단 이유가 우선 (원자적 플래그 + 알림자)\n```\n\n### 시작 전 vs 실행 중 취소\n\n- **시작 전 / 첫 번째 취소 확인 전**:\n  - `ct.wait()`에서 경쟁하는 `task::future` 사용자는 `select!`에 진입하면 즉시 취소를 해결할 수 있습니다.\n  - `task::blocking` 사용자는 클로저 코드가 `heartbeat()`에 도달할 때만 취소를 감지합니다. 클로저가 일찍 heartbeat를 수행하지 않으면 취소가 지연됩니다.\n\n- **실행 중**:\n  - `blocking`: 다음 `heartbeat()`가 `Err(\"Aborted: ...\")`를 반환합니다.\n  - `future`: `ct.wait()` 브랜치가 `select!`에서 승리하고, 이후 코드가 종속 비동기 기계를 취소합니다(셸의 경우: Tokio 토큰을 취소하고, 최대 2초 대기 후 태스크를 중단합니다).\n\n## 장기 실행 루프에 대한 Heartbeat 요구 사항\n\n`heartbeat()`는 크기가 제한되지 않거나 큰 작업 세트를 가진 루프에서 예측 가능한 주기로 실행되어야 합니다.\n\n관찰된 패턴:\n\n- `glob::filter_entries`: 필터링/매칭 전에 각 항목을 확인합니다.\n- `fd::score_entries`: 스캔된 각 후보를 확인합니다.\n- `grep_sync`: 무거운 검색 단계 전에 명시적 취소 확인, 그리고 토큰도 수신하는 fs-cache 호출.\n- `run_pty_sync`: 매 루프 틱마다 확인(~16ms 슬립 주기)하고 취소 시 자식 프로세스를 종료합니다.\n\n실용적 규칙: 외부 크기 입력에 대한 루프는 heartbeat 없이 짧은 제한 구간을 초과해서는 안 됩니다.\n\n## JS에 대한 실패 동작 및 오류 전파\n\n### 블로킹 태스크\n\n오류 경로:\n\n1. 클로저가 `Err(napi::Error)`를 반환합니다(`heartbeat()` 중단 포함).\n2. `Task::compute()`가 `Err`를 반환합니다.\n3. `AsyncTask`가 JS 프로미스를 거부합니다.\n\n일반적인 오류 문자열:\n\n- `Aborted: Timeout`\n- `Aborted: Signal`\n- 도메인 오류 (`Failed to decode image: ...`, `Conversion error: ...` 등)\n\n### Future 태스크\n\n오류 경로:\n\n1. 비동기 본문이 `Err(napi::Error)`를 반환하거나 조인 실패가 매핑됩니다(`... task failed: {err}`).\n2. `task::future`가 스폰한 프로미스가 거부됩니다.\n3. 일부 API는 거부 대신 의도적으로 구조화된 취소 결과를 반환합니다(`ShellRunResult`/`ShellExecuteResult`에 `cancelled`/`timed_out` 플래그 및 `exit_code: None` 포함).\n\n### 취소 보고 방식 분리\n\n- **오류로서의 중단**: `heartbeat()?`를 사용하는 대부분의 블로킹 익스포트.\n- **타입화된 결과로서의 중단**: 결과 구조체에서 취소를 모델링하는 셸/pty 스타일 명령 API.\n\nAPI별로 하나의 모델을 선택하고 명시적으로 문서화하십시오.\n\n## 일반적인 함정\n\n1. **블로킹 루프에서 heartbeat 누락**\n   - 증상: 루프가 끝날 때까지 타임아웃/신호가 무시되는 것처럼 보입니다.\n   - 해결: 루프 상단과 항목별 비용이 큰 단계 전에 `ct.heartbeat()?`를 추가합니다.\n\n2. **취소할 수 없는 긴 섹션**\n   - 증상: 단일 대형 호출(디코드, 정렬, 압축 등) 중에 취소 지연이 급증합니다.\n   - 해결: 작업을 heartbeat 경계가 있는 청크로 분할합니다; 불가능한 경우 지연을 문서화합니다.\n\n3. **비동기 실행기 블로킹**\n   - 증상: 동기 집약적 코드가 future 내에서 직접 실행될 때 비동기 API가 멈춥니다.\n   - 해결: CPU/동기 블록을 `task::blocking` 또는 `tokio::task::spawn_blocking`으로 이동합니다.\n\n4. **일관성 없는 취소 시맨틱**\n   - 증상: 한 API는 취소 시 거부하고, 다른 API는 플래그와 함께 해결하여 호출자를 혼란스럽게 합니다.\n   - 해결: 도메인별로 표준화하고 래퍼 문서를 일치시킵니다.\n\n5. **중첩된 비동기 태스크에서 취소 브리지 누락**\n   - 증상: 외부 토큰이 취소되었지만 내부 리더/서브프로세스 태스크가 계속 실행됩니다.\n   - 해결: 내부 토큰/신호로 취소를 브리지하고 유예 타임아웃 + 강제 중단 폴백을 적용합니다.\n\n## 새로운 취소 가능 익스포트를 위한 체크리스트\n\n1. 작업을 올바르게 분류합니다:\n   - CPU 집약적이거나 동기 블로킹 -> `task::blocking`\n   - 비동기 I/O / `await` 오케스트레이션 -> `task::future`\n\n2. 필요할 때 취소 입력을 노출합니다:\n   - `#[napi(object)]` 옵션에 `timeoutMs`와 `signal`을 포함합니다\n   - `let ct = task::CancelToken::new(timeout_ms, signal);`을 생성합니다\n\n3. 모든 레이어에 취소를 연결합니다:\n   - 블로킹 루프: 안정적인 간격으로 `ct.heartbeat()?`\n   - 비동기 오케스트레이션: `ct.wait()`와 경쟁하고 서브태스크/토큰을 취소합니다\n\n4. 취소 계약을 결정합니다:\n   - 중단 오류로 프로미스를 거부하거나,\n   - 타입화된 `{ cancelled, timedOut, ... }`으로 해결합니다\n   - API 계열에 대해 이 계약을 일관되게 유지합니다\n\n5. 컨텍스트와 함께 실패를 전파합니다:\n   - `Error::from_reason(format!(\"...: {err}\"))`를 통해 오류를 매핑합니다\n   - 단계별 접두사를 포함합니다 (`spawn`, `decode`, `wait` 등)\n\n6. 시작 전 및 실행 중 취소를 처리합니다:\n   - 취소 확인/대기는 비용이 큰 본문 전과 장기 실행 중에 이루어져야 합니다\n\n7. 실행기 오용이 없는지 검증합니다:\n   - `spawn_blocking`/블로킹 태스크 래퍼 없이 비동기 future 내에서 직접 장기 동기 작업을 실행하지 않습니다\n",
	"ko/natives/natives-shell-pty-process.md": "---\ntitle: '네이티브 셸, PTY, 프로세스 및 키 내부 구조'\ndescription: '네이티브 레이어에서의 셸 실행, PTY 관리, 프로세스 생명주기 및 키 이벤트 처리.'\nsidebar:\n  order: 4\n  label: '셸, PTY 및 프로세스'\ni18n:\n  sourceHash: 00ea95614c6a\n  translator: machine\n---\n\n# 네이티브 셸, PTY, 프로세스 및 키 내부 구조\n\n이 문서는 `@f5-sales-demo/pi-natives`의 **실행/프로세스/터미널 기본 요소**인 `shell`, `pty`, `ps`, `keys`를 `docs/natives-architecture.md`의 아키텍처 용어를 사용하여 설명합니다.\n\n## 구현 파일\n\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/shell/windows.rs` (Windows 전용)\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/ps.rs`\n- `crates/pi-natives/src/keys.rs`\n- `crates/pi-natives/src/task.rs` (shell/pty에서 사용하는 공유 취소 동작)\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/bindings.ts`\n\n## 레이어 소유권\n\n- **TS 래퍼/API 레이어** (`packages/natives/src/*`): 타입이 지정된 진입점, 취소 인터페이스 (`timeoutMs`, `AbortSignal`), JS 편의 기능.\n- **Rust N-API 모듈 레이어** (`crates/pi-natives/src/*`): 셸/PTY 프로세스 실행, 프로세스 트리 순회/종료, 키 시퀀스 파싱.\n- **유효성 검사 게이트** (`native.ts`, 아키텍처 레벨): 래퍼가 사용되기 전에 필요한 내보내기(`Shell`, `executeShell`, `PtySession`, `killTree`, `listDescendants`, 키 헬퍼)가 존재하는지 확인.\n\n## 셸 서브시스템 (`shell`)\n\n### API 모델\n\n두 가지 실행 모드가 제공됩니다:\n\n1. `executeShell(options, onChunk?)`를 통한 **일회성** 실행.\n2. `new Shell(options?)`을 통한 **영구 세션** 생성 후 `shell.run(...)`을 반복 호출.\n\n두 모드 모두 스레드 안전 콜백을 통해 출력을 스트리밍하며 `{ exitCode?, cancelled, timedOut }`을 반환합니다.\n\n### 세션 생성 및 환경 모델\n\nRust는 다음과 같이 `brush_core::Shell`을 생성합니다:\n\n- 비대화형 모드,\n- `do_not_inherit_env: true`,\n- 호스트 환경에서의 명시적인 환경 재구성,\n- 셸 민감 변수(`PS1`, `PWD`, `SHLVL`, bash 함수 내보내기 등)에 대한 스킵 목록 적용.\n\n세션 환경 동작:\n\n- `ShellOptions.sessionEnv`는 세션 생성 시 한 번만 적용됩니다.\n- `ShellRunOptions.env`는 명령 범위(`EnvironmentScope::Command`)로 각 실행 후 팝됩니다.\n- `PATH`는 Windows에서 대소문자 구분 없는 중복 제거와 함께 특별히 병합됩니다.\n\nWindows 전용 경로 보강(`shell/windows.rs`): 발견된 Git-for-Windows 경로(`cmd`, `bin`, `usr/bin`)가 존재하고 아직 포함되지 않은 경우 추가됩니다.\n\n### 런타임 생명주기 및 상태 전환\n\n영구 셸(`Shell.run`)은 다음 상태 머신을 사용합니다:\n\n- **유휴/미초기화**: `session: None`.\n- **실행 중**: 첫 번째 `run()`이 세션을 지연 생성하고, `current_abort` 토큰을 저장하며, 명령을 실행합니다.\n- **완료 + 킵얼라이브**: 실행 제어 흐름이 `Normal`이면 `current_abort`가 지워지고 세션이 재사용됩니다.\n- **완료 + 종료**: 제어 흐름이 루프/스크립트/셸 종료 관련(`BreakLoop`, `ContinueLoop`, `ReturnFromFunctionOrScript`, `ExitShell`)이면 세션이 삭제됩니다(`session: None`).\n- **취소됨/타임아웃**: 실행 태스크가 취소되고, 2초의 유예 대기 후 강제 중단되며, 세션이 삭제됩니다.\n- **오류**: 세션이 삭제됩니다.\n\n일회성 셸(`executeShell`)은 호출마다 항상 새 세션을 생성하고 삭제합니다.\n\n### 스트리밍/출력 동작\n\n- 표준 출력/표준 오류는 공유 파이프로 라우팅되어 동시에 읽힙니다.\n- 리더는 UTF-8을 증분 방식으로 디코딩하며, 잘못된 바이트 시퀀스는 `U+FFFD` 대체 청크를 내보냅니다.\n- 프로세스 완료 후, 출력 드레인에는 백그라운드 작업이 디스크립터를 열어두는 경우 중단을 방지하기 위해 유휴/최대 가드(`250ms` 유휴, `2s` 최대)가 적용됩니다.\n\n### 취소, 타임아웃 및 백그라운드 작업\n\n- `CancelToken`은 `timeoutMs`와 선택적 `AbortSignal`로부터 구성됩니다.\n- 취소/타임아웃 시 셸 취소 토큰이 트리거되고, 강제 중단 전에 태스크에 2초의 유예 기간이 부여됩니다.\n- 취소가 발생하면 brush 작업 메타데이터를 사용하여 백그라운드 작업이 종료됩니다(`TERM`, 이후 지연 `KILL`).\n\n`Shell.abort()` 동작:\n\n- 해당 `Shell` 인스턴스에서 현재 실행 중인 명령만 중단합니다,\n- 실행 중인 것이 없으면 성공적인 no-op입니다.\n\n### 실패 동작\n\n자주 발생하는 오류에는 다음이 포함됩니다:\n\n- 세션 초기화 실패(`Failed to initialize shell`),\n- cwd 오류(`Failed to set cwd`),\n- 환경 설정/팝 실패,\n- 스냅샷 소스 실패,\n- 파이프 생성/복제 실패,\n- 실행 실패(`Shell execution failed: ...`),\n- 태스크 래퍼 실패(`Shell execution task failed: ...`).\n\n결과 수준의 취소 플래그:\n\n- 타임아웃 -> `exitCode: undefined`, `timedOut: true`.\n- 중단 신호 -> `exitCode: undefined`, `cancelled: true`.\n\n## PTY 서브시스템 (`pty`)\n\n### API 모델\n\n`new PtySession()`은 다음을 제공합니다:\n\n- `start(options, onChunk?) -> Promise<{ exitCode?, cancelled, timedOut }>`\n- `write(data)`\n- `resize(cols, rows)`\n- `kill()`\n\n### 런타임 생명주기 및 상태 전환\n\n`PtySession` 상태 머신:\n\n- **유휴**: `core: None`.\n- **예약됨**: `start()`가 비동기 작업 시작 전에 동기적으로 제어 채널을 설치하므로(`core: Some`), `write/resize/kill`이 즉시 유효해집니다.\n- **실행 중**: 블로킹 PTY 루프가 자식 상태, 리더 이벤트, 취소 하트비트 및 제어 메시지를 처리합니다.\n- **터미널 종료**: 자식 종료 + 리더 완료.\n- **최종화됨**: `core`는 start 태스크 완료(성공 또는 오류) 후 항상 `None`으로 재설정됩니다.\n\n동시성 가드:\n\n- 이미 실행 중인 상태에서 시작하면 `PTY session already running`을 반환합니다.\n\n### 스폰/연결/쓰기/읽기/종료 패턴\n\n- PTY는 `portable_pty::native_pty_system().openpty(...)`를 통해 열립니다.\n- 명령은 현재 선택적 `cwd` 및 환경 오버라이드와 함께 `sh -lc <command>`로 실행됩니다.\n- `write()`는 원시 바이트를 PTY 표준 입력으로 전송합니다.\n- `resize()`는 차원을 클램프(`cols 20..400`, `rows 5..200`)하고 마스터 크기 조정을 호출합니다.\n- `kill()`은 실행을 취소됨으로 표시하고 자식 프로세스를 종료합니다.\n\n출력 경로:\n\n- 전용 리더 스레드가 마스터 스트림을 읽고,\n- 잘못된 바이트에서 `U+FFFD` 대체와 함께 증분 UTF-8 디코딩을 수행하며,\n- 청크는 N-API 스레드 안전 콜백을 통해 전달됩니다.\n\n### 취소 및 타임아웃 의미론\n\n- `timeoutMs`와 `AbortSignal`이 `CancelToken`을 구성합니다.\n- 루프는 주기적으로 `ct.heartbeat()`를 호출하며, 중단은 자식 종료를 트리거합니다.\n- 타임아웃 분류는 하트비트 오류에서 문자열 기반(`\"Timeout\"` 부분 문자열)으로 수행됩니다.\n\n### 실패 동작\n\n오류 발생 지점에는 다음이 포함됩니다:\n\n- PTY 할당/열기 실패,\n- PTY 스폰 실패,\n- 라이터/리더 획득 실패,\n- 자식 상태/대기 실패,\n- 락 포이즈닝,\n- 제어 채널 연결 끊김(`PTY session is no longer available`).\n\n실행 중이 아닐 때의 제어 호출 실패:\n\n- `write/resize/kill`은 `PTY session is not running`을 반환합니다.\n\n## 프로세스 트리 서브시스템 (`ps`)\n\n### API 모델\n\n- `killTree(pid, signal) -> number`\n- `listDescendants(pid) -> number[]`\n\nTS 래퍼는 또한 `setNativeKillTree(native.killTree)`를 통해 네이티브 킬 트리 통합을 공유 유틸리티에 등록합니다.\n\n### 플랫폼별 구현\n\n- **Linux**: `/proc/<pid>/task/<pid>/children`을 재귀적으로 읽습니다.\n- **macOS**: `libproc`의 `proc_listchildpids`를 사용합니다.\n- **Windows**: `CreateToolhelp32Snapshot`으로 프로세스 테이블을 스냅샷하고, 부모->자식 맵을 구성하며, `OpenProcess(PROCESS_TERMINATE)` + `TerminateProcess`로 종료합니다.\n\n### 킬 트리 동작\n\n- 자손은 재귀적으로 수집됩니다.\n- 종료 순서는 하위에서 상위 방향(가장 깊은 자손 먼저)으로, 고아 재부모화를 줄이기 위함입니다.\n- 루트 pid는 마지막에 종료됩니다.\n- 반환 값은 성공적으로 종료된 수입니다.\n\n시그널 동작:\n\n- POSIX: 제공된 `signal`이 `kill`에 전달됩니다.\n- Windows: `signal`은 무시되며, 종료는 무조건적인 프로세스 종료입니다.\n\n### 실패 동작\n\n이 모듈은 API 표면에서 의도적으로 예외를 발생시키지 않습니다:\n\n- 누락되거나 접근 불가능한 프로세스 트리 브랜치는 건너뜁니다,\n- pid별 종료 실패는 오류가 아닌 실패로 계산됩니다,\n- 조회 실패 시 일반적으로 `listDescendants`는 `[]`를, `killTree`는 `0`을 반환합니다.\n\n## 키 파싱 서브시스템 (`keys`)\n\n### API 모델\n\n제공되는 헬퍼:\n\n- `parseKey(data, kittyProtocolActive)`\n- `matchesKey(data, keyId, kittyProtocolActive)`\n- `parseKittySequence(data)`\n- `matchesKittySequence(data, expectedCodepoint, expectedModifier)`\n- `matchesLegacySequence(data, keyName)`\n\n### 파싱 모델\n\n파서는 다음을 결합합니다:\n\n- 직접 단일 바이트 매핑(`enter`, `tab`, `ctrl+<letter>`, 인쇄 가능한 ASCII),\n- O(1) 레거시 이스케이프 시퀀스 조회 (PHF 맵),\n- xterm `modifyOtherKeys` 파싱,\n- Kitty 프로토콜 파싱(`CSI u`, `CSI ~`, `CSI 1;...<letter>`),\n- 키 ID로의 정규화(`ctrl+c`, `shift+tab`, `pageUp`, `f5` 등).\n\n수정자 처리:\n\n- 키 매칭에는 shift/alt/ctrl 비트만 비교됩니다,\n- 잠금 비트는 비교 전에 마스크됩니다.\n\n레이아웃 동작:\n\n- 기본 레이아웃 폴백은 의도적으로 제한되어 있어 리매핑된 레이아웃이 ASCII 문자/기호에 대한 잘못된 매칭을 생성하지 않습니다.\n\n### 실패 동작\n\n- 인식되지 않거나 잘못된 시퀀스는 파싱 함수에서 `null`을 반환합니다.\n- 매치 함수는 파싱 실패 또는 불일치 시 `false`를 반환합니다.\n- 잘못된 키 입력에 대해 발생하는 오류 표면이 없습니다.\n\n## JS 래퍼 API ↔ Rust 내보내기 매핑\n\n### 셸 + PTY + 프로세스\n\n| TS 래퍼 API | Rust N-API 내보내기 | 비고 |\n|---|---|---|\n| `executeShell(options, onChunk?)` | `executeShell` (`execute_shell`) | 일회성 셸 실행 |\n| `new Shell(options?)` | `Shell` 클래스 | 영구 셸 세션 |\n| `shell.run(options, onChunk?)` | `Shell::run` | 킵얼라이브 제어 흐름에서 세션 재사용 |\n| `shell.abort()` | `Shell::abort` | 해당 셸 인스턴스의 활성 실행 중단 |\n| `new PtySession()` | `PtySession` 클래스 | 상태 저장 PTY 세션 |\n| `pty.start(options, onChunk?)` | `PtySession::start` | 대화형 PTY 실행 |\n| `pty.write(data)` | `PtySession::write` | 원시 표준 입력 패스스루 |\n| `pty.resize(cols, rows)` | `PtySession::resize` | 클램프된 터미널 크기 |\n| `pty.kill()` | `PtySession::kill` | 활성 PTY 자식 강제 종료 |\n| `killTree(pid, signal)` | `killTree` (`kill_tree`) | 자식 우선 프로세스 트리 종료 |\n| `listDescendants(pid)` | `listDescendants` (`list_descendants`) | 재귀적 자손 목록 조회 |\n\n### 키\n\n| TS 래퍼 API | Rust N-API 내보내기 | 비고 |\n|---|---|---|\n| `matchesKittySequence(data, cp, mod)` | `matchesKittySequence` (`matches_kitty_sequence`) | Kitty 코드포인트+수정자 매칭 |\n| `parseKey(data, kittyProtocolActive)` | `parseKey` (`parse_key`) | 정규화된 키 ID 파서 |\n| `matchesLegacySequence(data, keyName)` | `matchesLegacySequence` (`matches_legacy_sequence`) | 정확한 레거시 시퀀스 맵 확인 |\n| `parseKittySequence(data)` | `parseKittySequence` (`parse_kitty_sequence`) | 구조화된 Kitty 파싱 결과 |\n| `matchesKey(data, keyId, kittyProtocolActive)` | `matchesKey` (`matches_key`) | 고수준 키 매처 |\n\n## 포기된 세션 정리 및 최종화 참고 사항\n\n- **셸 영구 세션**: 실행이 취소/타임아웃/오류/비킵얼라이브 제어 흐름인 경우, Rust는 내부 세션 상태를 명시적으로 삭제합니다. 정상적인 성공 실행은 세션을 재사용을 위해 유지합니다.\n- **PTY 세션**: `core`는 실패 경로를 포함하여 `start()`가 완료된 후 항상 지워집니다.\n- **명시적인 JS 파이널라이저 기반 종료 계약**은 래퍼에 의해 제공되지 않으며, 정리는 주로 실행 완료/취소 경로에 연결됩니다. 호출자는 결정론적 종료를 위해 `timeoutMs`, `AbortSignal`, `shell.abort()` 또는 `pty.kill()`을 사용해야 합니다.\n",
	"ko/natives/natives-text-search-pipeline.md": "---\ntitle: Natives 텍스트 및 검색 파이프라인\ndescription: >-\n  Native text search pipeline with grep, glob, and ripgrep-based file content\n  indexing.\nsidebar:\n  order: 6\n  label: 텍스트 & 검색 파이프라인\ni18n:\n  sourceHash: 0e93462fdd12\n  translator: machine\n---\n\n# Natives 텍스트/검색 파이프라인\n\n이 문서는 `@f5-sales-demo/pi-natives` 텍스트/검색 표면(`grep`, `glob`, `text`, `highlight`)을 TypeScript 래퍼에서 Rust N-API 내보내기로, 그리고 다시 JS 결과 객체로 매핑합니다.\n\n용어는 `docs/natives-architecture.md`를 따릅니다:\n\n- **래퍼(Wrapper)**: `packages/natives/src/*`의 TS API\n- **Rust 모듈 레이어**: `crates/pi-natives/src/*`의 N-API 내보내기\n- **공유 스캔 캐시**: 디스커버리/검색 흐름에서 사용하는 `fs_cache` 기반 디렉터리 항목 캐시\n\n## 구현 파일\n\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/glob_util.rs`\n- `crates/pi-natives/src/fs_cache.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/highlight.rs`\n- `crates/pi-natives/src/fd.rs`\n\n## JS API ↔ Rust 내보내기 매핑\n\n| JS 래퍼 API | Rust 내보내기 (`#[napi]`, snake_case -> camelCase) | Rust 모듈 |\n| --- | --- | --- |\n| `grep(options, onMatch?)` | `grep` | `grep.rs` |\n| `searchContent(content, options)` | `search` | `grep.rs` |\n| `hasMatch(content, pattern, options?)` | `hasMatch` | `grep.rs` |\n| `fuzzyFind(options)` | `fuzzyFind` | `fd.rs` |\n| `glob(options, onMatch?)` | `glob` | `glob.rs` |\n| `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `fs_cache.rs` |\n| `wrapTextWithAnsi(text, width)` | `wrapTextWithAnsi` | `text.rs` |\n| `truncateToWidth(text, maxWidth, ellipsis, pad)` | `truncateToWidth` | `text.rs` |\n| `sliceWithWidth(line, startCol, length, strict?)` | `sliceWithWidth` | `text.rs` |\n| `extractSegments(line, beforeEnd, afterStart, afterLen, strictAfter)` | `extractSegments` | `text.rs` |\n| `sanitizeText(text)` | `sanitizeText` | `text.rs` |\n| `visibleWidth(text)` | `visibleWidth` | `text.rs` |\n| `highlightCode(code, lang, colors)` | `highlightCode` | `highlight.rs` |\n| `supportsLanguage(lang)` | `supportsLanguage` | `highlight.rs` |\n| `getSupportedLanguages()` | `getSupportedLanguages` | `highlight.rs` |\n\n## 하위 시스템별 파이프라인 개요\n\n## 1) 정규식 검색 (`grep`, `searchContent`, `hasMatch`)\n\n### 입력/옵션 흐름\n\n1. TS 래퍼가 옵션을 네이티브로 전달합니다:\n   - `grep/index.ts`는 `options`를 대부분 그대로 전달하고 콜백을 `(match) => void`에서 napi threadsafe 콜백 형태 `(err, match)`로 래핑합니다.\n   - `searchContent`와 `hasMatch`는 문자열/`Uint8Array`를 직접 전달합니다.\n2. `grep.rs`의 Rust 옵션 구조체는 camelCase 필드(`ignoreCase`, `maxCount`, `contextBefore`, `contextAfter`, `maxColumns`, `timeoutMs`)를 역직렬화합니다.\n3. `grep`은 `timeoutMs` + `AbortSignal`로부터 `CancelToken`을 생성하고 `task::blocking(\"grep\", ...)` 내부에서 실행합니다.\n\n### 실행 분기\n\n- **인메모리 분기 (순수 유틸리티)**\n  - `search` → `search_sync` → 제공된 콘텐츠 바이트에 대해 `run_search` 실행.\n  - 파일시스템 스캔 없음, `fs_cache` 없음.\n- **단일 파일 분기 (파일시스템 의존)**\n  - `grep_sync`가 경로를 해석하고, 메타데이터가 파일인지 확인한 후, ripgrep 매처를 통해 파일당 최대 `MAX_FILE_BYTES`(`4 MiB`)까지 스트리밍합니다.\n- **디렉터리 분기 (파일시스템 의존)**\n  - `cache: true`일 때 `fs_cache::get_or_scan`을 통한 선택적 캐시 조회.\n  - `cache: false`일 때 `fs_cache::force_rescan`을 통한 새로운 스캔.\n  - 캐시 수명이 `empty_recheck_ms()`를 초과할 때 빈 결과에 대한 선택적 재검사.\n  - 항목 필터링: 파일만 + 선택적 glob 필터(`glob_util`) + 선택적 타입 필터 매핑(`js`, `ts`, `rust` 등).\n\n### 검색/수집 의미론\n\n- 정규식 엔진: `ignoreCase`와 `multiline`을 사용하는 `grep_regex::RegexMatcherBuilder`.\n- 컨텍스트 해석:\n  - `contextBefore/contextAfter`가 레거시 `context`를 오버라이드합니다.\n  - 비콘텐츠 모드에서는 컨텍스트 수집을 0으로 설정합니다.\n- 출력 모드:\n  - `content` => 히트당 하나의 `GrepMatch`.\n  - `count`와 `filesWithMatches` 모두 카운트 스타일 항목으로 매핑(`lineNumber=0`, `line=\"\"`, `matchCount` 설정).\n- 제한:\n  - 전역 `offset`과 `maxCount`가 파일 전체에 적용됩니다.\n  - 병렬 경로는 `maxCount`가 설정되지 않고 `offset == 0`일 때만 사용됩니다; 그렇지 않으면 순차 경로가 결정론적인 전역 오프셋/제한 의미론을 유지합니다.\n\n### JS로의 결과 변환\n\n- Rust `SearchResult`/`GrepResult` 필드는 N-API 객체 필드 변환을 통해 TS 타입으로 매핑됩니다.\n- 카운터는 N-API 경계를 넘기 전에 `u32`로 클램핑됩니다.\n- 선택적 불리언은 일부 경로에서 true가 아닌 한 생략됩니다(`limitReached`).\n- 스트리밍 콜백은 각각 변환된 `GrepMatch`(콘텐츠 또는 카운트 항목)를 수신합니다.\n\n### 실패 동작\n\n- `searchContent`는 정규식/검색 실패 시 예외를 던지는 대신 `SearchResult.error`를 반환합니다.\n- `grep`은 하드 에러(유효하지 않은 경로, 유효하지 않은 glob/정규식, 취소 타임아웃/중단) 시 거부합니다.\n- `hasMatch`는 `Result<bool>`을 반환하며 유효하지 않은 패턴/UTF-8 디코딩 오류 시 예외를 던집니다.\n- 다중 파일 스캔에서의 파일 열기/검색 오류는 파일별로 건너뛰며; 스캔은 계속됩니다.\n\n### 잘못된 정규식 처리\n\n`grep.rs`는 정규식 컴파일 전에 중괄호를 정제합니다:\n\n- 유효하지 않은 반복 형태의 중괄호는 `{N}`, `{N,}`, `{N,M}`을 형성할 수 없을 때 이스케이프됩니다(`{`/`}` -> `\\{`/`\\}`).\n- 이는 일반적인 리터럴 템플릿 조각(예: `${platform}`)이 잘못된 반복으로 실패하는 것을 방지합니다.\n- 나머지 유효하지 않은 정규식 구문은 여전히 정규식 오류를 반환합니다.\n\n## 2) 파일 디스커버리 (`glob`) 및 퍼지 경로 검색 (`fuzzyFind`)\n\n`glob`과 `fuzzyFind`는 `fs_cache` 스캔을 공유하며; 매칭 로직이 다릅니다.\n\n### `glob` 흐름\n\n1. TS 래퍼 (`glob/index.ts`):\n   - `path.resolve(options.path)`.\n   - 기본값: `pattern=\"*\"`, `hidden=false`, `gitignore=true`, `recursive=true`.\n2. Rust `glob`이 `GlobConfig`를 구성하고 `glob_util::compile_glob`을 통해 패턴을 컴파일합니다.\n3. 항목 소스:\n   - `cache=true` => `get_or_scan` + 선택적 오래된 빈 결과 `force_rescan`.\n   - `cache=false` => `force_rescan(..., store=false)` (새로운 스캔만).\n4. 필터링:\n   - `.git`은 항상 건너뜁니다.\n   - `node_modules`는 요청하지 않는 한 건너뜁니다(`includeNodeModules` 또는 node_modules를 언급하는 패턴).\n   - glob 매칭 적용.\n   - 파일 타입 필터 적용; 심링크 `file/dir` 필터는 대상 메타데이터를 해석합니다.\n5. `maxResults`로 잘라내기 전에 선택적으로 mtime 내림차순 정렬(`sortByMtime`).\n\n### `fuzzyFind` 흐름 (`fd.rs`에 구현)\n\n1. TS 래퍼는 `grep` 모듈에서 내보내지지만, Rust 구현은 `fd.rs`에 있습니다.\n2. 동일한 캐시/비캐시 분할 및 오래된 빈 결과 재검사 정책을 가진 `fs_cache`의 공유 스캔 소스.\n3. 스코어링:\n   - 정확 일치 / 시작 일치 / 포함 / 부분 시퀀스 기반 퍼지 점수\n   - 구분자/구두점 정규화된 스코어링 경로\n   - 디렉터리 보너스 및 결정론적 동점 처리(`score desc`, 그 다음 `path asc`)\n4. 심링크 항목은 퍼지 결과에서 제외됩니다.\n\n### 실패 동작\n\n- 유효하지 않은 glob 패턴 => `glob_util::compile_glob`에서 오류.\n- 검색 루트는 기존 디렉터리여야 합니다(`resolve_search_path`), 그렇지 않으면 오류.\n- 취소/타임아웃은 루프 내 `CancelToken::heartbeat()` 검사를 통해 중단 오류로 전파됩니다.\n\n### 잘못된 glob 처리\n\n`glob_util::build_glob_pattern`은 관대합니다:\n\n- `\\`를 `/`로 정규화합니다.\n- `recursive=true`일 때 단순 재귀 패턴에 `**/`를 자동 접두사로 추가합니다.\n- 컴파일 전에 불균형한 `{...` 교대 그룹을 자동으로 닫습니다.\n\n## 3) 공유 스캔/캐시 수명주기 (`fs_cache`)\n\n`fs_cache`는 스캔 결과를 정규화된 상대 항목(`path`, `fileType`, 선택적 `mtime`)으로 저장하며 다음을 키로 사용합니다:\n\n- 정규화된 검색 루트\n- `include_hidden`\n- `use_gitignore`\n\n### 캐시 상태 전이\n\n1. **미스 / 비활성화**\n   - TTL이 `0`이거나 키가 없거나/만료됨 -> 새로운 `collect_entries`.\n2. **히트**\n   - 항목 수명 `< cache_ttl_ms()` -> 캐시된 항목 + `cache_age_ms` 반환.\n3. **오래된 빈 결과 재검사** (`glob`/`grep`/`fd`의 호출자 정책)\n   - 쿼리가 0개의 매치를 반환하고 `cache_age_ms >= empty_recheck_ms()`이면, 한 번의 재스캔을 강제합니다.\n4. **무효화**\n   - `invalidateFsScanCache(path?)`:\n     - 인수 없음: 모든 키 삭제\n     - path 인수: 해당 대상 경로를 접두사로 하는 루트의 키 제거\n\n### 오래된 결과 트레이드오프\n\n- 캐시는 즉각적인 일관성보다 반복 스캔의 낮은 지연 시간을 우선합니다.\n- TTL 윈도우는 오래된 긍정/부정 결과를 반환할 수 있습니다.\n- 빈 결과 재검사는 한 번의 추가 스캔 비용으로 오래된 캐시 스캔의 오래된 부정 결과를 줄입니다.\n- 명시적 무효화는 파일 변경 후 의도된 정확성 후크입니다.\n\n## 4) ANSI 텍스트 유틸리티 (`text`)\n\n이들은 순수 인메모리 유틸리티입니다(파일시스템 스캔 없음).\n\n### 경계와 책임\n\n- **`text.rs`는 터미널 셀 의미론을 담당합니다**:\n  - ANSI 시퀀스 파싱\n  - 자소(grapheme) 인식 너비 및 슬라이싱\n  - 줄바꿈/잘라내기/정제 동작\n- **`grep.rs`의 라인 잘라내기(`maxColumns`)는 별개입니다**:\n  - `...`을 사용한 매칭된 라인의 단순 문자 경계 잘라내기\n  - ANSI 상태를 보존하지 않으며 터미널 셀 너비를 인식하지 않음\n\n### 주요 동작\n\n- `wrapTextWithAnsi`: 보이는 너비로 줄바꿈하며, 활성 SGR 코드를 줄바꿈된 라인에 걸쳐 전달합니다.\n- `truncateToWidth`: 줄임표 정책(`Unicode`, `Ascii`, `Omit`)에 따른 보이는 셀 잘라내기, 선택적 오른쪽 패딩, 그리고 변경되지 않을 때 원본 JS 문자열을 반환하는 빠른 경로.\n- `sliceWithWidth`: 선택적 엄격 너비 적용을 사용한 열 슬라이싱.\n- `extractSegments`: 오버레이 주변의 전/후 세그먼트를 추출하며 `after` 세그먼트에 대한 ANSI 상태를 복원합니다.\n- `sanitizeText`: ANSI 이스케이프 + 제어 문자를 제거하고, 고립된 서로게이트를 삭제하며, `\\r`을 제거하여 CR/LF를 정규화합니다.\n- `visibleWidth`: 보이는 터미널 셀을 셉니다(탭은 Rust 구현의 고정 `TAB_WIDTH`를 사용합니다).\n\n### 실패 동작\n\n텍스트 함수는 일반적으로 결정론적으로 변환된 출력을 반환합니다; 오류는 JS 문자열 변환 경계(N-API 인수 변환 실패)에 한정됩니다.\n\n## 5) 구문 강조 (`highlight`)\n\n`highlight.rs`는 순수 변환입니다(FS 없음, 캐시 없음).\n\n### 흐름\n\n1. 래퍼가 `code`, 선택적 `lang`, 그리고 ANSI 색상 팔레트를 전달합니다.\n2. Rust가 다음을 통해 구문을 해석합니다:\n   - 토큰/이름 조회\n   - 확장자 조회\n   - 별칭 테이블 폴백(`ts/tsx/js -> JavaScript` 등)\n   - 해석되지 않을 때 일반 텍스트 구문으로 폴백\n3. syntect `ParseState`와 스코프 스택으로 각 라인을 파싱합니다.\n4. 스코프를 11개의 의미적 색상 카테고리에 매핑하고 ANSI 색상 코드를 삽입/재설정합니다.\n\n### 실패 동작\n\n- 라인별 파싱 실패는 호출을 실패시키지 않습니다: 해당 라인은 강조 없이 추가되며 처리가 계속됩니다.\n- 알 수 없는/지원되지 않는 언어는 일반 텍스트 구문으로 폴백합니다.\n\n## 순수 유틸리티 vs 파일시스템 의존 흐름\n\n| 흐름 | 파일시스템 접근 | 공유 캐시 | 비고 |\n| --- | --- | --- | --- |\n| `searchContent` / `hasMatch` | 아니오 | 아니오 | 제공된 바이트/문자열에 대한 정규식만 |\n| `text` 모듈 함수 | 아니오 | 아니오 | ANSI/너비/정제만 |\n| `highlight` 모듈 함수 | 아니오 | 아니오 | 구문 + ANSI 색상 처리만 |\n| `glob` | 예 | 선택적 | 디렉터리 스캔 + glob 필터링 |\n| `fuzzyFind` | 예 | 선택적 | 디렉터리 스캔 + 퍼지 스코어링 |\n| `grep` (파일/디렉터리 경로) | 예 | 선택적 (디렉터리 모드) | 파일에 대한 ripgrep, 선택적 필터/콜백 |\n\n## 전체 수명주기 요약\n\n1. 호출자가 타입이 지정된 옵션으로 TS 래퍼를 호출합니다.\n2. 래퍼가 기본값을 정규화하고(특히 `glob`) `native.*` 내보내기로 전달합니다.\n3. Rust가 옵션을 검증/정규화하고 매처/검색 설정을 구성합니다.\n4. 파일시스템 흐름의 경우, 항목이 스캔(캐시 히트/미스/재스캔)된 후 필터링/스코어링됩니다.\n5. 워커 루프가 주기적으로 취소 하트비트를 호출합니다; 타임아웃/중단이 실행을 종료할 수 있습니다.\n6. Rust가 출력을 N-API 객체(`lineNumber`, `matchCount`, `limitReached` 등)로 변환합니다.\n7. TS 래퍼가 타입이 지정된 JS 객체를 반환합니다(그리고 `grep`/`glob`에 대한 선택적 매치별 콜백).\n",
	"ko/natives/porting-to-natives.md": "---\ntitle: pi-natives(N-API)로 포팅하기 — 현장 노트\ndescription: Node.js child_process 및 셸 코드를 Rust N-API 네이티브 레이어로 마이그레이션하기 위한 현장 노트.\nsidebar:\n  order: 9\n  label: pi-natives로 포팅하기\ni18n:\n  sourceHash: 4f5150286535\n  translator: machine\n---\n\n# pi-natives(N-API)로 포팅하기 — 현장 노트\n\n이 문서는 핫 패스를 `crates/pi-natives`로 옮기고 JS 바인딩을 통해 연결하기 위한 실용적인 가이드입니다. 동일한 실패가 반복되지 않도록 하기 위해 작성되었습니다.\n\n## 포팅 시점\n\n다음 중 하나라도 해당되면 포팅하십시오:\n\n- 핫 패스가 렌더 루프, 빈번한 UI 업데이트 또는 대규모 배치에서 실행됩니다.\n- JS 할당이 지배적입니다 (문자열 처리, 정규식 백트래킹, 대형 배열).\n- 이미 JS 기준선이 있고 두 버전을 나란히 벤치마크할 수 있습니다.\n- 작업이 CPU 바운드이거나 libuv 스레드 풀에서 실행할 수 있는 블로킹 I/O입니다.\n- 작업이 Tokio 런타임에서 실행할 수 있는 비동기 I/O입니다 (예: 셸 실행).\n\nJS 전용 상태나 동적 임포트에 의존하는 포팅은 피하십시오. N-API 내보내기는 순수한 데이터 입출력이어야 합니다. 장시간 실행되는 작업은 `task::blocking`(CPU 바운드/블로킹 I/O) 또는 `task::future`(비동기 I/O)를 통해 취소 기능과 함께 처리해야 합니다.\n\n## 네이티브 내보내기의 구조\n\n**Rust 측:**\n\n- 구현은 `crates/pi-natives/src/<module>.rs`에 위치합니다. 새 모듈을 추가하는 경우 `crates/pi-natives/src/lib.rs`에 등록하십시오.\n- `#[napi]`로 내보내기합니다; snake_case 내보내기는 자동으로 camelCase로 변환됩니다. 실제 별칭/비기본 이름에만 명시적 `js_name`을 사용하십시오. 구조체에는 `#[napi(object)]`를 사용하십시오.\n- CPU 바운드 또는 블로킹 작업에는 `task::blocking(tag, cancel_token, work)`(`crates/pi-natives/src/task.rs` 참조)를 사용하십시오. Tokio가 필요한 비동기 작업(예: 셸 세션)에는 `task::future(env, tag, work)`를 사용하십시오. `timeoutMs` 또는 `AbortSignal`을 노출할 때 `CancelToken`을 전달하십시오.\n\n**JS 측:**\n\n- `packages/natives/src/bindings.ts`에 기본 `NativeBindings` 인터페이스가 있습니다.\n- `packages/natives/src/<module>/types.ts`에서 TS 타입을 정의하고 선언 병합을 통해 `NativeBindings`를 확장합니다.\n- `packages/natives/src/native.ts`에서 각 `<module>/types.ts` 파일을 임포트하여 선언을 활성화합니다.\n- `packages/natives/src/<module>/index.ts`에서 `packages/natives/src/native.ts`의 `native` 바인딩을 래핑합니다.\n- `packages/natives/src/native.ts`에서 애드온을 로드하고 `validateNative`가 필수 내보내기를 검증합니다.\n- `packages/natives/src/index.ts`에서 `packages/*`의 호출자를 위해 래퍼를 재내보내기합니다.\n\n## 포팅 체크리스트\n\n1. **Rust 구현 추가**\n\n- 핵심 로직을 일반 Rust 함수에 작성합니다.\n- 새 모듈인 경우 `crates/pi-natives/src/lib.rs`에 추가합니다.\n- `#[napi]`로 노출하여 기본 snake_case -> camelCase 매핑이 일관되게 유지되도록 합니다.\n- 시그니처는 소유형이고 단순하게 유지합니다: `String`, `Vec<String>`, `Uint8Array`, 또는 큰 문자열/바이트 입력을 위한 `Either<JsString, Uint8Array>`.\n- CPU 바운드 또는 블로킹 작업에는 `task::blocking`을, 비동기 작업에는 `task::future`를 사용합니다. `CancelToken`을 전달하고 긴 루프 내에서 `heartbeat()`를 호출합니다.\n\n2. **JS 바인딩 연결**\n\n- `packages/natives/src/<module>/types.ts`에 타입과 `NativeBindings` 확장을 추가합니다.\n- `packages/natives/src/native.ts`에서 `./<module>/types`를 임포트하여 선언 병합을 트리거합니다.\n- `packages/natives/src/<module>/index.ts`에 `native`를 호출하는 래퍼를 추가합니다.\n- `packages/natives/src/index.ts`에서 재내보내기합니다.\n\n3. **네이티브 검증 업데이트**\n\n- `validateNative`(`packages/natives/src/native.ts`)에 `checkFn(\"newExport\")`를 추가합니다.\n\n4. **벤치마크 추가**\n\n- 벤치마크는 소유 패키지 옆에 배치합니다 (`packages/tui/bench`, `packages/natives/bench`, 또는 `packages/coding-agent/bench`).\n- 동일한 실행에서 JS 기준선과 네이티브 버전을 모두 포함합니다.\n- `Bun.nanoseconds()`와 고정된 반복 횟수를 사용합니다.\n- 벤치마크 입력은 작고 현실적으로 유지합니다 (핫 패스에서 실제로 관찰된 데이터).\n\n5. **네이티브 바이너리 빌드**\n\n- `bun --cwd=packages/natives run build`\n- `bun --cwd=packages/natives run build`를 사용하고 테스트 중 로더 진단을 원하면 `PI_DEV=1`을 설정합니다.\n\n6. **벤치마크 실행**\n\n- `bun run packages/<pkg>/bench/<bench>.ts` (또는 `bun --cwd=packages/natives run bench`)\n\n7. **사용 여부 결정**\n\n- 네이티브가 더 느리면, **JS를 유지**하고 네이티브 내보내기는 사용하지 않은 채로 둡니다.\n- 네이티브가 더 빠르면, 호출 지점을 네이티브 래퍼로 전환합니다.\n\n## 문제점과 해결 방법\n\n### 1) 오래된 `pi_natives.node`가 새 내보내기를 차단함\n\n로더는 `packages/natives/native`에 있는 플랫폼 태그 바이너리(`pi_natives.<platform>-<arch>.node`)를 우선합니다. `PI_DEV=1`은 이제 로더 진단만 활성화하며, 더 이상 별도의 dev 애드온 파일명으로 전환하지 않습니다. `pi_natives.node` 폴백도 있습니다. 컴파일된 바이너리는 `~/.xcsh/natives/<version>/pi_natives.<platform>-<arch>.node`로 추출됩니다. 이 중 하나라도 오래된 경우 내보내기가 업데이트되지 않습니다.\n\n**해결:** 재빌드 전에 오래된 파일을 제거합니다.\n\n```bash\nrm packages/natives/native/pi_natives.linux-x64.node\nrm packages/natives/native/pi_natives.node\nbun --cwd=packages/natives run build\n```\n\n컴파일된 바이너리를 실행 중인 경우, 캐시된 애드온 디렉토리를 삭제합니다:\n\n```bash\nrm -rf ~/.xcsh/natives/<version>\n```\n\n그런 다음 바이너리에 내보내기가 존재하는지 확인합니다:\n\n```bash\nbun -e 'const tag = `${process.platform}-${process.arch}`; const mod = require(`./packages/natives/native/pi_natives.${tag}.node`); console.log(Object.keys(mod).includes(\"newExport\"));'\n```\n\n### 2) `validateNative`의 \"Missing exports\" 오류\n\n이것은 **정상입니다** — 자동 불일치를 방지합니다. 다음과 같은 메시지가 표시되면:\n\n```\nNative addon missing exports ... Missing: visibleWidth\n```\n\n이는 바이너리가 오래되었거나, Rust 내보내기 이름(또는 사용 시 명시적 별칭)이 JS 이름과 일치하지 않거나, 내보내기가 컴파일되지 않았음을 의미합니다. 빌드와 이름 불일치를 수정하고, 검증을 약화시키지 마십시오.\n\n### 3) Rust 시그니처 불일치\n\n단순하고 소유형으로 유지하십시오. `String`, `Vec<String>`, `Uint8Array`가 작동합니다. 공개 내보내기에서 `&str`과 같은 참조는 피하십시오. 구조화된 데이터가 필요한 경우 `#[napi(object)]` 구조체로 래핑하십시오.\n\n### 4) 벤치마킹 실수\n\n- 서로 다른 입력이나 할당을 비교하지 마십시오.\n- JS와 네이티브가 동일한 입력 배열을 사용하도록 하십시오.\n- 편차를 방지하기 위해 동일한 벤치마크 파일에서 둘 다 실행하십시오.\n\n## 벤치마크 템플릿\n\n```ts\nconst ITERATIONS = 2000;\n\nfunction bench(name: string, fn: () => void): number {\n const start = Bun.nanoseconds();\n for (let i = 0; i < ITERATIONS; i++) fn();\n const elapsed = (Bun.nanoseconds() - start) / 1e6;\n console.log(`${name}: ${elapsed.toFixed(2)}ms total (${(elapsed / ITERATIONS).toFixed(6)}ms/op)`);\n return elapsed;\n}\n\nbench(\"feature/js\", () => {\n jsImpl(sample);\n});\n\nbench(\"feature/native\", () => {\n nativeImpl(sample);\n});\n```\n\n## 검증 체크리스트\n\n- `validateNative`가 통과합니다 (누락된 내보내기 없음).\n- `NativeBindings`가 `packages/natives/src/<module>/types.ts`에서 확장되고 래퍼가 `packages/natives/src/index.ts`에서 재내보내기됩니다.\n- `Object.keys(require(...))`에 새 내보내기가 포함됩니다.\n- 벤치마크 수치가 PR/노트에 기록됩니다.\n- 호출 지점은 네이티브가 더 빠르거나 동등한 **경우에만** 업데이트됩니다.\n\n## 경험 법칙\n\n- 네이티브가 더 느리면, **전환하지 마십시오**. 향후 작업을 위해 내보내기는 유지하되, TUI는 더 빠른 경로를 유지해야 합니다.\n- 네이티브가 더 빠르면, 호출 지점을 전환하고 회귀를 감지하기 위해 벤치마크를 유지합니다.\n",
	"ko/providers/models.md": "---\ntitle: 모델 및 프로바이더 구성\ndescription: '라우팅, 폴백 및 가격 책정을 포함한 models.yml을 통한 모델 레지스트리 및 프로바이더 구성.'\nsidebar:\n  order: 1\n  label: 모델 및 프로바이더\ni18n:\n  sourceHash: 8053df967ff6\n  translator: machine\n---\n\n# 모델 및 프로바이더 구성 (`models.yml`)\n\n이 문서는 코딩 에이전트가 현재 모델을 로드하고, 재정의를 적용하며, 자격 증명을 확인하고, 런타임에 모델을 선택하는 방법을 설명합니다.\n\n## 모델 동작을 제어하는 요소\n\n주요 구현 파일:\n\n- `src/config/model-registry.ts` — 내장 + 커스텀 모델, 프로바이더 재정의, 런타임 검색, 인증 통합 로드\n- `src/config/model-resolver.ts` — 모델 패턴 파싱 및 초기/smol/slow 모델 선택\n- `src/config/settings-schema.ts` — 모델 관련 설정 (`modelRoles`, 프로바이더 전송 기본값)\n- `src/session/auth-storage.ts` — API 키 + OAuth 확인 순서\n- `packages/ai/src/models.ts` 및 `packages/ai/src/types.ts` — 내장 프로바이더/모델 및 `Model`/`compat` 타입\n\n## 구성 파일 위치 및 레거시 동작\n\n기본 구성 경로:\n\n- `~/.xcsh/agent/models.yml`\n\n여전히 존재하는 레거시 동작:\n\n- `models.yml`이 없고 동일한 위치에 `models.json`이 존재하는 경우, `models.yml`로 마이그레이션됩니다.\n- 명시적인 `.json` / `.jsonc` 구성 경로는 `ModelRegistry`에 프로그래밍 방식으로 전달될 때 여전히 지원됩니다.\n\n## `models.yml` 구조\n\n```yaml\nconfigVersion: 1  # 선택 사항 — 자동 구성에 의해 작성되며 마이그레이션 감지에 사용됨\nproviders:\n  <provider-id>:\n    # 프로바이더 수준 구성\nequivalence:\n  overrides:\n    <provider-id>/<model-id>: <canonical-model-id>\n  exclude:\n    - <provider-id>/<model-id>\n```\n\n`configVersion`은 자동 구성 시스템에 의해 작성되는 선택적 정수입니다. 존재하는 경우 xcsh는 이를 사용하여 오래된 구성을 감지하고 자동으로 업그레이드합니다.\n\n`provider-id`는 선택 및 인증 조회 전반에 걸쳐 사용되는 정규 프로바이더 키입니다.\n\n`equivalence`는 선택 사항이며 구체적인 프로바이더 모델 위에 정규 모델 그룹화를 구성합니다:\n\n- `overrides`는 정확한 구체적 선택자(`provider/modelId`)를 공식 업스트림 정규 ID에 매핑합니다.\n- `exclude`는 구체적 선택자를 정규 그룹화에서 제외합니다.\n\n## 프로바이더 수준 필드\n\n```yaml\nproviders:\n  my-provider:\n    baseUrl: https://api.example.com/v1\n    apiKey: MY_PROVIDER_API_KEY\n    api: openai-completions\n    headers:\n      X-Team: platform\n    authHeader: true\n    auth: apiKey\n    discovery:\n      type: ollama\n    modelOverrides:\n      some-model-id:\n        name: Renamed model\n    models:\n      - id: some-model-id\n        name: Some Model\n        api: openai-completions\n        reasoning: false\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 128000\n        maxTokens: 16384\n        headers:\n          X-Model: value\n        compat:\n          supportsStore: true\n          supportsDeveloperRole: true\n          supportsReasoningEffort: true\n          maxTokensField: max_completion_tokens\n          openRouterRouting:\n            only: [anthropic]\n          vercelGatewayRouting:\n            order: [anthropic, openai]\n          extraBody:\n            gateway: m1-01\n            controller: mlx\n```\n\n### 허용되는 프로바이더/모델 `api` 값\n\n- `openai-completions`\n- `openai-responses`\n- `openai-codex-responses`\n- `azure-openai-responses`\n- `anthropic-messages`\n- `google-generative-ai`\n- `google-vertex`\n\n### 허용되는 auth/discovery 값\n\n- `auth`: `apiKey` (기본값) 또는 `none`\n- `discovery.type`: `ollama`\n\n## 유효성 검사 규칙 (현재)\n\n### 완전한 커스텀 프로바이더 (`models`가 비어 있지 않은 경우)\n\n필수 항목:\n\n- `baseUrl`\n- `auth: none`이 아닌 경우 `apiKey`\n- 프로바이더 수준 또는 각 모델의 `api`\n\n### 재정의 전용 프로바이더 (`models`가 없거나 비어 있는 경우)\n\n다음 중 하나 이상을 정의해야 합니다:\n\n- `baseUrl`\n- `modelOverrides`\n- `discovery`\n\n### 검색\n\n- `discovery`는 프로바이더 수준 `api`를 필요로 합니다.\n\n### 모델 값 검사\n\n- `id` 필수\n- 제공된 경우 `contextWindow` 및 `maxTokens`는 양수여야 합니다.\n\n## 병합 및 재정의 순서\n\nModelRegistry 파이프라인 (새로 고침 시):\n\n1. `@f5-sales-demo/pi-ai`에서 내장 프로바이더/모델 로드.\n2. `models.yml` 커스텀 구성 로드.\n3. 내장 모델에 프로바이더 재정의 적용 (`baseUrl`, `headers`).\n4. `modelOverrides` 적용 (프로바이더 + 모델 ID 기준).\n5. 커스텀 `models` 병합:\n   - 동일한 `provider + id`는 기존 항목을 대체\n   - 그렇지 않으면 추가\n6. 런타임 검색된 모델 적용 (현재 Ollama 및 LM Studio), 이후 모델 재정의 재적용.\n\n## 정규 모델 동등성 및 통합\n\n레지스트리는 모든 구체적인 프로바이더 모델을 유지하고 그 위에 정규 계층을 구성합니다.\n\n정규 ID는 공식 업스트림 ID만 사용합니다. 예를 들어:\n\n- `claude-opus-4-6`\n- `claude-haiku-4-5`\n- `gpt-5.3-codex`\n\n### `models.yml` 동등성 구성\n\n예시:\n\n```yaml\nproviders:\n  zenmux:\n    baseUrl: https://api.zenmux.example/v1\n    apiKey: ZENMUX_API_KEY\n    api: openai-codex-responses\n    models:\n      - id: codex\n        name: Zenmux Codex\n        reasoning: true\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 200000\n        maxTokens: 32768\n\nequivalence:\n  overrides:\n    zenmux/codex: gpt-5.3-codex\n    p-codex/codex: gpt-5.3-codex\n  exclude:\n    - demo/codex-preview\n```\n\n정규 그룹화를 위한 빌드 순서:\n\n1. `equivalence.overrides`의 정확한 사용자 재정의\n2. 내장 모델 메타데이터의 번들된 공식 ID 일치\n3. 게이트웨이/프로바이더 변형에 대한 보수적인 휴리스틱 정규화\n4. 구체적인 모델 자체 ID로 폴백\n\n현재 휴리스틱은 의도적으로 좁게 설정되어 있습니다:\n\n- 내장된 업스트림 접두사는 존재하는 경우 제거될 수 있습니다. 예: `anthropic/...` 또는 `openai/...`\n- 점 및 대시로 구분된 버전 변형은 기존 공식 ID에 매핑되는 경우에만 정규화될 수 있습니다. 예: `4.6 -> 4-6`\n- 번들된 일치 또는 명시적 재정의 없이는 모호한 계열 또는 버전이 병합되지 않습니다.\n\n### 정규 확인 동작\n\n여러 구체적인 변형이 정규 ID를 공유하는 경우, 확인은 다음을 사용합니다:\n\n1. 가용성 및 인증\n2. `config.yml` `modelProviderOrder`\n3. `modelProviderOrder`가 설정되지 않은 경우 기존 레지스트리/프로바이더 순서\n\n비활성화되거나 인증되지 않은 프로바이더는 건너뜁니다.\n\n세션 상태 및 트랜스크립트는 실제로 해당 턴을 실행한 구체적인 프로바이더/모델을 계속 기록합니다.\n\n프로바이더 기본값 대 모델별 재정의:\n\n- 프로바이더 `headers`는 기준값입니다.\n- 모델 `headers`는 프로바이더 헤더 키를 재정의합니다.\n- `modelOverrides`는 모델 메타데이터 (`name`, `reasoning`, `input`, `cost`, `contextWindow`, `maxTokens`, `headers`, `compat`, `contextPromotionTarget`)를 재정의할 수 있습니다.\n- `compat`은 중첩된 라우팅 블록 (`openRouterRouting`, `vercelGatewayRouting`, `extraBody`)에 대해 깊은 병합됩니다.\n\n## 런타임 검색 통합\n\n### 암시적 Ollama 검색\n\n`ollama`가 명시적으로 구성되지 않은 경우, 레지스트리는 암시적으로 검색 가능한 프로바이더를 추가합니다:\n\n- 프로바이더: `ollama`\n- api: `openai-completions`\n- 기본 URL: `OLLAMA_BASE_URL` 또는 `http://127.0.0.1:11434`\n- 인증 모드: 키 없음 (`auth: none` 동작)\n\n런타임 검색은 Ollama에서 `GET /api/tags`를 호출하고 로컬 기본값으로 모델 항목을 합성합니다.\n\n### 암시적 llama.cpp 검색\n\n`llama.cpp`가 명시적으로 구성되지 않은 경우, 레지스트리는 암시적으로 검색 가능한 프로바이더를 추가합니다:\n참고: openai-completions 대신 최신 anthropic messages API를 사용합니다.\n\n- 프로바이더: `llama.cpp`\n- api: `openai-responses`\n- 기본 URL: `LLAMA_CPP_BASE_URL` 또는 `http://127.0.0.1:8080`\n- 인증 모드: 키 없음 (`auth: none` 동작)\n\n런타임 검색은 llama.cpp에서 `GET models`를 호출하고 로컬 기본값으로 모델 항목을 합성합니다.\n\n### 암시적 LM Studio 검색\n\n`lm-studio`가 명시적으로 구성되지 않은 경우, 레지스트리는 암시적으로 검색 가능한 프로바이더를 추가합니다:\n\n- 프로바이더: `lm-studio`\n- api: `openai-completions`\n- 기본 URL: `LM_STUDIO_BASE_URL` 또는 `http://127.0.0.1:1234/v1`\n- 인증 모드: 키 없음 (`auth: none` 동작)\n\n런타임 검색은 모델을 가져오고 (`GET /models`) 로컬 기본값으로 모델 항목을 합성합니다.\n\n### 명시적 프로바이더 검색\n\n직접 검색을 구성할 수 있습니다:\n\n```yaml\nproviders:\n  ollama:\n    baseUrl: http://127.0.0.1:11434\n    api: openai-completions\n    auth: none\n    discovery:\n      type: ollama\n      \n  llama.cpp:\n    baseUrl: http://127.0.0.1:8080\n    api: openai-responses\n    auth: none\n    discovery:\n      type: llama.cpp\n```\n\n### 확장 프로바이더 등록\n\n확장 기능은 런타임에 프로바이더를 등록할 수 있습니다 (`pi.registerProvider(...)`). 다음을 포함합니다:\n\n- 프로바이더에 대한 모델 교체/추가\n- 새로운 API ID에 대한 커스텀 스트림 핸들러 등록\n- 커스텀 OAuth 프로바이더 등록\n\n## 인증 및 API 키 확인 순서\n\n프로바이더에 대한 키를 요청할 때의 유효 순서:\n\n1. 런타임 재정의 (CLI `--api-key`)\n2. `agent.db`에 저장된 API 키 자격 증명\n3. `agent.db`에 저장된 OAuth 자격 증명 (새로 고침 포함)\n4. 환경 변수 매핑 (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY` 등)\n5. ModelRegistry 폴백 확인자 (`models.yml`의 프로바이더 `apiKey`, env-name-or-literal 의미론)\n\n`models.yml` `apiKey` 동작:\n\n- 값은 먼저 환경 변수 이름으로 처리됩니다.\n- 환경 변수가 없으면 리터럴 문자열이 토큰으로 사용됩니다.\n\n`authHeader: true`이고 프로바이더 `apiKey`가 설정된 경우, 모델은 다음을 받습니다:\n\n- `Authorization: Bearer <resolved-key>` 헤더 주입.\n\n키 없는 프로바이더:\n\n- `auth: none`으로 표시된 프로바이더는 자격 증명 없이 사용 가능한 것으로 처리됩니다.\n- `getApiKey*`는 해당 프로바이더에 대해 `kNoAuth`를 반환합니다.\n\n## 모델 가용성 대 전체 모델\n\n- `getAll()`은 로드된 모델 레지스트리(내장 + 병합된 커스텀 + 검색된 모델)를 반환합니다.\n- `getAvailable()`은 키 없는 모델이나 확인 가능한 인증이 있는 모델로 필터링합니다.\n\n따라서 모델은 레지스트리에 존재하더라도 인증이 가능해질 때까지 선택할 수 없습니다.\n\n## 런타임 모델 확인\n\n### CLI 및 패턴 파싱\n\n`model-resolver.ts`는 다음을 지원합니다:\n\n- 정확한 `provider/modelId`\n- 정확한 정규 모델 ID\n- 정확한 모델 ID (프로바이더 추론)\n- 퍼지/부분 문자열 매칭\n- `--models`의 glob 범위 패턴 (예: `openai/*`, `*sonnet*`)\n- 선택적 `:thinkingLevel` 접미사 (`off|minimal|low|medium|high|xhigh`)\n\n`--provider`는 레거시입니다; `--model`이 권장됩니다.\n\n정확한 선택자에 대한 확인 우선순위:\n\n1. 정확한 `provider/modelId`는 통합을 건너뜁니다.\n2. 정확한 정규 ID는 정규 인덱스를 통해 확인됩니다.\n3. 정확한 bare 구체적 ID도 동작합니다.\n4. 퍼지 및 glob 매칭은 정확한 경로 이후에 실행됩니다.\n\n### 초기 모델 선택 우선순위\n\n`findInitialModel(...)`은 다음 순서를 사용합니다:\n\n1. 명시적인 CLI 프로바이더+모델\n2. 첫 번째 범위 모델 (재개하지 않는 경우)\n3. 저장된 기본 프로바이더/모델\n4. 사용 가능한 모델 중 알려진 프로바이더 기본값 (예: OpenAI/Anthropic 등)\n5. 첫 번째 사용 가능한 모델\n\n### 역할 별칭 및 설정\n\n지원되는 모델 역할:\n\n- `default`, `smol`, `slow`, `plan`, `commit`\n\n`pi/smol`과 같은 역할 별칭은 `settings.modelRoles`를 통해 확장됩니다. 각 역할 값은 `:minimal`, `:low`, `:medium`, `:high`와 같은 thinking 선택자를 추가할 수 있습니다.\n\n역할이 다른 역할을 가리키는 경우, 대상 모델은 여전히 정상적으로 상속되며 참조 역할의 명시적 접미사는 해당 역할별 사용에 우선합니다.\n\n관련 설정:\n\n- `modelRoles` (레코드)\n- `enabledModels` (범위 패턴 목록)\n- `modelProviderOrder` (전역 정규-프로바이더 우선순위)\n- `providers.kimiApiFormat` (`openai` 또는 `anthropic` 요청 형식)\n- `providers.openaiWebsockets` (OpenAI Codex 전송을 위한 `auto|off|on` 웹소켓 기본값)\n\n`modelRoles`는 다음 중 하나를 저장할 수 있습니다:\n\n- 구체적인 프로바이더 변형을 고정하기 위한 `provider/modelId`\n- 프로바이더 통합을 허용하는 `gpt-5.3-codex`와 같은 정규 ID\n\n`enabledModels` 및 CLI `--models`의 경우:\n\n- 정확한 정규 ID는 해당 정규 그룹의 모든 구체적인 변형으로 확장됩니다.\n- 명시적인 `provider/modelId` 항목은 정확하게 유지됩니다.\n- glob 및 퍼지 매칭은 여전히 구체적인 모델에서 동작합니다.\n\n## `/model` 및 `--list-models`\n\n두 인터페이스 모두 프로바이더 접두사가 붙은 모델을 표시하고 선택할 수 있도록 유지합니다.\n\n이제 정규/통합된 모델도 노출합니다:\n\n- `/model`은 프로바이더 탭과 함께 정규 뷰를 포함합니다.\n- `--list-models`는 정규 섹션과 구체적인 프로바이더 행을 출력합니다.\n\n정규 항목을 선택하면 정규 선택자가 저장됩니다. 프로바이더 행을 선택하면 명시적인 `provider/modelId`가 저장됩니다.\n\n## 컨텍스트 승격 (모델 수준 폴백 체인)\n\n컨텍스트 승격은 소규모 컨텍스트 변형 (예: `*-spark`)에 대한 오버플로 복구 메커니즘으로, API가 컨텍스트 길이 오류로 요청을 거부할 때 자동으로 더 큰 컨텍스트의 형제 모델로 승격합니다.\n\n### 트리거 및 순서\n\n컨텍스트 오버플로 오류 (예: `context_length_exceeded`)로 턴이 실패하면, `AgentSession`은 압축으로 폴백하기 **전에** 승격을 시도합니다:\n\n1. `contextPromotion.enabled`가 true인 경우 승격 대상을 확인합니다 (아래 참조).\n2. 대상이 발견되면 해당 모델로 전환하고 요청을 재시도합니다 — 압축이 필요하지 않습니다.\n3. 대상을 사용할 수 없는 경우 현재 모델에서 자동 압축으로 넘어갑니다.\n\n### 대상 선택\n\n선택은 역할 기반이 아닌 모델 기반입니다:\n\n1. `currentModel.contextPromotionTarget` (구성된 경우)\n2. 동일한 프로바이더 + API에서 가장 작은 더 큰 컨텍스트 모델\n\n자격 증명이 확인되지 않는 경우 후보는 무시됩니다 (`ModelRegistry.getApiKey(...)`).\n\n### OpenAI Codex 웹소켓 핸드오프\n\n`openai-codex-responses`로/에서 전환하는 경우, 모델 전환 전에 세션 프로바이더 상태 키 `openai-codex-responses`가 닫힙니다. 이는 웹소켓 전송 상태를 제거하여 다음 턴이 승격된 모델에서 깨끗하게 시작되도록 합니다.\n\n### 지속성 동작\n\n승격은 임시 전환을 사용합니다 (`setModelTemporary`):\n\n- 세션 기록에 임시 `model_change`로 기록됩니다.\n- 저장된 역할 매핑을 다시 쓰지 않습니다.\n\n### 명시적 폴백 체인 구성\n\n`contextPromotionTarget`을 통해 모델 메타데이터에서 직접 폴백을 구성합니다.\n\n`contextPromotionTarget`은 다음 중 하나를 허용합니다:\n\n- `provider/model-id` (명시적)\n- `model-id` (현재 프로바이더 내에서 확인)\n\nSpark -> 동일 프로바이더의 non-Spark에 대한 예시 (`models.yml`):\n\n```yaml\nproviders:\n  openai-codex:\n    modelOverrides:\n      gpt-5.3-codex-spark:\n        contextPromotionTarget: openai-codex/gpt-5.3-codex\n```\n\n내장 모델 생성기는 동일한 프로바이더의 기본 모델이 존재하는 경우 `*-spark` 모델에 대해 이를 자동으로 할당합니다.\n\n## 호환성 및 라우팅 필드\n\n`models.yml`은 다음 `compat` 서브셋을 지원합니다:\n\n- `supportsStore`\n- `supportsDeveloperRole`\n- `supportsReasoningEffort`\n- `maxTokensField` (`max_completion_tokens` 또는 `max_tokens`)\n- `openRouterRouting.only` / `openRouterRouting.order`\n- `vercelGatewayRouting.only` / `vercelGatewayRouting.order`\n\n이는 OpenAI-completions 전송 로직에 의해 소비되며 URL 기반 자동 감지와 결합됩니다.\n\n## 실용적인 예시\n\n### 로컬 OpenAI 호환 엔드포인트 (인증 없음)\n\n```yaml\nproviders:\n  local-openai:\n    baseUrl: http://127.0.0.1:8000/v1\n    auth: none\n    api: openai-completions\n    models:\n      - id: Qwen/Qwen2.5-Coder-32B-Instruct\n        name: Qwen 2.5 Coder 32B (local)\n```\n\n### 환경 변수 기반 키를 사용하는 호스팅 프록시\n\n```yaml\nproviders:\n  anthropic-proxy:\n    baseUrl: https://proxy.example.com/anthropic\n    apiKey: ANTHROPIC_PROXY_API_KEY\n    api: anthropic-messages\n    authHeader: true\n    models:\n      - id: claude-sonnet-4-20250514\n        name: Claude Sonnet 4 (Proxy)\n        reasoning: true\n        input: [text, image]\n```\n\n### 내장 프로바이더 경로 + 모델 메타데이터 재정의\n\n```yaml\nproviders:\n  openrouter:\n    baseUrl: https://my-proxy.example.com/v1\n    headers:\n      X-Team: platform\n    modelOverrides:\n      anthropic/claude-sonnet-4:\n        name: Sonnet 4 (Corp)\n        compat:\n          openRouterRouting:\n            only: [anthropic]\n```\n\n## LiteLLM 프록시 자동 구성\n\n`LITELLM_BASE_URL` 및 `LITELLM_API_KEY` 환경 변수가 모두 설정된 경우, xcsh는 LiteLLM 프록시에 대한 `models.yml` 구성을 자동으로 관리합니다.\n\n### 최초 실행 자동 생성\n\n`models.yml`이 없고 LiteLLM 환경 변수가 감지된 경우, xcsh는 자동으로 생성합니다:\n\n```yaml\n# Auto-generated by xcsh for LiteLLM proxy\n# API key resolved from LITELLM_API_KEY env var at runtime\nconfigVersion: 1\nproviders:\n  anthropic:\n    baseUrl: \"https://your-litellm-proxy.example.com/anthropic\"\n    apiKey: LITELLM_API_KEY\n```\n\n기본 `config.yml`도 합리적인 이미지 프로바이더 설정으로 생성됩니다.\n\n### 시작 시 자가 복구\n\n시작할 때마다 모델 레지스트리의 `startupHealthCheck()`는 다음 검사를 실행합니다:\n\n| 조건 | 작업 |\n|-----------|--------|\n| `models.yml` 없음 | 환경 변수에서 자동 생성 |\n| `models.yml` 손상 또는 파싱 불가 | `.bak`으로 백업, 재생성 |\n| `baseUrl`이 `LITELLM_BASE_URL`과 일치하지 않음 | `.bak`으로 백업, 새 URL로 재생성 |\n| `configVersion` 없음 또는 오래됨 | `.bak`으로 백업, 현재 버전으로 재생성 |\n| 구성이 정상 | 조치 없음 |\n\n모든 수리 작업은 덮어쓰기 전에 `.bak` 백업을 생성합니다. 모든 작업은 멱등적입니다.\n\n### CLI 명령\n\n```bash\nxcsh setup litellm              # LiteLLM 구성 생성 또는 수정\nxcsh setup litellm --check      # 쓰기 없이 유효성 검사\nxcsh setup litellm --check --json  # 기계 판독 가능한 유효성 검사 출력\n```\n\n### 필수 환경 변수\n\n| 변수 | 목적 |\n|----------|---------|\n| `LITELLM_BASE_URL` | LiteLLM 프록시 URL (예: `https://your-proxy.example.com`). `http://` 또는 `https://`로 시작해야 합니다. |\n| `LITELLM_API_KEY` | 프록시의 API 키. 생성된 구성에서 이름으로 참조되며 런타임에 확인됩니다. |\n\n두 변수 중 하나라도 설정되지 않으면 자동 구성은 자동으로 건너뜁니다.\n\n### 구성 버전 관리\n\n생성된 구성에는 `configVersion` 필드가 포함됩니다. 향후 릴리스에서 생성된 형식이 변경되면 xcsh는 오래된 구성을 감지하고 자동으로 업그레이드합니다 (백업 포함).\n\n## 레거시 소비자 주의 사항\n\n이제 대부분의 모델 구성은 `ModelRegistry`를 통해 `models.yml`로 흐릅니다.\n\n주목할 만한 레거시 경로가 하나 남아 있습니다: 웹 검색 Anthropic 인증 확인은 `src/web/search/auth.ts`에서 `~/.xcsh/agent/models.json`을 직접 읽습니다.\n\n해당 특정 경로에 의존하는 경우 해당 모듈이 마이그레이션될 때까지 JSON 호환성을 염두에 두십시오.\n\n## 실패 모드\n\n`models.yml`이 스키마 또는 유효성 검사 검사에 실패하는 경우:\n\n- `LITELLM_BASE_URL` 및 `LITELLM_API_KEY`가 설정된 경우, 시작 상태 검사는 자동 복구를 시도합니다 (손상된 파일 백업, 환경 변수에서 재생성). 복구가 성공하면 레지스트리는 수정된 구성을 다시 로드합니다.\n- 자동 복구가 불가능한 경우 (환경 변수 미설정, 쓰기 실패), 레지스트리는 내장 모델로 계속 작동합니다.\n- 오류는 `ModelRegistry.getError()`를 통해 노출되고 UI/알림에 표시됩니다.\n",
	"ko/providers/provider-streaming-internals.md": "---\ntitle: 프로바이더 스트리밍 내부 구조\ndescription: 'SSE 파싱, 토큰 카운팅, 역압력 처리를 포함한 프로바이더 스트리밍 구현.'\nsidebar:\n  order: 2\n  label: 스트리밍 내부 구조\ni18n:\n  sourceHash: a32ffa769c4d\n  translator: machine\n---\n\n# 프로바이더 스트리밍 내부 구조\n\n이 문서는 `@f5-sales-demo/pi-ai`에서 토큰/도구 스트리밍이 어떻게 정규화되는지, 그리고 `@f5-sales-demo/pi-agent-core` 및 `coding-agent` 세션 이벤트를 통해 어떻게 전파되는지 설명합니다.\n\n## 엔드-투-엔드 흐름\n\n1. `streamSimple()` (`packages/ai/src/stream.ts`)는 일반 옵션을 매핑하고 프로바이더 스트림 함수로 디스패치합니다.\n2. 프로바이더 스트림 함수(`anthropic.ts`, `openai-responses.ts`, `google.ts`)는 프로바이더 네이티브 스트림 이벤트를 통합된 `AssistantMessageEvent` 시퀀스로 변환합니다.\n3. 각 프로바이더는 이벤트를 `AssistantMessageEventStream` (`packages/ai/src/utils/event-stream.ts`)으로 푸시하며, 이는 델타 이벤트를 스로틀링하고 다음을 노출합니다:\n   - 증분 업데이트를 위한 비동기 이터레이션\n   - 최종 `AssistantMessage`를 위한 `result()`\n4. `agentLoop` (`packages/agent/src/agent-loop.ts`)는 해당 이벤트를 소비하고, 처리 중인 어시스턴트 상태를 변경하며, 원시 `assistantMessageEvent`를 담은 `message_update` 이벤트를 발행합니다.\n5. `AgentSession` (`packages/coding-agent/src/session/agent-session.ts`)은 에이전트 이벤트를 구독하고, 메시지를 지속하며, 확장 훅을 구동하고, 세션 동작(재시도, 압축, TTSR, 스트리밍 편집 중단 검사)을 적용합니다.\n\n## `@f5-sales-demo/pi-ai`의 통합 스트림 계약\n\n모든 프로바이더는 동일한 형태(`packages/ai/src/types.ts`의 `AssistantMessageEvent`)로 이벤트를 발행합니다:\n\n- `start`\n- 콘텐츠 블록 생명주기 트리플릿:\n  - 텍스트: `text_start` → `text_delta`* → `text_end`\n  - 사고: `thinking_start` → `thinking_delta`* → `thinking_end`\n  - 도구 호출: `toolcall_start` → `toolcall_delta`* → `toolcall_end`\n- 터미널 이벤트:\n  - `done` (`reason: \"stop\" | \"length\" | \"toolUse\"` 포함)\n  - 또는 `error` (`reason: \"aborted\" | \"error\"` 포함)\n\n`AssistantMessageEventStream`이 보장하는 사항:\n\n- 최종 결과는 터미널 이벤트(`done` 또는 `error`)에 의해 해결됨\n- 델타는 일괄 처리/스로틀링됨 (~50ms)\n- 버퍼링된 델타는 비-델타 이벤트 전 및 완료 전에 플러시됨\n\n## 델타 스로틀링 및 조화 동작\n\n`AssistantMessageEventStream`은 `text_delta`, `thinking_delta`, `toolcall_delta`를 병합 가능한 이벤트로 처리합니다:\n\n- 버퍼링된 델타는 **타입 + contentIndex**가 일치할 때만 병합됨\n- 병합은 최신 `partial` 스냅샷을 유지함\n- 비-델타 이벤트는 즉각적인 플러시를 강제함\n\n이는 TUI/이벤트 소비자를 위해 고빈도 프로바이더 스트림을 부드럽게 처리하지만, 프로바이더 역압력은 아닙니다. 프로바이더는 여전히 전속력으로 생산하며, 로컬 스트림이 버퍼링합니다.\n\n## 프로바이더 정규화 세부 사항\n\n## Anthropic (`anthropic-messages`)\n\n소스: `packages/ai/src/providers/anthropic.ts`\n\n정규화 포인트:\n\n- `message_start`는 사용량(입력/출력/캐시 토큰)을 초기화함\n- `content_block_start`는 텍스트/사고/도구 호출 시작으로 매핑됨\n- `content_block_delta` 매핑:\n  - `text_delta` → `text_delta`\n  - `thinking_delta` → `thinking_delta`\n  - `input_json_delta` → `toolcall_delta`\n  - `signature_delta`는 `thinkingSignature`만 업데이트함 (이벤트 없음)\n- `content_block_stop`은 대응하는 `*_end`를 발행함\n- `message_delta.stop_reason`은 `mapStopReason()`을 통해 매핑됨\n\n도구 호출 인수 스트리밍:\n\n- 각 도구 블록은 내부 `partialJson`을 보유함\n- 모든 JSON 델타는 `partialJson`에 추가됨\n- `arguments`는 `parseStreamingJson()`을 통해 각 델타마다 재파싱됨\n- `toolcall_end`는 한 번 더 재파싱한 후 `partialJson`을 제거함\n\n## OpenAI Responses (`openai-responses`)\n\n소스: `packages/ai/src/providers/openai-responses.ts`\n\n정규화 포인트:\n\n- `response.output_item.added`는 추론/텍스트/함수 호출 블록을 시작함\n- 추론 요약 이벤트(`response.reasoning_summary_text.delta`)는 `thinking_delta`가 됨\n- 출력/거부 델타는 `text_delta`가 됨\n- `response.function_call_arguments.delta`는 `toolcall_delta`가 됨\n- `response.output_item.done`은 `thinking_end` / `text_end` / `toolcall_end`를 발행함\n- `response.completed`는 상태를 중지 이유 및 사용량으로 매핑함\n\n도구 호출 인수 스트리밍:\n\n- Anthropic과 동일한 `partialJson` 누적 패턴\n- `response.function_call_arguments.done`만 전송하는 프로바이더도 최종 인수를 채움\n- 도구 호출 ID는 `\"<call_id>|<item_id>\"`로 정규화됨\n\n## Google Generative AI (`google-generative-ai`)\n\n소스: `packages/ai/src/providers/google.ts`\n\n정규화 포인트:\n\n- `candidate.content.parts`를 반복함\n- 텍스트 파트는 `isThinkingPart(part)`에 의해 사고와 텍스트로 분리됨\n- 블록 전환 시 새 블록을 시작하기 전에 이전 블록을 닫음\n- `part.functionCall`은 완전한 도구 호출로 처리됨 (start/delta/end가 즉시 발행됨)\n- 완료 이유는 `google-shared.ts`의 `mapStopReason()`에 의해 매핑됨\n\n도구 호출 인수 스트리밍:\n\n- 함수 호출 인수는 증분 JSON 텍스트가 아닌 구조화된 객체로 도착함\n- 구현은 `JSON.stringify(arguments)`를 포함하는 하나의 합성 `toolcall_delta`를 발행함\n- 이 경로에서 Google에는 부분 JSON 파서가 필요하지 않음\n\n## 부분 도구 호출 JSON 누적 및 복구\n\nAnthropic/OpenAI Responses의 공유 동작은 `parseStreamingJson()` (`packages/ai/src/utils/json-parse.ts`)을 사용합니다:\n\n1. `JSON.parse` 시도\n2. 불완전한 조각에 대해 `partial-json` 파서로 폴백\n3. 둘 다 실패하면 `{}`를 반환\n\n시사점:\n\n- 잘못된 형식이거나 잘린 인수 델타는 스트림 처리를 즉시 중단하지 않음\n- 처리 중인 `arguments`는 일시적으로 `{}`가 될 수 있음\n- 이후의 유효한 델타는 모든 추가 시마다 파싱이 재시도되므로 구조화된 인수를 복구할 수 있음\n- 최종 `toolcall_end`는 발행 전에 한 번 더 파싱을 시도함\n\n## 중지 이유 대 전송/런타임 오류\n\n프로바이더 중지 이유는 정규화된 `stopReason`으로 매핑됩니다:\n\n- Anthropic: `end_turn`→`stop`, `max_tokens`→`length`, `tool_use`→`toolUse`, 안전/거부 케이스→`error`\n- OpenAI Responses: `completed`→`stop`, `incomplete`→`length`, `failed/cancelled`→`error`\n- Google: `STOP`→`stop`, `MAX_TOKENS`→`length`, 안전/금지/잘못된 함수 호출 클래스→`error`\n\n오류 의미론은 두 단계로 분리됩니다:\n\n1. **모델 완료 의미론** (프로바이더가 보고한 완료 이유/상태)\n2. **전송/런타임 실패** (네트워크/클라이언트/파서/중단 예외)\n\n프로바이더 스트림이 오류를 발생시키거나 실패를 신호하면, 각 프로바이더 래퍼는 이를 캐치하고 다음과 함께 터미널 `error` 이벤트를 발행합니다:\n\n- 중단 신호가 설정된 경우 `stopReason = \"aborted\"`\n- 그렇지 않으면 `stopReason = \"error\"`\n- `errorMessage = formatErrorMessageWithRetryAfter(error)`\n\n## 잘못된 형식의 청크 / SSE 파싱 실패 동작\n\n이러한 프로바이더 경로에서 청크/SSE 프레이밍은 벤더 SDK 스트림(Anthropic SDK, OpenAI SDK, Google SDK)에 의해 처리됩니다. 이 코드는 여기서 커스텀 SSE 디코더를 구현하지 않습니다.\n\n현재 구현에서 관찰된 동작:\n\n- SDK 레벨에서의 잘못된 형식의 청크/SSE 파싱은 예외 또는 스트림 `error` 이벤트로 나타남\n- 프로바이더 래퍼는 이를 통합된 터미널 `error` 이벤트로 변환함\n- 스트림 함수 내부에 프로바이더 특정 재개/재시도 없음\n- 상위 레벨 재시도는 `AgentSession` 자동 재시도 로직에서 처리됨 (스트림 청크 재생이 아닌 메시지 레벨 재시도)\n\n## 취소 경계\n\n취소는 계층화되어 있습니다:\n\n- AI 프로바이더 요청: `options.signal`이 프로바이더 클라이언트 스트림 호출에 전달됨.\n- 프로바이더 래퍼: 스트림 루프 이후, 중단된 신호는 오류 경로(`\"Request was aborted\"`)를 강제함.\n- 에이전트 루프: 각 프로바이더 이벤트 처리 전에 `signal.aborted`를 확인하고 최신 부분에서 중단된 어시스턴트 메시지를 합성할 수 있음.\n- 세션/에이전트 컨트롤: `AgentSession.abort()` -> `agent.abort()` -> 공유 중단 컨트롤러 취소.\n\n도구 실행 취소는 모델 스트림 취소와 별개입니다:\n\n- 도구 러너는 `AbortSignal.any([agentSignal, steeringAbortSignal])`을 사용함\n- 스티어링 인터럽트는 이미 생성된 도구 결과를 보존하면서 나머지 도구 실행을 중단할 수 있음\n\n## 역압력 경계\n\n프로바이더 SDK 스트림과 다운스트림 소비자 사이에는 하드 역압력 메커니즘이 없습니다:\n\n- `EventStream`은 최대 크기 없이 인메모리 큐를 사용함\n- 스로틀링은 UI 업데이트 속도를 줄이지만 프로바이더 수신을 늦추지 않음\n- 소비자가 크게 지연되면 완료될 때까지 대기 중인 이벤트가 증가할 수 있음\n\n현재 설계는 제한된 버퍼 흐름 제어보다 응답성과 단순한 순서를 우선시합니다.\n\n## 스트림 이벤트가 에이전트/세션 이벤트로 표시되는 방식\n\n`agentLoop.streamAssistantResponse()`는 `AssistantMessageEvent`를 `AgentEvent`로 연결합니다:\n\n- `start` 시: 플레이스홀더 어시스턴트 메시지를 푸시하고 `message_start`를 발행함\n- 블록 이벤트(`text_*`, `thinking_*`, `toolcall_*`) 시: 마지막 어시스턴트 메시지를 업데이트하고 원시 `assistantMessageEvent`와 함께 `message_update`를 발행함\n- 터미널(`done`/`error`) 시: `response.result()`에서 최종 메시지를 해결하고 `message_end`를 발행함\n\n`AgentSession`은 이후 세션 레벨 동작을 위해 해당 이벤트를 소비합니다:\n\n- TTSR은 `text_delta` 및 `toolcall_delta`에 대해 `message_update.assistantMessageEvent`를 감시함\n- 스트리밍 편집 가드는 `edit` 호출 시 `toolcall_delta`/`toolcall_end`를 검사하고 조기에 중단할 수 있음\n- 지속성은 `message_end`에서 완료된 메시지를 씀\n- 자동 재시도는 어시스턴트 `stopReason === \"error\"` 및 `errorMessage` 휴리스틱을 검사함\n\n## 통합 대 프로바이더 특정 책임\n\n통합 (공통 계약):\n\n- 이벤트 형태 (`AssistantMessageEvent`)\n- 최종 결과 추출 (`done`/`error`)\n- 델타 스로틀링 + 병합 규칙\n- 에이전트/세션 이벤트 전파 모델\n\n프로바이더 특정 (완전히 추상화되지 않음):\n\n- 업스트림 이벤트 분류 및 매핑 로직\n- 중지 이유 변환 테이블\n- 도구 호출 ID 규칙\n- 추론/사고 블록 의미론 및 서명\n- 사용량 토큰 의미론 및 가용성 타이밍\n- API별 메시지 변환 제약 조건\n\n## 구현 파일\n\n- [`../../ai/src/stream.ts`](../../packages/ai/src/stream.ts) — 프로바이더 디스패치, 옵션 매핑, API 키/세션 배관.\n- [`../../ai/src/utils/event-stream.ts`](../../packages/ai/src/utils/event-stream.ts) — 일반 스트림 큐 + 어시스턴트 델타 스로틀링.\n- [`../../ai/src/utils/json-parse.ts`](../../packages/ai/src/utils/json-parse.ts) — 스트리밍된 도구 인수를 위한 부분 JSON 파싱.\n- [`../../ai/src/providers/anthropic.ts`](../../packages/ai/src/providers/anthropic.ts) — Anthropic 이벤트 변환 및 도구 JSON 델타 누적.\n- [`../../ai/src/providers/openai-responses.ts`](../../packages/ai/src/providers/openai-responses.ts) — OpenAI Responses 이벤트 변환 및 상태 매핑.\n- [`../../ai/src/providers/google.ts`](../../packages/ai/src/providers/google.ts) — Gemini 스트림 청크-블록 변환.\n- [`../../ai/src/providers/google-shared.ts`](../../packages/ai/src/providers/google-shared.ts) — Gemini 완료 이유 매핑 및 공유 변환 규칙.\n- [`../../agent/src/agent-loop.ts`](../../packages/agent/src/agent-loop.ts) — 프로바이더 스트림 소비 및 `message_update` 연결.\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — 스트리밍 업데이트, 중단, 재시도, 지속성의 세션 레벨 처리.\n",
	"ko/providers/python-repl.md": "---\ntitle: Python 도구 및 IPython 런타임\ndescription: 'IPython 커널 관리, 실행 및 출력 캡처를 갖춘 Python REPL 도구 런타임.'\nsidebar:\n  order: 3\n  label: Python 및 IPython\ni18n:\n  sourceHash: 70f0a034ecef\n  translator: machine\n---\n\n# Python 도구 및 IPython 런타임\n\n이 문서는 `packages/coding-agent`의 현재 Python 실행 스택을 설명합니다.\n도구 동작, 커널/게이트웨이 생명주기, 환경 처리, 실행 의미론, 출력 렌더링, 운영상의 장애 모드를 다룹니다.\n\n## 범위 및 주요 파일\n\n- 도구 인터페이스: `src/tools/python.ts`\n- 세션/호출별 커널 오케스트레이션: `src/ipy/executor.ts`\n- 커널 프로토콜 + 게이트웨이 통합: `src/ipy/kernel.ts`\n- 공유 로컬 게이트웨이 코디네이터: `src/ipy/gateway-coordinator.ts`\n- 사용자 트리거 Python 실행을 위한 인터랙티브 모드 렌더러: `src/modes/components/python-execution.ts`\n- 런타임/환경 필터링 및 Python 해석: `src/ipy/runtime.ts`\n\n## Python 도구란\n\n`python` 도구는 하나 이상의 Python 셀을 Jupyter Kernel Gateway 기반 커널을 통해 실행합니다(셀당 `python -c`를 직접 생성하는 방식이 아닙니다).\n\n도구 매개변수:\n\n```ts\n{\n  cells: Array<{ code: string; title?: string }>;\n  timeout?: number; // 초 단위, 1..600으로 제한, 기본값 30\n  cwd?: string;\n  reset?: boolean; // 첫 번째 셀 전에만 커널 초기화\n}\n```\n\n도구는 세션에 대해 `concurrency = \"exclusive\"`이므로 호출이 겹치지 않습니다.\n\n## 게이트웨이 생명주기\n\n### 모드\n\n두 가지 게이트웨이 경로가 있습니다:\n\n1. **외부 게이트웨이** (`PI_PYTHON_GATEWAY_URL` 설정 시)\n   - 구성된 URL을 직접 사용합니다.\n   - `PI_PYTHON_GATEWAY_TOKEN`을 이용한 선택적 인증.\n   - 로컬 게이트웨이 프로세스를 생성하거나 관리하지 않습니다.\n\n2. **로컬 공유 게이트웨이** (기본 경로)\n   - `~/.xcsh/agent/python-gateway` 아래에서 조율되는 단일 공유 프로세스를 사용합니다.\n   - 메타데이터 파일: `gateway.json`\n   - 잠금 파일: `gateway.lock`\n   - 생성 명령:\n     - `python -m kernel_gateway`\n     - `127.0.0.1:<할당된-포트>`에 바인딩\n     - 시작 상태 확인: `GET /api/kernelspecs`\n\n### 로컬 공유 게이트웨이 조율\n\n`acquireSharedGateway()`:\n\n- 하트비트와 함께 파일 잠금(`gateway.lock`)을 획득합니다.\n- PID가 살아 있고 상태 확인을 통과하면 `gateway.json`을 재사용합니다.\n- 필요 시 오래된 정보/PID를 정리합니다.\n- 정상 상태인 게이트웨이가 없을 때 새 게이트웨이를 시작합니다.\n\n`releaseSharedGateway()`는 현재 no-op입니다(커널 종료 시 공유 게이트웨이를 해제하지 않음).\n\n`shutdownSharedGateway()`는 공유 프로세스를 명시적으로 종료하고 게이트웨이 메타데이터를 지웁니다.\n\n### 중요한 제약사항\n\n`python.sharedGateway=false`는 커널 시작 시 거부됩니다:\n\n- 오류: `Shared Python gateway required; local gateways are disabled`\n- 프로세스별 비공유 로컬 게이트웨이 모드는 없습니다.\n\n## 커널 생명주기\n\n각 실행은 선택된 게이트웨이에서 `POST /api/kernels`를 통해 생성된 커널을 사용합니다.\n\n커널 시작 순서:\n\n1. 가용성 확인 (`checkPythonKernelAvailability`)\n2. 커널 생성 (`/api/kernels`)\n3. 웹소켓 열기 (`/api/kernels/:id/channels`)\n4. 커널 환경 초기화 (`cwd`, 환경 변수, `sys.path`)\n5. `PYTHON_PRELUDE` 실행\n6. 다음 위치에서 확장 모듈 로드:\n   - 사용자: `~/.xcsh/agent/modules/*.py`\n   - 프로젝트: `<cwd>/.xcsh/modules/*.py` (동일한 이름의 사용자 모듈을 재정의)\n\n커널 종료:\n\n- `DELETE /api/kernels/:id`를 통해 원격 커널 삭제\n- 웹소켓 닫기\n- 공유 게이트웨이 해제 훅 호출 (현재 no-op)\n\n## 세션 영속성 의미론\n\n`python.kernelMode`는 커널 재사용을 제어합니다:\n\n- `session` (기본값)\n  - 세션 ID + cwd로 키가 지정된 커널 세션을 재사용합니다.\n  - 실행은 큐를 통해 세션별로 직렬화됩니다.\n  - 유휴 세션은 5분 후 제거됩니다.\n  - 최대 4개의 세션; 초과 시 가장 오래된 세션이 제거됩니다.\n  - 하트비트 확인으로 죽은 커널을 감지합니다.\n  - 자동 재시작이 한 번 허용됩니다; 반복 충돌 시 하드 실패.\n\n- `per-call`\n  - 각 실행 요청마다 새로운 커널을 생성합니다.\n  - 요청 후 커널을 종료합니다.\n  - 호출 간 상태 영속성 없음.\n\n### 단일 도구 호출에서의 다중 셀 동작\n\n셀은 해당 도구 호출에 대해 동일한 커널 인스턴스에서 순차적으로 실행됩니다.\n\n중간 셀이 실패하면:\n\n- 이전 셀의 상태는 메모리에 남습니다.\n- 도구는 어느 셀이 실패했는지를 나타내는 오류를 반환합니다.\n- 이후 셀은 실행되지 않습니다.\n\n`reset=true`는 해당 호출의 첫 번째 셀 실행에만 적용됩니다.\n\n## 환경 필터링 및 런타임 해석\n\n게이트웨이/커널 런타임을 시작하기 전에 환경이 필터링됩니다:\n\n- 허용 목록에는 `PATH`, `HOME`, 로케일 변수, `VIRTUAL_ENV`, `PYTHONPATH` 등 핵심 변수가 포함됩니다.\n- 허용 접두사: `LC_`, `XDG_`, `PI_`\n- 거부 목록은 일반적인 API 키(OpenAI/Anthropic/Gemini 등)를 제거합니다.\n\n런타임 선택 순서:\n\n1. 활성/위치된 가상 환경 (`VIRTUAL_ENV`, 이후 `<cwd>/.venv`, `<cwd>/venv`)\n2. `~/.xcsh/python-env`의 관리형 가상 환경\n3. PATH에 있는 `python` 또는 `python3`\n\n가상 환경이 선택된 경우, 해당 bin/Scripts 경로가 `PATH` 앞에 추가됩니다.\n\nPython 내부의 커널 환경 초기화도:\n\n- `os.chdir(cwd)`\n- 제공된 환경 맵을 `os.environ`에 주입\n- cwd가 `sys.path`에 있는지 확인\n\n## 도구 가용성 및 모드 선택\n\n`python.toolMode` (기본값 `both`) + 선택적 `PI_PY` 재정의가 노출을 제어합니다:\n\n- `ipy-only`\n- `bash-only`\n- `both`\n\n`PI_PY` 허용 값:\n\n- `0` / `bash` -> `bash-only`\n- `1` / `py` -> `ipy-only`\n- `mix` / `both` -> `both`\n\nPython 사전 점검이 실패하면 해당 세션에 대해 도구 생성이 bash 전용으로 저하됩니다.\n\n## 실행 흐름 및 취소/타임아웃\n\n### 도구 수준 타임아웃\n\n`python` 도구 타임아웃은 초 단위이며, 기본값 30, `1..600`으로 제한됩니다.\n\n도구는 다음을 결합합니다:\n\n- 호출자 중단 신호\n- 타임아웃 중단 신호\n\n`AbortSignal.any(...)`로 결합됩니다.\n\n### 커널 실행 취소\n\n중단/타임아웃 시:\n\n- 실행이 취소됨으로 표시됩니다.\n- REST(`POST /interrupt`) 및 제어 채널 `interrupt_request`를 통해 커널 인터럽트가 시도됩니다.\n- 결과에 `cancelled=true`가 포함됩니다.\n- 타임아웃 경로는 출력에 `Command timed out after <n> seconds`를 주석으로 답니다.\n\n### stdin 동작\n\n인터랙티브 stdin은 지원되지 않습니다.\n\n커널이 `input_request`를 발행하면:\n\n- 도구가 `stdinRequested=true`를 기록합니다.\n- 설명 텍스트를 출력합니다.\n- 빈 `input_reply`를 전송합니다.\n- 실행은 executor 레이어에서 실패로 처리됩니다.\n\n## 출력 캡처 및 렌더링\n\n### 캡처된 출력 클래스\n\n커널 메시지에서:\n\n- `stream` -> 일반 텍스트 청크\n- `display_data`/`execute_result` -> 풍부한 디스플레이 처리\n- `error` -> 트레이스백 텍스트\n- 커스텀 MIME `application/x-xcsh-status` -> 구조화된 상태 이벤트\n\n디스플레이 MIME 우선순위:\n\n1. `text/markdown`\n2. `text/plain`\n3. `text/html` (기본 마크다운으로 변환)\n\n구조화된 출력으로 추가 캡처:\n\n- `application/json` -> JSON 트리 데이터\n- `image/png` -> 이미지 페이로드\n- `application/x-xcsh-status` -> 상태 이벤트\n\n### 저장 및 잘림\n\n출력은 `OutputSink`를 통해 스트리밍되며 아티팩트 저장소에 저장될 수 있습니다.\n\n도구 결과에는 잘림 메타데이터와 전체 출력 복구를 위한 `artifact://<id>`가 포함될 수 있습니다.\n\n### 렌더러 동작\n\n- 도구 렌더러 (`python.ts`):\n  - 셀별 상태와 함께 코드 셀 블록을 표시합니다.\n  - 축소된 미리보기는 기본적으로 10줄입니다.\n  - 전체 출력 및 더 풍부한 상태 세부 정보를 위한 확장 모드를 지원합니다.\n- 인터랙티브 렌더러 (`python-execution.ts`):\n  - TUI에서 사용자 트리거 Python 실행에 사용됩니다.\n  - 축소된 미리보기는 기본적으로 20줄입니다.\n  - 표시 안전을 위해 매우 긴 개별 줄을 4000자로 제한합니다.\n  - 취소/오류/잘림 알림을 표시합니다.\n\n## 외부 게이트웨이 지원\n\n설정:\n\n```bash\nexport PI_PYTHON_GATEWAY_URL=\"http://127.0.0.1:8888\"\n# 선택 사항:\nexport PI_PYTHON_GATEWAY_TOKEN=\"...\"\n```\n\n로컬 공유 게이트웨이와의 동작 차이:\n\n- 로컬 게이트웨이 잠금/정보 파일 없음\n- 로컬 프로세스 생성/종료 없음\n- 상태 확인 및 커널 CRUD가 외부 엔드포인트에 대해 실행됩니다.\n- 인증 실패는 명시적인 토큰 안내와 함께 표시됩니다.\n\n## 운영 문제 해결 (현재 장애 모드)\n\n- **Python 도구를 사용할 수 없음**\n  - `python.toolMode` / `PI_PY`를 확인하세요.\n  - 사전 점검이 실패하면 런타임이 bash 전용으로 폴백됩니다.\n\n- **커널 가용성 오류**\n  - 로컬 모드는 해석된 Python 런타임에서 `kernel_gateway`와 `ipykernel` 모두 가져올 수 있어야 합니다.\n  - 설치 방법:\n\n    ```bash\n    python -m pip install jupyter_kernel_gateway ipykernel\n    ```\n\n- **`python.sharedGateway=false`로 인한 시작 실패**\n  - 현재 구현에서 예상된 동작입니다.\n\n- **외부 게이트웨이 인증/연결 실패**\n  - 401/403 -> `PI_PYTHON_GATEWAY_TOKEN`을 설정하세요.\n  - 타임아웃/연결 불가 -> URL/네트워크 및 게이트웨이 상태를 확인하세요.\n\n- **실행이 멈춘 후 타임아웃**\n  - 작업이 정당한 경우 도구 `timeout`을 늘리세요(최대 600초).\n  - 중단된 코드의 경우 취소 시 커널 인터럽트가 트리거되지만 사용자 코드를 리팩터링해야 할 수 있습니다.\n\n- **Python 코드의 stdin/input 프롬프트**\n  - `input()`은 이 런타임 경로에서 인터랙티브로 지원되지 않습니다; 데이터를 프로그래밍 방식으로 전달하세요.\n\n- **리소스 고갈 (`EMFILE` / 열린 파일이 너무 많음)**\n  - 세션 관리자가 공유 게이트웨이 복구를 트리거합니다(세션 종료 + 공유 게이트웨이 재시작).\n\n- **작업 디렉터리 오류**\n  - 도구는 실행 전에 `cwd`가 존재하고 디렉터리인지 유효성을 검사합니다.\n\n## 관련 환경 변수\n\n- `PI_PY` — 도구 노출 재정의 (위의 `bash-only`/`ipy-only`/`both` 매핑)\n- `PI_PYTHON_GATEWAY_URL` — 외부 게이트웨이 사용\n- `PI_PYTHON_GATEWAY_TOKEN` — 선택적 외부 게이트웨이 인증 토큰\n- `PI_PYTHON_SKIP_CHECK=1` — Python 사전 점검/워밍 확인 우회\n- `PI_PYTHON_IPC_TRACE=1` — 커널 IPC 송수신 추적 로그\n- `PI_DEBUG_STARTUP=1` — 시작 단계 디버그 마커 출력\n",
	"ko/runtime-tools/bash-tool-runtime.md": "---\ntitle: Bash 도구 런타임\ndescription: '셸 프로세스 관리, 샌드박싱, 타임아웃, 출력 스트리밍을 지원하는 Bash 도구 런타임.'\nsidebar:\n  order: 1\n  label: Bash 도구\ni18n:\n  sourceHash: 18b12aa5dbd5\n  translator: machine\n---\n\n# Bash 도구 런타임\n\n이 문서는 에이전트 도구 호출에서 사용되는 **`bash` 도구** 런타임 경로를 설명합니다. 명령어 정규화부터 실행, 잘라내기/아티팩트, 렌더링까지 다룹니다.\n\n또한 대화형 TUI, 출력 모드, RPC 모드, 사용자 주도 뱅(`!`) 셸 실행에서 동작이 달라지는 부분도 설명합니다.\n\n## 범위와 런타임 실행 표면\n\ncoding-agent에는 두 가지 서로 다른 bash 실행 표면이 있습니다:\n\n1. **도구 호출 표면** (`toolName: \"bash\"`): 모델이 bash 도구를 호출할 때 사용됩니다.\n   - 진입점: `BashTool.execute()`.\n2. **사용자 뱅 명령어 표면** (대화형 입력에서 `!cmd` 또는 RPC `bash` 명령): 세션 수준 헬퍼 경로입니다.\n   - 진입점: `AgentSession.executeBash()`.\n\n두 경로 모두 결국 비-PTY 실행을 위해 `src/exec/bash-executor.ts`의 `executeBash()`를 사용하지만, 도구 호출 경로만 정규화/차단 및 도구 렌더러 로직을 실행합니다.\n\n## 도구 호출 엔드투엔드 파이프라인\n\n## 1) 입력 정규화 및 매개변수 병합\n\n`BashTool.execute()`는 먼저 `normalizeBashCommand()`를 통해 원시 명령어를 정규화합니다:\n\n- 후행 `| head -n N`, `| head -N`, `| tail -n N`, `| tail -N`을 구조화된 제한값으로 추출합니다,\n- 후행/선행 공백을 제거합니다,\n- 내부 공백은 유지합니다.\n\n그런 다음 추출된 제한값을 명시적 도구 인수와 병합합니다:\n\n- 명시적 `head`/`tail` 인수가 추출된 값을 재정의합니다,\n- 추출된 값은 대체값으로만 사용됩니다.\n\n### 주의사항\n\n`bash-normalize.ts` 주석에서는 `2>&1` 제거를 언급하지만, 현재 구현에서는 실제로 이를 제거하지 않습니다. 런타임 동작은 여전히 올바르지만(stdout/stderr가 이미 병합되어 있음), 정규화 동작은 주석이 시사하는 것보다 범위가 좁습니다.\n\n## 2) 선택적 차단 (차단 명령어 경로)\n\n`bashInterceptor.enabled`가 true이면, `BashTool`은 설정에서 규칙을 로드하고 정규화된 명령어에 대해 `checkBashInterception()`을 실행합니다.\n\n차단 동작:\n\n- 명령어가 차단되는 경우는 **다음 조건이 모두 충족될 때만**입니다:\n  - 정규식 규칙이 일치하고,\n  - 제안된 도구가 `ctx.toolNames`에 존재할 때.\n- 유효하지 않은 정규식 규칙은 조용히 건너뜁니다.\n- 차단 시, `BashTool`은 다음 메시지와 함께 `ToolError`를 던집니다:\n  - `Blocked: ...`\n  - 원본 명령어 포함.\n\n기본 규칙 패턴(코드에 정의됨)은 일반적인 오용을 대상으로 합니다:\n\n- 파일 읽기 도구 (`cat`, `head`, `tail`, ...)\n- 검색 도구 (`grep`, `rg`, ...)\n- 파일 찾기 도구 (`find`, `fd`, ...)\n- 인플레이스 편집기 (`sed -i`, `perl -i`, `awk -i inplace`)\n- 셸 리디렉션 쓰기 (`echo ... > file`, heredoc 리디렉션)\n\n### 주의사항\n\n`InterceptionResult`에는 `suggestedTool`이 포함되어 있지만, `BashTool`은 현재 메시지 텍스트만 표시합니다(`details`에 구조화된 제안 도구 필드 없음).\n\n## 3) CWD 유효성 검사 및 타임아웃 클램핑\n\n`cwd`는 세션 cwd(`resolveToCwd`) 기준으로 해석된 후, `stat`를 통해 유효성이 검증됩니다:\n\n- 경로가 없는 경우 -> `ToolError(\"Working directory does not exist: ...\")`\n- 디렉터리가 아닌 경우 -> `ToolError(\"Working directory is not a directory: ...\")`\n\n타임아웃은 `[1, 3600]`초로 클램핑되어 밀리초로 변환됩니다.\n\n## 4) 아티팩트 할당\n\n실행 전에 도구는 잘라낸 출력 저장을 위한 아티팩트 경로/ID를 최선 노력(best-effort)으로 할당합니다.\n\n- 아티팩트 할당 실패는 치명적이지 않습니다(아티팩트 스필 파일 없이 실행이 계속됩니다),\n- 아티팩트 ID/경로는 잘라내기 시 전체 출력 영속을 위해 실행 경로에 전달됩니다.\n\n## 5) PTY vs 비-PTY 실행 선택\n\n`BashTool`은 다음 조건이 모두 참일 때만 PTY 실행을 선택합니다:\n\n- `bash.virtualTerminal === \"on\"`\n- `PI_NO_PTY !== \"1\"`\n- 도구 컨텍스트에 UI가 있을 때 (`ctx.hasUI === true`이고 `ctx.ui`가 설정됨)\n\n그렇지 않으면 비대화형 `executeBash()`를 사용합니다.\n\n즉, 출력 모드와 비-UI RPC/도구 컨텍스트는 항상 비-PTY를 사용합니다.\n\n## 비대화형 실행 엔진 (`executeBash`)\n\n## 셸 세션 재사용 모델\n\n`executeBash()`는 다음 키로 구성된 프로세스 전역 맵에 네이티브 `Shell` 인스턴스를 캐시합니다:\n\n- 셸 경로,\n- 설정된 명령어 접두사,\n- 스냅샷 경로,\n- 직렬화된 셸 환경 변수,\n- 선택적 에이전트 세션 키.\n\n세션 수준 실행의 경우, `AgentSession.executeBash()`는 `sessionKey: this.sessionId`를 전달하여 세션별로 재사용을 격리합니다.\n\n도구 호출 경로는 `sessionKey`를 전달하지 **않으므로**, 재사용 범위는 셸 설정/스냅샷/환경 변수에 기반합니다.\n\n## 셸 설정 및 스냅샷 동작\n\n각 호출마다 실행기는 설정의 셸 구성(`shell`, `env`, 선택적 `prefix`)을 로드합니다.\n\n선택한 셸이 `bash`를 포함하는 경우, `getOrCreateSnapshot()` 시도합니다:\n\n- 스냅샷은 사용자 rc의 별칭/함수/옵션을 캡처합니다,\n- 스냅샷 생성은 최선 노력(best-effort)입니다,\n- 실패 시 스냅샷 없이 대체됩니다.\n\n`prefix`가 설정된 경우, 명령어는 다음과 같이 됩니다:\n\n```text\n<prefix> <command>\n```\n\n## 스트리밍 및 취소\n\n`Shell.run()`은 청크를 콜백으로 스트리밍합니다. 실행기는 각 청크를 `OutputSink`와 선택적 `onChunk` 콜백으로 파이프합니다.\n\n취소:\n\n- 중단 신호가 발생하면 `shellSession.abort(...)`를 트리거합니다,\n- 네이티브 결과의 타임아웃은 `cancelled: true` + 주석 텍스트로 매핑됩니다,\n- 명시적 취소도 마찬가지로 `cancelled: true` + 주석을 반환합니다.\n\n타임아웃/취소에 대해 실행기 내부에서는 예외가 던져지지 않습니다; 구조화된 `BashResult`를 반환하고 호출자가 오류 의미를 매핑하도록 합니다.\n\n## 대화형 PTY 경로 (`runInteractiveBashPty`)\n\nPTY가 활성화되면, 도구는 오버레이 콘솔 컴포넌트를 열고 네이티브 `PtySession`을 구동하는 `runInteractiveBashPty()`를 실행합니다.\n\n주요 동작:\n\n- xterm-headless 가상 터미널이 오버레이에서 뷰포트를 렌더링합니다,\n- 키보드 입력이 정규화됩니다(Kitty 시퀀스 및 애플리케이션 커서 모드 처리 포함),\n- 실행 중 `esc`를 누르면 PTY 세션이 종료됩니다,\n- 터미널 크기 변경이 PTY에 전파됩니다(`session.resize(cols, rows)`).\n\n무인 실행을 위해 환경 강화 기본값이 주입됩니다:\n\n- 페이저 비활성화 (`PAGER=cat`, `GIT_PAGER=cat` 등),\n- 편집기 프롬프트 비활성화 (`GIT_EDITOR=true`, `EDITOR=true`, ...),\n- 터미널/인증 프롬프트 축소 (`GIT_TERMINAL_PROMPT=0`, `SSH_ASKPASS=/usr/bin/false`, `CI=1`),\n- 비대화형 동작을 위한 패키지 관리자/도구 자동화 플래그.\n\nPTY 출력은 정규화(`CRLF`/`CR`을 `LF`로, `sanitizeText`)되어 아티팩트 스필 지원을 포함하여 `OutputSink`에 기록됩니다.\n\nPTY 시작/런타임 오류 시, 싱크는 `PTY error: ...` 라인을 수신하고 명령어는 정의되지 않은 종료 코드로 완료됩니다.\n\n## 출력 처리: 스트리밍, 잘라내기, 아티팩트 스필\n\nPTY와 비-PTY 경로 모두 `OutputSink`를 사용합니다.\n\n## OutputSink 의미론\n\n- 메모리 내 UTF-8 안전 테일 버퍼를 유지합니다(`DEFAULT_MAX_BYTES`, 현재 50KB),\n- 확인된 총 바이트/라인 수를 추적합니다,\n- 아티팩트 경로가 존재하고 출력이 오버플로되면(또는 파일이 이미 활성 상태이면), 전체 스트림을 아티팩트 파일에 기록합니다,\n- 메모리 임계값이 오버플로되면, 메모리 내 버퍼를 테일로 트리밍합니다(UTF-8 경계 안전),\n- 오버플로/파일 스필이 발생하면 `truncated`로 표시합니다.\n\n`dump()`는 다음을 반환합니다:\n\n- `output` (가능한 주석 접두사 포함),\n- `truncated`,\n- `totalLines/totalBytes`,\n- `outputLines/outputBytes`,\n- 아티팩트 파일이 활성인 경우 `artifactId`.\n\n### 긴 출력 주의사항\n\n런타임 잘라내기는 `OutputSink`에서 바이트 임계값 기반입니다(기본 50KB). 이 코드 경로에서는 하드 2000라인 제한을 적용하지 않습니다.\n\n## 실시간 도구 업데이트\n\n비-PTY 실행의 경우, `BashTool`은 부분 업데이트를 위한 별도의 `TailBuffer`를 사용하고 명령어 실행 중에 `onUpdate` 스냅샷을 발행합니다.\n\nPTY 실행의 경우, 실시간 렌더링은 `onUpdate` 텍스트 청크가 아닌 커스텀 UI 오버레이에 의해 처리됩니다.\n\n## 결과 형성, 메타데이터 및 오류 매핑\n\n실행 후:\n\n1. `cancelled` 처리:\n   - 중단 신호가 중단된 경우 -> `ToolAbortError` 던지기 (중단 의미론),\n   - 그렇지 않으면 -> `ToolError` 던지기 (도구 실패로 처리).\n2. PTY `timedOut` -> `ToolError` 던지기.\n3. 최종 출력 텍스트에 head/tail 필터 적용 (`applyHeadTail`, head 먼저 그 다음 tail).\n4. 빈 출력은 `(no output)`이 됩니다.\n5. `toolResult(...).truncationFromSummary(result, { direction: \"tail\" })`를 통해 잘라내기 메타데이터 첨부.\n6. 종료 코드 매핑:\n   - 종료 코드 없음 -> `ToolError(\"... missing exit status\")`\n   - 0이 아닌 종료 -> `ToolError(\"... Command exited with code N\")`\n   - 0 종료 -> 성공 결과.\n\n성공 페이로드 구조:\n\n- `content`: 텍스트 출력,\n- 잘라내기 시 `details.meta.truncation`, 포함 내용:\n  - `direction`, `truncatedBy`, 총/출력 라인+바이트 수,\n  - `shownRange`,\n  - 사용 가능한 경우 `artifactId`.\n\n내장 도구는 `wrapToolWithMetaNotice()`로 래핑되므로, 잘라내기 알림 텍스트가 최종 텍스트 콘텐츠에 자동으로 추가됩니다(예: `Full: artifact://<id>`).\n\n## 렌더링 경로\n\n## 도구 호출 렌더러 (`bashToolRenderer`)\n\n`bashToolRenderer`는 도구 호출 메시지(`toolCall` / `toolResult`)에 사용됩니다:\n\n- 축소 모드에서는 시각적 라인이 잘라낸 미리보기를 보여줍니다,\n- 확장 모드에서는 현재 사용 가능한 모든 출력 텍스트를 보여줍니다,\n- 경고 라인에는 잘라내기 사유와 잘라내기 시 `artifact://<id>`가 포함됩니다,\n- 타임아웃 값(인수에서)이 하단 메타데이터 라인에 표시됩니다.\n\n### 주의사항: 전체 아티팩트 확장\n\n`BashRenderContext`에는 `isFullOutput`이 있지만, 현재 렌더러 컨텍스트 빌더는 bash 도구 결과에 대해 이를 설정하지 않습니다. 확장 뷰는 다른 호출자가 전체 아티팩트 콘텐츠를 제공하지 않는 한 결과 콘텐츠에 이미 있는 텍스트(테일/잘라낸 출력)를 사용합니다.\n\n## 사용자 뱅 명령어 컴포넌트 (`BashExecutionComponent`)\n\n`BashExecutionComponent`는 대화형 모드에서 사용자 `!` 명령어를 위한 것입니다(모델 도구 호출이 아님):\n\n- 청크를 실시간으로 스트리밍합니다,\n- 축소 미리보기는 마지막 20개 논리 라인을 유지합니다,\n- 라인당 4000자에서 라인 클램프합니다,\n- 메타데이터가 존재할 때 잘라내기 + 아티팩트 경고를 표시합니다,\n- 취소/오류/종료 상태를 별도로 표시합니다.\n\n이 컴포넌트는 `CommandController.handleBashCommand()`에 의해 연결되며 `AgentSession.executeBash()`에서 데이터를 받습니다.\n\n## 모드별 동작 차이\n\n| 표면                            | 진입 경로                                              | PTY 사용 가능 여부                                                     | 실시간 출력 UX                                                             | 오류 표시                                         |\n| ------------------------------ | ----------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------ |\n| 대화형 도구 호출                  | `BashTool.execute`                                    | 예, `bash.virtualTerminal=on`이고 UI가 존재하며 `PI_NO_PTY!=1`일 때     | PTY 오버레이 (대화형) 또는 스트리밍 테일 업데이트                              | 도구 오류가 `toolResult.isError`로 변환됨           |\n| 출력 모드 도구 호출               | `BashTool.execute`                                    | 아니오 (UI 컨텍스트 없음)                                               | TUI 오버레이 없음; 이벤트 스트림/최종 어시스턴트 텍스트 흐름에 출력 표시         | 동일한 도구 오류 매핑                               |\n| RPC 도구 호출 (에이전트 도구)      | `BashTool.execute`                                    | 보통 UI 없음 -> 비-PTY                                                 | 구조화된 도구 이벤트/결과                                                   | 동일한 도구 오류 매핑                               |\n| 대화형 뱅 명령어 (`!`)            | `AgentSession.executeBash` + `BashExecutionComponent` | 아니오 (실행기 직접 사용)                                                | 전용 bash 실행 컴포넌트                                                    | 컨트롤러가 예외를 잡아 UI 오류 표시                  |\n| RPC `bash` 명령어                | `rpc-mode` -> `session.executeBash`                   | 아니오                                                                 | `BashResult`를 직접 반환                                                   | 소비자가 반환된 필드를 처리                          |\n\n## 운영 주의사항\n\n- 인터셉터는 제안된 도구가 현재 컨텍스트에서 사용 가능한 경우에만 명령어를 차단합니다.\n- 아티팩트 할당이 실패하면 잘라내기는 여전히 발생하지만 `artifact://` 역참조를 사용할 수 없습니다.\n- 셸 세션 캐시에는 이 모듈에서 명시적 퇴거(eviction)가 없습니다; 수명은 프로세스 범위입니다.\n- PTY와 비-PTY 타임아웃 표면이 다릅니다:\n  - PTY는 명시적 `timedOut` 결과 필드를 노출합니다,\n  - 비-PTY는 타임아웃을 `cancelled + annotation` 요약으로 매핑합니다.\n\n## 구현 파일\n\n- [`src/tools/bash.ts`](../../packages/coding-agent/src/tools/bash.ts) — 도구 진입점, 정규화/차단, PTY/비-PTY 선택, 결과/오류 매핑, bash 도구 렌더러.\n- [`src/tools/bash-normalize.ts`](../../packages/coding-agent/src/tools/bash-normalize.ts) — 명령어 정규화 및 실행 후 head/tail 필터링.\n- [`src/tools/bash-interceptor.ts`](../../packages/coding-agent/src/tools/bash-interceptor.ts) — 인터셉터 규칙 매칭 및 차단 명령어 메시지.\n- [`src/exec/bash-executor.ts`](../../packages/coding-agent/src/exec/bash-executor.ts) — 비-PTY 실행기, 셸 세션 재사용, 취소 연결, 출력 싱크 통합.\n- [`src/tools/bash-interactive.ts`](../../packages/coding-agent/src/tools/bash-interactive.ts) — PTY 런타임, 오버레이 UI, 입력 정규화, 비대화형 환경 기본값.\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — `OutputSink` 잘라내기/아티팩트 스필 및 요약 메타데이터.\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — 아티팩트 할당 헬퍼 및 스트리밍 테일 버퍼.\n- [`src/tools/output-meta.ts`](../../packages/coding-agent/src/tools/output-meta.ts) — 잘라내기 메타데이터 형태 + 알림 주입 래퍼.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — 세션 수준 `executeBash`, 메시지 기록, 중단 수명 주기.\n- [`src/modes/components/bash-execution.ts`](../../packages/coding-agent/src/modes/components/bash-execution.ts) — 대화형 `!` 명령어 실행 컴포넌트.\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts) — 대화형 `!` 명령어 UI 스트림/업데이트 완료를 위한 연결.\n- [`src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts) — RPC `bash` 및 `abort_bash` 명령어 표면.\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — `artifact://<id>` 해석.\n",
	"ko/runtime-tools/context-command.md": "---\ntitle: F5 XC 컨텍스트\ndescription: 'xcsh를 F5 Distributed Cloud 테넌트에 연결 -- 인증 컨텍스트를 생성, 전환 및 관리합니다.'\nsidebar:\n  order: 1\n  label: F5 XC 컨텍스트\ni18n:\n  sourceHash: a9cccbc338f0\n  translator: machine\n---\n\n# F5 XC 컨텍스트\n\nxcsh는 **컨텍스트**를 통해 F5 Distributed Cloud에 연결합니다 -- 컨텍스트는 테넌트 URL, API 토큰, 네임스페이스를 바인딩하는 이름이 지정된 자격 증명 세트입니다. `kubectl config use-context` 또는 `kubectx`를 사용해 본 적이 있다면 워크플로는 동일합니다: 컨텍스트를 생성하고, 이름으로 전환하며, `-`를 사용하여 이전 컨텍스트로 돌아갑니다.\n\n## 시작하기\n\n### 1. 첫 번째 컨텍스트 생성\n\nF5 XC 콘솔에서 세 가지가 필요합니다: 테넌트 URL, API 토큰, 그리고 선택적으로 네임스페이스입니다.\n\n```\n/context create production https://acme.console.ves.volterra.io p12k3-your-api-token\n```\n\n```\nContext 'production' created. Use /context activate production to switch to it.\n```\n\n단계별 프롬프트를 선호한다면 가이드 마법사를 사용할 수도 있습니다:\n\n```\n/context wizard\n```\n\n### 2. 활성화\n\n```\n/context production\n```\n\n```\n╭─ production ─────────────────────────────────────────────────╮\n│ XCSH_TENANT     acme                                         │\n│ XCSH_API_URL    https://acme.console.ves.volterra.io         │\n│ XCSH_API_TOKEN  ...oken                                      │\n│ Status          Connected (312ms)                            │\n├─ Environment ────────────────────────────────────────────────┤\n│ XCSH_NAMESPACE  default                                      │\n╰──────────────────────────────────────────────────────────────╯\n```\n\n활성화되면 xcsh가 테넌트 자격 증명을 세션에 주입합니다. 이제 에이전트가 F5 XC API 호출을 수행할 수 있으며, 상태 표시줄에 활성 컨텍스트가 표시됩니다.\n\n### 3. 컨텍스트 추가 및 전환\n\n```\n/context create staging https://staging.console.ves.volterra.io p12k3-staging-token\n```\n\n이름으로 전환 -- 하위 명령 동사가 필요 없습니다:\n\n```\n/context staging\n```\n\n이전 컨텍스트로 돌아가기 (`cd -` 스타일):\n\n```\n/context -\n```\n\n`/context -`를 두 번 호출하면 시작했던 곳으로 돌아갑니다.\n\n### 4. 현재 상태 확인\n\n```\n/context\n```\n\n```\n  production           https://acme.console.ves.volterra.io\n* staging              https://staging.console.ves.volterra.io\n```\n\n`*`는 활성 컨텍스트를 표시합니다.\n\n## 일상적인 명령어\n\n| 명령어 | 설명 |\n|---|---|\n| `/context` | 모든 컨텍스트 나열 |\n| `/context <name>` | 해당 컨텍스트로 전환 |\n| `/context -` | 이전 컨텍스트로 전환 |\n| `/context show` | 활성 컨텍스트 세부 정보 표시 (토큰 마스킹) |\n| `/context status` | 현재 인증 상태 표시 |\n\n## 컨텍스트 수명주기\n\n| 명령어 | 설명 |\n|---|---|\n| `/context create <name> <url> <token> [namespace]` | 컨텍스트 생성 |\n| `/context delete <name> --confirm` | 컨텍스트 삭제 (`--confirm` 필요) |\n| `/context rename <old> <new>` | 컨텍스트 이름 변경 |\n| `/context validate <name>` | 전환하지 않고 자격 증명 테스트 |\n| `/context export [name] [--include-token]` | JSON으로 내보내기 (기본적으로 토큰 마스킹) |\n| `/context import <path-or-json> [--overwrite]` | 파일 또는 인라인 JSON에서 가져오기 |\n| `/context wizard` | 가이드 대화형 설정 |\n\n## 네임스페이스 전환\n\n각 컨텍스트에는 기본 네임스페이스가 있습니다. 컨텍스트를 변경하지 않고 네임스페이스만 전환할 수 있습니다:\n\n```\n/context namespace system\n```\n\n탭 완성은 활성 테넌트의 네임스페이스 이름을 제공합니다.\n\n## 컨텍스트의 환경 변수\n\n컨텍스트는 활성화 시 세션에 주입되는 추가 환경 변수를 포함할 수 있습니다. 자격 증명 세트에 포함되지 않는 테넌트별 구성에 유용합니다.\n\n```\n/context set CUSTOM_HEADER=x-acme-trace\n/context set LOG_LEVEL=debug\n/context env list\n/context unset LOG_LEVEL\n```\n\n별칭: `add` = `set`, `remove`/`clear` = `unset`.\n\n## 탭 완성\n\n`/context `를 입력하고 Tab을 누르세요. 드롭다운에 다음이 표시됩니다:\n\n1. **컨텍스트 이름** -- 테넌트 URL 힌트가 포함되어 테넌트를 구분할 수 있습니다\n2. **`-`** -- 이전에 전환한 적이 있을 때 표시되며, 어떤 컨텍스트로 전환될지 보여줍니다\n3. **하위 명령** -- `list`, `create`, `delete` 등\n\n전환이 가장 일반적인 작업이므로 컨텍스트 이름이 먼저 표시됩니다.\n\n하위 명령 수준의 완성도 작동합니다: `/context activate <Tab>`은 컨텍스트 이름을 완성하고, `/context namespace <Tab>`은 네임스페이스를 완성하며, `/context unset <Tab>`은 알려진 환경 변수 키를 완성합니다.\n\n## 이름 규칙\n\n컨텍스트 이름은 1-64자여야 합니다: 문자, 숫자, 하이픈, 밑줄.\n\n하위 명령과 충돌하는 이름은 거부됩니다:\n\n```\n/context create list https://example.com tok\n```\n\n```\nError: Context name 'list' conflicts with a /context subcommand. Choose a different name.\n```\n\n전체 예약어 세트: `list`, `show`, `status`, `create`, `delete`, `rename`, `namespace`, `env`, `set`, `unset`, `add`, `remove`, `clear`, `activate`, `validate`, `export`, `import`, `wizard`, `help`. 비교는 대소문자를 구분하지 않습니다.\n\n## 환경 변수 오버라이드\n\nxcsh를 실행하기 전에 셸 환경에 `XCSH_API_URL`과 `XCSH_API_TOKEN`이 설정되어 있으면 모든 컨텍스트보다 우선합니다. 이는 영구 컨텍스트를 생성하지 않으려는 CI/CD 파이프라인이나 일회성 세션에 유용합니다.\n\n이 모드에서 실행할 때 `/context`는 환경 변수에서 가져온 자격 증명을 `(via env vars)` 레이블과 함께 표시합니다.\n\n## 이전 컨텍스트 동작\n\n- **세션 범위**: 이전 컨텍스트는 xcsh를 재시작하면 초기화됩니다. 디스크에 저장되지 않습니다.\n- **핑퐁**: `/context -`를 두 번 실행하면 시작했던 곳으로 돌아갑니다.\n- **변경에 안전**: 이전 컨텍스트를 삭제하면 포인터가 제거됩니다. 이름을 변경하면 포인터가 새 이름을 따라갑니다.\n- **재활성화는 무연산**: 이미 `production`에 있을 때 `/context production`을 실행하면 이전 포인터가 재설정되지 않습니다.\n\n## 설계 규칙\n\n`/context` UX는 다음을 따릅니다:\n\n- **kubectx**: 전환에 `kubectx <name>`, 이전으로 `kubectx -`, 나열에 `kubectx` 단독 사용\n- **kubectl**: 명시적 형식으로 `kubectl config use-context`\n- **셸**: 이전 디렉토리 추적을 위한 `cd -` / `OLDPWD`\n",
	"ko/runtime-tools/custom-tools.md": "---\ntitle: 커스텀 도구\ndescription: '에이전트를 확장하기 위한 커스텀 도구 등록, 스키마 정의 및 실행 파이프라인.'\nsidebar:\n  order: 4\n  label: 커스텀 도구\ni18n:\n  sourceHash: 5f4a441fc2e2\n  translator: machine\n---\n\n# 커스텀 도구\n\n커스텀 도구는 내장 도구와 동일한 도구 실행 파이프라인에 연결되는 모델 호출 가능 함수입니다.\n\n커스텀 도구는 팩토리를 내보내는 TypeScript/JavaScript 모듈입니다. 팩토리는 호스트 API(`CustomToolAPI`)를 수신하고 하나의 도구 또는 도구 배열을 반환합니다.\n\n## 이것이 무엇인지 (그리고 무엇이 아닌지)\n\n- **커스텀 도구**: 턴 중에 모델이 호출할 수 있습니다 (`execute` + TypeBox 스키마).\n- **Extension**: 도구를 등록하고 이벤트를 가로채거나 수정할 수 있는 라이프사이클/이벤트 프레임워크.\n- **Hook**: 외부 사전/사후 명령 스크립트.\n- **Skill**: 정적 가이던스/컨텍스트 패키지로, 실행 가능한 도구 코드가 아닙니다.\n\n모델이 코드를 직접 호출하도록 해야 한다면 커스텀 도구를 사용하세요.\n\n## 현재 코드의 통합 경로\n\n두 가지 활성 통합 스타일이 있습니다:\n\n1. **SDK 제공 커스텀 도구** (`options.customTools`)\n   - `CustomToolAdapter` 또는 extension 래퍼를 통해 에이전트 도구로 래핑됩니다.\n   - SDK 부트스트랩에서 항상 초기 활성 도구 집합에 포함됩니다.\n\n2. **로더 API를 통해 파일시스템에서 검색된 모듈** (`discoverAndLoadCustomTools` / `loadCustomTools`)\n   - `src/extensibility/custom-tools/loader.ts`에서 라이브러리 API로 노출됩니다.\n   - 호스트 코드는 이를 호출하여 config/provider/plugin 경로에서 도구 모듈을 검색하고 로드할 수 있습니다.\n\n```text\n모델 도구 호출 흐름\n\nLLM tool call\n   │\n   ▼\nTool registry (built-ins + custom tool adapters)\n   │\n   ▼\nCustomTool.execute(toolCallId, params, onUpdate, ctx, signal)\n   │\n   ├─ onUpdate(...)  -> streamed partial result\n   └─ return result  -> final tool content/details\n```\n\n## 검색 위치 (로더 API)\n\n`discoverAndLoadCustomTools(configuredPaths, cwd, builtInToolNames)`는 다음을 병합합니다:\n\n1. 기능 제공자 (`toolCapability`), 포함:\n   - 네이티브 OMP 설정 (`~/.xcsh/agent/tools`, `.xcsh/tools`)\n   - Claude 설정 (`~/.claude/tools`, `.claude/tools`)\n   - Codex 설정 (`~/.codex/tools`, `.codex/tools`)\n   - Claude 마켓플레이스 플러그인 캐시 제공자\n2. 설치된 플러그인 매니페스트 (`~/.xcsh/plugins/node_modules/*` via 플러그인 로더)\n3. 로더에 전달된 명시적으로 구성된 경로\n\n### 중요한 동작\n\n- 중복 해석된 경로는 중복 제거됩니다.\n- 도구 이름 충돌은 내장 도구 및 이미 로드된 커스텀 도구에 대해 거부됩니다.\n- 일부 제공자는 `.md` 및 `.json` 파일을 도구 메타데이터로 검색하지만, 실행 가능한 모듈 로더는 이를 실행 가능한 도구로 거부합니다.\n- 상대 구성 경로는 `cwd`에서 해석되며, `~`는 확장됩니다.\n\n## 모듈 계약\n\n커스텀 도구 모듈은 함수를 내보내야 합니다 (기본 내보내기 권장):\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = (pi) => ({\n name: \"repo_stats\",\n label: \"Repo Stats\",\n description: \"Counts tracked TypeScript files\",\n parameters: pi.typebox.Type.Object({\n  glob: pi.typebox.Type.Optional(pi.typebox.Type.String({ default: \"**/*.ts\" })),\n }),\n\n async execute(toolCallId, params, onUpdate, ctx, signal) {\n  onUpdate?.({\n   content: [{ type: \"text\", text: \"Scanning files...\" }],\n   details: { phase: \"scan\" },\n  });\n\n  const result = await pi.exec(\"git\", [\"ls-files\", params.glob ?? \"**/*.ts\"], { signal, cwd: pi.cwd });\n  if (result.killed) {\n   throw new Error(\"Scan was cancelled\");\n  }\n  if (result.code !== 0) {\n   throw new Error(result.stderr || \"git ls-files failed\");\n  }\n\n  const files = result.stdout.split(\"\\n\").filter(Boolean);\n  return {\n   content: [{ type: \"text\", text: `Found ${files.length} files` }],\n   details: { count: files.length, sample: files.slice(0, 10) },\n  };\n },\n\n onSession(event) {\n  if (event.reason === \"shutdown\") {\n   // cleanup resources if needed\n  }\n },\n});\n\nexport default factory;\n```\n\n팩토리 반환 타입:\n\n- `CustomTool`\n- `CustomTool[]`\n- `Promise<CustomTool | CustomTool[]>`\n\n## 팩토리에 전달되는 API 표면 (`CustomToolAPI`)\n\n`types.ts` 및 `loader.ts`에서:\n\n- `cwd`: 호스트 작업 디렉터리\n- `exec(command, args, options?)`: 프로세스 실행 헬퍼\n- `ui`: UI 컨텍스트 (헤드리스 모드에서 no-op 가능)\n- `hasUI`: 비대화형 흐름에서 `false`\n- `logger`: 공유 파일 로거\n- `typebox`: 주입된 `@sinclair/typebox`\n- `pi`: 주입된 `@f5-sales-demo/xcsh` 내보내기\n- `pushPendingAction(action)`: 숨겨진 `resolve` 도구의 미리보기 액션 등록 (`docs/resolve-tool-runtime.md`)\n\n로더는 no-op UI 컨텍스트로 시작하며, 실제 UI가 준비되면 호스트 코드가 `setUIContext(...)`를 호출해야 합니다.\n\n## 실행 계약 및 타이핑\n\n`CustomTool.execute` 시그니처:\n\n```ts\nexecute(toolCallId, params, onUpdate, ctx, signal)\n```\n\n- `params`는 `Static<TParams>`를 통해 TypeBox 스키마에서 정적으로 타입이 지정됩니다.\n- 런타임 인수 유효성 검사는 에이전트 루프에서 실행 전에 수행됩니다.\n- `onUpdate`는 UI 스트리밍을 위한 부분 결과를 내보냅니다.\n- `ctx`는 세션/모델 상태와 `abort()` 헬퍼를 포함합니다.\n- `signal`은 취소를 전달합니다.\n\n`CustomToolAdapter`는 이를 에이전트 도구 인터페이스에 연결하고 올바른 인수 순서로 호출을 전달합니다.\n\n## 모델에 도구가 노출되는 방법\n\n- 도구는 `AgentTool` 인스턴스(`CustomToolAdapter` 또는 extension 래퍼)로 래핑됩니다.\n- 이름별로 세션 도구 레지스트리에 삽입됩니다.\n- SDK 부트스트랩에서 커스텀 및 extension 등록 도구는 초기 활성 집합에 강제 포함됩니다.\n- CLI `--tools`는 현재 내장 도구 이름만 검증하며, 커스텀 도구 포함은 검색/등록 경로 및 SDK 옵션을 통해 처리됩니다.\n\n## 렌더링 훅\n\n선택적 렌더링 훅:\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\nTUI에서의 런타임 동작:\n\n- 훅이 있으면 도구 출력이 `Box` 컨테이너 안에 렌더링됩니다.\n- `renderResult`는 `{ expanded, isPartial, spinnerFrame? }`를 수신합니다.\n- 렌더러 오류는 캐치되어 기록되며, UI는 기본 텍스트 렌더링으로 대체됩니다.\n\n## 세션/상태 처리\n\n선택적 `onSession(event, ctx)`는 다음을 포함한 세션 라이프사이클 이벤트를 수신합니다:\n\n- `start`, `switch`, `branch`, `tree`, `shutdown`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`, `todo_reminder`\n\n브랜치/세션 컨텍스트가 변경될 때 히스토리에서 상태를 재구성하려면 `ctx.sessionManager`를 사용하세요.\n\n## 실패 및 취소 시맨틱\n\n### 동기/비동기 실패\n\n- `execute`에서 throw(또는 거부된 프로미스)는 도구 실패로 처리됩니다.\n- 에이전트 런타임은 실패를 `isError: true` 및 오류 텍스트 내용이 있는 도구 결과 메시지로 변환합니다.\n- extension 래퍼를 사용하면 `tool_result` 핸들러가 내용/세부 정보를 추가로 재작성하고 오류 상태를 재정의할 수도 있습니다.\n\n### 취소\n\n- 에이전트 중단은 `AbortSignal`을 통해 `execute`로 전파됩니다.\n- 협력적 취소를 위해 `signal`을 서브프로세스 작업에 전달하세요 (`pi.exec(..., { signal })`).\n- `ctx.abort()`를 통해 도구가 현재 에이전트 작업의 중단을 요청할 수 있습니다.\n\n### onSession 오류\n\n- `onSession` 오류는 캐치되어 경고로 기록되며, 세션을 충돌시키지 않습니다.\n\n## 설계 시 고려해야 할 실제 제약\n\n- 도구 이름은 활성 레지스트리에서 전역적으로 고유해야 합니다.\n- 렌더러/상태 재구성을 위해 `details`에서 결정론적이고 스키마 형태의 출력을 선호하세요.\n- `pi.hasUI`로 UI 사용을 보호하세요.\n- 도구 디렉터리의 `.md`/`.json`을 실행 가능한 모듈이 아닌 메타데이터로 처리하세요.\n",
	"ko/runtime-tools/notebook-tool-runtime.md": "---\ntitle: 노트북 도구 런타임 내부 구조\ndescription: '셀 실행, 커널 수명 주기 및 출력 렌더링을 포함한 Jupyter 노트북 도구 런타임.'\nsidebar:\n  order: 2\n  label: 노트북 도구\ni18n:\n  sourceHash: c1bafcb245e4\n  translator: machine\n---\n\n# 노트북 도구 런타임 내부 구조\n\n이 문서는 현재 `notebook` 도구 구현과 커널 기반 Python 런타임과의 관계를 설명합니다.\n\n핵심 구분: **`notebook`은 JSON 노트북 편집기이지, 노트북 실행기가 아닙니다**. `.ipynb` 셀 소스를 직접 편집하며, Python 커널을 시작하거나 통신하지 않습니다.\n\n## 구현 파일\n\n- [`src/tools/notebook.ts`](../../packages/coding-agent/src/tools/notebook.ts)\n- [`src/ipy/executor.ts`](../../packages/coding-agent/src/ipy/executor.ts)\n- [`src/ipy/kernel.ts`](../../packages/coding-agent/src/ipy/kernel.ts)\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts)\n- [`src/tools/python.ts`](../../packages/coding-agent/src/tools/python.ts)\n\n## 1) 런타임 경계: 편집 vs 실행\n\n## `notebook` 도구 (`src/tools/notebook.ts`)\n\n- `.ipynb` 파일에서 `action: edit | insert | delete`를 지원합니다.\n- 세션 CWD 기준으로 경로를 해석합니다(`resolveToCwd`).\n- 노트북 JSON을 로드하고, `cells` 배열을 검증하며, `cell_index` 범위를 검증합니다.\n- 소스 편집을 메모리 내에서 적용하고 `JSON.stringify(notebook, null, 1)`을 사용하여 전체 노트북 JSON을 다시 씁니다.\n- 텍스트 요약과 구조화된 `details`(`action`, `cellIndex`, `cellType`, `totalCells`, `cellSource`)를 반환합니다.\n\n이 도구에는 커널 수명 주기가 존재하지 않습니다:\n\n- 게이트웨이 획득 없음\n- 커널 세션 ID 없음\n- `execute_request` 없음\n- 커널 채널의 스트림 청크 없음\n- 리치 디스플레이 캡처 없음(`image/png`, JSON 디스플레이, 상태 MIME)\n\n## 노트북 유사 실행 경로 (`src/tools/python.ts` + `src/ipy/*`)\n\n에이전트가 셀 스타일 Python 코드(순차적 셀, 영구 상태, 리치 디스플레이)를 실행해야 할 때는 `notebook`이 아닌 **`python` 도구**를 통해 처리됩니다.\n\n커널 모드, 재시작/취소 동작, 청크 스트리밍, 출력 아티팩트 잘라내기가 이 경로에 존재합니다.\n\n## 2) 노트북 셀 처리 의미론 (`notebook` 도구)\n\n## 소스 정규화\n\n`content`는 개행 보존과 함께 `source: string[]`으로 분할됩니다:\n\n- 마지막이 아닌 각 줄은 후행 `\\n`을 유지합니다\n- 마지막 줄에는 강제 후행 개행이 없습니다\n\n이는 노트북 JSON 관례를 반영하며 이후 편집 시 우발적인 줄 연결을 방지합니다.\n\n## 액션 동작\n\n- `edit`\n  - `cells[cell_index].source`를 교체합니다\n  - 기존 `cell_type`을 보존합니다\n- `insert`\n  - `[0..cellCount]` 위치에 삽입합니다\n  - `cell_type`은 기본값이 `code`입니다\n  - 코드 셀은 `execution_count: null` 및 `outputs: []`로 초기화됩니다\n  - 마크다운 셀은 `metadata` + `source`만 초기화됩니다\n- `delete`\n  - `cells[cell_index]`를 제거합니다\n  - 렌더러 미리보기를 위해 제거된 `source`를 details에 반환합니다\n\n## 오류 처리\n\n다음 경우에 하드 실패가 발생합니다:\n\n- 노트북 파일 없음\n- 유효하지 않은 JSON\n- `cells` 누락 또는 배열이 아님\n- 범위를 벗어난 인덱스(삽입과 비삽입은 유효 범위가 다름)\n- `edit`/`insert`에 `content` 누락\n\n이는 상위에서 `Error:` 도구 응답이 되며, 렌더러는 노트북 경로와 형식화된 오류 텍스트를 사용합니다.\n\n## 3) 커널 세션 의미론 (실제로 존재하는 위치)\n\n커널 의미론은 `executePython` / `PythonKernel`에 구현되어 있으며 `python` 도구에 적용됩니다.\n\n## 모드\n\n`PythonKernelMode`:\n\n- `session` (기본값)\n  - `kernelSessions` 맵에서 커널 캐시\n  - 최대 4개 세션; 초과 시 가장 오래된 세션 제거\n  - 30초마다 유휴/비활성 정리, 5분 후 타임아웃\n  - 세션별 큐가 실행을 직렬화함(`session.queue`)\n- `per-call`\n  - 요청에 대한 커널 생성\n  - 실행\n  - `finally`에서 항상 커널 종료\n\n## 재설정 동작\n\n`python` 도구는 다중 셀 호출에서 첫 번째 셀에만 `reset`을 전달하며, 이후 셀은 항상 `reset: false`로 실행됩니다.\n\n## 커널 종료 / 재시작 / 재시도\n\n세션 모드(`withKernelSession`)에서:\n\n- 비활성 커널은 하트비트(`kernel.isAlive()` 5초마다 확인)나 실행 실패로 감지됩니다.\n- 실행 전 비활성 상태는 `restartKernelSession`을 트리거합니다.\n- 실행 중 충돌 경로는 한 번 재시도합니다: 커널 재시작, 핸들러 재실행.\n- 동일 세션에서 `restartCount > 1`이면 `Python kernel restarted too many times in this session`을 발생시킵니다.\n\n시작 재시도 동작:\n\n- 공유 게이트웨이 커널 생성은 HTTP 5xx와 함께 `SharedGatewayCreateError` 발생 시 한 번 재시도합니다.\n\n리소스 고갈 복구:\n\n- `EMFILE`/`ENFILE`/\"Too many open files\" 스타일 실패를 감지합니다\n- 추적된 세션을 초기화합니다\n- `shutdownSharedGateway()`를 호출합니다\n- 커널 세션 생성을 한 번 재시도합니다\n\n## 4) 환경/세션 변수 주입\n\n커널 시작 시 실행기에서 선택적 환경 맵을 수신합니다:\n\n- `PI_SESSION_FILE` (세션 상태 파일 경로)\n- `ARTIFACTS` (아티팩트 디렉터리)\n\n`PythonKernel.#initializeKernelEnvironment(...)`는 커널 내부에서 초기화 스크립트를 실행합니다:\n\n- `os.chdir(cwd)`\n- `os.environ`에 환경 항목 주입\n- 누락된 경우 `sys.path`에 cwd를 앞에 추가\n\n시사점:\n\n- 세션 또는 아티팩트 컨텍스트를 읽는 프리루드 헬퍼는 Python 프로세스 상태의 이러한 환경 변수에 의존합니다.\n\n## 5) 스트리밍/청크 및 디스플레이 처리 (커널 기반 경로)\n\n커널 클라이언트는 실행당 Jupyter 프로토콜 메시지를 처리합니다:\n\n- `stream` -> `onChunk`에 텍스트 청크 전달\n- `execute_result` / `display_data` ->\n  - MIME 우선순위에 따라 디스플레이 텍스트 선택: `text/markdown` > `text/plain` > 변환된 `text/html`\n  - 구조화된 출력을 별도로 캡처:\n    - `application/json` -> `{ type: \"json\" }`\n    - `image/png` -> `{ type: \"image\" }`\n    - `application/x-xcsh-status` -> `{ type: \"status\" }` (텍스트 발행 없음)\n- `error` -> 트레이스백 텍스트를 청크 스트림에 푸시 + 구조화된 오류 메타데이터\n- `input_request` -> stdin 경고 텍스트 발행, 빈 `input_reply` 전송, stdin 요청됨 표시\n- 완료는 `execute_reply`와 커널 `status=idle` 모두를 기다립니다\n\n취소/타임아웃:\n\n- 중단 신호는 `interrupt()`를 트리거합니다(REST `/interrupt` + 제어 채널 `interrupt_request`)\n- 결과는 `cancelled=true`로 표시됩니다\n- 타임아웃 경로는 출력에 `Command timed out after <n> seconds`를 주석으로 추가합니다\n\n## 6) 잘라내기 및 아티팩트 동작\n\n`src/session/streaming-output.ts`의 `OutputSink`는 커널 실행 경로(`executeWithKernel`)에서 사용됩니다:\n\n- 모든 청크를 정제합니다(`sanitizeText`)\n- 총 라인/출력 라인 및 바이트를 추적합니다\n- 선택적 아티팩트 스필 파일(`artifactPath`, `artifactId`)\n- 메모리 내 버퍼가 임계값(`DEFAULT_MAX_BYTES`, 재정의 가능)을 초과하면:\n  - 잘라내기 표시\n  - 메모리에 꼬리 바이트 유지(UTF-8 안전 경계)\n  - 전체 스트림을 아티팩트 싱크로 스필 가능\n\n`dump()`가 반환하는 값:\n\n- 표시 가능한 출력 텍스트(꼬리 잘라내기 가능)\n- 잘라내기 플래그 + 카운트\n- 아티팩트 ID(`artifact://<id>` 참조용)\n\n`python` 도구는 이 메타데이터를 결과 잘라내기 알림과 TUI 경고로 변환합니다.\n\n`notebook` 도구는 `OutputSink`를 **사용하지 않습니다**. 코드를 실행하지 않으므로 스트림/아티팩트 잘라내기 파이프라인이 없습니다.\n\n## 7) 렌더러 가정 및 포맷팅\n\n## 노트북 렌더러 (`notebookToolRenderer`)\n\n- 호출 뷰: 액션 + 노트북 경로 + 셀/타입 메타데이터가 포함된 상태 줄\n- 결과 뷰:\n  - `details`에서 도출된 성공 요약\n  - `renderCodeCell`을 통해 렌더링된 `cellSource`\n  - 마크다운 셀은 언어 힌트를 `markdown`으로 설정; 다른 셀에는 명시적 언어 재정의 없음\n  - 접힌 코드 미리보기 한도는 `PREVIEW_LIMITS.COLLAPSED_LINES * 2`\n  - 공유 렌더 옵션을 통해 확장 모드 지원\n  - 너비 + 확장 상태를 키로 하는 렌더 캐시 사용\n\n오류 렌더링 가정:\n\n- 첫 번째 텍스트 내용이 `Error:`로 시작하면 렌더러가 노트북 오류 블록으로 포맷합니다.\n\n## Python 렌더러 (실제 실행 출력용)\n\n커널 기반 실행 렌더링이 기대하는 값:\n\n- 셀별 상태 전환(`pending/running/complete/error`)\n- 선택적 구조화된 상태 이벤트 섹션\n- 선택적 JSON 출력 트리\n- 잘라내기 경고 + 선택적 `artifact://<id>` 포인터\n\n이 렌더러 동작은 `notebook` JSON 편집 결과와 무관합니다. 단, 둘 다 공유 TUI 프리미티브를 재사용합니다.\n\n## 8) 일반 Python 도구 동작과의 차이점\n\n\"일반 Python 도구\"가 `python` 실행 경로를 의미하는 경우:\n\n- `python`은 커널에서 코드를 실행하고, 모드에 따라 상태를 유지하며, 청크를 스트리밍하고, 리치 디스플레이를 캡처하며, 인터럽트/타임아웃을 처리하고, 출력 잘라내기/아티팩트를 지원합니다.\n- `notebook`은 결정론적 노트북 JSON 변형만 수행합니다. 실행, 커널 상태, 청크 스트림, 디스플레이 출력, 아티팩트 파이프라인이 없습니다.\n\n워크플로우에서 둘 다 필요한 경우:\n\n1. `notebook`으로 노트북 소스를 편집합니다\n2. `notebook`이 아닌 `python`을 통해 코드를 수동으로 전달하여 코드 셀을 실행합니다\n\n현재 구현은 `.ipynb`를 변형하면서 커널 컨텍스트를 통해 노트북 셀을 실행하는 단일 도구를 제공하지 않습니다.\n",
	"ko/runtime-tools/resolve-tool-runtime.md": "---\ntitle: Resolve 도구 런타임 내부 구조\ndescription: >-\n  Resolve tool runtime for file path resolution, content fetching, and URL-based\n  resource access.\nsidebar:\n  order: 3\n  label: Resolve 도구\ni18n:\n  sourceHash: 06e8be8c5a3c\n  translator: machine\n---\n\n# Resolve 도구 런타임 내부 구조\n\n이 문서는 coding-agent에서 미리보기/적용 워크플로우가 어떻게 모델링되는지, 그리고 커스텀 도구가 `pushPendingAction`을 통해 어떻게 참여할 수 있는지 설명합니다.\n\n## 범위 및 주요 파일\n\n- [`src/tools/resolve.ts`](../../packages/coding-agent/src/tools/resolve.ts)\n- [`src/tools/pending-action.ts`](../../packages/coding-agent/src/tools/pending-action.ts)\n- [`src/tools/ast-edit.ts`](../../packages/coding-agent/src/tools/ast-edit.ts)\n- [`src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts)\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n\n## `resolve`가 하는 일\n\n`resolve`는 대기 중인 미리보기 액션을 확정하는 숨겨진 도구입니다.\n\n- `action: \"apply\"`는 대기 중인 액션에 대해 `apply(reason)`를 실행하고 변경 사항을 저장합니다.\n- `action: \"discard\"`는 제공된 경우 `reject(reason)`를 호출하고, 그렇지 않으면 기본 \"Discarded\" 메시지와 함께 액션을 삭제합니다.\n\n대기 중인 액션이 없으면 `resolve`는 다음과 같은 오류로 실패합니다:\n\n- `No pending action to resolve. Nothing to apply or discard.`\n\n## 대기 액션은 스택(LIFO)입니다\n\n대기 액션은 `PendingActionStore`에 push/pop 스택으로 저장됩니다:\n\n- `push(action)`은 새로운 대기 액션을 맨 위에 추가합니다.\n- `peek()`는 현재 맨 위의 액션을 확인합니다.\n- `pop()`은 맨 위의 액션을 제거하고 반환합니다.\n- `hasPending`은 스택이 비어 있지 않은지를 나타냅니다.\n\n`resolve`는 항상 **최상위** 대기 액션을 먼저 소비(`pop()`)하므로, 여러 미리보기 생성 도구는 등록된 역순으로 해결됩니다.\n\n## 내장 생산자 예제 (`ast_edit`)\n\n`ast_edit`는 먼저 구조적 교체를 미리보기합니다. 미리보기에 교체 항목이 있고 아직 적용되지 않은 경우, 다음을 포함하는 대기 액션을 푸시합니다:\n\n- label (사람이 읽을 수 있는 요약)\n- `sourceToolName` (`ast_edit`)\n- `apply(reason: string)` 콜백 — `dryRun: false`로 AST 편집을 다시 실행합니다\n\n`resolve(action=\"apply\", reason=\"...\")`는 이 콜백에 `reason`을 전달합니다.\n\n## 커스텀 도구: `pushPendingAction`\n\n커스텀 도구는 `CustomToolAPI.pushPendingAction(...)`을 통해 resolve 호환 대기 액션을 등록할 수 있습니다.\n\n`CustomToolPendingAction`:\n\n- `label: string` (필수)\n- `apply(reason: string): Promise<AgentToolResult<unknown>>` (필수) — 적용 시 호출됩니다; `reason`은 `resolve`에 전달된 문자열입니다\n- `reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>` (선택) — 폐기 시 호출됩니다; 반환 값이 제공되면 기본 \"Discarded\" 메시지를 대체합니다\n- `details?: unknown` (선택)\n- `sourceToolName?: string` (선택, 기본값은 `\"custom_tool\"`)\n\n### 최소 사용 예제\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = pi => ({\n name: \"batch_rename_preview\",\n label: \"Batch Rename Preview\",\n description: \"Previews renames and defers commit to resolve\",\n parameters: pi.typebox.Type.Object({\n  files: pi.typebox.Type.Array(pi.typebox.Type.String()),\n }),\n\n async execute(_toolCallId, params) {\n  const previewSummary = `Prepared rename plan for ${params.files.length} files`;\n\n  pi.pushPendingAction({\n   label: `Batch rename: ${params.files.length} files`,\n   sourceToolName: \"batch_rename_preview\",\n   apply: async (reason) => {\n    // apply writes here\n    return {\n     content: [{ type: \"text\", text: `Applied batch rename. Reason: ${reason}` }],\n    };\n   },\n   reject: async (reason) => {\n    // optional: cleanup or notify on discard\n    return {\n     content: [{ type: \"text\", text: `Discarded batch rename. Reason: ${reason}` }],\n    };\n   },\n  });\n\n  return {\n   content: [{ type: \"text\", text: `${previewSummary}. Call resolve to apply or discard.` }],\n  };\n },\n});\n\nexport default factory;\n```\n\n## 런타임 가용성 및 실패\n\n`pushPendingAction`은 활성 세션의 `PendingActionStore`를 사용하여 커스텀 도구 로더에 의해 연결됩니다.\n\n런타임에 pending-action 스토어가 없으면 `pushPendingAction`은 다음 오류를 던집니다:\n\n- `Pending action store unavailable for custom tools in this runtime.`\n\n## 도구 선택 동작\n\n`PendingActionStore.hasPending`이 true이면, 에이전트 런타임은 도구 선택을 `resolve`로 편향시켜 일반적인 도구 흐름이 계속되기 전에 대기 중인 미리보기가 명시적으로 확정되도록 합니다.\n\n## 개발자 가이드\n\n- 대기 액션은 명시적인 적용/폐기를 지원해야 하는 파괴적이거나 영향이 큰 작업에만 사용하세요.\n- `label`은 간결하고 구체적으로 유지하세요; resolve 렌더러 출력에 표시됩니다.\n- `apply(reason)`가 일회성 실행에 충분히 결정적이고 멱등성을 갖도록 하세요; `reason`은 정보 제공 목적이며 동작을 변경해서는 안 됩니다.\n- 폐기 시 정리가 필요한 경우(임시 상태, 잠금, 알림) `reject(reason)`를 구현하세요; 기본 메시지로 충분한 무상태 미리보기의 경우 생략하세요.\n- 도구가 여러 미리보기를 스테이징할 수 있는 경우, LIFO 의미론을 기억하세요: 가장 최근에 푸시된 액션이 먼저 해결됩니다.\n",
	"ko/runtime-tools/slash-command-internals.md": "---\ntitle: 슬래시 명령어 내부 구조\ndescription: '등록, 인자 파싱, 실행 디스패치를 포함한 슬래시 명령어 시스템 내부 구조.'\nsidebar:\n  order: 5\n  label: 슬래시 명령어\ni18n:\n  sourceHash: 2cbd44a3de87\n  translator: machine\n---\n\n# 슬래시 명령어 내부 구조\n\n이 문서는 `coding-agent`에서 슬래시 명령어가 어떻게 탐지되고, 중복 제거되며, 인터랙티브 모드에서 노출되고, 프롬프트 시점에 확장되는지를 설명합니다.\n\n## 구현 파일\n\n- [`src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n- [`src/capability/slash-command.ts`](../../packages/coding-agent/src/capability/slash-command.ts)\n- [`src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`src/discovery/claude.ts`](../../packages/coding-agent/src/discovery/claude.ts)\n- [`src/discovery/codex.ts`](../../packages/coding-agent/src/discovery/codex.ts)\n- [`src/discovery/claude-plugins.ts`](../../packages/coding-agent/src/discovery/claude-plugins.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n\n## 1) 탐지 모델\n\n슬래시 명령어는 명령어 이름을 키(`key: cmd => cmd.name`)로 사용하는 기능(`id: \"slash-commands\"`)입니다.\n\n기능 레지스트리는 등록된 모든 프로바이더를 로드하고, 프로바이더 우선순위 내림차순으로 정렬한 후 **첫 번째 항목 우선** 방식으로 키 기반 중복 제거를 수행합니다.\n\n### 프로바이더 우선순위\n\n현재 슬래시 명령어 프로바이더와 우선순위:\n\n1. `native` (OMP) — 우선순위 `100`\n2. `claude` — 우선순위 `80`\n3. `claude-plugins` — 우선순위 `70`\n4. `codex` — 우선순위 `70`\n\n동점 처리: 동일 우선순위의 프로바이더는 등록 순서를 유지합니다. 현재 import 순서상 `claude-plugins`가 `codex`보다 먼저 등록되므로, 이름이 충돌할 경우 플러그인 명령어가 codex 명령어보다 우선합니다.\n\n### 이름 충돌 처리\n\n`slash-commands`의 경우, 충돌은 기능 중복 제거를 통해 엄격하게 처리됩니다:\n\n- 가장 높은 우선순위의 항목이 `result.items`에 유지됩니다.\n- 낮은 우선순위의 중복 항목은 `result.all`에만 남고 `_shadowed = true`로 표시됩니다.\n\n이는 프로바이더 간뿐만 아니라, 단일 프로바이더가 중복 이름을 반환하는 경우에도 적용됩니다.\n\n### 파일 스캔 동작\n\n프로바이더는 대부분 `loadFilesFromDir(...)`를 사용하며, 현재 다음과 같이 동작합니다:\n\n- 기본값은 비재귀 매칭(`*.md`)\n- `gitignore: true`, `hidden: false`로 네이티브 glob 사용\n- 매칭된 각 파일을 읽고 `SlashCommand`로 변환\n\n따라서 숨김 파일/디렉터리는 로드되지 않으며, 무시된 경로는 건너뜁니다.\n\n## 2) 프로바이더별 소스 경로 및 로컬 우선순위\n\n## `native` 프로바이더 (`builtin.ts`)\n\n검색 루트는 `.xcsh` 디렉터리에서 가져옵니다:\n\n- 프로젝트: `<cwd>/.xcsh/commands/*.md`\n- 사용자: `~/.xcsh/agent/commands/*.md`\n\n`getConfigDirs()`는 프로젝트를 먼저, 그다음 사용자 순으로 반환하므로, 이름이 충돌할 경우 **프로젝트 네이티브 명령어가 사용자 네이티브 명령어보다 우선**합니다.\n\n## `claude` 프로바이더 (`claude.ts`)\n\n로드 대상:\n\n- 사용자: `~/.claude/commands/*.md`\n- 프로젝트: `<cwd>/.claude/commands/*.md`\n\n프로바이더는 사용자 항목을 프로젝트 항목보다 먼저 추가하므로, 이 프로바이더 내에서 이름이 충돌할 경우 **사용자 Claude 명령어가 프로젝트 Claude 명령어보다 우선**합니다.\n\n## `codex` 프로바이더 (`codex.ts`)\n\n로드 대상:\n\n- 사용자: `~/.codex/commands/*.md`\n- 프로젝트: `<cwd>/.codex/commands/*.md`\n\n양쪽을 로드한 후 사용자 우선 순서로 평탄화하므로, 충돌 시 **사용자 Codex 명령어가 프로젝트 Codex 명령어보다 우선**합니다.\n\nCodex 명령어 내용은 프론트매터 제거(`parseFrontmatter`)로 파싱되며, 명령어 이름은 프론트매터의 `name` 필드로 재정의할 수 있습니다. 지정하지 않으면 파일명이 사용됩니다.\n\n## `claude-plugins` 프로바이더 (`claude-plugins.ts`)\n\n`~/.claude/plugins/installed_plugins.json`에서 플러그인 명령어 루트를 로드한 후, `<pluginRoot>/commands/*.md`를 스캔합니다.\n\n순서는 레지스트리 반복 순서와 해당 JSON 데이터의 플러그인별 항목 순서를 따릅니다. 추가적인 정렬 단계는 없습니다.\n\n## 3) 런타임 `FileSlashCommand`로의 구체화\n\n`src/extensibility/slash-commands.ts`의 `loadSlashCommands()`는 기능 항목을 프롬프트 시점에 사용되는 `FileSlashCommand` 객체로 변환합니다.\n\n각 명령어에 대해:\n\n1. 프론트매터/본문 파싱(`parseFrontmatter`)\n2. 설명 소스:\n   - `frontmatter.description`이 있으면 해당 값 사용\n   - 없으면 본문의 첫 번째 비어있지 않은 줄(트림 후, 최대 60자, 초과 시 `...`)\n3. 파싱된 본문을 실행 가능한 템플릿 콘텐츠로 유지\n4. `via Claude Code Project`와 같은 표시용 소스 문자열 계산\n\n프론트매터 파싱 심각도는 소스에 따라 다릅니다:\n\n- `native` 레벨 → 파싱 오류는 `fatal`\n- `user`/`project` 레벨 → 파싱 오류는 `warn`이며 폴백 파싱 사용\n\n### 번들 폴백 명령어\n\n파일시스템/프로바이더 명령어 이후, 내장 명령어 템플릿(`EMBEDDED_COMMAND_TEMPLATES`)이 이름이 아직 존재하지 않는 경우 추가됩니다.\n\n현재 내장 세트는 `src/task/commands.ts`에서 가져오며 폴백으로 사용됩니다(`source: \"bundled\"`).\n\n## 4) 인터랙티브 모드: 명령어 목록의 출처\n\n인터랙티브 모드는 자동완성과 명령어 라우팅을 위해 여러 명령어 소스를 결합합니다.\n\n생성 시 다음으로부터 대기 중인 명령어 목록을 구성합니다:\n\n- 빌트인 명령어(`BUILTIN_SLASH_COMMANDS`, 선택된 명령어에 대한 인자 완성 및 인라인 힌트 포함)\n- 확장 등록된 슬래시 명령어(`extensionRunner.getRegisteredCommands(...)`)\n- TypeScript 커스텀 명령어(`session.customCommands`), 슬래시 명령어 레이블로 매핑\n- `skills.enableSkillCommands`가 활성화된 경우 선택적 스킬 명령어(`/skill:<name>`)\n\n이후 `init()`이 `refreshSlashCommandState(...)`를 호출하여 파일 기반 명령어를 로드하고 다음을 포함하는 `CombinedAutocompleteProvider` 하나를 설치합니다:\n\n- 위의 대기 중인 명령어\n- 탐지된 파일 기반 명령어\n\n`refreshSlashCommandState(...)`는 또한 `session.setSlashCommands(...)`를 업데이트하여 프롬프트 확장이 동일한 탐지된 파일 명령어 세트를 사용하도록 합니다.\n\n### 갱신 수명 주기\n\n슬래시 명령어 상태는 다음 시점에 갱신됩니다:\n\n- 인터랙티브 초기화 중\n- `/move`로 작업 디렉터리 변경 후(`handleMoveCommand`가 `resetCapabilities()`를 호출한 다음 `refreshSlashCommandState(newCwd)` 호출)\n\n명령어 디렉터리에 대한 지속적인 파일 감시자는 없습니다.\n\n### 기타 노출 방식\n\n확장 대시보드도 `slash-commands` 기능을 로드하여 `_shadowed` 중복 항목을 포함한 활성/섀도잉된 명령어 항목을 표시합니다.\n\n## 5) 프롬프트 파이프라인 위치\n\n`AgentSession.prompt(...)`의 슬래시 처리 순서(`expandPromptTemplates !== false`인 경우):\n\n1. **확장 명령어** (`#tryExecuteExtensionCommand`)  \n   `/name`이 확장 등록 명령어와 일치하면 핸들러가 즉시 실행되고 프롬프트가 반환됩니다.\n2. **TypeScript 커스텀 명령어** (`#tryExecuteCustomCommand`)  \n   경계 처리만: 일치하면 실행되며 다음을 반환할 수 있습니다:\n   - `string` → 해당 문자열로 프롬프트 텍스트 교체\n   - `void/undefined` → 처리된 것으로 간주; LLM 프롬프트 없음\n3. **파일 기반 슬래시 명령어** (`expandSlashCommand`)  \n   텍스트가 여전히 `/`로 시작하면 마크다운 명령어 확장을 시도합니다.\n4. **프롬프트 템플릿** (`expandPromptTemplate`)  \n   슬래시/커스텀 처리 이후 적용됩니다.\n5. **전달**\n   - 유휴 상태: 프롬프트가 즉시 에이전트로 전송됨\n   - 스트리밍 중: `streamingBehavior`에 따라 steer/follow-up으로 큐에 추가됨\n\n이것이 슬래시 명령어 확장이 프롬프트 템플릿 확장보다 먼저 수행되는 이유이며, 커스텀 명령어가 파일 명령어 매칭 전에 선행 슬래시를 변환할 수 있는 이유입니다.\n\n## 6) 파일 기반 슬래시 명령어의 확장 시맨틱\n\n`expandSlashCommand(text, fileCommands)` 동작:\n\n- 텍스트가 `/`로 시작할 때만 실행\n- `/` 이후 첫 번째 토큰에서 명령어 이름 파싱\n- `parseCommandArgs`를 통해 나머지 텍스트에서 인자 파싱\n- 로드된 `fileCommands`에서 정확한 이름 매칭 검색\n- 일치하면 다음을 적용:\n  - 위치 기반 교체: `$1`, `$2`, ...\n  - 집계 교체: `$ARGUMENTS` 및 `$@`\n  - 이후 `{ args, ARGUMENTS, arguments }`로 `prompt.render`를 통한 템플릿 렌더링\n- 일치하지 않으면 원본 텍스트를 그대로 반환\n\n### `parseCommandArgs` 주의사항\n\n파서는 단순한 따옴표 인식 분리 방식입니다:\n\n- 공백 유지를 위한 `'단일'` 및 `\"이중\"` 따옴표 지원\n- 따옴표 구분자 제거\n- 백슬래시 이스케이프 규칙 미구현\n- 짝이 맞지 않는 따옴표는 오류가 아님; 파서가 끝까지 소비\n\n## 7) 알 수 없는 `/...` 동작\n\n알 수 없는 슬래시 입력은 핵심 슬래시 로직에 의해 **거부되지 않습니다**.\n\n명령어가 확장/커스텀/파일 레이어에서 처리되지 않으면, `expandSlashCommand`는 원본 텍스트를 반환하고 리터럴 `/...` 프롬프트는 일반 프롬프트 템플릿 확장 및 LLM 전달을 통해 진행됩니다.\n\n인터랙티브 모드는 `InputController`에서 많은 빌트인 명령어를 별도로 직접 처리합니다(예: `/settings`, `/model`, `/mcp`, `/move`, `/exit`). 이들은 `session.prompt(...)`보다 먼저 소비되므로, 해당 경로에서는 파일 명령어 확장에 도달하지 않습니다.\n\n## 8) 스트리밍 중과 유휴 시의 차이점\n\n## 유휴 경로\n\n- `session.prompt(\"/x ...\")`는 명령어 파이프라인을 실행하고 명령어를 즉시 실행하거나 확장된 텍스트를 직접 전송합니다.\n\n## 스트리밍 경로 (`session.isStreaming === true`)\n\n- `prompt(...)`는 여전히 먼저 확장/커스텀/파일/템플릿 변환을 실행합니다.\n- 이후 `streamingBehavior`가 필요합니다:\n  - `\"steer\"` → 인터럽트 메시지 큐에 추가(`agent.steer`)\n  - `\"followUp\"` → 턴 후 메시지 큐에 추가(`agent.followUp`)\n- `streamingBehavior`가 생략되면 프롬프트에서 오류가 발생합니다.\n\n### 명령어별 중요한 스트리밍 동작\n\n- 확장 명령어는 스트리밍 중에도 즉시 실행됩니다(텍스트로 큐에 추가되지 않음).\n- `steer(...)`/`followUp(...)` 헬퍼 메서드는 확장 명령어를 거부합니다(`#throwIfExtensionCommand`). 동기적으로 실행해야 하는 핸들러를 위해 명령어 텍스트를 큐에 추가하지 않기 위함입니다.\n- 압축 큐 재실행은 `isKnownSlashCommand(...)`를 사용하여 큐에 추가된 항목을 `session.prompt(...)`로 재실행할지(알려진 슬래시 명령어의 경우), 아니면 raw steer/follow-up 메서드로 처리할지를 결정합니다.\n\n## 9) 오류 처리 및 실패 노출 지점\n\n- 프로바이더 로드 실패는 격리됩니다. 레지스트리는 경고를 수집하고 다른 프로바이더로 계속 진행합니다.\n- 유효하지 않은 슬래시 명령어 항목(이름/경로/콘텐츠 누락 또는 유효하지 않은 레벨)은 기능 유효성 검사에서 제거됩니다.\n- 프론트매터 파싱 실패:\n  - 네이티브 명령어: 치명적 파싱 오류가 전파됨\n  - 비네이티브 명령어: 경고 + 폴백 키/값 파싱\n- 확장/커스텀 명령어 핸들러 예외는 포착되어 확장 오류 채널(또는 확장 러너가 없는 커스텀 명령어의 경우 로거 폴백)을 통해 보고되며, 처리된 것으로 간주됩니다(의도치 않은 폴백 실행 없음).\n",
	"ko/runtime-tools/task-agent-discovery.md": "---\ntitle: 태스크 에이전트 탐색 및 선택\ndescription: 특수화된 서브에이전트 유형으로 작업을 라우팅하기 위한 태스크 에이전트 탐색 및 선택 로직\nsidebar:\n  order: 6\n  label: 태스크 에이전트 탐색\ni18n:\n  sourceHash: 8cf42457c672\n  translator: machine\n---\n\n# 태스크 에이전트 탐색 및 선택\n\n이 문서는 태스크 서브시스템이 에이전트 정의를 탐색하고, 여러 소스를 병합하며, 실행 시점에 요청된 에이전트를 해석하는 방법을 설명합니다.\n\n선행 순위, 잘못된 정의 처리, 에이전트를 사실상 사용 불가 상태로 만들 수 있는 스폰/깊이 제약을 포함하여 현재 구현된 런타임 동작을 다룹니다.\n\n## 구현 파일\n\n- [`src/task/discovery.ts`](../../packages/coding-agent/src/task/discovery.ts)\n- [`src/task/agents.ts`](../../packages/coding-agent/src/task/agents.ts)\n- [`src/task/types.ts`](../../packages/coding-agent/src/task/types.ts)\n- [`src/task/index.ts`](../../packages/coding-agent/src/task/index.ts)\n- [`src/task/commands.ts`](../../packages/coding-agent/src/task/commands.ts)\n- [`src/prompts/agents/task.md`](../../packages/coding-agent/src/prompts/agents/task.md)\n- [`src/prompts/tools/task.md`](../../packages/coding-agent/src/prompts/tools/task.md)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/config.ts`](../../packages/coding-agent/src/config.ts)\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts)\n\n---\n\n## 에이전트 정의 형태\n\n태스크 에이전트는 `AgentDefinition`으로 정규화됩니다 (`src/task/types.ts`):\n\n- `name`, `description`, `systemPrompt` (유효하게 로드된 에이전트에 필수)\n- 선택적 `tools`, `spawns`, `model`, `thinkingLevel`, `output`\n- `source`: `\"bundled\" | \"user\" | \"project\"`\n- 선택적 `filePath`\n\n파싱은 `parseAgentFields()`를 통해 프론트매터에서 이루어집니다 (`src/discovery/helpers.ts`):\n\n- `name` 또는 `description` 누락 => 유효하지 않음 (`null`), 호출자는 파싱 실패로 처리\n- `tools`는 CSV 또는 배열 허용; 제공된 경우 `submit_result`가 자동 추가됨\n- `spawns`는 `*`, CSV, 또는 배열 허용\n- 하위 호환성 동작: `spawns`가 없지만 `tools`에 `task`가 포함된 경우 `spawns`는 `*`이 됨\n- `output`은 불투명한 스키마 데이터로 그대로 전달됨\n\n## 번들 에이전트\n\n번들 에이전트는 텍스트 임포트를 사용하여 빌드 시점에 내장됩니다 (`src/task/agents.ts`).\n\n`EMBEDDED_AGENT_DEFS` 정의:\n\n- 프롬프트 파일에서 `explore`, `plan`, `designer`, `reviewer`\n- 공유 `task.md` 본문과 주입된 프론트매터에서 `task` 및 `quick_task`\n\n로딩 경로:\n\n1. `loadBundledAgents()`는 `parseAgent(..., \"bundled\", \"fatal\")`로 내장 마크다운을 파싱\n2. 결과는 메모리 내 캐시에 저장됨 (`bundledAgentsCache`)\n3. `clearBundledAgentsCache()`는 테스트 전용 캐시 초기화\n\n번들 파싱은 `level: \"fatal\"`을 사용하므로, 잘못된 번들 프론트매터는 예외를 발생시키고 탐색 전체를 실패하게 만들 수 있습니다.\n\n## 파일시스템 및 플러그인 탐색\n\n`discoverAgents(cwd, home)` (`src/task/discovery.ts`)는 번들 정의를 추가하기 전에 여러 위치의 에이전트를 병합합니다.\n\n### 탐색 입력\n\n1. `getConfigDirs(\"agents\", { project: false })`의 사용자 설정 에이전트 디렉터리\n2. `findAllNearestProjectConfigDirs(\"agents\", cwd)`의 가장 가까운 프로젝트 에이전트 디렉터리\n3. `listClaudePluginRoots(home)`의 Claude 플러그인 루트와 `agents/` 하위 디렉터리\n4. 번들 에이전트 (`loadBundledAgents()`)\n\n### 실제 소스 순서\n\n소스 패밀리 순서는 `getConfigDirs(\"\", { project: false })`에서 가져오며, 이는 `src/config.ts`의 `priorityList`에서 파생됩니다:\n\n1. `.xcsh`\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\n각 소스 패밀리에 대해 탐색 순서는 다음과 같습니다:\n\n1. 해당 소스의 가장 가까운 프로젝트 디렉터리 (발견된 경우)\n2. 해당 소스의 사용자 디렉터리\n\n모든 소스 패밀리 디렉터리 이후, 플러그인 `agents/` 디렉터리가 추가됩니다 (프로젝트 범위 플러그인 먼저, 그 다음 사용자 범위).\n\n번들 에이전트는 마지막에 추가됩니다.\n\n### 중요한 주의사항: 오래된 주석 vs 현재 코드\n\n`discovery.ts` 헤더 주석은 여전히 `.pi`를 언급하며 `.codex`/`.gemini`는 언급하지 않습니다. 실제 런타임 순서는 `src/config.ts`에 의해 결정되며 현재 `.xcsh`, `.claude`, `.codex`, `.gemini`를 사용합니다.\n\n## 병합 및 충돌 규칙\n\n탐색은 정확한 `agent.name`으로 선착순 중복 제거를 사용합니다:\n\n- `Set<string>`이 이미 확인된 이름을 추적합니다.\n- 로드된 에이전트는 디렉터리 순서로 평탄화되며 이름이 처음 확인된 경우에만 유지됩니다.\n- 번들 에이전트는 동일한 집합에 대해 필터링되며 아직 확인되지 않은 경우에만 추가됩니다.\n\n함의:\n\n- 동일한 소스 패밀리의 경우 프로젝트가 사용자를 재정의합니다.\n- 더 높은 우선순위의 소스 패밀리가 더 낮은 순위를 재정의합니다 (`.xcsh`가 `.claude`보다 앞).\n- 번들되지 않은 에이전트가 동일한 이름의 번들 에이전트를 재정의합니다.\n- 이름 매칭은 대소문자를 구분합니다 (`Task`와 `task`는 다름).\n- 하나의 디렉터리 내에서 마크다운 파일은 중복 제거 전에 파일 이름 사전순으로 읽힙니다.\n\n## 유효하지 않은/누락된 에이전트 파일 동작\n\n디렉터리별 (`loadAgentsFromDir`):\n\n- 읽을 수 없거나 누락된 디렉터리: 비어 있는 것으로 처리됨 (`readdir(...).catch(() => [])`)\n- 파일 읽기 또는 파싱 실패: 경고 기록, 파일 건너뜀\n- 파싱 경로는 `parseAgent(..., level: \"warn\")`을 사용\n\n프론트매터 실패 동작은 `parseFrontmatter`에서 옵니다:\n\n- `warn` 수준의 파싱 오류는 경고를 기록\n- 파서는 단순한 `key: value` 줄 파서로 폴백\n- 필수 필드가 여전히 누락된 경우 `parseAgentFields`가 실패하고, `AgentParsingError`가 발생하여 호출자에게 잡힘 (파일 건너뜀)\n\n결과적으로: 하나의 잘못된 커스텀 에이전트 파일이 다른 파일의 탐색을 중단시키지 않습니다.\n\n## 에이전트 조회 및 선택\n\n조회는 정확한 이름으로 선형 검색합니다:\n\n- `getAgent(agents, name)` => `agents.find(a => a.name === name)`\n\n태스크 실행 시 (`TaskTool.execute`):\n\n1. 호출 시점에 에이전트가 재탐색됨 (`discoverAgents(this.session.cwd)`)\n2. 요청된 `params.agent`가 `getAgent`를 통해 해석됨\n3. 에이전트 누락 시 즉각적인 도구 응답 반환:\n   - `Unknown agent \"...\". Available: ...`\n   - 서브프로세스 실행 없음\n\n### 설명 vs 실행 시점 탐색\n\n`TaskTool.create()`는 초기화 시점에 탐색 결과로 도구 설명을 빌드합니다 (`buildDescription`).\n\n`execute()`는 에이전트를 다시 탐색합니다. 따라서 세션 중간에 에이전트 파일이 변경된 경우 런타임 집합이 이전 도구 설명에 나열된 내용과 다를 수 있습니다.\n\n## 구조화된 출력 가드레일 및 스키마 우선순위\n\n`TaskTool.execute`의 런타임 출력 스키마 우선순위:\n\n1. 에이전트 프론트매터 `output`\n2. 태스크 호출 `params.schema`\n3. 부모 세션 `outputSchema`\n\n(`effectiveOutputSchema = effectiveAgent.output ?? outputSchema ?? this.session.outputSchema`)\n\n`src/prompts/tools/task.md`의 프롬프트 시점 가드레일 텍스트는 구조화 출력 에이전트(`explore`, `reviewer`)에 대한 불일치 동작에 대해 경고합니다: 산문에서의 출력 형식 지침이 내장 스키마와 충돌하여 `null` 출력을 생성할 수 있습니다.\n\n이것은 `discoverAgents`의 하드 런타임 유효성 검사 로직이 아닌 지침입니다.\n\n## 명령어 탐색 상호작용\n\n`src/task/commands.ts`는 워크플로우 명령어(에이전트 정의가 아님)를 위한 병렬 인프라이지만, 동일한 전반적인 패턴을 따릅니다:\n\n- 먼저 기능 제공자에서 탐색\n- 선착순으로 이름별 중복 제거\n- 아직 확인되지 않은 경우 번들 명령어 추가\n- `getCommand`를 통한 정확한 이름 조회\n\n`src/task/index.ts`에서 명령어 헬퍼는 에이전트 탐색 헬퍼와 함께 재내보내집니다. 에이전트 탐색 자체는 런타임에 명령어 탐색에 의존하지 않습니다.\n\n## 탐색 이후의 가용성 제약\n\n에이전트는 탐색 가능하더라도 실행 가드레일로 인해 여전히 실행할 수 없는 상태일 수 있습니다.\n\n### 부모 스폰 정책\n\n`TaskTool.execute`는 `session.getSessionSpawns()`를 확인합니다:\n\n- `\"*\"` => 모두 허용\n- `\"\"` => 모두 거부\n- CSV 목록 => 나열된 이름만 허용\n\n거부된 경우: 즉각적인 `Cannot spawn '...'. Allowed: ...` 응답.\n\n### 차단된 자기 재귀 환경 가드\n\n`PI_BLOCKED_AGENT`는 도구 생성 시점에 읽힙니다. 요청이 일치하는 경우 실행은 재귀 방지 메시지와 함께 거부됩니다.\n\n### 재귀 깊이 게이팅 (자식 세션 내 태스크 도구 가용성)\n\n`runSubprocess`에서 (`src/task/executor.ts`):\n\n- 깊이는 `taskDepth`에서 계산됨\n- `task.maxRecursionDepth`가 차단 기준을 제어\n- 최대 깊이 도달 시:\n  - `task` 도구가 자식 도구 목록에서 제거됨\n  - 자식 `spawns` 환경이 비어 있게 설정됨\n\n따라서 에이전트 정의에 `spawns`가 포함되어 있더라도 더 깊은 수준에서는 추가 태스크를 스폰할 수 없습니다.\n\n## 플랜 모드 주의사항 (현재 구현)\n\n`TaskTool.execute`는 플랜 모드를 위한 `effectiveAgent`를 계산하지만 (플랜 모드 프롬프트 앞에 추가, 읽기 전용 도구 서브셋 강제, 스폰 초기화), `runSubprocess`는 `effectiveAgent` 대신 `agent`로 호출됩니다.\n\n현재 효과:\n\n- 모델 재정의 / 사고 수준 / 출력 스키마는 `effectiveAgent`에서 파생됨\n- `effectiveAgent`의 시스템 프롬프트 및 도구/스폰 제한은 이 호출 경로에서 전달되지 않음\n\n이것은 플랜 모드 동작 기대치를 읽을 때 알아두어야 할 구현상의 주의사항입니다.\n",
	"ko/sessions/compaction.md": "---\ntitle: 압축 및 브랜치 요약\ndescription: 장기 세션을 위한 컨텍스트 윈도우 압축 및 브랜치 요약 생성.\nsidebar:\n  order: 5\n  label: 압축\ni18n:\n  sourceHash: dae425a900d8\n  translator: machine\n---\n\n# 압축 및 브랜치 요약\n\n압축(Compaction)과 브랜치 요약(Branch Summary)은 이전 작업 컨텍스트를 잃지 않으면서 장기 세션을 사용 가능한 상태로 유지하는 두 가지 메커니즘입니다.\n\n- **압축**은 현재 브랜치의 오래된 히스토리를 요약으로 재작성합니다.\n- **브랜치 요약**은 `/tree` 탐색 중 포기된 브랜치 컨텍스트를 캡처합니다.\n\n두 메커니즘 모두 세션 항목으로 지속되며, LLM 입력 재구성 시 사용자-컨텍스트 메시지로 변환됩니다.\n\n## 주요 구현 파일\n\n- `src/session/compaction/compaction.ts`\n- `src/session/compaction/branch-summarization.ts`\n- `src/session/compaction/pruning.ts`\n- `src/session/compaction/utils.ts`\n- `src/session/session-manager.ts`\n- `src/session/agent-session.ts`\n- `src/session/messages.ts`\n- `src/extensibility/hooks/types.ts`\n- `src/config/settings-schema.ts`\n\n## 세션 항목 모델\n\n압축과 브랜치 요약은 일반 assistant/user 메시지가 아닌 일급(first-class) 세션 항목입니다.\n\n- `CompactionEntry`\n  - `type: \"compaction\"`\n  - `summary`, 선택적 `shortSummary`\n  - `firstKeptEntryId` (압축 경계)\n  - `tokensBefore`\n  - 선택적 `details`, `preserveData`, `fromExtension`\n- `BranchSummaryEntry`\n  - `type: \"branch_summary\"`\n  - `fromId`, `summary`\n  - 선택적 `details`, `fromExtension`\n\n컨텍스트가 재구성될 때(`buildSessionContext`):\n\n1. 활성 경로의 최신 압축이 하나의 `compactionSummary` 메시지로 변환됩니다.\n2. `firstKeptEntryId`부터 압축 지점까지의 유지된 항목들이 재포함됩니다.\n3. 경로상의 이후 항목들이 추가됩니다.\n4. `branch_summary` 항목들이 `branchSummary` 메시지로 변환됩니다.\n5. `custom_message` 항목들이 `custom` 메시지로 변환됩니다.\n\n해당 커스텀 역할들은 이후 `convertToLlm()`에서 정적 템플릿을 사용하여 LLM 대향(facing) 사용자 메시지로 변환됩니다:\n\n- `prompts/compaction/compaction-summary-context.md`\n- `prompts/compaction/branch-summary-context.md`\n\n## 압축 파이프라인\n\n### 트리거\n\n압축은 세 가지 방식으로 실행될 수 있습니다:\n\n1. **수동**: `/compact [instructions]`가 `AgentSession.compact(...)`를 호출합니다.\n2. **자동 오버플로우 복구**: 컨텍스트 오버플로우와 일치하는 assistant 오류 이후.\n3. **자동 임계값 압축**: 컨텍스트가 임계값을 초과하는 성공적인 턴 이후.\n\n### 압축 구조 (시각적 표현)\n\n```text\n압축 전:\n\n  entry:  0     1     2     3      4     5     6      7      8     9\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┘\n                └────────┬───────┘ └──────────────┬──────────────┘\n               messagesToSummarize            kept messages\n                                   ↑\n                          firstKeptEntryId (entry 4)\n\n압축 후 (새 항목 추가됨):\n\n  entry:  0     1     2     3      4     5     6      7      8     9      10\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┬─────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │ cmp │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┴─────┘\n               └──────────┬──────┘ └──────────────────────┬───────────────────┘\n                 not sent to LLM                    sent to LLM\n                                                         ↑\n                                              starts from firstKeptEntryId\n\nLLM이 보는 내용:\n\n  ┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐\n  │ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │\n  └────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘\n       ↑         ↑      └─────────────────┬────────────────┘\n    prompt   from cmp          messages from firstKeptEntryId\n```\n\n### 오버플로우 재시도 vs 임계값 압축\n\n두 가지 자동 경로는 의도적으로 다르게 설계되었습니다:\n\n- **오버플로우 재시도 압축**\n  - 트리거: 현재 모델의 assistant 오류가 컨텍스트 오버플로우로 감지됨.\n  - 실패한 assistant 오류 메시지가 재시도 전 활성 에이전트 상태에서 제거됨.\n  - 자동 압축이 `reason: \"overflow\"` 및 `willRetry: true`로 실행됨.\n  - 성공 시, 압축 후 에이전트가 자동으로 계속 진행됨 (`agent.continue()`).\n\n- **임계값 압축**\n  - 트리거: `contextTokens > contextWindow - compaction.reserveTokens`.\n  - `reason: \"threshold\"` 및 `willRetry: false`로 실행됨.\n  - 성공 시, `compaction.autoContinue !== false`인 경우 합성 프롬프트를 주입함:\n    - `\"Continue if you have next steps.\"`\n\n### 압축 전 정리(Pruning)\n\n압축 검사 전, 도구 결과 정리가 실행될 수 있습니다 (`pruneToolOutputs`).\n\n기본 정리 정책:\n\n- 최신 `40_000` 도구 출력 토큰 보호.\n- 최소 `20_000` 총 예상 절감량 필요.\n- `skill` 또는 `read`의 도구 결과는 절대 정리하지 않음.\n\n정리된 도구 결과는 다음으로 대체됩니다:\n\n- `[Output truncated - N tokens]`\n\n정리로 인해 항목이 변경되면, 압축 결정 전 세션 저장소가 재작성되고 에이전트 메시지 상태가 새로고침됩니다.\n\n### 경계 및 절단 지점 로직\n\n`prepareCompaction()`은 마지막 압축 항목(있는 경우) 이후의 항목만 고려합니다.\n\n1. 이전 압축 인덱스를 찾습니다.\n2. `boundaryStart = prevCompactionIndex + 1`을 계산합니다.\n3. 가용 시 측정된 사용량 비율을 사용하여 `keepRecentTokens`를 조정합니다.\n4. 경계 윈도우에서 `findCutPoint()`를 실행합니다.\n\n유효한 절단 지점에는 다음이 포함됩니다:\n\n- 역할이 `user`, `assistant`, `bashExecution`, `hookMessage`, `branchSummary`, `compactionSummary`인 메시지 항목\n- `custom_message` 항목\n- `branch_summary` 항목\n\n절대 규칙: `toolResult`에서는 절대 절단하지 않습니다.\n\n절단 지점 바로 앞에 메시지가 아닌 메타데이터 항목(`model_change`, `thinking_level_change`, 레이블 등)이 있는 경우, 메시지 또는 압축 경계에 도달할 때까지 절단 인덱스를 뒤로 이동하여 유지 영역으로 포함시킵니다.\n\n### 분할 턴 처리\n\n절단 지점이 사용자 턴 시작 지점이 아닌 경우, 압축은 이를 분할 턴으로 처리합니다.\n\n턴 시작 감지는 다음을 사용자 턴 경계로 처리합니다:\n\n- `message.role === \"user\"`\n- `message.role === \"bashExecution\"`\n- `custom_message` 항목\n- `branch_summary` 항목\n\n분할 턴 압축은 두 개의 요약을 생성합니다:\n\n1. 히스토리 요약 (`messagesToSummarize`)\n2. 턴 접두사 요약 (`turnPrefixMessages`)\n\n최종 저장된 요약은 다음과 같이 병합됩니다:\n\n```markdown\n<history summary>\n\n---\n\n**Turn Context (split turn):**\n\n<turn prefix summary>\n```\n\n### 요약 생성\n\n`compact(...)`는 직렬화된 대화 텍스트로부터 요약을 빌드합니다:\n\n1. `convertToLlm()`을 통해 메시지를 변환합니다.\n2. `serializeConversation()`으로 직렬화합니다.\n3. `<conversation>...</conversation>`으로 감쌉니다.\n4. 선택적으로 `<previous-summary>...</previous-summary>`를 포함합니다.\n5. 선택적으로 훅 컨텍스트를 `<additional-context>` 목록으로 주입합니다.\n6. `SUMMARIZATION_SYSTEM_PROMPT`로 요약 프롬프트를 실행합니다.\n\n프롬프트 선택:\n\n- 최초 압축: `compaction-summary.md`\n- 이전 요약이 있는 반복 압축: `compaction-update-summary.md`\n- 분할 턴 2차 패스: `compaction-turn-prefix.md`\n- 짧은 UI 요약: `compaction-short-summary.md`\n\n원격 요약 모드:\n\n- `compaction.remoteEndpoint`가 설정된 경우, 압축은 다음을 POST합니다:\n  - `{ systemPrompt, prompt }`\n- 최소 `{ summary }`를 포함하는 JSON을 기대합니다.\n\n### 요약의 파일 작업 컨텍스트\n\n압축은 assistant 도구 호출을 사용하여 누적 파일 활동을 추적합니다:\n\n- `read(path)` → 읽기 집합\n- `write(path)` → 수정 집합\n- `edit(path)` → 수정 집합\n\n누적 동작:\n\n- 이전 압축 세부 정보는 이전 항목이 pi 생성(`fromExtension !== true`)된 경우에만 포함합니다.\n- 분할 턴에서는 턴 접두사 파일 작업도 포함합니다.\n- `readFiles`는 수정된 파일을 제외합니다.\n\n요약 텍스트에는 프롬프트 템플릿을 통해 파일 태그가 추가됩니다:\n\n```xml\n<read-files>\n...\n</read-files>\n<modified-files>\n...\n</modified-files>\n```\n\n### 지속 및 재로드\n\n요약 생성(또는 훅 제공 요약) 후, 에이전트 세션은:\n\n1. `appendCompaction(...)`으로 `CompactionEntry`를 추가합니다.\n2. `buildSessionContext()`를 통해 컨텍스트를 재구성합니다.\n3. 라이브 에이전트 메시지를 재구성된 컨텍스트로 교체합니다.\n4. `session_compact` 훅 이벤트를 발생시킵니다.\n\n## 브랜치 요약 파이프라인\n\n브랜치 요약은 토큰 오버플로우가 아닌 트리 탐색에 연결됩니다.\n\n### 트리거\n\n`navigateTree(...)` 중:\n\n1. `collectEntriesForBranchSummary(...)`를 사용하여 이전 리프에서 공통 조상까지의 포기된 항목을 계산합니다.\n2. 호출자가 요약을 요청한 경우(`options.summarize`), 리프를 전환하기 전에 요약을 생성합니다.\n3. 요약이 존재하는 경우, `branchWithSummary(...)`를 사용하여 탐색 대상에 첨부합니다.\n\n운영상 이는 `branchSummary.enabled`가 활성화된 경우 `/tree` 플로우에 의해 일반적으로 구동됩니다.\n\n### 브랜치 전환 구조 (시각적 표현)\n\n```text\n탐색 전 트리:\n\n         ┌─ B ─ C ─ D (old leaf, being abandoned)\n    A ───┤\n         └─ E ─ F (target)\n\n공통 조상: A\n요약할 항목: B, C, D\n\n요약과 함께 탐색 후:\n\n         ┌─ B ─ C ─ D ─ [summary of B,C,D]\n    A ───┤\n         └─ E ─ F (new leaf)\n```\n\n### 준비 및 토큰 예산\n\n`generateBranchSummary(...)`는 예산을 다음과 같이 계산합니다:\n\n- `tokenBudget = model.contextWindow - branchSummary.reserveTokens`\n\n`prepareBranchEntries(...)`는 이후:\n\n1. 1차 패스: pi 생성 `branch_summary` 세부 정보를 포함하여 요약된 모든 항목에서 누적 파일 작업을 수집합니다.\n2. 2차 패스: 최신→최구 순으로 탐색하며 토큰 예산에 도달할 때까지 메시지를 추가합니다.\n3. 최근 컨텍스트 보존을 선호합니다.\n4. 연속성을 위해 예산 경계 근처의 큰 요약 항목을 여전히 포함할 수 있습니다.\n\n압축 항목은 브랜치 요약 입력 중 메시지(`compactionSummary`)로 포함됩니다.\n\n### 요약 생성 및 지속\n\n브랜치 요약:\n\n1. 선택된 메시지를 변환하고 직렬화합니다.\n2. `<conversation>`으로 감쌉니다.\n3. 커스텀 지침이 제공된 경우 이를 사용하고, 그렇지 않으면 `branch-summary.md`를 사용합니다.\n4. `SUMMARIZATION_SYSTEM_PROMPT`로 요약 모델을 호출합니다.\n5. `branch-summary-preamble.md`를 앞에 추가합니다.\n6. 파일 작업 태그를 추가합니다.\n\n결과는 선택적 세부 정보(`readFiles`, `modifiedFiles`)와 함께 `BranchSummaryEntry`로 저장됩니다.\n\n## 확장 및 훅 접점\n\n### `session_before_compact`\n\n압축 전 훅.\n\n다음을 수행할 수 있습니다:\n\n- 압축 취소 (`{ cancel: true }`)\n- 완전한 커스텀 압축 페이로드 제공 (`{ compaction: CompactionResult }`)\n\n### `session.compacting`\n\n기본 압축을 위한 프롬프트/컨텍스트 커스터마이징 훅.\n\n다음을 반환할 수 있습니다:\n\n- `prompt` (기본 요약 프롬프트 재정의)\n- `context` (`<additional-context>`에 주입되는 추가 컨텍스트 줄)\n- `preserveData` (압축 항목에 저장됨)\n\n### `session_compact`\n\n저장된 `compactionEntry` 및 `fromExtension` 플래그와 함께 압축 후 알림.\n\n### `session_before_tree`\n\n기본 브랜치 요약 생성 전 트리 탐색 시 실행됩니다.\n\n다음을 수행할 수 있습니다:\n\n- 탐색 취소\n- 사용자가 요약을 요청한 경우 사용되는 커스텀 `{ summary: { summary, details } }` 제공\n\n### `session_tree`\n\n새/이전 리프와 선택적 요약 항목을 노출하는 탐색 후 이벤트.\n\n## 런타임 동작 및 실패 의미론\n\n- 수동 압축은 현재 에이전트 작업을 먼저 중단합니다.\n- `abortCompaction()`은 수동 및 자동 압축 컨트롤러를 모두 취소합니다.\n- 자동 압축은 UI/상태 업데이트를 위해 시작/종료 세션 이벤트를 발생시킵니다.\n- 자동 압축은 여러 모델 후보를 시도하고 일시적 실패를 재시도할 수 있습니다.\n- 오버플로우 오류는 압축에 의해 처리되므로 일반 재시도 경로에서 제외됩니다.\n- 자동 압축이 실패하는 경우:\n  - 오버플로우 경로는 `Context overflow recovery failed: ...`를 발생시킵니다.\n  - 임계값 경로는 `Auto-compaction failed: ...`를 발생시킵니다.\n- 브랜치 요약은 중단 신호(예: Escape)를 통해 취소될 수 있으며, 취소/중단된 탐색 결과를 반환합니다.\n\n## 설정 및 기본값\n\n`settings-schema.ts`에서:\n\n- `compaction.enabled` = `true`\n- `compaction.reserveTokens` = `16384`\n- `compaction.keepRecentTokens` = `20000`\n- `compaction.autoContinue` = `true`\n- `compaction.remoteEndpoint` = `undefined`\n- `branchSummary.enabled` = `false`\n- `branchSummary.reserveTokens` = `16384`\n\n이 값들은 런타임 시 `AgentSession` 및 압축/브랜치 요약 모듈에 의해 사용됩니다.\n",
	"ko/sessions/handoff-generation-pipeline.md": "---\ntitle: 핸드오프 생성 파이프라인\ndescription: 팀 협업을 위한 이식 가능한 세션 요약 생성을 위한 핸드오프 생성 파이프라인.\nsidebar:\n  order: 8\n  label: 핸드오프 파이프라인\ni18n:\n  sourceHash: 03666084b5ac\n  translator: machine\n---\n\n# `/handoff` 생성 파이프라인\n\n이 문서는 코딩 에이전트가 현재 `/handoff`를 구현하는 방식, 즉 트리거 경로, 생성 프롬프트, 완료 캡처, 세션 전환, 컨텍스트 재주입에 대해 설명합니다.\n\n## 범위\n\n다루는 내용:\n\n- 인터랙티브 `/handoff` 명령 디스패치\n- `AgentSession.handoff()` 라이프사이클 및 상태 전환\n- 어시스턴트 출력에서 핸드오프 출력이 캡처되는 방식\n- 구/신 세션이 핸드오프 데이터를 다르게 유지하는 방식\n- 성공, 취소, 실패 시 UI 동작\n\n다루지 않는 내용:\n\n- 일반적인 트리 탐색/브랜치 내부 구조\n- 핸드오프가 아닌 세션 명령(`/new`, `/fork`, `/resume`)\n\n## 구현 파일\n\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n\n## 트리거 경로\n\n1. `/handoff`는 빌트인 슬래시 명령 메타데이터(`slash-commands.ts`)에서 선택적 인라인 힌트 `[focus instructions]`와 함께 선언됩니다.\n2. 인터랙티브 입력 처리(`InputController`)에서 `/handoff` 또는 `/handoff ...`와 일치하는 제출 텍스트는 일반 프롬프트 제출 전에 가로채집니다.\n3. 에디터가 초기화되고 `handleHandoffCommand(customInstructions?)`가 호출됩니다.\n4. `CommandController.handleHandoffCommand`는 현재 항목을 사용하여 사전 점검 가드를 수행합니다:\n   - `type === \"message\"` 항목 수를 셉니다.\n   - 개수가 `< 2`이면, `Nothing to hand off (no messages yet)` 경고를 표시하고 반환합니다.\n\n동일한 최소 콘텐츠 가드가 `AgentSession.handoff()` 내부에도 존재하며, 위반 시 예외를 발생시킵니다. 이는 UI와 세션 레이어 양쪽에서 안전성을 중복으로 보장합니다.\n\n## 엔드투엔드 라이프사이클\n\n### 1) 핸드오프 생성 시작\n\n`AgentSession.handoff(customInstructions?)`:\n\n- 현재 브랜치 항목 읽기(`sessionManager.getBranch()`)\n- 최소 메시지 수 유효성 검사(`>= 2`)\n- `#handoffAbortController` 생성\n- 구조화된 핸드오프 문서(`Goal`, `Constraints & Preferences`, `Progress`, `Key Decisions`, `Critical Context`, `Next Steps`)를 요청하는 고정된 인라인 프롬프트 작성\n- 커스텀 지시가 제공된 경우 `Additional focus: ...` 추가\n\n프롬프트는 다음을 통해 전송됩니다:\n\n```ts\nawait this.prompt(handoffPrompt, { expandPromptTemplates: false });\n```\n\n`expandPromptTemplates: false`는 이 내부 지시 페이로드에 대한 슬래시/프롬프트 템플릿 확장을 방지합니다.\n\n### 2) 완료 캡처\n\n프롬프트 전송 전에 `handoff()`는 세션 이벤트를 구독하고 `agent_end`를 기다립니다.\n\n`agent_end` 시, 가장 최근 `assistant` 메시지를 역방향으로 스캔하여 에이전트 상태에서 핸드오프 텍스트를 추출한 후, `type === \"text\"`인 모든 `content` 블록을 `\\n`으로 연결합니다.\n\n중요한 추출 가정:\n\n- 텍스트 블록만 사용됩니다; 비텍스트 콘텐츠는 무시됩니다.\n- 최신 어시스턴트 메시지가 핸드오프 생성에 해당한다고 가정합니다.\n- 마크다운 섹션을 파싱하거나 형식 준수 여부를 검증하지 않습니다.\n- 어시스턴트 출력에 텍스트 블록이 없으면 핸드오프가 누락된 것으로 처리됩니다.\n\n### 3) 취소 확인\n\n다음 조건 중 하나라도 충족되면 `handoff()`는 `undefined`를 반환합니다:\n\n- 캡처된 핸드오프 텍스트가 없거나,\n- `#handoffAbortController.signal.aborted`가 true인 경우\n\n`finally`에서 항상 `#handoffAbortController`를 초기화합니다.\n\n### 4) 새 세션 생성\n\n텍스트가 캡처되었고 중단되지 않은 경우:\n\n1. 현재 세션 작성자 플러시(`sessionManager.flush()`)\n2. 새 세션 시작(`sessionManager.newSession()`)\n3. 인메모리 에이전트 상태 초기화(`agent.reset()`)\n4. `agent.sessionId`를 새 세션 id에 재바인딩\n5. 큐에 쌓인 컨텍스트 배열 초기화(`#steeringMessages`, `#followUpMessages`, `#pendingNextTurnMessages`)\n6. 할 일 리마인더 카운터 초기화\n\n`newSession()`은 새 헤더와 빈 항목 목록을 생성합니다(리프는 `null`로 초기화). 핸드오프 경로에서는 `parentSession`이 전달되지 않습니다.\n\n### 5) 핸드오프 컨텍스트 주입\n\n생성된 핸드오프 문서는 래핑되어 새 세션에 `custom_message` 항목으로 추가됩니다:\n\n```text\n<handoff-context>\n...handoff text...\n</handoff-context>\n\nThe above is a handoff document from a previous session. Use this context to continue the work seamlessly.\n```\n\n삽입 호출:\n\n```ts\nthis.sessionManager.appendCustomMessageEntry(\"handoff\", handoffContent, true);\n```\n\n의미론:\n\n- `customType`: `\"handoff\"`\n- `display`: `true` (TUI 재빌드 시 표시됨)\n- 항목 유형: `custom_message` (LLM 컨텍스트에 참여)\n\n### 6) 활성 에이전트 컨텍스트 재빌드\n\n주입 후:\n\n1. `sessionManager.buildSessionContext()`가 현재 리프에 대한 메시지 목록을 해석\n2. `agent.replaceMessages(sessionContext.messages)`가 주입된 핸드오프 메시지를 활성 컨텍스트로 설정\n3. 메서드가 `{ document: handoffText }` 반환\n\n이 시점에서 새 세션의 활성 LLM 컨텍스트에는 이전 트랜스크립트가 아닌 주입된 핸드오프 메시지가 포함됩니다.\n\n## 지속성 모델: 이전 세션 vs 새 세션\n\n### 이전 세션\n\n생성 중에는 일반 메시지 지속성이 활성 상태로 유지됩니다. 어시스턴트 핸드오프 응답은 `message_end` 시 일반 `message` 항목으로 유지됩니다.\n\n결과: 원본 세션에는 생성된 핸드오프가 기록 트랜스크립트의 일부로 표시됩니다.\n\n### 새 세션\n\n세션 재설정 후, 핸드오프는 `customType: \"handoff\"`를 가진 `custom_message`로 유지됩니다.\n\n`buildSessionContext()`는 이 항목을 `createCustomMessage(...)`를 통해 런타임 커스텀/사용자 컨텍스트 메시지로 변환하므로, 새 세션의 향후 프롬프트에 포함됩니다.\n\n## 컨트롤러/UI 동작\n\n`CommandController.handleHandoffCommand` 동작:\n\n- `await session.handoff(customInstructions)` 호출\n- 결과가 `undefined`인 경우: `showError(\"Handoff cancelled\")`\n- 성공 시:\n  - `rebuildChatFromMessages()` (주입된 핸드오프를 포함한 새 세션 컨텍스트 로드)\n  - 상태 표시줄 및 에디터 상단 테두리 무효화\n  - 할 일 항목 재로드\n  - 성공 채팅 라인 추가: `New session started with handoff context`\n- 예외 발생 시:\n  - 메시지가 `\"Handoff cancelled\"`이거나 오류 이름이 `AbortError`인 경우: `showError(\"Handoff cancelled\")`\n  - 그 외: `showError(\"Handoff failed: <message>\")`\n- 마지막에 렌더링 요청\n\n## 취소 의미론 (현재 동작)\n\n### 세션 수준 취소 기본 요소\n\n`AgentSession`이 노출하는 것:\n\n- `abortHandoff()` → `#handoffAbortController` 중단\n- `isGeneratingHandoff` → 컨트롤러가 존재하는 동안 true\n\n이 중단 경로가 사용되면, 핸드오프 구독자는 `Error(\"Handoff cancelled\")`로 거부하고, 명령 컨트롤러는 이를 취소 UI에 매핑합니다.\n\n### 인터랙티브 `/handoff` 경로 제한\n\n현재 인터랙티브 컨트롤러 배선에서, `/handoff`는 `abortHandoff()`를 호출하는 전용 Escape 핸들러를 설치하지 않습니다(일시적으로 `editor.onEscape`를 재정의하는 압축/브랜치 요약 경로와는 달리).\n\n실질적인 영향:\n\n- 세션 수준 취소 지원은 있지만, `/handoff` 명령 경로에는 핸드오프 전용 키바인딩 훅이 없습니다.\n- 더 광범위한 에이전트 중단 경로를 통해 사용자 중단이 발생할 수 있지만, 이는 `abortHandoff()`가 사용하는 명시적 취소 채널과 동일하지 않습니다.\n\n## 중단됨 vs 실패한 핸드오프\n\n현재 UI 분류:\n\n- **중단됨/취소됨**\n  - `abortHandoff()` 경로가 `\"Handoff cancelled\"`를 트리거하거나,\n  - `AbortError` 발생\n  - UI에 `Handoff cancelled` 표시\n\n- **실패**\n  - `handoff()` / 프롬프트 파이프라인에서 발생한 다른 오류 (모델/API 유효성 검사 오류, 런타임 예외 등)\n  - UI에 `Handoff failed: ...` 표시\n\n추가 세부 사항: 생성이 완료되었지만 텍스트가 추출되지 않은 경우, `handoff()`는 `undefined`를 반환하고 컨트롤러는 현재 **실패**가 아닌 **취소됨**으로 보고합니다.\n\n## 짧은 세션 및 최소 콘텐츠 가드레일\n\n두 가지 가드가 저신호 핸드오프를 방지합니다:\n\n- UI 레이어(`handleHandoffCommand`): `< 2` 메시지 항목에 대해 경고하고 조기 반환\n- 세션 레이어(`handoff()`): 동일한 조건을 오류로 발생\n\n이는 빈/거의 빈 핸드오프 컨텍스트로 새 세션이 생성되는 것을 방지합니다.\n\n## 상태 전환 요약\n\n고수준 상태 흐름:\n\n1. 인터랙티브 슬래시 명령 가로채기\n2. 사전 점검 메시지 수 가드\n3. `#handoffAbortController` 생성(`isGeneratingHandoff = true`)\n4. 내부 핸드오프 프롬프트 제출(일반 어시스턴트 생성으로 채팅에 표시)\n5. `agent_end` 시 마지막 어시스턴트 텍스트 추출\n6. 누락/중단된 경우 → `undefined` 반환 또는 취소 오류 경로\n7. 존재하는 경우:\n   - 이전 세션 플러시\n   - 새 빈 세션 생성\n   - 런타임 큐/카운터 초기화\n   - `custom_message(handoff)` 추가\n   - 활성 에이전트 메시지 재빌드 및 교체\n8. 컨트롤러가 채팅 UI를 재빌드하고 성공 알림\n9. `#handoffAbortController` 초기화(`isGeneratingHandoff = false`)\n\n## 알려진 가정 및 제한 사항\n\n- 핸드오프 추출은 휴리스틱 방식입니다: \"마지막 어시스턴트 텍스트 블록\"; 구조적 유효성 검사 없음.\n- 생성된 마크다운이 요청된 섹션 형식을 따르는지에 대한 엄격한 검사가 없습니다.\n- 추출된 텍스트가 없는 경우 컨트롤러 UX에서 취소로 보고됩니다.\n- `/handoff` 인터랙티브 흐름에는 현재 전용 Escape→`abortHandoff()` 바인딩이 없습니다.\n- 새 세션 계보 메타데이터(`parentSession`)는 이 경로에서 설정되지 않습니다.\n",
	"ko/sessions/memory.md": "---\ntitle: 자율 메모리\ndescription: '세션 간 사용자 선호도, 프로젝트 컨텍스트 및 피드백을 유지하기 위한 자율 메모리 시스템.'\nsidebar:\n  order: 7\n  label: 자율 메모리\ni18n:\n  sourceHash: 2aa9f516aa1e\n  translator: machine\n---\n\n# 자율 메모리\n\n활성화하면, 에이전트가 과거 세션에서 지속적인 지식을 자동으로 추출하고 각 새 세션에 압축된 요약을 주입합니다. 시간이 지남에 따라 프로젝트 범위의 메모리 저장소 — 기술적 결정, 반복 워크플로, 주의사항 — 를 구축하여 수동 작업 없이 다음 세션으로 이어집니다.\n\n기본적으로 비활성화되어 있습니다. `/settings` 또는 `config.yml`을 통해 활성화하세요:\n\n```yaml\nmemories:\n  enabled: true\n```\n\n## 사용법\n\n### 주입되는 내용\n\n세션 시작 시, 현재 프로젝트에 대한 메모리 요약이 존재하면 시스템 프롬프트에 **Memory Guidance** 블록으로 주입됩니다. 에이전트는 다음과 같이 지시받습니다:\n\n- 메모리를 휴리스틱 컨텍스트로 취급 — 프로세스와 이전 결정에는 유용하지만, 현재 저장소 상태에 대해 권위적이지 않음.\n- 메모리가 계획을 변경할 때 메모리 아티팩트 경로를 인용하고, 행동하기 전에 현재 저장소 증거와 함께 확인.\n- 저장소 상태와 사용자 지시가 메모리와 충돌할 때 이를 우선시하며, 충돌하는 메모리는 오래된 것으로 취급.\n\n### 메모리 아티팩트 읽기\n\n에이전트는 `read` 도구와 함께 `memory://` URL을 사용하여 메모리 파일을 직접 읽을 수 있습니다:\n\n| URL | 내용 |\n|---|---|\n| `memory://root` | 시작 시 주입되는 압축 요약 |\n| `memory://root/MEMORY.md` | 전체 장기 메모리 문서 |\n| `memory://root/skills/<name>/SKILL.md` | 생성된 스킬 플레이북 |\n\n### `/memory` 슬래시 명령어\n\n| 하위 명령어 | 효과 |\n|---|---|\n| `view` | 현재 메모리 주입 페이로드 표시 |\n| `clear` / `reset` | 모든 메모리 데이터 및 생성된 아티팩트 삭제 |\n| `enqueue` / `rebuild` | 다음 시작 시 통합 실행 강제 |\n\n## 작동 방식\n\n메모리는 시작 시 또는 슬래시 명령어를 통해 수동으로 트리거되는 백그라운드 파이프라인에 의해 구축됩니다.\n\n**1단계 — 세션별 추출:** 마지막 처리 이후 변경된 각 과거 세션에 대해, 모델이 세션 기록을 읽고 지속적인 신호를 추출합니다: 기술적 결정, 제약 조건, 해결된 실패, 반복 워크플로. 너무 최근이거나, 너무 오래되었거나, 현재 활성 상태인 세션은 건너뜁니다. 각 추출은 해당 세션에 대한 원시 메모리 블록과 짧은 요약을 생성합니다.\n\n**2단계 — 통합:** 추출 후, 두 번째 모델 패스가 모든 세션별 추출을 읽고 디스크에 기록되는 세 가지 출력을 생성합니다:\n\n- `MEMORY.md` — 큐레이션된 장기 메모리 문서\n- `memory_summary.md` — 세션 시작 시 주입되는 압축 텍스트\n- `skills/` — 재사용 가능한 절차적 플레이북, 각각 자체 하위 디렉터리에 저장\n\n2단계는 여러 프로세스가 동시에 시작될 때 중복 실행을 방지하기 위해 리스를 사용합니다. 이전 실행의 오래된 스킬 디렉터리는 자동으로 정리됩니다.\n\n모든 출력은 디스크에 기록되기 전에 시크릿 스캔을 거칩니다.\n\n### 추출 동작\n\n메모리 추출 및 통합 동작은 전적으로 `src/prompts/memories/`의 정적 프롬프트 파일에 의해 구동됩니다.\n\n| 파일 | 용도 | 변수 |\n|---|---|---|\n| `stage_one_system.md` | 세션별 추출을 위한 시스템 프롬프트 | — |\n| `stage_one_input.md` | 세션 콘텐츠를 래핑하는 사용자 턴 템플릿 | `{{thread_id}}`, `{{response_items_json}}` |\n| `consolidation.md` | 세션 간 통합을 위한 프롬프트 | `{{raw_memories}}`, `{{rollout_summaries}}` |\n| `read_path.md` | 라이브 세션에 주입되는 메모리 가이던스 | `{{memory_summary}}` |\n\n### 모델 선택\n\n메모리는 모델 역할 시스템을 활용합니다.\n\n| 단계 | 역할 | 용도 |\n|---|---|---|\n| 1단계 (추출) | `default` | 세션별 지식 추출 |\n| 2단계 (통합) | `smol` | 세션 간 합성 |\n\n`smol`이 구성되지 않은 경우, 2단계는 `default` 역할로 폴백합니다.\n\n## 구성\n\n| 설정 | 기본값 | 설명 |\n|---|---|---|\n| `memories.enabled` | `false` | 마스터 스위치 |\n| `memories.maxRolloutAgeDays` | `30` | 이보다 오래된 세션은 처리되지 않음 |\n| `memories.minRolloutIdleHours` | `12` | 이보다 최근에 활성화된 세션은 건너뜀 |\n| `memories.maxRolloutsPerStartup` | `64` | 단일 시작 시 처리되는 세션 수 상한 |\n| `memories.summaryInjectionTokenLimit` | `5000` | 시스템 프롬프트에 주입되는 요약의 최대 토큰 수 |\n\n추가 튜닝 옵션(동시성, 리스 기간, 토큰 예산)은 고급 사용을 위해 config에서 사용할 수 있습니다.\n\n## 주요 파일\n\n- `src/memories/index.ts` — 파이프라인 오케스트레이션, 주입, 슬래시 명령어 처리\n- `src/memories/storage.ts` — SQLite 기반 작업 큐 및 스레드 레지스트리\n- `src/prompts/memories/` — 메모리 프롬프트 템플릿\n- `src/internal-urls/memory-protocol.ts` — `memory://` URL 핸들러\n",
	"ko/sessions/non-compaction-retry-policy.md": "---\ntitle: 비압축 자동 재시도 정책\ndescription: 압축 경로 외부의 일시적 API 실패에 대한 자동 재시도 정책.\nsidebar:\n  order: 6\n  label: 재시도 정책\ni18n:\n  sourceHash: 8999a0258dd8\n  translator: machine\n---\n\n# 비압축 자동 재시도 정책\n\n이 문서는 `AgentSession`의 표준 API 오류 재시도 경로를 설명합니다.\n\n자동 압축을 통한 컨텍스트 오버플로 복구는 명시적으로 제외합니다. 오버플로는 압축 로직에 의해 처리되며, [`compaction.md`](./compaction.md)에 별도로 문서화되어 있습니다.\n\n## 구현 파일\n\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/config/settings-schema.ts`](../../packages/coding-agent/src/config/settings-schema.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts)\n- [`../src/modes/rpc/rpc-client.ts`](../../packages/coding-agent/src/modes/rpc/rpc-client.ts)\n- [`../src/modes/rpc/rpc-types.ts`](../../packages/coding-agent/src/modes/rpc/rpc-types.ts)\n\n## 압축과의 범위 경계\n\n재시도와 압축은 동일한 `agent_end` 경로에서 확인되지만, 의도적으로 분리되어 있습니다:\n\n1. `agent_end`가 마지막 어시스턴트 메시지를 검사합니다.\n2. `#isRetryableError(...)`가 먼저 실행됩니다.\n3. 재시도가 시작되면, 해당 턴에 대한 압축 확인은 건너뜁니다.\n4. 컨텍스트 오버플로 오류는 재시도 분류에서 엄격하게 제외됩니다(`isContextOverflow(...)`가 재시도를 단락시킵니다).\n5. 따라서 오버플로는 표준 재시도 대신 `#checkCompaction(...)`으로 넘어갑니다.\n\n즉, 과부하/속도 제한/서버/네트워크 유형의 실패는 이 재시도 정책을 사용하고, 컨텍스트 윈도 오버플로는 압축 복구를 사용합니다.\n\n## 재시도 분류\n\n`#isRetryableError(...)`는 다음 조건을 모두 충족해야 합니다:\n\n- 어시스턴트 `stopReason === \"error\"`\n- `errorMessage`가 존재함\n- 메시지가 **컨텍스트 오버플로가 아님**\n- `errorMessage`가 `#isRetryableErrorMessage(...)`와 일치함\n\n현재 재시도 가능 패턴 집합(정규식 기반):\n\n- overloaded\n- rate limit / usage limit / too many requests\n- HTTP 유사 서버 클래스: 429, 500, 502, 503, 504\n- service unavailable / server error / internal error\n- connection error / fetch failed\n- `retry delay` 문구\n\n이는 타입화된 프로바이더 오류 코드가 아닌 문자열 패턴 분류입니다.\n\n## 재시도 생명 주기 및 상태 전환\n\n재시도에 사용되는 세션 상태:\n\n- `#retryAttempt: number` (`0`은 유휴 상태를 의미)\n- `#retryPromise: Promise<void> | undefined` (진행 중인 재시도 생명 주기 추적)\n- `#retryResolve: (() => void) | undefined` (`#retryPromise`를 해결)\n- `#retryAbortController: AbortController | undefined` (백오프 슬립 취소)\n\n흐름(`#handleRetryableError`):\n\n1. `retry` 설정 그룹을 읽습니다.\n2. `retry.enabled === false`이면 즉시 중단합니다(`false`, 재시도 미시작).\n3. `#retryAttempt`를 증가시킵니다.\n4. `#retryPromise`를 한 번 생성합니다(체인의 첫 번째 시도).\n5. 시도 횟수가 `retry.maxRetries`를 초과하면 최종 실패 이벤트를 발생시키고 중단합니다.\n6. 지연 시간을 계산합니다: `retry.baseDelayMs * 2^(attempt-1)`.\n7. 사용량 제한 오류의 경우, 재시도 힌트를 파싱하고 인증 저장소를 호출합니다(`markUsageLimitReached(...)`). 프로바이더/모델 전환이 성공하면 지연 시간을 `0`으로 강제 설정합니다.\n8. `auto_retry_start`를 발생시킵니다.\n9. 에이전트 런타임 상태에서 후행 어시스턴트 오류 메시지를 제거합니다(영구 세션 기록에는 유지).\n10. 중단 지원이 포함된 슬립을 실행합니다.\n11. 깨어나면 `setTimeout(..., 0)`을 통해 `agent.continue()`를 예약합니다.\n\n### 재시도 카운터 초기화 시점\n\n`#retryAttempt`는 다음 경우에 `0`으로 초기화됩니다:\n\n- 재시도 시작 후 첫 번째 성공적인 비오류, 비중단 어시스턴트 메시지 발생 시(`auto_retry_end { success: true }` 발생)\n- 백오프 슬립 중 재시도 취소 시\n- 최대 재시도 횟수 초과 경로\n\n`#retryPromise`는 재시도 체인이 종료될 때(성공, 취소 또는 최대 초과 시) `#resolveRetry()`를 통해 해결/초기화됩니다.\n\n## 백오프 및 최대 시도 횟수 의미론\n\n설정:\n\n- `retry.enabled` (기본값 `true`)\n- `retry.maxRetries` (기본값 `3`)\n- `retry.baseDelayMs` (기본값 `2000`)\n\n시도 번호 매기기:\n\n- 최대 횟수 확인 전에 카운터가 증가됩니다\n- 시작 이벤트는 현재 시도 횟수(1-기반)를 사용합니다\n- 최대 초과 종료 이벤트는 `attempt: this.#retryAttempt - 1`을 보고합니다(마지막 시도된 재시도 횟수)\n\n기본 설정에서의 백오프 순서:\n\n- 시도 1: 2000ms\n- 시도 2: 4000ms\n- 시도 3: 8000ms\n\n지연 재정의 입력은 사용량 제한 처리 경로에서만 사용되며, 인증 저장소 모델/계정 전환 결정에 영향을 주기 위해서만 사용됩니다. 주요 비압축 재시도 경로에서는 전환이 성공하지 않는 한(`delayMs = 0`) 백오프가 로컬 지수 지연으로 유지됩니다.\n\n## 중단 메커니즘\n\n### 명시적 재시도 중단\n\n`abortRetry()`:\n\n- `#retryAbortController`를 중단합니다(존재하는 경우)\n- 재시도 프로미스를 해결합니다(`#resolveRetry()`)하여 대기자가 차단 해제됩니다\n\n슬립 중 중단이 발생하면, 캐치 경로에서 다음을 발생시킵니다:\n\n- `auto_retry_end { success: false, finalError: \"Retry cancelled\" }`\n- 시도 횟수/컨트롤러 초기화\n\n### 전역 작업 중단 상호작용\n\n`abort()`는 활성 에이전트 스트림을 중단하기 전에 `abortRetry()`를 호출합니다. 이는 사용자가 일반 중단을 실행할 때 재시도 백오프가 취소되도록 보장합니다.\n\n### TUI 상호작용\n\n`auto_retry_start` 발생 시, EventController가:\n\n- `Esc` 핸들러를 `session.abortRetry()`로 교체합니다\n- 로더 텍스트를 렌더링합니다: `Retrying (attempt/maxAttempts) in Ns… (esc to cancel)`\n\n`auto_retry_end` 발생 시, 이전 `Esc` 핸들러를 복원하고 로더 상태를 초기화합니다.\n\n## 스트리밍 및 프롬프트 완료 동작\n\n`prompt()`는 궁극적으로 `agent.prompt(...)`가 반환된 후 `#waitForRetry()`를 기다립니다.\n\n효과:\n\n- 프롬프트 호출은 시작된 재시도 체인이 완료될 때까지(성공/실패/취소) 완전히 해결되지 않습니다\n- 재시도 생명 주기는 하나의 논리적 프롬프트 실행 경계의 일부입니다\n\n이는 호출자가 재시도 중인 턴을 너무 일찍 완료된 것으로 처리하는 것을 방지합니다.\n\n## 제어: 설정 및 RPC\n\n### 구성 옵션\n\n재시도 그룹 아래 설정 스키마에 정의됨:\n\n- `retry.enabled`\n- `retry.maxRetries`\n- `retry.baseDelayMs`\n\n세션의 프로그래밍 방식 토글:\n\n- `setAutoRetryEnabled(enabled)`: `retry.enabled`를 씁니다\n- `autoRetryEnabled`: `retry.enabled`를 읽습니다\n- `isRetrying`: 재시도 생명 주기 프로미스가 활성 상태인지 보고합니다\n\n### RPC 제어\n\nRPC 명령 표면:\n\n- `set_auto_retry` → `session.setAutoRetryEnabled(command.enabled)`\n- `abort_retry` → `session.abortRetry()`\n\n클라이언트 헬퍼:\n\n- `RpcClient.setAutoRetry(enabled)`\n- `RpcClient.abortRetry()`\n\n두 명령 모두 성공 응답을 반환하며, 재시도 진행/실패 세부 정보는 명령 응답 페이로드가 아닌 스트리밍된 세션 이벤트에서 전달됩니다.\n\n## 이벤트 발생 및 실패 노출\n\n세션 수준 재시도 이벤트:\n\n- `auto_retry_start { attempt, maxAttempts, delayMs, errorMessage }`\n- `auto_retry_end { success, attempt, finalError? }`\n\n전파:\n\n- `AgentSession.subscribe(...)`를 통해 발생됩니다\n- 익스텐션 이벤트로서 익스텐션 러너에 전달됩니다\n- RPC 모드에서는 JSON 이벤트 객체로 직접 전달됩니다(`session.subscribe(event => output(event))`)\n- TUI에서는 로더/오류 UI를 위해 `EventController`가 소비합니다\n\n최종 실패 노출:\n\n- 최대 초과 또는 취소 시 `auto_retry_end.success === false`\n- TUI 표시: `Retry failed after N attempts: <finalError>`\n- 익스텐션/훅은 동일한 필드로 `auto_retry_end`를 수신합니다\n- RPC 소비자는 stdout 스트림에서 동일한 이벤트 객체를 수신합니다\n\n## 영구 중단 조건\n\n다음 중 하나가 발생하면 재시도가 중단되고 자동으로 계속되지 않습니다:\n\n- `retry.enabled`가 false인 경우\n- 오류가 재시도로 분류되지 않은 경우\n- 오류가 컨텍스트 오버플로인 경우(압축 경로에 위임)\n- 최대 재시도 횟수 초과 시\n- 사용자가 재시도를 취소한 경우(`abort_retry` 또는 재시도 로더 중 `Esc`)\n- 전역 중단(`abort`)이 먼저 재시도를 취소한 경우\n\n카운터가 초기화된 후 향후 재시도 가능한 오류에서 새로운 재시도 체인이 다시 시작될 수 있습니다.\n\n## 운영상의 주의 사항\n\n- 분류는 정규식 텍스트 매칭이며, 여기서는 프로바이더별 구조화된 오류가 사용되지 않습니다.\n- 재시도는 재계속 전에 **런타임 컨텍스트**에서 실패한 어시스턴트 오류를 제거하지만, 세션 기록에는 해당 오류 항목이 유지됩니다.\n- `RpcSessionState`는 현재 `autoCompactionEnabled`를 노출하지만 `autoRetryEnabled` 필드는 노출하지 않습니다. RPC 호출자는 자체적으로 토글 상태를 추적하거나 다른 API를 통해 설정을 조회해야 합니다.\n",
	"ko/sessions/session-operations-export-share-fork-resume.md": "---\ntitle: '세션 작업: 내보내기, 덤프, 공유, 포크, 재개'\ndescription: '대화 내보내기, 공유, 포크 및 재개를 위한 세션 작업입니다.'\nsidebar:\n  order: 3\n  label: 작업\ni18n:\n  sourceHash: e3c210b29c3e\n  translator: machine\n---\n\n# 세션 작업: export, dump, share, fork, resume/continue\n\n이 문서는 현재 구현된 세션 내보내기/공유/포크/재개 작업에 대한 운영자가 확인할 수 있는 동작을 설명합니다.\n\n## 구현 파일\n\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/export/html/index.ts`](../../packages/coding-agent/src/export/html/index.ts)\n- [`../src/export/custom-share.ts`](../../packages/coding-agent/src/export/custom-share.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n\n## 작업 매트릭스\n\n| 작업 | 진입 경로 | 세션 변경 | 세션 파일 생성/전환 | 출력 아티팩트 |\n|---|---|---|---|---|\n| `/dump` | 대화형 슬래시 명령 | 아니오 | 아니오 | 클립보드 텍스트 |\n| `/export [path]` | 대화형 슬래시 명령 | 아니오 | 아니오 | HTML 파일 |\n| `--export <session.jsonl> [outputPath]` | CLI 시작 패스트패스 | 런타임 세션 변경 없음 | 활성 세션 없음; 대상 파일을 읽음 | HTML 파일 |\n| `/share` | 대화형 슬래시 명령 | 아니오 | 아니오 | 임시 HTML + 공유 URL/gist |\n| `/fork` | 대화형 슬래시 명령 | 예 (활성 세션 ID 변경) | 새 세션 파일 생성 후 현재 세션을 해당 파일로 전환 (영구 모드에서만) | 아티팩트 디렉토리가 있으면 새 세션 네임스페이스로 복사 |\n| `/resume` | 대화형 슬래시 명령 | 예 (활성 인메모리 상태 교체) | 선택한 기존 세션 파일로 전환 | 없음 |\n| `--resume` | CLI 시작 (선택기) | 세션 생성 후 예 | 선택한 기존 세션 파일 열기 | 없음 |\n| `--resume <id\\|path>` | CLI 시작 | 세션 생성 후 예 | 기존 세션 열기; 프로젝트 간 경우 현재 프로젝트로 포크 가능 | 없음 |\n| `--continue` | CLI 시작 | 세션 생성 후 예 | 터미널 브레드크럼 또는 가장 최근 세션 열기; 없으면 새로 생성 | 없음 |\n\n## 내보내기와 덤프\n\n### `/export [outputPath]` (대화형)\n\n흐름:\n\n1. `InputController`가 `/export...`를 `CommandController.handleExportCommand`로 라우팅합니다.\n2. 명령은 공백으로 분할하며 `/export` 이후 첫 번째 인수만 `outputPath`로 사용합니다.\n3. `AgentSession.exportToHtml()`이 `exportSessionToHtml(sessionManager, state, { outputPath, themeName })`을 호출합니다.\n4. 성공 시 UI에 경로가 표시되고 브라우저에서 파일이 열립니다.\n\n동작 세부사항:\n\n- `--copy`, `clipboard`, `copy` 인수는 `/dump`를 사용하라는 경고와 함께 명시적으로 거부됩니다.\n- 내보내기에는 세션 헤더/항목/리프와 현재 `systemPrompt` 및 에이전트 상태의 도구 설명이 포함됩니다.\n- 내보내기 중 세션 항목이 추가되지 않습니다.\n\n주의사항:\n\n- 인수 파싱이 공백 기반(`text.split(/\\s+/)`)이므로 공백이 포함된 인용 경로는 이 명령 경로에서 단일 경로로 보존되지 않습니다.\n\n### `--export <inputSessionFile> [outputPath]` (CLI)\n\n`main.ts`에서의 흐름:\n\n1. 초기에 처리됩니다 (대화형/세션 시작 전).\n2. `exportFromFile(inputPath, outputPath?)`를 호출합니다.\n3. `SessionManager.open(inputPath)`이 항목을 로드한 후 HTML이 생성되어 기록됩니다.\n4. 프로세스가 `Exported to: ...`를 출력하고 종료합니다.\n\n동작 세부사항:\n\n- 입력 파일이 없으면 `File not found: <path>`로 표시됩니다.\n- 이 경로는 `AgentSession`을 생성하지 않으며 실행 중인 세션을 변경하지 않습니다.\n\n### `/dump` (대화형 클립보드 내보내기)\n\n흐름:\n\n1. `CommandController.handleDumpCommand()`가 `session.formatSessionAsText()`를 호출합니다.\n2. 빈 문자열이면 `No messages to dump yet.`을 보고합니다.\n3. 그렇지 않으면 네이티브 `copyToClipboard`를 통해 클립보드에 복사합니다.\n\n덤프 내용에 포함되는 것:\n\n- 시스템 프롬프트\n- 활성 모델/사고 수준\n- 도구 정의 + 매개변수\n- 사용자/어시스턴트 메시지\n- 사고 블록 및 도구 호출\n- 도구 결과 및 실행 블록 (`excludeFromContext` bash/python 항목 제외)\n- 커스텀/훅/파일 멘션/브랜치 요약/압축 요약 항목\n\n덤프로 인한 세션 영속성 변경은 없습니다.\n\n## 공유\n\n`/share`는 대화형 전용이며 항상 현재 세션을 임시 HTML 파일로 내보내는 것으로 시작합니다.\n\n### 1단계: 임시 내보내기\n\n- 임시 파일 경로: `${os.tmpdir()}/${Snowflake.next()}.html`\n- `session.exportToHtml(tmpFile)` 사용\n- 내보내기가 실패하면 (특히 인메모리 세션의 경우) 공유가 오류와 함께 종료됩니다.\n\n### 2단계: 커스텀 공유 핸들러 (있는 경우)\n\n`loadCustomShare()`는 `~/.xcsh/agent`에서 첫 번째로 존재하는 후보를 확인합니다:\n\n- `share.ts`\n- `share.js`\n- `share.mjs`\n\n요구사항:\n\n- 모듈은 `(htmlPath) => Promise<CustomShareResult | string | undefined>` 함수를 기본 내보내기해야 합니다.\n\n존재하고 유효한 경우:\n\n- UI가 `Sharing...` 로더 상태에 진입합니다.\n- 핸들러 결과 해석:\n  - 문자열 => URL로 처리되어 표시 및 열기\n  - 객체 => `url` 및/또는 `message` 표시; `url` 열기\n  - `undefined`/falsy => 일반적인 `Session shared`\n- 완료 후 임시 파일이 삭제됩니다.\n\n중요한 폴백 동작:\n\n- 커스텀 핸들러가 존재하지만 로딩에 실패하면 명령이 오류를 반환합니다.\n- 커스텀 핸들러가 실행되었지만 예외를 던지면 명령이 오류를 반환합니다.\n- 두 실패 경우 모두 GitHub gist로 **폴백하지 않습니다**.\n- Gist 폴백은 커스텀 공유 스크립트가 없을 때만 발생합니다.\n\n### 3단계: 기본 gist 폴백\n\n커스텀 공유 핸들러가 없을 때만:\n\n1. `gh auth status`를 검증합니다.\n2. `Creating gist...` 로더를 표시합니다.\n3. `gh gist create --public=false <tmpFile>`을 실행합니다.\n4. Gist URL을 파싱하고 gist ID를 추출하여 미리보기 URL `https://gistpreview.github.io/?<id>`를 구성합니다.\n5. 미리보기와 gist URL을 모두 표시하고 미리보기를 엽니다.\n\n공유에서의 취소/중단 시맨틱:\n\n- 로더에는 에디터 UI를 복원하고 `Share cancelled`를 보고하는 `onAbort` 훅이 있습니다.\n- 이 코드 경로에서 기본 `gh gist create` 명령에는 중단 신호가 전달되지 않습니다; 취소는 UI 수준이며 명령 반환 후 확인됩니다.\n\n## 포크\n\n`/fork`는 현재 세션에서 새 세션을 생성하고 활성 세션 ID를 전환합니다.\n\n### 전제 조건 및 즉시 가드\n\n- 에이전트가 스트리밍 중이면 `/fork`가 경고와 함께 거부됩니다.\n- 작업 전에 UI 상태/로딩 표시기가 클리어됩니다.\n\n### 세션 수준 흐름\n\n`AgentSession.fork()`:\n\n1. `reason: \"fork\"`와 함께 `session_before_switch`를 발행합니다 (취소 가능).\n2. 보류 중인 쓰기를 플러시합니다.\n3. `SessionManager.fork()`를 호출합니다.\n4. 이전 세션 네임스페이스에서 새 네임스페이스로 아티팩트 디렉토리를 복사합니다 (베스트 에포트; ENOENT가 아닌 복사 실패는 로그만 남기고 치명적이지 않음).\n5. `agent.sessionId`를 업데이트합니다.\n6. `reason: \"fork\"`와 함께 `session_switch`를 발행합니다.\n\n`SessionManager.fork()` 동작:\n\n- 영구 모드와 기존 세션 파일이 필요합니다.\n- 새 세션 ID와 새 JSONL 파일 경로를 생성합니다.\n- 다음과 같이 헤더를 다시 작성합니다:\n  - 새 `id`\n  - 새 타임스탬프\n  - `cwd` 변경 없음\n  - `parentSession`을 이전 세션 ID로 설정\n- 새 파일에서 헤더 이외의 모든 항목은 변경되지 않습니다.\n\n### 비영구 동작\n\n- 인메모리 세션 매니저는 `fork()`에서 `undefined`를 반환합니다.\n- `AgentSession.fork()`는 `false`를 반환합니다.\n- UI에 `Fork failed (session not persisted or cancelled)`가 보고됩니다.\n\n## 재개와 계속\n\n## 대화형 `/resume`\n\n흐름:\n\n1. `SessionManager.list(currentCwd, currentSessionDir)`를 통해 채워진 세션 선택기를 엽니다.\n2. 선택 시 `SelectorController.handleResumeSession(sessionPath)`이 `session.switchSession(sessionPath)`을 호출합니다.\n3. UI가 채팅과 할 일을 클리어/재구성한 후 `Resumed session`을 보고합니다.\n\n참고사항:\n\n- 이 선택기는 현재 세션 디렉토리 범위 내의 세션만 나열합니다.\n- 전역 프로젝트 간 검색을 사용하지 않습니다.\n\n## CLI `--resume`\n\n### `--resume` (값 없음)\n\n- `main.ts`가 현재 cwd/sessionDir에 대한 세션을 나열하고 선택기를 엽니다.\n- 선택된 경로는 세션 생성 전에 `SessionManager.open(selectedPath)`로 열립니다.\n\n### `--resume <value>`\n\n`createSessionManager()` 해결 순서:\n\n1. 값이 경로처럼 보이면 (`/`, `\\`, 또는 `.jsonl`) 직접 열기.\n2. 그렇지 않으면 ID 접두사로 처리:\n   - 현재 범위 검색 (`SessionManager.list(cwd, sessionDir)`)\n   - 찾지 못하고 명시적 `sessionDir`이 없으면 전역 검색 (`SessionManager.listAll()`)\n\n프로젝트 간 ID 매치 동작:\n\n- 매칭된 세션 cwd가 현재 cwd와 다르면 CLI가 질문합니다:\n  - `Session found in different project ... Fork into current directory? [y/N]`\n- 예: `SessionManager.forkFrom(match.path, cwd, sessionDir)`가 새 로컬 포크 파일을 생성합니다.\n- 아니오/비TTY 기본값: 명령이 오류로 종료합니다.\n\n## CLI `--continue`\n\n`SessionManager.continueRecent(cwd, sessionDir)`:\n\n1. 현재 cwd에 대한 세션 디렉토리를 해석합니다.\n2. 먼저 터미널 범위의 브레드크럼을 읽습니다.\n3. 가장 최근 수정된 세션 파일로 폴백합니다.\n4. 찾은 세션을 열고; 없으면 새 세션을 생성합니다.\n\n이것은 시작 시에만 동작하며; 대화형 `/continue` 슬래시 명령은 없습니다.\n\n## 세션 전환이 실제로 런타임 상태를 변경하는 방법\n\n`AgentSession.switchSession(sessionPath)`는 재개 류의 작업에서 사용하는 런타임 전환을 수행합니다:\n\n1. `reason: \"resume\"`과 `targetSessionFile`과 함께 `session_before_switch`를 발행합니다 (취소 가능).\n2. 에이전트 이벤트 구독을 해제하고 진행 중인 작업을 중단합니다.\n3. 대기 중인 조향/후속/다음 턴 메시지를 클리어합니다.\n4. 현재 세션 매니저 쓰기를 플러시합니다.\n5. `sessionManager.setSessionFile(sessionPath)`하고 `agent.sessionId`를 업데이트합니다.\n6. 로드된 항목에서 세션 컨텍스트를 구성합니다.\n7. `reason: \"resume\"`과 함께 `session_switch`를 발행합니다.\n8. 컨텍스트에서 에이전트 메시지를 교체합니다.\n9. 모델을 복원합니다 (현재 레지스트리에서 사용 가능한 경우).\n10. 사고 수준을 복원하거나 초기화합니다.\n11. 에이전트 이벤트 구독을 다시 연결합니다.\n\n`switchSession()` 자체는 새 세션 파일을 생성하지 않습니다.\n\n## 이벤트 발행 및 취소 지점\n\n### 전환/포크 라이프사이클 훅\n\n`newSession`, `fork`, `switchSession`의 경우:\n\n- 전 이벤트: `session_before_switch`\n  - 이유: `new`, `fork`, `resume`\n  - `{ cancel: true }` 반환으로 취소 가능\n- 후 이벤트: `session_switch`\n  - 동일한 이유 집합\n  - `previousSessionFile` 포함\n\n`ExtensionRunner.emit()`는 첫 번째 취소하는 전 이벤트 결과에서 조기 반환합니다.\n\n### 커스텀 도구 `onSession` 동작\n\nSDK 브릿지가 확장 세션 이벤트를 커스텀 도구 `onSession` 콜백에 전달합니다:\n\n- `session_switch` -> `onSession({ reason: \"switch\", previousSessionFile })`\n- `session_branch` -> `reason: \"branch\"`\n- `session_start` -> `reason: \"start\"`\n- `session_tree` -> `reason: \"tree\"`\n- `session_shutdown` -> `reason: \"shutdown\"`\n\n이 콜백들은 관찰 목적이며; 전환/포크를 취소하지 않습니다.\n\n### 이 문서와 관련된 기타 취소 지점\n\n- `/fork`는 스트리밍 중에 차단됩니다 (사용자가 현재 응답을 기다리거나 중단해야 합니다).\n- `/resume` 선택기는 사용자가 선택기를 닫아 취소할 수 있습니다.\n- 프로젝트 간 `--resume <id>`는 포크 프롬프트를 거부하여 취소할 수 있습니다.\n- `/share`는 gist 흐름에 대한 UI 중단 경로(`Share cancelled`)가 있습니다; 이 코드 경로에서 `gh gist create`에 대한 프로세스 종료 시맨틱을 연결하지 않습니다.\n\n## 비영구 (인메모리) 세션 동작\n\n세션 매니저가 `SessionManager.inMemory()`(`--no-session`)로 생성된 경우:\n\n- 세션 파일 경로가 없습니다.\n- `/export`와 `/share`는 `Cannot export in-memory session to HTML`로 실패합니다 (명령 오류 UI로 전파됨).\n- `/fork`는 `SessionManager.fork()`가 영속성을 필요로 하므로 실패합니다.\n- `/dump`는 인메모리 에이전트 상태를 직렬화하므로 여전히 작동합니다.\n- `--no-session`이 설정되면 CLI 재개/계속 시맨틱이 우회됩니다. 매니저 생성이 즉시 인메모리를 반환하기 때문입니다.\n\n## 알려진 구현 주의사항 (현재 코드 기준)\n\n- `SelectorController.handleResumeSession()`은 `session.switchSession(...)`의 불리언 결과를 확인하지 않습니다; 훅으로 취소된 전환이 여전히 UI \"Resumed session\" 다시 그리기/상태 경로를 통해 진행될 수 있습니다.\n- `/share` 커스텀 공유 실패는 기본 gist 폴백으로 격하되지 않습니다; 오류와 함께 명령을 종료합니다.\n- `/export` 인수 토큰화는 단순하며 공백이 포함된 인용 경로를 보존하지 않습니다.\n",
	"ko/sessions/session-switching-and-recent-listing.md": "---\ntitle: 세션 전환 및 최근 세션 목록\ndescription: 세션 전환 메커니즘과 검색 및 필터링을 포함한 최근 세션 목록 기능.\nsidebar:\n  order: 4\n  label: 전환 및 최근 세션\ni18n:\n  sourceHash: aae56130b508\n  translator: machine\n---\n\n# 세션 전환 및 최근 세션 목록\n\n이 문서는 coding-agent가 최근 세션을 검색하고, `--resume` 대상을 해석하며, 세션 선택기를 표시하고, 활성 런타임 세션을 전환하는 방법을 설명합니다.\n\n현재 구현 동작에 초점을 맞추며, 대체 경로와 주의사항도 포함합니다.\n\n## 구현 파일\n\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/cli/session-picker.ts`](../../packages/coding-agent/src/cli/session-picker.ts)\n- [`../src/modes/components/session-selector.ts`](../../packages/coding-agent/src/modes/components/session-selector.ts)\n- [`../src/modes/controllers/selector-controller.ts`](../../packages/coding-agent/src/modes/controllers/selector-controller.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n\n## 최근 세션 검색\n\n### 디렉토리 범위\n\n`SessionManager`는 기본적으로 cwd 범위 디렉토리에 세션을 저장합니다:\n\n- `~/.xcsh/agent/sessions/--<cwd-encoded>--/*.jsonl`\n\n`SessionManager.list(cwd, sessionDir?)`는 명시적인 `sessionDir`이 제공되지 않는 한 해당 디렉토리만 읽습니다.\n\n### 서로 다른 페이로드를 가진 두 가지 목록 파이프라인\n\n두 가지 서로 다른 목록 파이프라인이 있습니다:\n\n1. `getRecentSessions(sessionDir, limit)` (환영/요약 뷰)\n   - 각 파일에서 4KB 접두사(`readTextPrefix(..., 4096)`)만 읽습니다.\n   - 헤더 + 가장 이른 사용자 텍스트 미리보기를 파싱합니다.\n   - 지연 `name` 및 `timeAgo` getter를 가진 경량 `RecentSessionInfo`를 반환합니다.\n   - 파일 `mtime` 내림차순으로 정렬합니다.\n\n2. `SessionManager.list(...)` / `SessionManager.listAll()` (재개 선택기 및 ID 매칭)\n   - 전체 세션 파일을 읽습니다.\n   - `SessionInfo` 객체(`id`, `cwd`, `title`, `messageCount`, `firstMessage`, `allMessagesText`, 타임스탬프)를 생성합니다.\n   - `message` 항목이 없는 세션은 제외합니다.\n   - `modified` 내림차순으로 정렬합니다.\n\n### 메타데이터 대체 동작\n\n최근 요약(`RecentSessionInfo`)의 경우:\n\n- 표시 이름 우선순위: `header.title` -> 첫 번째 사용자 프롬프트 -> `header.id` -> 파일명\n- 간결한 표시를 위해 이름은 40자로 잘립니다\n- 제목에서 파생된 이름에서 제어 문자/줄바꿈은 제거/정제됩니다\n\n`SessionInfo` 목록 항목의 경우:\n\n- `title`은 `header.title` 또는 최신 압축의 `shortSummary`입니다\n- `firstMessage`는 첫 번째 사용자 메시지 텍스트이거나 `\"(no messages)\"`입니다\n\n## `--continue` 해석 및 터미널 브레드크럼 우선순위\n\n`SessionManager.continueRecent(cwd, sessionDir?)`는 다음 순서로 대상을 해석합니다:\n\n1. 터미널 범위 브레드크럼 읽기 (`~/.xcsh/agent/terminal-sessions/<terminal-id>`)\n2. 브레드크럼 유효성 검사:\n   - 현재 터미널을 식별할 수 있는지\n   - 브레드크럼 cwd가 현재 cwd와 일치하는지 (해석된 경로 비교)\n   - 참조된 파일이 여전히 존재하는지\n3. 브레드크럼이 유효하지 않거나 없으면, 세션 디렉토리에서 mtime 기준 가장 최신 파일로 대체 (`findMostRecentSession`)\n4. 찾지 못하면 새 세션 생성\n\n터미널 ID 도출은 TTY 경로를 우선하며 환경 변수 기반 식별자(`KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION`)로 대체합니다.\n\n브레드크럼 쓰기는 최선 노력(best-effort)이며 실패해도 치명적이지 않습니다.\n\n## 시작 시 재개 대상 해석 (`main.ts`)\n\n### `--resume <value>`\n\n`createSessionManager(...)`는 문자열 값의 `--resume`을 두 가지 모드로 처리합니다:\n\n1. 경로 형태의 값 (`/`, `\\\\` 포함 또는 `.jsonl`로 끝남)\n   - 직접 `SessionManager.open(sessionArg, parsed.sessionDir)`\n\n2. ID 접두사 값\n   - `SessionManager.list(cwd, sessionDir)`에서 `id.startsWith(sessionArg)`로 일치 항목 검색\n   - 로컬에서 일치하지 않고 `sessionDir`이 강제되지 않은 경우, `SessionManager.listAll()` 시도\n   - 첫 번째 일치 항목 사용 (모호성 프롬프트 없음)\n\n프로젝트 간 일치 동작:\n\n- 일치된 세션의 cwd가 현재 cwd와 다른 경우, CLI가 현재 프로젝트로 포크할지 묻습니다\n- 예 -> `SessionManager.forkFrom(...)`\n- 아니오 -> 오류 발생 (`Session \"...\" is in another project (...)`)\n\n일치 없음 -> 오류 발생 (`Session \"...\" not found.`).\n\n### `--resume` (값 없음)\n\n초기 세션 매니저 생성 후 처리됩니다:\n\n1. `SessionManager.list(cwd, parsed.sessionDir)`로 로컬 세션 목록 조회\n2. 비어 있으면: `No sessions found` 출력 후 조기 종료\n3. TUI 선택기 열기 (`selectSession`)\n4. 취소된 경우: `No session selected` 출력 후 조기 종료\n5. 선택된 경우: `SessionManager.open(selectedPath)`\n\n### `--continue`\n\n`SessionManager.continueRecent(...)`를 직접 사용합니다 (위의 브레드크럼 우선 동작).\n\n## 선택기 기반 세션 선택 내부 동작\n\n## CLI 선택기 (`src/cli/session-picker.ts`)\n\n`selectSession(sessions)`는 `SessionSelectorComponent`로 독립 TUI를 생성하고 정확히 한 번 해석합니다:\n\n- 선택 -> 선택된 경로로 해석\n- 취소 (Esc) -> `null`로 해석\n- 강제 종료 (Ctrl+C 경로) -> TUI 중지 및 `process.exit(0)`\n\n## 대화형 세션 내 선택기 (`SelectorController.showSessionSelector`)\n\n흐름:\n\n1. `SessionManager.list(currentCwd, currentSessionDir)`를 통해 현재 세션 디렉토리에서 세션 가져오기\n2. `showSelector(...)`를 사용하여 편집기 영역에 `SessionSelectorComponent` 마운트\n3. 콜백:\n   - 선택 -> 선택기 닫기 및 `handleResumeSession(sessionPath)` 호출\n   - 취소 -> 편집기 복원 및 재렌더링\n   - 종료 -> `ctx.shutdown()`\n\n## 세션 선택기 컴포넌트 동작\n\n`SessionList`는 다음을 지원합니다:\n\n- 화살표/페이지 탐색\n- Enter로 선택\n- Esc로 취소\n- Ctrl+C로 종료\n- 세션 id/제목/cwd/첫 번째 메시지/모든 메시지/경로에 대한 퍼지 검색\n\n빈 목록 렌더링 동작:\n\n- 충돌 대신 메시지를 렌더링합니다\n- 빈 상태에서 Enter는 아무 동작도 하지 않습니다 (콜백 없음)\n- Esc/Ctrl+C는 여전히 동작합니다\n\n주의사항: UI 텍스트에 `Press Tab to view all`이라고 표시되지만, 이 컴포넌트에는 현재 Tab 핸들러가 없으며 현재 연결은 현재 범위 세션만 나열합니다.\n\n## 런타임 전환 실행 (`AgentSession.switchSession`)\n\n`switchSession(sessionPath)`는 핵심 프로세스 내 전환 경로입니다.\n\n생명주기/상태 전이:\n\n1. `previousSessionFile` 캡처\n2. `session_before_switch` 훅 이벤트 발생 (`reason: \"resume\"`, 취소 가능)\n3. 취소된 경우 -> 전환 없이 `false` 반환\n4. 현재 에이전트 이벤트 스트림에서 연결 해제\n5. 활성 생성/도구 흐름 중단\n6. 대기 중인 조향/후속/다음 턴 메시지 버퍼 지우기\n7. 세션 라이터 플러시 (`sessionManager.flush()`) - 보류 중인 쓰기 영속화\n8. `sessionManager.setSessionFile(sessionPath)`\n   - 세션 파일 포인터 업데이트\n   - 터미널 브레드크럼 쓰기\n   - 항목 로드 / 마이그레이션 / blob 해석 / 재인덱싱\n   - 파일 데이터가 없거나 유효하지 않은 경우: 해당 경로에 새 세션을 초기화하고 헤더 재작성\n9. `agent.sessionId` 업데이트\n10. `buildSessionContext()`를 통한 컨텍스트 재구축\n11. `session_switch` 훅 이벤트 발생 (`reason: \"resume\"`, `previousSessionFile`)\n12. 에이전트 메시지를 재구축된 컨텍스트로 교체\n13. `sessionContext.models.default`가 사용 가능하고 모델 레지스트리에 존재하면 기본 모델 복원\n14. 사고 수준 복원:\n    - 브랜치에 이미 `thinking_level_change`가 있으면 저장된 세션 수준 적용\n    - 그렇지 않으면 설정에서 기본 사고 수준을 도출하고, 모델 능력에 맞게 제한하고, 설정한 후 새 `thinking_level_change` 항목 추가\n15. 에이전트 리스너 재연결 및 `true` 반환\n\n## 대화형 전환 후 UI 상태 재구축\n\n`SelectorController.handleResumeSession`은 `switchSession` 전후로 UI 초기화를 수행합니다:\n\n- 로딩 애니메이션 중지\n- 상태 컨테이너 지우기\n- 보류 메시지 UI 및 보류 도구 맵 지우기\n- 스트리밍 컴포넌트/메시지 참조 초기화\n- `session.switchSession(...)` 호출\n- 채팅 컨테이너 지우고 세션 컨텍스트에서 재렌더링 (`renderInitialMessages`)\n- 새 세션 아티팩트에서 할 일 목록 다시 로드\n- `Resumed session` 표시\n\n따라서 보이는 대화/할 일 상태는 새 세션 파일에서 재구축됩니다.\n\n## 시작 시 재개 vs 세션 내 전환\n\n### 시작 시 재개 (`--continue`, `--resume`, 직접 열기)\n\n- `createAgentSession(...)` 이전에 세션 파일이 선택됩니다.\n- `sdk.ts`가 `existingSession = sessionManager.buildSessionContext()`를 구축합니다.\n- 에이전트 메시지는 세션 생성 중 한 번 복원됩니다.\n- 모델/사고 수준은 생성 중에 선택됩니다 (복원/대체 로직 포함).\n- 대화형 모드에서 `#restoreModeFromSession()`을 실행하여 영속화된 모드 상태에 다시 진입합니다 (현재 plan/plan_paused).\n\n### 세션 내 전환 (`/resume` 스타일 선택기 경로)\n\n- 이미 실행 중인 `AgentSession`에서 `AgentSession.switchSession(...)`을 사용합니다.\n- 메시지/모델/사고 수준이 즉시 현재 위치에서 재구축됩니다.\n- `session_before_switch`/`session_switch` 훅 이벤트가 발생합니다.\n- UI 채팅/할 일이 새로 고쳐집니다.\n- 선택기 흐름에서는 전환 후 전용 모드 복원 호출이 이루어지지 않습니다; 모드 재진입 동작은 시작 시 `#restoreModeFromSession()`과 대칭적이지 않습니다.\n\n## 실패 및 엣지 케이스 동작\n\n### 취소 경로\n\n- CLI 선택기 취소 -> `null` 반환, 호출자가 `No session selected` 출력, 프로세스 조기 종료.\n- 대화형 선택기 취소 -> 편집기 복원, 세션 변경 없음.\n- 훅 취소 (`session_before_switch`) -> `switchSession()`이 `false` 반환.\n\n### 빈 목록 경로\n\n- CLI `--resume` (값 없음): 빈 목록은 `No sessions found`를 출력하고 종료합니다.\n- 대화형 선택기: 빈 목록은 메시지를 렌더링하고 취소 가능한 상태를 유지합니다.\n\n### 대상 세션 파일이 없거나 유효하지 않은 경우\n\n특정 경로로 열기/전환할 때 (`setSessionFile`):\n\n- ENOENT -> 비어 있는 것으로 처리 -> 해당 정확한 경로에 새 세션을 초기화하고 영속화합니다.\n- 잘못된 형식/유효하지 않은 헤더 (또는 사실상 읽을 수 없는 파싱된 항목) -> 비어 있는 것으로 처리 -> 새 세션을 초기화하고 영속화합니다.\n\n이것은 복구 동작이며 하드 실패가 아닙니다.\n\n### 하드 실패\n\n전환/열기는 실제 I/O 실패(권한 오류, 재작성 실패 등)에서 여전히 예외를 발생시킬 수 있으며, 이는 호출자에게 전파됩니다.\n\n### ID 접두사 매칭 주의사항\n\n- ID 매칭은 `startsWith`를 사용하고 정렬된 목록에서 첫 번째 일치를 가져옵니다.\n- 여러 세션이 접두사를 공유하는 경우 모호성 UI가 없습니다.\n- `SessionManager.list(...)`는 메시지가 없는 세션을 제외하므로, 해당 세션은 ID 매칭/목록 선택기를 통해 재개할 수 없습니다.\n",
	"ko/sessions/session-tree-plan.md": "---\ntitle: 세션 트리 아키텍처\ndescription: '분기, 탐색, 부모-자식 대화 관계를 포함한 세션 트리 아키텍처.'\nsidebar:\n  order: 2\n  label: 트리 아키텍처\ni18n:\n  sourceHash: bd8b78d6c33a\n  translator: machine\n---\n\n# 세션 트리 아키텍처 (현재)\n\n참조: [session.md](./session.md)\n\n이 문서는 현재 세션 트리 탐색이 어떻게 동작하는지 설명합니다: 인메모리 트리 모델, 리프 이동 규칙, 분기 동작, 확장/이벤트 통합.\n\n## 이 서브시스템이 하는 일\n\n세션은 추가 전용(append-only) 항목 로그로 저장되지만, 런타임 동작은 트리 기반입니다:\n\n- 모든 비헤더 항목에는 `id`와 `parentId`가 있습니다.\n- 활성 위치는 `SessionManager`의 `leafId`입니다.\n- 항목을 추가하면 항상 현재 리프의 자식이 생성됩니다.\n- 분기는 히스토리를 **재작성하지 않습니다**; 다음 추가 전에 리프가 가리키는 위치만 변경됩니다.\n\n주요 파일:\n\n- `src/session/session-manager.ts` — 트리 데이터 모델, 순회, 리프 이동, 분기/세션 추출\n- `src/session/agent-session.ts` — `/tree` 탐색 흐름, 요약, 훅/이벤트 방출\n- `src/modes/components/tree-selector.ts` — 대화형 트리 UI 동작 및 필터링\n- `src/modes/controllers/selector-controller.ts` — `/tree` 및 `/branch`를 위한 선택기 오케스트레이션\n- `src/modes/controllers/input-controller.ts` — 명령 라우팅 (`/tree`, `/branch`, 더블 이스케이프 동작)\n- `src/session/messages.ts` — `branch_summary`, `compaction`, `custom_message` 항목을 LLM 컨텍스트 메시지로 변환\n\n## `SessionManager`의 트리 데이터 모델\n\n런타임 인덱스:\n\n- `#byId: Map<string, SessionEntry>` — 모든 항목에 대한 빠른 조회\n- `#leafId: string | null` — 트리에서 현재 위치\n- `#labelsById: Map<string, string>` — 대상 항목 id별 해석된 레이블\n\n트리 API:\n\n- `getBranch(fromId?)`는 부모 링크를 따라 루트까지 이동하여 루트→노드 경로를 반환합니다\n- `getTree()`는 `SessionTreeNode[]`(`entry`, `children`, `label`)를 반환합니다\n  - 부모 링크가 자식 배열로 변환됩니다\n  - 부모가 없는 항목은 루트로 처리됩니다\n  - 자식은 타임스탬프 기준으로 오래된 것부터 최신 순으로 정렬됩니다\n- `getChildren(parentId)`는 직계 자식을 반환합니다\n- `getLabel(id)`는 `labelsById`에서 현재 레이블을 해석합니다\n\n`getTree()`는 런타임 프로젝션입니다; 영속성은 추가 전용 JSONL 항목으로 유지됩니다.\n\n## 리프 이동 의미론\n\n세 가지 리프 이동 기본 연산이 있습니다:\n\n1. `branch(entryId)`\n   - 항목이 존재하는지 유효성 검사\n   - `leafId = entryId` 설정\n   - 새 항목이 기록되지 않음\n\n2. `resetLeaf()`\n   - `leafId = null` 설정\n   - 다음 추가 시 새 루트 항목 생성 (`parentId = null`)\n\n3. `branchWithSummary(branchFromId, summary, details?, fromExtension?)`\n   - `branchFromId: string | null` 수락\n   - `leafId = branchFromId` 설정\n   - 해당 리프의 자식으로 `branch_summary` 항목 추가\n   - `branchFromId`가 `null`이면 `fromId`가 `\"root\"`로 저장됨\n\n## `/tree` 탐색 동작 (동일 세션 파일)\n\n`AgentSession.navigateTree()`는 탐색이며 파일 포킹이 아닙니다.\n\n흐름:\n\n1. 대상을 유효성 검사하고 포기된 경로 계산 (`collectEntriesForBranchSummary`)\n2. `TreePreparation`과 함께 `session_before_tree` 방출\n3. 포기된 항목 선택적 요약 (훅 제공 요약 또는 내장 요약기)\n4. 새 리프 대상 계산:\n   - **user** 메시지 선택: 리프가 부모로 이동하고, 메시지 텍스트가 에디터 미리 채우기용으로 반환됨\n   - **custom_message** 선택: user 메시지와 동일한 규칙 (리프 = 부모, 텍스트가 에디터 미리 채우기)\n   - 다른 항목 선택: 리프 = 선택된 항목 id\n5. 리프 이동 적용:\n   - 요약 있음: `branchWithSummary(newLeafId, ...)`\n   - 요약 없고 `newLeafId === null`: `resetLeaf()`\n   - 그 외: `branch(newLeafId)`\n6. 새 리프에서 에이전트 컨텍스트 재빌드 및 `session_tree` 방출\n\n중요: 요약 항목은 포기된 분기 끝이 아니라 **새 탐색 위치**에 연결됩니다.\n\n## `/branch` 동작 (새 세션 파일)\n\n`/branch`와 `/tree`는 의도적으로 다릅니다:\n\n- `/tree`는 현재 세션 파일 내에서 탐색합니다.\n- `/branch`는 새 세션 분기 파일을 생성합니다 (또는 비영속 모드의 경우 인메모리 교체).\n\n사용자 대면 `/branch` 흐름 (`SelectorController.showUserMessageSelector` → `AgentSession.branch`):\n\n- 분기 소스는 반드시 **user 메시지**여야 합니다.\n- 선택된 user 텍스트가 에디터 미리 채우기용으로 추출됩니다.\n- 선택된 user 메시지가 루트인 경우 (`parentId === null`): `newSession({ parentSession: previousSessionFile })`을 통해 새 세션 시작.\n- 그 외: `createBranchedSession(selectedEntry.parentId)`으로 선택된 프롬프트 경계까지 히스토리 포킹.\n\n`SessionManager.createBranchedSession(leafId)` 세부 사항:\n\n- `getBranch(leafId)`를 통해 루트→리프 경로 빌드; 누락 시 예외 발생.\n- 복사된 경로에서 기존 `label` 항목 제외.\n- 경로에 남아 있는 항목에 대해 해석된 `labelsById`에서 새 레이블 항목 재빌드.\n- 영속 모드: 새 JSONL 파일 작성 및 매니저를 해당 파일로 전환; 새 파일 경로 반환.\n- 인메모리 모드: 인메모리 항목 교체; `undefined` 반환.\n\n## 컨텍스트 재구성 및 요약/커스텀 통합\n\n`buildSessionContext()`(`session-manager.ts` 내)는 활성 루트→리프 경로를 해석하고 유효한 LLM 컨텍스트 상태를 빌드합니다:\n\n- 경로상 최신 thinking/model/mode/ttsr 상태를 추적합니다.\n- 경로상 최신 컴팩션을 처리합니다:\n  - 컴팩션 요약을 먼저 방출\n  - `firstKeptEntryId`에서 컴팩션 지점까지 유지된 메시지 재생\n  - 이후 컴팩션 이후 메시지 재생\n- `branch_summary`와 `custom_message` 항목을 `AgentMessage` 객체로 포함합니다.\n\n그런 다음 `session/messages.ts`가 모델 입력을 위해 이러한 메시지 유형을 매핑합니다:\n\n- `branchSummary`와 `compactionSummary`는 user 역할 템플릿 컨텍스트 메시지가 됨\n- `custom`/`hookMessage`는 user 역할 콘텐츠 메시지가 됨\n\n따라서 트리 이동은 이전 항목을 변경하지 않고 활성 리프 경로를 변경함으로써 컨텍스트를 변경합니다.\n\n## 레이블 및 트리 UI 동작\n\n레이블 영속성:\n\n- `appendLabelChange(targetId, label?)`는 현재 리프 체인에 `label` 항목을 씁니다.\n- `labelsById`는 즉시 업데이트됩니다 (설정 또는 삭제).\n- `getTree()`는 반환된 각 노드에 현재 레이블을 해석합니다.\n\n트리 선택기 동작 (`tree-selector.ts`):\n\n- 탐색을 위해 트리를 평탄화하고, 활성 경로 강조 표시를 유지하며, 활성 분기를 먼저 표시하도록 우선순위를 부여합니다.\n- 필터 모드 지원: `default`, `no-tools`, `user-only`, `labeled-only`, `all`.\n- 렌더링된 의미론적 콘텐츠에 대한 자유 텍스트 검색 지원.\n- `Shift+L`로 인라인 레이블 편집을 열고 `appendLabelChange`를 통해 씁니다.\n\n명령 라우팅:\n\n- `/tree`는 항상 트리 선택기를 엽니다.\n- `/branch`는 `doubleEscapeAction=tree`가 아닌 한 user 메시지 선택기를 엽니다; 해당 경우 트리 선택기 UX도 사용합니다.\n\n## 트리 작업을 위한 확장 및 훅 접점\n\n명령 시점 확장 API (`ExtensionCommandContext`):\n\n- `branch(entryId)` — 분기된 세션 파일 생성\n- `navigateTree(targetId, { summarize? })` — 현재 트리/파일 내에서 이동\n\n트리 탐색 관련 이벤트:\n\n- `session_before_tree`\n  - `TreePreparation`을 수신:\n    - `targetId`\n    - `oldLeafId`\n    - `commonAncestorId`\n    - `entriesToSummarize`\n    - `userWantsSummary`\n  - 탐색을 취소할 수 있음\n  - 내장 요약기 대신 사용되는 요약 페이로드를 제공할 수 있음\n  - 중단 `signal` 수신 (이스케이프 취소 경로)\n- `session_tree`\n  - `newLeafId`, `oldLeafId` 방출\n  - 요약이 생성된 경우 `summaryEntry` 포함\n  - `fromExtension`은 요약 출처를 나타냄\n\n인접하지만 관련된 라이프사이클 훅:\n\n- `/branch` 흐름을 위한 `session_before_branch` / `session_branch`\n- 이후 트리 컨텍스트 재구성에 영향을 미치는 컴팩션 항목을 위한 `session_before_compact`, `session.compacting`, `session_compact`\n\n## 실제 제약 조건 및 엣지 케이스\n\n- `branch()`는 `null`을 대상으로 할 수 없습니다; 첫 번째 항목 이전 루트 상태에는 `resetLeaf()`를 사용하세요.\n- `branchWithSummary()`는 `null` 대상을 지원하며 `fromId: \"root\"`를 기록합니다.\n- 트리 선택기에서 현재 리프를 선택하는 것은 no-op입니다.\n- 요약에는 활성 모델이 필요합니다; 없으면 요약 탐색이 즉시 실패합니다.\n- 요약이 중단되면 탐색이 취소되고 리프는 변경되지 않습니다.\n- 인메모리 세션은 `createBranchedSession`에서 분기 파일 경로를 반환하지 않습니다.\n\n## 현재 존재하는 레거시 호환성\n\n세션 마이그레이션이 로드 시 계속 실행됩니다:\n\n- v1→v2는 `id`/`parentId`를 추가하고 컴팩션 인덱스 앵커를 id 앵커로 변환합니다\n- v2→v3은 레거시 `hookMessage` 역할을 `custom`으로 마이그레이션합니다\n\n현재 런타임 동작은 마이그레이션 후 버전 3 트리 의미론입니다.\n",
	"ko/sessions/session.md": "---\ntitle: 세션 저장소 및 엔트리 모델\ndescription: 'Append-only 세션 저장소 모델 — 엔트리 유형, 영속성, 포맷 간 마이그레이션.'\nsidebar:\n  order: 1\n  label: 저장소 및 엔트리 모델\ni18n:\n  sourceHash: 42fe17549e00\n  translator: machine\n---\n\n# 세션 저장소 및 엔트리 모델\n\n이 문서는 코딩 에이전트 세션이 어떻게 표현되고, 영속화되며, 마이그레이션되고, 런타임에 재구성되는지에 대한 정보 원천(source of truth)입니다.\n\n## 범위\n\n다루는 내용:\n\n- 세션 JSONL 포맷 및 버전 관리\n- 엔트리 분류 체계 및 트리 의미론 (`id`/`parentId` + 리프 포인터)\n- 오래되거나 잘못된 형식의 파일을 로드할 때의 마이그레이션/호환성 동작\n- 컨텍스트 재구성 (`buildSessionContext`)\n- 영속성 보장, 실패 동작, 잘라내기/블롭 외부화\n- 저장소 추상화 (`FileSessionStorage`, `MemorySessionStorage`) 및 관련 유틸리티\n\n세션 데이터에 영향을 미치는 의미론을 넘어서는 `/tree` UI 렌더링 동작은 다루지 않습니다.\n\n## 구현 파일\n\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`src/session/messages.ts`](../../packages/coding-agent/src/session/messages.ts)\n- [`src/session/session-storage.ts`](../../packages/coding-agent/src/session/session-storage.ts)\n- [`src/session/history-storage.ts`](../../packages/coding-agent/src/session/history-storage.ts)\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts)\n\n## 디스크 레이아웃\n\n기본 세션 파일 위치:\n\n```text\n~/.xcsh/agent/sessions/--<cwd-encoded>--/<timestamp>_<sessionId>.jsonl\n```\n\n`<cwd-encoded>`는 작업 디렉터리에서 선행 슬래시를 제거하고 `/`, `\\\\`, `:`를 `-`로 대체하여 생성됩니다.\n\n블롭 저장소 위치:\n\n```text\n~/.xcsh/agent/blobs/<sha256>\n```\n\n터미널 브레드크럼 파일은 다음 경로에 기록됩니다:\n\n```text\n~/.xcsh/agent/terminal-sessions/<terminal-id>\n```\n\n브레드크럼 내용은 두 줄로 구성됩니다: 원래 cwd, 그다음 세션 파일 경로. `continueRecent()`는 가장 최근 mtime을 스캔하기 전에 이 터미널 범위 포인터를 우선적으로 사용합니다.\n\n## 파일 포맷\n\n세션 파일은 JSONL 형식입니다: 한 줄에 하나의 JSON 객체.\n\n- 1번째 줄은 항상 세션 헤더입니다 (`type: \"session\"`).\n- 나머지 줄은 `SessionEntry` 값입니다.\n- 엔트리는 런타임에 추가 전용(append-only)입니다; 브랜치 탐색은 기존 엔트리를 변경하는 대신 포인터(`leafId`)를 이동합니다.\n\n### 헤더 (`SessionHeader`)\n\n```json\n{\n  \"type\": \"session\",\n  \"version\": 3,\n  \"id\": \"1f9d2a6b9c0d1234\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\",\n  \"cwd\": \"/work/pi\",\n  \"title\": \"optional session title\",\n  \"parentSession\": \"optional lineage marker\"\n}\n```\n\n참고 사항:\n\n- `version`은 v1 파일에서는 선택 사항입니다; 없으면 v1을 의미합니다.\n- `parentSession`은 불투명한(opaque) 계보 문자열입니다. 현재 코드는 흐름에 따라 세션 id 또는 세션 경로를 기록합니다 (`fork`, `forkFrom`, `createBranchedSession`, 또는 명시적 `newSession({ parentSession })`). 타입이 지정된 외래 키가 아닌 메타데이터로 취급하십시오.\n\n### 엔트리 기본 (`SessionEntryBase`)\n\n모든 비헤더 엔트리는 다음을 포함합니다:\n\n```json\n{\n  \"type\": \"...\",\n  \"id\": \"8-char-id\",\n  \"parentId\": \"previous-or-branch-parent\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\"\n}\n```\n\n`parentId`는 루트 엔트리(첫 번째 추가, 또는 `resetLeaf()` 이후)의 경우 `null`일 수 있습니다.\n\n## 엔트리 분류 체계\n\n`SessionEntry`는 다음의 합집합(union)입니다:\n\n- `message`\n- `thinking_level_change`\n- `model_change`\n- `compaction`\n- `branch_summary`\n- `custom`\n- `custom_message`\n- `label`\n- `ttsr_injection`\n- `session_init`\n- `mode_change`\n\n### `message`\n\n`AgentMessage`를 직접 저장합니다.\n\n```json\n{\n  \"type\": \"message\",\n  \"id\": \"a1b2c3d4\",\n  \"parentId\": null,\n  \"timestamp\": \"2026-02-16T10:21:00.000Z\",\n  \"message\": {\n    \"role\": \"assistant\",\n    \"provider\": \"anthropic\",\n    \"model\": \"claude-sonnet-4-5\",\n    \"content\": [{ \"type\": \"text\", \"text\": \"Done.\" }],\n    \"usage\": { \"input\": 100, \"output\": 20, \"cacheRead\": 0, \"cacheWrite\": 0, \"cost\": { \"input\": 0, \"output\": 0, \"cacheRead\": 0, \"cacheWrite\": 0, \"total\": 0 } },\n    \"timestamp\": 1760000000000\n  }\n}\n```\n\n### `model_change`\n\n```json\n{\n  \"type\": \"model_change\",\n  \"id\": \"b1c2d3e4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:21:30.000Z\",\n  \"model\": \"openai/gpt-4o\",\n  \"role\": \"default\"\n}\n```\n\n`role`은 선택 사항입니다; 누락된 경우 컨텍스트 재구성 시 `default`로 처리됩니다.\n\n### `thinking_level_change`\n\n```json\n{\n  \"type\": \"thinking_level_change\",\n  \"id\": \"c1d2e3f4\",\n  \"parentId\": \"b1c2d3e4\",\n  \"timestamp\": \"2026-02-16T10:22:00.000Z\",\n  \"thinkingLevel\": \"high\"\n}\n```\n\n### `compaction`\n\n```json\n{\n  \"type\": \"compaction\",\n  \"id\": \"d1e2f3a4\",\n  \"parentId\": \"c1d2e3f4\",\n  \"timestamp\": \"2026-02-16T10:23:00.000Z\",\n  \"summary\": \"Conversation summary\",\n  \"shortSummary\": \"Short recap\",\n  \"firstKeptEntryId\": \"a1b2c3d4\",\n  \"tokensBefore\": 42000,\n  \"details\": { \"readFiles\": [\"src/a.ts\"] },\n  \"preserveData\": { \"hookState\": true },\n  \"fromExtension\": false\n}\n```\n\n### `branch_summary`\n\n```json\n{\n  \"type\": \"branch_summary\",\n  \"id\": \"e1f2a3b4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:24:00.000Z\",\n  \"fromId\": \"a1b2c3d4\",\n  \"summary\": \"Summary of abandoned path\",\n  \"details\": { \"note\": \"optional\" },\n  \"fromExtension\": true\n}\n```\n\n루트에서 분기하는 경우 (`branchFromId === null`), `fromId`는 리터럴 문자열 `\"root\"`입니다.\n\n### `custom`\n\n확장 기능 상태 영속성; `buildSessionContext`에서 무시됩니다.\n\n```json\n{\n  \"type\": \"custom\",\n  \"id\": \"f1a2b3c4\",\n  \"parentId\": \"e1f2a3b4\",\n  \"timestamp\": \"2026-02-16T10:25:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"data\": { \"state\": 1 }\n}\n```\n\n### `custom_message`\n\nLLM 컨텍스트에 참여하는 확장 기능 제공 메시지입니다.\n\n```json\n{\n  \"type\": \"custom_message\",\n  \"id\": \"a2b3c4d5\",\n  \"parentId\": \"f1a2b3c4\",\n  \"timestamp\": \"2026-02-16T10:26:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"content\": \"Injected context\",\n  \"display\": true,\n  \"details\": { \"debug\": false }\n}\n```\n\n### `label`\n\n```json\n{\n  \"type\": \"label\",\n  \"id\": \"b2c3d4e5\",\n  \"parentId\": \"a2b3c4d5\",\n  \"timestamp\": \"2026-02-16T10:27:00.000Z\",\n  \"targetId\": \"a1b2c3d4\",\n  \"label\": \"checkpoint\"\n}\n```\n\n`label: undefined`는 `targetId`에 대한 레이블을 지웁니다.\n\n### `ttsr_injection`\n\n```json\n{\n  \"type\": \"ttsr_injection\",\n  \"id\": \"c2d3e4f5\",\n  \"parentId\": \"b2c3d4e5\",\n  \"timestamp\": \"2026-02-16T10:28:00.000Z\",\n  \"injectedRules\": [\"ruleA\", \"ruleB\"]\n}\n```\n\n### `session_init`\n\n```json\n{\n  \"type\": \"session_init\",\n  \"id\": \"d2e3f4a5\",\n  \"parentId\": \"c2d3e4f5\",\n  \"timestamp\": \"2026-02-16T10:29:00.000Z\",\n  \"systemPrompt\": \"...\",\n  \"task\": \"...\",\n  \"tools\": [\"read\", \"edit\"],\n  \"outputSchema\": { \"type\": \"object\" }\n}\n```\n\n### `mode_change`\n\n```json\n{\n  \"type\": \"mode_change\",\n  \"id\": \"e2f3a4b5\",\n  \"parentId\": \"d2e3f4a5\",\n  \"timestamp\": \"2026-02-16T10:30:00.000Z\",\n  \"mode\": \"plan\",\n  \"data\": { \"planFile\": \"/tmp/plan.md\" }\n}\n```\n\n## 버전 관리 및 마이그레이션\n\n현재 세션 버전: `3`.\n\n### v1 -> v2\n\n헤더 `version`이 누락되었거나 `< 2`일 때 적용됩니다:\n\n- 각 비헤더 엔트리에 `id`와 `parentId`를 추가합니다.\n- 파일 순서를 사용하여 선형 부모 체인을 재구성합니다.\n- `firstKeptEntryIndex` -> `firstKeptEntryId` 압축 필드가 있는 경우 마이그레이션합니다.\n- 헤더 `version = 2`로 설정합니다.\n\n### v2 -> v3\n\n헤더 `version < 3`일 때 적용됩니다:\n\n- `message` 엔트리의 경우: 레거시 `message.role === \"hookMessage\"`를 `\"custom\"`으로 재작성합니다.\n- 헤더 `version = 3`으로 설정합니다.\n\n### 마이그레이션 트리거 및 영속화\n\n- 마이그레이션은 세션 로드(`setSessionFile`) 중에 실행됩니다.\n- 마이그레이션이 실행된 경우, 전체 파일이 즉시 디스크에 다시 기록됩니다.\n- 마이그레이션은 먼저 메모리 내 엔트리를 변경한 다음, 재작성된 JSONL을 영속화합니다.\n\n## 로드 및 호환성 동작\n\n`loadEntriesFromFile(path)` 동작:\n\n- 파일이 없는 경우 (`ENOENT`) -> `[]`를 반환합니다.\n- 파싱할 수 없는 줄은 관대한 JSONL 파서(`parseJsonlLenient`)에 의해 처리됩니다.\n- 첫 번째 파싱된 엔트리가 유효한 세션 헤더가 아닌 경우 (`type !== \"session\"` 또는 문자열 `id`가 누락) -> `[]`를 반환합니다.\n\n`SessionManager.setSessionFile()` 동작:\n\n- 로더에서 반환된 `[]`는 비어 있거나 존재하지 않는 세션으로 처리되며, 해당 경로에 새로 초기화된 세션 파일로 대체됩니다.\n- 유효한 파일은 로드되고, 필요한 경우 마이그레이션되며, 블롭 참조가 해결된 후 인덱싱됩니다.\n\n## 트리 및 리프 의미론\n\n기본 모델은 추가 전용 트리 + 변경 가능한 리프 포인터입니다:\n\n- 모든 추가 메서드는 현재 `leafId`를 `parentId`로 하는 새 엔트리를 정확히 하나 생성합니다.\n- 새 엔트리가 새로운 `leafId`가 됩니다.\n- `branch(entryId)`는 `leafId`만 이동합니다; 기존 엔트리는 변경되지 않습니다.\n- `resetLeaf()`는 `leafId = null`로 설정합니다; 다음 추가는 새로운 루트 엔트리(`parentId: null`)를 생성합니다.\n- `branchWithSummary()`는 리프를 브랜치 대상으로 설정하고 `branch_summary` 엔트리를 추가합니다.\n\n`getEntries()`는 모든 비헤더 엔트리를 삽입 순서대로 반환합니다. 기존 엔트리는 정상 운영 중에 삭제되지 않습니다; 재작성은 표현을 업데이트하면서 논리적 이력을 보존합니다(마이그레이션, 이동, 대상 지정 재작성 헬퍼).\n\n## 컨텍스트 재구성 (`buildSessionContext`)\n\n`buildSessionContext(entries, leafId, byId?)`는 모델에 전송되는 내용을 결정합니다.\n\n알고리즘:\n\n1. 리프 결정:\n   - `leafId === null` -> 빈 컨텍스트를 반환합니다.\n   - 명시적 `leafId` -> 해당 엔트리가 존재하면 사용합니다.\n   - 그 외의 경우 마지막 엔트리로 폴백합니다.\n2. 리프에서 루트까지 `parentId` 체인을 따라가고 루트->리프 경로로 역순 정렬합니다.\n3. 경로를 따라 런타임 상태를 도출합니다:\n   - `thinkingLevel`은 가장 최근 `thinking_level_change`에서 가져옵니다 (기본값 `\"off\"`)\n   - 모델 맵은 `model_change` 엔트리에서 가져옵니다 (`role ?? \"default\"`)\n   - 명시적 모델 변경이 없는 경우 어시스턴트 메시지 provider/model에서 폴백 `models.default`를 가져옵니다\n   - 모든 `ttsr_injection` 엔트리에서 중복 제거된 `injectedTtsrRules`\n   - 가장 최근 `mode_change`에서 mode/modeData (기본 모드 `\"none\"`)\n4. 메시지 목록 구성:\n   - `message` 엔트리는 그대로 전달됩니다\n   - `custom_message` 엔트리는 `createCustomMessage`를 통해 `custom` AgentMessages가 됩니다\n   - `branch_summary` 엔트리는 `createBranchSummaryMessage`를 통해 `branchSummary` AgentMessages가 됩니다\n   - 경로에 `compaction`이 존재하는 경우:\n     - 먼저 압축 요약을 발행합니다 (`createCompactionSummaryMessage`)\n     - `firstKeptEntryId`부터 압축 경계까지의 경로 엔트리를 발행합니다\n     - 압축 경계 이후의 엔트리를 발행합니다\n\n`custom` 및 `session_init` 엔트리는 모델 컨텍스트에 직접 주입되지 않습니다.\n\n## 영속성 보장 및 실패 모델\n\n### 영속 vs 인메모리\n\n- `SessionManager.create/open/continueRecent/forkFrom` -> 영속 모드 (`persist = true`).\n- `SessionManager.inMemory` -> 비영속 모드 (`persist = false`), `MemorySessionStorage` 사용.\n\n### 쓰기 파이프라인\n\n쓰기는 내부 프로미스 체인(`#persistChain`)과 `NdjsonFileWriter`를 통해 직렬화됩니다.\n\n- `append*`는 인메모리 상태를 즉시 업데이트합니다.\n- 영속화는 최소 하나의 어시스턴트 메시지가 존재할 때까지 지연됩니다.\n  - 첫 번째 어시스턴트 이전: 엔트리는 메모리에 유지됩니다; 파일 추가가 발생하지 않습니다.\n  - 첫 번째 어시스턴트가 존재할 때: 전체 인메모리 세션이 파일로 플러시됩니다.\n  - 이후: 새 엔트리가 점진적으로 추가됩니다.\n\n코드 내 근거: 어시스턴트 응답을 생성하지 않은 세션의 영속화를 방지합니다.\n\n### 내구성 연산\n\n- `flush()`는 라이터를 플러시하고 `fsync()`를 호출합니다.\n- 원자적 전체 재작성(`#rewriteFile`)은 임시 파일에 쓰고, flush+fsync, close, 그런 다음 대상 위에 rename합니다.\n- 마이그레이션, `setSessionName`, `rewriteEntries`, 이동 연산, 도구 호출 인자 재작성에 사용됩니다.\n\n### 오류 동작\n\n- 영속화 오류는 래치됩니다(`#persistError`), 후속 연산에서 다시 throw됩니다.\n- 첫 번째 오류는 세션 파일 컨텍스트와 함께 한 번만 로깅됩니다.\n- 라이터 close는 최선 노력(best-effort)이지만 첫 번째 의미 있는 오류를 전파합니다.\n\n## 데이터 크기 제어 및 블롭 외부화\n\n엔트리를 영속화하기 전에:\n\n- 큰 문자열은 `MAX_PERSIST_CHARS` (500,000자)로 잘리며 알림이 포함됩니다:\n  - `\"[Session persistence truncated large content]\"`\n- 일시적 필드 `partialJson` 및 `jsonlEvents`가 제거됩니다.\n- 객체에 `content`와 `lineCount`가 모두 있는 경우, 잘라내기 후 줄 수가 다시 계산됩니다.\n- `content` 배열의 이미지 블록 중 base64 길이가 >= 1024인 것은 블롭 참조로 외부화됩니다:\n  - `blob:sha256:<hash>`로 저장됩니다\n  - 원시 바이트는 블롭 저장소에 기록됩니다 (`BlobStore.put`)\n\n로드 시, 블롭 참조는 message/custom_message 이미지 블록에 대해 다시 base64로 해결됩니다.\n\n## 저장소 추상화\n\n`SessionStorage` 인터페이스는 `SessionManager`가 사용하는 모든 파일시스템 연산을 제공합니다:\n\n- 동기: `ensureDirSync`, `existsSync`, `writeTextSync`, `statSync`, `listFilesSync`\n- 비동기: `exists`, `readText`, `readTextPrefix`, `writeText`, `rename`, `unlink`, `openWriter`\n\n구현체:\n\n- `FileSessionStorage`: 실제 파일시스템 (Bun + node fs)\n- `MemorySessionStorage`: 테스트/비영속 세션을 위한 맵 기반 인메모리 구현\n\n`SessionStorageWriter`는 `writeLine`, `flush`, `fsync`, `close`, `getError`를 노출합니다.\n\n## 세션 탐색 유틸리티\n\n`session-manager.ts`에 정의되어 있습니다:\n\n- `getRecentSessions(sessionDir, limit)` -> UI/세션 선택기를 위한 경량 메타데이터\n- `findMostRecentSession(sessionDir)` -> mtime 기준 가장 최근 세션\n- `list(cwd, sessionDir?)` -> 하나의 프로젝트 범위 내 세션들\n- `listAll()` -> `~/.xcsh/agent/sessions` 하위 모든 프로젝트 범위의 세션들\n\n메타데이터 추출은 가능한 경우 접두사만 읽습니다 (`readTextPrefix(..., 4096)`).\n\n## 관련되지만 별개인 것: 프롬프트 히스토리 저장소\n\n`HistoryStorage` (`history-storage.ts`)는 세션 재생이 아닌 프롬프트 재호출/검색을 위한 별도의 SQLite 하위 시스템입니다.\n\n- DB: `~/.xcsh/agent/history.db`\n- 테이블: `history(id, prompt, created_at, cwd)`\n- FTS5 인덱스: 트리거로 유지되는 동기화와 함께 `history_fts`\n- 인메모리 마지막 프롬프트 캐시를 사용하여 연속된 동일 프롬프트를 중복 제거합니다\n- 비동기 삽입(`setImmediate`)으로 프롬프트 캡처가 턴 실행을 차단하지 않습니다\n\n대화 그래프/상태 재생에는 세션 파일을 사용하고, 프롬프트 히스토리 UX에는 `HistoryStorage`를 사용하십시오.\n",
	"ko/sessions/ttsr-injection-lifecycle.md": "---\ntitle: TTSR 주입 생명주기\ndescription: '컨텍스트 관리를 위한 TTSR (tool-use, tool-result, system-reminder) 주입 생명주기.'\nsidebar:\n  order: 9\n  label: TTSR 주입\ni18n:\n  sourceHash: d6179a286584\n  translator: machine\n---\n\n# TTSR 주입 생명주기\n\n이 문서는 규칙 탐색부터 스트림 중단, 재시도 주입, 확장 알림, 세션 상태 처리에 이르는 현재의 Time Traveling Stream Rules (TTSR) 런타임 경로를 다룹니다.\n\n## 구현 파일\n\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/export/ttsr.ts`](../../packages/coding-agent/src/export/ttsr.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/prompts/system/ttsr-interrupt.md`](../../packages/coding-agent/src/prompts/system/ttsr-interrupt.md)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/types.ts`](../../packages/coding-agent/src/extensibility/extensions/types.ts)\n- [`../src/extensibility/hooks/types.ts`](../../packages/coding-agent/src/extensibility/hooks/types.ts)\n- [`../src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n\n## 1. 탐색 피드 및 규칙 등록\n\n세션 생성 시, `createAgentSession()`은 탐색된 모든 규칙을 로드하고 `TtsrManager`를 구성합니다:\n\n```ts\nconst ttsrSettings = settings.getGroup(\"ttsr\");\nconst ttsrManager = new TtsrManager(ttsrSettings);\nconst rulesResult = await loadCapability<Rule>(ruleCapability.id, { cwd });\nfor (const rule of rulesResult.items) {\n  if (rule.ttsrTrigger) ttsrManager.addRule(rule);\n}\n```\n\n### 등록 전 중복 제거 동작\n\n`loadCapability(\"rules\")`는 `rule.name` 기준으로 선착순 의미론(우선순위가 높은 제공자 우선)으로 중복을 제거합니다. 가려진 중복 항목은 TTSR 등록 전에 제거됩니다.\n\n### `TtsrManager.addRule()` 동작\n\n다음의 경우 등록이 건너뛰어집니다:\n\n- `rule.ttsrTrigger`가 없는 경우\n- 동일한 `rule.name`을 가진 규칙이 이 매니저에 이미 등록된 경우\n- 정규식 컴파일이 실패하는 경우 (`new RegExp(rule.ttsrTrigger)`에서 예외 발생)\n\n유효하지 않은 정규식 트리거는 경고로 기록되고 무시됩니다; 세션 시작은 계속됩니다.\n\n### 설정 관련 주의사항\n\n`TtsrSettings.enabled`는 매니저에 로드되지만 현재 런타임 게이팅에서 확인되지 않습니다. 규칙이 존재하면 매칭이 여전히 실행됩니다.\n\n## 2. 스트리밍 모니터 생명주기\n\nTTSR 감지는 `AgentSession.#handleAgentEvent` 내에서 실행됩니다.\n\n### 턴 시작\n\n`turn_start` 시, 스트림 버퍼가 초기화됩니다:\n\n- `ttsrManager.resetBuffer()`\n\n### 스트림 중 (`message_update`)\n\n어시스턴트 업데이트가 도착하고 규칙이 존재하는 경우:\n\n- `text_delta`와 `toolcall_delta`를 모니터링\n- 델타를 매니저 버퍼에 추가\n- `check(buffer)` 호출\n\n`check()`는 등록된 규칙을 순회하며 반복 정책(`#canTrigger`)을 통과하는 모든 매칭 규칙을 반환합니다.\n\n## 3. 트리거 결정 및 즉시 중단 경로\n\n하나 이상의 규칙이 매칭되면:\n\n1. `markInjected(matches)`가 매니저 주입 상태에 규칙 이름을 기록합니다.\n2. 매칭된 규칙이 `#pendingTtsrInjections`에 대기열로 추가됩니다.\n3. `#ttsrAbortPending = true`로 설정됩니다.\n4. `agent.abort()`가 즉시 호출됩니다.\n5. `ttsr_triggered` 이벤트가 비동기적으로 발행됩니다(fire-and-forget).\n6. 재시도 작업이 `setTimeout(..., 50)`을 통해 예약됩니다.\n\n중단은 확장 콜백을 기다리지 않습니다.\n\n## 4. 재시도 스케줄링, 컨텍스트 모드, 리마인더 주입\n\n50ms 타임아웃 이후:\n\n1. `#ttsrAbortPending = false`\n2. `ttsrManager.getSettings().contextMode` 읽기\n3. `contextMode === \"discard\"`인 경우, `agent.popMessage()`로 부분 어시스턴트 출력 삭제\n4. `ttsr-interrupt.md` 템플릿을 사용하여 대기 중인 규칙으로부터 주입 콘텐츠 생성\n5. 규칙당 하나의 `<system-interrupt ...>` 블록을 포함하는 합성 사용자 메시지 추가\n6. `agent.continue()`를 호출하여 생성 재시도\n\n템플릿 페이로드:\n\n```xml\n<system-interrupt reason=\"rule_violation\" rule=\"{{name}}\" path=\"{{path}}\">\n...\n{{content}}\n</system-interrupt>\n```\n\n대기 중인 주입은 콘텐츠 생성 후 정리됩니다.\n\n### 부분 출력에 대한 `contextMode` 동작\n\n- `discard`: 부분/중단된 어시스턴트 메시지가 재시도 전에 제거됩니다.\n- `keep`: 부분 어시스턴트 출력이 대화 상태에 유지됩니다; 리마인더가 그 뒤에 추가됩니다.\n\n## 5. 반복 정책 및 간격 로직\n\n`TtsrManager`는 `#messageCount`와 규칙별 `lastInjectedAt`을 추적합니다.\n\n### `repeatMode: \"once\"`\n\n규칙은 주입 기록이 있으면 한 번만 트리거될 수 있습니다.\n\n### `repeatMode: \"after-gap\"`\n\n규칙은 다음 조건에서만 재트리거될 수 있습니다:\n\n- `messageCount - lastInjectedAt >= repeatGap`\n\n`messageCount`는 `turn_end` 시 증가하므로, 간격은 스트림 청크가 아닌 완료된 턴 단위로 측정됩니다.\n\n## 6. 이벤트 발행 및 확장/훅 인터페이스\n\n### 세션 이벤트\n\n`AgentSessionEvent`는 다음을 포함합니다:\n\n```ts\n{ type: \"ttsr_triggered\"; rules: Rule[] }\n```\n\n### 확장 러너\n\n`#emitSessionEvent()`는 이벤트를 다음으로 라우팅합니다:\n\n- 확장 리스너 (`ExtensionRunner.emit({ type: \"ttsr_triggered\", rules })`)\n- 로컬 세션 구독자\n\n### 훅 및 커스텀 도구 타이핑\n\n- 확장 API는 `on(\"ttsr_triggered\", ...)`를 노출합니다\n- 훅 API는 `on(\"ttsr_triggered\", ...)`를 노출합니다\n- 커스텀 도구는 `onSession({ reason: \"ttsr_triggered\", rules })`를 수신합니다\n\n### 인터랙티브 모드 렌더링 차이\n\n인터랙티브 모드는 TTSR 중단 중에 중단된 어시스턴트 중지 사유를 표시되는 실패로 보여주는 것을 억제하기 위해 `session.isTtsrAbortPending`을 사용하며, 이벤트가 도착하면 `TtsrNotificationComponent`를 렌더링합니다.\n\n## 7. 영속성 및 재개 상태 (현재 구현)\n\n`SessionManager`는 주입된 규칙 영속성에 대한 전체 스키마 지원을 갖추고 있습니다:\n\n- 항목 타입: `ttsr_injection`\n- 추가 API: `appendTtsrInjection(ruleNames)`\n- 조회 API: `getInjectedTtsrRules()`\n- 컨텍스트 재구성에 `SessionContext.injectedTtsrRules` 포함\n\n`TtsrManager`는 `restoreInjected(ruleNames)`를 통한 복원도 지원합니다.\n\n### 현재 연결 상태\n\n현재 런타임 경로에서:\n\n- `AgentSession`은 TTSR 트리거 시 `ttsr_injection` 항목을 추가하지 않습니다.\n- `createAgentSession()`은 `existingSession.injectedTtsrRules`를 `ttsrManager`에 다시 복원하지 않습니다.\n\n실질적 효과: 주입된 규칙 억제는 실행 중인 프로세스의 인메모리에서만 적용되며, 현재 이 경로를 통한 세션 리로드/재개 시 영속성/복원이 이루어지지 않습니다.\n\n## 8. 경쟁 조건 경계 및 순서 보장\n\n### 중단 vs 재시도 콜백\n\n- 중단은 TTSR 핸들러 관점에서 동기적입니다 (`agent.abort()`가 즉시 호출됨)\n- 재시도는 타이머로 지연됩니다 (`50ms`)\n- 확장 알림은 비동기적이며 의도적으로 중단/재시도 스케줄링 전에 대기하지 않습니다\n\n### 동일 스트림 윈도우 내 다중 매칭\n\n`check()`는 현재 매칭되는 모든 적격 규칙을 반환합니다. 이들은 다음 재시도 메시지에서 일괄 주입됩니다.\n\n### 중단과 계속 사이\n\n타이머 윈도우 동안 상태가 변경될 수 있습니다(사용자 중단, 모드 액션, 추가 이벤트). 재시도 호출은 최선 노력 방식입니다: `agent.continue().catch(() => {})`가 후속 오류를 흡수합니다.\n\n## 9. 엣지 케이스 요약\n\n- 유효하지 않은 `ttsr_trigger` 정규식: 경고와 함께 건너뛰어짐; 다른 규칙은 계속됩니다.\n- 능력 계층에서의 중복 규칙 이름: 우선순위가 낮은 중복 항목은 등록 전에 가려집니다.\n- 매니저 계층에서의 중복 이름: 두 번째 등록은 무시됩니다.\n- `contextMode: \"keep\"`: 위반하는 부분 출력이 리마인더 재시도 전에 컨텍스트에 남을 수 있습니다.\n- after-gap 반복은 `turn_end` 시의 턴 카운트 증가에 의존합니다; 턴 중간 청크는 간격 카운터를 진행시키지 않습니다.\n",
	"ko/tui/theme.md": "---\ntitle: 테마 레퍼런스\ndescription: '색상 토큰, 폰트 설정, 테마 커스터마이징을 포함한 TUI 테마 레퍼런스입니다.'\nsidebar:\n  order: 3\n  label: 테마\ni18n:\n  sourceHash: 7e962a7da157\n  translator: machine\n---\n\n# 테마 레퍼런스\n\n이 문서는 현재 코딩 에이전트에서 테마가 작동하는 방식을 설명합니다: 스키마, 로딩, 런타임 동작, 실패 모드를 포함합니다.\n\n## 테마 시스템이 제어하는 것\n\n테마 시스템이 구동하는 요소:\n\n- TUI 전반에 사용되는 전경/배경 색상 토큰\n- 마크다운 스타일링 어댑터 (`getMarkdownTheme()`)\n- 선택기/편집기/설정 목록 어댑터 (`getSelectListTheme()`, `getEditorTheme()`, `getSettingsListTheme()`)\n- 심볼 프리셋 + 심볼 오버라이드 (`unicode`, `nerd`, `ascii`)\n- 네이티브 하이라이터(`@f5-sales-demo/pi-natives`)가 사용하는 구문 강조 색상\n- 상태 라인 세그먼트 색상\n\n주요 구현: `src/modes/theme/theme.ts`.\n\n## 테마 JSON 구조\n\n테마 파일은 `theme.ts`의 런타임 스키마(`ThemeJsonSchema`)에 대해 검증되고 `src/modes/theme/theme-schema.json`에 미러링되는 JSON 객체입니다.\n\n최상위 필드:\n\n- `name` (필수)\n- `colors` (필수; 모든 색상 토큰 필수)\n- `vars` (선택; 재사용 가능한 색상 변수)\n- `export` (선택; HTML 내보내기 색상)\n- `symbols` (선택)\n  - `preset` (선택: `unicode | nerd | ascii`)\n  - `overrides` (선택: `SymbolKey`에 대한 키/값 오버라이드)\n\n색상 값이 허용하는 형식:\n\n- 16진수 문자열 (`\"#RRGGBB\"`)\n- 256색 인덱스 (`0..255`)\n- 변수 참조 문자열 (`vars`를 통해 해석)\n- 빈 문자열 (`\"\"`) - 터미널 기본값을 의미 (`\\x1b[39m` fg, `\\x1b[49m` bg)\n\n## 필수 색상 토큰 (현재)\n\n아래의 모든 토큰은 `colors`에 필수입니다.\n\n### 핵심 텍스트 및 테두리 (11)\n\n`accent`, `border`, `borderAccent`, `borderMuted`, `success`, `error`, `warning`, `muted`, `dim`, `text`, `thinkingText`\n\n### 배경 블록 (7)\n\n`selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`, `statusLineBg`\n\n### 메시지/도구 텍스트 (5)\n\n`userMessageText`, `customMessageText`, `customMessageLabel`, `toolTitle`, `toolOutput`\n\n### 마크다운 (10)\n\n`mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet`\n\n### 도구 diff + 구문 강조 (12)\n\n`toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext`,\n`syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation`\n\n### 모드/사고 테두리 (8)\n\n`thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh`, `bashMode`, `pythonMode`\n\n### 상태 라인 세그먼트 색상 (14)\n\n`statusLineSep`, `statusLineModel`, `statusLinePath`, `statusLineGitClean`, `statusLineGitDirty`, `statusLineContext`, `statusLineSpend`, `statusLineStaged`, `statusLineDirty`, `statusLineUntracked`, `statusLineOutput`, `statusLineCost`, `statusLineSubagents`\n\n## 선택 토큰\n\n### `export` 섹션 (선택)\n\nHTML 내보내기 테마 헬퍼에 사용됩니다:\n\n- `export.pageBg`\n- `export.cardBg`\n- `export.infoBg`\n\n생략된 경우, 내보내기 코드는 해석된 테마 색상에서 기본값을 도출합니다.\n\n### `symbols` 섹션 (선택)\n\n- `symbols.preset`은 테마 레벨의 기본 심볼 세트를 설정합니다.\n- `symbols.overrides`는 개별 `SymbolKey` 값을 오버라이드할 수 있습니다.\n\n런타임 우선순위:\n\n1. 설정의 `symbolPreset` 오버라이드 (설정된 경우)\n2. 테마 JSON의 `symbols.preset`\n3. 폴백 `\"unicode\"`\n\n유효하지 않은 오버라이드 키는 무시되고 로깅됩니다 (`logger.debug`).\n\n## 내장 vs 커스텀 테마 소스\n\n테마 조회 순서 (`loadThemeJson`):\n\n1. 내장 임베디드 테마 (`defaults/xcsh-dark.json` 및 `defaults/xcsh-light.json`이 `defaultThemes`로 컴파일됨)\n2. 커스텀 테마 파일: `<customThemesDir>/<name>.json`\n\n커스텀 테마 디렉토리는 `getCustomThemesDir()`에서 가져옵니다:\n\n- 기본값: `~/.xcsh/agent/themes`\n- `PI_CODING_AGENT_DIR`로 오버라이드 가능 (`$PI_CODING_AGENT_DIR/themes`)\n\n`getAvailableThemes()`는 내장 + 커스텀 이름을 병합하여 정렬된 결과를 반환하며, 이름 충돌 시 내장 테마가 우선합니다.\n\n## 로딩, 검증, 해석\n\n커스텀 테마 파일의 경우:\n\n1. JSON 읽기\n2. JSON 파싱\n3. `ThemeJsonSchema`에 대해 검증\n4. `vars` 참조를 재귀적으로 해석\n5. 해석된 값을 터미널 기능 모드에 따라 ANSI로 변환\n\n검증 동작:\n\n- 필수 색상 토큰 누락: 명시적 그룹화된 오류 메시지\n- 잘못된 토큰 타입/값: JSON 경로가 포함된 검증 오류\n- 알 수 없는 테마 파일: `Theme not found: <name>`\n\n변수 참조 동작:\n\n- 중첩 참조 지원\n- 누락된 변수 참조 시 예외 발생\n- 순환 참조 시 예외 발생\n\n## 터미널 색상 모드 동작\n\n색상 모드 감지 (`detectColorMode`):\n\n- `COLORTERM=truecolor|24bit` => truecolor\n- `WT_SESSION` => truecolor\n- `TERM`이 `dumb`, `linux`, 또는 빈 값 => 256color\n- 그 외 => truecolor\n\n변환 동작:\n\n- hex -> `Bun.color(..., \"ansi-16m\" | \"ansi-256\")`\n- numeric -> `38;5` / `48;5` ANSI\n- `\"\"` -> 기본 fg/bg 리셋\n\n## 런타임 전환 동작\n\n### 초기 테마 (`initTheme`)\n\n`main.ts`는 설정으로 테마를 초기화합니다:\n\n- `symbolPreset`\n- `colorBlindMode`\n- `theme.dark`\n- `theme.light`\n\n자동 테마 슬롯 선택은 `COLORFGBG` 배경 감지를 사용합니다:\n\n- `COLORFGBG`에서 배경 인덱스 파싱\n- `< 8` => 다크 슬롯 (`theme.dark`)\n- `>= 8` => 라이트 슬롯 (`theme.light`)\n- 파싱 실패 => 다크 슬롯\n\n설정 스키마의 현재 기본값:\n\n- `theme.dark = \"xcsh-dark\"`\n- `theme.light = \"xcsh-light\"`\n- `symbolPreset = \"unicode\"`\n- `colorBlindMode = false`\n\n### 명시적 전환 (`setTheme`)\n\n- 선택한 테마를 로드\n- 전역 `theme` 싱글턴 업데이트\n- 선택적으로 워처 시작\n- `onThemeChange` 콜백 트리거\n\n실패 시:\n\n- 내장 `dark`로 폴백\n- `{ success: false, error }` 반환\n\n### 미리보기 전환 (`previewTheme`)\n\n- 임시 미리보기 테마를 전역 `theme`에 적용\n- 자체적으로 영속화된 설정을 변경하지 **않음**\n- 폴백 대체 없이 성공/오류 반환\n\n설정 UI는 이를 라이브 미리보기에 사용하고, 취소 시 이전 테마를 복원합니다.\n\n## 워처 및 라이브 리로드\n\n워처가 활성화된 경우 (`setTheme(..., true)` / 대화형 초기화):\n\n- 커스텀 파일 경로 `<customThemesDir>/<currentTheme>.json`만 감시\n- 내장 테마는 사실상 감시되지 않음\n- 파일 `change`: 리로드 시도 (디바운스 적용)\n- 파일 `rename`/삭제: `dark`로 폴백, 워처 종료\n\n자동 모드는 `SIGWINCH` 리스너도 설치하며, 터미널 상태가 변경될 때 다크/라이트 슬롯 매핑을 재평가할 수 있습니다.\n\n## 색맹 모드 동작\n\n`colorBlindMode`는 런타임에 하나의 토큰만 변경합니다:\n\n- `toolDiffAdded`가 HSV 조정됨 (녹색이 파란색 쪽으로 이동)\n- 조정은 해석된 값이 16진수 문자열인 경우에만 적용됨\n\n다른 토큰은 변경되지 않습니다.\n\n## 테마 설정이 영속화되는 위치\n\n테마 관련 설정은 `Settings`에 의해 전역 설정 YAML에 영속화됩니다:\n\n- 경로: `<agentDir>/config.yml`\n- 기본 에이전트 디렉토리: `~/.xcsh/agent`\n- 실질적 기본 파일: `~/.xcsh/agent/config.yml`\n\n영속화되는 키:\n\n- `theme.dark`\n- `theme.light`\n- `symbolPreset`\n- `colorBlindMode`\n\n레거시 마이그레이션 존재: 이전 플랫 형식 `theme: \"name\"`은 휘도 감지를 기반으로 중첩된 `theme.dark` 또는 `theme.light`로 마이그레이션됩니다.\n\n## 커스텀 테마 만들기 (실용)\n\n1. 커스텀 테마 디렉토리에 파일을 생성합니다. 예: `~/.xcsh/agent/themes/my-theme.json`.\n2. `name`, 선택적 `vars`, 그리고 **모든 필수** `colors` 토큰을 포함합니다.\n3. 선택적으로 `symbols` 및 `export`를 포함합니다.\n4. 설정에서 테마를 선택합니다 (`Display -> Dark theme` 또는 `Display -> Light theme`) - 원하는 자동 슬롯에 따라 선택합니다.\n\n최소 뼈대. `colors`의 모든 키는 필수입니다 — 런타임 검증기(`additionalProperties: false`)는 누락된 키와 알 수 없는 키를 모두 거부합니다.\n제공된 레퍼런스 구현은\n[`packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json)\n및 [`xcsh-light.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-light.json)을 참조하세요.\n\n상태 라인에는 이슈 #242에 문서화된 두 가지 병렬 색상 시스템이 있습니다:\n\n- 16진수 텍스트 색상 (`statusLinePath`, `statusLineGitClean`, `statusLineGitDirty`,\n  `statusLineStaged`, `statusLineDirty`, `statusLineUntracked`)은 비-파워라인\n  렌더링을 구동합니다.\n- 256색 팔레트 인덱스 (`statusLine<Segment>Bg` / `statusLine<Segment>Fg`)는\n  파워라인 세그먼트 채우기를 구동합니다. 이들은 위의 16진수 키와 독립적이므로 —\n  둘 다 설정해야 합니다.\n\n```json\n{\n  \"name\": \"my-theme\",\n  \"vars\": {\n    \"accent\": \"#7aa2f7\",\n    \"muted\": 244\n  },\n  \"colors\": {\n    \"accent\": \"accent\",\n    \"chromeAccent\": \"accent\",\n    \"spinnerAccent\": \"accent\",\n    \"contentAccent\": \"muted\",\n    \"border\": \"#4c566a\",\n    \"borderAccent\": \"accent\",\n    \"borderMuted\": \"muted\",\n    \"success\": \"#9ece6a\",\n    \"error\": \"#f7768e\",\n    \"warning\": \"#e0af68\",\n    \"muted\": \"muted\",\n    \"dim\": 240,\n    \"gutterSuccess\": \"#7dcfff\",\n    \"gutterWarning\": \"#e0af68\",\n    \"text\": \"\",\n    \"thinkingText\": \"muted\",\n\n    \"selectedBg\": \"#2a2f45\",\n    \"userMessageBg\": \"#1f2335\",\n    \"userMessageText\": \"\",\n    \"customMessageBg\": \"#24283b\",\n    \"customMessageText\": \"\",\n    \"customMessageLabel\": \"accent\",\n    \"toolPendingBg\": \"#1f2335\",\n    \"toolSuccessBg\": \"#1f2d2a\",\n    \"toolErrorBg\": \"#2d1f2a\",\n    \"toolTitle\": \"\",\n    \"toolOutput\": \"muted\",\n\n    \"mdHeading\": \"accent\",\n    \"mdLink\": \"accent\",\n    \"mdLinkUrl\": \"muted\",\n    \"mdCode\": \"#c0caf5\",\n    \"mdCodeBlock\": \"#c0caf5\",\n    \"mdCodeBlockBorder\": \"muted\",\n    \"mdQuote\": \"muted\",\n    \"mdQuoteBorder\": \"muted\",\n    \"mdHr\": \"muted\",\n    \"mdListBullet\": \"accent\",\n\n    \"toolDiffAdded\": \"#9ece6a\",\n    \"toolDiffRemoved\": \"#f7768e\",\n    \"toolDiffContext\": \"muted\",\n\n    \"syntaxComment\": \"#565f89\",\n    \"syntaxKeyword\": \"#bb9af7\",\n    \"syntaxFunction\": \"#7aa2f7\",\n    \"syntaxVariable\": \"#c0caf5\",\n    \"syntaxString\": \"#9ece6a\",\n    \"syntaxNumber\": \"#ff9e64\",\n    \"syntaxType\": \"#2ac3de\",\n    \"syntaxOperator\": \"#89ddff\",\n    \"syntaxPunctuation\": \"#9aa5ce\",\n    \"syntaxControl\": \"#bb9af7\",\n\n    \"thinkingOff\": 240,\n    \"thinkingMinimal\": 244,\n    \"thinkingLow\": \"#7aa2f7\",\n    \"thinkingMedium\": \"#2ac3de\",\n    \"thinkingHigh\": \"#bb9af7\",\n    \"thinkingXhigh\": \"#f7768e\",\n\n    \"bashMode\": \"#2ac3de\",\n    \"pythonMode\": \"#bb9af7\",\n\n    \"statusLineBg\": \"#16161e\",\n    \"statusLineSep\": 240,\n    \"statusLineModel\": \"#bb9af7\",\n    \"statusLinePath\": \"#7aa2f7\",\n    \"statusLineGitClean\": \"#9ece6a\",\n    \"statusLineGitDirty\": \"#e0af68\",\n    \"statusLineContext\": \"#2ac3de\",\n    \"statusLineSpend\": \"#7dcfff\",\n    \"statusLineStaged\": \"#9ece6a\",\n    \"statusLineDirty\": \"#e0af68\",\n    \"statusLineUntracked\": \"#f7768e\",\n    \"statusLineOutput\": \"#c0caf5\",\n    \"statusLineCost\": \"#ff9e64\",\n    \"statusLineSubagents\": \"#bb9af7\",\n\n    \"statusLineOsIconBg\": 7,\n    \"statusLineOsIconFg\": 232,\n    \"statusLinePathBg\": 4,\n    \"statusLinePathFg\": 254,\n    \"statusLineGitCleanBg\": 2,\n    \"statusLineGitCleanFg\": 0,\n    \"statusLineGitDirtyBg\": 3,\n    \"statusLineGitDirtyFg\": 0,\n    \"statusLineGitStagedBg\": 64,\n    \"statusLineGitStagedFg\": 0,\n    \"statusLineGitUntrackedBg\": 39,\n    \"statusLineGitUntrackedFg\": 0,\n    \"statusLineGitConflictBg\": 1,\n    \"statusLineGitConflictFg\": 7,\n    \"statusLinePlanModeBg\": 236,\n    \"statusLinePlanModeFg\": 117,\n    \"statusLineProfileXcshBg\": \"accent\",\n    \"statusLineProfileXcshFg\": 231\n  }\n}\n```\n\n## 커스텀 테마 테스트\n\n다음 워크플로를 사용하세요:\n\n1. 대화형 모드를 시작합니다 (시작 시 워처 활성화).\n2. 설정을 열고 테마 값을 미리봅니다 (라이브 `previewTheme`).\n3. 커스텀 테마 파일의 경우, 실행 중에 JSON을 편집하고 저장 시 자동 리로드를 확인합니다.\n4. 주요 표면을 검증합니다:\n   - 마크다운 렌더링\n   - 도구 블록 (대기/성공/오류)\n   - diff 렌더링 (추가/삭제/컨텍스트)\n   - 상태 라인 가독성\n   - 사고 레벨 테두리 변경\n   - bash/python 모드 테두리 색상\n5. 테마가 글리프 너비/외관에 의존하는 경우 두 심볼 프리셋을 모두 검증합니다.\n\n## 실제 제약 사항 및 주의 사항\n\n- 커스텀 테마에는 모든 `colors` 토큰이 필수입니다.\n- `export` 및 `symbols`는 선택입니다.\n- 테마 JSON의 `$schema`는 정보 제공용이며; 런타임 검증은 코드 내 컴파일된 TypeBox 스키마에 의해 강제됩니다.\n- `setTheme` 실패 시 `dark`로 폴백합니다; `previewTheme` 실패 시 현재 테마를 대체하지 않습니다.\n- 파일 워처 리로드 오류 시 성공적인 리로드 또는 폴백 경로가 트리거될 때까지 현재 로드된 테마를 유지합니다.\n",
	"ko/tui/tree.md": "---\ntitle: Tree 명령어 레퍼런스\ndescription: 세션 기록과 대화 분기를 시각화하기 위한 /tree 명령어 레퍼런스.\nsidebar:\n  order: 4\n  label: /tree 명령어\ni18n:\n  sourceHash: ee0e412fe993\n  translator: machine\n---\n\n# `/tree` 명령어 레퍼런스\n\n`/tree`는 대화형 **세션 트리** 탐색기를 엽니다. 현재 세션 파일의 모든 항목으로 이동하여 해당 지점부터 계속할 수 있습니다.\n\n이것은 파일 내 리프 이동이며, 새로운 세션 내보내기가 아닙니다.\n\n## `/tree`의 기능\n\n- 현재 세션 항목에서 트리를 구축합니다 (`SessionManager.getTree()`)\n- 키보드 탐색, 필터, 검색 기능이 포함된 `TreeSelectorComponent`를 엽니다\n- 선택 시 `AgentSession.navigateTree(targetId, { summarize, customInstructions })`를 호출합니다\n- 새로운 리프 경로에서 보이는 채팅을 재구축합니다\n- user/custom 메시지를 선택할 때 선택적으로 에디터 텍스트를 미리 채웁니다\n\n주요 구현:\n\n- `src/modes/controllers/input-controller.ts` (`/tree`, 키바인딩 연결, 이중 Escape 동작)\n- `src/modes/controllers/selector-controller.ts` (트리 UI 실행 + 요약 프롬프트 흐름)\n- `src/modes/components/tree-selector.ts` (탐색, 필터, 검색, 레이블, 렌더링)\n- `src/session/agent-session.ts` (`navigateTree` 리프 전환 + 선택적 요약)\n- `src/session/session-manager.ts` (`getTree`, `branch`, `branchWithSummary`, `resetLeaf`, 레이블 영속성)\n\n## 여는 방법\n\n다음 중 하나로 동일한 선택기를 열 수 있습니다:\n\n- `/tree`\n- 설정된 키바인딩 액션 `tree`\n- 빈 에디터에서 이중 Escape (`doubleEscapeAction = \"tree\"`인 경우, 기본값)\n- `/branch` (`doubleEscapeAction = \"tree\"`인 경우, 사용자 전용 분기 선택기 대신 트리 선택기로 라우팅)\n\n## 트리 UI 모델\n\n트리는 세션 항목의 부모 포인터(`id` / `parentId`)에서 렌더링됩니다.\n\n- 자식은 타임스탬프 오름차순으로 정렬됩니다 (오래된 것이 먼저, 새로운 것이 아래)\n- 활성 분기(루트에서 현재 리프까지의 경로)는 불릿으로 표시됩니다\n- 레이블(있는 경우)은 노드 텍스트 앞에 `[label]`로 렌더링됩니다\n- 여러 루트가 존재하는 경우(고아/끊어진 부모 체인), 가상 분기 루트 아래에 표시됩니다\n\n```text\n트리 뷰 예시 (활성 경로는 •로 표시):\n\n├─ user: \"작업 시작\"\n│  └─ assistant: \"계획\"\n│     ├─ • user: \"접근법 A 시도\"\n│     │  └─ • assistant: \"A 결과\"\n│     │     └─ • [milestone] user: \"A 계속\"\n│     └─ user: \"접근법 B 시도\"\n│        └─ assistant: \"B 결과\"\n```\n\n선택기는 현재 선택 항목을 중심으로 재배치되며 최대 다음만큼의 행을 표시합니다:\n\n- `max(5, floor(terminalHeight / 2))` 행\n\n## 트리 선택기 내 키바인딩\n\n- `Up` / `Down`: 선택 이동 (순환)\n- `Left` / `Right`: 페이지 위 / 페이지 아래\n- `Enter`: 노드 선택\n- `Esc`: 검색이 활성화된 경우 검색 지우기; 그렇지 않으면 선택기 닫기\n- `Ctrl+C`: 선택기 닫기\n- `Type`: 검색 쿼리에 추가\n- `Backspace`: 검색 문자 삭제\n- `Shift+L`: 선택된 항목의 레이블 편집/지우기\n- `Ctrl+O`: 필터를 앞으로 순환\n- `Shift+Ctrl+O`: 필터를 뒤로 순환\n- `Alt+D/T/U/L/A`: 특정 필터 모드로 직접 이동\n\n## 필터 및 검색 의미론\n\n필터 모드 (`TreeList`):\n\n1. `default`\n2. `no-tools`\n3. `user-only`\n4. `labeled-only`\n5. `all`\n\n### `default`\n\n대부분의 대화 노드를 표시하지만 관리용 항목 유형은 숨깁니다:\n\n- `label`\n- `custom`\n- `model_change`\n- `thinking_level_change`\n\n### `no-tools`\n\n`default`와 동일하며, 추가로 `toolResult` 메시지를 숨깁니다.\n\n### `user-only`\n\n역할이 `user`인 `message` 항목만 표시합니다.\n\n### `labeled-only`\n\n현재 레이블로 확인되는 항목만 표시합니다.\n\n### `all`\n\n관리용/custom 항목을 포함하여 세션 트리의 모든 것을 표시합니다.\n\n### 도구 전용 어시스턴트 노드 동작\n\n**도구 호출만** 포함하고 텍스트가 없는 어시스턴트 메시지는 다음의 경우를 제외하고 모든 필터 뷰에서 기본적으로 숨겨집니다:\n\n- 메시지가 오류/중단됨 (`stopReason`이 `stop`/`toolUse`가 아닌 경우), 또는\n- 현재 리프인 경우 (항상 표시 유지)\n\n### 검색 동작\n\n- 쿼리는 공백으로 토큰화됩니다\n- 매칭은 대소문자를 구분하지 않습니다\n- 모든 토큰이 일치해야 합니다 (AND 의미론)\n- 검색 가능한 텍스트에는 레이블, 역할, 유형별 콘텐츠(메시지 텍스트, 분기 요약 텍스트, custom 유형, 도구 명령어 스니펫 등)가 포함됩니다\n\n## 선택 결과 (중요)\n\n`navigateTree`는 선택된 항목 유형에 따라 새로운 리프 동작을 계산합니다:\n\n### `user` 메시지 선택\n\n- 새로운 리프는 선택된 항목의 `parentId`가 됩니다\n- 부모가 `null`인 경우(루트 사용자 메시지), 리프는 루트로 재설정됩니다 (`resetLeaf()`)\n- 선택된 메시지 텍스트가 편집/재제출을 위해 에디터에 복사됩니다\n\n### `custom_message` 선택\n\n- 사용자 메시지와 동일한 리프 규칙 (`parentId`)\n- 텍스트 콘텐츠가 추출되어 에디터에 복사됩니다\n\n### 비사용자 노드 선택 (assistant/tool/summary/compaction/custom 관리용 등)\n\n- 새로운 리프는 선택된 노드 id가 됩니다\n- 에디터에 미리 채워지지 않습니다\n\n### 현재 리프 선택\n\n- 무동작; 선택기가 \"이미 이 지점에 있습니다\" 메시지와 함께 닫힙니다\n\n```text\n선택 결정 (간략화):\n\n선택된 노드\n   │\n   ├─ 현재 리프인가? ── 예 ──> 선택기 닫기 (무동작)\n   │\n   ├─ user/custom_message인가? ── 예 ──> leaf := parentId (루트의 경우 resetLeaf)\n   │                                     + 에디터 텍스트 미리 채우기\n   │\n   └─ 그 외 ──> leaf := 선택된 노드 id\n                    + 에디터 미리 채우기 없음\n```\n\n## 전환 시 요약 흐름\n\n요약 프롬프트는 `branchSummary.enabled`로 제어됩니다 (기본값: `false`).\n\n활성화된 경우, 노드를 선택한 후 UI가 다음을 묻습니다:\n\n- `요약 없음`\n- `요약`\n- `사용자 정의 프롬프트로 요약`\n\n흐름 세부 사항:\n\n- 요약 프롬프트에서 Escape를 누르면 트리 선택기가 다시 열립니다\n- 사용자 정의 프롬프트 취소 시 요약 선택 루프로 돌아갑니다\n- 요약 중에 UI는 로더를 표시하고 `Esc`를 `abortBranchSummary()`에 바인딩합니다\n- 요약이 중단되면 트리 선택기가 다시 열리고 이동이 적용되지 않습니다\n\n`navigateTree` 내부 동작:\n\n- 이전 리프에서 공통 조상까지 버려진 분기 항목을 수집합니다\n- `session_before_tree`를 발생시킵니다 (확장 기능이 취소하거나 요약을 삽입할 수 있음)\n- 요청되고 필요한 경우에만 기본 요약기를 사용합니다\n- 다음을 통해 이동을 적용합니다:\n  - 요약이 있는 경우 `branchWithSummary(...)`\n  - 요약 없는 비루트 이동의 경우 `branch(newLeafId)`\n  - 요약 없는 루트 이동의 경우 `resetLeaf()`\n- 에이전트 대화를 재구축된 세션 컨텍스트로 교체합니다\n- `session_tree`를 발생시킵니다\n\n참고: 사용자가 요약을 요청했지만 요약할 내용이 없는 경우, 요약 항목을 생성하지 않고 탐색이 진행됩니다.\n\n## 레이블\n\n트리 UI에서의 레이블 편집은 `appendLabelChange(targetId, label)`을 호출합니다.\n\n- 비어 있지 않은 레이블은 확인된 레이블을 설정/업데이트합니다\n- 빈 레이블은 이를 지웁니다\n- 레이블은 추가 전용 `label` 항목으로 저장됩니다\n- 트리 노드는 원시 레이블 항목 기록이 아닌 확인된 레이블 상태를 표시합니다\n\n## `/tree` vs 인접 작업\n\n| 작업 | 범위 | 결과 |\n|---|---|---|\n| `/tree` | 현재 세션 파일 | 선택된 지점으로 리프를 이동 (동일 파일) |\n| `/branch` | 보통 현재 세션 파일 -> 새 세션 파일 | 기본적으로 선택된 **사용자** 메시지에서 새 세션 파일로 분기; `doubleEscapeAction = \"tree\"`인 경우, `/branch`는 대신 트리 탐색 UI를 엽니다 |\n| `/fork` | 전체 현재 세션 | 세션을 새로운 영속 세션 파일로 복제 |\n| `/resume` | 세션 목록 | 다른 세션 파일로 전환 |\n\n핵심 구분: `/tree`는 하나의 세션 파일 내에서의 탐색/위치 변경 도구입니다. `/branch`, `/fork`, `/resume`는 모두 세션 파일 컨텍스트를 변경합니다.\n\n## 운영자 워크플로우\n\n### 현재 분기를 잃지 않고 이전 사용자 프롬프트에서 다시 실행\n\n1. `/tree`\n2. 이전 사용자 메시지를 검색/선택\n3. `요약 없음` 선택 (필요한 경우 요약)\n4. 에디터에 미리 채워진 텍스트 편집\n5. 제출\n\n효과: 동일 세션 파일 내에서 선택된 지점으로부터 새 분기가 성장합니다.\n\n### 컨텍스트 브레드크럼과 함께 현재 분기 떠나기\n\n1. `branchSummary.enabled` 활성화\n2. `/tree`로 대상 노드 선택\n3. `요약` 선택 (또는 사용자 정의 프롬프트)\n\n효과: 계속하기 전에 대상 위치에 `branch_summary` 항목이 추가됩니다.\n\n### 숨겨진 관리용 항목 조사\n\n1. `/tree`\n2. `Alt+A` 누르기 (all)\n3. `model`, `thinking`, `custom` 또는 레이블 검색\n\n효과: 대화 노드뿐만 아니라 전체 내부 타임라인을 검사합니다.\n\n### 나중에 이동할 피벗 포인트 북마크\n\n1. `/tree`\n2. 항목으로 이동\n3. `Shift+L`을 누르고 레이블 설정\n4. 나중에 `Alt+L` (`labeled-only`)을 사용하여 빠르게 이동\n\n효과: 지속적인 분기 랜드마크 간 빠른 탐색이 가능합니다.\n",
	"ko/tui/tui-runtime-internals.md": "---\ntitle: TUI 런타임 내부 구조\ndescription: '렌더링 파이프라인, 입력 처리, 상태 관리를 포함한 터미널 UI 런타임 내부 구조.'\nsidebar:\n  order: 2\n  label: 런타임 내부 구조\ni18n:\n  sourceHash: cc8f7dcce46a\n  translator: machine\n---\n\n# TUI 런타임 내부 구조\n\n이 문서는 대화형 모드에서 터미널 입력부터 렌더링된 출력까지의 테마 외 런타임 경로를 설명합니다. `packages/tui`의 동작과 `packages/coding-agent` 컨트롤러의 통합에 초점을 맞춥니다.\n\n## 런타임 계층 및 소유권\n\n- **`packages/tui` 엔진**: 터미널 생명주기, stdin 정규화, 포커스 라우팅, 렌더 스케줄링, 차등 페인팅, 오버레이 합성, 하드웨어 커서 배치.\n- **`packages/coding-agent` 대화형 모드**: 컴포넌트 트리 구성, 에디터 콜백 및 키맵 바인딩, 에이전트/세션 이벤트 반응, 도메인 상태(스트리밍, 도구 실행, 재시도, 플랜 모드)를 UI 구성 요소로 변환.\n\n경계 규칙: TUI 엔진은 메시지에 독립적입니다. `Component.render(width)`, `handleInput(data)`, 포커스, 오버레이만 알고 있습니다. 에이전트 시맨틱은 대화형 컨트롤러에 유지됩니다.\n\n## 구현 파일\n\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/components/custom-editor.ts`](../../packages/coding-agent/src/modes/components/custom-editor.ts)\n- [`../../tui/src/tui.ts`](../../packages/tui/src/tui.ts)\n- [`../../tui/src/terminal.ts`](../../packages/tui/src/terminal.ts)\n- [`../../tui/src/editor-component.ts`](../../packages/tui/src/editor-component.ts)\n- [`../../tui/src/stdin-buffer.ts`](../../packages/tui/src/stdin-buffer.ts)\n- [`../../tui/src/components/loader.ts`](../../packages/tui/src/components/loader.ts)\n\n## 부팅 및 컴포넌트 트리 조립\n\n`InteractiveMode`는 `TUI(new ProcessTerminal(), showHardwareCursor)`를 생성하고 다음과 같은 영구 컨테이너를 만듭니다:\n\n- `chatContainer`\n- `pendingMessagesContainer`\n- `statusContainer`\n- `todoContainer`\n- `statusLine`\n- `editorContainer` (`CustomEditor` 포함)\n\n`init()`은 해당 순서로 트리를 연결하고, 에디터에 포커스를 설정하며, `InputController`를 통해 입력 핸들러를 등록하고, TUI를 시작한 후 강제 렌더를 요청합니다.\n\n강제 렌더(`requestRender(true)`)는 다시 페인팅하기 전에 이전 줄 캐시와 커서 북키핑을 초기화합니다.\n\n## 터미널 생명주기 및 stdin 정규화\n\n`ProcessTerminal.start()`:\n\n1. 원시 모드 및 브래킷 붙여넣기를 활성화합니다.\n2. 리사이즈 핸들러를 연결합니다.\n3. 부분 이스케이프 청크를 완전한 시퀀스로 분리하기 위해 `StdinBuffer`를 생성합니다.\n4. Kitty 키보드 프로토콜 지원을 쿼리(`CSI ? u`)한 후, 지원되면 프로토콜 플래그를 활성화합니다.\n5. Windows에서는 `kernel32` 모드 플래그를 통해 VT 입력 활성화를 시도합니다.\n\n`StdinBuffer` 동작:\n\n- 분열된 이스케이프 시퀀스(CSI/OSC/DCS/APC/SS3)를 버퍼링합니다.\n- 시퀀스가 완료되거나 타임아웃으로 플러시될 때만 `data`를 방출합니다.\n- 브래킷 붙여넣기를 감지하고 원시 붙여넣기 텍스트와 함께 `paste` 이벤트를 방출합니다.\n\n이를 통해 부분 이스케이프 청크가 일반 키 입력으로 잘못 해석되는 것을 방지합니다.\n\n## 입력 라우팅 및 포커스 모델\n\n입력 경로:\n\n`stdin -> ProcessTerminal -> StdinBuffer -> TUI.#handleInput -> focusedComponent.handleInput`\n\n라우팅 세부 사항:\n\n1. TUI는 등록된 입력 리스너를 먼저 실행하여(`addInputListener`) 소비/변환 동작을 허용합니다.\n2. TUI는 컴포넌트 디스패치 전에 전역 디버그 단축키(`shift+ctrl+d`)를 처리합니다.\n3. 포커스된 컴포넌트가 이제 숨겨지거나 보이지 않는 오버레이에 속하면, TUI는 포커스를 다음 보이는 오버레이 또는 저장된 오버레이 이전 포커스로 재할당합니다.\n4. 포커스된 컴포넌트가 `wantsKeyRelease = true`로 설정하지 않는 한 키 해제 이벤트는 필터링됩니다.\n5. 디스패치 후 TUI는 렌더를 스케줄링합니다.\n\n`setFocus()`는 `Focusable.focused`도 전환하여 컴포넌트가 하드웨어 커서 배치를 위해 `CURSOR_MARKER`를 방출할지 여부를 제어합니다.\n\n## 키 처리 분리: 에디터 vs 컨트롤러\n\n`CustomEditor`는 우선순위가 높은 조합(escape, ctrl-c/d/z, ctrl-v, ctrl-p 변형, ctrl-t, alt-up, 확장 사용자 지정 키)을 먼저 가로채고, 나머지는 기본 `Editor` 동작(텍스트 편집, 히스토리, 자동 완성, 커서 이동)에 위임합니다.\n\n`InputController.setupKeyHandlers()`는 에디터 콜백을 모드 액션에 바인딩합니다:\n\n- `Escape`에서 취소/모드 종료\n- 이중 `Ctrl+C` 또는 빈 에디터 `Ctrl+D`에서 종료\n- `Ctrl+Z`에서 일시 중단/재개\n- 슬래시 명령 및 선택기 단축키\n- 후속/대기열 제거 토글 및 확장 토글\n\n이를 통해 키 파싱/에디터 메커니즘은 `packages/tui`에 유지되고 모드 시맨틱은 coding-agent 컨트롤러에 유지됩니다.\n\n## 렌더 루프 및 diff 전략\n\n`TUI.requestRender()`는 `process.nextTick`을 사용하여 틱당 하나의 렌더로 디바운싱됩니다. 동일한 턴에서 여러 상태 변경이 병합됩니다.\n\n`#doRender()` 파이프라인:\n\n1. 루트 컴포넌트 트리를 `newLines`로 렌더링합니다.\n2. 보이는 오버레이가 있으면 합성합니다.\n3. 보이는 뷰포트 줄에서 `CURSOR_MARKER`를 추출하고 제거합니다.\n4. 비이미지 줄에 세그먼트 리셋 접미사를 추가합니다.\n5. 전체 재페인트와 차등 패치 중에서 선택합니다:\n   - 첫 번째 프레임\n   - 너비 변경\n   - `clearOnShrink`가 활성화되고 오버레이가 없는 상태에서 축소\n   - 이전 뷰포트 위의 편집\n6. 차등 업데이트의 경우 변경된 줄 범위만 패치하고, 필요한 경우 오래된 후행 줄을 지웁니다.\n7. IME 지원을 위해 하드웨어 커서를 재배치합니다.\n\n렌더 쓰기는 플리커/티어링을 줄이기 위해 동기화된 출력 모드(`CSI ? 2026 h/l`)를 사용합니다.\n\n## 렌더 안전 제약\n\n`TUI`의 중요 안전 검사:\n\n- 비이미지 렌더링 줄은 터미널 너비를 초과해서는 안 됩니다. 오버플로우 시 예외를 발생시키고 크래시 진단을 작성합니다.\n- 오버레이 합성에는 방어적 잘라내기와 합성 후 너비 검증이 포함됩니다.\n- 너비 변경은 줄 바꿈 시맨틱이 변경되기 때문에 전체 재그리기를 강제합니다.\n- 커서 위치는 이동 전에 클램핑됩니다.\n\n이러한 제약은 단순한 관례가 아닌 런타임 적용 사항입니다.\n\n## 리사이즈 처리\n\n리사이즈 이벤트는 `ProcessTerminal`에서 `TUI.requestRender()`로 이벤트 기반으로 처리됩니다.\n\n효과:\n\n- 너비 변경 시 전체 재그리기가 트리거됩니다.\n- 뷰포트/상단 추적(`#previousViewportTop`, `#maxLinesRendered`)은 콘텐츠나 터미널 크기가 변경될 때 잘못된 상대 커서 계산을 방지합니다.\n- 오버레이 가시성은 터미널 크기에 의존할 수 있으며(`OverlayOptions.visible`), 리사이즈 후 오버레이가 보이지 않게 되면 포커스가 수정됩니다.\n\n## 스트리밍 및 증분 UI 업데이트\n\n`EventController`는 `AgentSessionEvent`를 구독하고 UI를 증분 방식으로 업데이트합니다:\n\n- `agent_start`: `statusContainer`에서 로더를 시작합니다.\n- `message_start` 어시스턴트: `streamingComponent`를 생성하고 마운트합니다.\n- `message_update`: 스트리밍 어시스턴트 콘텐츠를 업데이트하고, 도구 호출이 나타날 때 도구 실행 구성 요소를 생성/업데이트합니다.\n- `tool_execution_update/end`: 도구 결과 구성 요소와 완료 상태를 업데이트합니다.\n- `message_end`: 어시스턴트 스트림을 완료하고, 중단/오류 주석을 처리하며, 정상 중지 시 보류 중인 도구 인수를 완료로 표시합니다.\n- `agent_end`: 로더를 중지하고, 임시 스트림 상태를 지우며, 지연된 모델 전환을 플러시하고, 백그라운드 상태이면 완료 알림을 발행합니다.\n\n읽기 도구 그룹화는 의도적으로 상태를 유지하며(`#lastReadGroup`), 비읽기 중단이 발생할 때까지 연속적인 읽기 도구 호출을 하나의 시각적 블록으로 병합합니다.\n\n## 상태 및 로더 오케스트레이션\n\n상태 레인 소유권:\n\n- `statusContainer`는 임시 로더(`loadingAnimation`, `autoCompactionLoader`, `retryLoader`)를 보유합니다.\n- `statusLine`은 영구 상태/훅/플랜 표시기를 렌더링하고 에디터 상단 테두리 업데이트를 구동합니다.\n\n로더 동작:\n\n- `Loader`는 인터벌을 통해 80ms마다 업데이트하고 각 프레임에서 렌더를 요청합니다.\n- 자동 압축 및 자동 재시도 중에는 이스케이프 핸들러가 해당 작업을 취소하기 위해 일시적으로 재정의됩니다.\n- 종료/취소 경로에서 컨트롤러는 이전 이스케이프 핸들러를 복원하고 로더 구성 요소를 중지/지웁니다.\n\n## 모드 전환 및 백그라운드 처리\n\n### Bash/Python 입력 모드\n\n입력 텍스트 접두사가 에디터 테두리 모드 플래그를 전환합니다:\n\n- `!` -> bash 모드\n- `$` (비템플릿 리터럴 접두사) -> python 모드\n\nEscape는 에디터 텍스트를 지우고 테두리 색상을 복원하여 비활성 모드를 종료합니다. 실행이 활성 상태일 때 Escape는 실행 중인 작업을 대신 중단합니다.\n\n### 플랜 모드\n\n`InteractiveMode`는 플랜 모드 플래그, 상태 줄 상태, 활성 도구, 모델 전환을 추적합니다. 진입/종료 시 세션 모드 항목과 상태/UI 상태를 업데이트하며, 스트리밍이 활성 상태이면 지연된 모델 전환을 포함합니다.\n\n### 일시 중단/재개 (`Ctrl+Z`)\n\n`InputController.handleCtrlZ()`:\n\n1. TUI를 다시 시작하고 강제 렌더하기 위해 일회성 `SIGCONT` 핸들러를 등록합니다.\n2. 일시 중단 전에 TUI를 중지합니다.\n3. 프로세스 그룹에 `SIGTSTP`를 전송합니다.\n\n### 백그라운드 모드 (`/background` 또는 `/bg`)\n\n`handleBackgroundCommand()`:\n\n- 유휴 상태일 때 거부합니다.\n- 도구 UI 컨텍스트를 비대화형(`hasUI=false`)으로 전환하여 대화형 UI 도구가 빠르게 실패하도록 합니다.\n- 로더/상태 줄을 중지하고 포그라운드 이벤트 핸들러의 구독을 취소합니다.\n- 백그라운드 이벤트 핸들러를 구독합니다(주로 `agent_end`를 기다림).\n- TUI를 중지하고 `SIGTSTP`를 전송합니다(POSIX 작업 제어 경로).\n\n대기 중인 작업 없이 백그라운드에서 `agent_end` 발생 시, 컨트롤러는 완료 알림을 전송하고 종료합니다.\n\n## 취소 경로\n\n주요 취소 입력:\n\n- 활성 스트림 로더 중 `Escape`: 대기 중인 메시지를 에디터에 복원하고 에이전트를 중단합니다.\n- bash/python 실행 중 `Escape`: 실행 중인 명령을 중단합니다.\n- 자동 압축/재시도 중 `Escape`: 임시 이스케이프 핸들러를 통해 전용 중단 메서드를 호출합니다.\n- `Ctrl+C` 단일 누름: 에디터 지우기; 500ms 내 두 번 누름: 종료.\n\n취소는 상태 조건부입니다. 동일한 키가 런타임 상태에 따라 중단, 모드 종료, 선택기 트리거, 또는 아무 동작 없음을 의미할 수 있습니다.\n\n## 이벤트 기반 vs 스로틀 동작\n\n이벤트 기반 업데이트:\n\n- 에이전트 세션 이벤트(`EventController`)\n- 키 입력 콜백(`InputController`)\n- 터미널 리사이즈 콜백\n- `InteractiveMode`의 테마/브랜치 감시자\n\n스로틀/디바운스 경로:\n\n- TUI 렌더링은 틱 디바운싱됩니다(`requestRender` 병합).\n- 로더 애니메이션은 고정 인터벌(80ms)로, 각 프레임에서 렌더를 요청합니다.\n- 에디터 자동 완성 업데이트(`Editor` 내부)는 디바운스 타이머를 사용하여 타이핑 중 재계산 오버헤드를 줄입니다.\n\n따라서 런타임은 이벤트 기반 상태 전환과 제한된 렌더 주기를 혼합하여 재페인트 폭풍 없이 대화형 응답성을 유지합니다.\n",
	"ko/tui/tui.md": "---\ntitle: 확장 기능 및 커스텀 도구를 위한 TUI 통합\ndescription: '확장 기능, 커스텀 도구, 커스텀 렌더러를 위한 TUI 통합 계약.'\nsidebar:\n  order: 1\n  label: 확장 기능 통합\ni18n:\n  sourceHash: 47f8f2b2045e\n  translator: machine\n---\n\n# 확장 기능 및 커스텀 도구를 위한 TUI 통합\n\n이 문서는 확장 UI, 커스텀 도구 UI, 커스텀 렌더러를 위해 `packages/coding-agent`와 `packages/tui`에서 사용하는 **현재** TUI 계약을 다룹니다.\n\n## 이 서브시스템이란\n\n런타임은 두 개의 레이어로 구성됩니다:\n\n- **렌더링 엔진 (`packages/tui`)**: 차분 터미널 렌더러, 입력 디스패치, 포커스, 오버레이, 커서 배치.\n- **통합 레이어 (`packages/coding-agent`)**: 확장/커스텀 도구 컴포넌트를 마운트하고, 키바인딩/테마를 연결하며, 에디터 상태를 복원합니다.\n\n## 모드별 런타임 동작\n\n| 모드 | `ctx.ui.custom(...)` 사용 가능 여부 | 비고 |\n| --- | --- | --- |\n| 인터랙티브 TUI | 지원됨 | 컴포넌트가 에디터 영역에 마운트되고 포커스되며, 반드시 `done(result)`를 호출하여 resolve해야 합니다. |\n| 백그라운드/헤드리스 | 비인터랙티브 | UI 컨텍스트는 no-op입니다 (`hasUI === false`). |\n| RPC 모드 | 지원되지 않음 | `custom()`은 `Promise<never>`를 반환하며 TUI 컴포넌트를 마운트하지 않습니다. |\n\n확장/도구가 비인터랙티브 모드에서 실행될 수 있다면, `ctx.hasUI` / `pi.hasUI`로 가드하세요.\n\n## 핵심 컴포넌트 계약 (`@f5-sales-demo/pi-tui`)\n\n`packages/tui/src/tui.ts`에서 정의합니다:\n\n```ts\nexport interface Component {\n  render(width: number): string[];\n  handleInput?(data: string): void;\n  wantsKeyRelease?: boolean;\n  invalidate(): void;\n}\n```\n\n`Focusable`은 별도입니다:\n\n```ts\nexport interface Focusable {\n  focused: boolean;\n}\n```\n\n커서 동작은 `CURSOR_MARKER`를 사용합니다 (`getCursorPosition`이 아님). 포커스된 컴포넌트는 렌더링된 텍스트에 마커를 출력하고, `TUI`가 이를 추출하여 하드웨어 커서를 위치시킵니다.\n\n## 렌더링 제약 조건 (터미널 안전성)\n\n`render(width)` 출력은 반드시 터미널에 안전해야 합니다:\n\n1. **어떤 라인에서도 `width`를 초과하지 마세요**. 이미지가 아닌 라인이 오버플로우되면 렌더러가 에러를 던집니다.\n2. **문자열 길이가 아닌 시각적 너비를 측정하세요**: `visibleWidth()`를 사용합니다.\n3. **ANSI 인식 텍스트를 잘라내거나 줄바꿈하세요**: `truncateToWidth()` / `wrapTextWithAnsi()`를 사용합니다.\n4. **외부 소스의 탭/콘텐츠를 정제하세요**: `replaceTabs()`를 사용합니다 (그리고 coding-agent 렌더 경로의 상위 레벨 정제기도 사용합니다).\n\n최소 패턴:\n\n```ts\nimport { replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\n\nrender(width: number): string[] {\n  return this.lines.map(line => truncateToWidth(replaceTabs(line), width));\n}\n```\n\n## 입력 처리 및 키바인딩\n\n### 원시 키 매칭\n\n탐색 키와 조합에는 `matchesKey(data, \"...\")`를 사용하세요.\n\n### 사용자 구성 앱 키바인딩 존중\n\n확장 UI 팩토리는 `KeybindingsManager`(인터랙티브 모드)를 수신하므로, 키를 하드코딩하는 대신 매핑된 액션을 사용할 수 있습니다:\n\n```ts\nif (keybindings.matches(data, \"interrupt\")) {\n  done(undefined);\n  return;\n}\n```\n\n### 키 릴리스/반복 이벤트\n\n키 릴리스 이벤트는 컴포넌트에서 다음을 설정하지 않으면 필터링됩니다:\n\n```ts\nwantsKeyRelease = true;\n```\n\n필요한 경우 `isKeyRelease()` / `isKeyRepeat()`를 사용하세요.\n\n## 포커스, 오버레이, 커서\n\n- `TUI.setFocus(component)`는 해당 컴포넌트로 입력을 라우팅합니다.\n- 오버레이 API는 `TUI`에 존재하지만 (`showOverlay`, `OverlayHandle`), 인터랙티브 모드에서 확장 `ctx.ui.custom` 마운팅은 현재 에디터 컴포넌트 영역을 직접 교체합니다.\n- `custom(..., options?: { overlay?: boolean })` 옵션은 확장 타입에 존재하지만, 현재 인터랙티브 확장 마운팅에서는 이 옵션을 무시합니다.\n\n## 마운트 포인트 및 반환 계약\n\n## 1) 확장 UI (`ExtensionUIContext`)\n\n현재 시그니처 (`extensibility/extensions/types.ts`):\n\n```ts\ncustom<T>(\n  factory: (\n    tui: TUI,\n    theme: Theme,\n    keybindings: KeybindingsManager,\n    done: (result: T) => void,\n  ) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n  options?: { overlay?: boolean },\n): Promise<T>\n```\n\n인터랙티브 모드에서의 동작 (`extension-ui-controller.ts`):\n\n- 에디터 텍스트를 저장합니다.\n- 에디터 컴포넌트를 사용자의 컴포넌트로 교체합니다.\n- 사용자의 컴포넌트에 포커스를 줍니다.\n- `done(result)` 호출 시: `component.dispose?.()`를 호출하고, 에디터와 텍스트를 복원하며, 에디터에 포커스를 주고, 프로미스를 resolve합니다.\n\n따라서 `done(...)`은 완료를 위해 필수입니다.\n\n## 2) 훅/커스텀 도구 UI 컨텍스트 (레거시 타이핑)\n\n`HookUIContext.custom`은 훅/커스텀 도구 타입에서 `(tui, theme, done)`으로 타이핑되어 있습니다.\n내부 인터랙티브 구현은 팩토리를 `(tui, theme, keybindings, done)`으로 호출합니다. JS 소비자는 추가 인자를 사용할 수 있으며, 타입 레벨 호환성은 여전히 3인자 레거시 시그니처를 반영합니다.\n\n커스텀 도구는 일반적으로 팩토리 스코프의 `pi.ui` 객체를 통해 동일한 UI 진입점을 사용한 다음, 선택된 값을 일반 도구 콘텐츠로 반환합니다:\n\n```ts\nasync execute(toolCallId, params, onUpdate, ctx, signal) {\n  if (!pi.hasUI) {\n    return { content: [{ type: \"text\", text: \"UI unavailable\" }] };\n  }\n\n  const picked = await pi.ui.custom<string | undefined>((tui, theme, done) => {\n    const component = new MyPickerComponent(done, signal);\n    return component;\n  });\n\n  return { content: [{ type: \"text\", text: picked ? `Picked: ${picked}` : \"Cancelled\" }] };\n}\n```\n\n## 3) 커스텀 도구 호출/결과 렌더러\n\n커스텀 도구와 확장 도구는 다음에서 컴포넌트를 반환할 수 있습니다:\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\n`options`에는 현재 다음이 포함됩니다:\n\n- `expanded: boolean`\n- `isPartial: boolean`\n- `spinnerFrame?: number`\n\n이러한 렌더러는 `ToolExecutionComponent`에 의해 마운트됩니다.\n\n## 생명주기 및 취소\n\n- `dispose()`는 타입 레벨에서 선택 사항이지만, 타이머, 서브프로세스, 워처, 소켓, 또는 오버레이를 소유하는 경우 구현해야 합니다.\n- `done(...)`은 컴포넌트 플로우에서 정확히 한 번 호출되어야 합니다.\n- 취소 가능한 장시간 실행 UI의 경우, `CancellableLoader`와 `AbortSignal`을 결합하고 `onAbort`에서 `done(...)`을 호출하세요.\n\n취소 패턴 예시:\n\n```ts\nconst loader = new CancellableLoader(tui, theme.fg(\"accent\"), theme.fg(\"muted\"), \"Working...\");\nloader.onAbort = () => done(undefined);\nvoid doWork(loader.signal).then(result => done(result));\nreturn loader;\n```\n\n## 실제 커스텀 컴포넌트 예시 (확장 명령)\n\n```ts\nimport type { Component } from \"@f5-sales-demo/pi-tui\";\nimport { SelectList, matchesKey, replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\nimport { getSelectListTheme, type ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nclass Picker implements Component {\n  list: SelectList;\n  keybindings: any;\n  done: (value: string | undefined) => void;\n\n  constructor(\n    items: Array<{ value: string; label: string }>,\n    keybindings: any,\n    done: (value: string | undefined) => void,\n  ) {\n    this.list = new SelectList(items, 8, getSelectListTheme());\n    this.keybindings = keybindings;\n    this.done = done;\n    this.list.onSelect = item => this.done(item.value);\n    this.list.onCancel = () => this.done(undefined);\n  }\n\n  handleInput(data: string): void {\n    if (this.keybindings.matches(data, \"interrupt\")) {\n      this.done(undefined);\n      return;\n    }\n    this.list.handleInput(data);\n  }\n\n  render(width: number): string[] {\n    return this.list.render(width).map(line => truncateToWidth(replaceTabs(line), width));\n  }\n\n  invalidate(): void {\n    this.list.invalidate();\n  }\n}\n\nexport default function extension(pi: ExtensionAPI): void {\n  pi.registerCommand(\"pick-model\", {\n    description: \"Pick a model profile\",\n    handler: async (_args, ctx) => {\n      if (!ctx.hasUI) return;\n\n      const selected = await ctx.ui.custom<string | undefined>((tui, theme, keybindings, done) => {\n        const items = [\n          { value: \"fast\", label: theme.fg(\"accent\", \"Fast\") },\n          { value: \"balanced\", label: \"Balanced\" },\n          { value: \"quality\", label: \"Quality\" },\n        ];\n        return new Picker(items, keybindings, done);\n      });\n\n      if (selected) ctx.ui.notify(`Selected profile: ${selected}`, \"info\");\n    },\n  });\n}\n```\n\n## 주요 구현 파일\n\n- `packages/tui/src/tui.ts` — `Component`, `Focusable`, 커서 마커, 포커스, 오버레이, 입력 디스패치.\n- `packages/tui/src/utils.ts` — 너비/잘라내기/정제 프리미티브.\n- `packages/tui/src/keys.ts` / `keybindings.ts` — 키 파싱 및 구성 가능한 액션 매핑.\n- `packages/coding-agent/src/modes/controllers/extension-ui-controller.ts` — 확장/훅/커스텀 도구 UI의 인터랙티브 마운팅/언마운팅.\n- `packages/coding-agent/src/extensibility/extensions/types.ts` — 확장 UI 및 렌더러 계약.\n- `packages/coding-agent/src/extensibility/hooks/types.ts` — 훅 UI 계약 (레거시 custom 시그니처).\n- `packages/coding-agent/src/extensibility/custom-tools/types.ts` — 커스텀 도구 execute/render 계약.\n- `packages/coding-agent/src/modes/components/tool-execution.ts` — `renderCall`/`renderResult` 컴포넌트 마운팅 및 부분 상태 옵션.\n- `packages/coding-agent/src/tools/context.ts` — 도구 UI 컨텍스트 전파 (`hasUI`, `ui`).\n",
	"pt-br/configuration/blob-artifact-architecture.md": "---\ntitle: Arquitetura de Armazenamento de Blobs e Artefatos\ndescription: >-\n  Armazenamento de blobs endereçável por conteúdo e registro de artefatos para\n  mídia de sessão, capturas de tela e saídas de ferramentas.\nsidebar:\n  order: 7\n  label: Armazenamento de blobs e artefatos\ni18n:\n  sourceHash: 70d255f48d5b\n  translator: machine\n---\n\n# Arquitetura de armazenamento de blobs e artefatos\n\nEste documento descreve como o coding-agent armazena payloads grandes/binários fora do JSONL de sessão, como a saída truncada de ferramentas é persistida e como as URLs internas (`artifact://`, `agent://`) resolvem de volta para os dados armazenados.\n\n## Por que dois sistemas de armazenamento existem\n\nO runtime utiliza dois mecanismos de persistência diferentes para diferentes formatos de dados:\n\n- **Blobs endereçados por conteúdo** (`blob:sha256:<hash>`): armazenamento global orientado a binários, usado para externalizar payloads base64 de imagens grandes das entradas de sessão persistidas.\n- **Artefatos com escopo de sessão** (arquivos sob `<arquivoDeSessão-sem-.jsonl>/`): arquivos de texto por sessão usados para saídas completas de ferramentas e saídas de subagentes.\n\nEles são intencionalmente separados:\n\n- o armazenamento de blobs otimiza a deduplicação e referências estáveis por hash de conteúdo,\n- o armazenamento de artefatos otimiza ferramentas de sessão append-only e recuperação por humanos/ferramentas através de IDs locais.\n\n## Limites de armazenamento e layout em disco\n\n## Limite do armazenamento de blobs (global)\n\n`SessionManager` constrói `BlobStore(getBlobsDir())`, então os arquivos de blob ficam em um diretório global compartilhado de blobs (não em uma pasta de sessão).\n\nNomenclatura de arquivos de blob:\n\n- caminho do arquivo: `<blobsDir>/<sha256-hex>`\n- sem extensão\n- string de referência armazenada nas entradas: `blob:sha256:<sha256-hex>`\n\nImplicações:\n\n- o mesmo conteúdo binário entre sessões resolve para o mesmo hash/caminho,\n- escritas são idempotentes no nível do conteúdo,\n- blobs podem sobreviver a qualquer arquivo de sessão individual.\n\n## Limite de artefatos (local à sessão)\n\n`ArtifactManager` deriva o diretório de artefatos a partir do caminho do arquivo de sessão:\n\n- arquivo de sessão: `.../<timestamp>_<sessionId>.jsonl`\n- diretório de artefatos: `.../<timestamp>_<sessionId>/` (remove `.jsonl`)\n\nOs tipos de artefatos compartilham este diretório:\n\n- arquivos de saída de ferramenta truncada: `<numericId>.<toolType>.log` (para `artifact://`)\n- arquivos de saída de subagente: `<outputId>.md` (para `agent://`)\n\n## Esquemas de alocação de IDs e nomes\n\n## IDs de blob: hash de conteúdo\n\n`BlobStore.put()` computa SHA-256 sobre os bytes binários brutos e retorna:\n\n- `hash`: digest hexadecimal,\n- `path`: `<blobsDir>/<hash>`,\n- `ref`: `blob:sha256:<hash>`.\n\nNenhum contador local de sessão é utilizado.\n\n## IDs de artefato: inteiro monotônico local à sessão\n\n`ArtifactManager` escaneia os arquivos de artefato `*.log` existentes no primeiro uso para encontrar o ID numérico máximo existente e define `nextId = max + 1`.\n\nComportamento de alocação:\n\n- formato do arquivo: `{id}.{toolType}.log`\n- IDs são strings sequenciais (`\"0\"`, `\"1\"`, ...)\n- a retomada não sobrescreve artefatos existentes porque o escaneamento acontece antes da alocação.\n\nSe o diretório de artefatos estiver ausente, o escaneamento retorna lista vazia e a alocação começa do `0`.\n\n## IDs de saída de agente (`agent://`)\n\n`AgentOutputManager` aloca IDs para saídas de subagentes como `<index>-<requestedId>` (opcionalmente aninhado sob prefixo pai, por exemplo, `0-Parent.1-Child`). Ele escaneia arquivos `.md` existentes na inicialização para continuar a partir do próximo índice na retomada.\n\n## Fluxo de dados de persistência\n\n## 1) Caminho de reescrita na persistência de entradas de sessão\n\nAntes que as entradas de sessão sejam escritas (`#rewriteFile` / persistência incremental), `SessionManager` chama `prepareEntryForPersistence()` (via `truncateForPersistence`).\n\nComportamentos-chave:\n\n1. **Truncamento de strings grandes**: strings superdimensionadas são cortadas e sufixadas com `\"[Session persistence truncated large content]\"`.\n2. **Remoção de campos transientes**: `partialJson` e `jsonlEvents` são removidos das entradas persistidas.\n3. **Externalização de imagens para blobs**:\n   - aplica-se apenas a blocos de imagem em arrays `content`,\n   - apenas quando `data` não é já uma referência de blob,\n   - apenas quando o comprimento do base64 é pelo menos o limite (`BLOB_EXTERNALIZE_THRESHOLD = 1024`),\n   - substitui base64 inline por `blob:sha256:<hash>`.\n\nIsso mantém o JSONL de sessão compacto enquanto preserva a recuperabilidade.\n\n## 2) Caminho de reidratação no carregamento de sessão\n\nAo abrir uma sessão (`setSessionFile`), após as migrações, `SessionManager` executa `resolveBlobRefsInEntries()`.\n\nPara cada bloco de imagem de message/custom-message com `blob:sha256:<hash>`:\n\n- lê os bytes do blob a partir do armazenamento de blobs,\n- converte os bytes de volta para base64,\n- modifica a entrada em memória para inline base64 para consumidores em tempo de execução.\n\nSe o blob estiver ausente:\n\n- `resolveImageData()` registra um aviso,\n- retorna a string de referência original sem alteração,\n- o carregamento continua (sem crash).\n\n## 3) Caminho de despejo/truncamento de saída de ferramenta\n\n`OutputSink` alimenta a saída em streaming no bash/python/ssh e executores relacionados.\n\nComportamento:\n\n1. Cada chunk é sanitizado e adicionado ao buffer de cauda em memória.\n2. Quando os bytes em memória excedem o limite de despejo (`DEFAULT_MAX_BYTES`, 50KB), o sink marca a saída como truncada.\n3. Se um caminho de artefato está disponível, o sink abre um escritor de arquivo e escreve:\n   - o conteúdo já em buffer uma vez,\n   - todos os chunks subsequentes.\n4. O buffer em memória é sempre aparado para a janela de cauda para exibição.\n5. `dump()` retorna um resumo incluindo `artifactId` apenas quando o file sink foi criado com sucesso.\n\nEfeito prático:\n\n- UI/retorno de ferramenta mostra a cauda truncada,\n- a saída completa é preservada no arquivo de artefato e referenciada como `artifact://<id>`.\n\nSe a criação do file sink falhar (erro de I/O, caminho ausente, etc.), o sink silenciosamente faz fallback para truncamento somente em memória; a saída completa não é persistida.\n\n## Modelo de acesso por URL\n\n## Referências `blob:`\n\n`blob:sha256:<hash>` é uma referência de persistência dentro dos payloads de entradas de sessão, não um esquema de URL interno tratado pelo roteador. A resolução é feita pelo `SessionManager` durante o carregamento da sessão.\n\n## `artifact://<id>`\n\nTratado pelo `ArtifactProtocolHandler`:\n\n- requer diretório de artefatos de sessão ativo,\n- o ID deve ser numérico,\n- resolve por correspondência do prefixo do nome de arquivo `<id>.`,\n- retorna texto bruto (`text/plain`) do arquivo `.log` correspondente,\n- quando ausente, o erro inclui lista de IDs de artefatos disponíveis.\n\nComportamento com diretório ausente:\n\n- se o diretório de artefatos não existir, lança `No artifacts directory found`.\n\n## `agent://<id>`\n\nTratado pelo `AgentProtocolHandler` sobre `<artifactsDir>/<id>.md`:\n\n- a forma simples retorna texto markdown,\n- as formas `/path` ou `?q=` realizam extração JSON,\n- extração por path e query não podem ser combinadas,\n- se extração é solicitada, o conteúdo do arquivo deve ser parseado como JSON.\n\nComportamento com diretório ausente:\n\n- lança `No artifacts directory found`.\n\nComportamento com saída ausente:\n\n- lança `Not found: <id>` com IDs disponíveis dos arquivos `.md` existentes.\n\nIntegração com a ferramenta read:\n\n- `read` suporta paginação com offset/limit para leituras de URL interna sem extração,\n- rejeita `offset/limit` quando extração `agent://` é utilizada.\n\n## Semânticas de retomada, fork e movimentação\n\n## Retomada\n\n- `ArtifactManager` escaneia arquivos `{id}.*.log` existentes na primeira alocação e continua a numeração.\n- `AgentOutputManager` escaneia IDs de saída `.md` existentes e continua a numeração.\n- `SessionManager` reidrata referências de blob para base64 no carregamento.\n\n## Fork\n\n`SessionManager.fork()` cria um novo arquivo de sessão com novo ID de sessão e link `parentSession`, então retorna os caminhos de arquivo antigo/novo. A cópia de artefatos é tratada pelo `AgentSession.fork()`:\n\n- tenta cópia recursiva do diretório de artefatos antigo para o novo diretório de artefatos,\n- diretório antigo ausente é tolerado,\n- erros de cópia que não são ENOENT são registrados como avisos e o fork ainda é concluído.\n\nImplicações de ID após o fork:\n\n- se a cópia foi bem-sucedida, os contadores de artefatos na nova sessão continuam após o ID máximo copiado,\n- se a cópia falhou/foi ignorada, os IDs de artefatos da nova sessão começam do `0`.\n\nImplicações de blob após o fork:\n\n- blobs são globais e endereçados por conteúdo, então nenhuma cópia de diretório de blobs é necessária.\n\n## Mover para novo cwd\n\n`SessionManager.moveTo()` renomeia tanto o arquivo de sessão quanto o diretório de artefatos para o novo diretório de sessão padrão, com lógica de rollback se uma etapa posterior falhar. Isso preserva a identidade dos artefatos enquanto realoca o escopo da sessão.\n\n## Tratamento de falhas e caminhos de fallback\n\n| Caso | Comportamento |\n| --- | --- |\n| Arquivo de blob ausente durante reidratação | Avisa e mantém a string de referência `blob:sha256:` em memória |\n| Blob read ENOENT via `BlobStore.get` | Retorna `null` |\n| Diretório de artefatos ausente (`ArtifactManager.listFiles`) | Retorna lista vazia (alocação pode começar do zero) |\n| Diretório de artefatos ausente (`artifact://` / `agent://`) | Lança explicitamente `No artifacts directory found` |\n| ID de artefato não encontrado | Lança com listagem de IDs disponíveis |\n| Falha na inicialização do escritor de artefato do OutputSink | Continua com truncamento somente de cauda (sem artefato de saída completa) |\n| Sem arquivo de sessão (alguns caminhos de tarefa) | Ferramenta Task faz fallback para diretório de artefatos temporário para saídas de subagente |\n\n## Externalização de blob binário vs artefatos de saída de texto\n\n- **Externalização de blob** é para payloads de imagens binárias dentro do conteúdo de entradas de sessão persistidas; substitui base64 inline no JSONL por referências de conteúdo estáveis.\n- **Artefatos** são arquivos de texto simples para saída de execução e saída de subagente; são endereçáveis por IDs locais à sessão através de URLs internas.\n\nOs dois sistemas se intersectam apenas indiretamente (ambos reduzem o inchaço do JSONL de sessão) mas possuem caminhos diferentes de identidade, tempo de vida e recuperação.\n\n## Arquivos de implementação\n\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts) — formato de referência de blob, hashing, put/get, helpers de externalização/resolução.\n- [`src/session/artifacts.ts`](../../packages/coding-agent/src/session/artifacts.ts) — modelo de diretório de artefatos de sessão e alocação de ID numérico de artefato.\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — comportamento de truncamento/despejo-para-arquivo do `OutputSink` e metadados de resumo.\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts) — transformações de persistência, reidratação de blob no carregamento, interações de fork/movimentação de sessão.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — cópia de diretório de artefatos durante fork interativo.\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — bootstrap do gerenciador de artefatos de ferramentas e alocação de caminho de artefato por ferramenta.\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — resolver de `artifact://`.\n- [`src/internal-urls/agent-protocol.ts`](../../packages/coding-agent/src/internal-urls/agent-protocol.ts) — resolver de `agent://` + extração JSON.\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — wiring do roteador de URLs internas e resolver de diretório de artefatos.\n- [`src/task/output-manager.ts`](../../packages/coding-agent/src/task/output-manager.ts) — alocação de ID de saída de agente com escopo de sessão para `agent://`.\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — escritas de artefatos de saída de subagente (`<id>.md`) e fallback para diretório de artefatos temporário.\n",
	"pt-br/configuration/config-usage.md": "---\ntitle: Descoberta e Resolução de Configuração\ndescription: >-\n  Como o xcsh descobre, resolve e organiza configurações a partir das raízes de\n  projeto, usuário e empresa.\nsidebar:\n  order: 1\n  label: Configuração\ni18n:\n  sourceHash: e38bd9792499\n  translator: machine\n---\n\n# Descoberta e Resolução de Configuração\n\nEste documento descreve como o coding-agent resolve a configuração atualmente: quais raízes são escaneadas, como a precedência funciona e como a configuração resolvida é consumida por settings, skills, hooks, tools e extensões.\n\n## Escopo\n\nImplementação principal:\n\n- `src/config.ts`\n- `src/config/settings.ts`\n- `src/config/settings-schema.ts`\n- `src/discovery/builtin.ts`\n- `src/discovery/helpers.ts`\n\nPontos-chave de integração:\n\n- `src/capability/index.ts`\n- `src/discovery/index.ts`\n- `src/extensibility/skills.ts`\n- `src/extensibility/hooks/loader.ts`\n- `src/extensibility/custom-tools/loader.ts`\n- `src/extensibility/extensions/loader.ts`\n\n---\n\n## Fluxo de resolução (visual)\n\n```text\n         Config roots (ordered)\n┌───────────────────────────────────────┐\n│ 1) ~/.xcsh/agent + <cwd>/.xcsh          │\n│ 2) ~/.claude   + <cwd>/.claude        │\n│ 3) ~/.codex    + <cwd>/.codex         │\n│ 4) ~/.gemini   + <cwd>/.gemini        │\n└───────────────────────────────────────┘\n                    │\n                    ▼\n        config.ts helper resolution\n  (getConfigDirs/findConfigFile/findNearest...)\n                    │\n                    ▼\n       capability providers enumerate items\n (native, claude, codex, gemini, agents, etc.)\n                    │\n                    ▼\n      priority sort + per-capability dedup\n                    │\n                    ▼\n          subsystem-specific consumption\n   (settings, skills, hooks, tools, extensions)\n```\n\n## 1) Raízes de configuração e ordem de fontes\n\n## Raízes canônicas\n\n`src/config.ts` define uma lista fixa de prioridade de fontes:\n\n1. `.xcsh` (nativo)\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nBases de nível de usuário:\n\n- `~/.xcsh/agent`\n- `~/.claude`\n- `~/.codex`\n- `~/.gemini`\n\nBases de nível de projeto:\n\n- `<cwd>/.xcsh`\n- `<cwd>/.claude`\n- `<cwd>/.codex`\n- `<cwd>/.gemini`\n\n`CONFIG_DIR_NAME` é `.xcsh` (`packages/utils/src/dirs.ts`).\n\n## Restrição importante\n\nOs helpers genéricos em `src/config.ts` **não** incluem `.pi` na ordem de descoberta de fontes.\n\n---\n\n## 2) Helpers principais de descoberta (`src/config.ts`)\n\n## `getConfigDirs(subpath, options)`\n\nRetorna entradas ordenadas:\n\n- Entradas de nível de usuário primeiro (por prioridade de fonte)\n- Depois entradas de nível de projeto (pela mesma prioridade de fonte)\n\nOpções:\n\n- `user` (padrão `true`)\n- `project` (padrão `true`)\n- `cwd` (padrão `getProjectDir()`)\n- `existingOnly` (padrão `false`)\n\nEsta API é utilizada para buscas de configuração baseadas em diretórios (commands, hooks, tools, agents, etc.).\n\n## `findConfigFile(subpath, options)` / `findConfigFileWithMeta(...)`\n\nBusca o primeiro arquivo existente entre as bases ordenadas, retorna a primeira correspondência (apenas o caminho ou caminho+metadados).\n\n## `findAllNearestProjectConfigDirs(subpath, cwd)`\n\nPercorre os diretórios ancestrais para cima e retorna o **diretório existente mais próximo por base de fonte** (`.xcsh`, `.claude`, `.codex`, `.gemini`), depois ordena os resultados por prioridade de fonte.\n\nUse isso quando a configuração de projeto deve ser herdada de diretórios ancestrais (comportamento de monorepo/workspace aninhado).\n\n---\n\n## 3) Wrapper de arquivo de configuração (`ConfigFile<T>` em `src/config.ts`)\n\n`ConfigFile<T>` é o carregador com validação de schema para arquivos de configuração únicos.\n\nFormatos suportados:\n\n- `.yml` / `.yaml`\n- `.json` / `.jsonc`\n\nComportamento:\n\n- Valida os dados parseados com AJV contra um schema TypeBox fornecido.\n- Armazena em cache o resultado do carregamento até `invalidate()`.\n- Retorna resultado de três estados via `tryLoad()`:\n  - `ok`\n  - `not-found`\n  - `error` (`ConfigError` com contexto de schema/parse)\n\nMigração legada ainda suportada:\n\n- Se o caminho alvo é `.yml`/`.yaml`, um `.json` adjacente é migrado automaticamente uma vez (`migrateJsonToYml`).\n\n---\n\n## 4) Modelo de resolução de settings (`src/config/settings.ts`)\n\nO modelo de settings em tempo de execução é organizado em camadas:\n\n1. Settings globais: `~/.xcsh/agent/config.yml`\n2. Settings de projeto: descobertas via capability de settings (`settings.json` dos providers)\n3. Overrides em tempo de execução: em memória, não persistentes\n4. Valores padrão do schema: do `SETTINGS_SCHEMA`\n\nCaminho efetivo de leitura:\n\n`defaults <- global <- project <- overrides`\n\nComportamento de escrita:\n\n- `settings.set(...)` escreve na camada **global** (`config.yml`) e enfileira salvamento em background.\n- Settings de projeto são somente leitura a partir da descoberta de capabilities.\n\n## Comportamento de migração ainda ativo\n\nNa inicialização, se `config.yml` não existe:\n\n1. Migra de `~/.xcsh/agent/settings.json` (renomeado para `.bak` em caso de sucesso)\n2. Mescla com settings legadas do DB de `agent.db`\n3. Escreve o resultado mesclado em `config.yml`\n\nMigrações em nível de campo em `#migrateRawSettings`:\n\n- `queueMode` -> `steeringMode`\n- `ask.timeout` milissegundos -> segundos quando o valor antigo parece ser ms (`> 1000`)\n- `theme: \"...\"` flat legado -> estrutura `theme.dark/theme.light`\n\n---\n\n## 5) Integração capability/discovery\n\nA maioria dos fluxos de carregamento de configuração não-core passa pelo registro de capabilities (`src/capability/index.ts` + `src/discovery/index.ts`).\n\n## Ordenação de providers\n\nProviders são ordenados por prioridade numérica (maior primeiro). Exemplos de prioridades:\n\n- Native OMP (`builtin.ts`): `100`\n- Claude: `80`\n- Codex / agents / Claude marketplace: `70`\n- Gemini: `60`\n\n```text\nProvider precedence (higher wins)\n\nnative (.xcsh)          priority 100\nclaude                 priority  80\ncodex / agents / ...   priority  70\ngemini                 priority  60\n```\n\n## Semântica de deduplicação\n\nCapabilities definem uma `key(item)`:\n\n- mesma chave => primeiro item vence (item de maior prioridade/carregado primeiro)\n- sem chave (`undefined`) => sem deduplicação, todos os itens são mantidos\n\nChaves relevantes:\n\n- skills: `name`\n- tools: `name`\n- hooks: `${type}:${tool}:${name}`\n- extension modules: `name`\n- extensions: `name`\n- settings: sem deduplicação (todos os itens são preservados)\n\n---\n\n## 6) Comportamento do provider nativo `.xcsh` (`src/discovery/builtin.ts`)\n\nO provider nativo (`id: native`) lê de:\n\n- projeto: `<cwd>/.xcsh/...`\n- usuário: `~/.xcsh/agent/...`\n\n### Regra de admissão de diretório\n\n`builtin.ts` só inclui uma raiz de configuração se o diretório existir **e não estiver vazio** (`ifNonEmptyDir`).\n\n### Carregamento específico por escopo\n\n- Skills: `skills/*/SKILL.md`\n- Slash commands: `commands/*.md`\n- Rules: `rules/*.{md,mdc}`\n- Prompts: `prompts/*.md`\n- Instructions: `instructions/*.md`\n- Hooks: `hooks/pre/*`, `hooks/post/*`\n- Tools: `tools/*.json|*.md` e `tools/<name>/index.ts`\n- Extension modules: descobertos em `extensions/` (+ array de strings legado `settings.json.extensions`)\n- Extensions: `extensions/<name>/gemini-extension.json`\n- Settings capability: `settings.json`\n\n### Nuance de busca de projeto mais próximo\n\nPara `SYSTEM.md` e `XCSH.md`, o provider nativo usa a busca de diretório `.xcsh` de projeto no ancestral mais próximo (subindo a árvore) mas ainda exige que o diretório `.xcsh` não esteja vazio.\n\n---\n\n## 7) Como os principais subsistemas consomem a configuração\n\n## Subsistema de settings\n\n- `Settings.init()` carrega o `config.yml` global + itens descobertos da capability de settings do projeto.\n- Apenas itens de capability com `level === \"project\"` são mesclados na camada de projeto.\n\n## Subsistema de skills\n\n- `extensibility/skills.ts` carrega via `loadCapability(skillCapability.id, { cwd })`.\n- Aplica toggles e filtros de fonte (`ignoredSkills`, `includeSkills`, diretórios customizados).\n- Toggles com nomes legados ainda existem (`skills.enablePiUser`, `skills.enablePiProject`) mas controlam o provider nativo (`provider === \"native\"`).\n\n## Subsistema de hooks\n\n- `discoverAndLoadHooks()` resolve caminhos de hooks a partir da capability de hooks + caminhos configurados explicitamente.\n- Depois carrega módulos via importação do Bun.\n\n## Subsistema de tools\n\n- `discoverAndLoadCustomTools()` resolve caminhos de tools a partir da capability de tools + caminhos de tools de plugins + caminhos configurados explicitamente.\n- Arquivos de tools declarativos `.md/.json` são apenas metadados; o carregamento executável espera módulos de código.\n\n## Subsistema de extensões\n\n- `discoverAndLoadExtensions()` resolve módulos de extensão a partir da capability de extension-module mais caminhos explícitos.\n- A implementação atual intencionalmente mantém apenas itens de capability com `_source.provider === \"native\"` antes do carregamento.\n\n---\n\n## 8) Regras de precedência nas quais confiar\n\nUse este modelo mental:\n\n1. A ordenação de diretórios de fonte do `config.ts` determina a ordem dos caminhos candidatos.\n2. A prioridade do provider de capability determina a precedência entre providers.\n3. A deduplicação por chave de capability determina o comportamento de colisão (primeiro vence para capabilities com chave).\n4. A lógica de merge específica do subsistema pode alterar ainda mais a precedência efetiva (especialmente settings).\n\n### Ressalva específica de settings\n\nItens de capability de settings não são deduplicados; `Settings.#loadProjectSettings()` faz deep-merge dos itens de projeto na ordem retornada. Como o merge aplica valores de itens posteriores sobre valores anteriores, o comportamento efetivo de override depende da ordem de emissão do provider, não apenas da semântica de chave de capability.\n\n---\n\n## 9) Comportamentos de legado/compatibilidade ainda presentes\n\n- Migração de JSON -> YAML do `ConfigFile` para arquivos destinados a YAML.\n- Migração de settings de `settings.json` e `agent.db` para `config.yml`.\n- Migrações de chaves de settings (`queueMode`, `ask.timeout`, `theme` flat).\n- Compatibilidade de manifesto de extensão: o loader aceita tanto seções de manifesto `package.json.xcsh` quanto `package.json.pi`.\n- Nomes de settings legados `skills.enablePiUser` / `skills.enablePiProject` ainda são gates ativos para a fonte nativa de skills.\n\nSe esses caminhos de compatibilidade forem removidos no código, atualize este documento imediatamente; vários comportamentos em tempo de execução ainda dependem deles hoje.\n",
	"pt-br/configuration/environment-variables.md": "---\ntitle: Variáveis de Ambiente\ndescription: >-\n  Referência de variáveis de ambiente de runtime para configuração e controle de\n  comportamento do xcsh.\nsidebar:\n  order: 2\n  label: Variáveis de ambiente\ni18n:\n  sourceHash: e2890f963c02\n  translator: machine\n---\n\n# Variáveis de Ambiente (Referência de Runtime Atual)\n\nEsta referência é derivada dos caminhos de código atuais em:\n\n- `packages/coding-agent/src/**`\n- `packages/ai/src/**` (resolução de provedor/autenticação utilizada pelo coding-agent)\n- `packages/utils/src/**` e `packages/tui/src/**` onde essas variáveis afetam diretamente o runtime do coding-agent\n\nDocumenta apenas o comportamento ativo.\n\n## Modelo de resolução e precedência\n\nA maioria das consultas em runtime utiliza `$env` de `@f5-sales-demo/pi-utils` (`packages/utils/src/env.ts`).\n\nOrdem de carregamento do `$env`:\n\n1. Ambiente de processo existente (`Bun.env`)\n2. `.env` do projeto (`$PWD/.env`) para chaves ainda não definidas\n3. `.env` do diretório home (`~/.env`) para chaves ainda não definidas\n\nRegra adicional em arquivos `.env`: chaves `XCSH_*` são espelhadas para chaves `PI_*` durante o parse.\n\n---\n\n## 1) Autenticação de modelo/provedor\n\nEstas são consumidas via `getEnvApiKey()` (`packages/ai/src/stream.ts`), salvo indicação contrária.\n\n### Credenciais principais de provedor\n\n| Variável                        | Usada para | Necessária quando                                             | Notas / precedência                                                                                  |\n|---------------------------------|---|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|\n| `ANTHROPIC_OAUTH_TOKEN`         | Autenticação na API Anthropic | Usando Anthropic com autenticação por token OAuth             | Tem precedência sobre `ANTHROPIC_API_KEY` na resolução de autenticação do provedor                   |\n| `ANTHROPIC_API_KEY`             | Autenticação na API Anthropic | Usando Anthropic sem token OAuth                              | Fallback após `ANTHROPIC_OAUTH_TOKEN`                                                               |\n| `ANTHROPIC_FOUNDRY_API_KEY`     | Anthropic via Azure Foundry / gateway empresarial | `CLAUDE_CODE_USE_FOUNDRY` habilitado                          | Tem precedência sobre `ANTHROPIC_OAUTH_TOKEN` e `ANTHROPIC_API_KEY` quando o modo Foundry está habilitado |\n| `OPENAI_API_KEY`                | Autenticação OpenAI | Usando provedores da família OpenAI sem argumento apiKey explícito | Usado pelos provedores OpenAI Completions/Responses                                                 |\n| `GEMINI_API_KEY`                | Autenticação Google Gemini | Usando modelos do provedor `google`                           | Chave principal para mapeamento do provedor Gemini                                                  |\n| `GOOGLE_API_KEY`                | Fallback de autenticação da ferramenta de imagem Gemini | Usando a ferramenta `gemini_image` sem `GEMINI_API_KEY`       | Usado pelo caminho de fallback da ferramenta de imagem do coding-agent                              |\n| `GROQ_API_KEY`                  | Autenticação Groq | Usando modelos Groq                                           |                                                                                                     |\n| `CEREBRAS_API_KEY`              | Autenticação Cerebras | Usando modelos Cerebras                                       |                                                                                                     |\n| `TOGETHER_API_KEY`              | Autenticação Together | Usando provedor `together`                                    |                                                                                                     |\n| `HUGGINGFACE_HUB_TOKEN`         | Autenticação Hugging Face | Usando provedor `huggingface`                                 | Variável de ambiente principal do token Hugging Face                                                |\n| `HF_TOKEN`                      | Autenticação Hugging Face | Usando provedor `huggingface`                                 | Fallback quando `HUGGINGFACE_HUB_TOKEN` não está definido                                           |\n| `SYNTHETIC_API_KEY`             | Autenticação Synthetic | Usando modelos Synthetic                                      |                                                                                                     |\n| `NVIDIA_API_KEY`                | Autenticação NVIDIA | Usando provedor `nvidia`                                      |                                                                                                     |\n| `NANO_GPT_API_KEY`              | Autenticação NanoGPT | Usando provedor `nanogpt`                                     |                                                                                                     |\n| `VENICE_API_KEY`                | Autenticação Venice | Usando provedor `venice`                                      |                                                                                                     |\n| `LITELLM_API_KEY`               | Autenticação LiteLLM | Usando provedor `litellm`                                     | Chave de proxy LiteLLM compatível com OpenAI. Quando definido com `LITELLM_BASE_URL`, habilita a auto-configuração do `models.yml` |\n| `LM_STUDIO_API_KEY`             | Autenticação LM Studio (opcional) | Usando provedor `lm-studio` com hosts autenticados            | LM Studio local geralmente roda sem autenticação; qualquer token não vazio funciona quando uma chave é necessária |\n| `OLLAMA_API_KEY`                | Autenticação Ollama (opcional) | Usando provedor `ollama` com hosts autenticados               | Ollama local geralmente roda sem autenticação; qualquer token não vazio funciona quando uma chave é necessária |\n| `LLAMA_CPP_API_KEY`             | Autenticação Ollama (opcional) | Usando `llama-server` com parâmetro `--api-key`               | llama.cpp local geralmente roda sem autenticação; qualquer token não vazio funciona quando uma chave é configurada |\n| `XIAOMI_API_KEY`                | Autenticação Xiaomi MiMo | Usando provedor `xiaomi`                                      |                                                                                                     |\n| `MOONSHOT_API_KEY`              | Autenticação Moonshot | Usando provedor `moonshot`                                    |                                                                                                     |\n| `XAI_API_KEY`                   | Autenticação xAI | Usando modelos xAI                                            |                                                                                                     |\n| `OPENROUTER_API_KEY`            | Autenticação OpenRouter | Usando modelos OpenRouter                                     | Também usado pela ferramenta de imagem quando o provedor preferido/auto é OpenRouter                |\n| `MISTRAL_API_KEY`               | Autenticação Mistral | Usando modelos Mistral                                        |                                                                                                     |\n| `ZAI_API_KEY`                   | Autenticação z.ai | Usando modelos z.ai                                           | Também usado pelo provedor de busca web z.ai                                                        |\n| `MINIMAX_API_KEY`               | Autenticação MiniMax | Usando provedor `minimax`                                     |                                                                                                     |\n| `MINIMAX_CODE_API_KEY`          | Autenticação MiniMax Code | Usando provedor `minimax-code`                                |                                                                                                     |\n| `MINIMAX_CODE_CN_API_KEY`       | Autenticação MiniMax Code CN | Usando provedor `minimax-code-cn`                             |                                                                                                     |\n| `OPENCODE_API_KEY`              | Autenticação OpenCode | Usando modelos OpenCode                                       |                                                                                                     |\n| `QIANFAN_API_KEY`               | Autenticação Qianfan | Usando provedor `qianfan`                                     |                                                                                                     |\n| `QWEN_OAUTH_TOKEN`              | Autenticação Qwen Portal | Usando `qwen-portal` com token OAuth                          | Tem precedência sobre `QWEN_PORTAL_API_KEY`                                                         |\n| `QWEN_PORTAL_API_KEY`           | Autenticação Qwen Portal | Usando `qwen-portal` com chave API                            | Fallback após `QWEN_OAUTH_TOKEN`                                                                    |\n| `ZENMUX_API_KEY`                | Autenticação ZenMux | Usando provedor `zenmux`                                      | Usado para rotas compatíveis com OpenAI e Anthropic do ZenMux                                       |\n| `VLLM_API_KEY`                  | Autenticação/descoberta opt-in do vLLM | Usando provedor `vllm` (servidores locais compatíveis com OpenAI) | Qualquer valor não vazio funciona para servidores locais sem autenticação                           |\n| `CURSOR_ACCESS_TOKEN`           | Autenticação do provedor Cursor | Usando provedor Cursor                                        |                                                                                                     |\n| `AI_GATEWAY_API_KEY`            | Autenticação Vercel AI Gateway | Usando provedor `vercel-ai-gateway`                           |                                                                                                     |\n| `CLOUDFLARE_AI_GATEWAY_API_KEY` | Autenticação Cloudflare AI Gateway | Usando provedor `cloudflare-ai-gateway`                       | A URL base deve ser configurada como `https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic` |\n\n### Cadeias de token GitHub/Copilot\n\n| Variável | Usada para | Cadeia |\n|---|---|---|\n| `COPILOT_GITHUB_TOKEN` | Autenticação do provedor GitHub Copilot | `COPILOT_GITHUB_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN` |\n| `GH_TOKEN` | Fallback do Copilot; autenticação na API GitHub no web scraper | No web scraper: `GITHUB_TOKEN` → `GH_TOKEN` |\n| `GITHUB_TOKEN` | Fallback do Copilot; autenticação na API GitHub no web scraper | No web scraper: verificado antes de `GH_TOKEN` |\n\n---\n\n## 2) Configuração de runtime específica por provedor\n\n### Anthropic Foundry Gateway (Azure / proxy empresarial)\n\nQuando `CLAUDE_CODE_USE_FOUNDRY` está habilitado, as requisições Anthropic mudam para o modo Foundry:\n\n- A URL base é resolvida a partir de `FOUNDRY_BASE_URL` (o fallback permanece como a URL base padrão/do modelo se não definida).\n- A resolução da chave API para o provedor `anthropic` torna-se:\n  `ANTHROPIC_FOUNDRY_API_KEY` → `ANTHROPIC_OAUTH_TOKEN` → `ANTHROPIC_API_KEY`.\n- `ANTHROPIC_CUSTOM_HEADERS` é interpretado como pares `chave: valor` separados por vírgula/nova linha e mesclados nos cabeçalhos da requisição.\n- Material TLS de cliente/servidor pode ser injetado a partir de valores de ambiente:\n  `NODE_EXTRA_CA_CERTS`, `CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`.\n  Cada um aceita:\n  - um caminho de sistema de arquivos para conteúdo PEM, ou\n  - PEM inline (incluindo sequências `\\n` escapadas).\n\n| Variável | Tipo de valor | Comportamento |\n|---|---|---|\n| `CLAUDE_CODE_USE_FOUNDRY` | String tipo booleano (`1`, `true`, `yes`, `on`) | Habilita o modo Foundry para o provedor Anthropic |\n| `FOUNDRY_BASE_URL` | String URL | URL base do endpoint Anthropic no modo Foundry |\n| `ANTHROPIC_FOUNDRY_API_KEY` | String de token | Usado para `Authorization: Bearer <token>` |\n| `ANTHROPIC_CUSTOM_HEADERS` | String de lista de cabeçalhos | Cabeçalhos extras; formato `header-a: valor, header-b: valor` ou separados por nova linha |\n| `NODE_EXTRA_CA_CERTS` | Caminho PEM ou PEM inline | Cadeia CA extra para validação de certificado do servidor |\n| `CLAUDE_CODE_CLIENT_CERT` | Caminho PEM ou PEM inline | Certificado de cliente mTLS |\n| `CLAUDE_CODE_CLIENT_KEY` | Caminho PEM ou PEM inline | Chave privada do cliente mTLS (deve ser pareada com o certificado) |\n\n### Amazon Bedrock\n\n| Variável | Padrão / comportamento |\n|---|---|\n| `AWS_REGION` | Fonte principal de região |\n| `AWS_DEFAULT_REGION` | Fallback se `AWS_REGION` não estiver definida |\n| `AWS_PROFILE` | Habilita o caminho de autenticação por perfil nomeado |\n| `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` | Habilita o caminho de autenticação por chave IAM |\n| `AWS_BEARER_TOKEN_BEDROCK` | Habilita o caminho de autenticação por bearer token |\n| `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` / `AWS_CONTAINER_CREDENTIALS_FULL_URI` | Habilita o caminho de credencial de tarefa ECS |\n| `AWS_WEB_IDENTITY_TOKEN_FILE` + `AWS_ROLE_ARN` | Habilita o caminho de autenticação por web identity |\n| `AWS_BEDROCK_SKIP_AUTH` | Se `1`, injeta credenciais fictícias (cenários de proxy/sem autenticação) |\n| `AWS_BEDROCK_FORCE_HTTP1` | Se `1`, força o handler de requisição Node HTTP/1 |\n\nFallback de região no código do provedor: `options.region` → `AWS_REGION` → `AWS_DEFAULT_REGION` → `us-east-1`.\n\n### Azure OpenAI Responses\n\n| Variável | Padrão / comportamento |\n|---|---|\n| `AZURE_OPENAI_API_KEY` | Obrigatória a menos que a chave API seja passada como opção |\n| `AZURE_OPENAI_API_VERSION` | Padrão `v1` |\n| `AZURE_OPENAI_BASE_URL` | Override direto da URL base |\n| `AZURE_OPENAI_RESOURCE_NAME` | Usado para construir a URL base: `https://<resource>.openai.azure.com/openai/v1` |\n| `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` | String de mapeamento opcional: `modelId=deploymentName,model2=deployment2` |\n\nResolução da URL base: opção `azureBaseUrl` → env `AZURE_OPENAI_BASE_URL` → opção/env resource name → `model.baseUrl`.\n\n### Google Vertex AI\n\n| Variável | Obrigatória? | Notas |\n|---|---|---|\n| `GOOGLE_CLOUD_PROJECT` | Sim (a menos que passada nas opções) | Fallback: `GCLOUD_PROJECT` |\n| `GCLOUD_PROJECT` | Fallback | Usada como fonte alternativa de ID do projeto |\n| `GOOGLE_CLOUD_LOCATION` | Sim (a menos que passada nas opções) | Sem padrão no provedor |\n| `GOOGLE_APPLICATION_CREDENTIALS` | Condicional | Se definida, o arquivo deve existir; caso contrário, o caminho de fallback ADC é verificado (`~/.config/gcloud/application_default_credentials.json`) |\n\n### Kimi\n\n| Variável | Padrão / comportamento |\n|---|---|\n| `KIMI_CODE_OAUTH_HOST` | Override principal do host OAuth |\n| `KIMI_OAUTH_HOST` | Override de fallback do host OAuth |\n| `KIMI_CODE_BASE_URL` | Substitui a URL base do endpoint de uso do Kimi (`usage/kimi.ts`) |\n\nCadeia do host OAuth: `KIMI_CODE_OAUTH_HOST` → `KIMI_OAUTH_HOST` → `https://auth.kimi.com`.\n\n### Compatibilidade Antigravity/Gemini image\n\n| Variável | Padrão / comportamento |\n|---|---|\n| `PI_AI_ANTIGRAVITY_VERSION` | Substitui a tag de versão do user-agent Antigravity no provedor Gemini CLI |\n\n### OpenAI Codex responses (controles de funcionalidade/debug)\n\n| Variável | Comportamento |\n|---|---|\n| `PI_CODEX_DEBUG` | `1`/`true` habilita logs de debug do provedor Codex |\n| `PI_CODEX_WEBSOCKET` | `1`/`true` habilita preferência de transporte websocket |\n| `PI_CODEX_WEBSOCKET_V2` | `1`/`true` habilita caminho websocket v2 |\n| `PI_CODEX_WEBSOCKET_IDLE_TIMEOUT_MS` | Override de inteiro positivo (padrão 300000) |\n| `PI_CODEX_WEBSOCKET_RETRY_BUDGET` | Override de inteiro não negativo (padrão 5) |\n| `PI_CODEX_WEBSOCKET_RETRY_DELAY_MS` | Override de backoff base em inteiro positivo (padrão 500) |\n\n### Debug do provedor Cursor\n\n| Variável | Comportamento |\n|---|---|\n| `DEBUG_CURSOR` | Habilita logs de debug do provedor; `2`/`verbose` para trechos detalhados de payload |\n| `DEBUG_CURSOR_LOG` | Caminho de arquivo opcional para saída de log de debug JSONL |\n\n### Chave de compatibilidade de cache de prompt\n\n| Variável | Comportamento |\n|---|---|\n| `PI_CACHE_RETENTION` | Se `long`, habilita retenção longa onde suportado (`anthropic`, `openai-responses`, resolução de retenção Bedrock) |\n\n---\n\n## 3) Subsistema de busca web\n\n### Credenciais de provedor de busca\n\n| Variável | Usada por |\n|---|---|\n| `EXA_API_KEY` | Provedor de busca Exa e ferramentas MCP Exa |\n| `BRAVE_API_KEY` | Provedor de busca Brave |\n| `PERPLEXITY_API_KEY` | Modo chave API do provedor de busca Perplexity |\n| `TAVILY_API_KEY` | Provedor de busca Tavily |\n| `ZAI_API_KEY` | Provedor de busca z.ai (também verifica OAuth armazenado em `agent.db`) |\n| `OPENAI_API_KEY` / OAuth Codex no DB | Disponibilidade/autenticação do provedor de busca Codex |\n\n### Cadeia de autenticação de busca web Anthropic\n\n`packages/coding-agent/src/web/search/auth.ts` resolve credenciais de busca web Anthropic nesta ordem:\n\n1. `ANTHROPIC_SEARCH_API_KEY` (+ opcional `ANTHROPIC_SEARCH_BASE_URL`)\n2. Entrada de provedor em `models.json` com `api: \"anthropic-messages\"`\n3. Credenciais OAuth Anthropic de `agent.db` (não deve expirar dentro do buffer de 5 minutos)\n4. Fallback genérico de env Anthropic: chave do provedor (`ANTHROPIC_FOUNDRY_API_KEY`/`ANTHROPIC_OAUTH_TOKEN`/`ANTHROPIC_API_KEY`) + opcional `ANTHROPIC_BASE_URL` (`FOUNDRY_BASE_URL` quando o modo Foundry está habilitado)\n\nVariáveis relacionadas:\n\n| Variável | Padrão / comportamento |\n|---|---|\n| `ANTHROPIC_SEARCH_API_KEY` | Chave de busca explícita de maior prioridade |\n| `ANTHROPIC_SEARCH_BASE_URL` | Padrão `https://api.anthropic.com` quando omitida |\n| `ANTHROPIC_SEARCH_MODEL` | Padrão `claude-haiku-4-5` |\n| `ANTHROPIC_BASE_URL` | URL base de fallback genérica para o caminho de autenticação nível 4 |\n\n### Flag de comportamento do fluxo OAuth Perplexity\n\n| Variável | Comportamento |\n|---|---|\n| `PI_AUTH_NO_BORROW` | Se definida, desabilita o caminho de empréstimo de token de aplicativo nativo macOS no fluxo de login Perplexity |\n\n---\n\n## 4) Ferramentas Python e runtime de kernel\n\n| Variável | Padrão / comportamento |\n|---|---|\n| `PI_PY` | Override do modo de ferramenta Python: `0`/`bash`=`bash-only`, `1`/`py`=`ipy-only`, `mix`/`both`=`both`; valores inválidos são ignorados |\n| `PI_PYTHON_SKIP_CHECK` | Se `1`, pula verificações de disponibilidade/aquecimento do kernel Python |\n| `PI_PYTHON_GATEWAY_URL` | Se definida, usa gateway de kernel externo em vez do gateway compartilhado local |\n| `PI_PYTHON_GATEWAY_TOKEN` | Token de autenticação opcional para gateway externo (`Authorization: token <value>`) |\n| `PI_PYTHON_IPC_TRACE` | Se `1`, habilita caminho de rastreamento IPC de baixo nível no módulo de kernel |\n| `VIRTUAL_ENV` | Caminho de venv de maior prioridade para resolução do runtime Python |\n\nComportamento condicional extra:\n\n- Se `BUN_ENV=test` ou `NODE_ENV=test`, as verificações de disponibilidade do Python são tratadas como OK e o aquecimento é ignorado.\n- A filtragem de ambiente Python nega chaves API comuns e permite variáveis base seguras + prefixos `LC_`, `XDG_`, `PI_`.\n\n---\n\n## 5) Toggles de comportamento do agente/runtime\n\n| Variável                   | Padrão / comportamento                                                                       |\n|----------------------------|----------------------------------------------------------------------------------------------|\n| `PI_SMOL_MODEL`            | Override efêmero de model-role para `smol` (CLI `--smol` tem precedência)                    |\n| `PI_SLOW_MODEL`            | Override efêmero de model-role para `slow` (CLI `--slow` tem precedência)                    |\n| `PI_PLAN_MODEL`            | Override efêmero de model-role para `plan` (CLI `--plan` tem precedência)                    |\n| `PI_NO_TITLE`              | Se definida (qualquer valor não vazio), desabilita a geração automática de título de sessão na primeira mensagem do usuário |\n| `NULL_PROMPT`              | Se `true`, o construtor de prompt de sistema retorna string vazia                            |\n| `PI_BLOCKED_AGENT`         | Bloqueia um tipo específico de subagente na ferramenta de tarefa                             |\n| `PI_SUBPROCESS_CMD`        | Substitui o comando de spawn do subagente (bypass da resolução `xcsh` / `xcsh.cmd`)           |\n| `PI_TASK_MAX_OUTPUT_BYTES` | Máximo de bytes de saída capturados por subagente (padrão `500000`)                          |\n| `PI_TASK_MAX_OUTPUT_LINES` | Máximo de linhas de saída capturadas por subagente (padrão `5000`)                           |\n| `PI_TIMING`                | Se `1`, habilita logs de instrumentação de timing de startup/ferramenta                      |\n| `PI_DEBUG_STARTUP`         | Habilita prints de debug de estágio de startup para stderr em múltiplos caminhos de startup  |\n| `PI_PACKAGE_DIR`           | Substitui a resolução do diretório base de assets do pacote (busca de caminhos de docs/exemplos/changelog) |\n| `PI_DISABLE_LSPMUX`        | Se `1`, desabilita detecção/integração do lspmux e força o spawn direto do servidor LSP      |\n| `LITELLM_BASE_URL`         | URL base do proxy LiteLLM. Quando definida com `LITELLM_API_KEY`, dispara a auto-geração do `models.yml` na primeira execução e auto-reparo em cada startup |\n| `LM_STUDIO_BASE_URL`       | Override da URL base de descoberta implícita padrão do LM Studio (`http://127.0.0.1:1234/v1` se não definida) |\n| `OLLAMA_BASE_URL`          | Override da URL base de descoberta implícita padrão do Ollama (`http://127.0.0.1:11434` se não definida) |\n| `LLAMA_CPP_BASE_URL`       | Override da URL base de descoberta implícita padrão do Llama.cpp (`http://127.0.0.1:8080` se não definida) |\n| `PI_EDIT_VARIANT`          | Se `hashline`, força o modo de exibição hashline read/grep quando a ferramenta de edição está disponível |\n| `PI_NO_PTY`                | Se `1`, desabilita o caminho PTY interativo para a ferramenta bash                           |\n\n`PI_NO_PTY` também é definida internamente quando o CLI `--no-pty` é usado.\n\n---\n\n## 6) Caminhos raiz de armazenamento e configuração\n\nEstas são consumidas via `@f5-sales-demo/pi-utils/dirs` e afetam onde o coding-agent armazena dados.\n\n| Variável | Padrão / comportamento |\n|---|---|\n| `PI_CONFIG_DIR` | Nome do diretório raiz de configuração sob o home (padrão `.xcsh`) |\n| `PI_CODING_AGENT_DIR` | Override completo para o diretório do agente (padrão `~/<PI_CONFIG_DIR ou .xcsh>/agent`) |\n| `PWD` | Usado ao fazer correspondência do diretório de trabalho atual canônico em helpers de caminho |\n\n---\n\n## 7) Ambiente de execução de shell/ferramentas\n\n(De `packages/utils/src/procmgr.ts` e integração da ferramenta bash do coding-agent.)\n\n| Variável | Comportamento |\n|---|---|\n| `PI_BASH_NO_CI` | Suprime a injeção automática de `CI=true` no ambiente de shell gerado |\n| `CLAUDE_BASH_NO_CI` | Alias legado de fallback para `PI_BASH_NO_CI` |\n| `PI_BASH_NO_LOGIN` | Destinada a desabilitar o modo de shell de login |\n| `CLAUDE_BASH_NO_LOGIN` | Alias legado de fallback para `PI_BASH_NO_LOGIN` |\n| `PI_SHELL_PREFIX` | Wrapper de prefixo de comando opcional |\n| `CLAUDE_CODE_SHELL_PREFIX` | Alias legado de fallback para `PI_SHELL_PREFIX` |\n| `VISUAL` | Comando de editor externo preferido |\n| `EDITOR` | Comando de editor externo de fallback |\n\nNota da implementação atual: `PI_BASH_NO_LOGIN`/`CLAUDE_BASH_NO_LOGIN` são lidas, mas a implementação atual de `getShellArgs()` retorna `['-l','-c']` em ambas as ramificações (efetivamente sem efeito hoje).\n\n---\n\n## 8) Detecção de UI/tema/sessão (env auto-detectado)\n\nEstas são lidas como sinais de runtime; geralmente são definidas pelo terminal/SO em vez de configuradas manualmente.\n\n| Variável | Usada para |\n|---|---|\n| `COLORTERM`, `TERM`, `WT_SESSION` | Detecção de capacidade de cor (modo de cor do tema) |\n| `COLORFGBG` | Auto-detecção de fundo claro/escuro do terminal |\n| `TERM_PROGRAM`, `TERM_PROGRAM_VERSION`, `TERMINAL_EMULATOR` | Identidade do terminal no prompt/contexto do sistema |\n| `KDE_FULL_SESSION`, `XDG_CURRENT_DESKTOP`, `DESKTOP_SESSION`, `XDG_SESSION_DESKTOP`, `GDMSESSION`, `WINDOWMANAGER` | Detecção de desktop/gerenciador de janelas no prompt/contexto do sistema |\n| `KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION` | IDs de breadcrumb de sessão estáveis por terminal |\n| `SHELL`, `ComSpec`, `TERM_PROGRAM`, `TERM` | Diagnósticos de informações do sistema |\n| `APPDATA`, `XDG_CONFIG_HOME` | Resolução de caminho de configuração do lspmux |\n| `HOME` | Encurtamento de caminho na UI de comando MCP |\n\n---\n\n## 9) Flags de carregamento nativo/debug\n\n| Variável | Comportamento |\n|---|---|\n| `PI_DEV` | Habilita diagnósticos verbosos de carregamento de addon nativo em `packages/natives` |\n\n## 10) Flags de runtime da TUI (pacote compartilhado, afeta a UX do coding-agent)\n\n| Variável | Comportamento |\n|---|---|\n| `PI_NOTIFICATIONS` | `off` / `0` / `false` suprimem notificações de desktop |\n| `PI_TUI_WRITE_LOG` | Se definida, registra escritas da TUI em arquivo |\n| `PI_HARDWARE_CURSOR` | Se `1`, habilita modo de cursor de hardware |\n| `PI_CLEAR_ON_SHRINK` | Se `1`, limpa linhas vazias quando o conteúdo encolhe |\n| `PI_DEBUG_REDRAW` | Se `1`, habilita log de debug de redesenho |\n| `PI_TUI_DEBUG` | Se `1`, habilita caminho de dump de debug profundo da TUI |\n\n---\n\n## 11) Controles de geração de commit\n\n| Variável | Comportamento |\n|---|---|\n| `PI_COMMIT_TEST_FALLBACK` | Se `true` (case-insensitive), força o caminho de geração de commit por fallback |\n| `PI_COMMIT_NO_FALLBACK` | Se `true`, desabilita fallback quando o agente não retorna nenhuma proposta |\n| `PI_COMMIT_MAP_REDUCE` | Se `false`, desabilita o caminho de análise de commit por map-reduce |\n| `DEBUG` | Se definida, stack traces de erro do agente de commit são impressos |\n\n---\n\n## Variáveis sensíveis à segurança\n\nTrate estas como segredos; não as registre em logs nem as commit:\n\n- Chaves de provedor/API e credenciais OAuth/bearer (todas as `*_API_KEY`, `*_TOKEN`, tokens de acesso/refresh OAuth)\n- Credenciais de nuvem (`AWS_*`, o caminho de `GOOGLE_APPLICATION_CREDENTIALS` pode expor material de conta de serviço)\n- Variáveis de autenticação de busca/provedor (`EXA_API_KEY`, `BRAVE_API_KEY`, `PERPLEXITY_API_KEY`, chaves de busca Anthropic)\n- Material mTLS Foundry (`CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`, `NODE_EXTRA_CA_CERTS` quando aponta para bundles de CA privados)\n\nO runtime Python também remove explicitamente muitas variáveis de chave comuns antes de gerar subprocessos de kernel (`packages/coding-agent/src/ipy/runtime.ts`).\n",
	"pt-br/configuration/fs-scan-cache-architecture.md": "---\ntitle: Arquitetura do Cache de Varredura do Sistema de Arquivos\ndescription: >-\n  Contrato do cache de varredura do sistema de arquivos para descoberta rápida\n  de arquivos com semântica stale-while-revalidate.\nsidebar:\n  order: 8\n  label: Cache de varredura do sistema de arquivos\ni18n:\n  sourceHash: 2a2bde1726ac\n  translator: machine\n---\n\n# Contrato de Arquitetura do Cache de Varredura do Sistema de Arquivos\n\nEste documento define o contrato atual para o cache compartilhado de varredura do sistema de arquivos implementado em Rust (`crates/pi-natives/src/fs_cache.rs`) e consumido pelas APIs nativas de descoberta/busca expostas para `packages/coding-agent`.\n\n## O que é este cache\n\nO cache armazena listas completas de entradas de varredura de diretórios (`GlobMatch[]`) indexadas por escopo de varredura e política de travessia, permitindo que operações de nível superior (filtragem glob, pontuação fuzzy, seleção de arquivos grep) sejam executadas sobre essas entradas em cache.\n\nObjetivos principais:\n\n- evitar caminhadas repetidas no sistema de arquivos para chamadas repetidas de descoberta/busca\n- manter consistência entre `glob`, `fuzzyFind` e `grep` quando compartilham a mesma política de varredura\n- permitir recuperação explícita de obsolescência para resultados vazios e invalidação explícita após mutações de arquivos\n\n## Propriedade e superfície pública\n\n- Implementação e política do cache: `crates/pi-natives/src/fs_cache.rs`\n- Consumidores nativos:\n  - `crates/pi-natives/src/glob.rs`\n  - `crates/pi-natives/src/fd.rs` (`fuzzyFind`)\n  - `crates/pi-natives/src/grep.rs`\n- Binding/exportação JS:\n  - `packages/natives/src/glob/index.ts` (`invalidateFsScanCache`)\n  - `packages/natives/src/glob/types.ts`\n  - `packages/natives/src/grep/types.ts`\n- Helpers de invalidação por mutação do coding-agent:\n  - `packages/coding-agent/src/tools/fs-cache-invalidation.ts`\n\n## Particionamento da chave de cache (contrato rígido)\n\nCada entrada é indexada por:\n\n- caminho do diretório `root` canonicalizado\n- booleano `include_hidden`\n- booleano `use_gitignore`\n\nImplicações:\n\n- Varreduras com e sem arquivos ocultos **não** compartilham entradas.\n- Varreduras que respeitam gitignore e varreduras com ignore desabilitado **não** compartilham entradas.\n- Os consumidores devem passar semânticas estáveis para o comportamento de hidden/gitignore; alterar qualquer uma das flags cria uma partição de cache diferente.\n\nA inclusão de `node_modules` **não** faz parte da chave de cache. O cache armazena entradas com `node_modules` incluído; a filtragem por consumidor é aplicada após a recuperação.\n\n## Comportamento de coleta da varredura\n\nO preenchimento do cache utiliza um walker determinístico (`ignore::WalkBuilder`) configurado por `include_hidden` e `use_gitignore`:\n\n- `follow_links(false)`\n- ordenado por caminho do arquivo\n- `.git` é sempre ignorado\n- `node_modules` é sempre coletado no momento da varredura do cache (e opcionalmente filtrado depois)\n- o tipo de arquivo da entrada + `mtime` são capturados via `symlink_metadata`\n\nAs raízes de busca são resolvidas por `resolve_search_path`:\n\n- caminhos relativos são resolvidos em relação ao cwd atual\n- o destino deve ser um diretório existente\n- a raiz é canonicalizada quando possível\n\n## Política de frescor e evição\n\nPolítica global (sobrescrevível por variável de ambiente):\n\n- `FS_SCAN_CACHE_TTL_MS` (padrão `1000`)\n- `FS_SCAN_EMPTY_RECHECK_MS` (padrão `200`)\n- `FS_SCAN_CACHE_MAX_ENTRIES` (padrão `16`)\n\nComportamento:\n\n- `get_or_scan(...)`\n  - se o TTL for `0`: ignora o cache completamente, sempre faz varredura nova (`cache_age_ms = 0`)\n  - em cache hit dentro do TTL: retorna entradas em cache + `cache_age_ms` diferente de zero\n  - em hit expirado: remove a chave, faz nova varredura, armazena entrada nova\n- a aplicação do limite máximo de entradas utiliza evição por ordem de antiguidade baseada em `created_at`\n\n## Reverificação rápida de resultado vazio (separada de hits normais)\n\nCache hit normal:\n\n- um cache hit dentro do TTL retorna as entradas em cache e não faz mais nada.\n\nReverificação rápida de resultado vazio:\n\n- esta é uma política do **lado do chamador** usando `ScanResult.cache_age_ms`\n- se o resultado filtrado/consultado estiver vazio e a idade da varredura em cache for pelo menos `empty_recheck_ms()`, o chamador realiza um `force_rescan(...)` e tenta novamente\n- destinado a reduzir resultados falso-negativos obsoletos quando arquivos foram adicionados recentemente, mas o cache ainda está dentro do TTL\n\nConsumidores atuais:\n\n- `glob`: reverifica quando as correspondências filtradas estão vazias e a idade da varredura excede o limiar\n- `fuzzyFind` (`fd.rs`): reverifica apenas quando a query não está vazia e as correspondências pontuadas estão vazias\n- `grep`: reverifica quando a lista de arquivos candidatos selecionados está vazia\n\n## Padrões dos consumidores e uso do cache\n\nO cache é opt-in em todas as APIs expostas (`cache?: boolean`, padrão `false`).\n\nPadrões atuais nas APIs nativas:\n\n- `glob`: `hidden=false`, `gitignore=true`, `cache=false`\n- `fuzzyFind`: `hidden=false`, `gitignore=true`, `cache=false`\n- `grep`: `hidden=true`, `cache=false`, e a varredura de cache sempre usa `use_gitignore=true`\n\nChamadores do coding-agent atualmente:\n\n- Descoberta de candidatos a menção em alto volume habilita o cache:\n  - `packages/coding-agent/src/utils/file-mentions.ts`\n  - perfil: `hidden=true`, `gitignore=true`, `includeNodeModules=true`, `cache=true`\n- Integração de `grep` no nível de ferramenta atualmente desabilita o cache de varredura (`cache: false`):\n  - `packages/coding-agent/src/tools/grep.ts`\n\n## Contrato de invalidação\n\nPonto de entrada nativo de invalidação:\n\n- `invalidateFsScanCache(path?: string)`\n  - com `path`: remove entradas de cache cuja raiz é um prefixo do caminho de destino\n  - sem path: limpa todas as entradas do cache de varredura\n\nDetalhes do tratamento de caminhos:\n\n- caminhos de invalidação relativos são resolvidos em relação ao cwd\n- a invalidação tenta canonicalização\n- se o destino não existir (ex.: exclusão), o fallback canonicaliza o pai e reanexa o nome do arquivo quando possível\n- isso preserva o comportamento de invalidação para criação/exclusão/renomeação onde um dos lados pode não existir\n\n## Responsabilidades do fluxo de mutação do coding-agent\n\nO código do coding-agent deve invalidar após mutações bem-sucedidas no sistema de arquivos.\n\nHelpers centrais:\n\n- `invalidateFsScanAfterWrite(path)`\n- `invalidateFsScanAfterDelete(path)`\n- `invalidateFsScanAfterRename(oldPath, newPath)` (invalida ambos os lados quando os caminhos diferem)\n\nCallsites atuais de ferramentas de mutação:\n\n- `packages/coding-agent/src/tools/write.ts`\n- `packages/coding-agent/src/patch/index.ts` (fluxos hashline/patch/replace)\n\nRegra: se um fluxo muta conteúdo ou localização no sistema de arquivos e ignora esses helpers, bugs de obsolescência do cache são esperados.\n\n## Adicionando um novo consumidor de cache com segurança\n\nAo introduzir o uso de cache em um novo caminho de scanner/busca:\n\n1. **Use entradas estáveis de política de varredura**\n   - decida a semântica de hidden/gitignore primeiro\n   - passe-as consistentemente para `get_or_scan`/`force_rescan` para que as partições de cache sejam intencionais\n\n2. **Trate os dados do cache como pré-filtrados apenas pela política de travessia**\n   - aplique filtragem específica da ferramenta (padrões glob, filtros de tipo, regras de node_modules) após a recuperação\n   - nunca assuma que as entradas em cache já refletem seus filtros de nível superior\n\n3. **Implemente reverificação rápida de resultado vazio apenas para risco de falso-negativo obsoleto**\n   - use `scan.cache_age_ms >= empty_recheck_ms()`\n   - tente novamente uma vez com `force_rescan(..., store=true, ...)`\n   - mantenha esse caminho separado da lógica normal de cache-hit\n\n4. **Respeite o modo sem cache explicitamente**\n   - quando o chamador desabilitar o cache, chame `force_rescan(..., store=false, ...)`\n   - não popule o cache compartilhado em um caminho de requisição sem cache\n\n5. **Conecte a invalidação por mutação para qualquer novo caminho de escrita**\n   - após escrita/edição/exclusão/renomeação bem-sucedida, chame o helper de invalidação do coding-agent\n   - para renomeação/movimentação, invalide tanto o caminho antigo quanto o novo\n\n6. **Não adicione controles de TTL por chamada**\n   - o contrato atual é apenas política global (configurada por env), sem sobrescrita de TTL por requisição\n\n## Limites conhecidos\n\n- O escopo do cache é em memória e local ao processo (`DashMap`), não persistido entre reinicializações do processo.\n- O cache armazena entradas de varredura, não resultados finais das ferramentas.\n- `glob`/`fuzzyFind`/`grep` compartilham entradas de varredura apenas quando as dimensões da chave (`root`, `hidden`, `gitignore`) correspondem.\n- `.git` é sempre excluído no momento da coleta da varredura, independentemente das opções do chamador.\n",
	"pt-br/configuration/hooks.md": "---\ntitle: Hooks\ndescription: >-\n  Sistema de hooks para automação de eventos pré/pós no ciclo de vida do agente\n  de codificação.\nsidebar:\n  order: 4\n  label: Hooks\ni18n:\n  sourceHash: cdbec10bc405\n  translator: machine\n---\n\n# Hooks\n\nEste documento descreve o **código atual do subsistema de hooks** em `src/extensibility/hooks/*`.\n\n## Status atual em tempo de execução\n\nO pacote de hooks (`src/extensibility/hooks/`) ainda é exportado e utilizável como superfície de API, mas o runtime padrão do CLI agora inicializa o caminho do **executor de extensões**. No fluxo de inicialização atual:\n\n- `--hook` é tratado como um alias para `--extension` (os caminhos do CLI são mesclados em `additionalExtensionPaths`)\n- as ferramentas são encapsuladas por `ExtensionToolWrapper`, não por `HookToolWrapper`\n- as transformações de contexto e emissões de ciclo de vida passam pelo `ExtensionRunner`\n\nPortanto, este arquivo documenta a implementação do subsistema de hooks em si (tipos/carregador/executor/encapsulador), incluindo comportamento legado e restrições.\n\n## Arquivos principais\n\n- `src/extensibility/hooks/types.ts` — contexto de hook, tipos de eventos e contratos de resultado\n- `src/extensibility/hooks/loader.ts` — carregamento de módulos e bridge de descoberta de hooks\n- `src/extensibility/hooks/runner.ts` — despacho de eventos, busca de comandos e sinalização de erros\n- `src/extensibility/hooks/tool-wrapper.ts` — encapsulador de interceptação pré/pós de ferramentas\n- `src/extensibility/hooks/index.ts` — exportações/reexportações\n\n## O que é um módulo de hook\n\nUm módulo de hook deve exportar por padrão uma fábrica:\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function hook(pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName === \"bash\" && String(event.input.command ?? \"\").includes(\"rm -rf\")) {\n   return { block: true, reason: \"blocked by policy\" };\n  }\n });\n}\n```\n\nA fábrica pode:\n\n- registrar manipuladores de eventos com `pi.on(...)`\n- enviar mensagens personalizadas persistentes com `pi.sendMessage(...)`\n- persistir estado não-LLM com `pi.appendEntry(...)`\n- registrar comandos slash via `pi.registerCommand(...)`\n- registrar renderizadores de mensagens personalizados via `pi.registerMessageRenderer(...)`\n- executar comandos shell via `pi.exec(...)`\n\n## Descoberta e carregamento\n\n`discoverAndLoadHooks(configuredPaths, cwd)` executa:\n\n1. Carrega hooks descobertos do registro de capacidades (`loadCapability(\"hooks\")`)\n2. Acrescenta caminhos configurados explicitamente (deduplicados por caminho absoluto)\n3. Chama `loadHooks(allPaths, cwd)`\n\n`loadHooks` então importa cada caminho e espera uma função `default`.\n\n### Resolução de caminhos\n\n`loader.ts` resolve caminhos de hooks da seguinte forma:\n\n- caminho absoluto: usado como está\n- caminho com `~`: expandido\n- caminho relativo: resolvido em relação ao `cwd`\n\n### Incompatibilidade legada importante\n\nOs provedores de descoberta para `hookCapability` ainda modelam arquivos de hook shell pré/pós no estilo antigo (por exemplo, `.claude/hooks/pre/*`, `.xcsh/.../hooks/pre/*`).\n\nO carregador de hooks aqui usa importação dinâmica de módulos e requer uma fábrica de hook padrão em JS/TS. Se um caminho de hook descoberto não puder ser importado como módulo, o carregamento falha e é reportado em `LoadHooksResult.errors`.\n\n## Superfícies de eventos\n\nOs eventos de hook são fortemente tipados em `types.ts`.\n\n### Eventos de sessão\n\n- `session_start`\n- `session_before_switch` → pode retornar `{ cancel?: boolean }`\n- `session_switch`\n- `session_before_branch` → pode retornar `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_branch`\n- `session_before_compact` → pode retornar `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session.compacting` → pode retornar `{ context?: string[]; prompt?: string; preserveData?: Record<string, unknown> }`\n- `session_compact`\n- `session_before_tree` → pode retornar `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n- `session_tree`\n- `session_shutdown`\n\n### Eventos de agente/contexto\n\n- `context` → pode retornar `{ messages?: Message[] }`\n- `before_agent_start` → pode retornar `{ message?: { customType; content; display; details } }`\n- `agent_start`\n- `agent_end`\n- `turn_start`\n- `turn_end`\n- `auto_compaction_start`\n- `auto_compaction_end`\n- `auto_retry_start`\n- `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### Eventos de ferramentas (modelo pré/pós)\n\n- `tool_call` (pré-execução) → pode retornar `{ block?: boolean; reason?: string }`\n- `tool_result` (pós-execução) → pode retornar `{ content?; details?; isError? }`\n\nEste é o modelo central de interceptação pré/pós do subsistema de hooks.\n\n```text\nFluxo de interceptação de ferramentas por hook\n\nmanipuladores de tool_call\n   │\n   ├─ algum { block: true }? ── sim ──> throw (ferramenta bloqueada)\n   │\n   └─ não\n      │\n      ▼\n   executa a ferramenta subjacente\n      │\n      ├─ sucesso ──> manipuladores de tool_result podem sobrescrever { content, details }\n      │\n      └─ erro    ──> emite tool_result(isError=true) e depois relança o erro original\n```\n\n## Modelo de execução e semântica de mutação\n\n### 1) Pré-execução: `tool_call`\n\n`HookToolWrapper.execute()` emite `tool_call` antes da execução da ferramenta.\n\n- se qualquer manipulador retornar `{ block: true }`, a execução é interrompida\n- se o manipulador lançar uma exceção, o encapsulador falha de forma segura e bloqueia a execução\n- o `reason` retornado torna-se o texto do erro lançado\n\n### 2) Execução da ferramenta\n\nA ferramenta subjacente é executada normalmente se não for bloqueada.\n\n### 3) Pós-execução: `tool_result`\n\nApós o sucesso, o encapsulador emite `tool_result` com:\n\n- `toolName`, `toolCallId`, `input`\n- `content`\n- `details`\n- `isError: false`\n\nSe o manipulador retornar sobreposições:\n\n- `content` pode substituir o conteúdo do resultado\n- `details` pode substituir os detalhes do resultado\n\nEm caso de falha da ferramenta, o encapsulador emite `tool_result` com `isError: true` e o conteúdo do texto de erro, depois relança o erro original.\n\n### O que os hooks podem mutar\n\n- contexto LLM para uma única chamada via `context` (cadeia de substituição de `messages`)\n- conteúdo/detalhes da saída da ferramenta em chamadas bem-sucedidas (caminho `tool_result`)\n- mensagem injetada pré-agente via `before_agent_start`\n- cancelamento/compactação personalizada/comportamento de árvore via `session_before_*` e `session.compacting`\n\n### O que os hooks não podem mutar nesta implementação\n\n- parâmetros de entrada brutos da ferramenta in-place (apenas bloquear/permitir em `tool_call`)\n- continuação da execução após erros lançados pela ferramenta (o caminho de erro relança)\n- status final de sucesso/erro no comportamento do encapsulador (o `isError` retornado é tipado mas não aplicado pelo `HookToolWrapper`)\n\n## Ordenação e comportamento em conflito\n\n### Ordenação no nível de descoberta\n\nOs provedores de capacidades são ordenados por prioridade (maior primeiro). A deduplicação é feita por chave de capacidade; o primeiro encontrado vence.\n\nPara `hooks`, a chave de capacidade é `${type}:${tool}:${name}`. Duplicatas sombreadas de provedores de menor prioridade são marcadas e excluídas da lista descoberta efetiva.\n\n### Ordem de carregamento\n\n`discoverAndLoadHooks` constrói uma lista plana `allPaths`, deduplicada por caminho absoluto resolvido, e então `loadHooks` itera nessa ordem.\nA ordem dos arquivos dentro de cada diretório descoberto depende da saída de `readdir`; o carregador de hooks não realiza ordenação adicional.\n\n### Ordem de manipuladores em tempo de execução\n\nDentro de `HookRunner`, a ordem é determinística pela sequência de registro:\n\n1. ordem do array de hooks\n2. ordem de registro do manipulador por hook/evento\n\nComportamento em conflito por tipo de evento:\n\n- `tool_call`: o último resultado retornado vence, a menos que um manipulador bloqueie; o primeiro bloqueio causa curto-circuito\n- `tool_result`: a última sobreposição retornada vence (sem curto-circuito)\n- `context`: encadeado; cada manipulador recebe a saída de mensagens do manipulador anterior\n- `before_agent_start`: a primeira mensagem retornada é mantida; mensagens posteriores são ignoradas\n- `session_before_*`: o resultado retornado mais recente é rastreado; `cancel: true` causa curto-circuito imediatamente\n- `session.compacting`: o resultado retornado mais recente vence\n\nConflitos de comando/renderizador:\n\n- `getCommand(name)` retorna a primeira correspondência entre os hooks (o primeiro carregado vence)\n- `getMessageRenderer(customType)` retorna a primeira correspondência\n- `getRegisteredCommands()` retorna todos os comandos (sem deduplicação)\n\n## Interações com a UI (`HookContext.ui`)\n\n`HookUIContext` inclui:\n\n- `select`, `confirm`, `input`, `editor`\n- `notify`\n- `setStatus`\n- `custom`\n- `setEditorText`, `getEditorText`\n- getter `theme`\n\n`ctx.hasUI` indica se a UI interativa está disponível.\n\nAo executar sem UI, o comportamento padrão do contexto sem operação é:\n\n- `select/input/editor` retornam `undefined`\n- `confirm` retorna `false`\n- `notify`, `setStatus`, `setEditorText` são no-ops\n- `getEditorText` retorna `\"\"`\n\n### Comportamento da linha de status\n\nO texto de status do hook definido via `ctx.ui.setStatus(key, text)` é:\n\n- armazenado por chave\n- ordenado pelo nome da chave\n- sanitizado (`\\r`, `\\n`, `\\t` → espaços; espaços repetidos são colapsados)\n- concatenado e truncado por largura para exibição\n\n## Propagação de erros e fallback\n\n### Em tempo de carregamento\n\n- módulo inválido ou exportação padrão ausente → capturado em `LoadHooksResult.errors`\n- o carregamento continua para os outros hooks\n\n### Em tempo de evento\n\n`HookRunner.emit(...)` captura erros de manipuladores para a maioria dos eventos e emite `HookError` para os ouvintes (`hookPath`, `event`, `error`), então continua.\n\n`emitToolCall(...)` é mais restrito: erros de manipuladores não são suprimidos ali; eles se propagam para o chamador. Em `HookToolWrapper`, isso bloqueia a chamada da ferramenta (falha segura).\n\n## Exemplos realistas de API\n\n### Bloquear comandos bash inseguros\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName !== \"bash\") return;\n  const cmd = String(event.input.command ?? \"\");\n  if (!cmd.includes(\"rm -rf\")) return;\n\n  if (!ctx.hasUI) return { block: true, reason: \"rm -rf blocked (no UI)\" };\n  const ok = await ctx.ui.confirm(\"Dangerous command\", `Allow: ${cmd}`);\n  if (!ok) return { block: true, reason: \"user denied command\" };\n });\n}\n```\n\n### Redigir saída de ferramenta na pós-execução\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_result\", async event => {\n  if (event.toolName !== \"read\" || event.isError) return;\n\n  const redacted = event.content.map(chunk => {\n   if (chunk.type !== \"text\") return chunk;\n   return { ...chunk, text: chunk.text.replaceAll(/API_KEY=\\S+/g, \"API_KEY=[REDACTED]\") };\n  });\n\n  return { content: redacted };\n });\n}\n```\n\n### Modificar o contexto do modelo por chamada LLM\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"context\", async event => {\n  const filtered = event.messages.filter(msg => !(msg.role === \"custom\" && msg.customType === \"debug-only\"));\n  return { messages: filtered };\n });\n}\n```\n\n### Registrar comando slash com métodos de contexto seguros para comandos\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.registerCommand(\"handoff\", {\n  description: \"Create a new session with setup message\",\n  handler: async (_args, ctx) => {\n   await ctx.waitForIdle();\n   await ctx.newSession({\n    parentSession: ctx.sessionManager.getSessionFile(),\n    setup: async sm => {\n     sm.appendMessage({\n      role: \"user\",\n      content: [{ type: \"text\", text: \"Continue from prior session summary.\" }],\n      timestamp: Date.now(),\n     });\n    },\n   });\n  },\n });\n}\n```\n\n## Superfície de exportação\n\n`src/extensibility/hooks/index.ts` exporta:\n\n- APIs de carregamento (`discoverAndLoadHooks`, `loadHooks`)\n- executor e encapsulador (`HookRunner`, `HookToolWrapper`)\n- todos os tipos de hooks\n- reexportação de `execCommand`\n\nE a raiz do pacote (`src/index.ts`) reexporta os **tipos** de hook como superfície de compatibilidade legada.\n",
	"pt-br/configuration/porting-from-pi-mono.md": "---\ntitle: 'Portando do pi-mono: Um Guia Prático de Merge'\ndescription: >-\n  Guia prático para migrar código do monorepo pi-mono para a base de código\n  xcsh.\nsidebar:\n  order: 9\n  label: Portando do pi-mono\ni18n:\n  sourceHash: fd4e8c09303d\n  translator: machine\n---\n\n# Portando do pi-mono: Um Guia Prático de Merge\n\nEste guia é um checklist repetível para portar mudanças do pi-mono para este repositório.\nUse-o para qualquer merge: arquivo único, feature branch ou sincronização de release completa.\n\n## Último Ponto de Sincronização\n\n**Commit:** `b21b42d032919de2f2e6920a76fa9a37c3920c0a`\n**Data:** 2026-03-22\n\nAtualize esta seção após cada sincronização; não reutilize o intervalo anterior.\n\nAo iniciar uma nova sincronização, gere patches a partir deste commit em diante:\n\n```bash\ngit format-patch b21b42d032919de2f2e6920a76fa9a37c3920c0a..HEAD --stdout > changes.patch\n```\n\n## 0) Defina o escopo\n\n- Identifique a referência upstream (commit, tag ou PR).\n- Liste os pacotes ou pastas que você planeja alterar.\n- Decida quais funcionalidades estão no escopo e quais são intencionalmente ignoradas.\n\n## 1) Traga o código com segurança\n\n- Prefira um diff limpo e focado em vez de uma cópia integral.\n- Evite copiar artefatos de build ou arquivos gerados.\n- Se o upstream adicionou novos arquivos, adicione-os explicitamente e revise o conteúdo.\n\n## 2) Siga as convenções de extensão nos imports\n\nA maioria dos fontes TypeScript em runtime omitem `.js` nos imports internos, mas alguns entrypoints de test/bench mantêm `.js` para compatibilidade com ESM em runtime. Siga o estilo existente do pacote local; não remova extensões de forma indiscriminada.\n\n- Em fontes runtime de `packages/coding-agent`, mantenha imports internos sem extensão, exceto ao importar assets não-TS.\n- Em `packages/tui/test` e `packages/natives/bench`, mantenha `.js` onde os arquivos ao redor já o utilizam.\n- Mantenha extensões de arquivo reais quando exigidas por ferramentas (ex.: `.json`, `.css`, embeds de texto `.md`).\n- Exemplo: `import { x } from \"./foo.js\";` → `import { x } from \"./foo\";` (somente quando a convenção do pacote é sem extensão).\n\n## 3) Substitua os escopos dos imports\n\nO upstream usa escopos de pacote diferentes. Substitua-os de forma consistente.\n\n- Substitua escopos antigos pelo escopo local usado aqui.\n- Exemplos (ajuste para corresponder aos pacotes que você está portando):\n  - `@mariozechner/pi-coding-agent` → `@f5-sales-demo/xcsh`\n  - `@mariozechner/pi-agent-core` → `@f5-sales-demo/pi-agent-core`\n  - `@mariozechner/pi-tui` → `@f5-sales-demo/pi-tui`\n  - `@mariozechner/pi-ai` → `@f5-sales-demo/pi-ai`\n\n## 4) Use APIs do Bun quando elas melhorarem as do Node\n\nRodamos em Bun. Substitua APIs do Node apenas quando o Bun oferecer uma alternativa melhor.\n\n**SUBSTITUA:**\n\n- Criação de processos: `child_process.spawn` → Bun Shell `$` para comandos simples, `Bun.spawn`/`Bun.spawnSync` para streaming ou trabalhos de longa duração\n- I/O de arquivos: `fs.readFileSync` → `Bun.file().text()` / `Bun.write()`\n- Clientes HTTP: `node-fetch`, `axios` → `fetch` nativo\n- Hashing criptográfico: `node:crypto` → Web Crypto ou `Bun.hash`\n- SQLite: `better-sqlite3` → `bun:sqlite`\n- Carregamento de variáveis de ambiente: `dotenv` → Bun carrega `.env` automaticamente\n\n**NÃO substitua (estes funcionam bem no Bun):**\n\n- `os.homedir()` — NÃO substitua por `Bun.env.HOME`, `Bun.env.HOME` ou literal `\"~\"`\n- `os.tmpdir()` — NÃO substitua por `Bun.env.TMPDIR || \"/tmp\"` ou caminhos hardcoded\n- `fs.mkdtempSync()` — NÃO substitua por construção manual de caminho\n- `path.join()`, `path.resolve()`, etc. — estes estão ok\n\n**Estilo de import:** Use o prefixo `node:` apenas com namespace imports (sem named imports de `node:fs` ou `node:path`).\n\n**Convenções adicionais do Bun:**\n\n- Prefira Bun Shell `$` para comandos curtos e sem streaming; use `Bun.spawn` apenas quando precisar de I/O de streaming ou controle de processo.\n- Use `Bun.file()`/`Bun.write()` para arquivos e `node:fs/promises` para diretórios.\n- Evite verificações com `Bun.file().exists()`; use tratamento `isEnoent` em try/catch.\n- Prefira `Bun.sleep(ms)` em vez de wrappers de `setTimeout`.\n\n**Errado:**\n\n```typescript\n// BROKEN: env vars may be undefined, \"~\" is not expanded\nconst home = Bun.env.HOME || \"~\";\nconst tmp = Bun.env.TMPDIR || \"/tmp\";\n```\n\n**Correto:**\n\n```typescript\nimport * as os from \"node:os\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst configDir = path.join(os.homedir(), \".config\", \"myapp\");\nconst tempDir = fs.mkdtempSync(path.join(os.tmpdir(), \"myapp-\"));\n```\n\n## 5) Prefira embeds do Bun (sem cópia)\n\nNão copie assets de runtime ou arquivos vendor durante o build.\n\n- Se o upstream copia assets para uma pasta dist, substitua por embeds compatíveis com Bun.\n- Prompts são arquivos `.md` estáticos; use text imports do Bun (`with { type: \"text\" }`) e Handlebars em vez de strings de prompt inline.\n- Use `import.meta.dir` + `Bun.file` para carregar recursos adjacentes que não sejam texto.\n- Mantenha assets no repositório e deixe o bundler incluí-los.\n- Elimine scripts de cópia, a menos que o usuário solicite explicitamente.\n- Se o upstream lê um arquivo de fallback empacotado em runtime, substitua leituras do sistema de arquivos por um import de texto embed do Bun.\n  - Exemplo (fallback de instruções Codex):\n    - `const FALLBACK_PROMPT_PATH = join(import.meta.dir, \"codex-instructions.md\");` -> removido\n    - `import FALLBACK_INSTRUCTIONS from \"./codex-instructions.md\" with { type: \"text\" };`\n    - Use `return FALLBACK_INSTRUCTIONS;` em vez de `readFileSync(FALLBACK_PROMPT_PATH, \"utf8\")`\n\n## 6) Porte o `package.json` com cuidado\n\nTrate o `package.json` como um contrato. Faça o merge intencionalmente.\n\n- Mantenha `name`, `version`, `type`, `exports` e `bin` existentes, a menos que a portagem exija mudanças.\n- Substitua scripts npm/node por equivalentes Bun (ex.: `bun check`, `bun test`).\n- Certifique-se de que as dependências usem o escopo correto.\n- Não faça downgrade de dependências para corrigir erros de tipo; faça upgrade em vez disso.\n- Valide links de pacotes do workspace e `peerDependencies`.\n\n## 7) Alinhe o estilo de código e as ferramentas\n\n- Mantenha as convenções de formatação existentes.\n- Não introduza `any` a menos que seja necessário.\n- Evite imports dinâmicos e imports de tipo inline; use apenas imports no nível superior.\n- Nunca construa prompts no código; prompts são arquivos `.md` estáticos renderizados com Handlebars.\n- No coding-agent, nunca use `console.log`/`console.warn`/`console.error`; use `logger` de `@f5-sales-demo/pi-utils`.\n- Use `Promise.withResolvers()` em vez de `new Promise((resolve, reject) => ...)`.\n- **Sem palavras-chave `private`/`protected`/`public` em campos ou métodos de classe.** Use campos privados ES `#` para encapsulamento; deixe membros acessíveis sem palavra-chave. A única exceção são propriedades de parâmetro do construtor (`constructor(private readonly x: T)`), onde a palavra-chave é exigida pelo TypeScript. Ao portar código upstream que usa `private foo` ou `protected bar`, converta para `#foo` (privado) ou `bar` simples (acessível).\n- Prefira helpers e utilitários existentes em vez de código ad-hoc novo.\n- Preserve as mudanças de infraestrutura Bun-first já feitas neste repositório:\n  - O runtime é Bun (sem entry points Node).\n  - O gerenciador de pacotes é Bun (sem lockfiles npm).\n  - APIs pesadas do Node (`child_process`, `readline`) são substituídas por equivalentes Bun.\n  - APIs leves do Node (`os.homedir`, `os.tmpdir`, `fs.mkdtempSync`, `path.*`) são mantidas.\n  - Shebangs de CLI usam `bun` (não `node`, não `tsx`).\n  - Pacotes usam arquivos fonte diretamente (sem etapa de build TypeScript).\n  - Workflows de CI rodam Bun para install/check/test.\n\n## 8) Remova camadas antigas de compatibilidade\n\nA menos que solicitado, remova shims de compatibilidade do upstream.\n\n- Delete APIs antigas que foram substituídas.\n- Atualize todos os pontos de chamada para a nova API diretamente.\n- Não mantenha versões `*_v2` ou paralelas.\n\n## 9) Atualize documentação e referências\n\n- Substitua links do repositório pi-mono quando apropriado.\n- Atualize exemplos para usar Bun e os escopos de pacote corretos.\n- Certifique-se de que as instruções do README ainda correspondam ao comportamento atual do repositório.\n\n## 10) Valide a portagem\n\nExecute as verificações padrão após as mudanças:\n\n- `bun check`\n\nSe o repositório já tiver verificações falhando que não estão relacionadas às suas mudanças, sinalize isso.\nTestes usam o runner do Bun (não Vitest), mas só execute `bun test` quando explicitamente solicitado.\n\n## 11) Proteja funcionalidades melhoradas (lista de armadilhas de regressão)\n\nSe você já melhorou comportamentos localmente, trate-os como **inegociáveis**. Antes de portar, anote\nas melhorias e adicione verificações explícitas para que não se percam no merge.\n\n- **Congele o comportamento esperado**: adicione uma nota curta \"antes/depois\" para cada melhoria (entradas, saídas,\n  defaults, casos extremos). Isso previne rollback silencioso.\n- **Mapeie APIs antigas → novas**: se o upstream renomeou conceitos (hooks → extensions, custom tools → tools, etc.),\n  certifique-se de que cada ponto de entrada antigo ainda esteja conectado. Um flag ou export perdido equivale a funcionalidade perdida.\n- **Verifique exports**: confira `exports` do `package.json`, tipos públicos e arquivos barrel. Portagens do upstream frequentemente\n  esquecem de re-exportar adições locais.\n- **Cubra caminhos não-felizes**: se você corrigiu tratamento de erros, timeouts ou lógica de fallback, adicione um teste ou ao\n  menos um checklist manual que exercite esses caminhos.\n- **Verifique defaults e ordem de merge de configuração**: melhorias frequentemente vivem em defaults. Confirme que novos defaults\n  não reverteram (ex.: nova precedência de config, funcionalidades desabilitadas, listas de ferramentas).\n- **Audite comportamento de env/shell**: se você corrigiu execução ou sandboxing, verifique se o novo caminho ainda usa seu\n  env sanitizado e não reintroduz overrides de alias/função.\n- **Re-execute exemplos direcionados**: mantenha um conjunto mínimo de exemplos \"sabidamente bons\" e execute-os após a portagem\n  (flags de CLI, registro de extensão, execução de ferramentas).\n\n## 12) Detecte e trate código retrabalhado\n\nAntes de portar um arquivo, verifique se o upstream o refatorou significativamente:\n\n```bash\n# Compare the file you're about to port against what you have locally\ngit diff HEAD upstream/main -- path/to/file.ts\n```\n\nSe o diff mostrar que o arquivo foi **retrabalhado** (não apenas corrigido pontualmente):\n\n- Novas abstrações, conceitos renomeados, módulos mesclados, fluxo de dados alterado\n\nEntão você deve **ler a nova implementação completamente** antes de portar. Merge cego de código retrabalhado perde funcionalidade porque:\n\nNota: o modo interativo foi recentemente dividido em controllers/utils/types. Ao fazer backport de mudanças relacionadas, porte as atualizações para os arquivos individuais que criamos e certifique-se de que a conexão em `interactive-mode.ts` permaneça sincronizada.\n\n1. **Defaults mudam silenciosamente** - Uma nova variável `defaultFoo = [a, b]` pode substituir um antigo `getAllFoo()` que retornava `[a, b, c, d, e]`.\n\n2. **Opções de API são descartadas** - Quando sistemas se fundem (ex.: `hooks` + `customTools` → `extensions`), opções antigas podem não se conectar à nova implementação.\n\n3. **Caminhos de código ficam obsoletos** - Um conceito renomeado (ex.: `hookMessage` → `custom`) precisa de atualizações em cada switch statement, type guard e handler — não apenas na definição.\n\n4. **Contexto/capacidades encolhem** - APIs antigas podem ter exposto `{ logger, typebox, pi }` que novas APIs esqueceram de incluir.\n\n### Processo de portagem semântica\n\nQuando o upstream retrabalharam um módulo:\n\n1. **Leia a implementação antiga** - Entenda o que ela fazia, quais opções aceitava, o que expunha.\n\n2. **Leia a nova implementação** - Entenda as novas abstrações e como elas mapeiam para o comportamento antigo.\n\n3. **Verifique a paridade de funcionalidades** - Para cada capacidade no código antigo, confirme que o novo código a preserva ou a remove explicitamente.\n\n4. **Busque resquícios** - Procure por nomes/conceitos antigos que podem ter sido esquecidos em switch statements, handlers, componentes de UI.\n\n5. **Teste os limites** - Flags de CLI, opções do SDK, event handlers, valores padrão — é onde as regressões se escondem.\n\n### Verificações rápidas\n\n```bash\n# Find all uses of an old concept that may need updating\nrg \"oldConceptName\" --type ts\n\n# Compare default values between versions\ngit show upstream/main:path/to/file.ts | rg \"default|DEFAULT\"\n\n# Check if all enum/union values have handlers\nrg \"case \\\"\" path/to/file.ts\n```\n\n## 13) Checklist rápido de auditoria\n\nUse isto como uma passagem final antes de concluir:\n\n- [ ] Extensões de import seguem a convenção do pacote local (sem remoção indiscriminada de `.js`)\n- [ ] Nenhuma API exclusiva do Node em código novo/portado\n- [ ] Todos os escopos de pacote atualizados\n- [ ] Scripts do `package.json` usam Bun\n- [ ] Prompts são text imports `.md` (sem strings de prompt inline)\n- [ ] Sem `console.*` no coding-agent (use `logger`)\n- [ ] Assets carregam via padrões de embed do Bun (sem scripts de cópia)\n- [ ] Testes ou verificações executam (ou explicitamente notados como bloqueados)\n- [ ] Sem regressões de funcionalidade (veja seções 11-12)\n\n## 14) Formato da mensagem de commit\n\nAo fazer commit de um backport, siga o formato do repositório `<type>(scope): <descrição no passado>` e mantenha o intervalo\nde commits no título.\n\n```\nfix(coding-agent): backported pi-mono changes (<from>..<to>)\n\npackages/<package>:\n- <type>: <description>\n- <type>: <description> (#<issue> by @<contributor>)\n\npackages/<other-package>:\n- <type>: <description>\n```\n\n**Exemplo:**\n\n```\nfix(coding-agent): backported pi-mono changes (9f3eef65f..52532c7c0)\n\npackages/ai:\n- fix: handle \"sensitive\" stop reason from Anthropic API\n- fix: normalize tool call IDs with special characters for Responses API\n- fix: add overflow detection for Bedrock, MiniMax, Kimi providers\n- fix: 429 status is rate limiting, not context overflow\n\npackages/tui:\n- fix: refactored autocomplete state tracking\n- fix: file autocomplete should not trigger on empty text\n- fix: configurable autocomplete max visible items\n- fix: improved table column width calculation with word-aware wrapping\n\npackages/coding-agent:\n- fix: preserve external config.yml edits on save (#1046 by @nicobailonMD)\n- fix: resolve macOS NFD and curly quote variants in file paths\n```\n\n**Regras:**\n\n- Agrupe mudanças por pacote\n- Use tipos de commit convencionais (`fix`, `feat`, `refactor`, `perf`, `docs`)\n- Inclua números de issue/PR do upstream e atribuição de contribuidores para contribuições externas\n- O intervalo de commits no título ajuda a rastrear pontos de sincronização\n\n## 15) Divergências Intencionais\n\nNosso fork tem decisões arquiteturais que diferem do upstream. **Não porte estes padrões do upstream:**\n\n### Arquitetura de UI\n\n| Upstream                                    | Nosso Fork                                                | Motivo                                                                |\n| ------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------- |\n| Classe `FooterDataProvider`                 | `StatusLineComponent`                                     | Status line mais simples e integrado                                  |\n| `ctx.ui.setHeader()` / `ctx.ui.setFooter()` | Stub em modos não-TUI                                     | Implementado no TUI, no-op em outros                                  |\n| `ctx.ui.setEditorComponent()`               | Stub em modos não-TUI                                     | Implementado no TUI, no-op em outros                                  |\n| Objeto de opções `InteractiveModeOptions`   | Args posicionais no construtor (tipo de opções ainda exportado) | Mantenha a assinatura do construtor; atualize o tipo quando o upstream adicionar campos |\n\n### Nomenclatura de Componentes\n\n| Upstream                     | Nosso Fork              |\n| ---------------------------- | ----------------------- |\n| `extension-input.ts`         | `hook-input.ts`         |\n| `extension-selector.ts`      | `hook-selector.ts`      |\n| `ExtensionInputComponent`    | `HookInputComponent`    |\n| `ExtensionSelectorComponent` | `HookSelectorComponent` |\n\n### Nomenclatura de API\n\n| Upstream                                 | Nosso Fork                               | Notas                                     |\n| ---------------------------------------- | ---------------------------------------- | ----------------------------------------- |\n| `sessionManager.appendSessionInfo(name)` | `sessionManager.setSessionName(name)`    | Usamos `sessionName` em todo lugar        |\n| `sessionManager.getSessionName()`        | `sessionManager.getSessionName()`        | Igual (unificamos para corresponder ao RPC do upstream) |\n| `agent.sessionName` / `setSessionName()` | `agent.sessionName` / `setSessionName()` | Igual                                     |\n\n### Consolidação de Arquivos\n\n| Upstream                                           | Nosso Fork                              | Motivo                                  |\n| -------------------------------------------------- | --------------------------------------- | --------------------------------------- |\n| `clipboard.ts` + `clipboard-image.ts` (arquivos de ferramenta) | Módulo clipboard `@f5-sales-demo/pi-natives` | Mesclado em implementação nativa N-API  |\n\n### Framework de Testes\n\n| Upstream                  | Nosso Fork                    |\n| ------------------------- | ----------------------------- |\n| `vitest` com `vi.mock()`  | `bun:test` com `vi` do bun   |\n| Assertions `node:test`    | Matchers `expect()`           |\n\n### Arquitetura de Ferramentas\n\n| Upstream                            | Nosso Fork                                                        | Notas                                                     |\n| ----------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------- |\n| `createTool(cwd: string, options?)` | `createTools(session: ToolSession)` via registro `BUILTIN_TOOLS`  | Factories de ferramentas aceitam `ToolSession` e podem retornar `null` |\n| Interfaces `*Operations` por ferramenta | Interfaces por ferramenta permanecem (`FindOperations`, `GrepOperations`) | Usadas para overrides SSH/remoto                          |\n| `fs/promises` do Node.js em todo lugar | `Bun.file()`/`Bun.write()` para arquivos; `node:fs/promises` para diretórios | Prefira APIs do Bun quando elas simplificam               |\n\n### Armazenamento de Autenticação\n\n| Upstream                        | Nosso Fork                                  | Notas                                        |\n| ------------------------------- | ------------------------------------------- | -------------------------------------------- |\n| `proper-lockfile` + `auth.json` | `agent.db` (bun:sqlite)                     | Credenciais armazenadas exclusivamente em `agent.db` |\n| Credencial única por provedor   | Multi-credencial com seleção round-robin    | Afinidade de sessão e lógica de backoff preservadas |\n\n### Extensões\n\n| Upstream                      | Nosso Fork                                 |\n| ----------------------------- | ------------------------------------------ |\n| `jiti` para carregamento TypeScript | `import()` nativo do Bun                   |\n| Campo de manifesto `pkg.pi`   | `pkg.xcsh ?? pkg.pi` (prefira nosso namespace) |\n\n### Pule Estas Funcionalidades do Upstream\n\nAo portar, **pule** estes arquivos/funcionalidades inteiramente:\n\n- `footer-data-provider.ts` — usamos StatusLineComponent\n- `clipboard-image.ts` — clipboard está no módulo N-API `@f5-sales-demo/pi-natives`\n- Arquivos de workflow do GitHub — temos nosso próprio CI\n- `models.generated.ts` — auto-gerado, regenere localmente (como models.json em vez disso)\n\n### Funcionalidades que Adicionamos (Preserve Estas)\n\nEstas existem em nosso fork, mas não no upstream. **Nunca sobrescreva:**\n\n- `StatusLineComponent` no modo interativo\n- Auth multi-credencial com afinidade de sessão\n- Sistema de descoberta baseado em capacidades (`defineCapability`, `registerProvider`, `loadCapability`, `skillCapability`, etc.)\n- Integrações MCP/Exa/SSH\n- Writethrough LSP para format-on-save\n- Interceptação Bash (`checkBashInterception`)\n- Sugestões de caminho fuzzy na ferramenta de leitura\n",
	"pt-br/configuration/rpc.md": "---\ntitle: Referência do Protocolo RPC\ndescription: >-\n  Referência do protocolo JSON-RPC para comunicação entre processos dos\n  componentes do xcsh.\nsidebar:\n  order: 5\n  label: Protocolo RPC\ni18n:\n  sourceHash: b4a3ddaf08ab\n  translator: machine\n---\n\n# Referência do Protocolo RPC\n\nO modo RPC executa o agente de codificação como um protocolo JSON delimitado por nova linha sobre stdio.\n\n- **stdin**: comandos (`RpcCommand`) e respostas de UI de extensão\n- **stdout**: respostas de comandos (`RpcResponse`), eventos de sessão/agente, requisições de UI de extensão\n\nImplementação principal:\n\n- `src/modes/rpc/rpc-mode.ts`\n- `src/modes/rpc/rpc-types.ts`\n- `src/session/agent-session.ts`\n- `packages/agent/src/agent.ts`\n- `packages/agent/src/agent-loop.ts`\n\n## Inicialização\n\n```bash\nxcsh --mode rpc [regular CLI options]\n```\n\nNotas de comportamento:\n\n- Argumentos CLI `@file` são rejeitados no modo RPC.\n- O modo RPC desabilita a geração automática de título de sessão por padrão para evitar uma chamada extra ao modelo.\n- O modo RPC redefine as configurações que alteram o fluxo de trabalho `todo.*`, `task.*` e `async.*` para seus valores padrão embutidos, em vez de herdar substituições do usuário.\n- O processo lê stdin como JSONL (`readJsonl(Bun.stdin.stream())`).\n- Quando o stdin é fechado, o processo encerra com código `0`.\n- Respostas/eventos são escritos como um objeto JSON por linha.\n\n## Transporte e Enquadramento\n\nCada quadro é um único objeto JSON seguido por `\\n`.\n\nNão há envelope além da própria forma do objeto.\n\n### Categorias de quadros de saída (stdout)\n\n1. `RpcResponse` (`{ type: \"response\", ... }`)\n2. Objetos `AgentSessionEvent` (`agent_start`, `message_update`, etc.)\n3. `RpcExtensionUIRequest` (`{ type: \"extension_ui_request\", ... }`)\n4. Erros de extensão (`{ type: \"extension_error\", extensionPath, event, error }`)\n\n### Categorias de quadros de entrada (stdin)\n\n1. `RpcCommand`\n2. `RpcExtensionUIResponse` (`{ type: \"extension_ui_response\", ... }`)\n\n## Correlação de Requisição/Resposta\n\nTodos os comandos aceitam `id?: string` opcional.\n\n- Se fornecido, as respostas normais de comando ecoam o mesmo `id`.\n- `RpcClient` depende disso para resolução de requisições pendentes.\n\nComportamento de borda importante do runtime:\n\n- Respostas de comandos desconhecidos são emitidas com `id: undefined` (mesmo que a requisição tivesse um `id`).\n- Exceções de parse/handler no loop de entrada emitem `command: \"parse\"` com `id: undefined`.\n- `prompt` e `abort_and_prompt` retornam sucesso imediato, e podem então emitir uma resposta de erro posterior com o **mesmo** id se o agendamento assíncrono do prompt falhar.\n\n## Esquema de Comandos (canônico)\n\n`RpcCommand` é definido em `src/modes/rpc/rpc-types.ts`:\n\n### Prompting\n\n- `{ id?, type: \"prompt\", message: string, images?: ImageContent[], streamingBehavior?: \"steer\" | \"followUp\" }`\n- `{ id?, type: \"steer\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"follow_up\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"abort\" }`\n- `{ id?, type: \"abort_and_prompt\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"new_session\", parentSession?: string }`\n\n### Estado\n\n- `{ id?, type: \"get_state\" }`\n- `{ id?, type: \"set_todos\", phases: TodoPhase[] }`\n- `{ id?, type: \"set_host_tools\", tools: RpcHostToolDefinition[] }`\n\n### Modelo\n\n- `{ id?, type: \"set_model\", provider: string, modelId: string }`\n- `{ id?, type: \"cycle_model\" }`\n- `{ id?, type: \"get_available_models\" }`\n\n### Pensamento\n\n- `{ id?, type: \"set_thinking_level\", level: ThinkingLevel }`\n- `{ id?, type: \"cycle_thinking_level\" }`\n\n### Modos de fila\n\n- `{ id?, type: \"set_steering_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_follow_up_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_interrupt_mode\", mode: \"immediate\" | \"wait\" }`\n\n### Compactação\n\n- `{ id?, type: \"compact\", customInstructions?: string }`\n- `{ id?, type: \"set_auto_compaction\", enabled: boolean }`\n\n### Repetição\n\n- `{ id?, type: \"set_auto_retry\", enabled: boolean }`\n- `{ id?, type: \"abort_retry\" }`\n\n### Bash\n\n- `{ id?, type: \"bash\", command: string }`\n- `{ id?, type: \"abort_bash\" }`\n\n### Sessão\n\n- `{ id?, type: \"get_session_stats\" }`\n- `{ id?, type: \"export_html\", outputPath?: string }`\n- `{ id?, type: \"switch_session\", sessionPath: string }`\n- `{ id?, type: \"branch\", entryId: string }`\n- `{ id?, type: \"get_branch_messages\" }`\n- `{ id?, type: \"get_last_assistant_text\" }`\n- `{ id?, type: \"set_session_name\", name: string }`\n\n### Mensagens\n\n- `{ id?, type: \"get_messages\" }`\n\n## Esquema de Resposta\n\nTodos os resultados de comandos usam `RpcResponse`:\n\n- Sucesso: `{ id?, type: \"response\", command: <command>, success: true, data?: ... }`\n- Falha: `{ id?, type: \"response\", command: string, success: false, error: string }`\n\nOs payloads de dados são específicos por comando e definidos em `rpc-types.ts`.\n\n### Payload de `get_state`\n\n```json\n{\n  \"model\": { \"provider\": \"...\", \"id\": \"...\" },\n  \"thinkingLevel\": \"off|minimal|low|medium|high|xhigh\",\n  \"isStreaming\": false,\n  \"isCompacting\": false,\n  \"steeringMode\": \"all|one-at-a-time\",\n  \"followUpMode\": \"all|one-at-a-time\",\n  \"interruptMode\": \"immediate|wait\",\n  \"sessionFile\": \"...\",\n  \"sessionId\": \"...\",\n  \"sessionName\": \"...\",\n  \"autoCompactionEnabled\": true,\n  \"messageCount\": 0,\n  \"queuedMessageCount\": 0,\n  \"todoPhases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Todos\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the tool surface\",\n          \"status\": \"in_progress\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n### Payload de `set_todos`\n\nSubstitui o estado de tarefas em memória para a sessão atual e retorna a lista de fases normalizada:\n\n```json\n{\n  \"id\": \"req_2\",\n  \"type\": \"set_todos\",\n  \"phases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Evaluation\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the read tool surface\",\n          \"status\": \"in_progress\"\n        },\n        {\n          \"id\": \"task-2\",\n          \"content\": \"Exercise edit operations\",\n          \"status\": \"pending\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nIsso é útil para hosts que desejam pré-carregar um plano antes do primeiro prompt.\n\n### Payload de `set_host_tools`\n\nSubstitui o conjunto atual de ferramentas pertencentes ao host que o servidor RPC pode chamar de volta via stdio:\n\n```json\n{\n  \"id\": \"req_3\",\n  \"type\": \"set_host_tools\",\n  \"tools\": [\n    {\n      \"name\": \"echo_host\",\n      \"label\": \"Echo Host\",\n      \"description\": \"Echo a value from the embedding host\",\n      \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"message\": { \"type\": \"string\" }\n        },\n        \"required\": [\"message\"],\n        \"additionalProperties\": false\n      }\n    }\n  ]\n}\n```\n\nO payload de resposta é:\n\n```json\n{\n  \"toolNames\": [\"echo_host\"]\n}\n```\n\nEssas ferramentas são adicionadas ao registro de ferramentas da sessão ativa antes da próxima chamada ao modelo. Reenviar `set_host_tools` substitui o conjunto anterior pertencente ao host.\n\n## Esquema do Fluxo de Eventos\n\nO modo RPC encaminha objetos `AgentSessionEvent` de `AgentSession.subscribe(...)`.\n\nTipos de eventos comuns:\n\n- `agent_start`, `agent_end`\n- `turn_start`, `turn_end`\n- `message_start`, `message_update`, `message_end`\n- `tool_execution_start`, `tool_execution_update`, `tool_execution_end`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n- `todo_auto_clear`\n\nErros do executor de extensões são emitidos separadamente como:\n\n```json\n{ \"type\": \"extension_error\", \"extensionPath\": \"...\", \"event\": \"...\", \"error\": \"...\" }\n```\n\n`message_update` inclui deltas de streaming em `assistantMessageEvent` (deltas de texto/pensamento/chamada de ferramenta).\n\n## Concorrência e Ordenação de Prompt/Fila\n\nEste é o comportamento operacional mais importante.\n\n### Confirmação imediata vs conclusão\n\n`prompt` e `abort_and_prompt` são **confirmados imediatamente**:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n```\n\nIsso significa:\n\n- aceitação do comando != conclusão da execução\n- a conclusão final é observada via `agent_end`\n\n### Durante o streaming\n\n`AgentSession.prompt()` requer `streamingBehavior` durante streaming ativo:\n\n- `\"steer\"` => mensagem de direcionamento enfileirada (caminho de interrupção)\n- `\"followUp\"` => mensagem de acompanhamento enfileirada (caminho pós-turno)\n\nSe omitido durante o streaming, o prompt falha.\n\n### Padrões de fila\n\nDo esquema de configurações do agente de codificação (`packages/coding-agent/src/config/settings-schema.ts`):\n\n- `steeringMode`: `\"one-at-a-time\"`\n- `followUpMode`: `\"one-at-a-time\"`\n- `interruptMode`: `\"wait\"`\n\n### Semântica dos modos\n\n- `set_steering_mode` / `set_follow_up_mode`\n  - `\"one-at-a-time\"`: desenfileira uma mensagem enfileirada por turno\n  - `\"all\"`: desenfileira toda a fila de uma vez\n- `set_interrupt_mode`\n  - `\"immediate\"`: a execução de ferramentas verifica o direcionamento entre chamadas de ferramentas; direcionamento pendente pode abortar chamadas de ferramentas restantes no turno\n  - `\"wait\"`: adia o direcionamento até a conclusão do turno\n\n## Sub-Protocolo de UI de Extensão\n\nExtensões no modo RPC usam quadros de requisição/resposta de UI.\n\n### Requisição de saída\n\nMétodos de `RpcExtensionUIRequest` (`type: \"extension_ui_request\"`):\n\n- `select`, `confirm`, `input`, `editor`\n- `notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text`\n\nNota de runtime:\n\n- A geração automática de título de sessão é desabilitada no modo RPC, e requisições de UI `setTitle` também são suprimidas por padrão porque a maioria dos hosts não possui uma superfície significativa de título de terminal. Defina `PI_RPC_EMIT_TITLE=1` para reativar apenas o evento de UI.\n\nExemplo:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"123\", \"method\": \"confirm\", \"title\": \"Confirm\", \"message\": \"Continue?\", \"timeout\": 30000 }\n```\n\n### Resposta de entrada\n\n`RpcExtensionUIResponse` (`type: \"extension_ui_response\"`):\n\n- `{ type: \"extension_ui_response\", id: string, value: string }`\n- `{ type: \"extension_ui_response\", id: string, confirmed: boolean }`\n- `{ type: \"extension_ui_response\", id: string, cancelled: true }`\n\nSe um diálogo possui um timeout, o modo RPC resolve para um valor padrão quando o timeout/abort é disparado.\n\n## Sub-Protocolo de Ferramentas do Host\n\nHosts RPC podem expor ferramentas personalizadas ao agente enviando `set_host_tools`, e então servindo requisições de execução pelo mesmo transporte.\n\n### Requisição de saída\n\nQuando o agente quer que o host execute uma dessas ferramentas, o modo RPC emite:\n\n```json\n{\n  \"type\": \"host_tool_call\",\n  \"id\": \"host_1\",\n  \"toolCallId\": \"toolu_123\",\n  \"toolName\": \"echo_host\",\n  \"arguments\": { \"message\": \"hello\" }\n}\n```\n\nSe a execução da ferramenta for abortada posteriormente, o modo RPC emite:\n\n```json\n{\n  \"type\": \"host_tool_cancel\",\n  \"id\": \"host_cancel_1\",\n  \"targetId\": \"host_1\"\n}\n```\n\n### Atualizações de entrada e conclusão\n\nHosts podem opcionalmente transmitir progresso:\n\n```json\n{\n  \"type\": \"host_tool_update\",\n  \"id\": \"host_1\",\n  \"partialResult\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"working\" }]\n  }\n}\n```\n\nA conclusão usa:\n\n```json\n{\n  \"type\": \"host_tool_result\",\n  \"id\": \"host_1\",\n  \"result\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"done\" }]\n  }\n}\n```\n\nDefina `isError: true` em `host_tool_result` para apresentar o conteúdo retornado como um erro de ferramenta.\n\n## Modelo de Erros e Recuperabilidade\n\n### Falhas em nível de comando\n\nFalhas são `success: false` com `error` em string.\n\n```json\n{ \"id\": \"req_2\", \"type\": \"response\", \"command\": \"set_model\", \"success\": false, \"error\": \"Model not found: provider/model\" }\n```\n\n### Expectativas de recuperabilidade\n\n- A maioria das falhas de comando são recuperáveis; o processo permanece ativo.\n- JSONL malformado / exceções no loop de parse emitem uma resposta de erro `parse` e continuam lendo as linhas subsequentes.\n- `set_session_name` vazio é rejeitado (`Session name cannot be empty`).\n- Respostas de UI de extensão com `id` desconhecido são ignoradas.\n- Condições de encerramento do processo são o fechamento do stdin ou shutdown explícito disparado por extensão.\n\n## Fluxos de Comandos Compactos\n\n### 1) Prompt e streaming\n\nstdin:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"prompt\", \"message\": \"Summarize this repo\" }\n```\n\nSequência stdout (típica):\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n{ \"type\": \"agent_start\" }\n{ \"type\": \"message_update\", \"assistantMessageEvent\": { \"type\": \"text_delta\", \"delta\": \"...\" }, \"message\": { \"role\": \"assistant\", \"content\": [] } }\n{ \"type\": \"agent_end\", \"messages\": [] }\n```\n\n### 2) Prompt durante streaming com política de fila explícita\n\nstdin:\n\n```json\n{ \"id\": \"req_2\", \"type\": \"prompt\", \"message\": \"Also include risks\", \"streamingBehavior\": \"followUp\" }\n```\n\n### 3) Inspecionar e ajustar comportamento de fila\n\nstdin:\n\n```json\n{ \"id\": \"q1\", \"type\": \"get_state\" }\n{ \"id\": \"q2\", \"type\": \"set_steering_mode\", \"mode\": \"all\" }\n{ \"id\": \"q3\", \"type\": \"set_interrupt_mode\", \"mode\": \"wait\" }\n```\n\n### 4) Ida e volta de UI de extensão\n\nstdout:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"ui_7\", \"method\": \"input\", \"title\": \"Branch name\", \"placeholder\": \"feature/...\" }\n```\n\nstdin:\n\n```json\n{ \"type\": \"extension_ui_response\", \"id\": \"ui_7\", \"value\": \"feature/rpc-host\" }\n```\n\n## Notas sobre o helper `RpcClient`\n\n`src/modes/rpc/rpc-client.ts` é um wrapper de conveniência, não a definição do protocolo.\n\nCaracterísticas atuais do helper:\n\n- Inicia `bun <cliPath> --mode rpc`\n- Correlaciona respostas por ids gerados `req_<n>`\n- Despacha apenas tipos `AgentEvent` reconhecidos para os listeners\n- Suporta ferramentas personalizadas pertencentes ao host via `setCustomTools()` e tratamento automático de `host_tool_call` / `host_tool_cancel`\n- **Não** expõe métodos helper para todos os comandos do protocolo (por exemplo, `set_interrupt_mode` e `set_session_name` estão nos tipos do protocolo mas não são encapsulados como métodos dedicados)\n\nUse quadros de protocolo brutos se precisar de cobertura completa da superfície.\n",
	"pt-br/configuration/sdk.md": "---\ntitle: SDK\ndescription: >-\n  SDK para construção de agentes personalizados e integrações sobre o runtime do\n  agente de codificação xcsh.\nsidebar:\n  order: 6\n  label: SDK\ni18n:\n  sourceHash: 80f3a4374241\n  translator: machine\n---\n\n# SDK\n\nO SDK é a superfície de integração em processo para `@f5-sales-demo/xcsh`.\nUse-o quando desejar acesso direto ao estado do agente, streaming de eventos, configuração de ferramentas e controle de sessão a partir do seu próprio processo Bun/Node.\n\nSe você precisar de isolamento entre linguagens/processos, utilize o modo RPC.\n\n## Instalação\n\n```bash\nbun add @f5-sales-demo/xcsh\n```\n\n## Pontos de entrada\n\n`@f5-sales-demo/xcsh` exporta as APIs do SDK a partir da raiz do pacote (e também via `@f5-sales-demo/xcsh/sdk`).\n\nExportações principais para integradores:\n\n- `createAgentSession`\n- `SessionManager`\n- `Settings`\n- `AuthStorage`\n- `ModelRegistry`\n- `discoverAuthStorage`\n- Auxiliares de descoberta (`discoverExtensions`, `discoverSkills`, `discoverContextFiles`, `discoverPromptTemplates`, `discoverSlashCommands`, `discoverCustomTSCommands`, `discoverMCPServers`)\n- Superfície de fábrica de ferramentas (`createTools`, `BUILTIN_TOOLS`, classes de ferramentas)\n\n## Início rápido (padrões de autodescoberta)\n\n```ts\nimport { createAgentSession } from \"@f5-sales-demo/xcsh\";\n\nconst { session, modelFallbackMessage } = await createAgentSession();\n\nif (modelFallbackMessage) {\n process.stderr.write(`${modelFallbackMessage}\\n`);\n}\n\nconst unsubscribe = session.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Summarize this repository in 3 bullets.\");\nunsubscribe();\nawait session.dispose();\n```\n\n## O que `createAgentSession()` descobre por padrão\n\n`createAgentSession()` segue o princípio \"forneça para substituir, omita para descobrir\".\n\nSe omitidos, os seguintes valores são resolvidos:\n\n- `cwd`: `getProjectDir()`\n- `agentDir`: `~/.xcsh/agent` (via `getAgentDir()`)\n- `authStorage`: `discoverAuthStorage(agentDir)`\n- `modelRegistry`: `new ModelRegistry(authStorage)` + `await refresh()`\n- `settings`: `await Settings.init({ cwd, agentDir })`\n- `sessionManager`: `SessionManager.create(cwd)` (com persistência em arquivo)\n- skills/arquivos de contexto/templates de prompt/comandos slash/extensões/comandos TS personalizados\n- ferramentas nativas via `createTools(...)`\n- ferramentas MCP (habilitadas por padrão)\n- integração LSP (habilitada por padrão)\n\n### Entradas obrigatórias vs. opcionais\n\nNormalmente, você precisa fornecer apenas o que deseja controlar:\n\n- **Deve fornecer**: nada para uma sessão mínima\n- **Geralmente fornecido explicitamente** em integradores:\n    - `sessionManager` (se você precisar de memória volátil ou localização personalizada)\n    - `authStorage` + `modelRegistry` (se você gerencia o ciclo de vida de credenciais/modelos)\n    - `model` ou `modelPattern` (se a seleção determinística de modelo for importante)\n    - `settings` (se você precisar de configuração isolada/de teste)\n\n## Comportamento do gerenciador de sessão (persistente vs. em memória)\n\n`AgentSession` sempre utiliza um `SessionManager`; o comportamento depende de qual fábrica você usa.\n\n### Com persistência em arquivo (padrão)\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.create(process.cwd()),\n});\n\nconsole.log(session.sessionFile); // caminho absoluto .jsonl\n```\n\n- Persiste conversas/mensagens/deltas de estado em arquivos de sessão.\n- Suporta fluxos de retomada/abertura/listagem/ramificação.\n- `session.sessionFile` está definido.\n\n### Em memória\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.inMemory(),\n});\n\nconsole.log(session.sessionFile); // undefined\n```\n\n- Sem persistência no sistema de arquivos.\n- Útil para testes, workers efêmeros e agentes com escopo de requisição.\n- Os métodos de sessão ainda funcionam, mas comportamentos específicos de persistência (retomada/ramificação por arquivo) são naturalmente limitados.\n\n### Auxiliares de retomada/abertura/listagem\n\n```ts\nimport { SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst recent = await SessionManager.continueRecent(process.cwd());\nconst listed = await SessionManager.list(process.cwd());\nconst opened = listed[0] ? await SessionManager.open(listed[0].path) : null;\n```\n\n## Configuração de modelo e autenticação\n\n`createAgentSession()` utiliza `ModelRegistry` + `AuthStorage` para seleção de modelo e resolução de chaves de API.\n\n### Configuração explícita\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst available = modelRegistry.getAvailable();\nif (available.length === 0) throw new Error(\"No authenticated models available\");\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n model: available[0],\n thinkingLevel: \"medium\",\n sessionManager: SessionManager.inMemory(),\n});\n```\n\n### Ordem de seleção quando `model` é omitido\n\nQuando nenhum `model`/`modelPattern` explícito é fornecido:\n\n1. restaura o modelo da sessão existente (se restaurável + chave disponível)\n2. papel de modelo padrão nas configurações (`default`)\n3. primeiro modelo disponível com autenticação válida\n\nSe a restauração falhar, `modelFallbackMessage` explica o fallback utilizado.\n\n### Prioridade de autenticação\n\n`AuthStorage.getApiKey(...)` resolve na seguinte ordem:\n\n1. substituição em tempo de execução (`setRuntimeApiKey`)\n2. credenciais armazenadas no `agent.db`\n3. variáveis de ambiente do provedor\n4. fallback do resolvedor de provedor personalizado (se configurado)\n\n## Modelo de assinatura de eventos\n\nInscreva-se com `session.subscribe(listener)`; o método retorna uma função para cancelar a assinatura.\n\n```ts\nconst unsubscribe = session.subscribe(event => {\n switch (event.type) {\n  case \"agent_start\":\n  case \"turn_start\":\n  case \"tool_execution_start\":\n   break;\n  case \"message_update\":\n   if (event.assistantMessageEvent.type === \"text_delta\") {\n    process.stdout.write(event.assistantMessageEvent.delta);\n   }\n   break;\n }\n});\n```\n\n`AgentSessionEvent` inclui o `AgentEvent` principal além de eventos no nível de sessão:\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n## Ciclo de vida do prompt\n\n`session.prompt(text, options?)` é o ponto de entrada principal.\n\nComportamento:\n\n1. expansão opcional de comandos/templates (comandos `/`, comandos personalizados, comandos slash de arquivo, templates de prompt)\n2. se estiver fazendo streaming no momento:\n    - requer `streamingBehavior: \"steer\" | \"followUp\"`\n    - enfileira em vez de descartar o trabalho\n3. se estiver ocioso:\n    - valida o modelo + chave de API\n    - acrescenta a mensagem do usuário\n    - inicia o turno do agente\n\nAPIs relacionadas:\n\n- `sendUserMessage(content, { deliverAs? })`\n- `steer(text, images?)`\n- `followUp(text, images?)`\n- `sendCustomMessage({ customType, content, ... }, { deliverAs?, triggerTurn? })`\n- `abort()`\n\n## Ferramentas e integração de extensões\n\n### Ferramentas nativas e filtragem\n\n- As ferramentas nativas vêm de `createTools(...)` e `BUILTIN_TOOLS`.\n- `toolNames` funciona como uma lista de permissões para ferramentas nativas.\n- Ferramentas `customTools` e ferramentas registradas por extensões ainda são incluídas.\n- Ferramentas ocultas (por exemplo, `submit_result`) requerem opt-in, exceto quando exigidas pelas opções.\n\n```ts\nconst { session } = await createAgentSession({\n toolNames: [\"read\", \"grep\", \"find\", \"write\"],\n requireSubmitResultTool: true,\n});\n```\n\n### Extensões\n\n- `extensions`: `ExtensionFactory[]` inline\n- `additionalExtensionPaths`: carrega arquivos de extensão adicionais\n- `disableExtensionDiscovery`: desabilita a varredura automática de extensões\n- `preloadedExtensions`: reutiliza um conjunto de extensões já carregado\n\n### Alterações no conjunto de ferramentas em tempo de execução\n\n`AgentSession` suporta atualizações de ativação em tempo de execução:\n\n- `getActiveToolNames()`\n- `getAllToolNames()`\n- `setActiveToolsByName(names)`\n- `refreshMCPTools(mcpTools)`\n\nO prompt do sistema é reconstruído para refletir as alterações nas ferramentas ativas.\n\n## Auxiliares de descoberta\n\nUse-os quando quiser controle parcial sem recriar a lógica interna de descoberta:\n\n- `discoverAuthStorage(agentDir?)`\n- `discoverExtensions(cwd?)`\n- `discoverSkills(cwd?, _agentDir?, settings?)`\n- `discoverContextFiles(cwd?, _agentDir?)`\n- `discoverPromptTemplates(cwd?, agentDir?)`\n- `discoverSlashCommands(cwd?)`\n- `discoverCustomTSCommands(cwd?, agentDir?)`\n- `discoverMCPServers(cwd?)`\n- `buildSystemPrompt(options?)`\n\n## Opções orientadas a subagentes\n\nPara consumidores do SDK que constroem orquestradores (semelhante ao fluxo de executor de tarefas):\n\n- `outputSchema`: passa a expectativa de saída estruturada para o contexto da ferramenta\n- `requireSubmitResultTool`: força a inclusão da ferramenta `submit_result`\n- `taskDepth`: contexto de profundidade de recursão para sessões de tarefas aninhadas\n- `parentTaskPrefix`: prefixo de nomenclatura de artefatos para saídas de tarefas aninhadas\n\nEsses parâmetros são opcionais para incorporação normal de agente único.\n\n## Valor de retorno de `createAgentSession()`\n\n```ts\ntype CreateAgentSessionResult = {\n session: AgentSession;\n extensionsResult: LoadExtensionsResult;\n setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;\n mcpManager?: MCPManager;\n modelFallbackMessage?: string;\n lspServers?: Array<{ name: string; status: \"ready\" | \"error\"; fileTypes: string[]; error?: string }>;\n};\n```\n\nUse `setToolUIContext(...)` somente se o seu integrador fornecer capacidades de UI que ferramentas/extensões devam utilizar.\n\n## Exemplo mínimo de integração controlada\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n Settings,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst settings = Settings.isolated({\n \"compaction.enabled\": true,\n \"retry.enabled\": true,\n});\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n settings,\n sessionManager: SessionManager.inMemory(),\n toolNames: [\"read\", \"grep\", \"find\", \"edit\", \"write\"],\n enableMCP: false,\n enableLsp: true,\n});\n\nsession.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Find all TODO comments in this repo and propose fixes.\");\nawait session.dispose();\n```\n",
	"pt-br/configuration/secrets.md": "---\ntitle: Ofuscação de Segredos\ndescription: >-\n  Pipeline de ofuscação de segredos que redige valores sensíveis dos logs de\n  sessão e saídas.\nsidebar:\n  order: 3\n  label: Segredos\ni18n:\n  sourceHash: 1d9dc101c614\n  translator: machine\n---\n\n# Ofuscação de Segredos\n\nPrevine que valores sensíveis (chaves de API, tokens, senhas) sejam enviados para provedores de LLM. Quando habilitado, os segredos são substituídos por placeholders determinísticos antes de saírem do processo, e restaurados nos argumentos de chamadas de ferramentas retornados pelo modelo.\n\n## Habilitando\n\nHabilitado por padrão. Alterne via a interface `/settings` ou diretamente no `config.yml`:\n\n```yaml\nsecrets:\n  enabled: false\n```\n\n## Como funciona\n\n1. Na inicialização da sessão, os segredos são coletados de duas fontes:\n   - **Variáveis de ambiente** que correspondem a padrões comuns de segredos (`*_KEY`, `*_SECRET`, `*_TOKEN`, `*_PASSWORD`, etc.) com valores >= 8 caracteres\n   - **Arquivos `secrets.yml`** (veja abaixo)\n\n2. Mensagens enviadas ao LLM têm todos os valores de segredos substituídos por placeholders como `<<$env:S0>>`, `<<$env:S1>>`, etc.\n\n3. Argumentos de chamadas de ferramentas retornados pelo modelo são percorridos em profundidade e os placeholders são restaurados para os valores originais antes da execução.\n\nDois modos controlam o que acontece com cada segredo:\n\n| Modo | Comportamento | Reversível |\n|---|---|---|\n| `obfuscate` (padrão) | Substituído por placeholder indexado `<<$env:SN>>` | Sim (desofuscado nos argumentos de ferramentas) |\n| `replace` | Substituído por string determinística de mesmo comprimento | Não (unidirecional) |\n\n## secrets.yml\n\nDefina entradas de segredos personalizados em YAML. Dois locais são verificados:\n\n| Nível | Caminho | Propósito |\n|---|---|---|\n| Global | `~/.xcsh/agent/secrets.yml` | Segredos para todos os projetos |\n| Projeto | `<cwd>/.xcsh/secrets.yml` | Segredos específicos do projeto |\n\nEntradas de projeto sobrescrevem entradas globais com `content` correspondente.\n\n### Esquema\n\nCada entrada no array possui estes campos:\n\n| Campo | Tipo | Obrigatório | Descrição |\n|---|---|---|---|\n| `type` | `\"plain\"` ou `\"regex\"` | Sim | Estratégia de correspondência |\n| `content` | string | Sim | O valor do segredo (plain) ou padrão regex (regex) |\n| `mode` | `\"obfuscate\"` ou `\"replace\"` | Não | Padrão: `\"obfuscate\"` |\n| `replacement` | string | Não | Substituição personalizada (somente modo replace) |\n| `flags` | string | Não | Flags de regex (somente tipo regex) |\n\n### Exemplos\n\n#### Segredos simples (plain)\n\n```yaml\n# Obfuscar uma chave de API específica (modo padrão)\n- type: plain\n  content: sk-proj-abc123def456\n\n# Substituir uma senha de banco de dados por uma string fixa\n- type: plain\n  content: hunter2\n  mode: replace\n  replacement: \"********\"\n```\n\n#### Segredos com regex\n\n```yaml\n# Obfuscar qualquer chave no estilo AWS\n- type: regex\n  content: \"AKIA[0-9A-Z]{16}\"\n\n# Correspondência sem distinção de maiúsculas/minúsculas com flags explícitas\n- type: regex\n  content: \"api[_-]?key\\\\s*=\\\\s*\\\\w+\"\n  flags: \"i\"\n\n# Sintaxe de literal regex (padrão e flags em uma única string)\n- type: regex\n  content: \"/bearer\\\\s+[a-zA-Z0-9._~+\\\\/=-]+/i\"\n```\n\nEntradas regex sempre fazem varredura global (a flag `g` é aplicada automaticamente). A sintaxe de literal regex `/padrão/flags` é suportada como alternativa aos campos separados `content` + `flags`. Barras escapadas dentro do padrão (`\\\\/`) são tratadas corretamente.\n\n#### Modo replace com regex\n\n```yaml\n# Substituição unidirecional de strings de conexão (não reversível)\n- type: regex\n  content: \"postgres://[^\\\\s]+\"\n  mode: replace\n  replacement: \"postgres://***\"\n```\n\n## Interação com detecção de variáveis de ambiente\n\nVariáveis de ambiente são sempre coletadas primeiro. Entradas definidas em arquivo são adicionadas depois, de modo que entradas de arquivo podem cobrir segredos que não existem em variáveis de ambiente (arquivos de configuração, valores hardcoded, etc.). Se o mesmo valor aparecer em ambos, o modo da entrada do arquivo tem precedência.\n\n## Arquivos principais\n\n- `src/secrets/index.ts` -- carregamento, mesclagem, coleta de variáveis de ambiente\n- `src/secrets/obfuscator.ts` -- classe `SecretObfuscator`, geração de placeholders, ofuscação de mensagens\n- `src/secrets/regex.ts` -- análise e compilação de literais regex\n- `src/config/settings-schema.ts` -- definição da configuração `secrets.enabled`\n",
	"pt-br/extensions/extension-loading.md": "---\ntitle: Carregamento de Extensões (Módulos TypeScript/JavaScript)\ndescription: >-\n  Pipeline de carregamento de módulos TypeScript e JavaScript para extensões com\n  resolução, validação e cache.\nsidebar:\n  order: 2\n  label: Carregamento de extensões\ni18n:\n  sourceHash: a8cea231c660\n  translator: machine\n---\n\n# Carregamento de Extensões (Módulos TypeScript/JavaScript)\n\nEste documento aborda como o agente de codificação descobre e carrega **módulos de extensão** (`.ts`/`.js`) na inicialização.\n\nEle **não** aborda extensões de manifesto `gemini-extension.json` (documentadas separadamente).\n\n## O que este subsistema faz\n\nO carregamento de extensões constrói uma lista de arquivos de entrada de módulos, importa cada módulo com Bun, executa sua factory e retorna:\n\n- definições de extensão carregadas\n- erros de carregamento por caminho (sem abortar todo o carregamento)\n- um objeto compartilhado de runtime de extensão usado posteriormente pelo `ExtensionRunner`\n\n## Arquivos de implementação principais\n\n- `src/extensibility/extensions/loader.ts` — descoberta de caminhos + importação/execução\n- `src/extensibility/extensions/index.ts` — exportações públicas\n- `src/extensibility/extensions/runner.ts` — runtime/execução de eventos após o carregamento\n- `src/discovery/builtin.ts` — provedor nativo de autodescoberta para módulos de extensão\n- `src/config/settings.ts` — carrega configurações mescladas de `extensions` / `disabledExtensions`\n\n---\n\n## Entradas para o carregamento de extensões\n\n### 1) Módulos de extensão nativos autodescobertos\n\n`discoverAndLoadExtensions()` primeiro solicita aos provedores de descoberta itens com capacidade `extension-module`, depois mantém apenas os itens do provedor `native`.\n\nLocalizações nativas efetivas:\n\n- Projeto: `<cwd>/.xcsh/extensions`\n- Usuário: `~/.xcsh/agent/extensions`\n\nAs raízes de caminho vêm do provedor nativo (`SOURCE_PATHS.native`).\n\nNotas:\n\n- A autodescoberta nativa atualmente é baseada em `.xcsh`.\n- O legado `.pi` ainda é aceito nas chaves de manifesto do `package.json` (`pi.extensions`), mas não como raiz nativa aqui.\n\n### 2) Caminhos explicitamente configurados\n\nApós a autodescoberta, os caminhos configurados são adicionados e resolvidos.\n\nFontes de caminhos configurados no caminho de inicialização da sessão principal (`sdk.ts`):\n\n1. Caminhos fornecidos via CLI (`--extension/-e`, e `--hook` também é tratado como um caminho de extensão)\n2. Array `extensions` nas configurações (configurações globais + projeto mescladas)\n\nArquivo de configurações globais:\n\n- `~/.xcsh/agent/config.yml` (ou diretório de agente personalizado via `PI_CODING_AGENT_DIR`)\n\nArquivo de configurações do projeto:\n\n- `<cwd>/.xcsh/settings.json`\n\nExemplos:\n\n```yaml\n# ~/.xcsh/agent/config.yml\nextensions:\n  - ~/my-exts/safety.ts\n  - ./local/ext-pack\n```\n\n```json\n{\n  \"extensions\": [\"./.xcsh/extensions/my-extra\"]\n}\n```\n\n---\n\n## Controles de ativação/desativação\n\n### Desativar descoberta\n\n- CLI: `--no-extensions`\n- Opção do SDK: `disableExtensionDiscovery`\n\nDivisão de comportamento:\n\n- SDK: quando `disableExtensionDiscovery=true`, ainda carrega `additionalExtensionPaths` via `loadExtensions()`.\n- A construção de caminhos do CLI (`main.ts`) atualmente limpa os caminhos de extensão do CLI quando `--no-extensions` está definido, então `-e/--hook` explícitos não são encaminhados nesse modo.\n\n### Desativar módulos de extensão específicos\n\nA configuração `disabledExtensions` filtra pelo formato de id da extensão:\n\n- `extension-module:<derivedName>`\n\n`derivedName` é baseado no caminho de entrada (`getExtensionNameFromPath`), por exemplo:\n\n- `/x/foo.ts` -> `foo`\n- `/x/bar/index.ts` -> `bar`\n\nExemplo:\n\n```yaml\ndisabledExtensions:\n  - extension-module:foo\n```\n\n---\n\n## Resolução de caminhos e entradas\n\n### Normalização de caminhos\n\nPara caminhos configurados:\n\n1. Normalizar espaços unicode\n2. Expandir `~`\n3. Se relativo, resolver em relação ao `cwd` atual\n\n### Se o caminho configurado é um arquivo\n\nEle é usado diretamente como candidato a entrada de módulo.\n\n### Se o caminho configurado é um diretório\n\nOrdem de resolução:\n\n1. `package.json` nesse diretório com `xcsh.extensions` (ou legado `pi.extensions`) -> usar entradas declaradas\n2. `index.ts`\n3. `index.js`\n4. Caso contrário, varrer um nível em busca de entradas de extensão:\n   - `*.ts` / `*.js` diretos\n   - subdiretório `index.ts` / `index.js`\n   - subdiretório `package.json` com `xcsh.extensions` / `pi.extensions`\n\nRegras e restrições:\n\n- sem descoberta recursiva além de um nível de subdiretório\n- entradas de manifesto `extensions` declaradas são resolvidas em relação ao diretório do pacote\n- entradas declaradas são incluídas apenas se o arquivo existir/acesso for permitido\n- em pares `*/index.{ts,js}`, TypeScript é preferido em relação a JavaScript\n- links simbólicos são tratados como arquivos/diretórios elegíveis\n\n### O comportamento de ignorar difere por fonte\n\n- A autodescoberta nativa (`discoverExtensionModulePaths` nos helpers de descoberta) usa glob nativo com `gitignore: true` e `hidden: false`.\n- A varredura explícita de diretórios configurados em `loader.ts` usa regras de `readdir` e **não** aplica filtragem de gitignore.\n\n---\n\n## Ordem de carregamento e precedência\n\n`discoverAndLoadExtensions()` constrói uma lista ordenada e então chama `loadExtensions()`.\n\nOrdem:\n\n1. Módulos nativos autodescobertos\n2. Caminhos explicitamente configurados (na ordem fornecida)\n\nEm `sdk.ts`, a ordem configurada é:\n\n1. Caminhos adicionais do CLI\n2. `extensions` das configurações\n\nDeduplicação:\n\n- baseada em caminho absoluto\n- o primeiro caminho encontrado prevalece\n- duplicatas posteriores são ignoradas\n\nImplicação: se o mesmo caminho de módulo for autodescoberto e explicitamente configurado, ele é carregado uma vez na primeira posição (estágio de autodescoberta).\n\n---\n\n## Importação de módulo e contrato da factory\n\nCada caminho candidato é carregado com importação dinâmica:\n\n- `await import(resolvedPath)`\n- a factory é `module.default ?? module`\n- a factory deve ser uma função (`ExtensionFactory`)\n\nSe a exportação não for uma função, esse caminho falha com um erro estruturado e o carregamento continua.\n\n---\n\n## Tratamento de falhas e isolamento\n\n### Durante o carregamento\n\nPor caminho de extensão, falhas são capturadas como `{ path, error }` e não impedem o carregamento de outros caminhos.\n\nCasos comuns:\n\n- falha na importação / arquivo ausente\n- exportação de factory inválida (não é função)\n- exceção lançada durante a execução da factory\n\n### Modelo de isolamento em runtime\n\n- As extensões **não são sandboxed** (mesmo processo/runtime).\n- Elas compartilham um `EventBus` e uma instância de `ExtensionRuntime`.\n- Durante o carregamento, os métodos de ação do runtime intencionalmente lançam `ExtensionRuntimeNotInitializedError`; a vinculação de ações acontece posteriormente em `ExtensionRunner.initialize()`.\n\n### Após o carregamento\n\nQuando eventos são executados através do `ExtensionRunner`, exceções nos handlers são capturadas e emitidas como erros de extensão em vez de causar crash no loop do runner.\n\n---\n\n## Exemplos mínimos de layout de usuário/projeto\n\n### Nível de usuário\n\n```text\n~/.xcsh/agent/\n  config.yml\n  extensions/\n    guardrails.ts\n    audit/\n      index.ts\n```\n\n### Nível de projeto\n\n```text\n<repo>/\n  .xcsh/\n    settings.json\n    extensions/\n      checks/\n        package.json\n      lint-gates.ts\n```\n\n`checks/package.json`:\n\n```json\n{\n  \"xcsh\": {\n    \"extensions\": [\"./src/check-a.ts\", \"./src/check-b.js\"]\n  }\n}\n```\n\nChave de manifesto legada ainda aceita:\n\n```json\n{\n  \"pi\": {\n    \"extensions\": [\"./index.ts\"]\n  }\n}\n```\n",
	"pt-br/extensions/extensions.md": "---\ntitle: Extensões\ndescription: >-\n  Visão geral do runtime de extensões cobrindo tipos, ciclo de vida do runner,\n  registro e descoberta.\nsidebar:\n  order: 1\n  label: Visão Geral\ni18n:\n  sourceHash: 14cc16dbd98b\n  translator: machine\n---\n\n# Extensões\n\nGuia principal para criação de extensões de runtime em `packages/coding-agent`.\n\nEste documento abrange o runtime de extensões atual em:\n\n- `src/extensibility/extensions/types.ts`\n- `src/extensibility/extensions/runner.ts`\n- `src/extensibility/extensions/wrapper.ts`\n- `src/extensibility/extensions/index.ts`\n- `src/modes/controllers/extension-ui-controller.ts`\n\nPara caminhos de descoberta e regras de carregamento do sistema de arquivos, consulte `docs/extension-loading.md`.\n\n## O que é uma extensão\n\nUma extensão é um módulo TS/JS que exporta uma factory padrão:\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nexport default function myExtension(pi: ExtensionAPI) {\n // registrar handlers/tools/commands/renderers\n}\n```\n\nAs extensões podem combinar todos os seguintes em um único módulo:\n\n- manipuladores de eventos (`pi.on(...)`)\n- ferramentas invocáveis pelo LLM (`pi.registerTool(...)`)\n- comandos de barra (`pi.registerCommand(...)`)\n- atalhos de teclado e flags\n- renderização de mensagens personalizada\n- APIs de injeção de sessão/mensagem (`sendMessage`, `sendUserMessage`, `appendEntry`)\n\n## Modelo de runtime\n\n1. As extensões são importadas e suas funções factory são executadas.\n2. Durante essa fase de carregamento, os métodos de registro são válidos; os métodos de ação de runtime ainda não estão inicializados.\n3. `ExtensionRunner.initialize(...)` conecta ações/contextos ativos para o modo ativo.\n4. Eventos de ciclo de vida de sessão/agente/ferramenta são emitidos para os manipuladores.\n5. Toda execução de ferramenta é encapsulada com interceptação de extensão (`tool_call` / `tool_result`).\n\n```text\nCiclo de vida da extensão (simplificado)\n\ncaminhos de carregamento\n   │\n   ▼\nimportar módulo + executar factory (somente registro)\n   │\n   ▼\nExtensionRunner.initialize(modo/sessão/registro de ferramentas)\n   │\n   ├─ emitir eventos de sessão/agente para manipuladores\n   ├─ encapsular execução de ferramenta (tool_call/tool_result)\n   └─ expor ações de runtime (sendMessage, setActiveTools, ...)\n```\n\nRestrição importante de `loader.ts`:\n\n- chamar métodos de ação como `pi.sendMessage()` durante o carregamento da extensão lança `ExtensionRuntimeNotInitializedError`\n- registre primeiro; execute comportamentos de runtime a partir de eventos/comandos/ferramentas\n\n## Início rápido\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\nimport { Type } from \"@sinclair/typebox\";\n\nexport default function (pi: ExtensionAPI) {\n pi.setLabel(\"Safety + Utilities\");\n\n pi.on(\"session_start\", async (_event, ctx) => {\n  ctx.ui.notify(`Extension loaded in ${ctx.cwd}`, \"info\");\n });\n\n pi.on(\"tool_call\", async (event) => {\n  if (event.toolName === \"bash\" && event.input.command?.includes(\"rm -rf\")) {\n   return { block: true, reason: \"Blocked by extension policy\" };\n  }\n });\n\n pi.registerTool({\n  name: \"hello_extension\",\n  label: \"Hello Extension\",\n  description: \"Return a greeting\",\n  parameters: Type.Object({ name: Type.String() }),\n  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {\n   return {\n    content: [{ type: \"text\", text: `Hello, ${params.name}` }],\n    details: { greeted: params.name },\n   };\n  },\n });\n\n pi.registerCommand(\"hello-ext\", {\n  description: \"Show queue state\",\n  handler: async (_args, ctx) => {\n   ctx.ui.notify(`pending=${ctx.hasPendingMessages()}`, \"info\");\n  },\n });\n}\n```\n\n## Superfícies da API de extensão\n\n## 1) Registro e ações (`ExtensionAPI`)\n\nMétodos principais:\n\n- `on(event, handler)`\n- `registerTool`, `registerCommand`, `registerShortcut`, `registerFlag`\n- `registerMessageRenderer`\n- `sendMessage`, `sendUserMessage`, `appendEntry`\n- `getActiveTools`, `getAllTools`, `setActiveTools`\n- `getSessionName`, `setSessionName`\n- `setModel`, `getThinkingLevel`, `setThinkingLevel`\n- `registerProvider`\n- `events` (barramento de eventos compartilhado)\n\nNo modo interativo, os manipuladores de `input` são executados antes da verificação automática de título da primeira mensagem integrada. Extensões que chamam `await pi.setSessionName(...)` a partir de `input` podem definir o nome de sessão persistido e impedir que o título gerado automaticamente padrão seja executado para aquela sessão.\n\nTambém expostos:\n\n- `pi.logger`\n- `pi.typebox`\n- `pi.pi` (exportações do pacote)\n\n### Semântica de entrega de mensagens\n\n`pi.sendMessage(message, options)` suporta:\n\n- `deliverAs: \"steer\"` (padrão) — interrompe a execução atual\n- `deliverAs: \"followUp\"` — enfileirado para executar após a execução atual\n- `deliverAs: \"nextTurn\"` — armazenado e injetado no próximo prompt do usuário\n- `triggerTurn: true` — inicia um turno quando ocioso (`nextTurn` ignora isso)\n\n`pi.sendUserMessage(content, { deliverAs })` sempre passa pelo fluxo de prompt; durante o streaming, enfileira como steer/follow-up.\n\n## 2) Contexto do manipulador (`ExtensionContext`)\n\nManipuladores e o `execute` de ferramentas recebem `ctx` com:\n\n- `ui`\n- `hasUI`\n- `cwd`\n- `sessionManager` (somente leitura)\n- `modelRegistry`, `model`\n- `getContextUsage()`\n- `compact(...)`\n- `isIdle()`, `hasPendingMessages()`, `abort()`\n- `shutdown()`\n- `getSystemPrompt()`\n\n## 3) Contexto de comando (`ExtensionCommandContext`)\n\nManipuladores de comandos também recebem:\n\n- `waitForIdle()`\n- `newSession(...)`\n- `switchSession(...)`\n- `branch(entryId)`\n- `navigateTree(targetId, { summarize })`\n- `reload()`\n\nUse o contexto de comando para fluxos de controle de sessão; esses métodos são intencionalmente separados dos manipuladores de eventos gerais.\n\n## Superfície de eventos (nomes e comportamentos atuais)\n\nAs uniões de eventos canônicos e os tipos de payload estão em `types.ts`.\n\n### Ciclo de vida da sessão\n\n- `session_start`\n- `session_before_switch` / `session_switch`\n- `session_before_branch` / `session_branch`\n- `session_before_compact` / `session.compacting` / `session_compact`\n- `session_before_tree` / `session_tree`\n- `session_shutdown`\n\nPré-eventos canceláveis:\n\n- `session_before_switch` → `{ cancel?: boolean }`\n- `session_before_branch` → `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_before_compact` → `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session_before_tree` → `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n\n### Ciclo de vida do prompt e do turno\n\n- `input`\n- `before_agent_start`\n- `context`\n- `agent_start` / `agent_end`\n- `turn_start` / `turn_end`\n- `message_start` / `message_update` / `message_end`\n\n### Ciclo de vida da ferramenta\n\n- `tool_call` (pré-execução, pode bloquear)\n- `tool_result` (pós-execução, pode corrigir content/details/isError)\n- `tool_execution_start` / `tool_execution_update` / `tool_execution_end` (observabilidade)\n\n`tool_result` é estilo middleware: os manipuladores são executados na ordem das extensões e cada um vê as modificações anteriores.\n\n### Sinais de confiabilidade/runtime\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### Interceptação de comandos do usuário\n\n- `user_bash` (substituir com `{ result }`)\n- `user_python` (substituir com `{ result }`)\n\n### `resources_discover`\n\n`resources_discover` existe nos tipos de extensão e em `ExtensionRunner`.\nNota de runtime atual: `ExtensionRunner.emitResourcesDiscover(...)` está implementado, mas não há callsites de `AgentSession` invocando-o na base de código atual.\n\n## Detalhes de criação de ferramentas\n\n`registerTool` usa `ToolDefinition` de `types.ts`.\n\nAssinatura atual de `execute`:\n\n```ts\nexecute(\n toolCallId,\n params,\n signal,\n onUpdate,\n ctx,\n): Promise<AgentToolResult>\n```\n\nTemplate:\n\n```ts\npi.registerTool({\n name: \"my_tool\",\n label: \"My Tool\",\n description: \"...\",\n parameters: Type.Object({}),\n async execute(_id, _params, signal, onUpdate, ctx) {\n  if (signal?.aborted) {\n   return { content: [{ type: \"text\", text: \"Cancelled\" }] };\n  }\n  onUpdate?.({ content: [{ type: \"text\", text: \"Working...\" }] });\n  return { content: [{ type: \"text\", text: \"Done\" }], details: {} };\n },\n onSession(event, ctx) {\n  // reason: start|switch|branch|tree|shutdown\n },\n renderCall(args, theme) {\n  // renderização TUI opcional\n },\n renderResult(result, options, theme, args) {\n  // renderização TUI opcional\n },\n});\n```\n\n`tool_call`/`tool_result` interceptam todas as ferramentas assim que o registro é encapsulado em `sdk.ts`, incluindo ferramentas integradas e ferramentas de extensão/personalizadas.\n\n## Pontos de integração de UI\n\n`ctx.ui` implementa a interface `ExtensionUIContext`. O suporte difere por modo.\n\n### Modo interativo (`extension-ui-controller.ts`)\n\nSuportado:\n\n- diálogos: `select`, `confirm`, `input`, `editor`\n- notificações/status/texto do editor/entrada do terminal/overlays personalizados\n- listagem/carregamento de temas por nome (`setTheme` suporta nomes de string)\n- alternância de expansão de ferramentas\n\nMétodos no-op atuais neste controlador:\n\n- `setFooter`\n- `setHeader`\n- `setEditorComponent`\n\nObservação: `setWidget` atualmente é roteado para texto da linha de status via `setHookWidget(...)`.\n\n### Modo RPC (`rpc-mode.ts`)\n\n`ctx.ui` é suportado por eventos RPC `extension_ui_request`:\n\n- métodos de diálogo (`select`, `confirm`, `input`, `editor`) fazem round-trip para respostas do cliente\n- métodos fire-and-forget emitem requisições (`notify`, `setStatus`, `setWidget` para arrays de string, `setTitle`, `setEditorText`)\n\nNão suportado/no-op na implementação RPC:\n\n- `onTerminalInput`\n- `custom`\n- `setFooter`, `setHeader`, `setEditorComponent`\n- `setWorkingMessage`\n- troca/carregamento de temas (`setTheme` retorna falha)\n- controles de expansão de ferramentas são inertes\n\n### Caminhos print/headless/subagente\n\nQuando nenhum contexto de UI é fornecido à inicialização do runner, `ctx.hasUI` é `false` e os métodos são no-op/retornam valores padrão.\n\n### Modo interativo em segundo plano\n\nO modo em segundo plano instala um objeto de contexto de UI não interativo. Na implementação atual, `ctx.hasUI` ainda pode ser `true` enquanto diálogos interativos retornam comportamento padrão/no-op.\n\n## Padrões de sessão e estado\n\nPara estado de extensão durável:\n\n1. Persistir com `pi.appendEntry(customType, data)`.\n2. Reconstruir estado a partir de `ctx.sessionManager.getBranch()` em `session_start`, `session_branch`, `session_tree`.\n3. Manter o `details` do resultado da ferramenta estruturado quando o estado deve ser visível/reconstruível a partir do histórico de resultados de ferramentas.\n\nExemplo de padrão de reconstrução:\n\n```ts\npi.on(\"session_start\", async (_event, ctx) => {\n let latest;\n for (const entry of ctx.sessionManager.getBranch()) {\n  if (entry.type === \"custom\" && entry.customType === \"my-state\") {\n   latest = entry.data;\n  }\n }\n // restaurar a partir do latest\n});\n```\n\n## Pontos de extensão de renderização\n\n## Renderizador de mensagens personalizado\n\n```ts\npi.registerMessageRenderer(\"my-type\", (message, { expanded }, theme) => {\n // retornar Componente pi-tui\n});\n```\n\nUsado pela renderização interativa quando mensagens personalizadas são exibidas.\n\n## Renderizador de chamada/resultado de ferramenta\n\nForneça `renderCall` / `renderResult` nas definições de `registerTool` para visualização personalizada de ferramentas no TUI.\n\n## Restrições e armadilhas\n\n- Ações de runtime não estão disponíveis durante o carregamento da extensão.\n- Erros em `tool_call` bloqueiam a execução (fail-closed).\n- Conflitos de nome de comando com built-ins são ignorados com diagnósticos.\n- Atalhos reservados são ignorados (`ctrl+c`, `ctrl+d`, `ctrl+z`, `ctrl+k`, `ctrl+p`, `ctrl+l`, `ctrl+o`, `ctrl+t`, `ctrl+g`, `shift+tab`, `shift+ctrl+p`, `alt+enter`, `escape`, `enter`).\n- Trate `ctx.reload()` como terminal para o frame atual do manipulador de comando.\n\n## Extensões vs hooks vs ferramentas personalizadas\n\nUse a superfície correta:\n\n- **Extensões** (`src/extensibility/extensions/*`): sistema unificado (eventos + ferramentas + comandos + renderizadores + registro de provider).\n- **Hooks** (`src/extensibility/hooks/*`): API de eventos legada separada.\n- **Custom-tools** (`src/extensibility/custom-tools/*`): módulos focados em ferramentas; quando carregados junto com extensões, são adaptados e ainda passam pelos wrappers de interceptação de extensão.\n\nSe você precisa de um único pacote que gerencie política, ferramentas, UX de comando e renderização juntos, use extensões.\n",
	"pt-br/extensions/gemini-manifest-extensions.md": "---\ntitle: Extensões de Manifesto Gemini\ndescription: >-\n  Formato de extensão de manifesto Gemini para compatibilidade de skills e\n  agentes entre plataformas.\nsidebar:\n  order: 7\n  label: Manifesto Gemini\ni18n:\n  sourceHash: 7134165a5f6d\n  translator: machine\n---\n\n# Extensões de Manifesto Gemini (`gemini-extension.json`)\n\nEste documento descreve como o agente de codificação descobre e analisa extensões de manifesto no estilo Gemini (`gemini-extension.json`) na capacidade `extensions`.\n\nEle **não** cobre o carregamento de módulos de extensão TypeScript/JavaScript (`extensions/*.ts`, `index.ts`, `package.json xcsh.extensions`), que está documentado em `extension-loading.md`.\n\n## Arquivos de implementação\n\n- [`../src/discovery/gemini.ts`](../../packages/coding-agent/src/discovery/gemini.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/capability/extension.ts`](../../packages/coding-agent/src/capability/extension.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/loader.ts`](../../packages/coding-agent/src/extensibility/extensions/loader.ts)\n\n---\n\n## O que é descoberto\n\nO provedor Gemini (`id: gemini`, prioridade `60`) registra um carregador de `extensions` que varre duas raízes fixas:\n\n- Usuário: `~/.gemini/extensions`\n- Projeto: `<cwd>/.gemini/extensions`\n\nA resolução de caminho é feita diretamente a partir de `ctx.home` e `ctx.cwd` via `getUserPath()` / `getProjectPath()`.\n\nRegra importante de escopo: a busca no projeto é **somente no cwd**. Ela não percorre diretórios pai.\n\n---\n\n## Regras de varredura de diretório\n\nPara cada raiz (`~/.gemini/extensions` e `<cwd>/.gemini/extensions`), a descoberta realiza:\n\n1. `readDirEntries(root)`\n2. mantém apenas subdiretórios diretos (`entry.isDirectory()`)\n3. para cada filho `<name>`, tenta ler exatamente:\n   - `<root>/<name>/gemini-extension.json`\n\nNão há varredura recursiva além de um nível de diretório.\n\n### Diretórios ocultos\n\nA descoberta de manifesto Gemini **não** filtra nomes de diretórios com prefixo de ponto. Se um subdiretório oculto existir e contiver `gemini-extension.json`, ele será considerado.\n\n### Arquivos ausentes/ilegíveis\n\nSe `gemini-extension.json` estiver ausente ou ilegível, aquele diretório será ignorado silenciosamente (sem aviso).\n\n---\n\n## Estrutura do manifesto (conforme implementado)\n\nO tipo de capacidade define esta estrutura de manifesto:\n\n```ts\ninterface ExtensionManifest {\n name?: string;\n description?: string;\n mcpServers?: Record<string, Omit<MCPServer, \"name\" | \"_source\">>;\n tools?: unknown[];\n context?: unknown;\n}\n```\n\nO comportamento no momento da descoberta é intencionalmente permissivo:\n\n- É necessário que o parse JSON seja bem-sucedido.\n- Não há validação de esquema em tempo de execução para tipos/conteúdo de campos além da sintaxe JSON.\n- O objeto analisado é armazenado como `manifest` no item de capacidade.\n\n### Normalização de nome\n\n`Extension.name` é definido como:\n\n1. `manifest.name` se não for `null`/`undefined`\n2. caso contrário, o nome do diretório da extensão\n\nNenhuma imposição de tipo string é aplicada aqui.\n\n---\n\n## Materialização em itens de capacidade\n\nUm manifesto analisado válido cria um item de capacidade `Extension`:\n\n```ts\n{\n name: manifest.name ?? <directory-name>,\n path: <extension-directory>,\n manifest: <parsed-json>,\n level: \"user\" | \"project\",\n _source: {\n  provider: \"gemini\",\n  providerName: \"Gemini CLI\" // anexado pelo registro de capacidade\n  path: <absolute-manifest-path>,\n  level: \"user\" | \"project\"\n }\n}\n```\n\nObservações:\n\n- `_source.path` é normalizado para um caminho absoluto por `createSourceMeta()`.\n- A validação de capacidade no nível do registro para `extensions` verifica apenas a presença de `name` e `path`.\n- Os elementos internos do manifesto (`mcpServers`, `tools`, `context`) não são validados durante a descoberta.\n\n---\n\n## Tratamento de erros e semântica de avisos\n\n### Com aviso\n\n- JSON inválido em um arquivo de manifesto:\n  - formato do aviso: `Invalid JSON in <manifestPath>`\n\n### Sem aviso (ignorado silenciosamente)\n\n- diretório `extensions` ausente\n- subdiretório não possui `gemini-extension.json`\n- arquivo de manifesto ilegível\n- JSON do manifesto sintaticamente válido, mas semanticamente estranho/incompleto\n\nIsso significa que validade parcial é aceita: somente falha sintática de JSON emite um aviso.\n\n---\n\n## Precedência e deduplicação com outras fontes\n\nA capacidade `extensions` é agregada entre provedores pelo registro de capacidade.\n\nProvedores atuais para esta capacidade:\n\n- `native` (`packages/coding-agent/src/discovery/builtin.ts`) prioridade `100`\n- `gemini` (`packages/coding-agent/src/discovery/gemini.ts`) prioridade `60`\n\nA chave de deduplicação é `ext.name` (`extensionCapability.key = ext => ext.name`).\n\n### Precedência entre provedores\n\nO provedor de maior prioridade vence em nomes de extensão duplicados.\n\n- Se `native` e `gemini` emitirem o nome de extensão `foo`, o item nativo é mantido.\n- O duplicado de menor prioridade é retido apenas em `result.all` com `_shadowed = true`.\n\n### Efeitos de ordem intra-provedor\n\nComo a deduplicação segue a política \"primeiro visto vence\", a ordem dos itens locais do provedor importa.\n\n- O carregador Gemini acrescenta **usuário primeiro**, depois **projeto**.\n- Portanto, nomes duplicados entre `~/.gemini/extensions` e `<cwd>/.gemini/extensions` mantêm a entrada do usuário e ocultam a entrada do projeto.\n\nEm contraste, o provedor nativo constrói a ordem de diretórios de configuração de forma diferente (`project` depois `user` em `getConfigDirs()`), portanto o sombreamento intra-provedor nativo ocorre na direção oposta.\n\n---\n\n## Resumo do comportamento usuário vs projeto\n\nPara manifestos Gemini especificamente:\n\n- Ambas as raízes de usuário e projeto são varridas em cada carregamento.\n- A raiz do projeto é fixada em `<cwd>/.gemini/extensions` (sem percurso de ancestrais).\n- Nomes duplicados dentro da fonte Gemini são resolvidos para usuário-primeiro.\n- Nomes duplicados contra provedores de maior prioridade (notadamente o nativo) perdem por prioridade.\n\n---\n\n## Limite: metadados de descoberta vs carregamento de extensão em tempo de execução\n\nA descoberta de `gemini-extension.json` atualmente alimenta metadados de capacidade (itens `Extension`). Ela **não** carrega diretamente módulos de extensão TS/JS executáveis.\n\nO carregamento de módulos em tempo de execução (`discoverAndLoadExtensions()` / `loadExtensions()`) utiliza `extension-modules` e caminhos explícitos, e atualmente filtra os módulos autodescobertos apenas para o provedor `native`.\n\nImplicação prática:\n\n- Extensões de manifesto Gemini são descobríveis como registros de capacidade.\n- Elas não são, por si só, executadas como módulos de extensão em tempo de execução pelo pipeline do carregador de extensões.\n\nEste limite é intencional na implementação atual e explica por que a descoberta de manifesto e o carregamento de módulos executáveis podem divergir.\n",
	"pt-br/extensions/marketplace.md": "---\ntitle: Sistema de Marketplace de Plugins\ndescription: >-\n  Sistema de marketplace de plugins para descobrir, instalar e gerenciar\n  coleções de plugins curadas.\nsidebar:\n  order: 4\n  label: Marketplace\ni18n:\n  sourceHash: 71d9f8f93a81\n  translator: machine\n---\n\n# Sistema de marketplace de plugins\n\nO sistema de marketplace permite descobrir, instalar e gerenciar plugins a partir de catálogos hospedados em Git. É compatível com o formato de registro de plugins do Claude Code.\n\n## Início rápido\n\n```\n/marketplace add anthropics/f5-sales-demo-marketplace\n/marketplace install wordpress.com@f5-sales-demo-marketplace\n```\n\nOu simplesmente digite `/marketplace` sem argumentos para abrir o navegador interativo de plugins.\n\n## Conceitos\n\nUm **marketplace** é um repositório Git (ou diretório local) contendo um arquivo de catálogo em `.xcsh-plugin/marketplace.json`. O catálogo lista os plugins disponíveis com suas fontes, descrições e metadados.\n\nUm **plugin** é um diretório contendo skills, comandos, hooks, servidores MCP ou servidores LSP. Os plugins são identificados por `nome@marketplace` (ex.: `code-review@f5-sales-demo-marketplace`).\n\n**Escopos**: os plugins podem ser instalados em dois escopos:\n\n- **user** (padrão) -- disponível em todos os projetos, armazenado em `~/.xcsh/plugins/installed_plugins.json`\n- **project** -- disponível apenas no projeto atual, armazenado em `.xcsh/installed_plugins.json`\n\nInstalações com escopo de projeto substituem instalações com escopo de usuário do mesmo plugin.\n\n## Comandos\n\n### Modo interativo\n\n| Comando | Efeito |\n|---|---|\n| `/marketplace` | Abrir o navegador interativo de plugins (instalar) |\n\n### Gerenciamento de marketplace\n\n| Comando | Efeito |\n|---|---|\n| `/marketplace add <source>` | Adicionar uma fonte de marketplace |\n| `/marketplace remove <name>` | Remover um marketplace |\n| `/marketplace update [name]` | Recarregar catálogo(s); omita o nome para atualizar todos |\n| `/marketplace list` | Listar marketplaces configurados |\n\n### Operações com plugins\n\n| Comando | Efeito |\n|---|---|\n| `/marketplace discover [marketplace]` | Navegar pelos plugins disponíveis |\n| `/marketplace install [--force] [--scope user\\|project] name@marketplace` | Instalar um plugin |\n| `/marketplace uninstall [--scope user\\|project] name@marketplace` | Desinstalar um plugin |\n| `/marketplace installed` | Listar plugins de marketplace instalados |\n| `/marketplace upgrade [--scope user\\|project] [name@marketplace]` | Atualizar um ou todos os plugins |\n\n### Equivalentes na linha de comando\n\nAs mesmas operações estão disponíveis na linha de comando:\n\n```\nxcsh plugin marketplace add <source>\nxcsh plugin marketplace remove <name>\nxcsh plugin marketplace update [name]\nxcsh plugin marketplace list\nxcsh plugin discover [marketplace]\nxcsh plugin install --scope project name@marketplace\n```\n\n## Fontes de marketplace\n\nAo executar `/marketplace add <source>`, o sistema classifica a fonte:\n\n| Formato da fonte | Tipo | Exemplo |\n|---|---|---|\n| `owner/repo` | Atalho do GitHub | `anthropics/f5-sales-demo-marketplace` |\n| `https://...*.json` | URL direta do catálogo | `https://example.com/marketplace.json` |\n| `https://...*.git` ou `git@...` | Repositório Git | `https://github.com/org/repo.git` |\n| `./path` ou `~/path` ou `/path` | Diretório local | `./my-marketplace` |\n\nO sistema clona o repositório (ou lê o diretório local), localiza `.xcsh-plugin/marketplace.json`, valida-o e armazena o catálogo em cache localmente.\n\n## Formato do catálogo (marketplace.json)\n\nUm catálogo de marketplace fica em `.xcsh-plugin/marketplace.json` na raiz do repositório:\n\n```json\n{\n  \"$schema\": \"https://anthropic.com/claude-code/marketplace.schema.json\",\n  \"name\": \"my-marketplace\",\n  \"owner\": {\n    \"name\": \"Your Name\",\n    \"email\": \"you@example.com\"\n  },\n  \"description\": \"A collection of plugins\",\n  \"plugins\": [\n    {\n      \"name\": \"my-plugin\",\n      \"description\": \"What this plugin does\",\n      \"source\": \"./plugins/my-plugin\",\n      \"category\": \"development\",\n      \"homepage\": \"https://github.com/you/my-plugin\"\n    }\n  ]\n}\n```\n\n### Campos obrigatórios\n\n| Campo | Descrição |\n|---|---|\n| `name` | Nome do marketplace. Alfanumérico em minúsculas, hífens e pontos. Deve começar e terminar com alfanumérico. Máximo de 64 caracteres. |\n| `owner.name` | Nome do proprietário do marketplace |\n| `plugins` | Array de entradas de plugins |\n\n### Campos de entrada de plugin\n\n| Campo | Obrigatório | Descrição |\n|---|---|---|\n| `name` | sim | Nome do plugin (mesmas regras do nome do marketplace) |\n| `source` | sim | Onde encontrar o plugin (veja abaixo) |\n| `description` | não | Descrição curta |\n| `version` | não | String de versão |\n| `author` | não | `{ name, email? }` |\n| `homepage` | não | URL |\n| `category` | não | String de categoria (ex.: `development`, `productivity`, `security`) |\n| `tags` | não | Array de tags em string |\n| `strict` | não | Booleano |\n| `commands` | não | Comandos slash fornecidos |\n| `agents` | não | Agentes fornecidos |\n| `hooks` | não | Definições de hooks |\n| `mcpServers` | não | Definições de servidores MCP |\n| `lspServers` | não | Definições de servidores LSP |\n\n### Formatos de fonte de plugin\n\nO campo `source` suporta vários formatos:\n\n**Caminho relativo** (dentro do repositório do marketplace):\n\n```json\n\"source\": \"./plugins/my-plugin\"\n```\n\n**URL de repositório Git**:\n\n```json\n\"source\": {\n  \"source\": \"url\",\n  \"url\": \"https://github.com/org/repo.git\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Atalho do GitHub**:\n\n```json\n\"source\": {\n  \"source\": \"github\",\n  \"repo\": \"org/repo\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Subdiretório Git** (monorepo):\n\n```json\n\"source\": {\n  \"source\": \"git-subdir\",\n  \"url\": \"https://github.com/org/monorepo.git\",\n  \"path\": \"plugins/my-plugin\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Pacote npm**:\n\n```json\n\"source\": {\n  \"source\": \"npm\",\n  \"package\": \"@scope/my-plugin\",\n  \"version\": \"1.0.0\"\n}\n```\n\n## Layout em disco\n\n```\n~/.xcsh/\n  config/\n    marketplaces.json          # Registro de marketplaces adicionados\n  plugins/\n    installed_plugins.json     # Plugins instalados com escopo de usuário\n    cache/\n      marketplaces/            # Catálogos de marketplace em cache\n      plugins/                 # Diretórios de plugins em cache\n\n<project>/.xcsh/\n  installed_plugins.json       # Plugins instalados com escopo de projeto\n```\n\n## Regras de nomenclatura\n\nOs nomes de marketplace e de plugin devem:\n\n- Começar e terminar com uma letra minúscula ou dígito\n- Conter apenas letras minúsculas, dígitos, hífens e pontos\n- Ter no máximo 64 caracteres\n\nOs IDs de plugin (`nome@marketplace`) devem ter no máximo 128 caracteres no total.\n\nExemplos válidos: `my-plugin`, `code-review`, `wordpress.com`, `ai-firstify`\nExemplos inválidos: `-bad`, `bad-`, `.bad`, `Bad`, `under_score`\n",
	"pt-br/extensions/plugin-manager-installer-plumbing.md": "---\ntitle: Mecanismos Internos do Gerenciador e Instalador de Plugins\ndescription: >-\n  Internos do gerenciador de plugins cobrindo instalação, validação, resolução\n  de dependências e gerenciamento de ciclo de vida.\nsidebar:\n  order: 5\n  label: Gerenciador de plugins\ni18n:\n  sourceHash: 9c33e5a2c22a\n  translator: machine\n---\n\n# Mecanismos internos do gerenciador e instalador de plugins\n\nEste documento descreve como as operações `xcsh plugin` modificam o estado dos plugins em disco e como os plugins instalados se tornam capacidades de tempo de execução (ferramentas atualmente, resolução de caminho para hooks/comandos disponível).\n\n## Escopo e arquitetura\n\nExistem duas implementações de gerenciamento de plugins na base de código:\n\n1. **Caminho ativo usado pelos comandos CLI**: `PluginManager` (`src/extensibility/plugins/manager.ts`)\n2. **Módulo auxiliar legado**: funções de instalação (`src/extensibility/plugins/installer.ts`)\n\nA execução do comando `xcsh plugin ...` passa pelo `PluginManager`.\n\nO `installer.ts` ainda documenta verificações de segurança importantes e comportamento do sistema de arquivos, mas não é o caminho utilizado por `src/commands/plugin.ts` + `src/cli/plugin-cli.ts`.\n\n## Ciclo de vida: da invocação da CLI à disponibilidade em tempo de execução\n\n```text\nxcsh plugin <action> ...\n  -> src/commands/plugin.ts\n  -> runPluginCommand(...) in src/cli/plugin-cli.ts\n  -> PluginManager method (install/list/uninstall/link/...) \n  -> mutate ~/.xcsh/plugins/{package.json,node_modules,xcsh-plugins.lock.json}\n  -> runtime discovery: discoverAndLoadCustomTools(...)\n  -> getAllPluginToolPaths(cwd)\n  -> custom tool loader imports tool modules\n```\n\n### Pontos de entrada de comando\n\n- `src/commands/plugin.ts` define o comando/flags e encaminha para `runPluginCommand`.\n- `src/cli/plugin-cli.ts` mapeia subcomandos para métodos do `PluginManager`:\n  - `install`, `uninstall`, `list`, `link`, `doctor`, `features`, `config`, `enable`, `disable`\n- Não existe uma ação `update` explícita; a atualização é feita executando novamente `install` com uma especificação de pacote/versão nova.\n\n## Modelo em disco\n\nO estado global dos plugins reside em `~/.xcsh/plugins`:\n\n- `package.json` — manifesto de dependências usado por `bun install`/`bun uninstall`\n- `node_modules/` — pacotes de plugins instalados ou links simbólicos\n- `xcsh-plugins.lock.json` — estado de tempo de execução:\n  - habilitado/desabilitado por plugin\n  - conjunto de funcionalidades selecionadas por plugin\n  - configurações persistidas do plugin\n\nSubstituições locais do projeto residem em:\n\n- `<cwd>/.xcsh/plugin-overrides.json`\n\nAs substituições são somente leitura da perspectiva do gerenciador/carregador (sem caminho de escrita aqui) e podem desabilitar plugins ou substituir funcionalidades/configurações para este projeto.\n\n## Análise de especificações de plugins e interpretação de metadados\n\n## Gramática da especificação de instalação\n\n`parsePluginSpec` (`parser.ts`) suporta:\n\n- `pkg` -> `features: null` (comportamento padrão)\n- `pkg[*]` -> habilitar todas as funcionalidades do manifesto\n- `pkg[]` -> não habilitar funcionalidades opcionais\n- `pkg[a,b]` -> habilitar funcionalidades nomeadas\n- `@scope/pkg@1.2.3[feat]` -> pacote com escopo + versão com seleção explícita de funcionalidade\n\n`extractPackageName` remove o sufixo de versão para busca de caminho em disco após a instalação.\n\n## Fonte do manifesto e campos obrigatórios\n\nO manifesto é resolvido como:\n\n1. `package.json.xcsh`\n2. fallback `package.json.pi`\n3. fallback `{ version: package.version }`\n\nImplicações:\n\n- Não há validação de esquema estrita no gerenciador/carregador.\n- Um pacote sem manifesto `xcsh`/`pi` ainda pode ser instalado e listado.\n- O carregamento de plugins em tempo de execução (`getEnabledPlugins`) ignora pacotes sem manifesto `xcsh`/`pi`.\n- `manifest.version` é sempre sobrescrito a partir da `version` do pacote.\n\nJSON malformado em `package.json` é uma falha crítica no momento da leitura; uma estrutura de manifesto malformada pode falhar posteriormente apenas quando campos específicos são consumidos.\n\n## Fluxo de instalação/atualização (`PluginManager.install`)\n\n1. Analisar a sintaxe de colchetes de funcionalidades da especificação de instalação.\n2. Validar o nome do pacote contra regex + lista de negação de metacaracteres de shell.\n3. Garantir que o `package.json` do plugin exista (`xcsh-plugins`, mapa de dependências privadas).\n4. Executar `bun install <packageSpec>` em `~/.xcsh/plugins`.\n5. Ler o `package.json` do pacote instalado em `node_modules/<name>/package.json`.\n6. Resolver o manifesto e calcular `enabledFeatures`:\n   - `[*]`: todas as funcionalidades declaradas (ou `null` se não houver mapa de funcionalidades)\n   - `[a,b]`: valida que cada funcionalidade existe no mapa de funcionalidades do manifesto\n   - `[]`: lista de funcionalidades vazia\n   - especificação básica: `null` (usar política de padrões posteriormente no carregador)\n7. Inserir ou atualizar o estado de tempo de execução no arquivo de lock: `{ version, enabledFeatures, enabled: true }`.\n\n### Semântica de atualização\n\nComo a atualização é orientada pela instalação:\n\n- `xcsh plugin install pkg@newVersion` atualiza a dependência e a versão no arquivo de lock.\n- As configurações existentes são preservadas; a entrada de estado é sobrescrita para versão/funcionalidades/habilitado.\n- Não existe lógica separada de \"verificar atualizações\" ou migração transacional.\n\n## Fluxo de remoção (`PluginManager.uninstall`)\n\n1. Validar o nome do pacote.\n2. Executar `bun uninstall <name>` no diretório de plugins.\n3. Remover o estado de tempo de execução do plugin do arquivo de lock:\n   - `config.plugins[name]`\n   - `config.settings[name]`\n\nSe o comando de desinstalação falhar, o estado de tempo de execução não é alterado.\n\n## Fluxo de listagem (`PluginManager.list`)\n\n1. Ler o mapa de dependências de plugins de `~/.xcsh/plugins/package.json`.\n2. Carregar a configuração de tempo de execução do arquivo de lock (arquivo ausente -> padrões vazios).\n3. Carregar substituições do projeto (`<cwd>/.xcsh/plugin-overrides.json`, erros de análise/leitura -> objeto vazio com aviso).\n4. Para cada dependência com um `package.json` resolvível:\n   - construir registro `InstalledPlugin`\n   - mesclar estado de funcionalidade/habilitação:\n     - base do arquivo de lock (ou padrões)\n     - substituições do projeto podem substituir a seleção de funcionalidades\n     - lista `disabled` do projeto mascara o plugin como desabilitado\n\nEste é o estado efetivo usado pela saída de status da CLI e pelas operações de configurações/funcionalidades.\n\n## Fluxo de link (`PluginManager.link`)\n\n`link` suporta o desenvolvimento local de plugins criando um link simbólico de um pacote local em `~/.xcsh/plugins/node_modules/<pkg.name>`.\n\nComportamento:\n\n1. Resolver `localPath` em relação ao cwd do gerenciador.\n2. Exigir `package.json` local e campo `name`.\n3. Garantir que os diretórios de plugins existam.\n4. Para nomes com escopo, criar o diretório de escopo.\n5. Remover o caminho existente no local do link de destino.\n6. Criar link simbólico.\n7. Adicionar entrada no arquivo de lock de tempo de execução habilitada com funcionalidades padrão (`null`).\n\nRessalva: o `PluginManager.link` atual não impõe a verificação de limite de caminho `cwd` presente no `installer.ts` legado (`normalizedPath.startsWith(normalizedCwd)`), portanto a confiança é responsabilidade do chamador.\n\n## Carregamento em tempo de execução: do plugin instalado às capacidades invocáveis\n\n## Porta de descoberta\n\n`getEnabledPlugins(cwd)` (`plugins/loader.ts`) lê:\n\n- manifesto de dependências de plugins (`package.json`)\n- estado de tempo de execução do arquivo de lock\n- substituições do projeto via `getConfigDirPaths(\"plugin-overrides.json\", { user: false, cwd })`\n\nFiltragem:\n\n- ignorar se não houver `package.json` do plugin\n- ignorar se o manifesto (`xcsh`/`pi`) estiver ausente\n- ignorar se globalmente desabilitado no arquivo de lock\n- ignorar se desabilitado pelo projeto\n\n## Resolução de caminho de capacidades\n\nPara cada plugin habilitado:\n\n- `resolvePluginToolPaths(plugin)`\n- `resolvePluginHookPaths(plugin)`\n- `resolvePluginCommandPaths(plugin)`\n\nCada resolvedor inclui entradas base mais entradas de funcionalidades:\n\n- lista explícita de funcionalidades -> apenas funcionalidades selecionadas\n- `enabledFeatures === null` -> habilitar funcionalidades marcadas com `default: true`\n\nArquivos ausentes são ignorados silenciosamente (guarda `existsSync`).\n\n## Diferenças atuais no roteamento em tempo de execução\n\n- **As ferramentas estão roteadas no tempo de execução hoje** via `discoverAndLoadCustomTools` (`custom-tools/loader.ts`), que chama `getAllPluginToolPaths(cwd)`.\n- Os caminhos são desduplicados por caminho absoluto resolvido na descoberta de ferramentas personalizadas (conjunto `seen`, o primeiro caminho vence).\n- **Os resolvedores de hooks/comandos existem** e são exportados, mas este caminho de código não os conecta atualmente a um registro de tempo de execução da mesma forma que as ferramentas são conectadas.\n\n## Detalhes de gerenciamento de lock/estado\n\n`PluginManager` armazena em cache a configuração de tempo de execução em memória por instância (`#runtimeConfig`) e carrega preguiçosamente uma vez.\n\nComportamento de carregamento:\n\n- arquivo de lock ausente -> `{ plugins: {}, settings: {} }`\n- falha na leitura/análise do arquivo de lock -> aviso + mesmos padrões vazios\n\nComportamento de salvamento:\n\n- escreve o JSON completo do arquivo de lock com formatação a cada mutação\n\nNão existe bloqueio entre processos nem estratégia de mesclagem; escritores concorrentes podem sobrescrever uns aos outros.\n\n## Verificações de segurança e limites de confiança\n\n## Validação de entrada/pacote\n\nO caminho ativo do gerenciador impõe validação de nome de pacote:\n\n- regex para especificações de pacote com e sem escopo (opcionalmente com versão)\n- lista de negação explícita de metacaracteres de shell (`[;&|`$(){}[]<>\\\\]`)\n\nIsso limita o risco de injeção de comandos ao invocar `bun install/uninstall`.\n\n## Limite de confiança do sistema de arquivos\n\n- O código do plugin é executado em processo quando os módulos de ferramentas personalizadas são importados; sem sandboxing.\n- Os caminhos relativos do manifesto são unidos ao diretório do pacote do plugin e apenas verificados quanto à existência.\n- O próprio pacote do plugin é código confiável após a instalação.\n\n## Verificações exclusivas do instalador legado\n\n`installer.ts` inclui verificações adicionais em tempo de link não espelhadas em `PluginManager.link`:\n\n- o caminho local deve ser resolvido dentro do cwd do projeto\n- guardas adicionais de travessia de nome de pacote/caminho para nomeação do destino do link simbólico\n\nComo a CLI usa `PluginManager`, essas guardas de link mais estritas não estão atualmente no caminho principal.\n\n## Comportamento de falha, sucesso parcial e reversão\n\nO gerenciador de plugins não é transacional.\n\n| Estágio da operação | Comportamento em caso de falha | Reversão |\n| --- | --- | --- |\n| `bun install` falha | instalação é abortada com stderr | N/A (sem gravações de estado ainda) |\n| Instalação bem-sucedida, então validação de manifesto/funcionalidade falha | comando falha | Sem reversão de desinstalação; a dependência pode permanecer em `node_modules`/`package.json` |\n| Instalação bem-sucedida, então gravação do arquivo de lock falha | comando falha | Sem reversão do pacote instalado |\n| `bun uninstall` bem-sucedido, gravação do arquivo de lock falha | comando falha | Pacote removido, estado de tempo de execução obsoleto pode permanecer |\n| `link` remove o destino antigo e então a criação do link simbólico falha | comando falha | Sem restauração do link/diretório anterior |\n\nOperacionalmente, `doctor --fix` pode corrigir alguma divergência (execução de `bun install`, limpeza de configuração órfã, limpeza de funcionalidades inválidas), mas é uma operação de melhor esforço.\n\n## Resumo do comportamento com manifesto malformado/ausente\n\n- Campo `xcsh`/`pi` ausente:\n  - instalação/listagem: tolerado (manifesto mínimo)\n  - descoberta de plugins habilitados em tempo de execução: ignorado como não-plugin\n- Funcionalidade ausente referenciada pela especificação de instalação ou `features --set/--enable`: erro crítico com lista de funcionalidades disponíveis\n- `plugin-overrides.json` inválido: ignorado com fallback para `{}` nos caminhos do gerenciador e do carregador\n- Caminhos de arquivos de ferramentas/hooks/comandos referenciados pelo manifesto ausentes: ignorados silenciosamente durante a expansão do resolvedor; sinalizados como erros apenas pelo `doctor`\n\n## Diferenças de modo e precedência\n\n- `--dry-run` (install): retorna resultado de instalação sintético, sem gravações em sistema de arquivos/rede/estado.\n- `--json`: apenas formatação de saída, sem alteração de comportamento.\n- As substituições do projeto sempre têm precedência sobre o arquivo de lock global para visualização de funcionalidades/configurações.\n- A habilitação efetiva é `runtimeEnabled && !projectDisabled`.\n\n## Arquivos de implementação\n\n- [`src/commands/plugin.ts`](../../packages/coding-agent/src/commands/plugin.ts) — declaração do comando CLI e mapeamento de flags\n- [`src/cli/plugin-cli.ts`](../../packages/coding-agent/src/cli/plugin-cli.ts) — despacho de ações, manipuladores de comandos voltados ao usuário\n- [`src/extensibility/plugins/manager.ts`](../../packages/coding-agent/src/extensibility/plugins/manager.ts) — implementação ativa de install/remove/list/link/state/doctor\n- [`src/extensibility/plugins/installer.ts`](../../packages/coding-agent/src/extensibility/plugins/installer.ts) — auxiliares do instalador legado e verificações adicionais de segurança de link\n- [`src/extensibility/plugins/loader.ts`](../../packages/coding-agent/src/extensibility/plugins/loader.ts) — descoberta de plugins habilitados e resolução de caminhos de ferramentas/hooks/comandos\n- [`src/extensibility/plugins/parser.ts`](../../packages/coding-agent/src/extensibility/plugins/parser.ts) — auxiliares de análise de especificação de instalação e nome de pacote\n- [`src/extensibility/plugins/types.ts`](../../packages/coding-agent/src/extensibility/plugins/types.ts) — contratos de tipos de manifesto/tempo de execução/substituição\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts) — roteamento em tempo de execução para módulos de ferramentas fornecidos por plugins\n",
	"pt-br/extensions/rulebook-matching-pipeline.md": "---\ntitle: Pipeline de Correspondência de Rulebook\ndescription: >-\n  Pipeline de correspondência de rulebook para seleção e aplicação de conjuntos\n  de instruções específicos de contexto em sessões de agente.\nsidebar:\n  order: 6\n  label: Correspondência de rulebook\ni18n:\n  sourceHash: a16a9c565053\n  translator: machine\n---\n\n# Pipeline de Correspondência de Rulebook\n\nEste documento descreve como o coding-agent descobre regras a partir de formatos de configuração suportados, normaliza-as em uma única estrutura `Rule`, resolve conflitos de precedência e divide o resultado em:\n\n- **Regras de Rulebook** (disponíveis para o modelo via system prompt + URLs `rule://`)\n- **Regras TTSR** (regras de interrupção de stream por viagem no tempo)\n\nEle reflete a implementação atual, incluindo semânticas parciais e metadados que são analisados mas não aplicados.\n\n## Arquivos de implementação\n\n- [`../src/capability/rule.ts`](../../packages/coding-agent/src/capability/rule.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/discovery/index.ts`](../../packages/coding-agent/src/discovery/index.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/cursor.ts`](../../packages/coding-agent/src/discovery/cursor.ts)\n- [`../src/discovery/windsurf.ts`](../../packages/coding-agent/src/discovery/windsurf.ts)\n- [`../src/discovery/cline.ts`](../../packages/coding-agent/src/discovery/cline.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/system-prompt.ts`](../../packages/coding-agent/src/system-prompt.ts)\n- [`../src/internal-urls/rule-protocol.ts`](../../packages/coding-agent/src/internal-urls/rule-protocol.ts)\n- [`../src/utils/frontmatter.ts`](../../packages/coding-agent/src/utils/frontmatter.ts)\n\n## 1. Estrutura canônica da regra\n\nTodos os provedores normalizam os arquivos-fonte em `Rule`:\n\n```ts\ninterface Rule {\n  name: string;\n  path: string;\n  content: string;\n  globs?: string[];\n  alwaysApply?: boolean;\n  description?: string;\n  ttsrTrigger?: string;\n  _source: SourceMeta;\n}\n```\n\nA identidade da capability é `rule.name` (`ruleCapability.key = rule => rule.name`).\n\nConsequência: precedência e deduplicação são **baseadas apenas no nome**. Dois arquivos diferentes com o mesmo `name` são considerados a mesma regra lógica.\n\n## 2. Fontes de descoberta e normalização\n\n`src/discovery/index.ts` registra automaticamente os provedores. Para `rules`, os provedores atuais são:\n\n- `native` (prioridade `100`)\n- `cursor` (prioridade `50`)\n- `windsurf` (prioridade `50`)\n- `cline` (prioridade `40`)\n\n### Provedor nativo (`builtin.ts`)\n\nCarrega regras `.xcsh` de:\n\n- projeto: `<cwd>/.xcsh/rules/*.{md,mdc}`\n- usuário: `~/.xcsh/agent/rules/*.{md,mdc}`\n\nNormalização:\n\n- `name` = nome do arquivo sem `.md`/`.mdc`\n- frontmatter analisado via `parseFrontmatter`\n- `content` = corpo (frontmatter removido)\n- `globs`, `alwaysApply`, `description`, `ttsr_trigger` mapeados diretamente\n\nRessalva importante: `globs` é convertido como `string[] | undefined` sem filtragem de elementos neste provedor.\n\n### Provedor Cursor (`cursor.ts`)\n\nCarrega de:\n\n- usuário: `~/.cursor/rules/*.{mdc,md}`\n- projeto: `<cwd>/.cursor/rules/*.{mdc,md}`\n\nNormalização (`transformMDCRule`):\n\n- `description`: mantido apenas se for string\n- `alwaysApply`: apenas `true` é preservado (`false` torna-se `undefined`)\n- `globs`: aceita array (apenas elementos string) ou string única\n- `ttsr_trigger`: apenas string\n- `name` a partir do nome do arquivo sem extensão\n\n### Provedor Windsurf (`windsurf.ts`)\n\nCarrega de:\n\n- usuário: `~/.codeium/windsurf/memories/global_rules.md` (nome de regra fixo `global_rules`)\n- projeto: `<cwd>/.windsurf/rules/*.md`\n\nNormalização:\n\n- `globs`: array de strings ou string única\n- `alwaysApply`, `description` obtidos do frontmatter\n- `ttsr_trigger`: apenas string\n- `name` a partir do nome do arquivo para regras de projeto\n\n### Provedor Cline (`cline.ts`)\n\nBusca ascendentemente a partir de `cwd` pelo `.clinerules` mais próximo:\n\n- se diretório: carrega `*.md` dentro dele\n- se arquivo: carrega arquivo único como regra nomeada `clinerules`\n\nNormalização:\n\n- `globs`: array de strings ou string única\n- `alwaysApply`: apenas se booleano\n- `description`: apenas string\n- `ttsr_trigger`: apenas string\n\n## 3. Comportamento do parsing de frontmatter e ambiguidade\n\nTodos os provedores usam `parseFrontmatter` (`utils/frontmatter.ts`) com estas semânticas:\n\n1. O frontmatter é analisado apenas quando o conteúdo começa com `---` e possui um fechamento `\\n---`.\n2. O corpo é aparado após a extração do frontmatter.\n3. Se o parsing YAML falhar:\n   - um aviso é registrado,\n   - o parser recorre a uma análise simples de linhas `key: value` (`^(\\w+):\\s*(.*)$`).\n\nConsequências da ambiguidade:\n\n- O parser de fallback não suporta arrays, objetos aninhados, regras de aspas ou chaves com hífen.\n- Valores de fallback tornam-se strings (por exemplo `alwaysApply: true` torna-se a string `\"true\"`), então provedores que exigem tipos boolean/string podem descartar metadados.\n- `ttsr_trigger` funciona no fallback (chave com underscore); chaves como `thinking-level` não funcionariam.\n- Arquivos sem frontmatter válido ainda são carregados como regras com metadados vazios e corpo de conteúdo completo.\n\n## 4. Precedência de provedores e deduplicação\n\n`loadCapability(\"rules\")` (`capability/index.ts`) mescla as saídas dos provedores e depois deduplica por `rule.name`.\n\n### Modelo de precedência\n\n- Os provedores são ordenados por prioridade decrescente.\n- Prioridade igual mantém a ordem de registro (`cursor` antes de `windsurf` conforme `discovery/index.ts`).\n- A deduplicação é por primeiro encontrado: o primeiro nome de regra encontrado é mantido; itens posteriores com o mesmo nome são marcados como `_shadowed` em `all` e excluídos de `items`.\n\nA ordem efetiva dos provedores de regras atualmente é:\n\n1. `native` (100)\n2. `cursor` (50)\n3. `windsurf` (50)\n4. `cline` (40)\n\n### Ressalva sobre ordenação intra-provedor\n\nDentro de um provedor, a ordem dos itens vem da ordenação do resultado glob de `loadFilesFromDir` mais a ordem explícita de push. Isso é determinístico o suficiente para uso normal, mas não é explicitamente ordenado no código.\n\nDiferenças notáveis na ordem das fontes:\n\n- `native` adiciona diretórios de configuração do projeto e depois do usuário.\n- `cursor` adiciona resultados do usuário e depois do projeto.\n- `windsurf` adiciona `global_rules` do usuário primeiro, depois regras do projeto.\n- `cline` carrega apenas a fonte `.clinerules` mais próxima.\n\n## 5. Divisão em buckets de Rulebook, Always-Apply e TTSR\n\nApós a descoberta de regras em `createAgentSession` (`sdk.ts`):\n\n1. Todas as regras descobertas são examinadas.\n2. Regras com `condition` (chave de frontmatter; `ttsr_trigger` / `ttsrTrigger` aceitos como fallback) são registradas no `TtsrManager`.\n3. Uma lista separada `rulebookRules` é construída com este predicado:\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && !rule.alwaysApply && !!rule.description\n```\n\n4. Uma lista `alwaysApplyRules` é construída:\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && rule.alwaysApply === true\n```\n\n### Comportamento dos buckets\n\n- **Bucket TTSR**: qualquer regra com `condition` (description não é obrigatória). Tem prioridade sobre os outros buckets.\n- **Bucket always-apply**: `alwaysApply === true`, não é TTSR. Conteúdo completo injetado no system prompt. Resolvível via `rule://`.\n- **Bucket rulebook**: deve ter description, não deve ser TTSR, não deve ser `alwaysApply`. Listado no system prompt por nome+descrição; conteúdo lido sob demanda via `rule://`.\n- Uma regra com tanto `condition` quanto `alwaysApply` vai apenas para TTSR (TTSR tem prioridade).\n- Uma regra com tanto `alwaysApply` quanto `description` vai apenas para always-apply (não para rulebook).\n\n## 6. Como os metadados afetam as superfícies em tempo de execução\n\n### `description`\n\n- Obrigatório para inclusão no rulebook.\n- Renderizado no bloco `<rules>` do system prompt.\n- Description ausente significa que a regra não está disponível via `rule://` e não é listada nas regras do system prompt.\n\n### `globs`\n\n- Transportado na `Rule`.\n- Renderizado como entradas `<glob>...</glob>` no bloco de regras do system prompt.\n- Exposto no estado da UI de regras (lista de modo `extensions`).\n- **Não é aplicado para correspondência automática neste pipeline.** Não há matcher de glob em tempo de execução selecionando regras pelo arquivo atual/alvo da ferramenta.\n\n### `alwaysApply`\n\n- Analisado e preservado pelos provedores.\n- Usado na exibição da UI (rótulo de trigger `\"always\"` no gerenciador de estado de extensões).\n- Usado como condição de exclusão de `rulebookRules`.\n- **O conteúdo completo da regra é auto-injetado no system prompt** (antes da seção de regras do rulebook).\n- A regra também é acessível via `rule://<name>` para releitura.\n\n### `ttsr_trigger`\n\n- Mapeado para `rule.ttsrTrigger`.\n- Se presente, a regra é direcionada ao gerenciador TTSR, não ao rulebook.\n\n## 7. Caminho de inclusão no system prompt\n\n`buildSystemPromptInternal` recebe tanto `rules` (rulebook) quanto `alwaysApplyRules`.\n\nAs regras always-apply são renderizadas primeiro, injetando seu conteúdo bruto diretamente no prompt.\n\nAs regras de rulebook são renderizadas em uma seção `# Rules` com:\n\n- `Read rule://<name> when working in matching domain`\n- O `name`, `description` e lista opcional de `<glob>` de cada regra\n\nIsso é consultivo/contextual: o texto do prompt solicita que o modelo leia as regras aplicáveis, mas o código não aplica a correspondência de glob.\n\n## 8. Comportamento da URL interna `rule://`\n\n`RuleProtocolHandler` é registrado com:\n\n```ts\nnew RuleProtocolHandler({ getRules: () => [...rulebookRules, ...alwaysApplyRules] })\n```\n\nImplicações:\n\n- `rule://<name>` resolve contra tanto **rulebookRules** quanto **alwaysApplyRules**.\n- Regras exclusivamente TTSR e regras sem description e sem `alwaysApply` não são acessíveis via `rule://`.\n- A resolução é por correspondência exata de nome.\n- Nomes desconhecidos retornam erro listando os nomes de regras disponíveis.\n- O conteúdo retornado é o `rule.content` bruto (frontmatter removido), tipo de conteúdo `text/markdown`.\n\n## 9. Semânticas parciais / não aplicadas conhecidas\n\n1. As descrições dos provedores mencionam arquivos legados (`.cursorrules`, `.windsurfrules`), mas os caminhos de código do carregador atual não leem realmente esses arquivos.\n2. Os metadados `globs` são expostos ao prompt/UI mas não são aplicados pela lógica de seleção de regras.\n3. A seleção de regras para `rule://` inclui regras de rulebook e always-apply, mas não regras exclusivamente TTSR.\n4. Avisos de descoberta (`loadCapability(\"rules\").warnings`) são produzidos mas `createAgentSession` atualmente não os exibe/registra neste caminho.\n",
	"pt-br/extensions/skills.md": "---\ntitle: Skills\ndescription: >-\n  Sistema de skills para registrar, descobrir e invocar capacidades\n  especializadas no agente de codificação.\nsidebar:\n  order: 3\n  label: Skills\ni18n:\n  sourceHash: 3e062cc13851\n  translator: machine\n---\n\n# Skills\n\nSkills são pacotes de capacidades baseados em arquivos, descobertos na inicialização e expostos ao modelo como:\n\n- metadados leves no prompt do sistema (nome + descrição)\n- conteúdo sob demanda via `read skill://...`\n- comandos interativos opcionais `/skill:<name>`\n\nEste documento abrange o comportamento atual do runtime em `src/extensibility/skills.ts`, `src/discovery/builtin.ts`, `src/internal-urls/skill-protocol.ts` e `src/discovery/agents-md.ts`.\n\n## O que é uma skill nesta base de código\n\nUma skill descoberta é representada como:\n\n- `name`\n- `description`\n- `filePath` (o caminho `SKILL.md`)\n- `baseDir` (diretório da skill)\n- metadados de origem (`provider`, `level`, caminho)\n\nO runtime requer apenas `name` e `path` para validade. Na prática, a qualidade da correspondência depende de `description` ser significativa.\n\n## Layout obrigatório e expectativas do SKILL.md\n\n### Layout de diretório\n\nPara descoberta baseada em provider (providers nativos/Claude/Codex/Agents/plugin), as skills são descobertas **um nível abaixo de `skills/`**:\n\n- `<skills-root>/<skill-name>/SKILL.md`\n\nPadrões aninhados como `<skills-root>/group/<skill>/SKILL.md` não são descobertos pelos carregadores de provider.\n\nPara `skills.customDirectories`, a varredura usa o mesmo layout não recursivo (`*/SKILL.md`).\n\n```text\nLayout descoberto por provider (não recursivo abaixo de skills/):\n\n<root>/skills/\n  ├─ postgres/\n  │   └─ SKILL.md      ✅ descoberto\n  ├─ pdf/\n  │   └─ SKILL.md      ✅ descoberto\n  └─ team/\n      └─ internal/\n          └─ SKILL.md  ❌ não descoberto pelos carregadores de provider\n\nA varredura de diretórios personalizados também é não recursiva, portanto caminhos aninhados são ignorados a menos que você aponte `customDirectories` para esse diretório pai aninhado.\n```\n\n### Frontmatter do `SKILL.md`\n\nCampos de frontmatter suportados no tipo de skill:\n\n- `name?: string`\n- `description?: string`\n- `globs?: string[]`\n- `alwaysApply?: boolean`\n- chaves adicionais são preservadas como metadados desconhecidos\n\nComportamento atual do runtime:\n\n- `name` tem como padrão o nome do diretório da skill\n- `description` é obrigatório para:\n  - descoberta de skills do provider `.xcsh` nativo (`requireDescription: true`)\n  - varreduras de `skills.customDirectories` via `scanSkillsFromDir` em `src/discovery/helpers.ts` (não recursivo)\n- providers não nativos podem carregar skills sem descrição\n\n## Pipeline de descoberta\n\n`discoverSkills()` em `src/extensibility/skills.ts` realiza duas passagens:\n\n1. **Providers de capacidade** via `loadCapability(\"skills\")`\n2. **Diretórios personalizados** via `scanSkillsFromDir(..., { requireDescription: true })` (enumeração de diretório em um nível)\n\nSe `skills.enabled` for `false`, a descoberta não retorna skills.\n\n### Providers de skill integrados e precedência\n\nA ordenação de providers é por prioridade primeiro (maior vence), depois pela ordem de registro para empates.\n\nProviders de skill registrados atualmente:\n\n1. `native` (prioridade 100) — skills de usuário/projeto `.xcsh` via `src/discovery/builtin.ts`\n2. `claude` (prioridade 80)\n3. grupo de prioridade 70 (na ordem de registro):\n   - `claude-plugins`\n   - `agents`\n   - `codex`\n\nA chave de deduplicação é o nome da skill. O primeiro item com um determinado nome vence.\n\n### Alternâncias de origem e filtragem\n\n`discoverSkills()` aplica estes controles:\n\n- alternâncias de origem: `enableCodexUser`, `enableClaudeUser`, `enableClaudeProject`, `enablePiUser`, `enablePiProject`\n- filtros glob no nome da skill:\n  - `ignoredSkills` (excluir)\n  - `includeSkills` (lista de permissões de inclusão; vazia significa incluir todas)\n\nA ordem de filtragem é:\n\n1. origem habilitada\n2. não ignorada\n3. incluída (se a lista de inclusão estiver presente)\n\nPara providers além de codex/claude/native (por exemplo, `agents`, `claude-plugins`), a habilitação atualmente recorre a: habilitado se **qualquer** alternância de origem integrada estiver habilitada.\n\n### Tratamento de colisões e duplicatas\n\n- A deduplicação de capacidade já mantém a primeira skill por nome (provider de maior precedência)\n- `extensibility/skills.ts` adicionalmente:\n  - deduplica arquivos idênticos por `realpath` (seguro para symlinks)\n  - emite avisos de colisão quando um nome de skill posterior entra em conflito\n  - mantém a API de conveniência `discoverSkillsFromDir({ dir, source })` como um adaptador fino sobre `scanSkillsFromDir`\n- Skills de diretórios personalizados são mescladas após as skills do provider e seguem o mesmo comportamento de colisão\n\n## Comportamento de uso no runtime\n\n### Exposição no prompt do sistema\n\nA construção do prompt do sistema (`src/system-prompt.ts`) usa as skills descobertas da seguinte forma:\n\n- se a ferramenta `read` estiver disponível:\n  - inclui a lista de skills descobertas no prompt\n- caso contrário:\n  - omite a lista descoberta\n\nSubagentes de ferramentas de tarefa recebem a lista de skills descobertas/fornecidas da sessão via criação normal de sessão; não há substituição de fixação de skill por tarefa.\n\n### Comandos interativos `/skill:<name>`\n\nSe `skills.enableSkillCommands` for verdadeiro, o modo interativo registra um comando slash por skill descoberta.\n\nComportamento de `/skill:<name> [args]`:\n\n- lê o arquivo da skill diretamente de `filePath`\n- remove o frontmatter\n- injeta o corpo da skill como uma mensagem personalizada de acompanhamento\n- acrescenta metadados (`Skill: <path>`, `User: <args>` opcional)\n\n## Comportamento de URL `skill://`\n\n`src/internal-urls/skill-protocol.ts` suporta:\n\n- `skill://<name>` → resolve para o `SKILL.md` dessa skill\n- `skill://<name>/<relative-path>` → resolve dentro desse diretório de skill\n\n```text\nResolução de URL skill://\n\nskill://pdf\n  -> <pdf-base>/SKILL.md\n\nskill://pdf/references/tables.md\n  -> <pdf-base>/references/tables.md\n\nGuardas:\n- rejeitar caminhos absolutos\n- rejeitar travessia `..`\n- rejeitar qualquer caminho resolvido que escape de <pdf-base>\n```\n\nDetalhes de resolução:\n\n- o nome da skill deve corresponder exatamente\n- caminhos relativos são decodificados por URL\n- caminhos absolutos são rejeitados\n- travessia de caminho (`..`) é rejeitada\n- o caminho resolvido deve permanecer dentro de `baseDir`\n- arquivos ausentes retornam um erro explícito `File not found`\n\nTipo de conteúdo:\n\n- `.md` => `text/markdown`\n- todo o resto => `text/plain`\n\nNenhuma busca de fallback é realizada para recursos ausentes.\n\n## Skills vs XCSH.md, comandos, ferramentas, hooks\n\n### Skills vs XCSH.md\n\n- **Skills**: pacotes de capacidade nomeados e opcionais, selecionados pelo contexto da tarefa ou explicitamente solicitados\n- **XCSH.md/arquivos de contexto**: arquivos de instrução persistentes carregados como capacidade de arquivo de contexto e mesclados por regras de nível/profundidade\n\n`src/discovery/agents-md.ts` especificamente percorre diretórios ancestrais a partir de `cwd` para descobrir arquivos `XCSH.md` independentes (até profundidade 20), excluindo segmentos de diretórios ocultos.\n\n### Skills vs comandos slash\n\n- **Skills**: conteúdo de conhecimento/fluxo de trabalho legível pelo modelo\n- **Comandos slash**: pontos de entrada de comandos invocados pelo usuário\n- `/skill:<name>` é um wrapper de conveniência que injeta o texto da skill; não altera a semântica de descoberta de skills\n\n### Skills vs ferramentas personalizadas\n\n- **Skills**: conteúdo de documentação/fluxo de trabalho carregado por contexto de prompt e `read`\n- **Ferramentas personalizadas**: APIs de ferramentas executáveis que podem ser chamadas pelo modelo com schemas e efeitos colaterais no runtime\n\n### Skills vs hooks\n\n- **Skills**: conteúdo passivo\n- **Hooks**: interceptores de runtime orientados a eventos que podem bloquear/modificar o comportamento durante a execução\n\n## Orientação prática de autoria vinculada à lógica de descoberta\n\n- Coloque cada skill em seu próprio diretório: `<skills-root>/<skill-name>/SKILL.md`\n- Sempre inclua frontmatter explícito com `name` e `description`\n- Mantenha os recursos referenciados sob o mesmo diretório da skill e acesse-os com `skill://<name>/...`\n- Para taxonomia aninhada (`team/domain/skill`), aponte `skills.customDirectories` para o diretório pai aninhado; a própria varredura permanece não recursiva\n- Evite nomes de skill duplicados entre origens; a primeira correspondência vence pela precedência do provider\n",
	"pt-br/index.md": "---\ntitle: Documentação do xcsh\ndescription: >-\n  CLI de desenvolvimento com IA, agente de codificação TypeScript e camada\n  nativa em Rust para sessões de longa duração, suporte a MCP e empacotamento\n  multiplataforma.\nsidebar:\n  order: 0\n  label: Visão Geral\ni18n:\n  sourceHash: b9288f42bf46\n  translator: machine\n---\n\nxcsh é uma CLI de desenvolvimento com IA que inclui um agente de codificação TypeScript e uma\ncamada nativa em Rust (`pi-natives`). Ele estende a linha open-source\n[`badlogic/pi-mono`](https://github.com/badlogic/pi-mono) com um\nruntime reforçado, sessões de longa duração com navegação em árvore e compactação,\numa ferramenta Python IPython, suporte completo a MCP, um sistema de skills e\nempacotamento multiplataforma para Linux, macOS e Windows.\n\n## Por onde começar\n\n- **[Contextos F5 XC](/runtime-tools/context-command)** — conecte-se a tenants do F5 Distributed Cloud.\n  Crie contextos, alterne entre eles, gerencie namespaces e credenciais.\n- **Configuração** — como o xcsh descobre, resolve e organiza a configuração em camadas.\n- **Runtime e Ferramentas** — os runtimes de bash / notebook / resolve tool e a\n  superfície de slash-commands.\n- **Sessões** — log de entradas append-only, navegação em árvore, compactação e o\n  sistema autônomo de memória.\n- **Natives (Rust)** — arquitetura do addon N-API `pi-natives` que\n  fornece shell / PTY / mídia / busca.\n- **MCP** — configuração, detalhes do protocolo, ciclo de vida do runtime e como\n  criar servidores e ferramentas.\n- **Extensões, Skills e Plugins** — criação, carregamento, regras de correspondência, o\n  marketplace e o instalador de plugins.\n- **Provedores e Modelos** — configuração de modelos, detalhes de streaming e o\n  runtime Python / IPython.\n- **TUI** — temas, o comando `/tree` e hooks de integração para\n  extensões e ferramentas personalizadas.\n\n## Como esta documentação está organizada\n\nCada grupo de nível superior na barra lateral corresponde a um subsistema do agente. Dentro\nde um grupo, as páginas vão de \"visão geral\" até \"detalhes internos\", para que você possa parar de ler\nquando tiver contexto suficiente para a tarefa em questão.\n",
	"pt-br/mcp/mcp-config.md": "---\ntitle: Configuração MCP\ndescription: >-\n  Configuração, validação e gerenciamento de servidores MCP para o runtime do\n  agente de codificação.\nsidebar:\n  order: 1\n  label: Configuração\ni18n:\n  sourceHash: ef8b49458ce9\n  translator: machine\n---\n\n# Configuração MCP no OMP\n\nEste guia explica como adicionar, editar e validar servidores MCP para o agente de codificação OMP.\n\nFonte de verdade no código:\n\n- Tipos de configuração de runtime: `packages/coding-agent/src/mcp/types.ts`\n- Escritor de configuração: `packages/coding-agent/src/mcp/config-writer.ts`\n- Carregador + validação: `packages/coding-agent/src/mcp/config.ts`\n- Descoberta autônoma de `mcp.json`: `packages/coding-agent/src/discovery/mcp-json.ts`\n- Schema: `packages/coding-agent/src/config/mcp-schema.json`\n\n## Locais de configuração preferidos\n\nO OMP pode descobrir servidores MCP de múltiplas ferramentas (`.claude/`, `.cursor/`, `.vscode/`, `opencode.json` e mais), mas para configuração nativa do OMP você geralmente deve usar um destes arquivos:\n\n- Projeto: `.xcsh/mcp.json`\n- Usuário: `~/.xcsh/mcp.json`\n\nO OMP também aceita arquivos autônomos de fallback na raiz do projeto:\n\n- `mcp.json`\n- `.mcp.json`\n\nUse `.xcsh/mcp.json` quando quiser que o OMP seja o proprietário da configuração. Use `mcp.json` / `.mcp.json` na raiz apenas quando quiser um arquivo de fallback portátil que outros clientes MCP também possam ler.\n\n## Adicionar uma referência de schema\n\nAdicione esta linha no topo do arquivo para autocompletar e validação no editor:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {}\n}\n```\n\nO OMP agora escreve isso automaticamente quando `/mcp add`, `/mcp enable`, `/mcp disable`, `/mcp reauth` ou outros fluxos de escrita de configuração criam ou atualizam um arquivo MCP gerenciado pelo OMP.\n\n## Estrutura do arquivo\n\nO OMP suporta esta estrutura de nível superior:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"server-name\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"some-mcp-server\"]\n    }\n  },\n  \"disabledServers\": [\"server-name\"]\n}\n```\n\nChaves de nível superior:\n\n- `$schema` — URL opcional do JSON Schema para ferramentas\n- `mcpServers` — mapa de nome do servidor para configuração do servidor\n- `disabledServers` — lista de bloqueio em nível de usuário usada para desativar servidores descobertos pelo nome\n\nOs nomes dos servidores devem corresponder a `^[a-zA-Z0-9_.-]{1,100}$`.\n\n## Campos de servidor suportados\n\nCampos compartilhados para todos os transportes:\n\n- `enabled?: boolean` — ignora este servidor quando `false`\n- `timeout?: number` — timeout de conexão em milissegundos\n- `auth?: { ... }` — metadados de autenticação usados pelo OMP para fluxos OAuth/API-key\n- `oauth?: { ... }` — configurações explícitas de cliente OAuth usadas durante auth/reauth\n\n### Transporte `stdio`\n\n`stdio` é o padrão quando `type` é omitido.\n\nObrigatório:\n\n- `command: string`\n\nOpcional:\n\n- `type?: \"stdio\"`\n- `args?: string[]`\n- `env?: Record<string, string>`\n- `cwd?: string`\n\nExemplo:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/Users/alice/projects\",\n        \"/Users/alice/Documents\"\n      ]\n    }\n  }\n}\n```\n\nIsso segue o pacote oficial do servidor MCP Filesystem (`@modelcontextprotocol/server-filesystem`).\n\n### Transporte `http`\n\nObrigatório:\n\n- `type: \"http\"`\n- `url: string`\n\nOpcional:\n\n- `headers?: Record<string, string>`\n\nExemplo:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\nIsso corresponde ao endpoint hospedado do servidor MCP do GitHub.\n\n### Transporte `sse`\n\nObrigatório:\n\n- `type: \"sse\"`\n- `url: string`\n\nOpcional:\n\n- `headers?: Record<string, string>`\n\nExemplo:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"legacy-remote\": {\n      \"type\": \"sse\",\n      \"url\": \"https://example.com/mcp/sse\"\n    }\n  }\n}\n```\n\n`sse` ainda é suportado por compatibilidade, mas a especificação MCP agora prefere Streamable HTTP (`type: \"http\"`) para novos servidores.\n\n## Campos de autenticação\n\nO OMP compreende dois objetos relacionados à autenticação.\n\n### `auth`\n\n```json\n{\n  \"type\": \"oauth\" | \"apikey\",\n  \"credentialId\": \"optional-stored-credential-id\",\n  \"tokenUrl\": \"optional-token-endpoint\",\n  \"clientId\": \"optional-client-id\",\n  \"clientSecret\": \"optional-client-secret\"\n}\n```\n\nUse isso quando o OMP deve lembrar como reidratar credenciais para um servidor.\n\n### `oauth`\n\n```json\n{\n  \"clientId\": \"...\",\n  \"clientSecret\": \"...\",\n  \"redirectUri\": \"...\",\n  \"callbackPort\": 3334,\n  \"callbackPath\": \"/oauth/callback\"\n}\n```\n\nUse isso quando o servidor MCP requer configurações explícitas de cliente OAuth.\n\nO Slack é o exemplo atual mais claro. O servidor MCP do Slack é hospedado em `https://mcp.slack.com/mcp`, usa Streamable HTTP e requer OAuth confidencial com as credenciais de cliente do seu aplicativo Slack.\n\nExemplo:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\nEndpoints relevantes do Slack na documentação do Slack:\n\n- Endpoint MCP: `https://mcp.slack.com/mcp`\n- Endpoint de autorização: `https://slack.com/oauth/v2_user/authorize`\n- Endpoint de token: `https://slack.com/api/oauth.v2.user.access`\n\n## Exemplos comuns para copiar e colar\n\n### Servidor Filesystem via stdio\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/absolute/path/one\",\n        \"/absolute/path/two\"\n      ]\n    }\n  }\n}\n```\n\n### Servidor hospedado do GitHub via HTTP\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\n### Servidor local do GitHub via Docker\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"-i\",\n        \"--rm\",\n        \"-e\",\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\",\n        \"ghcr.io/github/github-mcp-server\"\n      ],\n      \"env\": {\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n      }\n    }\n  }\n}\n```\n\nIsso corresponde à imagem Docker oficial local do GitHub `ghcr.io/github/github-mcp-server`.\n\n### Servidor hospedado do Slack via OAuth\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\n## Segredos e resolução de variáveis\n\nEsta é a parte que geralmente confunde as pessoas.\n\n### Em `.xcsh/mcp.json` e `~/.xcsh/mcp.json`\n\nAntes de o OMP iniciar um servidor ou fazer uma requisição HTTP, ele resolve os valores de `env` e `headers` desta forma:\n\n1. Se um valor começa com `!`, o OMP o executa como um comando shell e usa o stdout sem espaços extras.\n2. Caso contrário, o OMP primeiro verifica se o valor corresponde a um nome de variável de ambiente.\n3. Se essa variável de ambiente não estiver definida, o OMP usa a string literalmente.\n\nExemplos:\n\n```json\n{\n  \"env\": {\n    \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n  },\n  \"headers\": {\n    \"X-MCP-Insiders\": \"true\"\n  }\n}\n```\n\nIsso significa que o seguinte é válido e conveniente para segredos locais:\n\n- `\"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"` → copiar do ambiente shell atual\n- `\"Authorization\": \"Bearer hardcoded-token\"` → usar o valor literal\n- `\"Authorization\": \"!printf 'Bearer %s' \\\"$GITHUB_TOKEN\\\"\"` → construir o header a partir de um comando\n\n### Em `mcp.json` e `.mcp.json` na raiz\n\nO carregador de fallback autônomo também expande `${VAR}` e `${VAR:-default}` dentro de strings durante a descoberta.\n\nExemplo:\n\n```json\n{\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\",\n      \"headers\": {\n        \"Authorization\": \"Bearer ${GITHUB_TOKEN}\"\n      }\n    }\n  }\n}\n```\n\nSe você deseja o comportamento menos surpreendente do OMP, prefira `.xcsh/mcp.json` e use valores explícitos de env/header.\n\n## `disabledServers`\n\n`disabledServers` é principalmente útil no arquivo de configuração do usuário (`~/.xcsh/mcp.json`) quando um servidor é descoberto de outra fonte e você deseja que o OMP o ignore sem editar a configuração dessa outra ferramenta.\n\nExemplo:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"disabledServers\": [\"github\", \"slack\"]\n}\n```\n\n## `/mcp add` vs editar o JSON diretamente\n\nUse `/mcp add` quando quiser uma configuração guiada.\n\nUse a edição direta do JSON quando:\n\n- você precisa de uma opção de transporte ou autenticação que o assistente ainda não oferece\n- você deseja colar uma definição de servidor de outro cliente MCP\n- você deseja validação baseada em schema no seu editor\n\nApós editar, use:\n\n- `/mcp reload` para redescobrir e reconectar servidores na sessão atual\n- `/mcp list` para ver de qual arquivo de configuração um servidor veio\n- `/mcp test <name>` para testar um único servidor\n\n## Regras de validação que o OMP aplica\n\nDe `validateServerConfig()` em `packages/coding-agent/src/mcp/config.ts`:\n\n- `stdio` requer `command`\n- `http` e `sse` requerem `url`\n- um servidor não pode definir tanto `command` quanto `url`\n- valores de `type` desconhecidos são rejeitados\n\nImplicações práticas:\n\n- Omitir `type` significa `stdio`\n- Se você colar uma configuração de servidor remoto e esquecer `\"type\": \"http\"`, o OMP tratará como `stdio` e reclamará que `command` está faltando\n- `sse` permanece válido por compatibilidade, mas novos servidores hospedados geralmente devem ser configurados como `http`\n\n## Descoberta e precedência\n\nO OMP não mescla definições duplicadas de servidor entre arquivos. Os provedores de descoberta são priorizados, e a definição de maior prioridade prevalece.\n\nNa prática:\n\n- prefira `.xcsh/mcp.json` ou `~/.xcsh/mcp.json` quando quiser uma substituição específica do OMP\n- mantenha os nomes dos servidores únicos entre ferramentas quando possível\n- use `disabledServers` na configuração do usuário quando uma configuração de terceiros continua reintroduzindo um servidor que você não deseja\n\n## Solução de problemas\n\n### `Server \"name\": stdio server requires \"command\" field`\n\nVocê provavelmente omitiu `type: \"http\"` em um servidor remoto.\n\n### `Server \"name\": both \"command\" and \"url\" are set`\n\nEscolha um transporte. O OMP trata `command` como stdio e `url` como http/sse.\n\n### `/mcp add` funcionou mas o servidor ainda não conecta\n\nO JSON é válido, mas o servidor ainda pode estar inacessível. Use `/mcp test <name>` e verifique se:\n\n- o binário ou imagem Docker existe\n- as variáveis de ambiente necessárias estão definidas\n- a URL remota está acessível\n- o token OAuth ou API é válido\n\n### O servidor existe na configuração de outra ferramenta mas não no OMP\n\nExecute `/mcp list`. O OMP descobre muitos arquivos MCP de terceiros, mas o carregamento em nível de projeto também pode ser desabilitado através da configuração `mcp.enableProjectConfig`.\n\n## Referências\n\n- Especificação de transporte MCP: <https://modelcontextprotocol.io/specification/2025-03-26/basic/transports>\n- Pacote do servidor Filesystem: <https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem>\n- Servidor MCP do GitHub: <https://github.com/github/github-mcp-server>\n- Documentação do servidor MCP do Slack: <https://docs.slack.dev/ai/slack-mcp-server/>\n",
	"pt-br/mcp/mcp-protocol-transports.md": "---\ntitle: Protocolo MCP e Internos de Transporte\ndescription: >-\n  Implementação do protocolo MCP com camadas de transporte stdio, SSE e HTTP\n  streamable.\nsidebar:\n  order: 2\n  label: Protocolo e transportes\ni18n:\n  sourceHash: 48632064dd00\n  translator: machine\n---\n\n# Protocolo MCP e Internos de Transporte\n\nEste documento descreve como o coding-agent implementa a mensageria JSON-RPC do MCP e como as responsabilidades de protocolo são separadas das responsabilidades de transporte.\n\n## Escopo\n\nAbrange:\n\n- Fluxo de requisição/resposta e notificação JSON-RPC\n- Correlação de requisições e ciclo de vida para transportes stdio e HTTP/SSE\n- Comportamento de timeout e cancelamento\n- Propagação de erros e tratamento de payloads malformados\n- Limites de seleção de transporte (`stdio` vs `http`/`sse`)\n- Quais responsabilidades de reconexão/retry são do nível de transporte vs nível de gerenciador\n\nNão abrange UX de criação de extensões ou UI de comandos.\n\n## Arquivos de implementação\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/transports/stdio.ts`](../../packages/coding-agent/src/mcp/transports/stdio.ts)\n- [`src/mcp/transports/http.ts`](../../packages/coding-agent/src/mcp/transports/http.ts)\n- [`src/mcp/transports/index.ts`](../../packages/coding-agent/src/mcp/transports/index.ts)\n- [`src/mcp/json-rpc.ts`](../../packages/coding-agent/src/mcp/json-rpc.ts)\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n\n## Limites entre camadas\n\n### Camada de protocolo (JSON-RPC + métodos MCP)\n\n- Os formatos de mensagem são definidos em `types.ts` (`JsonRpcRequest`, `JsonRpcNotification`, `JsonRpcResponse`, `JsonRpcMessage`).\n- A lógica do cliente MCP (`client.ts`) decide a ordem dos métodos e o handshake de sessão:\n  1. Requisição `initialize`\n  2. Notificação `notifications/initialized`\n  3. Chamadas de métodos como `tools/list`, `tools/call`\n\n### Camada de transporte (`MCPTransport`)\n\n`MCPTransport` abstrai a entrega e o ciclo de vida:\n\n- `request(method, params, options?) -> Promise<T>`\n- `notify(method, params?) -> Promise<void>`\n- `close()`\n- `connected`\n- callbacks opcionais: `onClose`, `onError`, `onNotification`\n\nAs implementações de transporte são responsáveis pelo enquadramento e detalhes de I/O:\n\n- `StdioTransport`: JSON delimitado por nova linha sobre stdio de subprocesso\n- `HttpTransport`: JSON-RPC sobre HTTP POST, com respostas/escuta SSE opcionais\n\n### Ressalva importante atual\n\nOs callbacks de transporte (`onClose`, `onError`, `onNotification`) estão implementados, mas os fluxos atuais de `MCPClient`/`MCPManager` não conectam a lógica de reconexão a esses callbacks. Notificações só são consumidas se o chamador registrar handlers.\n\n## Seleção de transporte\n\n`client.ts:createTransport()` escolhe o transporte a partir da configuração:\n\n- `type` omitido ou `\"stdio\"` -> `createStdioTransport`\n- `\"http\"` ou `\"sse\"` -> `createHttpTransport`\n\n`\"sse\"` é tratado como uma variante de transporte HTTP (mesma classe), não como uma implementação de transporte separada.\n\n## Fluxo de mensagens JSON-RPC e correlação\n\n## IDs de requisição\n\nCada transporte gera IDs por requisição (string de `Math.random` + timestamp). Os IDs são tokens de correlação locais ao transporte.\n\n## Caminho de correlação stdio\n\n- A requisição de saída é serializada como um objeto JSON + `\\n`.\n- `#pendingRequests: Map<id, {resolve,reject}>` armazena as requisições em andamento.\n- O loop de leitura analisa JSONL do stdout e chama `#handleMessage`.\n- Se a mensagem de entrada possui `id` correspondente, a requisição resolve/rejeita.\n- Se a mensagem de entrada possui `method` e não possui `id`, é tratada como notificação e enviada para `onNotification`.\n\nIDs desconhecidos são ignorados (sem rejeição, sem callback de erro).\n\n## Caminho de correlação HTTP\n\n- A requisição de saída é um HTTP `POST` com corpo JSON e `id` gerado.\n- Caminho de resposta não-SSE: analisa uma resposta JSON-RPC e retorna `result`/lança exceção em `error`.\n- Caminho de resposta SSE (`Content-Type: text/event-stream`): transmite eventos, retorna a primeira mensagem cujo `id` corresponde ao ID esperado da requisição e possui `result` ou `error`.\n- Mensagens SSE com `method` e sem `id` são tratadas como notificações.\n\nSe o stream SSE encerrar antes da resposta correspondente, a requisição falha com `No response received for request ID ...`.\n\n## Notificações\n\nO cliente emite notificações JSON-RPC via `transport.notify(...)`.\n\n- Stdio: escreve o frame de notificação no stdin (`jsonrpc`, `method`, `params` opcional) mais nova linha.\n- HTTP: envia corpo POST sem `id`; sucesso aceita `2xx` ou `202 Accepted`.\n\nNotificações iniciadas pelo servidor são expostas apenas através do `onNotification` do transporte; não há assinante global padrão no gerenciador/cliente.\n\n## Internos do transporte stdio\n\n## Ciclo de vida e transições de estado\n\n- Inicial: `connected=false`, `process=null`, mapa de pendentes vazio\n- `connect()`:\n  - cria subprocesso com comando/args/env/cwd configurados\n  - marca como conectado\n  - inicia loop de leitura do stdout (`readJsonl`)\n  - inicia loop do stderr (leitura/descarte; atualmente silencioso)\n- `close()`:\n  - marca como desconectado\n  - rejeita todas as requisições pendentes (`Transport closed`)\n  - encerra o subprocesso\n  - aguarda encerramento do loop de leitura\n  - emite `onClose`\n\nSe o loop de leitura encerrar inesperadamente, o `finally` aciona `#handleClose()` que realiza a mesma rejeição de requisições pendentes e callback de fechamento.\n\n## Timeout e cancelamento\n\nPor requisição:\n\n- timeout padrão de `config.timeout ?? 30000`\n- `AbortSignal` opcional do chamador\n- tanto abort quanto timeout rejeitam a promise pendente e limpam a entrada no mapa\n\nO cancelamento é apenas local: o transporte não envia notificação de cancelamento em nível de protocolo para o servidor.\n\n## Tratamento de payload malformado\n\nNo loop de leitura:\n\n- cada linha JSONL analisada é passada para `#handleMessage` em `try/catch`\n- exceções de tratamento de mensagens malformadas/inválidas são descartadas (comentário `Skip malformed lines`)\n- o loop continua, então uma mensagem ruim não encerra a conexão\n\nSe o parser de stream subjacente lançar exceção, `onError` é invocado (quando ainda conectado), então a conexão é fechada.\n\n## Comportamento de desconexão/falha\n\nQuando o processo encerra ou o stream fecha:\n\n- todas as requisições em andamento são rejeitadas com `Transport closed`\n- sem reinício ou reconexão automática\n- camadas superiores devem reconectar criando um novo transporte\n\n## Notas sobre backpressure/streaming\n\n- Escritas de saída usam `stdin.write()` + `flush()` sem aguardar semântica de drain.\n- Não há fila explícita ou gerenciamento de high-watermark no transporte.\n- O processamento de entrada é orientado por stream (`for await` sobre `readJsonl`), uma mensagem analisada por vez.\n\n## Internos do transporte HTTP/SSE\n\n## Ciclo de vida e semântica de conexão\n\nO transporte HTTP possui estado de conexão lógico, mas o caminho de requisição é stateless por chamada HTTP:\n\n- `connect()` define `connected=true` (sem handshake de socket/sessão)\n- rastreamento opcional de sessão do servidor via header `Mcp-Session-Id`\n- `close()` opcionalmente envia `DELETE` com `Mcp-Session-Id`, aborta o listener SSE, emite `onClose`\n\nPortanto, `connected` significa \"transporte utilizável\", não \"stream persistente estabelecido\".\n\n## Comportamento do header de sessão\n\n- Na resposta do POST, se o header `Mcp-Session-Id` estiver presente, o transporte o armazena.\n- Requisições/notificações subsequentes incluem `Mcp-Session-Id`.\n- `close()` tenta encerrar a sessão do servidor com HTTP DELETE; falhas de encerramento são ignoradas.\n\n## Timeout e cancelamento\n\nPara ambos `request()` e `notify()`:\n\n- timeout usa `AbortController` (`config.timeout ?? 30000`)\n- sinal externo, se fornecido, é mesclado via `AbortSignal.any([...])`\n- tratamento de AbortError distingue abort do chamador vs timeout\n\nErros lançados:\n\n- timeout: `Request timeout after ...ms` (ou `SSE response timeout ...`, `Notify timeout ...`)\n- abort do chamador: AbortError original é relançado quando o sinal externo já está abortado\n\n## Propagação de erros HTTP\n\nEm resposta não-OK:\n\n- texto da resposta é incluído no erro lançado (`HTTP <status>: <text>`)\n- se presente, dicas de autenticação de `WWW-Authenticate` e `Mcp-Auth-Server` são adicionadas\n\nEm objeto de erro JSON-RPC:\n\n- lança `MCP error <code>: <message>`\n\nCorpo JSON malformado (falha em `response.json()`) propaga como exceção de parse.\n\n## Comportamento e modos SSE\n\nExistem dois caminhos SSE:\n\n1. **Resposta SSE por requisição** (`#parseSSEResponse`)\n   - usado quando o content type da resposta POST é `text/event-stream`\n   - consome o stream até encontrar o ID de resposta correspondente\n   - pode processar notificações intercaladas durante o mesmo stream\n\n2. **Listener SSE em background** (`startSSEListener()`)\n   - listener GET opcional para notificações iniciadas pelo servidor\n   - atualmente não é iniciado automaticamente pelo gerenciador/cliente MCP\n   - se GET retorna `405`, o listener se desabilita silenciosamente (servidor não suporta este modo)\n\n## Tratamento de payload malformado e desconexão\n\nErros de parse JSON no SSE propagam de `readSseJson` e rejeitam requisição/listener.\n\n- Erros de parse SSE de requisição rejeitam a requisição ativa.\n- Erros do listener em background acionam `onError` (exceto AbortError).\n- Sem reconexão automática para o listener em background.\n\n## Utilitário `json-rpc.ts` vs abstração de transporte\n\n`src/mcp/json-rpc.ts` fornece os helpers `callMCP()` e `parseSSE()` para chamadas HTTP MCP diretas (usado pela integração Exa), não a abstração `MCPTransport` usada por `MCPClient`/`MCPManager`.\n\nDiferenças notáveis do `HttpTransport`:\n\n- analisa o texto completo da resposta primeiro, depois extrai a primeira linha `data:` (`parseSSE`), com fallback para JSON\n- sem gerenciamento de timeout de requisição, sem API de abort, sem tratamento de session-id, sem ciclo de vida de transporte\n- retorna o envelope JSON-RPC bruto\n\nEste caminho é leve, mas menos robusto que a implementação completa de transporte.\n\n## Responsabilidades de retry/reconexão\n\n## Nível de transporte\n\nAs implementações atuais de transporte **não**:\n\n- tentam novamente requisições com falha\n- reconectam após saída do processo stdio\n- reconectam listeners SSE\n- reenviam requisições em andamento após desconexão\n\nElas falham rapidamente e propagam erros.\n\n## Nível de gerenciador/cliente\n\n`MCPManager` lida com a orquestração de descoberta/conexão inicial e pode reconectar apenas executando os fluxos de conexão novamente (caminhos `connectToServer`/`discoverAndConnect`). Ele não repara automaticamente um transporte já conectado em callbacks de falha em tempo de execução.\n\n`MCPManager` possui comportamento de fallback na inicialização para servidores lentos (ferramentas adiadas do cache), mas isso é fallback de disponibilidade de ferramentas, não retry de transporte.\n\n## Resumo de cenários de falha\n\n- **Linha de mensagem stdio malformada**: descartada; stream continua.\n- **Stream/processo stdio encerra**: transporte fecha; requisições pendentes rejeitadas como `Transport closed`.\n- **HTTP não-2xx**: requisição/notificação lança erro HTTP.\n- **Resposta JSON inválida**: exceção de parse propagada.\n- **SSE encerra sem ID correspondente**: requisição falha com `No response received for request ID ...`.\n- **Timeout**: erro de timeout específico do transporte.\n- **Abort do chamador**: AbortError/razão propagado do sinal do chamador.\n\n## Regra prática de limite\n\nSe a preocupação é formato de mensagem, correlação de ID ou ordenação de métodos MCP, pertence à lógica de protocolo/cliente.\n\nSe a preocupação é enquadramento (JSONL vs HTTP/SSE), parse de stream, ciclo de vida de fetch/spawn, relógios de timeout ou encerramento de conexão, pertence à implementação de transporte.\n",
	"pt-br/mcp/mcp-runtime-lifecycle.md": "---\ntitle: Ciclo de Vida do MCP em Tempo de Execução\ndescription: >-\n  Ciclo de vida do processo do servidor MCP desde a inicialização até o registro\n  de ferramentas, monitoramento de saúde e encerramento.\nsidebar:\n  order: 3\n  label: Ciclo de vida em tempo de execução\ni18n:\n  sourceHash: d04cefaf38f8\n  translator: machine\n---\n\n# Ciclo de vida do MCP em tempo de execução\n\nEste documento descreve como os servidores MCP são descobertos, conectados, expostos como ferramentas, atualizados e encerrados no runtime do coding-agent.\n\n## Visão geral do ciclo de vida\n\n1. **Inicialização do SDK** chama `discoverAndLoadMCPTools()` (a menos que o MCP esteja desabilitado).\n2. **Descoberta** (`loadAllMCPConfigs`) resolve as configurações dos servidores MCP a partir das fontes de capacidade, filtra entradas desabilitadas/de projeto/Exa e preserva os metadados de origem.\n3. **Fase de conexão do Manager** (`MCPManager.connectServers`) inicia a conexão por servidor + `tools/list` em paralelo.\n4. **Porta de inicialização rápida** aguarda até 250ms, depois pode retornar:\n   - `MCPTool`s totalmente carregadas,\n   - falhas por servidor,\n   - ou `DeferredMCPTool`s em cache para servidores ainda pendentes.\n5. **Integração do SDK** mescla as ferramentas MCP no registro de ferramentas do runtime para a sessão.\n6. **Sessão ativa** pode atualizar as ferramentas MCP via fluxos `/mcp` (`disconnectAll` + redescobrir + `session.refreshMCPTools`).\n7. **Encerramento** ocorre quando os chamadores invocam `disconnectServer`/`disconnectAll`; o manager também limpa os registros de ferramentas MCP para servidores desconectados.\n\n## Fase de descoberta e carregamento\n\n### Caminho de entrada a partir do SDK\n\n`createAgentSession()` em `src/sdk.ts` realiza a inicialização do MCP quando `enableMCP` é true (padrão):\n\n- chama `discoverAndLoadMCPTools(cwd, { ... })`,\n- passa `authStorage`, armazenamento de cache e configuração `mcp.enableProjectConfig`,\n- sempre define `filterExa: true`,\n- registra erros de carregamento/conexão por servidor,\n- armazena o manager retornado em `toolSession.mcpManager` e no resultado da sessão.\n\nSe `enableMCP` for false, a descoberta MCP é totalmente ignorada.\n\n### Descoberta e filtragem de configuração\n\n`loadAllMCPConfigs()` (`src/mcp/config.ts`) carrega itens canônicos de servidores MCP através da descoberta de capacidades, depois converte para `MCPServerConfig` legado.\n\nComportamento de filtragem:\n\n- `enableProjectConfig: false` remove entradas de nível de projeto (`_source.level === \"project\"`).\n- Servidores com `enabled: false` são ignorados antes das tentativas de conexão.\n- Servidores Exa são filtrados por padrão e as chaves de API são extraídas para integração nativa da ferramenta Exa.\n\nO resultado inclui tanto `configs` quanto `sources` (metadados usados posteriormente para rotulagem de provedor).\n\n### Comportamento de falha no nível de descoberta\n\n`discoverAndLoadMCPTools()` distingue duas classes de falha:\n\n- **Falha crítica de descoberta** (exceção de `manager.discoverAndConnect`, tipicamente da descoberta de configuração): retorna um conjunto de ferramentas vazio e um erro sintético `{ path: \".mcp.json\", error }`.\n- **Falha de runtime/conexão por servidor**: o manager retorna sucesso parcial com mapa de `errors`; outros servidores continuam.\n\nPortanto, a inicialização não falha a sessão inteira do agente quando servidores MCP individuais falham.\n\n## Modelo de estado do Manager\n\n`MCPManager` rastreia o ciclo de vida em tempo de execução com registros separados:\n\n- `#connections: Map<string, MCPServerConnection>` — servidores totalmente conectados.\n- `#pendingConnections: Map<string, Promise<MCPServerConnection>>` — handshake em andamento.\n- `#pendingToolLoads: Map<string, Promise<{ connection, serverTools }>>` — conectado mas ferramentas ainda carregando.\n- `#tools: CustomTool[]` — visão atual das ferramentas MCP exposta aos chamadores.\n- `#sources: Map<string, SourceMeta>` — metadados de provedor/origem mesmo antes da conexão ser concluída.\n\n`getConnectionStatus(name)` deriva o status a partir desses mapas:\n\n- `connected` se estiver em `#connections`,\n- `connecting` se houver conexão pendente ou carregamento de ferramentas pendente,\n- `disconnected` caso contrário.\n\n## Estabelecimento de conexão e temporização de inicialização\n\n## Pipeline de conexão por servidor\n\nPara cada servidor descoberto em `connectServers()`:\n\n1. armazenar/atualizar metadados de origem,\n2. ignorar se já estiver conectado/pendente,\n3. validar campos de transporte (`validateServerConfig`),\n4. resolver substituições de autenticação/shell (`#resolveAuthConfig`),\n5. chamar `connectToServer(name, resolvedConfig)`,\n6. chamar `listTools(connection)`,\n7. armazenar definições de ferramentas em cache (`MCPToolCache.set`) com melhor esforço.\n\nComportamento de `connectToServer()` (`src/mcp/client.ts`):\n\n- cria transporte stdio ou HTTP/SSE,\n- realiza `initialize` + `notifications/initialized` do MCP,\n- usa timeout (`config.timeout` ou padrão de 30s),\n- fecha o transporte em caso de falha na inicialização.\n\n### Porta de inicialização rápida + fallback diferido\n\n`connectServers()` aguarda uma corrida entre:\n\n- todas as tarefas de conexão/carregamento de ferramentas resolvidas, e\n- `STARTUP_TIMEOUT_MS = 250`.\n\nApós 250ms:\n\n- tarefas concluídas tornam-se `MCPTool`s ativas,\n- tarefas rejeitadas produzem erros por servidor,\n- tarefas ainda pendentes:\n  - usam definições de ferramentas em cache, se disponíveis (`MCPToolCache.get`), para criar `DeferredMCPTool`s,\n  - caso contrário, aguardam até que as tarefas pendentes sejam resolvidas.\n\nEste é um modelo de inicialização híbrido: retorno rápido quando o cache está disponível, espera por correção quando o cache não está.\n\n### Comportamento de conclusão em segundo plano\n\nCada `toolsPromise` pendente também tem uma continuação em segundo plano que eventualmente:\n\n- substitui a fatia de ferramentas daquele servidor no estado do manager via `#replaceServerTools`,\n- escreve no cache,\n- registra falhas tardias somente após a inicialização (`allowBackgroundLogging`).\n\n## Exposição de ferramentas e disponibilidade na sessão ativa\n\n### Registro na inicialização\n\n`discoverAndLoadMCPTools()` converte as ferramentas do manager em `LoadedCustomTool[]` e decora os caminhos (`mcp:<server> via <providerName>` quando conhecido).\n\n`createAgentSession()` então insere essas ferramentas em `customTools`, que são encapsuladas e adicionadas ao registro de ferramentas do runtime com nomes como `mcp_<server>_<tool>`.\n\n### Chamadas de ferramentas\n\n- `MCPTool` chama ferramentas através de uma `MCPServerConnection` já conectada.\n- `DeferredMCPTool` aguarda `waitForConnection(server)` antes de chamar; isso permite que ferramentas em cache existam antes da conexão estar pronta.\n\nAmbas retornam saída estruturada da ferramenta e convertem erros de transporte/ferramenta em conteúdo `MCP error: ...` da ferramenta (abort permanece abort).\n\n## Caminhos de atualização/recarga (inicialização vs recarga ao vivo)\n\n### Caminho de inicialização inicial\n\n- descoberta/carregamento único em `sdk.ts`,\n- ferramentas são registradas no registro inicial de ferramentas da sessão.\n\n### Caminho de recarga interativa\n\nO caminho `/mcp reload` (`src/modes/controllers/mcp-command-controller.ts`) faz:\n\n1. `mcpManager.disconnectAll()`,\n2. `mcpManager.discoverAndConnect()`,\n3. `session.refreshMCPTools(mcpManager.getTools())`.\n\n`session.refreshMCPTools()` (`src/session/agent-session.ts`) remove todas as ferramentas `mcp_`, re-encapsula as ferramentas MCP mais recentes e reativa o conjunto de ferramentas para que as alterações do MCP se apliquem sem reiniciar a sessão.\n\nHá também um caminho de acompanhamento para conexões tardias: após aguardar um servidor específico, se o status se tornar `connected`, ele re-executa `session.refreshMCPTools(...)` para que as ferramentas recém-disponíveis sejam reconectadas na sessão.\n\n## Saúde, reconexão e comportamento de falha parcial\n\nO comportamento atual do runtime é intencionalmente mínimo:\n\n- **Sem monitor autônomo de saúde** no manager/cliente.\n- **Sem loop de reconexão automática** quando um transporte cai.\n- O manager não se inscreve em `onClose`/`onError` do transporte; o status é baseado no registro.\n- A reconexão é explícita: fluxo de recarga ou invocação direta de `connectServers()`.\n\nOperacionalmente:\n\n- a falha de um servidor não remove ferramentas de servidores saudáveis,\n- falhas de conexão/listagem são isoladas por servidor,\n- cache de ferramentas e atualizações em segundo plano são de melhor esforço (avisos/erros são registrados, sem interrupção forçada).\n\n## Semântica de encerramento\n\n### Encerramento no nível do servidor\n\n`disconnectServer(name)`:\n\n- remove entradas pendentes/metadados de origem,\n- fecha o transporte se conectado,\n- remove as ferramentas `mcp_` daquele servidor do estado do manager.\n\n### Encerramento global\n\n`disconnectAll()`:\n\n- fecha todos os transportes ativos com `Promise.allSettled`,\n- limpa mapas pendentes, origens, conexões e lista de ferramentas do manager.\n\nNa integração atual, o encerramento explícito é usado nos fluxos de comando MCP (para recarga/remoção/desabilitação). Não há um hook separado de descarte automático do manager no próprio caminho de inicialização; os chamadores são responsáveis por invocar os métodos de desconexão do manager quando precisam de um encerramento MCP determinístico.\n\n## Modos de falha e garantias\n\n| Cenário | Comportamento | Falha crítica vs melhor esforço |\n| --- | --- | --- |\n| Descoberta lança exceção (caminho de carregamento de capacidade/configuração) | O loader retorna ferramentas vazias + erro sintético `.mcp.json` | Inicialização da sessão com melhor esforço |\n| Configuração de servidor inválida | Servidor ignorado com entrada de erro de validação | Melhor esforço por servidor |\n| Timeout de conexão/falha na inicialização | Erro do servidor registrado; outros continuam | Melhor esforço por servidor |\n| `tools/list` ainda pendente na inicialização com acerto de cache | Ferramentas diferidas retornadas imediatamente | Inicialização rápida com melhor esforço |\n| `tools/list` ainda pendente na inicialização sem cache | Inicialização aguarda pendentes serem resolvidos | Espera forçada para correção |\n| Falha tardia de carregamento de ferramentas em segundo plano | Registrado após a porta de inicialização | Log com melhor esforço |\n| Transporte interrompido em tempo de execução | Sem reconexão automática; chamadas futuras falham até reconectar/recarregar | Recuperação com melhor esforço via ação manual |\n\n## Superfície da API pública\n\n`src/mcp/index.ts` re-exporta as APIs de loader/manager/cliente para chamadores externos. `src/sdk.ts` expõe `discoverMCPServers()` como um wrapper de conveniência retornando o mesmo formato de resultado do loader.\n\n## Arquivos de implementação\n\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts) — fachada do loader, normalização de erros de descoberta, conversão para `LoadedCustomTool`.\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts) — registros de estado do ciclo de vida, fluxo paralelo de conexão/listagem, atualização/desconexão.\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts) — configuração de transporte, handshake de inicialização, listagem/chamada/desconexão.\n- [`src/mcp/index.ts`](../../packages/coding-agent/src/mcp/index.ts) — exportações da API do módulo MCP.\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — integração de inicialização no registro de sessão/ferramentas.\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts) — descoberta/filtragem/validação de configuração usada pelo manager.\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts) — comportamento em tempo de execução de `MCPTool` e `DeferredMCPTool`.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — reconexão ao vivo via `refreshMCPTools`.\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts) — fluxos interativos de recarga/reconexão.\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — proxy MCP de subagente via conexões do manager pai.\n",
	"pt-br/mcp/mcp-server-tool-authoring.md": "---\ntitle: Criação de Servidores e Ferramentas MCP\ndescription: >-\n  Guia para construir servidores MCP personalizados e registrar ferramentas para\n  o agente de codificação.\nsidebar:\n  order: 4\n  label: Criação de servidores e ferramentas\ni18n:\n  sourceHash: 160e7560ef1f\n  translator: machine\n---\n\n# Criação de servidores e ferramentas MCP\n\nEste documento explica como definições de servidores MCP se tornam ferramentas `mcp_*` invocáveis no coding-agent, e o que os operadores devem esperar quando as configurações são inválidas, duplicadas, desabilitadas ou protegidas por autenticação.\n\n## Arquitetura em visão geral\n\n```text\nConfig sources (.xcsh/.claude/.cursor/.vscode/mcp.json, mcp.json, etc.)\n  -> discovery providers normalize to canonical MCPServer\n  -> capability loader dedupes by server name (higher provider priority wins)\n  -> loadAllMCPConfigs converts to MCPServerConfig + skips enabled:false\n  -> MCPManager connects/listTools (with auth/header/env resolution)\n  -> MCPTool/DeferredMCPTool bridge exposes tools as mcp_<server>_<tool>\n  -> AgentSession.refreshMCPTools replaces live MCP tools immediately\n```\n\n## 1) Modelo de configuração do servidor e validação\n\n`src/mcp/types.ts` define a forma de autoria usada por escritores de configuração MCP e pelo runtime:\n\n- `stdio` (padrão quando `type` está ausente): requer `command`, opcionais `args`, `env`, `cwd`\n- `http`: requer `url`, opcionais `headers`\n- `sse`: requer `url`, opcionais `headers` (mantido para compatibilidade)\n- campos compartilhados: `enabled`, `timeout`, `auth`\n\n`validateServerConfig()` (`src/mcp/config.ts`) valida os requisitos básicos de transporte:\n\n- rejeita configurações que definem tanto `command` quanto `url`\n- requer `command` para stdio\n- requer `url` para http/sse\n- rejeita `type` desconhecido\n\n`config-writer.ts` aplica esta validação para operações de adição/atualização e também valida nomes de servidores:\n\n- não vazio\n- máximo de 100 caracteres\n- apenas `[a-zA-Z0-9_.-]`\n\n### Armadilhas de transporte\n\n- `type` omitido significa stdio. Se você pretendia HTTP/SSE mas omitiu `type`, `command` se torna obrigatório.\n- `sse` ainda é aceito, mas tratado como transporte HTTP internamente (`createHttpTransport`).\n- A validação é estrutural, não de acessibilidade: uma URL sintaticamente válida ainda pode falhar no momento da conexão.\n\n## 2) Descoberta, normalização e precedência\n\n### Descoberta baseada em capacidades\n\n`loadAllMCPConfigs()` (`src/mcp/config.ts`) carrega itens canônicos `MCPServer` via `loadCapability(mcpCapability.id)`.\n\nA camada de capacidades (`src/capability/index.ts`) então:\n\n1. carrega provedores em ordem de prioridade\n2. desduplicar por `server.name` (primeira ocorrência vence = maior prioridade)\n3. valida os itens desduplicados\n\nResultado: nomes de servidores duplicados entre fontes não são mesclados. Uma definição vence; duplicatas de menor prioridade são ocultadas.\n\n### `.mcp.json` e arquivos relacionados\n\nO provedor de fallback dedicado em `src/discovery/mcp-json.ts` lê `mcp.json` e `.mcp.json` da raiz do projeto (baixa prioridade).\n\nNa prática, servidores MCP também vêm de provedores de maior prioridade (por exemplo, `.xcsh/...` nativo e diretórios de configuração específicos de ferramentas). Orientação de autoria:\n\n- Prefira `.xcsh/mcp.json` (projeto) ou `~/.xcsh/mcp.json` (usuário) para controle explícito.\n- Use `mcp.json` / `.mcp.json` na raiz quando precisar de compatibilidade como fallback.\n- Reutilizar o mesmo nome de servidor em múltiplas fontes causa ocultação por precedência, não mesclagem.\n\n### Comportamento de normalização\n\n`convertToLegacyConfig()` (`src/mcp/config.ts`) mapeia `MCPServer` canônico para `MCPServerConfig` de runtime.\n\nComportamento principal:\n\n- transporte inferido como `server.transport ?? (command ? \"stdio\" : url ? \"http\" : \"stdio\")`\n- servidores desabilitados (`enabled === false`) são descartados antes da conexão\n- campos opcionais são preservados quando presentes\n\n### Expansão de variáveis de ambiente durante a descoberta\n\n`mcp-json.ts` expande placeholders de variáveis de ambiente em campos de string com `expandEnvVarsDeep()`:\n\n- suporta `${VAR}` e `${VAR:-default}`\n- valores não resolvidos permanecem como strings literais `${VAR}`\n\n`mcp-json.ts` também realiza verificações de tipo em runtime para JSON de usuário e registra avisos para valores inválidos de `enabled`/`timeout` em vez de falhar completamente o arquivo.\n\n## 3) Autenticação e resolução de valores em runtime\n\n`MCPManager.prepareConfig()`/`#resolveAuthConfig()` (`src/mcp/manager.ts`) é a passagem final pré-conexão.\n\n### Injeção de credenciais OAuth\n\nSe a configuração possui:\n\n```ts\nauth: { type: \"oauth\", credentialId: \"...\" }\n```\n\ne a credencial existe no armazenamento de autenticação:\n\n- `http`/`sse`: injeta header `Authorization: Bearer <access_token>`\n- `stdio`: injeta variável de ambiente `OAUTH_ACCESS_TOKEN`\n\nSe a busca de credencial falhar, o manager registra um aviso e continua com autenticação não resolvida.\n\n### Resolução de valores de headers/env\n\nAntes de conectar, o manager resolve cada valor de header/env via `resolveConfigValue()` (`src/config/resolve-config-value.ts`):\n\n- valor começando com `!` => executa comando shell, usa stdout com trim (em cache)\n- caso contrário, trata o valor como nome de variável de ambiente primeiro (`process.env[name]`), fallback para valor literal\n- valores de comando/env não resolvidos são omitidos do mapa final de headers/env\n\nRessalva operacional: isso significa que um comando/chave de env de segredo digitado incorretamente pode silenciosamente remover aquela entrada de header/env, produzindo falhas 401/403 downstream ou falhas na inicialização do servidor.\n\n## 4) Ponte de ferramentas: MCP -> ferramentas invocáveis pelo agente\n\n`src/mcp/tool-bridge.ts` converte definições de ferramentas MCP em `CustomTool`s.\n\n### Nomenclatura e domínio de colisão\n\nOs nomes de ferramentas são gerados como:\n\n```text\nmcp_<sanitized_server_name>_<sanitized_tool_name>\n```\n\nRegras:\n\n- converte para minúsculas\n- caracteres não `[a-z_]` tornam-se `_`\n- underscores repetidos são colapsados\n- prefixo redundante `<server>_` no nome da ferramenta é removido uma vez\n\nIsso evita muitas colisões, mas não todas. Nomes brutos diferentes ainda podem ser sanitizados para o mesmo identificador (por exemplo `my-server` e `my.server` são sanitizados de forma similar), e a inserção no registro é última-escrita-vence.\n\n### Mapeamento de esquema\n\n`convertSchema()` mantém o JSON Schema do MCP praticamente como está, mas corrige esquemas de objetos sem `properties` com `{}` para compatibilidade com provedores.\n\n### Mapeamento de execução\n\n`MCPTool.execute()` / `DeferredMCPTool.execute()`:\n\n- chama MCP `tools/call`\n- achata conteúdo MCP em texto exibível\n- retorna detalhes estruturados (`serverName`, `mcpToolName`, metadados do provedor)\n- mapeia `isError` reportado pelo servidor para resultado de texto `Error: ...`\n- mapeia falhas de transporte/runtime lançadas para `MCP error: ...`\n- preserva semântica de cancelamento traduzindo AbortError em `ToolAbortError`\n\n## 5) Ciclo de vida do operador: adicionar/editar/remover e atualizações ao vivo\n\nO modo interativo expõe `/mcp` em `src/modes/controllers/mcp-command-controller.ts`.\n\nOperações suportadas:\n\n- `add` (assistente ou adição rápida)\n- `remove` / `rm`\n- `enable` / `disable`\n- `test`\n- `reauth` / `unauth`\n- `reload`\n\nAs escritas de configuração são atômicas (`writeMCPConfigFile`: arquivo temporário + renomeação).\n\nApós as alterações, o controller chama `#reloadMCP()`:\n\n1. `mcpManager.disconnectAll()`\n2. `mcpManager.discoverAndConnect()`\n3. `session.refreshMCPTools(mcpManager.getTools())`\n\n`refreshMCPTools()` substitui todas as entradas `mcp_` do registro e imediatamente reativa o conjunto mais recente de ferramentas MCP, então as alterações entram em vigor sem reiniciar a sessão.\n\n### Diferenças de modo\n\n- **Modo interativo/TUI**: `/mcp` fornece UX dentro do aplicativo (assistente, fluxo OAuth, texto de status de conexão, revinculação imediata em runtime).\n- **Integração SDK/headless**: `discoverAndLoadMCPTools()` (`src/mcp/loader.ts`) retorna ferramentas carregadas + erros por servidor; sem UX do comando `/mcp`.\n\n## 6) Superfícies de erro visíveis ao usuário\n\nStrings de erro comuns que usuários/operadores veem:\n\n- falhas de validação ao adicionar/atualizar:\n  - `Invalid server config: ...`\n  - `Server \"<name>\" already exists in <path>`\n- problemas de argumentos na adição rápida:\n  - `Use either --url or -- <command...>, not both.`\n  - `--token requires --url (HTTP/SSE transport).`\n- falhas de conexão/teste:\n  - `Failed to connect to \"<name>\": <message>`\n  - texto de ajuda sobre timeout sugere aumentar o tempo limite\n  - texto de ajuda de autenticação para `401/403`\n- fluxos de auth/OAuth:\n  - `Authentication required ... OAuth endpoints could not be discovered`\n  - `OAuth flow timed out. Please try again.`\n  - `OAuth authentication failed: ...`\n- uso de servidor desabilitado:\n  - `Server \"<name>\" is disabled. Run /mcp enable <name> first.`\n\nJSON de fonte com problemas na descoberta é geralmente tratado como avisos/logs; os caminhos do config-writer lançam erros explícitos.\n\n## 7) Orientação prática de autoria\n\nPara autoria MCP robusta neste codebase:\n\n1. Mantenha nomes de servidores globalmente únicos em todas as fontes de configuração compatíveis com MCP.\n2. Prefira nomes alfanuméricos/underscore para evitar colisões de nomes sanitizados nos nomes de ferramentas `mcp_*` gerados.\n3. Use `type` explícito para evitar padrões stdio acidentais.\n4. Trate `enabled: false` como desligamento definitivo: o servidor é omitido do conjunto de conexão em runtime.\n5. Para configurações OAuth, armazene um `credentialId` válido; caso contrário, a injeção de autenticação é ignorada.\n6. Se usar resolução de segredos baseada em comando (`!cmd`), verifique se a saída do comando é estável e não vazia.\n\n## Arquivos de implementação\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts)\n- [`src/mcp/config-writer.ts`](../../packages/coding-agent/src/mcp/config-writer.ts)\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts)\n- [`src/discovery/mcp-json.ts`](../../packages/coding-agent/src/discovery/mcp-json.ts)\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/config/resolve-config-value.ts`](../../packages/coding-agent/src/config/resolve-config-value.ts)\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts)\n",
	"pt-br/natives/natives-addon-loader-runtime.md": "---\ntitle: Runtime do Carregador de Addon Nativo\ndescription: >-\n  N-API addon loader runtime with platform detection, fallback strategies, and\n  module resolution.\nsidebar:\n  order: 3\n  label: Carregador de addon\ni18n:\n  sourceHash: 743ea3e32c7c\n  translator: machine\n---\n\n# Runtime do Carregador de Addon Nativo\n\nEste documento analisa em profundidade a camada de carregamento/validação de addon em `@f5-sales-demo/pi-natives`: como `native.ts` decide qual arquivo `.node` carregar, quando a extração de payload embutido é executada e como falhas de inicialização são reportadas.\n\n## Arquivos de implementação\n\n- `packages/natives/src/native.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/package.json`\n\n## Escopo e responsabilidade\n\nAs responsabilidades do carregador/runtime são intencionalmente restritas:\n\n- Construir uma lista de candidatos para nomes de arquivos e diretórios do addon com reconhecimento de plataforma/CPU.\n- Opcionalmente materializar um addon embutido em um diretório de cache versionado por usuário.\n- Tentar candidatos em ordem determinística.\n- Rejeitar addons obsoletos ou incompatíveis via `validateNative` antes de expor os bindings.\n\nFora do escopo aqui: comportamento específico de módulo para grep/text/highlight.\n\n## Entradas do runtime e estado derivado\n\nNa inicialização do módulo (`export const native = loadNative();`), `native.ts` computa o contexto estático:\n\n- **Tag de plataforma**: ``${process.platform}-${process.arch}`` (por exemplo `darwin-arm64`).\n- **Versão do pacote**: de `packages/natives/package.json` (campo `version`).\n- **Diretórios principais**:\n  - `nativeDir`: `packages/natives/native` local ao pacote.\n  - `execDir`: diretório contendo `process.execPath`.\n  - `versionedDir`: `<getNativesDir()>/<packageVersion>`.\n  - Fallback `userDataDir`:\n    - Windows: `%LOCALAPPDATA%/xcsh` (ou `%USERPROFILE%/AppData/Local/xcsh`).\n    - Não-Windows: `~/.local/bin`.\n- **Modo binário compilado** (`isCompiledBinary`): verdadeiro se qualquer uma das condições for atendida:\n  - Variável de ambiente `PI_COMPILED` está definida, ou\n  - `import.meta.url` contém marcadores embutidos do Bun (`$bunfs`, `~BUN`, `%7EBUN`).\n- **Override de variante**: `PI_NATIVE_VARIANT` (apenas `modern`/`baseline`; valores inválidos são ignorados).\n- **Variante selecionada**: override explícito, caso contrário detecção de AVX2 em tempo de execução no x64 (`modern` se AVX2, senão `baseline`).\n\n## Suporte de plataforma e resolução de tag\n\n`SUPPORTED_PLATFORMS` é fixado em:\n\n- `linux-x64`\n- `linux-arm64`\n- `darwin-x64`\n- `darwin-arm64`\n- `win32-x64`\n\nDetalhes de comportamento:\n\n- Plataformas não suportadas não são rejeitadas antecipadamente.\n- O carregador ainda tenta todos os candidatos computados primeiro.\n- Se nada for carregado, ele lança um erro explícito de plataforma não suportada listando as tags suportadas.\n\nIsso preserva diagnósticos úteis para casos de quase-acerto enquanto ainda falha de forma definitiva para alvos verdadeiramente não suportados.\n\n## Seleção de variante (`modern` / `baseline` / padrão)\n\n### Comportamento x64\n\n1. Se `PI_NATIVE_VARIANT` é `modern` ou `baseline`, esse valor prevalece.\n2. Caso contrário, detecta suporte a AVX2:\n   - Linux: escaneia `/proc/cpuinfo` por `avx2`.\n   - macOS: consulta `sysctl` (`machdep.cpu.leaf7_features`, fallback `machdep.cpu.features`).\n   - Windows: executa PowerShell `[System.Runtime.Intrinsics.X86.Avx2]::IsSupported`.\n3. Resultado:\n   - AVX2 disponível -> `modern`\n   - AVX2 indisponível/indetectável -> `baseline`\n\n### Comportamento não-x64\n\n- Nenhuma variante é usada; o carregador permanece com o nome de arquivo padrão (`pi_natives.<platform>-<arch>.node`).\n\n### Construção do nome de arquivo\n\nDado `tag = <platform>-<arch>`:\n\n- Não-x64 ou sem variante: `pi_natives.<tag>.node`\n- x64 + `modern`: tenta na ordem\n  1. `pi_natives.<tag>-modern.node`\n  2. `pi_natives.<tag>-baseline.node` (fallback intencional)\n- x64 + `baseline`: apenas `pi_natives.<tag>-baseline.node`\n\nO `addonLabel` usado nas mensagens de erro finais é `<tag>` ou `<tag> (<variant>)`.\n\n## Construção do caminho de candidatos e ordenação de fallback\n\n`native.ts` constrói pools de candidatos antes de qualquer chamada `require(...)`.\n\n### Candidatos de release\n\nConstruídos a partir da lista de nomes de arquivos resolvida por variante e pesquisados nesta ordem:\n\n- **Runtime não compilado**:\n  1. `<nativeDir>/<filename>`\n  2. `<execDir>/<filename>`\n\n- **Runtime compilado** (`PI_COMPILED` ou marcadores embutidos do Bun):\n  1. `<versionedDir>/<filename>`\n  2. `<userDataDir>/<filename>`\n  3. `<nativeDir>/<filename>`\n  4. `<execDir>/<filename>`\n\n`dedupedCandidates` remove duplicatas preservando a ordem da primeira ocorrência.\n\n### Sequência final do runtime\n\nNo momento do carregamento:\n\n1. Candidato de extração embutida opcional (se produzido) é inserido no início.\n2. Candidatos deduplicados restantes são tentados em ordem.\n3. O primeiro candidato que tanto executa `require(...)` quanto passa em `validateNative(...)` vence.\n\n## Ciclo de vida da extração de addon embutido\n\n`embedded-addon.ts` define uma forma de manifesto gerado:\n\n- `platformTag`\n- `version`\n- `files[]` onde cada entrada possui `variant`, `filename`, `filePath`\n\nO padrão atual no repositório é `embeddedAddon: null`; artefatos compilados podem substituir isso com metadados reais.\n\n### Máquina de estados da extração\n\nA extração (`maybeExtractEmbeddedAddon`) executa apenas quando todas as condições são atendidas:\n\n1. `isCompiledBinary === true`\n2. `embeddedAddon !== null`\n3. `embeddedAddon.platformTag === platformTag`\n4. `embeddedAddon.version === packageVersion`\n5. Um arquivo embutido apropriado para a variante é encontrado\n\nA seleção de arquivo por variante espelha a intenção da variante do runtime:\n\n- Não-x64: preferir `default`, depois o primeiro arquivo disponível.\n- x64 + `modern`: preferir `modern`, fallback para `baseline`.\n- x64 + `baseline`: exigir `baseline`.\n\nComportamento de materialização:\n\n1. Garantir que `<versionedDir>` existe (`mkdirSync(..., { recursive: true })`).\n2. Se `<versionedDir>/<selected filename>` já existe, reutilizá-lo (sem reescrita).\n3. Caso contrário, ler o `filePath` fonte embutido e gravar o arquivo de destino.\n4. Retornar o caminho de destino para a tentativa de carregamento de maior prioridade.\n\nEm caso de falha, a extração não causa crash imediatamente; ela adiciona uma entrada de erro (falha na criação de diretório ou gravação) e o carregador prossegue para a sondagem normal de candidatos.\n\n## Ciclo de vida e transições de estado\n\n```text\nInit\n  -> Compute platform/version/variant/candidate lists\n  -> (Compiled + embedded manifest matches?)\n       yes -> Try extract embedded to versionedDir (record errors, continue)\n       no  -> Skip extraction\n  -> For each runtime candidate in order:\n       require(candidate)\n       -> success: validateNative\n            -> pass: return bindings (READY)\n            -> fail: record error, continue\n       -> failure: record error, continue\n  -> none loaded:\n       if unsupported platform tag -> throw Unsupported platform\n       else -> throw Failed to load (full tried-path diagnostics + hints)\n```\n\n## Verificações de contrato do `validateNative`\n\n`validateNative(bindings, source)` impõe um contrato somente de funções sobre `NativeBindings` na inicialização.\n\nMecânica:\n\n- Para cada nome de exportação requerido, verifica `typeof bindings[name] === \"function\"`.\n- Nomes ausentes são agregados.\n- Se algum estiver ausente, o carregador lança:\n  - caminho do addon fonte,\n  - lista de exportações ausentes,\n  - dica de comando de rebuild.\n\nEsta é uma porta de compatibilidade rígida contra binários obsoletos, builds parciais e desvio de símbolos/nomes.\n\n### Mapeamento da API JS ↔ exportação nativa (porta de validação)\n\n| Nome do binding JS verificado em `validateNative` | Nome esperado da exportação nativa |\n| --- | --- |\n| `grep` | `grep` |\n| `glob` | `glob` |\n| `highlightCode` | `highlightCode` |\n| `executeShell` | `executeShell` |\n| `PtySession` | `PtySession` |\n| `Shell` | `Shell` |\n| `visibleWidth` | `visibleWidth` |\n| `getSystemInfo` | `getSystemInfo` |\n| `getWorkProfile` | `getWorkProfile` |\n| `invalidateFsScanCache` | `invalidateFsScanCache` |\n\nNota: `bindings.ts` declara apenas o membro base `cancelWork(id)`; arquivos `types.ts` dos módulos fazem declaration-merge de símbolos adicionais que `validateNative` impõe.\n\n## Comportamento de falha e diagnósticos\n\n## Plataforma não suportada\n\nSe todos os candidatos falharem e `platformTag` não estiver em `SUPPORTED_PLATFORMS`, o carregador lança:\n\n- `Unsupported platform: <tag>`\n- Lista completa de plataformas suportadas\n- Orientação explícita para reportar problemas\n\n## Sintomas de binário obsoleto / incompatibilidade\n\nSinal típico de incompatibilidade de binário obsoleto:\n\n- `Native addon missing exports (<candidate>). Missing: ...`\n\nCausas comuns:\n\n- Binário `.node` antigo de versão/forma de API anterior do pacote.\n- Artefato de variante incorreto selecionado (para x64).\n- Nova exportação Rust não presente no artefato carregado.\n\nComportamento do carregador:\n\n- Registra falhas de exportações ausentes por candidato.\n- Continua sondando os candidatos restantes.\n- Se nenhum candidato validar, o erro final inclui cada caminho tentado com cada mensagem de falha.\n\n## Falhas de inicialização em binário compilado\n\nNo modo compilado, os diagnósticos finais incluem:\n\n- caminhos esperados do cache versionado (`<versionedDir>/<filename>`),\n- remediação para excluir o `<versionedDir>` obsoleto e reexecutar,\n- comandos `curl` de download direto de release para cada nome de arquivo esperado.\n\n## Falhas de inicialização não compiladas\n\nNo modo normal de pacote/runtime, os diagnósticos finais incluem:\n\n- dica de reinstalação (`bun install @f5-sales-demo/pi-natives`),\n- comando de rebuild local (`bun --cwd=packages/natives run build`),\n- dica opcional de build de variante x64 (`TARGET_VARIANT=baseline|modern ...`).\n\n## Comportamento do runtime\n\n- O carregador sempre usa a cadeia de candidatos de release.\n- Definir `PI_DEV` apenas habilita diagnósticos por candidato no console (`Loaded native addon...` e erros de carregamento).\n",
	"pt-br/natives/natives-architecture.md": "---\ntitle: Arquitetura do Natives\ndescription: >-\n  Arquitetura de addon nativo Rust N-API que conecta TypeScript a operações\n  específicas de plataforma.\nsidebar:\n  order: 1\n  label: Arquitetura\ni18n:\n  sourceHash: d38ed2437bb7\n  translator: machine\n---\n\n# Arquitetura do Natives\n\n`@f5-sales-demo/pi-natives` é uma pilha de três camadas:\n\n1. **Camada de wrapper/API TypeScript** expõe pontos de entrada JS/TS estáveis.\n2. **Camada de carregamento/validação do addon** resolve e valida o binário `.node` para o runtime atual.\n3. **Camada de módulo Rust N-API** implementa primitivas de desempenho crítico exportadas para JS.\n\nEste documento é a base para documentações mais detalhadas a nível de módulo.\n\n## Arquivos de implementação\n\n- `packages/natives/src/index.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `crates/pi-natives/src/lib.rs`\n\n## Camada 1: Camada de wrapper/API TypeScript\n\n`packages/natives/src/index.ts` é o barrel público. Ele agrupa exportações por domínio de capacidade e reexporta wrappers tipados em vez de expor diretamente os bindings N-API brutos.\n\nGrupos de nível superior atuais:\n\n- **Primitivas de busca/texto**: `grep`, `glob`, `text`, `highlight`\n- **Primitivas de execução/processo/terminal**: `shell`, `pty`, `ps`, `keys`\n- **Primitivas de sistema/mídia/conversão**: `image`, `html`, `clipboard`, `system-info`, `work`\n\n`packages/natives/src/bindings.ts` define o contrato de interface base:\n\n- `NativeBindings` começa com membros compartilhados (`cancelWork(id: number)`)\n- bindings específicos de módulo são adicionados por declaration merging a partir do `types.ts` de cada módulo\n- `Cancellable` padroniza opções de timeout e abort-signal para wrappers que expõem cancelamento\n\n**Contrato garantido (voltado para API):** consumidores importam de `@f5-sales-demo/pi-natives` e utilizam wrappers tipados.\n\n**Detalhe de implementação (pode mudar):** declaration merging e layout interno de wrappers (`src/<module>/index.ts`, `src/<module>/types.ts`).\n\n## Camada 2: Carregamento e validação do addon\n\n`packages/natives/src/native.ts` é responsável pela seleção do addon em runtime, extração opcional e validação de exportações.\n\n### Modelo de resolução de candidatos\n\n- A tag de plataforma é `\"${process.platform}-${process.arch}\"`.\n- As tags suportadas atualmente são:\n  - `linux-x64`\n  - `linux-arm64`\n  - `darwin-x64`\n  - `darwin-arm64`\n  - `win32-x64`\n- x64 pode usar variantes de CPU:\n  - `modern` (com suporte a AVX2)\n  - `baseline` (fallback)\n- Não-x64 usa o nome de arquivo padrão (sem sufixo de variante).\n\nEstratégia de nomeação de arquivos:\n\n- Release: `pi_natives.<platform>-<arch>.node`\n- Release com variante x64: `pi_natives.<platform>-<arch>-modern.node` e/ou `...-baseline.node`\n- `PI_DEV` habilita diagnósticos do loader, mas não altera os nomes de arquivo do addon\n\n### Detecção de variante específica por plataforma\n\nPara x64, a seleção de variante utiliza:\n\n- **Linux**: `/proc/cpuinfo`\n- **macOS**: `sysctl machdep.cpu.leaf7_features` / `machdep.cpu.features`\n- **Windows**: verificação via PowerShell para `System.Runtime.Intrinsics.X86.Avx2`\n\n`PI_NATIVE_VARIANT` pode forçar explicitamente `modern` ou `baseline`.\n\n### Modelo de distribuição e extração de binários\n\n`packages/natives/package.json` inclui tanto `src` quanto `native` nos arquivos publicados. O diretório `native/` armazena artefatos pré-compilados por plataforma.\n\nPara binários compilados (marcadores de runtime `PI_COMPILED` ou Bun embedded), o comportamento do loader é:\n\n1. Verificar o caminho de cache do usuário versionado: `<getNativesDir()>/<packageVersion>/...`\n2. Verificar a localização legada de binários compilados:\n   - Windows: `%LOCALAPPDATA%/xcsh` (fallback `%USERPROFILE%/AppData/Local/xcsh`)\n   - não-Windows: `~/.local/bin`\n3. Usar como fallback o diretório `native/` empacotado e candidatos no diretório do executável\n\nSe um manifesto de addon embarcado estiver presente (`embedded-addon.ts` gerado por `scripts/embed-native.ts`), `native.ts` pode materializar o binário embarcado correspondente no diretório de cache versionado antes do carregamento.\n\n### Validação e modos de falha\n\nApós `require(candidate)`, `validateNative(...)` verifica as exportações necessárias (por exemplo `grep`, `glob`, `highlightCode`, `PtySession`, `Shell`, `getSystemInfo`, `getWorkProfile`, `invalidateFsScanCache`).\n\nOs caminhos de falha são explícitos:\n\n- **Tag de plataforma não suportada**: lança erro com a lista de plataformas suportadas\n- **Nenhum candidato carregável**: lança erro com todos os caminhos tentados e dicas de remediação\n- **Exportações ausentes**: lança erro com os nomes exatos ausentes e comando de rebuild\n- **Erros de extração embarcada**: registra falhas de diretório/escrita e as inclui no diagnóstico final de carregamento\n\n**Contrato garantido (voltado para API):** o carregamento do addon ou é bem-sucedido com um conjunto de bindings validado ou falha rapidamente com texto de erro acionável.\n\n**Detalhe de implementação (pode mudar):** ordem exata de busca de candidatos e ordenação do caminho de fallback de binários compilados.\n\n## Camada 3: Camada de módulo Rust N-API\n\n`crates/pi-natives/src/lib.rs` é o módulo de entrada Rust que declara a propriedade dos módulos exportados:\n\n- `clipboard`\n- `fd`\n- `fs_cache`\n- `glob`\n- `glob_util`\n- `grep`\n- `highlight`\n- `html`\n- `image`\n- `keys`\n- `prof`\n- `ps`\n- `pty`\n- `shell`\n- `system_info`\n- `task`\n- `text`\n\nEsses módulos implementam os símbolos N-API consumidos e validados por `native.ts`. Os nomes no nível JS são expostos através dos wrappers TS em `packages/natives/src`.\n\n**Contrato garantido (voltado para API):** as exportações dos módulos Rust devem corresponder aos nomes de binding esperados por `validateNative` e pelos módulos wrapper.\n\n**Detalhe de implementação (pode mudar):** decomposição interna dos módulos Rust e limites de módulos auxiliares (`glob_util`, `task`, etc.).\n\n## Limites de propriedade\n\nNo nível de arquitetura, a propriedade é dividida da seguinte forma:\n\n- **Propriedade do wrapper/API TS (`packages/natives/src`)**\n  - agrupamento de API pública, tipagem de opções e ergonomia JS estável\n  - superfície de cancelamento (`timeoutMs`, `AbortSignal`) exposta aos chamadores\n- **Propriedade do loader (`packages/natives/src/native.ts`)**\n  - seleção de binário em runtime\n  - seleção de variante de CPU e tratamento de override\n  - extração de binários compilados e probing de candidatos\n  - validação rígida das exportações nativas necessárias\n- **Propriedade do Rust (`crates/pi-natives/src`)**\n  - implementação algorítmica e a nível de sistema\n  - comportamento nativo de plataforma e lógica sensível a desempenho\n  - implementação de símbolos N-API que os wrappers TS consomem\n\n## Fluxo em runtime (alto nível)\n\n1. O consumidor importa de `@f5-sales-demo/pi-natives`.\n2. O módulo wrapper chama o binding singleton `native`.\n3. `native.ts` seleciona o binário candidato para plataforma/arch/variante.\n4. Extração opcional de binário embarcado ocorre para distribuições compiladas.\n5. O addon é carregado e o conjunto de exportações é validado.\n6. O wrapper retorna resultados tipados ao chamador.\n\n## Glossário\n\n- **Native addon**: Um binário `.node` carregado via Node-API (N-API).\n- **Tag de plataforma**: Tupla de runtime `platform-arch` (por exemplo `darwin-arm64`).\n- **Variante**: Flavor de build específico para CPU x64 (`modern` AVX2, `baseline` fallback).\n- **Wrapper**: Função/classe TS que fornece API tipada sobre exportações nativas brutas.\n- **Declaration merging**: Técnica TS usada por arquivos `types.ts` de módulos para estender `NativeBindings`.\n- **Modo de binário compilado**: Modo de runtime onde a CLI é empacotada e addons nativos são resolvidos a partir de caminhos extraídos/cache em vez de apenas caminhos locais do pacote.\n- **Addon embarcado**: Metadados de artefatos de build e referências de arquivo gerados em `embedded-addon.ts` para que binários compilados possam extrair payloads `.node` correspondentes.\n- **Gate de validação**: Verificação `validateNative(...)` que rejeita binários obsoletos/incompatíveis que estejam faltando exportações necessárias.\n",
	"pt-br/natives/natives-binding-contract.md": "---\ntitle: Contrato de Binding Nativo (Lado TypeScript)\ndescription: >-\n  Contrato de binding do lado TypeScript para chamadas a funções nativas Rust\n  via N-API.\nsidebar:\n  order: 2\n  label: Contrato de binding\ni18n:\n  sourceHash: 36dc5fed1f0a\n  translator: machine\n---\n\n# Contrato de Binding Nativo (Lado TypeScript)\n\nEste documento define o contrato do lado TypeScript que fica entre os chamadores de `@f5-sales-demo/pi-natives` e o addon N-API carregado.\n\nEle foca em três partes:\n\n1. formato do contrato (`NativeBindings` + augmentação de módulo),\n2. comportamento do wrapper (`src/<module>/index.ts`),\n3. superfície de exportação pública (`src/index.ts`).\n\n## Arquivos de implementação\n\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/system-info/types.ts`\n- `packages/natives/src/system-info/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/work/types.ts`\n- `packages/natives/src/work/index.ts`\n\n## Modelo do contrato\n\n`packages/natives/src/bindings.ts` define o contrato base:\n\n- `NativeBindings` (interface base, atualmente inclui `cancelWork(id: number): void`)\n- `Cancellable` (`timeoutMs?: number`, `signal?: AbortSignal`)\n- `TsFunc<T>` formato de callback usado por callbacks threadsafe do N-API\n\nCada módulo adiciona seus próprios campos por meio de declaration merging:\n\n```ts\n// packages/natives/src/<module>/types.ts\ndeclare module \"../bindings\" {\n interface NativeBindings {\n  grep(options: GrepOptions, onMatch?: TsFunc<GrepMatch>): Promise<GrepResult>;\n }\n}\n```\n\nIsso mantém uma interface de binding agregada sem um arquivo de tipos central monolítico.\n\n## Ciclo de vida do declaration-merging e transições de estado\n\n### 1) Montagem de tipos em tempo de compilação\n\n- `bindings.ts` fornece o símbolo base `NativeBindings`.\n- Cada `src/<module>/types.ts` estende `NativeBindings` por augmentação.\n- `src/native.ts` importa todos os arquivos `./<module>/types` por efeitos colaterais, de modo que o contrato mesclado esteja no escopo onde `NativeBindings` é utilizado.\n\nTransição de estado: **Contrato base** → **Contrato mesclado**.\n\n### 2) Carregamento do addon em tempo de execução e validação\n\n- `src/native.ts` carrega binários `.node` candidatos.\n- O objeto carregado é tratado como `NativeBindings` e imediatamente passado por `validateNative(...)`.\n- `validateNative` verifica as chaves de exportação necessárias usando `typeof bindings[name] === \"function\"`.\n\nTransição de estado: **Objeto addon não confiável** → **Objeto de binding nativo validado** (ou falha crítica).\n\n### 3) Invocação do wrapper\n\n- Os wrappers de módulo em `src/<module>/index.ts` chamam `native.<export>`.\n- Os wrappers adaptam valores padrão e formato de callback (`(err, value)` para padrões de callback somente com valor nas APIs JS).\n- `src/index.ts` re-exporta os wrappers/tipos dos módulos como a API pública do pacote.\n\nTransição de estado: **Bindings brutos validados** → **API pública ergonômica**.\n\n## Responsabilidades dos wrappers\n\nOs wrappers são intencionalmente finos; eles não reimplementam a lógica nativa.\n\nResponsabilidades principais:\n\n- **Normalização/definição de valores padrão dos argumentos**\n  - `glob()` resolve `options.path` para caminho absoluto e define valores padrão para `hidden`, `gitignore`, `recursive`.\n  - `hasMatch()` preenche flags padrão (`ignoreCase`, `multiline`) antes da chamada nativa.\n- **Adaptação de callback**\n  - `grep()`, `glob()`, `executeShell()` convertem `TsFunc<T>` (`error, value`) em callback do usuário recebendo apenas valores bem-sucedidos.\n- **Comportamento de ambiente ou política em torno das chamadas nativas**\n  - O wrapper de clipboard adiciona tratamento para OSC52/Termux/headless e trata copy como melhor esforço.\n- **Nomeação pública e curadoria de re-exportação**\n  - `searchContent()` mapeia para a exportação nativa `search`.\n\n## Organização da superfície de exportação pública\n\n`packages/natives/src/index.ts` é o barrel público canônico. Ele agrupa exportações por domínio de capacidade:\n\n- Busca/texto: `grep`, `glob`, `text`, `highlight`\n- Execução/processo/terminal: `shell`, `pty`, `ps`, `keys`\n- Sistema/mídia/conversão: `image`, `html`, `clipboard`, `system-info`, `work`\n\nRegra para mantenedores: se um wrapper não é re-exportado de `src/index.ts`, ele não faz parte da superfície pública pretendida do pacote.\n\n## Mapeamento API JS ↔ exportação nativa (representativo)\n\nO lado Rust usa nomes de exportação N-API (tipicamente da conversão `#[napi]` snake_case -> camelCase, com aliases explícitos ocasionais) que devem corresponder a estas chaves de binding.\n\n| Categoria | API JS pública (wrapper) | Chave de binding nativo | Tipo de retorno | Assíncrono? |\n|---|---|---|---|---|\n| Grep | `grep(options, onMatch?)` | `grep` | `Promise<GrepResult>` | Sim |\n| Grep | `searchContent(content, options)` | `search` | `SearchResult` | Não |\n| Grep | `hasMatch(content, pattern, opts?)` | `hasMatch` | `boolean` | Não |\n| Grep | `fuzzyFind(options)` | `fuzzyFind` | `Promise<FuzzyFindResult>` | Sim |\n| Glob | `glob(options, onMatch?)` | `glob` | `Promise<GlobResult>` | Sim |\n| Glob | `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `void` | Não |\n| Shell | `executeShell(options, onChunk?)` | `executeShell` | `Promise<ShellExecuteResult>` | Sim |\n| Shell | `Shell` | `Shell` | construtor de classe | N/A |\n| PTY | `PtySession` | `PtySession` | construtor de classe | N/A |\n| Text | `truncateToWidth(...)` | `truncateToWidth` | `string` | Não |\n| Text | `sliceWithWidth(...)` | `sliceWithWidth` | `SliceWithWidthResult` | Não |\n| Text | `visibleWidth(text)` | `visibleWidth` | `number` | Não |\n| Highlight | `highlightCode(code, lang, colors)` | `highlightCode` | `string` | Não |\n| HTML | `htmlToMarkdown(html, options?)` | `htmlToMarkdown` | `Promise<string>` | Sim |\n| System | `getSystemInfo()` | `getSystemInfo` | `SystemInfo` | Não |\n| Work | `getWorkProfile(lastSeconds)` | `getWorkProfile` | `WorkProfile` | Não |\n| Process | `killTree(pid, signal)` | `killTree` | `number` | Não |\n| Process | `listDescendants(pid)` | `listDescendants` | `number[]` | Não |\n| Clipboard | `copyToClipboard(text)` | `copyToClipboard` | `Promise<void>` (comportamento de melhor esforço do wrapper) | Sim |\n| Clipboard | `readImageFromClipboard()` | `readImageFromClipboard` | `Promise<ClipboardImage \\| null>` | Sim |\n| Keys | `parseKey(data, kittyProtocolActive)` | `parseKey` | `string \\| null` | Não |\n\n## Diferenças de contrato síncrono vs assíncrono\n\nO contrato mistura APIs síncronas e assíncronas; os wrappers preservam o estilo de chamada nativa em vez de forçar um único modelo:\n\n- **Exportações assíncronas baseadas em Promise** para I/O ou trabalho de longa duração (`grep`, `glob`, `htmlToMarkdown`, `executeShell`, clipboard, operações de imagem).\n- **Exportações síncronas** para transformações/parsers determinísticos em memória (`search`, `hasMatch`, highlighting, largura/fatiamento de texto, parsing de teclas, consultas de processos).\n- **Exportações de construtor** para objetos de runtime com estado (`Shell`, `PtySession`, `PhotonImage`).\n\nImplicação para mantenedores: alterar síncrono ↔ assíncrono para uma exportação existente é uma mudança de API e contrato que quebra compatibilidade em wrappers e chamadores.\n\n## Padrões de tipagem de objetos e enums\n\n### Padrões de objetos (objetos JS estilo `#[napi(object)]`)\n\nO TS modela valores nativos em formato de objeto como interfaces, por exemplo:\n\n- `GrepResult`, `SearchResult`, `GlobResult`\n- `SystemInfo`, `WorkProfile`\n- `ClipboardImage`, `ParsedKittyResult`\n\nEstes são contratos estruturais em tempo de compilação; a correção do formato em tempo de execução é de responsabilidade da implementação nativa.\n\n### Padrões de enum\n\nEnums nativos numéricos são representados como valores `const enum` no TS:\n\n- `FileType` (`1=file`, `2=dir`, `3=symlink`)\n- `ImageFormat` (`0=PNG`, `1=JPEG`, `2=WEBP`, `3=GIF`)\n- `SamplingFilter`, `Ellipsis`, `KeyEventType`\n\nOs chamadores veem membros nomeados do enum; a fronteira de binding passa números.\n\n## Como incompatibilidades são detectadas\n\nA detecção de incompatibilidades acontece em duas camadas:\n\n1. **Verificações de contrato TypeScript em tempo de compilação**\n   - Os wrappers chamam `native.<name>` contra o `NativeBindings` mesclado.\n   - Chaves de binding ausentes/renomeadas quebram a verificação de tipos do TS nos wrappers.\n\n2. **Validação em tempo de execução em `validateNative`**\n   - Após o carregamento, `native.ts` verifica as exportações necessárias e lança erro se alguma estiver faltando.\n   - A mensagem de erro inclui as chaves ausentes e instruções de rebuild.\n\nIsso captura a comum divergência de binário desatualizado: wrapper/tipo existe mas o `.node` carregado não possui a exportação.\n\n## Comportamento de falha e ressalvas\n\n### Falhas de carregamento/validação (falhas críticas)\n\n- Falha no carregamento do addon ou plataforma não suportada lança exceção durante a inicialização do módulo em `native.ts`.\n- Exportações necessárias ausentes lançam exceção antes que os wrappers sejam utilizáveis.\n\nEfeito: o pacote falha rapidamente em vez de postergar a falha para a primeira chamada.\n\n### Diferenças de comportamento no nível do wrapper\n\n- Alguns wrappers intencionalmente suavizam falhas (`copyToClipboard` é melhor esforço e suprime falhas nativas).\n- Callbacks de streaming ignoram payloads de erro do callback e apenas encaminham eventos de valores bem-sucedidos.\n\n### Ressalvas no nível de tipos (runtime mais rigoroso que o TS)\n\n- Campos opcionais do TS não garantem validade semântica; a camada nativa ainda pode rejeitar valores malformados.\n- A tipagem `const enum` não impede que valores numéricos fora do intervalo sejam passados por chamadores não tipados em tempo de execução.\n- `validateNative` verifica apenas presença/natureza de função das exportações necessárias, não compatibilidade profunda de formato de argumentos/retorno.\n- `bindings.ts` inclui `cancelWork(id)` na interface base, mas a lista atual de validação em tempo de execução não impõe essa chave.\n\n## Checklist do mantenedor para alterações de binding\n\nAo adicionar/alterar uma exportação, atualize todos os seguintes:\n\n1. `src/<module>/types.ts` (augmentação + tipos de contrato)\n2. `src/<module>/index.ts` (comportamento do wrapper)\n3. Importações em `src/native.ts` para os tipos do módulo (se for módulo novo)\n4. Verificações de exportação necessária em `validateNative`\n5. Re-exportações públicas em `src/index.ts`\n\nPular qualquer etapa cria divergência em tempo de compilação ou falha em tempo de carregamento no runtime.\n",
	"pt-br/natives/natives-build-release-debugging.md": "---\ntitle: 'Runbook de Build, Release e Depuração de Nativos'\ndescription: >-\n  Runbook de build, release e depuração do addon nativo em Rust para múltiplas\n  plataformas.\nsidebar:\n  order: 8\n  label: 'Build, release e depuração'\ni18n:\n  sourceHash: efe47aa5b466\n  translator: machine\n---\n\n# Runbook de Build, Release e Depuração de Nativos\n\nEste runbook descreve como o pipeline de build do `@f5-sales-demo/pi-natives` produz addons `.node`, como as distribuições compiladas os carregam e como depurar falhas de carregador/build.\n\nSegue os termos de arquitetura de `docs/natives-architecture.md`:\n\n- **produção de artefatos em tempo de build** (`scripts/build-native.ts`)\n- **geração de manifesto de addon embutido** (`scripts/embed-native.ts`)\n- **carregamento de addon em runtime + porta de validação** (`src/native.ts`)\n\n## Arquivos de implementação\n\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `packages/natives/src/native.ts`\n- `crates/pi-natives/Cargo.toml`\n\n## Visão geral do pipeline de build\n\n### 1) Entrypoints de build\n\nScripts em `packages/natives/package.json`:\n\n- `bun scripts/build-native.ts` (`build`) → build de release\n- `bun scripts/build-native.ts --dev` (`dev:native`) → build de perfil debug/dev (mesma nomenclatura de saída)\n- `bun scripts/embed-native.ts` (`embed:native`) → gerar `src/embedded-addon.ts` a partir dos arquivos compilados\n\n### 2) Build do artefato Rust\n\n`build-native.ts` executa o Cargo em `crates/pi-natives`:\n\n- comando base: `cargo build`\n- modo release adiciona `--release`, a menos que `--dev` seja passado\n- alvo de cross-compilação adiciona `--target <CROSS_TARGET>`\n\n`crates/pi-natives/Cargo.toml` declara `crate-type = [\"cdylib\"]`, portanto o Cargo emite uma biblioteca compartilhada (`.so`/`.dylib`/`.dll`) que é então copiada/renomeada para um nome de arquivo de addon `.node`.\n\n### 3) Descoberta e instalação do artefato\n\nApós a conclusão do Cargo, `build-native.ts` verifica os diretórios de saída candidatos na seguinte ordem:\n\n1. `${CARGO_TARGET_DIR}` (se definido)\n2. `<repo>/target`\n3. `crates/pi-natives/target`\n\nPara cada raiz, verifica os diretórios de perfil:\n\n- cross build: `<root>/<crossTarget>/<profile>` depois `<root>/<profile>`\n- build nativo: `<root>/<profile>`\n\nEm seguida, procura por um dos seguintes:\n\n- `libpi_natives.so`\n- `libpi_natives.dylib`\n- `pi_natives.dll`\n- `libpi_natives.dll`\n\nQuando encontrado, instala atomicamente em `packages/natives/native/` com semântica de arquivo temporário + renomeação (o fallback do Windows trata explicitamente falhas de substituição de DLL bloqueada).\n\n## Modelo de alvo/variante e convenções de nomenclatura\n\n## Tag de plataforma\n\nTanto o build quanto o runtime utilizam a tag de plataforma:\n\n`<platform>-<arch>` (exemplo: `darwin-arm64`, `linux-x64`)\n\n## Modelo de variante (somente x64)\n\nx64 suporta variantes de CPU:\n\n- `modern` (caminho com suporte a AVX2)\n- `baseline` (fallback)\n\nArquiteturas diferentes de x64 utilizam um único artefato padrão (sem sufixo de variante).\n\n### Nomes de arquivo de saída\n\nBuilds de release:\n\n- x64: `pi_natives.<platform>-<arch>-modern.node` ou `...-baseline.node`\n- não-x64: `pi_natives.<platform>-<arch>.node`\n\nBuild de dev (`--dev`):\n\n- Utiliza flags de perfil debug, mas mantém a nomenclatura de saída padrão com tag de plataforma\n\nOrdem dos candidatos do carregador em `native.ts`:\n\n- candidatos de release\n- o modo compilado antepõe candidatos extraídos/em cache antes dos arquivos locais do pacote\n\n## Flags de ambiente e opções de build\n\n## Flags de runtime\n\n- `PI_DEV` (comportamento do carregador): ativa diagnósticos do carregador\n- `PI_NATIVE_VARIANT` (comportamento do carregador, somente x64): força a seleção de `modern` ou `baseline` em runtime\n- `PI_COMPILED` (comportamento do carregador): ativa o comportamento de candidato/extração para binário compilado\n\n## Flags/opções em tempo de build\n\n- `--dev` (argumento do script): compila perfil debug\n- `CROSS_TARGET`: passado ao `--target` do Cargo\n- `TARGET_PLATFORM`: sobrescreve a nomenclatura da tag de plataforma na saída\n- `TARGET_ARCH`: sobrescreve a nomenclatura de arquitetura na saída\n- `TARGET_VARIANT` (somente x64): força `modern` ou `baseline` para o nome do arquivo de saída e política de RUSTFLAGS\n- `CARGO_TARGET_DIR`: raiz adicional ao buscar saídas do Cargo\n- `RUSTFLAGS`:\n  - se não definido e sem cross-compilação, o script define:\n    - modern: `-C target-cpu=x86-64-v3`\n    - baseline: `-C target-cpu=x86-64-v2`\n    - não-x64 / sem variante: `-C target-cpu=native`\n  - se já definido, o script não sobrescreve\n\n## Transições de estado/ciclo de vida do build\n\n### Ciclo de vida do build (`build-native.ts`)\n\n1. **Init**: analisa args/env (`--dev`, sobrescritas de alvo, flags de cross)\n2. **Resolução de variante**:\n   - não-x64 → sem variante\n   - x64 + `TARGET_VARIANT` → variante explícita\n   - cross-build x64 sem `TARGET_VARIANT` → erro fatal\n   - build local x64 sem sobrescrita → detecta AVX2 do host\n3. **Compilação**: executa o Cargo com perfil/alvo resolvidos\n4. **Localização do artefato**: varre raízes de alvo/diretórios de perfil/nomes de biblioteca\n5. **Instalação**: copia + renomeação atômica em `packages/natives/native`\n6. **Conclusão**: addon de saída pronto para candidatos do carregador\n\nAs saídas por falha ocorrem em qualquer etapa com texto de erro explícito (variante inválida, falha no build do cargo, biblioteca de saída ausente, falha de instalação/renomeação).\n\n### Ciclo de vida do embed (`embed-native.ts`)\n\n1. **Init**: computa a tag de plataforma a partir de `TARGET_PLATFORM`/`TARGET_ARCH` ou dos valores do host\n2. **Conjunto de candidatos**:\n   - x64 espera ambos `modern` e `baseline`\n   - não-x64 espera um arquivo padrão\n3. **Valida disponibilidade** em `packages/natives/native`\n4. **Gera manifesto** (`src/embedded-addon.ts`) com imports `file` do Bun e versão do pacote\n5. **Extração em runtime pronta** para o modo compilado\n\n`--reset` ignora a validação e escreve um stub de manifesto nulo (`embeddedAddon = null`).\n\n## Fluxo de desenvolvimento local vs. comportamento compilado/distribuído\n\n## Fluxo de trabalho de desenvolvimento local\n\nLoop local típico:\n\n1. Compilar o addon:\n   - release: `bun --cwd=packages/natives run build`\n   - perfil debug: `bun --cwd=packages/natives run dev:native`\n2. Definir `PI_DEV=1` ao testar diagnósticos do carregador\n3. O carregador em `native.ts` resolve candidatos em `native/` local ao pacote (e fallback de diretório do executável)\n4. `validateNative` impõe a compatibilidade de exports antes que os wrappers utilizem o binding\n\n## Fluxo de trabalho de binário compilado/distribuído\n\nNo modo compilado (`PI_COMPILED` ou marcadores embutidos do Bun):\n\n1. O carregador computa o diretório de cache versionado: `<getNativesDir()>/<packageVersion>` (operacionalmente `~/.xcsh/natives/<version>`)\n2. Se o manifesto embutido corresponder à plataforma+versão atual, o carregador pode extrair o arquivo embutido selecionado para esse diretório versionado\n3. A ordem dos candidatos em runtime inclui:\n   - diretório de cache versionado\n   - diretório legado de binário compilado (`%LOCALAPPDATA%/xcsh` no Windows, `~/.local/bin` em outros sistemas)\n   - diretórios do pacote/executável\n4. O primeiro addon carregado com sucesso ainda deve passar por `validateNative`\n\nÉ por isso que as expectativas de empacotamento e do carregador em runtime devem estar alinhadas: nomes de arquivo, tags de plataforma e símbolos exportados devem corresponder ao que `native.ts` sonda e valida.\n\n## Mapeamento de API JS ↔ export Rust (subconjunto da porta de validação)\n\n`native.ts` exige que esses exports visíveis em JS existam no addon carregado. Eles mapeiam para exports N-API em Rust em `crates/pi-natives/src`:\n\n| Nome JS exigido por `validateNative` | Declaração de export Rust | Arquivo-fonte Rust |\n| --- | --- | --- |\n| `glob` | `#[napi] pub fn glob(...)` | `crates/pi-natives/src/glob.rs` |\n| `grep` | `#[napi] pub fn grep(...)` | `crates/pi-natives/src/grep.rs` |\n| `search` | `#[napi] pub fn search(...)` | `crates/pi-natives/src/grep.rs` |\n| `highlightCode` | `#[napi] pub fn highlight_code(...)` | `crates/pi-natives/src/highlight.rs` |\n| `getSystemInfo` | `#[napi] pub fn get_system_info(...)` | `crates/pi-natives/src/system_info.rs` |\n| `getWorkProfile` | `#[napi] pub fn get_work_profile(...)` (export em camelCase) | `crates/pi-natives/src/prof.rs` |\n| `invalidateFsScanCache` | `#[napi] pub fn invalidate_fs_scan_cache(...)` | `crates/pi-natives/src/fs_cache.rs` |\n\nSe algum símbolo obrigatório estiver ausente, o carregador falha imediatamente com uma dica de rebuild.\n\n## Comportamento em caso de falha e diagnósticos\n\n## Falhas em tempo de build\n\n- Configuração de variante inválida:\n  - `TARGET_VARIANT` definido em não-x64 → erro imediato\n  - cross-build x64 sem `TARGET_VARIANT` explícito → erro imediato\n- Falha no build do Cargo:\n  - o script exibe saída não-zero e stderr\n- Artefato não encontrado:\n  - o script imprime cada diretório de perfil verificado\n- Falha na instalação:\n  - mensagem explícita; no Windows inclui dica sobre arquivo bloqueado\n\n## Falhas do carregador em runtime (`native.ts`)\n\n- Tag de plataforma não suportada:\n  - lança exceção com a lista de plataformas suportadas\n- Nenhum candidato pôde ser carregado:\n  - lança exceção com a lista completa de erros dos candidatos e dicas de correção específicas ao modo\n- Exports ausentes:\n  - lança exceção com os nomes exatos dos símbolos ausentes e o comando de rebuild\n- Problemas de extração embutida:\n  - erros de mkdir/escrita na extração são registrados e incluídos no diagnóstico final\n\n## Matriz de solução de problemas\n\n| Sintoma | Causa provável | Como verificar | Correção |\n| --- | --- | --- | --- |\n| `Native addon missing exports ... Missing: <name>` | Binário `.node` desatualizado, nome do export Rust incompatível ou binário errado carregado | Execute com `PI_DEV=1` para ver o caminho carregado; inspecione a lista de exports desse arquivo | Recompile com `build`; certifique-se de que o nome do export `#[napi]` em Rust (ou alias explícito quando necessário) corresponda à chave JS; remova arquivos versionados/em cache desatualizados |\n| Máquina x64 carrega baseline quando modern é esperado | `PI_NATIVE_VARIANT=baseline`, AVX2 não detectado ou apenas arquivo baseline presente | Verifique `PI_NATIVE_VARIANT`; inspecione `native/` em busca do arquivo `-modern` | Compile a variante modern (`TARGET_VARIANT=modern ... build`) e certifique-se de que o arquivo seja distribuído |\n| Cross-build produz binário inutilizável/rotulado incorretamente | Incompatibilidade entre `CROSS_TARGET` e `TARGET_PLATFORM`/`TARGET_ARCH`, ou `TARGET_VARIANT` ausente para x64 | Confirme a tupla de env e o nome do arquivo de saída | Reexecute com valores de env consistentes e `TARGET_VARIANT` x64 explícito |\n| Binário compilado falha após atualização | Cache extraído desatualizado (`~/.xcsh/natives/<versão-antiga-ou-incompatível>`) ou incompatibilidade no manifesto embutido | Inspecione o diretório de nativos versionado e a lista de erros do carregador | Exclua o cache de nativos versionado para a versão do pacote e reexecute; regenere o manifesto embutido durante o empacotamento |\n| O carregador verifica muitos caminhos e nenhum funciona | Incompatibilidade de plataforma ou artefato de release ausente em `native/` do pacote | Verifique `platformTag` versus o(s) nome(s) real(is) do(s) arquivo(s) | Certifique-se de que o nome do arquivo compilado corresponda exatamente à convenção `pi_natives.<platform>-<arch>(-variant).node` e que o pacote inclua `native/` |\n| `embed:native` falha com \"Incomplete native addons\" | Arquivos de variante necessários não compilados antes do embedding | Verifique a lista de esperados vs. encontrados no texto de erro | Compile os arquivos necessários primeiro (x64: ambos modern+baseline; não-x64: padrão) e então reexecute `embed:native` |\n\n## Comandos operacionais\n\n```bash\n# Artefato de release para o host atual\nbun --cwd=packages/natives run build\n\n# Build de artefato com perfil debug\nbun --cwd=packages/natives run dev:native\n\n# Compilar variantes x64 explícitas\nTARGET_VARIANT=modern bun --cwd=packages/natives run build\nTARGET_VARIANT=baseline bun --cwd=packages/natives run build\n\n# Gerar manifesto de addon embutido a partir dos arquivos nativos compilados\nbun --cwd=packages/natives run embed:native\n\n# Redefinir manifesto embutido para stub nulo\nbun --cwd=packages/natives run embed:native -- --reset\n```\n",
	"pt-br/natives/natives-media-system-utils.md": "---\ntitle: Utilitários Nativos de Mídia e Sistema\ndescription: >-\n  Utilitários nativos de processamento de mídia para capturas de tela,\n  manipulação de imagens e informações do sistema.\nsidebar:\n  order: 7\n  label: Utilitários de mídia e sistema\ni18n:\n  sourceHash: 430898c177bc\n  translator: machine\n---\n\n# Utilitários nativos de mídia + sistema\n\nEste documento é um aprofundamento de subsistema para a camada de **primitivos de sistema/mídia/conversão** descrita em [`docs/natives-architecture.md`](./natives-architecture.md): `image`, `html`, `clipboard` e perfilamento `work`.\n\n## Arquivos de implementação\n\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/prof.rs`\n- `crates/pi-natives/src/task.rs`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/work/index.ts`\n- `packages/natives/src/work/types.ts`\n\n> Nota: não existe `crates/pi-natives/src/work.rs`; o perfilamento de trabalho é implementado em `prof.rs` e alimentado pela instrumentação em `task.rs`.\n\n## Mapeamento de API TS ↔ exportação/módulo Rust\n\n| Exportação TS (packages/natives)            | Exportação N-API Rust                                                   | Módulo Rust                           |\n| ------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------- |\n| `PhotonImage.parse(bytes)`                  | `PhotonImage::parse`                                                     | `image.rs`                            |\n| `PhotonImage#resize(width, height, filter)` | `PhotonImage::resize`                                                    | `image.rs`                            |\n| `PhotonImage#encode(format, quality)`       | `PhotonImage::encode`                                                    | `image.rs`                            |\n| `htmlToMarkdown(html, options)`             | `html_to_markdown`                                                       | `html.rs`                             |\n| `copyToClipboard(text)`                     | `copy_to_clipboard` + lógica de fallback TS                              | `clipboard.rs` + `clipboard/index.ts` |\n| `readImageFromClipboard()`                  | `read_image_from_clipboard`                                              | `clipboard.rs`                        |\n| `getWorkProfile(lastSeconds)`               | `get_work_profile`                                                      | `prof.rs`                             |\n\n## Limites de formato de dados e conversões\n\n### Imagem (`image`)\n\n- **Limite de entrada JS**: `Uint8Array` com bytes de imagem codificada.\n- **Limite de decodificação Rust**: os bytes são copiados para `Vec<u8>`, o formato é inferido com `ImageReader::with_guessed_format()`, então decodificado para `DynamicImage`.\n- **Estado em memória**: `PhotonImage` armazena `Arc<DynamicImage>`.\n- **Limite de saída**: `encode(format, quality)` retorna `Promise<Uint8Array>` (`Vec<u8>` no Rust).\n\nIDs de formato são numéricos:\n\n- `0`: PNG\n- `1`: JPEG\n- `2`: WebP (codificador sem perdas)\n- `3`: GIF\n\nRestrições:\n\n- `quality` é utilizado apenas para JPEG.\n- PNG/WebP/GIF ignoram `quality`.\n- IDs de formato não suportados falham (`Invalid image format: <id>`).\n\n### Conversão HTML (`html`)\n\n- **Limite de entrada JS**: `string` HTML + objeto opcional `{ cleanContent?: boolean; skipImages?: boolean }`.\n- **Limite de conversão Rust**: a entrada `String` é convertida por `html_to_markdown_rs::convert`.\n- **Limite de saída**: `string` Markdown.\n\nComportamento da conversão:\n\n- `cleanContent` tem valor padrão `false`.\n- Quando `cleanContent=true`, o pré-processamento é habilitado com `PreprocessingPreset::Aggressive` e flags de remoção forçada para navegação/formulários.\n- `skipImages` tem valor padrão `false`.\n\n### Área de transferência (`clipboard`)\n\n- **Caminho de texto**:\n  - O TS primeiro emite OSC 52 (`\\x1b]52;c;<base64>\\x07`) quando stdout é um TTY.\n  - O mesmo texto é então tentado via API nativa de área de transferência (`native.copyToClipboard`) como melhor esforço.\n  - No Termux, o TS tenta `termux-clipboard-set` primeiro.\n- **Caminho de leitura de imagem**:\n  - O Rust lê a imagem bruta do `arboard`.\n  - O Rust recodifica para bytes PNG (crate `image`), retorna `{ data: Uint8Array, mimeType: \"image/png\" }`.\n  - O TS retorna `null` antecipadamente no Termux ou em sessões Linux sem servidor de exibição (`DISPLAY`/`WAYLAND_DISPLAY` ausentes).\n\n### Perfilamento de trabalho (`work`)\n\n- **Limite de coleta**: amostras de perfilamento são produzidas por guardas `profile_region(tag)` em `task::blocking` e `task::future`.\n- **Formato de armazenamento**: buffer circular de tamanho fixo (`MAX_SAMPLES = 10_000`) armazenando caminho de pilha + duração (`μs`) + timestamp (`μs desde início do processo`).\n- **Limite de saída**: `getWorkProfile(lastSeconds)` retorna objeto:\n  - `folded`: texto de pilha empilhada (entrada para flamegraph)\n  - `summary`: tabela resumo em markdown\n  - `svg`: SVG de flamegraph opcional\n  - `totalMs`, `sampleCount`\n\n## Ciclo de vida e transições de estado\n\n### Ciclo de vida da imagem\n\n1. `PhotonImage.parse(bytes)` agenda uma tarefa de decodificação bloqueante (`image.decode`).\n2. Em caso de sucesso, um handle nativo `PhotonImage` existe no JS.\n3. `resize(...)` cria um novo handle nativo (`image.resize`), handles antigos e novos podem coexistir.\n4. `encode(...)` materializa bytes (`image.encode`) sem alterar as dimensões da imagem.\n\nTransições de falha:\n\n- Falha na detecção de formato/decodificação rejeita a promise de parse.\n- Falha na codificação rejeita a promise de encode.\n- ID de formato inválido rejeita a promise de encode.\n\n### Ciclo de vida do HTML\n\n1. `htmlToMarkdown(html, options)` agenda uma tarefa de conversão bloqueante.\n2. A conversão executa com opções padrão (`cleanContent=false`, `skipImages=false`) a menos que especificado.\n3. Retorna string markdown ou rejeita.\n\nTransições de falha:\n\n- Falha no conversor retorna promise rejeitada (`Conversion error: ...`).\n\n### Ciclo de vida da área de transferência\n\n`copyToClipboard(text)` é intencionalmente melhor esforço e multi-caminho:\n\n1. Se TTY: tenta escrita OSC 52 (payload base64).\n2. Tenta comando Termux quando `TERMUX_VERSION` está definido.\n3. Tenta cópia de texto nativa via `arboard`.\n4. Suprime erros na camada TS.\n\n`readImageFromClipboard()` possui rigor diferente por estágio:\n\n1. O TS bloqueia fortemente contextos de runtime não suportados (Termux/Linux headless) para `null`.\n2. A leitura `arboard` do Rust executa apenas quando o TS permite.\n3. `ContentNotAvailable` é mapeado para `null`.\n4. Outros erros do Rust rejeitam.\n\n### Ciclo de vida do perfilamento de trabalho\n\n1. Sem início explícito: o perfilamento está sempre ativo quando os helpers de tarefa executam.\n2. Cada escopo de tarefa instrumentado registra uma amostra no drop do guard.\n3. Amostras sobrescrevem as entradas mais antigas após a capacidade do buffer ser atingida.\n4. `getWorkProfile(lastSeconds)` lê uma janela de tempo e deriva artefatos folded/summary/svg.\n\nTransições de falha:\n\n- Falha na geração de SVG é falha suave (`svg: null`), enquanto folded e summary ainda retornam.\n- Janela de amostra vazia retorna dados folded vazios e `svg: null`, não um erro.\n\n## Operações não suportadas e propagação de erros\n\n### Imagem\n\n- Entrada de decodificação não suportada ou bytes corrompidos: falha estrita (rejeição de promise).\n- ID de formato de codificação não suportado: falha estrita.\n- Sem caminho de fallback de melhor esforço no wrapper TS.\n\n### HTML\n\n- Erros de conversão são falhas estritas (rejeição).\n- Omissão de opções é padronização de melhor esforço, não falha.\n\n### Área de transferência\n\n- Cópia de texto é melhor esforço na camada TS: falhas operacionais são suprimidas.\n- Leitura de imagem distingue \"sem imagem\" (`null`) de falha operacional (rejeição).\n- Termux/Linux headless são tratados como contextos não suportados para leitura de imagem (`null`).\n\n### Perfilamento de trabalho\n\n- A recuperação é estrita para a chamada de função em si, mas a geração de artefatos é parcialmente melhor esforço (`svg` anulável).\n- Truncamento do buffer é comportamento esperado (buffer circular), não um bug de perda de dados.\n\n## Ressalvas de plataforma\n\n- **Texto da área de transferência**: OSC 52 depende do suporte do terminal; o acesso nativo à área de transferência depende do ambiente de desktop/sessão.\n- **Leitura de imagem da área de transferência**: bloqueada no TS para Termux e Linux sem servidor de exibição.\n",
	"pt-br/natives/natives-rust-task-cancellation.md": "---\ntitle: Execução e Cancelamento de Tarefas Nativas em Rust\ndescription: >-\n  Modelo de execução de tarefas assíncronas em Rust com cancelamento cooperativo\n  e semântica de limpeza.\nsidebar:\n  order: 5\n  label: Cancelamento de tarefas\ni18n:\n  sourceHash: 0fbf45c6d463\n  translator: machine\n---\n\n# Execução e cancelamento de tarefas nativas em Rust (`pi-natives`)\n\nEste documento descreve como `crates/pi-natives` agenda trabalho nativo e como o cancelamento flui das opções JS (`timeoutMs`, `AbortSignal`) para a execução em Rust.\n\n## Arquivos de implementação\n\n- `crates/pi-natives/src/task.rs`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/fd.rs`\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/ps.rs`\n\n## Primitivas centrais (`task.rs`)\n\n`task.rs` define três peças centrais:\n\n1. `task::blocking(tag, cancel_token, work)`\n   - Encapsula `napi::AsyncTask` / `Task`.\n   - `compute()` executa em threads de trabalho do libuv (para chamadas de sistema CPU-bound ou bloqueantes/síncronas).\n   - Retorna uma JS `Promise<T>`.\n\n2. `task::future(env, tag, work)`\n   - Encapsula `env.spawn_future(...)`.\n   - Executa trabalho assíncrono no runtime Tokio.\n   - Retorna `PromiseRaw<'env, T>`.\n\n3. `CancelToken` / `AbortToken` / `AbortReason`\n   - `CancelToken::new(timeout_ms, signal)` combina deadline + `AbortSignal` opcional.\n   - `CancelToken::heartbeat()` é cancelamento cooperativo para loops bloqueantes.\n   - `CancelToken::wait()` é espera assíncrona de cancelamento (`Signal` / `Timeout` / `User` Ctrl-C).\n   - `AbortToken` permite que código externo solicite abort (`abort(reason)`).\n\n## `blocking` vs `future`: modelo de execução e seleção\n\n### Use `task::blocking`\n\nUse quando o trabalho é intensivo em CPU ou fundamentalmente síncrono/bloqueante:\n\n- varredura de regex/arquivos (`grep`, `glob`, `fuzzy_find`)\n- internos de loop PTY síncrono (`run_pty_sync` via `spawn_blocking`)\n- conversões de clipboard/imagem/html\n\nComportamento:\n\n- A closure de trabalho recebe um `CancelToken` clonado.\n- O cancelamento só é observado onde o código verifica `ct.heartbeat()?`.\n- `Err(...)` na closure rejeita a promise JS.\n\n### Use `task::future`\n\nUse quando o trabalho precisa fazer `await` em operações assíncronas:\n\n- orquestração de sessão shell (`shell.run`, `executeShell`)\n- corrida de tarefas (`tokio::select!`) entre conclusão e cancelamento\n\nComportamento:\n\n- A future pode competir conclusão normal contra `ct.wait()`.\n- No caminho de cancelamento, implementações assíncronas tipicamente propagam o cancelamento para subsistemas internos (ex.: `tokio_util::CancellationToken`) e opcionalmente forçam abort após timeout de tolerância.\n\n## Mapeamento API JS ↔ export Rust (relevante para task/cancel)\n\n| API voltada para JS | Export Rust (`#[napi]`) | Agendador | Conexão de cancelamento |\n|---|---|---|---|\n| `grep(options, onMatch?)` | `grep` | `task::blocking(\"grep\", ct, ...)` | `CancelToken::new(options.timeoutMs, options.signal)` + `ct.heartbeat()` |\n| `glob(options, onMatch?)` | `glob` | `task::blocking(\"glob\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` no loop de filtro |\n| `fuzzyFind(options)` | `fuzzy_find` | `task::blocking(\"fuzzy_find\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` no loop de pontuação |\n| `shell.run(options, onChunk?)` | `Shell::run` | `task::future(env, \"shell.run\", ...)` | `ct.wait()` competindo contra task de execução; faz ponte com Tokio `CancellationToken` |\n| `executeShell(options, onChunk?)` | `execute_shell` | `task::future(env, \"shell.execute\", ...)` | mesmo que acima |\n| `pty.start(options, onChunk?)` | `PtySession::start` | `task::future(env, \"pty.start\", ...)` + `spawn_blocking` interno | `CancelToken` verificado no loop PTY síncrono via `heartbeat()` |\n| `htmlToMarkdown(html, options?)` | `html_to_markdown` | `task::blocking(\"html_to_markdown\", (), ...)` | nenhum (token `()`) |\n| `PhotonImage.parse/encode/resize` | `PhotonImage::{parse,encode,resize}` | `task::blocking(...)` | nenhum (token `()`) |\n| `copyToClipboard/readImageFromClipboard` | `copy_to_clipboard` / `read_image_from_clipboard` | `task::blocking(...)` | nenhum (token `()`) |\n\n`text.rs` e `ps.rs` atualmente não utilizam `task::blocking`/`task::future` e, portanto, não participam deste caminho de cancelamento.\n\n## Ciclo de vida do cancelamento e transições de estado\n\n### Ciclo de vida do `CancelToken`\n\n`CancelToken` é cooperativo e com estado:\n\n```text\nCreated\n  ├─ no signal + no timeout  -> passive token (never aborts unless externally emplaced)\n  ├─ signal registered        -> waits for AbortSignal callback\n  └─ deadline set             -> timeout check becomes active\n\nRunning\n  ├─ heartbeat()/wait() sees signal   -> AbortReason::Signal\n  ├─ heartbeat()/wait() sees deadline -> AbortReason::Timeout\n  ├─ wait() sees Ctrl-C               -> AbortReason::User\n  └─ no abort                         -> continue\n\nAborted (terminal)\n  └─ first abort reason wins (atomic flag + notifier)\n```\n\n### Cancelamento antes do início vs durante a execução\n\n- **Antes do início / antes da primeira verificação de cancelamento**:\n  - Usuários de `task::future` que competem em `ct.wait()` podem resolver o cancelamento imediatamente ao entrar no `select!`.\n  - Usuários de `task::blocking` só observam o cancelamento quando o código da closure alcança `heartbeat()`. Se a closure não fizer heartbeat cedo, o cancelamento é atrasado.\n\n- **Durante a execução**:\n  - `blocking`: o próximo `heartbeat()` retorna `Err(\"Aborted: ...\")`.\n  - `future`: o branch `ct.wait()` vence o `select!`, então o código cancela a maquinaria assíncrona subordinada (para shell: cancela o token Tokio, espera até 2s, então aborta a task).\n\n## Expectativas de heartbeat para loops de longa duração\n\n`heartbeat()` deve executar em cadência previsível em loops com conjuntos de trabalho ilimitados ou grandes.\n\nPadrões observados:\n\n- `glob::filter_entries`: verifica cada entrada antes de filtrar/corresponder.\n- `fd::score_entries`: verifica cada candidato varrido.\n- `grep_sync`: verificação explícita de cancelamento antes da fase pesada de busca, mais chamadas ao fs-cache que também recebem o token.\n- `run_pty_sync`: verifica a cada tick do loop (cadência de ~16ms de sleep) e mata o processo filho ao cancelar.\n\nRegra prática: nenhum loop sobre entrada de tamanho externo deve exceder um intervalo curto limitado sem um heartbeat.\n\n## Comportamento de falha e propagação de erros para JS\n\n### Tarefas bloqueantes\n\nCaminho de erro:\n\n1. A closure retorna `Err(napi::Error)` (incluindo abort de `heartbeat()`).\n2. `Task::compute()` retorna `Err`.\n3. `AsyncTask` rejeita a promise JS.\n\nStrings de erro típicas:\n\n- `Aborted: Timeout`\n- `Aborted: Signal`\n- erros de domínio (`Failed to decode image: ...`, `Conversion error: ...`, etc.)\n\n### Tarefas future\n\nCaminho de erro:\n\n1. O corpo assíncrono retorna `Err(napi::Error)` ou falha de join é mapeada (`... task failed: {err}`).\n2. A promise gerada por `task::future` rejeita.\n3. Algumas APIs intencionalmente retornam resultados de cancelamento estruturados em vez de rejeição (`ShellRunResult`/`ShellExecuteResult` com flags `cancelled`/`timed_out` e `exit_code: None`).\n\n### Divisão de reporte de cancelamento\n\n- **Abort como erro**: a maioria dos exports bloqueantes usando `heartbeat()?`.\n- **Abort como resultado tipado**: APIs estilo shell/pty de comando que modelam cancelamento em structs de resultado.\n\nEscolha um modelo por API e documente-o explicitamente.\n\n## Armadilhas comuns\n\n1. **Heartbeat ausente em loops bloqueantes**\n   - Sintoma: timeout/signal parece ser ignorado até o loop terminar.\n   - Correção: adicione `ct.heartbeat()?` no topo do loop e antes de passos caros por item.\n\n2. **Seções longas não-canceláveis**\n   - Sintoma: picos de latência de cancelamento durante uma única chamada grande (decode, sort, compressão, etc.).\n   - Correção: divida o trabalho em pedaços com limites de heartbeat; se impossível, documente a latência.\n\n3. **Bloqueando o executor assíncrono**\n   - Sintoma: API assíncrona trava quando código pesado de sync executa diretamente na future.\n   - Correção: mova blocos CPU/sync para `task::blocking` ou `tokio::task::spawn_blocking`.\n\n4. **Semântica de cancelamento inconsistente**\n   - Sintoma: uma API rejeita no cancelamento, outra resolve com flags, confundindo os chamadores.\n   - Correção: padronize por domínio e mantenha os docs dos wrappers alinhados.\n\n5. **Esquecendo a ponte de cancelamento em tarefas assíncronas aninhadas**\n   - Sintoma: token externo é cancelado mas tasks internas de leitura/subprocesso continuam executando.\n   - Correção: faça ponte do cancelamento para o token/signal interno e aplique timeout de tolerância + fallback de abort forçado.\n\n## Checklist para novos exports canceláveis\n\n1. Classifique o trabalho corretamente:\n   - CPU-bound ou bloqueante síncrono -> `task::blocking`\n   - I/O assíncrono / orquestração com `await` -> `task::future`\n\n2. Exponha entradas de cancelamento quando necessário:\n   - inclua `timeoutMs` e `signal` nas options `#[napi(object)]`\n   - crie `let ct = task::CancelToken::new(timeout_ms, signal);`\n\n3. Conecte o cancelamento por todas as camadas:\n   - loops bloqueantes: `ct.heartbeat()?` em intervalos estáveis\n   - orquestração assíncrona: compita com `ct.wait()` e cancele sub-tasks/tokens\n\n4. Decida o contrato de cancelamento:\n   - rejeitar a promise com erro de abort, ou\n   - resolver com resultado tipado `{ cancelled, timedOut, ... }`\n   - mantenha este contrato consistente para a família de APIs\n\n5. Propague falhas com contexto:\n   - mapeie erros via `Error::from_reason(format!(\"...: {err}\"))`\n   - inclua prefixos específicos de estágio (`spawn`, `decode`, `wait`, etc.)\n\n6. Trate cancelamento antes do início e durante a execução:\n   - verificação/await de cancelamento deve acontecer antes do corpo custoso e durante execução longa\n\n7. Valide que não há uso indevido do executor:\n   - nenhum trabalho síncrono longo diretamente dentro de futures assíncronas sem wrapper `spawn_blocking`/blocking task\n",
	"pt-br/natives/natives-shell-pty-process.md": "---\ntitle: 'Internos de Shell, PTY, Processo e Teclas do Natives'\ndescription: >-\n  Execução de shell, gerenciamento de PTY, ciclo de vida de processos e\n  tratamento de eventos de teclas na camada nativa.\nsidebar:\n  order: 4\n  label: 'Shell, PTY e processo'\ni18n:\n  sourceHash: 00ea95614c6a\n  translator: machine\n---\n\n# Internos de Shell, PTY, Processo e Teclas do Natives\n\nEste documento cobre as **primitivas de execução/processo/terminal** em `@f5-sales-demo/pi-natives`: `shell`, `pty`, `ps` e `keys`, utilizando os termos de arquitetura de `docs/natives-architecture.md`.\n\n## Arquivos de implementação\n\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/shell/windows.rs` (apenas Windows)\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/ps.rs`\n- `crates/pi-natives/src/keys.rs`\n- `crates/pi-natives/src/task.rs` (comportamento de cancelamento compartilhado usado por shell/pty)\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/bindings.ts`\n\n## Responsabilidade das camadas\n\n- **Camada de wrapper/API TS** (`packages/natives/src/*`): pontos de entrada tipados, superfície de cancelamento (`timeoutMs`, `AbortSignal`) e ergonomia JS.\n- **Camada do módulo Rust N-API** (`crates/pi-natives/src/*`): execução de processos shell/PTY, travessia/terminação de árvore de processos e análise de sequências de teclas.\n- **Portão de validação** (`native.ts`, nível de arquitetura): garante que as exportações necessárias (`Shell`, `executeShell`, `PtySession`, `killTree`, `listDescendants`, auxiliares de teclas) existam antes que os wrappers sejam utilizados.\n\n## Subsistema Shell (`shell`)\n\n### Modelo de API\n\nDois modos de execução são expostos:\n\n1. **Execução única** via `executeShell(options, onChunk?)`.\n2. **Sessão persistente** via `new Shell(options?)` e depois `shell.run(...)` repetidamente.\n\nAmbos transmitem a saída através de um callback threadsafe e retornam `{ exitCode?, cancelled, timedOut }`.\n\n### Criação de sessão e modelo de ambiente\n\nO Rust cria `brush_core::Shell` com:\n\n- modo não interativo,\n- `do_not_inherit_env: true`,\n- reconstrução explícita do ambiente a partir do env do host,\n- lista de exclusão para variáveis sensíveis ao shell (`PS1`, `PWD`, `SHLVL`, exportações de funções bash, etc.).\n\nComportamento do ambiente da sessão:\n\n- `ShellOptions.sessionEnv` é aplicado uma vez na criação da sessão.\n- `ShellRunOptions.env` tem escopo de comando (`EnvironmentScope::Command`) e é removido após cada execução.\n- `PATH` é mesclado de forma especial no Windows com deduplicação case-insensitive.\n\nEnriquecimento de PATH apenas no Windows (`shell/windows.rs`): caminhos descobertos do Git-for-Windows (`cmd`, `bin`, `usr/bin`) são adicionados se presentes e ainda não incluídos.\n\n### Ciclo de vida em tempo de execução e transições de estado\n\nO shell persistente (`Shell.run`) utiliza esta máquina de estados:\n\n- **Idle/Não inicializado**: `session: None`.\n- **Em execução**: o primeiro `run()` cria a sessão de forma lazy, armazena o token `current_abort`, executa o comando.\n- **Concluído + keepalive**: se o fluxo de controle da execução é `Normal`, `current_abort` é limpo e a sessão é reutilizada.\n- **Concluído + teardown**: se o fluxo de controle está relacionado a loop/script/saída do shell (`BreakLoop`, `ContinueLoop`, `ReturnFromFunctionOrScript`, `ExitShell`), a sessão é descartada (`session: None`).\n- **Cancelado/Tempo esgotado**: a tarefa de execução é cancelada, espera graciosa (2s), depois abort forçado; a sessão é descartada.\n- **Erro**: a sessão é descartada.\n\nO shell de execução única (`executeShell`) sempre cria e descarta uma sessão nova por chamada.\n\n### Comportamento de streaming/saída\n\n- Stdout/stderr são roteados para um pipe compartilhado e lidos concorrentemente.\n- O leitor decodifica UTF-8 incrementalmente; sequências de bytes inválidas emitem chunks de substituição `U+FFFD`.\n- Após a conclusão do processo, o dreno de saída possui guardas de idle/máximo (`250ms` idle, `2s` máximo) para evitar travamento em jobs em background mantendo descritores abertos.\n\n### Cancelamento, timeout e jobs em background\n\n- `CancelToken` é construído a partir de `timeoutMs` e `AbortSignal` opcional.\n- No cancelamento/timeout, o token de cancelamento do shell é acionado, depois a tarefa recebe uma janela graciosa de 2s antes do abort forçado.\n- Se o cancelamento ocorre, jobs em background são terminados (`TERM`, depois `KILL` com atraso) usando metadados de jobs do brush.\n\nComportamento de `Shell.abort()`:\n\n- aborta apenas o comando atualmente em execução para aquela instância de `Shell`,\n- retorna sucesso sem efeito quando nada está em execução.\n\n### Comportamento de falha\n\nErros comumente expostos incluem:\n\n- falhas de inicialização da sessão (`Failed to initialize shell`),\n- erros de cwd (`Failed to set cwd`),\n- falhas de set/pop de env,\n- falhas de fonte de snapshot,\n- falhas de criação/clone de pipe,\n- falha de execução (`Shell execution failed: ...`),\n- falhas do wrapper de tarefa (`Shell execution task failed: ...`).\n\nFlags de cancelamento em nível de resultado:\n\n- timeout -> `exitCode: undefined`, `timedOut: true`.\n- sinal de abort -> `exitCode: undefined`, `cancelled: true`.\n\n## Subsistema PTY (`pty`)\n\n### Modelo de API\n\n`new PtySession()` expõe:\n\n- `start(options, onChunk?) -> Promise<{ exitCode?, cancelled, timedOut }>`\n- `write(data)`\n- `resize(cols, rows)`\n- `kill()`\n\n### Ciclo de vida em tempo de execução e transições de estado\n\nMáquina de estados do `PtySession`:\n\n- **Idle**: `core: None`.\n- **Reservado**: `start()` instala o canal de controle sincronamente (`core: Some`) antes do trabalho assíncrono começar, então `write/resize/kill` se tornam imediatamente válidos.\n- **Em execução**: loop PTY bloqueante trata estado do filho, eventos do leitor, heartbeat de cancelamento e mensagens de controle.\n- **Terminal fechado**: saída do filho + conclusão do leitor.\n- **Finalizado**: `core` é sempre redefinido para `None` após a conclusão da tarefa start (sucesso ou erro).\n\nGuarda de concorrência:\n\n- iniciar enquanto já está em execução retorna `PTY session already running`.\n\n### Padrões de spawn/attach/write/read/terminate\n\n- PTY é aberto via `portable_pty::native_pty_system().openpty(...)`.\n- O comando atualmente executa como `sh -lc <command>` com `cwd` e substituições de env opcionais.\n- `write()` envia bytes brutos para o stdin do PTY.\n- `resize()` limita as dimensões (`cols 20..400`, `rows 5..200`) e chama o redimensionamento do master.\n- `kill()` marca a execução como cancelada e mata o processo filho.\n\nCaminho de saída:\n\n- thread de leitura dedicada lê o stream master,\n- decodificação UTF-8 incremental com substituição `U+FFFD` em bytes inválidos,\n- chunks encaminhados através de callback threadsafe N-API.\n\n### Semântica de cancelamento e timeout\n\n- `timeoutMs` e `AbortSignal` alimentam um `CancelToken`.\n- O loop chama `ct.heartbeat()` periodicamente; abort aciona kill do filho.\n- A classificação de timeout é baseada em string (substring `\"Timeout\"` no erro do heartbeat).\n\n### Comportamento de falha\n\nSuperfícies de erro incluem:\n\n- falha de alocação/abertura do PTY,\n- falha de spawn do PTY,\n- falha de aquisição do writer/reader,\n- falhas de status/espera do filho,\n- envenenamento de lock,\n- desconexão do canal de controle (`PTY session is no longer available`).\n\nFalhas de chamadas de controle quando não está em execução:\n\n- `write/resize/kill` retornam `PTY session is not running`.\n\n## Subsistema de árvore de processos (`ps`)\n\n### Modelo de API\n\n- `killTree(pid, signal) -> number`\n- `listDescendants(pid) -> number[]`\n\nO wrapper TS também registra a integração nativa de kill-tree nos utilitários compartilhados via `setNativeKillTree(native.killTree)`.\n\n### Implementação específica por plataforma\n\n- **Linux**: lê recursivamente `/proc/<pid>/task/<pid>/children`.\n- **macOS**: usa `libproc` `proc_listchildpids`.\n- **Windows**: captura snapshot da tabela de processos com `CreateToolhelp32Snapshot`, constrói mapa pai->filhos, termina com `OpenProcess(PROCESS_TERMINATE)` + `TerminateProcess`.\n\n### Comportamento do kill-tree\n\n- Descendentes são coletados recursivamente.\n- A ordem de kill é de baixo para cima (descendentes mais profundos primeiro) para reduzir re-parenteamento de órfãos.\n- O pid raiz é morto por último.\n- O valor de retorno é a contagem de terminações bem-sucedidas.\n\nComportamento de sinal:\n\n- POSIX: o `signal` fornecido é passado para `kill`.\n- Windows: `signal` é ignorado; a terminação é incondicional.\n\n### Comportamento de falha\n\nEste módulo é intencionalmente não-lançador na superfície de API:\n\n- branches de árvore de processos ausentes/inacessíveis são ignorados,\n- falhas de kill por pid são contadas como malsucedidas (não como erros),\n- ausência de resultado tipicamente produz `[]` de `listDescendants` e `0` de `killTree`.\n\n## Subsistema de análise de teclas (`keys`)\n\n### Modelo de API\n\nAuxiliares expostos:\n\n- `parseKey(data, kittyProtocolActive)`\n- `matchesKey(data, keyId, kittyProtocolActive)`\n- `parseKittySequence(data)`\n- `matchesKittySequence(data, expectedCodepoint, expectedModifier)`\n- `matchesLegacySequence(data, keyName)`\n\n### Modelo de análise\n\nO parser combina:\n\n- mapeamentos diretos de byte único (`enter`, `tab`, `ctrl+<letra>`, ASCII imprimível),\n- busca de sequência de escape legada O(1) (mapa PHF),\n- análise xterm `modifyOtherKeys`,\n- análise do protocolo Kitty (`CSI u`, `CSI ~`, `CSI 1;...<letra>`),\n- normalização para IDs de tecla (`ctrl+c`, `shift+tab`, `pageUp`, `f5`, etc.).\n\nTratamento de modificadores:\n\n- apenas bits de shift/alt/ctrl são comparados para correspondência de teclas,\n- bits de lock são mascarados antes das comparações.\n\nComportamento de layout:\n\n- o fallback de layout base é intencionalmente restrito para que layouts remapeados não criem correspondências falsas para letras/símbolos ASCII.\n\n### Comportamento de falha\n\n- Sequências não reconhecidas ou inválidas produzem `null` nas funções de análise.\n- Funções de correspondência retornam `false` em caso de falha de análise ou incompatibilidade.\n- Nenhuma superfície de erro lançado para entrada de tecla malformada.\n\n## Mapeamento API do wrapper JS ↔ exportação Rust\n\n### Shell + PTY + Processo\n\n| API do wrapper TS | Exportação Rust N-API | Notas |\n|---|---|---|\n| `executeShell(options, onChunk?)` | `executeShell` (`execute_shell`) | Execução de shell única |\n| `new Shell(options?)` | classe `Shell` | Sessão de shell persistente |\n| `shell.run(options, onChunk?)` | `Shell::run` | Reutiliza sessão em fluxo de controle keepalive |\n| `shell.abort()` | `Shell::abort` | Aborta execução ativa daquela instância de shell |\n| `new PtySession()` | classe `PtySession` | Sessão PTY com estado |\n| `pty.start(options, onChunk?)` | `PtySession::start` | Execução PTY interativa |\n| `pty.write(data)` | `PtySession::write` | Passagem direta de stdin bruto |\n| `pty.resize(cols, rows)` | `PtySession::resize` | Dimensões de terminal limitadas |\n| `pty.kill()` | `PtySession::kill` | Força o kill do filho PTY ativo |\n| `killTree(pid, signal)` | `killTree` (`kill_tree`) | Terminação de árvore de processos filhos primeiro |\n| `listDescendants(pid)` | `listDescendants` (`list_descendants`) | Listagem recursiva de descendentes |\n\n### Teclas\n\n| API do wrapper TS | Exportação Rust N-API | Notas |\n|---|---|---|\n| `matchesKittySequence(data, cp, mod)` | `matchesKittySequence` (`matches_kitty_sequence`) | Correspondência de codepoint+modificador Kitty |\n| `parseKey(data, kittyProtocolActive)` | `parseKey` (`parse_key`) | Parser de key-id normalizado |\n| `matchesLegacySequence(data, keyName)` | `matchesLegacySequence` (`matches_legacy_sequence`) | Verificação exata no mapa de sequência legada |\n| `parseKittySequence(data)` | `parseKittySequence` (`parse_kitty_sequence`) | Resultado de análise Kitty estruturado |\n| `matchesKey(data, keyId, kittyProtocolActive)` | `matchesKey` (`matches_key`) | Correspondedor de tecla de alto nível |\n\n## Notas sobre limpeza de sessões abandonadas e finalização\n\n- **Sessão persistente de Shell**: se uma execução é cancelada/tempo esgotado/erro/fluxo de controle não-keepalive, o Rust descarta explicitamente o estado interno da sessão. Execuções normais bem-sucedidas mantêm a sessão para reutilização.\n- **Sessão PTY**: `core` é sempre limpo após `start()` terminar, incluindo caminhos de falha.\n- **Nenhum contrato explícito de kill dirigido por finalizador JS** é exposto pelos wrappers; a limpeza está primariamente vinculada aos caminhos de conclusão/cancelamento da execução. Chamadores devem usar `timeoutMs`, `AbortSignal`, `shell.abort()` ou `pty.kill()` para teardown determinístico.\n",
	"pt-br/natives/natives-text-search-pipeline.md": "---\ntitle: Pipeline Nativa de Texto e Busca\ndescription: >-\n  Pipeline nativa de busca de texto com indexação de conteúdo de arquivos\n  baseada em grep, glob e ripgrep.\nsidebar:\n  order: 6\n  label: Texto e busca pipeline\ni18n:\n  sourceHash: 0e93462fdd12\n  translator: machine\n---\n\n# Pipeline Nativa de Texto/Busca\n\nEste documento mapeia a superfície de texto/busca do `@f5-sales-demo/pi-natives` (`grep`, `glob`, `text`, `highlight`) desde os wrappers TypeScript até as exportações Rust N-API e de volta aos objetos de resultado JS.\n\nA terminologia segue `docs/natives-architecture.md`:\n\n- **Wrapper**: API TS em `packages/natives/src/*`\n- **Camada de módulo Rust**: exportações N-API em `crates/pi-natives/src/*`\n- **Cache de varredura compartilhado**: cache de entradas de diretório baseado em `fs_cache` usado pelos fluxos de descoberta/busca\n\n## Arquivos de implementação\n\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/glob_util.rs`\n- `crates/pi-natives/src/fs_cache.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/highlight.rs`\n- `crates/pi-natives/src/fd.rs`\n\n## Mapeamento API JS ↔ exportação Rust\n\n| API wrapper JS | Exportação Rust (`#[napi]`, snake_case -> camelCase) | Módulo Rust |\n| --- | --- | --- |\n| `grep(options, onMatch?)` | `grep` | `grep.rs` |\n| `searchContent(content, options)` | `search` | `grep.rs` |\n| `hasMatch(content, pattern, options?)` | `hasMatch` | `grep.rs` |\n| `fuzzyFind(options)` | `fuzzyFind` | `fd.rs` |\n| `glob(options, onMatch?)` | `glob` | `glob.rs` |\n| `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `fs_cache.rs` |\n| `wrapTextWithAnsi(text, width)` | `wrapTextWithAnsi` | `text.rs` |\n| `truncateToWidth(text, maxWidth, ellipsis, pad)` | `truncateToWidth` | `text.rs` |\n| `sliceWithWidth(line, startCol, length, strict?)` | `sliceWithWidth` | `text.rs` |\n| `extractSegments(line, beforeEnd, afterStart, afterLen, strictAfter)` | `extractSegments` | `text.rs` |\n| `sanitizeText(text)` | `sanitizeText` | `text.rs` |\n| `visibleWidth(text)` | `visibleWidth` | `text.rs` |\n| `highlightCode(code, lang, colors)` | `highlightCode` | `highlight.rs` |\n| `supportsLanguage(lang)` | `supportsLanguage` | `highlight.rs` |\n| `getSupportedLanguages()` | `getSupportedLanguages` | `highlight.rs` |\n\n## Visão geral da pipeline por subsistema\n\n## 1) Busca regex (`grep`, `searchContent`, `hasMatch`)\n\n### Fluxo de entrada/opções\n\n1. O wrapper TS encaminha as opções para o nativo:\n   - `grep/index.ts` passa `options` praticamente sem alterações e converte o callback de `(match) => void` para o formato de callback threadsafe do napi `(err, match)`.\n   - `searchContent` e `hasMatch` passam string/`Uint8Array` diretamente.\n2. Structs de opção em Rust em `grep.rs` deserializam campos camelCase (`ignoreCase`, `maxCount`, `contextBefore`, `contextAfter`, `maxColumns`, `timeoutMs`).\n3. `grep` cria `CancelToken` a partir de `timeoutMs` + `AbortSignal` e executa dentro de `task::blocking(\"grep\", ...)`.\n\n### Ramificações de execução\n\n- **Ramificação em memória (utilitário puro)**\n  - `search` → `search_sync` → `run_search` nos bytes de conteúdo fornecidos.\n  - Sem varredura de sistema de arquivos, sem `fs_cache`.\n- **Ramificação de arquivo único (dependente do sistema de arquivos)**\n  - `grep_sync` resolve o caminho, verifica se os metadados são de arquivo, processa até `MAX_FILE_BYTES` por arquivo (`4 MiB`) através do matcher ripgrep.\n- **Ramificação de diretório (dependente do sistema de arquivos)**\n  - Consulta opcional ao cache via `fs_cache::get_or_scan` quando `cache: true`.\n  - Varredura nova via `fs_cache::force_rescan` quando `cache: false`.\n  - Reverificação opcional de resultado vazio quando a idade do cache excede `empty_recheck_ms()`.\n  - Filtragem de entradas: somente arquivos + filtro glob opcional (`glob_util`) + filtro de tipo opcional (`js`, `ts`, `rust`, etc.).\n\n### Semântica de busca/coleta\n\n- Motor de regex: `grep_regex::RegexMatcherBuilder` com `ignoreCase` e `multiline`.\n- Resolução de contexto:\n  - `contextBefore/contextAfter` substituem o legado `context`.\n  - Modos sem conteúdo zeram a coleta de contexto.\n- Modos de saída:\n  - `content` => um `GrepMatch` por ocorrência.\n  - `count` e `filesWithMatches` ambos mapeiam para entradas estilo contagem (`lineNumber=0`, `line=\"\"`, `matchCount` definido).\n- Limites:\n  - `offset` global e `maxCount` aplicados em todos os arquivos.\n  - O caminho paralelo é usado apenas quando `maxCount` não está definido e `offset == 0`; caso contrário, o caminho sequencial preserva a semântica determinística de offset/limite global.\n\n### Formatação do resultado de volta para JS\n\n- Os campos de `SearchResult`/`GrepResult` do Rust mapeiam para tipos TS via conversão de campos de objeto N-API.\n- Contadores são limitados a `u32` antes de cruzar a fronteira N-API.\n- Booleanos opcionais são omitidos a menos que sejam true em alguns caminhos (`limitReached`).\n- O callback de streaming recebe cada `GrepMatch` formatado (entrada de conteúdo ou contagem).\n\n### Comportamento em caso de falha\n\n- `searchContent` retorna `SearchResult.error` para falhas de regex/busca em vez de lançar exceção.\n- `grep` rejeita em erros graves (caminho inválido, glob/regex inválido, timeout/abort de cancelamento).\n- `hasMatch` retorna `Result<bool>` e lança exceção em padrão inválido/erros de decodificação UTF-8.\n- Erros de abertura/busca de arquivo em varreduras multi-arquivo são ignorados por arquivo; a varredura continua.\n\n### Tratamento de regex malformado\n\n`grep.rs` sanitiza chaves antes da compilação de regex:\n\n- Chaves semelhantes a repetição inválidas são escapadas (`{`/`}` -> `\\{`/`\\}`) quando não podem formar `{N}`, `{N,}`, `{N,M}`.\n- Isso impede que fragmentos comuns de template literal (por exemplo `${platform}`) falhem como repetição malformada.\n- Sintaxe de regex inválida remanescente ainda retorna um erro de regex.\n\n## 2) Descoberta de arquivos (`glob`) e busca fuzzy de caminhos (`fuzzyFind`)\n\n`glob` e `fuzzyFind` compartilham varreduras do `fs_cache`; a lógica de correspondência difere.\n\n### Fluxo do `glob`\n\n1. Wrapper TS (`glob/index.ts`):\n   - `path.resolve(options.path)`.\n   - Padrões: `pattern=\"*\"`, `hidden=false`, `gitignore=true`, `recursive=true`.\n2. O Rust `glob` constrói `GlobConfig` e compila o padrão via `glob_util::compile_glob`.\n3. Fonte de entradas:\n   - `cache=true` => `get_or_scan` + `force_rescan` opcional para vazio obsoleto.\n   - `cache=false` => `force_rescan(..., store=false)` (somente novo).\n4. Filtragem:\n   - Sempre ignora `.git`.\n   - Ignora `node_modules` a menos que solicitado (`includeNodeModules` ou padrão mencionando node_modules).\n   - Aplica correspondência glob.\n   - Aplica filtro de tipo de arquivo; filtros `file/dir` de symlink resolvem metadados do alvo.\n5. Ordenação opcional por mtime decrescente (`sortByMtime`) antes de truncar para `maxResults`.\n\n### Fluxo do `fuzzyFind` (implementado em `fd.rs`)\n\n1. O wrapper TS é exportado do módulo `grep`, mas a implementação Rust reside em `fd.rs`.\n2. Fonte de varredura compartilhada do `fs_cache` com a mesma divisão cache/sem-cache e política de reverificação de vazio obsoleto.\n3. Pontuação:\n   - pontuação fuzzy baseada em exata / começa-com / contém / subsequência\n   - caminho de pontuação normalizado por separador/pontuação\n   - bônus de diretório e desempate determinístico (`score desc`, depois `path asc`)\n4. Entradas de symlink são excluídas dos resultados fuzzy.\n\n### Comportamento em caso de falha\n\n- Padrão glob inválido => erro de `glob_util::compile_glob`.\n- A raiz da busca deve ser um diretório existente (`resolve_search_path`), caso contrário erro.\n- Cancelamentos/timeouts propagam como erros de abort via verificações de `CancelToken::heartbeat()` nos loops.\n\n### Tratamento de glob malformado\n\n`glob_util::build_glob_pattern` é tolerante:\n\n- Normaliza `\\` para `/`.\n- Auto-prefixa padrões recursivos simples com `**/` quando `recursive=true`.\n- Auto-fecha grupos de alternação `{...` desbalanceados antes da compilação.\n\n## 3) Ciclo de vida do cache/varredura compartilhado (`fs_cache`)\n\n`fs_cache` armazena resultados de varredura como entradas relativas normalizadas (`path`, `fileType`, `mtime` opcional) indexadas por:\n\n- raiz de busca canônica\n- `include_hidden`\n- `use_gitignore`\n\n### Transições de estado do cache\n\n1. **Miss / desabilitado**\n   - TTL é `0` ou chave ausente/expirada -> `collect_entries` novo.\n2. **Hit**\n   - Idade da entrada `< cache_ttl_ms()` -> retorna entradas em cache + `cache_age_ms`.\n3. **Reverificação de vazio obsoleto** (política do chamador em `glob`/`grep`/`fd`)\n   - Se a consulta retorna zero correspondências e `cache_age_ms >= empty_recheck_ms()`, força uma revarredura.\n4. **Invalidação**\n   - `invalidateFsScanCache(path?)`:\n     - sem argumento: limpa todas as chaves\n     - argumento path: remove chaves cuja raiz é prefixo daquele caminho alvo\n\n### Compromisso de resultado obsoleto\n\n- O cache favorece varreduras repetidas de baixa latência sobre consistência imediata.\n- A janela de TTL pode retornar positivos/negativos obsoletos.\n- A reverificação de resultado vazio reduz negativos obsoletos para varreduras em cache mais antigas ao custo de uma varredura extra.\n- A invalidação explícita é o mecanismo de correção pretendido após mutações de arquivo.\n\n## 4) Utilitários de texto ANSI (`text`)\n\nEstes são utilitários puros, em memória (sem varredura de sistema de arquivos).\n\n### Limites e responsabilidades\n\n- **`text.rs` é responsável pela semântica de células de terminal**:\n  - Análise de sequências ANSI\n  - Largura e fatiamento conscientes de grafemas\n  - Comportamento de wrap/truncate/sanitize\n- **Truncamento de linha do `grep.rs` (`maxColumns`) é separado**:\n  - truncamento simples por limite de caractere de linhas correspondentes com `...`\n  - não preserva estado ANSI e não é consciente de largura de células de terminal\n\n### Comportamentos principais\n\n- `wrapTextWithAnsi`: quebra por largura visível, carrega códigos SGR ativos entre linhas quebradas.\n- `truncateToWidth`: truncamento por célula visível com política de reticências (`Unicode`, `Ascii`, `Omit`), preenchimento à direita opcional e caminho rápido retornando a string JS original quando inalterada.\n- `sliceWithWidth`: fatiamento por coluna com aplicação opcional de largura estrita.\n- `extractSegments`: extrai segmentos antes/depois em torno de uma sobreposição enquanto restaura o estado ANSI para o segmento `after`.\n- `sanitizeText`: remove escapes ANSI + caracteres de controle, descarta surrogates isolados, normaliza CR/LF removendo `\\r`.\n- `visibleWidth`: conta células visíveis do terminal (tabs usam `TAB_WIDTH` fixo da implementação Rust).\n\n### Comportamento em caso de falha\n\nFunções de texto geralmente retornam saída transformada determinística; erros são limitados às fronteiras de conversão de string JS (falhas de conversão de argumentos N-API).\n\n## 5) Destaque de sintaxe (`highlight`)\n\n`highlight.rs` é transformação pura (sem FS, sem cache).\n\n### Fluxo\n\n1. O wrapper encaminha `code`, `lang` opcional e paleta de cores ANSI.\n2. O Rust resolve a sintaxe por:\n   - busca por token/nome\n   - busca por extensão\n   - fallback de tabela de alias (`ts/tsx/js -> JavaScript`, etc.)\n   - fallback para sintaxe de texto simples quando não resolvido\n3. Analisa cada linha com `ParseState` do syntect e pilha de escopos.\n4. Mapeia escopos para 11 categorias semânticas de cor e injeta/reseta códigos de cor ANSI.\n\n### Comportamento em caso de falha\n\n- Falha de análise por linha não falha a chamada: aquela linha é adicionada sem destaque e o processamento continua.\n- Linguagem desconhecida/não suportada faz fallback para sintaxe de texto simples.\n\n## Fluxos de utilitário puro vs dependentes de sistema de arquivos\n\n| Fluxo | Acesso ao sistema de arquivos | Cache compartilhado | Notas |\n| --- | --- | --- | --- |\n| `searchContent` / `hasMatch` | Não | Não | regex apenas nos bytes/string fornecidos |\n| Funções do módulo `text` | Não | Não | Apenas ANSI/largura/sanitização |\n| Funções do módulo `highlight` | Não | Não | Apenas sintaxe + coloração ANSI |\n| `glob` | Sim | Opcional | varreduras de diretório + filtragem glob |\n| `fuzzyFind` | Sim | Opcional | varreduras de diretório + pontuação fuzzy |\n| `grep` (caminho de arquivo/diretório) | Sim | Opcional (modo diretório) | ripgrep sobre arquivos, filtros/callback opcionais |\n\n## Resumo do ciclo de vida ponta a ponta\n\n1. O chamador invoca o wrapper TS com opções tipadas.\n2. O wrapper normaliza padrões (notavelmente `glob`) e encaminha para a exportação `native.*`.\n3. O Rust valida/normaliza opções e constrói o matcher/configuração de busca.\n4. Para fluxos de sistema de arquivos, as entradas são varridas (hit/miss/revarredura do cache) e depois filtradas/pontuadas.\n5. Loops de workers periodicamente chamam o heartbeat de cancelamento; timeout/abort pode terminar a execução.\n6. O Rust formata as saídas em objetos N-API (`lineNumber`, `matchCount`, `limitReached`, etc.).\n7. O wrapper TS retorna objetos JS tipados (e callbacks opcionais por correspondência para `grep`/`glob`).\n",
	"pt-br/natives/porting-to-natives.md": "---\ntitle: Portando para pi-natives (N-API) — Notas de Campo\ndescription: >-\n  Notas de campo para migrar código de child_process e shell do Node.js para a\n  camada nativa Rust N-API.\nsidebar:\n  order: 9\n  label: Portando para pi-natives\ni18n:\n  sourceHash: 4f5150286535\n  translator: machine\n---\n\n# Portando para pi-natives (N-API) — Notas de Campo\n\nEste é um guia prático para mover caminhos críticos (hot paths) para `crates/pi-natives` e conectá-los através das bindings JS. Ele existe para evitar que os mesmos erros aconteçam duas vezes.\n\n## Quando portar\n\nPorte quando qualquer uma destas condições for verdadeira:\n\n- O caminho crítico executa em loops de renderização, atualizações rápidas de UI ou lotes grandes.\n- Alocações JS dominam (rotatividade de strings, backtracking de regex, arrays grandes).\n- Você já tem uma baseline JS e pode comparar ambas as versões lado a lado.\n- O trabalho é limitado por CPU ou I/O bloqueante que pode rodar no thread pool do libuv.\n- O trabalho é I/O assíncrono que pode rodar no runtime do Tokio (ex.: execução de shell).\n\nEvite portar código que dependa de estado exclusivo do JS ou imports dinâmicos. Exports N-API devem ser puros, dados-entram/dados-saem. Trabalhos de longa duração devem passar por `task::blocking` (limitado por CPU/I/O bloqueante) ou `task::future` (I/O assíncrono) com cancelamento.\n\n## Anatomia de um export nativo\n\n**Lado Rust:**\n\n- A implementação fica em `crates/pi-natives/src/<module>.rs`. Se você adicionar um novo módulo, registre-o em `crates/pi-natives/src/lib.rs`.\n- Exporte com `#[napi]`; exports em snake_case são convertidos para camelCase automaticamente. Use `js_name` explícito apenas para aliases verdadeiros/nomes não-padrão. Use `#[napi(object)]` para structs.\n- Use `task::blocking(tag, cancel_token, work)` (veja `crates/pi-natives/src/task.rs`) para trabalho limitado por CPU ou bloqueante. Use `task::future(env, tag, work)` para trabalho assíncrono que precisa do Tokio (ex.: sessões de shell). Passe um `CancelToken` quando expor `timeoutMs` ou `AbortSignal`.\n\n**Lado JS:**\n\n- `packages/natives/src/bindings.ts` contém a interface base `NativeBindings`.\n- `packages/natives/src/<module>/types.ts` define tipos TS e estende `NativeBindings` via declaration merging.\n- `packages/natives/src/native.ts` importa cada arquivo `<module>/types.ts` para ativar as declarações.\n- `packages/natives/src/<module>/index.ts` encapsula a binding `native` de `packages/natives/src/native.ts`.\n- `packages/natives/src/native.ts` carrega o addon e `validateNative` garante os exports obrigatórios.\n- `packages/natives/src/index.ts` re-exporta o wrapper para consumidores em `packages/*`.\n\n## Checklist de portabilidade\n\n1. **Adicione a implementação Rust**\n\n- Coloque a lógica principal em uma função Rust simples.\n- Se for um novo módulo, adicione-o em `crates/pi-natives/src/lib.rs`.\n- Exponha com `#[napi]` para que o mapeamento padrão snake_case -> camelCase permaneça consistente.\n- Mantenha as assinaturas owned e simples: `String`, `Vec<String>`, `Uint8Array`, ou `Either<JsString, Uint8Array>` para inputs grandes de string/bytes.\n- Para trabalho limitado por CPU ou bloqueante, use `task::blocking`; para trabalho assíncrono, use `task::future`. Passe um `CancelToken` e chame `heartbeat()` dentro de loops longos.\n\n2. **Conecte as bindings JS**\n\n- Adicione os tipos e a extensão de `NativeBindings` em `packages/natives/src/<module>/types.ts`.\n- Importe `./<module>/types` em `packages/natives/src/native.ts` para acionar o declaration merging.\n- Adicione um wrapper em `packages/natives/src/<module>/index.ts` que chame `native`.\n- Re-exporte de `packages/natives/src/index.ts`.\n\n3. **Atualize a validação nativa**\n\n- Adicione `checkFn(\"newExport\")` em `validateNative` (`packages/natives/src/native.ts`).\n\n4. **Adicione benchmarks**\n\n- Coloque benchmarks junto ao pacote proprietário (`packages/tui/bench`, `packages/natives/bench`, ou `packages/coding-agent/bench`).\n- Inclua uma baseline JS e a versão nativa na mesma execução.\n- Use `Bun.nanoseconds()` e uma contagem fixa de iterações.\n- Mantenha os inputs do benchmark pequenos e realistas (dados reais vistos no caminho crítico).\n\n5. **Compile o binário nativo**\n\n- `bun --cwd=packages/natives run build`\n- Use `bun --cwd=packages/natives run build` e defina `PI_DEV=1` se quiser diagnósticos do loader durante os testes.\n\n6. **Execute o benchmark**\n\n- `bun run packages/<pkg>/bench/<bench>.ts` (ou `bun --cwd=packages/natives run bench`)\n\n7. **Decida sobre o uso**\n\n- Se o nativo for mais lento, **mantenha o JS** e deixe o export nativo sem uso.\n- Se o nativo for mais rápido, mude os pontos de chamada para o wrapper nativo.\n\n## Pontos problemáticos e como evitá-los\n\n### 1) `pi_natives.node` desatualizado impede novos exports\n\nO loader prefere o binário com tag de plataforma em `packages/natives/native` (`pi_natives.<platform>-<arch>.node`). `PI_DEV=1` agora apenas habilita diagnósticos do loader; não altera mais para um nome de arquivo de addon de desenvolvimento separado. Há também um fallback `pi_natives.node`. Binários compilados são extraídos para `~/.xcsh/natives/<version>/pi_natives.<platform>-<arch>.node`. Se qualquer um destes estiver desatualizado, os exports não serão atualizados.\n\n**Correção:** remova o arquivo desatualizado antes de recompilar.\n\n```bash\nrm packages/natives/native/pi_natives.linux-x64.node\nrm packages/natives/native/pi_natives.node\nbun --cwd=packages/natives run build\n```\n\nSe você está executando um binário compilado, delete o diretório de addon em cache:\n\n```bash\nrm -rf ~/.xcsh/natives/<version>\n```\n\nEm seguida, verifique se o export existe no binário:\n\n```bash\nbun -e 'const tag = `${process.platform}-${process.arch}`; const mod = require(`./packages/natives/native/pi_natives.${tag}.node`); console.log(Object.keys(mod).includes(\"newExport\"));'\n```\n\n### 2) Erros de \"Missing exports\" do `validateNative`\n\nIsso é **bom** — previne incompatibilidades silenciosas. Quando você vê isto:\n\n```\nNative addon missing exports ... Missing: visibleWidth\n```\n\nsignifica que seu binário está desatualizado, o nome do export Rust (ou alias explícito quando usado) não corresponde ao nome JS, ou o export nunca foi compilado. Corrija o build e a incompatibilidade de nomes, não enfraqueça a validação.\n\n### 3) Incompatibilidade de assinatura Rust\n\nMantenha simples e owned. `String`, `Vec<String>` e `Uint8Array` funcionam. Evite referências como `&str` em exports públicos. Se precisar de dados estruturados, encapsule em structs com `#[napi(object)]`.\n\n### 4) Erros de benchmarking\n\n- Não compare inputs ou alocações diferentes.\n- Mantenha JS e nativo usando arrays de input idênticos.\n- Execute ambos no mesmo arquivo de benchmark para evitar distorções.\n\n## Template de benchmark\n\n```ts\nconst ITERATIONS = 2000;\n\nfunction bench(name: string, fn: () => void): number {\n const start = Bun.nanoseconds();\n for (let i = 0; i < ITERATIONS; i++) fn();\n const elapsed = (Bun.nanoseconds() - start) / 1e6;\n console.log(`${name}: ${elapsed.toFixed(2)}ms total (${(elapsed / ITERATIONS).toFixed(6)}ms/op)`);\n return elapsed;\n}\n\nbench(\"feature/js\", () => {\n jsImpl(sample);\n});\n\nbench(\"feature/native\", () => {\n nativeImpl(sample);\n});\n```\n\n## Checklist de verificação\n\n- `validateNative` passa (sem exports faltando).\n- `NativeBindings` está estendido em `packages/natives/src/<module>/types.ts` e o wrapper está re-exportado em `packages/natives/src/index.ts`.\n- `Object.keys(require(...))` inclui seu novo export.\n- Números de benchmark registrados no PR/notas.\n- Ponto de chamada atualizado **apenas se** o nativo for mais rápido ou equivalente.\n\n## Regra geral\n\n- Se o nativo for mais lento, **não mude**. Mantenha o export para trabalho futuro, mas o TUI deve permanecer no caminho mais rápido.\n- Se o nativo for mais rápido, mude o ponto de chamada e mantenha o benchmark em vigor para detectar regressões.\n",
	"pt-br/providers/models.md": "---\ntitle: Configuração de Modelos e Provedores\ndescription: >-\n  Registro de modelos e configuração de provedores via models.yml com\n  roteamento, fallback e precificação.\nsidebar:\n  order: 1\n  label: Modelos e provedores\ni18n:\n  sourceHash: 8053df967ff6\n  translator: machine\n---\n\n# Configuração de Modelos e Provedores (`models.yml`)\n\nEste documento descreve como o coding-agent atualmente carrega modelos, aplica substituições, resolve credenciais e escolhe modelos em tempo de execução.\n\n## O que controla o comportamento dos modelos\n\nArquivos de implementação principais:\n\n- `src/config/model-registry.ts` — carrega modelos integrados + customizados, substituições de provedores, descoberta em tempo de execução, integração de autenticação\n- `src/config/model-resolver.ts` — analisa padrões de modelo e seleciona modelos initial/smol/slow\n- `src/config/settings-schema.ts` — configurações relacionadas a modelos (`modelRoles`, preferências de transporte do provedor)\n- `src/session/auth-storage.ts` — ordem de resolução de chave de API + OAuth\n- `packages/ai/src/models.ts` e `packages/ai/src/types.ts` — provedores/modelos integrados e tipos `Model`/`compat`\n\n## Localização do arquivo de configuração e comportamento legado\n\nCaminho de configuração padrão:\n\n- `~/.xcsh/agent/models.yml`\n\nComportamento legado ainda presente:\n\n- Se `models.yml` estiver ausente e `models.json` existir no mesmo local, ele é migrado para `models.yml`.\n- Caminhos de configuração explícitos `.json` / `.jsonc` ainda são suportados quando passados programaticamente para `ModelRegistry`.\n\n## Estrutura do `models.yml`\n\n```yaml\nconfigVersion: 1  # optional — written by auto-config, used for migration detection\nproviders:\n  <provider-id>:\n    # provider-level config\nequivalence:\n  overrides:\n    <provider-id>/<model-id>: <canonical-model-id>\n  exclude:\n    - <provider-id>/<model-id>\n```\n\n`configVersion` é um inteiro opcional escrito pelo sistema de auto-configuração. Quando presente, o xcsh o utiliza para detectar configurações desatualizadas e atualizá-las automaticamente.\n\n`provider-id` é a chave canônica do provedor usada em seleção e busca de autenticação.\n\n`equivalence` é opcional e configura o agrupamento de modelos canônicos sobre os modelos concretos do provedor:\n\n- `overrides` mapeia um seletor concreto exato (`provider/modelId`) para um id canônico oficial upstream\n- `exclude` retira um seletor concreto do agrupamento canônico\n\n## Campos em nível de provedor\n\n```yaml\nproviders:\n  my-provider:\n    baseUrl: https://api.example.com/v1\n    apiKey: MY_PROVIDER_API_KEY\n    api: openai-completions\n    headers:\n      X-Team: platform\n    authHeader: true\n    auth: apiKey\n    discovery:\n      type: ollama\n    modelOverrides:\n      some-model-id:\n        name: Renamed model\n    models:\n      - id: some-model-id\n        name: Some Model\n        api: openai-completions\n        reasoning: false\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 128000\n        maxTokens: 16384\n        headers:\n          X-Model: value\n        compat:\n          supportsStore: true\n          supportsDeveloperRole: true\n          supportsReasoningEffort: true\n          maxTokensField: max_completion_tokens\n          openRouterRouting:\n            only: [anthropic]\n          vercelGatewayRouting:\n            order: [anthropic, openai]\n          extraBody:\n            gateway: m1-01\n            controller: mlx\n```\n\n### Valores permitidos de `api` para provedor/modelo\n\n- `openai-completions`\n- `openai-responses`\n- `openai-codex-responses`\n- `azure-openai-responses`\n- `anthropic-messages`\n- `google-generative-ai`\n- `google-vertex`\n\n### Valores permitidos de auth/discovery\n\n- `auth`: `apiKey` (padrão) ou `none`\n- `discovery.type`: `ollama`\n\n## Regras de validação (atuais)\n\n### Provedor customizado completo (`models` não vazio)\n\nObrigatório:\n\n- `baseUrl`\n- `apiKey` a menos que `auth: none`\n- `api` no nível do provedor ou em cada modelo\n\n### Provedor apenas com substituições (`models` ausente ou vazio)\n\nDeve definir pelo menos um dos seguintes:\n\n- `baseUrl`\n- `modelOverrides`\n- `discovery`\n\n### Discovery\n\n- `discovery` requer `api` no nível do provedor.\n\n### Verificações de valores do modelo\n\n- `id` obrigatório\n- `contextWindow` e `maxTokens` devem ser positivos se fornecidos\n\n## Ordem de mesclagem e substituição\n\nPipeline do ModelRegistry (ao atualizar):\n\n1. Carregar provedores/modelos integrados de `@f5-sales-demo/pi-ai`.\n2. Carregar configuração customizada de `models.yml`.\n3. Aplicar substituições de provedor (`baseUrl`, `headers`) aos modelos integrados.\n4. Aplicar `modelOverrides` (por provedor + id do modelo).\n5. Mesclar `models` customizados:\n   - mesmo `provider + id` substitui o existente\n   - caso contrário, adiciona ao final\n6. Aplicar modelos descobertos em tempo de execução (atualmente Ollama e LM Studio), depois reaplicar substituições de modelo.\n\n## Equivalência canônica de modelos e coalescência\n\nO registro mantém cada modelo concreto do provedor e então constrói uma camada canônica acima deles.\n\nIds canônicos são apenas ids oficiais upstream, por exemplo:\n\n- `claude-opus-4-6`\n- `claude-haiku-4-5`\n- `gpt-5.3-codex`\n\n### Configuração de equivalência no `models.yml`\n\nExemplo:\n\n```yaml\nproviders:\n  zenmux:\n    baseUrl: https://api.zenmux.example/v1\n    apiKey: ZENMUX_API_KEY\n    api: openai-codex-responses\n    models:\n      - id: codex\n        name: Zenmux Codex\n        reasoning: true\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 200000\n        maxTokens: 32768\n\nequivalence:\n  overrides:\n    zenmux/codex: gpt-5.3-codex\n    p-codex/codex: gpt-5.3-codex\n  exclude:\n    - demo/codex-preview\n```\n\nOrdem de construção para agrupamento canônico:\n\n1. substituição exata do usuário de `equivalence.overrides`\n2. correspondências de id oficial integradas dos metadados do modelo built-in\n3. normalização heurística conservadora para variantes de gateway/provedor\n4. fallback para o próprio id do modelo concreto\n\nAs heurísticas atuais são intencionalmente restritas:\n\n- prefixos upstream incorporados podem ser removidos quando presentes, por exemplo `anthropic/...` ou `openai/...`\n- variantes de versão com pontos e hífens podem normalizar apenas quando mapeiam para um id oficial existente, por exemplo `4.6 -> 4-6`\n- famílias ou versões ambíguas não são mescladas sem uma correspondência integrada ou substituição explícita\n\n### Comportamento de resolução canônica\n\nQuando múltiplas variantes concretas compartilham um id canônico, a resolução usa:\n\n1. disponibilidade e autenticação\n2. `modelProviderOrder` do `config.yml`\n3. ordem existente no registro/provedor se `modelProviderOrder` não estiver definido\n\nProvedores desabilitados ou não autenticados são ignorados.\n\nO estado da sessão e as transcrições continuam registrando o provedor/modelo concreto que realmente executou o turno.\n\nPadrões do provedor vs substituições por modelo:\n\n- `headers` do provedor são a base.\n- `headers` do modelo substituem as chaves de header do provedor.\n- `modelOverrides` pode substituir metadados do modelo (`name`, `reasoning`, `input`, `cost`, `contextWindow`, `maxTokens`, `headers`, `compat`, `contextPromotionTarget`).\n- `compat` é mesclado profundamente para blocos de roteamento aninhados (`openRouterRouting`, `vercelGatewayRouting`, `extraBody`).\n\n## Integração de descoberta em tempo de execução\n\n### Descoberta implícita do Ollama\n\nSe `ollama` não estiver explicitamente configurado, o registro adiciona um provedor descobrível implícito:\n\n- provedor: `ollama`\n- api: `openai-completions`\n- URL base: `OLLAMA_BASE_URL` ou `http://127.0.0.1:11434`\n- modo de autenticação: sem chave (comportamento `auth: none`)\n\nA descoberta em tempo de execução chama `GET /api/tags` no Ollama e sintetiza entradas de modelo com padrões locais.\n\n### Descoberta implícita do llama.cpp\n\nSe `llama.cpp` não estiver explicitamente configurado, o registro adiciona um provedor descobrível implícito:\nNota: está usando a API de mensagens antropic mais recente em vez de openai-completions.\n\n- provedor: `llama.cpp`\n- api: `openai-responses`\n- URL base: `LLAMA_CPP_BASE_URL` ou `http://127.0.0.1:8080`\n- modo de autenticação: sem chave (comportamento `auth: none`)\n\nA descoberta em tempo de execução chama `GET models` no llama.cpp e sintetiza entradas de modelo com padrões locais.\n\n### Descoberta implícita do LM Studio\n\nSe `lm-studio` não estiver explicitamente configurado, o registro adiciona um provedor descobrível implícito:\n\n- provedor: `lm-studio`\n- api: `openai-completions`\n- URL base: `LM_STUDIO_BASE_URL` ou `http://127.0.0.1:1234/v1`\n- modo de autenticação: sem chave (comportamento `auth: none`)\n\nA descoberta em tempo de execução busca modelos (`GET /models`) e sintetiza entradas de modelo com padrões locais.\n\n### Descoberta explícita de provedor\n\nVocê pode configurar a descoberta manualmente:\n\n```yaml\nproviders:\n  ollama:\n    baseUrl: http://127.0.0.1:11434\n    api: openai-completions\n    auth: none\n    discovery:\n      type: ollama\n      \n  llama.cpp:\n    baseUrl: http://127.0.0.1:8080\n    api: openai-responses\n    auth: none\n    discovery:\n      type: llama.cpp\n```\n\n### Registro de provedor por extensão\n\nExtensões podem registrar provedores em tempo de execução (`pi.registerProvider(...)`), incluindo:\n\n- substituição/adição de modelos para um provedor\n- registro de handler de stream customizado para novos IDs de API\n- registro de provedor OAuth customizado\n\n## Ordem de resolução de autenticação e chave de API\n\nAo solicitar uma chave para um provedor, a ordem efetiva é:\n\n1. Substituição em tempo de execução (CLI `--api-key`)\n2. Credencial de chave de API armazenada em `agent.db`\n3. Credencial OAuth armazenada em `agent.db` (com refresh)\n4. Mapeamento de variável de ambiente (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc.)\n5. Resolver de fallback do ModelRegistry (`apiKey` do provedor em `models.yml`, semântica de nome-de-env-ou-literal)\n\nComportamento de `apiKey` no `models.yml`:\n\n- O valor é primeiro tratado como um nome de variável de ambiente.\n- Se nenhuma variável de ambiente existir, a string literal é usada como token.\n\nSe `authHeader: true` e `apiKey` do provedor estiver definido, os modelos recebem:\n\n- Header `Authorization: Bearer <chave-resolvida>` injetado.\n\nProvedores sem chave:\n\n- Provedores marcados com `auth: none` são tratados como disponíveis sem credenciais.\n- `getApiKey*` retorna `kNoAuth` para eles.\n\n## Disponibilidade de modelos vs todos os modelos\n\n- `getAll()` retorna o registro de modelos carregado (integrados + customizados mesclados + descobertos).\n- `getAvailable()` filtra para modelos que são sem chave ou possuem autenticação resolvível.\n\nPortanto, um modelo pode existir no registro, mas não ser selecionável até que a autenticação esteja disponível.\n\n## Resolução de modelo em tempo de execução\n\n### CLI e análise de padrões\n\n`model-resolver.ts` suporta:\n\n- `provider/modelId` exato\n- id canônico de modelo exato\n- id de modelo exato (provedor inferido)\n- correspondência fuzzy/substring\n- padrões glob de escopo em `--models` (ex.: `openai/*`, `*sonnet*`)\n- sufixo opcional `:thinkingLevel` (`off|minimal|low|medium|high|xhigh`)\n\n`--provider` é legado; `--model` é preferido.\n\nPrecedência de resolução para seletores exatos:\n\n1. `provider/modelId` exato ignora coalescência\n2. id canônico exato resolve através do índice canônico\n3. id concreto bare exato ainda funciona\n4. correspondência fuzzy e glob executa após os caminhos exatos\n\n### Prioridade de seleção do modelo inicial\n\n`findInitialModel(...)` usa esta ordem:\n\n1. provedor+modelo explícito via CLI\n2. primeiro modelo com escopo (se não estiver retomando)\n3. provedor/modelo padrão salvo\n4. padrões conhecidos de provedores (ex.: OpenAI/Anthropic/etc.) entre modelos disponíveis\n5. primeiro modelo disponível\n\n### Aliases de papel e configurações\n\nPapéis de modelo suportados:\n\n- `default`, `smol`, `slow`, `plan`, `commit`\n\nAliases de papel como `pi/smol` expandem através de `settings.modelRoles`. Cada valor de papel também pode adicionar um seletor de thinking como `:minimal`, `:low`, `:medium` ou `:high`.\n\nSe um papel aponta para outro papel, o modelo alvo ainda herda normalmente e qualquer sufixo explícito no papel referente prevalece para aquele uso específico do papel.\n\nConfigurações relacionadas:\n\n- `modelRoles` (registro)\n- `enabledModels` (lista de padrões com escopo)\n- `modelProviderOrder` (precedência global canônica-provedor)\n- `providers.kimiApiFormat` (formato de requisição `openai` ou `anthropic`)\n- `providers.openaiWebsockets` (preferência de websocket `auto|off|on` para transporte OpenAI Codex)\n\n`modelRoles` pode armazenar tanto:\n\n- `provider/modelId` para fixar uma variante concreta de provedor\n- um id canônico como `gpt-5.3-codex` para permitir coalescência de provedor\n\nPara `enabledModels` e CLI `--models`:\n\n- ids canônicos exatos expandem para todas as variantes concretas naquele grupo canônico\n- entradas explícitas `provider/modelId` permanecem exatas\n- globs e correspondências fuzzy ainda operam sobre modelos concretos\n\n## `/model` e `--list-models`\n\nAmbas as interfaces mantêm modelos com prefixo de provedor visíveis e selecionáveis.\n\nAgora também expõem modelos canônicos/coalescidos:\n\n- `/model` inclui uma visualização canônica junto com as abas de provedor\n- `--list-models` imprime uma seção canônica mais as linhas concretas por provedor\n\nSelecionar uma entrada canônica armazena o seletor canônico. Selecionar uma linha de provedor armazena o `provider/modelId` explícito.\n\n## Promoção de contexto (cadeias de fallback em nível de modelo)\n\nA promoção de contexto é um mecanismo de recuperação de overflow para variantes de contexto pequeno (por exemplo `*-spark`) que automaticamente promove para um modelo irmão de contexto maior quando a API rejeita uma requisição com erro de comprimento de contexto.\n\n### Gatilho e ordem\n\nQuando um turno falha com erro de overflow de contexto (ex.: `context_length_exceeded`), `AgentSession` tenta a promoção **antes** de recorrer à compactação:\n\n1. Se `contextPromotion.enabled` for true, resolver um alvo de promoção (veja abaixo).\n2. Se um alvo for encontrado, trocar para ele e retentar a requisição — sem necessidade de compactação.\n3. Se nenhum alvo estiver disponível, prosseguir para auto-compactação no modelo atual.\n\n### Seleção do alvo\n\nA seleção é orientada por modelo, não por papel:\n\n1. `currentModel.contextPromotionTarget` (se configurado)\n2. menor modelo de contexto maior no mesmo provedor + API\n\nCandidatos são ignorados a menos que as credenciais sejam resolvidas (`ModelRegistry.getApiKey(...)`).\n\n### Handoff de websocket do OpenAI Codex\n\nSe trocar de/para `openai-codex-responses`, a chave de estado do provedor da sessão `openai-codex-responses` é fechada antes da troca de modelo. Isso descarta o estado de transporte websocket para que o próximo turno inicie limpo no modelo promovido.\n\n### Comportamento de persistência\n\nA promoção usa troca temporária (`setModelTemporary`):\n\n- registrada como uma `model_change` temporária no histórico da sessão\n- não reescreve o mapeamento de papel salvo\n\n### Configurando cadeias de fallback explícitas\n\nConfigure o fallback diretamente nos metadados do modelo via `contextPromotionTarget`.\n\n`contextPromotionTarget` aceita tanto:\n\n- `provider/model-id` (explícito)\n- `model-id` (resolvido dentro do provedor atual)\n\nExemplo (`models.yml`) para Spark -> não-Spark no mesmo provedor:\n\n```yaml\nproviders:\n  openai-codex:\n    modelOverrides:\n      gpt-5.3-codex-spark:\n        contextPromotionTarget: openai-codex/gpt-5.3-codex\n```\n\nO gerador de modelos integrado também atribui isso automaticamente para modelos `*-spark` quando um modelo base do mesmo provedor existe.\n\n## Campos de compatibilidade e roteamento\n\n`models.yml` suporta este subconjunto de `compat`:\n\n- `supportsStore`\n- `supportsDeveloperRole`\n- `supportsReasoningEffort`\n- `maxTokensField` (`max_completion_tokens` ou `max_tokens`)\n- `openRouterRouting.only` / `openRouterRouting.order`\n- `vercelGatewayRouting.only` / `vercelGatewayRouting.order`\n\nEstes são consumidos pela lógica de transporte OpenAI-completions e combinados com auto-detecção baseada em URL.\n\n## Exemplos práticos\n\n### Endpoint local compatível com OpenAI (sem autenticação)\n\n```yaml\nproviders:\n  local-openai:\n    baseUrl: http://127.0.0.1:8000/v1\n    auth: none\n    api: openai-completions\n    models:\n      - id: Qwen/Qwen2.5-Coder-32B-Instruct\n        name: Qwen 2.5 Coder 32B (local)\n```\n\n### Proxy hospedado com chave baseada em variável de ambiente\n\n```yaml\nproviders:\n  anthropic-proxy:\n    baseUrl: https://proxy.example.com/anthropic\n    apiKey: ANTHROPIC_PROXY_API_KEY\n    api: anthropic-messages\n    authHeader: true\n    models:\n      - id: claude-sonnet-4-20250514\n        name: Claude Sonnet 4 (Proxy)\n        reasoning: true\n        input: [text, image]\n```\n\n### Substituir rota de provedor integrado + metadados do modelo\n\n```yaml\nproviders:\n  openrouter:\n    baseUrl: https://my-proxy.example.com/v1\n    headers:\n      X-Team: platform\n    modelOverrides:\n      anthropic/claude-sonnet-4:\n        name: Sonnet 4 (Corp)\n        compat:\n          openRouterRouting:\n            only: [anthropic]\n```\n\n## Auto-configuração do proxy LiteLLM\n\nQuando ambas as variáveis de ambiente `LITELLM_BASE_URL` e `LITELLM_API_KEY` estão definidas, o xcsh gerencia automaticamente a configuração de `models.yml` para o proxy LiteLLM.\n\n### Auto-geração na primeira execução\n\nSe `models.yml` não existir e as variáveis de ambiente do LiteLLM forem detectadas, o xcsh o gera automaticamente:\n\n```yaml\n# Auto-generated by xcsh for LiteLLM proxy\n# API key resolved from LITELLM_API_KEY env var at runtime\nconfigVersion: 1\nproviders:\n  anthropic:\n    baseUrl: \"https://your-litellm-proxy.example.com/anthropic\"\n    apiKey: LITELLM_API_KEY\n```\n\nUm `config.yml` padrão também é gerado com configurações sensatas de provedor de imagens.\n\n### Auto-reparo na inicialização\n\nA cada inicialização, `startupHealthCheck()` no registro de modelos executa as seguintes verificações:\n\n| Condição | Ação |\n|----------|------|\n| `models.yml` ausente | Auto-gerar a partir das variáveis de ambiente |\n| `models.yml` corrompido ou não analisável | Backup para `.bak`, regenerar |\n| `baseUrl` não corresponde ao `LITELLM_BASE_URL` | Backup para `.bak`, regenerar com nova URL |\n| `configVersion` ausente ou desatualizado | Backup para `.bak`, regenerar com versão atual |\n| Configuração saudável | Nenhuma ação |\n\nTodos os reparos criam backups `.bak` antes de sobrescrever. Todas as operações são idempotentes.\n\n### Comando CLI\n\n```bash\nxcsh setup litellm              # Generate or fix LiteLLM config\nxcsh setup litellm --check      # Validate without writing\nxcsh setup litellm --check --json  # Machine-readable validation output\n```\n\n### Variáveis de ambiente obrigatórias\n\n| Variável | Propósito |\n|----------|-----------|\n| `LITELLM_BASE_URL` | URL do proxy LiteLLM (ex.: `https://your-proxy.example.com`). Deve começar com `http://` ou `https://`. |\n| `LITELLM_API_KEY` | Chave de API para o proxy. Referenciada por nome na configuração gerada, resolvida em tempo de execução. |\n\nSe qualquer variável não estiver definida, a auto-configuração é silenciosamente ignorada.\n\n### Versionamento de configuração\n\nConfigurações geradas incluem um campo `configVersion`. Quando o formato gerado muda em versões futuras, o xcsh detecta configurações desatualizadas e as atualiza automaticamente (com backup).\n\n## Ressalva sobre consumidor legado\n\nA maioria das configurações de modelo agora flui através de `models.yml` via `ModelRegistry`.\n\nUm caminho legado notável permanece: a resolução de autenticação Anthropic para busca web ainda lê `~/.xcsh/agent/models.json` diretamente em `src/web/search/auth.ts`.\n\nSe você depende desse caminho específico, mantenha a compatibilidade com JSON em mente até que esse módulo seja migrado.\n\n## Modo de falha\n\nSe `models.yml` falhar nas verificações de schema ou validação:\n\n- Se `LITELLM_BASE_URL` e `LITELLM_API_KEY` estiverem definidos, a verificação de saúde na inicialização tenta auto-reparo (backup do arquivo corrompido, regeneração a partir das variáveis de ambiente). Se o reparo for bem-sucedido, o registro recarrega a configuração corrigida.\n- Se o auto-reparo não for possível (variáveis de ambiente não definidas, falha de escrita), o registro continua operando com modelos integrados.\n- O erro é exposto via `ModelRegistry.getError()` e exibido na UI/notificações.\n",
	"pt-br/providers/provider-streaming-internals.md": "---\ntitle: Internos do Streaming de Providers\ndescription: >-\n  Implementação de streaming de providers com parsing de SSE, contagem de tokens\n  e tratamento de backpressure.\nsidebar:\n  order: 2\n  label: Internos do streaming\ni18n:\n  sourceHash: a32ffa769c4d\n  translator: machine\n---\n\n# Internos do streaming de providers\n\nEste documento explica como o streaming de tokens/ferramentas é normalizado em `@f5-sales-demo/pi-ai`, depois propagado através de `@f5-sales-demo/pi-agent-core` e dos eventos de sessão do `coding-agent`.\n\n## Fluxo de ponta a ponta\n\n1. `streamSimple()` (`packages/ai/src/stream.ts`) mapeia opções genéricas e despacha para uma função de stream do provider.\n2. As funções de stream do provider (`anthropic.ts`, `openai-responses.ts`, `google.ts`) traduzem os eventos nativos de stream do provider na sequência unificada de `AssistantMessageEvent`.\n3. Cada provider envia eventos para `AssistantMessageEventStream` (`packages/ai/src/utils/event-stream.ts`), que limita a frequência dos eventos delta e expõe:\n   - iteração assíncrona para atualizações incrementais\n   - `result()` para a `AssistantMessage` final\n4. `agentLoop` (`packages/agent/src/agent-loop.ts`) consome esses eventos, muta o estado do assistente em andamento e emite eventos `message_update` contendo o `assistantMessageEvent` bruto.\n5. `AgentSession` (`packages/coding-agent/src/session/agent-session.ts`) se inscreve nos eventos do agente, persiste mensagens, aciona hooks de extensão e aplica comportamentos de sessão (retry, compactação, TTSR, verificações de aborto de edição em streaming).\n\n## Contrato unificado de stream em `@f5-sales-demo/pi-ai`\n\nTodos os providers emitem o mesmo formato (`AssistantMessageEvent` em `packages/ai/src/types.ts`):\n\n- `start`\n- tripletos de ciclo de vida de blocos de conteúdo:\n  - texto: `text_start` → `text_delta`* → `text_end`\n  - pensamento: `thinking_start` → `thinking_delta`* → `thinking_end`\n  - chamada de ferramenta: `toolcall_start` → `toolcall_delta`* → `toolcall_end`\n- evento terminal:\n  - `done` com `reason: \"stop\" | \"length\" | \"toolUse\"`\n  - ou `error` com `reason: \"aborted\" | \"error\"`\n\n`AssistantMessageEventStream` garante:\n\n- o resultado final é resolvido pelo evento terminal (`done` ou `error`)\n- deltas são agrupados/limitados em frequência (~50ms)\n- deltas em buffer são descarregados antes de eventos não-delta e antes da conclusão\n\n## Comportamento de limitação de frequência e harmonização de deltas\n\n`AssistantMessageEventStream` trata `text_delta`, `thinking_delta` e `toolcall_delta` como eventos mesclável:\n\n- deltas em buffer são mesclados apenas quando **type + contentIndex** coincidem\n- a mesclagem mantém o snapshot `partial` mais recente\n- eventos não-delta forçam descarregamento imediato\n\nIsso suaviza streams de alta frequência dos providers para consumidores TUI/eventos, mas não é backpressure do provider: os providers ainda produzem em velocidade máxima, enquanto o stream local faz buffer.\n\n## Detalhes de normalização por provider\n\n## Anthropic (`anthropic-messages`)\n\nFonte: `packages/ai/src/providers/anthropic.ts`\n\nPontos de normalização:\n\n- `message_start` inicializa o uso (tokens de entrada/saída/cache)\n- `content_block_start` mapeia para inícios de texto/pensamento/chamada de ferramenta\n- `content_block_delta` mapeia:\n  - `text_delta` → `text_delta`\n  - `thinking_delta` → `thinking_delta`\n  - `input_json_delta` → `toolcall_delta`\n  - `signature_delta` atualiza apenas `thinkingSignature` (sem evento)\n- `content_block_stop` emite o `*_end` correspondente\n- `message_delta.stop_reason` mapeia via `mapStopReason()`\n\nStreaming de argumentos de chamada de ferramenta:\n\n- cada bloco de ferramenta carrega um `partialJson` interno\n- cada delta JSON é concatenado ao `partialJson`\n- `arguments` são reanalisados a cada delta via `parseStreamingJson()`\n- `toolcall_end` reanalisa mais uma vez, depois remove `partialJson`\n\n## OpenAI Responses (`openai-responses`)\n\nFonte: `packages/ai/src/providers/openai-responses.ts`\n\nPontos de normalização:\n\n- `response.output_item.added` inicia blocos de raciocínio/texto/chamada de função\n- eventos de resumo de raciocínio (`response.reasoning_summary_text.delta`) tornam-se `thinking_delta`\n- deltas de saída/recusa tornam-se `text_delta`\n- `response.function_call_arguments.delta` torna-se `toolcall_delta`\n- `response.output_item.done` emite `thinking_end` / `text_end` / `toolcall_end`\n- `response.completed` mapeia status para razão de parada e uso\n\nStreaming de argumentos de chamada de ferramenta:\n\n- mesmo padrão de acumulação `partialJson` do Anthropic\n- providers que enviam apenas `response.function_call_arguments.done` ainda populam os args finais\n- IDs de chamada de ferramenta são normalizados como `\"<call_id>|<item_id>\"`\n\n## Google Generative AI (`google-generative-ai`)\n\nFonte: `packages/ai/src/providers/google.ts`\n\nPontos de normalização:\n\n- itera `candidate.content.parts`\n- partes de texto são separadas em pensamento vs texto por `isThinkingPart(part)`\n- transições de bloco fecham o bloco anterior antes de iniciar um novo\n- `part.functionCall` é tratado como uma chamada de ferramenta completa (start/delta/end emitidos imediatamente)\n- razão de término mapeada por `mapStopReason()` de `google-shared.ts`\n\nStreaming de argumentos de chamada de ferramenta:\n\n- argumentos de chamada de função chegam como objeto estruturado, não como texto JSON incremental\n- a implementação emite um `toolcall_delta` sintético contendo `JSON.stringify(arguments)`\n- não é necessário parser de JSON parcial para o Google neste caminho\n\n## Acumulação e recuperação de JSON parcial em chamadas de ferramenta\n\nO comportamento compartilhado para Anthropic/OpenAI Responses usa `parseStreamingJson()` (`packages/ai/src/utils/json-parse.ts`):\n\n1. tenta `JSON.parse`\n2. fallback para parser `partial-json` para fragmentos incompletos\n3. se ambos falharem, retorna `{}`\n\nImplicações:\n\n- deltas de argumentos malformados ou truncados não causam crash no processamento do stream imediatamente\n- `arguments` em andamento podem temporariamente ser `{}`\n- deltas válidos posteriores podem recuperar argumentos estruturados porque o parsing é retentado a cada concatenação\n- o `toolcall_end` final realiza mais uma tentativa de parse antes da emissão\n\n## Razões de parada vs erros de transporte/runtime\n\nAs razões de parada dos providers são mapeadas para `stopReason` normalizado:\n\n- Anthropic: `end_turn`→`stop`, `max_tokens`→`length`, `tool_use`→`toolUse`, casos de segurança/recusa→`error`\n- OpenAI Responses: `completed`→`stop`, `incomplete`→`length`, `failed/cancelled`→`error`\n- Google: `STOP`→`stop`, `MAX_TOKENS`→`length`, classes de segurança/proibido/chamada de função malformada→`error`\n\nA semântica de erros é dividida em dois estágios:\n\n1. **Semântica de conclusão do modelo** (razão de término/status reportado pelo provider)\n2. **Falha de transporte/runtime** (exceções de rede/cliente/parser/aborto)\n\nSe o stream do provider lança exceção ou sinaliza falha, cada wrapper de provider captura e emite o evento terminal `error` com:\n\n- `stopReason = \"aborted\"` quando o sinal de aborto está definido\n- caso contrário `stopReason = \"error\"`\n- `errorMessage = formatErrorMessageWithRetryAfter(error)`\n\n## Comportamento de falha de parsing de chunk/SSE malformado\n\nPara esses caminhos de provider, o enquadramento de chunk/SSE é tratado pelos streams do SDK do fornecedor (Anthropic SDK, OpenAI SDK, Google SDK). Este código não implementa um decodificador SSE customizado aqui.\n\nComportamento observado na implementação atual:\n\n- parsing de chunk/SSE malformado no nível do SDK aparece como uma exceção ou evento `error` do stream\n- o wrapper do provider converte isso em evento terminal `error` unificado\n- não há resume/retry específico do provider dentro da própria função de stream\n- retries de nível superior são tratados na lógica de auto-retry do `AgentSession` (retry em nível de mensagem, não replay de chunk de stream)\n\n## Limites de cancelamento\n\nO cancelamento é em camadas:\n\n- Requisição ao provider de IA: `options.signal` é passado para a chamada de stream do cliente do provider.\n- Wrapper do provider: após o loop de stream, o sinal abortado força o caminho de erro (`\"Request was aborted\"`).\n- Loop do agente: verifica `signal.aborted` antes de tratar cada evento do provider e pode sintetizar uma mensagem de assistente abortada a partir do parcial mais recente.\n- Controles de sessão/agente: `AgentSession.abort()` -> `agent.abort()` -> cancelamento do abort controller compartilhado.\n\nO cancelamento de execução de ferramentas é separado do cancelamento de stream do modelo:\n\n- os executores de ferramentas usam `AbortSignal.any([agentSignal, steeringAbortSignal])`\n- interrupções de direcionamento podem abortar a execução de ferramentas restantes enquanto preservam resultados de ferramentas já produzidos\n\n## Limites de backpressure\n\nNão há mecanismo rígido de backpressure entre o stream do SDK do provider e os consumidores downstream:\n\n- `EventStream` usa filas em memória sem tamanho máximo\n- a limitação de frequência reduz a taxa de atualização da UI, mas não desacelera a ingestão do provider\n- se os consumidores ficarem significativamente atrasados, os eventos enfileirados podem crescer até a conclusão\n\nO design atual favorece responsividade e ordenação simples em vez de controle de fluxo com buffer limitado.\n\n## Como eventos de stream aparecem como eventos de agente/sessão\n\n`agentLoop.streamAssistantResponse()` faz a ponte entre `AssistantMessageEvent` e `AgentEvent`:\n\n- em `start`: insere uma mensagem de assistente placeholder e emite `message_start`\n- em eventos de bloco (`text_*`, `thinking_*`, `toolcall_*`): atualiza a última mensagem do assistente, emite `message_update` com o `assistantMessageEvent` bruto\n- em terminal (`done`/`error`): resolve a mensagem final de `response.result()`, emite `message_end`\n\n`AgentSession` então consome esses eventos para comportamentos em nível de sessão:\n\n- TTSR observa `message_update.assistantMessageEvent` para `text_delta` e `toolcall_delta`\n- o guarda de edição em streaming inspeciona `toolcall_delta`/`toolcall_end` em chamadas `edit` e pode abortar antecipadamente\n- a persistência grava mensagens finalizadas em `message_end`\n- o auto-retry examina `stopReason === \"error\"` do assistente mais heurísticas de `errorMessage`\n\n## Responsabilidades unificadas vs específicas do provider\n\nUnificadas (contrato comum):\n\n- formato do evento (`AssistantMessageEvent`)\n- extração do resultado final (`done`/`error`)\n- regras de limitação de frequência + mesclagem de deltas\n- modelo de propagação de eventos agente/sessão\n\nEspecíficas do provider (não totalmente abstraídas):\n\n- taxonomias de eventos upstream e lógica de mapeamento\n- tabelas de tradução de razão de parada\n- convenções de ID de chamada de ferramenta\n- semânticas de blocos de raciocínio/pensamento e assinaturas\n- semânticas de tokens de uso e momento de disponibilidade\n- restrições de conversão de mensagem por API\n\n## Arquivos de implementação\n\n- [`../../ai/src/stream.ts`](../../packages/ai/src/stream.ts) — despacho de provider, mapeamento de opções, conexão de chave de API/sessão.\n- [`../../ai/src/utils/event-stream.ts`](../../packages/ai/src/utils/event-stream.ts) — fila de stream genérica + limitação de frequência de deltas do assistente.\n- [`../../ai/src/utils/json-parse.ts`](../../packages/ai/src/utils/json-parse.ts) — parsing de JSON parcial para argumentos de ferramentas em streaming.\n- [`../../ai/src/providers/anthropic.ts`](../../packages/ai/src/providers/anthropic.ts) — tradução de eventos Anthropic e acumulação de delta JSON de ferramentas.\n- [`../../ai/src/providers/openai-responses.ts`](../../packages/ai/src/providers/openai-responses.ts) — tradução de eventos OpenAI Responses e mapeamento de status.\n- [`../../ai/src/providers/google.ts`](../../packages/ai/src/providers/google.ts) — tradução de chunk-para-bloco do stream Gemini.\n- [`../../ai/src/providers/google-shared.ts`](../../packages/ai/src/providers/google-shared.ts) — mapeamento de razão de término Gemini e regras de conversão compartilhadas.\n- [`../../agent/src/agent-loop.ts`](../../packages/agent/src/agent-loop.ts) — consumo de stream do provider e ponte de `message_update`.\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — tratamento em nível de sessão de atualizações de streaming, aborto, retry e persistência.\n",
	"pt-br/providers/python-repl.md": "---\ntitle: Ferramenta Python e Runtime IPython\ndescription: >-\n  Runtime da ferramenta Python REPL com gerenciamento de kernel IPython,\n  execução e captura de saída.\nsidebar:\n  order: 3\n  label: Python e IPython\ni18n:\n  sourceHash: 70f0a034ecef\n  translator: machine\n---\n\n# Ferramenta Python e Runtime IPython\n\nEste documento descreve a pilha de execução Python atual em `packages/coding-agent`.\nAbrange o comportamento da ferramenta, ciclo de vida do kernel/gateway, tratamento de ambiente, semântica de execução, renderização de saída e modos de falha operacional.\n\n## Escopo e Arquivos Principais\n\n- Interface da ferramenta: `src/tools/python.ts`\n- Orquestração de kernel por sessão/chamada: `src/ipy/executor.ts`\n- Protocolo de kernel + integração com gateway: `src/ipy/kernel.ts`\n- Coordenador de gateway local compartilhado: `src/ipy/gateway-coordinator.ts`\n- Renderizador de modo interativo para execuções Python iniciadas pelo usuário: `src/modes/components/python-execution.ts`\n- Filtragem de runtime/ambiente e resolução de Python: `src/ipy/runtime.ts`\n\n## O que é a ferramenta Python\n\nA ferramenta `python` executa uma ou mais células Python através de um kernel respaldado pelo Jupyter Kernel Gateway (não por meio da criação direta de um processo `python -c` por célula).\n\nParâmetros da ferramenta:\n\n```ts\n{\n  cells: Array<{ code: string; title?: string }>;\n  timeout?: number; // segundos, limitado a 1..600, padrão 30\n  cwd?: string;\n  reset?: boolean; // reinicia o kernel apenas antes da primeira célula\n}\n```\n\nA ferramenta possui `concurrency = \"exclusive\"` por sessão, portanto as chamadas não se sobrepõem.\n\n## Ciclo de vida do gateway\n\n### Modos\n\nExistem dois caminhos de gateway:\n\n1. **Gateway externo** (`PI_PYTHON_GATEWAY_URL` definido)\n   - Utiliza a URL configurada diretamente.\n   - Autenticação opcional com `PI_PYTHON_GATEWAY_TOKEN`.\n   - Nenhum processo de gateway local é criado ou gerenciado.\n\n2. **Gateway local compartilhado** (caminho padrão)\n   - Utiliza um único processo compartilhado coordenado em `~/.xcsh/agent/python-gateway`.\n   - Arquivo de metadados: `gateway.json`\n   - Arquivo de bloqueio: `gateway.lock`\n   - Comando de inicialização:\n     - `python -m kernel_gateway`\n     - vinculado a `127.0.0.1:<porta-alocada>`\n     - verificação de integridade na inicialização: `GET /api/kernelspecs`\n\n### Coordenação do gateway local compartilhado\n\n`acquireSharedGateway()`:\n\n- Adquire um bloqueio de arquivo (`gateway.lock`) com heartbeat.\n- Reutiliza `gateway.json` se o PID estiver ativo e a verificação de integridade for aprovada.\n- Limpa informações/PIDs obsoletos quando necessário.\n- Inicia um novo gateway quando nenhum saudável existir.\n\n`releaseSharedGateway()` atualmente é uma operação sem efeito (o encerramento do kernel não derruba o gateway compartilhado).\n\n`shutdownSharedGateway()` encerra explicitamente o processo compartilhado e limpa os metadados do gateway.\n\n### Restrição importante\n\n`python.sharedGateway=false` é rejeitado na inicialização do kernel:\n\n- Erro: `Shared Python gateway required; local gateways are disabled`\n- Não existe modo de gateway local não compartilhado por processo.\n\n## Ciclo de vida do kernel\n\nCada execução utiliza um kernel criado via `POST /api/kernels` no gateway selecionado.\n\nSequência de inicialização do kernel:\n\n1. Verificação de disponibilidade (`checkPythonKernelAvailability`)\n2. Criar kernel (`/api/kernels`)\n3. Abrir websocket (`/api/kernels/:id/channels`)\n4. Inicializar ambiente do kernel (`cwd`, variáveis de ambiente, `sys.path`)\n5. Executar `PYTHON_PRELUDE`\n6. Carregar módulos de extensão de:\n   - usuário: `~/.xcsh/agent/modules/*.py`\n   - projeto: `<cwd>/.xcsh/modules/*.py` (substitui módulo de usuário com mesmo nome)\n\nEncerramento do kernel:\n\n- Exclui o kernel remoto via `DELETE /api/kernels/:id`\n- Fecha o websocket\n- Chama o gancho de liberação do gateway compartilhado (sem efeito atualmente)\n\n## Semântica de persistência de sessão\n\n`python.kernelMode` controla a reutilização do kernel:\n\n- `session` (padrão)\n  - Reutiliza sessões de kernel identificadas por identidade de sessão + cwd.\n  - A execução é serializada por sessão através de uma fila.\n  - Sessões ociosas são removidas após 5 minutos.\n  - No máximo 4 sessões; a mais antiga é removida em caso de estouro.\n  - Verificações de heartbeat detectam kernels mortos.\n  - Reinicialização automática permitida uma vez; falha repetida => falha definitiva.\n\n- `per-call`\n  - Cria um kernel novo para cada requisição de execução.\n  - Encerra o kernel após a requisição.\n  - Sem persistência de estado entre chamadas.\n\n### Comportamento de múltiplas células em uma única chamada de ferramenta\n\nAs células são executadas sequencialmente na mesma instância de kernel para aquela chamada de ferramenta.\n\nSe uma célula intermediária falhar:\n\n- O estado das células anteriores permanece na memória.\n- A ferramenta retorna um erro direcionado indicando qual célula falhou.\n- As células subsequentes não são executadas.\n\n`reset=true` se aplica apenas à execução da primeira célula naquela chamada.\n\n## Filtragem de ambiente e resolução de runtime\n\nO ambiente é filtrado antes de iniciar o runtime do gateway/kernel:\n\n- A lista de permissões inclui variáveis essenciais como `PATH`, `HOME`, variáveis de locale, `VIRTUAL_ENV`, `PYTHONPATH`, etc.\n- Prefixos permitidos: `LC_`, `XDG_`, `PI_`\n- A lista de bloqueio remove chaves de API comuns (OpenAI/Anthropic/Gemini/etc.)\n\nOrdem de seleção do runtime:\n\n1. Venv ativo/localizado (`VIRTUAL_ENV`, depois `<cwd>/.venv`, `<cwd>/venv`)\n2. Venv gerenciado em `~/.xcsh/python-env`\n3. `python` ou `python3` no PATH\n\nQuando um venv é selecionado, seu caminho bin/Scripts é inserido no início de `PATH`.\n\nA inicialização do ambiente do kernel dentro do Python também:\n\n- `os.chdir(cwd)`\n- injeta o mapa de ambiente fornecido em `os.environ`\n- garante que o cwd esteja em `sys.path`\n\n## Disponibilidade da ferramenta e seleção de modo\n\n`python.toolMode` (padrão `both`) + substituição opcional `PI_PY` controla a exposição:\n\n- `ipy-only`\n- `bash-only`\n- `both`\n\nValores aceitos por `PI_PY`:\n\n- `0` / `bash` -> `bash-only`\n- `1` / `py` -> `ipy-only`\n- `mix` / `both` -> `both`\n\nSe a verificação prévia do Python falhar, a criação da ferramenta é rebaixada para bash-only naquela sessão.\n\n## Fluxo de execução e cancelamento/timeout\n\n### Timeout no nível da ferramenta\n\nO timeout da ferramenta `python` é em segundos, padrão 30, limitado a `1..600`.\n\nA ferramenta combina:\n\n- sinal de cancelamento do chamador\n- sinal de cancelamento por timeout\n\ncom `AbortSignal.any(...)`.\n\n### Cancelamento de execução do kernel\n\nEm caso de cancelamento/timeout:\n\n- A execução é marcada como cancelada.\n- A interrupção do kernel é tentada via REST (`POST /interrupt`) e pelo canal de controle `interrupt_request`.\n- O resultado inclui `cancelled=true`.\n- O caminho de timeout anota a saída como `Command timed out after <n> seconds`.\n\n### Comportamento do stdin\n\nO stdin interativo não é suportado.\n\nSe o kernel emitir `input_request`:\n\n- A ferramenta registra `stdinRequested=true`\n- Emite texto explicativo\n- Envia `input_reply` vazio\n- A execução é tratada como falha na camada do executor\n\n## Captura e renderização de saída\n\n### Classes de saída capturadas\n\nA partir das mensagens do kernel:\n\n- `stream` -> fragmentos de texto simples\n- `display_data`/`execute_result` -> tratamento de exibição rica\n- `error` -> texto de traceback\n- MIME personalizado `application/x-xcsh-status` -> eventos de status estruturados\n\nPrecedência de MIME para exibição:\n\n1. `text/markdown`\n2. `text/plain`\n3. `text/html` (convertido para markdown básico)\n\nAdicionalmente capturados como saídas estruturadas:\n\n- `application/json` -> dados de árvore JSON\n- `image/png` -> payloads de imagem\n- `application/x-xcsh-status` -> eventos de status\n\n### Armazenamento e truncamento\n\nA saída é transmitida por meio de `OutputSink` e pode ser persistida no armazenamento de artefatos.\n\nOs resultados da ferramenta podem incluir metadados de truncamento e `artifact://<id>` para recuperação da saída completa.\n\n### Comportamento do renderizador\n\n- Renderizador da ferramenta (`python.ts`):\n  - exibe blocos de células de código com status por célula\n  - a pré-visualização recolhida tem padrão de 10 linhas\n  - suporta modo expandido para saída completa e detalhes de status mais ricos\n- Renderizador interativo (`python-execution.ts`):\n  - utilizado para execução Python iniciada pelo usuário no TUI\n  - a pré-visualização recolhida tem padrão de 20 linhas\n  - limita linhas individuais muito longas a 4000 caracteres para segurança na exibição\n  - exibe avisos de cancelamento/erro/truncamento\n\n## Suporte a gateway externo\n\nDefina:\n\n```bash\nexport PI_PYTHON_GATEWAY_URL=\"http://127.0.0.1:8888\"\n# Opcional:\nexport PI_PYTHON_GATEWAY_TOKEN=\"...\"\n```\n\nDiferenças de comportamento em relação ao gateway local compartilhado:\n\n- Sem arquivos locais de bloqueio/informação do gateway\n- Sem criação/encerramento de processo local\n- Verificações de integridade e operações CRUD de kernel executadas no endpoint externo\n- Falhas de autenticação são exibidas com orientação explícita sobre o token\n\n## Solução de problemas operacionais (modos de falha atuais)\n\n- **Ferramenta Python não disponível**\n  - Verifique `python.toolMode` / `PI_PY`.\n  - Se a verificação prévia falhar, o runtime volta para bash-only.\n\n- **Erros de disponibilidade do kernel**\n  - O modo local requer que tanto `kernel_gateway` quanto `ipykernel` sejam importáveis no runtime Python resolvido.\n  - Instale com:\n\n    ```bash\n    python -m pip install jupyter_kernel_gateway ipykernel\n    ```\n\n- **`python.sharedGateway=false` causa falha na inicialização**\n  - Isso é esperado com a implementação atual.\n\n- **Falhas de autenticação/acessibilidade do gateway externo**\n  - 401/403 -> defina `PI_PYTHON_GATEWAY_TOKEN`.\n  - timeout/inacessível -> verifique a URL/rede e a integridade do gateway.\n\n- **Execução trava e então expira por timeout**\n  - Aumente o `timeout` da ferramenta (máx. 600s) se a carga de trabalho for legítima.\n  - Para código travado, o cancelamento aciona a interrupção do kernel, mas o código do usuário pode ainda precisar ser refatorado.\n\n- **Prompts de stdin/input no código Python**\n  - `input()` não é suportado interativamente neste caminho de runtime; passe os dados programaticamente.\n\n- **Esgotamento de recursos (`EMFILE` / muitos arquivos abertos)**\n  - O gerenciador de sessões aciona a recuperação do gateway compartilhado (encerramento de sessão + reinicialização do gateway compartilhado).\n\n- **Erros de diretório de trabalho**\n  - A ferramenta valida que `cwd` existe e é um diretório antes da execução.\n\n## Variáveis de ambiente relevantes\n\n- `PI_PY` — substituição de exposição da ferramenta (mapeamento `bash-only`/`ipy-only`/`both` acima)\n- `PI_PYTHON_GATEWAY_URL` — utilizar gateway externo\n- `PI_PYTHON_GATEWAY_TOKEN` — token de autenticação opcional para gateway externo\n- `PI_PYTHON_SKIP_CHECK=1` — ignorar verificações prévias/de aquecimento do Python\n- `PI_PYTHON_IPC_TRACE=1` — registrar traces de envio/recebimento de IPC do kernel\n- `PI_DEBUG_STARTUP=1` — emitir marcadores de depuração da fase de inicialização\n",
	"pt-br/runtime-tools/bash-tool-runtime.md": "---\ntitle: Runtime da Ferramenta Bash\ndescription: >-\n  Bash tool runtime with shell process management, sandboxing, timeout, and\n  output streaming.\nsidebar:\n  order: 1\n  label: Ferramenta Bash\ni18n:\n  sourceHash: 18b12aa5dbd5\n  translator: machine\n---\n\n# Runtime da ferramenta Bash\n\nEste documento descreve o caminho de runtime da **ferramenta `bash`** utilizado por chamadas de ferramentas do agente, desde a normalização de comandos até a execução, truncamento/artefatos e renderização.\n\nTambém destaca onde o comportamento diverge no TUI interativo, modo de impressão, modo RPC e execução de shell iniciada pelo usuário com bang (`!`).\n\n## Escopo e superfícies de runtime\n\nExistem duas superfícies diferentes de execução bash no coding-agent:\n\n1. **Superfície de chamada de ferramenta** (`toolName: \"bash\"`): utilizada quando o modelo chama a ferramenta bash.\n   - Ponto de entrada: `BashTool.execute()`.\n2. **Superfície de comando bang do usuário** (`!cmd` a partir de entrada interativa ou comando RPC `bash`): caminho auxiliar em nível de sessão.\n   - Ponto de entrada: `AgentSession.executeBash()`.\n\nAmbas eventualmente utilizam `executeBash()` em `src/exec/bash-executor.ts` para execução sem PTY, mas apenas o caminho de chamada de ferramenta executa a lógica de normalização/interceptação e renderização de ferramenta.\n\n## Pipeline de chamada de ferramenta de ponta a ponta\n\n## 1) Normalização de entrada e mesclagem de parâmetros\n\n`BashTool.execute()` primeiro normaliza o comando bruto via `normalizeBashCommand()`:\n\n- extrai `| head -n N`, `| head -N`, `| tail -n N`, `| tail -N` no final em limites estruturados,\n- remove espaços em branco no início e no final,\n- mantém espaços em branco internos intactos.\n\nEm seguida, mescla os limites extraídos com os argumentos explícitos da ferramenta:\n\n- argumentos explícitos `head`/`tail` sobrescrevem os valores extraídos,\n- valores extraídos são apenas fallback.\n\n### Ressalva\n\nOs comentários em `bash-normalize.ts` mencionam a remoção de `2>&1`, mas a implementação atual não o remove. O comportamento em runtime ainda está correto (stdout/stderr já são mesclados), mas o comportamento de normalização é mais restrito do que os comentários sugerem.\n\n## 2) Interceptação opcional (caminho de comando bloqueado)\n\nSe `bashInterceptor.enabled` estiver ativado, `BashTool` carrega as regras das configurações e executa `checkBashInterception()` contra o comando normalizado.\n\nComportamento de interceptação:\n\n- o comando é bloqueado **apenas** quando:\n  - a regra regex corresponde, e\n  - a ferramenta sugerida está presente em `ctx.toolNames`.\n- regras regex inválidas são silenciosamente ignoradas.\n- ao bloquear, `BashTool` lança `ToolError` com a mensagem:\n  - `Blocked: ...`\n  - comando original incluído.\n\nPadrões de regras padrão (definidos no código) visam usos indevidos comuns:\n\n- leitores de arquivo (`cat`, `head`, `tail`, ...)\n- ferramentas de busca (`grep`, `rg`, ...)\n- localizadores de arquivo (`find`, `fd`, ...)\n- editores in-place (`sed -i`, `perl -i`, `awk -i inplace`)\n- escritas de redirecionamento shell (`echo ... > file`, redirecionamento heredoc)\n\n### Ressalva\n\n`InterceptionResult` inclui `suggestedTool`, mas `BashTool` atualmente expõe apenas o texto da mensagem (sem campo estruturado de ferramenta sugerida em `details`).\n\n## 3) Validação de CWD e limitação de timeout\n\n`cwd` é resolvido em relação ao cwd da sessão (`resolveToCwd`), então validado via `stat`:\n\n- caminho ausente -> `ToolError(\"Working directory does not exist: ...\")`\n- não é diretório -> `ToolError(\"Working directory is not a directory: ...\")`\n\nO timeout é limitado a `[1, 3600]` segundos e convertido para milissegundos.\n\n## 4) Alocação de artefato\n\nAntes da execução, a ferramenta aloca um caminho/id de artefato (melhor esforço) para armazenamento de saída truncada.\n\n- falha na alocação de artefato não é fatal (a execução continua sem arquivo de transbordamento de artefato),\n- id/caminho do artefato são passados para o caminho de execução para persistência de saída completa em caso de truncamento.\n\n## 5) Seleção de execução PTY vs não-PTY\n\n`BashTool` escolhe execução PTY apenas quando todas as condições são verdadeiras:\n\n- `bash.virtualTerminal === \"on\"`\n- `PI_NO_PTY !== \"1\"`\n- o contexto da ferramenta possui UI (`ctx.hasUI === true` e `ctx.ui` definido)\n\nCaso contrário, utiliza `executeBash()` não interativo.\n\nIsso significa que o modo de impressão e contextos RPC/ferramenta sem UI sempre usam não-PTY.\n\n## Motor de execução não interativo (`executeBash`)\n\n## Modelo de reutilização de sessão shell\n\n`executeBash()` armazena em cache instâncias nativas de `Shell` em um mapa global do processo, indexado por:\n\n- caminho do shell,\n- prefixo de comando configurado,\n- caminho do snapshot,\n- env do shell serializado,\n- chave de sessão do agente opcional.\n\nPara execuções em nível de sessão, `AgentSession.executeBash()` passa `sessionKey: this.sessionId`, isolando a reutilização por sessão.\n\nO caminho de chamada de ferramenta **não** passa `sessionKey`, então o escopo de reutilização é baseado na configuração/snapshot/env do shell.\n\n## Configuração do shell e comportamento de snapshot\n\nEm cada chamada, o executor carrega a configuração do shell das configurações (`shell`, `env`, `prefix` opcional).\n\nSe o shell selecionado inclui `bash`, ele tenta `getOrCreateSnapshot()`:\n\n- o snapshot captura aliases/funções/opções do rc do usuário,\n- a criação do snapshot é feita com melhor esforço,\n- falha recai em não usar snapshot.\n\nSe `prefix` estiver configurado, o comando se torna:\n\n```text\n<prefix> <command>\n```\n\n## Streaming e cancelamento\n\n`Shell.run()` transmite chunks para callback. O executor direciona cada chunk para `OutputSink` e callback `onChunk` opcional.\n\nCancelamento:\n\n- sinal de abort acionado dispara `shellSession.abort(...)`,\n- timeout do resultado nativo é mapeado para `cancelled: true` + texto de anotação,\n- cancelamento explícito similarmente retorna `cancelled: true` + anotação.\n\nNenhuma exceção é lançada dentro do executor para timeout/cancelamento; ele retorna `BashResult` estruturado e permite que o chamador mapeie a semântica de erro.\n\n## Caminho PTY interativo (`runInteractiveBashPty`)\n\nQuando PTY está habilitado, a ferramenta executa `runInteractiveBashPty()` que abre um componente de console overlay e controla um `PtySession` nativo.\n\nDestaques do comportamento:\n\n- terminal virtual xterm-headless renderiza viewport no overlay,\n- entrada do teclado é normalizada (incluindo sequências Kitty e tratamento de modo de cursor de aplicação),\n- `esc` durante a execução mata a sessão PTY,\n- redimensionamento do terminal é propagado para o PTY (`session.resize(cols, rows)`).\n\nPadrões de hardening de ambiente são injetados para execuções não assistidas:\n\n- paginadores desabilitados (`PAGER=cat`, `GIT_PAGER=cat`, etc.),\n- prompts de editor desabilitados (`GIT_EDITOR=true`, `EDITOR=true`, ...),\n- prompts de terminal/autenticação reduzidos (`GIT_TERMINAL_PROMPT=0`, `SSH_ASKPASS=/usr/bin/false`, `CI=1`),\n- flags de automação de gerenciador de pacotes/ferramenta para comportamento não interativo.\n\nA saída do PTY é normalizada (`CRLF`/`CR` para `LF`, `sanitizeText`) e escrita no `OutputSink`, incluindo suporte a transbordamento de artefato.\n\nEm caso de erro de inicialização/runtime do PTY, o sink recebe a linha `PTY error: ...` e o comando finaliza com código de saída indefinido.\n\n## Tratamento de saída: streaming, truncamento, transbordamento de artefato\n\nAmbos os caminhos PTY e não-PTY utilizam `OutputSink`.\n\n## Semântica do OutputSink\n\n- mantém um buffer de cauda em memória seguro para UTF-8 (`DEFAULT_MAX_BYTES`, atualmente 50KB),\n- rastreia total de bytes/linhas vistos,\n- se o caminho de artefato existir e a saída transbordar (ou arquivo já estiver ativo), escreve o stream completo no arquivo de artefato,\n- quando o limite de memória transborda, reduz o buffer em memória para a cauda (seguro em limites UTF-8),\n- marca `truncated` quando ocorre transbordamento/escrita em arquivo.\n\n`dump()` retorna:\n\n- `output` (possivelmente com prefixo anotado),\n- `truncated`,\n- `totalLines/totalBytes`,\n- `outputLines/outputBytes`,\n- `artifactId` se o arquivo de artefato estava ativo.\n\n### Ressalva sobre saída longa\n\nO truncamento em runtime é baseado em limite de bytes no `OutputSink` (50KB padrão). Ele não impõe um limite rígido de 2000 linhas neste caminho de código.\n\n## Atualizações ao vivo da ferramenta\n\nPara execução não-PTY, `BashTool` usa um `TailBuffer` separado para atualizações parciais e emite snapshots `onUpdate` enquanto o comando está em execução.\n\nPara execução PTY, a renderização ao vivo é tratada pelo overlay de UI personalizado, não por chunks de texto `onUpdate`.\n\n## Modelagem de resultado, metadados e mapeamento de erros\n\nApós a execução:\n\n1. Tratamento de `cancelled`:\n   - se o sinal de abort foi abortado -> lança `ToolAbortError` (semântica de abort),\n   - caso contrário -> lança `ToolError` (tratado como falha da ferramenta).\n2. PTY `timedOut` -> lança `ToolError`.\n3. aplica filtros head/tail ao texto de saída final (`applyHeadTail`, head depois tail).\n4. saída vazia se torna `(no output)`.\n5. anexa metadados de truncamento via `toolResult(...).truncationFromSummary(result, { direction: \"tail\" })`.\n6. Mapeamento de código de saída:\n   - código de saída ausente -> `ToolError(\"... missing exit status\")`\n   - saída não-zero -> `ToolError(\"... Command exited with code N\")`\n   - saída zero -> resultado de sucesso.\n\nEstrutura do payload de sucesso:\n\n- `content`: saída em texto,\n- `details.meta.truncation` quando truncado, incluindo:\n  - `direction`, `truncatedBy`, contagens totais/de saída de linhas+bytes,\n  - `shownRange`,\n  - `artifactId` quando disponível.\n\nComo ferramentas integradas são envolvidas com `wrapToolWithMetaNotice()`, o texto de aviso de truncamento é automaticamente anexado ao conteúdo de texto final (por exemplo: `Full: artifact://<id>`).\n\n## Caminhos de renderização\n\n## Renderizador de chamada de ferramenta (`bashToolRenderer`)\n\n`bashToolRenderer` é usado para mensagens de chamada de ferramenta (`toolCall` / `toolResult`):\n\n- modo colapsado mostra prévia truncada por linhas visuais,\n- modo expandido mostra todo o texto de saída disponível no momento,\n- linha de aviso inclui razão do truncamento e `artifact://<id>` quando truncado,\n- valor de timeout (dos argumentos) é mostrado na linha de metadados do rodapé.\n\n### Ressalva: expansão completa do artefato\n\n`BashRenderContext` possui `isFullOutput`, mas o construtor de contexto do renderizador atual não o define para resultados da ferramenta bash. A visualização expandida ainda usa o texto já presente no conteúdo do resultado (saída truncada/cauda) a menos que outro chamador forneça o conteúdo completo do artefato.\n\n## Componente de comando bang do usuário (`BashExecutionComponent`)\n\n`BashExecutionComponent` é para comandos `!` do usuário no modo interativo (não chamadas de ferramenta do modelo):\n\n- transmite chunks ao vivo,\n- prévia colapsada mantém as últimas 20 linhas lógicas,\n- limite de 4000 caracteres por linha,\n- mostra avisos de truncamento + artefato quando metadados estão presentes,\n- marca estados de cancelamento/erro/saída separadamente.\n\nEste componente é conectado por `CommandController.handleBashCommand()` e alimentado por `AgentSession.executeBash()`.\n\n## Diferenças de comportamento específicas por modo\n\n| Superfície                     | Caminho de entrada                                    | Elegível para PTY                                                      | UX de saída ao vivo                                                       | Exposição de erros                               |\n| ------------------------------ | ----------------------------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------ |\n| Chamada de ferramenta interativa | `BashTool.execute`                                  | Sim, quando `bash.virtualTerminal=on` e UI existe e `PI_NO_PTY!=1`     | Overlay PTY (interativo) ou atualizações de cauda transmitidas            | Erros de ferramenta tornam-se `toolResult.isError` |\n| Chamada de ferramenta em modo impressão | `BashTool.execute`                            | Não (sem contexto de UI)                                               | Sem overlay TUI; saída aparece no stream de eventos/fluxo de texto final do assistente | Mesmo mapeamento de erros de ferramenta          |\n| Chamada de ferramenta RPC (tooling do agente) | `BashTool.execute`                         | Geralmente sem UI -> não-PTY                                           | Eventos/resultados de ferramenta estruturados                              | Mesmo mapeamento de erros de ferramenta          |\n| Comando bang interativo (`!`)  | `AgentSession.executeBash` + `BashExecutionComponent` | Não (usa executor diretamente)                                         | Componente dedicado de execução bash                                       | Controller captura exceções e mostra erro na UI  |\n| Comando RPC `bash`             | `rpc-mode` -> `session.executeBash`                   | Não                                                                    | Retorna `BashResult` diretamente                                           | Consumidor trata os campos retornados            |\n\n## Ressalvas operacionais\n\n- O interceptador só bloqueia comandos quando a ferramenta sugerida está atualmente disponível no contexto.\n- Se a alocação de artefato falhar, o truncamento ainda ocorre, mas nenhuma referência `artifact://` está disponível.\n- O cache de sessão shell não possui evição explícita neste módulo; o tempo de vida é delimitado pelo processo.\n- As superfícies de timeout PTY e não-PTY diferem:\n  - PTY expõe campo de resultado `timedOut` explícito,\n  - não-PTY mapeia timeout em resumo `cancelled + annotation`.\n\n## Arquivos de implementação\n\n- [`src/tools/bash.ts`](../../packages/coding-agent/src/tools/bash.ts) — ponto de entrada da ferramenta, normalização/interceptação, seleção PTY/não-PTY, mapeamento de resultado/erro, renderizador da ferramenta bash.\n- [`src/tools/bash-normalize.ts`](../../packages/coding-agent/src/tools/bash-normalize.ts) — normalização de comando e filtragem head/tail pós-execução.\n- [`src/tools/bash-interceptor.ts`](../../packages/coding-agent/src/tools/bash-interceptor.ts) — correspondência de regras do interceptador e mensagens de comando bloqueado.\n- [`src/exec/bash-executor.ts`](../../packages/coding-agent/src/exec/bash-executor.ts) — executor não-PTY, reutilização de sessão shell, conexão de cancelamento, integração com output sink.\n- [`src/tools/bash-interactive.ts`](../../packages/coding-agent/src/tools/bash-interactive.ts) — runtime PTY, overlay de UI, normalização de entrada, padrões de env não interativo.\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — truncamento/transbordamento de artefato do `OutputSink` e metadados de resumo.\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — auxiliares de alocação de artefato e buffer de cauda para streaming.\n- [`src/tools/output-meta.ts`](../../packages/coding-agent/src/tools/output-meta.ts) — formato de metadados de truncamento + wrapper de injeção de aviso.\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — `executeBash` em nível de sessão, gravação de mensagens, ciclo de vida de abort.\n- [`src/modes/components/bash-execution.ts`](../../packages/coding-agent/src/modes/components/bash-execution.ts) — componente de execução do comando interativo `!`.\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts) — conexão para stream/conclusão de atualização da UI do comando interativo `!`.\n- [`src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts) — superfície de comandos RPC `bash` e `abort_bash`.\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — resolução de `artifact://<id>`.\n",
	"pt-br/runtime-tools/context-command.md": "---\ntitle: Contextos F5 XC\ndescription: >-\n  Conecte o xcsh a tenants do F5 Distributed Cloud -- crie, alterne e gerencie\n  contextos de autenticação.\nsidebar:\n  order: 1\n  label: Contextos F5 XC\ni18n:\n  sourceHash: a9cccbc338f0\n  translator: machine\n---\n\n# Contextos F5 XC\n\nO xcsh se conecta ao F5 Distributed Cloud por meio de **contextos** -- conjuntos de credenciais nomeados que vinculam uma URL de tenant, um token de API e um namespace. Se você já usou `kubectl config use-context` ou `kubectx`, o fluxo de trabalho é idêntico: crie um contexto, alterne entre eles pelo nome e use `-` para voltar ao anterior.\n\n## Primeiros passos\n\n### 1. Crie seu primeiro contexto\n\nVocê precisa de três informações do seu console F5 XC: a URL do tenant, um token de API e, opcionalmente, um namespace.\n\n```\n/context create production https://acme.console.ves.volterra.io p12k3-your-api-token\n```\n\n```\nContext 'production' created. Use /context activate production to switch to it.\n```\n\nOu use o assistente guiado se preferir prompts passo a passo:\n\n```\n/context wizard\n```\n\n### 2. Ative-o\n\n```\n/context production\n```\n\n```\n╭─ production ─────────────────────────────────────────────────╮\n│ XCSH_TENANT     acme                                         │\n│ XCSH_API_URL    https://acme.console.ves.volterra.io         │\n│ XCSH_API_TOKEN  ...oken                                      │\n│ Status          Connected (312ms)                            │\n├─ Environment ────────────────────────────────────────────────┤\n│ XCSH_NAMESPACE  default                                      │\n╰──────────────────────────────────────────────────────────────╯\n```\n\nUma vez ativado, o xcsh injeta as credenciais do tenant na sua sessão. O agente agora pode fazer chamadas à API do F5 XC, e a linha de status mostra o contexto ativo.\n\n### 3. Adicione mais contextos e alterne entre eles\n\n```\n/context create staging https://staging.console.ves.volterra.io p12k3-staging-token\n```\n\nAlterne pelo nome -- nenhum subcomando verbal é necessário:\n\n```\n/context staging\n```\n\nVolte ao contexto anterior (estilo `cd -`):\n\n```\n/context -\n```\n\nChamar `/context -` duas vezes retorna você ao ponto de partida.\n\n### 4. Veja o que você tem\n\n```\n/context\n```\n\n```\n  production           https://acme.console.ves.volterra.io\n* staging              https://staging.console.ves.volterra.io\n```\n\nO `*` marca o contexto ativo.\n\n## Comandos do dia a dia\n\n| Comando | O que faz |\n|---|---|\n| `/context` | Lista todos os contextos |\n| `/context <name>` | Alterna para um contexto |\n| `/context -` | Alterna para o contexto anterior |\n| `/context show` | Mostra detalhes do contexto ativo (tokens mascarados) |\n| `/context status` | Mostra o status atual de autenticação |\n\n## Ciclo de vida do contexto\n\n| Comando | O que faz |\n|---|---|\n| `/context create <name> <url> <token> [namespace]` | Cria um contexto |\n| `/context delete <name> --confirm` | Exclui um contexto (requer `--confirm`) |\n| `/context rename <old> <new>` | Renomeia um contexto |\n| `/context validate <name>` | Testa credenciais sem alternar |\n| `/context export [name] [--include-token]` | Exporta como JSON (tokens mascarados por padrão) |\n| `/context import <path-or-json> [--overwrite]` | Importa de arquivo ou JSON inline |\n| `/context wizard` | Configuração interativa guiada |\n\n## Alternando namespaces\n\nCada contexto possui um namespace padrão. Alterne-o sem mudar o contexto:\n\n```\n/context namespace system\n```\n\nO autocompletar com Tab oferece nomes de namespace do tenant ativo.\n\n## Variáveis de ambiente em contextos\n\nContextos podem carregar variáveis de ambiente extras que são injetadas na sua sessão ao serem ativados. Útil para configurações por tenant que não fazem parte do conjunto de credenciais.\n\n```\n/context set CUSTOM_HEADER=x-acme-trace\n/context set LOG_LEVEL=debug\n/context env list\n/context unset LOG_LEVEL\n```\n\nAliases: `add` = `set`, `remove`/`clear` = `unset`.\n\n## Autocompletar com Tab\n\nDigite `/context ` e pressione Tab. O menu suspenso mostra:\n\n1. **Nomes de contexto** -- com dicas de URL do tenant, para que você possa distinguir os tenants\n2. **`-`** -- aparece quando você já alternou antes, mostrando para qual contexto você voltaria\n3. **Subcomandos** -- `list`, `create`, `delete`, etc.\n\nNomes de contexto aparecem primeiro porque alternar é a ação mais comum.\n\nAutocompletar no nível de subcomando também funciona: `/context activate <Tab>` completa nomes de contexto, `/context namespace <Tab>` completa namespaces, `/context unset <Tab>` completa chaves de variáveis de ambiente conhecidas.\n\n## Regras de nomenclatura\n\nNomes de contexto devem ter de 1 a 64 caracteres: letras, dígitos, hífens, sublinhados.\n\nNomes que colidem com subcomandos são rejeitados:\n\n```\n/context create list https://example.com tok\n```\n\n```\nError: Context name 'list' conflicts with a /context subcommand. Choose a different name.\n```\n\nO conjunto completo de nomes reservados: `list`, `show`, `status`, `create`, `delete`, `rename`, `namespace`, `env`, `set`, `unset`, `add`, `remove`, `clear`, `activate`, `validate`, `export`, `import`, `wizard`, `help`. A comparação não diferencia maiúsculas de minúsculas.\n\n## Substituição por variáveis de ambiente\n\nSe `XCSH_API_URL` e `XCSH_API_TOKEN` estiverem definidos no ambiente do seu shell antes de iniciar o xcsh, eles têm precedência sobre qualquer contexto. Isso é útil para pipelines de CI/CD ou sessões avulsas onde você não deseja criar um contexto persistente.\n\nAo operar nesse modo, `/context` mostra as credenciais obtidas do ambiente com o rótulo `(via env vars)`.\n\n## Comportamento do contexto anterior\n\n- **Escopo de sessão**: o contexto anterior é redefinido quando você reinicia o xcsh. Ele não é persistido em disco.\n- **Ping-pong**: `/context -` duas vezes retorna você ao ponto de partida.\n- **Seguro em mutações**: se você excluir o contexto anterior, o ponteiro é limpo. Se você renomeá-lo, o ponteiro acompanha o novo nome.\n- **Reativação é um no-op**: `/context production` quando já está em `production` não redefine o ponteiro anterior.\n\n## Convenções de design\n\nA experiência do `/context` segue:\n\n- **kubectx**: `kubectx <name>` para alternar, `kubectx -` para o anterior, `kubectx` sem argumentos para listar\n- **kubectl**: `kubectl config use-context` para a forma explícita\n- **Shell**: `cd -` / `OLDPWD` para rastreamento do diretório anterior\n",
	"pt-br/runtime-tools/custom-tools.md": "---\ntitle: Ferramentas Personalizadas\ndescription: >-\n  Registro de ferramentas personalizadas, definição de esquema e pipeline de\n  execução para estender o agente.\nsidebar:\n  order: 4\n  label: Ferramentas personalizadas\ni18n:\n  sourceHash: 5f4a441fc2e2\n  translator: machine\n---\n\n# Ferramentas Personalizadas\n\nFerramentas personalizadas são funções chamáveis pelo modelo que se conectam ao mesmo pipeline de execução de ferramentas que as ferramentas integradas.\n\nUma ferramenta personalizada é um módulo TypeScript/JavaScript que exporta uma factory. A factory recebe uma API do host (`CustomToolAPI`) e retorna uma ferramenta ou um array de ferramentas.\n\n## O que isso é (e o que não é)\n\n- **Ferramenta personalizada**: chamável pelo modelo durante um turno (`execute` + esquema TypeBox).\n- **Extensão**: framework de ciclo de vida/eventos que pode registrar ferramentas e interceptar/modificar eventos.\n- **Hook**: scripts externos de pré/pós comando.\n- **Skill**: pacote estático de orientação/contexto, não código de ferramenta executável.\n\nSe você precisa que o modelo chame código diretamente, use uma ferramenta personalizada.\n\n## Caminhos de integração no código atual\n\nExistem dois estilos de integração ativos:\n\n1. **Ferramentas personalizadas fornecidas pelo SDK** (`options.customTools`)\n   - Encapsuladas em ferramentas do agente via `CustomToolAdapter` ou wrappers de extensão.\n   - Sempre incluídas no conjunto inicial de ferramentas ativas no bootstrap do SDK.\n\n2. **Módulos descobertos no sistema de arquivos via API de carregamento** (`discoverAndLoadCustomTools` / `loadCustomTools`)\n   - Expostos como APIs de biblioteca em `src/extensibility/custom-tools/loader.ts`.\n   - O código host pode chamá-los para descobrir e carregar módulos de ferramentas a partir de caminhos de configuração/provedor/plugin.\n\n```text\nModel tool call flow\n\nLLM tool call\n   │\n   ▼\nTool registry (built-ins + custom tool adapters)\n   │\n   ▼\nCustomTool.execute(toolCallId, params, onUpdate, ctx, signal)\n   │\n   ├─ onUpdate(...)  -> streamed partial result\n   └─ return result  -> final tool content/details\n```\n\n## Locais de descoberta (API de carregamento)\n\n`discoverAndLoadCustomTools(configuredPaths, cwd, builtInToolNames)` mescla:\n\n1. Provedores de capacidade (`toolCapability`), incluindo:\n   - Configuração nativa OMP (`~/.xcsh/agent/tools`, `.xcsh/tools`)\n   - Configuração Claude (`~/.claude/tools`, `.claude/tools`)\n   - Configuração Codex (`~/.codex/tools`, `.codex/tools`)\n   - Provedor de cache de plugins do marketplace Claude\n2. Manifestos de plugins instalados (`~/.xcsh/plugins/node_modules/*` via carregador de plugins)\n3. Caminhos configurados explicitamente passados ao carregador\n\n### Comportamento importante\n\n- Caminhos resolvidos duplicados são deduplicados.\n- Conflitos de nomes de ferramentas são rejeitados contra ferramentas integradas e ferramentas personalizadas já carregadas.\n- Arquivos `.md` e `.json` são descobertos como metadados de ferramentas por alguns provedores, mas o carregador de módulos executáveis os rejeita como ferramentas executáveis.\n- Caminhos configurados relativos são resolvidos a partir de `cwd`; `~` é expandido.\n\n## Contrato do módulo\n\nUm módulo de ferramenta personalizada deve exportar uma função (exportação padrão preferida):\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = (pi) => ({\n name: \"repo_stats\",\n label: \"Repo Stats\",\n description: \"Counts tracked TypeScript files\",\n parameters: pi.typebox.Type.Object({\n  glob: pi.typebox.Type.Optional(pi.typebox.Type.String({ default: \"**/*.ts\" })),\n }),\n\n async execute(toolCallId, params, onUpdate, ctx, signal) {\n  onUpdate?.({\n   content: [{ type: \"text\", text: \"Scanning files...\" }],\n   details: { phase: \"scan\" },\n  });\n\n  const result = await pi.exec(\"git\", [\"ls-files\", params.glob ?? \"**/*.ts\"], { signal, cwd: pi.cwd });\n  if (result.killed) {\n   throw new Error(\"Scan was cancelled\");\n  }\n  if (result.code !== 0) {\n   throw new Error(result.stderr || \"git ls-files failed\");\n  }\n\n  const files = result.stdout.split(\"\\n\").filter(Boolean);\n  return {\n   content: [{ type: \"text\", text: `Found ${files.length} files` }],\n   details: { count: files.length, sample: files.slice(0, 10) },\n  };\n },\n\n onSession(event) {\n  if (event.reason === \"shutdown\") {\n   // cleanup resources if needed\n  }\n },\n});\n\nexport default factory;\n```\n\nTipo de retorno da factory:\n\n- `CustomTool`\n- `CustomTool[]`\n- `Promise<CustomTool | CustomTool[]>`\n\n## Superfície da API passada às factories (`CustomToolAPI`)\n\nDe `types.ts` e `loader.ts`:\n\n- `cwd`: diretório de trabalho do host\n- `exec(command, args, options?)`: auxiliar de execução de processos\n- `ui`: contexto de UI (pode ser no-op em modos headless)\n- `hasUI`: `false` em fluxos não interativos\n- `logger`: logger de arquivo compartilhado\n- `typebox`: `@sinclair/typebox` injetado\n- `pi`: exportações de `@f5-sales-demo/xcsh` injetadas\n- `pushPendingAction(action)`: registra uma ação de pré-visualização para a ferramenta oculta `resolve` (`docs/resolve-tool-runtime.md`)\n\nO carregador inicia com um contexto de UI no-op e requer que o código host chame `setUIContext(...)` quando a UI real estiver pronta.\n\n## Contrato de execução e tipagem\n\nAssinatura de `CustomTool.execute`:\n\n```ts\nexecute(toolCallId, params, onUpdate, ctx, signal)\n```\n\n- `params` é tipado estaticamente a partir do seu esquema TypeBox via `Static<TParams>`.\n- A validação dos argumentos em tempo de execução ocorre antes da execução no loop do agente.\n- `onUpdate` emite resultados parciais para streaming na UI.\n- `ctx` inclui estado de sessão/modelo e um auxiliar `abort()`.\n- `signal` carrega o cancelamento.\n\n`CustomToolAdapter` faz a ponte para a interface de ferramenta do agente e encaminha as chamadas na ordem correta de argumentos.\n\n## Como as ferramentas são expostas ao modelo\n\n- As ferramentas são encapsuladas em instâncias `AgentTool` (`CustomToolAdapter` ou wrappers de extensão).\n- Elas são inseridas no registro de ferramentas da sessão por nome.\n- No bootstrap do SDK, ferramentas personalizadas e registradas por extensão são forçosamente incluídas no conjunto ativo inicial.\n- O CLI `--tools` atualmente valida apenas nomes de ferramentas integradas; a inclusão de ferramentas personalizadas é tratada através dos caminhos de descoberta/registro e opções do SDK.\n\n## Hooks de renderização\n\nHooks de renderização opcionais:\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\nComportamento em tempo de execução na TUI:\n\n- Se os hooks existirem, a saída da ferramenta é renderizada dentro de um contêiner `Box`.\n- `renderResult` recebe `{ expanded, isPartial, spinnerFrame? }`.\n- Erros do renderizador são capturados e registrados em log; a UI recorre à renderização de texto padrão.\n\n## Tratamento de sessão/estado\n\nO `onSession(event, ctx)` opcional recebe eventos de ciclo de vida da sessão, incluindo:\n\n- `start`, `switch`, `branch`, `tree`, `shutdown`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`, `todo_reminder`\n\nUse `ctx.sessionManager` para reconstruir o estado a partir do histórico quando o contexto de branch/sessão mudar.\n\n## Semântica de falhas e cancelamento\n\n### Falhas síncronas/assíncronas\n\n- Lançar exceções (ou promises rejeitadas) em `execute` é tratado como falha da ferramenta.\n- O runtime do agente converte falhas em mensagens de resultado da ferramenta com `isError: true` e conteúdo de texto do erro.\n- Com wrappers de extensão, handlers de `tool_result` podem ainda reescrever conteúdo/detalhes e até sobrescrever o status de erro.\n\n### Cancelamento\n\n- O abort do agente se propaga através do `AbortSignal` para `execute`.\n- Encaminhe `signal` para trabalho de subprocesso (`pi.exec(..., { signal })`) para cancelamento cooperativo.\n- `ctx.abort()` permite que uma ferramenta solicite o abort da operação atual do agente.\n\n### Erros em onSession\n\n- Erros em `onSession` são capturados e registrados como avisos; eles não causam crash na sessão.\n\n## Restrições reais para considerar no design\n\n- Nomes de ferramentas devem ser globalmente únicos no registro ativo.\n- Prefira saídas determinísticas e formatadas conforme o esquema em `details` para reconstrução de renderizador/estado.\n- Proteja o uso de UI com `pi.hasUI`.\n- Trate `.md`/`.json` em diretórios de ferramentas como metadados, não como módulos executáveis.\n",
	"pt-br/runtime-tools/notebook-tool-runtime.md": "---\ntitle: Componentes Internos do Runtime da Ferramenta Notebook\ndescription: >-\n  Runtime da ferramenta de notebook Jupyter com execução de células, ciclo de\n  vida do kernel e renderização de saída.\nsidebar:\n  order: 2\n  label: Ferramenta Notebook\ni18n:\n  sourceHash: c1bafcb245e4\n  translator: machine\n---\n\n# Componentes internos do runtime da ferramenta Notebook\n\nEste documento descreve a implementação atual da ferramenta `notebook` e sua relação com o runtime Python suportado por kernel.\n\nA distinção crítica: **`notebook` é um editor JSON de notebooks, não um executor de notebooks**. Ela edita os fontes das células de arquivos `.ipynb` diretamente; não inicia nem se comunica com um kernel Python.\n\n## Arquivos de implementação\n\n- [`src/tools/notebook.ts`](../../packages/coding-agent/src/tools/notebook.ts)\n- [`src/ipy/executor.ts`](../../packages/coding-agent/src/ipy/executor.ts)\n- [`src/ipy/kernel.ts`](../../packages/coding-agent/src/ipy/kernel.ts)\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts)\n- [`src/tools/python.ts`](../../packages/coding-agent/src/tools/python.ts)\n\n## 1) Fronteira do runtime: edição vs. execução\n\n## Ferramenta `notebook` (`src/tools/notebook.ts`)\n\n- Suporta `action: edit | insert | delete` em um arquivo `.ipynb`.\n- Resolve o caminho relativo ao CWD da sessão (`resolveToCwd`).\n- Carrega o JSON do notebook, valida o array `cells`, valida os limites de `cell_index`.\n- Aplica edições de fonte na memória e grava o JSON completo do notebook de volta com `JSON.stringify(notebook, null, 1)`.\n- Retorna resumo textual + `details` estruturado (`action`, `cellIndex`, `cellType`, `totalCells`, `cellSource`).\n\nNão existe ciclo de vida de kernel nesta ferramenta:\n\n- nenhuma aquisição de gateway\n- nenhum ID de sessão de kernel\n- nenhum `execute_request`\n- nenhum fragmento de stream proveniente de canais do kernel\n- nenhuma captura de exibição rica (`image/png`, exibição JSON, status MIME)\n\n## Caminho de execução similar a notebook (`src/tools/python.ts` + `src/ipy/*`)\n\nQuando o agente precisa executar código Python no estilo de célula (células sequenciais, estado persistente, exibições ricas), isso passa pela ferramenta **`python`**, não pela `notebook`.\n\nÉ nesse caminho que residem os modos de kernel, o comportamento de reinicialização/cancelamento, o streaming de fragmentos e o truncamento de artefatos de saída.\n\n## 2) Semânticas de tratamento de células do notebook (ferramenta `notebook`)\n\n## Normalização de fonte\n\n`content` é dividido em `source: string[]` com preservação de quebras de linha:\n\n- cada linha não-final mantém o `\\n` ao final\n- a linha final não tem quebra de linha forçada ao final\n\nIsso espelha as convenções do JSON de notebooks e evita concatenação acidental de linhas em edições posteriores.\n\n## Comportamento das ações\n\n- `edit`\n  - substitui `cells[cell_index].source`\n  - preserva o `cell_type` existente\n- `insert`\n  - insere em `[0..cellCount]`\n  - `cell_type` tem como padrão `code`\n  - células de código inicializam `execution_count: null` e `outputs: []`\n  - células markdown inicializam apenas `metadata` + `source`\n- `delete`\n  - remove `cells[cell_index]`\n  - retorna o `source` removido em details para preview do renderizador\n\n## Superfícies de erro\n\nFalhas críticas são lançadas para:\n\n- arquivo de notebook ausente\n- JSON inválido\n- `cells` ausente ou não-array\n- índice fora do intervalo (inserção e não-inserção têm intervalos válidos diferentes)\n- `content` ausente para `edit`/`insert`\n\nEsses erros se tornam respostas de ferramenta `Error:` na camada superior; o renderizador usa o caminho do notebook + texto de erro formatado.\n\n## 3) Semânticas de sessão de kernel (onde elas realmente existem)\n\nAs semânticas de kernel são implementadas em `executePython` / `PythonKernel` e se aplicam à ferramenta `python`.\n\n## Modos\n\n`PythonKernelMode`:\n\n- `session` (padrão)\n  - kernels armazenados em cache no mapa `kernelSessions`\n  - máximo de 4 sessões; a mais antiga é removida ao ultrapassar o limite\n  - limpeza de sessões ociosas/mortas a cada 30s, timeout após 5 minutos\n  - fila por sessão serializa a execução (`session.queue`)\n- `per-call`\n  - cria kernel para a requisição\n  - executa\n  - sempre encerra o kernel no bloco `finally`\n\n## Comportamento de reinicialização\n\nA ferramenta `python` passa `reset` apenas para a primeira célula em uma chamada de múltiplas células; células posteriores sempre executam com `reset: false`.\n\n## Morte/reinicialização/nova tentativa do kernel\n\nNo modo de sessão (`withKernelSession`):\n\n- kernel morto detectado por heartbeat (verificação `kernel.isAlive()` a cada 5s) ou falha na execução.\n- estado morto pré-execução aciona `restartKernelSession`.\n- caminho de falha durante execução realiza nova tentativa uma vez: reinicia o kernel, reexecuta o handler.\n- `restartCount > 1` na mesma sessão lança `Python kernel restarted too many times in this session`.\n\nComportamento de nova tentativa na inicialização:\n\n- criação de kernel no gateway compartilhado realiza nova tentativa uma vez em `SharedGatewayCreateError` com HTTP 5xx.\n\nRecuperação por esgotamento de recursos:\n\n- detecta falhas do tipo `EMFILE`/`ENFILE`/\"Too many open files\"\n- limpa as sessões rastreadas\n- chama `shutdownSharedGateway()`\n- realiza nova tentativa de criação de sessão de kernel uma vez\n\n## 4) Injeção de variáveis de ambiente/sessão\n\nA inicialização do kernel recebe mapa de ambiente opcional do executor:\n\n- `PI_SESSION_FILE` (caminho do arquivo de estado da sessão)\n- `ARTIFACTS` (diretório de artefatos)\n\n`PythonKernel.#initializeKernelEnvironment(...)` então executa o script de inicialização dentro do kernel para:\n\n- `os.chdir(cwd)`\n- injetar entradas de ambiente em `os.environ`\n- adicionar cwd ao início de `sys.path` se ausente\n\nImplicação:\n\n- helpers de prelúdio que leem contexto de sessão ou artefato dependem dessas variáveis de ambiente no estado do processo Python.\n\n## 5) Tratamento de streaming/fragmentos e exibição (caminho com suporte a kernel)\n\nO cliente de kernel processa mensagens do protocolo Jupyter por execução:\n\n- `stream` -> fragmento de texto para `onChunk`\n- `execute_result` / `display_data` ->\n  - texto de exibição escolhido por precedência de MIME: `text/markdown` > `text/plain` > `text/html` convertido\n  - saídas estruturadas capturadas separadamente:\n    - `application/json` -> `{ type: \"json\" }`\n    - `image/png` -> `{ type: \"image\" }`\n    - `application/x-xcsh-status` -> `{ type: \"status\" }` (sem emissão de texto)\n- `error` -> texto de traceback enviado ao stream de fragmentos + metadados de erro estruturados\n- `input_request` -> emite texto de aviso de stdin, envia `input_reply` vazio, marca stdin como solicitado\n- a conclusão aguarda tanto `execute_reply` quanto `status=idle` do kernel\n\nCancelamento/timeout:\n\n- sinal de cancelamento aciona `interrupt()` (REST `/interrupt` + `interrupt_request` pelo canal de controle)\n- resultado marca `cancelled=true`\n- caminho de timeout anota a saída com `Command timed out after <n> seconds`\n\n## 6) Comportamento de truncamento e artefatos\n\n`OutputSink` em `src/session/streaming-output.ts` é utilizado pelos caminhos de execução de kernel (`executeWithKernel`):\n\n- sanitiza cada fragmento (`sanitizeText`)\n- rastreia total de linhas e bytes de saída\n- arquivo de spill de artefato opcional (`artifactPath`, `artifactId`)\n- quando o buffer em memória excede o limite (`DEFAULT_MAX_BYTES`, a menos que seja sobrescrito):\n  - marca como truncado\n  - mantém os bytes finais na memória (limite seguro UTF-8)\n  - pode despejar o stream completo para o sink de artefato\n\n`dump()` retorna:\n\n- texto de saída visível (possivelmente truncado ao final)\n- flag de truncamento + contagens\n- ID do artefato (para referências `artifact://<id>`)\n\nA ferramenta `python` converte esses metadados em avisos de truncamento de resultado e avisos na TUI.\n\nA ferramenta `notebook` **não** utiliza `OutputSink`; ela não possui pipeline de stream/truncamento de artefatos porque não executa código.\n\n## 7) Premissas do renderizador e formatação\n\n## Renderizador de notebook (`notebookToolRenderer`)\n\n- visão de chamada: linha de status com ação + caminho do notebook + metadados de célula/tipo\n- visão de resultado:\n  - resumo de sucesso derivado de `details`\n  - `cellSource` renderizado via `renderCodeCell`\n  - células markdown definem dica de linguagem `markdown`; outras células não têm substituição de linguagem explícita\n  - limite de preview de código recolhido é `PREVIEW_LIMITS.COLLAPSED_LINES * 2`\n  - suporta modo expandido via opções de renderização compartilhadas\n  - utiliza cache de renderização com chave por largura + estado expandido\n\nPremissa de renderização de erros:\n\n- se o primeiro conteúdo de texto começa com `Error:`, o renderizador formata como bloco de erro de notebook.\n\n## Renderizador Python (para saída de execução real)\n\nA renderização de execução suportada por kernel espera:\n\n- transições de status por célula (`pending/running/complete/error`)\n- seção opcional de evento de status estruturado\n- árvores de saída JSON opcionais\n- avisos de truncamento + ponteiro opcional `artifact://<id>`\n\nEsse comportamento do renderizador não tem relação com os resultados de edição JSON do `notebook`, exceto pelo fato de que ambos reutilizam primitivas TUI compartilhadas.\n\n## 8) Divergência em relação ao comportamento da ferramenta Python simples\n\nSe \"ferramenta Python simples\" significa o caminho de execução `python`:\n\n- `python` executa código em um kernel, persiste estado por modo, faz streaming de fragmentos, captura exibições ricas, trata interrupções/timeouts e suporta truncamento de saída/artefatos.\n- `notebook` realiza apenas mutações determinísticas de JSON de notebook; sem execução, sem estado de kernel, sem stream de fragmentos, sem saídas de exibição, sem pipeline de artefatos.\n\nSe um fluxo de trabalho necessita de ambos:\n\n1. editar o fonte do notebook com `notebook`\n2. executar células de código via `python` (passando o código manualmente), não através do `notebook`\n\nA implementação atual não fornece uma única ferramenta que tanto modifique o `.ipynb` quanto execute células do notebook através do contexto de kernel.\n",
	"pt-br/runtime-tools/resolve-tool-runtime.md": "---\ntitle: Internos do Runtime da Ferramenta Resolve\ndescription: >-\n  Runtime da ferramenta resolve para resolução de caminhos de arquivo, busca de\n  conteúdo e acesso a recursos baseados em URL.\nsidebar:\n  order: 3\n  label: Ferramenta resolve\ni18n:\n  sourceHash: 06e8be8c5a3c\n  translator: machine\n---\n\n# Internos do runtime da ferramenta resolve\n\nEste documento explica como os fluxos de trabalho de pré-visualização/aplicação são modelados no coding-agent e como ferramentas personalizadas podem participar via `pushPendingAction`.\n\n## Escopo e arquivos principais\n\n- [`src/tools/resolve.ts`](../../packages/coding-agent/src/tools/resolve.ts)\n- [`src/tools/pending-action.ts`](../../packages/coding-agent/src/tools/pending-action.ts)\n- [`src/tools/ast-edit.ts`](../../packages/coding-agent/src/tools/ast-edit.ts)\n- [`src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts)\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n\n## O que `resolve` faz\n\n`resolve` é uma ferramenta oculta que finaliza uma ação de pré-visualização pendente.\n\n- `action: \"apply\"` executa `apply(reason)` na ação pendente e persiste as alterações.\n- `action: \"discard\"` invoca `reject(reason)` se fornecido; caso contrário, descarta a ação com uma mensagem padrão \"Discarded\".\n\nSe não houver ação pendente, `resolve` falha com:\n\n- `No pending action to resolve. Nothing to apply or discard.`\n\n## Ações pendentes são uma pilha (LIFO)\n\nAs ações pendentes são armazenadas em `PendingActionStore` como uma pilha push/pop:\n\n- `push(action)` adiciona uma nova ação pendente no topo.\n- `peek()` inspeciona a ação atual no topo.\n- `pop()` remove e retorna a ação do topo.\n- `hasPending` indica se a pilha não está vazia.\n\n`resolve` sempre consome a ação pendente do **topo** primeiro (`pop()`), portanto múltiplas ferramentas que produzem pré-visualizações são resolvidas na ordem inversa de registro.\n\n## Exemplo de produtor integrado (`ast_edit`)\n\n`ast_edit` pré-visualiza substituições estruturais primeiro. Quando a pré-visualização contém substituições e ainda não foi aplicada, ela adiciona uma ação pendente que contém:\n\n- label (resumo legível por humanos)\n- `sourceToolName` (`ast_edit`)\n- callback `apply(reason: string)` que reexecuta a edição AST com `dryRun: false`\n\n`resolve(action=\"apply\", reason=\"...\")` passa `reason` para esse callback.\n\n## Ferramentas personalizadas: `pushPendingAction`\n\nFerramentas personalizadas podem registrar ações pendentes compatíveis com resolve por meio de `CustomToolAPI.pushPendingAction(...)`.\n\n`CustomToolPendingAction`:\n\n- `label: string` (obrigatório)\n- `apply(reason: string): Promise<AgentToolResult<unknown>>` (obrigatório) — invocado ao aplicar; `reason` é a string passada para `resolve`\n- `reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>` (opcional) — invocado ao descartar; o valor de retorno substitui a mensagem padrão \"Discarded\" se fornecido\n- `details?: unknown` (opcional)\n- `sourceToolName?: string` (opcional, padrão `\"custom_tool\"`)\n\n### Exemplo de uso mínimo\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = pi => ({\n name: \"batch_rename_preview\",\n label: \"Batch Rename Preview\",\n description: \"Previews renames and defers commit to resolve\",\n parameters: pi.typebox.Type.Object({\n  files: pi.typebox.Type.Array(pi.typebox.Type.String()),\n }),\n\n async execute(_toolCallId, params) {\n  const previewSummary = `Prepared rename plan for ${params.files.length} files`;\n\n  pi.pushPendingAction({\n   label: `Batch rename: ${params.files.length} files`,\n   sourceToolName: \"batch_rename_preview\",\n   apply: async (reason) => {\n    // apply writes here\n    return {\n     content: [{ type: \"text\", text: `Applied batch rename. Reason: ${reason}` }],\n    };\n   },\n   reject: async (reason) => {\n    // optional: cleanup or notify on discard\n    return {\n     content: [{ type: \"text\", text: `Discarded batch rename. Reason: ${reason}` }],\n    };\n   },\n  });\n\n  return {\n   content: [{ type: \"text\", text: `${previewSummary}. Call resolve to apply or discard.` }],\n  };\n },\n});\n\nexport default factory;\n```\n\n## Disponibilidade do runtime e falhas\n\n`pushPendingAction` é conectado pelo carregador de ferramentas personalizadas usando o `PendingActionStore` da sessão ativa.\n\nSe o runtime não tiver um armazenamento de ações pendentes, `pushPendingAction` lança:\n\n- `Pending action store unavailable for custom tools in this runtime.`\n\n## Comportamento de seleção de ferramenta\n\nQuando `PendingActionStore.hasPending` é verdadeiro, o runtime do agente direciona a seleção de ferramenta para `resolve`, de modo que as pré-visualizações pendentes sejam explicitamente finalizadas antes que o fluxo normal de ferramentas continue.\n\n## Orientações para desenvolvedores\n\n- Use ações pendentes apenas para operações destrutivas ou de alto impacto que devem suportar aplicação/descarte explícitos.\n- Mantenha `label` conciso e específico; ele é exibido na saída do renderizador de resolve.\n- Certifique-se de que `apply(reason)` seja determinístico e suficientemente idempotente para execução única; `reason` é informativo e não deve alterar o comportamento.\n- Implemente `reject(reason)` quando o descarte necessitar de limpeza (estado temporário, bloqueios, notificações); omita-o para pré-visualizações sem estado onde a mensagem padrão é suficiente.\n- Se sua ferramenta puder organizar múltiplas pré-visualizações, lembre-se da semântica LIFO: a última ação adicionada é resolvida primeiro.\n",
	"pt-br/runtime-tools/slash-command-internals.md": "---\ntitle: Internos de Comandos Slash\ndescription: >-\n  Internos do sistema de comandos slash com registro, análise de argumentos e\n  despacho de execução.\nsidebar:\n  order: 5\n  label: Comandos slash\ni18n:\n  sourceHash: 2cbd44a3de87\n  translator: machine\n---\n\n# Internos de comandos slash\n\nEste documento descreve como os comandos slash são descobertos, deduplicados, exibidos no modo interativo e expandidos no momento do prompt no `coding-agent`.\n\n## Arquivos de implementação\n\n- [`src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n- [`src/capability/slash-command.ts`](../../packages/coding-agent/src/capability/slash-command.ts)\n- [`src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`src/discovery/claude.ts`](../../packages/coding-agent/src/discovery/claude.ts)\n- [`src/discovery/codex.ts`](../../packages/coding-agent/src/discovery/codex.ts)\n- [`src/discovery/claude-plugins.ts`](../../packages/coding-agent/src/discovery/claude-plugins.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n\n## 1) Modelo de descoberta\n\nOs comandos slash são uma capacidade (`id: \"slash-commands\"`) indexada pelo nome do comando (`key: cmd => cmd.name`).\n\nO registro de capacidades carrega todos os provedores registrados, ordenados por prioridade de provedor em ordem decrescente, e deduplica por chave com semântica de **primeiro ganha**.\n\n### Precedência de provedores\n\nProvedores de comandos slash atuais e suas prioridades:\n\n1. `native` (OMP) — prioridade `100`\n2. `claude` — prioridade `80`\n3. `claude-plugins` — prioridade `70`\n4. `codex` — prioridade `70`\n\nComportamento em empate: provedores com prioridade igual mantêm a ordem de registro. A ordem de importação atual registra `claude-plugins` antes de `codex`, portanto comandos de plugins prevalecem sobre comandos do codex em colisões de nomes.\n\n### Comportamento de colisão de nomes\n\nPara `slash-commands`, colisões são resolvidas estritamente pela deduplicação de capacidades:\n\n- o item de maior precedência é mantido em `result.items`\n- duplicatas de menor precedência permanecem apenas em `result.all` e são marcadas com `_shadowed = true`\n\nIsso se aplica entre provedores e também dentro de um provedor se ele retornar nomes duplicados.\n\n### Comportamento de varredura de arquivos\n\nOs provedores utilizam principalmente `loadFilesFromDir(...)`, que atualmente:\n\n- usa correspondência não recursiva por padrão (`*.md`)\n- utiliza glob nativo com `gitignore: true`, `hidden: false`\n- lê cada arquivo correspondido e o transforma em um `SlashCommand`\n\nPortanto, arquivos e diretórios ocultos não são carregados, e caminhos ignorados são descartados.\n\n## 2) Caminhos de origem específicos por provedor e precedência local\n\n## Provedor `native` (`builtin.ts`)\n\nAs raízes de busca vêm dos diretórios `.xcsh`:\n\n- projeto: `<cwd>/.xcsh/commands/*.md`\n- usuário: `~/.xcsh/agent/commands/*.md`\n\n`getConfigDirs()` retorna o projeto primeiro, depois o usuário, portanto **comandos nativos do projeto prevalecem sobre comandos nativos do usuário** em colisões de nomes.\n\n## Provedor `claude` (`claude.ts`)\n\nCarrega:\n\n- usuário: `~/.claude/commands/*.md`\n- projeto: `<cwd>/.claude/commands/*.md`\n\nO provedor insere os itens do usuário antes dos itens do projeto, portanto **comandos Claude do usuário prevalecem sobre comandos Claude do projeto** em colisões de mesmo nome dentro deste provedor.\n\n## Provedor `codex` (`codex.ts`)\n\nCarrega:\n\n- usuário: `~/.codex/commands/*.md`\n- projeto: `<cwd>/.codex/commands/*.md`\n\nAmbos os lados são carregados e então nivelados em ordem de usuário primeiro, portanto **comandos Codex do usuário prevalecem sobre comandos Codex do projeto** em colisões.\n\nO conteúdo dos comandos Codex é analisado com remoção de frontmatter (`parseFrontmatter`), e o nome do comando pode ser substituído pelo frontmatter `name`; caso contrário, o nome do arquivo é utilizado.\n\n## Provedor `claude-plugins` (`claude-plugins.ts`)\n\nCarrega as raízes de comandos de plugins a partir de `~/.claude/plugins/installed_plugins.json`, e então varre `<pluginRoot>/commands/*.md`.\n\nA ordenação segue a ordem de iteração do registro e a ordem de entradas por plugin desse arquivo JSON. Não há etapa de ordenação adicional.\n\n## 3) Materialização para `FileSlashCommand` em tempo de execução\n\n`loadSlashCommands()` em `src/extensibility/slash-commands.ts` converte os itens de capacidade em objetos `FileSlashCommand` utilizados no momento do prompt.\n\nPara cada comando:\n\n1. analisar frontmatter/corpo (`parseFrontmatter`)\n2. fonte da descrição:\n   - `frontmatter.description` se presente\n   - caso contrário, a primeira linha não vazia do corpo (aparada, máx. 60 caracteres com `...`)\n3. manter o corpo analisado como conteúdo de template executável\n4. calcular uma string de fonte de exibição como `via Claude Code Project`\n\nA severidade da análise do frontmatter depende da fonte:\n\n- nível `native` -> erros de análise são `fatal`\n- níveis `user`/`project` -> erros de análise são `warn` com análise de fallback\n\n### Comandos de fallback embutidos\n\nApós os comandos baseados em sistema de arquivos/provedor, templates de comandos embutidos são adicionados (`EMBEDDED_COMMAND_TEMPLATES`) caso seus nomes ainda não estejam presentes.\n\nO conjunto embutido atual vem de `src/task/commands.ts` e é utilizado como fallback (`source: \"bundled\"`).\n\n## 4) Modo interativo: origem das listas de comandos\n\nO modo interativo combina múltiplas fontes de comandos para autocompletar e roteamento de comandos.\n\nNo momento da construção, ele cria uma lista de comandos pendentes a partir de:\n\n- comandos internos (`BUILTIN_SLASH_COMMANDS`, incluindo completação de argumentos e dicas inline para comandos selecionados)\n- comandos slash registrados por extensões (`extensionRunner.getRegisteredCommands(...)`)\n- comandos personalizados TypeScript (`session.customCommands`), mapeados para rótulos de comandos slash\n- comandos de habilidades opcionais (`/skill:<name>`) quando `skills.enableSkillCommands` está habilitado\n\nEm seguida, `init()` chama `refreshSlashCommandState(...)` para carregar comandos baseados em arquivo e instalar um `CombinedAutocompleteProvider` contendo:\n\n- os comandos pendentes acima\n- comandos baseados em arquivo descobertos\n\n`refreshSlashCommandState(...)` também atualiza `session.setSlashCommands(...)` para que a expansão de prompt utilize o mesmo conjunto de comandos de arquivo descobertos.\n\n### Ciclo de vida de atualização\n\nO estado dos comandos slash é atualizado:\n\n- durante a inicialização interativa\n- após `/move` alterar o diretório de trabalho (`handleMoveCommand` chama `resetCapabilities()` e depois `refreshSlashCommandState(newCwd)`)\n\nNão há observador contínuo de arquivos para diretórios de comandos.\n\n### Outras superfícies de exibição\n\nO painel de Extensões também carrega a capacidade `slash-commands` e exibe entradas de comandos ativos/sombreados, incluindo duplicatas `_shadowed`.\n\n## 5) Posicionamento no pipeline de prompt\n\nOrdem de tratamento de slash em `AgentSession.prompt(...)` (quando `expandPromptTemplates !== false`):\n\n1. **Comandos de extensão** (`#tryExecuteExtensionCommand`)  \n   Se `/nome` corresponder a um comando registrado por extensão, o handler é executado imediatamente e o prompt retorna.\n2. **Comandos personalizados TypeScript** (`#tryExecuteCustomCommand`)  \n   Apenas fronteira: se correspondido, é executado e pode retornar:\n   - `string` -> substituir o texto do prompt por essa string\n   - `void/undefined` -> tratado como tratado; sem prompt LLM\n3. **Comandos slash baseados em arquivo** (`expandSlashCommand`)  \n   Se o texto ainda começa com `/`, tenta a expansão do comando markdown.\n4. **Templates de prompt** (`expandPromptTemplate`)  \n   Aplicados após o processamento de slash/personalizado.\n5. **Entrega**\n   - ocioso: o prompt é enviado imediatamente ao agente\n   - em streaming: o prompt é enfileirado como steer/follow-up dependendo de `streamingBehavior`\n\nPor isso a expansão de comandos slash ocorre antes da expansão de templates de prompt, e por isso os comandos personalizados podem transformar o slash inicial antes da correspondência de comandos de arquivo.\n\n## 6) Semântica de expansão para comandos slash baseados em arquivo\n\nComportamento de `expandSlashCommand(text, fileCommands)`:\n\n- executa apenas quando o texto começa com `/`\n- analisa o nome do comando a partir do primeiro token após `/`\n- analisa os argumentos do texto restante via `parseCommandArgs`\n- encontra correspondência exata de nome nos `fileCommands` carregados\n- se correspondido, aplica:\n  - substituição posicional: `$1`, `$2`, ...\n  - substituição agregada: `$ARGUMENTS` e `$@`\n  - então renderização de template via `prompt.render` com `{ args, ARGUMENTS, arguments }`\n- se não houver correspondência, retorna o texto original sem alterações\n\n### Ressalvas sobre `parseCommandArgs`\n\nO analisador é uma divisão simples com reconhecimento de aspas:\n\n- suporta aspas `'simples'` e `\"duplas\"` para preservar espaços\n- remove os delimitadores de aspas\n- não implementa regras de escape com barra invertida\n- aspas sem correspondência não são um erro; o analisador consome até o final\n\n## 7) Comportamento de `/...` desconhecido\n\nEntradas slash desconhecidas **não são rejeitadas** pela lógica central de slash.\n\nSe o comando não for tratado pelas camadas de extensão/personalizado/arquivo, `expandSlashCommand` retorna o texto original, e o prompt literal `/...` prossegue pela expansão normal de templates de prompt e entrega ao LLM.\n\nO modo interativo trata separadamente muitos comandos internos de forma rígida no `InputController` (por exemplo `/settings`, `/model`, `/mcp`, `/move`, `/exit`). Esses são consumidos antes de `session.prompt(...)` e, portanto, nunca chegam à expansão de comandos de arquivo nesse caminho.\n\n## 8) Diferenças em tempo de streaming vs ocioso\n\n## Caminho ocioso\n\n- `session.prompt(\"/x ...\")` executa o pipeline de comandos e ou executa o comando imediatamente ou envia o texto expandido diretamente.\n\n## Caminho de streaming (`session.isStreaming === true`)\n\n- `prompt(...)` ainda executa as transformações de extensão/personalizado/arquivo/template primeiro\n- então requer `streamingBehavior`:\n  - `\"steer\"` -> enfileirar mensagem de interrupção (`agent.steer`)\n  - `\"followUp\"` -> enfileirar mensagem pós-turno (`agent.followUp`)\n- se `streamingBehavior` for omitido, o prompt lança um erro\n\n### Comportamento de streaming específico por comando\n\n- Comandos de extensão são executados imediatamente mesmo durante o streaming (não enfileirados como texto).\n- Os métodos auxiliares `steer(...)`/`followUp(...)` rejeitam comandos de extensão (`#throwIfExtensionCommand`) para evitar enfileirar texto de comando para handlers que devem executar de forma síncrona.\n- A reprodução da fila de compactação usa `isKnownSlashCommand(...)` para decidir se as entradas enfileiradas devem ser reproduzidas via `session.prompt(...)` (para comandos slash conhecidos) versus métodos raw de steer/follow-up.\n\n## 9) Tratamento de erros e superfícies de falha\n\n- Falhas de carregamento de provedor são isoladas; o registro coleta avisos e continua com outros provedores.\n- Itens de comandos slash inválidos (nome/caminho/conteúdo ausente ou nível inválido) são descartados pela validação de capacidade.\n- Falhas de análise de frontmatter:\n  - comandos nativos: erro de análise fatal é propagado\n  - comandos não nativos: aviso + análise de fallback de chave/valor\n- Exceções de handlers de comandos de extensão/personalizado são capturadas e reportadas via canal de erros de extensão (ou fallback de logger para comandos personalizados sem executor de extensão), e tratadas como tratadas (sem execução de fallback não intencional).\n",
	"pt-br/runtime-tools/task-agent-discovery.md": "---\ntitle: Descoberta e Seleção de Agentes de Tarefa\ndescription: >-\n  Lógica de descoberta e seleção de agentes de tarefa para roteamento de\n  trabalho para tipos especializados de subagentes.\nsidebar:\n  order: 6\n  label: Descoberta de agentes de tarefa\ni18n:\n  sourceHash: 8cf42457c672\n  translator: machine\n---\n\n# Descoberta e Seleção de Agentes de Tarefa\n\nEste documento descreve como o subsistema de tarefas descobre definições de agentes, mescla múltiplas fontes e resolve um agente solicitado no momento da execução.\n\nEle abrange o comportamento em tempo de execução conforme implementado atualmente, incluindo precedência, tratamento de definições inválidas e restrições de spawn/profundidade que podem tornar um agente efetivamente indisponível.\n\n## Arquivos de implementação\n\n- [`src/task/discovery.ts`](../../packages/coding-agent/src/task/discovery.ts)\n- [`src/task/agents.ts`](../../packages/coding-agent/src/task/agents.ts)\n- [`src/task/types.ts`](../../packages/coding-agent/src/task/types.ts)\n- [`src/task/index.ts`](../../packages/coding-agent/src/task/index.ts)\n- [`src/task/commands.ts`](../../packages/coding-agent/src/task/commands.ts)\n- [`src/prompts/agents/task.md`](../../packages/coding-agent/src/prompts/agents/task.md)\n- [`src/prompts/tools/task.md`](../../packages/coding-agent/src/prompts/tools/task.md)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/config.ts`](../../packages/coding-agent/src/config.ts)\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts)\n\n---\n\n## Formato de definição de agente\n\nOs agentes de tarefa são normalizados para `AgentDefinition` (`src/task/types.ts`):\n\n- `name`, `description`, `systemPrompt` (obrigatórios para um agente carregado válido)\n- `tools`, `spawns`, `model`, `thinkingLevel`, `output` (opcionais)\n- `source`: `\"bundled\" | \"user\" | \"project\"`\n- `filePath` (opcional)\n\nA análise vem do frontmatter via `parseAgentFields()` (`src/discovery/helpers.ts`):\n\n- `name` ou `description` ausentes => inválido (`null`), o chamador trata como falha de análise\n- `tools` aceita CSV ou array; se fornecido, `submit_result` é adicionado automaticamente\n- `spawns` aceita `*`, CSV ou array\n- comportamento de compatibilidade retroativa: se `spawns` estiver ausente mas `tools` incluir `task`, `spawns` passa a ser `*`\n- `output` é repassado como dados de esquema opacos\n\n## Agentes embutidos\n\nOs agentes embutidos são incorporados no momento da compilação (`src/task/agents.ts`) usando importações de texto.\n\n`EMBEDDED_AGENT_DEFS` define:\n\n- `explore`, `plan`, `designer`, `reviewer` a partir de arquivos de prompt\n- `task` e `quick_task` a partir do corpo compartilhado `task.md` mais frontmatter injetado\n\nCaminho de carregamento:\n\n1. `loadBundledAgents()` analisa o markdown incorporado com `parseAgent(..., \"bundled\", \"fatal\")`\n2. os resultados são armazenados em cache na memória (`bundledAgentsCache`)\n3. `clearBundledAgentsCache()` é uma redefinição de cache exclusiva para testes\n\nComo a análise dos agentes embutidos usa `level: \"fatal\"`, frontmatter malformado lança uma exceção e pode causar falha na descoberta inteiramente.\n\n## Descoberta no sistema de arquivos e em plugins\n\n`discoverAgents(cwd, home)` (`src/task/discovery.ts`) mescla agentes de múltiplos locais antes de acrescentar as definições embutidas.\n\n### Entradas de descoberta\n\n1. Diretórios de agentes da configuração do usuário obtidos via `getConfigDirs(\"agents\", { project: false })`\n2. Diretórios de agentes do projeto mais próximo obtidos via `findAllNearestProjectConfigDirs(\"agents\", cwd)`\n3. Raízes de plugins Claude (`listClaudePluginRoots(home)`) com subdiretórios `agents/`\n4. Agentes embutidos (`loadBundledAgents()`)\n\n### Ordem real das fontes\n\nA ordem das famílias de fontes vem de `getConfigDirs(\"\", { project: false })`, derivada de `priorityList` em `src/config.ts`:\n\n1. `.xcsh`\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nPara cada família de fontes, a ordem de descoberta é:\n\n1. diretório do projeto mais próximo para aquela fonte (se encontrado)\n2. diretório do usuário para aquela fonte\n\nApós todos os diretórios de famílias de fontes, os diretórios `agents/` de plugins são acrescentados (plugins de escopo de projeto primeiro, depois de escopo de usuário).\n\nOs agentes embutidos são acrescentados por último.\n\n### Ressalva importante: comentários desatualizados vs. código atual\n\nOs comentários de cabeçalho em `discovery.ts` ainda mencionam `.pi` e não mencionam `.codex`/`.gemini`. A ordem em tempo de execução real é determinada por `src/config.ts` e atualmente utiliza `.xcsh`, `.claude`, `.codex`, `.gemini`.\n\n## Regras de mesclagem e colisão\n\nA descoberta usa deduplicação por nome exato com política de primeiro a chegar:\n\n- Um `Set<string>` rastreia os nomes já vistos.\n- Os agentes carregados são achatados na ordem dos diretórios e mantidos apenas se o nome ainda não foi visto.\n- Os agentes embutidos são filtrados em relação ao mesmo conjunto e adicionados apenas se ainda não foram vistos.\n\nImplicações:\n\n- O projeto substitui o usuário para a mesma família de fontes.\n- Famílias de fontes de maior prioridade substituem as de menor prioridade (`.xcsh` antes de `.claude`, etc.).\n- Agentes não embutidos substituem agentes embutidos com o mesmo nome.\n- A correspondência de nomes é sensível a maiúsculas e minúsculas (`Task` e `task` são distintos).\n- Dentro de um mesmo diretório, os arquivos markdown são lidos em ordem lexicográfica de nome de arquivo antes da deduplicação.\n\n## Comportamento com arquivos de agente inválidos ou ausentes\n\nPor diretório (`loadAgentsFromDir`):\n\n- diretório ilegível ou ausente: tratado como vazio (`readdir(...).catch(() => [])`)\n- falha de leitura ou análise de arquivo: aviso registrado, arquivo ignorado\n- o caminho de análise usa `parseAgent(..., level: \"warn\")`\n\nO comportamento de falha do frontmatter vem de `parseFrontmatter`:\n\n- erro de análise no nível `warn` registra aviso\n- o analisador retorna a um parser simples de linha `key: value`\n- se os campos obrigatórios ainda estiverem ausentes, `parseAgentFields` falha, então `AgentParsingError` é lançado e capturado pelo chamador (arquivo ignorado)\n\nEfeito líquido: um arquivo de agente personalizado inválido não aborta a descoberta dos demais arquivos.\n\n## Pesquisa e seleção de agente\n\nA pesquisa é uma busca linear por nome exato:\n\n- `getAgent(agents, name)` => `agents.find(a => a.name === name)`\n\nNa execução de tarefas (`TaskTool.execute`):\n\n1. os agentes são redescobertos no momento da chamada (`discoverAgents(this.session.cwd)`)\n2. o `params.agent` solicitado é resolvido via `getAgent`\n3. agente ausente retorna resposta imediata da ferramenta:\n   - `Unknown agent \"...\". Available: ...`\n   - nenhum subprocesso é executado\n\n### Descoberta na descrição vs. no momento da execução\n\n`TaskTool.create()` constrói a descrição da ferramenta a partir dos resultados de descoberta no momento da inicialização (`buildDescription`).\n\n`execute()` redescobre os agentes novamente. Portanto, o conjunto em tempo de execução pode diferir do que foi listado na descrição anterior da ferramenta se os arquivos de agente foram alterados durante a sessão.\n\n## Proteções de saída estruturada e precedência de esquema\n\nPrecedência do esquema de saída em tempo de execução em `TaskTool.execute`:\n\n1. `output` do frontmatter do agente\n2. `params.schema` da chamada de tarefa\n3. `outputSchema` da sessão pai\n\n(`effectiveOutputSchema = effectiveAgent.output ?? outputSchema ?? this.session.outputSchema`)\n\nO texto de proteção em tempo de prompt em `src/prompts/tools/task.md` averte sobre o comportamento de incompatibilidade para agentes de saída estruturada (`explore`, `reviewer`): instruções de formato de saída em prosa podem conflitar com o esquema embutido e produzir saídas `null`.\n\nIsso é uma orientação, não lógica de validação em tempo de execução em `discoverAgents`.\n\n## Interação com a descoberta de comandos\n\n`src/task/commands.ts` é uma infraestrutura paralela para comandos de fluxo de trabalho (não definições de agente), mas segue o mesmo padrão geral:\n\n- descobrir a partir dos provedores de capacidade primeiro\n- deduplicar por nome com política de primeiro a chegar\n- acrescentar comandos embutidos se ainda não vistos\n- pesquisa por nome exato via `getCommand`\n\nEm `src/task/index.ts`, os auxiliares de comando são reexportados junto com os auxiliares de descoberta de agentes. A descoberta de agentes em si não depende da descoberta de comandos em tempo de execução.\n\n## Restrições de disponibilidade além da descoberta\n\nUm agente pode ser detectável, mas ainda assim indisponível para execução devido a proteções de execução.\n\n### Política de spawn do pai\n\n`TaskTool.execute` verifica `session.getSessionSpawns()`:\n\n- `\"*\"` => permitir qualquer um\n- `\"\"` => negar todos\n- lista CSV => permitir apenas os nomes listados\n\nSe negado: resposta imediata `Cannot spawn '...'. Allowed: ...`.\n\n### Proteção de ambiente contra autorrecursão bloqueada\n\n`PI_BLOCKED_AGENT` é lido na construção da ferramenta. Se a solicitação corresponder, a execução é rejeitada com uma mensagem de prevenção de recursão.\n\n### Limitação de profundidade de recursão (disponibilidade da ferramenta de tarefa em sessões filhas)\n\nEm `runSubprocess` (`src/task/executor.ts`):\n\n- a profundidade é calculada a partir de `taskDepth`\n- `task.maxRecursionDepth` controla o limite\n- ao atingir a profundidade máxima:\n  - a ferramenta `task` é removida da lista de ferramentas da sessão filha\n  - `spawns` do ambiente filho é definido como vazio\n\nPortanto, níveis mais profundos não podem gerar mais tarefas mesmo que a definição do agente inclua `spawns`.\n\n## Ressalva sobre o modo de plano (implementação atual)\n\n`TaskTool.execute` calcula um `effectiveAgent` para o modo de plano (adiciona o prompt do modo de plano ao início, força um subconjunto de ferramentas somente leitura, limpa spawns), mas `runSubprocess` é chamado com `agent` em vez de `effectiveAgent`.\n\nEfeito atual:\n\n- a substituição de modelo / nível de raciocínio / esquema de saída são derivados de `effectiveAgent`\n- o prompt do sistema e as restrições de ferramenta/spawn de `effectiveAgent` não são repassados neste caminho de chamada\n\nEsta é uma ressalva de implementação importante ao analisar as expectativas de comportamento do modo de plano.\n",
	"pt-br/sessions/compaction.md": "---\ntitle: Compactação e Resumos de Branch\ndescription: >-\n  Compactação da janela de contexto e geração de resumos de branch para sessões\n  de longa duração.\nsidebar:\n  order: 5\n  label: Compactação\ni18n:\n  sourceHash: dae425a900d8\n  translator: machine\n---\n\n# Compactação e Resumos de Branch\n\nCompactação e resumos de branch são os dois mecanismos que mantêm sessões longas utilizáveis sem perder o contexto de trabalho anterior.\n\n- **Compactação** reescreve o histórico antigo em um resumo na branch atual.\n- **Resumo de branch** captura o contexto de branches abandonadas durante a navegação com `/tree`.\n\nAmbos são persistidos como entradas de sessão e convertidos de volta em mensagens de contexto do usuário ao reconstruir a entrada do LLM.\n\n## Arquivos de implementação principais\n\n- `src/session/compaction/compaction.ts`\n- `src/session/compaction/branch-summarization.ts`\n- `src/session/compaction/pruning.ts`\n- `src/session/compaction/utils.ts`\n- `src/session/session-manager.ts`\n- `src/session/agent-session.ts`\n- `src/session/messages.ts`\n- `src/extensibility/hooks/types.ts`\n- `src/config/settings-schema.ts`\n\n## Modelo de entrada de sessão\n\nCompactação e resumos de branch são entradas de sessão de primeira classe, não simples mensagens de assistente/usuário.\n\n- `CompactionEntry`\n  - `type: \"compaction\"`\n  - `summary`, opcional `shortSummary`\n  - `firstKeptEntryId` (fronteira da compactação)\n  - `tokensBefore`\n  - opcional `details`, `preserveData`, `fromExtension`\n- `BranchSummaryEntry`\n  - `type: \"branch_summary\"`\n  - `fromId`, `summary`\n  - opcional `details`, `fromExtension`\n\nQuando o contexto é reconstruído (`buildSessionContext`):\n\n1. A compactação mais recente no caminho ativo é convertida em uma mensagem `compactionSummary`.\n2. As entradas mantidas de `firstKeptEntryId` até o ponto de compactação são reincluídas.\n3. As entradas posteriores no caminho são adicionadas.\n4. As entradas `branch_summary` são convertidas em mensagens `branchSummary`.\n5. As entradas `custom_message` são convertidas em mensagens `custom`.\n\nEsses papéis personalizados são então transformados em mensagens de usuário voltadas ao LLM em `convertToLlm()` usando os templates estáticos:\n\n- `prompts/compaction/compaction-summary-context.md`\n- `prompts/compaction/branch-summary-context.md`\n\n## Pipeline de compactação\n\n### Gatilhos\n\nA compactação pode ser executada de três formas:\n\n1. **Manual**: `/compact [instruções]` chama `AgentSession.compact(...)`.\n2. **Recuperação automática de overflow**: após um erro do assistente que corresponde a overflow de contexto.\n3. **Compactação automática por limiar**: após um turno bem-sucedido quando o contexto excede o limiar.\n\n### Forma da compactação (visual)\n\n```text\nAntes da compactação:\n\n  entry:  0     1     2     3      4     5     6      7      8     9\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┘\n                └────────┬───────┘ └──────────────┬──────────────┘\n               messagesToSummarize            kept messages\n                                   ↑\n                          firstKeptEntryId (entry 4)\n\nApós a compactação (nova entrada adicionada):\n\n  entry:  0     1     2     3      4     5     6      7      8     9      10\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┬─────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │ cmp │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┴─────┘\n               └──────────┬──────┘ └──────────────────────┬───────────────────┘\n                 não enviado ao LLM                 enviado ao LLM\n                                                         ↑\n                                              começa em firstKeptEntryId\n\nO que o LLM vê:\n\n  ┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐\n  │ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │\n  └────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘\n       ↑         ↑      └─────────────────┬────────────────┘\n    prompt   de cmp          mensagens a partir de firstKeptEntryId\n```\n\n### Compactação por overflow-retry vs por limiar\n\nOs dois caminhos automáticos são intencionalmente diferentes:\n\n- **Compactação por overflow-retry**\n  - Gatilho: erro do assistente no modelo atual é detectado como overflow de contexto.\n  - A mensagem de erro do assistente que falhou é removida do estado ativo do agente antes da retentativa.\n  - A compactação automática é executada com `reason: \"overflow\"` e `willRetry: true`.\n  - Em caso de sucesso, o agente continua automaticamente (`agent.continue()`) após a compactação.\n\n- **Compactação por limiar**\n  - Gatilho: `contextTokens > contextWindow - compaction.reserveTokens`.\n  - Executada com `reason: \"threshold\"` e `willRetry: false`.\n  - Em caso de sucesso, se `compaction.autoContinue !== false`, injeta um prompt sintético:\n    - `\"Continue if you have next steps.\"`\n\n### Poda pré-compactação\n\nAntes das verificações de compactação, a poda de resultados de ferramentas pode ser executada (`pruneToolOutputs`).\n\nPolítica de poda padrão:\n\n- Proteger os `40_000` tokens mais recentes de saída de ferramentas.\n- Exigir pelo menos `20_000` de economia total estimada.\n- Nunca podar resultados de ferramentas de `skill` ou `read`.\n\nResultados de ferramentas podados são substituídos por:\n\n- `[Output truncated - N tokens]`\n\nSe a poda alterar entradas, o armazenamento da sessão é reescrito e o estado de mensagens do agente é atualizado antes das decisões de compactação.\n\n### Lógica de fronteira e ponto de corte\n\n`prepareCompaction()` considera apenas entradas desde a última entrada de compactação (se existir).\n\n1. Encontrar o índice da compactação anterior.\n2. Calcular `boundaryStart = prevCompactionIndex + 1`.\n3. Adaptar `keepRecentTokens` usando a razão de uso medida quando disponível.\n4. Executar `findCutPoint()` sobre a janela de fronteira.\n\nPontos de corte válidos incluem:\n\n- entradas de mensagem com papéis: `user`, `assistant`, `bashExecution`, `hookMessage`, `branchSummary`, `compactionSummary`\n- entradas `custom_message`\n- entradas `branch_summary`\n\nRegra rígida: nunca cortar em `toolResult`.\n\nSe houver entradas de metadados não-mensagem imediatamente antes do ponto de corte (`model_change`, `thinking_level_change`, labels, etc.), elas são puxadas para a região mantida movendo o índice de corte para trás até que uma mensagem ou fronteira de compactação seja encontrada.\n\n### Tratamento de turno dividido\n\nSe o ponto de corte não estiver no início de um turno do usuário, a compactação o trata como um turno dividido.\n\nA detecção de início de turno trata estes como fronteiras de turno do usuário:\n\n- `message.role === \"user\"`\n- `message.role === \"bashExecution\"`\n- entrada `custom_message`\n- entrada `branch_summary`\n\nA compactação de turno dividido gera dois resumos:\n\n1. Resumo do histórico (`messagesToSummarize`)\n2. Resumo do prefixo do turno (`turnPrefixMessages`)\n\nO resumo final armazenado é mesclado como:\n\n```markdown\n<history summary>\n\n---\n\n**Turn Context (split turn):**\n\n<turn prefix summary>\n```\n\n### Geração de resumo\n\n`compact(...)` constrói resumos a partir de texto de conversa serializado:\n\n1. Converte mensagens via `convertToLlm()`.\n2. Serializa com `serializeConversation()`.\n3. Envolve em `<conversation>...</conversation>`.\n4. Opcionalmente inclui `<previous-summary>...</previous-summary>`.\n5. Opcionalmente injeta contexto de hook como lista `<additional-context>`.\n6. Executa o prompt de sumarização com `SUMMARIZATION_SYSTEM_PROMPT`.\n\nSeleção de prompt:\n\n- primeira compactação: `compaction-summary.md`\n- compactação iterativa com resumo anterior: `compaction-update-summary.md`\n- segunda passagem de turno dividido: `compaction-turn-prefix.md`\n- resumo curto para UI: `compaction-short-summary.md`\n\nModo de sumarização remota:\n\n- Se `compaction.remoteEndpoint` estiver definido, a compactação faz POST com:\n  - `{ systemPrompt, prompt }`\n- Espera JSON contendo pelo menos `{ summary }`.\n\n### Contexto de operações de arquivo nos resumos\n\nA compactação rastreia a atividade cumulativa de arquivos usando chamadas de ferramenta do assistente:\n\n- `read(path)` → conjunto de leitura\n- `write(path)` → conjunto de modificação\n- `edit(path)` → conjunto de modificação\n\nComportamento cumulativo:\n\n- Inclui detalhes da compactação anterior somente quando a entrada anterior é gerada internamente (`fromExtension !== true`).\n- Em turnos divididos, inclui também as operações de arquivo do prefixo do turno.\n- `readFiles` exclui arquivos que também foram modificados.\n\nO texto do resumo recebe tags de arquivo anexadas via template de prompt:\n\n```xml\n<read-files>\n...\n</read-files>\n<modified-files>\n...\n</modified-files>\n```\n\n### Persistência e recarga\n\nApós a geração do resumo (ou resumo fornecido por hook), a sessão do agente:\n\n1. Adiciona `CompactionEntry` com `appendCompaction(...)`.\n2. Reconstrói o contexto via `buildSessionContext()`.\n3. Substitui as mensagens ativas do agente pelo contexto reconstruído.\n4. Emite o evento de hook `session_compact`.\n\n## Pipeline de sumarização de branch\n\nA sumarização de branch está vinculada à navegação em árvore, não ao overflow de tokens.\n\n### Gatilho\n\nDurante `navigateTree(...)`:\n\n1. Calcula as entradas abandonadas da folha antiga até o ancestral comum usando `collectEntriesForBranchSummary(...)`.\n2. Se o chamador solicitou resumo (`options.summarize`), gera o resumo antes de trocar a folha.\n3. Se o resumo existir, anexa-o no alvo de navegação usando `branchWithSummary(...)`.\n\nOperacionalmente, isso é comumente acionado pelo fluxo `/tree` quando `branchSummary.enabled` está habilitado.\n\n### Forma da troca de branch (visual)\n\n```text\nÁrvore antes da navegação:\n\n         ┌─ B ─ C ─ D (folha antiga, sendo abandonada)\n    A ───┤\n         └─ E ─ F (alvo)\n\nAncestral comum: A\nEntradas a resumir: B, C, D\n\nApós navegação com resumo:\n\n         ┌─ B ─ C ─ D ─ [resumo de B,C,D]\n    A ───┤\n         └─ E ─ F (nova folha)\n```\n\n### Preparação e orçamento de tokens\n\n`generateBranchSummary(...)` calcula o orçamento como:\n\n- `tokenBudget = model.contextWindow - branchSummary.reserveTokens`\n\n`prepareBranchEntries(...)` então:\n\n1. Primeira passagem: coleta operações de arquivo cumulativas de todas as entradas resumidas, incluindo detalhes anteriores de `branch_summary` gerados internamente.\n2. Segunda passagem: percorre do mais recente ao mais antigo, adicionando mensagens até que o orçamento de tokens seja atingido.\n3. Prefere preservar contexto recente.\n4. Pode ainda incluir entradas de resumo grandes perto do limite do orçamento para continuidade.\n\nEntradas de compactação são incluídas como mensagens (`compactionSummary`) durante a entrada da sumarização de branch.\n\n### Geração e persistência do resumo\n\nSumarização de branch:\n\n1. Converte e serializa as mensagens selecionadas.\n2. Envolve em `<conversation>`.\n3. Usa instruções personalizadas se fornecidas, caso contrário `branch-summary.md`.\n4. Chama o modelo de sumarização com `SUMMARIZATION_SYSTEM_PROMPT`.\n5. Prepende `branch-summary-preamble.md`.\n6. Anexa tags de operação de arquivo.\n\nO resultado é armazenado como `BranchSummaryEntry` com detalhes opcionais (`readFiles`, `modifiedFiles`).\n\n## Pontos de extensão e hooks\n\n### `session_before_compact`\n\nHook pré-compactação.\n\nPode:\n\n- cancelar a compactação (`{ cancel: true }`)\n- fornecer payload de compactação personalizado completo (`{ compaction: CompactionResult }`)\n\n### `session.compacting`\n\nHook de personalização de prompt/contexto para compactação padrão.\n\nPode retornar:\n\n- `prompt` (substituir o prompt base do resumo)\n- `context` (linhas de contexto extra injetadas em `<additional-context>`)\n- `preserveData` (armazenado na entrada de compactação)\n\n### `session_compact`\n\nNotificação pós-compactação com `compactionEntry` salva e flag `fromExtension`.\n\n### `session_before_tree`\n\nExecutado na navegação em árvore antes da geração padrão de resumo de branch.\n\nPode:\n\n- cancelar a navegação\n- fornecer `{ summary: { summary, details } }` personalizado usado quando o usuário solicitou sumarização\n\n### `session_tree`\n\nEvento pós-navegação expondo nova/antiga folha e entrada de resumo opcional.\n\n## Comportamento em tempo de execução e semântica de falha\n\n- A compactação manual aborta a operação atual do agente primeiro.\n- `abortCompaction()` cancela tanto os controladores de compactação manual quanto automática.\n- A compactação automática emite eventos de sessão de início/fim para atualizações de UI/estado.\n- A compactação automática pode tentar múltiplos modelos candidatos e retentar falhas transitórias.\n- Erros de overflow são excluídos do caminho genérico de retentativa porque são tratados pela compactação.\n- Se a compactação automática falhar:\n  - o caminho de overflow emite `Context overflow recovery failed: ...`\n  - o caminho de limiar emite `Auto-compaction failed: ...`\n- A sumarização de branch pode ser cancelada via sinal de aborto (ex.: Escape), retornando resultado de navegação cancelado/abortado.\n\n## Configurações e padrões\n\nDe `settings-schema.ts`:\n\n- `compaction.enabled` = `true`\n- `compaction.reserveTokens` = `16384`\n- `compaction.keepRecentTokens` = `20000`\n- `compaction.autoContinue` = `true`\n- `compaction.remoteEndpoint` = `undefined`\n- `branchSummary.enabled` = `false`\n- `branchSummary.reserveTokens` = `16384`\n\nEsses valores são consumidos em tempo de execução por `AgentSession` e pelos módulos de compactação/sumarização de branch.\n",
	"pt-br/sessions/handoff-generation-pipeline.md": "---\ntitle: Pipeline de Geração de Handoff\ndescription: >-\n  Pipeline de geração de handoff para criação de resumos de sessão portáteis\n  para colaboração em equipe.\nsidebar:\n  order: 8\n  label: Pipeline de handoff\ni18n:\n  sourceHash: 03666084b5ac\n  translator: machine\n---\n\n# Pipeline de geração `/handoff`\n\nEste documento descreve como o coding-agent implementa o `/handoff` atualmente: caminho de disparo, prompt de geração, captura de conclusão, troca de sessão e reinjeção de contexto.\n\n## Escopo\n\nAbrange:\n\n- Despacho interativo do comando `/handoff`\n- Ciclo de vida e transições de estado de `AgentSession.handoff()`\n- Como a saída do handoff é capturada a partir da saída do assistente\n- Como sessões antigas/novas persistem dados de handoff de maneira diferente\n- Comportamento da UI para sucesso, cancelamento e falha\n\nNão abrange:\n\n- Internos genéricos de navegação em árvore/branch\n- Comandos de sessão não relacionados a handoff (`/new`, `/fork`, `/resume`)\n\n## Arquivos de implementação\n\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n\n## Caminho de disparo\n\n1. `/handoff` é declarado nos metadados de comandos slash embutidos (`slash-commands.ts`) com dica inline opcional: `[focus instructions]`.\n2. No tratamento de entrada interativa (`InputController`), texto submetido correspondendo a `/handoff` ou `/handoff ...` é interceptado antes do envio normal do prompt.\n3. O editor é limpo e `handleHandoffCommand(customInstructions?)` é chamado.\n4. `CommandController.handleHandoffCommand` realiza uma verificação prévia usando as entradas atuais:\n   - Conta entradas do `type === \"message\"`.\n   - Se `< 2`, exibe aviso: `Nothing to hand off (no messages yet)` e retorna.\n\nA mesma verificação de conteúdo mínimo existe novamente dentro de `AgentSession.handoff()` e lança uma exceção se violada. Isso duplica a segurança nas camadas de UI e sessão.\n\n## Ciclo de vida de ponta a ponta\n\n### 1) Iniciar geração do handoff\n\n`AgentSession.handoff(customInstructions?)`:\n\n- Lê as entradas do branch atual (`sessionManager.getBranch()`)\n- Valida a contagem mínima de mensagens (`>= 2`)\n- Cria `#handoffAbortController`\n- Constrói um prompt fixo e inline solicitando um documento de handoff estruturado (`Goal`, `Constraints & Preferences`, `Progress`, `Key Decisions`, `Critical Context`, `Next Steps`)\n- Adiciona `Additional focus: ...` se instruções personalizadas forem fornecidas\n\nO prompt é enviado via:\n\n```ts\nawait this.prompt(handoffPrompt, { expandPromptTemplates: false });\n```\n\n`expandPromptTemplates: false` impede a expansão de slash/prompt-template neste payload de instrução interna.\n\n### 2) Captura da conclusão\n\nAntes de enviar o prompt, `handoff()` se inscreve nos eventos da sessão e aguarda `agent_end`.\n\nNo `agent_end`, extrai o texto do handoff do estado do agente escaneando de trás para frente em busca da mensagem mais recente do `assistant`, depois concatenando todos os blocos `content` onde `type === \"text\"` com `\\n`.\n\nSuposições importantes da extração:\n\n- Apenas blocos de texto são utilizados; conteúdo não-texto é ignorado.\n- Assume que a mensagem mais recente do assistente corresponde à geração do handoff.\n- Não analisa seções markdown nem valida conformidade de formato.\n- Se a saída do assistente não tiver blocos de texto, o handoff é tratado como ausente.\n\n### 3) Verificações de cancelamento\n\n`handoff()` retorna `undefined` quando qualquer uma das condições é verdadeira:\n\n- nenhum texto de handoff capturado, ou\n- `#handoffAbortController.signal.aborted` é verdadeiro\n\nSempre limpa `#handoffAbortController` no `finally`.\n\n### 4) Criação de nova sessão\n\nSe o texto foi capturado e não foi abortado:\n\n1. Descarrega o writer da sessão atual (`sessionManager.flush()`)\n2. Inicia uma sessão totalmente nova (`sessionManager.newSession()`)\n3. Reseta o estado do agente em memória (`agent.reset()`)\n4. Vincula novamente `agent.sessionId` ao id da nova sessão\n5. Limpa os arrays de contexto enfileirados (`#steeringMessages`, `#followUpMessages`, `#pendingNextTurnMessages`)\n6. Reseta o contador de lembrete de tarefas\n\n`newSession()` cria um cabeçalho novo e uma lista de entradas vazia (leaf resetado para `null`). No caminho de handoff, nenhum `parentSession` é passado.\n\n### 5) Injeção de contexto do handoff\n\nO documento de handoff gerado é encapsulado e adicionado à nova sessão como uma entrada `custom_message`:\n\n```text\n<handoff-context>\n...handoff text...\n</handoff-context>\n\nThe above is a handoff document from a previous session. Use this context to continue the work seamlessly.\n```\n\nChamada de inserção:\n\n```ts\nthis.sessionManager.appendCustomMessageEntry(\"handoff\", handoffContent, true);\n```\n\nSemântica:\n\n- `customType`: `\"handoff\"`\n- `display`: `true` (visível na reconstrução da TUI)\n- Tipo da entrada: `custom_message` (participa do contexto do LLM)\n\n### 6) Reconstruir contexto ativo do agente\n\nApós a injeção:\n\n1. `sessionManager.buildSessionContext()` resolve a lista de mensagens para o leaf atual\n2. `agent.replaceMessages(sessionContext.messages)` torna a mensagem de handoff injetada o contexto ativo\n3. O método retorna `{ document: handoffText }`\n\nNeste ponto, o contexto ativo do LLM na nova sessão contém a mensagem de handoff injetada, não a transcrição antiga.\n\n## Modelo de persistência: sessão antiga vs nova sessão\n\n### Sessão antiga\n\nDurante a geração, a persistência normal de mensagens permanece ativa. A resposta de handoff do assistente é persistida como uma entrada regular `message` no `message_end`.\n\nResultado: a sessão original contém o handoff gerado visível como parte da transcrição histórica.\n\n### Nova sessão\n\nApós o reset da sessão, o handoff é persistido como `custom_message` com `customType: \"handoff\"`.\n\n`buildSessionContext()` converte esta entrada em uma mensagem de contexto custom/user em tempo de execução via `createCustomMessage(...)`, de modo que ela é incluída em futuros prompts da nova sessão.\n\n## Comportamento do Controller/UI\n\nComportamento de `CommandController.handleHandoffCommand`:\n\n- Chama `await session.handoff(customInstructions)`\n- Se o resultado for `undefined`: `showError(\"Handoff cancelled\")`\n- Em caso de sucesso:\n  - `rebuildChatFromMessages()` (carrega o contexto da nova sessão, incluindo o handoff injetado)\n  - invalida a linha de status e a borda superior do editor\n  - recarrega as tarefas\n  - adiciona linha de sucesso no chat: `New session started with handoff context`\n- Em caso de exceção:\n  - se a mensagem for `\"Handoff cancelled\"` ou o nome do erro for `AbortError`: `showError(\"Handoff cancelled\")`\n  - caso contrário: `showError(\"Handoff failed: <message>\")`\n- Solicita renderização ao final\n\n## Semântica de cancelamento (comportamento atual)\n\n### Primitiva de cancelamento no nível da sessão\n\n`AgentSession` expõe:\n\n- `abortHandoff()` → aborta `#handoffAbortController`\n- `isGeneratingHandoff` → verdadeiro enquanto o controller existir\n\nQuando este caminho de abort é utilizado, o subscriber do handoff rejeita com `Error(\"Handoff cancelled\")`, e o command controller o mapeia para a UI de cancelamento.\n\n### Limitação do caminho interativo `/handoff`\n\nNa fiação atual do controller interativo, `/handoff` não instala um handler dedicado de Escape que chame `abortHandoff()` (diferentemente dos caminhos de compactação/resumo de branch que temporariamente sobrescrevem `editor.onEscape`).\n\nImpacto prático:\n\n- Há suporte a cancelamento no nível da sessão, mas nenhum hook de keybinding específico para handoff no caminho do comando `/handoff`.\n- A interrupção pelo usuário ainda pode ocorrer através de caminhos mais amplos de abort do agente, mas esse não é o mesmo canal de cancelamento explícito utilizado por `abortHandoff()`.\n\n## Handoff abortado vs falho\n\nClassificação atual da UI:\n\n- **Abortado/cancelado**\n  - O caminho `abortHandoff()` dispara `\"Handoff cancelled\"`, ou\n  - `AbortError` lançado\n  - A UI exibe `Handoff cancelled`\n\n- **Falho**\n  - qualquer outro erro lançado por `handoff()` / pipeline de prompt (erros de validação de modelo/API, exceções em tempo de execução, etc.)\n  - A UI exibe `Handoff failed: ...`\n\nNuance adicional: se a geração é concluída mas nenhum texto é extraído, `handoff()` retorna `undefined` e o controller atualmente reporta **cancelado**, não **falho**.\n\n## Proteções de sessão curta e conteúdo mínimo\n\nDuas proteções impedem handoffs com baixo sinal informacional:\n\n- Camada de UI (`handleHandoffCommand`): avisa e retorna antecipadamente para `< 2` entradas de mensagem\n- Camada de sessão (`handoff()`): lança a mesma condição como um erro\n\nIsso evita criar uma nova sessão com contexto de handoff vazio/quase vazio.\n\n## Resumo das transições de estado\n\nFluxo de estado de alto nível:\n\n1. Comando slash interativo interceptado\n2. Verificação prévia de contagem de mensagens\n3. `#handoffAbortController` criado (`isGeneratingHandoff = true`)\n4. Prompt interno de handoff submetido (visível no chat como geração normal do assistente)\n5. No `agent_end`, último texto do assistente extraído\n6. Se ausente/abortado → retorna `undefined` ou caminho de erro de cancelamento\n7. Se presente:\n   - descarrega sessão antiga\n   - cria nova sessão vazia\n   - reseta filas/contadores em tempo de execução\n   - adiciona `custom_message(handoff)`\n   - reconstrói e substitui mensagens ativas do agente\n8. Controller reconstrói a UI do chat e anuncia sucesso\n9. `#handoffAbortController` limpo (`isGeneratingHandoff = false`)\n\n## Suposições e limitações conhecidas\n\n- A extração do handoff é heurística: \"últimos blocos de texto do assistente\"; sem validação estrutural.\n- Não há verificação rígida de que o markdown gerado segue o formato de seções solicitado.\n- Texto extraído ausente é reportado como cancelamento na UX do controller.\n- O fluxo interativo do `/handoff` atualmente não possui uma vinculação dedicada de Escape→`abortHandoff()`.\n- Metadados de linhagem da nova sessão (`parentSession`) não são definidos por este caminho.\n",
	"pt-br/sessions/memory.md": "---\ntitle: Memória Autônoma\ndescription: >-\n  Sistema de memória autônoma para persistir preferências do usuário, contexto\n  do projeto e feedback entre sessões.\nsidebar:\n  order: 7\n  label: Memória autônoma\ni18n:\n  sourceHash: 2aa9f516aa1e\n  translator: machine\n---\n\n# Memória Autônoma\n\nQuando habilitado, o agente extrai automaticamente conhecimento durável de sessões passadas e injeta um resumo compacto em cada nova sessão. Com o tempo, ele constrói um armazenamento de memória com escopo de projeto — decisões técnicas, fluxos de trabalho recorrentes, armadilhas — que é transferido automaticamente sem esforço manual.\n\nDesabilitado por padrão. Habilite via `/settings` ou `config.yml`:\n\n```yaml\nmemories:\n  enabled: true\n```\n\n## Uso\n\n### O que é injetado\n\nNo início da sessão, se um resumo de memória existir para o projeto atual, ele é injetado no prompt do sistema como um bloco de **Memory Guidance**. O agente é instruído a:\n\n- Tratar a memória como contexto heurístico — útil para processos e decisões anteriores, não autoritativo sobre o estado atual do repositório.\n- Citar o caminho do artefato de memória quando a memória alterar o plano, e combiná-lo com evidências do repositório atual antes de agir.\n- Preferir o estado do repositório e a instrução do usuário quando conflitarem com a memória; tratar memória conflitante como obsoleta.\n\n### Lendo artefatos de memória\n\nO agente pode ler arquivos de memória diretamente usando URLs `memory://` com a ferramenta `read`:\n\n| URL | Conteúdo |\n|---|---|\n| `memory://root` | Resumo compacto injetado na inicialização |\n| `memory://root/MEMORY.md` | Documento completo de memória de longo prazo |\n| `memory://root/skills/<name>/SKILL.md` | Um playbook de habilidade gerado |\n\n### Comando slash `/memory`\n\n| Subcomando | Efeito |\n|---|---|\n| `view` | Mostrar o payload atual de injeção de memória |\n| `clear` / `reset` | Excluir todos os dados de memória e artefatos gerados |\n| `enqueue` / `rebuild` | Forçar a consolidação a executar na próxima inicialização |\n\n## Como funciona\n\nAs memórias são construídas por um pipeline em segundo plano que é executado na inicialização ou acionado manualmente via comando slash.\n\n**Fase 1 — extração por sessão:** Para cada sessão passada que foi alterada desde o último processamento, um modelo lê o histórico da sessão e extrai sinais duráveis: decisões técnicas, restrições, falhas resolvidas, fluxos de trabalho recorrentes. Sessões muito recentes, muito antigas ou atualmente ativas são ignoradas. Cada extração produz um bloco de memória bruto e uma sinopse curta para aquela sessão.\n\n**Fase 2 — consolidação:** Após a extração, uma segunda passagem do modelo lê todas as extrações por sessão e produz três saídas escritas em disco:\n\n- `MEMORY.md` — um documento de memória de longo prazo curado\n- `memory_summary.md` — o texto compacto injetado no início da sessão\n- `skills/` — playbooks procedurais reutilizáveis, cada um em seu próprio subdiretório\n\nA Fase 2 usa um lease para evitar execução duplicada quando múltiplos processos iniciam simultaneamente. Diretórios de habilidades obsoletos de execuções anteriores são podados automaticamente.\n\nToda saída é verificada quanto a segredos antes de ser escrita em disco.\n\n### Comportamento de extração\n\nO comportamento de extração e consolidação de memória é inteiramente orientado por arquivos de prompt estáticos em `src/prompts/memories/`.\n\n| Arquivo | Propósito | Variáveis |\n|---|---|---|\n| `stage_one_system.md` | Prompt do sistema para extração por sessão | — |\n| `stage_one_input.md` | Template de turno do usuário envolvendo conteúdo da sessão | `{{thread_id}}`, `{{response_items_json}}` |\n| `consolidation.md` | Prompt para consolidação entre sessões | `{{raw_memories}}`, `{{rollout_summaries}}` |\n| `read_path.md` | Orientação de memória injetada em sessões ativas | `{{memory_summary}}` |\n\n### Seleção de modelo\n\nA memória utiliza o sistema de roles de modelo.\n\n| Fase | Role | Propósito |\n|---|---|---|\n| Fase 1 (extração) | `default` | Extração de conhecimento por sessão |\n| Fase 2 (consolidação) | `smol` | Síntese entre sessões |\n\nSe `smol` não estiver configurado, a Fase 2 recorre ao role `default`.\n\n## Configuração\n\n| Configuração | Padrão | Descrição |\n|---|---|---|\n| `memories.enabled` | `false` | Chave principal |\n| `memories.maxRolloutAgeDays` | `30` | Sessões mais antigas que este valor não são processadas |\n| `memories.minRolloutIdleHours` | `12` | Sessões ativas mais recentemente que este valor são ignoradas |\n| `memories.maxRolloutsPerStartup` | `64` | Limite de sessões processadas em uma única inicialização |\n| `memories.summaryInjectionTokenLimit` | `5000` | Máximo de tokens do resumo injetado no prompt do sistema |\n\nAjustes adicionais (concorrência, durações de lease, orçamentos de tokens) estão disponíveis na configuração para uso avançado.\n\n## Arquivos principais\n\n- `src/memories/index.ts` — orquestração do pipeline, injeção, tratamento de comandos slash\n- `src/memories/storage.ts` — fila de trabalhos e registro de threads com suporte SQLite\n- `src/prompts/memories/` — templates de prompts de memória\n- `src/internal-urls/memory-protocol.ts` — manipulador de URLs `memory://`\n",
	"pt-br/sessions/non-compaction-retry-policy.md": "---\ntitle: Política de Auto-Retry Fora da Compactação\ndescription: >-\n  Política de auto-retry para falhas transitórias de API fora do caminho de\n  compactação.\nsidebar:\n  order: 6\n  label: Política de retry\ni18n:\n  sourceHash: 8999a0258dd8\n  translator: machine\n---\n\n# Política de auto-retry fora da compactação\n\nEste documento descreve o caminho padrão de retry para erros de API no `AgentSession`.\n\nEle exclui explicitamente a recuperação de estouro de contexto via auto-compactação. O estouro é tratado pela lógica de compactação e está documentado separadamente em [`compaction.md`](./compaction.md).\n\n## Arquivos de implementação\n\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/config/settings-schema.ts`](../../packages/coding-agent/src/config/settings-schema.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts)\n- [`../src/modes/rpc/rpc-client.ts`](../../packages/coding-agent/src/modes/rpc/rpc-client.ts)\n- [`../src/modes/rpc/rpc-types.ts`](../../packages/coding-agent/src/modes/rpc/rpc-types.ts)\n\n## Limite de escopo vs compactação\n\nRetry e compactação são verificados a partir do mesmo caminho `agent_end`, mas são intencionalmente separados:\n\n1. `agent_end` inspeciona a última mensagem do assistente.\n2. `#isRetryableError(...)` executa primeiro.\n3. Se o retry é iniciado, as verificações de compactação são ignoradas para aquele turno.\n4. Erros de estouro de contexto são rigorosamente excluídos da classificação de retry (`isContextOverflow(...)` interrompe o retry antecipadamente).\n5. O estouro, portanto, cai para `#checkCompaction(...)` em vez do retry padrão.\n\nPortanto: falhas de estilo sobrecarga/rate/servidor/rede usam esta política de retry; estouro de janela de contexto usa recuperação por compactação.\n\n## Classificação de retry\n\n`#isRetryableError(...)` requer todas as seguintes condições:\n\n- `stopReason === \"error\"` do assistente\n- `errorMessage` existe\n- a mensagem **não** é estouro de contexto\n- `errorMessage` corresponde a `#isRetryableErrorMessage(...)`\n\nConjunto atual de padrões retryáveis (baseado em regex):\n\n- overloaded\n- rate limit / usage limit / too many requests\n- classes de servidor tipo HTTP: 429, 500, 502, 503, 504\n- service unavailable / server error / internal error\n- connection error / fetch failed\n- expressão `retry delay`\n\nEsta é uma classificação por padrão de texto, não códigos de erro tipados do provedor.\n\n## Ciclo de vida do retry e transições de estado\n\nEstado da sessão usado pelo retry:\n\n- `#retryAttempt: number` (`0` significa ocioso)\n- `#retryPromise: Promise<void> | undefined` (rastreia o ciclo de vida do retry em andamento)\n- `#retryResolve: (() => void) | undefined` (resolve `#retryPromise`)\n- `#retryAbortController: AbortController | undefined` (cancela o sleep de backoff)\n\nFluxo (`#handleRetryableError`):\n\n1. Lê o grupo de configurações `retry`.\n2. Se `retry.enabled === false`, para imediatamente (`false`, nenhum retry iniciado).\n3. Incrementa `#retryAttempt`.\n4. Cria `#retryPromise` uma vez (primeira tentativa em uma cadeia).\n5. Se a tentativa excedeu `retry.maxRetries`, emite evento de falha final e para.\n6. Calcula o atraso: `retry.baseDelayMs * 2^(attempt-1)`.\n7. Para erros de limite de uso, analisa dicas de retry e chama o armazenamento de autenticação (`markUsageLimitReached(...)`); se a troca de provedor/modelo for bem-sucedida, força o atraso para `0`.\n8. Emite `auto_retry_start`.\n9. Remove a mensagem de erro do assistente final do estado de runtime do agente (mantida no histórico de sessão persistido).\n10. Dorme com suporte a abort.\n11. Ao acordar, agenda `agent.continue()` via `setTimeout(..., 0)`.\n\n### O que reseta os contadores de retry\n\n`#retryAttempt` reseta para `0` nestes casos:\n\n- primeira mensagem bem-sucedida do assistente (sem erro, sem abort) após o início dos retries (emite `auto_retry_end { success: true }`)\n- cancelamento do retry durante o sleep de backoff\n- caminho de máximo de retries excedido\n\n`#retryPromise` resolve/limpa quando a cadeia de retry termina (sucesso, cancelamento ou máximo excedido), via `#resolveRetry()`.\n\n## Semântica de backoff e máximo de tentativas\n\nConfigurações:\n\n- `retry.enabled` (padrão `true`)\n- `retry.maxRetries` (padrão `3`)\n- `retry.baseDelayMs` (padrão `2000`)\n\nNumeração de tentativas:\n\n- o contador de tentativas é incrementado antes da verificação de máximo\n- eventos de início usam a tentativa atual (base 1)\n- evento de fim por máximo excedido reporta `attempt: this.#retryAttempt - 1` (contagem da última tentativa de retry)\n\nSequência de backoff com configurações padrão:\n\n- tentativa 1: 2000 ms\n- tentativa 2: 4000 ms\n- tentativa 3: 8000 ms\n\nEntradas de substituição de atraso são usadas apenas no caminho de tratamento de limite de uso, e apenas para influenciar a decisão de troca de modelo/conta no armazenamento de autenticação. No caminho principal de retry fora da compactação, o backoff permanece como atraso exponencial local, a menos que a troca seja bem-sucedida (`delayMs = 0`).\n\n## Mecânica de abort\n\n### Abort explícito de retry\n\n`abortRetry()`:\n\n- aborta `#retryAbortController` (se presente)\n- resolve a promise de retry (`#resolveRetry()`) para que os awaitters sejam desbloqueados\n\nSe o abort ocorre durante o sleep, o caminho de catch emite:\n\n- `auto_retry_end { success: false, finalError: \"Retry cancelled\" }`\n- reseta tentativa/controlador\n\n### Interação com abort de operação global\n\n`abort()` chama `abortRetry()` antes de abortar o stream ativo do agente. Isso garante que o backoff de retry seja cancelado quando o usuário emite um abort geral.\n\n### Interação com TUI\n\nNo `auto_retry_start`, EventController:\n\n- troca o handler de `Esc` para `session.abortRetry()`\n- renderiza texto de loader: `Retrying (attempt/maxAttempts) in Ns… (esc to cancel)`\n\nNo `auto_retry_end`, restaura o handler anterior de `Esc` e limpa o estado do loader.\n\n## Comportamento de streaming e conclusão de prompt\n\n`prompt()` em última instância aguarda `#waitForRetry()` após `agent.prompt(...)` retornar.\n\nEfeito:\n\n- uma chamada de prompt não resolve completamente até que qualquer cadeia de retry iniciada termine (sucesso/falha/cancelamento)\n- o ciclo de vida do retry faz parte de um limite lógico de execução de prompt\n\nIsso impede que os chamadores tratem um turno em retry como concluído prematuramente.\n\n## Controles: configurações e RPC\n\n### Opções de configuração\n\nDefinidas no esquema de configurações sob o grupo retry:\n\n- `retry.enabled`\n- `retry.maxRetries`\n- `retry.baseDelayMs`\n\nAlternadores programáticos na sessão:\n\n- `setAutoRetryEnabled(enabled)` escreve `retry.enabled`\n- `autoRetryEnabled` lê `retry.enabled`\n- `isRetrying` reporta se a promise do ciclo de vida de retry está ativa\n\n### Controles RPC\n\nSuperfície de comandos RPC:\n\n- `set_auto_retry` → `session.setAutoRetryEnabled(command.enabled)`\n- `abort_retry` → `session.abortRetry()`\n\nHelpers do cliente:\n\n- `RpcClient.setAutoRetry(enabled)`\n- `RpcClient.abortRetry()`\n\nAmbos os comandos retornam respostas de sucesso; detalhes de progresso/falha de retry vêm de eventos de sessão transmitidos via streaming, não de payloads de resposta de comando.\n\n## Emissão de eventos e exposição de falhas\n\nEventos de retry em nível de sessão:\n\n- `auto_retry_start { attempt, maxAttempts, delayMs, errorMessage }`\n- `auto_retry_end { success, attempt, finalError? }`\n\nPropagação:\n\n- emitidos através de `AgentSession.subscribe(...)`\n- encaminhados ao executor de extensão como eventos de extensão\n- no modo RPC, encaminhados diretamente como objetos de evento JSON (`session.subscribe(event => output(event))`)\n- no TUI, consumidos pelo `EventController` para UI de loader/erro\n\nExposição de falha final:\n\n- No máximo excedido ou cancelamento, `auto_retry_end.success === false`\n- TUI exibe: `Retry failed after N attempts: <finalError>`\n- Extensões/hooks recebem `auto_retry_end` com os mesmos campos\n- Consumidores RPC recebem o mesmo objeto de evento no stream stdout\n\n## Condições de parada permanente\n\nO retry para e não continua automaticamente quando qualquer uma dessas condições ocorre:\n\n- `retry.enabled` é false\n- o erro não é classificado como retryável\n- o erro é estouro de contexto (delegado ao caminho de compactação)\n- máximo de retries excedido\n- usuário cancela o retry (`abort_retry` ou `Esc` durante o loader de retry)\n- abort global (`abort`) cancela o retry primeiro\n\nUma nova cadeia de retry ainda pode iniciar posteriormente em um erro retryável futuro após os contadores serem resetados.\n\n## Ressalvas operacionais\n\n- A classificação é por correspondência de texto via regex; erros estruturados específicos do provedor não são usados aqui.\n- O retry remove o erro do assistente que falhou do **contexto de runtime** antes de re-continuar, mas o histórico da sessão ainda mantém essa entrada de erro.\n- `RpcSessionState` atualmente expõe `autoCompactionEnabled` mas não um campo `autoRetryEnabled`; chamadores RPC devem rastrear seu próprio estado de alternância ou consultar configurações através de outras APIs.\n",
	"pt-br/sessions/session-operations-export-share-fork-resume.md": "---\ntitle: 'Operações de Sessão: Exportar, Dump, Compartilhar, Fork, Retomar'\ndescription: >-\n  Operações de sessão para exportação, compartilhamento, fork e retomada de\n  conversas.\nsidebar:\n  order: 3\n  label: Operações\ni18n:\n  sourceHash: e3c210b29c3e\n  translator: machine\n---\n\n# Operações de Sessão: export, dump, share, fork, resume/continue\n\nEste documento descreve o comportamento visível ao operador para as operações de exportação/compartilhamento/fork/retomada de sessão conforme implementadas atualmente.\n\n## Arquivos de implementação\n\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/export/html/index.ts`](../../packages/coding-agent/src/export/html/index.ts)\n- [`../src/export/custom-share.ts`](../../packages/coding-agent/src/export/custom-share.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n\n## Matriz de operações\n\n| Operação | Caminho de entrada | Mutação de sessão | Criação/troca de arquivo de sessão | Artefato de saída |\n|---|---|---|---|---|\n| `/dump` | Comando slash interativo | Não | Não | Texto na área de transferência |\n| `/export [path]` | Comando slash interativo | Não | Não | Arquivo HTML |\n| `--export <session.jsonl> [outputPath]` | Caminho rápido de inicialização CLI | Sem mutação de sessão em tempo de execução | Sem sessão ativa; lê o arquivo alvo | Arquivo HTML |\n| `/share` | Comando slash interativo | Não | Não | HTML temporário + URL de compartilhamento/gist |\n| `/fork` | Comando slash interativo | Sim (identidade da sessão ativa muda) | Cria novo arquivo de sessão e alterna a sessão atual para ele (somente modo persistente) | Copia diretório de artefatos para o novo namespace de sessão quando presente |\n| `/resume` | Comando slash interativo | Sim (estado ativo em memória é substituído) | Alterna para arquivo de sessão existente selecionado | Nenhum |\n| `--resume` | Inicialização CLI (seletor) | Sim após criação da sessão | Abre arquivo de sessão existente selecionado | Nenhum |\n| `--resume <id\\|path>` | Inicialização CLI | Sim após criação da sessão | Abre sessão existente; caso cross-project pode fazer fork no projeto atual | Nenhum |\n| `--continue` | Inicialização CLI | Sim após criação da sessão | Abre breadcrumb do terminal ou sessão mais recente; cria uma nova se nenhuma existir | Nenhum |\n\n## Export e dump\n\n### `/export [outputPath]` (interativo)\n\nFluxo:\n\n1. `InputController` roteia `/export...` para `CommandController.handleExportCommand`.\n2. O comando divide por espaços em branco e usa apenas o primeiro argumento após `/export` como `outputPath`.\n3. `AgentSession.exportToHtml()` chama `exportSessionToHtml(sessionManager, state, { outputPath, themeName })`.\n4. Em caso de sucesso, a UI mostra o caminho e abre o arquivo no navegador.\n\nDetalhes de comportamento:\n\n- Argumentos `--copy`, `clipboard` e `copy` são explicitamente rejeitados com um aviso para usar `/dump`.\n- A exportação incorpora cabeçalho/entradas/folha da sessão mais o `systemPrompt` atual e descrições de ferramentas do estado do agente.\n- Nenhuma entrada de sessão é adicionada durante a exportação.\n\nRessalva:\n\n- A análise de argumentos é baseada em espaços em branco (`text.split(/\\s+/)`), então caminhos entre aspas com espaços não são preservados como um único caminho por este fluxo de comando.\n\n### `--export <inputSessionFile> [outputPath]` (CLI)\n\nFluxo em `main.ts`:\n\n1. Tratado antecipadamente (antes da inicialização interativa/de sessão).\n2. Chama `exportFromFile(inputPath, outputPath?)`.\n3. `SessionManager.open(inputPath)` carrega as entradas, então o HTML é gerado e escrito.\n4. O processo imprime `Exported to: ...` e encerra.\n\nDetalhes de comportamento:\n\n- Arquivo de entrada ausente é exibido como `File not found: <path>`.\n- Este caminho não cria um `AgentSession` e não altera nenhuma sessão em execução.\n\n### `/dump` (exportação interativa para área de transferência)\n\nFluxo:\n\n1. `CommandController.handleDumpCommand()` chama `session.formatSessionAsText()`.\n2. Se retornar string vazia, reporta `No messages to dump yet.`\n3. Caso contrário, copia para a área de transferência via `copyToClipboard` nativo.\n\nO conteúdo do dump inclui:\n\n- Prompt do sistema\n- Modelo ativo/nível de pensamento\n- Definições de ferramentas + parâmetros\n- Mensagens de usuário/assistente\n- Blocos de pensamento e chamadas de ferramentas\n- Resultados de ferramentas e blocos de execução (exceto entradas bash/python com `excludeFromContext`)\n- Entradas de custom/hook/menção de arquivo/resumo de branch/resumo de compactação\n\nNenhuma alteração de persistência de sessão é feita pelo dump.\n\n## Share\n\n`/share` é somente interativo e sempre começa exportando a sessão atual para um arquivo HTML temporário.\n\n### Fase 1: exportação temporária\n\n- Caminho do arquivo temporário: `${os.tmpdir()}/${Snowflake.next()}.html`\n- Usa `session.exportToHtml(tmpFile)`\n- Se a exportação falhar (notavelmente em sessões em memória), o compartilhamento termina com erro.\n\n### Fase 2: handler de compartilhamento personalizado (se presente)\n\n`loadCustomShare()` verifica `~/.xcsh/agent` para o primeiro candidato existente:\n\n- `share.ts`\n- `share.js`\n- `share.mjs`\n\nRequisitos:\n\n- O módulo deve exportar por padrão uma função `(htmlPath) => Promise<CustomShareResult | string | undefined>`.\n\nSe presente e válido:\n\n- A UI entra no estado de carregamento `Sharing...`.\n- Interpretação do resultado do handler:\n  - string => tratada como URL, exibida e aberta\n  - objeto => `url` e/ou `message` exibidos; `url` aberta\n  - `undefined`/falsy => `Session shared` genérico\n- O arquivo temporário é removido após a conclusão.\n\nComportamento crítico de fallback:\n\n- Se o handler personalizado existe mas o carregamento falha, o comando apresenta erro e retorna.\n- Se o handler personalizado executa e lança exceção, o comando apresenta erro e retorna.\n- Em ambos os casos de falha, **não** faz fallback para GitHub gist.\n- O fallback para gist acontece apenas quando nenhum script de compartilhamento personalizado existe.\n\n### Fase 3: fallback padrão para gist\n\nApenas quando nenhum handler de compartilhamento personalizado é encontrado:\n\n1. Valida `gh auth status`.\n2. Mostra carregamento `Creating gist...`.\n3. Executa `gh gist create --public=false <tmpFile>`.\n4. Analisa a URL do gist, deriva o id do gist, constrói URL de preview `https://gistpreview.github.io/?<id>`.\n5. Mostra ambas as URLs de preview e gist; abre o preview.\n\nSemânticas de cancelamento/abort no compartilhamento:\n\n- O carregamento tem um hook `onAbort` que restaura a UI do editor e reporta `Share cancelled`.\n- O comando subjacente `gh gist create` não recebe um sinal de abort neste fluxo de código; o cancelamento é em nível de UI e verificado após o comando retornar.\n\n## Fork\n\n`/fork` cria uma nova sessão a partir da atual e alterna a identidade da sessão ativa.\n\n### Pré-condições e guardas imediatas\n\n- Se o agente está em streaming, `/fork` é rejeitado com aviso.\n- Indicadores de status/carregamento da UI são limpos antes da operação.\n\n### Fluxo em nível de sessão\n\n`AgentSession.fork()`:\n\n1. Emite `session_before_switch` com `reason: \"fork\"` (cancelável).\n2. Descarrega escritas pendentes.\n3. Chama `SessionManager.fork()`.\n4. Copia o diretório de artefatos do namespace da sessão antiga para o novo namespace (melhor esforço; falhas de cópia que não sejam ENOENT são registradas em log, não são fatais).\n5. Atualiza `agent.sessionId`.\n6. Emite `session_switch` com `reason: \"fork\"`.\n\nComportamento de `SessionManager.fork()`:\n\n- Requer modo persistente e arquivo de sessão existente.\n- Cria novo id de sessão e novo caminho de arquivo JSONL.\n- Reescreve o cabeçalho com:\n  - novo `id`\n  - novo timestamp\n  - `cwd` inalterado\n  - `parentSession` definido como o id da sessão anterior\n- Mantém todas as entradas não-cabeçalho inalteradas no novo arquivo.\n\n### Comportamento não persistente\n\n- O gerenciador de sessão em memória retorna `undefined` de `fork()`.\n- `AgentSession.fork()` retorna `false`.\n- A UI reporta `Fork failed (session not persisted or cancelled)`.\n\n## Resume e continue\n\n## `/resume` interativo\n\nFluxo:\n\n1. Abre o seletor de sessão populado via `SessionManager.list(currentCwd, currentSessionDir)`.\n2. Na seleção, `SelectorController.handleResumeSession(sessionPath)` chama `session.switchSession(sessionPath)`.\n3. A UI limpa/reconstrói o chat e tarefas, então reporta `Resumed session`.\n\nNotas:\n\n- Este seletor lista apenas sessões no escopo do diretório de sessão atual.\n- Não usa busca global cross-project.\n\n## CLI `--resume`\n\n### `--resume` (sem valor)\n\n- `main.ts` lista sessões para o cwd/sessionDir atual e abre o seletor.\n- O caminho selecionado é aberto com `SessionManager.open(selectedPath)` antes da criação da sessão.\n\n### `--resume <value>`\n\nOrdem de resolução de `createSessionManager()`:\n\n1. Se o valor parece um caminho (`/`, `\\`, ou `.jsonl`), abre diretamente.\n2. Caso contrário, trata como prefixo de id:\n   - busca no escopo atual (`SessionManager.list(cwd, sessionDir)`)\n   - se não encontrado e sem `sessionDir` explícito, busca global (`SessionManager.listAll()`)\n\nComportamento de correspondência de id cross-project:\n\n- Se o cwd da sessão correspondente difere do cwd atual, o CLI pergunta:\n  - `Session found in different project ... Fork into current directory? [y/N]`\n- Em caso afirmativo: `SessionManager.forkFrom(match.path, cwd, sessionDir)` cria um novo arquivo local com fork.\n- Em caso negativo/padrão não-TTY: o comando apresenta erro.\n\n## CLI `--continue`\n\n`SessionManager.continueRecent(cwd, sessionDir)`:\n\n1. Resolve o diretório de sessão para o cwd atual.\n2. Lê primeiro o breadcrumb com escopo de terminal.\n3. Faz fallback para o arquivo de sessão modificado mais recentemente.\n4. Abre a sessão encontrada; se nenhuma existir, cria uma nova sessão.\n\nEste é um comportamento apenas de inicialização; não existe um comando slash interativo `/continue`.\n\n## Como a troca de sessão realmente altera o estado em tempo de execução\n\n`AgentSession.switchSession(sessionPath)` faz a transição em tempo de execução usada por operações do tipo resume:\n\n1. Emite `session_before_switch` com `reason: \"resume\"` e `targetSessionFile` (cancelável).\n2. Desconecta a assinatura de eventos do agente e aborta trabalho em andamento.\n3. Limpa mensagens enfileiradas de steering/follow-up/próximo turno.\n4. Descarrega escritas do gerenciador de sessão atual.\n5. `sessionManager.setSessionFile(sessionPath)` e atualiza `agent.sessionId`.\n6. Constrói contexto de sessão a partir das entradas carregadas.\n7. Emite `session_switch` com `reason: \"resume\"`.\n8. Substitui mensagens do agente a partir do contexto.\n9. Restaura o modelo (se disponível no registro atual).\n10. Restaura ou inicializa o nível de pensamento.\n11. Reconecta a assinatura de eventos do agente.\n\nNenhum novo arquivo de sessão é criado pelo próprio `switchSession()`.\n\n## Emissões de eventos e pontos de cancelamento\n\n### Hooks de ciclo de vida de switch/fork\n\nPara `newSession`, `fork` e `switchSession`:\n\n- Evento anterior: `session_before_switch`\n  - razões: `new`, `fork`, `resume`\n  - cancelável retornando `{ cancel: true }`\n- Evento posterior: `session_switch`\n  - mesmo conjunto de razões\n  - inclui `previousSessionFile`\n\n`ExtensionRunner.emit()` retorna antecipadamente no primeiro resultado de evento anterior que cancele.\n\n### Comportamento `onSession` de ferramentas personalizadas\n\nO SDK faz ponte entre eventos de sessão de extensão e callbacks `onSession` de ferramentas personalizadas:\n\n- `session_switch` -> `onSession({ reason: \"switch\", previousSessionFile })`\n- `session_branch` -> `reason: \"branch\"`\n- `session_start` -> `reason: \"start\"`\n- `session_tree` -> `reason: \"tree\"`\n- `session_shutdown` -> `reason: \"shutdown\"`\n\nEsses callbacks são observacionais; eles não cancelam switch/fork.\n\n### Outras superfícies de cancelamento relevantes para este documento\n\n- `/fork` é bloqueado durante streaming (o usuário deve aguardar/abortar a resposta atual primeiro).\n- O seletor de `/resume` pode ser cancelado pelo usuário fechando o seletor.\n- `--resume <id>` cross-project pode ser cancelado recusando o prompt de fork.\n- `/share` tem caminho de abort na UI (`Share cancelled`) para o fluxo de gist; não implementa semânticas de kill de processo para `gh gist create` neste fluxo de código.\n\n## Comportamento de sessão não persistente (em memória)\n\nQuando o gerenciador de sessão é criado com `SessionManager.inMemory()` (`--no-session`):\n\n- O caminho do arquivo de sessão está ausente.\n- `/export` e `/share` falham com `Cannot export in-memory session to HTML` (propagado para a UI de erro do comando).\n- `/fork` falha porque `SessionManager.fork()` requer persistência.\n- `/dump` ainda funciona porque serializa o estado do agente em memória.\n- As semânticas de resume/continue do CLI são ignoradas se `--no-session` estiver definido, porque a criação do gerenciador retorna em memória imediatamente.\n\n## Ressalvas de implementação conhecidas (conforme código atual)\n\n- `SelectorController.handleResumeSession()` não verifica o resultado booleano de `session.switchSession(...)`; uma troca cancelada por hook ainda pode prosseguir pelo caminho de repaint/status da UI \"Resumed session\".\n- Falhas de `/share` com compartilhamento personalizado não degradam para o fallback de gist padrão; elas encerram o comando com erro.\n- A tokenização de argumentos de `/export` é simplista e não preserva caminhos entre aspas com espaços.\n",
	"pt-br/sessions/session-switching-and-recent-listing.md": "---\ntitle: Alternância de Sessões e Listagem de Sessões Recentes\ndescription: >-\n  Mecânicas de alternância de sessões e listagem de sessões recentes com busca e\n  filtragem.\nsidebar:\n  order: 4\n  label: Alternância e recentes\ni18n:\n  sourceHash: aae56130b508\n  translator: machine\n---\n\n# Alternância de sessões e listagem de sessões recentes\n\nEste documento descreve como o coding-agent descobre sessões recentes, resolve alvos de `--resume`, apresenta seletores de sessão e alterna a sessão ativa em tempo de execução.\n\nO foco está no comportamento da implementação atual, incluindo caminhos de fallback e ressalvas.\n\n## Arquivos de implementação\n\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/cli/session-picker.ts`](../../packages/coding-agent/src/cli/session-picker.ts)\n- [`../src/modes/components/session-selector.ts`](../../packages/coding-agent/src/modes/components/session-selector.ts)\n- [`../src/modes/controllers/selector-controller.ts`](../../packages/coding-agent/src/modes/controllers/selector-controller.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n\n## Descoberta de sessões recentes\n\n### Escopo de diretório\n\nO `SessionManager` armazena sessões em um diretório com escopo de cwd por padrão:\n\n- `~/.xcsh/agent/sessions/--<cwd-encoded>--/*.jsonl`\n\n`SessionManager.list(cwd, sessionDir?)` lê apenas esse diretório, a menos que um `sessionDir` explícito seja fornecido.\n\n### Dois caminhos de listagem com payloads diferentes\n\nExistem dois pipelines de listagem diferentes:\n\n1. `getRecentSessions(sessionDir, limit)` (visão de boas-vindas/resumo)\n   - Lê apenas um prefixo de 4KB (`readTextPrefix(..., 4096)`) de cada arquivo.\n   - Analisa o cabeçalho + prévia do texto mais antigo do usuário.\n   - Retorna `RecentSessionInfo` leve com getters lazy de `name` e `timeAgo`.\n   - Ordena por `mtime` do arquivo em ordem decrescente.\n\n2. `SessionManager.list(...)` / `SessionManager.listAll()` (seletores de retomada e correspondência por ID)\n   - Lê os arquivos de sessão completos.\n   - Constrói objetos `SessionInfo` (`id`, `cwd`, `title`, `messageCount`, `firstMessage`, `allMessagesText`, timestamps).\n   - Descarta sessões com zero entradas `message`.\n   - Ordena por `modified` em ordem decrescente.\n\n### Comportamento de fallback de metadados\n\nPara resumos recentes (`RecentSessionInfo`):\n\n- preferência de nome de exibição: `header.title` -> primeiro prompt do usuário -> `header.id` -> nome do arquivo\n- o nome é truncado para 40 caracteres em exibições compactas\n- caracteres de controle/quebras de linha são removidos/sanitizados de nomes derivados do título\n\nPara entradas de lista `SessionInfo`:\n\n- `title` é `header.title` ou o `shortSummary` da compactação mais recente\n- `firstMessage` é o texto da primeira mensagem do usuário ou `\"(no messages)\"`\n\n## Resolução de `--continue` e preferência de breadcrumb do terminal\n\n`SessionManager.continueRecent(cwd, sessionDir?)` resolve o alvo nesta ordem:\n\n1. Lê o breadcrumb com escopo de terminal (`~/.xcsh/agent/terminal-sessions/<terminal-id>`)\n2. Valida o breadcrumb:\n   - o terminal atual pode ser identificado\n   - o cwd do breadcrumb corresponde ao cwd atual (comparação de caminho resolvido)\n   - o arquivo referenciado ainda existe\n3. Se o breadcrumb for inválido/ausente, faz fallback para o arquivo mais recente por mtime no diretório de sessões (`findMostRecentSession`)\n4. Se nenhum for encontrado, cria uma nova sessão\n\nA derivação do ID do terminal prefere o caminho TTY e faz fallback para identificadores baseados em variáveis de ambiente (`KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION`).\n\nAs escritas de breadcrumb são best-effort e não fatais.\n\n## Resolução do alvo de retomada no tempo de inicialização (`main.ts`)\n\n### `--resume <valor>`\n\n`createSessionManager(...)` trata `--resume` com valor string em dois modos:\n\n1. Valor semelhante a caminho (contém `/`, `\\\\`, ou termina com `.jsonl`)\n   - `SessionManager.open(sessionArg, parsed.sessionDir)` direto\n\n2. Valor de prefixo de ID\n   - encontra correspondência em `SessionManager.list(cwd, sessionDir)` por `id.startsWith(sessionArg)`\n   - se não houver correspondência local e `sessionDir` não for forçado, tenta `SessionManager.listAll()`\n   - a primeira correspondência é usada (sem prompt de ambiguidade)\n\nComportamento de correspondência entre projetos:\n\n- se o cwd da sessão correspondente difere do cwd atual, o CLI pergunta se deseja fazer fork para o projeto atual\n- sim -> `SessionManager.forkFrom(...)`\n- não -> lança erro (`Session \"...\" is in another project (...)`)\n\nSem correspondência -> lança erro (`Session \"...\" not found.`).\n\n### `--resume` (sem valor)\n\nTratado após a construção inicial do session-manager:\n\n1. lista sessões locais com `SessionManager.list(cwd, parsed.sessionDir)`\n2. se vazio: imprime `No sessions found` e encerra antecipadamente\n3. abre o seletor TUI (`selectSession`)\n4. se cancelado: imprime `No session selected` e encerra antecipadamente\n5. se selecionado: `SessionManager.open(selectedPath)`\n\n### `--continue`\n\nUsa `SessionManager.continueRecent(...)` diretamente (comportamento breadcrumb-first descrito acima).\n\n## Detalhes internos da seleção por picker\n\n## Picker CLI (`src/cli/session-picker.ts`)\n\n`selectSession(sessions)` cria uma TUI standalone com `SessionSelectorComponent` e resolve exatamente uma vez:\n\n- seleção -> resolve o caminho selecionado\n- cancelar (Esc) -> resolve `null`\n- saída forçada (caminho Ctrl+C) -> para a TUI e `process.exit(0)`\n\n## Picker interativo em sessão (`SelectorController.showSessionSelector`)\n\nFluxo:\n\n1. busca sessões do diretório de sessão atual via `SessionManager.list(currentCwd, currentSessionDir)`\n2. monta `SessionSelectorComponent` na área do editor usando `showSelector(...)`\n3. callbacks:\n   - seleção -> fecha o seletor e chama `handleResumeSession(sessionPath)`\n   - cancelar -> restaura o editor e rerenderiza\n   - sair -> `ctx.shutdown()`\n\n## Comportamento do componente seletor de sessão\n\n`SessionList` suporta:\n\n- navegação por setas/página\n- Enter para selecionar\n- Esc para cancelar\n- Ctrl+C para sair\n- busca fuzzy entre id/title/cwd/primeira mensagem/todas as mensagens/caminho da sessão\n\nComportamento de renderização com lista vazia:\n\n- renderiza uma mensagem em vez de falhar\n- Enter em lista vazia não faz nada (sem callback)\n- Esc/Ctrl+C continuam funcionando\n\nRessalva: O texto da UI diz `Press Tab to view all`, mas este componente atualmente não possui handler para Tab e a conexão atual lista apenas sessões do escopo atual.\n\n## Execução da alternância em tempo de execução (`AgentSession.switchSession`)\n\n`switchSession(sessionPath)` é o caminho principal de alternância em processo.\n\nCiclo de vida/transição de estado:\n\n1. captura `previousSessionFile`\n2. emite evento de hook `session_before_switch` (`reason: \"resume\"`, cancelável)\n3. se cancelado -> retorna `false` sem alternância\n4. desconecta do stream de eventos do agente atual\n5. aborta geração/fluxo de ferramenta ativo\n6. limpa buffers de mensagens enfileiradas de steering/follow-up/próximo turno\n7. faz flush do writer de sessão (`sessionManager.flush()`) para persistir escritas pendentes\n8. `sessionManager.setSessionFile(sessionPath)`\n   - atualiza o ponteiro do arquivo de sessão\n   - escreve breadcrumb do terminal\n   - carrega entradas / migra / resolve blobs / reindexa\n   - se dados do arquivo estiverem ausentes/inválidos: inicializa uma nova sessão naquele caminho e reescreve o cabeçalho\n9. atualiza `agent.sessionId`\n10. reconstrói o contexto via `buildSessionContext()`\n11. emite evento de hook `session_switch` (`reason: \"resume\"`, `previousSessionFile`)\n12. substitui mensagens do agente pelo contexto reconstruído\n13. restaura o modelo padrão de `sessionContext.models.default` se disponível e presente no registro de modelos\n14. restaura o nível de pensamento:\n    - se o branch já possui `thinking_level_change`, aplica o nível de sessão salvo\n    - caso contrário, deriva o nível de pensamento padrão das configurações, limita à capacidade do modelo, define-o e adiciona uma nova entrada `thinking_level_change`\n15. reconecta listeners do agente e retorna `true`\n\n## Reconstrução do estado da UI após alternância interativa\n\n`SelectorController.handleResumeSession` realiza reset da UI ao redor de `switchSession`:\n\n- para a animação de carregamento\n- limpa o contêiner de status\n- limpa a UI de mensagem pendente e o mapa de ferramentas pendentes\n- reseta referências de componente/mensagem de streaming\n- chama `session.switchSession(...)`\n- limpa o contêiner de chat e rerenderiza a partir do contexto da sessão (`renderInitialMessages`)\n- recarrega todos a partir dos artefatos da nova sessão\n- exibe `Resumed session`\n\nPortanto, o estado visível de conversa/todos é reconstruído a partir do novo arquivo de sessão.\n\n## Retomada na inicialização vs alternância em sessão\n\n### Retomada na inicialização (`--continue`, `--resume`, abertura direta)\n\n- O arquivo de sessão é escolhido antes de `createAgentSession(...)`.\n- `sdk.ts` constrói `existingSession = sessionManager.buildSessionContext()`.\n- As mensagens do agente são restauradas uma vez durante a criação da sessão.\n- Modelo/pensamento são selecionados durante a criação (incluindo lógica de restauração/fallback).\n- O modo interativo então executa `#restoreModeFromSession()` para reentrar no estado de modo persistido (atualmente plan/plan_paused).\n\n### Alternância em sessão (caminho do seletor estilo `/resume`)\n\n- Usa `AgentSession.switchSession(...)` em uma `AgentSession` já em execução.\n- Mensagens/modelo/pensamento são reconstruídos imediatamente no local.\n- Eventos de hook `session_before_switch`/`session_switch` são emitidos.\n- Chat/todos da UI são atualizados.\n- Nenhuma chamada dedicada de restauração de modo pós-alternância é feita no fluxo do seletor; o comportamento de reentrada de modo não é simétrico com o `#restoreModeFromSession()` da inicialização.\n\n## Comportamento de falha e casos extremos\n\n### Caminhos de cancelamento\n\n- Cancelamento do picker CLI -> retorna `null`, o chamador imprime `No session selected`, o processo encerra antecipadamente.\n- Cancelamento do picker interativo -> editor restaurado, sem mudança de sessão.\n- Cancelamento por hook (`session_before_switch`) -> `switchSession()` retorna `false`.\n\n### Caminhos de lista vazia\n\n- CLI `--resume` (sem valor): lista vazia imprime `No sessions found` e encerra.\n- Seletor interativo: lista vazia renderiza mensagem e permanece cancelável.\n\n### Arquivo de sessão alvo ausente/inválido\n\nAo abrir/alternar para um caminho específico (`setSessionFile`):\n\n- ENOENT -> tratado como vazio -> nova sessão inicializada naquele caminho exato e persistida.\n- cabeçalho malformado/inválido (ou entradas analisadas efetivamente ilegíveis) -> tratado como vazio -> nova sessão inicializada e persistida.\n\nEste é um comportamento de recuperação, não uma falha fatal.\n\n### Falhas fatais\n\nAlternância/abertura ainda pode lançar exceção em falhas de I/O verdadeiras (erros de permissão, falhas de reescrita, etc.), que são propagadas aos chamadores.\n\n### Ressalvas da correspondência por prefixo de ID\n\n- A correspondência por ID usa `startsWith` e pega a primeira correspondência na lista ordenada.\n- Nenhuma UI de ambiguidade se múltiplas sessões compartilham o mesmo prefixo.\n- `SessionManager.list(...)` exclui sessões com zero mensagens, portanto essas sessões não são retomáveis via correspondência por ID/picker de lista.\n",
	"pt-br/sessions/session-tree-plan.md": "---\ntitle: Arquitetura de Árvore de Sessão\ndescription: >-\n  Arquitetura de árvore de sessão com ramificação, navegação e relacionamentos\n  de conversação pai-filho.\nsidebar:\n  order: 2\n  label: Arquitetura de árvore\ni18n:\n  sourceHash: bd8b78d6c33a\n  translator: machine\n---\n\n# Arquitetura de árvore de sessão (atual)\n\nReferência: [session.md](./session.md)\n\nEste documento descreve como a navegação na árvore de sessão funciona atualmente: modelo de árvore em memória, regras de movimentação de folha, comportamento de ramificação e integração de extensões/eventos.\n\n## O que é este subsistema\n\nA sessão é armazenada como um log de entradas somente-adição (append-only), mas o comportamento em tempo de execução é baseado em árvore:\n\n- Toda entrada que não é cabeçalho possui `id` e `parentId`.\n- A posição ativa é `leafId` no `SessionManager`.\n- Adicionar uma entrada sempre cria um filho da folha atual.\n- A ramificação **não** reescreve o histórico; ela apenas altera para onde a folha aponta antes do próximo append.\n\nArquivos-chave:\n\n- `src/session/session-manager.ts` — modelo de dados da árvore, travessia, movimentação de folha, extração de branch/sessão\n- `src/session/agent-session.ts` — fluxo de navegação `/tree`, sumarização, emissão de hooks/eventos\n- `src/modes/components/tree-selector.ts` — comportamento interativo da UI de árvore e filtragem\n- `src/modes/controllers/selector-controller.ts` — orquestração do seletor para `/tree` e `/branch`\n- `src/modes/controllers/input-controller.ts` — roteamento de comandos (`/tree`, `/branch`, comportamento de duplo-escape)\n- `src/session/messages.ts` — conversão de entradas `branch_summary`, `compaction` e `custom_message` em mensagens de contexto para LLM\n\n## Modelo de dados da árvore no `SessionManager`\n\nÍndices em tempo de execução:\n\n- `#byId: Map<string, SessionEntry>` — busca rápida para qualquer entrada\n- `#leafId: string | null` — posição atual na árvore\n- `#labelsById: Map<string, string>` — rótulos resolvidos pelo id da entrada alvo\n\nAPIs da árvore:\n\n- `getBranch(fromId?)` percorre os links de pai até a raiz e retorna o caminho raiz→nó\n- `getTree()` retorna `SessionTreeNode[]` (`entry`, `children`, `label`)\n  - links de pai se tornam arrays de filhos\n  - entradas com pais ausentes são tratadas como raízes\n  - filhos são ordenados do mais antigo→mais recente por timestamp\n- `getChildren(parentId)` retorna os filhos diretos\n- `getLabel(id)` resolve o rótulo atual de `labelsById`\n\n`getTree()` é uma projeção em tempo de execução; a persistência permanece como entradas JSONL somente-adição.\n\n## Semântica de movimentação de folha\n\nExistem três primitivas de movimentação de folha:\n\n1. `branch(entryId)`\n   - Valida que a entrada existe\n   - Define `leafId = entryId`\n   - Nenhuma nova entrada é escrita\n\n2. `resetLeaf()`\n   - Define `leafId = null`\n   - O próximo append cria uma nova entrada raiz (`parentId = null`)\n\n3. `branchWithSummary(branchFromId, summary, details?, fromExtension?)`\n   - Aceita `branchFromId: string | null`\n   - Define `leafId = branchFromId`\n   - Adiciona uma entrada `branch_summary` como filho dessa folha\n   - Quando `branchFromId` é `null`, `fromId` é persistido como `\"root\"`\n\n## Comportamento de navegação `/tree` (mesmo arquivo de sessão)\n\n`AgentSession.navigateTree()` é navegação, não bifurcação de arquivo.\n\nFluxo:\n\n1. Validar o alvo e computar o caminho abandonado (`collectEntriesForBranchSummary`)\n2. Emitir `session_before_tree` com `TreePreparation`\n3. Opcionalmente sumarizar entradas abandonadas (resumo fornecido por hook ou sumarizador integrado)\n4. Computar o novo alvo da folha:\n   - selecionando uma mensagem de **usuário**: a folha move para o pai, e o texto da mensagem é retornado para preenchimento do editor\n   - selecionando uma **custom_message**: mesma regra de mensagem de usuário (folha = pai, texto preenche o editor)\n   - selecionando qualquer outra entrada: folha = id da entrada selecionada\n5. Aplicar movimentação da folha:\n   - com resumo: `branchWithSummary(newLeafId, ...)`\n   - sem resumo e `newLeafId === null`: `resetLeaf()`\n   - caso contrário: `branch(newLeafId)`\n6. Reconstruir o contexto do agente a partir da nova folha e emitir `session_tree`\n\nImportante: entradas de resumo são anexadas na **nova posição de navegação**, não na cauda do branch abandonado.\n\n## Comportamento do `/branch` (novo arquivo de sessão)\n\n`/branch` e `/tree` são intencionalmente diferentes:\n\n- `/tree` navega dentro do arquivo de sessão atual.\n- `/branch` cria um novo arquivo de branch de sessão (ou substituição em memória para modo não-persistente).\n\nFluxo do `/branch` voltado ao usuário (`SelectorController.showUserMessageSelector` → `AgentSession.branch`):\n\n- A origem do branch deve ser uma **mensagem de usuário**.\n- O texto do usuário selecionado é extraído para preenchimento do editor.\n- Se a mensagem de usuário selecionada é raiz (`parentId === null`): inicia uma nova sessão via `newSession({ parentSession: previousSessionFile })`.\n- Caso contrário: `createBranchedSession(selectedEntry.parentId)` para bifurcar o histórico até o limite do prompt selecionado.\n\nEspecificidades do `SessionManager.createBranchedSession(leafId)`:\n\n- Constrói o caminho raiz→folha via `getBranch(leafId)`; lança exceção se ausente.\n- Exclui entradas `label` existentes do caminho copiado.\n- Reconstrói entradas de rótulo novas a partir de `labelsById` resolvidos para entradas que permanecem no caminho.\n- Modo persistente: escreve novo arquivo JSONL e alterna o manager para ele; retorna o novo caminho do arquivo.\n- Modo em memória: substitui as entradas em memória; retorna `undefined`.\n\n## Reconstrução de contexto e integração de resumo/custom\n\n`buildSessionContext()` (em `session-manager.ts`) resolve o caminho ativo raiz→folha e constrói o estado efetivo de contexto do LLM:\n\n- Rastreia o estado mais recente de thinking/model/mode/ttsr no caminho.\n- Trata a compactação mais recente no caminho:\n  - emite o resumo de compactação primeiro\n  - reproduz as mensagens mantidas de `firstKeptEntryId` até o ponto de compactação\n  - depois reproduz as mensagens pós-compactação\n- Inclui entradas `branch_summary` e `custom_message` como objetos `AgentMessage`.\n\n`session/messages.ts` então mapeia esses tipos de mensagem para entrada do modelo:\n\n- `branchSummary` e `compactionSummary` se tornam mensagens de contexto com template na role de usuário\n- `custom`/`hookMessage` se tornam mensagens de conteúdo na role de usuário\n\nPortanto, a movimentação na árvore altera o contexto mudando o caminho ativo da folha, não mutando entradas antigas.\n\n## Rótulos e comportamento da UI de árvore\n\nPersistência de rótulos:\n\n- `appendLabelChange(targetId, label?)` escreve entradas `label` na cadeia da folha atual.\n- `labelsById` é atualizado imediatamente (definir ou deletar).\n- `getTree()` resolve o rótulo atual em cada nó retornado.\n\nComportamento do seletor de árvore (`tree-selector.ts`):\n\n- Achata a árvore para navegação, mantém destaque do caminho ativo e prioriza a exibição do branch ativo primeiro.\n- Suporta modos de filtro: `default`, `no-tools`, `user-only`, `labeled-only`, `all`.\n- Suporta busca de texto livre sobre conteúdo semântico renderizado.\n- `Shift+L` abre edição inline de rótulo e escreve via `appendLabelChange`.\n\nRoteamento de comandos:\n\n- `/tree` sempre abre o seletor de árvore.\n- `/branch` abre o seletor de mensagens de usuário, a menos que `doubleEscapeAction=tree`, caso em que também usa a UX do seletor de árvore.\n\n## Pontos de integração de extensões e hooks para operações de árvore\n\nAPI de extensão em tempo de comando (`ExtensionCommandContext`):\n\n- `branch(entryId)` — criar arquivo de sessão ramificado\n- `navigateTree(targetId, { summarize? })` — mover dentro da árvore/arquivo atual\n\nEventos em torno da navegação na árvore:\n\n- `session_before_tree`\n  - recebe `TreePreparation`:\n    - `targetId`\n    - `oldLeafId`\n    - `commonAncestorId`\n    - `entriesToSummarize`\n    - `userWantsSummary`\n  - pode cancelar a navegação\n  - pode fornecer payload de resumo usado em vez do sumarizador integrado\n  - recebe `signal` de abort (caminho de cancelamento via Escape)\n- `session_tree`\n  - emite `newLeafId`, `oldLeafId`\n  - inclui `summaryEntry` quando um resumo foi criado\n  - `fromExtension` indica a origem do resumo\n\nHooks de ciclo de vida adjacentes, mas relacionados:\n\n- `session_before_branch` / `session_branch` para o fluxo `/branch`\n- `session_before_compact`, `session.compacting`, `session_compact` para entradas de compactação que posteriormente afetam a reconstrução de contexto da árvore\n\n## Restrições reais e condições de borda\n\n- `branch()` não pode ter como alvo `null`; use `resetLeaf()` para o estado raiz-antes-da-primeira-entrada.\n- `branchWithSummary()` suporta alvo `null` e registra `fromId: \"root\"`.\n- Selecionar a folha atual no seletor de árvore é uma operação sem efeito (no-op).\n- A sumarização requer um modelo ativo; se ausente, a navegação com sumarização falha rapidamente.\n- Se a sumarização for abortada, a navegação é cancelada e a folha permanece inalterada.\n- Sessões em memória nunca retornam um caminho de arquivo de branch de `createBranchedSession`.\n\n## Compatibilidade legada ainda presente\n\nMigrações de sessão ainda são executadas no carregamento:\n\n- v1→v2 adiciona `id`/`parentId` e converte âncora de índice de compactação para âncora de id\n- v2→v3 migra role legado `hookMessage` para `custom`\n\nO comportamento atual em tempo de execução usa semântica de árvore versão 3 após a migração.\n",
	"pt-br/sessions/session.md": "---\ntitle: Armazenamento de Sessão e Modelo de Entradas\ndescription: >-\n  Modelo de armazenamento de sessão append-only com tipos de entrada,\n  persistência e migração entre formatos.\nsidebar:\n  order: 1\n  label: Armazenamento e modelo de entradas\ni18n:\n  sourceHash: 42fe17549e00\n  translator: machine\n---\n\n# Armazenamento de Sessão e Modelo de Entradas\n\nEste documento é a fonte de verdade sobre como as sessões do coding-agent são representadas, persistidas, migradas e reconstruídas em tempo de execução.\n\n## Escopo\n\nAbrange:\n\n- Formato JSONL de sessão e versionamento\n- Taxonomia de entradas e semântica de árvore (`id`/`parentId` + ponteiro de folha)\n- Comportamento de migração/compatibilidade ao carregar arquivos antigos ou malformados\n- Reconstrução de contexto (`buildSessionContext`)\n- Garantias de persistência, comportamento em falhas, truncamento/externalização de blobs\n- Abstrações de armazenamento (`FileSessionStorage`, `MemorySessionStorage`) e utilitários relacionados\n\nNão abrange o comportamento de renderização da UI `/tree` além das semânticas que afetam os dados da sessão.\n\n## Arquivos de Implementação\n\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`src/session/messages.ts`](../../packages/coding-agent/src/session/messages.ts)\n- [`src/session/session-storage.ts`](../../packages/coding-agent/src/session/session-storage.ts)\n- [`src/session/history-storage.ts`](../../packages/coding-agent/src/session/history-storage.ts)\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts)\n\n## Layout em Disco\n\nLocalização padrão do arquivo de sessão:\n\n```text\n~/.xcsh/agent/sessions/--<cwd-encoded>--/<timestamp>_<sessionId>.jsonl\n```\n\n`<cwd-encoded>` é derivado do diretório de trabalho removendo a barra inicial e substituindo `/`, `\\\\` e `:` por `-`.\n\nLocalização do armazenamento de blobs:\n\n```text\n~/.xcsh/agent/blobs/<sha256>\n```\n\nArquivos de breadcrumb do terminal são escritos em:\n\n```text\n~/.xcsh/agent/terminal-sessions/<terminal-id>\n```\n\nO conteúdo do breadcrumb possui duas linhas: o cwd original, seguido do caminho do arquivo de sessão. `continueRecent()` prefere este ponteiro com escopo de terminal antes de buscar pelo mtime mais recente.\n\n## Formato do Arquivo\n\nArquivos de sessão são JSONL: um objeto JSON por linha.\n\n- A linha 1 é sempre o cabeçalho da sessão (`type: \"session\"`).\n- As linhas restantes são valores `SessionEntry`.\n- Entradas são append-only em tempo de execução; a navegação de branches move um ponteiro (`leafId`) em vez de mutar entradas existentes.\n\n### Cabeçalho (`SessionHeader`)\n\n```json\n{\n  \"type\": \"session\",\n  \"version\": 3,\n  \"id\": \"1f9d2a6b9c0d1234\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\",\n  \"cwd\": \"/work/pi\",\n  \"title\": \"optional session title\",\n  \"parentSession\": \"optional lineage marker\"\n}\n```\n\nObservações:\n\n- `version` é opcional em arquivos v1; ausência significa v1.\n- `parentSession` é uma string opaca de linhagem. O código atual escreve um id de sessão ou um caminho de sessão dependendo do fluxo (`fork`, `forkFrom`, `createBranchedSession`, ou `newSession({ parentSession })` explícito). Trate como metadado, não como uma chave estrangeira tipada.\n\n### Base de Entrada (`SessionEntryBase`)\n\nTodas as entradas não-cabeçalho incluem:\n\n```json\n{\n  \"type\": \"...\",\n  \"id\": \"8-char-id\",\n  \"parentId\": \"previous-or-branch-parent\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\"\n}\n```\n\n`parentId` pode ser `null` para uma entrada raiz (primeiro append, ou após `resetLeaf()`).\n\n## Taxonomia de Entradas\n\n`SessionEntry` é a união de:\n\n- `message`\n- `thinking_level_change`\n- `model_change`\n- `compaction`\n- `branch_summary`\n- `custom`\n- `custom_message`\n- `label`\n- `ttsr_injection`\n- `session_init`\n- `mode_change`\n\n### `message`\n\nArmazena um `AgentMessage` diretamente.\n\n```json\n{\n  \"type\": \"message\",\n  \"id\": \"a1b2c3d4\",\n  \"parentId\": null,\n  \"timestamp\": \"2026-02-16T10:21:00.000Z\",\n  \"message\": {\n    \"role\": \"assistant\",\n    \"provider\": \"anthropic\",\n    \"model\": \"claude-sonnet-4-5\",\n    \"content\": [{ \"type\": \"text\", \"text\": \"Done.\" }],\n    \"usage\": { \"input\": 100, \"output\": 20, \"cacheRead\": 0, \"cacheWrite\": 0, \"cost\": { \"input\": 0, \"output\": 0, \"cacheRead\": 0, \"cacheWrite\": 0, \"total\": 0 } },\n    \"timestamp\": 1760000000000\n  }\n}\n```\n\n### `model_change`\n\n```json\n{\n  \"type\": \"model_change\",\n  \"id\": \"b1c2d3e4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:21:30.000Z\",\n  \"model\": \"openai/gpt-4o\",\n  \"role\": \"default\"\n}\n```\n\n`role` é opcional; ausência é tratada como `default` na reconstrução de contexto.\n\n### `thinking_level_change`\n\n```json\n{\n  \"type\": \"thinking_level_change\",\n  \"id\": \"c1d2e3f4\",\n  \"parentId\": \"b1c2d3e4\",\n  \"timestamp\": \"2026-02-16T10:22:00.000Z\",\n  \"thinkingLevel\": \"high\"\n}\n```\n\n### `compaction`\n\n```json\n{\n  \"type\": \"compaction\",\n  \"id\": \"d1e2f3a4\",\n  \"parentId\": \"c1d2e3f4\",\n  \"timestamp\": \"2026-02-16T10:23:00.000Z\",\n  \"summary\": \"Conversation summary\",\n  \"shortSummary\": \"Short recap\",\n  \"firstKeptEntryId\": \"a1b2c3d4\",\n  \"tokensBefore\": 42000,\n  \"details\": { \"readFiles\": [\"src/a.ts\"] },\n  \"preserveData\": { \"hookState\": true },\n  \"fromExtension\": false\n}\n```\n\n### `branch_summary`\n\n```json\n{\n  \"type\": \"branch_summary\",\n  \"id\": \"e1f2a3b4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:24:00.000Z\",\n  \"fromId\": \"a1b2c3d4\",\n  \"summary\": \"Summary of abandoned path\",\n  \"details\": { \"note\": \"optional\" },\n  \"fromExtension\": true\n}\n```\n\nSe ramificando a partir da raiz (`branchFromId === null`), `fromId` é a string literal `\"root\"`.\n\n### `custom`\n\nPersistência de estado de extensão; ignorada por `buildSessionContext`.\n\n```json\n{\n  \"type\": \"custom\",\n  \"id\": \"f1a2b3c4\",\n  \"parentId\": \"e1f2a3b4\",\n  \"timestamp\": \"2026-02-16T10:25:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"data\": { \"state\": 1 }\n}\n```\n\n### `custom_message`\n\nMensagem fornecida por extensão que participa do contexto do LLM.\n\n```json\n{\n  \"type\": \"custom_message\",\n  \"id\": \"a2b3c4d5\",\n  \"parentId\": \"f1a2b3c4\",\n  \"timestamp\": \"2026-02-16T10:26:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"content\": \"Injected context\",\n  \"display\": true,\n  \"details\": { \"debug\": false }\n}\n```\n\n### `label`\n\n```json\n{\n  \"type\": \"label\",\n  \"id\": \"b2c3d4e5\",\n  \"parentId\": \"a2b3c4d5\",\n  \"timestamp\": \"2026-02-16T10:27:00.000Z\",\n  \"targetId\": \"a1b2c3d4\",\n  \"label\": \"checkpoint\"\n}\n```\n\n`label: undefined` limpa um rótulo para o `targetId`.\n\n### `ttsr_injection`\n\n```json\n{\n  \"type\": \"ttsr_injection\",\n  \"id\": \"c2d3e4f5\",\n  \"parentId\": \"b2c3d4e5\",\n  \"timestamp\": \"2026-02-16T10:28:00.000Z\",\n  \"injectedRules\": [\"ruleA\", \"ruleB\"]\n}\n```\n\n### `session_init`\n\n```json\n{\n  \"type\": \"session_init\",\n  \"id\": \"d2e3f4a5\",\n  \"parentId\": \"c2d3e4f5\",\n  \"timestamp\": \"2026-02-16T10:29:00.000Z\",\n  \"systemPrompt\": \"...\",\n  \"task\": \"...\",\n  \"tools\": [\"read\", \"edit\"],\n  \"outputSchema\": { \"type\": \"object\" }\n}\n```\n\n### `mode_change`\n\n```json\n{\n  \"type\": \"mode_change\",\n  \"id\": \"e2f3a4b5\",\n  \"parentId\": \"d2e3f4a5\",\n  \"timestamp\": \"2026-02-16T10:30:00.000Z\",\n  \"mode\": \"plan\",\n  \"data\": { \"planFile\": \"/tmp/plan.md\" }\n}\n```\n\n## Versionamento e Migração\n\nVersão atual da sessão: `3`.\n\n### v1 -> v2\n\nAplicada quando o `version` do cabeçalho está ausente ou `< 2`:\n\n- Adiciona `id` e `parentId` a cada entrada não-cabeçalho.\n- Reconstrói uma cadeia linear de parentesco usando a ordem do arquivo.\n- Migra o campo de compactação `firstKeptEntryIndex` -> `firstKeptEntryId` quando presente.\n- Define o `version = 2` do cabeçalho.\n\n### v2 -> v3\n\nAplicada quando o `version` do cabeçalho `< 3`:\n\n- Para entradas `message`: reescreve o legado `message.role === \"hookMessage\"` para `\"custom\"`.\n- Define o `version = 3` do cabeçalho.\n\n### Gatilho de Migração e Persistência\n\n- As migrações são executadas durante o carregamento da sessão (`setSessionFile`).\n- Se alguma migração foi executada, o arquivo inteiro é reescrito em disco imediatamente.\n- A migração muta as entradas em memória primeiro e depois persiste o JSONL reescrito.\n\n## Comportamento de Carregamento e Compatibilidade\n\nComportamento de `loadEntriesFromFile(path)`:\n\n- Arquivo ausente (`ENOENT`) -> retorna `[]`.\n- Linhas não parseáveis são tratadas pelo parser JSONL leniente (`parseJsonlLenient`).\n- Se a primeira entrada parseada não for um cabeçalho de sessão válido (`type !== \"session\"` ou `id` string ausente) -> retorna `[]`.\n\nComportamento de `SessionManager.setSessionFile()`:\n\n- `[]` do carregador é tratado como sessão vazia/inexistente e substituído por um novo arquivo de sessão inicializado naquele caminho.\n- Arquivos válidos são carregados, migrados se necessário, referências de blob resolvidas e então indexados.\n\n## Semântica de Árvore e Folha\n\nO modelo subjacente é árvore append-only + ponteiro de folha mutável:\n\n- Todo método de append cria exatamente uma nova entrada cujo `parentId` é o `leafId` atual.\n- A nova entrada se torna o novo `leafId`.\n- `branch(entryId)` move apenas o `leafId`; entradas existentes permanecem inalteradas.\n- `resetLeaf()` define `leafId = null`; o próximo append cria uma nova entrada raiz (`parentId: null`).\n- `branchWithSummary()` define a folha para o alvo do branch e faz append de uma entrada `branch_summary`.\n\n`getEntries()` retorna todas as entradas não-cabeçalho na ordem de inserção. Entradas existentes não são deletadas em operação normal; reescritas preservam o histórico lógico enquanto atualizam a representação (migrações, movimentação, helpers de reescrita direcionada).\n\n## Reconstrução de Contexto (`buildSessionContext`)\n\n`buildSessionContext(entries, leafId, byId?)` resolve o que é enviado ao modelo.\n\nAlgoritmo:\n\n1. Determinar a folha:\n   - `leafId === null` -> retorna contexto vazio.\n   - `leafId` explícito -> usa aquela entrada se encontrada.\n   - caso contrário, fallback para a última entrada.\n2. Percorrer a cadeia `parentId` da folha até a raiz e inverter para o caminho raiz->folha.\n3. Derivar o estado de runtime ao longo do caminho:\n   - `thinkingLevel` do `thinking_level_change` mais recente (padrão `\"off\"`)\n   - mapa de modelos a partir das entradas `model_change` (`role ?? \"default\"`)\n   - fallback `models.default` do provider/model da mensagem do assistente se não houver mudança explícita de modelo\n   - `injectedTtsrRules` deduplicadas de todas as entradas `ttsr_injection`\n   - mode/modeData do `mode_change` mais recente (modo padrão `\"none\"`)\n4. Construir lista de mensagens:\n   - Entradas `message` passam diretamente\n   - Entradas `custom_message` se tornam `AgentMessages` `custom` via `createCustomMessage`\n   - Entradas `branch_summary` se tornam `AgentMessages` `branchSummary` via `createBranchSummaryMessage`\n   - Se uma `compaction` existir no caminho:\n     - emite o resumo da compactação primeiro (`createCompactionSummaryMessage`)\n     - emite entradas do caminho começando em `firstKeptEntryId` até o limite da compactação\n     - emite entradas após o limite da compactação\n\nEntradas `custom` e `session_init` não injetam contexto de modelo diretamente.\n\n## Garantias de Persistência e Modelo de Falhas\n\n### Persistir vs em memória\n\n- `SessionManager.create/open/continueRecent/forkFrom` -> modo persistente (`persist = true`).\n- `SessionManager.inMemory` -> modo não persistente (`persist = false`) com `MemorySessionStorage`.\n\n### Pipeline de escrita\n\nAs escritas são serializadas através de uma cadeia interna de promises (`#persistChain`) e `NdjsonFileWriter`.\n\n- `append*` atualiza o estado em memória imediatamente.\n- A persistência é adiada até que pelo menos uma mensagem do assistente exista.\n  - Antes do primeiro assistente: entradas são retidas em memória; nenhum append em arquivo ocorre.\n  - Quando o primeiro assistente existe: toda a sessão em memória é descarregada no arquivo.\n  - Depois disso: novas entradas são acrescentadas incrementalmente.\n\nJustificativa no código: evitar persistir sessões que nunca produziram uma resposta do assistente.\n\n### Operações de durabilidade\n\n- `flush()` descarrega o writer e chama `fsync()`.\n- Reescritas completas atômicas (`#rewriteFile`) escrevem em arquivo temporário, flush+fsync, close e então renomeiam sobre o alvo.\n- Usadas para migrações, `setSessionName`, `rewriteEntries`, operações de movimentação e reescritas de argumentos de tool-call.\n\n### Comportamento em erros\n\n- Erros de persistência são registrados (`#persistError`) e relançados em operações subsequentes.\n- O primeiro erro é logado uma vez com o contexto do arquivo de sessão.\n- O close do writer é best-effort, mas propaga o primeiro erro significativo.\n\n## Controles de Tamanho de Dados e Externalização de Blobs\n\nAntes de persistir entradas:\n\n- Strings grandes são truncadas para `MAX_PERSIST_CHARS` (500.000 caracteres) com aviso:\n  - `\"[Session persistence truncated large content]\"`\n- Campos transientes `partialJson` e `jsonlEvents` são removidos.\n- Se o objeto possui tanto `content` quanto `lineCount`, a contagem de linhas é recalculada após o truncamento.\n- Blocos de imagem em arrays `content` com comprimento base64 >= 1024 são externalizados para referências de blob:\n  - armazenados como `blob:sha256:<hash>`\n  - bytes brutos escritos no armazenamento de blobs (`BlobStore.put`)\n\nAo carregar, referências de blob são resolvidas de volta para base64 para blocos de imagem de message/custom_message.\n\n## Abstrações de Armazenamento\n\nA interface `SessionStorage` fornece todas as operações de sistema de arquivos usadas por `SessionManager`:\n\n- síncronas: `ensureDirSync`, `existsSync`, `writeTextSync`, `statSync`, `listFilesSync`\n- assíncronas: `exists`, `readText`, `readTextPrefix`, `writeText`, `rename`, `unlink`, `openWriter`\n\nImplementações:\n\n- `FileSessionStorage`: sistema de arquivos real (Bun + node fs)\n- `MemorySessionStorage`: implementação em memória baseada em map para testes/sessões não persistentes\n\n`SessionStorageWriter` expõe `writeLine`, `flush`, `fsync`, `close`, `getError`.\n\n## Utilitários de Descoberta de Sessões\n\nDefinidos em `session-manager.ts`:\n\n- `getRecentSessions(sessionDir, limit)` -> metadados leves para UI/seletor de sessão\n- `findMostRecentSession(sessionDir)` -> mais recente por mtime\n- `list(cwd, sessionDir?)` -> sessões em um escopo de projeto\n- `listAll()` -> sessões em todos os escopos de projeto sob `~/.xcsh/agent/sessions`\n\nA extração de metadados lê apenas um prefixo (`readTextPrefix(..., 4096)`) quando possível.\n\n## Relacionado mas Distinto: Armazenamento de Histórico de Prompts\n\n`HistoryStorage` (`history-storage.ts`) é um subsistema SQLite separado para recall/busca de prompts, não para replay de sessão.\n\n- Banco de dados: `~/.xcsh/agent/history.db`\n- Tabela: `history(id, prompt, created_at, cwd)`\n- Índice FTS5: `history_fts` com sincronização mantida por trigger\n- Deduplica prompts idênticos consecutivos usando cache em memória do último prompt\n- Inserção assíncrona (`setImmediate`) para que a captura de prompt não bloqueie a execução do turno\n\nUse arquivos de sessão para replay de grafo/estado de conversa; use `HistoryStorage` para a UX de histórico de prompts.\n",
	"pt-br/sessions/ttsr-injection-lifecycle.md": "---\ntitle: Ciclo de Vida da Injeção TTSR\ndescription: >-\n  Ciclo de vida da injeção TTSR (tool-use, tool-result, system-reminder) para\n  gerenciamento de contexto.\nsidebar:\n  order: 9\n  label: Injeção TTSR\ni18n:\n  sourceHash: d6179a286584\n  translator: machine\n---\n\n# Ciclo de Vida da Injeção TTSR\n\nEste documento abrange o caminho de execução atual do Time Traveling Stream Rules (TTSR), desde a descoberta de regras até a interrupção de stream, injeção de retry, notificações de extensões e gerenciamento de estado de sessão.\n\n## Arquivos de implementação\n\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/export/ttsr.ts`](../../packages/coding-agent/src/export/ttsr.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/prompts/system/ttsr-interrupt.md`](../../packages/coding-agent/src/prompts/system/ttsr-interrupt.md)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/types.ts`](../../packages/coding-agent/src/extensibility/extensions/types.ts)\n- [`../src/extensibility/hooks/types.ts`](../../packages/coding-agent/src/extensibility/hooks/types.ts)\n- [`../src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n\n## 1. Feed de descoberta e registro de regras\n\nNa criação da sessão, `createAgentSession()` carrega todas as regras descobertas e constrói um `TtsrManager`:\n\n```ts\nconst ttsrSettings = settings.getGroup(\"ttsr\");\nconst ttsrManager = new TtsrManager(ttsrSettings);\nconst rulesResult = await loadCapability<Rule>(ruleCapability.id, { cwd });\nfor (const rule of rulesResult.items) {\n  if (rule.ttsrTrigger) ttsrManager.addRule(rule);\n}\n```\n\n### Comportamento de deduplicação pré-registro\n\n`loadCapability(\"rules\")` deduplica por `rule.name` com semântica de primeira ocorrência vence (prioridade maior do provedor primeiro). Duplicatas sombreadas são removidas antes do registro TTSR.\n\n### Comportamento de `TtsrManager.addRule()`\n\nO registro é ignorado quando:\n\n- `rule.ttsrTrigger` está ausente\n- uma regra com o mesmo `rule.name` já foi registrada neste gerenciador\n- a regex falha ao compilar (`new RegExp(rule.ttsrTrigger)` lança exceção)\n\nTriggers com regex inválida são registrados como warnings e ignorados; a inicialização da sessão continua.\n\n### Ressalva sobre configuração\n\n`TtsrSettings.enabled` é carregado no gerenciador, mas atualmente não é verificado no controle de execução em tempo de runtime. Se existirem regras, a verificação de correspondência ainda será executada.\n\n## 2. Ciclo de vida do monitor de streaming\n\nA detecção TTSR é executada dentro de `AgentSession.#handleAgentEvent`.\n\n### Início do turno\n\nNo `turn_start`, o buffer de stream é resetado:\n\n- `ttsrManager.resetBuffer()`\n\n### Durante o stream (`message_update`)\n\nQuando atualizações do assistente chegam e existem regras:\n\n- monitora `text_delta` e `toolcall_delta`\n- anexa o delta ao buffer do gerenciador\n- chama `check(buffer)`\n\n`check()` itera as regras registradas e retorna todas as regras correspondentes que passam pela política de repetição (`#canTrigger`).\n\n## 3. Decisão de trigger e caminho de abort imediato\n\nQuando uma ou mais regras correspondem:\n\n1. `markInjected(matches)` registra os nomes das regras no estado de injeção do gerenciador.\n2. as regras correspondentes são enfileiradas em `#pendingTtsrInjections`.\n3. `#ttsrAbortPending = true`.\n4. `agent.abort()` é chamado imediatamente.\n5. o evento `ttsr_triggered` é emitido de forma assíncrona (fire-and-forget).\n6. o trabalho de retry é agendado via `setTimeout(..., 50)`.\n\nO abort não é bloqueado por callbacks de extensões.\n\n## 4. Agendamento de retry, modo de contexto e injeção de lembrete\n\nApós o timeout de 50ms:\n\n1. `#ttsrAbortPending = false`\n2. lê `ttsrManager.getSettings().contextMode`\n3. se `contextMode === \"discard\"`, descarta a saída parcial do assistente com `agent.popMessage()`\n4. constrói o conteúdo de injeção a partir das regras pendentes usando o template `ttsr-interrupt.md`\n5. anexa uma mensagem de usuário sintética contendo um bloco `<system-interrupt ...>` por regra\n6. chama `agent.continue()` para tentar novamente a geração\n\nO payload do template é:\n\n```xml\n<system-interrupt reason=\"rule_violation\" rule=\"{{name}}\" path=\"{{path}}\">\n...\n{{content}}\n</system-interrupt>\n```\n\nAs injeções pendentes são limpas após a geração do conteúdo.\n\n### Comportamento do `contextMode` na saída parcial\n\n- `discard`: a mensagem parcial/abortada do assistente é removida antes do retry.\n- `keep`: a saída parcial do assistente permanece no estado da conversa; o lembrete é anexado após ela.\n\n## 5. Política de repetição e lógica de intervalo\n\n`TtsrManager` rastreia `#messageCount` e `lastInjectedAt` por regra.\n\n### `repeatMode: \"once\"`\n\nUma regra pode ser acionada apenas uma vez após ter um registro de injeção.\n\n### `repeatMode: \"after-gap\"`\n\nUma regra pode ser reacionada apenas quando:\n\n- `messageCount - lastInjectedAt >= repeatGap`\n\n`messageCount` é incrementado no `turn_end`, então o intervalo é medido em turnos completos, não em chunks de stream.\n\n## 6. Emissão de eventos e superfícies de extensão/hook\n\n### Evento de sessão\n\n`AgentSessionEvent` inclui:\n\n```ts\n{ type: \"ttsr_triggered\"; rules: Rule[] }\n```\n\n### Runner de extensão\n\n`#emitSessionEvent()` encaminha o evento para:\n\n- listeners de extensão (`ExtensionRunner.emit({ type: \"ttsr_triggered\", rules })`)\n- assinantes locais da sessão\n\n### Tipagem de hook e custom-tool\n\n- a API de extensão expõe `on(\"ttsr_triggered\", ...)`\n- a API de hook expõe `on(\"ttsr_triggered\", ...)`\n- custom tools recebem `onSession({ reason: \"ttsr_triggered\", rules })`\n\n### Diferença de renderização no modo interativo\n\nO modo interativo usa `session.isTtsrAbortPending` para suprimir a exibição do motivo de parada do assistente abortado como uma falha visível durante a interrupção TTSR, e renderiza um `TtsrNotificationComponent` quando o evento chega.\n\n## 7. Persistência e estado de retomada (implementação atual)\n\n`SessionManager` possui suporte completo de schema para persistência de regras injetadas:\n\n- tipo de entrada: `ttsr_injection`\n- API de anexação: `appendTtsrInjection(ruleNames)`\n- API de consulta: `getInjectedTtsrRules()`\n- a reconstrução de contexto inclui `SessionContext.injectedTtsrRules`\n\n`TtsrManager` também suporta restauração via `restoreInjected(ruleNames)`.\n\n### Status atual da integração\n\nNo caminho de execução atual:\n\n- `AgentSession` não anexa entradas `ttsr_injection` quando o TTSR é acionado.\n- `createAgentSession()` não restaura `existingSession.injectedTtsrRules` de volta no `ttsrManager`.\n\nEfeito líquido: a supressão de regras injetadas é aplicada em memória para o processo ativo, mas atualmente não é persistida/restaurada entre recarregamento/retomada de sessão por este caminho.\n\n## 8. Limites de race condition e garantias de ordenação\n\n### Abort vs callback de retry\n\n- o abort é síncrono da perspectiva do handler TTSR (`agent.abort()` é chamado imediatamente)\n- o retry é adiado por timer (`50ms`)\n- a notificação de extensão é assíncrona e intencionalmente não aguardada antes do agendamento de abort/retry\n\n### Múltiplas correspondências na mesma janela de stream\n\n`check()` retorna todas as regras elegíveis que correspondem atualmente. Elas são injetadas como um lote na próxima mensagem de retry.\n\n### Entre abort e continue\n\nDurante a janela do timer, o estado pode mudar (interrupção do usuário, ações de modo, eventos adicionais). A chamada de retry é de melhor esforço: `agent.continue().catch(() => {})` engole erros subsequentes.\n\n## 9. Resumo de casos extremos\n\n- Regex `ttsr_trigger` inválida: ignorada com warning; outras regras continuam.\n- Nomes de regras duplicados na camada de capability: duplicatas de menor prioridade são sombreadas antes do registro.\n- Nomes duplicados na camada do gerenciador: o segundo registro é ignorado.\n- `contextMode: \"keep\"`: a saída parcial violadora pode permanecer no contexto antes do retry com lembrete.\n- Repeat-after-gap depende dos incrementos de contagem de turnos no `turn_end`; chunks no meio do turno não avançam os contadores de intervalo.\n",
	"pt-br/tui/theme.md": "---\ntitle: Referência de Temas\ndescription: >-\n  Referência de temas TUI com tokens de cor, configurações de fonte e\n  personalização de temas.\nsidebar:\n  order: 3\n  label: Temas\ni18n:\n  sourceHash: 7e962a7da157\n  translator: machine\n---\n\n# Referência de Temas\n\nEste documento descreve como o sistema de temas funciona no agente de codificação atualmente: esquema, carregamento, comportamento em tempo de execução e modos de falha.\n\n## O que o sistema de temas controla\n\nO sistema de temas gerencia:\n\n- tokens de cor de primeiro plano/segundo plano utilizados em toda a TUI\n- adaptadores de estilo markdown (`getMarkdownTheme()`)\n- adaptadores de seletor/editor/lista de configurações (`getSelectListTheme()`, `getEditorTheme()`, `getSettingsListTheme()`)\n- preset de símbolos + substituições de símbolos (`unicode`, `nerd`, `ascii`)\n- cores de realce de sintaxe utilizadas pelo realçador nativo (`@f5-sales-demo/pi-natives`)\n- cores dos segmentos da linha de status\n\nImplementação principal: `src/modes/theme/theme.ts`.\n\n## Estrutura do JSON de tema\n\nOs arquivos de tema são objetos JSON validados contra o esquema em tempo de execução em `theme.ts` (`ThemeJsonSchema`) e espelhados por `src/modes/theme/theme-schema.json`.\n\nCampos de nível superior:\n\n- `name` (obrigatório)\n- `colors` (obrigatório; todos os tokens de cor são obrigatórios)\n- `vars` (opcional; variáveis de cor reutilizáveis)\n- `export` (opcional; cores para exportação HTML)\n- `symbols` (opcional)\n  - `preset` (opcional: `unicode | nerd | ascii`)\n  - `overrides` (opcional: substituições de chave/valor para `SymbolKey`)\n\nOs valores de cor aceitam:\n\n- string hexadecimal (`\"#RRGGBB\"`)\n- índice de 256 cores (`0..255`)\n- string de referência a variável (resolvida através de `vars`)\n- string vazia (`\"\"`) significando padrão do terminal (`\\x1b[39m` fg, `\\x1b[49m` bg)\n\n## Tokens de cor obrigatórios (atual)\n\nTodos os tokens abaixo são obrigatórios em `colors`.\n\n### Texto principal e bordas (11)\n\n`accent`, `border`, `borderAccent`, `borderMuted`, `success`, `error`, `warning`, `muted`, `dim`, `text`, `thinkingText`\n\n### Blocos de fundo (7)\n\n`selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`, `statusLineBg`\n\n### Texto de mensagem/ferramenta (5)\n\n`userMessageText`, `customMessageText`, `customMessageLabel`, `toolTitle`, `toolOutput`\n\n### Markdown (10)\n\n`mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet`\n\n### Diff de ferramenta + realce de sintaxe (12)\n\n`toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext`,\n`syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation`\n\n### Bordas de modo/pensamento (8)\n\n`thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh`, `bashMode`, `pythonMode`\n\n### Cores dos segmentos da linha de status (14)\n\n`statusLineSep`, `statusLineModel`, `statusLinePath`, `statusLineGitClean`, `statusLineGitDirty`, `statusLineContext`, `statusLineSpend`, `statusLineStaged`, `statusLineDirty`, `statusLineUntracked`, `statusLineOutput`, `statusLineCost`, `statusLineSubagents`\n\n## Tokens opcionais\n\n### Seção `export` (opcional)\n\nUtilizada para auxiliares de temas na exportação HTML:\n\n- `export.pageBg`\n- `export.cardBg`\n- `export.infoBg`\n\nSe omitida, o código de exportação deriva os valores padrão a partir das cores do tema resolvidas.\n\n### Seção `symbols` (opcional)\n\n- `symbols.preset` define um conjunto de símbolos padrão no nível do tema.\n- `symbols.overrides` pode substituir valores individuais de `SymbolKey`.\n\nPrecedência em tempo de execução:\n\n1. substituição `symbolPreset` nas configurações (se definida)\n2. `symbols.preset` no JSON do tema\n3. fallback `\"unicode\"`\n\nChaves de substituição inválidas são ignoradas e registradas em log (`logger.debug`).\n\n## Fontes de temas integrados vs. personalizados\n\nOrdem de busca de temas (`loadThemeJson`):\n\n1. temas integrados embutidos (`defaults/xcsh-dark.json` e `defaults/xcsh-light.json` compilados em `defaultThemes`)\n2. arquivo de tema personalizado: `<customThemesDir>/<name>.json`\n\nO diretório de temas personalizados vem de `getCustomThemesDir()`:\n\n- padrão: `~/.xcsh/agent/themes`\n- substituído por `PI_CODING_AGENT_DIR` (`$PI_CODING_AGENT_DIR/themes`)\n\n`getAvailableThemes()` retorna os nomes integrados + personalizados mesclados, ordenados, com os integrados tendo precedência em caso de colisão de nomes.\n\n## Carregamento, validação e resolução\n\nPara arquivos de tema personalizados:\n\n1. leitura do JSON\n2. análise do JSON\n3. validação contra `ThemeJsonSchema`\n4. resolução recursiva de referências em `vars`\n5. conversão dos valores resolvidos para ANSI de acordo com o modo de capacidade do terminal\n\nComportamento de validação:\n\n- tokens de cor obrigatórios ausentes: mensagem de erro explícita agrupada\n- tipos/valores de token incorretos: erros de validação com caminho JSON\n- arquivo de tema desconhecido: `Theme not found: <name>`\n\nComportamento de referência a variáveis:\n\n- suporta referências aninhadas\n- lança exceção em referência a variável inexistente\n- lança exceção em referências circulares\n\n## Comportamento do modo de cores do terminal\n\nDetecção do modo de cores (`detectColorMode`):\n\n- `COLORTERM=truecolor|24bit` => truecolor\n- `WT_SESSION` => truecolor\n- `TERM` em `dumb`, `linux`, ou vazio => 256color\n- caso contrário => truecolor\n\nComportamento de conversão:\n\n- hex -> `Bun.color(..., \"ansi-16m\" | \"ansi-256\")`\n- numérico -> ANSI `38;5` / `48;5`\n- `\"\"` -> redefinição padrão de fg/bg\n\n## Comportamento de alternância em tempo de execução\n\n### Tema inicial (`initTheme`)\n\n`main.ts` inicializa o tema com as configurações:\n\n- `symbolPreset`\n- `colorBlindMode`\n- `theme.dark`\n- `theme.light`\n\nA seleção automática do slot de tema utiliza a detecção de fundo por `COLORFGBG`:\n\n- analisa o índice de fundo a partir de `COLORFGBG`\n- `< 8` => slot escuro (`theme.dark`)\n- `>= 8` => slot claro (`theme.light`)\n- falha na análise => slot escuro\n\nPadrões atuais do esquema de configurações:\n\n- `theme.dark = \"xcsh-dark\"`\n- `theme.light = \"xcsh-light\"`\n- `symbolPreset = \"unicode\"`\n- `colorBlindMode = false`\n\n### Alternância explícita (`setTheme`)\n\n- carrega o tema selecionado\n- atualiza o singleton global `theme`\n- opcionalmente inicia o observador de arquivos\n- aciona o callback `onThemeChange`\n\nEm caso de falha:\n\n- reverte para o tema integrado `dark`\n- retorna `{ success: false, error }`\n\n### Alternância de pré-visualização (`previewTheme`)\n\n- aplica o tema de pré-visualização temporário ao `theme` global\n- **não** altera as configurações persistidas por si só\n- retorna sucesso/erro sem substituição por fallback\n\nA interface de configurações utiliza isso para pré-visualização ao vivo e restaura o tema anterior ao cancelar.\n\n## Observadores e recarga ao vivo\n\nQuando o observador está habilitado (`setTheme(..., true)` / inicialização interativa):\n\n- observa apenas o caminho de arquivo personalizado `<customThemesDir>/<currentTheme>.json`\n- os temas integrados efetivamente não são observados\n- evento `change` no arquivo: tenta recarregar (com debounce)\n- evento `rename`/exclusão no arquivo: reverte para `dark`, fecha o observador\n\nO modo automático também instala um listener para `SIGWINCH` e pode reavaliar o mapeamento de slots escuro/claro quando o estado do terminal muda.\n\n## Comportamento do modo para daltonismo\n\n`colorBlindMode` altera apenas um token em tempo de execução:\n\n- `toolDiffAdded` é ajustado em HSV (verde deslocado em direção ao azul)\n- o ajuste é aplicado somente quando o valor resolvido é uma string hexadecimal\n\nOs demais tokens permanecem inalterados.\n\n## Onde as configurações de tema são persistidas\n\nAs configurações relacionadas ao tema são persistidas pelo `Settings` no YAML de configuração global:\n\n- caminho: `<agentDir>/config.yml`\n- diretório padrão do agente: `~/.xcsh/agent`\n- arquivo padrão efetivo: `~/.xcsh/agent/config.yml`\n\nChaves persistidas:\n\n- `theme.dark`\n- `theme.light`\n- `symbolPreset`\n- `colorBlindMode`\n\nExiste migração legada: o antigo `theme: \"name\"` simples é migrado para `theme.dark` ou `theme.light` aninhado com base na detecção de luminância.\n\n## Criando um tema personalizado (prático)\n\n1. Crie o arquivo no diretório de temas personalizados, por exemplo, `~/.xcsh/agent/themes/my-theme.json`.\n2. Inclua `name`, `vars` opcional e **todos** os tokens obrigatórios em `colors`.\n3. Opcionalmente, inclua `symbols` e `export`.\n4. Selecione o tema em Configurações (`Display -> Dark theme` ou `Display -> Light theme`) dependendo de qual slot automático você deseja.\n\nEsqueleto mínimo. Cada chave em `colors` é obrigatória — o validador em tempo de execução\n(`additionalProperties: false`) rejeita tanto chaves ausentes quanto chaves desconhecidas.\nPara as implementações de referência fornecidas, consulte\n[`packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json)\ne [`xcsh-light.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-light.json).\n\nA linha de status possui dois sistemas de cor paralelos documentados na issue #242:\n\n- Cores de texto em hexadecimal (`statusLinePath`, `statusLineGitClean`, `statusLineGitDirty`,\n  `statusLineStaged`, `statusLineDirty`, `statusLineUntracked`) controlam a renderização sem powerline.\n- Índices de paleta de 256 cores (`statusLine<Segment>Bg` / `statusLine<Segment>Fg`)\n  controlam o preenchimento dos segmentos powerline. Eles são independentes das chaves hexadecimais acima —\n  ambos devem ser definidos.\n\n```json\n{\n  \"name\": \"my-theme\",\n  \"vars\": {\n    \"accent\": \"#7aa2f7\",\n    \"muted\": 244\n  },\n  \"colors\": {\n    \"accent\": \"accent\",\n    \"chromeAccent\": \"accent\",\n    \"spinnerAccent\": \"accent\",\n    \"contentAccent\": \"muted\",\n    \"border\": \"#4c566a\",\n    \"borderAccent\": \"accent\",\n    \"borderMuted\": \"muted\",\n    \"success\": \"#9ece6a\",\n    \"error\": \"#f7768e\",\n    \"warning\": \"#e0af68\",\n    \"muted\": \"muted\",\n    \"dim\": 240,\n    \"gutterSuccess\": \"#7dcfff\",\n    \"gutterWarning\": \"#e0af68\",\n    \"text\": \"\",\n    \"thinkingText\": \"muted\",\n\n    \"selectedBg\": \"#2a2f45\",\n    \"userMessageBg\": \"#1f2335\",\n    \"userMessageText\": \"\",\n    \"customMessageBg\": \"#24283b\",\n    \"customMessageText\": \"\",\n    \"customMessageLabel\": \"accent\",\n    \"toolPendingBg\": \"#1f2335\",\n    \"toolSuccessBg\": \"#1f2d2a\",\n    \"toolErrorBg\": \"#2d1f2a\",\n    \"toolTitle\": \"\",\n    \"toolOutput\": \"muted\",\n\n    \"mdHeading\": \"accent\",\n    \"mdLink\": \"accent\",\n    \"mdLinkUrl\": \"muted\",\n    \"mdCode\": \"#c0caf5\",\n    \"mdCodeBlock\": \"#c0caf5\",\n    \"mdCodeBlockBorder\": \"muted\",\n    \"mdQuote\": \"muted\",\n    \"mdQuoteBorder\": \"muted\",\n    \"mdHr\": \"muted\",\n    \"mdListBullet\": \"accent\",\n\n    \"toolDiffAdded\": \"#9ece6a\",\n    \"toolDiffRemoved\": \"#f7768e\",\n    \"toolDiffContext\": \"muted\",\n\n    \"syntaxComment\": \"#565f89\",\n    \"syntaxKeyword\": \"#bb9af7\",\n    \"syntaxFunction\": \"#7aa2f7\",\n    \"syntaxVariable\": \"#c0caf5\",\n    \"syntaxString\": \"#9ece6a\",\n    \"syntaxNumber\": \"#ff9e64\",\n    \"syntaxType\": \"#2ac3de\",\n    \"syntaxOperator\": \"#89ddff\",\n    \"syntaxPunctuation\": \"#9aa5ce\",\n    \"syntaxControl\": \"#bb9af7\",\n\n    \"thinkingOff\": 240,\n    \"thinkingMinimal\": 244,\n    \"thinkingLow\": \"#7aa2f7\",\n    \"thinkingMedium\": \"#2ac3de\",\n    \"thinkingHigh\": \"#bb9af7\",\n    \"thinkingXhigh\": \"#f7768e\",\n\n    \"bashMode\": \"#2ac3de\",\n    \"pythonMode\": \"#bb9af7\",\n\n    \"statusLineBg\": \"#16161e\",\n    \"statusLineSep\": 240,\n    \"statusLineModel\": \"#bb9af7\",\n    \"statusLinePath\": \"#7aa2f7\",\n    \"statusLineGitClean\": \"#9ece6a\",\n    \"statusLineGitDirty\": \"#e0af68\",\n    \"statusLineContext\": \"#2ac3de\",\n    \"statusLineSpend\": \"#7dcfff\",\n    \"statusLineStaged\": \"#9ece6a\",\n    \"statusLineDirty\": \"#e0af68\",\n    \"statusLineUntracked\": \"#f7768e\",\n    \"statusLineOutput\": \"#c0caf5\",\n    \"statusLineCost\": \"#ff9e64\",\n    \"statusLineSubagents\": \"#bb9af7\",\n\n    \"statusLineOsIconBg\": 7,\n    \"statusLineOsIconFg\": 232,\n    \"statusLinePathBg\": 4,\n    \"statusLinePathFg\": 254,\n    \"statusLineGitCleanBg\": 2,\n    \"statusLineGitCleanFg\": 0,\n    \"statusLineGitDirtyBg\": 3,\n    \"statusLineGitDirtyFg\": 0,\n    \"statusLineGitStagedBg\": 64,\n    \"statusLineGitStagedFg\": 0,\n    \"statusLineGitUntrackedBg\": 39,\n    \"statusLineGitUntrackedFg\": 0,\n    \"statusLineGitConflictBg\": 1,\n    \"statusLineGitConflictFg\": 7,\n    \"statusLinePlanModeBg\": 236,\n    \"statusLinePlanModeFg\": 117,\n    \"statusLineProfileXcshBg\": \"accent\",\n    \"statusLineProfileXcshFg\": 231\n  }\n}\n```\n\n## Testando temas personalizados\n\nUtilize este fluxo de trabalho:\n\n1. Inicie o modo interativo (observador habilitado desde a inicialização).\n2. Abra as configurações e pré-visualize os valores do tema (`previewTheme` ao vivo).\n3. Para arquivos de tema personalizados, edite o JSON durante a execução e confirme o recarregamento automático ao salvar.\n4. Teste as superfícies críticas:\n   - renderização de markdown\n   - blocos de ferramentas (pendente/sucesso/erro)\n   - renderização de diff (adicionado/removido/contexto)\n   - legibilidade da linha de status\n   - mudanças nas bordas do nível de pensamento\n   - cores de borda nos modos bash/python\n5. Valide ambos os presets de símbolos se o seu tema depender da largura/aparência dos glifos.\n\n## Restrições e ressalvas reais\n\n- Todos os tokens de `colors` são obrigatórios para temas personalizados.\n- `export` e `symbols` são opcionais.\n- `$schema` no JSON do tema é informativo; a validação em tempo de execução é aplicada pelo esquema TypeBox compilado no código.\n- A falha de `setTheme` reverte para `dark`; a falha de `previewTheme` não substitui o tema atual.\n- Erros de recarga do observador de arquivos mantêm o tema atualmente carregado até que um recarregamento bem-sucedido ocorra ou o caminho de fallback seja acionado.\n",
	"pt-br/tui/tree.md": "---\ntitle: Referência do Comando Tree\ndescription: >-\n  Referência do comando /tree para visualização do histórico de sessão e\n  ramificações de conversa.\nsidebar:\n  order: 4\n  label: Comando /tree\ni18n:\n  sourceHash: ee0e412fe993\n  translator: machine\n---\n\n# Referência do Comando `/tree`\n\n`/tree` abre o navegador interativo **Session Tree**. Ele permite que você salte para qualquer entrada no arquivo de sessão atual e continue a partir daquele ponto.\n\nEsta é uma movimentação de folha dentro do arquivo, não uma exportação de nova sessão.\n\n## O que `/tree` faz\n\n- Constrói uma árvore a partir das entradas da sessão atual (`SessionManager.getTree()`)\n- Abre `TreeSelectorComponent` com navegação por teclado, filtros e busca\n- Na seleção, chama `AgentSession.navigateTree(targetId, { summarize, customInstructions })`\n- Reconstrói o chat visível a partir do novo caminho da folha\n- Opcionalmente preenche o editor com texto ao selecionar uma mensagem de usuário/personalizada\n\nImplementação principal:\n\n- `src/modes/controllers/input-controller.ts` (`/tree`, mapeamento de teclas de atalho, comportamento de duplo escape)\n- `src/modes/controllers/selector-controller.ts` (inicialização da UI de árvore + fluxo de prompt de resumo)\n- `src/modes/components/tree-selector.ts` (navegação, filtros, busca, rótulos, renderização)\n- `src/session/agent-session.ts` (`navigateTree` troca de folha + resumo opcional)\n- `src/session/session-manager.ts` (`getTree`, `branch`, `branchWithSummary`, `resetLeaf`, persistência de rótulos)\n\n## Como abrir\n\nQualquer uma das seguintes opções abre o mesmo seletor:\n\n- `/tree`\n- ação de tecla de atalho configurada `tree`\n- duplo escape no editor vazio quando `doubleEscapeAction = \"tree\"` (padrão)\n- `/branch` quando `doubleEscapeAction = \"tree\"` (direciona para o seletor de árvore em vez do seletor de ramificação apenas de usuário)\n\n## Modelo de UI da árvore\n\nA árvore é renderizada a partir dos ponteiros de entrada pai da sessão (`id` / `parentId`).\n\n- Os filhos são ordenados por timestamp ascendente (mais antigo primeiro, mais recente abaixo)\n- A ramificação ativa (caminho da raiz até a folha atual) é marcada com um marcador\n- Rótulos (se presentes) são renderizados como `[label]` antes do texto do nó\n- Se múltiplas raízes existirem (cadeias de pai órfãs/quebradas), elas são exibidas sob uma raiz de ramificação virtual\n\n```text\nExample tree view (active path marked with •):\n\n├─ user: \"Start task\"\n│  └─ assistant: \"Plan\"\n│     ├─ • user: \"Try approach A\"\n│     │  └─ • assistant: \"A result\"\n│     │     └─ • [milestone] user: \"Continue A\"\n│     └─ user: \"Try approach B\"\n│        └─ assistant: \"B result\"\n```\n\nO seletor se recentra em torno da seleção atual e exibe até:\n\n- `max(5, floor(terminalHeight / 2))` linhas\n\n## Teclas de atalho dentro do seletor de árvore\n\n- `Up` / `Down`: mover seleção (com retorno circular)\n- `Left` / `Right`: página acima / página abaixo\n- `Enter`: selecionar nó\n- `Esc`: limpar busca se ativa; caso contrário, fechar seletor\n- `Ctrl+C`: fechar seletor\n- `Type`: adicionar à consulta de busca\n- `Backspace`: apagar caractere da busca\n- `Shift+L`: editar/limpar rótulo na entrada selecionada\n- `Ctrl+O`: alternar filtro para frente\n- `Shift+Ctrl+O`: alternar filtro para trás\n- `Alt+D/T/U/L/A`: pular diretamente para um modo de filtro específico\n\n## Filtros e semântica de busca\n\nModos de filtro (`TreeList`):\n\n1. `default`\n2. `no-tools`\n3. `user-only`\n4. `labeled-only`\n5. `all`\n\n### `default`\n\nExibe a maioria dos nós conversacionais, mas oculta tipos de entrada de controle interno:\n\n- `label`\n- `custom`\n- `model_change`\n- `thinking_level_change`\n\n### `no-tools`\n\nIgual ao `default`, mas também oculta mensagens `toolResult`.\n\n### `user-only`\n\nApenas entradas `message` onde o papel é `user`.\n\n### `labeled-only`\n\nApenas entradas que atualmente resolvem para um rótulo.\n\n### `all`\n\nTudo na árvore da sessão, incluindo entradas de controle interno/personalizadas.\n\n### Comportamento de nó assistente somente com ferramentas\n\nMensagens do assistente que contêm **apenas chamadas de ferramentas** (sem texto) são ocultadas por padrão em todas as visualizações filtradas, a menos que:\n\n- a mensagem seja de erro/abortada (`stopReason` diferente de `stop`/`toolUse`), ou\n- seja a folha atual (sempre mantida visível)\n\n### Comportamento da busca\n\n- A consulta é tokenizada por espaços\n- A correspondência não diferencia maiúsculas de minúsculas\n- Todos os tokens devem corresponder (semântica AND)\n- O texto pesquisável inclui rótulo, papel e conteúdo específico do tipo (texto da mensagem, texto de resumo de ramificação, tipo personalizado, trechos de comandos de ferramentas, etc.)\n\n## Resultados da seleção (importante)\n\n`navigateTree` calcula o novo comportamento da folha a partir do tipo de entrada selecionada:\n\n### Selecionando mensagem `user`\n\n- A nova folha se torna o `parentId` da entrada selecionada\n- Se o pai for `null` (mensagem raiz do usuário), a folha é redefinida para a raiz (`resetLeaf()`)\n- O texto da mensagem selecionada é copiado para o editor para edição/reenvio\n\n### Selecionando `custom_message`\n\n- Mesma regra de folha que mensagens de usuário (`parentId`)\n- O conteúdo de texto é extraído e copiado para o editor\n\n### Selecionando nó não-usuário (assistente/ferramenta/resumo/compactação/controle interno personalizado/etc.)\n\n- A nova folha se torna o id do nó selecionado\n- O editor não é preenchido\n\n### Selecionando a folha atual\n\n- Sem efeito; o seletor fecha com \"Already at this point\"\n\n```text\nSelection decision (simplified):\n\nselected node\n   │\n   ├─ is current leaf? ── yes ──> close selector (no-op)\n   │\n   ├─ is user/custom_message? ── yes ──> leaf := parentId (or resetLeaf for root)\n   │                                     + prefill editor text\n   │\n   └─ otherwise ──> leaf := selected node id\n                    + no editor prefill\n```\n\n## Fluxo de resumo na troca\n\nO prompt de resumo é controlado por `branchSummary.enabled` (padrão: `false`).\n\nQuando habilitado, após selecionar um nó a UI pergunta:\n\n- `No summary`\n- `Summarize`\n- `Summarize with custom prompt`\n\nDetalhes do fluxo:\n\n- Escape no prompt de resumo reabre o seletor de árvore\n- O cancelamento do prompt personalizado retorna ao loop de escolha de resumo\n- Durante a sumarização, a UI exibe um indicador de carregamento e vincula `Esc` a `abortBranchSummary()`\n- Se a sumarização for abortada, o seletor de árvore reabre e nenhuma movimentação é aplicada\n\nInternos de `navigateTree`:\n\n- Coleta entradas de ramificação abandonada da folha antiga até o ancestral comum\n- Emite `session_before_tree` (extensões podem cancelar ou injetar resumo)\n- Usa o sumarizador padrão apenas se solicitado e necessário\n- Aplica a movimentação com:\n  - `branchWithSummary(...)` quando o resumo existe\n  - `branch(newLeafId)` para movimentação não-raiz sem resumo\n  - `resetLeaf()` para movimentação à raiz sem resumo\n- Substitui a conversa do agente com o contexto de sessão reconstruído\n- Emite `session_tree`\n\nNota: se o usuário solicitar resumo mas não houver nada a resumir, a navegação prossegue sem criar uma entrada de resumo.\n\n## Rótulos\n\nEdições de rótulos na UI de árvore chamam `appendLabelChange(targetId, label)`.\n\n- rótulo não vazio define/atualiza o rótulo resolvido\n- rótulo vazio o limpa\n- rótulos são armazenados como entradas `label` de adição apenas (append-only)\n- os nós da árvore exibem o estado do rótulo resolvido, não o histórico bruto de entradas de rótulo\n\n## `/tree` vs operações adjacentes\n\n| Operação | Escopo | Resultado |\n|---|---|---|\n| `/tree` | Arquivo de sessão atual | Move a folha para o ponto selecionado (mesmo arquivo) |\n| `/branch` | Geralmente arquivo de sessão atual -> novo arquivo de sessão | Por padrão, ramifica a partir da mensagem de **usuário** selecionada em um novo arquivo de sessão; se `doubleEscapeAction = \"tree\"`, `/branch` abre a UI de navegação em árvore |\n| `/fork` | Sessão atual inteira | Duplica a sessão em um novo arquivo de sessão persistido |\n| `/resume` | Lista de sessões | Alterna para outro arquivo de sessão |\n\nDistinção principal: `/tree` é uma ferramenta de navegação/reposicionamento dentro de um arquivo de sessão. `/branch`, `/fork` e `/resume` todos alteram o contexto do arquivo de sessão.\n\n## Fluxos de trabalho do operador\n\n### Reexecutar a partir de um prompt de usuário anterior sem perder a ramificação atual\n\n1. `/tree`\n2. buscar/selecionar mensagem de usuário anterior\n3. escolher `No summary` (ou resumir se necessário)\n4. editar o texto preenchido no editor\n5. enviar\n\nEfeito: nova ramificação cresce a partir do ponto selecionado dentro do mesmo arquivo de sessão.\n\n### Deixar a ramificação atual com um marcador de contexto\n\n1. habilitar `branchSummary.enabled`\n2. `/tree` e selecionar o nó alvo\n3. escolher `Summarize` (ou prompt personalizado)\n\nEfeito: uma entrada `branch_summary` é adicionada na posição alvo antes de continuar.\n\n### Investigar entradas de controle interno ocultas\n\n1. `/tree`\n2. pressionar `Alt+A` (all)\n3. buscar por `model`, `thinking`, `custom` ou rótulos\n\nEfeito: inspecionar a linha do tempo interna completa, não apenas os nós conversacionais.\n\n### Marcar pontos de pivô para saltos posteriores\n\n1. `/tree`\n2. mover para a entrada\n3. `Shift+L` e definir rótulo\n4. depois usar `Alt+L` (`labeled-only`) para navegar rapidamente\n\nEfeito: navegação rápida entre marcos duráveis de ramificação.\n",
	"pt-br/tui/tui-runtime-internals.md": "---\ntitle: Internos do Runtime TUI\ndescription: >-\n  Internos do runtime de interface de terminal (TUI) cobrindo pipeline de\n  renderização, tratamento de entrada e gerenciamento de estado.\nsidebar:\n  order: 2\n  label: Internos do runtime\ni18n:\n  sourceHash: cc8f7dcce46a\n  translator: machine\n---\n\n# Internos do runtime TUI\n\nEste documento mapeia o caminho de runtime sem tema, desde a entrada do terminal até a saída renderizada no modo interativo. O foco está no comportamento em `packages/tui` e em sua integração a partir dos controladores de `packages/coding-agent`.\n\n## Camadas de runtime e responsabilidades\n\n- **Engine `packages/tui`**: ciclo de vida do terminal, normalização de stdin, roteamento de foco, agendamento de renderização, pintura diferencial, composição de sobreposições, posicionamento do cursor de hardware.\n- **Modo interativo `packages/coding-agent`**: constrói a árvore de componentes, vincula callbacks do editor e mapeamentos de teclas, reage a eventos do agente/sessão e traduz o estado do domínio (streaming, execução de ferramentas, retentativas, modo de plano) em componentes de UI.\n\nRegra de fronteira: o engine TUI é agnóstico a mensagens. Ele conhece apenas `Component.render(width)`, `handleInput(data)`, foco e sobreposições. A semântica do agente permanece nos controladores interativos.\n\n## Arquivos de implementação\n\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/components/custom-editor.ts`](../../packages/coding-agent/src/modes/components/custom-editor.ts)\n- [`../../tui/src/tui.ts`](../../packages/tui/src/tui.ts)\n- [`../../tui/src/terminal.ts`](../../packages/tui/src/terminal.ts)\n- [`../../tui/src/editor-component.ts`](../../packages/tui/src/editor-component.ts)\n- [`../../tui/src/stdin-buffer.ts`](../../packages/tui/src/stdin-buffer.ts)\n- [`../../tui/src/components/loader.ts`](../../packages/tui/src/components/loader.ts)\n\n## Inicialização e montagem da árvore de componentes\n\n`InteractiveMode` constrói `TUI(new ProcessTerminal(), showHardwareCursor)` e cria contêineres persistentes:\n\n- `chatContainer`\n- `pendingMessagesContainer`\n- `statusContainer`\n- `todoContainer`\n- `statusLine`\n- `editorContainer` (contém `CustomEditor`)\n\n`init()` conecta a árvore nessa ordem, foca o editor, registra manipuladores de entrada via `InputController`, inicia o TUI e solicita uma renderização forçada.\n\nUma renderização forçada (`requestRender(true)`) redefine os caches de linha anterior e o rastreamento do cursor antes de repintar.\n\n## Ciclo de vida do terminal e normalização do stdin\n\n`ProcessTerminal.start()`:\n\n1. Habilita o modo raw e o bracketed paste.\n2. Anexa o manipulador de redimensionamento.\n3. Cria um `StdinBuffer` para dividir fragmentos de escape parciais em sequências completas.\n4. Consulta o suporte ao protocolo de teclado Kitty (`CSI ? u`) e, em seguida, habilita os flags do protocolo se suportado.\n5. No Windows, tenta a habilitação de entrada VT via flags de modo `kernel32`.\n\nComportamento do `StdinBuffer`:\n\n- Armazena em buffer sequências de escape fragmentadas (CSI/OSC/DCS/APC/SS3).\n- Emite `data` apenas quando uma sequência está completa ou é descarregada por timeout.\n- Detecta bracketed paste e emite um evento `paste` com o texto colado bruto.\n\nIsso impede que fragmentos de escape parciais sejam interpretados erroneamente como pressionamentos de tecla normais.\n\n## Roteamento de entrada e modelo de foco\n\nCaminho de entrada:\n\n`stdin -> ProcessTerminal -> StdinBuffer -> TUI.#handleInput -> focusedComponent.handleInput`\n\nDetalhes do roteamento:\n\n1. O TUI executa primeiro os listeners de entrada registrados (`addInputListener`), permitindo comportamento de consumo/transformação.\n2. O TUI trata o atalho global de depuração (`shift+ctrl+d`) antes do despacho para o componente.\n3. Se o componente focado pertencer a uma sobreposição que agora está oculta/invisível, o TUI reatribui o foco para a próxima sobreposição visível ou para o foco pré-sobreposição salvo.\n4. Eventos de liberação de tecla são filtrados, a menos que o componente focado defina `wantsKeyRelease = true`.\n5. Após o despacho, o TUI agenda uma renderização.\n\n`setFocus()` também alterna `Focusable.focused`, que controla se os componentes emitem `CURSOR_MARKER` para posicionamento do cursor de hardware.\n\n## Divisão do tratamento de teclas: editor vs controlador\n\n`CustomEditor` intercepta primeiro as combinações de alta prioridade (escape, ctrl-c/d/z, ctrl-v, variantes ctrl-p, ctrl-t, alt-up, teclas personalizadas de extensão) e delega o restante ao comportamento base do `Editor` (edição de texto, histórico, autocompletar, movimentação do cursor).\n\n`InputController.setupKeyHandlers()` então vincula os callbacks do editor às ações do modo:\n\n- cancelamento / saída de modo no `Escape`\n- encerramento no `Ctrl+C` duplo ou `Ctrl+D` com editor vazio\n- suspender/retomar no `Ctrl+Z`\n- teclas de atalho para slash-command e seletor\n- alternâncias de acompanhamento/desenfileiramento e alternâncias de expansão\n\nIsso mantém a análise de teclas/mecânicas do editor em `packages/tui` e a semântica do modo nos controladores do coding-agent.\n\n## Loop de renderização e estratégia de diff\n\n`TUI.requestRender()` é debounced para uma renderização por tick usando `process.nextTick`. Múltiplas mudanças de estado no mesmo turno são coalescidas.\n\nPipeline de `#doRender()`:\n\n1. Renderiza a árvore de componentes raiz em `newLines`.\n2. Compõe as sobreposições visíveis (se houver).\n3. Extrai e remove `CURSOR_MARKER` das linhas do viewport visível.\n4. Acrescenta sufixos de reset de segmento para linhas sem imagem.\n5. Escolhe entre repintura completa ou patch diferencial:\n   - primeiro frame\n   - mudança de largura\n   - redução com `clearOnShrink` habilitado e sem sobreposições\n   - edições acima do viewport anterior\n6. Para atualizações diferenciais, aplica patch apenas no intervalo de linhas alteradas e limpa as linhas finais obsoletas quando necessário.\n7. Reposiciona o cursor de hardware para suporte a IME.\n\nAs escritas de renderização usam o modo de saída sincronizado (`CSI ? 2026 h/l`) para reduzir oscilação/rasgamento.\n\n## Restrições de segurança na renderização\n\nVerificações críticas de segurança no `TUI`:\n\n- Linhas renderizadas sem imagem não devem exceder a largura do terminal; overflow lança erro e grava diagnósticos de falha.\n- A composição de sobreposições inclui truncamento defensivo e verificação de largura pós-composição.\n- Mudanças de largura forçam redesenho completo porque a semântica de quebra de linha muda.\n- A posição do cursor é limitada antes do movimento.\n\nEssas restrições são imposições em tempo de execução, não apenas convenções.\n\n## Tratamento de redimensionamento\n\nEventos de redimensionamento são orientados a eventos, de `ProcessTerminal` para `TUI.requestRender()`.\n\nEfeitos:\n\n- Qualquer mudança de largura aciona redesenho completo.\n- O rastreamento de viewport/topo (`#previousViewportTop`, `#maxLinesRendered`) evita cálculos de cursor relativo inválidos quando o conteúdo ou o tamanho do terminal muda.\n- A visibilidade da sobreposição pode depender das dimensões do terminal (`OverlayOptions.visible`); o foco é corrigido quando as sobreposições se tornam não visíveis após o redimensionamento.\n\n## Streaming e atualizações incrementais de UI\n\n`EventController` se inscreve em `AgentSessionEvent` e atualiza a UI incrementalmente:\n\n- `agent_start`: inicia o loader em `statusContainer`.\n- `message_start` do assistente: cria `streamingComponent` e o monta.\n- `message_update`: atualiza o conteúdo do assistente em streaming; cria/atualiza componentes de execução de ferramentas conforme as chamadas de ferramenta aparecem.\n- `tool_execution_update/end`: atualiza os componentes de resultado de ferramenta e o estado de conclusão.\n- `message_end`: finaliza o stream do assistente, trata anotações de erro/interrupção, marca os argumentos de ferramentas pendentes como completos na parada normal.\n- `agent_end`: para os loaders, limpa o estado transiente de stream, executa o switch de modelo adiado, emite notificação de conclusão se em segundo plano.\n\nO agrupamento de ferramentas de leitura é intencionalmente com estado (`#lastReadGroup`) para coalescir chamadas consecutivas de ferramentas de leitura em um único bloco visual até que ocorra uma quebra por uma não-leitura.\n\n## Orquestração de status e loader\n\nResponsabilidade da faixa de status:\n\n- `statusContainer` contém loaders transitórios (`loadingAnimation`, `autoCompactionLoader`, `retryLoader`).\n- `statusLine` renderiza indicadores persistentes de status/hooks/plano e aciona atualizações da borda superior do editor.\n\nComportamento do loader:\n\n- `Loader` atualiza a cada 80ms via intervalo e solicita renderização em cada frame.\n- Os manipuladores de escape são temporariamente sobrescritos durante a compactação automática e a retentativa automática para cancelar essas operações.\n- Nos caminhos de encerramento/cancelamento, os controladores restauram os manipuladores de escape anteriores e param/limpam os componentes do loader.\n\n## Transições de modo e segundo plano\n\n### Modos de entrada Bash/Python\n\nPrefixos de texto de entrada alternam os flags de modo de borda do editor:\n\n- `!` -> modo bash\n- `$` (prefixo que não é template literal) -> modo python\n\nO Escape sai do modo inativo limpando o texto do editor e restaurando a cor da borda; quando a execução está ativa, o Escape aborta a tarefa em execução.\n\n### Modo de plano\n\n`InteractiveMode` rastreia flags de modo de plano, estado da linha de status, ferramentas ativas e troca de modelo. Entrada/saída atualiza as entradas de modo da sessão e o estado de status/UI, incluindo troca de modelo adiada se o streaming estiver ativo.\n\n### Suspender/retomar (`Ctrl+Z`)\n\n`InputController.handleCtrlZ()`:\n\n1. Registra um manipulador `SIGCONT` de disparo único para reiniciar o TUI e forçar renderização.\n2. Para o TUI antes de suspender.\n3. Envia `SIGTSTP` para o grupo de processos.\n\n### Modo em segundo plano (`/background` ou `/bg`)\n\n`handleBackgroundCommand()`:\n\n- Rejeita quando ocioso.\n- Muda o contexto de UI de ferramentas para não interativo (`hasUI=false`) para que ferramentas de UI interativas falhem rapidamente.\n- Para loaders/linha de status e cancela a inscrição do manipulador de eventos em primeiro plano.\n- Inscreve o manipulador de eventos em segundo plano (aguarda principalmente por `agent_end`).\n- Para o TUI e envia `SIGTSTP` (caminho de controle de job POSIX).\n\nEm `agent_end` em segundo plano sem trabalho enfileirado, o controlador envia notificação de conclusão e encerra.\n\n## Caminhos de cancelamento\n\nEntradas principais de cancelamento:\n\n- `Escape` durante o loader de stream ativo: restaura as mensagens enfileiradas para o editor e aborta o agente.\n- `Escape` durante a execução bash/python: aborta o comando em execução.\n- `Escape` durante compactação automática/retentativa: invoca métodos de aborto dedicados através de manipuladores de escape temporários.\n- `Ctrl+C` pressionado uma vez: limpa o editor; pressionado duas vezes em 500ms: encerramento.\n\nO cancelamento é condicional ao estado; a mesma tecla pode significar abortar, sair do modo, acionar o seletor ou não fazer nada, dependendo do estado em tempo de execução.\n\n## Comportamento orientado a eventos vs. com limitação de taxa\n\nAtualizações orientadas a eventos:\n\n- Eventos de sessão do agente (`EventController`)\n- Callbacks de entrada de teclas (`InputController`)\n- Callback de redimensionamento do terminal\n- Observadores de tema/branch em `InteractiveMode`\n\nCaminhos com throttle/debounce:\n\n- A renderização do TUI é debounced por tick (coalescimento de `requestRender`).\n- A animação do loader é de intervalo fixo (80ms), com cada frame solicitando renderização.\n- As atualizações de autocompletar do editor (dentro de `Editor`) usam timers de debounce, reduzindo o reprocessamento durante a digitação.\n\nO runtime, portanto, combina transições de estado orientadas a eventos com cadência de renderização limitada para manter a interatividade responsiva sem explosões de repintura.\n",
	"pt-br/tui/tui.md": "---\ntitle: Integração TUI para Extensões e Ferramentas Personalizadas\ndescription: >-\n  Contrato de integração TUI para extensões, ferramentas personalizadas e\n  renderizadores personalizados.\nsidebar:\n  order: 1\n  label: Integração de extensões\ni18n:\n  sourceHash: 47f8f2b2045e\n  translator: machine\n---\n\n# Integração TUI para extensões e ferramentas personalizadas\n\nEste documento cobre o contrato TUI **atual** utilizado por `packages/coding-agent` e `packages/tui` para UI de extensões, UI de ferramentas personalizadas e renderizadores personalizados.\n\n## O que é este subsistema\n\nO runtime possui duas camadas:\n\n- **Motor de renderização (`packages/tui`)**: renderizador diferencial de terminal, despacho de entrada, foco, overlays, posicionamento de cursor.\n- **Camada de integração (`packages/coding-agent`)**: monta componentes de extensões/ferramentas personalizadas, conecta keybindings/tema e restaura o estado do editor.\n\n## Comportamento do runtime por modo\n\n| Modo | Disponibilidade de `ctx.ui.custom(...)` | Notas |\n| --- | --- | --- |\n| TUI interativo | Suportado | O componente é montado na área do editor, recebe foco, e deve chamar `done(result)` para resolver. |\n| Background/headless | Não interativo | O contexto de UI é no-op (`hasUI === false`). |\n| Modo RPC | Não suportado | `custom()` retorna `Promise<never>` e não monta componentes TUI. |\n\nSe sua extensão/ferramenta pode funcionar em modo não interativo, proteja com `ctx.hasUI` / `pi.hasUI`.\n\n## Contrato principal do componente (`@f5-sales-demo/pi-tui`)\n\n`packages/tui/src/tui.ts` define:\n\n```ts\nexport interface Component {\n  render(width: number): string[];\n  handleInput?(data: string): void;\n  wantsKeyRelease?: boolean;\n  invalidate(): void;\n}\n```\n\n`Focusable` é separado:\n\n```ts\nexport interface Focusable {\n  focused: boolean;\n}\n```\n\nO comportamento do cursor utiliza `CURSOR_MARKER` (não `getCursorPosition`). Componentes com foco emitem o marcador no texto renderizado; `TUI` o extrai e posiciona o cursor de hardware.\n\n## Restrições de renderização (segurança de terminal)\n\nA saída do seu `render(width)` deve ser segura para terminal:\n\n1. **Nunca exceda `width` em nenhuma linha**. O renderizador lança erro se uma linha não-imagem ultrapassar o limite.\n2. **Meça a largura visual**, não o comprimento da string: use `visibleWidth()`.\n3. **Trunque/quebre texto com reconhecimento ANSI** com `truncateToWidth()` / `wrapTextWithAnsi()`.\n4. **Sanitize tabs/conteúdo** de fontes externas usando `replaceTabs()` (e sanitizadores de nível superior nos caminhos de renderização do coding-agent).\n\nPadrão mínimo:\n\n```ts\nimport { replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\n\nrender(width: number): string[] {\n  return this.lines.map(line => truncateToWidth(replaceTabs(line), width));\n}\n```\n\n## Tratamento de entrada e keybindings\n\n### Correspondência de tecla bruta\n\nUse `matchesKey(data, \"...\")` para teclas de navegação e combinações.\n\n### Respeite os keybindings configurados pelo usuário\n\nAs factories de UI de extensão recebem um `KeybindingsManager` (modo interativo) para que você possa honrar ações mapeadas em vez de fixar teclas no código:\n\n```ts\nif (keybindings.matches(data, \"interrupt\")) {\n  done(undefined);\n  return;\n}\n```\n\n### Eventos de liberação/repetição de tecla\n\nEventos de liberação de tecla são filtrados a menos que seu componente defina:\n\n```ts\nwantsKeyRelease = true;\n```\n\nEntão use `isKeyRelease()` / `isKeyRepeat()` se necessário.\n\n## Foco, overlays e cursor\n\n- `TUI.setFocus(component)` direciona a entrada para aquele componente.\n- APIs de overlay existem no `TUI` (`showOverlay`, `OverlayHandle`), mas a montagem de `ctx.ui.custom` de extensão no modo interativo atualmente substitui a área do componente do editor diretamente.\n- A opção `custom(..., options?: { overlay?: boolean })` existe nos tipos de extensão; a montagem interativa de extensão atualmente ignora esta opção.\n\n## Pontos de montagem e contratos de retorno\n\n## 1) UI de Extensão (`ExtensionUIContext`)\n\nAssinatura atual (`extensibility/extensions/types.ts`):\n\n```ts\ncustom<T>(\n  factory: (\n    tui: TUI,\n    theme: Theme,\n    keybindings: KeybindingsManager,\n    done: (result: T) => void,\n  ) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n  options?: { overlay?: boolean },\n): Promise<T>\n```\n\nComportamento no modo interativo (`extension-ui-controller.ts`):\n\n- Salva o texto do editor.\n- Substitui o componente do editor pelo seu componente.\n- Foca o seu componente.\n- Ao chamar `done(result)`: chama `component.dispose?.()`, restaura o editor + texto, foca o editor, resolve a promise.\n\nPortanto, `done(...)` é obrigatório para conclusão.\n\n## 2) Contexto de UI de Hook/ferramenta personalizada (tipagem legada)\n\n`HookUIContext.custom` é tipado como `(tui, theme, done)` nos tipos de hook/ferramenta personalizada.\nA implementação interativa subjacente chama as factories com `(tui, theme, keybindings, done)`. Consumidores JS podem usar o argumento extra; a compatibilidade em nível de tipo ainda reflete a assinatura legada de 3 argumentos.\n\nFerramentas personalizadas tipicamente usam o mesmo ponto de entrada de UI via o objeto `pi.ui` no escopo da factory, e então retornam o valor selecionado no conteúdo normal da ferramenta:\n\n```ts\nasync execute(toolCallId, params, onUpdate, ctx, signal) {\n  if (!pi.hasUI) {\n    return { content: [{ type: \"text\", text: \"UI unavailable\" }] };\n  }\n\n  const picked = await pi.ui.custom<string | undefined>((tui, theme, done) => {\n    const component = new MyPickerComponent(done, signal);\n    return component;\n  });\n\n  return { content: [{ type: \"text\", text: picked ? `Picked: ${picked}` : \"Cancelled\" }] };\n}\n```\n\n## 3) Renderizadores personalizados de chamada/resultado de ferramenta\n\nFerramentas personalizadas e ferramentas de extensão podem retornar componentes a partir de:\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\n`options` atualmente inclui:\n\n- `expanded: boolean`\n- `isPartial: boolean`\n- `spinnerFrame?: number`\n\nEsses renderizadores são montados por `ToolExecutionComponent`.\n\n## Ciclo de vida e cancelamento\n\n- `dispose()` é opcional em nível de tipo, mas deve ser implementado quando você gerencia timers, subprocessos, watchers, sockets ou overlays.\n- `done(...)` deve ser chamado exatamente uma vez no fluxo do seu componente.\n- Para UI de longa duração cancelável, combine `CancellableLoader` com `AbortSignal` e chame `done(...)` a partir de `onAbort`.\n\nExemplo de padrão de cancelamento:\n\n```ts\nconst loader = new CancellableLoader(tui, theme.fg(\"accent\"), theme.fg(\"muted\"), \"Working...\");\nloader.onAbort = () => done(undefined);\nvoid doWork(loader.signal).then(result => done(result));\nreturn loader;\n```\n\n## Exemplo realista de componente personalizado (comando de extensão)\n\n```ts\nimport type { Component } from \"@f5-sales-demo/pi-tui\";\nimport { SelectList, matchesKey, replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\nimport { getSelectListTheme, type ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nclass Picker implements Component {\n  list: SelectList;\n  keybindings: any;\n  done: (value: string | undefined) => void;\n\n  constructor(\n    items: Array<{ value: string; label: string }>,\n    keybindings: any,\n    done: (value: string | undefined) => void,\n  ) {\n    this.list = new SelectList(items, 8, getSelectListTheme());\n    this.keybindings = keybindings;\n    this.done = done;\n    this.list.onSelect = item => this.done(item.value);\n    this.list.onCancel = () => this.done(undefined);\n  }\n\n  handleInput(data: string): void {\n    if (this.keybindings.matches(data, \"interrupt\")) {\n      this.done(undefined);\n      return;\n    }\n    this.list.handleInput(data);\n  }\n\n  render(width: number): string[] {\n    return this.list.render(width).map(line => truncateToWidth(replaceTabs(line), width));\n  }\n\n  invalidate(): void {\n    this.list.invalidate();\n  }\n}\n\nexport default function extension(pi: ExtensionAPI): void {\n  pi.registerCommand(\"pick-model\", {\n    description: \"Pick a model profile\",\n    handler: async (_args, ctx) => {\n      if (!ctx.hasUI) return;\n\n      const selected = await ctx.ui.custom<string | undefined>((tui, theme, keybindings, done) => {\n        const items = [\n          { value: \"fast\", label: theme.fg(\"accent\", \"Fast\") },\n          { value: \"balanced\", label: \"Balanced\" },\n          { value: \"quality\", label: \"Quality\" },\n        ];\n        return new Picker(items, keybindings, done);\n      });\n\n      if (selected) ctx.ui.notify(`Selected profile: ${selected}`, \"info\");\n    },\n  });\n}\n```\n\n## Arquivos-chave de implementação\n\n- `packages/tui/src/tui.ts` — `Component`, `Focusable`, marcador de cursor, foco, overlay, despacho de entrada.\n- `packages/tui/src/utils.ts` — primitivas de largura/truncamento/sanitização.\n- `packages/tui/src/keys.ts` / `keybindings.ts` — parsing de teclas e mapeamento configurável de ações.\n- `packages/coding-agent/src/modes/controllers/extension-ui-controller.ts` — montagem/desmontagem interativa para UI de extensão/hook/ferramenta personalizada.\n- `packages/coding-agent/src/extensibility/extensions/types.ts` — contratos de UI e renderizador de extensão.\n- `packages/coding-agent/src/extensibility/hooks/types.ts` — contrato de UI de hook (assinatura custom legada).\n- `packages/coding-agent/src/extensibility/custom-tools/types.ts` — contratos de execução/renderização de ferramenta personalizada.\n- `packages/coding-agent/src/modes/components/tool-execution.ts` — montagem de componentes `renderCall`/`renderResult` e opções de estado parcial.\n- `packages/coding-agent/src/tools/context.ts` — propagação do contexto de UI da ferramenta (`hasUI`, `ui`).\n",
	"th/configuration/blob-artifact-architecture.md": "---\ntitle: สถาปัตยกรรม Blob และการจัดเก็บ Artifact\ndescription: >-\n  ที่เก็บ blob แบบ content-addressable และ registry ของ artifact\n  สำหรับสื่อในเซสชัน ภาพหน้าจอ และผลลัพธ์จากเครื่องมือ\nsidebar:\n  order: 7\n  label: การจัดเก็บ Blob และ Artifact\ni18n:\n  sourceHash: 70d255f48d5b\n  translator: machine\n---\n\n# สถาปัตยกรรมการจัดเก็บ Blob และ Artifact\n\nเอกสารนี้อธิบายวิธีที่ coding-agent จัดเก็บ payload ขนาดใหญ่/ไบนารีไว้ภายนอก session JSONL วิธีที่ผลลัพธ์ของเครื่องมือที่ถูกตัดทอนได้รับการบันทึก และวิธีที่ URL ภายใน (`artifact://`, `agent://`) แก้ไขกลับไปยังข้อมูลที่จัดเก็บไว้\n\n## เหตุใดจึงมีระบบจัดเก็บสองระบบ\n\nรันไทม์ใช้กลไกการบันทึกข้อมูลสองแบบที่แตกต่างกันสำหรับรูปแบบข้อมูลที่ต่างกัน:\n\n- **Blob แบบ content-addressed** (`blob:sha256:<hash>`): พื้นที่จัดเก็บแบบไบนารีที่ใช้งานร่วมกันทั่วโลก ใช้เพื่อนำ payload base64 ของรูปภาพขนาดใหญ่ออกจากรายการเซสชันที่บันทึกไว้\n- **Artifact แบบ session-scoped** (ไฟล์ภายใต้ `<sessionFile-without-.jsonl>/`): ไฟล์ข้อความต่อเซสชันสำหรับผลลัพธ์เครื่องมือฉบับสมบูรณ์และผลลัพธ์ของ subagent\n\nทั้งสองระบบถูกแยกออกจากกันโดยตั้งใจ:\n\n- การจัดเก็บ blob เพิ่มประสิทธิภาพการลดความซ้ำซ้อนและการอ้างอิงที่เสถียรตาม content hash\n- การจัดเก็บ artifact เพิ่มประสิทธิภาพการใช้เครื่องมือเซสชันแบบ append-only และการเรียกคืนโดยมนุษย์/เครื่องมือผ่าน ID ในเครื่อง\n\n## ขอบเขตการจัดเก็บและโครงสร้างบนดิสก์\n\n## ขอบเขต Blob Store (ทั่วโลก)\n\n`SessionManager` สร้าง `BlobStore(getBlobsDir())` ดังนั้นไฟล์ blob จึงอยู่ในไดเรกทอรี blob ทั่วโลกที่ใช้ร่วมกัน (ไม่ใช่ในโฟลเดอร์เซสชัน)\n\nการตั้งชื่อไฟล์ Blob:\n\n- เส้นทางไฟล์: `<blobsDir>/<sha256-hex>`\n- ไม่มีนามสกุล\n- สตริงอ้างอิงที่จัดเก็บในรายการ: `blob:sha256:<sha256-hex>`\n\nผลที่ตามมา:\n\n- เนื้อหาไบนารีเดียวกันข้ามเซสชันจะแก้ไขไปยัง hash/path เดียวกัน\n- การเขียนเป็น idempotent ที่ระดับเนื้อหา\n- blob สามารถมีอายุยืนยาวกว่าไฟล์เซสชันใด ๆ ก็ได้\n\n## ขอบเขต Artifact (ในเครื่อง session)\n\n`ArtifactManager` กำหนดไดเรกทอรี artifact จากเส้นทางไฟล์เซสชัน:\n\n- ไฟล์เซสชัน: `.../<timestamp>_<sessionId>.jsonl`\n- ไดเรกทอรี artifact: `.../<timestamp>_<sessionId>/` (ตัด `.jsonl` ออก)\n\nประเภท artifact ใช้ไดเรกทอรีนี้ร่วมกัน:\n\n- ไฟล์ผลลัพธ์เครื่องมือที่ถูกตัดทอน: `<numericId>.<toolType>.log` (สำหรับ `artifact://`)\n- ไฟล์ผลลัพธ์ subagent: `<outputId>.md` (สำหรับ `agent://`)\n\n## รูปแบบการจัดสรร ID และชื่อ\n\n## Blob ID: Content Hash\n\n`BlobStore.put()` คำนวณ SHA-256 จากไบต์ไบนารีดิบและคืนค่า:\n\n- `hash`: hex digest\n- `path`: `<blobsDir>/<hash>`\n- `ref`: `blob:sha256:<hash>`\n\nไม่มีการใช้ตัวนับในเครื่องของเซสชัน\n\n## Artifact ID: จำนวนเต็มแบบ monotonic ในเครื่อง session\n\n`ArtifactManager` สแกนไฟล์ artifact `*.log` ที่มีอยู่ในการใช้งานครั้งแรกเพื่อหา ID ตัวเลขสูงสุดที่มีอยู่และตั้งค่า `nextId = max + 1`\n\nพฤติกรรมการจัดสรร:\n\n- รูปแบบไฟล์: `{id}.{toolType}.log`\n- ID เป็นสตริงที่ต่อเนื่องกัน (`\"0\"`, `\"1\"`, ...)\n- การเริ่มต้นใหม่จะไม่เขียนทับ artifact ที่มีอยู่เพราะการสแกนเกิดขึ้นก่อนการจัดสรร\n\nหากไดเรกทอรี artifact หายไป การสแกนจะได้รายการว่างและการจัดสรรเริ่มจาก `0`\n\n## Agent Output ID (`agent://`)\n\n`AgentOutputManager` จัดสรร ID สำหรับผลลัพธ์ subagent เป็น `<index>-<requestedId>` (อาจซ้อนภายใต้ prefix ของ parent เช่น `0-Parent.1-Child`) โดยสแกนไฟล์ `.md` ที่มีอยู่เมื่อเริ่มต้นเพื่อดำเนินต่อจาก index ถัดไปเมื่อเริ่มใหม่\n\n## กระบวนการไหลของการบันทึกข้อมูล\n\n## 1) เส้นทางการเขียนซ้ำการบันทึกรายการเซสชัน\n\nก่อนที่รายการเซสชันจะถูกเขียน (`#rewriteFile` / การบันทึกแบบ incremental) `SessionManager` จะเรียก `prepareEntryForPersistence()` (ผ่าน `truncateForPersistence`)\n\nพฤติกรรมหลัก:\n\n1. **การตัดทอนสตริงขนาดใหญ่**: สตริงที่มีขนาดเกินกำหนดจะถูกตัดและต่อท้ายด้วย `\"[Session persistence truncated large content]\"`\n2. **การลบฟิลด์ชั่วคราว**: `partialJson` และ `jsonlEvents` จะถูกลบออกจากรายการที่บันทึก\n3. **การนำรูปภาพออกไปจัดเก็บใน blob**:\n   - ใช้กับบล็อกรูปภาพใน arrays `content` เท่านั้น\n   - เฉพาะเมื่อ `data` ยังไม่เป็น blob ref\n   - เฉพาะเมื่อความยาว base64 มากกว่าหรือเท่ากับเกณฑ์ (`BLOB_EXTERNALIZE_THRESHOLD = 1024`)\n   - แทนที่ inline base64 ด้วย `blob:sha256:<hash>`\n\nซึ่งทำให้ session JSONL กะทัดรัดในขณะที่ยังคงความสามารถในการกู้คืน\n\n## 2) เส้นทางการ rehydrate เมื่อโหลดเซสชัน\n\nเมื่อเปิดเซสชัน (`setSessionFile`) หลังจาก migration `SessionManager` จะรัน `resolveBlobRefsInEntries()`\n\nสำหรับแต่ละบล็อกรูปภาพ message/custom-message ที่มี `blob:sha256:<hash>`:\n\n- อ่านไบต์ blob จาก blob store\n- แปลงไบต์กลับเป็น base64\n- แก้ไขรายการในหน่วยความจำเพื่อ inline base64 สำหรับผู้ใช้งานรันไทม์\n\nหาก blob หายไป:\n\n- `resolveImageData()` บันทึกคำเตือน\n- คืนสตริง ref เดิมโดยไม่เปลี่ยนแปลง\n- การโหลดดำเนินต่อไป (ไม่มีการหยุดทำงานอย่างฉับพลัน)\n\n## 3) เส้นทางการ spill/truncation ของผลลัพธ์เครื่องมือ\n\n`OutputSink` ขับเคลื่อนการสตรีมผลลัพธ์ใน bash/python/ssh และ executor ที่เกี่ยวข้อง\n\nพฤติกรรม:\n\n1. ทุก chunk จะถูกทำความสะอาดและเพิ่มต่อท้ายบัฟเฟอร์ tail ในหน่วยความจำ\n2. เมื่อไบต์ในหน่วยความจำเกินเกณฑ์ spill (`DEFAULT_MAX_BYTES`, 50KB) sink จะทำเครื่องหมายว่าผลลัพธ์ถูกตัดทอน\n3. หากมีเส้นทาง artifact sink จะเปิด file writer และเขียน:\n   - เนื้อหาที่บัฟเฟอร์ไว้หนึ่งครั้ง\n   - chunk ที่ตามมาทั้งหมด\n4. บัฟเฟอร์ในหน่วยความจำจะถูกตัดให้พอดีกับ tail window เสมอเพื่อการแสดงผล\n5. `dump()` คืนค่าสรุปที่รวม `artifactId` เฉพาะเมื่อ file sink ถูกสร้างสำเร็จ\n\nผลในทางปฏิบัติ:\n\n- UI/tool return แสดง tail ที่ถูกตัดทอน\n- ผลลัพธ์ฉบับสมบูรณ์ถูกเก็บในไฟล์ artifact และอ้างอิงเป็น `artifact://<id>`\n\nหากการสร้าง file sink ล้มเหลว (ข้อผิดพลาด I/O, เส้นทางหายไป ฯลฯ) sink จะถอยกลับไปใช้การตัดทอนในหน่วยความจำอย่างเดียวโดยไม่แสดงข้อผิดพลาด โดยผลลัพธ์ฉบับสมบูรณ์จะไม่ถูกบันทึก\n\n## โมเดลการเข้าถึง URL\n\n## การอ้างอิง `blob:`\n\n`blob:sha256:<hash>` คือการอ้างอิงการบันทึกข้อมูลภายใน payload ของรายการเซสชัน ไม่ใช่รูปแบบ URL ภายในที่จัดการโดย router การแก้ไขทำโดย `SessionManager` ระหว่างการโหลดเซสชัน\n\n## `artifact://<id>`\n\nจัดการโดย `ArtifactProtocolHandler`:\n\n- ต้องมีไดเรกทอรี artifact ของเซสชันที่ทำงานอยู่\n- ID ต้องเป็นตัวเลข\n- แก้ไขโดยการจับคู่ prefix ชื่อไฟล์ `<id>.`\n- คืนข้อความดิบ (`text/plain`) จากไฟล์ `.log` ที่ตรงกัน\n- เมื่อหาไม่พบ ข้อผิดพลาดจะรวม artifact ID ที่มีอยู่\n\nพฤติกรรมเมื่อไดเรกทอรีหายไป:\n\n- หากไดเรกทอรี artifact ไม่มีอยู่ จะส่ง exception `No artifacts directory found`\n\n## `agent://<id>`\n\nจัดการโดย `AgentProtocolHandler` ผ่าน `<artifactsDir>/<id>.md`:\n\n- รูปแบบปกติคืนข้อความ markdown\n- รูปแบบ `/path` หรือ `?q=` ทำการ JSON extraction\n- ไม่สามารถใช้ path และ query extraction พร้อมกันได้\n- หากมีการร้องขอ extraction เนื้อหาไฟล์ต้องแปลงเป็น JSON ได้\n\nพฤติกรรมเมื่อไดเรกทอรีหายไป:\n\n- ส่ง exception `No artifacts directory found`\n\nพฤติกรรมเมื่อผลลัพธ์หายไป:\n\n- ส่ง exception `Not found: <id>` พร้อมรายการ ID ที่มีอยู่จากไฟล์ `.md` ที่มีอยู่\n\nการผสานรวม read tool:\n\n- `read` รองรับการแบ่งหน้าด้วย offset/limit สำหรับการอ่าน URL ภายในที่ไม่ใช่ extraction\n- ปฏิเสธ `offset/limit` เมื่อใช้ `agent://` extraction\n\n## ความหมายของ Resume, Fork และ Move\n\n## Resume\n\n- `ArtifactManager` สแกนไฟล์ `{id}.*.log` ที่มีอยู่เมื่อจัดสรรครั้งแรกและดำเนินการนับต่อ\n- `AgentOutputManager` สแกน ID ผลลัพธ์ `.md` ที่มีอยู่และดำเนินการนับต่อ\n- `SessionManager` rehydrate blob ref ไปเป็น base64 เมื่อโหลด\n\n## Fork\n\n`SessionManager.fork()` สร้างไฟล์เซสชันใหม่พร้อม session ID ใหม่และ link `parentSession` จากนั้นคืนเส้นทางไฟล์เก่า/ใหม่ การคัดลอก artifact จัดการโดย `AgentSession.fork()`:\n\n- พยายามคัดลอก recursive ของไดเรกทอรี artifact เก่าไปยังไดเรกทอรี artifact ใหม่\n- ยอมรับกรณีที่ไดเรกทอรีเก่าหายไป\n- ข้อผิดพลาดในการคัดลอกที่ไม่ใช่ ENOENT จะถูกบันทึกเป็นคำเตือนและ fork ยังคงสำเร็จ\n\nผลกระทบต่อ ID หลัง fork:\n\n- หากคัดลอกสำเร็จ ตัวนับ artifact ในเซสชันใหม่จะดำเนินต่อหลังจาก ID สูงสุดที่คัดลอกมา\n- หากคัดลอกล้มเหลว/ข้าม artifact ID ของเซสชันใหม่จะเริ่มจาก `0`\n\nผลกระทบต่อ Blob หลัง fork:\n\n- blob เป็น global และ content-addressed ดังนั้นจึงไม่จำเป็นต้องคัดลอกไดเรกทอรี blob\n\n## ย้ายไปยัง cwd ใหม่\n\n`SessionManager.moveTo()` เปลี่ยนชื่อทั้งไฟล์เซสชันและไดเรกทอรี artifact ไปยังไดเรกทอรีเซสชันเริ่มต้นใหม่ พร้อมตรรกะ rollback หากขั้นตอนถัดไปล้มเหลว ซึ่งช่วยรักษา artifact identity ขณะย้าย session scope\n\n## การจัดการความล้มเหลวและเส้นทางสำรอง\n\n| กรณี | พฤติกรรม |\n| --- | --- |\n| ไม่พบไฟล์ blob ระหว่าง rehydration | เตือนและเก็บสตริง `blob:sha256:` ref ไว้ในหน่วยความจำ |\n| อ่าน Blob ENOENT ผ่าน `BlobStore.get` | คืนค่า `null` |\n| ไดเรกทอรี artifact หายไป (`ArtifactManager.listFiles`) | คืนรายการว่าง (การจัดสรรสามารถเริ่มใหม่ได้) |\n| ไดเรกทอรี artifact หายไป (`artifact://` / `agent://`) | ส่ง exception `No artifacts directory found` อย่างชัดเจน |\n| ไม่พบ Artifact ID | ส่ง exception พร้อมรายการ ID ที่มีอยู่ |\n| การเริ่มต้น artifact writer ของ OutputSink ล้มเหลว | ดำเนินต่อด้วยการตัดทอนแบบ tail เท่านั้น (ไม่มี artifact ผลลัพธ์ฉบับสมบูรณ์) |\n| ไม่มีไฟล์เซสชัน (เส้นทาง task บางส่วน) | task tool ถอยกลับไปใช้ไดเรกทอรี artifact ชั่วคราวสำหรับผลลัพธ์ subagent |\n\n## การนำ Binary Blob ออกจัดเก็บเทียบกับ Text-Output Artifact\n\n- **Blob externalization** สำหรับ payload รูปภาพไบนารีภายในเนื้อหารายการเซสชันที่บันทึก โดยแทนที่ inline base64 ใน JSONL ด้วยการอ้างอิงเนื้อหาที่เสถียร\n- **Artifact** คือไฟล์ข้อความสำหรับผลลัพธ์การประมวลผลและผลลัพธ์ subagent สามารถระบุที่อยู่ได้ด้วย ID ในเครื่องของเซสชันผ่าน URL ภายใน\n\nทั้งสองระบบมีจุดตัดกันโดยอ้อมเท่านั้น (ทั้งคู่ช่วยลดความเทอะทะของ session JSONL) แต่มีเส้นทาง identity, lifetime และการเรียกคืนที่แตกต่างกัน\n\n## ไฟล์การนำไปใช้งาน\n\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts) — รูปแบบการอ้างอิง blob, การ hashing, put/get, helpers สำหรับ externalize/resolve\n- [`src/session/artifacts.ts`](../../packages/coding-agent/src/session/artifacts.ts) — โมเดลไดเรกทอรี artifact ของเซสชันและการจัดสรร artifact ID แบบตัวเลข\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — พฤติกรรมการตัดทอน/spill-to-file ของ `OutputSink` และ metadata สรุป\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts) — การแปลงข้อมูลสำหรับการบันทึก, blob rehydration เมื่อโหลด, การโต้ตอบ session fork/move\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — การคัดลอกไดเรกทอรี artifact ระหว่าง interactive fork\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — การบูตสแตรป artifact manager ของเครื่องมือและการจัดสรรเส้นทาง artifact ต่อเครื่องมือ\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — resolver สำหรับ `artifact://`\n- [`src/internal-urls/agent-protocol.ts`](../../packages/coding-agent/src/internal-urls/agent-protocol.ts) — resolver สำหรับ `agent://` + JSON extraction\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — การเดินสาย router URL ภายในและ resolver ไดเรกทอรี artifacts\n- [`src/task/output-manager.ts`](../../packages/coding-agent/src/task/output-manager.ts) — การจัดสรร agent output ID แบบ session-scoped สำหรับ `agent://`\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — การเขียน artifact ผลลัพธ์ subagent (`<id>.md`) และ fallback ไดเรกทอรี artifact ชั่วคราว\n",
	"th/configuration/config-usage.md": "---\ntitle: การค้นพบและการแก้ไขการกำหนดค่า\ndescription: >-\n  วิธีที่ xcsh ค้นพบ แก้ไข และจัดเลเยอร์การกำหนดค่าจากรูทของโปรเจกต์ ผู้ใช้\n  และองค์กร\nsidebar:\n  order: 1\n  label: การกำหนดค่า\ni18n:\n  sourceHash: e38bd9792499\n  translator: machine\n---\n\n# การค้นพบและการแก้ไขการกำหนดค่า\n\nเอกสารนี้อธิบายวิธีที่ coding-agent แก้ไขการกำหนดค่าในปัจจุบัน: รูทใดถูกสแกน การทำงานของลำดับความสำคัญ และการนำการกำหนดค่าที่แก้ไขแล้วไปใช้โดย settings, skills, hooks, เครื่องมือ และส่วนขยาย\n\n## ขอบเขต\n\nการนำไปใช้หลัก:\n\n- `src/config.ts`\n- `src/config/settings.ts`\n- `src/config/settings-schema.ts`\n- `src/discovery/builtin.ts`\n- `src/discovery/helpers.ts`\n\nจุดรวมสำคัญ:\n\n- `src/capability/index.ts`\n- `src/discovery/index.ts`\n- `src/extensibility/skills.ts`\n- `src/extensibility/hooks/loader.ts`\n- `src/extensibility/custom-tools/loader.ts`\n- `src/extensibility/extensions/loader.ts`\n\n---\n\n## ขั้นตอนการแก้ไข (แผนภาพ)\n\n```text\n         Config roots (ordered)\n┌───────────────────────────────────────┐\n│ 1) ~/.xcsh/agent + <cwd>/.xcsh          │\n│ 2) ~/.claude   + <cwd>/.claude        │\n│ 3) ~/.codex    + <cwd>/.codex         │\n│ 4) ~/.gemini   + <cwd>/.gemini        │\n└───────────────────────────────────────┘\n                    │\n                    ▼\n        config.ts helper resolution\n  (getConfigDirs/findConfigFile/findNearest...)\n                    │\n                    ▼\n       capability providers enumerate items\n (native, claude, codex, gemini, agents, etc.)\n                    │\n                    ▼\n      priority sort + per-capability dedup\n                    │\n                    ▼\n          subsystem-specific consumption\n   (settings, skills, hooks, tools, extensions)\n```\n\n## 1) รูทของการกำหนดค่าและลำดับแหล่งที่มา\n\n## รูทมาตรฐาน\n\n`src/config.ts` กำหนดรายการลำดับความสำคัญของแหล่งที่มาแบบคงที่:\n\n1. `.xcsh` (native)\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nฐานระดับผู้ใช้:\n\n- `~/.xcsh/agent`\n- `~/.claude`\n- `~/.codex`\n- `~/.gemini`\n\nฐานระดับโปรเจกต์:\n\n- `<cwd>/.xcsh`\n- `<cwd>/.claude`\n- `<cwd>/.codex`\n- `<cwd>/.gemini`\n\n`CONFIG_DIR_NAME` คือ `.xcsh` (`packages/utils/src/dirs.ts`)\n\n## ข้อจำกัดสำคัญ\n\nhelpers ทั่วไปใน `src/config.ts` **ไม่รวม** `.pi` ในลำดับการค้นพบแหล่งที่มา\n\n---\n\n## 2) helpers การค้นพบหลัก (`src/config.ts`)\n\n## `getConfigDirs(subpath, options)`\n\nส่งคืนรายการที่เรียงลำดับ:\n\n- รายการระดับผู้ใช้ก่อน (ตามลำดับความสำคัญของแหล่งที่มา)\n- จากนั้นรายการระดับโปรเจกต์ (ตามลำดับความสำคัญเดียวกัน)\n\nตัวเลือก:\n\n- `user` (ค่าเริ่มต้น `true`)\n- `project` (ค่าเริ่มต้น `true`)\n- `cwd` (ค่าเริ่มต้น `getProjectDir()`)\n- `existingOnly` (ค่าเริ่มต้น `false`)\n\nAPI นี้ใช้สำหรับการค้นหาการกำหนดค่าแบบไดเรกทอรี (commands, hooks, tools, agents เป็นต้น)\n\n## `findConfigFile(subpath, options)` / `findConfigFileWithMeta(...)`\n\nค้นหาไฟล์ที่มีอยู่ไฟล์แรกจากฐานที่เรียงลำดับแล้ว ส่งคืนการจับคู่แรก (เฉพาะพาธ หรือ พาธ+ข้อมูลเมตา)\n\n## `findAllNearestProjectConfigDirs(subpath, cwd)`\n\nเดินขึ้นไปในไดเรกทอรีแม่และส่งคืน **ไดเรกทอรีที่ใกล้ที่สุดต่อฐานแหล่งที่มา** (`.xcsh`, `.claude`, `.codex`, `.gemini`) จากนั้นจัดเรียงผลลัพธ์ตามลำดับความสำคัญของแหล่งที่มา\n\nใช้เมื่อการกำหนดค่าโปรเจกต์ควรสืบทอดจากไดเรกทอรีบรรพบุรุษ (พฤติกรรม monorepo/nested workspace)\n\n---\n\n## 3) wrapper ไฟล์การกำหนดค่า (`ConfigFile<T>` ใน `src/config.ts`)\n\n`ConfigFile<T>` คือตัวโหลดที่ตรวจสอบ schema สำหรับไฟล์การกำหนดค่าเดียว\n\nรูปแบบที่รองรับ:\n\n- `.yml` / `.yaml`\n- `.json` / `.jsonc`\n\nพฤติกรรม:\n\n- ตรวจสอบข้อมูลที่แยกวิเคราะห์ด้วย AJV กับ TypeBox schema ที่ให้มา\n- แคชผลการโหลดจนกว่าจะเรียก `invalidate()`\n- ส่งคืนผลลัพธ์ tri-state ผ่าน `tryLoad()`:\n  - `ok`\n  - `not-found`\n  - `error` (`ConfigError` พร้อม schema/parse context)\n\nยังรองรับการ migration แบบ Legacy:\n\n- หากพาธเป้าหมายเป็น `.yml`/`.yaml` จะมีการ migration อัตโนมัติครั้งเดียวจาก sibling `.json` (`migrateJsonToYml`)\n\n---\n\n## 4) โมเดลการแก้ไข Settings (`src/config/settings.ts`)\n\nโมเดล settings รันไทม์มีการจัดเลเยอร์:\n\n1. Global settings: `~/.xcsh/agent/config.yml`\n2. Project settings: ค้นพบผ่าน settings capability (`settings.json` จาก providers)\n3. Runtime overrides: อยู่ใน memory ไม่มีการบันทึกถาวร\n4. Schema defaults: จาก `SETTINGS_SCHEMA`\n\nเส้นทางการอ่านที่มีผล:\n\n`defaults <- global <- project <- overrides`\n\nพฤติกรรมการเขียน:\n\n- `settings.set(...)` เขียนไปยังเลเยอร์ **global** (`config.yml`) และจัดคิวการบันทึกในเบื้องหลัง\n- Project settings อ่านได้อย่างเดียวจากการค้นพบ capability\n\n## พฤติกรรม Migration ที่ยังใช้งานอยู่\n\nเมื่อเริ่มต้น หาก `config.yml` ไม่มีอยู่:\n\n1. Migration จาก `~/.xcsh/agent/settings.json` (เปลี่ยนชื่อเป็น `.bak` เมื่อสำเร็จ)\n2. รวมกับ legacy DB settings จาก `agent.db`\n3. เขียนผลลัพธ์ที่รวมแล้วไปยัง `config.yml`\n\nการ migration ระดับ field ใน `#migrateRawSettings`:\n\n- `queueMode` -> `steeringMode`\n- มิลลิวินาที `ask.timeout` -> วินาที เมื่อค่าเก่าดูเหมือนเป็น ms (`> 1000`)\n- Legacy flat `theme: \"...\"` -> โครงสร้าง `theme.dark/theme.light`\n\n---\n\n## 5) การรวม Capability/discovery\n\nการโหลดการกำหนดค่าที่ไม่ใช่ core ส่วนใหญ่ไหลผ่าน capability registry (`src/capability/index.ts` + `src/discovery/index.ts`)\n\n## การเรียงลำดับ Provider\n\nProviders ถูกจัดเรียงตามลำดับความสำคัญตัวเลข (สูงกว่าก่อน) ตัวอย่างลำดับความสำคัญ:\n\n- Native OMP (`builtin.ts`): `100`\n- Claude: `80`\n- Codex / agents / Claude marketplace: `70`\n- Gemini: `60`\n\n```text\nProvider precedence (higher wins)\n\nnative (.xcsh)          priority 100\nclaude                 priority  80\ncodex / agents / ...   priority  70\ngemini                 priority  60\n```\n\n## Dedup semantics\n\nCapabilities กำหนด `key(item)`:\n\n- key เดียวกัน => รายการแรกชนะ (รายการที่มีลำดับความสำคัญสูงกว่า/โหลดก่อน)\n- ไม่มี key (`undefined`) => ไม่มี dedup รายการทั้งหมดถูกเก็บไว้\n\nKeys ที่เกี่ยวข้อง:\n\n- skills: `name`\n- tools: `name`\n- hooks: `${type}:${tool}:${name}`\n- extension modules: `name`\n- extensions: `name`\n- settings: ไม่มี dedup (รายการทั้งหมดถูกเก็บรักษาไว้)\n\n---\n\n## 6) พฤติกรรม Native `.xcsh` provider (`src/discovery/builtin.ts`)\n\nNative provider (`id: native`) อ่านจาก:\n\n- project: `<cwd>/.xcsh/...`\n- user: `~/.xcsh/agent/...`\n\n### กฎการรับไดเรกทอรี\n\n`builtin.ts` รวมรูทการกำหนดค่าเฉพาะเมื่อไดเรกทอรีมีอยู่ **และไม่ว่างเปล่า** (`ifNonEmptyDir`)\n\n### การโหลดเฉพาะ Scope\n\n- Skills: `skills/*/SKILL.md`\n- Slash commands: `commands/*.md`\n- Rules: `rules/*.{md,mdc}`\n- Prompts: `prompts/*.md`\n- Instructions: `instructions/*.md`\n- Hooks: `hooks/pre/*`, `hooks/post/*`\n- Tools: `tools/*.json|*.md` และ `tools/<name>/index.ts`\n- Extension modules: ค้นพบใน `extensions/` (+ legacy `settings.json.extensions` string array)\n- Extensions: `extensions/<name>/gemini-extension.json`\n- Settings capability: `settings.json`\n\n### ความละเอียดอ่อนของการค้นหาโปรเจกต์ที่ใกล้ที่สุด\n\nสำหรับ `SYSTEM.md` และ `XCSH.md` native provider ใช้การค้นหาไดเรกทอรี `.xcsh` ของโปรเจกต์ที่ใกล้ที่สุดในบรรพบุรุษ (walk-up) แต่ยังคงต้องการให้ไดเรกทอรี `.xcsh` ไม่ว่างเปล่า\n\n---\n\n## 7) วิธีที่ subsystems หลักใช้การกำหนดค่า\n\n## Settings subsystem\n\n- `Settings.init()` โหลด global `config.yml` + รายการ `settings.json` capability ของโปรเจกต์ที่ค้นพบ\n- เฉพาะรายการ capability ที่มี `level === \"project\"` เท่านั้นที่ถูกรวมเข้าเลเยอร์โปรเจกต์\n\n## Skills subsystem\n\n- `extensibility/skills.ts` โหลดผ่าน `loadCapability(skillCapability.id, { cwd })`\n- ใช้ source toggles และ filters (`ignoredSkills`, `includeSkills`, custom dirs)\n- Legacy-named toggles ยังคงมีอยู่ (`skills.enablePiUser`, `skills.enablePiProject`) แต่ใช้ควบคุม native provider (`provider === \"native\"`)\n\n## Hooks subsystem\n\n- `discoverAndLoadHooks()` แก้ไขพาธ hook จาก hook capability + พาธที่กำหนดค่าอย่างชัดเจน\n- จากนั้นโหลด modules ผ่าน Bun import\n\n## Tools subsystem\n\n- `discoverAndLoadCustomTools()` แก้ไขพาธ tool จาก tool capability + plugin tool paths + พาธที่กำหนดค่าอย่างชัดเจน\n- ไฟล์ tool แบบ declarative `.md/.json` เป็นเพียง metadata เท่านั้น การโหลด executable คาดหวัง code modules\n\n## Extensions subsystem\n\n- `discoverAndLoadExtensions()` แก้ไข extension modules จาก extension-module capability บวกกับพาธที่ชัดเจน\n- การนำไปใช้ปัจจุบันจงใจเก็บเฉพาะรายการ capability ที่มี `_source.provider === \"native\"` ก่อนโหลด\n\n---\n\n## 8) กฎลำดับความสำคัญที่ควรพึ่งพา\n\nใช้โมเดลความคิดนี้:\n\n1. ลำดับไดเรกทอรีแหล่งที่มาจาก `config.ts` กำหนดลำดับพาธผู้สมัคร\n2. ลำดับความสำคัญของ capability provider กำหนดลำดับความสำคัญข้าม provider\n3. Capability key dedup กำหนดพฤติกรรมการชนกัน (first wins สำหรับ keyed capabilities)\n4. Logic การรวม subsystem เฉพาะสามารถเปลี่ยนลำดับความสำคัญที่มีผลเพิ่มเติม (โดยเฉพาะ settings)\n\n### ข้อแม้เฉพาะ Settings\n\nรายการ settings capability ไม่ถูก deduplicate; `Settings.#loadProjectSettings()` deep-merges รายการโปรเจกต์ตามลำดับที่ส่งคืน เนื่องจากการ merge ใช้ค่าของรายการที่อยู่หลังทับค่าก่อนหน้า พฤติกรรม override ที่มีผลขึ้นอยู่กับลำดับการ emit ของ provider ไม่ใช่แค่ semantics ของ capability key เท่านั้น\n\n---\n\n## 9) พฤติกรรม Legacy/compatibility ที่ยังคงมีอยู่\n\n- การ migration JSON -> YAML ของ `ConfigFile` สำหรับไฟล์ที่กำหนดเป้าหมายเป็น YAML\n- การ migration Settings จาก `settings.json` และ `agent.db` ไปยัง `config.yml`\n- การ migration key ของ Settings (`queueMode`, `ask.timeout`, flat `theme`)\n- ความเข้ากันได้ของ extension manifest: loader รับทั้งส่วน manifest `package.json.xcsh` และ `package.json.pi`\n- Legacy setting names `skills.enablePiUser` / `skills.enablePiProject` ยังคงเป็น active gates สำหรับ native skill source\n\nหากพาธ compatibility เหล่านี้ถูกลบออกจากโค้ด ให้อัปเดตเอกสารนี้ทันที เนื่องจากพฤติกรรมรันไทม์หลายอย่างยังคงพึ่งพาพวกมันในปัจจุบัน\n",
	"th/configuration/environment-variables.md": "---\ntitle: ตัวแปรสภาพแวดล้อม\ndescription: >-\n  เอกสารอ้างอิงตัวแปรสภาพแวดล้อมขณะรันไทม์สำหรับการกำหนดค่าและการควบคุมพฤติกรรมของ\n  xcsh\nsidebar:\n  order: 2\n  label: ตัวแปรสภาพแวดล้อม\ni18n:\n  sourceHash: e2890f963c02\n  translator: machine\n---\n\n# ตัวแปรสภาพแวดล้อม (เอกสารอ้างอิงรันไทม์ปัจจุบัน)\n\nเอกสารอ้างอิงนี้ได้รับการรวบรวมจากเส้นทางโค้ดปัจจุบันใน:\n\n- `packages/coding-agent/src/**`\n- `packages/ai/src/**` (การแก้ไข provider/auth ที่ใช้โดย coding-agent)\n- `packages/utils/src/**` และ `packages/tui/src/**` ในกรณีที่ตัวแปรเหล่านั้นส่งผลโดยตรงต่อรันไทม์ของ coding-agent\n\nเอกสารนี้บันทึกเฉพาะพฤติกรรมที่ใช้งานอยู่จริง\n\n## โมเดลการแก้ไขและลำดับความสำคัญ\n\nการค้นหาค่ารันไทม์ส่วนใหญ่ใช้ `$env` จาก `@f5-sales-demo/pi-utils` (`packages/utils/src/env.ts`)\n\nลำดับการโหลดของ `$env`:\n\n1. สภาพแวดล้อมของกระบวนการที่มีอยู่ (`Bun.env`)\n2. ไฟล์ `.env` ของโปรเจกต์ (`$PWD/.env`) สำหรับคีย์ที่ยังไม่ได้ตั้งค่า\n3. ไฟล์ `.env` ของ Home (`~/.env`) สำหรับคีย์ที่ยังไม่ได้ตั้งค่า\n\nกฎเพิ่มเติมในไฟล์ `.env`: คีย์ `XCSH_*` จะถูกสะท้อนไปยังคีย์ `PI_*` ระหว่างการแยกวิเคราะห์\n\n---\n\n## 1) การพิสูจน์ตัวตน model/provider\n\nตัวแปรเหล่านี้ถูกใช้งานผ่าน `getEnvApiKey()` (`packages/ai/src/stream.ts`) เว้นแต่จะระบุไว้เป็นอย่างอื่น\n\n### ข้อมูลประจำตัวของ provider หลัก\n\n| ตัวแปร                          | ใช้สำหรับ | จำเป็นเมื่อ                                                  | หมายเหตุ / ลำดับความสำคัญ                                                                                  |\n|---------------------------------|---|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|\n| `ANTHROPIC_OAUTH_TOKEN`         | การพิสูจน์ตัวตน Anthropic API | ใช้ Anthropic ด้วยการพิสูจน์ตัวตนแบบ OAuth token            | มีลำดับความสำคัญเหนือ `ANTHROPIC_API_KEY` สำหรับการแก้ไข provider auth                              |\n| `ANTHROPIC_API_KEY`             | การพิสูจน์ตัวตน Anthropic API | ใช้ Anthropic โดยไม่มี OAuth token                           | Fallback หลังจาก `ANTHROPIC_OAUTH_TOKEN`                                                             |\n| `ANTHROPIC_FOUNDRY_API_KEY`     | Anthropic ผ่าน Azure Foundry / enterprise gateway | เปิดใช้งาน `CLAUDE_CODE_USE_FOUNDRY`                         | มีลำดับความสำคัญเหนือ `ANTHROPIC_OAUTH_TOKEN` และ `ANTHROPIC_API_KEY` เมื่อเปิดใช้งาน Foundry mode  |\n| `OPENAI_API_KEY`                | การพิสูจน์ตัวตน OpenAI | ใช้ provider ในตระกูล OpenAI โดยไม่มีอาร์กิวเมนต์ apiKey ที่ระบุชัดเจน | ใช้โดย OpenAI Completions/Responses providers                                                       |\n| `GEMINI_API_KEY`                | การพิสูจน์ตัวตน Google Gemini | ใช้โมเดล provider `google`                                  | คีย์หลักสำหรับการแมป Gemini provider                                                                |\n| `GOOGLE_API_KEY`                | Fallback สำหรับการพิสูจน์ตัวตนของ Gemini image tool | ใช้เครื่องมือ `gemini_image` โดยไม่มี `GEMINI_API_KEY`      | ใช้โดย fallback path ของ image tool ใน coding-agent                                                |\n| `GROQ_API_KEY`                  | การพิสูจน์ตัวตน Groq | ใช้โมเดล Groq                                               |                                                                                                     |\n| `CEREBRAS_API_KEY`              | การพิสูจน์ตัวตน Cerebras | ใช้โมเดล Cerebras                                           |                                                                                                     |\n| `TOGETHER_API_KEY`              | การพิสูจน์ตัวตน Together | ใช้ provider `together`                                      |                                                                                                     |\n| `HUGGINGFACE_HUB_TOKEN`         | การพิสูจน์ตัวตน Hugging Face | ใช้ provider `huggingface`                                   | ตัวแปร env token หลักของ Hugging Face                                                               |\n| `HF_TOKEN`                      | การพิสูจน์ตัวตน Hugging Face | ใช้ provider `huggingface`                                   | Fallback เมื่อ `HUGGINGFACE_HUB_TOKEN` ไม่ได้ตั้งค่า                                               |\n| `SYNTHETIC_API_KEY`             | การพิสูจน์ตัวตน Synthetic | ใช้โมเดล Synthetic                                          |                                                                                                     |\n| `NVIDIA_API_KEY`                | การพิสูจน์ตัวตน NVIDIA | ใช้ provider `nvidia`                                        |                                                                                                     |\n| `NANO_GPT_API_KEY`              | การพิสูจน์ตัวตน NanoGPT | ใช้ provider `nanogpt`                                       |                                                                                                     |\n| `VENICE_API_KEY`                | การพิสูจน์ตัวตน Venice | ใช้ provider `venice`                                        |                                                                                                     |\n| `LITELLM_API_KEY`               | การพิสูจน์ตัวตน LiteLLM | ใช้ provider `litellm`                                       | คีย์ LiteLLM proxy ที่เข้ากันได้กับ OpenAI เมื่อตั้งค่าพร้อมกับ `LITELLM_BASE_URL` จะเปิดใช้งานการกำหนดค่าอัตโนมัติของ `models.yml` |\n| `LM_STUDIO_API_KEY`             | การพิสูจน์ตัวตน LM Studio (ไม่บังคับ) | ใช้ provider `lm-studio` กับ host ที่ต้องการการพิสูจน์ตัวตน | LM Studio ในเครื่องส่วนใหญ่จะทำงานโดยไม่มีการพิสูจน์ตัวตน โทเค็นที่ไม่ว่างเปล่าใดๆ จะทำงานได้เมื่อต้องใช้คีย์ |\n| `OLLAMA_API_KEY`                | การพิสูจน์ตัวตน Ollama (ไม่บังคับ) | ใช้ provider `ollama` กับ host ที่ต้องการการพิสูจน์ตัวตน   | Ollama ในเครื่องส่วนใหญ่จะทำงานโดยไม่มีการพิสูจน์ตัวตน โทเค็นที่ไม่ว่างเปล่าใดๆ จะทำงานได้เมื่อต้องใช้คีย์ |\n| `LLAMA_CPP_API_KEY`             | การพิสูจน์ตัวตน Ollama (ไม่บังคับ) | ใช้ `llama-server` พร้อมพารามิเตอร์ `--api-key`            | llama.cpp ในเครื่องส่วนใหญ่จะทำงานโดยไม่มีการพิสูจน์ตัวตน โทเค็นที่ไม่ว่างเปล่าใดๆ จะทำงานได้เมื่อกำหนดค่าคีย์ไว้ |\n| `XIAOMI_API_KEY`                | การพิสูจน์ตัวตน Xiaomi MiMo | ใช้ provider `xiaomi`                                        |                                                                                                     |\n| `MOONSHOT_API_KEY`              | การพิสูจน์ตัวตน Moonshot | ใช้ provider `moonshot`                                      |                                                                                                     |\n| `XAI_API_KEY`                   | การพิสูจน์ตัวตน xAI | ใช้โมเดล xAI                                                |                                                                                                     |\n| `OPENROUTER_API_KEY`            | การพิสูจน์ตัวตน OpenRouter | ใช้โมเดล OpenRouter                                         | ยังใช้โดย image tool เมื่อ preferred/auto provider คือ OpenRouter                                  |\n| `MISTRAL_API_KEY`               | การพิสูจน์ตัวตน Mistral | ใช้โมเดล Mistral                                            |                                                                                                     |\n| `ZAI_API_KEY`                   | การพิสูจน์ตัวตน z.ai | ใช้โมเดล z.ai                                               | ยังใช้โดย z.ai web search provider                                                                  |\n| `MINIMAX_API_KEY`               | การพิสูจน์ตัวตน MiniMax | ใช้ provider `minimax`                                       |                                                                                                     |\n| `MINIMAX_CODE_API_KEY`          | การพิสูจน์ตัวตน MiniMax Code | ใช้ provider `minimax-code`                                  |                                                                                                     |\n| `MINIMAX_CODE_CN_API_KEY`       | การพิสูจน์ตัวตน MiniMax Code CN | ใช้ provider `minimax-code-cn`                               |                                                                                                     |\n| `OPENCODE_API_KEY`              | การพิสูจน์ตัวตน OpenCode | ใช้โมเดล OpenCode                                           |                                                                                                     |\n| `QIANFAN_API_KEY`               | การพิสูจน์ตัวตน Qianfan | ใช้ provider `qianfan`                                       |                                                                                                     |\n| `QWEN_OAUTH_TOKEN`              | การพิสูจน์ตัวตน Qwen Portal | ใช้ `qwen-portal` ด้วย OAuth token                          | มีลำดับความสำคัญเหนือ `QWEN_PORTAL_API_KEY`                                                        |\n| `QWEN_PORTAL_API_KEY`           | การพิสูจน์ตัวตน Qwen Portal | ใช้ `qwen-portal` ด้วย API key                              | Fallback หลังจาก `QWEN_OAUTH_TOKEN`                                                                 |\n| `ZENMUX_API_KEY`                | การพิสูจน์ตัวตน ZenMux | ใช้ provider `zenmux`                                        | ใช้สำหรับเส้นทาง ZenMux OpenAI และ Anthropic-compatible                                            |\n| `VLLM_API_KEY`                  | การพิสูจน์ตัวตน vLLM / เปิดใช้งานการค้นหา | ใช้ provider `vllm` (local OpenAI-compatible servers)        | ค่าที่ไม่ว่างเปล่าใดๆ จะทำงานได้สำหรับ local server ที่ไม่ต้องการการพิสูจน์ตัวตน                   |\n| `CURSOR_ACCESS_TOKEN`           | การพิสูจน์ตัวตน Cursor provider | ใช้ Cursor provider                                          |                                                                                                     |\n| `AI_GATEWAY_API_KEY`            | การพิสูจน์ตัวตน Vercel AI Gateway | ใช้ provider `vercel-ai-gateway`                             |                                                                                                     |\n| `CLOUDFLARE_AI_GATEWAY_API_KEY` | การพิสูจน์ตัวตน Cloudflare AI Gateway | ใช้ provider `cloudflare-ai-gateway`                         | Base URL ต้องกำหนดค่าเป็น `https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic` |\n\n### ห่วงโซ่โทเค็น GitHub/Copilot\n\n| ตัวแปร | ใช้สำหรับ | ห่วงโซ่ |\n|---|---|---|\n| `COPILOT_GITHUB_TOKEN` | การพิสูจน์ตัวตน GitHub Copilot provider | `COPILOT_GITHUB_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN` |\n| `GH_TOKEN` | Fallback ของ Copilot; การพิสูจน์ตัวตน GitHub API ใน web scraper | ใน web scraper: `GITHUB_TOKEN` → `GH_TOKEN` |\n| `GITHUB_TOKEN` | Fallback ของ Copilot; การพิสูจน์ตัวตน GitHub API ใน web scraper | ใน web scraper: ตรวจสอบก่อน `GH_TOKEN` |\n\n---\n\n## 2) การกำหนดค่ารันไทม์เฉพาะ provider\n\n### Anthropic Foundry Gateway (Azure / enterprise proxy)\n\nเมื่อเปิดใช้งาน `CLAUDE_CODE_USE_FOUNDRY` คำขอ Anthropic จะเปลี่ยนไปใช้ Foundry mode:\n\n- Base URL แก้ไขจาก `FOUNDRY_BASE_URL` (fallback ยังคงเป็น model/default base URL หากไม่ได้ตั้งค่า)\n- การแก้ไข API key สำหรับ provider `anthropic` จะกลายเป็น:\n  `ANTHROPIC_FOUNDRY_API_KEY` → `ANTHROPIC_OAUTH_TOKEN` → `ANTHROPIC_API_KEY`\n- `ANTHROPIC_CUSTOM_HEADERS` ถูกแยกวิเคราะห์เป็นคู่ `key: value` ที่คั่นด้วยเครื่องหมายจุลภาค/บรรทัดใหม่ และรวมเข้าใน request headers\n- วัสดุ TLS client/server สามารถฉีดได้จากค่า env:\n  `NODE_EXTRA_CA_CERTS`, `CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`\n  แต่ละตัวยอมรับ:\n  - เส้นทางระบบไฟล์ไปยังเนื้อหา PEM หรือ\n  - PEM แบบ inline (รวมถึงลำดับ `\\n` ที่ escape แล้ว)\n\n| ตัวแปร | ประเภทค่า | พฤติกรรม |\n|---|---|---|\n| `CLAUDE_CODE_USE_FOUNDRY` | สตริงแบบ Boolean (`1`, `true`, `yes`, `on`) | เปิดใช้งาน Foundry mode สำหรับ Anthropic provider |\n| `FOUNDRY_BASE_URL` | สตริง URL | Anthropic endpoint base URL ใน Foundry mode |\n| `ANTHROPIC_FOUNDRY_API_KEY` | สตริงโทเค็น | ใช้สำหรับ `Authorization: Bearer <token>` |\n| `ANTHROPIC_CUSTOM_HEADERS` | สตริงรายการ header | Header เพิ่มเติม; รูปแบบ `header-a: value, header-b: value` หรือคั่นด้วยบรรทัดใหม่ |\n| `NODE_EXTRA_CA_CERTS` | เส้นทาง PEM หรือ PEM แบบ inline | CA chain เพิ่มเติมสำหรับการตรวจสอบใบรับรองเซิร์ฟเวอร์ |\n| `CLAUDE_CODE_CLIENT_CERT` | เส้นทาง PEM หรือ PEM แบบ inline | ใบรับรอง client สำหรับ mTLS |\n| `CLAUDE_CODE_CLIENT_KEY` | เส้นทาง PEM หรือ PEM แบบ inline | private key ของ client สำหรับ mTLS (ต้องจับคู่กับใบรับรอง) |\n\n### Amazon Bedrock\n\n| ตัวแปร | ค่าเริ่มต้น / พฤติกรรม |\n|---|---|\n| `AWS_REGION` | แหล่งข้อมูล region หลัก |\n| `AWS_DEFAULT_REGION` | Fallback หาก `AWS_REGION` ไม่ได้ตั้งค่า |\n| `AWS_PROFILE` | เปิดใช้งาน named profile auth path |\n| `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` | เปิดใช้งาน IAM key auth path |\n| `AWS_BEARER_TOKEN_BEDROCK` | เปิดใช้งาน bearer token auth path |\n| `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` / `AWS_CONTAINER_CREDENTIALS_FULL_URI` | เปิดใช้งาน ECS task credential path |\n| `AWS_WEB_IDENTITY_TOKEN_FILE` + `AWS_ROLE_ARN` | เปิดใช้งาน web identity auth path |\n| `AWS_BEDROCK_SKIP_AUTH` | หากเป็น `1` จะฉีด dummy credentials (สถานการณ์ proxy/non-auth) |\n| `AWS_BEDROCK_FORCE_HTTP1` | หากเป็น `1` จะบังคับใช้ Node HTTP/1 request handler |\n\nRegion fallback ในโค้ด provider: `options.region` → `AWS_REGION` → `AWS_DEFAULT_REGION` → `us-east-1`\n\n### Azure OpenAI Responses\n\n| ตัวแปร | ค่าเริ่มต้น / พฤติกรรม |\n|---|---|\n| `AZURE_OPENAI_API_KEY` | จำเป็นเว้นแต่จะส่ง API key เป็น option |\n| `AZURE_OPENAI_API_VERSION` | ค่าเริ่มต้น `v1` |\n| `AZURE_OPENAI_BASE_URL` | การแทนที่ base URL โดยตรง |\n| `AZURE_OPENAI_RESOURCE_NAME` | ใช้เพื่อสร้าง base URL: `https://<resource>.openai.azure.com/openai/v1` |\n| `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` | สตริงการแมปแบบเลือกได้: `modelId=deploymentName,model2=deployment2` |\n\nการแก้ไข Base URL: option `azureBaseUrl` → env `AZURE_OPENAI_BASE_URL` → option/env resource name → `model.baseUrl`\n\n### Google Vertex AI\n\n| ตัวแปร | จำเป็น? | หมายเหตุ |\n|---|---|---|\n| `GOOGLE_CLOUD_PROJECT` | ใช่ (เว้นแต่จะส่งใน options) | Fallback: `GCLOUD_PROJECT` |\n| `GCLOUD_PROJECT` | Fallback | ใช้เป็นแหล่ง project ID สำรอง |\n| `GOOGLE_CLOUD_LOCATION` | ใช่ (เว้นแต่จะส่งใน options) | ไม่มีค่าเริ่มต้นใน provider |\n| `GOOGLE_APPLICATION_CREDENTIALS` | มีเงื่อนไข | หากตั้งค่าไว้ ไฟล์ต้องมีอยู่ มิฉะนั้นจะตรวจสอบ ADC fallback path (`~/.config/gcloud/application_default_credentials.json`) |\n\n### Kimi\n\n| ตัวแปร | ค่าเริ่มต้น / พฤติกรรม |\n|---|---|\n| `KIMI_CODE_OAUTH_HOST` | การแทนที่ OAuth host หลัก |\n| `KIMI_OAUTH_HOST` | การแทนที่ OAuth host สำรอง |\n| `KIMI_CODE_BASE_URL` | แทนที่ base URL ของ Kimi usage endpoint (`usage/kimi.ts`) |\n\nห่วงโซ่ OAuth host: `KIMI_CODE_OAUTH_HOST` → `KIMI_OAUTH_HOST` → `https://auth.kimi.com`\n\n### ความเข้ากันได้ Antigravity/Gemini image\n\n| ตัวแปร | ค่าเริ่มต้น / พฤติกรรม |\n|---|---|\n| `PI_AI_ANTIGRAVITY_VERSION` | แทนที่แท็กเวอร์ชัน user-agent ของ Antigravity ใน Gemini CLI provider |\n\n### OpenAI Codex responses (การควบคุม feature/debug)\n\n| ตัวแปร | พฤติกรรม |\n|---|---|\n| `PI_CODEX_DEBUG` | `1`/`true` เปิดใช้งาน debug logging ของ Codex provider |\n| `PI_CODEX_WEBSOCKET` | `1`/`true` เปิดใช้งานการตั้งค่าการขนส่งแบบ websocket |\n| `PI_CODEX_WEBSOCKET_V2` | `1`/`true` เปิดใช้งาน websocket v2 path |\n| `PI_CODEX_WEBSOCKET_IDLE_TIMEOUT_MS` | การแทนที่ด้วยจำนวนเต็มบวก (ค่าเริ่มต้น 300000) |\n| `PI_CODEX_WEBSOCKET_RETRY_BUDGET` | การแทนที่ด้วยจำนวนเต็มไม่ติดลบ (ค่าเริ่มต้น 5) |\n| `PI_CODEX_WEBSOCKET_RETRY_DELAY_MS` | การแทนที่ base backoff ด้วยจำนวนเต็มบวก (ค่าเริ่มต้น 500) |\n\n### Cursor provider debug\n\n| ตัวแปร | พฤติกรรม |\n|---|---|\n| `DEBUG_CURSOR` | เปิดใช้งาน debug logs ของ provider; `2`/`verbose` สำหรับข้อมูล payload โดยละเอียด |\n| `DEBUG_CURSOR_LOG` | เส้นทางไฟล์แบบเลือกได้สำหรับ JSONL debug log output |\n\n### สวิตช์ความเข้ากันได้ของ prompt cache\n\n| ตัวแปร | พฤติกรรม |\n|---|---|\n| `PI_CACHE_RETENTION` | หากเป็น `long` จะเปิดใช้งาน long retention ในกรณีที่รองรับ (`anthropic`, `openai-responses`, การแก้ไข Bedrock retention) |\n\n---\n\n## 3) ระบบย่อยการค้นหาเว็บ\n\n### ข้อมูลประจำตัวของ search provider\n\n| ตัวแปร | ใช้โดย |\n|---|---|\n| `EXA_API_KEY` | Exa search provider และ Exa MCP tools |\n| `BRAVE_API_KEY` | Brave search provider |\n| `PERPLEXITY_API_KEY` | Perplexity search provider แบบ API-key |\n| `TAVILY_API_KEY` | Tavily search provider |\n| `ZAI_API_KEY` | z.ai search provider (ยังตรวจสอบ OAuth ที่จัดเก็บไว้ใน `agent.db`) |\n| `OPENAI_API_KEY` / Codex OAuth ใน DB | ความพร้อมใช้งาน/การพิสูจน์ตัวตนของ Codex search provider |\n\n### ห่วงโซ่การพิสูจน์ตัวตน Anthropic web search\n\n`packages/coding-agent/src/web/search/auth.ts` แก้ไข Anthropic web-search credentials ตามลำดับนี้:\n\n1. `ANTHROPIC_SEARCH_API_KEY` (+ `ANTHROPIC_SEARCH_BASE_URL` แบบเลือกได้)\n2. รายการ provider ใน `models.json` ที่มี `api: \"anthropic-messages\"`\n3. Anthropic OAuth credentials จาก `agent.db` (ต้องไม่หมดอายุภายใน 5 นาที)\n4. Generic Anthropic env fallback: provider key (`ANTHROPIC_FOUNDRY_API_KEY`/`ANTHROPIC_OAUTH_TOKEN`/`ANTHROPIC_API_KEY`) + `ANTHROPIC_BASE_URL` แบบเลือกได้ (`FOUNDRY_BASE_URL` เมื่อเปิดใช้งาน Foundry mode)\n\nตัวแปรที่เกี่ยวข้อง:\n\n| ตัวแปร | ค่าเริ่มต้น / พฤติกรรม |\n|---|---|\n| `ANTHROPIC_SEARCH_API_KEY` | คีย์การค้นหาที่ระบุชัดเจนและมีลำดับความสำคัญสูงสุด |\n| `ANTHROPIC_SEARCH_BASE_URL` | ค่าเริ่มต้นเป็น `https://api.anthropic.com` เมื่อไม่ได้ระบุ |\n| `ANTHROPIC_SEARCH_MODEL` | ค่าเริ่มต้นเป็น `claude-haiku-4-5` |\n| `ANTHROPIC_BASE_URL` | Generic fallback base URL สำหรับ tier-4 auth path |\n\n### แฟล็กพฤติกรรม Perplexity OAuth flow\n\n| ตัวแปร | พฤติกรรม |\n|---|---|\n| `PI_AUTH_NO_BORROW` | หากตั้งค่าไว้ จะปิดใช้งาน macOS native-app token borrowing path ใน Perplexity login flow |\n\n---\n\n## 4) เครื่องมือ Python และรันไทม์ kernel\n\n| ตัวแปร | ค่าเริ่มต้น / พฤติกรรม |\n|---|---|\n| `PI_PY` | การแทนที่โหมดเครื่องมือ Python: `0`/`bash`=`bash-only`, `1`/`py`=`ipy-only`, `mix`/`both`=`both`; ค่าที่ไม่ถูกต้องจะถูกละเว้น |\n| `PI_PYTHON_SKIP_CHECK` | หากเป็น `1` จะข้ามการตรวจสอบความพร้อมใช้งาน Python kernel/การตรวจสอบ warm |\n| `PI_PYTHON_GATEWAY_URL` | หากตั้งค่าไว้ จะใช้ external kernel gateway แทน local shared gateway |\n| `PI_PYTHON_GATEWAY_TOKEN` | โทเค็น auth แบบเลือกได้สำหรับ external gateway (`Authorization: token <value>`) |\n| `PI_PYTHON_IPC_TRACE` | หากเป็น `1` จะเปิดใช้งาน low-level IPC trace path ใน kernel module |\n| `VIRTUAL_ENV` | เส้นทาง venv ที่มีลำดับความสำคัญสูงสุดสำหรับการแก้ไข Python runtime |\n\nพฤติกรรมเงื่อนไขเพิ่มเติม:\n\n- หาก `BUN_ENV=test` หรือ `NODE_ENV=test` การตรวจสอบความพร้อมใช้งาน Python จะถือว่าผ่านและข้ามการ warming\n- การกรองสภาพแวดล้อม Python จะปฏิเสธตัวแปร API key ทั่วไปและอนุญาตเฉพาะตัวแปรพื้นฐานที่ปลอดภัยและ prefix `LC_`, `XDG_`, `PI_`\n\n---\n\n## 5) สวิตช์พฤติกรรม agent/runtime\n\n| ตัวแปร                   | ค่าเริ่มต้น / พฤติกรรม                                                                           |\n|----------------------------|----------------------------------------------------------------------------------------------|\n| `PI_SMOL_MODEL`            | การแทนที่ model-role ชั่วคราวสำหรับ `smol` (CLI `--smol` มีลำดับความสำคัญสูงกว่า)               |\n| `PI_SLOW_MODEL`            | การแทนที่ model-role ชั่วคราวสำหรับ `slow` (CLI `--slow` มีลำดับความสำคัญสูงกว่า)               |\n| `PI_PLAN_MODEL`            | การแทนที่ model-role ชั่วคราวสำหรับ `plan` (CLI `--plan` มีลำดับความสำคัญสูงกว่า)               |\n| `PI_NO_TITLE`              | หากตั้งค่าไว้ (ค่าใดๆ ที่ไม่ว่างเปล่า) จะปิดใช้งานการสร้างชื่อ session อัตโนมัติในข้อความผู้ใช้แรก   |\n| `NULL_PROMPT`              | หากเป็น `true` ตัวสร้าง system prompt จะส่งคืนสตริงว่าง                                         |\n| `PI_BLOCKED_AGENT`         | บล็อก subagent ประเภทเฉพาะใน task tool                                                        |\n| `PI_SUBPROCESS_CMD`        | แทนที่คำสั่ง spawn ของ subagent (การข้ามการแก้ไข `xcsh` / `xcsh.cmd`)                           |\n| `PI_TASK_MAX_OUTPUT_BYTES` | จำนวนไบต์ output สูงสุดที่จับได้ต่อ subagent (ค่าเริ่มต้น `500000`)                             |\n| `PI_TASK_MAX_OUTPUT_LINES` | จำนวนบรรทัด output สูงสุดที่จับได้ต่อ subagent (ค่าเริ่มต้น `5000`)                             |\n| `PI_TIMING`                | หากเป็น `1` จะเปิดใช้งาน log การวัดเวลา startup/tool                                           |\n| `PI_DEBUG_STARTUP`         | เปิดใช้งาน debug prints ของ startup stage ไปยัง stderr ในหลาย startup path                     |\n| `PI_PACKAGE_DIR`           | แทนที่การแก้ไข package asset base dir (การค้นหาเส้นทาง docs/examples/changelog)                |\n| `PI_DISABLE_LSPMUX`        | หากเป็น `1` จะปิดใช้งานการตรวจจับ/การผสาน lspmux และบังคับใช้การ spawn LSP server โดยตรง      |\n| `LITELLM_BASE_URL`         | LiteLLM proxy base URL เมื่อตั้งค่าพร้อมกับ `LITELLM_API_KEY` จะทริกเกอร์การสร้าง `models.yml` อัตโนมัติเมื่อรันครั้งแรกและ self-healing ทุกครั้งที่เริ่มต้น |\n| `LM_STUDIO_BASE_URL`       | การแทนที่ LM Studio discovery base URL เริ่มต้นโดยปริยาย (`http://127.0.0.1:1234/v1` หากไม่ได้ตั้งค่า) |\n| `OLLAMA_BASE_URL`          | การแทนที่ Ollama discovery base URL เริ่มต้นโดยปริยาย (`http://127.0.0.1:11434` หากไม่ได้ตั้งค่า) |\n| `LLAMA_CPP_BASE_URL`       | การแทนที่ Llama.cpp discovery base URL เริ่มต้นโดยปริยาย (`http://127.0.0.1:8080` หากไม่ได้ตั้งค่า) |\n| `PI_EDIT_VARIANT`          | หากเป็น `hashline` จะบังคับใช้ hashline read/grep display mode เมื่อ edit tool พร้อมใช้งาน     |\n| `PI_NO_PTY`                | หากเป็น `1` จะปิดใช้งาน interactive PTY path สำหรับ bash tool                                  |\n\n`PI_NO_PTY` ยังถูกตั้งค่าภายในเมื่อใช้ CLI `--no-pty`\n\n---\n\n## 6) เส้นทาง storage และ config root\n\nตัวแปรเหล่านี้ถูกใช้งานผ่าน `@f5-sales-demo/pi-utils/dirs` และส่งผลต่อที่ที่ coding-agent จัดเก็บข้อมูล\n\n| ตัวแปร | ค่าเริ่มต้น / พฤติกรรม |\n|---|---|\n| `PI_CONFIG_DIR` | ชื่อ dirname ของ config root ภายใต้ home (ค่าเริ่มต้น `.xcsh`) |\n| `PI_CODING_AGENT_DIR` | การแทนที่แบบสมบูรณ์สำหรับ agent directory (ค่าเริ่มต้น `~/<PI_CONFIG_DIR or .xcsh>/agent`) |\n| `PWD` | ใช้เมื่อจับคู่ canonical current working directory ใน path helpers |\n\n---\n\n## 7) สภาพแวดล้อมการรัน shell/tool\n\n(จาก `packages/utils/src/procmgr.ts` และการผสาน bash tool ของ coding-agent)\n\n| ตัวแปร | พฤติกรรม |\n|---|---|\n| `PI_BASH_NO_CI` | ระงับการฉีด `CI=true` อัตโนมัติเข้าสู่สภาพแวดล้อม shell ที่ spawn |\n| `CLAUDE_BASH_NO_CI` | Legacy alias fallback สำหรับ `PI_BASH_NO_CI` |\n| `PI_BASH_NO_LOGIN` | มีจุดประสงค์เพื่อปิดใช้งาน login shell mode |\n| `CLAUDE_BASH_NO_LOGIN` | Legacy alias fallback สำหรับ `PI_BASH_NO_LOGIN` |\n| `PI_SHELL_PREFIX` | wrapper คำนำหน้าคำสั่งแบบเลือกได้ |\n| `CLAUDE_CODE_SHELL_PREFIX` | Legacy alias fallback สำหรับ `PI_SHELL_PREFIX` |\n| `VISUAL` | คำสั่ง external editor ที่ต้องการ |\n| `EDITOR` | คำสั่ง external editor สำรอง |\n\nหมายเหตุการใช้งานปัจจุบัน: `PI_BASH_NO_LOGIN`/`CLAUDE_BASH_NO_LOGIN` ถูกอ่าน แต่ `getShellArgs()` ปัจจุบันส่งคืน `['-l','-c']` ในทั้งสองสาขา (ไม่มีผลจริงในปัจจุบัน)\n\n---\n\n## 8) การตรวจจับ UI/theme/session (สภาพแวดล้อมที่ตรวจจับอัตโนมัติ)\n\nตัวแปรเหล่านี้ถูกอ่านเป็นสัญญาณรันไทม์ โดยส่วนใหญ่ถูกตั้งค่าโดย terminal/OS แทนที่จะกำหนดค่าด้วยตนเอง\n\n| ตัวแปร | ใช้สำหรับ |\n|---|---|\n| `COLORTERM`, `TERM`, `WT_SESSION` | การตรวจจับความสามารถสี (theme color mode) |\n| `COLORFGBG` | การตรวจจับพื้นหลัง terminal แบบ light/dark อัตโนมัติ |\n| `TERM_PROGRAM`, `TERM_PROGRAM_VERSION`, `TERMINAL_EMULATOR` | เอกลักษณ์ terminal ใน system prompt/context |\n| `KDE_FULL_SESSION`, `XDG_CURRENT_DESKTOP`, `DESKTOP_SESSION`, `XDG_SESSION_DESKTOP`, `GDMSESSION`, `WINDOWMANAGER` | การตรวจจับ desktop/window-manager ใน system prompt/context |\n| `KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION` | ID การติดตาม session ที่เสถียรต่อ terminal |\n| `SHELL`, `ComSpec`, `TERM_PROGRAM`, `TERM` | การวินิจฉัยข้อมูลระบบ |\n| `APPDATA`, `XDG_CONFIG_HOME` | การแก้ไข lspmux config path |\n| `HOME` | การย่อเส้นทางใน MCP command UI |\n\n---\n\n## 9) แฟล็ก native loader/debug\n\n| ตัวแปร | พฤติกรรม |\n|---|---|\n| `PI_DEV` | เปิดใช้งาน verbose native addon load diagnostics ใน `packages/natives` |\n\n## 10) แฟล็กรันไทม์ TUI (shared package ส่งผลต่อ UX ของ coding-agent)\n\n| ตัวแปร | พฤติกรรม |\n|---|---|\n| `PI_NOTIFICATIONS` | `off` / `0` / `false` ระงับการแจ้งเตือน desktop |\n| `PI_TUI_WRITE_LOG` | หากตั้งค่าไว้ จะบันทึก TUI writes ลงไฟล์ |\n| `PI_HARDWARE_CURSOR` | หากเป็น `1` จะเปิดใช้งาน hardware cursor mode |\n| `PI_CLEAR_ON_SHRINK` | หากเป็น `1` จะล้างแถวว่างเมื่อเนื้อหาย่อขนาด |\n| `PI_DEBUG_REDRAW` | หากเป็น `1` จะเปิดใช้งาน debug logging การ redraw |\n| `PI_TUI_DEBUG` | หากเป็น `1` จะเปิดใช้งาน deep TUI debug dump path |\n\n---\n\n## 11) การควบคุมการสร้าง commit\n\n| ตัวแปร | พฤติกรรม |\n|---|---|\n| `PI_COMMIT_TEST_FALLBACK` | หากเป็น `true` (ไม่คำนึงถึงตัวพิมพ์ใหญ่/เล็ก) จะบังคับใช้ fallback generation path ของ commit |\n| `PI_COMMIT_NO_FALLBACK` | หากเป็น `true` จะปิดใช้งาน fallback เมื่อ agent ไม่ส่งคืน proposal |\n| `PI_COMMIT_MAP_REDUCE` | หากเป็น `false` จะปิดใช้งาน map-reduce commit analysis path |\n| `DEBUG` | หากตั้งค่าไว้ จะพิมพ์ stack trace ข้อผิดพลาดของ commit agent |\n\n---\n\n## ตัวแปรที่มีความอ่อนไหวด้านความปลอดภัย\n\nปฏิบัติต่อตัวแปรเหล่านี้เป็น secrets อย่าบันทึก log หรือ commit:\n\n- Provider/API key และ OAuth/bearer credentials (ทั้งหมดที่เป็น `*_API_KEY`, `*_TOKEN`, OAuth access/refresh tokens)\n- Cloud credentials (`AWS_*`, เส้นทาง `GOOGLE_APPLICATION_CREDENTIALS` อาจเปิดเผยข้อมูล service-account)\n- ตัวแปร search/provider auth (`EXA_API_KEY`, `BRAVE_API_KEY`, `PERPLEXITY_API_KEY`, Anthropic search keys)\n- วัสดุ Foundry mTLS (`CLAUDE_CODE_CLIENT_CERT`, `CLAUDE_CODE_CLIENT_KEY`, `NODE_EXTRA_CA_CERTS` เมื่อชี้ไปยัง private CA bundles)\n\nPython runtime ยังลบตัวแปร key ทั่วไปหลายตัวออกอย่างชัดเจนก่อนที่จะ spawn kernel subprocesses (`packages/coding-agent/src/ipy/runtime.ts`)\n",
	"th/configuration/fs-scan-cache-architecture.md": "---\ntitle: สถาปัตยกรรมแคชการสแกนระบบไฟล์\ndescription: >-\n  สัญญาแคชการสแกนระบบไฟล์สำหรับการค้นพบไฟล์อย่างรวดเร็วด้วยความหมายแบบ\n  stale-while-revalidate\nsidebar:\n  order: 8\n  label: แคชการสแกนระบบไฟล์\ni18n:\n  sourceHash: 2a2bde1726ac\n  translator: machine\n---\n\n# สัญญาสถาปัตยกรรมแคชการสแกนระบบไฟล์\n\nเอกสารนี้กำหนดสัญญาปัจจุบันสำหรับแคชการสแกนระบบไฟล์ร่วมที่ถูกใช้งานใน Rust (`crates/pi-natives/src/fs_cache.rs`) และถูกเรียกใช้โดย API การค้นพบ/ค้นหาแบบเนทีฟที่เปิดเผยต่อ `packages/coding-agent`\n\n## แคชนี้คืออะไร\n\nแคชจัดเก็บรายการรายการสแกนไดเรกทอรีแบบเต็ม (`GlobMatch[]`) โดยใช้ขอบเขตการสแกนและนโยบายการข้ามผ่านเป็นคีย์ จากนั้นให้การดำเนินการระดับสูงขึ้น (การกรอง glob, การให้คะแนนแบบ fuzzy, การเลือกไฟล์สำหรับ grep) ทำงานบนรายการที่แคชไว้เหล่านั้น\n\nเป้าหมายหลัก:\n\n- หลีกเลี่ยงการเดินผ่านระบบไฟล์ซ้ำสำหรับการเรียกค้นพบ/ค้นหาที่ซ้ำกัน\n- รักษาความสอดคล้องกันระหว่าง `glob`, `fuzzyFind`, และ `grep` เมื่อใช้นโยบายการสแกนเดียวกัน\n- อนุญาตให้มีการกู้คืนความเก่าของข้อมูลอย่างชัดเจนสำหรับผลลัพธ์ว่างเปล่า และการทำให้ไม่ถูกต้องอย่างชัดเจนหลังจากการเปลี่ยนแปลงไฟล์\n\n## ความเป็นเจ้าของและพื้นผิวสาธารณะ\n\n- การใช้งานแคชและนโยบาย: `crates/pi-natives/src/fs_cache.rs`\n- ผู้เรียกใช้แบบเนทีฟ:\n  - `crates/pi-natives/src/glob.rs`\n  - `crates/pi-natives/src/fd.rs` (`fuzzyFind`)\n  - `crates/pi-natives/src/grep.rs`\n- การผูกและส่งออก JS:\n  - `packages/natives/src/glob/index.ts` (`invalidateFsScanCache`)\n  - `packages/natives/src/glob/types.ts`\n  - `packages/natives/src/grep/types.ts`\n- ตัวช่วยการทำให้ไม่ถูกต้องจากการกลายพันธุ์ของ coding-agent:\n  - `packages/coding-agent/src/tools/fs-cache-invalidation.ts`\n\n## การแบ่งพาร์ติชันคีย์แคช (สัญญาที่เข้มงวด)\n\nแต่ละรายการใช้คีย์ตาม:\n\n- เส้นทางไดเรกทอรี `root` ที่ถูก canonicalize แล้ว\n- บูลีน `include_hidden`\n- บูลีน `use_gitignore`\n\nผลที่ตามมา:\n\n- การสแกนแบบซ่อนและไม่ซ่อน **ไม่แชร์** รายการ\n- การสแกนที่เคารพ gitignore และการสแกนที่ปิดใช้งาน ignore **ไม่แชร์** รายการ\n- ผู้เรียกใช้ต้องส่งความหมายที่เสถียรสำหรับพฤติกรรม hidden/gitignore; การเปลี่ยนแฟล็กใดแฟล็กหนึ่งจะสร้างพาร์ติชันแคชที่แตกต่างออกไป\n\nการรวม `node_modules` **ไม่อยู่** ในคีย์แคช แคชจัดเก็บรายการที่รวม `node_modules` ไว้ด้วย โดยการกรองต่อผู้เรียกใช้จะถูกใช้หลังการดึงข้อมูล\n\n## พฤติกรรมการรวบรวมการสแกน\n\nการเติมแคชใช้ตัวเดินที่เป็นแบบกำหนดได้ (`ignore::WalkBuilder`) ที่กำหนดค่าโดย `include_hidden` และ `use_gitignore`:\n\n- `follow_links(false)`\n- เรียงลำดับตามเส้นทางไฟล์\n- `.git` จะถูกข้ามเสมอ\n- `node_modules` จะถูกรวบรวมเสมอในขณะสแกนแคช (และอาจกรองออกในภายหลัง)\n- ประเภทไฟล์ + `mtime` ของรายการจะถูกจับภาพผ่าน `symlink_metadata`\n\nรากการค้นหาจะถูกแก้ไขโดย `resolve_search_path`:\n\n- เส้นทางสัมพัทธ์จะถูกแก้ไขตาม cwd ปัจจุบัน\n- เป้าหมายต้องเป็นไดเรกทอรีที่มีอยู่\n- รากจะถูก canonicalize เมื่อเป็นไปได้\n\n## นโยบายความสดและการขับออก\n\nนโยบายส่วนกลาง (กำหนดค่าได้ผ่านสภาพแวดล้อม):\n\n- `FS_SCAN_CACHE_TTL_MS` (ค่าเริ่มต้น `1000`)\n- `FS_SCAN_EMPTY_RECHECK_MS` (ค่าเริ่มต้น `200`)\n- `FS_SCAN_CACHE_MAX_ENTRIES` (ค่าเริ่มต้น `16`)\n\nพฤติกรรม:\n\n- `get_or_scan(...)`\n  - หาก TTL เป็น `0`: ข้ามแคชโดยสิ้นเชิง, สแกนใหม่เสมอ (`cache_age_ms = 0`)\n  - เมื่อแคชถูกต้องภายใน TTL: คืนรายการที่แคชไว้ + `cache_age_ms` ที่ไม่ใช่ศูนย์\n  - เมื่อแคชหมดอายุ: ขับรายการออก, สแกนใหม่, จัดเก็บรายการใหม่\n- การบังคับใช้จำนวนรายการสูงสุดใช้การขับออกแบบเก่าที่สุดก่อนตาม `created_at`\n\n## การตรวจสอบซ้ำเร็วสำหรับผลลัพธ์ว่าง (แยกจากการถูกต้องปกติ)\n\nการถูกต้องของแคชปกติ:\n\n- การถูกต้องของแคชภายใน TTL จะคืนรายการที่แคชไว้และไม่ทำอะไรเพิ่มเติม\n\nการตรวจสอบซ้ำเร็วสำหรับผลลัพธ์ว่าง:\n\n- นี่คือนโยบาย **ฝั่งผู้เรียกใช้** ที่ใช้ `ScanResult.cache_age_ms`\n- หากผลลัพธ์ที่กรอง/ค้นหาแล้วว่างเปล่าและอายุการสแกนที่แคชมีอย่างน้อย `empty_recheck_ms()`, ผู้เรียกใช้จะทำ `force_rescan(...)` หนึ่งครั้งและลองใหม่\n- มีจุดประสงค์เพื่อลดผลลัพธ์เชิงลบที่เก่าเมื่อไฟล์ถูกเพิ่มล่าสุดแต่แคชยังอยู่ภายใน TTL\n\nผู้เรียกใช้ปัจจุบัน:\n\n- `glob`: ตรวจสอบซ้ำเมื่อการจับคู่ที่กรองแล้วว่างเปล่าและอายุการสแกนเกินขีดกำหนด\n- `fuzzyFind` (`fd.rs`): ตรวจสอบซ้ำเฉพาะเมื่อการค้นหาไม่ว่างเปล่าและการจับคู่ที่ให้คะแนนแล้วว่างเปล่า\n- `grep`: ตรวจสอบซ้ำเมื่อรายการไฟล์ผู้สมัครที่เลือกว่างเปล่า\n\n## ค่าเริ่มต้นของผู้เรียกใช้และการใช้งานแคช\n\nแคชเป็นแบบเลือกเปิดใช้งานบน API ที่เปิดเผยทั้งหมด (`cache?: boolean`, ค่าเริ่มต้น `false`)\n\nค่าเริ่มต้นปัจจุบันใน API แบบเนทีฟ:\n\n- `glob`: `hidden=false`, `gitignore=true`, `cache=false`\n- `fuzzyFind`: `hidden=false`, `gitignore=true`, `cache=false`\n- `grep`: `hidden=true`, `cache=false`, และการสแกนแคชจะใช้ `use_gitignore=true` เสมอ\n\nผู้เรียกใช้ coding-agent ในปัจจุบัน:\n\n- การค้นพบผู้สมัครที่กล่าวถึงปริมาณสูงเปิดใช้งานแคช:\n  - `packages/coding-agent/src/utils/file-mentions.ts`\n  - โปรไฟล์: `hidden=true`, `gitignore=true`, `includeNodeModules=true`, `cache=true`\n- การผสานรวม `grep` ระดับเครื่องมือในปัจจุบันปิดใช้งานแคชการสแกน (`cache: false`):\n  - `packages/coding-agent/src/tools/grep.ts`\n\n## สัญญาการทำให้ไม่ถูกต้อง\n\nจุดเข้าการทำให้ไม่ถูกต้องแบบเนทีฟ:\n\n- `invalidateFsScanCache(path?: string)`\n  - ด้วย `path`: ลบรายการแคชที่รากเป็น prefix ของเส้นทางเป้าหมาย\n  - ไม่มี path: ล้างรายการแคชการสแกนทั้งหมด\n\nรายละเอียดการจัดการเส้นทาง:\n\n- เส้นทางการทำให้ไม่ถูกต้องแบบสัมพัทธ์จะถูกแก้ไขตาม cwd\n- การทำให้ไม่ถูกต้องจะพยายาม canonicalize\n- หากเป้าหมายไม่มีอยู่ (เช่น ถูกลบ), ทางเลือกสำรองจะ canonicalize ไดเรกทอรีหลักและต่อชื่อไฟล์กลับเข้าไปเมื่อเป็นไปได้\n- ซึ่งรักษาพฤติกรรมการทำให้ไม่ถูกต้องสำหรับการสร้าง/ลบ/เปลี่ยนชื่อ ที่ด้านใดด้านหนึ่งอาจไม่มีอยู่\n\n## ความรับผิดชอบของกระบวนการกลายพันธุ์ของ coding-agent\n\nโค้ด coding-agent ต้องทำให้ไม่ถูกต้องหลังจากการกลายพันธุ์ระบบไฟล์ที่สำเร็จ\n\nตัวช่วยกลาง:\n\n- `invalidateFsScanAfterWrite(path)`\n- `invalidateFsScanAfterDelete(path)`\n- `invalidateFsScanAfterRename(oldPath, newPath)` (ทำให้ไม่ถูกต้องทั้งสองด้านเมื่อเส้นทางแตกต่างกัน)\n\nจุดเรียกใช้เครื่องมือการกลายพันธุ์ปัจจุบัน:\n\n- `packages/coding-agent/src/tools/write.ts`\n- `packages/coding-agent/src/patch/index.ts` (กระบวนการ hashline/patch/replace)\n\nกฎ: หากกระบวนการใดกลายพันธุ์เนื้อหาหรือตำแหน่งของระบบไฟล์และข้ามตัวช่วยเหล่านี้ ข้อบกพร่องจากความเก่าของแคชเป็นสิ่งที่คาดหวังได้\n\n## การเพิ่มผู้เรียกใช้แคชใหม่อย่างปลอดภัย\n\nเมื่อนำการใช้งานแคชมาใช้ในเส้นทางสแกน/ค้นหาใหม่:\n\n1. **ใช้อินพุตนโยบายการสแกนที่เสถียร**\n   - กำหนดความหมาย hidden/gitignore ก่อน\n   - ส่งค่าเหล่านั้นอย่างสม่ำเสมอไปยัง `get_or_scan`/`force_rescan` เพื่อให้พาร์ติชันแคชเป็นไปตามที่ตั้งใจ\n\n2. **ถือว่าข้อมูลแคชถูกกรองล่วงหน้าเฉพาะตามนโยบายการข้ามผ่านเท่านั้น**\n   - ใช้การกรองเฉพาะเครื่องมือ (รูปแบบ glob, ตัวกรองประเภท, กฎ node_modules) หลังการดึงข้อมูล\n   - อย่าสมมติว่ารายการที่แคชไว้สะท้อนตัวกรองระดับสูงของคุณแล้ว\n\n3. **ใช้งานการตรวจสอบซ้ำเร็วสำหรับผลลัพธ์ว่างเฉพาะสำหรับความเสี่ยงผลลัพธ์เชิงลบที่เก่าเท่านั้น**\n   - ใช้ `scan.cache_age_ms >= empty_recheck_ms()`\n   - ลองใหม่หนึ่งครั้งด้วย `force_rescan(..., store=true, ...)`\n   - แยกเส้นทางนี้ออกจากตรรกะการถูกต้องของแคชปกติ\n\n4. **เคารพโหมดไม่ใช้แคชอย่างชัดเจน**\n   - เมื่อผู้เรียกใช้ปิดใช้งานแคช ให้เรียก `force_rescan(..., store=false, ...)`\n   - อย่าเติมแคชร่วมในเส้นทางคำขอแบบไม่ใช้แคช\n\n5. **เชื่อมต่อการทำให้ไม่ถูกต้องจากการกลายพันธุ์สำหรับเส้นทางการเขียนใหม่ทุกเส้นทาง**\n   - หลังจากการเขียน/แก้ไข/ลบ/เปลี่ยนชื่อที่สำเร็จ ให้เรียกตัวช่วยการทำให้ไม่ถูกต้องของ coding-agent\n   - สำหรับการเปลี่ยนชื่อ/ย้าย ให้ทำให้ไม่ถูกต้องทั้งเส้นทางเก่าและใหม่\n\n6. **อย่าเพิ่มปุ่มปรับ TTL ต่อการเรียกใช้**\n   - สัญญาปัจจุบันเป็นนโยบายส่วนกลางเท่านั้น (กำหนดค่าผ่านสภาพแวดล้อม), ไม่มีการแทนที่ TTL ต่อคำขอ\n\n## ขอบเขตที่ทราบ\n\n- ขอบเขตแคชเป็นในหน่วยความจำเฉพาะกระบวนการ (`DashMap`) ไม่มีการคงอยู่ข้ามการรีสตาร์ทกระบวนการ\n- แคชจัดเก็บรายการการสแกน ไม่ใช่ผลลัพธ์เครื่องมือขั้นสุดท้าย\n- `glob`/`fuzzyFind`/`grep` แชร์รายการการสแกนเฉพาะเมื่อมิติคีย์ (`root`, `hidden`, `gitignore`) ตรงกัน\n- `.git` จะถูกยกเว้นเสมอในเวลารวบรวมการสแกน โดยไม่คำนึงถึงตัวเลือกของผู้เรียกใช้\n",
	"th/configuration/hooks.md": "---\ntitle: Hooks\ndescription: ระบบ Hook สำหรับการทำงานอัตโนมัติก่อน/หลังเหตุการณ์ในวงจรชีวิตของ coding agent\nsidebar:\n  order: 4\n  label: Hooks\ni18n:\n  sourceHash: cdbec10bc405\n  translator: machine\n---\n\n# Hooks\n\nเอกสารนี้อธิบาย **โค้ดระบบย่อย hook ปัจจุบัน** ใน `src/extensibility/hooks/*`\n\n## สถานะปัจจุบันใน runtime\n\nแพ็กเกจ hook (`src/extensibility/hooks/`) ยังคงถูก export และใช้งานได้ในฐานะพื้นผิว API แต่ CLI runtime เริ่มต้นปัจจุบันจะเริ่มต้นใช้เส้นทาง **extension runner** แทน ในขั้นตอนการเริ่มต้นปัจจุบัน:\n\n- `--hook` ถูกใช้เป็น alias ของ `--extension` (เส้นทาง CLI จะถูกรวมเข้าใน `additionalExtensionPaths`)\n- เครื่องมือถูกห่อหุ้มด้วย `ExtensionToolWrapper` ไม่ใช่ `HookToolWrapper`\n- การแปลงบริบทและการส่งสัญญาณวงจรชีวิตผ่าน `ExtensionRunner`\n\nดังนั้นไฟล์นี้จึงจัดทำเอกสารการใช้งานระบบย่อย hook (types/loader/runner/wrapper) รวมถึงพฤติกรรมเดิมและข้อจำกัด\n\n## ไฟล์หลัก\n\n- `src/extensibility/hooks/types.ts` — บริบท hook, ประเภทเหตุการณ์, และสัญญาของผลลัพธ์\n- `src/extensibility/hooks/loader.ts` — การโหลดโมดูลและสะพานเชื่อมการค้นพบ hook\n- `src/extensibility/hooks/runner.ts` — การส่งเหตุการณ์, การค้นหาคำสั่ง, การส่งสัญญาณข้อผิดพลาด\n- `src/extensibility/hooks/tool-wrapper.ts` — wrapper สกัดกั้นเครื่องมือก่อน/หลัง\n- `src/extensibility/hooks/index.ts` — exports/re-exports\n\n## Hook module คืออะไร\n\nhook module ต้อง default-export ฟังก์ชัน factory:\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function hook(pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName === \"bash\" && String(event.input.command ?? \"\").includes(\"rm -rf\")) {\n   return { block: true, reason: \"blocked by policy\" };\n  }\n });\n}\n```\n\nfactory สามารถ:\n\n- ลงทะเบียน event handler ด้วย `pi.on(...)`\n- ส่งข้อความกำหนดเองที่ถาวรด้วย `pi.sendMessage(...)`\n- จัดเก็บสถานะที่ไม่ใช่ LLM ด้วย `pi.appendEntry(...)`\n- ลงทะเบียนคำสั่ง slash ผ่าน `pi.registerCommand(...)`\n- ลงทะเบียน renderer ข้อความกำหนดเองผ่าน `pi.registerMessageRenderer(...)`\n- รันคำสั่ง shell ผ่าน `pi.exec(...)`\n\n## การค้นพบและการโหลด\n\n`discoverAndLoadHooks(configuredPaths, cwd)` ทำดังนี้:\n\n1. โหลด hook ที่ค้นพบจาก capability registry (`loadCapability(\"hooks\")`)\n2. เพิ่มเส้นทางที่กำหนดค่าไว้อย่างชัดเจน (ตัดรายการซ้ำตามเส้นทางสัมบูรณ์)\n3. เรียก `loadHooks(allPaths, cwd)`\n\nจากนั้น `loadHooks` จะ import แต่ละเส้นทางและคาดว่าจะมีฟังก์ชัน `default`\n\n### การระบุเส้นทาง\n\n`loader.ts` ระบุเส้นทาง hook ดังนี้:\n\n- เส้นทางสัมบูรณ์: ใช้ตามที่เป็น\n- เส้นทาง `~`: ขยายให้ครบถ้วน\n- เส้นทางสัมพัทธ์: ระบุตาม `cwd`\n\n### ความไม่ตรงกันของ legacy ที่สำคัญ\n\nผู้ให้บริการการค้นพบสำหรับ `hookCapability` ยังคงจำลองไฟล์ hook สไตล์ shell แบบ pre/post (เช่น `.claude/hooks/pre/*`, `.xcsh/.../hooks/pre/*`)\n\nhook loader ที่นี่ใช้การ import โมดูลแบบ dynamic และต้องการ factory JS/TS ที่เป็น default หากเส้นทาง hook ที่ค้นพบไม่สามารถ import เป็นโมดูลได้ การโหลดจะล้มเหลวและรายงานใน `LoadHooksResult.errors`\n\n## พื้นผิวเหตุการณ์\n\nเหตุการณ์ hook มีการกำหนดประเภทอย่างเข้มงวดใน `types.ts`\n\n### เหตุการณ์ Session\n\n- `session_start`\n- `session_before_switch` → สามารถคืนค่า `{ cancel?: boolean }`\n- `session_switch`\n- `session_before_branch` → สามารถคืนค่า `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_branch`\n- `session_before_compact` → สามารถคืนค่า `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session.compacting` → สามารถคืนค่า `{ context?: string[]; prompt?: string; preserveData?: Record<string, unknown> }`\n- `session_compact`\n- `session_before_tree` → สามารถคืนค่า `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n- `session_tree`\n- `session_shutdown`\n\n### เหตุการณ์ Agent/บริบท\n\n- `context` → สามารถคืนค่า `{ messages?: Message[] }`\n- `before_agent_start` → สามารถคืนค่า `{ message?: { customType; content; display; details } }`\n- `agent_start`\n- `agent_end`\n- `turn_start`\n- `turn_end`\n- `auto_compaction_start`\n- `auto_compaction_end`\n- `auto_retry_start`\n- `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### เหตุการณ์เครื่องมือ (โมเดล pre/post)\n\n- `tool_call` (ก่อนการประมวลผล) → สามารถคืนค่า `{ block?: boolean; reason?: string }`\n- `tool_result` (หลังการประมวลผล) → สามารถคืนค่า `{ content?; details?; isError? }`\n\nนี่คือโมเดลสกัดกั้นก่อน/หลังหลักของระบบย่อย hook\n\n```text\nHook tool interception flow\n\ntool_call handlers\n   │\n   ├─ any { block: true }? ── yes ──> throw (tool blocked)\n   │\n   └─ no\n      │\n      ▼\n   execute underlying tool\n      │\n      ├─ success ──> tool_result handlers can override { content, details }\n      │\n      └─ error   ──> emit tool_result(isError=true) then rethrow original error\n```\n\n## โมเดลการประมวลผลและความหมายของการ mutate\n\n### 1) ก่อนการประมวลผล: `tool_call`\n\n`HookToolWrapper.execute()` ส่งสัญญาณ `tool_call` ก่อนการประมวลผลเครื่องมือ\n\n- หากตัวจัดการใดคืนค่า `{ block: true }` การประมวลผลจะหยุด\n- หาก handler throw เกิดขึ้น wrapper จะล้มเหลวแบบปิดและบล็อกการประมวลผล\n- `reason` ที่คืนค่ากลับมาจะกลายเป็นข้อความข้อผิดพลาดที่ throw\n\n### 2) การประมวลผลเครื่องมือ\n\nเครื่องมือพื้นฐานจะประมวลผลตามปกติหากไม่ถูกบล็อก\n\n### 3) หลังการประมวลผล: `tool_result`\n\nหลังจากสำเร็จ wrapper จะส่งสัญญาณ `tool_result` พร้อมด้วย:\n\n- `toolName`, `toolCallId`, `input`\n- `content`\n- `details`\n- `isError: false`\n\nหาก handler คืนค่า overrides:\n\n- `content` สามารถแทนที่เนื้อหาผลลัพธ์ได้\n- `details` สามารถแทนที่รายละเอียดผลลัพธ์ได้\n\nเมื่อเครื่องมือล้มเหลว wrapper จะส่งสัญญาณ `tool_result` พร้อม `isError: true` และเนื้อหาข้อความข้อผิดพลาด จากนั้น rethrow ข้อผิดพลาดดั้งเดิม\n\n### สิ่งที่ hook สามารถ mutate ได้\n\n- บริบท LLM สำหรับการเรียกครั้งเดียวผ่าน `context` (ห่วงโซ่การแทนที่ `messages`)\n- เนื้อหา/รายละเอียดเอาต์พุตของเครื่องมือเมื่อเรียกเครื่องมือสำเร็จ (เส้นทาง `tool_result`)\n- ข้อความที่แทรกก่อน agent ผ่าน `before_agent_start`\n- พฤติกรรมการยกเลิก/การบีบอัดกำหนดเอง/tree ผ่าน `session_before_*` และ `session.compacting`\n\n### สิ่งที่ hook ไม่สามารถ mutate ได้ในการใช้งานนี้\n\n- พารามิเตอร์ input ของเครื่องมือโดยตรง (เฉพาะบล็อก/อนุญาตใน `tool_call` เท่านั้น)\n- การดำเนินการต่อหลังจากเกิดข้อผิดพลาดของเครื่องมือ (เส้นทางข้อผิดพลาด rethrow)\n- สถานะสำเร็จ/ข้อผิดพลาดสุดท้ายในพฤติกรรม wrapper (ค่า `isError` ที่คืนกลับมามีประเภทกำหนดไว้แต่ไม่ถูกนำไปใช้โดย `HookToolWrapper`)\n\n## ลำดับและพฤติกรรมความขัดแย้ง\n\n### ลำดับระดับการค้นพบ\n\nผู้ให้บริการ capability จะถูกเรียงลำดับตามลำดับความสำคัญ (สูงกว่าก่อน) การตัดรายการซ้ำตาม capability key โดย first wins\n\nสำหรับ `hooks` capability key คือ `${type}:${tool}:${name}` รายการซ้ำที่ถูกแทนที่จากผู้ให้บริการที่มีลำดับความสำคัญต่ำกว่าจะถูกทำเครื่องหมายและแยกออกจากรายการที่ค้นพบที่มีผล\n\n### ลำดับการโหลด\n\n`discoverAndLoadHooks` สร้างรายการ `allPaths` แบบแบน ตัดรายการซ้ำตามเส้นทางสัมบูรณ์ที่ระบุ จากนั้น `loadHooks` จะวนซ้ำตามลำดับนั้น\nลำดับไฟล์ภายในแต่ละไดเรกทอรีที่ค้นพบขึ้นอยู่กับเอาต์พุตของ `readdir`; hook loader ไม่ได้ทำการเรียงลำดับเพิ่มเติม\n\n### ลำดับ handler ขณะ runtime\n\nภายใน `HookRunner` ลำดับจะกำหนดชัดเจนตามลำดับการลงทะเบียน:\n\n1. ลำดับอาร์เรย์ hooks\n2. ลำดับการลงทะเบียน handler ต่อ hook/event\n\nพฤติกรรมความขัดแย้งตามประเภทเหตุการณ์:\n\n- `tool_call`: ผลลัพธ์ที่คืนมาล่าสุดชนะ ยกเว้น handler บล็อก; การบล็อกครั้งแรก short-circuits\n- `tool_result`: override ที่คืนมาล่าสุดชนะ (ไม่มี short-circuit)\n- `context`: ต่อเชื่อมกัน; handler แต่ละตัวจะได้รับเอาต์พุตข้อความของ handler ก่อนหน้า\n- `before_agent_start`: ข้อความแรกที่คืนมาจะถูกเก็บไว้; ข้อความถัดไปจะถูกละเว้น\n- `session_before_*`: ผลลัพธ์ล่าสุดที่คืนมาจะถูกติดตาม; `cancel: true` short-circuits ทันที\n- `session.compacting`: ผลลัพธ์ล่าสุดที่คืนมาชนะ\n\nความขัดแย้งของคำสั่ง/renderer:\n\n- `getCommand(name)` คืนค่าการจับคู่แรกในทุก hook (โหลดครั้งแรกชนะ)\n- `getMessageRenderer(customType)` คืนค่าการจับคู่แรก\n- `getRegisteredCommands()` คืนค่าคำสั่งทั้งหมด (ไม่มีการตัดรายการซ้ำ)\n\n## การโต้ตอบกับ UI (`HookContext.ui`)\n\n`HookUIContext` รวมถึง:\n\n- `select`, `confirm`, `input`, `editor`\n- `notify`\n- `setStatus`\n- `custom`\n- `setEditorText`, `getEditorText`\n- getter ของ `theme`\n\n`ctx.hasUI` ระบุว่ามี UI แบบโต้ตอบให้ใช้งานหรือไม่\n\nเมื่อรันโดยไม่มี UI พฤติกรรมบริบท no-op เริ่มต้นคือ:\n\n- `select/input/editor` คืนค่า `undefined`\n- `confirm` คืนค่า `false`\n- `notify`, `setStatus`, `setEditorText` เป็น no-op\n- `getEditorText` คืนค่า `\"\"`\n\n### พฤติกรรมบรรทัดสถานะ\n\nข้อความสถานะ hook ที่กำหนดผ่าน `ctx.ui.setStatus(key, text)` จะ:\n\n- จัดเก็บตาม key\n- เรียงลำดับตามชื่อ key\n- ทำความสะอาด (`\\r`, `\\n`, `\\t` → spaces; ช่องว่างซ้ำจะถูกรวม)\n- รวมและตัดความกว้างสำหรับการแสดงผล\n\n## การส่งต่อข้อผิดพลาดและ fallback\n\n### ขณะโหลด\n\n- โมดูลไม่ถูกต้องหรือไม่มี default export → ถูกบันทึกใน `LoadHooksResult.errors`\n- การโหลดดำเนินต่อสำหรับ hook อื่นๆ\n\n### ขณะเกิดเหตุการณ์\n\n`HookRunner.emit(...)` จับข้อผิดพลาดของ handler สำหรับเหตุการณ์ส่วนใหญ่และส่ง `HookError` ไปยัง listener (`hookPath`, `event`, `error`) จากนั้นดำเนินต่อ\n\n`emitToolCall(...)` เข้มงวดกว่า: ข้อผิดพลาดของ handler ไม่ถูกกลืนที่นั่น; มันถูกส่งต่อไปยังผู้เรียก ใน `HookToolWrapper` สิ่งนี้จะบล็อกการเรียกเครื่องมือ (fail-safe)\n\n## ตัวอย่าง API จริง\n\n### บล็อกคำสั่ง bash ที่ไม่ปลอดภัย\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName !== \"bash\") return;\n  const cmd = String(event.input.command ?? \"\");\n  if (!cmd.includes(\"rm -rf\")) return;\n\n  if (!ctx.hasUI) return { block: true, reason: \"rm -rf blocked (no UI)\" };\n  const ok = await ctx.ui.confirm(\"Dangerous command\", `Allow: ${cmd}`);\n  if (!ok) return { block: true, reason: \"user denied command\" };\n });\n}\n```\n\n### ปิดบังเอาต์พุตเครื่องมือหลังการประมวลผล\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_result\", async event => {\n  if (event.toolName !== \"read\" || event.isError) return;\n\n  const redacted = event.content.map(chunk => {\n   if (chunk.type !== \"text\") return chunk;\n   return { ...chunk, text: chunk.text.replaceAll(/API_KEY=\\S+/g, \"API_KEY=[REDACTED]\") };\n  });\n\n  return { content: redacted };\n });\n}\n```\n\n### แก้ไขบริบทโมเดลต่อการเรียก LLM\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"context\", async event => {\n  const filtered = event.messages.filter(msg => !(msg.role === \"custom\" && msg.customType === \"debug-only\"));\n  return { messages: filtered };\n });\n}\n```\n\n### ลงทะเบียนคำสั่ง slash พร้อม context methods ที่ปลอดภัยสำหรับคำสั่ง\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.registerCommand(\"handoff\", {\n  description: \"Create a new session with setup message\",\n  handler: async (_args, ctx) => {\n   await ctx.waitForIdle();\n   await ctx.newSession({\n    parentSession: ctx.sessionManager.getSessionFile(),\n    setup: async sm => {\n     sm.appendMessage({\n      role: \"user\",\n      content: [{ type: \"text\", text: \"Continue from prior session summary.\" }],\n      timestamp: Date.now(),\n     });\n    },\n   });\n  },\n });\n}\n```\n\n## พื้นผิวการ export\n\n`src/extensibility/hooks/index.ts` export:\n\n- loading APIs (`discoverAndLoadHooks`, `loadHooks`)\n- runner และ wrapper (`HookRunner`, `HookToolWrapper`)\n- ประเภท hook ทั้งหมด\n- `execCommand` re-export\n\nและ package root (`src/index.ts`) re-export **ประเภท** hook เป็นพื้นผิวความเข้ากันได้แบบ legacy\n",
	"th/configuration/porting-from-pi-mono.md": "---\ntitle: 'Porting From pi-mono: คู่มือการผสานรวมเชิงปฏิบัติ'\ndescription: >-\n  คู่มือเชิงปฏิบัติสำหรับการย้ายโค้ดจาก monorepo ของ pi-mono เข้าสู่ codebase\n  ของ xcsh\nsidebar:\n  order: 9\n  label: การพอร์ตจาก pi-mono\ni18n:\n  sourceHash: fd4e8c09303d\n  translator: machine\n---\n\n# การพอร์ตจาก pi-mono: คู่มือการผสานรวมเชิงปฏิบัติ\n\nคู่มือนี้เป็นรายการตรวจสอบที่สามารถใช้ซ้ำได้สำหรับการพอร์ตการเปลี่ยนแปลงจาก pi-mono เข้าสู่ repo นี้\nใช้ได้กับทุกการผสาน: ไฟล์เดียว, feature branch, หรือการซิงค์รีลีสทั้งหมด\n\n## จุดซิงค์ล่าสุด\n\n**Commit:** `b21b42d032919de2f2e6920a76fa9a37c3920c0a`\n**วันที่:** 2026-03-22\n\nอัปเดตส่วนนี้หลังจากการซิงค์แต่ละครั้ง; อย่าใช้ช่วงก่อนหน้าซ้ำ\n\nเมื่อเริ่มการซิงค์ใหม่ ให้สร้าง patches จาก commit นี้เป็นต้นไป:\n\n```bash\ngit format-patch b21b42d032919de2f2e6920a76fa9a37c3920c0a..HEAD --stdout > changes.patch\n```\n\n## 0) กำหนดขอบเขต\n\n- ระบุข้อมูลอ้างอิงจาก upstream (commit, tag, หรือ PR)\n- ระบุรายการ packages หรือโฟลเดอร์ที่คุณวางแผนจะแก้ไข\n- ตัดสินใจว่าฟีเจอร์ใดอยู่ในขอบเขตและฟีเจอร์ใดที่ข้ามโดยตั้งใจ\n\n## 1) นำโค้ดมาอย่างปลอดภัย\n\n- ใช้ diff ที่สะอาดและมีจุดเน้นชัดเจน แทนการคัดลอกทั้งหมด\n- หลีกเลี่ยงการคัดลอก built artifacts หรือไฟล์ที่สร้างอัตโนมัติ\n- หาก upstream เพิ่มไฟล์ใหม่ ให้เพิ่มอย่างชัดเจนและตรวจสอบเนื้อหา\n\n## 2) จับคู่ข้อกำหนดนามสกุลไฟล์ในการ import\n\nซอร์ส TypeScript สำหรับ runtime ส่วนใหญ่จะละ `.js` ในการ import ภายใน แต่ entrypoints ของ test/bench บางไฟล์จะคง `.js` ไว้เพื่อความเข้ากันได้กับ ESM runtime ให้ทำตามรูปแบบที่มีอยู่ของ package นั้นๆ; อย่าลบนามสกุลไฟล์แบบหว่านแห\n\n- ในซอร์ส runtime ของ `packages/coding-agent` ให้ import ภายในไม่ต้องมีนามสกุล ยกเว้นเมื่อ import สิ่งที่ไม่ใช่ TS\n- ใน `packages/tui/test` และ `packages/natives/bench` ให้คง `.js` ไว้ในจุดที่ไฟล์โดยรอบใช้อยู่แล้ว\n- คงนามสกุลไฟล์จริงเมื่อเครื่องมือต้องการ (เช่น `.json`, `.css`, `.md` text embeds)\n- ตัวอย่าง: `import { x } from \"./foo.js\";` → `import { x } from \"./foo\";` (เฉพาะเมื่อข้อกำหนดของ package คือไม่มีนามสกุล)\n\n## 3) แทนที่ scope ของ import\n\nUpstream ใช้ package scope ที่แตกต่างกัน ให้แทนที่อย่างสม่ำเสมอ\n\n- แทนที่ scope เก่าด้วย scope ที่ใช้ในที่นี้\n- ตัวอย่าง (ปรับให้ตรงกับ packages ที่คุณกำลังพอร์ต):\n  - `@mariozechner/pi-coding-agent` → `@f5-sales-demo/xcsh`\n  - `@mariozechner/pi-agent-core` → `@f5-sales-demo/pi-agent-core`\n  - `@mariozechner/pi-tui` → `@f5-sales-demo/pi-tui`\n  - `@mariozechner/pi-ai` → `@f5-sales-demo/pi-ai`\n\n## 4) ใช้ Bun APIs เมื่อดีกว่า Node\n\nเรารันบน Bun แทนที่ Node APIs เฉพาะเมื่อ Bun มีทางเลือกที่ดีกว่า\n\n**ควรแทนที่:**\n\n- การสร้าง Process: `child_process.spawn` → Bun Shell `$` สำหรับคำสั่งง่ายๆ, `Bun.spawn`/`Bun.spawnSync` สำหรับ streaming หรืองานที่ทำงานนาน\n- File I/O: `fs.readFileSync` → `Bun.file().text()` / `Bun.write()`\n- HTTP clients: `node-fetch`, `axios` → native `fetch`\n- Crypto hashing: `node:crypto` → Web Crypto หรือ `Bun.hash`\n- SQLite: `better-sqlite3` → `bun:sqlite`\n- การโหลด Env: `dotenv` → Bun โหลด `.env` อัตโนมัติ\n\n**ไม่ควรแทนที่ (สิ่งเหล่านี้ทำงานได้ดีใน Bun):**\n\n- `os.homedir()` — อย่าแทนที่ด้วย `Bun.env.HOME`, `Bun.env.HOME`, หรือค่าตายตัว `\"~\"`\n- `os.tmpdir()` — อย่าแทนที่ด้วย `Bun.env.TMPDIR || \"/tmp\"` หรือ path แบบ hardcoded\n- `fs.mkdtempSync()` — อย่าแทนที่ด้วยการสร้าง path แบบ manual\n- `path.join()`, `path.resolve()` ฯลฯ — ใช้ได้ปกติ\n\n**รูปแบบ Import:** ใช้ prefix `node:` กับ namespace imports เท่านั้น (ไม่ใช้ named imports จาก `node:fs` หรือ `node:path`)\n\n**ข้อกำหนดเพิ่มเติมของ Bun:**\n\n- ใช้ Bun Shell `$` สำหรับคำสั่งสั้นๆ ที่ไม่ต้อง streaming; ใช้ `Bun.spawn` เฉพาะเมื่อต้องการ streaming I/O หรือการควบคุม process\n- ใช้ `Bun.file()`/`Bun.write()` สำหรับไฟล์ และ `node:fs/promises` สำหรับไดเรกทอรี\n- หลีกเลี่ยงการตรวจสอบ `Bun.file().exists()`; ใช้การจัดการ `isEnoent` ใน try/catch\n- ใช้ `Bun.sleep(ms)` แทน wrappers ของ `setTimeout`\n\n**ผิด:**\n\n```typescript\n// BROKEN: env vars may be undefined, \"~\" is not expanded\nconst home = Bun.env.HOME || \"~\";\nconst tmp = Bun.env.TMPDIR || \"/tmp\";\n```\n\n**ถูกต้อง:**\n\n```typescript\nimport * as os from \"node:os\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst configDir = path.join(os.homedir(), \".config\", \"myapp\");\nconst tempDir = fs.mkdtempSync(path.join(os.tmpdir(), \"myapp-\"));\n```\n\n## 5) ใช้ Bun embeds แทน (ไม่ต้องคัดลอก)\n\nอย่าคัดลอก runtime assets หรือ vendor files ตอน build time\n\n- หาก upstream คัดลอก assets ลงในโฟลเดอร์ dist ให้แทนที่ด้วย Bun-friendly embeds\n- Prompts เป็นไฟล์ `.md` แบบ static; ใช้ Bun text imports (`with { type: \"text\" }`) และ Handlebars แทน inline prompt strings\n- ใช้ `import.meta.dir` + `Bun.file` เพื่อโหลดทรัพยากรที่ไม่ใช่ข้อความที่อยู่ใกล้เคียง\n- เก็บ assets ไว้ใน repo และให้ bundler รวมเข้ามา\n- ลบ copy scripts ยกเว้นผู้ใช้ร้องขออย่างชัดเจน\n- หาก upstream อ่านไฟล์ bundled fallback ตอน runtime ให้แทนที่การอ่าน filesystem ด้วย Bun text embed import\n  - ตัวอย่าง (Codex instructions fallback):\n    - `const FALLBACK_PROMPT_PATH = join(import.meta.dir, \"codex-instructions.md\");` -> ลบออก\n    - `import FALLBACK_INSTRUCTIONS from \"./codex-instructions.md\" with { type: \"text\" };`\n    - ใช้ `return FALLBACK_INSTRUCTIONS;` แทน `readFileSync(FALLBACK_PROMPT_PATH, \"utf8\")`\n\n## 6) พอร์ต `package.json` อย่างระมัดระวัง\n\nถือว่า `package.json` เป็นสัญญา ผสานอย่างตั้งใจ\n\n- คง `name`, `version`, `type`, `exports`, และ `bin` ที่มีอยู่ ยกเว้นการพอร์ตจำเป็นต้องเปลี่ยนแปลง\n- แทนที่ npm/node scripts ด้วย Bun equivalents (เช่น `bun check`, `bun test`)\n- ตรวจสอบให้แน่ใจว่า dependencies ใช้ scope ที่ถูกต้อง\n- อย่า downgrade dependencies เพื่อแก้ type errors; ให้ upgrade แทน\n- ตรวจสอบ workspace package links และ `peerDependencies`\n\n## 7) จัดรูปแบบโค้ดและเครื่องมือให้สอดคล้อง\n\n- คงข้อกำหนดการจัดรูปแบบที่มีอยู่\n- อย่าใช้ `any` ยกเว้นจำเป็น\n- หลีกเลี่ยง dynamic imports และ inline type imports; ใช้ top-level imports เท่านั้น\n- อย่าสร้าง prompts ในโค้ด; prompts เป็นไฟล์ `.md` แบบ static ที่ render ด้วย Handlebars\n- ใน coding-agent อย่าใช้ `console.log`/`console.warn`/`console.error`; ใช้ `logger` จาก `@f5-sales-demo/pi-utils`\n- ใช้ `Promise.withResolvers()` แทน `new Promise((resolve, reject) => ...)`\n- **ไม่ใช้คีย์เวิร์ด `private`/`protected`/`public` บน class fields หรือ methods** ใช้ ES `#` private fields สำหรับการห่อหุ้ม; ปล่อย accessible members ไม่ต้องมีคีย์เวิร์ด ข้อยกเว้นเดียวคือ constructor parameter properties (`constructor(private readonly x: T)`) ซึ่ง TypeScript กำหนดให้ต้องมีคีย์เวิร์ด เมื่อพอร์ตโค้ด upstream ที่ใช้ `private foo` หรือ `protected bar` ให้แปลงเป็น `#foo` (private) หรือ bare `bar` (accessible)\n- ใช้ helpers และ utilities ที่มีอยู่แทนโค้ด ad-hoc ใหม่\n- รักษาการเปลี่ยนแปลงโครงสร้างพื้นฐานแบบ Bun-first ที่ทำไว้แล้วใน repo นี้:\n  - Runtime คือ Bun (ไม่มี Node entry points)\n  - Package manager คือ Bun (ไม่มี npm lockfiles)\n  - Node APIs หนักๆ (`child_process`, `readline`) ถูกแทนที่ด้วย Bun equivalents\n  - Node APIs เบาๆ (`os.homedir`, `os.tmpdir`, `fs.mkdtempSync`, `path.*`) ยังคงใช้อยู่\n  - CLI shebangs ใช้ `bun` (ไม่ใช่ `node`, ไม่ใช่ `tsx`)\n  - Packages ใช้ไฟล์ source โดยตรง (ไม่มี TypeScript build step)\n  - CI workflows รัน Bun สำหรับ install/check/test\n\n## 8) ลบ compatibility layers เก่า\n\nยกเว้นได้รับการร้องขอ ให้ลบ upstream compatibility shims\n\n- ลบ APIs เก่าที่ถูกแทนที่แล้ว\n- อัปเดตทุก call sites ให้ใช้ API ใหม่โดยตรง\n- อย่าคง `*_v2` หรือเวอร์ชันคู่ขนานไว้\n\n## 9) อัปเดตเอกสารและการอ้างอิง\n\n- แทนที่ลิงก์ repo ของ pi-mono ตามความเหมาะสม\n- อัปเดตตัวอย่างให้ใช้ Bun และ package scopes ที่ถูกต้อง\n- ตรวจสอบให้แน่ใจว่าคำแนะนำใน README ยังตรงกับพฤติกรรมปัจจุบันของ repo\n\n## 10) ตรวจสอบการพอร์ต\n\nรันการตรวจสอบมาตรฐานหลังจากทำการเปลี่ยนแปลง:\n\n- `bun check`\n\nหาก repo มีการตรวจสอบที่ล้มเหลวอยู่แล้วที่ไม่เกี่ยวข้องกับการเปลี่ยนแปลงของคุณ ให้แจ้งเรื่องนั้น\nการทดสอบใช้ runner ของ Bun (ไม่ใช่ Vitest) แต่รัน `bun test` เฉพาะเมื่อมีการร้องขอโดยชัดเจนเท่านั้น\n\n## 11) ปกป้องฟีเจอร์ที่ปรับปรุงแล้ว (รายการดักจับ regression)\n\nหากคุณปรับปรุงพฤติกรรมในเครื่องแล้ว ให้ถือว่าเป็น**สิ่งที่เปลี่ยนไม่ได้** ก่อนพอร์ต ให้จดบันทึก\nการปรับปรุงและเพิ่มการตรวจสอบอย่างชัดเจนเพื่อไม่ให้สูญหายในการผสาน\n\n- **ตรึงพฤติกรรมที่คาดหวัง**: เพิ่มบันทึก \"ก่อน/หลัง\" สั้นๆ สำหรับการปรับปรุงแต่ละอย่าง (inputs, outputs,\n  defaults, edge cases) เพื่อป้องกันการ rollback แบบเงียบ\n- **แมป API เก่า → ใหม่**: หาก upstream เปลี่ยนชื่อแนวคิด (hooks → extensions, custom tools → tools ฯลฯ)\n  ตรวจสอบให้แน่ใจว่าทุก entry point เก่ายังเชื่อมต่ออยู่ flag หรือ export ที่พลาดไปหนึ่งรายการเท่ากับฟังก์ชันที่สูญหาย\n- **ตรวจสอบ exports**: ตรวจสอบ `package.json` `exports`, public types, และ barrel files Upstream ports\n  มักลืม re-export สิ่งที่เพิ่มเข้ามาในเครื่อง\n- **ครอบคลุม non-happy paths**: หากคุณแก้ไข error handling, timeouts, หรือ fallback logic ให้เพิ่ม test หรือ\n  อย่างน้อย manual checklist ที่ทดสอบ paths เหล่านั้น\n- **ตรวจสอบ defaults และลำดับการ merge config**: การปรับปรุงมักอยู่ใน defaults ยืนยันว่า defaults ใหม่\n  ไม่ได้ย้อนกลับ (เช่น ลำดับความสำคัญของ config ใหม่, ฟีเจอร์ที่ปิดการใช้งาน, รายการเครื่องมือ)\n- **ตรวจสอบพฤติกรรม env/shell**: หากคุณแก้ไข execution หรือ sandboxing ให้ตรวจสอบว่า path ใหม่ยังใช้\n  env ที่ sanitized ของคุณ และไม่ได้นำ alias/function overrides กลับมา\n- **รันตัวอย่างที่กำหนดเป้าหมายอีกครั้ง**: เก็บชุด \"known good\" ตัวอย่างขั้นต่ำ และรันหลังจากพอร์ต\n  (CLI flags, extension registration, tool execution)\n\n## 12) ตรวจจับและจัดการโค้ดที่ถูกปรับโครงสร้างใหม่\n\nก่อนพอร์ตไฟล์ ให้ตรวจสอบว่า upstream ได้ refactor อย่างมีนัยสำคัญหรือไม่:\n\n```bash\n# Compare the file you're about to port against what you have locally\ngit diff HEAD upstream/main -- path/to/file.ts\n```\n\nหาก diff แสดงว่าไฟล์ถูก**ปรับโครงสร้างใหม่** (ไม่ใช่แค่แก้ไขเล็กน้อย):\n\n- abstractions ใหม่, แนวคิดที่เปลี่ยนชื่อ, modules ที่รวม, data flow ที่เปลี่ยน\n\nคุณต้อง**อ่าน implementation ใหม่อย่างละเอียด**ก่อนพอร์ต การผสานแบบสุ่มสี่สุ่มห้าของโค้ดที่ปรับโครงสร้างใหม่จะทำให้สูญเสียฟังก์ชันการทำงานเนื่องจาก:\n\nหมายเหตุ: interactive mode ถูกแยกออกเป็น controllers/utils/types เมื่อเร็วๆ นี้ เมื่อ backport การเปลี่ยนแปลงที่เกี่ยวข้อง ให้พอร์ตการอัปเดตเข้าสู่ไฟล์แต่ละไฟล์ที่เราสร้างขึ้น และตรวจสอบให้แน่ใจว่าการเชื่อมต่อของ `interactive-mode.ts` ยังซิงค์อยู่\n\n1. **Defaults เปลี่ยนแบบเงียบ** - ตัวแปรใหม่ `defaultFoo = [a, b]` อาจแทนที่ `getAllFoo()` เก่าที่คืนค่า `[a, b, c, d, e]`\n\n2. **ตัวเลือก API ถูกตัดทิ้ง** - เมื่อระบบรวมกัน (เช่น `hooks` + `customTools` → `extensions`) ตัวเลือกเก่าอาจไม่เชื่อมต่อกับ implementation ใหม่\n\n3. **Code paths เก่าไม่ทำงาน** - แนวคิดที่เปลี่ยนชื่อ (เช่น `hookMessage` → `custom`) ต้องอัปเดตในทุก switch statement, type guard, และ handler — ไม่ใช่แค่ definition\n\n4. **Context/capabilities ลดลง** - APIs เก่าอาจเปิดเผย `{ logger, typebox, pi }` ที่ APIs ใหม่ลืมรวม\n\n### กระบวนการพอร์ตเชิงความหมาย\n\nเมื่อ upstream ปรับโครงสร้างโมดูลใหม่:\n\n1. **อ่าน implementation เก่า** - ทำความเข้าใจว่ามันทำอะไร, รับตัวเลือกอะไร, เปิดเผยอะไร\n\n2. **อ่าน implementation ใหม่** - ทำความเข้าใจ abstractions ใหม่และวิธีที่แมปกับพฤติกรรมเก่า\n\n3. **ตรวจสอบความเท่าเทียมของฟีเจอร์** - สำหรับแต่ละความสามารถในโค้ดเก่า ยืนยันว่าโค้ดใหม่ยังคงรักษาไว้หรือลบออกอย่างชัดเจน\n\n4. **ค้นหาสิ่งที่ตกหล่น** - ค้นหาชื่อ/แนวคิดเก่าที่อาจพลาดไปใน switch statements, handlers, UI components\n\n5. **ทดสอบขอบเขต** - CLI flags, SDK options, event handlers, ค่า default — เหล่านี้คือจุดที่ regression ซ่อนอยู่\n\n### การตรวจสอบด่วน\n\n```bash\n# Find all uses of an old concept that may need updating\nrg \"oldConceptName\" --type ts\n\n# Compare default values between versions\ngit show upstream/main:path/to/file.ts | rg \"default|DEFAULT\"\n\n# Check if all enum/union values have handlers\nrg \"case \\\"\" path/to/file.ts\n```\n\n## 13) รายการตรวจสอบด่วน\n\nใช้เป็นรอบสุดท้ายก่อนที่คุณจะเสร็จ:\n\n- [ ] นามสกุลไฟล์ Import ตามข้อกำหนดของ package ในเครื่อง (ไม่ลบ `.js` แบบหว่านแห)\n- [ ] ไม่มี Node-only APIs ในโค้ดใหม่/ที่พอร์ต\n- [ ] อัปเดต package scopes ทั้งหมดแล้ว\n- [ ] scripts ใน `package.json` ใช้ Bun\n- [ ] Prompts เป็น `.md` text imports (ไม่มี inline prompt strings)\n- [ ] ไม่มี `console.*` ใน coding-agent (ใช้ `logger`)\n- [ ] Assets โหลดผ่านรูปแบบ Bun embed (ไม่มี copy scripts)\n- [ ] Tests หรือ checks รันได้ (หรือระบุชัดเจนว่าติดขัด)\n- [ ] ไม่มี regression ของฟังก์ชันการทำงาน (ดูส่วนที่ 11-12)\n\n## 14) รูปแบบ commit message\n\nเมื่อ commit backport ให้ทำตามรูปแบบของ repo `<type>(scope): <past-tense description>` และเก็บช่วง commit\nไว้ในชื่อเรื่อง\n\n```\nfix(coding-agent): backported pi-mono changes (<from>..<to>)\n\npackages/<package>:\n- <type>: <description>\n- <type>: <description> (#<issue> by @<contributor>)\n\npackages/<other-package>:\n- <type>: <description>\n```\n\n**ตัวอย่าง:**\n\n```\nfix(coding-agent): backported pi-mono changes (9f3eef65f..52532c7c0)\n\npackages/ai:\n- fix: handle \"sensitive\" stop reason from Anthropic API\n- fix: normalize tool call IDs with special characters for Responses API\n- fix: add overflow detection for Bedrock, MiniMax, Kimi providers\n- fix: 429 status is rate limiting, not context overflow\n\npackages/tui:\n- fix: refactored autocomplete state tracking\n- fix: file autocomplete should not trigger on empty text\n- fix: configurable autocomplete max visible items\n- fix: improved table column width calculation with word-aware wrapping\n\npackages/coding-agent:\n- fix: preserve external config.yml edits on save (#1046 by @nicobailonMD)\n- fix: resolve macOS NFD and curly quote variants in file paths\n```\n\n**กฎ:**\n\n- จัดกลุ่มการเปลี่ยนแปลงตาม package\n- ใช้ conventional commit types (`fix`, `feat`, `refactor`, `perf`, `docs`)\n- รวมหมายเลข issue/PR ของ upstream และการระบุตัวผู้มีส่วนร่วมสำหรับ contributions จากภายนอก\n- ช่วง commit ในชื่อเรื่องช่วยติดตามจุดซิงค์\n\n## 15) การแตกต่างโดยตั้งใจ\n\nfork ของเรามีการตัดสินใจทางสถาปัตยกรรมที่แตกต่างจาก upstream **อย่าพอร์ตรูปแบบ upstream เหล่านี้:**\n\n### สถาปัตยกรรม UI\n\n| Upstream                                    | Fork ของเรา                                               | เหตุผล                                                                |\n| ------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------- |\n| `FooterDataProvider` class                  | `StatusLineComponent`                                     | Status line ที่ง่ายกว่าและรวมเข้าด้วยกัน                              |\n| `ctx.ui.setHeader()` / `ctx.ui.setFooter()` | Stub ในโหมดที่ไม่ใช่ TUI                                  | ใช้งานใน TUI, no-op ที่อื่น                                           |\n| `ctx.ui.setEditorComponent()`               | Stub ในโหมดที่ไม่ใช่ TUI                                  | ใช้งานใน TUI, no-op ที่อื่น                                           |\n| `InteractiveModeOptions` options object     | Positional constructor args (options type ยังคง export อยู่) | คง constructor signature ไว้; อัปเดต type เมื่อ upstream เพิ่ม fields |\n\n### การตั้งชื่อ Component\n\n| Upstream                     | Fork ของเรา             |\n| ---------------------------- | ----------------------- |\n| `extension-input.ts`         | `hook-input.ts`         |\n| `extension-selector.ts`      | `hook-selector.ts`      |\n| `ExtensionInputComponent`    | `HookInputComponent`    |\n| `ExtensionSelectorComponent` | `HookSelectorComponent` |\n\n### การตั้งชื่อ API\n\n| Upstream                                 | Fork ของเรา                              | หมายเหตุ                                  |\n| ---------------------------------------- | ---------------------------------------- | ----------------------------------------- |\n| `sessionManager.appendSessionInfo(name)` | `sessionManager.setSessionName(name)`    | เราใช้ `sessionName` ทั่วทั้งระบบ         |\n| `sessionManager.getSessionName()`        | `sessionManager.getSessionName()`        | เหมือนกัน (เรารวมให้ตรงกับ RPC ของ upstream) |\n| `agent.sessionName` / `setSessionName()` | `agent.sessionName` / `setSessionName()` | เหมือนกัน                                 |\n\n### การรวมไฟล์\n\n| Upstream                                           | Fork ของเรา                             | เหตุผล                                  |\n| -------------------------------------------------- | --------------------------------------- | --------------------------------------- |\n| `clipboard.ts` + `clipboard-image.ts` (tool files) | `@f5-sales-demo/pi-natives` clipboard module | รวมเข้าสู่ N-API native implementation |\n\n### Test Framework\n\n| Upstream                  | Fork ของเรา                   |\n| ------------------------- | ----------------------------- |\n| `vitest` กับ `vi.mock()`  | `bun:test` กับ `vi` จาก bun  |\n| `node:test` assertions    | `expect()` matchers           |\n\n### สถาปัตยกรรม Tool\n\n| Upstream                            | Fork ของเรา                                                      | หมายเหตุ                                                  |\n| ----------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------- |\n| `createTool(cwd: string, options?)` | `createTools(session: ToolSession)` ผ่าน `BUILTIN_TOOLS` registry | Tool factories รับ `ToolSession` และสามารถคืนค่า `null` ได้ |\n| Per-tool `*Operations` interfaces   | Per-tool interfaces ยังคงอยู่ (`FindOperations`, `GrepOperations`)  | ใช้สำหรับ SSH/remote overrides                            |\n| Node.js `fs/promises` ทุกที่        | `Bun.file()`/`Bun.write()` สำหรับไฟล์; `node:fs/promises` สำหรับ dirs | ใช้ Bun APIs เมื่อทำให้ง่ายขึ้น                           |\n\n### การเก็บข้อมูล Auth\n\n| Upstream                        | Fork ของเรา                                 | หมายเหตุ                                     |\n| ------------------------------- | ------------------------------------------- | -------------------------------------------- |\n| `proper-lockfile` + `auth.json` | `agent.db` (bun:sqlite)                     | Credentials เก็บเฉพาะใน `agent.db`           |\n| Single credential per provider  | Multi-credential กับ round-robin selection  | รักษา session affinity และ backoff logic ไว้  |\n\n### Extensions\n\n| Upstream                      | Fork ของเรา                                |\n| ----------------------------- | ------------------------------------------ |\n| `jiti` สำหรับโหลด TypeScript  | Native Bun `import()`                      |\n| `pkg.pi` manifest field       | `pkg.xcsh ?? pkg.pi` (ใช้ namespace ของเราก่อน) |\n\n### ข้ามฟีเจอร์ Upstream เหล่านี้\n\nเมื่อพอร์ต ให้**ข้าม**ไฟล์/ฟีเจอร์เหล่านี้ทั้งหมด:\n\n- `footer-data-provider.ts` — เราใช้ StatusLineComponent\n- `clipboard-image.ts` — clipboard อยู่ใน `@f5-sales-demo/pi-natives` N-API module\n- ไฟล์ GitHub workflow — เรามี CI ของตัวเอง\n- `models.generated.ts` — สร้างอัตโนมัติ, สร้างใหม่ในเครื่อง (เป็น models.json แทน)\n\n### ฟีเจอร์ที่เราเพิ่ม (ต้องรักษาไว้)\n\nสิ่งเหล่านี้มีอยู่ใน fork ของเราแต่ไม่มีใน upstream **อย่าเขียนทับ:**\n\n- `StatusLineComponent` ใน interactive mode\n- Multi-credential auth กับ session affinity\n- ระบบค้นหาแบบ capability-based (`defineCapability`, `registerProvider`, `loadCapability`, `skillCapability` ฯลฯ)\n- การรวม MCP/Exa/SSH\n- LSP writethrough สำหรับ format-on-save\n- การดักจับ Bash (`checkBashInterception`)\n- การแนะนำ path แบบ fuzzy ใน read tool\n",
	"th/configuration/rpc.md": "---\ntitle: เอกสารอ้างอิงโปรโตคอล RPC\ndescription: >-\n  เอกสารอ้างอิงโปรโตคอล JSON-RPC สำหรับการสื่อสารระหว่างกระบวนการของส่วนประกอบ\n  xcsh\nsidebar:\n  order: 5\n  label: โปรโตคอล RPC\ni18n:\n  sourceHash: b4a3ddaf08ab\n  translator: machine\n---\n\n# เอกสารอ้างอิงโปรโตคอล RPC\n\nโหมด RPC รันตัวแทนการเขียนโค้ดเป็นโปรโตคอล JSON ที่คั่นด้วยขึ้นบรรทัดใหม่ผ่าน stdio\n\n- **stdin**: คำสั่ง (`RpcCommand`) และการตอบสนองจาก UI ของส่วนขยาย\n- **stdout**: การตอบสนองคำสั่ง (`RpcResponse`), เหตุการณ์ session/agent, คำขอ UI ของส่วนขยาย\n\nการนำไปใช้งานหลัก:\n\n- `src/modes/rpc/rpc-mode.ts`\n- `src/modes/rpc/rpc-types.ts`\n- `src/session/agent-session.ts`\n- `packages/agent/src/agent.ts`\n- `packages/agent/src/agent-loop.ts`\n\n## การเริ่มต้น\n\n```bash\nxcsh --mode rpc [regular CLI options]\n```\n\nหมายเหตุเกี่ยวกับพฤติกรรม:\n\n- อาร์กิวเมนต์ CLI แบบ `@file` จะถูกปฏิเสธในโหมด RPC\n- โหมด RPC ปิดใช้งานการสร้างชื่อ session อัตโนมัติโดยค่าเริ่มต้น เพื่อหลีกเลี่ยงการเรียกโมเดลเพิ่มเติม\n- โหมด RPC รีเซ็ตการตั้งค่า `todo.*`, `task.*` และ `async.*` ที่เปลี่ยนแปลงเวิร์กโฟลว์กลับเป็นค่าเริ่มต้นในตัว แทนที่จะรับค่าที่ผู้ใช้กำหนดเอง\n- กระบวนการอ่าน stdin เป็น JSONL (`readJsonl(Bun.stdin.stream())`)\n- เมื่อ stdin ปิด กระบวนการจะออกด้วยรหัส `0`\n- การตอบสนอง/เหตุการณ์จะถูกเขียนเป็นหนึ่งวัตถุ JSON ต่อบรรทัด\n\n## การขนส่งและการจัดกรอบ\n\nแต่ละเฟรมคือวัตถุ JSON เดี่ยวตามด้วย `\\n`\n\nไม่มีซองจดหมายนอกเหนือจากรูปร่างของวัตถุเอง\n\n### ประเภทเฟรมขาออก (stdout)\n\n1. `RpcResponse` (`{ type: \"response\", ... }`)\n2. วัตถุ `AgentSessionEvent` (`agent_start`, `message_update`, ฯลฯ)\n3. `RpcExtensionUIRequest` (`{ type: \"extension_ui_request\", ... }`)\n4. ข้อผิดพลาดส่วนขยาย (`{ type: \"extension_error\", extensionPath, event, error }`)\n\n### ประเภทเฟรมขาเข้า (stdin)\n\n1. `RpcCommand`\n2. `RpcExtensionUIResponse` (`{ type: \"extension_ui_response\", ... }`)\n\n## การเชื่อมโยงคำขอ/การตอบสนอง\n\nคำสั่งทั้งหมดรับ `id?: string` แบบไม่บังคับ\n\n- ถ้าระบุ การตอบสนองคำสั่งปกติจะสะท้อน `id` เดิมกลับ\n- `RpcClient` ใช้สิ่งนี้สำหรับการแก้ไขคำขอที่รอดำเนินการ\n\nพฤติกรรมขอบจากรันไทม์ที่สำคัญ:\n\n- การตอบสนองคำสั่งที่ไม่รู้จักจะถูกส่งออกพร้อมกับ `id: undefined` (แม้คำขอจะมี `id`)\n- ข้อยกเว้นการแยกวิเคราะห์/ตัวจัดการในลูปอินพุตจะส่งออก `command: \"parse\"` พร้อมกับ `id: undefined`\n- `prompt` และ `abort_and_prompt` จะคืนค่าความสำเร็จทันที จากนั้นอาจส่งการตอบสนองข้อผิดพลาดในภายหลังพร้อมกับ **id เดิม** หากการกำหนดเวลาพรอมต์แบบอะซิงโครนัสล้มเหลว\n\n## สคีมาคำสั่ง (มาตรฐาน)\n\n`RpcCommand` ถูกกำหนดใน `src/modes/rpc/rpc-types.ts`:\n\n### การพรอมต์\n\n- `{ id?, type: \"prompt\", message: string, images?: ImageContent[], streamingBehavior?: \"steer\" | \"followUp\" }`\n- `{ id?, type: \"steer\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"follow_up\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"abort\" }`\n- `{ id?, type: \"abort_and_prompt\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"new_session\", parentSession?: string }`\n\n### สถานะ\n\n- `{ id?, type: \"get_state\" }`\n- `{ id?, type: \"set_todos\", phases: TodoPhase[] }`\n- `{ id?, type: \"set_host_tools\", tools: RpcHostToolDefinition[] }`\n\n### โมเดล\n\n- `{ id?, type: \"set_model\", provider: string, modelId: string }`\n- `{ id?, type: \"cycle_model\" }`\n- `{ id?, type: \"get_available_models\" }`\n\n### การคิด\n\n- `{ id?, type: \"set_thinking_level\", level: ThinkingLevel }`\n- `{ id?, type: \"cycle_thinking_level\" }`\n\n### โหมดคิว\n\n- `{ id?, type: \"set_steering_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_follow_up_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_interrupt_mode\", mode: \"immediate\" | \"wait\" }`\n\n### การบีบอัด\n\n- `{ id?, type: \"compact\", customInstructions?: string }`\n- `{ id?, type: \"set_auto_compaction\", enabled: boolean }`\n\n### การลองใหม่\n\n- `{ id?, type: \"set_auto_retry\", enabled: boolean }`\n- `{ id?, type: \"abort_retry\" }`\n\n### Bash\n\n- `{ id?, type: \"bash\", command: string }`\n- `{ id?, type: \"abort_bash\" }`\n\n### Session\n\n- `{ id?, type: \"get_session_stats\" }`\n- `{ id?, type: \"export_html\", outputPath?: string }`\n- `{ id?, type: \"switch_session\", sessionPath: string }`\n- `{ id?, type: \"branch\", entryId: string }`\n- `{ id?, type: \"get_branch_messages\" }`\n- `{ id?, type: \"get_last_assistant_text\" }`\n- `{ id?, type: \"set_session_name\", name: string }`\n\n### ข้อความ\n\n- `{ id?, type: \"get_messages\" }`\n\n## สคีมาการตอบสนอง\n\nผลลัพธ์คำสั่งทั้งหมดใช้ `RpcResponse`:\n\n- สำเร็จ: `{ id?, type: \"response\", command: <command>, success: true, data?: ... }`\n- ล้มเหลว: `{ id?, type: \"response\", command: string, success: false, error: string }`\n\nเพย์โหลดข้อมูลเป็นแบบเฉพาะคำสั่งและกำหนดไว้ใน `rpc-types.ts`\n\n### เพย์โหลด `get_state`\n\n```json\n{\n  \"model\": { \"provider\": \"...\", \"id\": \"...\" },\n  \"thinkingLevel\": \"off|minimal|low|medium|high|xhigh\",\n  \"isStreaming\": false,\n  \"isCompacting\": false,\n  \"steeringMode\": \"all|one-at-a-time\",\n  \"followUpMode\": \"all|one-at-a-time\",\n  \"interruptMode\": \"immediate|wait\",\n  \"sessionFile\": \"...\",\n  \"sessionId\": \"...\",\n  \"sessionName\": \"...\",\n  \"autoCompactionEnabled\": true,\n  \"messageCount\": 0,\n  \"queuedMessageCount\": 0,\n  \"todoPhases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Todos\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the tool surface\",\n          \"status\": \"in_progress\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n### เพย์โหลด `set_todos`\n\nแทนที่สถานะ todo ในหน่วยความจำสำหรับ session ปัจจุบัน และคืนค่ารายการเฟสที่ผ่านการทำให้เป็นมาตรฐานแล้ว:\n\n```json\n{\n  \"id\": \"req_2\",\n  \"type\": \"set_todos\",\n  \"phases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Evaluation\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the read tool surface\",\n          \"status\": \"in_progress\"\n        },\n        {\n          \"id\": \"task-2\",\n          \"content\": \"Exercise edit operations\",\n          \"status\": \"pending\"\n        }\n      ]\n    }\n  ]\n}\n```\n\nสิ่งนี้มีประโยชน์สำหรับโฮสต์ที่ต้องการกำหนดแผนล่วงหน้าก่อนพรอมต์แรก\n\n### เพย์โหลด `set_host_tools`\n\nแทนที่ชุดเครื่องมือที่เป็นของโฮสต์ปัจจุบันที่เซิร์ฟเวอร์ RPC อาจเรียกกลับผ่าน stdio:\n\n```json\n{\n  \"id\": \"req_3\",\n  \"type\": \"set_host_tools\",\n  \"tools\": [\n    {\n      \"name\": \"echo_host\",\n      \"label\": \"Echo Host\",\n      \"description\": \"Echo a value from the embedding host\",\n      \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"message\": { \"type\": \"string\" }\n        },\n        \"required\": [\"message\"],\n        \"additionalProperties\": false\n      }\n    }\n  ]\n}\n```\n\nเพย์โหลดการตอบสนองคือ:\n\n```json\n{\n  \"toolNames\": [\"echo_host\"]\n}\n```\n\nเครื่องมือเหล่านี้จะถูกเพิ่มในรีจิสทรีเครื่องมือ session ที่ใช้งานอยู่ก่อนการเรียกโมเดลครั้งถัดไป การส่ง `set_host_tools` ซ้ำจะแทนที่ชุดที่เป็นของโฮสต์ก่อนหน้า\n\n## สคีมาสตรีมเหตุการณ์\n\nโหมด RPC ส่งต่อวัตถุ `AgentSessionEvent` จาก `AgentSession.subscribe(...)`\n\nประเภทเหตุการณ์ทั่วไป:\n\n- `agent_start`, `agent_end`\n- `turn_start`, `turn_end`\n- `message_start`, `message_update`, `message_end`\n- `tool_execution_start`, `tool_execution_update`, `tool_execution_end`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n- `todo_auto_clear`\n\nข้อผิดพลาดของตัวรันส่วนขยายจะถูกส่งออกแยกต่างหากเป็น:\n\n```json\n{ \"type\": \"extension_error\", \"extensionPath\": \"...\", \"event\": \"...\", \"error\": \"...\" }\n```\n\n`message_update` รวมเดลต้าสตรีมมิ่งใน `assistantMessageEvent` (เดลต้าข้อความ/การคิด/การเรียกเครื่องมือ)\n\n## ความพร้อมกันและลำดับของพรอมต์/คิว\n\nนี่คือพฤติกรรมการดำเนินงานที่สำคัญที่สุด\n\n### การยืนยันทันทีเทียบกับการเสร็จสิ้น\n\n`prompt` และ `abort_and_prompt` จะถูก **ยืนยันทันที**:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n```\n\nซึ่งหมายความว่า:\n\n- การยอมรับคำสั่ง != การเสร็จสิ้นการรัน\n- การเสร็จสิ้นสุดท้ายสังเกตได้ผ่าน `agent_end`\n\n### ขณะสตรีมมิ่ง\n\n`AgentSession.prompt()` ต้องการ `streamingBehavior` ระหว่างการสตรีมมิ่งที่ใช้งานอยู่:\n\n- `\"steer\"` => ข้อความ steering ที่เข้าคิว (เส้นทางขัดจังหวะ)\n- `\"followUp\"` => ข้อความติดตามที่เข้าคิว (เส้นทางหลังเทิร์น)\n\nหากละเว้นระหว่างสตรีมมิ่ง พรอมต์จะล้มเหลว\n\n### ค่าเริ่มต้นของคิว\n\nจากสคีมาการตั้งค่าตัวแทนการเขียนโค้ด (`packages/coding-agent/src/config/settings-schema.ts`):\n\n- `steeringMode`: `\"one-at-a-time\"`\n- `followUpMode`: `\"one-at-a-time\"`\n- `interruptMode`: `\"wait\"`\n\n### ความหมายของโหมด\n\n- `set_steering_mode` / `set_follow_up_mode`\n  - `\"one-at-a-time\"`: นำข้อความที่เข้าคิวหนึ่งข้อความออกต่อเทิร์น\n  - `\"all\"`: นำคิวทั้งหมดออกพร้อมกัน\n- `set_interrupt_mode`\n  - `\"immediate\"`: การดำเนินการเครื่องมือตรวจสอบ steering ระหว่างการเรียกเครื่องมือ; steering ที่รอดำเนินการสามารถยกเลิกการเรียกเครื่องมือที่เหลือในเทิร์นได้\n  - `\"wait\"`: เลื่อน steering จนกว่าเทิร์นจะเสร็จสิ้น\n\n## โปรโตคอลย่อย Extension UI\n\nส่วนขยายในโหมด RPC ใช้เฟรม UI แบบคำขอ/การตอบสนอง\n\n### คำขอขาออก\n\nเมธอด `RpcExtensionUIRequest` (`type: \"extension_ui_request\"`):\n\n- `select`, `confirm`, `input`, `editor`\n- `notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text`\n\nหมายเหตุรันไทม์:\n\n- การสร้างชื่อ session อัตโนมัติถูกปิดใช้งานในโหมด RPC และคำขอ UI `setTitle`\n  ก็ถูกระงับโดยค่าเริ่มต้นเช่นกัน เนื่องจากโฮสต์ส่วนใหญ่ไม่มีพื้นผิวชื่อเทอร์มินัลที่มีความหมาย ตั้ง `PI_RPC_EMIT_TITLE=1` เพื่อเลือกรับเหตุการณ์ UI กลับ\n\nตัวอย่าง:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"123\", \"method\": \"confirm\", \"title\": \"Confirm\", \"message\": \"Continue?\", \"timeout\": 30000 }\n```\n\n### การตอบสนองขาเข้า\n\n`RpcExtensionUIResponse` (`type: \"extension_ui_response\"`):\n\n- `{ type: \"extension_ui_response\", id: string, value: string }`\n- `{ type: \"extension_ui_response\", id: string, confirmed: boolean }`\n- `{ type: \"extension_ui_response\", id: string, cancelled: true }`\n\nหากไดอะล็อกมีการหมดเวลา โหมด RPC จะแก้ไขเป็นค่าเริ่มต้นเมื่อการหมดเวลา/การยกเลิกเกิดขึ้น\n\n## โปรโตคอลย่อย Host Tool\n\nโฮสต์ RPC สามารถเปิดเผยเครื่องมือที่กำหนดเองให้กับตัวแทนโดยการส่ง `set_host_tools` จากนั้นให้บริการคำขอการดำเนินการผ่านการขนส่งเดิม\n\n### คำขอขาออก\n\nเมื่อตัวแทนต้องการให้โฮสต์ดำเนินการเครื่องมือใดเครื่องมือหนึ่ง โหมด RPC จะส่งออก:\n\n```json\n{\n  \"type\": \"host_tool_call\",\n  \"id\": \"host_1\",\n  \"toolCallId\": \"toolu_123\",\n  \"toolName\": \"echo_host\",\n  \"arguments\": { \"message\": \"hello\" }\n}\n```\n\nหากการดำเนินการเครื่องมือถูกยกเลิกในภายหลัง โหมด RPC จะส่งออก:\n\n```json\n{\n  \"type\": \"host_tool_cancel\",\n  \"id\": \"host_cancel_1\",\n  \"targetId\": \"host_1\"\n}\n```\n\n### การอัปเดตและการเสร็จสิ้นขาเข้า\n\nโฮสต์สามารถสตรีมความคืบหน้าโดยไม่บังคับ:\n\n```json\n{\n  \"type\": \"host_tool_update\",\n  \"id\": \"host_1\",\n  \"partialResult\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"working\" }]\n  }\n}\n```\n\nการเสร็จสิ้นใช้:\n\n```json\n{\n  \"type\": \"host_tool_result\",\n  \"id\": \"host_1\",\n  \"result\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"done\" }]\n  }\n}\n```\n\nตั้งค่า `isError: true` บน `host_tool_result` เพื่อแสดงเนื้อหาที่ส่งคืนเป็นข้อผิดพลาดเครื่องมือ\n\n## โมเดลข้อผิดพลาดและความสามารถในการกู้คืน\n\n### ความล้มเหลวระดับคำสั่ง\n\nความล้มเหลวคือ `success: false` พร้อมสตริง `error`\n\n```json\n{ \"id\": \"req_2\", \"type\": \"response\", \"command\": \"set_model\", \"success\": false, \"error\": \"Model not found: provider/model\" }\n```\n\n### ความคาดหวังเกี่ยวกับความสามารถในการกู้คืน\n\n- ความล้มเหลวของคำสั่งส่วนใหญ่สามารถกู้คืนได้; กระบวนการยังคงทำงานอยู่\n- JSONL ที่ผิดรูปแบบ / ข้อยกเว้นในลูปการแยกวิเคราะห์จะส่งการตอบสนองข้อผิดพลาด `parse` และดำเนินการอ่านบรรทัดถัดไปต่อ\n- `set_session_name` ที่ว่างเปล่าจะถูกปฏิเสธ (`Session name cannot be empty`)\n- การตอบสนอง Extension UI ที่มี `id` ที่ไม่รู้จักจะถูกละเว้น\n- เงื่อนไขการสิ้นสุดกระบวนการคือการปิด stdin หรือการปิดระบบที่ส่วนขยายเริ่มต้น\n\n## โฟลว์คำสั่งแบบย่อ\n\n### 1) พรอมต์และสตรีม\n\nstdin:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"prompt\", \"message\": \"Summarize this repo\" }\n```\n\nลำดับ stdout (โดยทั่วไป):\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n{ \"type\": \"agent_start\" }\n{ \"type\": \"message_update\", \"assistantMessageEvent\": { \"type\": \"text_delta\", \"delta\": \"...\" }, \"message\": { \"role\": \"assistant\", \"content\": [] } }\n{ \"type\": \"agent_end\", \"messages\": [] }\n```\n\n### 2) พรอมต์ระหว่างสตรีมมิ่งพร้อมนโยบายคิวที่ชัดเจน\n\nstdin:\n\n```json\n{ \"id\": \"req_2\", \"type\": \"prompt\", \"message\": \"Also include risks\", \"streamingBehavior\": \"followUp\" }\n```\n\n### 3) ตรวจสอบและปรับพฤติกรรมคิว\n\nstdin:\n\n```json\n{ \"id\": \"q1\", \"type\": \"get_state\" }\n{ \"id\": \"q2\", \"type\": \"set_steering_mode\", \"mode\": \"all\" }\n{ \"id\": \"q3\", \"type\": \"set_interrupt_mode\", \"mode\": \"wait\" }\n```\n\n### 4) การส่งข้อมูลไป-กลับของ Extension UI\n\nstdout:\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"ui_7\", \"method\": \"input\", \"title\": \"Branch name\", \"placeholder\": \"feature/...\" }\n```\n\nstdin:\n\n```json\n{ \"type\": \"extension_ui_response\", \"id\": \"ui_7\", \"value\": \"feature/rpc-host\" }\n```\n\n## หมายเหตุเกี่ยวกับตัวช่วย `RpcClient`\n\n`src/modes/rpc/rpc-client.ts` คือ wrapper ที่สะดวก ไม่ใช่นิยามโปรโตคอล\n\nคุณสมบัติของตัวช่วยปัจจุบัน:\n\n- สร้าง `bun <cliPath> --mode rpc`\n- เชื่อมโยงการตอบสนองด้วย id ที่สร้างขึ้น `req_<n>`\n- ส่งต่อเฉพาะประเภท `AgentEvent` ที่รู้จักไปยัง listener\n- รองรับเครื่องมือที่กำหนดเองที่เป็นของโฮสต์ผ่าน `setCustomTools()` และการจัดการอัตโนมัติของ `host_tool_call` / `host_tool_cancel`\n- **ไม่** เปิดเผยเมธอดตัวช่วยสำหรับทุกคำสั่งโปรโตคอล (ตัวอย่างเช่น `set_interrupt_mode` และ `set_session_name` อยู่ในประเภทโปรโตคอลแต่ไม่ได้ถูกห่อเป็นเมธอดที่กำหนดไว้)\n\nใช้เฟรมโปรโตคอลแบบดิบหากคุณต้องการครอบคลุมพื้นผิวทั้งหมด\n",
	"th/configuration/sdk.md": "---\ntitle: SDK\ndescription: SDK สำหรับสร้าง agent และการผสานรวมแบบกำหนดเองบนรันไทม์ coding agent ของ xcsh\nsidebar:\n  order: 6\n  label: SDK\ni18n:\n  sourceHash: 80f3a4374241\n  translator: machine\n---\n\n# SDK\n\nSDK คือพื้นผิวการผสานรวมแบบ in-process สำหรับ `@f5-sales-demo/xcsh`\nใช้งานเมื่อคุณต้องการเข้าถึงสถานะ agent, การสตรีมเหตุการณ์, การเชื่อมต่อเครื่องมือ และการควบคุม session โดยตรงจากกระบวนการ Bun/Node ของคุณเอง\n\nหากคุณต้องการการแยกข้ามภาษา/กระบวนการ ให้ใช้โหมด RPC แทน\n\n## การติดตั้ง\n\n```bash\nbun add @f5-sales-demo/xcsh\n```\n\n## จุดเข้าถึง\n\n`@f5-sales-demo/xcsh` ส่งออก SDK APIs จาก root ของแพ็กเกจ (และยังส่งออกผ่าน `@f5-sales-demo/xcsh/sdk` ด้วย)\n\nการส่งออกหลักสำหรับผู้ฝัง:\n\n- `createAgentSession`\n- `SessionManager`\n- `Settings`\n- `AuthStorage`\n- `ModelRegistry`\n- `discoverAuthStorage`\n- ตัวช่วยค้นหา (`discoverExtensions`, `discoverSkills`, `discoverContextFiles`, `discoverPromptTemplates`, `discoverSlashCommands`, `discoverCustomTSCommands`, `discoverMCPServers`)\n- พื้นผิว tool factory (`createTools`, `BUILTIN_TOOLS`, คลาส tool)\n\n## เริ่มต้นอย่างรวดเร็ว (ค่าเริ่มต้นแบบค้นหาอัตโนมัติ)\n\n```ts\nimport { createAgentSession } from \"@f5-sales-demo/xcsh\";\n\nconst { session, modelFallbackMessage } = await createAgentSession();\n\nif (modelFallbackMessage) {\n process.stderr.write(`${modelFallbackMessage}\\n`);\n}\n\nconst unsubscribe = session.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Summarize this repository in 3 bullets.\");\nunsubscribe();\nawait session.dispose();\n```\n\n## สิ่งที่ `createAgentSession()` ค้นหาโดยค่าเริ่มต้น\n\n`createAgentSession()` ทำตามหลักการ \"ให้ค่าเพื่อแทนที่ ละเว้นเพื่อค้นหา\"\n\nหากละเว้น จะแก้ไขค่าดังนี้:\n\n- `cwd`: `getProjectDir()`\n- `agentDir`: `~/.xcsh/agent` (ผ่าน `getAgentDir()`)\n- `authStorage`: `discoverAuthStorage(agentDir)`\n- `modelRegistry`: `new ModelRegistry(authStorage)` + `await refresh()`\n- `settings`: `await Settings.init({ cwd, agentDir })`\n- `sessionManager`: `SessionManager.create(cwd)` (รองรับไฟล์)\n- skills/context files/prompt templates/slash commands/extensions/custom TS commands\n- เครื่องมือในตัวผ่าน `createTools(...)`\n- เครื่องมือ MCP (เปิดใช้งานโดยค่าเริ่มต้น)\n- การผสานรวม LSP (เปิดใช้งานโดยค่าเริ่มต้น)\n\n### ข้อมูลนำเข้าที่จำเป็นและเป็นทางเลือก\n\nโดยทั่วไปคุณต้องระบุเฉพาะสิ่งที่คุณต้องการควบคุม:\n\n- **ต้องระบุ**: ไม่มีสำหรับ session ขั้นต่ำ\n- **มักระบุอย่างชัดเจน** ในผู้ฝัง:\n    - `sessionManager` (หากคุณต้องการ in-memory หรือตำแหน่งที่กำหนดเอง)\n    - `authStorage` + `modelRegistry` (หากคุณเป็นเจ้าของวงจรชีวิตของ credential/model)\n    - `model` หรือ `modelPattern` (หากการเลือก model แบบกำหนดตายตัวมีความสำคัญ)\n    - `settings` (หากคุณต้องการการกำหนดค่าแบบแยกหรือสำหรับทดสอบ)\n\n## พฤติกรรมของ session manager (แบบถาวร vs in-memory)\n\n`AgentSession` ใช้ `SessionManager` เสมอ; พฤติกรรมขึ้นอยู่กับ factory ที่คุณใช้\n\n### รองรับไฟล์ (ค่าเริ่มต้น)\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.create(process.cwd()),\n});\n\nconsole.log(session.sessionFile); // absolute .jsonl path\n```\n\n- บันทึกการสนทนา/ข้อความ/เดลต้าสถานะไปยังไฟล์ session\n- รองรับเวิร์กโฟลว์การกลับมาใช้ต่อ/เปิด/แสดงรายการ/fork\n- `session.sessionFile` มีการกำหนดค่าไว้\n\n### In-memory\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.inMemory(),\n});\n\nconsole.log(session.sessionFile); // undefined\n```\n\n- ไม่มีการบันทึกลงระบบไฟล์\n- เหมาะสำหรับการทดสอบ, งานชั่วคราว, agent ที่กำหนดขอบเขตตาม request\n- เมธอด session ยังคงทำงานได้ แต่พฤติกรรมเฉพาะการบันทึก (เส้นทางการกลับมาใช้ต่อ/fork จากไฟล์) มีข้อจำกัดตามธรรมชาติ\n\n### ตัวช่วย Resume/open/list\n\n```ts\nimport { SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst recent = await SessionManager.continueRecent(process.cwd());\nconst listed = await SessionManager.list(process.cwd());\nconst opened = listed[0] ? await SessionManager.open(listed[0].path) : null;\n```\n\n## การเชื่อมต่อ model และ auth\n\n`createAgentSession()` ใช้ `ModelRegistry` + `AuthStorage` สำหรับการเลือก model และการแก้ไข API key\n\n### การเชื่อมต่ออย่างชัดเจน\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst available = modelRegistry.getAvailable();\nif (available.length === 0) throw new Error(\"No authenticated models available\");\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n model: available[0],\n thinkingLevel: \"medium\",\n sessionManager: SessionManager.inMemory(),\n});\n```\n\n### ลำดับการเลือกเมื่อละเว้น `model`\n\nเมื่อไม่มีการระบุ `model`/`modelPattern` อย่างชัดเจน:\n\n1. กู้คืน model จาก session ที่มีอยู่ (หากสามารถกู้คืนได้ + key พร้อมใช้งาน)\n2. บทบาท model เริ่มต้นจากการตั้งค่า (`default`)\n3. model แรกที่พร้อมใช้งานซึ่งมี auth ที่ถูกต้อง\n\nหากการกู้คืนล้มเหลว `modelFallbackMessage` จะอธิบายการ fallback\n\n### ลำดับความสำคัญของ Auth\n\n`AuthStorage.getApiKey(...)` แก้ไขในลำดับนี้:\n\n1. การแทนที่รันไทม์ (`setRuntimeApiKey`)\n2. ข้อมูลประจำตัวที่จัดเก็บใน `agent.db`\n3. ตัวแปรสภาพแวดล้อมของผู้ให้บริการ\n4. การ fallback ตัวแก้ไข custom-provider (หากกำหนดค่าไว้)\n\n## โมเดลการสมัครรับเหตุการณ์\n\nสมัครรับด้วย `session.subscribe(listener)`; จะคืนฟังก์ชัน unsubscribe\n\n```ts\nconst unsubscribe = session.subscribe(event => {\n switch (event.type) {\n  case \"agent_start\":\n  case \"turn_start\":\n  case \"tool_execution_start\":\n   break;\n  case \"message_update\":\n   if (event.assistantMessageEvent.type === \"text_delta\") {\n    process.stdout.write(event.assistantMessageEvent.delta);\n   }\n   break;\n }\n});\n```\n\n`AgentSessionEvent` ประกอบด้วย `AgentEvent` หลักบวกกับเหตุการณ์ระดับ session:\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n## วงจรชีวิตของ Prompt\n\n`session.prompt(text, options?)` คือจุดเข้าถึงหลัก\n\nพฤติกรรม:\n\n1. การขยายคำสั่ง/template ที่เป็นทางเลือก (คำสั่ง `/`, คำสั่งกำหนดเอง, คำสั่ง slash จากไฟล์, prompt templates)\n2. หากกำลังสตรีมอยู่:\n    - ต้องการ `streamingBehavior: \"steer\" | \"followUp\"`\n    - จัดคิวแทนที่จะทิ้งงาน\n3. หากไม่ได้ใช้งาน:\n    - ตรวจสอบ model + API key\n    - เพิ่มข้อความผู้ใช้\n    - เริ่ม agent turn\n\nAPI ที่เกี่ยวข้อง:\n\n- `sendUserMessage(content, { deliverAs? })`\n- `steer(text, images?)`\n- `followUp(text, images?)`\n- `sendCustomMessage({ customType, content, ... }, { deliverAs?, triggerTurn? })`\n- `abort()`\n\n## การผสานรวมเครื่องมือและส่วนขยาย\n\n### เครื่องมือในตัวและการกรอง\n\n- เครื่องมือในตัวมาจาก `createTools(...)` และ `BUILTIN_TOOLS`\n- `toolNames` ทำหน้าที่เป็น allowlist สำหรับเครื่องมือในตัว\n- `customTools` และเครื่องมือที่ลงทะเบียนผ่านส่วนขยายยังคงรวมอยู่\n- เครื่องมือที่ซ่อนอยู่ (เช่น `submit_result`) ต้องเปิดใช้งานอย่างชัดเจน เว้นแต่จะถูกกำหนดโดย options\n\n```ts\nconst { session } = await createAgentSession({\n toolNames: [\"read\", \"grep\", \"find\", \"write\"],\n requireSubmitResultTool: true,\n});\n```\n\n### ส่วนขยาย\n\n- `extensions`: `ExtensionFactory[]` แบบ inline\n- `additionalExtensionPaths`: โหลดไฟล์ส่วนขยายเพิ่มเติม\n- `disableExtensionDiscovery`: ปิดการสแกนส่วนขยายอัตโนมัติ\n- `preloadedExtensions`: ใช้ชุดส่วนขยายที่โหลดไว้แล้วซ้ำ\n\n### การเปลี่ยนแปลงชุดเครื่องมือรันไทม์\n\n`AgentSession` รองรับการอัปเดตการเปิดใช้งานรันไทม์:\n\n- `getActiveToolNames()`\n- `getAllToolNames()`\n- `setActiveToolsByName(names)`\n- `refreshMCPTools(mcpTools)`\n\nSystem prompt จะถูกสร้างใหม่เพื่อสะท้อนการเปลี่ยนแปลงเครื่องมือที่ใช้งานอยู่\n\n## ตัวช่วยค้นหา\n\nใช้สิ่งเหล่านี้เมื่อคุณต้องการการควบคุมบางส่วนโดยไม่ต้องสร้างตรรกะการค้นหาภายในใหม่:\n\n- `discoverAuthStorage(agentDir?)`\n- `discoverExtensions(cwd?)`\n- `discoverSkills(cwd?, _agentDir?, settings?)`\n- `discoverContextFiles(cwd?, _agentDir?)`\n- `discoverPromptTemplates(cwd?, agentDir?)`\n- `discoverSlashCommands(cwd?)`\n- `discoverCustomTSCommands(cwd?, agentDir?)`\n- `discoverMCPServers(cwd?)`\n- `buildSystemPrompt(options?)`\n\n## ตัวเลือกสำหรับ Subagent\n\nสำหรับผู้บริโภค SDK ที่สร้าง orchestrator (คล้ายกับโฟลว์ตัวดำเนินการงาน):\n\n- `outputSchema`: ส่งความคาดหวังผลลัพธ์แบบมีโครงสร้างเข้าไปใน tool context\n- `requireSubmitResultTool`: บังคับให้รวมเครื่องมือ `submit_result`\n- `taskDepth`: บริบทความลึกของการเรียกซ้ำสำหรับ session งานที่ซ้อนกัน\n- `parentTaskPrefix`: คำนำหน้าการตั้งชื่อ artifact สำหรับผลลัพธ์งานที่ซ้อนกัน\n\nสิ่งเหล่านี้เป็นทางเลือกสำหรับการฝัง single-agent ปกติ\n\n## ค่าที่ส่งคืนของ `createAgentSession()`\n\n```ts\ntype CreateAgentSessionResult = {\n session: AgentSession;\n extensionsResult: LoadExtensionsResult;\n setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;\n mcpManager?: MCPManager;\n modelFallbackMessage?: string;\n lspServers?: Array<{ name: string; status: \"ready\" | \"error\"; fileTypes: string[]; error?: string }>;\n};\n```\n\nใช้ `setToolUIContext(...)` เฉพาะเมื่อผู้ฝังของคุณมีความสามารถด้าน UI ที่เครื่องมือ/ส่วนขยายควรเรียกใช้\n\n## ตัวอย่างการฝังแบบควบคุมขั้นต่ำ\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n Settings,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst settings = Settings.isolated({\n \"compaction.enabled\": true,\n \"retry.enabled\": true,\n});\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n settings,\n sessionManager: SessionManager.inMemory(),\n toolNames: [\"read\", \"grep\", \"find\", \"edit\", \"write\"],\n enableMCP: false,\n enableLsp: true,\n});\n\nsession.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Find all TODO comments in this repo and propose fixes.\");\nawait session.dispose();\n```\n",
	"th/configuration/secrets.md": "---\ntitle: การปกปิดข้อมูลลับ\ndescription: ไปป์ไลน์การปกปิดข้อมูลลับที่ลบค่าที่ละเอียดอ่อนออกจากล็อกเซสชันและเอาต์พุต\nsidebar:\n  order: 3\n  label: ข้อมูลลับ\ni18n:\n  sourceHash: 1d9dc101c614\n  translator: machine\n---\n\n# การปกปิดข้อมูลลับ\n\nป้องกันไม่ให้ค่าที่ละเอียดอ่อน (API key, โทเค็น, รหัสผ่าน) ถูกส่งไปยังผู้ให้บริการ LLM เมื่อเปิดใช้งาน ข้อมูลลับจะถูกแทนที่ด้วยตัวแทนที่กำหนดค่าไว้ล่วงหน้าก่อนออกจากกระบวนการ และได้รับการกู้คืนในอาร์กิวเมนต์ของ tool call ที่โมเดลส่งกลับมา\n\n## การเปิดใช้งาน\n\nเปิดใช้งานโดยค่าเริ่มต้น สลับการใช้งานผ่าน UI `/settings` หรือโดยตรงใน `config.yml`:\n\n```yaml\nsecrets:\n  enabled: false\n```\n\n## วิธีการทำงาน\n\n1. เมื่อเริ่มต้นเซสชัน ข้อมูลลับจะถูกรวบรวมจากสองแหล่ง:\n   - **ตัวแปรสภาพแวดล้อม** ที่ตรงกับรูปแบบข้อมูลลับทั่วไป (`*_KEY`, `*_SECRET`, `*_TOKEN`, `*_PASSWORD`, ฯลฯ) โดยมีค่าที่มีความยาว >= 8 ตัวอักษร\n   - **ไฟล์ `secrets.yml`** (ดูด้านล่าง)\n\n2. ข้อความขาออกไปยัง LLM จะมีค่าข้อมูลลับทั้งหมดถูกแทนที่ด้วยตัวแทน เช่น `<<$env:S0>>`, `<<$env:S1>>`, เป็นต้น\n\n3. อาร์กิวเมนต์ของ tool call ที่โมเดลส่งกลับมาจะถูกตรวจสอบแบบ deep-walk และตัวแทนจะได้รับการกู้คืนเป็นค่าเดิมก่อนการดำเนินการ\n\nสองโหมดควบคุมสิ่งที่เกิดขึ้นกับข้อมูลลับแต่ละรายการ:\n\n| โหมด | พฤติกรรม | กู้คืนได้ |\n|---|---|---|\n| `obfuscate` (ค่าเริ่มต้น) | แทนที่ด้วยตัวแทนที่มีดัชนี `<<$env:SN>>` | ใช่ (ถอดรหัสใน tool args) |\n| `replace` | แทนที่ด้วยสตริงที่กำหนดค่าไว้ล่วงหน้าที่มีความยาวเท่ากัน | ไม่ (ทางเดียว) |\n\n## secrets.yml\n\nกำหนดรายการข้อมูลลับแบบกำหนดเองใน YAML โดยมีการตรวจสอบสองตำแหน่ง:\n\n| ระดับ | เส้นทาง | วัตถุประสงค์ |\n|---|---|---|\n| ส่วนกลาง | `~/.xcsh/agent/secrets.yml` | ข้อมูลลับสำหรับทุกโปรเจกต์ |\n| โปรเจกต์ | `<cwd>/.xcsh/secrets.yml` | ข้อมูลลับเฉพาะโปรเจกต์ |\n\nรายการระดับโปรเจกต์จะแทนที่รายการส่วนกลางที่มี `content` ตรงกัน\n\n### โครงสร้าง\n\nแต่ละรายการในอาร์เรย์มีฟิลด์ดังนี้:\n\n| ฟิลด์ | ประเภท | จำเป็น | คำอธิบาย |\n|---|---|---|---|\n| `type` | `\"plain\"` หรือ `\"regex\"` | ใช่ | กลยุทธ์การจับคู่ |\n| `content` | string | ใช่ | ค่าข้อมูลลับ (plain) หรือรูปแบบ regex (regex) |\n| `mode` | `\"obfuscate\"` หรือ `\"replace\"` | ไม่ | ค่าเริ่มต้น: `\"obfuscate\"` |\n| `replacement` | string | ไม่ | ข้อความแทนที่แบบกำหนดเอง (เฉพาะโหมด replace) |\n| `flags` | string | ไม่ | แฟล็ก regex (เฉพาะประเภท regex) |\n\n### ตัวอย่าง\n\n#### ข้อมูลลับแบบ plain\n\n```yaml\n# ปกปิด API key เฉพาะ (โหมดเริ่มต้น)\n- type: plain\n  content: sk-proj-abc123def456\n\n# แทนที่รหัสผ่านฐานข้อมูลด้วยสตริงคงที่\n- type: plain\n  content: hunter2\n  mode: replace\n  replacement: \"********\"\n```\n\n#### ข้อมูลลับแบบ regex\n\n```yaml\n# ปกปิด key รูปแบบ AWS ใดก็ได้\n- type: regex\n  content: \"AKIA[0-9A-Z]{16}\"\n\n# จับคู่แบบไม่คำนึงถึงตัวพิมพ์เล็กใหญ่พร้อมแฟล็กที่ระบุอย่างชัดเจน\n- type: regex\n  content: \"api[_-]?key\\\\s*=\\\\s*\\\\w+\"\n  flags: \"i\"\n\n# ไวยากรณ์ regex literal (รูปแบบและแฟล็กในสตริงเดียว)\n- type: regex\n  content: \"/bearer\\\\s+[a-zA-Z0-9._~+\\\\/=-]+/i\"\n```\n\nรายการ regex จะสแกนแบบ global เสมอ (แฟล็ก `g` ถูกบังคับใช้โดยอัตโนมัติ) ไวยากรณ์ regex literal `/pattern/flags` รองรับเป็นทางเลือกแทนฟิลด์ `content` + `flags` แยกกัน สแลชที่ใช้ escape ภายในรูปแบบ (`\\\\/`) ได้รับการจัดการอย่างถูกต้อง\n\n#### โหมด replace กับ regex\n\n```yaml\n# แทนที่ connection strings แบบทางเดียว (กู้คืนไม่ได้)\n- type: regex\n  content: \"postgres://[^\\\\s]+\"\n  mode: replace\n  replacement: \"postgres://***\"\n```\n\n## การทำงานร่วมกับการตรวจจับตัวแปรสภาพแวดล้อม\n\nตัวแปรสภาพแวดล้อมจะถูกรวบรวมก่อนเสมอ รายการที่กำหนดในไฟล์จะถูกเพิ่มต่อท้าย ดังนั้นรายการในไฟล์จึงสามารถครอบคลุมข้อมูลลับที่ไม่ได้อยู่ในตัวแปรสภาพแวดล้อม (ไฟล์คอนฟิก, ค่าที่ฝังในโค้ด, ฯลฯ) หากค่าเดียวกันปรากฏในทั้งสองแหล่ง โหมดของรายการในไฟล์จะมีความสำคัญกว่า\n\n## ไฟล์หลัก\n\n- `src/secrets/index.ts` -- การโหลด, การรวม, การรวบรวมตัวแปรสภาพแวดล้อม\n- `src/secrets/obfuscator.ts` -- คลาส `SecretObfuscator`, การสร้างตัวแทน, การปกปิดข้อความ\n- `src/secrets/regex.ts` -- การวิเคราะห์และคอมไพล์ regex literal\n- `src/config/settings-schema.ts` -- คำนิยามการตั้งค่า `secrets.enabled`\n",
	"th/extensions/extension-loading.md": "---\ntitle: การโหลดส่วนขยาย (โมดูล TypeScript/JavaScript)\ndescription: >-\n  ไปป์ไลน์การโหลดโมดูล TypeScript และ JavaScript สำหรับส่วนขยาย พร้อมการแก้ไขพาธ\n  การตรวจสอบ และการแคช\nsidebar:\n  order: 2\n  label: การโหลดส่วนขยาย\ni18n:\n  sourceHash: a8cea231c660\n  translator: machine\n---\n\n# การโหลดส่วนขยาย (โมดูล TypeScript/JavaScript)\n\nเอกสารนี้ครอบคลุมวิธีที่ตัวแทนการเขียนโค้ดค้นพบและโหลด**โมดูลส่วนขยาย** (`.ts`/`.js`) เมื่อเริ่มต้นทำงาน\n\nเอกสารนี้**ไม่ครอบคลุม**ส่วนขยายไฟล์ manifest `gemini-extension.json` (มีเอกสารแยกต่างหาก)\n\n## หน้าที่ของระบบย่อยนี้\n\nการโหลดส่วนขยายจะสร้างรายการไฟล์ entry ของโมดูล นำเข้าแต่ละโมดูลด้วย Bun รันฟังก์ชัน factory และส่งคืน:\n\n- คำจำกัดความส่วนขยายที่โหลดแล้ว\n- ข้อผิดพลาดการโหลดต่อพาธ (โดยไม่หยุดการโหลดทั้งหมด)\n- อ็อบเจกต์ runtime ของส่วนขยายที่ใช้ร่วมกัน ซึ่ง `ExtensionRunner` จะใช้ในภายหลัง\n\n## ไฟล์ implementation หลัก\n\n- `src/extensibility/extensions/loader.ts` — การค้นพบพาธ + การนำเข้า/การรัน\n- `src/extensibility/extensions/index.ts` — การส่งออกสาธารณะ\n- `src/extensibility/extensions/runner.ts` — การรัน runtime/event หลังการโหลด\n- `src/discovery/builtin.ts` — ผู้ให้บริการค้นพบอัตโนมัติแบบ native สำหรับโมดูลส่วนขยาย\n- `src/config/settings.ts` — โหลดการตั้งค่า `extensions` / `disabledExtensions` ที่ผสานแล้ว\n\n---\n\n## ข้อมูลนำเข้าสำหรับการโหลดส่วนขยาย\n\n### 1) โมดูลส่วนขยาย native ที่ค้นพบอัตโนมัติ\n\n`discoverAndLoadExtensions()` จะสอบถามผู้ให้บริการค้นพบก่อนสำหรับรายการที่มีความสามารถ `extension-module` จากนั้นเก็บเฉพาะรายการที่เป็น provider `native`\n\nตำแหน่ง native ที่มีผล:\n\n- โปรเจกต์: `<cwd>/.xcsh/extensions`\n- ผู้ใช้: `~/.xcsh/agent/extensions`\n\nรูทของพาธมาจาก native provider (`SOURCE_PATHS.native`)\n\nหมายเหตุ:\n\n- การค้นพบอัตโนมัติแบบ native ใช้ `.xcsh` เป็นฐาน\n- Legacy `.pi` ยังคงรองรับในคีย์ manifest ของ `package.json` (`pi.extensions`) แต่ไม่ใช่ในฐานะ native root ที่นี่\n\n### 2) พาธที่กำหนดค่าไว้อย่างชัดเจน\n\nหลังการค้นพบอัตโนมัติ พาธที่กำหนดค่าไว้จะถูกผนวกและแก้ไข\n\nแหล่งพาธที่กำหนดค่าไว้ในพาธเริ่มต้น session หลัก (`sdk.ts`):\n\n1. พาธที่ระบุผ่าน CLI (`--extension/-e` และ `--hook` จะถูกถือว่าเป็นพาธส่วนขยายด้วย)\n2. อาร์เรย์ `extensions` ของการตั้งค่า (การตั้งค่าส่วนกลาง + โปรเจกต์ที่ผสานแล้ว)\n\nไฟล์การตั้งค่าส่วนกลาง:\n\n- `~/.xcsh/agent/config.yml` (หรือไดเรกทอรี agent แบบกำหนดเองผ่าน `PI_CODING_AGENT_DIR`)\n\nไฟล์การตั้งค่าโปรเจกต์:\n\n- `<cwd>/.xcsh/settings.json`\n\nตัวอย่าง:\n\n```yaml\n# ~/.xcsh/agent/config.yml\nextensions:\n  - ~/my-exts/safety.ts\n  - ./local/ext-pack\n```\n\n```json\n{\n  \"extensions\": [\"./.xcsh/extensions/my-extra\"]\n}\n```\n\n---\n\n## ตัวควบคุมการเปิดใช้งาน/ปิดใช้งาน\n\n### ปิดใช้งานการค้นพบ\n\n- CLI: `--no-extensions`\n- ตัวเลือก SDK: `disableExtensionDiscovery`\n\nการแบ่งพฤติกรรม:\n\n- SDK: เมื่อ `disableExtensionDiscovery=true` ยังคงโหลด `additionalExtensionPaths` ผ่าน `loadExtensions()`\n- การสร้างพาธของ CLI (`main.ts`) จะล้างพาธส่วนขยาย CLI เมื่อตั้งค่า `--no-extensions` ดังนั้น `-e/--hook` อย่างชัดเจนจะไม่ถูกส่งต่อในโหมดนั้น\n\n### ปิดใช้งานโมดูลส่วนขยายเฉพาะ\n\nการตั้งค่า `disabledExtensions` กรองตามรูปแบบ extension id:\n\n- `extension-module:<derivedName>`\n\n`derivedName` อ้างอิงจากพาธ entry (`getExtensionNameFromPath`) ตัวอย่างเช่น:\n\n- `/x/foo.ts` -> `foo`\n- `/x/bar/index.ts` -> `bar`\n\nตัวอย่าง:\n\n```yaml\ndisabledExtensions:\n  - extension-module:foo\n```\n\n---\n\n## การแก้ไขพาธและ entry\n\n### การทำให้พาธเป็นมาตรฐาน\n\nสำหรับพาธที่กำหนดค่าไว้:\n\n1. ทำให้ unicode spaces เป็นมาตรฐาน\n2. ขยาย `~`\n3. หากเป็นพาธสัมพัทธ์ ให้แก้ไขเทียบกับ `cwd` ปัจจุบัน\n\n### หากพาธที่กำหนดค่าไว้เป็นไฟล์\n\nจะถูกใช้โดยตรงเป็นตัวเลือก module entry\n\n### หากพาธที่กำหนดค่าไว้เป็นไดเรกทอรี\n\nลำดับการแก้ไข:\n\n1. `package.json` ในไดเรกทอรีนั้นที่มี `xcsh.extensions` (หรือ legacy `pi.extensions`) -> ใช้ entries ที่ประกาศไว้\n2. `index.ts`\n3. `index.js`\n4. มิฉะนั้นสแกนหนึ่งระดับเพื่อหา extension entries:\n   - `*.ts` / `*.js` โดยตรง\n   - `index.ts` / `index.js` ใน subdir\n   - `package.json` ใน subdir ที่มี `xcsh.extensions` / `pi.extensions`\n\nกฎและข้อจำกัด:\n\n- ไม่มีการค้นพบแบบ recursive เกินหนึ่งระดับ subdirectory\n- entries ที่ประกาศไว้ใน manifest `extensions` จะถูกแก้ไขเทียบกับไดเรกทอรีแพ็กเกจนั้น\n- entries ที่ประกาศไว้จะถูกรวมเฉพาะเมื่อไฟล์มีอยู่/อนุญาตให้เข้าถึงได้\n- ในคู่ `*/index.{ts,js}` TypeScript จะถูกเลือกมากกว่า JavaScript\n- symlinks ถูกถือว่าเป็นไฟล์/ไดเรกทอรีที่ใช้ได้\n\n### พฤติกรรมการละเว้นแตกต่างกันตามแหล่งที่มา\n\n- การค้นพบอัตโนมัติแบบ native (`discoverExtensionModulePaths` ใน discovery helpers) ใช้ native glob ที่มี `gitignore: true` และ `hidden: false`\n- การสแกนไดเรกทอรีที่กำหนดค่าไว้อย่างชัดเจนใน `loader.ts` ใช้กฎ `readdir` และ**ไม่**ใช้การกรอง gitignore\n\n---\n\n## ลำดับการโหลดและลำดับความสำคัญ\n\n`discoverAndLoadExtensions()` สร้างรายการลำดับเดียวแล้วเรียก `loadExtensions()`\n\nลำดับ:\n\n1. โมดูลที่ค้นพบอัตโนมัติแบบ native\n2. พาธที่กำหนดค่าไว้อย่างชัดเจน (ตามลำดับที่ระบุ)\n\nใน `sdk.ts` ลำดับที่กำหนดค่าไว้คือ:\n\n1. พาธเพิ่มเติมจาก CLI\n2. `extensions` ของการตั้งค่า\n\nการขจัดข้อมูลซ้ำ:\n\n- อ้างอิงจากพาธสัมบูรณ์\n- พาธที่พบก่อนจะชนะ\n- รายการซ้ำที่ตามมาจะถูกละเว้น\n\nผลที่ตามมา: หากโมดูลพาธเดียวกันถูกค้นพบอัตโนมัติและกำหนดค่าไว้อย่างชัดเจน จะถูกโหลดครั้งเดียวที่ตำแหน่งแรก (ขั้นตอนการค้นพบอัตโนมัติ)\n\n---\n\n## การนำเข้าโมดูลและข้อกำหนด factory\n\nแต่ละพาธที่เป็นตัวเลือกจะถูกโหลดด้วย dynamic import:\n\n- `await import(resolvedPath)`\n- factory คือ `module.default ?? module`\n- factory ต้องเป็นฟังก์ชัน (`ExtensionFactory`)\n\nหากการส่งออกไม่ใช่ฟังก์ชัน พาธนั้นจะล้มเหลวพร้อมข้อผิดพลาดที่มีโครงสร้าง และการโหลดจะดำเนินต่อไป\n\n---\n\n## การจัดการความล้มเหลวและการแยกส่วน\n\n### ระหว่างการโหลด\n\nต่อพาธส่วนขยาย ความล้มเหลวจะถูกจับเก็บเป็น `{ path, error }` และไม่หยุดไม่ให้พาธอื่นโหลด\n\nกรณีทั่วไป:\n\n- การนำเข้าล้มเหลว / ไม่พบไฟล์\n- การส่งออก factory ไม่ถูกต้อง (ไม่ใช่ฟังก์ชัน)\n- ข้อยกเว้นที่เกิดขึ้นระหว่างการรัน factory\n\n### โมเดลการแยกส่วน runtime\n\n- ส่วนขยาย**ไม่ถูก sandbox** (กระบวนการ/runtime เดียวกัน)\n- ส่วนขยายใช้ `EventBus` หนึ่งตัวและ `ExtensionRuntime` instance หนึ่งตัวร่วมกัน\n- ระหว่างการโหลด เมธอด action ของ runtime จะ throw `ExtensionRuntimeNotInitializedError` โดยตั้งใจ โดย action wiring จะเกิดขึ้นในภายหลังใน `ExtensionRunner.initialize()`\n\n### หลังการโหลด\n\nเมื่อ events ทำงานผ่าน `ExtensionRunner` ข้อยกเว้นของ handler จะถูกจับและส่งออกเป็น extension errors แทนที่จะทำให้ runner loop พัง\n\n---\n\n## ตัวอย่าง layout ผู้ใช้/โปรเจกต์ขั้นต่ำ\n\n### ระดับผู้ใช้\n\n```text\n~/.xcsh/agent/\n  config.yml\n  extensions/\n    guardrails.ts\n    audit/\n      index.ts\n```\n\n### ระดับโปรเจกต์\n\n```text\n<repo>/\n  .xcsh/\n    settings.json\n    extensions/\n      checks/\n        package.json\n      lint-gates.ts\n```\n\n`checks/package.json`:\n\n```json\n{\n  \"xcsh\": {\n    \"extensions\": [\"./src/check-a.ts\", \"./src/check-b.js\"]\n  }\n}\n```\n\nคีย์ manifest แบบ legacy ยังคงรองรับ:\n\n```json\n{\n  \"pi\": {\n    \"extensions\": [\"./index.ts\"]\n  }\n}\n```\n",
	"th/extensions/extensions.md": "---\ntitle: ส่วนขยาย\ndescription: >-\n  ภาพรวมรันไทม์ส่วนขยาย ครอบคลุมประเภท วงจรชีวิตของ runner การลงทะเบียน\n  และการค้นพบ\nsidebar:\n  order: 1\n  label: ภาพรวม\ni18n:\n  sourceHash: 14cc16dbd98b\n  translator: machine\n---\n\n# ส่วนขยาย\n\nคู่มือหลักสำหรับการเขียนส่วนขยายรันไทม์ใน `packages/coding-agent`\n\nเอกสารนี้ครอบคลุมรันไทม์ส่วนขยายในปัจจุบันใน:\n\n- `src/extensibility/extensions/types.ts`\n- `src/extensibility/extensions/runner.ts`\n- `src/extensibility/extensions/wrapper.ts`\n- `src/extensibility/extensions/index.ts`\n- `src/modes/controllers/extension-ui-controller.ts`\n\nสำหรับเส้นทางการค้นพบและกฎการโหลดจากระบบไฟล์ โปรดดูที่ `docs/extension-loading.md`\n\n## ส่วนขยายคืออะไร\n\nส่วนขยายคือโมดูล TS/JS ที่ส่งออก factory เริ่มต้น:\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nexport default function myExtension(pi: ExtensionAPI) {\n // register handlers/tools/commands/renderers\n}\n```\n\nส่วนขยายสามารถรวมทุกสิ่งต่อไปนี้ไว้ในโมดูลเดียว:\n\n- ตัวจัดการเหตุการณ์ (`pi.on(...)`)\n- เครื่องมือที่ LLM เรียกใช้ได้ (`pi.registerTool(...)`)\n- คำสั่ง slash (`pi.registerCommand(...)`)\n- ทางลัดแป้นพิมพ์และแฟล็ก\n- การเรนเดอร์ข้อความแบบกำหนดเอง\n- API การฉีดเซสชัน/ข้อความ (`sendMessage`, `sendUserMessage`, `appendEntry`)\n\n## โมเดลรันไทม์\n\n1. ส่วนขยายถูกนำเข้าและฟังก์ชัน factory จะถูกรัน\n2. ในช่วงการโหลดนั้น เมธอดการลงทะเบียนใช้งานได้ แต่เมธอดการดำเนินการรันไทม์ยังไม่ได้รับการเริ่มต้น\n3. `ExtensionRunner.initialize(...)` เชื่อมต่อการดำเนินการ/บริบทสดสำหรับโหมดที่ใช้งานอยู่\n4. เหตุการณ์วงจรชีวิตของเซสชัน/agent/เครื่องมือจะถูกส่งไปยังตัวจัดการ\n5. การดำเนินการเครื่องมือทุกครั้งถูกห่อด้วยการสกัดกั้นส่วนขยาย (`tool_call` / `tool_result`)\n\n```text\nExtension lifecycle (simplified)\n\nload paths\n   │\n   ▼\nimport module + run factory (registration only)\n   │\n   ▼\nExtensionRunner.initialize(mode/session/tool registry)\n   │\n   ├─ emit session/agent events to handlers\n   ├─ wrap tool execution (tool_call/tool_result)\n   └─ expose runtime actions (sendMessage, setActiveTools, ...)\n```\n\nข้อจำกัดสำคัญจาก `loader.ts`:\n\n- การเรียกเมธอดการดำเนินการเช่น `pi.sendMessage()` ระหว่างการโหลดส่วนขยายจะ throw `ExtensionRuntimeNotInitializedError`\n- ลงทะเบียนก่อน แล้วจึงดำเนินการรันไทม์จากเหตุการณ์/คำสั่ง/เครื่องมือ\n\n## เริ่มต้นอย่างรวดเร็ว\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\nimport { Type } from \"@sinclair/typebox\";\n\nexport default function (pi: ExtensionAPI) {\n pi.setLabel(\"Safety + Utilities\");\n\n pi.on(\"session_start\", async (_event, ctx) => {\n  ctx.ui.notify(`Extension loaded in ${ctx.cwd}`, \"info\");\n });\n\n pi.on(\"tool_call\", async (event) => {\n  if (event.toolName === \"bash\" && event.input.command?.includes(\"rm -rf\")) {\n   return { block: true, reason: \"Blocked by extension policy\" };\n  }\n });\n\n pi.registerTool({\n  name: \"hello_extension\",\n  label: \"Hello Extension\",\n  description: \"Return a greeting\",\n  parameters: Type.Object({ name: Type.String() }),\n  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {\n   return {\n    content: [{ type: \"text\", text: `Hello, ${params.name}` }],\n    details: { greeted: params.name },\n   };\n  },\n });\n\n pi.registerCommand(\"hello-ext\", {\n  description: \"Show queue state\",\n  handler: async (_args, ctx) => {\n   ctx.ui.notify(`pending=${ctx.hasPendingMessages()}`, \"info\");\n  },\n });\n}\n```\n\n## พื้นผิว API ของส่วนขยาย\n\n## 1) การลงทะเบียนและการดำเนินการ (`ExtensionAPI`)\n\nเมธอดหลัก:\n\n- `on(event, handler)`\n- `registerTool`, `registerCommand`, `registerShortcut`, `registerFlag`\n- `registerMessageRenderer`\n- `sendMessage`, `sendUserMessage`, `appendEntry`\n- `getActiveTools`, `getAllTools`, `setActiveTools`\n- `getSessionName`, `setSessionName`\n- `setModel`, `getThinkingLevel`, `setThinkingLevel`\n- `registerProvider`\n- `events` (บัสเหตุการณ์ที่ใช้ร่วมกัน)\n\nในโหมดโต้ตอบ ตัวจัดการ `input` จะทำงานก่อนการตรวจสอบชื่ออัตโนมัติสำหรับข้อความแรกในตัวเอง ส่วนขยายที่เรียก `await pi.setSessionName(...)` จาก `input` สามารถตั้งชื่อเซสชันที่บันทึกไว้และป้องกันไม่ให้ชื่อที่สร้างอัตโนมัติโดยค่าเริ่มต้นทำงานสำหรับเซสชันนั้น\n\nนอกจากนี้ยังเปิดเผย:\n\n- `pi.logger`\n- `pi.typebox`\n- `pi.pi` (การส่งออกแพ็กเกจ)\n\n### ความหมายของการส่งข้อความ\n\n`pi.sendMessage(message, options)` รองรับ:\n\n- `deliverAs: \"steer\"` (ค่าเริ่มต้น) — ขัดจังหวะการรันปัจจุบัน\n- `deliverAs: \"followUp\"` — จัดคิวเพื่อรันหลังการรันปัจจุบัน\n- `deliverAs: \"nextTurn\"` — จัดเก็บและฉีดในเทิร์นถัดไปที่ผู้ใช้ป้อน\n- `triggerTurn: true` — เริ่มเทิร์นเมื่อไม่มีการทำงาน (`nextTurn` จะไม่สนใจสิ่งนี้)\n\n`pi.sendUserMessage(content, { deliverAs })` จะผ่านขั้นตอนพร้อมต์เสมอ ขณะสตรีมมิ่งจะจัดคิวเป็น steer/follow-up\n\n## 2) บริบทตัวจัดการ (`ExtensionContext`)\n\nตัวจัดการและ `execute` ของเครื่องมือจะได้รับ `ctx` พร้อม:\n\n- `ui`\n- `hasUI`\n- `cwd`\n- `sessionManager` (อ่านอย่างเดียว)\n- `modelRegistry`, `model`\n- `getContextUsage()`\n- `compact(...)`\n- `isIdle()`, `hasPendingMessages()`, `abort()`\n- `shutdown()`\n- `getSystemPrompt()`\n\n## 3) บริบทคำสั่ง (`ExtensionCommandContext`)\n\nตัวจัดการคำสั่งได้รับเพิ่มเติม:\n\n- `waitForIdle()`\n- `newSession(...)`\n- `switchSession(...)`\n- `branch(entryId)`\n- `navigateTree(targetId, { summarize })`\n- `reload()`\n\nใช้บริบทคำสั่งสำหรับการไหลควบคุมเซสชัน เมธอดเหล่านี้ถูกแยกออกจากตัวจัดการเหตุการณ์ทั่วไปโดยตั้งใจ\n\n## พื้นผิวเหตุการณ์ (ชื่อและพฤติกรรมในปัจจุบัน)\n\nunion เหตุการณ์ canonical และประเภทเพย์โหลดอยู่ใน `types.ts`\n\n### วงจรชีวิตเซสชัน\n\n- `session_start`\n- `session_before_switch` / `session_switch`\n- `session_before_branch` / `session_branch`\n- `session_before_compact` / `session.compacting` / `session_compact`\n- `session_before_tree` / `session_tree`\n- `session_shutdown`\n\nเหตุการณ์ก่อนที่ยกเลิกได้:\n\n- `session_before_switch` → `{ cancel?: boolean }`\n- `session_before_branch` → `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_before_compact` → `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session_before_tree` → `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n\n### วงจรชีวิตพร้อมต์และเทิร์น\n\n- `input`\n- `before_agent_start`\n- `context`\n- `agent_start` / `agent_end`\n- `turn_start` / `turn_end`\n- `message_start` / `message_update` / `message_end`\n\n### วงจรชีวิตเครื่องมือ\n\n- `tool_call` (ก่อนดำเนินการ อาจบล็อก)\n- `tool_result` (หลังดำเนินการ อาจแก้ไขเนื้อหา/รายละเอียด/isError)\n- `tool_execution_start` / `tool_execution_update` / `tool_execution_end` (การสังเกตการณ์)\n\n`tool_result` เป็นแบบ middleware: ตัวจัดการทำงานตามลำดับส่วนขยายและแต่ละตัวจะเห็นการแก้ไขก่อนหน้า\n\n### สัญญาณความน่าเชื่อถือ/รันไทม์\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### การสกัดกั้นคำสั่งผู้ใช้\n\n- `user_bash` (แทนที่ด้วย `{ result }`)\n- `user_python` (แทนที่ด้วย `{ result }`)\n\n### `resources_discover`\n\n`resources_discover` มีอยู่ในประเภทส่วนขยายและ `ExtensionRunner`\nหมายเหตุรันไทม์ปัจจุบัน: `ExtensionRunner.emitResourcesDiscover(...)` ได้รับการนำไปใช้งานแล้ว แต่ไม่มี callsite ของ `AgentSession` ที่เรียกใช้ในโค้ดเบสปัจจุบัน\n\n## รายละเอียดการเขียนเครื่องมือ\n\n`registerTool` ใช้ `ToolDefinition` จาก `types.ts`\n\nลายเซ็น `execute` ปัจจุบัน:\n\n```ts\nexecute(\n toolCallId,\n params,\n signal,\n onUpdate,\n ctx,\n): Promise<AgentToolResult>\n```\n\nเทมเพลต:\n\n```ts\npi.registerTool({\n name: \"my_tool\",\n label: \"My Tool\",\n description: \"...\",\n parameters: Type.Object({}),\n async execute(_id, _params, signal, onUpdate, ctx) {\n  if (signal?.aborted) {\n   return { content: [{ type: \"text\", text: \"Cancelled\" }] };\n  }\n  onUpdate?.({ content: [{ type: \"text\", text: \"Working...\" }] });\n  return { content: [{ type: \"text\", text: \"Done\" }], details: {} };\n },\n onSession(event, ctx) {\n  // reason: start|switch|branch|tree|shutdown\n },\n renderCall(args, theme) {\n  // optional TUI render\n },\n renderResult(result, options, theme, args) {\n  // optional TUI render\n },\n});\n```\n\n`tool_call`/`tool_result` สกัดกั้นเครื่องมือทั้งหมดเมื่อ registry ถูกห่อใน `sdk.ts` รวมถึงเครื่องมือในตัวและเครื่องมือส่วนขยาย/กำหนดเอง\n\n## จุดรวมการทำงานกับ UI\n\n`ctx.ui` ใช้งาน interface `ExtensionUIContext` การรองรับแตกต่างกันตามโหมด\n\n### โหมดโต้ตอบ (`extension-ui-controller.ts`)\n\nรองรับ:\n\n- dialog: `select`, `confirm`, `input`, `editor`\n- การแจ้งเตือน/สถานะ/ข้อความในตัวแก้ไข/การป้อนข้อมูลเทอร์มินัล/overlay แบบกำหนดเอง\n- การแสดงรายการ/โหลดธีมตามชื่อ (`setTheme` รองรับชื่อสตริง)\n- การสลับการขยายเครื่องมือ\n\nเมธอดที่ไม่ทำงานปัจจุบันใน controller นี้:\n\n- `setFooter`\n- `setHeader`\n- `setEditorComponent`\n\nหมายเหตุเพิ่มเติม: `setWidget` ในปัจจุบันจะส่งต่อไปยังข้อความบรรทัดสถานะผ่าน `setHookWidget(...)`\n\n### โหมด RPC (`rpc-mode.ts`)\n\n`ctx.ui` ได้รับการสนับสนุนโดยเหตุการณ์ RPC `extension_ui_request`:\n\n- เมธอด dialog (`select`, `confirm`, `input`, `editor`) จะรอบทริปไปยังการตอบสนองของไคลเอนต์\n- เมธอดแบบ fire-and-forget จะส่งออกคำขอ (`notify`, `setStatus`, `setWidget` สำหรับอาร์เรย์สตริง, `setTitle`, `setEditorText`)\n\nไม่รองรับ/ไม่ทำงานใน RPC implementation:\n\n- `onTerminalInput`\n- `custom`\n- `setFooter`, `setHeader`, `setEditorComponent`\n- `setWorkingMessage`\n- การสลับ/โหลดธีม (`setTheme` จะคืนค่าความล้มเหลว)\n- การควบคุมการขยายเครื่องมือไม่มีผล\n\n### เส้นทาง Print/headless/subagent\n\nเมื่อไม่มีบริบท UI ที่ส่งไปยังการเริ่มต้น runner `ctx.hasUI` จะเป็น `false` และเมธอดจะไม่ทำงาน/คืนค่าเริ่มต้น\n\n### โหมดโต้ตอบเบื้องหลัง\n\nโหมดเบื้องหลังจะติดตั้งออบเจ็กต์บริบท UI แบบไม่โต้ตอบ ใน implementation ปัจจุบัน `ctx.hasUI` อาจยังคงเป็น `true` ในขณะที่ dialog โต้ตอบจะคืนค่าเริ่มต้น/ไม่ทำงาน\n\n## รูปแบบเซสชันและสถานะ\n\nสำหรับสถานะส่วนขยายที่ถาวร:\n\n1. บันทึกด้วย `pi.appendEntry(customType, data)`\n2. สร้างสถานะใหม่จาก `ctx.sessionManager.getBranch()` ใน `session_start`, `session_branch`, `session_tree`\n3. รักษา `details` ของผลลัพธ์เครื่องมือให้มีโครงสร้างเมื่อสถานะควรมองเห็นได้/สร้างใหม่ได้จากประวัติผลลัพธ์เครื่องมือ\n\nตัวอย่างรูปแบบการสร้างใหม่:\n\n```ts\npi.on(\"session_start\", async (_event, ctx) => {\n let latest;\n for (const entry of ctx.sessionManager.getBranch()) {\n  if (entry.type === \"custom\" && entry.customType === \"my-state\") {\n   latest = entry.data;\n  }\n }\n // restore from latest\n});\n```\n\n## จุดขยายการเรนเดอร์\n\n## ตัวเรนเดอร์ข้อความแบบกำหนดเอง\n\n```ts\npi.registerMessageRenderer(\"my-type\", (message, { expanded }, theme) => {\n // return pi-tui Component\n});\n```\n\nใช้โดยการเรนเดอร์โต้ตอบเมื่อแสดงข้อความแบบกำหนดเอง\n\n## ตัวเรนเดอร์ tool call/result\n\nระบุ `renderCall` / `renderResult` ในคำจำกัดความ `registerTool` สำหรับการแสดงผลเครื่องมือแบบกำหนดเองใน TUI\n\n## ข้อจำกัดและกับดัก\n\n- การดำเนินการรันไทม์ไม่พร้อมใช้งานระหว่างการโหลดส่วนขยาย\n- ข้อผิดพลาดของ `tool_call` จะบล็อกการดำเนินการ (fail-closed)\n- ความขัดแย้งของชื่อคำสั่งกับคำสั่งในตัวจะถูกข้ามพร้อมการวินิจฉัย\n- ทางลัดที่สงวนไว้จะถูกละเว้น (`ctrl+c`, `ctrl+d`, `ctrl+z`, `ctrl+k`, `ctrl+p`, `ctrl+l`, `ctrl+o`, `ctrl+t`, `ctrl+g`, `shift+tab`, `shift+ctrl+p`, `alt+enter`, `escape`, `enter`)\n- ถือว่า `ctx.reload()` เป็นการสิ้นสุดสำหรับเฟรมตัวจัดการคำสั่งปัจจุบัน\n\n## ส่วนขยาย vs hooks vs custom-tools\n\nใช้พื้นผิวที่เหมาะสม:\n\n- **ส่วนขยาย** (`src/extensibility/extensions/*`): ระบบรวม (เหตุการณ์ + เครื่องมือ + คำสั่ง + ตัวเรนเดอร์ + การลงทะเบียน provider)\n- **Hooks** (`src/extensibility/hooks/*`): API เหตุการณ์ legacy แยกต่างหาก\n- **Custom-tools** (`src/extensibility/custom-tools/*`): โมดูลที่เน้นเครื่องมือ เมื่อโหลดควบคู่กับส่วนขยายจะถูกปรับและยังคงผ่าน wrapper การสกัดกั้นส่วนขยาย\n\nหากคุณต้องการแพ็กเกจเดียวที่เป็นเจ้าของนโยบาย เครื่องมือ UX ของคำสั่ง และการเรนเดอร์ร่วมกัน ให้ใช้ส่วนขยาย\n",
	"th/extensions/gemini-manifest-extensions.md": "---\ntitle: ส่วนขยาย Gemini Manifest\ndescription: >-\n  รูปแบบส่วนขยาย Gemini manifest สำหรับความเข้ากันได้ข้ามแพลตฟอร์มของ skill และ\n  agent\nsidebar:\n  order: 7\n  label: Gemini manifest\ni18n:\n  sourceHash: 7134165a5f6d\n  translator: machine\n---\n\n# ส่วนขยาย Gemini Manifest (`gemini-extension.json`)\n\nเอกสารนี้ครอบคลุมวิธีที่ coding-agent ค้นพบและแยกวิเคราะห์ส่วนขยาย Gemini-style manifest (`gemini-extension.json`) ลงในความสามารถ `extensions`\n\nเอกสารนี้ **ไม่ครอบคลุม** การโหลดโมดูลส่วนขยาย TypeScript/JavaScript (`extensions/*.ts`, `index.ts`, `package.json xcsh.extensions`) ซึ่งมีเอกสารอยู่ใน `extension-loading.md`\n\n## ไฟล์การดำเนินการ\n\n- [`../src/discovery/gemini.ts`](../../packages/coding-agent/src/discovery/gemini.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/capability/extension.ts`](../../packages/coding-agent/src/capability/extension.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/loader.ts`](../../packages/coding-agent/src/extensibility/extensions/loader.ts)\n\n---\n\n## สิ่งที่ถูกค้นพบ\n\nผู้ให้บริการ Gemini (`id: gemini`, ลำดับความสำคัญ `60`) ลงทะเบียน loader ของ `extensions` ที่สแกนรากสองตำแหน่งที่กำหนดไว้:\n\n- ผู้ใช้: `~/.gemini/extensions`\n- โปรเจกต์: `<cwd>/.gemini/extensions`\n\nการแก้ไขเส้นทางดำเนินการโดยตรงจาก `ctx.home` และ `ctx.cwd` ผ่าน `getUserPath()` / `getProjectPath()`\n\nกฎขอบเขตที่สำคัญ: การค้นหาโปรเจกต์เป็น **เฉพาะ cwd** เท่านั้น ไม่มีการเดินผ่านไดเรกทอรีหลัก\n\n---\n\n## กฎการสแกนไดเรกทอรี\n\nสำหรับแต่ละราก (`~/.gemini/extensions` และ `<cwd>/.gemini/extensions`) การค้นพบจะทำสิ่งต่อไปนี้:\n\n1. `readDirEntries(root)`\n2. เก็บเฉพาะไดเรกทอรีลูกโดยตรง (`entry.isDirectory()`)\n3. สำหรับแต่ละลูก `<name>` พยายามอ่านเฉพาะ:\n   - `<root>/<name>/gemini-extension.json`\n\nไม่มีการสแกนแบบเรียกซ้ำเกินหนึ่งระดับไดเรกทอรี\n\n### ไดเรกทอรีที่ซ่อนอยู่\n\nการค้นพบ Gemini manifest **ไม่กรอง** ชื่อไดเรกทอรีที่ขึ้นต้นด้วยจุด หากมีไดเรกทอรีลูกที่ซ่อนอยู่และมี `gemini-extension.json` อยู่ภายใน ไดเรกทอรีนั้นจะถูกพิจารณา\n\n### ไฟล์ที่หายไป/อ่านไม่ได้\n\nหาก `gemini-extension.json` หายไปหรืออ่านไม่ได้ ไดเรกทอรีนั้นจะถูกข้ามอย่างเงียบๆ (ไม่มีคำเตือน)\n\n---\n\n## รูปแบบ Manifest (ตามที่ดำเนินการ)\n\nประเภทความสามารถกำหนดรูปแบบ manifest ดังนี้:\n\n```ts\ninterface ExtensionManifest {\n name?: string;\n description?: string;\n mcpServers?: Record<string, Omit<MCPServer, \"name\" | \"_source\">>;\n tools?: unknown[];\n context?: unknown;\n}\n```\n\nพฤติกรรมในเวลาค้นพบมีความยืดหยุ่นโดยเจตนา:\n\n- จำเป็นต้องแยกวิเคราะห์ JSON ได้สำเร็จ\n- ไม่มีการตรวจสอบ schema ขณะรันไทม์สำหรับประเภทฟิลด์/เนื้อหาเกินกว่าไวยากรณ์ JSON\n- วัตถุที่แยกวิเคราะห์แล้วจะถูกเก็บไว้เป็น `manifest` บนรายการความสามารถ\n\n### การทำให้ชื่อเป็นมาตรฐาน\n\n`Extension.name` ถูกตั้งค่าเป็น:\n\n1. `manifest.name` หากไม่ใช่ `null`/`undefined`\n2. มิฉะนั้นจะใช้ชื่อไดเรกทอรีส่วนขยาย\n\nไม่มีการบังคับใช้ประเภทสตริงที่นี่\n\n---\n\n## การสร้างรายการความสามารถ\n\nmanifest ที่แยกวิเคราะห์ได้ถูกต้องจะสร้างรายการความสามารถ `Extension` หนึ่งรายการ:\n\n```ts\n{\n name: manifest.name ?? <directory-name>,\n path: <extension-directory>,\n manifest: <parsed-json>,\n level: \"user\" | \"project\",\n _source: {\n  provider: \"gemini\",\n  providerName: \"Gemini CLI\" // แนบโดย capability registry\n  path: <absolute-manifest-path>,\n  level: \"user\" | \"project\"\n }\n}\n```\n\nหมายเหตุ:\n\n- `_source.path` ถูกทำให้เป็นเส้นทางสัมบูรณ์โดย `createSourceMeta()`\n- การตรวจสอบความสามารถระดับ Registry สำหรับ `extensions` จะตรวจสอบเฉพาะการมีอยู่ของ `name` และ `path`\n- รายละเอียดภายใน manifest (`mcpServers`, `tools`, `context`) ไม่ได้รับการตรวจสอบในระหว่างการค้นพบ\n\n---\n\n## การจัดการข้อผิดพลาดและความหมายของคำเตือน\n\n### มีคำเตือน\n\n- JSON ไม่ถูกต้องในไฟล์ manifest:\n  - รูปแบบคำเตือน: `Invalid JSON in <manifestPath>`\n\n### ไม่มีคำเตือน (ข้ามอย่างเงียบๆ)\n\n- ไดเรกทอรี `extensions` หายไป\n- ไดเรกทอรีลูกไม่มี `gemini-extension.json`\n- ไฟล์ manifest อ่านไม่ได้\n- manifest JSON มีไวยากรณ์ถูกต้องแต่มีความหมายที่แปลกหรือไม่สมบูรณ์\n\nซึ่งหมายความว่าการยอมรับความถูกต้องบางส่วน: เฉพาะความล้มเหลวด้านไวยากรณ์ JSON เท่านั้นที่ส่งคำเตือน\n\n---\n\n## ลำดับความสำคัญและการกำจัดซ้ำกับแหล่งอื่น\n\nความสามารถ `extensions` ถูกรวบรวมข้ามผู้ให้บริการโดย capability registry\n\nผู้ให้บริการปัจจุบันสำหรับความสามารถนี้:\n\n- `native` (`packages/coding-agent/src/discovery/builtin.ts`) ลำดับความสำคัญ `100`\n- `gemini` (`packages/coding-agent/src/discovery/gemini.ts`) ลำดับความสำคัญ `60`\n\nคีย์การกำจัดซ้ำคือ `ext.name` (`extensionCapability.key = ext => ext.name`)\n\n### ลำดับความสำคัญข้ามผู้ให้บริการ\n\nผู้ให้บริการที่มีลำดับความสำคัญสูงกว่าจะชนะเมื่อมีชื่อส่วนขยายซ้ำกัน\n\n- หาก `native` และ `gemini` ต่างปล่อยชื่อส่วนขยาย `foo` รายการของ native จะถูกเก็บไว้\n- รายการที่ซ้ำกันซึ่งมีลำดับความสำคัญต่ำกว่าจะถูกเก็บไว้เฉพาะใน `result.all` โดยมี `_shadowed = true`\n\n### ผลกระทบของลำดับภายในผู้ให้บริการ\n\nเนื่องจากการกำจัดซ้ำใช้หลัก \"ที่เห็นก่อนชนะ\" ลำดับรายการภายในผู้ให้บริการจึงมีความสำคัญ\n\n- Gemini loader เพิ่ม **ผู้ใช้ก่อน** จากนั้นจึงเป็น **โปรเจกต์**\n- ดังนั้น ชื่อซ้ำกันระหว่าง `~/.gemini/extensions` และ `<cwd>/.gemini/extensions` จะเก็บรายการของผู้ใช้และซ่อนรายการของโปรเจกต์\n\nในทางกลับกัน ผู้ให้บริการ native สร้างลำดับไดเรกทอรี config แตกต่างออกไป (`project` ก่อน `user` ใน `getConfigDirs()`) ดังนั้นการซ่อนภายในผู้ให้บริการ native จึงเป็นทิศทางตรงกันข้าม\n\n---\n\n## สรุปพฤติกรรม User กับ Project\n\nสำหรับ Gemini manifest โดยเฉพาะ:\n\n- ทั้งรากของผู้ใช้และโปรเจกต์จะถูกสแกนทุกครั้งที่โหลด\n- รากของโปรเจกต์ถูกกำหนดไว้ที่ `<cwd>/.gemini/extensions` (ไม่มีการเดินผ่านไปยังบรรพบุรุษ)\n- ชื่อซ้ำกันภายในแหล่ง Gemini จะแก้ไขโดยใช้ผู้ใช้ก่อน\n- ชื่อซ้ำกันกับผู้ให้บริการที่มีลำดับความสำคัญสูงกว่า (โดยเฉพาะ native) จะแพ้ตามลำดับความสำคัญ\n\n---\n\n## ขอบเขต: เมตาดาต้าการค้นพบ กับ การโหลดส่วนขยายขณะรันไทม์\n\nการค้นพบ `gemini-extension.json` ในปัจจุบันป้อนข้อมูลเมตาดาต้าความสามารถ (รายการ `Extension`) เท่านั้น **ไม่ได้** โหลดโมดูลส่วนขยาย TS/JS ที่สามารถรันได้โดยตรง\n\nการโหลดโมดูลขณะรันไทม์ (`discoverAndLoadExtensions()` / `loadExtensions()`) ใช้ `extension-modules` และเส้นทางที่ระบุอย่างชัดเจน และในปัจจุบันกรองโมดูลที่ค้นพบอัตโนมัติให้เฉพาะผู้ให้บริการ `native` เท่านั้น\n\nผลที่เป็นรูปธรรม:\n\n- ส่วนขยาย Gemini manifest สามารถค้นพบได้ในฐานะระเบียนความสามารถ\n- ส่วนขยายเหล่านั้นไม่ถูกดำเนินการเป็นโมดูลส่วนขยายขณะรันไทม์โดยไปป์ไลน์ extension loader โดยตัวมันเอง\n\nขอบเขตนี้เป็นเจตนาในการดำเนินการปัจจุบัน และอธิบายว่าเหตุใดการค้นพบ manifest และการโหลดโมดูลที่ดำเนินการได้จึงอาจแตกต่างกัน\n",
	"th/extensions/marketplace.md": "---\ntitle: ระบบ Marketplace Plugin\ndescription: >-\n  ระบบ Marketplace Plugin สำหรับค้นหา ติดตั้ง\n  และจัดการคอลเลกชันปลั๊กอินที่คัดสรรมาแล้ว\nsidebar:\n  order: 4\n  label: ตลาดกลาง\ni18n:\n  sourceHash: 71d9f8f93a81\n  translator: machine\n---\n\n# ระบบ Marketplace Plugin\n\nระบบตลาดกลางช่วยให้คุณค้นหา ติดตั้ง และจัดการปลั๊กอินจากแคตตาล็อกที่โฮสต์บน Git ระบบนี้รองรับรูปแบบรีจิสทรีปลั๊กอินของ Claude Code\n\n## เริ่มต้นอย่างรวดเร็ว\n\n```\n/marketplace add anthropics/f5-sales-demo-marketplace\n/marketplace install wordpress.com@f5-sales-demo-marketplace\n```\n\nหรือพิมพ์เพียง `/marketplace` โดยไม่มีอาร์กิวเมนต์เพื่อเปิดเบราว์เซอร์ปลั๊กอินแบบโต้ตอบ\n\n## แนวคิดพื้นฐาน\n\n**ตลาดกลาง** คือ Git repository (หรือไดเรกทอรีในเครื่อง) ที่มีไฟล์แคตตาล็อกอยู่ที่ `.xcsh-plugin/marketplace.json` แคตตาล็อกจะแสดงรายการปลั๊กอินที่มีอยู่พร้อมแหล่งที่มา คำอธิบาย และข้อมูลเมตาดาตา\n\n**ปลั๊กอิน** คือไดเรกทอรีที่บรรจุ skills, commands, hooks, MCP servers หรือ LSP servers ปลั๊กอินถูกระบุด้วย `name@marketplace` (เช่น `code-review@f5-sales-demo-marketplace`)\n\n**ขอบเขต**: ปลั๊กอินสามารถติดตั้งได้สองขอบเขต:\n\n- **user** (ค่าเริ่มต้น) — ใช้งานได้ในทุกโปรเจกต์ เก็บไว้ที่ `~/.xcsh/plugins/installed_plugins.json`\n- **project** — ใช้งานได้เฉพาะในโปรเจกต์ปัจจุบัน เก็บไว้ที่ `.xcsh/installed_plugins.json`\n\nการติดตั้งในขอบเขต project จะบดบังการติดตั้งในขอบเขต user สำหรับปลั๊กอินชื่อเดียวกัน\n\n## คำสั่ง\n\n### โหมดโต้ตอบ\n\n| คำสั่ง | ผลลัพธ์ |\n|---|---|\n| `/marketplace` | เปิดเบราว์เซอร์ปลั๊กอินแบบโต้ตอบ (ติดตั้ง) |\n\n### การจัดการตลาดกลาง\n\n| คำสั่ง | ผลลัพธ์ |\n|---|---|\n| `/marketplace add <source>` | เพิ่มแหล่งตลาดกลาง |\n| `/marketplace remove <name>` | ลบตลาดกลาง |\n| `/marketplace update [name]` | ดึงแคตตาล็อกใหม่ ละเว้นชื่อเพื่ออัปเดตทั้งหมด |\n| `/marketplace list` | แสดงรายการตลาดกลางที่กำหนดค่าไว้ |\n\n### การดำเนินการกับปลั๊กอิน\n\n| คำสั่ง | ผลลัพธ์ |\n|---|---|\n| `/marketplace discover [marketplace]` | เรียกดูปลั๊กอินที่มีอยู่ |\n| `/marketplace install [--force] [--scope user\\|project] name@marketplace` | ติดตั้งปลั๊กอิน |\n| `/marketplace uninstall [--scope user\\|project] name@marketplace` | ถอนการติดตั้งปลั๊กอิน |\n| `/marketplace installed` | แสดงรายการปลั๊กอินตลาดกลางที่ติดตั้งแล้ว |\n| `/marketplace upgrade [--scope user\\|project] [name@marketplace]` | อัปเกรดปลั๊กอินหนึ่งหรือทั้งหมด |\n\n### คำสั่งเทียบเท่าใน CLI\n\nการดำเนินการเดียวกันนี้สามารถใช้ได้จาก command line:\n\n```\nxcsh plugin marketplace add <source>\nxcsh plugin marketplace remove <name>\nxcsh plugin marketplace update [name]\nxcsh plugin marketplace list\nxcsh plugin discover [marketplace]\nxcsh plugin install --scope project name@marketplace\n```\n\n## แหล่งตลาดกลาง\n\nเมื่อคุณรัน `/marketplace add <source>` ระบบจะจำแนกประเภทของแหล่งที่มา:\n\n| รูปแบบแหล่งที่มา | ประเภท | ตัวอย่าง |\n|---|---|---|\n| `owner/repo` | GitHub shorthand | `anthropics/f5-sales-demo-marketplace` |\n| `https://...*.json` | URL แคตตาล็อกโดยตรง | `https://example.com/marketplace.json` |\n| `https://...*.git` หรือ `git@...` | Git repository | `https://github.com/org/repo.git` |\n| `./path` หรือ `~/path` หรือ `/path` | ไดเรกทอรีในเครื่อง | `./my-marketplace` |\n\nระบบจะโคลน repository (หรืออ่านไดเรกทอรีในเครื่อง) ระบุตำแหน่ง `.xcsh-plugin/marketplace.json` ตรวจสอบความถูกต้อง และแคชแคตตาล็อกไว้ในเครื่อง\n\n## รูปแบบแคตตาล็อก (marketplace.json)\n\nแคตตาล็อกของตลาดกลางอยู่ที่ `.xcsh-plugin/marketplace.json` ในโฟลเดอร์ root ของ repository:\n\n```json\n{\n  \"$schema\": \"https://anthropic.com/claude-code/marketplace.schema.json\",\n  \"name\": \"my-marketplace\",\n  \"owner\": {\n    \"name\": \"Your Name\",\n    \"email\": \"you@example.com\"\n  },\n  \"description\": \"A collection of plugins\",\n  \"plugins\": [\n    {\n      \"name\": \"my-plugin\",\n      \"description\": \"What this plugin does\",\n      \"source\": \"./plugins/my-plugin\",\n      \"category\": \"development\",\n      \"homepage\": \"https://github.com/you/my-plugin\"\n    }\n  ]\n}\n```\n\n### ฟิลด์ที่จำเป็น\n\n| ฟิลด์ | คำอธิบาย |\n|---|---|\n| `name` | ชื่อตลาดกลาง ตัวอักษรพิมพ์เล็กและตัวเลข เครื่องหมายขีดกลาง และจุด ต้องเริ่มและจบด้วยตัวอักษรพิมพ์เล็กหรือตัวเลข ความยาวสูงสุด 64 ตัวอักษร |\n| `owner.name` | ชื่อเจ้าของตลาดกลาง |\n| `plugins` | อาร์เรย์ของรายการปลั๊กอิน |\n\n### ฟิลด์รายการปลั๊กอิน\n\n| ฟิลด์ | จำเป็น | คำอธิบาย |\n|---|---|---|\n| `name` | ใช่ | ชื่อปลั๊กอิน (กฎเดียวกับชื่อตลาดกลาง) |\n| `source` | ใช่ | แหล่งที่มาของปลั๊กอิน (ดูด้านล่าง) |\n| `description` | ไม่ | คำอธิบายสั้น ๆ |\n| `version` | ไม่ | สตริงเวอร์ชัน |\n| `author` | ไม่ | `{ name, email? }` |\n| `homepage` | ไม่ | URL |\n| `category` | ไม่ | สตริงหมวดหมู่ (เช่น `development`, `productivity`, `security`) |\n| `tags` | ไม่ | อาร์เรย์ของแท็กสตริง |\n| `strict` | ไม่ | Boolean |\n| `commands` | ไม่ | คำสั่ง slash ที่ให้บริการ |\n| `agents` | ไม่ | Agents ที่ให้บริการ |\n| `hooks` | ไม่ | นิยาม hook |\n| `mcpServers` | ไม่ | นิยาม MCP server |\n| `lspServers` | ไม่ | นิยาม LSP server |\n\n### รูปแบบแหล่งที่มาของปลั๊กอิน\n\nฟิลด์ `source` รองรับหลายรูปแบบ:\n\n**Relative path** (ภายใน marketplace repo):\n\n```json\n\"source\": \"./plugins/my-plugin\"\n```\n\n**Git repository URL**:\n\n```json\n\"source\": {\n  \"source\": \"url\",\n  \"url\": \"https://github.com/org/repo.git\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**GitHub shorthand**:\n\n```json\n\"source\": {\n  \"source\": \"github\",\n  \"repo\": \"org/repo\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Git subdirectory** (monorepo):\n\n```json\n\"source\": {\n  \"source\": \"git-subdir\",\n  \"url\": \"https://github.com/org/monorepo.git\",\n  \"path\": \"plugins/my-plugin\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**npm package**:\n\n```json\n\"source\": {\n  \"source\": \"npm\",\n  \"package\": \"@scope/my-plugin\",\n  \"version\": \"1.0.0\"\n}\n```\n\n## โครงสร้างไฟล์บนดิสก์\n\n```\n~/.xcsh/\n  config/\n    marketplaces.json          # Registry ของตลาดกลางที่เพิ่มไว้\n  plugins/\n    installed_plugins.json     # ปลั๊กอินที่ติดตั้งในขอบเขต user\n    cache/\n      marketplaces/            # แคชแคตตาล็อกตลาดกลาง\n      plugins/                 # แคชไดเรกทอรีปลั๊กอิน\n\n<project>/.xcsh/\n  installed_plugins.json       # ปลั๊กอินที่ติดตั้งในขอบเขต project\n```\n\n## กฎการตั้งชื่อ\n\nชื่อตลาดกลางและปลั๊กอินต้อง:\n\n- เริ่มและจบด้วยตัวอักษรพิมพ์เล็กหรือตัวเลข\n- ประกอบด้วยตัวอักษรพิมพ์เล็ก ตัวเลข เครื่องหมายขีดกลาง และจุดเท่านั้น\n- มีความยาวไม่เกิน 64 ตัวอักษร\n\nPlugin ID (`name@marketplace`) ต้องมีความยาวรวมไม่เกิน 128 ตัวอักษร\n\nตัวอย่างที่ถูกต้อง: `my-plugin`, `code-review`, `wordpress.com`, `ai-firstify`\nตัวอย่างที่ไม่ถูกต้อง: `-bad`, `bad-`, `.bad`, `Bad`, `under_score`\n",
	"th/extensions/plugin-manager-installer-plumbing.md": "---\ntitle: ตัวจัดการปลั๊กอินและการติดตั้งภายใน\ndescription: >-\n  ภายในของตัวจัดการปลั๊กอินที่ครอบคลุมการติดตั้ง การตรวจสอบความถูกต้อง\n  การแก้ไขการพึ่งพา และการจัดการวงจรชีวิต\nsidebar:\n  order: 5\n  label: ตัวจัดการปลั๊กอิน\ni18n:\n  sourceHash: 9c33e5a2c22a\n  translator: machine\n---\n\n# ตัวจัดการปลั๊กอินและการติดตั้งภายใน\n\nเอกสารนี้อธิบายว่าการทำงานของ `xcsh plugin` เปลี่ยนแปลงสถานะปลั๊กอินบนดิสก์อย่างไร และปลั๊กอินที่ติดตั้งแล้วกลายเป็นความสามารถขณะรันไทม์ได้อย่างไร (ปัจจุบันเป็นเครื่องมือ รวมถึงการแก้ไขเส้นทาง hooks/commands ที่พร้อมใช้งาน)\n\n## ขอบเขตและสถาปัตยกรรม\n\nมีการนำไปใช้งานของการจัดการปลั๊กอินสองแบบในโค้ดเบส:\n\n1. **เส้นทางที่ใช้งานโดยคำสั่ง CLI**: `PluginManager` (`src/extensibility/plugins/manager.ts`)\n2. **โมดูลช่วยเหลือรุ่นเก่า**: ฟังก์ชัน installer (`src/extensibility/plugins/installer.ts`)\n\nการเรียกใช้คำสั่ง `xcsh plugin ...` ผ่าน `PluginManager`\n\n`installer.ts` ยังคงบันทึกการตรวจสอบความปลอดภัยที่สำคัญและพฤติกรรมของระบบไฟล์ แต่ไม่ใช่เส้นทางที่ใช้โดย `src/commands/plugin.ts` + `src/cli/plugin-cli.ts`\n\n## วงจรชีวิต: จากการเรียกใช้ CLI ไปสู่ความพร้อมใช้งานขณะรันไทม์\n\n```text\nxcsh plugin <action> ...\n  -> src/commands/plugin.ts\n  -> runPluginCommand(...) in src/cli/plugin-cli.ts\n  -> PluginManager method (install/list/uninstall/link/...) \n  -> mutate ~/.xcsh/plugins/{package.json,node_modules,xcsh-plugins.lock.json}\n  -> runtime discovery: discoverAndLoadCustomTools(...)\n  -> getAllPluginToolPaths(cwd)\n  -> custom tool loader imports tool modules\n```\n\n### จุดเข้าของคำสั่ง\n\n- `src/commands/plugin.ts` กำหนดคำสั่ง/แฟล็ก และส่งต่อไปยัง `runPluginCommand`\n- `src/cli/plugin-cli.ts` แมปคำสั่งย่อยไปยังเมธอดของ `PluginManager`:\n  - `install`, `uninstall`, `list`, `link`, `doctor`, `features`, `config`, `enable`, `disable`\n- ไม่มีการกระทำ `update` อย่างชัดเจน การอัปเดตทำโดยการรัน `install` ซ้ำด้วยสเปคแพ็กเกจ/เวอร์ชันใหม่\n\n## รูปแบบข้อมูลบนดิสก์\n\nสถานะปลั๊กอินส่วนกลางอยู่ภายใต้ `~/.xcsh/plugins`:\n\n- `package.json` — ไฟล์ manifest ของการพึ่งพาที่ใช้โดย `bun install`/`bun uninstall`\n- `node_modules/` — แพ็กเกจปลั๊กอินที่ติดตั้งแล้วหรือ symlinks\n- `xcsh-plugins.lock.json` — สถานะขณะรันไทม์:\n  - เปิด/ปิดใช้งานต่อปลั๊กอิน\n  - ชุดฟีเจอร์ที่เลือกต่อปลั๊กอิน\n  - การตั้งค่าปลั๊กอินที่บันทึกไว้\n\nการแทนที่ระดับโปรเจกต์อยู่ที่:\n\n- `<cwd>/.xcsh/plugin-overrides.json`\n\nการแทนที่เป็นแบบอ่านอย่างเดียวจากมุมมองของ manager/loader (ไม่มีเส้นทางการเขียนที่นี่) และสามารถปิดใช้งานปลั๊กอินหรือแทนที่ฟีเจอร์/การตั้งค่าสำหรับโปรเจกต์นี้\n\n## การแยกวิเคราะห์สเปคปลั๊กอินและการตีความ metadata\n\n## ไวยากรณ์สเปคการติดตั้ง\n\n`parsePluginSpec` (`parser.ts`) รองรับ:\n\n- `pkg` -> `features: null` (พฤติกรรมค่าเริ่มต้น)\n- `pkg[*]` -> เปิดใช้งานทุกฟีเจอร์ใน manifest\n- `pkg[]` -> ไม่เปิดใช้งานฟีเจอร์เสริม\n- `pkg[a,b]` -> เปิดใช้งานฟีเจอร์ที่ระบุชื่อ\n- `@scope/pkg@1.2.3[feat]` -> แพ็กเกจแบบมีขอบเขต + มีเวอร์ชัน พร้อมการเลือกฟีเจอร์อย่างชัดเจน\n\n`extractPackageName` ตัดส่วนต่อท้ายเวอร์ชันออกสำหรับการค้นหาเส้นทางบนดิสก์หลังการติดตั้ง\n\n## แหล่งที่มาของ manifest และฟิลด์ที่จำเป็น\n\nManifest ถูกแก้ไขดังนี้:\n\n1. `package.json.xcsh`\n2. ทางเลือกสำรอง `package.json.pi`\n3. ทางเลือกสำรอง `{ version: package.version }`\n\nผลกระทบ:\n\n- ไม่มีการตรวจสอบ schema อย่างเข้มงวดใน manager/loader\n- แพ็กเกจที่ขาด manifest `xcsh`/`pi` ยังคงสามารถติดตั้งและแสดงรายการได้\n- การโหลดปลั๊กอินขณะรันไทม์ (`getEnabledPlugins`) ข้ามแพ็กเกจที่ไม่มี manifest `xcsh`/`pi`\n- `manifest.version` ถูกเขียนทับเสมอจาก `version` ของแพ็กเกจ\n\nJSON ของ `package.json` ที่มีรูปแบบผิดพลาดเป็นความล้มเหลวร้ายแรงในขณะอ่าน รูปร่าง manifest ที่มีรูปแบบผิดพลาดอาจล้มเหลวในภายหลังเมื่อมีการใช้งานฟิลด์เฉพาะ\n\n## กระบวนการติดตั้ง/อัปเดต (`PluginManager.install`)\n\n1. แยกวิเคราะห์ไวยากรณ์วงเล็บฟีเจอร์จากสเปคการติดตั้ง\n2. ตรวจสอบชื่อแพ็กเกจกับ regex + รายการที่ปฏิเสธอักขระพิเศษของ shell\n3. ตรวจสอบให้แน่ใจว่า `package.json` ของปลั๊กอินมีอยู่ (`xcsh-plugins`, แผนที่การพึ่งพาส่วนตัว)\n4. รัน `bun install <packageSpec>` ใน `~/.xcsh/plugins`\n5. อ่าน `node_modules/<name>/package.json` ของแพ็กเกจที่ติดตั้ง\n6. แก้ไข manifest และคำนวณ `enabledFeatures`:\n   - `[*]`: ทุกฟีเจอร์ที่ประกาศ (หรือ `null` ถ้าไม่มีแผนที่ฟีเจอร์)\n   - `[a,b]`: ตรวจสอบว่าแต่ละฟีเจอร์มีอยู่ในแผนที่ฟีเจอร์ manifest\n   - `[]`: รายการฟีเจอร์ว่าง\n   - สเปคเปล่า: `null` (ใช้นโยบายค่าเริ่มต้นในภายหลังใน loader)\n7. อัปเดต upsert สถานะขณะรันไทม์ใน lockfile: `{ version, enabledFeatures, enabled: true }`\n\n### ความหมายของการอัปเดต\n\nเนื่องจากการอัปเดตขับเคลื่อนโดย install:\n\n- `xcsh plugin install pkg@newVersion` อัปเดตการพึ่งพาและเวอร์ชันใน lockfile\n- การตั้งค่าที่มีอยู่ถูกเก็บรักษาไว้ รายการสถานะถูกเขียนทับสำหรับเวอร์ชัน/ฟีเจอร์/การเปิดใช้งาน\n- ไม่มีตรรกะ \"ตรวจสอบการอัปเดต\" แยกต่างหากหรือการย้ายข้อมูลแบบทรานแซกชัน\n\n## กระบวนการลบ (`PluginManager.uninstall`)\n\n1. ตรวจสอบชื่อแพ็กเกจ\n2. รัน `bun uninstall <name>` ในไดเรกทอรีปลั๊กอิน\n3. ลบสถานะขณะรันไทม์ของปลั๊กอินออกจาก lockfile:\n   - `config.plugins[name]`\n   - `config.settings[name]`\n\nหากคำสั่ง uninstall ล้มเหลว สถานะขณะรันไทม์จะไม่เปลี่ยนแปลง\n\n## กระบวนการแสดงรายการ (`PluginManager.list`)\n\n1. อ่านแผนที่การพึ่งพาปลั๊กอินจาก `~/.xcsh/plugins/package.json`\n2. โหลดการกำหนดค่าขณะรันไทม์ใน lockfile (ไฟล์หายไป -> ค่าเริ่มต้นว่าง)\n3. โหลดการแทนที่ของโปรเจกต์ (`<cwd>/.xcsh/plugin-overrides.json`, ข้อผิดพลาดในการแยกวิเคราะห์/อ่าน -> วัตถุว่างพร้อมคำเตือน)\n4. สำหรับแต่ละการพึ่งพาที่มี package.json ที่แก้ไขได้:\n   - สร้างบันทึก `InstalledPlugin`\n   - รวมสถานะฟีเจอร์/การเปิดใช้งาน:\n     - ฐานจาก lockfile (หรือค่าเริ่มต้น)\n     - การแทนที่ของโปรเจกต์สามารถแทนที่การเลือกฟีเจอร์ได้\n     - รายการ `disabled` ของโปรเจกต์ทำให้ปลั๊กอินถูกมาสก์ว่าปิดใช้งาน\n\nนี่คือสถานะที่มีผลซึ่งใช้โดยเอาต์พุตสถานะ CLI และการทำงานของ settings/features\n\n## กระบวนการเชื่อมโยง (`PluginManager.link`)\n\n`link` รองรับการพัฒนาปลั๊กอินในเครื่องโดยการสร้าง symlink ของแพ็กเกจในเครื่องไปยัง `~/.xcsh/plugins/node_modules/<pkg.name>`\n\nพฤติกรรม:\n\n1. แก้ไข `localPath` กับ cwd ของ manager\n2. ต้องการ `package.json` ในเครื่องและฟิลด์ `name`\n3. ตรวจสอบให้แน่ใจว่าไดเรกทอรีปลั๊กอินมีอยู่\n4. สำหรับชื่อแบบมีขอบเขต สร้างไดเรกทอรีขอบเขต\n5. ลบเส้นทางที่มีอยู่ที่ตำแหน่ง link เป้าหมาย\n6. สร้าง symlink\n7. เพิ่มรายการใน lockfile ขณะรันไทม์โดยเปิดใช้งานพร้อมฟีเจอร์เริ่มต้น (`null`)\n\nข้อควรระวัง: `PluginManager.link` ปัจจุบันไม่บังคับใช้การตรวจสอบขอบเขตเส้นทาง `cwd` ที่มีอยู่ใน `installer.ts` รุ่นเก่า (`normalizedPath.startsWith(normalizedCwd)`) ดังนั้นความน่าเชื่อถือจึงเป็นความรับผิดชอบของผู้เรียกใช้\n\n## การโหลดขณะรันไทม์: จากปลั๊กอินที่ติดตั้งแล้วไปสู่ความสามารถที่เรียกได้\n\n## ประตูการค้นพบ\n\n`getEnabledPlugins(cwd)` (`plugins/loader.ts`) อ่าน:\n\n- manifest การพึ่งพาปลั๊กอิน (`package.json`)\n- สถานะขณะรันไทม์ใน lockfile\n- การแทนที่ของโปรเจกต์ผ่าน `getConfigDirPaths(\"plugin-overrides.json\", { user: false, cwd })`\n\nการกรอง:\n\n- ข้ามถ้าไม่มี package.json ของปลั๊กอิน\n- ข้ามถ้าไม่มี manifest (`xcsh`/`pi`)\n- ข้ามถ้าปิดใช้งานส่วนกลางใน lockfile\n- ข้ามถ้าโปรเจกต์ปิดใช้งาน\n\n## การแก้ไขเส้นทางความสามารถ\n\nสำหรับแต่ละปลั๊กอินที่เปิดใช้งาน:\n\n- `resolvePluginToolPaths(plugin)`\n- `resolvePluginHookPaths(plugin)`\n- `resolvePluginCommandPaths(plugin)`\n\nตัวแก้ไขแต่ละตัวรวมรายการฐานบวกรายการฟีเจอร์:\n\n- รายการฟีเจอร์ที่ชัดเจน -> เฉพาะฟีเจอร์ที่เลือก\n- `enabledFeatures === null` -> เปิดใช้งานฟีเจอร์ที่ทำเครื่องหมาย `default: true`\n\nไฟล์ที่หายไปจะถูกข้ามอย่างเงียบๆ (การตรวจสอบด้วย `existsSync`)\n\n## ความแตกต่างในการเดินสายขณะรันไทม์ปัจจุบัน\n\n- **เครื่องมือถูกเดินสายเข้าสู่รันไทม์ในปัจจุบัน** ผ่าน `discoverAndLoadCustomTools` (`custom-tools/loader.ts`) ซึ่งเรียก `getAllPluginToolPaths(cwd)`\n- เส้นทางถูกลบข้อมูลซ้ำโดยเส้นทางสัมบูรณ์ที่แก้ไขแล้วในการค้นพบเครื่องมือที่กำหนดเอง (ชุด `seen`, เส้นทางแรกชนะ)\n- **ตัวแก้ไข Hooks/commands มีอยู่** และถูก export แล้ว แต่เส้นทางโค้ดนี้ปัจจุบันยังไม่ได้เดินสายเข้าสู่ registry ขณะรันไทม์ในลักษณะเดียวกับที่เครื่องมือถูกเดินสาย\n\n## รายละเอียดการจัดการ Lock/สถานะ\n\n`PluginManager` แคชการกำหนดค่าขณะรันไทม์ในหน่วยความจำต่อ instance (`#runtimeConfig`) และโหลดแบบ lazy ครั้งเดียว\n\nพฤติกรรมการโหลด:\n\n- lockfile หายไป -> `{ plugins: {}, settings: {} }`\n- ความล้มเหลวในการอ่าน/แยกวิเคราะห์ lockfile -> คำเตือน + ค่าเริ่มต้นว่างเช่นกัน\n\nพฤติกรรมการบันทึก:\n\n- เขียน JSON ของ lockfile แบบ pretty-printed เต็มรูปแบบในแต่ละการเปลี่ยนแปลง\n\nไม่มีการล็อคข้ามกระบวนการหรือกลยุทธ์การรวมที่มีอยู่ การเขียนพร้อมกันสามารถเขียนทับซึ่งกันและกันได้\n\n## การตรวจสอบความปลอดภัยและขอบเขตความน่าเชื่อถือ\n\n## การตรวจสอบ input/แพ็กเกจ\n\nเส้นทาง manager ที่ใช้งานบังคับใช้การตรวจสอบชื่อแพ็กเกจ:\n\n- regex สำหรับสเปคแพ็กเกจแบบมีขอบเขต/ไม่มีขอบเขต (พร้อมเวอร์ชันตามต้องการ)\n- รายการที่ปฏิเสธอักขระพิเศษ shell อย่างชัดเจน (`[;&|`$(){}[]<>\\\\]`)\n\nสิ่งนี้จำกัดความเสี่ยงการ inject คำสั่งเมื่อเรียกใช้ `bun install/uninstall`\n\n## ขอบเขตความน่าเชื่อถือของระบบไฟล์\n\n- โค้ดปลั๊กอินทำงานใน process เมื่อโมดูลเครื่องมือที่กำหนดเองถูก import ไม่มี sandboxing\n- เส้นทางสัมพัทธ์ของ manifest ถูกรวมกับไดเรกทอรีแพ็กเกจปลั๊กอินและตรวจสอบเฉพาะการมีอยู่\n- แพ็กเกจปลั๊กอินเองเป็นโค้ดที่น่าเชื่อถือเมื่อติดตั้งแล้ว\n\n## การตรวจสอบเฉพาะ installer รุ่นเก่า\n\n`installer.ts` รวมการตรวจสอบเพิ่มเติมในขณะ link ที่ไม่ได้สะท้อนใน `PluginManager.link`:\n\n- เส้นทางในเครื่องต้องแก้ไขภายใน cwd ของโปรเจกต์\n- การตรวจสอบชื่อแพ็กเกจ/การข้ามผ่านเส้นทางเพิ่มเติมสำหรับการตั้งชื่อ target ของ symlink\n\nเนื่องจาก CLI ใช้ `PluginManager` การตรวจสอบ link ที่เข้มงวดกว่าเหล่านี้จึงไม่ได้อยู่บนเส้นทางหลักในปัจจุบัน\n\n## พฤติกรรมความล้มเหลว ความสำเร็จบางส่วน และการย้อนกลับ\n\nตัวจัดการปลั๊กอินไม่ใช่แบบทรานแซกชัน\n\n| ขั้นตอนการทำงาน | พฤติกรรมเมื่อล้มเหลว | การย้อนกลับ |\n| --- | --- | --- |\n| `bun install` ล้มเหลว | การติดตั้งยกเลิกพร้อม stderr | ไม่มี (ยังไม่มีการเขียนสถานะ) |\n| ติดตั้งสำเร็จ จากนั้นการตรวจสอบ manifest/ฟีเจอร์ล้มเหลว | คำสั่งล้มเหลว | ไม่มีการย้อนกลับการ uninstall การพึ่งพาอาจยังคงอยู่ใน `node_modules`/`package.json` |\n| ติดตั้งสำเร็จ จากนั้นการเขียน lockfile ล้มเหลว | คำสั่งล้มเหลว | ไม่มีการย้อนกลับของแพ็กเกจที่ติดตั้ง |\n| `bun uninstall` สำเร็จ การเขียน lockfile ล้มเหลว | คำสั่งล้มเหลว | แพ็กเกจถูกลบ สถานะขณะรันไทม์ที่ค้างอยู่อาจยังคงอยู่ |\n| `link` ลบ target เก่าแล้ว การสร้าง symlink ล้มเหลว | คำสั่งล้มเหลว | ไม่มีการกู้คืน link/ไดเรกทอรีก่อนหน้า |\n\nในทางปฏิบัติ `doctor --fix` สามารถซ่อมแซมความเบี่ยงเบนบางส่วนได้ (`bun install`, การล้างค่า config ที่ไม่มีเจ้าของ, การล้างฟีเจอร์ที่ไม่ถูกต้อง) แต่เป็นแบบ best-effort\n\n## สรุปพฤติกรรมเมื่อ manifest มีรูปแบบผิดพลาดหรือขาดหายไป\n\n- ฟิลด์ `xcsh`/`pi` หายไป:\n  - ติดตั้ง/แสดงรายการ: ยอมรับได้ (manifest ขั้นต่ำ)\n  - การค้นพบปลั๊กอินที่เปิดใช้งานขณะรันไทม์: ถูกข้ามในฐานะที่ไม่ใช่ปลั๊กอิน\n- ฟีเจอร์ที่ขาดหายไปซึ่งอ้างถึงโดยสเปคการติดตั้งหรือ `features --set/--enable`: ข้อผิดพลาดร้ายแรงพร้อมรายการฟีเจอร์ที่มีอยู่\n- `plugin-overrides.json` ที่ไม่ถูกต้อง: ถูกละเว้นพร้อมการใช้ `{}` สำรองในทั้ง manager และ loader paths\n- เส้นทางไฟล์ tool/hook/command ที่ขาดหายไปซึ่งอ้างถึงโดย manifest: ถูกละเว้นอย่างเงียบๆ ในระหว่างการขยาย resolver ถูกระบุเป็นข้อผิดพลาดเฉพาะโดย `doctor`\n\n## ความแตกต่างของโหมดและลำดับความสำคัญ\n\n- `--dry-run` (install): ส่งคืนผลลัพธ์การติดตั้งสังเคราะห์ ไม่มีการเขียนระบบไฟล์/เครือข่าย/สถานะ\n- `--json`: การจัดรูปแบบเอาต์พุตเท่านั้น ไม่มีการเปลี่ยนแปลงพฤติกรรม\n- การแทนที่ของโปรเจกต์มีลำดับความสำคัญเหนือ lockfile ส่วนกลางเสมอสำหรับมุมมอง feature/settings\n- การเปิดใช้งานที่มีผลคือ `runtimeEnabled && !projectDisabled`\n\n## ไฟล์การนำไปใช้งาน\n\n- [`src/commands/plugin.ts`](../../packages/coding-agent/src/commands/plugin.ts) — การประกาศคำสั่ง CLI และการแมปแฟล็ก\n- [`src/cli/plugin-cli.ts`](../../packages/coding-agent/src/cli/plugin-cli.ts) — การส่งคำสั่ง, handler คำสั่งสำหรับผู้ใช้\n- [`src/extensibility/plugins/manager.ts`](../../packages/coding-agent/src/extensibility/plugins/manager.ts) — การนำไปใช้งาน install/remove/list/link/state/doctor ที่ใช้งาน\n- [`src/extensibility/plugins/installer.ts`](../../packages/coding-agent/src/extensibility/plugins/installer.ts) — helper ของ installer รุ่นเก่าและการตรวจสอบความปลอดภัย link เพิ่มเติม\n- [`src/extensibility/plugins/loader.ts`](../../packages/coding-agent/src/extensibility/plugins/loader.ts) — การค้นพบปลั๊กอินที่เปิดใช้งานและการแก้ไขเส้นทาง tool/hook/command\n- [`src/extensibility/plugins/parser.ts`](../../packages/coding-agent/src/extensibility/plugins/parser.ts) — helper การแยกวิเคราะห์สเปคการติดตั้งและชื่อแพ็กเกจ\n- [`src/extensibility/plugins/types.ts`](../../packages/coding-agent/src/extensibility/plugins/types.ts) — สัญญาประเภท manifest/runtime/override\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts) — การเดินสายขณะรันไทม์สำหรับโมดูลเครื่องมือที่จัดหาโดยปลั๊กอิน\n",
	"th/extensions/rulebook-matching-pipeline.md": "---\ntitle: ไปป์ไลน์การจับคู่ Rulebook\ndescription: >-\n  ไปป์ไลน์การจับคู่ Rulebook\n  สำหรับการเลือกและนำชุดคำสั่งเฉพาะบริบทไปใช้กับเซสชันของ agent\nsidebar:\n  order: 6\n  label: การจับคู่ Rulebook\ni18n:\n  sourceHash: a16a9c565053\n  translator: machine\n---\n\n# ไปป์ไลน์การจับคู่ Rulebook\n\nเอกสารนี้อธิบายว่า coding-agent ค้นพบกฎจากรูปแบบคอนฟิกที่รองรับได้อย่างไร ทำให้กฎเหล่านั้นเป็นรูปแบบ `Rule` เดียวกัน แก้ไขข้อขัดแย้งด้านลำดับความสำคัญ และแบ่งผลลัพธ์ออกเป็น:\n\n- **กฎ Rulebook** (ให้โมเดลใช้ได้ผ่าน system prompt + URL `rule://`)\n- **กฎ TTSR** (กฎการหยุดสตรีมแบบ time-travel)\n\nเอกสารนี้สะท้อนการใช้งานในปัจจุบัน รวมถึง semantics บางส่วนและ metadata ที่ถูกแยกวิเคราะห์แต่ไม่ได้บังคับใช้\n\n## ไฟล์การใช้งาน\n\n- [`../src/capability/rule.ts`](../../packages/coding-agent/src/capability/rule.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/discovery/index.ts`](../../packages/coding-agent/src/discovery/index.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/cursor.ts`](../../packages/coding-agent/src/discovery/cursor.ts)\n- [`../src/discovery/windsurf.ts`](../../packages/coding-agent/src/discovery/windsurf.ts)\n- [`../src/discovery/cline.ts`](../../packages/coding-agent/src/discovery/cline.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/system-prompt.ts`](../../packages/coding-agent/src/system-prompt.ts)\n- [`../src/internal-urls/rule-protocol.ts`](../../packages/coding-agent/src/internal-urls/rule-protocol.ts)\n- [`../src/utils/frontmatter.ts`](../../packages/coding-agent/src/utils/frontmatter.ts)\n\n## 1. รูปร่างกฎตามรูปแบบมาตรฐาน\n\nผู้ให้บริการทั้งหมดทำให้ไฟล์ต้นทางอยู่ในรูปแบบ `Rule`:\n\n```ts\ninterface Rule {\n  name: string;\n  path: string;\n  content: string;\n  globs?: string[];\n  alwaysApply?: boolean;\n  description?: string;\n  ttsrTrigger?: string;\n  _source: SourceMeta;\n}\n```\n\nเอกลักษณ์ของความสามารถคือ `rule.name` (`ruleCapability.key = rule => rule.name`)\n\nผลลัพธ์: ลำดับความสำคัญและการขจัดข้อมูลซ้ำซ้อนเป็นแบบ**อิงตามชื่อเท่านั้น** ไฟล์ที่ต่างกันสองไฟล์ที่มี `name` เหมือนกันจะถือว่าเป็นกฎเชิงตรรกะเดียวกัน\n\n## 2. แหล่งที่มาของการค้นพบและการทำให้เป็นรูปแบบมาตรฐาน\n\n`src/discovery/index.ts` ลงทะเบียนผู้ให้บริการโดยอัตโนมัติ สำหรับ `rules` ผู้ให้บริการในปัจจุบันได้แก่:\n\n- `native` (ลำดับความสำคัญ `100`)\n- `cursor` (ลำดับความสำคัญ `50`)\n- `windsurf` (ลำดับความสำคัญ `50`)\n- `cline` (ลำดับความสำคัญ `40`)\n\n### ผู้ให้บริการ Native (`builtin.ts`)\n\nโหลดกฎ `.xcsh` จาก:\n\n- โปรเจกต์: `<cwd>/.xcsh/rules/*.{md,mdc}`\n- ผู้ใช้: `~/.xcsh/agent/rules/*.{md,mdc}`\n\nการทำให้เป็นรูปแบบมาตรฐาน:\n\n- `name` = ชื่อไฟล์โดยไม่มีนามสกุล `.md`/`.mdc`\n- frontmatter ถูกแยกวิเคราะห์ผ่าน `parseFrontmatter`\n- `content` = เนื้อหา (frontmatter ถูกตัดออก)\n- `globs`, `alwaysApply`, `description`, `ttsr_trigger` ถูก map โดยตรง\n\nข้อควรระวังสำคัญ: `globs` ถูก cast เป็น `string[] | undefined` โดยไม่มีการกรองสมาชิกในผู้ให้บริการนี้\n\n### ผู้ให้บริการ Cursor (`cursor.ts`)\n\nโหลดจาก:\n\n- ผู้ใช้: `~/.cursor/rules/*.{mdc,md}`\n- โปรเจกต์: `<cwd>/.cursor/rules/*.{mdc,md}`\n\nการทำให้เป็นรูปแบบมาตรฐาน (`transformMDCRule`):\n\n- `description`: เก็บไว้เฉพาะเมื่อเป็น string เท่านั้น\n- `alwaysApply`: เก็บเฉพาะค่า `true` เท่านั้น (`false` จะกลายเป็น `undefined`)\n- `globs`: รับ array (เฉพาะสมาชิกที่เป็น string) หรือ string เดี่ยว\n- `ttsr_trigger`: เฉพาะ string เท่านั้น\n- `name` จากชื่อไฟล์โดยไม่มีนามสกุล\n\n### ผู้ให้บริการ Windsurf (`windsurf.ts`)\n\nโหลดจาก:\n\n- ผู้ใช้: `~/.codeium/windsurf/memories/global_rules.md` (ชื่อกฎคงที่ `global_rules`)\n- โปรเจกต์: `<cwd>/.windsurf/rules/*.md`\n\nการทำให้เป็นรูปแบบมาตรฐาน:\n\n- `globs`: array ของ string หรือ string เดี่ยว\n- `alwaysApply`, `description` ถูก cast จาก frontmatter\n- `ttsr_trigger`: เฉพาะ string เท่านั้น\n- `name` จากชื่อไฟล์สำหรับกฎในโปรเจกต์\n\n### ผู้ให้บริการ Cline (`cline.ts`)\n\nค้นหาขึ้นไปจาก `cwd` เพื่อหา `.clinerules` ที่ใกล้ที่สุด:\n\n- หากเป็นไดเรกทอรี: โหลด `*.md` ที่อยู่ข้างใน\n- หากเป็นไฟล์: โหลดไฟล์เดียวเป็นกฎที่ชื่อว่า `clinerules`\n\nการทำให้เป็นรูปแบบมาตรฐาน:\n\n- `globs`: array ของ string หรือ string เดี่ยว\n- `alwaysApply`: เฉพาะเมื่อเป็น boolean เท่านั้น\n- `description`: เฉพาะ string เท่านั้น\n- `ttsr_trigger`: เฉพาะ string เท่านั้น\n\n## 3. พฤติกรรมการแยกวิเคราะห์ Frontmatter และความคลุมเครือ\n\nผู้ให้บริการทั้งหมดใช้ `parseFrontmatter` (`utils/frontmatter.ts`) ด้วย semantics เหล่านี้:\n\n1. Frontmatter จะถูกแยกวิเคราะห์เฉพาะเมื่อเนื้อหาเริ่มต้นด้วย `---` และมีการปิด `\\n---`\n2. เนื้อหาจะถูกตัดช่องว่างหลังจากแยก frontmatter ออก\n3. หาก YAML แยกวิเคราะห์ล้มเหลว:\n   - จะบันทึกคำเตือน\n   - parser จะ fallback ไปใช้การแยกวิเคราะห์แบบ `key: value` บรรทัดต่อบรรทัด (`^(\\w+):\\s*(.*)$`)\n\nผลที่ตามมาของความคลุมเครือ:\n\n- Fallback parser ไม่รองรับ array, nested object, กฎการ quoting หรือ key ที่มีขีดกลาง\n- ค่า fallback จะกลายเป็น string (ตัวอย่างเช่น `alwaysApply: true` จะกลายเป็น string `\"true\"`) ดังนั้นผู้ให้บริการที่ต้องการประเภท boolean/string อาจสูญเสีย metadata\n- `ttsr_trigger` ใช้งานได้ใน fallback (key ที่ใช้ underscore); key เช่น `thinking-level` จะไม่ทำงาน\n- ไฟล์ที่ไม่มี frontmatter ที่ถูกต้องจะยังคงโหลดเป็นกฎโดยมี metadata ว่างเปล่าและเนื้อหาครบถ้วน\n\n## 4. ลำดับความสำคัญของผู้ให้บริการและการขจัดข้อมูลซ้ำซ้อน\n\n`loadCapability(\"rules\")` (`capability/index.ts`) รวมผลลัพธ์จากผู้ให้บริการ จากนั้นขจัดข้อมูลซ้ำซ้อนด้วย `rule.name`\n\n### โมเดลลำดับความสำคัญ\n\n- ผู้ให้บริการถูกเรียงลำดับตาม priority จากมากไปน้อย\n- ลำดับความสำคัญเท่ากันจะรักษาลำดับการลงทะเบียน (`cursor` ก่อน `windsurf` จาก `discovery/index.ts`)\n- การขจัดข้อมูลซ้ำซ้อนแบบ first-wins: ชื่อกฎที่พบก่อนจะถูกเก็บไว้; รายการที่มีชื่อเดียวกันในภายหลังจะถูกทำเครื่องหมาย `_shadowed` ใน `all` และถูกแยกออกจาก `items`\n\nลำดับผู้ให้บริการกฎที่มีผลในปัจจุบันคือ:\n\n1. `native` (100)\n2. `cursor` (50)\n3. `windsurf` (50)\n4. `cline` (40)\n\n### ข้อควรระวังเกี่ยวกับการเรียงลำดับภายในผู้ให้บริการ\n\nภายในผู้ให้บริการ ลำดับรายการมาจากผลลัพธ์การ glob ของ `loadFilesFromDir` บวกกับลำดับการ push ที่ชัดเจน ซึ่งเพียงพอสำหรับการใช้งานปกติแต่ไม่ได้ระบุการเรียงลำดับไว้ชัดเจนในโค้ด\n\nความแตกต่างของลำดับแหล่งที่มาที่สังเกตได้:\n\n- `native` เพิ่มไดเรกทอรีคอนฟิกของโปรเจกต์ก่อน จากนั้นจึงเพิ่มของผู้ใช้\n- `cursor` เพิ่มผลลัพธ์ของผู้ใช้ก่อน จากนั้นจึงเพิ่มของโปรเจกต์\n- `windsurf` เพิ่ม `global_rules` ของผู้ใช้ก่อน จากนั้นจึงเพิ่มกฎของโปรเจกต์\n- `cline` โหลดเฉพาะแหล่ง `.clinerules` ที่ใกล้ที่สุดเท่านั้น\n\n## 5. การแบ่งออกเป็น bucket Rulebook, Always-Apply และ TTSR\n\nหลังจากค้นพบกฎใน `createAgentSession` (`sdk.ts`):\n\n1. กฎที่ค้นพบทั้งหมดจะถูกสแกน\n2. กฎที่มี `condition` (frontmatter key; `ttsr_trigger` / `ttsrTrigger` ถูกรับเป็นค่า fallback) จะถูกลงทะเบียนเข้าสู่ `TtsrManager`\n3. รายการ `rulebookRules` แยกต่างหากจะถูกสร้างขึ้นด้วย predicate นี้:\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && !rule.alwaysApply && !!rule.description\n```\n\n4. รายการ `alwaysApplyRules` จะถูกสร้างขึ้น:\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && rule.alwaysApply === true\n```\n\n### พฤติกรรมของ Bucket\n\n- **TTSR bucket**: กฎใดก็ตามที่มี `condition` (ไม่จำเป็นต้องมี description) มีลำดับความสำคัญเหนือ bucket อื่น\n- **Always-apply bucket**: `alwaysApply === true`, ไม่ใช่ TTSR เนื้อหาทั้งหมดถูก inject เข้าสู่ system prompt สามารถ resolve ผ่าน `rule://` ได้\n- **Rulebook bucket**: ต้องมี description, ต้องไม่ใช่ TTSR, ต้องไม่มี `alwaysApply` ถูกระบุไว้ใน system prompt ด้วยชื่อ+description; เนื้อหาถูกอ่านตามต้องการผ่าน `rule://`\n- กฎที่มีทั้ง `condition` และ `alwaysApply` จะไปอยู่ใน TTSR เท่านั้น (TTSR มีลำดับความสำคัญสูงกว่า)\n- กฎที่มีทั้ง `alwaysApply` และ `description` จะไปอยู่ใน always-apply เท่านั้น (ไม่ใช่ rulebook)\n\n## 6. วิธีที่ metadata ส่งผลต่อพื้นผิว runtime\n\n### `description`\n\n- จำเป็นสำหรับการรวมไว้ใน rulebook\n- แสดงผลใน block `<rules>` ของ system prompt\n- หาก description หายไป กฎจะไม่สามารถใช้งานผ่าน `rule://` และไม่ถูกระบุไว้ใน system prompt rules\n\n### `globs`\n\n- ถูกส่งต่อไปบน `Rule`\n- แสดงผลเป็นรายการ `<glob>...</glob>` ใน block rules ของ system prompt\n- แสดงในสถานะ UI ของกฎ (รายการโหมด `extensions`)\n- **ไม่ได้บังคับใช้สำหรับการจับคู่อัตโนมัติในไปป์ไลน์นี้** ไม่มี runtime glob matcher ที่เลือกกฎตาม file/tool target ในปัจจุบัน\n\n### `alwaysApply`\n\n- ถูกแยกวิเคราะห์และเก็บรักษาโดยผู้ให้บริการ\n- ใช้ในการแสดงผล UI (ป้ายกำกับ trigger `\"always\"` ใน extensions state manager)\n- ใช้เป็นเงื่อนไขการแยกออกจาก `rulebookRules`\n- **เนื้อหากฎทั้งหมดจะถูก inject เข้าสู่ system prompt โดยอัตโนมัติ** (ก่อนส่วน rulebook rules)\n- กฎยังสามารถระบุที่อยู่ได้ผ่าน `rule://<name>` เพื่ออ่านซ้ำ\n\n### `ttsr_trigger`\n\n- ถูก map ไปยัง `rule.ttsrTrigger`\n- หากมีอยู่ กฎจะถูกส่งต่อไปยัง TTSR manager ไม่ใช่ rulebook\n\n## 7. เส้นทางการรวมอยู่ใน System Prompt\n\n`buildSystemPromptInternal` รับทั้ง `rules` (rulebook) และ `alwaysApplyRules`\n\nกฎ Always-apply จะถูกแสดงผลก่อน โดย inject เนื้อหาดิบของกฎเข้าสู่ prompt โดยตรง\n\nกฎ Rulebook จะถูกแสดงผลในส่วน `# Rules` พร้อมด้วย:\n\n- `Read rule://<name> when working in matching domain`\n- `name`, `description` และรายการ `<glob>` ที่ไม่บังคับของแต่ละกฎ\n\nนี่เป็นแนวทางแบบ advisory/contextual: ข้อความ prompt ขอให้โมเดลอ่านกฎที่ใช้งานได้ แต่โค้ดไม่ได้บังคับการใช้งานได้ของ glob\n\n## 8. พฤติกรรม URL ภายใน `rule://`\n\n`RuleProtocolHandler` ถูกลงทะเบียนด้วย:\n\n```ts\nnew RuleProtocolHandler({ getRules: () => [...rulebookRules, ...alwaysApplyRules] })\n```\n\nผลที่ตามมา:\n\n- `rule://<name>` resolve กับทั้ง **rulebookRules** และ **alwaysApplyRules**\n- กฎที่อยู่ใน TTSR เท่านั้น และกฎที่ไม่มี description และไม่มี `alwaysApply` ไม่สามารถระบุที่อยู่ผ่าน `rule://` ได้\n- การ resolution เป็นการจับคู่ชื่อแบบตรงทั้งหมด\n- ชื่อที่ไม่รู้จักจะ return error พร้อมระบุชื่อกฎที่มีอยู่\n- เนื้อหาที่ return คือ `rule.content` ดิบ (frontmatter ถูกตัดออก) ประเภทเนื้อหา `text/markdown`\n\n## 9. Semantics บางส่วน / ที่ไม่ได้บังคับใช้ที่ทราบ\n\n1. คำอธิบายของผู้ให้บริการกล่าวถึงไฟล์ legacy (`.cursorrules`, `.windsurfrules`) แต่เส้นทางโค้ด loader ในปัจจุบันไม่ได้อ่านไฟล์เหล่านั้นจริง\n2. metadata `globs` ถูกนำเสนอไปยัง prompt/UI แต่ไม่ได้บังคับใช้โดย logic การเลือกกฎ\n3. การเลือกกฎสำหรับ `rule://` รวม rulebook และ always-apply rules แต่ไม่รวมกฎที่อยู่ใน TTSR เท่านั้น\n4. คำเตือนการค้นพบ (`loadCapability(\"rules\").warnings`) ถูกสร้างขึ้นแต่ `createAgentSession` ในปัจจุบันไม่ได้ surface/บันทึกคำเตือนเหล่านั้นในเส้นทางนี้\n",
	"th/extensions/skills.md": "---\ntitle: ทักษะ\ndescription: ระบบทักษะสำหรับการลงทะเบียน ค้นพบ และเรียกใช้ความสามารถพิเศษในตัวแทนเขียนโค้ด\nsidebar:\n  order: 3\n  label: ทักษะ\ni18n:\n  sourceHash: 3e062cc13851\n  translator: machine\n---\n\n# ทักษะ\n\nทักษะคือชุดความสามารถที่สนับสนุนด้วยไฟล์ ซึ่งถูกค้นพบเมื่อเริ่มต้นระบบและเปิดเผยต่อโมเดลในรูปแบบ:\n\n- ข้อมูลเมตาแบบเบาในพรอมต์ระบบ (ชื่อ + คำอธิบาย)\n- เนื้อหาตามต้องการผ่าน `read skill://...`\n- คำสั่ง `/skill:<name>` แบบโต้ตอบที่ไม่บังคับ\n\nเอกสารนี้ครอบคลุมพฤติกรรมรันไทม์ปัจจุบันใน `src/extensibility/skills.ts`, `src/discovery/builtin.ts`, `src/internal-urls/skill-protocol.ts` และ `src/discovery/agents-md.ts`\n\n## ทักษะในโค้ดเบสนี้คืออะไร\n\nทักษะที่ถูกค้นพบจะแสดงด้วย:\n\n- `name`\n- `description`\n- `filePath` (เส้นทาง `SKILL.md`)\n- `baseDir` (ไดเรกทอรีทักษะ)\n- ข้อมูลเมตาแหล่งที่มา (`provider`, `level`, path)\n\nรันไทม์ต้องการเพียง `name` และ `path` เพื่อความถูกต้อง ในทางปฏิบัติ คุณภาพการจับคู่ขึ้นอยู่กับ `description` ที่มีความหมาย\n\n## โครงสร้างที่จำเป็นและข้อกำหนดของ SKILL.md\n\n### โครงสร้างไดเรกทอรี\n\nสำหรับการค้นพบที่ใช้ผู้ให้บริการ (native/Claude/Codex/Agents/plugin providers) ทักษะจะถูกค้นพบในระดับ **หนึ่งระดับใต้ `skills/`**:\n\n- `<skills-root>/<skill-name>/SKILL.md`\n\nรูปแบบที่ซ้อนกัน เช่น `<skills-root>/group/<skill>/SKILL.md` จะไม่ถูกค้นพบโดยตัวโหลดผู้ให้บริการ\n\nสำหรับ `skills.customDirectories` การสแกนใช้โครงสร้างแบบไม่เรียกซ้ำเดียวกัน (`*/SKILL.md`)\n\n```text\nProvider-discovered layout (non-recursive under skills/):\n\n<root>/skills/\n  ├─ postgres/\n  │   └─ SKILL.md      ✅ discovered\n  ├─ pdf/\n  │   └─ SKILL.md      ✅ discovered\n  └─ team/\n      └─ internal/\n          └─ SKILL.md  ❌ not discovered by provider loaders\n\nCustom-directory scanning is also non-recursive, so nested paths are ignored unless you point `customDirectories` at that nested parent.\n```\n\n### Frontmatter ของ `SKILL.md`\n\nฟิลด์ frontmatter ที่รองรับบน skill type:\n\n- `name?: string`\n- `description?: string`\n- `globs?: string[]`\n- `alwaysApply?: boolean`\n- คีย์เพิ่มเติมจะถูกเก็บรักษาไว้เป็นข้อมูลเมตาที่ไม่รู้จัก\n\nพฤติกรรมรันไทม์ปัจจุบัน:\n\n- `name` ค่าเริ่มต้นคือชื่อไดเรกทอรีทักษะ\n- `description` จำเป็นสำหรับ:\n  - การค้นพบทักษะผู้ให้บริการ `.xcsh` แบบ native (`requireDescription: true`)\n  - การสแกน `skills.customDirectories` ผ่าน `scanSkillsFromDir` ใน `src/discovery/helpers.ts` (แบบไม่เรียกซ้ำ)\n- ผู้ให้บริการที่ไม่ใช่ native สามารถโหลดทักษะโดยไม่มีคำอธิบายได้\n\n## ไปป์ไลน์การค้นพบ\n\n`discoverSkills()` ใน `src/extensibility/skills.ts` ทำงานสองรอบ:\n\n1. **Capability providers** ผ่าน `loadCapability(\"skills\")`\n2. **Custom directories** ผ่าน `scanSkillsFromDir(..., { requireDescription: true })` (การระบุไดเรกทอรีหนึ่งระดับ)\n\nหาก `skills.enabled` เป็น `false` การค้นพบจะไม่ส่งคืนทักษะใด\n\n### ผู้ให้บริการทักษะในตัวและลำดับความสำคัญ\n\nการเรียงลำดับผู้ให้บริการคือลำดับความสำคัญก่อน (สูงกว่าชนะ) จากนั้นลำดับการลงทะเบียนสำหรับกรณีเสมอกัน\n\nผู้ให้บริการทักษะที่ลงทะเบียนปัจจุบัน:\n\n1. `native` (ลำดับความสำคัญ 100) — ทักษะผู้ใช้/โปรเจกต์ `.xcsh` ผ่าน `src/discovery/builtin.ts`\n2. `claude` (ลำดับความสำคัญ 80)\n3. กลุ่มลำดับความสำคัญ 70 (ตามลำดับการลงทะเบียน):\n   - `claude-plugins`\n   - `agents`\n   - `codex`\n\nคีย์การขจัดซ้ำคือชื่อทักษะ รายการแรกที่มีชื่อที่กำหนดจะชนะ\n\n### การสลับแหล่งที่มาและการกรอง\n\n`discoverSkills()` ใช้การควบคุมเหล่านี้:\n\n- การสลับแหล่งที่มา: `enableCodexUser`, `enableClaudeUser`, `enableClaudeProject`, `enablePiUser`, `enablePiProject`\n- ตัวกรอง glob บนชื่อทักษะ:\n  - `ignoredSkills` (ยกเว้น)\n  - `includeSkills` (รายการที่อนุญาต; ว่างหมายถึงรวมทั้งหมด)\n\nลำดับการกรองคือ:\n\n1. แหล่งที่มาเปิดใช้งาน\n2. ไม่ถูกละเว้น\n3. รวมอยู่ (หากมีรายการที่รวม)\n\nสำหรับผู้ให้บริการอื่นนอกจาก codex/claude/native (เช่น `agents`, `claude-plugins`) การเปิดใช้งานปัจจุบันจะ fallback ไปที่: เปิดใช้งานหาก **ใดก็ตาม** ของการสลับแหล่งที่มาในตัวถูกเปิดใช้งาน\n\n### การจัดการการชนกันและการซ้ำซ้อน\n\n- การขจัดซ้ำของ Capability จะเก็บทักษะแรกต่อชื่อ (ผู้ให้บริการที่มีความสำคัญสูงสุด) ไว้แล้ว\n- `extensibility/skills.ts` เพิ่มเติม:\n  - ขจัดซ้ำไฟล์ที่เหมือนกันโดย `realpath` (ปลอดภัยต่อ symlink)\n  - ส่งคำเตือนการชนกันเมื่อชื่อทักษะที่ตามมาขัดแย้ง\n  - เก็บ API `discoverSkillsFromDir({ dir, source })` ที่สะดวกสบายไว้เป็น adapter บาง ๆ บน `scanSkillsFromDir`\n- ทักษะจาก custom-directory จะถูกรวมหลังจากทักษะของผู้ให้บริการและปฏิบัติตามพฤติกรรมการชนกันเดียวกัน\n\n## พฤติกรรมการใช้งานรันไทม์\n\n### การเปิดเผยพรอมต์ระบบ\n\nการสร้างพรอมต์ระบบ (`src/system-prompt.ts`) ใช้ทักษะที่ค้นพบดังนี้:\n\n- หากมี `read` tool:\n  - รวมรายการทักษะที่ค้นพบไว้ในพรอมต์\n- มิเช่นนั้น:\n  - ละเว้นรายการที่ค้นพบ\n\nsubagents ของ Task tool ได้รับรายการทักษะที่ค้นพบ/ให้มาของเซสชันผ่านการสร้างเซสชันปกติ ไม่มีการ override การปักหมุดทักษะต่องาน\n\n### คำสั่ง `/skill:<name>` แบบโต้ตอบ\n\nหาก `skills.enableSkillCommands` เป็น true โหมดโต้ตอบจะลงทะเบียนหนึ่ง slash command ต่อทักษะที่ค้นพบ\n\nพฤติกรรมของ `/skill:<name> [args]`:\n\n- อ่านไฟล์ทักษะโดยตรงจาก `filePath`\n- ลบ frontmatter ออก\n- ฉีดเนื้อหาทักษะเป็นข้อความกำหนดเองติดตาม\n- เพิ่มข้อมูลเมตา (`Skill: <path>`, `User: <args>` ที่ไม่บังคับ)\n\n## พฤติกรรม URL `skill://`\n\n`src/internal-urls/skill-protocol.ts` รองรับ:\n\n- `skill://<name>` → ไปยัง `SKILL.md` ของทักษะนั้น\n- `skill://<name>/<relative-path>` → ไปยังภายในไดเรกทอรีทักษะนั้น\n\n```text\nskill:// URL resolution\n\nskill://pdf\n  -> <pdf-base>/SKILL.md\n\nskill://pdf/references/tables.md\n  -> <pdf-base>/references/tables.md\n\nGuards:\n- reject absolute paths\n- reject `..` traversal\n- reject any resolved path escaping <pdf-base>\n```\n\nรายละเอียดการแก้ไข:\n\n- ชื่อทักษะต้องตรงกันทุกประการ\n- เส้นทางสัมพัทธ์จะถูก URL-decoded\n- เส้นทางแบบ absolute จะถูกปฏิเสธ\n- การ traversal เส้นทาง (`..`) จะถูกปฏิเสธ\n- เส้นทางที่แก้ไขแล้วต้องอยู่ภายใน `baseDir`\n- ไฟล์ที่ขาดหายจะส่งคืนข้อผิดพลาด `File not found` อย่างชัดเจน\n\nประเภทเนื้อหา:\n\n- `.md` => `text/markdown`\n- ทุกอย่างอื่น => `text/plain`\n\nไม่มีการค้นหา fallback สำหรับ assets ที่ขาดหาย\n\n## ทักษะ เทียบกับ XCSH.md คำสั่ง เครื่องมือ hooks\n\n### ทักษะ เทียบกับ XCSH.md\n\n- **ทักษะ**: ชุดความสามารถที่มีชื่อและไม่บังคับ ซึ่งเลือกตามบริบทงานหรือร้องขอโดยตรง\n- **XCSH.md/ไฟล์บริบท**: ไฟล์คำสั่งถาวรที่โหลดเป็น context-file capability และรวมกันตามกฎระดับ/ความลึก\n\n`src/discovery/agents-md.ts` เดินไดเรกทอรีบรรพบุรุษจาก `cwd` โดยเฉพาะเพื่อค้นหาไฟล์ `XCSH.md` แบบ standalone (ถึงความลึก 20) ยกเว้นส่วนที่เป็นไดเรกทอรีซ่อน\n\n### ทักษะ เทียบกับ slash commands\n\n- **ทักษะ**: เนื้อหาความรู้/เวิร์กโฟลว์ที่โมเดลอ่านได้\n- **Slash commands**: จุดเข้าคำสั่งที่ผู้ใช้เรียกใช้\n- `/skill:<name>` คือ wrapper ที่สะดวกสบายที่ฉีดข้อความทักษะ ไม่ได้เปลี่ยนความหมายของการค้นพบทักษะ\n\n### ทักษะ เทียบกับ custom tools\n\n- **ทักษะ**: เนื้อหาเอกสาร/เวิร์กโฟลว์ที่โหลดผ่านบริบทพรอมต์และ `read`\n- **Custom tools**: API เครื่องมือที่ปฏิบัติการได้ซึ่งโมเดลสามารถเรียกใช้ได้พร้อม schemas และผลข้างเคียงรันไทม์\n\n### ทักษะ เทียบกับ hooks\n\n- **ทักษะ**: เนื้อหาแบบ passive\n- **Hooks**: ตัวดักจับรันไทม์ที่ขับเคลื่อนด้วยเหตุการณ์ซึ่งสามารถบล็อก/แก้ไขพฤติกรรมระหว่างการประมวลผลได้\n\n## คำแนะนำการเขียนที่เป็นประโยชน์ซึ่งผูกกับตรรกะการค้นพบ\n\n- วางทักษะแต่ละอย่างในไดเรกทอรีของตัวเอง: `<skills-root>/<skill-name>/SKILL.md`\n- ใส่ frontmatter `name` และ `description` อย่างชัดเจนเสมอ\n- เก็บ assets ที่อ้างอิงไว้ใต้ไดเรกทอรีทักษะเดียวกันและเข้าถึงด้วย `skill://<name>/...`\n- สำหรับการจัดหมวดหมู่แบบซ้อน (`team/domain/skill`) ชี้ `skills.customDirectories` ไปที่ไดเรกทอรีพาเรนต์ที่ซ้อนกัน การสแกนเองยังคงเป็นแบบไม่เรียกซ้ำ\n- หลีกเลี่ยงชื่อทักษะซ้ำกันในแหล่งที่มาต่างๆ การจับคู่แรกชนะตามลำดับความสำคัญของผู้ให้บริการ\n",
	"th/index.md": "---\ntitle: เอกสารประกอบ xcsh\ndescription: >-\n  AI-powered development CLI with TypeScript coding agent and Rust native layer\n  for long-lived sessions, MCP support, and platform packaging.\nsidebar:\n  order: 0\n  label: ภาพรวม\ni18n:\n  sourceHash: b9288f42bf46\n  translator: machine\n---\n\nxcsh เป็น CLI สำหรับการพัฒนาที่ขับเคลื่อนด้วย AI พร้อม coding agent ที่เขียนด้วย TypeScript และ\nเลเยอร์ native ที่เขียนด้วย Rust (`pi-natives`) โดยขยายต่อจากโอเพนซอร์ส\n[`badlogic/pi-mono`](https://github.com/badlogic/pi-mono) ด้วย\nruntime ที่แข็งแกร่ง, เซสชันแบบยาวนานพร้อมการนำทางแบบ tree และการบีบอัด,\nเครื่องมือ Python IPython, รองรับ MCP เต็มรูปแบบ, ระบบ skills และการ\nแพ็กเกจแพลตฟอร์มที่รองรับ Linux, macOS และ Windows\n\n## จุดเริ่มต้น\n\n- **[F5 XC Contexts](/runtime-tools/context-command)** — เชื่อมต่อกับ F5 Distributed Cloud\n  tenants สร้าง contexts, สลับระหว่าง contexts, จัดการ namespaces และ credentials\n- **การกำหนดค่า** — วิธีที่ xcsh ค้นหา, resolve และจัดเลเยอร์การกำหนดค่า\n- **Runtime และเครื่องมือ** — รันไทม์ของ bash / notebook / resolve tool และ\n  พื้นผิว slash-command\n- **เซสชัน** — บันทึก entry แบบ append-only, การนำทางแบบ tree, การบีบอัด และ\n  ระบบหน่วยความจำอัตโนมัติ\n- **Natives (Rust)** — สถาปัตยกรรมของ `pi-natives` N-API addon ที่\n  ขับเคลื่อน shell / PTY / media / search\n- **MCP** — การกำหนดค่า, รายละเอียดภายในของโปรโตคอล, วงจรชีวิตรันไทม์ และวิธี\n  สร้าง servers และ tools\n- **Extensions, Skills และ Plugins** — การสร้าง, การโหลด, กฎการจับคู่,\n  marketplace และตัวติดตั้ง plugin\n- **Providers และ Models** — การกำหนดค่าโมเดล, รายละเอียดภายในของ streaming และ\n  รันไทม์ Python / IPython\n- **TUI** — การกำหนดธีม, คำสั่ง `/tree` และ integration hooks สำหรับ\n  extensions และเครื่องมือแบบกำหนดเอง\n\n## เอกสารชุดนี้จัดระเบียบอย่างไร\n\nแต่ละกลุ่มระดับบนสุดในแถบด้านข้างจะสอดคล้องกับระบบย่อยของ agent ภายใน\nแต่ละกลุ่ม หน้าต่าง ๆ จะเรียงลำดับจาก \"ภาพรวม\" ไปจนถึง \"รายละเอียดภายใน\" เพื่อให้คุณสามารถหยุดอ่าน\nได้เมื่อมีบริบทเพียงพอสำหรับงานที่ต้องทำ\n",
	"th/mcp/mcp-config.md": "---\ntitle: การกำหนดค่า MCP\ndescription: >-\n  การกำหนดค่า MCP server, การตรวจสอบความถูกต้อง, และการจัดการสำหรับ coding agent\n  runtime\nsidebar:\n  order: 1\n  label: การกำหนดค่า\ni18n:\n  sourceHash: ef8b49458ce9\n  translator: machine\n---\n\n# การกำหนดค่า MCP ใน OMP\n\nคู่มือนี้อธิบายวิธีการเพิ่ม แก้ไข และตรวจสอบความถูกต้องของ MCP server สำหรับ OMP coding agent\n\nแหล่งข้อมูลอ้างอิงในโค้ด:\n\n- ชนิดข้อมูลของ runtime config: `packages/coding-agent/src/mcp/types.ts`\n- Config writer: `packages/coding-agent/src/mcp/config-writer.ts`\n- Loader + validation: `packages/coding-agent/src/mcp/config.ts`\n- การค้นหา `mcp.json` แบบ standalone: `packages/coding-agent/src/discovery/mcp-json.ts`\n- Schema: `packages/coding-agent/src/config/mcp-schema.json`\n\n## ตำแหน่งไฟล์กำหนดค่าที่แนะนำ\n\nOMP สามารถค้นหา MCP server จากเครื่องมือหลายตัว (`.claude/`, `.cursor/`, `.vscode/`, `opencode.json` และอื่นๆ) แต่สำหรับการกำหนดค่าเฉพาะของ OMP คุณควรใช้ไฟล์ใดไฟล์หนึ่งต่อไปนี้:\n\n- ระดับโปรเจกต์: `.xcsh/mcp.json`\n- ระดับผู้ใช้: `~/.xcsh/mcp.json`\n\nOMP ยังรองรับไฟล์ standalone สำรองในรูทของโปรเจกต์:\n\n- `mcp.json`\n- `.mcp.json`\n\nใช้ `.xcsh/mcp.json` เมื่อคุณต้องการให้ OMP เป็นเจ้าของการกำหนดค่า ใช้ `mcp.json` / `.mcp.json` ที่รูทเฉพาะเมื่อคุณต้องการไฟล์สำรองที่พกพาได้ซึ่ง MCP client อื่นๆ อาจอ่านได้เช่นกัน\n\n## เพิ่มการอ้างอิง schema\n\nเพิ่มบรรทัดนี้ที่ด้านบนของไฟล์เพื่อรับ autocomplete และการตรวจสอบความถูกต้องในเอดิเตอร์:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {}\n}\n```\n\nปัจจุบัน OMP จะเขียนสิ่งนี้โดยอัตโนมัติเมื่อ `/mcp add`, `/mcp enable`, `/mcp disable`, `/mcp reauth` หรือขั้นตอนการเขียน config อื่นๆ สร้างหรืออัปเดตไฟล์ MCP ที่จัดการโดย OMP\n\n## โครงสร้างไฟล์\n\nOMP รองรับโครงสร้างระดับบนสุดนี้:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"server-name\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"some-mcp-server\"]\n    }\n  },\n  \"disabledServers\": [\"server-name\"]\n}\n```\n\nคีย์ระดับบนสุด:\n\n- `$schema` — URL ของ JSON Schema สำหรับเครื่องมือ (ไม่บังคับ)\n- `mcpServers` — แมปชื่อ server ไปยังการกำหนดค่า server\n- `disabledServers` — รายการปิดกั้นระดับผู้ใช้ที่ใช้ปิด server ที่ค้นพบตามชื่อ\n\nชื่อ server ต้องตรงกับ `^[a-zA-Z0-9_.-]{1,100}$`\n\n## ฟิลด์ server ที่รองรับ\n\nฟิลด์ร่วมสำหรับทุก transport:\n\n- `enabled?: boolean` — ข้ามเซิร์ฟเวอร์นี้เมื่อเป็น `false`\n- `timeout?: number` — ระยะหมดเวลาการเชื่อมต่อเป็นมิลลิวินาที\n- `auth?: { ... }` — ข้อมูลเมตาสำหรับการยืนยันตัวตนที่ OMP ใช้สำหรับขั้นตอน OAuth/API-key\n- `oauth?: { ... }` — การตั้งค่า OAuth client แบบชัดเจนที่ใช้ระหว่างการ auth/reauth\n\n### `stdio` transport\n\n`stdio` เป็นค่าเริ่มต้นเมื่อไม่ได้ระบุ `type`\n\nจำเป็น:\n\n- `command: string`\n\nไม่บังคับ:\n\n- `type?: \"stdio\"`\n- `args?: string[]`\n- `env?: Record<string, string>`\n- `cwd?: string`\n\nตัวอย่าง:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/Users/alice/projects\",\n        \"/Users/alice/Documents\"\n      ]\n    }\n  }\n}\n```\n\nตัวอย่างนี้ใช้แพ็กเกจ Filesystem MCP server อย่างเป็นทางการ (`@modelcontextprotocol/server-filesystem`)\n\n### `http` transport\n\nจำเป็น:\n\n- `type: \"http\"`\n- `url: string`\n\nไม่บังคับ:\n\n- `headers?: Record<string, string>`\n\nตัวอย่าง:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\nตัวอย่างนี้ตรงกับ endpoint ของ GitHub MCP server แบบ hosted ของ GitHub\n\n### `sse` transport\n\nจำเป็น:\n\n- `type: \"sse\"`\n- `url: string`\n\nไม่บังคับ:\n\n- `headers?: Record<string, string>`\n\nตัวอย่าง:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"legacy-remote\": {\n      \"type\": \"sse\",\n      \"url\": \"https://example.com/mcp/sse\"\n    }\n  }\n}\n```\n\n`sse` ยังคงรองรับเพื่อความเข้ากันได้ แต่ข้อกำหนด MCP ปัจจุบันแนะนำให้ใช้ Streamable HTTP (`type: \"http\"`) สำหรับ server ใหม่\n\n## ฟิลด์ Auth\n\nOMP เข้าใจออบเจกต์ที่เกี่ยวข้องกับ auth สองตัว\n\n### `auth`\n\n```json\n{\n  \"type\": \"oauth\" | \"apikey\",\n  \"credentialId\": \"optional-stored-credential-id\",\n  \"tokenUrl\": \"optional-token-endpoint\",\n  \"clientId\": \"optional-client-id\",\n  \"clientSecret\": \"optional-client-secret\"\n}\n```\n\nใช้เมื่อคุณต้องการให้ OMP จดจำวิธีการกู้คืนข้อมูลรับรองสำหรับ server\n\n### `oauth`\n\n```json\n{\n  \"clientId\": \"...\",\n  \"clientSecret\": \"...\",\n  \"redirectUri\": \"...\",\n  \"callbackPort\": 3334,\n  \"callbackPath\": \"/oauth/callback\"\n}\n```\n\nใช้เมื่อ MCP server ต้องการการตั้งค่า OAuth client แบบชัดเจน\n\nSlack เป็นตัวอย่างที่ชัดเจนที่สุดในปัจจุบัน MCP server ของ Slack โฮสต์อยู่ที่ `https://mcp.slack.com/mcp` ใช้ Streamable HTTP และต้องการ confidential OAuth ด้วย client credentials ของแอป Slack ของคุณ\n\nตัวอย่าง:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\nEndpoint ที่เกี่ยวข้องของ Slack จากเอกสารของ Slack:\n\n- MCP endpoint: `https://mcp.slack.com/mcp`\n- Authorization endpoint: `https://slack.com/oauth/v2_user/authorize`\n- Token endpoint: `https://slack.com/api/oauth.v2.user.access`\n\n## ตัวอย่างที่พร้อมคัดลอกและวาง\n\n### Filesystem server ผ่าน stdio\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/absolute/path/one\",\n        \"/absolute/path/two\"\n      ]\n    }\n  }\n}\n```\n\n### GitHub hosted server ผ่าน HTTP\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\n### GitHub local server ผ่าน Docker\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"-i\",\n        \"--rm\",\n        \"-e\",\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\",\n        \"ghcr.io/github/github-mcp-server\"\n      ],\n      \"env\": {\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n      }\n    }\n  }\n}\n```\n\nตัวอย่างนี้ตรงกับ Docker image อย่างเป็นทางการของ GitHub `ghcr.io/github/github-mcp-server`\n\n### Slack hosted server ผ่าน OAuth\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\n## ค่าลับและการแปลงค่าตัวแปร\n\nนี่คือส่วนที่มักทำให้ผู้ใช้สับสน\n\n### ใน `.xcsh/mcp.json` และ `~/.xcsh/mcp.json`\n\nก่อนที่ OMP จะเปิด server หรือทำ HTTP request จะแปลงค่า `env` และ `headers` ดังนี้:\n\n1. หากค่าเริ่มต้นด้วย `!` OMP จะรันเป็นคำสั่ง shell และใช้ stdout ที่ตัดช่องว่างแล้ว\n2. มิฉะนั้น OMP จะตรวจสอบก่อนว่าค่าตรงกับชื่อตัวแปรสภาพแวดล้อมหรือไม่\n3. หากตัวแปรสภาพแวดล้อมนั้นไม่ได้ถูกตั้งค่าไว้ OMP จะใช้สตริงตามตัวอักษร\n\nตัวอย่าง:\n\n```json\n{\n  \"env\": {\n    \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n  },\n  \"headers\": {\n    \"X-MCP-Insiders\": \"true\"\n  }\n}\n```\n\nหมายความว่าวิธีนี้ใช้ได้และสะดวกสำหรับค่าลับในเครื่อง:\n\n- `\"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"` → คัดลอกจากสภาพแวดล้อม shell ปัจจุบัน\n- `\"Authorization\": \"Bearer hardcoded-token\"` → ใช้ค่าตามตัวอักษร\n- `\"Authorization\": \"!printf 'Bearer %s' \\\"$GITHUB_TOKEN\\\"\"` → สร้าง header จากคำสั่ง\n\n### ใน `mcp.json` และ `.mcp.json` ที่รูท\n\nตัวโหลดไฟล์ standalone สำรองจะขยาย `${VAR}` และ `${VAR:-default}` ภายในสตริงระหว่างการค้นหาด้วย\n\nตัวอย่าง:\n\n```json\n{\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\",\n      \"headers\": {\n        \"Authorization\": \"Bearer ${GITHUB_TOKEN}\"\n      }\n    }\n  }\n}\n```\n\nหากคุณต้องการพฤติกรรมของ OMP ที่คาดเดาได้ง่ายที่สุด ให้เลือกใช้ `.xcsh/mcp.json` และใช้ค่า env/header แบบชัดเจน\n\n## `disabledServers`\n\n`disabledServers` มีประโยชน์หลักในไฟล์กำหนดค่าระดับผู้ใช้ (`~/.xcsh/mcp.json`) เมื่อ server ถูกค้นพบจากแหล่งอื่นและคุณต้องการให้ OMP เพิกเฉยโดยไม่ต้องแก้ไขไฟล์กำหนดค่าของเครื่องมืออื่น\n\nตัวอย่าง:\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"disabledServers\": [\"github\", \"slack\"]\n}\n```\n\n## `/mcp add` เทียบกับการแก้ไข JSON โดยตรง\n\nใช้ `/mcp add` เมื่อคุณต้องการการตั้งค่าแบบมีตัวช่วยนำทาง\n\nใช้การแก้ไข JSON โดยตรงเมื่อ:\n\n- คุณต้องการตัวเลือก transport หรือ auth ที่วิซาร์ดยังไม่ได้ถามถึง\n- คุณต้องการวางคำจำกัดความ server จาก MCP client อื่น\n- คุณต้องการการตรวจสอบความถูกต้องแบบ schema-backed ในเอดิเตอร์ของคุณ\n\nหลังจากแก้ไขแล้ว ใช้:\n\n- `/mcp reload` เพื่อค้นหาใหม่และเชื่อมต่อ server ใหม่ในเซสชันปัจจุบัน\n- `/mcp list` เพื่อดูว่า server มาจากไฟล์กำหนดค่าไหน\n- `/mcp test <name>` เพื่อทดสอบ server เดียว\n\n## กฎการตรวจสอบความถูกต้องที่ OMP บังคับใช้\n\nจาก `validateServerConfig()` ใน `packages/coding-agent/src/mcp/config.ts`:\n\n- `stdio` ต้องการ `command`\n- `http` และ `sse` ต้องการ `url`\n- server ไม่สามารถตั้งค่าทั้ง `command` และ `url` พร้อมกันได้\n- ค่า `type` ที่ไม่รู้จักจะถูกปฏิเสธ\n\nผลกระทบในทางปฏิบัติ:\n\n- การไม่ระบุ `type` หมายถึง `stdio`\n- หากคุณวางการกำหนดค่า remote server และลืม `\"type\": \"http\"` OMP จะถือว่าเป็น `stdio` และแจ้งว่า `command` หายไป\n- `sse` ยังคงใช้ได้เพื่อความเข้ากันได้ แต่ hosted server ใหม่ควรกำหนดค่าเป็น `http`\n\n## การค้นหาและลำดับความสำคัญ\n\nOMP ไม่รวมคำจำกัดความ server ที่ซ้ำกันข้ามไฟล์ ผู้ให้บริการค้นหาจะถูกจัดลำดับความสำคัญ และคำจำกัดความที่มีลำดับความสำคัญสูงกว่าจะชนะ\n\nในทางปฏิบัติ:\n\n- เลือกใช้ `.xcsh/mcp.json` หรือ `~/.xcsh/mcp.json` เมื่อคุณต้องการการแทนที่เฉพาะ OMP\n- ตั้งชื่อ server ให้ไม่ซ้ำกันข้ามเครื่องมือเมื่อเป็นไปได้\n- ใช้ `disabledServers` ในไฟล์กำหนดค่าระดับผู้ใช้เมื่อการกำหนดค่าจากบุคคลที่สามยังคงนำ server ที่คุณไม่ต้องการกลับมา\n\n## การแก้ไขปัญหา\n\n### `Server \"name\": stdio server requires \"command\" field`\n\nคุณอาจลืมระบุ `type: \"http\"` บน remote server\n\n### `Server \"name\": both \"command\" and \"url\" are set`\n\nเลือก transport อย่างใดอย่างหนึ่ง OMP ถือว่า `command` เป็น stdio และ `url` เป็น http/sse\n\n### `/mcp add` ทำงานแล้วแต่ server ยังไม่เชื่อมต่อ\n\nJSON ถูกต้อง แต่ server อาจยังเข้าถึงไม่ได้ ใช้ `/mcp test <name>` และตรวจสอบว่า:\n\n- ไบนารีหรือ Docker image มีอยู่\n- ตัวแปรสภาพแวดล้อมที่จำเป็นถูกตั้งค่าแล้ว\n- URL ระยะไกลสามารถเข้าถึงได้\n- OAuth หรือ API token ถูกต้อง\n\n### Server มีอยู่ในการกำหนดค่าของเครื่องมืออื่นแต่ไม่มีใน OMP\n\nรัน `/mcp list` OMP ค้นหาไฟล์ MCP จากบุคคลที่สามได้หลายไฟล์ แต่การโหลดระดับโปรเจกต์อาจถูกปิดได้ผ่านการตั้งค่า `mcp.enableProjectConfig`\n\n## เอกสารอ้างอิง\n\n- ข้อกำหนด MCP transport: <https://modelcontextprotocol.io/specification/2025-03-26/basic/transports>\n- แพ็กเกจ Filesystem server: <https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem>\n- GitHub MCP server: <https://github.com/github/github-mcp-server>\n- เอกสาร Slack MCP server: <https://docs.slack.dev/ai/slack-mcp-server/>\n",
	"th/mcp/mcp-protocol-transports.md": "---\ntitle: โปรโตคอล MCP และกลไกการขนส่งภายใน\ndescription: 'การใช้งานโปรโตคอล MCP พร้อมเลเยอร์การขนส่ง stdio, SSE และ streamable HTTP'\nsidebar:\n  order: 2\n  label: โปรโตคอลและการขนส่ง\ni18n:\n  sourceHash: 48632064dd00\n  translator: machine\n---\n\n# โปรโตคอล MCP และกลไกการขนส่งภายใน\n\nเอกสารนี้อธิบายวิธีที่ coding-agent ใช้งานการส่งข้อความ MCP JSON-RPC และวิธีแยกความรับผิดชอบของโปรโตคอลออกจากการขนส่ง\n\n## ขอบเขต\n\nครอบคลุม:\n\n- การไหลของคำขอ/การตอบสนองและการแจ้งเตือนแบบ JSON-RPC\n- การเชื่อมโยงคำขอและวงจรชีวิตสำหรับการขนส่ง stdio และ HTTP/SSE\n- พฤติกรรมหมดเวลาและการยกเลิก\n- การแพร่กระจายข้อผิดพลาดและการจัดการ payload ที่ผิดรูปแบบ\n- ขอบเขตการเลือกการขนส่ง (`stdio` เทียบกับ `http`/`sse`)\n- ความรับผิดชอบด้านการเชื่อมต่อใหม่/ลองใหม่ที่เป็นระดับการขนส่งเทียบกับระดับ manager\n\nไม่ครอบคลุม UX การเขียนส่วนขยายหรือ UI คำสั่ง\n\n## ไฟล์การใช้งาน\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/transports/stdio.ts`](../../packages/coding-agent/src/mcp/transports/stdio.ts)\n- [`src/mcp/transports/http.ts`](../../packages/coding-agent/src/mcp/transports/http.ts)\n- [`src/mcp/transports/index.ts`](../../packages/coding-agent/src/mcp/transports/index.ts)\n- [`src/mcp/json-rpc.ts`](../../packages/coding-agent/src/mcp/json-rpc.ts)\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n\n## ขอบเขตเลเยอร์\n\n### เลเยอร์โปรโตคอล (JSON-RPC + เมธอด MCP)\n\n- รูปแบบข้อความถูกกำหนดไว้ใน `types.ts` (`JsonRpcRequest`, `JsonRpcNotification`, `JsonRpcResponse`, `JsonRpcMessage`)\n- ลอจิก MCP client (`client.ts`) กำหนดลำดับเมธอดและ session handshake:\n  1. คำขอ `initialize`\n  2. การแจ้งเตือน `notifications/initialized`\n  3. การเรียกเมธอดเช่น `tools/list`, `tools/call`\n\n### เลเยอร์การขนส่ง (`MCPTransport`)\n\n`MCPTransport` สร้างนามธรรมสำหรับการส่งมอบและวงจรชีวิต:\n\n- `request(method, params, options?) -> Promise<T>`\n- `notify(method, params?) -> Promise<void>`\n- `close()`\n- `connected`\n- callbacks ที่เป็นทางเลือก: `onClose`, `onError`, `onNotification`\n\nการใช้งานการขนส่งเป็นเจ้าของรายละเอียด framing และ I/O:\n\n- `StdioTransport`: JSON ที่คั่นด้วยขึ้นบรรทัดใหม่ผ่าน stdio ของ subprocess\n- `HttpTransport`: JSON-RPC ผ่าน HTTP POST พร้อมการตอบสนอง/การรับฟัง SSE ที่เป็นทางเลือก\n\n### ข้อสังเกตสำคัญในปัจจุบัน\n\nTransport callbacks (`onClose`, `onError`, `onNotification`) ถูกใช้งานแล้ว แต่กระบวนการ `MCPClient`/`MCPManager` ในปัจจุบันไม่ได้เชื่อมต่อลอจิกการเชื่อมต่อใหม่กับ callbacks เหล่านี้ การแจ้งเตือนจะถูกใช้งานก็ต่อเมื่อผู้เรียกลงทะเบียน handlers เท่านั้น\n\n## การเลือกการขนส่ง\n\n`client.ts:createTransport()` เลือกการขนส่งจากการกำหนดค่า:\n\n- ละเว้น `type` หรือ `\"stdio\"` -> `createStdioTransport`\n- `\"http\"` หรือ `\"sse\"` -> `createHttpTransport`\n\n`\"sse\"` ถูกมองว่าเป็นตัวแปรของการขนส่ง HTTP (class เดียวกัน) ไม่ใช่การใช้งานการขนส่งแยกต่างหาก\n\n## การไหลของข้อความ JSON-RPC และการเชื่อมโยง\n\n## Request IDs\n\nการขนส่งแต่ละอย่างสร้าง ID ต่อคำขอ (สตริง `Math.random` + timestamp) ID เป็น correlation token ในระดับการขนส่ง\n\n## เส้นทางการเชื่อมโยง Stdio\n\n- คำขอขาออกถูก serialize เป็น JSON object หนึ่งชิ้น + `\\n`\n- `#pendingRequests: Map<id, {resolve,reject}>` เก็บคำขอที่อยู่ระหว่างดำเนินการ\n- read loop แยกวิเคราะห์ JSONL จาก stdout และเรียก `#handleMessage`\n- หากข้อความขาเข้ามี `id` ที่ตรงกัน คำขอจะ resolve/reject\n- หากข้อความขาเข้ามี `method` และไม่มี `id` จะถูกมองว่าเป็นการแจ้งเตือนและส่งไปยัง `onNotification`\n\nID ที่ไม่รู้จักจะถูกละเว้น (ไม่มีการ rejection ไม่มี error callback)\n\n## เส้นทางการเชื่อมโยง HTTP\n\n- คำขอขาออกเป็น HTTP `POST` พร้อม JSON body และ `id` ที่สร้างขึ้น\n- เส้นทางการตอบสนองแบบไม่ใช่ SSE: แยกวิเคราะห์การตอบสนอง JSON-RPC หนึ่งรายการและคืนค่า `result`/throw เมื่อเกิด `error`\n- เส้นทางการตอบสนอง SSE (`Content-Type: text/event-stream`): stream events คืนค่าข้อความแรกที่มี `id` ตรงกับ request ID ที่คาดหวังและมี `result` หรือ `error`\n- ข้อความ SSE ที่มี `method` และไม่มี `id` ถูกมองว่าเป็นการแจ้งเตือน\n\nหาก SSE stream สิ้นสุดก่อนที่จะได้รับการตอบสนองที่ตรงกัน คำขอจะล้มเหลวด้วย `No response received for request ID ...`\n\n## การแจ้งเตือน\n\nClient ส่งการแจ้งเตือน JSON-RPC ผ่าน `transport.notify(...)`\n\n- Stdio: เขียน notification frame ไปยัง stdin (`jsonrpc`, `method`, `params` ที่เป็นทางเลือก) บวกขึ้นบรรทัดใหม่\n- HTTP: ส่ง POST body โดยไม่มี `id`; ความสำเร็จยอมรับ `2xx` หรือ `202 Accepted`\n\nการแจ้งเตือนที่เริ่มต้นโดยเซิร์ฟเวอร์จะแสดงผลเฉพาะผ่าน `onNotification` ของการขนส่งเท่านั้น ไม่มี global subscriber เริ่มต้นใน manager/client\n\n## กลไกภายในของ Stdio Transport\n\n## วงจรชีวิตและการเปลี่ยนสถานะ\n\n- เริ่มต้น: `connected=false`, `process=null`, pending map ว่างเปล่า\n- `connect()`:\n  - spawn subprocess ด้วย command/args/env/cwd ที่กำหนดค่าไว้\n  - ทำเครื่องหมายว่าเชื่อมต่อแล้ว\n  - เริ่ม stdout read loop (`readJsonl`)\n  - เริ่ม stderr loop (อ่าน/ทิ้ง; ปัจจุบันเงียบ)\n- `close()`:\n  - ทำเครื่องหมายว่าตัดการเชื่อมต่อ\n  - reject คำขอที่รอดำเนินการทั้งหมด (`Transport closed`)\n  - kill subprocess\n  - รอการปิด read loop\n  - emit `onClose`\n\nหาก read loop ออกโดยไม่คาดคิด `finally` จะทริกเกอร์ `#handleClose()` ซึ่งทำการ rejection ของคำขอที่รอดำเนินการและ close callback เหมือนกัน\n\n## การหมดเวลาและการยกเลิก\n\nต่อคำขอ:\n\n- timeout ค่าเริ่มต้นคือ `config.timeout ?? 30000`\n- `AbortSignal` ที่เป็นทางเลือกจากผู้เรียก\n- การยกเลิกและ timeout ทั้งคู่จะ reject promise ที่รอดำเนินการและล้าง map entry\n\nการยกเลิกเป็นแบบ local เท่านั้น: การขนส่งไม่ส่งการแจ้งเตือนการยกเลิกระดับโปรโตคอลไปยังเซิร์ฟเวอร์\n\n## การจัดการ Payload ที่ผิดรูปแบบ\n\nใน read loop:\n\n- แต่ละบรรทัด JSONL ที่แยกวิเคราะห์แล้วถูกส่งไปยัง `#handleMessage` ใน `try/catch`\n- ข้อยกเว้นการจัดการข้อความที่ผิดรูปแบบ/ไม่ถูกต้องจะถูกทิ้ง (comment `Skip malformed lines`)\n- loop ดำเนินต่อ ดังนั้นข้อความเสียหายหนึ่งรายการจะไม่ทำให้การเชื่อมต่อล้มเหลว\n\nหาก stream parser พื้นฐาน throw `onError` จะถูกเรียก (เมื่อยังเชื่อมต่ออยู่) จากนั้นการเชื่อมต่อจะปิด\n\n## พฤติกรรมการตัดการเชื่อมต่อ/ความล้มเหลว\n\nเมื่อ process ออกหรือ stream ปิด:\n\n- คำขอที่อยู่ระหว่างดำเนินการทั้งหมดจะถูก reject ด้วย `Transport closed`\n- ไม่มีการรีสตาร์ทหรือเชื่อมต่อใหม่อัตโนมัติ\n- เลเยอร์ที่สูงกว่าต้องเชื่อมต่อใหม่โดยสร้างการขนส่งใหม่\n\n## หมายเหตุเกี่ยวกับ Backpressure/Streaming\n\n- การเขียนขาออกใช้ `stdin.write()` + `flush()` โดยไม่รอ drain semantics\n- ไม่มีการจัดการ queue หรือ high-watermark อย่างชัดเจนในการขนส่ง\n- การประมวลผลขาเข้าขับเคลื่อนด้วย stream (`for await` ผ่าน `readJsonl`) ทีละข้อความที่แยกวิเคราะห์แล้ว\n\n## กลไกภายในของ HTTP/SSE Transport\n\n## วงจรชีวิตและ Connection Semantics\n\nHTTP transport มีสถานะการเชื่อมต่อเชิงตรรกะ แต่เส้นทางคำขอเป็น stateless ต่อการเรียก HTTP:\n\n- `connect()` ตั้งค่า `connected=true` (ไม่มี socket/session handshake)\n- การติดตาม session ของเซิร์ฟเวอร์ที่เป็นทางเลือกผ่าน header `Mcp-Session-Id`\n- `close()` ส่ง `DELETE` พร้อม `Mcp-Session-Id` ที่เป็นทางเลือก, ยกเลิก SSE listener, emit `onClose`\n\nดังนั้น `connected` หมายความว่า \"การขนส่งใช้งานได้\" ไม่ใช่ \"สร้าง persistent stream แล้ว\"\n\n## พฤติกรรม Session Header\n\n- เมื่อตอบสนอง POST หากมี header `Mcp-Session-Id` การขนส่งจะเก็บไว้\n- คำขอ/การแจ้งเตือนต่อๆ ไปจะรวม `Mcp-Session-Id`\n- `close()` พยายามยุติ session ของเซิร์ฟเวอร์ด้วย HTTP DELETE; ความล้มเหลวในการยุติจะถูกละเว้น\n\n## การหมดเวลาและการยกเลิก\n\nสำหรับทั้ง `request()` และ `notify()`:\n\n- timeout ใช้ `AbortController` (`config.timeout ?? 30000`)\n- signal ภายนอก หากมีให้ จะถูกรวมผ่าน `AbortSignal.any([...])`\n- การจัดการ AbortError แยกแยะการยกเลิกของผู้เรียกเทียบกับ timeout\n\nข้อผิดพลาดที่ throw:\n\n- timeout: `Request timeout after ...ms` (หรือ `SSE response timeout ...`, `Notify timeout ...`)\n- การยกเลิกของผู้เรียก: AbortError/reason ดั้งเดิมถูก rethrow เมื่อ signal ภายนอกถูกยกเลิกแล้ว\n\n## การแพร่กระจายข้อผิดพลาด HTTP\n\nเมื่อการตอบสนองไม่ OK:\n\n- ข้อความการตอบสนองถูกรวมในข้อผิดพลาดที่ throw (`HTTP <status>: <text>`)\n- หากมี hints การยืนยันตัวตนจาก `WWW-Authenticate` และ `Mcp-Auth-Server` จะถูกเพิ่มต่อท้าย\n\nเมื่อเกิด JSON-RPC error object:\n\n- throw `MCP error <code>: <message>`\n\nความล้มเหลวของ JSON body ที่ผิดรูปแบบ (ความล้มเหลวของ `response.json()`) จะแพร่กระจายเป็น parse exception\n\n## พฤติกรรม SSE และโหมด\n\nมีเส้นทาง SSE สองเส้นทาง:\n\n1. **การตอบสนอง SSE ต่อคำขอ** (`#parseSSEResponse`)\n   - ใช้เมื่อประเภทเนื้อหาการตอบสนอง POST คือ `text/event-stream`\n   - ใช้ stream จนกว่าจะพบ response id ที่ตรงกัน\n   - สามารถประมวลผลการแจ้งเตือนที่คั่นระหว่างกันในระหว่าง stream เดียวกัน\n\n2. **Background SSE listener** (`startSSEListener()`)\n   - GET listener ที่เป็นทางเลือกสำหรับการแจ้งเตือนที่เริ่มต้นโดยเซิร์ฟเวอร์\n   - ปัจจุบันไม่ได้เริ่มต้นโดยอัตโนมัติโดย MCP manager/client\n   - หาก GET คืนค่า `405` listener จะปิดการใช้งานตัวเองโดยไม่แจ้ง (เซิร์ฟเวอร์ไม่รองรับโหมดนี้)\n\n## การจัดการ Payload ที่ผิดรูปแบบและการตัดการเชื่อมต่อ\n\nข้อผิดพลาดการแยกวิเคราะห์ JSON ของ SSE จะผุดขึ้นจาก `readSseJson` และ reject คำขอ/listener\n\n- ข้อผิดพลาด SSE parse ของคำขอ reject คำขอที่ใช้งานอยู่\n- ข้อผิดพลาด background listener ทริกเกอร์ `onError` (ยกเว้น AbortError)\n- ไม่มีการเชื่อมต่อใหม่อัตโนมัติสำหรับ background listener\n\n## ยูทิลิตี้ `json-rpc.ts` เทียบกับนามธรรมการขนส่ง\n\n`src/mcp/json-rpc.ts` จัดเตรียม helpers `callMCP()` และ `parseSSE()` สำหรับการเรียก HTTP MCP โดยตรง (ใช้โดยการผสาน Exa) ไม่ใช่นามธรรม `MCPTransport` ที่ใช้โดย `MCPClient`/`MCPManager`\n\nความแตกต่างที่น่าสังเกตจาก `HttpTransport`:\n\n- แยกวิเคราะห์ข้อความการตอบสนองทั้งหมดก่อน จากนั้นดึงบรรทัด `data:` แรก (`parseSSE`) พร้อม JSON fallback\n- ไม่มีการจัดการ request timeout ไม่มี abort API ไม่มีการจัดการ session-id ไม่มีวงจรชีวิตการขนส่ง\n- คืนค่า JSON-RPC envelope object ดิบ\n\nเส้นทางนี้มีน้ำหนักเบาแต่มีความแข็งแกร่งน้อยกว่าการใช้งานการขนส่งแบบเต็มรูปแบบ\n\n## ความรับผิดชอบในการลองใหม่/เชื่อมต่อใหม่\n\n## ระดับการขนส่ง\n\nการใช้งานการขนส่งในปัจจุบัน **ไม่**:\n\n- ลองคำขอที่ล้มเหลวใหม่\n- เชื่อมต่อใหม่หลังจาก stdio process ออก\n- เชื่อมต่อ SSE listeners ใหม่\n- ส่งคำขอที่อยู่ระหว่างดำเนินการใหม่หลังการตัดการเชื่อมต่อ\n\nพวกมันล้มเหลวอย่างรวดเร็วและแพร่กระจายข้อผิดพลาด\n\n## ระดับ Manager/Client\n\n`MCPManager` จัดการการค้นพบ/การประสานการเชื่อมต่อเริ่มต้นและสามารถเชื่อมต่อใหม่ได้โดยการเรียกใช้กระบวนการเชื่อมต่ออีกครั้ง (เส้นทาง `connectToServer`/`discoverAndConnect`) ไม่มีการ auto-heal การขนส่งที่เชื่อมต่ออยู่แล้วเมื่อเกิดความล้มเหลวของ runtime callbacks\n\n`MCPManager` มีพฤติกรรม startup fallback สำหรับเซิร์ฟเวอร์ที่ช้า (deferred tools จาก cache) แต่นั่นเป็น tool availability fallback ไม่ใช่การลองใหม่ของการขนส่ง\n\n## สรุปสถานการณ์ความล้มเหลว\n\n- **บรรทัดข้อความ stdio ที่ผิดรูปแบบ**: ถูกทิ้ง; stream ดำเนินต่อ\n- **Stdio stream/process สิ้นสุด**: การขนส่งปิด; คำขอที่รอดำเนินการถูก reject เป็น `Transport closed`\n- **HTTP non-2xx**: คำขอ/การแจ้งเตือน throw HTTP error\n- **การตอบสนอง JSON ที่ไม่ถูกต้อง**: parse exception แพร่กระจาย\n- **SSE สิ้นสุดโดยไม่มี id ที่ตรงกัน**: คำขอล้มเหลวด้วย `No response received for request ID ...`\n- **Timeout**: ข้อผิดพลาด timeout เฉพาะการขนส่ง\n- **การยกเลิกของผู้เรียก**: AbortError/reason แพร่กระจายจาก signal ของผู้เรียก\n\n## กฎขอบเขตเชิงปฏิบัติ\n\nหากความกังวลคือรูปแบบข้อความ การเชื่อมโยง id หรือลำดับเมธอด MCP ก็เป็นของลอจิก protocol/client\n\nหากความกังวลคือ framing (JSONL เทียบกับ HTTP/SSE) การแยกวิเคราะห์ stream วงจรชีวิต fetch/spawn นาฬิกา timeout หรือการยุติการเชื่อมต่อ ก็เป็นของการใช้งานการขนส่ง\n",
	"th/mcp/mcp-runtime-lifecycle.md": "---\ntitle: วงจรชีวิตรันไทม์ MCP\ndescription: >-\n  วงจรชีวิตกระบวนการ MCP server ตั้งแต่การเริ่มต้นจนถึงการลงทะเบียนเครื่องมือ\n  การตรวจสอบสุขภาพ และการปิดระบบ\nsidebar:\n  order: 3\n  label: วงจรชีวิตรันไทม์\ni18n:\n  sourceHash: d04cefaf38f8\n  translator: machine\n---\n\n# วงจรชีวิตรันไทม์ MCP\n\nเอกสารนี้อธิบายวิธีที่ MCP servers ถูกค้นหา เชื่อมต่อ เปิดเผยในฐานะเครื่องมือ รีเฟรช และปิดลงใน coding-agent runtime\n\n## ภาพรวมวงจรชีวิต\n\n1. **การเริ่มต้น SDK** เรียก `discoverAndLoadMCPTools()` (เว้นแต่ MCP จะถูกปิดการใช้งาน)\n2. **การค้นหา** (`loadAllMCPConfigs`) แก้ไขการกำหนดค่า MCP server จากแหล่ง capability กรองรายการที่ถูกปิดใช้งาน/project/Exa และเก็บรักษาข้อมูลเมตาของแหล่ง\n3. **ขั้นตอนการเชื่อมต่อ Manager** (`MCPManager.connectServers`) เริ่มการเชื่อมต่อต่อ server + `tools/list` แบบขนาน\n4. **ประตู Fast startup** รอสูงสุด 250ms จากนั้นอาจคืนค่า:\n   - `MCPTool` ที่โหลดเสร็จสมบูรณ์,\n   - ข้อผิดพลาดต่อ server,\n   - หรือ `DeferredMCPTool` ที่แคชไว้สำหรับ server ที่ยังค้างอยู่\n5. **การเชื่อมต่อ SDK** รวม MCP tools เข้ากับ runtime tool registry สำหรับ session\n6. **Live session** สามารถรีเฟรช MCP tools ผ่านกระบวนการ `/mcp` (`disconnectAll` + ค้นหาใหม่ + `session.refreshMCPTools`)\n7. **การปิดระบบ** เกิดขึ้นเมื่อผู้เรียกใช้งานเรียก `disconnectServer`/`disconnectAll`; manager ยังล้างการลงทะเบียน MCP tool สำหรับ server ที่ถูกตัดการเชื่อมต่อด้วย\n\n## ขั้นตอนการค้นหาและโหลด\n\n### เส้นทางเข้าจาก SDK\n\n`createAgentSession()` ใน `src/sdk.ts` ดำเนินการเริ่มต้น MCP เมื่อ `enableMCP` เป็น true (ค่าเริ่มต้น):\n\n- เรียก `discoverAndLoadMCPTools(cwd, { ... })`,\n- ส่ง `authStorage`, cache storage และการตั้งค่า `mcp.enableProjectConfig`,\n- ตั้งค่า `filterExa: true` เสมอ,\n- บันทึกข้อผิดพลาดการโหลด/เชื่อมต่อต่อ server,\n- เก็บ manager ที่คืนค่ากลับมาใน `toolSession.mcpManager` และผลลัพธ์ session\n\nหาก `enableMCP` เป็น false การค้นหา MCP จะถูกข้ามทั้งหมด\n\n### การค้นหา Config และการกรอง\n\n`loadAllMCPConfigs()` (`src/mcp/config.ts`) โหลดรายการ MCP server แบบ canonical ผ่านการค้นหา capability จากนั้นแปลงเป็น `MCPServerConfig` แบบ legacy\n\nพฤติกรรมการกรอง:\n\n- `enableProjectConfig: false` ลบรายการระดับ project (`_source.level === \"project\"`)\n- server ที่มี `enabled: false` จะถูกข้ามก่อนการพยายามเชื่อมต่อ\n- Exa servers ถูกกรองออกโดยค่าเริ่มต้น และ API keys จะถูกดึงออกมาสำหรับการรวม native Exa tool\n\nผลลัพธ์รวมทั้ง `configs` และ `sources` (ข้อมูลเมตาที่ใช้ภายหลังสำหรับการระบุ provider)\n\n### พฤติกรรมความล้มเหลวระดับการค้นหา\n\n`discoverAndLoadMCPTools()` แยกแยะความล้มเหลวสองประเภท:\n\n- **ความล้มเหลวหนักในการค้นหา** (exception จาก `manager.discoverAndConnect` มักจากการค้นหา config): คืนค่า tool set ว่างเปล่าและข้อผิดพลาดสังเคราะห์หนึ่งรายการ `{ path: \".mcp.json\", error }`\n- **ความล้มเหลว runtime/connect ต่อ server**: manager คืนค่าความสำเร็จบางส่วนพร้อม map `errors`; server อื่นๆ ดำเนินการต่อไป\n\nดังนั้นการเริ่มต้นจะไม่ทำให้ agent session ทั้งหมดล้มเหลวเมื่อ MCP server แต่ละตัวล้มเหลว\n\n## โมเดลสถานะ Manager\n\n`MCPManager` ติดตามวงจรชีวิตรันไทม์ด้วย registries แยกกัน:\n\n- `#connections: Map<string, MCPServerConnection>` — server ที่เชื่อมต่อสมบูรณ์แล้ว\n- `#pendingConnections: Map<string, Promise<MCPServerConnection>>` — handshake กำลังดำเนินการ\n- `#pendingToolLoads: Map<string, Promise<{ connection, serverTools }>>` — เชื่อมต่อแล้วแต่ tools ยังโหลด\n- `#tools: CustomTool[]` — มุมมอง MCP tool ปัจจุบันที่เปิดเผยต่อผู้เรียก\n- `#sources: Map<string, SourceMeta>` — ข้อมูลเมตา provider/source แม้ก่อนการเชื่อมต่อเสร็จสมบูรณ์\n\n`getConnectionStatus(name)` ดึงสถานะจาก map เหล่านี้:\n\n- `connected` ถ้าอยู่ใน `#connections`,\n- `connecting` ถ้า pending connect หรือ pending tool load,\n- `disconnected` ในกรณีอื่น\n\n## การสร้างการเชื่อมต่อและเวลาเริ่มต้น\n\n## Pipeline การเชื่อมต่อต่อ server\n\nสำหรับแต่ละ server ที่ค้นพบใน `connectServers()`:\n\n1. เก็บ/อัปเดตข้อมูลเมตา source,\n2. ข้ามถ้าเชื่อมต่อ/pending อยู่แล้ว,\n3. ตรวจสอบฟิลด์ transport (`validateServerConfig`),\n4. แก้ไข auth/shell substitutions (`#resolveAuthConfig`),\n5. เรียก `connectToServer(name, resolvedConfig)`,\n6. เรียก `listTools(connection)`,\n7. แคช tool definitions (`MCPToolCache.set`) แบบ best-effort\n\nพฤติกรรม `connectToServer()` (`src/mcp/client.ts`):\n\n- สร้าง stdio หรือ HTTP/SSE transport,\n- ดำเนินการ MCP `initialize` + `notifications/initialized`,\n- ใช้ timeout (`config.timeout` หรือค่าเริ่มต้น 30 วินาที),\n- ปิด transport เมื่อเกิดความล้มเหลวในการเริ่มต้น\n\n### ประตู Fast startup + Deferred fallback\n\n`connectServers()` รอบน race ระหว่าง:\n\n- งาน connect/tool-load ทั้งหมดเสร็จสิ้น และ\n- `STARTUP_TIMEOUT_MS = 250`\n\nหลังจาก 250ms:\n\n- งานที่สำเร็จกลายเป็น live `MCPTool`,\n- งานที่ถูกปฏิเสธสร้างข้อผิดพลาดต่อ server,\n- งานที่ยังค้างอยู่:\n  - ใช้ tool definitions ที่แคชไว้หากมี (`MCPToolCache.get`) เพื่อสร้าง `DeferredMCPTool`,\n  - มิฉะนั้นบล็อกจนกว่างาน pending เหล่านั้นจะเสร็จสิ้น\n\nนี่คือโมเดลการเริ่มต้นแบบ hybrid: คืนค่าเร็วเมื่อมีแคช รอเพื่อความถูกต้องเมื่อไม่มีแคช\n\n### พฤติกรรมการทำงานให้เสร็จในเบื้องหลัง\n\n`toolsPromise` ที่ pending แต่ละรายการยังมี background continuation ที่ท้ายที่สุดจะ:\n\n- แทนที่ tool slice ของ server นั้นใน manager state ผ่าน `#replaceServerTools`,\n- เขียน cache,\n- บันทึกความล้มเหลวล่าช้าหลังจากการเริ่มต้นเท่านั้น (`allowBackgroundLogging`)\n\n## การเปิดเผย Tool และความพร้อมใช้งานใน Live Session\n\n### การลงทะเบียนเมื่อเริ่มต้น\n\n`discoverAndLoadMCPTools()` แปลง manager tools เป็น `LoadedCustomTool[]` และตกแต่ง paths (`mcp:<server> via <providerName>` เมื่อทราบ)\n\n`createAgentSession()` จากนั้นดัน tools เหล่านี้เข้าสู่ `customTools` ซึ่งถูกห่อและเพิ่มเข้า runtime tool registry ด้วยชื่อเช่น `mcp_<server>_<tool>`\n\n### การเรียกใช้ Tool\n\n- `MCPTool` เรียก tools ผ่าน `MCPServerConnection` ที่เชื่อมต่ออยู่แล้ว\n- `DeferredMCPTool` รอ `waitForConnection(server)` ก่อนการเรียก; ซึ่งช่วยให้ cached tools มีอยู่ก่อนที่การเชื่อมต่อจะพร้อม\n\nทั้งคู่คืนค่า structured tool output และแปลง transport/tool errors เป็นเนื้อหา tool `MCP error: ...` (abort ยังคงเป็น abort)\n\n## เส้นทาง Refresh/Reload (การเริ่มต้น vs Live Reload)\n\n### เส้นทางการเริ่มต้นครั้งแรก\n\n- การค้นหา/โหลดครั้งเดียวใน `sdk.ts`,\n- tools ลงทะเบียนใน session tool registry เริ่มต้น\n\n### เส้นทาง Interactive Reload\n\nเส้นทาง `/mcp reload` (`src/modes/controllers/mcp-command-controller.ts`) ดำเนินการ:\n\n1. `mcpManager.disconnectAll()`,\n2. `mcpManager.discoverAndConnect()`,\n3. `session.refreshMCPTools(mcpManager.getTools())`\n\n`session.refreshMCPTools()` (`src/session/agent-session.ts`) ลบ tools `mcp_` ทั้งหมด ห่อ MCP tools ล่าสุดใหม่ และเปิดใช้งาน tool set อีกครั้งเพื่อให้การเปลี่ยนแปลง MCP มีผลโดยไม่ต้องรีสตาร์ท session\n\nยังมีเส้นทาง follow-up สำหรับการเชื่อมต่อล่าช้า: หลังจากรอ server เฉพาะ หากสถานะกลายเป็น `connected` จะรัน `session.refreshMCPTools(...)` อีกครั้งเพื่อให้ tools ที่มีใหม่ถูก rebind ใน session\n\n## พฤติกรรมสุขภาพ การเชื่อมต่อใหม่ และความล้มเหลวบางส่วน\n\nพฤติกรรม runtime ปัจจุบันมีความเรียบง่ายโดยเจตนา:\n\n- **ไม่มี health monitor อัตโนมัติ** ใน manager/client\n- **ไม่มี reconnect loop อัตโนมัติ** เมื่อ transport ขาดหาย\n- Manager ไม่ subscribe กับ transport `onClose`/`onError`; สถานะขับเคลื่อนด้วย registry\n- Reconnect เป็นแบบ explicit: reload flow หรือการเรียก `connectServers()` โดยตรง\n\nในเชิงปฏิบัติการ:\n\n- server หนึ่งล้มเหลวไม่ลบ tools จาก server ที่ทำงานปกติ,\n- ความล้มเหลว connect/list ถูก isolate ต่อ server,\n- tool cache และการอัปเดตเบื้องหลังเป็นแบบ best-effort (บันทึก warnings/errors ไม่หยุดทำงาน)\n\n## ความหมายของการปิดระบบ\n\n### การปิดระบบระดับ Server\n\n`disconnectServer(name)`:\n\n- ลบ pending entries/source metadata,\n- ปิด transport ถ้าเชื่อมต่ออยู่,\n- ลบ tools `mcp_` ของ server นั้นจาก manager state\n\n### การปิดระบบทั้งหมด\n\n`disconnectAll()`:\n\n- ปิด transport ที่ active ทั้งหมดด้วย `Promise.allSettled`,\n- ล้าง pending maps, sources, connections และ manager tool list\n\nในการเชื่อมต่อปัจจุบัน การปิดระบบแบบ explicit ใช้ใน MCP command flows (สำหรับ reload/remove/disable) ไม่มี hook การจัดการ manager disposal อัตโนมัติแยกต่างหากในเส้นทางการเริ่มต้นเอง; ผู้เรียกรับผิดชอบในการเรียก manager disconnect methods เมื่อต้องการการปิดระบบ MCP แบบ deterministic\n\n## โหมดความล้มเหลวและการรับประกัน\n\n| สถานการณ์ | พฤติกรรม | ความล้มเหลวหนัก vs best-effort |\n| --- | --- | --- |\n| Discovery ส่ง exception (เส้นทางโหลด capability/config) | Loader คืนค่า tools ว่างเปล่า + ข้อผิดพลาด `.mcp.json` สังเคราะห์ | Best-effort session startup |\n| Server config ไม่ถูกต้อง | Server ถูกข้ามพร้อมรายการข้อผิดพลาดการตรวจสอบ | Best-effort ต่อ server |\n| Connect timeout/init failure | ข้อผิดพลาด server ถูกบันทึก; server อื่นๆ ดำเนินการต่อ | Best-effort ต่อ server |\n| `tools/list` ยังค้างอยู่เมื่อเริ่มต้นพร้อม cache hit | Deferred tools คืนค่าทันที | Best-effort fast startup |\n| `tools/list` ยังค้างอยู่เมื่อเริ่มต้นไม่มี cache | การเริ่มต้นรอ pending ให้เสร็จสิ้น | Hard wait เพื่อความถูกต้อง |\n| ความล้มเหลว tool-load เบื้องหลังล่าช้า | บันทึกหลังประตูการเริ่มต้น | Best-effort logging |\n| Runtime transport ขาดหาย | ไม่มี reconnect อัตโนมัติ; การเรียกในอนาคตล้มเหลวจนกว่าจะ reconnect/reload | Best-effort recovery ผ่านการดำเนินการด้วยตนเอง |\n\n## พื้นผิว Public API\n\n`src/mcp/index.ts` ส่งออก loader/manager/client APIs อีกครั้งสำหรับผู้เรียกภายนอก `src/sdk.ts` เปิดเผย `discoverMCPServers()` เป็น convenience wrapper ที่คืนค่า loader result shape เดียวกัน\n\n## ไฟล์ Implementation\n\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts) — loader facade, การจัดการข้อผิดพลาดการค้นหา, การแปลง `LoadedCustomTool`\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts) — lifecycle state registries, กระบวนการ connect/list แบบขนาน, refresh/disconnect\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts) — การตั้งค่า transport, initialize handshake, list/call/disconnect\n- [`src/mcp/index.ts`](../../packages/coding-agent/src/mcp/index.ts) — การส่งออก MCP module API\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — การเชื่อมต่อการเริ่มต้นเข้าสู่ session/tool registry\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts) — การค้นหา config/การกรอง/การตรวจสอบที่ใช้โดย manager\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts) — พฤติกรรมรันไทม์ `MCPTool` และ `DeferredMCPTool`\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — `refreshMCPTools` live rebinding\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts) — กระบวนการ interactive reload/reconnect\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — subagent MCP proxying ผ่านการเชื่อมต่อ parent manager\n",
	"th/mcp/mcp-server-tool-authoring.md": "---\ntitle: การเขียน MCP Server และ Tool\ndescription: คู่มือการสร้าง MCP server แบบกำหนดเองและการลงทะเบียน tool สำหรับ coding agent\nsidebar:\n  order: 4\n  label: การเขียน Server และ Tool\ni18n:\n  sourceHash: 160e7560ef1f\n  translator: machine\n---\n\n# การเขียน MCP server และ tool\n\nเอกสารนี้อธิบายว่านิยาม MCP server กลายเป็น `mcp_*` tools ที่เรียกใช้ได้ใน coding-agent อย่างไร และสิ่งที่ผู้ดำเนินการควรคาดหวังเมื่อ config ไม่ถูกต้อง ซ้ำกัน ถูกปิดใช้งาน หรือต้องการการตรวจสอบสิทธิ์\n\n## สถาปัตยกรรมโดยสรุป\n\n```text\nConfig sources (.xcsh/.claude/.cursor/.vscode/mcp.json, mcp.json, etc.)\n  -> discovery providers normalize to canonical MCPServer\n  -> capability loader dedupes by server name (higher provider priority wins)\n  -> loadAllMCPConfigs converts to MCPServerConfig + skips enabled:false\n  -> MCPManager connects/listTools (with auth/header/env resolution)\n  -> MCPTool/DeferredMCPTool bridge exposes tools as mcp_<server>_<tool>\n  -> AgentSession.refreshMCPTools replaces live MCP tools immediately\n```\n\n## 1) โมเดล server config และการตรวจสอบความถูกต้อง\n\n`src/mcp/types.ts` กำหนดรูปแบบการเขียนที่ใช้โดยผู้เขียน MCP config และ runtime:\n\n- `stdio` (ค่าเริ่มต้นเมื่อไม่มี `type`): ต้องการ `command`, ไม่บังคับ `args`, `env`, `cwd`\n- `http`: ต้องการ `url`, ไม่บังคับ `headers`\n- `sse`: ต้องการ `url`, ไม่บังคับ `headers` (คงไว้เพื่อความเข้ากันได้)\n- ฟิลด์ร่วม: `enabled`, `timeout`, `auth`\n\n`validateServerConfig()` (`src/mcp/config.ts`) บังคับใช้พื้นฐานของ transport:\n\n- ปฏิเสธ config ที่ตั้งค่าทั้ง `command` และ `url` พร้อมกัน\n- ต้องการ `command` สำหรับ stdio\n- ต้องการ `url` สำหรับ http/sse\n- ปฏิเสธ `type` ที่ไม่รู้จัก\n\n`config-writer.ts` ใช้การตรวจสอบความถูกต้องนี้สำหรับการดำเนินการเพิ่ม/อัปเดต และยังตรวจสอบชื่อ server ด้วย:\n\n- ต้องไม่ว่างเปล่า\n- ความยาวสูงสุด 100 ตัวอักษร\n- เฉพาะ `[a-zA-Z0-9_.-]` เท่านั้น\n\n### ข้อผิดพลาดที่พบบ่อยของ transport\n\n- การละเว้น `type` หมายถึง stdio หากต้องการ HTTP/SSE แต่ละเว้น `type` ไว้ `command` จะกลายเป็นสิ่งที่บังคับต้องมี\n- `sse` ยังคงได้รับการยอมรับแต่ถูกจัดการเป็น HTTP transport ภายใน (`createHttpTransport`)\n- การตรวจสอบความถูกต้องเป็นเชิงโครงสร้าง ไม่ใช่การเข้าถึงได้จริง: URL ที่ถูกต้องตามไวยากรณ์ยังสามารถล้มเหลวในขณะเชื่อมต่อได้\n\n## 2) การค้นพบ การทำให้เป็นรูปแบบมาตรฐาน และลำดับความสำคัญ\n\n### การค้นพบตาม Capability\n\n`loadAllMCPConfigs()` (`src/mcp/config.ts`) โหลดรายการ `MCPServer` มาตรฐานผ่าน `loadCapability(mcpCapability.id)`\n\nเลเยอร์ capability (`src/capability/index.ts`) จะ:\n\n1. โหลด provider ตามลำดับความสำคัญ\n2. กำจัดรายการซ้ำตาม `server.name` (ได้รับก่อน = ความสำคัญสูงสุด)\n3. ตรวจสอบความถูกต้องของรายการที่กำจัดซ้ำแล้ว\n\nผลลัพธ์: ชื่อ server ที่ซ้ำกันในแหล่งต่าง ๆ จะไม่ถูกรวมกัน นิยามหนึ่งจะชนะ; รายการซ้ำที่มีความสำคัญต่ำกว่าจะถูกซ่อนไว้\n\n### ไฟล์ `.mcp.json` และไฟล์ที่เกี่ยวข้อง\n\nprovider สำรองโดยเฉพาะใน `src/discovery/mcp-json.ts` อ่าน `mcp.json` และ `.mcp.json` ที่ root ของโปรเจกต์ (ความสำคัญต่ำ)\n\nในทางปฏิบัติ MCP server ยังมาจาก provider ที่มีความสำคัญสูงกว่า (เช่น `.xcsh/...` แบบ native และ config dir เฉพาะของ tool) คำแนะนำในการเขียน:\n\n- แนะนำให้ใช้ `.xcsh/mcp.json` (โปรเจกต์) หรือ `~/.xcsh/mcp.json` (ผู้ใช้) เพื่อการควบคุมที่ชัดเจน\n- ใช้ root `mcp.json` / `.mcp.json` เมื่อต้องการความเข้ากันได้แบบสำรอง\n- การใช้ชื่อ server เดียวกันในหลายแหล่งทำให้เกิดการซ่อนตามลำดับความสำคัญ ไม่ใช่การรวมกัน\n\n### พฤติกรรมการทำให้เป็นรูปแบบมาตรฐาน\n\n`convertToLegacyConfig()` (`src/mcp/config.ts`) แมป `MCPServer` มาตรฐานไปยัง `MCPServerConfig` สำหรับ runtime\n\nพฤติกรรมสำคัญ:\n\n- transport อนุมานเป็น `server.transport ?? (command ? \"stdio\" : url ? \"http\" : \"stdio\")`\n- server ที่ถูกปิดใช้งาน (`enabled === false`) จะถูกตัดออกก่อนการเชื่อมต่อ\n- ฟิลด์ที่ไม่บังคับจะถูกเก็บรักษาไว้เมื่อมีอยู่\n\n### การขยาย environment ระหว่างการค้นพบ\n\n`mcp-json.ts` ขยาย placeholder ของ env ในฟิลด์ string ด้วย `expandEnvVarsDeep()`:\n\n- รองรับ `${VAR}` และ `${VAR:-default}`\n- ค่าที่ไม่สามารถแก้ไขได้จะคงเป็น string ตัวอักษร `${VAR}`\n\n`mcp-json.ts` ยังทำการตรวจสอบประเภท runtime สำหรับ JSON ของผู้ใช้ และบันทึกคำเตือนสำหรับค่า `enabled`/`timeout` ที่ไม่ถูกต้องแทนที่จะทำให้ไฟล์ทั้งหมดล้มเหลว\n\n## 3) Auth และการแก้ไขค่า runtime\n\n`MCPManager.prepareConfig()`/`#resolveAuthConfig()` (`src/mcp/manager.ts`) คือการประมวลผลก่อนเชื่อมต่อขั้นสุดท้าย\n\n### การฉีด OAuth credential\n\nหาก config มี:\n\n```ts\nauth: { type: \"oauth\", credentialId: \"...\" }\n```\n\nและ credential มีอยู่ใน auth storage:\n\n- `http`/`sse`: ฉีด header `Authorization: Bearer <access_token>`\n- `stdio`: ฉีด env var `OAUTH_ACCESS_TOKEN`\n\nหากการค้นหา credential ล้มเหลว manager จะบันทึกคำเตือนและดำเนินการต่อโดยมี auth ที่ไม่ได้รับการแก้ไข\n\n### การแก้ไขค่า header/env\n\nก่อนการเชื่อมต่อ manager จะแก้ไขค่า header/env แต่ละค่าผ่าน `resolveConfigValue()` (`src/config/resolve-config-value.ts`):\n\n- ค่าที่เริ่มต้นด้วย `!` => รันคำสั่ง shell ใช้ stdout ที่ตัดช่องว่างแล้ว (cached)\n- มิฉะนั้น ให้ถือว่าค่าเป็นชื่อตัวแปร environment ก่อน (`process.env[name]`) แล้วจึง fallback เป็นค่าตัวอักษร\n- ค่าคำสั่ง/env ที่ไม่ได้รับการแก้ไขจะถูกละเว้นจาก headers/env map ขั้นสุดท้าย\n\nข้อควรระวังในการดำเนินงาน: หมายความว่าคำสั่ง secret หรือ env key ที่พิมพ์ผิดสามารถลบรายการ header/env นั้นออกอย่างเงียบ ๆ ส่งผลให้เกิด 401/403 หรือการเริ่มต้น server ล้มเหลว\n\n## 4) Tool bridge: MCP -> tools ที่ agent เรียกใช้ได้\n\n`src/mcp/tool-bridge.ts` แปลงนิยาม MCP tool เป็น `CustomTool`\n\n### การตั้งชื่อและโดเมนการชนกัน\n\nชื่อ tool สร้างขึ้นเป็น:\n\n```text\nmcp_<sanitized_server_name>_<sanitized_tool_name>\n```\n\nกฎ:\n\n- เปลี่ยนเป็นตัวพิมพ์เล็ก\n- อักขระที่ไม่ใช่ `[a-z_]` จะกลายเป็น `_`\n- เครื่องหมายขีดล่างที่ซ้ำกันจะถูกรวบ\n- คำนำหน้า `<server>_` ที่ซ้ำซ้อนในชื่อ tool จะถูกตัดออกหนึ่งครั้ง\n\nวิธีนี้หลีกเลี่ยงการชนกันส่วนใหญ่ แต่ไม่ทั้งหมด ชื่อ raw ที่แตกต่างกันยังสามารถ sanitize เป็น identifier เดียวกันได้ (เช่น `my-server` และ `my.server` ต่างก็ sanitize ในลักษณะเดียวกัน) และการแทรก registry เป็นแบบ last-write-wins\n\n### การแมป Schema\n\n`convertSchema()` คง MCP JSON Schema ไว้เป็นส่วนใหญ่แต่ patch object schema ที่ขาด `properties` ด้วย `{}` เพื่อความเข้ากันได้กับ provider\n\n### การแมปการรันคำสั่ง\n\n`MCPTool.execute()` / `DeferredMCPTool.execute()`:\n\n- เรียก MCP `tools/call`\n- แปลง MCP content เป็นข้อความที่แสดงได้\n- คืนค่า details ที่มีโครงสร้าง (`serverName`, `mcpToolName`, metadata ของ provider)\n- แมป `isError` ที่รายงานโดย server เป็นผลลัพธ์ข้อความ `Error: ...`\n- แมปความล้มเหลวของ transport/runtime ที่เกิดขึ้นเป็น `MCP error: ...`\n- รักษาความหมายของการยกเลิกโดยแปล AbortError เป็น `ToolAbortError`\n\n## 5) วงจรชีวิตของผู้ดำเนินการ: เพิ่ม/แก้ไข/ลบ และการอัปเดตแบบ live\n\nโหมด Interactive เปิดเผย `/mcp` ใน `src/modes/controllers/mcp-command-controller.ts`\n\nการดำเนินการที่รองรับ:\n\n- `add` (wizard หรือ quick-add)\n- `remove` / `rm`\n- `enable` / `disable`\n- `test`\n- `reauth` / `unauth`\n- `reload`\n\nการเขียน config เป็นแบบ atomic (`writeMCPConfigFile`: ไฟล์ชั่วคราว + เปลี่ยนชื่อ)\n\nหลังจากเปลี่ยนแปลง controller จะเรียก `#reloadMCP()`:\n\n1. `mcpManager.disconnectAll()`\n2. `mcpManager.discoverAndConnect()`\n3. `session.refreshMCPTools(mcpManager.getTools())`\n\n`refreshMCPTools()` แทนที่รายการ registry `mcp_` ทั้งหมดและเปิดใช้งาน MCP tool ล่าสุดทันที ดังนั้นการเปลี่ยนแปลงจึงมีผลโดยไม่ต้องรีสตาร์ท session\n\n### ความแตกต่างของโหมด\n\n- **โหมด Interactive/TUI**: `/mcp` ให้ UX ในแอป (wizard, OAuth flow, ข้อความสถานะการเชื่อมต่อ, การ rebinding runtime ทันที)\n- **การผสานรวม SDK/headless**: `discoverAndLoadMCPTools()` (`src/mcp/loader.ts`) คืน tool ที่โหลดแล้ว + ข้อผิดพลาดต่อ server; ไม่มี UX คำสั่ง `/mcp`\n\n## 6) พื้นผิวข้อผิดพลาดที่ผู้ใช้มองเห็น\n\nstring ข้อผิดพลาดทั่วไปที่ผู้ใช้/ผู้ดำเนินการเห็น:\n\n- ความล้มเหลวในการตรวจสอบความถูกต้องของการเพิ่ม/อัปเดต:\n  - `Invalid server config: ...`\n  - `Server \"<name>\" already exists in <path>`\n- ปัญหา argument ของ quick-add:\n  - `Use either --url or -- <command...>, not both.`\n  - `--token requires --url (HTTP/SSE transport).`\n- ความล้มเหลวในการเชื่อมต่อ/ทดสอบ:\n  - `Failed to connect to \"<name>\": <message>`\n  - ข้อความช่วยเหลือ timeout แนะนำให้เพิ่มค่า timeout\n  - ข้อความช่วยเหลือ auth สำหรับ `401/403`\n- OAuth flow ของ auth:\n  - `Authentication required ... OAuth endpoints could not be discovered`\n  - `OAuth flow timed out. Please try again.`\n  - `OAuth authentication failed: ...`\n- การใช้งาน server ที่ถูกปิดใช้งาน:\n  - `Server \"<name>\" is disabled. Run /mcp enable <name> first.`\n\nJSON ต้นทางที่ไม่ถูกต้องในการค้นพบจะถูกจัดการเป็นคำเตือน/log โดยทั่วไป; เส้นทาง config-writer จะ throw ข้อผิดพลาดอย่างชัดเจน\n\n## 7) คำแนะนำการเขียนเชิงปฏิบัติ\n\nสำหรับการเขียน MCP ที่แข็งแกร่งใน codebase นี้:\n\n1. รักษาชื่อ server ให้ไม่ซ้ำกันทั่วโลกในแหล่ง config ที่รองรับ MCP ทั้งหมด\n2. แนะนำให้ใช้ชื่อที่เป็นตัวอักษรและตัวเลข/เครื่องหมายขีดล่าง เพื่อหลีกเลี่ยงการชนกันของชื่อที่ sanitize แล้วในชื่อ tool `mcp_*` ที่สร้างขึ้น\n3. ใช้ `type` อย่างชัดเจนเพื่อหลีกเลี่ยงค่าเริ่มต้น stdio โดยไม่ตั้งใจ\n4. ถือว่า `enabled: false` เป็นการปิดสนิท: server จะถูกละเว้นจากชุดการเชื่อมต่อ runtime\n5. สำหรับ OAuth config ให้เก็บ `credentialId` ที่ถูกต้อง; มิฉะนั้นการฉีด auth จะถูกข้ามไป\n6. หากใช้การแก้ไข secret ตามคำสั่ง (`!cmd`) ให้ตรวจสอบว่า output ของคำสั่งมีความเสถียรและไม่ว่างเปล่า\n\n## ไฟล์การ implementation\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts)\n- [`src/mcp/config-writer.ts`](../../packages/coding-agent/src/mcp/config-writer.ts)\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts)\n- [`src/discovery/mcp-json.ts`](../../packages/coding-agent/src/discovery/mcp-json.ts)\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/config/resolve-config-value.ts`](../../packages/coding-agent/src/config/resolve-config-value.ts)\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts)\n",
	"th/natives/natives-addon-loader-runtime.md": "---\ntitle: รันไทม์ตัวโหลด Addon ของ Natives\ndescription: >-\n  รันไทม์ตัวโหลด addon ของ N-API พร้อมการตรวจจับแพลตฟอร์ม กลยุทธ์การสำรอง\n  และการแก้ไขโมดูล\nsidebar:\n  order: 3\n  label: ตัวโหลด Addon\ni18n:\n  sourceHash: 743ea3e32c7c\n  translator: machine\n---\n\n# รันไทม์ตัวโหลด Addon ของ Natives\n\nเอกสารนี้เจาะลึกชั้นการโหลด/การตรวจสอบ addon ใน `@f5-sales-demo/pi-natives`: วิธีที่ `native.ts` ตัดสินใจว่าจะโหลดไฟล์ `.node` ใด เมื่อใดที่การแตกไฟล์ payload ที่ฝังไว้จะทำงาน และวิธีรายงานความล้มเหลวในการเริ่มต้น\n\n## ไฟล์การนำไปใช้งาน\n\n- `packages/natives/src/native.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/package.json`\n\n## ขอบเขตและความรับผิดชอบ\n\nความรับผิดชอบของตัวโหลด/รันไทม์มีขอบเขตจำกัดโดยเจตนา:\n\n- สร้างรายการผู้สมัครที่รองรับแพลตฟอร์ม/CPU สำหรับชื่อไฟล์ addon และไดเรกทอรี\n- สร้าง addon ที่ฝังไว้ในไดเรกทอรีแคชตามเวอร์ชันต่อผู้ใช้ตามต้องการ\n- ลองผู้สมัครตามลำดับที่กำหนด\n- ปฏิเสธ addon ที่ล้าสมัยหรือไม่เข้ากันผ่าน `validateNative` ก่อนเปิดเผย bindings\n\nนอกขอบเขตที่นี่: พฤติกรรม grep/text/highlight ที่เฉพาะเจาะจงกับโมดูล\n\n## อินพุตรันไทม์และสถานะที่ได้มา\n\nที่การเริ่มต้นโมดูล (`export const native = loadNative();`) `native.ts` คำนวณบริบทแบบสถิต:\n\n- **แท็กแพลตฟอร์ม**: ``${process.platform}-${process.arch}`` (เช่น `darwin-arm64`)\n- **เวอร์ชันแพ็กเกจ**: จาก `packages/natives/package.json` (ฟิลด์ `version`)\n- **ไดเรกทอรีหลัก**:\n  - `nativeDir`: `packages/natives/native` ในแพ็กเกจ\n  - `execDir`: ไดเรกทอรีที่มี `process.execPath`\n  - `versionedDir`: `<getNativesDir()>/<packageVersion>`\n  - ทางสำรอง `userDataDir`:\n    - Windows: `%LOCALAPPDATA%/xcsh` (หรือ `%USERPROFILE%/AppData/Local/xcsh`)\n    - ที่ไม่ใช่ Windows: `~/.local/bin`\n- **โหมดไบนารีที่คอมไพล์แล้ว** (`isCompiledBinary`): true หากมีข้อใดข้อหนึ่งต่อไปนี้:\n  - ตัวแปรสภาพแวดล้อม `PI_COMPILED` ถูกตั้งค่า หรือ\n  - `import.meta.url` มีเครื่องหมาย Bun-embedded (`$bunfs`, `~BUN`, `%7EBUN`)\n- **การแทนที่ variant**: `PI_NATIVE_VARIANT` (เฉพาะ `modern`/`baseline`; ค่าที่ไม่ถูกต้องจะถูกละเว้น)\n- **variant ที่เลือก**: การแทนที่อย่างชัดเจน มิฉะนั้นใช้การตรวจจับ AVX2 รันไทม์บน x64 (`modern` หาก AVX2 มิฉะนั้น `baseline`)\n\n## การรองรับแพลตฟอร์มและการแก้ไขแท็ก\n\n`SUPPORTED_PLATFORMS` ถูกกำหนดไว้สำหรับ:\n\n- `linux-x64`\n- `linux-arm64`\n- `darwin-x64`\n- `darwin-arm64`\n- `win32-x64`\n\nรายละเอียดพฤติกรรม:\n\n- แพลตฟอร์มที่ไม่รองรับจะไม่ถูกปฏิเสธล่วงหน้า\n- ตัวโหลดยังคงลองผู้สมัครที่คำนวณทั้งหมดก่อน\n- หากไม่มีสิ่งใดโหลดได้ จะส่งข้อผิดพลาดแพลตฟอร์มที่ไม่รองรับอย่างชัดเจนพร้อมรายการแท็กที่รองรับ\n\nการทำเช่นนี้รักษาการวินิจฉัยที่มีประโยชน์สำหรับกรณีที่ใกล้เคียง ขณะที่ยังคงล้มเหลวอย่างชัดเจนสำหรับเป้าหมายที่ไม่รองรับจริงๆ\n\n## การเลือก variant (`modern` / `baseline` / ค่าเริ่มต้น)\n\n### พฤติกรรม x64\n\n1. หาก `PI_NATIVE_VARIANT` เป็น `modern` หรือ `baseline` ค่านั้นจะชนะ\n2. มิฉะนั้นตรวจจับการรองรับ AVX2:\n   - Linux: สแกน `/proc/cpuinfo` เพื่อหา `avx2`\n   - macOS: สอบถาม `sysctl` (`machdep.cpu.leaf7_features` ทางสำรอง `machdep.cpu.features`)\n   - Windows: รัน PowerShell `[System.Runtime.Intrinsics.X86.Avx2]::IsSupported`\n3. ผลลัพธ์:\n   - AVX2 พร้อมใช้งาน -> `modern`\n   - AVX2 ไม่พร้อมใช้งาน/ตรวจจับไม่ได้ -> `baseline`\n\n### พฤติกรรมที่ไม่ใช่ x64\n\n- ไม่มีการใช้ variant; ตัวโหลดยังคงใช้ชื่อไฟล์เริ่มต้น (`pi_natives.<platform>-<arch>.node`)\n\n### การสร้างชื่อไฟล์\n\nกำหนด `tag = <platform>-<arch>`:\n\n- ที่ไม่ใช่ x64 หรือไม่มี variant: `pi_natives.<tag>.node`\n- x64 + `modern`: ลองตามลำดับ\n  1. `pi_natives.<tag>-modern.node`\n  2. `pi_natives.<tag>-baseline.node` (ทางสำรองโดยเจตนา)\n- x64 + `baseline`: เฉพาะ `pi_natives.<tag>-baseline.node`\n\n`addonLabel` ที่ใช้ในข้อความแสดงข้อผิดพลาดสุดท้ายคือ `<tag>` หรือ `<tag> (<variant>)`\n\n## การสร้างเส้นทางผู้สมัครและลำดับทางสำรอง\n\n`native.ts` สร้างกลุ่มผู้สมัครก่อนการเรียก `require(...)` ใดๆ\n\n### ผู้สมัครรีลีส\n\nสร้างจากรายการชื่อไฟล์ที่แก้ไข variant แล้ว และค้นหาตามลำดับนี้:\n\n- **รันไทม์ที่ไม่ได้คอมไพล์**:\n  1. `<nativeDir>/<filename>`\n  2. `<execDir>/<filename>`\n\n- **รันไทม์ที่คอมไพล์แล้ว** (`PI_COMPILED` หรือเครื่องหมาย Bun embedded):\n  1. `<versionedDir>/<filename>`\n  2. `<userDataDir>/<filename>`\n  3. `<nativeDir>/<filename>`\n  4. `<execDir>/<filename>`\n\n`dedupedCandidates` ลบรายการซ้ำขณะรักษาลำดับการปรากฏครั้งแรก\n\n### ลำดับรันไทม์สุดท้าย\n\nในเวลาโหลด:\n\n1. ผู้สมัครการแตกไฟล์ที่ฝังไว้แบบเสริม (หากผลิต) จะถูกแทรกไว้ที่หน้า\n2. ผู้สมัครที่ซ้ำกำจัดแล้วที่เหลือจะถูกลองตามลำดับ\n3. ผู้สมัครแรกที่ทั้ง `require(...)` ได้และผ่าน `validateNative(...)` จะชนะ\n\n## วงจรชีวิตการแตกไฟล์ addon ที่ฝังไว้\n\n`embedded-addon.ts` กำหนดรูปร่าง manifest ที่สร้างขึ้น:\n\n- `platformTag`\n- `version`\n- `files[]` โดยแต่ละรายการมี `variant`, `filename`, `filePath`\n\nค่าเริ่มต้นที่ตรวจสอบในปัจจุบันคือ `embeddedAddon: null`; อาร์ติแฟกต์ที่คอมไพล์แล้วอาจแทนที่ด้วยข้อมูลเมตาจริง\n\n### เครื่องสถานะการแตกไฟล์\n\nการแตกไฟล์ (`maybeExtractEmbeddedAddon`) ทำงานเฉพาะเมื่อผ่านเงื่อนไขทั้งหมด:\n\n1. `isCompiledBinary === true`\n2. `embeddedAddon !== null`\n3. `embeddedAddon.platformTag === platformTag`\n4. `embeddedAddon.version === packageVersion`\n5. พบไฟล์ที่ฝังไว้ที่เหมาะสมกับ variant\n\nการเลือกไฟล์ variant สะท้อนความตั้งใจ variant รันไทม์:\n\n- ที่ไม่ใช่ x64: ต้องการ `default` จากนั้นไฟล์แรกที่มี\n- x64 + `modern`: ต้องการ `modern` ทางสำรองเป็น `baseline`\n- x64 + `baseline`: ต้องการ `baseline`\n\nพฤติกรรมการสร้างไฟล์:\n\n1. ตรวจสอบให้แน่ใจว่า `<versionedDir>` มีอยู่ (`mkdirSync(..., { recursive: true })`)\n2. หาก `<versionedDir>/<selected filename>` มีอยู่แล้ว ให้ใช้ซ้ำ (ไม่เขียนใหม่)\n3. มิฉะนั้น อ่านต้นฉบับที่ฝัง `filePath` และเขียนไฟล์เป้าหมาย\n4. ส่งคืนเส้นทางเป้าหมายสำหรับการพยายามโหลดลำดับความสำคัญสูงสุด\n\nเมื่อล้มเหลว การแตกไฟล์จะไม่หยุดทำงานทันที; แต่จะเพิ่มรายการข้อผิดพลาด (การสร้างไดเรกทอรีหรือความล้มเหลวในการเขียน) และตัวโหลดดำเนินการตรวจสอบผู้สมัครตามปกติต่อไป\n\n## วงจรชีวิตและการเปลี่ยนสถานะ\n\n```text\nInit\n  -> Compute platform/version/variant/candidate lists\n  -> (Compiled + embedded manifest matches?)\n       yes -> Try extract embedded to versionedDir (record errors, continue)\n       no  -> Skip extraction\n  -> For each runtime candidate in order:\n       require(candidate)\n       -> success: validateNative\n            -> pass: return bindings (READY)\n            -> fail: record error, continue\n       -> failure: record error, continue\n  -> none loaded:\n       if unsupported platform tag -> throw Unsupported platform\n       else -> throw Failed to load (full tried-path diagnostics + hints)\n```\n\n## การตรวจสอบข้อกำหนดของ `validateNative`\n\n`validateNative(bindings, source)` บังคับใช้ข้อกำหนดเฉพาะฟังก์ชันเหนือ `NativeBindings` เมื่อเริ่มต้น\n\nกลไก:\n\n- สำหรับชื่อ export ที่ต้องการแต่ละรายการ จะตรวจสอบ `typeof bindings[name] === \"function\"`\n- ชื่อที่หายไปจะถูกรวบรวม\n- หากมีชื่อที่หายไป ตัวโหลดจะส่ง:\n  - เส้นทาง addon ต้นทาง\n  - รายการ export ที่หายไป\n  - คำแนะนำคำสั่ง rebuild\n\nนี่คือประตูความเข้ากันได้แบบเข้มงวดสำหรับไบนารีที่ล้าสมัย การสร้างบางส่วน และการเลื่อนหลุดของ symbol/ชื่อ\n\n### การแมป JS API ↔ native export (ประตูการตรวจสอบ)\n\n| ชื่อ JS binding ที่ตรวจสอบใน `validateNative` | ชื่อ native export ที่คาดหวัง |\n| --- | --- |\n| `grep` | `grep` |\n| `glob` | `glob` |\n| `highlightCode` | `highlightCode` |\n| `executeShell` | `executeShell` |\n| `PtySession` | `PtySession` |\n| `Shell` | `Shell` |\n| `visibleWidth` | `visibleWidth` |\n| `getSystemInfo` | `getSystemInfo` |\n| `getWorkProfile` | `getWorkProfile` |\n| `invalidateFsScanCache` | `invalidateFsScanCache` |\n\nหมายเหตุ: `bindings.ts` ประกาศเฉพาะสมาชิก `cancelWork(id)` พื้นฐาน; ไฟล์ `types.ts` ของโมดูลรวม declaration-merge สัญลักษณ์เพิ่มเติมที่ `validateNative` บังคับใช้\n\n## พฤติกรรมความล้มเหลวและการวินิจฉัย\n\n## แพลตฟอร์มที่ไม่รองรับ\n\nหากผู้สมัครทั้งหมดล้มเหลวและ `platformTag` ไม่อยู่ใน `SUPPORTED_PLATFORMS` ตัวโหลดจะส่ง:\n\n- `Unsupported platform: <tag>`\n- รายการแพลตฟอร์มที่รองรับทั้งหมด\n- คำแนะนำการรายงานปัญหาอย่างชัดเจน\n\n## อาการไบนารีล้าสมัย / ไม่ตรงกัน\n\nสัญญาณความไม่ตรงกันของสิ่งล้าสมัยที่พบบ่อย:\n\n- `Native addon missing exports (<candidate>). Missing: ...`\n\nสาเหตุทั่วไป:\n\n- ไบนารี `.node` เก่าจากเวอร์ชันแพ็กเกจ/รูปร่าง API ก่อนหน้า\n- อาร์ติแฟกต์ variant ผิดที่เลือกสำหรับ x64\n- การ export ของ Rust ใหม่ที่ไม่มีในอาร์ติแฟกต์ที่โหลด\n\nพฤติกรรมตัวโหลด:\n\n- บันทึกความล้มเหลวของ missing-export ต่อผู้สมัคร\n- ดำเนินการตรวจสอบผู้สมัครที่เหลือต่อไป\n- หากไม่มีผู้สมัครผ่านการตรวจสอบ ข้อผิดพลาดสุดท้ายจะรวมทุกเส้นทางที่พยายามพร้อมข้อความความล้มเหลวแต่ละรายการ\n\n## ความล้มเหลวในการเริ่มต้นไบนารีที่คอมไพล์แล้ว\n\nในโหมดที่คอมไพล์แล้ว การวินิจฉัยสุดท้ายประกอบด้วย:\n\n- เส้นทางเป้าหมายแคชตามเวอร์ชันที่คาดหวัง (`<versionedDir>/<filename>`)\n- การแก้ไขเพื่อลบ `<versionedDir>` ที่ล้าสมัยและรันใหม่\n- คำสั่ง `curl` ดาวน์โหลดรีลีสโดยตรงสำหรับแต่ละชื่อไฟล์ที่คาดหวัง\n\n## ความล้มเหลวในการเริ่มต้นที่ไม่ได้คอมไพล์\n\nในโหมดแพ็กเกจ/รันไทม์ปกติ การวินิจฉัยสุดท้ายประกอบด้วย:\n\n- คำแนะนำการติดตั้งใหม่ (`bun install @f5-sales-demo/pi-natives`)\n- คำสั่ง rebuild ในพื้นที่ (`bun --cwd=packages/natives run build`)\n- คำแนะนำการ build variant x64 แบบเสริม (`TARGET_VARIANT=baseline|modern ...`)\n\n## พฤติกรรมรันไทม์\n\n- ตัวโหลดใช้ chain ผู้สมัครรีลีสเสมอ\n- การตั้งค่า `PI_DEV` เปิดใช้งานเฉพาะการวินิจฉัย console ต่อผู้สมัคร (`Loaded native addon...` และข้อผิดพลาดในการโหลด)\n",
	"th/natives/natives-architecture.md": "---\ntitle: สถาปัตยกรรม Natives\ndescription: >-\n  สถาปัตยกรรม native addon แบบ Rust N-API ที่เชื่อมต่อระหว่าง TypeScript\n  และการดำเนินการเฉพาะแพลตฟอร์ม\nsidebar:\n  order: 1\n  label: สถาปัตยกรรม\ni18n:\n  sourceHash: d38ed2437bb7\n  translator: machine\n---\n\n# สถาปัตยกรรม Natives\n\n`@f5-sales-demo/pi-natives` เป็นสแต็กสามชั้น:\n\n1. **ชั้น TypeScript wrapper/API** เปิดเผย entrypoint แบบ JS/TS ที่มีความเสถียร\n2. **ชั้นโหลด/ตรวจสอบ Addon** ค้นหาและตรวจสอบไบนารี `.node` สำหรับ runtime ปัจจุบัน\n3. **ชั้นโมดูล Rust N-API** ที่ implement primitives ที่ต้องการประสิทธิภาพสูงและส่งออกไปยัง JS\n\nเอกสารนี้เป็นรากฐานสำหรับเอกสารระดับโมดูลที่ลึกยิ่งขึ้น\n\n## ไฟล์ที่ implement\n\n- `packages/natives/src/index.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `crates/pi-natives/src/lib.rs`\n\n## ชั้นที่ 1: ชั้น TypeScript wrapper/API\n\n`packages/natives/src/index.ts` คือ public barrel โดยจัดกลุ่ม export ตามโดเมนความสามารถ และ re-export typed wrappers แทนที่จะเปิดเผย raw N-API bindings โดยตรง\n\nกลุ่มระดับบนสุดในปัจจุบัน:\n\n- **Search/text primitives**: `grep`, `glob`, `text`, `highlight`\n- **Execution/process/terminal primitives**: `shell`, `pty`, `ps`, `keys`\n- **System/media/conversion primitives**: `image`, `html`, `clipboard`, `system-info`, `work`\n\n`packages/natives/src/bindings.ts` กำหนด contract ของ interface พื้นฐาน:\n\n- `NativeBindings` เริ่มต้นด้วย shared members (`cancelWork(id: number)`)\n- binding เฉพาะโมดูลถูกเพิ่มโดย declaration merging จาก `types.ts` ของแต่ละโมดูล\n- `Cancellable` กำหนดมาตรฐาน option ของ timeout และ abort-signal สำหรับ wrapper ที่เปิดเผยการยกเลิก\n\n**Contract ที่รับประกัน (ด้าน API):** ผู้ใช้งาน import จาก `@f5-sales-demo/pi-natives` และใช้ typed wrappers\n\n**รายละเอียด implement (อาจเปลี่ยนแปลงได้):** declaration merging และ layout ภายในของ wrapper (`src/<module>/index.ts`, `src/<module>/types.ts`)\n\n## ชั้นที่ 2: การโหลดและตรวจสอบ Addon\n\n`packages/natives/src/native.ts` จัดการการเลือก addon ในเวลา runtime การแตกไฟล์แบบ optional และการตรวจสอบ export\n\n### รูปแบบการค้นหา Candidate\n\n- Platform tag คือ `\"${process.platform}-${process.arch}\"`\n- tags ที่รองรับในปัจจุบัน:\n  - `linux-x64`\n  - `linux-arm64`\n  - `darwin-x64`\n  - `darwin-arm64`\n  - `win32-x64`\n- x64 สามารถใช้ CPU variants ได้:\n  - `modern` (รองรับ AVX2)\n  - `baseline` (fallback)\n- non-x64 ใช้ filename เริ่มต้น (ไม่มี variant suffix)\n\nกลยุทธ์ชื่อไฟล์:\n\n- Release: `pi_natives.<platform>-<arch>.node`\n- x64 variant release: `pi_natives.<platform>-<arch>-modern.node` และ/หรือ `...-baseline.node`\n- `PI_DEV` เปิดใช้งาน loader diagnostics แต่ไม่เปลี่ยนชื่อไฟล์ addon\n\n### การตรวจจับ Variant เฉพาะแพลตฟอร์ม\n\nสำหรับ x64 การเลือก variant ใช้:\n\n- **Linux**: `/proc/cpuinfo`\n- **macOS**: `sysctl machdep.cpu.leaf7_features` / `machdep.cpu.features`\n- **Windows**: PowerShell ตรวจสอบ `System.Runtime.Intrinsics.X86.Avx2`\n\n`PI_NATIVE_VARIANT` สามารถบังคับให้ใช้ `modern` หรือ `baseline` ได้อย่างชัดเจน\n\n### รูปแบบการแจกจ่ายและแตกไฟล์ไบนารี\n\n`packages/natives/package.json` รวมทั้ง `src` และ `native` ไว้ในไฟล์ที่เผยแพร่ โดยไดเรกทอรี `native/` เก็บ artifact ของแพลตฟอร์มที่สร้างไว้ล่วงหน้า\n\nสำหรับไบนารีที่คอมไพล์แล้ว (`PI_COMPILED` หรือ Bun embedded runtime markers) พฤติกรรมของ loader คือ:\n\n1. ตรวจสอบ versioned user cache path: `<getNativesDir()>/<packageVersion>/...`\n2. ตรวจสอบตำแหน่ง compiled-binary แบบ legacy:\n   - Windows: `%LOCALAPPDATA%/xcsh` (fallback `%USERPROFILE%/AppData/Local/xcsh`)\n   - non-Windows: `~/.local/bin`\n3. ใช้ `native/` ที่แพ็กเกจมาและ candidates ในไดเรกทอรีของ executable เป็น fallback\n\nหาก embedded addon manifest มีอยู่ (`embedded-addon.ts` ที่สร้างโดย `scripts/embed-native.ts`) `native.ts` สามารถแตกไบนารีที่ฝังตรงกันไปยังไดเรกทอรี versioned cache ก่อนโหลด\n\n### การตรวจสอบและโหมดความล้มเหลว\n\nหลังจาก `require(candidate)` แล้ว `validateNative(...)` จะตรวจสอบ export ที่จำเป็น (เช่น `grep`, `glob`, `highlightCode`, `PtySession`, `Shell`, `getSystemInfo`, `getWorkProfile`, `invalidateFsScanCache`)\n\nเส้นทางความล้มเหลวมีการระบุชัดเจน:\n\n- **Platform tag ที่ไม่รองรับ**: throw พร้อมรายการแพลตฟอร์มที่รองรับ\n- **ไม่มี candidate ที่โหลดได้**: throw พร้อมเส้นทางทั้งหมดที่ลองแล้วและคำแนะนำในการแก้ไข\n- **Export ที่ขาดหายไป**: throw พร้อมชื่อที่ขาดหายไปและคำสั่ง rebuild\n- **ข้อผิดพลาดการแตก embedded**: บันทึกความล้มเหลวของไดเรกทอรี/การเขียน และรวมไว้ใน load diagnostics สุดท้าย\n\n**Contract ที่รับประกัน (ด้าน API):** การโหลด addon จะสำเร็จพร้อม binding set ที่ตรวจสอบแล้ว หรือล้มเหลวอย่างรวดเร็วพร้อมข้อความแสดงข้อผิดพลาดที่นำไปดำเนินการได้\n\n**รายละเอียด implement (อาจเปลี่ยนแปลงได้):** ลำดับการค้นหา candidate และการเรียงลำดับ fallback path ของ compiled-binary\n\n## ชั้นที่ 3: ชั้นโมดูล Rust N-API\n\n`crates/pi-natives/src/lib.rs` คือ Rust entry module ที่ประกาศความเป็นเจ้าของโมดูลที่ส่งออก:\n\n- `clipboard`\n- `fd`\n- `fs_cache`\n- `glob`\n- `glob_util`\n- `grep`\n- `highlight`\n- `html`\n- `image`\n- `keys`\n- `prof`\n- `ps`\n- `pty`\n- `shell`\n- `system_info`\n- `task`\n- `text`\n\nโมดูลเหล่านี้ implement สัญลักษณ์ N-API ที่ถูกใช้งานและตรวจสอบโดย `native.ts` โดยชื่อระดับ JS จะถูกแสดงผ่าน TS wrappers ใน `packages/natives/src`\n\n**Contract ที่รับประกัน (ด้าน API):** export ของโมดูล Rust ต้องตรงกับชื่อ binding ที่คาดหวังโดย `validateNative` และโมดูล wrapper\n\n**รายละเอียด implement (อาจเปลี่ยนแปลงได้):** การแบ่งโมดูล Rust ภายในและขอบเขตของโมดูลตัวช่วย (`glob_util`, `task` ฯลฯ)\n\n## ขอบเขตความเป็นเจ้าของ\n\nในระดับสถาปัตยกรรม ความเป็นเจ้าของแบ่งดังนี้:\n\n- **TS wrapper/API ownership (`packages/natives/src`)**\n  - การจัดกลุ่ม public API การกำหนดประเภท option และ JS ergonomics ที่เสถียร\n  - surface การยกเลิก (`timeoutMs`, `AbortSignal`) ที่เปิดเผยแก่ผู้เรียก\n- **Loader ownership (`packages/natives/src/native.ts`)**\n  - การเลือกไบนารีในเวลา runtime\n  - การเลือก CPU variant และการจัดการ override\n  - การแตก compiled-binary และการตรวจสอบ candidate\n  - การตรวจสอบแบบ hard ของ native exports ที่จำเป็น\n- **Rust ownership (`crates/pi-natives/src`)**\n  - การ implement เชิงอัลกอริทึมและระดับระบบ\n  - พฤติกรรมเฉพาะแพลตฟอร์มและ logic ที่ต้องการประสิทธิภาพสูง\n  - การ implement สัญลักษณ์ N-API ที่ TS wrappers ใช้งาน\n\n## ขั้นตอนการทำงาน runtime (ภาพรวม)\n\n1. ผู้ใช้งาน import จาก `@f5-sales-demo/pi-natives`\n2. โมดูล wrapper เรียกใช้ singleton `native` binding\n3. `native.ts` เลือกไบนารี candidate สำหรับ platform/arch/variant\n4. การแตก embedded binary แบบ optional เกิดขึ้นสำหรับ compiled distributions\n5. Addon ถูกโหลดและชุด export ถูกตรวจสอบ\n6. Wrapper คืนค่าผลลัพธ์แบบ typed ให้ผู้เรียก\n\n## อภิธานศัพท์\n\n- **Native addon**: ไบนารี `.node` ที่โหลดผ่าน Node-API (N-API)\n- **Platform tag**: tuple ของ runtime `platform-arch` (เช่น `darwin-arm64`)\n- **Variant**: flavor ของการสร้างเฉพาะ CPU x64 (`modern` AVX2, `baseline` fallback)\n- **Wrapper**: TS function/class ที่ให้ typed API เหนือ raw native exports\n- **Declaration merging**: เทคนิค TS ที่ใช้โดยไฟล์ `types.ts` ของโมดูลเพื่อขยาย `NativeBindings`\n- **Compiled binary mode**: โหมด runtime ที่ CLI ถูกบันเดิลและ native addons ถูกค้นหาจากเส้นทาง extracted/cache แทนที่จะเป็นเฉพาะเส้นทาง package-local\n- **Embedded addon**: metadata ของ build artifact และการอ้างอิงไฟล์ที่สร้างลงใน `embedded-addon.ts` เพื่อให้ compiled binaries สามารถแตก payload `.node` ที่ตรงกันได้\n- **Validation gate**: การตรวจสอบ `validateNative(...)` ที่ปฏิเสธไบนารีที่ล้าสมัย/ไม่ตรงกัน ซึ่งขาด export ที่จำเป็น\n",
	"th/natives/natives-binding-contract.md": "---\ntitle: สัญญาการผูก Natives (ฝั่ง TypeScript)\ndescription: >-\n  สัญญาการผูกฝั่ง TypeScript สำหรับการเรียกใช้ฟังก์ชัน native ของ Rust ผ่าน\n  N-API\nsidebar:\n  order: 2\n  label: สัญญาการผูก\ni18n:\n  sourceHash: 36dc5fed1f0a\n  translator: machine\n---\n\n# สัญญาการผูก Natives (ฝั่ง TypeScript)\n\nเอกสารนี้กำหนดสัญญาฝั่ง TypeScript ที่อยู่ระหว่างผู้เรียกใช้ `@f5-sales-demo/pi-natives` และ N-API addon ที่โหลดไว้\n\nเอกสารนี้มุ่งเน้นที่สามส่วน:\n\n1. รูปแบบสัญญา (`NativeBindings` + module augmentation),\n2. พฤติกรรมของ wrapper (`src/<module>/index.ts`),\n3. พื้นผิวการส่งออกสาธารณะ (`src/index.ts`)\n\n## ไฟล์ที่นำมาใช้งาน\n\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/system-info/types.ts`\n- `packages/natives/src/system-info/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/work/types.ts`\n- `packages/natives/src/work/index.ts`\n\n## โมเดลสัญญา\n\n`packages/natives/src/bindings.ts` กำหนดสัญญาพื้นฐาน:\n\n- `NativeBindings` (อินเทอร์เฟซพื้นฐาน ปัจจุบันรวมถึง `cancelWork(id: number): void`)\n- `Cancellable` (`timeoutMs?: number`, `signal?: AbortSignal`)\n- รูปแบบ callback `TsFunc<T>` ที่ใช้โดย N-API threadsafe callbacks\n\nแต่ละโมดูลเพิ่มฟิลด์ของตนเองผ่าน declaration merging:\n\n```ts\n// packages/natives/src/<module>/types.ts\ndeclare module \"../bindings\" {\n interface NativeBindings {\n  grep(options: GrepOptions, onMatch?: TsFunc<GrepMatch>): Promise<GrepResult>;\n }\n}\n```\n\nวิธีนี้รักษาอินเทอร์เฟซการผูกรวมเดียวไว้โดยไม่ต้องมีไฟล์ประเภทกลางที่ใหญ่โต\n\n## วงจรชีวิต declaration-merging และการเปลี่ยนสถานะ\n\n### 1) การประกอบประเภทในเวลาคอมไพล์\n\n- `bindings.ts` ให้สัญลักษณ์ `NativeBindings` พื้นฐาน\n- ทุก `src/<module>/types.ts` เพิ่มส่วนขยายให้กับ `NativeBindings`\n- `src/native.ts` นำเข้าไฟล์ `./<module>/types` ทั้งหมดเพื่อผลข้างเคียง เพื่อให้สัญญาที่ผสานแล้วอยู่ในขอบเขตที่ใช้ `NativeBindings`\n\nการเปลี่ยนสถานะ: **สัญญาพื้นฐาน** → **สัญญาที่ผสานแล้ว**\n\n### 2) การโหลด addon ขณะ runtime และเกตการตรวจสอบ\n\n- `src/native.ts` โหลดไบนารี `.node` ที่เป็นตัวเลือก\n- อ็อบเจกต์ที่โหลดถูกจัดการเป็น `NativeBindings` และส่งผ่าน `validateNative(...)` ทันที\n- `validateNative` ตรวจสอบคีย์การส่งออกที่จำเป็นด้วย `typeof bindings[name] === \"function\"`\n\nการเปลี่ยนสถานะ: **อ็อบเจกต์ addon ที่ยังไม่ผ่านการตรวจสอบ** → **อ็อบเจกต์การผูก native ที่ผ่านการตรวจสอบแล้ว** (หรือเกิดความล้มเหลวอย่างสมบูรณ์)\n\n### 3) การเรียกใช้ wrapper\n\n- Module wrapper ใน `src/<module>/index.ts` เรียกใช้ `native.<export>`\n- Wrapper ปรับค่าเริ่มต้นและรูปแบบ callback (รูปแบบ `(err, value)` เป็นรูปแบบ callback ที่รับเฉพาะค่าใน JS APIs)\n- `src/index.ts` ส่งออกซ้ำ module wrapper/types เป็น API แพ็กเกจสาธารณะ\n\nการเปลี่ยนสถานะ: **การผูก raw ที่ผ่านการตรวจสอบแล้ว** → **API สาธารณะที่ใช้งานสะดวก**\n\n## ความรับผิดชอบของ wrapper\n\nWrapper ถูกออกแบบให้บางโดยตั้งใจ; ไม่นำตรรกะ native มาใช้ซ้ำ\n\nความรับผิดชอบหลัก:\n\n- **การปรับมาตรฐาน/กำหนดค่าเริ่มต้นของอาร์กิวเมนต์**\n  - `glob()` แปลง `options.path` เป็น absolute path และกำหนดค่าเริ่มต้นให้ `hidden`, `gitignore`, `recursive`\n  - `hasMatch()` เติมค่าเริ่มต้นของ flags (`ignoreCase`, `multiline`) ก่อนเรียกใช้ native\n- **การปรับ callback**\n  - `grep()`, `glob()`, `executeShell()` แปลง `TsFunc<T>` (`error, value`) เป็น user callback ที่รับเฉพาะค่าที่สำเร็จ\n- **พฤติกรรมของสภาพแวดล้อมหรือนโยบายรอบการเรียกใช้ native**\n  - Clipboard wrapper เพิ่มการจัดการ OSC52/Termux/headless และจัดการการคัดลอกเป็นแบบ best effort\n- **การตั้งชื่อสาธารณะและการจัดการการส่งออกซ้ำ**\n  - `searchContent()` แมปไปยังการส่งออก native `search`\n\n## การจัดระเบียบพื้นผิวการส่งออกสาธารณะ\n\n`packages/natives/src/index.ts` เป็น public barrel ที่เป็นมาตรฐาน โดยจัดกลุ่มการส่งออกตามโดเมนความสามารถ:\n\n- ค้นหา/ข้อความ: `grep`, `glob`, `text`, `highlight`\n- การดำเนินการ/กระบวนการ/เทอร์มินัล: `shell`, `pty`, `ps`, `keys`\n- ระบบ/สื่อ/การแปลง: `image`, `html`, `clipboard`, `system-info`, `work`\n\nกฎของผู้ดูแล: หาก wrapper ไม่ถูกส่งออกซ้ำจาก `src/index.ts` ก็ไม่ถือเป็นส่วนหนึ่งของพื้นผิวแพ็กเกจสาธารณะที่ตั้งใจไว้\n\n## การแมป JS API ↔ native export (ตัวอย่างที่เป็นตัวแทน)\n\nฝั่ง Rust ใช้ชื่อการส่งออก N-API (โดยทั่วไปจากการแปลง `#[napi]` snake_case -> camelCase พร้อม alias ที่กำหนดเองในบางครั้ง) ที่ต้องตรงกับคีย์การผูกเหล่านี้\n\n| หมวดหมู่ | JS API สาธารณะ (wrapper) | คีย์การผูก native | ประเภทที่คืนค่า | Async? |\n|---|---|---|---|---|\n| Grep | `grep(options, onMatch?)` | `grep` | `Promise<GrepResult>` | ใช่ |\n| Grep | `searchContent(content, options)` | `search` | `SearchResult` | ไม่ |\n| Grep | `hasMatch(content, pattern, opts?)` | `hasMatch` | `boolean` | ไม่ |\n| Grep | `fuzzyFind(options)` | `fuzzyFind` | `Promise<FuzzyFindResult>` | ใช่ |\n| Glob | `glob(options, onMatch?)` | `glob` | `Promise<GlobResult>` | ใช่ |\n| Glob | `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `void` | ไม่ |\n| Shell | `executeShell(options, onChunk?)` | `executeShell` | `Promise<ShellExecuteResult>` | ใช่ |\n| Shell | `Shell` | `Shell` | class constructor | ไม่มี |\n| PTY | `PtySession` | `PtySession` | class constructor | ไม่มี |\n| Text | `truncateToWidth(...)` | `truncateToWidth` | `string` | ไม่ |\n| Text | `sliceWithWidth(...)` | `sliceWithWidth` | `SliceWithWidthResult` | ไม่ |\n| Text | `visibleWidth(text)` | `visibleWidth` | `number` | ไม่ |\n| Highlight | `highlightCode(code, lang, colors)` | `highlightCode` | `string` | ไม่ |\n| HTML | `htmlToMarkdown(html, options?)` | `htmlToMarkdown` | `Promise<string>` | ใช่ |\n| System | `getSystemInfo()` | `getSystemInfo` | `SystemInfo` | ไม่ |\n| Work | `getWorkProfile(lastSeconds)` | `getWorkProfile` | `WorkProfile` | ไม่ |\n| Process | `killTree(pid, signal)` | `killTree` | `number` | ไม่ |\n| Process | `listDescendants(pid)` | `listDescendants` | `number[]` | ไม่ |\n| Clipboard | `copyToClipboard(text)` | `copyToClipboard` | `Promise<void>` (พฤติกรรม wrapper แบบ best effort) | ใช่ |\n| Clipboard | `readImageFromClipboard()` | `readImageFromClipboard` | `Promise<ClipboardImage \\| null>` | ใช่ |\n| Keys | `parseKey(data, kittyProtocolActive)` | `parseKey` | `string \\| null` | ไม่ |\n\n## ความแตกต่างของสัญญา sync และ async\n\nสัญญาผสม API แบบ sync และ async; wrapper รักษารูปแบบการเรียกใช้ native แทนที่จะบังคับใช้รูปแบบเดียว:\n\n- **การส่งออกแบบ Promise-based async** สำหรับ I/O หรืองานที่ทำงานนาน (`grep`, `glob`, `htmlToMarkdown`, `executeShell`, clipboard, การดำเนินการรูปภาพ)\n- **การส่งออกแบบ Synchronous** สำหรับการแปลงในหน่วยความจำแบบ deterministic/parsers (`search`, `hasMatch`, highlighting, ความกว้าง/การตัดข้อความ, การแยกวิเคราะห์คีย์, การสืบค้นกระบวนการ)\n- **การส่งออกแบบ Constructor** สำหรับอ็อบเจกต์ runtime ที่มีสถานะ (`Shell`, `PtySession`, `PhotonImage`)\n\nผลกระทบต่อผู้ดูแล: การเปลี่ยนแปลง sync ↔ async สำหรับการส่งออกที่มีอยู่ถือเป็นการเปลี่ยนแปลง API และสัญญาที่ทำลายความเข้ากันได้กับ wrapper และผู้เรียกใช้\n\n## รูปแบบการกำหนดประเภทของ Object และ enum\n\n### รูปแบบ Object (`#[napi(object)]`-style JS objects)\n\nTS สร้างแบบจำลองค่า native ที่มีรูปร่างเป็น object เป็น interface ตัวอย่างเช่น:\n\n- `GrepResult`, `SearchResult`, `GlobResult`\n- `SystemInfo`, `WorkProfile`\n- `ClipboardImage`, `ParsedKittyResult`\n\nสิ่งเหล่านี้เป็นสัญญาเชิงโครงสร้างในเวลาคอมไพล์; ความถูกต้องของรูปร่างในขณะ runtime เป็นของ native implementation\n\n### รูปแบบ Enum\n\nNative enum แบบตัวเลขถูกแสดงเป็นค่า `const enum` ใน TS:\n\n- `FileType` (`1=file`, `2=dir`, `3=symlink`)\n- `ImageFormat` (`0=PNG`, `1=JPEG`, `2=WEBP`, `3=GIF`)\n- `SamplingFilter`, `Ellipsis`, `KeyEventType`\n\nผู้เรียกใช้เห็นสมาชิก enum ที่ตั้งชื่อแล้ว; ขอบเขตการผูกส่งผ่านตัวเลข\n\n## วิธีการตรวจจับความไม่ตรงกัน\n\nการตรวจจับความไม่ตรงกันเกิดขึ้นในสองชั้น:\n\n1. **การตรวจสอบสัญญา TypeScript ในเวลาคอมไพล์**\n   - Wrapper เรียกใช้ `native.<name>` กับ `NativeBindings` ที่ผสานแล้ว\n   - คีย์การผูกที่หายไป/เปลี่ยนชื่อทำให้ TypeScript type-checking ใน wrapper ล้มเหลว\n\n2. **การตรวจสอบขณะ runtime ใน `validateNative`**\n   - หลังจากโหลด `native.ts` จะตรวจสอบการส่งออกที่จำเป็นและโยน error หากมีส่วนที่ขาดหายไป\n   - ข้อความ error รวมถึงคีย์ที่หายไปและคำแนะนำในการ rebuild\n\nวิธีนี้ตรวจจับปัญหา stale-binary drift ที่พบบ่อย: wrapper/type มีอยู่แต่ `.node` ที่โหลดขาดการส่งออก\n\n## พฤติกรรมความล้มเหลวและข้อควรระวัง\n\n### ความล้มเหลวในการโหลด/ตรวจสอบ (ความล้มเหลวอย่างสมบูรณ์)\n\n- ความล้มเหลวในการโหลด addon หรือแพลตฟอร์มที่ไม่รองรับจะโยน error ระหว่างการเริ่มต้น module ใน `native.ts`\n- การส่งออกที่จำเป็นที่หายไปจะโยน error ก่อนที่ wrapper จะสามารถใช้งานได้\n\nผล: แพ็กเกจล้มเหลวอย่างรวดเร็วแทนที่จะเลื่อนความล้มเหลวไปยังการเรียกใช้ครั้งแรก\n\n### ความแตกต่างพฤติกรรมระดับ wrapper\n\n- บาง wrapper ลดความรุนแรงของความล้มเหลวโดยตั้งใจ (`copyToClipboard` เป็นแบบ best effort และกลืนความล้มเหลวของ native)\n- Streaming callback ละเว้น error payload ของ callback และส่งต่อเฉพาะ event ค่าที่สำเร็จ\n\n### ข้อควรระวังระดับ type (runtime เข้มงวดกว่า TS)\n\n- ฟิลด์ optional ของ TS ไม่รับประกันความถูกต้องทางความหมาย; ชั้น native ยังคงสามารถปฏิเสธค่าที่ไม่ถูกต้องได้\n- การกำหนดประเภท `const enum` ไม่ป้องกันค่าตัวเลขที่อยู่นอกช่วงจากผู้เรียกใช้ที่ไม่มีประเภทในขณะ runtime\n- `validateNative` ตรวจสอบเฉพาะการมีอยู่/ความเป็น function ของการส่งออกที่จำเป็น ไม่ใช่ความเข้ากันได้เชิงลึกของอาร์กิวเมนต์/รูปร่างที่คืนค่า\n- `bindings.ts` รวมถึง `cancelWork(id)` ในอินเทอร์เฟซพื้นฐาน แต่รายการตรวจสอบความถูกต้อง runtime ปัจจุบันไม่บังคับใช้คีย์นั้น\n\n## รายการตรวจสอบของผู้ดูแลสำหรับการเปลี่ยนแปลงการผูก\n\nเมื่อเพิ่ม/เปลี่ยนแปลงการส่งออก ให้อัปเดตทั้งหมดของ:\n\n1. `src/<module>/types.ts` (augmentation + contract types)\n2. `src/<module>/index.ts` (พฤติกรรม wrapper)\n3. การนำเข้า `src/native.ts` สำหรับ module types (หากเป็น module ใหม่)\n4. การตรวจสอบการส่งออกที่จำเป็นของ `validateNative`\n5. การส่งออกซ้ำสาธารณะของ `src/index.ts`\n\nการข้ามขั้นตอนใดก็ตามจะสร้างการเบี่ยงเบนในเวลาคอมไพล์หรือความล้มเหลวในเวลาโหลด runtime\n",
	"th/natives/natives-build-release-debugging.md": "---\ntitle: 'คู่มือการสร้าง, ปล่อยเวอร์ชัน และแก้ไขข้อผิดพลาดของ Natives'\ndescription: >-\n  คู่มือการสร้าง, ปล่อยเวอร์ชัน และแก้ไขข้อผิดพลาดสำหรับ Rust native addon\n  บนหลายแพลตฟอร์ม\nsidebar:\n  order: 8\n  label: 'การสร้าง, ปล่อยเวอร์ชัน และแก้ไขข้อผิดพลาด'\ni18n:\n  sourceHash: efe47aa5b466\n  translator: machine\n---\n\n# คู่มือการสร้าง, ปล่อยเวอร์ชัน และแก้ไขข้อผิดพลาดของ Natives\n\nคู่มือนี้อธิบายวิธีที่ไปป์ไลน์การสร้างของ `@f5-sales-demo/pi-natives` ผลิตไฟล์ `.node` addons, วิธีที่การแจกจ่ายที่คอมไพล์แล้วโหลดไฟล์เหล่านั้น, และวิธีแก้ไขข้อผิดพลาดของ loader/build\n\nคู่มือนี้ใช้คำศัพท์ด้านสถาปัตยกรรมจาก `docs/natives-architecture.md`:\n\n- **การผลิต artifact ในช่วง build-time** (`scripts/build-native.ts`)\n- **การสร้าง embedded addon manifest** (`scripts/embed-native.ts`)\n- **การโหลด addon ขณะ runtime + validation gate** (`src/native.ts`)\n\n## ไฟล์ที่เกี่ยวข้องกับการนำไปใช้งาน\n\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `packages/natives/src/native.ts`\n- `crates/pi-natives/Cargo.toml`\n\n## ภาพรวมของไปป์ไลน์การสร้าง\n\n### 1) จุดเริ่มต้นการสร้าง\n\nสคริปต์ใน `packages/natives/package.json`:\n\n- `bun scripts/build-native.ts` (`build`) → สร้างแบบ release\n- `bun scripts/build-native.ts --dev` (`dev:native`) → สร้างแบบ debug/dev profile (ชื่อ output เหมือนกัน)\n- `bun scripts/embed-native.ts` (`embed:native`) → สร้าง `src/embedded-addon.ts` จากไฟล์ที่สร้างแล้ว\n\n### 2) การสร้าง Rust artifact\n\n`build-native.ts` รัน Cargo ใน `crates/pi-natives`:\n\n- คำสั่งพื้นฐาน: `cargo build`\n- โหมด release เพิ่ม `--release` เว้นแต่จะส่ง `--dev`\n- cross target เพิ่ม `--target <CROSS_TARGET>`\n\n`crates/pi-natives/Cargo.toml` ประกาศ `crate-type = [\"cdylib\"]` ทำให้ Cargo สร้าง shared library (`.so`/`.dylib`/`.dll`) ซึ่งจะถูกคัดลอก/เปลี่ยนชื่อเป็นชื่อไฟล์ `.node` addon\n\n### 3) การค้นหาและติดตั้ง artifact\n\nหลังจาก Cargo เสร็จสิ้น `build-native.ts` จะสแกนไดเรกทอรี output ที่เป็นตัวเลือกตามลำดับ:\n\n1. `${CARGO_TARGET_DIR}` (ถ้ากำหนดไว้)\n2. `<repo>/target`\n3. `crates/pi-natives/target`\n\nสำหรับแต่ละ root จะตรวจสอบไดเรกทอรี profile:\n\n- cross build: `<root>/<crossTarget>/<profile>` จากนั้น `<root>/<profile>`\n- native build: `<root>/<profile>`\n\nจากนั้นจะค้นหาหนึ่งในไฟล์ต่อไปนี้:\n\n- `libpi_natives.so`\n- `libpi_natives.dylib`\n- `pi_natives.dll`\n- `libpi_natives.dll`\n\nเมื่อพบแล้ว จะติดตั้งแบบ atomic ลงใน `packages/natives/native/` โดยใช้ temp-file + rename semantics (การ fallback บน Windows จัดการกับความล้มเหลวในการแทนที่ DLL ที่ถูกล็อกไว้อย่างชัดเจน)\n\n## โมเดล Target/Variant และข้อตกลงการตั้งชื่อ\n\n## Platform tag\n\nทั้งการสร้างและ runtime ใช้ platform tag:\n\n`<platform>-<arch>` (ตัวอย่าง: `darwin-arm64`, `linux-x64`)\n\n## โมเดล Variant (สำหรับ x64 เท่านั้น)\n\nx64 รองรับ CPU variants:\n\n- `modern` (เส้นทางที่รองรับ AVX2)\n- `baseline` (สำรอง)\n\nสถาปัตยกรรมที่ไม่ใช่ x64 ใช้ artifact เดียวแบบ default (ไม่มีส่วนต่อท้าย variant)\n\n### ชื่อไฟล์ output\n\nการสร้างแบบ release:\n\n- x64: `pi_natives.<platform>-<arch>-modern.node` หรือ `...-baseline.node`\n- ไม่ใช่ x64: `pi_natives.<platform>-<arch>.node`\n\nการสร้างแบบ dev (`--dev`):\n\n- ใช้ debug profile flags แต่คงชื่อ output ที่มี platform tag มาตรฐานไว้\n\nลำดับตัวเลือกของ runtime loader ใน `native.ts`:\n\n- ตัวเลือก release\n- โหมด compiled จะเพิ่มตัวเลือกที่แตกไฟล์/แคชไว้ก่อนไฟล์ในแพ็กเกจ\n\n## Environment flags และตัวเลือกการสร้าง\n\n## Runtime flags\n\n- `PI_DEV` (พฤติกรรม loader): เปิดใช้งานการวินิจฉัย loader\n- `PI_NATIVE_VARIANT` (พฤติกรรม loader, สำหรับ x64 เท่านั้น): บังคับเลือก `modern` หรือ `baseline` ขณะ runtime\n- `PI_COMPILED` (พฤติกรรม loader): เปิดใช้งานพฤติกรรมการเลือก candidate/แตกไฟล์ของ compiled-binary\n\n## Build-time flags/ตัวเลือก\n\n- `--dev` (อาร์กิวเมนต์ของสคริปต์): สร้าง debug profile\n- `CROSS_TARGET`: ส่งให้ Cargo `--target`\n- `TARGET_PLATFORM`: แทนที่การตั้งชื่อ platform tag ใน output\n- `TARGET_ARCH`: แทนที่การตั้งชื่อ arch ใน output\n- `TARGET_VARIANT` (สำหรับ x64 เท่านั้น): บังคับ `modern` หรือ `baseline` สำหรับชื่อไฟล์ output และนโยบาย RUSTFLAGS\n- `CARGO_TARGET_DIR`: root เพิ่มเติมเมื่อค้นหา Cargo outputs\n- `RUSTFLAGS`:\n  - ถ้าไม่ได้กำหนดและไม่ใช่ cross-compiling สคริปต์จะกำหนด:\n    - modern: `-C target-cpu=x86-64-v3`\n    - baseline: `-C target-cpu=x86-64-v2`\n    - ไม่ใช่ x64 / ไม่มี variant: `-C target-cpu=native`\n  - ถ้ากำหนดไว้แล้ว สคริปต์จะไม่แทนที่\n\n## สถานะ/การเปลี่ยนแปลงวงจรชีวิตของการสร้าง\n\n### วงจรชีวิตการสร้าง (`build-native.ts`)\n\n1. **เริ่มต้น**: แยกวิเคราะห์ args/env (`--dev`, target overrides, cross flags)\n2. **ระบุ Variant**:\n   - ไม่ใช่ x64 → ไม่มี variant\n   - x64 + `TARGET_VARIANT` → explicit variant\n   - x64 cross-build ไม่มี `TARGET_VARIANT` → ข้อผิดพลาดร้ายแรง\n   - x64 local build ไม่มี override → ตรวจสอบ host AVX2\n3. **คอมไพล์**: รัน Cargo ด้วย profile/target ที่ระบุแล้ว\n4. **ค้นหา artifact**: สแกน target roots/profile dirs/library names\n5. **ติดตั้ง**: คัดลอก + atomic rename ลงใน `packages/natives/native`\n6. **เสร็จสิ้น**: addon พร้อมสำหรับตัวเลือกของ loader\n\nการออกจากโปรแกรมเมื่อเกิดความล้มเหลวจะเกิดขึ้นในทุกขั้นตอนพร้อมข้อความแสดงข้อผิดพลาดที่ชัดเจน (variant ไม่ถูกต้อง, cargo build ล้มเหลว, ไม่พบ output library, ความล้มเหลวในการติดตั้ง/rename)\n\n### วงจรชีวิต Embed (`embed-native.ts`)\n\n1. **เริ่มต้น**: คำนวณ platform tag จาก `TARGET_PLATFORM`/`TARGET_ARCH` หรือค่า host\n2. **ชุดตัวเลือก**:\n   - x64 คาดหวังทั้ง `modern` และ `baseline`\n   - ไม่ใช่ x64 คาดหวังไฟล์ default หนึ่งไฟล์\n3. **ตรวจสอบความพร้อมใช้งาน** ใน `packages/natives/native`\n4. **สร้าง manifest** (`src/embedded-addon.ts`) ด้วย Bun `file` imports และ package version\n5. **พร้อมแตกไฟล์ขณะ runtime** สำหรับโหมด compiled\n\n`--reset` จะข้ามการตรวจสอบและเขียน null manifest stub (`embeddedAddon = null`)\n\n## เวิร์กโฟลว์การพัฒนา vs พฤติกรรม shipped/compiled\n\n## เวิร์กโฟลว์การพัฒนาในเครื่อง\n\nวงจรการทำงานในเครื่องทั่วไป:\n\n1. สร้าง addon:\n   - release: `bun --cwd=packages/natives run build`\n   - debug profile: `bun --cwd=packages/natives run dev:native`\n2. กำหนด `PI_DEV=1` เมื่อทดสอบการวินิจฉัย loader\n3. Loader ใน `native.ts` ค้นหาตัวเลือกใน `native/` ของแพ็กเกจ (และ executable-dir fallback)\n4. `validateNative` บังคับใช้ความเข้ากันได้ของ export ก่อนที่ wrappers จะใช้ binding\n\n## เวิร์กโฟลว์ไบนารี shipped/compiled\n\nในโหมด compiled (`PI_COMPILED` หรือ Bun embedded markers):\n\n1. Loader คำนวณ versioned cache dir: `<getNativesDir()>/<packageVersion>` (ในการใช้งานจริงคือ `~/.xcsh/natives/<version>`)\n2. ถ้า embedded manifest ตรงกับ platform+version ปัจจุบัน loader อาจแตกไฟล์ที่เลือกไว้ใน embedded ลงในไดเรกทอรีที่มี version นั้น\n3. ลำดับตัวเลือกขณะ runtime รวมถึง:\n   - versioned cache dir\n   - legacy compiled-binary dir (`%LOCALAPPDATA%/xcsh` บน Windows, `~/.local/bin` บนระบบอื่น)\n   - ไดเรกทอรีแพ็กเกจ/ไฟล์ปฏิบัติการ\n4. addon ที่โหลดสำเร็จเป็นตัวแรกยังต้องผ่าน `validateNative`\n\nนี่คือเหตุผลที่การ packaging + ความคาดหวังของ runtime loader ต้องสอดคล้องกัน: ชื่อไฟล์, platform tags, และ exported symbols ต้องตรงกับสิ่งที่ `native.ts` ตรวจสอบและ validate\n\n## การแมป JS API ↔ Rust export (subset ของ validation gate)\n\n`native.ts` กำหนดให้ exports ที่มองเห็นได้ใน JS เหล่านี้ต้องมีอยู่ใน addon ที่โหลดแล้ว โดยแมปกับ Rust N-API exports ใน `crates/pi-natives/src`:\n\n| ชื่อ JS ที่ `validateNative` ต้องการ | การประกาศ Rust export | ไฟล์ต้นทาง Rust |\n| --- | --- | --- |\n| `glob` | `#[napi] pub fn glob(...)` | `crates/pi-natives/src/glob.rs` |\n| `grep` | `#[napi] pub fn grep(...)` | `crates/pi-natives/src/grep.rs` |\n| `search` | `#[napi] pub fn search(...)` | `crates/pi-natives/src/grep.rs` |\n| `highlightCode` | `#[napi] pub fn highlight_code(...)` | `crates/pi-natives/src/highlight.rs` |\n| `getSystemInfo` | `#[napi] pub fn get_system_info(...)` | `crates/pi-natives/src/system_info.rs` |\n| `getWorkProfile` | `#[napi] pub fn get_work_profile(...)` (camel-cased export) | `crates/pi-natives/src/prof.rs` |\n| `invalidateFsScanCache` | `#[napi] pub fn invalidate_fs_scan_cache(...)` | `crates/pi-natives/src/fs_cache.rs` |\n\nถ้าสัญลักษณ์ที่ต้องการขาดหายไป loader จะล้มเหลวทันทีพร้อมคำแนะนำการ rebuild\n\n## พฤติกรรมเมื่อเกิดความล้มเหลวและการวินิจฉัย\n\n## ความล้มเหลวในช่วง build-time\n\n- การกำหนดค่า variant ไม่ถูกต้อง:\n  - กำหนด `TARGET_VARIANT` บนสถาปัตยกรรมที่ไม่ใช่ x64 → ข้อผิดพลาดทันที\n  - x64 cross-build ไม่มี explicit `TARGET_VARIANT` → ข้อผิดพลาดทันที\n- ความล้มเหลวในการสร้าง Cargo:\n  - สคริปต์แสดง exit code ที่ไม่ใช่ศูนย์และ stderr\n- ไม่พบ artifact:\n  - สคริปต์พิมพ์ทุกไดเรกทอรี profile ที่ตรวจสอบ\n- ความล้มเหลวในการติดตั้ง:\n  - ข้อความที่ชัดเจน; บน Windows รวมคำแนะนำเกี่ยวกับไฟล์ที่ถูกล็อก\n\n## ความล้มเหลวของ runtime loader (`native.ts`)\n\n- Platform tag ที่ไม่รองรับ:\n  - throw พร้อมรายการ platform ที่รองรับ\n- ไม่มีตัวเลือกใดที่โหลดได้:\n  - throw พร้อมรายการข้อผิดพลาดของตัวเลือกทั้งหมดและคำแนะนำการแก้ไขตามโหมด\n- Export ที่ขาดหายไป:\n  - throw พร้อมชื่อสัญลักษณ์ที่ขาดหายไปอย่างแน่ชัดและคำสั่ง rebuild\n- ปัญหาการแตกไฟล์ embedded:\n  - ข้อผิดพลาด mkdir/write ขณะแตกไฟล์จะถูกบันทึกและรวมไว้ในการวินิจฉัยขั้นสุดท้าย\n\n## ตารางการแก้ไขปัญหา\n\n| อาการ | สาเหตุที่น่าจะเป็น | การตรวจสอบ | การแก้ไข |\n| --- | --- | --- | --- |\n| `Native addon missing exports ... Missing: <name>` | ไบนารี `.node` ที่ล้าสมัย, ชื่อ Rust export ไม่ตรงกัน, หรือโหลดไบนารีผิดไฟล์ | รันด้วย `PI_DEV=1` เพื่อดู path ที่โหลด; ตรวจสอบรายการ export ของไฟล์นั้น | Rebuild `build`; ตรวจสอบให้แน่ใจว่าชื่อ Rust `#[napi]` export (หรือ alias ที่ชัดเจนเมื่อจำเป็น) ตรงกับ JS key; ลบไฟล์ที่แคชหรือมี version เก่า |\n| เครื่อง x64 โหลด baseline แทน modern | `PI_NATIVE_VARIANT=baseline`, ไม่ตรวจพบ AVX2, หรือมีเฉพาะไฟล์ baseline | ตรวจสอบ `PI_NATIVE_VARIANT`; ตรวจสอบ `native/` สำหรับไฟล์ `-modern` | สร้าง modern variant (`TARGET_VARIANT=modern ... build`) และตรวจสอบให้แน่ใจว่าไฟล์ถูกรวมไว้ |\n| Cross-build ผลิตไบนารีที่ใช้ไม่ได้/ติดป้ายกำกับผิด | ไม่ตรงกันระหว่าง `CROSS_TARGET` กับ `TARGET_PLATFORM`/`TARGET_ARCH`, หรือไม่มี `TARGET_VARIANT` สำหรับ x64 | ยืนยัน env tuple และชื่อไฟล์ output | รันใหม่ด้วยค่า env ที่สอดคล้องกันและ `TARGET_VARIANT` ที่ชัดเจนสำหรับ x64 |\n| Compiled binary ล้มเหลวหลังอัปเกรด | แคชที่แตกไฟล์ไว้ล้าสมัย (`~/.xcsh/natives/<old-or-mismatched-version>`) หรือ embedded manifest ไม่ตรงกัน | ตรวจสอบ versioned natives dir และรายการข้อผิดพลาดของ loader | ลบ versioned natives cache สำหรับ package version นั้นและรันใหม่; สร้าง embedded manifest ใหม่ในช่วง packaging |\n| Loader ตรวจสอบหลาย path แต่ไม่มีที่ใดทำงานได้ | Platform ไม่ตรงกันหรือไม่มี release artifact ใน `native/` ของแพ็กเกจ | ตรวจสอบ `platformTag` เทียบกับชื่อไฟล์จริง | ตรวจสอบให้แน่ใจว่าชื่อไฟล์ที่สร้างตรงกับข้อตกลง `pi_natives.<platform>-<arch>(-variant).node` และแพ็กเกจรวม `native/` ไว้ด้วย |\n| `embed:native` ล้มเหลวพร้อม \"Incomplete native addons\" | ไม่ได้สร้างไฟล์ variant ที่ต้องการก่อน embedding | ตรวจสอบรายการ expected vs found ในข้อความแสดงข้อผิดพลาด | สร้างไฟล์ที่ต้องการก่อน (x64: ทั้ง modern+baseline; ไม่ใช่ x64: default) จากนั้นรัน `embed:native` อีกครั้ง |\n\n## คำสั่งสำหรับการใช้งาน\n\n```bash\n# Release artifact สำหรับ host ปัจจุบัน\nbun --cwd=packages/natives run build\n\n# สร้าง debug profile artifact\nbun --cwd=packages/natives run dev:native\n\n# สร้าง x64 variants ที่ชัดเจน\nTARGET_VARIANT=modern bun --cwd=packages/natives run build\nTARGET_VARIANT=baseline bun --cwd=packages/natives run build\n\n# สร้าง embedded addon manifest จากไฟล์ native ที่สร้างแล้ว\nbun --cwd=packages/natives run embed:native\n\n# รีเซ็ต embedded manifest เป็น null stub\nbun --cwd=packages/natives run embed:native -- --reset\n```\n",
	"th/natives/natives-media-system-utils.md": "---\ntitle: ยูทิลิตี้มีเดียและระบบแบบ Native\ndescription: >-\n  ยูทิลิตี้การประมวลผลมีเดียแบบ Native สำหรับสกรีนช็อต การจัดการรูปภาพ\n  และข้อมูลระบบ\nsidebar:\n  order: 7\n  label: ยูทิลิตี้มีเดียและระบบ\ni18n:\n  sourceHash: 430898c177bc\n  translator: machine\n---\n\n# มีเดีย Native + ยูทิลิตี้ระบบ\n\nเอกสารนี้เป็นการวิเคราะห์เชิงลึกของระบบย่อยสำหรับชั้น **system/media/conversion primitives** ที่อธิบายไว้ใน [`docs/natives-architecture.md`](./natives-architecture.md): `image`, `html`, `clipboard` และการโปรไฟล์ `work`\n\n## ไฟล์การติดตั้ง\n\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/prof.rs`\n- `crates/pi-natives/src/task.rs`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/work/index.ts`\n- `packages/natives/src/work/types.ts`\n\n> หมายเหตุ: ไม่มี `crates/pi-natives/src/work.rs`; การโปรไฟล์งานถูกติดตั้งใน `prof.rs` และป้อนข้อมูลโดยการ instrumentation ใน `task.rs`\n\n## การแมปการส่งออก/โมดูล TS API ↔ Rust\n\n| การส่งออก TS (packages/natives)             | การส่งออก Rust N-API                                                    | โมดูล Rust                            |\n| ------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------- |\n| `PhotonImage.parse(bytes)`                  | `PhotonImage::parse`                                                     | `image.rs`                            |\n| `PhotonImage#resize(width, height, filter)` | `PhotonImage::resize`                                                    | `image.rs`                            |\n| `PhotonImage#encode(format, quality)`       | `PhotonImage::encode`                                                    | `image.rs`                            |\n| `htmlToMarkdown(html, options)`             | `html_to_markdown`                                                       | `html.rs`                             |\n| `copyToClipboard(text)`                     | `copy_to_clipboard` + TS fallback logic                                  | `clipboard.rs` + `clipboard/index.ts` |\n| `readImageFromClipboard()`                  | `read_image_from_clipboard`                                              | `clipboard.rs`                        |\n| `getWorkProfile(lastSeconds)`               | `get_work_profile`                                                      | `prof.rs`                             |\n\n## ขอบเขตรูปแบบข้อมูลและการแปลง\n\n### รูปภาพ (`image`)\n\n- **ขอบเขตอินพุต JS**: ไบต์รูปภาพที่เข้ารหัสเป็น `Uint8Array`\n- **ขอบเขตการถอดรหัส Rust**: ไบต์ถูกคัดลอกไปยัง `Vec<u8>` ระบบเดาฟอร์แมตด้วย `ImageReader::with_guessed_format()` จากนั้นถอดรหัสเป็น `DynamicImage`\n- **สถานะในหน่วยความจำ**: `PhotonImage` เก็บ `Arc<DynamicImage>`\n- **ขอบเขตเอาต์พุต**: `encode(format, quality)` คืนค่า `Promise<Uint8Array>` (Rust `Vec<u8>`)\n\nรหัสฟอร์แมตเป็นตัวเลข:\n\n- `0`: PNG\n- `1`: JPEG\n- `2`: WebP (ตัวเข้ารหัสแบบไม่สูญเสียข้อมูล)\n- `3`: GIF\n\nข้อจำกัด:\n\n- `quality` ใช้งานได้เฉพาะกับ JPEG เท่านั้น\n- PNG/WebP/GIF ไม่สนใจค่า `quality`\n- รหัสฟอร์แมตที่ไม่รองรับจะล้มเหลว (`Invalid image format: <id>`)\n\n### การแปลง HTML (`html`)\n\n- **ขอบเขตอินพุต JS**: `string` ของ HTML + ออบเจ็กต์ทางเลือก `{ cleanContent?: boolean; skipImages?: boolean }`\n- **ขอบเขตการแปลง Rust**: อินพุต `String` ถูกแปลงโดย `html_to_markdown_rs::convert`\n- **ขอบเขตเอาต์พุต**: `string` ของ Markdown\n\nพฤติกรรมการแปลง:\n\n- `cleanContent` ค่าเริ่มต้นคือ `false`\n- เมื่อ `cleanContent=true` จะเปิดใช้งานการประมวลผลล่วงหน้าด้วย `PreprocessingPreset::Aggressive` และแฟล็กลบอย่างถาวรสำหรับการนำทาง/ฟอร์ม\n- `skipImages` ค่าเริ่มต้นคือ `false`\n\n### คลิปบอร์ด (`clipboard`)\n\n- **เส้นทางข้อความ**:\n  - TS ปล่อย OSC 52 (`\\x1b]52;c;<base64>\\x07`) ก่อนเมื่อ stdout เป็น TTY\n  - ข้อความเดียวกันจะถูกพยายามผ่าน clipboard API แบบ native (`native.copyToClipboard`) ในฐานะ best-effort\n  - บน Termux, TS จะพยายาม `termux-clipboard-set` ก่อน\n- **เส้นทางอ่านรูปภาพ**:\n  - Rust อ่านรูปภาพดิบจาก `arboard`\n  - Rust เข้ารหัสซ้ำเป็นไบต์ PNG (ไลบรารี `image`) คืนค่า `{ data: Uint8Array, mimeType: \"image/png\" }`\n  - TS คืนค่า `null` ก่อนกำหนดบน Termux หรือ Linux sessions ที่ไม่มี display server (`DISPLAY`/`WAYLAND_DISPLAY` ขาดหายไป)\n\n### การโปรไฟล์งาน (`work`)\n\n- **ขอบเขตการเก็บข้อมูล**: ตัวอย่างการโปรไฟล์ถูกสร้างโดย guards `profile_region(tag)` ใน `task::blocking` และ `task::future`\n- **รูปแบบการจัดเก็บ**: บัฟเฟอร์วงกลมขนาดคงที่ (`MAX_SAMPLES = 10_000`) เก็บ stack path + duration (`μs`) + timestamp (`μs นับจากการเริ่มต้นกระบวนการ`)\n- **ขอบเขตเอาต์พุต**: `getWorkProfile(lastSeconds)` คืนค่าออบเจ็กต์:\n  - `folded`: ข้อความ folded-stack (อินพุตสำหรับ flamegraph)\n  - `summary`: สรุปตาราง markdown\n  - `svg`: SVG flamegraph แบบทางเลือก\n  - `totalMs`, `sampleCount`\n\n## วงจรชีวิตและการเปลี่ยนสถานะ\n\n### วงจรชีวิตของรูปภาพ\n\n1. `PhotonImage.parse(bytes)` กำหนดตารางเวลา blocking decode task (`image.decode`)\n2. เมื่อสำเร็จ จะมี handle `PhotonImage` แบบ native ใน JS\n3. `resize(...)` สร้าง handle แบบ native ใหม่ (`image.resize`) โดย handle เก่าและใหม่สามารถอยู่ร่วมกันได้\n4. `encode(...)` สร้างไบต์จริง (`image.encode`) โดยไม่เปลี่ยนแปลงขนาดของรูปภาพ\n\nการเปลี่ยนสถานะเมื่อล้มเหลว:\n\n- การตรวจจับฟอร์แมต/การถอดรหัสล้มเหลว จะปฏิเสธ promise การแยกวิเคราะห์\n- การเข้ารหัสล้มเหลว จะปฏิเสธ promise การเข้ารหัส\n- รหัสฟอร์แมตไม่ถูกต้อง จะปฏิเสธ promise การเข้ารหัส\n\n### วงจรชีวิตของ HTML\n\n1. `htmlToMarkdown(html, options)` กำหนดตารางเวลา blocking conversion task\n2. การแปลงทำงานด้วยออปชันค่าเริ่มต้น (`cleanContent=false`, `skipImages=false`) หากไม่ได้ระบุ\n3. คืนค่า markdown string หรือปฏิเสธ\n\nการเปลี่ยนสถานะเมื่อล้มเหลว:\n\n- ความล้มเหลวของตัวแปลงคืนค่า rejected promise (`Conversion error: ...`)\n\n### วงจรชีวิตของคลิปบอร์ด\n\n`copyToClipboard(text)` ถูกออกแบบมาให้เป็น best-effort และมีหลายเส้นทางโดยเจตนา:\n\n1. ถ้าเป็น TTY: พยายามเขียน OSC 52 (payload base64)\n2. ลอง Termux command เมื่อตั้งค่า `TERMUX_VERSION` ไว้\n3. ลองคัดลอกข้อความแบบ native ด้วย `arboard`\n4. กลืนกินข้อผิดพลาดที่ชั้น TS\n\nความเข้มงวดของ `readImageFromClipboard()` แตกต่างกันตามขั้นตอน:\n\n1. TS ปิดกั้นอย่างเด็ดขาดสำหรับ runtime context ที่ไม่รองรับ (Termux/headless Linux) ให้เป็น `null`\n2. Rust `arboard` read ทำงานเฉพาะเมื่อ TS อนุญาต\n3. `ContentNotAvailable` แมปเป็น `null`\n4. ข้อผิดพลาด Rust อื่นๆ จะปฏิเสธ\n\n### วงจรชีวิตของการโปรไฟล์งาน\n\n1. ไม่มีการเริ่มต้นอย่างชัดเจน: การโปรไฟล์เปิดอยู่เสมอเมื่อ task helpers ทำงาน\n2. ทุก instrumented task scope บันทึกหนึ่งตัวอย่างเมื่อ guard drop\n3. ตัวอย่างจะเขียนทับรายการเก่าที่สุดหลังจากถึงความจุของบัฟเฟอร์\n4. `getWorkProfile(lastSeconds)` อ่านช่วงเวลาหนึ่งและสร้างผลลัพธ์ folded/summary/svg\n\nการเปลี่ยนสถานะเมื่อล้มเหลว:\n\n- การสร้าง SVG ล้มเหลวจะเป็น soft-fail (`svg: null`) ในขณะที่ folded และ summary ยังคงคืนค่า\n- ช่วงเวลาตัวอย่างว่างเปล่าจะคืนค่าข้อมูล folded ว่างเปล่าและ `svg: null` ไม่ใช่ข้อผิดพลาด\n\n## การดำเนินการที่ไม่รองรับและการส่งต่อข้อผิดพลาด\n\n### รูปภาพ\n\n- อินพุตการถอดรหัสที่ไม่รองรับหรือไบต์เสียหาย: ล้มเหลวอย่างเข้มงวด (promise rejection)\n- รหัสฟอร์แมตการเข้ารหัสที่ไม่รองรับ: ล้มเหลวอย่างเข้มงวด\n- ไม่มีเส้นทาง best-effort fallback ใน TS wrapper\n\n### HTML\n\n- ข้อผิดพลาดการแปลงเป็นความล้มเหลวอย่างเข้มงวด (rejection)\n- การละเว้นออปชันเป็น best-effort defaulting ไม่ใช่ความล้มเหลว\n\n### คลิปบอร์ด\n\n- การคัดลอกข้อความเป็น best-effort ที่ชั้น TS: ความล้มเหลวในการดำเนินงานถูกระงับ\n- การอ่านรูปภาพแยกแยะ \"ไม่มีรูปภาพ\" (`null`) จากความล้มเหลวในการดำเนินงาน (rejection)\n- Termux/headless Linux ถูกถือว่าเป็น context ที่ไม่รองรับสำหรับการอ่านรูปภาพ (`null`)\n\n### การโปรไฟล์งาน\n\n- การดึงข้อมูลเป็นความเข้มงวดสำหรับการเรียกใช้ฟังก์ชันเอง แต่การสร้าง artifact เป็น best-effort บางส่วน (`svg` เป็น nullable)\n- การตัดทอนบัฟเฟอร์เป็นพฤติกรรมที่คาดหวัง (ring buffer) ไม่ใช่บัคการสูญหายของข้อมูล\n\n## ข้อจำกัดของแพลตฟอร์ม\n\n- **ข้อความคลิปบอร์ด**: OSC 52 ขึ้นอยู่กับการรองรับของ terminal; การเข้าถึง clipboard แบบ native ขึ้นอยู่กับ desktop environment/session\n- **การอ่านรูปภาพจากคลิปบอร์ด**: ถูกบล็อกใน TS สำหรับ Termux และ Linux ที่ไม่มี display server\n",
	"th/natives/natives-rust-task-cancellation.md": "---\ntitle: การดำเนินงานและการยกเลิกงาน Native Rust\ndescription: >-\n  โมเดลการดำเนินงานแบบ async ของ Rust\n  พร้อมความหมายของการยกเลิกแบบร่วมมือและการทำความสะอาด\nsidebar:\n  order: 5\n  label: การยกเลิกงาน\ni18n:\n  sourceHash: 0fbf45c6d463\n  translator: machine\n---\n\n# การดำเนินงานและการยกเลิกงาน Native Rust (`pi-natives`)\n\nเอกสารนี้อธิบายวิธีที่ `crates/pi-natives` จัดตารางงาน native และวิธีที่การยกเลิกไหลจากตัวเลือก JS (`timeoutMs`, `AbortSignal`) ไปยังการดำเนินงาน Rust\n\n## ไฟล์ที่ใช้งาน\n\n- `crates/pi-natives/src/task.rs`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/fd.rs`\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/ps.rs`\n\n## ส่วนประกอบหลัก (`task.rs`)\n\n`task.rs` กำหนดส่วนประกอบหลักสามส่วน:\n\n1. `task::blocking(tag, cancel_token, work)`\n   - ครอบ `napi::AsyncTask` / `Task`\n   - `compute()` ทำงานบน libuv worker threads (สำหรับงานที่ใช้ CPU หนักหรือ system calls แบบ blocking/sync)\n   - คืนค่า JS `Promise<T>`\n\n2. `task::future(env, tag, work)`\n   - ครอบ `env.spawn_future(...)`\n   - ทำงาน async บน Tokio runtime\n   - คืนค่า `PromiseRaw<'env, T>`\n\n3. `CancelToken` / `AbortToken` / `AbortReason`\n   - `CancelToken::new(timeout_ms, signal)` รวม deadline + `AbortSignal` ที่เป็นตัวเลือก\n   - `CancelToken::heartbeat()` คือการยกเลิกแบบร่วมมือสำหรับ blocking loops\n   - `CancelToken::wait()` คือการรอการยกเลิกแบบ async (`Signal` / `Timeout` / `User` Ctrl-C)\n   - `AbortToken` ให้โค้ดภายนอกร้องขอการยกเลิก (`abort(reason)`)\n\n## `blocking` เทียบกับ `future`: โมเดลการดำเนินงานและการเลือกใช้\n\n### ใช้ `task::blocking`\n\nใช้เมื่องานใช้ CPU หนักหรือมีลักษณะซิงโครนัส/blocking โดยพื้นฐาน:\n\n- การสแกน regex/ไฟล์ (`grep`, `glob`, `fuzzy_find`)\n- ภายใน PTY loop แบบซิงโครนัส (`run_pty_sync` ผ่าน `spawn_blocking`)\n- การแปลง clipboard/image/html\n\nพฤติกรรม:\n\n- Work closure รับ `CancelToken` ที่ถูก clone มา\n- การยกเลิกจะถูกสังเกตเฉพาะที่โค้ดตรวจสอบ `ct.heartbeat()?` เท่านั้น\n- Closure `Err(...)` ปฏิเสธ JS promise\n\n### ใช้ `task::future`\n\nใช้เมื่องานต้องรอการทำงาน async ด้วย `await`:\n\n- การประสานงาน shell session (`shell.run`, `executeShell`)\n- การแข่งขันของงาน (`tokio::select!`) ระหว่างการเสร็จสิ้นและการยกเลิก\n\nพฤติกรรม:\n\n- Future สามารถแข่งระหว่างการเสร็จสิ้นปกติกับ `ct.wait()`\n- เมื่อยกเลิก การดำเนินงาน async มักจะส่งต่อการยกเลิกไปยังระบบย่อยภายใน (เช่น `tokio_util::CancellationToken`) และอาจบังคับยกเลิกเมื่อ grace timeout หมด\n\n## การแมป JS API ↔ Rust export (ที่เกี่ยวกับงาน/การยกเลิก)\n\n| JS API | Rust export (`#[napi]`) | Scheduler | การเชื่อมการยกเลิก |\n|---|---|---|---|\n| `grep(options, onMatch?)` | `grep` | `task::blocking(\"grep\", ct, ...)` | `CancelToken::new(options.timeoutMs, options.signal)` + `ct.heartbeat()` |\n| `glob(options, onMatch?)` | `glob` | `task::blocking(\"glob\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` ใน filter loop |\n| `fuzzyFind(options)` | `fuzzy_find` | `task::blocking(\"fuzzy_find\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` ใน scoring loop |\n| `shell.run(options, onChunk?)` | `Shell::run` | `task::future(env, \"shell.run\", ...)` | `ct.wait()` แข่งกับ run task; เชื่อมต่อไปยัง Tokio `CancellationToken` |\n| `executeShell(options, onChunk?)` | `execute_shell` | `task::future(env, \"shell.execute\", ...)` | เหมือนกับข้างบน |\n| `pty.start(options, onChunk?)` | `PtySession::start` | `task::future(env, \"pty.start\", ...)` + `spawn_blocking` ภายใน | `CancelToken` ตรวจสอบใน sync PTY loop ผ่าน `heartbeat()` |\n| `htmlToMarkdown(html, options?)` | `html_to_markdown` | `task::blocking(\"html_to_markdown\", (), ...)` | ไม่มี (token `()`) |\n| `PhotonImage.parse/encode/resize` | `PhotonImage::{parse,encode,resize}` | `task::blocking(...)` | ไม่มี (token `()`) |\n| `copyToClipboard/readImageFromClipboard` | `copy_to_clipboard` / `read_image_from_clipboard` | `task::blocking(...)` | ไม่มี (token `()`) |\n\n`text.rs` และ `ps.rs` ในปัจจุบันไม่ใช้ `task::blocking`/`task::future` ดังนั้นจึงไม่เข้าร่วมในเส้นทางการยกเลิกนี้\n\n## วงจรชีวิตการยกเลิกและการเปลี่ยนแปลงสถานะ\n\n### วงจรชีวิตของ `CancelToken`\n\n`CancelToken` เป็นแบบร่วมมือและมีสถานะ:\n\n```text\nCreated\n  ├─ no signal + no timeout  -> passive token (never aborts unless externally emplaced)\n  ├─ signal registered        -> waits for AbortSignal callback\n  └─ deadline set             -> timeout check becomes active\n\nRunning\n  ├─ heartbeat()/wait() sees signal   -> AbortReason::Signal\n  ├─ heartbeat()/wait() sees deadline -> AbortReason::Timeout\n  ├─ wait() sees Ctrl-C               -> AbortReason::User\n  └─ no abort                         -> continue\n\nAborted (terminal)\n  └─ first abort reason wins (atomic flag + notifier)\n```\n\n### การยกเลิกก่อนเริ่มต้นเทียบกับระหว่างการดำเนินงาน\n\n- **ก่อนเริ่มต้น / ก่อนการตรวจสอบการยกเลิกครั้งแรก**:\n  - ผู้ใช้ `task::future` ที่แข่งกันด้วย `ct.wait()` สามารถแก้ไขการยกเลิกได้ทันทีเมื่อเข้าสู่ `select!`\n  - ผู้ใช้ `task::blocking` จะสังเกตการยกเลิกเฉพาะเมื่อโค้ด closure ถึง `heartbeat()` ถ้า closure ไม่ทำ heartbeat ก่อน การยกเลิกจะล่าช้า\n\n- **ระหว่างการดำเนินงาน**:\n  - `blocking`: `heartbeat()` ครั้งถัดไปคืนค่า `Err(\"Aborted: ...\")`\n  - `future`: branch ของ `ct.wait()` ชนะ `select!` แล้วโค้ดยกเลิกเครื่องจักร async รอง (สำหรับ shell: ยกเลิก Tokio token, รอสูงสุด 2 วินาที, จากนั้นยกเลิกงาน)\n\n## ความคาดหวัง Heartbeat สำหรับ loops ที่ทำงานนาน\n\n`heartbeat()` ต้องทำงานในจังหวะที่คาดเดาได้ใน loops ที่มีชุดงานขนาดใหญ่หรือไม่จำกัด\n\nรูปแบบที่พบ:\n\n- `glob::filter_entries`: ตรวจสอบแต่ละรายการก่อน filtering/matching\n- `fd::score_entries`: ตรวจสอบแต่ละตัวเลือกที่สแกน\n- `grep_sync`: ตรวจสอบการยกเลิกอย่างชัดเจนก่อนขั้นตอนการค้นหาที่หนัก บวกกับการเรียก fs-cache ที่รับ token ด้วย\n- `run_pty_sync`: ตรวจสอบทุก loop tick (~16ms sleep cadence) และ kill child เมื่อยกเลิก\n\nกฎปฏิบัติ: ไม่มี loop ที่ประมวลผลข้อมูลที่มีขนาดจากภายนอกควรเกินช่วงเวลาสั้นๆ ที่กำหนดโดยไม่มี heartbeat\n\n## พฤติกรรมความล้มเหลวและการส่งต่อข้อผิดพลาดไปยัง JS\n\n### Blocking tasks\n\nเส้นทางข้อผิดพลาด:\n\n1. Closure คืนค่า `Err(napi::Error)` (รวมถึงการยกเลิกของ `heartbeat()`)\n2. `Task::compute()` คืนค่า `Err`\n3. `AsyncTask` ปฏิเสธ JS promise\n\nสตริงข้อผิดพลาดทั่วไป:\n\n- `Aborted: Timeout`\n- `Aborted: Signal`\n- ข้อผิดพลาดเฉพาะโดเมน (`Failed to decode image: ...`, `Conversion error: ...`, ฯลฯ)\n\n### Future tasks\n\nเส้นทางข้อผิดพลาด:\n\n1. Async body คืนค่า `Err(napi::Error)` หรือ join failure ถูกแมป (`... task failed: {err}`)\n2. Promise ที่สร้างโดย `task::future` ถูกปฏิเสธ\n3. API บางรายการจงใจคืนค่าผลลัพธ์การยกเลิกแบบมีโครงสร้างแทนการปฏิเสธ (`ShellRunResult`/`ShellExecuteResult` พร้อม flags `cancelled`/`timed_out` และ `exit_code: None`)\n\n### การแบ่งการรายงานการยกเลิก\n\n- **ยกเลิกเป็น error**: export ที่ blocking ส่วนใหญ่ใช้ `heartbeat()?`\n- **ยกเลิกเป็น typed result**: API คำสั่งแบบ shell/pty ที่จำลองการยกเลิกใน result structs\n\nเลือกโมเดลเดียวต่อ API และจัดทำเอกสารอย่างชัดเจน\n\n## ข้อผิดพลาดที่พบบ่อย\n\n1. **ขาด heartbeat ใน blocking loops**\n   - อาการ: timeout/signal ดูเหมือนถูกละเลยจนกว่า loop จะสิ้นสุด\n   - การแก้ไข: เพิ่ม `ct.heartbeat()?` ที่ด้านบนของ loop และก่อนขั้นตอนที่มีราคาแพงต่อรายการ\n\n2. **ส่วนที่ยกเลิกไม่ได้ยาวนาน**\n   - อาการ: latency การยกเลิกพุ่งสูงระหว่างการเรียกครั้งเดียวที่ใหญ่ (decode, sort, compression ฯลฯ)\n   - การแก้ไข: แบ่งงานเป็น chunks พร้อม heartbeat boundaries; ถ้าเป็นไปไม่ได้ ให้จัดทำเอกสาร latency\n\n3. **การบล็อก async executor**\n   - อาการ: API แบบ async หยุดทำงานเมื่อโค้ดที่ใช้ CPU/sync ทำงานโดยตรงใน future\n   - การแก้ไข: ย้าย CPU/sync blocks ไปยัง `task::blocking` หรือ `tokio::task::spawn_blocking`\n\n4. **ความหมายการยกเลิกที่ไม่สอดคล้องกัน**\n   - อาการ: API หนึ่งปฏิเสธเมื่อยกเลิก อีก API แก้ไขด้วย flags ทำให้ผู้เรียกสับสน\n   - การแก้ไข: กำหนดมาตรฐานต่อโดเมนและให้ wrapper docs สอดคล้องกัน\n\n5. **ลืมการเชื่อม cancellation ใน nested async tasks**\n   - อาการ: outer token ถูกยกเลิก แต่ inner readers/subprocess tasks ยังคงทำงาน\n   - การแก้ไข: เชื่อมการยกเลิกไปยัง inner token/signal และบังคับ grace timeout + fallback การยกเลิกบังคับ\n\n## Checklist สำหรับ exports ที่ยกเลิกได้ใหม่\n\n1. จำแนกงานอย่างถูกต้อง:\n   - CPU-bound หรือ sync blocking -> `task::blocking`\n   - async I/O / การประสาน `await` -> `task::future`\n\n2. เปิดเผยข้อมูล cancel เมื่อจำเป็น:\n   - รวม `timeoutMs` และ `signal` ใน options ของ `#[napi(object)]`\n   - สร้าง `let ct = task::CancelToken::new(timeout_ms, signal);`\n\n3. เชื่อมการยกเลิกผ่านทุก layer:\n   - blocking loops: `ct.heartbeat()?` ที่ช่วงเวลาที่สม่ำเสมอ\n   - async orchestration: แข่งกับ `ct.wait()` และยกเลิก sub-tasks/tokens\n\n4. กำหนดสัญญาการยกเลิก:\n   - ปฏิเสธ promise ด้วย abort error หรือ\n   - แก้ไข typed `{ cancelled, timedOut, ... }`\n   - รักษาสัญญานี้ให้สอดคล้องกันสำหรับ API family\n\n5. ส่งต่อความล้มเหลวพร้อมบริบท:\n   - แมป errors ผ่าน `Error::from_reason(format!(\"...: {err}\"))`\n   - รวม prefixes เฉพาะขั้นตอน (`spawn`, `decode`, `wait`, ฯลฯ)\n\n6. จัดการการยกเลิกก่อนเริ่มต้นและระหว่างการดำเนินงาน:\n   - การตรวจสอบ/รอการยกเลิกต้องเกิดขึ้นก่อน body ที่มีราคาแพงและระหว่างการดำเนินงานที่ยาวนาน\n\n7. ตรวจสอบว่าไม่มีการใช้ executor ผิดวิธี:\n   - ไม่มีงาน sync ที่ยาวนานโดยตรงภายใน async futures โดยไม่มี `spawn_blocking`/blocking task wrapper\n",
	"th/natives/natives-shell-pty-process.md": "---\ntitle: 'โครงสร้างภายในของ Shell, PTY, กระบวนการ และคีย์ในระดับ Native'\ndescription: >-\n  การรันคำสั่ง Shell, การจัดการ PTY, วงจรชีวิตของกระบวนการ\n  และการจัดการเหตุการณ์คีย์ในระดับ Native\nsidebar:\n  order: 4\n  label: 'Shell, PTY และกระบวนการ'\ni18n:\n  sourceHash: 00ea95614c6a\n  translator: machine\n---\n\n# โครงสร้างภายในของ Shell, PTY, กระบวนการ และคีย์ในระดับ Native\n\nเอกสารนี้ครอบคลุม **primitives สำหรับการรัน/กระบวนการ/เทอร์มินัล** ใน `@f5-sales-demo/pi-natives` ได้แก่ `shell`, `pty`, `ps` และ `keys` โดยใช้คำศัพท์สถาปัตยกรรมจาก `docs/natives-architecture.md`\n\n## ไฟล์ที่เกี่ยวข้องกับการ Implement\n\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/shell/windows.rs` (เฉพาะ Windows)\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/ps.rs`\n- `crates/pi-natives/src/keys.rs`\n- `crates/pi-natives/src/task.rs` (พฤติกรรมการยกเลิกที่ใช้ร่วมกันโดย shell/pty)\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/bindings.ts`\n\n## การเป็นเจ้าของในแต่ละชั้น\n\n- **ชั้น TS wrapper/API** (`packages/natives/src/*`): จุดเข้าถึงแบบ typed, พื้นผิวการยกเลิก (`timeoutMs`, `AbortSignal`) และ ergonomics ของ JS\n- **ชั้น Rust N-API module** (`crates/pi-natives/src/*`): การรันกระบวนการ shell/PTY, การสำรวจ/ยุติ process-tree และการแยกวิเคราะห์ key-sequence\n- **Validation gate** (`native.ts`, ระดับสถาปัตยกรรม): ตรวจสอบว่า export ที่จำเป็น (`Shell`, `executeShell`, `PtySession`, `killTree`, `listDescendants`, ตัวช่วย key) มีอยู่ก่อนที่จะใช้งาน wrapper\n\n## ระบบย่อย Shell (`shell`)\n\n### โมเดล API\n\nมีสองโหมดการรัน:\n\n1. **แบบครั้งเดียว** ผ่าน `executeShell(options, onChunk?)`\n2. **เซสชันถาวร** ผ่าน `new Shell(options?)` แล้วตามด้วย `shell.run(...)` ซ้ำๆ\n\nทั้งสองโหมดส่งออกผลลัพธ์ผ่าน threadsafe callback และคืนค่า `{ exitCode?, cancelled, timedOut }`\n\n### การสร้างเซสชันและโมเดลสภาพแวดล้อม\n\nRust สร้าง `brush_core::Shell` ด้วย:\n\n- โหมดไม่โต้ตอบ,\n- `do_not_inherit_env: true`,\n- การสร้างสภาพแวดล้อมใหม่อย่างชัดเจนจาก env ของ host,\n- รายการ skip สำหรับตัวแปรที่ sensitive ต่อ shell (`PS1`, `PWD`, `SHLVL`, การ export ฟังก์ชัน bash เป็นต้น)\n\nพฤติกรรมของ env ในเซสชัน:\n\n- `ShellOptions.sessionEnv` ถูกใช้งานเพียงครั้งเดียวเมื่อสร้างเซสชัน\n- `ShellRunOptions.env` มีขอบเขตต่อคำสั่ง (`EnvironmentScope::Command`) และถูกนำออกหลังจากการรันแต่ละครั้ง\n- `PATH` ถูก merge เป็นพิเศษบน Windows โดยมีการ dedupe แบบ case-insensitive\n\nการเพิ่มประสิทธิภาพ path เฉพาะ Windows (`shell/windows.rs`): path ของ Git-for-Windows ที่ค้นพบ (`cmd`, `bin`, `usr/bin`) จะถูกต่อท้ายหากมีอยู่และยังไม่ได้รวมอยู่\n\n### วงจรชีวิตขณะรันและการเปลี่ยนสถานะ\n\nShell แบบถาวร (`Shell.run`) ใช้ state machine นี้:\n\n- **Idle/Uninitialized**: `session: None`\n- **Running**: การเรียก `run()` ครั้งแรกสร้างเซสชันแบบ lazy, เก็บ token `current_abort`, และรันคำสั่ง\n- **Completed + keepalive**: หากการควบคุม execution flow เป็น `Normal`, `current_abort` จะถูกล้างและเซสชันจะถูกนำกลับมาใช้\n- **Completed + teardown**: หาก control flow เกี่ยวข้องกับ loop/script/shell-exit (`BreakLoop`, `ContinueLoop`, `ReturnFromFunctionOrScript`, `ExitShell`) เซสชันจะถูก drop (`session: None`)\n- **Cancelled/Timed out**: task การรันถูกยกเลิก, รอแบบ grace (2 วินาที), แล้วบังคับ abort; เซสชันจะถูก drop\n- **Error**: เซสชันจะถูก drop\n\nShell แบบครั้งเดียว (`executeShell`) จะสร้างและ drop เซสชันใหม่ทุกครั้งที่เรียก\n\n### พฤติกรรม Streaming/Output\n\n- Stdout/stderr ถูกส่งเข้าไปยัง pipe ที่ใช้ร่วมกันและอ่านพร้อมกัน\n- Reader ถอดรหัส UTF-8 แบบเพิ่มทีละน้อย; ลำดับ byte ที่ไม่ถูกต้องจะปล่อย chunk การแทนที่ `U+FFFD`\n- หลังจากกระบวนการเสร็จสิ้น การระบาย output จะมีการป้องกัน idle/max (`250ms` idle, `2s` สูงสุด) เพื่อหลีกเลี่ยงการค้างกับ background jobs ที่เปิด descriptor ไว้\n\n### การยกเลิก, timeout และ background jobs\n\n- `CancelToken` สร้างจาก `timeoutMs` และ `AbortSignal` ที่เป็น optional\n- เมื่อยกเลิก/timeout token การยกเลิก shell จะถูก trigger แล้ว task จะได้รับเวลา grace 2 วินาทีก่อนการบังคับ abort\n- หากเกิดการยกเลิก background jobs จะถูกยุติ (`TERM` แล้วตามด้วย `KILL` ที่มีความล่าช้า) โดยใช้ข้อมูล job ของ brush\n\nพฤติกรรม `Shell.abort()`:\n\n- abort เฉพาะคำสั่งที่กำลังรันอยู่สำหรับ instance `Shell` นั้น,\n- เป็น no-op success เมื่อไม่มีอะไรกำลังรันอยู่\n\n### พฤติกรรมเมื่อเกิดความล้มเหลว\n\nข้อผิดพลาดที่พบบ่อย ได้แก่:\n\n- ความล้มเหลวในการ init เซสชัน (`Failed to initialize shell`),\n- ข้อผิดพลาด cwd (`Failed to set cwd`),\n- ความล้มเหลวในการ set/pop env,\n- ความล้มเหลวในการรับ snapshot source,\n- ความล้มเหลวในการสร้าง/clone pipe,\n- ความล้มเหลวในการรัน (`Shell execution failed: ...`),\n- ความล้มเหลวของ task wrapper (`Shell execution task failed: ...`)\n\nFlag การยกเลิกในระดับ Result:\n\n- timeout -> `exitCode: undefined`, `timedOut: true`\n- abort signal -> `exitCode: undefined`, `cancelled: true`\n\n## ระบบย่อย PTY (`pty`)\n\n### โมเดล API\n\n`new PtySession()` เปิดเผย:\n\n- `start(options, onChunk?) -> Promise<{ exitCode?, cancelled, timedOut }>`\n- `write(data)`\n- `resize(cols, rows)`\n- `kill()`\n\n### วงจรชีวิตขณะรันและการเปลี่ยนสถานะ\n\nState machine ของ `PtySession`:\n\n- **Idle**: `core: None`\n- **Reserved**: `start()` ติดตั้ง control channel แบบ synchronous (`core: Some`) ก่อนที่งาน async จะเริ่มต้น ดังนั้น `write/resize/kill` จึงพร้อมใช้งานทันที\n- **Running**: loop การบล็อก PTY จัดการสถานะ child, เหตุการณ์ reader, heartbeat การยกเลิก และข้อความ control\n- **Terminal closed**: การออกของ child + การเสร็จสิ้นของ reader\n- **Finalized**: `core` จะถูก reset เป็น `None` เสมอหลังจาก task start เสร็จสิ้น (ทั้งสำเร็จและเกิดข้อผิดพลาด)\n\nการป้องกัน Concurrency:\n\n- การเริ่มต้นในขณะที่กำลังรันอยู่จะคืนค่า `PTY session already running`\n\n### รูปแบบ Spawn/Attach/Write/Read/Terminate\n\n- PTY เปิดผ่าน `portable_pty::native_pty_system().openpty(...)`\n- คำสั่งปัจจุบันรันเป็น `sh -lc <command>` พร้อม `cwd` และการ override env ที่เป็น optional\n- `write()` ส่ง raw bytes ไปยัง stdin ของ PTY\n- `resize()` จำกัดขนาด (`cols 20..400`, `rows 5..200`) และเรียก master resize\n- `kill()` ทำเครื่องหมายการรันว่าถูกยกเลิกและ kill child process\n\nเส้นทาง Output:\n\n- thread reader เฉพาะอ่าน master stream,\n- ถอดรหัส UTF-8 แบบเพิ่มทีละน้อยพร้อมการแทนที่ `U+FFFD` สำหรับ byte ที่ไม่ถูกต้อง,\n- chunk ส่งต่อผ่าน N-API threadsafe callback\n\n### ความหมายของการยกเลิกและ timeout\n\n- `timeoutMs` และ `AbortSignal` ป้อนเข้า `CancelToken`\n- loop เรียก `ct.heartbeat()` เป็นระยะ; การ abort จะ trigger การ kill child\n- การจำแนก timeout อาศัย string (`\"Timeout\"` substring ในข้อผิดพลาด heartbeat)\n\n### พฤติกรรมเมื่อเกิดความล้มเหลว\n\nพื้นผิวของ Error ได้แก่:\n\n- ความล้มเหลวในการ allocate/open PTY,\n- ความล้มเหลวในการ spawn PTY,\n- ความล้มเหลวในการรับ writer/reader,\n- ความล้มเหลวในสถานะ/การรอของ child,\n- lock poisoning,\n- การตัดการเชื่อมต่อของ control-channel (`PTY session is no longer available`)\n\nความล้มเหลวของการเรียก control เมื่อไม่ได้รันอยู่:\n\n- `write/resize/kill` คืนค่า `PTY session is not running`\n\n## ระบบย่อย Process-tree (`ps`)\n\n### โมเดล API\n\n- `killTree(pid, signal) -> number`\n- `listDescendants(pid) -> number[]`\n\nTS wrapper ยังลงทะเบียน native kill-tree integration เข้าใน shared utils ผ่าน `setNativeKillTree(native.killTree)`\n\n### การ Implement เฉพาะแพลตฟอร์ม\n\n- **Linux**: อ่าน `/proc/<pid>/task/<pid>/children` แบบ recursive\n- **macOS**: ใช้ `libproc` `proc_listchildpids`\n- **Windows**: สร้าง snapshot ตาราง process ด้วย `CreateToolhelp32Snapshot`, สร้าง map parent->children, ยุติด้วย `OpenProcess(PROCESS_TERMINATE)` + `TerminateProcess`\n\n### พฤติกรรม Kill-tree\n\n- Descendants ถูกเก็บรวบรวมแบบ recursive\n- ลำดับการ Kill เป็นแบบ bottom-up (descendants ที่ลึกที่สุดก่อน) เพื่อลด orphan re-parenting\n- Root pid ถูก kill เป็นลำดับสุดท้าย\n- ค่าที่คืนมาคือจำนวนการยุติที่สำเร็จ\n\nพฤติกรรม Signal:\n\n- POSIX: `signal` ที่ให้มาจะถูกส่งไปยัง `kill`\n- Windows: `signal` จะถูกเพิกเฉย; การยุติเป็นแบบ unconditional process terminate\n\n### พฤติกรรมเมื่อเกิดความล้มเหลว\n\nโมดูลนี้ออกแบบมาโดยเจตนาให้ไม่ throw ที่พื้นผิว API:\n\n- สาขา process tree ที่หายไป/เข้าถึงไม่ได้จะถูกข้าม,\n- ความล้มเหลวในการ kill ต่อ pid จะนับเป็นไม่สำเร็จ (ไม่ใช่ข้อผิดพลาด),\n- การค้นหาที่พลาดมักให้ผล `[]` จาก `listDescendants` และ `0` จาก `killTree`\n\n## ระบบย่อยการแยกวิเคราะห์คีย์ (`keys`)\n\n### โมเดล API\n\nตัวช่วยที่เปิดเผย:\n\n- `parseKey(data, kittyProtocolActive)`\n- `matchesKey(data, keyId, kittyProtocolActive)`\n- `parseKittySequence(data)`\n- `matchesKittySequence(data, expectedCodepoint, expectedModifier)`\n- `matchesLegacySequence(data, keyName)`\n\n### โมเดลการแยกวิเคราะห์\n\nParser รวม:\n\n- การ mapping แบบ single-byte โดยตรง (`enter`, `tab`, `ctrl+<letter>`, ASCII ที่พิมพ์ได้),\n- การค้นหา escape-sequence แบบ legacy แบบ O(1) (PHF map),\n- การแยกวิเคราะห์ `modifyOtherKeys` ของ xterm,\n- การแยกวิเคราะห์ Kitty protocol (`CSI u`, `CSI ~`, `CSI 1;...<letter>`),\n- การ normalize เป็น key IDs (`ctrl+c`, `shift+tab`, `pageUp`, `f5` เป็นต้น)\n\nการจัดการ Modifier:\n\n- เปรียบเทียบเฉพาะ bit shift/alt/ctrl สำหรับการจับคู่คีย์,\n- bit lock ถูก mask ออกก่อนการเปรียบเทียบ\n\nพฤติกรรม Layout:\n\n- การ fallback ของ base-layout ถูกจำกัดโดยเจตนาเพื่อให้ layout ที่ถูก remap ไม่สร้าง false match สำหรับตัวอักษร/สัญลักษณ์ ASCII\n\n### พฤติกรรมเมื่อเกิดความล้มเหลว\n\n- ลำดับที่ไม่รู้จักหรือไม่ถูกต้องจะให้ผล `null` จากฟังก์ชัน parse\n- ฟังก์ชัน match คืนค่า `false` เมื่อ parse ล้มเหลวหรือไม่ตรงกัน\n- ไม่มีพื้นผิวของ error ที่ throw สำหรับ key input ที่ไม่ถูกต้อง\n\n## การ mapping ระหว่าง JS wrapper API และ Rust export\n\n### Shell + PTY + กระบวนการ\n\n| TS wrapper API | Rust N-API export | หมายเหตุ |\n|---|---|---|\n| `executeShell(options, onChunk?)` | `executeShell` (`execute_shell`) | การรัน shell แบบครั้งเดียว |\n| `new Shell(options?)` | `Shell` class | เซสชัน shell แบบถาวร |\n| `shell.run(options, onChunk?)` | `Shell::run` | นำเซสชันกลับมาใช้เมื่อ keepalive control flow |\n| `shell.abort()` | `Shell::abort` | Abort การรันที่ active สำหรับ shell instance นั้น |\n| `new PtySession()` | `PtySession` class | เซสชัน PTY แบบ stateful |\n| `pty.start(options, onChunk?)` | `PtySession::start` | การรัน PTY แบบ interactive |\n| `pty.write(data)` | `PtySession::write` | Raw stdin passthrough |\n| `pty.resize(cols, rows)` | `PtySession::resize` | ขนาดเทอร์มินัลแบบ clamped |\n| `pty.kill()` | `PtySession::kill` | Force-kill child PTY ที่ active |\n| `killTree(pid, signal)` | `killTree` (`kill_tree`) | การยุติ process tree แบบ children-first |\n| `listDescendants(pid)` | `listDescendants` (`list_descendants`) | รายการ descendants แบบ recursive |\n\n### คีย์\n\n| TS wrapper API | Rust N-API export | หมายเหตุ |\n|---|---|---|\n| `matchesKittySequence(data, cp, mod)` | `matchesKittySequence` (`matches_kitty_sequence`) | การจับคู่ Kitty codepoint+modifier |\n| `parseKey(data, kittyProtocolActive)` | `parseKey` (`parse_key`) | Parser key-id แบบ normalized |\n| `matchesLegacySequence(data, keyName)` | `matchesLegacySequence` (`matches_legacy_sequence`) | การตรวจสอบ legacy sequence map แบบตรงทั้งหมด |\n| `parseKittySequence(data)` | `parseKittySequence` (`parse_kitty_sequence`) | ผลการ parse Kitty แบบ structured |\n| `matchesKey(data, keyId, kittyProtocolActive)` | `matchesKey` (`matches_key`) | ตัวจับคู่คีย์ระดับสูง |\n\n## หมายเหตุเกี่ยวกับการทำความสะอาดเซสชันที่ถูกทิ้งและการ Finalize\n\n- **เซสชัน Shell แบบถาวร**: หากการรันถูกยกเลิก/timeout/เกิดข้อผิดพลาด/non-keepalive control flow Rust จะ drop สถานะเซสชันภายในอย่างชัดเจน การรันปกติที่สำเร็จจะเก็บเซสชันไว้เพื่อนำกลับมาใช้\n- **เซสชัน PTY**: `core` จะถูกล้างเสมอหลังจาก `start()` เสร็จสิ้น รวมถึงเส้นทางความล้มเหลว\n- **ไม่มี contract การ kill ที่ขับเคลื่อนด้วย JS finalizer อย่างชัดเจน** ที่เปิดเผยโดย wrapper; การทำความสะอาดเชื่อมโยงกับเส้นทางการเสร็จสิ้น/ยกเลิกการรันเป็นหลัก ผู้เรียกควรใช้ `timeoutMs`, `AbortSignal`, `shell.abort()` หรือ `pty.kill()` เพื่อการ teardown ที่กำหนดได้\n",
	"th/natives/natives-text-search-pipeline.md": "---\ntitle: ไปป์ไลน์ข้อความและการค้นหาแบบเนทีฟ\ndescription: >-\n  ไปป์ไลน์การค้นหาข้อความแบบเนทีฟด้วย grep, glob\n  และการจัดทำดัชนีเนื้อหาไฟล์บนพื้นฐาน ripgrep\nsidebar:\n  order: 6\n  label: ไปป์ไลน์ข้อความและการค้นหา\ni18n:\n  sourceHash: 0e93462fdd12\n  translator: machine\n---\n\n# ไปป์ไลน์ข้อความ/การค้นหาแบบเนทีฟ\n\nเอกสารนี้แมปพื้นผิวข้อความ/การค้นหาของ `@f5-sales-demo/pi-natives` (`grep`, `glob`, `text`, `highlight`) จาก TypeScript wrappers ไปยัง Rust N-API exports และกลับมาเป็น JS result objects\n\nคำศัพท์ตาม `docs/natives-architecture.md`:\n\n- **Wrapper**: TS API ใน `packages/natives/src/*`\n- **Rust module layer**: N-API exports ใน `crates/pi-natives/src/*`\n- **Shared scan cache**: แคชรายการไดเรกทอรีที่รองรับโดย `fs_cache` ซึ่งใช้โดยกระบวนการค้นพบ/ค้นหา\n\n## ไฟล์การนำไปใช้งาน\n\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/glob_util.rs`\n- `crates/pi-natives/src/fs_cache.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/highlight.rs`\n- `crates/pi-natives/src/fd.rs`\n\n## การแมป JS API ↔ Rust export\n\n| JS wrapper API | Rust export (`#[napi]`, snake_case -> camelCase) | Rust module |\n| --- | --- | --- |\n| `grep(options, onMatch?)` | `grep` | `grep.rs` |\n| `searchContent(content, options)` | `search` | `grep.rs` |\n| `hasMatch(content, pattern, options?)` | `hasMatch` | `grep.rs` |\n| `fuzzyFind(options)` | `fuzzyFind` | `fd.rs` |\n| `glob(options, onMatch?)` | `glob` | `glob.rs` |\n| `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `fs_cache.rs` |\n| `wrapTextWithAnsi(text, width)` | `wrapTextWithAnsi` | `text.rs` |\n| `truncateToWidth(text, maxWidth, ellipsis, pad)` | `truncateToWidth` | `text.rs` |\n| `sliceWithWidth(line, startCol, length, strict?)` | `sliceWithWidth` | `text.rs` |\n| `extractSegments(line, beforeEnd, afterStart, afterLen, strictAfter)` | `extractSegments` | `text.rs` |\n| `sanitizeText(text)` | `sanitizeText` | `text.rs` |\n| `visibleWidth(text)` | `visibleWidth` | `text.rs` |\n| `highlightCode(code, lang, colors)` | `highlightCode` | `highlight.rs` |\n| `supportsLanguage(lang)` | `supportsLanguage` | `highlight.rs` |\n| `getSupportedLanguages()` | `getSupportedLanguages` | `highlight.rs` |\n\n## ภาพรวมไปป์ไลน์แยกตามระบบย่อย\n\n## 1) การค้นหาด้วย Regex (`grep`, `searchContent`, `hasMatch`)\n\n### กระบวนการรับ input/options\n\n1. TS wrapper ส่ง options ไปยัง native:\n   - `grep/index.ts` ส่ง `options` ไปเกือบไม่เปลี่ยนแปลง และห่อ callback จาก `(match) => void` ให้เป็น napi threadsafe callback shape `(err, match)`\n   - `searchContent` และ `hasMatch` ส่ง string/`Uint8Array` โดยตรง\n2. Rust option structs ใน `grep.rs` แปลงฟิลด์ camelCase (`ignoreCase`, `maxCount`, `contextBefore`, `contextAfter`, `maxColumns`, `timeoutMs`)\n3. `grep` สร้าง `CancelToken` จาก `timeoutMs` + `AbortSignal` และรันภายใน `task::blocking(\"grep\", ...)`\n\n### สาขาการประมวลผล\n\n- **สาขาในหน่วยความจำ (pure utility)**\n  - `search` → `search_sync` → `run_search` บนไบต์เนื้อหาที่ให้มา\n  - ไม่มีการสแกนระบบไฟล์ ไม่ใช้ `fs_cache`\n- **สาขาไฟล์เดียว (ขึ้นอยู่กับระบบไฟล์)**\n  - `grep_sync` แก้ไข path, ตรวจสอบ metadata ว่าเป็นไฟล์, สตรีมสูงสุด `MAX_FILE_BYTES` ต่อไฟล์ (`4 MiB`) ผ่าน ripgrep matcher\n- **สาขาไดเรกทอรี (ขึ้นอยู่กับระบบไฟล์)**\n  - ค้นหาแคชเสริมผ่าน `fs_cache::get_or_scan` เมื่อ `cache: true`\n  - สแกนใหม่ผ่าน `fs_cache::force_rescan` เมื่อ `cache: false`\n  - ตรวจสอบผลลัพธ์ว่างซ้ำเสริมเมื่ออายุแคชเกิน `empty_recheck_ms()`\n  - การกรอง entry: เฉพาะไฟล์ + ตัวกรอง glob เสริม (`glob_util`) + ตัวกรองประเภทเสริม (`js`, `ts`, `rust` เป็นต้น)\n\n### ความหมายการค้นหา/รวบรวมผลลัพธ์\n\n- Regex engine: `grep_regex::RegexMatcherBuilder` พร้อม `ignoreCase` และ `multiline`\n- การกำหนด context:\n  - `contextBefore/contextAfter` แทนที่ค่า `context` เดิม\n  - โหมดที่ไม่ใช่ content จะเซ็ตการรวบรวม context เป็นศูนย์\n- โหมดเอาต์พุต:\n  - `content` => หนึ่ง `GrepMatch` ต่อการพบ\n  - `count` และ `filesWithMatches` ทั้งสองแมปไปยัง count-style entries (`lineNumber=0`, `line=\"\"`, `matchCount` ถูกตั้งค่า)\n- ขีดจำกัด:\n  - `offset` และ `maxCount` แบบ global ใช้กับทุกไฟล์\n  - เส้นทางขนานใช้เฉพาะเมื่อ `maxCount` ไม่ได้ตั้งค่าและ `offset == 0` มิฉะนั้นเส้นทางลำดับจะรักษาความหมาย global offset/limit แบบ deterministic\n\n### การจัดรูปแบบผลลัพธ์กลับสู่ JS\n\n- ฟิลด์ `SearchResult`/`GrepResult` ของ Rust แมปไปยัง TS types ผ่านการแปลงฟิลด์ N-API object\n- ตัวนับถูกจำกัดที่ `u32` ก่อนข้าม N-API\n- Boolean เสริมจะถูกละเว้นหากไม่เป็น true ในบางเส้นทาง (`limitReached`)\n- Streaming callback รับแต่ละ `GrepMatch` ที่จัดรูปแบบแล้ว (content หรือ count entry)\n\n### พฤติกรรมเมื่อเกิดข้อผิดพลาด\n\n- `searchContent` คืนค่า `SearchResult.error` สำหรับข้อผิดพลาด regex/การค้นหาแทนการ throw\n- `grep` ปฏิเสธด้วยข้อผิดพลาดร้ายแรง (path ไม่ถูกต้อง, glob/regex ไม่ถูกต้อง, การยกเลิก timeout/abort)\n- `hasMatch` คืนค่า `Result<bool>` และ throw เมื่อ pattern/UTF-8 decoding errors ไม่ถูกต้อง\n- ข้อผิดพลาดการเปิดไฟล์/การค้นหาในการสแกนหลายไฟล์จะถูกข้ามต่อไฟล์ การสแกนดำเนินต่อไป\n\n### การจัดการ regex ที่ผิดรูปแบบ\n\n`grep.rs` ทำความสะอาดวงเล็บปีกกาก่อน compile regex:\n\n- วงเล็บปีกกาที่คล้ายการทำซ้ำที่ไม่ถูกต้องจะถูก escape (`{`/`}` -> `\\{`/`\\}`) เมื่อไม่สามารถสร้าง `{N}`, `{N,}`, `{N,M}` ได้\n- ป้องกันไม่ให้ template fragments ทั่วไป (เช่น `${platform}`) ล้มเหลวเป็น malformed repetition\n- ไวยากรณ์ regex ที่ไม่ถูกต้องที่เหลืออยู่ยังคงคืนค่า regex error\n\n## 2) การค้นพบไฟล์ (`glob`) และการค้นหา path แบบ fuzzy (`fuzzyFind`)\n\n`glob` และ `fuzzyFind` แชร์การสแกน `fs_cache` ร่วมกัน แต่ logic การจับคู่แตกต่างกัน\n\n### กระบวนการ `glob`\n\n1. TS wrapper (`glob/index.ts`):\n   - `path.resolve(options.path)`\n   - ค่าเริ่มต้น: `pattern=\"*\"`, `hidden=false`, `gitignore=true`, `recursive=true`\n2. Rust `glob` สร้าง `GlobConfig` และ compile pattern ผ่าน `glob_util::compile_glob`\n3. แหล่ง entry:\n   - `cache=true` => `get_or_scan` + `force_rescan` เสริมเมื่อผลลัพธ์ว่างล้าสมัย\n   - `cache=false` => `force_rescan(..., store=false)` (ใหม่เท่านั้น)\n4. การกรอง:\n   - ข้าม `.git` เสมอ\n   - ข้าม `node_modules` ยกเว้นเมื่อร้องขอ (`includeNodeModules` หรือ pattern ที่กล่าวถึง node_modules)\n   - ใช้การจับคู่ glob\n   - ใช้ตัวกรองประเภทไฟล์ การกรอง symlink `file/dir` จะแก้ไข target metadata\n5. เรียงลำดับเสริมตาม mtime จากมากไปน้อย (`sortByMtime`) ก่อนตัดเหลือ `maxResults`\n\n### กระบวนการ `fuzzyFind` (นำไปใช้ใน `fd.rs`)\n\n1. TS wrapper ส่งออกจาก module `grep` แต่การนำไปใช้งาน Rust อยู่ใน `fd.rs`\n2. แหล่งการสแกนที่แชร์จาก `fs_cache` พร้อมการแยก cache/no-cache และนโยบาย stale-empty recheck เดียวกัน\n3. การให้คะแนน:\n   - exact / starts-with / contains / subsequence-based fuzzy score\n   - เส้นทางการให้คะแนนที่ normalize separator/punctuation\n   - directory bonus และ tie-break แบบ deterministic (`score desc`, แล้ว `path asc`)\n4. Symlink entries ถูกยกเว้นจากผลลัพธ์ fuzzy\n\n### พฤติกรรมเมื่อเกิดข้อผิดพลาด\n\n- glob pattern ไม่ถูกต้อง => error จาก `glob_util::compile_glob`\n- Search root ต้องเป็นไดเรกทอรีที่มีอยู่ (`resolve_search_path`) มิฉะนั้น error\n- การยกเลิก/timeout ส่งต่อเป็น abort errors ผ่านการตรวจสอบ `CancelToken::heartbeat()` ในลูป\n\n### การจัดการ glob ที่ผิดรูปแบบ\n\n`glob_util::build_glob_pattern` มีความยืดหยุ่น:\n\n- Normalize `\\` เป็น `/`\n- เพิ่ม `**/` นำหน้าโดยอัตโนมัติสำหรับ simple recursive patterns เมื่อ `recursive=true`\n- ปิดกลุ่ม alternation `{...` ที่ไม่สมดุลโดยอัตโนมัติก่อน compile\n\n## 3) วงจรชีวิตการสแกน/แคชที่แชร์ (`fs_cache`)\n\n`fs_cache` เก็บผลลัพธ์การสแกนเป็น entries สัมพัทธ์ที่ normalize (`path`, `fileType`, `mtime` เสริม) ที่ key โดย:\n\n- search root แบบ canonical\n- `include_hidden`\n- `use_gitignore`\n\n### การเปลี่ยนสถานะแคช\n\n1. **Miss / ปิดใช้งาน**\n   - TTL เป็น `0` หรือ key ไม่มี/หมดอายุ -> `collect_entries` ใหม่\n2. **Hit**\n   - อายุ entry `< cache_ttl_ms()` -> คืนค่า entries ที่แคชไว้ + `cache_age_ms`\n3. **Stale-empty recheck** (นโยบายผู้เรียกใน `glob`/`grep`/`fd`)\n   - หาก query ให้ผลการจับคู่เป็นศูนย์และ `cache_age_ms >= empty_recheck_ms()` จะ force rescan หนึ่งครั้ง\n4. **การยกเลิก**\n   - `invalidateFsScanCache(path?)`:\n     - ไม่มี argument: ลบ keys ทั้งหมด\n     - มี path argument: ลบ keys ที่ root เป็น prefix ของ target path นั้น\n\n### การแลกเปลี่ยนผลลัพธ์ล้าสมัย\n\n- แคชให้ความสำคัญกับการสแกนซ้ำที่มี latency ต่ำมากกว่าความสอดคล้องทันที\n- ช่วง TTL อาจคืนค่า positives/negatives ที่ล้าสมัย\n- Empty-result recheck ลด stale negatives สำหรับการสแกนที่แคชไว้เก่ากว่าโดยแลกกับการสแกนเพิ่มเติมหนึ่งครั้ง\n- การยกเลิกที่ชัดเจนคือ hook ความถูกต้องที่ตั้งใจไว้หลังจากการเปลี่ยนแปลงไฟล์\n\n## 4) ยูทิลิตี้ข้อความ ANSI (`text`)\n\nยูทิลิตี้เหล่านี้เป็น pure, in-memory utilities (ไม่มีการสแกนระบบไฟล์)\n\n### ขอบเขตและความรับผิดชอบ\n\n- **`text.rs` เป็นเจ้าของ terminal-cell semantics**:\n  - การแยกวิเคราะห์ ANSI sequence\n  - ความกว้างและการตัด slice ที่รับรู้ grapheme\n  - พฤติกรรม wrap/truncate/sanitize\n- **การตัด line ใน `grep.rs` (`maxColumns`) แยกต่างหาก**:\n  - การตัด matched lines ที่ character-boundary อย่างง่ายพร้อม `...`\n  - ไม่รักษา ANSI-state และไม่รับรู้ความกว้าง terminal-cell\n\n### พฤติกรรมหลัก\n\n- `wrapTextWithAnsi`: ตัดบรรทัดตามความกว้างที่มองเห็นได้ ส่ง active SGR codes ข้ามบรรทัดที่ตัดแล้ว\n- `truncateToWidth`: การตัด visible-cell พร้อมนโยบาย ellipsis (`Unicode`, `Ascii`, `Omit`), padding ด้านขวาเสริม และ fast-path ที่คืนค่า JS string เดิมเมื่อไม่เปลี่ยนแปลง\n- `sliceWithWidth`: การตัด column slice พร้อมการบังคับความกว้างแบบ strict เสริม\n- `extractSegments`: แยก before/after segments รอบ overlay ขณะคืนค่า ANSI state สำหรับ segment `after`\n- `sanitizeText`: ลบ ANSI escapes + control chars, ทิ้ง lone surrogates, normalize CR/LF โดยลบ `\\r`\n- `visibleWidth`: นับ visible terminal cells (tabs ใช้ `TAB_WIDTH` คงที่จากการนำไปใช้ Rust)\n\n### พฤติกรรมเมื่อเกิดข้อผิดพลาด\n\nฟังก์ชัน Text โดยทั่วไปคืนค่าเอาต์พุตที่แปลงแบบ deterministic ข้อผิดพลาดจำกัดอยู่ที่ขอบเขตการแปลง JS string (ความล้มเหลวการแปลง argument ของ N-API)\n\n## 5) การ highlight syntax (`highlight`)\n\n`highlight.rs` เป็น pure transformation (ไม่มี FS ไม่มีแคช)\n\n### กระบวนการ\n\n1. Wrapper ส่ง `code`, `lang` เสริม และ ANSI color palette\n2. Rust แก้ไข syntax โดย:\n   - ค้นหา token/ชื่อ\n   - ค้นหา extension\n   - fallback ของ alias table (`ts/tsx/js -> JavaScript` เป็นต้น)\n   - fallback เป็น plain text syntax เมื่อไม่สามารถแก้ไขได้\n3. แยกวิเคราะห์แต่ละบรรทัดด้วย syntect `ParseState` และ scope stack\n4. แมป scopes ไปยัง 11 หมวดหมู่สี semantic และ inject/reset ANSI color codes\n\n### พฤติกรรมเมื่อเกิดข้อผิดพลาด\n\n- ข้อผิดพลาดการแยกวิเคราะห์ต่อบรรทัดไม่ทำให้การเรียกล้มเหลว: บรรทัดนั้นถูกเพิ่มโดยไม่ highlight และการประมวลผลดำเนินต่อไป\n- ภาษาที่ไม่รู้จัก/ไม่รองรับจะ fallback เป็น plain text syntax\n\n## Pure utility เทียบกับ flows ที่ขึ้นอยู่กับระบบไฟล์\n\n| Flow | การเข้าถึงระบบไฟล์ | แคชที่แชร์ | หมายเหตุ |\n| --- | --- | --- | --- |\n| `searchContent` / `hasMatch` | ไม่มี | ไม่มี | regex บน bytes/string ที่ให้มาเท่านั้น |\n| ฟังก์ชัน module `text` | ไม่มี | ไม่มี | ANSI/width/sanitization เท่านั้น |\n| ฟังก์ชัน module `highlight` | ไม่มี | ไม่มี | syntax + ANSI coloring เท่านั้น |\n| `glob` | มี | เสริม | directory scans + glob filtering |\n| `fuzzyFind` | มี | เสริม | directory scans + fuzzy scoring |\n| `grep` (file/dir path) | มี | เสริม (dir mode) | ripgrep เหนือไฟล์, filters/callback เสริม |\n\n## สรุปวงจรชีวิต end-to-end\n\n1. Caller เรียก TS wrapper พร้อม typed options\n2. Wrapper normalize ค่าเริ่มต้น (โดยเฉพาะ `glob`) และส่งต่อไปยัง export `native.*`\n3. Rust ตรวจสอบ/normalize options และสร้าง matcher/search config\n4. สำหรับ filesystem flows entries จะถูกสแกน (cache hit/miss/rescan) จากนั้นกรอง/ให้คะแนน\n5. Worker loops เรียก cancel heartbeat เป็นระยะ timeout/abort สามารถยุติการประมวลผล\n6. Rust จัดรูปแบบเอาต์พุตเป็น N-API objects (`lineNumber`, `matchCount`, `limitReached` เป็นต้น)\n7. TS wrapper คืนค่า typed JS objects (และ per-match callbacks เสริมสำหรับ `grep`/`glob`)\n",
	"th/natives/porting-to-natives.md": "---\ntitle: การพอร์ตไปยัง pi-natives (N-API) — บันทึกภาคสนาม\ndescription: >-\n  บันทึกภาคสนามสำหรับการย้าย Node.js child_process และโค้ด shell ไปยังเลเยอร์\n  native N-API ของ Rust\nsidebar:\n  order: 9\n  label: การพอร์ตไปยัง pi-natives\ni18n:\n  sourceHash: 4f5150286535\n  translator: machine\n---\n\n# การพอร์ตไปยัง pi-natives (N-API) — บันทึกภาคสนาม\n\nนี่คือคู่มือปฏิบัติสำหรับการย้าย hot paths เข้าสู่ `crates/pi-natives` และเชื่อมต่อผ่าน JS bindings มีอยู่เพื่อป้องกันไม่ให้ความผิดพลาดเดิมเกิดขึ้นซ้ำสอง\n\n## เมื่อใดควรพอร์ต\n\nให้พอร์ตเมื่อเป็นไปตามเงื่อนไขใดเงื่อนไขหนึ่งต่อไปนี้:\n\n- hot path ทำงานใน render loops, การอัปเดต UI ที่หนาแน่น หรือ batch ขนาดใหญ่\n- JS allocations ครอบงำ (string churn, regex backtracking, อาร์เรย์ขนาดใหญ่)\n- คุณมี JS baseline อยู่แล้วและสามารถ benchmark ทั้งสองเวอร์ชันควบคู่กันได้\n- งานผูกกับ CPU หรือเป็น blocking I/O ที่สามารถรันบน libuv thread pool ได้\n- งานเป็น async I/O ที่สามารถรันบน Tokio's runtime ได้ (เช่น การรัน shell)\n\nหลีกเลี่ยงการพอร์ตที่ขึ้นอยู่กับ JS-only state หรือ dynamic imports N-API exports ควรเป็น pure, data-in/data-out งานที่ใช้เวลานานควรผ่าน `task::blocking` (CPU-bound/blocking I/O) หรือ `task::future` (async I/O) พร้อมการยกเลิก\n\n## โครงสร้างของ native export\n\n**ฝั่ง Rust:**\n\n- การ implementation อยู่ใน `crates/pi-natives/src/<module>.rs` หากคุณเพิ่ม module ใหม่ ให้ลงทะเบียนใน `crates/pi-natives/src/lib.rs`\n- Export ด้วย `#[napi]`; snake_case exports จะถูกแปลงเป็น camelCase โดยอัตโนมัติ ใช้ `js_name` อย่างชัดเจนเฉพาะสำหรับ aliases/ชื่อที่ไม่ใช่ค่าเริ่มต้นเท่านั้น ใช้ `#[napi(object)]` สำหรับ structs\n- ใช้ `task::blocking(tag, cancel_token, work)` (ดู `crates/pi-natives/src/task.rs`) สำหรับงาน CPU-bound หรือ blocking ใช้ `task::future(env, tag, work)` สำหรับงาน async ที่ต้องการ Tokio (เช่น shell sessions) ส่ง `CancelToken` เมื่อคุณเปิดเผย `timeoutMs` หรือ `AbortSignal`\n\n**ฝั่ง JS:**\n\n- `packages/natives/src/bindings.ts` เก็บ interface `NativeBindings` พื้นฐาน\n- `packages/natives/src/<module>/types.ts` กำหนด TS types และเพิ่มเติม `NativeBindings` ผ่าน declaration merging\n- `packages/natives/src/native.ts` import ไฟล์ `<module>/types.ts` แต่ละไฟล์เพื่อเปิดใช้งาน declarations\n- `packages/natives/src/<module>/index.ts` ห่อหุ้ม `native` binding จาก `packages/natives/src/native.ts`\n- `packages/natives/src/native.ts` โหลด addon และ `validateNative` บังคับใช้ exports ที่จำเป็น\n- `packages/natives/src/index.ts` re-export wrapper สำหรับ callers ใน `packages/*`\n\n## รายการตรวจสอบการพอร์ต\n\n1. **เพิ่ม Rust implementation**\n\n- วาง core logic ในฟังก์ชัน Rust ธรรมดา\n- หากเป็น module ใหม่ ให้เพิ่มใน `crates/pi-natives/src/lib.rs`\n- เปิดเผยด้วย `#[napi]` เพื่อให้การ mapping snake_case -> camelCase เริ่มต้นสอดคล้องกัน\n- รักษา signatures ให้เป็น owned และเรียบง่าย: `String`, `Vec<String>`, `Uint8Array`, หรือ `Either<JsString, Uint8Array>` สำหรับ input string/byte ขนาดใหญ่\n- สำหรับงาน CPU-bound หรือ blocking ให้ใช้ `task::blocking`; สำหรับงาน async ให้ใช้ `task::future` ส่ง `CancelToken` และเรียก `heartbeat()` ภายใน loop ที่ใช้เวลานาน\n\n2. **เชื่อมต่อ JS bindings**\n\n- เพิ่ม types และ `NativeBindings` augmentation ใน `packages/natives/src/<module>/types.ts`\n- Import `./<module>/types` ใน `packages/natives/src/native.ts` เพื่อเรียกใช้ declaration merging\n- เพิ่ม wrapper ใน `packages/natives/src/<module>/index.ts` ที่เรียก `native`\n- Re-export จาก `packages/natives/src/index.ts`\n\n3. **อัปเดต native validation**\n\n- เพิ่ม `checkFn(\"newExport\")` ใน `validateNative` (`packages/natives/src/native.ts`)\n\n4. **เพิ่ม benchmarks**\n\n- วาง benchmarks ไว้ข้างๆ package ที่เป็นเจ้าของ (`packages/tui/bench`, `packages/natives/bench`, หรือ `packages/coding-agent/bench`)\n- รวม JS baseline และเวอร์ชัน native ในการรันเดียวกัน\n- ใช้ `Bun.nanoseconds()` และจำนวน iteration ที่กำหนด\n- รักษา benchmark inputs ให้เล็กและสมจริง (ข้อมูลจริงที่พบใน hot path)\n\n5. **Build native binary**\n\n- `bun --cwd=packages/natives run build`\n- ใช้ `bun --cwd=packages/natives run build` และตั้งค่า `PI_DEV=1` หากต้องการ loader diagnostics ขณะทดสอบ\n\n6. **รัน benchmark**\n\n- `bun run packages/<pkg>/bench/<bench>.ts` (หรือ `bun --cwd=packages/natives run bench`)\n\n7. **ตัดสินใจเรื่องการใช้งาน**\n\n- หาก native ช้ากว่า **ให้คง JS ไว้** และปล่อย native export ไว้โดยไม่ใช้งาน\n- หาก native เร็วกว่า ให้เปลี่ยน call sites ไปใช้ native wrapper\n\n## จุดเจ็บปวดและวิธีหลีกเลี่ยง\n\n### 1) `pi_natives.node` ที่ล้าสมัยป้องกัน exports ใหม่\n\nloader ให้ความสำคัญกับ binary ที่มีแท็กแพลตฟอร์มใน `packages/natives/native` (`pi_natives.<platform>-<arch>.node`) ตอนนี้ `PI_DEV=1` เปิดใช้งานเฉพาะ loader diagnostics เท่านั้น ไม่เปลี่ยนไปใช้ชื่อไฟล์ dev addon แยกต่างหากอีกต่อไป นอกจากนี้ยังมี fallback `pi_natives.node` Compiled binaries จะแตกไฟล์ไปยัง `~/.xcsh/natives/<version>/pi_natives.<platform>-<arch>.node` หากไฟล์เหล่านี้ล้าสมัย exports จะไม่อัปเดต\n\n**แก้ไข:** ลบไฟล์ที่ล้าสมัยก่อน rebuild\n\n```bash\nrm packages/natives/native/pi_natives.linux-x64.node\nrm packages/natives/native/pi_natives.node\nbun --cwd=packages/natives run build\n```\n\nหากคุณกำลังรัน compiled binary ให้ลบ cached addon directory:\n\n```bash\nrm -rf ~/.xcsh/natives/<version>\n```\n\nจากนั้นตรวจสอบว่า export มีอยู่ใน binary:\n\n```bash\nbun -e 'const tag = `${process.platform}-${process.arch}`; const mod = require(`./packages/natives/native/pi_natives.${tag}.node`); console.log(Object.keys(mod).includes(\"newExport\"));'\n```\n\n### 2) ข้อผิดพลาด \"Missing exports\" จาก `validateNative`\n\nนี่เป็นสิ่งที่ **ดี** — มันป้องกันความไม่ตรงกันที่ไม่มีเสียง เมื่อคุณเห็นสิ่งนี้:\n\n```\nNative addon missing exports ... Missing: visibleWidth\n```\n\nหมายความว่า binary ของคุณล้าสมัย, ชื่อ Rust export (หรือ explicit alias เมื่อใช้งาน) ไม่ตรงกับชื่อ JS, หรือ export ไม่ได้ compile เข้ามา แก้ไข build และความไม่ตรงกันของการตั้งชื่อ อย่าทำ validation อ่อนแอลง\n\n### 3) Rust signature ไม่ตรงกัน\n\nรักษาให้เรียบง่ายและ owned `String`, `Vec<String>`, และ `Uint8Array` ใช้งานได้ หลีกเลี่ยง references อย่าง `&str` ใน public exports หากต้องการข้อมูลที่มีโครงสร้าง ให้ห่อใน `#[napi(object)]` structs\n\n### 4) ความผิดพลาดใน benchmarking\n\n- อย่าเปรียบเทียบ inputs หรือ allocations ที่แตกต่างกัน\n- รักษา JS และ native ให้ใช้ input arrays ที่เหมือนกัน\n- รันทั้งคู่ในไฟล์ benchmark เดียวกันเพื่อหลีกเลี่ยงความเบี่ยงเบน\n\n## เทมเพลต Benchmark\n\n```ts\nconst ITERATIONS = 2000;\n\nfunction bench(name: string, fn: () => void): number {\n const start = Bun.nanoseconds();\n for (let i = 0; i < ITERATIONS; i++) fn();\n const elapsed = (Bun.nanoseconds() - start) / 1e6;\n console.log(`${name}: ${elapsed.toFixed(2)}ms total (${(elapsed / ITERATIONS).toFixed(6)}ms/op)`);\n return elapsed;\n}\n\nbench(\"feature/js\", () => {\n jsImpl(sample);\n});\n\nbench(\"feature/native\", () => {\n nativeImpl(sample);\n});\n```\n\n## รายการตรวจสอบการยืนยัน\n\n- `validateNative` ผ่าน (ไม่มี exports ที่ขาดหายไป)\n- `NativeBindings` ถูกเพิ่มเติมใน `packages/natives/src/<module>/types.ts` และ wrapper ถูก re-export ใน `packages/natives/src/index.ts`\n- `Object.keys(require(...))` รวม export ใหม่ของคุณ\n- ตัวเลข Bench ถูกบันทึกใน PR/notes\n- Call site อัปเดต **เฉพาะเมื่อ** native เร็วกว่าหรือเท่ากัน\n\n## กฎเกณฑ์ทั่วไป\n\n- หาก native ช้ากว่า **อย่าเปลี่ยน** เก็บ export ไว้สำหรับงานในอนาคต แต่ TUI ควรอยู่บน path ที่เร็วกว่า\n- หาก native เร็วกว่า ให้เปลี่ยน call site และเก็บ benchmark ไว้เพื่อตรวจจับ regressions\n",
	"th/providers/models.md": "---\ntitle: การกำหนดค่าโมเดลและผู้ให้บริการ\ndescription: >-\n  รีจิสทรีโมเดลและการกำหนดค่าผู้ให้บริการผ่าน models.yml พร้อมการกำหนดเส้นทาง\n  การสำรอง และราคา\nsidebar:\n  order: 1\n  label: โมเดลและผู้ให้บริการ\ni18n:\n  sourceHash: 8053df967ff6\n  translator: machine\n---\n\n# การกำหนดค่าโมเดลและผู้ให้บริการ (`models.yml`)\n\nเอกสารนี้อธิบายว่า coding-agent โหลดโมเดลอย่างไร ใช้การแทนที่อย่างไร แก้ไขข้อมูลรับรองอย่างไร และเลือกโมเดลในขณะรันไทม์อย่างไร\n\n## สิ่งที่ควบคุมพฤติกรรมของโมเดล\n\nไฟล์การนำไปใช้งานหลัก:\n\n- `src/config/model-registry.ts` — โหลดโมเดลในตัว + โมเดลที่กำหนดเอง, การแทนที่ผู้ให้บริการ, การค้นพบขณะรันไทม์, การรวมการตรวจสอบสิทธิ์\n- `src/config/model-resolver.ts` — แยกวิเคราะห์รูปแบบโมเดลและเลือกโมเดล initial/smol/slow\n- `src/config/settings-schema.ts` — การตั้งค่าที่เกี่ยวข้องกับโมเดล (`modelRoles`, ค่ากำหนดการส่งของผู้ให้บริการ)\n- `src/session/auth-storage.ts` — ลำดับการแก้ไข API key + OAuth\n- `packages/ai/src/models.ts` และ `packages/ai/src/types.ts` — ผู้ให้บริการ/โมเดลในตัว และประเภท `Model`/`compat`\n\n## ตำแหน่งไฟล์กำหนดค่าและพฤติกรรมเดิม\n\nเส้นทางกำหนดค่าเริ่มต้น:\n\n- `~/.xcsh/agent/models.yml`\n\nพฤติกรรมเดิมที่ยังมีอยู่:\n\n- หาก `models.yml` ไม่มีอยู่และ `models.json` มีอยู่ในตำแหน่งเดียวกัน ไฟล์จะถูกย้ายไปเป็น `models.yml`\n- เส้นทางกำหนดค่า `.json` / `.jsonc` ที่ระบุอย่างชัดเจนยังคงรองรับเมื่อส่งผ่านทางโปรแกรมไปยัง `ModelRegistry`\n\n## โครงสร้าง `models.yml`\n\n```yaml\nconfigVersion: 1  # optional — written by auto-config, used for migration detection\nproviders:\n  <provider-id>:\n    # provider-level config\nequivalence:\n  overrides:\n    <provider-id>/<model-id>: <canonical-model-id>\n  exclude:\n    - <provider-id>/<model-id>\n```\n\n`configVersion` คือจำนวนเต็มเสริมที่เขียนโดยระบบกำหนดค่าอัตโนมัติ เมื่อมีอยู่ xcsh จะใช้เพื่อตรวจจับการกำหนดค่าที่ล้าสมัยและอัปเกรดโดยอัตโนมัติ\n\n`provider-id` คือคีย์ผู้ให้บริการตามแบบแผนที่ใช้ทั่วทั้งการเลือกและการค้นหาการตรวจสอบสิทธิ์\n\n`equivalence` เป็นค่าเสริมและกำหนดค่าการจัดกลุ่มโมเดลตามแบบแผนบนโมเดลผู้ให้บริการที่เป็นรูปธรรม:\n\n- `overrides` แมปตัวเลือกที่เป็นรูปธรรมแบบแน่นอน (`provider/modelId`) ไปยัง id ตามแบบแผนอย่างเป็นทางการ\n- `exclude` ยกเว้นตัวเลือกที่เป็นรูปธรรมออกจากการจัดกลุ่มตามแบบแผน\n\n## ฟิลด์ระดับผู้ให้บริการ\n\n```yaml\nproviders:\n  my-provider:\n    baseUrl: https://api.example.com/v1\n    apiKey: MY_PROVIDER_API_KEY\n    api: openai-completions\n    headers:\n      X-Team: platform\n    authHeader: true\n    auth: apiKey\n    discovery:\n      type: ollama\n    modelOverrides:\n      some-model-id:\n        name: Renamed model\n    models:\n      - id: some-model-id\n        name: Some Model\n        api: openai-completions\n        reasoning: false\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 128000\n        maxTokens: 16384\n        headers:\n          X-Model: value\n        compat:\n          supportsStore: true\n          supportsDeveloperRole: true\n          supportsReasoningEffort: true\n          maxTokensField: max_completion_tokens\n          openRouterRouting:\n            only: [anthropic]\n          vercelGatewayRouting:\n            order: [anthropic, openai]\n          extraBody:\n            gateway: m1-01\n            controller: mlx\n```\n\n### ค่า `api` ที่อนุญาตสำหรับผู้ให้บริการ/โมเดล\n\n- `openai-completions`\n- `openai-responses`\n- `openai-codex-responses`\n- `azure-openai-responses`\n- `anthropic-messages`\n- `google-generative-ai`\n- `google-vertex`\n\n### ค่า auth/discovery ที่อนุญาต\n\n- `auth`: `apiKey` (ค่าเริ่มต้น) หรือ `none`\n- `discovery.type`: `ollama`\n\n## กฎการตรวจสอบความถูกต้อง (ปัจจุบัน)\n\n### ผู้ให้บริการที่กำหนดเองอย่างสมบูรณ์ (`models` ไม่ว่างเปล่า)\n\nจำเป็นต้องมี:\n\n- `baseUrl`\n- `apiKey` ยกเว้นเมื่อ `auth: none`\n- `api` ที่ระดับผู้ให้บริการหรือในแต่ละโมเดล\n\n### ผู้ให้บริการที่แทนที่เท่านั้น (`models` หายไปหรือว่างเปล่า)\n\nต้องกำหนดอย่างน้อยหนึ่งอย่างในนี้:\n\n- `baseUrl`\n- `modelOverrides`\n- `discovery`\n\n### การค้นพบ\n\n- `discovery` ต้องการ `api` ระดับผู้ให้บริการ\n\n### การตรวจสอบค่าโมเดล\n\n- `id` จำเป็นต้องมี\n- `contextWindow` และ `maxTokens` ต้องเป็นค่าบวกหากระบุไว้\n\n## ลำดับการรวมและการแทนที่\n\nไปป์ไลน์ ModelRegistry (เมื่อรีเฟรช):\n\n1. โหลดผู้ให้บริการ/โมเดลในตัวจาก `@f5-sales-demo/pi-ai`\n2. โหลดการกำหนดค่าที่กำหนดเองจาก `models.yml`\n3. ใช้การแทนที่ผู้ให้บริการ (`baseUrl`, `headers`) กับโมเดลในตัว\n4. ใช้ `modelOverrides` (ต่อผู้ให้บริการ + model id)\n5. รวม `models` ที่กำหนดเอง:\n   - `provider + id` เดียวกันจะแทนที่ที่มีอยู่\n   - มิฉะนั้นจะต่อท้าย\n6. ใช้โมเดลที่ค้นพบขณะรันไทม์ (ปัจจุบันคือ Ollama และ LM Studio) จากนั้นใช้การแทนที่โมเดลซ้ำ\n\n## ความเทียบเท่าของโมเดลตามแบบแผนและการรวม\n\nรีจิสทรีเก็บโมเดลผู้ให้บริการที่เป็นรูปธรรมทุกโมเดล จากนั้นสร้างชั้นตามแบบแผนอยู่เหนือโมเดลเหล่านั้น\n\nCanonical id คือ id อย่างเป็นทางการจาก upstream เท่านั้น ตัวอย่างเช่น:\n\n- `claude-opus-4-6`\n- `claude-haiku-4-5`\n- `gpt-5.3-codex`\n\n### การกำหนดค่าความเทียบเท่าใน `models.yml`\n\nตัวอย่าง:\n\n```yaml\nproviders:\n  zenmux:\n    baseUrl: https://api.zenmux.example/v1\n    apiKey: ZENMUX_API_KEY\n    api: openai-codex-responses\n    models:\n      - id: codex\n        name: Zenmux Codex\n        reasoning: true\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 200000\n        maxTokens: 32768\n\nequivalence:\n  overrides:\n    zenmux/codex: gpt-5.3-codex\n    p-codex/codex: gpt-5.3-codex\n  exclude:\n    - demo/codex-preview\n```\n\nลำดับการสร้างสำหรับการจัดกลุ่มตามแบบแผน:\n\n1. การแทนที่ของผู้ใช้แบบแน่นอนจาก `equivalence.overrides`\n2. การจับคู่ official-id ที่รวมอยู่จากข้อมูลเมตาโมเดลในตัว\n3. การทำให้เป็นมาตรฐานด้วยฮิวริสติกเชิงอนุรักษ์สำหรับตัวแปร gateway/provider\n4. ใช้ id ของโมเดลที่เป็นรูปธรรมเองเป็นค่าสำรอง\n\nฮิวริสติกปัจจุบันแคบโดยเจตนา:\n\n- คำนำหน้า upstream ที่ฝังอยู่สามารถตัดออกได้เมื่อมีอยู่ เช่น `anthropic/...` หรือ `openai/...`\n- ตัวแปรเวอร์ชันที่มีจุดและขีดกลางสามารถทำให้เป็นมาตรฐานได้เฉพาะเมื่อแมปกับ official id ที่มีอยู่ เช่น `4.6 -> 4-6`\n- ตระกูลหรือเวอร์ชันที่คลุมเครือจะไม่ถูกรวมโดยไม่มีการจับคู่ที่รวมอยู่หรือการแทนที่อย่างชัดเจน\n\n### พฤติกรรมการแก้ไขตามแบบแผน\n\nเมื่อตัวแปรที่เป็นรูปธรรมหลายตัวแชร์ canonical id การแก้ไขจะใช้:\n\n1. ความพร้อมใช้งานและการตรวจสอบสิทธิ์\n2. `modelProviderOrder` ใน `config.yml`\n3. ลำดับรีจิสทรี/ผู้ให้บริการที่มีอยู่หาก `modelProviderOrder` ไม่ได้ตั้งค่าไว้\n\nผู้ให้บริการที่ปิดใช้งานหรือไม่ได้รับการตรวจสอบสิทธิ์จะถูกข้าม\n\nสถานะเซสชันและบันทึกการสนทนายังคงบันทึกผู้ให้บริการ/โมเดลที่เป็นรูปธรรมที่ดำเนินการเทิร์นจริง\n\nค่าเริ่มต้นผู้ให้บริการเทียบกับการแทนที่ต่อโมเดล:\n\n- `headers` ของผู้ให้บริการเป็นพื้นฐาน\n- `headers` ของโมเดลแทนที่คีย์ header ของผู้ให้บริการ\n- `modelOverrides` สามารถแทนที่ข้อมูลเมตาของโมเดล (`name`, `reasoning`, `input`, `cost`, `contextWindow`, `maxTokens`, `headers`, `compat`, `contextPromotionTarget`)\n- `compat` ถูกรวมแบบลึกสำหรับบล็อกการกำหนดเส้นทางที่ซ้อนกัน (`openRouterRouting`, `vercelGatewayRouting`, `extraBody`)\n\n## การรวมการค้นพบขณะรันไทม์\n\n### การค้นพบ Ollama โดยปริยาย\n\nหากไม่ได้กำหนดค่า `ollama` อย่างชัดเจน รีจิสทรีจะเพิ่มผู้ให้บริการที่ค้นพบได้โดยปริยาย:\n\n- ผู้ให้บริการ: `ollama`\n- api: `openai-completions`\n- URL พื้นฐาน: `OLLAMA_BASE_URL` หรือ `http://127.0.0.1:11434`\n- โหมดการตรวจสอบสิทธิ์: ไม่ต้องใช้คีย์ (พฤติกรรม `auth: none`)\n\nการค้นพบขณะรันไทม์เรียก `GET /api/tags` บน Ollama และสังเคราะห์รายการโมเดลด้วยค่าเริ่มต้นในเครื่อง\n\n### การค้นพบ llama.cpp โดยปริยาย\n\nหากไม่ได้กำหนดค่า `llama.cpp` อย่างชัดเจน รีจิสทรีจะเพิ่มผู้ให้บริการที่ค้นพบได้โดยปริยาย:\nหมายเหตุ: ใช้ anthropic messages api ใหม่กว่าแทน openai-completions\n\n- ผู้ให้บริการ: `llama.cpp`\n- api: `openai-responses`\n- URL พื้นฐาน: `LLAMA_CPP_BASE_URL` หรือ `http://127.0.0.1:8080`\n- โหมดการตรวจสอบสิทธิ์: ไม่ต้องใช้คีย์ (พฤติกรรม `auth: none`)\n\nการค้นพบขณะรันไทม์เรียก `GET models` บน llama.cpp และสังเคราะห์รายการโมเดลด้วยค่าเริ่มต้นในเครื่อง\n\n### การค้นพบ LM Studio โดยปริยาย\n\nหากไม่ได้กำหนดค่า `lm-studio` อย่างชัดเจน รีจิสทรีจะเพิ่มผู้ให้บริการที่ค้นพบได้โดยปริยาย:\n\n- ผู้ให้บริการ: `lm-studio`\n- api: `openai-completions`\n- URL พื้นฐาน: `LM_STUDIO_BASE_URL` หรือ `http://127.0.0.1:1234/v1`\n- โหมดการตรวจสอบสิทธิ์: ไม่ต้องใช้คีย์ (พฤติกรรม `auth: none`)\n\nการค้นพบขณะรันไทม์ดึงโมเดล (`GET /models`) และสังเคราะห์รายการโมเดลด้วยค่าเริ่มต้นในเครื่อง\n\n### การค้นพบผู้ให้บริการอย่างชัดเจน\n\nคุณสามารถกำหนดค่าการค้นพบด้วยตัวเอง:\n\n```yaml\nproviders:\n  ollama:\n    baseUrl: http://127.0.0.1:11434\n    api: openai-completions\n    auth: none\n    discovery:\n      type: ollama\n      \n  llama.cpp:\n    baseUrl: http://127.0.0.1:8080\n    api: openai-responses\n    auth: none\n    discovery:\n      type: llama.cpp\n```\n\n### การลงทะเบียนผู้ให้บริการส่วนขยาย\n\nส่วนขยายสามารถลงทะเบียนผู้ให้บริการขณะรันไทม์ (`pi.registerProvider(...)`) ได้ รวมถึง:\n\n- การแทนที่/ต่อท้ายโมเดลสำหรับผู้ให้บริการ\n- การลงทะเบียน stream handler ที่กำหนดเองสำหรับ API ID ใหม่\n- การลงทะเบียนผู้ให้บริการ OAuth ที่กำหนดเอง\n\n## ลำดับการแก้ไข Auth และ API key\n\nเมื่อขอคีย์สำหรับผู้ให้บริการ ลำดับที่มีผลบังคับใช้คือ:\n\n1. การแทนที่ขณะรันไทม์ (CLI `--api-key`)\n2. ข้อมูลรับรอง API key ที่เก็บไว้ใน `agent.db`\n3. ข้อมูลรับรอง OAuth ที่เก็บไว้ใน `agent.db` (พร้อมการรีเฟรช)\n4. การแมปตัวแปรสภาพแวดล้อม (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, เป็นต้น)\n5. ตัวแก้ไขสำรอง ModelRegistry (ผู้ให้บริการ `apiKey` จาก `models.yml`, ความหมายของชื่อ env หรือค่าตัวอักษร)\n\nพฤติกรรม `apiKey` ใน `models.yml`:\n\n- ค่าจะถูกพิจารณาเป็นชื่อตัวแปรสภาพแวดล้อมก่อน\n- หากไม่มีตัวแปร env สตริงตัวอักษรจะถูกใช้เป็น token\n\nหาก `authHeader: true` และตั้งค่า `apiKey` ของผู้ให้บริการ โมเดลจะได้รับ:\n\n- header `Authorization: Bearer <resolved-key>` ที่แทรกเข้าไป\n\nผู้ให้บริการที่ไม่ต้องใช้คีย์:\n\n- ผู้ให้บริการที่ทำเครื่องหมาย `auth: none` ถือว่าพร้อมใช้งานโดยไม่ต้องมีข้อมูลรับรอง\n- `getApiKey*` ส่งคืน `kNoAuth` สำหรับผู้ให้บริการเหล่านั้น\n\n## ความพร้อมใช้งานของโมเดลเทียบกับโมเดลทั้งหมด\n\n- `getAll()` ส่งคืนรีจิสทรีโมเดลที่โหลด (ในตัว + ที่กำหนดเองที่รวม + ที่ค้นพบ)\n- `getAvailable()` กรองเฉพาะโมเดลที่ไม่ต้องใช้คีย์หรือมีการตรวจสอบสิทธิ์ที่แก้ไขได้\n\nดังนั้นโมเดลอาจมีอยู่ในรีจิสทรีแต่ไม่สามารถเลือกได้จนกว่าจะมีการตรวจสอบสิทธิ์\n\n## การแก้ไขโมเดลขณะรันไทม์\n\n### CLI และการแยกวิเคราะห์รูปแบบ\n\n`model-resolver.ts` รองรับ:\n\n- `provider/modelId` แบบแน่นอน\n- canonical model id แบบแน่นอน\n- model id แบบแน่นอน (ผู้ให้บริการถูกอนุมาน)\n- การจับคู่แบบฟัซซี/ซับสตริง\n- รูปแบบขอบเขต glob ใน `--models` (เช่น `openai/*`, `*sonnet*`)\n- ส่วนต่อท้าย `:thinkingLevel` เสริม (`off|minimal|low|medium|high|xhigh`)\n\n`--provider` เป็นรุ่นเดิม; `--model` เป็นที่แนะนำ\n\nลำดับความสำคัญของการแก้ไขสำหรับตัวเลือกแบบแน่นอน:\n\n1. `provider/modelId` แบบแน่นอนจะข้ามการรวม\n2. canonical id แบบแน่นอนแก้ไขผ่าน canonical index\n3. bare concrete id แบบแน่นอนยังคงใช้งานได้\n4. การจับคู่แบบฟัซซีและ glob ทำงานหลังจากเส้นทางแบบแน่นอน\n\n### ลำดับความสำคัญในการเลือกโมเดลเริ่มต้น\n\n`findInitialModel(...)` ใช้ลำดับนี้:\n\n1. ผู้ให้บริการ+โมเดล CLI อย่างชัดเจน\n2. โมเดลที่มีขอบเขตแรก (หากไม่ได้กำลังกลับมาทำต่อ)\n3. ผู้ให้บริการ/โมเดลเริ่มต้นที่บันทึกไว้\n4. ค่าเริ่มต้นผู้ให้บริการที่รู้จัก (เช่น OpenAI/Anthropic/เป็นต้น) ในโมเดลที่พร้อมใช้งาน\n5. โมเดลแรกที่พร้อมใช้งาน\n\n### นามแฝงบทบาทและการตั้งค่า\n\nบทบาทโมเดลที่รองรับ:\n\n- `default`, `smol`, `slow`, `plan`, `commit`\n\nนามแฝงบทบาทเช่น `pi/smol` ขยายผ่าน `settings.modelRoles` ค่าบทบาทแต่ละอย่างยังสามารถเพิ่มตัวเลือก thinking เช่น `:minimal`, `:low`, `:medium`, หรือ `:high`\n\nหากบทบาทชี้ไปยังบทบาทอื่น โมเดลเป้าหมายยังคงรับค่าตามปกติและส่วนต่อท้ายอย่างชัดเจนใดๆ บนบทบาทที่อ้างอิงจะชนะสำหรับการใช้งานเฉพาะบทบาทนั้น\n\nการตั้งค่าที่เกี่ยวข้อง:\n\n- `modelRoles` (record)\n- `enabledModels` (รายการรูปแบบที่มีขอบเขต)\n- `modelProviderOrder` (ลำดับความสำคัญ canonical-provider ทั่วโลก)\n- `providers.kimiApiFormat` (รูปแบบคำขอ `openai` หรือ `anthropic`)\n- `providers.openaiWebsockets` (ค่ากำหนด websocket `auto|off|on` สำหรับ OpenAI Codex transport)\n\n`modelRoles` อาจเก็บ:\n\n- `provider/modelId` เพื่อปักหมุดตัวแปรผู้ให้บริการที่เป็นรูปธรรม\n- canonical id เช่น `gpt-5.3-codex` เพื่ออนุญาตการรวมผู้ให้บริการ\n\nสำหรับ `enabledModels` และ CLI `--models`:\n\n- canonical id แบบแน่นอนขยายไปยังตัวแปรที่เป็นรูปธรรมทั้งหมดในกลุ่ม canonical นั้น\n- รายการ `provider/modelId` อย่างชัดเจนยังคงแน่นอน\n- glob และการจับคู่แบบฟัซซียังคงทำงานบนโมเดลที่เป็นรูปธรรม\n\n## `/model` และ `--list-models`\n\nทั้งสองพื้นผิวยังคงให้โมเดลที่มีคำนำหน้าผู้ให้บริการมองเห็นและเลือกได้\n\nปัจจุบันยังแสดงโมเดลตามแบบแผน/ที่รวมแล้ว:\n\n- `/model` รวมมุมมองตามแบบแผนพร้อมกับแท็บผู้ให้บริการ\n- `--list-models` พิมพ์ส่วนตามแบบแผนพร้อมกับแถวผู้ให้บริการที่เป็นรูปธรรม\n\nการเลือกรายการตามแบบแผนจะเก็บตัวเลือกตามแบบแผน การเลือกแถวผู้ให้บริการจะเก็บ `provider/modelId` อย่างชัดเจน\n\n## การโปรโมตบริบท (ห่วงโซ่สำรองระดับโมเดล)\n\nการโปรโมตบริบทเป็นกลไกการกู้คืนจากการล้นสำหรับตัวแปรบริบทขนาดเล็ก (เช่น `*-spark`) ที่โปรโมตไปยังพี่น้องที่มีบริบทขนาดใหญ่กว่าโดยอัตโนมัติเมื่อ API ปฏิเสธคำขอด้วยข้อผิดพลาดความยาวบริบท\n\n### ทริกเกอร์และลำดับ\n\nเมื่อเทิร์นล้มเหลวด้วยข้อผิดพลาด context overflow (เช่น `context_length_exceeded`), `AgentSession` จะพยายามโปรโมต **ก่อน** ที่จะสำรองไปยังการบีบอัด:\n\n1. หาก `contextPromotion.enabled` เป็น true ให้แก้ไขเป้าหมายการโปรโมต (ดูด้านล่าง)\n2. หากพบเป้าหมาย ให้เปลี่ยนไปใช้และลองคำขออีกครั้ง — ไม่จำเป็นต้องบีบอัด\n3. หากไม่มีเป้าหมาย ให้ผ่านไปยังการบีบอัดอัตโนมัติบนโมเดลปัจจุบัน\n\n### การเลือกเป้าหมาย\n\nการเลือกขับเคลื่อนด้วยโมเดล ไม่ใช่บทบาท:\n\n1. `currentModel.contextPromotionTarget` (หากกำหนดค่าไว้)\n2. โมเดลที่มีบริบทขนาดใหญ่กว่าที่เล็กที่สุดบนผู้ให้บริการ + API เดียวกัน\n\nผู้สมัครจะถูกละเว้นเว้นแต่ข้อมูลรับรองจะแก้ไขได้ (`ModelRegistry.getApiKey(...)`)\n\n### การส่งต่อ websocket OpenAI Codex\n\nหากเปลี่ยนจาก/ไปยัง `openai-codex-responses` คีย์สถานะผู้ให้บริการเซสชัน `openai-codex-responses` จะถูกปิดก่อนเปลี่ยนโมเดล ซึ่งจะทิ้งสถานะ websocket transport เพื่อให้เทิร์นถัดไปเริ่มต้นใหม่บนโมเดลที่ได้รับการโปรโมต\n\n### พฤติกรรมการคงอยู่\n\nการโปรโมตใช้การสลับชั่วคราว (`setModelTemporary`):\n\n- บันทึกเป็น `model_change` ชั่วคราวในประวัติเซสชัน\n- ไม่เขียนทับการแมปบทบาทที่บันทึกไว้\n\n### การกำหนดค่าห่วงโซ่สำรองอย่างชัดเจน\n\nกำหนดค่าสำรองโดยตรงในข้อมูลเมตาโมเดลผ่าน `contextPromotionTarget`\n\n`contextPromotionTarget` รับ:\n\n- `provider/model-id` (อย่างชัดเจน)\n- `model-id` (แก้ไขภายในผู้ให้บริการปัจจุบัน)\n\nตัวอย่าง (`models.yml`) สำหรับ Spark -> non-Spark บนผู้ให้บริการเดียวกัน:\n\n```yaml\nproviders:\n  openai-codex:\n    modelOverrides:\n      gpt-5.3-codex-spark:\n        contextPromotionTarget: openai-codex/gpt-5.3-codex\n```\n\nตัวสร้างโมเดลในตัวยังกำหนดสิ่งนี้โดยอัตโนมัติสำหรับโมเดล `*-spark` เมื่อมีโมเดลพื้นฐานบนผู้ให้บริการเดียวกัน\n\n## ฟิลด์ความเข้ากันได้และการกำหนดเส้นทาง\n\n`models.yml` รองรับ `compat` ชุดนี้:\n\n- `supportsStore`\n- `supportsDeveloperRole`\n- `supportsReasoningEffort`\n- `maxTokensField` (`max_completion_tokens` หรือ `max_tokens`)\n- `openRouterRouting.only` / `openRouterRouting.order`\n- `vercelGatewayRouting.only` / `vercelGatewayRouting.order`\n\nฟิลด์เหล่านี้ถูกใช้โดยตรรกะ transport ของ OpenAI-completions และรวมกับการตรวจจับอัตโนมัติตาม URL\n\n## ตัวอย่างเชิงปฏิบัติ\n\n### endpoint ที่เข้ากันได้กับ OpenAI ในเครื่อง (ไม่ต้องตรวจสอบสิทธิ์)\n\n```yaml\nproviders:\n  local-openai:\n    baseUrl: http://127.0.0.1:8000/v1\n    auth: none\n    api: openai-completions\n    models:\n      - id: Qwen/Qwen2.5-Coder-32B-Instruct\n        name: Qwen 2.5 Coder 32B (local)\n```\n\n### Proxy ที่โฮสต์ด้วยคีย์ที่อิงกับ env\n\n```yaml\nproviders:\n  anthropic-proxy:\n    baseUrl: https://proxy.example.com/anthropic\n    apiKey: ANTHROPIC_PROXY_API_KEY\n    api: anthropic-messages\n    authHeader: true\n    models:\n      - id: claude-sonnet-4-20250514\n        name: Claude Sonnet 4 (Proxy)\n        reasoning: true\n        input: [text, image]\n```\n\n### แทนที่เส้นทางผู้ให้บริการในตัว + ข้อมูลเมตาโมเดล\n\n```yaml\nproviders:\n  openrouter:\n    baseUrl: https://my-proxy.example.com/v1\n    headers:\n      X-Team: platform\n    modelOverrides:\n      anthropic/claude-sonnet-4:\n        name: Sonnet 4 (Corp)\n        compat:\n          openRouterRouting:\n            only: [anthropic]\n```\n\n## การกำหนดค่าอัตโนมัติของ LiteLLM proxy\n\nเมื่อตั้งค่าตัวแปรสภาพแวดล้อมทั้ง `LITELLM_BASE_URL` และ `LITELLM_API_KEY` xcsh จะจัดการการกำหนดค่า `models.yml` สำหรับ LiteLLM proxy โดยอัตโนมัติ\n\n### การสร้างอัตโนมัติครั้งแรก\n\nหาก `models.yml` ไม่มีอยู่และตรวจพบตัวแปร env ของ LiteLLM xcsh จะสร้างโดยอัตโนมัติ:\n\n```yaml\n# Auto-generated by xcsh for LiteLLM proxy\n# API key resolved from LITELLM_API_KEY env var at runtime\nconfigVersion: 1\nproviders:\n  anthropic:\n    baseUrl: \"https://your-litellm-proxy.example.com/anthropic\"\n    apiKey: LITELLM_API_KEY\n```\n\n`config.yml` เริ่มต้นยังถูกสร้างขึ้นพร้อมการตั้งค่าผู้ให้บริการรูปภาพที่เหมาะสม\n\n### การซ่อมแซมตัวเองเมื่อเริ่มต้น\n\nเมื่อเริ่มต้นทุกครั้ง `startupHealthCheck()` ในรีจิสทรีโมเดลจะทำการตรวจสอบต่อไปนี้:\n\n| เงื่อนไข | การดำเนินการ |\n|-----------|--------|\n| `models.yml` ไม่มีอยู่ | สร้างอัตโนมัติจากตัวแปร env |\n| `models.yml` เสียหายหรืออ่านไม่ได้ | สำรองเป็น `.bak`, สร้างใหม่ |\n| `baseUrl` ไม่ตรงกับ `LITELLM_BASE_URL` | สำรองเป็น `.bak`, สร้างใหม่ด้วย URL ใหม่ |\n| `configVersion` หายไปหรือล้าสมัย | สำรองเป็น `.bak`, สร้างใหม่ด้วยเวอร์ชันปัจจุบัน |\n| Config ปกติดี | ไม่มีการดำเนินการ |\n\nการซ่อมแซมทั้งหมดสร้างไฟล์สำรอง `.bak` ก่อนเขียนทับ การดำเนินการทั้งหมด idempotent\n\n### คำสั่ง CLI\n\n```bash\nxcsh setup litellm              # Generate or fix LiteLLM config\nxcsh setup litellm --check      # Validate without writing\nxcsh setup litellm --check --json  # Machine-readable validation output\n```\n\n### ตัวแปรสภาพแวดล้อมที่จำเป็น\n\n| ตัวแปร | วัตถุประสงค์ |\n|----------|---------|\n| `LITELLM_BASE_URL` | URL ของ LiteLLM proxy (เช่น `https://your-proxy.example.com`) ต้องเริ่มต้นด้วย `http://` หรือ `https://` |\n| `LITELLM_API_KEY` | API key สำหรับ proxy อ้างอิงด้วยชื่อในการกำหนดค่าที่สร้าง แก้ไขขณะรันไทม์ |\n\nหากตัวแปรใดตัวแปรหนึ่งไม่ได้ตั้งค่า การกำหนดค่าอัตโนมัติจะถูกข้ามโดยไม่มีเสียง\n\n### การกำหนดเวอร์ชัน Config\n\nการกำหนดค่าที่สร้างจะรวมฟิลด์ `configVersion` เมื่อรูปแบบที่สร้างเปลี่ยนแปลงในรุ่นอนาคต xcsh จะตรวจจับการกำหนดค่าที่ล้าสมัยและอัปเกรดโดยอัตโนมัติ (พร้อมการสำรอง)\n\n## ข้อควรระวังสำหรับผู้บริโภครุ่นเดิม\n\nการกำหนดค่าโมเดลส่วนใหญ่ปัจจุบันไหลผ่าน `models.yml` ผ่าน `ModelRegistry`\n\nเส้นทางเดิมที่น่าสังเกตยังคงอยู่: การแก้ไขการตรวจสอบสิทธิ์ Anthropic สำหรับ web-search ยังคงอ่าน `~/.xcsh/agent/models.json` โดยตรงใน `src/web/search/auth.ts`\n\nหากคุณพึ่งพาเส้นทางเฉพาะนั้น โปรดคำนึงถึงความเข้ากันได้ของ JSON จนกว่าโมดูลนั้นจะได้รับการย้าย\n\n## โหมดความล้มเหลว\n\nหาก `models.yml` ล้มเหลวการตรวจสอบ schema หรือความถูกต้อง:\n\n- หากตั้งค่า `LITELLM_BASE_URL` และ `LITELLM_API_KEY` การตรวจสอบความสมบูรณ์เมื่อเริ่มต้นจะพยายามซ่อมแซมอัตโนมัติ (สำรองไฟล์ที่เสียหาย, สร้างใหม่จากตัวแปร env) หากการซ่อมแซมสำเร็จ รีจิสทรีจะโหลดการกำหนดค่าที่แก้ไขใหม่\n- หากการซ่อมแซมอัตโนมัติไม่สามารถทำได้ (ตัวแปร env ไม่ได้ตั้งค่า, ความล้มเหลวในการเขียน) รีจิสทรีจะดำเนินการต่อด้วยโมเดลในตัว\n- ข้อผิดพลาดถูกเปิดเผยผ่าน `ModelRegistry.getError()` และแสดงใน UI/การแจ้งเตือน\n",
	"th/providers/provider-streaming-internals.md": "---\ntitle: โครงสร้างภายในของ Provider Streaming\ndescription: >-\n  การใช้งาน Provider Streaming พร้อมการแยกวิเคราะห์ SSE การนับโทเค็น\n  และการจัดการ backpressure\nsidebar:\n  order: 2\n  label: โครงสร้างภายในของ Streaming\ni18n:\n  sourceHash: a32ffa769c4d\n  translator: machine\n---\n\n# โครงสร้างภายในของ Provider Streaming\n\nเอกสารนี้อธิบายวิธีการที่การ streaming โทเค็น/เครื่องมือถูกทำให้เป็นมาตรฐานใน `@f5-sales-demo/pi-ai` จากนั้นเผยแพร่ผ่าน `@f5-sales-demo/pi-agent-core` และ session events ของ coding-agent\n\n## กระบวนการจากต้นทางถึงปลายทาง\n\n1. `streamSimple()` (`packages/ai/src/stream.ts`) แมปตัวเลือกทั่วไปและส่งไปยังฟังก์ชัน provider stream\n2. ฟังก์ชัน provider stream (`anthropic.ts`, `openai-responses.ts`, `google.ts`) แปล stream events เฉพาะของ provider ให้เป็นลำดับ `AssistantMessageEvent` แบบรวมศูนย์\n3. แต่ละ provider จะส่ง events เข้าสู่ `AssistantMessageEventStream` (`packages/ai/src/utils/event-stream.ts`) ซึ่งควบคุมอัตราการส่ง delta events และเปิดเผย:\n   - async iteration สำหรับการอัปเดตแบบเพิ่มทีละน้อย\n   - `result()` สำหรับ `AssistantMessage` ขั้นสุดท้าย\n4. `agentLoop` (`packages/agent/src/agent-loop.ts`) รับ events เหล่านั้น แก้ไขสถานะ assistant ที่กำลังทำงาน และส่ง `message_update` events พร้อม `assistantMessageEvent` ดิบ\n5. `AgentSession` (`packages/coding-agent/src/session/agent-session.ts`) สมัครรับ agent events บันทึกข้อความ ขับเคลื่อน extension hooks และใช้งาน session behaviors (retry, compaction, TTSR, การตรวจสอบการยกเลิก streaming-edit)\n\n## สัญญา stream แบบรวมศูนย์ใน `@f5-sales-demo/pi-ai`\n\nProvider ทั้งหมดส่ง events ในรูปแบบเดียวกัน (`AssistantMessageEvent` ใน `packages/ai/src/types.ts`):\n\n- `start`\n- triplets ของ lifecycle สำหรับ content block:\n  - text: `text_start` → `text_delta`* → `text_end`\n  - thinking: `thinking_start` → `thinking_delta`* → `thinking_end`\n  - tool call: `toolcall_start` → `toolcall_delta`* → `toolcall_end`\n- terminal event:\n  - `done` พร้อม `reason: \"stop\" | \"length\" | \"toolUse\"`\n  - หรือ `error` พร้อม `reason: \"aborted\" | \"error\"`\n\n`AssistantMessageEventStream` รับประกัน:\n\n- ผลลัพธ์สุดท้ายถูก resolve โดย terminal event (`done` หรือ `error`)\n- deltas จะถูกรวมและควบคุมอัตราการส่ง (~50ms)\n- deltas ที่ถูกบัฟเฟอร์จะถูก flush ก่อน non-delta events และก่อนการสิ้นสุด\n\n## พฤติกรรมการควบคุมอัตราและการประสาน delta\n\n`AssistantMessageEventStream` จัดการ `text_delta`, `thinking_delta` และ `toolcall_delta` เป็น events ที่รวมกันได้:\n\n- deltas ที่ถูกบัฟเฟอร์จะถูกรวมก็ต่อเมื่อ **type + contentIndex** ตรงกันเท่านั้น\n- การรวมจะเก็บ snapshot `partial` ล่าสุดไว้\n- non-delta events บังคับให้ flush ทันที\n\nกระบวนการนี้ทำให้ provider streams ความถี่สูงราบรื่นขึ้นสำหรับผู้บริโภค TUI/event แต่ไม่ใช่ backpressure ของ provider: providers ยังคงผลิตข้อมูลด้วยความเร็วเต็มที่ในขณะที่ local stream ทำการบัฟเฟอร์\n\n## รายละเอียดการทำให้ Provider เป็นมาตรฐาน\n\n## Anthropic (`anthropic-messages`)\n\nแหล่งที่มา: `packages/ai/src/providers/anthropic.ts`\n\nจุดที่ทำให้เป็นมาตรฐาน:\n\n- `message_start` เริ่มต้นการใช้งาน (input/output/cache tokens)\n- `content_block_start` แมปไปยัง text/thinking/toolcall starts\n- `content_block_delta` แมป:\n  - `text_delta` → `text_delta`\n  - `thinking_delta` → `thinking_delta`\n  - `input_json_delta` → `toolcall_delta`\n  - `signature_delta` อัปเดต `thinkingSignature` เท่านั้น (ไม่มี event)\n- `content_block_stop` ส่ง `*_end` ที่สอดคล้องกัน\n- `message_delta.stop_reason` แมปผ่าน `mapStopReason()`\n\nการ streaming ของ argument สำหรับ tool-call:\n\n- แต่ละ tool block มี `partialJson` ภายใน\n- JSON delta ทุกตัวจะต่อท้ายเข้าไปใน `partialJson`\n- `arguments` จะถูก parse ใหม่ทุก delta ผ่าน `parseStreamingJson()`\n- `toolcall_end` parse อีกครั้งหนึ่งครั้ง จากนั้นลบ `partialJson` ออก\n\n## OpenAI Responses (`openai-responses`)\n\nแหล่งที่มา: `packages/ai/src/providers/openai-responses.ts`\n\nจุดที่ทำให้เป็นมาตรฐาน:\n\n- `response.output_item.added` เริ่มต้น reasoning/text/function-call blocks\n- reasoning summary events (`response.reasoning_summary_text.delta`) กลายเป็น `thinking_delta`\n- output/refusal deltas กลายเป็น `text_delta`\n- `response.function_call_arguments.delta` กลายเป็น `toolcall_delta`\n- `response.output_item.done` ส่ง `thinking_end` / `text_end` / `toolcall_end`\n- `response.completed` แมป status ไปยัง stop reason และ usage\n\nการ streaming ของ argument สำหรับ tool-call:\n\n- ใช้รูปแบบการสะสม `partialJson` เดียวกับ Anthropic\n- providers ที่ส่งเฉพาะ `response.function_call_arguments.done` ยังคง populate args สุดท้ายได้\n- tool call IDs ถูกทำให้เป็นมาตรฐานเป็น `\"<call_id>|<item_id>\"`\n\n## Google Generative AI (`google-generative-ai`)\n\nแหล่งที่มา: `packages/ai/src/providers/google.ts`\n\nจุดที่ทำให้เป็นมาตรฐาน:\n\n- วนซ้ำ `candidate.content.parts`\n- ส่วน text ถูกแบ่งเป็น thinking และ text โดย `isThinkingPart(part)`\n- การเปลี่ยน block จะปิด block ก่อนหน้าก่อนเริ่ม block ใหม่\n- `part.functionCall` ถูกจัดการเป็น tool call ที่สมบูรณ์ (start/delta/end ถูกส่งทันที)\n- finish reason ถูกแมปโดย `mapStopReason()` จาก `google-shared.ts`\n\nการ streaming ของ argument สำหรับ tool-call:\n\n- argument ของ function call มาถึงในรูปแบบ structured object ไม่ใช่ JSON text แบบเพิ่มทีละน้อย\n- การ implement ส่ง `toolcall_delta` สังเคราะห์หนึ่งตัวที่มี `JSON.stringify(arguments)`\n- ไม่จำเป็นต้องใช้ partial JSON parser สำหรับ Google ในเส้นทางนี้\n\n## การสะสมและการกู้คืน partial JSON ของ tool-call\n\nพฤติกรรมที่ใช้ร่วมกันสำหรับ Anthropic/OpenAI Responses ใช้ `parseStreamingJson()` (`packages/ai/src/utils/json-parse.ts`):\n\n1. ลอง `JSON.parse`\n2. ใช้ `partial-json` parser เป็น fallback สำหรับ fragments ที่ไม่สมบูรณ์\n3. หากทั้งคู่ล้มเหลว ส่งคืน `{}`\n\nผลกระทบ:\n\n- argument deltas ที่ผิดรูปแบบหรือถูกตัดทอนจะไม่ทำให้การประมวลผล stream หยุดทำงานทันที\n- `arguments` ที่กำลังดำเนินการอาจเป็น `{}` ชั่วคราว\n- deltas ที่ถูกต้องในภายหลังสามารถกู้คืน arguments ที่มีโครงสร้างได้เนื่องจากการ parse ถูกลองใหม่ทุกครั้งที่มีการต่อท้าย\n- `toolcall_end` ขั้นสุดท้ายทำการ parse อีกครั้งหนึ่งครั้งก่อนการส่ง\n\n## Stop reasons กับ transport/runtime errors\n\nStop reasons ของ provider ถูกแมปไปยัง `stopReason` ที่เป็นมาตรฐาน:\n\n- Anthropic: `end_turn`→`stop`, `max_tokens`→`length`, `tool_use`→`toolUse`, กรณี safety/refusal→`error`\n- OpenAI Responses: `completed`→`stop`, `incomplete`→`length`, `failed/cancelled`→`error`\n- Google: `STOP`→`stop`, `MAX_TOKENS`→`length`, คลาส safety/prohibited/malformed-function-call→`error`\n\nความหมายของ error แบ่งออกเป็นสองขั้นตอน:\n\n1. **ความหมายของการสิ้นสุด model** (finish reason/status ที่ provider รายงาน)\n2. **ความล้มเหลวด้าน transport/runtime** (exceptions จาก network/client/parser/abort)\n\nหาก provider stream ส่ง exception หรือส่งสัญญาณความล้มเหลว provider wrapper แต่ละตัวจะดักจับและส่ง terminal `error` event พร้อม:\n\n- `stopReason = \"aborted\"` เมื่อ abort signal ถูกตั้งค่า\n- มิฉะนั้น `stopReason = \"error\"`\n- `errorMessage = formatErrorMessageWithRetryAfter(error)`\n\n## พฤติกรรมเมื่อ chunk/SSE parse ล้มเหลว\n\nสำหรับเส้นทาง provider เหล่านี้ การจัดการ chunk/SSE framing ดำเนินการโดย vendor SDK streams (Anthropic SDK, OpenAI SDK, Google SDK) โค้ดนี้ไม่ได้ implement custom SSE decoder ที่นี่\n\nพฤติกรรมที่สังเกตได้ในการ implement ปัจจุบัน:\n\n- การ parse chunk/SSE ที่ผิดรูปแบบในระดับ SDK จะแสดงผลเป็น exception หรือ stream `error` event\n- provider wrapper แปลงสิ่งนั้นให้เป็น terminal `error` event แบบรวมศูนย์\n- ไม่มีการ resume/retry เฉพาะของ provider ภายในฟังก์ชัน stream เอง\n- retries ระดับสูงกว่าถูกจัดการใน `AgentSession` auto-retry logic (message-level retry ไม่ใช่ stream-chunk replay)\n\n## ขอบเขตของการยกเลิก\n\nการยกเลิกเป็นแบบหลายชั้น:\n\n- คำขอ AI provider: `options.signal` ถูกส่งเข้าสู่การเรียก stream ของ provider client\n- Provider wrapper: หลังจาก stream loop สัญญาณที่ถูก abort บังคับให้ใช้เส้นทาง error (`\"Request was aborted\"`)\n- Agent loop: ตรวจสอบ `signal.aborted` ก่อนจัดการแต่ละ provider event และสามารถสังเคราะห์ assistant message ที่ถูก abort จาก partial ล่าสุด\n- การควบคุม Session/agent: `AgentSession.abort()` -> `agent.abort()` -> การยกเลิก shared abort controller\n\nการยกเลิกการ execute เครื่องมือแยกต่างหากจากการยกเลิก model stream:\n\n- tool runners ใช้ `AbortSignal.any([agentSignal, steeringAbortSignal])`\n- steering interrupts สามารถยกเลิกการ execute เครื่องมือที่เหลืออยู่ในขณะที่รักษา tool results ที่ผลิตแล้วไว้\n\n## ขอบเขตของ Backpressure\n\nไม่มีกลไก backpressure แบบ hard ระหว่าง provider SDK stream และผู้บริโภคปลายทาง:\n\n- `EventStream` ใช้ in-memory queues โดยไม่มีขนาดสูงสุด\n- การควบคุมอัตราลดอัตราการอัปเดต UI แต่ไม่ได้ชะลอการรับข้อมูลจาก provider\n- หากผู้บริโภคล่าช้าอย่างมีนัยสำคัญ events ที่เข้าคิวอาจเพิ่มขึ้นจนกว่าจะสิ้นสุด\n\nการออกแบบปัจจุบันให้ความสำคัญกับการตอบสนองและการจัดลำดับที่เรียบง่ายมากกว่าการควบคุมกระแสข้อมูลแบบ bounded-buffer\n\n## วิธีที่ stream events แสดงเป็น agent/session events\n\n`agentLoop.streamAssistantResponse()` เชื่อม `AssistantMessageEvent` กับ `AgentEvent`:\n\n- เมื่อได้รับ `start`: push placeholder assistant message และส่ง `message_start`\n- เมื่อได้รับ block events (`text_*`, `thinking_*`, `toolcall_*`): อัปเดต assistant message ล่าสุด ส่ง `message_update` พร้อม `assistantMessageEvent` ดิบ\n- เมื่อได้รับ terminal (`done`/`error`): resolve final message จาก `response.result()` ส่ง `message_end`\n\n`AgentSession` จากนั้นรับ events เหล่านั้นสำหรับพฤติกรรมระดับ session:\n\n- TTSR จับตาดู `message_update.assistantMessageEvent` สำหรับ `text_delta` และ `toolcall_delta`\n- streaming edit guard ตรวจสอบ `toolcall_delta`/`toolcall_end` บนการเรียก `edit` และสามารถยกเลิกได้ก่อนกำหนด\n- การ persistence เขียนข้อความที่สรุปแล้วที่ `message_end`\n- auto-retry ตรวจสอบ assistant `stopReason === \"error\"` บวกกับ `errorMessage` heuristics\n\n## ความรับผิดชอบแบบรวมศูนย์กับเฉพาะ Provider\n\nแบบรวมศูนย์ (สัญญาร่วม):\n\n- รูปแบบ event (`AssistantMessageEvent`)\n- การดึงผลลัพธ์สุดท้าย (`done`/`error`)\n- กฎการควบคุมอัตราและการรวม delta\n- โมเดลการเผยแพร่ agent/session event\n\nเฉพาะ Provider (ไม่ได้ถูก abstract อย่างสมบูรณ์):\n\n- taxonomies ของ upstream event และ mapping logic\n- ตาราง translation ของ stop-reason\n- conventions ของ tool-call ID\n- ความหมายและ signatures ของ reasoning/thinking block\n- ความหมายของ usage token และเวลาที่พร้อมใช้งาน\n- ข้อจำกัดการแปลง message ต่อ API\n\n## ไฟล์ที่ implement\n\n- [`../../ai/src/stream.ts`](../../packages/ai/src/stream.ts) — การ dispatch ของ provider การแมป option และการเชื่อมต่อ API key/session\n- [`../../ai/src/utils/event-stream.ts`](../../packages/ai/src/utils/event-stream.ts) — คิว stream ทั่วไปและการควบคุมอัตราของ assistant delta\n- [`../../ai/src/utils/json-parse.ts`](../../packages/ai/src/utils/json-parse.ts) — การ parse partial JSON สำหรับ tool arguments ที่ถูก stream\n- [`../../ai/src/providers/anthropic.ts`](../../packages/ai/src/providers/anthropic.ts) — การแปล Anthropic event และการสะสม tool JSON delta\n- [`../../ai/src/providers/openai-responses.ts`](../../packages/ai/src/providers/openai-responses.ts) — การแปล OpenAI Responses event และการแมป status\n- [`../../ai/src/providers/google.ts`](../../packages/ai/src/providers/google.ts) — การแปล Gemini stream chunk-to-block\n- [`../../ai/src/providers/google-shared.ts`](../../packages/ai/src/providers/google-shared.ts) — การแมป Gemini finish-reason และกฎ conversion ที่ใช้ร่วมกัน\n- [`../../agent/src/agent-loop.ts`](../../packages/agent/src/agent-loop.ts) — การรับ provider stream และการเชื่อม `message_update`\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — การจัดการระดับ session สำหรับ streaming updates การยกเลิก retry และ persistence\n",
	"th/providers/python-repl.md": "---\ntitle: เครื่องมือ Python และ IPython Runtime\ndescription: >-\n  Python REPL tool runtime พร้อมการจัดการ IPython kernel, การประมวลผล\n  และการจับภาพผลลัพธ์\nsidebar:\n  order: 3\n  label: Python และ IPython\ni18n:\n  sourceHash: 70f0a034ecef\n  translator: machine\n---\n\n# เครื่องมือ Python และ IPython Runtime\n\nเอกสารนี้อธิบาย Python execution stack ปัจจุบันใน `packages/coding-agent`\nครอบคลุมพฤติกรรมของ tool, วงจรชีวิต kernel/gateway, การจัดการ environment, ความหมายของการประมวลผล, การแสดงผลลัพธ์ และรูปแบบความล้มเหลวในการปฏิบัติงาน\n\n## ขอบเขตและไฟล์สำคัญ\n\n- พื้นผิวของ tool: `src/tools/python.ts`\n- การจัดการ kernel ต่อ session/การเรียก: `src/ipy/executor.ts`\n- โปรโตคอล kernel + การรวม gateway: `src/ipy/kernel.ts`\n- ตัวประสานงาน local gateway ที่ใช้ร่วมกัน: `src/ipy/gateway-coordinator.ts`\n- Renderer สำหรับโหมดโต้ตอบเมื่อผู้ใช้เรียกใช้ Python: `src/modes/components/python-execution.ts`\n- การกรอง runtime/env และการระบุตำแหน่ง Python: `src/ipy/runtime.ts`\n\n## เครื่องมือ Python คืออะไร\n\nเครื่องมือ `python` ประมวลผล Python cell หนึ่งหรือหลาย cell ผ่าน kernel ที่รองรับโดย Jupyter Kernel Gateway (ไม่ใช่การ spawn `python -c` โดยตรงต่อ cell)\n\nพารามิเตอร์ของ tool:\n\n```ts\n{\n  cells: Array<{ code: string; title?: string }>;\n  timeout?: number; // วินาที, จำกัดในช่วง 1..600, ค่าเริ่มต้น 30\n  cwd?: string;\n  reset?: boolean; // รีเซ็ต kernel ก่อน cell แรกเท่านั้น\n}\n```\n\ntool มีค่า `concurrency = \"exclusive\"` ต่อ session ดังนั้นการเรียกจึงไม่ทับซ้อนกัน\n\n## วงจรชีวิตของ Gateway\n\n### โหมด\n\nมี gateway path สองแบบ:\n\n1. **External gateway** (ตั้งค่า `PI_PYTHON_GATEWAY_URL`)\n   - ใช้ URL ที่กำหนดโดยตรง\n   - การยืนยันตัวตนเพิ่มเติมด้วย `PI_PYTHON_GATEWAY_TOKEN`\n   - ไม่มีการ spawn หรือจัดการ local gateway process\n\n2. **Local shared gateway** (เส้นทางเริ่มต้น)\n   - ใช้ process ที่ใช้ร่วมกันหนึ่งตัวซึ่งประสานงานภายใต้ `~/.xcsh/agent/python-gateway`\n   - ไฟล์ metadata: `gateway.json`\n   - ไฟล์ lock: `gateway.lock`\n   - คำสั่ง spawn:\n     - `python -m kernel_gateway`\n     - ผูกกับ `127.0.0.1:<allocated-port>`\n     - การตรวจสอบสุขภาพเมื่อเริ่มต้น: `GET /api/kernelspecs`\n\n### การประสานงาน local shared gateway\n\n`acquireSharedGateway()`:\n\n- ใช้ file lock (`gateway.lock`) พร้อม heartbeat\n- นำ `gateway.json` กลับมาใช้ใหม่หาก PID ยังทำงานอยู่และผ่านการตรวจสอบสุขภาพ\n- ล้างข้อมูล/PID ที่หมดอายุเมื่อจำเป็น\n- เริ่มต้น gateway ใหม่เมื่อไม่มี gateway ที่ใช้งานได้\n\n`releaseSharedGateway()` ปัจจุบันเป็น no-op (การปิด kernel ไม่ได้ปิด shared gateway)\n\n`shutdownSharedGateway()` ยุติ shared process โดยชัดเจนและล้าง gateway metadata\n\n### ข้อจำกัดสำคัญ\n\n`python.sharedGateway=false` จะถูกปฏิเสธเมื่อเริ่มต้น kernel:\n\n- ข้อผิดพลาด: `Shared Python gateway required; local gateways are disabled`\n- ไม่มีโหมด local gateway แบบไม่ใช้ร่วมกันต่อ process\n\n## วงจรชีวิตของ Kernel\n\nการประมวลผลแต่ละครั้งใช้ kernel ที่สร้างผ่าน `POST /api/kernels` บน gateway ที่เลือก\n\nลำดับการเริ่มต้น kernel:\n\n1. การตรวจสอบความพร้อม (`checkPythonKernelAvailability`)\n2. สร้าง kernel (`/api/kernels`)\n3. เปิด websocket (`/api/kernels/:id/channels`)\n4. เริ่มต้น kernel env (`cwd`, env vars, `sys.path`)\n5. ประมวลผล `PYTHON_PRELUDE`\n6. โหลด extension module จาก:\n   - ผู้ใช้: `~/.xcsh/agent/modules/*.py`\n   - โปรเจกต์: `<cwd>/.xcsh/modules/*.py` (แทนที่ module ของผู้ใช้ที่มีชื่อเดียวกัน)\n\nการปิด kernel:\n\n- ลบ remote kernel ผ่าน `DELETE /api/kernels/:id`\n- ปิด websocket\n- เรียก shared gateway release hook (ปัจจุบันเป็น no-op)\n\n## ความหมายของการคงสถานะ Session\n\n`python.kernelMode` ควบคุมการนำ kernel กลับมาใช้ใหม่:\n\n- `session` (ค่าเริ่มต้น)\n  - นำ kernel session กลับมาใช้ใหม่โดยใช้ session identity + cwd เป็นคีย์\n  - การประมวลผลถูก serialize ต่อ session ผ่าน queue\n  - Session ที่ไม่ได้ใช้งานจะถูกลบออกหลังจาก 5 นาที\n  - จำกัดไว้ที่ 4 session; session เก่าที่สุดจะถูกลบออกเมื่อเกิน\n  - การตรวจสอบ heartbeat ตรวจจับ kernel ที่ตาย\n  - อนุญาตให้รีสตาร์ทอัตโนมัติได้หนึ่งครั้ง; การ crash ซ้ำ => ความล้มเหลวถาวร\n\n- `per-call`\n  - สร้าง kernel ใหม่สำหรับแต่ละคำขอประมวลผล\n  - ปิด kernel หลังจากคำขอ\n  - ไม่มีการคงสถานะข้ามการเรียก\n\n### พฤติกรรมหลาย cell ในการเรียก tool ครั้งเดียว\n\nCell ทำงานตามลำดับในอินสแตนซ์ kernel เดียวกันสำหรับการเรียก tool ครั้งนั้น\n\nหาก cell กลางล้มเหลว:\n\n- สถานะของ cell ก่อนหน้ายังคงอยู่ในหน่วยความจำ\n- tool ส่งคืนข้อผิดพลาดที่ระบุว่า cell ใดล้มเหลว\n- Cell ถัดไปจะไม่ถูกประมวลผล\n\n`reset=true` ใช้ได้กับการประมวลผล cell แรกในการเรียกครั้งนั้นเท่านั้น\n\n## การกรอง Environment และการระบุ Runtime\n\nEnvironment จะถูกกรองก่อนเปิดใช้งาน gateway/kernel runtime:\n\n- Allowlist รวมถึง var หลัก เช่น `PATH`, `HOME`, locale vars, `VIRTUAL_ENV`, `PYTHONPATH` เป็นต้น\n- Allow-prefix: `LC_`, `XDG_`, `PI_`\n- Denylist ลบ API key ทั่วไป (OpenAI/Anthropic/Gemini/ฯลฯ)\n\nลำดับการเลือก runtime:\n\n1. venv ที่ใช้งานอยู่/ค้นพบ (`VIRTUAL_ENV`, จากนั้น `<cwd>/.venv`, `<cwd>/venv`)\n2. Managed venv ที่ `~/.xcsh/python-env`\n3. `python` หรือ `python3` บน PATH\n\nเมื่อเลือก venv แล้ว path ของ bin/Scripts จะถูกนำหน้าไปยัง `PATH`\n\nการเริ่มต้น kernel env ภายใน Python ยังรวมถึง:\n\n- `os.chdir(cwd)`\n- ใส่ env map ที่ให้มาลงใน `os.environ`\n- ตรวจสอบให้แน่ใจว่า cwd อยู่ใน `sys.path`\n\n## ความพร้อมใช้งานของ Tool และการเลือกโหมด\n\n`python.toolMode` (ค่าเริ่มต้น `both`) + การแทนที่ `PI_PY` เพิ่มเติม ควบคุมการเปิดเผย:\n\n- `ipy-only`\n- `bash-only`\n- `both`\n\nค่าที่ยอมรับสำหรับ `PI_PY`:\n\n- `0` / `bash` -> `bash-only`\n- `1` / `py` -> `ipy-only`\n- `mix` / `both` -> `both`\n\nหาก Python preflight ล้มเหลว การสร้าง tool จะลดระดับลงเป็น bash-only สำหรับ session นั้น\n\n## ขั้นตอนการประมวลผลและการยกเลิก/หมดเวลา\n\n### หมดเวลาระดับ Tool\n\ntimeout ของ tool `python` เป็นวินาที ค่าเริ่มต้น 30 จำกัดในช่วง `1..600`\n\ntool รวม:\n\n- abort signal ของผู้เรียก\n- abort signal ของ timeout\n\nด้วย `AbortSignal.any(...)`\n\n### การยกเลิกการประมวลผล Kernel\n\nเมื่อยกเลิก/หมดเวลา:\n\n- การประมวลผลถูกทำเครื่องหมายว่ายกเลิก\n- ความพยายาม interrupt kernel ผ่าน REST (`POST /interrupt`) และ control-channel `interrupt_request`\n- ผลลัพธ์รวมถึง `cancelled=true`\n- เส้นทาง timeout ระบุผลลัพธ์ว่า `Command timed out after <n> seconds`\n\n### พฤติกรรม stdin\n\nstdin แบบโต้ตอบไม่ได้รับการสนับสนุน\n\nหาก kernel ส่ง `input_request`:\n\n- tool บันทึก `stdinRequested=true`\n- ส่งข้อความอธิบาย\n- ส่ง `input_reply` ว่างเปล่า\n- การประมวลผลถือว่าเป็นความล้มเหลวที่ชั้น executor\n\n## การจับภาพและการแสดงผลลัพธ์\n\n### คลาสผลลัพธ์ที่จับภาพ\n\nจาก kernel message:\n\n- `stream` -> ส่วนของข้อความธรรมดา\n- `display_data`/`execute_result` -> การจัดการการแสดงผลแบบ rich\n- `error` -> ข้อความ traceback\n- MIME แบบกำหนดเอง `application/x-xcsh-status` -> เหตุการณ์สถานะแบบมีโครงสร้าง\n\nลำดับความสำคัญของ display MIME:\n\n1. `text/markdown`\n2. `text/plain`\n3. `text/html` (แปลงเป็น markdown พื้นฐาน)\n\nนอกจากนี้ยังจับภาพเป็นผลลัพธ์แบบมีโครงสร้าง:\n\n- `application/json` -> ข้อมูล JSON tree\n- `image/png` -> payload รูปภาพ\n- `application/x-xcsh-status` -> เหตุการณ์สถานะ\n\n### การจัดเก็บและการตัดทอน\n\nผลลัพธ์จะถูก stream ผ่าน `OutputSink` และอาจถูกบันทึกลงใน artifact storage\n\nผลลัพธ์ของ tool อาจรวมถึง metadata การตัดทอนและ `artifact://<id>` สำหรับการกู้คืนผลลัพธ์เต็ม\n\n### พฤติกรรมของ Renderer\n\n- Tool renderer (`python.ts`):\n  - แสดงบล็อก code cell พร้อมสถานะต่อ cell\n  - ตัวอย่างแบบยุบค่าเริ่มต้น 10 บรรทัด\n  - รองรับโหมดขยายสำหรับผลลัพธ์เต็มและรายละเอียดสถานะที่สมบูรณ์ยิ่งขึ้น\n- Interactive renderer (`python-execution.ts`):\n  - ใช้สำหรับการประมวลผล Python ที่ผู้ใช้เรียกใช้ใน TUI\n  - ตัวอย่างแบบยุบค่าเริ่มต้น 20 บรรทัด\n  - จำกัดบรรทัดยาวมากไว้ที่ 4000 ตัวอักษรเพื่อความปลอดภัยในการแสดงผล\n  - แสดงการแจ้งเตือนการยกเลิก/ข้อผิดพลาด/การตัดทอน\n\n## การสนับสนุน External Gateway\n\nตั้งค่า:\n\n```bash\nexport PI_PYTHON_GATEWAY_URL=\"http://127.0.0.1:8888\"\n# เพิ่มเติม:\nexport PI_PYTHON_GATEWAY_TOKEN=\"...\"\n```\n\nความแตกต่างของพฤติกรรมจาก local shared gateway:\n\n- ไม่มีไฟล์ lock/info ของ local gateway\n- ไม่มีการ spawn/ยุติ local process\n- การตรวจสอบสุขภาพและ kernel CRUD ทำงานกับ external endpoint\n- ความล้มเหลวในการยืนยันตัวตนจะแสดงพร้อมคำแนะนำ token ที่ชัดเจน\n\n## การแก้ไขปัญหาเชิงปฏิบัติ (รูปแบบความล้มเหลวปัจจุบัน)\n\n- **Python tool ไม่พร้อมใช้งาน**\n  - ตรวจสอบ `python.toolMode` / `PI_PY`\n  - หาก preflight ล้มเหลว runtime จะ fallback เป็น bash-only\n\n- **ข้อผิดพลาดความพร้อมใช้งานของ Kernel**\n  - โหมด local ต้องการทั้ง `kernel_gateway` และ `ipykernel` ที่ import ได้ใน Python runtime ที่ระบุ\n  - ติดตั้งด้วย:\n\n    ```bash\n    python -m pip install jupyter_kernel_gateway ipykernel\n    ```\n\n- **`python.sharedGateway=false` ทำให้เกิดความล้มเหลวในการเริ่มต้น**\n  - นี่เป็นพฤติกรรมที่คาดหวังกับการใช้งานปัจจุบัน\n\n- **ความล้มเหลวในการยืนยันตัวตน/การเข้าถึง External gateway**\n  - 401/403 -> ตั้งค่า `PI_PYTHON_GATEWAY_TOKEN`\n  - timeout/ไม่สามารถเข้าถึงได้ -> ตรวจสอบ URL/เครือข่ายและสุขภาพของ gateway\n\n- **การประมวลผลค้างแล้วหมดเวลา**\n  - เพิ่ม `timeout` ของ tool (สูงสุด 600s) หากปริมาณงานเป็นงานที่ถูกต้อง\n  - สำหรับโค้ดที่ค้าง การยกเลิกจะ trigger การ interrupt kernel แต่โค้ดของผู้ใช้อาจยังต้องการการปรับปรุง\n\n- **stdin/input prompt ใน Python code**\n  - `input()` ไม่รองรับการโต้ตอบในเส้นทาง runtime นี้; ส่งข้อมูลผ่านโปรแกรม\n\n- **การหมดทรัพยากร (`EMFILE` / ไฟล์เปิดมากเกินไป)**\n  - Session manager trigger การกู้คืน shared gateway (การปิด session + รีสตาร์ท shared gateway)\n\n- **ข้อผิดพลาด working directory**\n  - Tool ตรวจสอบว่า `cwd` มีอยู่และเป็น directory ก่อนการประมวลผล\n\n## Environment Variable ที่เกี่ยวข้อง\n\n- `PI_PY` — การแทนที่การเปิดเผย tool (`bash-only`/`ipy-only`/`both` ตามการแมปข้างต้น)\n- `PI_PYTHON_GATEWAY_URL` — ใช้ external gateway\n- `PI_PYTHON_GATEWAY_TOKEN` — token ยืนยันตัวตน external gateway เพิ่มเติม\n- `PI_PYTHON_SKIP_CHECK=1` — ข้ามการตรวจสอบ Python preflight/warm\n- `PI_PYTHON_IPC_TRACE=1` — บันทึก kernel IPC send/receive trace\n- `PI_DEBUG_STARTUP=1` — ส่ง debug marker ระยะเริ่มต้น\n",
	"th/runtime-tools/bash-tool-runtime.md": "---\ntitle: รันไทม์ Bash Tool\ndescription: >-\n  รันไทม์ Bash tool พร้อมการจัดการกระบวนการ shell, การจำกัดสภาพแวดล้อม, timeout\n  และการสตรีมผลลัพธ์\nsidebar:\n  order: 1\n  label: Bash tool\ni18n:\n  sourceHash: 18b12aa5dbd5\n  translator: machine\n---\n\n# รันไทม์ Bash tool\n\nเอกสารนี้อธิบายเส้นทางรันไทม์ของ **`bash` tool** ที่ใช้โดยการเรียก agent tool ตั้งแต่การ normalize คำสั่งไปจนถึงการประมวลผล การตัดทอน/artifacts และการแสดงผล\n\nนอกจากนี้ยังระบุจุดที่พฤติกรรมแตกต่างกันในโหมด TUI แบบโต้ตอบ, print mode, RPC mode และการรัน shell ด้วยเครื่องหมาย bang (`!`) ที่ผู้ใช้เริ่มต้นเอง\n\n## ขอบเขตและพื้นผิวรันไทม์\n\nมีพื้นผิวการประมวลผล bash สองแบบที่แตกต่างกันใน coding-agent:\n\n1. **พื้นผิว Tool-call** (`toolName: \"bash\"`): ใช้เมื่อโมเดลเรียก bash tool\n   - จุดเข้า: `BashTool.execute()`\n2. **พื้นผิวคำสั่ง User bang** (`!cmd` จากอินพุตแบบโต้ตอบหรือคำสั่ง RPC `bash`): เส้นทาง helper ระดับ session\n   - จุดเข้า: `AgentSession.executeBash()`\n\nทั้งสองเส้นทางในที่สุดใช้ `executeBash()` ใน `src/exec/bash-executor.ts` สำหรับการประมวลผลแบบ non-PTY แต่เฉพาะเส้นทาง tool-call เท่านั้นที่รันตรรกะ normalization/interception และ tool renderer\n\n## ไปป์ไลน์ tool-call แบบ end-to-end\n\n## 1) การ normalize อินพุตและการรวมพารามิเตอร์\n\n`BashTool.execute()` ทำการ normalize คำสั่งดิบก่อนผ่าน `normalizeBashCommand()`:\n\n- ดึง `| head -n N`, `| head -N`, `| tail -n N`, `| tail -N` ที่ท้ายคำสั่งออกเป็น limit แบบมีโครงสร้าง\n- ตัด whitespace ส่วนหน้าและส่วนท้าย\n- คง whitespace ภายในไว้ครบถ้วน\n\nจากนั้นรวม limit ที่ดึงออกมากับ tool args ที่ระบุไว้ชัดเจน:\n\n- args `head`/`tail` ที่ระบุชัดเจนจะ override ค่าที่ดึงออกมา\n- ค่าที่ดึงออกมาใช้เป็น fallback เท่านั้น\n\n### ข้อควรระวัง\n\nคอมเมนต์ใน `bash-normalize.ts` กล่าวถึงการลบ `2>&1` แต่การ implement ปัจจุบันไม่ได้ลบออก พฤติกรรมรันไทม์ยังคงถูกต้อง (stdout/stderr ถูกรวมไว้แล้ว) แต่พฤติกรรม normalization แคบกว่าที่คอมเมนต์บอกไว้\n\n## 2) การสกัดกั้นเสริม (เส้นทางคำสั่งที่ถูกบล็อก)\n\nหาก `bashInterceptor.enabled` เป็น true, `BashTool` จะโหลดกฎจาก settings และรัน `checkBashInterception()` กับคำสั่งที่ normalize แล้ว\n\nพฤติกรรมการสกัดกั้น:\n\n- คำสั่งถูกบล็อก **เฉพาะ** เมื่อ:\n  - กฎ regex ตรงกัน และ\n  - tool ที่แนะนำมีอยู่ใน `ctx.toolNames`\n- กฎ regex ที่ไม่ถูกต้องจะถูกข้ามอย่างเงียบ ๆ\n- เมื่อถูกบล็อก `BashTool` จะ throw `ToolError` พร้อมข้อความ:\n  - `Blocked: ...`\n  - รวมคำสั่งต้นฉบับ\n\nรูปแบบกฎเริ่มต้น (กำหนดในโค้ด) กำหนดเป้าหมายการใช้งานผิดวัตถุประสงค์ทั่วไป:\n\n- ตัวอ่านไฟล์ (`cat`, `head`, `tail`, ...)\n- เครื่องมือค้นหา (`grep`, `rg`, ...)\n- ตัวค้นหาไฟล์ (`find`, `fd`, ...)\n- โปรแกรมแก้ไขแบบ in-place (`sed -i`, `perl -i`, `awk -i inplace`)\n- การเขียนด้วย shell redirection (`echo ... > file`, heredoc redirection)\n\n### ข้อควรระวัง\n\n`InterceptionResult` รวม `suggestedTool` ไว้ แต่ปัจจุบัน `BashTool` แสดงเฉพาะข้อความ (ไม่มี field suggested-tool แบบมีโครงสร้างใน `details`)\n\n## 3) การตรวจสอบ CWD และการ clamp timeout\n\n`cwd` ถูก resolve โดยอ้างอิงจาก session cwd (`resolveToCwd`) จากนั้นตรวจสอบผ่าน `stat`:\n\n- path ไม่มีอยู่ -> `ToolError(\"Working directory does not exist: ...\")`\n- ไม่ใช่ไดเรกทอรี -> `ToolError(\"Working directory is not a directory: ...\")`\n\nTimeout ถูก clamp ไว้ที่ `[1, 3600]` วินาที และแปลงเป็นมิลลิวินาที\n\n## 4) การจัดสรร artifact\n\nก่อนการประมวลผล tool จะจัดสรร artifact path/id (best-effort) สำหรับจัดเก็บผลลัพธ์ที่ถูกตัดทอน\n\n- การจัดสรร artifact ที่ล้มเหลวไม่ถือเป็น fatal (การประมวลผลดำเนินต่อไปโดยไม่มีไฟล์ spill ของ artifact)\n- artifact id/path ถูกส่งเข้าไปในเส้นทางการประมวลผลสำหรับการบันทึกผลลัพธ์เต็มรูปแบบเมื่อมีการตัดทอน\n\n## 5) การเลือกการประมวลผลแบบ PTY กับ non-PTY\n\n`BashTool` เลือกการประมวลผลแบบ PTY เฉพาะเมื่อเป็นจริงทั้งหมด:\n\n- `bash.virtualTerminal === \"on\"`\n- `PI_NO_PTY !== \"1\"`\n- context ของ tool มี UI (`ctx.hasUI === true` และ `ctx.ui` ถูกตั้งค่า)\n\nมิฉะนั้นจะใช้ `executeBash()` แบบ non-interactive\n\nซึ่งหมายความว่า print mode และ RPC/tool contexts แบบ non-UI จะใช้ non-PTY เสมอ\n\n## เอนจินการประมวลผลแบบ non-interactive (`executeBash`)\n\n## โมเดลการนำ shell session กลับมาใช้ใหม่\n\n`executeBash()` cache instance `Shell` แบบ native ไว้ใน map ระดับ process-global โดย key จาก:\n\n- shell path\n- command prefix ที่กำหนดค่าไว้\n- snapshot path\n- shell env ที่ serialize แล้ว\n- agent session key เสริม\n\nสำหรับการประมวลผลระดับ session, `AgentSession.executeBash()` ส่ง `sessionKey: this.sessionId` เพื่อแยก reuse ต่อ session\n\nเส้นทาง tool-call **ไม่** ส่ง `sessionKey` ดังนั้นขอบเขต reuse จะอิงจาก shell config/snapshot/env\n\n## พฤติกรรม shell config และ snapshot\n\nในแต่ละการเรียก executor จะโหลด settings shell config (`shell`, `env`, optional `prefix`)\n\nหาก shell ที่เลือกรวม `bash` ไว้ จะพยายาม `getOrCreateSnapshot()`:\n\n- snapshot จะ capture aliases/functions/options จาก user rc\n- การสร้าง snapshot เป็นแบบ best-effort\n- หากล้มเหลวจะ fallback เป็นไม่มี snapshot\n\nหาก `prefix` ถูกกำหนดค่าไว้ คำสั่งจะกลายเป็น:\n\n```text\n<prefix> <command>\n```\n\n## การสตรีมและการยกเลิก\n\n`Shell.run()` สตรีม chunk ไปยัง callback Executor ส่ง chunk แต่ละอันเข้า `OutputSink` และ callback `onChunk` เสริม\n\nการยกเลิก:\n\n- signal ที่ถูก abort จะ trigger `shellSession.abort(...)`\n- timeout จากผลลัพธ์ native จะถูก map ไปเป็น `cancelled: true` + ข้อความ annotation\n- การยกเลิกที่ชัดเจนจะ return `cancelled: true` + annotation เช่นกัน\n\nไม่มีการ throw exception ภายใน executor สำหรับ timeout/cancel แต่จะ return `BashResult` แบบมีโครงสร้างและให้ caller map ความหมายของข้อผิดพลาด\n\n## เส้นทาง PTY แบบโต้ตอบ (`runInteractiveBashPty`)\n\nเมื่อเปิดใช้งาน PTY tool จะรัน `runInteractiveBashPty()` ซึ่งเปิด overlay console component และขับเคลื่อน `PtySession` แบบ native\n\nจุดเด่นของพฤติกรรม:\n\n- virtual terminal xterm-headless แสดงผล viewport ใน overlay\n- อินพุตจากแป้นพิมพ์ถูก normalize (รวมถึง Kitty sequences และการจัดการ application cursor mode)\n- `esc` ขณะรันอยู่จะ kill PTY session\n- การปรับขนาด terminal จะ propagate ไปยัง PTY (`session.resize(cols, rows)`)\n\nค่าเริ่มต้น hardening ของสภาพแวดล้อมถูก inject สำหรับการรันแบบ unattended:\n\n- ปิดใช้งาน pager (`PAGER=cat`, `GIT_PAGER=cat`, ฯลฯ)\n- ปิดใช้งาน editor prompts (`GIT_EDITOR=true`, `EDITOR=true`, ...)\n- ลด terminal/auth prompts (`GIT_TERMINAL_PROMPT=0`, `SSH_ASKPASS=/usr/bin/false`, `CI=1`)\n- flag automation ของ package-manager/tool สำหรับพฤติกรรม non-interactive\n\nผลลัพธ์ PTY ถูก normalize (`CRLF`/`CR` เป็น `LF`, `sanitizeText`) และเขียนลงใน `OutputSink` รวมถึงรองรับ artifact spill\n\nเมื่อเกิดข้อผิดพลาดในการเริ่มต้น/รันไทม์ของ PTY sink จะได้รับบรรทัด `PTY error: ...` และคำสั่งจะสิ้นสุดด้วย exit code ที่ไม่ได้กำหนด\n\n## การจัดการผลลัพธ์: การสตรีม การตัดทอน artifact spill\n\nทั้งเส้นทาง PTY และ non-PTY ใช้ `OutputSink`\n\n## ความหมายของ OutputSink\n\n- เก็บ tail buffer แบบ UTF-8-safe ไว้ในหน่วยความจำ (`DEFAULT_MAX_BYTES` ปัจจุบัน 50KB)\n- ติดตาม total bytes/lines ที่เห็น\n- หาก artifact path มีอยู่และผลลัพธ์ overflow (หรือไฟล์ active อยู่แล้ว) จะเขียน stream เต็มรูปแบบไปยังไฟล์ artifact\n- เมื่อ memory threshold overflow จะ trim in-memory buffer เป็น tail (safe ต่อ UTF-8 boundary)\n- ทำเครื่องหมาย `truncated` เมื่อเกิด overflow/file spill\n\n`dump()` return:\n\n- `output` (มี prefix annotation ที่เป็นไปได้)\n- `truncated`\n- `totalLines/totalBytes`\n- `outputLines/outputBytes`\n- `artifactId` หากไฟล์ artifact active อยู่\n\n### ข้อควรระวังสำหรับผลลัพธ์ที่ยาว\n\nการตัดทอนรันไทม์อ้างอิงจาก byte-threshold ใน `OutputSink` (ค่าเริ่มต้น 50KB) ไม่ได้บังคับใช้ hard cap 2000 บรรทัดในเส้นทางโค้ดนี้\n\n## การอัปเดต tool แบบ live\n\nสำหรับการประมวลผลแบบ non-PTY, `BashTool` ใช้ `TailBuffer` แยกต่างหากสำหรับการอัปเดตบางส่วนและส่ง snapshot `onUpdate` ขณะที่คำสั่งกำลังรัน\n\nสำหรับการประมวลผลแบบ PTY การแสดงผลแบบ live จะถูกจัดการโดย UI overlay แบบ custom ไม่ใช่ผ่าน text chunk ของ `onUpdate`\n\n## การกำหนดรูปร่างผลลัพธ์ metadata และการ map ข้อผิดพลาด\n\nหลังการประมวลผล:\n\n1. การจัดการ `cancelled`:\n   - หาก abort signal ถูก abort -> throw `ToolAbortError` (ความหมาย abort)\n   - มิฉะนั้น -> throw `ToolError` (ถือเป็น tool failure)\n2. PTY `timedOut` -> throw `ToolError`\n3. ใช้ head/tail filter กับข้อความผลลัพธ์สุดท้าย (`applyHeadTail`, head ก่อน tail)\n4. ผลลัพธ์ว่างเปล่ากลายเป็น `(no output)`\n5. แนบ truncation metadata ผ่าน `toolResult(...).truncationFromSummary(result, { direction: \"tail\" })`\n6. การ map exit-code:\n   - ไม่มี exit code -> `ToolError(\"... missing exit status\")`\n   - exit ไม่เป็น zero -> `ToolError(\"... Command exited with code N\")`\n   - exit เป็น zero -> ผลลัพธ์ success\n\nโครงสร้าง payload ของ success:\n\n- `content`: ข้อความผลลัพธ์\n- `details.meta.truncation` เมื่อถูกตัดทอน รวมถึง:\n  - `direction`, `truncatedBy`, total/output line+byte counts\n  - `shownRange`\n  - `artifactId` เมื่อมี\n\nเนื่องจาก built-in tool ถูก wrap ด้วย `wrapToolWithMetaNotice()` ข้อความแจ้งเตือนการตัดทอนจะถูกเพิ่มต่อท้ายเนื้อหาข้อความสุดท้ายโดยอัตโนมัติ (เช่น: `Full: artifact://<id>`)\n\n## เส้นทางการแสดงผล\n\n## Tool-call renderer (`bashToolRenderer`)\n\n`bashToolRenderer` ใช้สำหรับข้อความ tool-call (`toolCall` / `toolResult`):\n\n- โหมด collapsed แสดง preview แบบตัดทอน visual-line\n- โหมด expanded แสดงข้อความผลลัพธ์ที่มีอยู่ทั้งหมดในขณะนั้น\n- บรรทัด warning รวม truncation reason และ `artifact://<id>` เมื่อถูกตัดทอน\n- ค่า timeout (จาก args) แสดงในบรรทัด metadata ที่ footer\n\n### ข้อควรระวัง: การขยาย artifact เต็มรูปแบบ\n\n`BashRenderContext` มี `isFullOutput` แต่ context builder ของ renderer ปัจจุบันไม่ได้ตั้งค่าสำหรับผลลัพธ์ bash tool expanded view ยังคงใช้ข้อความที่มีอยู่แล้วใน result content (tail/truncated output) เว้นแต่ caller อื่นจะให้เนื้อหา artifact เต็มรูปแบบ\n\n## User bang-command component (`BashExecutionComponent`)\n\n`BashExecutionComponent` ใช้สำหรับคำสั่ง `!` ของผู้ใช้ในโหมดโต้ตอบ (ไม่ใช่การเรียก tool ของโมเดล):\n\n- สตรีม chunk แบบ live\n- preview แบบ collapsed เก็บ 20 logical line ล่าสุด\n- จำกัดบรรทัดที่ 4000 ตัวอักษรต่อบรรทัด\n- แสดงคำเตือน truncation + artifact เมื่อมี metadata\n- ทำเครื่องหมาย cancelled/error/exit state แยกต่างหาก\n\nComponent นี้ถูกเชื่อมโดย `CommandController.handleBashCommand()` และได้รับข้อมูลจาก `AgentSession.executeBash()`\n\n## ความแตกต่างของพฤติกรรมตามโหมด\n\n| พื้นผิว                        | เส้นทางเข้า                                           | มีสิทธิ์ใช้ PTY                                                      | UX ผลลัพธ์แบบ live                                                       | การแสดงข้อผิดพลาด                               |\n| ------------------------------ | ----------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------ |\n| Tool call แบบโต้ตอบ            | `BashTool.execute`                                    | ใช่ เมื่อ `bash.virtualTerminal=on` และมี UI และ `PI_NO_PTY!=1`     | PTY overlay (โต้ตอบ) หรือ streamed tail updates                         | Tool error กลายเป็น `toolResult.isError`         |\n| Tool call แบบ print mode       | `BashTool.execute`                                    | ไม่ (ไม่มี UI context)                                               | ไม่มี TUI overlay; ผลลัพธ์ปรากฏใน event stream/final assistant text flow | การ map tool error เหมือนเดิม                    |\n| RPC tool call (agent tooling)  | `BashTool.execute`                                    | โดยปกติไม่มี UI -> non-PTY                                          | Structured tool events/results                                           | การ map tool error เหมือนเดิม                    |\n| คำสั่ง bang แบบโต้ตอบ (`!`)   | `AgentSession.executeBash` + `BashExecutionComponent` | ไม่ (ใช้ executor โดยตรง)                                            | Dedicated bash execution component                                       | Controller catch exceptions และแสดง UI error     |\n| คำสั่ง RPC `bash`              | `rpc-mode` -> `session.executeBash`                   | ไม่                                                                  | Return `BashResult` โดยตรง                                              | Consumer จัดการ field ที่ return มา             |\n\n## ข้อควรระวังในการดำเนินการ\n\n- Interceptor บล็อกคำสั่งเฉพาะเมื่อ tool ที่แนะนำมีอยู่ใน context ในขณะนั้น\n- หากการจัดสรร artifact ล้มเหลว การตัดทอนยังคงเกิดขึ้นแต่ไม่มี back-reference `artifact://` ที่ใช้ได้\n- Shell session cache ไม่มีการ eviction ที่ชัดเจนในโมดูลนี้ อายุการใช้งานถูกกำหนดในระดับ process\n- PTY และ non-PTY มีพื้นผิว timeout ที่แตกต่างกัน:\n  - PTY แสดง field ผลลัพธ์ `timedOut` ที่ชัดเจน\n  - non-PTY map timeout เป็น `cancelled + annotation` summary\n\n## ไฟล์การ implement\n\n- [`src/tools/bash.ts`](../../packages/coding-agent/src/tools/bash.ts) — จุดเข้า tool, normalization/interception, การเลือก PTY/non-PTY, การ map result/error, bash tool renderer\n- [`src/tools/bash-normalize.ts`](../../packages/coding-agent/src/tools/bash-normalize.ts) — การ normalize คำสั่งและการกรอง head/tail หลังรัน\n- [`src/tools/bash-interceptor.ts`](../../packages/coding-agent/src/tools/bash-interceptor.ts) — การ matching กฎ interceptor และข้อความคำสั่งที่ถูกบล็อก\n- [`src/exec/bash-executor.ts`](../../packages/coding-agent/src/exec/bash-executor.ts) — executor แบบ non-PTY, การนำ shell session กลับมาใช้ใหม่, การเชื่อมต่อการยกเลิก, การ integrate output sink\n- [`src/tools/bash-interactive.ts`](../../packages/coding-agent/src/tools/bash-interactive.ts) — PTY runtime, overlay UI, การ normalize อินพุต, ค่าเริ่มต้น env แบบ non-interactive\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — `OutputSink` truncation/artifact spill และ summary metadata\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — helpers การจัดสรร artifact และ streaming tail buffer\n- [`src/tools/output-meta.ts`](../../packages/coding-agent/src/tools/output-meta.ts) — รูปร่าง truncation metadata + wrapper การ inject notice\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — `executeBash` ระดับ session, การบันทึกข้อความ, วงจร abort\n- [`src/modes/components/bash-execution.ts`](../../packages/coding-agent/src/modes/components/bash-execution.ts) — component การประมวลผลคำสั่ง `!` แบบโต้ตอบ\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts) — การเชื่อมต่อ UI stream/update completion ของคำสั่ง `!` แบบโต้ตอบ\n- [`src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts) — พื้นผิวคำสั่ง RPC `bash` และ `abort_bash`\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — การ resolve `artifact://<id>`\n",
	"th/runtime-tools/context-command.md": "---\ntitle: F5 XC Contexts\ndescription: >-\n  เชื่อมต่อ xcsh กับ F5 Distributed Cloud tenants -- สร้าง สลับ และจัดการ\n  authentication contexts\nsidebar:\n  order: 1\n  label: F5 XC Contexts\ni18n:\n  sourceHash: a9cccbc338f0\n  translator: machine\n---\n\n# F5 XC Contexts\n\nxcsh เชื่อมต่อกับ F5 Distributed Cloud ผ่าน **contexts** -- ชุดข้อมูลรับรองที่มีชื่อซึ่งผูก tenant URL, API token และ namespace เข้าด้วยกัน หากคุณเคยใช้ `kubectl config use-context` หรือ `kubectx` มาก่อน workflow จะเหมือนกันทุกประการ: สร้าง context, สลับระหว่างกันด้วยชื่อ และใช้ `-` เพื่อสลับกลับ\n\n## เริ่มต้นใช้งาน\n\n### 1. สร้าง context แรกของคุณ\n\nคุณต้องการข้อมูลสามอย่างจาก F5 XC คอนโซล ของคุณ: tenant URL, API token และ namespace (ไม่บังคับ)\n\n```\n/context create production https://acme.console.ves.volterra.io p12k3-your-api-token\n```\n\n```\nContext 'production' created. Use /context activate production to switch to it.\n```\n\nหรือใช้ guided wizard หากคุณต้องการคำแนะนำทีละขั้นตอน:\n\n```\n/context wizard\n```\n\n### 2. เปิดใช้งาน\n\n```\n/context production\n```\n\n```\n╭─ production ─────────────────────────────────────────────────╮\n│ XCSH_TENANT     acme                                         │\n│ XCSH_API_URL    https://acme.console.ves.volterra.io         │\n│ XCSH_API_TOKEN  ...oken                                      │\n│ Status          Connected (312ms)                            │\n├─ Environment ────────────────────────────────────────────────┤\n│ XCSH_NAMESPACE  default                                      │\n╰──────────────────────────────────────────────────────────────╯\n```\n\nเมื่อเปิดใช้งานแล้ว xcsh จะฉีด tenant credentials เข้าไปในเซสชันของคุณ agent สามารถเรียกใช้ F5 XC API ได้แล้ว และแถบสถานะจะแสดง context ที่ใช้งานอยู่\n\n### 3. เพิ่ม contexts เพิ่มเติมและสลับระหว่างกัน\n\n```\n/context create staging https://staging.console.ves.volterra.io p12k3-staging-token\n```\n\nสลับด้วยชื่อ -- ไม่จำเป็นต้องใช้คำสั่งย่อย:\n\n```\n/context staging\n```\n\nสลับกลับไปยัง context ก่อนหน้า (สไตล์ `cd -`):\n\n```\n/context -\n```\n\nการเรียก `/context -` สองครั้งจะพาคุณกลับไปยังจุดเริ่มต้น\n\n### 4. ดูสิ่งที่คุณมี\n\n```\n/context\n```\n\n```\n  production           https://acme.console.ves.volterra.io\n* staging              https://staging.console.ves.volterra.io\n```\n\n`*` แสดง context ที่ใช้งานอยู่\n\n## คำสั่งที่ใช้บ่อย\n\n| คำสั่ง | หน้าที่ |\n|---|---|\n| `/context` | แสดงรายการ contexts ทั้งหมด |\n| `/context <name>` | สลับไปยัง context |\n| `/context -` | สลับไปยัง context ก่อนหน้า |\n| `/context show` | แสดงรายละเอียด context ที่ใช้งานอยู่ (ซ่อน tokens) |\n| `/context status` | แสดงสถานะการตรวจสอบสิทธิ์ปัจจุบัน |\n\n## วงจรชีวิตของ Context\n\n| คำสั่ง | หน้าที่ |\n|---|---|\n| `/context create <name> <url> <token> [namespace]` | สร้าง context |\n| `/context delete <name> --confirm` | ลบ context (ต้องใช้ `--confirm`) |\n| `/context rename <old> <new>` | เปลี่ยนชื่อ context |\n| `/context validate <name>` | ทดสอบข้อมูลรับรองโดยไม่สลับ |\n| `/context export [name] [--include-token]` | ส่งออกเป็น JSON (ซ่อน tokens ตามค่าเริ่มต้น) |\n| `/context import <path-or-json> [--overwrite]` | นำเข้าจากไฟล์หรือ inline JSON |\n| `/context wizard` | การตั้งค่าแบบโต้ตอบที่มีคำแนะนำ |\n\n## การสลับ Namespaces\n\nแต่ละ context มี namespace เริ่มต้น สลับโดยไม่ต้องเปลี่ยน context:\n\n```\n/context namespace system\n```\n\nTab completion จะแสดงชื่อ namespace จาก tenant ที่ใช้งานอยู่\n\n## Environment variables บน Contexts\n\nContexts สามารถพกพา environment variables เพิ่มเติมที่จะถูกฉีดเข้าไปในเซสชันของคุณเมื่อเปิดใช้งาน มีประโยชน์สำหรับการกำหนดค่าต่อ tenant ที่ไม่ได้เป็นส่วนหนึ่งของชุดข้อมูลรับรอง\n\n```\n/context set CUSTOM_HEADER=x-acme-trace\n/context set LOG_LEVEL=debug\n/context env list\n/context unset LOG_LEVEL\n```\n\nAliases: `add` = `set`, `remove`/`clear` = `unset`\n\n## Tab Completion\n\nพิมพ์ `/context ` แล้วกด Tab dropdown จะแสดง:\n\n1. **ชื่อ Context** -- พร้อม hints ของ tenant URL เพื่อให้คุณแยกแยะ tenants ได้\n2. **`-`** -- ปรากฏเมื่อคุณเคยสลับมาก่อน แสดง context ที่คุณจะสลับไป\n3. **Subcommands** -- `list`, `create`, `delete` เป็นต้น\n\nชื่อ context ปรากฏก่อนเนื่องจากการสลับเป็นการกระทำที่ใช้บ่อยที่สุด\n\nSubcommand-level completions ก็ใช้งานได้เช่นกัน: `/context activate <Tab>` สำเร็จรูปชื่อ context, `/context namespace <Tab>` สำเร็จรูป namespaces, `/context unset <Tab>` สำเร็จรูป env var keys ที่รู้จัก\n\n## กฎการตั้งชื่อ\n\nชื่อ context ต้องมี 1-64 ตัวอักษร: ตัวอักษร, ตัวเลข, ยัติภังค์, เครื่องหมายขีดล่าง\n\nชื่อที่ชนกับ subcommands จะถูกปฏิเสธ:\n\n```\n/context create list https://example.com tok\n```\n\n```\nError: Context name 'list' conflicts with a /context subcommand. Choose a different name.\n```\n\nชุดที่สงวนไว้ทั้งหมด: `list`, `show`, `status`, `create`, `delete`, `rename`, `namespace`, `env`, `set`, `unset`, `add`, `remove`, `clear`, `activate`, `validate`, `export`, `import`, `wizard`, `help` การเปรียบเทียบไม่คำนึงถึงตัวพิมพ์เล็ก-ใหญ่\n\n## การแทนที่ด้วย Environment Variable\n\nหาก `XCSH_API_URL` และ `XCSH_API_TOKEN` ถูกตั้งค่าใน shell environment ของคุณก่อนเปิด xcsh ค่าเหล่านั้นจะมีความสำคัญเหนือกว่า context ใดๆ ซึ่งมีประโยชน์สำหรับ CI/CD pipelines หรือเซสชันชั่วคราวที่คุณไม่ต้องการสร้าง context ถาวร\n\nเมื่อทำงานในโหมดนี้ `/context` จะแสดงข้อมูลรับรองที่มาจาก environment พร้อมป้ายกำกับ `(via env vars)`\n\n## พฤติกรรมของ Context ก่อนหน้า\n\n- **กำหนดขอบเขตเซสชัน**: context ก่อนหน้าจะรีเซ็ตเมื่อคุณรีสตาร์ท xcsh และไม่ถูกบันทึกลงดิสก์\n- **Ping-pong**: `/context -` สองครั้งจะพาคุณกลับไปยังจุดเริ่มต้น\n- **ปลอดภัยเมื่อมีการเปลี่ยนแปลง**: หากคุณลบ context ก่อนหน้า ตัวชี้จะถูกล้าง หากคุณเปลี่ยนชื่อ ตัวชี้จะติดตามชื่อใหม่\n- **การเปิดใช้งานซ้ำไม่มีผล**: `/context production` เมื่ออยู่บน `production` อยู่แล้วจะไม่รีเซ็ตตัวชี้ก่อนหน้า\n\n## แนวทางการออกแบบ\n\nUX ของ `/context` ปฏิบัติตาม:\n\n- **kubectx**: `kubectx <name>` สำหรับการสลับ, `kubectx -` สำหรับก่อนหน้า, `kubectx` เปล่าสำหรับการแสดงรายการ\n- **kubectl**: `kubectl config use-context` สำหรับรูปแบบที่ชัดเจน\n- **Shell**: `cd -` / `OLDPWD` สำหรับการติดตาม previous-directory\n",
	"th/runtime-tools/custom-tools.md": "---\ntitle: เครื่องมือที่กำหนดเอง\ndescription: >-\n  การลงทะเบียนเครื่องมือที่กำหนดเอง การกำหนดสคีมา\n  และกระบวนการประมวลผลสำหรับการขยายความสามารถของ agent\nsidebar:\n  order: 4\n  label: เครื่องมือที่กำหนดเอง\ni18n:\n  sourceHash: 5f4a441fc2e2\n  translator: machine\n---\n\n# เครื่องมือที่กำหนดเอง\n\nเครื่องมือที่กำหนดเองคือฟังก์ชันที่โมเดลสามารถเรียกใช้ได้ ซึ่งเชื่อมต่อเข้ากับกระบวนการประมวลผลเครื่องมือเดียวกันกับเครื่องมือที่มีอยู่ในตัว\n\nเครื่องมือที่กำหนดเองคือโมดูล TypeScript/JavaScript ที่ส่งออก factory โดย factory จะรับ host API (`CustomToolAPI`) และส่งคืนเครื่องมือหนึ่งชิ้นหรืออาร์เรย์ของเครื่องมือ\n\n## สิ่งที่เป็น (และไม่ใช่)\n\n- **เครื่องมือที่กำหนดเอง**: สามารถเรียกใช้โดยโมเดลระหว่างรอบการทำงาน (`execute` + TypeBox schema)\n- **ส่วนขยาย**: กรอบงานวงจรชีวิต/เหตุการณ์ที่สามารถลงทะเบียนเครื่องมือและสกัดกั้น/ปรับเปลี่ยนเหตุการณ์ได้\n- **Hook**: สคริปต์คำสั่งภายนอกก่อน/หลังการทำงาน\n- **Skill**: แพ็กเกจแนวทาง/บริบทแบบคงที่ ไม่ใช่โค้ดเครื่องมือที่ประมวลผลได้\n\nหากต้องการให้โมเดลเรียกใช้โค้ดโดยตรง ให้ใช้เครื่องมือที่กำหนดเอง\n\n## รูปแบบการผสานรวมในโค้ดปัจจุบัน\n\nมีรูปแบบการผสานรวมที่ใช้งานอยู่สองแบบ:\n\n1. **เครื่องมือที่กำหนดเองที่ SDK จัดเตรียมให้** (`options.customTools`)\n   - ถูกรวมเข้าเป็นเครื่องมือ agent ผ่าน `CustomToolAdapter` หรือ extension wrappers\n   - รวมอยู่ใน active tool set เริ่มต้นเสมอใน SDK bootstrap\n\n2. **โมดูลที่ค้นพบจากระบบไฟล์ผ่าน loader API** (`discoverAndLoadCustomTools` / `loadCustomTools`)\n   - เปิดเผยเป็น library API ใน `src/extensibility/custom-tools/loader.ts`\n   - โค้ดฝั่ง host สามารถเรียกใช้เพื่อค้นพบและโหลดโมดูลเครื่องมือจากพาธของ config/provider/plugin\n\n```text\nModel tool call flow\n\nLLM tool call\n   │\n   ▼\nTool registry (built-ins + custom tool adapters)\n   │\n   ▼\nCustomTool.execute(toolCallId, params, onUpdate, ctx, signal)\n   │\n   ├─ onUpdate(...)  -> streamed partial result\n   └─ return result  -> final tool content/details\n```\n\n## ตำแหน่งการค้นพบ (loader API)\n\n`discoverAndLoadCustomTools(configuredPaths, cwd, builtInToolNames)` รวมจาก:\n\n1. Capability providers (`toolCapability`) ได้แก่:\n   - Native OMP config (`~/.xcsh/agent/tools`, `.xcsh/tools`)\n   - Claude config (`~/.claude/tools`, `.claude/tools`)\n   - Codex config (`~/.codex/tools`, `.codex/tools`)\n   - Claude marketplace plugin cache provider\n2. Installed plugin manifests (`~/.xcsh/plugins/node_modules/*` ผ่าน plugin loader)\n3. พาธที่กำหนดค่าไว้อย่างชัดเจนที่ส่งให้ loader\n\n### พฤติกรรมที่สำคัญ\n\n- พาธที่แก้ไขซ้ำกันจะถูกกำจัดออก\n- ชื่อเครื่องมือที่ขัดแย้งกันจะถูกปฏิเสธเมื่อซ้ำกับ built-ins และเครื่องมือที่กำหนดเองที่โหลดไว้แล้ว\n- ไฟล์ `.md` และ `.json` จะถูกค้นพบเป็น tool metadata โดย provider บางส่วน แต่ executable module loader จะปฏิเสธไม่ให้รันเป็นเครื่องมือ\n- พาธที่กำหนดค่าแบบ relative จะถูกแก้ไขจาก `cwd`; `~` จะถูกขยาย\n\n## สัญญาของโมดูล\n\nโมดูลเครื่องมือที่กำหนดเองต้องส่งออกฟังก์ชัน (แนะนำให้ใช้ default export):\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = (pi) => ({\n name: \"repo_stats\",\n label: \"Repo Stats\",\n description: \"Counts tracked TypeScript files\",\n parameters: pi.typebox.Type.Object({\n  glob: pi.typebox.Type.Optional(pi.typebox.Type.String({ default: \"**/*.ts\" })),\n }),\n\n async execute(toolCallId, params, onUpdate, ctx, signal) {\n  onUpdate?.({\n   content: [{ type: \"text\", text: \"Scanning files...\" }],\n   details: { phase: \"scan\" },\n  });\n\n  const result = await pi.exec(\"git\", [\"ls-files\", params.glob ?? \"**/*.ts\"], { signal, cwd: pi.cwd });\n  if (result.killed) {\n   throw new Error(\"Scan was cancelled\");\n  }\n  if (result.code !== 0) {\n   throw new Error(result.stderr || \"git ls-files failed\");\n  }\n\n  const files = result.stdout.split(\"\\n\").filter(Boolean);\n  return {\n   content: [{ type: \"text\", text: `Found ${files.length} files` }],\n   details: { count: files.length, sample: files.slice(0, 10) },\n  };\n },\n\n onSession(event) {\n  if (event.reason === \"shutdown\") {\n   // cleanup resources if needed\n  }\n },\n});\n\nexport default factory;\n```\n\nประเภทที่ส่งคืนจาก factory:\n\n- `CustomTool`\n- `CustomTool[]`\n- `Promise<CustomTool | CustomTool[]>`\n\n## พื้นผิว API ที่ส่งให้ factories (`CustomToolAPI`)\n\nจาก `types.ts` และ `loader.ts`:\n\n- `cwd`: working directory ของ host\n- `exec(command, args, options?)`: ตัวช่วยประมวลผล process\n- `ui`: UI context (อาจเป็น no-op ในโหมด headless)\n- `hasUI`: `false` ในกระบวนการที่ไม่โต้ตอบ\n- `logger`: shared file logger\n- `typebox`: `@sinclair/typebox` ที่ inject มาให้\n- `pi`: exports ของ `@f5-sales-demo/xcsh` ที่ inject มาให้\n- `pushPendingAction(action)`: ลงทะเบียน preview action สำหรับ `resolve` tool ที่ซ่อนอยู่ (`docs/resolve-tool-runtime.md`)\n\nLoader เริ่มต้นด้วย no-op UI context และต้องการให้โค้ดฝั่ง host เรียก `setUIContext(...)` เมื่อ UI จริงพร้อมใช้งาน\n\n## สัญญาการประมวลผลและการกำหนดประเภท\n\nลายเซ็นของ `CustomTool.execute`:\n\n```ts\nexecute(toolCallId, params, onUpdate, ctx, signal)\n```\n\n- `params` มีการกำหนดประเภทแบบ static จาก TypeBox schema ผ่าน `Static<TParams>`\n- การตรวจสอบความถูกต้องของอาร์กิวเมนต์ที่รันไทม์เกิดขึ้นก่อนการประมวลผลใน agent loop\n- `onUpdate` ส่งผลลัพธ์บางส่วนสำหรับ UI streaming\n- `ctx` รวม session/model state และตัวช่วย `abort()`\n- `signal` รับการยกเลิก\n\n`CustomToolAdapter` เชื่อมต่อสิ่งนี้กับอินเทอร์เฟซเครื่องมือ agent และส่งต่อการเรียกในลำดับอาร์กิวเมนต์ที่ถูกต้อง\n\n## วิธีการเปิดเผยเครื่องมือให้โมเดล\n\n- เครื่องมือถูกรวมเข้าเป็น `AgentTool` instances (`CustomToolAdapter` หรือ extension wrappers)\n- ถูกแทรกเข้าใน session tool registry ตามชื่อ\n- ใน SDK bootstrap เครื่องมือที่กำหนดเองและที่ลงทะเบียนผ่าน extension จะถูกบังคับรวมใน active set เริ่มต้น\n- CLI `--tools` ในปัจจุบันตรวจสอบเฉพาะชื่อเครื่องมือ built-in; การรวมเครื่องมือที่กำหนดเองจัดการผ่านพาธการค้นพบ/การลงทะเบียนและ SDK options\n\n## Rendering hooks\n\nRendering hooks ที่ไม่บังคับ:\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\nพฤติกรรมรันไทม์ใน TUI:\n\n- หาก hook มีอยู่ output ของเครื่องมือจะถูก render ภายในคอนเทนเนอร์ `Box`\n- `renderResult` รับ `{ expanded, isPartial, spinnerFrame? }`\n- ข้อผิดพลาดของ renderer จะถูกจับและบันทึก; UI จะ fallback ไปยังการ render ข้อความเริ่มต้น\n\n## การจัดการ session/state\n\n`onSession(event, ctx)` ที่ไม่บังคับจะรับเหตุการณ์วงจรชีวิต session รวมถึง:\n\n- `start`, `switch`, `branch`, `tree`, `shutdown`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`, `todo_reminder`\n\nใช้ `ctx.sessionManager` เพื่อสร้าง state จาก history ใหม่เมื่อบริบท branch/session เปลี่ยนแปลง\n\n## ความล้มเหลวและความหมายของการยกเลิก\n\n### ความล้มเหลวแบบ synchronous/async\n\n- การ throw (หรือ rejected promises) ใน `execute` ถือเป็นความล้มเหลวของเครื่องมือ\n- Agent runtime แปลงความล้มเหลวเป็น tool result messages ที่มี `isError: true` และเนื้อหาข้อผิดพลาด\n- ด้วย extension wrappers, `tool_result` handlers สามารถเขียน content/details ใหม่ และแม้แต่แทนที่สถานะข้อผิดพลาดได้\n\n### การยกเลิก\n\n- Agent abort จะแพร่กระจายผ่าน `AbortSignal` ไปยัง `execute`\n- ส่ง `signal` ต่อไปยังการทำงานของ subprocess (`pi.exec(..., { signal })`) เพื่อการยกเลิกแบบร่วมมือ\n- `ctx.abort()` ให้เครื่องมือร้องขอการ abort ของ agent operation ปัจจุบันได้\n\n### ข้อผิดพลาดของ onSession\n\n- ข้อผิดพลาดของ `onSession` จะถูกจับและบันทึกเป็นคำเตือน โดยจะไม่ทำให้ session หยุดทำงาน\n\n## ข้อจำกัดจริงที่ต้องออกแบบรองรับ\n\n- ชื่อเครื่องมือต้องไม่ซ้ำกันทั่วทั้ง active registry\n- แนะนำให้ใช้ output ที่กำหนดรูปแบบตาม schema แบบ deterministic ใน `details` สำหรับการ render/สร้าง state ใหม่\n- ป้องกันการใช้งาน UI ด้วย `pi.hasUI`\n- ถือว่าไฟล์ `.md`/`.json` ในไดเรกทอรีเครื่องมือเป็น metadata ไม่ใช่โมดูลที่ประมวลผลได้\n",
	"th/runtime-tools/notebook-tool-runtime.md": "---\ntitle: ส่วนภายในของรันไทม์เครื่องมือ Notebook\ndescription: >-\n  รันไทม์เครื่องมือ Jupyter notebook พร้อมการรันเซลล์ วงจรชีวิตของเคอร์เนล\n  และการแสดงผลลัพธ์\nsidebar:\n  order: 2\n  label: เครื่องมือ Notebook\ni18n:\n  sourceHash: c1bafcb245e4\n  translator: machine\n---\n\n# ส่วนภายในของรันไทม์เครื่องมือ Notebook\n\nเอกสารนี้อธิบายการใช้งานเครื่องมือ `notebook` ในปัจจุบัน และความสัมพันธ์กับรันไทม์ Python ที่รองรับด้วยเคอร์เนล\n\nความแตกต่างที่สำคัญ: **`notebook` คือเครื่องมือแก้ไข JSON notebook ไม่ใช่ตัวรัน notebook** โดยจะแก้ไขซอร์สเซลล์ `.ipynb` โดยตรง ไม่ได้เริ่มต้นหรือสื่อสารกับ Python เคอร์เนล\n\n## ไฟล์การใช้งาน\n\n- [`src/tools/notebook.ts`](../../packages/coding-agent/src/tools/notebook.ts)\n- [`src/ipy/executor.ts`](../../packages/coding-agent/src/ipy/executor.ts)\n- [`src/ipy/kernel.ts`](../../packages/coding-agent/src/ipy/kernel.ts)\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts)\n- [`src/tools/python.ts`](../../packages/coding-agent/src/tools/python.ts)\n\n## 1) ขอบเขตรันไทม์: การแก้ไขเทียบกับการรัน\n\n## เครื่องมือ `notebook` (`src/tools/notebook.ts`)\n\n- รองรับ `action: edit | insert | delete` บนไฟล์ `.ipynb`\n- แก้ไขพาธสัมพันธ์กับ session CWD (`resolveToCwd`)\n- โหลด JSON ของ notebook ตรวจสอบความถูกต้องของอาร์เรย์ `cells` และตรวจสอบขอบเขตของ `cell_index`\n- ใช้การแก้ไขซอร์สในหน่วยความจำและเขียน JSON ของ notebook ทั้งหมดกลับด้วย `JSON.stringify(notebook, null, 1)`\n- คืนค่าสรุปเป็นข้อความ + `details` แบบโครงสร้าง (`action`, `cellIndex`, `cellType`, `totalCells`, `cellSource`)\n\nไม่มีวงจรชีวิตของเคอร์เนลในเครื่องมือนี้:\n\n- ไม่มีการรับ gateway\n- ไม่มี kernel session ID\n- ไม่มี `execute_request`\n- ไม่มี stream chunks จากช่องทางของเคอร์เนล\n- ไม่มีการจับภาพ rich display (`image/png`, JSON display, status MIME)\n\n## เส้นทางการรันแบบ Notebook (`src/tools/python.ts` + `src/ipy/*`)\n\nเมื่อ agent ต้องการรันโค้ด Python แบบเซลล์ (เซลล์ตามลำดับ, สถานะที่คงอยู่, rich displays) จะดำเนินการผ่าน **เครื่องมือ `python`** ไม่ใช่ `notebook`\n\nเส้นทางนี้คือที่ที่โหมดเคอร์เนล, พฤติกรรม restart/cancel, chunk streaming และการตัดทอนเอาต์พุตอาร์ติแฟกต์อยู่\n\n## 2) ความหมายของการจัดการเซลล์ Notebook (เครื่องมือ `notebook`)\n\n## การทำให้ซอร์สเป็นมาตรฐาน\n\n`content` จะถูกแบ่งเป็น `source: string[]` โดยรักษาบรรทัดใหม่:\n\n- แต่ละบรรทัดที่ไม่ใช่บรรทัดสุดท้ายจะคงท้าย `\\n` ไว้\n- บรรทัดสุดท้ายไม่มีการบังคับใส่บรรทัดใหม่ท้าย\n\nสิ่งนี้สะท้อนถึงข้อกำหนด JSON ของ notebook และหลีกเลี่ยงการต่อบรรทัดโดยไม่ตั้งใจในการแก้ไขครั้งต่อไป\n\n## พฤติกรรมของแต่ละ Action\n\n- `edit`\n  - แทนที่ `cells[cell_index].source`\n  - รักษา `cell_type` เดิมไว้\n- `insert`\n  - แทรกที่ตำแหน่ง `[0..cellCount]`\n  - `cell_type` ค่าเริ่มต้นคือ `code`\n  - เซลล์โค้ดเริ่มต้นด้วย `execution_count: null` และ `outputs: []`\n  - เซลล์ markdown เริ่มต้นด้วยเพียง `metadata` + `source`\n- `delete`\n  - ลบ `cells[cell_index]`\n  - คืนค่า `source` ที่ถูกลบใน details สำหรับการแสดงตัวอย่างของ renderer\n\n## พื้นผิวของข้อผิดพลาด\n\nความล้มเหลวแบบ Hard จะถูก throw สำหรับ:\n\n- ไม่พบไฟล์ notebook\n- JSON ไม่ถูกต้อง\n- `cells` ขาดหายหรือไม่ใช่อาร์เรย์\n- index อยู่นอกช่วง (insert และ non-insert มีช่วงที่ถูกต้องต่างกัน)\n- `content` ขาดหายสำหรับ `edit`/`insert`\n\nสิ่งเหล่านี้จะกลายเป็นการตอบสนองเครื่องมือ `Error:` ที่ต้นน้ำ โดย renderer ใช้พาธของ notebook + ข้อความข้อผิดพลาดที่จัดรูปแบบแล้ว\n\n## 3) ความหมายของ Kernel Session (ที่มีอยู่จริง)\n\nความหมายของเคอร์เนลถูกใช้งานใน `executePython` / `PythonKernel` และใช้กับเครื่องมือ `python`\n\n## โหมด\n\n`PythonKernelMode`:\n\n- `session` (ค่าเริ่มต้น)\n  - เคอร์เนลถูก cache ในแมป `kernelSessions`\n  - สูงสุด 4 sessions โดยตัวเก่าสุดจะถูกขับออกเมื่อเกิน\n  - ทำความสะอาด idle/dead ทุก 30 วินาที, หมดเวลาหลังจาก 5 นาที\n  - คิวต่อ session จัดลำดับการรัน (`session.queue`)\n- `per-call`\n  - สร้างเคอร์เนลสำหรับ request\n  - รัน\n  - ปิดเคอร์เนลเสมอใน `finally`\n\n## พฤติกรรม Reset\n\nเครื่องมือ `python` ส่ง `reset` เฉพาะสำหรับเซลล์แรกในการเรียกแบบหลายเซลล์เท่านั้น เซลล์ถัดไปจะรันด้วย `reset: false` เสมอ\n\n## การตายของเคอร์เนล / Restart / Retry\n\nในโหมด session (`withKernelSession`):\n\n- ตรวจพบเคอร์เนลที่ตายแล้วด้วย heartbeat (ตรวจสอบ `kernel.isAlive()` ทุก 5 วินาที) หรือความล้มเหลวในการรัน\n- สถานะตายก่อนรันทริกเกอร์ `restartKernelSession`\n- เส้นทาง crash ในขณะรัน retry หนึ่งครั้ง: restart เคอร์เนล, รัน handler ใหม่\n- `restartCount > 1` ใน session เดียวกันจะ throw `Python kernel restarted too many times in this session`\n\nพฤติกรรม Startup retry:\n\n- การสร้างเคอร์เนล shared gateway retry หนึ่งครั้งเมื่อเกิด `SharedGatewayCreateError` ที่มี HTTP 5xx\n\nการกู้คืนจากทรัพยากรหมด:\n\n- ตรวจพบความล้มเหลวแบบ `EMFILE`/`ENFILE`/\"Too many open files\"\n- ล้าง sessions ที่ถูกติดตาม\n- เรียก `shutdownSharedGateway()`\n- retry การสร้าง kernel session หนึ่งครั้ง\n\n## 4) การฉีด Environment/Session Variable\n\nการเริ่มต้นเคอร์เนลรับ env map ที่ไม่บังคับจาก executor:\n\n- `PI_SESSION_FILE` (พาธไฟล์สถานะ session)\n- `ARTIFACTS` (ไดเรกทอรีอาร์ติแฟกต์)\n\nจากนั้น `PythonKernel.#initializeKernelEnvironment(...)` จะรันสคริปต์เริ่มต้นภายในเคอร์เนลเพื่อ:\n\n- `os.chdir(cwd)`\n- ฉีด env entries เข้าสู่ `os.environ`\n- เพิ่ม cwd ไว้ที่ต้นของ `sys.path` หากยังไม่มี\n\nผลกระทบ:\n\n- prelude helpers ที่อ่าน session หรือ artifact context อาศัย env vars เหล่านี้ในสถานะ Python process\n\n## 5) การจัดการ Streaming/Chunk และ Display (เส้นทางที่รองรับด้วยเคอร์เนล)\n\nkernel client ประมวลผลข้อความโปรโตคอล Jupyter ต่อการรัน:\n\n- `stream` -> text chunk ไปยัง `onChunk`\n- `execute_result` / `display_data` ->\n  - ข้อความ display ถูกเลือกตามลำดับ MIME: `text/markdown` > `text/plain` > แปลงจาก `text/html`\n  - เอาต์พุตแบบโครงสร้างถูกจับแยกต่างหาก:\n    - `application/json` -> `{ type: \"json\" }`\n    - `image/png` -> `{ type: \"image\" }`\n    - `application/x-xcsh-status` -> `{ type: \"status\" }` (ไม่ปล่อยข้อความ)\n- `error` -> ข้อความ traceback ถูก push ไปยัง chunk stream + metadata ข้อผิดพลาดแบบโครงสร้าง\n- `input_request` -> ปล่อยข้อความเตือน stdin, ส่ง `input_reply` ว่าง, ทำเครื่องหมายว่ามีการร้องขอ stdin\n- การสิ้นสุดรอทั้ง `execute_reply` และ kernel `status=idle`\n\nCancellation/timeout:\n\n- abort signal ทริกเกอร์ `interrupt()` (REST `/interrupt` + control-channel `interrupt_request`)\n- ผลลัพธ์ทำเครื่องหมาย `cancelled=true`\n- เส้นทาง timeout เพิ่มข้อความใน output ว่า `Command timed out after <n> seconds`\n\n## 6) พฤติกรรมการตัดทอนและอาร์ติแฟกต์\n\n`OutputSink` ใน `src/session/streaming-output.ts` ถูกใช้โดยเส้นทางการรันเคอร์เนล (`executeWithKernel`):\n\n- ทำความสะอาดทุก chunk (`sanitizeText`)\n- ติดตามจำนวนบรรทัดและไบต์รวม/ที่ส่งออก\n- ไฟล์ spill อาร์ติแฟกต์ที่ไม่บังคับ (`artifactPath`, `artifactId`)\n- เมื่อบัฟเฟอร์ในหน่วยความจำเกินเกณฑ์ (`DEFAULT_MAX_BYTES` หากไม่มีการ override):\n  - ทำเครื่องหมาย truncated\n  - เก็บ tail bytes ไว้ในหน่วยความจำ (ขอบเขตที่ปลอดภัยสำหรับ UTF-8)\n  - สามารถ spill stream ทั้งหมดไปยัง artifact sink\n\n`dump()` คืนค่า:\n\n- ข้อความเอาต์พุตที่มองเห็นได้ (อาจถูกตัดทอนจาก tail)\n- flag การตัดทอน + จำนวน\n- artifact ID (สำหรับการอ้างอิง `artifact://<id>`)\n\nเครื่องมือ `python` แปลง metadata นี้เป็นการแจ้งเตือนการตัดทอนผลลัพธ์และคำเตือน TUI\n\nเครื่องมือ `notebook` **ไม่ได้** ใช้ `OutputSink` เพราะไม่มี pipeline สำหรับ stream/artifact truncation เนื่องจากไม่ได้รันโค้ด\n\n## 7) สมมติฐานของ Renderer และการจัดรูปแบบ\n\n## Notebook Renderer (`notebookToolRenderer`)\n\n- call view: บรรทัดสถานะพร้อม action + พาธ notebook + metadata ของเซลล์/ประเภท\n- result view:\n  - สรุปความสำเร็จที่ได้จาก `details`\n  - `cellSource` แสดงผ่าน `renderCodeCell`\n  - เซลล์ markdown ตั้ง language hint เป็น `markdown` เซลล์อื่นไม่มีการ override ภาษาอย่างชัดเจน\n  - ขีดจำกัดตัวอย่างโค้ดแบบย่อคือ `PREVIEW_LIMITS.COLLAPSED_LINES * 2`\n  - รองรับโหมดขยายผ่าน shared render options\n  - ใช้ render cache ที่กำหนดคีย์ตามความกว้าง + สถานะการขยาย\n\nสมมติฐานการแสดงข้อผิดพลาด:\n\n- หากเนื้อหาข้อความแรกขึ้นต้นด้วย `Error:` renderer จะจัดรูปแบบเป็นบล็อกข้อผิดพลาดของ notebook\n\n## Python Renderer (สำหรับเอาต์พุตการรันจริง)\n\nการแสดงผลการรันที่รองรับด้วยเคอร์เนลคาดหวัง:\n\n- การเปลี่ยนสถานะต่อเซลล์ (`pending/running/complete/error`)\n- ส่วน status event แบบโครงสร้างที่ไม่บังคับ\n- โครงสร้างต้นไม้ JSON output ที่ไม่บังคับ\n- คำเตือนการตัดทอน + ตัวชี้ `artifact://<id>` ที่ไม่บังคับ\n\nพฤติกรรมของ renderer นี้ไม่เกี่ยวข้องกับผลลัพธ์การแก้ไข `notebook` JSON ยกเว้นที่ทั้งสองใช้ TUI primitives ที่ใช้ร่วมกัน\n\n## 8) ความแตกต่างจากพฤติกรรมเครื่องมือ Python แบบเรียบง่าย\n\nหาก \"เครื่องมือ Python แบบเรียบง่าย\" หมายถึงเส้นทางการรัน `python`:\n\n- `python` รันโค้ดในเคอร์เนล, คงสถานะตามโหมด, streaming chunks, จับภาพ rich displays, จัดการ interrupts/timeouts และรองรับการตัดทอน output/อาร์ติแฟกต์\n- `notebook` ดำเนินการ mutation JSON ของ notebook อย่างแน่นอนเท่านั้น ไม่มีการรัน ไม่มีสถานะเคอร์เนล ไม่มี chunk stream ไม่มี display outputs ไม่มี artifact pipeline\n\nหาก workflow ต้องการทั้งสองอย่าง:\n\n1. แก้ไขซอร์สของ notebook ด้วย `notebook`\n2. รันเซลล์โค้ดผ่าน `python` (ส่งโค้ดด้วยตนเอง) ไม่ใช่ผ่าน `notebook`\n\nการใช้งานในปัจจุบันไม่มีเครื่องมือเดียวที่สามารถทั้ง mutate `.ipynb` และรันเซลล์ notebook ผ่าน kernel context ได้\n",
	"th/runtime-tools/resolve-tool-runtime.md": "---\ntitle: ภายในรันไทม์ของเครื่องมือ Resolve\ndescription: >-\n  รันไทม์ของเครื่องมือ Resolve สำหรับการระบุเส้นทางไฟล์ การดึงเนื้อหา\n  และการเข้าถึงทรัพยากรผ่าน URL\nsidebar:\n  order: 3\n  label: เครื่องมือ Resolve\ni18n:\n  sourceHash: 06e8be8c5a3c\n  translator: machine\n---\n\n# ภายในรันไทม์ของเครื่องมือ Resolve\n\nเอกสารนี้อธิบายวิธีการสร้างแบบจำลองขั้นตอนการทำงาน preview/apply ใน coding-agent และวิธีที่เครื่องมือแบบกำหนดเองสามารถเข้าร่วมได้ผ่าน `pushPendingAction`\n\n## ขอบเขตและไฟล์หลัก\n\n- [`src/tools/resolve.ts`](../../packages/coding-agent/src/tools/resolve.ts)\n- [`src/tools/pending-action.ts`](../../packages/coding-agent/src/tools/pending-action.ts)\n- [`src/tools/ast-edit.ts`](../../packages/coding-agent/src/tools/ast-edit.ts)\n- [`src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts)\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n\n## สิ่งที่ `resolve` ทำ\n\n`resolve` คือเครื่องมือที่ซ่อนอยู่ซึ่งทำให้การดำเนินการ preview ที่รอดำเนินการเสร็จสมบูรณ์\n\n- `action: \"apply\"` จะเรียกใช้ `apply(reason)` บนการดำเนินการที่รอดำเนินการและบันทึกการเปลี่ยนแปลง\n- `action: \"discard\"` จะเรียกใช้ `reject(reason)` หากมีการกำหนดไว้ มิฉะนั้นจะยกเลิกการดำเนินการด้วยข้อความเริ่มต้น \"Discarded\"\n\nหากไม่มีการดำเนินการที่รอดำเนินการ `resolve` จะล้มเหลวพร้อมข้อความ:\n\n- `No pending action to resolve. Nothing to apply or discard.`\n\n## การดำเนินการที่รอดำเนินการเป็นสแตก (LIFO)\n\nการดำเนินการที่รอดำเนินการจะถูกจัดเก็บใน `PendingActionStore` ในรูปแบบสแตก push/pop:\n\n- `push(action)` เพิ่มการดำเนินการที่รอดำเนินการใหม่ขึ้นไปที่ด้านบน\n- `peek()` ตรวจสอบการดำเนินการที่อยู่ด้านบนในปัจจุบัน\n- `pop()` นำการดำเนินการที่อยู่ด้านบนออกและส่งคืน\n- `hasPending` ระบุว่าสแตกไม่ว่างเปล่าหรือไม่\n\n`resolve` จะใช้งานการดำเนินการที่รอดำเนินการที่ **อยู่บนสุด** เสมอก่อน (`pop()`) ดังนั้นเครื่องมือที่สร้าง preview หลายตัวจะถูก resolve ตามลำดับย้อนกลับของการลงทะเบียน\n\n## ตัวอย่าง producer ในตัว (`ast_edit`)\n\n`ast_edit` จะแสดง preview การแทนที่โครงสร้างก่อน เมื่อ preview มีการแทนที่และยังไม่ได้ถูก apply จะมีการ push การดำเนินการที่รอดำเนินการซึ่งประกอบด้วย:\n\n- label (สรุปที่มนุษย์อ่านได้)\n- `sourceToolName` (`ast_edit`)\n- callback `apply(reason: string)` ที่รันการแก้ไข AST อีกครั้งด้วย `dryRun: false`\n\n`resolve(action=\"apply\", reason=\"...\")` จะส่ง `reason` เข้าไปยัง callback นี้\n\n## เครื่องมือแบบกำหนดเอง: `pushPendingAction`\n\nเครื่องมือแบบกำหนดเองสามารถลงทะเบียนการดำเนินการที่รอดำเนินการที่เข้ากันได้กับ resolve ผ่าน `CustomToolAPI.pushPendingAction(...)`\n\n`CustomToolPendingAction`:\n\n- `label: string` (จำเป็น)\n- `apply(reason: string): Promise<AgentToolResult<unknown>>` (จำเป็น) — ถูกเรียกใช้เมื่อ apply; `reason` คือสตริงที่ส่งไปยัง `resolve`\n- `reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>` (ไม่บังคับ) — ถูกเรียกใช้เมื่อ discard; ค่าที่ส่งคืนจะแทนที่ข้อความ \"Discarded\" เริ่มต้นหากมีการกำหนดไว้\n- `details?: unknown` (ไม่บังคับ)\n- `sourceToolName?: string` (ไม่บังคับ ค่าเริ่มต้นคือ `\"custom_tool\"`)\n\n### ตัวอย่างการใช้งานขั้นต่ำ\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = pi => ({\n name: \"batch_rename_preview\",\n label: \"Batch Rename Preview\",\n description: \"Previews renames and defers commit to resolve\",\n parameters: pi.typebox.Type.Object({\n  files: pi.typebox.Type.Array(pi.typebox.Type.String()),\n }),\n\n async execute(_toolCallId, params) {\n  const previewSummary = `Prepared rename plan for ${params.files.length} files`;\n\n  pi.pushPendingAction({\n   label: `Batch rename: ${params.files.length} files`,\n   sourceToolName: \"batch_rename_preview\",\n   apply: async (reason) => {\n    // apply writes here\n    return {\n     content: [{ type: \"text\", text: `Applied batch rename. Reason: ${reason}` }],\n    };\n   },\n   reject: async (reason) => {\n    // optional: cleanup or notify on discard\n    return {\n     content: [{ type: \"text\", text: `Discarded batch rename. Reason: ${reason}` }],\n    };\n   },\n  });\n\n  return {\n   content: [{ type: \"text\", text: `${previewSummary}. Call resolve to apply or discard.` }],\n  };\n },\n});\n\nexport default factory;\n```\n\n## ความพร้อมใช้งานของรันไทม์และความล้มเหลว\n\n`pushPendingAction` ถูกเชื่อมต่อโดย custom tool loader โดยใช้ `PendingActionStore` ของเซสชันที่ใช้งานอยู่\n\nหากรันไทม์ไม่มี pending-action store `pushPendingAction` จะ throw ข้อผิดพลาด:\n\n- `Pending action store unavailable for custom tools in this runtime.`\n\n## พฤติกรรมการเลือกเครื่องมือ\n\nเมื่อ `PendingActionStore.hasPending` เป็น true รันไทม์ของ agent จะเน้นการเลือกเครื่องมือไปที่ `resolve` เพื่อให้ preview ที่รอดำเนินการถูก finalize อย่างชัดเจนก่อนที่การทำงานของเครื่องมือปกติจะดำเนินต่อไป\n\n## คำแนะนำสำหรับนักพัฒนา\n\n- ใช้ pending actions เฉพาะสำหรับการดำเนินการที่ทำลายข้อมูลหรือมีผลกระทบสูงที่ควรรองรับการ apply/discard อย่างชัดเจน\n- รักษา `label` ให้กระชับและเฉพาะเจาะจง เนื่องจากจะแสดงในผลลัพธ์ของ resolve renderer\n- ตรวจสอบให้แน่ใจว่า `apply(reason)` ทำงานแบบ deterministic และ idempotent เพียงพอสำหรับการ execute ครั้งเดียว; `reason` เป็นข้อมูลเสริมและไม่ควรเปลี่ยนแปลงพฤติกรรม\n- ใช้งาน `reject(reason)` เมื่อการ discard ต้องการการล้างข้อมูล (สถานะชั่วคราว, locks, การแจ้งเตือน); ละเว้นสำหรับ preview ที่ไม่มีสถานะซึ่งข้อความเริ่มต้นเพียงพอแล้ว\n- หากเครื่องมือของคุณสามารถเตรียม preview หลายรายการได้ ให้จำไว้ว่ามีความหมายแบบ LIFO: การดำเนินการที่ push ล่าสุดจะถูก resolve ก่อน\n",
	"th/runtime-tools/slash-command-internals.md": "---\ntitle: ภายในระบบ Slash Command\ndescription: >-\n  ภายในระบบ slash command พร้อมการลงทะเบียน การแยกวิเคราะห์อาร์กิวเมนต์\n  และการส่งการดำเนินการ\nsidebar:\n  order: 5\n  label: Slash commands\ni18n:\n  sourceHash: 2cbd44a3de87\n  translator: machine\n---\n\n# ภายในระบบ Slash Command\n\nเอกสารนี้อธิบายวิธีที่ slash command ถูกค้นพบ ตรวจสอบรายการซ้ำ แสดงในโหมดโต้ตอบ และขยายผลตอนป้อน prompt ใน `coding-agent`\n\n## ไฟล์การดำเนินการ\n\n- [`src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n- [`src/capability/slash-command.ts`](../../packages/coding-agent/src/capability/slash-command.ts)\n- [`src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`src/discovery/claude.ts`](../../packages/coding-agent/src/discovery/claude.ts)\n- [`src/discovery/codex.ts`](../../packages/coding-agent/src/discovery/codex.ts)\n- [`src/discovery/claude-plugins.ts`](../../packages/coding-agent/src/discovery/claude-plugins.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n\n## 1) โมเดลการค้นพบ\n\nSlash command เป็น capability (`id: \"slash-commands\"`) ที่มีคีย์ตามชื่อคำสั่ง (`key: cmd => cmd.name`)\n\nรีจิสทรี capability จะโหลด provider ที่ลงทะเบียนทั้งหมด เรียงลำดับตามลำดับความสำคัญของ provider จากมากไปน้อย และตรวจสอบรายการซ้ำตามคีย์โดยใช้ semantics แบบ **ตัวแรกชนะ**\n\n### ลำดับความสำคัญของ Provider\n\nProvider ของ slash command ในปัจจุบันและลำดับความสำคัญ:\n\n1. `native` (OMP) — ลำดับความสำคัญ `100`\n2. `claude` — ลำดับความสำคัญ `80`\n3. `claude-plugins` — ลำดับความสำคัญ `70`\n4. `codex` — ลำดับความสำคัญ `70`\n\nพฤติกรรมเมื่อเสมอกัน: provider ที่มีลำดับความสำคัญเท่ากันจะรักษาลำดับการลงทะเบียนไว้ ลำดับการ import ปัจจุบันจะลงทะเบียน `claude-plugins` ก่อน `codex` ดังนั้นคำสั่ง plugin จะชนะคำสั่ง codex เมื่อชื่อชนกัน\n\n### พฤติกรรมเมื่อชื่อชนกัน\n\nสำหรับ `slash-commands` การชนกันจะถูกแก้ไขโดยการตรวจสอบรายการซ้ำของ capability อย่างเคร่งครัด:\n\n- รายการที่มีลำดับความสำคัญสูงสุดจะถูกเก็บไว้ใน `result.items`\n- รายการซ้ำที่มีลำดับความสำคัญต่ำกว่าจะอยู่ใน `result.all` เท่านั้น และถูกทำเครื่องหมาย `_shadowed = true`\n\nซึ่งใช้ได้ทั้งกับ provider ต่าง ๆ และภายใน provider เดียวกันหากส่งคืนชื่อซ้ำกัน\n\n### พฤติกรรมการสแกนไฟล์\n\nProvider ส่วนใหญ่ใช้ `loadFilesFromDir(...)` ซึ่งปัจจุบัน:\n\n- ค่าเริ่มต้นเป็นการจับคู่แบบไม่ recursive (`*.md`)\n- ใช้ native glob พร้อม `gitignore: true`, `hidden: false`\n- อ่านแต่ละไฟล์ที่จับคู่และแปลงเป็น `SlashCommand`\n\nดังนั้นไฟล์/ไดเรกทอรีที่ซ่อนอยู่จะไม่ถูกโหลด และเส้นทางที่ถูก ignore จะถูกข้าม\n\n## 2) เส้นทางต้นทางเฉพาะของ Provider และลำดับความสำคัญในท้องถิ่น\n\n## Provider `native` (`builtin.ts`)\n\nต้นทางการค้นหามาจากไดเรกทอรี `.xcsh`:\n\n- โปรเจกต์: `<cwd>/.xcsh/commands/*.md`\n- ผู้ใช้: `~/.xcsh/agent/commands/*.md`\n\n`getConfigDirs()` จะส่งคืนโปรเจกต์ก่อน จากนั้นผู้ใช้ ดังนั้น **คำสั่ง native ของโปรเจกต์จะชนะคำสั่ง native ของผู้ใช้** เมื่อชื่อชนกัน\n\n## Provider `claude` (`claude.ts`)\n\nโหลด:\n\n- ผู้ใช้: `~/.claude/commands/*.md`\n- โปรเจกต์: `<cwd>/.claude/commands/*.md`\n\nProvider จะ push รายการผู้ใช้ก่อนรายการโปรเจกต์ ดังนั้น **คำสั่ง Claude ของผู้ใช้จะชนะคำสั่ง Claude ของโปรเจกต์** เมื่อชื่อเดียวกันชนกันภายใน provider นี้\n\n## Provider `codex` (`codex.ts`)\n\nโหลด:\n\n- ผู้ใช้: `~/.codex/commands/*.md`\n- โปรเจกต์: `<cwd>/.codex/commands/*.md`\n\nทั้งสองฝั่งจะถูกโหลดแล้ว flatten ตามลำดับผู้ใช้ก่อน ดังนั้น **คำสั่ง Codex ของผู้ใช้จะชนะคำสั่ง Codex ของโปรเจกต์** เมื่อชนกัน\n\nเนื้อหาคำสั่ง Codex ถูก parse ด้วยการ strip frontmatter (`parseFrontmatter`) และชื่อคำสั่งสามารถถูกแทนที่ด้วย frontmatter `name` ได้ มิฉะนั้นจะใช้ชื่อไฟล์แทน\n\n## Provider `claude-plugins` (`claude-plugins.ts`)\n\nโหลด root ของคำสั่ง plugin จาก `~/.claude/plugins/installed_plugins.json` แล้วสแกน `<pluginRoot>/commands/*.md`\n\nการจัดลำดับเป็นไปตามลำดับการ iterate ของ registry และลำดับ entry ต่อ plugin จากข้อมูล JSON นั้น ไม่มีขั้นตอนการเรียงลำดับเพิ่มเติม\n\n## 3) การ Materialize ไปยัง `FileSlashCommand` ที่ใช้งานจริง\n\n`loadSlashCommands()` ใน `src/extensibility/slash-commands.ts` แปลง capability item เป็น object `FileSlashCommand` ที่ใช้ตอนป้อน prompt\n\nสำหรับแต่ละคำสั่ง:\n\n1. parse frontmatter/body (`parseFrontmatter`)\n2. แหล่งที่มาของคำอธิบาย:\n   - `frontmatter.description` หากมี\n   - มิฉะนั้นจะใช้บรรทัดแรกของ body ที่ไม่ว่างเปล่า (trimmed ไม่เกิน 60 ตัวอักษรพร้อม `...`)\n3. เก็บ body ที่ parse แล้วเป็นเนื้อหา template ที่ดำเนินการได้\n4. คำนวณสตริงแหล่งที่มาสำหรับแสดงผล เช่น `via Claude Code Project`\n\nระดับความรุนแรงของการ parse frontmatter ขึ้นอยู่กับแหล่งที่มา:\n\n- ระดับ `native` -> ข้อผิดพลาดในการ parse เป็น `fatal`\n- ระดับ `user`/`project` -> ข้อผิดพลาดในการ parse เป็น `warn` พร้อม fallback parsing\n\n### คำสั่ง fallback ที่ฝังไว้\n\nหลังจากคำสั่งจากระบบไฟล์/provider จะมีการ append command template ที่ฝังไว้ (`EMBEDDED_COMMAND_TEMPLATES`) หากชื่อของคำสั่งเหล่านั้นยังไม่มีอยู่\n\nชุดที่ฝังไว้ปัจจุบันมาจาก `src/task/commands.ts` และใช้เป็น fallback (`source: \"bundled\"`)\n\n## 4) โหมดโต้ตอบ: แหล่งที่มาของรายการคำสั่ง\n\nโหมดโต้ตอบรวมแหล่งที่มาของคำสั่งหลายแหล่งสำหรับ autocomplete และการกำหนดเส้นทางคำสั่ง\n\nตอน construction จะสร้างรายการคำสั่งที่รอดำเนินการจาก:\n\n- built-in (`BUILTIN_SLASH_COMMANDS` ซึ่งรวมถึงการเติมอาร์กิวเมนต์และ inline hint สำหรับคำสั่งที่เลือก)\n- slash command ที่ลงทะเบียนโดย extension (`extensionRunner.getRegisteredCommands(...)`)\n- คำสั่งกำหนดเองของ TypeScript (`session.customCommands`) ที่ map ไปยัง label ของ slash command\n- คำสั่ง skill เพิ่มเติม (`/skill:<name>`) เมื่อ `skills.enableSkillCommands` เปิดใช้งาน\n\nจากนั้น `init()` จะเรียก `refreshSlashCommandState(...)` เพื่อโหลดคำสั่งจากไฟล์และติดตั้ง `CombinedAutocompleteProvider` หนึ่งตัวที่ประกอบด้วย:\n\n- คำสั่งที่รอดำเนินการข้างต้น\n- คำสั่งจากไฟล์ที่ค้นพบ\n\n`refreshSlashCommandState(...)` ยังอัปเดต `session.setSlashCommands(...)` ด้วย เพื่อให้การขยายผล prompt ใช้ชุดคำสั่งจากไฟล์ที่ค้นพบเดียวกัน\n\n### วงจรชีวิตการรีเฟรช\n\nสถานะ slash command จะถูกรีเฟรช:\n\n- ระหว่างการ init แบบโต้ตอบ\n- หลังจาก `/move` เปลี่ยนไดเรกทอรีการทำงาน (`handleMoveCommand` เรียก `resetCapabilities()` แล้ว `refreshSlashCommandState(newCwd)`)\n\nไม่มี file watcher ต่อเนื่องสำหรับไดเรกทอรีคำสั่ง\n\n### การแสดงผลอื่น ๆ\n\nแดชบอร์ด Extensions ยังโหลด capability `slash-commands` และแสดง entry ของคำสั่งที่ active/shadowed รวมถึงรายการซ้ำที่มีเครื่องหมาย `_shadowed`\n\n## 5) ตำแหน่งใน Prompt Pipeline\n\nลำดับการจัดการ slash ของ `AgentSession.prompt(...)` (เมื่อ `expandPromptTemplates !== false`):\n\n1. **คำสั่ง Extension** (`#tryExecuteExtensionCommand`)  \n   หาก `/name` ตรงกับคำสั่งที่ลงทะเบียนโดย extension handler จะดำเนินการทันทีและ prompt จะส่งคืน\n2. **คำสั่งกำหนดเองของ TypeScript** (`#tryExecuteCustomCommand`)  \n   เฉพาะขอบเขต: หากตรงกัน จะดำเนินการและอาจส่งคืน:\n   - `string` -> แทนที่ข้อความ prompt ด้วยสตริงนั้น\n   - `void/undefined` -> ถือว่าถูกจัดการแล้ว ไม่มี LLM prompt\n3. **Slash command จากไฟล์** (`expandSlashCommand`)  \n   หากข้อความยังคงขึ้นต้นด้วย `/` จะพยายามขยาย markdown command\n4. **Prompt template** (`expandPromptTemplate`)  \n   ใช้หลังจากประมวลผล slash/custom แล้ว\n5. **การส่งมอบ**\n   - idle: prompt จะถูกส่งไปยัง agent ทันที\n   - streaming: prompt จะถูกเข้าคิวเป็น steer/follow-up ตาม `streamingBehavior`\n\nนี่คือเหตุผลที่การขยาย slash command อยู่ก่อนการขยาย prompt-template และเหตุผลที่คำสั่งกำหนดเองสามารถแปลง slash นำหน้าออกก่อนการจับคู่ file-command\n\n## 6) Semantics การขยายสำหรับ slash command จากไฟล์\n\nพฤติกรรมของ `expandSlashCommand(text, fileCommands)`:\n\n- ทำงานเฉพาะเมื่อข้อความขึ้นต้นด้วย `/`\n- parse ชื่อคำสั่งจาก token แรกหลัง `/`\n- parse args จากข้อความที่เหลือผ่าน `parseCommandArgs`\n- ค้นหาการจับคู่ชื่อที่แน่ชัดใน `fileCommands` ที่โหลดแล้ว\n- หากจับคู่ได้ จะใช้:\n  - การแทนที่ตำแหน่ง: `$1`, `$2`, ...\n  - การแทนที่รวม: `$ARGUMENTS` และ `$@`\n  - จากนั้น template rendering ผ่าน `prompt.render` ด้วย `{ args, ARGUMENTS, arguments }`\n- หากไม่จับคู่ได้ จะส่งคืนข้อความต้นฉบับโดยไม่เปลี่ยนแปลง\n\n### ข้อควรระวังของ `parseCommandArgs`\n\nParser เป็น quote-aware splitting แบบง่าย:\n\n- รองรับการ quote แบบ `'single'` และ `\"double\"` เพื่อรักษาช่องว่าง\n- ลบตัวคั่น quote ออก\n- ไม่ implement กฎการ escape ด้วย backslash\n- quote ที่ไม่มีคู่ไม่ถือเป็นข้อผิดพลาด parser จะดำเนินการจนถึงสิ้นสุด\n\n## 7) พฤติกรรมของ `/...` ที่ไม่รู้จัก\n\ninput slash ที่ไม่รู้จัก **จะไม่ถูกปฏิเสธ** โดย core slash logic\n\nหากคำสั่งไม่ถูกจัดการโดย layer ของ extension/custom/file `expandSlashCommand` จะส่งคืนข้อความต้นฉบับ และ prompt `/...` ตามตัวอักษรจะดำเนินต่อผ่านการขยาย prompt-template ปกติและการส่งไปยัง LLM\n\nโหมดโต้ตอบจะจัดการ built-in หลายตัวโดยตรงใน `InputController` แยกต่างหาก (เช่น `/settings`, `/model`, `/mcp`, `/move`, `/exit`) ซึ่งจะถูกใช้ก่อน `session.prompt(...)` ดังนั้นจึงไม่เคยไปถึงการขยาย file-command ในเส้นทางนั้น\n\n## 8) ความแตกต่างในเวลา Streaming เทียบกับ Idle\n\n## เส้นทาง Idle\n\n- `session.prompt(\"/x ...\")` รัน command pipeline และทั้งดำเนินการคำสั่งทันทีหรือส่งข้อความที่ขยายแล้วโดยตรง\n\n## เส้นทาง Streaming (`session.isStreaming === true`)\n\n- `prompt(...)` ยังคงรัน transform ของ extension/custom/file/template ก่อน\n- จากนั้นต้องการ `streamingBehavior`:\n  - `\"steer\"` -> เข้าคิวข้อความ interrupt (`agent.steer`)\n  - `\"followUp\"` -> เข้าคิวข้อความหลัง turn (`agent.followUp`)\n- หากละเว้น `streamingBehavior` prompt จะ throw error\n\n### พฤติกรรม streaming เฉพาะของแต่ละคำสั่งที่สำคัญ\n\n- คำสั่ง Extension จะดำเนินการทันทีแม้ระหว่าง streaming (ไม่เข้าคิวเป็นข้อความ)\n- method helper `steer(...)`/`followUp(...)` จะปฏิเสธคำสั่ง extension (`#throwIfExtensionCommand`) เพื่อหลีกเลี่ยงการเข้าคิวข้อความคำสั่งสำหรับ handler ที่ต้องรันแบบ synchronous\n- การ replay คิว compaction ใช้ `isKnownSlashCommand(...)` เพื่อตัดสินใจว่า entry ที่เข้าคิวควรถูก replay ผ่าน `session.prompt(...)` (สำหรับ slash command ที่รู้จัก) หรือผ่าน method steer/follow-up แบบ raw\n\n## 9) การจัดการข้อผิดพลาดและพื้นผิวของความล้มเหลว\n\n- ความล้มเหลวในการโหลด provider จะถูก isolate รีจิสทรีจะรวบรวมคำเตือนและดำเนินต่อกับ provider อื่น\n- slash command item ที่ไม่ถูกต้อง (ไม่มีชื่อ/เส้นทาง/เนื้อหา หรือ level ไม่ถูกต้อง) จะถูกละทิ้งโดยการ validation ของ capability\n- ความล้มเหลวในการ parse frontmatter:\n  - คำสั่ง native: ข้อผิดพลาด parse ที่ fatal จะ bubble ขึ้น\n  - คำสั่งที่ไม่ใช่ native: คำเตือน + fallback key/value parse\n- exception ของ handler ของคำสั่ง extension/custom จะถูก catch และรายงานผ่าน channel ข้อผิดพลาดของ extension (หรือ logger fallback สำหรับคำสั่งกำหนดเองที่ไม่มี extension runner) และถือว่าถูกจัดการแล้ว (ไม่มีการดำเนินการ fallback โดยไม่ตั้งใจ)\n",
	"th/runtime-tools/task-agent-discovery.md": "---\ntitle: การค้นพบและการเลือก Task Agent\ndescription: >-\n  ตรรกะการค้นพบและการเลือก task agent สำหรับการกำหนดเส้นทางงานไปยัง subagent\n  ประเภทเฉพาะทาง\nsidebar:\n  order: 6\n  label: การค้นพบ Task agent\ni18n:\n  sourceHash: 8cf42457c672\n  translator: machine\n---\n\n# การค้นพบและการเลือก Task Agent\n\nเอกสารนี้อธิบายวิธีที่ระบบย่อย task ค้นพบคำจำกัดความของ agent ผสานหลายแหล่งเข้าด้วยกัน และแก้ไข agent ที่ร้องขอในเวลาประมวลผล\n\nเนื้อหาครอบคลุมพฤติกรรม runtime ตามที่นำไปใช้งานในปัจจุบัน รวมถึงลำดับความสำคัญ การจัดการคำจำกัดความที่ไม่ถูกต้อง และข้อจำกัดด้าน spawn/depth ที่อาจทำให้ agent ไม่สามารถใช้งานได้จริง\n\n## ไฟล์การนำไปใช้งาน\n\n- [`src/task/discovery.ts`](../../packages/coding-agent/src/task/discovery.ts)\n- [`src/task/agents.ts`](../../packages/coding-agent/src/task/agents.ts)\n- [`src/task/types.ts`](../../packages/coding-agent/src/task/types.ts)\n- [`src/task/index.ts`](../../packages/coding-agent/src/task/index.ts)\n- [`src/task/commands.ts`](../../packages/coding-agent/src/task/commands.ts)\n- [`src/prompts/agents/task.md`](../../packages/coding-agent/src/prompts/agents/task.md)\n- [`src/prompts/tools/task.md`](../../packages/coding-agent/src/prompts/tools/task.md)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/config.ts`](../../packages/coding-agent/src/config.ts)\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts)\n\n---\n\n## รูปแบบคำจำกัดความของ Agent\n\nTask agent จะถูกทำให้เป็นมาตรฐานเป็น `AgentDefinition` (`src/task/types.ts`):\n\n- `name`, `description`, `systemPrompt` (จำเป็นสำหรับ agent ที่โหลดได้อย่างถูกต้อง)\n- ตัวเลือก `tools`, `spawns`, `model`, `thinkingLevel`, `output`\n- `source`: `\"bundled\" | \"user\" | \"project\"`\n- ตัวเลือก `filePath`\n\nการแยกวิเคราะห์มาจาก frontmatter ผ่าน `parseAgentFields()` (`src/discovery/helpers.ts`):\n\n- ขาด `name` หรือ `description` => ไม่ถูกต้อง (`null`) ผู้เรียกถือว่าเป็นความล้มเหลวในการแยกวิเคราะห์\n- `tools` รับ CSV หรืออาร์เรย์ หากระบุ จะเพิ่ม `submit_result` โดยอัตโนมัติ\n- `spawns` รับ `*`, CSV หรืออาร์เรย์\n- พฤติกรรมความเข้ากันได้ย้อนหลัง: หาก `spawns` ขาดหายแต่ `tools` มี `task` อยู่ `spawns` จะกลายเป็น `*`\n- `output` จะถูกส่งต่อเป็นข้อมูล schema แบบ opaque\n\n## Bundled Agents\n\nBundled agents จะถูกฝังไว้ในเวลา build (`src/task/agents.ts`) โดยใช้การ import ข้อความ\n\n`EMBEDDED_AGENT_DEFS` กำหนด:\n\n- `explore`, `plan`, `designer`, `reviewer` จากไฟล์ prompt\n- `task` และ `quick_task` จาก body ของ `task.md` ที่ใช้ร่วมกัน บวกกับ frontmatter ที่ inject เข้ามา\n\nเส้นทางการโหลด:\n\n1. `loadBundledAgents()` แยกวิเคราะห์ markdown ที่ฝังด้วย `parseAgent(..., \"bundled\", \"fatal\")`\n2. ผลลัพธ์จะถูกแคชไว้ในหน่วยความจำ (`bundledAgentsCache`)\n3. `clearBundledAgentsCache()` ใช้สำหรับรีเซ็ตแคชในการทดสอบเท่านั้น\n\nเนื่องจากการแยกวิเคราะห์ bundled ใช้ `level: \"fatal\"` frontmatter ที่มีรูปแบบผิดจะ throw และอาจทำให้การค้นพบล้มเหลวทั้งหมด\n\n## การค้นพบจากระบบไฟล์และปลั๊กอิน\n\n`discoverAgents(cwd, home)` (`src/task/discovery.ts`) ผสาน agent จากหลายแหล่งก่อนที่จะต่อท้ายคำจำกัดความแบบ bundled\n\n### อินพุตการค้นพบ\n\n1. ไดเรกทอรี agent ของ user config จาก `getConfigDirs(\"agents\", { project: false })`\n2. ไดเรกทอรี agent ของ project ที่ใกล้ที่สุดจาก `findAllNearestProjectConfigDirs(\"agents\", cwd)`\n3. Claude plugin roots (`listClaudePluginRoots(home)`) พร้อม subdirectory `agents/`\n4. Bundled agents (`loadBundledAgents()`)\n\n### ลำดับแหล่งข้อมูลจริง\n\nลำดับของกลุ่มแหล่งข้อมูลมาจาก `getConfigDirs(\"\", { project: false })` ซึ่งได้มาจาก `priorityList` ใน `src/config.ts`:\n\n1. `.xcsh`\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\nสำหรับแต่ละกลุ่มแหล่งข้อมูล ลำดับการค้นพบคือ:\n\n1. ไดเรกทอรี project ที่ใกล้ที่สุดสำหรับแหล่งข้อมูลนั้น (หากพบ)\n2. ไดเรกทอรี user สำหรับแหล่งข้อมูลนั้น\n\nหลังจากไดเรกทอรีกลุ่มแหล่งข้อมูลทั้งหมด ไดเรกทอรี `agents/` ของปลั๊กอินจะถูกต่อท้าย (ปลั๊กอิน project-scope ก่อน จากนั้น user-scope)\n\nBundled agents จะถูกต่อท้ายเป็นลำดับสุดท้าย\n\n### ข้อควรระวังสำคัญ: ความคิดเห็นที่ล้าสมัย vs โค้ดปัจจุบัน\n\nความคิดเห็นในส่วนหัวของ `discovery.ts` ยังคงกล่าวถึง `.pi` และไม่ได้กล่าวถึง `.codex`/`.gemini` ลำดับ runtime จริงขับเคลื่อนโดย `src/config.ts` และปัจจุบันใช้ `.xcsh`, `.claude`, `.codex`, `.gemini`\n\n## กฎการผสานและการชนกัน\n\nการค้นพบใช้การลบรายการซ้ำแบบ first-wins ตามชื่อ `agent.name` ที่แน่นอน:\n\n- `Set<string>` ติดตามชื่อที่เห็นแล้ว\n- Loaded agents จะถูกทำให้แบนในลำดับไดเรกทอรีและเก็บไว้เฉพาะถ้าชื่อยังไม่เคยเห็น\n- Bundled agents จะถูกกรองกับชุดเดียวกันและเพิ่มเฉพาะถ้ายังไม่เคยเห็น\n\nผลที่ตามมา:\n\n- Project จะแทนที่ user สำหรับกลุ่มแหล่งข้อมูลเดียวกัน\n- กลุ่มแหล่งข้อมูลที่มีลำดับความสำคัญสูงกว่าจะแทนที่ลำดับต่ำกว่า (`.xcsh` ก่อน `.claude` เป็นต้น)\n- Agent ที่ไม่ใช่ bundled จะแทนที่ bundled agents ที่มีชื่อเดียวกัน\n- การจับคู่ชื่อคำนึงถึงตัวพิมพ์ใหญ่-เล็ก (`Task` และ `task` แตกต่างกัน)\n- ภายในไดเรกทอรีเดียว ไฟล์ markdown จะถูกอ่านตามลำดับชื่อไฟล์แบบ lexicographic ก่อนการลบรายการซ้ำ\n\n## พฤติกรรมเมื่อ agent ไม่ถูกต้องหรือไม่มีไฟล์\n\nสำหรับแต่ละไดเรกทอรี (`loadAgentsFromDir`):\n\n- ไดเรกทอรีที่อ่านไม่ได้/ไม่มี: ถือว่าว่างเปล่า (`readdir(...).catch(() => [])`)\n- ความล้มเหลวในการอ่านหรือแยกวิเคราะห์ไฟล์: บันทึกคำเตือน ข้ามไฟล์\n- เส้นทางการแยกวิเคราะห์ใช้ `parseAgent(..., level: \"warn\")`\n\nพฤติกรรมความล้มเหลวของ frontmatter มาจาก `parseFrontmatter`:\n\n- ข้อผิดพลาดในการแยกวิเคราะห์ที่ระดับ `warn` จะบันทึกคำเตือน\n- parser จะ fallback ไปที่ parser แบบ `key: value` บรรทัดอย่างง่าย\n- หาก field ที่จำเป็นยังขาดอยู่ `parseAgentFields` จะล้มเหลว จากนั้น `AgentParsingError` จะถูก throw และถูก catch โดยผู้เรียก (ข้ามไฟล์)\n\nผลสุทธิ: ไฟล์ custom agent ที่เสียหายไฟล์เดียวไม่ทำให้การค้นพบไฟล์อื่นหยุด\n\n## การค้นหาและการเลือก Agent\n\nการค้นหาเป็นการค้นหาเชิงเส้นตามชื่อที่แน่นอน:\n\n- `getAgent(agents, name)` => `agents.find(a => a.name === name)`\n\nในการประมวลผล task (`TaskTool.execute`):\n\n1. agents จะถูกค้นพบใหม่ในเวลาเรียกใช้ (`discoverAgents(this.session.cwd)`)\n2. `params.agent` ที่ร้องขอจะถูกแก้ไขผ่าน `getAgent`\n3. หากไม่พบ agent จะส่งคืน tool response ทันที:\n   - `Unknown agent \"...\". Available: ...`\n   - ไม่มีการรัน subprocess\n\n### การค้นพบในเวลาคำอธิบายเทียบกับเวลาประมวลผล\n\n`TaskTool.create()` สร้างคำอธิบาย tool จากผลการค้นพบในเวลาเริ่มต้น (`buildDescription`)\n\n`execute()` ค้นพบ agents ใหม่อีกครั้ง ดังนั้นชุด runtime อาจแตกต่างจากที่แสดงในคำอธิบาย tool ก่อนหน้า หากไฟล์ agent เปลี่ยนแปลงระหว่าง session\n\n## Guardrails ของ Structured-output และลำดับความสำคัญของ Schema\n\nลำดับความสำคัญของ output schema ในเวลา runtime ใน `TaskTool.execute`:\n\n1. `output` ของ agent frontmatter\n2. `params.schema` ของการเรียก task\n3. `outputSchema` ของ parent session\n\n(`effectiveOutputSchema = effectiveAgent.output ?? outputSchema ?? this.session.outputSchema`)\n\nข้อความ guardrail ในเวลา prompt ใน `src/prompts/tools/task.md` เตือนเกี่ยวกับพฤติกรรมที่ไม่ตรงกันสำหรับ structured-output agents (`explore`, `reviewer`): คำสั่งรูปแบบ output ในรูปแบบร้อยแก้วอาจขัดแย้งกับ schema ที่ built-in และสร้าง output เป็น `null`\n\nนี่คือคำแนะนำ ไม่ใช่ตรรกะการตรวจสอบ runtime ใน `discoverAgents`\n\n## การโต้ตอบกับการค้นพบคำสั่ง\n\n`src/task/commands.ts` เป็นโครงสร้างพื้นฐานแบบขนานสำหรับ workflow commands (ไม่ใช่คำจำกัดความ agent) แต่ปฏิบัติตามรูปแบบโดยรวมเดียวกัน:\n\n- ค้นพบจาก capability providers ก่อน\n- ลบรายการซ้ำตามชื่อด้วย first-wins\n- ต่อท้าย bundled commands หากยังไม่เคยเห็น\n- ค้นหาตามชื่อที่แน่นอนผ่าน `getCommand`\n\nใน `src/task/index.ts` command helpers จะถูก re-export พร้อมกับ agent discovery helpers การค้นพบ agent เองไม่ได้ขึ้นอยู่กับการค้นพบคำสั่งในเวลา runtime\n\n## ข้อจำกัดความพร้อมใช้งานที่นอกเหนือจากการค้นพบ\n\nAgent อาจถูกค้นพบได้แต่ยังไม่สามารถใช้งานได้จริงเนื่องจาก guardrails ในการประมวลผล\n\n### นโยบาย Spawn ของ Parent\n\n`TaskTool.execute` ตรวจสอบ `session.getSessionSpawns()`:\n\n- `\"*\"` => อนุญาตทั้งหมด\n- `\"\"` => ปฏิเสธทั้งหมด\n- รายการ CSV => อนุญาตเฉพาะชื่อที่ระบุ\n\nหากถูกปฏิเสธ: ส่งคืน `Cannot spawn '...'. Allowed: ...` ทันที\n\n### การป้องกัน Self-recursion แบบ Blocked ด้วย env\n\n`PI_BLOCKED_AGENT` จะถูกอ่านในเวลาสร้าง tool หากคำขอตรงกัน การประมวลผลจะถูกปฏิเสธพร้อมข้อความป้องกัน recursion\n\n### การ Gate Recursion-depth (ความพร้อมใช้งานของ task tool ภายใน child sessions)\n\nใน `runSubprocess` (`src/task/executor.ts`):\n\n- depth คำนวณจาก `taskDepth`\n- `task.maxRecursionDepth` ควบคุมจุดตัด\n- เมื่อถึง depth สูงสุด:\n  - `task` tool จะถูกลบออกจากรายการ tool ของ child\n  - `spawns` env ของ child จะถูกตั้งค่าเป็นว่างเปล่า\n\nดังนั้นระดับที่ลึกกว่าไม่สามารถ spawn task เพิ่มเติมได้แม้ว่าคำจำกัดความ agent จะมี `spawns` อยู่ก็ตาม\n\n## ข้อควรระวังของโหมด Plan (การนำไปใช้งานปัจจุบัน)\n\n`TaskTool.execute` คำนวณ `effectiveAgent` สำหรับโหมด plan (เพิ่ม plan-mode prompt ไว้ข้างหน้า บังคับใช้ชุด tool แบบอ่านอย่างเดียว ล้าง spawns) แต่ `runSubprocess` ถูกเรียกด้วย `agent` แทนที่จะเป็น `effectiveAgent`\n\nผลที่เกิดขึ้นในปัจจุบัน:\n\n- การ override model / ระดับการคิด / output schema มาจาก `effectiveAgent`\n- system prompt และข้อจำกัด tool/spawn จาก `effectiveAgent` จะไม่ถูกส่งผ่านในเส้นทางการเรียกนี้\n\nนี่เป็นข้อควรระวังในการนำไปใช้งานที่ควรทราบเมื่ออ่านพฤติกรรมที่คาดหวังของโหมด plan\n",
	"th/sessions/compaction.md": "---\ntitle: การบีบอัดและสรุปสาขา\ndescription: การบีบอัดหน้าต่างบริบทและการสร้างสรุปสาขาสำหรับเซสชันที่ยาวนาน\nsidebar:\n  order: 5\n  label: การบีบอัด\ni18n:\n  sourceHash: dae425a900d8\n  translator: machine\n---\n\n# การบีบอัดและสรุปสาขา\n\nการบีบอัดและสรุปสาขาเป็นสองกลไกที่ทำให้เซสชันที่ยาวนานยังคงใช้งานได้โดยไม่สูญเสียบริบทการทำงานก่อนหน้า\n\n- **การบีบอัด** เขียนประวัติเก่าใหม่เป็นสรุปบนสาขาปัจจุบัน\n- **สรุปสาขา** บันทึกบริบทของสาขาที่ถูกละทิ้งระหว่างการนำทางด้วย `/tree`\n\nทั้งสองถูกบันทึกเป็นรายการเซสชันและแปลงกลับเป็นข้อความบริบทผู้ใช้เมื่อสร้างอินพุต LLM ใหม่\n\n## ไฟล์การนำไปใช้งานหลัก\n\n- `src/session/compaction/compaction.ts`\n- `src/session/compaction/branch-summarization.ts`\n- `src/session/compaction/pruning.ts`\n- `src/session/compaction/utils.ts`\n- `src/session/session-manager.ts`\n- `src/session/agent-session.ts`\n- `src/session/messages.ts`\n- `src/extensibility/hooks/types.ts`\n- `src/config/settings-schema.ts`\n\n## โมเดลรายการเซสชัน\n\nการบีบอัดและสรุปสาขาเป็นรายการเซสชันระดับหลัก ไม่ใช่ข้อความ assistant/user ธรรมดา\n\n- `CompactionEntry`\n  - `type: \"compaction\"`\n  - `summary`, `shortSummary` ที่เป็นทางเลือก\n  - `firstKeptEntryId` (ขอบเขตการบีบอัด)\n  - `tokensBefore`\n  - `details`, `preserveData`, `fromExtension` ที่เป็นทางเลือก\n- `BranchSummaryEntry`\n  - `type: \"branch_summary\"`\n  - `fromId`, `summary`\n  - `details`, `fromExtension` ที่เป็นทางเลือก\n\nเมื่อมีการสร้างบริบทใหม่ (`buildSessionContext`):\n\n1. การบีบอัดล่าสุดบนเส้นทางที่ใช้งานอยู่จะถูกแปลงเป็นข้อความ `compactionSummary` หนึ่งรายการ\n2. รายการที่เก็บไว้จาก `firstKeptEntryId` ไปยังจุดบีบอัดจะถูกรวมซ้ำ\n3. รายการที่อยู่ถัดไปบนเส้นทางจะถูกต่อท้าย\n4. รายการ `branch_summary` จะถูกแปลงเป็นข้อความ `branchSummary`\n5. รายการ `custom_message` จะถูกแปลงเป็นข้อความ `custom`\n\nบทบาทกำหนดเองเหล่านั้นจะถูกแปลงเป็นข้อความผู้ใช้ที่ส่งไปยัง LLM ใน `convertToLlm()` โดยใช้เทมเพลตแบบคงที่:\n\n- `prompts/compaction/compaction-summary-context.md`\n- `prompts/compaction/branch-summary-context.md`\n\n## ขั้นตอนการบีบอัด\n\n### ตัวกระตุ้น\n\nการบีบอัดสามารถทำงานได้สามวิธี:\n\n1. **แบบแมนวล**: `/compact [instructions]` เรียก `AgentSession.compact(...)`\n2. **การกู้คืนจากล้นอัตโนมัติ**: หลังจากข้อผิดพลาดของ assistant ที่ตรงกับการล้นของบริบท\n3. **การบีบอัดแบบเกณฑ์อัตโนมัติ**: หลังจากเทิร์นสำเร็จเมื่อบริบทเกินเกณฑ์\n\n### รูปแบบการบีบอัด (ภาพ)\n\n```text\nBefore compaction:\n\n  entry:  0     1     2     3      4     5     6      7      8     9\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┘\n                └────────┬───────┘ └──────────────┬──────────────┘\n               messagesToSummarize            kept messages\n                                   ↑\n                          firstKeptEntryId (entry 4)\n\nAfter compaction (new entry appended):\n\n  entry:  0     1     2     3      4     5     6      7      8     9      10\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┬─────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │ cmp │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┴─────┘\n               └──────────┬──────┘ └──────────────────────┬───────────────────┘\n                 not sent to LLM                    sent to LLM\n                                                         ↑\n                                              starts from firstKeptEntryId\n\nWhat the LLM sees:\n\n  ┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐\n  │ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │\n  └────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘\n       ↑         ↑      └─────────────────┬────────────────┘\n    prompt   from cmp          messages from firstKeptEntryId\n```\n\n### การบีบอัดแบบล้นซ้ำ vs การบีบอัดแบบเกณฑ์\n\nเส้นทางอัตโนมัติสองเส้นทางนี้แตกต่างกันโดยเจตนา:\n\n- **การบีบอัดแบบล้นซ้ำ**\n  - ตัวกระตุ้น: ข้อผิดพลาดของ assistant ในโมเดลปัจจุบันถูกตรวจพบว่าเป็นการล้นของบริบท\n  - ข้อความแสดงข้อผิดพลาดของ assistant ที่ล้มเหลวจะถูกลบออกจากสถานะ agent ที่ใช้งานอยู่ก่อนลองซ้ำ\n  - การบีบอัดอัตโนมัติทำงานด้วย `reason: \"overflow\"` และ `willRetry: true`\n  - เมื่อสำเร็จ agent จะดำเนินการต่ออัตโนมัติ (`agent.continue()`) หลังการบีบอัด\n\n- **การบีบอัดแบบเกณฑ์**\n  - ตัวกระตุ้น: `contextTokens > contextWindow - compaction.reserveTokens`\n  - ทำงานด้วย `reason: \"threshold\"` และ `willRetry: false`\n  - เมื่อสำเร็จ ถ้า `compaction.autoContinue !== false` จะฉีดพรอมต์สังเคราะห์:\n    - `\"Continue if you have next steps.\"`\n\n### การตัดทอนก่อนการบีบอัด\n\nก่อนการตรวจสอบการบีบอัด การตัดทอนผลลัพธ์เครื่องมืออาจทำงาน (`pruneToolOutputs`)\n\nนโยบายการตัดทอนเริ่มต้น:\n\n- ป้องกันโทเคนผลลัพธ์เครื่องมือใหม่ล่าสุด `40_000` รายการ\n- ต้องการประหยัดรวมประมาณการอย่างน้อย `20_000`\n- ไม่ตัดทอนผลลัพธ์เครื่องมือจาก `skill` หรือ `read` เลย\n\nผลลัพธ์เครื่องมือที่ถูกตัดทอนจะถูกแทนที่ด้วย:\n\n- `[Output truncated - N tokens]`\n\nหากการตัดทอนเปลี่ยนรายการ พื้นที่จัดเก็บเซสชันจะถูกเขียนใหม่และสถานะข้อความ agent จะถูกรีเฟรชก่อนการตัดสินใจบีบอัด\n\n### ตรรกะขอบเขตและจุดตัด\n\n`prepareCompaction()` พิจารณาเฉพาะรายการนับจากรายการการบีบอัดล่าสุด (ถ้ามี)\n\n1. ค้นหาดัชนีการบีบอัดก่อนหน้า\n2. คำนวณ `boundaryStart = prevCompactionIndex + 1`\n3. ปรับ `keepRecentTokens` โดยใช้อัตราส่วนการใช้งานที่วัดได้เมื่อมี\n4. รัน `findCutPoint()` บนหน้าต่างขอบเขต\n\nจุดตัดที่ถูกต้องรวมถึง:\n\n- รายการข้อความที่มีบทบาท: `user`, `assistant`, `bashExecution`, `hookMessage`, `branchSummary`, `compactionSummary`\n- รายการ `custom_message`\n- รายการ `branch_summary`\n\nกฎหลัก: ห้ามตัดที่ `toolResult`\n\nหากมีรายการข้อมูลเมตาที่ไม่ใช่ข้อความอยู่ก่อนหน้าจุดตัดทันที (`model_change`, `thinking_level_change`, ป้ายชื่อ ฯลฯ) รายการเหล่านั้นจะถูกดึงเข้าในบริเวณที่เก็บไว้โดยเลื่อนดัชนีตัดไปข้างหลังจนกระทั่งพบข้อความหรือขอบเขตการบีบอัด\n\n### การจัดการเทิร์นแยก\n\nหากจุดตัดไม่ได้อยู่ที่จุดเริ่มต้นเทิร์นผู้ใช้ การบีบอัดจะถือว่าเป็นเทิร์นแยก\n\nการตรวจจับจุดเริ่มต้นเทิร์นถือว่าสิ่งเหล่านี้เป็นขอบเขตเทิร์นผู้ใช้:\n\n- `message.role === \"user\"`\n- `message.role === \"bashExecution\"`\n- รายการ `custom_message`\n- รายการ `branch_summary`\n\nการบีบอัดเทิร์นแยกสร้างสรุปสองรายการ:\n\n1. สรุปประวัติ (`messagesToSummarize`)\n2. สรุปคำนำหน้าเทิร์น (`turnPrefixMessages`)\n\nสรุปที่จัดเก็บสุดท้ายถูกรวมเป็น:\n\n```markdown\n<history summary>\n\n---\n\n**Turn Context (split turn):**\n\n<turn prefix summary>\n```\n\n### การสร้างสรุป\n\n`compact(...)` สร้างสรุปจากข้อความสนทนาที่ซีเรียลไลซ์:\n\n1. แปลงข้อความผ่าน `convertToLlm()`\n2. ซีเรียลไลซ์ด้วย `serializeConversation()`\n3. ห่อใน `<conversation>...</conversation>`\n4. รวม `<previous-summary>...</previous-summary>` ตามต้องการ\n5. ฉีดบริบท hook เป็นรายการ `<additional-context>` ตามต้องการ\n6. ดำเนินการพรอมต์สรุปด้วย `SUMMARIZATION_SYSTEM_PROMPT`\n\nการเลือกพรอมต์:\n\n- การบีบอัดครั้งแรก: `compaction-summary.md`\n- การบีบอัดซ้ำพร้อมสรุปก่อนหน้า: `compaction-update-summary.md`\n- การผ่านครั้งที่สองของเทิร์นแยก: `compaction-turn-prefix.md`\n- สรุป UI สั้น: `compaction-short-summary.md`\n\nโหมดสรุประยะไกล:\n\n- หาก `compaction.remoteEndpoint` ถูกตั้งค่า การบีบอัดจะ POST:\n  - `{ systemPrompt, prompt }`\n- คาดหวัง JSON ที่มีอย่างน้อย `{ summary }`\n\n### บริบทการดำเนินการไฟล์ในสรุป\n\nการบีบอัดติดตามกิจกรรมไฟล์สะสมโดยใช้การเรียกเครื่องมือของ assistant:\n\n- `read(path)` → ชุดที่อ่าน\n- `write(path)` → ชุดที่แก้ไข\n- `edit(path)` → ชุดที่แก้ไข\n\nพฤติกรรมสะสม:\n\n- รวมรายละเอียดการบีบอัดก่อนหน้าเฉพาะเมื่อรายการก่อนหน้าสร้างโดย pi (`fromExtension !== true`)\n- ในเทิร์นแยก รวมการดำเนินการไฟล์คำนำหน้าเทิร์นด้วย\n- `readFiles` ไม่รวมไฟล์ที่ถูกแก้ไขด้วย\n\nข้อความสรุปจะมีแท็กไฟล์ต่อท้ายผ่านเทมเพลตพรอมต์:\n\n```xml\n<read-files>\n...\n</read-files>\n<modified-files>\n...\n</modified-files>\n```\n\n### การบันทึกและโหลดซ้ำ\n\nหลังจากสร้างสรุป (หรือสรุปที่ hook ให้มา) เซสชัน agent จะ:\n\n1. ต่อท้าย `CompactionEntry` ด้วย `appendCompaction(...)`\n2. สร้างบริบทใหม่ผ่าน `buildSessionContext()`\n3. แทนที่ข้อความ agent สดด้วยบริบทที่สร้างใหม่\n4. ส่งเหตุการณ์ hook `session_compact`\n\n## ขั้นตอนการสรุปสาขา\n\nการสรุปสาขาเชื่อมกับการนำทางต้นไม้ ไม่ใช่การล้นของโทเคน\n\n### ตัวกระตุ้น\n\nระหว่าง `navigateTree(...)`:\n\n1. คำนวณรายการที่ถูกละทิ้งจากใบเก่าไปยังบรรพบุรุษร่วมโดยใช้ `collectEntriesForBranchSummary(...)`\n2. หากผู้เรียกร้องขอสรุป (`options.summarize`) ให้สร้างสรุปก่อนสลับใบ\n3. หากมีสรุป ให้แนบไว้ที่เป้าหมายการนำทางโดยใช้ `branchWithSummary(...)`\n\nในทางปฏิบัติ สิ่งนี้มักถูกขับเคลื่อนโดยขั้นตอน `/tree` เมื่อ `branchSummary.enabled` เปิดใช้งานอยู่\n\n### รูปแบบการสลับสาขา (ภาพ)\n\n```text\nTree before navigation:\n\n         ┌─ B ─ C ─ D (old leaf, being abandoned)\n    A ───┤\n         └─ E ─ F (target)\n\nCommon ancestor: A\nEntries to summarize: B, C, D\n\nAfter navigation with summary:\n\n         ┌─ B ─ C ─ D ─ [summary of B,C,D]\n    A ───┤\n         └─ E ─ F (new leaf)\n```\n\n### การเตรียมการและงบประมาณโทเคน\n\n`generateBranchSummary(...)` คำนวณงบประมาณเป็น:\n\n- `tokenBudget = model.contextWindow - branchSummary.reserveTokens`\n\nจากนั้น `prepareBranchEntries(...)` จะ:\n\n1. การผ่านครั้งแรก: รวบรวมการดำเนินการไฟล์สะสมจากรายการที่สรุปทั้งหมด รวมถึงรายละเอียด `branch_summary` ที่สร้างโดย pi ก่อนหน้า\n2. การผ่านครั้งที่สอง: เดินจากใหม่ล่าสุดไปเก่าสุด เพิ่มข้อความจนกว่าจะถึงงบประมาณโทเคน\n3. ให้ความสำคัญกับการรักษาบริบทล่าสุด\n4. อาจยังรวมรายการสรุปขนาดใหญ่ใกล้ขอบงบประมาณเพื่อความต่อเนื่อง\n\nรายการการบีบอัดจะถูกรวมเป็นข้อความ (`compactionSummary`) ระหว่างอินพุตการสรุปสาขา\n\n### การสร้างสรุปและการบันทึก\n\nการสรุปสาขา:\n\n1. แปลงและซีเรียลไลซ์ข้อความที่เลือก\n2. ห่อใน `<conversation>`\n3. ใช้คำแนะนำกำหนดเองหากมี มิฉะนั้น `branch-summary.md`\n4. เรียกโมเดลสรุปด้วย `SUMMARIZATION_SYSTEM_PROMPT`\n5. เติมด้านหน้าด้วย `branch-summary-preamble.md`\n6. ต่อท้ายแท็กการดำเนินการไฟล์\n\nผลลัพธ์จะถูกจัดเก็บเป็น `BranchSummaryEntry` พร้อมรายละเอียดเสริม (`readFiles`, `modifiedFiles`)\n\n## จุดสัมผัสของส่วนขยายและ hook\n\n### `session_before_compact`\n\nHook ก่อนการบีบอัด\n\nสามารถ:\n\n- ยกเลิกการบีบอัด (`{ cancel: true }`)\n- ให้เพย์โหลดการบีบอัดกำหนดเองเต็มรูปแบบ (`{ compaction: CompactionResult }`)\n\n### `session.compacting`\n\nHook การปรับแต่งพรอมต์/บริบทสำหรับการบีบอัดเริ่มต้น\n\nสามารถคืนค่า:\n\n- `prompt` (แทนที่พรอมต์สรุปหลัก)\n- `context` (บรรทัดบริบทเพิ่มเติมที่ฉีดเข้าใน `<additional-context>`)\n- `preserveData` (จัดเก็บบนรายการการบีบอัด)\n\n### `session_compact`\n\nการแจ้งเตือนหลังการบีบอัดพร้อม `compactionEntry` ที่บันทึกไว้และแฟล็ก `fromExtension`\n\n### `session_before_tree`\n\nทำงานบนการนำทางต้นไม้ก่อนการสร้างสรุปสาขาเริ่มต้น\n\nสามารถ:\n\n- ยกเลิกการนำทาง\n- ให้ `{ summary: { summary, details } }` กำหนดเองที่ใช้เมื่อผู้ใช้ร้องขอการสรุป\n\n### `session_tree`\n\nเหตุการณ์หลังการนำทางที่เปิดเผยใบใหม่/เก่าและรายการสรุปเสริม\n\n## พฤติกรรมรันไทม์และความหมายของความล้มเหลว\n\n- การบีบอัดแบบแมนวลจะยกเลิกการดำเนินการ agent ปัจจุบันก่อน\n- `abortCompaction()` ยกเลิกทั้งตัวควบคุมการบีบอัดแบบแมนวลและอัตโนมัติ\n- การบีบอัดอัตโนมัติส่งเหตุการณ์เซสชันเริ่ม/สิ้นสุดสำหรับการอัปเดต UI/สถานะ\n- การบีบอัดอัตโนมัติสามารถลองผู้สมัครโมเดลหลายรายและลองซ้ำความล้มเหลวชั่วคราว\n- ข้อผิดพลาดการล้นถูกแยกออกจากเส้นทางลองซ้ำทั่วไปเพราะจัดการโดยการบีบอัด\n- หากการบีบอัดอัตโนมัติล้มเหลว:\n  - เส้นทางการล้นส่ง `Context overflow recovery failed: ...`\n  - เส้นทางเกณฑ์ส่ง `Auto-compaction failed: ...`\n- การสรุปสาขาสามารถยกเลิกได้ผ่านสัญญาณยกเลิก (เช่น Escape) คืนค่าผลลัพธ์การนำทางที่ยกเลิก/ยุติ\n\n## การตั้งค่าและค่าเริ่มต้น\n\nจาก `settings-schema.ts`:\n\n- `compaction.enabled` = `true`\n- `compaction.reserveTokens` = `16384`\n- `compaction.keepRecentTokens` = `20000`\n- `compaction.autoContinue` = `true`\n- `compaction.remoteEndpoint` = `undefined`\n- `branchSummary.enabled` = `false`\n- `branchSummary.reserveTokens` = `16384`\n\nค่าเหล่านี้ถูกใช้ในรันไทม์โดย `AgentSession` และโมดูลการบีบอัด/สรุปสาขา\n",
	"th/sessions/handoff-generation-pipeline.md": "---\ntitle: ไปป์ไลน์การสร้าง Handoff\ndescription: >-\n  ไปป์ไลน์การสร้าง Handoff\n  สำหรับการสร้างสรุปเซสชันแบบพกพาเพื่อการทำงานร่วมกันของทีม\nsidebar:\n  order: 8\n  label: ไปป์ไลน์ Handoff\ni18n:\n  sourceHash: 03666084b5ac\n  translator: machine\n---\n\n# ไปป์ไลน์การสร้าง `/handoff`\n\nเอกสารนี้อธิบายวิธีที่ coding-agent ดำเนินการ `/handoff` ในปัจจุบัน: เส้นทางการเรียกใช้งาน การสร้างพรอมต์ การจับผลลัพธ์ที่สมบูรณ์ การสลับเซสชัน และการฉีดบริบทใหม่\n\n## ขอบเขต\n\nครอบคลุม:\n\n- การส่งคำสั่ง `/handoff` แบบโต้ตอบ\n- วงจรชีวิตและการเปลี่ยนสถานะของ `AgentSession.handoff()`\n- วิธีที่ผลลัพธ์ของ handoff ถูกจับจากเอาต์พุตของ assistant\n- วิธีที่เซสชันเก่า/ใหม่จัดเก็บข้อมูล handoff แตกต่างกัน\n- พฤติกรรม UI สำหรับความสำเร็จ การยกเลิก และความล้มเหลว\n\nไม่ครอบคลุม:\n\n- การนำทาง tree ทั่วไป/โครงสร้างภายใน branch\n- คำสั่งเซสชันที่ไม่ใช่ handoff (`/new`, `/fork`, `/resume`)\n\n## ไฟล์การดำเนินการ\n\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n\n## เส้นทางการเรียกใช้งาน\n\n1. `/handoff` ถูกประกาศในข้อมูลเมตาของ slash command ที่มีอยู่ (`slash-commands.ts`) พร้อมคำ힌트แบบ inline ที่เป็นตัวเลือก: `[focus instructions]`\n2. ในการจัดการอินพุตแบบโต้ตอบ (`InputController`) ข้อความที่ส่งซึ่งตรงกับ `/handoff` หรือ `/handoff ...` จะถูกดักจับก่อนการส่งพรอมต์ปกติ\n3. editor จะถูกล้างและเรียกใช้ `handleHandoffCommand(customInstructions?)`\n4. `CommandController.handleHandoffCommand` ดำเนินการตรวจสอบเบื้องต้นโดยใช้รายการปัจจุบัน:\n   - นับรายการ `type === \"message\"`\n   - หากมี `< 2` รายการ จะแสดงคำเตือน: `Nothing to hand off (no messages yet)` และคืนค่า\n\nการตรวจสอบเนื้อหาขั้นต่ำแบบเดียวกันมีอยู่อีกครั้งใน `AgentSession.handoff()` และจะโยนข้อผิดพลาดหากถูกละเมิด ซึ่งซ้ำซ้อนความปลอดภัยทั้งในชั้น UI และชั้นเซสชัน\n\n## วงจรชีวิตแบบ end-to-end\n\n### 1) เริ่มการสร้าง handoff\n\n`AgentSession.handoff(customInstructions?)`:\n\n- อ่านรายการ branch ปัจจุบัน (`sessionManager.getBranch()`)\n- ตรวจสอบจำนวนข้อความขั้นต่ำ (`>= 2`)\n- สร้าง `#handoffAbortController`\n- สร้างพรอมต์แบบ inline ที่กำหนดไว้ล่วงหน้า เพื่อขอเอกสาร handoff ที่มีโครงสร้าง (`Goal`, `Constraints & Preferences`, `Progress`, `Key Decisions`, `Critical Context`, `Next Steps`)\n- เพิ่ม `Additional focus: ...` หากมีคำสั่งที่กำหนดเองให้ไว้\n\nพรอมต์ถูกส่งผ่าน:\n\n```ts\nawait this.prompt(handoffPrompt, { expandPromptTemplates: false });\n```\n\n`expandPromptTemplates: false` ป้องกันการขยาย slash/prompt-template ของ payload คำสั่งภายในนี้\n\n### 2) จับผลลัพธ์ที่สมบูรณ์\n\nก่อนส่งพรอมต์ `handoff()` สมัครรับเหตุการณ์เซสชันและรอ `agent_end`\n\nเมื่อ `agent_end` เกิดขึ้น จะดึงข้อความ handoff จากสถานะ agent โดยสแกนย้อนกลับหาข้อความ `assistant` ล่าสุด จากนั้นต่อบล็อก `content` ทั้งหมดที่ `type === \"text\"` ด้วย `\\n`\n\nข้อสมมติสำคัญในการดึงข้อมูล:\n\n- ใช้เฉพาะบล็อกข้อความเท่านั้น เนื้อหาที่ไม่ใช่ข้อความจะถูกละเว้น\n- สมมติว่าข้อความ assistant ล่าสุดสอดคล้องกับการสร้าง handoff\n- ไม่แยกวิเคราะห์ส่วน markdown หรือตรวจสอบความสอดคล้องของรูปแบบ\n- หากเอาต์พุตของ assistant ไม่มีบล็อกข้อความ handoff จะถือว่าหายไป\n\n### 3) การตรวจสอบการยกเลิก\n\n`handoff()` คืนค่า `undefined` เมื่อเงื่อนไขใดเงื่อนไขหนึ่งเป็นจริง:\n\n- ไม่มีข้อความ handoff ที่จับได้ หรือ\n- `#handoffAbortController.signal.aborted` เป็น true\n\nเมื่อดำเนินการเสร็จ จะล้าง `#handoffAbortController` ใน `finally` เสมอ\n\n### 4) การสร้างเซสชันใหม่\n\nหากมีข้อความที่จับได้และไม่ถูกยกเลิก:\n\n1. ล้าง writer ของเซสชันปัจจุบัน (`sessionManager.flush()`)\n2. เริ่มเซสชันใหม่ (`sessionManager.newSession()`)\n3. รีเซ็ตสถานะ agent ในหน่วยความจำ (`agent.reset()`)\n4. ผูก `agent.sessionId` ใหม่กับ session id ใหม่\n5. ล้างอาร์เรย์บริบทที่อยู่ในคิว (`#steeringMessages`, `#followUpMessages`, `#pendingNextTurnMessages`)\n6. รีเซ็ตตัวนับการเตือน todo\n\n`newSession()` สร้างส่วนหัวใหม่และรายการว่างเปล่า (รีเซ็ต leaf เป็น `null`) ในเส้นทาง handoff ไม่มีการส่ง `parentSession`\n\n### 5) การฉีดบริบท handoff\n\nเอกสาร handoff ที่สร้างขึ้นจะถูกห่อและต่อท้ายเซสชันใหม่เป็นรายการ `custom_message`:\n\n```text\n<handoff-context>\n...handoff text...\n</handoff-context>\n\nThe above is a handoff document from a previous session. Use this context to continue the work seamlessly.\n```\n\nการเรียกใช้การแทรก:\n\n```ts\nthis.sessionManager.appendCustomMessageEntry(\"handoff\", handoffContent, true);\n```\n\nความหมาย:\n\n- `customType`: `\"handoff\"`\n- `display`: `true` (มองเห็นได้ใน TUI rebuild)\n- ประเภทรายการ: `custom_message` (มีส่วนร่วมใน LLM context)\n\n### 6) สร้างบริบท agent ที่ใช้งานอยู่ใหม่\n\nหลังจากการฉีด:\n\n1. `sessionManager.buildSessionContext()` แก้ไขรายการข้อความสำหรับ leaf ปัจจุบัน\n2. `agent.replaceMessages(sessionContext.messages)` ทำให้ข้อความ handoff ที่ฉีดเข้ามาเป็น active context\n3. เมธอดคืนค่า `{ document: handoffText }`\n\nณ จุดนี้ active LLM context ในเซสชันใหม่มีข้อความ handoff ที่ฉีดเข้ามา ไม่ใช่ transcript เก่า\n\n## โมเดลการคงอยู่: เซสชันเก่า vs เซสชันใหม่\n\n### เซสชันเก่า\n\nระหว่างการสร้าง การคงอยู่ของข้อความปกติยังคงทำงาน การตอบสนอง handoff ของ assistant จะถูกคงอยู่เป็นรายการ `message` ปกติใน `message_end`\n\nผลลัพธ์: เซสชันต้นฉบับมี handoff ที่สร้างขึ้นและมองเห็นได้เป็นส่วนหนึ่งของ transcript ประวัติ\n\n### เซสชันใหม่\n\nหลังจากรีเซ็ตเซสชัน handoff จะถูกคงอยู่เป็น `custom_message` พร้อม `customType: \"handoff\"`\n\n`buildSessionContext()` แปลงรายการนี้เป็นข้อความบริบท custom/user ในรันไทม์ผ่าน `createCustomMessage(...)` ดังนั้นจึงถูกรวมไว้ในพรอมต์ในอนาคตจากเซสชันใหม่\n\n## พฤติกรรม Controller/UI\n\nพฤติกรรมของ `CommandController.handleHandoffCommand`:\n\n- เรียกใช้ `await session.handoff(customInstructions)`\n- หากผลลัพธ์เป็น `undefined`: `showError(\"Handoff cancelled\")`\n- เมื่อสำเร็จ:\n  - `rebuildChatFromMessages()` (โหลดบริบทเซสชันใหม่ รวมถึง handoff ที่ฉีดเข้ามา)\n  - ทำให้ status line และ editor top border ไม่ถูกต้อง\n  - โหลด todo ใหม่\n  - ต่อท้ายบรรทัดแชทที่สำเร็จ: `New session started with handoff context`\n- เมื่อเกิดข้อยกเว้น:\n  - หากข้อความเป็น `\"Handoff cancelled\"` หรือชื่อ error เป็น `AbortError`: `showError(\"Handoff cancelled\")`\n  - มิฉะนั้น: `showError(\"Handoff failed: <message>\")`\n- ขอการ render เมื่อสิ้นสุด\n\n## ความหมายของการยกเลิก (พฤติกรรมปัจจุบัน)\n\n### พื้นฐานการยกเลิกระดับเซสชัน\n\n`AgentSession` เปิดเผย:\n\n- `abortHandoff()` → ยกเลิก `#handoffAbortController`\n- `isGeneratingHandoff` → เป็น true ในขณะที่ controller มีอยู่\n\nเมื่อใช้เส้นทางการยกเลิกนี้ subscriber ของ handoff จะปฏิเสธด้วย `Error(\"Handoff cancelled\")` และ command controller จะแมปไปยัง UI การยกเลิก\n\n### ข้อจำกัดของเส้นทาง `/handoff` แบบโต้ตอบ\n\nในการเชื่อมต่อ interactive controller ปัจจุบัน `/handoff` ไม่ได้ติดตั้ง handler Escape เฉพาะที่เรียก `abortHandoff()` (ต่างจากเส้นทาง compaction/branch-summary ที่แทนที่ `editor.onEscape` ชั่วคราว)\n\nผลกระทบในทางปฏิบัติ:\n\n- มีการรองรับการยกเลิกระดับเซสชัน แต่ไม่มี keybinding hook เฉพาะสำหรับ handoff ในเส้นทางคำสั่ง `/handoff`\n- การขัดจังหวะโดยผู้ใช้อาจยังคงเกิดขึ้นผ่านเส้นทางการยกเลิก agent ที่กว้างขึ้น แต่นั่นไม่ใช่ช่องทางการยกเลิกที่ชัดเจนแบบเดียวกับที่ใช้โดย `abortHandoff()`\n\n## Handoff ที่ถูกยกเลิก vs ล้มเหลว\n\nการจำแนกประเภท UI ปัจจุบัน:\n\n- **ถูกยกเลิก/ยกเลิกแล้ว**\n  - เส้นทาง `abortHandoff()` เรียกใช้ `\"Handoff cancelled\"` หรือ\n  - โยน `AbortError`\n  - UI แสดง `Handoff cancelled`\n\n- **ล้มเหลว**\n  - ข้อผิดพลาดอื่น ๆ ที่โยนจาก `handoff()` / prompt pipeline (ข้อผิดพลาดการตรวจสอบ model/API, ข้อยกเว้นในรันไทม์ ฯลฯ)\n  - UI แสดง `Handoff failed: ...`\n\nรายละเอียดเพิ่มเติม: หากการสร้างเสร็จสมบูรณ์แต่ไม่มีข้อความที่ดึงได้ `handoff()` จะคืนค่า `undefined` และ controller ในปัจจุบันรายงานว่า **ถูกยกเลิก** ไม่ใช่ **ล้มเหลว**\n\n## การป้องกันเซสชันสั้นและเนื้อหาขั้นต่ำ\n\nการป้องกันสองชั้นป้องกัน handoff ที่มีสัญญาณน้อย:\n\n- ชั้น UI (`handleHandoffCommand`): แสดงคำเตือนและคืนค่าก่อนกำหนดสำหรับรายการข้อความ `< 2`\n- ชั้นเซสชัน (`handoff()`): โยนเงื่อนไขเดียวกันเป็นข้อผิดพลาด\n\nซึ่งหลีกเลี่ยงการสร้างเซสชันใหม่ด้วยบริบท handoff ที่ว่างเปล่า/ใกล้เปล่า\n\n## สรุปการเปลี่ยนสถานะ\n\nลำดับสถานะระดับสูง:\n\n1. slash command แบบโต้ตอบถูกดักจับ\n2. การตรวจสอบจำนวนข้อความเบื้องต้น\n3. สร้าง `#handoffAbortController` (`isGeneratingHandoff = true`)\n4. ส่งพรอมต์ handoff ภายใน (มองเห็นได้ในแชทเป็นการสร้าง assistant ปกติ)\n5. เมื่อ `agent_end` ข้อความ assistant ล่าสุดจะถูกดึงออก\n6. หากหายไป/ถูกยกเลิก → คืนค่า `undefined` หรือเส้นทางข้อผิดพลาดการยกเลิก\n7. หากมีอยู่:\n   - ล้างเซสชันเก่า\n   - สร้างเซสชันว่างใหม่\n   - รีเซ็ตคิว/ตัวนับในรันไทม์\n   - ต่อท้าย `custom_message(handoff)`\n   - สร้างและแทนที่ข้อความ agent ที่ใช้งานอยู่\n8. Controller สร้าง chat UI ใหม่และประกาศความสำเร็จ\n9. ล้าง `#handoffAbortController` (`isGeneratingHandoff = false`)\n\n## ข้อสมมติและข้อจำกัดที่ทราบ\n\n- การดึง handoff เป็นการคาดเดา: \"บล็อกข้อความ assistant ล่าสุด\" ไม่มีการตรวจสอบโครงสร้าง\n- ไม่มีการตรวจสอบแบบเข้มงวดว่า markdown ที่สร้างขึ้นเป็นไปตามรูปแบบส่วนที่ขอ\n- ข้อความที่ดึงได้หายไปจะถูกรายงานเป็นการยกเลิกใน UX ของ controller\n- การไหลแบบโต้ตอบของ `/handoff` ในปัจจุบันขาด binding Escape→`abortHandoff()` เฉพาะ\n- ข้อมูลเมตา lineage ของเซสชันใหม่ (`parentSession`) ไม่ได้ถูกตั้งค่าโดยเส้นทางนี้\n",
	"th/sessions/memory.md": "---\ntitle: หน่วยความจำอัตโนมัติ\ndescription: >-\n  ระบบหน่วยความจำอัตโนมัติสำหรับจัดเก็บค่ากำหนดของผู้ใช้ บริบทของโปรเจกต์\n  และข้อเสนอแนะต่างๆ ข้ามเซสชัน\nsidebar:\n  order: 7\n  label: หน่วยความจำอัตโนมัติ\ni18n:\n  sourceHash: 2aa9f516aa1e\n  translator: machine\n---\n\n# หน่วยความจำอัตโนมัติ\n\nเมื่อเปิดใช้งาน เอเจนต์จะดึงความรู้ที่คงทนจากเซสชันที่ผ่านมาโดยอัตโนมัติ และแทรกสรุปแบบกระชบเข้าในแต่ละเซสชันใหม่ เมื่อเวลาผ่านไป ระบบจะสร้างที่เก็บหน่วยความจำระดับโปรเจกต์ — การตัดสินใจทางเทคนิค เวิร์กโฟลว์ที่ทำซ้ำ ข้อผิดพลาดที่พบบ่อย — ที่ส่งต่อไปได้โดยไม่ต้องดำเนินการด้วยตนเอง\n\nปิดใช้งานโดยค่าเริ่มต้น เปิดใช้งานผ่าน `/settings` หรือ `config.yml`:\n\n```yaml\nmemories:\n  enabled: true\n```\n\n## การใช้งาน\n\n### สิ่งที่ถูกแทรกเข้าไป\n\nเมื่อเริ่มเซสชัน หากมีสรุปหน่วยความจำสำหรับโปรเจกต์ปัจจุบัน ระบบจะแทรกเข้าใน system prompt เป็นบล็อก **Memory Guidance** เอเจนต์จะได้รับคำสั่งให้:\n\n- ถือว่าหน่วยความจำเป็นบริบทแบบฮิวริสติก — มีประโยชน์สำหรับกระบวนการและการตัดสินใจก่อนหน้า แต่ไม่ใช่แหล่งอ้างอิงที่เชื่อถือได้สำหรับสถานะ repo ปัจจุบัน\n- อ้างอิงเส้นทาง memory artifact เมื่อหน่วยความจำเปลี่ยนแปลงแผน และจับคู่กับหลักฐานจาก repo ปัจจุบันก่อนดำเนินการ\n- ให้ความสำคัญกับสถานะ repo และคำสั่งของผู้ใช้เมื่อขัดแย้งกับหน่วยความจำ ถือว่าหน่วยความจำที่ขัดแย้งเป็นข้อมูลที่ล้าสมัย\n\n### การอ่าน memory artifacts\n\nเอเจนต์สามารถอ่านไฟล์หน่วยความจำได้โดยตรงโดยใช้ URL `memory://` กับเครื่องมือ `read`:\n\n| URL | เนื้อหา |\n|---|---|\n| `memory://root` | สรุปแบบกระชับที่แทรกเมื่อเริ่มต้น |\n| `memory://root/MEMORY.md` | เอกสารหน่วยความจำระยะยาวฉบับเต็ม |\n| `memory://root/skills/<name>/SKILL.md` | playbook ทักษะที่สร้างขึ้น |\n\n### คำสั่ง slash `/memory`\n\n| คำสั่งย่อย | ผลลัพธ์ |\n|---|---|\n| `view` | แสดง payload ของหน่วยความจำที่แทรกในปัจจุบัน |\n| `clear` / `reset` | ลบข้อมูลหน่วยความจำทั้งหมดและ artifacts ที่สร้างขึ้น |\n| `enqueue` / `rebuild` | บังคับให้การรวมรวมทำงานในการเริ่มต้นครั้งถัดไป |\n\n## วิธีการทำงาน\n\nหน่วยความจำถูกสร้างขึ้นโดยไปป์ไลน์เบื้องหลังที่ทำงานเมื่อเริ่มต้นหรือถูกกระตุ้นด้วยตนเองผ่านคำสั่ง slash\n\n**เฟส 1 — การดึงข้อมูลต่อเซสชัน:** สำหรับแต่ละเซสชันที่ผ่านมาที่มีการเปลี่ยนแปลงตั้งแต่ครั้งสุดท้ายที่ถูกประมวลผล โมเดลจะอ่านประวัติเซสชันและดึงสัญญาณที่คงทน: การตัดสินใจทางเทคนิค ข้อจำกัด ความล้มเหลวที่แก้ไขแล้ว เวิร์กโฟลว์ที่ทำซ้ำ เซสชันที่ใหม่เกินไป เก่าเกินไป หรือกำลังใช้งานอยู่จะถูกข้าม การดึงข้อมูลแต่ละครั้งจะสร้างบล็อกหน่วยความจำดิบและสรุปสั้นสำหรับเซสชันนั้น\n\n**เฟส 2 — การรวมรวม:** หลังจากการดึงข้อมูล โมเดลจะทำการประมวลผลครั้งที่สองโดยอ่านข้อมูลที่ดึงจากทุกเซสชันและสร้างผลลัพธ์สามรายการที่เขียนลงดิสก์:\n\n- `MEMORY.md` — เอกสารหน่วยความจำระยะยาวที่ผ่านการคัดสรร\n- `memory_summary.md` — ข้อความกระชับที่แทรกเมื่อเริ่มเซสชัน\n- `skills/` — playbook เชิงกระบวนการที่ใช้ซ้ำได้ แต่ละรายการอยู่ในไดเรกทอรีย่อยของตัวเอง\n\nเฟส 2 ใช้ lease เพื่อป้องกันการทำงานซ้ำเมื่อหลายกระบวนการเริ่มต้นพร้อมกัน ไดเรกทอรีทักษะที่ล้าสมัยจากการทำงานก่อนหน้าจะถูกตัดออกโดยอัตโนมัติ\n\nผลลัพธ์ทั้งหมดจะถูกสแกนหาข้อมูลลับก่อนเขียนลงดิสก์\n\n### พฤติกรรมการดึงข้อมูล\n\nพฤติกรรมการดึงข้อมูลหน่วยความจำและการรวมรวมถูกขับเคลื่อนทั้งหมดโดยไฟล์ prompt แบบคงที่ใน `src/prompts/memories/`\n\n| ไฟล์ | วัตถุประสงค์ | ตัวแปร |\n|---|---|---|\n| `stage_one_system.md` | System prompt สำหรับการดึงข้อมูลต่อเซสชัน | — |\n| `stage_one_input.md` | เทมเพลต user-turn ที่ครอบเนื้อหาเซสชัน | `{{thread_id}}`, `{{response_items_json}}` |\n| `consolidation.md` | Prompt สำหรับการรวมรวมข้ามเซสชัน | `{{raw_memories}}`, `{{rollout_summaries}}` |\n| `read_path.md` | Memory guidance ที่แทรกเข้าในเซสชันที่กำลังทำงาน | `{{memory_summary}}` |\n\n### การเลือกโมเดล\n\nหน่วยความจำใช้ระบบ model role\n\n| เฟส | บทบาท | วัตถุประสงค์ |\n|---|---|---|\n| เฟส 1 (การดึงข้อมูล) | `default` | การดึงความรู้ต่อเซสชัน |\n| เฟส 2 (การรวมรวม) | `smol` | การสังเคราะห์ข้ามเซสชัน |\n\nหาก `smol` ไม่ได้ถูกกำหนดค่า เฟส 2 จะ fallback ไปใช้บทบาท `default`\n\n## การกำหนดค่า\n\n| การตั้งค่า | ค่าเริ่มต้น | คำอธิบาย |\n|---|---|---|\n| `memories.enabled` | `false` | สวิตช์หลัก |\n| `memories.maxRolloutAgeDays` | `30` | เซสชันที่เก่ากว่านี้จะไม่ถูกประมวลผล |\n| `memories.minRolloutIdleHours` | `12` | เซสชันที่ใช้งานล่าสุดกว่านี้จะถูกข้าม |\n| `memories.maxRolloutsPerStartup` | `64` | จำนวนสูงสุดของเซสชันที่ประมวลผลในการเริ่มต้นครั้งเดียว |\n| `memories.summaryInjectionTokenLimit` | `5000` | จำนวน token สูงสุดของสรุปที่แทรกเข้าใน system prompt |\n\nตัวปรับค่าเพิ่มเติม (concurrency, ระยะเวลา lease, token budgets) มีอยู่ใน config สำหรับการใช้งานขั้นสูง\n\n## ไฟล์สำคัญ\n\n- `src/memories/index.ts` — การจัดการไปป์ไลน์ การแทรก การจัดการคำสั่ง slash\n- `src/memories/storage.ts` — คิวงานและ thread registry ที่ใช้ SQLite เป็นฐาน\n- `src/prompts/memories/` — เทมเพลต prompt ของหน่วยความจำ\n- `src/internal-urls/memory-protocol.ts` — ตัวจัดการ URL `memory://`\n",
	"th/sessions/non-compaction-retry-policy.md": "---\ntitle: นโยบายการลองใหม่อัตโนมัติแบบไม่บีบอัด\ndescription: >-\n  นโยบายการลองใหม่อัตโนมัติสำหรับความล้มเหลวของ API\n  ชั่วคราวที่อยู่นอกเส้นทางการบีบอัด\nsidebar:\n  order: 6\n  label: นโยบายการลองใหม่\ni18n:\n  sourceHash: 8999a0258dd8\n  translator: machine\n---\n\n# นโยบายการลองใหม่อัตโนมัติแบบไม่บีบอัด\n\nเอกสารนี้อธิบายเส้นทางการลองใหม่เมื่อเกิดข้อผิดพลาด API มาตรฐานใน `AgentSession`\n\nโดยไม่ครอบคลุมการกู้คืนเมื่อบริบทล้นผ่านการบีบอัดอัตโนมัติ การล้นบริบทจะถูกจัดการด้วยลอจิกการบีบอัดและมีเอกสารแยกต่างหากใน [`compaction.md`](./compaction.md)\n\n## ไฟล์การดำเนินการ\n\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/config/settings-schema.ts`](../../packages/coding-agent/src/config/settings-schema.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts)\n- [`../src/modes/rpc/rpc-client.ts`](../../packages/coding-agent/src/modes/rpc/rpc-client.ts)\n- [`../src/modes/rpc/rpc-types.ts`](../../packages/coding-agent/src/modes/rpc/rpc-types.ts)\n\n## ขอบเขตและความแตกต่างจากการบีบอัด\n\nการลองใหม่และการบีบอัดถูกตรวจสอบจากเส้นทาง `agent_end` เดียวกัน แต่ถูกแยกออกจากกันโดยตั้งใจ:\n\n1. `agent_end` ตรวจสอบข้อความของ assistant ล่าสุด\n2. `#isRetryableError(...)` ทำงานก่อน\n3. หากเริ่มการลองใหม่ การตรวจสอบการบีบอัดจะถูกข้ามสำหรับรอบนั้น\n4. ข้อผิดพลาดที่บริบทล้นถูกยกเว้นอย่างเด็ดขาดจากการจำแนกประเภทการลองใหม่ (`isContextOverflow(...)` ตัดวงจรการลองใหม่)\n5. การล้นบริบทจึงตกไปยัง `#checkCompaction(...)` แทนที่จะเป็นการลองใหม่มาตรฐาน\n\nสรุป: ความล้มเหลวประเภทโหลดเกิน/อัตรา/เซิร์ฟเวอร์/เครือข่ายใช้นโยบายการลองใหม่นี้ ส่วนการล้นหน้าต่างบริบทใช้การกู้คืนด้วยการบีบอัด\n\n## การจำแนกประเภทการลองใหม่\n\n`#isRetryableError(...)` ต้องการเงื่อนไขทั้งหมดต่อไปนี้:\n\n- assistant `stopReason === \"error\"`\n- มี `errorMessage` อยู่\n- ข้อความ**ไม่ใช่**การล้นบริบท\n- `errorMessage` ตรงกับ `#isRetryableErrorMessage(...)`\n\nชุดรูปแบบที่ลองใหม่ได้ในปัจจุบัน (ใช้ regex):\n\n- overloaded\n- rate limit / usage limit / too many requests\n- คลาสเซิร์ฟเวอร์คล้าย HTTP: 429, 500, 502, 503, 504\n- service unavailable / server error / internal error\n- connection error / fetch failed\n- คำว่า `retry delay`\n\nการจำแนกประเภทนี้ใช้การจับคู่รูปแบบสตริง ไม่ใช่โค้ดข้อผิดพลาดของผู้ให้บริการแบบมีโครงสร้าง\n\n## วงจรชีวิตการลองใหม่และการเปลี่ยนสถานะ\n\nสถานะ session ที่ใช้โดยการลองใหม่:\n\n- `#retryAttempt: number` (`0` หมายถึงไม่ได้ทำงาน)\n- `#retryPromise: Promise<void> | undefined` (ติดตามวงจรชีวิตการลองใหม่ที่กำลังดำเนินการ)\n- `#retryResolve: (() => void) | undefined` (แก้ไข `#retryPromise`)\n- `#retryAbortController: AbortController | undefined` (ยกเลิกการรอ backoff)\n\nขั้นตอน (`#handleRetryableError`):\n\n1. อ่านกลุ่มการตั้งค่า `retry`\n2. ถ้า `retry.enabled === false` หยุดทันที (`false` ไม่เริ่มการลองใหม่)\n3. เพิ่มค่า `#retryAttempt`\n4. สร้าง `#retryPromise` ครั้งเดียว (ครั้งแรกในห่วงโซ่)\n5. ถ้าความพยายามเกิน `retry.maxRetries` ส่งเหตุการณ์ความล้มเหลวสุดท้ายและหยุด\n6. คำนวณหน่วงเวลา: `retry.baseDelayMs * 2^(attempt-1)`\n7. สำหรับข้อผิดพลาดขีดจำกัดการใช้งาน แยกวิเคราะห์คำใบ้การลองใหม่และเรียก auth storage (`markUsageLimitReached(...)`); ถ้าการสลับผู้ให้บริการ/โมเดลสำเร็จ บังคับหน่วงเวลาเป็น `0`\n8. ส่ง `auto_retry_start`\n9. ลบข้อความผิดพลาดของ assistant ท้ายสุดออกจากสถานะรันไทม์ของ agent (เก็บไว้ในประวัติ session ที่บันทึกแล้ว)\n10. รอโดยมีการรองรับการยกเลิก\n11. เมื่อตื่น กำหนดเวลา `agent.continue()` ผ่าน `setTimeout(..., 0)`\n\n### สิ่งที่รีเซ็ตตัวนับการลองใหม่\n\n`#retryAttempt` รีเซ็ตเป็น `0` ในกรณีเหล่านี้:\n\n- ข้อความของ assistant ที่สำเร็จแบบไม่มีข้อผิดพลาดและไม่ถูกยกเลิกครั้งแรกหลังจากเริ่มการลองใหม่ (ส่ง `auto_retry_end { success: true }`)\n- การยกเลิกการลองใหม่ระหว่างการรอ backoff\n- เส้นทางที่เกินจำนวนการลองใหม่สูงสุด\n\n`#retryPromise` แก้ไข/ล้างเมื่อห่วงโซ่การลองใหม่สิ้นสุด (สำเร็จ ยกเลิก หรือเกินสูงสุด) ผ่าน `#resolveRetry()`\n\n## ความหมายของ Backoff และจำนวนความพยายามสูงสุด\n\nการตั้งค่า:\n\n- `retry.enabled` (ค่าเริ่มต้น `true`)\n- `retry.maxRetries` (ค่าเริ่มต้น `3`)\n- `retry.baseDelayMs` (ค่าเริ่มต้น `2000`)\n\nการนับความพยายาม:\n\n- ตัวนับความพยายามถูกเพิ่มก่อนการตรวจสอบค่าสูงสุด\n- เหตุการณ์เริ่มต้นใช้ความพยายามปัจจุบัน (เริ่มจาก 1)\n- เหตุการณ์สิ้นสุดที่เกินสูงสุดรายงาน `attempt: this.#retryAttempt - 1` (จำนวนการลองใหม่ครั้งสุดท้าย)\n\nลำดับ backoff ด้วยการตั้งค่าเริ่มต้น:\n\n- ความพยายามที่ 1: 2000 มิลลิวินาที\n- ความพยายามที่ 2: 4000 มิลลิวินาที\n- ความพยายามที่ 3: 8000 มิลลิวินาที\n\nอินพุตการแทนที่หน่วงเวลาถูกใช้เฉพาะในเส้นทางการจัดการขีดจำกัดการใช้งาน และเพื่อมีอิทธิพลต่อการตัดสินใจสลับโมเดล/บัญชีของ auth storage เท่านั้น ในเส้นทางการลองใหม่แบบไม่บีบอัดหลัก backoff ยังคงเป็นการหน่วงเวลาแบบเอกซ์โพเนนเชียลเฉพาะที่ เว้นแต่การสลับสำเร็จ (`delayMs = 0`)\n\n## กลไกการยกเลิก\n\n### การยกเลิกการลองใหม่อย่างชัดเจน\n\n`abortRetry()`:\n\n- ยกเลิก `#retryAbortController` (ถ้ามี)\n- แก้ไข promise การลองใหม่ (`#resolveRetry()`) เพื่อปลดบล็อกผู้ที่รออยู่\n\nถ้าการยกเลิกเกิดขึ้นระหว่างการรอ เส้นทาง catch จะส่ง:\n\n- `auto_retry_end { success: false, finalError: \"Retry cancelled\" }`\n- รีเซ็ต attempt/controller\n\n### การโต้ตอบกับการยกเลิกการทำงานทั่วไป\n\n`abort()` เรียก `abortRetry()` ก่อนที่จะยกเลิก stream ของ agent ที่ทำงานอยู่ ซึ่งรับประกันว่า backoff การลองใหม่จะถูกยกเลิกเมื่อผู้ใช้สั่งยกเลิกทั่วไป\n\n### การโต้ตอบกับ TUI\n\nเมื่อ `auto_retry_start`, EventController:\n\n- สลับตัวจัดการ `Esc` เป็น `session.abortRetry()`\n- แสดงข้อความ loader: `Retrying (attempt/maxAttempts) in Ns… (esc to cancel)`\n\nเมื่อ `auto_retry_end` จะคืนค่าตัวจัดการ `Esc` ก่อนหน้าและล้างสถานะ loader\n\n## พฤติกรรมการสตรีมมิงและการเสร็จสิ้นพรอมต์\n\n`prompt()` สุดท้ายรอที่ `#waitForRetry()` หลังจาก `agent.prompt(...)` ส่งคืน\n\nผลลัพธ์:\n\n- การเรียก prompt จะไม่แก้ไขอย่างสมบูรณ์จนกว่าห่วงโซ่การลองใหม่ที่เริ่มแล้วจะสิ้นสุด (สำเร็จ/ล้มเหลว/ยกเลิก)\n- วงจรชีวิตการลองใหม่เป็นส่วนหนึ่งของขอบเขตการดำเนินการ prompt เชิงตรรกะเดียว\n\nสิ่งนี้ป้องกันผู้เรียกจากการถือว่ารอบที่กำลังลองใหม่เสร็จสิ้นก่อนเวลาอันควร\n\n## การควบคุม: การตั้งค่าและ RPC\n\n### ปุ่มการกำหนดค่า\n\nกำหนดในสคีมาการตั้งค่าภายใต้กลุ่ม retry:\n\n- `retry.enabled`\n- `retry.maxRetries`\n- `retry.baseDelayMs`\n\nการสลับแบบโปรแกรมใน session:\n\n- `setAutoRetryEnabled(enabled)` เขียน `retry.enabled`\n- `autoRetryEnabled` อ่าน `retry.enabled`\n- `isRetrying` รายงานว่า promise วงจรชีวิตการลองใหม่กำลังทำงานอยู่หรือไม่\n\n### การควบคุม RPC\n\nพื้นผิวคำสั่ง RPC:\n\n- `set_auto_retry` → `session.setAutoRetryEnabled(command.enabled)`\n- `abort_retry` → `session.abortRetry()`\n\nตัวช่วย client:\n\n- `RpcClient.setAutoRetry(enabled)`\n- `RpcClient.abortRetry()`\n\nทั้งสองคำสั่งส่งคืนการตอบสนองสำเร็จ รายละเอียดความคืบหน้า/ความล้มเหลวของการลองใหม่มาจากเหตุการณ์ session แบบ stream ไม่ใช่จาก payload การตอบสนองคำสั่ง\n\n## การส่งเหตุการณ์และการแสดงผลความล้มเหลว\n\nเหตุการณ์การลองใหม่ระดับ session:\n\n- `auto_retry_start { attempt, maxAttempts, delayMs, errorMessage }`\n- `auto_retry_end { success, attempt, finalError? }`\n\nการแพร่กระจาย:\n\n- ส่งผ่าน `AgentSession.subscribe(...)`\n- ส่งต่อไปยัง extension runner เป็น extension events\n- ในโหมด RPC ส่งต่อโดยตรงเป็น JSON event objects (`session.subscribe(event => output(event))`)\n- ใน TUI ถูกใช้งานโดย `EventController` สำหรับ UI loader/error\n\nการแสดงผลความล้มเหลวสุดท้าย:\n\n- เมื่อเกินสูงสุดหรือยกเลิก `auto_retry_end.success === false`\n- TUI แสดง: `Retry failed after N attempts: <finalError>`\n- Extensions/hooks ได้รับ `auto_retry_end` ด้วยฟิลด์เดียวกัน\n- ผู้บริโภค RPC ได้รับ event object เดียวกันบน stdout stream\n\n## เงื่อนไขการหยุดถาวร\n\nการลองใหม่จะหยุดและไม่ดำเนินการต่ออัตโนมัติเมื่อเกิดเหตุการณ์ใดๆ เหล่านี้:\n\n- `retry.enabled` เป็น false\n- ข้อผิดพลาดไม่ถูกจำแนกประเภทเป็นการลองใหม่ได้\n- ข้อผิดพลาดคือการล้นบริบท (ส่งต่อไปยังเส้นทางการบีบอัด)\n- เกินจำนวนการลองใหม่สูงสุด\n- ผู้ใช้ยกเลิกการลองใหม่ (`abort_retry` หรือ `Esc` ระหว่าง loader การลองใหม่)\n- การยกเลิกทั่วไป (`abort`) ยกเลิกการลองใหม่ก่อน\n\nห่วงโซ่การลองใหม่ใหม่ยังคงสามารถเริ่มต้นได้ในภายหลังเมื่อเกิดข้อผิดพลาดที่ลองใหม่ได้ในอนาคตหลังจากตัวนับรีเซ็ต\n\n## ข้อควรระวังในการดำเนินงาน\n\n- การจำแนกประเภทใช้การจับคู่ข้อความด้วย regex ไม่ใช้ข้อผิดพลาดที่มีโครงสร้างเฉพาะของผู้ให้บริการที่นี่\n- การลองใหม่จะลบข้อผิดพลาดของ assistant ที่ล้มเหลวออกจาก**บริบทรันไทม์**ก่อนดำเนินการต่อ แต่ประวัติ session ยังคงเก็บรายการข้อผิดพลาดนั้นไว้\n- `RpcSessionState` ในปัจจุบันเปิดเผย `autoCompactionEnabled` แต่ไม่มีฟิลด์ `autoRetryEnabled` ผู้เรียก RPC ต้องติดตามสถานะการสลับของตนเองหรือสอบถามการตั้งค่าผ่าน API อื่น\n",
	"th/sessions/session-operations-export-share-fork-resume.md": "---\ntitle: 'การดำเนินการกับเซสชัน: ส่งออก, ดัมพ์, แชร์, แยกสาขา, กลับมาใช้งานต่อ'\ndescription: 'การดำเนินการกับเซสชันสำหรับการส่งออก, แชร์, แยกสาขา, และกลับมาใช้งานบทสนทนาต่อ'\nsidebar:\n  order: 3\n  label: การดำเนินการ\ni18n:\n  sourceHash: e3c210b29c3e\n  translator: machine\n---\n\n# การดำเนินการกับเซสชัน: export, dump, share, fork, resume/continue\n\nเอกสารนี้อธิบายพฤติกรรมที่ผู้ดำเนินการมองเห็นได้สำหรับการดำเนินการ export/share/fork/resume ของเซสชัน ตามที่ได้นำไปใช้งานในปัจจุบัน\n\n## ไฟล์การนำไปใช้งาน\n\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/export/html/index.ts`](../../packages/coding-agent/src/export/html/index.ts)\n- [`../src/export/custom-share.ts`](../../packages/coding-agent/src/export/custom-share.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n\n## เมทริกซ์การดำเนินการ\n\n| การดำเนินการ | เส้นทางเข้าใช้งาน | การเปลี่ยนแปลงเซสชัน | การสร้าง/สลับไฟล์เซสชัน | ผลลัพธ์ที่ได้ |\n|---|---|---|---|---|\n| `/dump` | คำสั่ง slash แบบโต้ตอบ | ไม่มี | ไม่มี | ข้อความในคลิปบอร์ด |\n| `/export [path]` | คำสั่ง slash แบบโต้ตอบ | ไม่มี | ไม่มี | ไฟล์ HTML |\n| `--export <session.jsonl> [outputPath]` | เส้นทางเร็วเมื่อเริ่ม CLI | ไม่มีการเปลี่ยนแปลงเซสชันขณะทำงาน | ไม่มีเซสชันที่ใช้งานอยู่; อ่านไฟล์เป้าหมาย | ไฟล์ HTML |\n| `/share` | คำสั่ง slash แบบโต้ตอบ | ไม่มี | ไม่มี | HTML ชั่วคราว + URL แชร์/gist |\n| `/fork` | คำสั่ง slash แบบโต้ตอบ | มี (เอกลักษณ์เซสชันที่ใช้งานอยู่เปลี่ยนแปลง) | สร้างไฟล์เซสชันใหม่และสลับเซสชันปัจจุบันไปยังไฟล์นั้น (เฉพาะโหมดถาวร) | คัดลอกไดเรกทอรีอาร์ติแฟกต์ไปยังเนมสเปซเซสชันใหม่เมื่อมีอยู่ |\n| `/resume` | คำสั่ง slash แบบโต้ตอบ | มี (สถานะในหน่วยความจำที่ใช้งานอยู่ถูกแทนที่) | สลับไปยังไฟล์เซสชันที่มีอยู่ที่เลือก | ไม่มี |\n| `--resume` | เริ่ม CLI (ตัวเลือก) | มี หลังจากสร้างเซสชัน | เปิดไฟล์เซสชันที่มีอยู่ที่เลือก | ไม่มี |\n| `--resume <id\\|path>` | เริ่ม CLI | มี หลังจากสร้างเซสชัน | เปิดเซสชันที่มีอยู่; กรณีข้ามโปรเจกต์สามารถแยกสาขาไปยังโปรเจกต์ปัจจุบันได้ | ไม่มี |\n| `--continue` | เริ่ม CLI | มี หลังจากสร้างเซสชัน | เปิดเบรดครัมบ์เทอร์มินัลหรือเซสชันล่าสุด; สร้างอันใหม่หากไม่มี | ไม่มี |\n\n## การส่งออกและดัมพ์\n\n### `/export [outputPath]` (โต้ตอบ)\n\nขั้นตอนการทำงาน:\n\n1. `InputController` ส่งต่อ `/export...` ไปยัง `CommandController.handleExportCommand`\n2. คำสั่งแยกตามช่องว่างและใช้เฉพาะอาร์กิวเมนต์แรกหลัง `/export` เป็น `outputPath`\n3. `AgentSession.exportToHtml()` เรียก `exportSessionToHtml(sessionManager, state, { outputPath, themeName })`\n4. เมื่อสำเร็จ UI จะแสดงเส้นทางและเปิดไฟล์ในเบราว์เซอร์\n\nรายละเอียดพฤติกรรม:\n\n- อาร์กิวเมนต์ `--copy`, `clipboard`, และ `copy` จะถูกปฏิเสธอย่างชัดเจนพร้อมคำเตือนให้ใช้ `/dump` แทน\n- การส่งออกฝังส่วนหัวเซสชัน/รายการ/ใบไม้ บวกกับ `systemPrompt` ปัจจุบันและคำอธิบายเครื่องมือจากสถานะของเอเจนต์\n- ไม่มีการเพิ่มรายการเซสชันในระหว่างการส่งออก\n\nข้อควรระวัง:\n\n- การแยกวิเคราะห์อาร์กิวเมนต์อิงตามช่องว่าง (`text.split(/\\s+/)`) ดังนั้นเส้นทางที่มีเครื่องหมายอัญประกาศซึ่งมีช่องว่างจะไม่ถูกเก็บรักษาเป็นเส้นทางเดียวในเส้นทางคำสั่งนี้\n\n### `--export <inputSessionFile> [outputPath]` (CLI)\n\nขั้นตอนการทำงานใน `main.ts`:\n\n1. จัดการแต่เนิ่น (ก่อนเริ่มต้นแบบโต้ตอบ/เซสชัน)\n2. เรียก `exportFromFile(inputPath, outputPath?)`\n3. `SessionManager.open(inputPath)` โหลดรายการ จากนั้นสร้างและเขียน HTML\n4. กระบวนการพิมพ์ `Exported to: ...` และออก\n\nรายละเอียดพฤติกรรม:\n\n- ไฟล์นำเข้าที่ไม่มีอยู่จะแสดงผลเป็น `File not found: <path>`\n- เส้นทางนี้ไม่สร้าง `AgentSession` และไม่เปลี่ยนแปลงเซสชันที่กำลังทำงานอยู่\n\n### `/dump` (ส่งออกไปยังคลิปบอร์ดแบบโต้ตอบ)\n\nขั้นตอนการทำงาน:\n\n1. `CommandController.handleDumpCommand()` เรียก `session.formatSessionAsText()`\n2. หากได้สตริงว่าง จะรายงาน `No messages to dump yet.`\n3. มิฉะนั้นจะคัดลอกไปยังคลิปบอร์ดผ่าน `copyToClipboard` ของระบบ\n\nเนื้อหาดัมพ์ประกอบด้วย:\n\n- System prompt\n- โมเดลที่ใช้งานอยู่/ระดับการคิด\n- นิยามเครื่องมือ + พารามิเตอร์\n- ข้อความผู้ใช้/ผู้ช่วย\n- บล็อกการคิดและการเรียกใช้เครื่องมือ\n- ผลลัพธ์เครื่องมือและบล็อกการทำงาน (ยกเว้นรายการ bash/python ที่มี `excludeFromContext`)\n- รายการ custom/hook/file mention/branch summary/compaction summary\n\nการดัมพ์ไม่ทำให้เกิดการเปลี่ยนแปลงความคงอยู่ของเซสชัน\n\n## การแชร์\n\n`/share` ทำงานแบบโต้ตอบเท่านั้นและเริ่มต้นด้วยการส่งออกเซสชันปัจจุบันไปยังไฟล์ HTML ชั่วคราวเสมอ\n\n### ขั้นตอนที่ 1: การส่งออกชั่วคราว\n\n- เส้นทางไฟล์ชั่วคราว: `${os.tmpdir()}/${Snowflake.next()}.html`\n- ใช้ `session.exportToHtml(tmpFile)`\n- หากการส่งออกล้มเหลว (โดยเฉพาะเซสชันในหน่วยความจำ) การแชร์จะสิ้นสุดพร้อมข้อผิดพลาด\n\n### ขั้นตอนที่ 2: ตัวจัดการแชร์กำหนดเอง (หากมี)\n\n`loadCustomShare()` ตรวจสอบ `~/.xcsh/agent` สำหรับไฟล์ที่พบก่อนในลำดับนี้:\n\n- `share.ts`\n- `share.js`\n- `share.mjs`\n\nข้อกำหนด:\n\n- โมดูลต้องส่งออกฟังก์ชัน `(htmlPath) => Promise<CustomShareResult | string | undefined>` แบบ default\n\nหากมีอยู่และใช้งานได้:\n\n- UI เข้าสู่สถานะโหลด `Sharing...`\n- การตีความผลลัพธ์ของตัวจัดการ:\n  - string => ถือเป็น URL แสดงและเปิด\n  - object => แสดง `url` และ/หรือ `message`; เปิด `url`\n  - `undefined`/falsy => แสดงข้อความทั่วไป `Session shared`\n- ไฟล์ชั่วคราวจะถูกลบหลังจากเสร็จสิ้น\n\nพฤติกรรม fallback ที่สำคัญ:\n\n- หากมีตัวจัดการกำหนดเองแต่การโหลดล้มเหลว คำสั่งจะเกิดข้อผิดพลาดและส่งคืน\n- หากตัวจัดการกำหนดเองทำงานและเกิดข้อผิดพลาด คำสั่งจะเกิดข้อผิดพลาดและส่งคืน\n- ในทั้งสองกรณีที่ล้มเหลว **จะไม่** ถอยกลับไปใช้ GitHub gist\n- การถอยกลับไปใช้ gist เกิดขึ้นเฉพาะเมื่อไม่พบสคริปต์แชร์กำหนดเองเท่านั้น\n\n### ขั้นตอนที่ 3: การถอยกลับไปใช้ gist เริ่มต้น\n\nเฉพาะเมื่อไม่พบตัวจัดการแชร์กำหนดเอง:\n\n1. ตรวจสอบ `gh auth status`\n2. แสดงโหลด `Creating gist...`\n3. รัน `gh gist create --public=false <tmpFile>`\n4. แยกวิเคราะห์ URL ของ gist ดึง gist id สร้าง preview URL `https://gistpreview.github.io/?<id>`\n5. แสดงทั้ง URL preview และ gist; เปิด preview\n\nความหมายของการยกเลิก/ยกเลิกการดำเนินการในการแชร์:\n\n- โหลดมี hook `onAbort` ที่คืนค่า UI ของตัวแก้ไขและรายงาน `Share cancelled`\n- คำสั่ง `gh gist create` ที่ทำงานอยู่ไม่ได้รับการส่งสัญญาณยกเลิกในเส้นทางโค้ดนี้; การยกเลิกอยู่ในระดับ UI และตรวจสอบหลังจากคำสั่งส่งคืน\n\n## การแยกสาขา (Fork)\n\n`/fork` สร้างเซสชันใหม่จากเซสชันปัจจุบันและสลับเอกลักษณ์ของเซสชันที่ใช้งานอยู่\n\n### เงื่อนไขเบื้องต้นและการตรวจสอบเบื้องต้น\n\n- หากเอเจนต์กำลังสตรีม `/fork` จะถูกปฏิเสธพร้อมคำเตือน\n- ตัวบ่งชี้สถานะ/การโหลด UI จะถูกล้างก่อนดำเนินการ\n\n### ขั้นตอนระดับเซสชัน\n\n`AgentSession.fork()`:\n\n1. ส่ง `session_before_switch` พร้อม `reason: \"fork\"` (สามารถยกเลิกได้)\n2. ล้างการเขียนที่รอดำเนินการ\n3. เรียก `SessionManager.fork()`\n4. คัดลอกไดเรกทอรีอาร์ติแฟกต์จากเนมสเปซเซสชันเก่าไปยังเนมสเปซใหม่ (ทำได้ดีที่สุด; ความล้มเหลวในการคัดลอกที่ไม่ใช่ ENOENT จะถูกบันทึก ไม่ถือว่าร้ายแรง)\n5. อัปเดต `agent.sessionId`\n6. ส่ง `session_switch` พร้อม `reason: \"fork\"`\n\nพฤติกรรมของ `SessionManager.fork()`:\n\n- ต้องการโหมดถาวรและไฟล์เซสชันที่มีอยู่\n- สร้าง session id ใหม่และเส้นทางไฟล์ JSONL ใหม่\n- เขียนส่วนหัวใหม่ด้วย:\n  - `id` ใหม่\n  - timestamp ใหม่\n  - `cwd` ไม่เปลี่ยนแปลง\n  - `parentSession` ตั้งค่าเป็น session id ก่อนหน้า\n- เก็บรายการที่ไม่ใช่ส่วนหัวทั้งหมดไว้ในไฟล์ใหม่โดยไม่เปลี่ยนแปลง\n\n### พฤติกรรมแบบไม่ถาวร\n\n- ตัวจัดการเซสชันในหน่วยความจำส่งคืน `undefined` จาก `fork()`\n- `AgentSession.fork()` ส่งคืน `false`\n- UI รายงาน `Fork failed (session not persisted or cancelled)`\n\n## การกลับมาใช้งานต่อและการต่อเนื่อง\n\n## `/resume` แบบโต้ตอบ\n\nขั้นตอนการทำงาน:\n\n1. เปิดตัวเลือกเซสชันที่เติมข้อมูลผ่าน `SessionManager.list(currentCwd, currentSessionDir)`\n2. เมื่อเลือกแล้ว `SelectorController.handleResumeSession(sessionPath)` เรียก `session.switchSession(sessionPath)`\n3. UI ล้าง/สร้างแชทและ todos ใหม่ จากนั้นรายงาน `Resumed session`\n\nหมายเหตุ:\n\n- ตัวเลือกนี้แสดงเซสชันเฉพาะในขอบเขตไดเรกทอรีเซสชันปัจจุบัน\n- ไม่ใช้การค้นหาข้ามโปรเจกต์แบบทั่วโลก\n\n## CLI `--resume`\n\n### `--resume` (ไม่มีค่า)\n\n- `main.ts` แสดงรายการเซสชันสำหรับ cwd/sessionDir ปัจจุบันและเปิดตัวเลือก\n- เส้นทางที่เลือกจะเปิดด้วย `SessionManager.open(selectedPath)` ก่อนสร้างเซสชัน\n\n### `--resume <value>`\n\nลำดับการแก้ไขของ `createSessionManager()`:\n\n1. หากค่าดูเหมือนเส้นทาง (`/`, `\\`, หรือ `.jsonl`) ให้เปิดโดยตรง\n2. มิฉะนั้นให้ถือเป็นคำนำหน้า id:\n   - ค้นหาในขอบเขตปัจจุบัน (`SessionManager.list(cwd, sessionDir)`)\n   - หากไม่พบและไม่มี `sessionDir` ที่ชัดเจน ให้ค้นหาทั่วโลก (`SessionManager.listAll()`)\n\nพฤติกรรมเมื่อพบ id ข้ามโปรเจกต์:\n\n- หาก cwd ของเซสชันที่พบแตกต่างจาก cwd ปัจจุบัน CLI จะถามว่า:\n  - `Session found in different project ... Fork into current directory? [y/N]`\n- หากตอบใช่: `SessionManager.forkFrom(match.path, cwd, sessionDir)` สร้างไฟล์แยกสาขาใหม่ในเครื่อง\n- หากตอบไม่/ค่าเริ่มต้นแบบไม่ใช่ TTY: คำสั่งจะเกิดข้อผิดพลาด\n\n## CLI `--continue`\n\n`SessionManager.continueRecent(cwd, sessionDir)`:\n\n1. แก้ไขไดเรกทอรีเซสชันสำหรับ cwd ปัจจุบัน\n2. อ่านเบรดครัมบ์ที่กำหนดขอบเขตเทอร์มินัลก่อน\n3. ถอยกลับไปใช้ไฟล์เซสชันที่แก้ไขล่าสุด\n4. เปิดเซสชันที่พบ; หากไม่มี ให้สร้างเซสชันใหม่\n\nนี่คือพฤติกรรมเฉพาะเมื่อเริ่มต้น; ไม่มีคำสั่ง slash `/continue` แบบโต้ตอบ\n\n## วิธีที่การสลับเซสชันเปลี่ยนแปลงสถานะขณะทำงานจริง\n\n`AgentSession.switchSession(sessionPath)` ดำเนินการเปลี่ยนผ่านขณะทำงานที่ใช้โดยการดำเนินการแบบ resume:\n\n1. ส่ง `session_before_switch` พร้อม `reason: \"resume\"` และ `targetSessionFile` (สามารถยกเลิกได้)\n2. ยกเลิกการสมัครรับเหตุการณ์ของเอเจนต์และยกเลิกงานที่กำลังดำเนินอยู่\n3. ล้างข้อความ steering/follow-up/next-turn ที่อยู่ในคิว\n4. ล้างการเขียนตัวจัดการเซสชันปัจจุบัน\n5. `sessionManager.setSessionFile(sessionPath)` และอัปเดต `agent.sessionId`\n6. สร้างบริบทเซสชันจากรายการที่โหลด\n7. ส่ง `session_switch` พร้อม `reason: \"resume\"`\n8. แทนที่ข้อความเอเจนต์จากบริบท\n9. คืนค่าโมเดล (หากมีในรีจิสทรีปัจจุบัน)\n10. คืนค่าหรือเริ่มต้นระดับการคิด\n11. เชื่อมต่อการสมัครรับเหตุการณ์เอเจนต์ใหม่\n\n`switchSession()` เองไม่สร้างไฟล์เซสชันใหม่\n\n## การส่งเหตุการณ์และจุดยกเลิก\n\n### hooks วงจรชีวิตของการสลับ/แยกสาขา\n\nสำหรับ `newSession`, `fork`, และ `switchSession`:\n\n- เหตุการณ์ก่อน: `session_before_switch`\n  - เหตุผล: `new`, `fork`, `resume`\n  - สามารถยกเลิกได้โดยการส่งคืน `{ cancel: true }`\n- เหตุการณ์หลัง: `session_switch`\n  - ชุดเหตุผลเดียวกัน\n  - รวม `previousSessionFile`\n\n`ExtensionRunner.emit()` ส่งคืนก่อนกำหนดเมื่อพบผลลัพธ์ก่อนเหตุการณ์ที่ยกเลิกแรก\n\n### พฤติกรรม `onSession` ของเครื่องมือกำหนดเอง\n\nSDK เชื่อมต่อเหตุการณ์เซสชันส่วนขยายกับ callbacks `onSession` ของเครื่องมือกำหนดเอง:\n\n- `session_switch` -> `onSession({ reason: \"switch\", previousSessionFile })`\n- `session_branch` -> `reason: \"branch\"`\n- `session_start` -> `reason: \"start\"`\n- `session_tree` -> `reason: \"tree\"`\n- `session_shutdown` -> `reason: \"shutdown\"`\n\ncallbacks เหล่านี้มีไว้สำหรับการสังเกตเท่านั้น ไม่สามารถยกเลิกการสลับ/แยกสาขาได้\n\n### พื้นผิวการยกเลิกอื่น ๆ ที่เกี่ยวข้องกับเอกสารนี้\n\n- `/fork` ถูกบล็อกขณะสตรีม (ผู้ใช้ต้องรอ/ยกเลิกการตอบสนองปัจจุบันก่อน)\n- ตัวเลือก `/resume` สามารถยกเลิกได้โดยผู้ใช้ปิดตัวเลือก\n- `--resume <id>` ข้ามโปรเจกต์สามารถยกเลิกได้โดยการปฏิเสธพรอมต์การแยกสาขา\n- `/share` มีเส้นทางยกเลิก UI (`Share cancelled`) สำหรับขั้นตอน gist; ไม่เชื่อมต่อ semantics การฆิลกระบวนการสำหรับ `gh gist create` ในเส้นทางโค้ดนี้\n\n## พฤติกรรมเซสชันแบบไม่ถาวร (ในหน่วยความจำ)\n\nเมื่อตัวจัดการเซสชันสร้างด้วย `SessionManager.inMemory()` (`--no-session`):\n\n- เส้นทางไฟล์เซสชันไม่มีอยู่\n- `/export` และ `/share` ล้มเหลวพร้อมข้อความ `Cannot export in-memory session to HTML` (ส่งต่อไปยัง UI ข้อผิดพลาดคำสั่ง)\n- `/fork` ล้มเหลวเพราะ `SessionManager.fork()` ต้องการความคงอยู่\n- `/dump` ยังคงใช้งานได้เพราะทำการ serialize สถานะเอเจนต์ในหน่วยความจำ\n- semantics ของ resume/continue ใน CLI จะถูกข้ามหากตั้งค่า `--no-session` เพราะการสร้างตัวจัดการส่งคืนแบบในหน่วยความจำทันที\n\n## ข้อควรระวังที่ทราบในการนำไปใช้งาน (ตามโค้ดปัจจุบัน)\n\n- `SelectorController.handleResumeSession()` ไม่ตรวจสอบผลลัพธ์ boolean จาก `session.switchSession(...)`; การสลับที่ถูกยกเลิกโดย hook ยังคงดำเนินผ่านเส้นทาง repaint/status \"Resumed session\" ของ UI ได้\n- ความล้มเหลวของ custom-share ใน `/share` จะไม่ลดระดับลงไปใช้ gist fallback เริ่มต้น; แต่จะสิ้นสุดคำสั่งพร้อมข้อผิดพลาด\n- การแยกวิเคราะห์อาร์กิวเมนต์ของ `/export` เป็นแบบง่ายและไม่เก็บรักษาเส้นทางที่มีเครื่องหมายอัญประกาศซึ่งมีช่องว่าง\n",
	"th/sessions/session-switching-and-recent-listing.md": "---\ntitle: การสลับเซสชันและการแสดงรายการเซสชันล่าสุด\ndescription: กลไกการสลับเซสชันและการแสดงรายการเซสชันล่าสุดพร้อมการค้นหาและการกรอง\nsidebar:\n  order: 4\n  label: การสลับและเซสชันล่าสุด\ni18n:\n  sourceHash: aae56130b508\n  translator: machine\n---\n\n# การสลับเซสชันและการแสดงรายการเซสชันล่าสุด\n\nเอกสารนี้อธิบายวิธีที่ coding-agent ค้นพบเซสชันล่าสุด แก้ไขเป้าหมาย `--resume` แสดงตัวเลือกเซสชัน และสลับเซสชันรันไทม์ที่กำลังใช้งานอยู่\n\nเนื้อหาเน้นไปที่พฤติกรรมการใช้งานปัจจุบัน รวมถึงเส้นทางสำรองและข้อควรระวังต่างๆ\n\n## ไฟล์การใช้งาน\n\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/cli/session-picker.ts`](../../packages/coding-agent/src/cli/session-picker.ts)\n- [`../src/modes/components/session-selector.ts`](../../packages/coding-agent/src/modes/components/session-selector.ts)\n- [`../src/modes/controllers/selector-controller.ts`](../../packages/coding-agent/src/modes/controllers/selector-controller.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n\n## การค้นพบเซสชันล่าสุด\n\n### ขอบเขตไดเรกทอรี\n\n`SessionManager` จัดเก็บเซสชันภายใต้ไดเรกทอรีที่กำหนดขอบเขตตาม cwd โดยค่าเริ่มต้น:\n\n- `~/.xcsh/agent/sessions/--<cwd-encoded>--/*.jsonl`\n\n`SessionManager.list(cwd, sessionDir?)` อ่านเฉพาะไดเรกทอรีนั้น เว้นแต่จะมีการระบุ `sessionDir` อย่างชัดเจน\n\n### สองเส้นทางการแสดงรายการที่มีข้อมูลต่างกัน\n\nมีสองไปป์ไลน์การแสดงรายการที่แตกต่างกัน:\n\n1. `getRecentSessions(sessionDir, limit)` (มุมมองต้อนรับ/สรุป)\n   - อ่านเฉพาะส่วนนำขนาด 4KB (`readTextPrefix(..., 4096)`) จากแต่ละไฟล์\n   - แยกวิเคราะห์ส่วนหัวและข้อความแสดงตัวอย่างของผู้ใช้ในช่วงต้น\n   - คืนค่า `RecentSessionInfo` แบบเบาพร้อม getter แบบ lazy สำหรับ `name` และ `timeAgo`\n   - เรียงลำดับตาม `mtime` ของไฟล์จากมากไปน้อย\n\n2. `SessionManager.list(...)` / `SessionManager.listAll()` (ตัวเลือกสำหรับ resume และการจับคู่ ID)\n   - อ่านไฟล์เซสชันทั้งหมด\n   - สร้างออบเจ็กต์ `SessionInfo` (`id`, `cwd`, `title`, `messageCount`, `firstMessage`, `allMessagesText`, timestamps)\n   - ตัดทิ้งเซสชันที่มีรายการ `message` เป็นศูนย์\n   - เรียงลำดับตาม `modified` จากมากไปน้อย\n\n### พฤติกรรมสำรองของข้อมูลเมตา\n\nสำหรับสรุปล่าสุด (`RecentSessionInfo`):\n\n- ลำดับความสำคัญของชื่อที่แสดง: `header.title` -> prompt แรกของผู้ใช้ -> `header.id` -> ชื่อไฟล์\n- ชื่อถูกตัดให้เหลือ 40 ตัวอักษรสำหรับการแสดงผลแบบกระชับ\n- อักขระควบคุมและการขึ้นบรรทัดใหม่จะถูกลบ/ทำความสะอาดจากชื่อที่ได้มาจากชื่อเรื่อง\n\nสำหรับรายการ `SessionInfo`:\n\n- `title` คือ `header.title` หรือ `shortSummary` จากการบีบอัดล่าสุด\n- `firstMessage` คือข้อความของข้อความแรกจากผู้ใช้ หรือ `\"(no messages)\"`\n\n## การแก้ไข `--continue` และการให้ความสำคัญกับ breadcrumb ของเทอร์มินัล\n\n`SessionManager.continueRecent(cwd, sessionDir?)` แก้ไขเป้าหมายตามลำดับดังนี้:\n\n1. อ่าน breadcrumb ที่กำหนดขอบเขตตามเทอร์มินัล (`~/.xcsh/agent/terminal-sessions/<terminal-id>`)\n2. ตรวจสอบ breadcrumb:\n   - สามารถระบุเทอร์มินัลปัจจุบันได้\n   - cwd ของ breadcrumb ตรงกับ cwd ปัจจุบัน (เปรียบเทียบ resolved path)\n   - ไฟล์ที่อ้างอิงยังคงมีอยู่\n3. หาก breadcrumb ไม่ถูกต้อง/ขาดหายไป ให้ใช้ไฟล์ล่าสุดตาม mtime ในไดเรกทอรีเซสชันแทน (`findMostRecentSession`)\n4. หาไม่พบ ให้สร้างเซสชันใหม่\n\nการหา Terminal ID จะให้ความสำคัญกับเส้นทาง TTY และใช้ตัวระบุจากสภาพแวดล้อมเป็นทางเลือกสำรอง (`KITTY_WINDOW_ID`, `TMUX_PANE`, `TERM_SESSION_ID`, `WT_SESSION`)\n\nการเขียน breadcrumb เป็นการดำเนินการแบบ best-effort และไม่ทำให้เกิดข้อผิดพลาดร้ายแรง\n\n## การแก้ไขเป้าหมาย resume ณ เวลาเริ่มต้น (`main.ts`)\n\n### `--resume <value>`\n\n`createSessionManager(...)` จัดการ `--resume` ที่มีค่าเป็น string ใน 2 โหมด:\n\n1. ค่าที่มีลักษณะเป็น path (มี `/`, `\\\\`, หรือลงท้ายด้วย `.jsonl`)\n   - เปิดโดยตรงด้วย `SessionManager.open(sessionArg, parsed.sessionDir)`\n\n2. ค่าที่เป็น ID prefix\n   - ค้นหาการจับคู่ใน `SessionManager.list(cwd, sessionDir)` โดยใช้ `id.startsWith(sessionArg)`\n   - หาไม่พบในพื้นที่และไม่ได้บังคับ `sessionDir` ให้ลอง `SessionManager.listAll()`\n   - ใช้การจับคู่แรกที่พบ (ไม่มีการถามยืนยันเมื่อพบหลายรายการ)\n\nพฤติกรรมเมื่อพบการจับคู่ข้ามโปรเจกต์:\n\n- หาก cwd ของเซสชันที่จับคู่ได้แตกต่างจาก cwd ปัจจุบัน CLI จะถามว่าต้องการ fork ไปยังโปรเจกต์ปัจจุบันหรือไม่\n- ใช่ -> `SessionManager.forkFrom(...)`\n- ไม่ -> โยน error (`Session \"...\" is in another project (...)`)\n\nไม่พบการจับคู่ -> โยน error (`Session \"...\" not found.`)\n\n### `--resume` (ไม่มีค่า)\n\nจัดการหลังจากการสร้าง session-manager เริ่มต้น:\n\n1. แสดงรายการเซสชันในพื้นที่ด้วย `SessionManager.list(cwd, parsed.sessionDir)`\n2. หากว่าง: แสดง `No sessions found` และออกจากโปรแกรมก่อน\n3. เปิดตัวเลือก TUI (`selectSession`)\n4. หากยกเลิก: แสดง `No session selected` และออกจากโปรแกรมก่อน\n5. หากเลือก: `SessionManager.open(selectedPath)`\n\n### `--continue`\n\nใช้ `SessionManager.continueRecent(...)` โดยตรง (พฤติกรรม breadcrumb-first ดังที่อธิบายข้างต้น)\n\n## กลไกภายในของการเลือกผ่านตัวเลือก\n\n## ตัวเลือก CLI (`src/cli/session-picker.ts`)\n\n`selectSession(sessions)` สร้าง TUI แบบ standalone ด้วย `SessionSelectorComponent` และ resolve ครั้งเดียว:\n\n- เลือก -> resolve path ที่เลือก\n- ยกเลิก (Esc) -> resolve `null`\n- ออกแบบบังคับ (เส้นทาง Ctrl+C) -> หยุด TUI และ `process.exit(0)`\n\n## ตัวเลือกภายในเซสชันแบบ interactive (`SelectorController.showSessionSelector`)\n\nขั้นตอนการทำงาน:\n\n1. ดึงเซสชันจากไดเรกทอรีเซสชันปัจจุบันผ่าน `SessionManager.list(currentCwd, currentSessionDir)`\n2. ติดตั้ง `SessionSelectorComponent` ในพื้นที่ editor โดยใช้ `showSelector(...)`\n3. callbacks:\n   - เลือก -> ปิดตัวเลือกและเรียก `handleResumeSession(sessionPath)`\n   - ยกเลิก -> คืนค่า editor และ rerender\n   - ออก -> `ctx.shutdown()`\n\n## พฤติกรรมของส่วนประกอบตัวเลือกเซสชัน\n\n`SessionList` รองรับ:\n\n- การนำทางด้วยลูกศร/หน้า\n- Enter เพื่อเลือก\n- Esc เพื่อยกเลิก\n- Ctrl+C เพื่อออก\n- การค้นหาแบบ fuzzy ข้ามข้อมูล id/title/cwd/first message/all messages/path ของเซสชัน\n\nพฤติกรรมการแสดงผลเมื่อรายการว่าง:\n\n- แสดงข้อความแทนที่จะเกิด crash\n- การกด Enter บนรายการว่างไม่ทำอะไร (ไม่มี callback)\n- Esc/Ctrl+C ยังคงทำงานได้\n\nข้อควรระวัง: ข้อความใน UI ระบุว่า `Press Tab to view all` แต่ส่วนประกอบนี้ยังไม่มี handler สำหรับ Tab ในปัจจุบัน และการเชื่อมต่อปัจจุบันแสดงรายการเฉพาะเซสชันในขอบเขตปัจจุบันเท่านั้น\n\n## การดำเนินการสลับรันไทม์ (`AgentSession.switchSession`)\n\n`switchSession(sessionPath)` คือเส้นทางหลักในการสลับภายในกระบวนการ\n\nวงจรชีวิต/การเปลี่ยนสถานะ:\n\n1. บันทึก `previousSessionFile`\n2. ส่ง event hook `session_before_switch` (`reason: \"resume\"`, สามารถยกเลิกได้)\n3. หากยกเลิก -> คืนค่า `false` โดยไม่สลับ\n4. ตัดการเชื่อมต่อจาก event stream ของ agent ปัจจุบัน\n5. ยกเลิกการสร้าง/กระบวนการ tool ที่กำลังทำงาน\n6. ล้าง buffer ข้อความ steering/follow-up/next-turn ที่รอคิว\n7. flush session writer (`sessionManager.flush()`) เพื่อบันทึกการเขียนที่รอดำเนินการ\n8. `sessionManager.setSessionFile(sessionPath)`\n   - อัปเดต pointer ไฟล์เซสชัน\n   - เขียน terminal breadcrumb\n   - โหลด entries / migrate / blob-resolve / reindex\n   - หากข้อมูลไฟล์ขาดหาย/ไม่ถูกต้อง: เริ่มต้นเซสชันใหม่ที่ path นั้นและเขียนส่วนหัวใหม่\n9. อัปเดต `agent.sessionId`\n10. สร้าง context ใหม่ผ่าน `buildSessionContext()`\n11. ส่ง event hook `session_switch` (`reason: \"resume\"`, `previousSessionFile`)\n12. แทนที่ข้อความ agent ด้วย context ที่สร้างใหม่\n13. คืนค่า model เริ่มต้นจาก `sessionContext.models.default` หากมีและอยู่ใน model registry\n14. คืนค่าระดับ thinking:\n    - หาก branch มี `thinking_level_change` อยู่แล้ว ให้ใช้ระดับเซสชันที่บันทึกไว้\n    - มิฉะนั้น ให้หาระดับ thinking เริ่มต้นจาก settings, จำกัดให้อยู่ในความสามารถของ model, ตั้งค่า และเพิ่มรายการ `thinking_level_change` ใหม่\n15. เชื่อมต่อ agent listeners อีกครั้งและคืนค่า `true`\n\n## การสร้างสถานะ UI ใหม่หลังการสลับแบบ interactive\n\n`SelectorController.handleResumeSession` ดำเนินการรีเซ็ต UI รอบๆ `switchSession`:\n\n- หยุดแอนิเมชันการโหลด\n- ล้าง status container\n- ล้าง pending-message UI และ pending tool map\n- รีเซ็ต streaming component/message references\n- เรียก `session.switchSession(...)`\n- ล้าง chat container และ rerender จาก session context (`renderInitialMessages`)\n- โหลด todos ใหม่จาก artifacts ของเซสชันใหม่\n- แสดง `Resumed session`\n\nดังนั้นสถานะการสนทนา/todo ที่มองเห็นได้จะถูกสร้างใหม่จากไฟล์เซสชันใหม่\n\n## การ resume ขณะเริ่มต้น vs การสลับภายในเซสชัน\n\n### การ resume ขณะเริ่มต้น (`--continue`, `--resume`, การเปิดโดยตรง)\n\n- เลือกไฟล์เซสชันก่อน `createAgentSession(...)`\n- `sdk.ts` สร้าง `existingSession = sessionManager.buildSessionContext()`\n- ข้อความ agent ถูกคืนค่าครั้งเดียวระหว่างการสร้างเซสชัน\n- Model/thinking ถูกเลือกระหว่างการสร้าง (รวมถึง logic การคืนค่า/fallback)\n- โหมด interactive จากนั้นรัน `#restoreModeFromSession()` เพื่อกลับเข้าสู่สถานะโหมดที่บันทึกไว้ (ปัจจุบันคือ plan/plan_paused)\n\n### การสลับภายในเซสชัน (เส้นทางตัวเลือกแบบ `/resume`)\n\n- ใช้ `AgentSession.switchSession(...)` บน `AgentSession` ที่กำลังทำงานอยู่แล้ว\n- ข้อความ/model/thinking ถูกสร้างใหม่ทันทีในที่เดิม\n- ส่ง event hook `session_before_switch`/`session_switch`\n- UI chat/todos ถูกรีเฟรช\n- ไม่มีการเรียก mode restore หลังการสลับโดยเฉพาะในเส้นทาง selector; พฤติกรรมการกลับเข้าสู่โหมดไม่สมมาตรกับ `#restoreModeFromSession()` ขณะเริ่มต้น\n\n## พฤติกรรมเมื่อเกิดความล้มเหลวและกรณีขอบ\n\n### เส้นทางการยกเลิก\n\n- ยกเลิกตัวเลือก CLI -> คืนค่า `null`, caller แสดง `No session selected`, กระบวนการออกก่อน\n- ยกเลิกตัวเลือกแบบ interactive -> คืนค่า editor โดยไม่มีการเปลี่ยนเซสชัน\n- การยกเลิกผ่าน hook (`session_before_switch`) -> `switchSession()` คืนค่า `false`\n\n### เส้นทางรายการว่าง\n\n- CLI `--resume` (ไม่มีค่า): รายการว่างแสดง `No sessions found` และออก\n- ตัวเลือกแบบ interactive: รายการว่างแสดงข้อความและยังคงสามารถยกเลิกได้\n\n### ไฟล์เซสชันเป้าหมายที่ขาดหาย/ไม่ถูกต้อง\n\nเมื่อเปิด/สลับไปยัง path เฉพาะ (`setSessionFile`):\n\n- ENOENT -> ถือว่าว่าง -> เริ่มต้นเซสชันใหม่ที่ path นั้นและบันทึก\n- ส่วนหัวที่ผิดรูปแบบ/ไม่ถูกต้อง (หรือรายการที่แยกวิเคราะห์ไม่ได้) -> ถือว่าว่าง -> เริ่มต้นเซสชันใหม่และบันทึก\n\nนี่คือพฤติกรรมการกู้คืน ไม่ใช่ความล้มเหลวแบบร้ายแรง\n\n### ความล้มเหลวแบบร้ายแรง\n\nการสลับ/เปิดยังคงสามารถโยน error จากความล้มเหลวของ I/O จริงๆ (ข้อผิดพลาดสิทธิ์การเข้าถึง, ความล้มเหลวในการเขียนใหม่ ฯลฯ) ซึ่งจะแพร่กระจายไปยัง caller\n\n### ข้อควรระวังในการจับคู่ ID prefix\n\n- การจับคู่ ID ใช้ `startsWith` และใช้การจับคู่แรกในรายการที่เรียงลำดับแล้ว\n- ไม่มี UI สำหรับการยืนยันหากเซสชันหลายรายการมี prefix เดียวกัน\n- `SessionManager.list(...)` ไม่รวมเซสชันที่มีข้อความเป็นศูนย์ ดังนั้นเซสชันเหล่านั้นจึงไม่สามารถ resume ได้ผ่านการจับคู่ ID/ตัวเลือกรายการ\n",
	"th/sessions/session-tree-plan.md": "---\ntitle: สถาปัตยกรรมต้นไม้เซสชัน\ndescription: >-\n  สถาปัตยกรรมต้นไม้เซสชันพร้อมการแตกสาขา การนำทาง\n  และความสัมพันธ์การสนทนาแบบพ่อแม่-ลูก\nsidebar:\n  order: 2\n  label: สถาปัตยกรรมต้นไม้\ni18n:\n  sourceHash: bd8b78d6c33a\n  translator: machine\n---\n\n# สถาปัตยกรรมต้นไม้เซสชัน (ปัจจุบัน)\n\nอ้างอิง: [session.md](./session.md)\n\nเอกสารนี้อธิบายวิธีการทำงานของการนำทางต้นไม้เซสชันในปัจจุบัน ได้แก่ โมเดลต้นไม้ในหน่วยความจำ กฎการเคลื่อนที่ของใบ พฤติกรรมการแตกสาขา และการผสานรวมส่วนขยาย/เหตุการณ์\n\n## ระบบย่อยนี้คืออะไร\n\nเซสชันถูกจัดเก็บเป็นบันทึกรายการแบบ append-only แต่พฤติกรรมขณะรันไทม์เป็นแบบต้นไม้:\n\n- ทุกรายการที่ไม่ใช่ส่วนหัวมี `id` และ `parentId`\n- ตำแหน่งที่ใช้งานอยู่คือ `leafId` ใน `SessionManager`\n- การต่อท้ายรายการจะสร้างรายการลูกของใบปัจจุบันเสมอ\n- การแตกสาขา **ไม่** เขียนทับประวัติ แต่เพียงเปลี่ยนตำแหน่งที่ใบชี้ไปก่อนการต่อท้ายครั้งถัดไป\n\nไฟล์หลัก:\n\n- `src/session/session-manager.ts` — โมเดลข้อมูลต้นไม้ การสำรวจ การเคลื่อนที่ของใบ การแยกสาขา/เซสชัน\n- `src/session/agent-session.ts` — ขั้นตอนการนำทาง `/tree` การสรุป การปล่อยฮุก/เหตุการณ์\n- `src/modes/components/tree-selector.ts` — พฤติกรรม UI ต้นไม้แบบโต้ตอบและการกรอง\n- `src/modes/controllers/selector-controller.ts` — การประสานงาน selector สำหรับ `/tree` และ `/branch`\n- `src/modes/controllers/input-controller.ts` — การกำหนดเส้นทางคำสั่ง (`/tree`, `/branch`, พฤติกรรม double-escape)\n- `src/session/messages.ts` — การแปลงรายการ `branch_summary`, `compaction` และ `custom_message` เป็นข้อความบริบท LLM\n\n## โมเดลข้อมูลต้นไม้ใน `SessionManager`\n\nดัชนีขณะรันไทม์:\n\n- `#byId: Map<string, SessionEntry>` — การค้นหาแบบรวดเร็วสำหรับรายการใดก็ได้\n- `#leafId: string | null` — ตำแหน่งปัจจุบันในต้นไม้\n- `#labelsById: Map<string, string>` — ป้ายกำกับที่แก้ไขแล้วตาม id รายการเป้าหมาย\n\nTree API:\n\n- `getBranch(fromId?)` เดินตามลิงก์พ่อแม่ไปยังรากและคืนค่าเส้นทาง root→node\n- `getTree()` คืนค่า `SessionTreeNode[]` (`entry`, `children`, `label`)\n  - ลิงก์พ่อแม่กลายเป็นอาร์เรย์ลูก\n  - รายการที่ไม่มีพ่อแม่จะถูกถือว่าเป็นราก\n  - ลูกจะถูกจัดเรียงจากเก่าไปใหม่ตาม timestamp\n- `getChildren(parentId)` คืนค่าลูกโดยตรง\n- `getLabel(id)` แก้ไขป้ายกำกับปัจจุบันจาก `labelsById`\n\n`getTree()` คือการฉายภาพขณะรันไทม์ การคงอยู่ยังคงเป็นรายการ JSONL แบบ append-only\n\n## ความหมายของการเคลื่อนที่ใบ\n\nมีการดำเนินการพื้นฐานการเคลื่อนที่ใบสามแบบ:\n\n1. `branch(entryId)`\n   - ตรวจสอบว่ารายการมีอยู่\n   - ตั้งค่า `leafId = entryId`\n   - ไม่มีการเขียนรายการใหม่\n\n2. `resetLeaf()`\n   - ตั้งค่า `leafId = null`\n   - การต่อท้ายครั้งถัดไปจะสร้างรายการรากใหม่ (`parentId = null`)\n\n3. `branchWithSummary(branchFromId, summary, details?, fromExtension?)`\n   - รับ `branchFromId: string | null`\n   - ตั้งค่า `leafId = branchFromId`\n   - ต่อท้ายรายการ `branch_summary` เป็นลูกของใบนั้น\n   - เมื่อ `branchFromId` เป็น `null` จะบันทึก `fromId` เป็น `\"root\"`\n\n## พฤติกรรมการนำทาง `/tree` (ไฟล์เซสชันเดียวกัน)\n\n`AgentSession.navigateTree()` คือการนำทาง ไม่ใช่การแยกไฟล์\n\nขั้นตอน:\n\n1. ตรวจสอบเป้าหมายและคำนวณเส้นทางที่ถูกละทิ้ง (`collectEntriesForBranchSummary`)\n2. ปล่อย `session_before_tree` พร้อม `TreePreparation`\n3. สรุปรายการที่ถูกละทิ้งโดยเลือกได้ (สรุปที่ฮุกจัดหามาหรือตัวสรุปในตัว)\n4. คำนวณเป้าหมายใบใหม่:\n   - การเลือกข้อความ **user**: ใบย้ายไปยังพ่อแม่ของมัน และข้อความจะถูกส่งกลับสำหรับการเติมล่วงหน้าของ editor\n   - การเลือก **custom_message**: กฎเดียวกับข้อความ user (ใบ = พ่อแม่, ข้อความเติมล่วงหน้า editor)\n   - การเลือกรายการอื่นใด: ใบ = id รายการที่เลือก\n5. ใช้การเคลื่อนที่ใบ:\n   - มีสรุป: `branchWithSummary(newLeafId, ...)`\n   - ไม่มีสรุปและ `newLeafId === null`: `resetLeaf()`\n   - มิฉะนั้น: `branch(newLeafId)`\n6. สร้างบริบท agent ใหม่จากใบใหม่และปล่อย `session_tree`\n\nสำคัญ: รายการสรุปจะแนบที่ **ตำแหน่งการนำทางใหม่** ไม่ใช่ที่ส่วนท้ายของสาขาที่ถูกละทิ้ง\n\n## พฤติกรรม `/branch` (ไฟล์เซสชันใหม่)\n\n`/branch` และ `/tree` แตกต่างกันโดยเจตนา:\n\n- `/tree` นำทางภายในไฟล์เซสชันปัจจุบัน\n- `/branch` สร้างไฟล์สาขาเซสชันใหม่ (หรือการแทนที่ในหน่วยความจำสำหรับโหมดที่ไม่คงอยู่)\n\nขั้นตอน `/branch` ที่ผู้ใช้เห็น (`SelectorController.showUserMessageSelector` → `AgentSession.branch`):\n\n- แหล่งที่มาของสาขาต้องเป็น **ข้อความ user**\n- ข้อความ user ที่เลือกจะถูกแยกออกมาสำหรับการเติมล่วงหน้าของ editor\n- หากข้อความ user ที่เลือกเป็นราก (`parentId === null`): เริ่มเซสชันใหม่ผ่าน `newSession({ parentSession: previousSessionFile })`\n- มิฉะนั้น: `createBranchedSession(selectedEntry.parentId)` เพื่อแยกประวัติไปยังขอบเขตพรอมต์ที่เลือก\n\nรายละเอียด `SessionManager.createBranchedSession(leafId)`:\n\n- สร้างเส้นทาง root→leaf ผ่าน `getBranch(leafId)` โยนข้อผิดพลาดหากไม่พบ\n- ยกเว้นรายการ `label` ที่มีอยู่จากเส้นทางที่คัดลอก\n- สร้างรายการป้ายกำกับใหม่จาก `labelsById` ที่แก้ไขแล้วสำหรับรายการที่ยังคงอยู่ในเส้นทาง\n- โหมดคงอยู่: เขียนไฟล์ JSONL ใหม่และเปลี่ยน manager ไปใช้ไฟล์นั้น คืนค่าเส้นทางไฟล์ใหม่\n- โหมดในหน่วยความจำ: แทนที่รายการในหน่วยความจำ คืนค่า `undefined`\n\n## การสร้างบริบทใหม่และการผสานรวมสรุป/กำหนดเอง\n\n`buildSessionContext()` (ใน `session-manager.ts`) แก้ไขเส้นทาง root→leaf ที่ใช้งานอยู่และสร้างสถานะบริบท LLM ที่มีผล:\n\n- ติดตามสถานะ thinking/model/mode/ttsr ล่าสุดในเส้นทาง\n- จัดการการบีบอัดล่าสุดในเส้นทาง:\n  - ปล่อยสรุปการบีบอัดก่อน\n  - เล่นซ้ำข้อความที่เก็บไว้จาก `firstKeptEntryId` ไปยังจุดบีบอัด\n  - จากนั้นเล่นซ้ำข้อความหลังการบีบอัด\n- รวมรายการ `branch_summary` และ `custom_message` เป็นออบเจ็กต์ `AgentMessage`\n\n`session/messages.ts` จากนั้นแมปประเภทข้อความเหล่านี้สำหรับอินพุตโมเดล:\n\n- `branchSummary` และ `compactionSummary` กลายเป็นข้อความบริบทที่ใช้เทมเพลตบทบาท user\n- `custom`/`hookMessage` กลายเป็นข้อความเนื้อหาบทบาท user\n\nดังนั้นการเคลื่อนที่ต้นไม้จะเปลี่ยนบริบทโดยการเปลี่ยนเส้นทางใบที่ใช้งานอยู่ ไม่ใช่โดยการเปลี่ยนแปลงรายการเก่า\n\n## ป้ายกำกับและพฤติกรรม UI ต้นไม้\n\nการคงอยู่ของป้ายกำกับ:\n\n- `appendLabelChange(targetId, label?)` เขียนรายการ `label` บนเชนใบปัจจุบัน\n- `labelsById` ถูกอัปเดตทันที (ตั้งค่าหรือลบ)\n- `getTree()` แก้ไขป้ายกำกับปัจจุบันลงในแต่ละโหนดที่คืนค่า\n\nพฤติกรรม tree selector (`tree-selector.ts`):\n\n- ทำให้ต้นไม้แบนราบสำหรับการนำทาง รักษาการเน้น active-path และจัดลำดับความสำคัญในการแสดงสาขาที่ใช้งานอยู่ก่อน\n- รองรับโหมดกรอง: `default`, `no-tools`, `user-only`, `labeled-only`, `all`\n- รองรับการค้นหาข้อความอิสระบนเนื้อหาเชิงความหมายที่แสดงผล\n- `Shift+L` เปิดการแก้ไขป้ายกำกับแบบ inline และเขียนผ่าน `appendLabelChange`\n\nการกำหนดเส้นทางคำสั่ง:\n\n- `/tree` เปิด tree selector เสมอ\n- `/branch` เปิด user-message selector เว้นแต่ `doubleEscapeAction=tree` ซึ่งในกรณีนั้นจะใช้ UX ของ tree selector ด้วย\n\n## จุดเชื่อมต่อส่วนขยายและฮุกสำหรับการดำเนินการต้นไม้\n\nExtension API ขณะใช้คำสั่ง (`ExtensionCommandContext`):\n\n- `branch(entryId)` — สร้างไฟล์เซสชันที่แตกสาขา\n- `navigateTree(targetId, { summarize? })` — ย้ายภายในต้นไม้/ไฟล์ปัจจุบัน\n\nเหตุการณ์รอบการนำทางต้นไม้:\n\n- `session_before_tree`\n  - รับ `TreePreparation`:\n    - `targetId`\n    - `oldLeafId`\n    - `commonAncestorId`\n    - `entriesToSummarize`\n    - `userWantsSummary`\n  - อาจยกเลิกการนำทาง\n  - อาจจัดหา payload สรุปที่ใช้แทนตัวสรุปในตัว\n  - รับ `signal` ยกเลิก (เส้นทางการยกเลิกด้วย Escape)\n- `session_tree`\n  - ปล่อย `newLeafId`, `oldLeafId`\n  - รวม `summaryEntry` เมื่อมีการสร้างสรุป\n  - `fromExtension` ระบุแหล่งที่มาของสรุป\n\nฮุก lifecycle ที่อยู่ใกล้เคียงแต่เกี่ยวข้อง:\n\n- `session_before_branch` / `session_branch` สำหรับขั้นตอน `/branch`\n- `session_before_compact`, `session.compacting`, `session_compact` สำหรับรายการบีบอัดที่ภายหลังส่งผลต่อการสร้างบริบทต้นไม้ใหม่\n\n## ข้อจำกัดจริงและเงื่อนไขขอบเขต\n\n- `branch()` ไม่สามารถกำหนดเป้าหมายเป็น `null` ได้ ใช้ `resetLeaf()` สำหรับสถานะ root-before-first-entry\n- `branchWithSummary()` รองรับเป้าหมาย `null` และบันทึก `fromId: \"root\"`\n- การเลือกใบปัจจุบันใน tree selector เป็นการดำเนินการที่ไม่มีผล\n- การสรุปต้องการโมเดลที่ใช้งานอยู่ หากไม่มี การนำทางพร้อมสรุปจะล้มเหลวทันที\n- หากการสรุปถูกยกเลิก การนำทางจะถูกยกเลิกและใบจะไม่เปลี่ยนแปลง\n- เซสชันในหน่วยความจำจะไม่คืนค่าเส้นทางไฟล์สาขาจาก `createBranchedSession` เลย\n\n## ความเข้ากันได้แบบเดิมที่ยังคงมีอยู่\n\nการย้ายโอนเซสชันยังคงทำงานเมื่อโหลด:\n\n- v1→v2 เพิ่ม `id`/`parentId` และแปลง anchor ดัชนีบีบอัดเดิมเป็น id anchor\n- v2→v3 ย้ายโอนบทบาท `hookMessage` เดิมไปยัง `custom`\n\nพฤติกรรมขณะรันไทม์ปัจจุบันคือ semantics ต้นไม้เวอร์ชัน 3 หลังการย้ายโอน\n",
	"th/sessions/session.md": "---\ntitle: โมเดลการจัดเก็บเซสชันและรายการ\ndescription: >-\n  โมเดลการจัดเก็บเซสชันแบบ append-only พร้อมประเภทรายการ การคงอยู่ของข้อมูล\n  และการย้ายข้ามรูปแบบต่างๆ\nsidebar:\n  order: 1\n  label: โมเดลการจัดเก็บและรายการ\ni18n:\n  sourceHash: 42fe17549e00\n  translator: machine\n---\n\n# โมเดลการจัดเก็บเซสชันและรายการ\n\nเอกสารนี้เป็นแหล่งข้อมูลที่เชื่อถือได้สำหรับวิธีการแสดง จัดเก็บ ย้าย และสร้างเซสชัน coding-agent ขึ้นมาใหม่ในขณะรันไทม์\n\n## ขอบเขต\n\nครอบคลุม:\n\n- รูปแบบ JSONL ของเซสชันและการกำหนดเวอร์ชัน\n- อนุกรมวิธานรายการและความหมายของโครงสร้างต้นไม้ (`id`/`parentId` + ตัวชี้ใบไม้)\n- พฤติกรรมการย้าย/ความเข้ากันได้เมื่อโหลดไฟล์เก่าหรือไฟล์ที่มีรูปแบบผิดพลาด\n- การสร้างบริบทขึ้นมาใหม่ (`buildSessionContext`)\n- การรับประกันการคงอยู่ พฤติกรรมเมื่อเกิดความล้มเหลว การตัดทอน/การแยกเก็บ blob ภายนอก\n- นามธรรมของการจัดเก็บ (`FileSessionStorage`, `MemorySessionStorage`) และยูทิลิตี้ที่เกี่ยวข้อง\n\nไม่ครอบคลุมพฤติกรรมการแสดงผล UI `/tree` นอกเหนือจากความหมายที่ส่งผลต่อข้อมูลเซสชัน\n\n## ไฟล์การนำไปใช้งาน\n\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`src/session/messages.ts`](../../packages/coding-agent/src/session/messages.ts)\n- [`src/session/session-storage.ts`](../../packages/coding-agent/src/session/session-storage.ts)\n- [`src/session/history-storage.ts`](../../packages/coding-agent/src/session/history-storage.ts)\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts)\n\n## โครงสร้างไฟล์บนดิสก์\n\nตำแหน่งไฟล์เซสชันเริ่มต้น:\n\n```text\n~/.xcsh/agent/sessions/--<cwd-encoded>--/<timestamp>_<sessionId>.jsonl\n```\n\n`<cwd-encoded>` ได้มาจากไดเรกทอรีการทำงานโดยตัดเครื่องหมายทับนำหน้าออกและแทนที่ `/`, `\\\\`, และ `:` ด้วย `-`\n\nตำแหน่ง blob store:\n\n```text\n~/.xcsh/agent/blobs/<sha256>\n```\n\nไฟล์ breadcrumb ของเทอร์มินัลจะถูกเขียนไว้ที่:\n\n```text\n~/.xcsh/agent/terminal-sessions/<terminal-id>\n```\n\nเนื้อหา breadcrumb มีสองบรรทัด: cwd เดิม จากนั้นเป็นเส้นทางไฟล์เซสชัน `continueRecent()` จะให้ความสำคัญกับตัวชี้ขอบเขตเทอร์มินัลนี้ก่อนการสแกน mtime ล่าสุด\n\n## รูปแบบไฟล์\n\nไฟล์เซสชันเป็น JSONL: หนึ่งออบเจกต์ JSON ต่อหนึ่งบรรทัด\n\n- บรรทัดที่ 1 จะเป็น session header เสมอ (`type: \"session\"`)\n- บรรทัดที่เหลือเป็นค่า `SessionEntry`\n- รายการเป็นแบบ append-only ในขณะรันไทม์ การนำทางสาขาจะย้ายตัวชี้ (`leafId`) แทนที่จะแก้ไขรายการที่มีอยู่\n\n### Header (`SessionHeader`)\n\n```json\n{\n  \"type\": \"session\",\n  \"version\": 3,\n  \"id\": \"1f9d2a6b9c0d1234\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\",\n  \"cwd\": \"/work/pi\",\n  \"title\": \"optional session title\",\n  \"parentSession\": \"optional lineage marker\"\n}\n```\n\nหมายเหตุ:\n\n- `version` เป็นตัวเลือกในไฟล์ v1 หากไม่มีหมายความว่าเป็น v1\n- `parentSession` เป็นสตริงสายวงศ์ที่ไม่โปร่งใส โค้ดปัจจุบันเขียนทั้ง session id หรือเส้นทางเซสชันขึ้นอยู่กับกระบวนการ (`fork`, `forkFrom`, `createBranchedSession`, หรือ `newSession({ parentSession })` ที่ระบุชัดเจน) ถือเป็นข้อมูล metadata ไม่ใช่ foreign key ที่มีประเภทกำหนด\n\n### ฐาน Entry (`SessionEntryBase`)\n\nรายการที่ไม่ใช่ header ทั้งหมดรวมถึง:\n\n```json\n{\n  \"type\": \"...\",\n  \"id\": \"8-char-id\",\n  \"parentId\": \"previous-or-branch-parent\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\"\n}\n```\n\n`parentId` สามารถเป็น `null` สำหรับรายการรากได้ (การ append ครั้งแรก หรือหลังจาก `resetLeaf()`)\n\n## อนุกรมวิธานรายการ\n\n`SessionEntry` คือ union ของ:\n\n- `message`\n- `thinking_level_change`\n- `model_change`\n- `compaction`\n- `branch_summary`\n- `custom`\n- `custom_message`\n- `label`\n- `ttsr_injection`\n- `session_init`\n- `mode_change`\n\n### `message`\n\nจัดเก็บ `AgentMessage` โดยตรง\n\n```json\n{\n  \"type\": \"message\",\n  \"id\": \"a1b2c3d4\",\n  \"parentId\": null,\n  \"timestamp\": \"2026-02-16T10:21:00.000Z\",\n  \"message\": {\n    \"role\": \"assistant\",\n    \"provider\": \"anthropic\",\n    \"model\": \"claude-sonnet-4-5\",\n    \"content\": [{ \"type\": \"text\", \"text\": \"Done.\" }],\n    \"usage\": { \"input\": 100, \"output\": 20, \"cacheRead\": 0, \"cacheWrite\": 0, \"cost\": { \"input\": 0, \"output\": 0, \"cacheRead\": 0, \"cacheWrite\": 0, \"total\": 0 } },\n    \"timestamp\": 1760000000000\n  }\n}\n```\n\n### `model_change`\n\n```json\n{\n  \"type\": \"model_change\",\n  \"id\": \"b1c2d3e4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:21:30.000Z\",\n  \"model\": \"openai/gpt-4o\",\n  \"role\": \"default\"\n}\n```\n\n`role` เป็นตัวเลือก หากไม่มีจะถูกถือว่าเป็น `default` ในการสร้างบริบทขึ้นมาใหม่\n\n### `thinking_level_change`\n\n```json\n{\n  \"type\": \"thinking_level_change\",\n  \"id\": \"c1d2e3f4\",\n  \"parentId\": \"b1c2d3e4\",\n  \"timestamp\": \"2026-02-16T10:22:00.000Z\",\n  \"thinkingLevel\": \"high\"\n}\n```\n\n### `compaction`\n\n```json\n{\n  \"type\": \"compaction\",\n  \"id\": \"d1e2f3a4\",\n  \"parentId\": \"c1d2e3f4\",\n  \"timestamp\": \"2026-02-16T10:23:00.000Z\",\n  \"summary\": \"Conversation summary\",\n  \"shortSummary\": \"Short recap\",\n  \"firstKeptEntryId\": \"a1b2c3d4\",\n  \"tokensBefore\": 42000,\n  \"details\": { \"readFiles\": [\"src/a.ts\"] },\n  \"preserveData\": { \"hookState\": true },\n  \"fromExtension\": false\n}\n```\n\n### `branch_summary`\n\n```json\n{\n  \"type\": \"branch_summary\",\n  \"id\": \"e1f2a3b4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:24:00.000Z\",\n  \"fromId\": \"a1b2c3d4\",\n  \"summary\": \"Summary of abandoned path\",\n  \"details\": { \"note\": \"optional\" },\n  \"fromExtension\": true\n}\n```\n\nหากแยกสาขาจากราก (`branchFromId === null`) `fromId` จะเป็นสตริงตัวอักษร `\"root\"`\n\n### `custom`\n\nการคงอยู่ของสถานะส่วนขยาย ถูกละเว้นโดย `buildSessionContext`\n\n```json\n{\n  \"type\": \"custom\",\n  \"id\": \"f1a2b3c4\",\n  \"parentId\": \"e1f2a3b4\",\n  \"timestamp\": \"2026-02-16T10:25:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"data\": { \"state\": 1 }\n}\n```\n\n### `custom_message`\n\nข้อความที่ส่วนขยายจัดเตรียมซึ่งเข้าร่วมในบริบท LLM\n\n```json\n{\n  \"type\": \"custom_message\",\n  \"id\": \"a2b3c4d5\",\n  \"parentId\": \"f1a2b3c4\",\n  \"timestamp\": \"2026-02-16T10:26:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"content\": \"Injected context\",\n  \"display\": true,\n  \"details\": { \"debug\": false }\n}\n```\n\n### `label`\n\n```json\n{\n  \"type\": \"label\",\n  \"id\": \"b2c3d4e5\",\n  \"parentId\": \"a2b3c4d5\",\n  \"timestamp\": \"2026-02-16T10:27:00.000Z\",\n  \"targetId\": \"a1b2c3d4\",\n  \"label\": \"checkpoint\"\n}\n```\n\n`label: undefined` จะล้าง label สำหรับ `targetId`\n\n### `ttsr_injection`\n\n```json\n{\n  \"type\": \"ttsr_injection\",\n  \"id\": \"c2d3e4f5\",\n  \"parentId\": \"b2c3d4e5\",\n  \"timestamp\": \"2026-02-16T10:28:00.000Z\",\n  \"injectedRules\": [\"ruleA\", \"ruleB\"]\n}\n```\n\n### `session_init`\n\n```json\n{\n  \"type\": \"session_init\",\n  \"id\": \"d2e3f4a5\",\n  \"parentId\": \"c2d3e4f5\",\n  \"timestamp\": \"2026-02-16T10:29:00.000Z\",\n  \"systemPrompt\": \"...\",\n  \"task\": \"...\",\n  \"tools\": [\"read\", \"edit\"],\n  \"outputSchema\": { \"type\": \"object\" }\n}\n```\n\n### `mode_change`\n\n```json\n{\n  \"type\": \"mode_change\",\n  \"id\": \"e2f3a4b5\",\n  \"parentId\": \"d2e3f4a5\",\n  \"timestamp\": \"2026-02-16T10:30:00.000Z\",\n  \"mode\": \"plan\",\n  \"data\": { \"planFile\": \"/tmp/plan.md\" }\n}\n```\n\n## การกำหนดเวอร์ชันและการย้าย\n\nเวอร์ชันเซสชันปัจจุบัน: `3`\n\n### v1 -> v2\n\nใช้เมื่อ header `version` ขาดหายหรือ `< 2`:\n\n- เพิ่ม `id` และ `parentId` ให้กับแต่ละรายการที่ไม่ใช่ header\n- สร้างห่วงโซ่ parent เชิงเส้นขึ้นใหม่โดยใช้ลำดับไฟล์\n- ย้ายฟิลด์ compaction `firstKeptEntryIndex` -> `firstKeptEntryId` เมื่อมีอยู่\n- กำหนด header `version = 2`\n\n### v2 -> v3\n\nใช้เมื่อ header `version < 3`:\n\n- สำหรับรายการ `message`: เขียน `message.role === \"hookMessage\"` แบบเดิมใหม่เป็น `\"custom\"`\n- กำหนด header `version = 3`\n\n### การเรียกใช้การย้ายและการคงอยู่\n\n- การย้ายจะทำงานระหว่างการโหลดเซสชัน (`setSessionFile`)\n- หากมีการย้ายใดๆ เกิดขึ้น ไฟล์ทั้งหมดจะถูกเขียนใหม่ลงดิสก์ทันที\n- การย้ายจะแก้ไขรายการในหน่วยความจำก่อน จากนั้นจึงคงไว้ซึ่ง JSONL ที่เขียนใหม่\n\n## พฤติกรรมการโหลดและความเข้ากันได้\n\nพฤติกรรมของ `loadEntriesFromFile(path)`:\n\n- ไม่พบไฟล์ (`ENOENT`) -> คืนค่า `[]`\n- บรรทัดที่ไม่สามารถแยกวิเคราะห์ได้จะถูกจัดการโดย lenient JSONL parser (`parseJsonlLenient`)\n- หากรายการที่แยกวิเคราะห์ครั้งแรกไม่ใช่ session header ที่ถูกต้อง (`type !== \"session\"` หรือ `id` สตริงหายไป) -> คืนค่า `[]`\n\nพฤติกรรมของ `SessionManager.setSessionFile()`:\n\n- `[]` จาก loader ถูกถือว่าเป็นเซสชันว่างเปล่า/ไม่มีอยู่และถูกแทนที่ด้วยไฟล์เซสชันที่เริ่มต้นใหม่ในเส้นทางนั้น\n- ไฟล์ที่ถูกต้องจะถูกโหลด ย้ายหากจำเป็น แก้ไข blob refs จากนั้นจึงจัดทำดัชนี\n\n## ความหมายของต้นไม้และใบไม้\n\nโมเดลพื้นฐานเป็นต้นไม้แบบ append-only + ตัวชี้ใบไม้ที่เปลี่ยนแปลงได้:\n\n- ทุกวิธี append จะสร้างรายการใหม่หนึ่งรายการซึ่ง `parentId` คือ `leafId` ปัจจุบัน\n- รายการใหม่จะกลายเป็น `leafId` ใหม่\n- `branch(entryId)` จะย้ายเฉพาะ `leafId` เท่านั้น รายการที่มีอยู่จะไม่เปลี่ยนแปลง\n- `resetLeaf()` กำหนด `leafId = null` การ append ครั้งถัดไปจะสร้างรายการรากใหม่ (`parentId: null`)\n- `branchWithSummary()` กำหนด leaf ไปยังเป้าหมายสาขาและ append รายการ `branch_summary`\n\n`getEntries()` คืนค่ารายการที่ไม่ใช่ header ทั้งหมดตามลำดับการแทรก รายการที่มีอยู่จะไม่ถูกลบในการดำเนินการปกติ การเขียนใหม่จะรักษาประวัติทางตรรกะไว้ในขณะที่อัปเดตการแสดงแทน (การย้าย การย้ายตำแหน่ง ผู้ช่วยเขียนใหม่แบบกำหนดเป้าหมาย)\n\n## การสร้างบริบทขึ้นมาใหม่ (`buildSessionContext`)\n\n`buildSessionContext(entries, leafId, byId?)` แก้ไขสิ่งที่ส่งไปยังโมเดล\n\nอัลกอริทึม:\n\n1. กำหนด leaf:\n   - `leafId === null` -> คืนค่าบริบทว่าง\n   - `leafId` ที่ระบุชัดเจน -> ใช้รายการนั้นหากพบ\n   - มิฉะนั้นให้ fallback ไปยังรายการสุดท้าย\n2. เดิน `parentId` chain จาก leaf ไปยัง root และย้อนกลับเป็นเส้นทาง root->leaf\n3. ดึงสถานะรันไทม์ตลอดเส้นทาง:\n   - `thinkingLevel` จาก `thinking_level_change` ล่าสุด (ค่าเริ่มต้น `\"off\"`)\n   - แผนที่โมเดลจากรายการ `model_change` (`role ?? \"default\"`)\n   - fallback `models.default` จาก provider/model ของข้อความ assistant หากไม่มีการเปลี่ยนโมเดลอย่างชัดเจน\n   - `injectedTtsrRules` ที่ไม่ซ้ำกันจากรายการ `ttsr_injection` ทั้งหมด\n   - mode/modeData จาก `mode_change` ล่าสุด (mode เริ่มต้น `\"none\"`)\n4. สร้างรายการข้อความ:\n   - รายการ `message` จะผ่านไปโดยตรง\n   - รายการ `custom_message` จะกลายเป็น `custom` AgentMessages ผ่าน `createCustomMessage`\n   - รายการ `branch_summary` จะกลายเป็น `branchSummary` AgentMessages ผ่าน `createBranchSummaryMessage`\n   - หากมี `compaction` อยู่บนเส้นทาง:\n     - ส่งออก compaction summary ก่อน (`createCompactionSummaryMessage`)\n     - ส่งออกรายการเส้นทางที่เริ่มต้นที่ `firstKeptEntryId` ไปจนถึงขอบเขต compaction\n     - ส่งออกรายการหลังขอบเขต compaction\n\nรายการ `custom` และ `session_init` จะไม่แทรกบริบทโมเดลโดยตรง\n\n## การรับประกันการคงอยู่และโมเดลความล้มเหลว\n\n### การคงอยู่เทียบกับในหน่วยความจำ\n\n- `SessionManager.create/open/continueRecent/forkFrom` -> โหมดคงอยู่ (`persist = true`)\n- `SessionManager.inMemory` -> โหมดไม่คงอยู่ (`persist = false`) พร้อม `MemorySessionStorage`\n\n### ไปป์ไลน์การเขียน\n\nการเขียนจะถูกดำเนินการตามลำดับผ่าน promise chain ภายใน (`#persistChain`) และ `NdjsonFileWriter`\n\n- `append*` อัปเดตสถานะในหน่วยความจำทันที\n- การคงอยู่จะถูกเลื่อนออกไปจนกว่าจะมีข้อความ assistant อย่างน้อยหนึ่งข้อความ\n  - ก่อนข้อความ assistant แรก: รายการจะถูกเก็บไว้ในหน่วยความจำ ไม่มีการ append ไฟล์เกิดขึ้น\n  - เมื่อมี assistant แรกแล้ว: เซสชันในหน่วยความจำทั้งหมดจะถูกส่งออกไปยังไฟล์\n  - หลังจากนั้น: รายการใหม่จะถูก append แบบเพิ่มทีละน้อย\n\nเหตุผลในโค้ด: หลีกเลี่ยงการคงเซสชันที่ไม่เคยสร้างการตอบสนอง assistant\n\n### การดำเนินการด้านความทนทาน\n\n- `flush()` ส่งออก writer และเรียก `fsync()`\n- การเขียนใหม่แบบ atomic เต็มรูปแบบ (`#rewriteFile`) เขียนไปยังไฟล์ชั่วคราว flush+fsync ปิด จากนั้น rename ทับเป้าหมาย\n- ใช้สำหรับการย้าย `setSessionName` `rewriteEntries` การดำเนินการย้าย และการเขียน tool-call arg ใหม่\n\n### พฤติกรรมเมื่อเกิดข้อผิดพลาด\n\n- ข้อผิดพลาดการคงอยู่จะถูกบันทึกไว้ (`#persistError`) และ rethrow ในการดำเนินการถัดไป\n- ข้อผิดพลาดแรกจะถูก log ครั้งเดียวพร้อมบริบทไฟล์เซสชัน\n- การปิด writer จะใช้ความพยายามอย่างดีที่สุดแต่จะส่งต่อข้อผิดพลาดที่มีความหมายแรก\n\n## การควบคุมขนาดข้อมูลและการแยกเก็บ Blob ภายนอก\n\nก่อนการคงรายการ:\n\n- สตริงขนาดใหญ่จะถูกตัดทอนให้เหลือ `MAX_PERSIST_CHARS` (500,000 chars) พร้อมประกาศ:\n  - `\"[Session persistence truncated large content]\"`\n- ฟิลด์ชั่วคราว `partialJson` และ `jsonlEvents` จะถูกลบออก\n- หากออบเจกต์มีทั้ง `content` และ `lineCount` จำนวนบรรทัดจะถูกคำนวณใหม่หลังการตัดทอน\n- บล็อกรูปภาพใน `content` arrays ที่มีความยาว base64 >= 1024 จะถูกแยกเก็บภายนอกเป็น blob refs:\n  - จัดเก็บเป็น `blob:sha256:<hash>`\n  - เขียน raw bytes ไปยัง blob store (`BlobStore.put`)\n\nเมื่อโหลด blob refs จะถูกแก้ไขกลับเป็น base64 สำหรับบล็อกรูปภาพ message/custom_message\n\n## นามธรรมของการจัดเก็บ\n\nอินเทอร์เฟซ `SessionStorage` ให้การดำเนินการระบบไฟล์ทั้งหมดที่ `SessionManager` ใช้:\n\n- sync: `ensureDirSync`, `existsSync`, `writeTextSync`, `statSync`, `listFilesSync`\n- async: `exists`, `readText`, `readTextPrefix`, `writeText`, `rename`, `unlink`, `openWriter`\n\nการนำไปใช้งาน:\n\n- `FileSessionStorage`: ระบบไฟล์จริง (Bun + node fs)\n- `MemorySessionStorage`: การนำไปใช้งานในหน่วยความจำแบบ map สำหรับการทดสอบ/เซสชันที่ไม่คงอยู่\n\n`SessionStorageWriter` เปิดเผย `writeLine`, `flush`, `fsync`, `close`, `getError`\n\n## ยูทิลิตี้การค้นหาเซสชัน\n\nกำหนดไว้ใน `session-manager.ts`:\n\n- `getRecentSessions(sessionDir, limit)` -> metadata แบบเบาสำหรับ UI/session picker\n- `findMostRecentSession(sessionDir)` -> ใหม่ที่สุดตาม mtime\n- `list(cwd, sessionDir?)` -> เซสชันในขอบเขตโปรเจกต์หนึ่ง\n- `listAll()` -> เซสชันในทุกขอบเขตโปรเจกต์ภายใต้ `~/.xcsh/agent/sessions`\n\nการดึง metadata จะอ่านเฉพาะส่วนนำหน้า (`readTextPrefix(..., 4096)`) เมื่อเป็นไปได้\n\n## ที่เกี่ยวข้องแต่แตกต่าง: การจัดเก็บประวัติ Prompt\n\n`HistoryStorage` (`history-storage.ts`) เป็นระบบย่อย SQLite แยกต่างหากสำหรับการเรียกคืน/ค้นหา prompt ไม่ใช่การ replay เซสชัน\n\n- DB: `~/.xcsh/agent/history.db`\n- Table: `history(id, prompt, created_at, cwd)`\n- FTS5 index: `history_fts` พร้อม trigger-maintained sync\n- ลบ prompt ที่เหมือนกันติดต่อกันโดยใช้ in-memory last-prompt cache\n- การแทรกแบบ async (`setImmediate`) เพื่อให้การจับ prompt ไม่บล็อกการดำเนินการ turn\n\nใช้ไฟล์เซสชันสำหรับ conversation graph/state replay ใช้ `HistoryStorage` สำหรับ UX ประวัติ prompt\n",
	"th/sessions/ttsr-injection-lifecycle.md": "---\ntitle: วงจรชีวิตการฉีด TTSR\ndescription: >-\n  วงจรชีวิตการฉีด TTSR (tool-use, tool-result, system-reminder)\n  สำหรับการจัดการบริบท\nsidebar:\n  order: 9\n  label: การฉีด TTSR\ni18n:\n  sourceHash: d6179a286584\n  translator: machine\n---\n\n# วงจรชีวิตการฉีด TTSR\n\nเอกสารนี้ครอบคลุมเส้นทางรันไทม์ปัจจุบันของ Time Traveling Stream Rules (TTSR) ตั้งแต่การค้นพบกฎไปจนถึงการหยุดกระแสข้อมูล การฉีดลองใหม่ การแจ้งเตือนส่วนขยาย และการจัดการสถานะเซสชัน\n\n## ไฟล์การนำไปใช้งาน\n\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/export/ttsr.ts`](../../packages/coding-agent/src/export/ttsr.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/prompts/system/ttsr-interrupt.md`](../../packages/coding-agent/src/prompts/system/ttsr-interrupt.md)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/types.ts`](../../packages/coding-agent/src/extensibility/extensions/types.ts)\n- [`../src/extensibility/hooks/types.ts`](../../packages/coding-agent/src/extensibility/hooks/types.ts)\n- [`../src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n\n## 1. ฟีดการค้นพบและการลงทะเบียนกฎ\n\nเมื่อสร้างเซสชัน `createAgentSession()` จะโหลดกฎที่ค้นพบทั้งหมดและสร้าง `TtsrManager`:\n\n```ts\nconst ttsrSettings = settings.getGroup(\"ttsr\");\nconst ttsrManager = new TtsrManager(ttsrSettings);\nconst rulesResult = await loadCapability<Rule>(ruleCapability.id, { cwd });\nfor (const rule of rulesResult.items) {\n  if (rule.ttsrTrigger) ttsrManager.addRule(rule);\n}\n```\n\n### พฤติกรรมการตัดรายการซ้ำก่อนลงทะเบียน\n\n`loadCapability(\"rules\")` ตัดรายการซ้ำโดยใช้ `rule.name` ด้วยหลักการ first-wins (ลำดับความสำคัญของผู้ให้บริการที่สูงกว่าก่อน) รายการที่ซ้ำซ้อนซึ่งถูกบดบังจะถูกลบออกก่อนการลงทะเบียน TTSR\n\n### พฤติกรรมของ `TtsrManager.addRule()`\n\nการลงทะเบียนจะถูกข้ามเมื่อ:\n\n- `rule.ttsrTrigger` ไม่มีอยู่\n- กฎที่มี `rule.name` เดียวกันถูกลงทะเบียนในตัวจัดการนี้แล้ว\n- regex ไม่สามารถคอมไพล์ได้ (`new RegExp(rule.ttsrTrigger)` เกิดข้อผิดพลาด)\n\nทริกเกอร์ regex ที่ไม่ถูกต้องจะถูกบันทึกเป็นคำเตือนและถูกเพิกเฉย การเริ่มต้นเซสชันจะดำเนินต่อไป\n\n### ข้อควรระวังเกี่ยวกับการตั้งค่า\n\n`TtsrSettings.enabled` ถูกโหลดเข้าสู่ตัวจัดการแต่ไม่ได้รับการตรวจสอบในการควบคุมรันไทม์ในปัจจุบัน หากกฎมีอยู่ การจับคู่จะยังคงทำงาน\n\n## 2. วงจรชีวิตของตัวตรวจสอบการสตรีม\n\nการตรวจจับ TTSR ทำงานภายใน `AgentSession.#handleAgentEvent`\n\n### เริ่มต้นเทิร์น\n\nเมื่อ `turn_start` บัฟเฟอร์กระแสข้อมูลจะถูกรีเซ็ต:\n\n- `ttsrManager.resetBuffer()`\n\n### ระหว่างการสตรีม (`message_update`)\n\nเมื่อการอัปเดตจากผู้ช่วยมาถึงและมีกฎอยู่:\n\n- ตรวจสอบ `text_delta` และ `toolcall_delta`\n- ผนวก delta เข้าสู่บัฟเฟอร์ของตัวจัดการ\n- เรียก `check(buffer)`\n\n`check()` จะวนซ้ำกฎที่ลงทะเบียนและส่งคืนกฎที่ตรงกันทั้งหมดซึ่งผ่านนโยบายการทำซ้ำ (`#canTrigger`)\n\n## 3. การตัดสินใจเรื่องทริกเกอร์และเส้นทางการยกเลิกทันที\n\nเมื่อกฎตั้งแต่หนึ่งกฎขึ้นไปตรงกัน:\n\n1. `markInjected(matches)` บันทึกชื่อกฎในสถานะการฉีดของตัวจัดการ\n2. กฎที่ตรงกันจะถูกจัดคิวใน `#pendingTtsrInjections`\n3. `#ttsrAbortPending = true`\n4. `agent.abort()` ถูกเรียกทันที\n5. เหตุการณ์ `ttsr_triggered` ถูกส่งออกแบบอะซิงโครนัส (fire-and-forget)\n6. งานลองใหม่ถูกตั้งเวลาผ่าน `setTimeout(..., 50)`\n\nการยกเลิกไม่ได้ถูกบล็อกโดยการเรียกกลับของส่วนขยาย\n\n## 4. การตั้งเวลาลองใหม่ โหมดบริบท และการฉีดตัวเตือน\n\nหลังจากหมดเวลา 50ms:\n\n1. `#ttsrAbortPending = false`\n2. อ่าน `ttsrManager.getSettings().contextMode`\n3. หาก `contextMode === \"discard\"` ให้ทิ้งผลลัพธ์บางส่วนของผู้ช่วยด้วย `agent.popMessage()`\n4. สร้างเนื้อหาการฉีดจากกฎที่รอดำเนินการโดยใช้เทมเพลต `ttsr-interrupt.md`\n5. ผนวกข้อความผู้ใช้สังเคราะห์ที่มีบล็อก `<system-interrupt ...>` หนึ่งบล็อกต่อกฎ\n6. เรียก `agent.continue()` เพื่อลองสร้างใหม่\n\nเพย์โหลดของเทมเพลตคือ:\n\n```xml\n<system-interrupt reason=\"rule_violation\" rule=\"{{name}}\" path=\"{{path}}\">\n...\n{{content}}\n</system-interrupt>\n```\n\nการฉีดที่รอดำเนินการจะถูกล้างหลังจากการสร้างเนื้อหา\n\n### พฤติกรรม `contextMode` ต่อผลลัพธ์บางส่วน\n\n- `discard`: ข้อความผู้ช่วยที่บางส่วน/ถูกยกเลิกจะถูกลบออกก่อนลองใหม่\n- `keep`: ผลลัพธ์บางส่วนของผู้ช่วยยังคงอยู่ในสถานะการสนทนา ตัวเตือนจะถูกผนวกต่อท้าย\n\n## 5. นโยบายการทำซ้ำและตรรกะช่วงห่าง\n\n`TtsrManager` ติดตาม `#messageCount` และ `lastInjectedAt` ต่อกฎ\n\n### `repeatMode: \"once\"`\n\nกฎสามารถทริกเกอร์ได้ครั้งเดียวหลังจากมีบันทึกการฉีด\n\n### `repeatMode: \"after-gap\"`\n\nกฎสามารถทริกเกอร์ซ้ำได้เฉพาะเมื่อ:\n\n- `messageCount - lastInjectedAt >= repeatGap`\n\n`messageCount` เพิ่มขึ้นเมื่อ `turn_end` ดังนั้นช่วงห่างจะวัดเป็นจำนวนเทิร์นที่เสร็จสมบูรณ์ ไม่ใช่ชิ้นส่วนของกระแสข้อมูล\n\n## 6. การส่งเหตุการณ์และพื้นผิวส่วนขยาย/hook\n\n### เหตุการณ์เซสชัน\n\n`AgentSessionEvent` ประกอบด้วย:\n\n```ts\n{ type: \"ttsr_triggered\"; rules: Rule[] }\n```\n\n### ตัวรันส่วนขยาย\n\n`#emitSessionEvent()` ส่งต่อเหตุการณ์ไปยัง:\n\n- ผู้ฟังส่วนขยาย (`ExtensionRunner.emit({ type: \"ttsr_triggered\", rules })`)\n- ผู้สมัครรับข้อมูลเซสชันภายในเครื่อง\n\n### การกำหนดประเภท hook และเครื่องมือที่กำหนดเอง\n\n- API ส่วนขยายเปิดเผย `on(\"ttsr_triggered\", ...)`\n- API hook เปิดเผย `on(\"ttsr_triggered\", ...)`\n- เครื่องมือที่กำหนดเองรับ `onSession({ reason: \"ttsr_triggered\", rules })`\n\n### ความแตกต่างในการแสดงผลโหมดอินเทอร์แอคทีฟ\n\nโหมดอินเทอร์แอคทีฟใช้ `session.isTtsrAbortPending` เพื่อระงับการแสดงเหตุผลการหยุดของผู้ช่วยที่ถูกยกเลิกเป็นความล้มเหลวที่มองเห็นได้ในระหว่างการหยุดชะงักของ TTSR และแสดง `TtsrNotificationComponent` เมื่อเหตุการณ์มาถึง\n\n## 7. สถานะการคงอยู่และการกลับมาดำเนินการ (การนำไปใช้งานปัจจุบัน)\n\n`SessionManager` มีการสนับสนุนสคีมาเต็มรูปแบบสำหรับการคงอยู่ของกฎที่ฉีดแล้ว:\n\n- ประเภทรายการ: `ttsr_injection`\n- API เพิ่มเติม: `appendTtsrInjection(ruleNames)`\n- API สอบถาม: `getInjectedTtsrRules()`\n- การสร้างบริบทใหม่รวมถึง `SessionContext.injectedTtsrRules`\n\n`TtsrManager` ยังรองรับการกู้คืนผ่าน `restoreInjected(ruleNames)`\n\n### สถานะการเชื่อมต่อในปัจจุบัน\n\nในเส้นทางรันไทม์ปัจจุบัน:\n\n- `AgentSession` ไม่ได้ผนวกรายการ `ttsr_injection` เมื่อ TTSR ทริกเกอร์\n- `createAgentSession()` ไม่ได้กู้คืน `existingSession.injectedTtsrRules` กลับเข้าสู่ `ttsrManager`\n\nผลกระทบสุทธิ: การระงับกฎที่ฉีดแล้วจะถูกบังคับใช้ในหน่วยความจำสำหรับกระบวนการที่ทำงานอยู่ แต่ปัจจุบันยังไม่ได้รับการคงอยู่/กู้คืนข้ามการโหลด/กลับมาดำเนินการเซสชันในเส้นทางนี้\n\n## 8. ขอบเขตการแข่งขันและการรับประกันลำดับ\n\n### การยกเลิกเทียบกับการเรียกกลับลองใหม่\n\n- การยกเลิกเป็นแบบซิงโครนัสจากมุมมองของตัวจัดการ TTSR (`agent.abort()` ถูกเรียกทันที)\n- การลองใหม่ถูกเลื่อนออกไปด้วยตัวจับเวลา (`50ms`)\n- การแจ้งเตือนส่วนขยายเป็นแบบอะซิงโครนัสและตั้งใจไม่รอก่อนการตั้งเวลายกเลิก/ลองใหม่\n\n### การจับคู่หลายรายการในหน้าต่างกระแสข้อมูลเดียวกัน\n\n`check()` ส่งคืนกฎที่มีสิทธิ์ตรงกันทั้งหมดในปัจจุบัน กฎเหล่านี้จะถูกฉีดเป็นชุดในข้อความลองใหม่ครั้งถัดไป\n\n### ระหว่างการยกเลิกและการดำเนินการต่อ\n\nในช่วงหน้าต่างตัวจับเวลา สถานะอาจเปลี่ยนแปลงได้ (การหยุดชะงักของผู้ใช้ การดำเนินการโหมด เหตุการณ์เพิ่มเติม) การเรียกลองใหม่เป็นแบบ best-effort: `agent.continue().catch(() => {})` กลืนข้อผิดพลาดที่ตามมา\n\n## 9. สรุปกรณีขอบ\n\n- regex `ttsr_trigger` ที่ไม่ถูกต้อง: ถูกข้ามพร้อมคำเตือน กฎอื่นๆ ยังคงดำเนินต่อไป\n- ชื่อกฎที่ซ้ำกันในเลเยอร์ความสามารถ: รายการที่ซ้ำซึ่งมีลำดับความสำคัญต่ำกว่าจะถูกบดบังก่อนการลงทะเบียน\n- ชื่อที่ซ้ำกันในเลเยอร์ตัวจัดการ: การลงทะเบียนครั้งที่สองจะถูกเพิกเฉย\n- `contextMode: \"keep\"`: ผลลัพธ์บางส่วนที่ละเมิดอาจยังคงอยู่ในบริบทก่อนการลองใหม่ด้วยตัวเตือน\n- การทำซ้ำหลังช่วงห่างขึ้นอยู่กับการเพิ่มจำนวนเทิร์นเมื่อ `turn_end` ชิ้นส่วนกลางเทิร์นจะไม่เพิ่มตัวนับช่วงห่าง\n",
	"th/tui/theme.md": "---\ntitle: เอกสารอ้างอิงการปรับแต่งธีม\ndescription: >-\n  เอกสารอ้างอิงการปรับแต่งธีม TUI พร้อมโทเค็นสี การตั้งค่าฟอนต์\n  และการปรับแต่งธีม\nsidebar:\n  order: 3\n  label: การปรับแต่งธีม\ni18n:\n  sourceHash: 7e962a7da157\n  translator: machine\n---\n\n# เอกสารอ้างอิงการปรับแต่งธีม\n\nเอกสารนี้อธิบายการทำงานของระบบธีมใน coding-agent ในปัจจุบัน ได้แก่ สคีมา การโหลด พฤติกรรมระหว่างรันไทม์ และรูปแบบความล้มเหลว\n\n## สิ่งที่ระบบธีมควบคุม\n\nระบบธีมขับเคลื่อน:\n\n- โทเค็นสีพื้นหน้า/พื้นหลังที่ใช้ทั่วทั้ง TUI\n- อะแดปเตอร์การจัดรูปแบบ markdown (`getMarkdownTheme()`)\n- อะแดปเตอร์รายการตัวเลือก/ตัวแก้ไข/รายการการตั้งค่า (`getSelectListTheme()`, `getEditorTheme()`, `getSettingsListTheme()`)\n- ชุดสัญลักษณ์พรีเซ็ต + การแทนที่สัญลักษณ์ (`unicode`, `nerd`, `ascii`)\n- สีการไฮไลต์ไวยากรณ์ที่ใช้โดย native highlighter (`@f5-sales-demo/pi-natives`)\n- สีของเซกเมนต์บรรทัดสถานะ\n\nการดำเนินการหลัก: `src/modes/theme/theme.ts`\n\n## รูปแบบ JSON ของธีม\n\nไฟล์ธีมเป็นออบเจ็กต์ JSON ที่ถูกตรวจสอบความถูกต้องกับสคีมารันไทม์ใน `theme.ts` (`ThemeJsonSchema`) และสะท้อนโดย `src/modes/theme/theme-schema.json`\n\nฟิลด์ระดับบนสุด:\n\n- `name` (จำเป็น)\n- `colors` (จำเป็น; โทเค็นสีทั้งหมดจำเป็น)\n- `vars` (ไม่บังคับ; ตัวแปรสีที่ใช้ซ้ำได้)\n- `export` (ไม่บังคับ; สีสำหรับการส่งออก HTML)\n- `symbols` (ไม่บังคับ)\n  - `preset` (ไม่บังคับ: `unicode | nerd | ascii`)\n  - `overrides` (ไม่บังคับ: การแทนที่ค่าคีย์สำหรับ `SymbolKey`)\n\nค่าสีรองรับ:\n\n- สตริง hex (`\"#RRGGBB\"`)\n- ดัชนีสี 256 สี (`0..255`)\n- สตริงอ้างอิงตัวแปร (แก้ไขผ่าน `vars`)\n- สตริงว่าง (`\"\"`) หมายถึงค่าเริ่มต้นของเทอร์มินัล (`\\x1b[39m` fg, `\\x1b[49m` bg)\n\n## โทเค็นสีที่จำเป็น (ปัจจุบัน)\n\nโทเค็นทั้งหมดด้านล่างจำเป็นต้องมีใน `colors`\n\n### ข้อความหลักและขอบ (11)\n\n`accent`, `border`, `borderAccent`, `borderMuted`, `success`, `error`, `warning`, `muted`, `dim`, `text`, `thinkingText`\n\n### บล็อกพื้นหลัง (7)\n\n`selectedBg`, `userMessageBg`, `customMessageBg`, `toolPendingBg`, `toolSuccessBg`, `toolErrorBg`, `statusLineBg`\n\n### ข้อความข้อความ/เครื่องมือ (5)\n\n`userMessageText`, `customMessageText`, `customMessageLabel`, `toolTitle`, `toolOutput`\n\n### Markdown (10)\n\n`mdHeading`, `mdLink`, `mdLinkUrl`, `mdCode`, `mdCodeBlock`, `mdCodeBlockBorder`, `mdQuote`, `mdQuoteBorder`, `mdHr`, `mdListBullet`\n\n### Tool diff + การไฮไลต์ไวยากรณ์ (12)\n\n`toolDiffAdded`, `toolDiffRemoved`, `toolDiffContext`,\n`syntaxComment`, `syntaxKeyword`, `syntaxFunction`, `syntaxVariable`, `syntaxString`, `syntaxNumber`, `syntaxType`, `syntaxOperator`, `syntaxPunctuation`\n\n### ขอบโหมด/การคิด (8)\n\n`thinkingOff`, `thinkingMinimal`, `thinkingLow`, `thinkingMedium`, `thinkingHigh`, `thinkingXhigh`, `bashMode`, `pythonMode`\n\n### สีของเซกเมนต์บรรทัดสถานะ (14)\n\n`statusLineSep`, `statusLineModel`, `statusLinePath`, `statusLineGitClean`, `statusLineGitDirty`, `statusLineContext`, `statusLineSpend`, `statusLineStaged`, `statusLineDirty`, `statusLineUntracked`, `statusLineOutput`, `statusLineCost`, `statusLineSubagents`\n\n## โทเค็นที่ไม่บังคับ\n\n### ส่วน `export` (ไม่บังคับ)\n\nใช้สำหรับตัวช่วยการปรับแต่งธีมการส่งออก HTML:\n\n- `export.pageBg`\n- `export.cardBg`\n- `export.infoBg`\n\nหากละไว้ โค้ดส่งออกจะดึงค่าเริ่มต้นจากสีธีมที่แก้ไขแล้ว\n\n### ส่วน `symbols` (ไม่บังคับ)\n\n- `symbols.preset` กำหนดชุดสัญลักษณ์เริ่มต้นระดับธีม\n- `symbols.overrides` สามารถแทนที่ค่า `SymbolKey` แต่ละค่าได้\n\nลำดับความสำคัญรันไทม์:\n\n1. การแทนที่ `symbolPreset` ในการตั้งค่า (ถ้ากำหนดไว้)\n2. `symbols.preset` ใน theme JSON\n3. ค่าสำรอง `\"unicode\"`\n\nคีย์การแทนที่ที่ไม่ถูกต้องจะถูกละเว้นและบันทึกลอก (`logger.debug`)\n\n## แหล่งธีมในตัว vs ธีมที่กำหนดเอง\n\nลำดับการค้นหาธีม (`loadThemeJson`):\n\n1. ธีมที่ฝังในตัว (`defaults/xcsh-dark.json` และ `defaults/xcsh-light.json` ที่คอมไพล์เป็น `defaultThemes`)\n2. ไฟล์ธีมที่กำหนดเอง: `<customThemesDir>/<name>.json`\n\nไดเรกทอรีธีมที่กำหนดเองมาจาก `getCustomThemesDir()`:\n\n- ค่าเริ่มต้น: `~/.xcsh/agent/themes`\n- แทนที่ด้วย `PI_CODING_AGENT_DIR` (`$PI_CODING_AGENT_DIR/themes`)\n\n`getAvailableThemes()` ส่งคืนชื่อที่รวมธีมในตัว + ธีมที่กำหนดเองเรียงลำดับแล้ว โดยธีมในตัวมีความสำคัญก่อนเมื่อชื่อซ้ำกัน\n\n## การโหลด การตรวจสอบ และการแก้ไข\n\nสำหรับไฟล์ธีมที่กำหนดเอง:\n\n1. อ่าน JSON\n2. แยกวิเคราะห์ JSON\n3. ตรวจสอบกับ `ThemeJsonSchema`\n4. แก้ไขการอ้างอิง `vars` แบบเรียกซ้ำ\n5. แปลงค่าที่แก้ไขแล้วเป็น ANSI ตามโหมดความสามารถของเทอร์มินัล\n\nพฤติกรรมการตรวจสอบ:\n\n- โทเค็นสีที่จำเป็นขาดหายไป: ข้อความแสดงข้อผิดพลาดแบบจัดกลุ่มชัดเจน\n- ประเภท/ค่าโทเค็นไม่ถูกต้อง: ข้อผิดพลาดการตรวจสอบพร้อมพาธ JSON\n- ไม่พบไฟล์ธีม: `Theme not found: <name>`\n\nพฤติกรรมการอ้างอิงตัวแปร:\n\n- รองรับการอ้างอิงแบบซ้อน\n- ส่งข้อผิดพลาดเมื่อการอ้างอิงตัวแปรขาดหายไป\n- ส่งข้อผิดพลาดเมื่อมีการอ้างอิงแบบวงกลม\n\n## พฤติกรรมโหมดสีของเทอร์มินัล\n\nการตรวจจับโหมดสี (`detectColorMode`):\n\n- `COLORTERM=truecolor|24bit` => truecolor\n- `WT_SESSION` => truecolor\n- `TERM` ใน `dumb`, `linux`, หรือว่างเปล่า => 256color\n- มิฉะนั้น => truecolor\n\nพฤติกรรมการแปลง:\n\n- hex -> `Bun.color(..., \"ansi-16m\" | \"ansi-256\")`\n- ตัวเลข -> `38;5` / `48;5` ANSI\n- `\"\"` -> รีเซ็ต fg/bg เริ่มต้น\n\n## พฤติกรรมการสลับระหว่างรันไทม์\n\n### ธีมเริ่มต้น (`initTheme`)\n\n`main.ts` เริ่มต้นธีมด้วยการตั้งค่า:\n\n- `symbolPreset`\n- `colorBlindMode`\n- `theme.dark`\n- `theme.light`\n\nการเลือกสล็อตธีมอัตโนมัติใช้การตรวจจับพื้นหลัง `COLORFGBG`:\n\n- แยกวิเคราะห์ดัชนีพื้นหลังจาก `COLORFGBG`\n- `< 8` => สล็อตมืด (`theme.dark`)\n- `>= 8` => สล็อตสว่าง (`theme.light`)\n- การแยกวิเคราะห์ล้มเหลว => สล็อตมืด\n\nค่าเริ่มต้นปัจจุบันจากสคีมาการตั้งค่า:\n\n- `theme.dark = \"xcsh-dark\"`\n- `theme.light = \"xcsh-light\"`\n- `symbolPreset = \"unicode\"`\n- `colorBlindMode = false`\n\n### การสลับโดยตรง (`setTheme`)\n\n- โหลดธีมที่เลือก\n- อัปเดต singleton `theme` ส่วนกลาง\n- เริ่มต้น watcher ตามต้องการ\n- เรียกใช้ callback `onThemeChange`\n\nเมื่อล้มเหลว:\n\n- ใช้ธีม `dark` ในตัวเป็นตัวสำรอง\n- ส่งคืน `{ success: false, error }`\n\n### การสลับตัวอย่าง (`previewTheme`)\n\n- ใช้ธีมตัวอย่างชั่วคราวกับ `theme` ส่วนกลาง\n- **ไม่** เปลี่ยนแปลงการตั้งค่าที่บันทึกไว้ด้วยตัวเอง\n- ส่งคืนสำเร็จ/ข้อผิดพลาดโดยไม่มีการแทนที่ fallback\n\nUI การตั้งค่าใช้สิ่งนี้สำหรับการแสดงตัวอย่างแบบสด และกู้คืนธีมก่อนหน้าเมื่อยกเลิก\n\n## Watchers และการโหลดซ้ำแบบสด\n\nเมื่อเปิดใช้งาน watcher (`setTheme(..., true)` / การเริ่มต้นแบบอินเทอร์แอคทีฟ):\n\n- ดูเฉพาะพาธไฟล์ที่กำหนดเอง `<customThemesDir>/<currentTheme>.json`\n- ธีมในตัวแทบไม่ถูกดู\n- ไฟล์ `change`: พยายามโหลดซ้ำ (debounced)\n- ไฟล์ `rename`/ลบ: ใช้ `dark` เป็นตัวสำรอง ปิด watcher\n\nโหมดอัตโนมัติยังติดตั้ง listener `SIGWINCH` และสามารถประเมินการแมปสล็อตมืด/สว่างใหม่เมื่อสถานะเทอร์มินัลเปลี่ยนแปลง\n\n## พฤติกรรมโหมดตาบอดสี\n\n`colorBlindMode` เปลี่ยนเพียงหนึ่งโทเค็นระหว่างรันไทม์:\n\n- `toolDiffAdded` ถูกปรับด้วย HSV (เขียวเปลี่ยนไปทางน้ำเงิน)\n- การปรับใช้เฉพาะเมื่อค่าที่แก้ไขแล้วเป็นสตริง hex\n\nโทเค็นอื่นไม่เปลี่ยนแปลง\n\n## ตำแหน่งที่บันทึกการตั้งค่าธีม\n\nการตั้งค่าที่เกี่ยวข้องกับธีมถูกบันทึกโดย `Settings` ไปยัง YAML การกำหนดค่าส่วนกลาง:\n\n- พาธ: `<agentDir>/config.yml`\n- ไดเรกทอรี agent เริ่มต้น: `~/.xcsh/agent`\n- ไฟล์เริ่มต้นที่มีผล: `~/.xcsh/agent/config.yml`\n\nคีย์ที่บันทึก:\n\n- `theme.dark`\n- `theme.light`\n- `symbolPreset`\n- `colorBlindMode`\n\nมีการย้ายข้อมูลเดิม: `theme: \"name\"` แบบแบนเดิมถูกย้ายไปเป็น `theme.dark` หรือ `theme.light` แบบซ้อนตามการตรวจจับความสว่าง\n\n## การสร้างธีมที่กำหนดเอง (เชิงปฏิบัติ)\n\n1. สร้างไฟล์ในไดเรกทอรีธีมที่กำหนดเอง เช่น `~/.xcsh/agent/themes/my-theme.json`\n2. รวม `name`, `vars` ที่ไม่บังคับ และโทเค็น `colors` **ทั้งหมดที่จำเป็น**\n3. รวม `symbols` และ `export` ตามต้องการ\n4. เลือกธีมในการตั้งค่า (`Display -> Dark theme` หรือ `Display -> Light theme`) ขึ้นอยู่กับสล็อตอัตโนมัติที่ต้องการ\n\nโครงสร้างขั้นต่ำ ทุกคีย์ใน `colors` จำเป็น — ตัวตรวจสอบรันไทม์\n(`additionalProperties: false`) ปฏิเสธทั้งคีย์ที่ขาดหายไปและคีย์ที่ไม่รู้จัก\nสำหรับการดำเนินการอ้างอิงที่จัดส่งแล้ว ดูที่\n[`packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json)\nและ [`xcsh-light.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-light.json)\n\nบรรทัดสถานะมีระบบสีสองแบบคู่ขนานที่บันทึกไว้ในปัญหา #242:\n\n- สีข้อความ hex (`statusLinePath`, `statusLineGitClean`, `statusLineGitDirty`,\n  `statusLineStaged`, `statusLineDirty`, `statusLineUntracked`) ขับเคลื่อนการเรนเดอร์แบบไม่ใช้ powerline\n- ดัชนีพาเลตต์สี 256 สี (`statusLine<Segment>Bg` / `statusLine<Segment>Fg`)\n  ขับเคลื่อนการเติม powerline segment เป็นอิสระจากคีย์ hex ด้านบน —\n  ทั้งสองต้องถูกกำหนด\n\n```json\n{\n  \"name\": \"my-theme\",\n  \"vars\": {\n    \"accent\": \"#7aa2f7\",\n    \"muted\": 244\n  },\n  \"colors\": {\n    \"accent\": \"accent\",\n    \"chromeAccent\": \"accent\",\n    \"spinnerAccent\": \"accent\",\n    \"contentAccent\": \"muted\",\n    \"border\": \"#4c566a\",\n    \"borderAccent\": \"accent\",\n    \"borderMuted\": \"muted\",\n    \"success\": \"#9ece6a\",\n    \"error\": \"#f7768e\",\n    \"warning\": \"#e0af68\",\n    \"muted\": \"muted\",\n    \"dim\": 240,\n    \"gutterSuccess\": \"#7dcfff\",\n    \"gutterWarning\": \"#e0af68\",\n    \"text\": \"\",\n    \"thinkingText\": \"muted\",\n\n    \"selectedBg\": \"#2a2f45\",\n    \"userMessageBg\": \"#1f2335\",\n    \"userMessageText\": \"\",\n    \"customMessageBg\": \"#24283b\",\n    \"customMessageText\": \"\",\n    \"customMessageLabel\": \"accent\",\n    \"toolPendingBg\": \"#1f2335\",\n    \"toolSuccessBg\": \"#1f2d2a\",\n    \"toolErrorBg\": \"#2d1f2a\",\n    \"toolTitle\": \"\",\n    \"toolOutput\": \"muted\",\n\n    \"mdHeading\": \"accent\",\n    \"mdLink\": \"accent\",\n    \"mdLinkUrl\": \"muted\",\n    \"mdCode\": \"#c0caf5\",\n    \"mdCodeBlock\": \"#c0caf5\",\n    \"mdCodeBlockBorder\": \"muted\",\n    \"mdQuote\": \"muted\",\n    \"mdQuoteBorder\": \"muted\",\n    \"mdHr\": \"muted\",\n    \"mdListBullet\": \"accent\",\n\n    \"toolDiffAdded\": \"#9ece6a\",\n    \"toolDiffRemoved\": \"#f7768e\",\n    \"toolDiffContext\": \"muted\",\n\n    \"syntaxComment\": \"#565f89\",\n    \"syntaxKeyword\": \"#bb9af7\",\n    \"syntaxFunction\": \"#7aa2f7\",\n    \"syntaxVariable\": \"#c0caf5\",\n    \"syntaxString\": \"#9ece6a\",\n    \"syntaxNumber\": \"#ff9e64\",\n    \"syntaxType\": \"#2ac3de\",\n    \"syntaxOperator\": \"#89ddff\",\n    \"syntaxPunctuation\": \"#9aa5ce\",\n    \"syntaxControl\": \"#bb9af7\",\n\n    \"thinkingOff\": 240,\n    \"thinkingMinimal\": 244,\n    \"thinkingLow\": \"#7aa2f7\",\n    \"thinkingMedium\": \"#2ac3de\",\n    \"thinkingHigh\": \"#bb9af7\",\n    \"thinkingXhigh\": \"#f7768e\",\n\n    \"bashMode\": \"#2ac3de\",\n    \"pythonMode\": \"#bb9af7\",\n\n    \"statusLineBg\": \"#16161e\",\n    \"statusLineSep\": 240,\n    \"statusLineModel\": \"#bb9af7\",\n    \"statusLinePath\": \"#7aa2f7\",\n    \"statusLineGitClean\": \"#9ece6a\",\n    \"statusLineGitDirty\": \"#e0af68\",\n    \"statusLineContext\": \"#2ac3de\",\n    \"statusLineSpend\": \"#7dcfff\",\n    \"statusLineStaged\": \"#9ece6a\",\n    \"statusLineDirty\": \"#e0af68\",\n    \"statusLineUntracked\": \"#f7768e\",\n    \"statusLineOutput\": \"#c0caf5\",\n    \"statusLineCost\": \"#ff9e64\",\n    \"statusLineSubagents\": \"#bb9af7\",\n\n    \"statusLineOsIconBg\": 7,\n    \"statusLineOsIconFg\": 232,\n    \"statusLinePathBg\": 4,\n    \"statusLinePathFg\": 254,\n    \"statusLineGitCleanBg\": 2,\n    \"statusLineGitCleanFg\": 0,\n    \"statusLineGitDirtyBg\": 3,\n    \"statusLineGitDirtyFg\": 0,\n    \"statusLineGitStagedBg\": 64,\n    \"statusLineGitStagedFg\": 0,\n    \"statusLineGitUntrackedBg\": 39,\n    \"statusLineGitUntrackedFg\": 0,\n    \"statusLineGitConflictBg\": 1,\n    \"statusLineGitConflictFg\": 7,\n    \"statusLinePlanModeBg\": 236,\n    \"statusLinePlanModeFg\": 117,\n    \"statusLineProfileXcshBg\": \"accent\",\n    \"statusLineProfileXcshFg\": 231\n  }\n}\n```\n\n## การทดสอบธีมที่กำหนดเอง\n\nใช้ขั้นตอนนี้:\n\n1. เริ่มโหมดอินเทอร์แอคทีฟ (เปิดใช้งาน watcher ตั้งแต่เริ่มต้น)\n2. เปิดการตั้งค่าและดูตัวอย่างค่าธีม (สด `previewTheme`)\n3. สำหรับไฟล์ธีมที่กำหนดเอง ให้แก้ไข JSON ขณะรันและยืนยันการโหลดซ้ำอัตโนมัติเมื่อบันทึก\n4. ทดสอบพื้นผิวสำคัญ:\n   - การเรนเดอร์ markdown\n   - บล็อก tool (pending/success/error)\n   - การเรนเดอร์ diff (added/removed/context)\n   - ความอ่านออกได้ของบรรทัดสถานะ\n   - การเปลี่ยนแปลงขอบระดับการคิด\n   - สีขอบโหมด bash/python\n5. ตรวจสอบชุดสัญลักษณ์ทั้งสองหากธีมของคุณขึ้นอยู่กับความกว้าง/ลักษณะของ glyph\n\n## ข้อจำกัดและข้อควรระวังที่แท้จริง\n\n- โทเค็น `colors` ทั้งหมดจำเป็นสำหรับธีมที่กำหนดเอง\n- `export` และ `symbols` ไม่บังคับ\n- `$schema` ใน theme JSON เป็นเพียงข้อมูล; การตรวจสอบรันไทม์ถูกบังคับใช้โดยสคีมา TypeBox ที่คอมไพล์แล้วในโค้ด\n- ความล้มเหลวของ `setTheme` ใช้ `dark` เป็นตัวสำรอง; ความล้มเหลวของ `previewTheme` ไม่แทนที่ธีมปัจจุบัน\n- ข้อผิดพลาดการโหลดซ้ำของ file watcher จะรักษาธีมที่โหลดอยู่ปัจจุบันจนกว่าการโหลดซ้ำสำเร็จหรือมีการเรียกพาธ fallback\n",
	"th/tui/tree.md": "---\ntitle: คู่มืออ้างอิงคำสั่ง Tree\ndescription: คู่มืออ้างอิงคำสั่ง /tree สำหรับการแสดงภาพประวัติเซสชันและสาขาการสนทนา\nsidebar:\n  order: 4\n  label: คำสั่ง /tree\ni18n:\n  sourceHash: ee0e412fe993\n  translator: machine\n---\n\n# คู่มืออ้างอิงคำสั่ง `/tree`\n\n`/tree` เปิดตัวนำทาง **Session Tree** แบบโต้ตอบ ซึ่งช่วยให้คุณข้ามไปยังรายการใดก็ได้ในไฟล์เซสชันปัจจุบันและดำเนินการต่อจากจุดนั้น\n\nนี่คือการย้าย leaf ภายในไฟล์เดียวกัน ไม่ใช่การส่งออกเซสชันใหม่\n\n## สิ่งที่ `/tree` ทำ\n\n- สร้างต้นไม้จากรายการเซสชันปัจจุบัน (`SessionManager.getTree()`)\n- เปิด `TreeSelectorComponent` พร้อมการนำทางด้วยแป้นพิมพ์ ตัวกรอง และการค้นหา\n- เมื่อเลือก จะเรียก `AgentSession.navigateTree(targetId, { summarize, customInstructions })`\n- สร้างการแชทที่มองเห็นได้ใหม่จาก leaf path ใหม่\n- เติมข้อความในตัวแก้ไขล่วงหน้าเมื่อเลือกข้อความของผู้ใช้/ข้อความกำหนดเอง (เป็นตัวเลือก)\n\nการนำไปใช้งานหลัก:\n\n- `src/modes/controllers/input-controller.ts` (การเชื่อมสาย `/tree`, keybinding, พฤติกรรม double-escape)\n- `src/modes/controllers/selector-controller.ts` (การเปิด UI ต้นไม้ + กระบวนการแจ้งสรุป)\n- `src/modes/components/tree-selector.ts` (การนำทาง, ตัวกรอง, การค้นหา, ป้ายกำกับ, การแสดงผล)\n- `src/session/agent-session.ts` (การสลับ leaf `navigateTree` + สรุปเป็นตัวเลือก)\n- `src/session/session-manager.ts` (`getTree`, `branch`, `branchWithSummary`, `resetLeaf`, การคงไว้ซึ่งป้ายกำกับ)\n\n## วิธีเปิด\n\nสิ่งใดสิ่งหนึ่งต่อไปนี้จะเปิดตัวเลือกเดียวกัน:\n\n- `/tree`\n- การกระทำ keybinding ที่กำหนดค่า `tree`\n- double-escape บนตัวแก้ไขว่างเมื่อ `doubleEscapeAction = \"tree\"` (ค่าเริ่มต้น)\n- `/branch` เมื่อ `doubleEscapeAction = \"tree\"` (นำไปยังตัวเลือกต้นไม้แทนตัวเลือกสาขาเฉพาะผู้ใช้)\n\n## โมเดล UI ของต้นไม้\n\nต้นไม้จะถูกแสดงผลจากพอยน์เตอร์ parent ของรายการเซสชัน (`id` / `parentId`)\n\n- ลูกจะถูกเรียงลำดับตามเวลาประทับจากน้อยไปมาก (เก่ากว่าก่อน ใหม่กว่าต่ำกว่า)\n- สาขาที่ใช้งานอยู่ (เส้นทางจาก root ถึง leaf ปัจจุบัน) จะถูกทำเครื่องหมายด้วยจุด\n- ป้ายกำกับ (ถ้ามี) จะแสดงเป็น `[label]` ก่อนข้อความของโหนด\n- หากมี root หลายตัว (parent chain ที่ถูกตัดขาด/เสีย) จะแสดงภายใต้ virtual branching root\n\n```text\nตัวอย่างมุมมองต้นไม้ (เส้นทางที่ใช้งานอยู่ทำเครื่องหมายด้วย •):\n\n├─ user: \"Start task\"\n│  └─ assistant: \"Plan\"\n│     ├─ • user: \"Try approach A\"\n│     │  └─ • assistant: \"A result\"\n│     │     └─ • [milestone] user: \"Continue A\"\n│     └─ user: \"Try approach B\"\n│        └─ assistant: \"B result\"\n```\n\nตัวเลือกจะจัดกึ่งกลางรอบการเลือกปัจจุบันและแสดงได้สูงสุด:\n\n- `max(5, floor(terminalHeight / 2))` แถว\n\n## Keybinding ภายในตัวเลือกต้นไม้\n\n- `Up` / `Down`: ย้ายการเลือก (วนรอบ)\n- `Left` / `Right`: เลื่อนหน้าขึ้น / เลื่อนหน้าลง\n- `Enter`: เลือกโหนด\n- `Esc`: ล้างการค้นหาหากใช้งานอยู่ มิฉะนั้นปิดตัวเลือก\n- `Ctrl+C`: ปิดตัวเลือก\n- `Type`: ต่อท้ายคำค้นหา\n- `Backspace`: ลบอักขระค้นหา\n- `Shift+L`: แก้ไข/ล้างป้ายกำกับบนรายการที่เลือก\n- `Ctrl+O`: วนรอบตัวกรองไปข้างหน้า\n- `Shift+Ctrl+O`: วนรอบตัวกรองไปข้างหลัง\n- `Alt+D/T/U/L/A`: ข้ามไปยังโหมดตัวกรองเฉพาะโดยตรง\n\n## ตัวกรองและความหมายการค้นหา\n\nโหมดตัวกรอง (`TreeList`):\n\n1. `default`\n2. `no-tools`\n3. `user-only`\n4. `labeled-only`\n5. `all`\n\n### `default`\n\nแสดงโหนดการสนทนาส่วนใหญ่ แต่ซ่อนประเภทรายการจัดเก็บบัญชี:\n\n- `label`\n- `custom`\n- `model_change`\n- `thinking_level_change`\n\n### `no-tools`\n\nเหมือนกับ `default` แต่ซ่อนข้อความ `toolResult` เพิ่มเติม\n\n### `user-only`\n\nเฉพาะรายการ `message` ที่ role เป็น `user`\n\n### `labeled-only`\n\nเฉพาะรายการที่ปัจจุบันแก้ไขเป็นป้ายกำกับ\n\n### `all`\n\nทุกอย่างในต้นไม้เซสชัน รวมถึงรายการจัดเก็บบัญชี/กำหนดเอง\n\n### พฤติกรรมโหนด assistant เฉพาะเครื่องมือ\n\nข้อความ assistant ที่มี **เฉพาะการเรียกเครื่องมือ** (ไม่มีข้อความ) จะถูกซ่อนโดยค่าเริ่มต้นในมุมมองที่ถูกกรองทั้งหมด เว้นแต่:\n\n- ข้อความเป็น error/aborted (`stopReason` ไม่ใช่ `stop`/`toolUse`) หรือ\n- เป็น leaf ปัจจุบัน (แสดงให้เห็นอยู่เสมอ)\n\n### พฤติกรรมการค้นหา\n\n- คำค้นหาจะถูกแบ่งโดยเว้นวรรค\n- การจับคู่ไม่คำนึงถึงตัวพิมพ์ใหญ่-เล็ก\n- token ทั้งหมดต้องตรงกัน (ความหมาย AND)\n- ข้อความที่ค้นหาได้รวมถึงป้ายกำกับ role และเนื้อหาเฉพาะประเภท (ข้อความข้อความ, ข้อความสรุปสาขา, ประเภทกำหนดเอง, ข้อมูลโค้ดคำสั่งเครื่องมือ ฯลฯ)\n\n## ผลลัพธ์การเลือก (สำคัญ)\n\n`navigateTree` คำนวณพฤติกรรม leaf ใหม่จากประเภทรายการที่เลือก:\n\n### การเลือกข้อความ `user`\n\n- leaf ใหม่จะกลายเป็น `parentId` ของรายการที่เลือก\n- หาก parent เป็น `null` (ข้อความผู้ใช้ root) leaf จะรีเซ็ตเป็น root (`resetLeaf()`)\n- ข้อความที่เลือกจะถูกคัดลอกไปยังตัวแก้ไขสำหรับการแก้ไข/ส่งใหม่\n\n### การเลือก `custom_message`\n\n- กฎ leaf เดียวกับข้อความผู้ใช้ (`parentId`)\n- เนื้อหาข้อความจะถูกดึงออกและคัดลอกไปยังตัวแก้ไข\n\n### การเลือกโหนดที่ไม่ใช่ผู้ใช้ (assistant/tool/summary/compaction/custom bookkeeping/ฯลฯ)\n\n- leaf ใหม่จะกลายเป็น id ของโหนดที่เลือก\n- ตัวแก้ไขจะไม่ถูกเติมข้อมูลล่วงหน้า\n\n### การเลือก leaf ปัจจุบัน\n\n- ไม่มีการดำเนินการ; ตัวเลือกจะปิดพร้อมข้อความ \"Already at this point\"\n\n```text\nการตัดสินใจเลือก (แบบง่าย):\n\nselected node\n   │\n   ├─ is current leaf? ── yes ──> close selector (no-op)\n   │\n   ├─ is user/custom_message? ── yes ──> leaf := parentId (or resetLeaf for root)\n   │                                     + prefill editor text\n   │\n   └─ otherwise ──> leaf := selected node id\n                    + no editor prefill\n```\n\n## กระบวนการสรุปเมื่อสลับ\n\nการแจ้งสรุปถูกควบคุมโดย `branchSummary.enabled` (ค่าเริ่มต้น: `false`)\n\nเมื่อเปิดใช้งาน หลังจากเลือกโหนด UI จะถาม:\n\n- `No summary`\n- `Summarize`\n- `Summarize with custom prompt`\n\nรายละเอียดกระบวนการ:\n\n- Escape ในการแจ้งสรุปจะเปิดตัวเลือกต้นไม้อีกครั้ง\n- การยกเลิกการแจ้งกำหนดเองจะกลับไปที่วงจรการเลือกสรุป\n- ระหว่างการสรุป UI จะแสดง loader และผูก `Esc` กับ `abortBranchSummary()`\n- หากการสรุปถูกยกเลิก ตัวเลือกต้นไม้จะเปิดใหม่และไม่มีการย้ายถูกนำไปใช้\n\nภายใน `navigateTree`:\n\n- รวบรวมรายการสาขาที่ถูกละทิ้งจาก leaf เก่าไปยัง common ancestor\n- ส่ง `session_before_tree` (ส่วนขยายสามารถยกเลิกหรือแทรกสรุปได้)\n- ใช้ตัวสรุปเริ่มต้นเฉพาะเมื่อร้องขอและจำเป็น\n- ใช้การย้ายด้วย:\n  - `branchWithSummary(...)` เมื่อมีสรุปอยู่\n  - `branch(newLeafId)` สำหรับการย้ายที่ไม่ใช่ root โดยไม่มีสรุป\n  - `resetLeaf()` สำหรับการย้าย root โดยไม่มีสรุป\n- แทนที่การสนทนาของ agent ด้วย session context ที่สร้างใหม่\n- ส่ง `session_tree`\n\nหมายเหตุ: หากผู้ใช้ร้องขอสรุปแต่ไม่มีอะไรให้สรุป การนำทางจะดำเนินต่อโดยไม่สร้างรายการสรุป\n\n## ป้ายกำกับ\n\nการแก้ไขป้ายกำกับใน UI ต้นไม้จะเรียก `appendLabelChange(targetId, label)`\n\n- ป้ายกำกับที่ไม่ว่างเปล่าจะตั้งค่า/อัปเดตป้ายกำกับที่แก้ไขแล้ว\n- ป้ายกำกับว่างจะล้างมัน\n- ป้ายกำกับถูกจัดเก็บเป็นรายการ `label` แบบ append-only\n- โหนดต้นไม้จะแสดงสถานะป้ายกำกับที่แก้ไขแล้ว ไม่ใช่ประวัติรายการป้ายกำกับดิบ\n\n## `/tree` เทียบกับการดำเนินการที่เกี่ยวข้อง\n\n| การดำเนินการ | ขอบเขต | ผลลัพธ์ |\n|---|---|---|\n| `/tree` | ไฟล์เซสชันปัจจุบัน | ย้าย leaf ไปยังจุดที่เลือก (ไฟล์เดียวกัน) |\n| `/branch` | โดยปกติไฟล์เซสชันปัจจุบัน -> ไฟล์เซสชันใหม่ | โดยค่าเริ่มต้นจะแตกสาขาจากข้อความ **user** ที่เลือกไปเป็นไฟล์เซสชันใหม่; หาก `doubleEscapeAction = \"tree\"` `/branch` จะเปิด UI การนำทางต้นไม้แทน |\n| `/fork` | เซสชันปัจจุบันทั้งหมด | ทำสำเนาเซสชันไปเป็นไฟล์เซสชันใหม่ที่คงอยู่ |\n| `/resume` | รายการเซสชัน | สลับไปยังไฟล์เซสชันอื่น |\n\nความแตกต่างหลัก: `/tree` เป็นเครื่องมือนำทาง/จัดตำแหน่งใหม่ภายในไฟล์เซสชันเดียว `/branch`, `/fork` และ `/resume` ทั้งหมดเปลี่ยน session-file context\n\n## เวิร์กโฟลว์ของผู้ปฏิบัติงาน\n\n### เรียกใช้ใหม่จากการแจ้งผู้ใช้ก่อนหน้าโดยไม่สูญเสียสาขาปัจจุบัน\n\n1. `/tree`\n2. ค้นหา/เลือกข้อความผู้ใช้ก่อนหน้า\n3. เลือก `No summary` (หรือสรุปหากจำเป็น)\n4. แก้ไขข้อความที่เติมล่วงหน้าในตัวแก้ไข\n5. ส่ง\n\nผลลัพธ์: สาขาใหม่เติบโตจากจุดที่เลือกภายในไฟล์เซสชันเดียวกัน\n\n### ออกจากสาขาปัจจุบันพร้อม breadcrumb บริบท\n\n1. เปิดใช้งาน `branchSummary.enabled`\n2. `/tree` และเลือกโหนดเป้าหมาย\n3. เลือก `Summarize` (หรือการแจ้งกำหนดเอง)\n\nผลลัพธ์: รายการ `branch_summary` จะถูกต่อท้ายที่ตำแหน่งเป้าหมายก่อนดำเนินการต่อ\n\n### ตรวจสอบรายการจัดเก็บบัญชีที่ซ่อนอยู่\n\n1. `/tree`\n2. กด `Alt+A` (all)\n3. ค้นหา `model`, `thinking`, `custom` หรือป้ายกำกับ\n\nผลลัพธ์: ตรวจสอบไทม์ไลน์ภายในทั้งหมด ไม่ใช่เฉพาะโหนดการสนทนา\n\n### บุ๊กมาร์กจุดหมุนสำหรับการข้ามในภายหลัง\n\n1. `/tree`\n2. ย้ายไปยังรายการ\n3. `Shift+L` และตั้งป้ายกำกับ\n4. ใช้ `Alt+L` (`labeled-only`) ในภายหลังเพื่อข้ามอย่างรวดเร็ว\n\nผลลัพธ์: การนำทางอย่างรวดเร็วในหมู่ branch landmark ที่ยั่งยืน\n",
	"th/tui/tui-runtime-internals.md": "---\ntitle: TUI Runtime ภายใน\ndescription: >-\n  TUI ภายในรันไทม์ของเทอร์มินัล UI ครอบคลุมไปป์ไลน์การเรนเดอร์ การจัดการอินพุต\n  และการจัดการสถานะ\nsidebar:\n  order: 2\n  label: ภายในรันไทม์\ni18n:\n  sourceHash: cc8f7dcce46a\n  translator: machine\n---\n\n# TUI ภายในรันไทม์\n\nเอกสารนี้จัดทำแผนที่เส้นทางรันไทม์ที่ไม่ใช่ธีมจากอินพุตเทอร์มินัลไปยังผลลัพธ์ที่เรนเดอร์ในโหมดอินเทอร์แอคทีฟ โดยเน้นที่พฤติกรรมใน `packages/tui` และการผสานรวมจากคอนโทรลเลอร์ใน `packages/coding-agent`\n\n## เลเยอร์รันไทม์และความเป็นเจ้าของ\n\n- **เอนจิน `packages/tui`**: วงจรชีวิตเทอร์มินัล, การนอร์มัลไลซ์ stdin, การกำหนดเส้นทางโฟกัส, การตั้งเวลาเรนเดอร์, การวาดแบบดิฟเฟอเรนเชียล, การประกอบโอเวอร์เลย์, การวางเคอร์เซอร์ฮาร์ดแวร์\n- **โหมดอินเทอร์แอคทีฟของ `packages/coding-agent`**: สร้างต้นไม้ส่วนประกอบ, ผูก callbacks ของตัวแก้ไขและ keymaps, ตอบสนองต่อเหตุการณ์ agent/session, และแปลสถานะโดเมน (สตรีมมิง, การเรียกใช้เครื่องมือ, การลองซ้ำ, โหมดแผน) เป็นส่วนประกอบ UI\n\nกฎขอบเขต: เอนจิน TUI ไม่รับรู้ข้อความ มันรู้จักเพียง `Component.render(width)`, `handleInput(data)`, โฟกัส, และโอเวอร์เลย์ ความหมายของ Agent อยู่ในคอนโทรลเลอร์แบบอินเทอร์แอคทีฟ\n\n## ไฟล์การนำไปใช้งาน\n\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/components/custom-editor.ts`](../../packages/coding-agent/src/modes/components/custom-editor.ts)\n- [`../../tui/src/tui.ts`](../../packages/tui/src/tui.ts)\n- [`../../tui/src/terminal.ts`](../../packages/tui/src/terminal.ts)\n- [`../../tui/src/editor-component.ts`](../../packages/tui/src/editor-component.ts)\n- [`../../tui/src/stdin-buffer.ts`](../../packages/tui/src/stdin-buffer.ts)\n- [`../../tui/src/components/loader.ts`](../../packages/tui/src/components/loader.ts)\n\n## การบูตและการประกอบต้นไม้ส่วนประกอบ\n\n`InteractiveMode` สร้าง `TUI(new ProcessTerminal(), showHardwareCursor)` และสร้างคอนเทนเนอร์ที่คงอยู่:\n\n- `chatContainer`\n- `pendingMessagesContainer`\n- `statusContainer`\n- `todoContainer`\n- `statusLine`\n- `editorContainer` (เก็บ `CustomEditor`)\n\n`init()` เชื่อมต้นไม้ตามลำดับนั้น, โฟกัสที่ตัวแก้ไข, ลงทะเบียน input handlers ผ่าน `InputController`, เริ่ม TUI, และขอการเรนเดอร์แบบบังคับ\n\nการเรนเดอร์แบบบังคับ (`requestRender(true)`) รีเซ็ตแคชบรรทัดก่อนหน้าและการบุ๊กคีปปิ้งเคอร์เซอร์ก่อนการวาดใหม่\n\n## วงจรชีวิตเทอร์มินัลและการนอร์มัลไลซ์ stdin\n\n`ProcessTerminal.start()`:\n\n1. เปิดใช้งานโหมด raw และ bracketed paste\n2. แนบ resize handler\n3. สร้าง `StdinBuffer` เพื่อแยก escape chunks ที่ไม่สมบูรณ์ออกเป็นลำดับที่สมบูรณ์\n4. สอบถามการรองรับโปรโตคอลคีย์บอร์ด Kitty (`CSI ? u`) จากนั้นเปิดใช้งาน protocol flags หากรองรับ\n5. บน Windows, พยายามเปิดใช้งาน VT input ผ่าน mode flags ของ `kernel32`\n\nพฤติกรรมของ `StdinBuffer`:\n\n- บัฟเฟอร์ลำดับ escape ที่แยกส่วน (CSI/OSC/DCS/APC/SS3)\n- ส่งออก `data` เฉพาะเมื่อลำดับสมบูรณ์หรือ flush ตามเวลาหมดอายุ\n- ตรวจจับ bracketed paste และส่งออกเหตุการณ์ `paste` พร้อมข้อความที่วางดิบ\n\nสิ่งนี้ป้องกันไม่ให้ escape chunks ที่ไม่สมบูรณ์ถูกตีความผิดว่าเป็นการกดแป้นปกติ\n\n## การกำหนดเส้นทางอินพุตและโมเดลโฟกัส\n\nเส้นทางอินพุต:\n\n`stdin -> ProcessTerminal -> StdinBuffer -> TUI.#handleInput -> focusedComponent.handleInput`\n\nรายละเอียดการกำหนดเส้นทาง:\n\n1. TUI รัน input listeners ที่ลงทะเบียนไว้ก่อน (`addInputListener`) ซึ่งช่วยให้มีพฤติกรรมการใช้/แปลง\n2. TUI จัดการ global debug shortcut (`shift+ctrl+d`) ก่อนการ dispatch ส่วนประกอบ\n3. หากส่วนประกอบที่โฟกัสอยู่ในโอเวอร์เลย์ที่ซ่อนอยู่/มองไม่เห็น, TUI จะกำหนดโฟกัสใหม่ไปยังโอเวอร์เลย์ที่มองเห็นถัดไปหรือโฟกัสก่อนโอเวอร์เลย์ที่บันทึกไว้\n4. เหตุการณ์ key release จะถูกกรองออกเว้นแต่ส่วนประกอบที่โฟกัสตั้งค่า `wantsKeyRelease = true`\n5. หลังจาก dispatch, TUI ตั้งเวลาเรนเดอร์\n\n`setFocus()` ยังสลับ `Focusable.focused` ซึ่งควบคุมว่าส่วนประกอบส่ง `CURSOR_MARKER` สำหรับการวางเคอร์เซอร์ฮาร์ดแวร์หรือไม่\n\n## การแยกการจัดการคีย์: editor กับ controller\n\n`CustomEditor` สกัดกั้น combos ที่มีความสำคัญสูงก่อน (escape, ctrl-c/d/z, ctrl-v, ctrl-p variants, ctrl-t, alt-up, คีย์กำหนดเองของส่วนขยาย) และมอบสิ่งที่เหลือให้พฤติกรรมพื้นฐานของ `Editor` (การแก้ไขข้อความ, ประวัติ, การเติมข้อความอัตโนมัติ, การเลื่อนเคอร์เซอร์)\n\n`InputController.setupKeyHandlers()` จากนั้นผูก editor callbacks กับการดำเนินการโหมด:\n\n- การยกเลิก / การออกจากโหมดที่ `Escape`\n- การปิดระบบเมื่อกด `Ctrl+C` สองครั้งหรือ `Ctrl+D` เมื่อ editor ว่างเปล่า\n- ระงับ/ดำเนินการต่อที่ `Ctrl+Z`\n- slash-command และ hotkeys ของตัวเลือก\n- การสลับ follow-up/dequeue และการสลับการขยาย\n\nสิ่งนี้รักษาการแยกวิเคราะห์คีย์/กลไก editor ไว้ใน `packages/tui` และความหมายของโหมดในคอนโทรลเลอร์ coding-agent\n\n## วงจรเรนเดอร์และกลยุทธ์การ diff\n\n`TUI.requestRender()` ถูก debounce ให้เรนเดอร์หนึ่งครั้งต่อ tick โดยใช้ `process.nextTick` การเปลี่ยนแปลงสถานะหลายรายการในรอบเดียวกันจะรวมกัน\n\nไปป์ไลน์ `#doRender()`:\n\n1. เรนเดอร์ต้นไม้ส่วนประกอบรากไปยัง `newLines`\n2. ประกอบโอเวอร์เลย์ที่มองเห็น (ถ้ามี)\n3. ดึงและลบ `CURSOR_MARKER` ออกจากบรรทัดที่มองเห็นใน viewport\n4. ต่อท้าย suffix รีเซ็ต segment สำหรับบรรทัดที่ไม่ใช่รูปภาพ\n5. เลือกการวาดใหม่ทั้งหมดกับการแพตช์แบบดิฟเฟอเรนเชียล:\n   - เฟรมแรก\n   - การเปลี่ยนแปลงความกว้าง\n   - การย่อขนาดด้วย `clearOnShrink` เปิดใช้งานและไม่มีโอเวอร์เลย์\n   - การแก้ไขเหนือ viewport ก่อนหน้า\n6. สำหรับการอัปเดตแบบดิฟเฟอเรนเชียล, แพตช์เฉพาะช่วงบรรทัดที่เปลี่ยนแปลงและล้างบรรทัดท้ายที่ล้าสมัยเมื่อจำเป็น\n7. วางเคอร์เซอร์ฮาร์ดแวร์ใหม่สำหรับการรองรับ IME\n\nการเขียนเรนเดอร์ใช้โหมดผลลัพธ์แบบซิงโครไนซ์ (`CSI ? 2026 h/l`) เพื่อลดการกะพริบ/การฉีกขาด\n\n## ข้อจำกัดความปลอดภัยในการเรนเดอร์\n\nการตรวจสอบความปลอดภัยที่สำคัญใน `TUI`:\n\n- บรรทัดที่เรนเดอร์แล้วที่ไม่ใช่รูปภาพต้องไม่เกินความกว้างเทอร์มินัล; การล้นจะ throw และเขียน crash diagnostics\n- การประกอบโอเวอร์เลย์รวมถึงการตัดทอนเชิงป้องกันและการตรวจสอบความกว้างหลังการประกอบ\n- การเปลี่ยนแปลงความกว้างบังคับให้วาดใหม่ทั้งหมดเนื่องจากความหมายของการตัดบรรทัดเปลี่ยนแปลง\n- ตำแหน่งเคอร์เซอร์ถูก clamp ก่อนการเลื่อน\n\nข้อจำกัดเหล่านี้คือการบังคับใช้รันไทม์ ไม่ใช่แค่แนวทางปฏิบัติ\n\n## การจัดการการปรับขนาด\n\nเหตุการณ์การปรับขนาดถูกขับเคลื่อนโดยเหตุการณ์จาก `ProcessTerminal` ไปยัง `TUI.requestRender()`\n\nผลกระทบ:\n\n- การเปลี่ยนแปลงความกว้างใด ๆ จะกระตุ้นการวาดใหม่ทั้งหมด\n- การติดตาม Viewport/top (`#previousViewportTop`, `#maxLinesRendered`) หลีกเลี่ยงคณิตศาสตร์เคอร์เซอร์สัมพัทธ์ที่ไม่ถูกต้องเมื่อเนื้อหาหรือขนาดเทอร์มินัลเปลี่ยนแปลง\n- การมองเห็นโอเวอร์เลย์สามารถขึ้นอยู่กับมิติเทอร์มินัล (`OverlayOptions.visible`); โฟกัสได้รับการแก้ไขเมื่อโอเวอร์เลย์กลายเป็นไม่มองเห็นหลังการปรับขนาด\n\n## การสตรีมและการอัปเดต UI แบบส่วนเพิ่ม\n\n`EventController` สมัครสมาชิก `AgentSessionEvent` และอัปเดต UI แบบส่วนเพิ่ม:\n\n- `agent_start`: เริ่ม loader ใน `statusContainer`\n- `message_start` assistant: สร้าง `streamingComponent` และเมาท์\n- `message_update`: อัปเดตเนื้อหา assistant ที่สตรีม; สร้าง/อัปเดตส่วนประกอบการเรียกใช้เครื่องมือเมื่อ tool calls ปรากฏขึ้น\n- `tool_execution_update/end`: อัปเดตส่วนประกอบผลลัพธ์เครื่องมือและสถานะการเสร็จสมบูรณ์\n- `message_end`: สรุป assistant stream, จัดการ annotations ที่ถูกยกเลิก/ข้อผิดพลาด, ทำเครื่องหมาย tool args ที่รอดำเนินการว่าสมบูรณ์เมื่อหยุดตามปกติ\n- `agent_end`: หยุด loaders, ล้างสถานะ stream ชั่วคราว, flush การสลับโมเดลที่เลื่อนออกไป, ออกการแจ้งเตือนการเสร็จสมบูรณ์หากอยู่เบื้องหลัง\n\nการจัดกลุ่ม read-tool มีสถานะโดยเจตนา (`#lastReadGroup`) เพื่อรวม tool calls read ที่ต่อเนื่องกันเป็นบล็อกภาพเดียวจนกว่าจะมีการหยุดที่ไม่ใช่ read เกิดขึ้น\n\n## การจัดการ status และ loader\n\nความเป็นเจ้าของช่องสถานะ:\n\n- `statusContainer` เก็บ loaders ชั่วคราว (`loadingAnimation`, `autoCompactionLoader`, `retryLoader`)\n- `statusLine` เรนเดอร์สถานะ/hooks/plan indicators ที่คงอยู่และขับเคลื่อนการอัปเดตขอบด้านบนของ editor\n\nพฤติกรรม Loader:\n\n- `Loader` อัปเดตทุก 80ms ผ่าน interval และขอเรนเดอร์ในแต่ละเฟรม\n- Escape handlers ถูกแทนที่ชั่วคราวระหว่าง auto-compaction และ auto-retry เพื่อยกเลิกการดำเนินการเหล่านั้น\n- บนเส้นทาง end/cancel, คอนโทรลเลอร์กู้คืน escape handlers ก่อนหน้าและหยุด/ล้างส่วนประกอบ loader\n\n## การเปลี่ยนโหมดและการทำงานเบื้องหลัง\n\n### โหมดอินพุต Bash/Python\n\nคำนำหน้าข้อความอินพุตสลับ mode flags ขอบ editor:\n\n- `!` -> โหมด bash\n- `$` (คำนำหน้าที่ไม่ใช่ template literal) -> โหมด python\n\nEscape ออกจากโหมดที่ไม่ทำงานโดยล้างข้อความ editor และกู้คืนสีขอบ; เมื่อการเรียกใช้งานทำงานอยู่, escape จะยกเลิกงานที่กำลังทำงานแทน\n\n### โหมดแผน\n\n`InteractiveMode` ติดตาม plan mode flags, สถานะของ status-line, เครื่องมือที่ทำงานอยู่, และการสลับโมเดล Enter/exit อัปเดตรายการโหมด session และสถานะ UI รวมถึงการสลับโมเดลที่เลื่อนออกไปหากกำลังสตรีม\n\n### ระงับ/ดำเนินการต่อ (`Ctrl+Z`)\n\n`InputController.handleCtrlZ()`:\n\n1. ลงทะเบียน `SIGCONT` handler แบบ one-shot เพื่อรีสตาร์ท TUI และบังคับเรนเดอร์\n2. หยุด TUI ก่อนระงับ\n3. ส่ง `SIGTSTP` ไปยัง process group\n\n### โหมดเบื้องหลัง (`/background` หรือ `/bg`)\n\n`handleBackgroundCommand()`:\n\n- ปฏิเสธเมื่อว่างเปล่า\n- สลับบริบท tool UI เป็นแบบไม่อินเทอร์แอคทีฟ (`hasUI=false`) เพื่อให้เครื่องมือ interactive UI ล้มเหลวอย่างรวดเร็ว\n- หยุด loaders/status line และยกเลิกการสมัครสมาชิก foreground event handler\n- สมัครสมาชิก background event handler (รอ `agent_end` เป็นหลัก)\n- หยุด TUI และส่ง `SIGTSTP` (เส้นทาง POSIX job control)\n\nเมื่อ `agent_end` ในเบื้องหลังโดยไม่มีงานในคิว, คอนโทรลเลอร์จะส่งการแจ้งเตือนการเสร็จสมบูรณ์และปิดระบบ\n\n## เส้นทางการยกเลิก\n\nอินพุตการยกเลิกหลัก:\n\n- `Escape` ระหว่าง active stream loader: กู้คืนข้อความที่อยู่ในคิวไปยัง editor และยกเลิก agent\n- `Escape` ระหว่างการเรียกใช้งาน bash/python: ยกเลิกคำสั่งที่กำลังทำงาน\n- `Escape` ระหว่าง auto-compaction/retry: เรียก abort methods เฉพาะผ่าน escape handlers ชั่วคราว\n- `Ctrl+C` กดครั้งเดียว: ล้าง editor; กดสองครั้งภายใน 500ms: ปิดระบบ\n\nการยกเลิกมีเงื่อนไขตามสถานะ; คีย์เดียวกันสามารถหมายถึงการยกเลิก, การออกจากโหมด, การกระตุ้นตัวเลือก, หรือไม่มีการดำเนินการ ขึ้นอยู่กับสถานะรันไทม์\n\n## พฤติกรรมขับเคลื่อนด้วยเหตุการณ์กับแบบ throttled\n\nการอัปเดตขับเคลื่อนด้วยเหตุการณ์:\n\n- เหตุการณ์ agent session (`EventController`)\n- key input callbacks (`InputController`)\n- callback การปรับขนาดเทอร์มินัล\n- ตัวเฝ้าดู theme/branch ใน `InteractiveMode`\n\nเส้นทาง throttled/debounced:\n\n- การเรนเดอร์ TUI ถูก tick-debounce (`requestRender` coalescing)\n- แอนิเมชัน loader มีช่วงเวลาคงที่ (80ms), แต่ละเฟรมขอเรนเดอร์\n- การอัปเดต autocomplete ของ editor (ภายใน `Editor`) ใช้ debounce timers เพื่อลดการคำนวณซ้ำระหว่างการพิมพ์\n\nดังนั้นรันไทม์จึงผสมผสานการเปลี่ยนสถานะขับเคลื่อนด้วยเหตุการณ์กับจังหวะการเรนเดอร์ที่มีขอบเขตเพื่อให้การตอบสนองต่อผู้ใช้รวดเร็วโดยไม่เกิดการวาดซ้ำมากเกินไป\n",
	"th/tui/tui.md": "---\ntitle: การผสานรวม TUI สำหรับส่วนขยายและเครื่องมือกำหนดเอง\ndescription: สัญญาการผสานรวม TUI สำหรับส่วนขยาย เครื่องมือกำหนดเอง และตัวเรนเดอร์กำหนดเอง\nsidebar:\n  order: 1\n  label: การผสานรวมส่วนขยาย\ni18n:\n  sourceHash: 47f8f2b2045e\n  translator: machine\n---\n\n# การผสานรวม TUI สำหรับส่วนขยายและเครื่องมือกำหนดเอง\n\nเอกสารนี้ครอบคลุมสัญญา TUI **ปัจจุบัน** ที่ใช้โดย `packages/coding-agent` และ `packages/tui` สำหรับ UI ของส่วนขยาย UI ของเครื่องมือกำหนดเอง และตัวเรนเดอร์กำหนดเอง\n\n## ระบบย่อยนี้คืออะไร\n\nรันไทม์มีสองชั้น:\n\n- **เอนจินเรนเดอร์ (`packages/tui`)**: ตัวเรนเดอร์เทอร์มินัลแบบ differential, การส่งต่อ input, การโฟกัส, overlays, การวางเคอร์เซอร์\n- **ชั้นการผสานรวม (`packages/coding-agent`)**: เมานต์คอมโพเนนต์ส่วนขยาย/เครื่องมือกำหนดเอง, เชื่อมต่อ keybindings/theme และกู้คืนสถานะ editor\n\n## พฤติกรรมรันไทม์ตามโหมด\n\n| โหมด | ความพร้อมใช้งานของ `ctx.ui.custom(...)` | หมายเหตุ |\n| --- | --- | --- |\n| Interactive TUI | รองรับ | คอมโพเนนต์จะถูกเมานต์ในพื้นที่ editor, โฟกัส, และต้องเรียก `done(result)` เพื่อ resolve |\n| Background/headless | ไม่ interactive | UI context เป็น no-op (`hasUI === false`) |\n| RPC mode | ไม่รองรับ | `custom()` คืนค่า `Promise<never>` และไม่เมานต์คอมโพเนนต์ TUI |\n\nหากส่วนขยาย/เครื่องมือของคุณสามารถทำงานในโหมดไม่ interactive ได้ ให้ใช้ `ctx.hasUI` / `pi.hasUI` ในการตรวจสอบ\n\n## สัญญาคอมโพเนนต์หลัก (`@f5-sales-demo/pi-tui`)\n\n`packages/tui/src/tui.ts` กำหนด:\n\n```ts\nexport interface Component {\n  render(width: number): string[];\n  handleInput?(data: string): void;\n  wantsKeyRelease?: boolean;\n  invalidate(): void;\n}\n```\n\n`Focusable` แยกออกมาต่างหาก:\n\n```ts\nexport interface Focusable {\n  focused: boolean;\n}\n```\n\nพฤติกรรมเคอร์เซอร์ใช้ `CURSOR_MARKER` (ไม่ใช่ `getCursorPosition`) คอมโพเนนต์ที่โฟกัสจะปล่อย marker ในข้อความที่เรนเดอร์ จากนั้น `TUI` จะดึงข้อมูลและวางเคอร์เซอร์ฮาร์ดแวร์\n\n## ข้อจำกัดการเรนเดอร์ (ความปลอดภัยของเทอร์มินัล)\n\noutput ของ `render(width)` ต้องปลอดภัยสำหรับเทอร์มินัล:\n\n1. **ห้ามเกิน `width` ในบรรทัดใดก็ตาม** ตัวเรนเดอร์จะ throw หากบรรทัดที่ไม่ใช่รูปภาพล้น\n2. **วัดความกว้างที่มองเห็นได้** ไม่ใช่ความยาว string: ใช้ `visibleWidth()`\n3. **ตัดทอน/จัดการข้อความ ANSI** ด้วย `truncateToWidth()` / `wrapTextWithAnsi()`\n4. **Sanitize tabs/เนื้อหา** จากแหล่งภายนอกโดยใช้ `replaceTabs()` (และ sanitizer ระดับสูงกว่าใน render paths ของ coding-agent)\n\nรูปแบบขั้นต่ำ:\n\n```ts\nimport { replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\n\nrender(width: number): string[] {\n  return this.lines.map(line => truncateToWidth(replaceTabs(line), width));\n}\n```\n\n## การจัดการ Input และ Keybindings\n\n### การจับคู่ key แบบ Raw\n\nใช้ `matchesKey(data, \"...\")` สำหรับคีย์นำทางและคอมโบ\n\n### รองรับ keybindings ของแอปที่ผู้ใช้กำหนดค่า\n\nfactories ของ UI ส่วนขยายจะได้รับ `KeybindingsManager` (โหมด interactive) เพื่อให้คุณสามารถรองรับ action ที่แมปไว้แทนการ hardcode คีย์:\n\n```ts\nif (keybindings.matches(data, \"interrupt\")) {\n  done(undefined);\n  return;\n}\n```\n\n### เหตุการณ์ Key release/repeat\n\nเหตุการณ์ key release จะถูกกรองออก เว้นแต่คอมโพเนนต์ของคุณตั้งค่า:\n\n```ts\nwantsKeyRelease = true;\n```\n\nจากนั้นใช้ `isKeyRelease()` / `isKeyRepeat()` หากจำเป็น\n\n## การโฟกัส, Overlays และเคอร์เซอร์\n\n- `TUI.setFocus(component)` ส่งต่อ input ไปยังคอมโพเนนต์นั้น\n- Overlay API มีอยู่ใน `TUI` (`showOverlay`, `OverlayHandle`) แต่การเมานต์ `ctx.ui.custom` ของส่วนขยายในโหมด interactive ปัจจุบันจะแทนที่พื้นที่คอมโพเนนต์ editor โดยตรง\n- ตัวเลือก `custom(..., options?: { overlay?: boolean })` มีอยู่ใน extension types; การเมานต์ส่วนขยาย interactive ปัจจุบันไม่สนใจตัวเลือกนี้\n\n## จุดเมานต์และสัญญาการคืนค่า\n\n## 1) Extension UI (`ExtensionUIContext`)\n\nลายเซ็นปัจจุบัน (`extensibility/extensions/types.ts`):\n\n```ts\ncustom<T>(\n  factory: (\n    tui: TUI,\n    theme: Theme,\n    keybindings: KeybindingsManager,\n    done: (result: T) => void,\n  ) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n  options?: { overlay?: boolean },\n): Promise<T>\n```\n\nพฤติกรรมในโหมด interactive (`extension-ui-controller.ts`):\n\n- บันทึกข้อความ editor\n- แทนที่คอมโพเนนต์ editor ด้วยคอมโพเนนต์ของคุณ\n- โฟกัสคอมโพเนนต์ของคุณ\n- เมื่อ `done(result)`: เรียก `component.dispose?.()`, กู้คืน editor + ข้อความ, โฟกัส editor, resolve promise\n\nดังนั้น `done(...)` เป็นสิ่งจำเป็นสำหรับการเสร็จสิ้น\n\n## 2) Hook/custom-tool UI context (การพิมพ์แบบ legacy)\n\n`HookUIContext.custom` ถูกพิมพ์เป็น `(tui, theme, done)` ใน hook/custom-tool types\nการนำไปใช้งาน interactive พื้นฐานเรียก factories ด้วย `(tui, theme, keybindings, done)` ผู้ใช้ JS สามารถใช้ argument เพิ่มเติมได้; ความเข้ากันได้ระดับ type ยังคงสะท้อนลายเซ็น 3 argument แบบ legacy\n\nเครื่องมือกำหนดเองโดยทั่วไปใช้จุดเข้า UI เดียวกันผ่าน object `pi.ui` ที่กำหนดขอบเขต factory จากนั้นคืนค่าที่เลือกในเนื้อหาเครื่องมือปกติ:\n\n```ts\nasync execute(toolCallId, params, onUpdate, ctx, signal) {\n  if (!pi.hasUI) {\n    return { content: [{ type: \"text\", text: \"UI unavailable\" }] };\n  }\n\n  const picked = await pi.ui.custom<string | undefined>((tui, theme, done) => {\n    const component = new MyPickerComponent(done, signal);\n    return component;\n  });\n\n  return { content: [{ type: \"text\", text: picked ? `Picked: ${picked}` : \"Cancelled\" }] };\n}\n```\n\n## 3) ตัวเรนเดอร์ tool call/result กำหนดเอง\n\nเครื่องมือกำหนดเองและเครื่องมือส่วนขยายสามารถคืนค่าคอมโพเนนต์จาก:\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\n`options` ปัจจุบันรวมถึง:\n\n- `expanded: boolean`\n- `isPartial: boolean`\n- `spinnerFrame?: number`\n\nตัวเรนเดอร์เหล่านี้จะถูกเมานต์โดย `ToolExecutionComponent`\n\n## วงจรชีวิตและการยกเลิก\n\n- `dispose()` เป็น optional ในระดับ type แต่ควรนำไปใช้เมื่อคุณเป็นเจ้าของ timers, subprocesses, watchers, sockets หรือ overlays\n- `done(...)` ควรถูกเรียกเพียงครั้งเดียวจาก flow ของคอมโพเนนต์\n- สำหรับ UI ที่ทำงานนานและสามารถยกเลิกได้ ให้จับคู่ `CancellableLoader` กับ `AbortSignal` และเรียก `done(...)` จาก `onAbort`\n\nตัวอย่างรูปแบบการยกเลิก:\n\n```ts\nconst loader = new CancellableLoader(tui, theme.fg(\"accent\"), theme.fg(\"muted\"), \"Working...\");\nloader.onAbort = () => done(undefined);\nvoid doWork(loader.signal).then(result => done(result));\nreturn loader;\n```\n\n## ตัวอย่างคอมโพเนนต์กำหนดเองที่สมจริง (คำสั่งส่วนขยาย)\n\n```ts\nimport type { Component } from \"@f5-sales-demo/pi-tui\";\nimport { SelectList, matchesKey, replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\nimport { getSelectListTheme, type ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nclass Picker implements Component {\n  list: SelectList;\n  keybindings: any;\n  done: (value: string | undefined) => void;\n\n  constructor(\n    items: Array<{ value: string; label: string }>,\n    keybindings: any,\n    done: (value: string | undefined) => void,\n  ) {\n    this.list = new SelectList(items, 8, getSelectListTheme());\n    this.keybindings = keybindings;\n    this.done = done;\n    this.list.onSelect = item => this.done(item.value);\n    this.list.onCancel = () => this.done(undefined);\n  }\n\n  handleInput(data: string): void {\n    if (this.keybindings.matches(data, \"interrupt\")) {\n      this.done(undefined);\n      return;\n    }\n    this.list.handleInput(data);\n  }\n\n  render(width: number): string[] {\n    return this.list.render(width).map(line => truncateToWidth(replaceTabs(line), width));\n  }\n\n  invalidate(): void {\n    this.list.invalidate();\n  }\n}\n\nexport default function extension(pi: ExtensionAPI): void {\n  pi.registerCommand(\"pick-model\", {\n    description: \"Pick a model profile\",\n    handler: async (_args, ctx) => {\n      if (!ctx.hasUI) return;\n\n      const selected = await ctx.ui.custom<string | undefined>((tui, theme, keybindings, done) => {\n        const items = [\n          { value: \"fast\", label: theme.fg(\"accent\", \"Fast\") },\n          { value: \"balanced\", label: \"Balanced\" },\n          { value: \"quality\", label: \"Quality\" },\n        ];\n        return new Picker(items, keybindings, done);\n      });\n\n      if (selected) ctx.ui.notify(`Selected profile: ${selected}`, \"info\");\n    },\n  });\n}\n```\n\n## ไฟล์การนำไปใช้งานหลัก\n\n- `packages/tui/src/tui.ts` — `Component`, `Focusable`, cursor marker, การโฟกัส, overlay, การส่งต่อ input\n- `packages/tui/src/utils.ts` — primitives สำหรับ width/truncation/sanitization\n- `packages/tui/src/keys.ts` / `keybindings.ts` — การ parse คีย์และการแมป action ที่กำหนดค่าได้\n- `packages/coding-agent/src/modes/controllers/extension-ui-controller.ts` — การเมานต์/ถอดเมานต์ interactive สำหรับ UI ของส่วนขยาย/hook/เครื่องมือกำหนดเอง\n- `packages/coding-agent/src/extensibility/extensions/types.ts` — สัญญา UI ของส่วนขยายและตัวเรนเดอร์\n- `packages/coding-agent/src/extensibility/hooks/types.ts` — สัญญา UI ของ hook (ลายเซ็นกำหนดเอง legacy)\n- `packages/coding-agent/src/extensibility/custom-tools/types.ts` — สัญญา execute/render ของเครื่องมือกำหนดเอง\n- `packages/coding-agent/src/modes/components/tool-execution.ts` — การเมานต์คอมโพเนนต์ `renderCall`/`renderResult` และตัวเลือก partial-state\n- `packages/coding-agent/src/tools/context.ts` — การ propagate UI context ของเครื่องมือ (`hasUI`, `ui`)\n",
	"zh-cn/configuration/blob-artifact-architecture.md": "---\ntitle: Blob 与 Artifact 存储架构\ndescription: 基于内容寻址的 Blob 存储与 Artifact 注册表，用于会话媒体、截图和工具输出。\nsidebar:\n  order: 7\n  label: Blob 与 artifact 存储\ni18n:\n  sourceHash: 70d255f48d5b\n  translator: machine\n---\n\n# Blob 与 artifact 存储架构\n\n本文档描述了 coding-agent 如何在会话 JSONL 之外存储大型/二进制负载，截断的工具输出如何持久化，以及内部 URL（`artifact://`、`agent://`）如何解析回存储的数据。\n\n## 为什么存在两套存储系统\n\n运行时针对不同的数据形态使用两种不同的持久化机制：\n\n- **内容寻址 blob**（`blob:sha256:<hash>`）：全局的、面向二进制的存储，用于将大型图像 base64 负载从持久化的会话条目中外部化。\n- **会话作用域 artifact**（`<sessionFile-without-.jsonl>/` 下的文件）：按会话组织的文本文件，用于完整的工具输出和子代理输出。\n\n它们被有意设计为分离的：\n\n- blob 存储通过内容哈希优化去重和稳定引用，\n- artifact 存储通过本地 ID 优化仅追加的会话工具操作和人工/工具检索。\n\n## 存储边界与磁盘布局\n\n## Blob 存储边界（全局）\n\n`SessionManager` 构造 `BlobStore(getBlobsDir())`，因此 blob 文件存放在共享的全局 blob 目录中（不在会话文件夹中）。\n\nBlob 文件命名：\n\n- 文件路径：`<blobsDir>/<sha256-hex>`\n- 无扩展名\n- 条目中存储的引用字符串：`blob:sha256:<sha256-hex>`\n\n影响：\n\n- 跨会话的相同二进制内容会解析到相同的哈希/路径，\n- 在内容层面写入是幂等的，\n- blob 的生命周期可以超过任何单个会话文件。\n\n## Artifact 边界（会话本地）\n\n`ArtifactManager` 从会话文件路径派生 artifact 目录：\n\n- 会话文件：`.../<timestamp>_<sessionId>.jsonl`\n- artifact 目录：`.../<timestamp>_<sessionId>/`（去掉 `.jsonl`）\n\nArtifact 类型共享此目录：\n\n- 截断的工具输出文件：`<numericId>.<toolType>.log`（对应 `artifact://`）\n- 子代理输出文件：`<outputId>.md`（对应 `agent://`）\n\n## ID 和名称分配方案\n\n## Blob ID：内容哈希\n\n`BlobStore.put()` 对原始二进制字节计算 SHA-256 并返回：\n\n- `hash`：十六进制摘要，\n- `path`：`<blobsDir>/<hash>`，\n- `ref`：`blob:sha256:<hash>`。\n\n不使用会话本地计数器。\n\n## Artifact ID：会话本地单调递增整数\n\n`ArtifactManager` 在首次使用时扫描已有的 `*.log` artifact 文件以找到最大的数字 ID，并设置 `nextId = max + 1`。\n\n分配行为：\n\n- 文件格式：`{id}.{toolType}.log`\n- ID 是顺序字符串（`\"0\"`、`\"1\"`、...）\n- 恢复时不会覆盖已有的 artifact，因为扫描在分配之前进行。\n\n如果 artifact 目录不存在，扫描返回空列表，分配从 `0` 开始。\n\n## 代理输出 ID（`agent://`）\n\n`AgentOutputManager` 为子代理输出分配 ID，格式为 `<index>-<requestedId>`（可选地嵌套在父前缀下，例如 `0-Parent.1-Child`）。它在初始化时扫描已有的 `.md` 文件，以便在恢复时从下一个索引继续。\n\n## 持久化数据流\n\n## 1）会话条目持久化重写路径\n\n在会话条目写入之前（`#rewriteFile` / 增量持久化），`SessionManager` 调用 `prepareEntryForPersistence()`（通过 `truncateForPersistence`）。\n\n关键行为：\n\n1. **大字符串截断**：超大字符串被截断并添加后缀 `\"[Session persistence truncated large content]\"`。\n2. **临时字段剥离**：从持久化条目中移除 `partialJson` 和 `jsonlEvents`。\n3. **图像外部化为 blob**：\n   - 仅适用于 `content` 数组中的图像块，\n   - 仅当 `data` 尚未是 blob 引用时，\n   - 仅当 base64 长度至少达到阈值（`BLOB_EXTERNALIZE_THRESHOLD = 1024`）时，\n   - 将内联 base64 替换为 `blob:sha256:<hash>`。\n\n这使会话 JSONL 保持紧凑，同时保留可恢复性。\n\n## 2）会话加载再水合路径\n\n打开会话时（`setSessionFile`），在迁移之后，`SessionManager` 运行 `resolveBlobRefsInEntries()`。\n\n对于每个带有 `blob:sha256:<hash>` 的 message/custom-message 图像块：\n\n- 从 blob 存储读取 blob 字节，\n- 将字节转换回 base64，\n- 修改内存中的条目为内联 base64，供运行时消费者使用。\n\n如果 blob 缺失：\n\n- `resolveImageData()` 记录警告，\n- 返回原始引用字符串不变，\n- 加载继续（不会硬崩溃）。\n\n## 3）工具输出溢出/截断路径\n\n`OutputSink` 驱动 bash/python/ssh 及相关执行器中的流式输出。\n\n行为：\n\n1. 每个数据块被清理并追加到内存尾部缓冲区。\n2. 当内存字节超过溢出阈值（`DEFAULT_MAX_BYTES`，50KB）时，sink 标记输出为已截断。\n3. 如果 artifact 路径可用，sink 打开文件写入器并写入：\n   - 已有的缓冲内容（一次性写入），\n   - 所有后续数据块。\n4. 内存缓冲区始终被裁剪到尾部窗口以供显示。\n5. `dump()` 返回的摘要仅在文件 sink 成功创建时包含 `artifactId`。\n\n实际效果：\n\n- UI/工具返回显示截断的尾部，\n- 完整输出保存在 artifact 文件中，并以 `artifact://<id>` 引用。\n\n如果文件 sink 创建失败（I/O 错误、路径缺失等），sink 静默回退到仅内存截断；完整输出不会被持久化。\n\n## URL 访问模型\n\n## `blob:` 引用\n\n`blob:sha256:<hash>` 是持久化在会话条目负载中的引用，不是由路由器处理的内部 URL 方案。解析由 `SessionManager` 在会话加载时完成。\n\n## `artifact://<id>`\n\n由 `ArtifactProtocolHandler` 处理：\n\n- 需要活跃的会话 artifact 目录，\n- ID 必须是数字，\n- 通过匹配文件名前缀 `<id>.` 来解析，\n- 从匹配的 `.log` 文件返回原始文本（`text/plain`），\n- 缺失时，错误信息包含可用 artifact ID 列表。\n\n目录缺失行为：\n\n- 如果 artifact 目录不存在，抛出 `No artifacts directory found`。\n\n## `agent://<id>`\n\n由 `AgentProtocolHandler` 处理，操作 `<artifactsDir>/<id>.md`：\n\n- 普通形式返回 markdown 文本，\n- `/path` 或 `?q=` 形式执行 JSON 提取，\n- 路径和查询提取不能组合使用，\n- 如果请求了提取，文件内容必须可解析为 JSON。\n\n目录缺失行为：\n\n- 抛出 `No artifacts directory found`。\n\n输出缺失行为：\n\n- 抛出 `Not found: <id>`，并列出已有 `.md` 文件中的可用 ID。\n\n读取工具集成：\n\n- `read` 对非提取的内部 URL 读取支持 offset/limit 分页，\n- 当使用 `agent://` 提取时拒绝 `offset/limit`。\n\n## 恢复、分叉和移动语义\n\n## 恢复\n\n- `ArtifactManager` 在首次分配时扫描已有的 `{id}.*.log` 文件并继续编号。\n- `AgentOutputManager` 扫描已有的 `.md` 输出 ID 并继续编号。\n- `SessionManager` 在加载时将 blob 引用再水合为 base64。\n\n## 分叉\n\n`SessionManager.fork()` 创建带有新会话 ID 和 `parentSession` 链接的新会话文件，然后返回旧/新文件路径。Artifact 复制由 `AgentSession.fork()` 处理：\n\n- 尝试将旧 artifact 目录递归复制到新 artifact 目录，\n- 容忍旧目录缺失，\n- 非 ENOENT 的复制错误记录为警告，分叉仍然完成。\n\n分叉后的 ID 影响：\n\n- 如果复制成功，新会话中的 artifact 计数器从已复制的最大 ID 之后继续，\n- 如果复制失败/跳过，新会话 artifact ID 从 `0` 开始。\n\n分叉后的 blob 影响：\n\n- blob 是全局的且基于内容寻址，因此不需要复制 blob 目录。\n\n## 移动到新的工作目录\n\n`SessionManager.moveTo()` 将会话文件和 artifact 目录重命名到新的默认会话目录，并在后续步骤失败时提供回滚逻辑。这在重新定位会话作用域的同时保持 artifact 身份不变。\n\n## 故障处理与回退路径\n\n| 场景 | 行为 |\n| --- | --- |\n| 再水合期间 blob 文件缺失 | 记录警告并在内存中保留 `blob:sha256:` 引用字符串 |\n| 通过 `BlobStore.get` 读取 blob 时 ENOENT | 返回 `null` |\n| Artifact 目录缺失（`ArtifactManager.listFiles`） | 返回空列表（分配可以从头开始） |\n| Artifact 目录缺失（`artifact://` / `agent://`） | 抛出明确的 `No artifacts directory found` |\n| Artifact ID 未找到 | 抛出异常并列出可用 ID |\n| OutputSink artifact 写入器初始化失败 | 继续仅使用尾部截断（不生成完整输出 artifact） |\n| 无会话文件（某些任务路径） | Task 工具回退到临时 artifact 目录用于子代理输出 |\n\n## 二进制 blob 外部化与文本输出 artifact\n\n- **Blob 外部化** 用于持久化会话条目内容中的二进制图像负载；它将 JSONL 中的内联 base64 替换为稳定的内容引用。\n- **Artifact** 是用于执行输出和子代理输出的纯文本文件；它们通过会话本地 ID 经由内部 URL 进行寻址。\n\n这两个系统仅间接交叉（都减少了会话 JSONL 膨胀），但具有不同的身份标识、生命周期和检索路径。\n\n## 实现文件\n\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts) — blob 引用格式、哈希计算、put/get、外部化/解析辅助函数。\n- [`src/session/artifacts.ts`](../../packages/coding-agent/src/session/artifacts.ts) — 会话 artifact 目录模型和数字 artifact ID 分配。\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — `OutputSink` 截断/溢出到文件行为和摘要元数据。\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts) — 持久化转换、加载时 blob 再水合、会话分叉/移动交互。\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — 交互式分叉期间的 artifact 目录复制。\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — 工具 artifact 管理器引导和按工具的 artifact 路径分配。\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — `artifact://` 解析器。\n- [`src/internal-urls/agent-protocol.ts`](../../packages/coding-agent/src/internal-urls/agent-protocol.ts) — `agent://` 解析器 + JSON 提取。\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — 内部 URL 路由器接线和 artifact 目录解析器。\n- [`src/task/output-manager.ts`](../../packages/coding-agent/src/task/output-manager.ts) — 会话作用域的代理输出 ID 分配，用于 `agent://`。\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — 子代理输出 artifact 写入（`<id>.md`）和临时 artifact 目录回退。\n",
	"zh-cn/configuration/config-usage.md": "---\ntitle: 配置发现与解析\ndescription: xcsh 如何从项目、用户和企业根目录发现、解析和分层配置。\nsidebar:\n  order: 1\n  label: 配置\ni18n:\n  sourceHash: e38bd9792499\n  translator: machine\n---\n\n# 配置发现与解析\n\n本文档描述了 coding-agent 当前如何解析配置：扫描哪些根目录、优先级如何运作，以及已解析的配置如何被设置、技能、钩子、工具和扩展所使用。\n\n## 范围\n\n主要实现：\n\n- `src/config.ts`\n- `src/config/settings.ts`\n- `src/config/settings-schema.ts`\n- `src/discovery/builtin.ts`\n- `src/discovery/helpers.ts`\n\n关键集成点：\n\n- `src/capability/index.ts`\n- `src/discovery/index.ts`\n- `src/extensibility/skills.ts`\n- `src/extensibility/hooks/loader.ts`\n- `src/extensibility/custom-tools/loader.ts`\n- `src/extensibility/extensions/loader.ts`\n\n---\n\n## 解析流程（可视化）\n\n```text\n         Config roots (ordered)\n┌───────────────────────────────────────┐\n│ 1) ~/.xcsh/agent + <cwd>/.xcsh          │\n│ 2) ~/.claude   + <cwd>/.claude        │\n│ 3) ~/.codex    + <cwd>/.codex         │\n│ 4) ~/.gemini   + <cwd>/.gemini        │\n└───────────────────────────────────────┘\n                    │\n                    ▼\n        config.ts helper resolution\n  (getConfigDirs/findConfigFile/findNearest...)\n                    │\n                    ▼\n       capability providers enumerate items\n (native, claude, codex, gemini, agents, etc.)\n                    │\n                    ▼\n      priority sort + per-capability dedup\n                    │\n                    ▼\n          subsystem-specific consumption\n   (settings, skills, hooks, tools, extensions)\n```\n\n## 1) 配置根目录与来源顺序\n\n## 规范根目录\n\n`src/config.ts` 定义了一个固定的来源优先级列表：\n\n1. `.xcsh`（原生）\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\n用户级基础目录：\n\n- `~/.xcsh/agent`\n- `~/.claude`\n- `~/.codex`\n- `~/.gemini`\n\n项目级基础目录：\n\n- `<cwd>/.xcsh`\n- `<cwd>/.claude`\n- `<cwd>/.codex`\n- `<cwd>/.gemini`\n\n`CONFIG_DIR_NAME` 为 `.xcsh`（`packages/utils/src/dirs.ts`）。\n\n## 重要约束\n\n`src/config.ts` 中的通用辅助函数在来源发现顺序中**不**包含 `.pi`。\n\n---\n\n## 2) 核心发现辅助函数（`src/config.ts`）\n\n## `getConfigDirs(subpath, options)`\n\n返回有序条目：\n\n- 用户级条目优先（按来源优先级排序）\n- 然后是项目级条目（按相同来源优先级排序）\n\n选项：\n\n- `user`（默认 `true`）\n- `project`（默认 `true`）\n- `cwd`（默认 `getProjectDir()`）\n- `existingOnly`（默认 `false`）\n\n此 API 用于基于目录的配置查找（命令、钩子、工具、代理等）。\n\n## `findConfigFile(subpath, options)` / `findConfigFileWithMeta(...)`\n\n在有序的基础目录中搜索第一个存在的文件，返回第一个匹配项（仅路径或路径+元数据）。\n\n## `findAllNearestProjectConfigDirs(subpath, cwd)`\n\n向上遍历父目录，返回**每个来源基础目录最近的现有目录**（`.xcsh`、`.claude`、`.codex`、`.gemini`），然后按来源优先级排序结果。\n\n当项目配置需要从祖先目录继承时使用此函数（monorepo/嵌套工作区行为）。\n\n---\n\n## 3) 文件配置包装器（`src/config.ts` 中的 `ConfigFile<T>`）\n\n`ConfigFile<T>` 是用于单个配置文件的带模式验证的加载器。\n\n支持的格式：\n\n- `.yml` / `.yaml`\n- `.json` / `.jsonc`\n\n行为：\n\n- 使用 AJV 根据提供的 TypeBox 模式验证解析后的数据。\n- 缓存加载结果直到调用 `invalidate()`。\n- 通过 `tryLoad()` 返回三态结果：\n  - `ok`\n  - `not-found`\n  - `error`（包含模式/解析上下文的 `ConfigError`）\n\n仍支持遗留迁移：\n\n- 如果目标路径是 `.yml`/`.yaml`，同级 `.json` 文件会被自动迁移一次（`migrateJsonToYml`）。\n\n---\n\n## 4) 设置解析模型（`src/config/settings.ts`）\n\n运行时设置模型是分层的：\n\n1. 全局设置：`~/.xcsh/agent/config.yml`\n2. 项目设置：通过设置能力发现（来自提供者的 `settings.json`）\n3. 运行时覆盖：内存中，非持久化\n4. 模式默认值：来自 `SETTINGS_SCHEMA`\n\n有效读取路径：\n\n`defaults <- global <- project <- overrides`\n\n写入行为：\n\n- `settings.set(...)` 写入**全局**层（`config.yml`）并排队后台保存。\n- 项目设置从能力发现中只读获取。\n\n## 迁移行为仍然活跃\n\n启动时，如果 `config.yml` 不存在：\n\n1. 从 `~/.xcsh/agent/settings.json` 迁移（成功后重命名为 `.bak`）\n2. 与 `agent.db` 中的遗留数据库设置合并\n3. 将合并结果写入 `config.yml`\n\n`#migrateRawSettings` 中的字段级迁移：\n\n- `queueMode` -> `steeringMode`\n- `ask.timeout` 毫秒 -> 秒（当旧值看起来像毫秒时，即 `> 1000`）\n- 遗留的扁平 `theme: \"...\"` -> `theme.dark/theme.light` 结构\n\n---\n\n## 5) 能力/发现集成\n\n大多数非核心配置加载通过能力注册表（`src/capability/index.ts` + `src/discovery/index.ts`）进行。\n\n## 提供者排序\n\n提供者按数值优先级排序（越高越优先）。示例优先级：\n\n- 原生 OMP（`builtin.ts`）：`100`\n- Claude：`80`\n- Codex / agents / Claude marketplace：`70`\n- Gemini：`60`\n\n```text\nProvider precedence (higher wins)\n\nnative (.xcsh)          priority 100\nclaude                 priority  80\ncodex / agents / ...   priority  70\ngemini                 priority  60\n```\n\n## 去重语义\n\n能力定义了一个 `key(item)`：\n\n- 相同 key => 第一个项目胜出（更高优先级/更早加载的项目）\n- 无 key（`undefined`）=> 不去重，保留所有项目\n\n相关键：\n\n- 技能：`name`\n- 工具：`name`\n- 钩子：`${type}:${tool}:${name}`\n- 扩展模块：`name`\n- 扩展：`name`\n- 设置：不去重（保留所有项目）\n\n---\n\n## 6) 原生 `.xcsh` 提供者行为（`src/discovery/builtin.ts`）\n\n原生提供者（`id: native`）从以下位置读取：\n\n- 项目：`<cwd>/.xcsh/...`\n- 用户：`~/.xcsh/agent/...`\n\n### 目录准入规则\n\n`builtin.ts` 仅在目录存在**且非空**（`ifNonEmptyDir`）时才包含配置根目录。\n\n### 按作用域加载\n\n- 技能：`skills/*/SKILL.md`\n- 斜杠命令：`commands/*.md`\n- 规则：`rules/*.{md,mdc}`\n- 提示词：`prompts/*.md`\n- 指令：`instructions/*.md`\n- 钩子：`hooks/pre/*`、`hooks/post/*`\n- 工具：`tools/*.json|*.md` 和 `tools/<name>/index.ts`\n- 扩展模块：在 `extensions/` 下发现（+ 遗留的 `settings.json.extensions` 字符串数组）\n- 扩展：`extensions/<name>/gemini-extension.json`\n- 设置能力：`settings.json`\n\n### 最近项目查找的细微差别\n\n对于 `SYSTEM.md` 和 `XCSH.md`，原生提供者使用最近祖先项目 `.xcsh` 目录搜索（向上遍历），但仍然要求 `.xcsh` 目录非空。\n\n---\n\n## 7) 主要子系统如何消费配置\n\n## 设置子系统\n\n- `Settings.init()` 加载全局 `config.yml` + 已发现的项目 `settings.json` 能力项。\n- 仅 `level === \"project\"` 的能力项会被合并到项目层。\n\n## 技能子系统\n\n- `extensibility/skills.ts` 通过 `loadCapability(skillCapability.id, { cwd })` 加载。\n- 应用来源开关和过滤器（`ignoredSkills`、`includeSkills`、自定义目录）。\n- 遗留命名的开关仍然存在（`skills.enablePiUser`、`skills.enablePiProject`），但它们控制原生提供者（`provider === \"native\"`）。\n\n## 钩子子系统\n\n- `discoverAndLoadHooks()` 从钩子能力 + 显式配置路径解析钩子路径。\n- 然后通过 Bun import 加载模块。\n\n## 工具子系统\n\n- `discoverAndLoadCustomTools()` 从工具能力 + 插件工具路径 + 显式配置路径解析工具路径。\n- 声明式 `.md/.json` 工具文件仅为元数据；可执行加载需要代码模块。\n\n## 扩展子系统\n\n- `discoverAndLoadExtensions()` 从扩展模块能力加上显式路径解析扩展模块。\n- 当前实现在加载前有意只保留 `_source.provider === \"native\"` 的能力项。\n\n---\n\n## 8) 可依赖的优先级规则\n\n使用以下心智模型：\n\n1. `config.ts` 中的来源目录排序决定候选路径顺序。\n2. 能力提供者优先级决定跨提供者的优先顺序。\n3. 能力键去重决定冲突行为（对于有键的能力，第一个胜出）。\n4. 子系统特定的合并逻辑可以进一步改变有效优先级（特别是设置）。\n\n### 设置相关的注意事项\n\n设置能力项不会被去重；`Settings.#loadProjectSettings()` 按返回顺序对项目项进行深度合并。因为合并会将后面项的值覆盖前面项的值，有效的覆盖行为取决于提供者的发出顺序，而不仅仅是能力键语义。\n\n---\n\n## 9) 仍然存在的遗留/兼容性行为\n\n- `ConfigFile` 对以 YAML 为目标的文件进行 JSON -> YAML 迁移。\n- 设置从 `settings.json` 和 `agent.db` 迁移到 `config.yml`。\n- 设置键迁移（`queueMode`、`ask.timeout`、扁平 `theme`）。\n- 扩展清单兼容性：加载器同时接受 `package.json.xcsh` 和 `package.json.pi` 清单部分。\n- 遗留设置名称 `skills.enablePiUser` / `skills.enablePiProject` 仍然是原生技能来源的活跃控制开关。\n\n如果这些兼容性路径在代码中被移除，请立即更新本文档；目前有多个运行时行为仍然依赖于它们。\n",
	"zh-cn/configuration/environment-variables.md": "---\ntitle: 环境变量\ndescription: xcsh 配置和行为控制的运行时环境变量参考。\nsidebar:\n  order: 2\n  label: 环境变量\ni18n:\n  sourceHash: e2890f963c02\n  translator: machine\n---\n\n# 环境变量（当前运行时参考）\n\n本参考文档基于以下代码路径中的当前实现：\n\n- `packages/coding-agent/src/**`\n- `packages/ai/src/**`（coding-agent 使用的提供商/认证解析）\n- `packages/utils/src/**` 和 `packages/tui/src/**` 中直接影响 coding-agent 运行时的变量\n\n本文档仅记录当前有效的行为。\n\n## 解析模型和优先级\n\n大多数运行时查找使用来自 `@f5-sales-demo/pi-utils`（`packages/utils/src/env.ts`）的 `$env`。\n\n`$env` 加载顺序：\n\n1. 现有进程环境（`Bun.env`）\n2. 项目 `.env`（`$PWD/.env`），仅用于未设置的键\n3. 用户主目录 `.env`（`~/.env`），仅用于未设置的键\n\n`.env` 文件的附加规则：解析时 `XCSH_*` 键会被镜像到 `PI_*` 键。\n\n---\n\n## 1) 模型/提供商认证\n\n除非另有说明，这些变量通过 `getEnvApiKey()`（`packages/ai/src/stream.ts`）使用。\n\n### 核心提供商凭据\n\n| 变量                        | 用途 | 何时需要                                                 | 备注 / 优先级                                                                                  |\n|---------------------------------|---|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|\n| `ANTHROPIC_OAUTH_TOKEN`         | Anthropic API 认证 | 使用 Anthropic 的 OAuth 令牌认证时                         | 在提供商认证解析中优先于 `ANTHROPIC_API_KEY`                              |\n| `ANTHROPIC_API_KEY`             | Anthropic API 认证 | 不使用 OAuth 令牌时使用 Anthropic                           | `ANTHROPIC_OAUTH_TOKEN` 之后的备选项                                                              |\n| `ANTHROPIC_FOUNDRY_API_KEY`     | 通过 Azure Foundry / 企业网关使用 Anthropic | 启用 `CLAUDE_CODE_USE_FOUNDRY` 时                             | 启用 Foundry 模式时优先于 `ANTHROPIC_OAUTH_TOKEN` 和 `ANTHROPIC_API_KEY`  |\n| `OPENAI_API_KEY`                | OpenAI 认证 | 使用 OpenAI 系列提供商且未显式传入 apiKey 参数时 | 用于 OpenAI Completions/Responses 提供商                                                      |\n| `GEMINI_API_KEY`                | Google Gemini 认证 | 使用 `google` 提供商模型时                                | Gemini 提供商映射的主要密钥                                                             |\n| `GOOGLE_API_KEY`                | Gemini 图像工具认证备选 | 使用 `gemini_image` 工具且未设置 `GEMINI_API_KEY` 时            | 用于 coding-agent 图像工具备选路径                                                       |\n| `GROQ_API_KEY`                  | Groq 认证 | 使用 Groq 模型时                                             |                                                                                                     |\n| `CEREBRAS_API_KEY`              | Cerebras 认证 | 使用 Cerebras 模型时                                         |                                                                                                     |\n| `TOGETHER_API_KEY`              | Together 认证 | 使用 `together` 提供商时                                     |                                                                                                     |\n| `HUGGINGFACE_HUB_TOKEN`         | Hugging Face 认证 | 使用 `huggingface` 提供商时                                  | 主要的 Hugging Face 令牌环境变量                                                                  |\n| `HF_TOKEN`                      | Hugging Face 认证 | 使用 `huggingface` 提供商时                                  | `HUGGINGFACE_HUB_TOKEN` 未设置时的备选项                                                      |\n| `SYNTHETIC_API_KEY`             | Synthetic 认证 | 使用 Synthetic 模型时                                        |                                                                                                     |\n| `NVIDIA_API_KEY`                | NVIDIA 认证 | 使用 `nvidia` 提供商时                                       |                                                                                                     |\n| `NANO_GPT_API_KEY`              | NanoGPT 认证 | 使用 `nanogpt` 提供商时                                      |                                                                                                     |\n| `VENICE_API_KEY`                | Venice 认证 | 使用 `venice` 提供商时                                       |                                                                                                     |\n| `LITELLM_API_KEY`               | LiteLLM 认证 | 使用 `litellm` 提供商时                                      | 兼容 OpenAI 的 LiteLLM 代理密钥。与 `LITELLM_BASE_URL` 一起设置时，可自动配置 `models.yml` |\n| `LM_STUDIO_API_KEY`             | LM Studio 认证（可选） | 使用 `lm-studio` 提供商连接需认证的主机时           | 本地 LM Studio 通常无需认证运行；需要密钥时任何非空令牌均可         |\n| `OLLAMA_API_KEY`                | Ollama 认证（可选） | 使用 `ollama` 提供商连接需认证的主机时              | 本地 Ollama 通常无需认证运行；需要密钥时任何非空令牌均可            |\n| `LLAMA_CPP_API_KEY`             | Ollama 认证（可选） | 使用带 `--api-key` 参数的 `llama-server` 时              | 本地 llama.cpp 通常无需认证运行；配置密钥时任何非空令牌均可       |\n| `XIAOMI_API_KEY`                | 小米 MiMo 认证 | 使用 `xiaomi` 提供商时                                       |                                                                                                     |\n| `MOONSHOT_API_KEY`              | Moonshot 认证 | 使用 `moonshot` 提供商时                                     |                                                                                                     |\n| `XAI_API_KEY`                   | xAI 认证 | 使用 xAI 模型时                                              |                                                                                                     |\n| `OPENROUTER_API_KEY`            | OpenRouter 认证 | 使用 OpenRouter 模型时                                       | 当首选/自动提供商为 OpenRouter 时，图像工具也会使用此密钥                                  |\n| `MISTRAL_API_KEY`               | Mistral 认证 | 使用 Mistral 模型时                                          |                                                                                                     |\n| `ZAI_API_KEY`                   | z.ai 认证 | 使用 z.ai 模型时                                             | z.ai 网络搜索提供商也使用此密钥                                                               |\n| `MINIMAX_API_KEY`               | MiniMax 认证 | 使用 `minimax` 提供商时                                      |                                                                                                     |\n| `MINIMAX_CODE_API_KEY`          | MiniMax Code 认证 | 使用 `minimax-code` 提供商时                                 |                                                                                                     |\n| `MINIMAX_CODE_CN_API_KEY`       | MiniMax Code CN 认证 | 使用 `minimax-code-cn` 提供商时                              |                                                                                                     |\n| `OPENCODE_API_KEY`              | OpenCode 认证 | 使用 OpenCode 模型时                                         |                                                                                                     |\n| `QIANFAN_API_KEY`               | 千帆认证 | 使用 `qianfan` 提供商时                                      |                                                                                                     |\n| `QWEN_OAUTH_TOKEN`              | 通义千问门户认证 | 使用 `qwen-portal` 的 OAuth 令牌时                          | 优先于 `QWEN_PORTAL_API_KEY`                                                         |\n| `QWEN_PORTAL_API_KEY`           | 通义千问门户认证 | 使用 `qwen-portal` 的 API 密钥时                              | `QWEN_OAUTH_TOKEN` 之后的备选项                                                                   |\n| `ZENMUX_API_KEY`                | ZenMux 认证 | 使用 `zenmux` 提供商时                                       | 用于 ZenMux 兼容 OpenAI 和 Anthropic 的路由                                              |\n| `VLLM_API_KEY`                  | vLLM 认证/发现选择加入 | 使用 `vllm` 提供商（本地兼容 OpenAI 的服务器）时       | 对于无认证的本地服务器，任何非空值均可                                                 |\n| `CURSOR_ACCESS_TOKEN`           | Cursor 提供商认证 | 使用 Cursor 提供商时                                         |                                                                                                     |\n| `AI_GATEWAY_API_KEY`            | Vercel AI Gateway 认证 | 使用 `vercel-ai-gateway` 提供商时                            |                                                                                                     |\n| `CLOUDFLARE_AI_GATEWAY_API_KEY` | Cloudflare AI Gateway 认证 | 使用 `cloudflare-ai-gateway` 提供商时                        | 基础 URL 必须配置为 `https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic` |\n\n### GitHub/Copilot 令牌链\n\n| 变量 | 用途 | 链 |\n|---|---|---|\n| `COPILOT_GITHUB_TOKEN` | GitHub Copilot 提供商认证 | `COPILOT_GITHUB_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN` |\n| `GH_TOKEN` | Copilot 备选；网页抓取器中的 GitHub API 认证 | 在网页抓取器中：`GITHUB_TOKEN` → `GH_TOKEN` |\n| `GITHUB_TOKEN` | Copilot 备选；网页抓取器中的 GitHub API 认证 | 在网页抓取器中：在 `GH_TOKEN` 之前检查 |\n\n---\n\n## 2) 提供商特定运行时配置\n\n### Anthropic Foundry 网关（Azure / 企业代理）\n\n当启用 `CLAUDE_CODE_USE_FOUNDRY` 时，Anthropic 请求切换到 Foundry 模式：\n\n- 基础 URL 从 `FOUNDRY_BASE_URL` 解析（未设置时回退到模型/默认基础 URL）。\n- 提供商 `anthropic` 的 API 密钥解析顺序变为：\n  `ANTHROPIC_FOUNDRY_API_KEY` → `ANTHROPIC_OAUTH_TOKEN` → `ANTHROPIC_API_KEY`。\n- `ANTHROPIC_CUSTOM_HEADERS` 被解析为逗号/换行符分隔的 `key: value` 对，并合并到请求头中。\n- TLS 客户端/服务端材料可从环境变量值注入：\n  `NODE_EXTRA_CA_CERTS`、`CLAUDE_CODE_CLIENT_CERT`、`CLAUDE_CODE_CLIENT_KEY`。\n  每个变量接受以下格式之一：\n  - PEM 内容的文件系统路径，或\n  - 内联 PEM（包括转义的 `\\n` 序列）。\n\n| 变量 | 值类型 | 行为 |\n|---|---|---|\n| `CLAUDE_CODE_USE_FOUNDRY` | 布尔型字符串（`1`、`true`、`yes`、`on`） | 为 Anthropic 提供商启用 Foundry 模式 |\n| `FOUNDRY_BASE_URL` | URL 字符串 | Foundry 模式下的 Anthropic 端点基础 URL |\n| `ANTHROPIC_FOUNDRY_API_KEY` | 令牌字符串 | 用于 `Authorization: Bearer <token>` |\n| `ANTHROPIC_CUSTOM_HEADERS` | 请求头列表字符串 | 额外请求头；格式为 `header-a: value, header-b: value` 或换行符分隔 |\n| `NODE_EXTRA_CA_CERTS` | PEM 路径或内联 PEM | 用于服务器证书验证的额外 CA 链 |\n| `CLAUDE_CODE_CLIENT_CERT` | PEM 路径或内联 PEM | mTLS 客户端证书 |\n| `CLAUDE_CODE_CLIENT_KEY` | PEM 路径或内联 PEM | mTLS 客户端私钥（必须与证书配对） |\n\n### Amazon Bedrock\n\n| 变量 | 默认值 / 行为 |\n|---|---|\n| `AWS_REGION` | 主要区域来源 |\n| `AWS_DEFAULT_REGION` | `AWS_REGION` 未设置时的备选 |\n| `AWS_PROFILE` | 启用命名配置文件认证路径 |\n| `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` | 启用 IAM 密钥认证路径 |\n| `AWS_BEARER_TOKEN_BEDROCK` | 启用持有者令牌认证路径 |\n| `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` / `AWS_CONTAINER_CREDENTIALS_FULL_URI` | 启用 ECS 任务凭据路径 |\n| `AWS_WEB_IDENTITY_TOKEN_FILE` + `AWS_ROLE_ARN` | 启用 Web 身份认证路径 |\n| `AWS_BEDROCK_SKIP_AUTH` | 如果为 `1`，注入虚拟凭据（代理/无认证场景） |\n| `AWS_BEDROCK_FORCE_HTTP1` | 如果为 `1`，强制使用 Node HTTP/1 请求处理器 |\n\n提供商代码中的区域回退：`options.region` → `AWS_REGION` → `AWS_DEFAULT_REGION` → `us-east-1`。\n\n### Azure OpenAI Responses\n\n| 变量 | 默认值 / 行为 |\n|---|---|\n| `AZURE_OPENAI_API_KEY` | 除非通过选项传入 API 密钥，否则必需 |\n| `AZURE_OPENAI_API_VERSION` | 默认 `v1` |\n| `AZURE_OPENAI_BASE_URL` | 直接覆盖基础 URL |\n| `AZURE_OPENAI_RESOURCE_NAME` | 用于构建基础 URL：`https://<resource>.openai.azure.com/openai/v1` |\n| `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` | 可选映射字符串：`modelId=deploymentName,model2=deployment2` |\n\n基础 URL 解析：选项 `azureBaseUrl` → 环境变量 `AZURE_OPENAI_BASE_URL` → 选项/环境变量中的资源名称 → `model.baseUrl`。\n\n### Google Vertex AI\n\n| 变量 | 是否必需？ | 备注 |\n|---|---|---|\n| `GOOGLE_CLOUD_PROJECT` | 是（除非通过选项传入） | 备选：`GCLOUD_PROJECT` |\n| `GCLOUD_PROJECT` | 备选 | 用作替代项目 ID 来源 |\n| `GOOGLE_CLOUD_LOCATION` | 是（除非通过选项传入） | 提供商中无默认值 |\n| `GOOGLE_APPLICATION_CREDENTIALS` | 有条件 | 如果设置，文件必须存在；否则检查 ADC 备选路径（`~/.config/gcloud/application_default_credentials.json`） |\n\n### Kimi\n\n| 变量 | 默认值 / 行为 |\n|---|---|\n| `KIMI_CODE_OAUTH_HOST` | 主要 OAuth 主机覆盖 |\n| `KIMI_OAUTH_HOST` | 备选 OAuth 主机覆盖 |\n| `KIMI_CODE_BASE_URL` | 覆盖 Kimi 使用端点基础 URL（`usage/kimi.ts`） |\n\nOAuth 主机链：`KIMI_CODE_OAUTH_HOST` → `KIMI_OAUTH_HOST` → `https://auth.kimi.com`。\n\n### Antigravity/Gemini 图像兼容性\n\n| 变量 | 默认值 / 行为 |\n|---|---|\n| `PI_AI_ANTIGRAVITY_VERSION` | 覆盖 Gemini CLI 提供商中的 Antigravity user-agent 版本标签 |\n\n### OpenAI Codex responses（功能/调试控制）\n\n| 变量 | 行为 |\n|---|---|\n| `PI_CODEX_DEBUG` | `1`/`true` 启用 Codex 提供商调试日志 |\n| `PI_CODEX_WEBSOCKET` | `1`/`true` 启用 WebSocket 传输优先 |\n| `PI_CODEX_WEBSOCKET_V2` | `1`/`true` 启用 WebSocket v2 路径 |\n| `PI_CODEX_WEBSOCKET_IDLE_TIMEOUT_MS` | 正整数覆盖（默认 300000） |\n| `PI_CODEX_WEBSOCKET_RETRY_BUDGET` | 非负整数覆盖（默认 5） |\n| `PI_CODEX_WEBSOCKET_RETRY_DELAY_MS` | 正整数基础退避覆盖（默认 500） |\n\n### Cursor 提供商调试\n\n| 变量 | 行为 |\n|---|---|\n| `DEBUG_CURSOR` | 启用提供商调试日志；`2`/`verbose` 可查看详细的负载片段 |\n| `DEBUG_CURSOR_LOG` | 可选的 JSONL 调试日志输出文件路径 |\n\n### 提示缓存兼容性开关\n\n| 变量 | 行为 |\n|---|---|\n| `PI_CACHE_RETENTION` | 如果为 `long`，在支持的提供商中启用长期保留（`anthropic`、`openai-responses`、Bedrock 保留解析） |\n\n---\n\n## 3) 网络搜索子系统\n\n### 搜索提供商凭据\n\n| 变量 | 使用者 |\n|---|---|\n| `EXA_API_KEY` | Exa 搜索提供商和 Exa MCP 工具 |\n| `BRAVE_API_KEY` | Brave 搜索提供商 |\n| `PERPLEXITY_API_KEY` | Perplexity 搜索提供商 API 密钥模式 |\n| `TAVILY_API_KEY` | Tavily 搜索提供商 |\n| `ZAI_API_KEY` | z.ai 搜索提供商（也检查 `agent.db` 中存储的 OAuth） |\n| `OPENAI_API_KEY` / 数据库中的 Codex OAuth | Codex 搜索提供商可用性/认证 |\n\n### Anthropic 网络搜索认证链\n\n`packages/coding-agent/src/web/search/auth.ts` 按以下顺序解析 Anthropic 网络搜索凭据：\n\n1. `ANTHROPIC_SEARCH_API_KEY`（+ 可选的 `ANTHROPIC_SEARCH_BASE_URL`）\n2. `models.json` 中 `api: \"anthropic-messages\"` 的提供商条目\n3. `agent.db` 中的 Anthropic OAuth 凭据（不得在 5 分钟缓冲期内过期）\n4. 通用 Anthropic 环境变量备选：提供商密钥（`ANTHROPIC_FOUNDRY_API_KEY`/`ANTHROPIC_OAUTH_TOKEN`/`ANTHROPIC_API_KEY`）+ 可选的 `ANTHROPIC_BASE_URL`（Foundry 模式启用时为 `FOUNDRY_BASE_URL`）\n\n相关变量：\n\n| 变量 | 默认值 / 行为 |\n|---|---|\n| `ANTHROPIC_SEARCH_API_KEY` | 最高优先级的显式搜索密钥 |\n| `ANTHROPIC_SEARCH_BASE_URL` | 省略时默认为 `https://api.anthropic.com` |\n| `ANTHROPIC_SEARCH_MODEL` | 默认为 `claude-haiku-4-5` |\n| `ANTHROPIC_BASE_URL` | 第 4 层认证路径的通用备选基础 URL |\n\n### Perplexity OAuth 流程行为标志\n\n| 变量 | 行为 |\n|---|---|\n| `PI_AUTH_NO_BORROW` | 如果设置，在 Perplexity 登录流程中禁用 macOS 原生应用令牌借用路径 |\n\n---\n\n## 4) Python 工具和内核运行时\n\n| 变量 | 默认值 / 行为 |\n|---|---|\n| `PI_PY` | Python 工具模式覆盖：`0`/`bash`=`bash-only`，`1`/`py`=`ipy-only`，`mix`/`both`=`both`；无效值将被忽略 |\n| `PI_PYTHON_SKIP_CHECK` | 如果为 `1`，跳过 Python 内核可用性检查/预热检查 |\n| `PI_PYTHON_GATEWAY_URL` | 如果设置，使用外部内核网关代替本地共享网关 |\n| `PI_PYTHON_GATEWAY_TOKEN` | 外部网关的可选认证令牌（`Authorization: token <value>`） |\n| `PI_PYTHON_IPC_TRACE` | 如果为 `1`，在内核模块中启用底层 IPC 跟踪路径 |\n| `VIRTUAL_ENV` | Python 运行时解析中最高优先级的虚拟环境路径 |\n\n额外的条件行为：\n\n- 如果 `BUN_ENV=test` 或 `NODE_ENV=test`，Python 可用性检查将被视为正常，并跳过预热。\n- Python 环境过滤会拒绝常见的 API 密钥，并允许安全的基础变量以及 `LC_`、`XDG_`、`PI_` 前缀。\n\n---\n\n## 5) 代理/运行时行为开关\n\n| 变量                   | 默认值 / 行为                                                                           |\n|----------------------------|----------------------------------------------------------------------------------------------|\n| `PI_SMOL_MODEL`            | `smol` 的临时模型角色覆盖（CLI `--smol` 优先）                     |\n| `PI_SLOW_MODEL`            | `slow` 的临时模型角色覆盖（CLI `--slow` 优先）                     |\n| `PI_PLAN_MODEL`            | `plan` 的临时模型角色覆盖（CLI `--plan` 优先）                     |\n| `PI_NO_TITLE`              | 如果设置（任何非空值），在第一条用户消息时禁用自动会话标题生成   |\n| `NULL_PROMPT`              | 如果为 `true`，系统提示构建器返回空字符串                                        |\n| `PI_BLOCKED_AGENT`         | 在任务工具中阻止特定的子代理类型                                                 |\n| `PI_SUBPROCESS_CMD`        | 覆盖子代理生成命令（绕过 `xcsh` / `xcsh.cmd` 解析）                       |\n| `PI_TASK_MAX_OUTPUT_BYTES` | 每个子代理的最大捕获输出字节数（默认 `500000`）                                    |\n| `PI_TASK_MAX_OUTPUT_LINES` | 每个子代理的最大捕获输出行数（默认 `5000`）                                      |\n| `PI_TIMING`                | 如果为 `1`，启用启动/工具计时检测日志                                     |\n| `PI_DEBUG_STARTUP`         | 在多个启动路径中启用启动阶段调试输出到 stderr                       |\n| `PI_PACKAGE_DIR`           | 覆盖包资源基础目录解析（文档/示例/变更日志路径查找）            |\n| `PI_DISABLE_LSPMUX`        | 如果为 `1`，禁用 lspmux 检测/集成，强制直接生成 LSP 服务器          |\n| `LITELLM_BASE_URL`         | LiteLLM 代理基础 URL。与 `LITELLM_API_KEY` 一起设置时，首次运行时触发自动生成 `models.yml`，并在每次启动时进行自我修复 |\n| `LM_STUDIO_BASE_URL`       | 默认隐式 LM Studio 发现基础 URL 覆盖（未设置时为 `http://127.0.0.1:1234/v1`） |\n| `OLLAMA_BASE_URL`          | 默认隐式 Ollama 发现基础 URL 覆盖（未设置时为 `http://127.0.0.1:11434`）      |\n| `LLAMA_CPP_BASE_URL`       | 默认隐式 Llama.cpp 发现基础 URL 覆盖（未设置时为 `http://127.0.0.1:8080`）    |\n| `PI_EDIT_VARIANT`          | 如果为 `hashline`，当编辑工具可用时强制使用 hashline 读取/grep 显示模式               |\n| `PI_NO_PTY`                | 如果为 `1`，禁用 bash 工具的交互式 PTY 路径                                          |\n\n`PI_NO_PTY` 在使用 CLI `--no-pty` 时也会被内部设置。\n\n---\n\n## 6) 存储和配置根路径\n\n这些变量通过 `@f5-sales-demo/pi-utils/dirs` 使用，影响 coding-agent 存储数据的位置。\n\n| 变量 | 默认值 / 行为 |\n|---|---|\n| `PI_CONFIG_DIR` | 用户主目录下的配置根目录名（默认 `.xcsh`） |\n| `PI_CODING_AGENT_DIR` | 代理目录的完整覆盖路径（默认 `~/<PI_CONFIG_DIR or .xcsh>/agent`） |\n| `PWD` | 在路径辅助工具中用于匹配规范化的当前工作目录 |\n\n---\n\n## 7) Shell/工具执行环境\n\n（来自 `packages/utils/src/procmgr.ts` 和 coding-agent bash 工具集成。）\n\n| 变量 | 行为 |\n|---|---|\n| `PI_BASH_NO_CI` | 抑制自动向生成的 shell 环境注入 `CI=true` |\n| `CLAUDE_BASH_NO_CI` | `PI_BASH_NO_CI` 的旧版别名备选 |\n| `PI_BASH_NO_LOGIN` | 用于禁用登录 shell 模式 |\n| `CLAUDE_BASH_NO_LOGIN` | `PI_BASH_NO_LOGIN` 的旧版别名备选 |\n| `PI_SHELL_PREFIX` | 可选的命令前缀包装器 |\n| `CLAUDE_CODE_SHELL_PREFIX` | `PI_SHELL_PREFIX` 的旧版别名备选 |\n| `VISUAL` | 首选外部编辑器命令 |\n| `EDITOR` | 备选外部编辑器命令 |\n\n当前实现说明：`PI_BASH_NO_LOGIN`/`CLAUDE_BASH_NO_LOGIN` 会被读取，但当前 `getShellArgs()` 在两个分支中都返回 `['-l','-c']`（目前实际上是无操作）。\n\n---\n\n## 8) UI/主题/会话检测（自动检测的环境变量）\n\n这些变量作为运行时信号被读取；它们通常由终端/操作系统设置，而非手动配置。\n\n| 变量 | 用途 |\n|---|---|\n| `COLORTERM`、`TERM`、`WT_SESSION` | 颜色能力检测（主题颜色模式） |\n| `COLORFGBG` | 终端背景亮/暗自动检测 |\n| `TERM_PROGRAM`、`TERM_PROGRAM_VERSION`、`TERMINAL_EMULATOR` | 系统提示/上下文中的终端标识 |\n| `KDE_FULL_SESSION`、`XDG_CURRENT_DESKTOP`、`DESKTOP_SESSION`、`XDG_SESSION_DESKTOP`、`GDMSESSION`、`WINDOWMANAGER` | 系统提示/上下文中的桌面/窗口管理器检测 |\n| `KITTY_WINDOW_ID`、`TMUX_PANE`、`TERM_SESSION_ID`、`WT_SESSION` | 稳定的每终端会话标识 ID |\n| `SHELL`、`ComSpec`、`TERM_PROGRAM`、`TERM` | 系统信息诊断 |\n| `APPDATA`、`XDG_CONFIG_HOME` | lspmux 配置路径解析 |\n| `HOME` | MCP 命令 UI 中的路径缩短 |\n\n---\n\n## 9) 原生加载器/调试标志\n\n| 变量 | 行为 |\n|---|---|\n| `PI_DEV` | 在 `packages/natives` 中启用详细的原生插件加载诊断 |\n\n## 10) TUI 运行时标志（共享包，影响 coding-agent 用户体验）\n\n| 变量 | 行为 |\n|---|---|\n| `PI_NOTIFICATIONS` | `off` / `0` / `false` 抑制桌面通知 |\n| `PI_TUI_WRITE_LOG` | 如果设置，将 TUI 写入操作记录到文件 |\n| `PI_HARDWARE_CURSOR` | 如果为 `1`，启用硬件光标模式 |\n| `PI_CLEAR_ON_SHRINK` | 如果为 `1`，当内容收缩时清除空行 |\n| `PI_DEBUG_REDRAW` | 如果为 `1`，启用重绘调试日志 |\n| `PI_TUI_DEBUG` | 如果为 `1`，启用深层 TUI 调试转储路径 |\n\n---\n\n## 11) 提交生成控制\n\n| 变量 | 行为 |\n|---|---|\n| `PI_COMMIT_TEST_FALLBACK` | 如果为 `true`（不区分大小写），强制使用提交备选生成路径 |\n| `PI_COMMIT_NO_FALLBACK` | 如果为 `true`，当代理未返回提案时禁用备选 |\n| `PI_COMMIT_MAP_REDUCE` | 如果为 `false`，禁用 map-reduce 提交分析路径 |\n| `DEBUG` | 如果设置，打印提交代理错误堆栈跟踪 |\n\n---\n\n## 安全敏感变量\n\n请将以下变量视为机密信息；不要记录或提交它们：\n\n- 提供商/API 密钥和 OAuth/持有者凭据（所有 `*_API_KEY`、`*_TOKEN`、OAuth 访问/刷新令牌）\n- 云凭据（`AWS_*`，`GOOGLE_APPLICATION_CREDENTIALS` 路径可能暴露服务账号材料）\n- 搜索/提供商认证变量（`EXA_API_KEY`、`BRAVE_API_KEY`、`PERPLEXITY_API_KEY`、Anthropic 搜索密钥）\n- Foundry mTLS 材料（`CLAUDE_CODE_CLIENT_CERT`、`CLAUDE_CODE_CLIENT_KEY`、`NODE_EXTRA_CA_CERTS` 当指向私有 CA 包时）\n\nPython 运行时在生成内核子进程之前也会显式剥离许多常见的密钥变量（`packages/coding-agent/src/ipy/runtime.ts`）。\n",
	"zh-cn/configuration/fs-scan-cache-architecture.md": "---\ntitle: 文件系统扫描缓存架构\ndescription: 文件系统扫描缓存契约，用于实现快速文件发现及 stale-while-revalidate 语义。\nsidebar:\n  order: 8\n  label: 文件系统扫描缓存\ni18n:\n  sourceHash: 2a2bde1726ac\n  translator: machine\n---\n\n# 文件系统扫描缓存架构契约\n\n本文档定义了在 Rust 中实现的共享文件系统扫描缓存（`crates/pi-natives/src/fs_cache.rs`）的当前契约，该缓存由暴露给 `packages/coding-agent` 的原生发现/搜索 API 使用。\n\n## 该缓存是什么\n\n缓存存储完整的目录扫描条目列表（`GlobMatch[]`），以扫描范围和遍历策略为键，然后让上层操作（glob 过滤、模糊评分、grep 文件选择）基于这些缓存条目运行。\n\n主要目标：\n\n- 避免重复的发现/搜索调用导致重复的文件系统遍历\n- 当 `glob`、`fuzzyFind` 和 `grep` 共享相同的扫描策略时，保持一致性\n- 允许对空结果进行显式过期恢复，以及在文件变更后进行显式失效处理\n\n## 所有权和公共接口\n\n- 缓存实现和策略：`crates/pi-natives/src/fs_cache.rs`\n- 原生消费者：\n  - `crates/pi-natives/src/glob.rs`\n  - `crates/pi-natives/src/fd.rs`（`fuzzyFind`）\n  - `crates/pi-natives/src/grep.rs`\n- JS 绑定/导出：\n  - `packages/natives/src/glob/index.ts`（`invalidateFsScanCache`）\n  - `packages/natives/src/glob/types.ts`\n  - `packages/natives/src/grep/types.ts`\n- Coding-agent 变更失效辅助函数：\n  - `packages/coding-agent/src/tools/fs-cache-invalidation.ts`\n\n## 缓存键分区（硬性契约）\n\n每个条目以以下内容为键：\n\n- 规范化的 `root` 目录路径\n- `include_hidden` 布尔值\n- `use_gitignore` 布尔值\n\n含义：\n\n- 隐藏文件扫描和非隐藏文件扫描**不**共享条目。\n- 遵循 gitignore 的扫描和禁用 ignore 的扫描**不**共享条目。\n- 消费者必须为隐藏文件/gitignore 行为传递稳定的语义；更改任一标志会创建不同的缓存分区。\n\n`node_modules` 的包含**不**在缓存键中。缓存存储包含 `node_modules` 的条目；每个消费者的过滤在检索后应用。\n\n## 扫描收集行为\n\n缓存填充使用确定性遍历器（`ignore::WalkBuilder`），由 `include_hidden` 和 `use_gitignore` 配置：\n\n- `follow_links(false)`\n- 按文件路径排序\n- `.git` 始终被跳过\n- `node_modules` 在缓存扫描时始终被收集（之后可选择性过滤）\n- 通过 `symlink_metadata` 捕获条目文件类型和 `mtime`\n\n搜索根目录由 `resolve_search_path` 解析：\n\n- 相对路径基于当前 cwd 解析\n- 目标必须是已存在的目录\n- 根目录在可能时进行规范化\n\n## 新鲜度和淘汰策略\n\n全局策略（可通过环境变量覆盖）：\n\n- `FS_SCAN_CACHE_TTL_MS`（默认 `1000`）\n- `FS_SCAN_EMPTY_RECHECK_MS`（默认 `200`）\n- `FS_SCAN_CACHE_MAX_ENTRIES`（默认 `16`）\n\n行为：\n\n- `get_or_scan(...)`\n  - 如果 TTL 为 `0`：完全绕过缓存，始终进行新鲜扫描（`cache_age_ms = 0`）\n  - 在 TTL 内命中缓存时：返回缓存条目及非零的 `cache_age_ms`\n  - 命中已过期条目时：淘汰该键，重新扫描，存储新条目\n- 最大条目数强制执行按 `created_at` 最旧优先淘汰\n\n## 空结果快速重检（独立于正常命中）\n\n正常缓存命中：\n\n- TTL 内的缓存命中返回缓存条目，不做其他操作。\n\n空结果快速重检：\n\n- 这是一个**调用方侧**的策略，使用 `ScanResult.cache_age_ms`\n- 如果过滤/查询结果为空且缓存扫描年龄至少达到 `empty_recheck_ms()`，调用方执行一次 `force_rescan(...)` 并重试\n- 旨在减少文件最近添加但缓存仍在 TTL 内时的过期否定结果\n\n当前消费者：\n\n- `glob`：当过滤匹配结果为空且扫描年龄超过阈值时重新检查\n- `fuzzyFind`（`fd.rs`）：仅当查询非空且评分匹配为空时重新检查\n- `grep`：当选定的候选文件列表为空时重新检查\n\n## 消费者默认值和缓存使用\n\n缓存在所有暴露的 API 上为可选启用（`cache?: boolean`，默认 `false`）。\n\n原生 API 中的当前默认值：\n\n- `glob`：`hidden=false`、`gitignore=true`、`cache=false`\n- `fuzzyFind`：`hidden=false`、`gitignore=true`、`cache=false`\n- `grep`：`hidden=true`、`cache=false`，且缓存扫描始终使用 `use_gitignore=true`\n\n当前的 Coding-agent 调用方：\n\n- 高频提及候选发现启用缓存：\n  - `packages/coding-agent/src/utils/file-mentions.ts`\n  - 配置：`hidden=true`、`gitignore=true`、`includeNodeModules=true`、`cache=true`\n- 工具级 `grep` 集成当前禁用扫描缓存（`cache: false`）：\n  - `packages/coding-agent/src/tools/grep.ts`\n\n## 失效契约\n\n原生失效入口：\n\n- `invalidateFsScanCache(path?: string)`\n  - 带 `path`：移除根目录为目标路径前缀的缓存条目\n  - 不带 path：清除所有扫描缓存条目\n\n路径处理细节：\n\n- 相对失效路径基于 cwd 解析\n- 失效时尝试规范化\n- 如果目标不存在（例如删除操作），回退为规范化父目录并在可能时重新附加文件名\n- 这保留了创建/删除/重命名操作中一侧可能不存在时的失效行为\n\n## Coding-agent 变更流程职责\n\nCoding-agent 代码必须在成功的文件系统变更后进行失效处理。\n\n核心辅助函数：\n\n- `invalidateFsScanAfterWrite(path)`\n- `invalidateFsScanAfterDelete(path)`\n- `invalidateFsScanAfterRename(oldPath, newPath)`（当路径不同时使两侧失效）\n\n当前变更工具调用位置：\n\n- `packages/coding-agent/src/tools/write.ts`\n- `packages/coding-agent/src/patch/index.ts`（hashline/patch/replace 流程）\n\n规则：如果某个流程变更了文件系统内容或位置但绕过了这些辅助函数，则预期会出现缓存过期 bug。\n\n## 安全地添加新的缓存消费者\n\n在新的扫描器/搜索路径中引入缓存使用时：\n\n1. **使用稳定的扫描策略输入**\n   - 首先确定隐藏文件/gitignore 语义\n   - 一致地传递给 `get_or_scan`/`force_rescan`，使缓存分区是有意为之的\n\n2. **将缓存数据视为仅按遍历策略预过滤的**\n   - 在检索后应用工具特定的过滤（glob 模式、类型过滤器、node_modules 规则）\n   - 永远不要假设缓存条目已经反映了你的上层过滤器\n\n3. **仅在存在过期否定风险时实现空结果快速重检**\n   - 使用 `scan.cache_age_ms >= empty_recheck_ms()`\n   - 通过 `force_rescan(..., store=true, ...)` 重试一次\n   - 将此路径与正常缓存命中逻辑分开\n\n4. **显式遵循无缓存模式**\n   - 当调用方禁用缓存时，调用 `force_rescan(..., store=false, ...)`\n   - 不要在无缓存的请求路径中填充共享缓存\n\n5. **为任何新的写入路径接入变更失效**\n   - 在成功的写入/编辑/删除/重命名后，调用 coding-agent 失效辅助函数\n   - 对于重命名/移动，使旧路径和新路径都失效\n\n6. **不要添加每次调用的 TTL 调节参数**\n   - 当前契约仅支持全局策略（通过环境变量配置），不支持每请求的 TTL 覆盖\n\n## 已知边界\n\n- 缓存范围是进程本地的内存存储（`DashMap`），不会在进程重启间持久化。\n- 缓存存储扫描条目，而非最终的工具结果。\n- `glob`/`fuzzyFind`/`grep` 仅在键维度（`root`、`hidden`、`gitignore`）匹配时共享扫描条目。\n- `.git` 在扫描收集时始终被排除，无论调用方选项如何。\n",
	"zh-cn/configuration/hooks.md": "---\ntitle: Hooks\ndescription: 编码代理生命周期中用于事件前/后自动化的 Hook 系统。\nsidebar:\n  order: 4\n  label: Hooks\ni18n:\n  sourceHash: cdbec10bc405\n  translator: machine\n---\n\n# Hooks\n\n本文档描述 `src/extensibility/hooks/*` 中**当前 Hook 子系统的代码**。\n\n## 运行时的当前状态\n\nHook 包（`src/extensibility/hooks/`）仍作为 API 接口导出并可正常使用，但默认 CLI 运行时现在初始化的是**扩展运行器**路径。在当前启动流程中：\n\n- `--hook` 被视为 `--extension` 的别名（CLI 路径合并至 `additionalExtensionPaths`）\n- 工具由 `ExtensionToolWrapper` 而非 `HookToolWrapper` 进行包装\n- 上下文转换和生命周期事件通过 `ExtensionRunner` 发送\n\n因此，本文档描述的是 Hook 子系统的实现本身（类型/加载器/运行器/包装器），包括遗留行为与约束。\n\n## 关键文件\n\n- `src/extensibility/hooks/types.ts` — Hook 上下文、事件类型和结果契约\n- `src/extensibility/hooks/loader.ts` — 模块加载与 Hook 发现桥接\n- `src/extensibility/hooks/runner.ts` — 事件分发、命令查找、错误信号\n- `src/extensibility/hooks/tool-wrapper.ts` — 工具执行前/后拦截包装器\n- `src/extensibility/hooks/index.ts` — 导出/重新导出\n\n## Hook 模块是什么\n\n一个 Hook 模块必须默认导出一个工厂函数：\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function hook(pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName === \"bash\" && String(event.input.command ?? \"\").includes(\"rm -rf\")) {\n   return { block: true, reason: \"blocked by policy\" };\n  }\n });\n}\n```\n\n该工厂函数可以：\n\n- 通过 `pi.on(...)` 注册事件处理程序\n- 通过 `pi.sendMessage(...)` 发送持久化自定义消息\n- 通过 `pi.appendEntry(...)` 持久化非 LLM 状态\n- 通过 `pi.registerCommand(...)` 注册斜杠命令\n- 通过 `pi.registerMessageRenderer(...)` 注册自定义消息渲染器\n- 通过 `pi.exec(...)` 运行 Shell 命令\n\n## 发现与加载\n\n`discoverAndLoadHooks(configuredPaths, cwd)` 的执行步骤：\n\n1. 从能力注册表加载已发现的 Hook（`loadCapability(\"hooks\")`）\n2. 追加显式配置的路径（按绝对路径去重）\n3. 调用 `loadHooks(allPaths, cwd)`\n\n`loadHooks` 随后导入每个路径，并期望其具有 `default` 函数。\n\n### 路径解析\n\n`loader.ts` 对 Hook 路径的解析规则如下：\n\n- 绝对路径：直接使用\n- `~` 路径：展开处理\n- 相对路径：相对于 `cwd` 进行解析\n\n### 重要的遗留不匹配问题\n\n`hookCapability` 的发现提供者仍以 Shell 风格的前/后 Hook 文件为模型（例如 `.claude/hooks/pre/*`、`.xcsh/.../hooks/pre/*`）。\n\n此处的 Hook 加载器使用动态模块导入，并要求具有默认 JS/TS Hook 工厂函数。若某个已发现的 Hook 路径无法作为模块导入，则加载失败，并记录在 `LoadHooksResult.errors` 中。\n\n## 事件接口\n\nHook 事件在 `types.ts` 中具有强类型定义。\n\n### 会话事件\n\n- `session_start`\n- `session_before_switch` → 可返回 `{ cancel?: boolean }`\n- `session_switch`\n- `session_before_branch` → 可返回 `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_branch`\n- `session_before_compact` → 可返回 `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session.compacting` → 可返回 `{ context?: string[]; prompt?: string; preserveData?: Record<string, unknown> }`\n- `session_compact`\n- `session_before_tree` → 可返回 `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n- `session_tree`\n- `session_shutdown`\n\n### 代理/上下文事件\n\n- `context` → 可返回 `{ messages?: Message[] }`\n- `before_agent_start` → 可返回 `{ message?: { customType; content; display; details } }`\n- `agent_start`\n- `agent_end`\n- `turn_start`\n- `turn_end`\n- `auto_compaction_start`\n- `auto_compaction_end`\n- `auto_retry_start`\n- `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### 工具事件（前/后模型）\n\n- `tool_call`（执行前）→ 可返回 `{ block?: boolean; reason?: string }`\n- `tool_result`（执行后）→ 可返回 `{ content?; details?; isError? }`\n\n这是 Hook 子系统的核心前/后拦截模型。\n\n```text\nHook 工具拦截流程\n\ntool_call 处理程序\n   │\n   ├─ 任意 { block: true }？── 是 ──> 抛出异常（工具被阻止）\n   │\n   └─ 否\n      │\n      ▼\n   执行底层工具\n      │\n      ├─ 成功 ──> tool_result 处理程序可覆盖 { content, details }\n      │\n      └─ 错误 ──> 发送 tool_result(isError=true) 后重新抛出原始错误\n```\n\n## 执行模型与变更语义\n\n### 1) 执行前：`tool_call`\n\n`HookToolWrapper.execute()` 在工具执行前发送 `tool_call`。\n\n- 若任意处理程序返回 `{ block: true }`，则停止执行\n- 若处理程序抛出异常，包装器以安全失败方式阻止执行\n- 返回的 `reason` 将作为抛出的错误文本\n\n### 2) 工具执行\n\n若未被阻止，底层工具正常执行。\n\n### 3) 执行后：`tool_result`\n\n成功后，包装器发送包含以下内容的 `tool_result`：\n\n- `toolName`、`toolCallId`、`input`\n- `content`\n- `details`\n- `isError: false`\n\n若处理程序返回覆盖值：\n\n- `content` 可替换结果内容\n- `details` 可替换结果详情\n\n工具失败时，包装器发送带有 `isError: true` 和错误文本内容的 `tool_result`，然后重新抛出原始错误。\n\n### Hook 可以变更的内容\n\n- 通过 `context` 变更单次调用的 LLM 上下文（`messages` 替换链）\n- 通过 `tool_result` 路径变更成功工具调用的输出内容/详情\n- 通过 `before_agent_start` 变更代理启动前注入的消息\n- 通过 `session_before_*` 和 `session.compacting` 变更取消/自定义压缩/树形行为\n\n### Hook 在此实现中无法变更的内容\n\n- 原始工具输入参数（`tool_call` 只支持阻止/允许）\n- 工具错误抛出后的执行续行（错误路径会重新抛出）\n- 包装器行为中的最终成功/错误状态（返回的 `isError` 已有类型定义，但 `HookToolWrapper` 不会应用它）\n\n## 顺序与冲突行为\n\n### 发现层面的顺序\n\n能力提供者按优先级排序（高优先级在前）。去重依据是能力键，先到先得。\n\n对于 `hooks`，能力键为 `${type}:${tool}:${name}`。来自较低优先级提供者的重复项将被标记并从有效发现列表中排除。\n\n### 加载顺序\n\n`discoverAndLoadHooks` 构建一个按解析后绝对路径去重的扁平 `allPaths` 列表，然后 `loadHooks` 按该顺序迭代。每个已发现目录中文件的顺序取决于 `readdir` 的输出；Hook 加载器不执行额外排序。\n\n### 运行时处理程序顺序\n\n在 `HookRunner` 内部，顺序由注册序列确定：\n\n1. Hooks 数组顺序\n2. 每个 Hook/事件的处理程序注册顺序\n\n按事件类型的冲突行为：\n\n- `tool_call`：最后返回的结果获胜，除非某处理程序阻止；首个阻止操作会立即短路\n- `tool_result`：最后返回的覆盖值获胜（无短路）\n- `context`：链式处理；每个处理程序接收前一处理程序的消息输出\n- `before_agent_start`：第一个返回的消息被保留；后续消息被忽略\n- `session_before_*`：跟踪最新返回的结果；`cancel: true` 立即短路\n- `session.compacting`：最新返回的结果获胜\n\n命令/渲染器冲突：\n\n- `getCommand(name)` 返回跨 Hook 的第一个匹配项（最先加载的获胜）\n- `getMessageRenderer(customType)` 返回第一个匹配项\n- `getRegisteredCommands()` 返回所有命令（不去重）\n\n## UI 交互（`HookContext.ui`）\n\n`HookUIContext` 包含：\n\n- `select`、`confirm`、`input`、`editor`\n- `notify`\n- `setStatus`\n- `custom`\n- `setEditorText`、`getEditorText`\n- `theme` getter\n\n`ctx.hasUI` 表示是否可使用交互式 UI。\n\n在无 UI 的情况下运行时，默认无操作上下文行为如下：\n\n- `select/input/editor` 返回 `undefined`\n- `confirm` 返回 `false`\n- `notify`、`setStatus`、`setEditorText` 为空操作\n- `getEditorText` 返回 `\"\"`\n\n### 状态栏行为\n\n通过 `ctx.ui.setStatus(key, text)` 设置的 Hook 状态文本：\n\n- 按键存储\n- 按键名称排序\n- 经过清理（`\\r`、`\\n`、`\\t` → 空格；连续空格合并）\n- 合并后按宽度截断以供显示\n\n## 错误传播与回退\n\n### 加载时\n\n- 无效模块或缺少默认导出 → 记录在 `LoadHooksResult.errors` 中\n- 继续加载其他 Hook\n\n### 事件时\n\n`HookRunner.emit(...)` 对大多数事件捕获处理程序错误，并向监听器发送 `HookError`（包含 `hookPath`、`event`、`error`），然后继续执行。\n\n`emitToolCall(...)` 更为严格：处理程序错误不会被吞掉，而是传播给调用方。在 `HookToolWrapper` 中，这将阻止工具调用（安全失败）。\n\n## 实际 API 示例\n\n### 阻止不安全的 Bash 命令\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName !== \"bash\") return;\n  const cmd = String(event.input.command ?? \"\");\n  if (!cmd.includes(\"rm -rf\")) return;\n\n  if (!ctx.hasUI) return { block: true, reason: \"rm -rf blocked (no UI)\" };\n  const ok = await ctx.ui.confirm(\"Dangerous command\", `Allow: ${cmd}`);\n  if (!ok) return { block: true, reason: \"user denied command\" };\n });\n}\n```\n\n### 在执行后对工具输出进行脱敏处理\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_result\", async event => {\n  if (event.toolName !== \"read\" || event.isError) return;\n\n  const redacted = event.content.map(chunk => {\n   if (chunk.type !== \"text\") return chunk;\n   return { ...chunk, text: chunk.text.replaceAll(/API_KEY=\\S+/g, \"API_KEY=[REDACTED]\") };\n  });\n\n  return { content: redacted };\n });\n}\n```\n\n### 在每次 LLM 调用时修改模型上下文\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"context\", async event => {\n  const filtered = event.messages.filter(msg => !(msg.role === \"custom\" && msg.customType === \"debug-only\"));\n  return { messages: filtered };\n });\n}\n```\n\n### 注册带有命令安全上下文方法的斜杠命令\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.registerCommand(\"handoff\", {\n  description: \"Create a new session with setup message\",\n  handler: async (_args, ctx) => {\n   await ctx.waitForIdle();\n   await ctx.newSession({\n    parentSession: ctx.sessionManager.getSessionFile(),\n    setup: async sm => {\n     sm.appendMessage({\n      role: \"user\",\n      content: [{ type: \"text\", text: \"Continue from prior session summary.\" }],\n      timestamp: Date.now(),\n     });\n    },\n   });\n  },\n });\n}\n```\n\n## 导出接口\n\n`src/extensibility/hooks/index.ts` 导出：\n\n- 加载 API（`discoverAndLoadHooks`、`loadHooks`）\n- 运行器和包装器（`HookRunner`、`HookToolWrapper`）\n- 所有 Hook 类型\n- `execCommand` 重新导出\n\n包根文件（`src/index.ts`）将 Hook **类型**作为遗留兼容性接口重新导出。\n",
	"zh-cn/configuration/porting-from-pi-mono.md": "---\ntitle: 从 pi-mono 移植：实用合并指南\ndescription: 将代码从 pi-mono monorepo 迁移到 xcsh 代码库的实用指南。\nsidebar:\n  order: 9\n  label: 从 pi-mono 移植\ni18n:\n  sourceHash: fd4e8c09303d\n  translator: machine\n---\n\n# 从 pi-mono 移植：实用合并指南\n\n本指南是一个可重复使用的检查清单，用于将 pi-mono 中的变更移植到本仓库中。\n适用于任何合并操作：单个文件、功能分支或完整的发布同步。\n\n## 上次同步点\n\n**提交：** `b21b42d032919de2f2e6920a76fa9a37c3920c0a`\n**日期：** 2026-03-22\n\n每次同步后更新此部分；不要重复使用之前的范围。\n\n开始新的同步时，从此提交开始生成补丁：\n\n```bash\ngit format-patch b21b42d032919de2f2e6920a76fa9a37c3920c0a..HEAD --stdout > changes.patch\n```\n\n## 0) 定义范围\n\n- 确定上游参考（提交、标签或 PR）。\n- 列出您计划涉及的包或文件夹。\n- 决定哪些功能在范围内，哪些是有意跳过的。\n\n## 1) 安全地引入代码\n\n- 优先使用干净、聚焦的 diff，而非批量复制。\n- 避免复制构建产物或生成的文件。\n- 如果上游新增了文件，请明确添加并审查其内容。\n\n## 2) 匹配导入扩展名约定\n\n大多数运行时 TypeScript 源码在内部导入中省略 `.js`，但某些 test/bench 入口文件为了 ESM\n运行时兼容性保留了 `.js`。遵循本地包的现有风格；不要一刀切地去除扩展名。\n\n- 在 `packages/coding-agent` 运行时源码中，除非导入非 TS 资源，否则保持内部导入无扩展名。\n- 在 `packages/tui/test` 和 `packages/natives/bench` 中，当周围文件已使用 `.js` 时保留它。\n- 当工具链要求时保留真实的文件扩展名（例如 `.json`、`.css`、`.md` 文本嵌入）。\n- 示例：`import { x } from \"./foo.js\";` → `import { x } from \"./foo\";`（仅当包约定为无扩展名时）。\n\n## 3) 替换导入作用域\n\n上游使用不同的包作用域。请一致地替换它们。\n\n- 将旧作用域替换为此处使用的本地作用域。\n- 示例（根据您实际移植的包进行调整）：\n  - `@mariozechner/pi-coding-agent` → `@f5-sales-demo/xcsh`\n  - `@mariozechner/pi-agent-core` → `@f5-sales-demo/pi-agent-core`\n  - `@mariozechner/pi-tui` → `@f5-sales-demo/pi-tui`\n  - `@mariozechner/pi-ai` → `@f5-sales-demo/pi-ai`\n\n## 4) 在 Bun API 优于 Node 时使用 Bun\n\n我们运行在 Bun 上。仅当 Bun 提供更好的替代方案时才替换 Node API。\n\n**应当替换：**\n\n- 进程派生：`child_process.spawn` → Bun Shell `$` 用于简单命令，`Bun.spawn`/`Bun.spawnSync` 用于流式或长时间运行的工作\n- 文件 I/O：`fs.readFileSync` → `Bun.file().text()` / `Bun.write()`\n- HTTP 客户端：`node-fetch`、`axios` → 原生 `fetch`\n- 加密哈希：`node:crypto` → Web Crypto 或 `Bun.hash`\n- SQLite：`better-sqlite3` → `bun:sqlite`\n- 环境变量加载：`dotenv` → Bun 自动加载 `.env`\n\n**不应替换（这些在 Bun 中工作正常）：**\n\n- `os.homedir()` — 不要替换为 `Bun.env.HOME`、`Bun.env.HOME` 或字面量 `\"~\"`\n- `os.tmpdir()` — 不要替换为 `Bun.env.TMPDIR || \"/tmp\"` 或硬编码路径\n- `fs.mkdtempSync()` — 不要替换为手动路径构造\n- `path.join()`、`path.resolve()` 等 — 这些没问题\n\n**导入风格：** 使用 `node:` 前缀并配合命名空间导入（不要从 `node:fs` 或 `node:path` 进行具名导入）。\n\n**额外的 Bun 约定：**\n\n- 对于短小的、非流式命令优先使用 Bun Shell `$`；仅在需要流式 I/O 或进程控制时使用 `Bun.spawn`。\n- 文件操作使用 `Bun.file()`/`Bun.write()`，目录操作使用 `node:fs/promises`。\n- 避免 `Bun.file().exists()` 检查；在 try/catch 中使用 `isEnoent` 处理。\n- 优先使用 `Bun.sleep(ms)` 而非 `setTimeout` 包装器。\n\n**错误示例：**\n\n```typescript\n// BROKEN: env vars may be undefined, \"~\" is not expanded\nconst home = Bun.env.HOME || \"~\";\nconst tmp = Bun.env.TMPDIR || \"/tmp\";\n```\n\n**正确示例：**\n\n```typescript\nimport * as os from \"node:os\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst configDir = path.join(os.homedir(), \".config\", \"myapp\");\nconst tempDir = fs.mkdtempSync(path.join(os.tmpdir(), \"myapp-\"));\n```\n\n## 5) 优先使用 Bun 嵌入（不要复制）\n\n不要在构建时复制运行时资源或第三方文件。\n\n- 如果上游将资源复制到 dist 文件夹，请替换为 Bun 友好的嵌入方式。\n- 提示词是静态 `.md` 文件；使用 Bun 文本导入（`with { type: \"text\" }`）和 Handlebars，而非内联提示词字符串。\n- 使用 `import.meta.dir` + `Bun.file` 加载相邻的非文本资源。\n- 将资源保留在仓库中，让打包器将其包含进来。\n- 除非用户明确要求，否则移除复制脚本。\n- 如果上游在运行时读取打包的回退文件，请用 Bun 文本嵌入导入替换文件系统读取。\n  - 示例（Codex 指令回退）：\n    - `const FALLBACK_PROMPT_PATH = join(import.meta.dir, \"codex-instructions.md\");` -> 移除\n    - `import FALLBACK_INSTRUCTIONS from \"./codex-instructions.md\" with { type: \"text\" };`\n    - 使用 `return FALLBACK_INSTRUCTIONS;` 代替 `readFileSync(FALLBACK_PROMPT_PATH, \"utf8\")`\n\n## 6) 谨慎移植 `package.json`\n\n将 `package.json` 视为契约。有意识地进行合并。\n\n- 保持现有的 `name`、`version`、`type`、`exports` 和 `bin`，除非移植需要更改。\n- 将 npm/node 脚本替换为 Bun 等效项（例如 `bun check`、`bun test`）。\n- 确保依赖使用正确的作用域。\n- 不要降级依赖以修复类型错误；应当升级。\n- 验证工作空间包链接和 `peerDependencies`。\n\n## 7) 对齐代码风格和工具链\n\n- 保持现有的格式化约定。\n- 除非必要，不要引入 `any`。\n- 避免动态导入和内联类型导入；仅使用顶层导入。\n- 永远不要在代码中构建提示词；提示词是使用 Handlebars 渲染的静态 `.md` 文件。\n- 在 coding-agent 中，永远不要使用 `console.log`/`console.warn`/`console.error`；使用 `@f5-sales-demo/pi-utils` 中的 `logger`。\n- 使用 `Promise.withResolvers()` 代替 `new Promise((resolve, reject) => ...)`。\n- **类字段或方法上不使用 `private`/`protected`/`public` 关键字。** 使用 ES `#` 私有字段进行封装；可访问的成员保持裸露（无关键字）。唯一的例外是构造函数参数属性（`constructor(private readonly x: T)`），这是 TypeScript 要求的。当移植使用 `private foo` 或 `protected bar` 的上游代码时，转换为 `#foo`（私有）或裸露的 `bar`（可访问）。\n- 优先使用现有的辅助函数和工具，而非新的临时代码。\n- 保留本仓库中已有的 Bun 优先基础设施变更：\n  - 运行时是 Bun（无 Node 入口点）。\n  - 包管理器是 Bun（无 npm 锁文件）。\n  - 重量级 Node API（`child_process`、`readline`）已替换为 Bun 等效项。\n  - 轻量级 Node API（`os.homedir`、`os.tmpdir`、`fs.mkdtempSync`、`path.*`）保留使用。\n  - CLI shebang 使用 `bun`（不是 `node`，不是 `tsx`）。\n  - 包直接使用源文件（无 TypeScript 构建步骤）。\n  - CI 工作流使用 Bun 进行安装/检查/测试。\n\n## 8) 移除旧的兼容层\n\n除非有明确要求，否则移除上游的兼容性垫片。\n\n- 删除已被替换的旧 API。\n- 直接将所有调用点更新到新 API。\n- 不要保留 `*_v2` 或并行版本。\n\n## 9) 更新文档和引用\n\n- 适当替换 pi-mono 仓库链接。\n- 更新示例以使用 Bun 和正确的包作用域。\n- 确保 README 说明仍与当前仓库行为一致。\n\n## 10) 验证移植\n\n变更后运行标准检查：\n\n- `bun check`\n\n如果仓库已经存在与您的变更无关的失败检查，请指出。\n测试使用 Bun 的运行器（不是 Vitest），但仅在明确要求时才运行 `bun test`。\n\n## 11) 保护已改进的功能（回归陷阱清单）\n\n如果您已在本地改进了行为，请将其视为**不可妥协的**。在移植之前，记录下\n改进内容并添加明确的检查，以免在合并中丢失。\n\n- **冻结预期行为**：为每项改进添加简短的\"前/后\"说明（输入、输出、\n  默认值、边界情况）。这可以防止静默回退。\n- **映射旧 → 新 API**：如果上游重命名了概念（hooks → extensions、custom tools → tools 等），\n  确保每个旧入口点仍然能正确连通。遗漏一个标志或导出就等于丢失功能。\n- **验证导出**：检查 `package.json` 的 `exports`、公共类型和桶文件。上游移植经常\n  忘记重新导出本地新增内容。\n- **覆盖非正常路径**：如果您修复了错误处理、超时或回退逻辑，请添加测试或\n  至少一个手动检查清单来验证这些路径。\n- **检查默认值和配置合并顺序**：改进通常体现在默认值中。确认新的默认值\n  没有被还原（例如新的配置优先级、禁用的功能、工具列表）。\n- **审计环境/shell 行为**：如果您修复了执行或沙箱化，验证新路径仍然使用您\n  净化后的环境，并且没有重新引入别名/函数覆盖。\n- **重新运行针对性示例**：保留一组最小的\"已知正常\"示例，并在移植后运行它们\n  （CLI 标志、扩展注册、工具执行）。\n\n## 12) 检测和处理重构的代码\n\n在移植文件之前，检查上游是否对其进行了重大重构：\n\n```bash\n# Compare the file you're about to port against what you have locally\ngit diff HEAD upstream/main -- path/to/file.ts\n```\n\n如果 diff 显示文件已被**重构**（不仅仅是修补）：\n\n- 新的抽象、重命名的概念、合并的模块、改变的数据流\n\n那么您必须在移植之前**彻底阅读新实现**。盲目合并重构的代码会丢失功能，因为：\n\n注意：交互模式最近被拆分为 controllers/utils/types。当回移相关变更时，请将更新移植到我们创建的各个文件中，并确保 `interactive-mode.ts` 的连线保持同步。\n\n1. **默认值静默改变** - 新变量 `defaultFoo = [a, b]` 可能替换了返回 `[a, b, c, d, e]` 的旧 `getAllFoo()`。\n\n2. **API 选项被丢弃** - 当系统合并时（例如 `hooks` + `customTools` → `extensions`），旧选项可能没有连通到新实现。\n\n3. **代码路径变得陈旧** - 重命名的概念（例如 `hookMessage` → `custom`）需要在每个 switch 语句、类型守卫和处理器中更新——不仅仅是定义处。\n\n4. **上下文/能力缩减** - 旧 API 可能暴露了 `{ logger, typebox, pi }`，而新 API 忘记包含它们。\n\n### 语义化移植流程\n\n当上游重构了一个模块时：\n\n1. **阅读旧实现** - 了解它做了什么、接受什么选项、暴露了什么。\n\n2. **阅读新实现** - 了解新的抽象以及它们如何映射到旧行为。\n\n3. **验证功能对等** - 对于旧代码中的每项功能，确认新代码保留了它或明确移除了它。\n\n4. **搜索遗漏** - 搜索可能在 switch 语句、处理器、UI 组件中遗漏的旧名称/概念。\n\n5. **测试边界** - CLI 标志、SDK 选项、事件处理器、默认值——这些是回归隐藏的地方。\n\n### 快速检查\n\n```bash\n# Find all uses of an old concept that may need updating\nrg \"oldConceptName\" --type ts\n\n# Compare default values between versions\ngit show upstream/main:path/to/file.ts | rg \"default|DEFAULT\"\n\n# Check if all enum/union values have handlers\nrg \"case \\\"\" path/to/file.ts\n```\n\n## 13) 快速审计检查清单\n\n在完成之前将其作为最终检查：\n\n- [ ] 导入扩展名遵循本地包约定（不要一刀切去除 `.js`）\n- [ ] 新增/移植的代码中没有 Node 专有 API\n- [ ] 所有包作用域已更新\n- [ ] `package.json` 脚本使用 Bun\n- [ ] 提示词是 `.md` 文本导入（无内联提示词字符串）\n- [ ] coding-agent 中没有 `console.*`（使用 `logger`）\n- [ ] 资源通过 Bun 嵌入模式加载（无复制脚本）\n- [ ] 测试或检查已运行（或明确说明被阻塞）\n- [ ] 无功能回归（见第 11-12 节）\n\n## 14) 提交信息格式\n\n提交回移时，遵循仓库格式 `<type>(scope): <past-tense description>` 并在标题中保留提交范围。\n\n```\nfix(coding-agent): backported pi-mono changes (<from>..<to>)\n\npackages/<package>:\n- <type>: <description>\n- <type>: <description> (#<issue> by @<contributor>)\n\npackages/<other-package>:\n- <type>: <description>\n```\n\n**示例：**\n\n```\nfix(coding-agent): backported pi-mono changes (9f3eef65f..52532c7c0)\n\npackages/ai:\n- fix: handle \"sensitive\" stop reason from Anthropic API\n- fix: normalize tool call IDs with special characters for Responses API\n- fix: add overflow detection for Bedrock, MiniMax, Kimi providers\n- fix: 429 status is rate limiting, not context overflow\n\npackages/tui:\n- fix: refactored autocomplete state tracking\n- fix: file autocomplete should not trigger on empty text\n- fix: configurable autocomplete max visible items\n- fix: improved table column width calculation with word-aware wrapping\n\npackages/coding-agent:\n- fix: preserve external config.yml edits on save (#1046 by @nicobailonMD)\n- fix: resolve macOS NFD and curly quote variants in file paths\n```\n\n**规则：**\n\n- 按包分组变更\n- 使用约定式提交类型（`fix`、`feat`、`refactor`、`perf`、`docs`）\n- 包含上游 issue/PR 编号和外部贡献者的署名\n- 标题中的提交范围有助于跟踪同步点\n\n## 15) 有意的差异\n\n我们的 fork 有与上游不同的架构决策。**不要移植这些上游模式：**\n\n### UI 架构\n\n| 上游                                        | 我们的 Fork                                               | 原因                                                                  |\n| ------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------- |\n| `FooterDataProvider` 类                     | `StatusLineComponent`                                     | 更简单的集成状态行                                                    |\n| `ctx.ui.setHeader()` / `ctx.ui.setFooter()` | 在非 TUI 模式下为桩函数                                  | 在 TUI 中实现，其他模式为空操作                                       |\n| `ctx.ui.setEditorComponent()`               | 在非 TUI 模式下为桩函数                                  | 在 TUI 中实现，其他模式为空操作                                       |\n| `InteractiveModeOptions` 选项对象           | 位置参数构造函数（选项类型仍然导出）                      | 保持构造函数签名；当上游添加字段时更新类型                             |\n\n### 组件命名\n\n| 上游                         | 我们的 Fork             |\n| ---------------------------- | ----------------------- |\n| `extension-input.ts`         | `hook-input.ts`         |\n| `extension-selector.ts`      | `hook-selector.ts`      |\n| `ExtensionInputComponent`    | `HookInputComponent`    |\n| `ExtensionSelectorComponent` | `HookSelectorComponent` |\n\n### API 命名\n\n| 上游                                     | 我们的 Fork                              | 备注                                      |\n| ---------------------------------------- | ---------------------------------------- | ----------------------------------------- |\n| `sessionManager.appendSessionInfo(name)` | `sessionManager.setSessionName(name)`    | 我们全程使用 `sessionName`                |\n| `sessionManager.getSessionName()`        | `sessionManager.getSessionName()`        | 相同（我们统一以匹配上游的 RPC）          |\n| `agent.sessionName` / `setSessionName()` | `agent.sessionName` / `setSessionName()` | 相同                                      |\n\n### 文件整合\n\n| 上游                                               | 我们的 Fork                             | 原因                                    |\n| -------------------------------------------------- | --------------------------------------- | --------------------------------------- |\n| `clipboard.ts` + `clipboard-image.ts`（工具文件）   | `@f5-sales-demo/pi-natives` 剪贴板模块 | 合并到 N-API 原生实现中                |\n\n### 测试框架\n\n| 上游                      | 我们的 Fork                   |\n| ------------------------- | ----------------------------- |\n| `vitest` 配合 `vi.mock()` | `bun:test` 配合 bun 的 `vi`  |\n| `node:test` 断言          | `expect()` 匹配器            |\n\n### 工具架构\n\n| 上游                                | 我们的 Fork                                                       | 备注                                                      |\n| ----------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------- |\n| `createTool(cwd: string, options?)` | `createTools(session: ToolSession)` 通过 `BUILTIN_TOOLS` 注册表   | 工具工厂接受 `ToolSession` 且可以返回 `null`              |\n| 每个工具的 `*Operations` 接口       | 保留每个工具的接口（`FindOperations`、`GrepOperations`）           | 用于 SSH/远程覆盖                                         |\n| 到处使用 Node.js `fs/promises`      | 文件操作用 `Bun.file()`/`Bun.write()`；目录操作用 `node:fs/promises` | 在 Bun API 能简化时优先使用                               |\n\n### 认证存储\n\n| 上游                            | 我们的 Fork                                 | 备注                                         |\n| ------------------------------- | ------------------------------------------- | -------------------------------------------- |\n| `proper-lockfile` + `auth.json` | `agent.db` (bun:sqlite)                     | 凭证exclusively存储在 `agent.db` 中          |\n| 每个提供商单一凭证              | 多凭证轮询选择                              | 保留会话亲和性和退避逻辑                      |\n\n### 扩展\n\n| 上游                          | 我们的 Fork                                |\n| ----------------------------- | ------------------------------------------ |\n| `jiti` 用于 TypeScript 加载   | 原生 Bun `import()`                        |\n| `pkg.pi` 清单字段             | `pkg.xcsh ?? pkg.pi`（优先使用我们的命名空间）|\n\n### 跳过这些上游功能\n\n移植时，**完全跳过**这些文件/功能：\n\n- `footer-data-provider.ts` — 我们使用 StatusLineComponent\n- `clipboard-image.ts` — 剪贴板在 `@f5-sales-demo/pi-natives` N-API 模块中\n- GitHub 工作流文件 — 我们有自己的 CI\n- `models.generated.ts` — 自动生成，在本地重新生成（使用 models.json 代替）\n\n### 我们新增的功能（保留这些）\n\n这些存在于我们的 fork 中但不在上游。**永远不要覆盖：**\n\n- 交互模式中的 `StatusLineComponent`\n- 带会话亲和性的多凭证认证\n- 基于能力的发现系统（`defineCapability`、`registerProvider`、`loadCapability`、`skillCapability` 等）\n- MCP/Exa/SSH 集成\n- LSP 写透用于保存时格式化\n- Bash 拦截（`checkBashInterception`）\n- read 工具中的模糊路径建议\n",
	"zh-cn/configuration/rpc.md": "---\ntitle: RPC 协议参考\ndescription: 用于 xcsh 组件间进程通信的 JSON-RPC 协议参考。\nsidebar:\n  order: 5\n  label: RPC 协议\ni18n:\n  sourceHash: b4a3ddaf08ab\n  translator: machine\n---\n\n# RPC 协议参考\n\nRPC 模式将编程代理作为基于 stdio 的换行符分隔 JSON 协议运行。\n\n- **stdin**：命令（`RpcCommand`）和扩展 UI 响应\n- **stdout**：命令响应（`RpcResponse`）、会话/代理事件、扩展 UI 请求\n\n主要实现：\n\n- `src/modes/rpc/rpc-mode.ts`\n- `src/modes/rpc/rpc-types.ts`\n- `src/session/agent-session.ts`\n- `packages/agent/src/agent.ts`\n- `packages/agent/src/agent-loop.ts`\n\n## 启动\n\n```bash\nxcsh --mode rpc [regular CLI options]\n```\n\n行为说明：\n\n- `@file` CLI 参数在 RPC 模式下会被拒绝。\n- RPC 模式默认禁用自动会话标题生成，以避免额外的模型调用。\n- RPC 模式会将影响工作流的 `todo.*`、`task.*` 和 `async.*` 设置重置为内置默认值，而不是继承用户的覆盖配置。\n- 进程以 JSONL 格式读取 stdin（`readJsonl(Bun.stdin.stream())`）。\n- 当 stdin 关闭时，进程以退出码 `0` 退出。\n- 响应/事件以每行一个 JSON 对象的形式写入。\n\n## 传输与帧格式\n\n每一帧是一个 JSON 对象后跟 `\\n`。\n\n除对象本身的结构外，没有额外的封装。\n\n### 出站帧类别（stdout）\n\n1. `RpcResponse`（`{ type: \"response\", ... }`）\n2. `AgentSessionEvent` 对象（`agent_start`、`message_update` 等）\n3. `RpcExtensionUIRequest`（`{ type: \"extension_ui_request\", ... }`）\n4. 扩展错误（`{ type: \"extension_error\", extensionPath, event, error }`）\n\n### 入站帧类别（stdin）\n\n1. `RpcCommand`\n2. `RpcExtensionUIResponse`（`{ type: \"extension_ui_response\", ... }`）\n\n## 请求/响应关联\n\n所有命令接受可选的 `id?: string`。\n\n- 如果提供了 id，正常的命令响应会回显相同的 `id`。\n- `RpcClient` 依赖此机制进行待处理请求的解析。\n\n运行时的重要边界行为：\n\n- 未知命令的响应会以 `id: undefined` 发出（即使请求中包含了 `id`）。\n- 输入循环中的解析/处理异常会发出 `command: \"parse\"`，`id: undefined`。\n- `prompt` 和 `abort_and_prompt` 会返回即时成功响应，如果异步提示调度失败，之后可能会发出带有**相同** id 的错误响应。\n\n## 命令模式（规范定义）\n\n`RpcCommand` 在 `src/modes/rpc/rpc-types.ts` 中定义：\n\n### 提示\n\n- `{ id?, type: \"prompt\", message: string, images?: ImageContent[], streamingBehavior?: \"steer\" | \"followUp\" }`\n- `{ id?, type: \"steer\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"follow_up\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"abort\" }`\n- `{ id?, type: \"abort_and_prompt\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"new_session\", parentSession?: string }`\n\n### 状态\n\n- `{ id?, type: \"get_state\" }`\n- `{ id?, type: \"set_todos\", phases: TodoPhase[] }`\n- `{ id?, type: \"set_host_tools\", tools: RpcHostToolDefinition[] }`\n\n### 模型\n\n- `{ id?, type: \"set_model\", provider: string, modelId: string }`\n- `{ id?, type: \"cycle_model\" }`\n- `{ id?, type: \"get_available_models\" }`\n\n### 思考\n\n- `{ id?, type: \"set_thinking_level\", level: ThinkingLevel }`\n- `{ id?, type: \"cycle_thinking_level\" }`\n\n### 队列模式\n\n- `{ id?, type: \"set_steering_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_follow_up_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_interrupt_mode\", mode: \"immediate\" | \"wait\" }`\n\n### 压缩\n\n- `{ id?, type: \"compact\", customInstructions?: string }`\n- `{ id?, type: \"set_auto_compaction\", enabled: boolean }`\n\n### 重试\n\n- `{ id?, type: \"set_auto_retry\", enabled: boolean }`\n- `{ id?, type: \"abort_retry\" }`\n\n### Bash\n\n- `{ id?, type: \"bash\", command: string }`\n- `{ id?, type: \"abort_bash\" }`\n\n### 会话\n\n- `{ id?, type: \"get_session_stats\" }`\n- `{ id?, type: \"export_html\", outputPath?: string }`\n- `{ id?, type: \"switch_session\", sessionPath: string }`\n- `{ id?, type: \"branch\", entryId: string }`\n- `{ id?, type: \"get_branch_messages\" }`\n- `{ id?, type: \"get_last_assistant_text\" }`\n- `{ id?, type: \"set_session_name\", name: string }`\n\n### 消息\n\n- `{ id?, type: \"get_messages\" }`\n\n## 响应模式\n\n所有命令结果使用 `RpcResponse`：\n\n- 成功：`{ id?, type: \"response\", command: <command>, success: true, data?: ... }`\n- 失败：`{ id?, type: \"response\", command: string, success: false, error: string }`\n\n数据载荷因命令而异，在 `rpc-types.ts` 中定义。\n\n### `get_state` 载荷\n\n```json\n{\n  \"model\": { \"provider\": \"...\", \"id\": \"...\" },\n  \"thinkingLevel\": \"off|minimal|low|medium|high|xhigh\",\n  \"isStreaming\": false,\n  \"isCompacting\": false,\n  \"steeringMode\": \"all|one-at-a-time\",\n  \"followUpMode\": \"all|one-at-a-time\",\n  \"interruptMode\": \"immediate|wait\",\n  \"sessionFile\": \"...\",\n  \"sessionId\": \"...\",\n  \"sessionName\": \"...\",\n  \"autoCompactionEnabled\": true,\n  \"messageCount\": 0,\n  \"queuedMessageCount\": 0,\n  \"todoPhases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Todos\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the tool surface\",\n          \"status\": \"in_progress\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n### `set_todos` 载荷\n\n替换当前会话的内存中待办事项状态，并返回规范化的阶段列表：\n\n```json\n{\n  \"id\": \"req_2\",\n  \"type\": \"set_todos\",\n  \"phases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Evaluation\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the read tool surface\",\n          \"status\": \"in_progress\"\n        },\n        {\n          \"id\": \"task-2\",\n          \"content\": \"Exercise edit operations\",\n          \"status\": \"pending\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n这对于希望在第一个提示之前预设计划的宿主程序非常有用。\n\n### `set_host_tools` 载荷\n\n替换当前 RPC 服务器可能通过 stdio 回调的宿主拥有的工具集：\n\n```json\n{\n  \"id\": \"req_3\",\n  \"type\": \"set_host_tools\",\n  \"tools\": [\n    {\n      \"name\": \"echo_host\",\n      \"label\": \"Echo Host\",\n      \"description\": \"Echo a value from the embedding host\",\n      \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"message\": { \"type\": \"string\" }\n        },\n        \"required\": [\"message\"],\n        \"additionalProperties\": false\n      }\n    }\n  ]\n}\n```\n\n响应载荷为：\n\n```json\n{\n  \"toolNames\": [\"echo_host\"]\n}\n```\n\n这些工具会在下次模型调用之前添加到活跃会话的工具注册表中。重新发送 `set_host_tools` 会替换之前的宿主拥有的工具集。\n\n## 事件流模式\n\nRPC 模式转发来自 `AgentSession.subscribe(...)` 的 `AgentSessionEvent` 对象。\n\n常见事件类型：\n\n- `agent_start`、`agent_end`\n- `turn_start`、`turn_end`\n- `message_start`、`message_update`、`message_end`\n- `tool_execution_start`、`tool_execution_update`、`tool_execution_end`\n- `auto_compaction_start`、`auto_compaction_end`\n- `auto_retry_start`、`auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n- `todo_auto_clear`\n\n扩展运行器错误作为单独的事件发出：\n\n```json\n{ \"type\": \"extension_error\", \"extensionPath\": \"...\", \"event\": \"...\", \"error\": \"...\" }\n```\n\n`message_update` 在 `assistantMessageEvent` 中包含流式增量数据（文本/思考/工具调用增量）。\n\n## 提示/队列并发与排序\n\n这是最重要的操作行为。\n\n### 即时确认 vs 完成\n\n`prompt` 和 `abort_and_prompt` 会**立即确认**：\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n```\n\n这意味着：\n\n- 命令接受 != 运行完成\n- 最终完成通过 `agent_end` 观察\n\n### 流式传输期间\n\n`AgentSession.prompt()` 在活跃流式传输期间需要 `streamingBehavior`：\n\n- `\"steer\"` => 排队的引导消息（中断路径）\n- `\"followUp\"` => 排队的后续消息（回合后路径）\n\n如果在流式传输期间省略，提示将失败。\n\n### 队列默认值\n\n来自编程代理设置模式（`packages/coding-agent/src/config/settings-schema.ts`）：\n\n- `steeringMode`：`\"one-at-a-time\"`\n- `followUpMode`：`\"one-at-a-time\"`\n- `interruptMode`：`\"wait\"`\n\n### 模式语义\n\n- `set_steering_mode` / `set_follow_up_mode`\n  - `\"one-at-a-time\"`：每个回合出队一条排队消息\n  - `\"all\"`：一次性出队整个队列\n- `set_interrupt_mode`\n  - `\"immediate\"`：工具执行在工具调用之间检查引导；待处理的引导可以中止回合中剩余的工具调用\n  - `\"wait\"`：将引导推迟到回合完成\n\n## 扩展 UI 子协议\n\nRPC 模式中的扩展使用请求/响应 UI 帧。\n\n### 出站请求\n\n`RpcExtensionUIRequest`（`type: \"extension_ui_request\"`）方法：\n\n- `select`、`confirm`、`input`、`editor`\n- `notify`、`setStatus`、`setWidget`、`setTitle`、`set_editor_text`\n\n运行时说明：\n\n- RPC 模式下自动会话标题生成被禁用，`setTitle` UI 请求默认也会被抑制，因为大多数宿主没有有意义的终端标题界面。设置 `PI_RPC_EMIT_TITLE=1` 可重新启用该 UI 事件。\n\n示例：\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"123\", \"method\": \"confirm\", \"title\": \"Confirm\", \"message\": \"Continue?\", \"timeout\": 30000 }\n```\n\n### 入站响应\n\n`RpcExtensionUIResponse`（`type: \"extension_ui_response\"`）：\n\n- `{ type: \"extension_ui_response\", id: string, value: string }`\n- `{ type: \"extension_ui_response\", id: string, confirmed: boolean }`\n- `{ type: \"extension_ui_response\", id: string, cancelled: true }`\n\n如果对话框有超时设置，RPC 模式会在超时/中止触发时解析为默认值。\n\n## 宿主工具子协议\n\nRPC 宿主可以通过发送 `set_host_tools` 向代理暴露自定义工具，然后通过相同的传输通道处理执行请求。\n\n### 出站请求\n\n当代理希望宿主执行其中一个工具时，RPC 模式会发出：\n\n```json\n{\n  \"type\": \"host_tool_call\",\n  \"id\": \"host_1\",\n  \"toolCallId\": \"toolu_123\",\n  \"toolName\": \"echo_host\",\n  \"arguments\": { \"message\": \"hello\" }\n}\n```\n\n如果工具执行后来被中止，RPC 模式会发出：\n\n```json\n{\n  \"type\": \"host_tool_cancel\",\n  \"id\": \"host_cancel_1\",\n  \"targetId\": \"host_1\"\n}\n```\n\n### 入站更新和完成\n\n宿主可以选择性地流式传输进度：\n\n```json\n{\n  \"type\": \"host_tool_update\",\n  \"id\": \"host_1\",\n  \"partialResult\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"working\" }]\n  }\n}\n```\n\n完成使用：\n\n```json\n{\n  \"type\": \"host_tool_result\",\n  \"id\": \"host_1\",\n  \"result\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"done\" }]\n  }\n}\n```\n\n在 `host_tool_result` 上设置 `isError: true` 可将返回的内容作为工具错误呈现。\n\n## 错误模型与可恢复性\n\n### 命令级失败\n\n失败为 `success: false`，附带字符串 `error`。\n\n```json\n{ \"id\": \"req_2\", \"type\": \"response\", \"command\": \"set_model\", \"success\": false, \"error\": \"Model not found: provider/model\" }\n```\n\n### 可恢复性预期\n\n- 大多数命令失败是可恢复的；进程保持存活。\n- 格式错误的 JSONL / 解析循环异常会发出 `parse` 错误响应并继续读取后续行。\n- 空的 `set_session_name` 会被拒绝（`Session name cannot be empty`）。\n- 具有未知 `id` 的扩展 UI 响应会被忽略。\n- 进程终止条件为 stdin 关闭或扩展触发的显式关闭。\n\n## 简明命令流程\n\n### 1) 提示和流式传输\n\nstdin：\n\n```json\n{ \"id\": \"req_1\", \"type\": \"prompt\", \"message\": \"Summarize this repo\" }\n```\n\nstdout 序列（典型）：\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n{ \"type\": \"agent_start\" }\n{ \"type\": \"message_update\", \"assistantMessageEvent\": { \"type\": \"text_delta\", \"delta\": \"...\" }, \"message\": { \"role\": \"assistant\", \"content\": [] } }\n{ \"type\": \"agent_end\", \"messages\": [] }\n```\n\n### 2) 在流式传输期间使用显式队列策略提示\n\nstdin：\n\n```json\n{ \"id\": \"req_2\", \"type\": \"prompt\", \"message\": \"Also include risks\", \"streamingBehavior\": \"followUp\" }\n```\n\n### 3) 检查和调整队列行为\n\nstdin：\n\n```json\n{ \"id\": \"q1\", \"type\": \"get_state\" }\n{ \"id\": \"q2\", \"type\": \"set_steering_mode\", \"mode\": \"all\" }\n{ \"id\": \"q3\", \"type\": \"set_interrupt_mode\", \"mode\": \"wait\" }\n```\n\n### 4) 扩展 UI 往返\n\nstdout：\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"ui_7\", \"method\": \"input\", \"title\": \"Branch name\", \"placeholder\": \"feature/...\" }\n```\n\nstdin：\n\n```json\n{ \"type\": \"extension_ui_response\", \"id\": \"ui_7\", \"value\": \"feature/rpc-host\" }\n```\n\n## 关于 `RpcClient` 辅助工具的说明\n\n`src/modes/rpc/rpc-client.ts` 是一个便捷封装，而非协议定义。\n\n当前辅助工具特性：\n\n- 生成 `bun <cliPath> --mode rpc` 进程\n- 通过生成的 `req_<n>` id 关联响应\n- 仅将已识别的 `AgentEvent` 类型分发给监听器\n- 通过 `setCustomTools()` 支持宿主拥有的自定义工具，并自动处理 `host_tool_call` / `host_tool_cancel`\n- **未**为每个协议命令暴露辅助方法（例如，`set_interrupt_mode` 和 `set_session_name` 存在于协议类型中，但未作为专用方法封装）\n\n如果需要完整的协议覆盖，请使用原始协议帧。\n",
	"zh-cn/configuration/sdk.md": "---\ntitle: SDK\ndescription: 用于在 xcsh 编码智能体运行时之上构建自定义智能体和集成的 SDK。\nsidebar:\n  order: 6\n  label: SDK\ni18n:\n  sourceHash: 80f3a4374241\n  translator: machine\n---\n\n# SDK\n\nSDK 是 `@f5-sales-demo/xcsh` 的进程内集成接口。\n当您希望从自己的 Bun/Node 进程直接访问智能体状态、事件流、工具连接和会话控制时，请使用它。\n\n如果您需要跨语言/进程隔离，请改用 RPC 模式。\n\n## 安装\n\n```bash\nbun add @f5-sales-demo/xcsh\n```\n\n## 入口点\n\n`@f5-sales-demo/xcsh` 从包根目录（以及通过 `@f5-sales-demo/xcsh/sdk`）导出 SDK API。\n\n嵌入方的核心导出：\n\n- `createAgentSession`\n- `SessionManager`\n- `Settings`\n- `AuthStorage`\n- `ModelRegistry`\n- `discoverAuthStorage`\n- 发现辅助函数（`discoverExtensions`、`discoverSkills`、`discoverContextFiles`、`discoverPromptTemplates`、`discoverSlashCommands`、`discoverCustomTSCommands`、`discoverMCPServers`）\n- 工具工厂接口（`createTools`、`BUILTIN_TOOLS`、工具类）\n\n## 快速入门（自动发现默认值）\n\n```ts\nimport { createAgentSession } from \"@f5-sales-demo/xcsh\";\n\nconst { session, modelFallbackMessage } = await createAgentSession();\n\nif (modelFallbackMessage) {\n process.stderr.write(`${modelFallbackMessage}\\n`);\n}\n\nconst unsubscribe = session.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Summarize this repository in 3 bullets.\");\nunsubscribe();\nawait session.dispose();\n```\n\n## `createAgentSession()` 默认发现的内容\n\n`createAgentSession()` 遵循\"提供则覆盖，省略则自动发现\"的原则。\n\n如果省略，它将解析：\n\n- `cwd`：`getProjectDir()`\n- `agentDir`：`~/.xcsh/agent`（通过 `getAgentDir()`）\n- `authStorage`：`discoverAuthStorage(agentDir)`\n- `modelRegistry`：`new ModelRegistry(authStorage)` + `await refresh()`\n- `settings`：`await Settings.init({ cwd, agentDir })`\n- `sessionManager`：`SessionManager.create(cwd)`（基于文件）\n- 技能/上下文文件/提示模板/斜杠命令/扩展/自定义 TS 命令\n- 通过 `createTools(...)` 获取内置工具\n- MCP 工具（默认启用）\n- LSP 集成（默认启用）\n\n### 必填与可选输入\n\n通常您只需提供想要控制的部分：\n\n- **必须提供**：最小会话无需提供任何内容\n- **嵌入方通常显式提供**：\n    - `sessionManager`（如果您需要内存存储或自定义位置）\n    - `authStorage` + `modelRegistry`（如果您自行管理凭据/模型生命周期）\n    - `model` 或 `modelPattern`（如果需要确定性的模型选择）\n    - `settings`（如果您需要隔离/测试配置）\n\n## 会话管理器行为（持久化与内存）\n\n`AgentSession` 始终使用 `SessionManager`；行为取决于您使用的工厂方法。\n\n### 基于文件（默认）\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.create(process.cwd()),\n});\n\nconsole.log(session.sessionFile); // 绝对 .jsonl 路径\n```\n\n- 将对话/消息/状态增量持久化到会话文件中。\n- 支持恢复/打开/列出/分叉工作流。\n- `session.sessionFile` 已定义。\n\n### 内存存储\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.inMemory(),\n});\n\nconsole.log(session.sessionFile); // undefined\n```\n\n- 不进行文件系统持久化。\n- 适用于测试、短暂的工作进程、请求范围的智能体。\n- 会话方法仍然有效，但持久化相关行为（文件恢复/分叉路径）自然受到限制。\n\n### 恢复/打开/列出辅助函数\n\n```ts\nimport { SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst recent = await SessionManager.continueRecent(process.cwd());\nconst listed = await SessionManager.list(process.cwd());\nconst opened = listed[0] ? await SessionManager.open(listed[0].path) : null;\n```\n\n## 模型与认证连接\n\n`createAgentSession()` 使用 `ModelRegistry` + `AuthStorage` 进行模型选择和 API 密钥解析。\n\n### 显式连接\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst available = modelRegistry.getAvailable();\nif (available.length === 0) throw new Error(\"No authenticated models available\");\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n model: available[0],\n thinkingLevel: \"medium\",\n sessionManager: SessionManager.inMemory(),\n});\n```\n\n### 省略 `model` 时的选择顺序\n\n当未提供显式 `model`/`modelPattern` 时：\n\n1. 从现有会话中恢复模型（如果可恢复且密钥可用）\n2. 设置中的默认模型角色（`default`）\n3. 第一个具有有效认证的可用模型\n\n如果恢复失败，`modelFallbackMessage` 将解释回退原因。\n\n### 认证优先级\n\n`AuthStorage.getApiKey(...)` 按以下顺序解析：\n\n1. 运行时覆盖（`setRuntimeApiKey`）\n2. `agent.db` 中存储的凭据\n3. 提供方环境变量\n4. 自定义提供方解析器回退（如果已配置）\n\n## 事件订阅模型\n\n使用 `session.subscribe(listener)` 订阅；它返回一个取消订阅函数。\n\n```ts\nconst unsubscribe = session.subscribe(event => {\n switch (event.type) {\n  case \"agent_start\":\n  case \"turn_start\":\n  case \"tool_execution_start\":\n   break;\n  case \"message_update\":\n   if (event.assistantMessageEvent.type === \"text_delta\") {\n    process.stdout.write(event.assistantMessageEvent.delta);\n   }\n   break;\n }\n});\n```\n\n`AgentSessionEvent` 包含核心 `AgentEvent` 以及会话级事件：\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n## 提示生命周期\n\n`session.prompt(text, options?)` 是主要入口点。\n\n行为：\n\n1. 可选的命令/模板展开（`/` 命令、自定义命令、文件斜杠命令、提示模板）\n2. 如果当前正在流式传输：\n    - 需要 `streamingBehavior: \"steer\" | \"followUp\"`\n    - 入队而不是丢弃工作\n3. 如果空闲：\n    - 验证模型 + API 密钥\n    - 追加用户消息\n    - 启动智能体轮次\n\n相关 API：\n\n- `sendUserMessage(content, { deliverAs? })`\n- `steer(text, images?)`\n- `followUp(text, images?)`\n- `sendCustomMessage({ customType, content, ... }, { deliverAs?, triggerTurn? })`\n- `abort()`\n\n## 工具与扩展集成\n\n### 内置工具与过滤\n\n- 内置工具来自 `createTools(...)` 和 `BUILTIN_TOOLS`。\n- `toolNames` 作为内置工具的允许列表。\n- `customTools` 和扩展注册的工具仍会包含在内。\n- 隐藏工具（例如 `submit_result`）需要显式选择启用，除非选项中要求。\n\n```ts\nconst { session } = await createAgentSession({\n toolNames: [\"read\", \"grep\", \"find\", \"write\"],\n requireSubmitResultTool: true,\n});\n```\n\n### 扩展\n\n- `extensions`：内联 `ExtensionFactory[]`\n- `additionalExtensionPaths`：加载额外的扩展文件\n- `disableExtensionDiscovery`：禁用自动扩展扫描\n- `preloadedExtensions`：复用已加载的扩展集\n\n### 运行时工具集变更\n\n`AgentSession` 支持运行时激活更新：\n\n- `getActiveToolNames()`\n- `getAllToolNames()`\n- `setActiveToolsByName(names)`\n- `refreshMCPTools(mcpTools)`\n\n系统提示将重新构建以反映活动工具的变化。\n\n## 发现辅助函数\n\n当您希望进行部分控制而无需重新创建内部发现逻辑时，请使用这些函数：\n\n- `discoverAuthStorage(agentDir?)`\n- `discoverExtensions(cwd?)`\n- `discoverSkills(cwd?, _agentDir?, settings?)`\n- `discoverContextFiles(cwd?, _agentDir?)`\n- `discoverPromptTemplates(cwd?, agentDir?)`\n- `discoverSlashCommands(cwd?)`\n- `discoverCustomTSCommands(cwd?, agentDir?)`\n- `discoverMCPServers(cwd?)`\n- `buildSystemPrompt(options?)`\n\n## 子智能体相关选项\n\n适用于构建编排器的 SDK 使用方（类似于任务执行器流程）：\n\n- `outputSchema`：将结构化输出期望传递到工具上下文中\n- `requireSubmitResultTool`：强制包含 `submit_result` 工具\n- `taskDepth`：嵌套任务会话的递归深度上下文\n- `parentTaskPrefix`：嵌套任务输出的制品命名前缀\n\n这些对于普通的单智能体嵌入是可选的。\n\n## `createAgentSession()` 返回值\n\n```ts\ntype CreateAgentSessionResult = {\n session: AgentSession;\n extensionsResult: LoadExtensionsResult;\n setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;\n mcpManager?: MCPManager;\n modelFallbackMessage?: string;\n lspServers?: Array<{ name: string; status: \"ready\" | \"error\"; fileTypes: string[]; error?: string }>;\n};\n```\n\n仅当您的嵌入方提供工具/扩展应调用的 UI 能力时，才使用 `setToolUIContext(...)`。\n\n## 最小受控嵌入示例\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n Settings,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst settings = Settings.isolated({\n \"compaction.enabled\": true,\n \"retry.enabled\": true,\n});\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n settings,\n sessionManager: SessionManager.inMemory(),\n toolNames: [\"read\", \"grep\", \"find\", \"edit\", \"write\"],\n enableMCP: false,\n enableLsp: true,\n});\n\nsession.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Find all TODO comments in this repo and propose fixes.\");\nawait session.dispose();\n```\n",
	"zh-cn/configuration/secrets.md": "---\ntitle: 敏感信息混淆\ndescription: 敏感信息混淆管道，用于从会话日志和输出中遮蔽敏感值。\nsidebar:\n  order: 3\n  label: 敏感信息\ni18n:\n  sourceHash: 1d9dc101c614\n  translator: machine\n---\n\n# 敏感信息混淆\n\n防止敏感值（API 密钥、令牌、密码）被发送到 LLM 提供商。启用后，敏感信息在离开进程前会被替换为确定性占位符，并在模型返回的工具调用参数中恢复。\n\n## 启用\n\n默认启用。可通过 `/settings` 界面或直接在 `config.yml` 中切换：\n\n```yaml\nsecrets:\n  enabled: false\n```\n\n## 工作原理\n\n1. 在会话启动时，敏感信息从两个来源收集：\n   - **环境变量**，匹配常见的敏感信息模式（`*_KEY`、`*_SECRET`、`*_TOKEN`、`*_PASSWORD` 等），且值长度 >= 8 个字符\n   - **`secrets.yml` 文件**（见下文）\n\n2. 发送到 LLM 的出站消息中，所有敏感值会被替换为占位符，如 `<<$env:S0>>`、`<<$env:S1>>` 等。\n\n3. 模型返回的工具调用参数会被深度遍历，占位符在执行前恢复为原始值。\n\n两种模式控制每个敏感信息的处理方式：\n\n| 模式 | 行为 | 可逆 |\n|---|---|---|\n| `obfuscate`（默认） | 替换为索引占位符 `<<$env:SN>>` | 是（在工具参数中反混淆） |\n| `replace` | 替换为确定性的等长字符串 | 否（单向） |\n\n## secrets.yml\n\n在 YAML 中定义自定义敏感信息条目。检查两个位置：\n\n| 级别 | 路径 | 用途 |\n|---|---|---|\n| 全局 | `~/.xcsh/agent/secrets.yml` | 跨所有项目的敏感信息 |\n| 项目 | `<cwd>/.xcsh/secrets.yml` | 项目特定的敏感信息 |\n\n具有匹配 `content` 的项目条目会覆盖全局条目。\n\n### 模式定义\n\n数组中每个条目包含以下字段：\n\n| 字段 | 类型 | 必需 | 描述 |\n|---|---|---|---|\n| `type` | `\"plain\"` 或 `\"regex\"` | 是 | 匹配策略 |\n| `content` | string | 是 | 敏感值（plain 类型）或正则表达式模式（regex 类型） |\n| `mode` | `\"obfuscate\"` 或 `\"replace\"` | 否 | 默认：`\"obfuscate\"` |\n| `replacement` | string | 否 | 自定义替换文本（仅 replace 模式） |\n| `flags` | string | 否 | 正则标志（仅 regex 类型） |\n\n### 示例\n\n#### 纯文本敏感信息\n\n```yaml\n# Obfuscate a specific API key (default mode)\n- type: plain\n  content: sk-proj-abc123def456\n\n# Replace a database password with a fixed string\n- type: plain\n  content: hunter2\n  mode: replace\n  replacement: \"********\"\n```\n\n#### 正则表达式敏感信息\n\n```yaml\n# Obfuscate any AWS-style key\n- type: regex\n  content: \"AKIA[0-9A-Z]{16}\"\n\n# Case-insensitive match with explicit flags\n- type: regex\n  content: \"api[_-]?key\\\\s*=\\\\s*\\\\w+\"\n  flags: \"i\"\n\n# Regex literal syntax (pattern and flags in one string)\n- type: regex\n  content: \"/bearer\\\\s+[a-zA-Z0-9._~+\\\\/=-]+/i\"\n```\n\n正则表达式条目始终进行全局扫描（`g` 标志会自动强制添加）。支持正则字面量语法 `/pattern/flags` 作为分开使用 `content` + `flags` 字段的替代方式。模式中的转义斜杠（`\\\\/`）会被正确处理。\n\n#### 使用正则表达式的 replace 模式\n\n```yaml\n# One-way replace connection strings (not reversible)\n- type: regex\n  content: \"postgres://[^\\\\s]+\"\n  mode: replace\n  replacement: \"postgres://***\"\n```\n\n## 与环境变量检测的交互\n\n环境变量始终最先被收集。文件定义的条目随后追加，因此文件条目可以覆盖不存在于环境变量中的敏感信息（配置文件、硬编码值等）。如果同一个值同时出现在两者中，文件条目的模式优先。\n\n## 关键文件\n\n- `src/secrets/index.ts` -- 加载、合并、环境变量收集\n- `src/secrets/obfuscator.ts` -- `SecretObfuscator` 类、占位符生成、消息混淆\n- `src/secrets/regex.ts` -- 正则字面量解析和编译\n- `src/config/settings-schema.ts` -- `secrets.enabled` 设置定义\n",
	"zh-cn/extensions/extension-loading.md": "---\ntitle: 扩展加载（TypeScript/JavaScript 模块）\ndescription: 扩展的 TypeScript 和 JavaScript 模块加载管线，包含解析、验证和缓存功能。\nsidebar:\n  order: 2\n  label: 扩展加载\ni18n:\n  sourceHash: a8cea231c660\n  translator: machine\n---\n\n# 扩展加载（TypeScript/JavaScript 模块）\n\n本文档介绍编码代理如何在启动时发现和加载**扩展模块**（`.ts`/`.js`）。\n\n本文档**不**涵盖 `gemini-extension.json` 清单扩展（另有单独文档说明）。\n\n## 此子系统的功能\n\n扩展加载会构建一个模块入口文件列表，使用 Bun 导入每个模块，执行其工厂函数，并返回：\n\n- 已加载的扩展定义\n- 按路径分类的加载错误（不会中止整体加载过程）\n- 一个共享的扩展运行时对象，供后续 `ExtensionRunner` 使用\n\n## 主要实现文件\n\n- `src/extensibility/extensions/loader.ts` — 路径发现 + 导入/执行\n- `src/extensibility/extensions/index.ts` — 公共导出\n- `src/extensibility/extensions/runner.ts` — 加载后的运行时/事件执行\n- `src/discovery/builtin.ts` — 用于扩展模块的原生自动发现提供程序\n- `src/config/settings.ts` — 加载合并后的 `extensions` / `disabledExtensions` 设置\n\n---\n\n## 扩展加载的输入\n\n### 1）自动发现的原生扩展模块\n\n`discoverAndLoadExtensions()` 首先向发现提供程序请求具有 `extension-module` 能力的项目，然后仅保留提供程序为 `native` 的项目。\n\n有效的原生位置：\n\n- 项目级别：`<cwd>/.xcsh/extensions`\n- 用户级别：`~/.xcsh/agent/extensions`\n\n路径根目录来自原生提供程序（`SOURCE_PATHS.native`）。\n\n注意事项：\n\n- 原生自动发现当前基于 `.xcsh`。\n- 旧版 `.pi` 仍可在 `package.json` 清单键（`pi.extensions`）中使用，但不作为此处的原生根目录。\n\n### 2）显式配置的路径\n\n自动发现完成后，配置的路径会被追加并解析。\n\n在主会话启动路径（`sdk.ts`）中的配置路径来源：\n\n1. CLI 提供的路径（`--extension/-e`，`--hook` 也被视为扩展路径）\n2. 设置中的 `extensions` 数组（合并全局 + 项目设置）\n\n全局设置文件：\n\n- `~/.xcsh/agent/config.yml`（或通过 `PI_CODING_AGENT_DIR` 自定义代理目录）\n\n项目设置文件：\n\n- `<cwd>/.xcsh/settings.json`\n\n示例：\n\n```yaml\n# ~/.xcsh/agent/config.yml\nextensions:\n  - ~/my-exts/safety.ts\n  - ./local/ext-pack\n```\n\n```json\n{\n  \"extensions\": [\"./.xcsh/extensions/my-extra\"]\n}\n```\n\n---\n\n## 启用/禁用控制\n\n### 禁用发现\n\n- CLI：`--no-extensions`\n- SDK 选项：`disableExtensionDiscovery`\n\n行为差异：\n\n- SDK：当 `disableExtensionDiscovery=true` 时，仍会通过 `loadExtensions()` 加载 `additionalExtensionPaths`。\n- CLI 路径构建（`main.ts`）在设置 `--no-extensions` 时会清除 CLI 扩展路径，因此在该模式下不会转发显式的 `-e/--hook`。\n\n### 禁用特定扩展模块\n\n`disabledExtensions` 设置按扩展 ID 格式进行过滤：\n\n- `extension-module:<derivedName>`\n\n`derivedName` 基于入口路径（`getExtensionNameFromPath`），例如：\n\n- `/x/foo.ts` -> `foo`\n- `/x/bar/index.ts` -> `bar`\n\n示例：\n\n```yaml\ndisabledExtensions:\n  - extension-module:foo\n```\n\n---\n\n## 路径和入口解析\n\n### 路径规范化\n\n对于配置的路径：\n\n1. 规范化 Unicode 空格\n2. 展开 `~`\n3. 如果是相对路径，则相对于当前 `cwd` 解析\n\n### 如果配置的路径是文件\n\n直接作为模块入口候选使用。\n\n### 如果配置的路径是目录\n\n解析顺序：\n\n1. 该目录中的 `package.json` 包含 `xcsh.extensions`（或旧版 `pi.extensions`）-> 使用声明的入口\n2. `index.ts`\n3. `index.js`\n4. 否则扫描一级目录查找扩展入口：\n   - 直接的 `*.ts` / `*.js`\n   - 子目录中的 `index.ts` / `index.js`\n   - 子目录中包含 `xcsh.extensions` / `pi.extensions` 的 `package.json`\n\n规则和约束：\n\n- 不会递归发现超过一个子目录层级\n- 声明的 `extensions` 清单入口相对于该包目录解析\n- 声明的入口仅在文件存在/可访问时才会被包含\n- 在 `*/index.{ts,js}` 对中，TypeScript 优先于 JavaScript\n- 符号链接被视为合格的文件/目录\n\n### 忽略行为因来源而异\n\n- 原生自动发现（发现辅助程序中的 `discoverExtensionModulePaths`）使用原生 glob，设置 `gitignore: true` 和 `hidden: false`。\n- `loader.ts` 中的显式配置目录扫描使用 `readdir` 规则，**不**应用 gitignore 过滤。\n\n---\n\n## 加载顺序和优先级\n\n`discoverAndLoadExtensions()` 构建一个有序列表，然后调用 `loadExtensions()`。\n\n顺序：\n\n1. 原生自动发现的模块\n2. 显式配置的路径（按提供顺序）\n\n在 `sdk.ts` 中，配置的顺序为：\n\n1. CLI 附加路径\n2. 设置中的 `extensions`\n\n去重：\n\n- 基于绝对路径\n- 先出现的路径优先\n- 后续重复项被忽略\n\n含义：如果同一模块路径既被自动发现又被显式配置，它只会在第一个位置（自动发现阶段）加载一次。\n\n---\n\n## 模块导入和工厂函数契约\n\n每个候选路径通过动态导入加载：\n\n- `await import(resolvedPath)`\n- 工厂函数为 `module.default ?? module`\n- 工厂函数必须是函数（`ExtensionFactory`）\n\n如果导出不是函数，该路径会以结构化错误的形式失败，加载过程继续进行。\n\n---\n\n## 故障处理和隔离\n\n### 加载期间\n\n每个扩展路径的故障以 `{ path, error }` 的形式捕获，不会阻止其他路径的加载。\n\n常见情况：\n\n- 导入失败 / 文件缺失\n- 无效的工厂函数导出（非函数）\n- 执行工厂函数时抛出异常\n\n### 运行时隔离模型\n\n- 扩展**未经沙箱化**（同一进程/运行时）。\n- 它们共享一个 `EventBus` 和一个 `ExtensionRuntime` 实例。\n- 在加载期间，运行时操作方法会故意抛出 `ExtensionRuntimeNotInitializedError`；操作连接在后续的 `ExtensionRunner.initialize()` 中进行。\n\n### 加载之后\n\n当事件通过 `ExtensionRunner` 运行时，处理程序异常会被捕获并作为扩展错误发出，而不是导致运行器循环崩溃。\n\n---\n\n## 最简用户/项目布局示例\n\n### 用户级别\n\n```text\n~/.xcsh/agent/\n  config.yml\n  extensions/\n    guardrails.ts\n    audit/\n      index.ts\n```\n\n### 项目级别\n\n```text\n<repo>/\n  .xcsh/\n    settings.json\n    extensions/\n      checks/\n        package.json\n      lint-gates.ts\n```\n\n`checks/package.json`：\n\n```json\n{\n  \"xcsh\": {\n    \"extensions\": [\"./src/check-a.ts\", \"./src/check-b.js\"]\n  }\n}\n```\n\n旧版清单键仍可接受：\n\n```json\n{\n  \"pi\": {\n    \"extensions\": [\"./index.ts\"]\n  }\n}\n```\n",
	"zh-cn/extensions/extensions.md": "---\ntitle: 扩展\ndescription: 扩展运行时概述，涵盖类型、运行器生命周期、注册与发现。\nsidebar:\n  order: 1\n  label: 概述\ni18n:\n  sourceHash: 14cc16dbd98b\n  translator: machine\n---\n\n# 扩展\n\n`packages/coding-agent` 中运行时扩展的主要编写指南。\n\n本文档涵盖以下文件中的当前扩展运行时：\n\n- `src/extensibility/extensions/types.ts`\n- `src/extensibility/extensions/runner.ts`\n- `src/extensibility/extensions/wrapper.ts`\n- `src/extensibility/extensions/index.ts`\n- `src/modes/controllers/extension-ui-controller.ts`\n\n有关发现路径和文件系统加载规则，请参阅 `docs/extension-loading.md`。\n\n## 什么是扩展\n\n扩展是一个导出默认工厂函数的 TS/JS 模块：\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nexport default function myExtension(pi: ExtensionAPI) {\n // register handlers/tools/commands/renderers\n}\n```\n\n扩展可以在一个模块中组合以下所有功能：\n\n- 事件处理器（`pi.on(...)`）\n- 可被 LLM 调用的工具（`pi.registerTool(...)`）\n- 斜杠命令（`pi.registerCommand(...)`）\n- 键盘快捷键和标志\n- 自定义消息渲染\n- 会话/消息注入 API（`sendMessage`、`sendUserMessage`、`appendEntry`）\n\n## 运行时模型\n\n1. 扩展被导入，其工厂函数随即运行。\n2. 在加载阶段，注册方法有效；运行时动作方法尚未初始化。\n3. `ExtensionRunner.initialize(...)` 为当前模式连接实时动作/上下文。\n4. 会话/代理/工具生命周期事件被发送至处理器。\n5. 每次工具执行均通过扩展拦截进行包装（`tool_call` / `tool_result`）。\n\n```text\nExtension lifecycle (simplified)\n\nload paths\n   │\n   ▼\nimport module + run factory (registration only)\n   │\n   ▼\nExtensionRunner.initialize(mode/session/tool registry)\n   │\n   ├─ emit session/agent events to handlers\n   ├─ wrap tool execution (tool_call/tool_result)\n   └─ expose runtime actions (sendMessage, setActiveTools, ...)\n```\n\n来自 `loader.ts` 的重要约束：\n\n- 在扩展加载期间调用 `pi.sendMessage()` 等动作方法会抛出 `ExtensionRuntimeNotInitializedError`\n- 请先注册；再从事件/命令/工具中执行运行时行为\n\n## 快速入门\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\nimport { Type } from \"@sinclair/typebox\";\n\nexport default function (pi: ExtensionAPI) {\n pi.setLabel(\"Safety + Utilities\");\n\n pi.on(\"session_start\", async (_event, ctx) => {\n  ctx.ui.notify(`Extension loaded in ${ctx.cwd}`, \"info\");\n });\n\n pi.on(\"tool_call\", async (event) => {\n  if (event.toolName === \"bash\" && event.input.command?.includes(\"rm -rf\")) {\n   return { block: true, reason: \"Blocked by extension policy\" };\n  }\n });\n\n pi.registerTool({\n  name: \"hello_extension\",\n  label: \"Hello Extension\",\n  description: \"Return a greeting\",\n  parameters: Type.Object({ name: Type.String() }),\n  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {\n   return {\n    content: [{ type: \"text\", text: `Hello, ${params.name}` }],\n    details: { greeted: params.name },\n   };\n  },\n });\n\n pi.registerCommand(\"hello-ext\", {\n  description: \"Show queue state\",\n  handler: async (_args, ctx) => {\n   ctx.ui.notify(`pending=${ctx.hasPendingMessages()}`, \"info\");\n  },\n });\n}\n```\n\n## 扩展 API 接口\n\n## 1) 注册与动作（`ExtensionAPI`）\n\n核心方法：\n\n- `on(event, handler)`\n- `registerTool`、`registerCommand`、`registerShortcut`、`registerFlag`\n- `registerMessageRenderer`\n- `sendMessage`、`sendUserMessage`、`appendEntry`\n- `getActiveTools`、`getAllTools`、`setActiveTools`\n- `getSessionName`、`setSessionName`\n- `setModel`、`getThinkingLevel`、`setThinkingLevel`\n- `registerProvider`\n- `events`（共享事件总线）\n\n在交互模式下，`input` 处理器在内置的首条消息自动标题检查之前运行。在 `input` 中调用 `await pi.setSessionName(...)` 的扩展可以设置持久化会话名称，并阻止该会话运行默认的自动生成标题逻辑。\n\n此外还暴露：\n\n- `pi.logger`\n- `pi.typebox`\n- `pi.pi`（包导出）\n\n### 消息投递语义\n\n`pi.sendMessage(message, options)` 支持：\n\n- `deliverAs: \"steer\"`（默认）——中断当前运行\n- `deliverAs: \"followUp\"`——在当前运行完成后排队执行\n- `deliverAs: \"nextTurn\"`——存储并在下一次用户提示时注入\n- `triggerTurn: true`——在空闲时启动一个轮次（`nextTurn` 会忽略此选项）\n\n`pi.sendUserMessage(content, { deliverAs })` 始终通过提示流程；在流式传输期间，其作为 steer/follow-up 排队。\n\n## 2) 处理器上下文（`ExtensionContext`）\n\n处理器和工具 `execute` 接收包含以下内容的 `ctx`：\n\n- `ui`\n- `hasUI`\n- `cwd`\n- `sessionManager`（只读）\n- `modelRegistry`、`model`\n- `getContextUsage()`\n- `compact(...)`\n- `isIdle()`、`hasPendingMessages()`、`abort()`\n- `shutdown()`\n- `getSystemPrompt()`\n\n## 3) 命令上下文（`ExtensionCommandContext`）\n\n命令处理器额外获得：\n\n- `waitForIdle()`\n- `newSession(...)`\n- `switchSession(...)`\n- `branch(entryId)`\n- `navigateTree(targetId, { summarize })`\n- `reload()`\n\n将命令上下文用于会话控制流程；这些方法有意与通用事件处理器分离。\n\n## 事件接口（当前名称与行为）\n\n规范的事件联合类型和载荷类型定义于 `types.ts`。\n\n### 会话生命周期\n\n- `session_start`\n- `session_before_switch` / `session_switch`\n- `session_before_branch` / `session_branch`\n- `session_before_compact` / `session.compacting` / `session_compact`\n- `session_before_tree` / `session_tree`\n- `session_shutdown`\n\n可取消的预事件：\n\n- `session_before_switch` → `{ cancel?: boolean }`\n- `session_before_branch` → `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_before_compact` → `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session_before_tree` → `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n\n### 提示与轮次生命周期\n\n- `input`\n- `before_agent_start`\n- `context`\n- `agent_start` / `agent_end`\n- `turn_start` / `turn_end`\n- `message_start` / `message_update` / `message_end`\n\n### 工具生命周期\n\n- `tool_call`（执行前，可阻止）\n- `tool_result`（执行后，可修补 content/details/isError）\n- `tool_execution_start` / `tool_execution_update` / `tool_execution_end`（可观测性）\n\n`tool_result` 为中间件风格：处理器按扩展顺序运行，每个处理器均可看到之前的修改。\n\n### 可靠性/运行时信号\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### 用户命令拦截\n\n- `user_bash`（通过 `{ result }` 覆盖）\n- `user_python`（通过 `{ result }` 覆盖）\n\n### `resources_discover`\n\n`resources_discover` 存在于扩展类型和 `ExtensionRunner` 中。\n当前运行时说明：`ExtensionRunner.emitResourcesDiscover(...)` 已实现，但当前代码库中没有 `AgentSession` 调用点对其进行调用。\n\n## 工具编写详情\n\n`registerTool` 使用来自 `types.ts` 的 `ToolDefinition`。\n\n当前 `execute` 签名：\n\n```ts\nexecute(\n toolCallId,\n params,\n signal,\n onUpdate,\n ctx,\n): Promise<AgentToolResult>\n```\n\n模板：\n\n```ts\npi.registerTool({\n name: \"my_tool\",\n label: \"My Tool\",\n description: \"...\",\n parameters: Type.Object({}),\n async execute(_id, _params, signal, onUpdate, ctx) {\n  if (signal?.aborted) {\n   return { content: [{ type: \"text\", text: \"Cancelled\" }] };\n  }\n  onUpdate?.({ content: [{ type: \"text\", text: \"Working...\" }] });\n  return { content: [{ type: \"text\", text: \"Done\" }], details: {} };\n },\n onSession(event, ctx) {\n  // reason: start|switch|branch|tree|shutdown\n },\n renderCall(args, theme) {\n  // optional TUI render\n },\n renderResult(result, options, theme, args) {\n  // optional TUI render\n },\n});\n```\n\n`tool_call`/`tool_result` 在 `sdk.ts` 中将注册表包装后，会拦截所有工具，包括内置工具和扩展/自定义工具。\n\n## UI 集成点\n\n`ctx.ui` 实现了 `ExtensionUIContext` 接口。不同模式下的支持情况有所差异。\n\n### 交互模式（`extension-ui-controller.ts`）\n\n支持：\n\n- 对话框：`select`、`confirm`、`input`、`editor`\n- 通知/状态/编辑器文本/终端输入/自定义覆盖层\n- 按名称列出/加载主题（`setTheme` 支持字符串名称）\n- 工具展开切换\n\n此控制器中当前为空操作的方法：\n\n- `setFooter`\n- `setHeader`\n- `setEditorComponent`\n\n另请注意：`setWidget` 当前通过 `setHookWidget(...)` 路由至状态栏文本。\n\n### RPC 模式（`rpc-mode.ts`）\n\n`ctx.ui` 由 RPC `extension_ui_request` 事件驱动：\n\n- 对话框方法（`select`、`confirm`、`input`、`editor`）往返于客户端响应\n- 即发即忘方法发出请求（`notify`、`setStatus`、字符串数组的 `setWidget`、`setTitle`、`setEditorText`）\n\nRPC 实现中不支持/为空操作的方法：\n\n- `onTerminalInput`\n- `custom`\n- `setFooter`、`setHeader`、`setEditorComponent`\n- `setWorkingMessage`\n- 主题切换/加载（`setTheme` 返回失败）\n- 工具展开控件无效\n\n### 打印/无头/子代理路径\n\n当运行器初始化时未提供 UI 上下文，`ctx.hasUI` 为 `false`，方法为空操作/返回默认值。\n\n### 后台交互模式\n\n后台模式安装非交互式 UI 上下文对象。在当前实现中，`ctx.hasUI` 仍可能为 `true`，而交互式对话框返回默认值/空操作行为。\n\n## 会话与状态模式\n\n对于持久化扩展状态：\n\n1. 使用 `pi.appendEntry(customType, data)` 进行持久化。\n2. 在 `session_start`、`session_branch`、`session_tree` 时，通过 `ctx.sessionManager.getBranch()` 重建状态。\n3. 当状态需要从工具结果历史中可见/可重建时，保持工具结果 `details` 的结构化。\n\n状态重建示例模式：\n\n```ts\npi.on(\"session_start\", async (_event, ctx) => {\n let latest;\n for (const entry of ctx.sessionManager.getBranch()) {\n  if (entry.type === \"custom\" && entry.customType === \"my-state\") {\n   latest = entry.data;\n  }\n }\n // restore from latest\n});\n```\n\n## 渲染扩展点\n\n## 自定义消息渲染器\n\n```ts\npi.registerMessageRenderer(\"my-type\", (message, { expanded }, theme) => {\n // return pi-tui Component\n});\n```\n\n在显示自定义消息时由交互式渲染使用。\n\n## 工具调用/结果渲染器\n\n在 `registerTool` 定义上提供 `renderCall` / `renderResult`，用于在 TUI 中自定义工具可视化。\n\n## 约束与注意事项\n\n- 运行时动作在扩展加载期间不可用。\n- `tool_call` 错误会阻止执行（失败关闭）。\n- 与内置命令名称冲突的命令会被跳过并输出诊断信息。\n- 保留的快捷键会被忽略（`ctrl+c`、`ctrl+d`、`ctrl+z`、`ctrl+k`、`ctrl+p`、`ctrl+l`、`ctrl+o`、`ctrl+t`、`ctrl+g`、`shift+tab`、`shift+ctrl+p`、`alt+enter`、`escape`、`enter`）。\n- 将 `ctx.reload()` 视为当前命令处理器帧的终止操作。\n\n## 扩展 vs 钩子 vs 自定义工具\n\n请使用正确的接口：\n\n- **扩展**（`src/extensibility/extensions/*`）：统一系统（事件 + 工具 + 命令 + 渲染器 + 提供者注册）。\n- **钩子**（`src/extensibility/hooks/*`）：独立的旧版事件 API。\n- **自定义工具**（`src/extensibility/custom-tools/*`）：以工具为中心的模块；与扩展一同加载时，它们会被适配，并仍通过扩展拦截包装器。\n\n如果您需要一个统一管理策略、工具、命令 UX 和渲染的包，请使用扩展。\n",
	"zh-cn/extensions/gemini-manifest-extensions.md": "---\ntitle: Gemini 清单扩展\ndescription: 用于跨平台技能和代理兼容性的 Gemini 清单扩展格式。\nsidebar:\n  order: 7\n  label: Gemini 清单\ni18n:\n  sourceHash: 7134165a5f6d\n  translator: machine\n---\n\n# Gemini 清单扩展（`gemini-extension.json`）\n\n本文档介绍编码代理如何发现并将 Gemini 风格的清单扩展（`gemini-extension.json`）解析为 `extensions` 能力。\n\n本文档**不**涵盖 TypeScript/JavaScript 扩展模块加载（`extensions/*.ts`、`index.ts`、`package.json xcsh.extensions`），相关内容记录于 `extension-loading.md`。\n\n## 实现文件\n\n- [`../src/discovery/gemini.ts`](../../packages/coding-agent/src/discovery/gemini.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/capability/extension.ts`](../../packages/coding-agent/src/capability/extension.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/loader.ts`](../../packages/coding-agent/src/extensibility/extensions/loader.ts)\n\n---\n\n## 发现的内容\n\nGemini 提供者（`id: gemini`，优先级 `60`）注册一个 `extensions` 加载器，扫描两个固定根目录：\n\n- 用户级：`~/.gemini/extensions`\n- 项目级：`<cwd>/.gemini/extensions`\n\n路径解析通过 `getUserPath()` / `getProjectPath()` 直接从 `ctx.home` 和 `ctx.cwd` 获取。\n\n重要的作用域规则：项目查找**仅限于当前工作目录**，不会向上遍历父目录。\n\n---\n\n## 目录扫描规则\n\n对于每个根目录（`~/.gemini/extensions` 和 `<cwd>/.gemini/extensions`），发现过程执行以下操作：\n\n1. `readDirEntries(root)`\n2. 仅保留直接子目录（`entry.isDirectory()`）\n3. 对于每个子目录 `<name>`，尝试精确读取：\n   - `<root>/<name>/gemini-extension.json`\n\n不会在一级目录以外进行递归扫描。\n\n### 隐藏目录\n\nGemini 清单发现**不**过滤以点号开头的目录名。如果某个隐藏子目录存在且包含 `gemini-extension.json`，则该目录会被纳入考虑。\n\n### 缺失/不可读文件\n\n如果 `gemini-extension.json` 缺失或不可读，该目录会被静默跳过（不发出警告）。\n\n---\n\n## 清单结构（按实现定义）\n\n能力类型定义了以下清单结构：\n\n```ts\ninterface ExtensionManifest {\n name?: string;\n description?: string;\n mcpServers?: Record<string, Omit<MCPServer, \"name\" | \"_source\">>;\n tools?: unknown[];\n context?: unknown;\n}\n```\n\n发现阶段的行为有意保持宽松：\n\n- 需要 JSON 解析成功。\n- 除 JSON 语法外，不对字段类型/内容进行运行时模式验证。\n- 解析后的对象作为 `manifest` 存储于能力条目上。\n\n### 名称规范化\n\n`Extension.name` 设置为：\n\n1. 若 `manifest.name` 不为 `null`/`undefined`，则使用该值\n2. 否则使用扩展目录名\n\n此处不强制执行字符串类型检查。\n\n---\n\n## 物化为能力条目\n\n经过有效解析的清单会创建一个 `Extension` 能力条目：\n\n```ts\n{\n name: manifest.name ?? <directory-name>,\n path: <extension-directory>,\n manifest: <parsed-json>,\n level: \"user\" | \"project\",\n _source: {\n  provider: \"gemini\",\n  providerName: \"Gemini CLI\" // 由能力注册表附加\n  path: <absolute-manifest-path>,\n  level: \"user\" | \"project\"\n }\n}\n```\n\n说明：\n\n- `_source.path` 由 `createSourceMeta()` 规范化为绝对路径。\n- `extensions` 的注册表级能力验证仅检查 `name` 和 `path` 是否存在。\n- 清单内部字段（`mcpServers`、`tools`、`context`）在发现阶段不进行验证。\n\n---\n\n## 错误处理与警告语义\n\n### 发出警告\n\n- 清单文件中存在无效 JSON：\n  - 警告格式：`Invalid JSON in <manifestPath>`\n\n### 不发出警告（静默跳过）\n\n- `extensions` 目录缺失\n- 子目录中不存在 `gemini-extension.json`\n- 清单文件不可读\n- 清单 JSON 语法有效但语义异常/不完整\n\n这意味着接受部分有效性：仅语法 JSON 失败才会发出警告。\n\n---\n\n## 与其他来源的优先级和去重\n\n`extensions` 能力由能力注册表跨提供者聚合。\n\n当前支持此能力的提供者：\n\n- `native`（`packages/coding-agent/src/discovery/builtin.ts`）优先级 `100`\n- `gemini`（`packages/coding-agent/src/discovery/gemini.ts`）优先级 `60`\n\n去重键为 `ext.name`（`extensionCapability.key = ext => ext.name`）。\n\n### 跨提供者优先级\n\n重复扩展名时，优先级更高的提供者获胜。\n\n- 若 `native` 和 `gemini` 都发出扩展名 `foo`，则保留 native 条目。\n- 低优先级的重复条目仅保留在 `result.all` 中，并标记 `_shadowed = true`。\n\n### 提供者内部顺序影响\n\n由于去重策略为\"先出现者获胜\"，提供者内部的条目顺序至关重要。\n\n- Gemini 加载器先追加**用户级**，再追加**项目级**。\n- 因此，`~/.gemini/extensions` 和 `<cwd>/.gemini/extensions` 之间重复的名称会保留用户级条目，项目级条目被遮蔽。\n\n相比之下，native 提供者通过 `getConfigDirs()` 以不同顺序构建配置目录（`project` 先于 `user`），因此 native 提供者内部的遮蔽方向相反。\n\n---\n\n## 用户级与项目级行为摘要\n\n对于 Gemini 清单：\n\n- 每次加载时都会扫描用户级和项目级根目录。\n- 项目级根目录固定为 `<cwd>/.gemini/extensions`（不进行祖先目录遍历）。\n- Gemini 来源内部的重复名称以用户级优先解析。\n- 与更高优先级提供者（尤其是 native）的重复名称会因优先级较低而落败。\n\n---\n\n## 边界：发现元数据与运行时扩展加载\n\n`gemini-extension.json` 发现当前为能力元数据（`Extension` 条目）提供数据，**不**直接加载可运行的 TS/JS 扩展模块。\n\n运行时模块加载（`discoverAndLoadExtensions()` / `loadExtensions()`）使用 `extension-modules` 和显式路径，并且当前仅将自动发现的模块过滤为提供者 `native`。\n\n实际影响：\n\n- Gemini 清单扩展可作为能力记录被发现。\n- 它们本身不会被扩展加载器管道作为运行时扩展模块执行。\n\n这一边界在当前实现中是有意为之的，解释了为何清单发现与可执行模块加载可能存在差异。\n",
	"zh-cn/extensions/marketplace.md": "---\ntitle: 插件市场系统\ndescription: 用于发现、安装和管理精选插件集合的插件市场系统。\nsidebar:\n  order: 4\n  label: 插件市场\ni18n:\n  sourceHash: 71d9f8f93a81\n  translator: machine\n---\n\n# 插件市场系统\n\n插件市场系统允许您从 Git 托管的目录中发现、安装和管理插件。它与 Claude Code 插件注册表格式兼容。\n\n## 快速开始\n\n```\n/marketplace add anthropics/f5-sales-demo-marketplace\n/marketplace install wordpress.com@f5-sales-demo-marketplace\n```\n\n或者直接输入 `/marketplace`（不带任何参数）以打开交互式插件浏览器。\n\n## 核心概念\n\n**插件市场**是一个 Git 仓库（或本地目录），其中在 `.xcsh-plugin/marketplace.json` 路径下包含一个目录文件。该目录列出了可用插件及其来源、描述和元数据。\n\n**插件**是一个包含技能、命令、钩子、MCP 服务器或 LSP 服务器的目录。插件通过 `name@marketplace` 格式进行标识（例如 `code-review@f5-sales-demo-marketplace`）。\n\n**作用域**：插件可以在两个作用域下安装：\n\n- **user**（默认）—— 在所有项目中可用，存储于 `~/.xcsh/plugins/installed_plugins.json`\n- **project** —— 仅在当前项目中可用，存储于 `.xcsh/installed_plugins.json`\n\n项目级安装会覆盖同名插件的用户级安装。\n\n## 命令\n\n### 交互模式\n\n| 命令 | 效果 |\n|---|---|\n| `/marketplace` | 打开交互式插件浏览器（安装） |\n\n### 插件市场管理\n\n| 命令 | 效果 |\n|---|---|\n| `/marketplace add <source>` | 添加插件市场来源 |\n| `/marketplace remove <name>` | 移除插件市场 |\n| `/marketplace update [name]` | 重新获取目录；省略名称则更新全部 |\n| `/marketplace list` | 列出已配置的插件市场 |\n\n### 插件操作\n\n| 命令 | 效果 |\n|---|---|\n| `/marketplace discover [marketplace]` | 浏览可用插件 |\n| `/marketplace install [--force] [--scope user\\|project] name@marketplace` | 安装插件 |\n| `/marketplace uninstall [--scope user\\|project] name@marketplace` | 卸载插件 |\n| `/marketplace installed` | 列出已安装的插件市场插件 |\n| `/marketplace upgrade [--scope user\\|project] [name@marketplace]` | 升级一个或所有插件 |\n\n### CLI 等效命令\n\n相同的操作也可通过命令行执行：\n\n```\nxcsh plugin marketplace add <source>\nxcsh plugin marketplace remove <name>\nxcsh plugin marketplace update [name]\nxcsh plugin marketplace list\nxcsh plugin discover [marketplace]\nxcsh plugin install --scope project name@marketplace\n```\n\n## 插件市场来源\n\n当您运行 `/marketplace add <source>` 时，系统会对来源进行分类：\n\n| 来源格式 | 类型 | 示例 |\n|---|---|---|\n| `owner/repo` | GitHub 简写 | `anthropics/f5-sales-demo-marketplace` |\n| `https://...*.json` | 直接目录 URL | `https://example.com/marketplace.json` |\n| `https://...*.git` 或 `git@...` | Git 仓库 | `https://github.com/org/repo.git` |\n| `./path` 或 `~/path` 或 `/path` | 本地目录 | `./my-marketplace` |\n\n系统会克隆仓库（或读取本地目录），定位 `.xcsh-plugin/marketplace.json`，对其进行验证，并在本地缓存目录。\n\n## 目录格式（marketplace.json）\n\n插件市场目录位于仓库根目录的 `.xcsh-plugin/marketplace.json`：\n\n```json\n{\n  \"$schema\": \"https://anthropic.com/claude-code/marketplace.schema.json\",\n  \"name\": \"my-marketplace\",\n  \"owner\": {\n    \"name\": \"Your Name\",\n    \"email\": \"you@example.com\"\n  },\n  \"description\": \"A collection of plugins\",\n  \"plugins\": [\n    {\n      \"name\": \"my-plugin\",\n      \"description\": \"What this plugin does\",\n      \"source\": \"./plugins/my-plugin\",\n      \"category\": \"development\",\n      \"homepage\": \"https://github.com/you/my-plugin\"\n    }\n  ]\n}\n```\n\n### 必填字段\n\n| 字段 | 描述 |\n|---|---|\n| `name` | 插件市场名称。由小写字母、数字、连字符和点号组成。必须以字母或数字开头和结尾。最多 64 个字符。 |\n| `owner.name` | 插件市场所有者名称 |\n| `plugins` | 插件条目数组 |\n\n### 插件条目字段\n\n| 字段 | 是否必填 | 描述 |\n|---|---|---|\n| `name` | 是 | 插件名称（规则与插件市场名称相同） |\n| `source` | 是 | 插件获取路径（见下文） |\n| `description` | 否 | 简短描述 |\n| `version` | 否 | 版本字符串 |\n| `author` | 否 | `{ name, email? }` |\n| `homepage` | 否 | URL |\n| `category` | 否 | 分类字符串（例如 `development`、`productivity`、`security`） |\n| `tags` | 否 | 字符串标签数组 |\n| `strict` | 否 | 布尔值 |\n| `commands` | 否 | 提供的斜杠命令 |\n| `agents` | 否 | 提供的代理 |\n| `hooks` | 否 | 钩子定义 |\n| `mcpServers` | 否 | MCP 服务器定义 |\n| `lspServers` | 否 | LSP 服务器定义 |\n\n### 插件来源格式\n\n`source` 字段支持以下几种格式：\n\n**相对路径**（插件市场仓库内）：\n\n```json\n\"source\": \"./plugins/my-plugin\"\n```\n\n**Git 仓库 URL**：\n\n```json\n\"source\": {\n  \"source\": \"url\",\n  \"url\": \"https://github.com/org/repo.git\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**GitHub 简写**：\n\n```json\n\"source\": {\n  \"source\": \"github\",\n  \"repo\": \"org/repo\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Git 子目录**（monorepo）：\n\n```json\n\"source\": {\n  \"source\": \"git-subdir\",\n  \"url\": \"https://github.com/org/monorepo.git\",\n  \"path\": \"plugins/my-plugin\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**npm 包**：\n\n```json\n\"source\": {\n  \"source\": \"npm\",\n  \"package\": \"@scope/my-plugin\",\n  \"version\": \"1.0.0\"\n}\n```\n\n## 磁盘目录结构\n\n```\n~/.xcsh/\n  config/\n    marketplaces.json          # 已添加插件市场的注册表\n  plugins/\n    installed_plugins.json     # 用户级已安装插件\n    cache/\n      marketplaces/            # 缓存的插件市场目录\n      plugins/                 # 缓存的插件目录\n\n<project>/.xcsh/\n  installed_plugins.json       # 项目级已安装插件\n```\n\n## 命名规则\n\n插件市场和插件名称必须满足以下条件：\n\n- 以小写字母或数字开头和结尾\n- 仅包含小写字母、数字、连字符和点号\n- 最多 64 个字符\n\n插件 ID（`name@marketplace`）总长度最多为 128 个字符。\n\n有效示例：`my-plugin`、`code-review`、`wordpress.com`、`ai-firstify`\n无效示例：`-bad`、`bad-`、`.bad`、`Bad`、`under_score`\n",
	"zh-cn/extensions/plugin-manager-installer-plumbing.md": "---\ntitle: 插件管理器与安装器内部机制\ndescription: 插件管理器内部机制，涵盖安装、验证、依赖解析和生命周期管理。\nsidebar:\n  order: 5\n  label: 插件管理器\ni18n:\n  sourceHash: 9c33e5a2c22a\n  translator: machine\n---\n\n# 插件管理器与安装器内部机制\n\n本文档描述了 `xcsh plugin` 操作如何修改磁盘上的插件状态，以及已安装的插件如何成为运行时能力（目前支持工具，钩子/命令路径解析可用）。\n\n## 范围与架构\n\n代码库中有两个插件管理实现：\n\n1. **CLI 命令使用的活跃路径**：`PluginManager`（`src/extensibility/plugins/manager.ts`）\n2. **旧版辅助模块**：安装器函数（`src/extensibility/plugins/installer.ts`）\n\n`xcsh plugin ...` 命令执行通过 `PluginManager` 进行。\n\n`installer.ts` 仍然记录了重要的安全检查和文件系统行为，但它不是 `src/commands/plugin.ts` + `src/cli/plugin-cli.ts` 所使用的路径。\n\n## 生命周期：从 CLI 调用到运行时可用\n\n```text\nxcsh plugin <action> ...\n  -> src/commands/plugin.ts\n  -> runPluginCommand(...) in src/cli/plugin-cli.ts\n  -> PluginManager method (install/list/uninstall/link/...) \n  -> mutate ~/.xcsh/plugins/{package.json,node_modules,xcsh-plugins.lock.json}\n  -> runtime discovery: discoverAndLoadCustomTools(...)\n  -> getAllPluginToolPaths(cwd)\n  -> custom tool loader imports tool modules\n```\n\n### 命令入口点\n\n- `src/commands/plugin.ts` 定义命令/标志并转发到 `runPluginCommand`。\n- `src/cli/plugin-cli.ts` 将子命令映射到 `PluginManager` 方法：\n  - `install`、`uninstall`、`list`、`link`、`doctor`、`features`、`config`、`enable`、`disable`\n- 不存在显式的 `update` 操作；更新通过使用新的包/版本规范重新运行 `install` 来完成。\n\n## 磁盘模型\n\n全局插件状态位于 `~/.xcsh/plugins` 下：\n\n- `package.json` — `bun install`/`bun uninstall` 使用的依赖清单\n- `node_modules/` — 已安装的插件包或符号链接\n- `xcsh-plugins.lock.json` — 运行时状态：\n  - 每个插件的启用/禁用状态\n  - 每个插件选定的功能集\n  - 持久化的插件设置\n\n项目本地覆盖位于：\n\n- `<cwd>/.xcsh/plugin-overrides.json`\n\n从管理器/加载器的角度来看，覆盖是只读的（此处没有写入路径），可以为当前项目禁用插件或覆盖功能/设置。\n\n## 插件规范解析与元数据解释\n\n## 安装规范语法\n\n`parsePluginSpec`（`parser.ts`）支持：\n\n- `pkg` -> `features: null`（默认行为）\n- `pkg[*]` -> 启用所有清单功能\n- `pkg[]` -> 不启用可选功能\n- `pkg[a,b]` -> 启用指定功能\n- `@scope/pkg@1.2.3[feat]` -> 带作用域 + 版本号的包，显式选择功能\n\n`extractPackageName` 去除版本后缀，用于安装后的磁盘路径查找。\n\n## 清单来源与必需字段\n\n清单按以下顺序解析：\n\n1. `package.json.xcsh`\n2. 回退到 `package.json.pi`\n3. 回退到 `{ version: package.version }`\n\n影响：\n\n- 管理器/加载器中没有严格的模式验证。\n- 缺少 `xcsh`/`pi` 的包仍然可以安装和列出。\n- 运行时插件加载（`getEnabledPlugins`）会跳过没有 `xcsh`/`pi` 清单的包。\n- `manifest.version` 始终从包的 `version` 字段覆盖。\n\n格式错误的 `package.json` JSON 在读取时会导致硬失败；格式错误的清单结构可能仅在使用特定字段时才会失败。\n\n## 安装/更新流程（`PluginManager.install`）\n\n1. 从安装规范中解析功能括号语法。\n2. 通过正则表达式 + shell 元字符拒绝列表验证包名。\n3. 确保插件 `package.json` 存在（`xcsh-plugins`，私有依赖映射）。\n4. 在 `~/.xcsh/plugins` 中运行 `bun install <packageSpec>`。\n5. 读取已安装包的 `node_modules/<name>/package.json`。\n6. 解析清单并计算 `enabledFeatures`：\n   - `[*]`：所有声明的功能（如果没有功能映射则为 `null`）\n   - `[a,b]`：验证每个功能是否存在于清单功能映射中\n   - `[]`：空功能列表\n   - 裸规范：`null`（稍后在加载器中使用默认策略）\n7. 更新或插入锁文件运行时状态：`{ version, enabledFeatures, enabled: true }`。\n\n### 更新语义\n\n由于更新是由安装驱动的：\n\n- `xcsh plugin install pkg@newVersion` 会更新依赖和锁文件版本。\n- 现有设置被保留；状态条目中的版本/功能/启用状态会被覆盖。\n- 不存在单独的\"检查更新\"或事务性迁移逻辑。\n\n## 移除流程（`PluginManager.uninstall`）\n\n1. 验证包名。\n2. 在插件目录中运行 `bun uninstall <name>`。\n3. 从锁文件中移除插件运行时状态：\n   - `config.plugins[name]`\n   - `config.settings[name]`\n\n如果卸载命令失败，运行时状态不会更改。\n\n## 列表流程（`PluginManager.list`）\n\n1. 从 `~/.xcsh/plugins/package.json` 读取插件依赖映射。\n2. 加载锁文件运行时配置（文件缺失 -> 空默认值）。\n3. 加载项目覆盖（`<cwd>/.xcsh/plugin-overrides.json`，解析/读取错误 -> 空对象并发出警告）。\n4. 对于每个具有可解析 package.json 的依赖：\n   - 构建 `InstalledPlugin` 记录\n   - 合并功能/启用状态：\n     - 基础状态来自锁文件（或默认值）\n     - 项目覆盖可以替换功能选择\n     - 项目的 `disabled` 列表将插件标记为禁用\n\n这是 CLI 状态输出和设置/功能操作使用的有效状态。\n\n## 链接流程（`PluginManager.link`）\n\n`link` 通过将本地包符号链接到 `~/.xcsh/plugins/node_modules/<pkg.name>` 来支持本地插件开发。\n\n行为：\n\n1. 根据管理器 cwd 解析 `localPath`。\n2. 要求本地 `package.json` 和 `name` 字段。\n3. 确保插件目录存在。\n4. 对于带作用域的名称，创建作用域目录。\n5. 移除目标链接位置的现有路径。\n6. 创建符号链接。\n7. 添加运行时锁文件条目，启用并使用默认功能（`null`）。\n\n注意：当前的 `PluginManager.link` 没有强制执行旧版 `installer.ts` 中存在的 `cwd` 路径边界检查（`normalizedPath.startsWith(normalizedCwd)`），因此信任由调用者负责。\n\n## 运行时加载：从已安装插件到可调用能力\n\n## 发现门控\n\n`getEnabledPlugins(cwd)`（`plugins/loader.ts`）读取：\n\n- 插件依赖清单（`package.json`）\n- 锁文件运行时状态\n- 通过 `getConfigDirPaths(\"plugin-overrides.json\", { user: false, cwd })` 获取的项目覆盖\n\n过滤条件：\n\n- 如果没有插件 package.json 则跳过\n- 如果清单（`xcsh`/`pi`）不存在则跳过\n- 如果在锁文件中全局禁用则跳过\n- 如果项目禁用则跳过\n\n## 能力路径解析\n\n对于每个已启用的插件：\n\n- `resolvePluginToolPaths(plugin)`\n- `resolvePluginHookPaths(plugin)`\n- `resolvePluginCommandPaths(plugin)`\n\n每个解析器包含基础条目和功能条目：\n\n- 显式功能列表 -> 仅选定的功能\n- `enabledFeatures === null` -> 启用标记为 `default: true` 的功能\n\n缺失的文件会被静默跳过（`existsSync` 守卫）。\n\n## 当前运行时连接差异\n\n- **工具目前已连接到运行时**，通过 `discoverAndLoadCustomTools`（`custom-tools/loader.ts`）调用 `getAllPluginToolPaths(cwd)`。\n- 路径在自定义工具发现中通过解析后的绝对路径进行去重（`seen` 集合，先到先得）。\n- **钩子/命令解析器已存在**并已导出，但此代码路径目前没有像工具那样将它们连接到运行时注册表中。\n\n## 锁/状态管理细节\n\n`PluginManager` 在每个实例的内存中缓存运行时配置（`#runtimeConfig`），并延迟加载一次。\n\n加载行为：\n\n- 锁文件缺失 -> `{ plugins: {}, settings: {} }`\n- 锁文件读取/解析失败 -> 警告 + 相同的空默认值\n\n保存行为：\n\n- 每次变更时写入完整的格式化锁文件 JSON\n\n不存在跨进程锁定或合并策略；并发写入者可能会相互覆盖。\n\n## 安全检查与信任边界\n\n## 输入/包验证\n\n活跃管理器路径强制执行包名验证：\n\n- 适用于带作用域/不带作用域包规范的正则表达式（可选带版本）\n- 显式 shell 元字符拒绝列表（`[;&|`$(){}[]<>\\\\]`）\n\n这限制了调用 `bun install/uninstall` 时的命令注入风险。\n\n## 文件系统信任边界\n\n- 插件代码在导入自定义工具模块时在进程内执行；没有沙箱隔离。\n- 清单相对路径与插件包目录拼接，仅进行存在性检查。\n- 插件包一旦安装即被视为受信任代码。\n\n## 仅限旧版安装器的检查\n\n`installer.ts` 包含未在 `PluginManager.link` 中镜像的额外链接时检查：\n\n- 本地路径必须解析到项目 cwd 内部\n- 针对符号链接目标命名的额外包名/路径穿越守卫\n\n由于 CLI 使用 `PluginManager`，这些更严格的链接守卫目前不在主路径上。\n\n## 失败、部分成功和回滚行为\n\n插件管理器不是事务性的。\n\n| 操作阶段 | 失败行为 | 回滚 |\n| --- | --- | --- |\n| `bun install` 失败 | 安装中止并输出 stderr | 不适用（尚未写入状态） |\n| 安装成功，但清单/功能验证失败 | 命令失败 | 不进行卸载回滚；依赖可能保留在 `node_modules`/`package.json` 中 |\n| 安装成功，但锁文件写入失败 | 命令失败 | 不回滚已安装的包 |\n| `bun uninstall` 成功，锁文件写入失败 | 命令失败 | 包已移除，过时的运行时状态可能保留 |\n| `link` 移除旧目标后符号链接创建失败 | 命令失败 | 不恢复之前的链接/目录 |\n\n在运维层面，`doctor --fix` 可以修复部分漂移（`bun install`、孤立配置清理、无效功能清理），但属于尽力而为。\n\n## 格式错误/缺失清单行为摘要\n\n- 缺失 `xcsh`/`pi` 字段：\n  - 安装/列表：可容忍（最小清单）\n  - 运行时已启用插件发现：作为非插件跳过\n- 安装规范或 `features --set/--enable` 引用了缺失的功能：硬错误并显示可用功能列表\n- 无效的 `plugin-overrides.json`：在管理器和加载器路径中均被忽略，回退到 `{}`\n- 清单引用的工具/钩子/命令文件路径缺失：在解析器展开期间静默忽略；仅由 `doctor` 标记为错误\n\n## 模式差异与优先级\n\n- `--dry-run`（安装）：返回合成的安装结果，不进行文件系统/网络/状态写入。\n- `--json`：仅影响输出格式，不改变行为。\n- 项目覆盖在功能/设置视图中始终优先于全局锁文件。\n- 有效启用状态为 `runtimeEnabled && !projectDisabled`。\n\n## 实现文件\n\n- [`src/commands/plugin.ts`](../../packages/coding-agent/src/commands/plugin.ts) — CLI 命令声明和标志映射\n- [`src/cli/plugin-cli.ts`](../../packages/coding-agent/src/cli/plugin-cli.ts) — 操作分发，面向用户的命令处理器\n- [`src/extensibility/plugins/manager.ts`](../../packages/coding-agent/src/extensibility/plugins/manager.ts) — 活跃的安装/移除/列表/链接/状态/诊断实现\n- [`src/extensibility/plugins/installer.ts`](../../packages/coding-agent/src/extensibility/plugins/installer.ts) — 旧版安装器辅助函数和额外的链接安全检查\n- [`src/extensibility/plugins/loader.ts`](../../packages/coding-agent/src/extensibility/plugins/loader.ts) — 已启用插件发现和工具/钩子/命令路径解析\n- [`src/extensibility/plugins/parser.ts`](../../packages/coding-agent/src/extensibility/plugins/parser.ts) — 安装规范和包名解析辅助函数\n- [`src/extensibility/plugins/types.ts`](../../packages/coding-agent/src/extensibility/plugins/types.ts) — 清单/运行时/覆盖类型契约\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts) — 插件提供的工具模块的运行时连接\n",
	"zh-cn/extensions/rulebook-matching-pipeline.md": "---\ntitle: 规则手册匹配流水线\ndescription: 用于选择和应用上下文特定指令集到代理会话的规则手册匹配流水线。\nsidebar:\n  order: 6\n  label: 规则手册匹配\ni18n:\n  sourceHash: a16a9c565053\n  translator: machine\n---\n\n# 规则手册匹配流水线\n\n本文档描述了 coding-agent 如何从支持的配置格式中发现规则、将其规范化为统一的 `Rule` 结构、解决优先级冲突，并将结果拆分为：\n\n- **规则手册规则**（通过系统提示词 + `rule://` URL 提供给模型）\n- **TTSR 规则**（时间回溯流中断规则）\n\n本文档反映了当前的实现，包括部分语义和已解析但未强制执行的元数据。\n\n## 实现文件\n\n- [`../src/capability/rule.ts`](../../packages/coding-agent/src/capability/rule.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/discovery/index.ts`](../../packages/coding-agent/src/discovery/index.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/cursor.ts`](../../packages/coding-agent/src/discovery/cursor.ts)\n- [`../src/discovery/windsurf.ts`](../../packages/coding-agent/src/discovery/windsurf.ts)\n- [`../src/discovery/cline.ts`](../../packages/coding-agent/src/discovery/cline.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/system-prompt.ts`](../../packages/coding-agent/src/system-prompt.ts)\n- [`../src/internal-urls/rule-protocol.ts`](../../packages/coding-agent/src/internal-urls/rule-protocol.ts)\n- [`../src/utils/frontmatter.ts`](../../packages/coding-agent/src/utils/frontmatter.ts)\n\n## 1. 规范的规则结构\n\n所有提供者将源文件规范化为 `Rule`：\n\n```ts\ninterface Rule {\n  name: string;\n  path: string;\n  content: string;\n  globs?: string[];\n  alwaysApply?: boolean;\n  description?: string;\n  ttsrTrigger?: string;\n  _source: SourceMeta;\n}\n```\n\n能力标识为 `rule.name`（`ruleCapability.key = rule => rule.name`）。\n\n结论：优先级和去重**仅基于名称**。两个不同的文件如果具有相同的 `name`，则被视为同一逻辑规则。\n\n## 2. 发现源和规范化\n\n`src/discovery/index.ts` 自动注册提供者。对于 `rules`，当前的提供者有：\n\n- `native`（优先级 `100`）\n- `cursor`（优先级 `50`）\n- `windsurf`（优先级 `50`）\n- `cline`（优先级 `40`）\n\n### Native 提供者（`builtin.ts`）\n\n从以下位置加载 `.xcsh` 规则：\n\n- 项目级：`<cwd>/.xcsh/rules/*.{md,mdc}`\n- 用户级：`~/.xcsh/agent/rules/*.{md,mdc}`\n\n规范化：\n\n- `name` = 去除 `.md`/`.mdc` 扩展名的文件名\n- 通过 `parseFrontmatter` 解析 frontmatter\n- `content` = 正文（已去除 frontmatter）\n- `globs`、`alwaysApply`、`description`、`ttsr_trigger` 直接映射\n\n重要注意事项：`globs` 在此提供者中被转换为 `string[] | undefined`，未进行元素过滤。\n\n### Cursor 提供者（`cursor.ts`）\n\n从以下位置加载：\n\n- 用户级：`~/.cursor/rules/*.{mdc,md}`\n- 项目级：`<cwd>/.cursor/rules/*.{mdc,md}`\n\n规范化（`transformMDCRule`）：\n\n- `description`：仅在为字符串时保留\n- `alwaysApply`：仅保留 `true`（`false` 变为 `undefined`）\n- `globs`：接受数组（仅字符串元素）或单个字符串\n- `ttsr_trigger`：仅字符串\n- `name` 来自去除扩展名的文件名\n\n### Windsurf 提供者（`windsurf.ts`）\n\n从以下位置加载：\n\n- 用户级：`~/.codeium/windsurf/memories/global_rules.md`（固定规则名 `global_rules`）\n- 项目级：`<cwd>/.windsurf/rules/*.md`\n\n规范化：\n\n- `globs`：字符串数组或单个字符串\n- `alwaysApply`、`description` 从 frontmatter 转换\n- `ttsr_trigger`：仅字符串\n- 项目规则的 `name` 来自文件名\n\n### Cline 提供者（`cline.ts`）\n\n从 `cwd` 向上搜索最近的 `.clinerules`：\n\n- 如果是目录：加载其中的 `*.md` 文件\n- 如果是文件：作为名为 `clinerules` 的单个规则加载\n\n规范化：\n\n- `globs`：字符串数组或单个字符串\n- `alwaysApply`：仅在为布尔值时保留\n- `description`：仅字符串\n- `ttsr_trigger`：仅字符串\n\n## 3. Frontmatter 解析行为和歧义\n\n所有提供者使用 `parseFrontmatter`（`utils/frontmatter.ts`），具有以下语义：\n\n1. 仅当内容以 `---` 开头并有结束的 `\\n---` 时才解析 frontmatter。\n2. 提取 frontmatter 后对正文进行修剪。\n3. 如果 YAML 解析失败：\n   - 记录警告，\n   - 解析器回退到简单的 `key: value` 逐行解析（`^(\\w+):\\s*(.*)$`）。\n\n歧义后果：\n\n- 回退解析器不支持数组、嵌套对象、引号规则或连字符键名。\n- 回退值变为字符串（例如 `alwaysApply: true` 变为字符串 `\"true\"`），因此要求布尔/字符串类型的提供者可能会丢弃元数据。\n- `ttsr_trigger` 在回退模式下可用（下划线键名）；像 `thinking-level` 这样的键则不可用。\n- 没有有效 frontmatter 的文件仍会作为具有空元数据和完整内容正文的规则加载。\n\n## 4. 提供者优先级和去重\n\n`loadCapability(\"rules\")`（`capability/index.ts`）合并提供者输出，然后按 `rule.name` 去重。\n\n### 优先级模型\n\n- 提供者按优先级降序排列。\n- 相同优先级保持注册顺序（`cursor` 在 `windsurf` 之前，来自 `discovery/index.ts`）。\n- 去重采用先到先得：首次遇到的规则名被保留；后续同名项在 `all` 中标记为 `_shadowed`，并从 `items` 中排除。\n\n当前有效的规则提供者顺序为：\n\n1. `native`（100）\n2. `cursor`（50）\n3. `windsurf`（50）\n4. `cline`（40）\n\n### 提供者内部排序注意事项\n\n在提供者内部，项目顺序来自 `loadFilesFromDir` 的 glob 结果排序加上显式的 push 顺序。这对于正常使用足够确定性，但代码中没有显式排序。\n\n值得注意的源顺序差异：\n\n- `native` 先追加项目目录，后追加用户配置目录。\n- `cursor` 先追加用户目录结果，后追加项目结果。\n- `windsurf` 先追加用户级 `global_rules`，后追加项目规则。\n- `cline` 仅加载最近的 `.clinerules` 源。\n\n## 5. 拆分为规则手册、始终应用和 TTSR 分类\n\n在 `createAgentSession`（`sdk.ts`）中完成规则发现后：\n\n1. 扫描所有已发现的规则。\n2. 具有 `condition`（frontmatter 键；`ttsr_trigger` / `ttsrTrigger` 作为回退接受）的规则被注册到 `TtsrManager`。\n3. 使用以下谓词构建单独的 `rulebookRules` 列表：\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && !rule.alwaysApply && !!rule.description\n```\n\n4. 构建 `alwaysApplyRules` 列表：\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && rule.alwaysApply === true\n```\n\n### 分类行为\n\n- **TTSR 分类**：任何具有 `condition` 的规则（不要求 description）。优先于其他分类。\n- **始终应用分类**：`alwaysApply === true`，非 TTSR。完整内容注入系统提示词。可通过 `rule://` 解析。\n- **规则手册分类**：必须有 description，不能是 TTSR，不能是 `alwaysApply`。在系统提示词中按名称+描述列出；内容通过 `rule://` 按需读取。\n- 同时具有 `condition` 和 `alwaysApply` 的规则仅归入 TTSR（TTSR 优先）。\n- 同时具有 `alwaysApply` 和 `description` 的规则仅归入始终应用分类（不归入规则手册）。\n\n## 6. 元数据如何影响运行时表现\n\n### `description`\n\n- 归入规则手册的必要条件。\n- 在系统提示词 `<rules>` 块中渲染。\n- 缺少 description 意味着规则不可通过 `rule://` 访问，也不会在系统提示词规则中列出。\n\n### `globs`\n\n- 在 `Rule` 上透传。\n- 在系统提示词规则块中渲染为 `<glob>...</glob>` 条目。\n- 在规则 UI 状态中暴露（`extensions` 模式列表）。\n- **在此流水线中不强制执行自动匹配。** 没有运行时 glob 匹配器根据当前文件/工具目标选择规则。\n\n### `alwaysApply`\n\n- 由提供者解析和保留。\n- 用于 UI 显示（扩展状态管理器中的 `\"always\"` 触发标签）。\n- 用作从 `rulebookRules` 中排除的条件。\n- **完整规则内容自动注入系统提示词**（在规则手册规则部分之前）。\n- 规则也可通过 `rule://<name>` 进行重新读取。\n\n### `ttsr_trigger`\n\n- 映射到 `rule.ttsrTrigger`。\n- 如果存在，规则被路由到 TTSR 管理器，而非规则手册。\n\n## 7. 系统提示词包含路径\n\n`buildSystemPromptInternal` 接收 `rules`（规则手册）和 `alwaysApplyRules`。\n\n始终应用规则首先渲染，将其原始内容直接注入提示词。\n\n规则手册规则在 `# Rules` 部分中渲染，包含：\n\n- `Read rule://<name> when working in matching domain`\n- 每个规则的 `name`、`description` 和可选的 `<glob>` 列表\n\n这是建议性/上下文性的：提示词文本要求模型读取适用的规则，但代码不强制执行 glob 适用性。\n\n## 8. `rule://` 内部 URL 行为\n\n`RuleProtocolHandler` 注册方式如下：\n\n```ts\nnew RuleProtocolHandler({ getRules: () => [...rulebookRules, ...alwaysApplyRules] })\n```\n\n影响：\n\n- `rule://<name>` 同时解析 **rulebookRules** 和 **alwaysApplyRules**。\n- 仅 TTSR 的规则和没有 description 且没有 `alwaysApply` 的规则不可通过 `rule://` 访问。\n- 解析为精确名称匹配。\n- 未知名称返回错误，列出可用的规则名称。\n- 返回的内容为原始 `rule.content`（已去除 frontmatter），内容类型为 `text/markdown`。\n\n## 9. 已知的部分/未强制执行的语义\n\n1. 提供者描述中提到了旧版文件（`.cursorrules`、`.windsurfrules`），但当前加载器代码路径实际上不会读取这些文件。\n2. `globs` 元数据在提示词/UI 中展示，但规则选择逻辑不强制执行。\n3. `rule://` 的规则选择包含规则手册和始终应用规则，但不包含仅 TTSR 的规则。\n4. 发现警告（`loadCapability(\"rules\").warnings`）会生成，但 `createAgentSession` 目前在此路径中不会展示/记录它们。\n",
	"zh-cn/extensions/skills.md": "---\ntitle: 技能\ndescription: 技能系统，用于在编码代理中注册、发现和调用专用能力。\nsidebar:\n  order: 3\n  label: 技能\ni18n:\n  sourceHash: 3e062cc13851\n  translator: machine\n---\n\n# 技能\n\n技能是基于文件的能力包，在启动时被发现，并以以下方式暴露给模型：\n\n- 系统提示中的轻量级元数据（名称 + 描述）\n- 通过 `read skill://...` 按需获取内容\n- 可选的交互式 `/skill:<name>` 命令\n\n本文档涵盖 `src/extensibility/skills.ts`、`src/discovery/builtin.ts`、`src/internal-urls/skill-protocol.ts` 和 `src/discovery/agents-md.ts` 中的当前运行时行为。\n\n## 本代码库中技能的定义\n\n一个被发现的技能表示为：\n\n- `name`\n- `description`\n- `filePath`（`SKILL.md` 路径）\n- `baseDir`（技能目录）\n- 来源元数据（`provider`、`level`、路径）\n\n运行时只需要 `name` 和 `path` 即可视为有效。实际上，匹配质量取决于 `description` 是否有意义。\n\n## 必需的目录布局和 SKILL.md 要求\n\n### 目录布局\n\n对于基于提供者的发现（native/Claude/Codex/Agents/plugin 提供者），技能以 **`skills/` 下一级** 的方式被发现：\n\n- `<skills-root>/<skill-name>/SKILL.md`\n\n像 `<skills-root>/group/<skill>/SKILL.md` 这样的嵌套模式不会被提供者加载器发现。\n\n对于 `skills.customDirectories`，扫描使用相同的非递归布局（`*/SKILL.md`）。\n\n```text\nProvider-discovered layout (non-recursive under skills/):\n\n<root>/skills/\n  ├─ postgres/\n  │   └─ SKILL.md      ✅ discovered\n  ├─ pdf/\n  │   └─ SKILL.md      ✅ discovered\n  └─ team/\n      └─ internal/\n          └─ SKILL.md  ❌ not discovered by provider loaders\n\nCustom-directory scanning is also non-recursive, so nested paths are ignored unless you point `customDirectories` at that nested parent.\n```\n\n### `SKILL.md` 前置元数据\n\n技能类型支持的前置元数据字段：\n\n- `name?: string`\n- `description?: string`\n- `globs?: string[]`\n- `alwaysApply?: boolean`\n- 其他键作为未知元数据保留\n\n当前运行时行为：\n\n- `name` 默认为技能目录名称\n- `description` 在以下情况下是必需的：\n  - 原生 `.xcsh` 提供者技能发现（`requireDescription: true`）\n  - 通过 `src/discovery/helpers.ts` 中的 `scanSkillsFromDir` 进行 `skills.customDirectories` 扫描（非递归）\n- 非原生提供者可以加载没有描述的技能\n\n## 发现流程\n\n`src/extensibility/skills.ts` 中的 `discoverSkills()` 执行两个阶段：\n\n1. **能力提供者** 通过 `loadCapability(\"skills\")`\n2. **自定义目录** 通过 `scanSkillsFromDir(..., { requireDescription: true })`（一级目录枚举）\n\n如果 `skills.enabled` 为 `false`，发现不会返回任何技能。\n\n### 内置技能提供者和优先级\n\n提供者排序以优先级为先（高优先级胜出），相同优先级按注册顺序。\n\n当前已注册的技能提供者：\n\n1. `native`（优先级 100）— 通过 `src/discovery/builtin.ts` 提供的 `.xcsh` 用户/项目技能\n2. `claude`（优先级 80）\n3. 优先级 70 组（按注册顺序）：\n   - `claude-plugins`\n   - `agents`\n   - `codex`\n\n去重键为技能名称。具有相同名称的第一个条目胜出。\n\n### 来源开关和过滤\n\n`discoverSkills()` 应用以下控制：\n\n- 来源开关：`enableCodexUser`、`enableClaudeUser`、`enableClaudeProject`、`enablePiUser`、`enablePiProject`\n- 基于技能名称的 glob 过滤器：\n  - `ignoredSkills`（排除）\n  - `includeSkills`（包含白名单；为空表示包含全部）\n\n过滤顺序为：\n\n1. 来源已启用\n2. 未被忽略\n3. 已包含（如果存在包含列表）\n\n对于 codex/claude/native 以外的提供者（例如 `agents`、`claude-plugins`），启用状态当前回退为：如果**任何**内置来源开关启用则启用。\n\n### 冲突和重复处理\n\n- 能力去重已经按名称保留第一个技能（最高优先级提供者）\n- `extensibility/skills.ts` 还会：\n  - 通过 `realpath` 对相同文件进行去重（符号链接安全）\n  - 当后续技能名称冲突时发出冲突警告\n  - 保留便捷的 `discoverSkillsFromDir({ dir, source })` API 作为 `scanSkillsFromDir` 的简单适配器\n- 自定义目录技能在提供者技能之后合并，遵循相同的冲突行为\n\n## 运行时使用行为\n\n### 系统提示暴露\n\n系统提示构建（`src/system-prompt.ts`）按如下方式使用已发现的技能：\n\n- 如果 `read` 工具可用：\n  - 在提示中包含已发现的技能列表\n- 否则：\n  - 省略已发现的列表\n\n任务工具子代理通过正常的会话创建接收会话的已发现/已提供技能列表；没有每任务的技能固定覆盖。\n\n### 交互式 `/skill:<name>` 命令\n\n如果 `skills.enableSkillCommands` 为 true，交互模式会为每个已发现的技能注册一个斜杠命令。\n\n`/skill:<name> [args]` 行为：\n\n- 直接从 `filePath` 读取技能文件\n- 去除前置元数据\n- 将技能主体作为后续自定义消息注入\n- 附加元数据（`Skill: <path>`，可选 `User: <args>`）\n\n## `skill://` URL 行为\n\n`src/internal-urls/skill-protocol.ts` 支持：\n\n- `skill://<name>` → 解析为该技能的 `SKILL.md`\n- `skill://<name>/<relative-path>` → 解析为该技能目录内的路径\n\n```text\nskill:// URL resolution\n\nskill://pdf\n  -> <pdf-base>/SKILL.md\n\nskill://pdf/references/tables.md\n  -> <pdf-base>/references/tables.md\n\nGuards:\n- reject absolute paths\n- reject `..` traversal\n- reject any resolved path escaping <pdf-base>\n```\n\n解析详情：\n\n- 技能名称必须精确匹配\n- 相对路径经过 URL 解码\n- 绝对路径被拒绝\n- 路径遍历（`..`）被拒绝\n- 解析后的路径必须保持在 `baseDir` 内\n- 缺失的文件返回明确的 `File not found` 错误\n\n内容类型：\n\n- `.md` => `text/markdown`\n- 其他所有文件 => `text/plain`\n\n不会对缺失的资源执行回退搜索。\n\n## 技能与 XCSH.md、命令、工具、钩子的对比\n\n### 技能与 XCSH.md\n\n- **技能**：命名的、可选的能力包，根据任务上下文选择或显式请求\n- **XCSH.md/上下文文件**：持久化的指令文件，作为上下文文件能力加载，并按级别/深度规则合并\n\n`src/discovery/agents-md.ts` 专门从 `cwd` 向上遍历祖先目录来发现独立的 `XCSH.md` 文件（最多深度 20），排除隐藏目录段。\n\n### 技能与斜杠命令\n\n- **技能**：模型可读的知识/工作流内容\n- **斜杠命令**：用户调用的命令入口点\n- `/skill:<name>` 是一个便捷包装器，注入技能文本；它不改变技能发现语义\n\n### 技能与自定义工具\n\n- **技能**：通过提示上下文和 `read` 加载的文档/工作流内容\n- **自定义工具**：模型可调用的可执行工具 API，具有模式定义和运行时副作用\n\n### 技能与钩子\n\n- **技能**：被动内容\n- **钩子**：事件驱动的运行时拦截器，可以在执行期间阻止/修改行为\n\n## 与发现逻辑相关的实用编写指南\n\n- 将每个技能放在自己的目录中：`<skills-root>/<skill-name>/SKILL.md`\n- 始终包含明确的 `name` 和 `description` 前置元数据\n- 将引用的资源放在同一技能目录下，并通过 `skill://<name>/...` 访问\n- 对于嵌套分类（`team/domain/skill`），将 `skills.customDirectories` 指向嵌套的父目录；扫描本身仍然是非递归的\n- 避免跨来源的重复技能名称；第一个匹配项按提供者优先级胜出\n",
	"zh-cn/index.md": "---\ntitle: xcsh 文档\ndescription: AI 驱动的开发 CLI，集成 TypeScript 编码代理和 Rust 原生层，支持长期会话、MCP 支持和平台打包。\nsidebar:\n  order: 0\n  label: 概述\ni18n:\n  sourceHash: b9288f42bf46\n  translator: machine\n---\n\nxcsh 是一个 AI 驱动的开发 CLI，集成了 TypeScript 编码代理和\nRust 原生层（`pi-natives`）。它扩展了开源项目\n[`badlogic/pi-mono`](https://github.com/badlogic/pi-mono)，提供了\n加固的运行时、支持树形导航和压缩的长期会话、\nPython IPython 工具、完整的 MCP 支持、技能系统，以及面向\nLinux、macOS 和 Windows 的平台打包。\n\n## 从哪里开始\n\n- **[F5 XC 上下文](/runtime-tools/context-command)** — 连接到 F5 Distributed Cloud\n  租户。创建上下文、在上下文间切换、管理命名空间和凭据。\n- **配置** — xcsh 如何发现、解析和分层配置。\n- **运行时与工具** — bash / notebook / resolve 工具运行时以及\n  斜杠命令接口。\n- **会话** — 仅追加的条目日志、树形导航、压缩，以及\n  自主记忆系统。\n- **原生层 (Rust)** — `pi-natives` N-API 插件的架构，\n  驱动 shell / PTY / 媒体 / 搜索功能。\n- **MCP** — 配置、协议内部机制、运行时生命周期，以及如何\n  编写服务器和工具。\n- **扩展、技能与插件** — 编写、加载、匹配规则、\n  市场，以及插件安装器。\n- **提供者与模型** — 模型配置、流式传输内部机制，以及\n  Python / IPython 运行时。\n- **TUI** — 主题、`/tree` 命令，以及用于\n  扩展和自定义工具的集成钩子。\n\n## 本文档集的组织方式\n\n侧边栏中的每个顶级分组对应代理的一个子系统。在\n每个分组内，页面从\"概述\"到\"内部机制\"依次排列，因此您可以在\n获得当前任务所需的足够上下文时停止阅读。\n",
	"zh-cn/mcp/mcp-config.md": "---\ntitle: MCP 配置\ndescription: 编码代理运行时的 MCP 服务器配置、验证和管理。\nsidebar:\n  order: 1\n  label: 配置\ni18n:\n  sourceHash: ef8b49458ce9\n  translator: machine\n---\n\n# OMP 中的 MCP 配置\n\n本指南介绍如何为 OMP 编码代理添加、编辑和验证 MCP 服务器。\n\n代码中的权威来源：\n\n- 运行时配置类型：`packages/coding-agent/src/mcp/types.ts`\n- 配置写入器：`packages/coding-agent/src/mcp/config-writer.ts`\n- 加载器 + 验证：`packages/coding-agent/src/mcp/config.ts`\n- 独立 `mcp.json` 发现：`packages/coding-agent/src/discovery/mcp-json.ts`\n- Schema：`packages/coding-agent/src/config/mcp-schema.json`\n\n## 首选配置位置\n\nOMP 可以从多种工具（`.claude/`、`.cursor/`、`.vscode/`、`opencode.json` 等）发现 MCP 服务器，但对于 OMP 原生配置，通常应使用以下文件之一：\n\n- 项目级别：`.xcsh/mcp.json`\n- 用户级别：`~/.xcsh/mcp.json`\n\nOMP 还接受项目根目录中的备用独立文件：\n\n- `mcp.json`\n- `.mcp.json`\n\n当你希望由 OMP 管理配置时，使用 `.xcsh/mcp.json`。仅当你需要一个其他 MCP 客户端也可能读取的可移植备用文件时，才使用根目录的 `mcp.json` / `.mcp.json`。\n\n## 添加 schema 引用\n\n在文件顶部添加以下内容以获得编辑器自动补全和验证：\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {}\n}\n```\n\n当 `/mcp add`、`/mcp enable`、`/mcp disable`、`/mcp reauth` 或其他配置写入流程创建或更新 OMP 管理的 MCP 文件时，OMP 现在会自动写入此内容。\n\n## 文件结构\n\nOMP 支持以下顶层结构：\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"server-name\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"some-mcp-server\"]\n    }\n  },\n  \"disabledServers\": [\"server-name\"]\n}\n```\n\n顶层键：\n\n- `$schema` — 可选的 JSON Schema URL，用于工具支持\n- `mcpServers` — 服务器名称到服务器配置的映射\n- `disabledServers` — 用户级别的拒绝列表，用于按名称关闭已发现的服务器\n\n服务器名称必须匹配 `^[a-zA-Z0-9_.-]{1,100}$`。\n\n## 支持的服务器字段\n\n所有传输方式共享的字段：\n\n- `enabled?: boolean` — 当值为 `false` 时跳过该服务器\n- `timeout?: number` — 连接超时时间（毫秒）\n- `auth?: { ... }` — OMP 用于 OAuth/API 密钥流程的认证元数据\n- `oauth?: { ... }` — 在认证/重新认证期间使用的显式 OAuth 客户端设置\n\n### `stdio` 传输\n\n当省略 `type` 时，默认使用 `stdio`。\n\n必需：\n\n- `command: string`\n\n可选：\n\n- `type?: \"stdio\"`\n- `args?: string[]`\n- `env?: Record<string, string>`\n- `cwd?: string`\n\n示例：\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/Users/alice/projects\",\n        \"/Users/alice/Documents\"\n      ]\n    }\n  }\n}\n```\n\n这遵循了官方文件系统 MCP 服务器包（`@modelcontextprotocol/server-filesystem`）。\n\n### `http` 传输\n\n必需：\n\n- `type: \"http\"`\n- `url: string`\n\n可选：\n\n- `headers?: Record<string, string>`\n\n示例：\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\n这与 GitHub 托管的 GitHub MCP 服务器端点一致。\n\n### `sse` 传输\n\n必需：\n\n- `type: \"sse\"`\n- `url: string`\n\n可选：\n\n- `headers?: Record<string, string>`\n\n示例：\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"legacy-remote\": {\n      \"type\": \"sse\",\n      \"url\": \"https://example.com/mcp/sse\"\n    }\n  }\n}\n```\n\n`sse` 仍然支持以保持兼容性，但 MCP 规范现在建议新服务器使用 Streamable HTTP（`type: \"http\"`）。\n\n## 认证字段\n\nOMP 支持两种与认证相关的对象。\n\n### `auth`\n\n```json\n{\n  \"type\": \"oauth\" | \"apikey\",\n  \"credentialId\": \"optional-stored-credential-id\",\n  \"tokenUrl\": \"optional-token-endpoint\",\n  \"clientId\": \"optional-client-id\",\n  \"clientSecret\": \"optional-client-secret\"\n}\n```\n\n当需要 OMP 记住如何为服务器恢复凭据时，使用此配置。\n\n### `oauth`\n\n```json\n{\n  \"clientId\": \"...\",\n  \"clientSecret\": \"...\",\n  \"redirectUri\": \"...\",\n  \"callbackPort\": 3334,\n  \"callbackPath\": \"/oauth/callback\"\n}\n```\n\n当 MCP 服务器需要显式 OAuth 客户端设置时，使用此配置。\n\nSlack 是当前最清晰的示例。Slack 的 MCP 服务器托管在 `https://mcp.slack.com/mcp`，使用 Streamable HTTP，并要求使用 Slack 应用的客户端凭据进行机密 OAuth 认证。\n\n示例：\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\n来自 Slack 文档的相关端点：\n\n- MCP 端点：`https://mcp.slack.com/mcp`\n- 授权端点：`https://slack.com/oauth/v2_user/authorize`\n- 令牌端点：`https://slack.com/api/oauth.v2.user.access`\n\n## 常用复制粘贴示例\n\n### 通过 stdio 使用文件系统服务器\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/absolute/path/one\",\n        \"/absolute/path/two\"\n      ]\n    }\n  }\n}\n```\n\n### 通过 HTTP 使用 GitHub 托管服务器\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\n### 通过 Docker 使用 GitHub 本地服务器\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"-i\",\n        \"--rm\",\n        \"-e\",\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\",\n        \"ghcr.io/github/github-mcp-server\"\n      ],\n      \"env\": {\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n      }\n    }\n  }\n}\n```\n\n这与 GitHub 官方本地 Docker 镜像 `ghcr.io/github/github-mcp-server` 一致。\n\n### 通过 OAuth 使用 Slack 托管服务器\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\n## 密钥和变量解析\n\n这是最容易让人困惑的部分。\n\n### 在 `.xcsh/mcp.json` 和 `~/.xcsh/mcp.json` 中\n\n在 OMP 启动服务器或发起 HTTP 请求之前，它会按以下方式解析 `env` 和 `headers` 的值：\n\n1. 如果值以 `!` 开头，OMP 会将其作为 shell 命令运行并使用去除首尾空白的标准输出。\n2. 否则，OMP 首先检查该值是否匹配某个环境变量名。\n3. 如果该环境变量未设置，OMP 将原样使用该字符串。\n\n示例：\n\n```json\n{\n  \"env\": {\n    \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n  },\n  \"headers\": {\n    \"X-MCP-Insiders\": \"true\"\n  }\n}\n```\n\n这意味着以下方式对于本地密钥是有效且便捷的：\n\n- `\"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"` → 从当前 shell 环境复制\n- `\"Authorization\": \"Bearer hardcoded-token\"` → 使用字面值\n- `\"Authorization\": \"!printf 'Bearer %s' \\\"$GITHUB_TOKEN\\\"\"` → 通过命令构建请求头\n\n### 在根目录 `mcp.json` 和 `.mcp.json` 中\n\n独立备用加载器还会在发现过程中展开字符串中的 `${VAR}` 和 `${VAR:-default}`。\n\n示例：\n\n```json\n{\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\",\n      \"headers\": {\n        \"Authorization\": \"Bearer ${GITHUB_TOKEN}\"\n      }\n    }\n  }\n}\n```\n\n如果你希望 OMP 的行为最不出人意料，建议使用 `.xcsh/mcp.json` 并使用显式的 env/header 值。\n\n## `disabledServers`\n\n`disabledServers` 主要在用户配置文件（`~/.xcsh/mcp.json`）中使用，当某个服务器是从其他来源发现的，而你希望 OMP 忽略它且不编辑该工具的配置时。\n\n示例：\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"disabledServers\": [\"github\", \"slack\"]\n}\n```\n\n## `/mcp add` 与直接编辑 JSON\n\n当你需要引导式设置时，使用 `/mcp add`。\n\n在以下情况下直接编辑 JSON：\n\n- 你需要向导尚未提示的传输方式或认证选项\n- 你想从另一个 MCP 客户端粘贴服务器定义\n- 你想在编辑器中获得基于 schema 的验证\n\n编辑后，使用：\n\n- `/mcp reload` 重新发现并在当前会话中重新连接服务器\n- `/mcp list` 查看服务器来自哪个配置文件\n- `/mcp test <name>` 测试单个服务器\n\n## OMP 强制执行的验证规则\n\n来自 `packages/coding-agent/src/mcp/config.ts` 中的 `validateServerConfig()`：\n\n- `stdio` 需要 `command`\n- `http` 和 `sse` 需要 `url`\n- 一个服务器不能同时设置 `command` 和 `url`\n- 未知的 `type` 值会被拒绝\n\n实际影响：\n\n- 省略 `type` 意味着 `stdio`\n- 如果你粘贴了一个远程服务器配置但忘记了 `\"type\": \"http\"`，OMP 会将其视为 `stdio` 并提示缺少 `command`\n- `sse` 仍然有效以保持兼容性，但新的托管服务器通常应配置为 `http`\n\n## 发现和优先级\n\nOMP 不会合并不同文件中的重复服务器定义。发现提供者有优先级排序，优先级更高的定义获胜。\n\n实际操作中：\n\n- 当你需要 OMP 特定的覆盖时，优先使用 `.xcsh/mcp.json` 或 `~/.xcsh/mcp.json`\n- 尽可能在各工具之间保持服务器名称唯一\n- 当第三方配置不断重新引入你不需要的服务器时，在用户配置中使用 `disabledServers`\n\n## 故障排除\n\n### `Server \"name\": stdio server requires \"command\" field`\n\n你可能在远程服务器上遗漏了 `type: \"http\"`。\n\n### `Server \"name\": both \"command\" and \"url\" are set`\n\n选择一种传输方式。OMP 将 `command` 视为 stdio，将 `url` 视为 http/sse。\n\n### `/mcp add` 成功但服务器仍然无法连接\n\nJSON 是有效的，但服务器可能仍然不可达。使用 `/mcp test <name>` 并检查：\n\n- 二进制文件或 Docker 镜像是否存在\n- 必需的环境变量是否已设置\n- 远程 URL 是否可达\n- OAuth 或 API 令牌是否有效\n\n### 服务器存在于其他工具的配置中但不在 OMP 中\n\n运行 `/mcp list`。OMP 可以发现许多第三方 MCP 文件，但项目级别的加载也可以通过 `mcp.enableProjectConfig` 设置来禁用。\n\n## 参考资料\n\n- MCP 传输规范：<https://modelcontextprotocol.io/specification/2025-03-26/basic/transports>\n- 文件系统服务器包：<https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem>\n- GitHub MCP 服务器：<https://github.com/github/github-mcp-server>\n- Slack MCP 服务器文档：<https://docs.slack.dev/ai/slack-mcp-server/>\n",
	"zh-cn/mcp/mcp-protocol-transports.md": "---\ntitle: MCP 协议与传输层内部机制\ndescription: >-\n  MCP protocol implementation with stdio, SSE, and streamable HTTP transport\n  layers.\nsidebar:\n  order: 2\n  label: 协议与传输层\ni18n:\n  sourceHash: 48632064dd00\n  translator: machine\n---\n\n# MCP 协议与传输层内部机制\n\n本文档描述了 coding-agent 如何实现 MCP JSON-RPC 消息传递，以及协议关注点与传输关注点的分离方式。\n\n## 范围\n\n涵盖内容：\n\n- JSON-RPC 请求/响应与通知流程\n- stdio 和 HTTP/SSE 传输的请求关联与生命周期\n- 超时与取消行为\n- 错误传播与畸形负载处理\n- 传输选择边界（`stdio` vs `http`/`sse`）\n- 哪些重连/重试职责属于传输层，哪些属于管理器层\n\n不涵盖扩展编写 UX 或命令 UI。\n\n## 实现文件\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/transports/stdio.ts`](../../packages/coding-agent/src/mcp/transports/stdio.ts)\n- [`src/mcp/transports/http.ts`](../../packages/coding-agent/src/mcp/transports/http.ts)\n- [`src/mcp/transports/index.ts`](../../packages/coding-agent/src/mcp/transports/index.ts)\n- [`src/mcp/json-rpc.ts`](../../packages/coding-agent/src/mcp/json-rpc.ts)\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n\n## 层级边界\n\n### 协议层（JSON-RPC + MCP 方法）\n\n- 消息结构定义在 `types.ts` 中（`JsonRpcRequest`、`JsonRpcNotification`、`JsonRpcResponse`、`JsonRpcMessage`）。\n- MCP 客户端逻辑（`client.ts`）决定方法顺序和会话握手：\n  1. `initialize` 请求\n  2. `notifications/initialized` 通知\n  3. 方法调用如 `tools/list`、`tools/call`\n\n### 传输层（`MCPTransport`）\n\n`MCPTransport` 抽象了消息传递与生命周期：\n\n- `request(method, params, options?) -> Promise<T>`\n- `notify(method, params?) -> Promise<void>`\n- `close()`\n- `connected`\n- 可选回调：`onClose`、`onError`、`onNotification`\n\n传输实现负责帧格式和 I/O 细节：\n\n- `StdioTransport`：通过子进程 stdio 的换行分隔 JSON\n- `HttpTransport`：通过 HTTP POST 的 JSON-RPC，支持可选的 SSE 响应/监听\n\n### 当前重要注意事项\n\n传输回调（`onClose`、`onError`、`onNotification`）已实现，但当前的 `MCPClient`/`MCPManager` 流程并未将重连逻辑连接到这些回调。通知仅在调用方注册处理器时才会被消费。\n\n## 传输选择\n\n`client.ts:createTransport()` 根据配置选择传输方式：\n\n- `type` 省略或为 `\"stdio\"` -> `createStdioTransport`\n- `\"http\"` 或 `\"sse\"` -> `createHttpTransport`\n\n`\"sse\"` 被视为 HTTP 传输的变体（同一个类），而非独立的传输实现。\n\n## JSON-RPC 消息流与关联\n\n## 请求 ID\n\n每个传输为每个请求生成 ID（`Math.random` + 时间戳字符串）。ID 是传输层本地的关联令牌。\n\n## Stdio 关联路径\n\n- 出站请求序列化为一个 JSON 对象 + `\\n`。\n- `#pendingRequests: Map<id, {resolve,reject}>` 存储进行中的请求。\n- 读取循环从 stdout 解析 JSONL 并调用 `#handleMessage`。\n- 如果入站消息有匹配的 `id`，请求会被 resolve/reject。\n- 如果入站消息有 `method` 但没有 `id`，则视为通知并发送到 `onNotification`。\n\n未知 ID 会被忽略（不会 reject，不会触发错误回调）。\n\n## HTTP 关联路径\n\n- 出站请求是带有 JSON 请求体和生成的 `id` 的 HTTP `POST`。\n- 非 SSE 响应路径：解析一个 JSON-RPC 响应并返回 `result`/在 `error` 时抛出异常。\n- SSE 响应路径（`Content-Type: text/event-stream`）：流式处理事件，返回第一个 `id` 匹配预期请求 ID 且包含 `result` 或 `error` 的消息。\n- 带有 `method` 但没有 `id` 的 SSE 消息被视为通知。\n\n如果 SSE 流在匹配到响应之前结束，请求将失败并报错 `No response received for request ID ...`。\n\n## 通知\n\n客户端通过 `transport.notify(...)` 发出 JSON-RPC 通知。\n\n- Stdio：将通知帧写入 stdin（`jsonrpc`、`method`、可选 `params`）加换行符。\n- HTTP：发送不带 `id` 的 POST 请求体；成功接受 `2xx` 或 `202 Accepted`。\n\n服务器发起的通知仅通过传输层的 `onNotification` 暴露；管理器/客户端中没有默认的全局订阅者。\n\n## Stdio 传输内部机制\n\n## 生命周期与状态转换\n\n- 初始状态：`connected=false`、`process=null`、pending map 为空\n- `connect()`：\n  - 使用配置的 command/args/env/cwd 生成子进程\n  - 标记为已连接\n  - 启动 stdout 读取循环（`readJsonl`）\n  - 启动 stderr 循环（读取/丢弃；当前静默处理）\n- `close()`：\n  - 标记为已断开\n  - reject 所有待处理请求（`Transport closed`）\n  - 终止子进程\n  - 等待读取循环关闭\n  - 触发 `onClose`\n\n如果读取循环意外退出，`finally` 会触发 `#handleClose()`，执行相同的待处理请求 reject 和关闭回调。\n\n## 超时与取消\n\n每个请求：\n\n- 超时默认为 `config.timeout ?? 30000`\n- 调用方可选传入 `AbortSignal`\n- 中止和超时都会 reject 待处理的 promise 并清理 map 条目\n\n取消仅在本地生效：传输层不会向服务器发送协议级别的取消通知。\n\n## 畸形负载处理\n\n在读取循环中：\n\n- 每行解析的 JSONL 在 `try/catch` 中传递给 `#handleMessage`\n- 畸形/无效消息的处理异常会被丢弃（`Skip malformed lines` 注释）\n- 循环继续运行，因此一条错误消息不会终止连接\n\n如果底层流解析器抛出异常，会调用 `onError`（在仍然连接的情况下），然后连接关闭。\n\n## 断开/故障行为\n\n当进程退出或流关闭时：\n\n- 所有进行中的请求会被 reject 为 `Transport closed`\n- 不会自动重启或重连\n- 上层必须通过创建新的传输来重新连接\n\n## 背压/流式处理说明\n\n- 出站写入使用 `stdin.write()` + `flush()`，不等待 drain 语义。\n- 传输层中没有显式的队列或高水位标记管理。\n- 入站处理是流驱动的（通过 `readJsonl` 的 `for await`），一次处理一条解析的消息。\n\n## HTTP/SSE 传输内部机制\n\n## 生命周期与连接语义\n\nHTTP 传输具有逻辑连接状态，但请求路径在每次 HTTP 调用中是无状态的：\n\n- `connect()` 设置 `connected=true`（无套接字/会话握手）\n- 通过 `Mcp-Session-Id` 头进行可选的服务器会话跟踪\n- `close()` 可选地发送带有 `Mcp-Session-Id` 的 `DELETE` 请求，中止 SSE 监听器，触发 `onClose`\n\n因此 `connected` 表示\"传输可用\"，而非\"持久流已建立\"。\n\n## 会话头行为\n\n- 在 POST 响应中，如果存在 `Mcp-Session-Id` 头，传输层会存储它。\n- 后续的请求/通知会包含 `Mcp-Session-Id`。\n- `close()` 尝试通过 HTTP DELETE 终止服务器会话；终止失败会被忽略。\n\n## 超时与取消\n\n对于 `request()` 和 `notify()`：\n\n- 超时使用 `AbortController`（`config.timeout ?? 30000`）\n- 外部信号（如果提供）通过 `AbortSignal.any([...])` 合并\n- AbortError 处理区分调用方中止与超时\n\n抛出的错误：\n\n- 超时：`Request timeout after ...ms`（或 `SSE response timeout ...`、`Notify timeout ...`）\n- 调用方中止：当外部信号已中止时重新抛出原始 AbortError\n\n## HTTP 错误传播\n\n在非 OK 响应时：\n\n- 响应文本包含在抛出的错误中（`HTTP <status>: <text>`）\n- 如果存在，来自 `WWW-Authenticate` 和 `Mcp-Auth-Server` 的认证提示会被附加\n\n在 JSON-RPC 错误对象时：\n\n- 抛出 `MCP error <code>: <message>`\n\n畸形 JSON 请求体（`response.json()` 失败）作为解析异常传播。\n\n## SSE 行为与模式\n\n存在两种 SSE 路径：\n\n1. **单请求 SSE 响应**（`#parseSSEResponse`）\n   - 在 POST 响应内容类型为 `text/event-stream` 时使用\n   - 消费流直到找到匹配的响应 id\n   - 可以在同一流中处理交错的通知\n\n2. **后台 SSE 监听器**（`startSSEListener()`）\n   - 用于服务器发起通知的可选 GET 监听器\n   - 当前不会被 MCP 管理器/客户端自动启动\n   - 如果 GET 返回 `405`，监听器会静默禁用自身（服务器不支持此模式）\n\n## 畸形负载与断开处理\n\nSSE JSON 解析错误从 `readSseJson` 冒泡并 reject 请求/监听器。\n\n- 请求 SSE 解析错误会 reject 活跃的请求。\n- 后台监听器错误触发 `onError`（AbortError 除外）。\n- 后台监听器不会自动重连。\n\n## `json-rpc.ts` 工具函数 vs 传输抽象\n\n`src/mcp/json-rpc.ts` 提供 `callMCP()` 和 `parseSSE()` 辅助函数用于直接的 HTTP MCP 调用（被 Exa 集成使用），而非 `MCPClient`/`MCPManager` 使用的 `MCPTransport` 抽象。\n\n与 `HttpTransport` 的显著区别：\n\n- 先解析整个响应文本，然后提取第一行 `data:`（`parseSSE`），并回退到 JSON 解析\n- 无请求超时管理，无中止 API，无会话 ID 处理，无传输生命周期\n- 返回原始 JSON-RPC 信封对象\n\n此路径轻量但不如完整传输实现健壮。\n\n## 重试/重连职责\n\n## 传输层\n\n当前传输实现**不会**：\n\n- 重试失败的请求\n- 在 stdio 进程退出后重连\n- 重连 SSE 监听器\n- 在断开后重新发送进行中的请求\n\n它们采用快速失败并传播错误的策略。\n\n## 管理器/客户端层\n\n`MCPManager` 处理发现/初始连接编排，只能通过重新运行连接流程来重连（`connectToServer`/`discoverAndConnect` 路径）。它不会在运行时故障回调中自动修复已连接的传输。\n\n`MCPManager` 确实有针对慢速服务器的启动回退行为（从缓存加载延迟工具），但这是工具可用性的回退，而非传输重试。\n\n## 故障场景总结\n\n- **畸形 stdio 消息行**：丢弃；流继续。\n- **Stdio 流/进程结束**：传输关闭；待处理请求被 reject 为 `Transport closed`。\n- **HTTP 非 2xx**：请求/通知抛出 HTTP 错误。\n- **无效 JSON 响应**：解析异常传播。\n- **SSE 在匹配 id 前结束**：请求失败并报错 `No response received for request ID ...`。\n- **超时**：传输层特定的超时错误。\n- **调用方中止**：从调用方信号传播 AbortError/reason。\n\n## 实践边界规则\n\n如果关注点是消息结构、id 关联或 MCP 方法排序，则属于协议/客户端逻辑。\n\n如果关注点是帧格式（JSONL vs HTTP/SSE）、流解析、fetch/spawn 生命周期、超时时钟或连接拆除，则属于传输实现。\n",
	"zh-cn/mcp/mcp-runtime-lifecycle.md": "---\ntitle: MCP 运行时生命周期\ndescription: MCP 服务器进程的生命周期，涵盖从初始化到工具注册、健康监控和关闭的全过程。\nsidebar:\n  order: 3\n  label: 运行时生命周期\ni18n:\n  sourceHash: d04cefaf38f8\n  translator: machine\n---\n\n# MCP 运行时生命周期\n\n本文档描述了 MCP 服务器在 coding-agent 运行时中如何被发现、连接、作为工具暴露、刷新以及销毁。\n\n## 生命周期概览\n\n1. **SDK 启动**时调用 `discoverAndLoadMCPTools()`（除非 MCP 被禁用）。\n2. **发现阶段**（`loadAllMCPConfigs`）从能力源解析 MCP 服务器配置，过滤已禁用的/项目级的/Exa 条目，并保留源元数据。\n3. **管理器连接阶段**（`MCPManager.connectServers`）并行启动每个服务器的连接 + `tools/list`。\n4. **快速启动门控**最多等待 250ms，然后可能返回：\n   - 完全加载的 `MCPTool`，\n   - 每个服务器的失败信息，\n   - 或仍在等待的服务器的缓存 `DeferredMCPTool`。\n5. **SDK 装配**将 MCP 工具合并到会话的运行时工具注册表中。\n6. **活动会话**可以通过 `/mcp` 流程刷新 MCP 工具（`disconnectAll` + 重新发现 + `session.refreshMCPTools`）。\n7. **销毁阶段**在调用者调用 `disconnectServer`/`disconnectAll` 时执行；管理器同时会清除已断开服务器的 MCP 工具注册。\n\n## 发现和加载阶段\n\n### 从 SDK 的入口路径\n\n`src/sdk.ts` 中的 `createAgentSession()` 在 `enableMCP` 为 true（默认值）时执行 MCP 启动：\n\n- 调用 `discoverAndLoadMCPTools(cwd, { ... })`，\n- 传入 `authStorage`、缓存存储和 `mcp.enableProjectConfig` 设置，\n- 始终设置 `filterExa: true`，\n- 记录每个服务器的加载/连接错误，\n- 将返回的管理器存储在 `toolSession.mcpManager` 和会话结果中。\n\n如果 `enableMCP` 为 false，则完全跳过 MCP 发现。\n\n### 配置发现与过滤\n\n`loadAllMCPConfigs()`（`src/mcp/config.ts`）通过能力发现加载规范的 MCP 服务器条目，然后转换为旧版 `MCPServerConfig`。\n\n过滤行为：\n\n- `enableProjectConfig: false` 会移除项目级条目（`_source.level === \"project\"`）。\n- `enabled: false` 的服务器在连接尝试之前即被跳过。\n- Exa 服务器默认被过滤掉，其 API 密钥被提取用于原生 Exa 工具集成。\n\n结果包含 `configs` 和 `sources`（元数据，后续用于提供者标签）。\n\n### 发现级别的失败行为\n\n`discoverAndLoadMCPTools()` 区分两类失败：\n\n- **发现硬失败**（来自 `manager.discoverAndConnect` 的异常，通常来自配置发现）：返回空工具集和一个合成错误 `{ path: \".mcp.json\", error }`。\n- **每服务器的运行时/连接失败**：管理器返回部分成功结果及 `errors` 映射；其他服务器继续运行。\n\n因此，当个别 MCP 服务器失败时，启动不会导致整个代理会话失败。\n\n## 管理器状态模型\n\n`MCPManager` 通过独立的注册表跟踪运行时生命周期：\n\n- `#connections: Map<string, MCPServerConnection>` — 已完全连接的服务器。\n- `#pendingConnections: Map<string, Promise<MCPServerConnection>>` — 握手进行中。\n- `#pendingToolLoads: Map<string, Promise<{ connection, serverTools }>>` — 已连接但工具仍在加载。\n- `#tools: CustomTool[]` — 暴露给调用者的当前 MCP 工具视图。\n- `#sources: Map<string, SourceMeta>` — 即使在连接完成前也存在的提供者/源元数据。\n\n`getConnectionStatus(name)` 从这些映射派生状态：\n\n- 如果在 `#connections` 中则为 `connected`，\n- 如果在待处理连接或待处理工具加载中则为 `connecting`，\n- 否则为 `disconnected`。\n\n## 连接建立与启动时序\n\n## 每服务器的连接管道\n\n对于 `connectServers()` 中发现的每个服务器：\n\n1. 存储/更新源元数据，\n2. 如果已连接/待处理则跳过，\n3. 验证传输字段（`validateServerConfig`），\n4. 解析认证/shell 替换（`#resolveAuthConfig`），\n5. 调用 `connectToServer(name, resolvedConfig)`，\n6. 调用 `listTools(connection)`，\n7. 尽力缓存工具定义（`MCPToolCache.set`）。\n\n`connectToServer()` 行为（`src/mcp/client.ts`）：\n\n- 创建 stdio 或 HTTP/SSE 传输，\n- 执行 MCP `initialize` + `notifications/initialized`，\n- 使用超时（`config.timeout` 或默认 30 秒），\n- 初始化失败时关闭传输。\n\n### 快速启动门控 + 延迟回退\n\n`connectServers()` 在以下两者之间进行竞争等待：\n\n- 所有连接/工具加载任务完成，以及\n- `STARTUP_TIMEOUT_MS = 250`。\n\n250ms 之后：\n\n- 已完成的任务变为活动的 `MCPTool`，\n- 已拒绝的任务产生每服务器的错误，\n- 仍在等待的任务：\n  - 如果有可用的缓存工具定义（`MCPToolCache.get`），则创建 `DeferredMCPTool`，\n  - 否则阻塞等待这些待处理任务完成。\n\n这是一种混合启动模型：缓存可用时快速返回，缓存不可用时等待以确保正确性。\n\n### 后台完成行为\n\n每个待处理的 `toolsPromise` 还有一个后台延续，最终会：\n\n- 通过 `#replaceServerTools` 替换管理器状态中该服务器的工具切片，\n- 写入缓存，\n- 仅在启动后记录延迟失败（`allowBackgroundLogging`）。\n\n## 工具暴露与活动会话可用性\n\n### 启动注册\n\n`discoverAndLoadMCPTools()` 将管理器工具转换为 `LoadedCustomTool[]`，并装饰路径（已知时为 `mcp:<server> via <providerName>`）。\n\n`createAgentSession()` 然后将这些工具推入 `customTools`，它们被包装并添加到运行时工具注册表中，名称格式为 `mcp_<server>_<tool>`。\n\n### 工具调用\n\n- `MCPTool` 通过已连接的 `MCPServerConnection` 调用工具。\n- `DeferredMCPTool` 在调用前等待 `waitForConnection(server)`；这允许缓存的工具在连接就绪前即可存在。\n\n两者都返回结构化的工具输出，并将传输/工具错误转换为 `MCP error: ...` 工具内容（中止仍为中止）。\n\n## 刷新/重新加载路径（启动 vs 实时重载）\n\n### 初始启动路径\n\n- 在 `sdk.ts` 中进行一次性发现/加载，\n- 工具注册到初始会话工具注册表中。\n\n### 交互式重载路径\n\n`/mcp reload` 路径（`src/modes/controllers/mcp-command-controller.ts`）执行：\n\n1. `mcpManager.disconnectAll()`，\n2. `mcpManager.discoverAndConnect()`，\n3. `session.refreshMCPTools(mcpManager.getTools())`。\n\n`session.refreshMCPTools()`（`src/session/agent-session.ts`）移除所有 `mcp_` 工具，重新包装最新的 MCP 工具，并重新激活工具集，使 MCP 更改无需重启会话即可生效。\n\n还有一个用于延迟连接的后续路径：在等待特定服务器后，如果状态变为 `connected`，它会重新运行 `session.refreshMCPTools(...)`，以便新可用的工具在会话中重新绑定。\n\n## 健康检查、重连和部分失败行为\n\n当前运行时行为有意保持最小化：\n\n- 管理器/客户端中**没有自主健康监控**。\n- 传输断开时**没有自动重连循环**。\n- 管理器不订阅传输的 `onClose`/`onError`；状态由注册表驱动。\n- 重连是显式的：通过重载流程或直接调用 `connectServers()`。\n\n在操作层面：\n\n- 一个服务器失败不会移除健康服务器的工具，\n- 连接/列表失败按服务器隔离，\n- 工具缓存和后台更新为尽力而为（记录警告/错误，不会硬停止）。\n\n## 销毁语义\n\n### 服务器级别的销毁\n\n`disconnectServer(name)`：\n\n- 移除待处理条目/源元数据，\n- 如果已连接则关闭传输，\n- 从管理器状态中移除该服务器的 `mcp_` 工具。\n\n### 全局销毁\n\n`disconnectAll()`：\n\n- 使用 `Promise.allSettled` 关闭所有活动传输，\n- 清除待处理映射、源、连接和管理器工具列表。\n\n在当前的装配中，显式销毁用于 MCP 命令流程（重载/移除/禁用）。启动路径本身没有单独的自动管理器处置钩子；调用者需要在需要确定性 MCP 关闭时负责调用管理器的断开方法。\n\n## 失败模式与保证\n\n| 场景 | 行为 | 硬失败 vs 尽力而为 |\n| --- | --- | --- |\n| 发现阶段抛出异常（能力/配置加载路径） | 加载器返回空工具 + 合成的 `.mcp.json` 错误 | 尽力而为的会话启动 |\n| 无效的服务器配置 | 服务器被跳过并记录验证错误条目 | 尽力而为（按服务器） |\n| 连接超时/初始化失败 | 记录服务器错误；其他服务器继续 | 尽力而为（按服务器） |\n| 启动时 `tools/list` 仍在等待但有缓存命中 | 立即返回延迟工具 | 尽力而为的快速启动 |\n| 启动时 `tools/list` 仍在等待且无缓存 | 启动等待待处理任务完成 | 硬等待以确保正确性 |\n| 后台工具加载延迟失败 | 在启动门控之后记录 | 尽力而为的日志记录 |\n| 运行时传输断开 | 无自动重连；后续调用失败直到重连/重载 | 通过手动操作尽力恢复 |\n\n## 公共 API 接口\n\n`src/mcp/index.ts` 为外部调用者重新导出加载器/管理器/客户端 API。`src/sdk.ts` 将 `discoverMCPServers()` 作为便捷包装暴露，返回相同的加载器结果形状。\n\n## 实现文件\n\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts) — 加载器门面、发现错误规范化、`LoadedCustomTool` 转换。\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts) — 生命周期状态注册表、并行连接/列表流程、刷新/断开。\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts) — 传输设置、初始化握手、列表/调用/断开。\n- [`src/mcp/index.ts`](../../packages/coding-agent/src/mcp/index.ts) — MCP 模块 API 导出。\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — 启动装配到会话/工具注册表。\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts) — 管理器使用的配置发现/过滤/验证。\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts) — `MCPTool` 和 `DeferredMCPTool` 运行时行为。\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — `refreshMCPTools` 实时重绑定。\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts) — 交互式重载/重连流程。\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — 通过父管理器连接进行子代理 MCP 代理。\n",
	"zh-cn/mcp/mcp-server-tool-authoring.md": "---\ntitle: MCP 服务器与工具编写\ndescription: 构建自定义 MCP 服务器并为编码代理注册工具的指南。\nsidebar:\n  order: 4\n  label: 服务器与工具编写\ni18n:\n  sourceHash: 160e7560ef1f\n  translator: machine\n---\n\n# MCP 服务器与工具编写\n\n本文档介绍 MCP 服务器定义如何在 coding-agent 中转化为可调用的 `mcp_*` 工具，以及当配置无效、重复、禁用或受认证限制时，运维人员应有何预期。\n\n## 架构概览\n\n```text\nConfig sources (.xcsh/.claude/.cursor/.vscode/mcp.json, mcp.json, etc.)\n  -> discovery providers normalize to canonical MCPServer\n  -> capability loader dedupes by server name (higher provider priority wins)\n  -> loadAllMCPConfigs converts to MCPServerConfig + skips enabled:false\n  -> MCPManager connects/listTools (with auth/header/env resolution)\n  -> MCPTool/DeferredMCPTool bridge exposes tools as mcp_<server>_<tool>\n  -> AgentSession.refreshMCPTools replaces live MCP tools immediately\n```\n\n## 1) 服务器配置模型与验证\n\n`src/mcp/types.ts` 定义了 MCP 配置编写者和运行时使用的编写模型：\n\n- `stdio`（`type` 缺省时的默认值）：需要 `command`，可选 `args`、`env`、`cwd`\n- `http`：需要 `url`，可选 `headers`\n- `sse`：需要 `url`，可选 `headers`（为兼容性保留）\n- 共享字段：`enabled`、`timeout`、`auth`\n\n`validateServerConfig()`（`src/mcp/config.ts`）执行传输层基本验证：\n\n- 拒绝同时设置 `command` 和 `url` 的配置\n- stdio 要求提供 `command`\n- http/sse 要求提供 `url`\n- 拒绝未知的 `type`\n\n`config-writer.ts` 在添加/更新操作时应用此验证，同时验证服务器名称：\n\n- 非空\n- 最多 100 个字符\n- 仅允许 `[a-zA-Z0-9_.-]`\n\n### 传输层注意事项\n\n- 省略 `type` 意味着 stdio。如果你原本打算使用 HTTP/SSE 但省略了 `type`，则 `command` 变为必填项。\n- `sse` 仍然被接受，但在内部作为 HTTP 传输处理（`createHttpTransport`）。\n- 验证是结构性的，而非可达性验证：语法上合法的 URL 在连接时仍可能失败。\n\n## 2) 发现、规范化与优先级\n\n### 基于能力的发现\n\n`loadAllMCPConfigs()`（`src/mcp/config.ts`）通过 `loadCapability(mcpCapability.id)` 加载规范的 `MCPServer` 项。\n\n能力层（`src/capability/index.ts`）随后：\n\n1. 按优先级顺序加载提供者\n2. 按 `server.name` 去重（先到先得 = 最高优先级）\n3. 验证去重后的项\n\n结果：跨来源的重复服务器名称不会合并。只有一个定义生效；较低优先级的重复项将被遮蔽。\n\n### `.mcp.json` 及相关文件\n\n`src/discovery/mcp-json.ts` 中的专用回退提供者读取项目根目录的 `mcp.json` 和 `.mcp.json`（低优先级）。\n\n实际上 MCP 服务器也来自更高优先级的提供者（例如原生 `.xcsh/...` 和工具特定的配置目录）。编写建议：\n\n- 优先使用 `.xcsh/mcp.json`（项目级）或 `~/.xcsh/mcp.json`（用户级）以获得明确控制。\n- 当需要回退兼容性时使用根目录 `mcp.json` / `.mcp.json`。\n- 在多个来源中使用相同的服务器名称会导致优先级遮蔽，而非合并。\n\n### 规范化行为\n\n`convertToLegacyConfig()`（`src/mcp/config.ts`）将规范的 `MCPServer` 映射为运行时 `MCPServerConfig`。\n\n关键行为：\n\n- 传输类型推断为 `server.transport ?? (command ? \"stdio\" : url ? \"http\" : \"stdio\")`\n- 已禁用的服务器（`enabled === false`）在连接前被丢弃\n- 可选字段存在时予以保留\n\n### 发现过程中的环境变量展开\n\n`mcp-json.ts` 使用 `expandEnvVarsDeep()` 展开字符串字段中的环境变量占位符：\n\n- 支持 `${VAR}` 和 `${VAR:-default}`\n- 未解析的值保持为字面量 `${VAR}` 字符串\n\n`mcp-json.ts` 还对用户 JSON 执行运行时类型检查，并对无效的 `enabled`/`timeout` 值记录警告，而非导致整个文件加载失败。\n\n## 3) 认证与运行时值解析\n\n`MCPManager.prepareConfig()`/`#resolveAuthConfig()`（`src/mcp/manager.ts`）是连接前的最终处理环节。\n\n### OAuth 凭据注入\n\n如果配置包含：\n\n```ts\nauth: { type: \"oauth\", credentialId: \"...\" }\n```\n\n且凭据存在于认证存储中：\n\n- `http`/`sse`：注入 `Authorization: Bearer <access_token>` 请求头\n- `stdio`：注入 `OAUTH_ACCESS_TOKEN` 环境变量\n\n如果凭据查找失败，管理器会记录警告并在未解析认证的情况下继续运行。\n\n### 请求头/环境变量值解析\n\n连接前，管理器通过 `resolveConfigValue()`（`src/config/resolve-config-value.ts`）解析每个请求头/环境变量值：\n\n- 以 `!` 开头的值 => 执行 shell 命令，使用修剪后的 stdout（有缓存）\n- 否则，首先将值视为环境变量名（`process.env[name]`），回退为字面值\n- 未解析的命令/环境变量值将从最终的 headers/env 映射中省略\n\n运维注意事项：这意味着拼写错误的密钥命令/环境变量名可能会静默移除该请求头/环境变量条目，导致下游 401/403 或服务器启动失败。\n\n## 4) 工具桥接：MCP -> 代理可调用工具\n\n`src/mcp/tool-bridge.ts` 将 MCP 工具定义转换为 `CustomTool`。\n\n### 命名与冲突域\n\n工具名称生成规则为：\n\n```text\nmcp_<sanitized_server_name>_<sanitized_tool_name>\n```\n\n规则：\n\n- 转为小写\n- 非 `[a-z_]` 字符变为 `_`\n- 重复的下划线合并\n- 工具名称中冗余的 `<server>_` 前缀被剥离一次\n\n这避免了许多冲突，但并非全部。不同的原始名称仍可能清理为相同的标识符（例如 `my-server` 和 `my.server` 清理后相似），且注册表插入采用后写覆盖策略。\n\n### Schema 映射\n\n`convertSchema()` 基本保持 MCP JSON Schema 不变，但为缺少 `properties` 的对象 schema 补充 `{}` 以确保提供者兼容性。\n\n### 执行映射\n\n`MCPTool.execute()` / `DeferredMCPTool.execute()`：\n\n- 调用 MCP `tools/call`\n- 将 MCP 内容扁平化为可展示的文本\n- 返回结构化详情（`serverName`、`mcpToolName`、提供者元数据）\n- 将服务器报告的 `isError` 映射为 `Error: ...` 文本结果\n- 将抛出的传输/运行时故障映射为 `MCP error: ...`\n- 通过将 AbortError 转换为 `ToolAbortError` 保留中止语义\n\n## 5) 运维生命周期：添加/编辑/删除与实时更新\n\n交互模式在 `src/modes/controllers/mcp-command-controller.ts` 中暴露 `/mcp` 命令。\n\n支持的操作：\n\n- `add`（向导或快速添加）\n- `remove` / `rm`\n- `enable` / `disable`\n- `test`\n- `reauth` / `unauth`\n- `reload`\n\n配置写入是原子性的（`writeMCPConfigFile`：临时文件 + 重命名）。\n\n更改后，控制器调用 `#reloadMCP()`：\n\n1. `mcpManager.disconnectAll()`\n2. `mcpManager.discoverAndConnect()`\n3. `session.refreshMCPTools(mcpManager.getTools())`\n\n`refreshMCPTools()` 替换所有 `mcp_` 注册表条目并立即重新激活最新的 MCP 工具集，因此更改无需重启会话即可生效。\n\n### 模式差异\n\n- **交互/TUI 模式**：`/mcp` 提供应用内用户体验（向导、OAuth 流程、连接状态文本、即时运行时重绑定）。\n- **SDK/无头集成**：`discoverAndLoadMCPTools()`（`src/mcp/loader.ts`）返回已加载的工具 + 按服务器的错误信息；无 `/mcp` 命令用户体验。\n\n## 6) 用户可见的错误信息\n\n用户/运维人员常见的错误字符串：\n\n- 添加/更新验证失败：\n  - `Invalid server config: ...`\n  - `Server \"<name>\" already exists in <path>`\n- 快速添加参数问题：\n  - `Use either --url or -- <command...>, not both.`\n  - `--token requires --url (HTTP/SSE transport).`\n- 连接/测试失败：\n  - `Failed to connect to \"<name>\": <message>`\n  - 超时帮助文本建议增加超时时间\n  - `401/403` 的认证帮助文本\n- 认证/OAuth 流程：\n  - `Authentication required ... OAuth endpoints could not be discovered`\n  - `OAuth flow timed out. Please try again.`\n  - `OAuth authentication failed: ...`\n- 使用已禁用的服务器：\n  - `Server \"<name>\" is disabled. Run /mcp enable <name> first.`\n\n发现过程中格式错误的源 JSON 通常以警告/日志形式处理；config-writer 路径会抛出明确的错误。\n\n## 7) 实用编写指南\n\n在此代码库中进行可靠的 MCP 编写：\n\n1. 在所有支持 MCP 的配置来源中保持服务器名称全局唯一。\n2. 优先使用字母数字/下划线名称，以避免生成的 `mcp_*` 工具名称中出现清理后的命名冲突。\n3. 使用显式 `type` 以避免意外的 stdio 默认行为。\n4. 将 `enabled: false` 视为硬关闭：服务器将从运行时连接集合中省略。\n5. 对于 OAuth 配置，存储有效的 `credentialId`；否则认证注入将被跳过。\n6. 如果使用基于命令的密钥解析（`!cmd`），请验证命令输出是稳定且非空的。\n\n## 实现文件\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts)\n- [`src/mcp/config-writer.ts`](../../packages/coding-agent/src/mcp/config-writer.ts)\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts)\n- [`src/discovery/mcp-json.ts`](../../packages/coding-agent/src/discovery/mcp-json.ts)\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/config/resolve-config-value.ts`](../../packages/coding-agent/src/config/resolve-config-value.ts)\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts)\n",
	"zh-cn/natives/natives-addon-loader-runtime.md": "---\ntitle: 原生 Addon 加载器运行时\ndescription: N-API addon 加载器运行时，具备平台检测、回退策略和模块解析功能。\nsidebar:\n  order: 3\n  label: Addon 加载器\ni18n:\n  sourceHash: 743ea3e32c7c\n  translator: machine\n---\n\n# 原生 Addon 加载器运行时\n\n本文档深入介绍了 `@f5-sales-demo/pi-natives` 中的 addon 加载/验证层：`native.ts` 如何决定加载哪个 `.node` 文件、嵌入式负载提取何时运行，以及启动失败如何报告。\n\n## 实现文件\n\n- `packages/natives/src/native.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/package.json`\n\n## 范围与职责\n\n加载器/运行时的职责被有意限定在较窄的范围内：\n\n- 构建一个基于平台/CPU 感知的候选列表，用于 addon 文件名和目录。\n- 可选地将嵌入式 addon 物化到一个带版本号的用户级缓存目录。\n- 按确定性顺序尝试候选项。\n- 在暴露绑定之前，通过 `validateNative` 拒绝过时或不兼容的 addon。\n\n不在本文档范围内的内容：模块特定的 grep/text/highlight 行为。\n\n## 运行时输入和派生状态\n\n在模块初始化时（`export const native = loadNative();`），`native.ts` 计算静态上下文：\n\n- **平台标签**：``${process.platform}-${process.arch}``（例如 `darwin-arm64`）。\n- **包版本**：来自 `packages/natives/package.json`（`version` 字段）。\n- **核心目录**：\n  - `nativeDir`：包本地路径 `packages/natives/native`。\n  - `execDir`：包含 `process.execPath` 的目录。\n  - `versionedDir`：`<getNativesDir()>/<packageVersion>`。\n  - `userDataDir` 回退路径：\n    - Windows：`%LOCALAPPDATA%/xcsh`（或 `%USERPROFILE%/AppData/Local/xcsh`）。\n    - 非 Windows：`~/.local/bin`。\n- **编译二进制模式**（`isCompiledBinary`）：在以下任一条件为真时启用：\n  - 设置了 `PI_COMPILED` 环境变量，或\n  - `import.meta.url` 包含 Bun 嵌入式标记（`$bunfs`、`~BUN`、`%7EBUN`）。\n- **变体覆盖**：`PI_NATIVE_VARIANT`（仅限 `modern`/`baseline`；无效值会被忽略）。\n- **选定变体**：显式覆盖值优先，否则在 x64 上进行运行时 AVX2 检测（支持 AVX2 则为 `modern`，否则为 `baseline`）。\n\n## 平台支持与标签解析\n\n`SUPPORTED_PLATFORMS` 固定为：\n\n- `linux-x64`\n- `linux-arm64`\n- `darwin-x64`\n- `darwin-arm64`\n- `win32-x64`\n\n行为细节：\n\n- 不支持的平台不会被预先拒绝。\n- 加载器仍然会先尝试所有计算出的候选项。\n- 如果没有任何候选项能够加载，则会抛出明确的不支持平台错误，并列出支持的标签。\n\n这在为接近匹配的情况保留有用诊断信息的同时，仍然会对真正不支持的目标进行硬失败。\n\n## 变体选择（`modern` / `baseline` / 默认）\n\n### x64 行为\n\n1. 如果 `PI_NATIVE_VARIANT` 为 `modern` 或 `baseline`，则该值优先。\n2. 否则检测 AVX2 支持：\n   - Linux：扫描 `/proc/cpuinfo` 查找 `avx2`。\n   - macOS：查询 `sysctl`（`machdep.cpu.leaf7_features`，回退到 `machdep.cpu.features`）。\n   - Windows：运行 PowerShell `[System.Runtime.Intrinsics.X86.Avx2]::IsSupported`。\n3. 结果：\n   - AVX2 可用 -> `modern`\n   - AVX2 不可用/无法检测 -> `baseline`\n\n### 非 x64 行为\n\n- 不使用变体；加载器使用默认文件名（`pi_natives.<platform>-<arch>.node`）。\n\n### 文件名构造\n\n给定 `tag = <platform>-<arch>`：\n\n- 非 x64 或无变体：`pi_natives.<tag>.node`\n- x64 + `modern`：按顺序尝试\n  1. `pi_natives.<tag>-modern.node`\n  2. `pi_natives.<tag>-baseline.node`（有意的回退）\n- x64 + `baseline`：仅 `pi_natives.<tag>-baseline.node`\n\n最终错误消息中使用的 `addonLabel` 为 `<tag>` 或 `<tag> (<variant>)`。\n\n## 候选路径构造与回退顺序\n\n`native.ts` 在任何 `require(...)` 调用之前构建候选池。\n\n### 发布候选项\n\n从变体解析后的文件名列表构建，按以下顺序搜索：\n\n- **非编译运行时**：\n  1. `<nativeDir>/<filename>`\n  2. `<execDir>/<filename>`\n\n- **编译运行时**（`PI_COMPILED` 或 Bun 嵌入式标记）：\n  1. `<versionedDir>/<filename>`\n  2. `<userDataDir>/<filename>`\n  3. `<nativeDir>/<filename>`\n  4. `<execDir>/<filename>`\n\n`dedupedCandidates` 在保持首次出现顺序的同时去除重复项。\n\n### 最终运行时序列\n\n在加载时：\n\n1. 可选的嵌入式提取候选项（如果生成了）被插入到队列最前面。\n2. 按顺序尝试剩余的去重候选项。\n3. 第一个同时通过 `require(...)` 和 `validateNative(...)` 的候选项胜出。\n\n## 嵌入式 addon 提取生命周期\n\n`embedded-addon.ts` 定义了一个生成的清单结构：\n\n- `platformTag`\n- `version`\n- `files[]`，其中每个条目包含 `variant`、`filename`、`filePath`\n\n当前签入的默认值为 `embeddedAddon: null`；编译产物可能会用真实元数据替换它。\n\n### 提取状态机\n\n提取（`maybeExtractEmbeddedAddon`）仅在所有门控条件通过时运行：\n\n1. `isCompiledBinary === true`\n2. `embeddedAddon !== null`\n3. `embeddedAddon.platformTag === platformTag`\n4. `embeddedAddon.version === packageVersion`\n5. 找到了与变体匹配的嵌入式文件\n\n变体文件选择与运行时变体意图一致：\n\n- 非 x64：优先选择 `default`，然后选择第一个可用文件。\n- x64 + `modern`：优先选择 `modern`，回退到 `baseline`。\n- x64 + `baseline`：要求 `baseline`。\n\n物化行为：\n\n1. 确保 `<versionedDir>` 存在（`mkdirSync(..., { recursive: true })`）。\n2. 如果 `<versionedDir>/<selected filename>` 已存在，则复用它（不重写）。\n3. 否则读取嵌入式源 `filePath` 并写入目标文件。\n4. 返回目标路径作为最高优先级加载尝试。\n\n提取失败时不会立即崩溃；它会附加一个错误条目（目录创建或写入失败），加载器继续进行正常的候选项探测。\n\n## 生命周期与状态转换\n\n```text\nInit\n  -> Compute platform/version/variant/candidate lists\n  -> (Compiled + embedded manifest matches?)\n       yes -> Try extract embedded to versionedDir (record errors, continue)\n       no  -> Skip extraction\n  -> For each runtime candidate in order:\n       require(candidate)\n       -> success: validateNative\n            -> pass: return bindings (READY)\n            -> fail: record error, continue\n       -> failure: record error, continue\n  -> none loaded:\n       if unsupported platform tag -> throw Unsupported platform\n       else -> throw Failed to load (full tried-path diagnostics + hints)\n```\n\n## `validateNative` 契约检查\n\n`validateNative(bindings, source)` 在启动时对 `NativeBindings` 强制执行纯函数契约。\n\n机制：\n\n- 对每个必需的导出名称，检查 `typeof bindings[name] === \"function\"`。\n- 缺失的名称会被聚合。\n- 如果有任何缺失，加载器会抛出错误，包含：\n  - 源 addon 路径，\n  - 缺失的导出列表，\n  - 重新构建的命令提示。\n\n这是一个针对过时二进制文件、部分构建和符号/名称漂移的硬兼容性门控。\n\n### JS API ↔ 原生导出映射（验证门控）\n\n| `validateNative` 中检查的 JS 绑定名称 | 期望的原生导出名称 |\n| --- | --- |\n| `grep` | `grep` |\n| `glob` | `glob` |\n| `highlightCode` | `highlightCode` |\n| `executeShell` | `executeShell` |\n| `PtySession` | `PtySession` |\n| `Shell` | `Shell` |\n| `visibleWidth` | `visibleWidth` |\n| `getSystemInfo` | `getSystemInfo` |\n| `getWorkProfile` | `getWorkProfile` |\n| `invalidateFsScanCache` | `invalidateFsScanCache` |\n\n注意：`bindings.ts` 仅声明了基础的 `cancelWork(id)` 成员；模块 `types.ts` 文件通过声明合并添加了 `validateNative` 所强制执行的附加符号。\n\n## 失败行为与诊断\n\n## 不支持的平台\n\n如果所有候选项都失败且 `platformTag` 不在 `SUPPORTED_PLATFORMS` 中，加载器会抛出：\n\n- `Unsupported platform: <tag>`\n- 完整的支持平台列表\n- 明确的问题报告指南\n\n## 过时二进制文件/不匹配症状\n\n典型的过时不匹配信号：\n\n- `Native addon missing exports (<candidate>). Missing: ...`\n\n常见原因：\n\n- 来自先前包版本/API 结构的旧 `.node` 二进制文件。\n- 选择了错误的变体产物（针对 x64）。\n- 新的 Rust 导出不存在于已加载的产物中。\n\n加载器行为：\n\n- 记录每个候选项的缺失导出失败。\n- 继续探测剩余候选项。\n- 如果没有候选项通过验证，最终错误会包含每个尝试过的路径及其失败消息。\n\n## 编译二进制启动失败\n\n在编译模式下，最终诊断信息包含：\n\n- 期望的带版本缓存目标路径（`<versionedDir>/<filename>`），\n- 删除过时 `<versionedDir>` 并重新运行的修复建议，\n- 每个期望文件名的直接发布下载 `curl` 命令。\n\n## 非编译启动失败\n\n在普通包/运行时模式下，最终诊断信息包含：\n\n- 重新安装提示（`bun install @f5-sales-demo/pi-natives`），\n- 本地重新构建命令（`bun --cwd=packages/natives run build`），\n- 可选的 x64 变体构建提示（`TARGET_VARIANT=baseline|modern ...`）。\n\n## 运行时行为\n\n- 加载器始终使用发布候选链。\n- 设置 `PI_DEV` 仅启用每个候选项的控制台诊断（`Loaded native addon...` 和加载错误）。\n",
	"zh-cn/natives/natives-architecture.md": "---\ntitle: 原生模块架构\ndescription: Rust N-API 原生插件架构，桥接 TypeScript 与平台特定操作。\nsidebar:\n  order: 1\n  label: 架构\ni18n:\n  sourceHash: d38ed2437bb7\n  translator: machine\n---\n\n# 原生模块架构\n\n`@f5-sales-demo/pi-natives` 是一个三层架构：\n\n1. **TypeScript 包装器/API 层**，暴露稳定的 JS/TS 入口点。\n2. **插件加载/验证层**，为当前运行时解析和验证 `.node` 二进制文件。\n3. **Rust N-API 模块层**，实现导出到 JS 的性能关键原语。\n\n本文档是更深层模块级文档的基础。\n\n## 实现文件\n\n- `packages/natives/src/index.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `crates/pi-natives/src/lib.rs`\n\n## 第一层：TypeScript 包装器/API 层\n\n`packages/natives/src/index.ts` 是公共的桶文件（barrel）。它按能力域分组导出，并重新导出类型化的包装器，而不是直接暴露原始 N-API 绑定。\n\n当前顶级分组：\n\n- **搜索/文本原语**：`grep`、`glob`、`text`、`highlight`\n- **执行/进程/终端原语**：`shell`、`pty`、`ps`、`keys`\n- **系统/媒体/转换原语**：`image`、`html`、`clipboard`、`system-info`、`work`\n\n`packages/natives/src/bindings.ts` 定义了基础接口契约：\n\n- `NativeBindings` 以共享成员开始（`cancelWork(id: number)`）\n- 模块特定的绑定通过每个模块的 `types.ts` 的声明合并添加\n- `Cancellable` 为暴露取消功能的包装器标准化了超时和中止信号选项\n\n**保证的契约（面向 API）：** 使用者从 `@f5-sales-demo/pi-natives` 导入并使用类型化的包装器。\n\n**实现细节（可能变更）：** 声明合并和内部包装器布局（`src/<module>/index.ts`、`src/<module>/types.ts`）。\n\n## 第二层：插件加载和验证\n\n`packages/natives/src/native.ts` 负责运行时插件选择、可选的提取以及导出验证。\n\n### 候选项解析模型\n\n- 平台标签为 `\"${process.platform}-${process.arch}\"`。\n- 当前支持的标签有：\n  - `linux-x64`\n  - `linux-arm64`\n  - `darwin-x64`\n  - `darwin-arm64`\n  - `win32-x64`\n- x64 可使用 CPU 变体：\n  - `modern`（支持 AVX2）\n  - `baseline`（回退方案）\n- 非 x64 使用默认文件名（无变体后缀）。\n\n文件名策略：\n\n- 发布版：`pi_natives.<platform>-<arch>.node`\n- x64 变体发布版：`pi_natives.<platform>-<arch>-modern.node` 和/或 `...-baseline.node`\n- `PI_DEV` 启用加载器诊断信息，但不改变插件文件名\n\n### 平台特定的变体检测\n\n对于 x64，变体选择使用：\n\n- **Linux**：`/proc/cpuinfo`\n- **macOS**：`sysctl machdep.cpu.leaf7_features` / `machdep.cpu.features`\n- **Windows**：PowerShell 检查 `System.Runtime.Intrinsics.X86.Avx2`\n\n`PI_NATIVE_VARIANT` 可以显式强制使用 `modern` 或 `baseline`。\n\n### 二进制分发和提取模型\n\n`packages/natives/package.json` 在发布文件中同时包含 `src` 和 `native`。`native/` 目录存储预构建的平台产物。\n\n对于编译后的二进制文件（`PI_COMPILED` 或 Bun 嵌入式运行时标记），加载器行为如下：\n\n1. 检查带版本的用户缓存路径：`<getNativesDir()>/<packageVersion>/...`\n2. 检查旧版编译二进制文件位置：\n   - Windows：`%LOCALAPPDATA%/xcsh`（回退到 `%USERPROFILE%/AppData/Local/xcsh`）\n   - 非 Windows：`~/.local/bin`\n3. 回退到打包的 `native/` 和可执行文件目录候选项\n\n如果存在嵌入式插件清单（由 `scripts/embed-native.ts` 生成的 `embedded-addon.ts`），`native.ts` 可以在加载前将匹配的嵌入式二进制文件具体化到带版本的缓存目录中。\n\n### 验证和失败模式\n\n在 `require(candidate)` 之后，`validateNative(...)` 验证所需的导出（例如 `grep`、`glob`、`highlightCode`、`PtySession`、`Shell`、`getSystemInfo`、`getWorkProfile`、`invalidateFsScanCache`）。\n\n失败路径是明确的：\n\n- **不支持的平台标签**：抛出错误并附带支持的平台列表\n- **无可加载的候选项**：抛出错误并附带所有尝试过的路径和修复提示\n- **缺少导出**：抛出错误并附带确切缺少的名称和重新构建命令\n- **嵌入式提取错误**：记录目录/写入失败，并将其包含在最终加载诊断信息中\n\n**保证的契约（面向 API）：** 插件加载要么成功并返回经过验证的绑定集，要么快速失败并提供可操作的错误文本。\n\n**实现细节（可能变更）：** 确切的候选项搜索顺序和编译二进制文件回退路径排序。\n\n## 第三层：Rust N-API 模块层\n\n`crates/pi-natives/src/lib.rs` 是 Rust 入口模块，声明了导出的模块所有权：\n\n- `clipboard`\n- `fd`\n- `fs_cache`\n- `glob`\n- `glob_util`\n- `grep`\n- `highlight`\n- `html`\n- `image`\n- `keys`\n- `prof`\n- `ps`\n- `pty`\n- `shell`\n- `system_info`\n- `task`\n- `text`\n\n这些模块实现了由 `native.ts` 消费和验证的 N-API 符号。JS 层面的名称通过 `packages/natives/src` 中的 TS 包装器暴露。\n\n**保证的契约（面向 API）：** Rust 模块导出必须与 `validateNative` 和包装器模块期望的绑定名称匹配。\n\n**实现细节（可能变更）：** 内部 Rust 模块分解和辅助模块边界（`glob_util`、`task` 等）。\n\n## 所有权边界\n\n在架构层面，所有权划分如下：\n\n- **TS 包装器/API 所有权（`packages/natives/src`）**\n  - 公共 API 分组、选项类型化和稳定的 JS 开发体验\n  - 暴露给调用者的取消机制（`timeoutMs`、`AbortSignal`）\n- **加载器所有权（`packages/natives/src/native.ts`）**\n  - 运行时二进制文件选择\n  - CPU 变体选择和覆盖处理\n  - 编译二进制文件提取和候选项探测\n  - 所需原生导出的严格验证\n- **Rust 所有权（`crates/pi-natives/src`）**\n  - 算法和系统级实现\n  - 平台原生行为和性能敏感逻辑\n  - TS 包装器消费的 N-API 符号实现\n\n## 运行时流程（高层级）\n\n1. 使用者从 `@f5-sales-demo/pi-natives` 导入。\n2. 包装器模块调用单例 `native` 绑定。\n3. `native.ts` 为当前平台/架构/变体选择候选二进制文件。\n4. 对于编译后的分发，执行可选的嵌入式二进制文件提取。\n5. 加载插件并验证导出集。\n6. 包装器向调用者返回类型化的结果。\n\n## 术语表\n\n- **原生插件**：通过 Node-API (N-API) 加载的 `.node` 二进制文件。\n- **平台标签**：运行时元组 `platform-arch`（例如 `darwin-arm64`）。\n- **变体**：x64 CPU 特定的构建版本（`modern` AVX2、`baseline` 回退）。\n- **包装器**：在原始原生导出之上提供类型化 API 的 TS 函数/类。\n- **声明合并**：模块 `types.ts` 文件用于扩展 `NativeBindings` 的 TS 技术。\n- **编译二进制模式**：CLI 被打包的运行时模式，原生插件从提取/缓存路径而非仅从包本地路径解析。\n- **嵌入式插件**：生成到 `embedded-addon.ts` 中的构建产物元数据和文件引用，使编译后的二进制文件能够提取匹配的 `.node` 载荷。\n- **验证门**：`validateNative(...)` 检查，拒绝缺少所需导出的过时/不匹配的二进制文件。\n",
	"zh-cn/natives/natives-binding-contract.md": "---\ntitle: 原生绑定契约（TypeScript 侧）\ndescription: 通过 N-API 调用 Rust 原生函数的 TypeScript 侧绑定契约。\nsidebar:\n  order: 2\n  label: 绑定契约\ni18n:\n  sourceHash: 36dc5fed1f0a\n  translator: machine\n---\n\n# 原生绑定契约（TypeScript 侧）\n\n本文档定义了 `@f5-sales-demo/pi-natives` 调用方与已加载的 N-API 插件之间的 TypeScript 侧契约。\n\n主要聚焦于三个部分：\n\n1. 契约形状（`NativeBindings` + 模块增强），\n2. 包装器行为（`src/<module>/index.ts`），\n3. 公共导出表面（`src/index.ts`）。\n\n## 实现文件\n\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/system-info/types.ts`\n- `packages/natives/src/system-info/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/work/types.ts`\n- `packages/natives/src/work/index.ts`\n\n## 契约模型\n\n`packages/natives/src/bindings.ts` 定义了基础契约：\n\n- `NativeBindings`（基础接口，当前包含 `cancelWork(id: number): void`）\n- `Cancellable`（`timeoutMs?: number`，`signal?: AbortSignal`）\n- `TsFunc<T>` N-API 线程安全回调所使用的回调函数签名\n\n每个模块通过声明合并添加自己的字段：\n\n```ts\n// packages/natives/src/<module>/types.ts\ndeclare module \"../bindings\" {\n interface NativeBindings {\n  grep(options: GrepOptions, onMatch?: TsFunc<GrepMatch>): Promise<GrepResult>;\n }\n}\n```\n\n这使得在没有单一庞大中央类型文件的情况下，维护一个聚合绑定接口。\n\n## 声明合并生命周期与状态转换\n\n### 1）编译时类型组装\n\n- `bindings.ts` 提供基础 `NativeBindings` 符号。\n- 每个 `src/<module>/types.ts` 增强 `NativeBindings`。\n- `src/native.ts` 为了副作用导入所有 `./<module>/types` 文件，使合并后的契约在使用 `NativeBindings` 的位置处于作用域内。\n\n状态转换：**基础契约** → **合并契约**。\n\n### 2）运行时插件加载与验证关口\n\n- `src/native.ts` 加载候选的 `.node` 二进制文件。\n- 加载的对象被视为 `NativeBindings` 并立即通过 `validateNative(...)` 进行验证。\n- `validateNative` 通过 `typeof bindings[name] === \"function\"` 验证所需的导出键。\n\n状态转换：**不可信的插件对象** → **已验证的原生绑定对象**（或硬失败）。\n\n### 3）包装器调用\n\n- `src/<module>/index.ts` 中的模块包装器调用 `native.<export>`。\n- 包装器适配默认值和回调签名（将 `(err, value)` 转换为 JS API 中仅传值的回调模式）。\n- `src/index.ts` 将模块包装器/类型作为公共包 API 重新导出。\n\n状态转换：**已验证的原始绑定** → **符合人体工程学的公共 API**。\n\n## 包装器职责\n\n包装器刻意保持精简；它们不重新实现原生逻辑。\n\n主要职责：\n\n- **参数规范化/默认值设置**\n  - `glob()` 将 `options.path` 解析为绝对路径，并为 `hidden`、`gitignore`、`recursive` 设置默认值。\n  - `hasMatch()` 在调用原生函数前填充默认标志（`ignoreCase`、`multiline`）。\n- **回调适配**\n  - `grep()`、`glob()`、`executeShell()` 将 `TsFunc<T>`（`error, value`）转换为用户回调，仅接收成功的值。\n- **围绕原生调用的环境或策略行为**\n  - 剪贴板包装器添加 OSC52/Termux/无头环境处理，并将复制操作视为尽力而为。\n- **公共命名与重新导出策划**\n  - `searchContent()` 映射到原生导出 `search`。\n\n## 公共导出表面组织\n\n`packages/natives/src/index.ts` 是规范的公共桶文件。它按功能域分组导出：\n\n- 搜索/文本：`grep`、`glob`、`text`、`highlight`\n- 执行/进程/终端：`shell`、`pty`、`ps`、`keys`\n- 系统/媒体/转换：`image`、`html`、`clipboard`、`system-info`、`work`\n\n维护者规则：如果一个包装器没有从 `src/index.ts` 重新导出，则它不属于预期的公共包表面。\n\n## JS API ↔ 原生导出映射（代表性示例）\n\nRust 侧使用 N-API 导出名称（通常通过 `#[napi]` 将 snake_case 转换为 camelCase，偶尔使用显式别名），这些名称必须与这些绑定键匹配。\n\n| 类别 | 公共 JS API（包装器） | 原生绑定键 | 返回类型 | 是否异步？ |\n|---|---|---|---|---|\n| Grep | `grep(options, onMatch?)` | `grep` | `Promise<GrepResult>` | 是 |\n| Grep | `searchContent(content, options)` | `search` | `SearchResult` | 否 |\n| Grep | `hasMatch(content, pattern, opts?)` | `hasMatch` | `boolean` | 否 |\n| Grep | `fuzzyFind(options)` | `fuzzyFind` | `Promise<FuzzyFindResult>` | 是 |\n| Glob | `glob(options, onMatch?)` | `glob` | `Promise<GlobResult>` | 是 |\n| Glob | `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `void` | 否 |\n| Shell | `executeShell(options, onChunk?)` | `executeShell` | `Promise<ShellExecuteResult>` | 是 |\n| Shell | `Shell` | `Shell` | 类构造函数 | N/A |\n| PTY | `PtySession` | `PtySession` | 类构造函数 | N/A |\n| Text | `truncateToWidth(...)` | `truncateToWidth` | `string` | 否 |\n| Text | `sliceWithWidth(...)` | `sliceWithWidth` | `SliceWithWidthResult` | 否 |\n| Text | `visibleWidth(text)` | `visibleWidth` | `number` | 否 |\n| Highlight | `highlightCode(code, lang, colors)` | `highlightCode` | `string` | 否 |\n| HTML | `htmlToMarkdown(html, options?)` | `htmlToMarkdown` | `Promise<string>` | 是 |\n| System | `getSystemInfo()` | `getSystemInfo` | `SystemInfo` | 否 |\n| Work | `getWorkProfile(lastSeconds)` | `getWorkProfile` | `WorkProfile` | 否 |\n| Process | `killTree(pid, signal)` | `killTree` | `number` | 否 |\n| Process | `listDescendants(pid)` | `listDescendants` | `number[]` | 否 |\n| Clipboard | `copyToClipboard(text)` | `copyToClipboard` | `Promise<void>`（尽力而为的包装器行为） | 是 |\n| Clipboard | `readImageFromClipboard()` | `readImageFromClipboard` | `Promise<ClipboardImage \\| null>` | 是 |\n| Keys | `parseKey(data, kittyProtocolActive)` | `parseKey` | `string \\| null` | 否 |\n\n## 同步与异步契约差异\n\n契约混合了同步和异步 API；包装器保留原生调用风格，而非强制统一模型：\n\n- **基于 Promise 的异步导出**用于 I/O 或长时间运行的工作（`grep`、`glob`、`htmlToMarkdown`、`executeShell`、剪贴板、图像操作）。\n- **同步导出**用于确定性的内存内转换/解析器（`search`、`hasMatch`、高亮、文本宽度/切片、按键解析、进程查询）。\n- **构造函数导出**用于有状态的运行时对象（`Shell`、`PtySession`、`PhotonImage`）。\n\n对维护者的影响：更改现有导出的同步 ↔ 异步模式是跨包装器和调用方的破坏性 API 和契约变更。\n\n## 对象与枚举类型模式\n\n### 对象模式（`#[napi(object)]` 风格的 JS 对象）\n\nTypeScript 将对象形状的原生值建模为接口，例如：\n\n- `GrepResult`、`SearchResult`、`GlobResult`\n- `SystemInfo`、`WorkProfile`\n- `ClipboardImage`、`ParsedKittyResult`\n\n这些是编译时的结构化契约；运行时形状的正确性由原生实现负责。\n\n### 枚举模式\n\n数值型原生枚举在 TypeScript 中表示为 `const enum` 值：\n\n- `FileType`（`1=file`、`2=dir`、`3=symlink`）\n- `ImageFormat`（`0=PNG`、`1=JPEG`、`2=WEBP`、`3=GIF`）\n- `SamplingFilter`、`Ellipsis`、`KeyEventType`\n\n调用方看到命名的枚举成员；绑定边界传递的是数字。\n\n## 如何捕获不匹配\n\n不匹配检测在两个层面进行：\n\n1. **编译时 TypeScript 契约检查**\n   - 包装器针对合并后的 `NativeBindings` 调用 `native.<name>`。\n   - 缺失/重命名的绑定键会导致包装器中的 TS 类型检查失败。\n\n2. **`validateNative` 中的运行时验证**\n   - 加载后，`native.ts` 检查所需的导出，如果缺失则抛出异常。\n   - 错误消息包含缺失的键和重新构建的指示。\n\n这捕获了常见的二进制文件过期漂移：包装器/类型存在但加载的 `.node` 缺少该导出。\n\n## 失败行为与注意事项\n\n### 加载/验证失败（硬失败）\n\n- 插件加载失败或不支持的平台会在 `native.ts` 的模块初始化期间抛出异常。\n- 缺少所需导出会在包装器可用之前抛出异常。\n\n效果：包快速失败，而非将失败推迟到首次调用时。\n\n### 包装器级别的行为差异\n\n- 某些包装器有意地软化失败（`copyToClipboard` 是尽力而为的，会吞掉原生失败）。\n- 流式回调忽略回调的错误载荷，仅转发成功的值事件。\n\n### 类型级别的注意事项（运行时比 TS 更严格）\n\n- TS 可选字段不能保证语义有效性；原生层仍然可以拒绝格式错误的值。\n- `const enum` 类型不能阻止来自运行时未类型化调用方的超出范围的数值。\n- `validateNative` 仅检查所需导出的存在性/是否为函数，不检查深层的参数/返回值形状兼容性。\n- `bindings.ts` 在基础接口中包含 `cancelWork(id)`，但当前的运行时验证列表并未强制检查该键。\n\n## 绑定变更的维护者检查清单\n\n添加/更改导出时，需更新以下所有内容：\n\n1. `src/<module>/types.ts`（增强 + 契约类型）\n2. `src/<module>/index.ts`（包装器行为）\n3. `src/native.ts` 中的模块类型导入（如果是新模块）\n4. `validateNative` 所需导出检查\n5. `src/index.ts` 公共重新导出\n\n跳过任何步骤都会导致编译时漂移或运行时加载失败。\n",
	"zh-cn/natives/natives-build-release-debugging.md": "---\ntitle: 原生模块构建、发布与调试运行手册\ndescription: Rust 原生插件在各平台上的构建、发布与调试运行手册。\nsidebar:\n  order: 8\n  label: 构建、发布与调试\ni18n:\n  sourceHash: efe47aa5b466\n  translator: machine\n---\n\n# 原生模块构建、发布与调试运行手册\n\n本运行手册描述了 `@f5-sales-demo/pi-natives` 构建流水线如何生成 `.node` 插件、编译后的发行版如何加载它们，以及如何调试加载器/构建故障。\n\n本文档遵循 `docs/natives-architecture.md` 中的架构术语：\n\n- **构建时制品生产** (`scripts/build-native.ts`)\n- **嵌入式插件清单生成** (`scripts/embed-native.ts`)\n- **运行时插件加载 + 验证门控** (`src/native.ts`)\n\n## 实现文件\n\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `packages/natives/src/native.ts`\n- `crates/pi-natives/Cargo.toml`\n\n## 构建流水线概述\n\n### 1) 构建入口\n\n`packages/natives/package.json` 脚本：\n\n- `bun scripts/build-native.ts` (`build`) → 发布构建\n- `bun scripts/build-native.ts --dev` (`dev:native`) → 调试/开发配置构建（输出命名相同）\n- `bun scripts/embed-native.ts` (`embed:native`) → 从构建文件生成 `src/embedded-addon.ts`\n\n### 2) Rust 制品构建\n\n`build-native.ts` 在 `crates/pi-natives` 中运行 Cargo：\n\n- 基础命令：`cargo build`\n- 发布模式添加 `--release`，除非传入了 `--dev`\n- 交叉编译目标添加 `--target <CROSS_TARGET>`\n\n`crates/pi-natives/Cargo.toml` 声明了 `crate-type = [\"cdylib\"]`，因此 Cargo 会输出一个共享库（`.so`/`.dylib`/`.dll`），然后将其复制/重命名为 `.node` 插件文件名。\n\n### 3) 制品发现与安装\n\nCargo 完成后，`build-native.ts` 按顺序扫描候选输出目录：\n\n1. `${CARGO_TARGET_DIR}`（如果已设置）\n2. `<repo>/target`\n3. `crates/pi-natives/target`\n\n对于每个根目录，它会检查配置目录：\n\n- 交叉构建：`<root>/<crossTarget>/<profile>` 然后 `<root>/<profile>`\n- 本地构建：`<root>/<profile>`\n\n然后查找以下文件之一：\n\n- `libpi_natives.so`\n- `libpi_natives.dylib`\n- `pi_natives.dll`\n- `libpi_natives.dll`\n\n找到后，使用临时文件 + 重命名语义原子性地安装到 `packages/natives/native/`（Windows 回退会显式处理锁定的 DLL 替换失败）。\n\n## 目标/变体模型与命名约定\n\n## 平台标签\n\n构建和运行时都使用平台标签：\n\n`<platform>-<arch>`（示例：`darwin-arm64`、`linux-x64`）\n\n## 变体模型（仅 x64）\n\nx64 支持 CPU 变体：\n\n- `modern`（支持 AVX2 的路径）\n- `baseline`（回退路径）\n\n非 x64 使用单一默认制品（无变体后缀）。\n\n### 输出文件名\n\n发布构建：\n\n- x64：`pi_natives.<platform>-<arch>-modern.node` 或 `...-baseline.node`\n- 非 x64：`pi_natives.<platform>-<arch>.node`\n\n开发构建（`--dev`）：\n\n- 使用调试配置标志，但保持标准的平台标签输出命名\n\n`native.ts` 中运行时加载器的候选顺序：\n\n- 发布候选\n- 编译模式在包本地文件之前插入提取/缓存候选\n\n## 环境标志与构建选项\n\n## 运行时标志\n\n- `PI_DEV`（加载器行为）：启用加载器诊断信息\n- `PI_NATIVE_VARIANT`（加载器行为，仅 x64）：在运行时强制选择 `modern` 或 `baseline`\n- `PI_COMPILED`（加载器行为）：启用编译二进制候选/提取行为\n\n## 构建时标志/选项\n\n- `--dev`（脚本参数）：构建调试配置\n- `CROSS_TARGET`：传递给 Cargo `--target`\n- `TARGET_PLATFORM`：覆盖输出平台标签命名\n- `TARGET_ARCH`：覆盖输出架构命名\n- `TARGET_VARIANT`（仅 x64）：为输出文件名和 RUSTFLAGS 策略强制指定 `modern` 或 `baseline`\n- `CARGO_TARGET_DIR`：搜索 Cargo 输出时的额外根目录\n- `RUSTFLAGS`：\n  - 如果未设置且非交叉编译，脚本设置为：\n    - modern：`-C target-cpu=x86-64-v3`\n    - baseline：`-C target-cpu=x86-64-v2`\n    - 非 x64 / 无变体：`-C target-cpu=native`\n  - 如果已设置，脚本不会覆盖\n\n## 构建状态/生命周期转换\n\n### 构建生命周期 (`build-native.ts`)\n\n1. **初始化**：解析参数/环境变量（`--dev`、目标覆盖、交叉编译标志）\n2. **变体解析**：\n   - 非 x64 → 无变体\n   - x64 + `TARGET_VARIANT` → 显式变体\n   - x64 交叉构建且无 `TARGET_VARIANT` → 严重错误\n   - x64 本地构建且无覆盖 → 检测主机 AVX2\n3. **编译**：使用解析后的配置/目标运行 Cargo\n4. **定位制品**：扫描目标根目录/配置目录/库名称\n5. **安装**：复制 + 原子重命名到 `packages/natives/native`\n6. **完成**：插件就绪可供加载器候选使用\n\n任何阶段的失败都会以明确的错误文本退出（无效变体、cargo 构建失败、缺少输出库、安装/重命名失败）。\n\n### 嵌入生命周期 (`embed-native.ts`)\n\n1. **初始化**：从 `TARGET_PLATFORM`/`TARGET_ARCH` 或主机值计算平台标签\n2. **候选集合**：\n   - x64 期望同时拥有 `modern` 和 `baseline`\n   - 非 x64 期望一个默认文件\n3. **验证可用性**：检查 `packages/natives/native`\n4. **生成清单**（`src/embedded-addon.ts`）：包含 Bun `file` 导入和包版本\n5. **运行时提取就绪**：可用于编译模式\n\n`--reset` 跳过验证并写入空清单存根（`embeddedAddon = null`）。\n\n## 开发工作流与发布/编译行为\n\n## 本地开发工作流\n\n典型的本地循环：\n\n1. 构建插件：\n   - 发布：`bun --cwd=packages/natives run build`\n   - 调试配置：`bun --cwd=packages/natives run dev:native`\n2. 测试加载器诊断时设置 `PI_DEV=1`\n3. `native.ts` 中的加载器解析包本地 `native/`（以及可执行文件目录回退）候选\n4. `validateNative` 在包装器使用绑定之前强制执行导出兼容性检查\n\n## 发布/编译二进制工作流\n\n在编译模式下（`PI_COMPILED` 或 Bun 嵌入标记）：\n\n1. 加载器计算版本化缓存目录：`<getNativesDir()>/<packageVersion>`（实际为 `~/.xcsh/natives/<version>`）\n2. 如果嵌入清单匹配当前平台+版本，加载器可能将选定的嵌入文件提取到该版本化目录\n3. 运行时候选顺序包括：\n   - 版本化缓存目录\n   - 旧版编译二进制目录（Windows 上为 `%LOCALAPPDATA%/xcsh`，其他系统为 `~/.local/bin`）\n   - 包/可执行文件目录\n4. 首个成功加载的插件仍需通过 `validateNative` 验证\n\n这就是为什么打包 + 运行时加载器的期望必须一致：文件名、平台标签和导出符号必须与 `native.ts` 探测和验证的内容匹配。\n\n## JS API ↔ Rust 导出映射（验证门控子集）\n\n`native.ts` 要求加载的插件上存在以下 JS 可见的导出。它们映射到 `crates/pi-natives/src` 中的 Rust N-API 导出：\n\n| `validateNative` 要求的 JS 名称 | Rust 导出声明 | Rust 源文件 |\n| --- | --- | --- |\n| `glob` | `#[napi] pub fn glob(...)` | `crates/pi-natives/src/glob.rs` |\n| `grep` | `#[napi] pub fn grep(...)` | `crates/pi-natives/src/grep.rs` |\n| `search` | `#[napi] pub fn search(...)` | `crates/pi-natives/src/grep.rs` |\n| `highlightCode` | `#[napi] pub fn highlight_code(...)` | `crates/pi-natives/src/highlight.rs` |\n| `getSystemInfo` | `#[napi] pub fn get_system_info(...)` | `crates/pi-natives/src/system_info.rs` |\n| `getWorkProfile` | `#[napi] pub fn get_work_profile(...)`（驼峰命名导出） | `crates/pi-natives/src/prof.rs` |\n| `invalidateFsScanCache` | `#[napi] pub fn invalidate_fs_scan_cache(...)` | `crates/pi-natives/src/fs_cache.rs` |\n\n如果任何必需的符号缺失，加载器会快速失败并提示重新构建。\n\n## 故障行为与诊断\n\n## 构建时故障\n\n- 无效的变体配置：\n  - 在非 x64 上设置 `TARGET_VARIANT` → 立即报错\n  - x64 交叉构建且无显式 `TARGET_VARIANT` → 立即报错\n- Cargo 构建失败：\n  - 脚本输出非零退出码和 stderr\n- 未找到制品：\n  - 脚本打印所有已检查的配置目录\n- 安装失败：\n  - 明确的消息；Windows 包含文件锁定提示\n\n## 运行时加载器故障 (`native.ts`)\n\n- 不支持的平台标签：\n  - 抛出异常并列出支持的平台列表\n- 无法加载任何候选：\n  - 抛出异常并列出完整的候选错误列表和特定模式的修复提示\n- 缺少导出：\n  - 抛出异常并列出确切的缺失符号名称和重新构建命令\n- 嵌入提取问题：\n  - mkdir/write 错误被记录并包含在最终诊断信息中\n\n## 故障排除矩阵\n\n| 症状 | 可能原因 | 验证方法 | 修复方法 |\n| --- | --- | --- | --- |\n| `Native addon missing exports ... Missing: <name>` | 过时的 `.node` 二进制文件、Rust 导出名称不匹配或加载了错误的二进制文件 | 使用 `PI_DEV=1` 运行以查看加载路径；检查该文件的导出列表 | 重新构建 `build`；确保 Rust `#[napi]` 导出名称（或需要时的显式别名）与 JS 键匹配；删除过时的缓存/版本化文件 |\n| x64 机器在期望 modern 时加载了 baseline | `PI_NATIVE_VARIANT=baseline`、未检测到 AVX2 或仅存在 baseline 文件 | 检查 `PI_NATIVE_VARIANT`；检查 `native/` 中是否有 `-modern` 文件 | 构建 modern 变体（`TARGET_VARIANT=modern ... build`）并确保文件已包含在发行版中 |\n| 交叉构建产生了不可用/标签错误的二进制文件 | `CROSS_TARGET` 与 `TARGET_PLATFORM`/`TARGET_ARCH` 不匹配，或 x64 缺少 `TARGET_VARIANT` | 确认环境变量组合和输出文件名 | 使用一致的环境变量值和显式的 x64 `TARGET_VARIANT` 重新运行 |\n| 升级后编译二进制文件失败 | 过时的提取缓存（`~/.xcsh/natives/<old-or-mismatched-version>`）或嵌入清单不匹配 | 检查版本化 natives 目录和加载器错误列表 | 删除对应包版本的版本化 natives 缓存并重新运行；在打包过程中重新生成嵌入清单 |\n| 加载器探测多个路径但均无法工作 | 平台不匹配或包 `native/` 中缺少发布制品 | 检查 `platformTag` 与实际文件名是否匹配 | 确保构建的文件名完全匹配 `pi_natives.<platform>-<arch>(-variant).node` 约定，且包中包含 `native/` |\n| `embed:native` 失败并报 \"Incomplete native addons\" | 在嵌入之前未构建所需的变体文件 | 检查错误文本中的期望与实际列表 | 先构建所需文件（x64：modern+baseline 两个；非 x64：默认文件），然后重新运行 `embed:native` |\n\n## 操作命令\n\n```bash\n# Release artifact for current host\nbun --cwd=packages/natives run build\n\n# Debug profile artifact build\nbun --cwd=packages/natives run dev:native\n\n# Build explicit x64 variants\nTARGET_VARIANT=modern bun --cwd=packages/natives run build\nTARGET_VARIANT=baseline bun --cwd=packages/natives run build\n\n# Generate embedded addon manifest from built native files\nbun --cwd=packages/natives run embed:native\n\n# Reset embedded manifest to null stub\nbun --cwd=packages/natives run embed:native -- --reset\n```\n",
	"zh-cn/natives/natives-media-system-utils.md": "---\ntitle: 原生媒体与系统工具\ndescription: 用于截图、图像处理和系统信息的原生媒体处理工具。\nsidebar:\n  order: 7\n  label: 媒体与系统工具\ni18n:\n  sourceHash: 430898c177bc\n  translator: machine\n---\n\n# 原生媒体 + 系统工具\n\n本文档是 [`docs/natives-architecture.md`](./natives-architecture.md) 中描述的**系统/媒体/转换原语**层的子系统深入解析，涵盖 `image`、`html`、`clipboard` 和 `work` 性能分析。\n\n## 实现文件\n\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/prof.rs`\n- `crates/pi-natives/src/task.rs`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/work/index.ts`\n- `packages/natives/src/work/types.ts`\n\n> 注意：不存在 `crates/pi-natives/src/work.rs`；工作性能分析在 `prof.rs` 中实现，由 `task.rs` 中的插桩提供数据。\n\n## TS API ↔ Rust 导出/模块映射\n\n| TS 导出 (packages/natives)                  | Rust N-API 导出                                                         | Rust 模块                             |\n| ------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------- |\n| `PhotonImage.parse(bytes)`                  | `PhotonImage::parse`                                                     | `image.rs`                            |\n| `PhotonImage#resize(width, height, filter)` | `PhotonImage::resize`                                                    | `image.rs`                            |\n| `PhotonImage#encode(format, quality)`       | `PhotonImage::encode`                                                    | `image.rs`                            |\n| `htmlToMarkdown(html, options)`             | `html_to_markdown`                                                       | `html.rs`                             |\n| `copyToClipboard(text)`                     | `copy_to_clipboard` + TS 回退逻辑                                        | `clipboard.rs` + `clipboard/index.ts` |\n| `readImageFromClipboard()`                  | `read_image_from_clipboard`                                              | `clipboard.rs`                        |\n| `getWorkProfile(lastSeconds)`               | `get_work_profile`                                                      | `prof.rs`                             |\n\n## 数据格式边界与转换\n\n### 图像 (`image`)\n\n- **JS 输入边界**：`Uint8Array` 编码的图像字节。\n- **Rust 解码边界**：字节被复制到 `Vec<u8>`，通过 `ImageReader::with_guessed_format()` 猜测格式，然后解码为 `DynamicImage`。\n- **内存中状态**：`PhotonImage` 存储 `Arc<DynamicImage>`。\n- **输出边界**：`encode(format, quality)` 返回 `Promise<Uint8Array>`（Rust `Vec<u8>`）。\n\n格式 ID 为数字：\n\n- `0`：PNG\n- `1`：JPEG\n- `2`：WebP（无损编码器）\n- `3`：GIF\n\n约束条件：\n\n- `quality` 仅用于 JPEG。\n- PNG/WebP/GIF 忽略 `quality`。\n- 不支持的格式 ID 会失败（`Invalid image format: <id>`）。\n\n### HTML 转换 (`html`)\n\n- **JS 输入边界**：HTML `string` + 可选对象 `{ cleanContent?: boolean; skipImages?: boolean }`。\n- **Rust 转换边界**：`String` 输入通过 `html_to_markdown_rs::convert` 转换。\n- **输出边界**：Markdown `string`。\n\n转换行为：\n\n- `cleanContent` 默认为 `false`。\n- 当 `cleanContent=true` 时，启用预处理，使用 `PreprocessingPreset::Aggressive` 以及导航/表单的强制移除标志。\n- `skipImages` 默认为 `false`。\n\n### 剪贴板 (`clipboard`)\n\n- **文本路径**：\n  - 当 stdout 是 TTY 时，TS 首先发出 OSC 52（`\\x1b]52;c;<base64>\\x07`）。\n  - 然后尝试通过原生剪贴板 API（`native.copyToClipboard`）以尽力而为的方式复制相同文本。\n  - 在 Termux 上，TS 首先尝试 `termux-clipboard-set`。\n- **图像读取路径**：\n  - Rust 从 `arboard` 读取原始图像。\n  - Rust 将其重新编码为 PNG 字节（`image` crate），返回 `{ data: Uint8Array, mimeType: \"image/png\" }`。\n  - 在 Termux 或没有显示服务器（缺少 `DISPLAY`/`WAYLAND_DISPLAY`）的 Linux 会话中，TS 提前返回 `null`。\n\n### 工作性能分析 (`work`)\n\n- **采集边界**：性能分析样本由 `task::blocking` 和 `task::future` 中的 `profile_region(tag)` 守卫产生。\n- **存储格式**：固定大小的循环缓冲区（`MAX_SAMPLES = 10_000`），存储调用栈路径 + 持续时间（`μs`）+ 时间戳（`自进程启动以来的 μs`）。\n- **输出边界**：`getWorkProfile(lastSeconds)` 返回对象：\n  - `folded`：折叠栈文本（火焰图输入）\n  - `summary`：Markdown 表格摘要\n  - `svg`：可选的火焰图 SVG\n  - `totalMs`、`sampleCount`\n\n## 生命周期与状态转换\n\n### 图像生命周期\n\n1. `PhotonImage.parse(bytes)` 调度一个阻塞解码任务（`image.decode`）。\n2. 成功后，JS 中存在一个原生 `PhotonImage` 句柄。\n3. `resize(...)` 创建一个新的原生句柄（`image.resize`），新旧句柄可以共存。\n4. `encode(...)` 生成字节（`image.encode`），不会修改图像尺寸。\n\n失败转换：\n\n- 格式检测/解码失败会拒绝 parse promise。\n- 编码失败会拒绝 encode promise。\n- 无效的格式 ID 会拒绝 encode promise。\n\n### HTML 生命周期\n\n1. `htmlToMarkdown(html, options)` 调度一个阻塞转换任务。\n2. 转换使用默认选项（`cleanContent=false`、`skipImages=false`）运行，除非明确指定。\n3. 返回 Markdown 字符串或拒绝。\n\n失败转换：\n\n- 转换器失败返回被拒绝的 promise（`Conversion error: ...`）。\n\n### 剪贴板生命周期\n\n`copyToClipboard(text)` 有意采用尽力而为的多路径策略：\n\n1. 如果是 TTY：尝试 OSC 52 写入（base64 载荷）。\n2. 当设置了 `TERMUX_VERSION` 时尝试 Termux 命令。\n3. 尝试原生 `arboard` 文本复制。\n4. 在 TS 层吞掉错误。\n\n`readImageFromClipboard()` 在不同阶段的严格程度不同：\n\n1. TS 对不支持的运行时上下文（Termux/无头 Linux）硬性门控为 `null`。\n2. Rust `arboard` 读取仅在 TS 允许时运行。\n3. `ContentNotAvailable` 映射为 `null`。\n4. 其他 Rust 错误会拒绝。\n\n### 工作性能分析生命周期\n\n1. 无需显式启动：当任务辅助函数执行时，性能分析始终处于开启状态。\n2. 每个被插桩的任务作用域在守卫析构时记录一个样本。\n3. 缓冲区容量达到上限后，样本会覆盖最旧的条目。\n4. `getWorkProfile(lastSeconds)` 读取一个时间窗口并生成折叠栈/摘要/SVG 产物。\n\n失败转换：\n\n- SVG 生成失败是软性失败（`svg: null`），折叠栈和摘要仍然返回。\n- 空的样本窗口返回空的折叠数据和 `svg: null`，而非错误。\n\n## 不支持的操作与错误传播\n\n### 图像\n\n- 不支持的解码输入或损坏的字节：严格失败（promise 拒绝）。\n- 不支持的编码格式 ID：严格失败。\n- TS 包装器中没有尽力而为的回退路径。\n\n### HTML\n\n- 转换错误为严格失败（拒绝）。\n- 选项省略为尽力而为的默认值处理，不会失败。\n\n### 剪贴板\n\n- 文本复制在 TS 层为尽力而为：操作失败会被抑制。\n- 图像读取区分\"无图像\"（`null`）和操作失败（拒绝）。\n- Termux/无头 Linux 被视为图像读取的不支持上下文（`null`）。\n\n### 工作性能分析\n\n- 函数调用本身的检索是严格的，但产物生成部分为尽力而为（`svg` 可为空）。\n- 缓冲区截断是预期行为（环形缓冲区），不是数据丢失的 bug。\n\n## 平台注意事项\n\n- **剪贴板文本**：OSC 52 依赖于终端支持；原生剪贴板访问依赖于桌面环境/会话。\n- **剪贴板图像读取**：在 Termux 和没有显示服务器的 Linux 中，在 TS 层被阻止。\n",
	"zh-cn/natives/natives-rust-task-cancellation.md": "---\ntitle: 原生 Rust 任务执行与取消\ndescription: 基于协作式取消和清理语义的 Rust 异步任务执行模型。\nsidebar:\n  order: 5\n  label: 任务取消\ni18n:\n  sourceHash: 0fbf45c6d463\n  translator: machine\n---\n\n# 原生 Rust 任务执行与取消 (`pi-natives`)\n\n本文档描述 `crates/pi-natives` 如何调度原生工作，以及取消操作如何从 JS 选项（`timeoutMs`、`AbortSignal`）传递到 Rust 执行层。\n\n## 实现文件\n\n- `crates/pi-natives/src/task.rs`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/fd.rs`\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/ps.rs`\n\n## 核心原语 (`task.rs`)\n\n`task.rs` 定义了三个核心组件：\n\n1. `task::blocking(tag, cancel_token, work)`\n   - 封装 `napi::AsyncTask` / `Task`。\n   - `compute()` 在 libuv 工作线程上运行（用于 CPU 密集型或阻塞/同步系统调用）。\n   - 返回 JS `Promise<T>`。\n\n2. `task::future(env, tag, work)`\n   - 封装 `env.spawn_future(...)`。\n   - 在 Tokio 运行时上运行异步工作。\n   - 返回 `PromiseRaw<'env, T>`。\n\n3. `CancelToken` / `AbortToken` / `AbortReason`\n   - `CancelToken::new(timeout_ms, signal)` 组合截止时间和可选的 `AbortSignal`。\n   - `CancelToken::heartbeat()` 用于阻塞循环中的协作式取消。\n   - `CancelToken::wait()` 是异步取消等待（`Signal` / `Timeout` / `User` Ctrl-C）。\n   - `AbortToken` 允许外部代码请求中止（`abort(reason)`）。\n\n## `blocking` vs `future`：执行模型与选择\n\n### 使用 `task::blocking`\n\n当工作是 CPU 密集型或本质上是同步/阻塞的时使用：\n\n- 正则/文件扫描（`grep`、`glob`、`fuzzy_find`）\n- 同步 PTY 循环内部逻辑（通过 `spawn_blocking` 的 `run_pty_sync`）\n- 剪贴板/图像/HTML 转换\n\n行为：\n\n- 工作闭包接收一个克隆的 `CancelToken`。\n- 仅在代码检查 `ct.heartbeat()?` 时才会观察到取消。\n- 闭包返回 `Err(...)` 会拒绝 JS promise。\n\n### 使用 `task::future`\n\n当工作必须 `await` 异步操作时使用：\n\n- shell 会话编排（`shell.run`、`executeShell`）\n- 任务竞争（`tokio::select!`）——完成与取消之间的竞争\n\n行为：\n\n- Future 可以将正常完成与 `ct.wait()` 进行竞争。\n- 在取消路径上，异步实现通常将取消传播到内部子系统（例如 `tokio_util::CancellationToken`），并可选择在宽限超时后强制中止。\n\n## JS API ↔ Rust 导出映射（任务/取消相关）\n\n| JS 端 API | Rust 导出 (`#[napi]`) | 调度器 | 取消挂钩 |\n|---|---|---|---|\n| `grep(options, onMatch?)` | `grep` | `task::blocking(\"grep\", ct, ...)` | `CancelToken::new(options.timeoutMs, options.signal)` + `ct.heartbeat()` |\n| `glob(options, onMatch?)` | `glob` | `task::blocking(\"glob\", ct, ...)` | `CancelToken::new(...)` + 过滤循环中的 `ct.heartbeat()` |\n| `fuzzyFind(options)` | `fuzzy_find` | `task::blocking(\"fuzzy_find\", ct, ...)` | `CancelToken::new(...)` + 评分循环中的 `ct.heartbeat()` |\n| `shell.run(options, onChunk?)` | `Shell::run` | `task::future(env, \"shell.run\", ...)` | `ct.wait()` 与运行任务竞争；桥接到 Tokio `CancellationToken` |\n| `executeShell(options, onChunk?)` | `execute_shell` | `task::future(env, \"shell.execute\", ...)` | 同上 |\n| `pty.start(options, onChunk?)` | `PtySession::start` | `task::future(env, \"pty.start\", ...)` + 内部 `spawn_blocking` | 在同步 PTY 循环中通过 `heartbeat()` 检查 `CancelToken` |\n| `htmlToMarkdown(html, options?)` | `html_to_markdown` | `task::blocking(\"html_to_markdown\", (), ...)` | 无（`()` 令牌） |\n| `PhotonImage.parse/encode/resize` | `PhotonImage::{parse,encode,resize}` | `task::blocking(...)` | 无（`()` 令牌） |\n| `copyToClipboard/readImageFromClipboard` | `copy_to_clipboard` / `read_image_from_clipboard` | `task::blocking(...)` | 无（`()` 令牌） |\n\n`text.rs` 和 `ps.rs` 目前不使用 `task::blocking`/`task::future`，因此不参与此取消路径。\n\n## 取消生命周期与状态转换\n\n### `CancelToken` 生命周期\n\n`CancelToken` 是协作式且有状态的：\n\n```text\nCreated\n  ├─ no signal + no timeout  -> passive token (never aborts unless externally emplaced)\n  ├─ signal registered        -> waits for AbortSignal callback\n  └─ deadline set             -> timeout check becomes active\n\nRunning\n  ├─ heartbeat()/wait() sees signal   -> AbortReason::Signal\n  ├─ heartbeat()/wait() sees deadline -> AbortReason::Timeout\n  ├─ wait() sees Ctrl-C               -> AbortReason::User\n  └─ no abort                         -> continue\n\nAborted (terminal)\n  └─ first abort reason wins (atomic flag + notifier)\n```\n\n### 启动前 vs 执行中取消\n\n- **启动前/首次取消检查之前**：\n  - 使用 `task::future` 且在 `ct.wait()` 上竞争的用户，一旦进入 `select!` 即可立即解决取消。\n  - 使用 `task::blocking` 的用户仅在闭包代码到达 `heartbeat()` 时才观察到取消。如果闭包没有提前进行心跳检查，取消会被延迟。\n\n- **执行中**：\n  - `blocking`：下一次 `heartbeat()` 返回 `Err(\"Aborted: ...\")`。\n  - `future`：`ct.wait()` 分支赢得 `select!`，然后代码取消从属异步机制（对于 shell：取消 Tokio 令牌，等待最多 2 秒，然后中止任务）。\n\n## 长时间运行循环的心跳预期\n\n`heartbeat()` 必须在具有无界或大型工作集的循环中以可预测的节奏运行。\n\n已观察到的模式：\n\n- `glob::filter_entries`：在过滤/匹配之前检查每个条目。\n- `fd::score_entries`：检查每个扫描的候选项。\n- `grep_sync`：在繁重的搜索阶段之前进行显式取消检查，加上同样接收令牌的 fs-cache 调用。\n- `run_pty_sync`：每个循环周期检查一次（约 16ms 睡眠节奏），取消时终止子进程。\n\n实践规则：任何遍历外部大小输入的循环，不应在没有心跳的情况下超过短暂的有界间隔。\n\n## 失败行为与向 JS 的错误传播\n\n### 阻塞任务\n\n错误路径：\n\n1. 闭包返回 `Err(napi::Error)`（包括 `heartbeat()` 中止）。\n2. `Task::compute()` 返回 `Err`。\n3. `AsyncTask` 拒绝 JS promise。\n\n典型错误字符串：\n\n- `Aborted: Timeout`\n- `Aborted: Signal`\n- 领域错误（`Failed to decode image: ...`、`Conversion error: ...` 等）\n\n### Future 任务\n\n错误路径：\n\n1. 异步体返回 `Err(napi::Error)` 或 join 失败被映射为（`... task failed: {err}`）。\n2. `task::future` 生成的 promise 被拒绝。\n3. 某些 API 有意返回结构化的取消结果而非拒绝（`ShellRunResult`/`ShellExecuteResult` 带有 `cancelled`/`timed_out` 标志和 `exit_code: None`）。\n\n### 取消报告方式的划分\n\n- **中止作为错误**：大多数使用 `heartbeat()?` 的阻塞导出。\n- **中止作为类型化结果**：shell/pty 风格的命令 API，在结果结构体中建模取消。\n\n为每个 API 选择一种模型并明确记录。\n\n## 常见陷阱\n\n1. **阻塞循环中缺少心跳**\n   - 症状：超时/信号似乎被忽略，直到循环结束。\n   - 修复：在循环顶部和昂贵的逐项步骤之前添加 `ct.heartbeat()?`。\n\n2. **长时间不可取消的段落**\n   - 症状：在单个大型调用（解码、排序、压缩等）期间取消延迟飙升。\n   - 修复：将工作拆分为带有心跳边界的块；如果不可能，请记录延迟。\n\n3. **阻塞异步执行器**\n   - 症状：当同步密集代码直接在 future 中运行时，异步 API 停滞。\n   - 修复：将 CPU/同步块移到 `task::blocking` 或 `tokio::task::spawn_blocking`。\n\n4. **不一致的取消语义**\n   - 症状：一个 API 在取消时拒绝，另一个用标志解决，使调用者困惑。\n   - 修复：按领域标准化，并保持包装器文档对齐。\n\n5. **在嵌套异步任务中忘记取消桥接**\n   - 症状：外部令牌已取消，但内部读取器/子进程任务仍在运行。\n   - 修复：将取消桥接到内部令牌/信号，并实施宽限超时 + 强制中止回退。\n\n## 新建可取消导出的检查清单\n\n1. 正确分类工作：\n   - CPU 密集型或同步阻塞 -> `task::blocking`\n   - 异步 I/O / `await` 编排 -> `task::future`\n\n2. 在需要时暴露取消输入：\n   - 在 `#[napi(object)]` 选项中包含 `timeoutMs` 和 `signal`\n   - 创建 `let ct = task::CancelToken::new(timeout_ms, signal);`\n\n3. 在所有层级中串联取消：\n   - 阻塞循环：以稳定间隔调用 `ct.heartbeat()?`\n   - 异步编排：与 `ct.wait()` 竞争并取消子任务/令牌\n\n4. 确定取消契约：\n   - 以中止错误拒绝 promise，或\n   - 解决为类型化 `{ cancelled, timedOut, ... }`\n   - 在 API 家族中保持此契约一致\n\n5. 带上下文传播失败：\n   - 通过 `Error::from_reason(format!(\"...: {err}\"))` 映射错误\n   - 包含阶段特定前缀（`spawn`、`decode`、`wait` 等）\n\n6. 处理启动前和执行中取消：\n   - 取消检查/等待必须在昂贵的主体之前和长时间执行期间发生\n\n7. 验证无执行器滥用：\n   - 不在异步 future 内部直接执行长时间同步工作，除非使用 `spawn_blocking`/阻塞任务包装器\n",
	"zh-cn/natives/natives-shell-pty-process.md": "---\ntitle: 原生层 Shell、PTY、进程与按键内部机制\ndescription: 原生层中的 Shell 执行、PTY 管理、进程生命周期和按键事件处理。\nsidebar:\n  order: 4\n  label: Shell、PTY 与进程\ni18n:\n  sourceHash: 00ea95614c6a\n  translator: machine\n---\n\n# 原生层 Shell、PTY、进程与按键内部机制\n\n本文档涵盖 `@f5-sales-demo/pi-natives` 中的**执行/进程/终端原语**：`shell`、`pty`、`ps` 和 `keys`，使用 `docs/natives-architecture.md` 中的架构术语。\n\n## 实现文件\n\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/shell/windows.rs`（仅 Windows）\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/ps.rs`\n- `crates/pi-natives/src/keys.rs`\n- `crates/pi-natives/src/task.rs`（shell/pty 共用的取消行为）\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/bindings.ts`\n\n## 层级职责\n\n- **TS 封装/API 层**（`packages/natives/src/*`）：类型化入口点、取消接口（`timeoutMs`、`AbortSignal`）以及 JS 人体工程学设计。\n- **Rust N-API 模块层**（`crates/pi-natives/src/*`）：shell/PTY 进程执行、进程树遍历/终止以及按键序列解析。\n- **验证门控**（`native.ts`，架构级）：确保所需导出（`Shell`、`executeShell`、`PtySession`、`killTree`、`listDescendants`、按键辅助函数）在封装器使用前存在。\n\n## Shell 子系统（`shell`）\n\n### API 模型\n\n暴露两种执行模式：\n\n1. **一次性执行**，通过 `executeShell(options, onChunk?)`。\n2. **持久会话**，通过 `new Shell(options?)` 然后重复调用 `shell.run(...)`。\n\n两者都通过线程安全回调流式传输输出，并返回 `{ exitCode?, cancelled, timedOut }`。\n\n### 会话创建与环境模型\n\nRust 创建 `brush_core::Shell` 时使用：\n\n- 非交互模式，\n- `do_not_inherit_env: true`，\n- 从宿主环境显式重建环境变量，\n- 跳过对 shell 敏感的变量（`PS1`、`PWD`、`SHLVL`、bash 函数导出等）。\n\n会话环境行为：\n\n- `ShellOptions.sessionEnv` 在会话创建时应用一次。\n- `ShellRunOptions.env` 的作用域为命令级（`EnvironmentScope::Command`），每次运行后弹出。\n- `PATH` 在 Windows 上进行特殊合并，采用大小写不敏感的去重。\n\nWindows 专有路径增强（`shell/windows.rs`）：如果发现 Git-for-Windows 路径（`cmd`、`bin`、`usr/bin`）且尚未包含，则将其追加。\n\n### 运行时生命周期与状态转换\n\n持久 shell（`Shell.run`）使用以下状态机：\n\n- **空闲/未初始化**：`session: None`。\n- **运行中**：首次 `run()` 延迟创建会话，存储 `current_abort` 令牌，执行命令。\n- **完成 + 保活**：如果执行控制流为 `Normal`，则清除 `current_abort` 并复用会话。\n- **完成 + 清理**：如果控制流与循环/脚本/shell 退出相关（`BreakLoop`、`ContinueLoop`、`ReturnFromFunctionOrScript`、`ExitShell`），则丢弃会话（`session: None`）。\n- **已取消/已超时**：取消运行任务，等待宽限期（2 秒），然后强制中止；会话被丢弃。\n- **错误**：会话被丢弃。\n\n一次性 shell（`executeShell`）每次调用始终创建并丢弃一个新会话。\n\n### 流式传输/输出行为\n\n- 标准输出/标准错误被路由到共享管道中并发读取。\n- 读取器增量解码 UTF-8；无效字节序列以 `U+FFFD` 替换字符形式输出。\n- 进程完成后，输出排空设有空闲/最大保护（`250ms` 空闲，`2s` 最大），以避免后台任务保持描述符打开而导致挂起。\n\n### 取消、超时与后台任务\n\n- `CancelToken` 由 `timeoutMs` 和可选的 `AbortSignal` 构造。\n- 取消/超时时，触发 shell 取消令牌，然后任务获得 2 秒宽限窗口后强制中止。\n- 如果发生取消，后台任务将使用 brush 作业元数据被终止（先 `TERM`，再延迟 `KILL`）。\n\n`Shell.abort()` 行为：\n\n- 仅中止该 `Shell` 实例当前正在运行的命令，\n- 没有运行中命令时为无操作成功。\n\n### 失败行为\n\n常见暴露的错误包括：\n\n- 会话初始化失败（`Failed to initialize shell`），\n- cwd 错误（`Failed to set cwd`），\n- 环境变量设置/弹出失败，\n- 快照源失败，\n- 管道创建/克隆失败，\n- 执行失败（`Shell execution failed: ...`），\n- 任务封装失败（`Shell execution task failed: ...`）。\n\n结果级取消标志：\n\n- 超时 -> `exitCode: undefined`，`timedOut: true`。\n- 中止信号 -> `exitCode: undefined`，`cancelled: true`。\n\n## PTY 子系统（`pty`）\n\n### API 模型\n\n`new PtySession()` 暴露：\n\n- `start(options, onChunk?) -> Promise<{ exitCode?, cancelled, timedOut }>`\n- `write(data)`\n- `resize(cols, rows)`\n- `kill()`\n\n### 运行时生命周期与状态转换\n\n`PtySession` 状态机：\n\n- **空闲**：`core: None`。\n- **已预留**：`start()` 在异步工作开始前同步安装控制通道（`core: Some`），因此 `write/resize/kill` 立即变为有效。\n- **运行中**：阻塞 PTY 循环处理子进程状态、读取器事件、取消心跳和控制消息。\n- **终端已关闭**：子进程退出 + 读取器完成。\n- **已终结**：`start()` 任务完成后（无论成功或错误），`core` 始终被重置为 `None`。\n\n并发保护：\n\n- 在已运行时启动将返回 `PTY session already running`。\n\n### 创建/附加/写入/读取/终止模式\n\n- PTY 通过 `portable_pty::native_pty_system().openpty(...)` 打开。\n- 命令当前以 `sh -lc <command>` 运行，支持可选的 `cwd` 和环境变量覆盖。\n- `write()` 向 PTY 标准输入发送原始字节。\n- `resize()` 限制尺寸（`cols 20..400`，`rows 5..200`）并调用主端 resize。\n- `kill()` 将运行标记为已取消并杀死子进程。\n\n输出路径：\n\n- 专用读取器线程从主端流读取数据，\n- 增量 UTF-8 解码，无效字节以 `U+FFFD` 替换，\n- 数据块通过 N-API 线程安全回调转发。\n\n### 取消与超时语义\n\n- `timeoutMs` 和 `AbortSignal` 馈入 `CancelToken`。\n- 循环周期性调用 `ct.heartbeat()`；中止触发子进程杀死。\n- 超时分类基于字符串（心跳错误中的 `\"Timeout\"` 子串）。\n\n### 失败行为\n\n错误表面包括：\n\n- PTY 分配/打开失败，\n- PTY 创建子进程失败，\n- 写入器/读取器获取失败，\n- 子进程状态/等待失败，\n- 锁中毒，\n- 控制通道断开连接（`PTY session is no longer available`）。\n\n非运行时的控制调用失败：\n\n- `write/resize/kill` 返回 `PTY session is not running`。\n\n## 进程树子系统（`ps`）\n\n### API 模型\n\n- `killTree(pid, signal) -> number`\n- `listDescendants(pid) -> number[]`\n\nTS 封装器还通过 `setNativeKillTree(native.killTree)` 将原生 kill-tree 实现注册到共享工具中。\n\n### 平台特定实现\n\n- **Linux**：递归读取 `/proc/<pid>/task/<pid>/children`。\n- **macOS**：使用 `libproc` 的 `proc_listchildpids`。\n- **Windows**：通过 `CreateToolhelp32Snapshot` 快照进程表，构建父->子映射，使用 `OpenProcess(PROCESS_TERMINATE)` + `TerminateProcess` 终止。\n\n### Kill-tree 行为\n\n- 递归收集后代进程。\n- 杀死顺序为自底向上（最深层后代优先），以减少孤儿进程重新挂靠。\n- 根 pid 最后被杀死。\n- 返回值为成功终止的数量。\n\n信号行为：\n\n- POSIX：提供的 `signal` 传递给 `kill`。\n- Windows：`signal` 被忽略；终止为无条件的进程终止。\n\n### 失败行为\n\n此模块在 API 表面有意设计为不抛出异常：\n\n- 缺失/不可访问的进程树分支被跳过，\n- 单个 pid 的杀死失败计为不成功（非错误），\n- 查找未命中通常从 `listDescendants` 返回 `[]`，从 `killTree` 返回 `0`。\n\n## 按键解析子系统（`keys`）\n\n### API 模型\n\n暴露的辅助函数：\n\n- `parseKey(data, kittyProtocolActive)`\n- `matchesKey(data, keyId, kittyProtocolActive)`\n- `parseKittySequence(data)`\n- `matchesKittySequence(data, expectedCodepoint, expectedModifier)`\n- `matchesLegacySequence(data, keyName)`\n\n### 解析模型\n\n解析器组合了：\n\n- 直接单字节映射（`enter`、`tab`、`ctrl+<letter>`、可打印 ASCII），\n- O(1) 传统转义序列查找（PHF 映射），\n- xterm `modifyOtherKeys` 解析，\n- Kitty 协议解析（`CSI u`、`CSI ~`、`CSI 1;...<letter>`），\n- 标准化为按键 ID（`ctrl+c`、`shift+tab`、`pageUp`、`f5` 等）。\n\n修饰键处理：\n\n- 按键匹配时仅比较 shift/alt/ctrl 位，\n- 比较前屏蔽锁定位。\n\n布局行为：\n\n- 基础布局回退有意受限，以防止重新映射的布局对 ASCII 字母/符号产生误匹配。\n\n### 失败行为\n\n- 无法识别或无效的序列从解析函数返回 `null`。\n- 匹配函数在解析失败或不匹配时返回 `false`。\n- 对格式错误的按键输入不抛出异常。\n\n## JS 封装器 API ↔ Rust 导出映射\n\n### Shell + PTY + 进程\n\n| TS 封装器 API | Rust N-API 导出 | 说明 |\n|---|---|---|\n| `executeShell(options, onChunk?)` | `executeShell` (`execute_shell`) | 一次性 shell 执行 |\n| `new Shell(options?)` | `Shell` class | 持久 shell 会话 |\n| `shell.run(options, onChunk?)` | `Shell::run` | 在保活控制流下复用会话 |\n| `shell.abort()` | `Shell::abort` | 中止该 shell 实例的活动运行 |\n| `new PtySession()` | `PtySession` class | 有状态的 PTY 会话 |\n| `pty.start(options, onChunk?)` | `PtySession::start` | 交互式 PTY 运行 |\n| `pty.write(data)` | `PtySession::write` | 原始标准输入透传 |\n| `pty.resize(cols, rows)` | `PtySession::resize` | 受限的终端尺寸 |\n| `pty.kill()` | `PtySession::kill` | 强制杀死活动的 PTY 子进程 |\n| `killTree(pid, signal)` | `killTree` (`kill_tree`) | 子进程优先的进程树终止 |\n| `listDescendants(pid)` | `listDescendants` (`list_descendants`) | 递归后代列表 |\n\n### 按键\n\n| TS 封装器 API | Rust N-API 导出 | 说明 |\n|---|---|---|\n| `matchesKittySequence(data, cp, mod)` | `matchesKittySequence` (`matches_kitty_sequence`) | Kitty 码点+修饰键匹配 |\n| `parseKey(data, kittyProtocolActive)` | `parseKey` (`parse_key`) | 标准化按键 ID 解析器 |\n| `matchesLegacySequence(data, keyName)` | `matchesLegacySequence` (`matches_legacy_sequence`) | 精确传统序列映射检查 |\n| `parseKittySequence(data)` | `parseKittySequence` (`parse_kitty_sequence`) | 结构化 Kitty 解析结果 |\n| `matchesKey(data, keyId, kittyProtocolActive)` | `matchesKey` (`matches_key`) | 高级按键匹配器 |\n\n## 废弃会话清理与终结说明\n\n- **Shell 持久会话**：如果运行被取消/超时/出错/非保活控制流，Rust 会显式丢弃内部会话状态。成功的正常运行会保留会话以供复用。\n- **PTY 会话**：`start()` 完成后 `core` 始终被清除，包括失败路径。\n- 封装器**未暴露显式的 JS 终结器驱动的杀死契约**；清理主要与运行完成/取消路径绑定。调用者应使用 `timeoutMs`、`AbortSignal`、`shell.abort()` 或 `pty.kill()` 进行确定性清理。\n",
	"zh-cn/natives/natives-text-search-pipeline.md": "---\ntitle: 原生文本与搜索流水线\ndescription: 基于 grep、glob 和 ripgrep 文件内容索引的原生文本搜索流水线。\nsidebar:\n  order: 6\n  label: 文本与搜索流水线\ni18n:\n  sourceHash: 0e93462fdd12\n  translator: machine\n---\n\n# 原生文本/搜索流水线\n\n本文档映射了 `@f5-sales-demo/pi-natives` 文本/搜索接口（`grep`、`glob`、`text`、`highlight`），从 TypeScript 封装层到 Rust N-API 导出，再到 JS 结果对象。\n\n术语遵循 `docs/natives-architecture.md`：\n\n- **封装层（Wrapper）**：`packages/natives/src/*` 中的 TS API\n- **Rust 模块层**：`crates/pi-natives/src/*` 中的 N-API 导出\n- **共享扫描缓存**：基于 `fs_cache` 的目录条目缓存，供发现/搜索流程使用\n\n## 实现文件\n\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/glob_util.rs`\n- `crates/pi-natives/src/fs_cache.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/highlight.rs`\n- `crates/pi-natives/src/fd.rs`\n\n## JS API ↔ Rust 导出映射\n\n| JS 封装层 API | Rust 导出（`#[napi]`，snake_case -> camelCase） | Rust 模块 |\n| --- | --- | --- |\n| `grep(options, onMatch?)` | `grep` | `grep.rs` |\n| `searchContent(content, options)` | `search` | `grep.rs` |\n| `hasMatch(content, pattern, options?)` | `hasMatch` | `grep.rs` |\n| `fuzzyFind(options)` | `fuzzyFind` | `fd.rs` |\n| `glob(options, onMatch?)` | `glob` | `glob.rs` |\n| `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `fs_cache.rs` |\n| `wrapTextWithAnsi(text, width)` | `wrapTextWithAnsi` | `text.rs` |\n| `truncateToWidth(text, maxWidth, ellipsis, pad)` | `truncateToWidth` | `text.rs` |\n| `sliceWithWidth(line, startCol, length, strict?)` | `sliceWithWidth` | `text.rs` |\n| `extractSegments(line, beforeEnd, afterStart, afterLen, strictAfter)` | `extractSegments` | `text.rs` |\n| `sanitizeText(text)` | `sanitizeText` | `text.rs` |\n| `visibleWidth(text)` | `visibleWidth` | `text.rs` |\n| `highlightCode(code, lang, colors)` | `highlightCode` | `highlight.rs` |\n| `supportsLanguage(lang)` | `supportsLanguage` | `highlight.rs` |\n| `getSupportedLanguages()` | `getSupportedLanguages` | `highlight.rs` |\n\n## 按子系统划分的流水线概述\n\n## 1) 正则搜索（`grep`、`searchContent`、`hasMatch`）\n\n### 输入/选项流程\n\n1. TS 封装层将选项转发至原生层：\n   - `grep/index.ts` 基本不修改 `options`，并将回调从 `(match) => void` 形式封装为 napi 线程安全回调形式 `(err, match)`。\n   - `searchContent` 和 `hasMatch` 直接传递字符串/`Uint8Array`。\n2. `grep.rs` 中的 Rust 选项结构体反序列化 camelCase 字段（`ignoreCase`、`maxCount`、`contextBefore`、`contextAfter`、`maxColumns`、`timeoutMs`）。\n3. `grep` 从 `timeoutMs` + `AbortSignal` 创建 `CancelToken`，并在 `task::blocking(\"grep\", ...)` 中运行。\n\n### 执行分支\n\n- **内存分支（纯工具性）**\n  - `search` → `search_sync` → 对提供的内容字节执行 `run_search`。\n  - 无文件系统扫描，不使用 `fs_cache`。\n- **单文件分支（依赖文件系统）**\n  - `grep_sync` 解析路径，检查元数据确认是文件，通过 ripgrep 匹配器流式处理每个文件最多 `MAX_FILE_BYTES`（`4 MiB`）。\n- **目录分支（依赖文件系统）**\n  - 当 `cache: true` 时，通过 `fs_cache::get_or_scan` 进行可选缓存查找。\n  - 当 `cache: false` 时，通过 `fs_cache::force_rescan` 进行全新扫描。\n  - 当缓存时间超过 `empty_recheck_ms()` 时，可选的空结果重新检查。\n  - 条目过滤：仅文件 + 可选的 glob 过滤（`glob_util`）+ 可选的类型过滤映射（`js`、`ts`、`rust` 等）。\n\n### 搜索/收集语义\n\n- 正则引擎：`grep_regex::RegexMatcherBuilder`，支持 `ignoreCase` 和 `multiline`。\n- 上下文解析：\n  - `contextBefore/contextAfter` 覆盖旧版 `context`。\n  - 非内容模式将上下文收集置零。\n- 输出模式：\n  - `content` => 每个匹配一个 `GrepMatch`。\n  - `count` 和 `filesWithMatches` 均映射为计数式条目（`lineNumber=0`、`line=\"\"`、`matchCount` 已设置）。\n- 限制：\n  - 全局 `offset` 和 `maxCount` 跨文件应用。\n  - 并行路径仅在 `maxCount` 未设置且 `offset == 0` 时使用；否则使用顺序路径以保持确定性的全局偏移/限制语义。\n\n### 结果塑形回传至 JS\n\n- Rust `SearchResult`/`GrepResult` 字段通过 N-API 对象字段转换映射到 TS 类型。\n- 计数器在跨越 N-API 前被截断为 `u32`。\n- 可选布尔值在某些路径中除非为 true 否则被省略（`limitReached`）。\n- 流式回调接收每个已塑形的 `GrepMatch`（内容条目或计数条目）。\n\n### 失败行为\n\n- `searchContent` 对正则/搜索失败返回 `SearchResult.error` 而非抛出异常。\n- `grep` 对硬错误（无效路径、无效 glob/正则、取消超时/中止）拒绝 Promise。\n- `hasMatch` 返回 `Result<bool>`，对无效模式/UTF-8 解码错误抛出异常。\n- 多文件扫描中的文件打开/搜索错误按文件跳过；扫描继续进行。\n\n### 格式错误的正则处理\n\n`grep.rs` 在正则编译前清理大括号：\n\n- 无法构成 `{N}`、`{N,}`、`{N,M}` 的无效重复样式大括号会被转义（`{`/`}` -> `\\{`/`\\}`）。\n- 这防止了常见的字面模板片段（例如 `${platform}`）作为格式错误的重复而失败。\n- 剩余的无效正则语法仍会返回正则错误。\n\n## 2) 文件发现（`glob`）和模糊路径搜索（`fuzzyFind`）\n\n`glob` 和 `fuzzyFind` 共享 `fs_cache` 扫描；匹配逻辑不同。\n\n### `glob` 流程\n\n1. TS 封装层（`glob/index.ts`）：\n   - `path.resolve(options.path)`。\n   - 默认值：`pattern=\"*\"`、`hidden=false`、`gitignore=true`、`recursive=true`。\n2. Rust `glob` 构建 `GlobConfig` 并通过 `glob_util::compile_glob` 编译模式。\n3. 条目来源：\n   - `cache=true` => `get_or_scan` + 可选的过期空结果 `force_rescan`。\n   - `cache=false` => `force_rescan(..., store=false)`（仅全新扫描）。\n4. 过滤：\n   - 始终跳过 `.git`。\n   - 除非请求（`includeNodeModules` 或模式中提及 node_modules），否则跳过 `node_modules`。\n   - 应用 glob 匹配。\n   - 应用文件类型过滤；符号链接 `file/dir` 过滤器解析目标元数据。\n5. 截断到 `maxResults` 之前可选按 mtime 降序排序（`sortByMtime`）。\n\n### `fuzzyFind` 流程（在 `fd.rs` 中实现）\n\n1. TS 封装层从 `grep` 模块导出，但 Rust 实现位于 `fd.rs`。\n2. 共享来自 `fs_cache` 的扫描源，具有相同的缓存/无缓存分支和过期空结果重新检查策略。\n3. 评分：\n   - 精确匹配 / 前缀匹配 / 包含匹配 / 基于子序列的模糊评分\n   - 分隔符/标点符号规范化的评分路径\n   - 目录加分和确定性平局打破（`score 降序`，然后 `path 升序`）\n4. 符号链接条目从模糊结果中排除。\n\n### 失败行为\n\n- 无效 glob 模式 => 来自 `glob_util::compile_glob` 的错误。\n- 搜索根目录必须是已存在的目录（`resolve_search_path`），否则返回错误。\n- 取消/超时通过循环中的 `CancelToken::heartbeat()` 检查作为中止错误传播。\n\n### 格式错误的 glob 处理\n\n`glob_util::build_glob_pattern` 具有容错性：\n\n- 将 `\\` 规范化为 `/`。\n- 当 `recursive=true` 时，自动为简单递归模式添加 `**/` 前缀。\n- 编译前自动关闭不平衡的 `{...` 交替组。\n\n## 3) 共享扫描/缓存生命周期（`fs_cache`）\n\n`fs_cache` 将扫描结果存储为规范化的相对条目（`path`、`fileType`、可选 `mtime`），键值由以下组合确定：\n\n- 规范搜索根目录\n- `include_hidden`\n- `use_gitignore`\n\n### 缓存状态转换\n\n1. **未命中 / 已禁用**\n   - TTL 为 `0` 或键不存在/已过期 -> 全新 `collect_entries`。\n2. **命中**\n   - 条目时间 `< cache_ttl_ms()` -> 返回缓存条目 + `cache_age_ms`。\n3. **过期空结果重新检查**（`glob`/`grep`/`fd` 中的调用方策略）\n   - 如果查询产生零匹配且 `cache_age_ms >= empty_recheck_ms()`，强制执行一次重新扫描。\n4. **失效**\n   - `invalidateFsScanCache(path?)`：\n     - 无参数：清除所有键\n     - 有 path 参数：移除根路径是该目标路径前缀的键\n\n### 过期结果权衡\n\n- 缓存偏向低延迟的重复扫描，而非即时一致性。\n- TTL 窗口期内可能返回过期的正结果/负结果。\n- 空结果重新检查以一次额外扫描的代价减少了较旧缓存扫描的过期负结果。\n- 显式失效是文件变更后预期的正确性钩子。\n\n## 4) ANSI 文本工具（`text`）\n\n这些是纯内存工具（无文件系统扫描）。\n\n### 边界与职责\n\n- **`text.rs` 负责终端单元格语义**：\n  - ANSI 序列解析\n  - 感知字素的宽度和切片\n  - 换行/截断/清理行为\n- **`grep.rs` 行截断（`maxColumns`）是独立的**：\n  - 匹配行的简单字符边界截断，附带 `...`\n  - 不保留 ANSI 状态，也不感知终端单元格宽度\n\n### 关键行为\n\n- `wrapTextWithAnsi`：按可见宽度换行，跨换行行传递活动的 SGR 代码。\n- `truncateToWidth`：可见单元格截断，支持省略号策略（`Unicode`、`Ascii`、`Omit`）、可选右填充，以及当未变更时返回原始 JS 字符串的快速路径。\n- `sliceWithWidth`：列切片，支持可选的严格宽度强制。\n- `extractSegments`：在覆盖层周围提取前/后段，同时为 `after` 段恢复 ANSI 状态。\n- `sanitizeText`：剥离 ANSI 转义符 + 控制字符，丢弃孤立代理项，通过移除 `\\r` 规范化 CR/LF。\n- `visibleWidth`：计算可见终端单元格数（制表符使用 Rust 实现中的固定 `TAB_WIDTH`）。\n\n### 失败行为\n\n文本函数通常返回确定性的转换输出；错误仅限于 JS 字符串转换边界（N-API 参数转换失败）。\n\n## 5) 语法高亮（`highlight`）\n\n`highlight.rs` 是纯转换（无文件系统访问，无缓存）。\n\n### 流程\n\n1. 封装层转发 `code`、可选的 `lang` 和 ANSI 调色板。\n2. Rust 通过以下方式解析语法：\n   - 令牌/名称查找\n   - 扩展名查找\n   - 别名表回退（`ts/tsx/js -> JavaScript` 等）\n   - 无法解析时回退到纯文本语法\n3. 使用 syntect `ParseState` 和作用域栈逐行解析。\n4. 将作用域映射到 11 个语义颜色类别，并注入/重置 ANSI 颜色代码。\n\n### 失败行为\n\n- 单行解析失败不会导致调用失败：该行以未高亮形式追加，处理继续。\n- 未知/不支持的语言回退到纯文本语法。\n\n## 纯工具流程 vs 依赖文件系统的流程\n\n| 流程 | 文件系统访问 | 共享缓存 | 备注 |\n| --- | --- | --- | --- |\n| `searchContent` / `hasMatch` | 否 | 否 | 仅对提供的字节/字符串执行正则 |\n| `text` 模块函数 | 否 | 否 | 仅 ANSI/宽度/清理 |\n| `highlight` 模块函数 | 否 | 否 | 仅语法 + ANSI 着色 |\n| `glob` | 是 | 可选 | 目录扫描 + glob 过滤 |\n| `fuzzyFind` | 是 | 可选 | 目录扫描 + 模糊评分 |\n| `grep`（文件/目录路径） | 是 | 可选（目录模式） | 对文件执行 ripgrep，可选过滤器/回调 |\n\n## 端到端生命周期总结\n\n1. 调用方使用类型化选项调用 TS 封装层。\n2. 封装层规范化默认值（特别是 `glob`）并转发至 `native.*` 导出。\n3. Rust 验证/规范化选项并构建匹配器/搜索配置。\n4. 对于文件系统流程，扫描条目（缓存命中/未命中/重新扫描）然后过滤/评分。\n5. 工作循环定期调用取消心跳；超时/中止可以终止执行。\n6. Rust 将输出塑形为 N-API 对象（`lineNumber`、`matchCount`、`limitReached` 等）。\n7. TS 封装层返回类型化的 JS 对象（以及 `grep`/`glob` 的可选逐匹配回调）。\n",
	"zh-cn/natives/porting-to-natives.md": "---\ntitle: 移植到 pi-natives (N-API) — 实践笔记\ndescription: 将 Node.js child_process 和 shell 代码迁移到 Rust N-API 原生层的实践笔记。\nsidebar:\n  order: 9\n  label: 移植到 pi-natives\ni18n:\n  sourceHash: 4f5150286535\n  translator: machine\n---\n\n# 移植到 pi-natives (N-API) — 实践笔记\n\n这是一份将热路径迁移到 `crates/pi-natives` 并通过 JS 绑定进行连接的实践指南。它的存在是为了避免同样的错误重复发生。\n\n## 何时进行移植\n\n当以下任一条件成立时进行移植：\n\n- 热路径运行在渲染循环、频繁的 UI 更新或大批量处理中。\n- JS 分配占主导地位（字符串频繁创建销毁、正则回溯、大数组）。\n- 你已经有 JS 基准线，可以并排对比两个版本的基准测试。\n- 工作是 CPU 密集型或可以在 libuv 线程池上运行的阻塞 I/O。\n- 工作是可以在 Tokio 运行时上运行的异步 I/O（例如 shell 执行）。\n\n避免移植依赖于仅 JS 状态或动态导入的代码。N-API 导出应该是纯粹的、数据输入/数据输出。长时间运行的工作应通过 `task::blocking`（CPU 密集型/阻塞 I/O）或 `task::future`（异步 I/O）并支持取消。\n\n## 原生导出的结构\n\n**Rust 端：**\n\n- 实现代码位于 `crates/pi-natives/src/<module>.rs`。如果添加新模块，需在 `crates/pi-natives/src/lib.rs` 中注册。\n- 使用 `#[napi]` 导出；snake_case 导出会自动转换为 camelCase。仅在真正需要别名/非默认名称时使用显式 `js_name`。结构体使用 `#[napi(object)]`。\n- 对于 CPU 密集型或阻塞工作，使用 `task::blocking(tag, cancel_token, work)`（参见 `crates/pi-natives/src/task.rs`）。对于需要 Tokio 的异步工作（例如 shell 会话），使用 `task::future(env, tag, work)`。当你暴露 `timeoutMs` 或 `AbortSignal` 时传递 `CancelToken`。\n\n**JS 端：**\n\n- `packages/natives/src/bindings.ts` 包含基础 `NativeBindings` 接口。\n- `packages/natives/src/<module>/types.ts` 定义 TS 类型并通过声明合并扩展 `NativeBindings`。\n- `packages/natives/src/native.ts` 导入每个 `<module>/types.ts` 文件以激活声明。\n- `packages/natives/src/<module>/index.ts` 封装来自 `packages/natives/src/native.ts` 的 `native` 绑定。\n- `packages/natives/src/native.ts` 加载插件，`validateNative` 强制检查必需的导出。\n- `packages/natives/src/index.ts` 为 `packages/*` 中的调用者重新导出封装。\n\n## 移植清单\n\n1. **添加 Rust 实现**\n\n- 将核心逻辑放在一个普通的 Rust 函数中。\n- 如果是新模块，将其添加到 `crates/pi-natives/src/lib.rs`。\n- 使用 `#[napi]` 导出，保持默认的 snake_case -> camelCase 映射一致性。\n- 保持签名为拥有所有权且简单的类型：`String`、`Vec<String>`、`Uint8Array`，或对于大字符串/字节输入使用 `Either<JsString, Uint8Array>`。\n- 对于 CPU 密集型或阻塞工作，使用 `task::blocking`；对于异步工作，使用 `task::future`。传递 `CancelToken` 并在长循环中调用 `heartbeat()`。\n\n2. **连接 JS 绑定**\n\n- 在 `packages/natives/src/<module>/types.ts` 中添加类型和 `NativeBindings` 扩展。\n- 在 `packages/natives/src/native.ts` 中导入 `./<module>/types` 以触发声明合并。\n- 在 `packages/natives/src/<module>/index.ts` 中添加调用 `native` 的封装。\n- 从 `packages/natives/src/index.ts` 重新导出。\n\n3. **更新原生验证**\n\n- 在 `validateNative`（`packages/natives/src/native.ts`）中添加 `checkFn(\"newExport\")`。\n\n4. **添加基准测试**\n\n- 将基准测试放在所属包旁边（`packages/tui/bench`、`packages/natives/bench` 或 `packages/coding-agent/bench`）。\n- 在同一次运行中包含 JS 基准线和原生版本。\n- 使用 `Bun.nanoseconds()` 和固定的迭代次数。\n- 保持基准测试输入小而真实（热路径中实际看到的数据）。\n\n5. **构建原生二进制文件**\n\n- `bun --cwd=packages/natives run build`\n- 使用 `bun --cwd=packages/natives run build`，如果想在测试时获取加载器诊断信息，设置 `PI_DEV=1`。\n\n6. **运行基准测试**\n\n- `bun run packages/<pkg>/bench/<bench>.ts`（或 `bun --cwd=packages/natives run bench`）\n\n7. **决定使用方式**\n\n- 如果原生更慢，**保留 JS**，让原生导出暂不使用。\n- 如果原生更快，将调用点切换到原生封装。\n\n## 痛点及避免方法\n\n### 1) 过期的 `pi_natives.node` 阻止新导出\n\n加载器优先使用 `packages/natives/native` 中带平台标签的二进制文件（`pi_natives.<platform>-<arch>.node`）。`PI_DEV=1` 现在仅启用加载器诊断信息；它不再切换到单独的开发插件文件名。还有一个回退的 `pi_natives.node`。编译后的二进制文件会提取到 `~/.xcsh/natives/<version>/pi_natives.<platform>-<arch>.node`。如果其中任何一个过期，导出都不会更新。\n\n**修复方法：** 在重新构建之前删除过期文件。\n\n```bash\nrm packages/natives/native/pi_natives.linux-x64.node\nrm packages/natives/native/pi_natives.node\nbun --cwd=packages/natives run build\n```\n\n如果你正在运行编译后的二进制文件，删除缓存的插件目录：\n\n```bash\nrm -rf ~/.xcsh/natives/<version>\n```\n\n然后验证二进制文件中是否存在该导出：\n\n```bash\nbun -e 'const tag = `${process.platform}-${process.arch}`; const mod = require(`./packages/natives/native/pi_natives.${tag}.node`); console.log(Object.keys(mod).includes(\"newExport\"));'\n```\n\n### 2) 来自 `validateNative` 的 \"Missing exports\" 错误\n\n这是**好事** — 它防止了静默的不匹配。当你看到：\n\n```\nNative addon missing exports ... Missing: visibleWidth\n```\n\n这意味着你的二进制文件过期了，Rust 导出名称（或使用时的显式别名）与 JS 名称不匹配，或者导出根本没有编译进去。修复构建和命名不匹配问题，不要削弱验证。\n\n### 3) Rust 签名不匹配\n\n保持简单且拥有所有权。`String`、`Vec<String>` 和 `Uint8Array` 可以正常工作。在公共导出中避免使用 `&str` 等引用。如果需要结构化数据，将其包装在 `#[napi(object)]` 结构体中。\n\n### 4) 基准测试错误\n\n- 不要比较不同的输入或分配。\n- 保持 JS 和原生使用完全相同的输入数组。\n- 在同一个基准测试文件中运行两者以避免偏差。\n\n## 基准测试模板\n\n```ts\nconst ITERATIONS = 2000;\n\nfunction bench(name: string, fn: () => void): number {\n const start = Bun.nanoseconds();\n for (let i = 0; i < ITERATIONS; i++) fn();\n const elapsed = (Bun.nanoseconds() - start) / 1e6;\n console.log(`${name}: ${elapsed.toFixed(2)}ms total (${(elapsed / ITERATIONS).toFixed(6)}ms/op)`);\n return elapsed;\n}\n\nbench(\"feature/js\", () => {\n jsImpl(sample);\n});\n\nbench(\"feature/native\", () => {\n nativeImpl(sample);\n});\n```\n\n## 验证清单\n\n- `validateNative` 通过（没有缺失的导出）。\n- `NativeBindings` 在 `packages/natives/src/<module>/types.ts` 中已扩展，封装已在 `packages/natives/src/index.ts` 中重新导出。\n- `Object.keys(require(...))` 包含你的新导出。\n- 基准测试数据已记录在 PR/笔记中。\n- **仅在**原生更快或持平时才更新调用点。\n\n## 经验法则\n\n- 如果原生更慢，**不要切换**。保留导出供将来使用，但 TUI 应该保持在更快的路径上。\n- 如果原生更快，切换调用点并保留基准测试以捕获性能回退。\n",
	"zh-cn/providers/models.md": "---\ntitle: 模型与提供商配置\ndescription: 通过 models.yml 进行模型注册和提供商配置，包括路由、回退和定价。\nsidebar:\n  order: 1\n  label: 模型与提供商\ni18n:\n  sourceHash: 8053df967ff6\n  translator: machine\n---\n\n# 模型与提供商配置（`models.yml`）\n\n本文档描述了编码代理当前如何加载模型、应用覆盖配置、解析凭据以及在运行时选择模型。\n\n## 控制模型行为的要素\n\n主要实现文件：\n\n- `src/config/model-registry.ts` — 加载内置和自定义模型、提供商覆盖、运行时发现、认证集成\n- `src/config/model-resolver.ts` — 解析模型模式并选择 initial/smol/slow 模型\n- `src/config/settings-schema.ts` — 模型相关设置（`modelRoles`、提供商传输偏好）\n- `src/session/auth-storage.ts` — API 密钥 + OAuth 解析顺序\n- `packages/ai/src/models.ts` 和 `packages/ai/src/types.ts` — 内置提供商/模型以及 `Model`/`compat` 类型\n\n## 配置文件位置和旧版行为\n\n默认配置路径：\n\n- `~/.xcsh/agent/models.yml`\n\n仍然存在的旧版行为：\n\n- 如果 `models.yml` 不存在但同一位置存在 `models.json`，则会自动迁移为 `models.yml`。\n- 当通过编程方式传递给 `ModelRegistry` 时，仍支持显式的 `.json` / `.jsonc` 配置路径。\n\n## `models.yml` 结构\n\n```yaml\nconfigVersion: 1  # optional — written by auto-config, used for migration detection\nproviders:\n  <provider-id>:\n    # provider-level config\nequivalence:\n  overrides:\n    <provider-id>/<model-id>: <canonical-model-id>\n  exclude:\n    - <provider-id>/<model-id>\n```\n\n`configVersion` 是一个可选整数，由自动配置系统写入。存在时，xcsh 使用它来检测过时的配置并自动升级。\n\n`provider-id` 是在选择和认证查找中使用的规范提供商键。\n\n`equivalence` 是可选的，用于在具体提供商模型之上配置规范模型分组：\n\n- `overrides` 将精确的具体选择器（`provider/modelId`）映射到官方上游规范 id\n- `exclude` 将具体选择器从规范分组中排除\n\n## 提供商级别字段\n\n```yaml\nproviders:\n  my-provider:\n    baseUrl: https://api.example.com/v1\n    apiKey: MY_PROVIDER_API_KEY\n    api: openai-completions\n    headers:\n      X-Team: platform\n    authHeader: true\n    auth: apiKey\n    discovery:\n      type: ollama\n    modelOverrides:\n      some-model-id:\n        name: Renamed model\n    models:\n      - id: some-model-id\n        name: Some Model\n        api: openai-completions\n        reasoning: false\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 128000\n        maxTokens: 16384\n        headers:\n          X-Model: value\n        compat:\n          supportsStore: true\n          supportsDeveloperRole: true\n          supportsReasoningEffort: true\n          maxTokensField: max_completion_tokens\n          openRouterRouting:\n            only: [anthropic]\n          vercelGatewayRouting:\n            order: [anthropic, openai]\n          extraBody:\n            gateway: m1-01\n            controller: mlx\n```\n\n### 允许的提供商/模型 `api` 值\n\n- `openai-completions`\n- `openai-responses`\n- `openai-codex-responses`\n- `azure-openai-responses`\n- `anthropic-messages`\n- `google-generative-ai`\n- `google-vertex`\n\n### 允许的 auth/discovery 值\n\n- `auth`：`apiKey`（默认）或 `none`\n- `discovery.type`：`ollama`\n\n## 验证规则（当前）\n\n### 完整自定义提供商（`models` 非空）\n\n必需：\n\n- `baseUrl`\n- `apiKey`（除非设置了 `auth: none`）\n- `api` 在提供商级别或每个模型上\n\n### 仅覆盖提供商（`models` 缺失或为空）\n\n必须至少定义以下之一：\n\n- `baseUrl`\n- `modelOverrides`\n- `discovery`\n\n### 发现\n\n- `discovery` 需要提供商级别的 `api`。\n\n### 模型值检查\n\n- `id` 必需\n- `contextWindow` 和 `maxTokens` 如果提供则必须为正数\n\n## 合并和覆盖顺序\n\nModelRegistry 管道（刷新时）：\n\n1. 从 `@f5-sales-demo/pi-ai` 加载内置提供商/模型。\n2. 加载 `models.yml` 自定义配置。\n3. 将提供商覆盖（`baseUrl`、`headers`）应用到内置模型。\n4. 应用 `modelOverrides`（按提供商 + 模型 id）。\n5. 合并自定义 `models`：\n   - 相同 `provider + id` 替换现有的\n   - 否则追加\n6. 应用运行时发现的模型（目前是 Ollama 和 LM Studio），然后重新应用模型覆盖。\n\n## 规范模型等价和合并\n\n注册表保留每个具体的提供商模型，然后在它们之上构建规范层。\n\n规范 id 仅为官方上游 id，例如：\n\n- `claude-opus-4-6`\n- `claude-haiku-4-5`\n- `gpt-5.3-codex`\n\n### `models.yml` 等价配置\n\n示例：\n\n```yaml\nproviders:\n  zenmux:\n    baseUrl: https://api.zenmux.example/v1\n    apiKey: ZENMUX_API_KEY\n    api: openai-codex-responses\n    models:\n      - id: codex\n        name: Zenmux Codex\n        reasoning: true\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 200000\n        maxTokens: 32768\n\nequivalence:\n  overrides:\n    zenmux/codex: gpt-5.3-codex\n    p-codex/codex: gpt-5.3-codex\n  exclude:\n    - demo/codex-preview\n```\n\n规范分组的构建顺序：\n\n1. 来自 `equivalence.overrides` 的精确用户覆盖\n2. 来自内置模型元数据的捆绑官方 id 匹配\n3. 针对网关/提供商变体的保守启发式规范化\n4. 回退到具体模型自身的 id\n\n当前启发式方法故意设计得较为保守：\n\n- 嵌入的上游前缀可以在存在时被剥离，例如 `anthropic/...` 或 `openai/...`\n- 点号和连字号版本变体仅在映射到现有官方 id 时才进行规范化，例如 `4.6 -> 4-6`\n- 模糊的系列或版本在没有捆绑匹配或显式覆盖的情况下不会被合并\n\n### 规范解析行为\n\n当多个具体变体共享同一个规范 id 时，解析使用：\n\n1. 可用性和认证\n2. `config.yml` 中的 `modelProviderOrder`\n3. 如果未设置 `modelProviderOrder`，则使用现有的注册表/提供商顺序\n\n被禁用或未认证的提供商会被跳过。\n\n会话状态和转录继续记录实际执行该轮次的具体提供商/模型。\n\n提供商默认值与按模型覆盖：\n\n- 提供商 `headers` 是基线。\n- 模型 `headers` 覆盖提供商的头部键。\n- `modelOverrides` 可以覆盖模型元数据（`name`、`reasoning`、`input`、`cost`、`contextWindow`、`maxTokens`、`headers`、`compat`、`contextPromotionTarget`）。\n- `compat` 对嵌套路由块（`openRouterRouting`、`vercelGatewayRouting`、`extraBody`）进行深度合并。\n\n## 运行时发现集成\n\n### 隐式 Ollama 发现\n\n如果 `ollama` 未被显式配置，注册表会添加一个隐式可发现提供商：\n\n- 提供商：`ollama`\n- api：`openai-completions`\n- 基础 URL：`OLLAMA_BASE_URL` 或 `http://127.0.0.1:11434`\n- 认证模式：无密钥（`auth: none` 行为）\n\n运行时发现调用 Ollama 的 `GET /api/tags` 并使用本地默认值合成模型条目。\n\n### 隐式 llama.cpp 发现\n\n如果 `llama.cpp` 未被显式配置，注册表会添加一个隐式可发现提供商：\n注意：它使用的是较新的 anthropic messages api 而不是 openai-completions。\n\n- 提供商：`llama.cpp`\n- api：`openai-responses`\n- 基础 URL：`LLAMA_CPP_BASE_URL` 或 `http://127.0.0.1:8080`\n- 认证模式：无密钥（`auth: none` 行为）\n\n运行时发现调用 llama.cpp 的 `GET models` 并使用本地默认值合成模型条目。\n\n### 隐式 LM Studio 发现\n\n如果 `lm-studio` 未被显式配置，注册表会添加一个隐式可发现提供商：\n\n- 提供商：`lm-studio`\n- api：`openai-completions`\n- 基础 URL：`LM_STUDIO_BASE_URL` 或 `http://127.0.0.1:1234/v1`\n- 认证模式：无密钥（`auth: none` 行为）\n\n运行时发现获取模型（`GET /models`）并使用本地默认值合成模型条目。\n\n### 显式提供商发现\n\n您可以自行配置发现：\n\n```yaml\nproviders:\n  ollama:\n    baseUrl: http://127.0.0.1:11434\n    api: openai-completions\n    auth: none\n    discovery:\n      type: ollama\n      \n  llama.cpp:\n    baseUrl: http://127.0.0.1:8080\n    api: openai-responses\n    auth: none\n    discovery:\n      type: llama.cpp\n```\n\n### 扩展提供商注册\n\n扩展可以在运行时注册提供商（`pi.registerProvider(...)`），包括：\n\n- 为提供商替换/追加模型\n- 为新 API ID 注册自定义流处理器\n- 自定义 OAuth 提供商注册\n\n## 认证和 API 密钥解析顺序\n\n请求提供商密钥时，有效顺序为：\n\n1. 运行时覆盖（CLI `--api-key`）\n2. 存储在 `agent.db` 中的 API 密钥凭据\n3. 存储在 `agent.db` 中的 OAuth 凭据（带刷新）\n4. 环境变量映射（`OPENAI_API_KEY`、`ANTHROPIC_API_KEY` 等）\n5. ModelRegistry 回退解析器（来自 `models.yml` 的提供商 `apiKey`，环境变量名或字面量语义）\n\n`models.yml` 中 `apiKey` 的行为：\n\n- 值首先被视为环境变量名。\n- 如果不存在对应的环境变量，则将字面字符串用作令牌。\n\n如果 `authHeader: true` 且提供商设置了 `apiKey`，模型会获得：\n\n- 注入的 `Authorization: Bearer <resolved-key>` 头部。\n\n无密钥提供商：\n\n- 标记为 `auth: none` 的提供商被视为无需凭据即可使用。\n- `getApiKey*` 对它们返回 `kNoAuth`。\n\n## 模型可用性与全部模型\n\n- `getAll()` 返回已加载的模型注册表（内置 + 合并的自定义 + 发现的）。\n- `getAvailable()` 过滤为无密钥或可解析认证的模型。\n\n因此，模型可以存在于注册表中，但在认证可用之前无法被选择。\n\n## 运行时模型解析\n\n### CLI 和模式解析\n\n`model-resolver.ts` 支持：\n\n- 精确的 `provider/modelId`\n- 精确的规范模型 id\n- 精确的模型 id（推断提供商）\n- 模糊/子字符串匹配\n- `--models` 中的通配符范围模式（例如 `openai/*`、`*sonnet*`）\n- 可选的 `:thinkingLevel` 后缀（`off|minimal|low|medium|high|xhigh`）\n\n`--provider` 是旧版选项；推荐使用 `--model`。\n\n精确选择器的解析优先级：\n\n1. 精确的 `provider/modelId` 绕过合并\n2. 精确的规范 id 通过规范索引解析\n3. 精确的裸具体 id 仍然有效\n4. 模糊和通配符匹配在精确路径之后运行\n\n### 初始模型选择优先级\n\n`findInitialModel(...)` 使用以下顺序：\n\n1. 显式的 CLI provider+model\n2. 第一个作用域模型（如果不是恢复会话）\n3. 已保存的默认 provider/model\n4. 可用模型中的已知提供商默认值（例如 OpenAI/Anthropic 等）\n5. 第一个可用模型\n\n### 角色别名和设置\n\n支持的模型角色：\n\n- `default`、`smol`、`slow`、`plan`、`commit`\n\n类似 `pi/smol` 的角色别名通过 `settings.modelRoles` 展开。每个角色值还可以附加思考选择器，如 `:minimal`、`:low`、`:medium` 或 `:high`。\n\n如果一个角色指向另一个角色，目标模型仍然正常继承，引用角色上的任何显式后缀在该角色特定使用中优先。\n\n相关设置：\n\n- `modelRoles`（记录）\n- `enabledModels`（作用域模式列表）\n- `modelProviderOrder`（全局规范提供商优先级）\n- `providers.kimiApiFormat`（`openai` 或 `anthropic` 请求格式）\n- `providers.openaiWebsockets`（`auto|off|on` OpenAI Codex 传输的 websocket 偏好）\n\n`modelRoles` 可以存储以下任一值：\n\n- `provider/modelId` 用于固定具体的提供商变体\n- 规范 id（如 `gpt-5.3-codex`）以允许提供商合并\n\n对于 `enabledModels` 和 CLI `--models`：\n\n- 精确的规范 id 展开为该规范组中的所有具体变体\n- 显式的 `provider/modelId` 条目保持精确\n- 通配符和模糊匹配仍然在具体模型上操作\n\n## `/model` 和 `--list-models`\n\n两个界面都保持提供商前缀模型的可见和可选性。\n\n它们现在还暴露了规范/合并的模型：\n\n- `/model` 在提供商标签旁包含规范视图\n- `--list-models` 打印规范部分加上具体的提供商行\n\n选择规范条目会存储规范选择器。选择提供商行会存储显式的 `provider/modelId`。\n\n## 上下文提升（模型级别回退链）\n\n上下文提升是一种溢出恢复机制，适用于小上下文变体（例如 `*-spark`），当 API 因上下文长度错误拒绝请求时，自动提升到更大上下文的同级模型。\n\n### 触发和顺序\n\n当一轮因上下文溢出错误（例如 `context_length_exceeded`）失败时，`AgentSession` 在回退到压缩**之前**尝试提升：\n\n1. 如果 `contextPromotion.enabled` 为 true，解析提升目标（见下文）。\n2. 如果找到目标，切换到该目标并重试请求——无需压缩。\n3. 如果没有可用目标，则在当前模型上回退到自动压缩。\n\n### 目标选择\n\n选择是模型驱动的，而不是角色驱动的：\n\n1. `currentModel.contextPromotionTarget`（如果已配置）\n2. 同一提供商 + API 上最小的更大上下文模型\n\n除非凭据可解析（`ModelRegistry.getApiKey(...)`），否则候选项会被忽略。\n\n### OpenAI Codex websocket 交接\n\n如果从/切换到 `openai-codex-responses`，会话提供商状态键 `openai-codex-responses` 在模型切换前关闭。这会丢弃 websocket 传输状态，使下一轮在提升后的模型上全新启动。\n\n### 持久化行为\n\n提升使用临时切换（`setModelTemporary`）：\n\n- 在会话历史中记录为临时 `model_change`\n- 不会重写已保存的角色映射\n\n### 配置显式回退链\n\n通过模型元数据中的 `contextPromotionTarget` 直接配置回退。\n\n`contextPromotionTarget` 接受以下任一格式：\n\n- `provider/model-id`（显式）\n- `model-id`（在当前提供商内解析）\n\n示例（`models.yml`）用于同一提供商上的 Spark -> 非 Spark：\n\n```yaml\nproviders:\n  openai-codex:\n    modelOverrides:\n      gpt-5.3-codex-spark:\n        contextPromotionTarget: openai-codex/gpt-5.3-codex\n```\n\n内置模型生成器在同一提供商存在基础模型时，也会自动为 `*-spark` 模型分配此项。\n\n## 兼容性和路由字段\n\n`models.yml` 支持以下 `compat` 子集：\n\n- `supportsStore`\n- `supportsDeveloperRole`\n- `supportsReasoningEffort`\n- `maxTokensField`（`max_completion_tokens` 或 `max_tokens`）\n- `openRouterRouting.only` / `openRouterRouting.order`\n- `vercelGatewayRouting.only` / `vercelGatewayRouting.order`\n\n这些由 OpenAI-completions 传输逻辑消费，并与基于 URL 的自动检测相结合。\n\n## 实际示例\n\n### 本地 OpenAI 兼容端点（无认证）\n\n```yaml\nproviders:\n  local-openai:\n    baseUrl: http://127.0.0.1:8000/v1\n    auth: none\n    api: openai-completions\n    models:\n      - id: Qwen/Qwen2.5-Coder-32B-Instruct\n        name: Qwen 2.5 Coder 32B (local)\n```\n\n### 使用基于环境变量密钥的托管代理\n\n```yaml\nproviders:\n  anthropic-proxy:\n    baseUrl: https://proxy.example.com/anthropic\n    apiKey: ANTHROPIC_PROXY_API_KEY\n    api: anthropic-messages\n    authHeader: true\n    models:\n      - id: claude-sonnet-4-20250514\n        name: Claude Sonnet 4 (Proxy)\n        reasoning: true\n        input: [text, image]\n```\n\n### 覆盖内置提供商路由 + 模型元数据\n\n```yaml\nproviders:\n  openrouter:\n    baseUrl: https://my-proxy.example.com/v1\n    headers:\n      X-Team: platform\n    modelOverrides:\n      anthropic/claude-sonnet-4:\n        name: Sonnet 4 (Corp)\n        compat:\n          openRouterRouting:\n            only: [anthropic]\n```\n\n## LiteLLM 代理自动配置\n\n当 `LITELLM_BASE_URL` 和 `LITELLM_API_KEY` 环境变量都已设置时，xcsh 会自动管理 LiteLLM 代理的 `models.yml` 配置。\n\n### 首次运行自动生成\n\n如果 `models.yml` 不存在且检测到 LiteLLM 环境变量，xcsh 会自动生成：\n\n```yaml\n# Auto-generated by xcsh for LiteLLM proxy\n# API key resolved from LITELLM_API_KEY env var at runtime\nconfigVersion: 1\nproviders:\n  anthropic:\n    baseUrl: \"https://your-litellm-proxy.example.com/anthropic\"\n    apiKey: LITELLM_API_KEY\n```\n\n同时还会生成一个带有合理图像提供商设置的默认 `config.yml`。\n\n### 启动自修复\n\n每次启动时，模型注册表中的 `startupHealthCheck()` 会运行以下检查：\n\n| 条件 | 操作 |\n|------|------|\n| `models.yml` 缺失 | 从环境变量自动生成 |\n| `models.yml` 损坏或无法解析 | 备份为 `.bak`，重新生成 |\n| `baseUrl` 与 `LITELLM_BASE_URL` 不匹配 | 备份为 `.bak`，使用新 URL 重新生成 |\n| `configVersion` 缺失或过时 | 备份为 `.bak`，使用当前版本重新生成 |\n| 配置健康 | 无操作 |\n\n所有修复在覆盖前都会创建 `.bak` 备份。所有操作都是幂等的。\n\n### CLI 命令\n\n```bash\nxcsh setup litellm              # Generate or fix LiteLLM config\nxcsh setup litellm --check      # Validate without writing\nxcsh setup litellm --check --json  # Machine-readable validation output\n```\n\n### 必需的环境变量\n\n| 变量 | 用途 |\n|------|------|\n| `LITELLM_BASE_URL` | LiteLLM 代理 URL（例如 `https://your-proxy.example.com`）。必须以 `http://` 或 `https://` 开头。 |\n| `LITELLM_API_KEY` | 代理的 API 密钥。在生成的配置中按名称引用，在运行时解析。 |\n\n如果任一变量未设置，自动配置将被静默跳过。\n\n### 配置版本控制\n\n生成的配置包含 `configVersion` 字段。当生成格式在未来版本中发生变化时，xcsh 会检测过时的配置并自动升级（带备份）。\n\n## 旧版消费者注意事项\n\n大多数模型配置现在通过 `ModelRegistry` 经由 `models.yml` 流转。\n\n一个值得注意的旧版路径仍然存在：Web 搜索 Anthropic 认证解析仍然直接在 `src/web/search/auth.ts` 中读取 `~/.xcsh/agent/models.json`。\n\n如果您依赖该特定路径，请在该模块迁移之前注意保持 JSON 兼容性。\n\n## 失败模式\n\n如果 `models.yml` 未通过模式或验证检查：\n\n- 如果设置了 `LITELLM_BASE_URL` 和 `LITELLM_API_KEY`，启动健康检查会尝试自动修复（备份损坏文件，从环境变量重新生成）。如果修复成功，注册表会重新加载修复后的配置。\n- 如果无法自动修复（环境变量未设置、写入失败），注册表将继续使用内置模型运行。\n- 错误通过 `ModelRegistry.getError()` 暴露，并在 UI/通知中显示。\n",
	"zh-cn/providers/provider-streaming-internals.md": "---\ntitle: Provider 流式处理内部机制\ndescription: Provider 流式处理实现，包括 SSE 解析、token 计数和背压处理。\nsidebar:\n  order: 2\n  label: 流式处理内部机制\ni18n:\n  sourceHash: a32ffa769c4d\n  translator: machine\n---\n\n# Provider 流式处理内部机制\n\n本文档解释了 `@f5-sales-demo/pi-ai` 中 token/工具流式处理的标准化方式，以及如何通过 `@f5-sales-demo/pi-agent-core` 和 `coding-agent` 会话事件进行传播。\n\n## 端到端流程\n\n1. `streamSimple()`（`packages/ai/src/stream.ts`）映射通用选项并分发到 provider 流函数。\n2. Provider 流函数（`anthropic.ts`、`openai-responses.ts`、`google.ts`）将 provider 原生流事件转换为统一的 `AssistantMessageEvent` 序列。\n3. 每个 provider 将事件推送到 `AssistantMessageEventStream`（`packages/ai/src/utils/event-stream.ts`），该模块对 delta 事件进行节流并提供：\n   - 用于增量更新的异步迭代\n   - `result()` 用于获取最终的 `AssistantMessage`\n4. `agentLoop`（`packages/agent/src/agent-loop.ts`）消费这些事件，修改正在进行的助手状态，并发出携带原始 `assistantMessageEvent` 的 `message_update` 事件。\n5. `AgentSession`（`packages/coding-agent/src/session/agent-session.ts`）订阅代理事件，持久化消息，驱动扩展钩子，并应用会话行为（重试、压缩、TTSR、流式编辑中止检查）。\n\n## `@f5-sales-demo/pi-ai` 中的统一流契约\n\n所有 provider 发出相同的形状（`packages/ai/src/types.ts` 中的 `AssistantMessageEvent`）：\n\n- `start`\n- 内容块生命周期三元组：\n  - 文本：`text_start` → `text_delta`* → `text_end`\n  - 思考：`thinking_start` → `thinking_delta`* → `thinking_end`\n  - 工具调用：`toolcall_start` → `toolcall_delta`* → `toolcall_end`\n- 终止事件：\n  - `done`，带有 `reason: \"stop\" | \"length\" | \"toolUse\"`\n  - 或 `error`，带有 `reason: \"aborted\" | \"error\"`\n\n`AssistantMessageEventStream` 保证：\n\n- 最终结果由终止事件（`done` 或 `error`）解析\n- delta 事件被批量化/节流（约 50ms）\n- 在非 delta 事件之前和完成之前刷新缓冲的 delta\n\n## Delta 节流与协调行为\n\n`AssistantMessageEventStream` 将 `text_delta`、`thinking_delta` 和 `toolcall_delta` 视为可合并事件：\n\n- 缓冲的 delta 仅在**类型 + contentIndex** 匹配时合并\n- 合并保留最新的 `partial` 快照\n- 非 delta 事件强制立即刷新\n\n这为 TUI/事件消费者平滑了高频 provider 流，但这不是 provider 背压：provider 仍以全速生产，而本地流进行缓冲。\n\n## Provider 标准化详情\n\n## Anthropic（`anthropic-messages`）\n\n源码：`packages/ai/src/providers/anthropic.ts`\n\n标准化要点：\n\n- `message_start` 初始化用量（输入/输出/缓存 token）\n- `content_block_start` 映射为文本/思考/工具调用的开始事件\n- `content_block_delta` 映射：\n  - `text_delta` → `text_delta`\n  - `thinking_delta` → `thinking_delta`\n  - `input_json_delta` → `toolcall_delta`\n  - `signature_delta` 仅更新 `thinkingSignature`（不产生事件）\n- `content_block_stop` 发出对应的 `*_end`\n- `message_delta.stop_reason` 通过 `mapStopReason()` 映射\n\n工具调用参数流式处理：\n\n- 每个工具块携带内部 `partialJson`\n- 每个 JSON delta 追加到 `partialJson`\n- 每次 delta 都通过 `parseStreamingJson()` 重新解析 `arguments`\n- `toolcall_end` 再次重新解析，然后移除 `partialJson`\n\n## OpenAI Responses（`openai-responses`）\n\n源码：`packages/ai/src/providers/openai-responses.ts`\n\n标准化要点：\n\n- `response.output_item.added` 启动推理/文本/函数调用块\n- 推理摘要事件（`response.reasoning_summary_text.delta`）变为 `thinking_delta`\n- 输出/拒绝 delta 变为 `text_delta`\n- `response.function_call_arguments.delta` 变为 `toolcall_delta`\n- `response.output_item.done` 发出 `thinking_end` / `text_end` / `toolcall_end`\n- `response.completed` 将状态映射为停止原因和用量\n\n工具调用参数流式处理：\n\n- 与 Anthropic 相同的 `partialJson` 累积模式\n- 仅发送 `response.function_call_arguments.done` 的 provider 仍然填充最终参数\n- 工具调用 ID 标准化为 `\"<call_id>|<item_id>\"`\n\n## Google Generative AI（`google-generative-ai`）\n\n源码：`packages/ai/src/providers/google.ts`\n\n标准化要点：\n\n- 迭代 `candidate.content.parts`\n- 文本部分通过 `isThinkingPart(part)` 分为思考与文本\n- 块转换在开始新块之前关闭上一个块\n- `part.functionCall` 被视为完整的工具调用（立即发出 start/delta/end）\n- 完成原因通过 `google-shared.ts` 中的 `mapStopReason()` 映射\n\n工具调用参数流式处理：\n\n- 函数调用参数以结构化对象形式到达，而非增量 JSON 文本\n- 实现发出一个合成的 `toolcall_delta`，包含 `JSON.stringify(arguments)`\n- 在此路径中 Google 不需要部分 JSON 解析器\n\n## 部分工具调用 JSON 累积与恢复\n\nAnthropic/OpenAI Responses 的共享行为使用 `parseStreamingJson()`（`packages/ai/src/utils/json-parse.ts`）：\n\n1. 尝试 `JSON.parse`\n2. 回退到 `partial-json` 解析器处理不完整片段\n3. 如果两者都失败，返回 `{}`\n\n影响：\n\n- 格式错误或截断的参数 delta 不会立即导致流处理崩溃\n- 进行中的 `arguments` 可能暂时为 `{}`\n- 后续有效的 delta 可以恢复结构化参数，因为每次追加都会重新解析\n- 最终的 `toolcall_end` 在发出之前执行最后一次解析尝试\n\n## 停止原因与传输/运行时错误\n\nProvider 停止原因映射为标准化的 `stopReason`：\n\n- Anthropic：`end_turn`→`stop`、`max_tokens`→`length`、`tool_use`→`toolUse`、安全/拒绝情况→`error`\n- OpenAI Responses：`completed`→`stop`、`incomplete`→`length`、`failed/cancelled`→`error`\n- Google：`STOP`→`stop`、`MAX_TOKENS`→`length`、安全/禁止/格式错误的函数调用类别→`error`\n\n错误语义分为两个阶段：\n\n1. **模型完成语义**（provider 报告的完成原因/状态）\n2. **传输/运行时故障**（网络/客户端/解析器/中止异常）\n\n如果 provider 流抛出异常或发出失败信号，每个 provider 包装器捕获并发出终止 `error` 事件，包含：\n\n- 当中止信号被设置时 `stopReason = \"aborted\"`\n- 否则 `stopReason = \"error\"`\n- `errorMessage = formatErrorMessageWithRetryAfter(error)`\n\n## 格式错误的 chunk / SSE 解析失败行为\n\n对于这些 provider 路径，chunk/SSE 帧处理由供应商 SDK 流（Anthropic SDK、OpenAI SDK、Google SDK）处理。此代码未在此处实现自定义 SSE 解码器。\n\n当前实现中观察到的行为：\n\n- SDK 级别的格式错误 chunk/SSE 解析以异常或流 `error` 事件的形式呈现\n- Provider 包装器将其转换为统一的终止 `error` 事件\n- 流函数本身内部没有 provider 特定的恢复/重试\n- 更高级别的重试在 `AgentSession` 自动重试逻辑中处理（消息级重试，而非流 chunk 重放）\n\n## 取消边界\n\n取消是分层的：\n\n- AI provider 请求：`options.signal` 被传递到 provider 客户端流调用中。\n- Provider 包装器：流循环结束后，中止信号强制进入错误路径（`\"Request was aborted\"`）。\n- 代理循环：在处理每个 provider 事件之前检查 `signal.aborted`，并可以从最新的部分内容合成一个中止的助手消息。\n- 会话/代理控制：`AgentSession.abort()` -> `agent.abort()` -> 共享中止控制器取消。\n\n工具执行取消与模型流取消是分开的：\n\n- 工具运行器使用 `AbortSignal.any([agentSignal, steeringAbortSignal])`\n- 引导中断可以中止剩余的工具执行，同时保留已产生的工具结果\n\n## 背压边界\n\nprovider SDK 流与下游消费者之间没有硬背压机制：\n\n- `EventStream` 使用无最大大小限制的内存队列\n- 节流降低了 UI 更新速率但不会减慢 provider 接收速度\n- 如果消费者严重滞后，排队的事件会持续增长直到完成\n\n当前设计优先考虑响应性和简单排序，而非有界缓冲区流控制。\n\n## 流事件如何呈现为代理/会话事件\n\n`agentLoop.streamAssistantResponse()` 将 `AssistantMessageEvent` 桥接到 `AgentEvent`：\n\n- 在 `start` 时：推送占位助手消息并发出 `message_start`\n- 在块事件（`text_*`、`thinking_*`、`toolcall_*`）时：更新最后的助手消息，发出带有原始 `assistantMessageEvent` 的 `message_update`\n- 在终止（`done`/`error`）时：从 `response.result()` 解析最终消息，发出 `message_end`\n\n`AgentSession` 然后消费这些事件以实现会话级行为：\n\n- TTSR 监视 `message_update.assistantMessageEvent` 中的 `text_delta` 和 `toolcall_delta`\n- 流式编辑守卫检查 `edit` 调用上的 `toolcall_delta`/`toolcall_end`，并可以提前中止\n- 持久化在 `message_end` 时写入最终消息\n- 自动重试检查助手的 `stopReason === \"error\"` 加上 `errorMessage` 启发式规则\n\n## 统一与 provider 特定职责\n\n统一（通用契约）：\n\n- 事件形状（`AssistantMessageEvent`）\n- 最终结果提取（`done`/`error`）\n- delta 节流 + 合并规则\n- 代理/会话事件传播模型\n\nProvider 特定（未完全抽象）：\n\n- 上游事件分类法和映射逻辑\n- 停止原因转换表\n- 工具调用 ID 约定\n- 推理/思考块语义和签名\n- 用量 token 语义和可用时间\n- 每个 API 的消息转换约束\n\n## 实现文件\n\n- [`../../ai/src/stream.ts`](../../packages/ai/src/stream.ts) — provider 分发、选项映射、API 密钥/会话管道。\n- [`../../ai/src/utils/event-stream.ts`](../../packages/ai/src/utils/event-stream.ts) — 通用流队列 + 助手 delta 节流。\n- [`../../ai/src/utils/json-parse.ts`](../../packages/ai/src/utils/json-parse.ts) — 用于流式工具参数的部分 JSON 解析。\n- [`../../ai/src/providers/anthropic.ts`](../../packages/ai/src/providers/anthropic.ts) — Anthropic 事件转换和工具 JSON delta 累积。\n- [`../../ai/src/providers/openai-responses.ts`](../../packages/ai/src/providers/openai-responses.ts) — OpenAI Responses 事件转换和状态映射。\n- [`../../ai/src/providers/google.ts`](../../packages/ai/src/providers/google.ts) — Gemini 流 chunk 到块的转换。\n- [`../../ai/src/providers/google-shared.ts`](../../packages/ai/src/providers/google-shared.ts) — Gemini 完成原因映射和共享转换规则。\n- [`../../agent/src/agent-loop.ts`](../../packages/agent/src/agent-loop.ts) — provider 流消费和 `message_update` 桥接。\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — 会话级流式更新处理、中止、重试和持久化。\n",
	"zh-cn/providers/python-repl.md": "---\ntitle: Python 工具与 IPython 运行时\ndescription: 基于 IPython 内核管理、执行和输出捕获的 Python REPL 工具运行时。\nsidebar:\n  order: 3\n  label: Python 与 IPython\ni18n:\n  sourceHash: 70f0a034ecef\n  translator: machine\n---\n\n# Python 工具与 IPython 运行时\n\n本文档描述了 `packages/coding-agent` 中当前的 Python 执行栈。\n内容涵盖工具行为、内核/网关生命周期、环境处理、执行语义、输出渲染以及运维故障模式。\n\n## 范围与关键文件\n\n- 工具层：`src/tools/python.ts`\n- 会话/每次调用的内核编排：`src/ipy/executor.ts`\n- 内核协议 + 网关集成：`src/ipy/kernel.ts`\n- 共享本地网关协调器：`src/ipy/gateway-coordinator.ts`\n- 用户触发 Python 运行的交互模式渲染器：`src/modes/components/python-execution.ts`\n- 运行时/环境过滤与 Python 解析：`src/ipy/runtime.ts`\n\n## Python 工具是什么\n\n`python` 工具通过 Jupyter Kernel Gateway 支持的内核执行一个或多个 Python 单元格（而非每个单元格直接生成 `python -c` 进程）。\n\n工具参数：\n\n```ts\n{\n  cells: Array<{ code: string; title?: string }>;\n  timeout?: number; // 秒，限制在 1..600 之间，默认 30\n  cwd?: string;\n  reset?: boolean; // 仅在第一个单元格执行前重置内核\n}\n```\n\n该工具在会话中为 `concurrency = \"exclusive\"`，因此调用不会重叠。\n\n## 网关生命周期\n\n### 模式\n\n有两种网关路径：\n\n1. **外部网关**（设置了 `PI_PYTHON_GATEWAY_URL`）\n   - 直接使用配置的 URL。\n   - 可选通过 `PI_PYTHON_GATEWAY_TOKEN` 进行认证。\n   - 不会生成或管理本地网关进程。\n\n2. **本地共享网关**（默认路径）\n   - 使用在 `~/.xcsh/agent/python-gateway` 下协调的单个共享进程。\n   - 元数据文件：`gateway.json`\n   - 锁文件：`gateway.lock`\n   - 启动命令：\n     - `python -m kernel_gateway`\n     - 绑定到 `127.0.0.1:<分配的端口>`\n     - 启动健康检查：`GET /api/kernelspecs`\n\n### 本地共享网关协调\n\n`acquireSharedGateway()`：\n\n- 通过心跳获取文件锁（`gateway.lock`）。\n- 如果 PID 存活且健康检查通过，则复用 `gateway.json`。\n- 在需要时清理过期的信息/PID。\n- 当不存在健康的网关时启动新网关。\n\n`releaseSharedGateway()` 目前为空操作（内核关闭不会销毁共享网关）。\n\n`shutdownSharedGateway()` 显式终止共享进程并清除网关元数据。\n\n### 重要约束\n\n`python.sharedGateway=false` 在内核启动时会被拒绝：\n\n- 错误信息：`Shared Python gateway required; local gateways are disabled`\n- 不存在每进程非共享的本地网关模式。\n\n## 内核生命周期\n\n每次执行都使用通过 `POST /api/kernels` 在选定网关上创建的内核。\n\n内核启动序列：\n\n1. 可用性检查（`checkPythonKernelAvailability`）\n2. 创建内核（`/api/kernels`）\n3. 打开 websocket（`/api/kernels/:id/channels`）\n4. 初始化内核环境（`cwd`、环境变量、`sys.path`）\n5. 执行 `PYTHON_PRELUDE`\n6. 从以下位置加载扩展模块：\n   - 用户级：`~/.xcsh/agent/modules/*.py`\n   - 项目级：`<cwd>/.xcsh/modules/*.py`（覆盖同名用户模块）\n\n内核关闭：\n\n- 通过 `DELETE /api/kernels/:id` 删除远程内核\n- 关闭 websocket\n- 调用共享网关释放钩子（目前为空操作）\n\n## 会话持久化语义\n\n`python.kernelMode` 控制内核复用：\n\n- `session`（默认）\n  - 按会话标识 + cwd 作为键复用内核会话。\n  - 每个会话的执行通过队列串行化。\n  - 空闲会话在 5 分钟后被驱逐。\n  - 最多 4 个会话；溢出时驱逐最旧的会话。\n  - 心跳检查检测已死亡的内核。\n  - 允许自动重启一次；反复崩溃则触发硬故障。\n\n- `per-call`\n  - 为每个执行请求创建全新的内核。\n  - 请求完成后关闭内核。\n  - 没有跨调用的状态持久化。\n\n### 单次工具调用中的多单元格行为\n\n单元格在该工具调用的同一内核实例中按顺序运行。\n\n如果中间单元格失败：\n\n- 之前单元格的状态保留在内存中。\n- 工具返回指向性错误，指示哪个单元格失败。\n- 后续单元格不会被执行。\n\n`reset=true` 仅应用于该调用中的第一个单元格执行。\n\n## 环境过滤与运行时解析\n\n在启动网关/内核运行时之前会对环境进行过滤：\n\n- 允许列表包括核心变量，如 `PATH`、`HOME`、区域设置变量、`VIRTUAL_ENV`、`PYTHONPATH` 等。\n- 允许前缀：`LC_`、`XDG_`、`PI_`\n- 拒绝列表会剥离常见的 API 密钥（OpenAI/Anthropic/Gemini 等）\n\n运行时选择顺序：\n\n1. 激活/定位的虚拟环境（`VIRTUAL_ENV`，然后是 `<cwd>/.venv`、`<cwd>/venv`）\n2. `~/.xcsh/python-env` 处的托管虚拟环境\n3. PATH 中的 `python` 或 `python3`\n\n当选择了虚拟环境时，其 bin/Scripts 路径会被前置到 `PATH` 中。\n\nPython 内部的内核环境初始化还包括：\n\n- `os.chdir(cwd)`\n- 将提供的环境映射注入 `os.environ`\n- 确保 cwd 在 `sys.path` 中\n\n## 工具可用性与模式选择\n\n`python.toolMode`（默认 `both`）+ 可选的 `PI_PY` 覆盖控制暴露方式：\n\n- `ipy-only`\n- `bash-only`\n- `both`\n\n`PI_PY` 接受的值：\n\n- `0` / `bash` -> `bash-only`\n- `1` / `py` -> `ipy-only`\n- `mix` / `both` -> `both`\n\n如果 Python 预检失败，工具创建会在该会话中降级为 bash-only。\n\n## 执行流程与取消/超时\n\n### 工具级超时\n\n`python` 工具超时以秒为单位，默认 30，限制在 `1..600` 之间。\n\n该工具组合了：\n\n- 调用者中止信号\n- 超时中止信号\n\n通过 `AbortSignal.any(...)` 实现。\n\n### 内核执行取消\n\n在中止/超时时：\n\n- 执行被标记为已取消。\n- 尝试通过 REST（`POST /interrupt`）和控制通道 `interrupt_request` 中断内核。\n- 结果包含 `cancelled=true`。\n- 超时路径将输出标注为 `Command timed out after <n> seconds`。\n\n### stdin 行为\n\n不支持交互式 stdin。\n\n如果内核发出 `input_request`：\n\n- 工具记录 `stdinRequested=true`\n- 发出解释性文本\n- 发送空的 `input_reply`\n- 执行在执行器层被视为失败\n\n## 输出捕获与渲染\n\n### 捕获的输出类别\n\n来自内核消息：\n\n- `stream` -> 纯文本块\n- `display_data`/`execute_result` -> 富显示处理\n- `error` -> 回溯文本\n- 自定义 MIME `application/x-xcsh-status` -> 结构化状态事件\n\n显示 MIME 优先级：\n\n1. `text/markdown`\n2. `text/plain`\n3. `text/html`（转换为基础 markdown）\n\n另外捕获为结构化输出的还有：\n\n- `application/json` -> JSON 树数据\n- `image/png` -> 图像载荷\n- `application/x-xcsh-status` -> 状态事件\n\n### 存储与截断\n\n输出通过 `OutputSink` 流式传输，可能会持久化到制品存储。\n\n工具结果可包含截断元数据和 `artifact://<id>` 以恢复完整输出。\n\n### 渲染器行为\n\n- 工具渲染器（`python.ts`）：\n  - 显示带有每个单元格状态的代码单元格块\n  - 折叠预览默认显示 10 行\n  - 支持展开模式以查看完整输出和更丰富的状态详情\n- 交互式渲染器（`python-execution.ts`）：\n  - 用于 TUI 中用户触发的 Python 执行\n  - 折叠预览默认显示 20 行\n  - 为显示安全性将超长单行限制为 4000 字符\n  - 显示取消/错误/截断通知\n\n## 外部网关支持\n\n设置：\n\n```bash\nexport PI_PYTHON_GATEWAY_URL=\"http://127.0.0.1:8888\"\n# 可选：\nexport PI_PYTHON_GATEWAY_TOKEN=\"...\"\n```\n\n与本地共享网关的行为差异：\n\n- 无本地网关锁/信息文件\n- 无本地进程生成/终止\n- 健康检查和内核 CRUD 针对外部端点运行\n- 认证失败会给出明确的令牌指导信息\n\n## 运维故障排除（当前故障模式）\n\n- **Python 工具不可用**\n  - 检查 `python.toolMode` / `PI_PY`。\n  - 如果预检失败，运行时会回退到 bash-only。\n\n- **内核可用性错误**\n  - 本地模式要求在解析的 Python 运行时中 `kernel_gateway` 和 `ipykernel` 均可导入。\n  - 使用以下命令安装：\n\n    ```bash\n    python -m pip install jupyter_kernel_gateway ipykernel\n    ```\n\n- **`python.sharedGateway=false` 导致启动失败**\n  - 这在当前实现中是预期行为。\n\n- **外部网关认证/可达性失败**\n  - 401/403 -> 设置 `PI_PYTHON_GATEWAY_TOKEN`。\n  - 超时/不可达 -> 验证 URL/网络及网关健康状态。\n\n- **执行挂起后超时**\n  - 如果工作负载合理，增加工具 `timeout`（最大 600 秒）。\n  - 对于卡住的代码，取消会触发内核中断，但用户代码可能仍需重构。\n\n- **Python 代码中的 stdin/输入提示**\n  - `input()` 在此运行时路径中不支持交互式使用；请以编程方式传递数据。\n\n- **资源耗尽（`EMFILE` / 打开文件过多）**\n  - 会话管理器触发共享网关恢复（会话拆除 + 共享网关重启）。\n\n- **工作目录错误**\n  - 工具在执行前会验证 `cwd` 存在且为目录。\n\n## 相关环境变量\n\n- `PI_PY` — 工具暴露覆盖（上述 `bash-only`/`ipy-only`/`both` 映射）\n- `PI_PYTHON_GATEWAY_URL` — 使用外部网关\n- `PI_PYTHON_GATEWAY_TOKEN` — 可选的外部网关认证令牌\n- `PI_PYTHON_SKIP_CHECK=1` — 跳过 Python 预检/预热检查\n- `PI_PYTHON_IPC_TRACE=1` — 记录内核 IPC 发送/接收跟踪\n- `PI_DEBUG_STARTUP=1` — 发出启动阶段调试标记\n",
	"zh-cn/runtime-tools/bash-tool-runtime.md": "---\ntitle: Bash 工具运行时\ndescription: >-\n  Bash tool runtime with shell process management, sandboxing, timeout, and\n  output streaming.\nsidebar:\n  order: 1\n  label: Bash 工具\ni18n:\n  sourceHash: 18b12aa5dbd5\n  translator: machine\n---\n\n# Bash 工具运行时\n\n本文档描述了 agent 工具调用所使用的 **`bash` 工具**运行时路径，涵盖从命令规范化到执行、截断/产物（artifacts）以及渲染的完整流程。\n\n文档还指出了在交互式 TUI、打印模式、RPC 模式以及用户发起的感叹号（`!`）shell 执行中行为存在差异的地方。\n\n## 作用域和运行时表面\n\ncoding-agent 中有两个不同的 bash 执行表面：\n\n1. **工具调用表面**（`toolName: \"bash\"`）：当模型调用 bash 工具时使用。\n   - 入口点：`BashTool.execute()`。\n2. **用户感叹号命令表面**（交互式输入中的 `!cmd` 或 RPC `bash` 命令）：会话级辅助路径。\n   - 入口点：`AgentSession.executeBash()`。\n\n两者最终都使用 `src/exec/bash-executor.ts` 中的 `executeBash()` 进行非 PTY 执行，但只有工具调用路径会运行规范化/拦截和工具渲染器逻辑。\n\n## 端到端工具调用管线\n\n## 1) 输入规范化和参数合并\n\n`BashTool.execute()` 首先通过 `normalizeBashCommand()` 规范化原始命令：\n\n- 提取尾部的 `| head -n N`、`| head -N`、`| tail -n N`、`| tail -N` 并转为结构化限制，\n- 去除首尾空白，\n- 保留内部空白不变。\n\n然后将提取的限制与显式工具参数合并：\n\n- 显式 `head`/`tail` 参数覆盖提取的值，\n- 提取的值仅作为后备。\n\n### 注意事项\n\n`bash-normalize.ts` 中的注释提到会去除 `2>&1`，但当前实现并未将其移除。运行时行为仍然正确（stdout/stderr 已经合并），但规范化行为比注释所描述的范围更窄。\n\n## 2) 可选拦截（命令阻止路径）\n\n如果 `bashInterceptor.enabled` 为 true，`BashTool` 会从设置中加载规则，并对规范化后的命令运行 `checkBashInterception()`。\n\n拦截行为：\n\n- 命令**仅在**以下条件同时满足时被阻止：\n  - 正则表达式规则匹配，且\n  - 建议的工具存在于 `ctx.toolNames` 中。\n- 无效的正则表达式规则会被静默跳过。\n- 阻止时，`BashTool` 抛出 `ToolError`，消息为：\n  - `Blocked: ...`\n  - 包含原始命令。\n\n默认规则模式（在代码中定义）针对常见的误用：\n\n- 文件读取工具（`cat`、`head`、`tail` 等）\n- 搜索工具（`grep`、`rg` 等）\n- 文件查找工具（`find`、`fd` 等）\n- 就地编辑器（`sed -i`、`perl -i`、`awk -i inplace`）\n- shell 重定向写入（`echo ... > file`、heredoc 重定向）\n\n### 注意事项\n\n`InterceptionResult` 包含 `suggestedTool`，但 `BashTool` 当前仅暴露消息文本（`details` 中没有结构化的建议工具字段）。\n\n## 3) CWD 验证和超时限制\n\n`cwd` 相对于会话 cwd（`resolveToCwd`）解析，然后通过 `stat` 验证：\n\n- 路径不存在 -> `ToolError(\"Working directory does not exist: ...\")`\n- 不是目录 -> `ToolError(\"Working directory is not a directory: ...\")`\n\n超时被限制在 `[1, 3600]` 秒范围内，并转换为毫秒。\n\n## 4) 产物分配\n\n在执行之前，工具为截断输出存储分配产物路径/ID（尽力而为）。\n\n- 产物分配失败是非致命的（执行继续进行，不使用产物溢出文件），\n- 产物 ID/路径传递到执行路径中，用于截断时的完整输出持久化。\n\n## 5) PTY 与非 PTY 执行选择\n\n`BashTool` 仅在以下条件全部为 true 时选择 PTY 执行：\n\n- `bash.virtualTerminal === \"on\"`\n- `PI_NO_PTY !== \"1\"`\n- 工具上下文有 UI（`ctx.hasUI === true` 且 `ctx.ui` 已设置）\n\n否则使用非交互式 `executeBash()`。\n\n这意味着打印模式和非 UI 的 RPC/工具上下文始终使用非 PTY。\n\n## 非交互式执行引擎 (`executeBash`)\n\n## Shell 会话复用模型\n\n`executeBash()` 在进程全局映射中缓存原生 `Shell` 实例，键由以下内容组成：\n\n- shell 路径，\n- 配置的命令前缀，\n- 快照路径，\n- 序列化的 shell 环境变量，\n- 可选的 agent 会话键。\n\n对于会话级执行，`AgentSession.executeBash()` 传递 `sessionKey: this.sessionId`，将复用隔离到每个会话。\n\n工具调用路径**不**传递 `sessionKey`，因此复用范围基于 shell 配置/快照/环境变量。\n\n## Shell 配置和快照行为\n\n每次调用时，执行器加载设置中的 shell 配置（`shell`、`env`、可选的 `prefix`）。\n\n如果选择的 shell 包含 `bash`，它会尝试 `getOrCreateSnapshot()`：\n\n- 快照从用户 rc 中捕获别名/函数/选项，\n- 快照创建是尽力而为的，\n- 失败时回退为无快照。\n\n如果配置了 `prefix`，命令变为：\n\n```text\n<prefix> <command>\n```\n\n## 流式输出和取消\n\n`Shell.run()` 将数据块流式传输到回调。执行器将每个数据块输送到 `OutputSink` 和可选的 `onChunk` 回调。\n\n取消：\n\n- abort 信号触发 `shellSession.abort(...)`，\n- 原生结果中的超时映射为 `cancelled: true` + 注释文本，\n- 显式取消同样返回 `cancelled: true` + 注释。\n\n执行器内部不会因超时/取消抛出异常；它返回结构化的 `BashResult`，由调用方映射错误语义。\n\n## 交互式 PTY 路径 (`runInteractiveBashPty`)\n\n当 PTY 启用时，工具运行 `runInteractiveBashPty()`，该函数打开一个覆盖层控制台组件并驱动一个原生 `PtySession`。\n\n行为要点：\n\n- xterm-headless 虚拟终端在覆盖层中渲染视口，\n- 键盘输入被规范化（包括 Kitty 序列和应用光标模式处理），\n- 运行时按 `esc` 会终止 PTY 会话，\n- 终端尺寸变化传播到 PTY（`session.resize(cols, rows)`）。\n\n为无人值守运行注入环境硬化默认值：\n\n- 禁用分页器（`PAGER=cat`、`GIT_PAGER=cat` 等），\n- 禁用编辑器提示（`GIT_EDITOR=true`、`EDITOR=true` 等），\n- 减少终端/认证提示（`GIT_TERMINAL_PROMPT=0`、`SSH_ASKPASS=/usr/bin/false`、`CI=1`），\n- 包管理器/工具的非交互行为自动化标志。\n\nPTY 输出被规范化（`CRLF`/`CR` 转为 `LF`、`sanitizeText`）并写入 `OutputSink`，包括产物溢出支持。\n\nPTY 启动/运行时错误时，sink 接收 `PTY error: ...` 行，命令以未定义的退出码结束。\n\n## 输出处理：流式、截断、产物溢出\n\nPTY 和非 PTY 路径都使用 `OutputSink`。\n\n## OutputSink 语义\n\n- 保持一个内存中的 UTF-8 安全尾部缓冲区（`DEFAULT_MAX_BYTES`，当前为 50KB），\n- 跟踪已见的总字节数/行数，\n- 如果产物路径存在且输出溢出（或文件已激活），将完整流写入产物文件，\n- 当内存阈值溢出时，将内存缓冲区修剪为尾部（UTF-8 边界安全），\n- 溢出/文件溢出发生时标记 `truncated`。\n\n`dump()` 返回：\n\n- `output`（可能带有注释前缀），\n- `truncated`，\n- `totalLines/totalBytes`，\n- `outputLines/outputBytes`，\n- 如果产物文件已激活则返回 `artifactId`。\n\n### 长输出注意事项\n\n运行时截断在 `OutputSink` 中基于字节阈值（默认 50KB）。在此代码路径中不强制执行硬性的 2000 行上限。\n\n## 实时工具更新\n\n对于非 PTY 执行，`BashTool` 使用单独的 `TailBuffer` 进行部分更新，并在命令运行期间发出 `onUpdate` 快照。\n\n对于 PTY 执行，实时渲染由自定义 UI 覆盖层处理，而非通过 `onUpdate` 文本块。\n\n## 结果整形、元数据和错误映射\n\n执行后：\n\n1. `cancelled` 处理：\n   - 如果 abort 信号已中止 -> 抛出 `ToolAbortError`（中止语义），\n   - 否则 -> 抛出 `ToolError`（视为工具失败）。\n2. PTY `timedOut` -> 抛出 `ToolError`。\n3. 对最终输出文本应用 head/tail 过滤（`applyHeadTail`，先 head 后 tail）。\n4. 空输出变为 `(no output)`。\n5. 通过 `toolResult(...).truncationFromSummary(result, { direction: \"tail\" })` 附加截断元数据。\n6. 退出码映射：\n   - 缺少退出码 -> `ToolError(\"... missing exit status\")`\n   - 非零退出 -> `ToolError(\"... Command exited with code N\")`\n   - 零退出 -> 成功结果。\n\n成功负载结构：\n\n- `content`：文本输出，\n- 截断时包含 `details.meta.truncation`，包括：\n  - `direction`、`truncatedBy`、总/输出行+字节计数，\n  - `shownRange`，\n  - 可用时包含 `artifactId`。\n\n由于内置工具被 `wrapToolWithMetaNotice()` 包装，截断通知文本会自动附加到最终文本内容中（例如：`Full: artifact://<id>`）。\n\n## 渲染路径\n\n## 工具调用渲染器 (`bashToolRenderer`)\n\n`bashToolRenderer` 用于工具调用消息（`toolCall` / `toolResult`）：\n\n- 折叠模式显示视觉行截断的预览，\n- 展开模式显示所有当前可用的输出文本，\n- 警告行包含截断原因和截断时的 `artifact://<id>`，\n- 超时值（来自参数）显示在底部元数据行中。\n\n### 注意事项：完整产物展开\n\n`BashRenderContext` 有 `isFullOutput`，但当前渲染器上下文构建器不会为 bash 工具结果设置它。展开视图仍然使用结果内容中已有的文本（尾部/截断输出），除非其他调用方提供完整的产物内容。\n\n## 用户感叹号命令组件 (`BashExecutionComponent`)\n\n`BashExecutionComponent` 用于交互模式中的用户 `!` 命令（非模型工具调用）：\n\n- 实时流式传输数据块，\n- 折叠预览保留最后 20 个逻辑行，\n- 每行限制 4000 字符，\n- 当存在元数据时显示截断 + 产物警告，\n- 分别标记已取消/错误/退出状态。\n\n此组件由 `CommandController.handleBashCommand()` 接入，数据来自 `AgentSession.executeBash()`。\n\n## 模式特定的行为差异\n\n| 表面 | 入口路径 | PTY 资格 | 实时输出 UX | 错误呈现 |\n| ------------------------------ | ----------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------ |\n| 交互式工具调用 | `BashTool.execute` | 是，当 `bash.virtualTerminal=on` 且 UI 存在且 `PI_NO_PTY!=1` | PTY 覆盖层（交互式）或流式尾部更新 | 工具错误变为 `toolResult.isError` |\n| 打印模式工具调用 | `BashTool.execute` | 否（无 UI 上下文） | 无 TUI 覆盖层；输出出现在事件流/最终助手文本流中 | 相同的工具错误映射 |\n| RPC 工具调用（agent 工具） | `BashTool.execute` | 通常无 UI -> 非 PTY | 结构化工具事件/结果 | 相同的工具错误映射 |\n| 交互式感叹号命令（`!`） | `AgentSession.executeBash` + `BashExecutionComponent` | 否（直接使用执行器） | 专用 bash 执行组件 | 控制器捕获异常并显示 UI 错误 |\n| RPC `bash` 命令 | `rpc-mode` -> `session.executeBash` | 否 | 直接返回 `BashResult` | 消费方处理返回的字段 |\n\n## 运维注意事项\n\n- 拦截器仅在建议的工具当前在上下文中可用时才阻止命令。\n- 如果产物分配失败，截断仍然发生，但没有可用的 `artifact://` 反向引用。\n- Shell 会话缓存在此模块中没有显式驱逐策略；生命周期为进程作用域。\n- PTY 和非 PTY 的超时表面不同：\n  - PTY 暴露显式的 `timedOut` 结果字段，\n  - 非 PTY 将超时映射为 `cancelled + annotation` 摘要。\n\n## 实现文件\n\n- [`src/tools/bash.ts`](../../packages/coding-agent/src/tools/bash.ts) — 工具入口点、规范化/拦截、PTY/非 PTY 选择、结果/错误映射、bash 工具渲染器。\n- [`src/tools/bash-normalize.ts`](../../packages/coding-agent/src/tools/bash-normalize.ts) — 命令规范化和运行后 head/tail 过滤。\n- [`src/tools/bash-interceptor.ts`](../../packages/coding-agent/src/tools/bash-interceptor.ts) — 拦截器规则匹配和命令阻止消息。\n- [`src/exec/bash-executor.ts`](../../packages/coding-agent/src/exec/bash-executor.ts) — 非 PTY 执行器、shell 会话复用、取消接入、输出 sink 集成。\n- [`src/tools/bash-interactive.ts`](../../packages/coding-agent/src/tools/bash-interactive.ts) — PTY 运行时、覆盖层 UI、输入规范化、非交互式环境默认值。\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — `OutputSink` 截断/产物溢出和摘要元数据。\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — 产物分配辅助函数和流式尾部缓冲区。\n- [`src/tools/output-meta.ts`](../../packages/coding-agent/src/tools/output-meta.ts) — 截断元数据结构 + 通知注入包装器。\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — 会话级 `executeBash`、消息记录、中止生命周期。\n- [`src/modes/components/bash-execution.ts`](../../packages/coding-agent/src/modes/components/bash-execution.ts) — 交互式 `!` 命令执行组件。\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts) — 交互式 `!` 命令 UI 流/更新完成的接入。\n- [`src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts) — RPC `bash` 和 `abort_bash` 命令表面。\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — `artifact://<id>` 解析。\n",
	"zh-cn/runtime-tools/context-command.md": "---\ntitle: F5 XC 上下文\ndescription: 将 xcsh 连接到 F5 Distributed Cloud 租户 -- 创建、切换和管理认证上下文。\nsidebar:\n  order: 1\n  label: F5 XC 上下文\ni18n:\n  sourceHash: a9cccbc338f0\n  translator: machine\n---\n\n# F5 XC 上下文\n\nxcsh 通过**上下文**连接到 F5 Distributed Cloud -- 上下文是将租户 URL、API 令牌和命名空间绑定在一起的命名凭据集。如果您使用过 `kubectl config use-context` 或 `kubectx`，工作流程完全相同：创建上下文、通过名称在上下文之间切换，并使用 `-` 快速切回。\n\n## 快速入门\n\n### 1. 创建您的第一个上下文\n\n您需要从 F5 XC 控制台获取三项信息：租户 URL、API 令牌，以及可选的命名空间。\n\n```\n/context create production https://acme.console.ves.volterra.io p12k3-your-api-token\n```\n\n```\nContext 'production' created. Use /context activate production to switch to it.\n```\n\n如果您喜欢逐步引导式提示，也可以使用交互式向导：\n\n```\n/context wizard\n```\n\n### 2. 激活上下文\n\n```\n/context production\n```\n\n```\n╭─ production ─────────────────────────────────────────────────╮\n│ XCSH_TENANT     acme                                         │\n│ XCSH_API_URL    https://acme.console.ves.volterra.io         │\n│ XCSH_API_TOKEN  ...oken                                      │\n│ Status          Connected (312ms)                            │\n├─ Environment ────────────────────────────────────────────────┤\n│ XCSH_NAMESPACE  default                                      │\n╰──────────────────────────────────────────────────────────────╯\n```\n\n激活后，xcsh 会将租户凭据注入您的会话中。代理现在可以进行 F5 XC API 调用，状态行会显示当前活跃的上下文。\n\n### 3. 添加更多上下文并在它们之间切换\n\n```\n/context create staging https://staging.console.ves.volterra.io p12k3-staging-token\n```\n\n按名称切换 -- 无需子命令动词：\n\n```\n/context staging\n```\n\n切回上一个上下文（`cd -` 风格）：\n\n```\n/context -\n```\n\n连续调用 `/context -` 两次会返回到起始位置。\n\n### 4. 查看已有上下文\n\n```\n/context\n```\n\n```\n  production           https://acme.console.ves.volterra.io\n* staging              https://staging.console.ves.volterra.io\n```\n\n`*` 标记表示当前活跃的上下文。\n\n## 日常命令\n\n| 命令 | 功能说明 |\n|---|---|\n| `/context` | 列出所有上下文 |\n| `/context <name>` | 切换到指定上下文 |\n| `/context -` | 切换到上一个上下文 |\n| `/context show` | 显示活跃上下文的详细信息（令牌已脱敏） |\n| `/context status` | 显示当前认证状态 |\n\n## 上下文生命周期\n\n| 命令 | 功能说明 |\n|---|---|\n| `/context create <name> <url> <token> [namespace]` | 创建上下文 |\n| `/context delete <name> --confirm` | 删除上下文（需要 `--confirm`） |\n| `/context rename <old> <new>` | 重命名上下文 |\n| `/context validate <name>` | 测试凭据而不切换上下文 |\n| `/context export [name] [--include-token]` | 导出为 JSON（默认脱敏令牌） |\n| `/context import <path-or-json> [--overwrite]` | 从文件或内联 JSON 导入 |\n| `/context wizard` | 交互式引导设置 |\n\n## 切换命名空间\n\n每个上下文都有一个默认命名空间。可以在不更改上下文的情况下切换命名空间：\n\n```\n/context namespace system\n```\n\nTab 补全会提供来自活跃租户的命名空间名称。\n\n## 上下文上的环境变量\n\n上下文可以携带额外的环境变量，这些变量会在激活时注入您的会话中。适用于不属于凭据集但与特定租户相关的配置。\n\n```\n/context set CUSTOM_HEADER=x-acme-trace\n/context set LOG_LEVEL=debug\n/context env list\n/context unset LOG_LEVEL\n```\n\n别名：`add` = `set`，`remove`/`clear` = `unset`。\n\n## Tab 补全\n\n输入 `/context ` 并按 Tab 键。下拉列表会显示：\n\n1. **上下文名称** -- 带有租户 URL 提示，便于区分不同租户\n2. **`-`** -- 在您之前切换过时出现，显示将要切换到的上下文\n3. **子命令** -- `list`、`create`、`delete` 等\n\n上下文名称排在最前面，因为切换是最常见的操作。\n\n子命令级别的补全同样有效：`/context activate <Tab>` 补全上下文名称，`/context namespace <Tab>` 补全命名空间，`/context unset <Tab>` 补全已知的环境变量键。\n\n## 命名规则\n\n上下文名称必须为 1-64 个字符：字母、数字、连字符、下划线。\n\n与子命令冲突的名称会被拒绝：\n\n```\n/context create list https://example.com tok\n```\n\n```\nError: Context name 'list' conflicts with a /context subcommand. Choose a different name.\n```\n\n完整的保留名称集：`list`、`show`、`status`、`create`、`delete`、`rename`、`namespace`、`env`、`set`、`unset`、`add`、`remove`、`clear`、`activate`、`validate`、`export`、`import`、`wizard`、`help`。比较不区分大小写。\n\n## 环境变量覆盖\n\n如果在启动 xcsh 之前在 shell 环境中设置了 `XCSH_API_URL` 和 `XCSH_API_TOKEN`，它们将优先于任何上下文。这对于 CI/CD 流水线或不需要创建持久上下文的临时会话非常有用。\n\n在此模式下运行时，`/context` 会显示来自环境变量的凭据，并带有 `(via env vars)` 标签。\n\n## 上一个上下文的行为\n\n- **会话级作用域**：上一个上下文会在重启 xcsh 时重置，不会持久化到磁盘。\n- **乒乓切换**：连续执行 `/context -` 两次会返回到起始位置。\n- **变更安全**：如果删除了上一个上下文，指针会被清除。如果重命名了上一个上下文，指针会跟随新名称。\n- **重复激活为空操作**：在已处于 `production` 时执行 `/context production` 不会重置上一个上下文的指针。\n\n## 设计惯例\n\n`/context` 的用户体验遵循以下设计：\n\n- **kubectx**：`kubectx <name>` 用于切换，`kubectx -` 切回上一个，单独的 `kubectx` 用于列表\n- **kubectl**：`kubectl config use-context` 作为显式形式\n- **Shell**：`cd -` / `OLDPWD` 用于上一目录跟踪\n",
	"zh-cn/runtime-tools/custom-tools.md": "---\ntitle: 自定义工具\ndescription: 自定义工具注册、Schema 定义及用于扩展 Agent 的执行管道。\nsidebar:\n  order: 4\n  label: 自定义工具\ni18n:\n  sourceHash: 5f4a441fc2e2\n  translator: machine\n---\n\n# 自定义工具\n\n自定义工具是模型可调用的函数，它们接入与内置工具相同的工具执行管道。\n\n自定义工具是一个 TypeScript/JavaScript 模块，导出一个工厂函数。该工厂函数接收一个宿主 API（`CustomToolAPI`）并返回一个或多个工具。\n\n## 它是什么（以及不是什么）\n\n- **自定义工具**：在一次对话轮次中可被模型调用（`execute` + TypeBox schema）。\n- **扩展**：可注册工具并拦截/修改事件的生命周期/事件框架。\n- **Hook**：外部的前置/后置命令脚本。\n- **Skill**：静态的指导/上下文包，不是可执行的工具代码。\n\n如果您需要模型直接调用代码，请使用自定义工具。\n\n## 当前代码中的集成路径\n\n目前有两种活跃的集成方式：\n\n1. **SDK 提供的自定义工具**（`options.customTools`）\n   - 通过 `CustomToolAdapter` 或扩展包装器封装为 Agent 工具。\n   - 在 SDK 引导阶段始终包含在初始活跃工具集中。\n\n2. **通过加载器 API 从文件系统发现的模块**（`discoverAndLoadCustomTools` / `loadCustomTools`）\n   - 作为库 API 暴露在 `src/extensibility/custom-tools/loader.ts` 中。\n   - 宿主代码可以调用这些 API 从配置/提供者/插件路径发现并加载工具模块。\n\n```text\nModel tool call flow\n\nLLM tool call\n   │\n   ▼\nTool registry (built-ins + custom tool adapters)\n   │\n   ▼\nCustomTool.execute(toolCallId, params, onUpdate, ctx, signal)\n   │\n   ├─ onUpdate(...)  -> streamed partial result\n   └─ return result  -> final tool content/details\n```\n\n## 发现位置（加载器 API）\n\n`discoverAndLoadCustomTools(configuredPaths, cwd, builtInToolNames)` 合并以下来源：\n\n1. 能力提供者（`toolCapability`），包括：\n   - 原生 OMP 配置（`~/.xcsh/agent/tools`、`.xcsh/tools`）\n   - Claude 配置（`~/.claude/tools`、`.claude/tools`）\n   - Codex 配置（`~/.codex/tools`、`.codex/tools`）\n   - Claude 市场插件缓存提供者\n2. 已安装的插件清单（`~/.xcsh/plugins/node_modules/*`，通过插件加载器）\n3. 传递给加载器的显式配置路径\n\n### 重要行为\n\n- 重复的解析路径会被去重。\n- 与内置工具或已加载的自定义工具存在名称冲突时，将被拒绝。\n- 某些提供者会发现 `.md` 和 `.json` 文件作为工具元数据，但可执行模块加载器会拒绝将它们作为可运行的工具。\n- 相对配置路径从 `cwd` 解析；`~` 会被展开。\n\n## 模块契约\n\n自定义工具模块必须导出一个函数（推荐使用默认导出）：\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = (pi) => ({\n name: \"repo_stats\",\n label: \"Repo Stats\",\n description: \"Counts tracked TypeScript files\",\n parameters: pi.typebox.Type.Object({\n  glob: pi.typebox.Type.Optional(pi.typebox.Type.String({ default: \"**/*.ts\" })),\n }),\n\n async execute(toolCallId, params, onUpdate, ctx, signal) {\n  onUpdate?.({\n   content: [{ type: \"text\", text: \"Scanning files...\" }],\n   details: { phase: \"scan\" },\n  });\n\n  const result = await pi.exec(\"git\", [\"ls-files\", params.glob ?? \"**/*.ts\"], { signal, cwd: pi.cwd });\n  if (result.killed) {\n   throw new Error(\"Scan was cancelled\");\n  }\n  if (result.code !== 0) {\n   throw new Error(result.stderr || \"git ls-files failed\");\n  }\n\n  const files = result.stdout.split(\"\\n\").filter(Boolean);\n  return {\n   content: [{ type: \"text\", text: `Found ${files.length} files` }],\n   details: { count: files.length, sample: files.slice(0, 10) },\n  };\n },\n\n onSession(event) {\n  if (event.reason === \"shutdown\") {\n   // cleanup resources if needed\n  }\n },\n});\n\nexport default factory;\n```\n\n工厂函数返回类型：\n\n- `CustomTool`\n- `CustomTool[]`\n- `Promise<CustomTool | CustomTool[]>`\n\n## 传递给工厂函数的 API 接口（`CustomToolAPI`）\n\n来自 `types.ts` 和 `loader.ts`：\n\n- `cwd`：宿主工作目录\n- `exec(command, args, options?)`：进程执行辅助函数\n- `ui`：UI 上下文（在无头模式下可以是空操作）\n- `hasUI`：在非交互式流程中为 `false`\n- `logger`：共享文件日志器\n- `typebox`：注入的 `@sinclair/typebox`\n- `pi`：注入的 `@f5-sales-demo/xcsh` 导出\n- `pushPendingAction(action)`：为隐藏的 `resolve` 工具注册预览操作（`docs/resolve-tool-runtime.md`）\n\n加载器以空操作 UI 上下文启动，需要宿主代码在真正的 UI 就绪时调用 `setUIContext(...)`。\n\n## 执行契约与类型\n\n`CustomTool.execute` 签名：\n\n```ts\nexecute(toolCallId, params, onUpdate, ctx, signal)\n```\n\n- `params` 通过 `Static<TParams>` 从您的 TypeBox schema 静态类型化。\n- 运行时参数验证在 Agent 循环中执行之前进行。\n- `onUpdate` 发送部分结果用于 UI 流式传输。\n- `ctx` 包含会话/模型状态和 `abort()` 辅助方法。\n- `signal` 承载取消信号。\n\n`CustomToolAdapter` 将其桥接到 Agent 工具接口，并以正确的参数顺序转发调用。\n\n## 工具如何暴露给模型\n\n- 工具被封装为 `AgentTool` 实例（`CustomToolAdapter` 或扩展包装器）。\n- 它们按名称插入到会话工具注册表中。\n- 在 SDK 引导阶段，自定义工具和扩展注册的工具被强制包含在初始活跃集中。\n- CLI `--tools` 当前仅验证内置工具名称；自定义工具的包含通过发现/注册路径和 SDK 选项处理。\n\n## 渲染钩子\n\n可选的渲染钩子：\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\nTUI 中的运行时行为：\n\n- 如果存在钩子，工具输出将在 `Box` 容器内渲染。\n- `renderResult` 接收 `{ expanded, isPartial, spinnerFrame? }`。\n- 渲染器错误会被捕获并记录；UI 回退到默认文本渲染。\n\n## 会话/状态处理\n\n可选的 `onSession(event, ctx)` 接收会话生命周期事件，包括：\n\n- `start`、`switch`、`branch`、`tree`、`shutdown`\n- `auto_compaction_start`、`auto_compaction_end`\n- `auto_retry_start`、`auto_retry_end`\n- `ttsr_triggered`、`todo_reminder`\n\n当分支/会话上下文变更时，使用 `ctx.sessionManager` 从历史记录重建状态。\n\n## 失败与取消语义\n\n### 同步/异步失败\n\n- 在 `execute` 中抛出异常（或 Promise 被拒绝）将被视为工具失败。\n- Agent 运行时将失败转换为带有 `isError: true` 和错误文本内容的工具结果消息。\n- 使用扩展包装器时，`tool_result` 处理程序可以进一步重写内容/详情，甚至覆盖错误状态。\n\n### 取消\n\n- Agent 中止通过 `AbortSignal` 传播到 `execute`。\n- 将 `signal` 转发给子进程工作（`pi.exec(..., { signal })`）以实现协作式取消。\n- `ctx.abort()` 允许工具请求中止当前 Agent 操作。\n\n### onSession 错误\n\n- `onSession` 错误会被捕获并记录为警告；它们不会导致会话崩溃。\n\n## 需要考虑的实际约束\n\n- 工具名称在活跃注册表中必须全局唯一。\n- 优先在 `details` 中使用确定性的、符合 schema 结构的输出，以便渲染器/状态重建。\n- 使用 `pi.hasUI` 保护 UI 的使用。\n- 将工具目录中的 `.md`/`.json` 文件视为元数据，而非可执行模块。\n",
	"zh-cn/runtime-tools/notebook-tool-runtime.md": "---\ntitle: Notebook 工具运行时内部机制\ndescription: Jupyter notebook 工具运行时，包含单元格执行、内核生命周期和输出渲染。\nsidebar:\n  order: 2\n  label: Notebook 工具\ni18n:\n  sourceHash: c1bafcb245e4\n  translator: machine\n---\n\n# Notebook 工具运行时内部机制\n\n本文档描述了当前 `notebook` 工具的实现及其与内核支持的 Python 运行时之间的关系。\n\n关键区别：**`notebook` 是一个 JSON notebook 编辑器，而非 notebook 执行器**。它直接编辑 `.ipynb` 单元格源码；它不会启动或与 Python 内核通信。\n\n## 实现文件\n\n- [`src/tools/notebook.ts`](../../packages/coding-agent/src/tools/notebook.ts)\n- [`src/ipy/executor.ts`](../../packages/coding-agent/src/ipy/executor.ts)\n- [`src/ipy/kernel.ts`](../../packages/coding-agent/src/ipy/kernel.ts)\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts)\n- [`src/tools/python.ts`](../../packages/coding-agent/src/tools/python.ts)\n\n## 1) 运行时边界：编辑与执行\n\n## `notebook` 工具 (`src/tools/notebook.ts`)\n\n- 支持对 `.ipynb` 文件执行 `action: edit | insert | delete` 操作。\n- 相对于会话工作目录解析路径（`resolveToCwd`）。\n- 加载 notebook JSON，验证 `cells` 数组，验证 `cell_index` 边界。\n- 在内存中应用源码编辑，并使用 `JSON.stringify(notebook, null, 1)` 将完整 notebook JSON 写回。\n- 返回文本摘要 + 结构化 `details`（`action`、`cellIndex`、`cellType`、`totalCells`、`cellSource`）。\n\n此工具中不存在内核生命周期：\n\n- 无网关获取\n- 无内核会话 ID\n- 无 `execute_request`\n- 无来自内核通道的流式数据块\n- 无富显示捕获（`image/png`、JSON 显示、状态 MIME）\n\n## 类 Notebook 的执行路径 (`src/tools/python.ts` + `src/ipy/*`)\n\n当代理需要运行单元格式的 Python 代码（顺序单元格、持久状态、富显示）时，该流程通过 **`python` 工具**执行，而非 `notebook`。\n\n内核模式、重启/取消行为、数据块流式传输和输出制品截断都在该路径中实现。\n\n## 2) Notebook 单元格处理语义（`notebook` 工具）\n\n## 源码规范化\n\n`content` 被拆分为带换行符保留的 `source: string[]`：\n\n- 每个非末尾行保留尾部 `\\n`\n- 末尾行不强制添加尾部换行符\n\n这符合 notebook JSON 约定，避免后续编辑时意外的行连接。\n\n## 操作行为\n\n- `edit`\n  - 替换 `cells[cell_index].source`\n  - 保留现有 `cell_type`\n- `insert`\n  - 在 `[0..cellCount]` 位置插入\n  - `cell_type` 默认为 `code`\n  - 代码单元格初始化 `execution_count: null` 和 `outputs: []`\n  - markdown 单元格仅初始化 `metadata` + `source`\n- `delete`\n  - 移除 `cells[cell_index]`\n  - 在 details 中返回已移除的 `source` 以供渲染器预览\n\n## 错误处理\n\n以下情况将抛出硬错误：\n\n- notebook 文件缺失\n- 无效 JSON\n- `cells` 缺失或非数组\n- 索引越界（插入和非插入操作有不同的有效范围）\n- `edit`/`insert` 操作缺少 `content`\n\n这些错误在上游成为 `Error:` 工具响应；渲染器使用 notebook 路径 + 格式化的错误文本。\n\n## 3) 内核会话语义（实际存在的位置）\n\n内核语义在 `executePython` / `PythonKernel` 中实现，适用于 `python` 工具。\n\n## 模式\n\n`PythonKernelMode`：\n\n- `session`（默认）\n  - 内核缓存在 `kernelSessions` 映射中\n  - 最多 4 个会话；溢出时驱逐最旧的\n  - 每 30 秒进行空闲/死亡清理，5 分钟后超时\n  - 每会话队列序列化执行（`session.queue`）\n- `per-call`\n  - 为请求创建内核\n  - 执行\n  - 始终在 `finally` 中关闭内核\n\n## 重置行为\n\n`python` 工具仅在多单元格调用的第一个单元格传递 `reset`；后续单元格始终以 `reset: false` 运行。\n\n## 内核死亡 / 重启 / 重试\n\n在会话模式（`withKernelSession`）中：\n\n- 通过心跳检测内核死亡（每 5 秒进行 `kernel.isAlive()` 检查）或执行失败。\n- 运行前检测到死亡状态将触发 `restartKernelSession`。\n- 执行时崩溃路径重试一次：重启内核，重新运行处理程序。\n- 同一会话中 `restartCount > 1` 将抛出 `Python kernel restarted too many times in this session`。\n\n启动重试行为：\n\n- 共享网关内核创建在遇到 HTTP 5xx 的 `SharedGatewayCreateError` 时重试一次。\n\n资源耗尽恢复：\n\n- 检测 `EMFILE`/`ENFILE`/\"Too many open files\" 类型的故障\n- 清除已跟踪的会话\n- 调用 `shutdownSharedGateway()`\n- 重试内核会话创建一次\n\n## 4) 环境/会话变量注入\n\n内核启动从执行器接收可选的环境变量映射：\n\n- `PI_SESSION_FILE`（会话状态文件路径）\n- `ARTIFACTS`（制品目录）\n\n`PythonKernel.#initializeKernelEnvironment(...)` 随后在内核内部运行初始化脚本以：\n\n- `os.chdir(cwd)`\n- 将环境变量条目注入 `os.environ`\n- 如果缺失，将 cwd 添加到 `sys.path` 开头\n\n含义：\n\n- 读取会话或制品上下文的预置辅助函数依赖于 Python 进程状态中的这些环境变量。\n\n## 5) 流式/数据块和显示处理（内核支持路径）\n\n内核客户端按执行处理 Jupyter 协议消息：\n\n- `stream` -> 文本块传递至 `onChunk`\n- `execute_result` / `display_data` ->\n  - 按 MIME 优先级选择显示文本：`text/markdown` > `text/plain` > 转换后的 `text/html`\n  - 结构化输出单独捕获：\n    - `application/json` -> `{ type: \"json\" }`\n    - `image/png` -> `{ type: \"image\" }`\n    - `application/x-xcsh-status` -> `{ type: \"status\" }`（不产生文本输出）\n- `error` -> 回溯文本推送至数据块流 + 结构化错误元数据\n- `input_request` -> 发出 stdin 警告文本，发送空 `input_reply`，标记已请求 stdin\n- 完成等待 `execute_reply` 和内核 `status=idle` 两者\n\n取消/超时：\n\n- 中止信号触发 `interrupt()`（REST `/interrupt` + 控制通道 `interrupt_request`）\n- 结果标记 `cancelled=true`\n- 超时路径在输出中添加 `Command timed out after <n> seconds` 注释\n\n## 6) 截断和制品行为\n\n`src/session/streaming-output.ts` 中的 `OutputSink` 被内核执行路径（`executeWithKernel`）使用：\n\n- 对每个数据块进行清理（`sanitizeText`）\n- 跟踪总行数/输出行数和字节数\n- 可选的制品溢出文件（`artifactPath`、`artifactId`）\n- 当内存缓冲区超过阈值（除非覆盖，否则为 `DEFAULT_MAX_BYTES`）时：\n  - 标记为已截断\n  - 在内存中保留尾部字节（UTF-8 安全边界）\n  - 可将完整流溢出到制品接收器\n\n`dump()` 返回：\n\n- 可见输出文本（可能经过尾部截断）\n- 截断标志 + 计数\n- 制品 ID（用于 `artifact://<id>` 引用）\n\n`python` 工具将此元数据转换为结果截断通知和 TUI 警告。\n\n`notebook` 工具**不使用** `OutputSink`；它没有流/制品截断管道，因为它不执行代码。\n\n## 7) 渲染器假设和格式化\n\n## Notebook 渲染器 (`notebookToolRenderer`)\n\n- 调用视图：包含操作 + notebook 路径 + 单元格/类型元数据的状态行\n- 结果视图：\n  - 从 `details` 派生的成功摘要\n  - `cellSource` 通过 `renderCodeCell` 渲染\n  - markdown 单元格设置语言提示 `markdown`；其他单元格无显式语言覆盖\n  - 折叠代码预览限制为 `PREVIEW_LIMITS.COLLAPSED_LINES * 2`\n  - 通过共享渲染选项支持展开模式\n  - 使用以宽度 + 展开状态为键的渲染缓存\n\n错误渲染假设：\n\n- 如果第一个文本内容以 `Error:` 开头，渲染器将其格式化为 notebook 错误块。\n\n## Python 渲染器（用于实际执行输出）\n\n内核支持的执行渲染期望：\n\n- 每个单元格的状态转换（`pending/running/complete/error`）\n- 可选的结构化状态事件部分\n- 可选的 JSON 输出树\n- 截断警告 + 可选的 `artifact://<id>` 指针\n\n此渲染器行为与 `notebook` JSON 编辑结果无关，但两者都复用共享的 TUI 原语。\n\n## 8) 与普通 Python 工具行为的差异\n\n如果\"普通 Python 工具\"指的是 `python` 执行路径：\n\n- `python` 在内核中执行代码，按模式持久化状态，流式传输数据块，捕获富显示，处理中断/超时，并支持输出截断/制品。\n- `notebook` 仅执行确定性的 notebook JSON 变更；无执行、无内核状态、无数据块流、无显示输出、无制品管道。\n\n如果工作流需要两者兼备：\n\n1. 使用 `notebook` 编辑 notebook 源码\n2. 通过 `python`（手动传递代码）执行代码单元格，而非通过 `notebook`\n\n当前实现不提供单一工具来同时变更 `.ipynb` 并通过内核上下文执行 notebook 单元格。\n",
	"zh-cn/runtime-tools/resolve-tool-runtime.md": "---\ntitle: Resolve 工具运行时内部机制\ndescription: >-\n  Resolve tool runtime for file path resolution, content fetching, and URL-based\n  resource access.\nsidebar:\n  order: 3\n  label: Resolve 工具\ni18n:\n  sourceHash: 06e8be8c5a3c\n  translator: machine\n---\n\n# Resolve 工具运行时内部机制\n\n本文档介绍了 coding-agent 中预览/应用工作流的建模方式，以及自定义工具如何通过 `pushPendingAction` 参与该流程。\n\n## 范围和关键文件\n\n- [`src/tools/resolve.ts`](../../packages/coding-agent/src/tools/resolve.ts)\n- [`src/tools/pending-action.ts`](../../packages/coding-agent/src/tools/pending-action.ts)\n- [`src/tools/ast-edit.ts`](../../packages/coding-agent/src/tools/ast-edit.ts)\n- [`src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts)\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n\n## `resolve` 的作用\n\n`resolve` 是一个隐藏工具，用于最终确认待处理的预览操作。\n\n- `action: \"apply\"` 在待处理操作上执行 `apply(reason)` 并持久化变更。\n- `action: \"discard\"` 如果提供了 `reject(reason)` 则调用该方法；否则使用默认的 \"Discarded\" 消息丢弃该操作。\n\n如果不存在待处理操作，`resolve` 将失败并返回：\n\n- `No pending action to resolve. Nothing to apply or discard.`\n\n## 待处理操作是一个栈（后进先出）\n\n待处理操作以推入/弹出栈的形式存储在 `PendingActionStore` 中：\n\n- `push(action)` 将新的待处理操作添加到栈顶。\n- `peek()` 查看当前栈顶操作。\n- `pop()` 移除并返回栈顶操作。\n- `hasPending` 指示栈是否非空。\n\n`resolve` 总是先消费**最顶部**的待处理操作（`pop()`），因此多个产生预览的工具按注册的逆序解析。\n\n## 内置生产者示例（`ast_edit`）\n\n`ast_edit` 首先预览结构替换。当预览包含替换内容且尚未应用时，它会推入一个待处理操作，其中包含：\n\n- label（人类可读的摘要）\n- `sourceToolName`（`ast_edit`）\n- `apply(reason: string)` 回调函数，以 `dryRun: false` 重新运行 AST 编辑\n\n`resolve(action=\"apply\", reason=\"...\")` 将 `reason` 传递给此回调函数。\n\n## 自定义工具：`pushPendingAction`\n\n自定义工具可以通过 `CustomToolAPI.pushPendingAction(...)` 注册与 resolve 兼容的待处理操作。\n\n`CustomToolPendingAction`：\n\n- `label: string`（必需）\n- `apply(reason: string): Promise<AgentToolResult<unknown>>`（必需）— 应用时调用；`reason` 是传递给 `resolve` 的字符串\n- `reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>`（可选）— 丢弃时调用；如果提供了返回值，则替换默认的 \"Discarded\" 消息\n- `details?: unknown`（可选）\n- `sourceToolName?: string`（可选，默认为 `\"custom_tool\"`）\n\n### 最小使用示例\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = pi => ({\n name: \"batch_rename_preview\",\n label: \"Batch Rename Preview\",\n description: \"Previews renames and defers commit to resolve\",\n parameters: pi.typebox.Type.Object({\n  files: pi.typebox.Type.Array(pi.typebox.Type.String()),\n }),\n\n async execute(_toolCallId, params) {\n  const previewSummary = `Prepared rename plan for ${params.files.length} files`;\n\n  pi.pushPendingAction({\n   label: `Batch rename: ${params.files.length} files`,\n   sourceToolName: \"batch_rename_preview\",\n   apply: async (reason) => {\n    // apply writes here\n    return {\n     content: [{ type: \"text\", text: `Applied batch rename. Reason: ${reason}` }],\n    };\n   },\n   reject: async (reason) => {\n    // optional: cleanup or notify on discard\n    return {\n     content: [{ type: \"text\", text: `Discarded batch rename. Reason: ${reason}` }],\n    };\n   },\n  });\n\n  return {\n   content: [{ type: \"text\", text: `${previewSummary}. Call resolve to apply or discard.` }],\n  };\n },\n});\n\nexport default factory;\n```\n\n## 运行时可用性与故障\n\n`pushPendingAction` 由自定义工具加载器使用活动会话的 `PendingActionStore` 进行连接。\n\n如果运行时没有待处理操作存储，`pushPendingAction` 将抛出异常：\n\n- `Pending action store unavailable for custom tools in this runtime.`\n\n## 工具选择行为\n\n当 `PendingActionStore.hasPending` 为 true 时，代理运行时会偏向选择 `resolve` 工具，以确保在正常工具流程继续之前，待处理的预览被显式地最终确认。\n\n## 开发者指南\n\n- 仅对需要支持显式应用/丢弃的破坏性或高影响操作使用待处理操作。\n- 保持 `label` 简洁且具体；它会在 resolve 渲染器输出中显示。\n- 确保 `apply(reason)` 具有确定性且足够幂等以支持一次性执行；`reason` 仅用于信息说明，不应改变行为。\n- 当丢弃操作需要清理（临时状态、锁、通知）时实现 `reject(reason)`；对于默认消息即可满足需求的无状态预览，可以省略它。\n- 如果您的工具可以暂存多个预览，请记住后进先出语义：最后推入的操作最先解析。\n",
	"zh-cn/runtime-tools/slash-command-internals.md": "---\ntitle: 斜杠命令内部机制\ndescription: 斜杠命令系统内部机制，包括注册、参数解析和执行调度。\nsidebar:\n  order: 5\n  label: 斜杠命令\ni18n:\n  sourceHash: 2cbd44a3de87\n  translator: machine\n---\n\n# 斜杠命令内部机制\n\n本文档描述了 `coding-agent` 中斜杠命令的发现、去重、在交互模式中的展示以及在提示词处理时的展开机制。\n\n## 实现文件\n\n- [`src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n- [`src/capability/slash-command.ts`](../../packages/coding-agent/src/capability/slash-command.ts)\n- [`src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`src/discovery/claude.ts`](../../packages/coding-agent/src/discovery/claude.ts)\n- [`src/discovery/codex.ts`](../../packages/coding-agent/src/discovery/codex.ts)\n- [`src/discovery/claude-plugins.ts`](../../packages/coding-agent/src/discovery/claude-plugins.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n\n## 1) 发现模型\n\n斜杠命令是一种能力（`id: \"slash-commands\"`），以命令名称作为键（`key: cmd => cmd.name`）。\n\n能力注册表会加载所有已注册的提供者，按提供者优先级降序排列，并以**先到先得**的语义进行键去重。\n\n### 提供者优先级\n\n当前斜杠命令提供者及其优先级：\n\n1. `native`（OMP）— 优先级 `100`\n2. `claude` — 优先级 `80`\n3. `claude-plugins` — 优先级 `70`\n4. `codex` — 优先级 `70`\n\n平级行为：优先级相同的提供者保持注册顺序。当前的导入顺序是 `claude-plugins` 在 `codex` 之前注册，因此在名称冲突时插件命令优先于 codex 命令。\n\n### 名称冲突行为\n\n对于 `slash-commands`，冲突严格通过能力去重来解决：\n\n- 最高优先级的项保留在 `result.items` 中\n- 较低优先级的重复项仅保留在 `result.all` 中，并被标记为 `_shadowed = true`\n\n这适用于跨提供者的情况，也适用于同一提供者返回重复名称的情况。\n\n### 文件扫描行为\n\n提供者主要使用 `loadFilesFromDir(...)`，目前：\n\n- 默认使用非递归匹配（`*.md`）\n- 使用原生 glob，配置 `gitignore: true`、`hidden: false`\n- 读取每个匹配的文件并将其转换为 `SlashCommand`\n\n因此不会加载隐藏文件/目录，被忽略的路径也会被跳过。\n\n## 2) 各提供者的源路径及本地优先级\n\n## `native` 提供者（`builtin.ts`）\n\n搜索根目录来自 `.xcsh` 目录：\n\n- 项目级：`<cwd>/.xcsh/commands/*.md`\n- 用户级：`~/.xcsh/agent/commands/*.md`\n\n`getConfigDirs()` 先返回项目目录，再返回用户目录，因此在名称冲突时**项目级原生命令优先于用户级原生命令**。\n\n## `claude` 提供者（`claude.ts`）\n\n加载路径：\n\n- 用户级：`~/.claude/commands/*.md`\n- 项目级：`<cwd>/.claude/commands/*.md`\n\n该提供者先推入用户级项，再推入项目级项，因此在该提供者内同名冲突时**用户级 Claude 命令优先于项目级 Claude 命令**。\n\n## `codex` 提供者（`codex.ts`）\n\n加载路径：\n\n- 用户级：`~/.codex/commands/*.md`\n- 项目级：`<cwd>/.codex/commands/*.md`\n\n两侧加载后以用户优先的顺序展平，因此在冲突时**用户级 Codex 命令优先于项目级 Codex 命令**。\n\nCodex 命令内容通过前言剥离（`parseFrontmatter`）进行解析，命令名称可由前言中的 `name` 覆盖；否则使用文件名。\n\n## `claude-plugins` 提供者（`claude-plugins.ts`）\n\n从 `~/.claude/plugins/installed_plugins.json` 加载插件命令根目录，然后扫描 `<pluginRoot>/commands/*.md`。\n\n排序遵循注册表迭代顺序和该 JSON 数据中每个插件的条目顺序。没有额外的排序步骤。\n\n## 3) 运行时 `FileSlashCommand` 的具体化\n\n`src/extensibility/slash-commands.ts` 中的 `loadSlashCommands()` 将能力项转换为提示词处理时使用的 `FileSlashCommand` 对象。\n\n对于每个命令：\n\n1. 解析前言/正文（`parseFrontmatter`）\n2. 描述来源：\n   - 如果存在 `frontmatter.description` 则使用\n   - 否则使用第一个非空正文行（修剪后，最多 60 个字符并加 `...`）\n3. 保留解析后的正文作为可执行模板内容\n4. 计算显示来源字符串，如 `via Claude Code Project`\n\n前言解析的严重级别取决于来源：\n\n- `native` 级别 -> 解析错误为 `fatal`\n- `user`/`project` 级别 -> 解析错误为 `warn`，并使用回退解析\n\n### 内置回退命令\n\n在文件系统/提供者命令之后，如果名称尚未存在，则追加嵌入式命令模板（`EMBEDDED_COMMAND_TEMPLATES`）。\n\n当前嵌入集来自 `src/task/commands.ts`，用作回退（`source: \"bundled\"`）。\n\n## 4) 交互模式：命令列表的来源\n\n交互模式组合多个命令源，用于自动补全和命令路由。\n\n构造时，它从以下来源构建待处理命令列表：\n\n- 内置命令（`BUILTIN_SLASH_COMMANDS`，包含对选定命令的参数补全和内联提示）\n- 扩展注册的斜杠命令（`extensionRunner.getRegisteredCommands(...)`）\n- TypeScript 自定义命令（`session.customCommands`），映射为斜杠命令标签\n- 可选的技能命令（`/skill:<name>`），当 `skills.enableSkillCommands` 启用时\n\n然后 `init()` 调用 `refreshSlashCommandState(...)` 来加载基于文件的命令，并安装一个包含以下内容的 `CombinedAutocompleteProvider`：\n\n- 上述待处理命令\n- 已发现的基于文件的命令\n\n`refreshSlashCommandState(...)` 还会更新 `session.setSlashCommands(...)`，以便提示词展开使用相同的已发现文件命令集。\n\n### 刷新生命周期\n\n斜杠命令状态在以下时机刷新：\n\n- 交互模式初始化期间\n- `/move` 更改工作目录后（`handleMoveCommand` 调用 `resetCapabilities()` 然后 `refreshSlashCommandState(newCwd)`）\n\n命令目录没有持续的文件监视器。\n\n### 其他展示\n\n扩展仪表板也会加载 `slash-commands` 能力并显示活动/被遮蔽的命令条目，包括标记为 `_shadowed` 的重复项。\n\n## 5) 提示词管道位置\n\n`AgentSession.prompt(...)` 斜杠处理顺序（当 `expandPromptTemplates !== false` 时）：\n\n1. **扩展命令**（`#tryExecuteExtensionCommand`）  \n   如果 `/name` 匹配扩展注册的命令，处理器立即执行，prompt 返回。\n2. **TypeScript 自定义命令**（`#tryExecuteCustomCommand`）  \n   仅限边界：如果匹配，则执行并可能返回：\n   - `string` -> 用该字符串替换提示词文本\n   - `void/undefined` -> 视为已处理；不发送 LLM 提示词\n3. **基于文件的斜杠命令**（`expandSlashCommand`）  \n   如果文本仍以 `/` 开头，尝试 markdown 命令展开。\n4. **提示词模板**（`expandPromptTemplate`）  \n   在斜杠/自定义处理之后应用。\n5. **发送**\n   - 空闲时：提示词立即发送给代理\n   - 流式传输时：提示词根据 `streamingBehavior` 作为转向/后续消息排队\n\n这就是为什么斜杠命令展开在提示词模板展开之前执行，以及为什么自定义命令可以在文件命令匹配之前移除前导斜杠。\n\n## 6) 基于文件的斜杠命令的展开语义\n\n`expandSlashCommand(text, fileCommands)` 的行为：\n\n- 仅在文本以 `/` 开头时运行\n- 从 `/` 后的第一个令牌解析命令名称\n- 通过 `parseCommandArgs` 从剩余文本解析参数\n- 在已加载的 `fileCommands` 中查找精确名称匹配\n- 如果匹配，则应用：\n  - 位置替换：`$1`、`$2`、...\n  - 聚合替换：`$ARGUMENTS` 和 `$@`\n  - 然后通过 `prompt.render` 使用 `{ args, ARGUMENTS, arguments }` 进行模板渲染\n- 如果未匹配，返回原始文本不变\n\n### `parseCommandArgs` 注意事项\n\n解析器是简单的引号感知分割：\n\n- 支持 `'单引号'` 和 `\"双引号\"` 引用以保留空格\n- 去除引号分隔符\n- 不实现反斜杠转义规则\n- 未匹配的引号不会报错；解析器会消费到末尾\n\n## 7) 未知 `/...` 行为\n\n未知的斜杠输入**不会被**核心斜杠逻辑拒绝。\n\n如果命令未被扩展/自定义/文件层处理，`expandSlashCommand` 返回原始文本，字面量 `/...` 提示词继续通过正常的提示词模板展开和 LLM 发送流程。\n\n交互模式在 `InputController` 中单独硬处理许多内置命令（例如 `/settings`、`/model`、`/mcp`、`/move`、`/exit`）。这些在 `session.prompt(...)` 之前被消费，因此在该路径中永远不会到达文件命令展开。\n\n## 8) 流式传输时与空闲时的差异\n\n## 空闲路径\n\n- `session.prompt(\"/x ...\")` 运行命令管道，要么立即执行命令，要么直接发送展开后的文本。\n\n## 流式传输路径（`session.isStreaming === true`）\n\n- `prompt(...)` 仍然先运行扩展/自定义/文件/模板转换\n- 然后需要 `streamingBehavior`：\n  - `\"steer\"` -> 排队中断消息（`agent.steer`）\n  - `\"followUp\"` -> 排队轮次后消息（`agent.followUp`）\n- 如果省略 `streamingBehavior`，prompt 会抛出错误\n\n### 重要的命令特定流式传输行为\n\n- 扩展命令即使在流式传输期间也会立即执行（不作为文本排队）。\n- `steer(...)`/`followUp(...)` 辅助方法会拒绝扩展命令（`#throwIfExtensionCommand`），以避免将命令文本排入需要同步运行的处理器队列。\n- 压缩队列重放使用 `isKnownSlashCommand(...)` 来决定排队条目应通过 `session.prompt(...)`（对于已知斜杠命令）还是原始的 steer/follow-up 方法来重放。\n\n## 9) 错误处理和故障面\n\n- 提供者加载失败是隔离的；注册表收集警告并继续处理其他提供者。\n- 无效的斜杠命令项（缺少名称/路径/内容或无效级别）会被能力验证丢弃。\n- 前言解析失败：\n  - 原生命令：致命解析错误向上冒泡\n  - 非原生命令：警告 + 回退键值解析\n- 扩展/自定义命令处理器异常会被捕获并通过扩展错误通道报告（或对于没有扩展运行器的自定义命令使用日志记录器回退），并视为已处理（不会发生意外的回退执行）。\n",
	"zh-cn/runtime-tools/task-agent-discovery.md": "---\ntitle: 任务代理发现与选择\ndescription: 任务代理发现与选择逻辑，用于将工作路由到专门的子代理类型。\nsidebar:\n  order: 6\n  label: 任务代理发现\ni18n:\n  sourceHash: 8cf42457c672\n  translator: machine\n---\n\n# 任务代理发现与选择\n\n本文档描述了任务子系统如何发现代理定义、合并多个来源，以及在执行时解析请求的代理。\n\n内容涵盖当前已实现的运行时行为，包括优先级、无效定义处理，以及可能导致代理实际不可用的生成/深度约束。\n\n## 实现文件\n\n- [`src/task/discovery.ts`](../../packages/coding-agent/src/task/discovery.ts)\n- [`src/task/agents.ts`](../../packages/coding-agent/src/task/agents.ts)\n- [`src/task/types.ts`](../../packages/coding-agent/src/task/types.ts)\n- [`src/task/index.ts`](../../packages/coding-agent/src/task/index.ts)\n- [`src/task/commands.ts`](../../packages/coding-agent/src/task/commands.ts)\n- [`src/prompts/agents/task.md`](../../packages/coding-agent/src/prompts/agents/task.md)\n- [`src/prompts/tools/task.md`](../../packages/coding-agent/src/prompts/tools/task.md)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/config.ts`](../../packages/coding-agent/src/config.ts)\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts)\n\n---\n\n## 代理定义结构\n\n任务代理标准化为 `AgentDefinition`（`src/task/types.ts`）：\n\n- `name`、`description`、`systemPrompt`（有效加载的代理必需）\n- 可选的 `tools`、`spawns`、`model`、`thinkingLevel`、`output`\n- `source`：`\"bundled\" | \"user\" | \"project\"`\n- 可选的 `filePath`\n\n解析来自 `parseAgentFields()`（`src/discovery/helpers.ts`）的 frontmatter：\n\n- 缺少 `name` 或 `description` => 无效（`null`），调用方视为解析失败\n- `tools` 接受 CSV 或数组；如果提供，`submit_result` 会自动添加\n- `spawns` 接受 `*`、CSV 或数组\n- 向后兼容行为：如果 `spawns` 缺失但 `tools` 包含 `task`，`spawns` 变为 `*`\n- `output` 作为不透明的 schema 数据直接传递\n\n## 内置代理\n\n内置代理在构建时嵌入（`src/task/agents.ts`），使用文本导入。\n\n`EMBEDDED_AGENT_DEFS` 定义了：\n\n- `explore`、`plan`、`designer`、`reviewer` 来自提示词文件\n- `task` 和 `quick_task` 来自共享的 `task.md` 正文加注入的 frontmatter\n\n加载路径：\n\n1. `loadBundledAgents()` 使用 `parseAgent(..., \"bundled\", \"fatal\")` 解析嵌入的 markdown\n2. 结果缓存在内存中（`bundledAgentsCache`）\n3. `clearBundledAgentsCache()` 仅用于测试的缓存重置\n\n由于内置解析使用 `level: \"fatal\"`，格式错误的内置 frontmatter 会抛出异常，可能导致整个发现过程失败。\n\n## 文件系统和插件发现\n\n`discoverAgents(cwd, home)`（`src/task/discovery.ts`）在追加内置定义之前，从多个位置合并代理。\n\n### 发现输入\n\n1. 来自 `getConfigDirs(\"agents\", { project: false })` 的用户配置代理目录\n2. 来自 `findAllNearestProjectConfigDirs(\"agents\", cwd)` 的最近项目代理目录\n3. Claude 插件根目录（`listClaudePluginRoots(home)`）及其 `agents/` 子目录\n4. 内置代理（`loadBundledAgents()`）\n\n### 实际来源顺序\n\n来源族的顺序来自 `getConfigDirs(\"\", { project: false })`，该函数派生自 `src/config.ts` 中的 `priorityList`：\n\n1. `.xcsh`\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\n对于每个来源族，发现顺序为：\n\n1. 该来源的最近项目目录（如果找到）\n2. 该来源的用户目录\n\n在所有来源族目录之后，追加插件 `agents/` 目录（项目级插件优先，然后是用户级插件）。\n\n内置代理最后追加。\n\n### 重要注意事项：过时注释与当前代码\n\n`discovery.ts` 的头部注释仍然提到 `.pi`，没有提到 `.codex`/`.gemini`。实际运行时顺序由 `src/config.ts` 驱动，当前使用 `.xcsh`、`.claude`、`.codex`、`.gemini`。\n\n## 合并与冲突规则\n\n发现使用按精确 `agent.name` 的先到先得去重：\n\n- 使用 `Set<string>` 跟踪已出现的名称。\n- 加载的代理按目录顺序展平，仅当名称未出现时保留。\n- 内置代理也通过同一集合过滤，仅在名称仍未出现时添加。\n\n影响：\n\n- 对于同一来源族，项目级覆盖用户级。\n- 优先级更高的来源族覆盖更低的（`.xcsh` 在 `.claude` 之前，等等）。\n- 非内置代理覆盖同名的内置代理。\n- 名称匹配区分大小写（`Task` 和 `task` 是不同的）。\n- 在同一目录内，markdown 文件在去重前按字典序的文件名顺序读取。\n\n## 无效/缺失代理文件行为\n\n每个目录（`loadAgentsFromDir`）：\n\n- 不可读/缺失目录：视为空（`readdir(...).catch(() => [])`）\n- 文件读取或解析失败：记录警告，跳过该文件\n- 解析路径使用 `parseAgent(..., level: \"warn\")`\n\nFrontmatter 失败行为来自 `parseFrontmatter`：\n\n- `warn` 级别的解析错误记录警告\n- 解析器回退到简单的 `key: value` 逐行解析器\n- 如果必需字段仍然缺失，`parseAgentFields` 失败，然后抛出 `AgentParsingError` 并被调用方捕获（跳过该文件）\n\n最终效果：一个损坏的自定义代理文件不会中止其他文件的发现。\n\n## 代理查找与选择\n\n查找是精确名称的线性搜索：\n\n- `getAgent(agents, name)` => `agents.find(a => a.name === name)`\n\n在任务执行中（`TaskTool.execute`）：\n\n1. 在调用时重新发现代理（`discoverAgents(this.session.cwd)`）\n2. 请求的 `params.agent` 通过 `getAgent` 解析\n3. 未找到的代理返回即时工具响应：\n   - `Unknown agent \"...\". Available: ...`\n   - 不运行子进程\n\n### 描述与执行时发现\n\n`TaskTool.create()` 在初始化时从发现结果构建工具描述（`buildDescription`）。\n\n`execute()` 会再次重新发现代理。因此如果代理文件在会话期间发生变化，运行时集合可能与早期工具描述中列出的不同。\n\n## 结构化输出护栏与 schema 优先级\n\n`TaskTool.execute` 中的运行时输出 schema 优先级：\n\n1. 代理 frontmatter 中的 `output`\n2. 任务调用的 `params.schema`\n3. 父会话的 `outputSchema`\n\n（`effectiveOutputSchema = effectiveAgent.output ?? outputSchema ?? this.session.outputSchema`）\n\n`src/prompts/tools/task.md` 中的提示时护栏文本警告结构化输出代理（`explore`、`reviewer`）的不匹配行为：散文中的输出格式指令可能与内置 schema 冲突，产生 `null` 输出。\n\n这是指导性说明，而非 `discoverAgents` 中的硬性运行时验证逻辑。\n\n## 命令发现交互\n\n`src/task/commands.ts` 是用于工作流命令（非代理定义）的并行基础设施，但遵循相同的总体模式：\n\n- 首先从能力提供者发现\n- 按名称先到先得去重\n- 如果仍未出现则追加内置命令\n- 通过 `getCommand` 进行精确名称查找\n\n在 `src/task/index.ts` 中，命令辅助函数与代理发现辅助函数一起重新导出。代理发现本身在运行时不依赖命令发现。\n\n## 发现之外的可用性约束\n\n代理可以被发现但仍然无法运行，因为存在执行护栏。\n\n### 父级生成策略\n\n`TaskTool.execute` 检查 `session.getSessionSpawns()`：\n\n- `\"*\"` => 允许任何\n- `\"\"` => 拒绝所有\n- CSV 列表 => 仅允许列出的名称\n\n如果被拒绝：即时返回 `Cannot spawn '...'. Allowed: ...` 响应。\n\n### 阻止自递归的环境变量守卫\n\n`PI_BLOCKED_AGENT` 在工具构造时读取。如果请求匹配，执行将被拒绝并返回递归防止消息。\n\n### 递归深度控制（子会话中的 task 工具可用性）\n\n在 `runSubprocess`（`src/task/executor.ts`）中：\n\n- 深度从 `taskDepth` 计算\n- `task.maxRecursionDepth` 控制截止点\n- 当达到最大深度时：\n  - `task` 工具从子工具列表中移除\n  - 子级 `spawns` 环境变量设为空\n\n因此更深的层级即使代理定义包含 `spawns` 也无法生成进一步的任务。\n\n## 计划模式注意事项（当前实现）\n\n`TaskTool.execute` 为计划模式计算 `effectiveAgent`（前置计划模式提示词、强制只读工具子集、清除 spawns），但 `runSubprocess` 的调用使用的是 `agent` 而非 `effectiveAgent`。\n\n当前效果：\n\n- 模型覆盖/思考级别/输出 schema 派生自 `effectiveAgent`\n- 来自 `effectiveAgent` 的系统提示词和工具/生成限制在此调用路径中未被传递\n\n这是在阅读计划模式行为预期时值得了解的实现注意事项。\n",
	"zh-cn/sessions/compaction.md": "---\ntitle: 压缩与分支摘要\ndescription: 长会话的上下文窗口压缩与分支摘要生成。\nsidebar:\n  order: 5\n  label: 压缩\ni18n:\n  sourceHash: dae425a900d8\n  translator: machine\n---\n\n# 压缩与分支摘要\n\n压缩与分支摘要是两种机制，用于在长会话中保持可用性，同时不丢失之前的工作上下文。\n\n- **压缩**：将旧历史记录改写为当前分支上的摘要。\n- **分支摘要**：在 `/tree` 导航期间捕获被放弃的分支上下文。\n\n两者均作为会话条目持久化，并在重建 LLM 输入时转换回用户上下文消息。\n\n## 关键实现文件\n\n- `src/session/compaction/compaction.ts`\n- `src/session/compaction/branch-summarization.ts`\n- `src/session/compaction/pruning.ts`\n- `src/session/compaction/utils.ts`\n- `src/session/session-manager.ts`\n- `src/session/agent-session.ts`\n- `src/session/messages.ts`\n- `src/extensibility/hooks/types.ts`\n- `src/config/settings-schema.ts`\n\n## 会话条目模型\n\n压缩和分支摘要是一等会话条目，而非普通的助手/用户消息。\n\n- `CompactionEntry`\n  - `type: \"compaction\"`\n  - `summary`，可选 `shortSummary`\n  - `firstKeptEntryId`（压缩边界）\n  - `tokensBefore`\n  - 可选 `details`、`preserveData`、`fromExtension`\n- `BranchSummaryEntry`\n  - `type: \"branch_summary\"`\n  - `fromId`、`summary`\n  - 可选 `details`、`fromExtension`\n\n当上下文被重建（`buildSessionContext`）时：\n\n1. 活动路径上最新的压缩条目被转换为一条 `compactionSummary` 消息。\n2. 从 `firstKeptEntryId` 到压缩点的保留条目被重新包含。\n3. 路径上之后的条目被追加。\n4. `branch_summary` 条目被转换为 `branchSummary` 消息。\n5. `custom_message` 条目被转换为 `custom` 消息。\n\n这些自定义角色随后在 `convertToLlm()` 中使用静态模板转换为面向 LLM 的用户消息：\n\n- `prompts/compaction/compaction-summary-context.md`\n- `prompts/compaction/branch-summary-context.md`\n\n## 压缩流程\n\n### 触发方式\n\n压缩可通过三种方式运行：\n\n1. **手动**：`/compact [instructions]` 调用 `AgentSession.compact(...)`。\n2. **自动溢出恢复**：助手错误匹配到上下文溢出后触发。\n3. **自动阈值压缩**：成功完成一轮对话后，当上下文超过阈值时触发。\n\n### 压缩结构（可视化）\n\n```text\n压缩前：\n\n  entry:  0     1     2     3      4     5     6      7      8     9\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┘\n                └────────┬───────┘ └──────────────┬──────────────┘\n               messagesToSummarize            kept messages\n                                   ↑\n                          firstKeptEntryId (entry 4)\n\n压缩后（追加新条目）：\n\n  entry:  0     1     2     3      4     5     6      7      8     9      10\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┬─────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │ cmp │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┴─────┘\n               └──────────┬──────┘ └──────────────────────┬───────────────────┘\n                 不发送给 LLM                          发送给 LLM\n                                                         ↑\n                                              从 firstKeptEntryId 开始\n\nLLM 所见内容：\n\n  ┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐\n  │ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │\n  └────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘\n       ↑         ↑      └─────────────────┬────────────────┘\n    提示词   来自 cmp         从 firstKeptEntryId 开始的消息\n```\n\n### 溢出重试 vs 阈值压缩\n\n两种自动路径在设计上有所不同：\n\n- **溢出重试压缩**\n  - 触发条件：当前模型的助手错误被检测为上下文溢出。\n  - 失败的助手错误消息在重试前从活动代理状态中移除。\n  - 自动压缩以 `reason: \"overflow\"` 和 `willRetry: true` 运行。\n  - 成功后，代理在压缩后自动继续（`agent.continue()`）。\n\n- **阈值压缩**\n  - 触发条件：`contextTokens > contextWindow - compaction.reserveTokens`。\n  - 以 `reason: \"threshold\"` 和 `willRetry: false` 运行。\n  - 成功后，若 `compaction.autoContinue !== false`，则注入一条合成提示：\n    - `\"Continue if you have next steps.\"`\n\n### 压缩前剪枝\n\n在压缩检查之前，可能会运行工具结果剪枝（`pruneToolOutputs`）。\n\n默认剪枝策略：\n\n- 保护最新的 `40_000` 个工具输出 token。\n- 要求至少节省 `20_000` 个 token 的估算总量。\n- 永不剪枝来自 `skill` 或 `read` 的工具结果。\n\n被剪枝的工具结果替换为：\n\n- `[Output truncated - N tokens]`\n\n若剪枝更改了条目，则在压缩决策之前重写会话存储并刷新代理消息状态。\n\n### 边界与截断点逻辑\n\n`prepareCompaction()` 仅考虑自上次压缩条目（若存在）以来的条目。\n\n1. 查找上一个压缩索引。\n2. 计算 `boundaryStart = prevCompactionIndex + 1`。\n3. 在可用时，使用实测使用率比例调整 `keepRecentTokens`。\n4. 在边界窗口上运行 `findCutPoint()`。\n\n有效截断点包括：\n\n- 角色为以下之一的消息条目：`user`、`assistant`、`bashExecution`、`hookMessage`、`branchSummary`、`compactionSummary`\n- `custom_message` 条目\n- `branch_summary` 条目\n\n硬性规则：永不在 `toolResult` 处截断。\n\n若截断点前紧接着有非消息元数据条目（`model_change`、`thinking_level_change`、标签等），则将截断索引向后移动，直至命中消息或压缩边界，以将这些条目纳入保留区域。\n\n### 分割轮次处理\n\n若截断点不在用户轮次起始处，压缩将其视为分割轮次。\n\n轮次起始检测将以下情况视为用户轮次边界：\n\n- `message.role === \"user\"`\n- `message.role === \"bashExecution\"`\n- `custom_message` 条目\n- `branch_summary` 条目\n\n分割轮次压缩生成两个摘要：\n\n1. 历史摘要（`messagesToSummarize`）\n2. 轮次前缀摘要（`turnPrefixMessages`）\n\n最终存储的摘要合并为：\n\n```markdown\n<history summary>\n\n---\n\n**Turn Context (split turn):**\n\n<turn prefix summary>\n```\n\n### 摘要生成\n\n`compact(...)` 从序列化的对话文本构建摘要：\n\n1. 通过 `convertToLlm()` 转换消息。\n2. 使用 `serializeConversation()` 序列化。\n3. 包装在 `<conversation>...</conversation>` 中。\n4. 可选地包含 `<previous-summary>...</previous-summary>`。\n5. 可选地将钩子上下文作为 `<additional-context>` 列表注入。\n6. 使用 `SUMMARIZATION_SYSTEM_PROMPT` 执行摘要提示。\n\n提示选择：\n\n- 首次压缩：`compaction-summary.md`\n- 带先前摘要的迭代压缩：`compaction-update-summary.md`\n- 分割轮次第二阶段：`compaction-turn-prefix.md`\n- 短 UI 摘要：`compaction-short-summary.md`\n\n远程摘要模式：\n\n- 若设置了 `compaction.remoteEndpoint`，压缩将 POST 以下内容：\n  - `{ systemPrompt, prompt }`\n- 期望返回至少包含 `{ summary }` 的 JSON。\n\n### 摘要中的文件操作上下文\n\n压缩使用助手工具调用跟踪累积文件活动：\n\n- `read(path)` → 读取集合\n- `write(path)` → 修改集合\n- `edit(path)` → 修改集合\n\n累积行为：\n\n- 仅当先前条目是 pi 生成的（`fromExtension !== true`）时，才包含先前压缩的详细信息。\n- 在分割轮次中，也包含轮次前缀的文件操作。\n- `readFiles` 不包含同时被修改的文件。\n\n摘要文本通过提示模板追加文件标签：\n\n```xml\n<read-files>\n...\n</read-files>\n<modified-files>\n...\n</modified-files>\n```\n\n### 持久化与重载\n\n生成摘要（或由钩子提供摘要）后，代理会话：\n\n1. 使用 `appendCompaction(...)` 追加 `CompactionEntry`。\n2. 通过 `buildSessionContext()` 重建上下文。\n3. 将活动代理消息替换为重建后的上下文。\n4. 发出 `session_compact` 钩子事件。\n\n## 分支摘要流程\n\n分支摘要与树导航相关，而非与 token 溢出相关。\n\n### 触发方式\n\n在 `navigateTree(...)` 期间：\n\n1. 使用 `collectEntriesForBranchSummary(...)` 从旧叶节点到公共祖先计算被放弃的条目。\n2. 若调用方请求摘要（`options.summarize`），在切换叶节点前生成摘要。\n3. 若摘要存在，使用 `branchWithSummary(...)` 将其附加到导航目标。\n\n通常在启用 `branchSummary.enabled` 时，由 `/tree` 流程驱动。\n\n### 分支切换结构（可视化）\n\n```text\n导航前的树结构：\n\n         ┌─ B ─ C ─ D （旧叶节点，即将被放弃）\n    A ───┤\n         └─ E ─ F （目标）\n\n公共祖先：A\n待摘要的条目：B、C、D\n\n带摘要的导航后：\n\n         ┌─ B ─ C ─ D ─ [B、C、D 的摘要]\n    A ───┤\n         └─ E ─ F （新叶节点）\n```\n\n### 准备与 token 预算\n\n`generateBranchSummary(...)` 将预算计算为：\n\n- `tokenBudget = model.contextWindow - branchSummary.reserveTokens`\n\n`prepareBranchEntries(...)` 随后：\n\n1. 第一遍：从所有待摘要条目（包括先前 pi 生成的 `branch_summary` 详细信息）收集累积文件操作。\n2. 第二遍：从最新到最旧遍历，添加消息直至达到 token 预算。\n3. 优先保留最近的上下文。\n4. 为保持连续性，仍可能在预算边缘附近包含较大的摘要条目。\n\n在分支摘要输入期间，压缩条目作为消息（`compactionSummary`）被包含。\n\n### 摘要生成与持久化\n\n分支摘要：\n\n1. 转换并序列化选定消息。\n2. 包装在 `<conversation>` 中。\n3. 若提供了自定义指令则使用，否则使用 `branch-summary.md`。\n4. 使用 `SUMMARIZATION_SYSTEM_PROMPT` 调用摘要模型。\n5. 在前面追加 `branch-summary-preamble.md`。\n6. 追加文件操作标签。\n\n结果以 `BranchSummaryEntry` 的形式存储，含可选详细信息（`readFiles`、`modifiedFiles`）。\n\n## 扩展与钩子接入点\n\n### `session_before_compact`\n\n压缩前钩子。\n\n可以：\n\n- 取消压缩（`{ cancel: true }`）\n- 提供完整的自定义压缩负载（`{ compaction: CompactionResult }`）\n\n### `session.compacting`\n\n默认压缩的提示/上下文自定义钩子。\n\n可以返回：\n\n- `prompt`（覆盖基础摘要提示）\n- `context`（注入 `<additional-context>` 的额外上下文行）\n- `preserveData`（存储在压缩条目上）\n\n### `session_compact`\n\n压缩后通知，携带已保存的 `compactionEntry` 和 `fromExtension` 标志。\n\n### `session_before_tree`\n\n在默认分支摘要生成之前的树导航时运行。\n\n可以：\n\n- 取消导航\n- 在用户请求摘要时提供自定义 `{ summary: { summary, details } }`\n\n### `session_tree`\n\n导航后事件，暴露新旧叶节点以及可选的摘要条目。\n\n## 运行时行为与失败语义\n\n- 手动压缩首先中止当前代理操作。\n- `abortCompaction()` 同时取消手动和自动压缩控制器。\n- 自动压缩为 UI/状态更新发出开始/结束会话事件。\n- 自动压缩可尝试多个模型候选项并重试瞬时失败。\n- 溢出错误被排除在通用重试路径之外，因为它们由压缩处理。\n- 若自动压缩失败：\n  - 溢出路径发出 `Context overflow recovery failed: ...`\n  - 阈值路径发出 `Auto-compaction failed: ...`\n- 分支摘要可通过中止信号取消（例如按 Escape），返回已取消/已中止的导航结果。\n\n## 设置与默认值\n\n来自 `settings-schema.ts`：\n\n- `compaction.enabled` = `true`\n- `compaction.reserveTokens` = `16384`\n- `compaction.keepRecentTokens` = `20000`\n- `compaction.autoContinue` = `true`\n- `compaction.remoteEndpoint` = `undefined`\n- `branchSummary.enabled` = `false`\n- `branchSummary.reserveTokens` = `16384`\n\n这些值在运行时由 `AgentSession` 以及压缩/分支摘要模块消费。\n",
	"zh-cn/sessions/handoff-generation-pipeline.md": "---\ntitle: 交接生成管线\ndescription: 用于创建可移植会话摘要以支持团队协作的交接生成管线。\nsidebar:\n  order: 8\n  label: 交接管线\ni18n:\n  sourceHash: 03666084b5ac\n  translator: machine\n---\n\n# `/handoff` 生成管线\n\n本文档描述了 coding-agent 当前如何实现 `/handoff`：触发路径、生成提示词、完成捕获、会话切换和上下文重注入。\n\n## 范围\n\n涵盖内容：\n\n- 交互式 `/handoff` 命令分发\n- `AgentSession.handoff()` 生命周期和状态转换\n- 交接输出如何从助手输出中捕获\n- 旧/新会话如何以不同方式持久化交接数据\n- 成功、取消和失败时的 UI 行为\n\n不涵盖内容：\n\n- 通用树导航/分支内部机制\n- 非交接会话命令（`/new`、`/fork`、`/resume`）\n\n## 实现文件\n\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n\n## 触发路径\n\n1. `/handoff` 在内置斜杠命令元数据（`slash-commands.ts`）中声明，带有可选的内联提示：`[focus instructions]`。\n2. 在交互式输入处理（`InputController`）中，匹配 `/handoff` 或 `/handoff ...` 的提交文本会在正常提示词提交之前被拦截。\n3. 编辑器被清空，并调用 `handleHandoffCommand(customInstructions?)`。\n4. `CommandController.handleHandoffCommand` 使用当前条目执行预检守卫：\n   - 统计 `type === \"message\"` 的条目数量。\n   - 如果 `< 2`，则警告：`Nothing to hand off (no messages yet)` 并返回。\n\n同样的最小内容守卫在 `AgentSession.handoff()` 内部也存在，如果违反则抛出异常。这在 UI 层和会话层都提供了双重安全保障。\n\n## 端到端生命周期\n\n### 1) 开始交接生成\n\n`AgentSession.handoff(customInstructions?)`：\n\n- 读取当前分支条目（`sessionManager.getBranch()`）\n- 验证最小消息数量（`>= 2`）\n- 创建 `#handoffAbortController`\n- 构建一个固定的内联提示词，请求生成结构化交接文档（`Goal`、`Constraints & Preferences`、`Progress`、`Key Decisions`、`Critical Context`、`Next Steps`）\n- 如果提供了自定义指令，则附加 `Additional focus: ...`\n\n提示词通过以下方式发送：\n\n```ts\nawait this.prompt(handoffPrompt, { expandPromptTemplates: false });\n```\n\n`expandPromptTemplates: false` 防止对此内部指令负载进行斜杠/提示词模板展开。\n\n### 2) 捕获完成结果\n\n在发送提示词之前，`handoff()` 订阅会话事件并等待 `agent_end`。\n\n在 `agent_end` 时，它通过向后扫描最近的 `assistant` 消息从代理状态中提取交接文本，然后用 `\\n` 连接所有 `type === \"text\"` 的 `content` 块。\n\n重要的提取假设：\n\n- 仅使用文本块；非文本内容被忽略。\n- 假设最新的助手消息对应于交接生成。\n- 不解析 markdown 章节或验证格式合规性。\n- 如果助手输出没有文本块，则交接被视为缺失。\n\n### 3) 取消检查\n\n当以下任一条件成立时，`handoff()` 返回 `undefined`：\n\n- 没有捕获到交接文本，或\n- `#handoffAbortController.signal.aborted` 为 true\n\n它始终在 `finally` 中清除 `#handoffAbortController`。\n\n### 4) 创建新会话\n\n如果捕获到文本且未被中止：\n\n1. 刷新当前会话写入器（`sessionManager.flush()`）\n2. 启动全新会话（`sessionManager.newSession()`）\n3. 重置内存中的代理状态（`agent.reset()`）\n4. 将 `agent.sessionId` 重新绑定到新会话 id\n5. 清除排队的上下文数组（`#steeringMessages`、`#followUpMessages`、`#pendingNextTurnMessages`）\n6. 重置待办提醒计数器\n\n`newSession()` 创建一个新的头部和空的条目列表（叶节点重置为 `null`）。在交接路径中，不传递 `parentSession`。\n\n### 5) 交接上下文注入\n\n生成的交接文档被包装并作为 `custom_message` 条目附加到新会话：\n\n```text\n<handoff-context>\n...handoff text...\n</handoff-context>\n\nThe above is a handoff document from a previous session. Use this context to continue the work seamlessly.\n```\n\n插入调用：\n\n```ts\nthis.sessionManager.appendCustomMessageEntry(\"handoff\", handoffContent, true);\n```\n\n语义：\n\n- `customType`：`\"handoff\"`\n- `display`：`true`（在 TUI 重建中可见）\n- 条目类型：`custom_message`（参与 LLM 上下文）\n\n### 6) 重建活跃代理上下文\n\n注入后：\n\n1. `sessionManager.buildSessionContext()` 解析当前叶节点的消息列表\n2. `agent.replaceMessages(sessionContext.messages)` 使注入的交接消息成为活跃上下文\n3. 方法返回 `{ document: handoffText }`\n\n此时，新会话中的活跃 LLM 上下文包含注入的交接消息，而非旧的对话记录。\n\n## 持久化模型：旧会话与新会话\n\n### 旧会话\n\n在生成期间，正常的消息持久化保持活跃。助手的交接响应作为常规 `message` 条目在 `message_end` 时被持久化。\n\n结果：原始会话包含可见的已生成交接内容，作为历史对话记录的一部分。\n\n### 新会话\n\n会话重置后，交接以 `custom_message` 形式持久化，`customType: \"handoff\"`。\n\n`buildSessionContext()` 通过 `createCustomMessage(...)` 将此条目转换为运行时自定义/用户上下文消息，因此它会包含在新会话的后续提示词中。\n\n## 控制器/UI 行为\n\n`CommandController.handleHandoffCommand` 行为：\n\n- 调用 `await session.handoff(customInstructions)`\n- 如果结果为 `undefined`：`showError(\"Handoff cancelled\")`\n- 成功时：\n  - `rebuildChatFromMessages()`（加载新会话上下文，包括注入的交接内容）\n  - 使状态行和编辑器顶部边框失效\n  - 重新加载待办事项\n  - 附加成功聊天行：`New session started with handoff context`\n- 异常时：\n  - 如果消息为 `\"Handoff cancelled\"` 或错误名称为 `AbortError`：`showError(\"Handoff cancelled\")`\n  - 否则：`showError(\"Handoff failed: <message>\")`\n- 最后请求渲染\n\n## 取消语义（当前行为）\n\n### 会话级取消原语\n\n`AgentSession` 暴露：\n\n- `abortHandoff()` → 中止 `#handoffAbortController`\n- `isGeneratingHandoff` → 控制器存在时为 true\n\n当使用此中止路径时，交接订阅者会以 `Error(\"Handoff cancelled\")` 拒绝，命令控制器将其映射到取消 UI。\n\n### 交互式 `/handoff` 路径的限制\n\n在当前的交互式控制器接线中，`/handoff` 没有安装专用的 Escape 处理程序来调用 `abortHandoff()`（不像压缩/分支摘要路径会临时覆盖 `editor.onEscape`）。\n\n实际影响：\n\n- 存在会话级取消支持，但在 `/handoff` 命令路径中没有交接专用的键绑定钩子。\n- 用户中断仍可能通过更广泛的代理中止路径发生，但这与 `abortHandoff()` 使用的显式取消通道不同。\n\n## 中止与失败的交接\n\n当前 UI 分类：\n\n- **中止/取消**\n  - `abortHandoff()` 路径触发 `\"Handoff cancelled\"`，或\n  - 抛出 `AbortError`\n  - UI 显示 `Handoff cancelled`\n\n- **失败**\n  - `handoff()` / 提示词管线抛出的任何其他错误（模型/API 验证错误、运行时异常等）\n  - UI 显示 `Handoff failed: ...`\n\n额外细节：如果生成完成但未提取到文本，`handoff()` 返回 `undefined`，控制器当前报告为**取消**，而非**失败**。\n\n## 短会话和最小内容守卫\n\n两个守卫防止低信号交接：\n\n- UI 层（`handleHandoffCommand`）：对 `< 2` 条消息条目发出警告并提前返回\n- 会话层（`handoff()`）：将相同条件作为错误抛出\n\n这避免了创建带有空/近空交接上下文的新会话。\n\n## 状态转换摘要\n\n高层状态流程：\n\n1. 交互式斜杠命令被拦截\n2. 预检消息数量守卫\n3. 创建 `#handoffAbortController`（`isGeneratingHandoff = true`）\n4. 提交内部交接提示词（在聊天中作为正常助手生成可见）\n5. 在 `agent_end` 时，提取最后的助手文本\n6. 如果缺失/中止 → 返回 `undefined` 或取消错误路径\n7. 如果存在：\n   - 刷新旧会话\n   - 创建新的空会话\n   - 重置运行时队列/计数器\n   - 附加 `custom_message(handoff)`\n   - 重建并替换活跃代理消息\n8. 控制器重建聊天 UI 并宣布成功\n9. 清除 `#handoffAbortController`（`isGeneratingHandoff = false`）\n\n## 已知假设和限制\n\n- 交接提取是启发式的：\"最后的助手文本块\"；无结构验证。\n- 没有硬性检查生成的 markdown 是否遵循请求的章节格式。\n- 缺失的提取文本在控制器 UX 中被报告为取消。\n- `/handoff` 交互流程当前缺少专用的 Escape→`abortHandoff()` 绑定。\n- 此路径未设置新会话血统元数据（`parentSession`）。\n",
	"zh-cn/sessions/memory.md": "---\ntitle: 自主记忆\ndescription: 自主记忆系统，用于在会话之间持久化用户偏好、项目上下文和反馈。\nsidebar:\n  order: 7\n  label: 自主记忆\ni18n:\n  sourceHash: 2aa9f516aa1e\n  translator: machine\n---\n\n# 自主记忆\n\n启用后，代理会自动从过去的会话中提取持久知识，并在每个新会话中注入紧凑的摘要。随着时间推移，它会构建一个项目范围的记忆存储——包括技术决策、常用工作流、常见问题——无需手动操作即可持续传递。\n\n默认禁用。可通过 `/settings` 或 `config.yml` 启用：\n\n```yaml\nmemories:\n  enabled: true\n```\n\n## 用法\n\n### 注入的内容\n\n在会话开始时，如果当前项目存在记忆摘要，它会作为 **Memory Guidance** 块注入到系统提示中。代理会被指示：\n\n- 将记忆视为启发式上下文——对流程和先前决策有用，但不能作为当前仓库状态的权威依据。\n- 当记忆改变了计划时，引用记忆产物路径，并在执行操作前结合当前仓库的证据。\n- 当仓库状态和用户指令与记忆冲突时，优先使用仓库状态和用户指令；将冲突的记忆视为过时信息。\n\n### 读取记忆产物\n\n代理可以使用 `read` 工具通过 `memory://` URL 直接读取记忆文件：\n\n| URL | 内容 |\n|---|---|\n| `memory://root` | 启动时注入的紧凑摘要 |\n| `memory://root/MEMORY.md` | 完整的长期记忆文档 |\n| `memory://root/skills/<name>/SKILL.md` | 生成的技能手册 |\n\n### `/memory` 斜杠命令\n\n| 子命令 | 效果 |\n|---|---|\n| `view` | 显示当前记忆注入的内容 |\n| `clear` / `reset` | 删除所有记忆数据和生成的产物 |\n| `enqueue` / `rebuild` | 强制在下次启动时运行整合 |\n\n## 工作原理\n\n记忆通过后台管道构建，在启动时或通过斜杠命令手动触发运行。\n\n**阶段 1 — 逐会话提取：** 对于自上次处理以来发生变化的每个过去会话，模型读取会话历史并提取持久信号：技术决策、约束条件、已解决的故障、常用工作流。过于新近、过于久远或当前活跃的会话会被跳过。每次提取会为该会话生成一个原始记忆块和一份简短概要。\n\n**阶段 2 — 整合：** 提取完成后，第二次模型处理会读取所有逐会话的提取结果，并生成三个写入磁盘的输出：\n\n- `MEMORY.md` — 精心整理的长期记忆文档\n- `memory_summary.md` — 在会话开始时注入的紧凑文本\n- `skills/` — 可复用的过程化手册，每个位于独立的子目录中\n\n阶段 2 使用租约机制来防止多个进程同时启动时重复运行。来自先前运行的过时技能目录会被自动清理。\n\n所有输出在写入磁盘之前都会进行敏感信息扫描。\n\n### 提取行为\n\n记忆提取和整合行为完全由 `src/prompts/memories/` 中的静态提示文件驱动。\n\n| 文件 | 用途 | 变量 |\n|---|---|---|\n| `stage_one_system.md` | 逐会话提取的系统提示 | — |\n| `stage_one_input.md` | 包装会话内容的用户轮次模板 | `{{thread_id}}`、`{{response_items_json}}` |\n| `consolidation.md` | 跨会话整合的提示 | `{{raw_memories}}`、`{{rollout_summaries}}` |\n| `read_path.md` | 注入到实时会话中的记忆引导 | `{{memory_summary}}` |\n\n### 模型选择\n\n记忆依托于模型角色系统。\n\n| 阶段 | 角色 | 用途 |\n|---|---|---|\n| 阶段 1（提取） | `default` | 逐会话知识提取 |\n| 阶段 2（整合） | `smol` | 跨会话综合 |\n\n如果未配置 `smol`，阶段 2 会回退到 `default` 角色。\n\n## 配置\n\n| 设置 | 默认值 | 描述 |\n|---|---|---|\n| `memories.enabled` | `false` | 主开关 |\n| `memories.maxRolloutAgeDays` | `30` | 超过此天数的会话不会被处理 |\n| `memories.minRolloutIdleHours` | `12` | 最近活跃时间短于此小时数的会话会被跳过 |\n| `memories.maxRolloutsPerStartup` | `64` | 单次启动处理的会话数上限 |\n| `memories.summaryInjectionTokenLimit` | `5000` | 注入系统提示的摘要最大 token 数 |\n\n高级用途可在配置中使用额外的调优参数（并发数、租约持续时间、token 预算）。\n\n## 关键文件\n\n- `src/memories/index.ts` — 管道编排、注入、斜杠命令处理\n- `src/memories/storage.ts` — 基于 SQLite 的任务队列和线程注册\n- `src/prompts/memories/` — 记忆提示模板\n- `src/internal-urls/memory-protocol.ts` — `memory://` URL 处理器\n",
	"zh-cn/sessions/non-compaction-retry-policy.md": "---\ntitle: 非压缩自动重试策略\ndescription: 针对压缩路径之外的临时性 API 故障的自动重试策略。\nsidebar:\n  order: 6\n  label: 重试策略\ni18n:\n  sourceHash: 8999a0258dd8\n  translator: machine\n---\n\n# 非压缩自动重试策略\n\n本文档描述了 `AgentSession` 中标准的 API 错误重试路径。\n\n本文明确排除了通过自动压缩进行的上下文溢出恢复。溢出由压缩逻辑处理，相关文档见 [`compaction.md`](./compaction.md)。\n\n## 实现文件\n\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/config/settings-schema.ts`](../../packages/coding-agent/src/config/settings-schema.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts)\n- [`../src/modes/rpc/rpc-client.ts`](../../packages/coding-agent/src/modes/rpc/rpc-client.ts)\n- [`../src/modes/rpc/rpc-types.ts`](../../packages/coding-agent/src/modes/rpc/rpc-types.ts)\n\n## 重试与压缩的边界划分\n\n重试和压缩从相同的 `agent_end` 路径进行检查，但它们被有意分开处理：\n\n1. `agent_end` 检查最后一条助手消息。\n2. `#isRetryableError(...)` 首先执行。\n3. 如果发起了重试，则该轮跳过压缩检查。\n4. 上下文溢出错误被硬性排除在重试分类之外（`isContextOverflow(...)` 会短路重试判断）。\n5. 因此溢出会落入 `#checkCompaction(...)` 而非标准重试。\n\n总结：过载/限流/服务端/网络类故障使用本重试策略；上下文窗口溢出使用压缩恢复。\n\n## 重试分类\n\n`#isRetryableError(...)` 需要满足以下所有条件：\n\n- 助手 `stopReason === \"error\"`\n- `errorMessage` 存在\n- 消息**不是**上下文溢出\n- `errorMessage` 匹配 `#isRetryableErrorMessage(...)`\n\n当前可重试的模式集（基于正则表达式）：\n\n- overloaded\n- rate limit / usage limit / too many requests\n- HTTP 类服务端状态码：429、500、502、503、504\n- service unavailable / server error / internal error\n- connection error / fetch failed\n- `retry delay` 相关措辞\n\n这是基于字符串模式的分类，而非类型化的提供商错误代码。\n\n## 重试生命周期和状态转换\n\n重试使用的会话状态：\n\n- `#retryAttempt: number`（`0` 表示空闲）\n- `#retryPromise: Promise<void> | undefined`（跟踪进行中的重试生命周期）\n- `#retryResolve: (() => void) | undefined`（解析 `#retryPromise`）\n- `#retryAbortController: AbortController | undefined`（取消退避等待）\n\n流程（`#handleRetryableError`）：\n\n1. 读取 `retry` 设置组。\n2. 如果 `retry.enabled === false`，立即停止（返回 `false`，不启动重试）。\n3. 递增 `#retryAttempt`。\n4. 首次尝试时创建 `#retryPromise`（链中的第一次尝试）。\n5. 如果尝试次数超过 `retry.maxRetries`，发出最终失败事件并停止。\n6. 计算延迟：`retry.baseDelayMs * 2^(attempt-1)`。\n7. 对于使用量限制错误，解析重试提示并调用认证存储（`markUsageLimitReached(...)`）；如果提供商/模型切换成功，则强制延迟为 `0`。\n8. 发出 `auto_retry_start`。\n9. 从代理运行时状态中移除尾部的助手错误消息（在持久化的会话历史中保留）。\n10. 支持中止的等待。\n11. 唤醒后，通过 `setTimeout(..., 0)` 调度 `agent.continue()`。\n\n### 重试计数器的重置条件\n\n`#retryAttempt` 在以下情况下重置为 `0`：\n\n- 重试开始后首次成功的非错误、非中止助手消息（发出 `auto_retry_end { success: true }`）\n- 退避等待期间重试被取消\n- 超过最大重试次数路径\n\n`#retryPromise` 在重试链结束时（成功、取消或超过最大次数）通过 `#resolveRetry()` 解析/清除。\n\n## 退避和最大尝试次数语义\n\n设置：\n\n- `retry.enabled`（默认 `true`）\n- `retry.maxRetries`（默认 `3`）\n- `retry.baseDelayMs`（默认 `2000`）\n\n尝试次数编号：\n\n- 尝试计数器在最大值检查之前递增\n- 开始事件使用当前尝试次数（从 1 开始）\n- 超过最大次数的结束事件报告 `attempt: this.#retryAttempt - 1`（最后尝试的重试次数）\n\n使用默认设置的退避序列：\n\n- 第 1 次尝试：2000 毫秒\n- 第 2 次尝试：4000 毫秒\n- 第 3 次尝试：8000 毫秒\n\n延迟覆盖输入仅在使用量限制处理路径中使用，且仅用于影响认证存储的模型/账户切换决策。在主要的非压缩重试路径中，退避保持本地指数延迟，除非切换成功（`delayMs = 0`）。\n\n## 中止机制\n\n### 显式重试中止\n\n`abortRetry()`：\n\n- 中止 `#retryAbortController`（如果存在）\n- 解析重试 promise（`#resolveRetry()`）以解除等待者的阻塞\n\n如果中止发生在等待期间，捕获路径会发出：\n\n- `auto_retry_end { success: false, finalError: \"Retry cancelled\" }`\n- 重置尝试次数/控制器\n\n### 全局操作中止交互\n\n`abort()` 在中止活动的代理流之前调用 `abortRetry()`。这保证了当用户发出通用中止时，重试退避会被取消。\n\n### TUI 交互\n\n收到 `auto_retry_start` 时，EventController：\n\n- 将 `Esc` 处理器切换为 `session.abortRetry()`\n- 渲染加载文本：`Retrying (attempt/maxAttempts) in Ns… (esc to cancel)`\n\n收到 `auto_retry_end` 时，恢复之前的 `Esc` 处理器并清除加载状态。\n\n## 流式处理和提示完成行为\n\n`prompt()` 最终在 `agent.prompt(...)` 返回后等待 `#waitForRetry()`。\n\n效果：\n\n- 一个 prompt 调用在任何已启动的重试链完成（成功/失败/取消）之前不会完全解析\n- 重试生命周期是一个逻辑提示执行边界的一部分\n\n这防止了调用者过早地将正在重试的轮次视为已完成。\n\n## 控制：设置和 RPC\n\n### 配置选项\n\n在设置模式的 retry 组中定义：\n\n- `retry.enabled`\n- `retry.maxRetries`\n- `retry.baseDelayMs`\n\n会话中的编程式切换：\n\n- `setAutoRetryEnabled(enabled)` 写入 `retry.enabled`\n- `autoRetryEnabled` 读取 `retry.enabled`\n- `isRetrying` 报告重试生命周期 promise 是否处于活动状态\n\n### RPC 控制\n\nRPC 命令接口：\n\n- `set_auto_retry` → `session.setAutoRetryEnabled(command.enabled)`\n- `abort_retry` → `session.abortRetry()`\n\n客户端辅助方法：\n\n- `RpcClient.setAutoRetry(enabled)`\n- `RpcClient.abortRetry()`\n\n两个命令都返回成功响应；重试进度/失败详情通过流式会话事件传递，而非命令响应负载。\n\n## 事件发送和失败呈现\n\n会话级重试事件：\n\n- `auto_retry_start { attempt, maxAttempts, delayMs, errorMessage }`\n- `auto_retry_end { success, attempt, finalError? }`\n\n传播：\n\n- 通过 `AgentSession.subscribe(...)` 发出\n- 作为扩展事件转发到扩展运行器\n- 在 RPC 模式下，直接作为 JSON 事件对象转发（`session.subscribe(event => output(event))`）\n- 在 TUI 中，由 `EventController` 消费用于加载/错误 UI\n\n最终失败呈现：\n\n- 超过最大次数或取消时，`auto_retry_end.success === false`\n- TUI 显示：`Retry failed after N attempts: <finalError>`\n- 扩展/钩子接收具有相同字段的 `auto_retry_end`\n- RPC 消费者在 stdout 流上接收相同的事件对象\n\n## 永久停止条件\n\n当发生以下任一情况时，重试停止且不会自动继续：\n\n- `retry.enabled` 为 false\n- 错误未被分类为可重试\n- 错误为上下文溢出（委托给压缩路径）\n- 超过最大重试次数\n- 用户取消重试（在重试加载期间按 `abort_retry` 或 `Esc`）\n- 全局中止（`abort`）先取消重试\n\n在计数器重置后，未来的可重试错误仍可启动新的重试链。\n\n## 操作注意事项\n\n- 分类基于正则文本匹配；此处不使用提供商特定的结构化错误。\n- 重试会从**运行时上下文**中剥离失败的助手错误，然后再继续，但会话历史仍保留该错误条目。\n- `RpcSessionState` 目前暴露了 `autoCompactionEnabled` 但没有 `autoRetryEnabled` 字段；RPC 调用者必须自行跟踪切换状态或通过其他 API 查询设置。\n",
	"zh-cn/sessions/session-operations-export-share-fork-resume.md": "---\ntitle: 会话操作：导出、转储、分享、分叉、恢复\ndescription: 用于导出、分享、分叉和恢复会话的会话操作。\nsidebar:\n  order: 3\n  label: 操作\ni18n:\n  sourceHash: e3c210b29c3e\n  translator: machine\n---\n\n# 会话操作：export、dump、share、fork、resume/continue\n\n本文档描述了当前实现中，操作者可见的会话导出/分享/分叉/恢复操作行为。\n\n## 实现文件\n\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/export/html/index.ts`](../../packages/coding-agent/src/export/html/index.ts)\n- [`../src/export/custom-share.ts`](../../packages/coding-agent/src/export/custom-share.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n\n## 操作矩阵\n\n| 操作 | 入口路径 | 会话变更 | 会话文件创建/切换 | 输出产物 |\n|---|---|---|---|---|\n| `/dump` | 交互式斜杠命令 | 否 | 否 | 剪贴板文本 |\n| `/export [path]` | 交互式斜杠命令 | 否 | 否 | HTML 文件 |\n| `--export <session.jsonl> [outputPath]` | CLI 启动快速路径 | 无运行时会话变更 | 无活跃会话；读取目标文件 | HTML 文件 |\n| `/share` | 交互式斜杠命令 | 否 | 否 | 临时 HTML + 分享 URL/gist |\n| `/fork` | 交互式斜杠命令 | 是（活跃会话身份改变） | 创建新会话文件并将当前会话切换至该文件（仅持久化模式） | 当存在产物目录时，将其复制到新会话命名空间 |\n| `/resume` | 交互式斜杠命令 | 是（活跃的内存状态被替换） | 切换到所选的现有会话文件 | 无 |\n| `--resume` | CLI 启动（选择器） | 会话创建后是 | 打开所选的现有会话文件 | 无 |\n| `--resume <id\\|path>` | CLI 启动 | 会话创建后是 | 打开现有会话；跨项目情况可分叉到当前项目 | 无 |\n| `--continue` | CLI 启动 | 会话创建后是 | 打开终端面包屑或最近的会话；如果不存在则创建新会话 | 无 |\n\n## 导出和转储\n\n### `/export [outputPath]`（交互式）\n\n流程：\n\n1. `InputController` 将 `/export...` 路由到 `CommandController.handleExportCommand`。\n2. 命令按空白字符分割，仅使用 `/export` 之后的第一个参数作为 `outputPath`。\n3. `AgentSession.exportToHtml()` 调用 `exportSessionToHtml(sessionManager, state, { outputPath, themeName })`。\n4. 成功后，UI 显示路径并在浏览器中打开文件。\n\n行为细节：\n\n- `--copy`、`clipboard` 和 `copy` 参数会被明确拒绝，并提示使用 `/dump`。\n- 导出会嵌入会话头部/条目/叶节点以及来自代理状态的当前 `systemPrompt` 和工具描述。\n- 导出过程中不会追加任何会话条目。\n\n注意事项：\n\n- 参数解析基于空白字符（`text.split(/\\s+/)`），因此包含空格的带引号路径不会被此命令路径保留为单一路径。\n\n### `--export <inputSessionFile> [outputPath]`（CLI）\n\n`main.ts` 中的流程：\n\n1. 在交互式/会话启动之前提前处理。\n2. 调用 `exportFromFile(inputPath, outputPath?)`。\n3. `SessionManager.open(inputPath)` 加载条目，然后生成并写入 HTML。\n4. 进程输出 `Exported to: ...` 后退出。\n\n行为细节：\n\n- 缺少输入文件时显示 `File not found: <path>`。\n- 此路径不创建 `AgentSession`，也不变更任何运行中的会话。\n\n### `/dump`（交互式剪贴板导出）\n\n流程：\n\n1. `CommandController.handleDumpCommand()` 调用 `session.formatSessionAsText()`。\n2. 如果返回空字符串，报告 `No messages to dump yet.`\n3. 否则通过原生 `copyToClipboard` 复制到剪贴板。\n\n转储内容包括：\n\n- 系统提示词\n- 活跃的模型/思考级别\n- 工具定义 + 参数\n- 用户/助手消息\n- 思考块和工具调用\n- 工具结果和执行块（不包括 `excludeFromContext` 的 bash/python 条目）\n- 自定义/钩子/文件提及/分支摘要/压缩摘要条目\n\n转储不会进行任何会话持久化更改。\n\n## 分享\n\n`/share` 仅限交互式使用，始终从将当前会话导出到临时 HTML 文件开始。\n\n### 阶段 1：临时导出\n\n- 临时文件路径：`${os.tmpdir()}/${Snowflake.next()}.html`\n- 使用 `session.exportToHtml(tmpFile)`\n- 如果导出失败（特别是内存会话），分享操作以错误结束。\n\n### 阶段 2：自定义分享处理器（如果存在）\n\n`loadCustomShare()` 在 `~/.xcsh/agent` 中检查第一个存在的候选文件：\n\n- `share.ts`\n- `share.js`\n- `share.mjs`\n\n要求：\n\n- 模块必须默认导出一个函数 `(htmlPath) => Promise<CustomShareResult | string | undefined>`。\n\n如果存在且有效：\n\n- UI 进入 `Sharing...` 加载状态。\n- 处理器结果解释：\n  - 字符串 => 视为 URL，显示并打开\n  - 对象 => 显示 `url` 和/或 `message`；打开 `url`\n  - `undefined`/假值 => 通用的 `Session shared`\n- 完成后删除临时文件。\n\n关键的回退行为：\n\n- 如果自定义处理器存在但加载失败，命令报错并返回。\n- 如果自定义处理器执行后抛出异常，命令报错并返回。\n- 在这两种失败情况下，**不会**回退到 GitHub gist。\n- 仅当不存在自定义分享脚本时，才会触发 gist 回退。\n\n### 阶段 3：默认 gist 回退\n\n仅当未找到自定义分享处理器时：\n\n1. 验证 `gh auth status`。\n2. 显示 `Creating gist...` 加载状态。\n3. 运行 `gh gist create --public=false <tmpFile>`。\n4. 解析 gist URL，提取 gist id，构建预览 URL `https://gistpreview.github.io/?<id>`。\n5. 同时显示预览和 gist URL；打开预览。\n\n分享中的取消/中止语义：\n\n- 加载器有 `onAbort` 钩子，用于恢复编辑器 UI 并报告 `Share cancelled`。\n- 在此代码路径中，底层的 `gh gist create` 命令未传递中止信号；取消是 UI 级别的，在命令返回后进行检查。\n\n## 分叉\n\n`/fork` 从当前会话创建新会话，并切换活跃的会话身份。\n\n### 前置条件和即时守卫\n\n- 如果代理正在流式传输，`/fork` 会被拒绝并发出警告。\n- 操作前清除 UI 状态/加载指示器。\n\n### 会话级流程\n\n`AgentSession.fork()`：\n\n1. 以 `reason: \"fork\"` 发出 `session_before_switch` 事件（可取消）。\n2. 刷新待写入内容。\n3. 调用 `SessionManager.fork()`。\n4. 将产物目录从旧会话命名空间复制到新命名空间（尽力而为；非 ENOENT 的复制失败会被记录日志，但不是致命错误）。\n5. 更新 `agent.sessionId`。\n6. 以 `reason: \"fork\"` 发出 `session_switch` 事件。\n\n`SessionManager.fork()` 行为：\n\n- 需要持久化模式和现有会话文件。\n- 创建新的会话 id 和新的 JSONL 文件路径。\n- 重写头部，包含：\n  - 新 `id`\n  - 新时间戳\n  - `cwd` 不变\n  - `parentSession` 设置为前一个会话 id\n- 新文件中保留所有非头部条目不变。\n\n### 非持久化行为\n\n- 内存会话管理器从 `fork()` 返回 `undefined`。\n- `AgentSession.fork()` 返回 `false`。\n- UI 报告 `Fork failed (session not persisted or cancelled)`。\n\n## 恢复和继续\n\n## 交互式 `/resume`\n\n流程：\n\n1. 打开通过 `SessionManager.list(currentCwd, currentSessionDir)` 填充的会话选择器。\n2. 选择后，`SelectorController.handleResumeSession(sessionPath)` 调用 `session.switchSession(sessionPath)`。\n3. UI 清除/重建聊天和待办事项，然后报告 `Resumed session`。\n\n注意：\n\n- 此选择器仅列出当前会话目录范围内的会话。\n- 不使用全局跨项目搜索。\n\n## CLI `--resume`\n\n### `--resume`（无值）\n\n- `main.ts` 列出当前 cwd/sessionDir 的会话并打开选择器。\n- 在会话创建之前，使用 `SessionManager.open(selectedPath)` 打开所选路径。\n\n### `--resume <value>`\n\n`createSessionManager()` 解析顺序：\n\n1. 如果值看起来像路径（`/`、`\\` 或 `.jsonl`），直接打开。\n2. 否则视为 id 前缀：\n   - 在当前范围搜索（`SessionManager.list(cwd, sessionDir)`）\n   - 如果未找到且没有显式 `sessionDir`，进行全局搜索（`SessionManager.listAll()`）\n\n跨项目 id 匹配行为：\n\n- 如果匹配的会话 cwd 与当前 cwd 不同，CLI 会询问：\n  - `Session found in different project ... Fork into current directory? [y/N]`\n- 选择是：`SessionManager.forkFrom(match.path, cwd, sessionDir)` 创建一个新的本地分叉文件。\n- 选择否/非 TTY 默认值：命令报错。\n\n## CLI `--continue`\n\n`SessionManager.continueRecent(cwd, sessionDir)`：\n\n1. 解析当前 cwd 的会话目录。\n2. 首先读取终端范围的面包屑。\n3. 回退到最近修改的会话文件。\n4. 打开找到的会话；如果不存在，创建新会话。\n\n这是仅在启动时的行为；不存在交互式 `/continue` 斜杠命令。\n\n## 会话切换如何实际变更运行时状态\n\n`AgentSession.switchSession(sessionPath)` 执行恢复类操作使用的运行时转换：\n\n1. 以 `reason: \"resume\"` 和 `targetSessionFile` 发出 `session_before_switch` 事件（可取消）。\n2. 断开代理事件订阅并中止进行中的工作。\n3. 清除排队的引导/后续/下一轮消息。\n4. 刷新当前会话管理器写入。\n5. `sessionManager.setSessionFile(sessionPath)` 并更新 `agent.sessionId`。\n6. 从加载的条目构建会话上下文。\n7. 以 `reason: \"resume\"` 发出 `session_switch` 事件。\n8. 从上下文替换代理消息。\n9. 恢复模型（如果在当前注册表中可用）。\n10. 恢复或初始化思考级别。\n11. 重新连接代理事件订阅。\n\n`switchSession()` 本身不创建新的会话文件。\n\n## 事件发出和取消点\n\n### 切换/分叉生命周期钩子\n\n对于 `newSession`、`fork` 和 `switchSession`：\n\n- 前置事件：`session_before_switch`\n  - 原因：`new`、`fork`、`resume`\n  - 可通过返回 `{ cancel: true }` 取消\n- 后置事件：`session_switch`\n  - 相同的原因集\n  - 包含 `previousSessionFile`\n\n`ExtensionRunner.emit()` 在遇到第一个取消性的前置事件结果时提前返回。\n\n### 自定义工具 `onSession` 行为\n\nSDK 将扩展会话事件桥接到自定义工具的 `onSession` 回调：\n\n- `session_switch` -> `onSession({ reason: \"switch\", previousSessionFile })`\n- `session_branch` -> `reason: \"branch\"`\n- `session_start` -> `reason: \"start\"`\n- `session_tree` -> `reason: \"tree\"`\n- `session_shutdown` -> `reason: \"shutdown\"`\n\n这些回调是观察性的；它们不能取消切换/分叉。\n\n### 与本文档相关的其他取消面\n\n- `/fork` 在流式传输期间被阻止（用户必须先等待/中止当前响应）。\n- `/resume` 选择器可以通过用户关闭选择器来取消。\n- 跨项目 `--resume <id>` 可以通过拒绝分叉提示来取消。\n- `/share` 在 gist 流程中有 UI 中止路径（`Share cancelled`）；在此代码路径中不对 `gh gist create` 连接进程终止语义。\n\n## 非持久化（内存）会话行为\n\n当使用 `SessionManager.inMemory()`（`--no-session`）创建会话管理器时：\n\n- 会话文件路径不存在。\n- `/export` 和 `/share` 失败，提示 `Cannot export in-memory session to HTML`（传播到命令错误 UI）。\n- `/fork` 失败，因为 `SessionManager.fork()` 需要持久化。\n- `/dump` 仍然有效，因为它序列化的是内存中的代理状态。\n- 如果设置了 `--no-session`，CLI 的 resume/continue 语义会被绕过，因为管理器创建会立即返回内存模式。\n\n## 已知的实现注意事项（基于当前代码）\n\n- `SelectorController.handleResumeSession()` 不检查 `session.switchSession(...)` 的布尔返回值；钩子取消的切换仍可能继续执行 UI 的 \"Resumed session\" 重绘/状态路径。\n- `/share` 自定义分享失败不会降级到默认 gist 回退；它们会以错误终止命令。\n- `/export` 参数分词方式简单，不能保留包含空格的带引号路径。\n",
	"zh-cn/sessions/session-switching-and-recent-listing.md": "---\ntitle: 会话切换与最近会话列表\ndescription: 会话切换机制以及带有搜索和过滤功能的最近会话列表。\nsidebar:\n  order: 4\n  label: 切换与最近会话\ni18n:\n  sourceHash: aae56130b508\n  translator: machine\n---\n\n# 会话切换与最近会话列表\n\n本文档描述了 coding-agent 如何发现最近的会话、解析 `--resume` 目标、展示会话选择器以及切换活动运行时会话。\n\n本文侧重于当前的实现行为，包括回退路径和注意事项。\n\n## 实现文件\n\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/cli/session-picker.ts`](../../packages/coding-agent/src/cli/session-picker.ts)\n- [`../src/modes/components/session-selector.ts`](../../packages/coding-agent/src/modes/components/session-selector.ts)\n- [`../src/modes/controllers/selector-controller.ts`](../../packages/coding-agent/src/modes/controllers/selector-controller.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n\n## 最近会话发现\n\n### 目录作用域\n\n`SessionManager` 默认在按 cwd 划分作用域的目录下存储会话：\n\n- `~/.xcsh/agent/sessions/--<cwd-encoded>--/*.jsonl`\n\n`SessionManager.list(cwd, sessionDir?)` 仅读取该目录，除非显式提供了 `sessionDir`。\n\n### 两种具有不同载荷的列表路径\n\n存在两种不同的列表管道：\n\n1. `getRecentSessions(sessionDir, limit)`（欢迎/摘要视图）\n   - 仅从每个文件读取 4KB 前缀（`readTextPrefix(..., 4096)`）。\n   - 解析头部 + 最早的用户文本预览。\n   - 返回轻量级的 `RecentSessionInfo`，包含惰性 `name` 和 `timeAgo` getter。\n   - 按文件 `mtime` 降序排序。\n\n2. `SessionManager.list(...)` / `SessionManager.listAll()`（恢复选择器和 ID 匹配）\n   - 读取完整的会话文件。\n   - 构建 `SessionInfo` 对象（`id`、`cwd`、`title`、`messageCount`、`firstMessage`、`allMessagesText`、时间戳）。\n   - 丢弃零条 `message` 记录的会话。\n   - 按 `modified` 降序排序。\n\n### 元数据回退行为\n\n对于最近摘要（`RecentSessionInfo`）：\n\n- 显示名称优先级：`header.title` -> 首条用户提示 -> `header.id` -> 文件名\n- 名称被截断为 40 个字符以便紧凑显示\n- 从标题派生的名称中会剥离/清理控制字符和换行符\n\n对于 `SessionInfo` 列表条目：\n\n- `title` 为 `header.title` 或最新压缩的 `shortSummary`\n- `firstMessage` 为首条用户消息文本或 `\"(no messages)\"`\n\n## `--continue` 解析与终端面包屑优先级\n\n`SessionManager.continueRecent(cwd, sessionDir?)` 按以下顺序解析目标：\n\n1. 读取终端作用域的面包屑（`~/.xcsh/agent/terminal-sessions/<terminal-id>`）\n2. 验证面包屑：\n   - 当前终端可被识别\n   - 面包屑的 cwd 与当前 cwd 匹配（解析路径比较）\n   - 引用的文件仍然存在\n3. 如果面包屑无效/缺失，回退到会话目录中按 mtime 排序的最新文件（`findMostRecentSession`）\n4. 如果未找到，创建新会话\n\n终端 ID 推导优先使用 TTY 路径，回退到基于环境变量的标识符（`KITTY_WINDOW_ID`、`TMUX_PANE`、`TERM_SESSION_ID`、`WT_SESSION`）。\n\n面包屑写入采用尽力而为策略，不会导致致命错误。\n\n## 启动时恢复目标解析（`main.ts`）\n\n### `--resume <value>`\n\n`createSessionManager(...)` 以两种模式处理字符串值的 `--resume`：\n\n1. 类路径值（包含 `/`、`\\\\`，或以 `.jsonl` 结尾）\n   - 直接调用 `SessionManager.open(sessionArg, parsed.sessionDir)`\n\n2. ID 前缀值\n   - 在 `SessionManager.list(cwd, sessionDir)` 中通过 `id.startsWith(sessionArg)` 查找匹配\n   - 如果本地无匹配且未强制指定 `sessionDir`，尝试 `SessionManager.listAll()`\n   - 使用第一个匹配项（无歧义提示）\n\n跨项目匹配行为：\n\n- 如果匹配的会话 cwd 与当前 cwd 不同，CLI 会提示是否分叉到当前项目\n- 是 -> `SessionManager.forkFrom(...)`\n- 否 -> 抛出错误（`Session \"...\" is in another project (...)`）\n\n无匹配 -> 抛出错误（`Session \"...\" not found.`）。\n\n### `--resume`（无值）\n\n在初始会话管理器构建之后处理：\n\n1. 使用 `SessionManager.list(cwd, parsed.sessionDir)` 列出本地会话\n2. 如果为空：打印 `No sessions found` 并提前退出\n3. 打开 TUI 选择器（`selectSession`）\n4. 如果取消：打印 `No session selected` 并提前退出\n5. 如果选中：`SessionManager.open(selectedPath)`\n\n### `--continue`\n\n直接使用 `SessionManager.continueRecent(...)`（上述面包屑优先行为）。\n\n## 基于选择器的选择内部机制\n\n## CLI 选择器（`src/cli/session-picker.ts`）\n\n`selectSession(sessions)` 创建一个独立的 TUI，使用 `SessionSelectorComponent` 并仅解析一次：\n\n- 选择 -> 解析为选中的路径\n- 取消（Esc）-> 解析为 `null`\n- 强制退出（Ctrl+C 路径）-> 停止 TUI 并 `process.exit(0)`\n\n## 交互式会话内选择器（`SelectorController.showSessionSelector`）\n\n流程：\n\n1. 通过 `SessionManager.list(currentCwd, currentSessionDir)` 从当前会话目录获取会话\n2. 使用 `showSelector(...)` 在编辑器区域挂载 `SessionSelectorComponent`\n3. 回调：\n   - 选择 -> 关闭选择器并调用 `handleResumeSession(sessionPath)`\n   - 取消 -> 恢复编辑器并重新渲染\n   - 退出 -> `ctx.shutdown()`\n\n## 会话选择器组件行为\n\n`SessionList` 支持：\n\n- 方向键/翻页导航\n- Enter 选择\n- Esc 取消\n- Ctrl+C 退出\n- 跨会话 id/title/cwd/首条消息/所有消息/路径的模糊搜索\n\n空列表渲染行为：\n\n- 渲染一条消息而非崩溃\n- 空列表时按 Enter 不执行任何操作（无回调）\n- Esc/Ctrl+C 仍然有效\n\n注意事项：UI 文本显示 `Press Tab to view all`，但此组件当前没有 Tab 处理程序，且当前接线仅列出当前作用域的会话。\n\n## 运行时切换执行（`AgentSession.switchSession`）\n\n`switchSession(sessionPath)` 是核心的进程内切换路径。\n\n生命周期/状态转换：\n\n1. 捕获 `previousSessionFile`\n2. 发出 `session_before_switch` 钩子事件（`reason: \"resume\"`，可取消）\n3. 如果被取消 -> 返回 `false` 且不进行切换\n4. 断开当前代理事件流\n5. 中止活动的生成/工具流程\n6. 清除排队的引导/后续/下一轮消息缓冲区\n7. 刷新会话写入器（`sessionManager.flush()`）以持久化待写入内容\n8. `sessionManager.setSessionFile(sessionPath)`\n   - 更新会话文件指针\n   - 写入终端面包屑\n   - 加载条目 / 迁移 / blob 解析 / 重建索引\n   - 如果文件数据缺失/无效：在该路径初始化新会话并重写头部\n9. 更新 `agent.sessionId`\n10. 通过 `buildSessionContext()` 重建上下文\n11. 发出 `session_switch` 钩子事件（`reason: \"resume\"`，`previousSessionFile`）\n12. 用重建的上下文替换代理消息\n13. 如果 `sessionContext.models.default` 可用且存在于模型注册表中，则恢复默认模型\n14. 恢复思考级别：\n    - 如果分支已有 `thinking_level_change`，应用保存的会话级别\n    - 否则从设置中推导默认思考级别，钳制到模型能力范围，设置它，并追加新的 `thinking_level_change` 条目\n15. 重新连接代理监听器并返回 `true`\n\n## 交互式切换后的 UI 状态重建\n\n`SelectorController.handleResumeSession` 围绕 `switchSession` 执行 UI 重置：\n\n- 停止加载动画\n- 清除状态容器\n- 清除待处理消息 UI 和待处理工具映射\n- 重置流式组件/消息引用\n- 调用 `session.switchSession(...)`\n- 清除聊天容器并从会话上下文重新渲染（`renderInitialMessages`）\n- 从新会话工件重新加载待办事项\n- 显示 `Resumed session`\n\n因此，可见的对话/待办事项状态是从新会话文件重建的。\n\n## 启动恢复与会话内切换\n\n### 启动恢复（`--continue`、`--resume`、直接打开）\n\n- 会话文件在 `createAgentSession(...)` 之前选择。\n- `sdk.ts` 构建 `existingSession = sessionManager.buildSessionContext()`。\n- 代理消息在会话创建期间恢复一次。\n- 模型/思考在创建期间选择（包括恢复/回退逻辑）。\n- 然后交互模式运行 `#restoreModeFromSession()` 以重新进入持久化的模式状态（当前为 plan/plan_paused）。\n\n### 会话内切换（`/resume` 风格的选择器路径）\n\n- 在已运行的 `AgentSession` 上使用 `AgentSession.switchSession(...)`。\n- 消息/模型/思考立即就地重建。\n- 发出 `session_before_switch`/`session_switch` 钩子事件。\n- 刷新 UI 聊天/待办事项。\n- 选择器流程中没有专门的切换后模式恢复调用；模式重新进入行为与启动时的 `#restoreModeFromSession()` 不对称。\n\n## 失败和边界情况行为\n\n### 取消路径\n\n- CLI 选择器取消 -> 返回 `null`，调用者打印 `No session selected`，进程提前退出。\n- 交互式选择器取消 -> 编辑器恢复，无会话更改。\n- 钩子取消（`session_before_switch`）-> `switchSession()` 返回 `false`。\n\n### 空列表路径\n\n- CLI `--resume`（无值）：空列表打印 `No sessions found` 并退出。\n- 交互式选择器：空列表渲染消息并保持可取消状态。\n\n### 目标会话文件缺失/无效\n\n当打开/切换到特定路径时（`setSessionFile`）：\n\n- ENOENT -> 视为空 -> 在该精确路径初始化新会话并持久化。\n- 格式错误/无效头部（或实际上不可读的解析条目）-> 视为空 -> 初始化新会话并持久化。\n\n这是恢复行为，不是硬失败。\n\n### 硬失败\n\n切换/打开在真正的 I/O 失败（权限错误、重写失败等）时仍可能抛出异常，这些异常会传播给调用者。\n\n### ID 前缀匹配注意事项\n\n- ID 匹配使用 `startsWith` 并取排序列表中的第一个匹配项。\n- 如果多个会话共享前缀，不会有歧义 UI。\n- `SessionManager.list(...)` 排除零消息的会话，因此这些会话无法通过 ID 匹配/列表选择器恢复。\n",
	"zh-cn/sessions/session-tree-plan.md": "---\ntitle: 会话树架构\ndescription: 具有分支、导航和父子对话关系的会话树架构。\nsidebar:\n  order: 2\n  label: 树架构\ni18n:\n  sourceHash: bd8b78d6c33a\n  translator: machine\n---\n\n# 会话树架构（当前）\n\n参考：[session.md](./session.md)\n\n本文档描述了会话树导航的当前工作方式：内存中的树模型、叶节点移动规则、分支行为以及扩展/事件集成。\n\n## 该子系统是什么\n\n会话以追加写入的条目日志形式存储，但运行时行为是基于树的：\n\n- 每个非头部条目都有 `id` 和 `parentId`。\n- 活动位置是 `SessionManager` 中的 `leafId`。\n- 追加条目始终会创建当前叶节点的子节点。\n- 分支**不会**重写历史；它只是在下一次追加之前改变叶节点的指向位置。\n\n关键文件：\n\n- `src/session/session-manager.ts` — 树数据模型、遍历、叶节点移动、分支/会话提取\n- `src/session/agent-session.ts` — `/tree` 导航流程、摘要生成、钩子/事件发射\n- `src/modes/components/tree-selector.ts` — 交互式树 UI 行为和过滤\n- `src/modes/controllers/selector-controller.ts` — `/tree` 和 `/branch` 的选择器编排\n- `src/modes/controllers/input-controller.ts` — 命令路由（`/tree`、`/branch`、双击 Escape 行为）\n- `src/session/messages.ts` — 将 `branch_summary`、`compaction` 和 `custom_message` 条目转换为 LLM 上下文消息\n\n## `SessionManager` 中的树数据模型\n\n运行时索引：\n\n- `#byId: Map<string, SessionEntry>` — 快速查找任意条目\n- `#leafId: string | null` — 树中的当前位置\n- `#labelsById: Map<string, string>` — 按目标条目 id 解析的标签\n\n树 API：\n\n- `getBranch(fromId?)` 沿父链接向上遍历到根节点，返回从根到节点的路径\n- `getTree()` 返回 `SessionTreeNode[]`（`entry`、`children`、`label`）\n  - 父链接转换为子数组\n  - 缺少父节点的条目被视为根节点\n  - 子节点按时间戳从旧到新排序\n- `getChildren(parentId)` 返回直接子节点\n- `getLabel(id)` 从 `labelsById` 解析当前标签\n\n`getTree()` 是运行时投影；持久化仍然是追加写入的 JSONL 条目。\n\n## 叶节点移动语义\n\n有三种叶节点移动原语：\n\n1. `branch(entryId)`\n   - 验证条目是否存在\n   - 设置 `leafId = entryId`\n   - 不写入新条目\n\n2. `resetLeaf()`\n   - 设置 `leafId = null`\n   - 下一次追加将创建新的根条目（`parentId = null`）\n\n3. `branchWithSummary(branchFromId, summary, details?, fromExtension?)`\n   - 接受 `branchFromId: string | null`\n   - 设置 `leafId = branchFromId`\n   - 将 `branch_summary` 条目作为该叶节点的子节点追加\n   - 当 `branchFromId` 为 `null` 时，`fromId` 持久化为 `\"root\"`\n\n## `/tree` 导航行为（同一会话文件内）\n\n`AgentSession.navigateTree()` 是导航操作，不是文件分叉。\n\n流程：\n\n1. 验证目标并计算被放弃的路径（`collectEntriesForBranchSummary`）\n2. 携带 `TreePreparation` 发射 `session_before_tree` 事件\n3. 可选地对被放弃的条目进行摘要（使用钩子提供的摘要或内置摘要器）\n4. 计算新的叶节点目标：\n   - 选择 **user** 消息：叶节点移动到其父节点，消息文本返回用于编辑器预填充\n   - 选择 **custom_message**：规则与 user 消息相同（叶节点 = 父节点，文本预填充编辑器）\n   - 选择任何其他条目：叶节点 = 所选条目 id\n5. 应用叶节点移动：\n   - 有摘要时：`branchWithSummary(newLeafId, ...)`\n   - 无摘要且 `newLeafId === null` 时：`resetLeaf()`\n   - 其他情况：`branch(newLeafId)`\n6. 从新叶节点重建代理上下文并发射 `session_tree` 事件\n\n重要：摘要条目附加在**新的导航位置**，而非被放弃分支的尾部。\n\n## `/branch` 行为（新会话文件）\n\n`/branch` 和 `/tree` 是有意区分的：\n\n- `/tree` 在当前会话文件内导航。\n- `/branch` 创建新的会话分支文件（或在非持久化模式下进行内存中替换）。\n\n面向用户的 `/branch` 流程（`SelectorController.showUserMessageSelector` → `AgentSession.branch`）：\n\n- 分支源必须是 **user 消息**。\n- 提取所选用户文本用于编辑器预填充。\n- 如果所选用户消息是根节点（`parentId === null`）：通过 `newSession({ parentSession: previousSessionFile })` 启动新会话。\n- 否则：`createBranchedSession(selectedEntry.parentId)` 将历史分叉到所选提示边界。\n\n`SessionManager.createBranchedSession(leafId)` 具体细节：\n\n- 通过 `getBranch(leafId)` 构建从根到叶的路径；如果缺失则抛出异常。\n- 从复制的路径中排除现有的 `label` 条目。\n- 为路径中保留的条目从已解析的 `labelsById` 重建新的标签条目。\n- 持久化模式：写入新的 JSONL 文件并将管理器切换到该文件；返回新文件路径。\n- 内存模式：替换内存中的条目；返回 `undefined`。\n\n## 上下文重建与摘要/自定义集成\n\n`buildSessionContext()`（位于 `session-manager.ts` 中）解析活动的根到叶路径并构建有效的 LLM 上下文状态：\n\n- 追踪路径上最新的 thinking/model/mode/ttsr 状态。\n- 处理路径上最新的压缩：\n  - 首先发出压缩摘要\n  - 从 `firstKeptEntryId` 到压缩点回放保留的消息\n  - 然后回放压缩后的消息\n- 将 `branch_summary` 和 `custom_message` 条目作为 `AgentMessage` 对象包含在内。\n\n`session/messages.ts` 然后将这些消息类型映射为模型输入：\n\n- `branchSummary` 和 `compactionSummary` 变为 user 角色的模板化上下文消息\n- `custom`/`hookMessage` 变为 user 角色的内容消息\n\n因此，树移动通过改变活动叶路径来更改上下文，而非修改旧条目。\n\n## 标签和树 UI 行为\n\n标签持久化：\n\n- `appendLabelChange(targetId, label?)` 在当前叶节点链上写入 `label` 条目。\n- `labelsById` 立即更新（设置或删除）。\n- `getTree()` 将当前标签解析到每个返回的节点上。\n\n树选择器行为（`tree-selector.ts`）：\n\n- 展平树以供导航，保持活动路径高亮，并优先显示活动分支。\n- 支持过滤模式：`default`、`no-tools`、`user-only`、`labeled-only`、`all`。\n- 支持对渲染的语义内容进行自由文本搜索。\n- `Shift+L` 打开内联标签编辑并通过 `appendLabelChange` 写入。\n\n命令路由：\n\n- `/tree` 始终打开树选择器。\n- `/branch` 打开用户消息选择器，除非 `doubleEscapeAction=tree`，在这种情况下也使用树选择器 UX。\n\n## 树操作的扩展和钩子触点\n\n命令时扩展 API（`ExtensionCommandContext`）：\n\n- `branch(entryId)` — 创建分支会话文件\n- `navigateTree(targetId, { summarize? })` — 在当前树/文件内移动\n\n树导航相关事件：\n\n- `session_before_tree`\n  - 接收 `TreePreparation`：\n    - `targetId`\n    - `oldLeafId`\n    - `commonAncestorId`\n    - `entriesToSummarize`\n    - `userWantsSummary`\n  - 可以取消导航\n  - 可以提供摘要载荷替代内置摘要器\n  - 接收中止 `signal`（Escape 取消路径）\n- `session_tree`\n  - 发射 `newLeafId`、`oldLeafId`\n  - 当创建了摘要时包含 `summaryEntry`\n  - `fromExtension` 指示摘要来源\n\n相邻但相关的生命周期钩子：\n\n- `session_before_branch` / `session_branch` 用于 `/branch` 流程\n- `session_before_compact`、`session.compacting`、`session_compact` 用于后续影响树上下文重建的压缩条目\n\n## 实际约束和边界条件\n\n- `branch()` 不能指向 `null`；要达到首条目之前的根状态请使用 `resetLeaf()`。\n- `branchWithSummary()` 支持 `null` 目标并记录 `fromId: \"root\"`。\n- 在树选择器中选择当前叶节点是空操作。\n- 摘要生成需要活动模型；如果不存在，摘要导航会快速失败。\n- 如果摘要生成被中止，导航将被取消且叶节点保持不变。\n- 内存中的会话在 `createBranchedSession` 中永远不会返回分支文件路径。\n\n## 仍然存在的遗留兼容性\n\n会话迁移仍在加载时运行：\n\n- v1→v2 添加 `id`/`parentId` 并将压缩索引锚点转换为 id 锚点\n- v2→v3 将遗留的 `hookMessage` 角色迁移为 `custom`\n\n迁移后的当前运行时行为是 version-3 树语义。\n",
	"zh-cn/sessions/session.md": "---\ntitle: 会话存储与条目模型\ndescription: 基于追加模式的会话存储模型，包含条目类型、持久化以及格式间的迁移。\nsidebar:\n  order: 1\n  label: 存储与条目模型\ni18n:\n  sourceHash: 42fe17549e00\n  translator: machine\n---\n\n# 会话存储与条目模型\n\n本文档是 coding-agent 会话如何表示、持久化、迁移以及在运行时重建的权威参考。\n\n## 范围\n\n涵盖内容：\n\n- 会话 JSONL 格式与版本控制\n- 条目分类与树语义（`id`/`parentId` + 叶指针）\n- 加载旧文件或格式错误文件时的迁移/兼容性行为\n- 上下文重建（`buildSessionContext`）\n- 持久化保证、失败行为、截断/blob 外部化\n- 存储抽象（`FileSessionStorage`、`MemorySessionStorage`）及相关工具\n\n不涵盖 `/tree` UI 渲染行为，除非涉及影响会话数据的语义。\n\n## 实现文件\n\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`src/session/messages.ts`](../../packages/coding-agent/src/session/messages.ts)\n- [`src/session/session-storage.ts`](../../packages/coding-agent/src/session/session-storage.ts)\n- [`src/session/history-storage.ts`](../../packages/coding-agent/src/session/history-storage.ts)\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts)\n\n## 磁盘布局\n\n默认会话文件位置：\n\n```text\n~/.xcsh/agent/sessions/--<cwd-encoded>--/<timestamp>_<sessionId>.jsonl\n```\n\n`<cwd-encoded>` 由工作目录派生而来，去除前导斜杠并将 `/`、`\\\\` 和 `:` 替换为 `-`。\n\nBlob 存储位置：\n\n```text\n~/.xcsh/agent/blobs/<sha256>\n```\n\n终端面包屑文件写入位置：\n\n```text\n~/.xcsh/agent/terminal-sessions/<terminal-id>\n```\n\n面包屑内容为两行：原始 cwd 和会话文件路径。`continueRecent()` 在扫描最近修改时间之前优先使用此终端范围的指针。\n\n## 文件格式\n\n会话文件为 JSONL 格式：每行一个 JSON 对象。\n\n- 第 1 行始终为会话头（`type: \"session\"`）。\n- 其余行为 `SessionEntry` 值。\n- 条目在运行时仅追加；分支导航通过移动指针（`leafId`）而非修改现有条目实现。\n\n### 头部（`SessionHeader`）\n\n```json\n{\n  \"type\": \"session\",\n  \"version\": 3,\n  \"id\": \"1f9d2a6b9c0d1234\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\",\n  \"cwd\": \"/work/pi\",\n  \"title\": \"optional session title\",\n  \"parentSession\": \"optional lineage marker\"\n}\n```\n\n说明：\n\n- `version` 在 v1 文件中为可选字段；缺失表示 v1。\n- `parentSession` 是不透明的血统字符串。当前代码根据流程（`fork`、`forkFrom`、`createBranchedSession` 或显式 `newSession({ parentSession })`）写入会话 id 或会话路径。视为元数据，而非类型化的外键。\n\n### 条目基类（`SessionEntryBase`）\n\n所有非头部条目包含：\n\n```json\n{\n  \"type\": \"...\",\n  \"id\": \"8-char-id\",\n  \"parentId\": \"previous-or-branch-parent\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\"\n}\n```\n\n`parentId` 对于根条目可以为 `null`（首次追加，或 `resetLeaf()` 之后）。\n\n## 条目分类\n\n`SessionEntry` 是以下类型的联合：\n\n- `message`\n- `thinking_level_change`\n- `model_change`\n- `compaction`\n- `branch_summary`\n- `custom`\n- `custom_message`\n- `label`\n- `ttsr_injection`\n- `session_init`\n- `mode_change`\n\n### `message`\n\n直接存储一个 `AgentMessage`。\n\n```json\n{\n  \"type\": \"message\",\n  \"id\": \"a1b2c3d4\",\n  \"parentId\": null,\n  \"timestamp\": \"2026-02-16T10:21:00.000Z\",\n  \"message\": {\n    \"role\": \"assistant\",\n    \"provider\": \"anthropic\",\n    \"model\": \"claude-sonnet-4-5\",\n    \"content\": [{ \"type\": \"text\", \"text\": \"Done.\" }],\n    \"usage\": { \"input\": 100, \"output\": 20, \"cacheRead\": 0, \"cacheWrite\": 0, \"cost\": { \"input\": 0, \"output\": 0, \"cacheRead\": 0, \"cacheWrite\": 0, \"total\": 0 } },\n    \"timestamp\": 1760000000000\n  }\n}\n```\n\n### `model_change`\n\n```json\n{\n  \"type\": \"model_change\",\n  \"id\": \"b1c2d3e4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:21:30.000Z\",\n  \"model\": \"openai/gpt-4o\",\n  \"role\": \"default\"\n}\n```\n\n`role` 为可选字段；缺失时在上下文重建中视为 `default`。\n\n### `thinking_level_change`\n\n```json\n{\n  \"type\": \"thinking_level_change\",\n  \"id\": \"c1d2e3f4\",\n  \"parentId\": \"b1c2d3e4\",\n  \"timestamp\": \"2026-02-16T10:22:00.000Z\",\n  \"thinkingLevel\": \"high\"\n}\n```\n\n### `compaction`\n\n```json\n{\n  \"type\": \"compaction\",\n  \"id\": \"d1e2f3a4\",\n  \"parentId\": \"c1d2e3f4\",\n  \"timestamp\": \"2026-02-16T10:23:00.000Z\",\n  \"summary\": \"Conversation summary\",\n  \"shortSummary\": \"Short recap\",\n  \"firstKeptEntryId\": \"a1b2c3d4\",\n  \"tokensBefore\": 42000,\n  \"details\": { \"readFiles\": [\"src/a.ts\"] },\n  \"preserveData\": { \"hookState\": true },\n  \"fromExtension\": false\n}\n```\n\n### `branch_summary`\n\n```json\n{\n  \"type\": \"branch_summary\",\n  \"id\": \"e1f2a3b4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:24:00.000Z\",\n  \"fromId\": \"a1b2c3d4\",\n  \"summary\": \"Summary of abandoned path\",\n  \"details\": { \"note\": \"optional\" },\n  \"fromExtension\": true\n}\n```\n\n如果从根节点分支（`branchFromId === null`），`fromId` 为字面字符串 `\"root\"`。\n\n### `custom`\n\n扩展状态持久化；被 `buildSessionContext` 忽略。\n\n```json\n{\n  \"type\": \"custom\",\n  \"id\": \"f1a2b3c4\",\n  \"parentId\": \"e1f2a3b4\",\n  \"timestamp\": \"2026-02-16T10:25:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"data\": { \"state\": 1 }\n}\n```\n\n### `custom_message`\n\n由扩展提供的消息，参与 LLM 上下文构建。\n\n```json\n{\n  \"type\": \"custom_message\",\n  \"id\": \"a2b3c4d5\",\n  \"parentId\": \"f1a2b3c4\",\n  \"timestamp\": \"2026-02-16T10:26:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"content\": \"Injected context\",\n  \"display\": true,\n  \"details\": { \"debug\": false }\n}\n```\n\n### `label`\n\n```json\n{\n  \"type\": \"label\",\n  \"id\": \"b2c3d4e5\",\n  \"parentId\": \"a2b3c4d5\",\n  \"timestamp\": \"2026-02-16T10:27:00.000Z\",\n  \"targetId\": \"a1b2c3d4\",\n  \"label\": \"checkpoint\"\n}\n```\n\n`label: undefined` 会清除 `targetId` 的标签。\n\n### `ttsr_injection`\n\n```json\n{\n  \"type\": \"ttsr_injection\",\n  \"id\": \"c2d3e4f5\",\n  \"parentId\": \"b2c3d4e5\",\n  \"timestamp\": \"2026-02-16T10:28:00.000Z\",\n  \"injectedRules\": [\"ruleA\", \"ruleB\"]\n}\n```\n\n### `session_init`\n\n```json\n{\n  \"type\": \"session_init\",\n  \"id\": \"d2e3f4a5\",\n  \"parentId\": \"c2d3e4f5\",\n  \"timestamp\": \"2026-02-16T10:29:00.000Z\",\n  \"systemPrompt\": \"...\",\n  \"task\": \"...\",\n  \"tools\": [\"read\", \"edit\"],\n  \"outputSchema\": { \"type\": \"object\" }\n}\n```\n\n### `mode_change`\n\n```json\n{\n  \"type\": \"mode_change\",\n  \"id\": \"e2f3a4b5\",\n  \"parentId\": \"d2e3f4a5\",\n  \"timestamp\": \"2026-02-16T10:30:00.000Z\",\n  \"mode\": \"plan\",\n  \"data\": { \"planFile\": \"/tmp/plan.md\" }\n}\n```\n\n## 版本控制与迁移\n\n当前会话版本：`3`。\n\n### v1 -> v2\n\n当头部 `version` 缺失或 `< 2` 时应用：\n\n- 为每个非头部条目添加 `id` 和 `parentId`。\n- 使用文件顺序重建线性父链。\n- 当存在时将压缩字段 `firstKeptEntryIndex` 迁移为 `firstKeptEntryId`。\n- 设置头部 `version = 2`。\n\n### v2 -> v3\n\n当头部 `version < 3` 时应用：\n\n- 对于 `message` 条目：将旧版 `message.role === \"hookMessage\"` 重写为 `\"custom\"`。\n- 设置头部 `version = 3`。\n\n### 迁移触发与持久化\n\n- 迁移在会话加载时运行（`setSessionFile`）。\n- 如果执行了任何迁移，整个文件会立即重写到磁盘。\n- 迁移首先修改内存中的条目，然后持久化重写后的 JSONL。\n\n## 加载与兼容性行为\n\n`loadEntriesFromFile(path)` 行为：\n\n- 文件不存在（`ENOENT`）-> 返回 `[]`。\n- 不可解析的行由宽容的 JSONL 解析器（`parseJsonlLenient`）处理。\n- 如果第一个解析的条目不是有效的会话头（`type !== \"session\"` 或缺少字符串 `id`）-> 返回 `[]`。\n\n`SessionManager.setSessionFile()` 行为：\n\n- 加载器返回 `[]` 视为空/不存在的会话，并在该路径创建新的初始化会话文件替代。\n- 有效文件被加载，如需要则迁移，解析 blob 引用，然后建立索引。\n\n## 树与叶语义\n\n底层模型为追加式树 + 可变叶指针：\n\n- 每个追加方法创建恰好一个新条目，其 `parentId` 为当前 `leafId`。\n- 新条目成为新的 `leafId`。\n- `branch(entryId)` 仅移动 `leafId`；现有条目保持不变。\n- `resetLeaf()` 将 `leafId` 设为 `null`；下一次追加创建新的根条目（`parentId: null`）。\n- `branchWithSummary()` 将叶设置为分支目标并追加一个 `branch_summary` 条目。\n\n`getEntries()` 按插入顺序返回所有非头部条目。正常操作中不删除现有条目；重写在更新表示的同时保留逻辑历史（迁移、移动、定向重写辅助函数）。\n\n## 上下文重建（`buildSessionContext`）\n\n`buildSessionContext(entries, leafId, byId?)` 解析发送给模型的内容。\n\n算法：\n\n1. 确定叶节点：\n   - `leafId === null` -> 返回空上下文。\n   - 显式 `leafId` -> 如果找到则使用该条目。\n   - 否则回退到最后一个条目。\n2. 从叶节点沿 `parentId` 链回溯到根节点，然后反转为根->叶路径。\n3. 沿路径推导运行时状态：\n   - `thinkingLevel` 取自最新的 `thinking_level_change`（默认 `\"off\"`）\n   - 模型映射取自 `model_change` 条目（`role ?? \"default\"`）\n   - 如果没有显式的模型变更，`models.default` 回退到助手消息的 provider/model\n   - 去重的 `injectedTtsrRules` 来自所有 `ttsr_injection` 条目\n   - mode/modeData 取自最新的 `mode_change`（默认模式 `\"none\"`）\n4. 构建消息列表：\n   - `message` 条目直接透传\n   - `custom_message` 条目通过 `createCustomMessage` 转为 `custom` AgentMessages\n   - `branch_summary` 条目通过 `createBranchSummaryMessage` 转为 `branchSummary` AgentMessages\n   - 如果路径上存在 `compaction`：\n     - 首先发出压缩摘要（`createCompactionSummaryMessage`）\n     - 发出从 `firstKeptEntryId` 到压缩边界的路径条目\n     - 发出压缩边界之后的条目\n\n`custom` 和 `session_init` 条目不直接注入模型上下文。\n\n## 持久化保证与失败模型\n\n### 持久化与内存模式\n\n- `SessionManager.create/open/continueRecent/forkFrom` -> 持久化模式（`persist = true`）。\n- `SessionManager.inMemory` -> 非持久化模式（`persist = false`），使用 `MemorySessionStorage`。\n\n### 写入管道\n\n写入通过内部 promise 链（`#persistChain`）和 `NdjsonFileWriter` 串行化。\n\n- `append*` 立即更新内存状态。\n- 持久化延迟到至少存在一条助手消息时执行。\n  - 首条助手消息之前：条目保留在内存中；不执行文件追加。\n  - 当首条助手消息存在时：将完整的内存会话刷写到文件。\n  - 之后：新条目增量追加。\n\n代码中的设计理由：避免持久化从未产生助手响应的会话。\n\n### 持久性操作\n\n- `flush()` 刷写写入器并调用 `fsync()`。\n- 原子完整重写（`#rewriteFile`）写入临时文件，刷写+fsync，关闭，然后重命名覆盖目标文件。\n- 用于迁移、`setSessionName`、`rewriteEntries`、移动操作以及工具调用参数重写。\n\n### 错误行为\n\n- 持久化错误被锁存（`#persistError`）并在后续操作中重新抛出。\n- 首个错误会附带会话文件上下文记录一次日志。\n- 写入器关闭采用尽力而为策略，但会传播第一个有意义的错误。\n\n## 数据大小控制与 Blob 外部化\n\n在持久化条目之前：\n\n- 大字符串被截断至 `MAX_PERSIST_CHARS`（500,000 字符）并附带提示：\n  - `\"[Session persistence truncated large content]\"`\n- 临时字段 `partialJson` 和 `jsonlEvents` 被移除。\n- 如果对象同时具有 `content` 和 `lineCount`，截断后会重新计算行数。\n- `content` 数组中 base64 长度 >= 1024 的图片块被外部化为 blob 引用：\n  - 存储为 `blob:sha256:<hash>`\n  - 原始字节写入 blob 存储（`BlobStore.put`）\n\n加载时，blob 引用会被还原为 base64 用于 message/custom_message 图片块。\n\n## 存储抽象\n\n`SessionStorage` 接口提供 `SessionManager` 使用的所有文件系统操作：\n\n- 同步：`ensureDirSync`、`existsSync`、`writeTextSync`、`statSync`、`listFilesSync`\n- 异步：`exists`、`readText`、`readTextPrefix`、`writeText`、`rename`、`unlink`、`openWriter`\n\n实现：\n\n- `FileSessionStorage`：真实文件系统（Bun + node fs）\n- `MemorySessionStorage`：基于 map 的内存实现，用于测试/非持久化会话\n\n`SessionStorageWriter` 暴露 `writeLine`、`flush`、`fsync`、`close`、`getError`。\n\n## 会话发现工具\n\n定义在 `session-manager.ts` 中：\n\n- `getRecentSessions(sessionDir, limit)` -> 用于 UI/会话选择器的轻量级元数据\n- `findMostRecentSession(sessionDir)` -> 按修改时间排序的最新会话\n- `list(cwd, sessionDir?)` -> 单个项目范围内的会话\n- `listAll()` -> `~/.xcsh/agent/sessions` 下所有项目范围的会话\n\n元数据提取尽可能仅读取前缀（`readTextPrefix(..., 4096)`）。\n\n## 相关但独立的：提示词历史存储\n\n`HistoryStorage`（`history-storage.ts`）是一个独立的 SQLite 子系统，用于提示词回调/搜索，而非会话回放。\n\n- 数据库：`~/.xcsh/agent/history.db`\n- 表：`history(id, prompt, created_at, cwd)`\n- FTS5 索引：`history_fts`，通过触发器维护同步\n- 使用内存中的上次提示词缓存对连续相同的提示词进行去重\n- 异步插入（`setImmediate`），使提示词捕获不阻塞轮次执行\n\n使用会话文件进行对话图/状态回放；使用 `HistoryStorage` 进行提示词历史 UX。\n",
	"zh-cn/sessions/ttsr-injection-lifecycle.md": "---\ntitle: TTSR 注入生命周期\ndescription: TTSR（tool-use、tool-result、system-reminder）上下文管理的注入生命周期。\nsidebar:\n  order: 9\n  label: TTSR 注入\ni18n:\n  sourceHash: d6179a286584\n  translator: machine\n---\n\n# TTSR 注入生命周期\n\n本文档涵盖了当前时间旅行流规则（TTSR）运行时路径，从规则发现到流中断、重试注入、扩展通知以及会话状态处理。\n\n## 实现文件\n\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/export/ttsr.ts`](../../packages/coding-agent/src/export/ttsr.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/prompts/system/ttsr-interrupt.md`](../../packages/coding-agent/src/prompts/system/ttsr-interrupt.md)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/types.ts`](../../packages/coding-agent/src/extensibility/extensions/types.ts)\n- [`../src/extensibility/hooks/types.ts`](../../packages/coding-agent/src/extensibility/hooks/types.ts)\n- [`../src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n\n## 1. 发现源与规则注册\n\n在会话创建时，`createAgentSession()` 加载所有已发现的规则并构建 `TtsrManager`：\n\n```ts\nconst ttsrSettings = settings.getGroup(\"ttsr\");\nconst ttsrManager = new TtsrManager(ttsrSettings);\nconst rulesResult = await loadCapability<Rule>(ruleCapability.id, { cwd });\nfor (const rule of rulesResult.items) {\n  if (rule.ttsrTrigger) ttsrManager.addRule(rule);\n}\n```\n\n### 预注册去重行为\n\n`loadCapability(\"rules\")` 按 `rule.name` 进行去重，采用先到先得语义（优先级更高的提供者优先）。被遮蔽的重复项在 TTSR 注册之前即被移除。\n\n### `TtsrManager.addRule()` 行为\n\n在以下情况下将跳过注册：\n\n- `rule.ttsrTrigger` 不存在\n- 该管理器中已注册了相同 `rule.name` 的规则\n- 正则表达式编译失败（`new RegExp(rule.ttsrTrigger)` 抛出异常）\n\n无效的正则触发器会作为警告记录并被忽略；会话启动继续进行。\n\n### 设置注意事项\n\n`TtsrSettings.enabled` 被加载到管理器中，但目前在运行时门控中未被检查。只要存在规则，匹配仍然会运行。\n\n## 2. 流监控生命周期\n\nTTSR 检测在 `AgentSession.#handleAgentEvent` 内部运行。\n\n### 轮次开始\n\n在 `turn_start` 时，流缓冲区被重置：\n\n- `ttsrManager.resetBuffer()`\n\n### 流传输期间（`message_update`）\n\n当助手更新到达且存在规则时：\n\n- 监控 `text_delta` 和 `toolcall_delta`\n- 将增量追加到管理器缓冲区\n- 调用 `check(buffer)`\n\n`check()` 遍历已注册的规则，并返回所有通过重复策略（`#canTrigger`）的匹配规则。\n\n## 3. 触发决策与立即中止路径\n\n当一个或多个规则匹配时：\n\n1. `markInjected(matches)` 在管理器注入状态中记录规则名称。\n2. 匹配的规则被加入 `#pendingTtsrInjections` 队列。\n3. `#ttsrAbortPending = true`。\n4. 立即调用 `agent.abort()`。\n5. 异步发出 `ttsr_triggered` 事件（即发即忘）。\n6. 通过 `setTimeout(..., 50)` 调度重试工作。\n\n中止不会阻塞在扩展回调上。\n\n## 4. 重试调度、上下文模式与提醒注入\n\n在 50ms 超时之后：\n\n1. `#ttsrAbortPending = false`\n2. 读取 `ttsrManager.getSettings().contextMode`\n3. 如果 `contextMode === \"discard\"`，使用 `agent.popMessage()` 丢弃部分助手输出\n4. 使用 `ttsr-interrupt.md` 模板从待处理规则构建注入内容\n5. 追加一条合成用户消息，其中包含每个规则对应的一个 `<system-interrupt ...>` 块\n6. 调用 `agent.continue()` 重试生成\n\n模板负载为：\n\n```xml\n<system-interrupt reason=\"rule_violation\" rule=\"{{name}}\" path=\"{{path}}\">\n...\n{{content}}\n</system-interrupt>\n```\n\n待处理的注入在内容生成后被清除。\n\n### `contextMode` 对部分输出的行为\n\n- `discard`：在重试之前移除部分/已中止的助手消息。\n- `keep`：部分助手输出保留在对话状态中；提醒在其后追加。\n\n## 5. 重复策略与间隔逻辑\n\n`TtsrManager` 跟踪 `#messageCount` 和每条规则的 `lastInjectedAt`。\n\n### `repeatMode: \"once\"`\n\n规则在拥有注入记录后只能触发一次。\n\n### `repeatMode: \"after-gap\"`\n\n规则只有在以下条件满足时才能重新触发：\n\n- `messageCount - lastInjectedAt >= repeatGap`\n\n`messageCount` 在 `turn_end` 时递增，因此间隔以完成的轮次为单位衡量，而非流数据块。\n\n## 6. 事件发出与扩展/钩子接口\n\n### 会话事件\n\n`AgentSessionEvent` 包括：\n\n```ts\n{ type: \"ttsr_triggered\"; rules: Rule[] }\n```\n\n### 扩展运行器\n\n`#emitSessionEvent()` 将事件路由到：\n\n- 扩展监听器（`ExtensionRunner.emit({ type: \"ttsr_triggered\", rules })`）\n- 本地会话订阅者\n\n### 钩子与自定义工具类型\n\n- 扩展 API 暴露 `on(\"ttsr_triggered\", ...)`\n- 钩子 API 暴露 `on(\"ttsr_triggered\", ...)`\n- 自定义工具接收 `onSession({ reason: \"ttsr_triggered\", rules })`\n\n### 交互模式渲染差异\n\n交互模式使用 `session.isTtsrAbortPending` 在 TTSR 中断期间抑制将已中止的助手停止原因显示为可见的失败，并在事件到达时渲染 `TtsrNotificationComponent`。\n\n## 7. 持久化与恢复状态（当前实现）\n\n`SessionManager` 完全支持已注入规则持久化的架构：\n\n- 条目类型：`ttsr_injection`\n- 追加 API：`appendTtsrInjection(ruleNames)`\n- 查询 API：`getInjectedTtsrRules()`\n- 上下文重建包含 `SessionContext.injectedTtsrRules`\n\n`TtsrManager` 也支持通过 `restoreInjected(ruleNames)` 进行恢复。\n\n### 当前接线状态\n\n在当前运行时路径中：\n\n- `AgentSession` 在 TTSR 触发时不会追加 `ttsr_injection` 条目。\n- `createAgentSession()` 不会将 `existingSession.injectedTtsrRules` 恢复回 `ttsrManager`。\n\n实际效果：已注入规则的抑制在活跃进程的内存中被执行，但目前通过此路径不会在会话重载/恢复之间进行持久化/恢复。\n\n## 8. 竞态边界与顺序保证\n\n### 中止与重试回调\n\n- 从 TTSR 处理器角度看，中止是同步的（立即调用 `agent.abort()`）\n- 重试通过定时器延迟（`50ms`）\n- 扩展通知是异步的，在中止/重试调度之前故意不等待\n\n### 同一流窗口中的多重匹配\n\n`check()` 返回当前所有匹配且符合条件的规则。它们在下一条重试消息中作为批次注入。\n\n### 中止与继续之间\n\n在定时器窗口期间，状态可能发生变化（用户中断、模式操作、额外事件）。重试调用是尽力而为的：`agent.continue().catch(() => {})` 会吞掉后续错误。\n\n## 9. 边界情况总结\n\n- 无效的 `ttsr_trigger` 正则表达式：以警告跳过；其他规则继续运行。\n- 能力层的重复规则名称：优先级较低的重复项在注册前被遮蔽。\n- 管理器层的重复名称：第二次注册被忽略。\n- `contextMode: \"keep\"`：部分违规输出可能在提醒重试之前保留在上下文中。\n- 间隔重复依赖于 `turn_end` 时的轮次计数递增；轮次中间的数据块不会推进间隔计数器。\n",
	"zh-cn/tui/theme.md": "---\ntitle: 主题参考\ndescription: TUI 主题参考，包含颜色令牌、字体设置和主题自定义。\nsidebar:\n  order: 3\n  label: 主题\ni18n:\n  sourceHash: 7e962a7da157\n  translator: machine\n---\n\n# 主题参考\n\n本文档描述了 coding-agent 中主题系统的工作原理：模式定义、加载机制、运行时行为和故障模式。\n\n## 主题系统控制的内容\n\n主题系统驱动以下内容：\n\n- TUI 中使用的前景/背景颜色令牌\n- markdown 样式适配器（`getMarkdownTheme()`）\n- 选择器/编辑器/设置列表适配器（`getSelectListTheme()`、`getEditorTheme()`、`getSettingsListTheme()`）\n- 符号预设 + 符号覆盖（`unicode`、`nerd`、`ascii`）\n- 原生高亮器使用的语法高亮颜色（`@f5-sales-demo/pi-natives`）\n- 状态栏分段颜色\n\n主要实现：`src/modes/theme/theme.ts`。\n\n## 主题 JSON 结构\n\n主题文件是 JSON 对象，根据 `theme.ts` 中的运行时模式（`ThemeJsonSchema`）进行验证，并镜像到 `src/modes/theme/theme-schema.json`。\n\n顶层字段：\n\n- `name`（必需）\n- `colors`（必需；所有颜色令牌均为必需）\n- `vars`（可选；可复用的颜色变量）\n- `export`（可选；HTML 导出颜色）\n- `symbols`（可选）\n  - `preset`（可选：`unicode | nerd | ascii`）\n  - `overrides`（可选：`SymbolKey` 的键/值覆盖）\n\n颜色值接受：\n\n- 十六进制字符串（`\"#RRGGBB\"`）\n- 256 色索引（`0..255`）\n- 变量引用字符串（通过 `vars` 解析）\n- 空字符串（`\"\"`）表示终端默认值（前景 `\\x1b[39m`，背景 `\\x1b[49m`）\n\n## 必需的颜色令牌（当前）\n\n以下所有令牌在 `colors` 中均为必需。\n\n### 核心文本和边框 (11)\n\n`accent`、`border`、`borderAccent`、`borderMuted`、`success`、`error`、`warning`、`muted`、`dim`、`text`、`thinkingText`\n\n### 背景块 (7)\n\n`selectedBg`、`userMessageBg`、`customMessageBg`、`toolPendingBg`、`toolSuccessBg`、`toolErrorBg`、`statusLineBg`\n\n### 消息/工具文本 (5)\n\n`userMessageText`、`customMessageText`、`customMessageLabel`、`toolTitle`、`toolOutput`\n\n### Markdown (10)\n\n`mdHeading`、`mdLink`、`mdLinkUrl`、`mdCode`、`mdCodeBlock`、`mdCodeBlockBorder`、`mdQuote`、`mdQuoteBorder`、`mdHr`、`mdListBullet`\n\n### 工具差异 + 语法高亮 (12)\n\n`toolDiffAdded`、`toolDiffRemoved`、`toolDiffContext`、\n`syntaxComment`、`syntaxKeyword`、`syntaxFunction`、`syntaxVariable`、`syntaxString`、`syntaxNumber`、`syntaxType`、`syntaxOperator`、`syntaxPunctuation`\n\n### 模式/思考边框 (8)\n\n`thinkingOff`、`thinkingMinimal`、`thinkingLow`、`thinkingMedium`、`thinkingHigh`、`thinkingXhigh`、`bashMode`、`pythonMode`\n\n### 状态栏分段颜色 (14)\n\n`statusLineSep`、`statusLineModel`、`statusLinePath`、`statusLineGitClean`、`statusLineGitDirty`、`statusLineContext`、`statusLineSpend`、`statusLineStaged`、`statusLineDirty`、`statusLineUntracked`、`statusLineOutput`、`statusLineCost`、`statusLineSubagents`\n\n## 可选令牌\n\n### `export` 部分（可选）\n\n用于 HTML 导出主题辅助功能：\n\n- `export.pageBg`\n- `export.cardBg`\n- `export.infoBg`\n\n如果省略，导出代码会从已解析的主题颜色中派生默认值。\n\n### `symbols` 部分（可选）\n\n- `symbols.preset` 设置主题级别的默认符号集。\n- `symbols.overrides` 可以覆盖单个 `SymbolKey` 值。\n\n运行时优先级：\n\n1. 设置中的 `symbolPreset` 覆盖（如果已设置）\n2. 主题 JSON 中的 `symbols.preset`\n3. 回退到 `\"unicode\"`\n\n无效的覆盖键会被忽略并记录日志（`logger.debug`）。\n\n## 内置主题与自定义主题来源\n\n主题查找顺序（`loadThemeJson`）：\n\n1. 内置嵌入式主题（编译到 `defaultThemes` 中的 `defaults/xcsh-dark.json` 和 `defaults/xcsh-light.json`）\n2. 自定义主题文件：`<customThemesDir>/<name>.json`\n\n自定义主题目录来自 `getCustomThemesDir()`：\n\n- 默认值：`~/.xcsh/agent/themes`\n- 可通过 `PI_CODING_AGENT_DIR` 覆盖（`$PI_CODING_AGENT_DIR/themes`）\n\n`getAvailableThemes()` 返回合并后的内置 + 自定义名称，已排序，名称冲突时内置主题优先。\n\n## 加载、验证和解析\n\n对于自定义主题文件：\n\n1. 读取 JSON\n2. 解析 JSON\n3. 根据 `ThemeJsonSchema` 验证\n4. 递归解析 `vars` 引用\n5. 根据终端能力模式将解析后的值转换为 ANSI\n\n验证行为：\n\n- 缺少必需的颜色令牌：显示明确的分组错误消息\n- 错误的令牌类型/值：带有 JSON 路径的验证错误\n- 未知主题文件：`Theme not found: <name>`\n\n变量引用行为：\n\n- 支持嵌套引用\n- 缺少变量引用时抛出异常\n- 循环引用时抛出异常\n\n## 终端颜色模式行为\n\n颜色模式检测（`detectColorMode`）：\n\n- `COLORTERM=truecolor|24bit` => 真彩色\n- `WT_SESSION` => 真彩色\n- `TERM` 为 `dumb`、`linux` 或空 => 256 色\n- 其他情况 => 真彩色\n\n转换行为：\n\n- 十六进制 -> `Bun.color(..., \"ansi-16m\" | \"ansi-256\")`\n- 数值 -> `38;5` / `48;5` ANSI\n- `\"\"` -> 默认前景/背景重置\n\n## 运行时切换行为\n\n### 初始主题（`initTheme`）\n\n`main.ts` 使用以下设置初始化主题：\n\n- `symbolPreset`\n- `colorBlindMode`\n- `theme.dark`\n- `theme.light`\n\n自动主题槽位选择使用 `COLORFGBG` 背景检测：\n\n- 从 `COLORFGBG` 解析背景索引\n- `< 8` => 暗色槽位（`theme.dark`）\n- `>= 8` => 亮色槽位（`theme.light`）\n- 解析失败 => 暗色槽位\n\n设置模式中的当前默认值：\n\n- `theme.dark = \"xcsh-dark\"`\n- `theme.light = \"xcsh-light\"`\n- `symbolPreset = \"unicode\"`\n- `colorBlindMode = false`\n\n### 显式切换（`setTheme`）\n\n- 加载选定的主题\n- 更新全局 `theme` 单例\n- 可选地启动文件监视器\n- 触发 `onThemeChange` 回调\n\n失败时：\n\n- 回退到内置 `dark` 主题\n- 返回 `{ success: false, error }`\n\n### 预览切换（`previewTheme`）\n\n- 将临时预览主题应用到全局 `theme`\n- **不会**自行更改持久化设置\n- 返回成功/错误，不进行回退替换\n\n设置界面使用此功能进行实时预览，取消时恢复之前的主题。\n\n## 文件监视器和热重载\n\n当文件监视器启用时（`setTheme(..., true)` / 交互式初始化）：\n\n- 仅监视自定义文件路径 `<customThemesDir>/<currentTheme>.json`\n- 内置主题实际上不会被监视\n- 文件 `change`：尝试重新加载（已防抖）\n- 文件 `rename`/删除：回退到 `dark`，关闭监视器\n\n自动模式还会安装 `SIGWINCH` 监听器，当终端状态变化时可以重新评估暗色/亮色槽位映射。\n\n## 色盲模式行为\n\n`colorBlindMode` 在运行时仅更改一个令牌：\n\n- `toolDiffAdded` 会进行 HSV 调整（绿色偏移向蓝色）\n- 仅当解析后的值为十六进制字符串时才应用调整\n\n其他令牌不受影响。\n\n## 主题设置的持久化位置\n\n与主题相关的设置由 `Settings` 持久化到全局配置 YAML 中：\n\n- 路径：`<agentDir>/config.yml`\n- 默认代理目录：`~/.xcsh/agent`\n- 有效默认文件：`~/.xcsh/agent/config.yml`\n\n持久化的键：\n\n- `theme.dark`\n- `theme.light`\n- `symbolPreset`\n- `colorBlindMode`\n\n存在旧版迁移功能：旧的扁平 `theme: \"name\"` 格式会根据亮度检测迁移为嵌套的 `theme.dark` 或 `theme.light`。\n\n## 创建自定义主题（实践）\n\n1. 在自定义主题目录中创建文件，例如 `~/.xcsh/agent/themes/my-theme.json`。\n2. 包含 `name`、可选的 `vars` 以及**所有必需的** `colors` 令牌。\n3. 可选地包含 `symbols` 和 `export`。\n4. 根据您想要的自动槽位，在设置中选择主题（`Display -> Dark theme` 或 `Display -> Light theme`）。\n\n最小骨架。`colors` 中的每个键都是必需的——运行时验证器\n（`additionalProperties: false`）会拒绝缺少的键和未知的键。\n有关已发布的参考实现，请参阅\n[`packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json)\n和 [`xcsh-light.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-light.json)。\n\n状态栏有两个并行的颜色系统，记录在 issue #242 中：\n\n- 十六进制文本颜色（`statusLinePath`、`statusLineGitClean`、`statusLineGitDirty`、\n  `statusLineStaged`、`statusLineDirty`、`statusLineUntracked`）驱动非 powerline\n  渲染。\n- 256 色调色板索引（`statusLine<Segment>Bg` / `statusLine<Segment>Fg`）\n  驱动 powerline 分段填充。它们独立于上述十六进制键——\n  两者都必须设置。\n\n```json\n{\n  \"name\": \"my-theme\",\n  \"vars\": {\n    \"accent\": \"#7aa2f7\",\n    \"muted\": 244\n  },\n  \"colors\": {\n    \"accent\": \"accent\",\n    \"chromeAccent\": \"accent\",\n    \"spinnerAccent\": \"accent\",\n    \"contentAccent\": \"muted\",\n    \"border\": \"#4c566a\",\n    \"borderAccent\": \"accent\",\n    \"borderMuted\": \"muted\",\n    \"success\": \"#9ece6a\",\n    \"error\": \"#f7768e\",\n    \"warning\": \"#e0af68\",\n    \"muted\": \"muted\",\n    \"dim\": 240,\n    \"gutterSuccess\": \"#7dcfff\",\n    \"gutterWarning\": \"#e0af68\",\n    \"text\": \"\",\n    \"thinkingText\": \"muted\",\n\n    \"selectedBg\": \"#2a2f45\",\n    \"userMessageBg\": \"#1f2335\",\n    \"userMessageText\": \"\",\n    \"customMessageBg\": \"#24283b\",\n    \"customMessageText\": \"\",\n    \"customMessageLabel\": \"accent\",\n    \"toolPendingBg\": \"#1f2335\",\n    \"toolSuccessBg\": \"#1f2d2a\",\n    \"toolErrorBg\": \"#2d1f2a\",\n    \"toolTitle\": \"\",\n    \"toolOutput\": \"muted\",\n\n    \"mdHeading\": \"accent\",\n    \"mdLink\": \"accent\",\n    \"mdLinkUrl\": \"muted\",\n    \"mdCode\": \"#c0caf5\",\n    \"mdCodeBlock\": \"#c0caf5\",\n    \"mdCodeBlockBorder\": \"muted\",\n    \"mdQuote\": \"muted\",\n    \"mdQuoteBorder\": \"muted\",\n    \"mdHr\": \"muted\",\n    \"mdListBullet\": \"accent\",\n\n    \"toolDiffAdded\": \"#9ece6a\",\n    \"toolDiffRemoved\": \"#f7768e\",\n    \"toolDiffContext\": \"muted\",\n\n    \"syntaxComment\": \"#565f89\",\n    \"syntaxKeyword\": \"#bb9af7\",\n    \"syntaxFunction\": \"#7aa2f7\",\n    \"syntaxVariable\": \"#c0caf5\",\n    \"syntaxString\": \"#9ece6a\",\n    \"syntaxNumber\": \"#ff9e64\",\n    \"syntaxType\": \"#2ac3de\",\n    \"syntaxOperator\": \"#89ddff\",\n    \"syntaxPunctuation\": \"#9aa5ce\",\n    \"syntaxControl\": \"#bb9af7\",\n\n    \"thinkingOff\": 240,\n    \"thinkingMinimal\": 244,\n    \"thinkingLow\": \"#7aa2f7\",\n    \"thinkingMedium\": \"#2ac3de\",\n    \"thinkingHigh\": \"#bb9af7\",\n    \"thinkingXhigh\": \"#f7768e\",\n\n    \"bashMode\": \"#2ac3de\",\n    \"pythonMode\": \"#bb9af7\",\n\n    \"statusLineBg\": \"#16161e\",\n    \"statusLineSep\": 240,\n    \"statusLineModel\": \"#bb9af7\",\n    \"statusLinePath\": \"#7aa2f7\",\n    \"statusLineGitClean\": \"#9ece6a\",\n    \"statusLineGitDirty\": \"#e0af68\",\n    \"statusLineContext\": \"#2ac3de\",\n    \"statusLineSpend\": \"#7dcfff\",\n    \"statusLineStaged\": \"#9ece6a\",\n    \"statusLineDirty\": \"#e0af68\",\n    \"statusLineUntracked\": \"#f7768e\",\n    \"statusLineOutput\": \"#c0caf5\",\n    \"statusLineCost\": \"#ff9e64\",\n    \"statusLineSubagents\": \"#bb9af7\",\n\n    \"statusLineOsIconBg\": 7,\n    \"statusLineOsIconFg\": 232,\n    \"statusLinePathBg\": 4,\n    \"statusLinePathFg\": 254,\n    \"statusLineGitCleanBg\": 2,\n    \"statusLineGitCleanFg\": 0,\n    \"statusLineGitDirtyBg\": 3,\n    \"statusLineGitDirtyFg\": 0,\n    \"statusLineGitStagedBg\": 64,\n    \"statusLineGitStagedFg\": 0,\n    \"statusLineGitUntrackedBg\": 39,\n    \"statusLineGitUntrackedFg\": 0,\n    \"statusLineGitConflictBg\": 1,\n    \"statusLineGitConflictFg\": 7,\n    \"statusLinePlanModeBg\": 236,\n    \"statusLinePlanModeFg\": 117,\n    \"statusLineProfileXcshBg\": \"accent\",\n    \"statusLineProfileXcshFg\": 231\n  }\n}\n```\n\n## 测试自定义主题\n\n使用以下工作流程：\n\n1. 启动交互模式（启动时自动启用文件监视器）。\n2. 打开设置并预览主题值（实时 `previewTheme`）。\n3. 对于自定义主题文件，在运行时编辑 JSON 并确认保存后自动重载。\n4. 测试关键界面：\n   - markdown 渲染\n   - 工具块（待处理/成功/错误）\n   - 差异渲染（新增/删除/上下文）\n   - 状态栏可读性\n   - 思考级别边框变化\n   - bash/python 模式边框颜色\n5. 如果您的主题依赖于字形宽度/外观，请验证两种符号预设。\n\n## 实际约束和注意事项\n\n- 自定义主题需要所有 `colors` 令牌。\n- `export` 和 `symbols` 是可选的。\n- 主题 JSON 中的 `$schema` 仅为参考信息；运行时验证由代码中编译的 TypeBox 模式强制执行。\n- `setTheme` 失败时回退到 `dark`；`previewTheme` 失败时不会替换当前主题。\n- 文件监视器重载错误时会保持当前已加载的主题，直到成功重载或触发回退路径。\n",
	"zh-cn/tui/tree.md": "---\ntitle: Tree 命令参考\ndescription: /tree 命令参考，用于可视化会话历史和对话分支。\nsidebar:\n  order: 4\n  label: /tree 命令\ni18n:\n  sourceHash: ee0e412fe993\n  translator: machine\n---\n\n# `/tree` 命令参考\n\n`/tree` 打开交互式**会话树**导航器。它允许你跳转到当前会话文件中的任意条目，并从该点继续。\n\n这是文件内的叶节点移动，不是新的会话导出。\n\n## `/tree` 的功能\n\n- 从当前会话条目构建树结构（`SessionManager.getTree()`）\n- 打开 `TreeSelectorComponent`，支持键盘导航、过滤和搜索\n- 选择后调用 `AgentSession.navigateTree(targetId, { summarize, customInstructions })`\n- 从新的叶节点路径重建可见聊天\n- 选择用户/自定义消息时可选地预填充编辑器文本\n\n主要实现：\n\n- `src/modes/controllers/input-controller.ts`（`/tree`、快捷键绑定、双击 Escape 行为）\n- `src/modes/controllers/selector-controller.ts`（树 UI 启动 + 摘要提示流程）\n- `src/modes/components/tree-selector.ts`（导航、过滤、搜索、标签、渲染）\n- `src/session/agent-session.ts`（`navigateTree` 叶节点切换 + 可选摘要）\n- `src/session/session-manager.ts`（`getTree`、`branch`、`branchWithSummary`、`resetLeaf`、标签持久化）\n\n## 如何打开\n\n以下任一方式都可打开相同的选择器：\n\n- `/tree`\n- 配置的快捷键动作 `tree`\n- 当编辑器为空时双击 Escape，且 `doubleEscapeAction = \"tree\"`（默认值）\n- 当 `doubleEscapeAction = \"tree\"` 时使用 `/branch`（路由到树选择器而非仅用户分支选择器）\n\n## 树 UI 模型\n\n树从会话条目的父指针（`id` / `parentId`）渲染而来。\n\n- 子节点按时间戳升序排列（较旧的在前，较新的在后）\n- 活动分支（从根到当前叶节点的路径）用圆点标记\n- 标签（如果存在）在节点文本前渲染为 `[label]`\n- 如果存在多个根节点（孤立/断裂的父链），它们显示在虚拟分支根节点下\n\n```text\nExample tree view (active path marked with •):\n\n├─ user: \"Start task\"\n│  └─ assistant: \"Plan\"\n│     ├─ • user: \"Try approach A\"\n│     │  └─ • assistant: \"A result\"\n│     │     └─ • [milestone] user: \"Continue A\"\n│     └─ user: \"Try approach B\"\n│        └─ assistant: \"B result\"\n```\n\n选择器围绕当前选择重新居中，最多显示：\n\n- `max(5, floor(terminalHeight / 2))` 行\n\n## 树选择器内的快捷键\n\n- `Up` / `Down`：移动选择（循环）\n- `Left` / `Right`：向上翻页 / 向下翻页\n- `Enter`：选择节点\n- `Esc`：如果搜索处于活动状态则清除搜索；否则关闭选择器\n- `Ctrl+C`：关闭选择器\n- `Type`：追加到搜索查询\n- `Backspace`：删除搜索字符\n- `Shift+L`：编辑/清除所选条目的标签\n- `Ctrl+O`：向前切换过滤器\n- `Shift+Ctrl+O`：向后切换过滤器\n- `Alt+D/T/U/L/A`：直接跳转到特定过滤模式\n\n## 过滤器和搜索语义\n\n过滤模式（`TreeList`）：\n\n1. `default`\n2. `no-tools`\n3. `user-only`\n4. `labeled-only`\n5. `all`\n\n### `default`\n\n显示大多数对话节点，但隐藏事务性条目类型：\n\n- `label`\n- `custom`\n- `model_change`\n- `thinking_level_change`\n\n### `no-tools`\n\n与 `default` 相同，但额外隐藏 `toolResult` 消息。\n\n### `user-only`\n\n仅显示角色为 `user` 的 `message` 条目。\n\n### `labeled-only`\n\n仅显示当前解析为有标签的条目。\n\n### `all`\n\n会话树中的所有内容，包括事务性/自定义条目。\n\n### 仅包含工具调用的助手节点行为\n\n仅包含**工具调用**（无文本）的助手消息在所有过滤视图中默认隐藏，除非：\n\n- 消息为错误/已中止（`stopReason` 不是 `stop`/`toolUse`），或\n- 它是当前叶节点（始终保持可见）\n\n### 搜索行为\n\n- 查询按空格分词\n- 匹配不区分大小写\n- 所有词条必须匹配（AND 语义）\n- 可搜索文本包括标签、角色和特定类型的内容（消息文本、分支摘要文本、自定义类型、工具命令片段等）\n\n## 选择结果（重要）\n\n`navigateTree` 根据所选条目类型计算新的叶节点行为：\n\n### 选择 `user` 消息\n\n- 新叶节点变为所选条目的 `parentId`\n- 如果父节点为 `null`（根用户消息），叶节点重置到根（`resetLeaf()`）\n- 所选消息文本被复制到编辑器以供编辑/重新提交\n\n### 选择 `custom_message`\n\n- 叶节点规则与用户消息相同（`parentId`）\n- 文本内容被提取并复制到编辑器\n\n### 选择非用户节点（助手/工具/摘要/压缩/自定义事务性条目等）\n\n- 新叶节点变为所选节点 id\n- 编辑器不会预填充\n\n### 选择当前叶节点\n\n- 无操作；选择器关闭并显示 \"Already at this point\"\n\n```text\nSelection decision (simplified):\n\nselected node\n   │\n   ├─ is current leaf? ── yes ──> close selector (no-op)\n   │\n   ├─ is user/custom_message? ── yes ──> leaf := parentId (or resetLeaf for root)\n   │                                     + prefill editor text\n   │\n   └─ otherwise ──> leaf := selected node id\n                    + no editor prefill\n```\n\n## 切换时的摘要流程\n\n摘要提示由 `branchSummary.enabled` 控制（默认值：`false`）。\n\n启用后，选择节点后 UI 会询问：\n\n- `No summary`\n- `Summarize`\n- `Summarize with custom prompt`\n\n流程详情：\n\n- 在摘要提示中按 Escape 会重新打开树选择器\n- 取消自定义提示会返回到摘要选择循环\n- 在摘要生成期间，UI 显示加载动画并将 `Esc` 绑定到 `abortBranchSummary()`\n- 如果摘要生成中止，树选择器重新打开且不应用任何移动\n\n`navigateTree` 内部机制：\n\n- 从旧叶节点到公共祖先收集被放弃分支的条目\n- 发出 `session_before_tree`（扩展可以取消或注入摘要）\n- 仅在请求且需要时使用默认摘要器\n- 通过以下方式应用移动：\n  - 当存在摘要时使用 `branchWithSummary(...)`\n  - 非根移动且无摘要时使用 `branch(newLeafId)`\n  - 根移动且无摘要时使用 `resetLeaf()`\n- 用重建的会话上下文替换代理对话\n- 发出 `session_tree`\n\n注意：如果用户请求摘要但没有可摘要的内容，导航将继续进行而不创建摘要条目。\n\n## 标签\n\n树 UI 中的标签编辑调用 `appendLabelChange(targetId, label)`。\n\n- 非空标签设置/更新解析后的标签\n- 空标签清除标签\n- 标签以仅追加的 `label` 条目形式存储\n- 树节点显示解析后的标签状态，而非原始标签条目历史\n\n## `/tree` 与相关操作的对比\n\n| 操作 | 范围 | 结果 |\n|---|---|---|\n| `/tree` | 当前会话文件 | 将叶节点移动到选定点（同一文件） |\n| `/branch` | 通常从当前会话文件到新会话文件 | 默认从选定的**用户**消息分支到新会话文件；如果 `doubleEscapeAction = \"tree\"`，`/branch` 会打开树导航 UI |\n| `/fork` | 整个当前会话 | 将会话复制到新的持久化会话文件 |\n| `/resume` | 会话列表 | 切换到另一个会话文件 |\n\n关键区别：`/tree` 是单个会话文件内的导航/重定位工具。`/branch`、`/fork` 和 `/resume` 都会更改会话文件上下文。\n\n## 操作工作流\n\n### 从较早的用户提示重新运行而不丢失当前分支\n\n1. `/tree`\n2. 搜索/选择较早的用户消息\n3. 选择 `No summary`（或根据需要生成摘要）\n4. 在编辑器中编辑预填充的文本\n5. 提交\n\n效果：在同一会话文件内从选定点生长出新分支。\n\n### 带上下文标记离开当前分支\n\n1. 启用 `branchSummary.enabled`\n2. `/tree` 并选择目标节点\n3. 选择 `Summarize`（或自定义提示）\n\n效果：在目标位置继续之前追加一个 `branch_summary` 条目。\n\n### 检查隐藏的事务性条目\n\n1. `/tree`\n2. 按 `Alt+A`（全部）\n3. 搜索 `model`、`thinking`、`custom` 或标签\n\n效果：检查完整的内部时间线，而不仅仅是对话节点。\n\n### 为后续跳转标记关键节点\n\n1. `/tree`\n2. 移动到条目\n3. `Shift+L` 并设置标签\n4. 之后使用 `Alt+L`（`labeled-only`）快速跳转\n\n效果：在持久化的分支地标之间快速导航。\n",
	"zh-cn/tui/tui-runtime-internals.md": "---\ntitle: TUI 运行时内部机制\ndescription: 终端 UI 运行时内部机制，涵盖渲染管线、输入处理和状态管理。\nsidebar:\n  order: 2\n  label: 运行时内部机制\ni18n:\n  sourceHash: cc8f7dcce46a\n  translator: machine\n---\n\n# TUI 运行时内部机制\n\n本文档描述了在交互模式下，从终端输入到渲染输出的非主题运行时路径。重点关注 `packages/tui` 中的行为及其与 `packages/coding-agent` 控制器的集成方式。\n\n## 运行时层次与职责划分\n\n- **`packages/tui` 引擎**：终端生命周期、stdin 规范化、焦点路由、渲染调度、差分绘制、叠加层合成、硬件光标定位。\n- **`packages/coding-agent` 交互模式**：构建组件树、绑定编辑器回调和键位映射、响应 agent/会话事件，并将领域状态（流式传输、工具执行、重试、计划模式）转换为 UI 组件。\n\n边界规则：TUI 引擎与消息内容无关。它只关注 `Component.render(width)`、`handleInput(data)`、焦点和叠加层。Agent 语义保留在交互控制器中。\n\n## 实现文件\n\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/components/custom-editor.ts`](../../packages/coding-agent/src/modes/components/custom-editor.ts)\n- [`../../tui/src/tui.ts`](../../packages/tui/src/tui.ts)\n- [`../../tui/src/terminal.ts`](../../packages/tui/src/terminal.ts)\n- [`../../tui/src/editor-component.ts`](../../packages/tui/src/editor-component.ts)\n- [`../../tui/src/stdin-buffer.ts`](../../packages/tui/src/stdin-buffer.ts)\n- [`../../tui/src/components/loader.ts`](../../packages/tui/src/components/loader.ts)\n\n## 启动与组件树组装\n\n`InteractiveMode` 构造 `TUI(new ProcessTerminal(), showHardwareCursor)` 并创建持久化容器：\n\n- `chatContainer`\n- `pendingMessagesContainer`\n- `statusContainer`\n- `todoContainer`\n- `statusLine`\n- `editorContainer`（包含 `CustomEditor`）\n\n`init()` 按上述顺序连接组件树，聚焦编辑器，通过 `InputController` 注册输入处理器，启动 TUI，并请求强制渲染。\n\n强制渲染（`requestRender(true)`）会在重绘之前重置前一行缓存和光标记录。\n\n## 终端生命周期与 stdin 规范化\n\n`ProcessTerminal.start()`：\n\n1. 启用原始模式和括号粘贴模式。\n2. 附加窗口大小变化处理器。\n3. 创建 `StdinBuffer`，将不完整的转义序列片段拆分为完整序列。\n4. 查询 Kitty 键盘协议支持（`CSI ? u`），如果支持则启用协议标志。\n5. 在 Windows 上，尝试通过 `kernel32` 模式标志启用 VT 输入。\n\n`StdinBuffer` 行为：\n\n- 缓冲碎片化的转义序列（CSI/OSC/DCS/APC/SS3）。\n- 仅在序列完整或超时刷新时触发 `data` 事件。\n- 检测括号粘贴并以原始粘贴文本触发 `paste` 事件。\n\n这可以防止不完整的转义序列片段被错误解释为普通按键。\n\n## 输入路由与焦点模型\n\n输入路径：\n\n`stdin -> ProcessTerminal -> StdinBuffer -> TUI.#handleInput -> focusedComponent.handleInput`\n\n路由详情：\n\n1. TUI 首先运行已注册的输入监听器（`addInputListener`），允许消费/转换行为。\n2. TUI 在组件分发之前处理全局调试快捷键（`shift+ctrl+d`）。\n3. 如果聚焦组件所属的叠加层当前已隐藏/不可见，TUI 会将焦点重新分配给下一个可见的叠加层或之前保存的叠加层前焦点。\n4. 除非聚焦组件设置了 `wantsKeyRelease = true`，否则按键释放事件会被过滤。\n5. 分发后，TUI 调度渲染。\n\n`setFocus()` 还会切换 `Focusable.focused` 状态，该状态控制组件是否为硬件光标定位发出 `CURSOR_MARKER`。\n\n## 按键处理分工：编辑器与控制器\n\n`CustomEditor` 首先拦截高优先级组合键（escape、ctrl-c/d/z、ctrl-v、ctrl-p 变体、ctrl-t、alt-up、扩展自定义键），其余委托给基础 `Editor` 行为（文本编辑、历史记录、自动补全、光标移动）。\n\n随后 `InputController.setupKeyHandlers()` 将编辑器回调绑定到模式操作：\n\n- `Escape` 取消/退出模式\n- 双击 `Ctrl+C` 或空编辑器 `Ctrl+D` 关闭\n- `Ctrl+Z` 挂起/恢复\n- 斜杠命令和选择器快捷键\n- 后续/出队切换和展开切换\n\n这使得按键解析/编辑器机制保留在 `packages/tui` 中，而模式语义保留在 coding-agent 控制器中。\n\n## 渲染循环与差分策略\n\n`TUI.requestRender()` 使用 `process.nextTick` 进行去抖，每个 tick 最多执行一次渲染。同一轮中的多个状态变更会被合并。\n\n`#doRender()` 管线：\n\n1. 将根组件树渲染为 `newLines`。\n2. 合成可见的叠加层（如果有）。\n3. 从可见视口行中提取并剥离 `CURSOR_MARKER`。\n4. 为非图像行追加段重置后缀。\n5. 选择全量重绘或差分补丁：\n   - 首帧\n   - 宽度变化\n   - 启用 `clearOnShrink` 且无叠加层时的收缩\n   - 前一视口上方的编辑\n6. 差分更新时，仅修补已变更的行范围，并在需要时清除过期的尾部行。\n7. 重新定位硬件光标以支持 IME。\n\n渲染写入使用同步输出模式（`CSI ? 2026 h/l`）以减少闪烁/撕裂。\n\n## 渲染安全约束\n\n`TUI` 中的关键安全检查：\n\n- 非图像渲染行不得超过终端宽度；溢出会抛出异常并写入崩溃诊断信息。\n- 叠加层合成包含防御性截断和合成后宽度验证。\n- 宽度变化强制全量重绘，因为换行语义会改变。\n- 光标位置在移动前会被限制在有效范围内。\n\n这些约束是运行时强制执行的，而不仅仅是约定。\n\n## 窗口大小变化处理\n\n窗口大小变化事件从 `ProcessTerminal` 事件驱动传递到 `TUI.requestRender()`。\n\n影响：\n\n- 任何宽度变化都会触发全量重绘。\n- 视口/顶部跟踪（`#previousViewportTop`、`#maxLinesRendered`）避免了内容或终端尺寸变化时无效的相对光标计算。\n- 叠加层可见性可能依赖终端尺寸（`OverlayOptions.visible`）；当叠加层在窗口大小变化后变为不可见时，焦点会被修正。\n\n## 流式传输与增量 UI 更新\n\n`EventController` 订阅 `AgentSessionEvent` 并增量更新 UI：\n\n- `agent_start`：在 `statusContainer` 中启动加载器。\n- `message_start` assistant：创建 `streamingComponent` 并挂载。\n- `message_update`：更新流式助手内容；在工具调用出现时创建/更新工具执行组件。\n- `tool_execution_update/end`：更新工具结果组件和完成状态。\n- `message_end`：完成助手流，处理中止/错误注解，在正常停止时标记待处理的工具参数完成。\n- `agent_end`：停止加载器，清除临时流状态，刷新延迟的模型切换，如果处于后台则发出完成通知。\n\n读取工具分组是有意为有状态的（`#lastReadGroup`），将连续的读取工具调用合并为一个视觉块，直到出现非读取中断。\n\n## 状态栏与加载器编排\n\n状态栏职责划分：\n\n- `statusContainer` 持有临时加载器（`loadingAnimation`、`autoCompactionLoader`、`retryLoader`）。\n- `statusLine` 渲染持久状态/钩子/计划指示器，并驱动编辑器顶部边框更新。\n\n加载器行为：\n\n- `Loader` 通过定时器每 80ms 更新一次，每帧请求渲染。\n- 在自动压缩和自动重试期间，Escape 处理器会被临时覆盖以取消这些操作。\n- 在结束/取消路径上，控制器恢复之前的 Escape 处理器并停止/清除加载器组件。\n\n## 模式切换与后台运行\n\n### Bash/Python 输入模式\n\n输入文本前缀切换编辑器边框模式标志：\n\n- `!` -> bash 模式\n- `$`（非模板字面量前缀）-> python 模式\n\nEscape 通过清除编辑器文本和恢复边框颜色退出非活动模式；当执行处于活动状态时，Escape 会中止正在运行的任务。\n\n### 计划模式\n\n`InteractiveMode` 跟踪计划模式标志、状态栏状态、活动工具和模型切换。进入/退出会更新会话模式条目和状态/UI 状态，包括在流式传输活动时的延迟模型切换。\n\n### 挂起/恢复（`Ctrl+Z`）\n\n`InputController.handleCtrlZ()`：\n\n1. 注册一次性 `SIGCONT` 处理器以重启 TUI 并强制渲染。\n2. 挂起前停止 TUI。\n3. 向进程组发送 `SIGTSTP`。\n\n### 后台模式（`/background` 或 `/bg`）\n\n`handleBackgroundCommand()`：\n\n- 空闲时拒绝执行。\n- 将工具 UI 上下文切换为非交互式（`hasUI=false`），使交互式 UI 工具快速失败。\n- 停止加载器/状态栏并取消订阅前台事件处理器。\n- 订阅后台事件处理器（主要等待 `agent_end`）。\n- 停止 TUI 并发送 `SIGTSTP`（POSIX 作业控制路径）。\n\n在后台收到 `agent_end` 且无排队工作时，控制器发送完成通知并关闭。\n\n## 取消路径\n\n主要取消输入：\n\n- 活动流加载器期间按 `Escape`：将排队消息恢复到编辑器并中止 agent。\n- bash/python 执行期间按 `Escape`：中止正在运行的命令。\n- 自动压缩/重试期间按 `Escape`：通过临时 Escape 处理器调用专用中止方法。\n- 单次按 `Ctrl+C`：清除编辑器；500ms 内双击：关闭。\n\n取消是状态条件相关的；同一按键可能意味着中止、模式退出、选择器触发或空操作，取决于运行时状态。\n\n## 事件驱动与节流行为\n\n事件驱动更新：\n\n- Agent 会话事件（`EventController`）\n- 按键输入回调（`InputController`）\n- 终端窗口大小变化回调\n- `InteractiveMode` 中的主题/分支监视器\n\n节流/去抖路径：\n\n- TUI 渲染按 tick 去抖（`requestRender` 合并）。\n- 加载器动画为固定间隔（80ms），每帧请求渲染。\n- 编辑器自动补全更新（在 `Editor` 内部）使用去抖计时器，减少输入过程中的重复计算。\n\n因此，运行时将事件驱动的状态转换与有界的渲染节奏相结合，在保持交互响应性的同时避免重绘风暴。\n",
	"zh-cn/tui/tui.md": "---\ntitle: 扩展和自定义工具的 TUI 集成\ndescription: 扩展、自定义工具和自定义渲染器的 TUI 集成契约。\nsidebar:\n  order: 1\n  label: 扩展集成\ni18n:\n  sourceHash: 47f8f2b2045e\n  translator: machine\n---\n\n# 扩展和自定义工具的 TUI 集成\n\n本文档涵盖了 `packages/coding-agent` 和 `packages/tui` 用于扩展 UI、自定义工具 UI 和自定义渲染器的**当前** TUI 契约。\n\n## 本子系统概述\n\n运行时包含两个层级：\n\n- **渲染引擎 (`packages/tui`)**：差异化终端渲染器、输入分发、焦点管理、覆盖层、光标定位。\n- **集成层 (`packages/coding-agent`)**：挂载扩展/自定义工具组件，绑定快捷键/主题，并恢复编辑器状态。\n\n## 各模式下的运行时行为\n\n| 模式 | `ctx.ui.custom(...)` 可用性 | 备注 |\n| --- | --- | --- |\n| 交互式 TUI | 支持 | 组件挂载在编辑器区域，获得焦点，必须调用 `done(result)` 来解析结果。 |\n| 后台/无头模式 | 非交互式 | UI 上下文为空操作（`hasUI === false`）。 |\n| RPC 模式 | 不支持 | `custom()` 返回 `Promise<never>`，不挂载 TUI 组件。 |\n\n如果您的扩展/工具可以在非交互模式下运行，请使用 `ctx.hasUI` / `pi.hasUI` 进行条件判断。\n\n## 核心组件契约 (`@f5-sales-demo/pi-tui`)\n\n`packages/tui/src/tui.ts` 定义了：\n\n```ts\nexport interface Component {\n  render(width: number): string[];\n  handleInput?(data: string): void;\n  wantsKeyRelease?: boolean;\n  invalidate(): void;\n}\n```\n\n`Focusable` 是独立的接口：\n\n```ts\nexport interface Focusable {\n  focused: boolean;\n}\n```\n\n光标行为使用 `CURSOR_MARKER`（而非 `getCursorPosition`）。获得焦点的组件在渲染文本中发出标记；`TUI` 提取该标记并定位硬件光标。\n\n## 渲染约束（终端安全）\n\n您的 `render(width)` 输出必须是终端安全的：\n\n1. **任何行都不得超过 `width`**。如果非图像行溢出，渲染器会抛出异常。\n2. **测量可视宽度**，而非字符串长度：使用 `visibleWidth()`。\n3. **使用 ANSI 感知方式截断/换行文本**，使用 `truncateToWidth()` / `wrapTextWithAnsi()`。\n4. **清理外部来源的制表符/内容**，使用 `replaceTabs()`（以及 coding-agent 渲染路径中的更高级清理器）。\n\n最简模式：\n\n```ts\nimport { replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\n\nrender(width: number): string[] {\n  return this.lines.map(line => truncateToWidth(replaceTabs(line), width));\n}\n```\n\n## 输入处理和快捷键绑定\n\n### 原始按键匹配\n\n使用 `matchesKey(data, \"...\")` 匹配导航键和组合键。\n\n### 遵循用户配置的应用快捷键\n\n扩展 UI 工厂函数接收一个 `KeybindingsManager`（交互模式），因此您可以遵循已映射的操作而非硬编码按键：\n\n```ts\nif (keybindings.matches(data, \"interrupt\")) {\n  done(undefined);\n  return;\n}\n```\n\n### 按键释放/重复事件\n\n除非您的组件设置了以下属性，否则按键释放事件会被过滤：\n\n```ts\nwantsKeyRelease = true;\n```\n\n然后根据需要使用 `isKeyRelease()` / `isKeyRepeat()`。\n\n## 焦点、覆盖层和光标\n\n- `TUI.setFocus(component)` 将输入路由到该组件。\n- `TUI` 中存在覆盖层 API（`showOverlay`、`OverlayHandle`），但扩展 `ctx.ui.custom` 在交互模式下的挂载目前直接替换编辑器组件区域。\n- `custom(..., options?: { overlay?: boolean })` 选项存在于扩展类型中；交互式扩展挂载目前忽略此选项。\n\n## 挂载点和返回契约\n\n## 1) 扩展 UI (`ExtensionUIContext`)\n\n当前签名（`extensibility/extensions/types.ts`）：\n\n```ts\ncustom<T>(\n  factory: (\n    tui: TUI,\n    theme: Theme,\n    keybindings: KeybindingsManager,\n    done: (result: T) => void,\n  ) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n  options?: { overlay?: boolean },\n): Promise<T>\n```\n\n交互模式下的行为（`extension-ui-controller.ts`）：\n\n- 保存编辑器文本。\n- 用您的组件替换编辑器组件。\n- 聚焦您的组件。\n- 当 `done(result)` 被调用时：调用 `component.dispose?.()`，恢复编辑器和文本，聚焦编辑器，解析 Promise。\n\n因此 `done(...)` 是完成流程所必需的。\n\n## 2) Hook/自定义工具 UI 上下文（旧版类型）\n\n`HookUIContext.custom` 在 hook/自定义工具类型中的类型签名为 `(tui, theme, done)`。\n底层交互实现使用 `(tui, theme, keybindings, done)` 调用工厂函数。JS 使用者可以使用额外的参数；类型层面的兼容性仍反映 3 参数的旧版签名。\n\n自定义工具通常通过工厂作用域的 `pi.ui` 对象使用相同的 UI 入口点，然后在正常的工具内容中返回所选值：\n\n```ts\nasync execute(toolCallId, params, onUpdate, ctx, signal) {\n  if (!pi.hasUI) {\n    return { content: [{ type: \"text\", text: \"UI unavailable\" }] };\n  }\n\n  const picked = await pi.ui.custom<string | undefined>((tui, theme, done) => {\n    const component = new MyPickerComponent(done, signal);\n    return component;\n  });\n\n  return { content: [{ type: \"text\", text: picked ? `Picked: ${picked}` : \"Cancelled\" }] };\n}\n```\n\n## 3) 自定义工具调用/结果渲染器\n\n自定义工具和扩展工具可以从以下方法返回组件：\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\n`options` 目前包括：\n\n- `expanded: boolean`\n- `isPartial: boolean`\n- `spinnerFrame?: number`\n\n这些渲染器由 `ToolExecutionComponent` 挂载。\n\n## 生命周期和取消\n\n- `dispose()` 在类型层面是可选的，但当您拥有定时器、子进程、监视器、套接字或覆盖层时应当实现。\n- `done(...)` 应在您的组件流程中恰好调用一次。\n- 对于可取消的长时间运行 UI，将 `CancellableLoader` 与 `AbortSignal` 配对使用，并在 `onAbort` 中调用 `done(...)`。\n\n取消模式示例：\n\n```ts\nconst loader = new CancellableLoader(tui, theme.fg(\"accent\"), theme.fg(\"muted\"), \"Working...\");\nloader.onAbort = () => done(undefined);\nvoid doWork(loader.signal).then(result => done(result));\nreturn loader;\n```\n\n## 完整的自定义组件示例（扩展命令）\n\n```ts\nimport type { Component } from \"@f5-sales-demo/pi-tui\";\nimport { SelectList, matchesKey, replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\nimport { getSelectListTheme, type ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nclass Picker implements Component {\n  list: SelectList;\n  keybindings: any;\n  done: (value: string | undefined) => void;\n\n  constructor(\n    items: Array<{ value: string; label: string }>,\n    keybindings: any,\n    done: (value: string | undefined) => void,\n  ) {\n    this.list = new SelectList(items, 8, getSelectListTheme());\n    this.keybindings = keybindings;\n    this.done = done;\n    this.list.onSelect = item => this.done(item.value);\n    this.list.onCancel = () => this.done(undefined);\n  }\n\n  handleInput(data: string): void {\n    if (this.keybindings.matches(data, \"interrupt\")) {\n      this.done(undefined);\n      return;\n    }\n    this.list.handleInput(data);\n  }\n\n  render(width: number): string[] {\n    return this.list.render(width).map(line => truncateToWidth(replaceTabs(line), width));\n  }\n\n  invalidate(): void {\n    this.list.invalidate();\n  }\n}\n\nexport default function extension(pi: ExtensionAPI): void {\n  pi.registerCommand(\"pick-model\", {\n    description: \"Pick a model profile\",\n    handler: async (_args, ctx) => {\n      if (!ctx.hasUI) return;\n\n      const selected = await ctx.ui.custom<string | undefined>((tui, theme, keybindings, done) => {\n        const items = [\n          { value: \"fast\", label: theme.fg(\"accent\", \"Fast\") },\n          { value: \"balanced\", label: \"Balanced\" },\n          { value: \"quality\", label: \"Quality\" },\n        ];\n        return new Picker(items, keybindings, done);\n      });\n\n      if (selected) ctx.ui.notify(`Selected profile: ${selected}`, \"info\");\n    },\n  });\n}\n```\n\n## 关键实现文件\n\n- `packages/tui/src/tui.ts` — `Component`、`Focusable`、光标标记、焦点、覆盖层、输入分发。\n- `packages/tui/src/utils.ts` — 宽度/截断/清理基础工具。\n- `packages/tui/src/keys.ts` / `keybindings.ts` — 按键解析和可配置的操作映射。\n- `packages/coding-agent/src/modes/controllers/extension-ui-controller.ts` — 扩展/hook/自定义工具 UI 的交互式挂载/卸载。\n- `packages/coding-agent/src/extensibility/extensions/types.ts` — 扩展 UI 和渲染器契约。\n- `packages/coding-agent/src/extensibility/hooks/types.ts` — Hook UI 契约（旧版 custom 签名）。\n- `packages/coding-agent/src/extensibility/custom-tools/types.ts` — 自定义工具执行/渲染契约。\n- `packages/coding-agent/src/modes/components/tool-execution.ts` — 挂载 `renderCall`/`renderResult` 组件和部分状态选项。\n- `packages/coding-agent/src/tools/context.ts` — 工具 UI 上下文传播（`hasUI`、`ui`）。\n",
	"zh-tw/configuration/blob-artifact-architecture.md": "---\ntitle: Blob 與 Artifact 儲存架構\ndescription: 內容定址的 blob 儲存與 artifact 註冊表，用於會話媒體、截圖和工具輸出。\nsidebar:\n  order: 7\n  label: Blob 與 artifact 儲存\ni18n:\n  sourceHash: 70d255f48d5b\n  translator: machine\n---\n\n# Blob 與 artifact 儲存架構\n\n本文件說明 coding-agent 如何將大型/二進位資料儲存在會話 JSONL 之外、截斷的工具輸出如何持久化，以及內部 URL（`artifact://`、`agent://`）如何解析回已儲存的資料。\n\n## 為何存在兩套儲存系統\n\n執行環境針對不同的資料形態使用兩種不同的持久化機制：\n\n- **內容定址 blob**（`blob:sha256:<hash>`）：全域、以二進位為導向的儲存，用於將大型圖片 base64 資料從持久化的會話條目中外部化。\n- **會話範圍的 artifact**（`<sessionFile-without-.jsonl>/` 下的檔案）：每個會話的文字檔案，用於完整的工具輸出和子代理輸出。\n\n它們被刻意分開：\n\n- blob 儲存透過內容雜湊優化去重複和穩定引用，\n- artifact 儲存透過本地 ID 優化僅附加的會話工具操作和人工/工具檢索。\n\n## 儲存邊界與磁碟配置\n\n## Blob 儲存邊界（全域）\n\n`SessionManager` 建構 `BlobStore(getBlobsDir())`，因此 blob 檔案存放在共享的全域 blob 目錄中（不在會話資料夾內）。\n\nBlob 檔案命名：\n\n- 檔案路徑：`<blobsDir>/<sha256-hex>`\n- 無副檔名\n- 條目中儲存的引用字串：`blob:sha256:<sha256-hex>`\n\n影響：\n\n- 跨會話的相同二進位內容會解析為相同的雜湊/路徑，\n- 在內容層級上寫入是冪等的，\n- blob 的生命週期可以超過任何單一會話檔案。\n\n## Artifact 邊界（會話本地）\n\n`ArtifactManager` 從會話檔案路徑衍生 artifact 目錄：\n\n- 會話檔案：`.../<timestamp>_<sessionId>.jsonl`\n- artifact 目錄：`.../<timestamp>_<sessionId>/`（去除 `.jsonl`）\n\nArtifact 類型共用此目錄：\n\n- 截斷的工具輸出檔案：`<numericId>.<toolType>.log`（用於 `artifact://`）\n- 子代理輸出檔案：`<outputId>.md`（用於 `agent://`）\n\n## ID 與名稱配置方案\n\n## Blob ID：內容雜湊\n\n`BlobStore.put()` 對原始二進位位元組計算 SHA-256 並返回：\n\n- `hash`：十六進位摘要，\n- `path`：`<blobsDir>/<hash>`，\n- `ref`：`blob:sha256:<hash>`。\n\n不使用會話本地計數器。\n\n## Artifact ID：會話本地單調遞增整數\n\n`ArtifactManager` 在首次使用時掃描現有的 `*.log` artifact 檔案，找到最大的現有數字 ID，並設定 `nextId = max + 1`。\n\n配置行為：\n\n- 檔案格式：`{id}.{toolType}.log`\n- ID 是連續字串（`\"0\"`、`\"1\"`、...）\n- 恢復會話時不會覆蓋現有 artifact，因為掃描在配置之前進行。\n\n如果 artifact 目錄不存在，掃描會產生空列表，配置從 `0` 開始。\n\n## 代理輸出 ID（`agent://`）\n\n`AgentOutputManager` 為子代理輸出配置 ID，格式為 `<index>-<requestedId>`（可選擇性地巢狀在父級前綴下，例如 `0-Parent.1-Child`）。它在初始化時掃描現有的 `.md` 檔案，以便在恢復時從下一個索引繼續。\n\n## 持久化資料流\n\n## 1）會話條目持久化重寫路徑\n\n在寫入會話條目之前（`#rewriteFile` / 增量持久化），`SessionManager` 呼叫 `prepareEntryForPersistence()`（透過 `truncateForPersistence`）。\n\n關鍵行為：\n\n1. **大型字串截斷**：過大的字串會被裁切並加上後綴 `\"[Session persistence truncated large content]\"`。\n2. **暫態欄位剝離**：`partialJson` 和 `jsonlEvents` 從持久化條目中移除。\n3. **圖片外部化至 blob**：\n   - 僅適用於 `content` 陣列中的圖片區塊，\n   - 僅當 `data` 尚未是 blob 引用時，\n   - 僅當 base64 長度至少達到閾值（`BLOB_EXTERNALIZE_THRESHOLD = 1024`）時，\n   - 將內聯 base64 替換為 `blob:sha256:<hash>`。\n\n這使會話 JSONL 保持精簡，同時保留可恢復性。\n\n## 2）會話載入再水合路徑\n\n開啟會話時（`setSessionFile`），在遷移之後，`SessionManager` 執行 `resolveBlobRefsInEntries()`。\n\n對於每個包含 `blob:sha256:<hash>` 的 message/custom-message 圖片區塊：\n\n- 從 blob 儲存讀取 blob 位元組，\n- 將位元組轉換回 base64，\n- 修改記憶體中的條目以內聯 base64 供執行環境消費者使用。\n\n如果 blob 遺失：\n\n- `resolveImageData()` 記錄警告，\n- 返回原始引用字串不變，\n- 載入繼續進行（不會硬性崩潰）。\n\n## 3）工具輸出溢出/截斷路徑\n\n`OutputSink` 為 bash/python/ssh 及相關執行器提供串流輸出功能。\n\n行為：\n\n1. 每個區塊都會被清理並附加到記憶體中的尾部緩衝區。\n2. 當記憶體中的位元組超過溢出閾值（`DEFAULT_MAX_BYTES`，50KB）時，sink 標記輸出為已截斷。\n3. 如果有可用的 artifact 路徑，sink 會開啟檔案寫入器並寫入：\n   - 現有緩衝內容（一次），\n   - 所有後續區塊。\n4. 記憶體緩衝區始終被修剪到尾部視窗以供顯示。\n5. `dump()` 僅在檔案 sink 成功建立時返回包含 `artifactId` 的摘要。\n\n實際效果：\n\n- UI/工具返回顯示截斷的尾部，\n- 完整輸出保存在 artifact 檔案中，並以 `artifact://<id>` 引用。\n\n如果檔案 sink 建立失敗（I/O 錯誤、路徑遺失等），sink 會靜默回退到僅記憶體截斷；完整輸出不會被持久化。\n\n## URL 存取模型\n\n## `blob:` 引用\n\n`blob:sha256:<hash>` 是會話條目資料中的持久化引用，不是由路由器處理的內部 URL 方案。解析由 `SessionManager` 在會話載入時完成。\n\n## `artifact://<id>`\n\n由 `ArtifactProtocolHandler` 處理：\n\n- 需要活動的會話 artifact 目錄，\n- ID 必須是數字，\n- 透過匹配檔名前綴 `<id>.` 來解析，\n- 從匹配的 `.log` 檔案返回原始文字（`text/plain`），\n- 當遺失時，錯誤訊息包含可用 artifact ID 列表。\n\n目錄遺失行為：\n\n- 如果 artifact 目錄不存在，拋出 `No artifacts directory found`。\n\n## `agent://<id>`\n\n由 `AgentProtocolHandler` 透過 `<artifactsDir>/<id>.md` 處理：\n\n- 純粹形式返回 markdown 文字，\n- `/path` 或 `?q=` 形式執行 JSON 擷取，\n- path 和 query 擷取不能組合使用，\n- 如果請求擷取，檔案內容必須能解析為 JSON。\n\n目錄遺失行為：\n\n- 拋出 `No artifacts directory found`。\n\n輸出遺失行為：\n\n- 拋出 `Not found: <id>` 並列出現有 `.md` 檔案中的可用 ID。\n\nRead 工具整合：\n\n- `read` 支援非擷取內部 URL 讀取的 offset/limit 分頁，\n- 當使用 `agent://` 擷取時拒絕 `offset/limit`。\n\n## 恢復、分叉與移動語義\n\n## 恢復\n\n- `ArtifactManager` 在首次配置時掃描現有的 `{id}.*.log` 檔案並繼續編號。\n- `AgentOutputManager` 掃描現有的 `.md` 輸出 ID 並繼續編號。\n- `SessionManager` 在載入時將 blob 引用再水合為 base64。\n\n## 分叉\n\n`SessionManager.fork()` 建立具有新會話 ID 和 `parentSession` 連結的新會話檔案，然後返回舊/新檔案路徑。Artifact 複製由 `AgentSession.fork()` 處理：\n\n- 嘗試將舊 artifact 目錄遞迴複製到新 artifact 目錄，\n- 容忍舊目錄不存在的情況，\n- 非 ENOENT 的複製錯誤會記錄為警告，分叉仍然完成。\n\n分叉後的 ID 影響：\n\n- 如果複製成功，新會話中的 artifact 計數器從最大已複製 ID 之後繼續，\n- 如果複製失敗/跳過，新會話 artifact ID 從 `0` 開始。\n\n分叉後的 blob 影響：\n\n- blob 是全域且內容定址的，因此不需要複製 blob 目錄。\n\n## 移動到新的工作目錄\n\n`SessionManager.moveTo()` 將會話檔案和 artifact 目錄重新命名到新的預設會話目錄，如果後續步驟失敗則具有回滾邏輯。這在重新定位會話範圍的同時保留 artifact 身份。\n\n## 故障處理與回退路徑\n\n| 情況 | 行為 |\n| --- | --- |\n| 再水合期間 blob 檔案遺失 | 發出警告並在記憶體中保留 `blob:sha256:` 引用字串 |\n| 透過 `BlobStore.get` 的 blob 讀取 ENOENT | 返回 `null` |\n| Artifact 目錄遺失（`ArtifactManager.listFiles`） | 返回空列表（配置可以重新開始） |\n| Artifact 目錄遺失（`artifact://` / `agent://`） | 拋出明確的 `No artifacts directory found` |\n| Artifact ID 未找到 | 拋出並列出可用 ID |\n| OutputSink artifact 寫入器初始化失敗 | 繼續僅尾部截斷（無完整輸出 artifact） |\n| 無會話檔案（某些任務路徑） | Task 工具回退到臨時 artifact 目錄用於子代理輸出 |\n\n## 二進位 blob 外部化 vs 文字輸出 artifact\n\n- **Blob 外部化**用於持久化會話條目內容中的二進位圖片資料；它將 JSONL 中的內聯 base64 替換為穩定的內容引用。\n- **Artifact** 是用於執行輸出和子代理輸出的純文字檔案；它們可透過會話本地 ID 經由內部 URL 定址。\n\n這兩套系統僅間接相關（都減少會話 JSONL 膨脹），但具有不同的身份識別、生命週期和檢索路徑。\n\n## 實作檔案\n\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts) — blob 引用格式、雜湊、put/get、外部化/解析輔助函式。\n- [`src/session/artifacts.ts`](../../packages/coding-agent/src/session/artifacts.ts) — 會話 artifact 目錄模型和數字 artifact ID 配置。\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — `OutputSink` 截斷/溢出至檔案行為和摘要中繼資料。\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts) — 持久化轉換、載入時 blob 再水合、會話分叉/移動互動。\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — 互動式分叉時的 artifact 目錄複製。\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — 工具 artifact 管理器啟動和每個工具的 artifact 路徑配置。\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — `artifact://` 解析器。\n- [`src/internal-urls/agent-protocol.ts`](../../packages/coding-agent/src/internal-urls/agent-protocol.ts) — `agent://` 解析器 + JSON 擷取。\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — 內部 URL 路由器接線和 artifact 目錄解析器。\n- [`src/task/output-manager.ts`](../../packages/coding-agent/src/task/output-manager.ts) — 會話範圍的代理輸出 ID 配置，用於 `agent://`。\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — 子代理輸出 artifact 寫入（`<id>.md`）和臨時 artifact 目錄回退。\n",
	"zh-tw/configuration/config-usage.md": "---\ntitle: 設定檔探索與解析\ndescription: xcsh 如何從專案、使用者和企業根目錄探索、解析並分層載入設定。\nsidebar:\n  order: 1\n  label: 設定檔\ni18n:\n  sourceHash: e38bd9792499\n  translator: machine\n---\n\n# 設定檔探索與解析\n\n本文件描述 coding-agent 目前如何解析設定檔：掃描哪些根目錄、優先順序如何運作，以及已解析的設定如何被 settings、skills、hooks、tools 和 extensions 消費。\n\n## 範圍\n\n主要實作：\n\n- `src/config.ts`\n- `src/config/settings.ts`\n- `src/config/settings-schema.ts`\n- `src/discovery/builtin.ts`\n- `src/discovery/helpers.ts`\n\n關鍵整合點：\n\n- `src/capability/index.ts`\n- `src/discovery/index.ts`\n- `src/extensibility/skills.ts`\n- `src/extensibility/hooks/loader.ts`\n- `src/extensibility/custom-tools/loader.ts`\n- `src/extensibility/extensions/loader.ts`\n\n---\n\n## 解析流程（視覺化）\n\n```text\n         Config roots (ordered)\n┌───────────────────────────────────────┐\n│ 1) ~/.xcsh/agent + <cwd>/.xcsh          │\n│ 2) ~/.claude   + <cwd>/.claude        │\n│ 3) ~/.codex    + <cwd>/.codex         │\n│ 4) ~/.gemini   + <cwd>/.gemini        │\n└───────────────────────────────────────┘\n                    │\n                    ▼\n        config.ts helper resolution\n  (getConfigDirs/findConfigFile/findNearest...)\n                    │\n                    ▼\n       capability providers enumerate items\n (native, claude, codex, gemini, agents, etc.)\n                    │\n                    ▼\n      priority sort + per-capability dedup\n                    │\n                    ▼\n          subsystem-specific consumption\n   (settings, skills, hooks, tools, extensions)\n```\n\n## 1) 設定根目錄與來源順序\n\n## 標準根目錄\n\n`src/config.ts` 定義了固定的來源優先順序列表：\n\n1. `.xcsh`（原生）\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\n使用者層級基礎路徑：\n\n- `~/.xcsh/agent`\n- `~/.claude`\n- `~/.codex`\n- `~/.gemini`\n\n專案層級基礎路徑：\n\n- `<cwd>/.xcsh`\n- `<cwd>/.claude`\n- `<cwd>/.codex`\n- `<cwd>/.gemini`\n\n`CONFIG_DIR_NAME` 為 `.xcsh`（`packages/utils/src/dirs.ts`）。\n\n## 重要限制\n\n`src/config.ts` 中的通用輔助函式在來源探索順序中**不**包含 `.pi`。\n\n---\n\n## 2) 核心探索輔助函式（`src/config.ts`）\n\n## `getConfigDirs(subpath, options)`\n\n回傳排序後的項目：\n\n- 使用者層級項目優先（按來源優先順序）\n- 然後是專案層級項目（按相同來源優先順序）\n\n選項：\n\n- `user`（預設 `true`）\n- `project`（預設 `true`）\n- `cwd`（預設 `getProjectDir()`）\n- `existingOnly`（預設 `false`）\n\n此 API 用於基於目錄的設定查詢（commands、hooks、tools、agents 等）。\n\n## `findConfigFile(subpath, options)` / `findConfigFileWithMeta(...)`\n\n在排序後的基礎路徑中搜尋第一個存在的檔案，回傳第一個匹配項（僅路徑或路徑+中繼資料）。\n\n## `findAllNearestProjectConfigDirs(subpath, cwd)`\n\n向上遍歷父目錄，回傳**每個來源基礎路徑（`.xcsh`、`.claude`、`.codex`、`.gemini`）最近的現有目錄**，然後按來源優先順序排序結果。\n\n當專案設定應從祖先目錄繼承時使用此函式（monorepo/巢狀工作區行為）。\n\n---\n\n## 3) 檔案設定包裝器（`src/config.ts` 中的 `ConfigFile<T>`）\n\n`ConfigFile<T>` 是用於單一設定檔的結構描述驗證載入器。\n\n支援的格式：\n\n- `.yml` / `.yaml`\n- `.json` / `.jsonc`\n\n行為：\n\n- 使用 AJV 針對提供的 TypeBox 結構描述驗證解析後的資料。\n- 快取載入結果直到呼叫 `invalidate()`。\n- 透過 `tryLoad()` 回傳三態結果：\n  - `ok`\n  - `not-found`\n  - `error`（含結構描述/解析上下文的 `ConfigError`）\n\n仍支援舊版遷移：\n\n- 如果目標路徑為 `.yml`/`.yaml`，同層的 `.json` 會自動遷移一次（`migrateJsonToYml`）。\n\n---\n\n## 4) 設定解析模型（`src/config/settings.ts`）\n\n執行時期設定模型為分層式：\n\n1. 全域設定：`~/.xcsh/agent/config.yml`\n2. 專案設定：透過 settings 能力探索（來自提供者的 `settings.json`）\n3. 執行時期覆寫：記憶體內，非持久性\n4. 結構描述預設值：來自 `SETTINGS_SCHEMA`\n\n有效讀取路徑：\n\n`defaults <- global <- project <- overrides`\n\n寫入行為：\n\n- `settings.set(...)` 寫入**全域**層（`config.yml`）並排入背景儲存佇列。\n- 專案設定從能力探索中為唯讀。\n\n## 遷移行為仍然生效\n\n啟動時，如果 `config.yml` 不存在：\n\n1. 從 `~/.xcsh/agent/settings.json` 遷移（成功後重新命名為 `.bak`）\n2. 與來自 `agent.db` 的舊版資料庫設定合併\n3. 將合併結果寫入 `config.yml`\n\n`#migrateRawSettings` 中的欄位層級遷移：\n\n- `queueMode` -> `steeringMode`\n- `ask.timeout` 毫秒 -> 秒（當舊值看起來像毫秒時，即 `> 1000`）\n- 舊版扁平 `theme: \"...\"` -> `theme.dark/theme.light` 結構\n\n---\n\n## 5) 能力/探索整合\n\n大多數非核心設定載入流程通過能力註冊表（`src/capability/index.ts` + `src/discovery/index.ts`）。\n\n## 提供者排序\n\n提供者按數字優先順序排序（較高者優先）。範例優先順序：\n\n- Native OMP（`builtin.ts`）：`100`\n- Claude：`80`\n- Codex / agents / Claude marketplace：`70`\n- Gemini：`60`\n\n```text\nProvider precedence (higher wins)\n\nnative (.xcsh)          priority 100\nclaude                 priority  80\ncodex / agents / ...   priority  70\ngemini                 priority  60\n```\n\n## 去重語義\n\n能力定義了 `key(item)`：\n\n- 相同的 key => 第一個項目獲勝（較高優先順序/較早載入的項目）\n- 無 key（`undefined`）=> 不去重，所有項目保留\n\n相關的 key：\n\n- skills：`name`\n- tools：`name`\n- hooks：`${type}:${tool}:${name}`\n- extension modules：`name`\n- extensions：`name`\n- settings：不去重（所有項目保留）\n\n---\n\n## 6) 原生 `.xcsh` 提供者行為（`src/discovery/builtin.ts`）\n\n原生提供者（`id: native`）讀取自：\n\n- 專案：`<cwd>/.xcsh/...`\n- 使用者：`~/.xcsh/agent/...`\n\n### 目錄准入規則\n\n`builtin.ts` 僅在目錄存在**且非空**時（`ifNonEmptyDir`）才納入設定根目錄。\n\n### 範圍特定載入\n\n- Skills：`skills/*/SKILL.md`\n- Slash commands：`commands/*.md`\n- Rules：`rules/*.{md,mdc}`\n- Prompts：`prompts/*.md`\n- Instructions：`instructions/*.md`\n- Hooks：`hooks/pre/*`、`hooks/post/*`\n- Tools：`tools/*.json|*.md` 和 `tools/<name>/index.ts`\n- Extension modules：在 `extensions/` 下探索（+ 舊版 `settings.json.extensions` 字串陣列）\n- Extensions：`extensions/<name>/gemini-extension.json`\n- Settings 能力：`settings.json`\n\n### 最近專案查詢的細微差異\n\n對於 `SYSTEM.md` 和 `XCSH.md`，原生提供者使用最近祖先專案 `.xcsh` 目錄搜尋（向上遍歷），但仍要求 `.xcsh` 目錄為非空。\n\n---\n\n## 7) 主要子系統如何消費設定\n\n## Settings 子系統\n\n- `Settings.init()` 載入全域 `config.yml` + 探索到的專案 `settings.json` 能力項目。\n- 僅 `level === \"project\"` 的能力項目會合併到專案層。\n\n## Skills 子系統\n\n- `extensibility/skills.ts` 透過 `loadCapability(skillCapability.id, { cwd })` 載入。\n- 套用來源開關和篩選器（`ignoredSkills`、`includeSkills`、自訂目錄）。\n- 舊版命名的開關仍然存在（`skills.enablePiUser`、`skills.enablePiProject`），但它們控制原生提供者（`provider === \"native\"`）。\n\n## Hooks 子系統\n\n- `discoverAndLoadHooks()` 從 hook 能力 + 明確設定的路徑解析 hook 路徑。\n- 然後透過 Bun import 載入模組。\n\n## Tools 子系統\n\n- `discoverAndLoadCustomTools()` 從 tool 能力 + 外掛 tool 路徑 + 明確設定的路徑解析 tool 路徑。\n- 宣告式 `.md/.json` tool 檔案僅為中繼資料；可執行載入需要程式碼模組。\n\n## Extensions 子系統\n\n- `discoverAndLoadExtensions()` 從 extension-module 能力加上明確路徑解析擴充模組。\n- 目前的實作刻意在載入前僅保留 `_source.provider === \"native\"` 的能力項目。\n\n---\n\n## 8) 可依賴的優先順序規則\n\n使用以下心智模型：\n\n1. `config.ts` 中的來源目錄排序決定候選路徑順序。\n2. 能力提供者優先順序決定跨提供者的優先等級。\n3. 能力 key 去重決定衝突行為（有 key 的能力中先到者獲勝）。\n4. 子系統特定的合併邏輯可進一步改變有效優先順序（特別是 settings）。\n\n### Settings 特定注意事項\n\nSettings 能力項目不會去重；`Settings.#loadProjectSettings()` 按回傳順序深度合併專案項目。因為合併時後面的項目值會覆蓋前面的值，有效的覆寫行為取決於提供者的發送順序，而不僅僅是能力 key 語義。\n\n---\n\n## 9) 仍然存在的舊版/相容性行為\n\n- `ConfigFile` 針對以 YAML 為目標的檔案進行 JSON -> YAML 遷移。\n- Settings 從 `settings.json` 和 `agent.db` 遷移到 `config.yml`。\n- Settings key 遷移（`queueMode`、`ask.timeout`、扁平 `theme`）。\n- Extension manifest 相容性：載入器同時接受 `package.json.xcsh` 和 `package.json.pi` manifest 區段。\n- 舊版設定名稱 `skills.enablePiUser` / `skills.enablePiProject` 仍為原生 skill 來源的有效控制開關。\n\n如果這些相容性路徑在程式碼中被移除，請立即更新本文件；目前仍有數個執行時期行為依賴它們。\n",
	"zh-tw/configuration/environment-variables.md": "---\ntitle: 環境變數\ndescription: xcsh 設定與行為控制的執行階段環境變數參考。\nsidebar:\n  order: 2\n  label: 環境變數\ni18n:\n  sourceHash: e2890f963c02\n  translator: machine\n---\n\n# 環境變數（目前執行階段參考）\n\n本參考文件衍生自以下目前的程式碼路徑：\n\n- `packages/coding-agent/src/**`\n- `packages/ai/src/**`（coding-agent 使用的提供者/驗證解析）\n- `packages/utils/src/**` 與 `packages/tui/src/**` 中直接影響 coding-agent 執行階段的變數\n\n本文件僅記錄目前有效的行為。\n\n## 解析模型與優先順序\n\n大多數執行階段查詢使用來自 `@f5-sales-demo/pi-utils`（`packages/utils/src/env.ts`）的 `$env`。\n\n`$env` 載入順序：\n\n1. 現有的程序環境（`Bun.env`）\n2. 專案 `.env`（`$PWD/.env`），僅用於尚未設定的鍵\n3. 家目錄 `.env`（`~/.env`），僅用於尚未設定的鍵\n\n`.env` 檔案中的額外規則：`XCSH_*` 鍵在解析期間會映射為 `PI_*` 鍵。\n\n---\n\n## 1) 模型/提供者驗證\n\n除非另有說明，這些變數透過 `getEnvApiKey()`（`packages/ai/src/stream.ts`）消費。\n\n### 核心提供者憑證\n\n| 變數                        | 用途 | 何時需要                                                 | 備註 / 優先順序                                                                                  |\n|---------------------------------|---|---------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|\n| `ANTHROPIC_OAUTH_TOKEN`         | Anthropic API 驗證 | 使用 Anthropic 搭配 OAuth 權杖驗證                         | 在提供者驗證解析中優先於 `ANTHROPIC_API_KEY`                              |\n| `ANTHROPIC_API_KEY`             | Anthropic API 驗證 | 使用 Anthropic 且未使用 OAuth 權杖                           | `ANTHROPIC_OAUTH_TOKEN` 之後的備用方案                                                              |\n| `ANTHROPIC_FOUNDRY_API_KEY`     | 透過 Azure Foundry / 企業閘道使用 Anthropic | 啟用 `CLAUDE_CODE_USE_FOUNDRY`                             | 當 Foundry 模式啟用時，優先於 `ANTHROPIC_OAUTH_TOKEN` 和 `ANTHROPIC_API_KEY`  |\n| `OPENAI_API_KEY`                | OpenAI 驗證 | 使用 OpenAI 系列提供者且未明確傳入 apiKey 參數 | 由 OpenAI Completions/Responses 提供者使用                                                      |\n| `GEMINI_API_KEY`                | Google Gemini 驗證 | 使用 `google` 提供者模型                                | Gemini 提供者映射的主要金鑰                                                             |\n| `GOOGLE_API_KEY`                | Gemini 圖像工具驗證備用 | 使用 `gemini_image` 工具且未設定 `GEMINI_API_KEY`            | 由 coding-agent 圖像工具備用路徑使用                                                       |\n| `GROQ_API_KEY`                  | Groq 驗證 | 使用 Groq 模型                                             |                                                                                                     |\n| `CEREBRAS_API_KEY`              | Cerebras 驗證 | 使用 Cerebras 模型                                         |                                                                                                     |\n| `TOGETHER_API_KEY`              | Together 驗證 | 使用 `together` 提供者                                     |                                                                                                     |\n| `HUGGINGFACE_HUB_TOKEN`         | Hugging Face 驗證 | 使用 `huggingface` 提供者                                  | 主要 Hugging Face 權杖環境變數                                                                  |\n| `HF_TOKEN`                      | Hugging Face 驗證 | 使用 `huggingface` 提供者                                  | 當 `HUGGINGFACE_HUB_TOKEN` 未設定時的備用方案                                                      |\n| `SYNTHETIC_API_KEY`             | Synthetic 驗證 | 使用 Synthetic 模型                                        |                                                                                                     |\n| `NVIDIA_API_KEY`                | NVIDIA 驗證 | 使用 `nvidia` 提供者                                       |                                                                                                     |\n| `NANO_GPT_API_KEY`              | NanoGPT 驗證 | 使用 `nanogpt` 提供者                                      |                                                                                                     |\n| `VENICE_API_KEY`                | Venice 驗證 | 使用 `venice` 提供者                                       |                                                                                                     |\n| `LITELLM_API_KEY`               | LiteLLM 驗證 | 使用 `litellm` 提供者                                      | OpenAI 相容的 LiteLLM 代理金鑰。當與 `LITELLM_BASE_URL` 一同設定時，啟用 `models.yml` 的自動設定 |\n| `LM_STUDIO_API_KEY`             | LM Studio 驗證（選用） | 使用 `lm-studio` 提供者搭配需要驗證的主機           | 本機 LM Studio 通常無需驗證執行；當需要金鑰時，任何非空權杖皆可使用         |\n| `OLLAMA_API_KEY`                | Ollama 驗證（選用） | 使用 `ollama` 提供者搭配需要驗證的主機              | 本機 Ollama 通常無需驗證執行；當需要金鑰時，任何非空權杖皆可使用            |\n| `LLAMA_CPP_API_KEY`             | Ollama 驗證（選用） | 使用 `llama-server` 搭配 `--api-key` 參數              | 本機 llama.cpp 通常無需驗證執行；當設定金鑰時，任何非空權杖皆可使用       |\n| `XIAOMI_API_KEY`                | Xiaomi MiMo 驗證 | 使用 `xiaomi` 提供者                                       |                                                                                                     |\n| `MOONSHOT_API_KEY`              | Moonshot 驗證 | 使用 `moonshot` 提供者                                     |                                                                                                     |\n| `XAI_API_KEY`                   | xAI 驗證 | 使用 xAI 模型                                              |                                                                                                     |\n| `OPENROUTER_API_KEY`            | OpenRouter 驗證 | 使用 OpenRouter 模型                                       | 當偏好/自動提供者為 OpenRouter 時，圖像工具也會使用                                  |\n| `MISTRAL_API_KEY`               | Mistral 驗證 | 使用 Mistral 模型                                          |                                                                                                     |\n| `ZAI_API_KEY`                   | z.ai 驗證 | 使用 z.ai 模型                                             | z.ai 網路搜尋提供者也會使用                                                               |\n| `MINIMAX_API_KEY`               | MiniMax 驗證 | 使用 `minimax` 提供者                                      |                                                                                                     |\n| `MINIMAX_CODE_API_KEY`          | MiniMax Code 驗證 | 使用 `minimax-code` 提供者                                 |                                                                                                     |\n| `MINIMAX_CODE_CN_API_KEY`       | MiniMax Code CN 驗證 | 使用 `minimax-code-cn` 提供者                              |                                                                                                     |\n| `OPENCODE_API_KEY`              | OpenCode 驗證 | 使用 OpenCode 模型                                         |                                                                                                     |\n| `QIANFAN_API_KEY`               | Qianfan 驗證 | 使用 `qianfan` 提供者                                      |                                                                                                     |\n| `QWEN_OAUTH_TOKEN`              | Qwen Portal 驗證 | 使用 `qwen-portal` 搭配 OAuth 權杖                          | 優先於 `QWEN_PORTAL_API_KEY`                                                         |\n| `QWEN_PORTAL_API_KEY`           | Qwen Portal 驗證 | 使用 `qwen-portal` 搭配 API 金鑰                              | `QWEN_OAUTH_TOKEN` 之後的備用方案                                                                   |\n| `ZENMUX_API_KEY`                | ZenMux 驗證 | 使用 `zenmux` 提供者                                       | 用於 ZenMux OpenAI 和 Anthropic 相容路由                                              |\n| `VLLM_API_KEY`                  | vLLM 驗證/探索啟用 | 使用 `vllm` 提供者（本機 OpenAI 相容伺服器）       | 對無驗證的本機伺服器，任何非空值皆可使用                                                 |\n| `CURSOR_ACCESS_TOKEN`           | Cursor 提供者驗證 | 使用 Cursor 提供者                                         |                                                                                                     |\n| `AI_GATEWAY_API_KEY`            | Vercel AI Gateway 驗證 | 使用 `vercel-ai-gateway` 提供者                            |                                                                                                     |\n| `CLOUDFLARE_AI_GATEWAY_API_KEY` | Cloudflare AI Gateway 驗證 | 使用 `cloudflare-ai-gateway` 提供者                        | 基底 URL 必須設定為 `https://gateway.ai.cloudflare.com/v1/<account>/<gateway>/anthropic` |\n\n### GitHub/Copilot 權杖鏈\n\n| 變數 | 用途 | 鏈 |\n|---|---|---|\n| `COPILOT_GITHUB_TOKEN` | GitHub Copilot 提供者驗證 | `COPILOT_GITHUB_TOKEN` → `GH_TOKEN` → `GITHUB_TOKEN` |\n| `GH_TOKEN` | Copilot 備用方案；網頁爬蟲中的 GitHub API 驗證 | 在網頁爬蟲中：`GITHUB_TOKEN` → `GH_TOKEN` |\n| `GITHUB_TOKEN` | Copilot 備用方案；網頁爬蟲中的 GitHub API 驗證 | 在網頁爬蟲中：先於 `GH_TOKEN` 檢查 |\n\n---\n\n## 2) 提供者特定的執行階段設定\n\n### Anthropic Foundry 閘道（Azure / 企業代理）\n\n當 `CLAUDE_CODE_USE_FOUNDRY` 啟用時，Anthropic 請求切換至 Foundry 模式：\n\n- 基底 URL 從 `FOUNDRY_BASE_URL` 解析（若未設定，備用方案仍為模型/預設基底 URL）。\n- 提供者 `anthropic` 的 API 金鑰解析變為：\n  `ANTHROPIC_FOUNDRY_API_KEY` → `ANTHROPIC_OAUTH_TOKEN` → `ANTHROPIC_API_KEY`。\n- `ANTHROPIC_CUSTOM_HEADERS` 以逗號/換行分隔的 `key: value` 對進行解析，並合併至請求標頭。\n- TLS 用戶端/伺服器材料可從環境變數值注入：\n  `NODE_EXTRA_CA_CERTS`、`CLAUDE_CODE_CLIENT_CERT`、`CLAUDE_CODE_CLIENT_KEY`。\n  每個都接受：\n  - PEM 內容的檔案系統路徑，或\n  - 內嵌 PEM（包含跳脫的 `\\n` 序列）。\n\n| 變數 | 值類型 | 行為 |\n|---|---|---|\n| `CLAUDE_CODE_USE_FOUNDRY` | 布林類字串（`1`、`true`、`yes`、`on`） | 為 Anthropic 提供者啟用 Foundry 模式 |\n| `FOUNDRY_BASE_URL` | URL 字串 | Foundry 模式中的 Anthropic 端點基底 URL |\n| `ANTHROPIC_FOUNDRY_API_KEY` | 權杖字串 | 用於 `Authorization: Bearer <token>` |\n| `ANTHROPIC_CUSTOM_HEADERS` | 標頭清單字串 | 額外標頭；格式為 `header-a: value, header-b: value` 或換行分隔 |\n| `NODE_EXTRA_CA_CERTS` | PEM 路徑或內嵌 PEM | 伺服器憑證驗證的額外 CA 鏈 |\n| `CLAUDE_CODE_CLIENT_CERT` | PEM 路徑或內嵌 PEM | mTLS 用戶端憑證 |\n| `CLAUDE_CODE_CLIENT_KEY` | PEM 路徑或內嵌 PEM | mTLS 用戶端私鑰（必須與憑證配對） |\n\n### Amazon Bedrock\n\n| 變數 | 預設值 / 行為 |\n|---|---|\n| `AWS_REGION` | 主要區域來源 |\n| `AWS_DEFAULT_REGION` | 當 `AWS_REGION` 未設定時的備用方案 |\n| `AWS_PROFILE` | 啟用具名設定檔驗證路徑 |\n| `AWS_ACCESS_KEY_ID` + `AWS_SECRET_ACCESS_KEY` | 啟用 IAM 金鑰驗證路徑 |\n| `AWS_BEARER_TOKEN_BEDROCK` | 啟用承載權杖驗證路徑 |\n| `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` / `AWS_CONTAINER_CREDENTIALS_FULL_URI` | 啟用 ECS 任務憑證路徑 |\n| `AWS_WEB_IDENTITY_TOKEN_FILE` + `AWS_ROLE_ARN` | 啟用 Web Identity 驗證路徑 |\n| `AWS_BEDROCK_SKIP_AUTH` | 若為 `1`，注入虛擬憑證（代理/無驗證情境） |\n| `AWS_BEDROCK_FORCE_HTTP1` | 若為 `1`，強制使用 Node HTTP/1 請求處理器 |\n\n提供者程式碼中的區域備用順序：`options.region` → `AWS_REGION` → `AWS_DEFAULT_REGION` → `us-east-1`。\n\n### Azure OpenAI Responses\n\n| 變數 | 預設值 / 行為 |\n|---|---|\n| `AZURE_OPENAI_API_KEY` | 除非以選項傳入 API 金鑰，否則為必要 |\n| `AZURE_OPENAI_API_VERSION` | 預設 `v1` |\n| `AZURE_OPENAI_BASE_URL` | 直接基底 URL 覆寫 |\n| `AZURE_OPENAI_RESOURCE_NAME` | 用於建構基底 URL：`https://<resource>.openai.azure.com/openai/v1` |\n| `AZURE_OPENAI_DEPLOYMENT_NAME_MAP` | 選用的映射字串：`modelId=deploymentName,model2=deployment2` |\n\n基底 URL 解析順序：選項 `azureBaseUrl` → 環境變數 `AZURE_OPENAI_BASE_URL` → 選項/環境變數資源名稱 → `model.baseUrl`。\n\n### Google Vertex AI\n\n| 變數 | 是否必要？ | 備註 |\n|---|---|---|\n| `GOOGLE_CLOUD_PROJECT` | 是（除非在選項中傳入） | 備用方案：`GCLOUD_PROJECT` |\n| `GCLOUD_PROJECT` | 備用方案 | 作為替代專案 ID 來源使用 |\n| `GOOGLE_CLOUD_LOCATION` | 是（除非在選項中傳入） | 提供者中無預設值 |\n| `GOOGLE_APPLICATION_CREDENTIALS` | 有條件 | 若已設定，檔案必須存在；否則檢查 ADC 備用路徑（`~/.config/gcloud/application_default_credentials.json`） |\n\n### Kimi\n\n| 變數 | 預設值 / 行為 |\n|---|---|\n| `KIMI_CODE_OAUTH_HOST` | 主要 OAuth 主機覆寫 |\n| `KIMI_OAUTH_HOST` | 備用 OAuth 主機覆寫 |\n| `KIMI_CODE_BASE_URL` | 覆寫 Kimi 使用端點基底 URL（`usage/kimi.ts`） |\n\nOAuth 主機鏈：`KIMI_CODE_OAUTH_HOST` → `KIMI_OAUTH_HOST` → `https://auth.kimi.com`。\n\n### Antigravity/Gemini 圖像相容性\n\n| 變數 | 預設值 / 行為 |\n|---|---|\n| `PI_AI_ANTIGRAVITY_VERSION` | 覆寫 Gemini CLI 提供者中的 Antigravity 使用者代理版本標籤 |\n\n### OpenAI Codex responses（功能/除錯控制）\n\n| 變數 | 行為 |\n|---|---|\n| `PI_CODEX_DEBUG` | `1`/`true` 啟用 Codex 提供者除錯記錄 |\n| `PI_CODEX_WEBSOCKET` | `1`/`true` 啟用 websocket 傳輸偏好 |\n| `PI_CODEX_WEBSOCKET_V2` | `1`/`true` 啟用 websocket v2 路徑 |\n| `PI_CODEX_WEBSOCKET_IDLE_TIMEOUT_MS` | 正整數覆寫（預設 300000） |\n| `PI_CODEX_WEBSOCKET_RETRY_BUDGET` | 非負整數覆寫（預設 5） |\n| `PI_CODEX_WEBSOCKET_RETRY_DELAY_MS` | 正整數基底退避覆寫（預設 500） |\n\n### Cursor 提供者除錯\n\n| 變數 | 行為 |\n|---|---|\n| `DEBUG_CURSOR` | 啟用提供者除錯記錄；`2`/`verbose` 可獲得詳細的有效載荷片段 |\n| `DEBUG_CURSOR_LOG` | 選用的 JSONL 除錯記錄輸出檔案路徑 |\n\n### 提示快取相容性切換\n\n| 變數 | 行為 |\n|---|---|\n| `PI_CACHE_RETENTION` | 若為 `long`，在支援的提供者啟用長期保留（`anthropic`、`openai-responses`、Bedrock 保留解析） |\n\n---\n\n## 3) 網路搜尋子系統\n\n### 搜尋提供者憑證\n\n| 變數 | 使用者 |\n|---|---|\n| `EXA_API_KEY` | Exa 搜尋提供者和 Exa MCP 工具 |\n| `BRAVE_API_KEY` | Brave 搜尋提供者 |\n| `PERPLEXITY_API_KEY` | Perplexity 搜尋提供者 API 金鑰模式 |\n| `TAVILY_API_KEY` | Tavily 搜尋提供者 |\n| `ZAI_API_KEY` | z.ai 搜尋提供者（也檢查 `agent.db` 中已儲存的 OAuth） |\n| `OPENAI_API_KEY` / DB 中的 Codex OAuth | Codex 搜尋提供者可用性/驗證 |\n\n### Anthropic 網路搜尋驗證鏈\n\n`packages/coding-agent/src/web/search/auth.ts` 按以下順序解析 Anthropic 網路搜尋憑證：\n\n1. `ANTHROPIC_SEARCH_API_KEY`（+ 選用的 `ANTHROPIC_SEARCH_BASE_URL`）\n2. `models.json` 中 `api: \"anthropic-messages\"` 的提供者項目\n3. 來自 `agent.db` 的 Anthropic OAuth 憑證（不得在 5 分鐘緩衝區內到期）\n4. 通用 Anthropic 環境變數備用方案：提供者金鑰（`ANTHROPIC_FOUNDRY_API_KEY`/`ANTHROPIC_OAUTH_TOKEN`/`ANTHROPIC_API_KEY`）+ 選用的 `ANTHROPIC_BASE_URL`（當 Foundry 模式啟用時為 `FOUNDRY_BASE_URL`）\n\n相關變數：\n\n| 變數 | 預設值 / 行為 |\n|---|---|\n| `ANTHROPIC_SEARCH_API_KEY` | 最高優先順序的明確搜尋金鑰 |\n| `ANTHROPIC_SEARCH_BASE_URL` | 省略時預設為 `https://api.anthropic.com` |\n| `ANTHROPIC_SEARCH_MODEL` | 預設為 `claude-haiku-4-5` |\n| `ANTHROPIC_BASE_URL` | 第 4 層驗證路徑的通用備用基底 URL |\n\n### Perplexity OAuth 流程行為旗標\n\n| 變數 | 行為 |\n|---|---|\n| `PI_AUTH_NO_BORROW` | 若已設定，在 Perplexity 登入流程中停用 macOS 原生應用程式權杖借用路徑 |\n\n---\n\n## 4) Python 工具與核心執行階段\n\n| 變數 | 預設值 / 行為 |\n|---|---|\n| `PI_PY` | Python 工具模式覆寫：`0`/`bash`=`bash-only`、`1`/`py`=`ipy-only`、`mix`/`both`=`both`；無效值會被忽略 |\n| `PI_PYTHON_SKIP_CHECK` | 若為 `1`，跳過 Python 核心可用性檢查/暖機檢查 |\n| `PI_PYTHON_GATEWAY_URL` | 若已設定，使用外部核心閘道而非本機共享閘道 |\n| `PI_PYTHON_GATEWAY_TOKEN` | 外部閘道的選用驗證權杖（`Authorization: token <value>`） |\n| `PI_PYTHON_IPC_TRACE` | 若為 `1`，在核心模組中啟用低階 IPC 追蹤路徑 |\n| `VIRTUAL_ENV` | Python 執行階段解析中最高優先順序的虛擬環境路徑 |\n\n額外條件行為：\n\n- 若 `BUN_ENV=test` 或 `NODE_ENV=test`，Python 可用性檢查視為通過，且跳過暖機。\n- Python 環境過濾會拒絕常見的 API 金鑰，並允許安全的基底變數 + `LC_`、`XDG_`、`PI_` 前綴。\n\n---\n\n## 5) 代理/執行階段行為切換\n\n| 變數                   | 預設值 / 行為                                                                           |\n|----------------------------|----------------------------------------------------------------------------------------------|\n| `PI_SMOL_MODEL`            | `smol` 的臨時模型角色覆寫（CLI `--smol` 優先）                     |\n| `PI_SLOW_MODEL`            | `slow` 的臨時模型角色覆寫（CLI `--slow` 優先）                     |\n| `PI_PLAN_MODEL`            | `plan` 的臨時模型角色覆寫（CLI `--plan` 優先）                     |\n| `PI_NO_TITLE`              | 若已設定（任何非空值），停用首次使用者訊息時的自動工作階段標題生成   |\n| `NULL_PROMPT`              | 若為 `true`，系統提示建構器回傳空字串                                        |\n| `PI_BLOCKED_AGENT`         | 在任務工具中封鎖特定的子代理類型                                                 |\n| `PI_SUBPROCESS_CMD`        | 覆寫子代理生成命令（`xcsh` / `xcsh.cmd` 解析繞過）                       |\n| `PI_TASK_MAX_OUTPUT_BYTES` | 每個子代理的最大擷取輸出位元組數（預設 `500000`）                                    |\n| `PI_TASK_MAX_OUTPUT_LINES` | 每個子代理的最大擷取輸出行數（預設 `5000`）                                      |\n| `PI_TIMING`                | 若為 `1`，啟用啟動/工具計時檢測記錄                                     |\n| `PI_DEBUG_STARTUP`         | 在多個啟動路徑中啟用啟動階段除錯輸出至 stderr                       |\n| `PI_PACKAGE_DIR`           | 覆寫套件資源基底目錄解析（文件/範例/變更日誌路徑查詢）            |\n| `PI_DISABLE_LSPMUX`        | 若為 `1`，停用 lspmux 偵測/整合並強制直接生成 LSP 伺服器          |\n| `LITELLM_BASE_URL`         | LiteLLM 代理基底 URL。當與 `LITELLM_API_KEY` 一同設定時，在首次執行時觸發自動生成 `models.yml`，並在每次啟動時自我修復 |\n| `LM_STUDIO_BASE_URL`       | 預設隱含 LM Studio 探索基底 URL 覆寫（若未設定則為 `http://127.0.0.1:1234/v1`） |\n| `OLLAMA_BASE_URL`          | 預設隱含 Ollama 探索基底 URL 覆寫（若未設定則為 `http://127.0.0.1:11434`）      |\n| `LLAMA_CPP_BASE_URL`       | 預設隱含 Llama.cpp 探索基底 URL 覆寫（若未設定則為 `http://127.0.0.1:8080`）    |\n| `PI_EDIT_VARIANT`          | 若為 `hashline`，當編輯工具可用時強制使用 hashline 讀取/grep 顯示模式               |\n| `PI_NO_PTY`                | 若為 `1`，停用 bash 工具的互動式 PTY 路徑                                          |\n\n當使用 CLI `--no-pty` 時，`PI_NO_PTY` 也會在內部被設定。\n\n---\n\n## 6) 儲存與設定根路徑\n\n這些變數透過 `@f5-sales-demo/pi-utils/dirs` 消費，影響 coding-agent 儲存資料的位置。\n\n| 變數 | 預設值 / 行為 |\n|---|---|\n| `PI_CONFIG_DIR` | 家目錄下的設定根目錄名稱（預設 `.xcsh`） |\n| `PI_CODING_AGENT_DIR` | 代理目錄的完整覆寫（預設 `~/<PI_CONFIG_DIR or .xcsh>/agent`） |\n| `PWD` | 在路徑輔助工具中用於匹配規範的目前工作目錄 |\n\n---\n\n## 7) Shell/工具執行環境\n\n（來自 `packages/utils/src/procmgr.ts` 和 coding-agent bash 工具整合。）\n\n| 變數 | 行為 |\n|---|---|\n| `PI_BASH_NO_CI` | 抑制自動將 `CI=true` 注入至生成的 shell 環境 |\n| `CLAUDE_BASH_NO_CI` | `PI_BASH_NO_CI` 的舊版別名備用方案 |\n| `PI_BASH_NO_LOGIN` | 用於停用登入 shell 模式 |\n| `CLAUDE_BASH_NO_LOGIN` | `PI_BASH_NO_LOGIN` 的舊版別名備用方案 |\n| `PI_SHELL_PREFIX` | 選用的命令前綴包裝器 |\n| `CLAUDE_CODE_SHELL_PREFIX` | `PI_SHELL_PREFIX` 的舊版別名備用方案 |\n| `VISUAL` | 偏好的外部編輯器命令 |\n| `EDITOR` | 備用外部編輯器命令 |\n\n目前實作備註：`PI_BASH_NO_LOGIN`/`CLAUDE_BASH_NO_LOGIN` 會被讀取，但目前的 `getShellArgs()` 在兩個分支中都回傳 `['-l','-c']`（實際上目前無效果）。\n\n---\n\n## 8) UI/主題/工作階段偵測（自動偵測的環境變數）\n\n這些變數作為執行階段信號被讀取；它們通常由終端機/作業系統設定，而非手動設定。\n\n| 變數 | 用途 |\n|---|---|\n| `COLORTERM`、`TERM`、`WT_SESSION` | 色彩能力偵測（主題色彩模式） |\n| `COLORFGBG` | 終端機背景明暗自動偵測 |\n| `TERM_PROGRAM`、`TERM_PROGRAM_VERSION`、`TERMINAL_EMULATOR` | 系統提示/上下文中的終端機識別 |\n| `KDE_FULL_SESSION`、`XDG_CURRENT_DESKTOP`、`DESKTOP_SESSION`、`XDG_SESSION_DESKTOP`、`GDMSESSION`、`WINDOWMANAGER` | 系統提示/上下文中的桌面環境/視窗管理器偵測 |\n| `KITTY_WINDOW_ID`、`TMUX_PANE`、`TERM_SESSION_ID`、`WT_SESSION` | 穩定的每終端機工作階段麵包屑 ID |\n| `SHELL`、`ComSpec`、`TERM_PROGRAM`、`TERM` | 系統資訊診斷 |\n| `APPDATA`、`XDG_CONFIG_HOME` | lspmux 設定路徑解析 |\n| `HOME` | MCP 命令 UI 中的路徑縮短 |\n\n---\n\n## 9) 原生載入器/除錯旗標\n\n| 變數 | 行為 |\n|---|---|\n| `PI_DEV` | 在 `packages/natives` 中啟用詳細的原生附加元件載入診斷 |\n\n## 10) TUI 執行階段旗標（共享套件，影響 coding-agent 使用者體驗）\n\n| 變數 | 行為 |\n|---|---|\n| `PI_NOTIFICATIONS` | `off` / `0` / `false` 抑制桌面通知 |\n| `PI_TUI_WRITE_LOG` | 若已設定，將 TUI 寫入記錄至檔案 |\n| `PI_HARDWARE_CURSOR` | 若為 `1`，啟用硬體游標模式 |\n| `PI_CLEAR_ON_SHRINK` | 若為 `1`，當內容縮小時清除空白列 |\n| `PI_DEBUG_REDRAW` | 若為 `1`，啟用重繪除錯記錄 |\n| `PI_TUI_DEBUG` | 若為 `1`，啟用深層 TUI 除錯傾印路徑 |\n\n---\n\n## 11) 提交生成控制\n\n| 變數 | 行為 |\n|---|---|\n| `PI_COMMIT_TEST_FALLBACK` | 若為 `true`（不區分大小寫），強制使用提交備用生成路徑 |\n| `PI_COMMIT_NO_FALLBACK` | 若為 `true`，當代理未回傳提案時停用備用方案 |\n| `PI_COMMIT_MAP_REDUCE` | 若為 `false`，停用 map-reduce 提交分析路徑 |\n| `DEBUG` | 若已設定，列印提交代理錯誤堆疊追蹤 |\n\n---\n\n## 安全敏感變數\n\n請將這些變數視為機密；請勿記錄或提交它們：\n\n- 提供者/API 金鑰和 OAuth/承載憑證（所有 `*_API_KEY`、`*_TOKEN`、OAuth 存取/重新整理權杖）\n- 雲端憑證（`AWS_*`、`GOOGLE_APPLICATION_CREDENTIALS` 路徑可能暴露服務帳戶材料）\n- 搜尋/提供者驗證變數（`EXA_API_KEY`、`BRAVE_API_KEY`、`PERPLEXITY_API_KEY`、Anthropic 搜尋金鑰）\n- Foundry mTLS 材料（`CLAUDE_CODE_CLIENT_CERT`、`CLAUDE_CODE_CLIENT_KEY`、`NODE_EXTRA_CA_CERTS` 當指向私有 CA 套件時）\n\nPython 執行階段在生成核心子程序之前也會明確剔除許多常見的金鑰變數（`packages/coding-agent/src/ipy/runtime.ts`）。\n",
	"zh-tw/configuration/fs-scan-cache-architecture.md": "---\ntitle: 檔案系統掃描快取架構\ndescription: 檔案系統掃描快取契約，用於快速檔案探索，具備 stale-while-revalidate 語意。\nsidebar:\n  order: 8\n  label: 檔案系統掃描快取\ni18n:\n  sourceHash: 2a2bde1726ac\n  translator: machine\n---\n\n# 檔案系統掃描快取架構契約\n\n本文件定義了以 Rust 實作的共享檔案系統掃描快取（`crates/pi-natives/src/fs_cache.rs`）的現行契約，並由暴露給 `packages/coding-agent` 的原生探索/搜尋 API 所使用。\n\n## 此快取的用途\n\n快取儲存完整的目錄掃描項目列表（`GlobMatch[]`），以掃描範圍和遍歷策略作為鍵值，然後讓更高層級的操作（glob 過濾、模糊評分、grep 檔案選取）針對這些快取項目執行。\n\n主要目標：\n\n- 避免重複的探索/搜尋呼叫對檔案系統進行重複走訪\n- 當 `glob`、`fuzzyFind` 和 `grep` 共享相同掃描策略時，保持一致性\n- 允許對空結果進行明確的過期恢復，以及在檔案變動後進行明確的失效處理\n\n## 所有權與公開介面\n\n- 快取實作與策略：`crates/pi-natives/src/fs_cache.rs`\n- 原生消費者：\n  - `crates/pi-natives/src/glob.rs`\n  - `crates/pi-natives/src/fd.rs`（`fuzzyFind`）\n  - `crates/pi-natives/src/grep.rs`\n- JS 綁定/匯出：\n  - `packages/natives/src/glob/index.ts`（`invalidateFsScanCache`）\n  - `packages/natives/src/glob/types.ts`\n  - `packages/natives/src/grep/types.ts`\n- Coding-agent 變動失效輔助工具：\n  - `packages/coding-agent/src/tools/fs-cache-invalidation.ts`\n\n## 快取鍵值分區（硬性契約）\n\n每個項目以下列條件作為鍵值：\n\n- 正規化的 `root` 目錄路徑\n- `include_hidden` 布林值\n- `use_gitignore` 布林值\n\n影響：\n\n- 隱藏檔案與非隱藏檔案的掃描**不會**共享項目。\n- 遵循 gitignore 與停用 ignore 的掃描**不會**共享項目。\n- 消費者必須傳遞穩定的隱藏檔案/gitignore 行為語意；變更任一旗標會建立不同的快取分區。\n\n`node_modules` 的包含與否**不在**快取鍵值中。快取儲存包含 `node_modules` 的項目；每個消費者的過濾在擷取後套用。\n\n## 掃描收集行為\n\n快取填充使用確定性走訪器（`ignore::WalkBuilder`），由 `include_hidden` 和 `use_gitignore` 配置：\n\n- `follow_links(false)`\n- 按檔案路徑排序\n- `.git` 始終被跳過\n- `node_modules` 在快取掃描時始終被收集（之後可選擇性過濾）\n- 項目的檔案類型 + `mtime` 透過 `symlink_metadata` 擷取\n\n搜尋根目錄由 `resolve_search_path` 解析：\n\n- 相對路徑根據當前 cwd 解析\n- 目標必須是現有目錄\n- 根目錄在可能時進行正規化\n\n## 新鮮度與驅逐策略\n\n全域策略（可透過環境變數覆寫）：\n\n- `FS_SCAN_CACHE_TTL_MS`（預設 `1000`）\n- `FS_SCAN_EMPTY_RECHECK_MS`（預設 `200`）\n- `FS_SCAN_CACHE_MAX_ENTRIES`（預設 `16`）\n\n行為：\n\n- `get_or_scan(...)`\n  - 若 TTL 為 `0`：完全繞過快取，始終進行全新掃描（`cache_age_ms = 0`）\n  - 在 TTL 內命中快取：回傳快取項目 + 非零的 `cache_age_ms`\n  - 命中但已過期：驅逐鍵值，重新掃描，儲存新項目\n- 最大項目數強制執行為依 `created_at` 最舊優先驅逐\n\n## 空結果快速重新檢查（與正常命中分開）\n\n正常快取命中：\n\n- TTL 內的快取命中回傳快取項目，不做其他處理。\n\n空結果快速重新檢查：\n\n- 這是**呼叫端**策略，使用 `ScanResult.cache_age_ms`\n- 若過濾/查詢結果為空，且快取掃描年齡至少為 `empty_recheck_ms()`，呼叫端執行一次 `force_rescan(...)` 並重試\n- 旨在減少當檔案最近新增但快取仍在 TTL 內時的過期否定結果\n\n目前的消費者：\n\n- `glob`：當過濾匹配為空且掃描年齡超過閾值時重新檢查\n- `fuzzyFind`（`fd.rs`）：僅在查詢非空且評分匹配為空時重新檢查\n- `grep`：當選取的候選檔案列表為空時重新檢查\n\n## 消費者預設值與快取使用\n\n快取在所有暴露的 API 上為選擇性啟用（`cache?: boolean`，預設 `false`）。\n\n原生 API 中的當前預設值：\n\n- `glob`：`hidden=false`、`gitignore=true`、`cache=false`\n- `fuzzyFind`：`hidden=false`、`gitignore=true`、`cache=false`\n- `grep`：`hidden=true`、`cache=false`，且快取掃描始終使用 `use_gitignore=true`\n\n目前的 Coding-agent 呼叫者：\n\n- 高流量提及候選探索啟用快取：\n  - `packages/coding-agent/src/utils/file-mentions.ts`\n  - 設定檔：`hidden=true`、`gitignore=true`、`includeNodeModules=true`、`cache=true`\n- 工具層級的 `grep` 整合目前停用掃描快取（`cache: false`）：\n  - `packages/coding-agent/src/tools/grep.ts`\n\n## 失效契約\n\n原生失效進入點：\n\n- `invalidateFsScanCache(path?: string)`\n  - 有 `path`：移除根目錄為目標路徑前綴的快取項目\n  - 無 path：清除所有掃描快取項目\n\n路徑處理細節：\n\n- 相對失效路徑根據 cwd 解析\n- 失效嘗試正規化\n- 若目標不存在（例如刪除），退回方案為正規化父目錄並在可能時重新附加檔名\n- 這保留了建立/刪除/重新命名中一側可能不存在時的失效行為\n\n## Coding-agent 變動流程責任\n\nCoding-agent 程式碼必須在成功的檔案系統變動後進行失效處理。\n\n中央輔助工具：\n\n- `invalidateFsScanAfterWrite(path)`\n- `invalidateFsScanAfterDelete(path)`\n- `invalidateFsScanAfterRename(oldPath, newPath)`（當路徑不同時對兩側進行失效處理）\n\n目前的變動工具呼叫點：\n\n- `packages/coding-agent/src/tools/write.ts`\n- `packages/coding-agent/src/patch/index.ts`（hashline/patch/replace 流程）\n\n規則：若某個流程變動了檔案系統內容或位置且繞過這些輔助工具，預期會出現快取過期錯誤。\n\n## 安全新增快取消費者\n\n在新的掃描器/搜尋路徑中引入快取使用時：\n\n1. **使用穩定的掃描策略輸入**\n   - 先決定隱藏檔案/gitignore 語意\n   - 一致地傳遞給 `get_or_scan`/`force_rescan`，使快取分區具有意圖性\n\n2. **將快取資料視為僅依遍歷策略預先過濾**\n   - 在擷取後套用工具特定的過濾（glob 模式、類型過濾器、node_modules 規則）\n   - 永遠不要假設快取項目已經反映您的更高層級過濾器\n\n3. **僅在有過期否定風險時實作空結果快速重新檢查**\n   - 使用 `scan.cache_age_ms >= empty_recheck_ms()`\n   - 以 `force_rescan(..., store=true, ...)` 重試一次\n   - 將此路徑與正常的快取命中邏輯分開\n\n4. **明確遵循無快取模式**\n   - 當呼叫端停用快取時，呼叫 `force_rescan(..., store=false, ...)`\n   - 在無快取請求路徑中不要填充共享快取\n\n5. **為任何新的寫入路徑接線變動失效**\n   - 在成功的寫入/編輯/刪除/重新命名後，呼叫 coding-agent 失效輔助工具\n   - 對於重新命名/移動，對舊路徑和新路徑都進行失效處理\n\n6. **不要新增每次呼叫的 TTL 調節旋鈕**\n   - 目前的契約僅為全域策略（環境變數配置），無每次請求的 TTL 覆寫\n\n## 已知邊界\n\n- 快取範圍為程序本地記憶體內（`DashMap`），不會跨程序重啟持久化。\n- 快取儲存掃描項目，而非最終工具結果。\n- `glob`/`fuzzyFind`/`grep` 僅在鍵值維度（`root`、`hidden`、`gitignore`）匹配時共享掃描項目。\n- `.git` 在掃描收集時始終被排除，不論呼叫者選項為何。\n",
	"zh-tw/configuration/hooks.md": "---\ntitle: Hooks\ndescription: 編碼代理程式生命週期中用於事件前/後自動化的 Hook 系統。\nsidebar:\n  order: 4\n  label: Hooks\ni18n:\n  sourceHash: cdbec10bc405\n  translator: machine\n---\n\n# Hooks\n\n本文件描述 `src/extensibility/hooks/*` 中的**目前 hook 子系統程式碼**。\n\n## 執行時期的目前狀態\n\nHook 套件（`src/extensibility/hooks/`）仍作為 API 介面匯出並可使用，但預設的 CLI 執行時期現在初始化的是**擴充執行器**路徑。在目前的啟動流程中：\n\n- `--hook` 被視為 `--extension` 的別名（CLI 路徑會合併至 `additionalExtensionPaths`）\n- 工具由 `ExtensionToolWrapper` 包裝，而非 `HookToolWrapper`\n- 情境轉換與生命週期發射透過 `ExtensionRunner` 進行\n\n因此本文件記錄的是 hook 子系統本身的實作（型別／載入器／執行器／包裝器），包含舊版行為與限制。\n\n## 主要檔案\n\n- `src/extensibility/hooks/types.ts` — hook 情境、事件型別與結果契約\n- `src/extensibility/hooks/loader.ts` — 模組載入與 hook 探索橋接\n- `src/extensibility/hooks/runner.ts` — 事件派送、命令查找與錯誤訊號\n- `src/extensibility/hooks/tool-wrapper.ts` — 工具前/後攔截包裝器\n- `src/extensibility/hooks/index.ts` — 匯出／重新匯出\n\n## Hook 模組是什麼\n\nHook 模組必須預設匯出一個工廠函式：\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function hook(pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName === \"bash\" && String(event.input.command ?? \"\").includes(\"rm -rf\")) {\n   return { block: true, reason: \"blocked by policy\" };\n  }\n });\n}\n```\n\n工廠函式可以：\n\n- 透過 `pi.on(...)` 註冊事件處理器\n- 透過 `pi.sendMessage(...)` 傳送持久性自訂訊息\n- 透過 `pi.appendEntry(...)` 持久化非 LLM 狀態\n- 透過 `pi.registerCommand(...)` 註冊斜線命令\n- 透過 `pi.registerMessageRenderer(...)` 註冊自訂訊息渲染器\n- 透過 `pi.exec(...)` 執行 shell 命令\n\n## 探索與載入\n\n`discoverAndLoadHooks(configuredPaths, cwd)` 的執行步驟：\n\n1. 從能力登錄檔載入已探索的 hooks（`loadCapability(\"hooks\")`）\n2. 附加明確設定的路徑（依絕對路徑去重）\n3. 呼叫 `loadHooks(allPaths, cwd)`\n\n`loadHooks` 接著匯入每個路徑並預期其具有 `default` 函式。\n\n### 路徑解析\n\n`loader.ts` 解析 hook 路徑的方式如下：\n\n- 絕對路徑：直接使用\n- `~` 路徑：展開後使用\n- 相對路徑：相對於 `cwd` 解析\n\n### 重要的舊版不一致\n\n`hookCapability` 的探索提供者仍以前/後 shell 風格的 hook 檔案為模型（例如 `.claude/hooks/pre/*`、`.xcsh/.../hooks/pre/*`）。\n\n此處的 hook 載入器使用動態模組匯入，並需要一個預設的 JS/TS hook 工廠函式。若探索到的 hook 路徑無法作為模組匯入，載入將失敗並回報於 `LoadHooksResult.errors`。\n\n## 事件介面\n\nHook 事件在 `types.ts` 中具有強型別定義。\n\n### Session 事件\n\n- `session_start`\n- `session_before_switch` → 可回傳 `{ cancel?: boolean }`\n- `session_switch`\n- `session_before_branch` → 可回傳 `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_branch`\n- `session_before_compact` → 可回傳 `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session.compacting` → 可回傳 `{ context?: string[]; prompt?: string; preserveData?: Record<string, unknown> }`\n- `session_compact`\n- `session_before_tree` → 可回傳 `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n- `session_tree`\n- `session_shutdown`\n\n### 代理程式／情境事件\n\n- `context` → 可回傳 `{ messages?: Message[] }`\n- `before_agent_start` → 可回傳 `{ message?: { customType; content; display; details } }`\n- `agent_start`\n- `agent_end`\n- `turn_start`\n- `turn_end`\n- `auto_compaction_start`\n- `auto_compaction_end`\n- `auto_retry_start`\n- `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### 工具事件（前/後模型）\n\n- `tool_call`（執行前）→ 可回傳 `{ block?: boolean; reason?: string }`\n- `tool_result`（執行後）→ 可回傳 `{ content?; details?; isError? }`\n\n這是 hook 子系統的核心前/後攔截模型。\n\n```text\nHook 工具攔截流程\n\ntool_call 處理器\n   │\n   ├─ 任何 { block: true }？── 是 ──> 拋出例外（工具已封鎖）\n   │\n   └─ 否\n      │\n      ▼\n   執行底層工具\n      │\n      ├─ 成功 ──> tool_result 處理器可覆寫 { content, details }\n      │\n      └─ 錯誤   ──> 發射 tool_result(isError=true) 後重新拋出原始錯誤\n```\n\n## 執行模型與變異語意\n\n### 1）執行前：`tool_call`\n\n`HookToolWrapper.execute()` 在工具執行前發射 `tool_call`。\n\n- 若任何處理器回傳 `{ block: true }`，執行停止\n- 若處理器拋出例外，包裝器以安全失敗（fail-closed）方式封鎖執行\n- 回傳的 `reason` 將成為拋出的錯誤訊息\n\n### 2）工具執行\n\n若未被封鎖，底層工具正常執行。\n\n### 3）執行後：`tool_result`\n\n成功後，包裝器發射 `tool_result` 並帶有：\n\n- `toolName`、`toolCallId`、`input`\n- `content`\n- `details`\n- `isError: false`\n\n若處理器回傳覆寫值：\n\n- `content` 可替換結果內容\n- `details` 可替換結果詳情\n\n工具失敗時，包裝器發射帶有 `isError: true` 及錯誤文字內容的 `tool_result`，然後重新拋出原始錯誤。\n\n### Hooks 可變異的內容\n\n- 單次呼叫的 LLM 情境，透過 `context`（`messages` 替換鏈）\n- 成功工具呼叫的工具輸出內容／詳情（`tool_result` 路徑）\n- 代理程式啟動前注入的訊息，透過 `before_agent_start`\n- 透過 `session_before_*` 與 `session.compacting` 取消／自訂壓縮／樹狀行為\n\n### 在此實作中 Hooks 無法變異的內容\n\n- 原地修改工具輸入參數（`tool_call` 上只能封鎖／允許）\n- 工具錯誤拋出後繼續執行（錯誤路徑會重新拋出）\n- 包裝器行為中的最終成功／錯誤狀態（回傳的 `isError` 已有型別定義，但 `HookToolWrapper` 不會套用）\n\n## 排序與衝突行為\n\n### 探索層級的排序\n\n能力提供者依優先順序排序（較高者優先）。去重依據能力金鑰，先者優先。\n\n對於 `hooks`，能力金鑰為 `${type}:${tool}:${name}`。來自較低優先順序提供者的重複項目會被標記並從有效探索清單中排除。\n\n### 載入順序\n\n`discoverAndLoadHooks` 建立一個扁平的 `allPaths` 清單，依解析後的絕對路徑去重，然後 `loadHooks` 依序迭代。每個探索目錄中的檔案順序取決於 `readdir` 的輸出；hook 載入器不進行額外排序。\n\n### 執行時期處理器順序\n\n在 `HookRunner` 內部，順序由註冊序列決定，具有確定性：\n\n1. hooks 陣列順序\n2. 每個 hook／事件的處理器註冊順序\n\n依事件型別的衝突行為：\n\n- `tool_call`：最後回傳的結果優先，除非某個處理器封鎖；第一個封鎖立即短路\n- `tool_result`：最後回傳的覆寫值優先（無短路）\n- `context`：鏈式執行；每個處理器接收前一個處理器的訊息輸出\n- `before_agent_start`：第一個回傳的訊息被保留；後續訊息忽略\n- `session_before_*`：追蹤最後回傳的結果；`cancel: true` 立即短路\n- `session.compacting`：最後回傳的結果優先\n\n命令／渲染器衝突：\n\n- `getCommand(name)` 回傳跨 hooks 的第一個匹配（先載入者優先）\n- `getMessageRenderer(customType)` 回傳第一個匹配\n- `getRegisteredCommands()` 回傳所有命令（不去重）\n\n## UI 互動（`HookContext.ui`）\n\n`HookUIContext` 包含：\n\n- `select`、`confirm`、`input`、`editor`\n- `notify`\n- `setStatus`\n- `custom`\n- `setEditorText`、`getEditorText`\n- `theme` getter\n\n`ctx.hasUI` 表示互動式 UI 是否可用。\n\n無 UI 執行時，預設的空操作情境行為為：\n\n- `select/input/editor` 回傳 `undefined`\n- `confirm` 回傳 `false`\n- `notify`、`setStatus`、`setEditorText` 為空操作\n- `getEditorText` 回傳 `\"\"`\n\n### 狀態列行為\n\n透過 `ctx.ui.setStatus(key, text)` 設定的 hook 狀態文字：\n\n- 依金鑰儲存\n- 依金鑰名稱排序\n- 經過清理（`\\r`、`\\n`、`\\t` → 空格；重複空格合併）\n- 合併後依寬度截斷以供顯示\n\n## 錯誤傳播與回退\n\n### 載入時期\n\n- 無效模組或缺少預設匯出 → 擷取至 `LoadHooksResult.errors`\n- 其他 hooks 繼續載入\n\n### 事件時期\n\n`HookRunner.emit(...)` 針對大多數事件捕捉處理器錯誤，並向監聽器發射 `HookError`（`hookPath`、`event`、`error`），然後繼續執行。\n\n`emitToolCall(...)` 較為嚴格：處理器錯誤在此不會被吞噬；它們會傳播至呼叫端。在 `HookToolWrapper` 中，這會封鎖工具呼叫（安全失敗）。\n\n## 實際 API 範例\n\n### 封鎖不安全的 bash 命令\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_call\", async (event, ctx) => {\n  if (event.toolName !== \"bash\") return;\n  const cmd = String(event.input.command ?? \"\");\n  if (!cmd.includes(\"rm -rf\")) return;\n\n  if (!ctx.hasUI) return { block: true, reason: \"rm -rf blocked (no UI)\" };\n  const ok = await ctx.ui.confirm(\"Dangerous command\", `Allow: ${cmd}`);\n  if (!ok) return { block: true, reason: \"user denied command\" };\n });\n}\n```\n\n### 執行後對工具輸出進行遮蔽\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"tool_result\", async event => {\n  if (event.toolName !== \"read\" || event.isError) return;\n\n  const redacted = event.content.map(chunk => {\n   if (chunk.type !== \"text\") return chunk;\n   return { ...chunk, text: chunk.text.replaceAll(/API_KEY=\\S+/g, \"API_KEY=[REDACTED]\") };\n  });\n\n  return { content: redacted };\n });\n}\n```\n\n### 每次 LLM 呼叫時修改模型情境\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.on(\"context\", async event => {\n  const filtered = event.messages.filter(msg => !(msg.role === \"custom\" && msg.customType === \"debug-only\"));\n  return { messages: filtered };\n });\n}\n```\n\n### 使用命令安全情境方法註冊斜線命令\n\n```ts\nimport type { HookAPI } from \"@f5-sales-demo/xcsh/hooks\";\n\nexport default function (pi: HookAPI): void {\n pi.registerCommand(\"handoff\", {\n  description: \"Create a new session with setup message\",\n  handler: async (_args, ctx) => {\n   await ctx.waitForIdle();\n   await ctx.newSession({\n    parentSession: ctx.sessionManager.getSessionFile(),\n    setup: async sm => {\n     sm.appendMessage({\n      role: \"user\",\n      content: [{ type: \"text\", text: \"Continue from prior session summary.\" }],\n      timestamp: Date.now(),\n     });\n    },\n   });\n  },\n });\n}\n```\n\n## 匯出介面\n\n`src/extensibility/hooks/index.ts` 匯出：\n\n- 載入 API（`discoverAndLoadHooks`、`loadHooks`）\n- 執行器與包裝器（`HookRunner`、`HookToolWrapper`）\n- 所有 hook 型別\n- `execCommand` 重新匯出\n\n套件根目錄（`src/index.ts`）將 hook **型別**重新匯出，作為舊版相容介面。\n",
	"zh-tw/configuration/porting-from-pi-mono.md": "---\ntitle: 從 pi-mono 移植：實務合併指南\ndescription: 將程式碼從 pi-mono monorepo 遷移至 xcsh 程式碼庫的實務指南。\nsidebar:\n  order: 9\n  label: 從 pi-mono 移植\ni18n:\n  sourceHash: fd4e8c09303d\n  translator: machine\n---\n\n# 從 pi-mono 移植：實務合併指南\n\n本指南是一份可重複使用的檢查清單，用於將 pi-mono 的變更移植到本儲存庫。\n適用於任何合併情境：單一檔案、功能分支或完整的版本同步。\n\n## 最後同步點\n\n**提交：** `b21b42d032919de2f2e6920a76fa9a37c3920c0a`\n**日期：** 2026-03-22\n\n每次同步後更新此區段；不要重複使用先前的範圍。\n\n開始新的同步時，從此提交開始產生補丁：\n\n```bash\ngit format-patch b21b42d032919de2f2e6920a76fa9a37c3920c0a..HEAD --stdout > changes.patch\n```\n\n## 0) 定義範圍\n\n- 確認上游參考（提交、標籤或 PR）。\n- 列出您計劃涉及的套件或資料夾。\n- 決定哪些功能在範圍內，哪些是有意跳過的。\n\n## 1) 安全地搬移程式碼\n\n- 優先選擇乾淨、聚焦的差異，而非整批複製。\n- 避免複製建置產物或自動產生的檔案。\n- 如果上游新增了檔案，請明確加入並審閱內容。\n\n## 2) 匹配匯入副檔名慣例\n\n大多數執行時期 TypeScript 原始碼在內部匯入中省略 `.js`，但某些測試/效能測試入口點保留 `.js` 以確保 ESM\n執行時期相容性。請遵循本地套件的現有風格；不要全面移除副檔名。\n\n- 在 `packages/coding-agent` 執行時期原始碼中，除非匯入非 TS 資源，否則保持內部匯入無副檔名。\n- 在 `packages/tui/test` 和 `packages/natives/bench` 中，如果周圍檔案已使用 `.js`，則保留。\n- 當工具要求時保留實際副檔名（例如 `.json`、`.css`、`.md` 文字嵌入）。\n- 範例：`import { x } from \"./foo.js\";` → `import { x } from \"./foo\";`（僅當套件慣例為無副檔名時）。\n\n## 3) 替換匯入範圍\n\n上游使用不同的套件範圍。請一致地替換它們。\n\n- 將舊範圍替換為此處使用的本地範圍。\n- 範例（根據您實際移植的套件進行調整）：\n  - `@mariozechner/pi-coding-agent` → `@f5-sales-demo/xcsh`\n  - `@mariozechner/pi-agent-core` → `@f5-sales-demo/pi-agent-core`\n  - `@mariozechner/pi-tui` → `@f5-sales-demo/pi-tui`\n  - `@mariozechner/pi-ai` → `@f5-sales-demo/pi-ai`\n\n## 4) 在 Bun API 優於 Node 時使用 Bun API\n\n我們在 Bun 上執行。僅在 Bun 提供更好替代方案時才替換 Node API。\n\n**應該替換：**\n\n- 程序產生：`child_process.spawn` → Bun Shell `$` 用於簡單命令，`Bun.spawn`/`Bun.spawnSync` 用於串流或長時間執行的工作\n- 檔案 I/O：`fs.readFileSync` → `Bun.file().text()` / `Bun.write()`\n- HTTP 客戶端：`node-fetch`、`axios` → 原生 `fetch`\n- 加密雜湊：`node:crypto` → Web Crypto 或 `Bun.hash`\n- SQLite：`better-sqlite3` → `bun:sqlite`\n- 環境變數載入：`dotenv` → Bun 自動載入 `.env`\n\n**不應該替換（這些在 Bun 中運作正常）：**\n\n- `os.homedir()` — 不要替換為 `Bun.env.HOME`、`Bun.env.HOME` 或字面值 `\"~\"`\n- `os.tmpdir()` — 不要替換為 `Bun.env.TMPDIR || \"/tmp\"` 或硬編碼路徑\n- `fs.mkdtempSync()` — 不要替換為手動路徑建構\n- `path.join()`、`path.resolve()` 等 — 這些沒問題\n\n**匯入風格：** 使用 `node:` 前綴搭配命名空間匯入（不要從 `node:fs` 或 `node:path` 使用具名匯入）。\n\n**額外的 Bun 慣例：**\n\n- 對於短的非串流命令，優先使用 Bun Shell `$`；僅在需要串流 I/O 或程序控制時使用 `Bun.spawn`。\n- 檔案使用 `Bun.file()`/`Bun.write()`，目錄使用 `node:fs/promises`。\n- 避免 `Bun.file().exists()` 檢查；在 try/catch 中使用 `isEnoent` 處理。\n- 優先使用 `Bun.sleep(ms)` 而非 `setTimeout` 包裝。\n\n**錯誤：**\n\n```typescript\n// BROKEN: env vars may be undefined, \"~\" is not expanded\nconst home = Bun.env.HOME || \"~\";\nconst tmp = Bun.env.TMPDIR || \"/tmp\";\n```\n\n**正確：**\n\n```typescript\nimport * as os from \"node:os\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\n\nconst configDir = path.join(os.homedir(), \".config\", \"myapp\");\nconst tempDir = fs.mkdtempSync(path.join(os.tmpdir(), \"myapp-\"));\n```\n\n## 5) 優先使用 Bun 嵌入（不複製）\n\n不要在建置時複製執行時期資源或供應商檔案。\n\n- 如果上游將資源複製到 dist 資料夾，請替換為 Bun 友善的嵌入方式。\n- 提示詞是靜態 `.md` 檔案；使用 Bun 文字匯入（`with { type: \"text\" }`）和 Handlebars，而非內嵌提示詞字串。\n- 使用 `import.meta.dir` + `Bun.file` 載入相鄰的非文字資源。\n- 將資源保留在儲存庫中，讓打包器包含它們。\n- 除非使用者明確要求，否則消除複製腳本。\n- 如果上游在執行時期讀取打包的備用檔案，請用 Bun 文字嵌入匯入替換檔案系統讀取。\n  - 範例（Codex 指令備用）：\n    - `const FALLBACK_PROMPT_PATH = join(import.meta.dir, \"codex-instructions.md\");` -> 移除\n    - `import FALLBACK_INSTRUCTIONS from \"./codex-instructions.md\" with { type: \"text\" };`\n    - 使用 `return FALLBACK_INSTRUCTIONS;` 而非 `readFileSync(FALLBACK_PROMPT_PATH, \"utf8\")`\n\n## 6) 謹慎移植 `package.json`\n\n將 `package.json` 視為合約。有意圖地合併。\n\n- 除非移植需要變更，否則保留現有的 `name`、`version`、`type`、`exports` 和 `bin`。\n- 將 npm/node 腳本替換為 Bun 等效項（例如 `bun check`、`bun test`）。\n- 確保依賴項使用正確的範圍。\n- 不要為修復型別錯誤而降級依賴項；應該升級。\n- 驗證工作區套件連結和 `peerDependencies`。\n\n## 7) 對齊程式碼風格和工具\n\n- 保持現有的格式化慣例。\n- 除非必要，不要引入 `any`。\n- 避免動態匯入和內嵌型別匯入；僅使用頂層匯入。\n- 永遠不要在程式碼中建構提示詞；提示詞是靜態 `.md` 檔案，使用 Handlebars 渲染。\n- 在 coding-agent 中，永遠不要使用 `console.log`/`console.warn`/`console.error`；使用來自 `@f5-sales-demo/pi-utils` 的 `logger`。\n- 使用 `Promise.withResolvers()` 而非 `new Promise((resolve, reject) => ...)`。\n- **不要在類別欄位或方法上使用 `private`/`protected`/`public` 關鍵字。** 使用 ES `#` 私有欄位進行封裝；可存取的成員保持不加關鍵字。唯一的例外是建構子參數屬性（`constructor(private readonly x: T)`），TypeScript 要求使用關鍵字。移植使用 `private foo` 或 `protected bar` 的上游程式碼時，轉換為 `#foo`（私有）或裸 `bar`（可存取）。\n- 優先使用現有的輔助函式和工具，而非新的臨時程式碼。\n- 保留本儲存庫中已做的 Bun 優先基礎設施變更：\n  - 執行時期為 Bun（沒有 Node 入口點）。\n  - 套件管理器為 Bun（沒有 npm 鎖定檔）。\n  - 重量級 Node API（`child_process`、`readline`）已替換為 Bun 等效項。\n  - 輕量級 Node API（`os.homedir`、`os.tmpdir`、`fs.mkdtempSync`、`path.*`）保留。\n  - CLI shebang 使用 `bun`（不是 `node`，不是 `tsx`）。\n  - 套件直接使用原始碼檔案（沒有 TypeScript 建置步驟）。\n  - CI 工作流程使用 Bun 進行安裝/檢查/測試。\n\n## 8) 移除舊的相容性層\n\n除非有要求，否則移除上游的相容性墊片。\n\n- 刪除已被替換的舊 API。\n- 直接更新所有呼叫端使用新 API。\n- 不要保留 `*_v2` 或並行版本。\n\n## 9) 更新文件和參考\n\n- 適當替換 pi-mono 儲存庫連結。\n- 更新範例以使用 Bun 和正確的套件範圍。\n- 確保 README 說明仍與當前儲存庫行為一致。\n\n## 10) 驗證移植結果\n\n變更後執行標準檢查：\n\n- `bun check`\n\n如果儲存庫已有與您的變更無關的失敗檢查，請指出。\n測試使用 Bun 的執行器（不是 Vitest），但僅在明確要求時才執行 `bun test`。\n\n## 11) 保護已改善的功能（回歸陷阱清單）\n\n如果您已在本地改善了行為，請將這些視為**不可妥協的**。移植前，記錄下\n改善之處並添加明確檢查，以免在合併中遺失。\n\n- **凍結預期行為**：為每項改善添加簡短的「前/後」備註（輸入、輸出、\n  預設值、邊界情況）。這可防止無聲回退。\n- **映射舊 → 新 API**：如果上游重新命名了概念（hooks → extensions、custom tools → tools 等），\n  確保每個舊入口點仍正確連接。遺漏一個旗標或匯出就等於失去功能。\n- **驗證匯出**：檢查 `package.json` 的 `exports`、公開型別和桶形檔案。上游移植經常\n  忘記重新匯出本地新增項目。\n- **涵蓋非正常路徑**：如果您修復了錯誤處理、逾時或備用邏輯，請添加測試或\n  至少一份手動檢查清單來驗證這些路徑。\n- **檢查預設值和配置合併順序**：改善通常存在於預設值中。確認新的預設值\n  沒有回退（例如新的配置優先順序、停用的功能、工具清單）。\n- **審核環境/shell 行為**：如果您修復了執行或沙箱化，驗證新路徑仍使用您的\n  清理過的環境，且沒有重新引入別名/函式覆蓋。\n- **重新執行目標範例**：保留一組最小的「已知良好」範例，並在移植後執行\n  （CLI 旗標、擴充套件註冊、工具執行）。\n\n## 12) 偵測並處理重構過的程式碼\n\n移植檔案前，檢查上游是否進行了大幅重構：\n\n```bash\n# Compare the file you're about to port against what you have locally\ngit diff HEAD upstream/main -- path/to/file.ts\n```\n\n如果差異顯示檔案被**重構**（不僅僅是修補）：\n\n- 新的抽象、重新命名的概念、合併的模組、變更的資料流\n\n那麼您必須在移植前**徹底閱讀新的實作**。盲目合併重構程式碼會導致功能遺失，因為：\n\n注意：互動模式最近被拆分為 controllers/utils/types。回移相關變更時，請將更新移植到我們建立的個別檔案中，並確保 `interactive-mode.ts` 的串接保持同步。\n\n1. **預設值無聲變更** - 新變數 `defaultFoo = [a, b]` 可能取代了原本回傳 `[a, b, c, d, e]` 的舊 `getAllFoo()`。\n\n2. **API 選項被丟棄** - 當系統合併時（例如 `hooks` + `customTools` → `extensions`），舊選項可能無法正確連接到新實作。\n\n3. **程式碼路徑變得陳舊** - 重新命名的概念（例如 `hookMessage` → `custom`）需要在每個 switch 陳述式、型別守衛和處理器中更新——不僅僅是定義處。\n\n4. **上下文/能力縮減** - 舊 API 可能暴露了 `{ logger, typebox, pi }`，而新 API 忘記包含。\n\n### 語義化移植流程\n\n當上游重構了一個模組時：\n\n1. **閱讀舊實作** - 了解它做了什麼、接受哪些選項、暴露了什麼。\n\n2. **閱讀新實作** - 了解新的抽象以及它們如何對映到舊行為。\n\n3. **驗證功能對等** - 對於舊程式碼中的每項能力，確認新程式碼保留了它或明確移除了它。\n\n4. **搜尋遺漏** - 搜尋可能在 switch 陳述式、處理器、UI 元件中遺漏的舊名稱/概念。\n\n5. **測試邊界** - CLI 旗標、SDK 選項、事件處理器、預設值——這些是回歸藏身之處。\n\n### 快速檢查\n\n```bash\n# Find all uses of an old concept that may need updating\nrg \"oldConceptName\" --type ts\n\n# Compare default values between versions\ngit show upstream/main:path/to/file.ts | rg \"default|DEFAULT\"\n\n# Check if all enum/union values have handlers\nrg \"case \\\"\" path/to/file.ts\n```\n\n## 13) 快速審核檢查清單\n\n在完成前用此作為最後一輪檢查：\n\n- [ ] 匯入副檔名遵循本地套件慣例（不全面移除 `.js`）\n- [ ] 新的/移植的程式碼中沒有 Node 專用 API\n- [ ] 所有套件範圍已更新\n- [ ] `package.json` 腳本使用 Bun\n- [ ] 提示詞為 `.md` 文字匯入（沒有內嵌提示詞字串）\n- [ ] coding-agent 中沒有 `console.*`（使用 `logger`）\n- [ ] 資源透過 Bun 嵌入模式載入（沒有複製腳本）\n- [ ] 測試或檢查可執行（或明確註記為被阻擋）\n- [ ] 沒有功能回歸（見第 11-12 節）\n\n## 14) 提交訊息格式\n\n提交回移時，遵循儲存庫格式 `<type>(scope): <past-tense description>` 並在標題中保留提交範圍。\n\n```\nfix(coding-agent): backported pi-mono changes (<from>..<to>)\n\npackages/<package>:\n- <type>: <description>\n- <type>: <description> (#<issue> by @<contributor>)\n\npackages/<other-package>:\n- <type>: <description>\n```\n\n**範例：**\n\n```\nfix(coding-agent): backported pi-mono changes (9f3eef65f..52532c7c0)\n\npackages/ai:\n- fix: handle \"sensitive\" stop reason from Anthropic API\n- fix: normalize tool call IDs with special characters for Responses API\n- fix: add overflow detection for Bedrock, MiniMax, Kimi providers\n- fix: 429 status is rate limiting, not context overflow\n\npackages/tui:\n- fix: refactored autocomplete state tracking\n- fix: file autocomplete should not trigger on empty text\n- fix: configurable autocomplete max visible items\n- fix: improved table column width calculation with word-aware wrapping\n\npackages/coding-agent:\n- fix: preserve external config.yml edits on save (#1046 by @nicobailonMD)\n- fix: resolve macOS NFD and curly quote variants in file paths\n```\n\n**規則：**\n\n- 按套件分組變更\n- 使用慣例提交類型（`fix`、`feat`、`refactor`、`perf`、`docs`）\n- 對外部貢獻包含上游 issue/PR 編號和貢獻者歸屬\n- 標題中的提交範圍有助於追蹤同步點\n\n## 15) 刻意的分歧\n\n我們的分支有與上游不同的架構決策。**不要移植以下上游模式：**\n\n### UI 架構\n\n| 上游                                        | 我們的分支                                                | 原因                                                                  |\n| ------------------------------------------- | --------------------------------------------------------- | --------------------------------------------------------------------- |\n| `FooterDataProvider` 類別                   | `StatusLineComponent`                                     | 更簡單、整合的狀態列                                                  |\n| `ctx.ui.setHeader()` / `ctx.ui.setFooter()` | 非 TUI 模式中為存根                                       | 在 TUI 中實作，其他地方為無操作                                       |\n| `ctx.ui.setEditorComponent()`               | 非 TUI 模式中為存根                                       | 在 TUI 中實作，其他地方為無操作                                       |\n| `InteractiveModeOptions` 選項物件           | 位置建構子參數（選項型別仍匯出）                          | 保持建構子簽名；上游新增欄位時更新型別                                |\n\n### 元件命名\n\n| 上游                         | 我們的分支              |\n| ---------------------------- | ----------------------- |\n| `extension-input.ts`         | `hook-input.ts`         |\n| `extension-selector.ts`      | `hook-selector.ts`      |\n| `ExtensionInputComponent`    | `HookInputComponent`    |\n| `ExtensionSelectorComponent` | `HookSelectorComponent` |\n\n### API 命名\n\n| 上游                                     | 我們的分支                               | 備註                                      |\n| ---------------------------------------- | ---------------------------------------- | ----------------------------------------- |\n| `sessionManager.appendSessionInfo(name)` | `sessionManager.setSessionName(name)`    | 我們全面使用 `sessionName`                |\n| `sessionManager.getSessionName()`        | `sessionManager.getSessionName()`        | 相同（我們統一以匹配上游的 RPC）          |\n| `agent.sessionName` / `setSessionName()` | `agent.sessionName` / `setSessionName()` | 相同                                      |\n\n### 檔案合併\n\n| 上游                                               | 我們的分支                              | 原因                                    |\n| -------------------------------------------------- | --------------------------------------- | --------------------------------------- |\n| `clipboard.ts` + `clipboard-image.ts`（工具檔案）   | `@f5-sales-demo/pi-natives` 剪貼簿模組 | 合併至 N-API 原生實作                   |\n\n### 測試框架\n\n| 上游                      | 我們的分支                    |\n| ------------------------- | ----------------------------- |\n| `vitest` 搭配 `vi.mock()` | `bun:test` 搭配 bun 的 `vi`  |\n| `node:test` 斷言          | `expect()` 匹配器            |\n\n### 工具架構\n\n| 上游                                | 我們的分支                                                        | 備註                                                      |\n| ----------------------------------- | ----------------------------------------------------------------- | --------------------------------------------------------- |\n| `createTool(cwd: string, options?)` | `createTools(session: ToolSession)` 透過 `BUILTIN_TOOLS` 註冊表   | 工具工廠接受 `ToolSession` 且可回傳 `null`                |\n| 每個工具的 `*Operations` 介面       | 每個工具的介面保留（`FindOperations`、`GrepOperations`）           | 用於 SSH/遠端覆蓋                                         |\n| 到處使用 Node.js `fs/promises`      | 檔案用 `Bun.file()`/`Bun.write()`；目錄用 `node:fs/promises`     | 當 Bun API 能簡化時優先使用                               |\n\n### 認證儲存\n\n| 上游                            | 我們的分支                                  | 備註                                         |\n| ------------------------------- | ------------------------------------------- | -------------------------------------------- |\n| `proper-lockfile` + `auth.json` | `agent.db`（bun:sqlite）                    | 憑證專門儲存在 `agent.db` 中                 |\n| 每個提供者單一憑證              | 多憑證搭配輪詢選取                          | 保留工作階段親和性和退避邏輯                 |\n\n### 擴充套件\n\n| 上游                          | 我們的分支                                 |\n| ----------------------------- | ------------------------------------------ |\n| `jiti` 用於 TypeScript 載入   | 原生 Bun `import()`                        |\n| `pkg.pi` 清單欄位             | `pkg.xcsh ?? pkg.pi`（優先使用我們的命名空間） |\n\n### 跳過這些上游功能\n\n移植時，**完全跳過**這些檔案/功能：\n\n- `footer-data-provider.ts` — 我們使用 StatusLineComponent\n- `clipboard-image.ts` — 剪貼簿在 `@f5-sales-demo/pi-natives` N-API 模組中\n- GitHub 工作流程檔案 — 我們有自己的 CI\n- `models.generated.ts` — 自動產生的，在本地重新產生（改為 models.json）\n\n### 我們新增的功能（保留這些）\n\n這些存在於我們的分支中但不在上游。**永遠不要覆蓋：**\n\n- 互動模式中的 `StatusLineComponent`\n- 帶工作階段親和性的多憑證認證\n- 基於能力的探索系統（`defineCapability`、`registerProvider`、`loadCapability`、`skillCapability` 等）\n- MCP/Exa/SSH 整合\n- LSP 寫入穿透用於儲存時格式化\n- Bash 攔截（`checkBashInterception`）\n- 讀取工具中的模糊路徑建議\n",
	"zh-tw/configuration/rpc.md": "---\ntitle: RPC 協定參考\ndescription: 用於 xcsh 元件之間進程間通訊的 JSON-RPC 協定參考。\nsidebar:\n  order: 5\n  label: RPC 協定\ni18n:\n  sourceHash: b4a3ddaf08ab\n  translator: machine\n---\n\n# RPC 協定參考\n\nRPC 模式以換行符分隔的 JSON 協定透過 stdio 執行編碼代理。\n\n- **stdin**：命令（`RpcCommand`）和擴充功能 UI 回應\n- **stdout**：命令回應（`RpcResponse`）、工作階段/代理事件、擴充功能 UI 請求\n\n主要實作：\n\n- `src/modes/rpc/rpc-mode.ts`\n- `src/modes/rpc/rpc-types.ts`\n- `src/session/agent-session.ts`\n- `packages/agent/src/agent.ts`\n- `packages/agent/src/agent-loop.ts`\n\n## 啟動\n\n```bash\nxcsh --mode rpc [regular CLI options]\n```\n\n行為說明：\n\n- `@file` CLI 引數在 RPC 模式中會被拒絕。\n- RPC 模式預設會停用自動工作階段標題生成，以避免額外的模型呼叫。\n- RPC 模式會將影響工作流程的 `todo.*`、`task.*` 和 `async.*` 設定重設為內建預設值，而非繼承使用者覆寫。\n- 進程以 JSONL 格式讀取 stdin（`readJsonl(Bun.stdin.stream())`）。\n- 當 stdin 關閉時，進程以代碼 `0` 退出。\n- 回應/事件以每行一個 JSON 物件的形式寫入。\n\n## 傳輸與封幀\n\n每個訊框是一個 JSON 物件後接 `\\n`。\n\n除了物件結構本身外，沒有其他封裝。\n\n### 輸出訊框類別（stdout）\n\n1. `RpcResponse`（`{ type: \"response\", ... }`）\n2. `AgentSessionEvent` 物件（`agent_start`、`message_update` 等）\n3. `RpcExtensionUIRequest`（`{ type: \"extension_ui_request\", ... }`）\n4. 擴充功能錯誤（`{ type: \"extension_error\", extensionPath, event, error }`）\n\n### 輸入訊框類別（stdin）\n\n1. `RpcCommand`\n2. `RpcExtensionUIResponse`（`{ type: \"extension_ui_response\", ... }`）\n\n## 請求/回應關聯\n\n所有命令接受可選的 `id?: string`。\n\n- 若提供，正常的命令回應會回傳相同的 `id`。\n- `RpcClient` 依賴此機制進行待處理請求的解析。\n\n來自執行時期的重要邊界行為：\n\n- 未知命令回應會以 `id: undefined` 發送（即使請求帶有 `id`）。\n- 輸入迴圈中的解析/處理例外會發送 `command: \"parse\"` 且 `id: undefined`。\n- `prompt` 和 `abort_and_prompt` 會回傳即時成功，之後若非同步提示排程失敗，可能會以**相同** id 發送後續錯誤回應。\n\n## 命令結構描述（標準）\n\n`RpcCommand` 定義在 `src/modes/rpc/rpc-types.ts`：\n\n### 提示\n\n- `{ id?, type: \"prompt\", message: string, images?: ImageContent[], streamingBehavior?: \"steer\" | \"followUp\" }`\n- `{ id?, type: \"steer\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"follow_up\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"abort\" }`\n- `{ id?, type: \"abort_and_prompt\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"new_session\", parentSession?: string }`\n\n### 狀態\n\n- `{ id?, type: \"get_state\" }`\n- `{ id?, type: \"set_todos\", phases: TodoPhase[] }`\n- `{ id?, type: \"set_host_tools\", tools: RpcHostToolDefinition[] }`\n\n### 模型\n\n- `{ id?, type: \"set_model\", provider: string, modelId: string }`\n- `{ id?, type: \"cycle_model\" }`\n- `{ id?, type: \"get_available_models\" }`\n\n### 思考\n\n- `{ id?, type: \"set_thinking_level\", level: ThinkingLevel }`\n- `{ id?, type: \"cycle_thinking_level\" }`\n\n### 佇列模式\n\n- `{ id?, type: \"set_steering_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_follow_up_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_interrupt_mode\", mode: \"immediate\" | \"wait\" }`\n\n### 壓縮\n\n- `{ id?, type: \"compact\", customInstructions?: string }`\n- `{ id?, type: \"set_auto_compaction\", enabled: boolean }`\n\n### 重試\n\n- `{ id?, type: \"set_auto_retry\", enabled: boolean }`\n- `{ id?, type: \"abort_retry\" }`\n\n### Bash\n\n- `{ id?, type: \"bash\", command: string }`\n- `{ id?, type: \"abort_bash\" }`\n\n### 工作階段\n\n- `{ id?, type: \"get_session_stats\" }`\n- `{ id?, type: \"export_html\", outputPath?: string }`\n- `{ id?, type: \"switch_session\", sessionPath: string }`\n- `{ id?, type: \"branch\", entryId: string }`\n- `{ id?, type: \"get_branch_messages\" }`\n- `{ id?, type: \"get_last_assistant_text\" }`\n- `{ id?, type: \"set_session_name\", name: string }`\n\n### 訊息\n\n- `{ id?, type: \"get_messages\" }`\n\n## 回應結構描述\n\n所有命令結果使用 `RpcResponse`：\n\n- 成功：`{ id?, type: \"response\", command: <command>, success: true, data?: ... }`\n- 失敗：`{ id?, type: \"response\", command: string, success: false, error: string }`\n\n資料酬載因命令而異，定義在 `rpc-types.ts` 中。\n\n### `get_state` 酬載\n\n```json\n{\n  \"model\": { \"provider\": \"...\", \"id\": \"...\" },\n  \"thinkingLevel\": \"off|minimal|low|medium|high|xhigh\",\n  \"isStreaming\": false,\n  \"isCompacting\": false,\n  \"steeringMode\": \"all|one-at-a-time\",\n  \"followUpMode\": \"all|one-at-a-time\",\n  \"interruptMode\": \"immediate|wait\",\n  \"sessionFile\": \"...\",\n  \"sessionId\": \"...\",\n  \"sessionName\": \"...\",\n  \"autoCompactionEnabled\": true,\n  \"messageCount\": 0,\n  \"queuedMessageCount\": 0,\n  \"todoPhases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Todos\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the tool surface\",\n          \"status\": \"in_progress\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n### `set_todos` 酬載\n\n替換當前工作階段的記憶體內待辦事項狀態，並回傳正規化的階段列表：\n\n```json\n{\n  \"id\": \"req_2\",\n  \"type\": \"set_todos\",\n  \"phases\": [\n    {\n      \"id\": \"phase-1\",\n      \"name\": \"Evaluation\",\n      \"tasks\": [\n        {\n          \"id\": \"task-1\",\n          \"content\": \"Map the read tool surface\",\n          \"status\": \"in_progress\"\n        },\n        {\n          \"id\": \"task-2\",\n          \"content\": \"Exercise edit operations\",\n          \"status\": \"pending\"\n        }\n      ]\n    }\n  ]\n}\n```\n\n這對於希望在第一次提示之前預先設定計畫的主機端非常有用。\n\n### `set_host_tools` 酬載\n\n替換 RPC 伺服器可透過 stdio 回呼的當前主機端擁有的工具集：\n\n```json\n{\n  \"id\": \"req_3\",\n  \"type\": \"set_host_tools\",\n  \"tools\": [\n    {\n      \"name\": \"echo_host\",\n      \"label\": \"Echo Host\",\n      \"description\": \"Echo a value from the embedding host\",\n      \"parameters\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"message\": { \"type\": \"string\" }\n        },\n        \"required\": [\"message\"],\n        \"additionalProperties\": false\n      }\n    }\n  ]\n}\n```\n\n回應酬載為：\n\n```json\n{\n  \"toolNames\": [\"echo_host\"]\n}\n```\n\n這些工具會在下次模型呼叫前加入活動工作階段的工具登錄中。重新發送 `set_host_tools` 會替換先前的主機端擁有集合。\n\n## 事件串流結構描述\n\nRPC 模式從 `AgentSession.subscribe(...)` 轉發 `AgentSessionEvent` 物件。\n\n常見事件類型：\n\n- `agent_start`、`agent_end`\n- `turn_start`、`turn_end`\n- `message_start`、`message_update`、`message_end`\n- `tool_execution_start`、`tool_execution_update`、`tool_execution_end`\n- `auto_compaction_start`、`auto_compaction_end`\n- `auto_retry_start`、`auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n- `todo_auto_clear`\n\n擴充功能執行器錯誤以單獨方式發送：\n\n```json\n{ \"type\": \"extension_error\", \"extensionPath\": \"...\", \"event\": \"...\", \"error\": \"...\" }\n```\n\n`message_update` 在 `assistantMessageEvent` 中包含串流增量（文字/思考/工具呼叫增量）。\n\n## 提示/佇列並行與排序\n\n這是最重要的操作行為。\n\n### 即時確認 vs 完成\n\n`prompt` 和 `abort_and_prompt` 會**立即確認**：\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n```\n\n這表示：\n\n- 命令接受 != 執行完成\n- 最終完成透過 `agent_end` 觀察\n\n### 串流期間\n\n`AgentSession.prompt()` 在活動串流期間需要 `streamingBehavior`：\n\n- `\"steer\"` => 佇列中的引導訊息（中斷路徑）\n- `\"followUp\"` => 佇列中的後續訊息（輪次後路徑）\n\n若在串流期間省略，提示將失敗。\n\n### 佇列預設值\n\n來自編碼代理設定結構描述（`packages/coding-agent/src/config/settings-schema.ts`）：\n\n- `steeringMode`：`\"one-at-a-time\"`\n- `followUpMode`：`\"one-at-a-time\"`\n- `interruptMode`：`\"wait\"`\n\n### 模式語義\n\n- `set_steering_mode` / `set_follow_up_mode`\n  - `\"one-at-a-time\"`：每輪從佇列取出一則訊息\n  - `\"all\"`：一次取出整個佇列\n- `set_interrupt_mode`\n  - `\"immediate\"`：工具執行在工具呼叫之間檢查引導；待處理的引導可以中止該輪中剩餘的工具呼叫\n  - `\"wait\"`：延遲引導直到輪次完成\n\n## 擴充功能 UI 子協定\n\nRPC 模式中的擴充功能使用請求/回應 UI 訊框。\n\n### 輸出請求\n\n`RpcExtensionUIRequest`（`type: \"extension_ui_request\"`）方法：\n\n- `select`、`confirm`、`input`、`editor`\n- `notify`、`setStatus`、`setWidget`、`setTitle`、`set_editor_text`\n\n執行時期說明：\n\n- 在 RPC 模式中自動工作階段標題生成已停用，且 `setTitle` UI 請求也預設被抑制，因為大多數主機端沒有有意義的終端機標題介面。設定 `PI_RPC_EMIT_TITLE=1` 可重新啟用 UI 事件。\n\n範例：\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"123\", \"method\": \"confirm\", \"title\": \"Confirm\", \"message\": \"Continue?\", \"timeout\": 30000 }\n```\n\n### 輸入回應\n\n`RpcExtensionUIResponse`（`type: \"extension_ui_response\"`）：\n\n- `{ type: \"extension_ui_response\", id: string, value: string }`\n- `{ type: \"extension_ui_response\", id: string, confirmed: boolean }`\n- `{ type: \"extension_ui_response\", id: string, cancelled: true }`\n\n若對話框有逾時設定，RPC 模式在逾時/中止觸發時會解析為預設值。\n\n## 主機端工具子協定\n\nRPC 主機端可以透過發送 `set_host_tools` 向代理公開自訂工具，然後透過相同的傳輸層服務執行請求。\n\n### 輸出請求\n\n當代理希望主機端執行其中一個工具時，RPC 模式會發送：\n\n```json\n{\n  \"type\": \"host_tool_call\",\n  \"id\": \"host_1\",\n  \"toolCallId\": \"toolu_123\",\n  \"toolName\": \"echo_host\",\n  \"arguments\": { \"message\": \"hello\" }\n}\n```\n\n若工具執行稍後被中止，RPC 模式會發送：\n\n```json\n{\n  \"type\": \"host_tool_cancel\",\n  \"id\": \"host_cancel_1\",\n  \"targetId\": \"host_1\"\n}\n```\n\n### 輸入更新與完成\n\n主機端可以選擇性地串流進度：\n\n```json\n{\n  \"type\": \"host_tool_update\",\n  \"id\": \"host_1\",\n  \"partialResult\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"working\" }]\n  }\n}\n```\n\n完成使用：\n\n```json\n{\n  \"type\": \"host_tool_result\",\n  \"id\": \"host_1\",\n  \"result\": {\n    \"content\": [{ \"type\": \"text\", \"text\": \"done\" }]\n  }\n}\n```\n\n在 `host_tool_result` 上設定 `isError: true` 可將回傳的內容作為工具錯誤呈現。\n\n## 錯誤模型與可恢復性\n\n### 命令層級失敗\n\n失敗為 `success: false` 附帶字串 `error`。\n\n```json\n{ \"id\": \"req_2\", \"type\": \"response\", \"command\": \"set_model\", \"success\": false, \"error\": \"Model not found: provider/model\" }\n```\n\n### 可恢復性預期\n\n- 大多數命令失敗是可恢復的；進程保持存活。\n- 格式錯誤的 JSONL / 解析迴圈例外會發送 `parse` 錯誤回應並繼續讀取後續行。\n- 空的 `set_session_name` 會被拒絕（`Session name cannot be empty`）。\n- 具有未知 `id` 的擴充功能 UI 回應會被忽略。\n- 進程終止條件為 stdin 關閉或擴充功能觸發的明確關閉。\n\n## 精簡命令流程\n\n### 1) 提示與串流\n\nstdin：\n\n```json\n{ \"id\": \"req_1\", \"type\": \"prompt\", \"message\": \"Summarize this repo\" }\n```\n\nstdout 序列（典型）：\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n{ \"type\": \"agent_start\" }\n{ \"type\": \"message_update\", \"assistantMessageEvent\": { \"type\": \"text_delta\", \"delta\": \"...\" }, \"message\": { \"role\": \"assistant\", \"content\": [] } }\n{ \"type\": \"agent_end\", \"messages\": [] }\n```\n\n### 2) 串流期間使用明確佇列策略提示\n\nstdin：\n\n```json\n{ \"id\": \"req_2\", \"type\": \"prompt\", \"message\": \"Also include risks\", \"streamingBehavior\": \"followUp\" }\n```\n\n### 3) 檢查與調整佇列行為\n\nstdin：\n\n```json\n{ \"id\": \"q1\", \"type\": \"get_state\" }\n{ \"id\": \"q2\", \"type\": \"set_steering_mode\", \"mode\": \"all\" }\n{ \"id\": \"q3\", \"type\": \"set_interrupt_mode\", \"mode\": \"wait\" }\n```\n\n### 4) 擴充功能 UI 往返\n\nstdout：\n\n```json\n{ \"type\": \"extension_ui_request\", \"id\": \"ui_7\", \"method\": \"input\", \"title\": \"Branch name\", \"placeholder\": \"feature/...\" }\n```\n\nstdin：\n\n```json\n{ \"type\": \"extension_ui_response\", \"id\": \"ui_7\", \"value\": \"feature/rpc-host\" }\n```\n\n## 關於 `RpcClient` 輔助工具的說明\n\n`src/modes/rpc/rpc-client.ts` 是一個便利封裝，而非協定定義。\n\n目前輔助工具特性：\n\n- 產生 `bun <cliPath> --mode rpc`\n- 透過生成的 `req_<n>` id 關聯回應\n- 僅將已識別的 `AgentEvent` 類型分派給監聽器\n- 透過 `setCustomTools()` 支援主機端擁有的自訂工具，並自動處理 `host_tool_call` / `host_tool_cancel`\n- **未**為每個協定命令提供輔助方法（例如，`set_interrupt_mode` 和 `set_session_name` 存在於協定類型中，但未封裝為專用方法）\n\n若您需要完整的介面覆蓋，請使用原始協定訊框。\n",
	"zh-tw/configuration/sdk.md": "---\ntitle: SDK\ndescription: 用於在 xcsh 編碼代理程式執行階段上構建自訂代理程式和整合的 SDK。\nsidebar:\n  order: 6\n  label: SDK\ni18n:\n  sourceHash: 80f3a4374241\n  translator: machine\n---\n\n# SDK\n\nSDK 是 `@f5-sales-demo/xcsh` 的同進程整合介面。\n當您需要從自己的 Bun/Node 進程直接存取代理程式狀態、事件串流、工具串接和會話控制時，請使用此 SDK。\n\n如果您需要跨語言/進程隔離，請改用 RPC 模式。\n\n## 安裝\n\n```bash\nbun add @f5-sales-demo/xcsh\n```\n\n## 進入點\n\n`@f5-sales-demo/xcsh` 從套件根目錄匯出 SDK API（也可透過 `@f5-sales-demo/xcsh/sdk` 存取）。\n\n嵌入器的核心匯出項目：\n\n- `createAgentSession`\n- `SessionManager`\n- `Settings`\n- `AuthStorage`\n- `ModelRegistry`\n- `discoverAuthStorage`\n- 探索輔助函式（`discoverExtensions`、`discoverSkills`、`discoverContextFiles`、`discoverPromptTemplates`、`discoverSlashCommands`、`discoverCustomTSCommands`、`discoverMCPServers`）\n- 工具工廠介面（`createTools`、`BUILTIN_TOOLS`、工具類別）\n\n## 快速入門（自動探索預設值）\n\n```ts\nimport { createAgentSession } from \"@f5-sales-demo/xcsh\";\n\nconst { session, modelFallbackMessage } = await createAgentSession();\n\nif (modelFallbackMessage) {\n process.stderr.write(`${modelFallbackMessage}\\n`);\n}\n\nconst unsubscribe = session.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Summarize this repository in 3 bullets.\");\nunsubscribe();\nawait session.dispose();\n```\n\n## `createAgentSession()` 預設探索的內容\n\n`createAgentSession()` 遵循「提供即覆寫，省略即探索」的原則。\n\n若省略，將自動解析：\n\n- `cwd`：`getProjectDir()`\n- `agentDir`：`~/.xcsh/agent`（透過 `getAgentDir()`）\n- `authStorage`：`discoverAuthStorage(agentDir)`\n- `modelRegistry`：`new ModelRegistry(authStorage)` + `await refresh()`\n- `settings`：`await Settings.init({ cwd, agentDir })`\n- `sessionManager`：`SessionManager.create(cwd)`（檔案後端）\n- 技能/上下文檔案/提示範本/斜線指令/擴充套件/自訂 TS 指令\n- 透過 `createTools(...)` 建立的內建工具\n- MCP 工具（預設啟用）\n- LSP 整合（預設啟用）\n\n### 必要與選用輸入\n\n通常您只需提供想要控制的部分：\n\n- **必須提供**：最小會話無需提供任何內容\n- **嵌入器中通常需明確提供**：\n    - `sessionManager`（若需要記憶體模式或自訂位置）\n    - `authStorage` + `modelRegistry`（若您自行管理憑證/模型生命週期）\n    - `model` 或 `modelPattern`（若需要確定性的模型選擇）\n    - `settings`（若需要隔離/測試設定）\n\n## 會話管理器行為（持久化 vs 記憶體模式）\n\n`AgentSession` 始終使用 `SessionManager`；行為取決於您使用的工廠方法。\n\n### 檔案後端（預設）\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.create(process.cwd()),\n});\n\nconsole.log(session.sessionFile); // 絕對 .jsonl 路徑\n```\n\n- 將對話/訊息/狀態差異持久化至會話檔案。\n- 支援繼續/開啟/列出/分叉工作流程。\n- `session.sessionFile` 有定義值。\n\n### 記憶體模式\n\n```ts\nimport { createAgentSession, SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.inMemory(),\n});\n\nconsole.log(session.sessionFile); // undefined\n```\n\n- 不進行檔案系統持久化。\n- 適用於測試、臨時工作者、請求範圍的代理程式。\n- 會話方法仍可正常運作，但與持久化相關的行為（檔案繼續/分叉路徑）自然受到限制。\n\n### 繼續/開啟/列出輔助函式\n\n```ts\nimport { SessionManager } from \"@f5-sales-demo/xcsh\";\n\nconst recent = await SessionManager.continueRecent(process.cwd());\nconst listed = await SessionManager.list(process.cwd());\nconst opened = listed[0] ? await SessionManager.open(listed[0].path) : null;\n```\n\n## 模型與驗證串接\n\n`createAgentSession()` 使用 `ModelRegistry` + `AuthStorage` 進行模型選擇和 API 金鑰解析。\n\n### 明確串接\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst available = modelRegistry.getAvailable();\nif (available.length === 0) throw new Error(\"No authenticated models available\");\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n model: available[0],\n thinkingLevel: \"medium\",\n sessionManager: SessionManager.inMemory(),\n});\n```\n\n### 省略 `model` 時的選擇順序\n\n當未明確提供 `model`/`modelPattern` 時：\n\n1. 從現有會話還原模型（若可還原且金鑰可用）\n2. 設定中的預設模型角色（`default`）\n3. 具有有效驗證的第一個可用模型\n\n若還原失敗，`modelFallbackMessage` 將說明回退原因。\n\n### 驗證優先順序\n\n`AuthStorage.getApiKey(...)` 依以下順序解析：\n\n1. 執行階段覆寫（`setRuntimeApiKey`）\n2. 儲存在 `agent.db` 中的憑證\n3. 提供者環境變數\n4. 自訂提供者解析器回退（若已設定）\n\n## 事件訂閱模型\n\n使用 `session.subscribe(listener)` 訂閱；返回一個取消訂閱函式。\n\n```ts\nconst unsubscribe = session.subscribe(event => {\n switch (event.type) {\n  case \"agent_start\":\n  case \"turn_start\":\n  case \"tool_execution_start\":\n   break;\n  case \"message_update\":\n   if (event.assistantMessageEvent.type === \"text_delta\") {\n    process.stdout.write(event.assistantMessageEvent.delta);\n   }\n   break;\n }\n});\n```\n\n`AgentSessionEvent` 包含核心 `AgentEvent` 以及會話層級事件：\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n## 提示詞生命週期\n\n`session.prompt(text, options?)` 是主要進入點。\n\n行為說明：\n\n1. 選用的指令/範本展開（`/` 指令、自訂指令、檔案斜線指令、提示範本）\n2. 若目前正在串流中：\n    - 需要 `streamingBehavior: \"steer\" | \"followUp\"`\n    - 加入佇列而非丟棄工作\n3. 若處於閒置狀態：\n    - 驗證模型 + API 金鑰\n    - 附加使用者訊息\n    - 啟動代理程式輪次\n\n相關 API：\n\n- `sendUserMessage(content, { deliverAs? })`\n- `steer(text, images?)`\n- `followUp(text, images?)`\n- `sendCustomMessage({ customType, content, ... }, { deliverAs?, triggerTurn? })`\n- `abort()`\n\n## 工具與擴充套件整合\n\n### 內建工具與篩選\n\n- 內建工具來自 `createTools(...)` 和 `BUILTIN_TOOLS`。\n- `toolNames` 作為內建工具的允許清單。\n- `customTools` 和擴充套件註冊的工具仍會包含在內。\n- 隱藏工具（例如 `submit_result`）需明確選用，除非選項要求。\n\n```ts\nconst { session } = await createAgentSession({\n toolNames: [\"read\", \"grep\", \"find\", \"write\"],\n requireSubmitResultTool: true,\n});\n```\n\n### 擴充套件\n\n- `extensions`：內嵌 `ExtensionFactory[]`\n- `additionalExtensionPaths`：載入額外的擴充套件檔案\n- `disableExtensionDiscovery`：停用自動擴充套件掃描\n- `preloadedExtensions`：重複使用已載入的擴充套件集合\n\n### 執行階段工具集變更\n\n`AgentSession` 支援執行階段啟用更新：\n\n- `getActiveToolNames()`\n- `getAllToolNames()`\n- `setActiveToolsByName(names)`\n- `refreshMCPTools(mcpTools)`\n\n系統提示詞將重新建立以反映啟用工具的變更。\n\n## 探索輔助函式\n\n當您需要部分控制而不重建內部探索邏輯時，請使用以下函式：\n\n- `discoverAuthStorage(agentDir?)`\n- `discoverExtensions(cwd?)`\n- `discoverSkills(cwd?, _agentDir?, settings?)`\n- `discoverContextFiles(cwd?, _agentDir?)`\n- `discoverPromptTemplates(cwd?, agentDir?)`\n- `discoverSlashCommands(cwd?)`\n- `discoverCustomTSCommands(cwd?, agentDir?)`\n- `discoverMCPServers(cwd?)`\n- `buildSystemPrompt(options?)`\n\n## 子代理程式導向選項\n\n適用於構建協調器的 SDK 使用者（類似任務執行器流程）：\n\n- `outputSchema`：將結構化輸出預期傳遞至工具上下文\n- `requireSubmitResultTool`：強制包含 `submit_result` 工具\n- `taskDepth`：巢狀任務會話的遞迴深度上下文\n- `parentTaskPrefix`：巢狀任務輸出的產出物命名前綴\n\n這些對於一般單一代理程式嵌入而言皆為選用。\n\n## `createAgentSession()` 返回值\n\n```ts\ntype CreateAgentSessionResult = {\n session: AgentSession;\n extensionsResult: LoadExtensionsResult;\n setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;\n mcpManager?: MCPManager;\n modelFallbackMessage?: string;\n lspServers?: Array<{ name: string; status: \"ready\" | \"error\"; fileTypes: string[]; error?: string }>;\n};\n```\n\n僅當您的嵌入器提供工具/擴充套件應呼叫的 UI 功能時，才需使用 `setToolUIContext(...)`。\n\n## 最小受控嵌入範例\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n Settings,\n} from \"@f5-sales-demo/xcsh\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst settings = Settings.isolated({\n \"compaction.enabled\": true,\n \"retry.enabled\": true,\n});\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n settings,\n sessionManager: SessionManager.inMemory(),\n toolNames: [\"read\", \"grep\", \"find\", \"edit\", \"write\"],\n enableMCP: false,\n enableLsp: true,\n});\n\nsession.subscribe(event => {\n if (event.type === \"message_update\" && event.assistantMessageEvent.type === \"text_delta\") {\n  process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Find all TODO comments in this repo and propose fixes.\");\nawait session.dispose();\n```\n",
	"zh-tw/configuration/secrets.md": "---\ntitle: 秘密混淆\ndescription: 秘密混淆管線，可從工作階段日誌和輸出中遮蔽敏感值。\nsidebar:\n  order: 3\n  label: 秘密\ni18n:\n  sourceHash: 1d9dc101c614\n  translator: machine\n---\n\n# 秘密混淆\n\n防止敏感值（API 金鑰、權杖、密碼）被傳送至 LLM 提供者。啟用後，秘密在離開程序之前會被替換為確定性的佔位符，並在模型回傳的工具呼叫引數中還原。\n\n## 啟用\n\n預設為啟用。可透過 `/settings` UI 切換，或直接在 `config.yml` 中設定：\n\n```yaml\nsecrets:\n  enabled: false\n```\n\n## 運作方式\n\n1. 在工作階段啟動時，從兩個來源收集秘密：\n   - **環境變數**：符合常見秘密模式（`*_KEY`、`*_SECRET`、`*_TOKEN`、`*_PASSWORD` 等）且值長度 >= 8 個字元\n   - **`secrets.yml` 檔案**（見下方）\n\n2. 傳送至 LLM 的外送訊息中，所有秘密值會被替換為如 `<<$env:S0>>`、`<<$env:S1>>` 等佔位符。\n\n3. 模型回傳的工具呼叫引數會被深層遍歷，佔位符會在執行前還原為原始值。\n\n兩種模式控制每個秘密的處理方式：\n\n| 模式 | 行為 | 可還原 |\n|---|---|---|\n| `obfuscate`（預設） | 替換為索引佔位符 `<<$env:SN>>` | 是（在工具引數中還原） |\n| `replace` | 替換為確定性的等長字串 | 否（單向） |\n\n## secrets.yml\n\n以 YAML 定義自訂秘密項目。會檢查兩個位置：\n\n| 層級 | 路徑 | 用途 |\n|---|---|---|\n| 全域 | `~/.xcsh/agent/secrets.yml` | 跨所有專案的秘密 |\n| 專案 | `<cwd>/.xcsh/secrets.yml` | 專案特定的秘密 |\n\n具有相符 `content` 的專案項目會覆蓋全域項目。\n\n### 結構定義\n\n陣列中的每個項目具有以下欄位：\n\n| 欄位 | 類型 | 必填 | 說明 |\n|---|---|---|---|\n| `type` | `\"plain\"` 或 `\"regex\"` | 是 | 比對策略 |\n| `content` | string | 是 | 秘密值（plain）或正規表達式模式（regex） |\n| `mode` | `\"obfuscate\"` 或 `\"replace\"` | 否 | 預設：`\"obfuscate\"` |\n| `replacement` | string | 否 | 自訂替換值（僅限 replace 模式） |\n| `flags` | string | 否 | 正規表達式旗標（僅限 regex 類型） |\n\n### 範例\n\n#### 純文字秘密\n\n```yaml\n# Obfuscate a specific API key (default mode)\n- type: plain\n  content: sk-proj-abc123def456\n\n# Replace a database password with a fixed string\n- type: plain\n  content: hunter2\n  mode: replace\n  replacement: \"********\"\n```\n\n#### 正規表達式秘密\n\n```yaml\n# Obfuscate any AWS-style key\n- type: regex\n  content: \"AKIA[0-9A-Z]{16}\"\n\n# Case-insensitive match with explicit flags\n- type: regex\n  content: \"api[_-]?key\\\\s*=\\\\s*\\\\w+\"\n  flags: \"i\"\n\n# Regex literal syntax (pattern and flags in one string)\n- type: regex\n  content: \"/bearer\\\\s+[a-zA-Z0-9._~+\\\\/=-]+/i\"\n```\n\n正規表達式項目始終會全域掃描（`g` 旗標會自動強制套用）。正規表達式字面語法 `/pattern/flags` 可作為分開使用 `content` + `flags` 欄位的替代方案。模式中的跳脫斜線（`\\\\/`）會被正確處理。\n\n#### 使用正規表達式的 replace 模式\n\n```yaml\n# One-way replace connection strings (not reversible)\n- type: regex\n  content: \"postgres://[^\\\\s]+\"\n  mode: replace\n  replacement: \"postgres://***\"\n```\n\n## 與環境變數偵測的互動\n\n環境變數始終會最先收集。檔案定義的項目會在之後附加，因此檔案項目可以涵蓋不在環境變數中的秘密（設定檔、寫死的值等）。如果相同的值同時出現在兩者中，檔案項目的模式會優先生效。\n\n## 關鍵檔案\n\n- `src/secrets/index.ts` -- 載入、合併、環境變數收集\n- `src/secrets/obfuscator.ts` -- `SecretObfuscator` 類別、佔位符產生、訊息混淆\n- `src/secrets/regex.ts` -- 正規表達式字面解析與編譯\n- `src/config/settings-schema.ts` -- `secrets.enabled` 設定定義\n",
	"zh-tw/extensions/extension-loading.md": "---\ntitle: 擴充功能載入（TypeScript/JavaScript 模組）\ndescription: 擴充功能的 TypeScript 與 JavaScript 模組載入管線，包含解析、驗證與快取。\nsidebar:\n  order: 2\n  label: 擴充功能載入\ni18n:\n  sourceHash: a8cea231c660\n  translator: machine\n---\n\n# 擴充功能載入（TypeScript/JavaScript 模組）\n\n本文件涵蓋編碼代理程式如何在啟動時探索並載入**擴充功能模組**（`.ts`/`.js`）。\n\n本文件**不**涵蓋 `gemini-extension.json` 清單擴充功能（另有獨立文件說明）。\n\n## 此子系統的功能\n\n擴充功能載入會建立模組入口檔案清單，使用 Bun 匯入每個模組，執行其工廠函式，並回傳：\n\n- 已載入的擴充功能定義\n- 依路徑分類的載入錯誤（不會中止整體載入流程）\n- 一個共用的擴充功能執行時期物件，供後續 `ExtensionRunner` 使用\n\n## 主要實作檔案\n\n- `src/extensibility/extensions/loader.ts` — 路徑探索 + 匯入/執行\n- `src/extensibility/extensions/index.ts` — 公開匯出\n- `src/extensibility/extensions/runner.ts` — 載入後的執行時期/事件執行\n- `src/discovery/builtin.ts` — 擴充功能模組的原生自動探索提供者\n- `src/config/settings.ts` — 載入合併後的 `extensions` / `disabledExtensions` 設定\n\n---\n\n## 擴充功能載入的輸入來源\n\n### 1) 自動探索的原生擴充功能模組\n\n`discoverAndLoadExtensions()` 首先向探索提供者請求具有 `extension-module` 能力的項目，然後僅保留提供者為 `native` 的項目。\n\n有效的原生位置：\n\n- 專案：`<cwd>/.xcsh/extensions`\n- 使用者：`~/.xcsh/agent/extensions`\n\n路徑根目錄來自原生提供者（`SOURCE_PATHS.native`）。\n\n注意事項：\n\n- 原生自動探索目前以 `.xcsh` 為基礎。\n- 舊版 `.pi` 在 `package.json` 清單鍵值（`pi.extensions`）中仍被接受，但在此處不作為原生根目錄。\n\n### 2) 明確設定的路徑\n\n自動探索完成後，會附加並解析已設定的路徑。\n\n在主要會話啟動路徑（`sdk.ts`）中的設定路徑來源：\n\n1. CLI 提供的路徑（`--extension/-e`，`--hook` 也被視為擴充功能路徑）\n2. 設定中的 `extensions` 陣列（合併全域 + 專案設定）\n\n全域設定檔案：\n\n- `~/.xcsh/agent/config.yml`（或透過 `PI_CODING_AGENT_DIR` 自訂代理程式目錄）\n\n專案設定檔案：\n\n- `<cwd>/.xcsh/settings.json`\n\n範例：\n\n```yaml\n# ~/.xcsh/agent/config.yml\nextensions:\n  - ~/my-exts/safety.ts\n  - ./local/ext-pack\n```\n\n```json\n{\n  \"extensions\": [\"./.xcsh/extensions/my-extra\"]\n}\n```\n\n---\n\n## 啟用/停用控制\n\n### 停用探索\n\n- CLI：`--no-extensions`\n- SDK 選項：`disableExtensionDiscovery`\n\n行為差異：\n\n- SDK：當 `disableExtensionDiscovery=true` 時，仍會透過 `loadExtensions()` 載入 `additionalExtensionPaths`。\n- CLI 路徑建構（`main.ts`）在設定 `--no-extensions` 時會清除 CLI 擴充功能路徑，因此在該模式下明確指定的 `-e/--hook` 不會被轉發。\n\n### 停用特定擴充功能模組\n\n`disabledExtensions` 設定依擴充功能 ID 格式進行過濾：\n\n- `extension-module:<derivedName>`\n\n`derivedName` 基於入口路徑（`getExtensionNameFromPath`），例如：\n\n- `/x/foo.ts` -> `foo`\n- `/x/bar/index.ts` -> `bar`\n\n範例：\n\n```yaml\ndisabledExtensions:\n  - extension-module:foo\n```\n\n---\n\n## 路徑與入口解析\n\n### 路徑正規化\n\n對於已設定的路徑：\n\n1. 正規化 Unicode 空格\n2. 展開 `~`\n3. 如果是相對路徑，則相對於目前 `cwd` 解析\n\n### 如果設定的路徑是檔案\n\n直接作為模組入口候選使用。\n\n### 如果設定的路徑是目錄\n\n解析順序：\n\n1. 該目錄中的 `package.json` 包含 `xcsh.extensions`（或舊版 `pi.extensions`）-> 使用宣告的入口\n2. `index.ts`\n3. `index.js`\n4. 否則掃描一層以尋找擴充功能入口：\n   - 直接的 `*.ts` / `*.js`\n   - 子目錄的 `index.ts` / `index.js`\n   - 子目錄的 `package.json` 包含 `xcsh.extensions` / `pi.extensions`\n\n規則與限制：\n\n- 不會遞迴探索超過一個子目錄層級\n- 宣告的 `extensions` 清單入口相對於該套件目錄解析\n- 宣告的入口僅在檔案存在/允許存取時才會被包含\n- 在 `*/index.{ts,js}` 配對中，TypeScript 優先於 JavaScript\n- 符號連結被視為合格的檔案/目錄\n\n### 忽略行為因來源而異\n\n- 原生自動探索（探索輔助工具中的 `discoverExtensionModulePaths`）使用原生 glob 並設定 `gitignore: true` 和 `hidden: false`。\n- `loader.ts` 中的明確設定目錄掃描使用 `readdir` 規則，**不會**套用 gitignore 過濾。\n\n---\n\n## 載入順序與優先順序\n\n`discoverAndLoadExtensions()` 建立一個有序清單，然後呼叫 `loadExtensions()`。\n\n順序：\n\n1. 原生自動探索的模組\n2. 明確設定的路徑（依提供順序）\n\n在 `sdk.ts` 中，設定順序為：\n\n1. CLI 額外路徑\n2. 設定中的 `extensions`\n\n去重複：\n\n- 基於絕對路徑\n- 先出現的路徑優先\n- 後續重複項目被忽略\n\n意涵：如果相同的模組路徑同時被自動探索和明確設定，它只會在第一個位置（自動探索階段）載入一次。\n\n---\n\n## 模組匯入與工廠合約\n\n每個候選路徑透過動態匯入載入：\n\n- `await import(resolvedPath)`\n- 工廠函式為 `module.default ?? module`\n- 工廠必須是一個函式（`ExtensionFactory`）\n\n如果匯出不是函式，該路徑會以結構化錯誤失敗，載入流程繼續進行。\n\n---\n\n## 失敗處理與隔離\n\n### 載入期間\n\n每個擴充功能路徑的失敗會被捕獲為 `{ path, error }`，不會阻止其他路徑的載入。\n\n常見情況：\n\n- 匯入失敗 / 檔案遺失\n- 無效的工廠匯出（非函式）\n- 執行工廠時拋出例外\n\n### 執行時期隔離模型\n\n- 擴充功能**未被沙箱化**（同一程序/執行時期）。\n- 它們共用一個 `EventBus` 和一個 `ExtensionRuntime` 實例。\n- 載入期間，執行時期動作方法會刻意拋出 `ExtensionRuntimeNotInitializedError`；動作連線在後續的 `ExtensionRunner.initialize()` 中進行。\n\n### 載入後\n\n當事件透過 `ExtensionRunner` 執行時，處理器例外會被捕獲並作為擴充功能錯誤發出，而不會導致執行器迴圈崩潰。\n\n---\n\n## 最小的使用者/專案配置範例\n\n### 使用者層級\n\n```text\n~/.xcsh/agent/\n  config.yml\n  extensions/\n    guardrails.ts\n    audit/\n      index.ts\n```\n\n### 專案層級\n\n```text\n<repo>/\n  .xcsh/\n    settings.json\n    extensions/\n      checks/\n        package.json\n      lint-gates.ts\n```\n\n`checks/package.json`：\n\n```json\n{\n  \"xcsh\": {\n    \"extensions\": [\"./src/check-a.ts\", \"./src/check-b.js\"]\n  }\n}\n```\n\n舊版清單鍵值仍被接受：\n\n```json\n{\n  \"pi\": {\n    \"extensions\": [\"./index.ts\"]\n  }\n}\n```\n",
	"zh-tw/extensions/extensions.md": "---\ntitle: 擴充功能\ndescription: 擴充功能執行時期概覽，涵蓋類型、執行器生命週期、註冊與探索。\nsidebar:\n  order: 1\n  label: 概覽\ni18n:\n  sourceHash: 14cc16dbd98b\n  translator: machine\n---\n\n# 擴充功能\n\n`packages/coding-agent` 中撰寫執行時期擴充功能的主要指南。\n\n本文件涵蓋以下檔案中的現行擴充功能執行時期：\n\n- `src/extensibility/extensions/types.ts`\n- `src/extensibility/extensions/runner.ts`\n- `src/extensibility/extensions/wrapper.ts`\n- `src/extensibility/extensions/index.ts`\n- `src/modes/controllers/extension-ui-controller.ts`\n\n關於探索路徑與檔案系統載入規則，請參閱 `docs/extension-loading.md`。\n\n## 什麼是擴充功能\n\n擴充功能是一個匯出預設工廠函式的 TS/JS 模組：\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nexport default function myExtension(pi: ExtensionAPI) {\n // register handlers/tools/commands/renderers\n}\n```\n\n擴充功能可以在單一模組中組合以下所有功能：\n\n- 事件處理器（`pi.on(...)`）\n- 可供 LLM 呼叫的工具（`pi.registerTool(...)`）\n- 斜線命令（`pi.registerCommand(...)`）\n- 鍵盤快捷鍵與旗標\n- 自訂訊息渲染\n- 工作階段/訊息注入 API（`sendMessage`、`sendUserMessage`、`appendEntry`）\n\n## 執行時期模型\n\n1. 擴充功能被匯入，其工廠函式隨即執行。\n2. 在載入階段期間，註冊方法有效；執行時期動作方法尚未初始化。\n3. `ExtensionRunner.initialize(...)` 為當前模式連接即時動作/上下文。\n4. 工作階段/代理程式/工具生命週期事件會發送至處理器。\n5. 每次工具執行都會以擴充功能攔截方式包裝（`tool_call` / `tool_result`）。\n\n```text\nExtension lifecycle (simplified)\n\nload paths\n   │\n   ▼\nimport module + run factory (registration only)\n   │\n   ▼\nExtensionRunner.initialize(mode/session/tool registry)\n   │\n   ├─ emit session/agent events to handlers\n   ├─ wrap tool execution (tool_call/tool_result)\n   └─ expose runtime actions (sendMessage, setActiveTools, ...)\n```\n\n來自 `loader.ts` 的重要限制：\n\n- 在擴充功能載入期間呼叫 `pi.sendMessage()` 等動作方法會拋出 `ExtensionRuntimeNotInitializedError`\n- 請先進行註冊；再從事件/命令/工具中執行執行時期行為\n\n## 快速開始\n\n```ts\nimport type { ExtensionAPI } from \"@f5-sales-demo/xcsh\";\nimport { Type } from \"@sinclair/typebox\";\n\nexport default function (pi: ExtensionAPI) {\n pi.setLabel(\"Safety + Utilities\");\n\n pi.on(\"session_start\", async (_event, ctx) => {\n  ctx.ui.notify(`Extension loaded in ${ctx.cwd}`, \"info\");\n });\n\n pi.on(\"tool_call\", async (event) => {\n  if (event.toolName === \"bash\" && event.input.command?.includes(\"rm -rf\")) {\n   return { block: true, reason: \"Blocked by extension policy\" };\n  }\n });\n\n pi.registerTool({\n  name: \"hello_extension\",\n  label: \"Hello Extension\",\n  description: \"Return a greeting\",\n  parameters: Type.Object({ name: Type.String() }),\n  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {\n   return {\n    content: [{ type: \"text\", text: `Hello, ${params.name}` }],\n    details: { greeted: params.name },\n   };\n  },\n });\n\n pi.registerCommand(\"hello-ext\", {\n  description: \"Show queue state\",\n  handler: async (_args, ctx) => {\n   ctx.ui.notify(`pending=${ctx.hasPendingMessages()}`, \"info\");\n  },\n });\n}\n```\n\n## 擴充功能 API 介面\n\n## 1) 註冊與動作（`ExtensionAPI`）\n\n核心方法：\n\n- `on(event, handler)`\n- `registerTool`、`registerCommand`、`registerShortcut`、`registerFlag`\n- `registerMessageRenderer`\n- `sendMessage`、`sendUserMessage`、`appendEntry`\n- `getActiveTools`、`getAllTools`、`setActiveTools`\n- `getSessionName`、`setSessionName`\n- `setModel`、`getThinkingLevel`、`setThinkingLevel`\n- `registerProvider`\n- `events`（共用事件匯流排）\n\n在互動模式中，`input` 處理器會在內建的首次訊息自動標題檢查之前執行。從 `input` 呼叫 `await pi.setSessionName(...)` 的擴充功能可以設定持久化的工作階段名稱，並防止預設自動產生的標題在該工作階段中執行。\n\n另外公開：\n\n- `pi.logger`\n- `pi.typebox`\n- `pi.pi`（套件匯出）\n\n### 訊息傳送語意\n\n`pi.sendMessage(message, options)` 支援：\n\n- `deliverAs: \"steer\"`（預設）— 中斷當前執行\n- `deliverAs: \"followUp\"` — 排隊於當前執行結束後執行\n- `deliverAs: \"nextTurn\"` — 儲存並在下一次使用者提示時注入\n- `triggerTurn: true` — 在閒置時啟動一個回合（`nextTurn` 會忽略此項）\n\n`pi.sendUserMessage(content, { deliverAs })` 始終通過提示流程；串流期間會以 steer/follow-up 方式排隊。\n\n## 2) 處理器上下文（`ExtensionContext`）\n\n處理器與工具 `execute` 會接收包含以下內容的 `ctx`：\n\n- `ui`\n- `hasUI`\n- `cwd`\n- `sessionManager`（唯讀）\n- `modelRegistry`、`model`\n- `getContextUsage()`\n- `compact(...)`\n- `isIdle()`、`hasPendingMessages()`、`abort()`\n- `shutdown()`\n- `getSystemPrompt()`\n\n## 3) 命令上下文（`ExtensionCommandContext`）\n\n命令處理器額外提供：\n\n- `waitForIdle()`\n- `newSession(...)`\n- `switchSession(...)`\n- `branch(entryId)`\n- `navigateTree(targetId, { summarize })`\n- `reload()`\n\n將命令上下文用於工作階段控制流程；這些方法刻意與一般事件處理器分離。\n\n## 事件介面（現行名稱與行為）\n\n標準事件聯合型別與載荷類型位於 `types.ts`。\n\n### 工作階段生命週期\n\n- `session_start`\n- `session_before_switch` / `session_switch`\n- `session_before_branch` / `session_branch`\n- `session_before_compact` / `session.compacting` / `session_compact`\n- `session_before_tree` / `session_tree`\n- `session_shutdown`\n\n可取消的預備事件：\n\n- `session_before_switch` → `{ cancel?: boolean }`\n- `session_before_branch` → `{ cancel?: boolean; skipConversationRestore?: boolean }`\n- `session_before_compact` → `{ cancel?: boolean; compaction?: CompactionResult }`\n- `session_before_tree` → `{ cancel?: boolean; summary?: { summary: string; details?: unknown } }`\n\n### 提示與回合生命週期\n\n- `input`\n- `before_agent_start`\n- `context`\n- `agent_start` / `agent_end`\n- `turn_start` / `turn_end`\n- `message_start` / `message_update` / `message_end`\n\n### 工具生命週期\n\n- `tool_call`（執行前，可封鎖）\n- `tool_result`（執行後，可修補 content/details/isError）\n- `tool_execution_start` / `tool_execution_update` / `tool_execution_end`（可觀測性）\n\n`tool_result` 採用中介軟體風格：處理器依擴充功能順序執行，每個處理器都能看到先前的修改。\n\n### 可靠性/執行時期訊號\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n\n### 使用者命令攔截\n\n- `user_bash`（以 `{ result }` 覆寫）\n- `user_python`（以 `{ result }` 覆寫）\n\n### `resources_discover`\n\n`resources_discover` 存在於擴充功能類型與 `ExtensionRunner` 中。\n現行執行時期注意事項：`ExtensionRunner.emitResourcesDiscover(...)` 已實作，但目前程式碼庫中沒有任何 `AgentSession` 呼叫點會呼叫它。\n\n## 工具撰寫詳情\n\n`registerTool` 使用來自 `types.ts` 的 `ToolDefinition`。\n\n現行 `execute` 簽章：\n\n```ts\nexecute(\n toolCallId,\n params,\n signal,\n onUpdate,\n ctx,\n): Promise<AgentToolResult>\n```\n\n範本：\n\n```ts\npi.registerTool({\n name: \"my_tool\",\n label: \"My Tool\",\n description: \"...\",\n parameters: Type.Object({}),\n async execute(_id, _params, signal, onUpdate, ctx) {\n  if (signal?.aborted) {\n   return { content: [{ type: \"text\", text: \"Cancelled\" }] };\n  }\n  onUpdate?.({ content: [{ type: \"text\", text: \"Working...\" }] });\n  return { content: [{ type: \"text\", text: \"Done\" }], details: {} };\n },\n onSession(event, ctx) {\n  // reason: start|switch|branch|tree|shutdown\n },\n renderCall(args, theme) {\n  // optional TUI render\n },\n renderResult(result, options, theme, args) {\n  // optional TUI render\n },\n});\n```\n\n`tool_call`/`tool_result` 一旦在 `sdk.ts` 中將登錄包裝，即會攔截所有工具，包括內建工具及擴充功能/自訂工具。\n\n## UI 整合點\n\n`ctx.ui` 實作 `ExtensionUIContext` 介面。各模式的支援程度有所不同。\n\n### 互動模式（`extension-ui-controller.ts`）\n\n支援的功能：\n\n- 對話框：`select`、`confirm`、`input`、`editor`\n- 通知/狀態/編輯器文字/終端機輸入/自訂覆蓋層\n- 主題列表/依名稱載入（`setTheme` 支援字串名稱）\n- 工具展開切換\n\n此控制器中目前為無操作的方法：\n\n- `setFooter`\n- `setHeader`\n- `setEditorComponent`\n\n另請注意：`setWidget` 目前透過 `setHookWidget(...)` 路由至狀態列文字。\n\n### RPC 模式（`rpc-mode.ts`）\n\n`ctx.ui` 由 RPC `extension_ui_request` 事件支援：\n\n- 對話框方法（`select`、`confirm`、`input`、`editor`）往返於客戶端回應\n- 即發即忘方法會發出請求（`notify`、`setStatus`、字串陣列的 `setWidget`、`setTitle`、`setEditorText`）\n\nRPC 實作中不支援/無操作的功能：\n\n- `onTerminalInput`\n- `custom`\n- `setFooter`、`setHeader`、`setEditorComponent`\n- `setWorkingMessage`\n- 主題切換/載入（`setTheme` 回傳失敗）\n- 工具展開控制無效\n\n### 列印/無介面/子代理程式路徑\n\n當沒有 UI 上下文提供給執行器初始化時，`ctx.hasUI` 為 `false`，且方法為無操作/回傳預設值。\n\n### 背景互動模式\n\n背景模式會安裝非互動式 UI 上下文物件。在現行實作中，`ctx.hasUI` 可能仍為 `true`，而互動式對話框則回傳預設值/無操作行為。\n\n## 工作階段與狀態模式\n\n若要持久化擴充功能狀態：\n\n1. 以 `pi.appendEntry(customType, data)` 進行持久化。\n2. 在 `session_start`、`session_branch`、`session_tree` 時，從 `ctx.sessionManager.getBranch()` 重建狀態。\n3. 當狀態應可從工具結果歷史中看見/重建時，保持工具結果 `details` 的結構化。\n\n重建範例模式：\n\n```ts\npi.on(\"session_start\", async (_event, ctx) => {\n let latest;\n for (const entry of ctx.sessionManager.getBranch()) {\n  if (entry.type === \"custom\" && entry.customType === \"my-state\") {\n   latest = entry.data;\n  }\n }\n // restore from latest\n});\n```\n\n## 渲染擴充點\n\n## 自訂訊息渲染器\n\n```ts\npi.registerMessageRenderer(\"my-type\", (message, { expanded }, theme) => {\n // return pi-tui Component\n});\n```\n\n在顯示自訂訊息時，由互動渲染使用。\n\n## 工具呼叫/結果渲染器\n\n在 `registerTool` 定義上提供 `renderCall` / `renderResult`，以在 TUI 中自訂工具視覺化呈現。\n\n## 限制與常見陷阱\n\n- 執行時期動作在擴充功能載入期間無法使用。\n- `tool_call` 錯誤會封鎖執行（封閉式失敗）。\n- 與內建命令名稱衝突的命令會被略過並記錄診斷資訊。\n- 保留的快捷鍵會被忽略（`ctrl+c`、`ctrl+d`、`ctrl+z`、`ctrl+k`、`ctrl+p`、`ctrl+l`、`ctrl+o`、`ctrl+t`、`ctrl+g`、`shift+tab`、`shift+ctrl+p`、`alt+enter`、`escape`、`enter`）。\n- 將 `ctx.reload()` 視為當前命令處理器框架的終止操作。\n\n## 擴充功能 vs 掛鉤 vs 自訂工具\n\n選用正確的介面：\n\n- **擴充功能**（`src/extensibility/extensions/*`）：統一系統（事件 + 工具 + 命令 + 渲染器 + 提供者註冊）。\n- **掛鉤**（`src/extensibility/hooks/*`）：獨立的舊版事件 API。\n- **自訂工具**（`src/extensibility/custom-tools/*`）：以工具為主的模組；與擴充功能一起載入時，會被適配並仍通過擴充功能攔截包裝器。\n\n如果您需要一個統一管理政策、工具、命令 UX 與渲染的套件，請使用擴充功能。\n",
	"zh-tw/extensions/gemini-manifest-extensions.md": "---\ntitle: Gemini 清單擴充功能\ndescription: 用於跨平台技能與代理相容性的 Gemini 清單擴充功能格式。\nsidebar:\n  order: 7\n  label: Gemini 清單\ni18n:\n  sourceHash: 7134165a5f6d\n  translator: machine\n---\n\n# Gemini 清單擴充功能（`gemini-extension.json`）\n\n本文件說明程式碼代理如何探索並解析 Gemini 風格的清單擴充功能（`gemini-extension.json`）至 `extensions` 能力。\n\n本文件**不**涵蓋 TypeScript/JavaScript 擴充功能模組載入（`extensions/*.ts`、`index.ts`、`package.json xcsh.extensions`），相關說明請參閱 `extension-loading.md`。\n\n## 實作檔案\n\n- [`../src/discovery/gemini.ts`](../../packages/coding-agent/src/discovery/gemini.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/capability/extension.ts`](../../packages/coding-agent/src/capability/extension.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/loader.ts`](../../packages/coding-agent/src/extensibility/extensions/loader.ts)\n\n---\n\n## 探索範圍\n\nGemini 提供者（`id: gemini`，優先順序 `60`）註冊一個 `extensions` 載入器，用於掃描兩個固定根目錄：\n\n- 使用者：`~/.gemini/extensions`\n- 專案：`<cwd>/.gemini/extensions`\n\n路徑解析透過 `getUserPath()` / `getProjectPath()` 直接從 `ctx.home` 和 `ctx.cwd` 取得。\n\n重要範圍規則：專案查找**僅限於 cwd**，不會向上遍歷父目錄。\n\n---\n\n## 目錄掃描規則\n\n針對每個根目錄（`~/.gemini/extensions` 與 `<cwd>/.gemini/extensions`），探索程序執行以下步驟：\n\n1. `readDirEntries(root)`\n2. 僅保留直接子目錄（`entry.isDirectory()`）\n3. 對每個子目錄 `<name>`，嘗試讀取：\n   - `<root>/<name>/gemini-extension.json`\n\n掃描不會超過一層目錄深度。\n\n### 隱藏目錄\n\nGemini 清單探索**不會**過濾以點號開頭的目錄名稱。若隱藏子目錄存在且包含 `gemini-extension.json`，則會納入探索。\n\n### 缺失或無法讀取的檔案\n\n若 `gemini-extension.json` 缺失或無法讀取，該目錄將被靜默略過（不發出警告）。\n\n---\n\n## 清單結構（依實作定義）\n\n能力類型定義了以下清單結構：\n\n```ts\ninterface ExtensionManifest {\n name?: string;\n description?: string;\n mcpServers?: Record<string, Omit<MCPServer, \"name\" | \"_source\">>;\n tools?: unknown[];\n context?: unknown;\n}\n```\n\n探索階段的行為刻意保持寬鬆：\n\n- 需要 JSON 解析成功。\n- 除 JSON 語法外，不對欄位類型或內容進行執行期綱要驗證。\n- 解析後的物件以 `manifest` 形式儲存於能力項目上。\n\n### 名稱正規化\n\n`Extension.name` 的設定規則：\n\n1. 若 `manifest.name` 不為 `null`/`undefined`，則使用該值\n2. 否則使用擴充功能目錄名稱\n\n此處不強制套用字串類型檢查。\n\n---\n\n## 具體化為能力項目\n\n成功解析的清單將建立一個 `Extension` 能力項目：\n\n```ts\n{\n name: manifest.name ?? <directory-name>,\n path: <extension-directory>,\n manifest: <parsed-json>,\n level: \"user\" | \"project\",\n _source: {\n  provider: \"gemini\",\n  providerName: \"Gemini CLI\" // 由能力登錄表附加\n  path: <absolute-manifest-path>,\n  level: \"user\" | \"project\"\n }\n}\n```\n\n注意事項：\n\n- `_source.path` 由 `createSourceMeta()` 正規化為絕對路徑。\n- 登錄表層級的 `extensions` 能力驗證僅檢查 `name` 與 `path` 是否存在。\n- 清單內部欄位（`mcpServers`、`tools`、`context`）在探索階段不進行驗證。\n\n---\n\n## 錯誤處理與警告語義\n\n### 會發出警告\n\n- 清單檔案中的 JSON 無效：\n  - 警告格式：`Invalid JSON in <manifestPath>`\n\n### 不發出警告（靜默略過）\n\n- `extensions` 目錄不存在\n- 子目錄不含 `gemini-extension.json`\n- 清單檔案無法讀取\n- 清單 JSON 語法正確但語義異常或不完整\n\n這意味著部分有效性是可接受的：只有 JSON 語法錯誤才會觸發警告。\n\n---\n\n## 與其他來源的優先順序與去重\n\n`extensions` 能力由能力登錄表跨提供者彙整。\n\n目前提供此能力的提供者：\n\n- `native`（`packages/coding-agent/src/discovery/builtin.ts`）優先順序 `100`\n- `gemini`（`packages/coding-agent/src/discovery/gemini.ts`）優先順序 `60`\n\n去重鍵為 `ext.name`（`extensionCapability.key = ext => ext.name`）。\n\n### 跨提供者優先順序\n\n優先順序較高的提供者在擴充功能名稱重複時獲勝。\n\n- 若 `native` 與 `gemini` 均輸出名稱為 `foo` 的擴充功能，保留 native 的項目。\n- 優先順序較低的重複項目僅保留於 `result.all` 中，並標記 `_shadowed = true`。\n\n### 提供者內部排序效果\n\n由於去重採用「先出現者優先」策略，提供者內部的項目順序至關重要。\n\n- Gemini 載入器的附加順序為**使用者優先**，其次為專案。\n- 因此，`~/.gemini/extensions` 與 `<cwd>/.gemini/extensions` 之間名稱重複時，保留使用者項目並遮蔽專案項目。\n\n相較之下，native 提供者透過 `getConfigDirs()` 以不同的設定目錄順序建立（先專案後使用者），因此 native 提供者內部的遮蔽方向相反。\n\n---\n\n## 使用者與專案行為摘要\n\n就 Gemini 清單而言：\n\n- 每次載入時均會掃描使用者與專案兩個根目錄。\n- 專案根目錄固定為 `<cwd>/.gemini/extensions`（不向上遍歷祖先目錄）。\n- Gemini 來源內部的名稱重複以使用者優先解析。\n- 與優先順序較高的提供者（尤其是 native）名稱重複時，依優先順序落敗。\n\n---\n\n## 邊界：探索中繼資料與執行期擴充功能載入\n\n`gemini-extension.json` 探索目前僅提供能力中繼資料（`Extension` 項目），**不會**直接載入可執行的 TS/JS 擴充功能模組。\n\n執行期模組載入（`discoverAndLoadExtensions()` / `loadExtensions()`）使用 `extension-modules` 及明確路徑，且目前僅篩選提供者為 `native` 的自動探索模組。\n\n實際影響：\n\n- Gemini 清單擴充功能可作為能力記錄被探索。\n- 它們本身不會被擴充功能載入器管線作為執行期擴充功能模組執行。\n\n此邊界在現行實作中是刻意設計的，也說明了清單探索與可執行模組載入為何可能出現差異。\n",
	"zh-tw/extensions/marketplace.md": "---\ntitle: 市集外掛系統\ndescription: 用於探索、安裝和管理精選外掛集合的市集外掛系統。\nsidebar:\n  order: 4\n  label: 市集\ni18n:\n  sourceHash: 71d9f8f93a81\n  translator: machine\n---\n\n# 市集外掛系統\n\n市集系統讓您可以從 Git 託管的目錄中探索、安裝和管理外掛。它與 Claude Code 外掛登錄格式相容。\n\n## 快速開始\n\n```\n/marketplace add anthropics/f5-sales-demo-marketplace\n/marketplace install wordpress.com@f5-sales-demo-marketplace\n```\n\n或直接輸入不帶任何參數的 `/marketplace`，以開啟互動式外掛瀏覽器。\n\n## 概念\n\n**市集**是一個 Git 儲存庫（或本地目錄），其中在 `.xcsh-plugin/marketplace.json` 位置包含一個目錄檔案。該目錄列出了可用的外掛及其來源、描述和中繼資料。\n\n**外掛**是一個包含技能、命令、鉤子、MCP 伺服器或 LSP 伺服器的目錄。外掛以 `name@marketplace` 的方式識別（例如 `code-review@f5-sales-demo-marketplace`）。\n\n**範圍**：外掛可以在兩種範圍下安裝：\n\n- **user**（預設）-- 在所有專案中可用，儲存於 `~/.xcsh/plugins/installed_plugins.json`\n- **project** -- 僅在目前專案中可用，儲存於 `.xcsh/installed_plugins.json`\n\n專案範圍的安裝會遮蔽同一外掛的使用者範圍安裝。\n\n## 命令\n\n### 互動模式\n\n| 命令 | 效果 |\n|---|---|\n| `/marketplace` | 開啟互動式外掛瀏覽器（安裝） |\n\n### 市集管理\n\n| 命令 | 效果 |\n|---|---|\n| `/marketplace add <source>` | 新增市集來源 |\n| `/marketplace remove <name>` | 移除市集 |\n| `/marketplace update [name]` | 重新擷取目錄；省略名稱則更新全部 |\n| `/marketplace list` | 列出已設定的市集 |\n\n### 外掛操作\n\n| 命令 | 效果 |\n|---|---|\n| `/marketplace discover [marketplace]` | 瀏覽可用外掛 |\n| `/marketplace install [--force] [--scope user\\|project] name@marketplace` | 安裝外掛 |\n| `/marketplace uninstall [--scope user\\|project] name@marketplace` | 解除安裝外掛 |\n| `/marketplace installed` | 列出已安裝的市集外掛 |\n| `/marketplace upgrade [--scope user\\|project] [name@marketplace]` | 升級一個或所有外掛 |\n\n### CLI 等效命令\n\n相同的操作也可透過命令列執行：\n\n```\nxcsh plugin marketplace add <source>\nxcsh plugin marketplace remove <name>\nxcsh plugin marketplace update [name]\nxcsh plugin marketplace list\nxcsh plugin discover [marketplace]\nxcsh plugin install --scope project name@marketplace\n```\n\n## 市集來源\n\n當您執行 `/marketplace add <source>` 時，系統會對來源進行分類：\n\n| 來源格式 | 類型 | 範例 |\n|---|---|---|\n| `owner/repo` | GitHub 簡寫 | `anthropics/f5-sales-demo-marketplace` |\n| `https://...*.json` | 直接目錄 URL | `https://example.com/marketplace.json` |\n| `https://...*.git` 或 `git@...` | Git 儲存庫 | `https://github.com/org/repo.git` |\n| `./path` 或 `~/path` 或 `/path` | 本地目錄 | `./my-marketplace` |\n\n系統會複製儲存庫（或讀取本地目錄），定位 `.xcsh-plugin/marketplace.json`，進行驗證，並在本地快取目錄。\n\n## 目錄格式（marketplace.json）\n\n市集目錄位於儲存庫根目錄的 `.xcsh-plugin/marketplace.json`：\n\n```json\n{\n  \"$schema\": \"https://anthropic.com/claude-code/marketplace.schema.json\",\n  \"name\": \"my-marketplace\",\n  \"owner\": {\n    \"name\": \"Your Name\",\n    \"email\": \"you@example.com\"\n  },\n  \"description\": \"A collection of plugins\",\n  \"plugins\": [\n    {\n      \"name\": \"my-plugin\",\n      \"description\": \"What this plugin does\",\n      \"source\": \"./plugins/my-plugin\",\n      \"category\": \"development\",\n      \"homepage\": \"https://github.com/you/my-plugin\"\n    }\n  ]\n}\n```\n\n### 必填欄位\n\n| 欄位 | 描述 |\n|---|---|\n| `name` | 市集名稱。小寫英數字元、連字號和點。必須以英數字元開頭和結尾。最多 64 個字元。 |\n| `owner.name` | 市集擁有者名稱 |\n| `plugins` | 外掛項目陣列 |\n\n### 外掛項目欄位\n\n| 欄位 | 必填 | 描述 |\n|---|---|---|\n| `name` | 是 | 外掛名稱（規則與市集名稱相同） |\n| `source` | 是 | 外掛的所在位置（見下方） |\n| `description` | 否 | 簡短描述 |\n| `version` | 否 | 版本字串 |\n| `author` | 否 | `{ name, email? }` |\n| `homepage` | 否 | URL |\n| `category` | 否 | 類別字串（例如 `development`、`productivity`、`security`） |\n| `tags` | 否 | 字串標籤陣列 |\n| `strict` | 否 | 布林值 |\n| `commands` | 否 | 提供的斜線命令 |\n| `agents` | 否 | 提供的代理程式 |\n| `hooks` | 否 | 鉤子定義 |\n| `mcpServers` | 否 | MCP 伺服器定義 |\n| `lspServers` | 否 | LSP 伺服器定義 |\n\n### 外掛來源格式\n\n`source` 欄位支援多種格式：\n\n**相對路徑**（在市集儲存庫內）：\n\n```json\n\"source\": \"./plugins/my-plugin\"\n```\n\n**Git 儲存庫 URL**：\n\n```json\n\"source\": {\n  \"source\": \"url\",\n  \"url\": \"https://github.com/org/repo.git\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**GitHub 簡寫**：\n\n```json\n\"source\": {\n  \"source\": \"github\",\n  \"repo\": \"org/repo\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**Git 子目錄**（單一儲存庫）：\n\n```json\n\"source\": {\n  \"source\": \"git-subdir\",\n  \"url\": \"https://github.com/org/monorepo.git\",\n  \"path\": \"plugins/my-plugin\",\n  \"ref\": \"main\",\n  \"sha\": \"abc123...\"\n}\n```\n\n**npm 套件**：\n\n```json\n\"source\": {\n  \"source\": \"npm\",\n  \"package\": \"@scope/my-plugin\",\n  \"version\": \"1.0.0\"\n}\n```\n\n## 磁碟上的目錄結構\n\n```\n~/.xcsh/\n  config/\n    marketplaces.json          # 已新增市集的登錄\n  plugins/\n    installed_plugins.json     # 使用者範圍已安裝的外掛\n    cache/\n      marketplaces/            # 已快取的市集目錄\n      plugins/                 # 已快取的外掛目錄\n\n<project>/.xcsh/\n  installed_plugins.json       # 專案範圍已安裝的外掛\n```\n\n## 命名規則\n\n市集和外掛名稱必須：\n\n- 以小寫字母或數字開頭和結尾\n- 僅包含小寫字母、數字、連字號和點\n- 最多 64 個字元\n\n外掛 ID（`name@marketplace`）總長度最多 128 個字元。\n\n有效範例：`my-plugin`、`code-review`、`wordpress.com`、`ai-firstify`\n無效範例：`-bad`、`bad-`、`.bad`、`Bad`、`under_score`\n",
	"zh-tw/extensions/plugin-manager-installer-plumbing.md": "---\ntitle: 套件管理器與安裝程式內部機制\ndescription: 套件管理器內部原理，涵蓋安裝、驗證、相依性解析及生命週期管理。\nsidebar:\n  order: 5\n  label: 套件管理器\ni18n:\n  sourceHash: 9c33e5a2c22a\n  translator: machine\n---\n\n# 套件管理器與安裝程式內部機制\n\n本文件說明 `xcsh plugin` 操作如何改變磁碟上的套件狀態，以及已安裝的套件如何成為執行時期功能（目前為工具，鉤子/指令路徑解析亦已可用）。\n\n## 範疇與架構\n\n程式碼庫中有兩套套件管理實作：\n\n1. **CLI 指令使用的主要路徑**：`PluginManager`（`src/extensibility/plugins/manager.ts`）\n2. **舊版輔助模組**：安裝程式函式（`src/extensibility/plugins/installer.ts`）\n\n`xcsh plugin ...` 指令執行會經過 `PluginManager`。\n\n`installer.ts` 仍記錄了重要的安全檢查與檔案系統行為，但並非 `src/commands/plugin.ts` + `src/cli/plugin-cli.ts` 所使用的路徑。\n\n## 生命週期：從 CLI 呼叫到執行時期可用性\n\n```text\nxcsh plugin <action> ...\n  -> src/commands/plugin.ts\n  -> runPluginCommand(...) in src/cli/plugin-cli.ts\n  -> PluginManager method (install/list/uninstall/link/...) \n  -> mutate ~/.xcsh/plugins/{package.json,node_modules,xcsh-plugins.lock.json}\n  -> runtime discovery: discoverAndLoadCustomTools(...)\n  -> getAllPluginToolPaths(cwd)\n  -> custom tool loader imports tool modules\n```\n\n### 指令進入點\n\n- `src/commands/plugin.ts` 定義指令/旗標並轉發至 `runPluginCommand`。\n- `src/cli/plugin-cli.ts` 將子指令對應至 `PluginManager` 方法：\n  - `install`、`uninstall`、`list`、`link`、`doctor`、`features`、`config`、`enable`、`disable`\n- 不存在明確的 `update` 動作；更新是透過以新套件/版本規格重新執行 `install` 來完成。\n\n## 磁碟模型\n\n全域套件狀態儲存於 `~/.xcsh/plugins`：\n\n- `package.json` — 由 `bun install`/`bun uninstall` 使用的相依性清單\n- `node_modules/` — 已安裝的套件或符號連結\n- `xcsh-plugins.lock.json` — 執行時期狀態：\n  - 每個套件的啟用/停用狀態\n  - 每個套件所選的功能集\n  - 持久化的套件設定\n\n專案本機覆寫設定儲存於：\n\n- `<cwd>/.xcsh/plugin-overrides.json`\n\n覆寫設定從管理器/載入器角度而言是唯讀的（此處無寫入路徑），可停用套件或覆寫此專案的功能/設定。\n\n## 套件規格解析與元資料解讀\n\n## 安裝規格語法\n\n`parsePluginSpec`（`parser.ts`）支援：\n\n- `pkg` -> `features: null`（預設行為）\n- `pkg[*]` -> 啟用清單中的所有功能\n- `pkg[]` -> 不啟用任何可選功能\n- `pkg[a,b]` -> 啟用具名功能\n- `@scope/pkg@1.2.3[feat]` -> 帶有明確功能選擇的有範疇 + 版本化套件\n\n`extractPackageName` 在安裝後的磁碟路徑查詢中會去除版本後綴。\n\n## 清單來源與必要欄位\n\n清單的解析順序為：\n\n1. `package.json.xcsh`\n2. 後備 `package.json.pi`\n3. 後備 `{ version: package.version }`\n\n影響：\n\n- 管理器/載入器中不存在嚴格的結構描述驗證。\n- 缺少 `xcsh`/`pi` 的套件仍可安裝並列出。\n- 執行時期套件載入（`getEnabledPlugins`）會跳過沒有 `xcsh`/`pi` 清單的套件。\n- `manifest.version` 一律從套件的 `version` 覆寫。\n\n格式錯誤的 `package.json` JSON 在讀取時即為嚴重失敗；格式錯誤的清單結構可能要等到特定欄位被使用時才會失敗。\n\n## 安裝/更新流程（`PluginManager.install`）\n\n1. 從安裝規格解析功能括號語法。\n2. 以正規表示式 + 殼層特殊字元拒絕清單驗證套件名稱。\n3. 確保套件 `package.json` 存在（`xcsh-plugins`、私有相依性對應）。\n4. 在 `~/.xcsh/plugins` 中執行 `bun install <packageSpec>`。\n5. 讀取已安裝套件的 `node_modules/<name>/package.json`。\n6. 解析清單並計算 `enabledFeatures`：\n   - `[*]`：所有已宣告的功能（若無功能對應則為 `null`）\n   - `[a,b]`：驗證每個功能是否存在於清單功能對應中\n   - `[]`：空功能清單\n   - 裸規格：`null`（稍後在載入器中使用預設策略）\n7. 在鎖定檔執行時期狀態中進行更新插入：`{ version, enabledFeatures, enabled: true }`。\n\n### 更新語意\n\n由於更新是由安裝驅動的：\n\n- `xcsh plugin install pkg@newVersion` 會更新相依性與鎖定檔版本。\n- 現有設定會保留；版本/功能/啟用狀態的條目會被覆寫。\n- 不存在獨立的「檢查更新」或交易式遷移邏輯。\n\n## 移除流程（`PluginManager.uninstall`）\n\n1. 驗證套件名稱。\n2. 在套件目錄中執行 `bun uninstall <name>`。\n3. 從鎖定檔中移除套件執行時期狀態：\n   - `config.plugins[name]`\n   - `config.settings[name]`\n\n若解除安裝指令失敗，執行時期狀態不會變更。\n\n## 列出流程（`PluginManager.list`）\n\n1. 從 `~/.xcsh/plugins/package.json` 讀取套件相依性對應。\n2. 載入鎖定檔執行時期設定（檔案不存在時 -> 空預設值）。\n3. 載入專案覆寫設定（`<cwd>/.xcsh/plugin-overrides.json`，解析/讀取錯誤 -> 帶有警告的空物件）。\n4. 對每個具有可解析 package.json 的相依性：\n   - 建立 `InstalledPlugin` 記錄\n   - 合併功能/啟用狀態：\n     - 基礎來自鎖定檔（或預設值）\n     - 專案覆寫可替換功能選擇\n     - 專案 `disabled` 清單會將套件標示為停用\n\n此為 CLI 狀態輸出及設定/功能操作所使用的有效狀態。\n\n## 連結流程（`PluginManager.link`）\n\n`link` 透過將本機套件符號連結至 `~/.xcsh/plugins/node_modules/<pkg.name>` 來支援本機套件開發。\n\n行為：\n\n1. 以管理器的 cwd 解析 `localPath`。\n2. 要求本機 `package.json` 及 `name` 欄位。\n3. 確保套件目錄存在。\n4. 若為有範疇名稱，建立範疇目錄。\n5. 移除目標連結位置的現有路徑。\n6. 建立符號連結。\n7. 以預設功能（`null`）新增已啟用的執行時期鎖定檔條目。\n\n注意：目前的 `PluginManager.link` 不強制執行舊版 `installer.ts` 中存在的 `cwd` 路徑邊界檢查（`normalizedPath.startsWith(normalizedCwd)`），因此信任由呼叫者負責。\n\n## 執行時期載入：從已安裝套件到可呼叫的功能\n\n## 探索關卡\n\n`getEnabledPlugins(cwd)`（`plugins/loader.ts`）讀取：\n\n- 套件相依性清單（`package.json`）\n- 鎖定檔執行時期狀態\n- 透過 `getConfigDirPaths(\"plugin-overrides.json\", { user: false, cwd })` 取得的專案覆寫設定\n\n過濾條件：\n\n- 若無套件 package.json 則跳過\n- 若清單（`xcsh`/`pi`）不存在則跳過\n- 若在鎖定檔中全域停用則跳過\n- 若專案停用則跳過\n\n## 功能路徑解析\n\n對每個已啟用的套件：\n\n- `resolvePluginToolPaths(plugin)`\n- `resolvePluginHookPaths(plugin)`\n- `resolvePluginCommandPaths(plugin)`\n\n每個解析器包含基礎條目加上功能條目：\n\n- 明確的功能清單 -> 僅選取的功能\n- `enabledFeatures === null` -> 啟用標記為 `default: true` 的功能\n\n遺失的檔案會被靜默略過（`existsSync` 防護）。\n\n## 目前執行時期連線差異\n\n- **工具今日已連線至執行時期**，透過 `discoverAndLoadCustomTools`（`custom-tools/loader.ts`），其呼叫 `getAllPluginToolPaths(cwd)`。\n- 路徑在自訂工具探索中以解析後的絕對路徑進行去重複（`seen` 集合，第一個路徑優先）。\n- **鉤子/指令解析器已存在**且已匯出，但此程式碼路徑目前並未以工具連線的相同方式將其連線至執行時期登錄中。\n\n## 鎖定/狀態管理細節\n\n`PluginManager` 在每個實例中快取執行時期設定於記憶體（`#runtimeConfig`），並於首次使用時延遲載入。\n\n載入行為：\n\n- 鎖定檔不存在 -> `{ plugins: {}, settings: {} }`\n- 鎖定檔讀取/解析失敗 -> 警告 + 相同的空預設值\n\n儲存行為：\n\n- 每次狀態異動都會寫入完整的鎖定檔 JSON（美化列印格式）\n\n不存在跨進程鎖定或合併策略；並行寫入者可能互相覆寫。\n\n## 安全檢查與信任邊界\n\n## 輸入/套件驗證\n\n主要管理器路徑強制執行套件名稱驗證：\n\n- 適用於有範疇/無範疇套件規格的正規表示式（可選帶版本）\n- 明確的殼層特殊字元拒絕清單（`[;&|`$(){}[]<>\\\\]`）\n\n這限制了呼叫 `bun install/uninstall` 時的指令注入風險。\n\n## 檔案系統信任邊界\n\n- 套件程式碼在匯入自訂工具模組時於同進程中執行；無沙箱化。\n- 清單相對路徑會相對於套件目錄進行合併，並僅進行存在性檢查。\n- 套件本身在安裝後即為受信任的程式碼。\n\n## 僅限舊版安裝程式的檢查\n\n`installer.ts` 包含未在 `PluginManager.link` 中鏡像的額外連結時檢查：\n\n- 本機路徑必須解析至專案 cwd 內部\n- 符號連結目標命名的額外套件名稱/路徑遍歷防護\n\n由於 CLI 使用 `PluginManager`，這些更嚴格的連結防護目前並不在主要路徑上。\n\n## 失敗、部分成功與回滾行為\n\n套件管理器並非交易式的。\n\n| 操作階段 | 失敗行為 | 回滾 |\n| --- | --- | --- |\n| `bun install` 失敗 | 安裝中止並顯示標準錯誤輸出 | 不適用（尚未進行狀態寫入） |\n| 安裝成功，然後清單/功能驗證失敗 | 指令失敗 | 無解除安裝回滾；相依性可能留在 `node_modules`/`package.json` 中 |\n| 安裝成功，然後鎖定檔寫入失敗 | 指令失敗 | 無已安裝套件的回滾 |\n| `bun uninstall` 成功，鎖定檔寫入失敗 | 指令失敗 | 套件已移除，可能留有過時的執行時期狀態 |\n| `link` 移除舊目標後符號連結建立失敗 | 指令失敗 | 無先前連結/目錄的還原 |\n\n在操作層面，`doctor --fix` 可修復部分不一致狀態（`bun install`、孤立設定清理、無效功能清理），但屬於盡力而為。\n\n## 格式錯誤/遺失清單行為摘要\n\n- 遺失 `xcsh`/`pi` 欄位：\n  - 安裝/列出：可容忍（最小清單）\n  - 執行時期已啟用套件探索：跳過為非套件\n- 安裝規格或 `features --set/--enable` 參照的遺失功能：嚴重錯誤，並顯示可用功能清單\n- 無效的 `plugin-overrides.json`：在管理器與載入器路徑中均被忽略，並後備至 `{}`\n- 清單參照的遺失工具/鉤子/指令檔案路徑：在解析器展開期間被靜默忽略；僅由 `doctor` 標記為錯誤\n\n## 模式差異與優先順序\n\n- `--dry-run`（安裝）：回傳模擬的安裝結果，不進行任何檔案系統/網路/狀態寫入。\n- `--json`：僅影響輸出格式，不改變行為。\n- 專案覆寫設定在功能/設定視圖中一律優先於全域鎖定檔。\n- 有效啟用狀態為 `runtimeEnabled && !projectDisabled`。\n\n## 實作檔案\n\n- [`src/commands/plugin.ts`](../../packages/coding-agent/src/commands/plugin.ts) — CLI 指令宣告與旗標對應\n- [`src/cli/plugin-cli.ts`](../../packages/coding-agent/src/cli/plugin-cli.ts) — 動作分派、面向使用者的指令處理器\n- [`src/extensibility/plugins/manager.ts`](../../packages/coding-agent/src/extensibility/plugins/manager.ts) — 主要安裝/移除/列出/連結/狀態/doctor 實作\n- [`src/extensibility/plugins/installer.ts`](../../packages/coding-agent/src/extensibility/plugins/installer.ts) — 舊版安裝程式輔助程式與額外連結安全檢查\n- [`src/extensibility/plugins/loader.ts`](../../packages/coding-agent/src/extensibility/plugins/loader.ts) — 已啟用套件探索與工具/鉤子/指令路徑解析\n- [`src/extensibility/plugins/parser.ts`](../../packages/coding-agent/src/extensibility/plugins/parser.ts) — 安裝規格與套件名稱解析輔助程式\n- [`src/extensibility/plugins/types.ts`](../../packages/coding-agent/src/extensibility/plugins/types.ts) — 清單/執行時期/覆寫型別合約\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts) — 套件提供的工具模組執行時期連線\n",
	"zh-tw/extensions/rulebook-matching-pipeline.md": "---\ntitle: 規則手冊匹配管線\ndescription: 用於選擇並套用情境特定指令集到代理工作階段的規則手冊匹配管線。\nsidebar:\n  order: 6\n  label: 規則手冊匹配\ni18n:\n  sourceHash: a16a9c565053\n  translator: machine\n---\n\n# 規則手冊匹配管線\n\n本文件描述 coding-agent 如何從支援的設定格式中發現規則、將其正規化為單一的 `Rule` 形狀、解決優先順序衝突，並將結果拆分為：\n\n- **規則手冊規則**（透過系統提示詞 + `rule://` URL 提供給模型使用）\n- **TTSR 規則**（時間旅行串流中斷規則）\n\n本文反映了目前的實作，包括部分語義以及已解析但未強制執行的中繼資料。\n\n## 實作檔案\n\n- [`../src/capability/rule.ts`](../../packages/coding-agent/src/capability/rule.ts)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/discovery/index.ts`](../../packages/coding-agent/src/discovery/index.ts)\n- [`../src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/cursor.ts`](../../packages/coding-agent/src/discovery/cursor.ts)\n- [`../src/discovery/windsurf.ts`](../../packages/coding-agent/src/discovery/windsurf.ts)\n- [`../src/discovery/cline.ts`](../../packages/coding-agent/src/discovery/cline.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/system-prompt.ts`](../../packages/coding-agent/src/system-prompt.ts)\n- [`../src/internal-urls/rule-protocol.ts`](../../packages/coding-agent/src/internal-urls/rule-protocol.ts)\n- [`../src/utils/frontmatter.ts`](../../packages/coding-agent/src/utils/frontmatter.ts)\n\n## 1. 標準規則形狀\n\n所有提供者將來源檔案正規化為 `Rule`：\n\n```ts\ninterface Rule {\n  name: string;\n  path: string;\n  content: string;\n  globs?: string[];\n  alwaysApply?: boolean;\n  description?: string;\n  ttsrTrigger?: string;\n  _source: SourceMeta;\n}\n```\n\n能力識別為 `rule.name`（`ruleCapability.key = rule => rule.name`）。\n\n結果：優先順序與去重**僅基於名稱**。兩個不同檔案若具有相同的 `name`，則被視為同一邏輯規則。\n\n## 2. 發現來源與正規化\n\n`src/discovery/index.ts` 自動註冊提供者。對於 `rules`，目前的提供者為：\n\n- `native`（優先順序 `100`）\n- `cursor`（優先順序 `50`）\n- `windsurf`（優先順序 `50`）\n- `cline`（優先順序 `40`）\n\n### Native 提供者（`builtin.ts`）\n\n從以下位置載入 `.xcsh` 規則：\n\n- 專案：`<cwd>/.xcsh/rules/*.{md,mdc}`\n- 使用者：`~/.xcsh/agent/rules/*.{md,mdc}`\n\n正規化：\n\n- `name` = 不含 `.md`/`.mdc` 的檔案名稱\n- 前置資料透過 `parseFrontmatter` 解析\n- `content` = 本文（前置資料已移除）\n- `globs`、`alwaysApply`、`description`、`ttsr_trigger` 直接對應\n\n重要注意事項：在此提供者中，`globs` 被轉型為 `string[] | undefined`，不會對元素進行過濾。\n\n### Cursor 提供者（`cursor.ts`）\n\n從以下位置載入：\n\n- 使用者：`~/.cursor/rules/*.{mdc,md}`\n- 專案：`<cwd>/.cursor/rules/*.{mdc,md}`\n\n正規化（`transformMDCRule`）：\n\n- `description`：僅在為字串時保留\n- `alwaysApply`：僅保留 `true`（`false` 變為 `undefined`）\n- `globs`：接受陣列（僅字串元素）或單一字串\n- `ttsr_trigger`：僅限字串\n- `name` 取自不含副檔名的檔案名稱\n\n### Windsurf 提供者（`windsurf.ts`）\n\n從以下位置載入：\n\n- 使用者：`~/.codeium/windsurf/memories/global_rules.md`（固定規則名稱 `global_rules`）\n- 專案：`<cwd>/.windsurf/rules/*.md`\n\n正規化：\n\n- `globs`：字串陣列或單一字串\n- `alwaysApply`、`description` 從前置資料轉型\n- `ttsr_trigger`：僅限字串\n- 專案規則的 `name` 取自檔案名稱\n\n### Cline 提供者（`cline.ts`）\n\n從 `cwd` 向上搜尋最近的 `.clinerules`：\n\n- 若為目錄：載入其中的 `*.md`\n- 若為檔案：載入單一檔案作為名稱為 `clinerules` 的規則\n\n正規化：\n\n- `globs`：字串陣列或單一字串\n- `alwaysApply`：僅在為布林值時保留\n- `description`：僅限字串\n- `ttsr_trigger`：僅限字串\n\n## 3. 前置資料解析行為與歧義\n\n所有提供者使用 `parseFrontmatter`（`utils/frontmatter.ts`），具有以下語義：\n\n1. 僅當內容以 `---` 開頭且有結尾的 `\\n---` 時才解析前置資料。\n2. 前置資料擷取後會修剪本文。\n3. 若 YAML 解析失敗：\n   - 記錄警告，\n   - 解析器退回至簡單的 `key: value` 逐行解析（`^(\\w+):\\s*(.*)$`）。\n\n歧義後果：\n\n- 退回解析器不支援陣列、巢狀物件、引號規則或含連字號的鍵。\n- 退回值變為字串（例如 `alwaysApply: true` 變為字串 `\"true\"`），因此需要布林/字串型別的提供者可能會丟棄中繼資料。\n- `ttsr_trigger` 在退回模式中可運作（底線鍵）；像 `thinking-level` 這樣的鍵則不行。\n- 沒有有效前置資料的檔案仍會作為具有空中繼資料和完整內容本文的規則載入。\n\n## 4. 提供者優先順序與去重\n\n`loadCapability(\"rules\")`（`capability/index.ts`）合併提供者輸出，然後依 `rule.name` 去重。\n\n### 優先順序模型\n\n- 提供者按優先順序降序排列。\n- 相同優先順序保持註冊順序（在 `discovery/index.ts` 中 `cursor` 在 `windsurf` 之前）。\n- 去重採先到先得：首先遇到的規則名稱被保留；後續同名項目在 `all` 中標記為 `_shadowed`，並從 `items` 中排除。\n\n目前有效的規則提供者順序為：\n\n1. `native`（100）\n2. `cursor`（50）\n3. `windsurf`（50）\n4. `cline`（40）\n\n### 提供者內部排序注意事項\n\n在提供者內部，項目順序來自 `loadFilesFromDir` glob 結果排序加上明確的 push 順序。這對於一般使用而言足夠確定性，但程式碼中並未明確排序。\n\n值得注意的來源順序差異：\n\n- `native` 先附加專案目錄，再附加使用者設定目錄。\n- `cursor` 先附加使用者結果，再附加專案結果。\n- `windsurf` 先附加使用者 `global_rules`，再附加專案規則。\n- `cline` 僅載入最近的 `.clinerules` 來源。\n\n## 5. 拆分為規則手冊、永遠套用和 TTSR 分類\n\n在 `createAgentSession`（`sdk.ts`）中規則發現之後：\n\n1. 掃描所有已發現的規則。\n2. 具有 `condition`（前置資料鍵；`ttsr_trigger` / `ttsrTrigger` 作為退回接受）的規則被註冊到 `TtsrManager`。\n3. 使用以下條件建立單獨的 `rulebookRules` 列表：\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && !rule.alwaysApply && !!rule.description\n```\n\n4. 建立 `alwaysApplyRules` 列表：\n\n```ts\n!registeredTtsrRuleNames.has(rule.name) && rule.alwaysApply === true\n```\n\n### 分類行為\n\n- **TTSR 分類**：任何具有 `condition` 的規則（不需要 description）。優先於其他分類。\n- **永遠套用分類**：`alwaysApply === true`，非 TTSR。完整內容注入系統提示詞。可透過 `rule://` 解析。\n- **規則手冊分類**：必須有 description，不能是 TTSR，不能是 `alwaysApply`。在系統提示詞中以名稱+描述列出；內容透過 `rule://` 按需讀取。\n- 同時具有 `condition` 和 `alwaysApply` 的規則僅進入 TTSR（TTSR 優先）。\n- 同時具有 `alwaysApply` 和 `description` 的規則僅進入永遠套用（非規則手冊）。\n\n## 6. 中繼資料如何影響執行時期介面\n\n### `description`\n\n- 納入規則手冊的必要條件。\n- 在系統提示詞 `<rules>` 區塊中呈現。\n- 缺少 description 表示規則無法透過 `rule://` 取得，也不會列在系統提示詞規則中。\n\n### `globs`\n\n- 在 `Rule` 上攜帶傳遞。\n- 在系統提示詞規則區塊中以 `<glob>...</glob>` 條目呈現。\n- 在規則 UI 狀態中公開（`extensions` 模式列表）。\n- **在此管線中不會強制執行自動匹配。** 沒有執行時期 glob 匹配器依據目前檔案/工具目標選擇規則。\n\n### `alwaysApply`\n\n- 由提供者解析並保留。\n- 用於 UI 顯示（擴充功能狀態管理器中的 `\"always\"` 觸發標籤）。\n- 用作從 `rulebookRules` 排除的條件。\n- **完整規則內容會自動注入系統提示詞**（在規則手冊規則區段之前）。\n- 規則也可透過 `rule://<name>` 重新讀取。\n\n### `ttsr_trigger`\n\n- 對應至 `rule.ttsrTrigger`。\n- 若存在，規則會路由至 TTSR 管理器，而非規則手冊。\n\n## 7. 系統提示詞包含路徑\n\n`buildSystemPromptInternal` 接收 `rules`（規則手冊）和 `alwaysApplyRules`。\n\n永遠套用規則先呈現，將其原始內容直接注入提示詞。\n\n規則手冊規則在 `# Rules` 區段中呈現，包含：\n\n- `Read rule://<name> when working in matching domain`\n- 每個規則的 `name`、`description` 和選用的 `<glob>` 列表\n\n這是建議性/情境性的：提示詞文字要求模型讀取適用的規則，但程式碼不會強制執行 glob 適用性。\n\n## 8. `rule://` 內部 URL 行為\n\n`RuleProtocolHandler` 以下列方式註冊：\n\n```ts\nnew RuleProtocolHandler({ getRules: () => [...rulebookRules, ...alwaysApplyRules] })\n```\n\n影響：\n\n- `rule://<name>` 會同時解析 **rulebookRules** 和 **alwaysApplyRules**。\n- 僅限 TTSR 的規則以及沒有 description 且沒有 `alwaysApply` 的規則無法透過 `rule://` 存取。\n- 解析採精確名稱匹配。\n- 未知名稱會傳回錯誤，列出可用的規則名稱。\n- 傳回的內容為原始 `rule.content`（前置資料已移除），內容類型為 `text/markdown`。\n\n## 9. 已知的部分/未強制執行語義\n\n1. 提供者描述中提到舊版檔案（`.cursorrules`、`.windsurfrules`），但目前的載入器程式碼路徑實際上並不讀取這些檔案。\n2. `globs` 中繼資料會呈現至提示詞/UI，但規則選擇邏輯並未強制執行。\n3. `rule://` 的規則選擇包含規則手冊和永遠套用規則，但不包含僅限 TTSR 的規則。\n4. 發現警告（`loadCapability(\"rules\").warnings`）會產生，但 `createAgentSession` 目前在此路徑中並未呈現/記錄它們。\n",
	"zh-tw/extensions/skills.md": "---\ntitle: 技能\ndescription: 用於註冊、發現和呼叫編碼代理中專門能力的技能系統。\nsidebar:\n  order: 3\n  label: 技能\ni18n:\n  sourceHash: 3e062cc13851\n  translator: machine\n---\n\n# 技能\n\n技能是基於檔案的能力套件，在啟動時被發現並以下列方式暴露給模型：\n\n- 系統提示詞中的輕量級中繼資料（名稱 + 描述）\n- 透過 `read skill://...` 按需取得的內容\n- 可選的互動式 `/skill:<name>` 命令\n\n本文件涵蓋 `src/extensibility/skills.ts`、`src/discovery/builtin.ts`、`src/internal-urls/skill-protocol.ts` 和 `src/discovery/agents-md.ts` 中的當前執行時行為。\n\n## 本程式碼庫中技能的定義\n\n一個被發現的技能表示為：\n\n- `name`\n- `description`\n- `filePath`（`SKILL.md` 路徑）\n- `baseDir`（技能目錄）\n- 來源中繼資料（`provider`、`level`、路徑）\n\n執行時僅要求 `name` 和 `path` 為有效值。實際上，匹配品質取決於 `description` 是否有意義。\n\n## 必要的佈局和 SKILL.md 預期\n\n### 目錄佈局\n\n對於基於提供者的發現（native/Claude/Codex/Agents/plugin 提供者），技能被發現為 **`skills/` 下的一層**：\n\n- `<skills-root>/<skill-name>/SKILL.md`\n\n像 `<skills-root>/group/<skill>/SKILL.md` 這樣的巢狀模式不會被提供者載入器發現。\n\n對於 `skills.customDirectories`，掃描使用相同的非遞迴佈局（`*/SKILL.md`）。\n\n```text\nProvider-discovered layout (non-recursive under skills/):\n\n<root>/skills/\n  ├─ postgres/\n  │   └─ SKILL.md      ✅ discovered\n  ├─ pdf/\n  │   └─ SKILL.md      ✅ discovered\n  └─ team/\n      └─ internal/\n          └─ SKILL.md  ❌ not discovered by provider loaders\n\nCustom-directory scanning is also non-recursive, so nested paths are ignored unless you point `customDirectories` at that nested parent.\n```\n\n### `SKILL.md` 前置資料\n\n技能類型上支援的前置資料欄位：\n\n- `name?: string`\n- `description?: string`\n- `globs?: string[]`\n- `alwaysApply?: boolean`\n- 額外的鍵會作為未知中繼資料保留\n\n當前執行時行為：\n\n- `name` 預設為技能目錄名稱\n- `description` 在以下情況是必需的：\n  - 原生 `.xcsh` 提供者技能發現（`requireDescription: true`）\n  - `skills.customDirectories` 透過 `src/discovery/helpers.ts` 中的 `scanSkillsFromDir` 掃描（非遞迴）\n- 非原生提供者可以載入沒有描述的技能\n\n## 發現管線\n\n`src/extensibility/skills.ts` 中的 `discoverSkills()` 執行兩個階段：\n\n1. **能力提供者** 透過 `loadCapability(\"skills\")`\n2. **自訂目錄** 透過 `scanSkillsFromDir(..., { requireDescription: true })`（單層目錄列舉）\n\n如果 `skills.enabled` 為 `false`，發現不會回傳任何技能。\n\n### 內建技能提供者和優先順序\n\n提供者排序以優先順序為主（較高者優先），相同優先順序時依註冊順序。\n\n目前已註冊的技能提供者：\n\n1. `native`（優先順序 100）— 透過 `src/discovery/builtin.ts` 的 `.xcsh` 使用者/專案技能\n2. `claude`（優先順序 80）\n3. 優先順序 70 群組（依註冊順序）：\n   - `claude-plugins`\n   - `agents`\n   - `codex`\n\n去重鍵為技能名稱。具有相同名稱的第一個項目優先。\n\n### 來源開關和過濾\n\n`discoverSkills()` 套用以下控制：\n\n- 來源開關：`enableCodexUser`、`enableClaudeUser`、`enableClaudeProject`、`enablePiUser`、`enablePiProject`\n- 基於技能名稱的 glob 過濾器：\n  - `ignoredSkills`（排除）\n  - `includeSkills`（包含允許清單；空值表示包含全部）\n\n過濾順序為：\n\n1. 來源已啟用\n2. 未被忽略\n3. 已包含（如果存在包含清單）\n\n對於 codex/claude/native 以外的提供者（例如 `agents`、`claude-plugins`），啟用狀態目前回退為：如果**任何**內建來源開關已啟用則啟用。\n\n### 衝突和重複處理\n\n- 能力去重已經保留每個名稱的第一個技能（最高優先順序的提供者）\n- `extensibility/skills.ts` 額外：\n  - 透過 `realpath` 去重相同檔案（符號連結安全）\n  - 當後續技能名稱衝突時發出衝突警告\n  - 保留便利的 `discoverSkillsFromDir({ dir, source })` API 作為 `scanSkillsFromDir` 的薄封裝\n- 自訂目錄技能在提供者技能之後合併，遵循相同的衝突行為\n\n## 執行時使用行為\n\n### 系統提示詞暴露\n\n系統提示詞建構（`src/system-prompt.ts`）如下使用已發現的技能：\n\n- 如果 `read` 工具可用：\n  - 在提示詞中包含已發現的技能清單\n- 否則：\n  - 省略已發現的清單\n\n任務工具子代理透過正常的工作階段建立接收工作階段的已發現/提供的技能清單；沒有按任務的技能固定覆蓋。\n\n### 互動式 `/skill:<name>` 命令\n\n如果 `skills.enableSkillCommands` 為 true，互動模式會為每個已發現的技能註冊一個斜線命令。\n\n`/skill:<name> [args]` 行為：\n\n- 直接從 `filePath` 讀取技能檔案\n- 移除前置資料\n- 將技能內容作為後續自訂訊息注入\n- 附加中繼資料（`Skill: <path>`，可選的 `User: <args>`）\n\n## `skill://` URL 行為\n\n`src/internal-urls/skill-protocol.ts` 支援：\n\n- `skill://<name>` → 解析為該技能的 `SKILL.md`\n- `skill://<name>/<relative-path>` → 解析為該技能目錄內的路徑\n\n```text\nskill:// URL resolution\n\nskill://pdf\n  -> <pdf-base>/SKILL.md\n\nskill://pdf/references/tables.md\n  -> <pdf-base>/references/tables.md\n\nGuards:\n- reject absolute paths\n- reject `..` traversal\n- reject any resolved path escaping <pdf-base>\n```\n\n解析細節：\n\n- 技能名稱必須完全匹配\n- 相對路徑經過 URL 解碼\n- 絕對路徑會被拒絕\n- 路徑遍歷（`..`）會被拒絕\n- 解析後的路徑必須保持在 `baseDir` 內\n- 缺少的檔案會回傳明確的 `File not found` 錯誤\n\n內容類型：\n\n- `.md` => `text/markdown`\n- 其他所有 => `text/plain`\n\n對於缺少的資源不會執行回退搜尋。\n\n## 技能 vs XCSH.md、命令、工具、鉤子\n\n### 技能 vs XCSH.md\n\n- **技能**：具名的、可選的能力套件，依任務上下文選擇或明確請求\n- **XCSH.md/上下文檔案**：持久性的指令檔案，作為上下文檔案能力載入並依層級/深度規則合併\n\n`src/discovery/agents-md.ts` 特別從 `cwd` 向上遍歷祖先目錄以發現獨立的 `XCSH.md` 檔案（最多深度 20），排除隱藏目錄段。\n\n### 技能 vs 斜線命令\n\n- **技能**：模型可讀的知識/工作流程內容\n- **斜線命令**：使用者呼叫的命令進入點\n- `/skill:<name>` 是一個便利封裝，注入技能文字；它不會改變技能發現語義\n\n### 技能 vs 自訂工具\n\n- **技能**：透過提示詞上下文和 `read` 載入的文件/工作流程內容\n- **自訂工具**：模型可呼叫的可執行工具 API，具有結構描述和執行時副作用\n\n### 技能 vs 鉤子\n\n- **技能**：被動內容\n- **鉤子**：事件驅動的執行時攔截器，可在執行期間阻止/修改行為\n\n## 與發現邏輯相關的實用撰寫指南\n\n- 將每個技能放在自己的目錄中：`<skills-root>/<skill-name>/SKILL.md`\n- 始終包含明確的 `name` 和 `description` 前置資料\n- 將參考資源放在相同的技能目錄下，並使用 `skill://<name>/...` 存取\n- 對於巢狀分類（`team/domain/skill`），將 `skills.customDirectories` 指向巢狀父目錄；掃描本身仍然是非遞迴的\n- 避免跨來源的重複技能名稱；依提供者優先順序第一個匹配者優先\n",
	"zh-tw/index.md": "---\ntitle: xcsh 文件\ndescription: 具備 AI 驅動的開發 CLI，搭配 TypeScript 編碼代理與 Rust 原生層，支援長期會話、MCP 支援及平台打包。\nsidebar:\n  order: 0\n  label: 概覽\ni18n:\n  sourceHash: b9288f42bf46\n  translator: machine\n---\n\nxcsh 是一個具備 AI 驅動的開發 CLI，搭配 TypeScript 編碼代理與 Rust 原生層（`pi-natives`）。它延伸了開源專案\n[`badlogic/pi-mono`](https://github.com/badlogic/pi-mono)，提供強化的執行環境、具備樹狀導覽與壓縮功能的長期會話、Python IPython 工具、完整的 MCP 支援、技能系統，以及針對 Linux、macOS 和 Windows 的平台打包。\n\n## 從何開始\n\n- **[F5 XC 情境](/runtime-tools/context-command)** — 連線至 F5 Distributed Cloud 租戶。建立情境、切換情境、管理命名空間與憑證。\n- **設定** — xcsh 如何探索、解析及分層設定。\n- **執行環境與工具** — bash / notebook / resolve 工具執行環境以及斜線命令介面。\n- **會話** — 僅附加的項目日誌、樹狀導覽、壓縮，以及自主記憶系統。\n- **原生層 (Rust)** — `pi-natives` N-API 附加模組的架構，驅動 shell / PTY / 媒體 / 搜尋功能。\n- **MCP** — 設定、協定內部機制、執行時期生命週期，以及如何撰寫伺服器與工具。\n- **擴充功能、技能與外掛** — 撰寫、載入、匹配規則、市集，以及外掛安裝程式。\n- **供應商與模型** — 模型設定、串流內部機制，以及 Python / IPython 執行環境。\n- **TUI** — 主題設定、`/tree` 命令，以及擴充功能與自訂工具的整合掛鉤。\n\n## 本文件集的組織方式\n\n側邊欄中的每個頂層群組對應代理的一個子系統。在群組內，頁面從「概覽」排列至「內部機制」，讓您在取得足夠的任務相關背景後即可停止閱讀。\n",
	"zh-tw/mcp/mcp-config.md": "---\ntitle: MCP 設定\ndescription: 編碼代理執行環境的 MCP 伺服器設定、驗證與管理。\nsidebar:\n  order: 1\n  label: 設定\ni18n:\n  sourceHash: ef8b49458ce9\n  translator: machine\n---\n\n# OMP 中的 MCP 設定\n\n本指南說明如何為 OMP 編碼代理新增、編輯及驗證 MCP 伺服器。\n\n程式碼中的權威來源：\n\n- 執行時設定類型：`packages/coding-agent/src/mcp/types.ts`\n- 設定寫入器：`packages/coding-agent/src/mcp/config-writer.ts`\n- 載入器 + 驗證：`packages/coding-agent/src/mcp/config.ts`\n- 獨立 `mcp.json` 探索：`packages/coding-agent/src/discovery/mcp-json.ts`\n- Schema：`packages/coding-agent/src/config/mcp-schema.json`\n\n## 建議的設定檔位置\n\nOMP 可以從多種工具探索 MCP 伺服器（`.claude/`、`.cursor/`、`.vscode/`、`opencode.json` 等），但對於 OMP 原生設定，您通常應使用以下其中一個檔案：\n\n- 專案層級：`.xcsh/mcp.json`\n- 使用者層級：`~/.xcsh/mcp.json`\n\nOMP 也接受專案根目錄中的備用獨立檔案：\n\n- `mcp.json`\n- `.mcp.json`\n\n當您希望由 OMP 管理設定時，請使用 `.xcsh/mcp.json`。僅當您需要一個其他 MCP 客戶端也能讀取的可攜式備用檔案時，才使用根目錄的 `mcp.json` / `.mcp.json`。\n\n## 新增 Schema 參考\n\n在檔案頂部新增此行以啟用編輯器自動完成和驗證：\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {}\n}\n```\n\n當 `/mcp add`、`/mcp enable`、`/mcp disable`、`/mcp reauth` 或其他設定寫入流程建立或更新 OMP 管理的 MCP 檔案時，OMP 現在會自動寫入此行。\n\n## 檔案結構\n\nOMP 支援以下頂層結構：\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"server-name\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"some-mcp-server\"]\n    }\n  },\n  \"disabledServers\": [\"server-name\"]\n}\n```\n\n頂層鍵：\n\n- `$schema` — 供工具使用的選用 JSON Schema URL\n- `mcpServers` — 伺服器名稱到伺服器設定的對應\n- `disabledServers` — 使用者層級的拒絕清單，用於按名稱關閉已探索的伺服器\n\n伺服器名稱必須符合 `^[a-zA-Z0-9_.-]{1,100}$`。\n\n## 支援的伺服器欄位\n\n所有傳輸方式的共用欄位：\n\n- `enabled?: boolean` — 當值為 `false` 時跳過此伺服器\n- `timeout?: number` — 連線逾時時間（毫秒）\n- `auth?: { ... }` — OMP 用於 OAuth/API 金鑰流程的驗證中繼資料\n- `oauth?: { ... }` — 驗證/重新驗證期間使用的明確 OAuth 客戶端設定\n\n### `stdio` 傳輸\n\n當省略 `type` 時，`stdio` 為預設值。\n\n必要欄位：\n\n- `command: string`\n\n選用欄位：\n\n- `type?: \"stdio\"`\n- `args?: string[]`\n- `env?: Record<string, string>`\n- `cwd?: string`\n\n範例：\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/Users/alice/projects\",\n        \"/Users/alice/Documents\"\n      ]\n    }\n  }\n}\n```\n\n這遵循官方 Filesystem MCP 伺服器套件（`@modelcontextprotocol/server-filesystem`）。\n\n### `http` 傳輸\n\n必要欄位：\n\n- `type: \"http\"`\n- `url: string`\n\n選用欄位：\n\n- `headers?: Record<string, string>`\n\n範例：\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\n這對應 GitHub 託管的 GitHub MCP 伺服器端點。\n\n### `sse` 傳輸\n\n必要欄位：\n\n- `type: \"sse\"`\n- `url: string`\n\n選用欄位：\n\n- `headers?: Record<string, string>`\n\n範例：\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"legacy-remote\": {\n      \"type\": \"sse\",\n      \"url\": \"https://example.com/mcp/sse\"\n    }\n  }\n}\n```\n\n`sse` 仍然為了相容性而受支援，但 MCP 規範現在建議新伺服器使用 Streamable HTTP（`type: \"http\"`）。\n\n## 驗證欄位\n\nOMP 理解兩個與驗證相關的物件。\n\n### `auth`\n\n```json\n{\n  \"type\": \"oauth\" | \"apikey\",\n  \"credentialId\": \"optional-stored-credential-id\",\n  \"tokenUrl\": \"optional-token-endpoint\",\n  \"clientId\": \"optional-client-id\",\n  \"clientSecret\": \"optional-client-secret\"\n}\n```\n\n當 OMP 需要記住如何為伺服器重新載入憑證時使用此設定。\n\n### `oauth`\n\n```json\n{\n  \"clientId\": \"...\",\n  \"clientSecret\": \"...\",\n  \"redirectUri\": \"...\",\n  \"callbackPort\": 3334,\n  \"callbackPath\": \"/oauth/callback\"\n}\n```\n\n當 MCP 伺服器需要明確的 OAuth 客戶端設定時使用此設定。\n\nSlack 是目前最明確的範例。Slack 的 MCP 伺服器託管於 `https://mcp.slack.com/mcp`，使用 Streamable HTTP，並需要使用您的 Slack 應用程式客戶端憑證進行機密 OAuth。\n\n範例：\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\n來自 Slack 文件的相關 Slack 端點：\n\n- MCP 端點：`https://mcp.slack.com/mcp`\n- 授權端點：`https://slack.com/oauth/v2_user/authorize`\n- Token 端點：`https://slack.com/api/oauth.v2.user.access`\n\n## 常用複製貼上範例\n\n### 透過 stdio 的檔案系統伺服器\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"filesystem\": {\n      \"command\": \"npx\",\n      \"args\": [\n        \"-y\",\n        \"@modelcontextprotocol/server-filesystem\",\n        \"/absolute/path/one\",\n        \"/absolute/path/two\"\n      ]\n    }\n  }\n}\n```\n\n### 透過 HTTP 的 GitHub 託管伺服器\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\"\n    }\n  }\n}\n```\n\n### 透過 Docker 的 GitHub 本機伺服器\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"github\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"-i\",\n        \"--rm\",\n        \"-e\",\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\",\n        \"ghcr.io/github/github-mcp-server\"\n      ],\n      \"env\": {\n        \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n      }\n    }\n  }\n}\n```\n\n這對應 GitHub 官方本機 Docker 映像 `ghcr.io/github/github-mcp-server`。\n\n### 透過 OAuth 的 Slack 託管伺服器\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"mcpServers\": {\n    \"slack\": {\n      \"type\": \"http\",\n      \"url\": \"https://mcp.slack.com/mcp\",\n      \"oauth\": {\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      },\n      \"auth\": {\n        \"type\": \"oauth\",\n        \"tokenUrl\": \"https://slack.com/api/oauth.v2.user.access\",\n        \"clientId\": \"YOUR_SLACK_CLIENT_ID\",\n        \"clientSecret\": \"YOUR_SLACK_CLIENT_SECRET\"\n      }\n    }\n  }\n}\n```\n\n## 密鑰與變數解析\n\n這是通常讓人困惑的部分。\n\n### 在 `.xcsh/mcp.json` 和 `~/.xcsh/mcp.json` 中\n\n在 OMP 啟動伺服器或發出 HTTP 請求之前，它會按以下方式解析 `env` 和 `headers` 的值：\n\n1. 如果值以 `!` 開頭，OMP 會將其作為 shell 指令執行，並使用去除空白後的標準輸出。\n2. 否則 OMP 會先檢查該值是否與環境變數名稱相符。\n3. 如果該環境變數未設定，OMP 會直接使用該字串字面值。\n\n範例：\n\n```json\n{\n  \"env\": {\n    \"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"\n  },\n  \"headers\": {\n    \"X-MCP-Insiders\": \"true\"\n  }\n}\n```\n\n這表示以下寫法是有效且方便用於本機密鑰的：\n\n- `\"GITHUB_PERSONAL_ACCESS_TOKEN\": \"GITHUB_PERSONAL_ACCESS_TOKEN\"` → 從目前的 shell 環境複製\n- `\"Authorization\": \"Bearer hardcoded-token\"` → 使用字面值\n- `\"Authorization\": \"!printf 'Bearer %s' \\\"$GITHUB_TOKEN\\\"\"` → 從指令建構標頭\n\n### 在根目錄的 `mcp.json` 和 `.mcp.json` 中\n\n獨立備用載入器在探索期間也會展開字串中的 `${VAR}` 和 `${VAR:-default}`。\n\n範例：\n\n```json\n{\n  \"mcpServers\": {\n    \"github\": {\n      \"type\": \"http\",\n      \"url\": \"https://api.githubcopilot.com/mcp/\",\n      \"headers\": {\n        \"Authorization\": \"Bearer ${GITHUB_TOKEN}\"\n      }\n    }\n  }\n}\n```\n\n如果您希望 OMP 的行為最不令人意外，請優先使用 `.xcsh/mcp.json` 並使用明確的 env/header 值。\n\n## `disabledServers`\n\n`disabledServers` 主要在使用者設定檔（`~/.xcsh/mcp.json`）中有用，當伺服器是從其他來源探索到的，而您希望 OMP 忽略它而不需要編輯該工具的設定時。\n\n範例：\n\n```json\n{\n  \"$schema\": \"https://raw.githubusercontent.com/f5-sales-demo/xcsh/main/packages/coding-agent/src/config/mcp-schema.json\",\n  \"disabledServers\": [\"github\", \"slack\"]\n}\n```\n\n## `/mcp add` 與直接編輯 JSON\n\n當您需要引導式設定時使用 `/mcp add`。\n\n在以下情況使用直接編輯 JSON：\n\n- 您需要精靈尚未提示的傳輸方式或驗證選項\n- 您想從其他 MCP 客戶端貼上伺服器定義\n- 您想在編輯器中獲得 Schema 支援的驗證\n\n編輯後，請使用：\n\n- `/mcp reload` 在目前會話中重新探索並重新連線伺服器\n- `/mcp list` 查看伺服器來自哪個設定檔\n- `/mcp test <name>` 測試單一伺服器\n\n## OMP 執行的驗證規則\n\n來自 `packages/coding-agent/src/mcp/config.ts` 中的 `validateServerConfig()`：\n\n- `stdio` 需要 `command`\n- `http` 和 `sse` 需要 `url`\n- 伺服器不能同時設定 `command` 和 `url`\n- 未知的 `type` 值會被拒絕\n\n實際影響：\n\n- 省略 `type` 表示 `stdio`\n- 如果您貼上遠端伺服器設定時忘記加上 `\"type\": \"http\"`，OMP 會將其視為 `stdio` 並提示 `command` 缺失\n- `sse` 仍然為了相容性而有效，但新的託管伺服器通常應設定為 `http`\n\n## 探索與優先順序\n\nOMP 不會合併跨檔案的重複伺服器定義。探索提供者有優先順序，較高優先順序的定義會勝出。\n\n實務上：\n\n- 當您需要 OMP 特定的覆寫時，優先使用 `.xcsh/mcp.json` 或 `~/.xcsh/mcp.json`\n- 盡可能在不同工具間保持伺服器名稱唯一\n- 當第三方設定持續重新引入您不需要的伺服器時，在使用者設定中使用 `disabledServers`\n\n## 疑難排解\n\n### `Server \"name\": stdio server requires \"command\" field`\n\n您可能在遠端伺服器上省略了 `type: \"http\"`。\n\n### `Server \"name\": both \"command\" and \"url\" are set`\n\n請選擇一種傳輸方式。OMP 將 `command` 視為 stdio，將 `url` 視為 http/sse。\n\n### `/mcp add` 成功但伺服器仍然無法連線\n\nJSON 是有效的，但伺服器可能仍然無法存取。使用 `/mcp test <name>` 並檢查：\n\n- 執行檔或 Docker 映像是否存在\n- 必要的環境變數是否已設定\n- 遠端 URL 是否可存取\n- OAuth 或 API Token 是否有效\n\n### 伺服器存在於其他工具的設定中但不在 OMP 中\n\n執行 `/mcp list`。OMP 會探索許多第三方 MCP 檔案，但專案層級的載入也可以透過 `mcp.enableProjectConfig` 設定來停用。\n\n## 參考資料\n\n- MCP 傳輸規範：<https://modelcontextprotocol.io/specification/2025-03-26/basic/transports>\n- 檔案系統伺服器套件：<https://www.npmjs.com/package/@modelcontextprotocol/server-filesystem>\n- GitHub MCP 伺服器：<https://github.com/github/github-mcp-server>\n- Slack MCP 伺服器文件：<https://docs.slack.dev/ai/slack-mcp-server/>\n",
	"zh-tw/mcp/mcp-protocol-transports.md": "---\ntitle: MCP 協定與傳輸層內部機制\ndescription: >-\n  MCP protocol implementation with stdio, SSE, and streamable HTTP transport\n  layers.\nsidebar:\n  order: 2\n  label: 協定與傳輸層\ni18n:\n  sourceHash: 48632064dd00\n  translator: machine\n---\n\n# MCP 協定與傳輸層內部機制\n\n本文件描述 coding-agent 如何實作 MCP JSON-RPC 訊息傳遞，以及協定層與傳輸層之間的關注點如何分離。\n\n## 涵蓋範圍\n\n涵蓋內容：\n\n- JSON-RPC 請求/回應與通知流程\n- stdio 和 HTTP/SSE 傳輸層的請求關聯與生命週期\n- 逾時與取消行為\n- 錯誤傳播與格式錯誤的載荷處理\n- 傳輸層選擇邊界（`stdio` vs `http`/`sse`）\n- 哪些重連/重試責任屬於傳輸層，哪些屬於管理層\n\n不涵蓋擴充功能開發體驗或命令列介面。\n\n## 實作檔案\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/transports/stdio.ts`](../../packages/coding-agent/src/mcp/transports/stdio.ts)\n- [`src/mcp/transports/http.ts`](../../packages/coding-agent/src/mcp/transports/http.ts)\n- [`src/mcp/transports/index.ts`](../../packages/coding-agent/src/mcp/transports/index.ts)\n- [`src/mcp/json-rpc.ts`](../../packages/coding-agent/src/mcp/json-rpc.ts)\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n\n## 層級邊界\n\n### 協定層（JSON-RPC + MCP 方法）\n\n- 訊息結構定義於 `types.ts`（`JsonRpcRequest`、`JsonRpcNotification`、`JsonRpcResponse`、`JsonRpcMessage`）。\n- MCP 客戶端邏輯（`client.ts`）決定方法順序與工作階段交握：\n  1. `initialize` 請求\n  2. `notifications/initialized` 通知\n  3. 方法呼叫如 `tools/list`、`tools/call`\n\n### 傳輸層（`MCPTransport`）\n\n`MCPTransport` 抽象化了訊息傳遞與生命週期：\n\n- `request(method, params, options?) -> Promise<T>`\n- `notify(method, params?) -> Promise<void>`\n- `close()`\n- `connected`\n- 可選的回呼：`onClose`、`onError`、`onNotification`\n\n傳輸層實作負責處理框架與 I/O 細節：\n\n- `StdioTransport`：透過子程序 stdio 進行以換行符分隔的 JSON 通訊\n- `HttpTransport`：透過 HTTP POST 進行 JSON-RPC 通訊，並支援可選的 SSE 回應/監聽\n\n### 目前重要的注意事項\n\n傳輸層回呼（`onClose`、`onError`、`onNotification`）已實作，但目前 `MCPClient`/`MCPManager` 的流程並未將重連邏輯接線到這些回呼。通知僅在呼叫者註冊處理程式時才會被消費。\n\n## 傳輸層選擇\n\n`client.ts:createTransport()` 根據組態選擇傳輸層：\n\n- `type` 省略或為 `\"stdio\"` -> `createStdioTransport`\n- `\"http\"` 或 `\"sse\"` -> `createHttpTransport`\n\n`\"sse\"` 被視為 HTTP 傳輸層的變體（同一類別），而非獨立的傳輸層實作。\n\n## JSON-RPC 訊息流程與關聯\n\n## 請求 ID\n\n每個傳輸層為每個請求產生 ID（`Math.random` + 時間戳字串）。ID 是傳輸層本地的關聯令牌。\n\n## Stdio 關聯路徑\n\n- 發送的請求序列化為一個 JSON 物件 + `\\n`。\n- `#pendingRequests: Map<id, {resolve,reject}>` 儲存進行中的請求。\n- 讀取迴圈從 stdout 解析 JSONL 並呼叫 `#handleMessage`。\n- 如果收到的訊息具有匹配的 `id`，請求會解析/拒絕。\n- 如果收到的訊息具有 `method` 且沒有 `id`，則視為通知並發送到 `onNotification`。\n\n未知的 ID 會被忽略（不會拒絕，也不會觸發錯誤回呼）。\n\n## HTTP 關聯路徑\n\n- 發送的請求是帶有 JSON 主體和產生的 `id` 的 HTTP `POST`。\n- 非 SSE 回應路徑：解析一個 JSON-RPC 回應並回傳 `result`/在 `error` 時拋出例外。\n- SSE 回應路徑（`Content-Type: text/event-stream`）：串流事件，回傳第一個 `id` 匹配預期請求 ID 且具有 `result` 或 `error` 的訊息。\n- 具有 `method` 且沒有 `id` 的 SSE 訊息被視為通知。\n\n如果 SSE 串流在匹配的回應到達前結束，請求會以 `No response received for request ID ...` 失敗。\n\n## 通知\n\n客戶端透過 `transport.notify(...)` 發送 JSON-RPC 通知。\n\n- Stdio：將通知框架（`jsonrpc`、`method`、可選的 `params`）加上換行符寫入 stdin。\n- HTTP：發送不含 `id` 的 POST 主體；成功接受 `2xx` 或 `202 Accepted`。\n\n伺服器發起的通知僅透過傳輸層的 `onNotification` 回呼傳遞；管理層/客戶端中沒有預設的全域訂閱者。\n\n## Stdio 傳輸層內部機制\n\n## 生命週期與狀態轉換\n\n- 初始狀態：`connected=false`、`process=null`、待處理映射為空\n- `connect()`：\n  - 使用組態的 command/args/env/cwd 產生子程序\n  - 標記為已連線\n  - 啟動 stdout 讀取迴圈（`readJsonl`）\n  - 啟動 stderr 迴圈（讀取/捨棄；目前為靜默處理）\n- `close()`：\n  - 標記為已斷線\n  - 拒絕所有待處理的請求（`Transport closed`）\n  - 終止子程序\n  - 等待讀取迴圈結束\n  - 觸發 `onClose`\n\n如果讀取迴圈意外退出，`finally` 會觸發 `#handleClose()`，執行相同的待處理請求拒絕和關閉回呼。\n\n## 逾時與取消\n\n每個請求：\n\n- 逾時預設為 `config.timeout ?? 30000`\n- 來自呼叫者的可選 `AbortSignal`\n- abort 和 timeout 都會拒絕待處理的 promise 並清除映射項目\n\n取消僅限本地：傳輸層不會向伺服器發送協定層級的取消通知。\n\n## 格式錯誤的載荷處理\n\n在讀取迴圈中：\n\n- 每個解析的 JSONL 行都會在 `try/catch` 中傳遞給 `#handleMessage`\n- 格式錯誤/無效訊息的處理例外會被捨棄（`Skip malformed lines` 註解）\n- 迴圈繼續，因此一條錯誤訊息不會終止連線\n\n如果底層串流解析器拋出例外，`onError` 會被呼叫（當仍處於連線狀態時），然後連線關閉。\n\n## 斷線/故障行為\n\n當程序退出或串流關閉時：\n\n- 所有進行中的請求以 `Transport closed` 被拒絕\n- 不會自動重啟或重連\n- 上層必須透過建立新的傳輸層來重新連線\n\n## 背壓/串流注意事項\n\n- 發送的寫入使用 `stdin.write()` + `flush()`，不等待 drain 語義。\n- 傳輸層中沒有明確的佇列或高水位線管理。\n- 接收處理是串流驅動的（透過 `readJsonl` 的 `for await`），一次處理一個解析的訊息。\n\n## HTTP/SSE 傳輸層內部機制\n\n## 生命週期與連線語義\n\nHTTP 傳輸層具有邏輯連線狀態，但請求路徑是每次 HTTP 呼叫無狀態的：\n\n- `connect()` 設定 `connected=true`（沒有 socket/工作階段交握）\n- 透過 `Mcp-Session-Id` 標頭進行可選的伺服器工作階段追蹤\n- `close()` 可選地發送帶有 `Mcp-Session-Id` 的 `DELETE`，中止 SSE 監聽器，觸發 `onClose`\n\n因此 `connected` 表示「傳輸層可用」，而非「持久串流已建立」。\n\n## 工作階段標頭行為\n\n- 在 POST 回應中，如果存在 `Mcp-Session-Id` 標頭，傳輸層會儲存它。\n- 後續的請求/通知會包含 `Mcp-Session-Id`。\n- `close()` 嘗試透過 HTTP DELETE 終止伺服器工作階段；終止失敗會被忽略。\n\n## 逾時與取消\n\n對於 `request()` 和 `notify()`：\n\n- 逾時使用 `AbortController`（`config.timeout ?? 30000`）\n- 如有提供外部 signal，會透過 `AbortSignal.any([...])` 合併\n- AbortError 處理會區分呼叫者中止與逾時\n\n拋出的錯誤：\n\n- 逾時：`Request timeout after ...ms`（或 `SSE response timeout ...`、`Notify timeout ...`）\n- 呼叫者中止：當外部 signal 已經中止時，重新拋出原始 AbortError\n\n## HTTP 錯誤傳播\n\n在非 OK 回應時：\n\n- 回應文字會包含在拋出的錯誤中（`HTTP <status>: <text>`）\n- 如果存在，來自 `WWW-Authenticate` 和 `Mcp-Auth-Server` 的驗證提示會被附加\n\n在 JSON-RPC 錯誤物件上：\n\n- 拋出 `MCP error <code>: <message>`\n\n格式錯誤的 JSON 主體（`response.json()` 失敗）會作為解析例外傳播。\n\n## SSE 行為與模式\n\n存在兩種 SSE 路徑：\n\n1. **每請求 SSE 回應**（`#parseSSEResponse`）\n   - 當 POST 回應的內容類型為 `text/event-stream` 時使用\n   - 消費串流直到找到匹配的回應 id\n   - 可以在同一串流中處理交錯的通知\n\n2. **背景 SSE 監聽器**（`startSSEListener()`）\n   - 用於伺服器發起通知的可選 GET 監聽器\n   - 目前不會被 MCP 管理層/客戶端自動啟動\n   - 如果 GET 回傳 `405`，監聽器會靜默停用自身（伺服器不支援此模式）\n\n## 格式錯誤的載荷與斷線處理\n\nSSE JSON 解析錯誤會從 `readSseJson` 向上冒泡並拒絕請求/監聽器。\n\n- 請求 SSE 解析錯誤會拒絕活動中的請求。\n- 背景監聽器錯誤會觸發 `onError`（AbortError 除外）。\n- 背景監聽器不會自動重連。\n\n## `json-rpc.ts` 工具函式與傳輸層抽象的差異\n\n`src/mcp/json-rpc.ts` 提供 `callMCP()` 和 `parseSSE()` 輔助函式，用於直接的 HTTP MCP 呼叫（由 Exa 整合使用），而非 `MCPClient`/`MCPManager` 使用的 `MCPTransport` 抽象。\n\n與 `HttpTransport` 的主要差異：\n\n- 先解析整個回應文字，然後提取第一個 `data:` 行（`parseSSE`），並以 JSON 作為備援\n- 沒有請求逾時管理、沒有 abort API、沒有 session-id 處理、沒有傳輸層生命週期\n- 回傳原始 JSON-RPC 封包物件\n\n此路徑較為輕量，但不如完整傳輸層實作穩健。\n\n## 重試/重連責任\n\n## 傳輸層級\n\n目前的傳輸層實作**不會**：\n\n- 重試失敗的請求\n- 在 stdio 程序退出後重連\n- 重連 SSE 監聽器\n- 在斷線後重新發送進行中的請求\n\n它們採取快速失敗策略並傳播錯誤。\n\n## 管理層/客戶端層級\n\n`MCPManager` 處理探索/初始連線的協調，只能透過再次執行連線流程（`connectToServer`/`discoverAndConnect` 路徑）來重連。它不會在執行期間的故障回呼中自動修復已連線的傳輸層。\n\n`MCPManager` 確實具有針對慢速伺服器的啟動備援行為（從快取延遲載入工具），但這是工具可用性備援，而非傳輸層重試。\n\n## 故障情境摘要\n\n- **格式錯誤的 stdio 訊息行**：捨棄；串流繼續。\n- **Stdio 串流/程序結束**：傳輸層關閉；待處理的請求以 `Transport closed` 被拒絕。\n- **HTTP 非 2xx**：請求/通知拋出 HTTP 錯誤。\n- **無效 JSON 回應**：解析例外被傳播。\n- **SSE 在匹配 id 前結束**：請求以 `No response received for request ID ...` 失敗。\n- **逾時**：傳輸層特定的逾時錯誤。\n- **呼叫者中止**：來自呼叫者 signal 的 AbortError/reason 被傳播。\n\n## 實務邊界規則\n\n如果關注點是訊息結構、id 關聯或 MCP 方法排序，它屬於協定/客戶端邏輯。\n\n如果關注點是框架（JSONL vs HTTP/SSE）、串流解析、fetch/spawn 生命週期、逾時計時器或連線拆除，它屬於傳輸層實作。\n",
	"zh-tw/mcp/mcp-runtime-lifecycle.md": "---\ntitle: MCP 執行時期生命週期\ndescription: MCP 伺服器程序的生命週期，從初始化到工具註冊、健康監控及關閉。\nsidebar:\n  order: 3\n  label: 執行時期生命週期\ni18n:\n  sourceHash: d04cefaf38f8\n  translator: machine\n---\n\n# MCP 執行時期生命週期\n\n本文件描述 MCP 伺服器如何在 coding-agent 執行時期中被發現、連線、公開為工具、重新整理及終止。\n\n## 生命週期概覽\n\n1. **SDK 啟動**時呼叫 `discoverAndLoadMCPTools()`（除非 MCP 已停用）。\n2. **發現階段**（`loadAllMCPConfigs`）從能力來源解析 MCP 伺服器設定、過濾已停用/專案/Exa 項目，並保留來源中繼資料。\n3. **管理器連線階段**（`MCPManager.connectServers`）以平行方式啟動各伺服器的連線 + `tools/list`。\n4. **快速啟動閘門**最多等待 250 毫秒，然後可能回傳：\n   - 完全載入的 `MCPTool`，\n   - 各伺服器的失敗資訊，\n   - 或針對仍在等待中的伺服器回傳快取的 `DeferredMCPTool`。\n5. **SDK 串接**將 MCP 工具合併至該工作階段的執行時期工具註冊表。\n6. **即時工作階段**可透過 `/mcp` 流程重新整理 MCP 工具（`disconnectAll` + 重新發現 + `session.refreshMCPTools`）。\n7. **終止**發生在呼叫者調用 `disconnectServer`/`disconnectAll` 時；管理器也會清除已中斷連線伺服器的 MCP 工具註冊。\n\n## 發現與載入階段\n\n### 從 SDK 進入的路徑\n\n`src/sdk.ts` 中的 `createAgentSession()` 在 `enableMCP` 為 true（預設值）時執行 MCP 啟動：\n\n- 呼叫 `discoverAndLoadMCPTools(cwd, { ... })`，\n- 傳入 `authStorage`、快取儲存，以及 `mcp.enableProjectConfig` 設定，\n- 始終設定 `filterExa: true`，\n- 記錄各伺服器的載入/連線錯誤，\n- 將回傳的管理器儲存在 `toolSession.mcpManager` 和工作階段結果中。\n\n若 `enableMCP` 為 false，則完全跳過 MCP 發現。\n\n### 設定發現與過濾\n\n`loadAllMCPConfigs()`（`src/mcp/config.ts`）透過能力發現載入標準 MCP 伺服器項目，然後轉換為舊版 `MCPServerConfig`。\n\n過濾行為：\n\n- `enableProjectConfig: false` 會移除專案層級的項目（`_source.level === \"project\"`）。\n- `enabled: false` 的伺服器在連線嘗試前即被跳過。\n- Exa 伺服器預設會被過濾掉，其 API 金鑰會被擷取用於原生 Exa 工具整合。\n\n結果包含 `configs` 和 `sources`（後續用於提供者標籤的中繼資料）。\n\n### 發現層級的失敗行為\n\n`discoverAndLoadMCPTools()` 區分兩種失敗類別：\n\n- **發現硬失敗**（來自 `manager.discoverAndConnect` 的例外，通常來自設定發現）：回傳空的工具集和一個合成錯誤 `{ path: \".mcp.json\", error }`。\n- **各伺服器的執行時期/連線失敗**：管理器回傳部分成功結果及 `errors` 映射；其他伺服器繼續運作。\n\n因此，當個別 MCP 伺服器失敗時，啟動不會使整個代理工作階段失敗。\n\n## 管理器狀態模型\n\n`MCPManager` 透過獨立的註冊表追蹤執行時期生命週期：\n\n- `#connections: Map<string, MCPServerConnection>` — 已完全連線的伺服器。\n- `#pendingConnections: Map<string, Promise<MCPServerConnection>>` — 正在進行交握。\n- `#pendingToolLoads: Map<string, Promise<{ connection, serverTools }>>` — 已連線但工具仍在載入中。\n- `#tools: CustomTool[]` — 公開給呼叫者的目前 MCP 工具檢視。\n- `#sources: Map<string, SourceMeta>` — 即使在連線完成前也保留的提供者/來源中繼資料。\n\n`getConnectionStatus(name)` 從這些映射推導狀態：\n\n- 若在 `#connections` 中則為 `connected`，\n- 若在待處理的連線或待處理的工具載入中則為 `connecting`，\n- 否則為 `disconnected`。\n\n## 連線建立與啟動時序\n\n## 各伺服器的連線管線\n\n對於 `connectServers()` 中每個已發現的伺服器：\n\n1. 儲存/更新來源中繼資料，\n2. 若已連線/待處理則跳過，\n3. 驗證傳輸欄位（`validateServerConfig`），\n4. 解析驗證/shell 替換（`#resolveAuthConfig`），\n5. 呼叫 `connectToServer(name, resolvedConfig)`，\n6. 呼叫 `listTools(connection)`，\n7. 盡力快取工具定義（`MCPToolCache.set`）。\n\n`connectToServer()` 行為（`src/mcp/client.ts`）：\n\n- 建立 stdio 或 HTTP/SSE 傳輸，\n- 執行 MCP `initialize` + `notifications/initialized`，\n- 使用逾時設定（`config.timeout` 或預設 30 秒），\n- 初始化失敗時關閉傳輸。\n\n### 快速啟動閘門 + 延遲回退\n\n`connectServers()` 等待以下兩者的競爭：\n\n- 所有連線/工具載入任務已結算，以及\n- `STARTUP_TIMEOUT_MS = 250`。\n\n250 毫秒之後：\n\n- 已完成的任務成為即時的 `MCPTool`，\n- 已拒絕的任務產生各伺服器的錯誤，\n- 仍在等待中的任務：\n  - 若有快取的工具定義可用（`MCPToolCache.get`），則建立 `DeferredMCPTool`，\n  - 否則阻塞等待這些待處理任務結算。\n\n這是一種混合啟動模型：當快取可用時快速回傳，當快取不可用時等待以確保正確性。\n\n### 背景完成行為\n\n每個待處理的 `toolsPromise` 也有一個背景延續，最終會：\n\n- 透過 `#replaceServerTools` 替換管理器狀態中該伺服器的工具切片，\n- 寫入快取，\n- 僅在啟動後記錄延遲失敗（`allowBackgroundLogging`）。\n\n## 工具公開與即時工作階段可用性\n\n### 啟動註冊\n\n`discoverAndLoadMCPTools()` 將管理器工具轉換為 `LoadedCustomTool[]`，並裝飾路徑（已知時為 `mcp:<server> via <providerName>`）。\n\n`createAgentSession()` 接著將這些工具推入 `customTools`，這些工具被包裝後加入執行時期工具註冊表，名稱格式為 `mcp_<server>_<tool>`。\n\n### 工具呼叫\n\n- `MCPTool` 透過已連線的 `MCPServerConnection` 呼叫工具。\n- `DeferredMCPTool` 在呼叫前等待 `waitForConnection(server)`；這允許快取的工具在連線就緒前即存在。\n\n兩者都回傳結構化的工具輸出，並將傳輸/工具錯誤轉換為 `MCP error: ...` 工具內容（中止仍為中止）。\n\n## 重新整理/重新載入路徑（啟動 vs 即時重新載入）\n\n### 初始啟動路徑\n\n- 在 `sdk.ts` 中進行一次性發現/載入，\n- 工具註冊在初始工作階段工具註冊表中。\n\n### 互動式重新載入路徑\n\n`/mcp reload` 路徑（`src/modes/controllers/mcp-command-controller.ts`）執行：\n\n1. `mcpManager.disconnectAll()`，\n2. `mcpManager.discoverAndConnect()`，\n3. `session.refreshMCPTools(mcpManager.getTools())`。\n\n`session.refreshMCPTools()`（`src/session/agent-session.ts`）移除所有 `mcp_` 工具、重新包裝最新的 MCP 工具，並重新啟用工具集，使 MCP 變更無需重啟工作階段即可生效。\n\n還有一個用於延遲連線的後續路徑：在等待特定伺服器後，若狀態變為 `connected`，它會重新執行 `session.refreshMCPTools(...)` 以在工作階段中重新綁定新可用的工具。\n\n## 健康狀態、重新連線與部分失敗行為\n\n目前的執行時期行為刻意保持最小化：\n\n- 管理器/客戶端中**沒有自主健康監控器**。\n- 當傳輸中斷時**沒有自動重新連線迴圈**。\n- 管理器不訂閱傳輸的 `onClose`/`onError`；狀態由註冊表驅動。\n- 重新連線是顯式的：透過重新載入流程或直接調用 `connectServers()`。\n\n實際運作上：\n\n- 一個伺服器失敗不會移除其他健康伺服器的工具，\n- 連線/列表失敗按各伺服器隔離，\n- 工具快取和背景更新為盡力而為（記錄警告/錯誤，不會硬停止）。\n\n## 終止語意\n\n### 伺服器層級終止\n\n`disconnectServer(name)`：\n\n- 移除待處理項目/來源中繼資料，\n- 若已連線則關閉傳輸，\n- 從管理器狀態中移除該伺服器的 `mcp_` 工具。\n\n### 全域終止\n\n`disconnectAll()`：\n\n- 以 `Promise.allSettled` 關閉所有作用中的傳輸，\n- 清除待處理映射、來源、連線及管理器工具列表。\n\n在目前的串接中，顯式終止用於 MCP 命令流程（重新載入/移除/停用）。啟動路徑本身沒有單獨的自動管理器釋放掛鉤；呼叫者需要在需要確定性 MCP 關閉時負責調用管理器的中斷連線方法。\n\n## 失敗模式與保證\n\n| 情境 | 行為 | 硬失敗 vs 盡力而為 |\n| --- | --- | --- |\n| 發現階段拋出例外（能力/設定載入路徑） | 載入器回傳空工具 + 合成 `.mcp.json` 錯誤 | 盡力而為的工作階段啟動 |\n| 無效的伺服器設定 | 伺服器被跳過並記錄驗證錯誤項目 | 各伺服器盡力而為 |\n| 連線逾時/初始化失敗 | 記錄伺服器錯誤；其他伺服器繼續 | 各伺服器盡力而為 |\n| 啟動時 `tools/list` 仍在等待且有快取命中 | 立即回傳延遲工具 | 盡力而為的快速啟動 |\n| 啟動時 `tools/list` 仍在等待且無快取 | 啟動等待待處理項目結算 | 為正確性進行硬等待 |\n| 延遲的背景工具載入失敗 | 在啟動閘門之後記錄 | 盡力而為的記錄 |\n| 執行時期傳輸中斷 | 無自動重新連線；後續呼叫失敗直到重新連線/重新載入 | 透過手動操作盡力恢復 |\n\n## 公開 API 介面\n\n`src/mcp/index.ts` 為外部呼叫者重新匯出載入器/管理器/客戶端 API。`src/sdk.ts` 將 `discoverMCPServers()` 公開為便利包裝器，回傳相同的載入器結果結構。\n\n## 實作檔案\n\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts) — 載入器外觀、發現錯誤正規化、`LoadedCustomTool` 轉換。\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts) — 生命週期狀態註冊表、平行連線/列表流程、重新整理/中斷連線。\n- [`src/mcp/client.ts`](../../packages/coding-agent/src/mcp/client.ts) — 傳輸設定、初始化交握、列表/呼叫/中斷連線。\n- [`src/mcp/index.ts`](../../packages/coding-agent/src/mcp/index.ts) — MCP 模組 API 匯出。\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts) — 啟動串接至工作階段/工具註冊表。\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts) — 管理器使用的設定發現/過濾/驗證。\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts) — `MCPTool` 和 `DeferredMCPTool` 執行時期行為。\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — `refreshMCPTools` 即時重新綁定。\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts) — 互動式重新載入/重新連線流程。\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts) — 透過父管理器連線進行子代理 MCP 代理。\n",
	"zh-tw/mcp/mcp-server-tool-authoring.md": "---\ntitle: MCP 伺服器與工具撰寫\ndescription: 建置自訂 MCP 伺服器並為程式碼代理註冊工具的指南。\nsidebar:\n  order: 4\n  label: 伺服器與工具撰寫\ni18n:\n  sourceHash: 160e7560ef1f\n  translator: machine\n---\n\n# MCP 伺服器與工具撰寫\n\n本文件說明 MCP 伺服器定義如何成為 coding-agent 中可呼叫的 `mcp_*` 工具，以及當設定無效、重複、停用或受認證閘控時，操作者應預期的行為。\n\n## 架構概覽\n\n```text\nConfig sources (.xcsh/.claude/.cursor/.vscode/mcp.json, mcp.json, etc.)\n  -> discovery providers normalize to canonical MCPServer\n  -> capability loader dedupes by server name (higher provider priority wins)\n  -> loadAllMCPConfigs converts to MCPServerConfig + skips enabled:false\n  -> MCPManager connects/listTools (with auth/header/env resolution)\n  -> MCPTool/DeferredMCPTool bridge exposes tools as mcp_<server>_<tool>\n  -> AgentSession.refreshMCPTools replaces live MCP tools immediately\n```\n\n## 1) 伺服器設定模型與驗證\n\n`src/mcp/types.ts` 定義了 MCP 設定撰寫者和執行階段使用的撰寫格式：\n\n- `stdio`（當 `type` 缺失時的預設值）：需要 `command`，可選 `args`、`env`、`cwd`\n- `http`：需要 `url`，可選 `headers`\n- `sse`：需要 `url`，可選 `headers`（保留以維持相容性）\n- 共用欄位：`enabled`、`timeout`、`auth`\n\n`validateServerConfig()`（`src/mcp/config.ts`）強制執行傳輸基本規則：\n\n- 拒絕同時設定 `command` 和 `url` 的設定\n- stdio 需要 `command`\n- http/sse 需要 `url`\n- 拒絕未知的 `type`\n\n`config-writer.ts` 在新增/更新操作時套用此驗證，並且也會驗證伺服器名稱：\n\n- 不可為空\n- 最多 100 個字元\n- 僅允許 `[a-zA-Z0-9_.-]`\n\n### 傳輸常見陷阱\n\n- 省略 `type` 表示 stdio。如果您原本想使用 HTTP/SSE 但省略了 `type`，`command` 就會變成必填。\n- `sse` 仍然被接受，但在內部被視為 HTTP 傳輸處理（`createHttpTransport`）。\n- 驗證是結構性的，不是可達性的：語法正確的 URL 仍然可能在連線時失敗。\n\n## 2) 探索、正規化與優先順序\n\n### 基於能力的探索\n\n`loadAllMCPConfigs()`（`src/mcp/config.ts`）透過 `loadCapability(mcpCapability.id)` 載入標準化的 `MCPServer` 項目。\n\n能力層（`src/capability/index.ts`）接著會：\n\n1. 按優先順序載入提供者\n2. 依 `server.name` 去除重複（先出現者獲勝 = 最高優先順序）\n3. 驗證去重後的項目\n\n結果：跨來源的重複伺服器名稱不會被合併。一個定義獲勝；較低優先順序的重複項會被遮蔽。\n\n### `.mcp.json` 與相關檔案\n\n`src/discovery/mcp-json.ts` 中的專用備援提供者會讀取專案根目錄的 `mcp.json` 和 `.mcp.json`（低優先順序）。\n\n實務上 MCP 伺服器也會來自更高優先順序的提供者（例如原生 `.xcsh/...` 和工具專屬設定目錄）。撰寫指引：\n\n- 優先使用 `.xcsh/mcp.json`（專案）或 `~/.xcsh/mcp.json`（使用者）以獲得明確控制。\n- 當需要備援相容性時使用根目錄的 `mcp.json` / `.mcp.json`。\n- 在多個來源中重複使用相同的伺服器名稱會導致優先順序遮蔽，而非合併。\n\n### 正規化行為\n\n`convertToLegacyConfig()`（`src/mcp/config.ts`）將標準化的 `MCPServer` 對應至執行階段的 `MCPServerConfig`。\n\n關鍵行為：\n\n- 傳輸推斷為 `server.transport ?? (command ? \"stdio\" : url ? \"http\" : \"stdio\")`\n- 停用的伺服器（`enabled === false`）在連線前就被丟棄\n- 可選欄位存在時會被保留\n\n### 探索期間的環境變數展開\n\n`mcp-json.ts` 使用 `expandEnvVarsDeep()` 展開字串欄位中的環境變數佔位符：\n\n- 支援 `${VAR}` 和 `${VAR:-default}`\n- 未解析的值會保持為字面字串 `${VAR}`\n\n`mcp-json.ts` 也會對使用者 JSON 執行執行階段型別檢查，並對無效的 `enabled`/`timeout` 值記錄警告，而非使整個檔案載入失敗。\n\n## 3) 認證與執行階段值解析\n\n`MCPManager.prepareConfig()`/`#resolveAuthConfig()`（`src/mcp/manager.ts`）是連線前的最終處理步驟。\n\n### OAuth 憑證注入\n\n如果設定包含：\n\n```ts\nauth: { type: \"oauth\", credentialId: \"...\" }\n```\n\n且認證儲存中存在該憑證：\n\n- `http`/`sse`：注入 `Authorization: Bearer <access_token>` 標頭\n- `stdio`：注入 `OAUTH_ACCESS_TOKEN` 環境變數\n\n如果憑證查詢失敗，管理器會記錄警告並以未解析的認證狀態繼續。\n\n### 標頭/環境變數值解析\n\n在連線前，管理器透過 `resolveConfigValue()`（`src/config/resolve-config-value.ts`）解析每個標頭/環境變數值：\n\n- 以 `!` 開頭的值 => 執行 shell 命令，使用修剪後的 stdout（已快取）\n- 否則，先將值視為環境變數名稱（`process.env[name]`），退而使用字面值\n- 未解析的命令/環境變數值會從最終的標頭/環境變數對應中省略\n\n操作注意事項：這意味著打字錯誤的密鑰命令/環境變數名稱可能會靜默地移除該標頭/環境變數項目，導致下游 401/403 或伺服器啟動失敗。\n\n## 4) 工具橋接：MCP -> 代理可呼叫工具\n\n`src/mcp/tool-bridge.ts` 將 MCP 工具定義轉換為 `CustomTool`。\n\n### 命名與碰撞範圍\n\n工具名稱的產生方式為：\n\n```text\nmcp_<sanitized_server_name>_<sanitized_tool_name>\n```\n\n規則：\n\n- 轉為小寫\n- 非 `[a-z_]` 字元變為 `_`\n- 重複的底線會合併\n- 工具名稱中多餘的 `<server>_` 前綴會被移除一次\n\n這避免了許多碰撞，但並非全部。不同的原始名稱仍可能經過清理後產生相同的識別符（例如 `my-server` 和 `my.server` 清理後相似），而註冊表插入採用後寫者獲勝。\n\n### Schema 對應\n\n`convertSchema()` 大致保持 MCP JSON Schema 不變，但會為缺少 `properties` 的物件 schema 補上 `{}` 以維持提供者相容性。\n\n### 執行對應\n\n`MCPTool.execute()` / `DeferredMCPTool.execute()`：\n\n- 呼叫 MCP `tools/call`\n- 將 MCP 內容展平為可顯示的文字\n- 回傳結構化詳細資訊（`serverName`、`mcpToolName`、提供者中繼資料）\n- 將伺服器回報的 `isError` 對應為 `Error: ...` 文字結果\n- 將拋出的傳輸/執行階段失敗對應為 `MCP error: ...`\n- 透過將 AbortError 轉換為 `ToolAbortError` 來保留中止語意\n\n## 5) 操作者生命週期：新增/編輯/移除與即時更新\n\n互動模式在 `src/modes/controllers/mcp-command-controller.ts` 中公開 `/mcp`。\n\n支援的操作：\n\n- `add`（精靈或快速新增）\n- `remove` / `rm`\n- `enable` / `disable`\n- `test`\n- `reauth` / `unauth`\n- `reload`\n\n設定寫入是原子性的（`writeMCPConfigFile`：暫存檔 + 重新命名）。\n\n變更後，控制器呼叫 `#reloadMCP()`：\n\n1. `mcpManager.disconnectAll()`\n2. `mcpManager.discoverAndConnect()`\n3. `session.refreshMCPTools(mcpManager.getTools())`\n\n`refreshMCPTools()` 取代所有 `mcp_` 註冊表條目並立即重新啟用最新的 MCP 工具集，因此變更無需重新啟動工作階段即可生效。\n\n### 模式差異\n\n- **互動/TUI 模式**：`/mcp` 提供應用程式內的使用者體驗（精靈、OAuth 流程、連線狀態文字、立即的執行階段重新繫結）。\n- **SDK/無頭整合**：`discoverAndLoadMCPTools()`（`src/mcp/loader.ts`）回傳已載入的工具 + 各伺服器的錯誤；無 `/mcp` 命令使用者體驗。\n\n## 6) 使用者可見的錯誤表面\n\n使用者/操作者常見的錯誤字串：\n\n- 新增/更新驗證失敗：\n  - `Invalid server config: ...`\n  - `Server \"<name>\" already exists in <path>`\n- 快速新增引數問題：\n  - `Use either --url or -- <command...>, not both.`\n  - `--token requires --url (HTTP/SSE transport).`\n- 連線/測試失敗：\n  - `Failed to connect to \"<name>\": <message>`\n  - 逾時說明文字建議增加逾時時間\n  - `401/403` 的認證說明文字\n- 認證/OAuth 流程：\n  - `Authentication required ... OAuth endpoints could not be discovered`\n  - `OAuth flow timed out. Please try again.`\n  - `OAuth authentication failed: ...`\n- 停用伺服器的使用：\n  - `Server \"<name>\" is disabled. Run /mcp enable <name> first.`\n\n探索中錯誤的來源 JSON 通常以警告/日誌方式處理；config-writer 路徑會拋出明確的錯誤。\n\n## 7) 實務撰寫指引\n\n在此程式碼庫中進行穩健的 MCP 撰寫：\n\n1. 在所有支援 MCP 的設定來源中保持伺服器名稱全域唯一。\n2. 優先使用英數字元/底線名稱，以避免在產生的 `mcp_*` 工具名稱中發生清理後的名稱碰撞。\n3. 使用明確的 `type` 以避免意外的 stdio 預設值。\n4. 將 `enabled: false` 視為硬關閉：伺服器會從執行階段連線集合中省略。\n5. 對於 OAuth 設定，儲存有效的 `credentialId`；否則認證注入會被跳過。\n6. 如果使用基於命令的密鑰解析（`!cmd`），請驗證命令輸出是穩定且非空的。\n\n## 實作檔案\n\n- [`src/mcp/types.ts`](../../packages/coding-agent/src/mcp/types.ts)\n- [`src/mcp/config.ts`](../../packages/coding-agent/src/mcp/config.ts)\n- [`src/mcp/config-writer.ts`](../../packages/coding-agent/src/mcp/config-writer.ts)\n- [`src/mcp/tool-bridge.ts`](../../packages/coding-agent/src/mcp/tool-bridge.ts)\n- [`src/discovery/mcp-json.ts`](../../packages/coding-agent/src/discovery/mcp-json.ts)\n- [`src/modes/controllers/mcp-command-controller.ts`](../../packages/coding-agent/src/modes/controllers/mcp-command-controller.ts)\n- [`src/mcp/manager.ts`](../../packages/coding-agent/src/mcp/manager.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/config/resolve-config-value.ts`](../../packages/coding-agent/src/config/resolve-config-value.ts)\n- [`src/mcp/loader.ts`](../../packages/coding-agent/src/mcp/loader.ts)\n",
	"zh-tw/natives/natives-addon-loader-runtime.md": "---\ntitle: 原生附加元件載入器執行階段\ndescription: >-\n  N-API addon loader runtime with platform detection, fallback strategies, and\n  module resolution.\nsidebar:\n  order: 3\n  label: 附加元件載入器\ni18n:\n  sourceHash: 743ea3e32c7c\n  translator: machine\n---\n\n# 原生附加元件載入器執行階段\n\n本文件深入探討 `@f5-sales-demo/pi-natives` 中的附加元件載入/驗證層：`native.ts` 如何決定載入哪個 `.node` 檔案、嵌入式酬載提取何時執行，以及啟動失敗如何回報。\n\n## 實作檔案\n\n- `packages/natives/src/native.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/package.json`\n\n## 範疇與職責\n\n載入器/執行階段的職責刻意維持狹窄：\n\n- 建立平台/CPU 感知的候選檔名與目錄清單。\n- 可選擇性地將嵌入式附加元件實體化至版本化的使用者快取目錄。\n- 以確定性順序嘗試候選項。\n- 在暴露繫結之前透過 `validateNative` 拒絕過時或不相容的附加元件。\n\n不在此範疇內：模組特定的 grep/text/highlight 行為。\n\n## 執行階段輸入與衍生狀態\n\n在模組初始化時（`export const native = loadNative();`），`native.ts` 計算靜態上下文：\n\n- **平台標籤**：``${process.platform}-${process.arch}``（例如 `darwin-arm64`）。\n- **套件版本**：來自 `packages/natives/package.json`（`version` 欄位）。\n- **核心目錄**：\n  - `nativeDir`：套件本地的 `packages/natives/native`。\n  - `execDir`：包含 `process.execPath` 的目錄。\n  - `versionedDir`：`<getNativesDir()>/<packageVersion>`。\n  - `userDataDir` 後備：\n    - Windows：`%LOCALAPPDATA%/xcsh`（或 `%USERPROFILE%/AppData/Local/xcsh`）。\n    - 非 Windows：`~/.local/bin`。\n- **已編譯二進位模式**（`isCompiledBinary`）：當以下任一條件為 true 時：\n  - `PI_COMPILED` 環境變數已設定，或\n  - `import.meta.url` 包含 Bun 嵌入標記（`$bunfs`、`~BUN`、`%7EBUN`）。\n- **變體覆寫**：`PI_NATIVE_VARIANT`（僅限 `modern`/`baseline`；無效值將被忽略）。\n- **選定的變體**：明確覆寫，否則在 x64 上進行執行階段 AVX2 偵測（若支援 AVX2 則為 `modern`，否則為 `baseline`）。\n\n## 平台支援與標籤解析\n\n`SUPPORTED_PLATFORMS` 固定為：\n\n- `linux-x64`\n- `linux-arm64`\n- `darwin-x64`\n- `darwin-arm64`\n- `win32-x64`\n\n行為細節：\n\n- 不支援的平台不會在前期被拒絕。\n- 載入器仍會先嘗試所有計算出的候選項。\n- 如果沒有任何候選項載入成功，它會拋出明確的不支援平台錯誤，並列出支援的標籤。\n\n這保留了對接近成功情況的有用診斷資訊，同時對真正不支援的目標仍會產生硬性失敗。\n\n## 變體選擇（`modern` / `baseline` / 預設）\n\n### x64 行為\n\n1. 如果 `PI_NATIVE_VARIANT` 是 `modern` 或 `baseline`，該值優先。\n2. 否則偵測 AVX2 支援：\n   - Linux：掃描 `/proc/cpuinfo` 尋找 `avx2`。\n   - macOS：查詢 `sysctl`（`machdep.cpu.leaf7_features`，後備 `machdep.cpu.features`）。\n   - Windows：執行 PowerShell `[System.Runtime.Intrinsics.X86.Avx2]::IsSupported`。\n3. 結果：\n   - AVX2 可用 -> `modern`\n   - AVX2 不可用/無法偵測 -> `baseline`\n\n### 非 x64 行為\n\n- 不使用變體；載入器維持預設檔名（`pi_natives.<platform>-<arch>.node`）。\n\n### 檔名建構\n\n給定 `tag = <platform>-<arch>`：\n\n- 非 x64 或無變體：`pi_natives.<tag>.node`\n- x64 + `modern`：依序嘗試\n  1. `pi_natives.<tag>-modern.node`\n  2. `pi_natives.<tag>-baseline.node`（有意的後備）\n- x64 + `baseline`：僅 `pi_natives.<tag>-baseline.node`\n\n最終錯誤訊息中使用的 `addonLabel` 為 `<tag>` 或 `<tag> (<variant>)`。\n\n## 候選路徑建構與後備順序\n\n`native.ts` 在任何 `require(...)` 呼叫之前建立候選池。\n\n### 發行候選項\n\n從變體解析的檔名清單建立，並依此順序搜尋：\n\n- **非編譯執行階段**：\n  1. `<nativeDir>/<filename>`\n  2. `<execDir>/<filename>`\n\n- **編譯執行階段**（`PI_COMPILED` 或 Bun 嵌入標記）：\n  1. `<versionedDir>/<filename>`\n  2. `<userDataDir>/<filename>`\n  3. `<nativeDir>/<filename>`\n  4. `<execDir>/<filename>`\n\n`dedupedCandidates` 移除重複項，同時保留首次出現的順序。\n\n### 最終執行階段順序\n\n在載入時：\n\n1. 可選的嵌入式提取候選項（如果產生的話）會被插入到最前面。\n2. 剩餘的去重候選項依序嘗試。\n3. 第一個同時通過 `require(...)` 和 `validateNative(...)` 的候選項勝出。\n\n## 嵌入式附加元件提取生命週期\n\n`embedded-addon.ts` 定義了一個產生的清單結構：\n\n- `platformTag`\n- `version`\n- `files[]`，其中每個項目包含 `variant`、`filename`、`filePath`\n\n目前已簽入的預設值為 `embeddedAddon: null`；已編譯的產出物可能會用實際的中繼資料替換它。\n\n### 提取狀態機\n\n提取（`maybeExtractEmbeddedAddon`）僅在所有閘門通過時執行：\n\n1. `isCompiledBinary === true`\n2. `embeddedAddon !== null`\n3. `embeddedAddon.platformTag === platformTag`\n4. `embeddedAddon.version === packageVersion`\n5. 找到與變體相符的嵌入式檔案\n\n變體檔案選擇反映執行階段變體意圖：\n\n- 非 x64：偏好 `default`，然後選擇第一個可用檔案。\n- x64 + `modern`：偏好 `modern`，後備至 `baseline`。\n- x64 + `baseline`：要求 `baseline`。\n\n實體化行為：\n\n1. 確保 `<versionedDir>` 存在（`mkdirSync(..., { recursive: true })`）。\n2. 如果 `<versionedDir>/<selected filename>` 已存在，則重用它（不重寫）。\n3. 否則讀取嵌入式來源 `filePath` 並寫入目標檔案。\n4. 傳回目標路徑作為最高優先級的載入嘗試。\n\n失敗時，提取不會立即崩潰；它會附加一個錯誤項目（目錄建立或寫入失敗），載入器繼續進行正常的候選項探測。\n\n## 生命週期與狀態轉換\n\n```text\nInit\n  -> Compute platform/version/variant/candidate lists\n  -> (Compiled + embedded manifest matches?)\n       yes -> Try extract embedded to versionedDir (record errors, continue)\n       no  -> Skip extraction\n  -> For each runtime candidate in order:\n       require(candidate)\n       -> success: validateNative\n            -> pass: return bindings (READY)\n            -> fail: record error, continue\n       -> failure: record error, continue\n  -> none loaded:\n       if unsupported platform tag -> throw Unsupported platform\n       else -> throw Failed to load (full tried-path diagnostics + hints)\n```\n\n## `validateNative` 合約檢查\n\n`validateNative(bindings, source)` 在啟動時對 `NativeBindings` 強制執行僅函式合約。\n\n機制：\n\n- 對每個必要的匯出名稱，它檢查 `typeof bindings[name] === \"function\"`。\n- 缺少的名稱會被彙總。\n- 如果有任何缺少，載入器會拋出：\n  - 來源附加元件路徑，\n  - 缺少的匯出清單，\n  - 重新建置命令提示。\n\n這是一個對過時二進位檔案、部分建置和符號/名稱漂移的硬性相容性閘門。\n\n### JS API ↔ 原生匯出對應（驗證閘門）\n\n| `validateNative` 中檢查的 JS 繫結名稱 | 預期的原生匯出名稱 |\n| --- | --- |\n| `grep` | `grep` |\n| `glob` | `glob` |\n| `highlightCode` | `highlightCode` |\n| `executeShell` | `executeShell` |\n| `PtySession` | `PtySession` |\n| `Shell` | `Shell` |\n| `visibleWidth` | `visibleWidth` |\n| `getSystemInfo` | `getSystemInfo` |\n| `getWorkProfile` | `getWorkProfile` |\n| `invalidateFsScanCache` | `invalidateFsScanCache` |\n\n注意：`bindings.ts` 僅宣告基礎的 `cancelWork(id)` 成員；模組 `types.ts` 檔案透過宣告合併新增 `validateNative` 所強制執行的額外符號。\n\n## 失敗行為與診斷\n\n## 不支援的平台\n\n如果所有候選項都失敗且 `platformTag` 不在 `SUPPORTED_PLATFORMS` 中，載入器會拋出：\n\n- `Unsupported platform: <tag>`\n- 完整的支援平台清單\n- 明確的問題回報指引\n\n## 過時二進位檔案 / 不匹配症狀\n\n典型的過時不匹配信號：\n\n- `Native addon missing exports (<candidate>). Missing: ...`\n\n常見原因：\n\n- 來自先前套件版本/API 結構的舊 `.node` 二進位檔案。\n- 選擇了錯誤的變體產出物（針對 x64）。\n- 載入的產出物中不存在新的 Rust 匯出。\n\n載入器行為：\n\n- 記錄每個候選項的缺少匯出失敗。\n- 繼續探測剩餘候選項。\n- 如果沒有候選項通過驗證，最終錯誤會包含每個嘗試過的路徑及其各自的失敗訊息。\n\n## 已編譯二進位檔案啟動失敗\n\n在編譯模式下，最終診斷包含：\n\n- 預期的版本化快取目標路徑（`<versionedDir>/<filename>`），\n- 刪除過時 `<versionedDir>` 並重新執行的修復方法，\n- 每個預期檔名的直接發行版下載 `curl` 命令。\n\n## 非編譯啟動失敗\n\n在正常套件/執行階段模式下，最終診斷包含：\n\n- 重新安裝提示（`bun install @f5-sales-demo/pi-natives`），\n- 本地重新建置命令（`bun --cwd=packages/natives run build`），\n- 可選的 x64 變體建置提示（`TARGET_VARIANT=baseline|modern ...`）。\n\n## 執行階段行為\n\n- 載入器始終使用發行候選項鏈。\n- 設定 `PI_DEV` 僅啟用每個候選項的主控台診斷（`Loaded native addon...` 和載入錯誤）。\n",
	"zh-tw/natives/natives-architecture.md": "---\ntitle: 原生模組架構\ndescription: Rust N-API 原生附加模組架構，橋接 TypeScript 與平台特定操作。\nsidebar:\n  order: 1\n  label: 架構\ni18n:\n  sourceHash: d38ed2437bb7\n  translator: machine\n---\n\n# 原生模組架構\n\n`@f5-sales-demo/pi-natives` 是一個三層堆疊架構：\n\n1. **TypeScript 封裝/API 層** 提供穩定的 JS/TS 進入點。\n2. **附加模組載入/驗證層** 為當前執行環境解析並驗證 `.node` 二進位檔案。\n3. **Rust N-API 模組層** 實作匯出至 JS 的效能關鍵基本元件。\n\n本文件是更深入模組層級文件的基礎。\n\n## 實作檔案\n\n- `packages/natives/src/index.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/embedded-addon.ts`\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `crates/pi-natives/src/lib.rs`\n\n## 第 1 層：TypeScript 封裝/API 層\n\n`packages/natives/src/index.ts` 是公開的桶狀模組（barrel module）。它按功能領域分組匯出，並重新匯出具型別的封裝函式，而非直接暴露原始 N-API 綁定。\n\n目前的頂層分組：\n\n- **搜尋/文字基本元件**：`grep`、`glob`、`text`、`highlight`\n- **執行/程序/終端基本元件**：`shell`、`pty`、`ps`、`keys`\n- **系統/媒體/轉換基本元件**：`image`、`html`、`clipboard`、`system-info`、`work`\n\n`packages/natives/src/bindings.ts` 定義了基礎介面契約：\n\n- `NativeBindings` 以共用成員開始（`cancelWork(id: number)`）\n- 模組特定的綁定透過各模組的 `types.ts` 使用宣告合併（declaration merging）來新增\n- `Cancellable` 為暴露取消功能的封裝函式標準化了逾時和中止信號選項\n\n**保證契約（API 面向）：** 消費者從 `@f5-sales-demo/pi-natives` 匯入並使用具型別的封裝函式。\n\n**實作細節（可能變更）：** 宣告合併和內部封裝佈局（`src/<module>/index.ts`、`src/<module>/types.ts`）。\n\n## 第 2 層：附加模組載入與驗證\n\n`packages/natives/src/native.ts` 負責執行階段的附加模組選擇、可選的解壓縮，以及匯出驗證。\n\n### 候選項解析模型\n\n- 平台標籤為 `\"${process.platform}-${process.arch}\"`。\n- 目前支援的標籤為：\n  - `linux-x64`\n  - `linux-arm64`\n  - `darwin-x64`\n  - `darwin-arm64`\n  - `win32-x64`\n- x64 可使用 CPU 變體：\n  - `modern`（支援 AVX2）\n  - `baseline`（備援方案）\n- 非 x64 使用預設檔名（無變體後綴）。\n\n檔名策略：\n\n- 正式版：`pi_natives.<platform>-<arch>.node`\n- x64 變體正式版：`pi_natives.<platform>-<arch>-modern.node` 和/或 `...-baseline.node`\n- `PI_DEV` 啟用載入器診斷資訊，但不會改變附加模組檔名\n\n### 平台特定變體偵測\n\n對於 x64，變體選擇使用：\n\n- **Linux**：`/proc/cpuinfo`\n- **macOS**：`sysctl machdep.cpu.leaf7_features` / `machdep.cpu.features`\n- **Windows**：PowerShell 檢查 `System.Runtime.Intrinsics.X86.Avx2`\n\n`PI_NATIVE_VARIANT` 可以明確強制指定 `modern` 或 `baseline`。\n\n### 二進位檔案分發與解壓縮模型\n\n`packages/natives/package.json` 在發布的檔案中包含 `src` 和 `native`。`native/` 目錄儲存預建置的平台產物。\n\n對於編譯二進位檔案（`PI_COMPILED` 或 Bun 嵌入式執行環境標記），載入器行為為：\n\n1. 檢查版本化的使用者快取路徑：`<getNativesDir()>/<packageVersion>/...`\n2. 檢查舊版編譯二進位檔案位置：\n   - Windows：`%LOCALAPPDATA%/xcsh`（備援 `%USERPROFILE%/AppData/Local/xcsh`）\n   - 非 Windows：`~/.local/bin`\n3. 回退至打包的 `native/` 和可執行檔目錄候選項\n\n如果存在嵌入式附加模組清單（由 `scripts/embed-native.ts` 產生的 `embedded-addon.ts`），`native.ts` 可以在載入前將匹配的嵌入式二進位檔案具體化到版本化的快取目錄中。\n\n### 驗證與失敗模式\n\n在 `require(candidate)` 之後，`validateNative(...)` 會驗證必要的匯出（例如 `grep`、`glob`、`highlightCode`、`PtySession`、`Shell`、`getSystemInfo`、`getWorkProfile`、`invalidateFsScanCache`）。\n\n失敗路徑是明確的：\n\n- **不支援的平台標籤**：拋出錯誤並列出支援的平台清單\n- **無可載入的候選項**：拋出錯誤並列出所有嘗試過的路徑及修復建議\n- **缺少匯出**：拋出錯誤並列出確切缺少的名稱及重建命令\n- **嵌入式解壓縮錯誤**：記錄目錄/寫入失敗，並將其包含在最終的載入診斷資訊中\n\n**保證契約（API 面向）：** 附加模組載入要麼成功並取得已驗證的綁定集合，要麼快速失敗並提供可操作的錯誤文字。\n\n**實作細節（可能變更）：** 確切的候選項搜尋順序和編譯二進位檔案備援路徑排序。\n\n## 第 3 層：Rust N-API 模組層\n\n`crates/pi-natives/src/lib.rs` 是宣告匯出模組所有權的 Rust 入口模組：\n\n- `clipboard`\n- `fd`\n- `fs_cache`\n- `glob`\n- `glob_util`\n- `grep`\n- `highlight`\n- `html`\n- `image`\n- `keys`\n- `prof`\n- `ps`\n- `pty`\n- `shell`\n- `system_info`\n- `task`\n- `text`\n\n這些模組實作了由 `native.ts` 消費和驗證的 N-API 符號。JS 層級的名稱透過 `packages/natives/src` 中的 TS 封裝函式來呈現。\n\n**保證契約（API 面向）：** Rust 模組匯出必須與 `validateNative` 和封裝模組所預期的綁定名稱一致。\n\n**實作細節（可能變更）：** 內部 Rust 模組分解和輔助模組邊界（`glob_util`、`task` 等）。\n\n## 所有權邊界\n\n在架構層面，所有權劃分如下：\n\n- **TS 封裝/API 所有權（`packages/natives/src`）**\n  - 公開 API 分組、選項型別定義，以及穩定的 JS 人體工學設計\n  - 向呼叫者暴露的取消介面（`timeoutMs`、`AbortSignal`）\n- **載入器所有權（`packages/natives/src/native.ts`）**\n  - 執行階段二進位檔案選擇\n  - CPU 變體選擇和覆寫處理\n  - 編譯二進位檔案解壓縮和候選項探測\n  - 必要原生匯出的嚴格驗證\n- **Rust 所有權（`crates/pi-natives/src`）**\n  - 演算法和系統層級實作\n  - 平台原生行為和效能敏感邏輯\n  - TS 封裝函式所消費的 N-API 符號實作\n\n## 執行階段流程（高層次）\n\n1. 消費者從 `@f5-sales-demo/pi-natives` 匯入。\n2. 封裝模組呼叫單例 `native` 綁定。\n3. `native.ts` 為平台/架構/變體選擇候選二進位檔案。\n4. 對於編譯分發版本，可選地進行嵌入式二進位檔案解壓縮。\n5. 載入附加模組並驗證匯出集合。\n6. 封裝函式向呼叫者回傳具型別的結果。\n\n## 術語表\n\n- **原生附加模組**：透過 Node-API (N-API) 載入的 `.node` 二進位檔案。\n- **平台標籤**：執行階段的元組 `platform-arch`（例如 `darwin-arm64`）。\n- **變體**：x64 CPU 特定的建置風格（`modern` AVX2、`baseline` 備援方案）。\n- **封裝函式**：在原始原生匯出之上提供具型別 API 的 TS 函式/類別。\n- **宣告合併**：模組 `types.ts` 檔案用來擴展 `NativeBindings` 的 TS 技術。\n- **編譯二進位檔案模式**：CLI 被打包的執行階段模式，原生附加模組從解壓縮/快取路徑解析，而非僅從套件本地路徑解析。\n- **嵌入式附加模組**：產生到 `embedded-addon.ts` 中的建置產物中繼資料和檔案參考，使編譯二進位檔案可以解壓縮匹配的 `.node` 負載。\n- **驗證閘道**：`validateNative(...)` 檢查，用於拒絕缺少必要匯出的過時/不匹配二進位檔案。\n",
	"zh-tw/natives/natives-binding-contract.md": "---\ntitle: 原生綁定契約（TypeScript 端）\ndescription: 透過 N-API 呼叫 Rust 原生函式的 TypeScript 端綁定契約。\nsidebar:\n  order: 2\n  label: 綁定契約\ni18n:\n  sourceHash: 36dc5fed1f0a\n  translator: machine\n---\n\n# 原生綁定契約（TypeScript 端）\n\n本文件定義了 `@f5-sales-demo/pi-natives` 呼叫端與載入的 N-API 附加元件之間的 TypeScript 端契約。\n\n本文著重於三個部分：\n\n1. 契約形狀（`NativeBindings` + 模組擴充），\n2. 包裝器行為（`src/<module>/index.ts`），\n3. 公開匯出介面（`src/index.ts`）。\n\n## 實作檔案\n\n- `packages/natives/src/bindings.ts`\n- `packages/natives/src/native.ts`\n- `packages/natives/src/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/system-info/types.ts`\n- `packages/natives/src/system-info/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/work/types.ts`\n- `packages/natives/src/work/index.ts`\n\n## 契約模型\n\n`packages/natives/src/bindings.ts` 定義了基礎契約：\n\n- `NativeBindings`（基礎介面，目前包含 `cancelWork(id: number): void`）\n- `Cancellable`（`timeoutMs?: number`、`signal?: AbortSignal`）\n- `TsFunc<T>` N-API 執行緒安全回呼所使用的回呼形狀\n\n每個模組透過宣告合併來新增自己的欄位：\n\n```ts\n// packages/natives/src/<module>/types.ts\ndeclare module \"../bindings\" {\n interface NativeBindings {\n  grep(options: GrepOptions, onMatch?: TsFunc<GrepMatch>): Promise<GrepResult>;\n }\n}\n```\n\n這樣可以維持一個聚合的綁定介面，而不需要一個龐大的集中式型別檔案。\n\n## 宣告合併生命週期與狀態轉換\n\n### 1) 編譯期型別組裝\n\n- `bindings.ts` 提供基礎的 `NativeBindings` 符號。\n- 每個 `src/<module>/types.ts` 擴充 `NativeBindings`。\n- `src/native.ts` 為了副作用匯入所有 `./<module>/types` 檔案，使合併後的契約在使用 `NativeBindings` 的地方處於作用域內。\n\n狀態轉換：**基礎契約** → **合併後契約**。\n\n### 2) 執行期附加元件載入與驗證閘門\n\n- `src/native.ts` 載入候選的 `.node` 二進位檔案。\n- 載入的物件被視為 `NativeBindings` 並立即通過 `validateNative(...)` 驗證。\n- `validateNative` 透過 `typeof bindings[name] === \"function\"` 驗證所需的匯出鍵。\n\n狀態轉換：**未信任的附加元件物件** → **已驗證的原生綁定物件**（或硬性失敗）。\n\n### 3) 包裝器調用\n\n- `src/<module>/index.ts` 中的模組包裝器呼叫 `native.<export>`。\n- 包裝器調整預設值和回呼形狀（JS API 中的 `(err, value)` 轉換為僅值回呼模式）。\n- `src/index.ts` 重新匯出模組包裝器/型別作為公開套件 API。\n\n狀態轉換：**已驗證的原始綁定** → **人性化的公開 API**。\n\n## 包裝器職責\n\n包裝器被刻意設計為薄層；它們不會重新實作原生邏輯。\n\n主要職責：\n\n- **引數正規化/預設值設定**\n  - `glob()` 將 `options.path` 解析為絕對路徑，並設定 `hidden`、`gitignore`、`recursive` 的預設值。\n  - `hasMatch()` 在原生呼叫前填入預設旗標（`ignoreCase`、`multiline`）。\n- **回呼適配**\n  - `grep()`、`glob()`、`executeShell()` 將 `TsFunc<T>`（`error, value`）轉換為使用者回呼，僅接收成功的值。\n- **圍繞原生呼叫的環境或策略行為**\n  - 剪貼簿包裝器新增 OSC52/Termux/無頭模式處理，並將複製視為盡力而為的操作。\n- **公開命名與重新匯出整理**\n  - `searchContent()` 對應到原生匯出 `search`。\n\n## 公開匯出介面組織\n\n`packages/natives/src/index.ts` 是標準的公開桶檔案。它按功能領域分組匯出：\n\n- 搜尋/文字：`grep`、`glob`、`text`、`highlight`\n- 執行/程序/終端：`shell`、`pty`、`ps`、`keys`\n- 系統/媒體/轉換：`image`、`html`、`clipboard`、`system-info`、`work`\n\n維護者規則：如果包裝器未從 `src/index.ts` 重新匯出，則它不屬於預期的公開套件介面。\n\n## JS API ↔ 原生匯出對應（代表性範例）\n\nRust 端使用 N-API 匯出名稱（通常從 `#[napi]` snake_case -> camelCase 轉換而來，偶爾使用明確的別名），這些名稱必須與這些綁定鍵匹配。\n\n| 類別 | 公開 JS API（包裝器） | 原生綁定鍵 | 回傳型別 | 非同步？ |\n|---|---|---|---|---|\n| Grep | `grep(options, onMatch?)` | `grep` | `Promise<GrepResult>` | 是 |\n| Grep | `searchContent(content, options)` | `search` | `SearchResult` | 否 |\n| Grep | `hasMatch(content, pattern, opts?)` | `hasMatch` | `boolean` | 否 |\n| Grep | `fuzzyFind(options)` | `fuzzyFind` | `Promise<FuzzyFindResult>` | 是 |\n| Glob | `glob(options, onMatch?)` | `glob` | `Promise<GlobResult>` | 是 |\n| Glob | `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `void` | 否 |\n| Shell | `executeShell(options, onChunk?)` | `executeShell` | `Promise<ShellExecuteResult>` | 是 |\n| Shell | `Shell` | `Shell` | class constructor | N/A |\n| PTY | `PtySession` | `PtySession` | class constructor | N/A |\n| Text | `truncateToWidth(...)` | `truncateToWidth` | `string` | 否 |\n| Text | `sliceWithWidth(...)` | `sliceWithWidth` | `SliceWithWidthResult` | 否 |\n| Text | `visibleWidth(text)` | `visibleWidth` | `number` | 否 |\n| Highlight | `highlightCode(code, lang, colors)` | `highlightCode` | `string` | 否 |\n| HTML | `htmlToMarkdown(html, options?)` | `htmlToMarkdown` | `Promise<string>` | 是 |\n| System | `getSystemInfo()` | `getSystemInfo` | `SystemInfo` | 否 |\n| Work | `getWorkProfile(lastSeconds)` | `getWorkProfile` | `WorkProfile` | 否 |\n| Process | `killTree(pid, signal)` | `killTree` | `number` | 否 |\n| Process | `listDescendants(pid)` | `listDescendants` | `number[]` | 否 |\n| Clipboard | `copyToClipboard(text)` | `copyToClipboard` | `Promise<void>`（盡力而為的包裝器行為） | 是 |\n| Clipboard | `readImageFromClipboard()` | `readImageFromClipboard` | `Promise<ClipboardImage \\| null>` | 是 |\n| Keys | `parseKey(data, kittyProtocolActive)` | `parseKey` | `string \\| null` | 否 |\n\n## 同步與非同步契約差異\n\n契約混合了同步和非同步 API；包裝器保留原生呼叫風格，而非強制採用單一模型：\n\n- **基於 Promise 的非同步匯出**用於 I/O 或長時間執行的工作（`grep`、`glob`、`htmlToMarkdown`、`executeShell`、剪貼簿、影像操作）。\n- **同步匯出**用於確定性的記憶體內轉換/解析器（`search`、`hasMatch`、語法高亮、文字寬度/切片、按鍵解析、程序查詢）。\n- **建構子匯出**用於有狀態的執行期物件（`Shell`、`PtySession`、`PhotonImage`）。\n\n對維護者的意義：更改現有匯出的同步 ↔ 非同步性質是跨包裝器和呼叫端的破壞性 API 與契約變更。\n\n## 物件與列舉型別模式\n\n### 物件模式（`#[napi(object)]` 風格的 JS 物件）\n\nTypeScript 將物件形狀的原生值建模為介面，例如：\n\n- `GrepResult`、`SearchResult`、`GlobResult`\n- `SystemInfo`、`WorkProfile`\n- `ClipboardImage`、`ParsedKittyResult`\n\n這些是編譯期的結構性契約；執行期的形狀正確性由原生實作負責。\n\n### 列舉模式\n\n數值型原生列舉在 TypeScript 中表示為 `const enum` 值：\n\n- `FileType`（`1=file`、`2=dir`、`3=symlink`）\n- `ImageFormat`（`0=PNG`、`1=JPEG`、`2=WEBP`、`3=GIF`）\n- `SamplingFilter`、`Ellipsis`、`KeyEventType`\n\n呼叫端看到具名的列舉成員；綁定邊界傳遞的是數字。\n\n## 如何捕捉不一致\n\n不一致的偵測發生在兩個層級：\n\n1. **編譯期 TypeScript 契約檢查**\n   - 包裝器透過合併後的 `NativeBindings` 呼叫 `native.<name>`。\n   - 缺少/更名的綁定鍵會導致包裝器中的 TypeScript 型別檢查失敗。\n\n2. **`validateNative` 中的執行期驗證**\n   - 載入後，`native.ts` 檢查所需的匯出，如果有缺少則拋出錯誤。\n   - 錯誤訊息包含缺少的鍵和重新建置指示。\n\n這可以捕捉常見的過期二進位檔偏移問題：包裝器/型別存在但載入的 `.node` 缺少該匯出。\n\n## 失敗行為與注意事項\n\n### 載入/驗證失敗（硬性失敗）\n\n- 附加元件載入失敗或不支援的平台會在 `native.ts` 中的模組初始化期間拋出錯誤。\n- 缺少所需的匯出會在包裝器可用之前拋出錯誤。\n\n效果：套件會快速失敗，而非將失敗延遲到第一次呼叫時。\n\n### 包裝器層級的行為差異\n\n- 某些包裝器刻意軟化失敗（`copyToClipboard` 是盡力而為的操作，會吞噬原生失敗）。\n- 串流回呼忽略回呼的錯誤負載，僅轉發成功的值事件。\n\n### 型別層級的注意事項（執行期比 TypeScript 更嚴格）\n\n- TypeScript 的可選欄位不保證語義有效性；原生層仍然可以拒絕格式錯誤的值。\n- `const enum` 型別不能防止未型別化的呼叫端在執行期傳入超出範圍的數值。\n- `validateNative` 僅檢查所需匯出的存在性/是否為函式，不檢查深層的引數/回傳形狀相容性。\n- `bindings.ts` 在基礎介面中包含 `cancelWork(id)`，但目前的執行期驗證清單並未強制要求該鍵。\n\n## 綁定變更的維護者檢查清單\n\n新增/變更匯出時，需更新以下所有項目：\n\n1. `src/<module>/types.ts`（擴充 + 契約型別）\n2. `src/<module>/index.ts`（包裝器行為）\n3. `src/native.ts` 對模組型別的匯入（如果是新模組）\n4. `validateNative` 所需的匯出檢查\n5. `src/index.ts` 公開重新匯出\n\n跳過任何步驟都會產生編譯期偏移或執行期載入失敗。\n",
	"zh-tw/natives/natives-build-release-debugging.md": "---\ntitle: Natives 建置、發布與除錯操作手冊\ndescription: 跨平台 Rust 原生擴充模組的建置、發布與除錯操作手冊。\nsidebar:\n  order: 8\n  label: 建置、發布與除錯\ni18n:\n  sourceHash: efe47aa5b466\n  translator: machine\n---\n\n# Natives 建置、發布與除錯操作手冊\n\n本操作手冊說明 `@f5-sales-demo/pi-natives` 建置管線如何產生 `.node` 擴充模組、編譯後的發行版本如何載入它們，以及如何除錯載入器/建置失敗問題。\n\n本文遵循 `docs/natives-architecture.md` 中的架構術語：\n\n- **建置階段產物產生** (`scripts/build-native.ts`)\n- **嵌入式擴充模組清單產生** (`scripts/embed-native.ts`)\n- **執行階段擴充模組載入 + 驗證閘門** (`src/native.ts`)\n\n## 實作檔案\n\n- `packages/natives/scripts/build-native.ts`\n- `packages/natives/scripts/embed-native.ts`\n- `packages/natives/package.json`\n- `packages/natives/src/native.ts`\n- `crates/pi-natives/Cargo.toml`\n\n## 建置管線概觀\n\n### 1) 建置進入點\n\n`packages/natives/package.json` 腳本：\n\n- `bun scripts/build-native.ts` (`build`) → 正式版建置\n- `bun scripts/build-native.ts --dev` (`dev:native`) → 除錯/開發模式建置（輸出命名相同）\n- `bun scripts/embed-native.ts` (`embed:native`) → 從建置檔案產生 `src/embedded-addon.ts`\n\n### 2) Rust 產物建置\n\n`build-native.ts` 在 `crates/pi-natives` 中執行 Cargo：\n\n- 基本命令：`cargo build`\n- 正式版模式加入 `--release`，除非傳入 `--dev`\n- 跨平台目標加入 `--target <CROSS_TARGET>`\n\n`crates/pi-natives/Cargo.toml` 宣告 `crate-type = [\"cdylib\"]`，因此 Cargo 會輸出共享函式庫（`.so`/`.dylib`/`.dll`），然後被複製/重新命名為 `.node` 擴充模組檔名。\n\n### 3) 產物探索與安裝\n\nCargo 完成後，`build-native.ts` 依序掃描候選輸出目錄：\n\n1. `${CARGO_TARGET_DIR}`（若已設定）\n2. `<repo>/target`\n3. `crates/pi-natives/target`\n\n對每個根目錄檢查設定檔目錄：\n\n- 跨平台建置：`<root>/<crossTarget>/<profile>` 然後 `<root>/<profile>`\n- 本機建置：`<root>/<profile>`\n\n然後尋找以下其中之一：\n\n- `libpi_natives.so`\n- `libpi_natives.dylib`\n- `pi_natives.dll`\n- `libpi_natives.dll`\n\n找到後，以暫存檔 + 重新命名的原子性語義安裝到 `packages/natives/native/`（Windows 備援機制會明確處理鎖定的 DLL 替換失敗）。\n\n## 目標/變體模型與命名慣例\n\n## 平台標籤\n\n建置和執行階段皆使用平台標籤：\n\n`<platform>-<arch>`（例如：`darwin-arm64`、`linux-x64`）\n\n## 變體模型（僅限 x64）\n\nx64 支援 CPU 變體：\n\n- `modern`（AVX2 相容路徑）\n- `baseline`（備援）\n\n非 x64 使用單一預設產物（無變體後綴）。\n\n### 輸出檔名\n\n正式版建置：\n\n- x64：`pi_natives.<platform>-<arch>-modern.node` 或 `...-baseline.node`\n- 非 x64：`pi_natives.<platform>-<arch>.node`\n\n開發建置（`--dev`）：\n\n- 使用除錯模式旗標但保持標準平台標籤輸出命名\n\n`native.ts` 中的執行階段載入器候選順序：\n\n- 正式版候選\n- 編譯模式在套件本地檔案之前加入解壓/快取候選\n\n## 環境旗標與建置選項\n\n## 執行階段旗標\n\n- `PI_DEV`（載入器行為）：啟用載入器診斷資訊\n- `PI_NATIVE_VARIANT`（載入器行為，僅限 x64）：在執行階段強制選擇 `modern` 或 `baseline`\n- `PI_COMPILED`（載入器行為）：啟用編譯二進位候選/解壓行為\n\n## 建置階段旗標/選項\n\n- `--dev`（腳本引數）：建置除錯模式\n- `CROSS_TARGET`：傳遞給 Cargo `--target`\n- `TARGET_PLATFORM`：覆寫輸出平台標籤命名\n- `TARGET_ARCH`：覆寫輸出架構命名\n- `TARGET_VARIANT`（僅限 x64）：強制輸出檔名和 RUSTFLAGS 策略使用 `modern` 或 `baseline`\n- `CARGO_TARGET_DIR`：搜尋 Cargo 輸出時的額外根目錄\n- `RUSTFLAGS`：\n  - 若未設定且非跨平台編譯，腳本會設定：\n    - modern：`-C target-cpu=x86-64-v3`\n    - baseline：`-C target-cpu=x86-64-v2`\n    - 非 x64 / 無變體：`-C target-cpu=native`\n  - 若已設定，腳本不會覆寫\n\n## 建置狀態/生命週期轉換\n\n### 建置生命週期（`build-native.ts`）\n\n1. **初始化**：解析引數/環境變數（`--dev`、目標覆寫、跨平台旗標）\n2. **變體解析**：\n   - 非 x64 → 無變體\n   - x64 + `TARGET_VARIANT` → 明確變體\n   - x64 跨平台建置且無 `TARGET_VARIANT` → 硬錯誤\n   - x64 本機建置且無覆寫 → 偵測主機 AVX2\n3. **編譯**：以解析的模式/目標執行 Cargo\n4. **定位產物**：掃描目標根目錄/模式目錄/函式庫名稱\n5. **安裝**：複製 + 原子性重新命名到 `packages/natives/native`\n6. **完成**：擴充模組準備就緒，可供載入器候選使用\n\n失敗會在任何階段以明確錯誤文字退出（無效變體、Cargo 建置失敗、找不到輸出函式庫、安裝/重新命名失敗）。\n\n### 嵌入生命週期（`embed-native.ts`）\n\n1. **初始化**：從 `TARGET_PLATFORM`/`TARGET_ARCH` 或主機值計算平台標籤\n2. **候選集合**：\n   - x64 預期同時有 `modern` 和 `baseline`\n   - 非 x64 預期一個預設檔案\n3. **驗證可用性**（在 `packages/natives/native` 中）\n4. **產生清單**（`src/embedded-addon.ts`），包含 Bun `file` 匯入和套件版本\n5. **執行階段解壓就緒**，供編譯模式使用\n\n`--reset` 繞過驗證並寫入空清單存根（`embeddedAddon = null`）。\n\n## 開發工作流程 vs 出貨/編譯行為\n\n## 本機開發工作流程\n\n典型本機迴圈：\n\n1. 建置擴充模組：\n   - 正式版：`bun --cwd=packages/natives run build`\n   - 除錯模式：`bun --cwd=packages/natives run dev:native`\n2. 測試載入器診斷時設定 `PI_DEV=1`\n3. `native.ts` 中的載入器解析套件本地 `native/`（及可執行檔目錄備援）候選\n4. `validateNative` 在包裝函式使用繫結之前強制執行匯出相容性檢查\n\n## 出貨/編譯二進位工作流程\n\n在編譯模式下（`PI_COMPILED` 或 Bun 嵌入標記）：\n\n1. 載入器計算版本化快取目錄：`<getNativesDir()>/<packageVersion>`（實際上為 `~/.xcsh/natives/<version>`）\n2. 若嵌入清單與目前平台+版本匹配，載入器可能將選定的嵌入檔案解壓到該版本化目錄\n3. 執行階段候選順序包括：\n   - 版本化快取目錄\n   - 舊版編譯二進位目錄（Windows 上為 `%LOCALAPPDATA%/xcsh`，其他為 `~/.local/bin`）\n   - 套件/可執行檔目錄\n4. 第一個成功載入的擴充模組仍必須通過 `validateNative`\n\n這就是為什麼打包 + 執行階段載入器的預期必須對齊：檔名、平台標籤和匯出符號必須與 `native.ts` 探測和驗證的內容相符。\n\n## JS API ↔ Rust 匯出對應（驗證閘門子集）\n\n`native.ts` 要求載入的擴充模組上存在這些 JS 可見的匯出。它們對應到 `crates/pi-natives/src` 中的 Rust N-API 匯出：\n\n| `validateNative` 要求的 JS 名稱 | Rust 匯出宣告 | Rust 原始檔 |\n| --- | --- | --- |\n| `glob` | `#[napi] pub fn glob(...)` | `crates/pi-natives/src/glob.rs` |\n| `grep` | `#[napi] pub fn grep(...)` | `crates/pi-natives/src/grep.rs` |\n| `search` | `#[napi] pub fn search(...)` | `crates/pi-natives/src/grep.rs` |\n| `highlightCode` | `#[napi] pub fn highlight_code(...)` | `crates/pi-natives/src/highlight.rs` |\n| `getSystemInfo` | `#[napi] pub fn get_system_info(...)` | `crates/pi-natives/src/system_info.rs` |\n| `getWorkProfile` | `#[napi] pub fn get_work_profile(...)`（駝峰式匯出） | `crates/pi-natives/src/prof.rs` |\n| `invalidateFsScanCache` | `#[napi] pub fn invalidate_fs_scan_cache(...)` | `crates/pi-natives/src/fs_cache.rs` |\n\n若任何必要符號缺失，載入器會立即失敗並提供重新建置提示。\n\n## 失敗行為與診斷\n\n## 建置階段失敗\n\n- 無效的變體設定：\n  - `TARGET_VARIANT` 設定在非 x64 上 → 立即錯誤\n  - x64 跨平台建置且無明確 `TARGET_VARIANT` → 立即錯誤\n- Cargo 建置失敗：\n  - 腳本顯示非零退出碼和 stderr\n- 找不到產物：\n  - 腳本印出每個已檢查的模式目錄\n- 安裝失敗：\n  - 明確訊息；Windows 包含鎖定檔案提示\n\n## 執行階段載入器失敗（`native.ts`）\n\n- 不支援的平台標籤：\n  - 拋出錯誤並列出支援的平台清單\n- 無法載入任何候選：\n  - 拋出錯誤並列出完整的候選錯誤清單和特定模式的修復提示\n- 缺少匯出：\n  - 拋出錯誤並列出確切的缺失符號名稱和重新建置命令\n- 嵌入解壓問題：\n  - 解壓的 mkdir/write 錯誤會被記錄並包含在最終診斷中\n\n## 疑難排解矩陣\n\n| 症狀 | 可能原因 | 驗證方式 | 修復方法 |\n| --- | --- | --- | --- |\n| `Native addon missing exports ... Missing: <name>` | 過時的 `.node` 二進位檔、Rust 匯出名稱不匹配、或載入了錯誤的二進位檔 | 使用 `PI_DEV=1` 執行以查看載入路徑；檢查該檔案的匯出清單 | 重新建置 `build`；確保 Rust `#[napi]` 匯出名稱（或需要時的明確別名）與 JS 鍵匹配；移除過時的快取/版本化檔案 |\n| x64 機器在預期 modern 時載入了 baseline | `PI_NATIVE_VARIANT=baseline`、未偵測到 AVX2、或僅存在 baseline 檔案 | 檢查 `PI_NATIVE_VARIANT`；檢查 `native/` 中是否有 `-modern` 檔案 | 建置 modern 變體（`TARGET_VARIANT=modern ... build`）並確保檔案已出貨 |\n| 跨平台建置產生無法使用/標記錯誤的二進位檔 | `CROSS_TARGET` 與 `TARGET_PLATFORM`/`TARGET_ARCH` 不匹配、或 x64 缺少 `TARGET_VARIANT` | 確認環境變數組合和輸出檔名 | 使用一致的環境變數值和明確的 x64 `TARGET_VARIANT` 重新執行 |\n| 升級後編譯二進位失敗 | 過時的解壓快取（`~/.xcsh/natives/<old-or-mismatched-version>`）或嵌入清單不匹配 | 檢查版本化 natives 目錄和載入器錯誤清單 | 刪除該套件版本的版本化 natives 快取並重新執行；在打包期間重新產生嵌入清單 |\n| 載入器探測多個路徑且全部失敗 | 平台不匹配或套件 `native/` 中缺少正式版產物 | 檢查 `platformTag` 與實際檔名 | 確保建置的檔名完全符合 `pi_natives.<platform>-<arch>(-variant).node` 慣例且套件包含 `native/` |\n| `embed:native` 失敗並出現 \"Incomplete native addons\" | 嵌入前未建置所需的變體檔案 | 檢查錯誤文字中的預期 vs 找到清單 | 先建置所需檔案（x64：modern+baseline 兩者；非 x64：預設），然後重新執行 `embed:native` |\n\n## 操作命令\n\n```bash\n# 當前主機的正式版產物\nbun --cwd=packages/natives run build\n\n# 除錯模式產物建置\nbun --cwd=packages/natives run dev:native\n\n# 建置明確的 x64 變體\nTARGET_VARIANT=modern bun --cwd=packages/natives run build\nTARGET_VARIANT=baseline bun --cwd=packages/natives run build\n\n# 從建置的原生檔案產生嵌入式擴充模組清單\nbun --cwd=packages/natives run embed:native\n\n# 重設嵌入清單為空存根\nbun --cwd=packages/natives run embed:native -- --reset\n```\n",
	"zh-tw/natives/natives-media-system-utils.md": "---\ntitle: 原生媒體與系統工具\ndescription: 用於截圖、影像處理和系統資訊的原生媒體處理工具。\nsidebar:\n  order: 7\n  label: 媒體與系統工具\ni18n:\n  sourceHash: 430898c177bc\n  translator: machine\n---\n\n# 原生媒體 + 系統工具\n\n本文件是 [`docs/natives-architecture.md`](./natives-architecture.md) 中描述的**系統/媒體/轉換原語**層的子系統深入探討：`image`、`html`、`clipboard` 和 `work` 效能分析。\n\n## 實作檔案\n\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/prof.rs`\n- `crates/pi-natives/src/task.rs`\n- `packages/natives/src/image/index.ts`\n- `packages/natives/src/image/types.ts`\n- `packages/natives/src/html/index.ts`\n- `packages/natives/src/html/types.ts`\n- `packages/natives/src/clipboard/index.ts`\n- `packages/natives/src/clipboard/types.ts`\n- `packages/natives/src/work/index.ts`\n- `packages/natives/src/work/types.ts`\n\n> 注意：不存在 `crates/pi-natives/src/work.rs`；工作效能分析實作於 `prof.rs`，並由 `task.rs` 中的檢測機制提供資料。\n\n## TS API ↔ Rust 匯出/模組對應\n\n| TS 匯出 (packages/natives)                  | Rust N-API 匯出                                                         | Rust 模組                             |\n| ------------------------------------------- | ----------------------------------------------------------------------- | ------------------------------------- |\n| `PhotonImage.parse(bytes)`                  | `PhotonImage::parse`                                                     | `image.rs`                            |\n| `PhotonImage#resize(width, height, filter)` | `PhotonImage::resize`                                                    | `image.rs`                            |\n| `PhotonImage#encode(format, quality)`       | `PhotonImage::encode`                                                    | `image.rs`                            |\n| `htmlToMarkdown(html, options)`             | `html_to_markdown`                                                       | `html.rs`                             |\n| `copyToClipboard(text)`                     | `copy_to_clipboard` + TS 備援邏輯                                        | `clipboard.rs` + `clipboard/index.ts` |\n| `readImageFromClipboard()`                  | `read_image_from_clipboard`                                              | `clipboard.rs`                        |\n| `getWorkProfile(lastSeconds)`               | `get_work_profile`                                                      | `prof.rs`                             |\n\n## 資料格式邊界與轉換\n\n### 影像 (`image`)\n\n- **JS 輸入邊界**：`Uint8Array` 編碼的影像位元組。\n- **Rust 解碼邊界**：位元組被複製到 `Vec<u8>`，透過 `ImageReader::with_guessed_format()` 猜測格式，然後解碼為 `DynamicImage`。\n- **記憶體中狀態**：`PhotonImage` 儲存 `Arc<DynamicImage>`。\n- **輸出邊界**：`encode(format, quality)` 回傳 `Promise<Uint8Array>`（Rust `Vec<u8>`）。\n\n格式 ID 為數值型：\n\n- `0`：PNG\n- `1`：JPEG\n- `2`：WebP（無損編碼器）\n- `3`：GIF\n\n限制條件：\n\n- `quality` 僅用於 JPEG。\n- PNG/WebP/GIF 忽略 `quality`。\n- 不支援的格式 ID 會失敗（`Invalid image format: <id>`）。\n\n### HTML 轉換 (`html`)\n\n- **JS 輸入邊界**：HTML `string` + 可選物件 `{ cleanContent?: boolean; skipImages?: boolean }`。\n- **Rust 轉換邊界**：`String` 輸入由 `html_to_markdown_rs::convert` 轉換。\n- **輸出邊界**：Markdown `string`。\n\n轉換行為：\n\n- `cleanContent` 預設為 `false`。\n- 當 `cleanContent=true` 時，啟用 `PreprocessingPreset::Aggressive` 預處理及導覽/表單的強制移除旗標。\n- `skipImages` 預設為 `false`。\n\n### 剪貼簿 (`clipboard`)\n\n- **文字路徑**：\n  - TS 首先在 stdout 為 TTY 時發送 OSC 52（`\\x1b]52;c;<base64>\\x07`）。\n  - 然後以盡力模式透過原生剪貼簿 API（`native.copyToClipboard`）嘗試相同文字。\n  - 在 Termux 上，TS 會先嘗試 `termux-clipboard-set`。\n- **影像讀取路徑**：\n  - Rust 從 `arboard` 讀取原始影像。\n  - Rust 將其重新編碼為 PNG 位元組（`image` crate），回傳 `{ data: Uint8Array, mimeType: \"image/png\" }`。\n  - 在 Termux 或沒有顯示伺服器的 Linux 工作階段（缺少 `DISPLAY`/`WAYLAND_DISPLAY`）上，TS 會提前回傳 `null`。\n\n### 工作效能分析 (`work`)\n\n- **收集邊界**：效能分析樣本由 `task::blocking` 和 `task::future` 中的 `profile_region(tag)` 守衛產生。\n- **儲存格式**：固定大小的環形緩衝區（`MAX_SAMPLES = 10_000`），儲存堆疊路徑 + 持續時間（`μs`）+ 時間戳記（`自程序啟動以來的 μs`）。\n- **輸出邊界**：`getWorkProfile(lastSeconds)` 回傳物件：\n  - `folded`：折疊堆疊文字（火焰圖輸入）\n  - `summary`：Markdown 表格摘要\n  - `svg`：可選的火焰圖 SVG\n  - `totalMs`、`sampleCount`\n\n## 生命週期與狀態轉換\n\n### 影像生命週期\n\n1. `PhotonImage.parse(bytes)` 排程一個阻塞式解碼任務（`image.decode`）。\n2. 成功後，JS 中存在一個原生 `PhotonImage` 控制代碼。\n3. `resize(...)` 建立一個新的原生控制代碼（`image.resize`），新舊控制代碼可同時存在。\n4. `encode(...)` 實體化位元組（`image.encode`），不會變更影像尺寸。\n\n失敗轉換：\n\n- 格式偵測/解碼失敗會拒絕 parse promise。\n- 編碼失敗會拒絕 encode promise。\n- 無效的格式 ID 會拒絕 encode promise。\n\n### HTML 生命週期\n\n1. `htmlToMarkdown(html, options)` 排程一個阻塞式轉換任務。\n2. 除非另行指定，否則使用預設選項（`cleanContent=false`、`skipImages=false`）執行轉換。\n3. 回傳 Markdown 字串或拒絕。\n\n失敗轉換：\n\n- 轉換器失敗回傳被拒絕的 promise（`Conversion error: ...`）。\n\n### 剪貼簿生命週期\n\n`copyToClipboard(text)` 刻意採用盡力模式和多路徑：\n\n1. 若為 TTY：嘗試 OSC 52 寫入（base64 負載）。\n2. 當設定了 `TERMUX_VERSION` 時嘗試 Termux 命令。\n3. 嘗試原生 `arboard` 文字複製。\n4. 在 TS 層吞噬錯誤。\n\n`readImageFromClipboard()` 在不同階段的嚴格程度有所不同：\n\n1. TS 對不支援的執行環境（Termux/無圖形界面 Linux）硬性閘控為 `null`。\n2. Rust `arboard` 讀取僅在 TS 允許時執行。\n3. `ContentNotAvailable` 對應到 `null`。\n4. 其他 Rust 錯誤會拒絕。\n\n### 工作效能分析生命週期\n\n1. 無需明確啟動：當任務輔助程式執行時，效能分析始終開啟。\n2. 每個受檢測的任務範圍在守衛銷毀時記錄一個樣本。\n3. 緩衝區容量達上限後，樣本會覆寫最舊的條目。\n4. `getWorkProfile(lastSeconds)` 讀取時間視窗並衍生折疊/摘要/SVG 產物。\n\n失敗轉換：\n\n- SVG 生成失敗為軟性失敗（`svg: null`），折疊資料和摘要仍會回傳。\n- 空的樣本視窗回傳空的折疊資料和 `svg: null`，而非錯誤。\n\n## 不支援的操作與錯誤傳播\n\n### 影像\n\n- 不支援的解碼輸入或損壞的位元組：嚴格失敗（promise 拒絕）。\n- 不支援的編碼格式 ID：嚴格失敗。\n- TS 包裝器中沒有盡力模式的備援路徑。\n\n### HTML\n\n- 轉換錯誤為嚴格失敗（拒絕）。\n- 省略選項為盡力模式的預設值設定，而非失敗。\n\n### 剪貼簿\n\n- 文字複製在 TS 層為盡力模式：操作失敗會被抑制。\n- 影像讀取區分「無影像」（`null`）和操作失敗（拒絕）。\n- Termux/無圖形界面 Linux 被視為影像讀取的不支援環境（`null`）。\n\n### 工作效能分析\n\n- 函式呼叫本身的擷取為嚴格模式，但產物生成為部分盡力模式（`svg` 可為 null）。\n- 緩衝區截斷為預期行為（環形緩衝區），而非資料遺失的錯誤。\n\n## 平台注意事項\n\n- **剪貼簿文字**：OSC 52 取決於終端機支援；原生剪貼簿存取取決於桌面環境/工作階段。\n- **剪貼簿影像讀取**：在 Termux 和沒有顯示伺服器的 Linux 上，於 TS 層被封鎖。\n",
	"zh-tw/natives/natives-rust-task-cancellation.md": "---\ntitle: 原生 Rust 任務執行與取消\ndescription: Rust 非同步任務執行模型，具備協作式取消與清理語意。\nsidebar:\n  order: 5\n  label: 任務取消\ni18n:\n  sourceHash: 0fbf45c6d463\n  translator: machine\n---\n\n# 原生 Rust 任務執行與取消（`pi-natives`）\n\n本文件說明 `crates/pi-natives` 如何排程原生工作，以及取消操作如何從 JS 選項（`timeoutMs`、`AbortSignal`）流向 Rust 執行層。\n\n## 實作檔案\n\n- `crates/pi-natives/src/task.rs`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/fd.rs`\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/html.rs`\n- `crates/pi-natives/src/image.rs`\n- `crates/pi-natives/src/clipboard.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/ps.rs`\n\n## 核心原語（`task.rs`）\n\n`task.rs` 定義了三個核心元件：\n\n1. `task::blocking(tag, cancel_token, work)`\n   - 封裝 `napi::AsyncTask` / `Task`。\n   - `compute()` 在 libuv 工作執行緒上執行（用於 CPU 密集型或阻塞式/同步系統呼叫）。\n   - 回傳 JS `Promise<T>`。\n\n2. `task::future(env, tag, work)`\n   - 封裝 `env.spawn_future(...)`。\n   - 在 Tokio 執行時期上執行非同步工作。\n   - 回傳 `PromiseRaw<'env, T>`。\n\n3. `CancelToken` / `AbortToken` / `AbortReason`\n   - `CancelToken::new(timeout_ms, signal)` 結合截止時間與可選的 `AbortSignal`。\n   - `CancelToken::heartbeat()` 為阻塞迴圈提供協作式取消機制。\n   - `CancelToken::wait()` 為非同步取消等待（`Signal` / `Timeout` / `User` Ctrl-C）。\n   - `AbortToken` 允許外部程式碼請求中止（`abort(reason)`）。\n\n## `blocking` 與 `future`：執行模型與選擇依據\n\n### 使用 `task::blocking`\n\n當工作為 CPU 密集型或本質上屬於同步/阻塞時使用：\n\n- 正規表達式/檔案掃描（`grep`、`glob`、`fuzzy_find`）\n- 同步 PTY 迴圈內部（透過 `spawn_blocking` 呼叫的 `run_pty_sync`）\n- 剪貼簿/圖片/html 轉換\n\n行為：\n\n- 工作閉包接收一個已複製的 `CancelToken`。\n- 只有在程式碼呼叫 `ct.heartbeat()?` 時才會觀察到取消。\n- 閉包回傳 `Err(...)` 會導致 JS promise 被拒絕。\n\n### 使用 `task::future`\n\n當工作必須 `await` 非同步操作時使用：\n\n- shell 工作階段協調（`shell.run`、`executeShell`）\n- 使用 `tokio::select!` 在完成與取消之間進行競速\n\n行為：\n\n- Future 可在正常完成與 `ct.wait()` 之間進行競速。\n- 在取消路徑上，非同步實作通常會將取消傳播至內部子系統（例如 `tokio_util::CancellationToken`），並可選擇在寬限逾時後強制中止。\n\n## JS API ↔ Rust 匯出對應（任務/取消相關）\n\n| JS 端 API | Rust 匯出（`#[napi]`） | 排程器 | 取消接線 |\n|---|---|---|---|\n| `grep(options, onMatch?)` | `grep` | `task::blocking(\"grep\", ct, ...)` | `CancelToken::new(options.timeoutMs, options.signal)` + `ct.heartbeat()` |\n| `glob(options, onMatch?)` | `glob` | `task::blocking(\"glob\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` 於過濾迴圈中 |\n| `fuzzyFind(options)` | `fuzzy_find` | `task::blocking(\"fuzzy_find\", ct, ...)` | `CancelToken::new(...)` + `ct.heartbeat()` 於評分迴圈中 |\n| `shell.run(options, onChunk?)` | `Shell::run` | `task::future(env, \"shell.run\", ...)` | `ct.wait()` 與執行任務競速；橋接至 Tokio `CancellationToken` |\n| `executeShell(options, onChunk?)` | `execute_shell` | `task::future(env, \"shell.execute\", ...)` | 同上 |\n| `pty.start(options, onChunk?)` | `PtySession::start` | `task::future(env, \"pty.start\", ...)` + 內部 `spawn_blocking` | `CancelToken` 在同步 PTY 迴圈中透過 `heartbeat()` 檢查 |\n| `htmlToMarkdown(html, options?)` | `html_to_markdown` | `task::blocking(\"html_to_markdown\", (), ...)` | 無（`()` token） |\n| `PhotonImage.parse/encode/resize` | `PhotonImage::{parse,encode,resize}` | `task::blocking(...)` | 無（`()` token） |\n| `copyToClipboard/readImageFromClipboard` | `copy_to_clipboard` / `read_image_from_clipboard` | `task::blocking(...)` | 無（`()` token） |\n\n`text.rs` 與 `ps.rs` 目前未使用 `task::blocking`/`task::future`，因此不參與此取消路徑。\n\n## 取消生命週期與狀態轉換\n\n### `CancelToken` 生命週期\n\n`CancelToken` 為協作式且具有狀態：\n\n```text\nCreated（已建立）\n  ├─ 無 signal + 無 timeout  -> 被動 token（除非從外部設置，否則永不中止）\n  ├─ 已註冊 signal            -> 等待 AbortSignal 回呼\n  └─ 已設定截止時間            -> 逾時檢查變為活躍\n\nRunning（執行中）\n  ├─ heartbeat()/wait() 偵測到 signal   -> AbortReason::Signal\n  ├─ heartbeat()/wait() 偵測到截止時間  -> AbortReason::Timeout\n  ├─ wait() 偵測到 Ctrl-C               -> AbortReason::User\n  └─ 無中止                             -> 繼續\n\nAborted（已中止，終態）\n  └─ 第一個中止原因優先（原子旗標 + 通知器）\n```\n\n### 啟動前與執行中的取消\n\n- **啟動前 / 首次取消檢查前**：\n  - 在 `ct.wait()` 上競速的 `task::future` 使用者，一旦進入 `select!` 即可立即解析取消。\n  - `task::blocking` 使用者只有在閉包程式碼到達 `heartbeat()` 時才會觀察到取消。若閉包未提前呼叫 heartbeat，取消將被延遲。\n\n- **執行中途**：\n  - `blocking`：下一次 `heartbeat()` 回傳 `Err(\"Aborted: ...\")`。\n  - `future`：`ct.wait()` 分支在 `select!` 中勝出，隨後程式碼取消下屬非同步機制（對於 shell：取消 Tokio token，等待最多 2 秒，然後中止任務）。\n\n## 長時間執行迴圈的 Heartbeat 要求\n\n`heartbeat()` 必須以可預測的頻率在具有無界或大型工作集的迴圈中執行。\n\n已觀察到的模式：\n\n- `glob::filter_entries`：在過濾/比對前檢查每個條目。\n- `fd::score_entries`：檢查每個掃描的候選項目。\n- `grep_sync`：在重度搜尋階段前進行明確的取消檢查，以及接收 token 的 fs-cache 呼叫。\n- `run_pty_sync`：每次迴圈週期進行檢查（約 16ms 休眠頻率），並在取消時終止子行程。\n\n實用規則：對外部大小輸入的任何迴圈，在沒有 heartbeat 的情況下不應超過短暫的有界間隔。\n\n## 失敗行為與錯誤傳播至 JS\n\n### 阻塞任務\n\n錯誤路徑：\n\n1. 閉包回傳 `Err(napi::Error)`（包含 `heartbeat()` 中止）。\n2. `Task::compute()` 回傳 `Err`。\n3. `AsyncTask` 拒絕 JS promise。\n\n典型錯誤字串：\n\n- `Aborted: Timeout`\n- `Aborted: Signal`\n- 領域錯誤（`Failed to decode image: ...`、`Conversion error: ...` 等）\n\n### Future 任務\n\n錯誤路徑：\n\n1. 非同步主體回傳 `Err(napi::Error)` 或 join 失敗被映射（`... task failed: {err}`）。\n2. `task::future` 生成的 promise 被拒絕。\n3. 某些 API 刻意回傳結構化的取消結果而非拒絕（`ShellRunResult`/`ShellExecuteResult`，含 `cancelled`/`timed_out` 旗標與 `exit_code: None`）。\n\n### 取消報告的分類\n\n- **以錯誤形式中止**：大多數使用 `heartbeat()?` 的阻塞匯出。\n- **以型別化結果中止**：shell/pty 風格的命令 API，在結果結構中模型化取消。\n\n每個 API 選擇一種模型並明確記錄。\n\n## 常見陷阱\n\n1. **阻塞迴圈中缺少 heartbeat**\n   - 症狀：逾時/signal 看似被忽略，直到迴圈結束才生效。\n   - 修正：在迴圈頂部以及每個昂貴的逐項步驟前加入 `ct.heartbeat()?`。\n\n2. **無法取消的長段程式碼**\n   - 症狀：取消延遲在單一大型呼叫期間飆升（解碼、排序、壓縮等）。\n   - 修正：將工作分割為具有 heartbeat 邊界的區塊；若無法實現，則記錄延遲情況。\n\n3. **阻塞非同步執行器**\n   - 症狀：同步密集型程式碼直接在 future 中執行時，非同步 API 停滯。\n   - 修正：將 CPU/同步區塊移至 `task::blocking` 或 `tokio::task::spawn_blocking`。\n\n4. **不一致的取消語意**\n   - 症狀：某個 API 在取消時拒絕，另一個以旗標方式解析，令呼叫方困惑。\n   - 修正：按領域統一標準，並保持封裝文件的一致性。\n\n5. **在巢狀非同步任務中忘記取消橋接**\n   - 症狀：外部 token 已取消，但內部讀取器/子行程任務仍持續執行。\n   - 修正：將取消橋接至內部 token/signal，並強制執行寬限逾時 + 強制中止備援機制。\n\n## 新取消匯出的檢查清單\n\n1. 正確分類工作：\n   - CPU 密集型或同步阻塞 -> `task::blocking`\n   - 非同步 I/O / `await` 協調 -> `task::future`\n\n2. 在需要時公開取消輸入：\n   - 在 `#[napi(object)]` 選項中加入 `timeoutMs` 與 `signal`\n   - 建立 `let ct = task::CancelToken::new(timeout_ms, signal);`\n\n3. 在所有層級中接線取消：\n   - 阻塞迴圈：以穩定間隔呼叫 `ct.heartbeat()?`\n   - 非同步協調：與 `ct.wait()` 競速並取消子任務/token\n\n4. 決定取消合約：\n   - 以中止錯誤拒絕 promise，或\n   - 解析型別化的 `{ cancelled, timedOut, ... }`\n   - 對 API 系列保持此合約的一致性\n\n5. 以上下文傳播失敗：\n   - 透過 `Error::from_reason(format!(\"...: {err}\"))` 映射錯誤\n   - 加入階段專屬前綴（`spawn`、`decode`、`wait` 等）\n\n6. 處理啟動前與執行中途的取消：\n   - 取消檢查/等待必須在昂貴的主體執行前以及長時間執行期間發生\n\n7. 驗證無執行器誤用：\n   - 不得在非同步 future 內部直接執行長時間同步工作，必須使用 `spawn_blocking`/阻塞任務封裝器\n",
	"zh-tw/natives/natives-shell-pty-process.md": "---\ntitle: 原生層 Shell、PTY、Process 與 Key 內部機制\ndescription: 原生層中的 Shell 執行、PTY 管理、程序生命週期與按鍵事件處理。\nsidebar:\n  order: 4\n  label: Shell、PTY 與 process\ni18n:\n  sourceHash: 00ea95614c6a\n  translator: machine\n---\n\n# 原生層 Shell、PTY、Process 與 Key 內部機制\n\n本文件涵蓋 `@f5-sales-demo/pi-natives` 中的**執行/程序/終端基礎元件**：`shell`、`pty`、`ps` 和 `keys`，使用 `docs/natives-architecture.md` 中的架構術語。\n\n## 實作檔案\n\n- `crates/pi-natives/src/shell.rs`\n- `crates/pi-natives/src/shell/windows.rs`（僅限 Windows）\n- `crates/pi-natives/src/pty.rs`\n- `crates/pi-natives/src/ps.rs`\n- `crates/pi-natives/src/keys.rs`\n- `crates/pi-natives/src/task.rs`（shell/pty 使用的共用取消行為）\n- `packages/natives/src/shell/index.ts`\n- `packages/natives/src/shell/types.ts`\n- `packages/natives/src/pty/index.ts`\n- `packages/natives/src/pty/types.ts`\n- `packages/natives/src/ps/index.ts`\n- `packages/natives/src/ps/types.ts`\n- `packages/natives/src/keys/index.ts`\n- `packages/natives/src/keys/types.ts`\n- `packages/natives/src/bindings.ts`\n\n## 層級職責\n\n- **TS 包裝/API 層**（`packages/natives/src/*`）：型別化入口點、取消介面（`timeoutMs`、`AbortSignal`）及 JS 使用便利性。\n- **Rust N-API 模組層**（`crates/pi-natives/src/*`）：shell/PTY 程序執行、程序樹遍歷/終止，以及按鍵序列解析。\n- **驗證閘道**（`native.ts`，架構層級）：確保所需的匯出項目（`Shell`、`executeShell`、`PtySession`、`killTree`、`listDescendants`、key 輔助函式）在包裝器使用前已存在。\n\n## Shell 子系統（`shell`）\n\n### API 模型\n\n提供兩種執行模式：\n\n1. **一次性執行**，透過 `executeShell(options, onChunk?)`。\n2. **持久性工作階段**，透過 `new Shell(options?)` 然後重複呼叫 `shell.run(...)`。\n\n兩者都透過執行緒安全的回呼函式串流輸出，並回傳 `{ exitCode?, cancelled, timedOut }`。\n\n### 工作階段建立與環境模型\n\nRust 建立 `brush_core::Shell` 時使用：\n\n- 非互動模式，\n- `do_not_inherit_env: true`，\n- 從主機環境明確重建環境變數，\n- 對 shell 敏感變數的跳過清單（`PS1`、`PWD`、`SHLVL`、bash 函式匯出等）。\n\n工作階段環境行為：\n\n- `ShellOptions.sessionEnv` 在工作階段建立時套用一次。\n- `ShellRunOptions.env` 是命令範疇（`EnvironmentScope::Command`），每次執行後會被彈出。\n- `PATH` 在 Windows 上以不區分大小寫的去重方式進行特殊合併。\n\nWindows 專屬路徑擴充（`shell/windows.rs`）：偵測到的 Git-for-Windows 路徑（`cmd`、`bin`、`usr/bin`）會在存在且尚未包含時附加。\n\n### 執行時期生命週期與狀態轉換\n\n持久性 shell（`Shell.run`）使用以下狀態機：\n\n- **閒置/未初始化**：`session: None`。\n- **執行中**：第一次 `run()` 延遲建立工作階段，儲存 `current_abort` 令牌，執行命令。\n- **完成 + 保活**：如果執行控制流為 `Normal`，`current_abort` 被清除且工作階段被重複使用。\n- **完成 + 拆除**：如果控制流與迴圈/腳本/shell 退出相關（`BreakLoop`、`ContinueLoop`、`ReturnFromFunctionOrScript`、`ExitShell`），工作階段被丟棄（`session: None`）。\n- **已取消/已逾時**：執行任務被取消，寬限等待（2 秒），然後強制中止；工作階段被丟棄。\n- **錯誤**：工作階段被丟棄。\n\n一次性 shell（`executeShell`）每次呼叫總是建立並丟棄一個新的工作階段。\n\n### 串流/輸出行為\n\n- 標準輸出/標準錯誤被路由到共用管道並同時讀取。\n- 讀取器以增量方式解碼 UTF-8；無效的位元組序列會發出 `U+FFFD` 替換字元區塊。\n- 程序完成後，輸出排空有閒置/最大保護（`250ms` 閒置，`2s` 最大），以避免因背景工作保持描述子開啟而卡住。\n\n### 取消、逾時與背景工作\n\n- `CancelToken` 由 `timeoutMs` 和可選的 `AbortSignal` 構建。\n- 在取消/逾時時，shell 取消令牌被觸發，然後任務獲得 2 秒寬限視窗後強制中止。\n- 如果發生取消，背景工作會使用 brush 工作中繼資料被終止（`TERM`，然後延遲 `KILL`）。\n\n`Shell.abort()` 行為：\n\n- 僅中止該 `Shell` 實例當前正在執行的命令，\n- 當沒有命令正在執行時為無操作的成功回傳。\n\n### 失敗行為\n\n常見的浮現錯誤包括：\n\n- 工作階段初始化失敗（`Failed to initialize shell`），\n- cwd 錯誤（`Failed to set cwd`），\n- 環境變數設定/彈出失敗，\n- 快照來源失敗，\n- 管道建立/複製失敗，\n- 執行失敗（`Shell execution failed: ...`），\n- 任務包裝器失敗（`Shell execution task failed: ...`）。\n\n結果層級的取消旗標：\n\n- 逾時 -> `exitCode: undefined`，`timedOut: true`。\n- 中止訊號 -> `exitCode: undefined`，`cancelled: true`。\n\n## PTY 子系統（`pty`）\n\n### API 模型\n\n`new PtySession()` 公開：\n\n- `start(options, onChunk?) -> Promise<{ exitCode?, cancelled, timedOut }>`\n- `write(data)`\n- `resize(cols, rows)`\n- `kill()`\n\n### 執行時期生命週期與狀態轉換\n\n`PtySession` 狀態機：\n\n- **閒置**：`core: None`。\n- **已預留**：`start()` 在非同步工作開始前同步安裝控制通道（`core: Some`），使 `write/resize/kill` 立即可用。\n- **執行中**：阻塞式 PTY 迴圈處理子程序狀態、讀取器事件、取消心跳及控制訊息。\n- **終端已關閉**：子程序退出 + 讀取器完成。\n- **已完成**：`core` 在 start 任務完成後（包括成功或錯誤路徑）總是被重設為 `None`。\n\n並行保護：\n\n- 在已經執行時再次啟動會回傳 `PTY session already running`。\n\n### 產生/附加/寫入/讀取/終止模式\n\n- PTY 透過 `portable_pty::native_pty_system().openpty(...)` 開啟。\n- 命令目前以 `sh -lc <command>` 執行，並支援可選的 `cwd` 和環境變數覆蓋。\n- `write()` 將原始位元組傳送到 PTY 標準輸入。\n- `resize()` 限制維度（`cols 20..400`、`rows 5..200`）並呼叫主端調整大小。\n- `kill()` 將執行標記為已取消並終止子程序。\n\n輸出路徑：\n\n- 專用讀取執行緒讀取主端串流，\n- 增量式 UTF-8 解碼，對無效位元組使用 `U+FFFD` 替換，\n- 區塊透過 N-API 執行緒安全回呼函式轉發。\n\n### 取消與逾時語意\n\n- `timeoutMs` 和 `AbortSignal` 饋入 `CancelToken`。\n- 迴圈定期呼叫 `ct.heartbeat()`；中止會觸發子程序終止。\n- 逾時分類是基於字串的（心跳錯誤中的 `\"Timeout\"` 子字串）。\n\n### 失敗行為\n\n錯誤介面包括：\n\n- PTY 配置/開啟失敗，\n- PTY 產生失敗，\n- 寫入器/讀取器取得失敗，\n- 子程序狀態/等待失敗，\n- 鎖中毒，\n- 控制通道斷線（`PTY session is no longer available`）。\n\n非執行時的控制呼叫失敗：\n\n- `write/resize/kill` 回傳 `PTY session is not running`。\n\n## 程序樹子系統（`ps`）\n\n### API 模型\n\n- `killTree(pid, signal) -> number`\n- `listDescendants(pid) -> number[]`\n\nTS 包裝器也透過 `setNativeKillTree(native.killTree)` 將原生 kill-tree 整合註冊到共用工具中。\n\n### 平台特定實作\n\n- **Linux**：遞迴讀取 `/proc/<pid>/task/<pid>/children`。\n- **macOS**：使用 `libproc` 的 `proc_listchildpids`。\n- **Windows**：使用 `CreateToolhelp32Snapshot` 快照程序表，建立父子對應表，以 `OpenProcess(PROCESS_TERMINATE)` + `TerminateProcess` 終止。\n\n### Kill-tree 行為\n\n- 子程序以遞迴方式收集。\n- 終止順序為由下而上（最深的子程序優先），以減少孤兒程序重新歸屬。\n- 根 pid 最後被終止。\n- 回傳值為成功終止的數量。\n\n訊號行為：\n\n- POSIX：提供的 `signal` 傳遞給 `kill`。\n- Windows：`signal` 被忽略；終止為無條件程序終止。\n\n### 失敗行為\n\n此模組在 API 介面上有意設計為不拋出例外：\n\n- 缺少/無法存取的程序樹分支會被跳過，\n- 每個 pid 的終止失敗計為不成功（非錯誤），\n- 查詢未命中通常從 `listDescendants` 產生 `[]`，從 `killTree` 產生 `0`。\n\n## 按鍵解析子系統（`keys`）\n\n### API 模型\n\n公開的輔助函式：\n\n- `parseKey(data, kittyProtocolActive)`\n- `matchesKey(data, keyId, kittyProtocolActive)`\n- `parseKittySequence(data)`\n- `matchesKittySequence(data, expectedCodepoint, expectedModifier)`\n- `matchesLegacySequence(data, keyName)`\n\n### 解析模型\n\n解析器結合：\n\n- 直接的單位元組對應（`enter`、`tab`、`ctrl+<letter>`、可列印 ASCII），\n- O(1) 傳統跳脫序列查詢（PHF 映射），\n- xterm `modifyOtherKeys` 解析，\n- Kitty 協定解析（`CSI u`、`CSI ~`、`CSI 1;...<letter>`），\n- 正規化為按鍵 ID（`ctrl+c`、`shift+tab`、`pageUp`、`f5` 等）。\n\n修飾鍵處理：\n\n- 按鍵比對時僅比較 shift/alt/ctrl 位元，\n- 鎖定位元在比較前會被遮罩掉。\n\n佈局行為：\n\n- 基本佈局回退是有意受限的，使重新對應的佈局不會對 ASCII 字母/符號產生誤匹配。\n\n### 失敗行為\n\n- 無法辨識或無效的序列從解析函式產生 `null`。\n- 比對函式在解析失敗或不匹配時回傳 `false`。\n- 對於格式錯誤的按鍵輸入不會拋出錯誤。\n\n## JS 包裝器 API ↔ Rust 匯出對應\n\n### Shell + PTY + Process\n\n| TS 包裝器 API | Rust N-API 匯出 | 備註 |\n|---|---|---|\n| `executeShell(options, onChunk?)` | `executeShell` (`execute_shell`) | 一次性 shell 執行 |\n| `new Shell(options?)` | `Shell` class | 持久性 shell 工作階段 |\n| `shell.run(options, onChunk?)` | `Shell::run` | 在保活控制流上重複使用工作階段 |\n| `shell.abort()` | `Shell::abort` | 中止該 shell 實例的活躍執行 |\n| `new PtySession()` | `PtySession` class | 有狀態的 PTY 工作階段 |\n| `pty.start(options, onChunk?)` | `PtySession::start` | 互動式 PTY 執行 |\n| `pty.write(data)` | `PtySession::write` | 原始標準輸入透傳 |\n| `pty.resize(cols, rows)` | `PtySession::resize` | 受限的終端維度 |\n| `pty.kill()` | `PtySession::kill` | 強制終止活躍的 PTY 子程序 |\n| `killTree(pid, signal)` | `killTree` (`kill_tree`) | 子程序優先的程序樹終止 |\n| `listDescendants(pid)` | `listDescendants` (`list_descendants`) | 遞迴子程序列表 |\n\n### Keys\n\n| TS 包裝器 API | Rust N-API 匯出 | 備註 |\n|---|---|---|\n| `matchesKittySequence(data, cp, mod)` | `matchesKittySequence` (`matches_kitty_sequence`) | Kitty 碼點+修飾鍵比對 |\n| `parseKey(data, kittyProtocolActive)` | `parseKey` (`parse_key`) | 正規化按鍵 ID 解析器 |\n| `matchesLegacySequence(data, keyName)` | `matchesLegacySequence` (`matches_legacy_sequence`) | 精確傳統序列映射檢查 |\n| `parseKittySequence(data)` | `parseKittySequence` (`parse_kitty_sequence`) | 結構化 Kitty 解析結果 |\n| `matchesKey(data, keyId, kittyProtocolActive)` | `matchesKey` (`matches_key`) | 高階按鍵比對器 |\n\n## 已放棄的工作階段清理與最終化備註\n\n- **Shell 持久性工作階段**：如果執行被取消/逾時/錯誤/非保活控制流，Rust 會明確丟棄內部工作階段狀態。成功的正常執行會保留工作階段以供重複使用。\n- **PTY 工作階段**：`core` 在 `start()` 完成後總是被清除，包括失敗路徑。\n- 包裝器**未公開明確的 JS 終結器驅動終止契約**；清理主要繫結於執行完成/取消路徑。呼叫者應使用 `timeoutMs`、`AbortSignal`、`shell.abort()` 或 `pty.kill()` 進行確定性拆除。\n",
	"zh-tw/natives/natives-text-search-pipeline.md": "---\ntitle: 原生文字與搜尋管線\ndescription: 基於 grep、glob 和 ripgrep 的原生文字搜尋管線，包含檔案內容索引功能。\nsidebar:\n  order: 6\n  label: 文字與搜尋管線\ni18n:\n  sourceHash: 0e93462fdd12\n  translator: machine\n---\n\n# 原生文字/搜尋管線\n\n本文件對應 `@f5-sales-demo/pi-natives` 文字/搜尋介面（`grep`、`glob`、`text`、`highlight`），從 TypeScript 封裝到 Rust N-API 匯出，再回到 JS 結果物件的完整映射。\n\n術語遵循 `docs/natives-architecture.md`：\n\n- **封裝層（Wrapper）**：位於 `packages/natives/src/*` 中的 TS API\n- **Rust 模組層**：位於 `crates/pi-natives/src/*` 中的 N-API 匯出\n- **共享掃描快取**：由 `fs_cache` 支援的目錄條目快取，供探索/搜尋流程使用\n\n## 實作檔案\n\n- `packages/natives/src/grep/index.ts`\n- `packages/natives/src/grep/types.ts`\n- `packages/natives/src/glob/index.ts`\n- `packages/natives/src/glob/types.ts`\n- `packages/natives/src/text/index.ts`\n- `packages/natives/src/text/types.ts`\n- `packages/natives/src/highlight/index.ts`\n- `packages/natives/src/highlight/types.ts`\n- `crates/pi-natives/src/grep.rs`\n- `crates/pi-natives/src/glob.rs`\n- `crates/pi-natives/src/glob_util.rs`\n- `crates/pi-natives/src/fs_cache.rs`\n- `crates/pi-natives/src/text.rs`\n- `crates/pi-natives/src/highlight.rs`\n- `crates/pi-natives/src/fd.rs`\n\n## JS API ↔ Rust 匯出映射\n\n| JS 封裝 API | Rust 匯出（`#[napi]`，snake_case -> camelCase） | Rust 模組 |\n| --- | --- | --- |\n| `grep(options, onMatch?)` | `grep` | `grep.rs` |\n| `searchContent(content, options)` | `search` | `grep.rs` |\n| `hasMatch(content, pattern, options?)` | `hasMatch` | `grep.rs` |\n| `fuzzyFind(options)` | `fuzzyFind` | `fd.rs` |\n| `glob(options, onMatch?)` | `glob` | `glob.rs` |\n| `invalidateFsScanCache(path?)` | `invalidateFsScanCache` | `fs_cache.rs` |\n| `wrapTextWithAnsi(text, width)` | `wrapTextWithAnsi` | `text.rs` |\n| `truncateToWidth(text, maxWidth, ellipsis, pad)` | `truncateToWidth` | `text.rs` |\n| `sliceWithWidth(line, startCol, length, strict?)` | `sliceWithWidth` | `text.rs` |\n| `extractSegments(line, beforeEnd, afterStart, afterLen, strictAfter)` | `extractSegments` | `text.rs` |\n| `sanitizeText(text)` | `sanitizeText` | `text.rs` |\n| `visibleWidth(text)` | `visibleWidth` | `text.rs` |\n| `highlightCode(code, lang, colors)` | `highlightCode` | `highlight.rs` |\n| `supportsLanguage(lang)` | `supportsLanguage` | `highlight.rs` |\n| `getSupportedLanguages()` | `getSupportedLanguages` | `highlight.rs` |\n\n## 各子系統管線概覽\n\n## 1) 正規表達式搜尋（`grep`、`searchContent`、`hasMatch`）\n\n### 輸入/選項流程\n\n1. TS 封裝層將選項轉發至原生層：\n   - `grep/index.ts` 大部分選項保持不變傳入 `options`，並將回呼從 `(match) => void` 封裝為 napi 執行緒安全回呼格式 `(err, match)`。\n   - `searchContent` 和 `hasMatch` 直接傳入字串/`Uint8Array`。\n2. `grep.rs` 中的 Rust 選項結構體反序列化 camelCase 欄位（`ignoreCase`、`maxCount`、`contextBefore`、`contextAfter`、`maxColumns`、`timeoutMs`）。\n3. `grep` 從 `timeoutMs` + `AbortSignal` 建立 `CancelToken`，並在 `task::blocking(\"grep\", ...)` 內執行。\n\n### 執行分支\n\n- **記憶體內分支（純工具函式）**\n  - `search` → `search_sync` → 對提供的內容位元組執行 `run_search`。\n  - 無檔案系統掃描，不使用 `fs_cache`。\n- **單一檔案分支（依賴檔案系統）**\n  - `grep_sync` 解析路徑，檢查中繼資料確認為檔案，透過 ripgrep 匹配器串流處理每個檔案最多 `MAX_FILE_BYTES`（`4 MiB`）。\n- **目錄分支（依賴檔案系統）**\n  - 當 `cache: true` 時，透過 `fs_cache::get_or_scan` 進行可選的快取查詢。\n  - 當 `cache: false` 時，透過 `fs_cache::force_rescan` 進行全新掃描。\n  - 當快取時間超過 `empty_recheck_ms()` 時，可選的空結果重新檢查。\n  - 條目過濾：僅限檔案 + 可選 glob 過濾器（`glob_util`）+ 可選類型過濾器映射（`js`、`ts`、`rust` 等）。\n\n### 搜尋/收集語意\n\n- 正規表達式引擎：`grep_regex::RegexMatcherBuilder`，支援 `ignoreCase` 和 `multiline`。\n- 上下文解析：\n  - `contextBefore/contextAfter` 覆蓋舊版 `context`。\n  - 非內容模式會將上下文收集歸零。\n- 輸出模式：\n  - `content` => 每個匹配產生一個 `GrepMatch`。\n  - `count` 和 `filesWithMatches` 都映射為計數式條目（`lineNumber=0`、`line=\"\"`，設定 `matchCount`）。\n- 限制：\n  - 全域 `offset` 和 `maxCount` 跨檔案套用。\n  - 僅當 `maxCount` 未設定且 `offset == 0` 時使用平行路徑；否則使用循序路徑以維持確定性的全域偏移/限制語意。\n\n### 結果塑形回傳至 JS\n\n- Rust `SearchResult`/`GrepResult` 欄位透過 N-API 物件欄位轉換映射至 TS 型別。\n- 計數器在跨越 N-API 前會限縮至 `u32`。\n- 可選布林值在某些路徑中僅在為 true 時才包含（`limitReached`）。\n- 串流回呼接收每個已塑形的 `GrepMatch`（內容或計數條目）。\n\n### 失敗行為\n\n- `searchContent` 在正規表達式/搜尋失敗時回傳 `SearchResult.error`，而非拋出例外。\n- `grep` 在硬性錯誤時拒絕（無效路徑、無效 glob/正規表達式、取消逾時/中止）。\n- `hasMatch` 回傳 `Result<bool>`，在無效模式/UTF-8 解碼錯誤時拋出例外。\n- 多檔案掃描中的檔案開啟/搜尋錯誤會按檔案跳過；掃描繼續進行。\n\n### 格式錯誤的正規表達式處理\n\n`grep.rs` 在正規表達式編譯前會清理大括號：\n\n- 無效的重複式大括號會被跳脫（`{`/`}` -> `\\{`/`\\}`），當它們無法構成 `{N}`、`{N,}`、`{N,M}` 時。\n- 這可防止常見的文字範本片段（例如 `${platform}`）因格式錯誤的重複而失敗。\n- 其餘無效的正規表達式語法仍會回傳正規表達式錯誤。\n\n## 2) 檔案探索（`glob`）與模糊路徑搜尋（`fuzzyFind`）\n\n`glob` 和 `fuzzyFind` 共享 `fs_cache` 掃描；匹配邏輯不同。\n\n### `glob` 流程\n\n1. TS 封裝層（`glob/index.ts`）：\n   - `path.resolve(options.path)`。\n   - 預設值：`pattern=\"*\"`、`hidden=false`、`gitignore=true`、`recursive=true`。\n2. Rust `glob` 建構 `GlobConfig` 並透過 `glob_util::compile_glob` 編譯模式。\n3. 條目來源：\n   - `cache=true` => `get_or_scan` + 可選的過期空結果 `force_rescan`。\n   - `cache=false` => `force_rescan(..., store=false)`（僅全新掃描）。\n4. 過濾：\n   - 始終跳過 `.git`。\n   - 除非要求（`includeNodeModules` 或模式中提及 node_modules），否則跳過 `node_modules`。\n   - 套用 glob 匹配。\n   - 套用檔案類型過濾器；符號連結 `file/dir` 過濾器會解析目標中繼資料。\n5. 在截斷至 `maxResults` 前，可選按 mtime 降序排序（`sortByMtime`）。\n\n### `fuzzyFind` 流程（實作於 `fd.rs`）\n\n1. TS 封裝層從 `grep` 模組匯出，但 Rust 實作位於 `fd.rs`。\n2. 來自 `fs_cache` 的共享掃描來源，具有相同的快取/無快取分流和過期空結果重新檢查策略。\n3. 評分：\n   - 精確匹配 / 前綴匹配 / 包含 / 基於子序列的模糊評分\n   - 分隔符號/標點符號正規化的評分路徑\n   - 目錄加分和確定性的平分決策（`score desc`，然後 `path asc`）\n4. 符號連結條目從模糊結果中排除。\n\n### 失敗行為\n\n- 無效的 glob 模式 => 從 `glob_util::compile_glob` 回傳錯誤。\n- 搜尋根目錄必須是現有目錄（`resolve_search_path`），否則回傳錯誤。\n- 取消/逾時透過迴圈中的 `CancelToken::heartbeat()` 檢查以中止錯誤傳播。\n\n### 格式錯誤的 glob 處理\n\n`glob_util::build_glob_pattern` 具有容錯性：\n\n- 將 `\\` 正規化為 `/`。\n- 當 `recursive=true` 時，自動為簡單的遞迴模式加上 `**/` 前綴。\n- 編譯前自動關閉未平衡的 `{...` 交替群組。\n\n## 3) 共享掃描/快取生命週期（`fs_cache`）\n\n`fs_cache` 將掃描結果儲存為正規化的相對條目（`path`、`fileType`、可選 `mtime`），鍵值由以下組成：\n\n- 標準化搜尋根目錄\n- `include_hidden`\n- `use_gitignore`\n\n### 快取狀態轉換\n\n1. **未命中 / 停用**\n   - TTL 為 `0` 或鍵值不存在/已過期 -> 全新 `collect_entries`。\n2. **命中**\n   - 條目年齡 `< cache_ttl_ms()` -> 回傳快取條目 + `cache_age_ms`。\n3. **過期空結果重新檢查**（呼叫者在 `glob`/`grep`/`fd` 中的策略）\n   - 若查詢產生零匹配且 `cache_age_ms >= empty_recheck_ms()`，則強制一次重新掃描。\n4. **失效**\n   - `invalidateFsScanCache(path?)`：\n     - 無參數：清除所有鍵值\n     - 路徑參數：移除根目錄為該目標路徑前綴的鍵值\n\n### 過期結果的取捨\n\n- 快取優先考慮重複掃描的低延遲，而非即時一致性。\n- TTL 視窗可能回傳過期的正面/負面結果。\n- 空結果重新檢查以一次額外掃描的代價，減少較舊快取掃描的過期負面結果。\n- 明確的失效機制是檔案變更後預期的正確性鉤子。\n\n## 4) ANSI 文字工具（`text`）\n\n這些是純粹的記憶體內工具函式（無檔案系統掃描）。\n\n### 邊界與職責\n\n- **`text.rs` 擁有終端機儲存格語意**：\n  - ANSI 序列解析\n  - 字形感知的寬度和切片\n  - 換行/截斷/清理行為\n- **`grep.rs` 行截斷（`maxColumns`）是獨立的**：\n  - 對匹配行進行簡單的字元邊界截斷，附加 `...`\n  - 不保留 ANSI 狀態，也不感知終端機儲存格寬度\n\n### 關鍵行為\n\n- `wrapTextWithAnsi`：依可見寬度換行，將作用中的 SGR 代碼跨換行傳遞。\n- `truncateToWidth`：可見儲存格截斷，具有省略號策略（`Unicode`、`Ascii`、`Omit`），可選右側填充，以及當未變更時回傳原始 JS 字串的快速路徑。\n- `sliceWithWidth`：欄位切片，具有可選的嚴格寬度強制。\n- `extractSegments`：擷取覆蓋區域周圍的前/後區段，同時為 `after` 區段還原 ANSI 狀態。\n- `sanitizeText`：移除 ANSI 跳脫字元 + 控制字元，丟棄孤立代理對，透過移除 `\\r` 正規化 CR/LF。\n- `visibleWidth`：計算可見終端機儲存格數（製表符使用 Rust 實作中的固定 `TAB_WIDTH`）。\n\n### 失敗行為\n\n文字函式通常回傳確定性的轉換輸出；錯誤僅限於 JS 字串轉換邊界（N-API 參數轉換失敗）。\n\n## 5) 語法高亮（`highlight`）\n\n`highlight.rs` 是純轉換（無檔案系統、無快取）。\n\n### 流程\n\n1. 封裝層轉發 `code`、可選 `lang` 和 ANSI 色彩調色盤。\n2. Rust 透過以下方式解析語法：\n   - token/名稱查詢\n   - 副檔名查詢\n   - 別名表回退（`ts/tsx/js -> JavaScript` 等）\n   - 無法解析時回退至純文字語法\n3. 使用 syntect `ParseState` 和範疇堆疊解析每一行。\n4. 將範疇映射至 11 個語意色彩類別，並注入/重設 ANSI 色彩代碼。\n\n### 失敗行為\n\n- 逐行解析失敗不會導致呼叫失敗：該行會以未高亮的方式附加，處理繼續進行。\n- 未知/不支援的語言回退至純文字語法。\n\n## 純工具函式 vs 依賴檔案系統的流程\n\n| 流程 | 檔案系統存取 | 共享快取 | 備註 |\n| --- | --- | --- | --- |\n| `searchContent` / `hasMatch` | 否 | 否 | 僅對提供的位元組/字串進行正規表達式匹配 |\n| `text` 模組函式 | 否 | 否 | 僅 ANSI/寬度/清理 |\n| `highlight` 模組函式 | 否 | 否 | 僅語法 + ANSI 著色 |\n| `glob` | 是 | 可選 | 目錄掃描 + glob 過濾 |\n| `fuzzyFind` | 是 | 可選 | 目錄掃描 + 模糊評分 |\n| `grep`（檔案/目錄路徑） | 是 | 可選（目錄模式） | ripgrep 處理檔案，可選過濾器/回呼 |\n\n## 端到端生命週期摘要\n\n1. 呼叫者以型別化選項呼叫 TS 封裝層。\n2. 封裝層正規化預設值（特別是 `glob`）並轉發至 `native.*` 匯出。\n3. Rust 驗證/正規化選項並建構匹配器/搜尋設定。\n4. 對於檔案系統流程，條目會被掃描（快取命中/未命中/重新掃描），然後進行過濾/評分。\n5. 工作執行緒迴圈定期呼叫取消心跳；逾時/中止可終止執行。\n6. Rust 將輸出塑形為 N-API 物件（`lineNumber`、`matchCount`、`limitReached` 等）。\n7. TS 封裝層回傳型別化的 JS 物件（以及 `grep`/`glob` 的可選逐匹配回呼）。\n",
	"zh-tw/natives/porting-to-natives.md": "---\ntitle: 移植到 pi-natives (N-API) — 實戰筆記\ndescription: 將 Node.js child_process 和 shell 程式碼遷移到 Rust N-API 原生層的實戰筆記。\nsidebar:\n  order: 9\n  label: 移植到 pi-natives\ni18n:\n  sourceHash: 4f5150286535\n  translator: machine\n---\n\n# 移植到 pi-natives (N-API) — 實戰筆記\n\n這是一份將熱路徑移入 `crates/pi-natives` 並透過 JS 綁定進行串接的實用指南。本文件的存在是為了避免相同的錯誤重複發生。\n\n## 何時應該移植\n\n當以下任一條件成立時進行移植：\n\n- 熱路徑在渲染迴圈、密集的 UI 更新或大批量處理中執行。\n- JS 記憶體分配佔主導地位（字串反覆產生、正規表達式回溯、大型陣列）。\n- 你已經有 JS 基準版本，可以並排比較兩個版本的效能。\n- 工作是 CPU 密集型或阻塞式 I/O，可以在 libuv 執行緒池上執行。\n- 工作是非同步 I/O，可以在 Tokio 的執行時上執行（例如 shell 執行）。\n\n避免移植依賴 JS 專有狀態或動態匯入的程式碼。N-API 匯出應該是純粹的、資料輸入/資料輸出。長時間執行的工作應透過 `task::blocking`（CPU 密集型/阻塞式 I/O）或 `task::future`（非同步 I/O）並搭配取消機制進行處理。\n\n## 原生匯出的結構\n\n**Rust 端：**\n\n- 實作放在 `crates/pi-natives/src/<module>.rs`。如果新增模組，請在 `crates/pi-natives/src/lib.rs` 中註冊。\n- 使用 `#[napi]` 匯出；snake_case 的匯出會自動轉換為 camelCase。僅在真正的別名/非預設名稱時才使用明確的 `js_name`。對結構體使用 `#[napi(object)]`。\n- 對 CPU 密集型或阻塞式工作使用 `task::blocking(tag, cancel_token, work)`（參見 `crates/pi-natives/src/task.rs`）。對需要 Tokio 的非同步工作（例如 shell 會話）使用 `task::future(env, tag, work)`。當你公開 `timeoutMs` 或 `AbortSignal` 時傳入 `CancelToken`。\n\n**JS 端：**\n\n- `packages/natives/src/bindings.ts` 持有基礎 `NativeBindings` 介面。\n- `packages/natives/src/<module>/types.ts` 定義 TS 類型，並透過宣告合併擴充 `NativeBindings`。\n- `packages/natives/src/native.ts` 匯入每個 `<module>/types.ts` 檔案以啟用宣告。\n- `packages/natives/src/<module>/index.ts` 封裝來自 `packages/natives/src/native.ts` 的 `native` 綁定。\n- `packages/natives/src/native.ts` 載入附加模組，`validateNative` 強制驗證所需的匯出。\n- `packages/natives/src/index.ts` 重新匯出封裝器供 `packages/*` 中的呼叫者使用。\n\n## 移植檢查清單\n\n1. **新增 Rust 實作**\n\n- 將核心邏輯放在純 Rust 函式中。\n- 如果是新模組，將其新增到 `crates/pi-natives/src/lib.rs`。\n- 使用 `#[napi]` 匯出，保持預設的 snake_case -> camelCase 對應一致性。\n- 保持簽名使用擁有權類型且簡單：`String`、`Vec<String>`、`Uint8Array`，或對大型字串/位元組輸入使用 `Either<JsString, Uint8Array>`。\n- 對 CPU 密集型或阻塞式工作使用 `task::blocking`；對非同步工作使用 `task::future`。傳入 `CancelToken` 並在長迴圈內呼叫 `heartbeat()`。\n\n2. **串接 JS 綁定**\n\n- 在 `packages/natives/src/<module>/types.ts` 中新增類型和 `NativeBindings` 擴充。\n- 在 `packages/natives/src/native.ts` 中匯入 `./<module>/types` 以觸發宣告合併。\n- 在 `packages/natives/src/<module>/index.ts` 中新增呼叫 `native` 的封裝器。\n- 從 `packages/natives/src/index.ts` 重新匯出。\n\n3. **更新原生驗證**\n\n- 在 `validateNative`（`packages/natives/src/native.ts`）中新增 `checkFn(\"newExport\")`。\n\n4. **新增基準測試**\n\n- 將基準測試放在所屬套件旁邊（`packages/tui/bench`、`packages/natives/bench` 或 `packages/coding-agent/bench`）。\n- 在同一次執行中包含 JS 基準版本和原生版本。\n- 使用 `Bun.nanoseconds()` 和固定的迭代次數。\n- 保持基準測試輸入小且切合實際（熱路徑中實際觀察到的資料）。\n\n5. **建置原生二進位檔**\n\n- `bun --cwd=packages/natives run build`\n- 使用 `bun --cwd=packages/natives run build`，如果你想在測試時查看載入器診斷資訊，請設定 `PI_DEV=1`。\n\n6. **執行基準測試**\n\n- `bun run packages/<pkg>/bench/<bench>.ts`（或 `bun --cwd=packages/natives run bench`）\n\n7. **決定使用方式**\n\n- 如果原生版本較慢，**保留 JS** 並讓原生匯出閒置。\n- 如果原生版本較快，將呼叫端切換到原生封裝器。\n\n## 痛點及如何避免\n\n### 1) 過時的 `pi_natives.node` 阻止新匯出\n\n載入器優先使用 `packages/natives/native` 中帶平台標籤的二進位檔（`pi_natives.<platform>-<arch>.node`）。`PI_DEV=1` 現在僅啟用載入器診斷資訊；它不再切換到單獨的開發附加模組檔名。還有一個備用的 `pi_natives.node`。編譯後的二進位檔會解壓到 `~/.xcsh/natives/<version>/pi_natives.<platform>-<arch>.node`。如果其中任何一個是過時的，匯出將不會更新。\n\n**修復方式：** 在重新建置前移除過時的檔案。\n\n```bash\nrm packages/natives/native/pi_natives.linux-x64.node\nrm packages/natives/native/pi_natives.node\nbun --cwd=packages/natives run build\n```\n\n如果你正在執行編譯後的二進位檔，請刪除快取的附加模組目錄：\n\n```bash\nrm -rf ~/.xcsh/natives/<version>\n```\n\n然後驗證匯出是否存在於二進位檔中：\n\n```bash\nbun -e 'const tag = `${process.platform}-${process.arch}`; const mod = require(`./packages/natives/native/pi_natives.${tag}.node`); console.log(Object.keys(mod).includes(\"newExport\"));'\n```\n\n### 2) 來自 `validateNative` 的「缺少匯出」錯誤\n\n這是**好事** — 它防止了靜默的不匹配。當你看到這個：\n\n```\nNative addon missing exports ... Missing: visibleWidth\n```\n\n這表示你的二進位檔是過時的、Rust 匯出名稱（或使用時的明確別名）與 JS 名稱不匹配，或者匯出根本沒有被編譯進去。修復建置和命名不匹配，不要削弱驗證。\n\n### 3) Rust 簽名不匹配\n\n保持簡單且使用擁有權類型。`String`、`Vec<String>` 和 `Uint8Array` 都可以。避免在公開匯出中使用引用如 `&str`。如果需要結構化資料，將其封裝在 `#[napi(object)]` 結構體中。\n\n### 4) 基準測試的常見錯誤\n\n- 不要比較不同的輸入或分配。\n- 保持 JS 和原生版本使用相同的輸入陣列。\n- 在同一個基準測試檔案中執行兩者以避免偏差。\n\n## 基準測試範本\n\n```ts\nconst ITERATIONS = 2000;\n\nfunction bench(name: string, fn: () => void): number {\n const start = Bun.nanoseconds();\n for (let i = 0; i < ITERATIONS; i++) fn();\n const elapsed = (Bun.nanoseconds() - start) / 1e6;\n console.log(`${name}: ${elapsed.toFixed(2)}ms total (${(elapsed / ITERATIONS).toFixed(6)}ms/op)`);\n return elapsed;\n}\n\nbench(\"feature/js\", () => {\n jsImpl(sample);\n});\n\nbench(\"feature/native\", () => {\n nativeImpl(sample);\n});\n```\n\n## 驗證檢查清單\n\n- `validateNative` 通過（無缺少的匯出）。\n- `NativeBindings` 已在 `packages/natives/src/<module>/types.ts` 中擴充，且封裝器已在 `packages/natives/src/index.ts` 中重新匯出。\n- `Object.keys(require(...))` 包含你的新匯出。\n- 基準測試數據已記錄在 PR/筆記中。\n- **僅在**原生版本更快或相等時才更新呼叫端。\n\n## 經驗法則\n\n- 如果原生版本較慢，**不要切換**。保留匯出供未來使用，但 TUI 應該繼續使用較快的路徑。\n- 如果原生版本較快，切換呼叫端並保留基準測試以捕捉效能回歸。\n",
	"zh-tw/providers/models.md": "---\ntitle: 模型與提供者配置\ndescription: 透過 models.yml 進行模型註冊表與提供者配置，包含路由、備援及定價功能。\nsidebar:\n  order: 1\n  label: 模型與提供者\ni18n:\n  sourceHash: 8053df967ff6\n  translator: machine\n---\n\n# 模型與提供者配置（`models.yml`）\n\n本文件描述 coding-agent 目前如何載入模型、套用覆寫設定、解析憑證，以及在執行階段選擇模型。\n\n## 控制模型行為的要素\n\n主要實作檔案：\n\n- `src/config/model-registry.ts` — 載入內建 + 自訂模型、提供者覆寫、執行階段探索、認證整合\n- `src/config/model-resolver.ts` — 解析模型模式並選擇 initial/smol/slow 模型\n- `src/config/settings-schema.ts` — 模型相關設定（`modelRoles`、提供者傳輸偏好）\n- `src/session/auth-storage.ts` — API 金鑰 + OAuth 解析順序\n- `packages/ai/src/models.ts` 和 `packages/ai/src/types.ts` — 內建提供者/模型及 `Model`/`compat` 類型\n\n## 配置檔位置與舊版行為\n\n預設配置路徑：\n\n- `~/.xcsh/agent/models.yml`\n\n仍保留的舊版行為：\n\n- 若 `models.yml` 不存在但同一位置有 `models.json`，會自動遷移至 `models.yml`。\n- 當以程式方式傳遞給 `ModelRegistry` 時，仍支援明確的 `.json` / `.jsonc` 配置路徑。\n\n## `models.yml` 結構\n\n```yaml\nconfigVersion: 1  # optional — written by auto-config, used for migration detection\nproviders:\n  <provider-id>:\n    # provider-level config\nequivalence:\n  overrides:\n    <provider-id>/<model-id>: <canonical-model-id>\n  exclude:\n    - <provider-id>/<model-id>\n```\n\n`configVersion` 是由自動配置系統寫入的選填整數。當存在時，xcsh 會用它來偵測過時的配置並自動升級。\n\n`provider-id` 是在選擇和認證查詢中使用的標準提供者金鑰。\n\n`equivalence` 為選填，用於在具體提供者模型之上配置標準模型分組：\n\n- `overrides` 將精確的具體選擇器（`provider/modelId`）對應到官方上游標準 ID\n- `exclude` 將具體選擇器從標準分組中排除\n\n## 提供者層級欄位\n\n```yaml\nproviders:\n  my-provider:\n    baseUrl: https://api.example.com/v1\n    apiKey: MY_PROVIDER_API_KEY\n    api: openai-completions\n    headers:\n      X-Team: platform\n    authHeader: true\n    auth: apiKey\n    discovery:\n      type: ollama\n    modelOverrides:\n      some-model-id:\n        name: Renamed model\n    models:\n      - id: some-model-id\n        name: Some Model\n        api: openai-completions\n        reasoning: false\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 128000\n        maxTokens: 16384\n        headers:\n          X-Model: value\n        compat:\n          supportsStore: true\n          supportsDeveloperRole: true\n          supportsReasoningEffort: true\n          maxTokensField: max_completion_tokens\n          openRouterRouting:\n            only: [anthropic]\n          vercelGatewayRouting:\n            order: [anthropic, openai]\n          extraBody:\n            gateway: m1-01\n            controller: mlx\n```\n\n### 允許的提供者/模型 `api` 值\n\n- `openai-completions`\n- `openai-responses`\n- `openai-codex-responses`\n- `azure-openai-responses`\n- `anthropic-messages`\n- `google-generative-ai`\n- `google-vertex`\n\n### 允許的 auth/discovery 值\n\n- `auth`：`apiKey`（預設）或 `none`\n- `discovery.type`：`ollama`\n\n## 驗證規則（目前）\n\n### 完整自訂提供者（`models` 非空）\n\n必要欄位：\n\n- `baseUrl`\n- `apiKey`（除非設定 `auth: none`）\n- `api` 於提供者層級或每個模型\n\n### 僅覆寫提供者（`models` 缺失或為空）\n\n必須定義以下至少一項：\n\n- `baseUrl`\n- `modelOverrides`\n- `discovery`\n\n### 探索\n\n- `discovery` 需要提供者層級的 `api`。\n\n### 模型值檢查\n\n- `id` 為必填\n- `contextWindow` 和 `maxTokens` 若提供則必須為正數\n\n## 合併與覆寫順序\n\nModelRegistry 處理流程（重新整理時）：\n\n1. 從 `@f5-sales-demo/pi-ai` 載入內建提供者/模型。\n2. 載入 `models.yml` 自訂配置。\n3. 將提供者覆寫（`baseUrl`、`headers`）套用至內建模型。\n4. 套用 `modelOverrides`（依提供者 + 模型 ID）。\n5. 合併自訂 `models`：\n   - 相同的 `provider + id` 會取代現有項目\n   - 否則附加\n6. 套用執行階段探索到的模型（目前為 Ollama 和 LM Studio），然後重新套用模型覆寫。\n\n## 標準模型等價與合併\n\n註冊表保留每個具體的提供者模型，然後在其上建立標準層。\n\n標準 ID 僅使用官方上游 ID，例如：\n\n- `claude-opus-4-6`\n- `claude-haiku-4-5`\n- `gpt-5.3-codex`\n\n### `models.yml` 等價配置\n\n範例：\n\n```yaml\nproviders:\n  zenmux:\n    baseUrl: https://api.zenmux.example/v1\n    apiKey: ZENMUX_API_KEY\n    api: openai-codex-responses\n    models:\n      - id: codex\n        name: Zenmux Codex\n        reasoning: true\n        input: [text]\n        cost:\n          input: 0\n          output: 0\n          cacheRead: 0\n          cacheWrite: 0\n        contextWindow: 200000\n        maxTokens: 32768\n\nequivalence:\n  overrides:\n    zenmux/codex: gpt-5.3-codex\n    p-codex/codex: gpt-5.3-codex\n  exclude:\n    - demo/codex-preview\n```\n\n標準分組的建立順序：\n\n1. 來自 `equivalence.overrides` 的精確使用者覆寫\n2. 來自內建模型中繼資料的官方 ID 匹配\n3. 針對閘道/提供者變體的保守式啟發式正規化\n4. 回退至具體模型本身的 ID\n\n目前的啟發式規則刻意設計為窄範圍：\n\n- 當存在時可移除嵌入的上游前綴，例如 `anthropic/...` 或 `openai/...`\n- 點分和橫線版本變體僅在對應到現有官方 ID 時才會正規化，例如 `4.6 -> 4-6`\n- 模糊的系列或版本不會在沒有內建匹配或明確覆寫的情況下合併\n\n### 標準解析行為\n\n當多個具體變體共享一個標準 ID 時，解析使用：\n\n1. 可用性和認證\n2. `config.yml` 中的 `modelProviderOrder`\n3. 若 `modelProviderOrder` 未設定，則使用現有的註冊表/提供者順序\n\n已停用或未認證的提供者會被跳過。\n\n會話狀態和記錄會繼續記錄實際執行該回合的具體提供者/模型。\n\n提供者預設值 vs 個別模型覆寫：\n\n- 提供者 `headers` 為基準。\n- 模型 `headers` 會覆寫提供者的標頭金鑰。\n- `modelOverrides` 可以覆寫模型中繼資料（`name`、`reasoning`、`input`、`cost`、`contextWindow`、`maxTokens`、`headers`、`compat`、`contextPromotionTarget`）。\n- `compat` 會對巢狀路由區塊進行深度合併（`openRouterRouting`、`vercelGatewayRouting`、`extraBody`）。\n\n## 執行階段探索整合\n\n### 隱式 Ollama 探索\n\n若未明確配置 `ollama`，註冊表會新增一個隱式可探索提供者：\n\n- 提供者：`ollama`\n- API：`openai-completions`\n- 基礎 URL：`OLLAMA_BASE_URL` 或 `http://127.0.0.1:11434`\n- 認證模式：免金鑰（`auth: none` 行為）\n\n執行階段探索會對 Ollama 呼叫 `GET /api/tags`，並以本地預設值合成模型項目。\n\n### 隱式 llama.cpp 探索\n\n若未明確配置 `llama.cpp`，註冊表會新增一個隱式可探索提供者：\n注意：它使用的是較新的 anthropic messages API，而非 openai-completions。\n\n- 提供者：`llama.cpp`\n- API：`openai-responses`\n- 基礎 URL：`LLAMA_CPP_BASE_URL` 或 `http://127.0.0.1:8080`\n- 認證模式：免金鑰（`auth: none` 行為）\n\n執行階段探索會對 llama.cpp 呼叫 `GET models`，並以本地預設值合成模型項目。\n\n### 隱式 LM Studio 探索\n\n若未明確配置 `lm-studio`，註冊表會新增一個隱式可探索提供者：\n\n- 提供者：`lm-studio`\n- API：`openai-completions`\n- 基礎 URL：`LM_STUDIO_BASE_URL` 或 `http://127.0.0.1:1234/v1`\n- 認證模式：免金鑰（`auth: none` 行為）\n\n執行階段探索會擷取模型（`GET /models`），並以本地預設值合成模型項目。\n\n### 明確提供者探索\n\n您可以自行配置探索：\n\n```yaml\nproviders:\n  ollama:\n    baseUrl: http://127.0.0.1:11434\n    api: openai-completions\n    auth: none\n    discovery:\n      type: ollama\n      \n  llama.cpp:\n    baseUrl: http://127.0.0.1:8080\n    api: openai-responses\n    auth: none\n    discovery:\n      type: llama.cpp\n```\n\n### 擴充提供者註冊\n\n擴充功能可以在執行階段註冊提供者（`pi.registerProvider(...)`），包括：\n\n- 替換/附加提供者的模型\n- 為新的 API ID 註冊自訂串流處理器\n- 註冊自訂 OAuth 提供者\n\n## 認證與 API 金鑰解析順序\n\n請求提供者金鑰時，生效順序為：\n\n1. 執行階段覆寫（CLI `--api-key`）\n2. 儲存在 `agent.db` 中的 API 金鑰憑證\n3. 儲存在 `agent.db` 中的 OAuth 憑證（含重新整理）\n4. 環境變數對應（`OPENAI_API_KEY`、`ANTHROPIC_API_KEY` 等）\n5. ModelRegistry 備援解析器（來自 `models.yml` 的提供者 `apiKey`，環境變數名稱或字面值語意）\n\n`models.yml` 中 `apiKey` 的行為：\n\n- 該值首先被當作環境變數名稱處理。\n- 若無對應的環境變數存在，則使用字面字串作為令牌。\n\n若 `authHeader: true` 且提供者 `apiKey` 已設定，模型會取得：\n\n- 注入 `Authorization: Bearer <resolved-key>` 標頭。\n\n免金鑰提供者：\n\n- 標記為 `auth: none` 的提供者視為無需憑證即可用。\n- `getApiKey*` 會為它們回傳 `kNoAuth`。\n\n## 模型可用性 vs 所有模型\n\n- `getAll()` 回傳已載入的模型註冊表（內建 + 合併的自訂 + 探索到的）。\n- `getAvailable()` 篩選出免金鑰或有可解析認證的模型。\n\n因此模型可以存在於註冊表中，但在認證可用之前無法被選擇。\n\n## 執行階段模型解析\n\n### CLI 與模式解析\n\n`model-resolver.ts` 支援：\n\n- 精確的 `provider/modelId`\n- 精確的標準模型 ID\n- 精確的模型 ID（推斷提供者）\n- 模糊/子字串匹配\n- `--models` 中的 glob 範圍模式（例如 `openai/*`、`*sonnet*`）\n- 選填的 `:thinkingLevel` 後綴（`off|minimal|low|medium|high|xhigh`）\n\n`--provider` 為舊版參數；建議使用 `--model`。\n\n精確選擇器的解析優先順序：\n\n1. 精確的 `provider/modelId` 繞過合併\n2. 精確的標準 ID 透過標準索引解析\n3. 精確的裸具體 ID 仍然有效\n4. 模糊和 glob 匹配在精確路徑之後執行\n\n### 初始模型選擇優先順序\n\n`findInitialModel(...)` 使用此順序：\n\n1. 明確的 CLI 提供者+模型\n2. 第一個範圍內模型（若非恢復會話）\n3. 已儲存的預設提供者/模型\n4. 可用模型中的已知提供者預設值（例如 OpenAI/Anthropic 等）\n5. 第一個可用模型\n\n### 角色別名與設定\n\n支援的模型角色：\n\n- `default`、`smol`、`slow`、`plan`、`commit`\n\n角色別名如 `pi/smol` 會透過 `settings.modelRoles` 展開。每個角色值也可以附加思考選擇器，例如 `:minimal`、`:low`、`:medium` 或 `:high`。\n\n若某個角色指向另一個角色，目標模型仍正常繼承，而引用角色上的任何明確後綴會在該角色特定用途中優先使用。\n\n相關設定：\n\n- `modelRoles`（記錄）\n- `enabledModels`（範圍模式列表）\n- `modelProviderOrder`（全域標準提供者優先順序）\n- `providers.kimiApiFormat`（`openai` 或 `anthropic` 請求格式）\n- `providers.openaiWebsockets`（`auto|off|on` OpenAI Codex 傳輸的 WebSocket 偏好）\n\n`modelRoles` 可以儲存以下任一種：\n\n- `provider/modelId` 以固定具體的提供者變體\n- 標準 ID 如 `gpt-5.3-codex` 以允許提供者合併\n\n對於 `enabledModels` 和 CLI `--models`：\n\n- 精確的標準 ID 會展開為該標準群組中的所有具體變體\n- 明確的 `provider/modelId` 項目保持精確\n- glob 和模糊匹配仍然作用於具體模型\n\n## `/model` 和 `--list-models`\n\n兩個介面都保持提供者前綴模型的可見性和可選擇性。\n\n它們現在也暴露標準/合併的模型：\n\n- `/model` 在提供者標籤旁包含標準檢視\n- `--list-models` 印出標準區段加上具體提供者列\n\n選擇標準項目會儲存標準選擇器。選擇提供者列會儲存明確的 `provider/modelId`。\n\n## 上下文提升（模型層級備援鏈）\n\n上下文提升是針對小上下文變體（例如 `*-spark`）的溢位恢復機制，當 API 因上下文長度錯誤拒絕請求時，會自動提升至較大上下文的同級模型。\n\n### 觸發與順序\n\n當某回合因上下文溢位錯誤（例如 `context_length_exceeded`）失敗時，`AgentSession` 會在回退至壓縮**之前**嘗試提升：\n\n1. 若 `contextPromotion.enabled` 為 true，解析提升目標（見下文）。\n2. 若找到目標，切換至該模型並重試請求——無需壓縮。\n3. 若無可用目標，則在當前模型上回退至自動壓縮。\n\n### 目標選擇\n\n選擇是模型驅動的，而非角色驅動的：\n\n1. `currentModel.contextPromotionTarget`（若已配置）\n2. 同一提供者 + API 上最小的較大上下文模型\n\n除非憑證可解析（`ModelRegistry.getApiKey(...)`），否則候選項會被忽略。\n\n### OpenAI Codex WebSocket 交接\n\n若從/切換至 `openai-codex-responses`，會話提供者狀態金鑰 `openai-codex-responses` 會在模型切換前關閉。這會丟棄 WebSocket 傳輸狀態，使下一回合在提升的模型上全新啟動。\n\n### 持久化行為\n\n提升使用臨時切換（`setModelTemporary`）：\n\n- 在會話歷史中記錄為臨時的 `model_change`\n- 不會重寫已儲存的角色對應\n\n### 配置明確的備援鏈\n\n透過模型中繼資料中的 `contextPromotionTarget` 直接配置備援。\n\n`contextPromotionTarget` 接受以下任一種：\n\n- `provider/model-id`（明確指定）\n- `model-id`（在當前提供者內解析）\n\n範例（`models.yml`）用於同一提供者上的 Spark -> 非 Spark：\n\n```yaml\nproviders:\n  openai-codex:\n    modelOverrides:\n      gpt-5.3-codex-spark:\n        contextPromotionTarget: openai-codex/gpt-5.3-codex\n```\n\n內建模型產生器也會在同一提供者存在基礎模型時，自動為 `*-spark` 模型指定此項。\n\n## 相容性與路由欄位\n\n`models.yml` 支援以下 `compat` 子集：\n\n- `supportsStore`\n- `supportsDeveloperRole`\n- `supportsReasoningEffort`\n- `maxTokensField`（`max_completion_tokens` 或 `max_tokens`）\n- `openRouterRouting.only` / `openRouterRouting.order`\n- `vercelGatewayRouting.only` / `vercelGatewayRouting.order`\n\n這些由 OpenAI-completions 傳輸邏輯消費，並與基於 URL 的自動偵測結合使用。\n\n## 實用範例\n\n### 本地 OpenAI 相容端點（無認證）\n\n```yaml\nproviders:\n  local-openai:\n    baseUrl: http://127.0.0.1:8000/v1\n    auth: none\n    api: openai-completions\n    models:\n      - id: Qwen/Qwen2.5-Coder-32B-Instruct\n        name: Qwen 2.5 Coder 32B (local)\n```\n\n### 使用環境變數金鑰的託管代理\n\n```yaml\nproviders:\n  anthropic-proxy:\n    baseUrl: https://proxy.example.com/anthropic\n    apiKey: ANTHROPIC_PROXY_API_KEY\n    api: anthropic-messages\n    authHeader: true\n    models:\n      - id: claude-sonnet-4-20250514\n        name: Claude Sonnet 4 (Proxy)\n        reasoning: true\n        input: [text, image]\n```\n\n### 覆寫內建提供者路由 + 模型中繼資料\n\n```yaml\nproviders:\n  openrouter:\n    baseUrl: https://my-proxy.example.com/v1\n    headers:\n      X-Team: platform\n    modelOverrides:\n      anthropic/claude-sonnet-4:\n        name: Sonnet 4 (Corp)\n        compat:\n          openRouterRouting:\n            only: [anthropic]\n```\n\n## LiteLLM 代理自動配置\n\n當 `LITELLM_BASE_URL` 和 `LITELLM_API_KEY` 環境變數皆已設定時，xcsh 會自動管理 LiteLLM 代理的 `models.yml` 配置。\n\n### 首次執行自動產生\n\n若 `models.yml` 不存在且偵測到 LiteLLM 環境變數，xcsh 會自動產生：\n\n```yaml\n# Auto-generated by xcsh for LiteLLM proxy\n# API key resolved from LITELLM_API_KEY env var at runtime\nconfigVersion: 1\nproviders:\n  anthropic:\n    baseUrl: \"https://your-litellm-proxy.example.com/anthropic\"\n    apiKey: LITELLM_API_KEY\n```\n\n同時也會產生一個具有合理圖片提供者設定的預設 `config.yml`。\n\n### 啟動時自我修復\n\n每次啟動時，模型註冊表中的 `startupHealthCheck()` 會執行以下檢查：\n\n| 條件 | 動作 |\n|-----------|--------|\n| `models.yml` 缺失 | 從環境變數自動產生 |\n| `models.yml` 損壞或無法解析 | 備份為 `.bak`，重新產生 |\n| `baseUrl` 與 `LITELLM_BASE_URL` 不符 | 備份為 `.bak`，以新 URL 重新產生 |\n| `configVersion` 缺失或過時 | 備份為 `.bak`，以目前版本重新產生 |\n| 配置正常 | 不執行任何動作 |\n\n所有修復在覆寫前都會建立 `.bak` 備份。所有操作都是冪等的。\n\n### CLI 命令\n\n```bash\nxcsh setup litellm              # Generate or fix LiteLLM config\nxcsh setup litellm --check      # Validate without writing\nxcsh setup litellm --check --json  # Machine-readable validation output\n```\n\n### 必要環境變數\n\n| 變數 | 用途 |\n|----------|---------|\n| `LITELLM_BASE_URL` | LiteLLM 代理 URL（例如 `https://your-proxy.example.com`）。必須以 `http://` 或 `https://` 開頭。 |\n| `LITELLM_API_KEY` | 代理的 API 金鑰。在產生的配置中以名稱引用，於執行階段解析。 |\n\n若任一變數未設定，自動配置會靜默跳過。\n\n### 配置版本控制\n\n產生的配置包含 `configVersion` 欄位。當產生的格式在未來版本中變更時，xcsh 會偵測過時的配置並自動升級（含備份）。\n\n## 舊版消費者注意事項\n\n大多數模型配置現在透過 `ModelRegistry` 經由 `models.yml` 流轉。\n\n仍存在一個值得注意的舊版路徑：網頁搜尋的 Anthropic 認證解析仍直接在 `src/web/search/auth.ts` 中讀取 `~/.xcsh/agent/models.json`。\n\n若您依賴該特定路徑，在該模組遷移之前，請注意保持 JSON 的相容性。\n\n## 故障模式\n\n若 `models.yml` 未通過結構描述或驗證檢查：\n\n- 若 `LITELLM_BASE_URL` 和 `LITELLM_API_KEY` 已設定，啟動健康檢查會嘗試自動修復（備份損壞的檔案，從環境變數重新產生）。若修復成功，註冊表會重新載入修復後的配置。\n- 若無法自動修復（環境變數未設定、寫入失敗），註冊表會繼續使用內建模型運作。\n- 錯誤會透過 `ModelRegistry.getError()` 暴露，並顯示在 UI/通知中。\n",
	"zh-tw/providers/provider-streaming-internals.md": "---\ntitle: Provider 串流內部機制\ndescription: Provider 串流實作，包含 SSE 解析、token 計數與背壓處理。\nsidebar:\n  order: 2\n  label: 串流內部機制\ni18n:\n  sourceHash: a32ffa769c4d\n  translator: machine\n---\n\n# Provider 串流內部機制\n\n本文件說明 `@f5-sales-demo/pi-ai` 中 token/工具串流的標準化方式，以及如何透過 `@f5-sales-demo/pi-agent-core` 和 `coding-agent` 的工作階段事件進行傳播。\n\n## 端對端流程\n\n1. `streamSimple()`（`packages/ai/src/stream.ts`）映射通用選項並分派至 provider 串流函式。\n2. Provider 串流函式（`anthropic.ts`、`openai-responses.ts`、`google.ts`）將 provider 原生串流事件轉換為統一的 `AssistantMessageEvent` 序列。\n3. 每個 provider 將事件推送至 `AssistantMessageEventStream`（`packages/ai/src/utils/event-stream.ts`），該元件會節流 delta 事件並提供：\n   - 用於增量更新的非同步迭代\n   - `result()` 用於取得最終的 `AssistantMessage`\n4. `agentLoop`（`packages/agent/src/agent-loop.ts`）消費這些事件，變更進行中的助理狀態，並發出攜帶原始 `assistantMessageEvent` 的 `message_update` 事件。\n5. `AgentSession`（`packages/coding-agent/src/session/agent-session.ts`）訂閱代理事件、持久化訊息、驅動擴充掛鉤，並套用工作階段行為（重試、壓縮、TTSR、串流編輯中止檢查）。\n\n## `@f5-sales-demo/pi-ai` 中的統一串流契約\n\n所有 provider 發出相同的結構（`packages/ai/src/types.ts` 中的 `AssistantMessageEvent`）：\n\n- `start`\n- 內容區塊生命週期三元組：\n  - 文字：`text_start` → `text_delta`* → `text_end`\n  - 思考：`thinking_start` → `thinking_delta`* → `thinking_end`\n  - 工具呼叫：`toolcall_start` → `toolcall_delta`* → `toolcall_end`\n- 終端事件：\n  - `done`，附帶 `reason: \"stop\" | \"length\" | \"toolUse\"`\n  - 或 `error`，附帶 `reason: \"aborted\" | \"error\"`\n\n`AssistantMessageEventStream` 保證：\n\n- 最終結果由終端事件（`done` 或 `error`）解析\n- delta 事件會批次/節流處理（約 50ms）\n- 緩衝的 delta 在非 delta 事件之前以及完成之前會被清空\n\n## Delta 節流與協調行為\n\n`AssistantMessageEventStream` 將 `text_delta`、`thinking_delta` 和 `toolcall_delta` 視為可合併事件：\n\n- 緩衝的 delta 僅在 **type + contentIndex** 匹配時才會合併\n- 合併保留最新的 `partial` 快照\n- 非 delta 事件會強制立即清空緩衝\n\n這能平滑高頻 provider 串流以供 TUI/事件消費者使用，但並非 provider 背壓機制：provider 仍以全速產出，本地串流僅進行緩衝。\n\n## Provider 標準化細節\n\n## Anthropic（`anthropic-messages`）\n\n來源：`packages/ai/src/providers/anthropic.ts`\n\n標準化要點：\n\n- `message_start` 初始化使用量（輸入/輸出/快取 token）\n- `content_block_start` 映射至文字/思考/工具呼叫的開始事件\n- `content_block_delta` 映射：\n  - `text_delta` → `text_delta`\n  - `thinking_delta` → `thinking_delta`\n  - `input_json_delta` → `toolcall_delta`\n  - `signature_delta` 僅更新 `thinkingSignature`（不發出事件）\n- `content_block_stop` 發出對應的 `*_end`\n- `message_delta.stop_reason` 透過 `mapStopReason()` 映射\n\n工具呼叫參數串流：\n\n- 每個工具區塊攜帶內部 `partialJson`\n- 每個 JSON delta 追加至 `partialJson`\n- `arguments` 在每次 delta 時透過 `parseStreamingJson()` 重新解析\n- `toolcall_end` 再次重新解析，然後移除 `partialJson`\n\n## OpenAI Responses（`openai-responses`）\n\n來源：`packages/ai/src/providers/openai-responses.ts`\n\n標準化要點：\n\n- `response.output_item.added` 開始推理/文字/函式呼叫區塊\n- 推理摘要事件（`response.reasoning_summary_text.delta`）成為 `thinking_delta`\n- 輸出/拒絕 delta 成為 `text_delta`\n- `response.function_call_arguments.delta` 成為 `toolcall_delta`\n- `response.output_item.done` 發出 `thinking_end` / `text_end` / `toolcall_end`\n- `response.completed` 將狀態映射至停止原因和使用量\n\n工具呼叫參數串流：\n\n- 與 Anthropic 相同的 `partialJson` 累積模式\n- 僅發送 `response.function_call_arguments.done` 的 provider 仍會填充最終參數\n- 工具呼叫 ID 標準化為 `\"<call_id>|<item_id>\"`\n\n## Google Generative AI（`google-generative-ai`）\n\n來源：`packages/ai/src/providers/google.ts`\n\n標準化要點：\n\n- 迭代 `candidate.content.parts`\n- 文字部分透過 `isThinkingPart(part)` 區分思考與文字\n- 區塊轉換時會先關閉前一個區塊再開始新區塊\n- `part.functionCall` 被視為完整的工具呼叫（立即發出 start/delta/end）\n- 結束原因透過 `google-shared.ts` 中的 `mapStopReason()` 映射\n\n工具呼叫參數串流：\n\n- 函式呼叫參數以結構化物件到達，非增量 JSON 文字\n- 實作發出一個合成的 `toolcall_delta`，包含 `JSON.stringify(arguments)`\n- 此路徑中 Google 不需要部分 JSON 解析器\n\n## 部分工具呼叫 JSON 累積與恢復\n\nAnthropic/OpenAI Responses 的共用行為使用 `parseStreamingJson()`（`packages/ai/src/utils/json-parse.ts`）：\n\n1. 嘗試 `JSON.parse`\n2. 退回使用 `partial-json` 解析器處理不完整片段\n3. 若兩者都失敗，回傳 `{}`\n\n影響：\n\n- 格式錯誤或截斷的參數 delta 不會立即導致串流處理崩潰\n- 進行中的 `arguments` 可能暫時為 `{}`\n- 後續有效的 delta 可以恢復結構化參數，因為每次追加都會重新嘗試解析\n- 最終的 `toolcall_end` 在發出前會執行最後一次解析嘗試\n\n## 停止原因 vs 傳輸/執行期錯誤\n\nProvider 停止原因映射至標準化的 `stopReason`：\n\n- Anthropic：`end_turn`→`stop`、`max_tokens`→`length`、`tool_use`→`toolUse`、安全/拒絕情況→`error`\n- OpenAI Responses：`completed`→`stop`、`incomplete`→`length`、`failed/cancelled`→`error`\n- Google：`STOP`→`stop`、`MAX_TOKENS`→`length`、安全/禁止/格式錯誤函式呼叫類別→`error`\n\n錯誤語意分為兩個階段：\n\n1. **模型完成語意**（provider 回報的結束原因/狀態）\n2. **傳輸/執行期失敗**（網路/客戶端/解析器/中止例外）\n\n若 provider 串流拋出例外或發出失敗訊號，每個 provider 包裝器會捕獲並發出終端 `error` 事件，包含：\n\n- 當中止訊號被設定時 `stopReason = \"aborted\"`\n- 否則 `stopReason = \"error\"`\n- `errorMessage = formatErrorMessageWithRetryAfter(error)`\n\n## 格式錯誤的區塊 / SSE 解析失敗行為\n\n對於這些 provider 路徑，區塊/SSE 框架處理由供應商 SDK 串流負責（Anthropic SDK、OpenAI SDK、Google SDK）。此程式碼在此處不實作自訂 SSE 解碼器。\n\n目前實作中觀察到的行為：\n\n- SDK 層級的格式錯誤區塊/SSE 解析會以例外或串流 `error` 事件浮現\n- Provider 包裝器將其轉換為統一的終端 `error` 事件\n- 串流函式本身內部不進行 provider 特定的恢復/重試\n- 更高層級的重試在 `AgentSession` 自動重試邏輯中處理（訊息層級重試，非串流區塊重播）\n\n## 取消邊界\n\n取消機制是分層的：\n\n- AI provider 請求：`options.signal` 傳入 provider 客戶端串流呼叫。\n- Provider 包裝器：串流迴圈結束後，已中止的訊號強制進入錯誤路徑（`\"Request was aborted\"`）。\n- 代理迴圈：在處理每個 provider 事件之前檢查 `signal.aborted`，並可從最新的部分內容合成已中止的助理訊息。\n- 工作階段/代理控制：`AgentSession.abort()` -> `agent.abort()` -> 共用中止控制器取消。\n\n工具執行取消與模型串流取消是分開的：\n\n- 工具執行器使用 `AbortSignal.any([agentSignal, steeringAbortSignal])`\n- 導向中斷可以中止剩餘的工具執行，同時保留已產出的工具結果\n\n## 背壓邊界\n\nProvider SDK 串流與下游消費者之間沒有硬性背壓機制：\n\n- `EventStream` 使用無最大大小限制的記憶體內佇列\n- 節流降低 UI 更新頻率但不會減緩 provider 的接收速度\n- 若消費者顯著落後，排隊的事件可能會持續增長直到完成\n\n目前的設計偏好回應性和簡單的排序，而非有界緩衝區的流量控制。\n\n## 串流事件如何以代理/工作階段事件呈現\n\n`agentLoop.streamAssistantResponse()` 將 `AssistantMessageEvent` 橋接至 `AgentEvent`：\n\n- 於 `start`：推送佔位的助理訊息並發出 `message_start`\n- 於區塊事件（`text_*`、`thinking_*`、`toolcall_*`）：更新最後的助理訊息，發出附帶原始 `assistantMessageEvent` 的 `message_update`\n- 於終端事件（`done`/`error`）：從 `response.result()` 解析最終訊息，發出 `message_end`\n\n`AgentSession` 隨後消費這些事件以進行工作階段層級的行為：\n\n- TTSR 監看 `message_update.assistantMessageEvent` 中的 `text_delta` 和 `toolcall_delta`\n- 串流編輯防護檢查 `edit` 呼叫上的 `toolcall_delta`/`toolcall_end` 並可提前中止\n- 持久化在 `message_end` 時寫入已完成的訊息\n- 自動重試檢查助理的 `stopReason === \"error\"` 加上 `errorMessage` 啟發式規則\n\n## 統一 vs provider 特定的職責\n\n統一（共用契約）：\n\n- 事件結構（`AssistantMessageEvent`）\n- 最終結果擷取（`done`/`error`）\n- delta 節流 + 合併規則\n- 代理/工作階段事件傳播模型\n\nProvider 特定（未完全抽象化）：\n\n- 上游事件分類法和映射邏輯\n- 停止原因轉換表\n- 工具呼叫 ID 慣例\n- 推理/思考區塊語意和簽章\n- 使用量 token 語意和可用性時機\n- 每個 API 的訊息轉換限制\n\n## 實作檔案\n\n- [`../../ai/src/stream.ts`](../../packages/ai/src/stream.ts) — provider 分派、選項映射、API 金鑰/工作階段配管。\n- [`../../ai/src/utils/event-stream.ts`](../../packages/ai/src/utils/event-stream.ts) — 通用串流佇列 + 助理 delta 節流。\n- [`../../ai/src/utils/json-parse.ts`](../../packages/ai/src/utils/json-parse.ts) — 串流工具參數的部分 JSON 解析。\n- [`../../ai/src/providers/anthropic.ts`](../../packages/ai/src/providers/anthropic.ts) — Anthropic 事件轉換和工具 JSON delta 累積。\n- [`../../ai/src/providers/openai-responses.ts`](../../packages/ai/src/providers/openai-responses.ts) — OpenAI Responses 事件轉換和狀態映射。\n- [`../../ai/src/providers/google.ts`](../../packages/ai/src/providers/google.ts) — Gemini 串流區塊到區塊的轉換。\n- [`../../ai/src/providers/google-shared.ts`](../../packages/ai/src/providers/google-shared.ts) — Gemini 結束原因映射和共用轉換規則。\n- [`../../agent/src/agent-loop.ts`](../../packages/agent/src/agent-loop.ts) — provider 串流消費和 `message_update` 橋接。\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — 串流更新、中止、重試和持久化的工作階段層級處理。\n",
	"zh-tw/providers/python-repl.md": "---\ntitle: Python 工具與 IPython 執行環境\ndescription: 具備 IPython 核心管理、執行與輸出擷取功能的 Python REPL 工具執行環境。\nsidebar:\n  order: 3\n  label: Python 與 IPython\ni18n:\n  sourceHash: 70f0a034ecef\n  translator: machine\n---\n\n# Python 工具與 IPython 執行環境\n\n本文件描述 `packages/coding-agent` 中目前的 Python 執行堆疊。\n涵蓋工具行為、核心/閘道器生命週期、環境處理、執行語意、輸出渲染以及操作故障模式。\n\n## 範圍與關鍵檔案\n\n- 工具介面：`src/tools/python.ts`\n- 工作階段/每次呼叫的核心協調：`src/ipy/executor.ts`\n- 核心協定 + 閘道器整合：`src/ipy/kernel.ts`\n- 共享本地閘道器協調器：`src/ipy/gateway-coordinator.ts`\n- 使用者觸發 Python 執行的互動模式渲染器：`src/modes/components/python-execution.ts`\n- 執行環境/環境過濾與 Python 解析：`src/ipy/runtime.ts`\n\n## Python 工具是什麼\n\n`python` 工具透過 Jupyter Kernel Gateway 支援的核心執行一或多個 Python 儲存格（而非透過每個儲存格直接產生 `python -c` 程序）。\n\n工具參數：\n\n```ts\n{\n  cells: Array<{ code: string; title?: string }>;\n  timeout?: number; // 秒，限制在 1..600，預設 30\n  cwd?: string;\n  reset?: boolean; // 僅在第一個儲存格前重設核心\n}\n```\n\n此工具對工作階段的 `concurrency = \"exclusive\"`，因此呼叫不會重疊。\n\n## 閘道器生命週期\n\n### 模式\n\n有兩種閘道器路徑：\n\n1. **外部閘道器**（設定 `PI_PYTHON_GATEWAY_URL`）\n   - 直接使用設定的 URL。\n   - 可選擇使用 `PI_PYTHON_GATEWAY_TOKEN` 進行驗證。\n   - 不會產生或管理本地閘道器程序。\n\n2. **本地共享閘道器**（預設路徑）\n   - 使用在 `~/.xcsh/agent/python-gateway` 下協調的單一共享程序。\n   - 中繼資料檔案：`gateway.json`\n   - 鎖定檔案：`gateway.lock`\n   - 產生命令：\n     - `python -m kernel_gateway`\n     - 繫結到 `127.0.0.1:<配置的連接埠>`\n     - 啟動健康檢查：`GET /api/kernelspecs`\n\n### 本地共享閘道器協調\n\n`acquireSharedGateway()`：\n\n- 取得檔案鎖定（`gateway.lock`）並帶有心跳機制。\n- 若 PID 存活且健康檢查通過，則重用 `gateway.json`。\n- 需要時清除過期的資訊/PID。\n- 當不存在健康的閘道器時啟動新的。\n\n`releaseSharedGateway()` 目前為空操作（核心關閉不會拆除共享閘道器）。\n\n`shutdownSharedGateway()` 明確終止共享程序並清除閘道器中繼資料。\n\n### 重要限制\n\n`python.sharedGateway=false` 在核心啟動時會被拒絕：\n\n- 錯誤：`Shared Python gateway required; local gateways are disabled`\n- 沒有每程序的非共享本地閘道器模式。\n\n## 核心生命週期\n\n每次執行使用透過 `POST /api/kernels` 在選定閘道器上建立的核心。\n\n核心啟動序列：\n\n1. 可用性檢查（`checkPythonKernelAvailability`）\n2. 建立核心（`/api/kernels`）\n3. 開啟 websocket（`/api/kernels/:id/channels`）\n4. 初始化核心環境（`cwd`、環境變數、`sys.path`）\n5. 執行 `PYTHON_PRELUDE`\n6. 從以下位置載入擴充模組：\n   - 使用者：`~/.xcsh/agent/modules/*.py`\n   - 專案：`<cwd>/.xcsh/modules/*.py`（覆寫同名的使用者模組）\n\n核心關閉：\n\n- 透過 `DELETE /api/kernels/:id` 刪除遠端核心\n- 關閉 websocket\n- 呼叫共享閘道器釋放掛鉤（目前為空操作）\n\n## 工作階段持久化語意\n\n`python.kernelMode` 控制核心重用：\n\n- `session`（預設）\n  - 以工作階段身分 + cwd 為鍵值重用核心工作階段。\n  - 每個工作階段的執行透過佇列序列化。\n  - 閒置工作階段在 5 分鐘後被驅逐。\n  - 最多 4 個工作階段；溢出時驅逐最舊的。\n  - 心跳檢查偵測已死亡的核心。\n  - 允許自動重啟一次；重複崩潰 => 硬性失敗。\n\n- `per-call`\n  - 為每個執行請求建立全新核心。\n  - 請求完成後關閉核心。\n  - 不跨呼叫保留狀態。\n\n### 單次工具呼叫中的多儲存格行為\n\n儲存格在該工具呼叫的同一核心實例中依序執行。\n\n如果中間儲存格失敗：\n\n- 先前儲存格的狀態仍保留在記憶體中。\n- 工具回傳指出哪個儲存格失敗的目標錯誤。\n- 後續儲存格不會被執行。\n\n`reset=true` 僅適用於該呼叫中的第一個儲存格執行。\n\n## 環境過濾與執行環境解析\n\n環境在啟動閘道器/核心執行環境之前會被過濾：\n\n- 允許清單包含核心變數如 `PATH`、`HOME`、地區設定變數、`VIRTUAL_ENV`、`PYTHONPATH` 等。\n- 允許前綴：`LC_`、`XDG_`、`PI_`\n- 拒絕清單移除常見 API 金鑰（OpenAI/Anthropic/Gemini 等）\n\n執行環境選擇順序：\n\n1. 啟用/找到的虛擬環境（`VIRTUAL_ENV`，然後 `<cwd>/.venv`、`<cwd>/venv`）\n2. `~/.xcsh/python-env` 的受管理虛擬環境\n3. PATH 上的 `python` 或 `python3`\n\n當選擇虛擬環境時，其 bin/Scripts 路徑會被前置到 `PATH`。\n\nPython 內部的核心環境初始化也會：\n\n- `os.chdir(cwd)`\n- 將提供的環境對應注入 `os.environ`\n- 確保 cwd 在 `sys.path` 中\n\n## 工具可用性與模式選擇\n\n`python.toolMode`（預設 `both`）+ 可選的 `PI_PY` 覆寫控制暴露方式：\n\n- `ipy-only`\n- `bash-only`\n- `both`\n\n`PI_PY` 接受的值：\n\n- `0` / `bash` -> `bash-only`\n- `1` / `py` -> `ipy-only`\n- `mix` / `both` -> `both`\n\n如果 Python 預檢失敗，該工作階段的工具建立會降級為僅 bash。\n\n## 執行流程與取消/逾時\n\n### 工具層級逾時\n\n`python` 工具逾時以秒為單位，預設 30，限制在 `1..600`。\n\n工具結合：\n\n- 呼叫者中止信號\n- 逾時中止信號\n\n使用 `AbortSignal.any(...)`。\n\n### 核心執行取消\n\n在中止/逾時時：\n\n- 執行被標記為已取消。\n- 嘗試透過 REST（`POST /interrupt`）和控制通道 `interrupt_request` 中斷核心。\n- 結果包含 `cancelled=true`。\n- 逾時路徑將輸出標註為 `Command timed out after <n> seconds`。\n\n### stdin 行為\n\n不支援互動式 stdin。\n\n如果核心發出 `input_request`：\n\n- 工具記錄 `stdinRequested=true`\n- 發出說明文字\n- 傳送空的 `input_reply`\n- 執行在執行器層被視為失敗\n\n## 輸出擷取與渲染\n\n### 擷取的輸出類別\n\n來自核心訊息：\n\n- `stream` -> 純文字區塊\n- `display_data`/`execute_result` -> 豐富顯示處理\n- `error` -> 回溯文字\n- 自訂 MIME `application/x-xcsh-status` -> 結構化狀態事件\n\n顯示 MIME 優先順序：\n\n1. `text/markdown`\n2. `text/plain`\n3. `text/html`（轉換為基本 markdown）\n\n另外作為結構化輸出擷取：\n\n- `application/json` -> JSON 樹狀資料\n- `image/png` -> 圖片負載\n- `application/x-xcsh-status` -> 狀態事件\n\n### 儲存與截斷\n\n輸出透過 `OutputSink` 串流，並可能持久化到成品儲存。\n\n工具結果可包含截斷中繼資料和 `artifact://<id>` 以供完整輸出復原。\n\n### 渲染器行為\n\n- 工具渲染器（`python.ts`）：\n  - 顯示帶有每個儲存格狀態的程式碼儲存格區塊\n  - 收合預覽預設為 10 行\n  - 支援展開模式以顯示完整輸出和更豐富的狀態詳情\n- 互動渲染器（`python-execution.ts`）：\n  - 用於 TUI 中使用者觸發的 Python 執行\n  - 收合預覽預設為 20 行\n  - 為顯示安全性，將非常長的個別行限制在 4000 個字元\n  - 顯示取消/錯誤/截斷通知\n\n## 外部閘道器支援\n\n設定：\n\n```bash\nexport PI_PYTHON_GATEWAY_URL=\"http://127.0.0.1:8888\"\n# 可選：\nexport PI_PYTHON_GATEWAY_TOKEN=\"...\"\n```\n\n與本地共享閘道器的行為差異：\n\n- 無本地閘道器鎖定/資訊檔案\n- 無本地程序產生/終止\n- 健康檢查和核心 CRUD 針對外部端點執行\n- 驗證失敗會顯示明確的權杖指引\n\n## 操作疑難排解（目前的故障模式）\n\n- **Python 工具不可用**\n  - 檢查 `python.toolMode` / `PI_PY`。\n  - 如果預檢失敗，執行環境會退回至僅 bash。\n\n- **核心可用性錯誤**\n  - 本地模式要求在解析的 Python 執行環境中可匯入 `kernel_gateway` 和 `ipykernel`。\n  - 安裝方式：\n\n    ```bash\n    python -m pip install jupyter_kernel_gateway ipykernel\n    ```\n\n- **`python.sharedGateway=false` 導致啟動失敗**\n  - 這在目前的實作中是預期行為。\n\n- **外部閘道器驗證/可達性失敗**\n  - 401/403 -> 設定 `PI_PYTHON_GATEWAY_TOKEN`。\n  - 逾時/無法連線 -> 驗證 URL/網路和閘道器健康狀態。\n\n- **執行掛起後逾時**\n  - 如果工作負載合理，增加工具 `timeout`（最大 600 秒）。\n  - 對於卡住的程式碼，取消會觸發核心中斷，但使用者程式碼可能仍需重構。\n\n- **Python 程式碼中的 stdin/輸入提示**\n  - 在此執行環境路徑中不支援互動式 `input()`；請以程式方式傳遞資料。\n\n- **資源耗盡（`EMFILE` / 開啟檔案過多）**\n  - 工作階段管理器觸發共享閘道器恢復（工作階段拆除 + 共享閘道器重啟）。\n\n- **工作目錄錯誤**\n  - 工具在執行前驗證 `cwd` 存在且為目錄。\n\n## 相關環境變數\n\n- `PI_PY` — 工具暴露覆寫（上述 `bash-only`/`ipy-only`/`both` 對應）\n- `PI_PYTHON_GATEWAY_URL` — 使用外部閘道器\n- `PI_PYTHON_GATEWAY_TOKEN` — 可選的外部閘道器驗證權杖\n- `PI_PYTHON_SKIP_CHECK=1` — 略過 Python 預檢/預熱檢查\n- `PI_PYTHON_IPC_TRACE=1` — 記錄核心 IPC 傳送/接收追蹤\n- `PI_DEBUG_STARTUP=1` — 發出啟動階段除錯標記\n",
	"zh-tw/runtime-tools/bash-tool-runtime.md": "---\ntitle: Bash 工具執行環境\ndescription: 具備 Shell 程序管理、沙箱機制、逾時處理及輸出串流功能的 Bash 工具執行環境。\nsidebar:\n  order: 1\n  label: Bash 工具\ni18n:\n  sourceHash: 18b12aa5dbd5\n  translator: machine\n---\n\n# Bash 工具執行環境\n\n本文件描述 agent 工具呼叫所使用的 **`bash` 工具**執行路徑，從命令正規化到執行、截斷/產出物，以及渲染。\n\n同時也指出在互動式 TUI、列印模式、RPC 模式，以及使用者發起的 bang（`!`）shell 執行之間的行為差異。\n\n## 範圍與執行介面\n\ncoding-agent 中有兩個不同的 bash 執行介面：\n\n1. **工具呼叫介面**（`toolName: \"bash\"`）：當模型呼叫 bash 工具時使用。\n   - 進入點：`BashTool.execute()`。\n2. **使用者 bang 命令介面**（互動式輸入的 `!cmd` 或 RPC `bash` 命令）：工作階段層級的輔助路徑。\n   - 進入點：`AgentSession.executeBash()`。\n\n兩者最終都使用 `src/exec/bash-executor.ts` 中的 `executeBash()` 進行非 PTY 執行，但只有工具呼叫路徑會執行正規化/攔截及工具渲染器邏輯。\n\n## 端對端工具呼叫流程\n\n## 1) 輸入正規化與參數合併\n\n`BashTool.execute()` 首先透過 `normalizeBashCommand()` 正規化原始命令：\n\n- 提取結尾的 `| head -n N`、`| head -N`、`| tail -n N`、`| tail -N` 為結構化限制，\n- 修剪結尾/開頭的空白字元，\n- 保持內部空白字元不變。\n\n然後將提取的限制與明確的工具引數合併：\n\n- 明確的 `head`/`tail` 引數會覆蓋提取的值，\n- 提取的值僅作為後備。\n\n### 注意事項\n\n`bash-normalize.ts` 的註解提到會移除 `2>&1`，但目前的實作並未移除它。執行時行為仍然正確（stdout/stderr 已經合併），但正規化行為比註解所描述的更為有限。\n\n## 2) 選擇性攔截（封鎖命令路徑）\n\n如果 `bashInterceptor.enabled` 為 true，`BashTool` 會從設定載入規則，並對正規化後的命令執行 `checkBashInterception()`。\n\n攔截行為：\n\n- 命令**僅在**以下條件時被封鎖：\n  - 正規表示式規則匹配，且\n  - 建議的工具存在於 `ctx.toolNames` 中。\n- 無效的正規表示式規則會被靜默跳過。\n- 封鎖時，`BashTool` 會拋出 `ToolError`，訊息為：\n  - `Blocked: ...`\n  - 包含原始命令。\n\n預設規則模式（定義在程式碼中）針對常見的誤用：\n\n- 檔案讀取器（`cat`、`head`、`tail`、...）\n- 搜尋工具（`grep`、`rg`、...）\n- 檔案查找器（`find`、`fd`、...）\n- 就地編輯器（`sed -i`、`perl -i`、`awk -i inplace`）\n- Shell 重導向寫入（`echo ... > file`、heredoc 重導向）\n\n### 注意事項\n\n`InterceptionResult` 包含 `suggestedTool`，但 `BashTool` 目前僅呈現訊息文字（`details` 中沒有結構化的建議工具欄位）。\n\n## 3) CWD 驗證與逾時限制\n\n`cwd` 相對於工作階段 cwd（`resolveToCwd`）解析，然後透過 `stat` 驗證：\n\n- 路徑不存在 -> `ToolError(\"Working directory does not exist: ...\")`\n- 非目錄 -> `ToolError(\"Working directory is not a directory: ...\")`\n\n逾時被限制在 `[1, 3600]` 秒之間，並轉換為毫秒。\n\n## 4) 產出物配置\n\n在執行之前，工具會配置產出物路徑/ID（盡力嘗試）用於截斷輸出的儲存。\n\n- 產出物配置失敗不會導致致命錯誤（執行會繼續，但沒有產出物溢出檔案），\n- 產出物 ID/路徑會傳入執行路徑，以便在截斷時持久化完整輸出。\n\n## 5) PTY 與非 PTY 執行選擇\n\n`BashTool` 僅在以下條件全部成立時選擇 PTY 執行：\n\n- `bash.virtualTerminal === \"on\"`\n- `PI_NO_PTY !== \"1\"`\n- 工具上下文具有 UI（`ctx.hasUI === true` 且 `ctx.ui` 已設定）\n\n否則使用非互動式 `executeBash()`。\n\n這意味著列印模式和非 UI 的 RPC/工具上下文始終使用非 PTY。\n\n## 非互動式執行引擎（`executeBash`）\n\n## Shell 工作階段重用模型\n\n`executeBash()` 在程序全域 map 中快取原生 `Shell` 實例，鍵值由以下組成：\n\n- shell 路徑，\n- 設定的命令前綴，\n- 快照路徑，\n- 序列化的 shell 環境變數，\n- 選用的 agent 工作階段金鑰。\n\n對於工作階段層級的執行，`AgentSession.executeBash()` 傳遞 `sessionKey: this.sessionId`，使重用隔離到每個工作階段。\n\n工具呼叫路徑**不**傳遞 `sessionKey`，因此重用範圍基於 shell 設定/快照/環境。\n\n## Shell 設定與快照行為\n\n每次呼叫時，執行器載入設定中的 shell 設定（`shell`、`env`、選用的 `prefix`）。\n\n如果選定的 shell 包含 `bash`，它會嘗試 `getOrCreateSnapshot()`：\n\n- 快照擷取使用者 rc 中的別名/函式/選項，\n- 快照建立為盡力嘗試，\n- 失敗時會回退到不使用快照。\n\n如果設定了 `prefix`，命令會變為：\n\n```text\n<prefix> <command>\n```\n\n## 串流與取消\n\n`Shell.run()` 將區塊串流到回呼函式。執行器將每個區塊導入 `OutputSink` 和選用的 `onChunk` 回呼函式。\n\n取消：\n\n- 中止信號觸發 `shellSession.abort(...)`，\n- 原生結果中的逾時被映射為 `cancelled: true` + 附註文字，\n- 明確的取消同樣回傳 `cancelled: true` + 附註。\n\n逾時/取消時，執行器內部不會拋出例外；它回傳結構化的 `BashResult`，讓呼叫者映射錯誤語義。\n\n## 互動式 PTY 路徑（`runInteractiveBashPty`）\n\n當 PTY 啟用時，工具執行 `runInteractiveBashPty()`，開啟覆蓋式主控台元件並驅動原生 `PtySession`。\n\n行為重點：\n\n- xterm-headless 虛擬終端機在覆蓋層中渲染視窗，\n- 鍵盤輸入經過正規化（包括 Kitty 序列和應用程式游標模式處理），\n- 執行時按 `esc` 會終止 PTY 工作階段，\n- 終端機大小調整會傳播到 PTY（`session.resize(cols, rows)`）。\n\n為無人值守執行注入了環境強化預設值：\n\n- 停用分頁器（`PAGER=cat`、`GIT_PAGER=cat` 等），\n- 停用編輯器提示（`GIT_EDITOR=true`、`EDITOR=true`、...），\n- 減少終端機/認證提示（`GIT_TERMINAL_PROMPT=0`、`SSH_ASKPASS=/usr/bin/false`、`CI=1`），\n- 套件管理器/工具的非互動行為自動化旗標。\n\nPTY 輸出經過正規化（`CRLF`/`CR` 轉為 `LF`、`sanitizeText`）並寫入 `OutputSink`，包括產出物溢出支援。\n\nPTY 啟動/執行錯誤時，sink 接收 `PTY error: ...` 行，命令以未定義的結束碼完成。\n\n## 輸出處理：串流、截斷、產出物溢出\n\nPTY 和非 PTY 路徑都使用 `OutputSink`。\n\n## OutputSink 語義\n\n- 在記憶體中保持 UTF-8 安全的尾端緩衝區（`DEFAULT_MAX_BYTES`，目前為 50KB），\n- 追蹤已看到的總位元組/行數，\n- 如果產出物路徑存在且輸出溢出（或檔案已啟用），將完整串流寫入產出物檔案，\n- 當記憶體閾值溢出時，將記憶體中的緩衝區修剪至尾端（UTF-8 邊界安全），\n- 溢出/檔案溢出發生時標記 `truncated`。\n\n`dump()` 回傳：\n\n- `output`（可能帶有附註前綴），\n- `truncated`，\n- `totalLines/totalBytes`，\n- `outputLines/outputBytes`，\n- 如果產出物檔案已啟用則包含 `artifactId`。\n\n### 長輸出注意事項\n\n`OutputSink` 中的執行時截斷是基於位元組閾值的（預設 50KB）。此程式碼路徑不強制執行嚴格的 2000 行上限。\n\n## 即時工具更新\n\n對於非 PTY 執行，`BashTool` 使用獨立的 `TailBuffer` 進行部分更新，並在命令執行時發出 `onUpdate` 快照。\n\n對於 PTY 執行，即時渲染由自訂 UI 覆蓋層處理，而非透過 `onUpdate` 文字區塊。\n\n## 結果塑形、中繼資料與錯誤映射\n\n執行後：\n\n1. `cancelled` 處理：\n   - 如果中止信號已中止 -> 拋出 `ToolAbortError`（中止語義），\n   - 否則 -> 拋出 `ToolError`（視為工具失敗）。\n2. PTY `timedOut` -> 拋出 `ToolError`。\n3. 對最終輸出文字套用 head/tail 篩選器（`applyHeadTail`，先 head 後 tail）。\n4. 空輸出變為 `(no output)`。\n5. 透過 `toolResult(...).truncationFromSummary(result, { direction: \"tail\" })` 附加截斷中繼資料。\n6. 結束碼映射：\n   - 缺少結束碼 -> `ToolError(\"... missing exit status\")`\n   - 非零結束碼 -> `ToolError(\"... Command exited with code N\")`\n   - 零結束碼 -> 成功結果。\n\n成功酬載結構：\n\n- `content`：文字輸出，\n- `details.meta.truncation`（截斷時），包括：\n  - `direction`、`truncatedBy`、總計/輸出的行數+位元組數，\n  - `shownRange`，\n  - 可用時包含 `artifactId`。\n\n由於內建工具以 `wrapToolWithMetaNotice()` 包裝，截斷通知文字會自動附加到最終文字內容中（例如：`Full: artifact://<id>`）。\n\n## 渲染路徑\n\n## 工具呼叫渲染器（`bashToolRenderer`）\n\n`bashToolRenderer` 用於工具呼叫訊息（`toolCall` / `toolResult`）：\n\n- 摺疊模式顯示視覺行截斷的預覽，\n- 展開模式顯示目前所有可用的輸出文字，\n- 警告行在截斷時包含截斷原因和 `artifact://<id>`，\n- 逾時值（來自引數）顯示在頁尾中繼資料行中。\n\n### 注意事項：完整產出物展開\n\n`BashRenderContext` 有 `isFullOutput`，但目前的渲染器上下文建構器並未為 bash 工具結果設定它。展開檢視仍然使用結果內容中已有的文字（尾端/截斷的輸出），除非其他呼叫者提供完整的產出物內容。\n\n## 使用者 bang 命令元件（`BashExecutionComponent`）\n\n`BashExecutionComponent` 用於互動模式中使用者的 `!` 命令（非模型工具呼叫）：\n\n- 即時串流區塊，\n- 摺疊預覽保留最後 20 個邏輯行，\n- 每行最多 4000 字元的行限制，\n- 存在中繼資料時顯示截斷 + 產出物警告，\n- 分別標記取消/錯誤/結束狀態。\n\n此元件由 `CommandController.handleBashCommand()` 連接，並由 `AgentSession.executeBash()` 提供資料。\n\n## 各模式的行為差異\n\n| 介面                        | 進入路徑                                            | PTY 資格                                                         | 即時輸出 UX                                                           | 錯誤呈現                                  |\n| ------------------------------ | ----------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------ |\n| 互動式工具呼叫          | `BashTool.execute`                                    | 是，當 `bash.virtualTerminal=on` 且 UI 存在且 `PI_NO_PTY!=1` 時 | PTY 覆蓋層（互動式）或串流尾端更新                       | 工具錯誤變為 `toolResult.isError`          |\n| 列印模式工具呼叫           | `BashTool.execute`                                    | 否（無 UI 上下文）                                                   | 無 TUI 覆蓋層；輸出出現在事件串流/最終助理文字流中 | 相同的工具錯誤映射                          |\n| RPC 工具呼叫（agent 工具）  | `BashTool.execute`                                    | 通常無 UI -> 非 PTY                                             | 結構化工具事件/結果                                           | 相同的工具錯誤映射                          |\n| 互動式 bang 命令（`!`） | `AgentSession.executeBash` + `BashExecutionComponent` | 否（直接使用執行器）                                          | 專用 bash 執行元件                                       | 控制器捕獲例外並顯示 UI 錯誤 |\n| RPC `bash` 命令             | `rpc-mode` -> `session.executeBash`                   | 否                                                                   | 直接回傳 `BashResult`                                            | 消費者處理回傳的欄位                 |\n\n## 操作注意事項\n\n- 攔截器僅在建議的工具目前在上下文中可用時才封鎖命令。\n- 如果產出物配置失敗，截斷仍會發生但沒有可用的 `artifact://` 反向參照。\n- Shell 工作階段快取在此模組中沒有明確的淘汰機制；生命週期為程序範圍。\n- PTY 和非 PTY 的逾時介面不同：\n  - PTY 公開明確的 `timedOut` 結果欄位，\n  - 非 PTY 將逾時映射為 `cancelled + annotation` 摘要。\n\n## 實作檔案\n\n- [`src/tools/bash.ts`](../../packages/coding-agent/src/tools/bash.ts) — 工具進入點、正規化/攔截、PTY/非 PTY 選擇、結果/錯誤映射、bash 工具渲染器。\n- [`src/tools/bash-normalize.ts`](../../packages/coding-agent/src/tools/bash-normalize.ts) — 命令正規化與執行後的 head/tail 篩選。\n- [`src/tools/bash-interceptor.ts`](../../packages/coding-agent/src/tools/bash-interceptor.ts) — 攔截器規則比對與封鎖命令訊息。\n- [`src/exec/bash-executor.ts`](../../packages/coding-agent/src/exec/bash-executor.ts) — 非 PTY 執行器、shell 工作階段重用、取消連接、輸出 sink 整合。\n- [`src/tools/bash-interactive.ts`](../../packages/coding-agent/src/tools/bash-interactive.ts) — PTY 執行環境、覆蓋層 UI、輸入正規化、非互動式環境預設值。\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts) — `OutputSink` 截斷/產出物溢出與摘要中繼資料。\n- [`src/tools/output-utils.ts`](../../packages/coding-agent/src/tools/output-utils.ts) — 產出物配置輔助函式與串流尾端緩衝區。\n- [`src/tools/output-meta.ts`](../../packages/coding-agent/src/tools/output-meta.ts) — 截斷中繼資料結構 + 通知注入包裝器。\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts) — 工作階段層級 `executeBash`、訊息記錄、中止生命週期。\n- [`src/modes/components/bash-execution.ts`](../../packages/coding-agent/src/modes/components/bash-execution.ts) — 互動式 `!` 命令執行元件。\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts) — 互動式 `!` 命令 UI 串流/更新完成的連接。\n- [`src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts) — RPC `bash` 和 `abort_bash` 命令介面。\n- [`src/internal-urls/artifact-protocol.ts`](../../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — `artifact://<id>` 解析。\n",
	"zh-tw/runtime-tools/context-command.md": "---\ntitle: F5 XC 環境上下文\ndescription: 將 xcsh 連線至 F5 Distributed Cloud 租戶——建立、切換及管理驗證環境上下文。\nsidebar:\n  order: 1\n  label: F5 XC 環境上下文\ni18n:\n  sourceHash: a9cccbc338f0\n  translator: machine\n---\n\n# F5 XC 環境上下文\n\nxcsh 透過**環境上下文（contexts）**連線至 F5 Distributed Cloud——環境上下文是具名的憑證集合，將租戶 URL、API 令牌和命名空間繫結在一起。如果您曾使用過 `kubectl config use-context` 或 `kubectx`，其工作流程完全相同：建立環境上下文、透過名稱在它們之間切換，並使用 `-` 快速切回上一個。\n\n## 入門指南\n\n### 1. 建立您的第一個環境上下文\n\n您需要從 F5 XC 控制台取得三項資訊：租戶 URL、API 令牌，以及選擇性的命名空間。\n\n```\n/context create production https://acme.console.ves.volterra.io p12k3-your-api-token\n```\n\n```\nContext 'production' created. Use /context activate production to switch to it.\n```\n\n如果您偏好逐步引導提示，也可以使用引導式精靈：\n\n```\n/context wizard\n```\n\n### 2. 啟用環境上下文\n\n```\n/context production\n```\n\n```\n╭─ production ─────────────────────────────────────────────────╮\n│ XCSH_TENANT     acme                                         │\n│ XCSH_API_URL    https://acme.console.ves.volterra.io         │\n│ XCSH_API_TOKEN  ...oken                                      │\n│ Status          Connected (312ms)                            │\n├─ Environment ────────────────────────────────────────────────┤\n│ XCSH_NAMESPACE  default                                      │\n╰──────────────────────────────────────────────────────────────╯\n```\n\n啟用後，xcsh 會將租戶憑證注入您的工作階段。代理程式現在可以進行 F5 XC API 呼叫，狀態列會顯示目前使用中的環境上下文。\n\n### 3. 新增更多環境上下文並在它們之間切換\n\n```\n/context create staging https://staging.console.ves.volterra.io p12k3-staging-token\n```\n\n透過名稱切換——不需要子命令動詞：\n\n```\n/context staging\n```\n\n切回上一個環境上下文（`cd -` 風格）：\n\n```\n/context -\n```\n\n呼叫 `/context -` 兩次會讓您回到起始位置。\n\n### 4. 檢視現有的環境上下文\n\n```\n/context\n```\n\n```\n  production           https://acme.console.ves.volterra.io\n* staging              https://staging.console.ves.volterra.io\n```\n\n`*` 標記目前使用中的環境上下文。\n\n## 日常命令\n\n| 命令 | 功能說明 |\n|---|---|\n| `/context` | 列出所有環境上下文 |\n| `/context <name>` | 切換至指定的環境上下文 |\n| `/context -` | 切換至上一個環境上下文 |\n| `/context show` | 顯示目前使用中的環境上下文詳細資訊（令牌已遮蔽） |\n| `/context status` | 顯示目前的驗證狀態 |\n\n## 環境上下文生命週期\n\n| 命令 | 功能說明 |\n|---|---|\n| `/context create <name> <url> <token> [namespace]` | 建立環境上下文 |\n| `/context delete <name> --confirm` | 刪除環境上下文（需要 `--confirm`） |\n| `/context rename <old> <new>` | 重新命名環境上下文 |\n| `/context validate <name>` | 測試憑證但不切換 |\n| `/context export [name] [--include-token]` | 匯出為 JSON（預設遮蔽令牌） |\n| `/context import <path-or-json> [--overwrite]` | 從檔案或行內 JSON 匯入 |\n| `/context wizard` | 引導式互動設定 |\n\n## 切換命名空間\n\n每個環境上下文都有一個預設命名空間。無需變更環境上下文即可切換命名空間：\n\n```\n/context namespace system\n```\n\nTab 自動補全會提供來自使用中租戶的命名空間名稱。\n\n## 環境上下文上的環境變數\n\n環境上下文可以攜帶額外的環境變數，這些變數會在啟用時注入您的工作階段。這對於不屬於憑證集合一部分的租戶專屬設定非常有用。\n\n```\n/context set CUSTOM_HEADER=x-acme-trace\n/context set LOG_LEVEL=debug\n/context env list\n/context unset LOG_LEVEL\n```\n\n別名：`add` = `set`，`remove`/`clear` = `unset`。\n\n## Tab 自動補全\n\n輸入 `/context ` 後按下 Tab 鍵。下拉選單會顯示：\n\n1. **環境上下文名稱**——附帶租戶 URL 提示，讓您能區分不同租戶\n2. **`-`**——當您之前有切換過時出現，顯示您將切換至哪個環境上下文\n3. **子命令**——`list`、`create`、`delete` 等\n\n環境上下文名稱會優先顯示，因為切換是最常見的操作。\n\n子命令層級的補全同樣有效：`/context activate <Tab>` 會補全環境上下文名稱，`/context namespace <Tab>` 會補全命名空間，`/context unset <Tab>` 會補全已知的環境變數鍵。\n\n## 命名規則\n\n環境上下文名稱必須為 1-64 個字元：字母、數字、連字號、底線。\n\n與子命令衝突的名稱會被拒絕：\n\n```\n/context create list https://example.com tok\n```\n\n```\nError: Context name 'list' conflicts with a /context subcommand. Choose a different name.\n```\n\n完整的保留字集合：`list`、`show`、`status`、`create`、`delete`、`rename`、`namespace`、`env`、`set`、`unset`、`add`、`remove`、`clear`、`activate`、`validate`、`export`、`import`、`wizard`、`help`。比較時不區分大小寫。\n\n## 環境變數覆寫\n\n如果在啟動 xcsh 之前，您的 shell 環境中已設定了 `XCSH_API_URL` 和 `XCSH_API_TOKEN`，它們將優先於任何環境上下文。這在 CI/CD 流水線或一次性工作階段中非常有用，讓您無需建立持久性的環境上下文。\n\n在此模式下執行時，`/context` 會以 `(via env vars)` 標籤顯示來源為環境變數的憑證。\n\n## 上一個環境上下文的行為\n\n- **工作階段範圍**：上一個環境上下文會在您重新啟動 xcsh 時重設，不會持久化到磁碟。\n- **來回切換**：`/context -` 執行兩次會讓您回到起始位置。\n- **安全應對變更操作**：如果您刪除了上一個環境上下文，指標會被清除。如果您重新命名它，指標會跟隨新名稱。\n- **重複啟用為無操作**：當已在 `production` 上時執行 `/context production` 不會重設上一個環境上下文的指標。\n\n## 設計慣例\n\n`/context` 的使用者體驗遵循：\n\n- **kubectx**：`kubectx <name>` 用於切換，`kubectx -` 用於切回上一個，單獨的 `kubectx` 用於列出\n- **kubectl**：`kubectl config use-context` 用於明確形式\n- **Shell**：`cd -` / `OLDPWD` 用於上一個目錄追蹤\n",
	"zh-tw/runtime-tools/custom-tools.md": "---\ntitle: 自訂工具\ndescription: 用於擴展代理程式的自訂工具註冊、架構定義與執行管線。\nsidebar:\n  order: 4\n  label: 自訂工具\ni18n:\n  sourceHash: 5f4a441fc2e2\n  translator: machine\n---\n\n# 自訂工具\n\n自訂工具是可由模型呼叫的函式，插入與內建工具相同的工具執行管線。\n\n自訂工具是一個 TypeScript/JavaScript 模組，匯出一個工廠函式。工廠函式接收一個宿主 API（`CustomToolAPI`），並回傳單一工具或工具陣列。\n\n## 這是什麼（以及不是什麼）\n\n- **自訂工具**：可在一個回合中由模型呼叫（`execute` + TypeBox 架構）。\n- **擴充功能**：生命週期/事件框架，可以註冊工具並攔截/修改事件。\n- **鉤子**：外部前置/後置命令腳本。\n- **技能**：靜態指引/情境套件，非可執行的工具程式碼。\n\n若需要模型直接呼叫程式碼，請使用自訂工具。\n\n## 目前程式碼中的整合路徑\n\n目前有兩種活躍的整合方式：\n\n1. **SDK 提供的自訂工具**（`options.customTools`）\n   - 透過 `CustomToolAdapter` 或擴充功能包裝器封裝為代理程式工具。\n   - 在 SDK 啟動時，一律包含於初始啟用工具集中。\n\n2. **透過載入器 API 進行檔案系統探索的模組**（`discoverAndLoadCustomTools` / `loadCustomTools`）\n   - 以程式庫 API 形式公開於 `src/extensibility/custom-tools/loader.ts`。\n   - 宿主程式碼可呼叫這些 API，從設定/提供者/外掛路徑中探索並載入工具模組。\n\n```text\n模型工具呼叫流程\n\nLLM 工具呼叫\n   │\n   ▼\n工具登錄檔（內建工具 + 自訂工具轉接器）\n   │\n   ▼\nCustomTool.execute(toolCallId, params, onUpdate, ctx, signal)\n   │\n   ├─ onUpdate(...)  -> 串流部分結果\n   └─ return result  -> 最終工具內容/詳情\n```\n\n## 探索位置（載入器 API）\n\n`discoverAndLoadCustomTools(configuredPaths, cwd, builtInToolNames)` 合併以下來源：\n\n1. 功能提供者（`toolCapability`），包含：\n   - 原生 OMP 設定（`~/.xcsh/agent/tools`、`.xcsh/tools`）\n   - Claude 設定（`~/.claude/tools`、`.claude/tools`）\n   - Codex 設定（`~/.codex/tools`、`.codex/tools`）\n   - Claude 市集外掛快取提供者\n2. 已安裝的外掛清單（透過外掛載入器從 `~/.xcsh/plugins/node_modules/*`）\n3. 傳遞給載入器的明確設定路徑\n\n### 重要行為\n\n- 重複的已解析路徑會被去重複。\n- 工具名稱衝突會針對內建工具及已載入的自訂工具進行拒絕。\n- `.md` 與 `.json` 檔案會被某些提供者作為工具元資料探索，但可執行模組載入器會拒絕將其作為可執行工具。\n- 相對設定路徑從 `cwd` 解析；`~` 會展開。\n\n## 模組契約\n\n自訂工具模組必須匯出一個函式（建議使用預設匯出）：\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = (pi) => ({\n name: \"repo_stats\",\n label: \"Repo Stats\",\n description: \"Counts tracked TypeScript files\",\n parameters: pi.typebox.Type.Object({\n  glob: pi.typebox.Type.Optional(pi.typebox.Type.String({ default: \"**/*.ts\" })),\n }),\n\n async execute(toolCallId, params, onUpdate, ctx, signal) {\n  onUpdate?.({\n   content: [{ type: \"text\", text: \"Scanning files...\" }],\n   details: { phase: \"scan\" },\n  });\n\n  const result = await pi.exec(\"git\", [\"ls-files\", params.glob ?? \"**/*.ts\"], { signal, cwd: pi.cwd });\n  if (result.killed) {\n   throw new Error(\"Scan was cancelled\");\n  }\n  if (result.code !== 0) {\n   throw new Error(result.stderr || \"git ls-files failed\");\n  }\n\n  const files = result.stdout.split(\"\\n\").filter(Boolean);\n  return {\n   content: [{ type: \"text\", text: `Found ${files.length} files` }],\n   details: { count: files.length, sample: files.slice(0, 10) },\n  };\n },\n\n onSession(event) {\n  if (event.reason === \"shutdown\") {\n   // cleanup resources if needed\n  }\n },\n});\n\nexport default factory;\n```\n\n工廠回傳型別：\n\n- `CustomTool`\n- `CustomTool[]`\n- `Promise<CustomTool | CustomTool[]>`\n\n## 傳遞給工廠的 API 介面（`CustomToolAPI`）\n\n來自 `types.ts` 與 `loader.ts`：\n\n- `cwd`：宿主工作目錄\n- `exec(command, args, options?)`：程序執行輔助函式\n- `ui`：UI 情境（在無頭模式下可為空操作）\n- `hasUI`：在非互動式流程中為 `false`\n- `logger`：共用檔案記錄器\n- `typebox`：注入的 `@sinclair/typebox`\n- `pi`：注入的 `@f5-sales-demo/xcsh` 匯出\n- `pushPendingAction(action)`：為隱藏的 `resolve` 工具註冊預覽動作（`docs/resolve-tool-runtime.md`）\n\n載入器以空操作 UI 情境啟動，並要求宿主程式碼在真實 UI 就緒時呼叫 `setUIContext(...)`。\n\n## 執行契約與型別定義\n\n`CustomTool.execute` 簽名：\n\n```ts\nexecute(toolCallId, params, onUpdate, ctx, signal)\n```\n\n- `params` 透過 `Static<TParams>` 從您的 TypeBox 架構靜態定型。\n- 執行前，代理程式迴圈會進行執行期參數驗證。\n- `onUpdate` 發出部分結果以供 UI 串流。\n- `ctx` 包含 session/模型狀態及 `abort()` 輔助函式。\n- `signal` 傳遞取消訊號。\n\n`CustomToolAdapter` 將此橋接至代理程式工具介面，並以正確的引數順序轉發呼叫。\n\n## 工具如何暴露給模型\n\n- 工具被封裝為 `AgentTool` 實例（`CustomToolAdapter` 或擴充功能包裝器）。\n- 它們按名稱插入 session 工具登錄檔。\n- 在 SDK 啟動時，自訂及擴充功能所註冊的工具會強制包含於初始啟用集合。\n- CLI `--tools` 目前僅驗證內建工具名稱；自訂工具的包含由探索/註冊路徑及 SDK 選項處理。\n\n## 渲染鉤子\n\n選用的渲染鉤子：\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\nTUI 中的執行期行為：\n\n- 若鉤子存在，工具輸出會在 `Box` 容器內渲染。\n- `renderResult` 接收 `{ expanded, isPartial, spinnerFrame? }`。\n- 渲染器錯誤會被捕捉並記錄；UI 會退回預設文字渲染。\n\n## Session/狀態處理\n\n選用的 `onSession(event, ctx)` 接收 session 生命週期事件，包括：\n\n- `start`、`switch`、`branch`、`tree`、`shutdown`\n- `auto_compaction_start`、`auto_compaction_end`\n- `auto_retry_start`、`auto_retry_end`\n- `ttsr_triggered`、`todo_reminder`\n\n使用 `ctx.sessionManager` 在分支/session 情境變更時從歷史記錄重建狀態。\n\n## 失敗與取消語義\n\n### 同步/非同步失敗\n\n- 在 `execute` 中拋出例外（或被拒絕的 Promise）會被視為工具失敗。\n- 代理程式執行期將失敗轉換為帶有 `isError: true` 及錯誤文字內容的工具結果訊息。\n- 使用擴充功能包裝器時，`tool_result` 處理器可進一步改寫內容/詳情，甚至覆寫錯誤狀態。\n\n### 取消\n\n- 代理程式中止透過 `AbortSignal` 傳播至 `execute`。\n- 將 `signal` 轉發至子程序工作（`pi.exec(..., { signal })`）以實現協作式取消。\n- `ctx.abort()` 讓工具可請求中止目前的代理程式操作。\n\n### onSession 錯誤\n\n- `onSession` 錯誤會被捕捉並記錄為警告；它們不會導致 session 崩潰。\n\n## 需要納入設計的實際限制\n\n- 工具名稱在啟用的登錄檔中必須是全域唯一的。\n- 建議在 `details` 中使用確定性、符合架構的輸出，以利渲染器/狀態重建。\n- 使用 `pi.hasUI` 防護 UI 用法。\n- 將工具目錄中的 `.md`/`.json` 視為元資料，而非可執行模組。\n",
	"zh-tw/runtime-tools/notebook-tool-runtime.md": "---\ntitle: Notebook 工具執行時期內部機制\ndescription: >-\n  Jupyter notebook tool runtime with cell execution, kernel lifecycle, and\n  output rendering.\nsidebar:\n  order: 2\n  label: Notebook 工具\ni18n:\n  sourceHash: c1bafcb245e4\n  translator: machine\n---\n\n# Notebook 工具執行時期內部機制\n\n本文件描述目前 `notebook` 工具的實作方式，以及它與核心支援的 Python 執行時期之間的關係。\n\n關鍵區別：**`notebook` 是一個 JSON notebook 編輯器，而非 notebook 執行器**。它直接編輯 `.ipynb` 的儲存格來源；它不會啟動或與 Python 核心通訊。\n\n## 實作檔案\n\n- [`src/tools/notebook.ts`](../../packages/coding-agent/src/tools/notebook.ts)\n- [`src/ipy/executor.ts`](../../packages/coding-agent/src/ipy/executor.ts)\n- [`src/ipy/kernel.ts`](../../packages/coding-agent/src/ipy/kernel.ts)\n- [`src/session/streaming-output.ts`](../../packages/coding-agent/src/session/streaming-output.ts)\n- [`src/tools/python.ts`](../../packages/coding-agent/src/tools/python.ts)\n\n## 1) 執行時期邊界：編輯 vs 執行\n\n## `notebook` 工具 (`src/tools/notebook.ts`)\n\n- 支援對 `.ipynb` 檔案執行 `action: edit | insert | delete`。\n- 相對於工作階段 CWD 解析路徑（`resolveToCwd`）。\n- 載入 notebook JSON，驗證 `cells` 陣列，驗證 `cell_index` 範圍。\n- 在記憶體中套用來源編輯，並以 `JSON.stringify(notebook, null, 1)` 寫回完整的 notebook JSON。\n- 回傳文字摘要 + 結構化 `details`（`action`、`cellIndex`、`cellType`、`totalCells`、`cellSource`）。\n\n此工具中不存在核心生命週期：\n\n- 沒有閘道器取得\n- 沒有核心工作階段 ID\n- 沒有 `execute_request`\n- 沒有來自核心通道的串流區塊\n- 沒有豐富顯示擷取（`image/png`、JSON 顯示、狀態 MIME）\n\n## 類 Notebook 執行路徑 (`src/tools/python.ts` + `src/ipy/*`)\n\n當代理需要執行儲存格式的 Python 程式碼（循序儲存格、持久狀態、豐富顯示）時，會透過 **`python` 工具**，而非 `notebook`。\n\n核心模式、重啟/取消行為、區塊串流以及輸出產出物截斷都存在於該路徑中。\n\n## 2) Notebook 儲存格處理語意（`notebook` 工具）\n\n## 來源正規化\n\n`content` 被分割為帶有換行符保留的 `source: string[]`：\n\n- 每個非最終行保留尾隨 `\\n`\n- 最終行沒有強制的尾隨換行符\n\n這符合 notebook JSON 慣例，並避免後續編輯時意外的行串接。\n\n## 動作行為\n\n- `edit`\n  - 替換 `cells[cell_index].source`\n  - 保留現有的 `cell_type`\n- `insert`\n  - 在 `[0..cellCount]` 處插入\n  - `cell_type` 預設為 `code`\n  - 程式碼儲存格初始化 `execution_count: null` 和 `outputs: []`\n  - markdown 儲存格僅初始化 `metadata` + `source`\n- `delete`\n  - 移除 `cells[cell_index]`\n  - 在 details 中回傳已移除的 `source` 以供渲染器預覽\n\n## 錯誤表面\n\n以下情況會拋出硬性失敗：\n\n- 缺少 notebook 檔案\n- 無效的 JSON\n- 缺少或非陣列的 `cells`\n- 超出範圍的索引（insert 和非 insert 有不同的有效範圍）\n- `edit`/`insert` 缺少 `content`\n\n這些會在上游成為 `Error:` 工具回應；渲染器使用 notebook 路徑 + 格式化的錯誤文字。\n\n## 3) 核心工作階段語意（實際存在之處）\n\n核心語意實作於 `executePython` / `PythonKernel` 中，適用於 `python` 工具。\n\n## 模式\n\n`PythonKernelMode`：\n\n- `session`（預設）\n  - 核心快取於 `kernelSessions` map 中\n  - 最多 4 個工作階段；溢出時驅逐最舊的\n  - 每 30 秒清理閒置/已終止的，5 分鐘後逾時\n  - 每個工作階段佇列序列化執行（`session.queue`）\n- `per-call`\n  - 為請求建立核心\n  - 執行\n  - 總是在 `finally` 中關閉核心\n\n## 重設行為\n\n`python` 工具僅在多儲存格呼叫的第一個儲存格傳遞 `reset`；後續儲存格總是以 `reset: false` 執行。\n\n## 核心終止 / 重啟 / 重試\n\n在 session 模式（`withKernelSession`）中：\n\n- 透過心跳（每 5 秒 `kernel.isAlive()` 檢查）或執行失敗偵測已終止的核心。\n- 執行前的終止狀態觸發 `restartKernelSession`。\n- 執行時期當機路徑重試一次：重啟核心，重新執行處理程式。\n- 同一工作階段中 `restartCount > 1` 會拋出 `Python kernel restarted too many times in this session`。\n\n啟動重試行為：\n\n- 共享閘道器核心建立在 HTTP 5xx 的 `SharedGatewayCreateError` 上重試一次。\n\n資源耗盡恢復：\n\n- 偵測 `EMFILE`/`ENFILE`/「Too many open files」類型的失敗\n- 清除已追蹤的工作階段\n- 呼叫 `shutdownSharedGateway()`\n- 重試核心工作階段建立一次\n\n## 4) 環境/工作階段變數注入\n\n核心啟動從執行器接收可選的 env map：\n\n- `PI_SESSION_FILE`（工作階段狀態檔案路徑）\n- `ARTIFACTS`（產出物目錄）\n\n`PythonKernel.#initializeKernelEnvironment(...)` 接著在核心內執行初始化腳本以：\n\n- `os.chdir(cwd)`\n- 將 env 項目注入 `os.environ`\n- 如果缺少，將 cwd 前置到 `sys.path`\n\n影響：\n\n- 讀取工作階段或產出物上下文的前導輔助程式依賴 Python 程序狀態中的這些環境變數。\n\n## 5) 串流/區塊與顯示處理（核心支援路徑）\n\n核心客戶端按每次執行處理 Jupyter 協定訊息：\n\n- `stream` -> 文字區塊至 `onChunk`\n- `execute_result` / `display_data` ->\n  - 顯示文字依 MIME 優先順序選擇：`text/markdown` > `text/plain` > 轉換的 `text/html`\n  - 結構化輸出單獨擷取：\n    - `application/json` -> `{ type: \"json\" }`\n    - `image/png` -> `{ type: \"image\" }`\n    - `application/x-xcsh-status` -> `{ type: \"status\" }`（不發出文字）\n- `error` -> 回溯文字推送至區塊串流 + 結構化錯誤中繼資料\n- `input_request` -> 發出 stdin 警告文字，傳送空的 `input_reply`，標記已請求 stdin\n- 完成等待 `execute_reply` 和核心 `status=idle` 兩者\n\n取消/逾時：\n\n- 中止信號觸發 `interrupt()`（REST `/interrupt` + 控制通道 `interrupt_request`）\n- 結果標記 `cancelled=true`\n- 逾時路徑在輸出中附加 `Command timed out after <n> seconds`\n\n## 6) 截斷與產出物行為\n\n`src/session/streaming-output.ts` 中的 `OutputSink` 被核心執行路徑（`executeWithKernel`）使用：\n\n- 清理每個區塊（`sanitizeText`）\n- 追蹤總行數/輸出行數和位元組數\n- 可選的產出物溢出檔案（`artifactPath`、`artifactId`）\n- 當記憶體內緩衝區超過閾值（`DEFAULT_MAX_BYTES`，除非被覆寫）時：\n  - 標記為已截斷\n  - 在記憶體中保留尾部位元組（UTF-8 安全邊界）\n  - 可將完整串流溢出至產出物接收器\n\n`dump()` 回傳：\n\n- 可見輸出文字（可能經尾部截斷）\n- 截斷旗標 + 計數\n- 產出物 ID（用於 `artifact://<id>` 參考）\n\n`python` 工具將此中繼資料轉換為結果截斷通知和 TUI 警告。\n\n`notebook` 工具**不**使用 `OutputSink`；它沒有串流/產出物截斷管線，因為它不執行程式碼。\n\n## 7) 渲染器假設與格式化\n\n## Notebook 渲染器（`notebookToolRenderer`）\n\n- 呼叫檢視：帶有動作 + notebook 路徑 + 儲存格/類型中繼資料的狀態行\n- 結果檢視：\n  - 成功摘要衍生自 `details`\n  - `cellSource` 透過 `renderCodeCell` 渲染\n  - markdown 儲存格設定語言提示 `markdown`；其他儲存格沒有明確的語言覆寫\n  - 折疊的程式碼預覽限制為 `PREVIEW_LIMITS.COLLAPSED_LINES * 2`\n  - 透過共享渲染選項支援展開模式\n  - 使用以寬度 + 展開狀態為鍵的渲染快取\n\n錯誤渲染假設：\n\n- 如果第一個文字內容以 `Error:` 開頭，渲染器將其格式化為 notebook 錯誤區塊。\n\n## Python 渲染器（用於實際執行輸出）\n\n核心支援的執行渲染預期：\n\n- 每個儲存格的狀態轉換（`pending/running/complete/error`）\n- 可選的結構化狀態事件區段\n- 可選的 JSON 輸出樹\n- 截斷警告 + 可選的 `artifact://<id>` 指標\n\n此渲染器行為與 `notebook` JSON 編輯結果無關，只是兩者共用共享的 TUI 基本元件。\n\n## 8) 與純 Python 工具行為的差異\n\n如果「純 Python 工具」指的是 `python` 執行路徑：\n\n- `python` 在核心中執行程式碼，依模式持久化狀態，串流區塊，擷取豐富顯示，處理中斷/逾時，並支援輸出截斷/產出物。\n- `notebook` 僅執行確定性的 notebook JSON 變更；沒有執行、沒有核心狀態、沒有區塊串流、沒有顯示輸出、沒有產出物管線。\n\n如果工作流程需要兩者：\n\n1. 使用 `notebook` 編輯 notebook 來源\n2. 透過 `python` 執行程式碼儲存格（手動傳遞程式碼），而非透過 `notebook`\n\n目前的實作不提供單一工具同時變更 `.ipynb` 並透過核心上下文執行 notebook 儲存格。\n",
	"zh-tw/runtime-tools/resolve-tool-runtime.md": "---\ntitle: Resolve 工具執行時內部機制\ndescription: >-\n  Resolve tool runtime for file path resolution, content fetching, and URL-based\n  resource access.\nsidebar:\n  order: 3\n  label: Resolve 工具\ni18n:\n  sourceHash: 06e8be8c5a3c\n  translator: machine\n---\n\n# Resolve 工具執行時內部機制\n\n本文件說明預覽/套用工作流程在 coding-agent 中的建模方式，以及自訂工具如何透過 `pushPendingAction` 參與其中。\n\n## 範圍與關鍵檔案\n\n- [`src/tools/resolve.ts`](../../packages/coding-agent/src/tools/resolve.ts)\n- [`src/tools/pending-action.ts`](../../packages/coding-agent/src/tools/pending-action.ts)\n- [`src/tools/ast-edit.ts`](../../packages/coding-agent/src/tools/ast-edit.ts)\n- [`src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`src/extensibility/custom-tools/loader.ts`](../../packages/coding-agent/src/extensibility/custom-tools/loader.ts)\n- [`src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n\n## `resolve` 的功能\n\n`resolve` 是一個隱藏工具，用於完成待處理的預覽動作。\n\n- `action: \"apply\"` 會對待處理動作執行 `apply(reason)` 並持久化變更。\n- `action: \"discard\"` 會呼叫 `reject(reason)`（如果有提供的話）；否則以預設的「Discarded」訊息捨棄該動作。\n\n如果沒有待處理的動作，`resolve` 會失敗並顯示：\n\n- `No pending action to resolve. Nothing to apply or discard.`\n\n## 待處理動作是堆疊（後進先出）\n\n待處理動作儲存在 `PendingActionStore` 中，以推入/彈出堆疊的方式運作：\n\n- `push(action)` 將新的待處理動作新增到頂端。\n- `peek()` 檢視目前頂端的動作。\n- `pop()` 移除並回傳頂端的動作。\n- `hasPending` 指示堆疊是否非空。\n\n`resolve` 總是先消耗**最頂端**的待處理動作（`pop()`），因此多個產生預覽的工具會按照註冊的反向順序進行解析。\n\n## 內建產生器範例（`ast_edit`）\n\n`ast_edit` 會先預覽結構替換。當預覽包含替換項目且尚未套用時，它會推入一個待處理動作，其中包含：\n\n- label（人類可讀的摘要）\n- `sourceToolName`（`ast_edit`）\n- `apply(reason: string)` 回呼函式，以 `dryRun: false` 重新執行 AST 編輯\n\n`resolve(action=\"apply\", reason=\"...\")` 會將 `reason` 傳入此回呼函式。\n\n## 自訂工具：`pushPendingAction`\n\n自訂工具可以透過 `CustomToolAPI.pushPendingAction(...)` 註冊與 resolve 相容的待處理動作。\n\n`CustomToolPendingAction`：\n\n- `label: string`（必要）\n- `apply(reason: string): Promise<AgentToolResult<unknown>>`（必要）— 在套用時呼叫；`reason` 是傳遞給 `resolve` 的字串\n- `reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>`（選用）— 在捨棄時呼叫；如果有提供回傳值，則取代預設的「Discarded」訊息\n- `details?: unknown`（選用）\n- `sourceToolName?: string`（選用，預設為 `\"custom_tool\"`）\n\n### 最小使用範例\n\n```ts\nimport type { CustomToolFactory } from \"@f5-sales-demo/xcsh\";\n\nconst factory: CustomToolFactory = pi => ({\n name: \"batch_rename_preview\",\n label: \"Batch Rename Preview\",\n description: \"Previews renames and defers commit to resolve\",\n parameters: pi.typebox.Type.Object({\n  files: pi.typebox.Type.Array(pi.typebox.Type.String()),\n }),\n\n async execute(_toolCallId, params) {\n  const previewSummary = `Prepared rename plan for ${params.files.length} files`;\n\n  pi.pushPendingAction({\n   label: `Batch rename: ${params.files.length} files`,\n   sourceToolName: \"batch_rename_preview\",\n   apply: async (reason) => {\n    // apply writes here\n    return {\n     content: [{ type: \"text\", text: `Applied batch rename. Reason: ${reason}` }],\n    };\n   },\n   reject: async (reason) => {\n    // optional: cleanup or notify on discard\n    return {\n     content: [{ type: \"text\", text: `Discarded batch rename. Reason: ${reason}` }],\n    };\n   },\n  });\n\n  return {\n   content: [{ type: \"text\", text: `${previewSummary}. Call resolve to apply or discard.` }],\n  };\n },\n});\n\nexport default factory;\n```\n\n## 執行時可用性與失敗情況\n\n`pushPendingAction` 由自訂工具載入器使用活動工作階段的 `PendingActionStore` 進行連接。\n\n如果執行時沒有待處理動作儲存區，`pushPendingAction` 會拋出：\n\n- `Pending action store unavailable for custom tools in this runtime.`\n\n## 工具選擇行為\n\n當 `PendingActionStore.hasPending` 為 true 時，代理執行時會將工具選擇偏向 `resolve`，以確保待處理的預覽在正常工具流程繼續之前被明確地完成。\n\n## 開發者指引\n\n- 僅對應支援明確套用/捨棄的破壞性或高影響操作使用待處理動作。\n- 保持 `label` 簡潔且具體；它會顯示在 resolve 渲染器的輸出中。\n- 確保 `apply(reason)` 具有確定性且足夠冪等，適合一次性執行；`reason` 僅供參考，不應改變行為。\n- 當捨棄操作需要清理（暫存狀態、鎖定、通知）時，請實作 `reject(reason)`；對於預設訊息已足夠的無狀態預覽，則可省略。\n- 如果您的工具可以暫存多個預覽，請記住後進先出語義：最後推入的動作會最先被解析。\n",
	"zh-tw/runtime-tools/slash-command-internals.md": "---\ntitle: 斜線指令內部機制\ndescription: 斜線指令系統內部機制，包含註冊、參數解析與執行分派。\nsidebar:\n  order: 5\n  label: 斜線指令\ni18n:\n  sourceHash: 2cbd44a3de87\n  translator: machine\n---\n\n# 斜線指令內部機制\n\n本文件說明在 `coding-agent` 中，斜線指令如何被探索、去重複、在互動模式中呈現，以及在提示時展開。\n\n## 實作檔案\n\n- [`src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n- [`src/capability/slash-command.ts`](../../packages/coding-agent/src/capability/slash-command.ts)\n- [`src/discovery/builtin.ts`](../../packages/coding-agent/src/discovery/builtin.ts)\n- [`src/discovery/claude.ts`](../../packages/coding-agent/src/discovery/claude.ts)\n- [`src/discovery/codex.ts`](../../packages/coding-agent/src/discovery/codex.ts)\n- [`src/discovery/claude-plugins.ts`](../../packages/coding-agent/src/discovery/claude-plugins.ts)\n- [`src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n- [`src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n\n## 1) 探索模型\n\n斜線指令是一種能力（`id: \"slash-commands\"`），以指令名稱作為索引鍵（`key: cmd => cmd.name`）。\n\n能力登錄表載入所有已註冊的提供者，依提供者優先順序遞減排序，並以**先進者優先**的語義依索引鍵去重複。\n\n### 提供者優先順序\n\n目前的斜線指令提供者及其優先順序：\n\n1. `native`（OMP）— 優先順序 `100`\n2. `claude` — 優先順序 `80`\n3. `claude-plugins` — 優先順序 `70`\n4. `codex` — 優先順序 `70`\n\n平手行為：相同優先順序的提供者保留其註冊順序。目前的匯入順序會先註冊 `claude-plugins`，再註冊 `codex`，因此在名稱衝突時外掛指令優先於 codex 指令。\n\n### 名稱衝突行為\n\n對於 `slash-commands`，衝突嚴格依能力去重複規則解決：\n\n- 最高優先順序的項目保留在 `result.items` 中\n- 較低優先順序的重複項目僅保留在 `result.all` 中，並標記為 `_shadowed = true`\n\n此規則適用於跨提供者的情況，以及同一提供者回傳重複名稱的情況。\n\n### 檔案掃描行為\n\n提供者大多使用 `loadFilesFromDir(...)`，其目前行為如下：\n\n- 預設為非遞迴比對（`*.md`）\n- 使用原生 glob，並設定 `gitignore: true`、`hidden: false`\n- 讀取每個符合的檔案並將其轉換為 `SlashCommand`\n\n因此隱藏的檔案或目錄不會被載入，且被忽略的路徑會被跳過。\n\n## 2) 提供者專屬來源路徑與本地優先順序\n\n## `native` 提供者（`builtin.ts`）\n\n搜尋根目錄來自 `.xcsh` 目錄：\n\n- 專案：`<cwd>/.xcsh/commands/*.md`\n- 使用者：`~/.xcsh/agent/commands/*.md`\n\n`getConfigDirs()` 先回傳專案目錄，再回傳使用者目錄，因此在名稱衝突時**專案原生指令優先於使用者原生指令**。\n\n## `claude` 提供者（`claude.ts`）\n\n載入：\n\n- 使用者：`~/.claude/commands/*.md`\n- 專案：`<cwd>/.claude/commands/*.md`\n\n提供者先推入使用者項目，再推入專案項目，因此在此提供者內部的同名衝突中，**使用者 Claude 指令優先於專案 Claude 指令**。\n\n## `codex` 提供者（`codex.ts`）\n\n載入：\n\n- 使用者：`~/.codex/commands/*.md`\n- 專案：`<cwd>/.codex/commands/*.md`\n\n兩側均被載入，然後以使用者優先的順序展平，因此在衝突時**使用者 Codex 指令優先於專案 Codex 指令**。\n\nCodex 指令內容使用前置資料剝離（`parseFrontmatter`）解析，指令名稱可由前置資料中的 `name` 欄位覆寫；否則使用檔名。\n\n## `claude-plugins` 提供者（`claude-plugins.ts`）\n\n從 `~/.claude/plugins/installed_plugins.json` 載入外掛指令根目錄，然後掃描 `<pluginRoot>/commands/*.md`。\n\n排序遵循登錄表的迭代順序以及該 JSON 資料中每個外掛的條目順序，不進行額外的排序步驟。\n\n## 3) 實體化為執行時期的 `FileSlashCommand`\n\n`src/extensibility/slash-commands.ts` 中的 `loadSlashCommands()` 將能力項目轉換為提示時使用的 `FileSlashCommand` 物件。\n\n對每個指令執行以下操作：\n\n1. 解析前置資料與內文（`parseFrontmatter`）\n2. 描述來源：\n   - 若存在 `frontmatter.description` 則使用之\n   - 否則使用內文第一個非空行（修剪後，最多 60 個字元並加上 `...`）\n3. 保留解析後的內文作為可執行的範本內容\n4. 計算顯示來源字串，例如 `via Claude Code Project`\n\n前置資料解析的嚴重性等級依來源而異：\n\n- `native` 層級 -> 解析錯誤為 `fatal`\n- `user`/`project` 層級 -> 解析錯誤為 `warn`，並使用備用解析\n\n### 內嵌備用指令\n\n在檔案系統/提供者指令之後，若名稱尚不存在，則附加內嵌的指令範本（`EMBEDDED_COMMAND_TEMPLATES`）。\n\n目前的內嵌集來自 `src/task/commands.ts`，作為備用使用（`source: \"bundled\"`）。\n\n## 4) 互動模式：指令清單的來源\n\n互動模式結合多個指令來源，用於自動補全與指令路由。\n\n在建構時，它從以下來源建立待處理指令清單：\n\n- 內建指令（`BUILTIN_SLASH_COMMANDS`，包含特定指令的參數補全與行內提示）\n- 擴充功能已註冊的斜線指令（`extensionRunner.getRegisteredCommands(...)`）\n- TypeScript 自訂指令（`session.customCommands`），映射至斜線指令標籤\n- 啟用 `skills.enableSkillCommands` 時的選用技能指令（`/skill:<name>`）\n\n然後 `init()` 呼叫 `refreshSlashCommandState(...)` 以載入檔案型指令，並安裝一個包含以下內容的 `CombinedAutocompleteProvider`：\n\n- 上述待處理指令\n- 已探索的檔案型指令\n\n`refreshSlashCommandState(...)` 也會更新 `session.setSlashCommands(...)`，使提示展開使用相同的已探索檔案指令集。\n\n### 重新整理生命週期\n\n斜線指令狀態的重新整理時機：\n\n- 在互動模式初始化期間\n- `/move` 變更工作目錄後（`handleMoveCommand` 呼叫 `resetCapabilities()`，再呼叫 `refreshSlashCommandState(newCwd)`）\n\n指令目錄沒有持續的檔案監視器。\n\n### 其他呈現方式\n\n擴充功能儀表板也會載入 `slash-commands` 能力，並顯示作用中與被遮蔽的指令條目，包括 `_shadowed` 重複項目。\n\n## 5) 提示管線中的位置\n\n`AgentSession.prompt(...)` 的斜線指令處理順序（當 `expandPromptTemplates !== false` 時）：\n\n1. **擴充功能指令**（`#tryExecuteExtensionCommand`）  \n   若 `/name` 符合擴充功能已註冊的指令，處理器立即執行，提示隨即返回。\n2. **TypeScript 自訂指令**（`#tryExecuteCustomCommand`）  \n   僅作為邊界：若符合，則執行並可能返回：\n   - `string` -> 以該字串替換提示文字\n   - `void/undefined` -> 視為已處理；不觸發 LLM 提示\n3. **檔案型斜線指令**（`expandSlashCommand`）  \n   若文字仍以 `/` 開頭，嘗試展開 markdown 指令。\n4. **提示範本**（`expandPromptTemplate`）  \n   在斜線/自訂指令處理之後套用。\n5. **傳遞**\n   - 閒置：提示立即傳送至代理\n   - 串流中：根據 `streamingBehavior` 將提示排入佇列作為引導/後續訊息\n\n這就是為何斜線指令展開位於提示範本展開之前，以及為何自訂指令可以在檔案指令比對之前轉換掉前導的斜線。\n\n## 6) 檔案型斜線指令的展開語義\n\n`expandSlashCommand(text, fileCommands)` 的行為：\n\n- 僅在文字以 `/` 開頭時執行\n- 從 `/` 後的第一個語彙單元解析指令名稱\n- 透過 `parseCommandArgs` 從剩餘文字解析參數\n- 在已載入的 `fileCommands` 中尋找完全符合的名稱\n- 若符合，則套用：\n  - 位置替換：`$1`、`$2`、...\n  - 彙總替換：`$ARGUMENTS` 與 `$@`\n  - 然後透過 `prompt.render` 進行範本渲染，使用 `{ args, ARGUMENTS, arguments }`\n- 若無符合項目，返回原始文字不變\n\n### `parseCommandArgs` 注意事項\n\n解析器是簡單的引號感知分割器：\n\n- 支援 `'單引號'` 和 `\"雙引號\"` 以保留空格\n- 去除引號分隔符號\n- 未實作反斜線跳脫規則\n- 未配對的引號不視為錯誤；解析器持續消耗至結尾\n\n## 7) 未知 `/...` 的行為\n\n未知的斜線輸入**不會**被核心斜線邏輯拒絕。\n\n若指令未被擴充功能/自訂/檔案層處理，`expandSlashCommand` 返回原始文字，字面上的 `/...` 提示繼續通過正常的提示範本展開與 LLM 傳遞流程。\n\n互動模式在 `InputController` 中另外對許多內建指令進行硬處理（例如 `/settings`、`/model`、`/mcp`、`/move`、`/exit`）。這些指令在 `session.prompt(...)` 之前被消耗，因此在該路徑中永遠不會到達檔案指令展開階段。\n\n## 8) 串流時與閒置時的差異\n\n## 閒置路徑\n\n- `session.prompt(\"/x ...\")` 執行指令管線，立即執行指令或直接傳送展開後的文字。\n\n## 串流路徑（`session.isStreaming === true`）\n\n- `prompt(...)` 仍會先執行擴充功能/自訂/檔案/範本的轉換\n- 然後需要 `streamingBehavior`：\n  - `\"steer\"` -> 排入中斷訊息（`agent.steer`）\n  - `\"followUp\"` -> 排入輪次後訊息（`agent.followUp`）\n- 若省略 `streamingBehavior`，提示將拋出錯誤\n\n### 重要的指令特定串流行為\n\n- 擴充功能指令即使在串流期間也會立即執行（不以文字形式排入佇列）。\n- `steer(...)`/`followUp(...)` 輔助方法會拒絕擴充功能指令（`#throwIfExtensionCommand`），以避免將必須同步執行的處理器指令文字排入佇列。\n- 壓縮佇列重播使用 `isKnownSlashCommand(...)` 來決定排入佇列的條目應透過 `session.prompt(...)`（針對已知斜線指令）還是原始的 steer/follow-up 方法重播。\n\n## 9) 錯誤處理與失敗面\n\n- 提供者載入失敗是隔離的；登錄表收集警告並繼續處理其他提供者。\n- 無效的斜線指令項目（缺少名稱/路徑/內容或層級無效）會被能力驗證丟棄。\n- 前置資料解析失敗：\n  - 原生指令：致命解析錯誤會向上冒泡\n  - 非原生指令：警告 + 備用鍵值解析\n- 擴充功能/自訂指令處理器的例外情況會被捕捉，並透過擴充功能錯誤頻道回報（或對無擴充功能執行器的自訂指令使用日誌記錄器備用），並視為已處理（不會發生意外的備用執行）。\n",
	"zh-tw/runtime-tools/task-agent-discovery.md": "---\ntitle: 任務代理探索與選擇\ndescription: 任務代理探索與選擇邏輯，用於將工作路由至專門的子代理類型。\nsidebar:\n  order: 6\n  label: 任務代理探索\ni18n:\n  sourceHash: 8cf42457c672\n  translator: machine\n---\n\n# 任務代理探索與選擇\n\n本文件描述任務子系統如何探索代理定義、合併多個來源，以及在執行時解析請求的代理。\n\n內容涵蓋目前已實作的執行時期行為，包括優先順序、無效定義處理，以及可能使代理實際上不可用的生成/深度限制。\n\n## 實作檔案\n\n- [`src/task/discovery.ts`](../../packages/coding-agent/src/task/discovery.ts)\n- [`src/task/agents.ts`](../../packages/coding-agent/src/task/agents.ts)\n- [`src/task/types.ts`](../../packages/coding-agent/src/task/types.ts)\n- [`src/task/index.ts`](../../packages/coding-agent/src/task/index.ts)\n- [`src/task/commands.ts`](../../packages/coding-agent/src/task/commands.ts)\n- [`src/prompts/agents/task.md`](../../packages/coding-agent/src/prompts/agents/task.md)\n- [`src/prompts/tools/task.md`](../../packages/coding-agent/src/prompts/tools/task.md)\n- [`src/discovery/helpers.ts`](../../packages/coding-agent/src/discovery/helpers.ts)\n- [`src/config.ts`](../../packages/coding-agent/src/config.ts)\n- [`src/task/executor.ts`](../../packages/coding-agent/src/task/executor.ts)\n\n---\n\n## 代理定義結構\n\n任務代理正規化為 `AgentDefinition`（`src/task/types.ts`）：\n\n- `name`、`description`、`systemPrompt`（載入有效代理時為必要欄位）\n- 選用的 `tools`、`spawns`、`model`、`thinkingLevel`、`output`\n- `source`：`\"bundled\" | \"user\" | \"project\"`\n- 選用的 `filePath`\n\n解析來自透過 `parseAgentFields()`（`src/discovery/helpers.ts`）處理的 frontmatter：\n\n- 缺少 `name` 或 `description` => 無效（`null`），呼叫端視為解析失敗\n- `tools` 接受 CSV 或陣列；若有提供，會自動加入 `submit_result`\n- `spawns` 接受 `*`、CSV 或陣列\n- 向後相容行為：若 `spawns` 缺失但 `tools` 包含 `task`，`spawns` 會變為 `*`\n- `output` 作為不透明的 schema 資料直接傳遞\n\n## 內建代理\n\n內建代理在建置時嵌入（`src/task/agents.ts`），使用文字匯入。\n\n`EMBEDDED_AGENT_DEFS` 定義：\n\n- `explore`、`plan`、`designer`、`reviewer` 來自提示詞檔案\n- `task` 和 `quick_task` 來自共用的 `task.md` 本體加上注入的 frontmatter\n\n載入路徑：\n\n1. `loadBundledAgents()` 使用 `parseAgent(..., \"bundled\", \"fatal\")` 解析嵌入的 markdown\n2. 結果快取在記憶體中（`bundledAgentsCache`）\n3. `clearBundledAgentsCache()` 僅供測試用的快取重設\n\n因為內建解析使用 `level: \"fatal\"`，格式錯誤的內建 frontmatter 會拋出例外，可能導致整個探索程序失敗。\n\n## 檔案系統與外掛探索\n\n`discoverAgents(cwd, home)`（`src/task/discovery.ts`）在附加內建定義之前，會合併來自多個位置的代理。\n\n### 探索輸入\n\n1. 來自 `getConfigDirs(\"agents\", { project: false })` 的使用者設定代理目錄\n2. 來自 `findAllNearestProjectConfigDirs(\"agents\", cwd)` 的最近專案代理目錄\n3. Claude 外掛根目錄（`listClaudePluginRoots(home)`）及其 `agents/` 子目錄\n4. 內建代理（`loadBundledAgents()`）\n\n### 實際來源順序\n\n來源家族順序來自 `getConfigDirs(\"\", { project: false })`，其衍生自 `src/config.ts` 中的 `priorityList`：\n\n1. `.xcsh`\n2. `.claude`\n3. `.codex`\n4. `.gemini`\n\n對於每個來源家族，探索順序為：\n\n1. 該來源的最近專案目錄（若找到的話）\n2. 該來源的使用者目錄\n\n在所有來源家族目錄之後，附加外掛 `agents/` 目錄（專案範圍的外掛優先，然後是使用者範圍的）。\n\n內建代理最後附加。\n\n### 重要注意事項：過時的註解與目前程式碼\n\n`discovery.ts` 的標頭註解仍然提到 `.pi`，且未提到 `.codex`/`.gemini`。實際執行時期順序由 `src/config.ts` 驅動，目前使用 `.xcsh`、`.claude`、`.codex`、`.gemini`。\n\n## 合併與衝突規則\n\n探索使用依精確 `agent.name` 的先到先贏去重：\n\n- 一個 `Set<string>` 追蹤已見過的名稱。\n- 載入的代理按目錄順序展平，僅在名稱未見過時保留。\n- 內建代理對照同一集合過濾，僅在名稱仍未見過時加入。\n\n影響：\n\n- 對於相同的來源家族，專案覆寫使用者。\n- 較高優先順序的來源家族覆寫較低的（`.xcsh` 在 `.claude` 之前，依此類推）。\n- 非內建代理覆寫同名的內建代理。\n- 名稱比對區分大小寫（`Task` 和 `task` 視為不同）。\n- 在同一目錄內，markdown 檔案在去重前按檔名字典序讀取。\n\n## 無效/缺失代理檔案行為\n\n按目錄（`loadAgentsFromDir`）：\n\n- 無法讀取/缺失的目錄：視為空（`readdir(...).catch(() => [])`）\n- 檔案讀取或解析失敗：記錄警告，跳過檔案\n- 解析路徑使用 `parseAgent(..., level: \"warn\")`\n\nFrontmatter 失敗行為來自 `parseFrontmatter`：\n\n- `warn` 層級的解析錯誤會記錄警告\n- 解析器退回至簡單的 `key: value` 逐行解析器\n- 若必要欄位仍然缺失，`parseAgentFields` 失敗，然後拋出 `AgentParsingError` 並由呼叫端捕獲（跳過檔案）\n\n淨效果：一個有問題的自訂代理檔案不會中止其他檔案的探索。\n\n## 代理查詢與選擇\n\n查詢是精確名稱的線性搜尋：\n\n- `getAgent(agents, name)` => `agents.find(a => a.name === name)`\n\n在任務執行中（`TaskTool.execute`）：\n\n1. 在呼叫時重新探索代理（`discoverAgents(this.session.cwd)`）\n2. 請求的 `params.agent` 透過 `getAgent` 解析\n3. 找不到代理時回傳立即的工具回應：\n   - `Unknown agent \"...\". Available: ...`\n   - 不執行子程序\n\n### 描述與執行時期探索的差異\n\n`TaskTool.create()` 在初始化時從探索結果建置工具描述（`buildDescription`）。\n\n`execute()` 會再次重新探索代理。因此若代理檔案在工作階段中途變更，執行時期的集合可能與先前工具描述中列出的不同。\n\n## 結構化輸出防護機制與 schema 優先順序\n\n`TaskTool.execute` 中的執行時期輸出 schema 優先順序：\n\n1. 代理 frontmatter 的 `output`\n2. 任務呼叫的 `params.schema`\n3. 父工作階段的 `outputSchema`\n\n（`effectiveOutputSchema = effectiveAgent.output ?? outputSchema ?? this.session.outputSchema`）\n\n`src/prompts/tools/task.md` 中的提示詞防護文字警告結構化輸出代理（`explore`、`reviewer`）的不匹配行為：散文中的輸出格式指示可能與內建 schema 衝突，產生 `null` 輸出。\n\n這是指導性質的，不是 `discoverAgents` 中的硬性執行時期驗證邏輯。\n\n## 命令探索互動\n\n`src/task/commands.ts` 是工作流程命令（非代理定義）的平行基礎設施，但它遵循相同的整體模式：\n\n- 首先從能力提供者探索\n- 以先到先贏方式按名稱去重\n- 若仍未見過則附加內建命令\n- 透過 `getCommand` 進行精確名稱查詢\n\n在 `src/task/index.ts` 中，命令輔助函式與代理探索輔助函式一起重新匯出。代理探索本身在執行時期不依賴命令探索。\n\n## 超出探索範圍的可用性限制\n\n代理可能是可探索的，但由於執行防護機制仍然無法執行。\n\n### 父代生成策略\n\n`TaskTool.execute` 檢查 `session.getSessionSpawns()`：\n\n- `\"*\"` => 允許任何\n- `\"\"` => 拒絕全部\n- CSV 列表 => 僅允許列出的名稱\n\n若被拒絕：立即回應 `Cannot spawn '...'. Allowed: ...`。\n\n### 阻止自我遞迴的環境變數防護\n\n`PI_BLOCKED_AGENT` 在工具建構時讀取。若請求匹配，執行會被拒絕並回傳遞迴預防訊息。\n\n### 遞迴深度閘控（子工作階段中的任務工具可用性）\n\n在 `runSubprocess`（`src/task/executor.ts`）中：\n\n- 深度從 `taskDepth` 計算\n- `task.maxRecursionDepth` 控制截止點\n- 達到最大深度時：\n  - `task` 工具從子工具列表中移除\n  - 子代的 `spawns` 環境變數設為空\n\n因此更深層級無法生成進一步的任務，即使代理定義包含 `spawns`。\n\n## 計畫模式注意事項（目前實作）\n\n`TaskTool.execute` 為計畫模式計算一個 `effectiveAgent`（前置計畫模式提示詞、強制唯讀工具子集、清除 spawns），但 `runSubprocess` 使用的是 `agent` 而非 `effectiveAgent`。\n\n目前的效果：\n\n- 模型覆寫 / 思考層級 / 輸出 schema 衍生自 `effectiveAgent`\n- 來自 `effectiveAgent` 的系統提示詞和工具/生成限制在此呼叫路徑中未被傳遞\n\n這是閱讀計畫模式行為預期時值得了解的實作注意事項。\n",
	"zh-tw/sessions/compaction.md": "---\ntitle: 壓縮與分支摘要\ndescription: 長期會話的上下文視窗壓縮與分支摘要生成。\nsidebar:\n  order: 5\n  label: 壓縮\ni18n:\n  sourceHash: dae425a900d8\n  translator: machine\n---\n\n# 壓縮與分支摘要\n\n壓縮與分支摘要是兩種機制，用於在不遺失先前工作上下文的情況下保持長期會話的可用性。\n\n- **壓縮**將當前分支上的舊歷史記錄重寫為摘要。\n- **分支摘要**在 `/tree` 導航期間捕獲被放棄的分支上下文。\n\n兩者都以會話條目的形式持久化，並在重建 LLM 輸入時轉換回使用者上下文訊息。\n\n## 關鍵實作檔案\n\n- `src/session/compaction/compaction.ts`\n- `src/session/compaction/branch-summarization.ts`\n- `src/session/compaction/pruning.ts`\n- `src/session/compaction/utils.ts`\n- `src/session/session-manager.ts`\n- `src/session/agent-session.ts`\n- `src/session/messages.ts`\n- `src/extensibility/hooks/types.ts`\n- `src/config/settings-schema.ts`\n\n## 會話條目模型\n\n壓縮與分支摘要是一等會話條目，而非普通的助理/使用者訊息。\n\n- `CompactionEntry`\n  - `type: \"compaction\"`\n  - `summary`，可選 `shortSummary`\n  - `firstKeptEntryId`（壓縮邊界）\n  - `tokensBefore`\n  - 可選 `details`、`preserveData`、`fromExtension`\n- `BranchSummaryEntry`\n  - `type: \"branch_summary\"`\n  - `fromId`、`summary`\n  - 可選 `details`、`fromExtension`\n\n當重建上下文（`buildSessionContext`）時：\n\n1. 活動路徑上最新的壓縮被轉換為一個 `compactionSummary` 訊息。\n2. 從 `firstKeptEntryId` 到壓縮點的保留條目會被重新包含。\n3. 路徑上後續的條目會被附加。\n4. `branch_summary` 條目被轉換為 `branchSummary` 訊息。\n5. `custom_message` 條目被轉換為 `custom` 訊息。\n\n這些自訂角色隨後在 `convertToLlm()` 中使用靜態範本轉換為面向 LLM 的使用者訊息：\n\n- `prompts/compaction/compaction-summary-context.md`\n- `prompts/compaction/branch-summary-context.md`\n\n## 壓縮管線\n\n### 觸發條件\n\n壓縮可以透過三種方式執行：\n\n1. **手動**：`/compact [instructions]` 呼叫 `AgentSession.compact(...)`。\n2. **自動溢出恢復**：在助理錯誤符合上下文溢出條件後觸發。\n3. **自動閾值壓縮**：在成功回合後，當上下文超過閾值時觸發。\n\n### 壓縮結構（視覺化）\n\n```text\nBefore compaction:\n\n  entry:  0     1     2     3      4     5     6      7      8     9\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┘\n                └────────┬───────┘ └──────────────┬──────────────┘\n               messagesToSummarize            kept messages\n                                   ↑\n                          firstKeptEntryId (entry 4)\n\nAfter compaction (new entry appended):\n\n  entry:  0     1     2     3      4     5     6      7      8     9      10\n        ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┬─────┐\n        │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │ cmp │\n        └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┴─────┘\n               └──────────┬──────┘ └──────────────────────┬───────────────────┘\n                 not sent to LLM                    sent to LLM\n                                                         ↑\n                                              starts from firstKeptEntryId\n\nWhat the LLM sees:\n\n  ┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐\n  │ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │\n  └────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘\n       ↑         ↑      └─────────────────┬────────────────┘\n    prompt   from cmp          messages from firstKeptEntryId\n```\n\n### 溢出重試 vs 閾值壓縮\n\n兩種自動路徑有意設計為不同：\n\n- **溢出重試壓縮**\n  - 觸發條件：當前模型的助理錯誤被偵測為上下文溢出。\n  - 失敗的助理錯誤訊息在重試前從活動代理狀態中移除。\n  - 自動壓縮以 `reason: \"overflow\"` 和 `willRetry: true` 執行。\n  - 成功後，代理自動繼續（`agent.continue()`），在壓縮之後執行。\n\n- **閾值壓縮**\n  - 觸發條件：`contextTokens > contextWindow - compaction.reserveTokens`。\n  - 以 `reason: \"threshold\"` 和 `willRetry: false` 執行。\n  - 成功後，若 `compaction.autoContinue !== false`，注入合成提示：\n    - `\"Continue if you have next steps.\"`\n\n### 壓縮前剪枝\n\n在壓縮檢查之前，可能會執行工具結果剪枝（`pruneToolOutputs`）。\n\n預設剪枝策略：\n\n- 保護最新的 `40_000` 個工具輸出 token。\n- 要求至少 `20_000` 個總估計節省量。\n- 永遠不剪枝來自 `skill` 或 `read` 的工具結果。\n\n被剪枝的工具結果會被替換為：\n\n- `[Output truncated - N tokens]`\n\n如果剪枝更改了條目，會話儲存會被重寫，且代理訊息狀態會在壓縮決策之前重新整理。\n\n### 邊界與切割點邏輯\n\n`prepareCompaction()` 僅考慮自上次壓縮條目（如果有的話）以來的條目。\n\n1. 找到上一個壓縮索引。\n2. 計算 `boundaryStart = prevCompactionIndex + 1`。\n3. 在有可用的測量使用率時，使用其調整 `keepRecentTokens`。\n4. 在邊界視窗上執行 `findCutPoint()`。\n\n有效的切割點包括：\n\n- 角色為以下的訊息條目：`user`、`assistant`、`bashExecution`、`hookMessage`、`branchSummary`、`compactionSummary`\n- `custom_message` 條目\n- `branch_summary` 條目\n\n硬性規則：永遠不在 `toolResult` 處切割。\n\n如果切割點之前緊鄰非訊息的中繼資料條目（`model_change`、`thinking_level_change`、標籤等），則透過將切割索引向後移動直到遇到訊息或壓縮邊界，將它們拉入保留區域。\n\n### 分割回合處理\n\n如果切割點不在使用者回合起始處，壓縮將其視為分割回合。\n\n回合起始偵測將以下視為使用者回合邊界：\n\n- `message.role === \"user\"`\n- `message.role === \"bashExecution\"`\n- `custom_message` 條目\n- `branch_summary` 條目\n\n分割回合壓縮生成兩個摘要：\n\n1. 歷史摘要（`messagesToSummarize`）\n2. 回合前綴摘要（`turnPrefixMessages`）\n\n最終儲存的摘要合併為：\n\n```markdown\n<history summary>\n\n---\n\n**Turn Context (split turn):**\n\n<turn prefix summary>\n```\n\n### 摘要生成\n\n`compact(...)` 從序列化的對話文字建構摘要：\n\n1. 透過 `convertToLlm()` 轉換訊息。\n2. 使用 `serializeConversation()` 序列化。\n3. 包裝在 `<conversation>...</conversation>` 中。\n4. 可選地包含 `<previous-summary>...</previous-summary>`。\n5. 可選地將鉤子上下文注入為 `<additional-context>` 列表。\n6. 使用 `SUMMARIZATION_SYSTEM_PROMPT` 執行摘要提示。\n\n提示選擇：\n\n- 首次壓縮：`compaction-summary.md`\n- 有先前摘要的迭代壓縮：`compaction-update-summary.md`\n- 分割回合第二輪：`compaction-turn-prefix.md`\n- 簡短 UI 摘要：`compaction-short-summary.md`\n\n遠端摘要模式：\n\n- 若設定了 `compaction.remoteEndpoint`，壓縮會 POST：\n  - `{ systemPrompt, prompt }`\n- 預期返回至少包含 `{ summary }` 的 JSON。\n\n### 摘要中的檔案操作上下文\n\n壓縮使用助理工具呼叫追蹤累計檔案活動：\n\n- `read(path)` → 讀取集合\n- `write(path)` → 修改集合\n- `edit(path)` → 修改集合\n\n累計行為：\n\n- 僅當先前條目是由 pi 生成（`fromExtension !== true`）時，才包含先前壓縮的詳細資訊。\n- 在分割回合中，也包含回合前綴的檔案操作。\n- `readFiles` 排除同時被修改的檔案。\n\n摘要文字透過提示範本附加檔案標籤：\n\n```xml\n<read-files>\n...\n</read-files>\n<modified-files>\n...\n</modified-files>\n```\n\n### 持久化與重新載入\n\n在摘要生成（或鉤子提供的摘要）之後，代理會話：\n\n1. 使用 `appendCompaction(...)` 附加 `CompactionEntry`。\n2. 透過 `buildSessionContext()` 重建上下文。\n3. 以重建的上下文替換即時代理訊息。\n4. 發出 `session_compact` 鉤子事件。\n\n## 分支摘要管線\n\n分支摘要與樹狀導航相關，而非 token 溢出。\n\n### 觸發條件\n\n在 `navigateTree(...)` 期間：\n\n1. 使用 `collectEntriesForBranchSummary(...)` 計算從舊葉節點到共同祖先的被放棄條目。\n2. 若呼叫者請求摘要（`options.summarize`），在切換葉節點之前生成摘要。\n3. 若摘要存在，使用 `branchWithSummary(...)` 將其附加到導航目標。\n\n在操作上，這通常由 `/tree` 流程在 `branchSummary.enabled` 啟用時驅動。\n\n### 分支切換結構（視覺化）\n\n```text\nTree before navigation:\n\n         ┌─ B ─ C ─ D (old leaf, being abandoned)\n    A ───┤\n         └─ E ─ F (target)\n\nCommon ancestor: A\nEntries to summarize: B, C, D\n\nAfter navigation with summary:\n\n         ┌─ B ─ C ─ D ─ [summary of B,C,D]\n    A ───┤\n         └─ E ─ F (new leaf)\n```\n\n### 準備與 token 預算\n\n`generateBranchSummary(...)` 計算預算為：\n\n- `tokenBudget = model.contextWindow - branchSummary.reserveTokens`\n\n`prepareBranchEntries(...)` 接著：\n\n1. 第一輪：從所有摘要條目中收集累計檔案操作，包含先前由 pi 生成的 `branch_summary` 詳細資訊。\n2. 第二輪：從最新到最舊遍歷，添加訊息直到達到 token 預算。\n3. 優先保留最近的上下文。\n4. 為了連續性，可能仍然在預算邊緣包含大型摘要條目。\n\n壓縮條目在分支摘要輸入期間作為訊息（`compactionSummary`）包含。\n\n### 摘要生成與持久化\n\n分支摘要：\n\n1. 轉換並序列化選定的訊息。\n2. 包裝在 `<conversation>` 中。\n3. 若有提供則使用自訂指令，否則使用 `branch-summary.md`。\n4. 使用 `SUMMARIZATION_SYSTEM_PROMPT` 呼叫摘要模型。\n5. 前置 `branch-summary-preamble.md`。\n6. 附加檔案操作標籤。\n\n結果以 `BranchSummaryEntry` 儲存，附帶可選的詳細資訊（`readFiles`、`modifiedFiles`）。\n\n## 擴充功能與鉤子接觸點\n\n### `session_before_compact`\n\n壓縮前鉤子。\n\n可以：\n\n- 取消壓縮（`{ cancel: true }`）\n- 提供完整的自訂壓縮酬載（`{ compaction: CompactionResult }`）\n\n### `session.compacting`\n\n預設壓縮的提示/上下文自訂鉤子。\n\n可以返回：\n\n- `prompt`（覆蓋基礎摘要提示）\n- `context`（注入 `<additional-context>` 的額外上下文行）\n- `preserveData`（儲存在壓縮條目上）\n\n### `session_compact`\n\n壓縮後通知，包含已儲存的 `compactionEntry` 和 `fromExtension` 旗標。\n\n### `session_before_tree`\n\n在預設分支摘要生成之前的樹狀導航時執行。\n\n可以：\n\n- 取消導航\n- 提供自訂的 `{ summary: { summary, details } }`，在使用者請求摘要時使用\n\n### `session_tree`\n\n導航後事件，公開新/舊葉節點和可選的摘要條目。\n\n## 執行時行為與失敗語義\n\n- 手動壓縮會先中止當前代理操作。\n- `abortCompaction()` 取消手動和自動壓縮控制器。\n- 自動壓縮為 UI/狀態更新發出開始/結束會話事件。\n- 自動壓縮可以嘗試多個候選模型並重試暫時性失敗。\n- 溢出錯誤被排除在通用重試路徑之外，因為它們由壓縮處理。\n- 若自動壓縮失敗：\n  - 溢出路徑發出 `Context overflow recovery failed: ...`\n  - 閾值路徑發出 `Auto-compaction failed: ...`\n- 分支摘要可以透過中止信號（例如 Escape）取消，返回已取消/已中止的導航結果。\n\n## 設定與預設值\n\n來自 `settings-schema.ts`：\n\n- `compaction.enabled` = `true`\n- `compaction.reserveTokens` = `16384`\n- `compaction.keepRecentTokens` = `20000`\n- `compaction.autoContinue` = `true`\n- `compaction.remoteEndpoint` = `undefined`\n- `branchSummary.enabled` = `false`\n- `branchSummary.reserveTokens` = `16384`\n\n這些值在執行時由 `AgentSession` 和壓縮/分支摘要模組使用。\n",
	"zh-tw/sessions/handoff-generation-pipeline.md": "---\ntitle: 交接產生流程\ndescription: >-\n  Handoff generation pipeline for creating portable session summaries for team\n  collaboration.\nsidebar:\n  order: 8\n  label: 交接流程\ni18n:\n  sourceHash: 03666084b5ac\n  translator: machine\n---\n\n# `/handoff` 產生流程\n\n本文件說明 coding-agent 目前如何實作 `/handoff`：觸發路徑、產生提示詞、完成擷取、工作階段切換，以及上下文重新注入。\n\n## 範圍\n\n涵蓋：\n\n- 互動式 `/handoff` 命令分發\n- `AgentSession.handoff()` 生命週期與狀態轉換\n- 交接輸出如何從助理輸出中擷取\n- 舊/新工作階段如何以不同方式持久化交接資料\n- 成功、取消和失敗的 UI 行為\n\n不涵蓋：\n\n- 通用樹狀導覽/分支內部機制\n- 非交接工作階段命令（`/new`、`/fork`、`/resume`）\n\n## 實作檔案\n\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/extensibility/slash-commands.ts`](../../packages/coding-agent/src/extensibility/slash-commands.ts)\n\n## 觸發路徑\n\n1. `/handoff` 在內建斜線命令元資料（`slash-commands.ts`）中宣告，並帶有可選的行內提示：`[focus instructions]`。\n2. 在互動式輸入處理（`InputController`）中，匹配 `/handoff` 或 `/handoff ...` 的提交文字會在正常提示詞提交之前被攔截。\n3. 編輯器被清除並呼叫 `handleHandoffCommand(customInstructions?)`。\n4. `CommandController.handleHandoffCommand` 使用當前項目執行預檢防護：\n   - 計算 `type === \"message\"` 項目的數量。\n   - 如果 `< 2`，則警告：`Nothing to hand off (no messages yet)` 並返回。\n\n相同的最低內容防護也存在於 `AgentSession.handoff()` 內部，若違反則拋出錯誤。這在 UI 層和工作階段層都進行了重複的安全檢查。\n\n## 端對端生命週期\n\n### 1) 開始交接產生\n\n`AgentSession.handoff(customInstructions?)`：\n\n- 讀取當前分支項目（`sessionManager.getBranch()`）\n- 驗證最低訊息數量（`>= 2`）\n- 建立 `#handoffAbortController`\n- 建構一個固定的行內提示詞，要求產生結構化的交接文件（`Goal`、`Constraints & Preferences`、`Progress`、`Key Decisions`、`Critical Context`、`Next Steps`）\n- 如果提供了自訂指示，則附加 `Additional focus: ...`\n\n提示詞透過以下方式送出：\n\n```ts\nawait this.prompt(handoffPrompt, { expandPromptTemplates: false });\n```\n\n`expandPromptTemplates: false` 防止此內部指令酬載進行斜線/提示詞範本展開。\n\n### 2) 擷取完成結果\n\n在送出提示詞之前，`handoff()` 訂閱工作階段事件並等待 `agent_end`。\n\n在 `agent_end` 時，它透過向後掃描最近的 `assistant` 訊息，從代理狀態中提取交接文字，然後將所有 `type === \"text\"` 的 `content` 區塊以 `\\n` 串接。\n\n重要的提取假設：\n\n- 僅使用文字區塊；非文字內容被忽略。\n- 假設最新的助理訊息對應交接產生結果。\n- 不解析 markdown 區段或驗證格式合規性。\n- 如果助理輸出沒有文字區塊，則交接被視為缺失。\n\n### 3) 取消檢查\n\n當以下任一條件成立時，`handoff()` 回傳 `undefined`：\n\n- 沒有擷取到交接文字，或\n- `#handoffAbortController.signal.aborted` 為 true\n\n它總是在 `finally` 中清除 `#handoffAbortController`。\n\n### 4) 建立新工作階段\n\n如果文字已擷取且未被中止：\n\n1. 刷新當前工作階段寫入器（`sessionManager.flush()`）\n2. 啟動全新工作階段（`sessionManager.newSession()`）\n3. 重置記憶體中的代理狀態（`agent.reset()`）\n4. 重新綁定 `agent.sessionId` 到新工作階段 id\n5. 清除佇列中的上下文陣列（`#steeringMessages`、`#followUpMessages`、`#pendingNextTurnMessages`）\n6. 重置待辦提醒計數器\n\n`newSession()` 建立一個新的標頭和空的項目清單（葉節點重置為 `null`）。在交接路徑中，不傳遞 `parentSession`。\n\n### 5) 交接上下文注入\n\n產生的交接文件被包裝並作為 `custom_message` 項目附加到新工作階段：\n\n```text\n<handoff-context>\n...handoff text...\n</handoff-context>\n\nThe above is a handoff document from a previous session. Use this context to continue the work seamlessly.\n```\n\n插入呼叫：\n\n```ts\nthis.sessionManager.appendCustomMessageEntry(\"handoff\", handoffContent, true);\n```\n\n語意：\n\n- `customType`：`\"handoff\"`\n- `display`：`true`（在 TUI 重建中可見）\n- 項目類型：`custom_message`（參與 LLM 上下文）\n\n### 6) 重建活躍代理上下文\n\n注入後：\n\n1. `sessionManager.buildSessionContext()` 解析當前葉節點的訊息清單\n2. `agent.replaceMessages(sessionContext.messages)` 使注入的交接訊息成為活躍上下文\n3. 方法回傳 `{ document: handoffText }`\n\n此時，新工作階段中的活躍 LLM 上下文包含注入的交接訊息，而非舊的對話記錄。\n\n## 持久化模型：舊工作階段 vs 新工作階段\n\n### 舊工作階段\n\n在產生期間，正常的訊息持久化保持活躍。助理交接回應在 `message_end` 時作為一般 `message` 項目被持久化。\n\n結果：原始工作階段包含作為歷史對話記錄一部分的可見已產生交接內容。\n\n### 新工作階段\n\n工作階段重置後，交接以 `custom_message`（`customType: \"handoff\"`）形式持久化。\n\n`buildSessionContext()` 透過 `createCustomMessage(...)` 將此項目轉換為執行時期自訂/使用者上下文訊息，因此它會被包含在新工作階段的未來提示詞中。\n\n## 控制器/UI 行為\n\n`CommandController.handleHandoffCommand` 行為：\n\n- 呼叫 `await session.handoff(customInstructions)`\n- 如果結果為 `undefined`：`showError(\"Handoff cancelled\")`\n- 成功時：\n  - `rebuildChatFromMessages()`（載入新工作階段上下文，包括注入的交接內容）\n  - 使狀態列和編輯器頂部邊框失效\n  - 重新載入待辦事項\n  - 附加成功聊天訊息行：`New session started with handoff context`\n- 發生例外時：\n  - 如果訊息為 `\"Handoff cancelled\"` 或錯誤名稱為 `AbortError`：`showError(\"Handoff cancelled\")`\n  - 否則：`showError(\"Handoff failed: <message>\")`\n- 結束時請求重新渲染\n\n## 取消語意（目前行為）\n\n### 工作階段層級取消原語\n\n`AgentSession` 公開：\n\n- `abortHandoff()` → 中止 `#handoffAbortController`\n- `isGeneratingHandoff` → 控制器存在時為 true\n\n當使用此中止路徑時，交接訂閱者以 `Error(\"Handoff cancelled\")` 拒絕，命令控制器將其映射為取消 UI。\n\n### 互動式 `/handoff` 路徑限制\n\n在目前的互動式控制器接線中，`/handoff` 未安裝呼叫 `abortHandoff()` 的專用 Escape 處理程式（不同於壓縮/分支摘要路徑會暫時覆蓋 `editor.onEscape`）。\n\n實際影響：\n\n- 存在工作階段層級的取消支援，但在 `/handoff` 命令路徑中沒有交接專用的按鍵綁定掛鉤。\n- 使用者中斷仍可能透過更廣泛的代理中止路徑發生，但那與 `abortHandoff()` 使用的明確取消通道不同。\n\n## 中止 vs 失敗的交接\n\n目前的 UI 分類：\n\n- **中止/取消**\n  - `abortHandoff()` 路徑觸發 `\"Handoff cancelled\"`，或\n  - 拋出 `AbortError`\n  - UI 顯示 `Handoff cancelled`\n\n- **失敗**\n  - 從 `handoff()` / 提示詞管線拋出的任何其他錯誤（模型/API 驗證錯誤、執行時期例外等）\n  - UI 顯示 `Handoff failed: ...`\n\n額外細微差異：如果產生完成但未提取到文字，`handoff()` 回傳 `undefined`，控制器目前報告為**取消**，而非**失敗**。\n\n## 短工作階段和最低內容防護\n\n兩個防護機制防止低訊號交接：\n\n- UI 層（`handleHandoffCommand`）：對 `< 2` 個訊息項目發出警告並提前返回\n- 工作階段層（`handoff()`）：以錯誤形式拋出相同條件\n\n這避免了建立具有空/幾乎為空交接上下文的新工作階段。\n\n## 狀態轉換摘要\n\n高層級狀態流程：\n\n1. 互動式斜線命令被攔截\n2. 預檢訊息數量防護\n3. `#handoffAbortController` 建立（`isGeneratingHandoff = true`）\n4. 內部交接提示詞提交（在聊天中作為正常助理產生可見）\n5. 在 `agent_end` 時，提取最後的助理文字\n6. 如果缺失/中止 → 回傳 `undefined` 或取消錯誤路徑\n7. 如果存在：\n   - 刷新舊工作階段\n   - 建立新的空工作階段\n   - 重置執行時期佇列/計數器\n   - 附加 `custom_message(handoff)`\n   - 重建並替換活躍代理訊息\n8. 控制器重建聊天 UI 並宣告成功\n9. `#handoffAbortController` 清除（`isGeneratingHandoff = false`）\n\n## 已知假設和限制\n\n- 交接提取是啟發式的：「最後的助理文字區塊」；沒有結構化驗證。\n- 沒有硬性檢查產生的 markdown 是否遵循請求的區段格式。\n- 缺失的提取文字在控制器 UX 中被報告為取消。\n- `/handoff` 互動式流程目前缺少專用的 Escape→`abortHandoff()` 綁定。\n- 新工作階段的血統元資料（`parentSession`）未在此路徑中設定。\n",
	"zh-tw/sessions/memory.md": "---\ntitle: 自主記憶\ndescription: 自主記憶系統，用於在不同工作階段之間持久化使用者偏好、專案上下文和回饋。\nsidebar:\n  order: 7\n  label: 自主記憶\ni18n:\n  sourceHash: 2aa9f516aa1e\n  translator: machine\n---\n\n# 自主記憶\n\n啟用後，代理會自動從過去的工作階段中提取持久性知識，並將精簡摘要注入到每個新工作階段中。隨著時間推移，它會建立一個專案範圍的記憶儲存——技術決策、反覆出現的工作流程、常見陷阱——無需手動操作即可持續累積。\n\n預設為停用。可透過 `/settings` 或 `config.yml` 啟用：\n\n```yaml\nmemories:\n  enabled: true\n```\n\n## 使用方式\n\n### 注入的內容\n\n在工作階段開始時，如果當前專案存在記憶摘要，它會以 **Memory Guidance** 區塊的形式注入到系統提示中。代理會被指示：\n\n- 將記憶視為啟發式上下文——對流程和先前決策有用，但對當前儲存庫狀態不具權威性。\n- 當記憶改變計畫時引用記憶工件路徑，並在採取行動前搭配當前儲存庫的證據。\n- 當記憶與儲存庫狀態和使用者指令衝突時，以後者為準；將衝突的記憶視為過時資料。\n\n### 讀取記憶工件\n\n代理可以使用 `read` 工具透過 `memory://` URL 直接讀取記憶檔案：\n\n| URL | 內容 |\n|---|---|\n| `memory://root` | 啟動時注入的精簡摘要 |\n| `memory://root/MEMORY.md` | 完整的長期記憶文件 |\n| `memory://root/skills/<name>/SKILL.md` | 生成的技能操作手冊 |\n\n### `/memory` 斜線命令\n\n| 子命令 | 效果 |\n|---|---|\n| `view` | 顯示當前的記憶注入內容 |\n| `clear` / `reset` | 刪除所有記憶資料和生成的工件 |\n| `enqueue` / `rebuild` | 強制在下次啟動時執行整合 |\n\n## 運作原理\n\n記憶由一個背景管線建構，在啟動時或透過斜線命令手動觸發時執行。\n\n**階段 1 — 逐工作階段提取：** 對於自上次處理以來有變更的每個過去工作階段，模型會讀取工作階段歷史並提取持久性訊號：技術決策、約束條件、已解決的故障、反覆出現的工作流程。過於近期、過於久遠或目前活躍的工作階段會被跳過。每次提取會產生一個原始記憶區塊和該工作階段的簡短摘要。\n\n**階段 2 — 整合：** 提取完成後，第二次模型處理會讀取所有逐工作階段的提取結果，並產生三個寫入磁碟的輸出：\n\n- `MEMORY.md` — 精心整理的長期記憶文件\n- `memory_summary.md` — 在工作階段開始時注入的精簡文本\n- `skills/` — 可重複使用的程序性操作手冊，每個位於各自的子目錄中\n\n階段 2 使用租約機制來防止多個程序同時啟動時重複執行。來自先前執行的過時技能目錄會自動清理。\n\n所有輸出在寫入磁碟前都會進行機密資訊掃描。\n\n### 提取行為\n\n記憶提取和整合行為完全由 `src/prompts/memories/` 中的靜態提示檔案驅動。\n\n| 檔案 | 用途 | 變數 |\n|---|---|---|\n| `stage_one_system.md` | 逐工作階段提取的系統提示 | — |\n| `stage_one_input.md` | 包裝工作階段內容的使用者輪次模板 | `{{thread_id}}`、`{{response_items_json}}` |\n| `consolidation.md` | 跨工作階段整合的提示 | `{{raw_memories}}`、`{{rollout_summaries}}` |\n| `read_path.md` | 注入到即時工作階段的記憶引導 | `{{memory_summary}}` |\n\n### 模型選擇\n\n記憶功能依附於模型角色系統。\n\n| 階段 | 角色 | 用途 |\n|---|---|---|\n| 階段 1（提取） | `default` | 逐工作階段知識提取 |\n| 階段 2（整合） | `smol` | 跨工作階段綜合 |\n\n如果未配置 `smol`，階段 2 會回退至 `default` 角色。\n\n## 配置\n\n| 設定 | 預設值 | 說明 |\n|---|---|---|\n| `memories.enabled` | `false` | 主開關 |\n| `memories.maxRolloutAgeDays` | `30` | 超過此天數的工作階段不會被處理 |\n| `memories.minRolloutIdleHours` | `12` | 比此時間更近期活躍的工作階段會被跳過 |\n| `memories.maxRolloutsPerStartup` | `64` | 單次啟動時處理的工作階段數量上限 |\n| `memories.summaryInjectionTokenLimit` | `5000` | 注入系統提示的摘要最大 token 數 |\n\n進階使用可在配置中調整額外的參數（並行數、租約持續時間、token 預算）。\n\n## 關鍵檔案\n\n- `src/memories/index.ts` — 管線編排、注入、斜線命令處理\n- `src/memories/storage.ts` — 基於 SQLite 的工作佇列和執行緒註冊\n- `src/prompts/memories/` — 記憶提示模板\n- `src/internal-urls/memory-protocol.ts` — `memory://` URL 處理器\n",
	"zh-tw/sessions/non-compaction-retry-policy.md": "---\ntitle: 非壓縮自動重試策略\ndescription: 針對壓縮路徑以外的暫時性 API 失敗之自動重試策略。\nsidebar:\n  order: 6\n  label: 重試策略\ni18n:\n  sourceHash: 8999a0258dd8\n  translator: machine\n---\n\n# 非壓縮自動重試策略\n\n本文件描述 `AgentSession` 中標準的 API 錯誤重試路徑。\n\n本文件明確排除透過自動壓縮進行的上下文溢位恢復。溢位由壓縮邏輯處理，並另行記載於 [`compaction.md`](./compaction.md)。\n\n## 實作檔案\n\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/config/settings-schema.ts`](../../packages/coding-agent/src/config/settings-schema.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/rpc/rpc-mode.ts`](../../packages/coding-agent/src/modes/rpc/rpc-mode.ts)\n- [`../src/modes/rpc/rpc-client.ts`](../../packages/coding-agent/src/modes/rpc/rpc-client.ts)\n- [`../src/modes/rpc/rpc-types.ts`](../../packages/coding-agent/src/modes/rpc/rpc-types.ts)\n\n## 範圍界限與壓縮的關係\n\n重試與壓縮從相同的 `agent_end` 路徑進行檢查，但它們被刻意分離：\n\n1. `agent_end` 檢查最後一則助理訊息。\n2. `#isRetryableError(...)` 首先執行。\n3. 如果啟動重試，該回合會跳過壓縮檢查。\n4. 上下文溢位錯誤被硬性排除在重試分類之外（`isContextOverflow(...)` 會短路重試）。\n5. 因此溢位會落入 `#checkCompaction(...)` 而非標準重試。\n\n所以：過載/速率限制/伺服器/網路類型的失敗使用此重試策略；上下文視窗溢位則使用壓縮恢復。\n\n## 重試分類\n\n`#isRetryableError(...)` 需要同時滿足以下所有條件：\n\n- 助理的 `stopReason === \"error\"`\n- `errorMessage` 存在\n- 訊息**不是**上下文溢位\n- `errorMessage` 匹配 `#isRetryableErrorMessage(...)`\n\n目前可重試的模式集合（基於正規表達式）：\n\n- overloaded\n- rate limit / usage limit / too many requests\n- 類 HTTP 伺服器狀態碼：429、500、502、503、504\n- service unavailable / server error / internal error\n- connection error / fetch failed\n- `retry delay` 相關措辭\n\n這是字串模式分類，而非型別化的提供者錯誤碼。\n\n## 重試生命週期與狀態轉換\n\n重試使用的工作階段狀態：\n\n- `#retryAttempt: number`（`0` 表示閒置）\n- `#retryPromise: Promise<void> | undefined`（追蹤進行中的重試生命週期）\n- `#retryResolve: (() => void) | undefined`（解決 `#retryPromise`）\n- `#retryAbortController: AbortController | undefined`（取消退避等待）\n\n流程（`#handleRetryableError`）：\n\n1. 讀取 `retry` 設定群組。\n2. 如果 `retry.enabled === false`，立即停止（`false`，不啟動重試）。\n3. 遞增 `#retryAttempt`。\n4. 首次嘗試時建立 `#retryPromise`（鏈中的第一次嘗試）。\n5. 如果嘗試次數超過 `retry.maxRetries`，發出最終失敗事件並停止。\n6. 計算延遲：`retry.baseDelayMs * 2^(attempt-1)`。\n7. 對於用量限制錯誤，解析重試提示並呼叫認證儲存（`markUsageLimitReached(...)`）；如果提供者/模型切換成功，將延遲強制設為 `0`。\n8. 發出 `auto_retry_start`。\n9. 從代理執行時狀態中移除尾端的助理錯誤訊息（保留在持久化的工作階段歷史中）。\n10. 以支援中止的方式進入等待。\n11. 喚醒後，透過 `setTimeout(..., 0)` 排程 `agent.continue()`。\n\n### 什麼會重設重試計數器\n\n`#retryAttempt` 在以下情況重設為 `0`：\n\n- 重試開始後首次成功的非錯誤、非中止助理訊息（發出 `auto_retry_end { success: true }`）\n- 退避等待期間的重試取消\n- 最大重試次數已超過的路徑\n\n`#retryPromise` 在重試鏈結束時（成功、取消或超過最大次數）透過 `#resolveRetry()` 解決/清除。\n\n## 退避與最大嘗試次數語義\n\n設定：\n\n- `retry.enabled`（預設 `true`）\n- `retry.maxRetries`（預設 `3`）\n- `retry.baseDelayMs`（預設 `2000`）\n\n嘗試次數編號：\n\n- 嘗試計數器在最大值檢查之前遞增\n- 開始事件使用目前的嘗試次數（從 1 開始）\n- 超過最大次數的結束事件回報 `attempt: this.#retryAttempt - 1`（最後嘗試的重試次數）\n\n使用預設設定的退避序列：\n\n- 第 1 次嘗試：2000 毫秒\n- 第 2 次嘗試：4000 毫秒\n- 第 3 次嘗試：8000 毫秒\n\n延遲覆寫輸入僅用於用量限制處理路徑，且僅用於影響認證儲存的模型/帳戶切換決策。在主要的非壓縮重試路徑中，退避保持本地指數延遲，除非切換成功（`delayMs = 0`）。\n\n## 中止機制\n\n### 明確的重試中止\n\n`abortRetry()`：\n\n- 中止 `#retryAbortController`（如果存在）\n- 解決重試 Promise（`#resolveRetry()`），使等待者不被阻塞\n\n如果中止發生在等待期間，捕獲路徑會發出：\n\n- `auto_retry_end { success: false, finalError: \"Retry cancelled\" }`\n- 重設嘗試次數/控制器\n\n### 全域操作中止交互\n\n`abort()` 在中止活動的代理串流之前呼叫 `abortRetry()`。這確保當使用者發出一般性中止時，重試退避會被取消。\n\n### TUI 交互\n\n收到 `auto_retry_start` 時，EventController：\n\n- 將 `Esc` 處理器切換為 `session.abortRetry()`\n- 呈現載入文字：`Retrying (attempt/maxAttempts) in Ns… (esc to cancel)`\n\n收到 `auto_retry_end` 時，恢復先前的 `Esc` 處理器並清除載入狀態。\n\n## 串流與提示完成行為\n\n`prompt()` 最終在 `agent.prompt(...)` 回傳後等待 `#waitForRetry()`。\n\n效果：\n\n- 一次提示呼叫在任何已啟動的重試鏈完成（成功/失敗/取消）之前不會完全解決\n- 重試生命週期是一次邏輯提示執行邊界的一部分\n\n這防止呼叫者過早將正在重試的回合視為已完成。\n\n## 控制：設定與 RPC\n\n### 設定選項\n\n定義在設定結構描述的 retry 群組下：\n\n- `retry.enabled`\n- `retry.maxRetries`\n- `retry.baseDelayMs`\n\n工作階段中的程式化切換：\n\n- `setAutoRetryEnabled(enabled)` 寫入 `retry.enabled`\n- `autoRetryEnabled` 讀取 `retry.enabled`\n- `isRetrying` 回報重試生命週期 Promise 是否活動中\n\n### RPC 控制\n\nRPC 命令介面：\n\n- `set_auto_retry` → `session.setAutoRetryEnabled(command.enabled)`\n- `abort_retry` → `session.abortRetry()`\n\n客戶端輔助方法：\n\n- `RpcClient.setAutoRetry(enabled)`\n- `RpcClient.abortRetry()`\n\n兩個命令都回傳成功回應；重試進度/失敗詳情來自串流的工作階段事件，而非命令回應的酬載。\n\n## 事件發出與失敗呈現\n\n工作階段層級的重試事件：\n\n- `auto_retry_start { attempt, maxAttempts, delayMs, errorMessage }`\n- `auto_retry_end { success, attempt, finalError? }`\n\n傳播：\n\n- 透過 `AgentSession.subscribe(...)` 發出\n- 以擴充功能事件的形式轉發至擴充功能執行器\n- 在 RPC 模式中，直接以 JSON 事件物件轉發（`session.subscribe(event => output(event))`）\n- 在 TUI 中，由 `EventController` 消費以呈現載入/錯誤 UI\n\n最終失敗呈現：\n\n- 超過最大次數或取消時，`auto_retry_end.success === false`\n- TUI 顯示：`Retry failed after N attempts: <finalError>`\n- 擴充功能/鉤子收到具有相同欄位的 `auto_retry_end`\n- RPC 消費者在 stdout 串流上收到相同的事件物件\n\n## 永久停止條件\n\n當發生以下任何情況時，重試會停止且不會自動繼續：\n\n- `retry.enabled` 為 false\n- 錯誤未被分類為可重試\n- 錯誤為上下文溢位（委派至壓縮路徑）\n- 超過最大重試次數\n- 使用者取消重試（重試載入器期間使用 `abort_retry` 或 `Esc`）\n- 全域中止（`abort`）會先取消重試\n\n在計數器重設後，未來遇到可重試錯誤時仍可啟動新的重試鏈。\n\n## 操作注意事項\n\n- 分類是正規表達式文字匹配；此處未使用提供者特定的結構化錯誤。\n- 重試在重新繼續前會從**執行時上下文**中移除失敗的助理錯誤，但工作階段歷史仍保留該錯誤條目。\n- `RpcSessionState` 目前公開 `autoCompactionEnabled` 但未公開 `autoRetryEnabled` 欄位；RPC 呼叫者必須自行追蹤切換狀態，或透過其他 API 查詢設定。\n",
	"zh-tw/sessions/session-operations-export-share-fork-resume.md": "---\ntitle: 工作階段操作：匯出、傾印、分享、分支、恢復\ndescription: 用於匯出、分享、分支和恢復對話的工作階段操作。\nsidebar:\n  order: 3\n  label: 操作\ni18n:\n  sourceHash: e3c210b29c3e\n  translator: machine\n---\n\n# 工作階段操作：export、dump、share、fork、resume/continue\n\n本文件描述目前實作中操作者可見的工作階段匯出/分享/分支/恢復操作行為。\n\n## 實作檔案\n\n- [`../src/modes/controllers/command-controller.ts`](../../packages/coding-agent/src/modes/controllers/command-controller.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/export/html/index.ts`](../../packages/coding-agent/src/export/html/index.ts)\n- [`../src/export/custom-share.ts`](../../packages/coding-agent/src/export/custom-share.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n\n## 操作矩陣\n\n| 操作 | 進入路徑 | 工作階段變更 | 工作階段檔案建立/切換 | 輸出產物 |\n|---|---|---|---|---|\n| `/dump` | 互動式斜線命令 | 否 | 否 | 剪貼簿文字 |\n| `/export [path]` | 互動式斜線命令 | 否 | 否 | HTML 檔案 |\n| `--export <session.jsonl> [outputPath]` | CLI 啟動快速路徑 | 無執行期工作階段變更 | 無使用中工作階段；讀取目標檔案 | HTML 檔案 |\n| `/share` | 互動式斜線命令 | 否 | 否 | 暫存 HTML + 分享 URL/gist |\n| `/fork` | 互動式斜線命令 | 是（使用中工作階段身分變更） | 建立新的工作階段檔案並切換目前工作階段至該檔案（僅限持久模式） | 當產物目錄存在時，複製到新工作階段命名空間 |\n| `/resume` | 互動式斜線命令 | 是（使用中記憶體狀態被替換） | 切換到所選的現有工作階段檔案 | 無 |\n| `--resume` | CLI 啟動（選擇器） | 工作階段建立後是 | 開啟所選的現有工作階段檔案 | 無 |\n| `--resume <id\\|path>` | CLI 啟動 | 工作階段建立後是 | 開啟現有工作階段；跨專案情況可分支到目前專案 | 無 |\n| `--continue` | CLI 啟動 | 工作階段建立後是 | 開啟終端機麵包屑或最近使用的工作階段；若不存在則建立新的 | 無 |\n\n## 匯出與傾印\n\n### `/export [outputPath]`（互動式）\n\n流程：\n\n1. `InputController` 將 `/export...` 路由到 `CommandController.handleExportCommand`。\n2. 命令以空白字元分割，僅使用 `/export` 之後的第一個引數作為 `outputPath`。\n3. `AgentSession.exportToHtml()` 呼叫 `exportSessionToHtml(sessionManager, state, { outputPath, themeName })`。\n4. 成功時，UI 顯示路徑並在瀏覽器中開啟檔案。\n\n行為細節：\n\n- `--copy`、`clipboard` 和 `copy` 引數會被明確拒絕，並顯示警告建議使用 `/dump`。\n- 匯出會嵌入工作階段標頭/條目/葉節點，以及來自代理狀態的目前 `systemPrompt` 和工具描述。\n- 匯出期間不會附加任何工作階段條目。\n\n注意事項：\n\n- 引數解析基於空白字元（`text.split(/\\s+/)`），因此包含空格的引號路徑不會被此命令路徑保留為單一路徑。\n\n### `--export <inputSessionFile> [outputPath]`（CLI）\n\n`main.ts` 中的流程：\n\n1. 提前處理（在互動式/工作階段啟動之前）。\n2. 呼叫 `exportFromFile(inputPath, outputPath?)`。\n3. `SessionManager.open(inputPath)` 載入條目，然後生成並寫入 HTML。\n4. 處理程序印出 `Exported to: ...` 並退出。\n\n行為細節：\n\n- 遺失的輸入檔案會顯示為 `File not found: <path>`。\n- 此路徑不會建立 `AgentSession`，也不會變更任何執行中的工作階段。\n\n### `/dump`（互動式剪貼簿匯出）\n\n流程：\n\n1. `CommandController.handleDumpCommand()` 呼叫 `session.formatSessionAsText()`。\n2. 若為空字串，回報 `No messages to dump yet.`\n3. 否則透過原生 `copyToClipboard` 複製到剪貼簿。\n\n傾印內容包括：\n\n- 系統提示詞\n- 使用中模型/思考層級\n- 工具定義 + 參數\n- 使用者/助手訊息\n- 思考區塊和工具呼叫\n- 工具結果和執行區塊（排除 `excludeFromContext` 的 bash/python 條目）\n- 自訂/鉤子/檔案提及/分支摘要/壓縮摘要條目\n\n傾印不會進行任何工作階段持久化變更。\n\n## 分享\n\n`/share` 僅限互動式使用，且始終以將目前工作階段匯出至暫存 HTML 檔案開始。\n\n### 階段 1：暫存匯出\n\n- 暫存檔案路徑：`${os.tmpdir()}/${Snowflake.next()}.html`\n- 使用 `session.exportToHtml(tmpFile)`\n- 若匯出失敗（特別是記憶體內工作階段），分享以錯誤結束。\n\n### 階段 2：自訂分享處理器（若存在）\n\n`loadCustomShare()` 檢查 `~/.xcsh/agent` 中第一個存在的候選檔案：\n\n- `share.ts`\n- `share.js`\n- `share.mjs`\n\n需求：\n\n- 模組必須預設匯出一個函式 `(htmlPath) => Promise<CustomShareResult | string | undefined>`。\n\n若存在且有效：\n\n- UI 進入 `Sharing...` 載入狀態。\n- 處理器結果解讀：\n  - string => 視為 URL，顯示並開啟\n  - object => 顯示 `url` 和/或 `message`；開啟 `url`\n  - `undefined`/falsy => 通用 `Session shared`\n- 完成後移除暫存檔案。\n\n關鍵的回退行為：\n\n- 若自訂處理器存在但載入失敗，命令出錯並返回。\n- 若自訂處理器執行後拋出例外，命令出錯並返回。\n- 在兩種失敗情況下，**不會**回退到 GitHub gist。\n- Gist 回退僅在不存在自訂分享腳本時發生。\n\n### 階段 3：預設 gist 回退\n\n僅在未找到自訂分享處理器時：\n\n1. 驗證 `gh auth status`。\n2. 顯示 `Creating gist...` 載入畫面。\n3. 執行 `gh gist create --public=false <tmpFile>`。\n4. 解析 gist URL，取得 gist id，建構預覽 URL `https://gistpreview.github.io/?<id>`。\n5. 同時顯示預覽和 gist URL；開啟預覽。\n\n分享中的取消/中止語意：\n\n- 載入器有 `onAbort` 鉤子，可恢復編輯器 UI 並回報 `Share cancelled`。\n- 在此程式碼路徑中，底層的 `gh gist create` 命令未傳遞中止信號；取消是 UI 層級的，在命令返回後進行檢查。\n\n## 分支\n\n`/fork` 從目前工作階段建立新的工作階段，並切換使用中的工作階段身分。\n\n### 前置條件和立即防護\n\n- 若代理正在串流中，`/fork` 會被拒絕並顯示警告。\n- 操作前會清除 UI 狀態/載入指示器。\n\n### 工作階段層級流程\n\n`AgentSession.fork()`：\n\n1. 發出 `session_before_switch`，reason 為 `\"fork\"`（可取消）。\n2. 刷新待寫入的內容。\n3. 呼叫 `SessionManager.fork()`。\n4. 將產物目錄從舊工作階段命名空間複製到新命名空間（盡力而為；非 ENOENT 的複製失敗會被記錄但不致命）。\n5. 更新 `agent.sessionId`。\n6. 發出 `session_switch`，reason 為 `\"fork\"`。\n\n`SessionManager.fork()` 行為：\n\n- 需要持久模式和現有工作階段檔案。\n- 建立新的工作階段 id 和新的 JSONL 檔案路徑。\n- 重寫標頭，包含：\n  - 新的 `id`\n  - 新的時間戳記\n  - `cwd` 不變\n  - `parentSession` 設為前一個工作階段 id\n- 在新檔案中保持所有非標頭條目不變。\n\n### 非持久行為\n\n- 記憶體內工作階段管理器從 `fork()` 返回 `undefined`。\n- `AgentSession.fork()` 返回 `false`。\n- UI 回報 `Fork failed (session not persisted or cancelled)`。\n\n## 恢復與繼續\n\n## 互動式 `/resume`\n\n流程：\n\n1. 開啟透過 `SessionManager.list(currentCwd, currentSessionDir)` 填充的工作階段選擇器。\n2. 選擇後，`SelectorController.handleResumeSession(sessionPath)` 呼叫 `session.switchSession(sessionPath)`。\n3. UI 清除/重建聊天和待辦事項，然後回報 `Resumed session`。\n\n注意：\n\n- 此選擇器僅列出目前工作階段目錄範圍內的工作階段。\n- 不使用全域跨專案搜尋。\n\n## CLI `--resume`\n\n### `--resume`（無值）\n\n- `main.ts` 列出目前 cwd/sessionDir 的工作階段並開啟選擇器。\n- 所選路徑在工作階段建立前以 `SessionManager.open(selectedPath)` 開啟。\n\n### `--resume <value>`\n\n`createSessionManager()` 解析順序：\n\n1. 若值看起來像路徑（`/`、`\\` 或 `.jsonl`），直接開啟。\n2. 否則視為 id 前綴：\n   - 搜尋目前範圍（`SessionManager.list(cwd, sessionDir)`）\n   - 若未找到且沒有明確的 `sessionDir`，搜尋全域（`SessionManager.listAll()`）\n\n跨專案 id 匹配行為：\n\n- 若匹配的工作階段 cwd 與目前 cwd 不同，CLI 會詢問：\n  - `Session found in different project ... Fork into current directory? [y/N]`\n- 選擇是：`SessionManager.forkFrom(match.path, cwd, sessionDir)` 建立新的本地分支檔案。\n- 選擇否/非 TTY 預設：命令出錯。\n\n## CLI `--continue`\n\n`SessionManager.continueRecent(cwd, sessionDir)`：\n\n1. 解析目前 cwd 的工作階段目錄。\n2. 優先讀取終端機範圍的麵包屑。\n3. 回退到最近修改的工作階段檔案。\n4. 開啟找到的工作階段；若不存在，建立新的工作階段。\n\n這是僅限啟動時的行為；沒有互動式 `/continue` 斜線命令。\n\n## 工作階段切換如何實際變更執行期狀態\n\n`AgentSession.switchSession(sessionPath)` 執行恢復類操作使用的執行期轉換：\n\n1. 發出 `session_before_switch`，reason 為 `\"resume\"` 且包含 `targetSessionFile`（可取消）。\n2. 斷開代理事件訂閱並中止進行中的工作。\n3. 清除佇列中的引導/後續/下一輪訊息。\n4. 刷新目前工作階段管理器的寫入。\n5. `sessionManager.setSessionFile(sessionPath)` 並更新 `agent.sessionId`。\n6. 從載入的條目建構工作階段上下文。\n7. 發出 `session_switch`，reason 為 `\"resume\"`。\n8. 從上下文替換代理訊息。\n9. 恢復模型（若在目前登錄檔中可用）。\n10. 恢復或初始化思考層級。\n11. 重新連接代理事件訂閱。\n\n`switchSession()` 本身不會建立新的工作階段檔案。\n\n## 事件發出與取消點\n\n### 切換/分支生命週期鉤子\n\n對於 `newSession`、`fork` 和 `switchSession`：\n\n- 前置事件：`session_before_switch`\n  - reason：`new`、`fork`、`resume`\n  - 可透過返回 `{ cancel: true }` 取消\n- 後置事件：`session_switch`\n  - 相同的 reason 集合\n  - 包含 `previousSessionFile`\n\n`ExtensionRunner.emit()` 在第一個取消的前置事件結果時提前返回。\n\n### 自訂工具 `onSession` 行為\n\nSDK 橋接擴充功能工作階段事件到自訂工具 `onSession` 回呼：\n\n- `session_switch` -> `onSession({ reason: \"switch\", previousSessionFile })`\n- `session_branch` -> `reason: \"branch\"`\n- `session_start` -> `reason: \"start\"`\n- `session_tree` -> `reason: \"tree\"`\n- `session_shutdown` -> `reason: \"shutdown\"`\n\n這些回呼是觀察性的；不會取消切換/分支。\n\n### 與本文件相關的其他取消面\n\n- `/fork` 在串流期間被阻擋（使用者必須先等待/中止目前回應）。\n- `/resume` 選擇器可透過使用者關閉選擇器來取消。\n- 跨專案 `--resume <id>` 可透過拒絕分支提示來取消。\n- `/share` 對 gist 流程有 UI 中止路徑（`Share cancelled`）；在此程式碼路徑中不會為 `gh gist create` 連接處理程序終止語意。\n\n## 非持久（記憶體內）工作階段行為\n\n當工作階段管理器以 `SessionManager.inMemory()`（`--no-session`）建立時：\n\n- 工作階段檔案路徑不存在。\n- `/export` 和 `/share` 以 `Cannot export in-memory session to HTML` 失敗（傳播到命令錯誤 UI）。\n- `/fork` 失敗，因為 `SessionManager.fork()` 需要持久化。\n- `/dump` 仍然可用，因為它序列化記憶體內的代理狀態。\n- 若設定了 `--no-session`，CLI resume/continue 語意會被繞過，因為管理器建立會立即返回記憶體內模式。\n\n## 已知實作注意事項（截至目前程式碼）\n\n- `SelectorController.handleResumeSession()` 不會檢查 `session.switchSession(...)` 的布林結果；被鉤子取消的切換仍可能繼續通過 UI 的「Resumed session」重繪/狀態路徑。\n- `/share` 自訂分享失敗不會降級到預設 gist 回退；它們會以錯誤終止命令。\n- `/export` 引數標記化是簡化的，不會保留包含空格的引號路徑。\n",
	"zh-tw/sessions/session-switching-and-recent-listing.md": "---\ntitle: 工作階段切換與近期工作階段列表\ndescription: 工作階段切換機制以及包含搜尋與篩選功能的近期工作階段列表。\nsidebar:\n  order: 4\n  label: 切換與近期\ni18n:\n  sourceHash: aae56130b508\n  translator: machine\n---\n\n# 工作階段切換與近期工作階段列表\n\n本文件描述 coding-agent 如何探索近期工作階段、解析 `--resume` 目標、呈現工作階段選擇器，以及切換作用中的執行時期工作階段。\n\n本文聚焦於目前的實作行為，包含備援路徑與注意事項。\n\n## 實作檔案\n\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/cli/session-picker.ts`](../../packages/coding-agent/src/cli/session-picker.ts)\n- [`../src/modes/components/session-selector.ts`](../../packages/coding-agent/src/modes/components/session-selector.ts)\n- [`../src/modes/controllers/selector-controller.ts`](../../packages/coding-agent/src/modes/controllers/selector-controller.ts)\n- [`../src/main.ts`](../../packages/coding-agent/src/main.ts)\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/utils/ui-helpers.ts`](../../packages/coding-agent/src/modes/utils/ui-helpers.ts)\n\n## 近期工作階段探索\n\n### 目錄範圍\n\n`SessionManager` 預設將工作階段儲存在以 cwd 為範圍的目錄下：\n\n- `~/.xcsh/agent/sessions/--<cwd-encoded>--/*.jsonl`\n\n`SessionManager.list(cwd, sessionDir?)` 僅讀取該目錄，除非明確提供 `sessionDir`。\n\n### 兩種具有不同資料承載的列表路徑\n\n存在兩種不同的列表管線：\n\n1. `getRecentSessions(sessionDir, limit)`（歡迎/摘要檢視）\n   - 僅從每個檔案讀取 4KB 前綴（`readTextPrefix(..., 4096)`）。\n   - 解析標頭 + 最早的使用者文字預覽。\n   - 回傳輕量化的 `RecentSessionInfo`，包含延遲載入的 `name` 和 `timeAgo` getter。\n   - 依檔案 `mtime` 降序排列。\n\n2. `SessionManager.list(...)` / `SessionManager.listAll()`（恢復選擇器與 ID 比對）\n   - 讀取完整的工作階段檔案。\n   - 建構 `SessionInfo` 物件（`id`、`cwd`、`title`、`messageCount`、`firstMessage`、`allMessagesText`、時間戳記）。\n   - 捨棄 `message` 項目為零的工作階段。\n   - 依 `modified` 降序排列。\n\n### 中繼資料備援行為\n\n對於近期摘要（`RecentSessionInfo`）：\n\n- 顯示名稱偏好順序：`header.title` -> 第一個使用者提示 -> `header.id` -> 檔案名稱\n- 名稱被截斷為 40 個字元以供精簡顯示\n- 控制字元/換行符號會從標題衍生的名稱中被移除/淨化\n\n對於 `SessionInfo` 列表項目：\n\n- `title` 為 `header.title` 或最新壓縮的 `shortSummary`\n- `firstMessage` 為第一個使用者訊息文字或 `\"(no messages)\"`\n\n## `--continue` 解析與終端機書籤偏好\n\n`SessionManager.continueRecent(cwd, sessionDir?)` 依以下順序解析目標：\n\n1. 讀取終端機範圍的書籤（`~/.xcsh/agent/terminal-sessions/<terminal-id>`）\n2. 驗證書籤：\n   - 可識別當前終端機\n   - 書籤的 cwd 與當前 cwd 相符（解析路徑比對）\n   - 參照的檔案仍然存在\n3. 若書籤無效/遺失，則退回到工作階段目錄中依 mtime 排列的最新檔案（`findMostRecentSession`）\n4. 若未找到任何檔案，則建立新的工作階段\n\n終端機 ID 衍生偏好使用 TTY 路徑，並退回到基於環境變數的識別碼（`KITTY_WINDOW_ID`、`TMUX_PANE`、`TERM_SESSION_ID`、`WT_SESSION`）。\n\n書籤寫入為盡力而為且非致命性的。\n\n## 啟動時恢復目標解析（`main.ts`）\n\n### `--resume <value>`\n\n`createSessionManager(...)` 以兩種模式處理字串值的 `--resume`：\n\n1. 類路徑值（包含 `/`、`\\\\`，或以 `.jsonl` 結尾）\n   - 直接 `SessionManager.open(sessionArg, parsed.sessionDir)`\n\n2. ID 前綴值\n   - 在 `SessionManager.list(cwd, sessionDir)` 中以 `id.startsWith(sessionArg)` 尋找匹配\n   - 若本地無匹配且未強制指定 `sessionDir`，則嘗試 `SessionManager.listAll()`\n   - 使用第一個匹配結果（無歧義提示）\n\n跨專案匹配行為：\n\n- 若匹配的工作階段 cwd 與當前 cwd 不同，CLI 會提示是否要分支到當前專案\n- 是 -> `SessionManager.forkFrom(...)`\n- 否 -> 拋出錯誤（`Session \"...\" is in another project (...)`）\n\n無匹配 -> 拋出錯誤（`Session \"...\" not found.`）。\n\n### `--resume`（無值）\n\n在初始工作階段管理器建構後處理：\n\n1. 以 `SessionManager.list(cwd, parsed.sessionDir)` 列出本地工作階段\n2. 若為空：印出 `No sessions found` 並提前結束\n3. 開啟 TUI 選擇器（`selectSession`）\n4. 若取消：印出 `No session selected` 並提前結束\n5. 若選取：`SessionManager.open(selectedPath)`\n\n### `--continue`\n\n直接使用 `SessionManager.continueRecent(...)`（上述的書籤優先行為）。\n\n## 選擇器式選取內部機制\n\n## CLI 選擇器（`src/cli/session-picker.ts`）\n\n`selectSession(sessions)` 建立一個獨立的 TUI 搭配 `SessionSelectorComponent`，並精確解析一次：\n\n- 選取 -> 解析為選取的路徑\n- 取消（Esc） -> 解析為 `null`\n- 強制結束（Ctrl+C 路徑） -> 停止 TUI 並 `process.exit(0)`\n\n## 互動式工作階段內選擇器（`SelectorController.showSessionSelector`）\n\n流程：\n\n1. 透過 `SessionManager.list(currentCwd, currentSessionDir)` 從當前工作階段目錄取得工作階段\n2. 使用 `showSelector(...)` 在編輯器區域掛載 `SessionSelectorComponent`\n3. 回呼：\n   - 選取 -> 關閉選擇器並呼叫 `handleResumeSession(sessionPath)`\n   - 取消 -> 還原編輯器並重新渲染\n   - 結束 -> `ctx.shutdown()`\n\n## 工作階段選擇器元件行為\n\n`SessionList` 支援：\n\n- 方向鍵/翻頁導覽\n- Enter 選取\n- Esc 取消\n- Ctrl+C 結束\n- 跨工作階段 id/標題/cwd/第一則訊息/所有訊息/路徑的模糊搜尋\n\n空列表渲染行為：\n\n- 渲染一則訊息而非當機\n- 在空列表上按 Enter 不執行任何動作（無回呼）\n- Esc/Ctrl+C 仍然有效\n\n注意事項：UI 文字顯示 `Press Tab to view all`，但此元件目前沒有 Tab 處理器，且當前的接線僅列出當前範圍的工作階段。\n\n## 執行時期切換執行（`AgentSession.switchSession`）\n\n`switchSession(sessionPath)` 是核心的行程內切換路徑。\n\n生命週期/狀態轉換：\n\n1. 擷取 `previousSessionFile`\n2. 發送 `session_before_switch` 鉤子事件（`reason: \"resume\"`，可取消）\n3. 若被取消 -> 回傳 `false` 且不切換\n4. 從當前代理事件串流斷開連線\n5. 中止作用中的生成/工具流程\n6. 清除已排入佇列的導向/後續/下一輪訊息緩衝區\n7. 刷新工作階段寫入器（`sessionManager.flush()`）以持久化待處理的寫入\n8. `sessionManager.setSessionFile(sessionPath)`\n   - 更新工作階段檔案指標\n   - 寫入終端機書籤\n   - 載入項目 / 遷移 / blob 解析 / 重新索引\n   - 若檔案資料遺失/無效：在該路徑初始化新的工作階段並重寫標頭\n9. 更新 `agent.sessionId`\n10. 透過 `buildSessionContext()` 重建上下文\n11. 發送 `session_switch` 鉤子事件（`reason: \"resume\"`，`previousSessionFile`）\n12. 以重建的上下文替換代理訊息\n13. 從 `sessionContext.models.default` 還原預設模型（若可用且存在於模型登錄中）\n14. 還原思考層級：\n    - 若分支已有 `thinking_level_change`，套用已儲存的工作階段層級\n    - 否則從設定衍生預設思考層級，限縮至模型能力範圍，設定後附加新的 `thinking_level_change` 項目\n15. 重新連接代理監聽器並回傳 `true`\n\n## 互動式切換後的 UI 狀態重建\n\n`SelectorController.handleResumeSession` 在 `switchSession` 前後執行 UI 重設：\n\n- 停止載入動畫\n- 清除狀態容器\n- 清除待處理訊息 UI 和待處理工具對應\n- 重設串流元件/訊息參照\n- 呼叫 `session.switchSession(...)`\n- 清除聊天容器並從工作階段上下文重新渲染（`renderInitialMessages`）\n- 從新工作階段的產出物重新載入待辦事項\n- 顯示 `Resumed session`\n\n因此可見的對話/待辦事項狀態是從新的工作階段檔案重建的。\n\n## 啟動時恢復 vs 工作階段內切換\n\n### 啟動時恢復（`--continue`、`--resume`、直接開啟）\n\n- 工作階段檔案在 `createAgentSession(...)` 之前選定。\n- `sdk.ts` 建構 `existingSession = sessionManager.buildSessionContext()`。\n- 代理訊息在工作階段建立期間還原一次。\n- 模型/思考在建立期間選定（包含還原/備援邏輯）。\n- 互動模式接著執行 `#restoreModeFromSession()` 以重新進入已持久化的模式狀態（目前為 plan/plan_paused）。\n\n### 工作階段內切換（`/resume` 式選擇器路徑）\n\n- 在已執行的 `AgentSession` 上使用 `AgentSession.switchSession(...)`。\n- 訊息/模型/思考立即就地重建。\n- 發送 `session_before_switch`/`session_switch` 鉤子事件。\n- UI 聊天/待辦事項已重新整理。\n- 選擇器流程中沒有專門的切換後模式還原呼叫；模式重新進入行為與啟動時的 `#restoreModeFromSession()` 不對稱。\n\n## 失敗與邊緣案例行為\n\n### 取消路徑\n\n- CLI 選擇器取消 -> 回傳 `null`，呼叫者印出 `No session selected`，程序提前結束。\n- 互動式選擇器取消 -> 編輯器還原，無工作階段變更。\n- 鉤子取消（`session_before_switch`） -> `switchSession()` 回傳 `false`。\n\n### 空列表路徑\n\n- CLI `--resume`（無值）：空列表印出 `No sessions found` 並結束。\n- 互動式選擇器：空列表渲染訊息且保持可取消。\n\n### 目標工作階段檔案遺失/無效\n\n當開啟/切換至特定路徑（`setSessionFile`）時：\n\n- ENOENT -> 視為空 -> 在該確切路徑初始化新工作階段並持久化。\n- 格式錯誤/無效標頭（或實際上無法讀取的解析項目） -> 視為空 -> 初始化新工作階段並持久化。\n\n這是復原行為，而非硬性失敗。\n\n### 硬性失敗\n\n切換/開啟在真正的 I/O 失敗（權限錯誤、重寫失敗等）時仍可能拋出例外，這些例外會傳播至呼叫者。\n\n### ID 前綴匹配注意事項\n\n- ID 匹配使用 `startsWith` 並取排序列表中的第一個匹配。\n- 若多個工作階段共用前綴，不會出現歧義 UI。\n- `SessionManager.list(...)` 會排除訊息為零的工作階段，因此這些工作階段無法透過 ID 匹配/列表選擇器恢復。\n",
	"zh-tw/sessions/session-tree-plan.md": "---\ntitle: 會話樹架構\ndescription: 具有分支、導航和父子對話關係的會話樹架構。\nsidebar:\n  order: 2\n  label: 樹架構\ni18n:\n  sourceHash: bd8b78d6c33a\n  translator: machine\n---\n\n# 會話樹架構（當前）\n\n參考：[session.md](./session.md)\n\n本文件描述會話樹導航目前的運作方式：記憶體內樹模型、葉節點移動規則、分支行為，以及擴充功能/事件整合。\n\n## 此子系統是什麼\n\n會話以僅追加的條目日誌形式儲存，但執行時行為是基於樹結構的：\n\n- 每個非標頭條目都有 `id` 和 `parentId`。\n- 活動位置是 `SessionManager` 中的 `leafId`。\n- 追加條目時總是建立當前葉節點的子節點。\n- 分支**不會**重寫歷史；它只會在下次追加之前改變葉節點指向的位置。\n\n關鍵檔案：\n\n- `src/session/session-manager.ts` — 樹資料模型、遍歷、葉節點移動、分支/會話擷取\n- `src/session/agent-session.ts` — `/tree` 導航流程、摘要生成、鉤子/事件發射\n- `src/modes/components/tree-selector.ts` — 互動式樹 UI 行為與過濾\n- `src/modes/controllers/selector-controller.ts` — `/tree` 和 `/branch` 的選擇器協調\n- `src/modes/controllers/input-controller.ts` — 命令路由（`/tree`、`/branch`、雙擊 Escape 行為）\n- `src/session/messages.ts` — 將 `branch_summary`、`compaction` 和 `custom_message` 條目轉換為 LLM 上下文訊息\n\n## `SessionManager` 中的樹資料模型\n\n執行時索引：\n\n- `#byId: Map<string, SessionEntry>` — 對任何條目的快速查找\n- `#leafId: string | null` — 樹中的當前位置\n- `#labelsById: Map<string, string>` — 依目標條目 id 解析的標籤\n\n樹 API：\n\n- `getBranch(fromId?)` 沿父連結走到根節點並返回根→節點路徑\n- `getTree()` 返回 `SessionTreeNode[]`（`entry`、`children`、`label`）\n  - 父連結轉換為子陣列\n  - 缺少父節點的條目被視為根節點\n  - 子節點按時間戳從最舊到最新排序\n- `getChildren(parentId)` 返回直接子節點\n- `getLabel(id)` 從 `labelsById` 解析當前標籤\n\n`getTree()` 是執行時投影；持久化仍為僅追加的 JSONL 條目。\n\n## 葉節點移動語義\n\n有三個葉節點移動原語：\n\n1. `branch(entryId)`\n   - 驗證條目存在\n   - 設定 `leafId = entryId`\n   - 不寫入新條目\n\n2. `resetLeaf()`\n   - 設定 `leafId = null`\n   - 下次追加會建立新的根條目（`parentId = null`）\n\n3. `branchWithSummary(branchFromId, summary, details?, fromExtension?)`\n   - 接受 `branchFromId: string | null`\n   - 設定 `leafId = branchFromId`\n   - 追加一個 `branch_summary` 條目作為該葉節點的子節點\n   - 當 `branchFromId` 為 `null` 時，`fromId` 會持久化為 `\"root\"`\n\n## `/tree` 導航行為（同一會話檔案內）\n\n`AgentSession.navigateTree()` 是導航，不是檔案分叉。\n\n流程：\n\n1. 驗證目標並計算被放棄的路徑（`collectEntriesForBranchSummary`）\n2. 發射帶有 `TreePreparation` 的 `session_before_tree` 事件\n3. 可選地對被放棄的條目進行摘要（鉤子提供的摘要或內建摘要器）\n4. 計算新的葉節點目標：\n   - 選擇 **user** 訊息：葉節點移動到其父節點，訊息文字返回用於編輯器預填\n   - 選擇 **custom_message**：與 user 訊息相同的規則（葉節點 = 父節點，文字預填編輯器）\n   - 選擇任何其他條目：葉節點 = 所選條目 id\n5. 套用葉節點移動：\n   - 有摘要時：`branchWithSummary(newLeafId, ...)`\n   - 無摘要且 `newLeafId === null` 時：`resetLeaf()`\n   - 否則：`branch(newLeafId)`\n6. 從新葉節點重建代理上下文並發射 `session_tree` 事件\n\n重要：摘要條目附加在**新的導航位置**，而非被放棄的分支尾端。\n\n## `/branch` 行為（新會話檔案）\n\n`/branch` 和 `/tree` 是刻意不同的：\n\n- `/tree` 在當前會話檔案內導航。\n- `/branch` 建立新的會話分支檔案（或在非持久化模式下進行記憶體內替換）。\n\n使用者面向的 `/branch` 流程（`SelectorController.showUserMessageSelector` → `AgentSession.branch`）：\n\n- 分支來源必須是 **user 訊息**。\n- 擷取所選使用者文字用於編輯器預填。\n- 如果所選 user 訊息是根節點（`parentId === null`）：透過 `newSession({ parentSession: previousSessionFile })` 開始新會話。\n- 否則：`createBranchedSession(selectedEntry.parentId)` 在所選提示邊界處分叉歷史。\n\n`SessionManager.createBranchedSession(leafId)` 的具體內容：\n\n- 透過 `getBranch(leafId)` 建立根→葉路徑；如缺失則拋出錯誤。\n- 從複製路徑中排除現有的 `label` 條目。\n- 為路徑中保留的條目從已解析的 `labelsById` 重建新的標籤條目。\n- 持久化模式：寫入新的 JSONL 檔案並切換管理器到該檔案；返回新檔案路徑。\n- 記憶體內模式：替換記憶體內條目；返回 `undefined`。\n\n## 上下文重建與摘要/自訂整合\n\n`buildSessionContext()`（在 `session-manager.ts` 中）解析活動的根→葉路徑並建立有效的 LLM 上下文狀態：\n\n- 追蹤路徑上最新的 thinking/model/mode/ttsr 狀態。\n- 處理路徑上最新的壓縮：\n  - 先發射壓縮摘要\n  - 從 `firstKeptEntryId` 到壓縮點重放保留的訊息\n  - 然後重放壓縮後的訊息\n- 將 `branch_summary` 和 `custom_message` 條目包含為 `AgentMessage` 物件。\n\n`session/messages.ts` 接著將這些訊息類型對映為模型輸入：\n\n- `branchSummary` 和 `compactionSummary` 成為 user 角色的模板化上下文訊息\n- `custom`/`hookMessage` 成為 user 角色的內容訊息\n\n因此，樹的移動是透過改變活動葉路徑來改變上下文，而非修改舊條目。\n\n## 標籤與樹 UI 行為\n\n標籤持久化：\n\n- `appendLabelChange(targetId, label?)` 在當前葉節點鏈上寫入 `label` 條目。\n- `labelsById` 會立即更新（設定或刪除）。\n- `getTree()` 將當前標籤解析到每個返回的節點上。\n\n樹選擇器行為（`tree-selector.ts`）：\n\n- 將樹扁平化以供導航，保持活動路徑高亮，並優先顯示活動分支。\n- 支援過濾模式：`default`、`no-tools`、`user-only`、`labeled-only`、`all`。\n- 支援對已渲染語義內容的自由文字搜尋。\n- `Shift+L` 開啟內嵌標籤編輯並透過 `appendLabelChange` 寫入。\n\n命令路由：\n\n- `/tree` 總是開啟樹選擇器。\n- `/branch` 開啟 user 訊息選擇器，除非 `doubleEscapeAction=tree`，此時也使用樹選擇器 UX。\n\n## 樹操作的擴充功能與鉤子接觸點\n\n命令時擴充功能 API（`ExtensionCommandContext`）：\n\n- `branch(entryId)` — 建立分支會話檔案\n- `navigateTree(targetId, { summarize? })` — 在當前樹/檔案內移動\n\n樹導航相關事件：\n\n- `session_before_tree`\n  - 接收 `TreePreparation`：\n    - `targetId`\n    - `oldLeafId`\n    - `commonAncestorId`\n    - `entriesToSummarize`\n    - `userWantsSummary`\n  - 可以取消導航\n  - 可以提供摘要酬載以替代內建摘要器\n  - 接收中止 `signal`（Escape 取消路徑）\n- `session_tree`\n  - 發射 `newLeafId`、`oldLeafId`\n  - 建立摘要時包含 `summaryEntry`\n  - `fromExtension` 指示摘要來源\n\n相鄰但相關的生命週期鉤子：\n\n- `session_before_branch` / `session_branch` 用於 `/branch` 流程\n- `session_before_compact`、`session.compacting`、`session_compact` 用於後續影響樹上下文重建的壓縮條目\n\n## 實際限制與邊界條件\n\n- `branch()` 不能以 `null` 為目標；使用 `resetLeaf()` 來達到首條目之前的根狀態。\n- `branchWithSummary()` 支援 `null` 目標並記錄 `fromId: \"root\"`。\n- 在樹選擇器中選擇當前葉節點是無操作。\n- 摘要生成需要活動模型；如果缺少，摘要導航會快速失敗。\n- 如果摘要生成被中止，導航會被取消且葉節點不變。\n- 記憶體內會話的 `createBranchedSession` 永遠不會返回分支檔案路徑。\n\n## 仍存在的舊版相容性\n\n會話遷移仍在載入時執行：\n\n- v1→v2 添加 `id`/`parentId` 並將壓縮索引錨點轉換為 id 錨點\n- v2→v3 將舊版 `hookMessage` 角色遷移為 `custom`\n\n遷移後的當前執行時行為是版本 3 的樹語義。\n",
	"zh-tw/sessions/session.md": "---\ntitle: 會話儲存與項目模型\ndescription: >-\n  Append-only session storage model with entry types, persistence, and migration\n  between formats.\nsidebar:\n  order: 1\n  label: 儲存與項目模型\ni18n:\n  sourceHash: 42fe17549e00\n  translator: machine\n---\n\n# 會話儲存與項目模型\n\n本文件是程式碼代理會話如何表示、持久化、遷移及在執行時重建的權威來源。\n\n## 範圍\n\n涵蓋內容：\n\n- 會話 JSONL 格式與版本管理\n- 項目分類與樹狀語義（`id`/`parentId` + 葉節點指標）\n- 載入舊版或格式錯誤檔案時的遷移/相容性行為\n- 上下文重建（`buildSessionContext`）\n- 持久化保證、失敗行為、截斷/blob 外部化\n- 儲存抽象層（`FileSessionStorage`、`MemorySessionStorage`）及相關工具\n\n不涵蓋 `/tree` UI 渲染行為，除非涉及影響會話資料的語義。\n\n## 實作檔案\n\n- [`src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`src/session/messages.ts`](../../packages/coding-agent/src/session/messages.ts)\n- [`src/session/session-storage.ts`](../../packages/coding-agent/src/session/session-storage.ts)\n- [`src/session/history-storage.ts`](../../packages/coding-agent/src/session/history-storage.ts)\n- [`src/session/blob-store.ts`](../../packages/coding-agent/src/session/blob-store.ts)\n\n## 磁碟佈局\n\n預設會話檔案位置：\n\n```text\n~/.xcsh/agent/sessions/--<cwd-encoded>--/<timestamp>_<sessionId>.jsonl\n```\n\n`<cwd-encoded>` 由工作目錄衍生而來，方式是移除前導斜線並將 `/`、`\\\\` 和 `:` 替換為 `-`。\n\nBlob 儲存位置：\n\n```text\n~/.xcsh/agent/blobs/<sha256>\n```\n\n終端機麵包屑檔案寫入位置：\n\n```text\n~/.xcsh/agent/terminal-sessions/<terminal-id>\n```\n\n麵包屑內容為兩行：原始工作目錄，然後是會話檔案路徑。`continueRecent()` 會優先使用此終端機範圍的指標，之後才掃描最近修改時間。\n\n## 檔案格式\n\n會話檔案為 JSONL 格式：每行一個 JSON 物件。\n\n- 第 1 行始終為會話標頭（`type: \"session\"`）。\n- 其餘行為 `SessionEntry` 值。\n- 項目在執行時僅追加；分支導航移動指標（`leafId`）而非修改既有項目。\n\n### 標頭（`SessionHeader`）\n\n```json\n{\n  \"type\": \"session\",\n  \"version\": 3,\n  \"id\": \"1f9d2a6b9c0d1234\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\",\n  \"cwd\": \"/work/pi\",\n  \"title\": \"optional session title\",\n  \"parentSession\": \"optional lineage marker\"\n}\n```\n\n備註：\n\n- `version` 在 v1 檔案中為可選；缺少表示 v1。\n- `parentSession` 是不透明的譜系字串。目前的程式碼依據流程（`fork`、`forkFrom`、`createBranchedSession` 或明確的 `newSession({ parentSession })`）寫入會話 id 或會話路徑。視為中繼資料，而非具型別的外鍵。\n\n### 項目基底（`SessionEntryBase`）\n\n所有非標頭項目包含：\n\n```json\n{\n  \"type\": \"...\",\n  \"id\": \"8-char-id\",\n  \"parentId\": \"previous-or-branch-parent\",\n  \"timestamp\": \"2026-02-16T10:20:30.000Z\"\n}\n```\n\n`parentId` 對於根項目可為 `null`（首次追加，或在 `resetLeaf()` 之後）。\n\n## 項目分類\n\n`SessionEntry` 是以下類型的聯合型別：\n\n- `message`\n- `thinking_level_change`\n- `model_change`\n- `compaction`\n- `branch_summary`\n- `custom`\n- `custom_message`\n- `label`\n- `ttsr_injection`\n- `session_init`\n- `mode_change`\n\n### `message`\n\n直接儲存 `AgentMessage`。\n\n```json\n{\n  \"type\": \"message\",\n  \"id\": \"a1b2c3d4\",\n  \"parentId\": null,\n  \"timestamp\": \"2026-02-16T10:21:00.000Z\",\n  \"message\": {\n    \"role\": \"assistant\",\n    \"provider\": \"anthropic\",\n    \"model\": \"claude-sonnet-4-5\",\n    \"content\": [{ \"type\": \"text\", \"text\": \"Done.\" }],\n    \"usage\": { \"input\": 100, \"output\": 20, \"cacheRead\": 0, \"cacheWrite\": 0, \"cost\": { \"input\": 0, \"output\": 0, \"cacheRead\": 0, \"cacheWrite\": 0, \"total\": 0 } },\n    \"timestamp\": 1760000000000\n  }\n}\n```\n\n### `model_change`\n\n```json\n{\n  \"type\": \"model_change\",\n  \"id\": \"b1c2d3e4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:21:30.000Z\",\n  \"model\": \"openai/gpt-4o\",\n  \"role\": \"default\"\n}\n```\n\n`role` 為可選；缺少時在上下文重建中視為 `default`。\n\n### `thinking_level_change`\n\n```json\n{\n  \"type\": \"thinking_level_change\",\n  \"id\": \"c1d2e3f4\",\n  \"parentId\": \"b1c2d3e4\",\n  \"timestamp\": \"2026-02-16T10:22:00.000Z\",\n  \"thinkingLevel\": \"high\"\n}\n```\n\n### `compaction`\n\n```json\n{\n  \"type\": \"compaction\",\n  \"id\": \"d1e2f3a4\",\n  \"parentId\": \"c1d2e3f4\",\n  \"timestamp\": \"2026-02-16T10:23:00.000Z\",\n  \"summary\": \"Conversation summary\",\n  \"shortSummary\": \"Short recap\",\n  \"firstKeptEntryId\": \"a1b2c3d4\",\n  \"tokensBefore\": 42000,\n  \"details\": { \"readFiles\": [\"src/a.ts\"] },\n  \"preserveData\": { \"hookState\": true },\n  \"fromExtension\": false\n}\n```\n\n### `branch_summary`\n\n```json\n{\n  \"type\": \"branch_summary\",\n  \"id\": \"e1f2a3b4\",\n  \"parentId\": \"a1b2c3d4\",\n  \"timestamp\": \"2026-02-16T10:24:00.000Z\",\n  \"fromId\": \"a1b2c3d4\",\n  \"summary\": \"Summary of abandoned path\",\n  \"details\": { \"note\": \"optional\" },\n  \"fromExtension\": true\n}\n```\n\n若從根節點分支（`branchFromId === null`），`fromId` 為字面字串 `\"root\"`。\n\n### `custom`\n\n擴充功能狀態持久化；被 `buildSessionContext` 忽略。\n\n```json\n{\n  \"type\": \"custom\",\n  \"id\": \"f1a2b3c4\",\n  \"parentId\": \"e1f2a3b4\",\n  \"timestamp\": \"2026-02-16T10:25:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"data\": { \"state\": 1 }\n}\n```\n\n### `custom_message`\n\n擴充功能提供的訊息，會參與 LLM 上下文。\n\n```json\n{\n  \"type\": \"custom_message\",\n  \"id\": \"a2b3c4d5\",\n  \"parentId\": \"f1a2b3c4\",\n  \"timestamp\": \"2026-02-16T10:26:00.000Z\",\n  \"customType\": \"my-extension\",\n  \"content\": \"Injected context\",\n  \"display\": true,\n  \"details\": { \"debug\": false }\n}\n```\n\n### `label`\n\n```json\n{\n  \"type\": \"label\",\n  \"id\": \"b2c3d4e5\",\n  \"parentId\": \"a2b3c4d5\",\n  \"timestamp\": \"2026-02-16T10:27:00.000Z\",\n  \"targetId\": \"a1b2c3d4\",\n  \"label\": \"checkpoint\"\n}\n```\n\n`label: undefined` 會清除 `targetId` 的標籤。\n\n### `ttsr_injection`\n\n```json\n{\n  \"type\": \"ttsr_injection\",\n  \"id\": \"c2d3e4f5\",\n  \"parentId\": \"b2c3d4e5\",\n  \"timestamp\": \"2026-02-16T10:28:00.000Z\",\n  \"injectedRules\": [\"ruleA\", \"ruleB\"]\n}\n```\n\n### `session_init`\n\n```json\n{\n  \"type\": \"session_init\",\n  \"id\": \"d2e3f4a5\",\n  \"parentId\": \"c2d3e4f5\",\n  \"timestamp\": \"2026-02-16T10:29:00.000Z\",\n  \"systemPrompt\": \"...\",\n  \"task\": \"...\",\n  \"tools\": [\"read\", \"edit\"],\n  \"outputSchema\": { \"type\": \"object\" }\n}\n```\n\n### `mode_change`\n\n```json\n{\n  \"type\": \"mode_change\",\n  \"id\": \"e2f3a4b5\",\n  \"parentId\": \"d2e3f4a5\",\n  \"timestamp\": \"2026-02-16T10:30:00.000Z\",\n  \"mode\": \"plan\",\n  \"data\": { \"planFile\": \"/tmp/plan.md\" }\n}\n```\n\n## 版本管理與遷移\n\n目前會話版本：`3`。\n\n### v1 -> v2\n\n當標頭 `version` 缺少或 `< 2` 時套用：\n\n- 為每個非標頭項目新增 `id` 和 `parentId`。\n- 使用檔案順序重建線性父鏈。\n- 當存在時將壓縮欄位 `firstKeptEntryIndex` 遷移為 `firstKeptEntryId`。\n- 設定標頭 `version = 2`。\n\n### v2 -> v3\n\n當標頭 `version < 3` 時套用：\n\n- 對於 `message` 項目：將舊版 `message.role === \"hookMessage\"` 重寫為 `\"custom\"`。\n- 設定標頭 `version = 3`。\n\n### 遷移觸發與持久化\n\n- 遷移在會話載入時執行（`setSessionFile`）。\n- 若執行了任何遷移，整個檔案會立即重寫至磁碟。\n- 遷移先修改記憶體中的項目，然後持久化重寫的 JSONL。\n\n## 載入與相容性行為\n\n`loadEntriesFromFile(path)` 行為：\n\n- 檔案不存在（`ENOENT`）-> 回傳 `[]`。\n- 無法解析的行由寬容的 JSONL 解析器（`parseJsonlLenient`）處理。\n- 若第一個已解析的項目不是有效的會話標頭（`type !== \"session\"` 或缺少字串 `id`）-> 回傳 `[]`。\n\n`SessionManager.setSessionFile()` 行為：\n\n- 從載入器取得的 `[]` 視為空/不存在的會話，並在該路徑以新的已初始化會話檔案取代。\n- 有效檔案會被載入、必要時遷移、解析 blob 參考，然後建立索引。\n\n## 樹狀結構與葉節點語義\n\n底層模型為僅追加樹狀結構 + 可變葉節點指標：\n\n- 每個追加方法恰好建立一個新項目，其 `parentId` 為目前的 `leafId`。\n- 新項目成為新的 `leafId`。\n- `branch(entryId)` 僅移動 `leafId`；既有項目保持不變。\n- `resetLeaf()` 設定 `leafId = null`；下次追加建立新的根項目（`parentId: null`）。\n- `branchWithSummary()` 設定葉節點至分支目標並追加 `branch_summary` 項目。\n\n`getEntries()` 以插入順序回傳所有非標頭項目。在正常操作中不會刪除既有項目；重寫在更新表示方式的同時保留邏輯歷史（遷移、移動、目標化重寫輔助程式）。\n\n## 上下文重建（`buildSessionContext`）\n\n`buildSessionContext(entries, leafId, byId?)` 解析要傳送給模型的內容。\n\n演算法：\n\n1. 確定葉節點：\n   - `leafId === null` -> 回傳空上下文。\n   - 明確的 `leafId` -> 使用該項目（若找到）。\n   - 否則退回至最後一個項目。\n2. 從葉節點沿 `parentId` 鏈走到根節點，然後反轉為根->葉路徑。\n3. 沿路徑推導執行時狀態：\n   - `thinkingLevel` 來自最新的 `thinking_level_change`（預設 `\"off\"`）\n   - 模型對映來自 `model_change` 項目（`role ?? \"default\"`）\n   - 若無明確的模型變更，退回 `models.default` 從助理訊息的 provider/model 取得\n   - 從所有 `ttsr_injection` 項目取得去重的 `injectedTtsrRules`\n   - 模式/modeData 來自最新的 `mode_change`（預設模式 `\"none\"`）\n4. 建立訊息列表：\n   - `message` 項目直接傳遞\n   - `custom_message` 項目透過 `createCustomMessage` 成為 `custom` AgentMessages\n   - `branch_summary` 項目透過 `createBranchSummaryMessage` 成為 `branchSummary` AgentMessages\n   - 若路徑上存在 `compaction`：\n     - 先發出壓縮摘要（`createCompactionSummaryMessage`）\n     - 發出從 `firstKeptEntryId` 到壓縮邊界的路徑項目\n     - 發出壓縮邊界之後的項目\n\n`custom` 和 `session_init` 項目不直接注入模型上下文。\n\n## 持久化保證與失敗模型\n\n### 持久化 vs 記憶體模式\n\n- `SessionManager.create/open/continueRecent/forkFrom` -> 持久化模式（`persist = true`）。\n- `SessionManager.inMemory` -> 非持久化模式（`persist = false`），使用 `MemorySessionStorage`。\n\n### 寫入管線\n\n寫入透過內部 promise 鏈（`#persistChain`）和 `NdjsonFileWriter` 序列化。\n\n- `append*` 立即更新記憶體狀態。\n- 持久化延遲到至少存在一個助理訊息時才進行。\n  - 第一個助理訊息之前：項目保留在記憶體中；不進行檔案追加。\n  - 當第一個助理訊息存在時：完整的記憶體會話被刷新至檔案。\n  - 之後：新項目以增量方式追加。\n\n程式碼中的理由：避免持久化從未產生助理回應的會話。\n\n### 持久性操作\n\n- `flush()` 刷新寫入器並呼叫 `fsync()`。\n- 原子性完整重寫（`#rewriteFile`）寫入暫存檔案，刷新+fsync，關閉，然後重新命名覆蓋目標。\n- 用於遷移、`setSessionName`、`rewriteEntries`、移動操作及工具呼叫參數重寫。\n\n### 錯誤行為\n\n- 持久化錯誤會被鎖定（`#persistError`）並在後續操作中重新拋出。\n- 第一個錯誤會連同會話檔案上下文記錄一次。\n- 寫入器關閉為盡力嘗試，但會傳播第一個有意義的錯誤。\n\n## 資料大小控制與 Blob 外部化\n\n在持久化項目之前：\n\n- 大型字串會截斷至 `MAX_PERSIST_CHARS`（500,000 字元）並附帶通知：\n  - `\"[Session persistence truncated large content]\"`\n- 暫態欄位 `partialJson` 和 `jsonlEvents` 會被移除。\n- 若物件同時具有 `content` 和 `lineCount`，行數會在截斷後重新計算。\n- `content` 陣列中 base64 長度 >= 1024 的影像區塊會外部化為 blob 參考：\n  - 儲存為 `blob:sha256:<hash>`\n  - 原始位元組寫入 blob 儲存（`BlobStore.put`）\n\n載入時，blob 參考會為 message/custom_message 影像區塊解析回 base64。\n\n## 儲存抽象層\n\n`SessionStorage` 介面提供 `SessionManager` 使用的所有檔案系統操作：\n\n- 同步：`ensureDirSync`、`existsSync`、`writeTextSync`、`statSync`、`listFilesSync`\n- 非同步：`exists`、`readText`、`readTextPrefix`、`writeText`、`rename`、`unlink`、`openWriter`\n\n實作：\n\n- `FileSessionStorage`：真實檔案系統（Bun + node fs）\n- `MemorySessionStorage`：基於 map 的記憶體實作，用於測試/非持久化會話\n\n`SessionStorageWriter` 公開 `writeLine`、`flush`、`fsync`、`close`、`getError`。\n\n## 會話探索工具\n\n定義在 `session-manager.ts` 中：\n\n- `getRecentSessions(sessionDir, limit)` -> 用於 UI/會話選擇器的輕量中繼資料\n- `findMostRecentSession(sessionDir)` -> 依修改時間最新者\n- `list(cwd, sessionDir?)` -> 單一專案範圍內的會話\n- `listAll()` -> `~/.xcsh/agent/sessions` 下所有專案範圍的會話\n\n中繼資料擷取盡可能僅讀取前綴（`readTextPrefix(..., 4096)`）。\n\n## 相關但獨立的：提示歷史儲存\n\n`HistoryStorage`（`history-storage.ts`）是獨立的 SQLite 子系統，用於提示回憶/搜尋，而非會話重播。\n\n- 資料庫：`~/.xcsh/agent/history.db`\n- 資料表：`history(id, prompt, created_at, cwd)`\n- FTS5 索引：`history_fts`，透過觸發器維護同步\n- 使用記憶體中的最後提示快取來去重連續相同的提示\n- 非同步插入（`setImmediate`），使提示捕獲不會阻塞回合執行\n\n使用會話檔案進行對話圖/狀態重播；使用 `HistoryStorage` 進行提示歷史使用者體驗。\n",
	"zh-tw/sessions/ttsr-injection-lifecycle.md": "---\ntitle: TTSR 注入生命週期\ndescription: TTSR（tool-use、tool-result、system-reminder）用於上下文管理的注入生命週期。\nsidebar:\n  order: 9\n  label: TTSR 注入\ni18n:\n  sourceHash: d6179a286584\n  translator: machine\n---\n\n# TTSR 注入生命週期\n\n本文件涵蓋目前 Time Traveling Stream Rules（TTSR）從規則發現到串流中斷、重試注入、擴充功能通知及會話狀態處理的完整執行路徑。\n\n## 實作檔案\n\n- [`../src/sdk.ts`](../../packages/coding-agent/src/sdk.ts)\n- [`../src/export/ttsr.ts`](../../packages/coding-agent/src/export/ttsr.ts)\n- [`../src/session/agent-session.ts`](../../packages/coding-agent/src/session/agent-session.ts)\n- [`../src/session/session-manager.ts`](../../packages/coding-agent/src/session/session-manager.ts)\n- [`../src/prompts/system/ttsr-interrupt.md`](../../packages/coding-agent/src/prompts/system/ttsr-interrupt.md)\n- [`../src/capability/index.ts`](../../packages/coding-agent/src/capability/index.ts)\n- [`../src/extensibility/extensions/types.ts`](../../packages/coding-agent/src/extensibility/extensions/types.ts)\n- [`../src/extensibility/hooks/types.ts`](../../packages/coding-agent/src/extensibility/hooks/types.ts)\n- [`../src/extensibility/custom-tools/types.ts`](../../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n\n## 1. 發現來源與規則註冊\n\n在會話建立時，`createAgentSession()` 會載入所有已發現的規則並建構一個 `TtsrManager`：\n\n```ts\nconst ttsrSettings = settings.getGroup(\"ttsr\");\nconst ttsrManager = new TtsrManager(ttsrSettings);\nconst rulesResult = await loadCapability<Rule>(ruleCapability.id, { cwd });\nfor (const rule of rulesResult.items) {\n  if (rule.ttsrTrigger) ttsrManager.addRule(rule);\n}\n```\n\n### 預註冊去重行為\n\n`loadCapability(\"rules\")` 以 `rule.name` 進行去重，採用先到先贏的語意（較高提供者優先順序優先）。被遮蔽的重複項在 TTSR 註冊之前即被移除。\n\n### `TtsrManager.addRule()` 行為\n\n在以下情況下會跳過註冊：\n\n- `rule.ttsrTrigger` 不存在\n- 相同 `rule.name` 的規則已在此管理器中註冊\n- 正規表達式編譯失敗（`new RegExp(rule.ttsrTrigger)` 拋出例外）\n\n無效的正規表達式觸發器會記錄為警告並被忽略；會話啟動將繼續進行。\n\n### 設定注意事項\n\n`TtsrSettings.enabled` 會被載入管理器，但目前在執行期間閘控中並未被檢查。只要規則存在，匹配仍會執行。\n\n## 2. 串流監控生命週期\n\nTTSR 偵測在 `AgentSession.#handleAgentEvent` 內部執行。\n\n### 回合開始\n\n在 `turn_start` 時，串流緩衝區會被重置：\n\n- `ttsrManager.resetBuffer()`\n\n### 串流期間（`message_update`）\n\n當助理更新到達且規則存在時：\n\n- 監控 `text_delta` 和 `toolcall_delta`\n- 將差異資料附加到管理器緩衝區\n- 呼叫 `check(buffer)`\n\n`check()` 會迭代已註冊的規則，並回傳所有通過重複策略（`#canTrigger`）的匹配規則。\n\n## 3. 觸發決策與立即中止路徑\n\n當一個或多個規則匹配時：\n\n1. `markInjected(matches)` 在管理器注入狀態中記錄規則名稱。\n2. 匹配的規則被排入 `#pendingTtsrInjections` 佇列。\n3. `#ttsrAbortPending = true`。\n4. 立即呼叫 `agent.abort()`。\n5. 非同步發出 `ttsr_triggered` 事件（發送後即忘）。\n6. 透過 `setTimeout(..., 50)` 排程重試工作。\n\n中止不會被擴充功能回呼阻塞。\n\n## 4. 重試排程、上下文模式與提醒注入\n\n在 50ms 逾時之後：\n\n1. `#ttsrAbortPending = false`\n2. 讀取 `ttsrManager.getSettings().contextMode`\n3. 若 `contextMode === \"discard\"`，透過 `agent.popMessage()` 丟棄部分助理輸出\n4. 使用 `ttsr-interrupt.md` 範本從待處理規則建構注入內容\n5. 附加一則合成使用者訊息，其中每個規則包含一個 `<system-interrupt ...>` 區塊\n6. 呼叫 `agent.continue()` 重試生成\n\n範本內容為：\n\n```xml\n<system-interrupt reason=\"rule_violation\" rule=\"{{name}}\" path=\"{{path}}\">\n...\n{{content}}\n</system-interrupt>\n```\n\n待處理注入在內容生成後會被清除。\n\n### `contextMode` 對部分輸出的行為\n\n- `discard`：部分/已中止的助理訊息在重試前會被移除。\n- `keep`：部分助理輸出保留在對話狀態中；提醒會附加在其後。\n\n## 5. 重複策略與間隔邏輯\n\n`TtsrManager` 追蹤 `#messageCount` 和每條規則的 `lastInjectedAt`。\n\n### `repeatMode: \"once\"`\n\n規則在已有注入記錄後只能觸發一次。\n\n### `repeatMode: \"after-gap\"`\n\n規則只有在以下條件成立時才能重新觸發：\n\n- `messageCount - lastInjectedAt >= repeatGap`\n\n`messageCount` 在 `turn_end` 時遞增，因此間隔是以已完成的回合來衡量，而非串流區塊。\n\n## 6. 事件發送與擴充功能/掛鉤介面\n\n### 會話事件\n\n`AgentSessionEvent` 包含：\n\n```ts\n{ type: \"ttsr_triggered\"; rules: Rule[] }\n```\n\n### 擴充功能執行器\n\n`#emitSessionEvent()` 將事件路由至：\n\n- 擴充功能監聽器（`ExtensionRunner.emit({ type: \"ttsr_triggered\", rules })`）\n- 本地會話訂閱者\n\n### 掛鉤與自訂工具型別\n\n- 擴充功能 API 公開 `on(\"ttsr_triggered\", ...)`\n- 掛鉤 API 公開 `on(\"ttsr_triggered\", ...)`\n- 自訂工具接收 `onSession({ reason: \"ttsr_triggered\", rules })`\n\n### 互動模式渲染差異\n\n互動模式使用 `session.isTtsrAbortPending` 來抑制在 TTSR 中斷期間將已中止的助理停止原因顯示為可見錯誤，並在事件到達時渲染 `TtsrNotificationComponent`。\n\n## 7. 持久化與恢復狀態（目前實作）\n\n`SessionManager` 對已注入規則的持久化具有完整的結構描述支援：\n\n- 條目類型：`ttsr_injection`\n- 附加 API：`appendTtsrInjection(ruleNames)`\n- 查詢 API：`getInjectedTtsrRules()`\n- 上下文重建包含 `SessionContext.injectedTtsrRules`\n\n`TtsrManager` 也支援透過 `restoreInjected(ruleNames)` 進行還原。\n\n### 目前的串接狀態\n\n在目前的執行路徑中：\n\n- `AgentSession` 在 TTSR 觸發時不會附加 `ttsr_injection` 條目。\n- `createAgentSession()` 不會將 `existingSession.injectedTtsrRules` 還原回 `ttsrManager`。\n\n淨效果：已注入規則的抑制在活躍程序的記憶體中強制執行，但目前透過此路徑不會在會話重新載入/恢復之間進行持久化/還原。\n\n## 8. 競爭邊界與排序保證\n\n### 中止與重試回呼\n\n- 從 TTSR 處理器的角度來看，中止是同步的（`agent.abort()` 被立即呼叫）\n- 重試透過計時器延遲（`50ms`）\n- 擴充功能通知是非同步的，且在中止/重試排程之前刻意不等待\n\n### 同一串流視窗中的多個匹配\n\n`check()` 回傳所有目前匹配的合格規則。它們在下一則重試訊息中以批次方式注入。\n\n### 中止與繼續之間\n\n在計時器視窗期間，狀態可能改變（使用者中斷、模式操作、額外事件）。重試呼叫為盡力而為：`agent.continue().catch(() => {})` 會吞掉後續錯誤。\n\n## 9. 邊界情況摘要\n\n- 無效的 `ttsr_trigger` 正規表達式：以警告跳過；其他規則繼續運作。\n- 能力層的重複規則名稱：較低優先順序的重複項在註冊前被遮蔽。\n- 管理器層的重複名稱：第二次註冊被忽略。\n- `contextMode: \"keep\"`：部分違規輸出在提醒重試前可能保留在上下文中。\n- after-gap 重複依賴於 `turn_end` 時的回合計數遞增；回合中的區塊不會推進間隔計數器。\n",
	"zh-tw/tui/theme.md": "---\ntitle: 主題設定參考\ndescription: TUI 主題設定參考，包含顏色標記、字型設定與主題自訂。\nsidebar:\n  order: 3\n  label: 主題設定\ni18n:\n  sourceHash: 7e962a7da157\n  translator: machine\n---\n\n# 主題設定參考\n\n本文件說明編碼代理程式中主題系統目前的運作方式：結構描述、載入機制、執行期行為以及失敗模式。\n\n## 主題系統控制的範圍\n\n主題系統驅動：\n\n- 整個 TUI 中使用的前景/背景顏色標記\n- markdown 樣式適配器（`getMarkdownTheme()`）\n- 選擇器/編輯器/設定列表適配器（`getSelectListTheme()`、`getEditorTheme()`、`getSettingsListTheme()`）\n- 符號預設集 + 符號覆寫（`unicode`、`nerd`、`ascii`）\n- 原生高亮器（`@f5-sales-demo/pi-natives`）使用的語法高亮顏色\n- 狀態列區段顏色\n\n主要實作位置：`src/modes/theme/theme.ts`。\n\n## 主題 JSON 結構\n\n主題檔案是 JSON 物件，透過 `theme.ts` 中的執行期結構描述（`ThemeJsonSchema`）進行驗證，並映射至 `src/modes/theme/theme-schema.json`。\n\n頂層欄位：\n\n- `name`（必填）\n- `colors`（必填；所有顏色標記皆為必填）\n- `vars`（選填；可重複使用的顏色變數）\n- `export`（選填；HTML 匯出顏色）\n- `symbols`（選填）\n  - `preset`（選填：`unicode | nerd | ascii`）\n  - `overrides`（選填：`SymbolKey` 的鍵值對覆寫）\n\n顏色值接受：\n\n- 十六進位字串（`\"#RRGGBB\"`）\n- 256 色索引（`0..255`）\n- 變數參考字串（透過 `vars` 解析）\n- 空字串（`\"\"`）表示終端機預設值（前景 `\\x1b[39m`，背景 `\\x1b[49m`）\n\n## 必填顏色標記（目前版本）\n\n以下所有標記在 `colors` 中皆為必填。\n\n### 核心文字與邊框（11 個）\n\n`accent`、`border`、`borderAccent`、`borderMuted`、`success`、`error`、`warning`、`muted`、`dim`、`text`、`thinkingText`\n\n### 背景區塊（7 個）\n\n`selectedBg`、`userMessageBg`、`customMessageBg`、`toolPendingBg`、`toolSuccessBg`、`toolErrorBg`、`statusLineBg`\n\n### 訊息/工具文字（5 個）\n\n`userMessageText`、`customMessageText`、`customMessageLabel`、`toolTitle`、`toolOutput`\n\n### Markdown（10 個）\n\n`mdHeading`、`mdLink`、`mdLinkUrl`、`mdCode`、`mdCodeBlock`、`mdCodeBlockBorder`、`mdQuote`、`mdQuoteBorder`、`mdHr`、`mdListBullet`\n\n### 工具差異比對 + 語法高亮（12 個）\n\n`toolDiffAdded`、`toolDiffRemoved`、`toolDiffContext`、\n`syntaxComment`、`syntaxKeyword`、`syntaxFunction`、`syntaxVariable`、`syntaxString`、`syntaxNumber`、`syntaxType`、`syntaxOperator`、`syntaxPunctuation`\n\n### 模式/思考邊框（8 個）\n\n`thinkingOff`、`thinkingMinimal`、`thinkingLow`、`thinkingMedium`、`thinkingHigh`、`thinkingXhigh`、`bashMode`、`pythonMode`\n\n### 狀態列區段顏色（14 個）\n\n`statusLineSep`、`statusLineModel`、`statusLinePath`、`statusLineGitClean`、`statusLineGitDirty`、`statusLineContext`、`statusLineSpend`、`statusLineStaged`、`statusLineDirty`、`statusLineUntracked`、`statusLineOutput`、`statusLineCost`、`statusLineSubagents`\n\n## 選填標記\n\n### `export` 區段（選填）\n\n用於 HTML 匯出的主題輔助功能：\n\n- `export.pageBg`\n- `export.cardBg`\n- `export.infoBg`\n\n若省略，匯出程式碼會從已解析的主題顏色推導預設值。\n\n### `symbols` 區段（選填）\n\n- `symbols.preset` 設定主題層級的預設符號集。\n- `symbols.overrides` 可覆寫個別的 `SymbolKey` 值。\n\n執行期優先順序：\n\n1. 設定中的 `symbolPreset` 覆寫（若已設定）\n2. 主題 JSON 中的 `symbols.preset`\n3. 回退預設值 `\"unicode\"`\n\n無效的覆寫鍵會被忽略並記錄（`logger.debug`）。\n\n## 內建與自訂主題來源\n\n主題查詢順序（`loadThemeJson`）：\n\n1. 內建嵌入主題（`defaults/xcsh-dark.json` 和 `defaults/xcsh-light.json`，編譯進 `defaultThemes`）\n2. 自訂主題檔案：`<customThemesDir>/<name>.json`\n\n自訂主題目錄來自 `getCustomThemesDir()`：\n\n- 預設：`~/.xcsh/agent/themes`\n- 可透過 `PI_CODING_AGENT_DIR` 覆寫（`$PI_CODING_AGENT_DIR/themes`）\n\n`getAvailableThemes()` 回傳合併後的內建 + 自訂名稱，已排序，名稱衝突時內建主題優先。\n\n## 載入、驗證與解析\n\n對於自訂主題檔案：\n\n1. 讀取 JSON\n2. 解析 JSON\n3. 依據 `ThemeJsonSchema` 進行驗證\n4. 遞迴解析 `vars` 參考\n5. 依終端機色彩能力模式將已解析的值轉換為 ANSI\n\n驗證行為：\n\n- 缺少必填顏色標記：明確的分組錯誤訊息\n- 錯誤的標記類型/值：包含 JSON 路徑的驗證錯誤\n- 未知的主題檔案：`Theme not found: <name>`\n\n變數參考行為：\n\n- 支援巢狀參考\n- 缺少變數參考時拋出例外\n- 循環參考時拋出例外\n\n## 終端機色彩模式行為\n\n色彩模式偵測（`detectColorMode`）：\n\n- `COLORTERM=truecolor|24bit` => truecolor\n- `WT_SESSION` => truecolor\n- `TERM` 為 `dumb`、`linux` 或空值 => 256color\n- 其他情況 => truecolor\n\n轉換行為：\n\n- hex -> `Bun.color(..., \"ansi-16m\" | \"ansi-256\")`\n- 數值 -> `38;5` / `48;5` ANSI\n- `\"\"` -> 預設前景/背景重設\n\n## 執行期切換行為\n\n### 初始主題（`initTheme`）\n\n`main.ts` 使用以下設定初始化主題：\n\n- `symbolPreset`\n- `colorBlindMode`\n- `theme.dark`\n- `theme.light`\n\n自動主題插槽選擇使用 `COLORFGBG` 背景偵測：\n\n- 從 `COLORFGBG` 解析背景索引\n- `< 8` => 深色插槽（`theme.dark`）\n- `>= 8` => 淺色插槽（`theme.light`）\n- 解析失敗 => 深色插槽\n\n目前設定結構描述的預設值：\n\n- `theme.dark = \"xcsh-dark\"`\n- `theme.light = \"xcsh-light\"`\n- `symbolPreset = \"unicode\"`\n- `colorBlindMode = false`\n\n### 明確切換（`setTheme`）\n\n- 載入選定的主題\n- 更新全域 `theme` 單例\n- 可選擇性啟動監視器\n- 觸發 `onThemeChange` 回呼\n\n失敗時：\n\n- 回退至內建 `dark`\n- 回傳 `{ success: false, error }`\n\n### 預覽切換（`previewTheme`）\n\n- 將暫時的預覽主題套用至全域 `theme`\n- **不會**自行變更已持久化的設定\n- 回傳成功/錯誤，不進行回退替換\n\n設定介面使用此功能進行即時預覽，取消時還原先前的主題。\n\n## 監視器與即時重新載入\n\n當監視器啟用時（`setTheme(..., true)` / 互動式初始化）：\n\n- 僅監視自訂檔案路徑 `<customThemesDir>/<currentTheme>.json`\n- 內建主題實際上不會被監視\n- 檔案 `change`：嘗試重新載入（防抖處理）\n- 檔案 `rename`/刪除：回退至 `dark`，關閉監視器\n\n自動模式也會安裝 `SIGWINCH` 監聽器，在終端機狀態變更時可重新評估深色/淺色插槽對應。\n\n## 色盲模式行為\n\n`colorBlindMode` 在執行期僅變更一個標記：\n\n- `toolDiffAdded` 會進行 HSV 調整（綠色偏移向藍色）\n- 僅當已解析的值為十六進位字串時才會套用調整\n\n其他標記不受影響。\n\n## 主題設定的持久化位置\n\n與主題相關的設定透過 `Settings` 持久化至全域設定 YAML：\n\n- 路徑：`<agentDir>/config.yml`\n- 預設代理目錄：`~/.xcsh/agent`\n- 實際預設檔案：`~/.xcsh/agent/config.yml`\n\n持久化的鍵：\n\n- `theme.dark`\n- `theme.light`\n- `symbolPreset`\n- `colorBlindMode`\n\n存在舊版遷移機制：舊的扁平格式 `theme: \"name\"` 會根據亮度偵測遷移至巢狀的 `theme.dark` 或 `theme.light`。\n\n## 建立自訂主題（實務操作）\n\n1. 在自訂主題目錄中建立檔案，例如 `~/.xcsh/agent/themes/my-theme.json`。\n2. 包含 `name`、選填的 `vars`，以及**所有必填的** `colors` 標記。\n3. 可選擇性包含 `symbols` 和 `export`。\n4. 在設定中選擇主題（`Display -> Dark theme` 或 `Display -> Light theme`），取決於您想要使用哪個自動插槽。\n\n最小骨架範例。`colors` 中的每個鍵都是必填的——執行期驗證器\n（`additionalProperties: false`）會同時拒絕缺少的鍵和未知的鍵。\n如需參考已發佈的實作範例，請參閱\n[`packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-dark.json)\n和 [`xcsh-light.json`](../../packages/coding-agent/src/modes/theme/defaults/xcsh-light.json)。\n\n狀態列有兩套平行的顏色系統，記錄在 issue #242 中：\n\n- 十六進位文字顏色（`statusLinePath`、`statusLineGitClean`、`statusLineGitDirty`、\n  `statusLineStaged`、`statusLineDirty`、`statusLineUntracked`）驅動非 powerline\n  的渲染。\n- 256 色調色盤索引（`statusLine<Segment>Bg` / `statusLine<Segment>Fg`）\n  驅動 powerline 區段填充。它們與上述十六進位鍵是獨立的——\n  兩者都必須設定。\n\n```json\n{\n  \"name\": \"my-theme\",\n  \"vars\": {\n    \"accent\": \"#7aa2f7\",\n    \"muted\": 244\n  },\n  \"colors\": {\n    \"accent\": \"accent\",\n    \"chromeAccent\": \"accent\",\n    \"spinnerAccent\": \"accent\",\n    \"contentAccent\": \"muted\",\n    \"border\": \"#4c566a\",\n    \"borderAccent\": \"accent\",\n    \"borderMuted\": \"muted\",\n    \"success\": \"#9ece6a\",\n    \"error\": \"#f7768e\",\n    \"warning\": \"#e0af68\",\n    \"muted\": \"muted\",\n    \"dim\": 240,\n    \"gutterSuccess\": \"#7dcfff\",\n    \"gutterWarning\": \"#e0af68\",\n    \"text\": \"\",\n    \"thinkingText\": \"muted\",\n\n    \"selectedBg\": \"#2a2f45\",\n    \"userMessageBg\": \"#1f2335\",\n    \"userMessageText\": \"\",\n    \"customMessageBg\": \"#24283b\",\n    \"customMessageText\": \"\",\n    \"customMessageLabel\": \"accent\",\n    \"toolPendingBg\": \"#1f2335\",\n    \"toolSuccessBg\": \"#1f2d2a\",\n    \"toolErrorBg\": \"#2d1f2a\",\n    \"toolTitle\": \"\",\n    \"toolOutput\": \"muted\",\n\n    \"mdHeading\": \"accent\",\n    \"mdLink\": \"accent\",\n    \"mdLinkUrl\": \"muted\",\n    \"mdCode\": \"#c0caf5\",\n    \"mdCodeBlock\": \"#c0caf5\",\n    \"mdCodeBlockBorder\": \"muted\",\n    \"mdQuote\": \"muted\",\n    \"mdQuoteBorder\": \"muted\",\n    \"mdHr\": \"muted\",\n    \"mdListBullet\": \"accent\",\n\n    \"toolDiffAdded\": \"#9ece6a\",\n    \"toolDiffRemoved\": \"#f7768e\",\n    \"toolDiffContext\": \"muted\",\n\n    \"syntaxComment\": \"#565f89\",\n    \"syntaxKeyword\": \"#bb9af7\",\n    \"syntaxFunction\": \"#7aa2f7\",\n    \"syntaxVariable\": \"#c0caf5\",\n    \"syntaxString\": \"#9ece6a\",\n    \"syntaxNumber\": \"#ff9e64\",\n    \"syntaxType\": \"#2ac3de\",\n    \"syntaxOperator\": \"#89ddff\",\n    \"syntaxPunctuation\": \"#9aa5ce\",\n    \"syntaxControl\": \"#bb9af7\",\n\n    \"thinkingOff\": 240,\n    \"thinkingMinimal\": 244,\n    \"thinkingLow\": \"#7aa2f7\",\n    \"thinkingMedium\": \"#2ac3de\",\n    \"thinkingHigh\": \"#bb9af7\",\n    \"thinkingXhigh\": \"#f7768e\",\n\n    \"bashMode\": \"#2ac3de\",\n    \"pythonMode\": \"#bb9af7\",\n\n    \"statusLineBg\": \"#16161e\",\n    \"statusLineSep\": 240,\n    \"statusLineModel\": \"#bb9af7\",\n    \"statusLinePath\": \"#7aa2f7\",\n    \"statusLineGitClean\": \"#9ece6a\",\n    \"statusLineGitDirty\": \"#e0af68\",\n    \"statusLineContext\": \"#2ac3de\",\n    \"statusLineSpend\": \"#7dcfff\",\n    \"statusLineStaged\": \"#9ece6a\",\n    \"statusLineDirty\": \"#e0af68\",\n    \"statusLineUntracked\": \"#f7768e\",\n    \"statusLineOutput\": \"#c0caf5\",\n    \"statusLineCost\": \"#ff9e64\",\n    \"statusLineSubagents\": \"#bb9af7\",\n\n    \"statusLineOsIconBg\": 7,\n    \"statusLineOsIconFg\": 232,\n    \"statusLinePathBg\": 4,\n    \"statusLinePathFg\": 254,\n    \"statusLineGitCleanBg\": 2,\n    \"statusLineGitCleanFg\": 0,\n    \"statusLineGitDirtyBg\": 3,\n    \"statusLineGitDirtyFg\": 0,\n    \"statusLineGitStagedBg\": 64,\n    \"statusLineGitStagedFg\": 0,\n    \"statusLineGitUntrackedBg\": 39,\n    \"statusLineGitUntrackedFg\": 0,\n    \"statusLineGitConflictBg\": 1,\n    \"statusLineGitConflictFg\": 7,\n    \"statusLinePlanModeBg\": 236,\n    \"statusLinePlanModeFg\": 117,\n    \"statusLineProfileXcshBg\": \"accent\",\n    \"statusLineProfileXcshFg\": 231\n  }\n}\n```\n\n## 測試自訂主題\n\n使用以下工作流程：\n\n1. 啟動互動模式（監視器從啟動時即啟用）。\n2. 開啟設定並預覽主題值（即時 `previewTheme`）。\n3. 對於自訂主題檔案，在執行中編輯 JSON 並確認儲存時自動重新載入。\n4. 測試關鍵介面：\n   - markdown 渲染\n   - 工具區塊（等待中/成功/錯誤）\n   - 差異比對渲染（新增/移除/上下文）\n   - 狀態列可讀性\n   - 思考層級邊框變化\n   - bash/python 模式邊框顏色\n5. 如果您的主題依賴字形寬度/外觀，請驗證兩種符號預設集。\n\n## 實際限制與注意事項\n\n- 自訂主題中所有 `colors` 標記皆為必填。\n- `export` 和 `symbols` 為選填。\n- 主題 JSON 中的 `$schema` 僅供參考；執行期驗證由程式碼中已編譯的 TypeBox 結構描述強制執行。\n- `setTheme` 失敗時回退至 `dark`；`previewTheme` 失敗時不會替換目前主題。\n- 檔案監視器重新載入錯誤會保持目前已載入的主題，直到成功重新載入或觸發回退路徑為止。\n",
	"zh-tw/tui/tree.md": "---\ntitle: Tree 指令參考\ndescription: /tree 指令參考，用於視覺化工作階段歷史記錄與對話分支。\nsidebar:\n  order: 4\n  label: /tree 指令\ni18n:\n  sourceHash: ee0e412fe993\n  translator: machine\n---\n\n# `/tree` 指令參考\n\n`/tree` 開啟互動式**工作階段樹狀圖**導覽器。它讓您跳轉到目前工作階段檔案中的任何項目，並從該點繼續。\n\n這是檔案內的葉節點移動，而非新的工作階段匯出。\n\n## `/tree` 的功能\n\n- 從目前工作階段項目建構樹狀結構（`SessionManager.getTree()`）\n- 開啟 `TreeSelectorComponent`，支援鍵盤導覽、篩選器及搜尋\n- 選取後，呼叫 `AgentSession.navigateTree(targetId, { summarize, customInstructions })`\n- 從新的葉節點路徑重建可見對話\n- 選取使用者/自訂訊息時，可選擇性地預填編輯器文字\n\n主要實作：\n\n- `src/modes/controllers/input-controller.ts`（`/tree`、快捷鍵綁定、雙擊 Escape 行為）\n- `src/modes/controllers/selector-controller.ts`（樹狀 UI 啟動 + 摘要提示流程）\n- `src/modes/components/tree-selector.ts`（導覽、篩選器、搜尋、標籤、渲染）\n- `src/session/agent-session.ts`（`navigateTree` 葉節點切換 + 可選摘要）\n- `src/session/session-manager.ts`（`getTree`、`branch`、`branchWithSummary`、`resetLeaf`、標籤持久化）\n\n## 如何開啟\n\n以下任一方式皆可開啟相同的選擇器：\n\n- `/tree`\n- 已設定的快捷鍵動作 `tree`\n- 在空白編輯器上雙擊 Escape，當 `doubleEscapeAction = \"tree\"` 時（預設值）\n- `/branch`，當 `doubleEscapeAction = \"tree\"` 時（路由至樹狀選擇器，而非僅限使用者的分支選擇器）\n\n## 樹狀 UI 模型\n\n樹狀結構從工作階段項目的父指標（`id` / `parentId`）進行渲染。\n\n- 子節點按時間戳升序排列（較舊的在前，較新的在下方）\n- 活動分支（從根到目前葉節點的路徑）以圓點標記\n- 標籤（如有）以 `[label]` 渲染在節點文字之前\n- 如果存在多個根節點（孤立/斷裂的父鏈），它們會顯示在虛擬分支根節點下\n\n```text\nExample tree view (active path marked with •):\n\n├─ user: \"Start task\"\n│  └─ assistant: \"Plan\"\n│     ├─ • user: \"Try approach A\"\n│     │  └─ • assistant: \"A result\"\n│     │     └─ • [milestone] user: \"Continue A\"\n│     └─ user: \"Try approach B\"\n│        └─ assistant: \"B result\"\n```\n\n選擇器以目前選取項目為中心重新定位，最多顯示：\n\n- `max(5, floor(terminalHeight / 2))` 列\n\n## 樹狀選擇器內的快捷鍵\n\n- `Up` / `Down`：移動選取項目（循環）\n- `Left` / `Right`：向上翻頁 / 向下翻頁\n- `Enter`：選取節點\n- `Esc`：如搜尋啟用則清除搜尋；否則關閉選擇器\n- `Ctrl+C`：關閉選擇器\n- `Type`：追加至搜尋查詢\n- `Backspace`：刪除搜尋字元\n- `Shift+L`：編輯/清除所選項目的標籤\n- `Ctrl+O`：向前循環篩選器\n- `Shift+Ctrl+O`：向後循環篩選器\n- `Alt+D/T/U/L/A`：直接跳轉到特定篩選模式\n\n## 篩選器與搜尋語意\n\n篩選模式（`TreeList`）：\n\n1. `default`\n2. `no-tools`\n3. `user-only`\n4. `labeled-only`\n5. `all`\n\n### `default`\n\n顯示大部分對話節點，但隱藏簿記項目類型：\n\n- `label`\n- `custom`\n- `model_change`\n- `thinking_level_change`\n\n### `no-tools`\n\n與 `default` 相同，另外隱藏 `toolResult` 訊息。\n\n### `user-only`\n\n僅顯示角色為 `user` 的 `message` 項目。\n\n### `labeled-only`\n\n僅顯示目前已解析為標籤的項目。\n\n### `all`\n\n工作階段樹狀結構中的所有內容，包括簿記/自訂項目。\n\n### 僅含工具的助理節點行為\n\n**僅包含工具呼叫**（無文字）的助理訊息在所有篩選檢視中預設為隱藏，除非：\n\n- 訊息為錯誤/中止狀態（`stopReason` 不是 `stop`/`toolUse`），或\n- 它是目前的葉節點（始終保持可見）\n\n### 搜尋行為\n\n- 查詢以空格進行分詞\n- 比對不區分大小寫\n- 所有詞彙必須匹配（AND 語意）\n- 可搜尋文字包括標籤、角色及特定類型的內容（訊息文字、分支摘要文字、自訂類型、工具指令片段等）\n\n## 選取結果（重要）\n\n`navigateTree` 根據所選項目類型計算新的葉節點行為：\n\n### 選取 `user` 訊息\n\n- 新葉節點變為所選項目的 `parentId`\n- 如果父節點為 `null`（根使用者訊息），葉節點重設為根節點（`resetLeaf()`）\n- 所選訊息文字複製到編輯器以供編輯/重新提交\n\n### 選取 `custom_message`\n\n- 與使用者訊息相同的葉節點規則（`parentId`）\n- 文字內容被擷取並複製到編輯器\n\n### 選取非使用者節點（助理/工具/摘要/壓縮/自訂簿記/等）\n\n- 新葉節點變為所選節點的 id\n- 編輯器不會預填\n\n### 選取目前葉節點\n\n- 無操作；選擇器以「Already at this point」關閉\n\n```text\nSelection decision (simplified):\n\nselected node\n   │\n   ├─ is current leaf? ── yes ──> close selector (no-op)\n   │\n   ├─ is user/custom_message? ── yes ──> leaf := parentId (or resetLeaf for root)\n   │                                     + prefill editor text\n   │\n   └─ otherwise ──> leaf := selected node id\n                    + no editor prefill\n```\n\n## 切換時摘要流程\n\n摘要提示由 `branchSummary.enabled`（預設：`false`）控制。\n\n啟用後，選取節點後 UI 會詢問：\n\n- `No summary`\n- `Summarize`\n- `Summarize with custom prompt`\n\n流程細節：\n\n- 在摘要提示中按 Escape 會重新開啟樹狀選擇器\n- 取消自訂提示會返回摘要選擇循環\n- 摘要生成期間，UI 顯示載入指示器並將 `Esc` 綁定至 `abortBranchSummary()`\n- 如果摘要生成中止，樹狀選擇器會重新開啟且不會套用任何移動\n\n`navigateTree` 內部機制：\n\n- 收集從舊葉節點到共同祖先的已放棄分支項目\n- 發出 `session_before_tree`（擴充功能可取消或注入摘要）\n- 僅在有請求且需要時使用預設摘要生成器\n- 以下列方式套用移動：\n  - `branchWithSummary(...)` 當摘要存在時\n  - `branch(newLeafId)` 用於無摘要的非根節點移動\n  - `resetLeaf()` 用於無摘要的根節點移動\n- 以重建的工作階段上下文替換代理對話\n- 發出 `session_tree`\n\n注意：如果使用者請求摘要但沒有內容可摘要，導覽會在不建立摘要項目的情況下繼續進行。\n\n## 標籤\n\n在樹狀 UI 中編輯標籤會呼叫 `appendLabelChange(targetId, label)`。\n\n- 非空標籤會設定/更新已解析的標籤\n- 空標籤會清除它\n- 標籤以僅追加的 `label` 項目儲存\n- 樹狀節點顯示已解析的標籤狀態，而非原始標籤項目歷史記錄\n\n## `/tree` 與相鄰操作的比較\n\n| 操作 | 範圍 | 結果 |\n|---|---|---|\n| `/tree` | 目前工作階段檔案 | 將葉節點移動到選取的位置（同一檔案） |\n| `/branch` | 通常從目前工作階段檔案 -> 新工作階段檔案 | 預設從選取的**使用者**訊息分支到新的工作階段檔案；如果 `doubleEscapeAction = \"tree\"`，`/branch` 會改為開啟樹狀導覽 UI |\n| `/fork` | 整個目前工作階段 | 將工作階段複製到新的持久化工作階段檔案 |\n| `/resume` | 工作階段列表 | 切換到另一個工作階段檔案 |\n\n關鍵區別：`/tree` 是單一工作階段檔案內的導覽/重新定位工具。`/branch`、`/fork` 和 `/resume` 都會變更工作階段檔案的上下文。\n\n## 操作工作流程\n\n### 從較早的使用者提示重新執行而不遺失目前分支\n\n1. `/tree`\n2. 搜尋/選取較早的使用者訊息\n3. 選擇 `No summary`（或視需要進行摘要）\n4. 在編輯器中編輯預填的文字\n5. 提交\n\n效果：新分支從同一工作階段檔案中的選取點開始成長。\n\n### 帶有上下文書籤離開目前分支\n\n1. 啟用 `branchSummary.enabled`\n2. `/tree` 並選取目標節點\n3. 選擇 `Summarize`（或自訂提示）\n\n效果：在繼續之前，於目標位置追加一個 `branch_summary` 項目。\n\n### 檢視隱藏的簿記項目\n\n1. `/tree`\n2. 按 `Alt+A`（全部）\n3. 搜尋 `model`、`thinking`、`custom` 或標籤\n\n效果：檢視完整的內部時間線，而不僅是對話節點。\n\n### 為後續跳轉建立書籤樞紐點\n\n1. `/tree`\n2. 移動到項目\n3. `Shift+L` 並設定標籤\n4. 之後使用 `Alt+L`（`labeled-only`）快速跳轉\n\n效果：在持久性分支地標之間快速導覽。\n",
	"zh-tw/tui/tui-runtime-internals.md": "---\ntitle: TUI 執行時期內部機制\ndescription: 終端 UI 執行時期內部機制，涵蓋渲染管線、輸入處理與狀態管理。\nsidebar:\n  order: 2\n  label: 執行時期內部機制\ni18n:\n  sourceHash: cc8f7dcce46a\n  translator: machine\n---\n\n# TUI 執行時期內部機制\n\n本文件描述從終端輸入到互動模式渲染輸出的非主題執行時期路徑。重點關注 `packages/tui` 中的行為及其與 `packages/coding-agent` 控制器的整合。\n\n## 執行時期層級與職責\n\n- **`packages/tui` 引擎**：終端生命週期、stdin 正規化、焦點路由、渲染排程、差異繪製、覆疊層合成、硬體游標定位。\n- **`packages/coding-agent` 互動模式**：建構元件樹、綁定編輯器回呼與按鍵映射、回應代理/工作階段事件，並將領域狀態（串流、工具執行、重試、計畫模式）轉換為 UI 元件。\n\n邊界規則：TUI 引擎與訊息無關。它僅知道 `Component.render(width)`、`handleInput(data)`、焦點和覆疊層。代理語意保留在互動控制器中。\n\n## 實作檔案\n\n- [`../src/modes/interactive-mode.ts`](../../packages/coding-agent/src/modes/interactive-mode.ts)\n- [`../src/modes/controllers/event-controller.ts`](../../packages/coding-agent/src/modes/controllers/event-controller.ts)\n- [`../src/modes/controllers/input-controller.ts`](../../packages/coding-agent/src/modes/controllers/input-controller.ts)\n- [`../src/modes/components/custom-editor.ts`](../../packages/coding-agent/src/modes/components/custom-editor.ts)\n- [`../../tui/src/tui.ts`](../../packages/tui/src/tui.ts)\n- [`../../tui/src/terminal.ts`](../../packages/tui/src/terminal.ts)\n- [`../../tui/src/editor-component.ts`](../../packages/tui/src/editor-component.ts)\n- [`../../tui/src/stdin-buffer.ts`](../../packages/tui/src/stdin-buffer.ts)\n- [`../../tui/src/components/loader.ts`](../../packages/tui/src/components/loader.ts)\n\n## 啟動與元件樹組裝\n\n`InteractiveMode` 建構 `TUI(new ProcessTerminal(), showHardwareCursor)` 並建立持久性容器：\n\n- `chatContainer`\n- `pendingMessagesContainer`\n- `statusContainer`\n- `todoContainer`\n- `statusLine`\n- `editorContainer`（包含 `CustomEditor`）\n\n`init()` 依此順序連接元件樹、聚焦編輯器、透過 `InputController` 註冊輸入處理器、啟動 TUI，並請求強制渲染。\n\n強制渲染（`requestRender(true)`）會在重新繪製前重置先前的行快取和游標簿記。\n\n## 終端生命週期與 stdin 正規化\n\n`ProcessTerminal.start()`：\n\n1. 啟用原始模式和括號貼上模式。\n2. 附加調整大小處理器。\n3. 建立 `StdinBuffer` 將部分跳脫序列區塊拆分為完整序列。\n4. 查詢 Kitty 鍵盤協定支援（`CSI ? u`），若支援則啟用協定旗標。\n5. 在 Windows 上，嘗試透過 `kernel32` 模式旗標啟用 VT 輸入。\n\n`StdinBuffer` 行為：\n\n- 緩衝片段化的跳脫序列（CSI/OSC/DCS/APC/SS3）。\n- 僅在序列完整或逾時刷新時發出 `data`。\n- 偵測括號貼上並以原始貼上文字發出 `paste` 事件。\n\n這可防止部分跳脫序列區塊被誤解為一般按鍵。\n\n## 輸入路由與焦點模型\n\n輸入路徑：\n\n`stdin -> ProcessTerminal -> StdinBuffer -> TUI.#handleInput -> focusedComponent.handleInput`\n\n路由細節：\n\n1. TUI 首先執行已註冊的輸入監聽器（`addInputListener`），允許消費/轉換行為。\n2. TUI 在元件分派前處理全域除錯快速鍵（`shift+ctrl+d`）。\n3. 若聚焦元件屬於已隱藏/不可見的覆疊層，TUI 會將焦點重新指派給下一個可見覆疊層或已儲存的覆疊層前焦點。\n4. 按鍵釋放事件會被過濾，除非聚焦元件設定 `wantsKeyRelease = true`。\n5. 分派後，TUI 排程渲染。\n\n`setFocus()` 也會切換 `Focusable.focused`，用於控制元件是否發出 `CURSOR_MARKER` 以進行硬體游標定位。\n\n## 按鍵處理分工：編輯器 vs 控制器\n\n`CustomEditor` 首先攔截高優先級組合鍵（escape、ctrl-c/d/z、ctrl-v、ctrl-p 變體、ctrl-t、alt-up、擴充自訂鍵），其餘則委派給基礎 `Editor` 行為（文字編輯、歷史記錄、自動完成、游標移動）。\n\n`InputController.setupKeyHandlers()` 接著將編輯器回呼綁定到模式動作：\n\n- `Escape` 時取消/退出模式\n- 雙擊 `Ctrl+C` 或空編輯器 `Ctrl+D` 時關閉\n- `Ctrl+Z` 時暫停/恢復\n- 斜線命令與選擇器快速鍵\n- 後續/出列切換與展開切換\n\n這使按鍵解析/編輯器機制保留在 `packages/tui`，模式語意則在 coding-agent 控制器中。\n\n## 渲染迴圈與差異策略\n\n`TUI.requestRender()` 使用 `process.nextTick` 進行防抖，每個 tick 只渲染一次。同一輪中的多個狀態變更會合併。\n\n`#doRender()` 管線：\n\n1. 將根元件樹渲染為 `newLines`。\n2. 合成可見覆疊層（若有）。\n3. 從可見視窗行中提取並移除 `CURSOR_MARKER`。\n4. 為非圖片行附加段落重置後綴。\n5. 選擇完整重繪或差異修補：\n   - 首幀\n   - 寬度變更\n   - 啟用 `clearOnShrink` 且無覆疊層時的縮減\n   - 先前視窗上方的編輯\n6. 對於差異更新，僅修補已變更的行範圍，並在需要時清除過時的尾部行。\n7. 重新定位硬體游標以支援 IME。\n\n渲染寫入使用同步輸出模式（`CSI ? 2026 h/l`）以減少閃爍/撕裂。\n\n## 渲染安全約束\n\n`TUI` 中的關鍵安全檢查：\n\n- 非圖片渲染行不得超過終端寬度；溢位會拋出例外並寫入當機診斷資訊。\n- 覆疊層合成包含防禦性截斷和合成後寬度驗證。\n- 寬度變更強制完整重繪，因為換行語意已改變。\n- 游標位置在移動前會被夾限。\n\n這些約束是執行時期強制措施，而非僅是慣例。\n\n## 調整大小處理\n\n調整大小事件由 `ProcessTerminal` 事件驅動至 `TUI.requestRender()`。\n\n效果：\n\n- 任何寬度變更都會觸發完整重繪。\n- 視窗/頂部追蹤（`#previousViewportTop`、`#maxLinesRendered`）可避免在內容或終端大小變更時產生無效的相對游標運算。\n- 覆疊層可見性可取決於終端尺寸（`OverlayOptions.visible`）；調整大小後覆疊層變為不可見時會修正焦點。\n\n## 串流與增量 UI 更新\n\n`EventController` 訂閱 `AgentSessionEvent` 並增量更新 UI：\n\n- `agent_start`：在 `statusContainer` 中啟動載入器。\n- `message_start` 助理：建立 `streamingComponent` 並掛載。\n- `message_update`：更新串流助理內容；隨工具呼叫出現而建立/更新工具執行元件。\n- `tool_execution_update/end`：更新工具結果元件和完成狀態。\n- `message_end`：完成助理串流、處理中止/錯誤註解、在正常停止時標記待處理工具參數為完成。\n- `agent_end`：停止載入器、清除暫態串流狀態、刷新延遲的模型切換、若在背景則發出完成通知。\n\n讀取工具分組是刻意維護狀態的（`#lastReadGroup`），用於將連續的讀取工具呼叫合併為一個視覺區塊，直到出現非讀取中斷。\n\n## 狀態與載入器協調\n\n狀態區域職責：\n\n- `statusContainer` 保存暫態載入器（`loadingAnimation`、`autoCompactionLoader`、`retryLoader`）。\n- `statusLine` 渲染持久性狀態/掛鉤/計畫指示器，並驅動編輯器頂部邊框更新。\n\n載入器行為：\n\n- `Loader` 透過間隔每 80ms 更新一次，並在每幀請求渲染。\n- 在自動壓縮和自動重試期間，跳脫處理器會被暫時覆寫以取消這些操作。\n- 在結束/取消路徑上，控制器會恢復先前的跳脫處理器並停止/清除載入器元件。\n\n## 模式轉換與背景化\n\n### Bash/Python 輸入模式\n\n輸入文字前綴切換編輯器邊框模式旗標：\n\n- `!` -> bash 模式\n- `$`（非範本字面值前綴）-> python 模式\n\nEscape 透過清除編輯器文字和恢復邊框顏色退出非活動模式；當執行處於活動狀態時，escape 改為中止正在執行的任務。\n\n### 計畫模式\n\n`InteractiveMode` 追蹤計畫模式旗標、狀態行狀態、活動工具和模型切換。進入/退出會更新工作階段模式項目和狀態/UI 狀態，包括串流活動時的延遲模型切換。\n\n### 暫停/恢復（`Ctrl+Z`）\n\n`InputController.handleCtrlZ()`：\n\n1. 註冊一次性 `SIGCONT` 處理器以重啟 TUI 並強制渲染。\n2. 暫停前停止 TUI。\n3. 向行程群組發送 `SIGTSTP`。\n\n### 背景模式（`/background` 或 `/bg`）\n\n`handleBackgroundCommand()`：\n\n- 閒置時拒絕。\n- 將工具 UI 上下文切換為非互動式（`hasUI=false`），使互動式 UI 工具快速失敗。\n- 停止載入器/狀態行並取消訂閱前景事件處理器。\n- 訂閱背景事件處理器（主要等待 `agent_end`）。\n- 停止 TUI 並發送 `SIGTSTP`（POSIX 工作控制路徑）。\n\n在背景模式下收到 `agent_end` 且無佇列工作時，控制器發送完成通知並關閉。\n\n## 取消路徑\n\n主要取消輸入：\n\n- 活動串流載入器期間按 `Escape`：將佇列訊息恢復到編輯器並中止代理。\n- bash/python 執行期間按 `Escape`：中止正在執行的命令。\n- 自動壓縮/重試期間按 `Escape`：透過暫時跳脫處理器呼叫專用中止方法。\n- 單擊 `Ctrl+C`：清除編輯器；500ms 內雙擊：關閉。\n\n取消是狀態條件式的；相同按鍵可能代表中止、退出模式、選擇器觸發或無操作，取決於執行時期狀態。\n\n## 事件驅動 vs 節流行為\n\n事件驅動更新：\n\n- 代理工作階段事件（`EventController`）\n- 按鍵輸入回呼（`InputController`）\n- 終端調整大小回呼\n- `InteractiveMode` 中的主題/分支監視器\n\n節流/防抖路徑：\n\n- TUI 渲染是 tick 防抖的（`requestRender` 合併）。\n- 載入器動畫是固定間隔的（80ms），每幀請求渲染。\n- 編輯器自動完成更新（在 `Editor` 內部）使用防抖計時器，減少打字時的重新計算抖動。\n\n因此，執行時期混合了事件驅動的狀態轉換與有界渲染節拍，在保持互動性回應的同時避免重繪風暴。\n",
	"zh-tw/tui/tui.md": "---\ntitle: 擴充功能與自訂工具的 TUI 整合\ndescription: 擴充功能、自訂工具和自訂渲染器的 TUI 整合契約。\nsidebar:\n  order: 1\n  label: 擴充功能整合\ni18n:\n  sourceHash: 47f8f2b2045e\n  translator: machine\n---\n\n# 擴充功能與自訂工具的 TUI 整合\n\n本文件涵蓋 `packages/coding-agent` 和 `packages/tui` 用於擴充功能 UI、自訂工具 UI 和自訂渲染器的**目前** TUI 契約。\n\n## 此子系統是什麼\n\n執行階段有兩個層級：\n\n- **渲染引擎（`packages/tui`）**：差異化終端渲染器、輸入分派、焦點、覆蓋層、游標定位。\n- **整合層（`packages/coding-agent`）**：掛載擴充功能/自訂工具元件，連接按鍵綁定/主題，並還原編輯器狀態。\n\n## 各模式的執行階段行為\n\n| 模式 | `ctx.ui.custom(...)` 可用性 | 備註 |\n| --- | --- | --- |\n| 互動式 TUI | 支援 | 元件會掛載到編輯器區域、取得焦點，且必須呼叫 `done(result)` 來解析。 |\n| 背景/無頭模式 | 非互動式 | UI 上下文為空操作（`hasUI === false`）。 |\n| RPC 模式 | 不支援 | `custom()` 回傳 `Promise<never>` 且不會掛載 TUI 元件。 |\n\n如果您的擴充功能/工具可以在非互動模式下執行，請使用 `ctx.hasUI` / `pi.hasUI` 進行檢查。\n\n## 核心元件契約（`@f5-sales-demo/pi-tui`）\n\n`packages/tui/src/tui.ts` 定義了：\n\n```ts\nexport interface Component {\n  render(width: number): string[];\n  handleInput?(data: string): void;\n  wantsKeyRelease?: boolean;\n  invalidate(): void;\n}\n```\n\n`Focusable` 是獨立的：\n\n```ts\nexport interface Focusable {\n  focused: boolean;\n}\n```\n\n游標行為使用 `CURSOR_MARKER`（而非 `getCursorPosition`）。取得焦點的元件會在渲染文字中發出標記；`TUI` 會提取它並定位硬體游標。\n\n## 渲染限制（終端安全性）\n\n您的 `render(width)` 輸出必須是終端安全的：\n\n1. **任何一行都不可超過 `width`**。如果非影像行溢出，渲染器會拋出錯誤。\n2. **測量視覺寬度**，而非字串長度：使用 `visibleWidth()`。\n3. **使用 ANSI 感知方式截斷/換行文字**，使用 `truncateToWidth()` / `wrapTextWithAnsi()`。\n4. **清理來自外部來源的 Tab 字元/內容**，使用 `replaceTabs()`（以及 coding-agent 渲染路徑中的更高階清理器）。\n\n最小模式：\n\n```ts\nimport { replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\n\nrender(width: number): string[] {\n  return this.lines.map(line => truncateToWidth(replaceTabs(line), width));\n}\n```\n\n## 輸入處理與按鍵綁定\n\n### 原始按鍵匹配\n\n使用 `matchesKey(data, \"...\")` 來匹配導覽鍵和組合鍵。\n\n### 尊重使用者配置的應用程式按鍵綁定\n\n擴充功能 UI 工廠會接收 `KeybindingsManager`（互動模式），因此您可以遵循映射的動作而非硬編碼按鍵：\n\n```ts\nif (keybindings.matches(data, \"interrupt\")) {\n  done(undefined);\n  return;\n}\n```\n\n### 按鍵釋放/重複事件\n\n按鍵釋放事件會被過濾，除非您的元件設定了：\n\n```ts\nwantsKeyRelease = true;\n```\n\n然後根據需要使用 `isKeyRelease()` / `isKeyRepeat()`。\n\n## 焦點、覆蓋層與游標\n\n- `TUI.setFocus(component)` 將輸入路由到該元件。\n- `TUI` 中存在覆蓋層 API（`showOverlay`、`OverlayHandle`），但在互動模式下，擴充功能 `ctx.ui.custom` 掛載目前會直接替換編輯器元件區域。\n- `custom(..., options?: { overlay?: boolean })` 選項存在於擴充功能類型中；互動式擴充功能掛載目前會忽略此選項。\n\n## 掛載點與回傳契約\n\n## 1) 擴充功能 UI（`ExtensionUIContext`）\n\n目前簽名（`extensibility/extensions/types.ts`）：\n\n```ts\ncustom<T>(\n  factory: (\n    tui: TUI,\n    theme: Theme,\n    keybindings: KeybindingsManager,\n    done: (result: T) => void,\n  ) => (Component & { dispose?(): void }) | Promise<Component & { dispose?(): void }>,\n  options?: { overlay?: boolean },\n): Promise<T>\n```\n\n互動模式下的行為（`extension-ui-controller.ts`）：\n\n- 儲存編輯器文字。\n- 用您的元件替換編輯器元件。\n- 讓您的元件取得焦點。\n- 當 `done(result)` 被呼叫時：呼叫 `component.dispose?.()`、還原編輯器 + 文字、讓編輯器取得焦點、解析 Promise。\n\n因此 `done(...)` 是完成操作的必要呼叫。\n\n## 2) Hook/自訂工具 UI 上下文（舊版型別）\n\n`HookUIContext.custom` 在 hook/自訂工具類型中被型別定義為 `(tui, theme, done)`。\n底層互動實作以 `(tui, theme, keybindings, done)` 呼叫工廠。JS 消費者可以使用額外的參數；型別層級的相容性仍反映 3 參數的舊版簽名。\n\n自訂工具通常透過工廠作用域的 `pi.ui` 物件使用相同的 UI 進入點，然後在一般工具內容中回傳選取的值：\n\n```ts\nasync execute(toolCallId, params, onUpdate, ctx, signal) {\n  if (!pi.hasUI) {\n    return { content: [{ type: \"text\", text: \"UI unavailable\" }] };\n  }\n\n  const picked = await pi.ui.custom<string | undefined>((tui, theme, done) => {\n    const component = new MyPickerComponent(done, signal);\n    return component;\n  });\n\n  return { content: [{ type: \"text\", text: picked ? `Picked: ${picked}` : \"Cancelled\" }] };\n}\n```\n\n## 3) 自訂工具呼叫/結果渲染器\n\n自訂工具和擴充功能工具可以從以下方法回傳元件：\n\n- `renderCall(args, theme)`\n- `renderResult(result, options, theme, args?)`\n\n`options` 目前包含：\n\n- `expanded: boolean`\n- `isPartial: boolean`\n- `spinnerFrame?: number`\n\n這些渲染器由 `ToolExecutionComponent` 掛載。\n\n## 生命週期與取消\n\n- `dispose()` 在型別層級是可選的，但當您擁有計時器、子行程、監視器、Socket 或覆蓋層時應該實作它。\n- `done(...)` 應在您的元件流程中恰好呼叫一次。\n- 對於可取消的長時間執行 UI，將 `CancellableLoader` 與 `AbortSignal` 配對使用，並從 `onAbort` 呼叫 `done(...)`。\n\n取消模式範例：\n\n```ts\nconst loader = new CancellableLoader(tui, theme.fg(\"accent\"), theme.fg(\"muted\"), \"Working...\");\nloader.onAbort = () => done(undefined);\nvoid doWork(loader.signal).then(result => done(result));\nreturn loader;\n```\n\n## 實際自訂元件範例（擴充功能命令）\n\n```ts\nimport type { Component } from \"@f5-sales-demo/pi-tui\";\nimport { SelectList, matchesKey, replaceTabs, truncateToWidth } from \"@f5-sales-demo/pi-tui\";\nimport { getSelectListTheme, type ExtensionAPI } from \"@f5-sales-demo/xcsh\";\n\nclass Picker implements Component {\n  list: SelectList;\n  keybindings: any;\n  done: (value: string | undefined) => void;\n\n  constructor(\n    items: Array<{ value: string; label: string }>,\n    keybindings: any,\n    done: (value: string | undefined) => void,\n  ) {\n    this.list = new SelectList(items, 8, getSelectListTheme());\n    this.keybindings = keybindings;\n    this.done = done;\n    this.list.onSelect = item => this.done(item.value);\n    this.list.onCancel = () => this.done(undefined);\n  }\n\n  handleInput(data: string): void {\n    if (this.keybindings.matches(data, \"interrupt\")) {\n      this.done(undefined);\n      return;\n    }\n    this.list.handleInput(data);\n  }\n\n  render(width: number): string[] {\n    return this.list.render(width).map(line => truncateToWidth(replaceTabs(line), width));\n  }\n\n  invalidate(): void {\n    this.list.invalidate();\n  }\n}\n\nexport default function extension(pi: ExtensionAPI): void {\n  pi.registerCommand(\"pick-model\", {\n    description: \"Pick a model profile\",\n    handler: async (_args, ctx) => {\n      if (!ctx.hasUI) return;\n\n      const selected = await ctx.ui.custom<string | undefined>((tui, theme, keybindings, done) => {\n        const items = [\n          { value: \"fast\", label: theme.fg(\"accent\", \"Fast\") },\n          { value: \"balanced\", label: \"Balanced\" },\n          { value: \"quality\", label: \"Quality\" },\n        ];\n        return new Picker(items, keybindings, done);\n      });\n\n      if (selected) ctx.ui.notify(`Selected profile: ${selected}`, \"info\");\n    },\n  });\n}\n```\n\n## 關鍵實作檔案\n\n- `packages/tui/src/tui.ts` — `Component`、`Focusable`、游標標記、焦點、覆蓋層、輸入分派。\n- `packages/tui/src/utils.ts` — 寬度/截斷/清理基礎工具。\n- `packages/tui/src/keys.ts` / `keybindings.ts` — 按鍵解析與可配置動作映射。\n- `packages/coding-agent/src/modes/controllers/extension-ui-controller.ts` — 擴充功能/hook/自訂工具 UI 的互動式掛載/卸載。\n- `packages/coding-agent/src/extensibility/extensions/types.ts` — 擴充功能 UI 與渲染器契約。\n- `packages/coding-agent/src/extensibility/hooks/types.ts` — hook UI 契約（舊版自訂簽名）。\n- `packages/coding-agent/src/extensibility/custom-tools/types.ts` — 自訂工具執行/渲染契約。\n- `packages/coding-agent/src/modes/components/tool-execution.ts` — 掛載 `renderCall`/`renderResult` 元件與部分狀態選項。\n- `packages/coding-agent/src/tools/context.ts` — 工具 UI 上下文傳播（`hasUI`、`ui`）。\n",
};
