@armadra/agent 0.4.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 +174 -1
- package/README.md +201 -82
- 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/session-cache.d.ts +5 -0
- package/dist/agent/session-cache.js +17 -1
- package/dist/agent/session-classifier.d.ts +1 -1
- package/dist/agent/session-classifier.js +3 -1
- package/dist/agent/session-compaction.d.ts +35 -6
- package/dist/agent/session-compaction.js +157 -31
- package/dist/agent/session-core.d.ts +27 -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 +40 -1
- package/dist/agent/session.d.ts +30 -29
- package/dist/agent/session.js +79 -123
- 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 +57 -4
- package/dist/agent/types-w5.d.ts +192 -0
- package/dist/agent/types-w5.js +10 -0
- package/dist/agent/types.d.ts +47 -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-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/openai-compat.d.ts +4 -1
- package/dist/ai/apis/openai-compat.js +8 -1
- 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 +20 -10
- package/dist/ai/providers/registry.js +82 -43
- package/dist/ai/types.d.ts +28 -2
- package/dist/ai/types.js +5 -0
- package/dist/bundle/ama.cjs +36203 -20499
- 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 +8 -4
- package/dist/cli/args.js +17 -97
- package/dist/cli/bootstrap.js +13 -1
- package/dist/cli/codemode-notice.d.ts +3 -2
- package/dist/cli/codemode-notice.js +3 -2
- 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 +3 -0
- package/dist/cli/compose-session.js +47 -4
- package/dist/cli/compose.d.ts +3 -0
- package/dist/cli/compose.js +33 -6
- package/dist/cli/deps.d.ts +6 -0
- package/dist/cli/exit-codes.d.ts +10 -0
- package/dist/cli/exit-codes.js +12 -0
- package/dist/cli/help-text.d.ts +4 -0
- package/dist/cli/help-text.js +102 -0
- package/dist/cli/startup-screen.js +5 -3
- package/dist/cli/startup-steps.js +7 -1
- package/dist/cli/subcommands/config.d.ts +4 -1
- package/dist/cli/subcommands/config.js +17 -6
- package/dist/cli/subcommands/doctor.js +21 -0
- package/dist/cli/subcommands/models-discover.js +5 -6
- package/dist/cli/subcommands/models.d.ts +2 -0
- package/dist/cli/subcommands/models.js +33 -14
- package/dist/cli/subcommands/providers.js +5 -6
- package/dist/cli/subcommands/sessions.d.ts +3 -1
- package/dist/cli/subcommands/sessions.js +36 -2
- 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/tool.d.ts +1 -1
- package/dist/codemode/tool.js +6 -4
- 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/json-schema.js +63 -2
- package/dist/config/key-docs.js +72 -3
- package/dist/config/merge.d.ts +5 -3
- package/dist/config/merge.js +81 -7
- 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 +68 -114
- package/dist/config/types-w5.d.ts +118 -0
- package/dist/config/types-w5.js +20 -0
- package/dist/config/types.d.ts +50 -1
- package/dist/config/types.js +12 -0
- 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 +55 -0
- 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 +21 -4
- package/dist/modes/interactive/approval-dialog.js +98 -20
- 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 +9 -1
- package/dist/modes/interactive/commands.js +19 -0
- 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 +14 -1
- package/dist/modes/interactive/interactive-mode.js +134 -136
- package/dist/modes/interactive/key-dispatch.d.ts +16 -2
- package/dist/modes/interactive/key-dispatch.js +49 -1
- package/dist/modes/interactive/line/line-mode.d.ts +2 -1
- package/dist/modes/interactive/line/line-mode.js +22 -5
- package/dist/modes/interactive/line/line-render.d.ts +2 -1
- package/dist/modes/interactive/line/line-render.js +40 -4
- package/dist/modes/interactive/message-view.d.ts +1 -1
- package/dist/modes/interactive/message-view.js +24 -2
- package/dist/modes/interactive/panels.d.ts +18 -1
- package/dist/modes/interactive/panels.js +19 -5
- 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/session-events.d.ts +24 -0
- package/dist/modes/interactive/session-events.js +96 -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 +99 -11
- package/dist/modes/interactive/status-bar.js +273 -90
- 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-view.d.ts +12 -2
- package/dist/modes/interactive/tool-view.js +34 -4
- package/dist/modes/print/print-mode.d.ts +13 -4
- package/dist/modes/print/print-mode.js +56 -12
- package/dist/modes/rpc/commands.d.ts +9 -0
- package/dist/modes/rpc/commands.js +44 -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 +5 -0
- package/dist/permissions/auto-safe.js +18 -0
- package/dist/permissions/classifier.d.ts +4 -0
- package/dist/permissions/classifier.js +2 -0
- package/dist/permissions/modes.js +1 -1
- package/dist/permissions/pipeline.d.ts +43 -4
- package/dist/permissions/pipeline.js +114 -9
- package/dist/permissions/readonly-bash.d.ts +30 -0
- package/dist/permissions/readonly-bash.js +118 -0
- package/dist/permissions/types.d.ts +42 -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 +33 -2
- package/dist/sdk.js +24 -2
- 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 +7 -2
- package/dist/tools/presets.js +11 -4
- 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/glyphs.d.ts +4 -0
- package/dist/tui/glyphs.js +4 -0
- package/dist/tui/keybindings.d.ts +5 -0
- package/dist/tui/keybindings.js +5 -0
- package/docs/acp.md +78 -0
- package/docs/agents.md +250 -0
- package/docs/codemode.md +12 -8
- package/docs/hooks.md +26 -22
- package/docs/permissions.md +77 -13
- package/docs/plan.md +107 -0
- package/docs/providers.md +228 -52
- package/docs/rewind-plan.md +181 -0
- package/docs/rpc.md +92 -5
- package/docs/sandbox.md +215 -0
- package/docs/session-format.md +41 -21
- package/docs/sessions.md +41 -1
- package/docs/tui-design.md +751 -0
- package/docs/tui.md +125 -6
- package/package.json +13 -2
|
@@ -8,9 +8,14 @@
|
|
|
8
8
|
* - 审批:`RpcApprovals` 是 UI broker(`set_client_capabilities` 声明 approvals 后挂上),
|
|
9
9
|
* `permission_response` 按 requestId 作答;先到的回答暂存,等 broker 被问到时取用。
|
|
10
10
|
* - 密钥不离开进程:`get_available_models` 只回 `hasKey` 与 `keySource`。
|
|
11
|
+
* - [W5-F] 计划:声明 `plans` 能力后计划审批交客户端(`plan_proposed` → `plan_response`),未声明按
|
|
12
|
+
* `plan.unattended`;`approve_fresh` 在这里新建会话并以计划全文开新回合。`get_tasks / get_agents`
|
|
13
|
+
* 读 `RpcContext.tasks / agents`(W5-G 的注册表与发现结果;未装配时回空表)。
|
|
11
14
|
*/
|
|
12
15
|
import { formatModelRef } from "../../ai/providers/channels.js";
|
|
13
16
|
import { AgentSessionImpl } from "../../agent/session.js";
|
|
17
|
+
import { planController } from "../../agent/session-plan.js";
|
|
18
|
+
import { currentTodos } from "../../plan/store.js";
|
|
14
19
|
import { getSupportedLevels } from "../../ai/thinking.js";
|
|
15
20
|
import { switchSession } from "../../cli/compose-session.js";
|
|
16
21
|
import { AmaError } from "../../errors.js";
|
|
@@ -62,6 +67,17 @@ function impl(session) {
|
|
|
62
67
|
return session;
|
|
63
68
|
throw new AmaError("not_implemented", "该会话不支持此命令");
|
|
64
69
|
}
|
|
70
|
+
/** [W5-F] 当前会话的计划控制面;会话没有装配 plan 扩展时报 not_implemented。 */
|
|
71
|
+
function plans(ctx) {
|
|
72
|
+
const controller = planController(ctx.session());
|
|
73
|
+
if (controller === undefined)
|
|
74
|
+
throw new AmaError("not_implemented", "该会话没有装配 plan 扩展");
|
|
75
|
+
return controller;
|
|
76
|
+
}
|
|
77
|
+
/** 声明了 `plans` 能力 → 客户端作答;否则按 `plan.unattended`。换会话后重新应用。 */
|
|
78
|
+
function applyPlanAttendance(ctx) {
|
|
79
|
+
planController(ctx.session())?.setAttendance(ctx.capabilities.has("plans") ? "client" : "unattended");
|
|
80
|
+
}
|
|
65
81
|
/** 开始一次运行:开始 / 入队 / 被处理后即返回,不等运行结束。 */
|
|
66
82
|
async function startRun(ctx, run) {
|
|
67
83
|
const session = ctx.session();
|
|
@@ -89,6 +105,7 @@ async function startRun(ctx, run) {
|
|
|
89
105
|
async function switched(ctx, next, reason) {
|
|
90
106
|
const session = await next;
|
|
91
107
|
ctx.onSessionChanged(session);
|
|
108
|
+
applyPlanAttendance(ctx);
|
|
92
109
|
if (session instanceof AgentSessionImpl)
|
|
93
110
|
session.announceStart(reason);
|
|
94
111
|
return { sessionId: session.state.sessionId, sessionFile: session.state.sessionFile };
|
|
@@ -213,12 +230,24 @@ export const handlers = {
|
|
|
213
230
|
]
|
|
214
231
|
: []),
|
|
215
232
|
}),
|
|
233
|
+
get_rewind_points: async (_p, ctx) => ({ points: ctx.session().rewindPoints() }),
|
|
234
|
+
rewind: async (p, ctx) => {
|
|
235
|
+
const request = { entryId: p.entryId, mode: p.mode };
|
|
236
|
+
if (p.dryRun !== undefined)
|
|
237
|
+
request.dryRun = p.dryRun;
|
|
238
|
+
if (p.onConflict !== undefined)
|
|
239
|
+
request.onConflict = p.onConflict;
|
|
240
|
+
return ctx.session().rewind(request);
|
|
241
|
+
},
|
|
242
|
+
summarize_from: async (p, ctx) => ctx.session().summarizeFrom(p.entryId, p.instructions),
|
|
243
|
+
summarize_up_to: async (p, ctx) => ctx.session().summarizeUpTo(p.entryId, p.instructions),
|
|
216
244
|
set_client_capabilities: async (p, ctx) => {
|
|
217
245
|
ctx.capabilities = new Set(p.capabilities);
|
|
218
246
|
const approvals = ctx.capabilities.has("approvals");
|
|
219
247
|
ctx.runtime.approvals.setUiBroker(approvals ? ctx.approvals : undefined);
|
|
220
248
|
if (!approvals)
|
|
221
249
|
ctx.approvals.cancelAll();
|
|
250
|
+
applyPlanAttendance(ctx);
|
|
222
251
|
return { capabilities: [...ctx.capabilities] };
|
|
223
252
|
},
|
|
224
253
|
permission_response: async (p, ctx) => ({
|
|
@@ -264,5 +293,20 @@ export const handlers = {
|
|
|
264
293
|
},
|
|
265
294
|
get_commands: async (_p, ctx) => ({ commands: commands(ctx.runtime) }),
|
|
266
295
|
get_skills: async (_p, ctx) => ({ skills: ctx.runtime.resources.skills }),
|
|
296
|
+
// [W5-F] 计划 / 任务命令(docs/wave5-plan.md §6.5、§7.5)
|
|
297
|
+
plan_response: async (p, ctx) => {
|
|
298
|
+
const result = await plans(ctx).respond(p);
|
|
299
|
+
if (result.freshPrompt !== undefined) {
|
|
300
|
+
const prompt = result.freshPrompt;
|
|
301
|
+
await switched(ctx, switchSession(ctx.runtime, { kind: "new" }), "new");
|
|
302
|
+
planController(ctx.session())?.adopt(result.plan);
|
|
303
|
+
await startRun(ctx, () => ctx.session().prompt(prompt, { origin: "plan" }));
|
|
304
|
+
}
|
|
305
|
+
return { planId: result.planId, decision: result.decision };
|
|
306
|
+
},
|
|
307
|
+
get_plan: async (p, ctx) => plans(ctx).get(p.planId),
|
|
308
|
+
get_todos: async (_p, ctx) => ({ items: currentTodos(impl(ctx.session()).manager.branch()) }),
|
|
309
|
+
get_tasks: async (_p, ctx) => ({ tasks: [...(ctx.tasks?.()?.list() ?? [])] }),
|
|
310
|
+
get_agents: async (_p, ctx) => ({ agents: [...(ctx.agents?.() ?? [])] }),
|
|
267
311
|
};
|
|
268
312
|
export const RPC_COMMAND_TYPES = Object.keys(handlers);
|
|
@@ -19,9 +19,11 @@ import { RPC_PROTOCOL_VERSION } from "../../rpc.js";
|
|
|
19
19
|
import { AMA_VERSION } from "../../version.js";
|
|
20
20
|
import { toJsonLine, toWireEvent } from "../print/json-event.js";
|
|
21
21
|
import { errorText, onTerminationSignals } from "../shared.js";
|
|
22
|
+
import { sessionAgents, taskRegistryView } from "../../agent/subagent-registry.js";
|
|
23
|
+
import { cachedAgentInfos } from "../../agents/external.js";
|
|
22
24
|
import { RpcApprovals, handlers } from "./commands.js";
|
|
23
25
|
import { createLineReader, writeChunked } from "./jsonl.js";
|
|
24
|
-
export const RPC_CAPABILITIES = ["approvals", "images", "hooks"];
|
|
26
|
+
export const RPC_CAPABILITIES = ["approvals", "images", "hooks", "plans"];
|
|
25
27
|
export async function runRpcMode(runtime, context, options = {}) {
|
|
26
28
|
const stdin = options.stdin ?? process.stdin;
|
|
27
29
|
const stdout = options.stdout ?? process.stdout;
|
|
@@ -48,6 +50,10 @@ export async function runRpcMode(runtime, context, options = {}) {
|
|
|
48
50
|
onBackgroundError(error) {
|
|
49
51
|
void write({ type: "notification", level: "error", message: errorText(error) });
|
|
50
52
|
},
|
|
53
|
+
// [W5-G] get_tasks / get_agents:当前会话的任务注册表与可用类型
|
|
54
|
+
tasks: () => taskRegistryView(session.state.sessionId),
|
|
55
|
+
// [W5-EG] 外部 Agent 的安装 / 版本要异步探测:会话建立时缓存、变化时刷新,未就绪时只列类型目录
|
|
56
|
+
agents: () => cachedAgentInfos(session.state.sessionId) ?? sessionAgents(session.state.sessionId),
|
|
51
57
|
};
|
|
52
58
|
runtime.notifier.set((message, level) => {
|
|
53
59
|
void write({ type: "notification", level, message });
|
|
@@ -47,6 +47,17 @@ export declare function warmStopText(reason: string | undefined): string;
|
|
|
47
47
|
export declare function warmingText(status: WarmerStatus, now: number, minSavingsUsd: number | undefined): string;
|
|
48
48
|
/** 缓存段的键值行(`/session` 与 `/cache` 共用)。 */
|
|
49
49
|
export declare function cacheRows(session: AgentSession, now?: number): KeyValueRow[];
|
|
50
|
+
/** [W5-U] 外部 Agent 用量一项:美元 / token / 请求数按各自单位,不换算。 */
|
|
51
|
+
export declare function externalUsageText(usage: {
|
|
52
|
+
runs: number;
|
|
53
|
+
unit: "usd" | "tokens" | "requests";
|
|
54
|
+
amount: number;
|
|
55
|
+
tokens?: number;
|
|
56
|
+
}): string;
|
|
57
|
+
/** [W5-U] 「外部 Agent」段(`getStats().external.byAgent`);没有外部运行时为空。 */
|
|
58
|
+
export declare function externalRows(session: AgentSession): KeyValueRow[];
|
|
59
|
+
/** [W5-U] 子 Agent 任务汇总(`getStats().tasks`);没有任务时 undefined。 */
|
|
60
|
+
export declare function taskStatsText(session: AgentSession): string | undefined;
|
|
50
61
|
/** 键值行 → 纯文本(键列对齐、去行尾空白)。 */
|
|
51
62
|
export declare function renderRows(rows: readonly KeyValueRow[], indent?: string): string[];
|
|
52
63
|
export declare function describeCache(session: AgentSession, now?: number): string;
|
|
@@ -219,6 +219,45 @@ export function cacheRows(session, now = Date.now()) {
|
|
|
219
219
|
}
|
|
220
220
|
return rows;
|
|
221
221
|
}
|
|
222
|
+
/** [W5-U] 外部 Agent 用量一项:美元 / token / 请求数按各自单位,不换算。 */
|
|
223
|
+
export function externalUsageText(usage) {
|
|
224
|
+
const amount = usage.unit === "usd"
|
|
225
|
+
? formatUsd(usage.amount)
|
|
226
|
+
: usage.unit === "tokens"
|
|
227
|
+
? `${formatTokenCount(usage.amount)} token`
|
|
228
|
+
: `${usage.amount} 次请求`;
|
|
229
|
+
const tokens = usage.unit !== "tokens" && usage.tokens !== undefined && usage.tokens > 0
|
|
230
|
+
? ` · ${formatTokenCount(usage.tokens)} token`
|
|
231
|
+
: "";
|
|
232
|
+
return `${usage.runs} 次运行 · ${amount}${tokens}`;
|
|
233
|
+
}
|
|
234
|
+
/** [W5-U] 「外部 Agent」段(`getStats().external.byAgent`);没有外部运行时为空。 */
|
|
235
|
+
export function externalRows(session) {
|
|
236
|
+
const byAgent = session.getStats().external?.byAgent ?? {};
|
|
237
|
+
return Object.entries(byAgent)
|
|
238
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
239
|
+
.map(([agent, usage]) => ({ key: agent, value: externalUsageText(usage) }));
|
|
240
|
+
}
|
|
241
|
+
/** [W5-U] 子 Agent 任务汇总(`getStats().tasks`);没有任务时 undefined。 */
|
|
242
|
+
export function taskStatsText(session) {
|
|
243
|
+
const tasks = session.getStats().tasks;
|
|
244
|
+
if (tasks === undefined || tasks.total === 0)
|
|
245
|
+
return undefined;
|
|
246
|
+
const parts = [`${tasks.total} 个任务`];
|
|
247
|
+
if (tasks.running > 0)
|
|
248
|
+
parts.push(`运行中 ${tasks.running}`);
|
|
249
|
+
const labels = {
|
|
250
|
+
completed: "完成",
|
|
251
|
+
failed: "失败",
|
|
252
|
+
aborted: "已停止",
|
|
253
|
+
max_turns: "轮数耗尽",
|
|
254
|
+
interrupted: "已中断",
|
|
255
|
+
};
|
|
256
|
+
for (const [status, count] of Object.entries(tasks.byStatus))
|
|
257
|
+
if (count !== undefined && count > 0)
|
|
258
|
+
parts.push(`${labels[status] ?? status} ${count}`);
|
|
259
|
+
return `${parts.join(" · ")}(/tasks)`;
|
|
260
|
+
}
|
|
222
261
|
/** 键值行 → 纯文本(键列对齐、去行尾空白)。 */
|
|
223
262
|
export function renderRows(rows, indent = "") {
|
|
224
263
|
return new KeyValue(rows)
|
|
@@ -257,7 +296,15 @@ export function describeSession(session, now = Date.now()) {
|
|
|
257
296
|
value: `${stats.contextTokens ?? "?"} / ${stats.contextWindow ?? "?"}(${percent}%)`,
|
|
258
297
|
},
|
|
259
298
|
];
|
|
260
|
-
|
|
299
|
+
const tasks = taskStatsText(session);
|
|
300
|
+
if (tasks !== undefined)
|
|
301
|
+
rows.push({ key: "子 Agent", value: tasks });
|
|
302
|
+
const external = externalRows(session);
|
|
303
|
+
return [
|
|
304
|
+
...renderRows(rows),
|
|
305
|
+
describeCache(session, now),
|
|
306
|
+
...(external.length > 0 ? ["外部 Agent", ...renderRows(external, " ")] : []),
|
|
307
|
+
].join("\n");
|
|
261
308
|
}
|
|
262
309
|
/** `/cache fingerprint`:最近一次真实请求的前缀指纹(system / tools 哈希与模型)。 */
|
|
263
310
|
export function describeFingerprint(session) {
|
|
@@ -58,3 +58,8 @@ export interface BashAutoAnalysis {
|
|
|
58
58
|
}
|
|
59
59
|
/** auto 模式下一条 bash 命令的规则层与静态判定结论。 */
|
|
60
60
|
export declare function analyzeBashForAuto(command: string, options: BashAutoOptions): BashAutoAnalysis;
|
|
61
|
+
/**
|
|
62
|
+
* [S2] 命令文本里出现的机密路径(读写都算,含嵌套命令);没有返回 undefined。default 模式「沙箱内免审批」
|
|
63
|
+
* 用它把 `cat .env` 之类留给正常审批:OS 沙箱挡不住读工作区里的机密文件,输出会进模型上下文。
|
|
64
|
+
*/
|
|
65
|
+
export declare function secretReferenceReason(command: string, cwd: string): string | undefined;
|
|
@@ -527,3 +527,21 @@ export function analyzeBashForAuto(command, options) {
|
|
|
527
527
|
}
|
|
528
528
|
return { safe: true, reason: "every command is in the auto safe list" };
|
|
529
529
|
}
|
|
530
|
+
/**
|
|
531
|
+
* [S2] 命令文本里出现的机密路径(读写都算,含嵌套命令);没有返回 undefined。default 模式「沙箱内免审批」
|
|
532
|
+
* 用它把 `cat .env` 之类留给正常审批:OS 沙箱挡不住读工作区里的机密文件,输出会进模型上下文。
|
|
533
|
+
*/
|
|
534
|
+
export function secretReferenceReason(command, cwd) {
|
|
535
|
+
const nested = collectNestedCommands(command);
|
|
536
|
+
for (const text of [command, ...nested.commands]) {
|
|
537
|
+
for (const segment of splitShellSegments(text)) {
|
|
538
|
+
const { argv, redirects } = parseSegment(segment);
|
|
539
|
+
for (const word of [...argv.slice(1), ...redirects.inputs, ...redirects.outputs]) {
|
|
540
|
+
const reason = secretInWord(word, cwd);
|
|
541
|
+
if (reason !== undefined)
|
|
542
|
+
return reason;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return undefined;
|
|
547
|
+
}
|
|
@@ -24,6 +24,10 @@ export interface ClassifierRequest {
|
|
|
24
24
|
projectRoot: string;
|
|
25
25
|
/** 最近一条用户消息(会被截断)。 */
|
|
26
26
|
userMessage?: string;
|
|
27
|
+
/** [S2] bash 将经 OS 沙箱运行(写入只限工作区与临时目录、网络按配置):交给分类器作为输入之一。 */
|
|
28
|
+
sandbox?: {
|
|
29
|
+
network: "deny" | "allow";
|
|
30
|
+
};
|
|
27
31
|
}
|
|
28
32
|
export interface ClassifierVerdict {
|
|
29
33
|
decision: "allow" | "ask";
|
|
@@ -65,6 +65,8 @@ export function buildClassifierPrompt(request) {
|
|
|
65
65
|
`project_root: ${neutralize(JSON.stringify(request.projectRoot))}`,
|
|
66
66
|
`arguments_json: ${neutralize(args)}`,
|
|
67
67
|
];
|
|
68
|
+
if (request.sandbox !== undefined)
|
|
69
|
+
lines.push(`os_sandbox: "writes limited to the project and temp dirs; network ${request.sandbox.network === "deny" ? "blocked" : "allowed"}"`);
|
|
68
70
|
if (request.userMessage !== undefined && request.userMessage.trim() !== "") {
|
|
69
71
|
const summary = truncate(request.userMessage.replace(/\s+/g, " ").trim(), CLASSIFIER_USER_LIMIT);
|
|
70
72
|
lines.push(`latest_user_message: ${neutralize(JSON.stringify(summary))}`);
|
|
@@ -8,7 +8,7 @@ import { PERMISSION_MODES_STRICT_FIRST } from "./types.js";
|
|
|
8
8
|
export const PERMISSION_MODE_INFO = {
|
|
9
9
|
default: { label: "Manual", description: "写文件、执行命令前询问" },
|
|
10
10
|
"auto-edit": { label: "Accept edits", description: "自动接受文件编辑,执行命令仍询问" },
|
|
11
|
-
plan: { label: "Plan", description: "
|
|
11
|
+
plan: { label: "Plan", description: "只读调研,只跑只读命令,出计划后审批执行" },
|
|
12
12
|
auto: { label: "Auto", description: "由 ama 判断每一步:安全的自动放行,有风险的才问" },
|
|
13
13
|
"full-auto": { label: "Bypass permissions", description: "全部放行(危险命令仍询问)" },
|
|
14
14
|
allowlist: {
|
|
@@ -3,7 +3,8 @@
|
|
|
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 全部允许
|
|
@@ -16,23 +17,45 @@
|
|
|
16
17
|
* 安全名单里的 bash)→ allow;都没决定 → ask 且 `classify: true`,由调用方问模型分类器(分类器只能
|
|
17
18
|
* 把它变 allow)。每个结论带 `auto{layer, decision, reason}`。
|
|
18
19
|
*
|
|
19
|
-
* allowlist:①②
|
|
20
|
-
* Hook
|
|
20
|
+
* allowlist:①② 同上;只读工具、只读 bash([W5-F] 与 plan 同一子集,保持 plan ⊆ allowlist)与
|
|
21
|
+
* allow 规则 / Hook allow 命中放行,task 同 plan 放行(子会话共用本管线),其余 deny——从不询问
|
|
22
|
+
* (危险命令、Hook ask 也 deny)。
|
|
21
23
|
*
|
|
22
24
|
* allow_session 记忆(内存,不落盘):bash 记命令的前两个词(`npm test`),之后以它开头的命令
|
|
23
25
|
* 命中;带 path 的工具记文件所在目录,之后该目录下的路径命中;其它工具只记工具名。
|
|
24
26
|
* 记忆只作用于第 ③ 步模式产生的 ask(auto 里只作用于规则层之后)。
|
|
25
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 / 会话记忆仍可放行)。
|
|
32
|
+
*
|
|
26
33
|
* 包装里的命令(`sh -c '…'`、`eval`、`xargs`、`find -exec`,见 dangerous.ts)逐层核对:deny 规则命中
|
|
27
34
|
* 任一层即 deny;allow 规则与会话记忆要求外层与每一层嵌套命令都被覆盖,嵌套超深时不放行。
|
|
28
35
|
*/
|
|
29
36
|
import type { AutoAuditEntry, AutoDecision, Decision, PermissionCheckInput, PermissionMode, PermissionPipelineApi, PermissionVerdict, Rule } from "./types.js";
|
|
30
37
|
import type { ToolPermission } from "../tools/types.js";
|
|
38
|
+
import { type BashSandbox } from "../sandbox/bash.js";
|
|
39
|
+
import type { PlanBashMode } from "../config/types-w5.js";
|
|
31
40
|
/**
|
|
32
41
|
* 第 ③ 步的模式真值表(不看输入)。auto 的 write / execute 在这里是 ask,真正的结论由规则层、
|
|
33
42
|
* 静态判定与分类器给出;allowlist 的 write / execute 是 deny(allow 规则可放行)。
|
|
34
43
|
*/
|
|
35
44
|
export declare function modeDecision(mode: PermissionMode, permission: ToolPermission): Decision;
|
|
45
|
+
/** [W5-F] plan 模式拒绝写 / 执行时的说明:带指引,提醒被压缩掉后模型也能从拒绝结果里恢复。 */
|
|
46
|
+
export declare const PLAN_MODE_MESSAGE = "Plan mode is active: write/execute tools are disabled. Finish the plan with a <proposed_plan> block.";
|
|
47
|
+
/** [W5-F] plan 下 `todo set / update` 的拒绝说明(计划与清单分开,避免跳过审批)。 */
|
|
48
|
+
export declare 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.";
|
|
49
|
+
/**
|
|
50
|
+
* [W5-F] plan 模式第 ③ 步(输入可见,docs/wave5-plan.md §6.2):
|
|
51
|
+
* read 放行(`todo` 只放行 get);bash 按 `plan.bash`(readonly:只读子集放行其余拒绝;ask:其余询问;
|
|
52
|
+
* deny:全拒);task 放行(子会话共用同一管线,同样处在 plan);其余 write / execute 拒绝。
|
|
53
|
+
* 返回 undefined = 交给通常的模式真值表。
|
|
54
|
+
*/
|
|
55
|
+
export declare function planDecision(input: Pick<PermissionCheckInput, "toolName" | "permission" | "input">, planBash: PlanBashMode, cwd: string, projectRoot?: string): {
|
|
56
|
+
decision: Decision;
|
|
57
|
+
message?: string;
|
|
58
|
+
};
|
|
36
59
|
export declare const UNATTENDED_MESSAGE = "This tool call requires approval, but no one is available to approve it (unattended mode).";
|
|
37
60
|
export declare const ALLOWLIST_MESSAGE = "Not in the allowlist (\u4E0D\u5728\u5141\u8BB8\u540D\u5355): permission mode \"allowlist\" only runs read-only tools and calls matched by an allow rule, and never asks.";
|
|
38
61
|
interface SessionGrant {
|
|
@@ -49,6 +72,10 @@ export interface PermissionPipelineOptions {
|
|
|
49
72
|
projectRoot?: string;
|
|
50
73
|
/** auto:安全名单追加(`permission.autoSafeCommands`)。 */
|
|
51
74
|
autoSafeCommands?: readonly string[];
|
|
75
|
+
/** [W5-F] plan 模式下的 bash(config `plan.bash`),缺省 readonly。 */
|
|
76
|
+
planBash?: PlanBashMode;
|
|
77
|
+
/** [S2] bash 的 OS 沙箱设定(与 bash 工具用同一份,保证「会不会在沙箱里跑」两边结论一致)。 */
|
|
78
|
+
bashSandbox?: BashSandbox;
|
|
52
79
|
}
|
|
53
80
|
export declare class PermissionPipeline implements PermissionPipelineApi {
|
|
54
81
|
private currentMode;
|
|
@@ -58,16 +85,28 @@ export declare class PermissionPipeline implements PermissionPipelineApi {
|
|
|
58
85
|
readonly cwd: string;
|
|
59
86
|
readonly projectRoot: string;
|
|
60
87
|
readonly autoSafeCommands: readonly string[];
|
|
88
|
+
private planBashMode;
|
|
89
|
+
readonly bashSandbox: BashSandbox | undefined;
|
|
61
90
|
constructor(options: PermissionPipelineOptions);
|
|
62
91
|
get mode(): PermissionMode;
|
|
63
92
|
setMode(mode: PermissionMode): void;
|
|
93
|
+
/** [W5-F] config `plan.bash`(plan 扩展在会话装配时设置)。 */
|
|
94
|
+
get planBash(): PlanBashMode;
|
|
95
|
+
setPlanBash(mode: PlanBashMode): void;
|
|
64
96
|
get rules(): readonly Rule[];
|
|
65
97
|
rememberForSession(toolName: string, input: unknown): void;
|
|
66
98
|
clearSessionGrants(): void;
|
|
67
99
|
recordAutoDecision(toolName: string, input: unknown, decision: AutoDecision): void;
|
|
68
100
|
autoDecisions(): readonly AutoAuditEntry[];
|
|
69
|
-
check(
|
|
101
|
+
check(request: PermissionCheckInput): PermissionVerdict;
|
|
70
102
|
private evaluate;
|
|
103
|
+
/**
|
|
104
|
+
* [S2] default / auto-edit 的「沙箱内免审批」:将在沙箱内运行、沙箱拒绝网络(联网可外带数据)、
|
|
105
|
+
* 命令文本不碰机密路径(沙箱挡不住读工作区里的 `.env`)、嵌套不超深。
|
|
106
|
+
*/
|
|
107
|
+
private sandboxAllows;
|
|
108
|
+
/** [S2] 沙箱生效时请求 `sandbox: false` 越出沙箱。 */
|
|
109
|
+
private escapesSandbox;
|
|
71
110
|
/** allow 规则(每层都覆盖)/ Hook allow / 会话记忆;都不命中返回 undefined。 */
|
|
72
111
|
private allowedBy;
|
|
73
112
|
/** auto 规则层(受保护路径、项目外写入、网络、删除类)要询问的原因。 */
|
|
@@ -3,7 +3,8 @@
|
|
|
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 全部允许
|
|
@@ -16,20 +17,29 @@
|
|
|
16
17
|
* 安全名单里的 bash)→ allow;都没决定 → ask 且 `classify: true`,由调用方问模型分类器(分类器只能
|
|
17
18
|
* 把它变 allow)。每个结论带 `auto{layer, decision, reason}`。
|
|
18
19
|
*
|
|
19
|
-
* allowlist:①②
|
|
20
|
-
* Hook
|
|
20
|
+
* allowlist:①② 同上;只读工具、只读 bash([W5-F] 与 plan 同一子集,保持 plan ⊆ allowlist)与
|
|
21
|
+
* allow 规则 / Hook allow 命中放行,task 同 plan 放行(子会话共用本管线),其余 deny——从不询问
|
|
22
|
+
* (危险命令、Hook ask 也 deny)。
|
|
21
23
|
*
|
|
22
24
|
* allow_session 记忆(内存,不落盘):bash 记命令的前两个词(`npm test`),之后以它开头的命令
|
|
23
25
|
* 命中;带 path 的工具记文件所在目录,之后该目录下的路径命中;其它工具只记工具名。
|
|
24
26
|
* 记忆只作用于第 ③ 步模式产生的 ask(auto 里只作用于规则层之后)。
|
|
25
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 / 会话记忆仍可放行)。
|
|
32
|
+
*
|
|
26
33
|
* 包装里的命令(`sh -c '…'`、`eval`、`xargs`、`find -exec`,见 dangerous.ts)逐层核对:deny 规则命中
|
|
27
34
|
* 任一层即 deny;allow 规则与会话记忆要求外层与每一层嵌套命令都被覆盖,嵌套超深时不放行。
|
|
28
35
|
*/
|
|
29
36
|
import { dirname, sep } from "node:path";
|
|
37
|
+
import { isBackgroundJobQuery } from "../tools/background-jobs.js";
|
|
30
38
|
import { findAllowRule, findDenyRule, inputCommand, inputPath, normalizeCommand, splitShellSegments, } from "./rules.js";
|
|
31
39
|
import { collectNestedCommands, matchDangerous } from "./dangerous.js";
|
|
32
|
-
import { analyzeBashForAuto } from "./auto-safe.js";
|
|
40
|
+
import { analyzeBashForAuto, secretReferenceReason } from "./auto-safe.js";
|
|
41
|
+
import { runsSandboxed, wantsUnsandboxed } from "../sandbox/bash.js";
|
|
42
|
+
import { isReadonlyBash } from "./readonly-bash.js";
|
|
33
43
|
import { secretPathReason, writeProtectionReason } from "./protected.js";
|
|
34
44
|
import { AUTO_AUDIT_LIMIT } from "./types.js";
|
|
35
45
|
/** bash 调用拆成「外层 + 每层嵌套命令」各自的输入;嵌套超深时 tooDeep 为真。非 bash 原样返回。 */
|
|
@@ -56,6 +66,42 @@ export function modeDecision(mode, permission) {
|
|
|
56
66
|
return permission === "write" ? "allow" : "ask";
|
|
57
67
|
return "ask";
|
|
58
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
|
+
}
|
|
59
105
|
export const UNATTENDED_MESSAGE = "This tool call requires approval, but no one is available to approve it (unattended mode).";
|
|
60
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.';
|
|
61
107
|
/** 审计摘要:bash 取命令,带 path 的取路径,其它取工具名;单行、截断。 */
|
|
@@ -103,8 +149,12 @@ export class PermissionPipeline {
|
|
|
103
149
|
cwd;
|
|
104
150
|
projectRoot;
|
|
105
151
|
autoSafeCommands;
|
|
152
|
+
planBashMode;
|
|
153
|
+
bashSandbox;
|
|
106
154
|
constructor(options) {
|
|
155
|
+
this.bashSandbox = options.bashSandbox;
|
|
107
156
|
this.currentMode = options.mode;
|
|
157
|
+
this.planBashMode = options.planBash ?? "readonly";
|
|
108
158
|
this.ruleList = [...options.rules];
|
|
109
159
|
this.cwd = options.cwd;
|
|
110
160
|
this.projectRoot = options.projectRoot ?? options.cwd;
|
|
@@ -116,6 +166,13 @@ export class PermissionPipeline {
|
|
|
116
166
|
setMode(mode) {
|
|
117
167
|
this.currentMode = mode;
|
|
118
168
|
}
|
|
169
|
+
/** [W5-F] config `plan.bash`(plan 扩展在会话装配时设置)。 */
|
|
170
|
+
get planBash() {
|
|
171
|
+
return this.planBashMode;
|
|
172
|
+
}
|
|
173
|
+
setPlanBash(mode) {
|
|
174
|
+
this.planBashMode = mode;
|
|
175
|
+
}
|
|
119
176
|
get rules() {
|
|
120
177
|
return this.ruleList;
|
|
121
178
|
}
|
|
@@ -138,7 +195,11 @@ export class PermissionPipeline {
|
|
|
138
195
|
autoDecisions() {
|
|
139
196
|
return [...this.audit];
|
|
140
197
|
}
|
|
141
|
-
check(
|
|
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;
|
|
142
203
|
const verdict = this.evaluate(input);
|
|
143
204
|
if (this.currentMode === "auto" && verdict.auto === undefined) {
|
|
144
205
|
verdict.auto = {
|
|
@@ -200,13 +261,16 @@ export class PermissionPipeline {
|
|
|
200
261
|
return this.evaluateAuto(input, layers, command);
|
|
201
262
|
if (this.currentMode === "allowlist")
|
|
202
263
|
return this.evaluateAllowlist(input, layers);
|
|
203
|
-
// ③
|
|
204
|
-
const
|
|
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);
|
|
205
269
|
if (byMode === "deny") {
|
|
206
270
|
return {
|
|
207
271
|
decision: "deny",
|
|
208
272
|
step: "mode",
|
|
209
|
-
message:
|
|
273
|
+
message: plan?.message ?? PLAN_MODE_MESSAGE,
|
|
210
274
|
};
|
|
211
275
|
}
|
|
212
276
|
let verdict = byMode === "allow"
|
|
@@ -228,6 +292,10 @@ export class PermissionPipeline {
|
|
|
228
292
|
else if (granted) {
|
|
229
293
|
verdict = { decision: "allow", step: "session" };
|
|
230
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
|
+
}
|
|
231
299
|
}
|
|
232
300
|
if (input.hookDecision === "ask") {
|
|
233
301
|
const reason = "hook";
|
|
@@ -240,6 +308,25 @@ export class PermissionPipeline {
|
|
|
240
308
|
}
|
|
241
309
|
return verdict;
|
|
242
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
|
+
}
|
|
243
330
|
/** allow 规则(每层都覆盖)/ Hook allow / 会话记忆;都不命中返回 undefined。 */
|
|
244
331
|
allowedBy(input, layers, useGrants) {
|
|
245
332
|
const { toolName } = input;
|
|
@@ -329,6 +416,16 @@ export class PermissionPipeline {
|
|
|
329
416
|
: "allowed for this session";
|
|
330
417
|
return { ...allowed, auto: rule("allow", reason) };
|
|
331
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
|
+
}
|
|
332
429
|
const fixed = (reason) => ({
|
|
333
430
|
decision: "allow",
|
|
334
431
|
step: "auto-static",
|
|
@@ -349,6 +446,9 @@ export class PermissionPipeline {
|
|
|
349
446
|
step: "auto-classify",
|
|
350
447
|
approvalReason: "mode",
|
|
351
448
|
classify: true,
|
|
449
|
+
...(command !== undefined && runsSandboxed(this.bashSandbox, input.input)
|
|
450
|
+
? { sandboxed: true }
|
|
451
|
+
: {}),
|
|
352
452
|
auto: {
|
|
353
453
|
layer: "classifier",
|
|
354
454
|
decision: "ask",
|
|
@@ -364,7 +464,12 @@ export class PermissionPipeline {
|
|
|
364
464
|
});
|
|
365
465
|
if (input.hookDecision === "ask")
|
|
366
466
|
return deny(input.hookReason);
|
|
367
|
-
|
|
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 }))
|
|
368
473
|
return { decision: "allow", step: "mode" };
|
|
369
474
|
return this.allowedBy(input, layers, false) ?? deny();
|
|
370
475
|
}
|
|
@@ -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;
|