@namzu/cli 26.3.0 → 28.0.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 +271 -0
- package/README.md +67 -46
- package/dist/bin.js +7 -0
- package/dist/bin.js.map +1 -1
- package/dist/checkpoints/store.d.ts +17 -4
- package/dist/checkpoints/store.d.ts.map +1 -1
- package/dist/checkpoints/store.js +20 -5
- package/dist/checkpoints/store.js.map +1 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +124 -14
- package/dist/cli.js.map +1 -1
- package/dist/commands/acp.d.ts +23 -1
- package/dist/commands/acp.d.ts.map +1 -1
- package/dist/commands/acp.js +41 -2
- package/dist/commands/acp.js.map +1 -1
- package/dist/commands/drain.d.ts +69 -2
- package/dist/commands/drain.d.ts.map +1 -1
- package/dist/commands/drain.js +216 -136
- package/dist/commands/drain.js.map +1 -1
- package/dist/commands/eval.js +1 -1
- package/dist/commands/eval.js.map +1 -1
- package/dist/commands/{run-flags.d.ts → exec-flags.d.ts} +36 -28
- package/dist/commands/exec-flags.d.ts.map +1 -0
- package/dist/commands/{run-flags.js → exec-flags.js} +35 -27
- package/dist/commands/exec-flags.js.map +1 -0
- package/dist/commands/exec-json.d.ts +75 -0
- package/dist/commands/exec-json.d.ts.map +1 -0
- package/dist/commands/exec-json.js +473 -0
- package/dist/commands/exec-json.js.map +1 -0
- package/dist/commands/exec.d.ts +42 -0
- package/dist/commands/exec.d.ts.map +1 -0
- package/dist/commands/exec.js +683 -0
- package/dist/commands/exec.js.map +1 -0
- package/dist/commands/host-queries.d.ts +12 -0
- package/dist/commands/host-queries.d.ts.map +1 -0
- package/dist/commands/host-queries.js +149 -0
- package/dist/commands/host-queries.js.map +1 -0
- package/dist/commands/prior-messages.js +1 -1
- package/dist/commands/prior-messages.js.map +1 -1
- package/dist/commands/provider-wait.d.ts +5 -5
- package/dist/commands/provider-wait.d.ts.map +1 -1
- package/dist/commands/provider-wait.js +2 -2
- package/dist/commands/registry.d.ts.map +1 -1
- package/dist/commands/registry.js +2 -0
- package/dist/commands/registry.js.map +1 -1
- package/dist/commands/resident-flags.d.ts +2 -2
- package/dist/commands/resident-flags.d.ts.map +1 -1
- package/dist/commands/resident-flags.js +4 -2
- package/dist/commands/resident-flags.js.map +1 -1
- package/dist/commands/resident-learning.js +1 -1
- package/dist/commands/resident-learning.js.map +1 -1
- package/dist/commands/resident.d.ts.map +1 -1
- package/dist/commands/resident.js +3 -2
- package/dist/commands/resident.js.map +1 -1
- package/dist/commands/serve.js +2 -2
- package/dist/commands/serve.js.map +1 -1
- package/dist/commands/skills.js +1 -1
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/state.js +8 -8
- package/dist/commands/state.js.map +1 -1
- package/dist/commands/stdin.d.ts +5 -5
- package/dist/commands/stdin.js +5 -5
- package/dist/commands/types.d.ts +2 -0
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/config/load.d.ts +1 -1
- package/dist/config/load.d.ts.map +1 -1
- package/dist/config/load.js +36 -5
- package/dist/config/load.js.map +1 -1
- package/dist/config/schema.d.ts +59 -20
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/config/tool-result-screens.js +1 -1
- package/dist/config/turn-guards.d.ts +23 -0
- package/dist/config/turn-guards.d.ts.map +1 -0
- package/dist/config/turn-guards.js +46 -0
- package/dist/config/turn-guards.js.map +1 -0
- package/dist/context/directories.d.ts +37 -0
- package/dist/context/directories.d.ts.map +1 -0
- package/dist/context/directories.js +69 -0
- package/dist/context/directories.js.map +1 -0
- package/dist/context/environment.d.ts +55 -0
- package/dist/context/environment.d.ts.map +1 -1
- package/dist/context/environment.js +96 -0
- package/dist/context/environment.js.map +1 -1
- package/dist/context/project-tracker.d.ts +3 -3
- package/dist/context/project-tracker.d.ts.map +1 -1
- package/dist/context/project-tracker.js +3 -3
- package/dist/context/project-tracker.js.map +1 -1
- package/dist/context/project.d.ts +2 -2
- package/dist/context/project.js +2 -2
- package/dist/context/sandbox.d.ts +22 -3
- package/dist/context/sandbox.d.ts.map +1 -1
- package/dist/context/sandbox.js +42 -4
- package/dist/context/sandbox.js.map +1 -1
- package/dist/doctor/checks/chain.d.ts +1 -1
- package/dist/doctor/checks/chain.js +7 -7
- package/dist/doctor/checks/chain.js.map +1 -1
- package/dist/doctor/checks/invariants.d.ts +2 -2
- package/dist/doctor/checks/invariants.js +5 -5
- package/dist/doctor/checks/invariants.js.map +1 -1
- package/dist/doctor/checks/logging.js +1 -1
- package/dist/doctor/checks/logging.js.map +1 -1
- package/dist/doctor/checks/sandbox.js +1 -1
- package/dist/doctor/checks/sandbox.js.map +1 -1
- package/dist/doctor/checks/session-export.d.ts +1 -1
- package/dist/doctor/checks/session-export.js +2 -2
- package/dist/doctor/checks/session-export.js.map +1 -1
- package/dist/doctor/checks/telemetry.d.ts +1 -1
- package/dist/doctor/checks/telemetry.js +1 -1
- package/dist/doctor/registry.js +2 -2
- package/dist/doctor/registry.js.map +1 -1
- package/dist/exit-codes.d.ts +2 -2
- package/dist/exit-codes.js +2 -2
- package/dist/integrations/mcp/servers.d.ts +1 -1
- package/dist/integrations/providers/access.d.ts +4 -1
- package/dist/integrations/providers/access.d.ts.map +1 -1
- package/dist/integrations/providers/access.js +6 -3
- package/dist/integrations/providers/access.js.map +1 -1
- package/dist/integrations/providers/chain-capabilities.d.ts +6 -6
- package/dist/integrations/providers/chain-capabilities.js +8 -8
- package/dist/integrations/providers/chain-capabilities.js.map +1 -1
- package/dist/integrations/providers/register.d.ts +1 -1
- package/dist/integrations/providers/register.js +1 -1
- package/dist/integrations/providers/registry.d.ts +1 -1
- package/dist/integrations/providers/zen-catalogue.d.ts +100 -0
- package/dist/integrations/providers/zen-catalogue.d.ts.map +1 -0
- package/dist/integrations/providers/zen-catalogue.js +196 -0
- package/dist/integrations/providers/zen-catalogue.js.map +1 -0
- package/dist/integrations/resident/inspection.d.ts +4 -1
- package/dist/integrations/resident/inspection.d.ts.map +1 -1
- package/dist/integrations/resident/inspection.js +53 -34
- package/dist/integrations/resident/inspection.js.map +1 -1
- package/dist/integrations/resident/learning-storage.d.ts +7 -2
- package/dist/integrations/resident/learning-storage.d.ts.map +1 -1
- package/dist/integrations/resident/learning-storage.js +12 -8
- package/dist/integrations/resident/learning-storage.js.map +1 -1
- package/dist/integrations/resident/runner-launch.d.ts +3 -3
- package/dist/integrations/resident/runner-launch.d.ts.map +1 -1
- package/dist/integrations/resident/runner-store.d.ts +1 -1
- package/dist/integrations/resident/runner-store.d.ts.map +1 -1
- package/dist/integrations/resident/runner-store.js +5 -2
- package/dist/integrations/resident/runner-store.js.map +1 -1
- package/dist/integrations/resident/runner-worker.js +23 -0
- package/dist/integrations/resident/runner-worker.js.map +1 -1
- package/dist/integrations/resident/session-log-reads.d.ts +37 -0
- package/dist/integrations/resident/session-log-reads.d.ts.map +1 -0
- package/dist/integrations/resident/session-log-reads.js +125 -0
- package/dist/integrations/resident/session-log-reads.js.map +1 -0
- package/dist/integrations/resident/session-step.d.ts +4 -2
- package/dist/integrations/resident/session-step.d.ts.map +1 -1
- package/dist/integrations/resident/session-step.js +16 -14
- package/dist/integrations/resident/session-step.js.map +1 -1
- package/dist/integrations/resident/storage.d.ts +23 -1
- package/dist/integrations/resident/storage.d.ts.map +1 -1
- package/dist/integrations/resident/storage.js +60 -17
- package/dist/integrations/resident/storage.js.map +1 -1
- package/dist/integrations/resident/tool-evidence.d.ts +4 -2
- package/dist/integrations/resident/tool-evidence.d.ts.map +1 -1
- package/dist/integrations/resident/tool-evidence.js +36 -30
- package/dist/integrations/resident/tool-evidence.js.map +1 -1
- package/dist/integrations/resident/verification.d.ts +2 -2
- package/dist/integrations/resident/verification.d.ts.map +1 -1
- package/dist/integrations/resident/verification.js +4 -4
- package/dist/integrations/resident/verification.js.map +1 -1
- package/dist/integrations/sessions/checkpoint-view.d.ts +13 -0
- package/dist/integrations/sessions/checkpoint-view.d.ts.map +1 -0
- package/dist/integrations/sessions/checkpoint-view.js +48 -0
- package/dist/integrations/sessions/checkpoint-view.js.map +1 -0
- package/dist/integrations/sessions/context-inventory.js +1 -1
- package/dist/integrations/sessions/context-inventory.js.map +1 -1
- package/dist/integrations/sessions/conversation-search.d.ts +16 -20
- package/dist/integrations/sessions/conversation-search.d.ts.map +1 -1
- package/dist/integrations/sessions/conversation-search.js +305 -731
- package/dist/integrations/sessions/conversation-search.js.map +1 -1
- package/dist/integrations/sessions/evidence-page-validation.d.ts +4 -4
- package/dist/integrations/sessions/evidence-page-validation.d.ts.map +1 -1
- package/dist/integrations/sessions/evidence-page-validation.js +1 -1
- package/dist/integrations/sessions/evidence-page-validation.js.map +1 -1
- package/dist/integrations/sessions/evidence-recall.d.ts +40 -2
- package/dist/integrations/sessions/evidence-recall.d.ts.map +1 -1
- package/dist/integrations/sessions/evidence-recall.js +41 -97
- package/dist/integrations/sessions/evidence-recall.js.map +1 -1
- package/dist/integrations/sessions/store.d.ts +153 -85
- package/dist/integrations/sessions/store.d.ts.map +1 -1
- package/dist/integrations/sessions/store.js +414 -484
- package/dist/integrations/sessions/store.js.map +1 -1
- package/dist/integrations/sessions/task-context.d.ts +15 -2
- package/dist/integrations/sessions/task-context.d.ts.map +1 -1
- package/dist/integrations/sessions/task-context.js +34 -11
- package/dist/integrations/sessions/task-context.js.map +1 -1
- package/dist/integrations/sessions/transcript-export.d.ts +13 -6
- package/dist/integrations/sessions/transcript-export.d.ts.map +1 -1
- package/dist/integrations/sessions/transcript-export.js +148 -263
- package/dist/integrations/sessions/transcript-export.js.map +1 -1
- package/dist/integrations/state/home.d.ts +4 -16
- package/dist/integrations/state/home.d.ts.map +1 -1
- package/dist/integrations/state/home.js +6 -42
- package/dist/integrations/state/home.js.map +1 -1
- package/dist/integrations/state/project.d.ts +6 -7
- package/dist/integrations/state/project.d.ts.map +1 -1
- package/dist/integrations/state/project.js +7 -9
- package/dist/integrations/state/project.js.map +1 -1
- package/dist/integrations/state/report.d.ts +27 -41
- package/dist/integrations/state/report.d.ts.map +1 -1
- package/dist/integrations/state/report.js +172 -464
- package/dist/integrations/state/report.js.map +1 -1
- package/dist/integrations/state/retention.d.ts +18 -0
- package/dist/integrations/state/retention.d.ts.map +1 -0
- package/dist/integrations/state/retention.js +18 -0
- package/dist/integrations/state/retention.js.map +1 -0
- package/dist/integrations/subagents/activity.d.ts +36 -31
- package/dist/integrations/subagents/activity.d.ts.map +1 -1
- package/dist/integrations/subagents/activity.js +27 -23
- package/dist/integrations/subagents/activity.js.map +1 -1
- package/dist/integrations/subagents/batches.d.ts +91 -0
- package/dist/integrations/subagents/batches.d.ts.map +1 -0
- package/dist/integrations/subagents/batches.js +172 -0
- package/dist/integrations/subagents/batches.js.map +1 -0
- package/dist/integrations/subagents/parent.d.ts +26 -11
- package/dist/integrations/subagents/parent.d.ts.map +1 -1
- package/dist/integrations/subagents/parent.js +13 -28
- package/dist/integrations/subagents/parent.js.map +1 -1
- package/dist/integrations/subagents/policy.d.ts +2 -2
- package/dist/integrations/subagents/policy.d.ts.map +1 -1
- package/dist/integrations/subagents/policy.js +2 -2
- package/dist/integrations/subagents/policy.js.map +1 -1
- package/dist/integrations/subagents/replay.d.ts +91 -26
- package/dist/integrations/subagents/replay.d.ts.map +1 -1
- package/dist/integrations/subagents/replay.js +216 -114
- package/dist/integrations/subagents/replay.js.map +1 -1
- package/dist/integrations/subagents/runtime.d.ts +50 -26
- package/dist/integrations/subagents/runtime.d.ts.map +1 -1
- package/dist/integrations/subagents/runtime.js +132 -126
- package/dist/integrations/subagents/runtime.js.map +1 -1
- package/dist/integrations/subagents/saved-agents.d.ts +57 -0
- package/dist/integrations/subagents/saved-agents.d.ts.map +1 -0
- package/dist/integrations/subagents/saved-agents.js +102 -0
- package/dist/integrations/subagents/saved-agents.js.map +1 -0
- package/dist/integrations/subagents/slash.d.ts +55 -0
- package/dist/integrations/subagents/slash.d.ts.map +1 -0
- package/dist/integrations/subagents/slash.js +45 -0
- package/dist/integrations/subagents/slash.js.map +1 -0
- package/dist/integrations/telemetry/session-export.d.ts +6 -6
- package/dist/integrations/telemetry/session-export.d.ts.map +1 -1
- package/dist/integrations/telemetry/session-export.js +2 -2
- package/dist/integrations/web/search.d.ts +1 -1
- package/dist/logging.d.ts +5 -5
- package/dist/logging.js +6 -6
- package/dist/memory/presentation.d.ts +11 -0
- package/dist/memory/presentation.d.ts.map +1 -1
- package/dist/memory/presentation.js +25 -0
- package/dist/memory/presentation.js.map +1 -1
- package/dist/memory/store.d.ts +13 -7
- package/dist/memory/store.d.ts.map +1 -1
- package/dist/memory/store.js +37 -11
- package/dist/memory/store.js.map +1 -1
- package/dist/memory/typed.d.ts +116 -0
- package/dist/memory/typed.d.ts.map +1 -0
- package/dist/memory/typed.js +336 -0
- package/dist/memory/typed.js.map +1 -0
- package/dist/permissions/checks.d.ts +1 -1
- package/dist/permissions/checks.js +1 -1
- package/dist/permissions/headless-trust.d.ts +4 -4
- package/dist/permissions/headless-trust.d.ts.map +1 -1
- package/dist/permissions/headless-trust.js +6 -6
- package/dist/permissions/headless-trust.js.map +1 -1
- package/dist/permissions/mode.d.ts +4 -4
- package/dist/permissions/mode.js +1 -1
- package/dist/permissions/rules.d.ts +1 -1
- package/dist/permissions/rules.js +1 -1
- package/dist/termination.d.ts +75 -0
- package/dist/termination.d.ts.map +1 -0
- package/dist/termination.js +143 -0
- package/dist/termination.js.map +1 -0
- package/dist/tui/AgentExplorer.d.ts +2 -2
- package/dist/tui/AgentExplorer.js +4 -4
- package/dist/tui/AgentExplorer.js.map +1 -1
- package/dist/tui/App.d.ts +9 -1
- package/dist/tui/App.d.ts.map +1 -1
- package/dist/tui/App.js +456 -307
- package/dist/tui/App.js.map +1 -1
- package/dist/tui/LiveActivity.d.ts +1 -1
- package/dist/tui/LiveActivity.d.ts.map +1 -1
- package/dist/tui/PermissionOverlay.d.ts +8 -0
- package/dist/tui/PermissionOverlay.d.ts.map +1 -1
- package/dist/tui/PermissionOverlay.js +25 -2
- package/dist/tui/PermissionOverlay.js.map +1 -1
- package/dist/tui/Picker.js +1 -1
- package/dist/tui/agent.d.ts +211 -135
- package/dist/tui/agent.d.ts.map +1 -1
- package/dist/tui/agent.js +535 -316
- package/dist/tui/agent.js.map +1 -1
- package/dist/tui/conversation-evidence-view.js +3 -3
- package/dist/tui/conversation-evidence-view.js.map +1 -1
- package/dist/tui/conversation-history.d.ts +4 -21
- package/dist/tui/conversation-history.d.ts.map +1 -1
- package/dist/tui/conversation-history.js +4 -28
- package/dist/tui/conversation-history.js.map +1 -1
- package/dist/tui/index.d.ts.map +1 -1
- package/dist/tui/index.js +27 -1
- package/dist/tui/index.js.map +1 -1
- package/dist/tui/log-pane.js +1 -1
- package/dist/tui/markdownParser.d.ts +1 -1
- package/dist/tui/markdownParser.js +1 -1
- package/dist/tui/permission-review.d.ts +16 -0
- package/dist/tui/permission-review.d.ts.map +1 -1
- package/dist/tui/permission-review.js +25 -2
- package/dist/tui/permission-review.js.map +1 -1
- package/dist/tui/slashCommands.d.ts +30 -41
- package/dist/tui/slashCommands.d.ts.map +1 -1
- package/dist/tui/slashCommands.js +75 -66
- package/dist/tui/slashCommands.js.map +1 -1
- package/dist/tui/status-panel-layout.js +1 -1
- package/dist/tui/status-panel-layout.js.map +1 -1
- package/dist/tui/{run-interruption.d.ts → turn-interruption.d.ts} +4 -4
- package/dist/tui/turn-interruption.d.ts.map +1 -0
- package/dist/tui/{run-interruption.js → turn-interruption.js} +20 -20
- package/dist/tui/turn-interruption.js.map +1 -0
- package/dist/tui/turn-limits-settings.d.ts +39 -0
- package/dist/tui/turn-limits-settings.d.ts.map +1 -0
- package/dist/tui/{run-limits-settings.js → turn-limits-settings.js} +18 -18
- package/dist/tui/turn-limits-settings.js.map +1 -0
- package/dist/tui/types.d.ts +3 -3
- package/dist/tui/types.d.ts.map +1 -1
- package/dist/user-commands/store.d.ts +4 -4
- package/dist/user-commands/store.js +5 -5
- package/dist/user-commands/store.js.map +1 -1
- package/package.json +6 -6
- package/dist/commands/run-flags.d.ts.map +0 -1
- package/dist/commands/run-flags.js.map +0 -1
- package/dist/commands/run-stream.d.ts +0 -65
- package/dist/commands/run-stream.d.ts.map +0 -1
- package/dist/commands/run-stream.js +0 -603
- package/dist/commands/run-stream.js.map +0 -1
- package/dist/commands/run.d.ts +0 -39
- package/dist/commands/run.d.ts.map +0 -1
- package/dist/commands/run.js +0 -533
- package/dist/commands/run.js.map +0 -1
- package/dist/config/run-limits.d.ts +0 -11
- package/dist/config/run-limits.d.ts.map +0 -1
- package/dist/config/run-limits.js +0 -64
- package/dist/config/run-limits.js.map +0 -1
- package/dist/integrations/sessions/compaction-evidence.d.ts +0 -5
- package/dist/integrations/sessions/compaction-evidence.d.ts.map +0 -1
- package/dist/integrations/sessions/compaction-evidence.js +0 -59
- package/dist/integrations/sessions/compaction-evidence.js.map +0 -1
- package/dist/integrations/sessions/database.d.ts +0 -5
- package/dist/integrations/sessions/database.d.ts.map +0 -1
- package/dist/integrations/sessions/database.js +0 -34
- package/dist/integrations/sessions/database.js.map +0 -1
- package/dist/integrations/sessions/paths.d.ts +0 -7
- package/dist/integrations/sessions/paths.d.ts.map +0 -1
- package/dist/integrations/sessions/paths.js +0 -14
- package/dist/integrations/sessions/paths.js.map +0 -1
- package/dist/integrations/sessions/run-discovery.d.ts +0 -24
- package/dist/integrations/sessions/run-discovery.d.ts.map +0 -1
- package/dist/integrations/sessions/run-discovery.js +0 -142
- package/dist/integrations/sessions/run-discovery.js.map +0 -1
- package/dist/integrations/sessions/turn-evidence.d.ts +0 -129
- package/dist/integrations/sessions/turn-evidence.d.ts.map +0 -1
- package/dist/integrations/sessions/turn-evidence.js +0 -517
- package/dist/integrations/sessions/turn-evidence.js.map +0 -1
- package/dist/integrations/subagents/history.d.ts +0 -26
- package/dist/integrations/subagents/history.d.ts.map +0 -1
- package/dist/integrations/subagents/history.js +0 -76
- package/dist/integrations/subagents/history.js.map +0 -1
- package/dist/integrations/subagents/runs.d.ts +0 -72
- package/dist/integrations/subagents/runs.d.ts.map +0 -1
- package/dist/integrations/subagents/runs.js +0 -155
- package/dist/integrations/subagents/runs.js.map +0 -1
- package/dist/tui/run-interruption.d.ts.map +0 -1
- package/dist/tui/run-interruption.js.map +0 -1
- package/dist/tui/run-limits-settings.d.ts +0 -39
- package/dist/tui/run-limits-settings.d.ts.map +0 -1
- package/dist/tui/run-limits-settings.js.map +0 -1
package/dist/commands/drain.d.ts
CHANGED
|
@@ -1,4 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* `namzu drain` — one pass over a session's parked turn.
|
|
3
|
+
*
|
|
4
|
+
* namzu can park a turn in one process and continue it in another: the turn
|
|
5
|
+
* stops with `turn_paused`, a decision it waits on is a `decision_requested`
|
|
6
|
+
* record, and whoever works on a session holds its `lease.json`. This is the
|
|
7
|
+
* caller that picks such a turn up. It finds the session's active turn, takes
|
|
8
|
+
* the session's lease, continues the turn under that lease, and gives the
|
|
9
|
+
* lease back.
|
|
10
|
+
*
|
|
11
|
+
* **It is not a daemon, and `namzu serve` still says namzu has no daemon.**
|
|
12
|
+
* That refusal is not weakened by this command, it is the reason for its
|
|
13
|
+
* shape: one bounded pass, exit code says what happened, and whatever the
|
|
14
|
+
* operator already uses to run things periodically runs it again. A turn is
|
|
15
|
+
* work a process does; a drain is a process that picks up work somebody
|
|
16
|
+
* else's machine dropped.
|
|
17
|
+
*
|
|
18
|
+
* A session has at most one active turn, so one pass continues at most one.
|
|
19
|
+
*/
|
|
20
|
+
import { type ProjectId, type ResumeOutcome, type SessionId, type SessionLease, type SessionLog, type TenantId, type TurnId } from '@namzu/sdk';
|
|
2
21
|
import type { CommandDef } from './types.js';
|
|
3
22
|
export interface DrainFlags {
|
|
4
23
|
store: string | null;
|
|
@@ -22,7 +41,7 @@ export interface DrainFlags {
|
|
|
22
41
|
* would act on a scope the operator did not name.
|
|
23
42
|
*/
|
|
24
43
|
export declare function parseDrainFlags(rawArgs: readonly string[]): DrainFlags;
|
|
25
|
-
/** The scope refusal, or the scope
|
|
44
|
+
/** The scope refusal, or the scope: the full tenant → project → session prefix. */
|
|
26
45
|
export declare function resolveDrainScope(flags: Pick<DrainFlags, 'tenant' | 'project' | 'session'>): {
|
|
27
46
|
readonly error: string;
|
|
28
47
|
} | {
|
|
@@ -32,5 +51,53 @@ export declare function resolveDrainScope(flags: Pick<DrainFlags, 'tenant' | 'pr
|
|
|
32
51
|
};
|
|
33
52
|
/** A per-process holder when the operator names none. */
|
|
34
53
|
export declare function defaultHolder(): string;
|
|
54
|
+
/** What one pass over a session did with its active turn. */
|
|
55
|
+
export interface DrainPassResult {
|
|
56
|
+
/** Turns the pass found to work on: the session's active turn, if any. */
|
|
57
|
+
readonly listed: number;
|
|
58
|
+
readonly resumed: readonly TurnId[];
|
|
59
|
+
/** Parked on a human decision: reported, never resumed past. */
|
|
60
|
+
readonly awaitingDecision: readonly TurnId[];
|
|
61
|
+
/** Nothing to continue from. */
|
|
62
|
+
readonly noCheckpoint: readonly TurnId[];
|
|
63
|
+
/** Another process holds the session's lease. */
|
|
64
|
+
readonly heldByOthers: readonly TurnId[];
|
|
65
|
+
/** Settled or replaced between the listing and the claim. */
|
|
66
|
+
readonly alreadyHandled: readonly TurnId[];
|
|
67
|
+
readonly failed: readonly {
|
|
68
|
+
readonly turnId: TurnId;
|
|
69
|
+
readonly error: string;
|
|
70
|
+
}[];
|
|
71
|
+
/** The work landed but the lease could not be given back. */
|
|
72
|
+
readonly unreleased: readonly {
|
|
73
|
+
readonly turnId: TurnId;
|
|
74
|
+
readonly error: string;
|
|
75
|
+
}[];
|
|
76
|
+
}
|
|
77
|
+
/** What {@link drainSessionPass} composes. Each is one SDK call in the command. */
|
|
78
|
+
export interface DrainPassDeps {
|
|
79
|
+
/** The session's log: where its active turn is read from. */
|
|
80
|
+
readonly log: Pick<SessionLog, 'activeTurn'>;
|
|
81
|
+
/** Turns with an unresolved `decision_requested` (`SessionIndex.listPendingDecisions`). */
|
|
82
|
+
readonly pendingDecisionTurns: ReadonlySet<TurnId>;
|
|
83
|
+
/** `claimSession`: the lease, or `null` when another process holds it. */
|
|
84
|
+
claim(): Promise<SessionLease | null>;
|
|
85
|
+
/** `releaseSession`, in a `finally`. */
|
|
86
|
+
release(lease: SessionLease): Promise<void>;
|
|
87
|
+
/** Continue the turn under the lease (`resumeSession` with this host's half attached). */
|
|
88
|
+
resume(turnId: TurnId, lease: SessionLease): Promise<ResumeOutcome>;
|
|
89
|
+
readonly info: (message: string) => void;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* One pass: find the session's active turn, report it if a human owes it an
|
|
93
|
+
* answer, otherwise take the session's lease, check the turn is still the
|
|
94
|
+
* active one under that lease, continue it, and give the lease back.
|
|
95
|
+
*
|
|
96
|
+
* A turn waiting on a decision is reported before any claim. The answer
|
|
97
|
+
* belongs to a person; a drainer that continued without it would discard
|
|
98
|
+
* the question the turn stopped to ask, and claiming the session to find
|
|
99
|
+
* that out would only block the person's own resolver.
|
|
100
|
+
*/
|
|
101
|
+
export declare function drainSessionPass(deps: DrainPassDeps): Promise<DrainPassResult>;
|
|
35
102
|
export declare const drainCommand: CommandDef;
|
|
36
103
|
//# sourceMappingURL=drain.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drain.d.ts","sourceRoot":"","sources":["../../src/commands/drain.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"drain.d.ts","sourceRoot":"","sources":["../../src/commands/drain.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/drain.js
CHANGED
|
@@ -1,29 +1,36 @@
|
|
|
1
|
-
import { sessionStore } from '../integrations/sessions/database.js';
|
|
2
1
|
/**
|
|
3
|
-
* `namzu drain` — one pass over a
|
|
2
|
+
* `namzu drain` — one pass over a session's parked turn.
|
|
4
3
|
*
|
|
5
|
-
* namzu
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* caller
|
|
9
|
-
* continues
|
|
4
|
+
* namzu can park a turn in one process and continue it in another: the turn
|
|
5
|
+
* stops with `turn_paused`, a decision it waits on is a `decision_requested`
|
|
6
|
+
* record, and whoever works on a session holds its `lease.json`. This is the
|
|
7
|
+
* caller that picks such a turn up. It finds the session's active turn, takes
|
|
8
|
+
* the session's lease, continues the turn under that lease, and gives the
|
|
9
|
+
* lease back.
|
|
10
10
|
*
|
|
11
11
|
* **It is not a daemon, and `namzu serve` still says namzu has no daemon.**
|
|
12
12
|
* That refusal is not weakened by this command, it is the reason for its
|
|
13
13
|
* shape: one bounded pass, exit code says what happened, and whatever the
|
|
14
|
-
* operator already uses to run things periodically runs it again. A
|
|
15
|
-
* a process; a drain is a process that picks up
|
|
16
|
-
* machine dropped.
|
|
14
|
+
* operator already uses to run things periodically runs it again. A turn is
|
|
15
|
+
* work a process does; a drain is a process that picks up work somebody
|
|
16
|
+
* else's machine dropped.
|
|
17
17
|
*
|
|
18
|
+
* A session has at most one active turn, so one pass continues at most one.
|
|
18
19
|
*/
|
|
19
|
-
import {
|
|
20
|
+
import { existsSync, lstatSync } from 'node:fs';
|
|
21
|
+
import { join, resolve } from 'node:path';
|
|
22
|
+
import { DiskSessionLog, InvalidIdError, SessionPaths, asProjectId, asSessionId, asTenantId, claimSession, openSessionIndex, releaseSession, } from '@namzu/sdk';
|
|
20
23
|
import { resolveTrustedProjectContext } from '../config/trusted-project-context.js';
|
|
21
|
-
import { EXIT_UNTRUSTED, EXIT_USAGE } from '../exit-codes.js';
|
|
22
|
-
import {
|
|
24
|
+
import { EXIT_FAIL, EXIT_UNTRUSTED, EXIT_USAGE } from '../exit-codes.js';
|
|
25
|
+
import { readSessionStart } from '../integrations/resident/session-log-reads.js';
|
|
23
26
|
import { contextLogging, createStderrSink, installCliLogging } from '../logging.js';
|
|
24
27
|
import { decideHeadlessTrust } from '../permissions/headless-trust.js';
|
|
25
28
|
import { compilePermissions } from '../permissions/rules.js';
|
|
26
|
-
import { applyProviderFlags, resolveWorkingDirectory } from './
|
|
29
|
+
import { applyProviderFlags, resolveWorkingDirectory } from './exec-flags.js';
|
|
30
|
+
/** The drain flags name a scope the store does not hold: an argument error, exit 64. */
|
|
31
|
+
class DrainScopeError extends Error {
|
|
32
|
+
name = 'DrainScopeError';
|
|
33
|
+
}
|
|
27
34
|
/** Lease length when the operator names none. Long enough for a real turn. */
|
|
28
35
|
const DEFAULT_TTL_MS = 600_000;
|
|
29
36
|
/**
|
|
@@ -94,25 +101,25 @@ export function parseDrainFlags(rawArgs) {
|
|
|
94
101
|
}
|
|
95
102
|
// Anything unrecognised is refused rather than ignored, positional
|
|
96
103
|
// arguments included — this command takes none. It acts on other
|
|
97
|
-
// people's
|
|
104
|
+
// people's turns; a typo'd `--tenant` that fell through would drain a
|
|
98
105
|
// scope nobody asked for.
|
|
99
106
|
out.unknown.push(a.split('=')[0]);
|
|
100
107
|
}
|
|
101
108
|
return out;
|
|
102
109
|
}
|
|
103
|
-
/** The scope refusal, or the scope
|
|
110
|
+
/** The scope refusal, or the scope: the full tenant → project → session prefix. */
|
|
104
111
|
export function resolveDrainScope(flags) {
|
|
105
112
|
if (!flags.tenant) {
|
|
106
113
|
return {
|
|
107
|
-
error: '--tenant is required. A
|
|
114
|
+
error: '--tenant is required. A drain with no tenant is a cross-tenant read with a friendly name, so there is no default to fall back to.',
|
|
108
115
|
};
|
|
109
116
|
}
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
117
|
+
// A parked turn belongs to one session, and the session to one project;
|
|
118
|
+
// the pass checks both against the session's own log before it claims
|
|
119
|
+
// anything, so it asks for the full prefix.
|
|
113
120
|
if (!flags.project || !flags.session) {
|
|
114
121
|
return {
|
|
115
|
-
error: '--project and --session are both required
|
|
122
|
+
error: '--project and --session are both required: a turn is drained within one session, and the pass checks that the session belongs to that project before it claims anything.',
|
|
116
123
|
};
|
|
117
124
|
}
|
|
118
125
|
// Prefix-checked, and refused in THIS function's shape rather than by
|
|
@@ -120,7 +127,7 @@ export function resolveDrainScope(flags) {
|
|
|
120
127
|
// sentence; an `InvalidIdError` escaping to the top level would be the one
|
|
121
128
|
// that arrives as a stack trace. A typo'd `--tenant prj_x` previously
|
|
122
129
|
// reached the store as a TenantId and listed nothing, which reads as "no
|
|
123
|
-
//
|
|
130
|
+
// turns" rather than "wrong flag".
|
|
124
131
|
try {
|
|
125
132
|
return {
|
|
126
133
|
tenantId: asTenantId(flags.tenant),
|
|
@@ -141,9 +148,86 @@ export function resolveDrainScope(flags) {
|
|
|
141
148
|
export function defaultHolder() {
|
|
142
149
|
return `namzu-drain-${process.pid}-${Date.now().toString(36)}`;
|
|
143
150
|
}
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
|
|
151
|
+
function errorText(error) {
|
|
152
|
+
return error instanceof Error ? error.message : String(error);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* One pass: find the session's active turn, report it if a human owes it an
|
|
156
|
+
* answer, otherwise take the session's lease, check the turn is still the
|
|
157
|
+
* active one under that lease, continue it, and give the lease back.
|
|
158
|
+
*
|
|
159
|
+
* A turn waiting on a decision is reported before any claim. The answer
|
|
160
|
+
* belongs to a person; a drainer that continued without it would discard
|
|
161
|
+
* the question the turn stopped to ask, and claiming the session to find
|
|
162
|
+
* that out would only block the person's own resolver.
|
|
163
|
+
*/
|
|
164
|
+
export async function drainSessionPass(deps) {
|
|
165
|
+
const result = {
|
|
166
|
+
listed: 0,
|
|
167
|
+
resumed: [],
|
|
168
|
+
awaitingDecision: [],
|
|
169
|
+
noCheckpoint: [],
|
|
170
|
+
heldByOthers: [],
|
|
171
|
+
alreadyHandled: [],
|
|
172
|
+
failed: [],
|
|
173
|
+
unreleased: [],
|
|
174
|
+
};
|
|
175
|
+
const active = await deps.log.activeTurn();
|
|
176
|
+
if (!active)
|
|
177
|
+
return result;
|
|
178
|
+
result.listed = 1;
|
|
179
|
+
const { turnId } = active;
|
|
180
|
+
if (deps.pendingDecisionTurns.has(turnId)) {
|
|
181
|
+
result.awaitingDecision.push(turnId);
|
|
182
|
+
deps.info(`⏸ ${turnId} · waiting on a human decision`);
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
const lease = await deps.claim();
|
|
186
|
+
if (!lease) {
|
|
187
|
+
result.heldByOthers.push(turnId);
|
|
188
|
+
deps.info(`⋯ ${turnId} · another process holds the session`);
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
deps.info(`⏵ ${turnId} · ${active.state} · fence ${lease.fence}`);
|
|
193
|
+
// Re-read under the lease: between the listing and the claim, the
|
|
194
|
+
// previous holder may have settled the turn, or a new one begun.
|
|
195
|
+
const current = await deps.log.activeTurn({ lease });
|
|
196
|
+
if (current?.turnId !== turnId) {
|
|
197
|
+
result.alreadyHandled.push(turnId);
|
|
198
|
+
deps.info(`↷ ${turnId} · settled before this pass took it`);
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
const outcome = await deps.resume(turnId, lease);
|
|
202
|
+
if (outcome.resumed) {
|
|
203
|
+
if (outcome.turn.status === 'failed' || outcome.turn.status === 'cancelled') {
|
|
204
|
+
throw new Error(`Resumed turn ended with status "${outcome.turn.status}".`);
|
|
205
|
+
}
|
|
206
|
+
result.resumed.push(turnId);
|
|
207
|
+
deps.info(`✔ ${turnId} · ${outcome.turn.status}`);
|
|
208
|
+
}
|
|
209
|
+
else if (outcome.reason === 'awaiting-decision') {
|
|
210
|
+
// The index had not yet seen the decision; the log had.
|
|
211
|
+
result.awaitingDecision.push(turnId);
|
|
212
|
+
deps.info(`⏸ ${turnId} · waiting on a human decision`);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
result.noCheckpoint.push(turnId);
|
|
216
|
+
deps.info(`∅ ${turnId} · no checkpoint to continue from`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
result.failed.push({ turnId, error: errorText(error) });
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
try {
|
|
224
|
+
await deps.release(lease);
|
|
225
|
+
}
|
|
226
|
+
catch (error) {
|
|
227
|
+
result.unreleased.push({ turnId, error: errorText(error) });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
return result;
|
|
147
231
|
}
|
|
148
232
|
function defaultPrefs(detected) {
|
|
149
233
|
const first = detected[0];
|
|
@@ -155,35 +239,45 @@ function defaultPrefs(detected) {
|
|
|
155
239
|
}
|
|
156
240
|
: null;
|
|
157
241
|
}
|
|
242
|
+
/** `--store` must already be a namzu home: a drain never creates state where it looks. */
|
|
243
|
+
function isNamzuHome(home) {
|
|
244
|
+
const projects = join(home, 'projects');
|
|
245
|
+
if (!existsSync(projects))
|
|
246
|
+
return false;
|
|
247
|
+
const entry = lstatSync(projects);
|
|
248
|
+
return entry.isDirectory() && !entry.isSymbolicLink();
|
|
249
|
+
}
|
|
158
250
|
export const drainCommand = {
|
|
159
251
|
name: 'drain',
|
|
160
|
-
description: 'Continue
|
|
252
|
+
description: 'Continue a turn another process left behind (one pass, then exit)',
|
|
161
253
|
passThrough: true,
|
|
162
254
|
help: [
|
|
163
255
|
'Usage: namzu drain --store <dir> --tenant <id> --project <id> --session <id>',
|
|
164
256
|
'',
|
|
165
|
-
|
|
166
|
-
'it from its last checkpoint
|
|
167
|
-
'has no daemon, and this is a
|
|
257
|
+
"Take the session's parked or interrupted turn if no worker holds the",
|
|
258
|
+
'session, continue it from its last checkpoint under the session lease, and',
|
|
259
|
+
'release the lease. One pass, then exit — namzu has no daemon, and this is a',
|
|
260
|
+
'command your scheduler runs, not a service.',
|
|
168
261
|
'',
|
|
169
262
|
'Options:',
|
|
170
|
-
' --store <dir> The
|
|
263
|
+
' --store <dir> The namzu home whose sessions to drain (NAMZU_HOME)',
|
|
171
264
|
' --tenant <id> Isolation boundary. Required; there is no default',
|
|
172
|
-
' --project <id> Required —
|
|
173
|
-
' --session <id> Required
|
|
174
|
-
' --holder <id> Who is taking the
|
|
265
|
+
' --project <id> Required — checked against the session log',
|
|
266
|
+
' --session <id> Required: the session whose turn to continue',
|
|
267
|
+
' --holder <id> Who is taking the session. Must be unique PER PROCESS',
|
|
175
268
|
' --ttl <ms> Lease length (default 600000)',
|
|
176
|
-
' --max-concurrent <n>
|
|
177
|
-
'
|
|
269
|
+
' --max-concurrent <n> Turns in flight at once (default 1). A session has',
|
|
270
|
+
' one active turn, so a pass continues at most one',
|
|
271
|
+
' --cwd <path> Directory the resumed turn works in',
|
|
178
272
|
' --provider <id> Provider to continue with',
|
|
179
273
|
' --model <id> Model to continue with',
|
|
180
274
|
' --trust Accept this folder for this pass',
|
|
181
275
|
'',
|
|
182
|
-
'A
|
|
276
|
+
'A turn that is parked on a human decision is REPORTED, never resumed past.',
|
|
183
277
|
'The answer belongs to a person; a drainer that continued without it would',
|
|
184
|
-
'discard the question the
|
|
278
|
+
'discard the question the turn stopped to ask.',
|
|
185
279
|
'',
|
|
186
|
-
'Exit codes: 0 when every
|
|
280
|
+
'Exit codes: 0 when every turn it took was continued, 1 when any turn failed,',
|
|
187
281
|
'64 when an argument is wrong, 77 when the folder has not been trusted.',
|
|
188
282
|
].join('\n'),
|
|
189
283
|
handler: async ({ ctx: bootstrapCtx, rawArgs }) => {
|
|
@@ -197,7 +291,7 @@ export const drainCommand = {
|
|
|
197
291
|
}
|
|
198
292
|
if (!flags.store) {
|
|
199
293
|
ctx.formatter.error({
|
|
200
|
-
message: '--store is required: name the
|
|
294
|
+
message: '--store is required: name the namzu home whose sessions to drain. There is no default, because an empty queue and a directory nobody writes to would report the same thing.',
|
|
201
295
|
});
|
|
202
296
|
return EXIT_USAGE;
|
|
203
297
|
}
|
|
@@ -213,6 +307,13 @@ export const drainCommand = {
|
|
|
213
307
|
});
|
|
214
308
|
return EXIT_USAGE;
|
|
215
309
|
}
|
|
310
|
+
if (flags.maxConcurrent !== null &&
|
|
311
|
+
(!Number.isSafeInteger(flags.maxConcurrent) || flags.maxConcurrent < 1)) {
|
|
312
|
+
ctx.formatter.error({
|
|
313
|
+
message: `--max-concurrent must be a whole number above zero, got ${flags.maxConcurrent}`,
|
|
314
|
+
});
|
|
315
|
+
return EXIT_USAGE;
|
|
316
|
+
}
|
|
216
317
|
const resolved = resolveWorkingDirectory(flags.cwd);
|
|
217
318
|
if ('error' in resolved) {
|
|
218
319
|
ctx.formatter.error({ message: resolved.error });
|
|
@@ -231,7 +332,7 @@ export const drainCommand = {
|
|
|
231
332
|
// The CLI owns stderr, not the kernel it drives (LOG-05) — a live sink
|
|
232
333
|
// at the level --verbose/--quiet/NAMZU_LOG_LEVEL named, instead of
|
|
233
334
|
// forcing the level to `silent` via `configureLogger`. `{ replace:
|
|
234
|
-
// true }`: see the identical comment in `
|
|
335
|
+
// true }`: see the identical comment in `exec.ts` — a real invocation
|
|
235
336
|
// calls this once, this package's own tests call a handler's more
|
|
236
337
|
// than once per process.
|
|
237
338
|
const logging = contextLogging(ctx);
|
|
@@ -253,54 +354,64 @@ export const drainCommand = {
|
|
|
253
354
|
: `permissions.${diagnostic.tool}`;
|
|
254
355
|
ctx.formatter.error({ message: `${where}: ${diagnostic.message}` });
|
|
255
356
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
catch (error) {
|
|
261
|
-
ctx.formatter.error({
|
|
262
|
-
message: `application state is unavailable: ${error instanceof Error ? error.message : String(error)}`,
|
|
263
|
-
});
|
|
264
|
-
return 1;
|
|
265
|
-
}
|
|
266
|
-
let conversationStore;
|
|
267
|
-
let owningSession;
|
|
357
|
+
const home = resolve(flags.store);
|
|
358
|
+
let log;
|
|
359
|
+
let topicId;
|
|
360
|
+
let pendingDecisionTurns;
|
|
268
361
|
try {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
362
|
+
if (!isNamzuHome(home))
|
|
363
|
+
throw new DrainScopeError(`${home} holds no projects/ directory`);
|
|
364
|
+
// The index is derived from the logs; opening it brings it up to date.
|
|
365
|
+
const index = await openSessionIndex({ home });
|
|
366
|
+
try {
|
|
367
|
+
const indexed = await index.getSession(scope.sessionId);
|
|
368
|
+
if (!indexed || indexed.projectId !== scope.projectId) {
|
|
369
|
+
throw new DrainScopeError(`Session ${scope.sessionId} is not persisted under Project ${scope.projectId}`);
|
|
370
|
+
}
|
|
371
|
+
if (indexed.depth > 0) {
|
|
372
|
+
throw new DrainScopeError(`Session ${scope.sessionId} is a child session; drain its root session ${indexed.rootId}`);
|
|
373
|
+
}
|
|
374
|
+
const paths = new SessionPaths({ home, slug: indexed.slug });
|
|
375
|
+
const opened = await readSessionStart(home, paths.sessionLog({ sessionId: scope.sessionId }));
|
|
376
|
+
if (!opened ||
|
|
377
|
+
opened.sessionId !== scope.sessionId ||
|
|
378
|
+
opened.projectId !== scope.projectId ||
|
|
379
|
+
opened.tenantId !== scope.tenantId) {
|
|
380
|
+
throw new DrainScopeError(`Session ${scope.sessionId} was not opened under Tenant ${scope.tenantId} and Project ${scope.projectId}`);
|
|
381
|
+
}
|
|
382
|
+
if (!opened.topicId) {
|
|
383
|
+
throw new DrainScopeError(`Session ${scope.sessionId} records no topic to continue under`);
|
|
384
|
+
}
|
|
385
|
+
topicId = opened.topicId;
|
|
386
|
+
log = DiskSessionLog.at(paths, { sessionId: scope.sessionId });
|
|
387
|
+
pendingDecisionTurns = new Set((await index.listPendingDecisions({ sessionId: scope.sessionId })).map((decision) => decision.turnId));
|
|
388
|
+
}
|
|
389
|
+
finally {
|
|
390
|
+
index.close();
|
|
276
391
|
}
|
|
277
392
|
}
|
|
278
393
|
catch (error) {
|
|
279
394
|
ctx.formatter.error({
|
|
280
|
-
message: `cannot resolve the drain session
|
|
395
|
+
message: `cannot resolve the drain session: ${errorText(error)}`,
|
|
281
396
|
});
|
|
282
|
-
|
|
397
|
+
// The flags named something that is not there (no namzu home, an
|
|
398
|
+
// unknown or child session, a scope mismatch): the caller's to fix.
|
|
399
|
+
// Anything else is state that could not be read, which is exit 1.
|
|
400
|
+
return error instanceof DrainScopeError ? EXIT_USAGE : EXIT_FAIL;
|
|
283
401
|
}
|
|
284
402
|
const session = await createAgentSession(prefs, probe.detected, {
|
|
285
403
|
cwd,
|
|
286
404
|
scope: {
|
|
287
|
-
// The
|
|
288
|
-
//
|
|
289
|
-
//
|
|
290
|
-
// never named, then resume the
|
|
405
|
+
// The session is exactly the one the required flags named.
|
|
406
|
+
// Constructing the provider under a random CLI Project and
|
|
407
|
+
// Session would create generated state for a scope the operator
|
|
408
|
+
// never named, then resume the turn under a different one.
|
|
291
409
|
sessionId: scope.sessionId,
|
|
292
|
-
topicId
|
|
293
|
-
projectId: scope.projectId,
|
|
294
|
-
tenantId: scope.tenantId,
|
|
295
|
-
},
|
|
296
|
-
stateRoot,
|
|
297
|
-
conversationSessions: {
|
|
298
|
-
store: conversationStore,
|
|
299
|
-
root: stateRoot,
|
|
410
|
+
topicId,
|
|
300
411
|
projectId: scope.projectId,
|
|
301
412
|
tenantId: scope.tenantId,
|
|
302
|
-
topicId: owningSession.topicId,
|
|
303
413
|
},
|
|
414
|
+
stateRoot: home,
|
|
304
415
|
rules: permissions.rules,
|
|
305
416
|
permissionMode: 'auto',
|
|
306
417
|
...(ctx.config.limits ? { limits: ctx.config.limits } : {}),
|
|
@@ -323,87 +434,56 @@ export const drainCommand = {
|
|
|
323
434
|
});
|
|
324
435
|
return 1;
|
|
325
436
|
}
|
|
326
|
-
// `resolveDrainScope` refuses a partial prefix above, so all three are
|
|
327
|
-
// present here; the store's attribution has no optional fields because
|
|
328
|
-
// a listing that guessed one would file runs under a project nobody
|
|
329
|
-
// named.
|
|
330
|
-
const store = new DiskCheckpointStore({ baseDir: flags.store }, {
|
|
331
|
-
tenantId: scope.tenantId,
|
|
332
|
-
projectId: scope.projectId,
|
|
333
|
-
sessionId: scope.sessionId,
|
|
334
|
-
});
|
|
335
437
|
const holder = flags.holder ?? defaultHolder();
|
|
336
|
-
const awaiting = [];
|
|
337
|
-
const empty = [];
|
|
338
438
|
try {
|
|
339
|
-
const result = await
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
holder,
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
ctx.formatter.info(`✔ ${entry.runId} · ${outcome.run.status}`);
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
// Reported, not resumed past. The two non-resumed outcomes mean
|
|
363
|
-
// opposite things and an operator has to be able to tell them
|
|
364
|
-
// apart: one is a question waiting on a person, the other is a
|
|
365
|
-
// run with nothing to continue.
|
|
366
|
-
if (outcome.reason === 'awaiting-decision') {
|
|
367
|
-
awaiting.push(entry.runId);
|
|
368
|
-
ctx.formatter.info(`⏸ ${entry.runId} · waiting on a human decision`);
|
|
369
|
-
}
|
|
370
|
-
else {
|
|
371
|
-
empty.push(entry.runId);
|
|
372
|
-
ctx.formatter.info(`∅ ${entry.runId} · no checkpoint to continue from`);
|
|
373
|
-
}
|
|
374
|
-
},
|
|
439
|
+
const result = await drainSessionPass({
|
|
440
|
+
log,
|
|
441
|
+
pendingDecisionTurns,
|
|
442
|
+
claim: () => claimSession(scope.sessionId, { holder, ttlMs, log }),
|
|
443
|
+
release: (lease) => releaseSession(scope.sessionId, lease, { log }),
|
|
444
|
+
resume: (turnId, lease) => session.resumeDurable({
|
|
445
|
+
entry: {
|
|
446
|
+
tenantId: scope.tenantId,
|
|
447
|
+
projectId: scope.projectId,
|
|
448
|
+
sessionId: scope.sessionId,
|
|
449
|
+
turnId,
|
|
450
|
+
},
|
|
451
|
+
sessionLog: log,
|
|
452
|
+
// The reason the lease is handed over at all: every record
|
|
453
|
+
// the resumed turn appends carries its fence, so a drainer
|
|
454
|
+
// that stalled past its lease cannot write over whoever
|
|
455
|
+
// took the session over.
|
|
456
|
+
lease,
|
|
457
|
+
}),
|
|
458
|
+
info: (message) => ctx.formatter.info(message),
|
|
375
459
|
});
|
|
376
460
|
ctx.formatter.print({
|
|
377
461
|
listed: result.listed,
|
|
378
|
-
resumed: result.
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
alreadyHandled: result.stale,
|
|
462
|
+
resumed: result.resumed.length,
|
|
463
|
+
awaitingDecision: result.awaitingDecision,
|
|
464
|
+
noCheckpoint: result.noCheckpoint,
|
|
465
|
+
heldByOthers: result.heldByOthers,
|
|
466
|
+
alreadyHandled: result.alreadyHandled,
|
|
384
467
|
failed: result.failed,
|
|
385
468
|
unreleased: result.unreleased,
|
|
386
469
|
});
|
|
387
470
|
for (const f of result.failed) {
|
|
388
|
-
ctx.formatter.error({ message: `${f.
|
|
471
|
+
ctx.formatter.error({ message: `${f.turnId}: ${f.error}` });
|
|
389
472
|
}
|
|
390
473
|
// A drain that could not give a lease back is reported too. The work
|
|
391
|
-
// landed, but the
|
|
474
|
+
// landed, but the session is unavailable to the next reader until the
|
|
392
475
|
// lease lapses, and an operator watching throughput needs to know.
|
|
393
476
|
for (const u of result.unreleased) {
|
|
394
477
|
ctx.formatter.error({
|
|
395
|
-
message: `${u.
|
|
478
|
+
message: `${u.turnId}: lease not released — ${u.error}`,
|
|
396
479
|
});
|
|
397
480
|
}
|
|
398
481
|
return result.failed.length > 0 ? 1 : 0;
|
|
399
482
|
}
|
|
400
483
|
catch (err) {
|
|
401
|
-
//
|
|
402
|
-
//
|
|
403
|
-
|
|
404
|
-
ctx.formatter.error({
|
|
405
|
-
message: err instanceof Error ? err.message : String(err),
|
|
406
|
-
});
|
|
484
|
+
// A refusal from the log or the lease lands here. Reported rather
|
|
485
|
+
// than swallowed into an empty pass that reads as "nothing was parked".
|
|
486
|
+
ctx.formatter.error({ message: errorText(err) });
|
|
407
487
|
return 1;
|
|
408
488
|
}
|
|
409
489
|
finally {
|