@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/tui/agent.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createCurrentCredentialReader } from '../integrations/providers/current-credential.js';
|
|
2
|
-
import { CliPathBuilder } from '../integrations/sessions/paths.js';
|
|
3
2
|
import { createWebSearchTool, resolveWebSearch, webSearchLabel, } from '../integrations/web/search.js';
|
|
4
3
|
/**
|
|
5
4
|
* TUI agent session — provider-direct, tool-enabled.
|
|
@@ -14,26 +13,28 @@ import { createWebSearchTool, resolveWebSearch, webSearchLabel, } from '../integ
|
|
|
14
13
|
* Unlike the earlier `chatStream()`-only adapter, this drives the full
|
|
15
14
|
* tool-execution loop: the model can call tools, their results are fed
|
|
16
15
|
* back, and the loop iterates until the turn settles. We translate the
|
|
17
|
-
* SDK's `
|
|
16
|
+
* SDK's `SessionEvent` stream into the TUI's smaller `AgentEvent` vocabulary
|
|
18
17
|
* (text deltas + tool start/end + done/error).
|
|
19
18
|
*
|
|
20
|
-
* The TUI
|
|
21
|
-
* every turn
|
|
19
|
+
* The TUI holds the conversation it renders and passes the full `Message[]`
|
|
20
|
+
* on every turn; the session's log, which the kernel appends to while the
|
|
21
|
+
* turn runs, is what a later resume folds back. Empty / partial states (no
|
|
22
22
|
* credentials, no preferences, no matching detected provider) return an
|
|
23
23
|
* `emptySession()` whose `send()` yields a single error event so the UI
|
|
24
24
|
* renders an actionable hint rather than crashing.
|
|
25
25
|
*/
|
|
26
|
-
import { BOOT_EVENT_NAMES, BackgroundJobRegistry,
|
|
26
|
+
import { BOOT_EVENT_NAMES, BackgroundJobRegistry, DiskSessionCheckpointStore, DiskSessionLog, DiskTaskStore, EVENT_NAME_ATTRIBUTE, GuardedFetchProvider, InMemorySessionLog, MarkdownMemoryStore, PromptContributionRegistry, ProviderRegistry, SESSION_GOAL_TOOL_NAMES, SessionPaths, ToolRegistry, WebFetchTool, abandonTurn, batchNeedsReview, buildAskUserQuestionTool, buildMemoryTools, buildResidentHistoryTools, buildResidentToolEvidenceTools, buildSessionGoalTools, compactNow, compactSession, createComputerUseTool, createFileReadTracker, createMemoryPromoter, createMemoryRecallStep, createResidentStepContext, createResidentStepContributions, createReviewHandler, createToolPresenter, ensureProject, generateSessionId, generateTenantId, generateTopicId, generateTurnId, getBuiltinTools, isReviewExempt, isTurnInProgressError, query, resumeSession, seedObservationLedger, webGuidanceContribution, withProviderFallback, } from '@namzu/sdk';
|
|
27
27
|
import { SubprocessComputerUseHost } from '@namzu/computer-use';
|
|
28
28
|
import { realpath, stat } from 'node:fs/promises';
|
|
29
|
-
import {
|
|
29
|
+
import { parse, resolve } from 'node:path';
|
|
30
30
|
import { FileCheckpointStore } from '../checkpoints/store.js';
|
|
31
31
|
import { CHECKPOINTED_TOOLS, withCheckpoints } from '../checkpoints/wrap.js';
|
|
32
|
-
import { readStoredRunGuards, resolveRunGuards } from '../config/run-limits.js';
|
|
33
32
|
import { configuredPassthroughTools, resolveToolResultScreens, unmatchedPassthroughTools, } from '../config/tool-result-screens.js';
|
|
33
|
+
import { readStoredTurnGuards, resolveTurnGuards } from '../config/turn-guards.js';
|
|
34
34
|
import { probeCapabilities } from '../context/capabilities.js';
|
|
35
|
+
import { createSessionDirectories } from '../context/directories.js';
|
|
35
36
|
import { NAMZU_DELEGATION_DOCTRINE, NAMZU_ORCHESTRATE_DOCTRINE, NAMZU_PLAN_MODE_DOCTRINE, NAMZU_WORKING_DOCTRINE, } from '../context/doctrine.js';
|
|
36
|
-
import { composeEnvironmentPrompt, readEnvironmentFacts } from '../context/environment.js';
|
|
37
|
+
import { composeEnvironmentPrompt, detectWsl, readEnvironmentFacts, } from '../context/environment.js';
|
|
37
38
|
import { ProjectInstructionTracker } from '../context/project-tracker.js';
|
|
38
39
|
import { resolveSandbox, sandboxResolvedSeverity, } from '../context/sandbox.js';
|
|
39
40
|
import { composeTurnSnapshot, readTurnSnapshot } from '../context/turn-snapshot.js';
|
|
@@ -44,23 +45,29 @@ import { canSelectModel } from '../integrations/providers/access.js';
|
|
|
44
45
|
import { createGeminiAccessTokenResolver } from '../integrations/providers/gemini-credentials.js';
|
|
45
46
|
import { CredentialRefreshRejectedError, CredentialWithdrawnError, PROVIDER_REGISTRY, chainCapabilityDisagreements, chainPositionName, describeAcceptedMismatch, describeCapabilityRefusal, discoverProviders, ensureFreshAnthropicToken, ensureFreshStoredCodexCredential, ensureRegistered, findDetected, isAnthropicOAuthToken, isRegistered, missingCredentialMessage, primaryProvider, readCodexCredentialFile, readPreferences, readSubscriptionCredential, resolveChainCapabilities, sameOAuthCredential, unresolvedMembers, unsupportedProviderMessage, } from '../integrations/providers/index.js';
|
|
46
47
|
import { modelReasoningView } from '../integrations/providers/model-reasoning.js';
|
|
47
|
-
import {
|
|
48
|
+
import { activeZenCatalogue, isOfferableModel } from '../integrations/providers/zen-catalogue.js';
|
|
49
|
+
import { sessionLogCheckpointView } from '../integrations/sessions/checkpoint-view.js';
|
|
48
50
|
import { createContextInventoryStep } from '../integrations/sessions/context-inventory.js';
|
|
49
51
|
import { CONVERSATION_EVIDENCE_GUIDANCE, buildConversationReadTool, buildConversationSearchTool, releaseConversationEvidence, } from '../integrations/sessions/conversation-search.js';
|
|
50
52
|
import { createConversationEvidenceRecall } from '../integrations/sessions/evidence-recall.js';
|
|
53
|
+
import { ensureSessionStarted } from '../integrations/sessions/store.js';
|
|
51
54
|
import { createTaskContextStep } from '../integrations/sessions/task-context.js';
|
|
55
|
+
import { resolveNamzuHome } from '../integrations/state/home.js';
|
|
52
56
|
import { ensurePrivateStateDirectory } from '../integrations/state/private-directory.js';
|
|
57
|
+
import { cliProjectRoot } from '../integrations/state/project.js';
|
|
58
|
+
import { CLI_CHECKPOINT_RETENTION } from '../integrations/state/retention.js';
|
|
59
|
+
import { listSavedBatches } from '../integrations/subagents/batches.js';
|
|
53
60
|
import { discoverAgentDefinitions } from '../integrations/subagents/definitions.js';
|
|
54
|
-
import { createDelegationHistoryStep } from '../integrations/subagents/history.js';
|
|
55
61
|
import { prepareDelegatedEffort } from '../integrations/subagents/model-effort.js';
|
|
56
|
-
import {
|
|
62
|
+
import { resolveSubagentParent } from '../integrations/subagents/parent.js';
|
|
57
63
|
import { replaySavedChildrenFor } from '../integrations/subagents/replay.js';
|
|
58
|
-
import { listSavedOrchestrationRuns, } from '../integrations/subagents/runs.js';
|
|
59
64
|
import { createSubagentRuntime } from '../integrations/subagents/runtime.js';
|
|
65
|
+
import { createSavedAgentHistory, createSavedAgentsStep, } from '../integrations/subagents/saved-agents.js';
|
|
60
66
|
import { cliLogger } from '../logging.js';
|
|
61
67
|
import { formatMemoryDiagnostics } from '../memory/presentation.js';
|
|
62
68
|
import { composeMemoryPrompt, readMemory } from '../memory/store.js';
|
|
63
|
-
import {
|
|
69
|
+
import { composeStoredMemoryPrompt, describeCuratedNotesImport, describeMemoryMigration, importCuratedNotes, migrateMemoryOnce, saveTypedNote, } from '../memory/typed.js';
|
|
70
|
+
import { projectTurnConversation } from './conversation-history.js';
|
|
64
71
|
import { buildSwitchModelTool } from './model-switch-tool.js';
|
|
65
72
|
import { resolveModelSwitch } from './model-switch.js';
|
|
66
73
|
/**
|
|
@@ -438,10 +445,10 @@ function builtinTools(backgroundJobs) {
|
|
|
438
445
|
return [tool.name === 'bash' ? foregroundOnlyBash(tool) : tool];
|
|
439
446
|
});
|
|
440
447
|
}
|
|
441
|
-
function buildToolRegistry(
|
|
442
|
-
// Configured here rather than on the
|
|
443
|
-
// builds for a
|
|
444
|
-
// registries below, which a
|
|
448
|
+
function buildToolRegistry(paths, backgroundJobs, checkpoints, screens) {
|
|
449
|
+
// Configured here rather than on the turn, so every registry this CLI
|
|
450
|
+
// builds for a turn carries the operator's choice — including the sub-agent
|
|
451
|
+
// registries below, which a turn-level option would reach only if each
|
|
445
452
|
// child's config were threaded as well. An absent key stays absent, so the
|
|
446
453
|
// kernel's default applies exactly as it does for any other host.
|
|
447
454
|
const screensConfig = resolveToolResultScreens(screens);
|
|
@@ -459,21 +466,20 @@ function buildToolRegistry(cwd, projectStateRoot = join(cwd, '.namzu'), backgrou
|
|
|
459
466
|
registry.register(withCheckpoints(tool, checkpoints));
|
|
460
467
|
}
|
|
461
468
|
}
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
//
|
|
466
|
-
//
|
|
467
|
-
const
|
|
468
|
-
const
|
|
469
|
-
const memoryStore = new DiskMemoryStore({ baseDir: projectStateRoot, directory });
|
|
469
|
+
// Stored memory: the agent gets search_memory / read_memory / save_memory
|
|
470
|
+
// over typed Markdown files, one per memory, in this project's `memory/`
|
|
471
|
+
// under the application home (`projects/<slug>/memory`), so every
|
|
472
|
+
// workspace keeps its own. Separate from the operator-curated files, which
|
|
473
|
+
// are prompt text.
|
|
474
|
+
const directory = ensurePrivateStateDirectory(paths.projectDir(), 'memory');
|
|
475
|
+
const memoryStore = new MarkdownMemoryStore({ directory });
|
|
470
476
|
// Search through the store's async boundary. Its concrete index is lazy:
|
|
471
477
|
// handing `getIndex()` to the synchronous overload before the first store
|
|
472
478
|
// read makes a new process report every persisted memory as absent.
|
|
473
479
|
registry.register(buildMemoryTools(memoryStore));
|
|
474
480
|
// query() mounts search_tools only if a deferred roster actually exists,
|
|
475
481
|
// after runtime tools are registered. Ordinary CLI task tools are active.
|
|
476
|
-
return { registry, memoryStore };
|
|
482
|
+
return { registry, memoryStore, memoryDirectory: directory };
|
|
477
483
|
}
|
|
478
484
|
/** Refresh plugin skill metadata before each provider operation. */
|
|
479
485
|
async function currentPluginSkills(registry) {
|
|
@@ -483,7 +489,6 @@ async function currentPluginSkills(registry) {
|
|
|
483
489
|
return registry.list();
|
|
484
490
|
}
|
|
485
491
|
export async function createAgentSession(prefs, detected, options = {}) {
|
|
486
|
-
const scope = options.scope ?? mintScope();
|
|
487
492
|
const fileObservations = new Map();
|
|
488
493
|
/**
|
|
489
494
|
* This process's observation ledger for one conversation, seeded from that
|
|
@@ -552,21 +557,34 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
552
557
|
cwd === parse(cwd).root) {
|
|
553
558
|
return emptySession(`Working-directory sandboxing refuses filesystem root ${cwd}; choose a project directory so confinement has a boundary, or explicitly select an ephemeral workspace.`, 'invocation');
|
|
554
559
|
}
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
560
|
+
// Generated state never defaults into the working directory. It used to:
|
|
561
|
+
// `<cwd>/.namzu` for any caller without a state root, which put runtime
|
|
562
|
+
// trees inside checkouts (and, run from the home directory, made the
|
|
563
|
+
// project root and the application home the same directory). It lives in
|
|
564
|
+
// the checkout's project under the application home: `projects/<slug>/`.
|
|
565
|
+
let paths;
|
|
566
|
+
let projectId;
|
|
558
567
|
try {
|
|
559
|
-
|
|
568
|
+
if (options.conversationSessions) {
|
|
569
|
+
paths = options.conversationSessions.paths;
|
|
570
|
+
projectId = options.conversationSessions.projectId;
|
|
571
|
+
}
|
|
572
|
+
else {
|
|
573
|
+
const home = resolve(options.stateRoot ?? resolveNamzuHome());
|
|
574
|
+
ensurePrivateStateDirectory(home, 'projects');
|
|
575
|
+
const project = await ensureProject({ home, cwd: cliProjectRoot(cwd) });
|
|
576
|
+
paths = new SessionPaths({ home, slug: project.slug });
|
|
577
|
+
projectId = project.projectId;
|
|
578
|
+
}
|
|
560
579
|
// Refuse an aliased or otherwise unsafe generated-state root before any
|
|
561
|
-
// provider, sandbox or plugin runtime is constructed.
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
// symlink.
|
|
565
|
-
ensurePrivateStateDirectory(projectStateRoot, 'memory');
|
|
580
|
+
// provider, sandbox or plugin runtime is constructed.
|
|
581
|
+
ensurePrivateStateDirectory(resolve(paths.projectDir(), '..'), paths.slug);
|
|
582
|
+
ensurePrivateStateDirectory(paths.projectDir(), 'memory');
|
|
566
583
|
}
|
|
567
584
|
catch (error) {
|
|
568
585
|
return emptySession(`Project state is unavailable: ${describeError(error)}`, 'environment');
|
|
569
586
|
}
|
|
587
|
+
const scope = options.scope ?? mintScope(projectId, options.sessionId);
|
|
570
588
|
// The head serves; the tail is fallen over to, in order, when it cannot.
|
|
571
589
|
const primary = primaryProvider(prefs);
|
|
572
590
|
const entry = PROVIDER_REGISTRY[primary.id];
|
|
@@ -583,9 +601,9 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
583
601
|
// line any more: `probeAgentSession` reports the same gap as a
|
|
584
602
|
// `credentialGap` and the App routes into the picker, where a credential
|
|
585
603
|
// can actually be entered. What still arrives here is a headless caller —
|
|
586
|
-
// `
|
|
604
|
+
// `exec`, `exec --json`, `drain` — which has no picker and for which both
|
|
587
605
|
// pieces of advice below are real: an environment variable, or
|
|
588
|
-
// `--provider`. Keeping the refusal is what makes those
|
|
606
|
+
// `--provider`. Keeping the refusal is what makes those turns fail rather
|
|
589
607
|
// than quietly start on something else.
|
|
590
608
|
return emptySession(`No credential found for ${entry.label}${entry.id === 'zen' ? ' with the selected model. Choose muse-spark-1.3-contributor-free for public access' : ''}. Set one of: ${entry.envVars.join(', ')} — or pass --provider with one that is configured.`);
|
|
591
609
|
}
|
|
@@ -826,7 +844,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
826
844
|
const providerForSession = (sessionId) => primary.id === 'zen' || primary.id === 'zen-go'
|
|
827
845
|
? constructProvider(primary.id, det, model, { sessionId })
|
|
828
846
|
: provider;
|
|
829
|
-
// Session-owned discovery with one drain cursor per
|
|
847
|
+
// Session-owned discovery with one drain cursor per turn. A child shares the
|
|
830
848
|
// discovered scopes without being able to consume the parent's update, and
|
|
831
849
|
// an edit takes effect in this session rather than only after reconnecting.
|
|
832
850
|
const projectInstructions = new ProjectInstructionTracker(cwd);
|
|
@@ -860,7 +878,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
860
878
|
});
|
|
861
879
|
throw err;
|
|
862
880
|
}
|
|
863
|
-
// AFTER resolveSandbox returns — the honest report of what THIS
|
|
881
|
+
// AFTER resolveSandbox returns — the honest report of what THIS turn got,
|
|
864
882
|
// never what was attempted. `unconfined` decides the severity: per the
|
|
865
883
|
// design, this is "the single highest-value line in the whole design,
|
|
866
884
|
// today computed and thrown away" — an operator reading default `info`
|
|
@@ -870,6 +888,33 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
870
888
|
[EVENT_NAME_ATTRIBUTE]: BOOT_EVENT_NAMES.SANDBOX_RESOLVED,
|
|
871
889
|
'namzu.sandbox.unconfined': sandbox.unconfined,
|
|
872
890
|
});
|
|
891
|
+
// The two boundaries a turn can be asked to cross, as questions rather
|
|
892
|
+
// than refusals. A path outside the working directory is reviewed on a
|
|
893
|
+
// host turn (the kernel looks only when there is no sandbox, since a
|
|
894
|
+
// sandboxed path is not mounted to be reached). A sandboxed command's
|
|
895
|
+
// escape is reviewed unless the operator turned escapes off; it is
|
|
896
|
+
// confirmed only by a person, or by `allowUnattendedEscape`.
|
|
897
|
+
const sandboxEscape = options.sandbox?.allowEscape === false ? 'refuse' : 'review';
|
|
898
|
+
const escalation = { outsideRootAccess: 'review', sandboxEscape };
|
|
899
|
+
const unattendedSandboxEscape = options.sandbox?.allowUnattendedEscape === true ? 'allow' : 'refuse';
|
|
900
|
+
// Read once: whether this is WSL does not change inside a session.
|
|
901
|
+
const wsl = detectWsl();
|
|
902
|
+
// Whether the last send had somebody to answer a prompt. A child's
|
|
903
|
+
// environment is composed outside any send, so it reads this.
|
|
904
|
+
let lastSendInteractive = false;
|
|
905
|
+
const boundaryFor = (interactive) => ({
|
|
906
|
+
...(sandbox.provider && sandbox.environment
|
|
907
|
+
? { sandbox: { environment: sandbox.environment, enforced: sandbox.enforced } }
|
|
908
|
+
: {}),
|
|
909
|
+
escape: sandboxEscape === 'refuse'
|
|
910
|
+
? 'refused'
|
|
911
|
+
: interactive
|
|
912
|
+
? 'ask'
|
|
913
|
+
: unattendedSandboxEscape === 'allow'
|
|
914
|
+
? 'unattended'
|
|
915
|
+
: 'refused',
|
|
916
|
+
interactive,
|
|
917
|
+
});
|
|
873
918
|
// Always built: the executor hands it to the tools only where it is
|
|
874
919
|
// safe — on the host, or inside a sandbox that can start a detached
|
|
875
920
|
// process — so a session under a sandbox that cannot simply has none.
|
|
@@ -889,27 +934,41 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
889
934
|
if (absolute !== resolve(cwd) && !directories.includes(absolute))
|
|
890
935
|
directories.push(absolute);
|
|
891
936
|
}
|
|
892
|
-
const sessionDirectories =
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
}
|
|
937
|
+
const sessionDirectories = createSessionDirectories(cwd, directories);
|
|
938
|
+
// `/restore` snapshots live with the conversation they belong to
|
|
939
|
+
// (`<session-id>/file-history/`), not in a tree of their own. Read per
|
|
940
|
+
// turn: `scope.sessionId` moves when the operator switches conversation.
|
|
941
|
+
const checkpoints = new FileCheckpointStore(() => paths.fileHistory({ sessionId: scope.sessionId }), cwd);
|
|
942
|
+
const { registry, memoryStore, memoryDirectory } = buildToolRegistry(paths, backgroundJobs, checkpoints, options.toolResultScreens);
|
|
943
|
+
// Once per store, idempotently: a launch that finds nothing to move moves
|
|
944
|
+
// nothing, and one interrupted halfway is finished by the next. A failure
|
|
945
|
+
// is a notice, never a refusal to start — the curated files and the store
|
|
946
|
+
// both still work without it.
|
|
947
|
+
const memoryMigrationNotices = await migrateMemoryOnce({
|
|
948
|
+
store: memoryStore,
|
|
949
|
+
directory: memoryDirectory,
|
|
950
|
+
cwd,
|
|
951
|
+
})
|
|
952
|
+
.then((report) => describeMemoryMigration(report, memoryDirectory))
|
|
953
|
+
.catch((error) => [
|
|
954
|
+
`Stored memory migration did not run: ${error instanceof Error ? error.message : String(error)}`,
|
|
955
|
+
]);
|
|
956
|
+
/**
|
|
957
|
+
* The stored-memory index for this turn's prompt, or null. A store that
|
|
958
|
+
* refuses to read (a malformed memory file) costs the turn its index and
|
|
959
|
+
* the operator a notice, not the turn.
|
|
960
|
+
*/
|
|
961
|
+
const storedMemoryPrompt = async () => {
|
|
962
|
+
try {
|
|
963
|
+
return { prompt: composeStoredMemoryPrompt(await memoryStore.readIndex()) };
|
|
964
|
+
}
|
|
965
|
+
catch (error) {
|
|
966
|
+
return {
|
|
967
|
+
prompt: null,
|
|
968
|
+
notice: `Stored memory index not loaded: ${error instanceof Error ? error.message : String(error)}`,
|
|
969
|
+
};
|
|
970
|
+
}
|
|
910
971
|
};
|
|
911
|
-
const checkpoints = new FileCheckpointStore(join(ensurePrivateStateDirectory(projectStateRoot, 'checkpoints'), scope.sessionId), cwd);
|
|
912
|
-
const { registry, memoryStore } = buildToolRegistry(cwd, projectStateRoot, backgroundJobs, checkpoints, options.stateRoot ? scope.projectId : undefined, options.toolResultScreens);
|
|
913
972
|
// Package presence is not tool reachability. The CLI used to probe and
|
|
914
973
|
// report @namzu/computer-use without ever constructing its host or mounting
|
|
915
974
|
// SDK's computer_use definition, so even an installed, healthy package was
|
|
@@ -958,12 +1017,12 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
958
1017
|
// Per-send denial further keeps the schemas out of ordinary human turns.
|
|
959
1018
|
const goalAuthorities = new Map();
|
|
960
1019
|
// A child is created after the parent query has started, from inside its
|
|
961
|
-
// Agent tool. Keying the review channel by the executing
|
|
1020
|
+
// Agent tool. Keying the review channel by the executing turn keeps two
|
|
962
1021
|
// concurrent sends from borrowing each other's prompt or approval latch.
|
|
963
1022
|
const delegatedResumeHandlers = new Map();
|
|
964
1023
|
const goalToolNames = new Set(SESSION_GOAL_TOOL_NAMES);
|
|
965
1024
|
if (options.sessionGoals) {
|
|
966
|
-
registry.register(buildSessionGoalTools(options.sessionGoals, (
|
|
1025
|
+
registry.register(buildSessionGoalTools(options.sessionGoals, (turnId) => goalAuthorities.get(turnId)));
|
|
967
1026
|
}
|
|
968
1027
|
// External tool servers, before the roster is counted, so `toolNames` and
|
|
969
1028
|
// the `/tools` list a user reads include what they configured. Connecting
|
|
@@ -1036,13 +1095,13 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1036
1095
|
const history = options.residentHistory;
|
|
1037
1096
|
const historyOwner = { ...scope };
|
|
1038
1097
|
registry.register(buildResidentHistoryTools((context) => {
|
|
1039
|
-
const owner = delegationScopes.get(context.
|
|
1098
|
+
const owner = delegationScopes.get(context.turnId);
|
|
1040
1099
|
if (!owner ||
|
|
1041
1100
|
owner.sessionId !== historyOwner.sessionId ||
|
|
1042
1101
|
owner.projectId !== historyOwner.projectId ||
|
|
1043
1102
|
owner.tenantId !== historyOwner.tenantId ||
|
|
1044
1103
|
owner.tenantId !== history.scope.tenantId)
|
|
1045
|
-
throw new Error('The requesting
|
|
1104
|
+
throw new Error('The requesting turn does not own this resident history.');
|
|
1046
1105
|
return history;
|
|
1047
1106
|
}));
|
|
1048
1107
|
}
|
|
@@ -1050,14 +1109,14 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1050
1109
|
const evidence = options.residentToolEvidence;
|
|
1051
1110
|
const evidenceOwner = { ...scope };
|
|
1052
1111
|
registry.register(buildResidentToolEvidenceTools((context) => {
|
|
1053
|
-
const owner = delegationScopes.get(context.
|
|
1112
|
+
const owner = delegationScopes.get(context.turnId);
|
|
1054
1113
|
if (!owner ||
|
|
1055
1114
|
owner.sessionId !== evidenceOwner.sessionId ||
|
|
1056
1115
|
owner.projectId !== evidenceOwner.projectId ||
|
|
1057
1116
|
owner.tenantId !== evidenceOwner.tenantId ||
|
|
1058
1117
|
owner.projectId !== evidence.scope.projectId ||
|
|
1059
1118
|
owner.tenantId !== evidence.scope.tenantId)
|
|
1060
|
-
throw new Error('The requesting
|
|
1119
|
+
throw new Error('The requesting turn does not own this resident tool evidence.');
|
|
1061
1120
|
return evidence;
|
|
1062
1121
|
}));
|
|
1063
1122
|
}
|
|
@@ -1065,11 +1124,11 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1065
1124
|
const sessions = options.conversationSessions;
|
|
1066
1125
|
for (const build of [buildConversationSearchTool, buildConversationReadTool])
|
|
1067
1126
|
registry.register(build((context) => {
|
|
1068
|
-
const owner = delegationScopes.get(context.
|
|
1127
|
+
const owner = delegationScopes.get(context.turnId);
|
|
1069
1128
|
if (!owner ||
|
|
1070
1129
|
owner.projectId !== sessions.projectId ||
|
|
1071
1130
|
owner.tenantId !== sessions.tenantId)
|
|
1072
|
-
throw new Error('The requesting
|
|
1131
|
+
throw new Error('The requesting turn does not own this conversation.');
|
|
1073
1132
|
return { sessions, sessionId: owner.sessionId };
|
|
1074
1133
|
}));
|
|
1075
1134
|
}
|
|
@@ -1080,18 +1139,31 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1080
1139
|
return [];
|
|
1081
1140
|
let step = evidenceRecallSteps.get(sessionId);
|
|
1082
1141
|
if (!step) {
|
|
1083
|
-
step = createConversationEvidenceRecall(sessions, sessionId, (
|
|
1084
|
-
const owner = delegationScopes.get(
|
|
1142
|
+
step = createConversationEvidenceRecall(sessions, sessionId, (turnId) => {
|
|
1143
|
+
const owner = turnId === undefined ? undefined : delegationScopes.get(turnId);
|
|
1085
1144
|
if (!owner ||
|
|
1086
1145
|
owner.sessionId !== sessionId ||
|
|
1087
1146
|
owner.tenantId !== sessions.tenantId ||
|
|
1088
1147
|
owner.projectId !== sessions.projectId)
|
|
1089
|
-
throw new Error('The requesting
|
|
1148
|
+
throw new Error('The requesting turn no longer owns this conversation.');
|
|
1090
1149
|
}, options.compaction?.resolveEvidenceQueries !== false);
|
|
1091
1150
|
evidenceRecallSteps.set(sessionId, step);
|
|
1092
1151
|
}
|
|
1093
1152
|
return [step];
|
|
1094
1153
|
};
|
|
1154
|
+
// The saved children of a conversation, read back from the session index and
|
|
1155
|
+
// the child logs: the delegation history a turn is told about.
|
|
1156
|
+
const conversationIndex = options.conversationSessions?.index;
|
|
1157
|
+
const savedAgentsSteps = (sessionId) => conversationIndex
|
|
1158
|
+
? [
|
|
1159
|
+
createSavedAgentsStep(createSavedAgentHistory({
|
|
1160
|
+
index: conversationIndex,
|
|
1161
|
+
paths,
|
|
1162
|
+
session: { sessionId },
|
|
1163
|
+
log: cliLogger(),
|
|
1164
|
+
})),
|
|
1165
|
+
]
|
|
1166
|
+
: [];
|
|
1095
1167
|
let subagentRuntime;
|
|
1096
1168
|
// Stays empty when the runtime below throws, which is the honest answer: the
|
|
1097
1169
|
// catch is non-fatal and the session then genuinely has no delegate to
|
|
@@ -1110,38 +1182,56 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1110
1182
|
});
|
|
1111
1183
|
}
|
|
1112
1184
|
const sub = await createSubagentRuntime({
|
|
1113
|
-
historyRoot: projectStateRoot,
|
|
1114
1185
|
cwd,
|
|
1115
1186
|
model,
|
|
1116
1187
|
tokenBudget: options.limits?.tokenBudget,
|
|
1117
1188
|
maxIterations: options.limits?.maxIterations,
|
|
1118
|
-
resolveLimits: (
|
|
1189
|
+
resolveLimits: (turnId) => delegationLimits.get(turnId),
|
|
1119
1190
|
timeoutMs: options.limits?.timeoutMs,
|
|
1120
1191
|
definitions: discovered.definitions,
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1192
|
+
// Children log under the parent's `<session-id>/subagents/`; an
|
|
1193
|
+
// ephemeral session keeps them in memory with its own log.
|
|
1194
|
+
...(options.ephemeral ? {} : { paths }),
|
|
1195
|
+
...(conversationIndex
|
|
1196
|
+
? {
|
|
1197
|
+
savedAgents: (sessionId) => createSavedAgentHistory({
|
|
1198
|
+
index: conversationIndex,
|
|
1199
|
+
paths,
|
|
1200
|
+
session: { sessionId },
|
|
1201
|
+
log: cliLogger(),
|
|
1202
|
+
}),
|
|
1203
|
+
}
|
|
1204
|
+
: {}),
|
|
1205
|
+
resolveParent: async (turnId) => {
|
|
1206
|
+
const parent = delegationScopes.get(turnId);
|
|
1124
1207
|
if (!parent)
|
|
1125
|
-
throw new Error(`
|
|
1126
|
-
return resolveSubagentParent(parent, cwd
|
|
1208
|
+
throw new Error(`Turn ${turnId} no longer owns delegation authority`);
|
|
1209
|
+
return resolveSubagentParent(parent, cwd);
|
|
1127
1210
|
},
|
|
1128
1211
|
sandboxWorkspace,
|
|
1129
|
-
resolveResumeHandler: (
|
|
1130
|
-
resolveWaitForInbound: (
|
|
1212
|
+
resolveResumeHandler: (turnId) => delegatedResumeHandlers.get(turnId),
|
|
1213
|
+
resolveWaitForInbound: (turnId) => delegatedInputWaiters.get(turnId),
|
|
1131
1214
|
...(sandbox.provider ? { sandboxProvider: sandbox.provider } : {}),
|
|
1132
1215
|
...(options.sandbox?.teardownTimeoutMs !== undefined
|
|
1133
1216
|
? { sandboxTeardownTimeoutMs: options.sandbox.teardownTimeoutMs }
|
|
1134
1217
|
: {}),
|
|
1218
|
+
// A child is reviewed through its parent's channel, so the same two
|
|
1219
|
+
// questions reach the same person — or the same refusal.
|
|
1220
|
+
...escalation,
|
|
1135
1221
|
// A sub-agent works in the same repository and writes the same code,
|
|
1136
1222
|
// so it is bound by the same instructions. Without this the parent
|
|
1137
1223
|
// honours the project's rules and every task it delegates quietly
|
|
1138
1224
|
// does not — the worse half of the feature, because the delegating
|
|
1139
1225
|
// turn reports success either way.
|
|
1140
|
-
projectInstructionContext: () => projectInstructions.
|
|
1226
|
+
projectInstructionContext: () => projectInstructions.createTurnContext(),
|
|
1141
1227
|
// Same argument as the instructions, one step further: a sub-agent that
|
|
1142
1228
|
// does not know what day it is dates a changelog entry from a training
|
|
1143
1229
|
// cut-off, and the parent reports the delegation as successful.
|
|
1144
|
-
readEnvironment: async () => composeEnvironmentPrompt(
|
|
1230
|
+
readEnvironment: async () => composeEnvironmentPrompt({
|
|
1231
|
+
...(await readEnvironmentFacts(cwd)),
|
|
1232
|
+
boundary: boundaryFor(lastSendInteractive),
|
|
1233
|
+
...(wsl ? { wsl } : {}),
|
|
1234
|
+
}),
|
|
1145
1235
|
// Each child has its own provider instance, never the parent's fallback cursor.
|
|
1146
1236
|
resolveModel: async (request, signal) => {
|
|
1147
1237
|
const resolution = await resolveModelSwitch(request, {
|
|
@@ -1185,6 +1275,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1185
1275
|
const models = await runPickerProviderOperation(signal, (childSignal) => source.listModels?.(childSignal) ?? Promise.resolve([]));
|
|
1186
1276
|
return models
|
|
1187
1277
|
.filter((m) => canSelectModel(item.entry, item.apiKey, m.id) &&
|
|
1278
|
+
isOfferableModel(item.entry.id, m.id) &&
|
|
1188
1279
|
terms.every((term) => `${item.entry.id} ${m.id} ${m.name}`.toLowerCase().includes(term)))
|
|
1189
1280
|
.map((m) => ({ provider: item.entry.id, ...m }));
|
|
1190
1281
|
}
|
|
@@ -1234,11 +1325,11 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1234
1325
|
// deferred and there is nothing for a search to load.
|
|
1235
1326
|
//
|
|
1236
1327
|
// The store this also builds is dropped, deliberately: a sub-agent
|
|
1237
|
-
// promoting its own
|
|
1238
|
-
//
|
|
1239
|
-
//
|
|
1240
|
-
//
|
|
1241
|
-
const childTools = buildToolRegistry(
|
|
1328
|
+
// promoting its own memory would write a record per delegation,
|
|
1329
|
+
// and a parent that delegated six times would leave seven accounts
|
|
1330
|
+
// of one piece of work for the next turn to read. The parent's
|
|
1331
|
+
// settle is the one that speaks for the whole task.
|
|
1332
|
+
const childTools = buildToolRegistry(paths, backgroundJobs, undefined, options.toolResultScreens).registry;
|
|
1242
1333
|
// Search owns its provider connection per call, so it is safe to share
|
|
1243
1334
|
// with a child. Preserve the parent's configured backend/off choice.
|
|
1244
1335
|
const search = registry.get('web_search');
|
|
@@ -1264,8 +1355,8 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1264
1355
|
// somebody is there to read it, the same condition `ask_user_question`
|
|
1265
1356
|
// mounts under further down. A child's roster is the registry
|
|
1266
1357
|
// `buildTools` builds above, which carries none of these: that is what
|
|
1267
|
-
// keeps narration the
|
|
1268
|
-
// headless host — `
|
|
1358
|
+
// keeps narration the turn's own voice rather than a child's. And a
|
|
1359
|
+
// headless host — `exec`, `exec --json`, `drain`, the resident step —
|
|
1269
1360
|
// has no rail for a line to appear above, so a tool whose entire
|
|
1270
1361
|
// result is "the operator saw this" would be answering with something
|
|
1271
1362
|
// that did not happen.
|
|
@@ -1306,7 +1397,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1306
1397
|
const modelSwitchHandlers = new Map();
|
|
1307
1398
|
if (options.allowModelSwitch) {
|
|
1308
1399
|
registry.register(buildSwitchModelTool(async (request, context) => {
|
|
1309
|
-
const handler = modelSwitchHandlers.get(context.
|
|
1400
|
+
const handler = modelSwitchHandlers.get(context.turnId);
|
|
1310
1401
|
if (!handler || context.abortSignal?.aborted) {
|
|
1311
1402
|
return {
|
|
1312
1403
|
kind: 'rejected',
|
|
@@ -1345,14 +1436,15 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1345
1436
|
return { action: 'continue' };
|
|
1346
1437
|
}
|
|
1347
1438
|
};
|
|
1348
|
-
// The park request carries the
|
|
1349
|
-
// handler above routes by the question, not by the
|
|
1439
|
+
// The park request carries the turn of the call that asked; the
|
|
1440
|
+
// handler above routes by the question, not by the turn, and no durable
|
|
1350
1441
|
// park recorder is supplied.
|
|
1351
1442
|
registry.register(buildAskUserQuestionTool({ resumeHandler: parkQuestion }));
|
|
1352
1443
|
}
|
|
1353
1444
|
// Task store → query registers task_create / task_update / task_list and
|
|
1354
|
-
// emits task_created/task_updated, so the agent can track a plan
|
|
1355
|
-
//
|
|
1445
|
+
// emits task_created/task_updated, so the agent can track a plan. Tasks
|
|
1446
|
+
// belong to the session (`<session-id>/tasks/`) and record the turn that
|
|
1447
|
+
// created them, so a plan outlives the turn. The kernel's default availability
|
|
1356
1448
|
// for them is `deferred`; this session overrides that to `active` at the
|
|
1357
1449
|
// query call, because the doctrine tells the model to plan with them and a
|
|
1358
1450
|
// tool it must search for first is a tool it skips.
|
|
@@ -1366,11 +1458,10 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1366
1458
|
// It is also why `toolNames` below reads the registry rather than a list
|
|
1367
1459
|
// captured on this line. The count at connect time is unchanged; what
|
|
1368
1460
|
// changes is that asking again later gets a later answer.
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
tenantId,
|
|
1461
|
+
const taskStoreFor = (sessionScope) => new DiskTaskStore({
|
|
1462
|
+
paths,
|
|
1463
|
+
session: { sessionId: sessionScope.sessionId },
|
|
1464
|
+
tenantId: sessionScope.tenantId,
|
|
1374
1465
|
});
|
|
1375
1466
|
let selectedTaskStore;
|
|
1376
1467
|
let taskSelectionGeneration = 0;
|
|
@@ -1392,10 +1483,10 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1392
1483
|
// and other asynchronous setup are still being prepared.
|
|
1393
1484
|
resetTaskStore();
|
|
1394
1485
|
const generation = taskSelectionGeneration;
|
|
1395
|
-
return (
|
|
1396
|
-
const store =
|
|
1397
|
-
if (generation === taskSelectionGeneration && matchesCurrentScope(
|
|
1398
|
-
selectedTaskStore = { scope: { ...
|
|
1486
|
+
return (sessionScope) => {
|
|
1487
|
+
const store = taskStoreFor(sessionScope);
|
|
1488
|
+
if (generation === taskSelectionGeneration && matchesCurrentScope(sessionScope)) {
|
|
1489
|
+
selectedTaskStore = { scope: { ...sessionScope }, store };
|
|
1399
1490
|
}
|
|
1400
1491
|
return store;
|
|
1401
1492
|
};
|
|
@@ -1403,7 +1494,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1403
1494
|
// Persists across turns: once the user picks "approve all", later tool
|
|
1404
1495
|
// batches in this session run without prompting.
|
|
1405
1496
|
const approval = { all: false };
|
|
1406
|
-
// Share the project store with tools and recall. Each
|
|
1497
|
+
// Share the project store with tools and recall. Each turn selects either
|
|
1407
1498
|
// this extracted-claim promoter or explicit consolidation, never both.
|
|
1408
1499
|
// Candidates without useful claims write nothing.
|
|
1409
1500
|
const promoteMemory = createMemoryPromoter({ store: memoryStore });
|
|
@@ -1421,21 +1512,19 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1421
1512
|
return emptySession(describeError(error));
|
|
1422
1513
|
}
|
|
1423
1514
|
// The session's own lifecycle, for hooks that set up or tear down
|
|
1424
|
-
// something per session rather than per
|
|
1425
|
-
//
|
|
1426
|
-
// the first turn rather than firing here, because
|
|
1427
|
-
// the scope holds at construction is provisional — it
|
|
1428
|
-
// the conversation is first made durable — and a hook
|
|
1429
|
-
// provisional id could never match it to a
|
|
1515
|
+
// something per session rather than per turn. These two calls belong to no
|
|
1516
|
+
// turn, so they carry no turn id — nothing is minted to fill the field.
|
|
1517
|
+
// `session_start` waits for the first turn rather than firing here, because
|
|
1518
|
+
// the conversation id the scope holds at construction is provisional — it
|
|
1519
|
+
// is replaced when the conversation is first made durable — and a hook
|
|
1520
|
+
// given the provisional id could never match it to a turn.
|
|
1430
1521
|
const sessionPlugins = pluginRuntime;
|
|
1431
|
-
const sessionHookRunId = generateRunId();
|
|
1432
1522
|
let sessionStarted = false;
|
|
1433
1523
|
const announceSessionStart = async () => {
|
|
1434
1524
|
if (!sessionPlugins || sessionStarted)
|
|
1435
1525
|
return;
|
|
1436
1526
|
sessionStarted = true;
|
|
1437
1527
|
await sessionPlugins.manager.executeHooks('session_start', {
|
|
1438
|
-
runId: sessionHookRunId,
|
|
1439
1528
|
sessionId: scope.sessionId,
|
|
1440
1529
|
});
|
|
1441
1530
|
};
|
|
@@ -1484,7 +1573,6 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1484
1573
|
sessionPlugins
|
|
1485
1574
|
? sessionPlugins.manager
|
|
1486
1575
|
.executeHooks('session_end', {
|
|
1487
|
-
runId: sessionHookRunId,
|
|
1488
1576
|
sessionId: scope.sessionId,
|
|
1489
1577
|
})
|
|
1490
1578
|
.catch(() => [])
|
|
@@ -1549,14 +1637,15 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1549
1637
|
effortNotice = `Reasoning effort levels could not be established for this session: ${describeError(error)}`;
|
|
1550
1638
|
}
|
|
1551
1639
|
/**
|
|
1552
|
-
* The kernel's resume with this session's half of the
|
|
1640
|
+
* The kernel's resume with this session's half of the turn attached: the
|
|
1553
1641
|
* provider, the tools, the working directory, the doctrine — the part a
|
|
1554
1642
|
* checkpoint cannot carry. `resumeDurable` and `resumePaused` differ only
|
|
1555
|
-
* in where the
|
|
1643
|
+
* in where the log and its lease come from.
|
|
1556
1644
|
*/
|
|
1557
|
-
const kernelResume = ({ entry, checkpointStore,
|
|
1645
|
+
const kernelResume = ({ entry, sessionLog, checkpointStore, lease, signal, checkpointId, listener, }) => operations.promise(signal, async (ownedSignal) => {
|
|
1558
1646
|
const selectTaskStore = beginTaskStoreReadout();
|
|
1559
|
-
|
|
1647
|
+
// The turn's own limits, as its `turn_started` recorded them.
|
|
1648
|
+
const resumedLimits = (await readStoredTurnGuards(sessionLog, entry.turnId)) ?? resolveTurnGuards(options.limits);
|
|
1560
1649
|
// The same prelude a turn runs, and for the same reasons: a lapsed
|
|
1561
1650
|
// OAuth token has to be renewed before the provider is used, and the
|
|
1562
1651
|
// fallback chain has to be built AFTER that so its members do not
|
|
@@ -1568,10 +1657,17 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1568
1657
|
const curatedMemory = readMemory(undefined, cwd);
|
|
1569
1658
|
for (const notice of formatMemoryDiagnostics(curatedMemory))
|
|
1570
1659
|
cliLogger().warn(notice);
|
|
1571
|
-
const
|
|
1660
|
+
const storedMemory = await storedMemoryPrompt();
|
|
1661
|
+
if (storedMemory.notice)
|
|
1662
|
+
cliLogger().warn(storedMemory.notice);
|
|
1663
|
+
const memoryPrompt = [composeMemoryPrompt(curatedMemory), storedMemory.prompt]
|
|
1664
|
+
.filter((part) => Boolean(part))
|
|
1665
|
+
.join('\n\n') || null;
|
|
1572
1666
|
const environmentPrompt = composeEnvironmentPrompt({
|
|
1573
1667
|
...(await readEnvironmentFacts(cwd)),
|
|
1574
1668
|
additionalDirectories: [...directories],
|
|
1669
|
+
boundary: boundaryFor(false),
|
|
1670
|
+
...(wsl ? { wsl } : {}),
|
|
1575
1671
|
});
|
|
1576
1672
|
const systemPrompt = [
|
|
1577
1673
|
NAMZU_IDENTITY,
|
|
@@ -1583,29 +1679,27 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1583
1679
|
]
|
|
1584
1680
|
.filter((s) => Boolean(s))
|
|
1585
1681
|
.join('\n\n') || undefined;
|
|
1586
|
-
const resumeHandler = makeResumeHandler(approval, undefined, options.permissionMode, (name, input) => isPromptExempt(registry, name, input));
|
|
1587
|
-
if (delegatedResumeHandlers.has(entry.
|
|
1588
|
-
throw new Error(`
|
|
1682
|
+
const resumeHandler = makeResumeHandler(approval, undefined, options.permissionMode, (name, input) => isPromptExempt(registry, name, input), { unattendedSandboxEscape });
|
|
1683
|
+
if (delegatedResumeHandlers.has(entry.turnId)) {
|
|
1684
|
+
throw new Error(`Turn ${entry.turnId} already owns a delegated review channel.`);
|
|
1589
1685
|
}
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
topicId: scope.topicId,
|
|
1596
|
-
});
|
|
1686
|
+
const turnScope = { ...entry, topicId: scope.topicId };
|
|
1687
|
+
delegatedResumeHandlers.set(entry.turnId, resumeHandler);
|
|
1688
|
+
delegationScopes.set(entry.turnId, turnScope);
|
|
1689
|
+
delegationLimits.set(entry.turnId, resumedLimits);
|
|
1690
|
+
const turnTaskStore = selectTaskStore(turnScope);
|
|
1597
1691
|
try {
|
|
1598
|
-
return await
|
|
1692
|
+
return await resumeSession({
|
|
1599
1693
|
provider: providerForSession(entry.sessionId),
|
|
1600
1694
|
fallbackProviders: fallbackPlan.build(currentToken, entry.sessionId),
|
|
1601
1695
|
tools: registry,
|
|
1602
1696
|
pluginManager: pluginRuntime?.manager,
|
|
1603
1697
|
skillRegistry: pluginRuntime?.skills,
|
|
1604
1698
|
skills: pluginSkills,
|
|
1605
|
-
taskStore:
|
|
1699
|
+
taskStore: turnTaskStore,
|
|
1606
1700
|
...(webCapability ? { web: webCapability } : {}),
|
|
1607
|
-
// The same availability the original
|
|
1608
|
-
// A resumed
|
|
1701
|
+
// The same availability the original turn registered under.
|
|
1702
|
+
// A resumed turn re-registers the task tools; leaving them at
|
|
1609
1703
|
// the kernel's `deferred` default would hand the model a plan
|
|
1610
1704
|
// it started with active tools and can no longer update.
|
|
1611
1705
|
runtimeToolOverrides: {
|
|
@@ -1614,9 +1708,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1614
1708
|
task_list: 'active',
|
|
1615
1709
|
},
|
|
1616
1710
|
...(subagentRuntime
|
|
1617
|
-
? {
|
|
1618
|
-
taskScheduler: await subagentRuntime.gatewayForRun(entry.runId),
|
|
1619
|
-
}
|
|
1711
|
+
? { taskScheduler: await subagentRuntime.gatewayForTurn(entry.turnId) }
|
|
1620
1712
|
: {}),
|
|
1621
1713
|
authorizationGate: gateFor(options.rules),
|
|
1622
1714
|
compactionConfig: compactionConfigFor(options.compaction),
|
|
@@ -1624,8 +1716,8 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1624
1716
|
? (options.compaction?.retainedToolPreviewChars ?? 4_000)
|
|
1625
1717
|
: undefined,
|
|
1626
1718
|
prepareStep: [
|
|
1627
|
-
createTaskContextStep(
|
|
1628
|
-
|
|
1719
|
+
createTaskContextStep(turnTaskStore, entry.tenantId),
|
|
1720
|
+
...savedAgentsSteps(entry.sessionId),
|
|
1629
1721
|
...(options.memory?.recall === false
|
|
1630
1722
|
? []
|
|
1631
1723
|
: [
|
|
@@ -1640,98 +1732,98 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1640
1732
|
...(options.compaction?.consolidate
|
|
1641
1733
|
? { consolidateInto: memoryStore }
|
|
1642
1734
|
: { promoteMemory }),
|
|
1643
|
-
projectInstructionContext: projectInstructions.
|
|
1644
|
-
|
|
1735
|
+
projectInstructionContext: projectInstructions.createTurnContext(),
|
|
1736
|
+
paths,
|
|
1645
1737
|
...(sandbox.provider ? { sandboxProvider: sandbox.provider } : {}),
|
|
1646
1738
|
...(options.sandbox?.teardownTimeoutMs !== undefined
|
|
1647
1739
|
? { sandboxTeardownTimeoutMs: options.sandbox.teardownTimeoutMs }
|
|
1648
1740
|
: {}),
|
|
1649
|
-
|
|
1650
|
-
// whose `attach` detaches whoever held it before, so a caller
|
|
1651
|
-
// resuming several runs in one process would leave only the last
|
|
1652
|
-
// one covered — and would look covered. A turn owns its process
|
|
1653
|
-
// end to end; a drainer does not.
|
|
1654
|
-
runConfig: {
|
|
1741
|
+
turnConfig: {
|
|
1655
1742
|
model,
|
|
1656
1743
|
...(nativeWebSearch ? { webSearch: nativeWebSearch } : {}),
|
|
1657
1744
|
...(sandbox.provider ? { sandbox: { workspace: sandboxWorkspace } } : {}),
|
|
1658
1745
|
...resumedLimits,
|
|
1659
1746
|
maxResponseTokens: 8192,
|
|
1660
1747
|
permissionMode: 'auto',
|
|
1748
|
+
pruneKeepLast: CLI_CHECKPOINT_RETENTION,
|
|
1661
1749
|
},
|
|
1662
1750
|
agentId: 'namzu',
|
|
1663
1751
|
agentName: 'namzu',
|
|
1664
1752
|
...(systemPrompt ? { systemPrompt } : {}),
|
|
1665
1753
|
workingDirectory: cwd,
|
|
1666
1754
|
...(directories.length > 0 ? { additionalDirectories: [...directories] } : {}),
|
|
1667
|
-
...
|
|
1755
|
+
...escalation,
|
|
1668
1756
|
// No `onPermission`: there is nobody at a drainer's terminal, so a
|
|
1669
|
-
// prompt would block the pass forever on a
|
|
1757
|
+
// prompt would block the pass forever on a turn nobody is watching.
|
|
1670
1758
|
// The gate's deny rules still apply.
|
|
1671
|
-
// One presenter for the whole stream, built from the registry this
|
|
1672
|
-
// scope already holds. It was the absence of the registry HERE that
|
|
1673
|
-
// forced presentation to be name matching: `toAgentEvent` was pure
|
|
1674
|
-
// over a `RunEvent` and could not ask a tool anything.
|
|
1675
1759
|
resumeHandler,
|
|
1676
1760
|
signal: ownedSignal,
|
|
1677
|
-
// Attribution comes from the ENTRY, not from this session: the
|
|
1761
|
+
// Attribution comes from the ENTRY, not from this session: the turn
|
|
1678
1762
|
// belongs to whoever started it, and stamping the drainer's ids onto
|
|
1679
1763
|
// it would file another tenant's work under this one.
|
|
1680
1764
|
tenantId: entry.tenantId,
|
|
1681
1765
|
projectId: entry.projectId,
|
|
1682
1766
|
sessionId: entry.sessionId,
|
|
1683
|
-
// …except the topic, which
|
|
1684
|
-
//
|
|
1685
|
-
// supplied here rather than pretended to have been recovered.
|
|
1767
|
+
// …except the topic, which the drainer supplies honestly rather
|
|
1768
|
+
// than pretending to have recovered it.
|
|
1686
1769
|
topicId: scope.topicId,
|
|
1687
|
-
scope:
|
|
1688
|
-
|
|
1689
|
-
|
|
1770
|
+
scope: turnScope,
|
|
1771
|
+
sessionLog,
|
|
1772
|
+
checkpointStore: checkpointStore ??
|
|
1773
|
+
new DiskSessionCheckpointStore({
|
|
1774
|
+
paths,
|
|
1775
|
+
log: sessionLogCheckpointView(sessionLog),
|
|
1776
|
+
// A drained child's log sits under its parent's `subagents/`;
|
|
1777
|
+
// its checkpoints are in its own directory there, not at the top.
|
|
1778
|
+
...(sessionLog instanceof DiskSessionLog && sessionLog.locator
|
|
1779
|
+
? { session: sessionLog.locator }
|
|
1780
|
+
: {}),
|
|
1781
|
+
}),
|
|
1782
|
+
...(lease ? { lease } : {}),
|
|
1690
1783
|
...(checkpointId !== undefined ? { checkpointId } : {}),
|
|
1691
|
-
...(listener
|
|
1784
|
+
...(listener || options.onSessionEvent
|
|
1785
|
+
? {
|
|
1786
|
+
listener: (event) => {
|
|
1787
|
+
options.onSessionEvent?.(event);
|
|
1788
|
+
listener?.(event);
|
|
1789
|
+
},
|
|
1790
|
+
}
|
|
1791
|
+
: {}),
|
|
1692
1792
|
});
|
|
1693
1793
|
}
|
|
1694
1794
|
finally {
|
|
1695
|
-
if (delegatedResumeHandlers.get(entry.
|
|
1696
|
-
delegatedResumeHandlers.delete(entry.
|
|
1697
|
-
delegationScopes.delete(entry.
|
|
1698
|
-
delegationLimits.delete(entry.
|
|
1699
|
-
await subagentRuntime?.
|
|
1795
|
+
if (delegatedResumeHandlers.get(entry.turnId) === resumeHandler) {
|
|
1796
|
+
delegatedResumeHandlers.delete(entry.turnId);
|
|
1797
|
+
delegationScopes.delete(entry.turnId);
|
|
1798
|
+
delegationLimits.delete(entry.turnId);
|
|
1799
|
+
await subagentRuntime?.releaseTurn(entry.turnId);
|
|
1700
1800
|
}
|
|
1701
1801
|
}
|
|
1702
1802
|
});
|
|
1703
1803
|
/**
|
|
1704
|
-
* `
|
|
1705
|
-
* listener. A small queue turns that into the stream `send` gives, so a
|
|
1706
|
-
* headless caller renders a resumed
|
|
1804
|
+
* `resumeSession` drains the loop and returns a settled turn; the events go
|
|
1805
|
+
* to a listener. A small queue turns that into the stream `send` gives, so a
|
|
1806
|
+
* headless caller renders a resumed turn exactly as it rendered the first
|
|
1807
|
+
* segment.
|
|
1707
1808
|
*/
|
|
1708
|
-
const resumePausedStream = ({
|
|
1809
|
+
const resumePausedStream = ({ turnId, checkpointId, signal, }) => {
|
|
1709
1810
|
const queue = [];
|
|
1710
1811
|
let wake;
|
|
1711
1812
|
let settled = false;
|
|
1712
1813
|
let failure;
|
|
1713
1814
|
const presenter = createToolPresenter(registry);
|
|
1714
|
-
// The
|
|
1715
|
-
|
|
1716
|
-
// `runs/`, attributed to this tenant and project.
|
|
1717
|
-
const store = new DiskCheckpointStore({
|
|
1718
|
-
baseDir: join(pathBuilder.sessionDir(scope.projectId, scope.sessionId), 'runs'),
|
|
1719
|
-
}, {
|
|
1720
|
-
tenantId: scope.tenantId,
|
|
1721
|
-
projectId: scope.projectId,
|
|
1722
|
-
sessionId: scope.sessionId,
|
|
1723
|
-
});
|
|
1724
|
-
const entry = {
|
|
1725
|
-
tenantId: scope.tenantId,
|
|
1726
|
-
projectId: scope.projectId,
|
|
1727
|
-
sessionId: scope.sessionId,
|
|
1728
|
-
runId: runId,
|
|
1729
|
-
};
|
|
1815
|
+
// The log the turn appends to, and its checkpoints beside it.
|
|
1816
|
+
const sessionLog = DiskSessionLog.at(paths, { sessionId: scope.sessionId });
|
|
1730
1817
|
const outcome = kernelResume({
|
|
1731
|
-
entry
|
|
1732
|
-
|
|
1818
|
+
entry: {
|
|
1819
|
+
tenantId: scope.tenantId,
|
|
1820
|
+
projectId: scope.projectId,
|
|
1821
|
+
sessionId: scope.sessionId,
|
|
1822
|
+
turnId: turnId,
|
|
1823
|
+
},
|
|
1824
|
+
sessionLog,
|
|
1733
1825
|
...(signal ? { signal } : {}),
|
|
1734
|
-
checkpointId: checkpointId,
|
|
1826
|
+
...(checkpointId !== undefined ? { checkpointId: checkpointId } : {}),
|
|
1735
1827
|
listener: (event) => {
|
|
1736
1828
|
queue.push(event);
|
|
1737
1829
|
wake?.();
|
|
@@ -1740,8 +1832,8 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1740
1832
|
.then((result) => {
|
|
1741
1833
|
if (!result.resumed) {
|
|
1742
1834
|
failure = new Error(result.reason === 'no-checkpoint'
|
|
1743
|
-
? `no checkpoint ${checkpointId} is recorded for
|
|
1744
|
-
: `
|
|
1835
|
+
? `no checkpoint ${checkpointId ?? ''} is recorded for turn ${turnId}`.replace(' ', ' ')
|
|
1836
|
+
: `turn ${turnId} is parked on a decision only a person can answer`);
|
|
1745
1837
|
}
|
|
1746
1838
|
})
|
|
1747
1839
|
.catch((err) => {
|
|
@@ -1790,23 +1882,30 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1790
1882
|
const sessionId = scope.sessionId;
|
|
1791
1883
|
const sessions = options.conversationSessions;
|
|
1792
1884
|
await prepareProviderCredential(signal);
|
|
1793
|
-
|
|
1794
|
-
messages,
|
|
1885
|
+
const common = {
|
|
1795
1886
|
config: compactionConfigFor(options.compaction),
|
|
1796
1887
|
provider: providerForSession(sessionId),
|
|
1797
1888
|
model,
|
|
1798
1889
|
signal,
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1890
|
+
};
|
|
1891
|
+
// A durable conversation compacts its own log: the kernel folds the
|
|
1892
|
+
// context from it and appends a `compaction{ trigger: 'manual' }`
|
|
1893
|
+
// record outside any turn, so the originals stay in the log and a
|
|
1894
|
+
// resume folds the summary. A session with no log compacts the
|
|
1895
|
+
// history it was handed.
|
|
1896
|
+
if (sessions && !options.ephemeral) {
|
|
1897
|
+
if (sessions.projectId !== scope.projectId || sessions.tenantId !== scope.tenantId)
|
|
1898
|
+
throw new Error('Manual compaction is outside the current conversation scope.');
|
|
1899
|
+
return await compactSession({
|
|
1900
|
+
...common,
|
|
1901
|
+
sessionId,
|
|
1902
|
+
locator: {
|
|
1903
|
+
log: DiskSessionLog.at(paths, { sessionId }),
|
|
1904
|
+
index: sessions.index,
|
|
1905
|
+
},
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
return compactNow({ ...common, messages });
|
|
1810
1909
|
}),
|
|
1811
1910
|
// Reads the same registry object the deferred registration mutates, at
|
|
1812
1911
|
// call time — the pair of `promptExemptTools` below, and for the same
|
|
@@ -1838,16 +1937,22 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1838
1937
|
listener(job);
|
|
1839
1938
|
}) ?? (() => { }),
|
|
1840
1939
|
...(subagentRuntime ? { subagents: subagentRuntime.activity } : {}),
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1940
|
+
...(conversationIndex
|
|
1941
|
+
? {
|
|
1942
|
+
savedChildren: () => replaySavedChildrenFor({
|
|
1943
|
+
index: conversationIndex,
|
|
1944
|
+
paths,
|
|
1945
|
+
session: { sessionId: scope.sessionId },
|
|
1946
|
+
log: cliLogger(),
|
|
1947
|
+
}),
|
|
1948
|
+
listSavedBatches: () => listSavedBatches({
|
|
1949
|
+
index: conversationIndex,
|
|
1950
|
+
paths,
|
|
1951
|
+
session: { sessionId: scope.sessionId },
|
|
1952
|
+
log: cliLogger(),
|
|
1953
|
+
}),
|
|
1954
|
+
}
|
|
1955
|
+
: {}),
|
|
1851
1956
|
get instructionFiles() {
|
|
1852
1957
|
return projectInstructions.instructionFiles;
|
|
1853
1958
|
},
|
|
@@ -1870,7 +1975,18 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1870
1975
|
: []),
|
|
1871
1976
|
...unresolvedNotice.map((line) => `Provider chain: capabilities could not be established for ${line}.`),
|
|
1872
1977
|
...fallbackPlan.notices,
|
|
1978
|
+
...memoryMigrationNotices,
|
|
1873
1979
|
],
|
|
1980
|
+
rememberNote: (text, type) => saveTypedNote(memoryStore, text, type),
|
|
1981
|
+
importCuratedNotes: async () => describeCuratedNotesImport(await importCuratedNotes({ store: memoryStore, directory: memoryDirectory, cwd }), memoryDirectory),
|
|
1982
|
+
storedMemoryIndex: async () => ({
|
|
1983
|
+
directory: memoryDirectory,
|
|
1984
|
+
index: await memoryStore.readIndex({ maxLines: Number.POSITIVE_INFINITY }),
|
|
1985
|
+
derived: await memoryStore.readIndex({
|
|
1986
|
+
maxLines: Number.POSITIVE_INFINITY,
|
|
1987
|
+
derived: true,
|
|
1988
|
+
}),
|
|
1989
|
+
}),
|
|
1874
1990
|
webSearchSummary: webSearchLabel(options.web, nativeSearchAvailable),
|
|
1875
1991
|
close: () => operations.close(),
|
|
1876
1992
|
errorHint: null,
|
|
@@ -1883,22 +1999,41 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1883
1999
|
promptExemptTools: () => promptExemptToolNames(registry).filter((name) => !goalToolNames.has(name)),
|
|
1884
2000
|
send: (messages, opts) => operations.stream(opts?.signal, (signal) => (async function* () {
|
|
1885
2001
|
const selectTaskStore = beginTaskStoreReadout();
|
|
1886
|
-
const
|
|
1887
|
-
const
|
|
1888
|
-
const turnOpts = { ...opts, runId, signal };
|
|
2002
|
+
const turnLimits = resolveTurnGuards(options.limits, opts?.limits);
|
|
2003
|
+
const turnOpts = { ...opts, signal };
|
|
1889
2004
|
let runTools = registry;
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
throw new Error(`Run ${runId} already owns a delegated review channel.`);
|
|
1893
|
-
}
|
|
1894
|
-
delegatedResumeHandlers.set(runId, resumeHandler);
|
|
1895
|
-
if (opts?.onModelSwitch)
|
|
1896
|
-
modelSwitchHandlers.set(runId, opts.onModelSwitch);
|
|
1897
|
-
if (opts?.waitForInbound)
|
|
1898
|
-
delegatedInputWaiters.set(runId, opts.waitForInbound);
|
|
2005
|
+
lastSendInteractive = opts?.onPermission !== undefined;
|
|
2006
|
+
const resumeHandler = makeResumeHandler(approval, opts?.onPermission, opts?.permissionMode ?? options.permissionMode, (name, input) => isPromptExempt(runTools, name, input), { unattendedSandboxEscape });
|
|
1899
2007
|
const turnScope = { ...scope };
|
|
1900
|
-
|
|
1901
|
-
|
|
2008
|
+
// The turn's id is reserved here, before the kernel begins it, because
|
|
2009
|
+
// everything that authorizes the turn is keyed by it: the review
|
|
2010
|
+
// channel its children borrow, the delegation gateway, the goal-round
|
|
2011
|
+
// authority. A caller may reserve it itself (a resident step names
|
|
2012
|
+
// its turn to its verifier first).
|
|
2013
|
+
const turnId = opts?.turnId ?? generateTurnId();
|
|
2014
|
+
const claimed = new Set();
|
|
2015
|
+
let capturedAuthority;
|
|
2016
|
+
const claimTurn = (turnId) => {
|
|
2017
|
+
if (claimed.has(turnId))
|
|
2018
|
+
return;
|
|
2019
|
+
if (delegatedResumeHandlers.has(turnId)) {
|
|
2020
|
+
throw new Error(`Turn ${turnId} already owns a delegated review channel.`);
|
|
2021
|
+
}
|
|
2022
|
+
claimed.add(turnId);
|
|
2023
|
+
delegatedResumeHandlers.set(turnId, resumeHandler);
|
|
2024
|
+
if (opts?.onModelSwitch)
|
|
2025
|
+
modelSwitchHandlers.set(turnId, opts.onModelSwitch);
|
|
2026
|
+
if (opts?.waitForInbound)
|
|
2027
|
+
delegatedInputWaiters.set(turnId, opts.waitForInbound);
|
|
2028
|
+
delegationScopes.set(turnId, turnScope);
|
|
2029
|
+
delegationLimits.set(turnId, turnLimits);
|
|
2030
|
+
if (capturedAuthority) {
|
|
2031
|
+
if (goalAuthorities.has(turnId)) {
|
|
2032
|
+
throw new Error(`Turn ${turnId} already owns goal-round authority.`);
|
|
2033
|
+
}
|
|
2034
|
+
goalAuthorities.set(turnId, capturedAuthority);
|
|
2035
|
+
}
|
|
2036
|
+
};
|
|
1902
2037
|
try {
|
|
1903
2038
|
// Renew a lapsed OAuth token before the turn runs (no-op for valid
|
|
1904
2039
|
// tokens and non-subscription credentials).
|
|
@@ -1927,7 +2062,13 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1927
2062
|
for (const notice of formatMemoryDiagnostics(curatedMemory)) {
|
|
1928
2063
|
yield { kind: 'context', text: notice, shed: false };
|
|
1929
2064
|
}
|
|
1930
|
-
const
|
|
2065
|
+
const storedMemory = await storedMemoryPrompt();
|
|
2066
|
+
if (storedMemory.notice) {
|
|
2067
|
+
yield { kind: 'context', text: storedMemory.notice, shed: false };
|
|
2068
|
+
}
|
|
2069
|
+
const memoryPrompt = [composeMemoryPrompt(curatedMemory), storedMemory.prompt]
|
|
2070
|
+
.filter((part) => Boolean(part))
|
|
2071
|
+
.join('\n\n') || null;
|
|
1931
2072
|
currentOnQuestion = opts?.onQuestion;
|
|
1932
2073
|
const [environmentFacts, turnSnapshot] = await Promise.all([
|
|
1933
2074
|
readEnvironmentFacts(cwd),
|
|
@@ -1936,10 +2077,16 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1936
2077
|
const environmentPrompt = composeEnvironmentPrompt({
|
|
1937
2078
|
...environmentFacts,
|
|
1938
2079
|
additionalDirectories: [...directories],
|
|
2080
|
+
boundary: boundaryFor(opts?.onPermission !== undefined),
|
|
2081
|
+
...(wsl ? { wsl } : {}),
|
|
1939
2082
|
});
|
|
1940
2083
|
// The repository as it stood when THIS turn began, through the
|
|
1941
|
-
// SDK's `
|
|
1942
|
-
//
|
|
2084
|
+
// SDK's `context` placement — request-only context after the
|
|
2085
|
+
// history that never enters it. An observation, not an
|
|
2086
|
+
// instruction: under `turn` it rode a system message the
|
|
2087
|
+
// hoisting driver moves ahead of the conversation, so every
|
|
2088
|
+
// new send's changed snapshot re-read the whole history
|
|
2089
|
+
// uncached. FIRST iteration only:
|
|
1943
2090
|
// later iterations work from state the model itself changed, and
|
|
1944
2091
|
// `git status` is the honest source for that. A registry per
|
|
1945
2092
|
// turn, closed over this turn's snapshot, rather than one
|
|
@@ -1949,7 +2096,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1949
2096
|
const promptContributions = new PromptContributionRegistry();
|
|
1950
2097
|
promptContributions.register({
|
|
1951
2098
|
id: 'namzu.turn-snapshot',
|
|
1952
|
-
placement: '
|
|
2099
|
+
placement: 'context',
|
|
1953
2100
|
render: ({ iteration }) => (iteration === 1 ? turnSnapshotPrompt : null),
|
|
1954
2101
|
});
|
|
1955
2102
|
// The citation rules that come with the web tools, only when the
|
|
@@ -1972,12 +2119,12 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
1972
2119
|
const bundle = opts?.residentLearningDisclosure === 'on-demand'
|
|
1973
2120
|
? createResidentStepContext({
|
|
1974
2121
|
...contextOptions,
|
|
1975
|
-
authorizeLearningRead: (context) => context.
|
|
1976
|
-
delegationScopes.get(context.
|
|
2122
|
+
authorizeLearningRead: (context) => claimed.has(context.turnId) &&
|
|
2123
|
+
delegationScopes.get(context.turnId) === turnScope,
|
|
1977
2124
|
})
|
|
1978
2125
|
: { contributions: createResidentStepContributions(contextOptions), tools: [] };
|
|
1979
2126
|
if (bundle.tools.length) {
|
|
1980
|
-
// Per-send membership: neither another send nor delegated
|
|
2127
|
+
// Per-send membership: neither another send nor delegated sessions inherit this tool.
|
|
1981
2128
|
runTools = runTools.fork();
|
|
1982
2129
|
for (const tool of bundle.tools)
|
|
1983
2130
|
runTools.register(tool);
|
|
@@ -2015,10 +2162,7 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
2015
2162
|
.join('\n\n') || undefined;
|
|
2016
2163
|
await announceSessionStart();
|
|
2017
2164
|
checkpoints.beginTurn(lastUserText(messages));
|
|
2018
|
-
let capturedAuthority;
|
|
2019
2165
|
if (opts?.goalRound) {
|
|
2020
|
-
if (!opts.runId)
|
|
2021
|
-
throw new Error('A goal round requires a caller-reserved runId.');
|
|
2022
2166
|
if (!options.sessionGoals)
|
|
2023
2167
|
throw new Error('This session has no durable goal store.');
|
|
2024
2168
|
if (opts.goalRound.sessionId !== scope.sessionId ||
|
|
@@ -2035,13 +2179,32 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
2035
2179
|
current.maxGoalRounds !== opts.goalRound.maxGoalRounds) {
|
|
2036
2180
|
throw new Error('Goal-round authority is stale or does not match the durable goal.');
|
|
2037
2181
|
}
|
|
2038
|
-
if (goalAuthorities.has(opts.runId)) {
|
|
2039
|
-
throw new Error(`Run ${opts.runId} already owns goal-round authority.`);
|
|
2040
|
-
}
|
|
2041
2182
|
capturedAuthority = Object.freeze({ ...opts.goalRound });
|
|
2042
|
-
goalAuthorities.set(opts.runId, capturedAuthority);
|
|
2043
2183
|
}
|
|
2044
|
-
|
|
2184
|
+
claimTurn(turnId);
|
|
2185
|
+
const turnTaskStore = selectTaskStore(turnScope);
|
|
2186
|
+
// An ephemeral session keeps its whole turn in memory: a fresh
|
|
2187
|
+
// log per send, opened with the `session_started` a turn follows.
|
|
2188
|
+
// A durable one appends to `<session-id>.jsonl`, created first
|
|
2189
|
+
// when this session is the one bringing it into existence.
|
|
2190
|
+
let ephemeralLog;
|
|
2191
|
+
if (options.ephemeral) {
|
|
2192
|
+
ephemeralLog = new InMemorySessionLog({ sessionId: turnScope.sessionId });
|
|
2193
|
+
await ensureSessionStarted(ephemeralLog, {
|
|
2194
|
+
...turnScope,
|
|
2195
|
+
cwd,
|
|
2196
|
+
agent: { id: 'namzu', name: 'namzu' },
|
|
2197
|
+
...(options.origin ? { origin: options.origin } : {}),
|
|
2198
|
+
});
|
|
2199
|
+
}
|
|
2200
|
+
else {
|
|
2201
|
+
await ensureSessionStarted(DiskSessionLog.at(paths, { sessionId: turnScope.sessionId }), {
|
|
2202
|
+
...turnScope,
|
|
2203
|
+
cwd,
|
|
2204
|
+
agent: { id: 'namzu', name: 'namzu' },
|
|
2205
|
+
...(options.origin ? { origin: options.origin } : {}),
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2045
2208
|
try {
|
|
2046
2209
|
yield* runTurn({
|
|
2047
2210
|
provider: providerForSession(turnScope.sessionId),
|
|
@@ -2071,9 +2234,13 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
2071
2234
|
skillRegistry: pluginRuntime?.skills,
|
|
2072
2235
|
skills: pluginSkills,
|
|
2073
2236
|
scope: turnScope,
|
|
2074
|
-
|
|
2237
|
+
turnId,
|
|
2238
|
+
paths,
|
|
2239
|
+
...(ephemeralLog ? { sessionLog: ephemeralLog } : {}),
|
|
2240
|
+
claimTurn,
|
|
2075
2241
|
workingDirectory: cwd,
|
|
2076
2242
|
...(directories.length > 0 ? { additionalDirectories: [...directories] } : {}),
|
|
2243
|
+
escalation,
|
|
2077
2244
|
limits: turnLimits,
|
|
2078
2245
|
sandboxWorkspace,
|
|
2079
2246
|
rules: options.rules,
|
|
@@ -2082,8 +2249,8 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
2082
2249
|
maxAnswerReviews: options.maxAnswerReviews,
|
|
2083
2250
|
promoteMemory: options.compaction?.consolidate ? undefined : promoteMemory,
|
|
2084
2251
|
prepareStep: [
|
|
2085
|
-
createTaskContextStep(
|
|
2086
|
-
|
|
2252
|
+
createTaskContextStep(turnTaskStore, turnScope.tenantId),
|
|
2253
|
+
...savedAgentsSteps(turnScope.sessionId),
|
|
2087
2254
|
...(options.memory?.recall === false
|
|
2088
2255
|
? []
|
|
2089
2256
|
: [
|
|
@@ -2097,26 +2264,25 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
2097
2264
|
...evidenceRecallFor(turnScope.sessionId),
|
|
2098
2265
|
...(options.residentEvidenceRecall ? [options.residentEvidenceRecall] : []),
|
|
2099
2266
|
],
|
|
2100
|
-
taskStore:
|
|
2267
|
+
taskStore: turnTaskStore,
|
|
2101
2268
|
systemPrompt,
|
|
2102
2269
|
messages,
|
|
2103
|
-
projectInstructionContext: projectInstructions.
|
|
2270
|
+
projectInstructionContext: projectInstructions.createTurnContext(),
|
|
2104
2271
|
opts: turnOpts,
|
|
2105
2272
|
resumeHandler,
|
|
2106
|
-
taskGateway: await subagentRuntime?.
|
|
2107
|
-
completionInbox: await subagentRuntime?.
|
|
2273
|
+
taskGateway: await subagentRuntime?.gatewayForTurn(turnId),
|
|
2274
|
+
completionInbox: await subagentRuntime?.completionInboxForTurn(turnId),
|
|
2108
2275
|
promptContributions,
|
|
2109
2276
|
...(webCapability ? { web: webCapability } : {}),
|
|
2110
2277
|
...(nativeWebSearch ? { webSearch: nativeWebSearch } : {}),
|
|
2111
|
-
// Tasks join this
|
|
2278
|
+
// Tasks join this turn's registry inside query, after the fork.
|
|
2112
2279
|
// Keep the existing eager path unless deferral was requested.
|
|
2113
2280
|
runtimeToolOverrides: {
|
|
2114
2281
|
task_create: options.toolLoading === 'deferred' ? 'deferred' : 'active',
|
|
2115
2282
|
task_update: options.toolLoading === 'deferred' ? 'deferred' : 'active',
|
|
2116
2283
|
task_list: options.toolLoading === 'deferred' ? 'deferred' : 'active',
|
|
2117
2284
|
},
|
|
2118
|
-
|
|
2119
|
-
onRunEvent: options.onRunEvent,
|
|
2285
|
+
onSessionEvent: options.onSessionEvent,
|
|
2120
2286
|
...(sandbox.provider ? { sandboxProvider: sandbox.provider } : {}),
|
|
2121
2287
|
...(options.sandbox?.teardownTimeoutMs !== undefined
|
|
2122
2288
|
? {
|
|
@@ -2126,31 +2292,36 @@ export async function createAgentSession(prefs, detected, options = {}) {
|
|
|
2126
2292
|
});
|
|
2127
2293
|
}
|
|
2128
2294
|
finally {
|
|
2129
|
-
|
|
2130
|
-
capturedAuthority &&
|
|
2131
|
-
|
|
2132
|
-
|
|
2295
|
+
for (const turnId of claimed) {
|
|
2296
|
+
if (capturedAuthority && goalAuthorities.get(turnId) === capturedAuthority) {
|
|
2297
|
+
goalAuthorities.delete(turnId);
|
|
2298
|
+
}
|
|
2133
2299
|
}
|
|
2134
2300
|
}
|
|
2135
2301
|
}
|
|
2136
2302
|
finally {
|
|
2137
|
-
|
|
2138
|
-
delegatedResumeHandlers.
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2303
|
+
for (const turnId of claimed) {
|
|
2304
|
+
if (delegatedResumeHandlers.get(turnId) !== resumeHandler)
|
|
2305
|
+
continue;
|
|
2306
|
+
delegatedResumeHandlers.delete(turnId);
|
|
2307
|
+
modelSwitchHandlers.delete(turnId);
|
|
2308
|
+
delegatedInputWaiters.delete(turnId);
|
|
2309
|
+
delegationScopes.delete(turnId);
|
|
2310
|
+
delegationLimits.delete(turnId);
|
|
2311
|
+
await subagentRuntime?.releaseTurn(turnId);
|
|
2144
2312
|
}
|
|
2145
2313
|
}
|
|
2146
2314
|
})()),
|
|
2147
|
-
resumeDurable: (
|
|
2148
|
-
entry,
|
|
2149
|
-
checkpointStore,
|
|
2150
|
-
...(claimFence !== undefined ? { claimFence } : {}),
|
|
2151
|
-
...(signal ? { signal } : {}),
|
|
2152
|
-
}),
|
|
2315
|
+
resumeDurable: (params) => kernelResume(params),
|
|
2153
2316
|
resumePaused: (params) => resumePausedStream(params),
|
|
2317
|
+
abandonTurn: (turnId, reason) => operations.promise(undefined, async () => {
|
|
2318
|
+
if (options.ephemeral)
|
|
2319
|
+
throw new Error('An ephemeral session keeps no turn to abandon.');
|
|
2320
|
+
await abandonTurn(scope.sessionId, turnId, reason, {
|
|
2321
|
+
log: DiskSessionLog.at(paths, { sessionId: scope.sessionId }),
|
|
2322
|
+
...(options.conversationSessions ? { index: options.conversationSessions.index } : {}),
|
|
2323
|
+
});
|
|
2324
|
+
}),
|
|
2154
2325
|
};
|
|
2155
2326
|
}
|
|
2156
2327
|
function planFallbacks(members, detected) {
|
|
@@ -2292,6 +2463,9 @@ export function constructProvider(id, det, model, context = {}) {
|
|
|
2292
2463
|
}),
|
|
2293
2464
|
baseURL: det?.baseUrl,
|
|
2294
2465
|
model,
|
|
2466
|
+
// Read at every lookup, so the launch's background catalogue refresh
|
|
2467
|
+
// reaches this provider whenever it lands, including after now.
|
|
2468
|
+
catalogue: activeZenCatalogue,
|
|
2295
2469
|
...(context.sessionId ? { sessionId: context.sessionId } : {}),
|
|
2296
2470
|
});
|
|
2297
2471
|
return provider;
|
|
@@ -2379,7 +2553,7 @@ export async function describeProviderModels(id, det, signal) {
|
|
|
2379
2553
|
signal?.throwIfAborted();
|
|
2380
2554
|
// constructProvider calls ProviderRegistry.create, which throws
|
|
2381
2555
|
// "Unsupported provider type" until the vendor package has registered
|
|
2382
|
-
// itself. The
|
|
2556
|
+
// itself. The turn path registers lazily via ensureRegistered; the
|
|
2383
2557
|
// listing path must do the same or every provider returns nothing.
|
|
2384
2558
|
await ensureRegistered(id);
|
|
2385
2559
|
signal?.throwIfAborted();
|
|
@@ -2389,7 +2563,11 @@ export async function describeProviderModels(id, det, signal) {
|
|
|
2389
2563
|
const models = await runPickerProviderOperation(signal, (operationSignal) => provider.listModels?.(operationSignal) ?? Promise.resolve([]));
|
|
2390
2564
|
return {
|
|
2391
2565
|
kind: 'ok',
|
|
2392
|
-
|
|
2566
|
+
// A Zen id served with no known wire is listed by the driver for hosts
|
|
2567
|
+
// that can name a protocol. This one cannot, so it is not offered.
|
|
2568
|
+
models: models
|
|
2569
|
+
.filter((m) => isOfferableModel(id, m.id))
|
|
2570
|
+
.map((m) => ({
|
|
2393
2571
|
id: m.id,
|
|
2394
2572
|
name: m.name || m.id,
|
|
2395
2573
|
...(m.inputModalities !== undefined ? { inputModalities: [...m.inputModalities] } : {}),
|
|
@@ -2499,15 +2677,20 @@ function lastUserText(messages) {
|
|
|
2499
2677
|
return '';
|
|
2500
2678
|
}
|
|
2501
2679
|
/**
|
|
2502
|
-
* A scope for a session no host supplied one for
|
|
2503
|
-
*
|
|
2504
|
-
*
|
|
2680
|
+
* A scope for a session no host supplied one for.
|
|
2681
|
+
*
|
|
2682
|
+
* The project is the one the working directory's checkout stands for
|
|
2683
|
+
* (`projects/<slug>/project.json`), not minted: a minted one gave every
|
|
2684
|
+
* session a project of its own, so generated memory and task state were
|
|
2685
|
+
* partitioned per launch and a second session in the same directory could
|
|
2686
|
+
* not see what the first had saved. The session, topic and tenant stay
|
|
2687
|
+
* minted — nothing here has a store to find existing ones in.
|
|
2505
2688
|
*/
|
|
2506
|
-
function mintScope() {
|
|
2689
|
+
function mintScope(projectId, sessionId) {
|
|
2507
2690
|
return {
|
|
2508
|
-
sessionId: generateSessionId(),
|
|
2691
|
+
sessionId: sessionId ?? generateSessionId(),
|
|
2509
2692
|
topicId: generateTopicId(),
|
|
2510
|
-
projectId
|
|
2693
|
+
projectId,
|
|
2511
2694
|
tenantId: generateTenantId(),
|
|
2512
2695
|
};
|
|
2513
2696
|
}
|
|
@@ -2544,7 +2727,7 @@ function gateFor(rules) {
|
|
|
2544
2727
|
// unbounded context growth on long ones.
|
|
2545
2728
|
//
|
|
2546
2729
|
// `contextWindowTokens` is deliberately omitted: the SDK resolves the window
|
|
2547
|
-
// from `
|
|
2730
|
+
// from `turnConfig.model`, which is the value the user actually chose. Pinning
|
|
2548
2731
|
// a number here would fix one window across every model the CLI can talk to.
|
|
2549
2732
|
const COMPACTION_CONFIG = {
|
|
2550
2733
|
strategy: 'salience',
|
|
@@ -2588,11 +2771,11 @@ function compactionConfigFor(compaction) {
|
|
|
2588
2771
|
: {}),
|
|
2589
2772
|
};
|
|
2590
2773
|
}
|
|
2591
|
-
async function* runTurn({
|
|
2774
|
+
async function* runTurn({ retainedToolPreviewChars, fileReadTracker, provider, compactionConfig, consolidateInto, backgroundJobs, backgroundJobOwner, fallbackProviders, model, tools, pluginManager, skillRegistry, skills, scope, turnId, paths, sessionLog, claimTurn, workingDirectory, limits, additionalDirectories, escalation, sandboxWorkspace, rules, structuredOutput, reviewAnswer, maxAnswerReviews, promoteMemory, prepareStep, resumeHandler, taskStore, systemPrompt, messages, projectInstructionContext, opts, taskGateway, completionInbox, promptContributions, runtimeToolOverrides, webSearch, web, sandboxProvider, sandboxTeardownTimeoutMs, onSessionEvent, }) {
|
|
2592
2775
|
const signal = opts?.signal;
|
|
2593
2776
|
// One presenter for the whole stream, built from the registry this scope
|
|
2594
2777
|
// already holds. Its absence HERE is what forced presentation to be name
|
|
2595
|
-
// matching in the first place: `toAgentEvent` is pure over a `
|
|
2778
|
+
// matching in the first place: `toAgentEvent` is pure over a `SessionEvent`
|
|
2596
2779
|
// and could not ask a tool anything, so the host guessed from the name.
|
|
2597
2780
|
const presenter = createToolPresenter(tools);
|
|
2598
2781
|
try {
|
|
@@ -2601,18 +2784,22 @@ async function* runTurn({ emergencySave, retainedToolPreviewChars, fileReadTrack
|
|
|
2601
2784
|
...(fileReadTracker ? { fileReadTracker } : {}),
|
|
2602
2785
|
...(structuredOutput ? { structuredOutput } : {}),
|
|
2603
2786
|
provider,
|
|
2604
|
-
|
|
2605
|
-
...(
|
|
2787
|
+
paths,
|
|
2788
|
+
...(sessionLog ? { sessionLog } : {}),
|
|
2789
|
+
// Reserved by the session, so a new turn begins under this id.
|
|
2790
|
+
turnId,
|
|
2791
|
+
...(opts?.origin ? { origin: opts.origin } : {}),
|
|
2792
|
+
...(opts?.abandonInterrupted ? { abandonInterrupted: true } : {}),
|
|
2606
2793
|
// Omitted rather than empty when there is no tail. `query` treats the
|
|
2607
|
-
// two the same, but an absent option reads as "this
|
|
2608
|
-
// where `[]` reads as "this
|
|
2794
|
+
// two the same, but an absent option reads as "this turn has no chain"
|
|
2795
|
+
// where `[]` reads as "this turn has a chain with nothing in it".
|
|
2609
2796
|
...(fallbackProviders.length > 0 ? { fallbackProviders } : {}),
|
|
2610
2797
|
tools,
|
|
2611
2798
|
...(pluginManager ? { pluginManager } : {}),
|
|
2612
2799
|
...(skillRegistry ? { skillRegistry } : {}),
|
|
2613
2800
|
...(skills ? { skills } : {}),
|
|
2614
2801
|
// Withheld at both provider and executor boundaries on every ordinary
|
|
2615
|
-
// turn. An admitted send owns the exact
|
|
2802
|
+
// turn. An admitted send owns the exact turn-scoped authority above.
|
|
2616
2803
|
...(!opts?.goalRound ? { deniedTools: SESSION_GOAL_TOOL_NAMES } : {}),
|
|
2617
2804
|
taskStore,
|
|
2618
2805
|
...(taskGateway ? { taskScheduler: taskGateway } : {}),
|
|
@@ -2626,20 +2813,19 @@ async function* runTurn({ emergencySave, retainedToolPreviewChars, fileReadTrack
|
|
|
2626
2813
|
compactionConfig,
|
|
2627
2814
|
...(consolidateInto ? { consolidateInto } : {}),
|
|
2628
2815
|
...(backgroundJobs ? { backgroundJobs, backgroundJobOwner } : {}),
|
|
2629
|
-
|
|
2630
|
-
// instead drains the run and writes its finish/runner receipts first.
|
|
2631
|
-
emergencySave,
|
|
2632
|
-
runConfig: {
|
|
2816
|
+
turnConfig: {
|
|
2633
2817
|
model,
|
|
2634
2818
|
...(sandboxProvider ? { sandbox: { workspace: sandboxWorkspace } } : {}),
|
|
2635
2819
|
...(opts?.effort !== undefined ? { effort: opts.effort } : {}),
|
|
2636
2820
|
...(webSearch ? { webSearch } : {}),
|
|
2637
|
-
...
|
|
2821
|
+
...resolveTurnGuards(limits),
|
|
2638
2822
|
maxResponseTokens: 8192,
|
|
2639
2823
|
permissionMode: 'auto',
|
|
2824
|
+
// The kernel keeps every checkpoint unless told otherwise.
|
|
2825
|
+
pruneKeepLast: CLI_CHECKPOINT_RETENTION,
|
|
2640
2826
|
},
|
|
2641
2827
|
// The operator's gate, if they set one. Omitted rather than passed
|
|
2642
|
-
// as undefined so a
|
|
2828
|
+
// as undefined so a turn with no gate is byte-identical to the one
|
|
2643
2829
|
// that shipped before gates existed.
|
|
2644
2830
|
...(reviewAnswer ? { reviewAnswer } : {}),
|
|
2645
2831
|
...(maxAnswerReviews !== undefined ? { maxAnswerReviews } : {}),
|
|
@@ -2656,6 +2842,7 @@ async function* runTurn({ emergencySave, retainedToolPreviewChars, fileReadTrack
|
|
|
2656
2842
|
...(completionInbox ? { completionInbox } : {}),
|
|
2657
2843
|
workingDirectory,
|
|
2658
2844
|
...(additionalDirectories?.length ? { additionalDirectories } : {}),
|
|
2845
|
+
...(escalation ?? {}),
|
|
2659
2846
|
// The exemption reads `tools` at decision time, so it sees the task
|
|
2660
2847
|
// tools `query()` registers deferred below and any tool server that
|
|
2661
2848
|
// connected after this session was built.
|
|
@@ -2673,28 +2860,35 @@ async function* runTurn({ emergencySave, retainedToolPreviewChars, fileReadTrack
|
|
|
2673
2860
|
const next = await events.next();
|
|
2674
2861
|
if (next.done) {
|
|
2675
2862
|
settled = true;
|
|
2676
|
-
// A
|
|
2677
|
-
// conversation. Only the latter crosses this host seam.
|
|
2678
|
-
// summaries survive because they ARE conversation state;
|
|
2679
|
-
// system prompts are rebuilt fresh on every send and stay
|
|
2680
|
-
|
|
2863
|
+
// A Turn contains its fresh static/dynamic system floor as well as
|
|
2864
|
+
// the conversation. Only the latter crosses this host seam.
|
|
2865
|
+
// Compaction summaries survive because they ARE conversation state;
|
|
2866
|
+
// arbitrary system prompts are rebuilt fresh on every send and stay
|
|
2867
|
+
// private to it.
|
|
2868
|
+
opts?.onConversationMessages?.(projectTurnConversation(next.value.messages));
|
|
2681
2869
|
return;
|
|
2682
2870
|
}
|
|
2683
2871
|
const event = next.value;
|
|
2872
|
+
// Every event inside a turn names it. Should the kernel have begun the
|
|
2873
|
+
// turn under an id other than the reserved one, that id gets the same
|
|
2874
|
+
// authority before any of its tools can run.
|
|
2875
|
+
if ('turnId' in event && typeof event.turnId === 'string' && event.turnId !== turnId) {
|
|
2876
|
+
claimTurn(event.turnId);
|
|
2877
|
+
}
|
|
2684
2878
|
// Before the abort check and before `toAgentEvent`: a session
|
|
2685
2879
|
// cancelled mid-turn still produced the events up to that point, and
|
|
2686
2880
|
// they are the interesting ones. Every event, not just the ones the
|
|
2687
2881
|
// TUI renders — an export that only saw what the screen showed would
|
|
2688
2882
|
// be a recording of the interface rather than of the session.
|
|
2689
|
-
|
|
2883
|
+
onSessionEvent?.(event);
|
|
2690
2884
|
if (signal?.aborted) {
|
|
2691
2885
|
if (!abortReported) {
|
|
2692
2886
|
abortReported = true;
|
|
2693
2887
|
yield { kind: 'error', message: 'aborted' };
|
|
2694
2888
|
}
|
|
2695
2889
|
// Let cancellation settle in the kernel. Calling return() here
|
|
2696
|
-
// discarded its
|
|
2697
|
-
//
|
|
2890
|
+
// discarded its Turn before the recorder closed it, losing tool
|
|
2891
|
+
// receipts and reasoning before the next user turn.
|
|
2698
2892
|
continue;
|
|
2699
2893
|
}
|
|
2700
2894
|
const mapped = toAgentEvent(event, presenter);
|
|
@@ -2704,7 +2898,7 @@ async function* runTurn({ emergencySave, retainedToolPreviewChars, fileReadTrack
|
|
|
2704
2898
|
}
|
|
2705
2899
|
}
|
|
2706
2900
|
finally {
|
|
2707
|
-
// Manual iteration is what exposes the generator's
|
|
2901
|
+
// Manual iteration is what exposes the generator's Turn return value.
|
|
2708
2902
|
// Preserve `for await`'s other guarantee too: a consumer that stops
|
|
2709
2903
|
// early must close the live query instead of abandoning its transport.
|
|
2710
2904
|
if (!settled)
|
|
@@ -2712,6 +2906,23 @@ async function* runTurn({ emergencySave, retainedToolPreviewChars, fileReadTrack
|
|
|
2712
2906
|
}
|
|
2713
2907
|
}
|
|
2714
2908
|
catch (err) {
|
|
2909
|
+
if (isTurnInProgressError(err)) {
|
|
2910
|
+
// Nothing was begun: the conversation already has an active turn. Said
|
|
2911
|
+
// by name, with the turn and its state, because the ways out differ —
|
|
2912
|
+
// a paused turn is resumed or abandoned, a running one is waited for.
|
|
2913
|
+
yield {
|
|
2914
|
+
kind: 'error',
|
|
2915
|
+
message: `This conversation already has a ${err.state} turn (${err.activeTurnId}). ${err.state === 'running'
|
|
2916
|
+
? 'Wait for it to finish.'
|
|
2917
|
+
: 'Resume it with /resume, or close it with /abandon.'}`,
|
|
2918
|
+
turnInProgress: {
|
|
2919
|
+
sessionId: String(err.sessionId),
|
|
2920
|
+
activeTurnId: String(err.activeTurnId),
|
|
2921
|
+
state: err.state,
|
|
2922
|
+
},
|
|
2923
|
+
};
|
|
2924
|
+
return;
|
|
2925
|
+
}
|
|
2715
2926
|
yield {
|
|
2716
2927
|
kind: 'error',
|
|
2717
2928
|
message: err instanceof Error ? err.message : String(err),
|
|
@@ -2725,12 +2936,16 @@ async function* runTurn({ emergencySave, retainedToolPreviewChars, fileReadTrack
|
|
|
2725
2936
|
* (`createReviewHandler`); what this application adds is the person to ask
|
|
2726
2937
|
* and the session's "approve all" box, which the screen also reads.
|
|
2727
2938
|
*/
|
|
2728
|
-
export function makeResumeHandler(approval, onPermission, mode = onPermission ? 'prompt' : 'auto', exempt = () => false) {
|
|
2939
|
+
export function makeResumeHandler(approval, onPermission, mode = onPermission ? 'prompt' : 'auto', exempt = () => false, escapePolicy = {}) {
|
|
2729
2940
|
return createReviewHandler({
|
|
2730
2941
|
mode,
|
|
2731
2942
|
prompt: onPermission,
|
|
2732
2943
|
exempt,
|
|
2733
2944
|
remembered: approval,
|
|
2945
|
+
// Refused unless the operator wrote `sandbox.allowUnattendedEscape`: a
|
|
2946
|
+
// session with nobody to ask has nobody to consent to leaving the
|
|
2947
|
+
// sandbox, and `auto` is not consent to a command it never showed.
|
|
2948
|
+
unattendedSandboxEscape: escapePolicy.unattendedSandboxEscape ?? 'refuse',
|
|
2734
2949
|
});
|
|
2735
2950
|
}
|
|
2736
2951
|
/**
|
|
@@ -2750,15 +2965,15 @@ export function promptExemptToolNames(registry) {
|
|
|
2750
2965
|
/** A batch needs explicit approval when any call mutates state. */
|
|
2751
2966
|
export const batchNeedsPrompt = batchNeedsReview;
|
|
2752
2967
|
/**
|
|
2753
|
-
* Translate one SDK `
|
|
2968
|
+
* Translate one SDK `SessionEvent` into the TUI's `AgentEvent` vocabulary, or
|
|
2754
2969
|
* `null` for events the chat surface doesn't render (iteration markers,
|
|
2755
|
-
*
|
|
2970
|
+
* checkpoints, plan lifecycle, …). Pure — unit-tested.
|
|
2756
2971
|
*/
|
|
2757
2972
|
export function toAgentEvent(event, presenter) {
|
|
2758
2973
|
switch (event.type) {
|
|
2759
2974
|
case 'hosted_tool': {
|
|
2760
2975
|
const common = {
|
|
2761
|
-
|
|
2976
|
+
turnId: event.turnId,
|
|
2762
2977
|
toolUseId: event.tool.id,
|
|
2763
2978
|
toolName: 'web_search',
|
|
2764
2979
|
};
|
|
@@ -2783,7 +2998,7 @@ export function toAgentEvent(event, presenter) {
|
|
|
2783
2998
|
text: event.text,
|
|
2784
2999
|
...(event.textPart ? { textPart: event.textPart } : {}),
|
|
2785
3000
|
...(event.messageId ? { messageId: event.messageId } : {}),
|
|
2786
|
-
...(event.
|
|
3001
|
+
...(event.turnId ? { turnId: event.turnId } : {}),
|
|
2787
3002
|
};
|
|
2788
3003
|
case 'reasoning_started':
|
|
2789
3004
|
// A redacted block has no text to show; the empty delta still says
|
|
@@ -2796,7 +3011,7 @@ export function toAgentEvent(event, presenter) {
|
|
|
2796
3011
|
case 'tool_executing':
|
|
2797
3012
|
return {
|
|
2798
3013
|
kind: 'tool-start',
|
|
2799
|
-
|
|
3014
|
+
turnId: event.turnId,
|
|
2800
3015
|
...(event.toolName === 'wait_for_task' &&
|
|
2801
3016
|
typeof event.input?.task_id === 'string'
|
|
2802
3017
|
? { taskId: event.input.task_id }
|
|
@@ -2818,7 +3033,7 @@ export function toAgentEvent(event, presenter) {
|
|
|
2818
3033
|
case 'tool_progress':
|
|
2819
3034
|
return {
|
|
2820
3035
|
kind: 'tool-progress',
|
|
2821
|
-
|
|
3036
|
+
turnId: event.turnId,
|
|
2822
3037
|
toolUseId: event.toolUseId,
|
|
2823
3038
|
toolName: event.toolName,
|
|
2824
3039
|
message: event.message,
|
|
@@ -2840,7 +3055,7 @@ export function toAgentEvent(event, presenter) {
|
|
|
2840
3055
|
return {
|
|
2841
3056
|
kind: 'tool-end',
|
|
2842
3057
|
output: event.result,
|
|
2843
|
-
|
|
3058
|
+
turnId: event.turnId,
|
|
2844
3059
|
toolUseId: event.toolUseId,
|
|
2845
3060
|
toolName: event.toolName,
|
|
2846
3061
|
isError: event.isError,
|
|
@@ -2862,6 +3077,8 @@ export function toAgentEvent(event, presenter) {
|
|
|
2862
3077
|
// cannot ground, and a `0` here would ground a wrong one.
|
|
2863
3078
|
return {
|
|
2864
3079
|
kind: 'usage',
|
|
3080
|
+
sessionId: event.sessionId,
|
|
3081
|
+
...(event.turnId ? { turnId: event.turnId } : {}),
|
|
2865
3082
|
totalTokens: event.usage.totalTokens,
|
|
2866
3083
|
...(event.budget ? { budget: event.budget } : {}),
|
|
2867
3084
|
cost: event.cost,
|
|
@@ -2920,7 +3137,7 @@ export function toAgentEvent(event, presenter) {
|
|
|
2920
3137
|
subject: event.subject,
|
|
2921
3138
|
status: event.status,
|
|
2922
3139
|
};
|
|
2923
|
-
case '
|
|
3140
|
+
case 'turn_paused':
|
|
2924
3141
|
// A pause is not an error and not an invisible end. The checkpoint and
|
|
2925
3142
|
// classification are the recovery surface; dropping this event made a
|
|
2926
3143
|
// shell report success and let the interactive queue run on a premise
|
|
@@ -2928,26 +3145,28 @@ export function toAgentEvent(event, presenter) {
|
|
|
2928
3145
|
return {
|
|
2929
3146
|
kind: 'paused',
|
|
2930
3147
|
...(event.budget ? { budget: event.budget } : {}),
|
|
2931
|
-
|
|
3148
|
+
turnId: String(event.turnId),
|
|
2932
3149
|
checkpointId: event.checkpointId,
|
|
2933
3150
|
reason: event.reason,
|
|
2934
3151
|
...(event.failure ? { failure: event.failure } : {}),
|
|
2935
3152
|
...(event.providerError ? { providerError: event.providerError } : {}),
|
|
2936
3153
|
...(event.explanation ? { explanation: event.explanation } : {}),
|
|
2937
3154
|
};
|
|
2938
|
-
case '
|
|
2939
|
-
// Carried through rather than dropped: `
|
|
3155
|
+
case 'turn_completed':
|
|
3156
|
+
// Carried through rather than dropped: `turn_failed` fires only from
|
|
2940
3157
|
// the throw path, so this event is also how a budget stop, a
|
|
2941
3158
|
// timeout, a cancellation and a blocked output guardrail arrive. A
|
|
2942
|
-
// consumer that reads this as success reports one for a
|
|
3159
|
+
// consumer that reads this as success reports one for a turn whose
|
|
2943
3160
|
// answer was refused.
|
|
2944
3161
|
return {
|
|
2945
3162
|
kind: 'done',
|
|
3163
|
+
sessionId: event.sessionId,
|
|
3164
|
+
turnId: event.turnId,
|
|
2946
3165
|
text: event.result,
|
|
2947
3166
|
...(event.budget ? { budget: event.budget } : {}),
|
|
2948
3167
|
...(event.stopReason ? { stopReason: event.stopReason } : {}),
|
|
2949
3168
|
};
|
|
2950
|
-
case '
|
|
3169
|
+
case 'turn_failed':
|
|
2951
3170
|
// Keep the compatibility string and the structure. Prefixing the
|
|
2952
3171
|
// message with only the coarse `provider_error` code discarded the
|
|
2953
3172
|
// stable explanation, retry delay and first-hand provider detail while
|
|
@@ -3057,7 +3276,7 @@ function describeCompactionFailure(event) {
|
|
|
3057
3276
|
* the logs use.
|
|
3058
3277
|
*/
|
|
3059
3278
|
const FALLBACK_REASONS = {
|
|
3060
|
-
rate_limit: 'it rate limited this
|
|
3279
|
+
rate_limit: 'it rate limited this turn and the retries did not clear it',
|
|
3061
3280
|
overloaded: 'it was overloaded and the retries did not clear it',
|
|
3062
3281
|
server_error: 'it kept failing and the retries did not clear it',
|
|
3063
3282
|
timeout: 'it did not answer in time',
|
|
@@ -3067,7 +3286,7 @@ const FALLBACK_REASONS = {
|
|
|
3067
3286
|
unknown: 'it failed in a way namzu could not classify',
|
|
3068
3287
|
};
|
|
3069
3288
|
/**
|
|
3070
|
-
* The one line an operator reads when their
|
|
3289
|
+
* The one line an operator reads when their turn changes hands.
|
|
3071
3290
|
*
|
|
3072
3291
|
* Both members are named with their chain position, because naming only the
|
|
3073
3292
|
* replacement leaves an operator with four declared members unable to tell
|
|
@@ -3322,7 +3541,7 @@ function emptySession(errorHint, errorKind = 'environment') {
|
|
|
3322
3541
|
},
|
|
3323
3542
|
// Throws rather than reporting `no-checkpoint`. A resume that reported
|
|
3324
3543
|
// "there is nothing to continue" when the truth is "this session has no
|
|
3325
|
-
// provider" would let a drainer mark every
|
|
3544
|
+
// provider" would let a drainer mark every turn in a queue as a dead end
|
|
3326
3545
|
// and move on — an unavailable capability degrading a check into a
|
|
3327
3546
|
// wrong answer, on the one path where the answer is destructive.
|
|
3328
3547
|
resumeDurable: async () => {
|