@armadra/agent 0.0.0-stage → 0.3.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 +37 -0
- package/LICENSE +21 -0
- package/README.md +470 -2
- package/dist/agent/agent.d.ts +56 -0
- package/dist/agent/agent.js +129 -0
- package/dist/agent/loop.d.ts +51 -0
- package/dist/agent/loop.js +158 -0
- package/dist/agent/queue.d.ts +25 -0
- package/dist/agent/queue.js +55 -0
- package/dist/agent/retry.d.ts +22 -0
- package/dist/agent/retry.js +92 -0
- package/dist/agent/schema.d.ts +27 -0
- package/dist/agent/schema.js +186 -0
- package/dist/agent/session-cache.d.ts +100 -0
- package/dist/agent/session-cache.js +514 -0
- package/dist/agent/session-compaction.d.ts +49 -0
- package/dist/agent/session-compaction.js +251 -0
- package/dist/agent/session-core.d.ts +103 -0
- package/dist/agent/session-core.js +7 -0
- package/dist/agent/session-run.d.ts +55 -0
- package/dist/agent/session-run.js +246 -0
- package/dist/agent/session-state.d.ts +39 -0
- package/dist/agent/session-state.js +119 -0
- package/dist/agent/session-subagent.d.ts +54 -0
- package/dist/agent/session-subagent.js +157 -0
- package/dist/agent/session-sync.d.ts +20 -0
- package/dist/agent/session-sync.js +111 -0
- package/dist/agent/session-tools.d.ts +24 -0
- package/dist/agent/session-tools.js +277 -0
- package/dist/agent/session.d.ts +117 -0
- package/dist/agent/session.js +485 -0
- package/dist/agent/system-prompt.d.ts +49 -0
- package/dist/agent/system-prompt.js +121 -0
- package/dist/agent/tool-runner.d.ts +64 -0
- package/dist/agent/tool-runner.js +309 -0
- package/dist/agent/transform.d.ts +26 -0
- package/dist/agent/transform.js +171 -0
- package/dist/agent/types.d.ts +378 -0
- package/dist/agent/types.js +21 -0
- package/dist/ai/apis/anthropic-messages.d.ts +26 -0
- package/dist/ai/apis/anthropic-messages.js +214 -0
- package/dist/ai/apis/anthropic-request.d.ts +42 -0
- package/dist/ai/apis/anthropic-request.js +282 -0
- package/dist/ai/apis/api.d.ts +28 -0
- package/dist/ai/apis/api.js +102 -0
- package/dist/ai/apis/cache-params.d.ts +48 -0
- package/dist/ai/apis/cache-params.js +163 -0
- package/dist/ai/apis/google-generative-ai.d.ts +32 -0
- package/dist/ai/apis/google-generative-ai.js +249 -0
- package/dist/ai/apis/google-request.d.ts +40 -0
- package/dist/ai/apis/google-request.js +255 -0
- package/dist/ai/apis/openai-compat.d.ts +35 -0
- package/dist/ai/apis/openai-compat.js +142 -0
- package/dist/ai/apis/openai-completions.d.ts +28 -0
- package/dist/ai/apis/openai-completions.js +246 -0
- package/dist/ai/apis/openai-request.d.ts +35 -0
- package/dist/ai/apis/openai-request.js +303 -0
- package/dist/ai/apis/openai-responses-request.d.ts +52 -0
- package/dist/ai/apis/openai-responses-request.js +266 -0
- package/dist/ai/apis/openai-responses.d.ts +28 -0
- package/dist/ai/apis/openai-responses.js +304 -0
- package/dist/ai/apis/shared.d.ts +56 -0
- package/dist/ai/apis/shared.js +179 -0
- package/dist/ai/cache/economics.d.ts +24 -0
- package/dist/ai/cache/economics.js +69 -0
- package/dist/ai/cache/fingerprint.d.ts +17 -0
- package/dist/ai/cache/fingerprint.js +39 -0
- package/dist/ai/cache/miss.d.ts +49 -0
- package/dist/ai/cache/miss.js +96 -0
- package/dist/ai/cache/reporting.d.ts +39 -0
- package/dist/ai/cache/reporting.js +103 -0
- package/dist/ai/cache/types.d.ts +94 -0
- package/dist/ai/cache/types.js +19 -0
- package/dist/ai/cache/warmer.d.ts +94 -0
- package/dist/ai/cache/warmer.js +224 -0
- package/dist/ai/context.d.ts +35 -0
- package/dist/ai/context.js +114 -0
- package/dist/ai/cost.d.ts +21 -0
- package/dist/ai/cost.js +60 -0
- package/dist/ai/event-stream.d.ts +33 -0
- package/dist/ai/event-stream.js +101 -0
- package/dist/ai/fake/fake-provider.d.ts +68 -0
- package/dist/ai/fake/fake-provider.js +278 -0
- package/dist/ai/fake/fake-script.d.ts +69 -0
- package/dist/ai/fake/fake-script.js +142 -0
- package/dist/ai/http.d.ts +54 -0
- package/dist/ai/http.js +212 -0
- package/dist/ai/json-partial.d.ts +17 -0
- package/dist/ai/json-partial.js +243 -0
- package/dist/ai/overflow.d.ts +22 -0
- package/dist/ai/overflow.js +61 -0
- package/dist/ai/providers/auth.d.ts +71 -0
- package/dist/ai/providers/auth.js +202 -0
- package/dist/ai/providers/builtin.d.ts +23 -0
- package/dist/ai/providers/builtin.js +148 -0
- package/dist/ai/providers/catalog-data.d.ts +5 -0
- package/dist/ai/providers/catalog-data.js +19 -0
- package/dist/ai/providers/catalog.d.ts +28 -0
- package/dist/ai/providers/catalog.js +166 -0
- package/dist/ai/providers/channels.d.ts +51 -0
- package/dist/ai/providers/channels.js +96 -0
- package/dist/ai/providers/enrich.d.ts +34 -0
- package/dist/ai/providers/enrich.js +86 -0
- package/dist/ai/providers/models-dev-cache.d.ts +53 -0
- package/dist/ai/providers/models-dev-cache.js +147 -0
- package/dist/ai/providers/models-dev.d.ts +99 -0
- package/dist/ai/providers/models-dev.js +315 -0
- package/dist/ai/providers/registry.d.ts +114 -0
- package/dist/ai/providers/registry.js +458 -0
- package/dist/ai/sse.d.ts +32 -0
- package/dist/ai/sse.js +140 -0
- package/dist/ai/thinking.d.ts +42 -0
- package/dist/ai/thinking.js +101 -0
- package/dist/ai/types.d.ts +448 -0
- package/dist/ai/types.js +19 -0
- package/dist/bundle/ama-sandbox.cjs +324 -0
- package/dist/bundle/ama.cjs +27837 -0
- package/dist/bundle.d.ts +9 -0
- package/dist/bundle.js +15 -0
- package/dist/cli/args.d.ts +85 -0
- package/dist/cli/args.js +408 -0
- package/dist/cli/bootstrap.d.ts +18 -0
- package/dist/cli/bootstrap.js +399 -0
- package/dist/cli/compose-providers.d.ts +37 -0
- package/dist/cli/compose-providers.js +107 -0
- package/dist/cli/compose-session.d.ts +72 -0
- package/dist/cli/compose-session.js +370 -0
- package/dist/cli/compose-store.d.ts +24 -0
- package/dist/cli/compose-store.js +125 -0
- package/dist/cli/compose.d.ts +108 -0
- package/dist/cli/compose.js +221 -0
- package/dist/cli/default-model.d.ts +16 -0
- package/dist/cli/default-model.js +24 -0
- package/dist/cli/deps.d.ts +207 -0
- package/dist/cli/deps.js +12 -0
- package/dist/cli/exit-codes.d.ts +29 -0
- package/dist/cli/exit-codes.js +40 -0
- package/dist/cli/main.d.ts +27 -0
- package/dist/cli/main.js +177 -0
- package/dist/cli/runtime.d.ts +90 -0
- package/dist/cli/runtime.js +8 -0
- package/dist/cli/startup-screen.d.ts +15 -0
- package/dist/cli/startup-screen.js +56 -0
- package/dist/cli/startup-steps.d.ts +35 -0
- package/dist/cli/startup-steps.js +202 -0
- package/dist/cli/subcommands/auth.d.ts +12 -0
- package/dist/cli/subcommands/auth.js +91 -0
- package/dist/cli/subcommands/config.d.ts +53 -0
- package/dist/cli/subcommands/config.js +277 -0
- package/dist/cli/subcommands/context.d.ts +21 -0
- package/dist/cli/subcommands/context.js +42 -0
- package/dist/cli/subcommands/doctor.d.ts +11 -0
- package/dist/cli/subcommands/doctor.js +246 -0
- package/dist/cli/subcommands/init.d.ts +6 -0
- package/dist/cli/subcommands/init.js +22 -0
- package/dist/cli/subcommands/model-meta.d.ts +16 -0
- package/dist/cli/subcommands/model-meta.js +54 -0
- package/dist/cli/subcommands/models-cache-probe.d.ts +34 -0
- package/dist/cli/subcommands/models-cache-probe.js +190 -0
- package/dist/cli/subcommands/models-discover.d.ts +46 -0
- package/dist/cli/subcommands/models-discover.js +232 -0
- package/dist/cli/subcommands/models.d.ts +40 -0
- package/dist/cli/subcommands/models.js +152 -0
- package/dist/cli/subcommands/providers-list.d.ts +8 -0
- package/dist/cli/subcommands/providers-list.js +97 -0
- package/dist/cli/subcommands/providers-plan.d.ts +79 -0
- package/dist/cli/subcommands/providers-plan.js +215 -0
- package/dist/cli/subcommands/providers.d.ts +27 -0
- package/dist/cli/subcommands/providers.js +433 -0
- package/dist/cli/subcommands/sessions.d.ts +11 -0
- package/dist/cli/subcommands/sessions.js +100 -0
- package/dist/codemode/capability.d.ts +37 -0
- package/dist/codemode/capability.js +55 -0
- package/dist/codemode/declarations.d.ts +44 -0
- package/dist/codemode/declarations.js +111 -0
- package/dist/codemode/host-side.d.ts +59 -0
- package/dist/codemode/host-side.js +253 -0
- package/dist/codemode/modes.d.ts +23 -0
- package/dist/codemode/modes.js +40 -0
- package/dist/codemode/protocol.d.ts +91 -0
- package/dist/codemode/protocol.js +114 -0
- package/dist/codemode/sandbox-entry.d.ts +25 -0
- package/dist/codemode/sandbox-entry.js +323 -0
- package/dist/codemode/store.d.ts +26 -0
- package/dist/codemode/store.js +53 -0
- package/dist/codemode/tool.d.ts +105 -0
- package/dist/codemode/tool.js +283 -0
- package/dist/compaction/branch-summary.d.ts +30 -0
- package/dist/compaction/branch-summary.js +79 -0
- package/dist/compaction/breaker.d.ts +32 -0
- package/dist/compaction/breaker.js +65 -0
- package/dist/compaction/cut-point.d.ts +23 -0
- package/dist/compaction/cut-point.js +70 -0
- package/dist/compaction/estimate.d.ts +30 -0
- package/dist/compaction/estimate.js +114 -0
- package/dist/compaction/prune-tier.d.ts +31 -0
- package/dist/compaction/prune-tier.js +83 -0
- package/dist/compaction/serialize.d.ts +20 -0
- package/dist/compaction/serialize.js +115 -0
- package/dist/compaction/summarize-tier.d.ts +111 -0
- package/dist/compaction/summarize-tier.js +305 -0
- package/dist/config/auth-file.d.ts +39 -0
- package/dist/config/auth-file.js +92 -0
- package/dist/config/context-files.d.ts +30 -0
- package/dist/config/context-files.js +77 -0
- package/dist/config/init.d.ts +30 -0
- package/dist/config/init.js +92 -0
- package/dist/config/json-schema.d.ts +14 -0
- package/dist/config/json-schema.js +182 -0
- package/dist/config/load.d.ts +45 -0
- package/dist/config/load.js +97 -0
- package/dist/config/merge.d.ts +73 -0
- package/dist/config/merge.js +248 -0
- package/dist/config/paths.d.ts +38 -0
- package/dist/config/paths.js +108 -0
- package/dist/config/profile.d.ts +27 -0
- package/dist/config/profile.js +65 -0
- package/dist/config/schema.d.ts +34 -0
- package/dist/config/schema.js +476 -0
- package/dist/config/trust.d.ts +42 -0
- package/dist/config/trust.js +122 -0
- package/dist/config/types.d.ts +190 -0
- package/dist/config/types.js +29 -0
- package/dist/config/write.d.ts +10 -0
- package/dist/config/write.js +25 -0
- package/dist/errors.d.ts +28 -0
- package/dist/errors.js +30 -0
- package/dist/hooks/config.d.ts +49 -0
- package/dist/hooks/config.js +102 -0
- package/dist/hooks/dispatcher.d.ts +41 -0
- package/dist/hooks/dispatcher.js +104 -0
- package/dist/hooks/matcher.d.ts +22 -0
- package/dist/hooks/matcher.js +108 -0
- package/dist/hooks/protocol.d.ts +33 -0
- package/dist/hooks/protocol.js +240 -0
- package/dist/hooks/runner.d.ts +30 -0
- package/dist/hooks/runner.js +143 -0
- package/dist/hooks/types.d.ts +147 -0
- package/dist/hooks/types.js +20 -0
- package/dist/host/api-impl.d.ts +75 -0
- package/dist/host/api-impl.js +173 -0
- package/dist/host/loader.d.ts +31 -0
- package/dist/host/loader.js +147 -0
- package/dist/host/types.d.ts +169 -0
- package/dist/host/types.js +14 -0
- package/dist/host.d.ts +5 -0
- package/dist/host.js +4 -0
- package/dist/index.d.ts +41 -0
- package/dist/index.js +28 -0
- package/dist/modes/commands-core.d.ts +44 -0
- package/dist/modes/commands-core.js +171 -0
- package/dist/modes/image-input.d.ts +27 -0
- package/dist/modes/image-input.js +78 -0
- package/dist/modes/interactive/approval-dialog.d.ts +48 -0
- package/dist/modes/interactive/approval-dialog.js +202 -0
- package/dist/modes/interactive/commands.d.ts +41 -0
- package/dist/modes/interactive/commands.js +201 -0
- package/dist/modes/interactive/completion.d.ts +53 -0
- package/dist/modes/interactive/completion.js +157 -0
- package/dist/modes/interactive/interactive-mode.d.ts +46 -0
- package/dist/modes/interactive/interactive-mode.js +491 -0
- package/dist/modes/interactive/key-dispatch.d.ts +29 -0
- package/dist/modes/interactive/key-dispatch.js +107 -0
- package/dist/modes/interactive/line/line-editor.d.ts +50 -0
- package/dist/modes/interactive/line/line-editor.js +232 -0
- package/dist/modes/interactive/line/line-mode.d.ts +20 -0
- package/dist/modes/interactive/line/line-mode.js +218 -0
- package/dist/modes/interactive/line/line-render.d.ts +35 -0
- package/dist/modes/interactive/line/line-render.js +171 -0
- package/dist/modes/interactive/line/paste-state.d.ts +26 -0
- package/dist/modes/interactive/line/paste-state.js +72 -0
- package/dist/modes/interactive/message-view.d.ts +77 -0
- package/dist/modes/interactive/message-view.js +269 -0
- package/dist/modes/interactive/pickers.d.ts +42 -0
- package/dist/modes/interactive/pickers.js +115 -0
- package/dist/modes/interactive/startup-ui.d.ts +33 -0
- package/dist/modes/interactive/startup-ui.js +268 -0
- package/dist/modes/interactive/status-bar.d.ts +48 -0
- package/dist/modes/interactive/status-bar.js +139 -0
- package/dist/modes/interactive/tool-view.d.ts +95 -0
- package/dist/modes/interactive/tool-view.js +344 -0
- package/dist/modes/print/json-event.d.ts +15 -0
- package/dist/modes/print/json-event.js +39 -0
- package/dist/modes/print/print-mode.d.ts +17 -0
- package/dist/modes/print/print-mode.js +107 -0
- package/dist/modes/rpc/commands.d.ts +40 -0
- package/dist/modes/rpc/commands.js +263 -0
- package/dist/modes/rpc/jsonl.d.ts +16 -0
- package/dist/modes/rpc/jsonl.js +55 -0
- package/dist/modes/rpc/rpc-mode.d.ts +22 -0
- package/dist/modes/rpc/rpc-mode.js +153 -0
- package/dist/modes/session-report.d.ts +55 -0
- package/dist/modes/session-report.js +278 -0
- package/dist/modes/shared.d.ts +13 -0
- package/dist/modes/shared.js +52 -0
- package/dist/modes/startup-ui-text.d.ts +16 -0
- package/dist/modes/startup-ui-text.js +103 -0
- package/dist/permissions/broker.d.ts +47 -0
- package/dist/permissions/broker.js +107 -0
- package/dist/permissions/dangerous.d.ts +47 -0
- package/dist/permissions/dangerous.js +370 -0
- package/dist/permissions/pipeline.d.ts +51 -0
- package/dist/permissions/pipeline.js +181 -0
- package/dist/permissions/preview.d.ts +43 -0
- package/dist/permissions/preview.js +463 -0
- package/dist/permissions/rules.d.ts +58 -0
- package/dist/permissions/rules.js +249 -0
- package/dist/permissions/types.d.ts +100 -0
- package/dist/permissions/types.js +18 -0
- package/dist/rpc.d.ts +173 -0
- package/dist/rpc.js +13 -0
- package/dist/sdk.d.ts +98 -0
- package/dist/sdk.js +195 -0
- package/dist/session/manager.d.ts +70 -0
- package/dist/session/manager.js +278 -0
- package/dist/session/migrate.d.ts +19 -0
- package/dist/session/migrate.js +53 -0
- package/dist/session/projection.d.ts +50 -0
- package/dist/session/projection.js +184 -0
- package/dist/session/store.d.ts +47 -0
- package/dist/session/store.js +210 -0
- package/dist/session/tree.d.ts +21 -0
- package/dist/session/tree.js +88 -0
- package/dist/session/types.d.ts +201 -0
- package/dist/session/types.js +18 -0
- package/dist/skills/discover.d.ts +60 -0
- package/dist/skills/discover.js +160 -0
- package/dist/skills/expand.d.ts +35 -0
- package/dist/skills/expand.js +46 -0
- package/dist/skills/frontmatter.d.ts +27 -0
- package/dist/skills/frontmatter.js +157 -0
- package/dist/skills/index-prompt.d.ts +13 -0
- package/dist/skills/index-prompt.js +42 -0
- package/dist/skills/templates.d.ts +52 -0
- package/dist/skills/templates.js +177 -0
- package/dist/tools/bash.d.ts +49 -0
- package/dist/tools/bash.js +222 -0
- package/dist/tools/edit-fuzzy.d.ts +36 -0
- package/dist/tools/edit-fuzzy.js +129 -0
- package/dist/tools/edit.d.ts +38 -0
- package/dist/tools/edit.js +241 -0
- package/dist/tools/file-mutex.d.ts +8 -0
- package/dist/tools/file-mutex.js +29 -0
- package/dist/tools/glob.d.ts +44 -0
- package/dist/tools/glob.js +217 -0
- package/dist/tools/grep.d.ts +39 -0
- package/dist/tools/grep.js +179 -0
- package/dist/tools/ignore.d.ts +40 -0
- package/dist/tools/ignore.js +174 -0
- package/dist/tools/image-file.d.ts +31 -0
- package/dist/tools/image-file.js +114 -0
- package/dist/tools/ls.d.ts +21 -0
- package/dist/tools/ls.js +96 -0
- package/dist/tools/output-accumulator.d.ts +48 -0
- package/dist/tools/output-accumulator.js +106 -0
- package/dist/tools/paths.d.ts +20 -0
- package/dist/tools/paths.js +58 -0
- package/dist/tools/presets.d.ts +71 -0
- package/dist/tools/presets.js +128 -0
- package/dist/tools/process-tree.d.ts +35 -0
- package/dist/tools/process-tree.js +125 -0
- package/dist/tools/read.d.ts +24 -0
- package/dist/tools/read.js +132 -0
- package/dist/tools/registry.d.ts +43 -0
- package/dist/tools/registry.js +106 -0
- package/dist/tools/shell.d.ts +38 -0
- package/dist/tools/shell.js +91 -0
- package/dist/tools/task.d.ts +29 -0
- package/dist/tools/task.js +107 -0
- package/dist/tools/todo.d.ts +28 -0
- package/dist/tools/todo.js +107 -0
- package/dist/tools/truncate.d.ts +44 -0
- package/dist/tools/truncate.js +175 -0
- package/dist/tools/types.d.ts +135 -0
- package/dist/tools/types.js +19 -0
- package/dist/tools/write.d.ts +20 -0
- package/dist/tools/write.js +84 -0
- package/dist/tui/ansi.d.ts +29 -0
- package/dist/tui/ansi.js +420 -0
- package/dist/tui/component.d.ts +38 -0
- package/dist/tui/component.js +13 -0
- package/dist/tui/components/box.d.ts +26 -0
- package/dist/tui/components/box.js +65 -0
- package/dist/tui/components/container.d.ts +14 -0
- package/dist/tui/components/container.js +39 -0
- package/dist/tui/components/editor-buffer.d.ts +67 -0
- package/dist/tui/components/editor-buffer.js +300 -0
- package/dist/tui/components/editor-paste.d.ts +29 -0
- package/dist/tui/components/editor-paste.js +64 -0
- package/dist/tui/components/editor.d.ts +114 -0
- package/dist/tui/components/editor.js +530 -0
- package/dist/tui/components/key-value.d.ts +29 -0
- package/dist/tui/components/key-value.js +48 -0
- package/dist/tui/components/loader.d.ts +35 -0
- package/dist/tui/components/loader.js +71 -0
- package/dist/tui/components/markdown.d.ts +90 -0
- package/dist/tui/components/markdown.js +462 -0
- package/dist/tui/components/meter.d.ts +29 -0
- package/dist/tui/components/meter.js +47 -0
- package/dist/tui/components/overlay.d.ts +24 -0
- package/dist/tui/components/overlay.js +52 -0
- package/dist/tui/components/select-list.d.ts +57 -0
- package/dist/tui/components/select-list.js +179 -0
- package/dist/tui/components/spacer.d.ts +11 -0
- package/dist/tui/components/spacer.js +16 -0
- package/dist/tui/components/text.d.ts +30 -0
- package/dist/tui/components/text.js +65 -0
- package/dist/tui/keybindings.d.ts +66 -0
- package/dist/tui/keybindings.js +120 -0
- package/dist/tui/keys.d.ts +38 -0
- package/dist/tui/keys.js +275 -0
- package/dist/tui/stdin-buffer.d.ts +41 -0
- package/dist/tui/stdin-buffer.js +206 -0
- package/dist/tui/terminal.d.ts +79 -0
- package/dist/tui/terminal.js +153 -0
- package/dist/tui/theme.d.ts +31 -0
- package/dist/tui/theme.js +199 -0
- package/dist/tui/tui.d.ts +103 -0
- package/dist/tui/tui.js +363 -0
- package/dist/tui/vt-screen.d.ts +46 -0
- package/dist/tui/vt-screen.js +251 -0
- package/dist/tui.d.ts +30 -0
- package/dist/tui.js +31 -0
- package/dist/version.d.ts +8 -0
- package/dist/version.js +24 -0
- package/docs/codemode.md +102 -0
- package/docs/hooks.md +196 -0
- package/docs/host-api.md +161 -0
- package/docs/providers.md +531 -0
- package/docs/rpc.md +253 -0
- package/docs/session-format.md +119 -0
- package/docs/tui.md +170 -0
- package/package.json +101 -4
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 执行前预览(第三波 §2.2):审批时列出「这一步会碰到什么」。[W3-B9a-2]
|
|
3
|
+
*
|
|
4
|
+
* - 只读:只用 `statSync / lstatSync / readdirSync / readFileSync`,不起子进程、不写盘;
|
|
5
|
+
* - 有上限:目录递归计数每个目标最多 `maxEntries`(缺省 2000)项,整次预览共用一个时间预算
|
|
6
|
+
* (缺省 200 ms,预算循环里逐项检查),超出只给提示;超时 / 超限不降低严重度;
|
|
7
|
+
* - bash:按段(含 `sh -c` / `eval` / `xargs` / `find -exec` 的嵌套命令)识别 `rm / rmdir / unlink`、
|
|
8
|
+
* `mv`、`git clean`、`git checkout -- <路径>`、`git reset --hard` 与 `>` / `>>` 重定向目标;
|
|
9
|
+
* 通配符、变量与 `{}` 不展开,原样显示并提示范围可能更大;段首的 `cd <路径>` 改变后续段的基准;
|
|
10
|
+
* - write:目标是否存在、现有行数 / 大小 → 新内容;覆盖本会话未 read 过的文件标 warn;
|
|
11
|
+
* - edit:读原文用 `planEdits` 干跑,成功给每处 −n/+m 行与总变化,失败提前说明原因;
|
|
12
|
+
* 超过 2 MiB 的文件只报大小,不读内容;
|
|
13
|
+
* - 其它工具:一行参数摘要。
|
|
14
|
+
*
|
|
15
|
+
* `lines` 不含颜色;`affected[].path` 与行里的路径一样是显示路径(cwd 内相对、`/` 分隔)。
|
|
16
|
+
*/
|
|
17
|
+
import { lstatSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
18
|
+
import { basename, join } from "node:path";
|
|
19
|
+
import { EditError, planEdits } from "../tools/edit.js";
|
|
20
|
+
import { normalizeToLF, splitBom } from "../tools/edit-fuzzy.js";
|
|
21
|
+
import { displayPath, resolvePath } from "../tools/paths.js";
|
|
22
|
+
import { collectNestedCommands, commandWords, matchDangerous, shellWords } from "./dangerous.js";
|
|
23
|
+
import { splitShellSegments } from "./rules.js";
|
|
24
|
+
export const PREVIEW_MAX_ENTRIES = 2000;
|
|
25
|
+
export const PREVIEW_BUDGET_MS = 200;
|
|
26
|
+
/** 超过它的文件不读内容,只报大小。 */
|
|
27
|
+
export const PREVIEW_MAX_READ_BYTES = 2 * 1024 * 1024;
|
|
28
|
+
/** 一次预览最多列出的目标数。 */
|
|
29
|
+
export const PREVIEW_MAX_TARGETS = 20;
|
|
30
|
+
const RANK = { info: 0, warn: 1, danger: 2 };
|
|
31
|
+
const NULL_DEVICES = new Set(["/dev/null", "/dev/stdout", "/dev/stderr", "/dev/tty", "nul"]);
|
|
32
|
+
const UNEXPANDED = /[*?[\]{}$`]/;
|
|
33
|
+
export function formatBytes(bytes) {
|
|
34
|
+
if (bytes < 1024)
|
|
35
|
+
return `${bytes} B`;
|
|
36
|
+
const units = ["KB", "MB", "GB", "TB"];
|
|
37
|
+
let value = bytes / 1024;
|
|
38
|
+
let unit = 0;
|
|
39
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
40
|
+
value /= 1024;
|
|
41
|
+
unit++;
|
|
42
|
+
}
|
|
43
|
+
return `${value.toFixed(1)} ${units[unit]}`;
|
|
44
|
+
}
|
|
45
|
+
function countLines(text) {
|
|
46
|
+
if (text === "")
|
|
47
|
+
return 0;
|
|
48
|
+
const n = normalizeToLF(text).split("\n").length;
|
|
49
|
+
return text.endsWith("\n") ? n - 1 : n;
|
|
50
|
+
}
|
|
51
|
+
class Collector {
|
|
52
|
+
options;
|
|
53
|
+
lines = [];
|
|
54
|
+
affected = [];
|
|
55
|
+
severity = "info";
|
|
56
|
+
timedOut = false;
|
|
57
|
+
hidden = 0;
|
|
58
|
+
deadline;
|
|
59
|
+
maxEntries;
|
|
60
|
+
now;
|
|
61
|
+
/** 显示路径的基准(会话 cwd)。 */
|
|
62
|
+
root;
|
|
63
|
+
/** 解析基准:随段首的 `cd` 与 `git -C` 变化。 */
|
|
64
|
+
cwd;
|
|
65
|
+
constructor(options) {
|
|
66
|
+
this.options = options;
|
|
67
|
+
this.root = options.cwd;
|
|
68
|
+
this.cwd = options.cwd;
|
|
69
|
+
this.now = options.now ?? Date.now;
|
|
70
|
+
this.deadline = this.now() + (options.budgetMs ?? PREVIEW_BUDGET_MS);
|
|
71
|
+
this.maxEntries = options.maxEntries ?? PREVIEW_MAX_ENTRIES;
|
|
72
|
+
}
|
|
73
|
+
raise(severity) {
|
|
74
|
+
if (RANK[severity] > RANK[this.severity])
|
|
75
|
+
this.severity = severity;
|
|
76
|
+
}
|
|
77
|
+
expired() {
|
|
78
|
+
if (!this.timedOut && this.now() > this.deadline)
|
|
79
|
+
this.timedOut = true;
|
|
80
|
+
return this.timedOut;
|
|
81
|
+
}
|
|
82
|
+
/** 一个目标的一行;超出 {@link PREVIEW_MAX_TARGETS} 只计数。 */
|
|
83
|
+
target(line, target) {
|
|
84
|
+
if (this.affected.length + this.hidden >= PREVIEW_MAX_TARGETS) {
|
|
85
|
+
this.hidden++;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
this.lines.push(line);
|
|
89
|
+
if (target !== undefined)
|
|
90
|
+
this.affected.push(target);
|
|
91
|
+
}
|
|
92
|
+
finish(kind) {
|
|
93
|
+
if (this.hidden > 0)
|
|
94
|
+
this.lines.push(`… 另 ${this.hidden} 处未列出`);
|
|
95
|
+
if (this.timedOut) {
|
|
96
|
+
this.lines.push(`统计超时(> ${this.options.budgetMs ?? PREVIEW_BUDGET_MS} ms),实际范围可能更大`);
|
|
97
|
+
this.raise("warn");
|
|
98
|
+
}
|
|
99
|
+
const preview = { kind, lines: this.lines, severity: this.severity };
|
|
100
|
+
if (this.affected.length > 0)
|
|
101
|
+
preview.affected = this.affected;
|
|
102
|
+
return preview;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function tryStat(path, follow = false) {
|
|
106
|
+
try {
|
|
107
|
+
return (follow ? statSync : lstatSync)(path, { throwIfNoEntry: false });
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** 目录递归计数(不跟随符号链接):文件数、字节数;超限 / 超时提前返回。 */
|
|
114
|
+
function tally(root, c) {
|
|
115
|
+
const out = { files: 0, bytes: 0, capped: false };
|
|
116
|
+
const stack = [root];
|
|
117
|
+
let entries = 0;
|
|
118
|
+
while (stack.length > 0) {
|
|
119
|
+
const dir = stack.pop();
|
|
120
|
+
let list;
|
|
121
|
+
try {
|
|
122
|
+
list = readdirSync(dir, { withFileTypes: true });
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
for (const entry of list) {
|
|
128
|
+
if (c.expired())
|
|
129
|
+
return out;
|
|
130
|
+
if (++entries > c.maxEntries)
|
|
131
|
+
return { ...out, capped: true };
|
|
132
|
+
const full = join(dir, entry.name);
|
|
133
|
+
if (entry.isDirectory())
|
|
134
|
+
stack.push(full);
|
|
135
|
+
else {
|
|
136
|
+
out.files++;
|
|
137
|
+
out.bytes += tryStat(full)?.size ?? 0;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
/** 统计一个路径:返回目标与「:」之后的说明。 */
|
|
144
|
+
function inspect(abs, c) {
|
|
145
|
+
const shown = displayPath(abs, c.root);
|
|
146
|
+
const info = tryStat(abs);
|
|
147
|
+
if (info === undefined)
|
|
148
|
+
return { target: { path: shown, exists: false }, text: "不存在" };
|
|
149
|
+
if (info.isSymbolicLink())
|
|
150
|
+
return { target: { path: shown, exists: true }, text: "符号链接" };
|
|
151
|
+
if (!info.isDirectory()) {
|
|
152
|
+
return {
|
|
153
|
+
target: { path: shown, exists: true, bytes: info.size },
|
|
154
|
+
text: `文件,${formatBytes(info.size)}`,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const t = tally(abs, c);
|
|
158
|
+
const target = { path: `${shown}/`, exists: true, files: t.files };
|
|
159
|
+
if (t.capped)
|
|
160
|
+
return { target, text: `目录,> ${c.maxEntries} 项` };
|
|
161
|
+
target.bytes = t.bytes;
|
|
162
|
+
const partial = c.timedOut ? "(统计未完成)" : "";
|
|
163
|
+
return { target, text: `目录,${t.files} 个文件,${formatBytes(t.bytes)}${partial}` };
|
|
164
|
+
}
|
|
165
|
+
/** 一个路径参数一行;含通配 / 变量的原样显示不展开。 */
|
|
166
|
+
function pathLine(verb, word, c, note = "") {
|
|
167
|
+
if (UNEXPANDED.test(word)) {
|
|
168
|
+
c.target(`${verb} ${word}:含通配符或变量,未展开,实际范围可能更大`);
|
|
169
|
+
c.raise("warn");
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const { target, text } = inspect(resolvePath(word, c.cwd), c);
|
|
173
|
+
if (target.exists)
|
|
174
|
+
c.raise("warn");
|
|
175
|
+
c.target(`${verb} ${target.path}:${text}${note}`, target);
|
|
176
|
+
}
|
|
177
|
+
/** 段里的 `>` / `>>` 重定向:返回目标与去掉重定向后的文本。 */
|
|
178
|
+
export function splitRedirects(segment) {
|
|
179
|
+
const targets = [];
|
|
180
|
+
let rest = "";
|
|
181
|
+
let quote;
|
|
182
|
+
for (let i = 0; i < segment.length; i++) {
|
|
183
|
+
const ch = segment[i];
|
|
184
|
+
if (quote !== undefined) {
|
|
185
|
+
rest += ch;
|
|
186
|
+
if (ch === "\\" && quote === '"' && i + 1 < segment.length)
|
|
187
|
+
rest += segment[++i];
|
|
188
|
+
else if (ch === quote)
|
|
189
|
+
quote = undefined;
|
|
190
|
+
}
|
|
191
|
+
else if (ch === "'" || ch === '"') {
|
|
192
|
+
quote = ch;
|
|
193
|
+
rest += ch;
|
|
194
|
+
}
|
|
195
|
+
else if (ch === "\\" && i + 1 < segment.length) {
|
|
196
|
+
rest += ch + segment[++i];
|
|
197
|
+
}
|
|
198
|
+
else if (ch !== ">" || segment[i + 1] === "(") {
|
|
199
|
+
rest += ch;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
rest = rest.replace(/(^|\s)(\d+|&)$/, "$1");
|
|
203
|
+
let j = i + 1;
|
|
204
|
+
const append = segment[j] === ">";
|
|
205
|
+
if (append || segment[j] === "|")
|
|
206
|
+
j++;
|
|
207
|
+
const dup = segment[j] === "&";
|
|
208
|
+
if (dup)
|
|
209
|
+
j++;
|
|
210
|
+
while (segment[j] === " " || segment[j] === "\t")
|
|
211
|
+
j++;
|
|
212
|
+
let end = j;
|
|
213
|
+
let q;
|
|
214
|
+
for (; end < segment.length; end++) {
|
|
215
|
+
const e = segment[end];
|
|
216
|
+
if (q !== undefined) {
|
|
217
|
+
if (e === q)
|
|
218
|
+
q = undefined;
|
|
219
|
+
}
|
|
220
|
+
else if (e === "'" || e === '"')
|
|
221
|
+
q = e;
|
|
222
|
+
else if (/[\s;|&<>()]/.test(e))
|
|
223
|
+
break;
|
|
224
|
+
}
|
|
225
|
+
const word = shellWords(segment.slice(j, end))[0] ?? "";
|
|
226
|
+
if (word !== "" && !(dup && /^(\d+|-)$/.test(word)))
|
|
227
|
+
targets.push({ word, append });
|
|
228
|
+
rest += " ";
|
|
229
|
+
i = end - 1;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return { rest, targets };
|
|
233
|
+
}
|
|
234
|
+
function operands(argv, valueFlags = []) {
|
|
235
|
+
const out = [];
|
|
236
|
+
let all = false;
|
|
237
|
+
for (let i = 1; i < argv.length; i++) {
|
|
238
|
+
const a = argv[i];
|
|
239
|
+
if (all || a === "-" || !a.startsWith("-"))
|
|
240
|
+
out.push(a);
|
|
241
|
+
else if (a === "--")
|
|
242
|
+
all = true;
|
|
243
|
+
else if (valueFlags.includes(a))
|
|
244
|
+
i++;
|
|
245
|
+
}
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
function previewSegment(segment, c) {
|
|
249
|
+
const { rest, targets } = splitRedirects(segment);
|
|
250
|
+
for (const { word, append } of targets) {
|
|
251
|
+
if (NULL_DEVICES.has(word.toLowerCase()))
|
|
252
|
+
continue;
|
|
253
|
+
pathLine(append ? "追加写入" : "覆盖写入", word, c);
|
|
254
|
+
}
|
|
255
|
+
const argv = commandWords(rest);
|
|
256
|
+
const name = basename(argv[0] ?? "");
|
|
257
|
+
if (name === "cd" && argv.length === 2 && !UNEXPANDED.test(argv[1])) {
|
|
258
|
+
c.cwd = resolvePath(argv[1], c.cwd);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (name === "rm" || name === "rmdir" || name === "unlink") {
|
|
262
|
+
const paths = operands(argv);
|
|
263
|
+
if (paths.length === 0)
|
|
264
|
+
c.target(`${name}:路径来自管道或展开,范围未知`);
|
|
265
|
+
for (const p of paths)
|
|
266
|
+
pathLine("删除", p, c);
|
|
267
|
+
}
|
|
268
|
+
else if (name === "mv") {
|
|
269
|
+
const paths = operands(argv, ["-t", "-S", "--target-directory", "--suffix"]);
|
|
270
|
+
const t = argv.indexOf("-t");
|
|
271
|
+
const dest = t > 0 ? argv[t + 1] : paths.pop();
|
|
272
|
+
for (const p of paths)
|
|
273
|
+
pathLine("移动", p, c);
|
|
274
|
+
if (dest !== undefined && paths.length > 0) {
|
|
275
|
+
const info = UNEXPANDED.test(dest) ? undefined : tryStat(resolvePath(dest, c.cwd));
|
|
276
|
+
const note = info?.isDirectory() ? "(已存在的目录,移入其中)" : "(已存在,将被覆盖)";
|
|
277
|
+
pathLine("移到", dest, c, info === undefined ? "" : note);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else if (name === "git") {
|
|
281
|
+
previewGit(argv, c);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const GIT_VALUE_OPTS = ["-C", "-c", "--git-dir", "--work-tree", "--namespace"];
|
|
285
|
+
function previewGit(argv, c) {
|
|
286
|
+
let i = 1;
|
|
287
|
+
let base = c.cwd;
|
|
288
|
+
while (i < argv.length && argv[i].startsWith("-")) {
|
|
289
|
+
if (argv[i] === "-C" && argv[i + 1] !== undefined)
|
|
290
|
+
base = resolvePath(argv[i + 1], base);
|
|
291
|
+
i += GIT_VALUE_OPTS.includes(argv[i]) ? 2 : 1;
|
|
292
|
+
}
|
|
293
|
+
const sub = argv[i];
|
|
294
|
+
const args = argv.slice(i);
|
|
295
|
+
const saved = c.cwd;
|
|
296
|
+
c.cwd = base;
|
|
297
|
+
try {
|
|
298
|
+
if (sub === "clean" &&
|
|
299
|
+
!args.some((a) => a === "-n" || a === "--dry-run" || /^-[a-z]*n/.test(a))) {
|
|
300
|
+
const paths = operands(args, ["-e", "--exclude"]);
|
|
301
|
+
for (const p of paths.length > 0 ? paths : ["."]) {
|
|
302
|
+
pathLine("git clean 范围", p, c, "(计数含已跟踪文件,实际只删未跟踪的)");
|
|
303
|
+
}
|
|
304
|
+
// 与危险规则无关地标红:`git -C 目录 clean -f` 之类危险表认不出的写法也算
|
|
305
|
+
if (args.some((a) => a === "--force" || /^-[A-Za-z]*f/.test(a)))
|
|
306
|
+
c.raise("danger");
|
|
307
|
+
}
|
|
308
|
+
else if (sub === "checkout" && args.includes("--")) {
|
|
309
|
+
for (const p of args.slice(args.indexOf("--") + 1))
|
|
310
|
+
pathLine("丢弃改动", p, c);
|
|
311
|
+
}
|
|
312
|
+
else if (sub === "reset" && args.includes("--hard")) {
|
|
313
|
+
c.lines.push("git reset --hard:丢弃工作区与暂存区的全部未提交改动");
|
|
314
|
+
c.raise("danger");
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
finally {
|
|
318
|
+
c.cwd = saved;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
function previewBash(command, c, dangerous) {
|
|
322
|
+
const hit = matchDangerous(command);
|
|
323
|
+
if (hit !== undefined)
|
|
324
|
+
c.lines.push(`危险:${hit.description}`);
|
|
325
|
+
if (hit !== undefined || dangerous)
|
|
326
|
+
c.raise("danger");
|
|
327
|
+
const nested = collectNestedCommands(command);
|
|
328
|
+
const start = c.cwd;
|
|
329
|
+
for (const text of [command, ...nested.commands]) {
|
|
330
|
+
c.cwd = start;
|
|
331
|
+
for (const segment of splitShellSegments(text)) {
|
|
332
|
+
if (c.timedOut)
|
|
333
|
+
break;
|
|
334
|
+
previewSegment(segment, c);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
c.cwd = start;
|
|
338
|
+
if (nested.tooDeep) {
|
|
339
|
+
c.lines.push("嵌套过深,内层命令未展开");
|
|
340
|
+
c.raise("warn");
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function readSmall(abs, info) {
|
|
344
|
+
return info.size > PREVIEW_MAX_READ_BYTES ? undefined : readFileSync(abs, "utf8");
|
|
345
|
+
}
|
|
346
|
+
function unread(abs, request, c, tool) {
|
|
347
|
+
const readFiles = request.context?.readFiles;
|
|
348
|
+
if (readFiles === undefined || readFiles.has(abs))
|
|
349
|
+
return;
|
|
350
|
+
c.lines.push(`本会话未 read 过此文件,${tool} 会被拒绝(先 read)`);
|
|
351
|
+
c.raise("warn");
|
|
352
|
+
}
|
|
353
|
+
function previewWrite(input, request, c) {
|
|
354
|
+
const abs = resolvePath(String(input["path"] ?? ""), c.cwd);
|
|
355
|
+
const content = typeof input["content"] === "string" ? input["content"] : "";
|
|
356
|
+
const next = `${countLines(content)} 行,${formatBytes(Buffer.byteLength(content, "utf8"))}`;
|
|
357
|
+
const shown = displayPath(abs, c.root);
|
|
358
|
+
const info = tryStat(abs, true);
|
|
359
|
+
if (info === undefined) {
|
|
360
|
+
c.target(`新建 ${shown}:${next}`, { path: shown, exists: false });
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (info.isDirectory()) {
|
|
364
|
+
c.target(`${shown} 是目录,write 会失败`, { path: `${shown}/`, exists: true });
|
|
365
|
+
c.raise("warn");
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const old = readSmall(abs, info);
|
|
369
|
+
const before = old === undefined ? formatBytes(info.size) : `${countLines(old)} 行,${formatBytes(info.size)}`;
|
|
370
|
+
c.target(`覆盖 ${shown}:${before} → ${next}`, { path: shown, exists: true, bytes: info.size });
|
|
371
|
+
unread(abs, request, c, "write");
|
|
372
|
+
}
|
|
373
|
+
function editFailure(message) {
|
|
374
|
+
const many = /^(\S+) matches (\d+) times/.exec(message);
|
|
375
|
+
if (many)
|
|
376
|
+
return `${many[1]} 匹配不唯一(${many[2]} 处)`;
|
|
377
|
+
const missing = /^Could not find (\S+)/.exec(message);
|
|
378
|
+
if (missing)
|
|
379
|
+
return `未找到 ${missing[1]}`;
|
|
380
|
+
if (message.includes("overlap"))
|
|
381
|
+
return "多处修改的区间重叠";
|
|
382
|
+
if (message.includes("no change"))
|
|
383
|
+
return "修改前后内容相同";
|
|
384
|
+
return message;
|
|
385
|
+
}
|
|
386
|
+
function previewEdit(input, request, c) {
|
|
387
|
+
const abs = resolvePath(String(input["path"] ?? ""), c.cwd);
|
|
388
|
+
const shown = displayPath(abs, c.root);
|
|
389
|
+
const info = tryStat(abs, true);
|
|
390
|
+
if (info === undefined || !info.isFile()) {
|
|
391
|
+
c.target(`${shown} 不存在或不是文件,edit 会失败`, { path: shown, exists: info !== undefined });
|
|
392
|
+
c.raise("warn");
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
const target = { path: shown, exists: true, bytes: info.size };
|
|
396
|
+
const raw = readSmall(abs, info);
|
|
397
|
+
if (raw === undefined) {
|
|
398
|
+
c.target(`修改 ${shown}:${formatBytes(info.size)},文件过大,未干跑`, target);
|
|
399
|
+
unread(abs, request, c, "edit");
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
const content = normalizeToLF(splitBom(raw).text);
|
|
403
|
+
const edits = Array.isArray(input["edits"]) ? input["edits"] : [];
|
|
404
|
+
try {
|
|
405
|
+
const plan = planEdits(content, edits, input["replaceAll"] === true);
|
|
406
|
+
const fuzzy = plan.fuzzy ? "(空白 / 引号归一后匹配)" : "";
|
|
407
|
+
c.target(`修改 ${shown}:${plan.replacements} 处替换${fuzzy}`, target);
|
|
408
|
+
edits.slice(0, 5).forEach((e, i) => {
|
|
409
|
+
c.lines.push(` #${i + 1} −${countLines(e.oldText)}/+${countLines(e.newText)} 行`);
|
|
410
|
+
});
|
|
411
|
+
if (edits.length > 5)
|
|
412
|
+
c.lines.push(` … 另 ${edits.length - 5} 处`);
|
|
413
|
+
c.lines.push(` 共 ${countLines(content)} → ${countLines(plan.result)} 行`);
|
|
414
|
+
}
|
|
415
|
+
catch (err) {
|
|
416
|
+
if (!(err instanceof EditError))
|
|
417
|
+
throw err;
|
|
418
|
+
c.target(`修改 ${shown}:干跑失败——${editFailure(err.message)}`, target);
|
|
419
|
+
c.raise("warn");
|
|
420
|
+
}
|
|
421
|
+
unread(abs, request, c, "edit");
|
|
422
|
+
}
|
|
423
|
+
const SUMMARY_KEYS = ["path", "command", "pattern", "description", "query", "url", "name"];
|
|
424
|
+
/** 审批请求的执行前预览;纯读,不抛错(内部错误返回空预览)。 */
|
|
425
|
+
export function previewAction(request, options) {
|
|
426
|
+
const c = new Collector(options);
|
|
427
|
+
const input = typeof request.input === "object" && request.input !== null
|
|
428
|
+
? request.input
|
|
429
|
+
: {};
|
|
430
|
+
const kind = request.toolName === "bash" || request.toolName === "write" || request.toolName === "edit"
|
|
431
|
+
? request.toolName
|
|
432
|
+
: "other";
|
|
433
|
+
try {
|
|
434
|
+
if (kind === "bash" && typeof input["command"] === "string") {
|
|
435
|
+
previewBash(input["command"], c, request.reason === "dangerous");
|
|
436
|
+
}
|
|
437
|
+
else if (kind === "write" && typeof input["path"] === "string") {
|
|
438
|
+
previewWrite(input, request, c);
|
|
439
|
+
}
|
|
440
|
+
else if (kind === "edit" && typeof input["path"] === "string") {
|
|
441
|
+
previewEdit(input, request, c);
|
|
442
|
+
}
|
|
443
|
+
else if (kind === "other") {
|
|
444
|
+
const key = SUMMARY_KEYS.find((k) => typeof input[k] === "string" && input[k] !== "");
|
|
445
|
+
if (key !== undefined)
|
|
446
|
+
c.lines.push(String(input[key])
|
|
447
|
+
.replace(/\s*\n\s*/g, " ⏎ ")
|
|
448
|
+
.slice(0, 120));
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
catch (err) {
|
|
452
|
+
c.lines.push(`预览失败:${err instanceof Error ? err.message : String(err)}`);
|
|
453
|
+
}
|
|
454
|
+
return c.finish(kind);
|
|
455
|
+
}
|
|
456
|
+
/** 对话框 / 行式界面里显示的预览行(`other` 已由输入摘要覆盖,不重复显示)。 */
|
|
457
|
+
export function previewDisplayLines(preview, max = 10) {
|
|
458
|
+
if (preview === undefined || preview.kind === "other")
|
|
459
|
+
return [];
|
|
460
|
+
if (preview.lines.length <= max)
|
|
461
|
+
return preview.lines;
|
|
462
|
+
return [...preview.lines.slice(0, max - 1), `… 另 ${preview.lines.length - max + 1} 行`];
|
|
463
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 权限规则(设计 §7.2、§5.2「通用安全」)。[B3]
|
|
3
|
+
*
|
|
4
|
+
* 语法:`tool` 或 `tool(pattern)`;`tool` 是工具名 glob(`canvas_*`、`*`);pattern:
|
|
5
|
+
* - bash:对命令文本做通配(`*` 任意字符含空格与 `/`,`?` 单字符),命令先折叠空白;
|
|
6
|
+
* - 带 `path` 的工具(read / write / edit / ls / grep / glob 及宿主工具):对路径做 glob(`**`)。
|
|
7
|
+
* 相对模式匹配 cwd 内的相对路径;以 `**` 开头的模式也匹配绝对路径(例如 .ssh 目录规则命中 ~/.ssh 下的文件);
|
|
8
|
+
* 绝对模式(`/…`、`~/…`)匹配绝对路径。缺 path 的调用以 cwd 为路径。
|
|
9
|
+
* - 其它工具带 pattern 的规则不匹配(无可比对的对象)。
|
|
10
|
+
*
|
|
11
|
+
* bash 复合命令:deny 规则命中整条或**任一**段即生效;allow 规则要求**每一**段都被某条 allow 覆盖,
|
|
12
|
+
* 且命令里没有 `$(…)` / 反引号替换——否则 allow 不生效(回到「问」)。
|
|
13
|
+
*
|
|
14
|
+
* 来源约束(§7.2):项目级只能收紧——allow 被忽略、mode 只能更严,均产生 warning。
|
|
15
|
+
*/
|
|
16
|
+
import type { PermissionMode, Rule, RuleSource } from "./types.js";
|
|
17
|
+
/** 内置缺省 deny 表(用户可经 `builtinDeny` 选项移除)。 */
|
|
18
|
+
export declare const BUILTIN_DENY_RULES: readonly string[];
|
|
19
|
+
export declare function parseRule(raw: string, effect: "allow" | "deny", source: RuleSource): Rule;
|
|
20
|
+
export declare function wildcardToRegExp(pattern: string): RegExp;
|
|
21
|
+
export declare function toolNameMatches(ruleTool: string, toolName: string): boolean;
|
|
22
|
+
export declare function normalizeCommand(command: string): string;
|
|
23
|
+
/**
|
|
24
|
+
* 按 `&&`、`||`、`;`、`|`、`&`、换行切分(引号内不切),每段 trim 后返回非空段。
|
|
25
|
+
*/
|
|
26
|
+
export declare function splitShellSegments(command: string): string[];
|
|
27
|
+
export declare function hasCommandSubstitution(command: string): boolean;
|
|
28
|
+
/** 输入里的路径(绝对);没有 path 字段的工具返回 undefined。 */
|
|
29
|
+
export declare function inputPath(toolName: string, input: unknown, cwd: string): string | undefined;
|
|
30
|
+
export declare function inputCommand(input: unknown): string | undefined;
|
|
31
|
+
export declare function pathMatches(pattern: string, absPath: string, cwd: string): boolean;
|
|
32
|
+
/** 第一条命中的 deny 规则。 */
|
|
33
|
+
export declare function findDenyRule(rules: readonly Rule[], toolName: string, input: unknown, cwd: string): Rule | undefined;
|
|
34
|
+
/** 覆盖本次调用的 allow 规则(复合命令返回第一段的覆盖者);不覆盖返回 undefined。 */
|
|
35
|
+
export declare function findAllowRule(rules: readonly Rule[], toolName: string, input: unknown, cwd: string): Rule | undefined;
|
|
36
|
+
/** a 是否比 b 更严(或相等)。 */
|
|
37
|
+
export declare function isAtLeastAsStrict(a: PermissionMode, b: PermissionMode): boolean;
|
|
38
|
+
export interface PermissionLayer {
|
|
39
|
+
source: RuleSource;
|
|
40
|
+
mode?: PermissionMode;
|
|
41
|
+
allow?: readonly string[];
|
|
42
|
+
deny?: readonly string[];
|
|
43
|
+
}
|
|
44
|
+
export interface ResolvedPermissions {
|
|
45
|
+
mode: PermissionMode;
|
|
46
|
+
rules: Rule[];
|
|
47
|
+
warnings: string[];
|
|
48
|
+
}
|
|
49
|
+
export interface ResolveOptions {
|
|
50
|
+
/** 缺省 true = 启用全部内置 deny;false 全部移除;数组 = 要移除的那些原文。 */
|
|
51
|
+
builtinDeny?: boolean | readonly string[];
|
|
52
|
+
defaultMode?: PermissionMode;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* 按给定顺序(调用方排好:用户级 → profile → 项目级 → 命令行)合并。项目级只能收紧。
|
|
56
|
+
* 不合法的规则文本记 warning 并跳过。
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolvePermissionLayers(layers: readonly PermissionLayer[], options?: ResolveOptions): ResolvedPermissions;
|