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