@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
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 一次性打印模式兜底(docs/wave5-plan.md §5.1 oneshot.ts)。[W5-E]
|
|
3
|
+
*
|
|
4
|
+
* `claude -p --output-format json`、`codex exec --json`、`gemini -p --output-format stream-json`:
|
|
5
|
+
* **不能审批**(能力 `permissions: "none"`),所以只在只读任务下用——open 时模式不是
|
|
6
|
+
* plan / allowlist 直接拒绝;子进程一律以只读方式启动(Claude `--permission-mode plan
|
|
7
|
+
* --permission-prompts none`,Codex `sandbox_mode="read-only"` + `approval_policy="never"`,
|
|
8
|
+
* Gemini `--approval-mode default`,无头时需要批准的工具不可用;未实测)。每回合起一个进程,提示从 stdin 给(Gemini 走 `-p` 参数);
|
|
9
|
+
* 续聊用各 CLI 自己的 resume。中断 = 结束进程树。
|
|
10
|
+
*/
|
|
11
|
+
import { randomUUID } from "node:crypto";
|
|
12
|
+
import { AmaError } from "../../errors.js";
|
|
13
|
+
import { createLineReader } from "../../modes/rpc/jsonl.js";
|
|
14
|
+
import { isReadOnlyMode, probeCandidate, spawnOf } from "../base.js";
|
|
15
|
+
import { stderrHint } from "../process.js";
|
|
16
|
+
import { TurnCollector, oneLine } from "../turn.js";
|
|
17
|
+
import { claudeStopReason } from "./claude-normalize.js";
|
|
18
|
+
export function oneshotArgs(dialect, options, session, prompt) {
|
|
19
|
+
switch (dialect) {
|
|
20
|
+
case "claude": {
|
|
21
|
+
const args = [
|
|
22
|
+
"-p",
|
|
23
|
+
"--output-format",
|
|
24
|
+
"json",
|
|
25
|
+
"--permission-mode",
|
|
26
|
+
"plan",
|
|
27
|
+
"--permission-prompts",
|
|
28
|
+
"none",
|
|
29
|
+
];
|
|
30
|
+
args.push(session.resume ? "--resume" : "--session-id", session.id);
|
|
31
|
+
if (options.model !== undefined)
|
|
32
|
+
args.push("--model", options.model);
|
|
33
|
+
if (options.budgetUsd !== undefined)
|
|
34
|
+
args.push("--max-budget-usd", String(options.budgetUsd));
|
|
35
|
+
return args;
|
|
36
|
+
}
|
|
37
|
+
case "codex": {
|
|
38
|
+
const common = ["--json", "-c", 'sandbox_mode="read-only"', "-c", 'approval_policy="never"'];
|
|
39
|
+
if (options.model !== undefined)
|
|
40
|
+
common.push("-m", options.model);
|
|
41
|
+
return session.resume
|
|
42
|
+
? ["exec", "resume", session.id, ...common, "-"]
|
|
43
|
+
: ["exec", ...common, "-"];
|
|
44
|
+
}
|
|
45
|
+
case "gemini": {
|
|
46
|
+
// default:无头模式下需要批准的工具直接不可用(Gemini 没有交互审批通道)
|
|
47
|
+
const args = ["--output-format", "stream-json", "--approval-mode", "default"];
|
|
48
|
+
if (options.model !== undefined)
|
|
49
|
+
args.push("--model", options.model);
|
|
50
|
+
return [...args, "-p", prompt];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export class OneshotDriver {
|
|
55
|
+
agentId;
|
|
56
|
+
candidate;
|
|
57
|
+
deps;
|
|
58
|
+
kind = "oneshot";
|
|
59
|
+
constructor(agentId, candidate, deps = {}) {
|
|
60
|
+
this.agentId = agentId;
|
|
61
|
+
this.candidate = candidate;
|
|
62
|
+
this.deps = deps;
|
|
63
|
+
}
|
|
64
|
+
probe() {
|
|
65
|
+
return probeCandidate(this.candidate, this.deps);
|
|
66
|
+
}
|
|
67
|
+
async open(options) {
|
|
68
|
+
if (!isReadOnlyMode(options.mode))
|
|
69
|
+
throw new AmaError("agent_mode_unsupported", `${this.agentId} 只有一次性打印模式可用,它不能审批,只能用于只读(plan)任务`);
|
|
70
|
+
const probed = await probeCandidate(this.candidate, this.deps);
|
|
71
|
+
const dialect = this.candidate.oneshot ?? "claude";
|
|
72
|
+
return new OneshotSession(this.agentId, dialect, probed.path ?? this.candidate.program, options, this.deps);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
class OneshotSession {
|
|
76
|
+
agentId;
|
|
77
|
+
dialect;
|
|
78
|
+
program;
|
|
79
|
+
options;
|
|
80
|
+
deps;
|
|
81
|
+
id;
|
|
82
|
+
resumable;
|
|
83
|
+
transport;
|
|
84
|
+
interrupted = false;
|
|
85
|
+
closed = false;
|
|
86
|
+
constructor(agentId, dialect, program, options, deps) {
|
|
87
|
+
this.agentId = agentId;
|
|
88
|
+
this.dialect = dialect;
|
|
89
|
+
this.program = program;
|
|
90
|
+
this.options = options;
|
|
91
|
+
this.deps = deps;
|
|
92
|
+
this.id = options.resume ?? (dialect === "claude" ? randomUUID() : "");
|
|
93
|
+
this.resumable = options.resume !== undefined;
|
|
94
|
+
}
|
|
95
|
+
get sessionId() {
|
|
96
|
+
return this.id;
|
|
97
|
+
}
|
|
98
|
+
async prompt(content, hooks) {
|
|
99
|
+
if (this.closed)
|
|
100
|
+
throw new AmaError("agent_closed", `${this.agentId} 会话已关闭`);
|
|
101
|
+
if (this.transport !== undefined)
|
|
102
|
+
throw new AmaError("busy", `${this.agentId} 正在运行`);
|
|
103
|
+
const turn = new TurnCollector((e) => hooks.onEvent(e));
|
|
104
|
+
const text = content.map((b) => (b.type === "text" ? b.text : "")).join("\n");
|
|
105
|
+
if (content.some((b) => b.type === "image"))
|
|
106
|
+
turn.push({
|
|
107
|
+
type: "notice",
|
|
108
|
+
level: "warn",
|
|
109
|
+
text: `${this.agentId} 一次性模式不传图片,已省略`,
|
|
110
|
+
});
|
|
111
|
+
const resume = this.resumable && this.id !== "";
|
|
112
|
+
const transport = spawnOf(this.deps)({
|
|
113
|
+
program: this.program,
|
|
114
|
+
args: oneshotArgs(this.dialect, this.options, { id: this.id, resume }, text),
|
|
115
|
+
cwd: this.options.cwd,
|
|
116
|
+
env: this.options.env,
|
|
117
|
+
});
|
|
118
|
+
this.transport = transport;
|
|
119
|
+
this.interrupted = false;
|
|
120
|
+
let claude;
|
|
121
|
+
let failure;
|
|
122
|
+
const done = new Promise((resolve) => createLineReader(transport.stdout, (line) => {
|
|
123
|
+
let msg;
|
|
124
|
+
try {
|
|
125
|
+
msg = JSON.parse(line);
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
if (this.dialect === "claude") {
|
|
131
|
+
if (msg["type"] === "result")
|
|
132
|
+
claude = msg;
|
|
133
|
+
}
|
|
134
|
+
else if (this.dialect === "codex")
|
|
135
|
+
failure = this.onCodex(msg, turn) ?? failure;
|
|
136
|
+
else
|
|
137
|
+
failure = this.onGemini(msg, turn) ?? failure;
|
|
138
|
+
}, resolve));
|
|
139
|
+
if (this.dialect !== "gemini")
|
|
140
|
+
transport.stdin.write(text);
|
|
141
|
+
transport.stdin.end();
|
|
142
|
+
try {
|
|
143
|
+
await done;
|
|
144
|
+
const code = await transport.exited;
|
|
145
|
+
if (this.dialect === "claude")
|
|
146
|
+
return this.claudeResult(claude, turn, code, transport);
|
|
147
|
+
if (this.interrupted)
|
|
148
|
+
return turn.result("cancelled");
|
|
149
|
+
if (failure !== undefined || code !== 0)
|
|
150
|
+
throw new AmaError("agent_failed", `${this.agentId} 失败:${oneLine(failure ?? (stderrHint(transport) || `退出码 ${String(code)}`), 300)}`);
|
|
151
|
+
this.resumable = this.dialect !== "gemini" && this.id !== "";
|
|
152
|
+
return turn.result("end_turn");
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
this.transport = undefined;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async cancel() {
|
|
159
|
+
if (this.transport === undefined)
|
|
160
|
+
return;
|
|
161
|
+
this.interrupted = true;
|
|
162
|
+
await this.transport.terminate(0);
|
|
163
|
+
}
|
|
164
|
+
async close() {
|
|
165
|
+
this.closed = true;
|
|
166
|
+
await this.cancel();
|
|
167
|
+
}
|
|
168
|
+
claudeResult(result, turn, code, transport) {
|
|
169
|
+
if (result === undefined) {
|
|
170
|
+
if (this.interrupted)
|
|
171
|
+
return turn.result("cancelled");
|
|
172
|
+
throw new AmaError("agent_failed", `${this.agentId} 没有输出结果(退出码 ${String(code)}):${stderrHint(transport)}`);
|
|
173
|
+
}
|
|
174
|
+
if (typeof result.session_id === "string")
|
|
175
|
+
this.id = result.session_id;
|
|
176
|
+
this.resumable = true;
|
|
177
|
+
const u = result.usage ?? {};
|
|
178
|
+
turn.push({
|
|
179
|
+
type: "usage",
|
|
180
|
+
...(u.input_tokens !== undefined ? { input: u.input_tokens } : {}),
|
|
181
|
+
...(u.output_tokens !== undefined ? { output: u.output_tokens } : {}),
|
|
182
|
+
...(u.cache_read_input_tokens !== undefined ? { cacheRead: u.cache_read_input_tokens } : {}),
|
|
183
|
+
...(typeof result.total_cost_usd === "number" ? { costUsd: result.total_cost_usd } : {}),
|
|
184
|
+
});
|
|
185
|
+
if (typeof result.result === "string")
|
|
186
|
+
turn.setFinalText(result.result);
|
|
187
|
+
const stop = claudeStopReason(result, this.interrupted);
|
|
188
|
+
if (stop === undefined)
|
|
189
|
+
throw new AmaError("agent_failed", `${this.agentId} 失败:${oneLine(result.result ?? result.subtype ?? "", 300)}`);
|
|
190
|
+
return turn.result(stop);
|
|
191
|
+
}
|
|
192
|
+
/** codex exec --json 的一行;返回失败信息(turn.failed / error)。 */
|
|
193
|
+
onCodex(msg, turn) {
|
|
194
|
+
const item = (msg["item"] ?? {});
|
|
195
|
+
switch (msg["type"]) {
|
|
196
|
+
case "thread.started":
|
|
197
|
+
if (typeof msg["thread_id"] === "string")
|
|
198
|
+
this.id = msg["thread_id"];
|
|
199
|
+
return undefined;
|
|
200
|
+
case "item.completed":
|
|
201
|
+
case "item.started": {
|
|
202
|
+
const started = msg["type"] === "item.started";
|
|
203
|
+
const id = String(item["id"] ?? "");
|
|
204
|
+
switch (item["type"]) {
|
|
205
|
+
case "agent_message":
|
|
206
|
+
if (!started && typeof item["text"] === "string") {
|
|
207
|
+
turn.push({
|
|
208
|
+
type: "message_delta",
|
|
209
|
+
text: turn.finalText === "" ? item["text"] : `\n${item["text"]}`,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
return undefined;
|
|
213
|
+
case "reasoning":
|
|
214
|
+
if (!started && typeof item["text"] === "string")
|
|
215
|
+
turn.push({ type: "thought_delta", text: item["text"] });
|
|
216
|
+
return undefined;
|
|
217
|
+
case "command_execution":
|
|
218
|
+
turn.push({
|
|
219
|
+
type: "tool_call",
|
|
220
|
+
id,
|
|
221
|
+
title: `$ ${oneLine(String(item["command"] ?? ""), 100)}`,
|
|
222
|
+
kind: "execute",
|
|
223
|
+
status: started
|
|
224
|
+
? "in_progress"
|
|
225
|
+
: item["status"] === "failed"
|
|
226
|
+
? "failed"
|
|
227
|
+
: "completed",
|
|
228
|
+
});
|
|
229
|
+
return undefined;
|
|
230
|
+
case "web_search":
|
|
231
|
+
turn.push({
|
|
232
|
+
type: "tool_call",
|
|
233
|
+
id,
|
|
234
|
+
title: `search ${oneLine(String(item["query"] ?? ""), 80)}`,
|
|
235
|
+
kind: "fetch",
|
|
236
|
+
status: started ? "in_progress" : "completed",
|
|
237
|
+
});
|
|
238
|
+
return undefined;
|
|
239
|
+
default:
|
|
240
|
+
return undefined;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
case "turn.completed": {
|
|
244
|
+
const u = (msg["usage"] ?? {});
|
|
245
|
+
turn.push({
|
|
246
|
+
type: "usage",
|
|
247
|
+
...(u["input_tokens"] !== undefined ? { input: u["input_tokens"] } : {}),
|
|
248
|
+
...(u["output_tokens"] !== undefined ? { output: u["output_tokens"] } : {}),
|
|
249
|
+
...(u["cached_input_tokens"] !== undefined
|
|
250
|
+
? { cacheRead: u["cached_input_tokens"] }
|
|
251
|
+
: {}),
|
|
252
|
+
});
|
|
253
|
+
return undefined;
|
|
254
|
+
}
|
|
255
|
+
case "turn.failed":
|
|
256
|
+
return String((msg["error"] ?? {})["message"] ?? "turn failed");
|
|
257
|
+
case "error":
|
|
258
|
+
return String(msg["message"] ?? "error");
|
|
259
|
+
default:
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
/** gemini -p --output-format stream-json 的一行。 */
|
|
264
|
+
onGemini(msg, turn) {
|
|
265
|
+
switch (msg["type"]) {
|
|
266
|
+
case "init":
|
|
267
|
+
if (typeof msg["session_id"] === "string")
|
|
268
|
+
this.id = msg["session_id"];
|
|
269
|
+
return undefined;
|
|
270
|
+
case "message":
|
|
271
|
+
if (msg["role"] === "assistant" && typeof msg["content"] === "string")
|
|
272
|
+
turn.push({ type: "message_delta", text: msg["content"] });
|
|
273
|
+
return undefined;
|
|
274
|
+
case "tool_use":
|
|
275
|
+
turn.push({
|
|
276
|
+
type: "tool_call",
|
|
277
|
+
id: String(msg["tool_id"] ?? ""),
|
|
278
|
+
title: String(msg["tool_name"] ?? "tool"),
|
|
279
|
+
kind: "other",
|
|
280
|
+
status: "in_progress",
|
|
281
|
+
});
|
|
282
|
+
return undefined;
|
|
283
|
+
case "tool_result": {
|
|
284
|
+
const id = String(msg["tool_id"] ?? "");
|
|
285
|
+
const known = turn.tool(id);
|
|
286
|
+
turn.push({
|
|
287
|
+
type: "tool_call",
|
|
288
|
+
id,
|
|
289
|
+
title: known?.title ?? "tool",
|
|
290
|
+
kind: known?.kind ?? "other",
|
|
291
|
+
status: msg["status"] === "error" ? "failed" : "completed",
|
|
292
|
+
});
|
|
293
|
+
return undefined;
|
|
294
|
+
}
|
|
295
|
+
case "result": {
|
|
296
|
+
const stats = (msg["stats"] ?? {});
|
|
297
|
+
turn.push({
|
|
298
|
+
type: "usage",
|
|
299
|
+
...(stats["input_tokens"] !== undefined ? { input: stats["input_tokens"] } : {}),
|
|
300
|
+
...(stats["output_tokens"] !== undefined ? { output: stats["output_tokens"] } : {}),
|
|
301
|
+
});
|
|
302
|
+
return msg["status"] === "error"
|
|
303
|
+
? String((msg["error"] ?? {})["message"] ?? "error")
|
|
304
|
+
: undefined;
|
|
305
|
+
}
|
|
306
|
+
case "error":
|
|
307
|
+
return String(msg["message"] ?? "error");
|
|
308
|
+
default:
|
|
309
|
+
return undefined;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 外部 Agent 的权限:交人、不代答(docs/wave5-plan.md §5.3,D15)。[W5-E]
|
|
3
|
+
*
|
|
4
|
+
* 1. 子 Agent 自己的策略先判,它决定要问人的才到 ama;到了以后只走审批通道(宿主 → UI →
|
|
5
|
+
* 无人值守拒绝),auto 分类器与模型都不参与——`approve` 由调用方接到会话的
|
|
6
|
+
* `requestApproval`(发 permission_request 事件、走 broker 链),而不是 `gateToolCall`。
|
|
7
|
+
* 2. 选项映射:允许 → 首个 allow_once;本会话允许 → allow_always(由子 Agent 自己记,ama 不缓存);
|
|
8
|
+
* 拒绝 → reject_once。reject_always(会改子 CLI 的持久配置)不由这里选。
|
|
9
|
+
* 3. 无人值守:reject_once(没有则 cancelled),不询问。
|
|
10
|
+
* 4. 父 abort / stop / 超时:cancelled。
|
|
11
|
+
* 5. 模式:外部 Agent 的模式不得比 ama 当前模式宽;`agents.<id>.maxMode`(只认用户级)显式放宽。
|
|
12
|
+
*/
|
|
13
|
+
import type { ApprovalDecision, ApprovalRequest, PermissionMode } from "../permissions/types.js";
|
|
14
|
+
import type { DriverPermissionOutcome, DriverPermissionRequest } from "./types.js";
|
|
15
|
+
export type ApproveFn = (request: ApprovalRequest, signal: AbortSignal) => Promise<ApprovalDecision>;
|
|
16
|
+
export interface AskHumanContext {
|
|
17
|
+
agent: string;
|
|
18
|
+
sessionId: string;
|
|
19
|
+
unattended: boolean;
|
|
20
|
+
approve: ApproveFn;
|
|
21
|
+
signal: AbortSignal;
|
|
22
|
+
/** 发起的 task 任务 id(对话框与 RPC `context.taskId`)。 */
|
|
23
|
+
taskId?: string;
|
|
24
|
+
}
|
|
25
|
+
/** 审批对话框里显示的工具名:`agent:<id>`(RPC permission_request 的 toolName)。 */
|
|
26
|
+
export declare function externalToolName(agent: string): string;
|
|
27
|
+
/** ApprovalDecision → 外部 Agent 的选项。 */
|
|
28
|
+
export declare function outcomeFor(request: DriverPermissionRequest, decision: ApprovalDecision): DriverPermissionOutcome;
|
|
29
|
+
/** 把外部 Agent 的权限请求交给人。 */
|
|
30
|
+
export declare function askHuman(request: DriverPermissionRequest, ctx: AskHumanContext): Promise<DriverPermissionOutcome>;
|
|
31
|
+
/** 两个模式里更严的那个。 */
|
|
32
|
+
export declare function stricterMode(a: PermissionMode, b: PermissionMode): PermissionMode;
|
|
33
|
+
/**
|
|
34
|
+
* 外部 Agent 实际使用的模式:请求的模式夹到上限之内;上限 = `maxMode`(用户显式配置)或父会话
|
|
35
|
+
* 当前模式。结果为 `allowlist` 时按 `plan` 运行:外部 Agent 的放行规则来自它自己的配置,与 ama 的
|
|
36
|
+
* allowlist 不等价,父会话在 plan / allowlist 时外部 Agent 只能只读。
|
|
37
|
+
*/
|
|
38
|
+
export declare function clampMode(requested: PermissionMode, parent: PermissionMode | undefined, maxMode: PermissionMode | undefined): PermissionMode;
|
|
39
|
+
/** 驱动支持的模式里,不宽于 `mode` 的最宽的一个;没有返回 undefined。 */
|
|
40
|
+
export declare function supportedMode(mode: PermissionMode, supported: readonly PermissionMode[]): PermissionMode | undefined;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 外部 Agent 的权限:交人、不代答(docs/wave5-plan.md §5.3,D15)。[W5-E]
|
|
3
|
+
*
|
|
4
|
+
* 1. 子 Agent 自己的策略先判,它决定要问人的才到 ama;到了以后只走审批通道(宿主 → UI →
|
|
5
|
+
* 无人值守拒绝),auto 分类器与模型都不参与——`approve` 由调用方接到会话的
|
|
6
|
+
* `requestApproval`(发 permission_request 事件、走 broker 链),而不是 `gateToolCall`。
|
|
7
|
+
* 2. 选项映射:允许 → 首个 allow_once;本会话允许 → allow_always(由子 Agent 自己记,ama 不缓存);
|
|
8
|
+
* 拒绝 → reject_once。reject_always(会改子 CLI 的持久配置)不由这里选。
|
|
9
|
+
* 3. 无人值守:reject_once(没有则 cancelled),不询问。
|
|
10
|
+
* 4. 父 abort / stop / 超时:cancelled。
|
|
11
|
+
* 5. 模式:外部 Agent 的模式不得比 ama 当前模式宽;`agents.<id>.maxMode`(只认用户级)显式放宽。
|
|
12
|
+
*/
|
|
13
|
+
import { randomUUID } from "node:crypto";
|
|
14
|
+
import { PERMISSION_MODES_STRICT_FIRST } from "../permissions/types.js";
|
|
15
|
+
/** 审批对话框里显示的工具名:`agent:<id>`(RPC permission_request 的 toolName)。 */
|
|
16
|
+
export function externalToolName(agent) {
|
|
17
|
+
return `agent:${agent}`;
|
|
18
|
+
}
|
|
19
|
+
function pick(request, kind) {
|
|
20
|
+
const option = request.options.find((o) => o.kind === kind);
|
|
21
|
+
return option === undefined ? undefined : { outcome: "selected", optionId: option.optionId };
|
|
22
|
+
}
|
|
23
|
+
const CANCELLED = { outcome: "cancelled" };
|
|
24
|
+
/** ApprovalDecision → 外部 Agent 的选项。 */
|
|
25
|
+
export function outcomeFor(request, decision) {
|
|
26
|
+
switch (decision) {
|
|
27
|
+
case "allow":
|
|
28
|
+
return pick(request, "allow_once") ?? CANCELLED;
|
|
29
|
+
case "allow_session":
|
|
30
|
+
return pick(request, "allow_always") ?? pick(request, "allow_once") ?? CANCELLED;
|
|
31
|
+
case "deny":
|
|
32
|
+
return pick(request, "reject_once") ?? CANCELLED;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** 把外部 Agent 的权限请求交给人。 */
|
|
36
|
+
export async function askHuman(request, ctx) {
|
|
37
|
+
if (ctx.signal.aborted)
|
|
38
|
+
return CANCELLED;
|
|
39
|
+
if (ctx.unattended)
|
|
40
|
+
return pick(request, "reject_once") ?? CANCELLED;
|
|
41
|
+
const approval = {
|
|
42
|
+
requestId: randomUUID(),
|
|
43
|
+
toolName: externalToolName(ctx.agent),
|
|
44
|
+
input: request.toolCall,
|
|
45
|
+
reason: "mode",
|
|
46
|
+
context: {
|
|
47
|
+
depth: 1,
|
|
48
|
+
...(ctx.taskId !== undefined ? { taskId: ctx.taskId } : {}),
|
|
49
|
+
origin: {
|
|
50
|
+
agent: ctx.agent,
|
|
51
|
+
sessionId: ctx.sessionId,
|
|
52
|
+
toolCall: request.toolCall,
|
|
53
|
+
options: request.options,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
let decision;
|
|
58
|
+
try {
|
|
59
|
+
decision = await ctx.approve(approval, ctx.signal);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return CANCELLED;
|
|
63
|
+
}
|
|
64
|
+
if (ctx.signal.aborted)
|
|
65
|
+
return CANCELLED;
|
|
66
|
+
return outcomeFor(request, decision);
|
|
67
|
+
}
|
|
68
|
+
function rank(mode) {
|
|
69
|
+
return PERMISSION_MODES_STRICT_FIRST.indexOf(mode);
|
|
70
|
+
}
|
|
71
|
+
/** 两个模式里更严的那个。 */
|
|
72
|
+
export function stricterMode(a, b) {
|
|
73
|
+
return rank(a) <= rank(b) ? a : b;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 外部 Agent 实际使用的模式:请求的模式夹到上限之内;上限 = `maxMode`(用户显式配置)或父会话
|
|
77
|
+
* 当前模式。结果为 `allowlist` 时按 `plan` 运行:外部 Agent 的放行规则来自它自己的配置,与 ama 的
|
|
78
|
+
* allowlist 不等价,父会话在 plan / allowlist 时外部 Agent 只能只读。
|
|
79
|
+
*/
|
|
80
|
+
export function clampMode(requested, parent, maxMode) {
|
|
81
|
+
const bound = maxMode ?? parent;
|
|
82
|
+
const mode = bound === undefined ? requested : stricterMode(requested, bound);
|
|
83
|
+
return mode === "allowlist" ? "plan" : mode;
|
|
84
|
+
}
|
|
85
|
+
/** 驱动支持的模式里,不宽于 `mode` 的最宽的一个;没有返回 undefined。 */
|
|
86
|
+
export function supportedMode(mode, supported) {
|
|
87
|
+
let best;
|
|
88
|
+
for (const m of supported)
|
|
89
|
+
if (rank(m) <= rank(mode) && (best === undefined || rank(m) > rank(best)))
|
|
90
|
+
best = m;
|
|
91
|
+
return best;
|
|
92
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 外部 Agent 进程登记与孤儿清理(docs/wave5-plan.md §5.4 孤儿进程)。[W5-E]
|
|
3
|
+
*
|
|
4
|
+
* `<dataDir>/drivers/pids.json` 记 `{ pid, program, owner }`(owner = 起它的 ama 进程)。
|
|
5
|
+
* 正常退出时 `process-tree.ts` 已同步杀掉仍存活的组;这里处理 ama 被 SIGKILL / 崩溃留下的孤儿:
|
|
6
|
+
* 下次建外部 runner 时,owner 已不在的条目若进程组仍在、且(POSIX)`ps` 看到的命令行含登记的
|
|
7
|
+
* 程序名,就杀进程树。对不上的(pid 被复用)只删登记不杀;Windows 不核对命令行,只删登记。
|
|
8
|
+
*/
|
|
9
|
+
export interface PidEntry {
|
|
10
|
+
pid: number;
|
|
11
|
+
program: string;
|
|
12
|
+
owner: number;
|
|
13
|
+
startedAt: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function pidsFile(dataDir: string): string;
|
|
16
|
+
export interface ReapDeps {
|
|
17
|
+
platform?: NodeJS.Platform;
|
|
18
|
+
isOwnerAlive?(pid: number): boolean;
|
|
19
|
+
isGroupAlive?(pid: number): boolean;
|
|
20
|
+
/** POSIX:进程的命令行(`ps -o command= -p pid`)。 */
|
|
21
|
+
commandOf?(pid: number): string | undefined;
|
|
22
|
+
kill?(pid: number): Promise<unknown>;
|
|
23
|
+
}
|
|
24
|
+
export declare class PidRegistry {
|
|
25
|
+
private readonly file;
|
|
26
|
+
constructor(file: string);
|
|
27
|
+
add(pid: number, program: string): void;
|
|
28
|
+
remove(pid: number): void;
|
|
29
|
+
entries(): PidEntry[];
|
|
30
|
+
/** 清理 owner 已不在的条目;返回被杀的 pid。 */
|
|
31
|
+
reapOrphans(deps?: ReapDeps): Promise<number[]>;
|
|
32
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 外部 Agent 进程登记与孤儿清理(docs/wave5-plan.md §5.4 孤儿进程)。[W5-E]
|
|
3
|
+
*
|
|
4
|
+
* `<dataDir>/drivers/pids.json` 记 `{ pid, program, owner }`(owner = 起它的 ama 进程)。
|
|
5
|
+
* 正常退出时 `process-tree.ts` 已同步杀掉仍存活的组;这里处理 ama 被 SIGKILL / 崩溃留下的孤儿:
|
|
6
|
+
* 下次建外部 runner 时,owner 已不在的条目若进程组仍在、且(POSIX)`ps` 看到的命令行含登记的
|
|
7
|
+
* 程序名,就杀进程树。对不上的(pid 被复用)只删登记不杀;Windows 不核对命令行,只删登记。
|
|
8
|
+
*/
|
|
9
|
+
import { spawnSync } from "node:child_process";
|
|
10
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
11
|
+
import { basename, dirname, join } from "node:path";
|
|
12
|
+
import { isGroupAlive, killProcessTree } from "../tools/process-tree.js";
|
|
13
|
+
export function pidsFile(dataDir) {
|
|
14
|
+
return join(dataDir, "drivers", "pids.json");
|
|
15
|
+
}
|
|
16
|
+
function read(file) {
|
|
17
|
+
try {
|
|
18
|
+
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
19
|
+
return Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function write(file, entries) {
|
|
26
|
+
try {
|
|
27
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
28
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
29
|
+
writeFileSync(tmp, `${JSON.stringify({ version: 1, entries }, null, 2)}\n`);
|
|
30
|
+
renameSync(tmp, file);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// 登记失败不影响运行(退出时仍有 process-tree 的同步清理)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function processAlive(pid) {
|
|
37
|
+
try {
|
|
38
|
+
process.kill(pid, 0);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
return err.code === "EPERM";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function psCommand(pid) {
|
|
46
|
+
const out = spawnSync("ps", ["-o", "command=", "-p", String(pid)], { encoding: "utf8" });
|
|
47
|
+
return out.status === 0 ? out.stdout.trim() : undefined;
|
|
48
|
+
}
|
|
49
|
+
export class PidRegistry {
|
|
50
|
+
file;
|
|
51
|
+
constructor(file) {
|
|
52
|
+
this.file = file;
|
|
53
|
+
}
|
|
54
|
+
add(pid, program) {
|
|
55
|
+
const entries = read(this.file).filter((e) => e.pid !== pid);
|
|
56
|
+
entries.push({ pid, program, owner: process.pid, startedAt: Date.now() });
|
|
57
|
+
write(this.file, entries);
|
|
58
|
+
}
|
|
59
|
+
remove(pid) {
|
|
60
|
+
const entries = read(this.file);
|
|
61
|
+
const next = entries.filter((e) => e.pid !== pid);
|
|
62
|
+
if (next.length !== entries.length)
|
|
63
|
+
write(this.file, next);
|
|
64
|
+
}
|
|
65
|
+
entries() {
|
|
66
|
+
return read(this.file);
|
|
67
|
+
}
|
|
68
|
+
/** 清理 owner 已不在的条目;返回被杀的 pid。 */
|
|
69
|
+
async reapOrphans(deps = {}) {
|
|
70
|
+
const platform = deps.platform ?? process.platform;
|
|
71
|
+
const ownerAlive = deps.isOwnerAlive ?? processAlive;
|
|
72
|
+
const groupAlive = deps.isGroupAlive ?? ((pid) => isGroupAlive(pid));
|
|
73
|
+
const commandOf = deps.commandOf ?? psCommand;
|
|
74
|
+
const kill = deps.kill ?? ((pid) => killProcessTree(pid, { platform }));
|
|
75
|
+
const keep = [];
|
|
76
|
+
const killed = [];
|
|
77
|
+
for (const entry of read(this.file)) {
|
|
78
|
+
if (entry.owner === process.pid || ownerAlive(entry.owner)) {
|
|
79
|
+
keep.push(entry);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (platform === "win32" || !groupAlive(entry.pid))
|
|
83
|
+
continue;
|
|
84
|
+
const command = commandOf(entry.pid);
|
|
85
|
+
if (command !== undefined && command.includes(basename(entry.program))) {
|
|
86
|
+
await kill(entry.pid);
|
|
87
|
+
killed.push(entry.pid);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
write(this.file, keep);
|
|
91
|
+
return killed;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 外部 Agent 并发池(docs/wave5-plan.md §5.4 并发)。[W5-E]
|
|
3
|
+
*
|
|
4
|
+
* 总并发 `agents.maxConcurrent`(缺省 3)+ 每个 Agent 的上限 `agents.<id>.maxConcurrent`
|
|
5
|
+
* (claude 缺省 2:订阅有速率限制);超出排队(先来先得),排队可被 abort。与 ama 自己的
|
|
6
|
+
* `SubagentPool`(子会话)分开计数。
|
|
7
|
+
*/
|
|
8
|
+
import { type AgentsConfig } from "../config/types-w5.js";
|
|
9
|
+
export declare const DEFAULT_MAX_CONCURRENT = 3;
|
|
10
|
+
export declare const DEFAULT_PER_AGENT: Readonly<Record<string, number>>;
|
|
11
|
+
export declare class DriverPool {
|
|
12
|
+
private readonly maxTotal;
|
|
13
|
+
private readonly perAgent;
|
|
14
|
+
private readonly active;
|
|
15
|
+
private total;
|
|
16
|
+
private readonly queue;
|
|
17
|
+
constructor(maxTotal?: number, perAgent?: (agentId: string) => number | undefined);
|
|
18
|
+
running(agentId?: string): number;
|
|
19
|
+
queued(): number;
|
|
20
|
+
private limitFor;
|
|
21
|
+
private canRun;
|
|
22
|
+
private take;
|
|
23
|
+
private drain;
|
|
24
|
+
/** 拿到一个名额(返回释放函数);abort 时离开队列并以 `aborted` 失败。 */
|
|
25
|
+
acquire(agentId: string, signal?: AbortSignal): Promise<() => void>;
|
|
26
|
+
}
|
|
27
|
+
/** 按配置建池(`agents.maxConcurrent`、`agents.<id>.maxConcurrent`)。 */
|
|
28
|
+
export declare function poolFromConfig(config: AgentsConfig | undefined): DriverPool;
|