@armadra/agent 0.4.0 → 0.5.1
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 +184 -1
- package/README.md +202 -83
- 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 +37605 -21619
- 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/choice-prompt.d.ts +58 -0
- package/dist/cli/choice-prompt.js +140 -0
- 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-cache-probe.d.ts +1 -1
- package/dist/cli/subcommands/models-cache-probe.js +3 -12
- 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.d.ts +1 -1
- package/dist/cli/subcommands/providers.js +8 -15
- 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 +13 -1
- package/dist/modes/commands-core.js +64 -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 +12 -1
- package/dist/modes/interactive/commands.js +31 -1
- package/dist/modes/interactive/confirm-dialog.d.ts +49 -0
- package/dist/modes/interactive/confirm-dialog.js +121 -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 +140 -136
- package/dist/modes/interactive/key-dispatch.d.ts +22 -2
- package/dist/modes/interactive/key-dispatch.js +66 -7
- package/dist/modes/interactive/line/line-mode.d.ts +4 -1
- package/dist/modes/interactive/line/line-mode.js +31 -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/startup-ui.js +5 -1
- 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/bypass.d.ts +32 -0
- package/dist/permissions/bypass.js +36 -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 +6 -1
- package/dist/tui/keybindings.js +6 -1
- 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 +78 -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 +179 -21
- package/package.json +13 -2
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plan 模式与 allowlist 放行的只读 bash 子集 `READONLY_BASH`(docs/wave5-plan.md §6.2、D21)。[W5-F]
|
|
3
|
+
*
|
|
4
|
+
* 判定 = auto 的静态判定(`analyzeBashForAuto`:分词、嵌套展开、规则层的网络 / 删除 / 写入目标 /
|
|
5
|
+
* 机密路径、命令替换、变量展开、点文件通配)**全部通过**,再加四条更窄的限制:
|
|
6
|
+
* - 每段命令都命中 {@link READONLY_BASH}(比 auto 安全名单窄:去掉测试 / 构建运行器、`printenv`
|
|
7
|
+
* 这类会把环境变量里的密钥打进上下文的命令);
|
|
8
|
+
* - 没有输出重定向(`/dev/null` 等设备除外);
|
|
9
|
+
* - 没有嵌套 shell(`sh -c`、`eval`、`xargs`、`find -exec`)与进程替换;
|
|
10
|
+
* - 段首没有环境赋值或包装命令(`GIT_EXTERNAL_DIFF=… git diff`、`env …`、`nohup …`)。
|
|
11
|
+
*
|
|
12
|
+
* 机密路径(`cat .env`)由 auto 规则层判为「要询问」,这里因此不算只读;deny 规则仍先于模式判定。
|
|
13
|
+
*/
|
|
14
|
+
export interface ReadonlySpec {
|
|
15
|
+
/** 词前缀(`["git", "log"]`);git 的全局选项只允许 `-C <dir>` 与 `--no-pager`。 */
|
|
16
|
+
words: readonly string[];
|
|
17
|
+
/** 前缀之后任一参数命中即不算只读。 */
|
|
18
|
+
forbid?: RegExp;
|
|
19
|
+
}
|
|
20
|
+
/** 只读命令名单(每条在 readonly-bash.test.ts 有正反例)。 */
|
|
21
|
+
export declare const READONLY_BASH: readonly ReadonlySpec[];
|
|
22
|
+
export interface ReadonlyBashOptions {
|
|
23
|
+
/** 相对路径(机密路径、写入目标)的解析基准;缺省 `process.cwd()`。 */
|
|
24
|
+
cwd?: string;
|
|
25
|
+
projectRoot?: string;
|
|
26
|
+
}
|
|
27
|
+
/** 不是只读的原因;只读返回 undefined。 */
|
|
28
|
+
export declare function readonlyBashReason(command: string, options?: ReadonlyBashOptions): string | undefined;
|
|
29
|
+
/** 每段命令都在 {@link READONLY_BASH} 且无重定向 / 命令替换 / 嵌套 shell。 */
|
|
30
|
+
export declare function isReadonlyBash(command: string, options?: ReadonlyBashOptions): boolean;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plan 模式与 allowlist 放行的只读 bash 子集 `READONLY_BASH`(docs/wave5-plan.md §6.2、D21)。[W5-F]
|
|
3
|
+
*
|
|
4
|
+
* 判定 = auto 的静态判定(`analyzeBashForAuto`:分词、嵌套展开、规则层的网络 / 删除 / 写入目标 /
|
|
5
|
+
* 机密路径、命令替换、变量展开、点文件通配)**全部通过**,再加四条更窄的限制:
|
|
6
|
+
* - 每段命令都命中 {@link READONLY_BASH}(比 auto 安全名单窄:去掉测试 / 构建运行器、`printenv`
|
|
7
|
+
* 这类会把环境变量里的密钥打进上下文的命令);
|
|
8
|
+
* - 没有输出重定向(`/dev/null` 等设备除外);
|
|
9
|
+
* - 没有嵌套 shell(`sh -c`、`eval`、`xargs`、`find -exec`)与进程替换;
|
|
10
|
+
* - 段首没有环境赋值或包装命令(`GIT_EXTERNAL_DIFF=… git diff`、`env …`、`nohup …`)。
|
|
11
|
+
*
|
|
12
|
+
* 机密路径(`cat .env`)由 auto 规则层判为「要询问」,这里因此不算只读;deny 规则仍先于模式判定。
|
|
13
|
+
*/
|
|
14
|
+
import { analyzeBashForAuto, extractRedirects } from "./auto-safe.js";
|
|
15
|
+
import { collectNestedCommands, commandWords, gitArgv, shellWords } from "./dangerous.js";
|
|
16
|
+
import { splitShellSegments } from "./rules.js";
|
|
17
|
+
const spec = (text, forbid) => forbid === undefined ? { words: text.split(" ") } : { words: text.split(" "), forbid };
|
|
18
|
+
const GIT_OUTPUT = /^(--output(=.*)?|--ext-diff|--textconv|-O.*|--open-files-in-pager.*)$/;
|
|
19
|
+
/** 只读命令名单(每条在 readonly-bash.test.ts 有正反例)。 */
|
|
20
|
+
export const READONLY_BASH = [
|
|
21
|
+
...[
|
|
22
|
+
"ls",
|
|
23
|
+
"cat",
|
|
24
|
+
"head",
|
|
25
|
+
"tail",
|
|
26
|
+
"wc",
|
|
27
|
+
"stat",
|
|
28
|
+
"echo",
|
|
29
|
+
"printf",
|
|
30
|
+
"pwd",
|
|
31
|
+
"which",
|
|
32
|
+
"du",
|
|
33
|
+
"tree",
|
|
34
|
+
"basename",
|
|
35
|
+
"dirname",
|
|
36
|
+
"realpath",
|
|
37
|
+
"true",
|
|
38
|
+
"false",
|
|
39
|
+
"cd",
|
|
40
|
+
].map((name) => spec(name, name === "tree" ? /^-o.*$/ : undefined)),
|
|
41
|
+
spec("file", /^(-C|--compile)$/),
|
|
42
|
+
...["grep", "egrep", "fgrep"].map((name) => spec(name)),
|
|
43
|
+
spec("rg", /^--pre(=.*)?$/),
|
|
44
|
+
spec("fd", /^(-x|-X|--exec|--exec-batch)(=.*)?$/),
|
|
45
|
+
spec("find", /^-(exec|execdir|ok|okdir|delete|fprint0?|fprintf|fls)$/),
|
|
46
|
+
spec("jq", /^(-i|--in-place)$/),
|
|
47
|
+
...["status", "rev-parse", "blame", "ls-files"].map((sub) => spec(`git ${sub}`, GIT_OUTPUT)),
|
|
48
|
+
...["log", "show", "diff"].map((sub) => spec(`git ${sub}`, GIT_OUTPUT)),
|
|
49
|
+
spec("git branch", /^(-[A-Za-z]*[dDmMcCfuet][A-Za-z]*|--(delete|move|copy|force|set-upstream-to.*|unset-upstream|edit-description|track.*|no-track|create-reflog))$/),
|
|
50
|
+
];
|
|
51
|
+
/** auto 安全名单里没有、但属于只读子集的命令(交给 analyzeBashForAuto 当作追加项)。 */
|
|
52
|
+
const EXTRA_FOR_AUTO = ["tree", "jq", "fd"];
|
|
53
|
+
const DEVICE_TARGETS = /^(\/dev\/(null|stdout|stderr|tty)|NUL)$/i;
|
|
54
|
+
function base(word) {
|
|
55
|
+
return (word ?? "").split("/").pop() ?? "";
|
|
56
|
+
}
|
|
57
|
+
/** git 子命令之前只允许 `-C <dir>` 与 `--no-pager`(`-c` 可改 pager / diff 程序)。 */
|
|
58
|
+
function gitPrefixSafe(argv) {
|
|
59
|
+
for (let i = 1; i < argv.length; i++) {
|
|
60
|
+
const a = argv[i];
|
|
61
|
+
if (a === "-C")
|
|
62
|
+
i++;
|
|
63
|
+
else if (a === "--no-pager")
|
|
64
|
+
continue;
|
|
65
|
+
else
|
|
66
|
+
return !a.startsWith("-");
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
function matches(argv, entry) {
|
|
71
|
+
let words = argv;
|
|
72
|
+
if (entry.words[0] === "git") {
|
|
73
|
+
if (base(argv[0]) !== "git" || !gitPrefixSafe(argv))
|
|
74
|
+
return false;
|
|
75
|
+
words = ["git", ...(gitArgv(argv) ?? argv).slice(1)];
|
|
76
|
+
}
|
|
77
|
+
else if (argv[0] !== entry.words[0])
|
|
78
|
+
return false;
|
|
79
|
+
if (words.length < entry.words.length)
|
|
80
|
+
return false;
|
|
81
|
+
if (!entry.words.every((w, i) => words[i] === w))
|
|
82
|
+
return false;
|
|
83
|
+
const rest = words.slice(entry.words.length);
|
|
84
|
+
return entry.forbid === undefined || !rest.some((a) => entry.forbid.test(a));
|
|
85
|
+
}
|
|
86
|
+
/** 不是只读的原因;只读返回 undefined。 */
|
|
87
|
+
export function readonlyBashReason(command, options = {}) {
|
|
88
|
+
const cwd = options.cwd ?? process.cwd();
|
|
89
|
+
const nested = collectNestedCommands(command);
|
|
90
|
+
if (nested.commands.length > 0 || nested.tooDeep)
|
|
91
|
+
return "nested shell command";
|
|
92
|
+
const auto = analyzeBashForAuto(command, {
|
|
93
|
+
cwd,
|
|
94
|
+
projectRoot: options.projectRoot ?? cwd,
|
|
95
|
+
extraSafe: EXTRA_FOR_AUTO,
|
|
96
|
+
});
|
|
97
|
+
if (!auto.safe)
|
|
98
|
+
return auto.ask ?? auto.reason;
|
|
99
|
+
for (const segment of splitShellSegments(command)) {
|
|
100
|
+
const redirects = extractRedirects(segment);
|
|
101
|
+
const target = redirects.outputs.find((t) => !DEVICE_TARGETS.test(t));
|
|
102
|
+
if (target !== undefined)
|
|
103
|
+
return `output redirection (${target})`;
|
|
104
|
+
const raw = shellWords(redirects.rest.replace(/^[({\s]+/, ""));
|
|
105
|
+
const argv = commandWords(redirects.rest, true);
|
|
106
|
+
if (argv.length === 0)
|
|
107
|
+
return "empty command";
|
|
108
|
+
if (raw[0] !== argv[0])
|
|
109
|
+
return `environment assignment or wrapper before ${argv[0]}`;
|
|
110
|
+
if (!READONLY_BASH.some((entry) => matches(argv, entry)))
|
|
111
|
+
return `${base(argv[0])} is not in the read-only list`;
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
/** 每段命令都在 {@link READONLY_BASH} 且无重定向 / 命令替换 / 嵌套 shell。 */
|
|
116
|
+
export function isReadonlyBash(command, options = {}) {
|
|
117
|
+
return readonlyBashReason(command, options) === undefined;
|
|
118
|
+
}
|
|
@@ -72,6 +72,43 @@ export interface ApprovalRequestContext {
|
|
|
72
72
|
parentToolCallId?: string;
|
|
73
73
|
/** [W3-C0] 本会话已 read 过的路径(`ToolContext.readFiles`),由 `gateToolCall` 填入。 */
|
|
74
74
|
readFiles?: ReadonlySet<string>;
|
|
75
|
+
/** [W5-C0] 子 Agent 任务 id(对话框标注 `[task:<agent>]`)。 */
|
|
76
|
+
taskId?: string;
|
|
77
|
+
/**
|
|
78
|
+
* [W5-C0] 来自外部 Agent 的权限请求(docs/wave5-plan.md §5.3):只走 broker 链(宿主 → UI →
|
|
79
|
+
* 无人值守拒绝),auto 分类器与模型都不参与;RPC `permission_request` 原样带出。
|
|
80
|
+
*/
|
|
81
|
+
origin?: ExternalPermissionOrigin;
|
|
82
|
+
}
|
|
83
|
+
/** [W5-C0] 外部 Agent 权限选项的种类(ACP 词汇)。 */
|
|
84
|
+
export type ExternalPermissionOptionKind = "allow_once" | "allow_always" | "reject_once" | "reject_always";
|
|
85
|
+
/** [W5-C0] 外部 Agent 发来的权限请求(ACP `session/request_permission` 子集)。 */
|
|
86
|
+
export interface ExternalPermissionOrigin {
|
|
87
|
+
/** 外部 Agent id(`claude` / `codex` / `acp:<program>`)。 */
|
|
88
|
+
agent: string;
|
|
89
|
+
/** 外部 CLI 自己的会话 id。 */
|
|
90
|
+
sessionId: string;
|
|
91
|
+
toolCall: {
|
|
92
|
+
title: string;
|
|
93
|
+
kind: string;
|
|
94
|
+
locations?: string[];
|
|
95
|
+
inputSummary?: string;
|
|
96
|
+
};
|
|
97
|
+
options: {
|
|
98
|
+
optionId: string;
|
|
99
|
+
kind: ExternalPermissionOptionKind;
|
|
100
|
+
}[];
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* [W5-EG] `permission_request` 事件的发起方(`ApprovalRequestContext` 的线上子集,可 JSON 序列化):
|
|
104
|
+
* 只在来自子 Agent / 外部 Agent 时出现。
|
|
105
|
+
*/
|
|
106
|
+
export interface PermissionRequestContext {
|
|
107
|
+
/** > 0:来自 task 子 Agent(对话框标 `[task:<agent>]`)。 */
|
|
108
|
+
depth?: number;
|
|
109
|
+
taskId?: string;
|
|
110
|
+
/** 外部 Agent 的权限请求(对话框标 `[claude · 会话 abc1]`)。 */
|
|
111
|
+
origin?: ExternalPermissionOrigin;
|
|
75
112
|
}
|
|
76
113
|
/** [W3-C0] 预览涉及的路径(bash 的 rm / mv / 重定向目标,write / edit 的目标文件)。 */
|
|
77
114
|
export interface ActionPreviewTarget {
|
|
@@ -129,6 +166,11 @@ export interface PermissionVerdict {
|
|
|
129
166
|
* 的 decision(ask,无人值守时已是 deny)。不认识这个字段的调用方得到的就是保守结论。
|
|
130
167
|
*/
|
|
131
168
|
classify?: boolean;
|
|
169
|
+
/**
|
|
170
|
+
* [S2] 这次 bash 调用将经 OS 沙箱运行(docs/sandbox.md「第二阶段」)。default / auto-edit 下因此免审批时
|
|
171
|
+
* 为真;auto 下随 `classify` 一起交给分类器作为输入。
|
|
172
|
+
*/
|
|
173
|
+
sandboxed?: boolean;
|
|
132
174
|
}
|
|
133
175
|
export interface PermissionPipelineApi {
|
|
134
176
|
readonly mode: PermissionMode;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plan 扩展的装配(`cli/compose-extensions.ts` 的一行)。[W5-F]
|
|
3
|
+
*
|
|
4
|
+
* 交互 / line 模式由用户在输入框里回复审批(文本回复,正式审批框归 W5-U),TUI 且无宿主时显示一行
|
|
5
|
+
* 提示;print / rpc 缺省按 `plan.unattended`,RPC 客户端声明 `plans` 能力后改由客户端作答。
|
|
6
|
+
*/
|
|
7
|
+
import type { SessionExtensionFactory } from "../agent/session-extensions.js";
|
|
8
|
+
import type { ComposeExtensionDeps } from "../cli/compose-extensions.js";
|
|
9
|
+
export declare function planExtensionFor(deps: ComposeExtensionDeps): SessionExtensionFactory;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plan 扩展的装配(`cli/compose-extensions.ts` 的一行)。[W5-F]
|
|
3
|
+
*
|
|
4
|
+
* 交互 / line 模式由用户在输入框里回复审批(文本回复,正式审批框归 W5-U),TUI 且无宿主时显示一行
|
|
5
|
+
* 提示;print / rpc 缺省按 `plan.unattended`,RPC 客户端声明 `plans` 能力后改由客户端作答。
|
|
6
|
+
*/
|
|
7
|
+
import { createPlanExtensionFactory } from "../agent/session-plan.js";
|
|
8
|
+
export function planExtensionFor(deps) {
|
|
9
|
+
const { assembly } = deps;
|
|
10
|
+
const attended = assembly.mode === "interactive" || assembly.mode === "line";
|
|
11
|
+
return createPlanExtensionFactory({
|
|
12
|
+
...(assembly.config.plan !== undefined ? { config: assembly.config.plan } : {}),
|
|
13
|
+
// 组装表测试只给 config:其余材料按可缺省读取
|
|
14
|
+
...(assembly.paths?.dataDir !== undefined ? { dataDir: assembly.paths.dataDir } : {}),
|
|
15
|
+
attendance: attended && !assembly.unattended ? "text" : "unattended",
|
|
16
|
+
notice: assembly.mode === "interactive" && assembly.host?.handle === undefined,
|
|
17
|
+
log: deps.log,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 计划控制面的类型与登记表(RPC / SDK / 界面通过 `planController(session)` 使用)。[W5-F]
|
|
3
|
+
* 实现在 `agent/session-plan.ts`。
|
|
4
|
+
*/
|
|
5
|
+
import type { PlanData, PlanDecisionKind, TodoItemView } from "../agent/types.js";
|
|
6
|
+
import type { PlanConfig } from "../config/types-w5.js";
|
|
7
|
+
import type { PermissionMode } from "../permissions/types.js";
|
|
8
|
+
/**
|
|
9
|
+
* 计划审批由谁回答:`text` 交互模式(回复 1 / 2 / 3 批准,其它文字即修改意见);`client` RPC 客户端
|
|
10
|
+
* 声明了 `plans` 能力(`plan_response`);`callback` SDK `plan.onProposed`;`unattended` 按
|
|
11
|
+
* `plan.unattended`。
|
|
12
|
+
*/
|
|
13
|
+
export type PlanAttendance = "text" | "client" | "callback" | "unattended";
|
|
14
|
+
/** 审批回答(RPC `plan_response` 同构)。 */
|
|
15
|
+
export interface PlanResponse {
|
|
16
|
+
planId: string;
|
|
17
|
+
decision: PlanDecisionKind;
|
|
18
|
+
mode?: PermissionMode;
|
|
19
|
+
feedback?: string;
|
|
20
|
+
editedMarkdown?: string;
|
|
21
|
+
}
|
|
22
|
+
/** SDK 回调的返回:不带 planId 的回答;undefined = 不作答(计划留在 proposed)。 */
|
|
23
|
+
export type PlanDecision = Omit<PlanResponse, "planId">;
|
|
24
|
+
export type PlanDecisionHandler = (plan: PlanData) => Promise<PlanDecision | undefined>;
|
|
25
|
+
export interface PlanResponseResult {
|
|
26
|
+
planId: string;
|
|
27
|
+
decision: PlanDecisionKind;
|
|
28
|
+
/** 批准后的执行模式。 */
|
|
29
|
+
mode?: PermissionMode;
|
|
30
|
+
/** 最终计划(编辑过则是新版本)。 */
|
|
31
|
+
plan: PlanData;
|
|
32
|
+
/** `approve_fresh`:新会话的首条用户消息(调用方新建会话后先 `adopt(plan)` 再发它)。 */
|
|
33
|
+
freshPrompt?: string;
|
|
34
|
+
}
|
|
35
|
+
/** RPC / SDK / 界面使用的计划控制面(`planController(session)` 取得)。 */
|
|
36
|
+
export interface PlanController {
|
|
37
|
+
/** 分支上最近的计划(任意状态);没有为 null。 */
|
|
38
|
+
current(): PlanData | null;
|
|
39
|
+
get(planId?: string): PlanData | null;
|
|
40
|
+
/** 等待审批的计划(最近一条且 status = proposed)。 */
|
|
41
|
+
pending(): PlanData | undefined;
|
|
42
|
+
todos(): TodoItemView[];
|
|
43
|
+
/** 进入 plan 前的模式(不在 plan 时 undefined)。 */
|
|
44
|
+
prePlanMode(): PermissionMode | undefined;
|
|
45
|
+
respond(response: PlanResponse): Promise<PlanResponseResult>;
|
|
46
|
+
/** `/plan approve` 的回退:回复里没有计划块时把上一条回复当作计划提交审批。 */
|
|
47
|
+
proposeFromLastReply(): PlanData | undefined;
|
|
48
|
+
/** 「新上下文执行」的新会话:记下获批计划并生成 todo。 */
|
|
49
|
+
adopt(plan: PlanData): void;
|
|
50
|
+
setAttendance(kind: PlanAttendance, handler?: PlanDecisionHandler): void;
|
|
51
|
+
}
|
|
52
|
+
export interface PlanExtensionOptions {
|
|
53
|
+
config?: PlanConfig;
|
|
54
|
+
/** 计划文件缺省目录 `<dataDir>/plans`;不给且没有 `plan.directory` 时不写文件。 */
|
|
55
|
+
dataDir?: string;
|
|
56
|
+
/** 缺省 unattended。 */
|
|
57
|
+
attendance?: PlanAttendance;
|
|
58
|
+
/**
|
|
59
|
+
* 交互模式(无宿主)在计划待审批时显示一行文本提示:以不落盘的 `message_start`(custom、
|
|
60
|
+
* `display:true`)事件发出,TUI 显示为提示行。正式的审批框归 W5-U。
|
|
61
|
+
*/
|
|
62
|
+
notice?: boolean;
|
|
63
|
+
log?(level: "debug" | "info" | "warn" | "error", message: string): void;
|
|
64
|
+
}
|
|
65
|
+
/** 扩展构造时登记(键 = 会话对象),dispose 时撤下。 */
|
|
66
|
+
export declare function registerPlanController(session: object, controller: PlanController | undefined): void;
|
|
67
|
+
/** 会话的计划控制面;会话没有装 plan 扩展(子会话、SDK 自装配)时 undefined。 */
|
|
68
|
+
export declare function planController(session: object): PlanController | undefined;
|
|
69
|
+
/** 文本提示行(只发事件,不落盘、不进上下文)。 */
|
|
70
|
+
export declare const PLAN_NOTICE_TYPE = "ama.plan_notice";
|
|
71
|
+
export declare const APPROVE_REPLY: Readonly<Record<string, PermissionMode | "pre">>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 计划控制面的类型与登记表(RPC / SDK / 界面通过 `planController(session)` 使用)。[W5-F]
|
|
3
|
+
* 实现在 `agent/session-plan.ts`。
|
|
4
|
+
*/
|
|
5
|
+
const controllers = new WeakMap();
|
|
6
|
+
/** 扩展构造时登记(键 = 会话对象),dispose 时撤下。 */
|
|
7
|
+
export function registerPlanController(session, controller) {
|
|
8
|
+
if (controller === undefined)
|
|
9
|
+
controllers.delete(session);
|
|
10
|
+
else
|
|
11
|
+
controllers.set(session, controller);
|
|
12
|
+
}
|
|
13
|
+
/** 会话的计划控制面;会话没有装 plan 扩展(子会话、SDK 自装配)时 undefined。 */
|
|
14
|
+
export function planController(session) {
|
|
15
|
+
return controllers.get(session);
|
|
16
|
+
}
|
|
17
|
+
/** 文本提示行(只发事件,不落盘、不进上下文)。 */
|
|
18
|
+
export const PLAN_NOTICE_TYPE = "ama.plan_notice";
|
|
19
|
+
export const APPROVE_REPLY = {
|
|
20
|
+
"1": "pre",
|
|
21
|
+
y: "pre",
|
|
22
|
+
yes: "pre",
|
|
23
|
+
approve: "pre",
|
|
24
|
+
批准: "pre",
|
|
25
|
+
"2": "auto-edit",
|
|
26
|
+
"3": "auto",
|
|
27
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [W5-Z] `[DONE:<步骤>]` 文本交接(docs/plan.md「交接」,D20 / R6 的回退):活动工具集里没有 `todo` 时,
|
|
3
|
+
* 批准计划的交接消息请模型每完成一步在回复里单独一行写 `[DONE:S1]`;ama 在回合结束时读这些标记,
|
|
4
|
+
* 把计划生成的待办(`planStep`)标为 done、下一个未完成项转 in_progress,照常落 `ama.todo` 与发
|
|
5
|
+
* `todo_updated`,界面与 RPC 的进度显示不依赖 todo 工具。
|
|
6
|
+
*/
|
|
7
|
+
import type { AgentMessage } from "../session/types.js";
|
|
8
|
+
import type { TodoItem } from "../tools/todo.js";
|
|
9
|
+
/** 文本里单独成行的 `[DONE:<id>]`(去重、按出现顺序)。 */
|
|
10
|
+
export declare function parseDoneMarkers(text: string): string[];
|
|
11
|
+
/**
|
|
12
|
+
* 按标记更新待办:只认带 `planStep` 的条目(按 `planStep` 或 `id` 匹配,大小写不敏感)。有变化时返回
|
|
13
|
+
* 新清单(被标记的 done;若此时没有 in_progress,第一个 pending 转 in_progress),否则 undefined。
|
|
14
|
+
*/
|
|
15
|
+
export declare function applyDoneMarkers(items: readonly TodoItem[], ids: readonly string[]): TodoItem[] | undefined;
|
|
16
|
+
/** 一条助手回复里的标记 → 新待办清单(不是助手消息、没有标记或没有变化时 undefined)。 */
|
|
17
|
+
export declare function todosFromDoneMarkers(message: AgentMessage, items: readonly TodoItem[]): TodoItem[] | undefined;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* [W5-Z] `[DONE:<步骤>]` 文本交接(docs/plan.md「交接」,D20 / R6 的回退):活动工具集里没有 `todo` 时,
|
|
3
|
+
* 批准计划的交接消息请模型每完成一步在回复里单独一行写 `[DONE:S1]`;ama 在回合结束时读这些标记,
|
|
4
|
+
* 把计划生成的待办(`planStep`)标为 done、下一个未完成项转 in_progress,照常落 `ama.todo` 与发
|
|
5
|
+
* `todo_updated`,界面与 RPC 的进度显示不依赖 todo 工具。
|
|
6
|
+
*/
|
|
7
|
+
const MARKER = /^[ \t]*\[DONE:\s*([A-Za-z0-9_.-]{1,32})\s*\][ \t]*$/gm;
|
|
8
|
+
/** 文本里单独成行的 `[DONE:<id>]`(去重、按出现顺序)。 */
|
|
9
|
+
export function parseDoneMarkers(text) {
|
|
10
|
+
const ids = [];
|
|
11
|
+
for (const match of text.matchAll(MARKER)) {
|
|
12
|
+
const id = match[1];
|
|
13
|
+
if (!ids.includes(id))
|
|
14
|
+
ids.push(id);
|
|
15
|
+
}
|
|
16
|
+
return ids;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 按标记更新待办:只认带 `planStep` 的条目(按 `planStep` 或 `id` 匹配,大小写不敏感)。有变化时返回
|
|
20
|
+
* 新清单(被标记的 done;若此时没有 in_progress,第一个 pending 转 in_progress),否则 undefined。
|
|
21
|
+
*/
|
|
22
|
+
export function applyDoneMarkers(items, ids) {
|
|
23
|
+
if (ids.length === 0)
|
|
24
|
+
return undefined;
|
|
25
|
+
const wanted = new Set(ids.map((id) => id.toLowerCase()));
|
|
26
|
+
let changed = false;
|
|
27
|
+
const next = items.map((item) => {
|
|
28
|
+
const step = item.planStep;
|
|
29
|
+
if (step === undefined || item.status === "done")
|
|
30
|
+
return { ...item };
|
|
31
|
+
if (!wanted.has(step.toLowerCase()) && !wanted.has(item.id.toLowerCase()))
|
|
32
|
+
return { ...item };
|
|
33
|
+
changed = true;
|
|
34
|
+
return { ...item, status: "done" };
|
|
35
|
+
});
|
|
36
|
+
if (!changed)
|
|
37
|
+
return undefined;
|
|
38
|
+
if (!next.some((item) => item.status === "in_progress")) {
|
|
39
|
+
const first = next.find((item) => item.status === "pending");
|
|
40
|
+
if (first !== undefined)
|
|
41
|
+
first.status = "in_progress";
|
|
42
|
+
}
|
|
43
|
+
return next;
|
|
44
|
+
}
|
|
45
|
+
/** 一条助手回复里的标记 → 新待办清单(不是助手消息、没有标记或没有变化时 undefined)。 */
|
|
46
|
+
export function todosFromDoneMarkers(message, items) {
|
|
47
|
+
if (message.role !== "assistant" || items.length === 0)
|
|
48
|
+
return undefined;
|
|
49
|
+
const text = message.content.map((b) => (b.type === "text" ? b.text : "")).join("\n");
|
|
50
|
+
return applyDoneMarkers(items, parseDoneMarkers(text));
|
|
51
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<proposed_plan>` 块的提取与步骤解析(docs/wave5-plan.md §6.3、D19)。[W5-F] 纯函数。
|
|
3
|
+
*
|
|
4
|
+
* - 只认开、闭标签**各占一行**(允许行首尾空白)、且不在代码围栏(``` / ~~~)内的块;
|
|
5
|
+
* 不闭合的块忽略;多个完整块取最后一个。
|
|
6
|
+
* - 步骤:优先 `- [ ] S1 …` 清单(任意位置的顶层清单项);没有清单时取「步骤 / Steps」小节里的
|
|
7
|
+
* 编号列表(没有该小节则取全部顶层编号列表)。行内标注 `[depends: S1, S2]`、`[agent: codex]`
|
|
8
|
+
* 解析进 `dependsOn` / `agent` 并从文字里去掉。上限 {@link MAX_PLAN_STEPS} 条,超出截断。
|
|
9
|
+
*/
|
|
10
|
+
import type { PlanStep } from "../agent/types.js";
|
|
11
|
+
export declare const PLAN_OPEN_TAG = "<proposed_plan>";
|
|
12
|
+
export declare const PLAN_CLOSE_TAG = "</proposed_plan>";
|
|
13
|
+
export declare const MAX_PLAN_STEPS = 30;
|
|
14
|
+
export interface ExtractedPlan {
|
|
15
|
+
/** 块内正文(不含标签),首尾空行去掉。 */
|
|
16
|
+
markdown: string;
|
|
17
|
+
steps: PlanStep[];
|
|
18
|
+
/** 步骤超过上限被截断。 */
|
|
19
|
+
truncated?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** 从一条回复里取最后一个完整的 `<proposed_plan>` 块;没有返回 undefined。 */
|
|
22
|
+
export declare function extractProposedPlan(text: string): ExtractedPlan | undefined;
|
|
23
|
+
/** 计划正文 → 步骤(用户编辑过的全文、「把上一条回复当作计划」也走这里)。 */
|
|
24
|
+
export declare function planFromMarkdown(markdown: string): ExtractedPlan;
|
|
25
|
+
/** 计划正文里的步骤;没有可识别的列表返回空数组。 */
|
|
26
|
+
export declare function parsePlanSteps(markdown: string): {
|
|
27
|
+
steps: PlanStep[];
|
|
28
|
+
truncated: boolean;
|
|
29
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<proposed_plan>` 块的提取与步骤解析(docs/wave5-plan.md §6.3、D19)。[W5-F] 纯函数。
|
|
3
|
+
*
|
|
4
|
+
* - 只认开、闭标签**各占一行**(允许行首尾空白)、且不在代码围栏(``` / ~~~)内的块;
|
|
5
|
+
* 不闭合的块忽略;多个完整块取最后一个。
|
|
6
|
+
* - 步骤:优先 `- [ ] S1 …` 清单(任意位置的顶层清单项);没有清单时取「步骤 / Steps」小节里的
|
|
7
|
+
* 编号列表(没有该小节则取全部顶层编号列表)。行内标注 `[depends: S1, S2]`、`[agent: codex]`
|
|
8
|
+
* 解析进 `dependsOn` / `agent` 并从文字里去掉。上限 {@link MAX_PLAN_STEPS} 条,超出截断。
|
|
9
|
+
*/
|
|
10
|
+
export const PLAN_OPEN_TAG = "<proposed_plan>";
|
|
11
|
+
export const PLAN_CLOSE_TAG = "</proposed_plan>";
|
|
12
|
+
export const MAX_PLAN_STEPS = 30;
|
|
13
|
+
const FENCE = /^\s*(```|~~~)/;
|
|
14
|
+
/** 从一条回复里取最后一个完整的 `<proposed_plan>` 块;没有返回 undefined。 */
|
|
15
|
+
export function extractProposedPlan(text) {
|
|
16
|
+
const lines = text.split(/\r?\n/);
|
|
17
|
+
let fence;
|
|
18
|
+
let open;
|
|
19
|
+
let last;
|
|
20
|
+
for (const [i, line] of lines.entries()) {
|
|
21
|
+
const marker = FENCE.exec(line)?.[1];
|
|
22
|
+
if (marker !== undefined) {
|
|
23
|
+
if (fence === undefined)
|
|
24
|
+
fence = marker;
|
|
25
|
+
else if (marker === fence)
|
|
26
|
+
fence = undefined;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (fence !== undefined)
|
|
30
|
+
continue;
|
|
31
|
+
const trimmed = line.trim();
|
|
32
|
+
if (trimmed === PLAN_OPEN_TAG)
|
|
33
|
+
open = i;
|
|
34
|
+
else if (trimmed === PLAN_CLOSE_TAG && open !== undefined) {
|
|
35
|
+
last = { from: open + 1, to: i };
|
|
36
|
+
open = undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (last === undefined)
|
|
40
|
+
return undefined;
|
|
41
|
+
const markdown = trimBlankLines(lines.slice(last.from, last.to)).join("\n");
|
|
42
|
+
if (markdown.trim() === "")
|
|
43
|
+
return undefined;
|
|
44
|
+
return planFromMarkdown(markdown);
|
|
45
|
+
}
|
|
46
|
+
/** 计划正文 → 步骤(用户编辑过的全文、「把上一条回复当作计划」也走这里)。 */
|
|
47
|
+
export function planFromMarkdown(markdown) {
|
|
48
|
+
const parsed = parsePlanSteps(markdown);
|
|
49
|
+
const plan = { markdown, steps: parsed.steps };
|
|
50
|
+
if (parsed.truncated)
|
|
51
|
+
plan.truncated = true;
|
|
52
|
+
return plan;
|
|
53
|
+
}
|
|
54
|
+
function trimBlankLines(lines) {
|
|
55
|
+
let from = 0;
|
|
56
|
+
let to = lines.length;
|
|
57
|
+
while (from < to && lines[from].trim() === "")
|
|
58
|
+
from++;
|
|
59
|
+
while (to > from && lines[to - 1].trim() === "")
|
|
60
|
+
to--;
|
|
61
|
+
return lines.slice(from, to);
|
|
62
|
+
}
|
|
63
|
+
const CHECKBOX = /^ {0,1}[-*+]\s+\[[ xX~]\]\s+(.+)$/;
|
|
64
|
+
const NUMBERED = /^ {0,1}(\d{1,3})[.)]\s+(.+)$/;
|
|
65
|
+
const STEP_ID = /^(S\d{1,3})\b[.:)]?\s*/;
|
|
66
|
+
const HEADING = /^#{1,6}\s+(.*)$/;
|
|
67
|
+
const STEPS_HEADING = /步骤|steps|implementation|实施|执行/i;
|
|
68
|
+
/** 计划正文里的步骤;没有可识别的列表返回空数组。 */
|
|
69
|
+
export function parsePlanSteps(markdown) {
|
|
70
|
+
const lines = markdown.split(/\r?\n/);
|
|
71
|
+
const outside = linesOutsideFences(lines);
|
|
72
|
+
const checkbox = outside.flatMap((line) => {
|
|
73
|
+
const m = CHECKBOX.exec(line);
|
|
74
|
+
return m === null ? [] : [m[1]];
|
|
75
|
+
});
|
|
76
|
+
let raw;
|
|
77
|
+
if (checkbox.length > 0) {
|
|
78
|
+
raw = checkbox.map(splitId);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const section = stepsSection(outside);
|
|
82
|
+
raw = section.flatMap((line) => {
|
|
83
|
+
const m = NUMBERED.exec(line);
|
|
84
|
+
if (m === null)
|
|
85
|
+
return [];
|
|
86
|
+
const item = splitId(m[2]);
|
|
87
|
+
return [{ id: item.id ?? `S${m[1]}`, text: item.text }];
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const truncated = raw.length > MAX_PLAN_STEPS;
|
|
91
|
+
const seen = new Set();
|
|
92
|
+
const steps = raw.slice(0, MAX_PLAN_STEPS).map((item, i) => {
|
|
93
|
+
let id = item.id ?? `S${i + 1}`;
|
|
94
|
+
if (seen.has(id))
|
|
95
|
+
id = `S${i + 1}`;
|
|
96
|
+
while (seen.has(id))
|
|
97
|
+
id = `${id}'`;
|
|
98
|
+
seen.add(id);
|
|
99
|
+
return annotate(id, item.text);
|
|
100
|
+
});
|
|
101
|
+
return { steps, truncated };
|
|
102
|
+
}
|
|
103
|
+
function linesOutsideFences(lines) {
|
|
104
|
+
const out = [];
|
|
105
|
+
let fence;
|
|
106
|
+
for (const line of lines) {
|
|
107
|
+
const marker = FENCE.exec(line)?.[1];
|
|
108
|
+
if (marker !== undefined) {
|
|
109
|
+
if (fence === undefined)
|
|
110
|
+
fence = marker;
|
|
111
|
+
else if (marker === fence)
|
|
112
|
+
fence = undefined;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (fence === undefined)
|
|
116
|
+
out.push(line);
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
/** 「步骤 / Steps」小节的行;没有该小节返回全部行。 */
|
|
121
|
+
function stepsSection(lines) {
|
|
122
|
+
const start = lines.findIndex((line) => {
|
|
123
|
+
const m = HEADING.exec(line.trim());
|
|
124
|
+
return m !== null && STEPS_HEADING.test(m[1]);
|
|
125
|
+
});
|
|
126
|
+
if (start < 0)
|
|
127
|
+
return lines;
|
|
128
|
+
const level = (/^#+/.exec(lines[start].trim())?.[0] ?? "#").length;
|
|
129
|
+
const out = [];
|
|
130
|
+
for (const line of lines.slice(start + 1)) {
|
|
131
|
+
const m = /^(#+)\s/.exec(line.trim());
|
|
132
|
+
if (m !== null && m[1].length <= level)
|
|
133
|
+
break;
|
|
134
|
+
out.push(line);
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
function splitId(text) {
|
|
139
|
+
const m = STEP_ID.exec(text.trim());
|
|
140
|
+
return m === null ? { text: text.trim() } : { id: m[1], text: text.trim().slice(m[0].length) };
|
|
141
|
+
}
|
|
142
|
+
const ANNOTATION = /\[(depends|agent)\s*:\s*([^\]]*)\]/gi;
|
|
143
|
+
function annotate(id, text) {
|
|
144
|
+
const step = { id, text: "" };
|
|
145
|
+
const cleaned = text.replace(ANNOTATION, (_all, key, value) => {
|
|
146
|
+
if (key.toLowerCase() === "depends") {
|
|
147
|
+
const ids = value
|
|
148
|
+
.split(/[,,\s]+/)
|
|
149
|
+
.map((v) => v.trim())
|
|
150
|
+
.filter((v) => v !== "");
|
|
151
|
+
if (ids.length > 0)
|
|
152
|
+
step.dependsOn = ids;
|
|
153
|
+
}
|
|
154
|
+
else if (value.trim() !== "")
|
|
155
|
+
step.agent = value.trim();
|
|
156
|
+
return "";
|
|
157
|
+
});
|
|
158
|
+
step.text = cleaned.replace(/\s{2,}/g, " ").trim() || id;
|
|
159
|
+
return step;
|
|
160
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* plan 模式注入给模型的文本(docs/wave5-plan.md §6.2、D18)。[W5-F]
|
|
3
|
+
*
|
|
4
|
+
* 全部以 `custom_message`(`display:false`)追加在尾部,投影成 user 消息,不碰 system 节与工具表
|
|
5
|
+
* (design §9.1)。文本是给模型看的,用英文;界面文案不在这里。
|
|
6
|
+
*/
|
|
7
|
+
import type { PlanData } from "../agent/types.js";
|
|
8
|
+
import type { AgentMessage } from "../session/types.js";
|
|
9
|
+
export declare const PLAN_MODE_TYPE = "ama.plan_mode";
|
|
10
|
+
export declare const PLAN_MODE_EXIT_TYPE = "ama.plan_mode_exit";
|
|
11
|
+
export declare const PLAN_APPROVED_TYPE = "ama.plan_approved";
|
|
12
|
+
/** 完整版(约 400 token):进入 plan 后首个提示、每第 5 次提醒、压缩之后。 */
|
|
13
|
+
export declare const PLAN_MODE_FULL = "Plan mode is active. You may read and search, run read-only shell commands (ls, cat, grep, rg, find, git status/log/show/diff, \u2026) and start read-only subagents with task. Do not modify files, run builds or tests, or change any state; write/execute tools are refused in this mode.\n\nHow to work:\n1. Explore first. Answer questions from the repository yourself instead of asking the user; ask only about decisions that the code cannot settle.\n2. When you are ready, write the plan as one block, with the tags on their own lines:\n\n<proposed_plan>\n# <title>\n\n## Background\n1\u20133 sentences: the goal and the key facts you found.\n\n## Steps\n- [ ] S1 <action> (files: path/a.ts)\n- [ ] S2 <action> [depends: S1] [agent: <name>]\n\n## Verification\n- <command or check>\n\n## Assumptions and risks\n- <defaults you chose for open questions>\n</proposed_plan>\n\nRules for the plan:\n- Make it specific enough that someone else could carry it out without making further decisions. List the key files; describe a repeated change once.\n- Always include a Verification section.\n- At most one <proposed_plan> block per reply; to revise, rewrite the whole block.\n- Do not ask \"is this OK?\" in prose: the plan block itself is shown to the user for approval.\n- If the user asks you to make changes while plan mode is active, treat it as a request to plan how to make them.\n- Do not use the todo tool to record the plan; the task list is created from the steps when the plan is approved.";
|
|
14
|
+
/** 简版:两次完整版之间每 5 个提示一次。 */
|
|
15
|
+
export declare const PLAN_MODE_BRIEF = "Plan mode is still active: read-only exploration only. Finish with a <proposed_plan> block (Background, Steps as - [ ] S1 \u2026, Verification, Assumptions and risks).";
|
|
16
|
+
/** 手动退出 plan(没有获批的计划)。 */
|
|
17
|
+
export declare const PLAN_MODE_EXIT = "Plan mode has ended. The read-only restrictions no longer apply; the current permission mode decides what you may do.";
|
|
18
|
+
/** 获批交接时开启新回合的用户消息(之后紧跟 `ama.plan_approved`)。 */
|
|
19
|
+
export declare const PLAN_APPROVED_PROMPT = "The plan is approved. Go ahead.";
|
|
20
|
+
/**
|
|
21
|
+
* 提醒节奏:plan 模式下第 n 个提示(从 1 起)。每 5 个提示提醒一次(第 1、6、11 … 个),
|
|
22
|
+
* 第 1、6、11 … 次提醒里每第 5 次用完整版(即第 1、26、51 … 个提示)。
|
|
23
|
+
*/
|
|
24
|
+
export declare function reminderKind(promptIndex: number): "full" | "brief" | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* 交接时怎么记进度:活动工具集里有 `todo` 就用 `todo update`;没有(D20 未过门,`default` 预设不含 todo)
|
|
27
|
+
* 时请模型在回复里单独一行写 `[DONE:<步骤>]`,由 `done-markers.ts` 读回待办([W5-Z])。
|
|
28
|
+
*/
|
|
29
|
+
export interface HandoffOptions {
|
|
30
|
+
todoTool: boolean;
|
|
31
|
+
}
|
|
32
|
+
/** 交接消息:计划已批准 + plan 模式结束 + 计划全文 + 文件路径 + 进度记法。 */
|
|
33
|
+
export declare function planApprovedText(plan: Pick<PlanData, "markdown" | "filePath" | "steps">, options?: HandoffOptions): string;
|
|
34
|
+
/** 「批准,在新上下文执行」:新会话的首条用户消息。 */
|
|
35
|
+
export declare function freshContextPrompt(plan: Pick<PlanData, "markdown" | "filePath" | "steps">, options?: HandoffOptions): string;
|
|
36
|
+
export declare function planMessage(customType: string, content: string): AgentMessage;
|