@cjhyy/code-shell-core 0.5.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +162 -0
- package/dist/agent/coordinator.d.ts +49 -0
- package/dist/agent/coordinator.js +77 -0
- package/dist/arena/arena.d.ts +43 -0
- package/dist/arena/arena.js +334 -0
- package/dist/arena/context/context-tools.d.ts +16 -0
- package/dist/arena/context/context-tools.js +231 -0
- package/dist/arena/detect-mode.d.ts +20 -0
- package/dist/arena/detect-mode.js +78 -0
- package/dist/arena/digest-builder.d.ts +25 -0
- package/dist/arena/digest-builder.js +120 -0
- package/dist/arena/index.d.ts +29 -0
- package/dist/arena/index.js +29 -0
- package/dist/arena/iterate/convergence.d.ts +25 -0
- package/dist/arena/iterate/convergence.js +103 -0
- package/dist/arena/iterate/formats/index.d.ts +22 -0
- package/dist/arena/iterate/formats/index.js +283 -0
- package/dist/arena/iterate/index.d.ts +11 -0
- package/dist/arena/iterate/index.js +9 -0
- package/dist/arena/iterate/iterative-arena.d.ts +23 -0
- package/dist/arena/iterate/iterative-arena.js +237 -0
- package/dist/arena/iterate/parse.d.ts +42 -0
- package/dist/arena/iterate/parse.js +123 -0
- package/dist/arena/iterate/phases/argue.d.ts +22 -0
- package/dist/arena/iterate/phases/argue.js +159 -0
- package/dist/arena/iterate/phases/revise.d.ts +16 -0
- package/dist/arena/iterate/phases/revise.js +62 -0
- package/dist/arena/iterate/phases/tournament.d.ts +34 -0
- package/dist/arena/iterate/phases/tournament.js +113 -0
- package/dist/arena/iterate/tools/web-tools.d.ts +13 -0
- package/dist/arena/iterate/tools/web-tools.js +54 -0
- package/dist/arena/iterate/types.d.ts +152 -0
- package/dist/arena/iterate/types.js +8 -0
- package/dist/arena/ledger.d.ts +47 -0
- package/dist/arena/ledger.js +151 -0
- package/dist/arena/lenses/architecture.d.ts +5 -0
- package/dist/arena/lenses/architecture.js +22 -0
- package/dist/arena/lenses/engineering.d.ts +5 -0
- package/dist/arena/lenses/engineering.js +22 -0
- package/dist/arena/lenses/general.d.ts +5 -0
- package/dist/arena/lenses/general.js +20 -0
- package/dist/arena/lenses/index.d.ts +16 -0
- package/dist/arena/lenses/index.js +47 -0
- package/dist/arena/lenses/product.d.ts +5 -0
- package/dist/arena/lenses/product.js +22 -0
- package/dist/arena/model-presets.d.ts +23 -0
- package/dist/arena/model-presets.js +44 -0
- package/dist/arena/phases/adjudication.d.ts +24 -0
- package/dist/arena/phases/adjudication.js +144 -0
- package/dist/arena/phases/build-consensus.d.ts +29 -0
- package/dist/arena/phases/build-consensus.js +86 -0
- package/dist/arena/phases/claim-registry.d.ts +26 -0
- package/dist/arena/phases/claim-registry.js +60 -0
- package/dist/arena/phases/cross-review.d.ts +45 -0
- package/dist/arena/phases/cross-review.js +224 -0
- package/dist/arena/phases/debate-rounds.d.ts +27 -0
- package/dist/arena/phases/debate-rounds.js +162 -0
- package/dist/arena/phases/participant-research.d.ts +38 -0
- package/dist/arena/phases/participant-research.js +317 -0
- package/dist/arena/phases/planning-detail-expansion.d.ts +38 -0
- package/dist/arena/phases/planning-detail-expansion.js +121 -0
- package/dist/arena/planner.d.ts +27 -0
- package/dist/arena/planner.js +312 -0
- package/dist/arena/providers/docs.d.ts +6 -0
- package/dist/arena/providers/docs.js +108 -0
- package/dist/arena/providers/git.d.ts +8 -0
- package/dist/arena/providers/git.js +174 -0
- package/dist/arena/providers/index.d.ts +32 -0
- package/dist/arena/providers/index.js +132 -0
- package/dist/arena/providers/none.d.ts +7 -0
- package/dist/arena/providers/none.js +11 -0
- package/dist/arena/providers/repo.d.ts +6 -0
- package/dist/arena/providers/repo.js +255 -0
- package/dist/arena/render/session.d.ts +17 -0
- package/dist/arena/render/session.js +190 -0
- package/dist/arena/render/terminal.d.ts +34 -0
- package/dist/arena/render/terminal.js +286 -0
- package/dist/arena/strategies/discussion.d.ts +25 -0
- package/dist/arena/strategies/discussion.js +143 -0
- package/dist/arena/strategies/index.d.ts +15 -0
- package/dist/arena/strategies/index.js +28 -0
- package/dist/arena/strategies/language-wrapper.d.ts +17 -0
- package/dist/arena/strategies/language-wrapper.js +102 -0
- package/dist/arena/strategies/lens-wrapper.d.ts +16 -0
- package/dist/arena/strategies/lens-wrapper.js +236 -0
- package/dist/arena/strategies/planning.d.ts +30 -0
- package/dist/arena/strategies/planning.js +225 -0
- package/dist/arena/strategies/review.d.ts +26 -0
- package/dist/arena/strategies/review.js +168 -0
- package/dist/arena/strategies/utils.d.ts +39 -0
- package/dist/arena/strategies/utils.js +647 -0
- package/dist/arena/tools/selector.d.ts +17 -0
- package/dist/arena/tools/selector.js +61 -0
- package/dist/arena/transitions.d.ts +48 -0
- package/dist/arena/transitions.js +92 -0
- package/dist/arena/types.d.ts +508 -0
- package/dist/arena/types.js +27 -0
- package/dist/cli/agent-server-stdio.d.ts +32 -0
- package/dist/cli/agent-server-stdio.js +116 -0
- package/dist/colorizer.d.ts +23 -0
- package/dist/colorizer.js +12 -0
- package/dist/context/compaction.d.ts +116 -0
- package/dist/context/compaction.js +534 -0
- package/dist/context/manager.d.ts +145 -0
- package/dist/context/manager.js +423 -0
- package/dist/context/token-counter.d.ts +27 -0
- package/dist/context/token-counter.js +91 -0
- package/dist/context/tool-result-storage.d.ts +96 -0
- package/dist/context/tool-result-storage.js +296 -0
- package/dist/cost-tracker.d.ts +64 -0
- package/dist/cost-tracker.js +314 -0
- package/dist/cron/scheduler.d.ts +34 -0
- package/dist/cron/scheduler.js +115 -0
- package/dist/data/deepseek-models.json +46 -0
- package/dist/data/gemini-models.json +67 -0
- package/dist/data/openai-models.json +175 -0
- package/dist/data/openrouter-models.d.ts +35 -0
- package/dist/data/openrouter-models.js +36 -0
- package/dist/data/openrouter-models.json +4626 -0
- package/dist/data/openrouter-sync.d.ts +16 -0
- package/dist/data/openrouter-sync.js +53 -0
- package/dist/data/static-catalogs.d.ts +25 -0
- package/dist/data/static-catalogs.js +26 -0
- package/dist/data/zai-models.json +56 -0
- package/dist/engine/cost-store.d.ts +22 -0
- package/dist/engine/cost-store.js +15 -0
- package/dist/engine/engine.d.ts +295 -0
- package/dist/engine/engine.js +1467 -0
- package/dist/engine/model-facade.d.ts +24 -0
- package/dist/engine/model-facade.js +194 -0
- package/dist/engine/parse-task.d.ts +59 -0
- package/dist/engine/parse-task.js +135 -0
- package/dist/engine/patch-orphaned-tools.d.ts +52 -0
- package/dist/engine/patch-orphaned-tools.js +95 -0
- package/dist/engine/query.d.ts +50 -0
- package/dist/engine/query.js +107 -0
- package/dist/engine/runtime.d.ts +50 -0
- package/dist/engine/runtime.js +59 -0
- package/dist/engine/streaming-tool-queue.d.ts +34 -0
- package/dist/engine/streaming-tool-queue.js +61 -0
- package/dist/engine/token-budget.d.ts +22 -0
- package/dist/engine/token-budget.js +40 -0
- package/dist/engine/tool-summary.d.ts +13 -0
- package/dist/engine/tool-summary.js +35 -0
- package/dist/engine/turn-loop.d.ts +122 -0
- package/dist/engine/turn-loop.js +602 -0
- package/dist/engine/turn-state.d.ts +20 -0
- package/dist/engine/turn-state.js +17 -0
- package/dist/exceptions.d.ts +61 -0
- package/dist/exceptions.js +116 -0
- package/dist/git/utils.d.ts +35 -0
- package/dist/git/utils.js +98 -0
- package/dist/git/worktree.d.ts +36 -0
- package/dist/git/worktree.js +139 -0
- package/dist/hooks/events.d.ts +114 -0
- package/dist/hooks/events.js +4 -0
- package/dist/hooks/inject.d.ts +19 -0
- package/dist/hooks/inject.js +29 -0
- package/dist/hooks/registry.d.ts +15 -0
- package/dist/hooks/registry.js +84 -0
- package/dist/hooks/shell-runner.d.ts +45 -0
- package/dist/hooks/shell-runner.js +173 -0
- package/dist/index.d.ts +117 -0
- package/dist/index.js +145 -0
- package/dist/llm/api-key-sanitize.d.ts +22 -0
- package/dist/llm/api-key-sanitize.js +81 -0
- package/dist/llm/capabilities/index.d.ts +20 -0
- package/dist/llm/capabilities/index.js +39 -0
- package/dist/llm/capabilities/rules.d.ts +26 -0
- package/dist/llm/capabilities/rules.js +215 -0
- package/dist/llm/capabilities/types.d.ts +114 -0
- package/dist/llm/capabilities/types.js +24 -0
- package/dist/llm/client-base.d.ts +29 -0
- package/dist/llm/client-base.js +137 -0
- package/dist/llm/client-factory.d.ts +10 -0
- package/dist/llm/client-factory.js +29 -0
- package/dist/llm/model-cache.d.ts +21 -0
- package/dist/llm/model-cache.js +46 -0
- package/dist/llm/model-fetcher.d.ts +29 -0
- package/dist/llm/model-fetcher.js +282 -0
- package/dist/llm/model-pool.d.ts +95 -0
- package/dist/llm/model-pool.js +246 -0
- package/dist/llm/provider-catalog.d.ts +32 -0
- package/dist/llm/provider-catalog.js +50 -0
- package/dist/llm/provider-kinds.d.ts +21 -0
- package/dist/llm/provider-kinds.js +132 -0
- package/dist/llm/providers/anthropic.d.ts +19 -0
- package/dist/llm/providers/anthropic.js +267 -0
- package/dist/llm/providers/openai.d.ts +64 -0
- package/dist/llm/providers/openai.js +606 -0
- package/dist/llm/retry.d.ts +12 -0
- package/dist/llm/retry.js +17 -0
- package/dist/llm/stream-watchdog.d.ts +42 -0
- package/dist/llm/stream-watchdog.js +70 -0
- package/dist/llm/token-counter.d.ts +17 -0
- package/dist/llm/token-counter.js +36 -0
- package/dist/llm/types.d.ts +37 -0
- package/dist/llm/types.js +4 -0
- package/dist/logging/logger.d.ts +109 -0
- package/dist/logging/logger.js +390 -0
- package/dist/logging/sanitize-messages.d.ts +42 -0
- package/dist/logging/sanitize-messages.js +150 -0
- package/dist/logging/session-recorder.d.ts +90 -0
- package/dist/logging/session-recorder.js +304 -0
- package/dist/lsp/client.d.ts +41 -0
- package/dist/lsp/client.js +166 -0
- package/dist/lsp/manager.d.ts +40 -0
- package/dist/lsp/manager.js +124 -0
- package/dist/lsp/servers.d.ts +16 -0
- package/dist/lsp/servers.js +60 -0
- package/dist/migrate-models.d.ts +45 -0
- package/dist/migrate-models.js +179 -0
- package/dist/onboarding.d.ts +189 -0
- package/dist/onboarding.js +625 -0
- package/dist/plugins/gitOps.d.ts +32 -0
- package/dist/plugins/gitOps.js +62 -0
- package/dist/plugins/installedPlugins.d.ts +15 -0
- package/dist/plugins/installedPlugins.js +56 -0
- package/dist/plugins/knownMarketplaces.d.ts +10 -0
- package/dist/plugins/knownMarketplaces.js +46 -0
- package/dist/plugins/loadPluginHooks.d.ts +52 -0
- package/dist/plugins/loadPluginHooks.js +162 -0
- package/dist/plugins/marketplaceManager.d.ts +49 -0
- package/dist/plugins/marketplaceManager.js +142 -0
- package/dist/plugins/parseMarketplaceInput.d.ts +16 -0
- package/dist/plugins/parseMarketplaceInput.js +73 -0
- package/dist/plugins/pluginCommandHook.d.ts +54 -0
- package/dist/plugins/pluginCommandHook.js +180 -0
- package/dist/plugins/pluginCommandsLoader.d.ts +25 -0
- package/dist/plugins/pluginCommandsLoader.js +114 -0
- package/dist/plugins/pluginInstaller.d.ts +36 -0
- package/dist/plugins/pluginInstaller.js +228 -0
- package/dist/plugins/schemas.d.ts +9 -0
- package/dist/plugins/schemas.js +136 -0
- package/dist/plugins/types.d.ts +75 -0
- package/dist/plugins/types.js +7 -0
- package/dist/plugins/varRewrite.d.ts +42 -0
- package/dist/plugins/varRewrite.js +129 -0
- package/dist/preset/index.d.ts +60 -0
- package/dist/preset/index.js +159 -0
- package/dist/product/define.d.ts +60 -0
- package/dist/product/define.js +123 -0
- package/dist/product/index.d.ts +5 -0
- package/dist/product/index.js +4 -0
- package/dist/product/types.d.ts +111 -0
- package/dist/product/types.js +21 -0
- package/dist/prompt/composer.d.ts +49 -0
- package/dist/prompt/composer.js +168 -0
- package/dist/prompt/instruction-scanner.d.ts +54 -0
- package/dist/prompt/instruction-scanner.js +187 -0
- package/dist/prompt/section-cache.d.ts +15 -0
- package/dist/prompt/section-cache.js +31 -0
- package/dist/prompt/section-loader.d.ts +26 -0
- package/dist/prompt/section-loader.js +53 -0
- package/dist/prompt/sections/base.md +35 -0
- package/dist/prompt/sections/coding.md +31 -0
- package/dist/prompt/sections/orchestration.md +16 -0
- package/dist/prompt/sections/tone.md +9 -0
- package/dist/protocol/chat-session-manager.d.ts +33 -0
- package/dist/protocol/chat-session-manager.js +65 -0
- package/dist/protocol/chat-session.d.ts +49 -0
- package/dist/protocol/chat-session.js +86 -0
- package/dist/protocol/client.d.ts +104 -0
- package/dist/protocol/client.js +260 -0
- package/dist/protocol/factories.d.ts +93 -0
- package/dist/protocol/factories.js +64 -0
- package/dist/protocol/helpers.d.ts +42 -0
- package/dist/protocol/helpers.js +55 -0
- package/dist/protocol/index.d.ts +4 -0
- package/dist/protocol/index.js +4 -0
- package/dist/protocol/server.d.ts +80 -0
- package/dist/protocol/server.js +855 -0
- package/dist/protocol/transport.d.ts +37 -0
- package/dist/protocol/transport.js +85 -0
- package/dist/protocol/types.d.ts +221 -0
- package/dist/protocol/types.js +61 -0
- package/dist/remote/bridge.d.ts +37 -0
- package/dist/remote/bridge.js +103 -0
- package/dist/run/ArtifactTracker.d.ts +38 -0
- package/dist/run/ArtifactTracker.js +132 -0
- package/dist/run/CheckpointWriter.d.ts +53 -0
- package/dist/run/CheckpointWriter.js +135 -0
- package/dist/run/EngineRunner.d.ts +72 -0
- package/dist/run/EngineRunner.js +119 -0
- package/dist/run/Evaluator.d.ts +56 -0
- package/dist/run/Evaluator.js +62 -0
- package/dist/run/FileRunStore.d.ts +39 -0
- package/dist/run/FileRunStore.js +176 -0
- package/dist/run/Heartbeat.d.ts +54 -0
- package/dist/run/Heartbeat.js +123 -0
- package/dist/run/RunApprovalBackend.d.ts +53 -0
- package/dist/run/RunApprovalBackend.js +85 -0
- package/dist/run/RunLock.d.ts +40 -0
- package/dist/run/RunLock.js +98 -0
- package/dist/run/RunManager.d.ts +82 -0
- package/dist/run/RunManager.js +587 -0
- package/dist/run/RunQueue.d.ts +27 -0
- package/dist/run/RunQueue.js +73 -0
- package/dist/run/RunStore.d.ts +25 -0
- package/dist/run/RunStore.js +9 -0
- package/dist/run/factory.d.ts +63 -0
- package/dist/run/factory.js +51 -0
- package/dist/run/index.d.ts +17 -0
- package/dist/run/index.js +23 -0
- package/dist/run/types.d.ts +136 -0
- package/dist/run/types.js +28 -0
- package/dist/runtime/safe-spawn.d.ts +118 -0
- package/dist/runtime/safe-spawn.js +268 -0
- package/dist/services/analytics.d.ts +29 -0
- package/dist/services/analytics.js +84 -0
- package/dist/services/auto-dream.d.ts +49 -0
- package/dist/services/auto-dream.js +124 -0
- package/dist/services/diagnostics.d.ts +41 -0
- package/dist/services/diagnostics.js +99 -0
- package/dist/services/extract-memories.d.ts +31 -0
- package/dist/services/extract-memories.js +71 -0
- package/dist/services/index.d.ts +11 -0
- package/dist/services/index.js +17 -0
- package/dist/services/memory-orchestrator.d.ts +60 -0
- package/dist/services/memory-orchestrator.js +133 -0
- package/dist/services/notifier.d.ts +25 -0
- package/dist/services/notifier.js +69 -0
- package/dist/services/oauth.d.ts +27 -0
- package/dist/services/oauth.js +153 -0
- package/dist/services/session-memory.d.ts +37 -0
- package/dist/services/session-memory.js +81 -0
- package/dist/session/file-history.d.ts +41 -0
- package/dist/session/file-history.js +115 -0
- package/dist/session/memory.d.ts +104 -0
- package/dist/session/memory.js +310 -0
- package/dist/session/session-manager.d.ts +41 -0
- package/dist/session/session-manager.js +261 -0
- package/dist/session/transcript.d.ts +39 -0
- package/dist/session/transcript.js +186 -0
- package/dist/settings/manager.d.ts +36 -0
- package/dist/settings/manager.js +187 -0
- package/dist/settings/schema.d.ts +1219 -0
- package/dist/settings/schema.js +246 -0
- package/dist/skills/frontmatter.d.ts +13 -0
- package/dist/skills/frontmatter.js +70 -0
- package/dist/skills/index.d.ts +7 -0
- package/dist/skills/index.js +6 -0
- package/dist/skills/scanner.d.ts +31 -0
- package/dist/skills/scanner.js +171 -0
- package/dist/state.d.ts +160 -0
- package/dist/state.js +267 -0
- package/dist/tool-system/builtin/agent-notifications.d.ts +84 -0
- package/dist/tool-system/builtin/agent-notifications.js +197 -0
- package/dist/tool-system/builtin/agent-registry.d.ts +67 -0
- package/dist/tool-system/builtin/agent-registry.js +126 -0
- package/dist/tool-system/builtin/agent-transcript-translator.d.ts +22 -0
- package/dist/tool-system/builtin/agent-transcript-translator.js +160 -0
- package/dist/tool-system/builtin/agent.d.ts +16 -0
- package/dist/tool-system/builtin/agent.js +364 -0
- package/dist/tool-system/builtin/apply-patch/applier.d.ts +26 -0
- package/dist/tool-system/builtin/apply-patch/applier.js +250 -0
- package/dist/tool-system/builtin/apply-patch/index.d.ts +20 -0
- package/dist/tool-system/builtin/apply-patch/index.js +101 -0
- package/dist/tool-system/builtin/apply-patch/parser.d.ts +17 -0
- package/dist/tool-system/builtin/apply-patch/parser.js +212 -0
- package/dist/tool-system/builtin/apply-patch/seek-sequence.d.ts +18 -0
- package/dist/tool-system/builtin/apply-patch/seek-sequence.js +123 -0
- package/dist/tool-system/builtin/apply-patch/types.d.ts +49 -0
- package/dist/tool-system/builtin/apply-patch/types.js +13 -0
- package/dist/tool-system/builtin/arena.d.ts +31 -0
- package/dist/tool-system/builtin/arena.js +416 -0
- package/dist/tool-system/builtin/ask-user.d.ts +20 -0
- package/dist/tool-system/builtin/ask-user.js +99 -0
- package/dist/tool-system/builtin/bash.d.ts +21 -0
- package/dist/tool-system/builtin/bash.js +149 -0
- package/dist/tool-system/builtin/brief.d.ts +6 -0
- package/dist/tool-system/builtin/brief.js +41 -0
- package/dist/tool-system/builtin/config.d.ts +6 -0
- package/dist/tool-system/builtin/config.js +67 -0
- package/dist/tool-system/builtin/cron.d.ts +10 -0
- package/dist/tool-system/builtin/cron.js +61 -0
- package/dist/tool-system/builtin/edit.d.ts +6 -0
- package/dist/tool-system/builtin/edit.js +100 -0
- package/dist/tool-system/builtin/file-cache.d.ts +11 -0
- package/dist/tool-system/builtin/file-cache.js +39 -0
- package/dist/tool-system/builtin/glob.d.ts +7 -0
- package/dist/tool-system/builtin/glob.js +76 -0
- package/dist/tool-system/builtin/grep.d.ts +7 -0
- package/dist/tool-system/builtin/grep.js +150 -0
- package/dist/tool-system/builtin/index.d.ts +20 -0
- package/dist/tool-system/builtin/index.js +449 -0
- package/dist/tool-system/builtin/lsp.d.ts +6 -0
- package/dist/tool-system/builtin/lsp.js +141 -0
- package/dist/tool-system/builtin/mcp-tools.d.ts +10 -0
- package/dist/tool-system/builtin/mcp-tools.js +102 -0
- package/dist/tool-system/builtin/memory.d.ts +26 -0
- package/dist/tool-system/builtin/memory.js +226 -0
- package/dist/tool-system/builtin/notebook-edit.d.ts +6 -0
- package/dist/tool-system/builtin/notebook-edit.js +124 -0
- package/dist/tool-system/builtin/plan.d.ts +15 -0
- package/dist/tool-system/builtin/plan.js +55 -0
- package/dist/tool-system/builtin/powershell.d.ts +11 -0
- package/dist/tool-system/builtin/powershell.js +64 -0
- package/dist/tool-system/builtin/read.d.ts +6 -0
- package/dist/tool-system/builtin/read.js +70 -0
- package/dist/tool-system/builtin/remote-trigger.d.ts +6 -0
- package/dist/tool-system/builtin/remote-trigger.js +54 -0
- package/dist/tool-system/builtin/repl.d.ts +11 -0
- package/dist/tool-system/builtin/repl.js +82 -0
- package/dist/tool-system/builtin/send-message.d.ts +6 -0
- package/dist/tool-system/builtin/send-message.js +47 -0
- package/dist/tool-system/builtin/skill-prompt.d.ts +20 -0
- package/dist/tool-system/builtin/skill-prompt.js +52 -0
- package/dist/tool-system/builtin/skill.d.ts +9 -0
- package/dist/tool-system/builtin/skill.js +55 -0
- package/dist/tool-system/builtin/sleep.d.ts +6 -0
- package/dist/tool-system/builtin/sleep.js +33 -0
- package/dist/tool-system/builtin/task.d.ts +48 -0
- package/dist/tool-system/builtin/task.js +139 -0
- package/dist/tool-system/builtin/tool-search.d.ts +11 -0
- package/dist/tool-system/builtin/tool-search.js +90 -0
- package/dist/tool-system/builtin/web-fetch.d.ts +10 -0
- package/dist/tool-system/builtin/web-fetch.js +328 -0
- package/dist/tool-system/builtin/web-search.d.ts +25 -0
- package/dist/tool-system/builtin/web-search.js +174 -0
- package/dist/tool-system/builtin/worktree.d.ts +10 -0
- package/dist/tool-system/builtin/worktree.js +95 -0
- package/dist/tool-system/builtin/write.d.ts +6 -0
- package/dist/tool-system/builtin/write.js +36 -0
- package/dist/tool-system/context.d.ts +165 -0
- package/dist/tool-system/context.js +18 -0
- package/dist/tool-system/executor.d.ts +49 -0
- package/dist/tool-system/executor.js +495 -0
- package/dist/tool-system/investigation-guard.d.ts +46 -0
- package/dist/tool-system/investigation-guard.js +155 -0
- package/dist/tool-system/mcp-manager.d.ts +67 -0
- package/dist/tool-system/mcp-manager.js +249 -0
- package/dist/tool-system/permission.d.ts +111 -0
- package/dist/tool-system/permission.js +774 -0
- package/dist/tool-system/registry.d.ts +32 -0
- package/dist/tool-system/registry.js +131 -0
- package/dist/tool-system/sandbox/bwrap.d.ts +13 -0
- package/dist/tool-system/sandbox/bwrap.js +63 -0
- package/dist/tool-system/sandbox/index.d.ts +78 -0
- package/dist/tool-system/sandbox/index.js +222 -0
- package/dist/tool-system/sandbox/off.d.ts +2 -0
- package/dist/tool-system/sandbox/off.js +8 -0
- package/dist/tool-system/sandbox/seatbelt.d.ts +18 -0
- package/dist/tool-system/sandbox/seatbelt.js +109 -0
- package/dist/tool-system/task-guard.d.ts +29 -0
- package/dist/tool-system/task-guard.js +70 -0
- package/dist/tool-system/validation.d.ts +5 -0
- package/dist/tool-system/validation.js +39 -0
- package/dist/types.d.ts +376 -0
- package/dist/types.js +4 -0
- package/dist/updater.d.ts +65 -0
- package/dist/updater.js +405 -0
- package/dist/utils/debug.d.ts +41 -0
- package/dist/utils/debug.js +110 -0
- package/dist/utils/earlyInput.d.ts +43 -0
- package/dist/utils/earlyInput.js +166 -0
- package/dist/utils/env.d.ts +24 -0
- package/dist/utils/env.js +104 -0
- package/dist/utils/envUtils.d.ts +51 -0
- package/dist/utils/envUtils.js +135 -0
- package/dist/utils/execFileNoThrow.d.ts +39 -0
- package/dist/utils/execFileNoThrow.js +74 -0
- package/dist/utils/format.d.ts +44 -0
- package/dist/utils/format.js +236 -0
- package/dist/utils/intl.d.ts +22 -0
- package/dist/utils/intl.js +83 -0
- package/dist/utils/lockfile.d.ts +15 -0
- package/dist/utils/lockfile.js +30 -0
- package/dist/utils/memoize.d.ts +19 -0
- package/dist/utils/memoize.js +14 -0
- package/dist/utils/semver.d.ts +6 -0
- package/dist/utils/semver.js +13 -0
- package/dist/utils/sliceAnsi.d.ts +12 -0
- package/dist/utils/sliceAnsi.js +62 -0
- package/dist/utils/systemTheme.d.ts +40 -0
- package/dist/utils/systemTheme.js +108 -0
- package/dist/utils/task-sanitizer.d.ts +6 -0
- package/dist/utils/task-sanitizer.js +24 -0
- package/dist/utils/theme.d.ts +93 -0
- package/dist/utils/theme.js +544 -0
- package/dist/utils/toolDisplay.d.ts +31 -0
- package/dist/utils/toolDisplay.js +188 -0
- package/package.json +72 -0
|
@@ -0,0 +1,774 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Permission system — classifier + approval backend.
|
|
3
|
+
*/
|
|
4
|
+
import { logger as rootPermLogger } from "../logging/logger.js";
|
|
5
|
+
export class HeadlessApprovalBackend {
|
|
6
|
+
mode;
|
|
7
|
+
constructor(mode) {
|
|
8
|
+
this.mode = mode;
|
|
9
|
+
}
|
|
10
|
+
async requestApproval(req) {
|
|
11
|
+
switch (this.mode) {
|
|
12
|
+
case "approve-all":
|
|
13
|
+
return { approved: true };
|
|
14
|
+
case "deny-all":
|
|
15
|
+
return { approved: false, reason: "headless deny-all mode" };
|
|
16
|
+
case "approve-read-only": {
|
|
17
|
+
const readOnlyTools = ["Read", "Glob", "Grep", "WebSearch", "WebFetch", "ToolSearch"];
|
|
18
|
+
if (readOnlyTools.includes(req.toolName)) {
|
|
19
|
+
return { approved: true };
|
|
20
|
+
}
|
|
21
|
+
return { approved: false, reason: "read-only mode: write operations denied" };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Auto approval backend — uses fast-path heuristics to auto-approve safe operations.
|
|
28
|
+
* Falls back to a delegate backend for uncertain cases.
|
|
29
|
+
*/
|
|
30
|
+
export class AutoApprovalBackend {
|
|
31
|
+
delegate;
|
|
32
|
+
constructor(delegate) {
|
|
33
|
+
this.delegate = delegate;
|
|
34
|
+
}
|
|
35
|
+
async requestApproval(req) {
|
|
36
|
+
// Fast-path: auto-approve low-risk operations
|
|
37
|
+
if (req.riskLevel === "low") {
|
|
38
|
+
return { approved: true };
|
|
39
|
+
}
|
|
40
|
+
// Auto-approve common safe patterns
|
|
41
|
+
if (this.isSafeOperation(req)) {
|
|
42
|
+
return { approved: true };
|
|
43
|
+
}
|
|
44
|
+
// Auto-deny high-risk dangerous commands
|
|
45
|
+
if (req.riskLevel === "high") {
|
|
46
|
+
if (this.delegate) {
|
|
47
|
+
return this.delegate.requestApproval(req);
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
approved: false,
|
|
51
|
+
reason: "auto mode: high-risk operation denied (no interactive approval available)",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// Medium risk: delegate if available, otherwise approve
|
|
55
|
+
if (this.delegate) {
|
|
56
|
+
return this.delegate.requestApproval(req);
|
|
57
|
+
}
|
|
58
|
+
return { approved: true };
|
|
59
|
+
}
|
|
60
|
+
isSafeOperation(req) {
|
|
61
|
+
const { toolName, args } = req;
|
|
62
|
+
// Write/Edit to known safe paths
|
|
63
|
+
if (toolName === "Write" || toolName === "Edit") {
|
|
64
|
+
const filePath = String(args.file_path ?? "");
|
|
65
|
+
// Allow writes to source code, tests, config, docs
|
|
66
|
+
if (/\.(ts|js|tsx|jsx|py|rs|go|java|c|cpp|h|css|html|json|yaml|yml|toml|md|txt|sh)$/.test(filePath)) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Safe bash commands
|
|
71
|
+
if (toolName === "Bash") {
|
|
72
|
+
const cmd = String(args.command ?? "");
|
|
73
|
+
const safePrefixes = [
|
|
74
|
+
"git ",
|
|
75
|
+
"npm ",
|
|
76
|
+
"pnpm ",
|
|
77
|
+
"yarn ",
|
|
78
|
+
"npx ",
|
|
79
|
+
"node ",
|
|
80
|
+
"tsc ",
|
|
81
|
+
"eslint ",
|
|
82
|
+
"prettier ",
|
|
83
|
+
"vitest ",
|
|
84
|
+
"jest ",
|
|
85
|
+
"cargo ",
|
|
86
|
+
"go ",
|
|
87
|
+
"python ",
|
|
88
|
+
"pip ",
|
|
89
|
+
"make ",
|
|
90
|
+
"ls ",
|
|
91
|
+
"cat ",
|
|
92
|
+
"head ",
|
|
93
|
+
"tail ",
|
|
94
|
+
"wc ",
|
|
95
|
+
"echo ",
|
|
96
|
+
"mkdir ",
|
|
97
|
+
"touch ",
|
|
98
|
+
"pwd",
|
|
99
|
+
"whoami",
|
|
100
|
+
"date",
|
|
101
|
+
"which ",
|
|
102
|
+
];
|
|
103
|
+
if (safePrefixes.some((p) => cmd.startsWith(p))) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Interactive approval backend — prompts the user via a callback.
|
|
112
|
+
*
|
|
113
|
+
* Stores "always allow" decisions at two scopes:
|
|
114
|
+
* - session: in-memory map, lost when the process exits
|
|
115
|
+
* - project: persisted to <cwd>/.code-shell/settings.local.json so the
|
|
116
|
+
* same project remembers the rule across REPL restarts
|
|
117
|
+
*
|
|
118
|
+
* The classifier reads project rules from settings on session start (see
|
|
119
|
+
* Engine.buildPermissionConfig), so persisted rules become "auto-allow"
|
|
120
|
+
* the next time the user opens the project.
|
|
121
|
+
*/
|
|
122
|
+
export class InteractiveApprovalBackend {
|
|
123
|
+
sessionRules = new Map();
|
|
124
|
+
promptFn = null;
|
|
125
|
+
cwd = null;
|
|
126
|
+
// Project rules saved during this session. Kept in-memory (not re-read from
|
|
127
|
+
// settings.local.json) so a stream of approvals all stay applied — the
|
|
128
|
+
// previous version passed only the freshly-added rule to the classifier,
|
|
129
|
+
// which silently dropped earlier approvals from the live classifier within
|
|
130
|
+
// the same session. We accumulate here and hand the full list to the callback.
|
|
131
|
+
savedProjectRules = [];
|
|
132
|
+
onProjectRules = null;
|
|
133
|
+
setPromptFn(fn) {
|
|
134
|
+
this.promptFn = fn;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Has someone installed a real prompt callback? Engine consults this
|
|
138
|
+
* to decide whether to use the interactive backend (= talk to a UI)
|
|
139
|
+
* or fall back to HeadlessApprovalBackend (= deny-all). Without it,
|
|
140
|
+
* the agent-server-stdio entry — which wires setInteractiveApprovalFn
|
|
141
|
+
* during server boot — would still fall through to deny-all because
|
|
142
|
+
* the engine couldn't tell the difference. */
|
|
143
|
+
hasPromptFn() {
|
|
144
|
+
return this.promptFn !== null;
|
|
145
|
+
}
|
|
146
|
+
/** Inject the project root so persistence writes to the right settings file. */
|
|
147
|
+
setCwd(cwd) {
|
|
148
|
+
this.cwd = cwd;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Inject a callback fired when the user approves "for this project". The
|
|
152
|
+
* callback receives the *full* accumulated list of project rules saved in
|
|
153
|
+
* this session, so the classifier can be reconfigured without losing
|
|
154
|
+
* earlier approvals.
|
|
155
|
+
*/
|
|
156
|
+
setOnProjectRules(fn) {
|
|
157
|
+
this.onProjectRules = fn;
|
|
158
|
+
}
|
|
159
|
+
async requestApproval(req) {
|
|
160
|
+
// Check session rules first
|
|
161
|
+
const toolRule = this.sessionRules.get(req.toolName);
|
|
162
|
+
if (toolRule === "allow")
|
|
163
|
+
return { approved: true };
|
|
164
|
+
if (toolRule === "deny")
|
|
165
|
+
return { approved: false };
|
|
166
|
+
if (!this.promptFn) {
|
|
167
|
+
return { approved: false, reason: "interactive approval backend has no prompt function" };
|
|
168
|
+
}
|
|
169
|
+
const result = await this.promptFn(req);
|
|
170
|
+
const scope = result.scope ?? (result.always ? "session" : "once");
|
|
171
|
+
if (scope === "session" && result.always) {
|
|
172
|
+
this.sessionRules.set(req.toolName, result.approved ? "allow" : "deny");
|
|
173
|
+
}
|
|
174
|
+
else if (scope === "project" && result.approved) {
|
|
175
|
+
// Project-scope: persist a PermissionRule to settings.local.json.
|
|
176
|
+
// We only persist allow rules — denies stay session-only because a
|
|
177
|
+
// persisted deny is harder to recover from than a session deny.
|
|
178
|
+
const rule = buildProjectRule(req.toolName, req.args);
|
|
179
|
+
if (rule && this.cwd) {
|
|
180
|
+
try {
|
|
181
|
+
persistProjectRule(this.cwd, rule);
|
|
182
|
+
// Dedup against the in-memory list using the same equality used by
|
|
183
|
+
// persistProjectRule so re-prompts on the same tool/argsPattern
|
|
184
|
+
// don't bloat the live rule set.
|
|
185
|
+
const dup = this.savedProjectRules.some((r) => r.tool === rule.tool &&
|
|
186
|
+
r.decision === rule.decision &&
|
|
187
|
+
JSON.stringify(r.argsPattern) === JSON.stringify(rule.argsPattern));
|
|
188
|
+
if (!dup)
|
|
189
|
+
this.savedProjectRules.push(rule);
|
|
190
|
+
this.onProjectRules?.(this.savedProjectRules);
|
|
191
|
+
rootPermLogger.info("permission.persist", {
|
|
192
|
+
cat: "permission",
|
|
193
|
+
tool: rule.tool,
|
|
194
|
+
decision: rule.decision,
|
|
195
|
+
totalProjectRules: this.savedProjectRules.length,
|
|
196
|
+
duplicate: dup,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
catch (err) {
|
|
200
|
+
rootPermLogger.error("permission.persist_failed", {
|
|
201
|
+
cat: "permission",
|
|
202
|
+
tool: rule.tool,
|
|
203
|
+
error: err.message,
|
|
204
|
+
});
|
|
205
|
+
// eslint-disable-next-line no-console
|
|
206
|
+
console.error("Failed to persist project permission rule:", err.message);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
// Also seed session map so the rest of this REPL session benefits
|
|
210
|
+
// even if the classifier path doesn't pick the rule up immediately.
|
|
211
|
+
this.sessionRules.set(req.toolName, "allow");
|
|
212
|
+
}
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Build a PermissionRule from a single approval. Bash narrows to the head
|
|
218
|
+
* command (first whitespace token) so "git status" → allow all `git ...`.
|
|
219
|
+
* Other tools currently allow at tool granularity — file-level whitelists
|
|
220
|
+
* could be added later by extending argsPattern.
|
|
221
|
+
*/
|
|
222
|
+
function buildProjectRule(toolName, args) {
|
|
223
|
+
if (toolName === "Bash") {
|
|
224
|
+
const cmd = String(args.command ?? "").trim();
|
|
225
|
+
const head = cmd.split(/\s+/)[0];
|
|
226
|
+
if (!head)
|
|
227
|
+
return null;
|
|
228
|
+
return {
|
|
229
|
+
tool: "Bash",
|
|
230
|
+
argsPattern: { command: `^${escapeRegex(head)}(\\s|$)` },
|
|
231
|
+
decision: "allow",
|
|
232
|
+
reason: `Allowed via permission prompt: ${head}`,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
tool: toolName,
|
|
237
|
+
decision: "allow",
|
|
238
|
+
reason: "Allowed via permission prompt",
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
function escapeRegex(s) {
|
|
242
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Append a rule to <cwd>/.code-shell/settings.local.json. Local file because
|
|
246
|
+
* permission grants are per-developer and shouldn't be checked into git.
|
|
247
|
+
* Idempotent: skips if an equivalent rule is already present.
|
|
248
|
+
*/
|
|
249
|
+
function persistProjectRule(cwd, rule) {
|
|
250
|
+
const dir = `${cwd}/.code-shell`;
|
|
251
|
+
const file = `${dir}/settings.local.json`;
|
|
252
|
+
// Lazy node imports — keep permission.ts free of fs at module load time
|
|
253
|
+
// so it can be used in non-Node contexts (tests, browsers via shim).
|
|
254
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
255
|
+
const fs = require("node:fs");
|
|
256
|
+
if (!fs.existsSync(dir))
|
|
257
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
258
|
+
let settings = {};
|
|
259
|
+
if (fs.existsSync(file)) {
|
|
260
|
+
try {
|
|
261
|
+
settings = JSON.parse(fs.readFileSync(file, "utf-8"));
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
/* start fresh on parse error */
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (!settings.permissions)
|
|
268
|
+
settings.permissions = {};
|
|
269
|
+
if (!settings.permissions.rules)
|
|
270
|
+
settings.permissions.rules = [];
|
|
271
|
+
// Dedup: same tool + same argsPattern.command (or no pattern) is a match
|
|
272
|
+
const exists = settings.permissions.rules.some((r) => r.tool === rule.tool &&
|
|
273
|
+
r.decision === "allow" &&
|
|
274
|
+
JSON.stringify(r.argsPattern) === JSON.stringify(rule.argsPattern));
|
|
275
|
+
if (exists)
|
|
276
|
+
return;
|
|
277
|
+
settings.permissions.rules.push(rule);
|
|
278
|
+
// Atomic write: stage to .tmp, then rename, so a crash mid-write can't
|
|
279
|
+
// truncate settings.local.json and lose every saved permission grant.
|
|
280
|
+
const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
|
|
281
|
+
fs.writeFileSync(tmp, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
282
|
+
fs.renameSync(tmp, file);
|
|
283
|
+
}
|
|
284
|
+
// Singleton interactive backend for use by the UI
|
|
285
|
+
let _interactiveBackend = null;
|
|
286
|
+
export function getInteractiveApprovalBackend() {
|
|
287
|
+
if (!_interactiveBackend) {
|
|
288
|
+
_interactiveBackend = new InteractiveApprovalBackend();
|
|
289
|
+
}
|
|
290
|
+
return _interactiveBackend;
|
|
291
|
+
}
|
|
292
|
+
export function setInteractiveApprovalFn(fn) {
|
|
293
|
+
getInteractiveApprovalBackend().setPromptFn(fn);
|
|
294
|
+
}
|
|
295
|
+
const DANGEROUS_PATTERNS = [
|
|
296
|
+
/rm\s+-rf/,
|
|
297
|
+
/rm\s+-r\s+\//,
|
|
298
|
+
/>\s*\/dev\/sd/,
|
|
299
|
+
/mkfs\./,
|
|
300
|
+
/dd\s+if=/,
|
|
301
|
+
/:(){ :|:& };:/,
|
|
302
|
+
/chmod\s+-R\s+777/,
|
|
303
|
+
/curl.*\|\s*(ba)?sh/,
|
|
304
|
+
/wget.*\|\s*(ba)?sh/,
|
|
305
|
+
/git\s+push\s+--force/,
|
|
306
|
+
/git\s+reset\s+--hard/,
|
|
307
|
+
/git\s+clean\s+-[fd]/,
|
|
308
|
+
/>\s*\/etc\//,
|
|
309
|
+
/sudo\s+rm/,
|
|
310
|
+
/pkill\s+-9/,
|
|
311
|
+
/kill\s+-9/,
|
|
312
|
+
/shutdown/,
|
|
313
|
+
/reboot/,
|
|
314
|
+
/systemctl\s+(stop|disable|mask)/,
|
|
315
|
+
/DROP\s+(TABLE|DATABASE)/i,
|
|
316
|
+
/TRUNCATE\s+TABLE/i,
|
|
317
|
+
];
|
|
318
|
+
const SAFE_READ_PATTERNS = [
|
|
319
|
+
/^(cat|head|tail|less|more|wc|file|stat|du|df)\s/,
|
|
320
|
+
/^(ls|tree|find|locate|which|whereis|type)\s/,
|
|
321
|
+
/^(grep|rg|ag|ack|fgrep|egrep)\s/,
|
|
322
|
+
/^(git\s+(status|log|diff|branch|show|blame|remote|tag|stash\s+list|rev-parse|describe))/,
|
|
323
|
+
/^(node|python|ruby|go|rustc|java|javac)\s+--version/,
|
|
324
|
+
/^(npm|pnpm|yarn|cargo|pip|gem|brew)\s+(list|ls|info|show|view|outdated|audit)/,
|
|
325
|
+
/^pwd$/,
|
|
326
|
+
/^whoami$/,
|
|
327
|
+
/^date$/,
|
|
328
|
+
/^(uname|hostname|id)\b/,
|
|
329
|
+
/^echo\s/,
|
|
330
|
+
/^env$/,
|
|
331
|
+
/^printenv/,
|
|
332
|
+
];
|
|
333
|
+
const SAFE_WRITE_PATTERNS = [
|
|
334
|
+
/^(git\s+(add|commit|stash\s+(save|push)))/,
|
|
335
|
+
/^mkdir\s/,
|
|
336
|
+
/^touch\s/,
|
|
337
|
+
/^(npm|pnpm|yarn)\s+(install|add|remove|uninstall|run|test|build|lint|format)/,
|
|
338
|
+
/^(cargo|go|pip|gem)\s+(build|test|install|run|fmt|clippy|vet)/,
|
|
339
|
+
/^(tsc|eslint|prettier|vitest|jest|mocha|pytest)\b/,
|
|
340
|
+
/^make\b/,
|
|
341
|
+
/^(cp|mv)\s/,
|
|
342
|
+
];
|
|
343
|
+
// ─── Shell metacharacter scanner ──────────────────────────────────
|
|
344
|
+
//
|
|
345
|
+
// A1 hardening: classifyBashCommand previously ran SAFE_READ_PATTERNS
|
|
346
|
+
// against the whole command string, so `ls -la; rm -rf /` would
|
|
347
|
+
// match /^ls\s/ and be returned as safe-read. We now scan the command
|
|
348
|
+
// once with quote/escape awareness:
|
|
349
|
+
//
|
|
350
|
+
// - top-level `;`, `&&`, `||`, newline split the command into
|
|
351
|
+
// segments. Every segment must independently classify as safe
|
|
352
|
+
// for the whole command to be safe.
|
|
353
|
+
// - unquoted command substitution (` ` ` or `$(` ), redirection
|
|
354
|
+
// (`>` `>>` `<` `<<`), process substitution (`<(` `>(`), and
|
|
355
|
+
// pipe-to-shell (`| sh`, `| bash`, ...) flag the command as
|
|
356
|
+
// dangerous.
|
|
357
|
+
// - quoted (`'…'`, `"…"`) and backslash-escaped characters are
|
|
358
|
+
// ignored — `echo "a; b"` is a single segment.
|
|
359
|
+
//
|
|
360
|
+
// This is intentionally detection-only, not a full shell parser. We
|
|
361
|
+
// only need to find unquoted metacharacters and split safely.
|
|
362
|
+
const PIPE_TO_SHELL_RE = /\|\s*(sh|bash|zsh|dash|ksh|fish|python|python3|node|nodejs|ruby|perl|php)\b/;
|
|
363
|
+
function scanShellCommand(input) {
|
|
364
|
+
const segments = [];
|
|
365
|
+
let buf = "";
|
|
366
|
+
let dangerous = false;
|
|
367
|
+
let i = 0;
|
|
368
|
+
// Track top-level quote state. We do NOT recurse into nested
|
|
369
|
+
// command substitutions for classification — the presence of `$(`
|
|
370
|
+
// or backticks already marks the command dangerous.
|
|
371
|
+
let quote = null;
|
|
372
|
+
const flush = () => {
|
|
373
|
+
const s = buf.trim();
|
|
374
|
+
if (s.length > 0)
|
|
375
|
+
segments.push(s);
|
|
376
|
+
buf = "";
|
|
377
|
+
};
|
|
378
|
+
while (i < input.length) {
|
|
379
|
+
const ch = input[i];
|
|
380
|
+
const next = input[i + 1];
|
|
381
|
+
if (quote === null) {
|
|
382
|
+
// Escape: skip the next character
|
|
383
|
+
if (ch === "\\" && next !== undefined) {
|
|
384
|
+
buf += ch + next;
|
|
385
|
+
i += 2;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
// Enter quote
|
|
389
|
+
if (ch === "'" || ch === '"') {
|
|
390
|
+
quote = ch;
|
|
391
|
+
buf += ch;
|
|
392
|
+
i++;
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
// Command substitution: backtick or $(
|
|
396
|
+
if (ch === "`" || (ch === "$" && next === "(")) {
|
|
397
|
+
dangerous = true;
|
|
398
|
+
buf += ch;
|
|
399
|
+
i++;
|
|
400
|
+
continue;
|
|
401
|
+
}
|
|
402
|
+
// Process substitution: <( or >(
|
|
403
|
+
if ((ch === "<" || ch === ">") && next === "(") {
|
|
404
|
+
dangerous = true;
|
|
405
|
+
buf += ch;
|
|
406
|
+
i++;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
// Redirection
|
|
410
|
+
if (ch === ">" || ch === "<") {
|
|
411
|
+
dangerous = true;
|
|
412
|
+
buf += ch;
|
|
413
|
+
i++;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
// Top-level separators
|
|
417
|
+
if (ch === ";" || ch === "\n") {
|
|
418
|
+
flush();
|
|
419
|
+
i++;
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if ((ch === "&" && next === "&") || (ch === "|" && next === "|")) {
|
|
423
|
+
flush();
|
|
424
|
+
i += 2;
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
// Background `&` is treated like `;`
|
|
428
|
+
if (ch === "&") {
|
|
429
|
+
flush();
|
|
430
|
+
i++;
|
|
431
|
+
continue;
|
|
432
|
+
}
|
|
433
|
+
buf += ch;
|
|
434
|
+
i++;
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
// Inside a quote
|
|
438
|
+
if (ch === "\\" && next !== undefined) {
|
|
439
|
+
buf += ch + next;
|
|
440
|
+
i += 2;
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
if (ch === quote) {
|
|
444
|
+
quote = null;
|
|
445
|
+
buf += ch;
|
|
446
|
+
i++;
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
buf += ch;
|
|
450
|
+
i++;
|
|
451
|
+
}
|
|
452
|
+
flush();
|
|
453
|
+
return { segments, dangerous };
|
|
454
|
+
}
|
|
455
|
+
function classifySegment(segment) {
|
|
456
|
+
if (DANGEROUS_PATTERNS.some((p) => p.test(segment)))
|
|
457
|
+
return "dangerous";
|
|
458
|
+
if (SAFE_READ_PATTERNS.some((p) => p.test(segment)))
|
|
459
|
+
return "safe-read";
|
|
460
|
+
if (SAFE_WRITE_PATTERNS.some((p) => p.test(segment)))
|
|
461
|
+
return "safe-write";
|
|
462
|
+
// Pipe handling within a segment: every command in the pipe must
|
|
463
|
+
// independently classify as safe-read for the segment to count as
|
|
464
|
+
// safe-read. We did not split on `|` in the scanner because pipes
|
|
465
|
+
// are not statement boundaries; they're per-segment data flow.
|
|
466
|
+
if (segment.includes("|")) {
|
|
467
|
+
const parts = segment.split("|").map((p) => p.trim());
|
|
468
|
+
if (parts.every((p) => SAFE_READ_PATTERNS.some((re) => re.test(p)))) {
|
|
469
|
+
return "safe-read";
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
return "unsafe";
|
|
473
|
+
}
|
|
474
|
+
const SAFETY_RANK = {
|
|
475
|
+
"safe-read": 3,
|
|
476
|
+
"safe-write": 2,
|
|
477
|
+
unsafe: 1,
|
|
478
|
+
dangerous: 0,
|
|
479
|
+
};
|
|
480
|
+
function minSafety(a, b) {
|
|
481
|
+
return SAFETY_RANK[a] <= SAFETY_RANK[b] ? a : b;
|
|
482
|
+
}
|
|
483
|
+
export function classifyBashCommand(command) {
|
|
484
|
+
const trimmed = command.trim();
|
|
485
|
+
if (trimmed.length === 0)
|
|
486
|
+
return "unsafe";
|
|
487
|
+
// Whole-command dangerous patterns (e.g. `curl ... | sh`) need to
|
|
488
|
+
// be checked against the raw text because some of them span
|
|
489
|
+
// separators we would split on.
|
|
490
|
+
if (DANGEROUS_PATTERNS.some((p) => p.test(trimmed)))
|
|
491
|
+
return "dangerous";
|
|
492
|
+
if (PIPE_TO_SHELL_RE.test(trimmed))
|
|
493
|
+
return "dangerous";
|
|
494
|
+
const scan = scanShellCommand(trimmed);
|
|
495
|
+
if (scan.dangerous)
|
|
496
|
+
return "dangerous";
|
|
497
|
+
if (scan.segments.length === 0)
|
|
498
|
+
return "unsafe";
|
|
499
|
+
// Every top-level segment must independently classify. The whole
|
|
500
|
+
// command's safety is the minimum across segments.
|
|
501
|
+
let overall = "safe-read";
|
|
502
|
+
for (const seg of scan.segments) {
|
|
503
|
+
overall = minSafety(overall, classifySegment(seg));
|
|
504
|
+
if (overall === "dangerous")
|
|
505
|
+
return "dangerous";
|
|
506
|
+
}
|
|
507
|
+
return overall;
|
|
508
|
+
}
|
|
509
|
+
export class DenialTracker {
|
|
510
|
+
denials = new Map();
|
|
511
|
+
periodMs;
|
|
512
|
+
maxDenials;
|
|
513
|
+
constructor(opts) {
|
|
514
|
+
this.periodMs = opts?.periodMs ?? 60 * 60 * 1000; // 1 hour
|
|
515
|
+
this.maxDenials = opts?.maxDenials ?? 5;
|
|
516
|
+
}
|
|
517
|
+
record(toolName) {
|
|
518
|
+
const now = Date.now();
|
|
519
|
+
const existing = this.denials.get(toolName);
|
|
520
|
+
if (existing && now - existing.firstAt < this.periodMs) {
|
|
521
|
+
existing.count++;
|
|
522
|
+
existing.lastAt = now;
|
|
523
|
+
}
|
|
524
|
+
else {
|
|
525
|
+
this.denials.set(toolName, { count: 1, firstAt: now, lastAt: now });
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
recordSuccess(toolName) {
|
|
529
|
+
this.denials.delete(toolName);
|
|
530
|
+
}
|
|
531
|
+
shouldWarn(toolName) {
|
|
532
|
+
const record = this.denials.get(toolName);
|
|
533
|
+
if (!record)
|
|
534
|
+
return false;
|
|
535
|
+
if (Date.now() - record.firstAt > this.periodMs) {
|
|
536
|
+
this.denials.delete(toolName);
|
|
537
|
+
return false;
|
|
538
|
+
}
|
|
539
|
+
return record.count >= this.maxDenials;
|
|
540
|
+
}
|
|
541
|
+
getWarningMessage(toolName) {
|
|
542
|
+
const record = this.denials.get(toolName);
|
|
543
|
+
if (!record)
|
|
544
|
+
return "";
|
|
545
|
+
return (`Tool "${toolName}" has been denied ${record.count} times in the last hour. ` +
|
|
546
|
+
`Please stop attempting this tool and try an alternative approach.`);
|
|
547
|
+
}
|
|
548
|
+
clear() {
|
|
549
|
+
this.denials.clear();
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
// A1 hardening: tools that `acceptEdits` mode will auto-allow without
|
|
553
|
+
// asking. Everything else falls through to `ask`. Read-only tools
|
|
554
|
+
// (Read/Glob/Grep) reach `allow` through rule matching or default
|
|
555
|
+
// classification; they do not need to be in this list.
|
|
556
|
+
//
|
|
557
|
+
// Keep this set tight. Adding a tool here means "the user is OK with
|
|
558
|
+
// this tool running silently when they opt into acceptEdits".
|
|
559
|
+
export const ACCEPT_EDITS_ALLOWLIST = new Set([
|
|
560
|
+
"Write",
|
|
561
|
+
"Edit",
|
|
562
|
+
"ApplyPatch",
|
|
563
|
+
"NotebookEdit",
|
|
564
|
+
"TodoWrite",
|
|
565
|
+
]);
|
|
566
|
+
export class PermissionClassifier {
|
|
567
|
+
rules;
|
|
568
|
+
defaultMode;
|
|
569
|
+
approvalBackend;
|
|
570
|
+
denialTracker = new DenialTracker();
|
|
571
|
+
/**
|
|
572
|
+
* Turn-scoped logger so permission events (ask/decision/auto-deny) are
|
|
573
|
+
* tagged with the current turn/turnId. Falls back to the root logger so
|
|
574
|
+
* tests and ad-hoc usages still write log lines.
|
|
575
|
+
*/
|
|
576
|
+
log = rootPermLogger;
|
|
577
|
+
constructor(rules, defaultMode = "default", approvalBackend = new HeadlessApprovalBackend("deny-all")) {
|
|
578
|
+
this.rules = rules;
|
|
579
|
+
this.defaultMode = defaultMode;
|
|
580
|
+
this.approvalBackend = approvalBackend;
|
|
581
|
+
}
|
|
582
|
+
setLogger(log) {
|
|
583
|
+
this.log = log;
|
|
584
|
+
}
|
|
585
|
+
/** Replace mode + backend in place so live tool execution sees the change. */
|
|
586
|
+
reconfigure(mode, approvalBackend, rules) {
|
|
587
|
+
this.defaultMode = mode;
|
|
588
|
+
this.approvalBackend = approvalBackend;
|
|
589
|
+
if (rules)
|
|
590
|
+
this.rules = rules;
|
|
591
|
+
}
|
|
592
|
+
getMode() {
|
|
593
|
+
return this.defaultMode;
|
|
594
|
+
}
|
|
595
|
+
classify(toolName, args) {
|
|
596
|
+
// bypassPermissions mode overrides everything
|
|
597
|
+
if (this.defaultMode === "bypassPermissions")
|
|
598
|
+
return "allow";
|
|
599
|
+
// Check explicit rules (ordered by specificity)
|
|
600
|
+
for (const rule of this.rules) {
|
|
601
|
+
if (this.matchesRule(rule, toolName, args)) {
|
|
602
|
+
return rule.decision;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
// YOLO classifier for Bash commands
|
|
606
|
+
if (toolName === "Bash") {
|
|
607
|
+
const level = classifyBashCommand(String(args.command ?? ""));
|
|
608
|
+
if (level === "dangerous")
|
|
609
|
+
return "ask";
|
|
610
|
+
if (level === "safe-read")
|
|
611
|
+
return "allow";
|
|
612
|
+
if (level === "safe-write" &&
|
|
613
|
+
(this.defaultMode === "acceptEdits" || this.defaultMode === "auto")) {
|
|
614
|
+
return "allow";
|
|
615
|
+
}
|
|
616
|
+
if (level === "unsafe")
|
|
617
|
+
return "ask";
|
|
618
|
+
}
|
|
619
|
+
// Fall back to default mode
|
|
620
|
+
switch (this.defaultMode) {
|
|
621
|
+
case "dontAsk":
|
|
622
|
+
return "deny";
|
|
623
|
+
case "acceptEdits":
|
|
624
|
+
// A1 hardening: acceptEdits is an allowlist, not allow-all.
|
|
625
|
+
// Only auto-allow tools that mutate the workspace in ways the
|
|
626
|
+
// user has explicitly opted in to. Everything else (network,
|
|
627
|
+
// shell, MCP, etc.) still requires an interactive prompt.
|
|
628
|
+
return ACCEPT_EDITS_ALLOWLIST.has(toolName) ? "allow" : "ask";
|
|
629
|
+
default:
|
|
630
|
+
return "ask";
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
async handleAsk(toolName, args, reason) {
|
|
634
|
+
if (this.defaultMode === "dontAsk") {
|
|
635
|
+
this.log.info("permission.auto_deny", {
|
|
636
|
+
cat: "permission",
|
|
637
|
+
tool: toolName,
|
|
638
|
+
reason: "dontAsk_mode",
|
|
639
|
+
});
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
if (this.defaultMode === "bypassPermissions") {
|
|
643
|
+
this.log.info("permission.auto_allow", {
|
|
644
|
+
cat: "permission",
|
|
645
|
+
tool: toolName,
|
|
646
|
+
reason: "bypassPermissions_mode",
|
|
647
|
+
});
|
|
648
|
+
return true;
|
|
649
|
+
}
|
|
650
|
+
// Check denial tracker — if too many denials, auto-deny
|
|
651
|
+
if (this.denialTracker.shouldWarn(toolName)) {
|
|
652
|
+
this.log.warn("permission.auto_deny", {
|
|
653
|
+
cat: "permission",
|
|
654
|
+
tool: toolName,
|
|
655
|
+
reason: "denial_tracker_threshold",
|
|
656
|
+
});
|
|
657
|
+
return false;
|
|
658
|
+
}
|
|
659
|
+
const riskLevel = this.assessRisk(toolName, args);
|
|
660
|
+
const span = this.log.span("permission.ask", {
|
|
661
|
+
cat: "permission",
|
|
662
|
+
tool: toolName,
|
|
663
|
+
riskLevel,
|
|
664
|
+
});
|
|
665
|
+
let result;
|
|
666
|
+
try {
|
|
667
|
+
const baseDescription = this.describeToolCall(toolName, args);
|
|
668
|
+
const description = reason
|
|
669
|
+
? `${baseDescription}\n\nReason (from pre_tool_use hook): ${reason}`
|
|
670
|
+
: baseDescription;
|
|
671
|
+
result = await this.approvalBackend.requestApproval({
|
|
672
|
+
toolName,
|
|
673
|
+
args,
|
|
674
|
+
description,
|
|
675
|
+
riskLevel,
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
catch (err) {
|
|
679
|
+
span.fail(err, { tool: toolName });
|
|
680
|
+
throw err;
|
|
681
|
+
}
|
|
682
|
+
if (result.approved) {
|
|
683
|
+
this.denialTracker.recordSuccess(toolName);
|
|
684
|
+
}
|
|
685
|
+
else {
|
|
686
|
+
this.denialTracker.record(toolName);
|
|
687
|
+
}
|
|
688
|
+
span.end({
|
|
689
|
+
tool: toolName,
|
|
690
|
+
approved: result.approved,
|
|
691
|
+
// `scope` is set by the interactive backend when the user picks
|
|
692
|
+
// "for this session" / "for this project"; absent for one-shot answers.
|
|
693
|
+
scope: result.scope,
|
|
694
|
+
reason: !result.approved ? result.reason : undefined,
|
|
695
|
+
});
|
|
696
|
+
return result.approved;
|
|
697
|
+
}
|
|
698
|
+
/** Get denial warning message if the model keeps getting denied. */
|
|
699
|
+
getDenialWarning(toolName) {
|
|
700
|
+
if (this.denialTracker.shouldWarn(toolName)) {
|
|
701
|
+
return this.denialTracker.getWarningMessage(toolName);
|
|
702
|
+
}
|
|
703
|
+
return undefined;
|
|
704
|
+
}
|
|
705
|
+
matchesRule(rule, toolName, args) {
|
|
706
|
+
if (rule.tool !== toolName && rule.tool !== "*")
|
|
707
|
+
return false;
|
|
708
|
+
if (rule.argsPattern) {
|
|
709
|
+
for (const [key, pattern] of Object.entries(rule.argsPattern)) {
|
|
710
|
+
const argVal = String(args[key] ?? "");
|
|
711
|
+
if (pattern instanceof RegExp) {
|
|
712
|
+
if (!pattern.test(argVal))
|
|
713
|
+
return false;
|
|
714
|
+
}
|
|
715
|
+
else if (typeof pattern === "string") {
|
|
716
|
+
// Rules persisted in settings.local.json lose their RegExp type
|
|
717
|
+
// through JSON round-trip — they come back as strings. buildProjectRule
|
|
718
|
+
// produces regex sources like `^cd(\\s|$)`; treat any string with
|
|
719
|
+
// regex metacharacters as a pattern and only fall back to substring
|
|
720
|
+
// match for plain literals (kept for backward compat with hand-edited
|
|
721
|
+
// settings files that used plain substrings).
|
|
722
|
+
const looksLikeRegex = /[\\^$.*+?()[\]|{}]/.test(pattern);
|
|
723
|
+
if (looksLikeRegex) {
|
|
724
|
+
let re;
|
|
725
|
+
try {
|
|
726
|
+
re = new RegExp(pattern);
|
|
727
|
+
}
|
|
728
|
+
catch {
|
|
729
|
+
if (!argVal.includes(pattern))
|
|
730
|
+
return false;
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
733
|
+
if (!re.test(argVal))
|
|
734
|
+
return false;
|
|
735
|
+
}
|
|
736
|
+
else if (!argVal.includes(pattern)) {
|
|
737
|
+
return false;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return true;
|
|
743
|
+
}
|
|
744
|
+
isDangerousCommand(args) {
|
|
745
|
+
const command = String(args.command ?? "");
|
|
746
|
+
return DANGEROUS_PATTERNS.some((p) => p.test(command));
|
|
747
|
+
}
|
|
748
|
+
assessRisk(toolName, args) {
|
|
749
|
+
if (toolName === "Bash" && this.isDangerousCommand(args))
|
|
750
|
+
return "high";
|
|
751
|
+
if (["Write", "Edit"].includes(toolName))
|
|
752
|
+
return "medium";
|
|
753
|
+
if (toolName === "Bash")
|
|
754
|
+
return "medium";
|
|
755
|
+
return "low";
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Short, human-friendly subtitle. The UI shows this under the tool title;
|
|
759
|
+
* full args (command bodies, diffs, etc.) come from the args field, so this
|
|
760
|
+
* stays compact — no truncation here, just a label.
|
|
761
|
+
*/
|
|
762
|
+
describeToolCall(toolName, args) {
|
|
763
|
+
switch (toolName) {
|
|
764
|
+
case "Bash":
|
|
765
|
+
return String(args.description ?? "Run shell command");
|
|
766
|
+
case "Write":
|
|
767
|
+
return `Create or overwrite ${args.file_path}`;
|
|
768
|
+
case "Edit":
|
|
769
|
+
return `Modify ${args.file_path}`;
|
|
770
|
+
default:
|
|
771
|
+
return toolName;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|