@armadra/agent 0.3.0 → 0.5.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/CHANGELOG.md +257 -0
- package/README.md +263 -95
- package/THIRD_PARTY_NOTICES.md +34 -0
- package/dist/acp.d.ts +18 -0
- package/dist/acp.js +19 -0
- package/dist/agent/limits.d.ts +38 -0
- package/dist/agent/limits.js +127 -0
- package/dist/agent/loop-guard.d.ts +38 -0
- package/dist/agent/loop-guard.js +95 -0
- package/dist/agent/loop.d.ts +4 -0
- package/dist/agent/loop.js +7 -0
- package/dist/agent/prompt-rules.d.ts +12 -0
- package/dist/agent/prompt-rules.js +18 -0
- package/dist/agent/reminders.d.ts +59 -0
- package/dist/agent/reminders.js +327 -0
- package/dist/agent/retry.d.ts +1 -1
- package/dist/agent/retry.js +2 -1
- package/dist/agent/session-cache.d.ts +5 -0
- package/dist/agent/session-cache.js +18 -2
- package/dist/agent/session-classifier.d.ts +18 -0
- package/dist/agent/session-classifier.js +105 -0
- package/dist/agent/session-compaction.d.ts +35 -6
- package/dist/agent/session-compaction.js +157 -31
- package/dist/agent/session-core.d.ts +40 -1
- package/dist/agent/session-extensions.d.ts +61 -0
- package/dist/agent/session-extensions.js +129 -0
- package/dist/agent/session-images.d.ts +23 -0
- package/dist/agent/session-images.js +97 -0
- package/dist/agent/session-plan.d.ts +21 -0
- package/dist/agent/session-plan.js +524 -0
- package/dist/agent/session-rewind.d.ts +81 -0
- package/dist/agent/session-rewind.js +354 -0
- package/dist/agent/session-run.d.ts +28 -1
- package/dist/agent/session-run.js +157 -6
- package/dist/agent/session-settings.d.ts +49 -0
- package/dist/agent/session-settings.js +95 -0
- package/dist/agent/session-subagent.d.ts +36 -24
- package/dist/agent/session-subagent.js +259 -129
- package/dist/agent/session-sync.d.ts +10 -3
- package/dist/agent/session-sync.js +37 -8
- package/dist/agent/session-telemetry.d.ts +39 -0
- package/dist/agent/session-telemetry.js +231 -0
- package/dist/agent/session-tools.js +63 -3
- package/dist/agent/session.d.ts +33 -29
- package/dist/agent/session.js +91 -126
- package/dist/agent/subagent-registry.d.ts +91 -0
- package/dist/agent/subagent-registry.js +494 -0
- package/dist/agent/system-prompt.d.ts +6 -3
- package/dist/agent/system-prompt.js +7 -2
- package/dist/agent/tool-runner.d.ts +8 -1
- package/dist/agent/tool-runner.js +72 -6
- package/dist/agent/types-w5.d.ts +192 -0
- package/dist/agent/types-w5.js +10 -0
- package/dist/agent/types.d.ts +55 -4
- package/dist/agent/types.js +2 -0
- package/dist/agent/worktree.d.ts +36 -0
- package/dist/agent/worktree.js +86 -0
- package/dist/agents/builtin.d.ts +14 -0
- package/dist/agents/builtin.js +39 -0
- package/dist/agents/catalog.d.ts +43 -0
- package/dist/agents/catalog.js +82 -0
- package/dist/agents/discover.d.ts +35 -0
- package/dist/agents/discover.js +83 -0
- package/dist/agents/external.d.ts +82 -0
- package/dist/agents/external.js +326 -0
- package/dist/agents/parse.d.ts +18 -0
- package/dist/agents/parse.js +116 -0
- package/dist/agents/result.d.ts +32 -0
- package/dist/agents/result.js +67 -0
- package/dist/agents/task-control.d.ts +17 -0
- package/dist/agents/task-control.js +17 -0
- package/dist/agents/task-record.d.ts +78 -0
- package/dist/agents/task-record.js +141 -0
- package/dist/agents/types.d.ts +46 -0
- package/dist/agents/types.js +8 -0
- package/dist/ai/apis/anthropic-compat.d.ts +36 -0
- package/dist/ai/apis/anthropic-compat.js +92 -0
- package/dist/ai/apis/anthropic-messages.js +3 -2
- package/dist/ai/apis/anthropic-request.d.ts +7 -6
- package/dist/ai/apis/anthropic-request.js +10 -28
- package/dist/ai/apis/cache-params.d.ts +11 -2
- package/dist/ai/apis/cache-params.js +25 -6
- package/dist/ai/apis/google-generative-ai.js +3 -2
- package/dist/ai/apis/openai-compat.d.ts +4 -1
- package/dist/ai/apis/openai-compat.js +8 -1
- package/dist/ai/apis/openai-completions.js +3 -2
- package/dist/ai/apis/openai-responses.js +3 -2
- package/dist/ai/http.d.ts +28 -6
- package/dist/ai/http.js +41 -8
- package/dist/ai/image-limits.d.ts +45 -0
- package/dist/ai/image-limits.js +68 -0
- package/dist/ai/providers/builtin.d.ts +18 -6
- package/dist/ai/providers/builtin.js +135 -6
- package/dist/ai/providers/catalog-data.js +17 -13
- package/dist/ai/providers/catalog.d.ts +60 -7
- package/dist/ai/providers/catalog.js +197 -38
- package/dist/ai/providers/channels.d.ts +33 -3
- package/dist/ai/providers/channels.js +108 -7
- package/dist/ai/providers/enrich.d.ts +6 -3
- package/dist/ai/providers/enrich.js +19 -4
- package/dist/ai/providers/models-dev-cache.d.ts +28 -24
- package/dist/ai/providers/models-dev-cache.js +92 -78
- package/dist/ai/providers/models-dev-data.d.ts +7 -0
- package/dist/ai/providers/models-dev-data.js +32 -0
- package/dist/ai/providers/models-dev-snapshot.d.ts +50 -0
- package/dist/ai/providers/models-dev-snapshot.js +137 -0
- package/dist/ai/providers/models-dev.d.ts +51 -10
- package/dist/ai/providers/models-dev.js +132 -39
- package/dist/ai/providers/registry.d.ts +28 -11
- package/dist/ai/providers/registry.js +112 -65
- package/dist/ai/providers/suggest.d.ts +18 -0
- package/dist/ai/providers/suggest.js +72 -0
- package/dist/ai/sse.d.ts +6 -2
- package/dist/ai/sse.js +22 -2
- package/dist/ai/types.d.ts +37 -4
- package/dist/ai/types.js +5 -0
- package/dist/bundle/ama.cjs +41979 -21147
- package/dist/checkpoints/backend.d.ts +61 -0
- package/dist/checkpoints/backend.js +135 -0
- package/dist/checkpoints/blobs.d.ts +60 -0
- package/dist/checkpoints/blobs.js +203 -0
- package/dist/checkpoints/gc.d.ts +47 -0
- package/dist/checkpoints/gc.js +144 -0
- package/dist/checkpoints/git-head.d.ts +15 -0
- package/dist/checkpoints/git-head.js +89 -0
- package/dist/checkpoints/index.d.ts +17 -0
- package/dist/checkpoints/index.js +17 -0
- package/dist/checkpoints/replay.d.ts +48 -0
- package/dist/checkpoints/replay.js +154 -0
- package/dist/checkpoints/restore.d.ts +86 -0
- package/dist/checkpoints/restore.js +263 -0
- package/dist/checkpoints/settings.d.ts +14 -0
- package/dist/checkpoints/settings.js +23 -0
- package/dist/checkpoints/shadow-git.d.ts +108 -0
- package/dist/checkpoints/shadow-git.js +491 -0
- package/dist/checkpoints/shadow-restore.d.ts +25 -0
- package/dist/checkpoints/shadow-restore.js +128 -0
- package/dist/checkpoints/tracker.d.ts +64 -0
- package/dist/checkpoints/tracker.js +192 -0
- package/dist/checkpoints/types.d.ts +99 -0
- package/dist/checkpoints/types.js +12 -0
- package/dist/cli/args.d.ts +26 -7
- package/dist/cli/args.js +75 -82
- package/dist/cli/bootstrap.js +40 -4
- package/dist/cli/codemode-notice.d.ts +21 -0
- package/dist/cli/codemode-notice.js +56 -0
- package/dist/cli/compose-agents.d.ts +27 -0
- package/dist/cli/compose-agents.js +111 -0
- package/dist/cli/compose-extensions.d.ts +29 -0
- package/dist/cli/compose-extensions.js +44 -0
- package/dist/cli/compose-session.d.ts +8 -0
- package/dist/cli/compose-session.js +76 -2
- package/dist/cli/compose-store.d.ts +1 -1
- package/dist/cli/compose-store.js +3 -1
- package/dist/cli/compose.d.ts +17 -4
- package/dist/cli/compose.js +60 -14
- package/dist/cli/default-model.d.ts +38 -1
- package/dist/cli/default-model.js +96 -9
- package/dist/cli/deps.d.ts +42 -0
- package/dist/cli/exit-codes.d.ts +12 -0
- package/dist/cli/exit-codes.js +15 -0
- package/dist/cli/fake-visibility.d.ts +13 -0
- package/dist/cli/fake-visibility.js +26 -0
- package/dist/cli/from-prompt.d.ts +18 -0
- package/dist/cli/from-prompt.js +49 -0
- package/dist/cli/help-text.d.ts +4 -0
- package/dist/cli/help-text.js +102 -0
- package/dist/cli/main.d.ts +9 -2
- package/dist/cli/main.js +77 -3
- package/dist/cli/proxy.d.ts +51 -0
- package/dist/cli/proxy.js +135 -0
- package/dist/cli/startup-screen.d.ts +29 -0
- package/dist/cli/startup-screen.js +51 -2
- package/dist/cli/startup-steps.d.ts +1 -1
- package/dist/cli/startup-steps.js +21 -13
- package/dist/cli/subcommands/config.d.ts +22 -3
- package/dist/cli/subcommands/config.js +119 -21
- package/dist/cli/subcommands/context.js +4 -1
- package/dist/cli/subcommands/doctor.js +33 -1
- package/dist/cli/subcommands/init.js +2 -1
- package/dist/cli/subcommands/models-discover.d.ts +15 -9
- package/dist/cli/subcommands/models-discover.js +58 -52
- package/dist/cli/subcommands/models.d.ts +2 -0
- package/dist/cli/subcommands/models.js +33 -14
- package/dist/cli/subcommands/probe-runner.d.ts +96 -0
- package/dist/cli/subcommands/probe-runner.js +264 -0
- package/dist/cli/subcommands/providers-probe.d.ts +34 -0
- package/dist/cli/subcommands/providers-probe.js +87 -0
- package/dist/cli/subcommands/providers.d.ts +3 -2
- package/dist/cli/subcommands/providers.js +53 -51
- package/dist/cli/subcommands/sessions-export.d.ts +10 -0
- package/dist/cli/subcommands/sessions-export.js +59 -0
- package/dist/cli/subcommands/sessions-search.d.ts +13 -0
- package/dist/cli/subcommands/sessions-search.js +103 -0
- package/dist/cli/subcommands/sessions.d.ts +6 -3
- package/dist/cli/subcommands/sessions.js +58 -3
- package/dist/cli/subcommands/stats.d.ts +17 -0
- package/dist/cli/subcommands/stats.js +198 -0
- package/dist/cli/system-prompt-arg.d.ts +11 -0
- package/dist/cli/system-prompt-arg.js +34 -0
- package/dist/codemode/capability.d.ts +24 -7
- package/dist/codemode/capability.js +38 -10
- package/dist/codemode/host-side.d.ts +14 -0
- package/dist/codemode/host-side.js +24 -1
- package/dist/codemode/modes.d.ts +4 -11
- package/dist/codemode/modes.js +5 -27
- package/dist/codemode/tool.d.ts +18 -14
- package/dist/codemode/tool.js +76 -31
- package/dist/compaction/breaker.d.ts +25 -9
- package/dist/compaction/breaker.js +47 -17
- package/dist/compaction/estimate.d.ts +11 -3
- package/dist/compaction/estimate.js +64 -22
- package/dist/compaction/image-budget.d.ts +45 -0
- package/dist/compaction/image-budget.js +115 -0
- package/dist/compaction/post-compact.d.ts +38 -0
- package/dist/compaction/post-compact.js +152 -0
- package/dist/compaction/protect.d.ts +27 -0
- package/dist/compaction/protect.js +56 -0
- package/dist/compaction/prune-tier.d.ts +58 -12
- package/dist/compaction/prune-tier.js +120 -54
- package/dist/compaction/summarize-tier.d.ts +23 -4
- package/dist/compaction/summarize-tier.js +100 -29
- package/dist/config/checker.d.ts +28 -0
- package/dist/config/checker.js +97 -0
- package/dist/config/init.d.ts +6 -2
- package/dist/config/init.js +12 -5
- package/dist/config/json-schema.d.ts +1 -0
- package/dist/config/json-schema.js +98 -4
- package/dist/config/key-docs.d.ts +22 -0
- package/dist/config/key-docs.js +174 -0
- package/dist/config/merge.d.ts +12 -9
- package/dist/config/merge.js +102 -13
- package/dist/config/profile.d.ts +2 -0
- package/dist/config/profile.js +2 -0
- package/dist/config/schema-w5.d.ts +18 -0
- package/dist/config/schema-w5.js +109 -0
- package/dist/config/schema.d.ts +5 -9
- package/dist/config/schema.js +91 -118
- package/dist/config/types-w5.d.ts +118 -0
- package/dist/config/types-w5.js +20 -0
- package/dist/config/types.d.ts +84 -7
- package/dist/config/types.js +29 -1
- package/dist/drivers/acp/client.d.ts +57 -0
- package/dist/drivers/acp/client.js +160 -0
- package/dist/drivers/acp/driver.d.ts +31 -0
- package/dist/drivers/acp/driver.js +274 -0
- package/dist/drivers/acp/testing/fake-agent-main.d.ts +5 -0
- package/dist/drivers/acp/testing/fake-agent-main.js +7 -0
- package/dist/drivers/acp/testing/fake-agent.d.ts +27 -0
- package/dist/drivers/acp/testing/fake-agent.js +211 -0
- package/dist/drivers/acp/types.d.ts +285 -0
- package/dist/drivers/acp/types.js +36 -0
- package/dist/drivers/agents.d.ts +58 -0
- package/dist/drivers/agents.js +142 -0
- package/dist/drivers/base.d.ts +35 -0
- package/dist/drivers/base.js +59 -0
- package/dist/drivers/catalog.d.ts +46 -0
- package/dist/drivers/catalog.js +167 -0
- package/dist/drivers/env.d.ts +20 -0
- package/dist/drivers/env.js +46 -0
- package/dist/drivers/host-runners.d.ts +18 -0
- package/dist/drivers/host-runners.js +45 -0
- package/dist/drivers/jsonrpc.d.ts +58 -0
- package/dist/drivers/jsonrpc.js +180 -0
- package/dist/drivers/native/claude-normalize.d.ts +46 -0
- package/dist/drivers/native/claude-normalize.js +106 -0
- package/dist/drivers/native/claude-stream.d.ts +29 -0
- package/dist/drivers/native/claude-stream.js +456 -0
- package/dist/drivers/native/codex-app-server.d.ts +26 -0
- package/dist/drivers/native/codex-app-server.js +407 -0
- package/dist/drivers/native/codex-normalize.d.ts +38 -0
- package/dist/drivers/native/codex-normalize.js +124 -0
- package/dist/drivers/native/oneshot.d.ts +28 -0
- package/dist/drivers/native/oneshot.js +312 -0
- package/dist/drivers/permissions.d.ts +40 -0
- package/dist/drivers/permissions.js +92 -0
- package/dist/drivers/pids.d.ts +32 -0
- package/dist/drivers/pids.js +93 -0
- package/dist/drivers/pool.d.ts +28 -0
- package/dist/drivers/pool.js +96 -0
- package/dist/drivers/probe.d.ts +47 -0
- package/dist/drivers/probe.js +172 -0
- package/dist/drivers/process.d.ts +48 -0
- package/dist/drivers/process.js +100 -0
- package/dist/drivers/runner.d.ts +61 -0
- package/dist/drivers/runner.js +388 -0
- package/dist/drivers/store.d.ts +49 -0
- package/dist/drivers/store.js +91 -0
- package/dist/drivers/turn.d.ts +41 -0
- package/dist/drivers/turn.js +99 -0
- package/dist/drivers/types.d.ts +122 -0
- package/dist/drivers/types.js +11 -0
- package/dist/git/info.d.ts +75 -0
- package/dist/git/info.js +191 -0
- package/dist/hooks/protocol.js +6 -1
- package/dist/hooks/types.d.ts +10 -1
- package/dist/hooks/types.js +2 -0
- package/dist/host/api-impl.d.ts +7 -0
- package/dist/host/api-impl.js +15 -0
- package/dist/host/types.d.ts +24 -2
- package/dist/host/types.js +4 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1 -0
- package/dist/modes/acp/acp-events.d.ts +46 -0
- package/dist/modes/acp/acp-events.js +233 -0
- package/dist/modes/acp/acp-mode.d.ts +17 -0
- package/dist/modes/acp/acp-mode.js +47 -0
- package/dist/modes/acp/acp-server.d.ts +79 -0
- package/dist/modes/acp/acp-server.js +315 -0
- package/dist/modes/commands-core.d.ts +7 -1
- package/dist/modes/commands-core.js +61 -5
- package/dist/modes/image-input.d.ts +12 -4
- package/dist/modes/image-input.js +22 -5
- package/dist/modes/interactive/agent-panels.d.ts +14 -0
- package/dist/modes/interactive/agent-panels.js +103 -0
- package/dist/modes/interactive/agent-ui.d.ts +73 -0
- package/dist/modes/interactive/agent-ui.js +234 -0
- package/dist/modes/interactive/approval-dialog.d.ts +40 -7
- package/dist/modes/interactive/approval-dialog.js +192 -35
- package/dist/modes/interactive/approval-merge.d.ts +41 -0
- package/dist/modes/interactive/approval-merge.js +89 -0
- package/dist/modes/interactive/clipboard-paste.d.ts +18 -0
- package/dist/modes/interactive/clipboard-paste.js +28 -0
- package/dist/modes/interactive/commands.d.ts +20 -3
- package/dist/modes/interactive/commands.js +75 -16
- package/dist/modes/interactive/double-esc.d.ts +20 -0
- package/dist/modes/interactive/double-esc.js +34 -0
- package/dist/modes/interactive/event-notices.d.ts +32 -0
- package/dist/modes/interactive/event-notices.js +79 -0
- package/dist/modes/interactive/external-editor.d.ts +16 -0
- package/dist/modes/interactive/external-editor.js +39 -0
- package/dist/modes/interactive/interactive-mode.d.ts +16 -2
- package/dist/modes/interactive/interactive-mode.js +184 -202
- package/dist/modes/interactive/key-dispatch.d.ts +18 -2
- package/dist/modes/interactive/key-dispatch.js +53 -6
- package/dist/modes/interactive/line/line-mode.d.ts +3 -1
- package/dist/modes/interactive/line/line-mode.js +28 -9
- package/dist/modes/interactive/line/line-render.d.ts +6 -1
- package/dist/modes/interactive/line/line-render.js +66 -8
- package/dist/modes/interactive/message-view.d.ts +49 -10
- package/dist/modes/interactive/message-view.js +262 -46
- package/dist/modes/interactive/panels.d.ts +35 -0
- package/dist/modes/interactive/panels.js +157 -0
- package/dist/modes/interactive/pickers.d.ts +23 -2
- package/dist/modes/interactive/pickers.js +48 -15
- package/dist/modes/interactive/plan-command.d.ts +40 -0
- package/dist/modes/interactive/plan-command.js +129 -0
- package/dist/modes/interactive/plan-dialog.d.ts +98 -0
- package/dist/modes/interactive/plan-dialog.js +290 -0
- package/dist/modes/interactive/plan-flow.d.ts +34 -0
- package/dist/modes/interactive/plan-flow.js +98 -0
- package/dist/modes/interactive/rewind-command.d.ts +31 -0
- package/dist/modes/interactive/rewind-command.js +106 -0
- package/dist/modes/interactive/rewind-flow.d.ts +37 -0
- package/dist/modes/interactive/rewind-flow.js +151 -0
- package/dist/modes/interactive/rewind-list.d.ts +36 -0
- package/dist/modes/interactive/rewind-list.js +123 -0
- package/dist/modes/interactive/rewind-panel.d.ts +77 -0
- package/dist/modes/interactive/rewind-panel.js +309 -0
- package/dist/modes/interactive/rewind-text.d.ts +40 -0
- package/dist/modes/interactive/rewind-text.js +139 -0
- package/dist/modes/interactive/run-indicator.d.ts +51 -0
- package/dist/modes/interactive/run-indicator.js +189 -0
- package/dist/modes/interactive/session-events.d.ts +24 -0
- package/dist/modes/interactive/session-events.js +96 -0
- package/dist/modes/interactive/startup-header.d.ts +40 -0
- package/dist/modes/interactive/startup-header.js +169 -0
- package/dist/modes/interactive/status-area.d.ts +73 -0
- package/dist/modes/interactive/status-area.js +190 -0
- package/dist/modes/interactive/status-bar.d.ts +107 -16
- package/dist/modes/interactive/status-bar.js +306 -81
- package/dist/modes/interactive/status-line.d.ts +33 -0
- package/dist/modes/interactive/status-line.js +113 -0
- package/dist/modes/interactive/subagent-view.d.ts +56 -0
- package/dist/modes/interactive/subagent-view.js +154 -0
- package/dist/modes/interactive/tasks-report.d.ts +27 -0
- package/dist/modes/interactive/tasks-report.js +114 -0
- package/dist/modes/interactive/tool-summary.d.ts +46 -0
- package/dist/modes/interactive/tool-summary.js +218 -0
- package/dist/modes/interactive/tool-view.d.ts +58 -15
- package/dist/modes/interactive/tool-view.js +233 -145
- package/dist/modes/print/print-mode.d.ts +40 -4
- package/dist/modes/print/print-mode.js +161 -8
- package/dist/modes/rpc/commands.d.ts +9 -0
- package/dist/modes/rpc/commands.js +49 -0
- package/dist/modes/rpc/rpc-mode.js +7 -1
- package/dist/modes/session-report.d.ts +11 -0
- package/dist/modes/session-report.js +48 -1
- package/dist/permissions/auto-safe.d.ts +65 -0
- package/dist/permissions/auto-safe.js +547 -0
- package/dist/permissions/classifier.d.ts +68 -0
- package/dist/permissions/classifier.js +186 -0
- package/dist/permissions/dangerous.d.ts +5 -0
- package/dist/permissions/dangerous.js +1 -1
- package/dist/permissions/modes.d.ts +30 -0
- package/dist/permissions/modes.js +78 -0
- package/dist/permissions/pipeline.d.ts +72 -6
- package/dist/permissions/pipeline.js +306 -11
- package/dist/permissions/protected.d.ts +19 -0
- package/dist/permissions/protected.js +74 -0
- package/dist/permissions/readonly-bash.d.ts +30 -0
- package/dist/permissions/readonly-bash.js +118 -0
- package/dist/permissions/rules.js +3 -0
- package/dist/permissions/types.d.ts +92 -3
- package/dist/permissions/types.js +3 -0
- package/dist/plan/compose.d.ts +9 -0
- package/dist/plan/compose.js +19 -0
- package/dist/plan/controller.d.ts +71 -0
- package/dist/plan/controller.js +27 -0
- package/dist/plan/done-markers.d.ts +17 -0
- package/dist/plan/done-markers.js +51 -0
- package/dist/plan/extract.d.ts +29 -0
- package/dist/plan/extract.js +160 -0
- package/dist/plan/prompts.d.ts +36 -0
- package/dist/plan/prompts.js +89 -0
- package/dist/plan/store.d.ts +37 -0
- package/dist/plan/store.js +90 -0
- package/dist/rpc.d.ts +58 -2
- package/dist/rpc.js +7 -0
- package/dist/sandbox/bash.d.ts +74 -0
- package/dist/sandbox/bash.js +126 -0
- package/dist/sandbox/detect.d.ts +48 -0
- package/dist/sandbox/detect.js +149 -0
- package/dist/sandbox/index.d.ts +5 -0
- package/dist/sandbox/index.js +5 -0
- package/dist/sandbox/profile.d.ts +33 -0
- package/dist/sandbox/profile.js +76 -0
- package/dist/sandbox/wrap.d.ts +29 -0
- package/dist/sandbox/wrap.js +117 -0
- package/dist/sdk.d.ts +42 -5
- package/dist/sdk.js +34 -4
- package/dist/session/export.d.ts +32 -0
- package/dist/session/export.js +187 -0
- package/dist/session/redact.d.ts +15 -0
- package/dist/session/redact.js +55 -0
- package/dist/session/reuse.d.ts +33 -0
- package/dist/session/reuse.js +86 -0
- package/dist/session/scan.d.ts +34 -0
- package/dist/session/scan.js +140 -0
- package/dist/session/search.d.ts +52 -0
- package/dist/session/search.js +211 -0
- package/dist/session/stats-aggregate.d.ts +63 -0
- package/dist/session/stats-aggregate.js +163 -0
- package/dist/session/stats-index.d.ts +26 -0
- package/dist/session/stats-index.js +91 -0
- package/dist/session/stats-scan.d.ts +54 -0
- package/dist/session/stats-scan.js +236 -0
- package/dist/session/types.d.ts +3 -1
- package/dist/session/types.js +1 -0
- package/dist/skills/builtin.d.ts +31 -0
- package/dist/skills/builtin.js +109 -0
- package/dist/skills/discover.d.ts +2 -1
- package/dist/skills/index-prompt.js +5 -8
- package/dist/tools/background-jobs.d.ts +71 -0
- package/dist/tools/background-jobs.js +198 -0
- package/dist/tools/bash.d.ts +53 -1
- package/dist/tools/bash.js +158 -7
- package/dist/tools/clipboard-image.d.ts +44 -0
- package/dist/tools/clipboard-image.js +148 -0
- package/dist/tools/edit.d.ts +2 -0
- package/dist/tools/edit.js +18 -3
- package/dist/tools/image-file.d.ts +38 -3
- package/dist/tools/image-file.js +81 -10
- package/dist/tools/image-resize.d.ts +58 -0
- package/dist/tools/image-resize.js +97 -0
- package/dist/tools/presets.d.ts +40 -8
- package/dist/tools/presets.js +65 -19
- package/dist/tools/read.d.ts +5 -2
- package/dist/tools/read.js +13 -10
- package/dist/tools/registry.d.ts +7 -1
- package/dist/tools/registry.js +27 -4
- package/dist/tools/task-ctl.d.ts +22 -0
- package/dist/tools/task-ctl.js +116 -0
- package/dist/tools/task.d.ts +22 -10
- package/dist/tools/task.js +66 -26
- package/dist/tools/todo.d.ts +22 -2
- package/dist/tools/todo.js +58 -14
- package/dist/tools/truncate.d.ts +15 -0
- package/dist/tools/truncate.js +25 -0
- package/dist/tools/types.d.ts +113 -0
- package/dist/tools/types.js +5 -0
- package/dist/tools/write.js +3 -0
- package/dist/tui/component.d.ts +8 -2
- package/dist/tui/component.js +3 -1
- package/dist/tui/components/box.d.ts +6 -1
- package/dist/tui/components/box.js +16 -6
- package/dist/tui/components/card.d.ts +23 -0
- package/dist/tui/components/card.js +37 -0
- package/dist/tui/components/editor-history.d.ts +6 -0
- package/dist/tui/components/editor-history.js +45 -0
- package/dist/tui/components/editor-paste.d.ts +1 -1
- package/dist/tui/components/editor-paste.js +4 -4
- package/dist/tui/components/editor.d.ts +11 -5
- package/dist/tui/components/editor.js +52 -58
- package/dist/tui/components/key-value.d.ts +3 -0
- package/dist/tui/components/key-value.js +16 -6
- package/dist/tui/components/loader.d.ts +37 -7
- package/dist/tui/components/loader.js +84 -21
- package/dist/tui/components/markdown.d.ts +5 -1
- package/dist/tui/components/markdown.js +45 -21
- package/dist/tui/components/meter.d.ts +3 -3
- package/dist/tui/components/meter.js +13 -11
- package/dist/tui/components/select-list.d.ts +23 -1
- package/dist/tui/components/select-list.js +76 -13
- package/dist/tui/glyphs.d.ts +72 -0
- package/dist/tui/glyphs.js +118 -0
- package/dist/tui/keybindings.d.ts +5 -0
- package/dist/tui/keybindings.js +5 -0
- package/dist/tui/theme.d.ts +30 -6
- package/dist/tui/theme.js +103 -17
- package/dist/tui.d.ts +4 -2
- package/dist/tui.js +3 -1
- package/docs/acp.md +78 -0
- package/docs/agents.md +250 -0
- package/docs/codemode.md +28 -10
- package/docs/hooks.md +36 -32
- package/docs/permissions.md +212 -0
- package/docs/plan.md +107 -0
- package/docs/providers.md +261 -62
- package/docs/rewind-plan.md +181 -0
- package/docs/rpc.md +127 -40
- package/docs/sandbox.md +215 -0
- package/docs/session-format.md +43 -22
- package/docs/sessions.md +174 -0
- package/docs/tui-design.md +751 -0
- package/docs/tui.md +260 -64
- package/package.json +15 -2
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 权限管线(设计 §6.3 第 3 步、§7.1、D11)。[B3]
|
|
2
|
+
* 权限管线(设计 §6.3 第 3 步、§7.1、§7.4、D11)。[B3]
|
|
3
3
|
*
|
|
4
4
|
* ① deny 规则 ∪ Hook deny → deny
|
|
5
5
|
* ② 危险命令(仅 bash) → ask(无人值守 deny),之后各步不能放宽
|
|
6
|
-
* ③ 模式:plan read 允许,write / execute deny
|
|
6
|
+
* ③ 模式:plan read 允许,write / execute deny([W5-F] 细化见 {@link planDecision}:只读 bash、
|
|
7
|
+
* task 放行,`todo set / update` 拒绝)
|
|
7
8
|
* default read 允许,write / execute 问
|
|
8
9
|
* auto-edit read / write 允许,execute 问
|
|
9
10
|
* full-auto 全部允许
|
|
@@ -11,16 +12,36 @@
|
|
|
11
12
|
* Hook ask:随后把 allow 变 ask——净效果是 Hook ask 总得到 ask(reason "hook",对话框显示 Hook 的 reason)
|
|
12
13
|
* 无人值守:最终 ask → deny。
|
|
13
14
|
*
|
|
15
|
+
* auto(§7.4):①② 同上;规则层再把受保护路径、项目外写入、网络命令、删除类命令判 ask(不能被 allow
|
|
16
|
+
* 规则越过);Hook ask → ask;allow 规则 / Hook allow / 会话记忆 → allow;静态判定(只读工具、项目内写入、
|
|
17
|
+
* 安全名单里的 bash)→ allow;都没决定 → ask 且 `classify: true`,由调用方问模型分类器(分类器只能
|
|
18
|
+
* 把它变 allow)。每个结论带 `auto{layer, decision, reason}`。
|
|
19
|
+
*
|
|
20
|
+
* allowlist:①② 同上;只读工具、只读 bash([W5-F] 与 plan 同一子集,保持 plan ⊆ allowlist)与
|
|
21
|
+
* allow 规则 / Hook allow 命中放行,task 同 plan 放行(子会话共用本管线),其余 deny——从不询问
|
|
22
|
+
* (危险命令、Hook ask 也 deny)。
|
|
23
|
+
*
|
|
14
24
|
* allow_session 记忆(内存,不落盘):bash 记命令的前两个词(`npm test`),之后以它开头的命令
|
|
15
25
|
* 命中;带 path 的工具记文件所在目录,之后该目录下的路径命中;其它工具只记工具名。
|
|
16
|
-
* 记忆只作用于第 ③ 步模式产生的 ask
|
|
26
|
+
* 记忆只作用于第 ③ 步模式产生的 ask(auto 里只作用于规则层之后)。
|
|
27
|
+
*
|
|
28
|
+
* [S2] bash 沙箱(docs/permissions.md「判定顺序」):default / auto-edit 下,bash 调用若将在 OS 沙箱内
|
|
29
|
+
* 运行且沙箱拒绝网络、没请求 `sandbox: false`、命令文本不碰机密路径、嵌套不超深,第 ③ 步的 ask 变
|
|
30
|
+
* allow(`sandboxed: true`);①② 与 Hook ask 照旧优先。auto 下沙箱不直接放行,只作为分类器输入;
|
|
31
|
+
* 请求 `sandbox: false` 越出沙箱时规则层询问(allow 规则 / Hook allow / 会话记忆仍可放行)。
|
|
17
32
|
*
|
|
18
33
|
* 包装里的命令(`sh -c '…'`、`eval`、`xargs`、`find -exec`,见 dangerous.ts)逐层核对:deny 规则命中
|
|
19
34
|
* 任一层即 deny;allow 规则与会话记忆要求外层与每一层嵌套命令都被覆盖,嵌套超深时不放行。
|
|
20
35
|
*/
|
|
21
36
|
import { dirname, sep } from "node:path";
|
|
37
|
+
import { isBackgroundJobQuery } from "../tools/background-jobs.js";
|
|
22
38
|
import { findAllowRule, findDenyRule, inputCommand, inputPath, normalizeCommand, splitShellSegments, } from "./rules.js";
|
|
23
39
|
import { collectNestedCommands, matchDangerous } from "./dangerous.js";
|
|
40
|
+
import { analyzeBashForAuto, secretReferenceReason } from "./auto-safe.js";
|
|
41
|
+
import { runsSandboxed, wantsUnsandboxed } from "../sandbox/bash.js";
|
|
42
|
+
import { isReadonlyBash } from "./readonly-bash.js";
|
|
43
|
+
import { secretPathReason, writeProtectionReason } from "./protected.js";
|
|
44
|
+
import { AUTO_AUDIT_LIMIT } from "./types.js";
|
|
24
45
|
/** bash 调用拆成「外层 + 每层嵌套命令」各自的输入;嵌套超深时 tooDeep 为真。非 bash 原样返回。 */
|
|
25
46
|
function layeredInputs(toolName, input) {
|
|
26
47
|
const command = toolName === "bash" ? inputCommand(input) : undefined;
|
|
@@ -32,17 +53,67 @@ function layeredInputs(toolName, input) {
|
|
|
32
53
|
tooDeep: nested.tooDeep,
|
|
33
54
|
};
|
|
34
55
|
}
|
|
35
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* 第 ③ 步的模式真值表(不看输入)。auto 的 write / execute 在这里是 ask,真正的结论由规则层、
|
|
58
|
+
* 静态判定与分类器给出;allowlist 的 write / execute 是 deny(allow 规则可放行)。
|
|
59
|
+
*/
|
|
36
60
|
export function modeDecision(mode, permission) {
|
|
37
61
|
if (permission === "read" || mode === "full-auto")
|
|
38
62
|
return "allow";
|
|
39
|
-
if (mode === "plan")
|
|
63
|
+
if (mode === "plan" || mode === "allowlist")
|
|
40
64
|
return "deny";
|
|
41
65
|
if (mode === "auto-edit")
|
|
42
66
|
return permission === "write" ? "allow" : "ask";
|
|
43
67
|
return "ask";
|
|
44
68
|
}
|
|
69
|
+
/** [W5-F] plan 模式拒绝写 / 执行时的说明:带指引,提醒被压缩掉后模型也能从拒绝结果里恢复。 */
|
|
70
|
+
export const PLAN_MODE_MESSAGE = "Plan mode is active: write/execute tools are disabled. Finish the plan with a <proposed_plan> block.";
|
|
71
|
+
/** [W5-F] plan 下 `todo set / update` 的拒绝说明(计划与清单分开,避免跳过审批)。 */
|
|
72
|
+
export const PLAN_TODO_MESSAGE = "Plan mode is active: the todo list is created from the approved plan. Write the steps in a <proposed_plan> block instead.";
|
|
73
|
+
/**
|
|
74
|
+
* [W5-F] plan 模式第 ③ 步(输入可见,docs/wave5-plan.md §6.2):
|
|
75
|
+
* read 放行(`todo` 只放行 get);bash 按 `plan.bash`(readonly:只读子集放行其余拒绝;ask:其余询问;
|
|
76
|
+
* deny:全拒);task 放行(子会话共用同一管线,同样处在 plan);其余 write / execute 拒绝。
|
|
77
|
+
* 返回 undefined = 交给通常的模式真值表。
|
|
78
|
+
*/
|
|
79
|
+
export function planDecision(input, planBash, cwd, projectRoot = cwd) {
|
|
80
|
+
const { toolName, permission } = input;
|
|
81
|
+
if (toolName === "todo") {
|
|
82
|
+
const action = typeof input.input === "object" && input.input !== null
|
|
83
|
+
? input.input["action"]
|
|
84
|
+
: undefined;
|
|
85
|
+
return action === "get"
|
|
86
|
+
? { decision: "allow" }
|
|
87
|
+
: { decision: "deny", message: PLAN_TODO_MESSAGE };
|
|
88
|
+
}
|
|
89
|
+
if (permission === "read")
|
|
90
|
+
return { decision: "allow" };
|
|
91
|
+
if (toolName === "task")
|
|
92
|
+
return { decision: "allow" };
|
|
93
|
+
if (toolName === "bash") {
|
|
94
|
+
if (planBash === "deny")
|
|
95
|
+
return { decision: "deny", message: PLAN_MODE_MESSAGE };
|
|
96
|
+
const command = inputCommand(input.input);
|
|
97
|
+
if (command !== undefined && isReadonlyBash(command, { cwd, projectRoot }))
|
|
98
|
+
return { decision: "allow" };
|
|
99
|
+
return planBash === "ask"
|
|
100
|
+
? { decision: "ask" }
|
|
101
|
+
: { decision: "deny", message: PLAN_MODE_MESSAGE };
|
|
102
|
+
}
|
|
103
|
+
return { decision: "deny", message: PLAN_MODE_MESSAGE };
|
|
104
|
+
}
|
|
45
105
|
export const UNATTENDED_MESSAGE = "This tool call requires approval, but no one is available to approve it (unattended mode).";
|
|
106
|
+
export const ALLOWLIST_MESSAGE = 'Not in the allowlist (不在允许名单): permission mode "allowlist" only runs read-only tools and calls matched by an allow rule, and never asks.';
|
|
107
|
+
/** 审计摘要:bash 取命令,带 path 的取路径,其它取工具名;单行、截断。 */
|
|
108
|
+
function auditSummary(toolName, input) {
|
|
109
|
+
const command = inputCommand(input);
|
|
110
|
+
const raw = command ??
|
|
111
|
+
(typeof input === "object" && input !== null
|
|
112
|
+
? input["path"]
|
|
113
|
+
: undefined);
|
|
114
|
+
const text = typeof raw === "string" ? raw.replace(/\s+/g, " ").trim() : toolName;
|
|
115
|
+
return text.length > 80 ? `${text.slice(0, 79)}…` : text;
|
|
116
|
+
}
|
|
46
117
|
export function sessionGrantFor(toolName, input, cwd) {
|
|
47
118
|
const command = inputCommand(input);
|
|
48
119
|
if (command !== undefined) {
|
|
@@ -74,11 +145,20 @@ export class PermissionPipeline {
|
|
|
74
145
|
currentMode;
|
|
75
146
|
ruleList;
|
|
76
147
|
grants = [];
|
|
148
|
+
audit = [];
|
|
77
149
|
cwd;
|
|
150
|
+
projectRoot;
|
|
151
|
+
autoSafeCommands;
|
|
152
|
+
planBashMode;
|
|
153
|
+
bashSandbox;
|
|
78
154
|
constructor(options) {
|
|
155
|
+
this.bashSandbox = options.bashSandbox;
|
|
79
156
|
this.currentMode = options.mode;
|
|
157
|
+
this.planBashMode = options.planBash ?? "readonly";
|
|
80
158
|
this.ruleList = [...options.rules];
|
|
81
159
|
this.cwd = options.cwd;
|
|
160
|
+
this.projectRoot = options.projectRoot ?? options.cwd;
|
|
161
|
+
this.autoSafeCommands = [...(options.autoSafeCommands ?? [])];
|
|
82
162
|
}
|
|
83
163
|
get mode() {
|
|
84
164
|
return this.currentMode;
|
|
@@ -86,6 +166,13 @@ export class PermissionPipeline {
|
|
|
86
166
|
setMode(mode) {
|
|
87
167
|
this.currentMode = mode;
|
|
88
168
|
}
|
|
169
|
+
/** [W5-F] config `plan.bash`(plan 扩展在会话装配时设置)。 */
|
|
170
|
+
get planBash() {
|
|
171
|
+
return this.planBashMode;
|
|
172
|
+
}
|
|
173
|
+
setPlanBash(mode) {
|
|
174
|
+
this.planBashMode = mode;
|
|
175
|
+
}
|
|
89
176
|
get rules() {
|
|
90
177
|
return this.ruleList;
|
|
91
178
|
}
|
|
@@ -95,8 +182,32 @@ export class PermissionPipeline {
|
|
|
95
182
|
clearSessionGrants() {
|
|
96
183
|
this.grants.length = 0;
|
|
97
184
|
}
|
|
98
|
-
|
|
185
|
+
recordAutoDecision(toolName, input, decision) {
|
|
186
|
+
this.audit.push({
|
|
187
|
+
...decision,
|
|
188
|
+
at: Date.now(),
|
|
189
|
+
toolName,
|
|
190
|
+
summary: auditSummary(toolName, input),
|
|
191
|
+
});
|
|
192
|
+
if (this.audit.length > AUTO_AUDIT_LIMIT)
|
|
193
|
+
this.audit.splice(0, this.audit.length - AUTO_AUDIT_LIMIT);
|
|
194
|
+
}
|
|
195
|
+
autoDecisions() {
|
|
196
|
+
return [...this.audit];
|
|
197
|
+
}
|
|
198
|
+
check(request) {
|
|
199
|
+
// [W5-H2] 后台 bash 的查询(bash{job, action},不带 command)只读 / 结束本会话自己启动的任务:按 read
|
|
200
|
+
const input = isBackgroundJobQuery(request.toolName, request.input)
|
|
201
|
+
? { ...request, permission: "read" }
|
|
202
|
+
: request;
|
|
99
203
|
const verdict = this.evaluate(input);
|
|
204
|
+
if (this.currentMode === "auto" && verdict.auto === undefined) {
|
|
205
|
+
verdict.auto = {
|
|
206
|
+
layer: "rule",
|
|
207
|
+
decision: verdict.decision,
|
|
208
|
+
reason: verdict.message ?? verdict.step,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
100
211
|
if (verdict.decision === "ask" && input.unattended) {
|
|
101
212
|
return { ...verdict, decision: "deny", message: UNATTENDED_MESSAGE };
|
|
102
213
|
}
|
|
@@ -131,20 +242,35 @@ export class PermissionPipeline {
|
|
|
131
242
|
const command = toolName === "bash" ? inputCommand(input.input) : undefined;
|
|
132
243
|
const danger = command !== undefined ? matchDangerous(command) : undefined;
|
|
133
244
|
if (danger) {
|
|
134
|
-
|
|
245
|
+
const message = `Potentially dangerous command: ${danger.description}`;
|
|
246
|
+
if (this.currentMode === "allowlist") {
|
|
247
|
+
return { decision: "deny", step: "allowlist", message: `${ALLOWLIST_MESSAGE} ${message}` };
|
|
248
|
+
}
|
|
249
|
+
const verdict = {
|
|
135
250
|
decision: "ask",
|
|
136
251
|
step: "dangerous",
|
|
137
252
|
approvalReason: "dangerous",
|
|
138
|
-
message
|
|
253
|
+
message,
|
|
139
254
|
};
|
|
255
|
+
if (this.currentMode === "auto") {
|
|
256
|
+
verdict.auto = { layer: "rule", decision: "ask", reason: danger.description };
|
|
257
|
+
}
|
|
258
|
+
return verdict;
|
|
140
259
|
}
|
|
141
|
-
|
|
142
|
-
|
|
260
|
+
if (this.currentMode === "auto")
|
|
261
|
+
return this.evaluateAuto(input, layers, command);
|
|
262
|
+
if (this.currentMode === "allowlist")
|
|
263
|
+
return this.evaluateAllowlist(input, layers);
|
|
264
|
+
// ③ 模式(plan 细化见 planDecision)
|
|
265
|
+
const plan = this.currentMode === "plan"
|
|
266
|
+
? planDecision(input, this.planBashMode, this.cwd, this.projectRoot)
|
|
267
|
+
: undefined;
|
|
268
|
+
const byMode = plan?.decision ?? modeDecision(this.currentMode, permission);
|
|
143
269
|
if (byMode === "deny") {
|
|
144
270
|
return {
|
|
145
271
|
decision: "deny",
|
|
146
272
|
step: "mode",
|
|
147
|
-
message:
|
|
273
|
+
message: plan?.message ?? PLAN_MODE_MESSAGE,
|
|
148
274
|
};
|
|
149
275
|
}
|
|
150
276
|
let verdict = byMode === "allow"
|
|
@@ -166,6 +292,10 @@ export class PermissionPipeline {
|
|
|
166
292
|
else if (granted) {
|
|
167
293
|
verdict = { decision: "allow", step: "session" };
|
|
168
294
|
}
|
|
295
|
+
else if ((this.currentMode === "default" || this.currentMode === "auto-edit") &&
|
|
296
|
+
this.sandboxAllows(input, command, layers.tooDeep)) {
|
|
297
|
+
verdict = { decision: "allow", step: "mode", sandboxed: true };
|
|
298
|
+
}
|
|
169
299
|
}
|
|
170
300
|
if (input.hookDecision === "ask") {
|
|
171
301
|
const reason = "hook";
|
|
@@ -178,4 +308,169 @@ export class PermissionPipeline {
|
|
|
178
308
|
}
|
|
179
309
|
return verdict;
|
|
180
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* [S2] default / auto-edit 的「沙箱内免审批」:将在沙箱内运行、沙箱拒绝网络(联网可外带数据)、
|
|
313
|
+
* 命令文本不碰机密路径(沙箱挡不住读工作区里的 `.env`)、嵌套不超深。
|
|
314
|
+
*/
|
|
315
|
+
sandboxAllows(input, command, tooDeep) {
|
|
316
|
+
if (input.toolName !== "bash" || command === undefined || tooDeep)
|
|
317
|
+
return false;
|
|
318
|
+
if (!runsSandboxed(this.bashSandbox, input.input))
|
|
319
|
+
return false;
|
|
320
|
+
if (this.bashSandbox?.network !== "deny")
|
|
321
|
+
return false;
|
|
322
|
+
return secretReferenceReason(command, this.cwd) === undefined;
|
|
323
|
+
}
|
|
324
|
+
/** [S2] 沙箱生效时请求 `sandbox: false` 越出沙箱。 */
|
|
325
|
+
escapesSandbox(input) {
|
|
326
|
+
return (input.toolName === "bash" &&
|
|
327
|
+
this.bashSandbox?.active === true &&
|
|
328
|
+
wantsUnsandboxed(input.input));
|
|
329
|
+
}
|
|
330
|
+
/** allow 规则(每层都覆盖)/ Hook allow / 会话记忆;都不命中返回 undefined。 */
|
|
331
|
+
allowedBy(input, layers, useGrants) {
|
|
332
|
+
const { toolName } = input;
|
|
333
|
+
const allow = layers.tooDeep
|
|
334
|
+
? undefined
|
|
335
|
+
: findAllowRule(this.ruleList, toolName, input.input, this.cwd);
|
|
336
|
+
if (allow !== undefined &&
|
|
337
|
+
layers.inputs.every((l) => findAllowRule(this.ruleList, toolName, l, this.cwd))) {
|
|
338
|
+
return { decision: "allow", step: "allow-rule", rule: allow };
|
|
339
|
+
}
|
|
340
|
+
if (input.hookDecision === "allow")
|
|
341
|
+
return { decision: "allow", step: "hook" };
|
|
342
|
+
if (useGrants &&
|
|
343
|
+
!layers.tooDeep &&
|
|
344
|
+
layers.inputs.every((l) => this.grants.some((g) => grantCovers(g, toolName, l, this.cwd)))) {
|
|
345
|
+
return { decision: "allow", step: "session" };
|
|
346
|
+
}
|
|
347
|
+
return undefined;
|
|
348
|
+
}
|
|
349
|
+
/** auto 规则层(受保护路径、项目外写入、网络、删除类)要询问的原因。 */
|
|
350
|
+
autoRuleReason(input, command) {
|
|
351
|
+
if (command !== undefined) {
|
|
352
|
+
const analysis = analyzeBashForAuto(command, {
|
|
353
|
+
cwd: this.cwd,
|
|
354
|
+
projectRoot: this.projectRoot,
|
|
355
|
+
extraSafe: this.autoSafeCommands,
|
|
356
|
+
});
|
|
357
|
+
const out = {
|
|
358
|
+
bashSafe: analysis.safe,
|
|
359
|
+
bashReason: analysis.reason,
|
|
360
|
+
};
|
|
361
|
+
if (analysis.ask !== undefined)
|
|
362
|
+
out.ask = analysis.ask;
|
|
363
|
+
return out;
|
|
364
|
+
}
|
|
365
|
+
const raw = typeof input.input === "object" && input.input !== null
|
|
366
|
+
? input.input["path"]
|
|
367
|
+
: undefined;
|
|
368
|
+
if (typeof raw !== "string" && input.permission !== "write")
|
|
369
|
+
return {};
|
|
370
|
+
const path = inputPath(input.toolName, input.input, this.cwd);
|
|
371
|
+
if (path === undefined)
|
|
372
|
+
return {};
|
|
373
|
+
const secret = secretPathReason(path);
|
|
374
|
+
if (secret !== undefined)
|
|
375
|
+
return { ask: secret };
|
|
376
|
+
if (input.permission === "write") {
|
|
377
|
+
const reason = writeProtectionReason(path, this.projectRoot);
|
|
378
|
+
if (reason !== undefined)
|
|
379
|
+
return { ask: reason };
|
|
380
|
+
}
|
|
381
|
+
return {};
|
|
382
|
+
}
|
|
383
|
+
evaluateAuto(input, layers, command) {
|
|
384
|
+
const rule = (decision, reason) => ({
|
|
385
|
+
layer: "rule",
|
|
386
|
+
decision,
|
|
387
|
+
reason,
|
|
388
|
+
});
|
|
389
|
+
const checked = this.autoRuleReason(input, command);
|
|
390
|
+
if (checked.ask !== undefined) {
|
|
391
|
+
return {
|
|
392
|
+
decision: "ask",
|
|
393
|
+
step: "auto-rule",
|
|
394
|
+
approvalReason: "mode",
|
|
395
|
+
message: `Auto mode asks before this: ${checked.ask}`,
|
|
396
|
+
auto: rule("ask", checked.ask),
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
if (input.hookDecision === "ask") {
|
|
400
|
+
const verdict = {
|
|
401
|
+
decision: "ask",
|
|
402
|
+
step: "hook",
|
|
403
|
+
approvalReason: "hook",
|
|
404
|
+
auto: rule("ask", input.hookReason ?? "PreToolUse hook asked"),
|
|
405
|
+
};
|
|
406
|
+
if (input.hookReason !== undefined)
|
|
407
|
+
verdict.message = input.hookReason;
|
|
408
|
+
return verdict;
|
|
409
|
+
}
|
|
410
|
+
const allowed = this.allowedBy(input, layers, true);
|
|
411
|
+
if (allowed !== undefined) {
|
|
412
|
+
const reason = allowed.step === "allow-rule"
|
|
413
|
+
? `allow rule ${allowed.rule?.raw ?? ""}`.trim()
|
|
414
|
+
: allowed.step === "hook"
|
|
415
|
+
? "PreToolUse hook allowed"
|
|
416
|
+
: "allowed for this session";
|
|
417
|
+
return { ...allowed, auto: rule("allow", reason) };
|
|
418
|
+
}
|
|
419
|
+
if (this.escapesSandbox(input)) {
|
|
420
|
+
const why = "runs outside the OS sandbox (sandbox:false)";
|
|
421
|
+
return {
|
|
422
|
+
decision: "ask",
|
|
423
|
+
step: "auto-rule",
|
|
424
|
+
approvalReason: "mode",
|
|
425
|
+
message: `Auto mode asks before this: ${why}`,
|
|
426
|
+
auto: rule("ask", why),
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
const fixed = (reason) => ({
|
|
430
|
+
decision: "allow",
|
|
431
|
+
step: "auto-static",
|
|
432
|
+
auto: { layer: "static", decision: "allow", reason },
|
|
433
|
+
});
|
|
434
|
+
if (input.permission === "read")
|
|
435
|
+
return fixed("read-only tool");
|
|
436
|
+
if (command !== undefined && checked.bashSafe === true) {
|
|
437
|
+
return fixed(checked.bashReason ?? "safe command");
|
|
438
|
+
}
|
|
439
|
+
if (command === undefined && input.permission === "write") {
|
|
440
|
+
const path = inputPath(input.toolName, input.input, this.cwd);
|
|
441
|
+
if (path !== undefined)
|
|
442
|
+
return fixed("write inside the project");
|
|
443
|
+
}
|
|
444
|
+
return {
|
|
445
|
+
decision: "ask",
|
|
446
|
+
step: "auto-classify",
|
|
447
|
+
approvalReason: "mode",
|
|
448
|
+
classify: true,
|
|
449
|
+
...(command !== undefined && runsSandboxed(this.bashSandbox, input.input)
|
|
450
|
+
? { sandboxed: true }
|
|
451
|
+
: {}),
|
|
452
|
+
auto: {
|
|
453
|
+
layer: "classifier",
|
|
454
|
+
decision: "ask",
|
|
455
|
+
reason: checked.bashReason ?? `${input.toolName} needs a judgement`,
|
|
456
|
+
},
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
evaluateAllowlist(input, layers) {
|
|
460
|
+
const deny = (why) => ({
|
|
461
|
+
decision: "deny",
|
|
462
|
+
step: "allowlist",
|
|
463
|
+
message: why === undefined ? ALLOWLIST_MESSAGE : `${ALLOWLIST_MESSAGE} ${why}`,
|
|
464
|
+
});
|
|
465
|
+
if (input.hookDecision === "ask")
|
|
466
|
+
return deny(input.hookReason);
|
|
467
|
+
// task 与 plan 同样放行:子会话共用本管线,同样只放行只读与名单内调用
|
|
468
|
+
if (input.permission === "read" || input.toolName === "task")
|
|
469
|
+
return { decision: "allow", step: "mode" };
|
|
470
|
+
const command = input.toolName === "bash" ? inputCommand(input.input) : undefined;
|
|
471
|
+
if (command !== undefined &&
|
|
472
|
+
isReadonlyBash(command, { cwd: this.cwd, projectRoot: this.projectRoot }))
|
|
473
|
+
return { decision: "allow", step: "mode" };
|
|
474
|
+
return this.allowedBy(input, layers, false) ?? deny();
|
|
475
|
+
}
|
|
181
476
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auto 模式规则层的受保护路径(§7.4,docs/permissions.md「规则层」)。
|
|
3
|
+
*
|
|
4
|
+
* - **机密路径**(读写都询问):`.env` / `.env.*`(`.example` / `.sample` / `.template` / `.dist` 除外)、
|
|
5
|
+
* `*.env`、`.ssh/`、`.gnupg/`、`.aws/`、`.kube/config`、`.docker/config.json`、`.config/gcloud/`、
|
|
6
|
+
* `.netrc`、`.pgpass`、`.git-credentials`、私钥(`id_rsa` 等、`*.pem`、`*.key`、`*.p12`、`*.pfx`、
|
|
7
|
+
* `*.jks`、`*.keystore`、`*.ppk`)、ama 自己的 `ama/auth.json`。
|
|
8
|
+
* - **受保护写入**(只对写询问):机密路径,`.git` 目录内部,项目里的 `.ama/`(Hook 与配置可能借此放宽),
|
|
9
|
+
* 项目目录外的任何路径(`/dev/null`、`/dev/stdout`、`/dev/stderr`、`/dev/tty` 除外)。
|
|
10
|
+
*
|
|
11
|
+
* 判断按词法路径(不跟随符号链接,与规则匹配一致)。返回英文短说明(进审计与 tool_result),
|
|
12
|
+
* 不受保护返回 undefined。
|
|
13
|
+
*/
|
|
14
|
+
/** 机密文件或目录:读与写都要询问。 */
|
|
15
|
+
export declare function secretPathReason(absPath: string): string | undefined;
|
|
16
|
+
/** 写入受保护的原因:机密、`.git` 内部、项目 `.ama/`、项目外;可以写返回 undefined。 */
|
|
17
|
+
export declare function writeProtectionReason(absPath: string, projectRoot: string): string | undefined;
|
|
18
|
+
/** 是否在项目目录内(含项目根本身)。 */
|
|
19
|
+
export declare function insideProject(absPath: string, projectRoot: string): boolean;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* auto 模式规则层的受保护路径(§7.4,docs/permissions.md「规则层」)。
|
|
3
|
+
*
|
|
4
|
+
* - **机密路径**(读写都询问):`.env` / `.env.*`(`.example` / `.sample` / `.template` / `.dist` 除外)、
|
|
5
|
+
* `*.env`、`.ssh/`、`.gnupg/`、`.aws/`、`.kube/config`、`.docker/config.json`、`.config/gcloud/`、
|
|
6
|
+
* `.netrc`、`.pgpass`、`.git-credentials`、私钥(`id_rsa` 等、`*.pem`、`*.key`、`*.p12`、`*.pfx`、
|
|
7
|
+
* `*.jks`、`*.keystore`、`*.ppk`)、ama 自己的 `ama/auth.json`。
|
|
8
|
+
* - **受保护写入**(只对写询问):机密路径,`.git` 目录内部,项目里的 `.ama/`(Hook 与配置可能借此放宽),
|
|
9
|
+
* 项目目录外的任何路径(`/dev/null`、`/dev/stdout`、`/dev/stderr`、`/dev/tty` 除外)。
|
|
10
|
+
*
|
|
11
|
+
* 判断按词法路径(不跟随符号链接,与规则匹配一致)。返回英文短说明(进审计与 tool_result),
|
|
12
|
+
* 不受保护返回 undefined。
|
|
13
|
+
*/
|
|
14
|
+
import { basename, relative } from "node:path";
|
|
15
|
+
import { isWithin, toPosix } from "../tools/paths.js";
|
|
16
|
+
const ENV_EXEMPT = /\.(example|sample|template|dist)$/i;
|
|
17
|
+
const KEY_FILE = /^id_(rsa|dsa|ecdsa|ed25519)(_sk)?$/;
|
|
18
|
+
const KEY_EXT = /\.(pem|key|p12|pfx|jks|keystore|ppk)$/i;
|
|
19
|
+
const SECRET_DIRS = new Set([".ssh", ".gnupg", ".aws"]);
|
|
20
|
+
const SECRET_FILES = new Set([".netrc", ".pgpass", ".git-credentials"]);
|
|
21
|
+
const DEVICE_SINKS = new Set(["/dev/null", "/dev/stdout", "/dev/stderr", "/dev/tty"]);
|
|
22
|
+
function segments(absPath) {
|
|
23
|
+
return toPosix(absPath).split("/").filter(Boolean);
|
|
24
|
+
}
|
|
25
|
+
/** 机密文件或目录:读与写都要询问。 */
|
|
26
|
+
export function secretPathReason(absPath) {
|
|
27
|
+
const parts = segments(absPath);
|
|
28
|
+
const name = basename(absPath);
|
|
29
|
+
if ((/^\.env(\..+)?$/.test(name) || /\.env$/.test(name)) && !ENV_EXEMPT.test(name)) {
|
|
30
|
+
return `secret file ${name}`;
|
|
31
|
+
}
|
|
32
|
+
if (KEY_FILE.test(name) || KEY_EXT.test(name))
|
|
33
|
+
return `private key or certificate ${name}`;
|
|
34
|
+
if (SECRET_FILES.has(name))
|
|
35
|
+
return `credentials file ${name}`;
|
|
36
|
+
const dir = parts.slice(0, -1);
|
|
37
|
+
const secretDir = parts.find((p) => SECRET_DIRS.has(p));
|
|
38
|
+
if (secretDir !== undefined)
|
|
39
|
+
return `credentials directory ${secretDir}/`;
|
|
40
|
+
for (let i = 0; i < dir.length; i++) {
|
|
41
|
+
const a = dir[i];
|
|
42
|
+
const b = parts[i + 1];
|
|
43
|
+
if (a === ".kube" && b === "config")
|
|
44
|
+
return "kubeconfig";
|
|
45
|
+
if (a === ".docker" && b === "config.json")
|
|
46
|
+
return "docker credentials";
|
|
47
|
+
if (a === ".config" && b === "gcloud")
|
|
48
|
+
return "gcloud credentials";
|
|
49
|
+
if ((a === "ama" || a === ".ama") && b === "auth.json" && i + 2 === parts.length) {
|
|
50
|
+
return "ama API key file";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
/** 写入受保护的原因:机密、`.git` 内部、项目 `.ama/`、项目外;可以写返回 undefined。 */
|
|
56
|
+
export function writeProtectionReason(absPath, projectRoot) {
|
|
57
|
+
if (DEVICE_SINKS.has(toPosix(absPath)))
|
|
58
|
+
return undefined;
|
|
59
|
+
const secret = secretPathReason(absPath);
|
|
60
|
+
if (secret !== undefined)
|
|
61
|
+
return secret;
|
|
62
|
+
if (segments(absPath).includes(".git"))
|
|
63
|
+
return "writes inside .git";
|
|
64
|
+
if (!isWithin(projectRoot, absPath))
|
|
65
|
+
return "writes outside the project directory";
|
|
66
|
+
const rel = toPosix(relative(projectRoot, absPath));
|
|
67
|
+
if (rel === ".ama" || rel.startsWith(".ama/"))
|
|
68
|
+
return "writes to the project .ama/ config";
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
/** 是否在项目目录内(含项目根本身)。 */
|
|
72
|
+
export function insideProject(absPath, projectRoot) {
|
|
73
|
+
return isWithin(projectRoot, absPath);
|
|
74
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plan 模式与 allowlist 放行的只读 bash 子集 `READONLY_BASH`(docs/wave5-plan.md §6.2、D21)。[W5-F]
|
|
3
|
+
*
|
|
4
|
+
* 判定 = auto 的静态判定(`analyzeBashForAuto`:分词、嵌套展开、规则层的网络 / 删除 / 写入目标 /
|
|
5
|
+
* 机密路径、命令替换、变量展开、点文件通配)**全部通过**,再加四条更窄的限制:
|
|
6
|
+
* - 每段命令都命中 {@link READONLY_BASH}(比 auto 安全名单窄:去掉测试 / 构建运行器、`printenv`
|
|
7
|
+
* 这类会把环境变量里的密钥打进上下文的命令);
|
|
8
|
+
* - 没有输出重定向(`/dev/null` 等设备除外);
|
|
9
|
+
* - 没有嵌套 shell(`sh -c`、`eval`、`xargs`、`find -exec`)与进程替换;
|
|
10
|
+
* - 段首没有环境赋值或包装命令(`GIT_EXTERNAL_DIFF=… git diff`、`env …`、`nohup …`)。
|
|
11
|
+
*
|
|
12
|
+
* 机密路径(`cat .env`)由 auto 规则层判为「要询问」,这里因此不算只读;deny 规则仍先于模式判定。
|
|
13
|
+
*/
|
|
14
|
+
export interface ReadonlySpec {
|
|
15
|
+
/** 词前缀(`["git", "log"]`);git 的全局选项只允许 `-C <dir>` 与 `--no-pager`。 */
|
|
16
|
+
words: readonly string[];
|
|
17
|
+
/** 前缀之后任一参数命中即不算只读。 */
|
|
18
|
+
forbid?: RegExp;
|
|
19
|
+
}
|
|
20
|
+
/** 只读命令名单(每条在 readonly-bash.test.ts 有正反例)。 */
|
|
21
|
+
export declare const READONLY_BASH: readonly ReadonlySpec[];
|
|
22
|
+
export interface ReadonlyBashOptions {
|
|
23
|
+
/** 相对路径(机密路径、写入目标)的解析基准;缺省 `process.cwd()`。 */
|
|
24
|
+
cwd?: string;
|
|
25
|
+
projectRoot?: string;
|
|
26
|
+
}
|
|
27
|
+
/** 不是只读的原因;只读返回 undefined。 */
|
|
28
|
+
export declare function readonlyBashReason(command: string, options?: ReadonlyBashOptions): string | undefined;
|
|
29
|
+
/** 每段命令都在 {@link READONLY_BASH} 且无重定向 / 命令替换 / 嵌套 shell。 */
|
|
30
|
+
export declare function isReadonlyBash(command: string, options?: ReadonlyBashOptions): boolean;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plan 模式与 allowlist 放行的只读 bash 子集 `READONLY_BASH`(docs/wave5-plan.md §6.2、D21)。[W5-F]
|
|
3
|
+
*
|
|
4
|
+
* 判定 = auto 的静态判定(`analyzeBashForAuto`:分词、嵌套展开、规则层的网络 / 删除 / 写入目标 /
|
|
5
|
+
* 机密路径、命令替换、变量展开、点文件通配)**全部通过**,再加四条更窄的限制:
|
|
6
|
+
* - 每段命令都命中 {@link READONLY_BASH}(比 auto 安全名单窄:去掉测试 / 构建运行器、`printenv`
|
|
7
|
+
* 这类会把环境变量里的密钥打进上下文的命令);
|
|
8
|
+
* - 没有输出重定向(`/dev/null` 等设备除外);
|
|
9
|
+
* - 没有嵌套 shell(`sh -c`、`eval`、`xargs`、`find -exec`)与进程替换;
|
|
10
|
+
* - 段首没有环境赋值或包装命令(`GIT_EXTERNAL_DIFF=… git diff`、`env …`、`nohup …`)。
|
|
11
|
+
*
|
|
12
|
+
* 机密路径(`cat .env`)由 auto 规则层判为「要询问」,这里因此不算只读;deny 规则仍先于模式判定。
|
|
13
|
+
*/
|
|
14
|
+
import { analyzeBashForAuto, extractRedirects } from "./auto-safe.js";
|
|
15
|
+
import { collectNestedCommands, commandWords, gitArgv, shellWords } from "./dangerous.js";
|
|
16
|
+
import { splitShellSegments } from "./rules.js";
|
|
17
|
+
const spec = (text, forbid) => forbid === undefined ? { words: text.split(" ") } : { words: text.split(" "), forbid };
|
|
18
|
+
const GIT_OUTPUT = /^(--output(=.*)?|--ext-diff|--textconv|-O.*|--open-files-in-pager.*)$/;
|
|
19
|
+
/** 只读命令名单(每条在 readonly-bash.test.ts 有正反例)。 */
|
|
20
|
+
export const READONLY_BASH = [
|
|
21
|
+
...[
|
|
22
|
+
"ls",
|
|
23
|
+
"cat",
|
|
24
|
+
"head",
|
|
25
|
+
"tail",
|
|
26
|
+
"wc",
|
|
27
|
+
"stat",
|
|
28
|
+
"echo",
|
|
29
|
+
"printf",
|
|
30
|
+
"pwd",
|
|
31
|
+
"which",
|
|
32
|
+
"du",
|
|
33
|
+
"tree",
|
|
34
|
+
"basename",
|
|
35
|
+
"dirname",
|
|
36
|
+
"realpath",
|
|
37
|
+
"true",
|
|
38
|
+
"false",
|
|
39
|
+
"cd",
|
|
40
|
+
].map((name) => spec(name, name === "tree" ? /^-o.*$/ : undefined)),
|
|
41
|
+
spec("file", /^(-C|--compile)$/),
|
|
42
|
+
...["grep", "egrep", "fgrep"].map((name) => spec(name)),
|
|
43
|
+
spec("rg", /^--pre(=.*)?$/),
|
|
44
|
+
spec("fd", /^(-x|-X|--exec|--exec-batch)(=.*)?$/),
|
|
45
|
+
spec("find", /^-(exec|execdir|ok|okdir|delete|fprint0?|fprintf|fls)$/),
|
|
46
|
+
spec("jq", /^(-i|--in-place)$/),
|
|
47
|
+
...["status", "rev-parse", "blame", "ls-files"].map((sub) => spec(`git ${sub}`, GIT_OUTPUT)),
|
|
48
|
+
...["log", "show", "diff"].map((sub) => spec(`git ${sub}`, GIT_OUTPUT)),
|
|
49
|
+
spec("git branch", /^(-[A-Za-z]*[dDmMcCfuet][A-Za-z]*|--(delete|move|copy|force|set-upstream-to.*|unset-upstream|edit-description|track.*|no-track|create-reflog))$/),
|
|
50
|
+
];
|
|
51
|
+
/** auto 安全名单里没有、但属于只读子集的命令(交给 analyzeBashForAuto 当作追加项)。 */
|
|
52
|
+
const EXTRA_FOR_AUTO = ["tree", "jq", "fd"];
|
|
53
|
+
const DEVICE_TARGETS = /^(\/dev\/(null|stdout|stderr|tty)|NUL)$/i;
|
|
54
|
+
function base(word) {
|
|
55
|
+
return (word ?? "").split("/").pop() ?? "";
|
|
56
|
+
}
|
|
57
|
+
/** git 子命令之前只允许 `-C <dir>` 与 `--no-pager`(`-c` 可改 pager / diff 程序)。 */
|
|
58
|
+
function gitPrefixSafe(argv) {
|
|
59
|
+
for (let i = 1; i < argv.length; i++) {
|
|
60
|
+
const a = argv[i];
|
|
61
|
+
if (a === "-C")
|
|
62
|
+
i++;
|
|
63
|
+
else if (a === "--no-pager")
|
|
64
|
+
continue;
|
|
65
|
+
else
|
|
66
|
+
return !a.startsWith("-");
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
function matches(argv, entry) {
|
|
71
|
+
let words = argv;
|
|
72
|
+
if (entry.words[0] === "git") {
|
|
73
|
+
if (base(argv[0]) !== "git" || !gitPrefixSafe(argv))
|
|
74
|
+
return false;
|
|
75
|
+
words = ["git", ...(gitArgv(argv) ?? argv).slice(1)];
|
|
76
|
+
}
|
|
77
|
+
else if (argv[0] !== entry.words[0])
|
|
78
|
+
return false;
|
|
79
|
+
if (words.length < entry.words.length)
|
|
80
|
+
return false;
|
|
81
|
+
if (!entry.words.every((w, i) => words[i] === w))
|
|
82
|
+
return false;
|
|
83
|
+
const rest = words.slice(entry.words.length);
|
|
84
|
+
return entry.forbid === undefined || !rest.some((a) => entry.forbid.test(a));
|
|
85
|
+
}
|
|
86
|
+
/** 不是只读的原因;只读返回 undefined。 */
|
|
87
|
+
export function readonlyBashReason(command, options = {}) {
|
|
88
|
+
const cwd = options.cwd ?? process.cwd();
|
|
89
|
+
const nested = collectNestedCommands(command);
|
|
90
|
+
if (nested.commands.length > 0 || nested.tooDeep)
|
|
91
|
+
return "nested shell command";
|
|
92
|
+
const auto = analyzeBashForAuto(command, {
|
|
93
|
+
cwd,
|
|
94
|
+
projectRoot: options.projectRoot ?? cwd,
|
|
95
|
+
extraSafe: EXTRA_FOR_AUTO,
|
|
96
|
+
});
|
|
97
|
+
if (!auto.safe)
|
|
98
|
+
return auto.ask ?? auto.reason;
|
|
99
|
+
for (const segment of splitShellSegments(command)) {
|
|
100
|
+
const redirects = extractRedirects(segment);
|
|
101
|
+
const target = redirects.outputs.find((t) => !DEVICE_TARGETS.test(t));
|
|
102
|
+
if (target !== undefined)
|
|
103
|
+
return `output redirection (${target})`;
|
|
104
|
+
const raw = shellWords(redirects.rest.replace(/^[({\s]+/, ""));
|
|
105
|
+
const argv = commandWords(redirects.rest, true);
|
|
106
|
+
if (argv.length === 0)
|
|
107
|
+
return "empty command";
|
|
108
|
+
if (raw[0] !== argv[0])
|
|
109
|
+
return `environment assignment or wrapper before ${argv[0]}`;
|
|
110
|
+
if (!READONLY_BASH.some((entry) => matches(argv, entry)))
|
|
111
|
+
return `${base(argv[0])} is not in the read-only list`;
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
/** 每段命令都在 {@link READONLY_BASH} 且无重定向 / 命令替换 / 嵌套 shell。 */
|
|
116
|
+
export function isReadonlyBash(command, options = {}) {
|
|
117
|
+
return readonlyBashReason(command, options) === undefined;
|
|
118
|
+
}
|
|
@@ -229,6 +229,9 @@ export function resolvePermissionLayers(layers, options = {}) {
|
|
|
229
229
|
if (!PERMISSION_MODES_STRICT_FIRST.includes(layer.mode)) {
|
|
230
230
|
warnings.push(`${layer.source}: unknown permission mode "${String(layer.mode)}"`);
|
|
231
231
|
}
|
|
232
|
+
else if (restricted && (layer.mode === "auto" || layer.mode === "full-auto")) {
|
|
233
|
+
warnings.push(`project config cannot set permission mode "${layer.mode}"; ignored`);
|
|
234
|
+
}
|
|
232
235
|
else if (restricted && !isAtLeastAsStrict(layer.mode, mode)) {
|
|
233
236
|
warnings.push(`project config cannot relax permission mode from "${mode}" to "${layer.mode}"; ignored`);
|
|
234
237
|
}
|