@aragon-agent/cli 0.6.46 → 0.6.48
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 +28 -0
- package/README.md +80 -12
- package/dist/agent/coalesce.d.ts +5 -1
- package/dist/agent/coalesce.js +13 -0
- package/dist/agent/controller.d.ts +41 -20
- package/dist/agent/controller.js +68 -15
- package/dist/agent/conversation-reset.d.ts +1 -1
- package/dist/agent/governance-wiring.d.ts +6 -6
- package/dist/agent/governance-wiring.js +4 -4
- package/dist/agent/headless.d.ts +5 -5
- package/dist/agent/headless.js +4 -4
- package/dist/agent/job-notices.d.ts +1 -1
- package/dist/agent/job-notices.js +1 -1
- package/dist/agent/kit-apply.d.ts +3 -3
- package/dist/agent/kit-apply.js +3 -3
- package/dist/agent/model-profile-settings.d.ts +2 -2
- package/dist/agent/model-profile-settings.js +6 -6
- package/dist/agent/prompt-options.d.ts +1 -1
- package/dist/agent/system-prompt.d.ts +1 -1
- package/dist/agent/user-attention.d.ts +17 -0
- package/dist/agent/user-attention.js +32 -0
- package/dist/{ui → app}/App.d.ts +9 -9
- package/dist/{ui → app}/App.js +113 -79
- package/dist/{ui → app}/use-startup-notices.d.ts +1 -1
- package/dist/{ui → app}/use-startup-notices.js +2 -2
- package/dist/cli.js +31 -32
- package/dist/commands/agents-command.js +1 -1
- package/dist/commands/builtins.js +9 -9
- package/dist/commands/governance-command.js +2 -2
- package/dist/commands/governance-runs-table.js +1 -1
- package/dist/commands/kit-command.js +1 -1
- package/dist/commands/perf.d.ts +3 -3
- package/dist/commands/perf.js +1 -1
- package/dist/commands/registry.d.ts +8 -8
- package/dist/commands/skills.js +2 -2
- package/dist/commands/team-command.js +1 -1
- package/dist/compaction/archive.d.ts +1 -1
- package/dist/compaction/archive.js +1 -1
- package/dist/compaction/child.d.ts +3 -3
- package/dist/compaction/child.js +4 -4
- package/dist/compaction/command.js +5 -5
- package/dist/compaction/compactor.d.ts +6 -6
- package/dist/compaction/compactor.js +6 -6
- package/dist/compaction/context-command.js +1 -1
- package/dist/compaction/digest.js +1 -1
- package/dist/compaction/memory-input.js +1 -1
- package/dist/compaction/memory-validation.js +1 -1
- package/dist/compaction/memory.js +1 -1
- package/dist/compaction/summarize-call.js +1 -1
- package/dist/compaction/summary-prompt.js +1 -1
- package/dist/compaction/tail-budget.js +2 -2
- package/dist/compaction/wiring.d.ts +5 -5
- package/dist/compaction/wiring.js +2 -2
- package/dist/config/cli-commands.d.ts +1 -1
- package/dist/config/cli-commands.js +3 -3
- package/dist/config/env.d.ts +1 -1
- package/dist/config/env.js +4 -4
- package/dist/config/load.d.ts +2 -2
- package/dist/config/load.js +10 -31
- package/dist/config/migrate-state-out-of-config.js +1 -1
- package/dist/config/model-profile-store.d.ts +4 -54
- package/dist/config/model-profile-store.js +3 -3
- package/dist/config/prompt-history.js +1 -1
- package/dist/config/store.d.ts +2 -2
- package/dist/config/store.js +3 -3
- package/dist/diagnostics/doctor.js +4 -4
- package/dist/exec/bridge/decisions.d.ts +1 -1
- package/dist/exec/bridge/human-answers.d.ts +1 -1
- package/dist/exec/bridge/human-answers.js +1 -1
- package/dist/exec/bridge/protocol.d.ts +1 -1
- package/dist/exec/bridge/server.js +1 -1
- package/dist/exec/events.d.ts +2 -2
- package/dist/exec/index.d.ts +1 -1
- package/dist/exec/index.js +1 -1
- package/dist/exec/permission.js +1 -1
- package/dist/exec/runner.d.ts +3 -3
- package/dist/exec/runner.js +2 -2
- package/dist/exec/threads/runtime.js +1 -1
- package/dist/fast/digest.d.ts +1 -1
- package/dist/fast/digest.js +1 -1
- package/dist/fast/frames.d.ts +1 -1
- package/dist/fast/frames.js +1 -1
- package/dist/fast/prompt.d.ts +1 -1
- package/dist/fast/prompt.js +1 -1
- package/dist/fast/review-call.js +1 -1
- package/dist/fast/reviewer.d.ts +3 -3
- package/dist/fast/reviewer.js +1 -1
- package/dist/fast/summary-call.d.ts +3 -3
- package/dist/fast/summary-call.js +2 -2
- package/dist/fast/summary-prompt.d.ts +1 -1
- package/dist/fast/summary-prompt.js +1 -1
- package/dist/fast/wiring.d.ts +4 -4
- package/dist/fast/wiring.js +1 -1
- package/dist/governance/card-normalize.d.ts +1 -1
- package/dist/governance/copy.d.ts +1 -1
- package/dist/governance/copy.js +2 -2
- package/dist/governance/engine/artifact-persist.d.ts +1 -1
- package/dist/governance/engine/budget.d.ts +1 -1
- package/dist/governance/engine/budget.js +1 -1
- package/dist/governance/engine/cards.d.ts +1 -1
- package/dist/governance/engine/digest.js +1 -1
- package/dist/governance/engine/engine-core.d.ts +5 -5
- package/dist/governance/engine/engine-core.js +1 -1
- package/dist/governance/engine/engine.d.ts +1 -1
- package/dist/governance/engine/engine.js +1 -1
- package/dist/governance/engine/estimate.d.ts +1 -1
- package/dist/governance/engine/estimate.js +1 -1
- package/dist/governance/engine/inspector.js +2 -2
- package/dist/governance/engine/integration.d.ts +1 -1
- package/dist/governance/engine/lifecycle.d.ts +1 -1
- package/dist/governance/engine/lifecycle.js +1 -1
- package/dist/governance/engine/phase-members.d.ts +1 -1
- package/dist/governance/engine/phase-planner.d.ts +1 -1
- package/dist/governance/engine/phase-planner.js +1 -1
- package/dist/governance/engine/rebuild.d.ts +1 -1
- package/dist/governance/engine/report.d.ts +1 -1
- package/dist/governance/engine/report.js +1 -1
- package/dist/governance/engine/run-digest.d.ts +1 -1
- package/dist/governance/engine/spec-builder.d.ts +2 -2
- package/dist/governance/engine/spec-builder.js +2 -2
- package/dist/governance/engine/stage-runner.js +2 -2
- package/dist/governance/engine/start-input.d.ts +1 -1
- package/dist/governance/engine/start-input.js +1 -1
- package/dist/governance/engine/state-machine.d.ts +1 -1
- package/dist/governance/engine/state-machine.js +3 -3
- package/dist/governance/engine/submission-queries.d.ts +1 -1
- package/dist/governance/engine/submission-render.d.ts +1 -1
- package/dist/governance/engine/submissions.d.ts +1 -1
- package/dist/governance/engine/submissions.js +1 -1
- package/dist/governance/engine/visibility.d.ts +1 -1
- package/dist/governance/engine/visibility.js +1 -1
- package/dist/governance/posture-transition.d.ts +2 -2
- package/dist/governance/posture-transition.js +1 -1
- package/dist/governance/profiles.d.ts +1 -1
- package/dist/governance/prompts/orchestrator-block.d.ts +2 -2
- package/dist/governance/prompts/orchestrator-block.js +2 -2
- package/dist/governance/prompts/plan-addendum.d.ts +1 -1
- package/dist/governance/prompts/plan-addendum.js +1 -1
- package/dist/governance/prompts/role-blocks.d.ts +2 -2
- package/dist/governance/prompts/role-blocks.js +1 -1
- package/dist/governance/report-reader.js +1 -1
- package/dist/governance/roles.d.ts +1 -1
- package/dist/governance/rules/claims.d.ts +1 -1
- package/dist/governance/rules/claims.js +1 -1
- package/dist/governance/rules/elimination.d.ts +1 -1
- package/dist/governance/rules/elimination.js +1 -1
- package/dist/governance/rules/failure-analysis.d.ts +2 -2
- package/dist/governance/rules/failure-analysis.js +1 -1
- package/dist/governance/rules/ledger-replay.d.ts +1 -1
- package/dist/governance/rules/ledger-state.d.ts +1 -1
- package/dist/governance/rules/ledger-state.js +1 -1
- package/dist/governance/rules/ledger.d.ts +2 -2
- package/dist/governance/rules/ledger.js +2 -2
- package/dist/governance/rules/rca.d.ts +1 -1
- package/dist/governance/rules/rca.js +1 -1
- package/dist/governance/rules/settlement.d.ts +2 -2
- package/dist/governance/rules/settlement.js +2 -2
- package/dist/governance/rules/suspicion.d.ts +1 -1
- package/dist/governance/store/artifacts.d.ts +1 -1
- package/dist/governance/store/artifacts.js +1 -1
- package/dist/governance/store/events-log.d.ts +1 -1
- package/dist/governance/store/run-store.d.ts +1 -1
- package/dist/governance/store/run-store.js +1 -1
- package/dist/governance/store/scoreboard-export.d.ts +1 -1
- package/dist/governance/tools/child-tools.d.ts +1 -1
- package/dist/governance/tools/guard.d.ts +1 -1
- package/dist/governance/tools/guard.js +1 -1
- package/dist/governance/tools/inspector-tools.d.ts +2 -2
- package/dist/governance/tools/inspector-tools.js +2 -2
- package/dist/governance/tools/lead-tools.d.ts +1 -1
- package/dist/governance/tools/lead-tools.js +1 -1
- package/dist/governance/tools/normalize-util.js +1 -1
- package/dist/governance/tools/scan-incomplete.js +1 -1
- package/dist/governance/tools/shared-tools.d.ts +1 -1
- package/dist/governance/tools/shared-tools.js +1 -1
- package/dist/governance/tools/submit-normalizers.d.ts +1 -1
- package/dist/governance/tools/submit-normalizers.js +1 -1
- package/dist/governance/tools/submit-tools.d.ts +1 -1
- package/dist/logging/cli-commands.js +1 -1
- package/dist/logging/install.d.ts +1 -1
- package/dist/logging/install.js +3 -3
- package/dist/logging/logger.d.ts +2 -2
- package/dist/logging/logger.js +31 -2
- package/dist/proc/kill-tree.js +1 -1
- package/dist/proc/log-mirror.js +1 -1
- package/dist/proc/log-ring.js +2 -2
- package/dist/proc/notices.d.ts +1 -1
- package/dist/proc/notices.js +2 -2
- package/dist/proc/prompt.d.ts +1 -1
- package/dist/proc/prompt.js +8 -5
- package/dist/proc/readiness.js +1 -1
- package/dist/proc/supervisor.d.ts +1 -1
- package/dist/proc/supervisor.js +10 -4
- package/dist/proc/wait-plan.d.ts +39 -0
- package/dist/proc/wait-plan.js +77 -0
- package/dist/proc/wait.d.ts +37 -4
- package/dist/proc/wait.js +68 -5
- package/dist/prompts/command.d.ts +1 -1
- package/dist/prompts/command.js +1 -1
- package/dist/prompts/normalize.d.ts +1 -1
- package/dist/prompts/normalize.js +1 -1
- package/dist/prompts/resolve.d.ts +1 -1
- package/dist/prompts/store.d.ts +2 -12
- package/dist/prompts/store.js +6 -60
- package/dist/session/checkpoint.d.ts +2 -2
- package/dist/session/checkpoint.js +2 -0
- package/dist/session/clear-archive.d.ts +2 -2
- package/dist/session/persist.d.ts +2 -2
- package/dist/session/persist.js +20 -3
- package/dist/session/startup-session.d.ts +2 -2
- package/dist/session/validate-session.js +6 -3
- package/dist/skills/service.d.ts +2 -2
- package/dist/team/bus.d.ts +1 -1
- package/dist/team/bus.js +1 -1
- package/dist/team/child-reviewer.d.ts +2 -2
- package/dist/team/child-reviewer.js +1 -1
- package/dist/team/comm-tools.js +1 -1
- package/dist/team/dispatch-gate.js +1 -1
- package/dist/team/forced-dispatch.js +1 -1
- package/dist/team/forced-mode.d.ts +1 -1
- package/dist/team/forced-prompt.d.ts +1 -1
- package/dist/team/forced-prompt.js +1 -1
- package/dist/team/normalize.d.ts +1 -1
- package/dist/team/normalize.js +1 -1
- package/dist/team/overseer.d.ts +3 -3
- package/dist/team/overseer.js +3 -3
- package/dist/team/prompt.d.ts +1 -1
- package/dist/team/prompt.js +1 -1
- package/dist/team/report.d.ts +2 -2
- package/dist/team/report.js +2 -2
- package/dist/team/retry.d.ts +1 -1
- package/dist/team/retry.js +1 -1
- package/dist/team/runtime.d.ts +4 -4
- package/dist/team/runtime.js +2 -2
- package/dist/team/settled.d.ts +2 -2
- package/dist/team/subagent.d.ts +4 -4
- package/dist/team/subagent.js +5 -5
- package/dist/team/task-tool.d.ts +1 -1
- package/dist/team/task-tool.js +1 -1
- package/dist/todo/normalize.d.ts +1 -1
- package/dist/todo/normalize.js +1 -1
- package/dist/todo/prompt.d.ts +1 -1
- package/dist/todo/prompt.js +1 -1
- package/dist/todo/store.d.ts +1 -1
- package/dist/todo/store.js +2 -2
- package/dist/todo/todo-tool.d.ts +1 -1
- package/dist/todo/todo-tool.js +2 -2
- package/dist/tools/bash-tool.d.ts +1 -1
- package/dist/tools/bash-tool.js +2 -2
- package/dist/tools/diff.d.ts +1 -1
- package/dist/tools/diff.js +1 -1
- package/dist/tools/file-change-store.d.ts +1 -1
- package/dist/tools/fs-tools.d.ts +2 -2
- package/dist/tools/fs-tools.js +1 -1
- package/dist/tools/index.d.ts +3 -3
- package/dist/tools/index.js +1 -1
- package/dist/tools/job-tools.d.ts +14 -3
- package/dist/tools/job-tools.js +158 -35
- package/dist/tools/plan-tools.d.ts +1 -1
- package/dist/tools/plan-tools.js +1 -1
- package/dist/tools/proc-tools.d.ts +1 -1
- package/dist/tools/proc-tools.js +2 -2
- package/dist/update/classify-failure.d.ts +1 -1
- package/dist/update/install-lock.js +1 -1
- package/dist/update/install-source.d.ts +1 -1
- package/dist/update/installer.js +2 -2
- package/dist/update/npm-view.d.ts +1 -1
- package/dist/update/npm-view.js +1 -1
- package/dist/update/registry.d.ts +1 -1
- package/dist/update/registry.js +1 -1
- package/dist/update/service.d.ts +2 -2
- package/dist/update/service.js +2 -2
- package/package.json +7 -7
- package/dist/agent/agent-mode.d.ts +0 -108
- package/dist/agent/agent-mode.js +0 -121
- package/dist/agent/entry-limits.d.ts +0 -65
- package/dist/agent/entry-limits.js +0 -133
- package/dist/agent/governance-view.d.ts +0 -40
- package/dist/agent/governance-view.js +0 -40
- package/dist/agent/invocation-card.d.ts +0 -77
- package/dist/agent/invocation-card.js +0 -169
- package/dist/agent/model-probe-limits.d.ts +0 -39
- package/dist/agent/model-probe-limits.js +0 -25
- package/dist/agent/model-probe.d.ts +0 -104
- package/dist/agent/model-probe.js +0 -391
- package/dist/agent/queued-messages.d.ts +0 -23
- package/dist/agent/queued-messages.js +0 -49
- package/dist/agent/reducer.d.ts +0 -837
- package/dist/agent/reducer.js +0 -1617
- package/dist/agent/retry-view.d.ts +0 -68
- package/dist/agent/retry-view.js +0 -95
- package/dist/agent/usage.d.ts +0 -47
- package/dist/agent/usage.js +0 -84
- package/dist/compaction/limits.d.ts +0 -61
- package/dist/compaction/limits.js +0 -64
- package/dist/compaction/meter.d.ts +0 -276
- package/dist/compaction/meter.js +0 -512
- package/dist/compaction/pressure.d.ts +0 -163
- package/dist/compaction/pressure.js +0 -199
- package/dist/compaction/reason-text.d.ts +0 -36
- package/dist/compaction/reason-text.js +0 -78
- package/dist/compaction/types.d.ts +0 -360
- package/dist/compaction/types.js +0 -22
- package/dist/config/governance-config.d.ts +0 -125
- package/dist/config/governance-config.js +0 -164
- package/dist/config/kit-resolution.d.ts +0 -93
- package/dist/config/kit-resolution.js +0 -226
- package/dist/config/model-kits.d.ts +0 -225
- package/dist/config/model-kits.js +0 -302
- package/dist/config/model-profile-resolution.d.ts +0 -28
- package/dist/config/model-profile-resolution.js +0 -88
- package/dist/config/model-profiles.d.ts +0 -103
- package/dist/config/model-profiles.js +0 -188
- package/dist/config/schema.d.ts +0 -1645
- package/dist/config/schema.js +0 -1377
- package/dist/fast/limits.d.ts +0 -152
- package/dist/fast/limits.js +0 -152
- package/dist/fast/resolve.d.ts +0 -44
- package/dist/fast/resolve.js +0 -107
- package/dist/fast/task-summary-types.d.ts +0 -66
- package/dist/fast/task-summary-types.js +0 -17
- package/dist/fast/types.d.ts +0 -141
- package/dist/fast/types.js +0 -14
- package/dist/governance/governed-dispatch.d.ts +0 -19
- package/dist/governance/governed-dispatch.js +0 -17
- package/dist/governance/limits.d.ts +0 -107
- package/dist/governance/limits.js +0 -131
- package/dist/governance/posture.d.ts +0 -35
- package/dist/governance/posture.js +0 -59
- package/dist/governance/rules/events-table.d.ts +0 -46
- package/dist/governance/rules/events-table.js +0 -94
- package/dist/governance/types.d.ts +0 -661
- package/dist/governance/types.js +0 -27
- package/dist/input/control-sequences.d.ts +0 -71
- package/dist/input/control-sequences.js +0 -222
- package/dist/input/csiu-keys.d.ts +0 -66
- package/dist/input/csiu-keys.js +0 -181
- package/dist/input/cursor-report.d.ts +0 -61
- package/dist/input/cursor-report.js +0 -97
- package/dist/input/enter-sequences.d.ts +0 -64
- package/dist/input/enter-sequences.js +0 -153
- package/dist/input/interrupt-gesture.d.ts +0 -18
- package/dist/input/interrupt-gesture.js +0 -19
- package/dist/input/keyboard-enhancement.d.ts +0 -42
- package/dist/input/keyboard-enhancement.js +0 -65
- package/dist/input/keymap.d.ts +0 -20
- package/dist/input/keymap.js +0 -99
- package/dist/input/limits.d.ts +0 -118
- package/dist/input/limits.js +0 -109
- package/dist/input/mouse-events.d.ts +0 -87
- package/dist/input/mouse-events.js +0 -212
- package/dist/input/paste-parse.d.ts +0 -83
- package/dist/input/paste-parse.js +0 -107
- package/dist/input/pointer-router.d.ts +0 -11
- package/dist/input/pointer-router.js +0 -14
- package/dist/input/stdin-filter.d.ts +0 -112
- package/dist/input/stdin-filter.js +0 -657
- package/dist/input/win32-input-mode.d.ts +0 -61
- package/dist/input/win32-input-mode.js +0 -269
- package/dist/logging/levels.d.ts +0 -40
- package/dist/logging/levels.js +0 -59
- package/dist/logging/secret-registry.d.ts +0 -32
- package/dist/logging/secret-registry.js +0 -73
- package/dist/proc/limits.d.ts +0 -200
- package/dist/proc/limits.js +0 -207
- package/dist/proc/progress.d.ts +0 -40
- package/dist/proc/progress.js +0 -135
- package/dist/proc/types.d.ts +0 -263
- package/dist/proc/types.js +0 -15
- package/dist/prompts/copy.d.ts +0 -89
- package/dist/prompts/copy.js +0 -131
- package/dist/prompts/fill.d.ts +0 -51
- package/dist/prompts/fill.js +0 -46
- package/dist/prompts/limits.d.ts +0 -53
- package/dist/prompts/limits.js +0 -53
- package/dist/prompts/search.d.ts +0 -58
- package/dist/prompts/search.js +0 -146
- package/dist/prompts/types.d.ts +0 -204
- package/dist/prompts/types.js +0 -13
- package/dist/team/activity.d.ts +0 -67
- package/dist/team/activity.js +0 -130
- package/dist/team/limits.d.ts +0 -308
- package/dist/team/limits.js +0 -330
- package/dist/team/model.d.ts +0 -104
- package/dist/team/model.js +0 -150
- package/dist/team/panel-rows.d.ts +0 -48
- package/dist/team/panel-rows.js +0 -75
- package/dist/team/timeline.d.ts +0 -74
- package/dist/team/timeline.js +0 -101
- package/dist/team/types.d.ts +0 -354
- package/dist/team/types.js +0 -13
- package/dist/todo/acceptance.d.ts +0 -120
- package/dist/todo/acceptance.js +0 -399
- package/dist/todo/follow-through.d.ts +0 -138
- package/dist/todo/follow-through.js +0 -248
- package/dist/todo/limits.d.ts +0 -152
- package/dist/todo/limits.js +0 -152
- package/dist/todo/panel-rows.d.ts +0 -45
- package/dist/todo/panel-rows.js +0 -70
- package/dist/todo/plan-sections.d.ts +0 -60
- package/dist/todo/plan-sections.js +0 -108
- package/dist/todo/row-window.d.ts +0 -51
- package/dist/todo/row-window.js +0 -107
- package/dist/todo/types.d.ts +0 -124
- package/dist/todo/types.js +0 -12
- package/dist/tools/human-input.d.ts +0 -212
- package/dist/tools/human-input.js +0 -265
- package/dist/tools/patch.d.ts +0 -115
- package/dist/tools/patch.js +0 -453
- package/dist/tools/terminal-output.d.ts +0 -115
- package/dist/tools/terminal-output.js +0 -201
- package/dist/tools/tool-output-store.d.ts +0 -68
- package/dist/tools/tool-output-store.js +0 -102
- package/dist/ui/ActivityLine.d.ts +0 -26
- package/dist/ui/ActivityLine.js +0 -48
- package/dist/ui/AutocompletePopup.d.ts +0 -23
- package/dist/ui/AutocompletePopup.js +0 -17
- package/dist/ui/BottomStatusRow.d.ts +0 -10
- package/dist/ui/BottomStatusRow.js +0 -9
- package/dist/ui/Composer.d.ts +0 -115
- package/dist/ui/Composer.js +0 -64
- package/dist/ui/GovernancePanel.d.ts +0 -50
- package/dist/ui/GovernancePanel.js +0 -77
- package/dist/ui/GovernanceRun.d.ts +0 -18
- package/dist/ui/GovernanceRun.js +0 -11
- package/dist/ui/Header.d.ts +0 -48
- package/dist/ui/Header.js +0 -27
- package/dist/ui/Logo.d.ts +0 -32
- package/dist/ui/Logo.js +0 -65
- package/dist/ui/Markdown.d.ts +0 -29
- package/dist/ui/Markdown.js +0 -116
- package/dist/ui/PromptCaret.d.ts +0 -11
- package/dist/ui/PromptCaret.js +0 -31
- package/dist/ui/PromptInput.d.ts +0 -192
- package/dist/ui/PromptInput.js +0 -689
- package/dist/ui/QueuePanel.d.ts +0 -8
- package/dist/ui/QueuePanel.js +0 -9
- package/dist/ui/QueueStatusRow.d.ts +0 -24
- package/dist/ui/QueueStatusRow.js +0 -51
- package/dist/ui/ScrollbackShell.d.ts +0 -132
- package/dist/ui/ScrollbackShell.js +0 -26
- package/dist/ui/SessionOpener.d.ts +0 -30
- package/dist/ui/SessionOpener.js +0 -12
- package/dist/ui/StatusBar.d.ts +0 -129
- package/dist/ui/StatusBar.js +0 -61
- package/dist/ui/TeamPanel.d.ts +0 -76
- package/dist/ui/TeamPanel.js +0 -103
- package/dist/ui/ToastStack.d.ts +0 -8
- package/dist/ui/ToastStack.js +0 -31
- package/dist/ui/TodoPanel.d.ts +0 -52
- package/dist/ui/TodoPanel.js +0 -214
- package/dist/ui/Transcript.d.ts +0 -105
- package/dist/ui/Transcript.js +0 -251
- package/dist/ui/UpdateLine.d.ts +0 -44
- package/dist/ui/UpdateLine.js +0 -129
- package/dist/ui/activity-phrases.d.ts +0 -33
- package/dist/ui/activity-phrases.js +0 -50
- package/dist/ui/capabilities.d.ts +0 -27
- package/dist/ui/capabilities.js +0 -65
- package/dist/ui/clipboard-task.d.ts +0 -29
- package/dist/ui/clipboard-task.js +0 -66
- package/dist/ui/clipboard.d.ts +0 -31
- package/dist/ui/clipboard.js +0 -157
- package/dist/ui/color-runtime.d.ts +0 -5
- package/dist/ui/color-runtime.js +0 -29
- package/dist/ui/composer-input.d.ts +0 -38
- package/dist/ui/composer-input.js +0 -79
- package/dist/ui/composer-limits.d.ts +0 -30
- package/dist/ui/composer-limits.js +0 -45
- package/dist/ui/composer-rows.d.ts +0 -73
- package/dist/ui/composer-rows.js +0 -129
- package/dist/ui/console-bridge.d.ts +0 -19
- package/dist/ui/console-bridge.js +0 -52
- package/dist/ui/density.d.ts +0 -26
- package/dist/ui/density.js +0 -34
- package/dist/ui/display-tab.d.ts +0 -51
- package/dist/ui/display-tab.js +0 -82
- package/dist/ui/editor-navigation.d.ts +0 -55
- package/dist/ui/editor-navigation.js +0 -96
- package/dist/ui/editor-reducer.d.ts +0 -108
- package/dist/ui/editor-reducer.js +0 -187
- package/dist/ui/enter-frames.d.ts +0 -47
- package/dist/ui/enter-frames.js +0 -117
- package/dist/ui/entries/AssistantEntry.d.ts +0 -37
- package/dist/ui/entries/AssistantEntry.js +0 -43
- package/dist/ui/entries/CompactionCard.d.ts +0 -88
- package/dist/ui/entries/CompactionCard.js +0 -222
- package/dist/ui/entries/DiffView.d.ts +0 -61
- package/dist/ui/entries/DiffView.js +0 -103
- package/dist/ui/entries/EntryFrame.d.ts +0 -23
- package/dist/ui/entries/EntryFrame.js +0 -6
- package/dist/ui/entries/FastCard.d.ts +0 -53
- package/dist/ui/entries/FastCard.js +0 -105
- package/dist/ui/entries/GovernanceCard.d.ts +0 -30
- package/dist/ui/entries/GovernanceCard.js +0 -32
- package/dist/ui/entries/InvocationCard.d.ts +0 -20
- package/dist/ui/entries/InvocationCard.js +0 -32
- package/dist/ui/entries/QueuedEntry.d.ts +0 -27
- package/dist/ui/entries/QueuedEntry.js +0 -33
- package/dist/ui/entries/RetryCard.d.ts +0 -56
- package/dist/ui/entries/RetryCard.js +0 -60
- package/dist/ui/entries/ServiceCard.d.ts +0 -82
- package/dist/ui/entries/ServiceCard.js +0 -151
- package/dist/ui/entries/TeamCard.d.ts +0 -55
- package/dist/ui/entries/TeamCard.js +0 -94
- package/dist/ui/entries/TodoCard.d.ts +0 -54
- package/dist/ui/entries/TodoCard.js +0 -98
- package/dist/ui/entries/ToolCard.d.ts +0 -81
- package/dist/ui/entries/ToolCard.js +0 -160
- package/dist/ui/entries/ToolPreview.d.ts +0 -59
- package/dist/ui/entries/ToolPreview.js +0 -208
- package/dist/ui/entries/UserEntry.d.ts +0 -36
- package/dist/ui/entries/UserEntry.js +0 -57
- package/dist/ui/exit-snapshot.d.ts +0 -43
- package/dist/ui/exit-snapshot.js +0 -26
- package/dist/ui/frame-differ.d.ts +0 -166
- package/dist/ui/frame-differ.js +0 -354
- package/dist/ui/frame-observer.d.ts +0 -17
- package/dist/ui/frame-observer.js +0 -94
- package/dist/ui/frame-parser.d.ts +0 -7
- package/dist/ui/frame-parser.js +0 -18
- package/dist/ui/gauge.d.ts +0 -39
- package/dist/ui/gauge.js +0 -55
- package/dist/ui/glyphs.d.ts +0 -178
- package/dist/ui/glyphs.js +0 -165
- package/dist/ui/governance-copy.d.ts +0 -69
- package/dist/ui/governance-copy.js +0 -104
- package/dist/ui/governance-status.d.ts +0 -145
- package/dist/ui/governance-status.js +0 -326
- package/dist/ui/gradient.d.ts +0 -11
- package/dist/ui/gradient.js +0 -50
- package/dist/ui/ink-runtime.d.ts +0 -4
- package/dist/ui/ink-runtime.js +0 -51
- package/dist/ui/interaction-copy.d.ts +0 -82
- package/dist/ui/interaction-copy.js +0 -38
- package/dist/ui/interaction-hints.d.ts +0 -44
- package/dist/ui/interaction-hints.js +0 -97
- package/dist/ui/layout/AppShell.d.ts +0 -17
- package/dist/ui/layout/AppShell.js +0 -6
- package/dist/ui/layout/Gutter.d.ts +0 -67
- package/dist/ui/layout/Gutter.js +0 -42
- package/dist/ui/layout/MeasuredEntry.d.ts +0 -21
- package/dist/ui/layout/MeasuredEntry.js +0 -30
- package/dist/ui/layout/OverlayFrame.d.ts +0 -73
- package/dist/ui/layout/OverlayFrame.js +0 -110
- package/dist/ui/layout/ScrollIndicator.d.ts +0 -32
- package/dist/ui/layout/ScrollIndicator.js +0 -22
- package/dist/ui/layout/ScrollViewport.d.ts +0 -40
- package/dist/ui/layout/ScrollViewport.js +0 -222
- package/dist/ui/layout/agents-board.d.ts +0 -102
- package/dist/ui/layout/agents-board.js +0 -389
- package/dist/ui/layout/autocomplete.d.ts +0 -18
- package/dist/ui/layout/autocomplete.js +0 -26
- package/dist/ui/layout/budget.d.ts +0 -43
- package/dist/ui/layout/budget.js +0 -52
- package/dist/ui/layout/cells.d.ts +0 -61
- package/dist/ui/layout/cells.js +0 -156
- package/dist/ui/layout/document-layout.d.ts +0 -13
- package/dist/ui/layout/document-layout.js +0 -10
- package/dist/ui/layout/follow-state.d.ts +0 -138
- package/dist/ui/layout/follow-state.js +0 -148
- package/dist/ui/layout/frame.d.ts +0 -36
- package/dist/ui/layout/frame.js +0 -39
- package/dist/ui/layout/governance-board.d.ts +0 -121
- package/dist/ui/layout/governance-board.js +0 -382
- package/dist/ui/layout/governance-card.d.ts +0 -53
- package/dist/ui/layout/governance-card.js +0 -134
- package/dist/ui/layout/governance-chip.d.ts +0 -38
- package/dist/ui/layout/governance-chip.js +0 -64
- package/dist/ui/layout/governance-panel.d.ts +0 -85
- package/dist/ui/layout/governance-panel.js +0 -358
- package/dist/ui/layout/governance-roster.d.ts +0 -65
- package/dist/ui/layout/governance-roster.js +0 -275
- package/dist/ui/layout/header-hints.d.ts +0 -90
- package/dist/ui/layout/header-hints.js +0 -110
- package/dist/ui/layout/header-left.d.ts +0 -115
- package/dist/ui/layout/header-left.js +0 -206
- package/dist/ui/layout/invocation-card.d.ts +0 -41
- package/dist/ui/layout/invocation-card.js +0 -91
- package/dist/ui/layout/kit-chip.d.ts +0 -50
- package/dist/ui/layout/kit-chip.js +0 -81
- package/dist/ui/layout/overlay-window.d.ts +0 -43
- package/dist/ui/layout/overlay-window.js +0 -51
- package/dist/ui/layout/queue-layout.d.ts +0 -19
- package/dist/ui/layout/queue-layout.js +0 -26
- package/dist/ui/layout/rail.d.ts +0 -7
- package/dist/ui/layout/rail.js +0 -16
- package/dist/ui/layout/scroll-indicator.d.ts +0 -27
- package/dist/ui/layout/scroll-indicator.js +0 -36
- package/dist/ui/layout/scroll.d.ts +0 -37
- package/dist/ui/layout/scroll.js +0 -99
- package/dist/ui/layout/scrollbar-geometry.d.ts +0 -17
- package/dist/ui/layout/scrollbar-geometry.js +0 -13
- package/dist/ui/layout/session-chip.d.ts +0 -52
- package/dist/ui/layout/session-chip.js +0 -64
- package/dist/ui/layout/shortcut-panel-layout.d.ts +0 -191
- package/dist/ui/layout/shortcut-panel-layout.js +0 -321
- package/dist/ui/layout/status-detail-layout.d.ts +0 -9
- package/dist/ui/layout/status-detail-layout.js +0 -79
- package/dist/ui/layout/status-layout.d.ts +0 -139
- package/dist/ui/layout/status-layout.js +0 -253
- package/dist/ui/layout/task-summary-cell.d.ts +0 -61
- package/dist/ui/layout/task-summary-cell.js +0 -65
- package/dist/ui/layout/team-panel.d.ts +0 -18
- package/dist/ui/layout/team-panel.js +0 -25
- package/dist/ui/layout/team-row.d.ts +0 -139
- package/dist/ui/layout/team-row.js +0 -246
- package/dist/ui/layout/todo-layout.d.ts +0 -62
- package/dist/ui/layout/todo-layout.js +0 -102
- package/dist/ui/layout/useTerminalSize.d.ts +0 -13
- package/dist/ui/layout/useTerminalSize.js +0 -50
- package/dist/ui/layout/viewport-geometry.d.ts +0 -29
- package/dist/ui/layout/viewport-geometry.js +0 -28
- package/dist/ui/layout/virtual-window.d.ts +0 -112
- package/dist/ui/layout/virtual-window.js +0 -488
- package/dist/ui/layout/wrap-rows.d.ts +0 -26
- package/dist/ui/layout/wrap-rows.js +0 -88
- package/dist/ui/live-clamp.d.ts +0 -14
- package/dist/ui/live-clamp.js +0 -46
- package/dist/ui/markdown-blocks.d.ts +0 -107
- package/dist/ui/markdown-blocks.js +0 -240
- package/dist/ui/overlays/AgentsBoardOverlay.d.ts +0 -57
- package/dist/ui/overlays/AgentsBoardOverlay.js +0 -183
- package/dist/ui/overlays/ConfirmDialog.d.ts +0 -31
- package/dist/ui/overlays/ConfirmDialog.js +0 -44
- package/dist/ui/overlays/GovernanceBoardOverlay.d.ts +0 -41
- package/dist/ui/overlays/GovernanceBoardOverlay.js +0 -76
- package/dist/ui/overlays/GovernanceSettingsPage.d.ts +0 -48
- package/dist/ui/overlays/GovernanceSettingsPage.js +0 -93
- package/dist/ui/overlays/HelpOverlay.d.ts +0 -29
- package/dist/ui/overlays/HelpOverlay.js +0 -232
- package/dist/ui/overlays/KitEditor.d.ts +0 -23
- package/dist/ui/overlays/KitEditor.js +0 -101
- package/dist/ui/overlays/KitManager.d.ts +0 -23
- package/dist/ui/overlays/KitManager.js +0 -104
- package/dist/ui/overlays/KitPicker.d.ts +0 -49
- package/dist/ui/overlays/KitPicker.js +0 -58
- package/dist/ui/overlays/ModelDiscoveryList.d.ts +0 -23
- package/dist/ui/overlays/ModelDiscoveryList.js +0 -161
- package/dist/ui/overlays/ModelPicker.d.ts +0 -26
- package/dist/ui/overlays/ModelPicker.js +0 -63
- package/dist/ui/overlays/ModelProfileEditor.d.ts +0 -29
- package/dist/ui/overlays/ModelProfileEditor.js +0 -168
- package/dist/ui/overlays/ModelProfilePicker.d.ts +0 -48
- package/dist/ui/overlays/ModelProfilePicker.js +0 -231
- package/dist/ui/overlays/PlanReviewOverlay.d.ts +0 -51
- package/dist/ui/overlays/PlanReviewOverlay.js +0 -132
- package/dist/ui/overlays/PromptEditorPage.d.ts +0 -48
- package/dist/ui/overlays/PromptEditorPage.js +0 -227
- package/dist/ui/overlays/PromptLibraryOverlay.d.ts +0 -37
- package/dist/ui/overlays/PromptLibraryOverlay.js +0 -191
- package/dist/ui/overlays/PromptPicker.d.ts +0 -36
- package/dist/ui/overlays/PromptPicker.js +0 -129
- package/dist/ui/overlays/PromptTextArea.d.ts +0 -86
- package/dist/ui/overlays/PromptTextArea.js +0 -141
- package/dist/ui/overlays/QuestionOverlay.d.ts +0 -39
- package/dist/ui/overlays/QuestionOverlay.js +0 -258
- package/dist/ui/overlays/QueueOverlay.d.ts +0 -22
- package/dist/ui/overlays/QueueOverlay.js +0 -79
- package/dist/ui/overlays/SettingsScreen.d.ts +0 -228
- package/dist/ui/overlays/SettingsScreen.js +0 -1037
- package/dist/ui/overlays/ShortcutPanel.d.ts +0 -39
- package/dist/ui/overlays/ShortcutPanel.js +0 -106
- package/dist/ui/overlays/TeamFanoutPage.d.ts +0 -51
- package/dist/ui/overlays/TeamFanoutPage.js +0 -90
- package/dist/ui/overlays/governance-settings.d.ts +0 -79
- package/dist/ui/overlays/governance-settings.js +0 -140
- package/dist/ui/overlays/kit-picker-state.d.ts +0 -87
- package/dist/ui/overlays/kit-picker-state.js +0 -136
- package/dist/ui/overlays/kit-settings-state.d.ts +0 -129
- package/dist/ui/overlays/kit-settings-state.js +0 -268
- package/dist/ui/overlays/model-probe-view.d.ts +0 -128
- package/dist/ui/overlays/model-probe-view.js +0 -225
- package/dist/ui/overlays/model-profile-state.d.ts +0 -177
- package/dist/ui/overlays/model-profile-state.js +0 -301
- package/dist/ui/overlays/prompt-library-state.d.ts +0 -162
- package/dist/ui/overlays/prompt-library-state.js +0 -335
- package/dist/ui/overlays/prompt-picker-state.d.ts +0 -71
- package/dist/ui/overlays/prompt-picker-state.js +0 -98
- package/dist/ui/overlays/team-fanout-state.d.ts +0 -106
- package/dist/ui/overlays/team-fanout-state.js +0 -155
- package/dist/ui/overlays/use-model-probe.d.ts +0 -30
- package/dist/ui/overlays/use-model-probe.js +0 -109
- package/dist/ui/palettes.d.ts +0 -112
- package/dist/ui/palettes.js +0 -130
- package/dist/ui/paste-frames.d.ts +0 -91
- package/dist/ui/paste-frames.js +0 -117
- package/dist/ui/paste-tokens.d.ts +0 -90
- package/dist/ui/paste-tokens.js +0 -147
- package/dist/ui/render-cache.d.ts +0 -81
- package/dist/ui/render-cache.js +0 -196
- package/dist/ui/render-governor.d.ts +0 -54
- package/dist/ui/render-governor.js +0 -78
- package/dist/ui/run-metrics.d.ts +0 -23
- package/dist/ui/run-metrics.js +0 -27
- package/dist/ui/run-status-row.d.ts +0 -61
- package/dist/ui/run-status-row.js +0 -54
- package/dist/ui/screen.d.ts +0 -107
- package/dist/ui/screen.js +0 -234
- package/dist/ui/scrollback/LiveTail.d.ts +0 -115
- package/dist/ui/scrollback/LiveTail.js +0 -48
- package/dist/ui/scrollback/band.d.ts +0 -114
- package/dist/ui/scrollback/band.js +0 -143
- package/dist/ui/scrollback/budget.d.ts +0 -90
- package/dist/ui/scrollback/budget.js +0 -100
- package/dist/ui/scrollback/canvas.d.ts +0 -100
- package/dist/ui/scrollback/canvas.js +0 -114
- package/dist/ui/scrollback/cursor-probe.d.ts +0 -71
- package/dist/ui/scrollback/cursor-probe.js +0 -133
- package/dist/ui/scrollback/drain.d.ts +0 -32
- package/dist/ui/scrollback/drain.js +0 -64
- package/dist/ui/scrollback/exit-summary.d.ts +0 -23
- package/dist/ui/scrollback/exit-summary.js +0 -25
- package/dist/ui/scrollback/limits.d.ts +0 -98
- package/dist/ui/scrollback/limits.js +0 -98
- package/dist/ui/scrollback/live-preview.d.ts +0 -34
- package/dist/ui/scrollback/live-preview.js +0 -118
- package/dist/ui/scrollback/overflow-print.d.ts +0 -108
- package/dist/ui/scrollback/overflow-print.js +0 -120
- package/dist/ui/scrollback/renderer.d.ts +0 -48
- package/dist/ui/scrollback/renderer.js +0 -186
- package/dist/ui/scrollback/scrollback-writer.d.ts +0 -174
- package/dist/ui/scrollback/scrollback-writer.js +0 -648
- package/dist/ui/scrollback/settled-boundary.d.ts +0 -42
- package/dist/ui/scrollback/settled-boundary.js +0 -109
- package/dist/ui/scrollback/tail-anchor.d.ts +0 -71
- package/dist/ui/scrollback/tail-anchor.js +0 -79
- package/dist/ui/scrollback/tail-window.d.ts +0 -73
- package/dist/ui/scrollback/tail-window.js +0 -89
- package/dist/ui/scrollback/use-scrollback-view.d.ts +0 -139
- package/dist/ui/scrollback/use-scrollback-view.js +0 -601
- package/dist/ui/scrollback/writer-shapes.d.ts +0 -127
- package/dist/ui/scrollback/writer-shapes.js +0 -159
- package/dist/ui/scrollbar-controller.d.ts +0 -30
- package/dist/ui/scrollbar-controller.js +0 -124
- package/dist/ui/selection/highlight.d.ts +0 -39
- package/dist/ui/selection/highlight.js +0 -64
- package/dist/ui/selection/screen-mirror.d.ts +0 -24
- package/dist/ui/selection/screen-mirror.js +0 -35
- package/dist/ui/selection/selection-controller.d.ts +0 -104
- package/dist/ui/selection/selection-controller.js +0 -372
- package/dist/ui/selection/selection.d.ts +0 -90
- package/dist/ui/selection/selection.js +0 -223
- package/dist/ui/shortcuts.d.ts +0 -81
- package/dist/ui/shortcuts.js +0 -221
- package/dist/ui/status-feedback.d.ts +0 -35
- package/dist/ui/status-feedback.js +0 -38
- package/dist/ui/stdout-frame-writer.d.ts +0 -74
- package/dist/ui/stdout-frame-writer.js +0 -155
- package/dist/ui/team-status.d.ts +0 -166
- package/dist/ui/team-status.js +0 -252
- package/dist/ui/terminal-plan.d.ts +0 -52
- package/dist/ui/terminal-plan.js +0 -49
- package/dist/ui/theme.d.ts +0 -96
- package/dist/ui/theme.js +0 -130
- package/dist/ui/transcript-text.d.ts +0 -41
- package/dist/ui/transcript-text.js +0 -282
- package/dist/ui/use-height-store.d.ts +0 -31
- package/dist/ui/use-height-store.js +0 -76
- package/dist/ui/use-render-governor.d.ts +0 -39
- package/dist/ui/use-render-governor.js +0 -57
- package/dist/ui/use-wheel-routing.d.ts +0 -57
- package/dist/ui/use-wheel-routing.js +0 -152
- package/dist/ui/win-vt-force.d.ts +0 -136
- package/dist/ui/win-vt-force.js +0 -237
- package/dist/ui/win-vt-input.d.ts +0 -57
- package/dist/ui/win-vt-input.js +0 -62
- package/dist/update/limits.d.ts +0 -111
- package/dist/update/limits.js +0 -111
- package/dist/update/semver.d.ts +0 -89
- package/dist/update/semver.js +0 -295
- package/dist/update/types.d.ts +0 -169
- package/dist/update/types.js +0 -68
- /package/dist/{ui → app}/session-bridge.d.ts +0 -0
- /package/dist/{ui → app}/session-bridge.js +0 -0
package/dist/agent/reducer.js
DELETED
|
@@ -1,1617 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure event → view reducer (spec §3.4 / §6.2).
|
|
3
|
-
*
|
|
4
|
-
* `reduceEvent(event)` maps a raw core `AgentEvent` into zero or more view
|
|
5
|
-
* actions; `viewReducer(state, action)` folds an action into the immutable
|
|
6
|
-
* `ViewState`. Keeping this pure lets the whole UI be tested without a live LLM.
|
|
7
|
-
*
|
|
8
|
-
* Two load-bearing behaviors from R2 (silent-failure surfacing) live here:
|
|
9
|
-
* - the `error` StreamEvent is rendered as an error notice, and
|
|
10
|
-
* - `agent_end` after a turn that produced no `turn_end` and no error notice
|
|
11
|
-
* synthesizes a generic error notice (a swallowed throw is never a no-op).
|
|
12
|
-
*/
|
|
13
|
-
import process from 'node:process';
|
|
14
|
-
import { reconcileSteeringReceipt } from './queued-messages.js';
|
|
15
|
-
import { emptyContextUsage } from '../compaction/meter.js';
|
|
16
|
-
import { addUsage, computeCost } from './usage.js';
|
|
17
|
-
import { ENTRY_LIMITS, appendBounded, entryRetain, trimEntries } from './entry-limits.js';
|
|
18
|
-
import { isGovernedDispatch } from '../governance/governed-dispatch.js';
|
|
19
|
-
import { adoptPosture, foldGovernanceSnapshot } from './governance-view.js';
|
|
20
|
-
/** Default auto-dismiss window for a toast. */
|
|
21
|
-
export const DEFAULT_TOAST_TTL_MS = 2500;
|
|
22
|
-
/**
|
|
23
|
-
* The zero session total.
|
|
24
|
-
*
|
|
25
|
-
* ONE LITERAL FOR TWO SITES (`initialViewState` and `resetConversation`). They
|
|
26
|
-
* used to spell the object out twice, which is how `cacheReadTokens` would have
|
|
27
|
-
* been added to one of them and not the other.
|
|
28
|
-
*/
|
|
29
|
-
export const EMPTY_USAGE_TOTAL = {
|
|
30
|
-
inputTokens: 0,
|
|
31
|
-
outputTokens: 0,
|
|
32
|
-
cacheReadTokens: 0,
|
|
33
|
-
cacheWriteTokens: 0,
|
|
34
|
-
costUsd: 0,
|
|
35
|
-
};
|
|
36
|
-
/**
|
|
37
|
-
* The initial view state, optionally seeded from config.
|
|
38
|
-
*
|
|
39
|
-
* THE DEFAULT IS `false`, NOT `true`, AND THAT IS THE WHOLE SAFETY ARGUMENT
|
|
40
|
-
* (D-2). There is exactly one production call site — `App`'s `useReducer`, which
|
|
41
|
-
* passes `cfg.showThinking`. Every other caller is a test. A future call site
|
|
42
|
-
* that forgets the seed fails CLOSED, toward the product default, rather than
|
|
43
|
-
* silently restoring the reasoning firehose for one code path with nothing
|
|
44
|
-
* reporting it.
|
|
45
|
-
*
|
|
46
|
-
* The seed is a PARAMETER rather than a post-mount dispatch so that `/resume`
|
|
47
|
-
* cannot flash a restored thinking block on frame 1.
|
|
48
|
-
*/
|
|
49
|
-
export function initialViewState(seed = {}) {
|
|
50
|
-
return {
|
|
51
|
-
entries: [],
|
|
52
|
-
pendingSteering: [],
|
|
53
|
-
seenSteeringIds: new Set(),
|
|
54
|
-
runPhase: 'idle',
|
|
55
|
-
runOutcome: 'none',
|
|
56
|
-
status: 'idle',
|
|
57
|
-
usageTotal: EMPTY_USAGE_TOTAL,
|
|
58
|
-
context: emptyContextUsage(),
|
|
59
|
-
overlay: null,
|
|
60
|
-
thinkingVisible: seed.thinkingVisible ?? false,
|
|
61
|
-
toasts: [],
|
|
62
|
-
expandedToolIds: {},
|
|
63
|
-
// Seeded to the default posture; the App overrides it once on mount from
|
|
64
|
-
// `controller.getAgentMode()` so `--plan` renders on the first frame.
|
|
65
|
-
agentMode: 'build',
|
|
66
|
-
pendingAgentMode: null,
|
|
67
|
-
posture: 'build',
|
|
68
|
-
pendingPosture: null,
|
|
69
|
-
governance: null,
|
|
70
|
-
team: null,
|
|
71
|
-
todos: null,
|
|
72
|
-
retry: null,
|
|
73
|
-
fast: null,
|
|
74
|
-
compaction: null,
|
|
75
|
-
taskSummary: null,
|
|
76
|
-
taskSummaryPending: false,
|
|
77
|
-
seq: 0,
|
|
78
|
-
teamEntryId: undefined,
|
|
79
|
-
todoEntryId: undefined,
|
|
80
|
-
retryEntryId: undefined,
|
|
81
|
-
fastEntryId: undefined,
|
|
82
|
-
compactionEntryId: undefined,
|
|
83
|
-
streamingId: undefined,
|
|
84
|
-
turnProduced: false,
|
|
85
|
-
errorNoticed: false,
|
|
86
|
-
aborted: false,
|
|
87
|
-
droppedEntries: 0,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
// ---------------------------------------------------------------------------
|
|
91
|
-
// Event → actions
|
|
92
|
-
// ---------------------------------------------------------------------------
|
|
93
|
-
// Stored preview caps (spec §3.7 / P1-3). Raised from 16 / 800 so a `Ctrl+O`
|
|
94
|
-
// expansion can actually reveal lines 9…200 and the bash `[exit code N]` footer
|
|
95
|
-
// survives ≥16-line output. The *collapsed* view still shows only 8 lines
|
|
96
|
-
// (COLLAPSED_LINES in ToolCard).
|
|
97
|
-
export const STORED_PREVIEW_CHARS = 8000;
|
|
98
|
-
export const STORED_PREVIEW_LINES = 200;
|
|
99
|
-
/**
|
|
100
|
-
* Marker appended to a truncated tool preview. ASCII on purpose: this string is
|
|
101
|
-
* produced outside `src/ui/**`, where no terminal capabilities are available, so
|
|
102
|
-
* it must be safe on a legacy `cmd.exe` (spec §4.1 tier A). `ToolPreview` dims
|
|
103
|
-
* this line and imports the constant rather than re-spelling it — the two used
|
|
104
|
-
* to be a matched pair of `…` literals, and splitting the spelling would break
|
|
105
|
-
* the dimming silently.
|
|
106
|
-
*/
|
|
107
|
-
export const PREVIEW_TRUNCATION_MARK = '...';
|
|
108
|
-
/**
|
|
109
|
-
* Flatten a ToolResult into a stored preview string. Truncation appends
|
|
110
|
-
* `PREVIEW_TRUNCATION_MARK` (line- or char-based) so a card footer never
|
|
111
|
-
* over-promises how much more is expandable.
|
|
112
|
-
*/
|
|
113
|
-
export function buildToolPreview(result) {
|
|
114
|
-
const text = result.content
|
|
115
|
-
.map((c) => (c.type === 'text' ? c.text : `[image: ${c.mediaType}]`))
|
|
116
|
-
.join('\n');
|
|
117
|
-
const allLines = text.split('\n');
|
|
118
|
-
const lines = allLines.slice(0, STORED_PREVIEW_LINES);
|
|
119
|
-
let preview = lines.join('\n');
|
|
120
|
-
const lineTruncated = allLines.length > STORED_PREVIEW_LINES;
|
|
121
|
-
if (preview.length > STORED_PREVIEW_CHARS) {
|
|
122
|
-
preview = `${preview.slice(0, STORED_PREVIEW_CHARS)}${PREVIEW_TRUNCATION_MARK}`;
|
|
123
|
-
}
|
|
124
|
-
else if (lineTruncated) {
|
|
125
|
-
preview = `${preview}\n${PREVIEW_TRUNCATION_MARK}`;
|
|
126
|
-
}
|
|
127
|
-
return preview;
|
|
128
|
-
}
|
|
129
|
-
/** The first line of a stored preview, without its trailing newline. */
|
|
130
|
-
function firstLine(text) {
|
|
131
|
-
const at = text.indexOf('\n');
|
|
132
|
-
return at === -1 ? text : text.slice(0, at);
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Map one core `AgentEvent` to view actions. `cost` (the active model's cost
|
|
136
|
-
* table) lets `turn_end` accumulate USD cost; omit it in tests that only assert
|
|
137
|
-
* token totals. `patches` is the CLI-local diff side channel (§3.3.7); omitting
|
|
138
|
-
* it — every existing test, and headless mode — produces byte-identical actions.
|
|
139
|
-
*/
|
|
140
|
-
export function reduceEvent(event, cost, patches) {
|
|
141
|
-
switch (event.type) {
|
|
142
|
-
case 'steering_accepted':
|
|
143
|
-
return [{ type: 'steeringAccepted', ids: event.ids }];
|
|
144
|
-
case 'agent_start':
|
|
145
|
-
return [{ type: 'runStart' }];
|
|
146
|
-
case 'turn_start':
|
|
147
|
-
return [{ type: 'turnStart' }];
|
|
148
|
-
case 'turn_end':
|
|
149
|
-
return [
|
|
150
|
-
{
|
|
151
|
-
type: 'turnEnd',
|
|
152
|
-
usage: event.usage,
|
|
153
|
-
costDelta: computeCost(event.usage, cost),
|
|
154
|
-
},
|
|
155
|
-
];
|
|
156
|
-
case 'agent_end':
|
|
157
|
-
return [{ type: 'runEnd' }];
|
|
158
|
-
case 'tool_execution_start':
|
|
159
|
-
return [{ type: 'toolExecStart', toolCallId: event.toolCallId, toolName: event.toolName }];
|
|
160
|
-
case 'tool_execution_end': {
|
|
161
|
-
const patch = patches?.take(event.toolCallId);
|
|
162
|
-
const preview = buildToolPreview(event.result);
|
|
163
|
-
return [
|
|
164
|
-
{
|
|
165
|
-
type: 'toolExecEnd',
|
|
166
|
-
toolCallId: event.toolCallId,
|
|
167
|
-
isError: event.isError,
|
|
168
|
-
duration: event.duration,
|
|
169
|
-
// AN ENTRY CARRIES A PATCH OR A FULL PREVIEW, NEVER BOTH (D-19). This
|
|
170
|
-
// is not a budget compromise, it is deleting a duplicate: `ToolCard`
|
|
171
|
-
// renders `DiffView` INSTEAD of `ToolPreview` when a patch exists, so
|
|
172
|
-
// the stored preview is never drawn on that card — and for `edit_file`
|
|
173
|
-
// it is literally the same diff, as text. Keeping the first line
|
|
174
|
-
// preserves the one thing outside `DiffView` that still reads it,
|
|
175
|
-
// `entryRevision`'s `preview?.length` term.
|
|
176
|
-
preview: patch ? firstLine(preview) : preview,
|
|
177
|
-
...(patch ? { patch } : {}),
|
|
178
|
-
},
|
|
179
|
-
];
|
|
180
|
-
}
|
|
181
|
-
case 'message_update':
|
|
182
|
-
return reduceStreamEvent(event.streamEvent);
|
|
183
|
-
case 'code_execution_start':
|
|
184
|
-
case 'code_execution_end':
|
|
185
|
-
// CodeAct sandbox is off by default; no dedicated view surface in v1.
|
|
186
|
-
return [];
|
|
187
|
-
default:
|
|
188
|
-
return [];
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Tools that render their own transcript entry, so the generic tool card would
|
|
193
|
-
* be a duplicate. `task` is the second member (F-8 A'3): every dispatch opens
|
|
194
|
-
* a TeamCard immediately after, so the pair read as TWO dispatches - the first
|
|
195
|
-
* failed, the second succeeded - when there was only ever one. `todo_write`'s
|
|
196
|
-
* reasons (todo-plan-execution §3.8 / D-10) carry over unchanged.
|
|
197
|
-
*
|
|
198
|
-
* SUPPRESSING ONLY `tool_call_start` IS SUFFICIENT, AND THAT IS THE WHOLE REASON
|
|
199
|
-
* THIS IS SAFE (D-10). Every later handler — `toolCallDelta` / `toolCallEnd` /
|
|
200
|
-
* `toolExecStart` / `toolExecEnd` — resolves its target through
|
|
201
|
-
* `findToolEntryId`, which returns `undefined` when no entry was created, and
|
|
202
|
-
* every one of them already early-returns on that. There is no fifth handler.
|
|
203
|
-
* Blast radius verified: headless and `exec` writers key on EVENTS, not tool
|
|
204
|
-
* names, and `transcript-text` renders `kind:'team'` on its own.
|
|
205
|
-
*/
|
|
206
|
-
const SELF_RENDERING_TOOLS = new Set(['todo_write', 'task']);
|
|
207
|
-
function reduceStreamEvent(se) {
|
|
208
|
-
switch (se.type) {
|
|
209
|
-
case 'thinking_start':
|
|
210
|
-
return [{ type: 'thinkingStart' }];
|
|
211
|
-
case 'thinking_delta':
|
|
212
|
-
return [{ type: 'thinkingDelta', delta: se.delta }];
|
|
213
|
-
case 'text_delta':
|
|
214
|
-
return [{ type: 'textDelta', delta: se.delta }];
|
|
215
|
-
case 'tool_call_start':
|
|
216
|
-
return [
|
|
217
|
-
{ type: 'toolCallStart', toolCallId: se.toolCallId, toolName: se.toolName },
|
|
218
|
-
];
|
|
219
|
-
case 'tool_call_delta':
|
|
220
|
-
return [
|
|
221
|
-
{ type: 'toolCallDelta', toolCallId: se.toolCallId, argsDelta: se.argsDelta },
|
|
222
|
-
];
|
|
223
|
-
case 'tool_call_end':
|
|
224
|
-
return [{ type: 'toolCallEnd', toolCallId: se.toolCallId, args: se.args }];
|
|
225
|
-
case 'error':
|
|
226
|
-
return [
|
|
227
|
-
{ type: 'runError' },
|
|
228
|
-
{
|
|
229
|
-
type: 'notice',
|
|
230
|
-
level: 'error',
|
|
231
|
-
text: formatStreamError(se.error),
|
|
232
|
-
},
|
|
233
|
-
];
|
|
234
|
-
case 'done':
|
|
235
|
-
// Authoritative usage is redundant with `turn_end`; nothing to add.
|
|
236
|
-
return [];
|
|
237
|
-
// --- API retry (llm-api-retry-backoff §6.4) ---------------------------
|
|
238
|
-
case 'retry_scheduled':
|
|
239
|
-
return [
|
|
240
|
-
{
|
|
241
|
-
type: 'retryScheduled',
|
|
242
|
-
attempt: se.attempt,
|
|
243
|
-
maxRetries: se.maxRetries,
|
|
244
|
-
delayMs: se.delayMs,
|
|
245
|
-
resumeAt: se.resumeAt,
|
|
246
|
-
errorType: se.errorType,
|
|
247
|
-
message: se.message,
|
|
248
|
-
},
|
|
249
|
-
];
|
|
250
|
-
case 'retry_attempt':
|
|
251
|
-
return [{ type: 'retryAttempt', attempt: se.attempt, maxRetries: se.maxRetries }];
|
|
252
|
-
case 'stream_restart':
|
|
253
|
-
return [{ type: 'streamRestart', discardedToolCallIds: se.discardedToolCallIds }];
|
|
254
|
-
default:
|
|
255
|
-
return [];
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
/**
|
|
259
|
-
* The opening words of the `context_overflow` banner.
|
|
260
|
-
*
|
|
261
|
-
* A SHARED CONSTANT RATHER THAN TWO LITERALS, because `compactionStart` matches
|
|
262
|
-
* on it to rewrite the notice a successful reactive recovery would otherwise
|
|
263
|
-
* leave behind (P1-4). Two spellings that must agree, with nothing checking that
|
|
264
|
-
* they do, is how that rewrite silently stops matching — at which point the red
|
|
265
|
-
* "start over" banner comes back and only on the recovery path, which is the one
|
|
266
|
-
* place nobody re-tests.
|
|
267
|
-
*/
|
|
268
|
-
export const CONTEXT_OVERFLOW_NOTICE_PREFIX = 'Context length exceeded';
|
|
269
|
-
/** Turn a provider error into a friendly banner, mapping LLMError.errorType. */
|
|
270
|
-
export function formatStreamError(error) {
|
|
271
|
-
const errorType = error.errorType;
|
|
272
|
-
const base = error.message || 'Unknown error';
|
|
273
|
-
switch (errorType) {
|
|
274
|
-
case 'auth_error':
|
|
275
|
-
return `Authentication failed - check your API key. (${base})`;
|
|
276
|
-
case 'rate_limit':
|
|
277
|
-
return `Rate limited by the provider - please retry shortly. (${base})`;
|
|
278
|
-
case 'overloaded':
|
|
279
|
-
return `Provider overloaded - please retry shortly. (${base})`;
|
|
280
|
-
case 'context_overflow':
|
|
281
|
-
// `/compact` IS NAMED FIRST, and adding it is the second half of P1-4's
|
|
282
|
-
// fix. This remedy list predates context compaction and was, until it
|
|
283
|
-
// existed, complete: throwing the conversation away really was the only
|
|
284
|
-
// exit. It is not any more, and a banner that offers only `/reset` now
|
|
285
|
-
// tells the user to discard a session the very next line of code may be
|
|
286
|
-
// about to save.
|
|
287
|
-
return `${CONTEXT_OVERFLOW_NOTICE_PREFIX} - try /compact, or start over with /reset. (${base})`;
|
|
288
|
-
case 'network_error':
|
|
289
|
-
return `Network error reaching the provider. (${base})`;
|
|
290
|
-
case 'timeout':
|
|
291
|
-
return `The request timed out. (${base})`;
|
|
292
|
-
default:
|
|
293
|
-
return base;
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
// ---------------------------------------------------------------------------
|
|
297
|
-
// Reducer
|
|
298
|
-
// ---------------------------------------------------------------------------
|
|
299
|
-
const GENERIC_RUN_FAILURE = 'The run ended without producing a response - check your API key, model, and network.';
|
|
300
|
-
function nextId(state) {
|
|
301
|
-
const seq = state.seq + 1;
|
|
302
|
-
return { id: `e${seq}`, seq };
|
|
303
|
-
}
|
|
304
|
-
function mapEntry(entries, id, fn) {
|
|
305
|
-
return entries.map((e) => (e.id === id ? fn(e) : e));
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Seal `thinkingMs`, once, as a spreadable patch (§3.1.2).
|
|
309
|
-
*
|
|
310
|
-
* Returns an EMPTY object when there is nothing to seal — no reasoning happened,
|
|
311
|
-
* or the clock has already stopped — so the three call sites can spread it
|
|
312
|
-
* unconditionally and none of them can re-seal a settled duration.
|
|
313
|
-
*/
|
|
314
|
-
function sealThinkingMs(entry) {
|
|
315
|
-
if (entry.thinkingStartedAt === undefined || entry.thinkingMs !== undefined)
|
|
316
|
-
return {};
|
|
317
|
-
return { thinkingMs: Math.max(0, Date.now() - entry.thinkingStartedAt) };
|
|
318
|
-
}
|
|
319
|
-
/**
|
|
320
|
-
* Append one entry and ring-trim the result (tui-render-performance L1).
|
|
321
|
-
*
|
|
322
|
-
* TRIMMING HAPPENS ONLY AT APPEND POINTS, and that is the whole reason this is a
|
|
323
|
-
* helper rather than a line in `viewReducer`. A mid-turn trim triggered by an
|
|
324
|
-
* UPDATE action could drop the entry `streamingId` / `teamEntryId` /
|
|
325
|
-
* `todoEntryId` points at while it is still being written to; appending is the
|
|
326
|
-
* only moment at which the tail is by definition the newest thing.
|
|
327
|
-
*
|
|
328
|
-
* I-L1-1 — the trim must never drop a live target. With `retain >= 200` and a
|
|
329
|
-
* trim that only ever removes from the head this is structurally true, but a
|
|
330
|
-
* future clamp change would break it SILENTLY (the `mapEntry` calls would simply
|
|
331
|
-
* stop matching), so development builds assert it.
|
|
332
|
-
*/
|
|
333
|
-
function appendEntry(state, entries, entry) {
|
|
334
|
-
const trimmed = trimEntries([...entries, entry], entryRetain());
|
|
335
|
-
if (trimmed.dropped > 0 && process.env.NODE_ENV !== 'production') {
|
|
336
|
-
// `retryEntryId` IS PART OF THIS LIST. A fourth live card that is not in it is
|
|
337
|
-
// a fourth card I-L1-1 does not cover — the trim would silently turn its
|
|
338
|
-
// `mapEntry` into a no-op instead of failing loudly.
|
|
339
|
-
const live = [
|
|
340
|
-
state.streamingId,
|
|
341
|
-
state.teamEntryId,
|
|
342
|
-
state.todoEntryId,
|
|
343
|
-
state.retryEntryId,
|
|
344
|
-
// `fastEntryId` IS PART OF THIS LIST for the reason the note above gives:
|
|
345
|
-
// a fifth live card left out of it is a card I-L1-1 does not cover.
|
|
346
|
-
state.fastEntryId,
|
|
347
|
-
// And `compactionEntryId` is the sixth. Same reason, and it is the one
|
|
348
|
-
// whose absence would be hardest to notice: a compaction card is rare, so
|
|
349
|
-
// a `mapEntry` that silently stopped matching would be found by a user
|
|
350
|
-
// rather than by a test.
|
|
351
|
-
state.compactionEntryId,
|
|
352
|
-
].filter((id) => typeof id === 'string');
|
|
353
|
-
for (const id of live) {
|
|
354
|
-
if (!trimmed.entries.some((e) => e.id === id)) {
|
|
355
|
-
throw new Error(`trimEntries dropped a live entry (${id}) - see I-L1-1`);
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
return {
|
|
360
|
-
entries: trimmed.entries,
|
|
361
|
-
droppedEntries: state.droppedEntries + trimmed.dropped,
|
|
362
|
-
};
|
|
363
|
-
}
|
|
364
|
-
/**
|
|
365
|
-
* Settle the turn's retry card, if it has one (llm-api-retry-backoff §6.4).
|
|
366
|
-
*
|
|
367
|
-
* SETTLING IS DERIVED HERE, NOT SENT BY CORE. Core knows it scheduled a retry; it
|
|
368
|
-
* does not know whether the turn eventually succeeded, was interrupted, or ran out
|
|
369
|
-
* of budget — that is the consumer's own timeline.
|
|
370
|
-
*
|
|
371
|
-
* FOUR PATHS REACH THIS, and the fourth is what makes the set exhaustive rather
|
|
372
|
-
* than enumerated: `turnEnd` (recovered), an error `notice` (exhausted),
|
|
373
|
-
* `abortMark` (interrupted) and `runEnd` (interrupted). The rule is "every path
|
|
374
|
-
* that reaches `status: 'idle'` settles the card", because a card left at
|
|
375
|
-
* `phase: 'waiting'` pins `Transcript`'s MONOTONIC settled boundary and re-renders
|
|
376
|
-
* the tail every frame for the rest of the session (R-10).
|
|
377
|
-
*
|
|
378
|
-
* Returning `retry: null` / `retryEntryId: undefined` unconditionally is
|
|
379
|
-
* deliberate: both are already that when there is no card, so every caller can
|
|
380
|
-
* spread the result without a branch of its own.
|
|
381
|
-
*/
|
|
382
|
-
function settleRetryCard(state, entries, phase) {
|
|
383
|
-
if (!state.retryEntryId)
|
|
384
|
-
return { entries, retry: null, retryEntryId: undefined };
|
|
385
|
-
const now = Date.now();
|
|
386
|
-
return {
|
|
387
|
-
entries: mapEntry(entries, state.retryEntryId, (e) => e.kind === 'retry'
|
|
388
|
-
? {
|
|
389
|
-
...e,
|
|
390
|
-
phase,
|
|
391
|
-
totalRetries: e.attempt,
|
|
392
|
-
elapsedMs: Math.max(0, now - e.startedAt),
|
|
393
|
-
resumeAt: undefined,
|
|
394
|
-
}
|
|
395
|
-
: e),
|
|
396
|
-
retry: null,
|
|
397
|
-
retryEntryId: undefined,
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
function releaseLiveTails(entries) {
|
|
401
|
-
let touched = false;
|
|
402
|
-
const next = entries.map((e) => {
|
|
403
|
-
if (e.kind !== 'tool' || !e.live)
|
|
404
|
-
return e;
|
|
405
|
-
if (e.status !== 'running' && e.status !== 'pending')
|
|
406
|
-
return e;
|
|
407
|
-
touched = true;
|
|
408
|
-
return { ...e, live: undefined, lastOutputAt: undefined, liveSeq: (e.liveSeq ?? 0) + 1 };
|
|
409
|
-
});
|
|
410
|
-
return touched ? next : entries;
|
|
411
|
-
}
|
|
412
|
-
/**
|
|
413
|
-
* Drop settled assistant entries that render nothing at all (defect 5).
|
|
414
|
-
*
|
|
415
|
-
* `turnStart` ALWAYS creates an entry, so a turn that only called a tool leaves a
|
|
416
|
-
* bubble behind that `AssistantEntry` then refuses to draw. A rendered absence is
|
|
417
|
-
* not the same thing as an absent entry: `virtual-window.ts:305` still estimates a
|
|
418
|
-
* row for it (two when `aborted`, and nothing draws the second), and `/save`
|
|
419
|
-
* exports a blank bubble. Settle is the only safe moment to prune — before it the
|
|
420
|
-
* entry is still the target of every `textDelta` / `thinkingDelta`.
|
|
421
|
-
*
|
|
422
|
-
* I-L1-1 — `streamingId` COMES BACK WITH THE ARRAY. If the dropped entry is the
|
|
423
|
-
* one that id pointed at, the id is cleared: leaving it set would point the whole
|
|
424
|
-
* reducer at an entry no `mapEntry` can ever match again, silently, because a
|
|
425
|
-
* `mapEntry` on a missing id is a no-op. `turnEnd` and `runEnd` clear the id
|
|
426
|
-
* themselves; `abortMark` does not, which is why the caller writes this back.
|
|
427
|
-
*
|
|
428
|
-
* Returns the SAME array when nothing was dropped, so every settled entry keeps
|
|
429
|
-
* its identity and `EntryView`'s memo boundary stays intact.
|
|
430
|
-
*/
|
|
431
|
-
function pruneEmptyAssistant(entries, streamingId) {
|
|
432
|
-
const isEmptySettledAssistant = (entry) => entry.kind === 'assistant' &&
|
|
433
|
-
!entry.streaming &&
|
|
434
|
-
entry.text.trim() === '' &&
|
|
435
|
-
(entry.thinking ?? '').trim() === '';
|
|
436
|
-
const kept = entries.filter((entry) => !isEmptySettledAssistant(entry));
|
|
437
|
-
if (kept.length === entries.length)
|
|
438
|
-
return { entries, streamingId };
|
|
439
|
-
const liveGone = streamingId !== undefined && !kept.some((entry) => entry.id === streamingId);
|
|
440
|
-
return { entries: kept, streamingId: liveGone ? undefined : streamingId };
|
|
441
|
-
}
|
|
442
|
-
function findToolEntryId(state, toolCallId) {
|
|
443
|
-
for (let i = state.entries.length - 1; i >= 0; i -= 1) {
|
|
444
|
-
const e = state.entries[i];
|
|
445
|
-
if (e && e.kind === 'tool' && e.toolCallId === toolCallId)
|
|
446
|
-
return e.id;
|
|
447
|
-
}
|
|
448
|
-
return undefined;
|
|
449
|
-
}
|
|
450
|
-
/** Runtime facts outlive transcript cards and are independent of command/clipboard notices. */
|
|
451
|
-
function reduceRunFacts(state, action) {
|
|
452
|
-
switch (action.type) {
|
|
453
|
-
case 'runStarting': return { ...state, runPhase: 'starting', runOutcome: 'none' };
|
|
454
|
-
case 'runStart': return { ...state, runPhase: 'waiting', runOutcome: 'none', activeTool: undefined };
|
|
455
|
-
case 'turnStart': return { ...state, runPhase: 'waiting' };
|
|
456
|
-
case 'thinkingStart':
|
|
457
|
-
case 'thinkingDelta': return { ...state, runPhase: 'thinking' };
|
|
458
|
-
case 'textDelta': return { ...state, runPhase: 'generating' };
|
|
459
|
-
case 'toolCallStart':
|
|
460
|
-
case 'toolCallDelta':
|
|
461
|
-
case 'toolCallEnd': return { ...state, runPhase: 'preparing-tool' };
|
|
462
|
-
case 'toolExecStart': {
|
|
463
|
-
const card = state.entries.find((entry) => entry.kind === 'tool' && entry.toolCallId === action.toolCallId);
|
|
464
|
-
return { ...state, runPhase: 'tool', activeTool: {
|
|
465
|
-
toolCallId: action.toolCallId,
|
|
466
|
-
name: action.toolName ?? (card?.kind === 'tool' ? card.name : 'tool'),
|
|
467
|
-
} };
|
|
468
|
-
}
|
|
469
|
-
case 'toolExecEnd': return state.activeTool?.toolCallId === action.toolCallId
|
|
470
|
-
? { ...state, runPhase: 'waiting', activeTool: undefined } : state;
|
|
471
|
-
case 'runError': return { ...state, runOutcome: 'failed' };
|
|
472
|
-
case 'abortMark': return { ...state, runOutcome: 'interrupted' };
|
|
473
|
-
case 'runEnd': return { ...state, runPhase: 'idle', activeTool: undefined,
|
|
474
|
-
runOutcome: state.runOutcome !== 'none' ? state.runOutcome
|
|
475
|
-
: !state.turnProduced && !state.errorNoticed && !state.aborted ? 'failed' : 'ended' };
|
|
476
|
-
case 'resetConversation':
|
|
477
|
-
case 'restoreEntries': return { ...state, runPhase: 'idle', activeTool: undefined,
|
|
478
|
-
runOutcome: 'none', seenSteeringIds: new Set() };
|
|
479
|
-
default: return state;
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
export function viewReducer(state, action) {
|
|
483
|
-
state = reduceRunFacts(state, action);
|
|
484
|
-
switch (action.type) {
|
|
485
|
-
case 'submit': {
|
|
486
|
-
const { id, seq } = nextId(state);
|
|
487
|
-
const entry = {
|
|
488
|
-
id, kind: 'user', text: action.text,
|
|
489
|
-
...(action.invocation ? { invocation: action.invocation } : {}),
|
|
490
|
-
};
|
|
491
|
-
return {
|
|
492
|
-
...state,
|
|
493
|
-
seq,
|
|
494
|
-
...appendEntry(state, state.entries, entry),
|
|
495
|
-
streamingId: undefined,
|
|
496
|
-
// A new turn gets its own card, in that turn's position in the
|
|
497
|
-
// transcript. The PREVIOUS card stays exactly where it is — it is
|
|
498
|
-
// history, and history is not retracted.
|
|
499
|
-
todoEntryId: undefined,
|
|
500
|
-
// Same rule for the retry card. `runStart` clears NONE of the three (it
|
|
501
|
-
// never has), and `runEnd`'s settle rule below is what makes that safe
|
|
502
|
-
// rather than merely lucky.
|
|
503
|
-
retryEntryId: undefined,
|
|
504
|
-
turnProduced: false,
|
|
505
|
-
errorNoticed: false,
|
|
506
|
-
aborted: false,
|
|
507
|
-
};
|
|
508
|
-
}
|
|
509
|
-
case 'steerQueued': {
|
|
510
|
-
if (state.seenSteeringIds.has(action.queueId))
|
|
511
|
-
return state;
|
|
512
|
-
const { id, seq } = nextId(state);
|
|
513
|
-
const entry = {
|
|
514
|
-
id, kind: 'queued', queueId: action.queueId, text: action.text,
|
|
515
|
-
...(action.invocation ? { invocation: action.invocation } : {}),
|
|
516
|
-
};
|
|
517
|
-
// NOTHING ELSE RESETS. This is not a new turn -- `turnProduced`,
|
|
518
|
-
// `errorNoticed` and `aborted` describe the run that is still in
|
|
519
|
-
// flight, and clearing them here would let the next `runEnd` failure
|
|
520
|
-
// guard misfire on a turn that produced plenty.
|
|
521
|
-
return {
|
|
522
|
-
...state, seq, ...appendEntry(state, state.entries, entry),
|
|
523
|
-
seenSteeringIds: new Set([...state.seenSteeringIds, action.queueId]),
|
|
524
|
-
pendingSteering: [...state.pendingSteering, {
|
|
525
|
-
queueId: action.queueId, text: action.text,
|
|
526
|
-
...(action.invocation ? { invocation: action.invocation } : {}),
|
|
527
|
-
}],
|
|
528
|
-
};
|
|
529
|
-
}
|
|
530
|
-
case 'steeringAccepted': {
|
|
531
|
-
const receipt = reconcileSteeringReceipt({
|
|
532
|
-
entries: state.entries, pending: state.pendingSteering, ids: action.ids,
|
|
533
|
-
});
|
|
534
|
-
if (!receipt.changed)
|
|
535
|
-
return state;
|
|
536
|
-
const trimmed = trimEntries(receipt.entries, entryRetain());
|
|
537
|
-
return { ...state, pendingSteering: receipt.pending, entries: trimmed.entries,
|
|
538
|
-
droppedEntries: state.droppedEntries + trimmed.dropped };
|
|
539
|
-
}
|
|
540
|
-
case 'runStart':
|
|
541
|
-
return {
|
|
542
|
-
...state,
|
|
543
|
-
status: 'running',
|
|
544
|
-
turnProduced: false,
|
|
545
|
-
errorNoticed: false,
|
|
546
|
-
aborted: false,
|
|
547
|
-
};
|
|
548
|
-
case 'turnStart': {
|
|
549
|
-
const { id, seq } = nextId(state);
|
|
550
|
-
const entry = {
|
|
551
|
-
id,
|
|
552
|
-
kind: 'assistant',
|
|
553
|
-
text: '',
|
|
554
|
-
thinking: undefined,
|
|
555
|
-
thinkingOpen: false,
|
|
556
|
-
streaming: true,
|
|
557
|
-
};
|
|
558
|
-
return { ...state, seq, ...appendEntry(state, state.entries, entry), streamingId: id };
|
|
559
|
-
}
|
|
560
|
-
case 'thinkingStart': {
|
|
561
|
-
if (!state.streamingId)
|
|
562
|
-
return state;
|
|
563
|
-
return {
|
|
564
|
-
...state,
|
|
565
|
-
entries: mapEntry(state.entries, state.streamingId, (e) => e.kind === 'assistant'
|
|
566
|
-
? {
|
|
567
|
-
...e,
|
|
568
|
-
thinking: e.thinking ?? '',
|
|
569
|
-
thinkingOpen: true,
|
|
570
|
-
// `Date.now()` in the reducer is not a new impurity class: the
|
|
571
|
-
// retry card's `startedAt` already reads the clock here.
|
|
572
|
-
thinkingStartedAt: e.thinkingStartedAt ?? Date.now(),
|
|
573
|
-
}
|
|
574
|
-
: e),
|
|
575
|
-
};
|
|
576
|
-
}
|
|
577
|
-
case 'thinkingDelta': {
|
|
578
|
-
if (!state.streamingId)
|
|
579
|
-
return state;
|
|
580
|
-
return {
|
|
581
|
-
...state,
|
|
582
|
-
entries: mapEntry(state.entries, state.streamingId, (e) => e.kind === 'assistant'
|
|
583
|
-
? { ...e, thinking: appendBounded(e.thinking ?? '', action.delta, ENTRY_LIMITS.thinking) }
|
|
584
|
-
: e),
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
case 'textDelta': {
|
|
588
|
-
if (!state.streamingId)
|
|
589
|
-
return state;
|
|
590
|
-
return {
|
|
591
|
-
...state,
|
|
592
|
-
entries: mapEntry(state.entries, state.streamingId, (e) => e.kind === 'assistant'
|
|
593
|
-
? {
|
|
594
|
-
...e,
|
|
595
|
-
text: appendBounded(e.text, action.delta, ENTRY_LIMITS.text),
|
|
596
|
-
// FIRST TEXT DELTA ONLY. Thinking is over the moment the answer
|
|
597
|
-
// starts; re-sealing on every delta would make the number climb
|
|
598
|
-
// with the answer's length.
|
|
599
|
-
...sealThinkingMs(e),
|
|
600
|
-
}
|
|
601
|
-
: e),
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
case 'toolCallStart': {
|
|
605
|
-
if (SELF_RENDERING_TOOLS.has(action.toolName))
|
|
606
|
-
return state;
|
|
607
|
-
const { id, seq } = nextId(state);
|
|
608
|
-
const entry = {
|
|
609
|
-
id,
|
|
610
|
-
kind: 'tool',
|
|
611
|
-
toolCallId: action.toolCallId,
|
|
612
|
-
name: action.toolName,
|
|
613
|
-
label: action.label ?? action.toolName,
|
|
614
|
-
argsRaw: '',
|
|
615
|
-
status: 'pending',
|
|
616
|
-
};
|
|
617
|
-
return { ...state, seq, ...appendEntry(state, state.entries, entry) };
|
|
618
|
-
}
|
|
619
|
-
case 'toolCallDelta': {
|
|
620
|
-
const id = findToolEntryId(state, action.toolCallId);
|
|
621
|
-
if (!id)
|
|
622
|
-
return state;
|
|
623
|
-
return {
|
|
624
|
-
...state,
|
|
625
|
-
entries: mapEntry(state.entries, id, (e) => e.kind === 'tool'
|
|
626
|
-
? { ...e, argsRaw: appendBounded(e.argsRaw, action.argsDelta, ENTRY_LIMITS.argsRaw) }
|
|
627
|
-
: e),
|
|
628
|
-
};
|
|
629
|
-
}
|
|
630
|
-
case 'toolCallEnd': {
|
|
631
|
-
const id = findToolEntryId(state, action.toolCallId);
|
|
632
|
-
if (!id)
|
|
633
|
-
return state;
|
|
634
|
-
return {
|
|
635
|
-
...state,
|
|
636
|
-
entries: mapEntry(state.entries, id, (e) => e.kind === 'tool'
|
|
637
|
-
? { ...e, args: action.args, argsRaw: safeStringify(action.args) }
|
|
638
|
-
: e),
|
|
639
|
-
};
|
|
640
|
-
}
|
|
641
|
-
case 'toolExecStart': {
|
|
642
|
-
const id = findToolEntryId(state, action.toolCallId);
|
|
643
|
-
if (!id)
|
|
644
|
-
return state;
|
|
645
|
-
return {
|
|
646
|
-
...state,
|
|
647
|
-
entries: mapEntry(state.entries, id, (e) => e.kind === 'tool' ? { ...e, status: 'running' } : e),
|
|
648
|
-
};
|
|
649
|
-
}
|
|
650
|
-
/**
|
|
651
|
-
* THE STATUS GUARD IS WHAT KEEPS A LATE CHUNK FROM RESURRECTING A SETTLED
|
|
652
|
-
* CARD. The child's last write and its `close` event race, so a chunk can
|
|
653
|
-
* legitimately arrive after `tool_execution_end`; without the guard it would
|
|
654
|
-
* reattach a tail to a card whose authoritative preview is already drawn.
|
|
655
|
-
*/
|
|
656
|
-
case 'toolOutputDelta': {
|
|
657
|
-
const id = findToolEntryId(state, action.toolCallId);
|
|
658
|
-
if (!id)
|
|
659
|
-
return state;
|
|
660
|
-
return {
|
|
661
|
-
...state,
|
|
662
|
-
entries: mapEntry(state.entries, id, (e) => e.kind === 'tool' && (e.status === 'running' || e.status === 'pending')
|
|
663
|
-
? {
|
|
664
|
-
...e,
|
|
665
|
-
live: action.rows,
|
|
666
|
-
liveSeq: (e.liveSeq ?? 0) + 1,
|
|
667
|
-
lastOutputAt: action.at,
|
|
668
|
-
}
|
|
669
|
-
: e),
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
case 'toolExecEnd': {
|
|
673
|
-
const id = findToolEntryId(state, action.toolCallId);
|
|
674
|
-
if (!id)
|
|
675
|
-
return state;
|
|
676
|
-
return {
|
|
677
|
-
...state,
|
|
678
|
-
entries: mapEntry(state.entries, id, (e) => e.kind === 'tool'
|
|
679
|
-
? {
|
|
680
|
-
...e,
|
|
681
|
-
status: action.isError ? 'error' : 'done',
|
|
682
|
-
isError: action.isError,
|
|
683
|
-
durationMs: action.duration,
|
|
684
|
-
preview: action.preview,
|
|
685
|
-
...(action.patch ? { patch: action.patch } : {}),
|
|
686
|
-
// THE TAIL IS NEVER MERGED INTO `preview` (D-23): the
|
|
687
|
-
// authoritative result already contains everything it held, in
|
|
688
|
-
// full, and concatenating them would double the output and break
|
|
689
|
-
// `bashBadge`'s last-line parse (`ToolPreview.tsx:35-54`).
|
|
690
|
-
live: undefined,
|
|
691
|
-
lastOutputAt: undefined,
|
|
692
|
-
}
|
|
693
|
-
: e),
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
case 'turnEnd': {
|
|
697
|
-
const entries = state.streamingId
|
|
698
|
-
? mapEntry(state.entries, state.streamingId, (e) => e.kind === 'assistant'
|
|
699
|
-
? {
|
|
700
|
-
...e,
|
|
701
|
-
streaming: false,
|
|
702
|
-
thinkingOpen: false,
|
|
703
|
-
usage: action.usage,
|
|
704
|
-
// The turn that thought and then called a tool without emitting
|
|
705
|
-
// any text never saw a `textDelta`, so this is where its clock
|
|
706
|
-
// is sealed.
|
|
707
|
-
...sealThinkingMs(e),
|
|
708
|
-
}
|
|
709
|
-
: e)
|
|
710
|
-
: state.entries;
|
|
711
|
-
return {
|
|
712
|
-
...state,
|
|
713
|
-
streamingId: undefined,
|
|
714
|
-
turnProduced: true,
|
|
715
|
-
// `contextTokens` IS NOT WRITTEN HERE ANY MORE (I-1). The occupancy this
|
|
716
|
-
// turn produced reaches the view through `ContextMeter`, which subscribes
|
|
717
|
-
// to the same `turn_end` and publishes the measured pressure - window,
|
|
718
|
-
// percentage and approximation markers included, none of which this
|
|
719
|
-
// branch ever had. Two branches writing one number is what P0-1 was.
|
|
720
|
-
usageTotal: addUsage(state.usageTotal, action.usage, action.costDelta),
|
|
721
|
-
// The turn produced an answer, so whatever it had to retry, it recovered.
|
|
722
|
-
// Defect 5: prune AFTER the mapEntry above has settled the entry, so an
|
|
723
|
-
// assistant bubble this turn never filled in is gone before the retry card
|
|
724
|
-
// is settled and before the height table ever sees it.
|
|
725
|
-
...settleRetryCard(state, pruneEmptyAssistant(entries, state.streamingId).entries, 'recovered'),
|
|
726
|
-
};
|
|
727
|
-
}
|
|
728
|
-
case 'runEnd': {
|
|
729
|
-
// Finalize any still-streaming entry.
|
|
730
|
-
let entries = state.streamingId
|
|
731
|
-
? mapEntry(state.entries, state.streamingId, (e) => e.kind === 'assistant' ? { ...e, streaming: false, ...sealThinkingMs(e) } : e)
|
|
732
|
-
: state.entries;
|
|
733
|
-
/*
|
|
734
|
-
* Defect 5, and BEFORE the silent-failure guard below so the guard and the
|
|
735
|
-
* retry settle both see the pruned array. `streamingId: undefined` is set in
|
|
736
|
-
* the returned state either way, so the returned id is not needed here.
|
|
737
|
-
*/
|
|
738
|
-
entries = pruneEmptyAssistant(entries, state.streamingId).entries;
|
|
739
|
-
let seq = state.seq;
|
|
740
|
-
let droppedEntries = state.droppedEntries;
|
|
741
|
-
// Silent-failure guard (R2): a swallowed throw shows nothing otherwise.
|
|
742
|
-
// A user abort is an expected empty run, not a failure — skip the guard.
|
|
743
|
-
if (!state.turnProduced && !state.errorNoticed && !state.aborted) {
|
|
744
|
-
const next = nextId({ ...state, seq });
|
|
745
|
-
seq = next.seq;
|
|
746
|
-
const appended = appendEntry(state, entries, {
|
|
747
|
-
id: next.id,
|
|
748
|
-
kind: 'notice',
|
|
749
|
-
level: 'error',
|
|
750
|
-
text: GENERIC_RUN_FAILURE,
|
|
751
|
-
});
|
|
752
|
-
entries = appended.entries;
|
|
753
|
-
droppedEntries = appended.droppedEntries;
|
|
754
|
-
}
|
|
755
|
-
// The card may now reach `<Static>` (C-5 / I-6): the turn that owns it is
|
|
756
|
-
// over, so nothing will rewrite it.
|
|
757
|
-
if (state.todoEntryId) {
|
|
758
|
-
entries = mapEntry(entries, state.todoEntryId, (e) => e.kind === 'todo' ? { ...e, live: false } : e);
|
|
759
|
-
}
|
|
760
|
-
return {
|
|
761
|
-
...state,
|
|
762
|
-
seq,
|
|
763
|
-
droppedEntries,
|
|
764
|
-
status: 'idle',
|
|
765
|
-
streamingId: undefined,
|
|
766
|
-
// THE TERMINAL BACKSTOP, and it is not redundant with `abortMark`
|
|
767
|
-
// (llm-api-retry-backoff P1-8 / AC-25b). `abortMark` is dispatched only
|
|
768
|
-
// from the Esc handler, but the ENGINE also aborts itself: the idle
|
|
769
|
-
// watchdog calls `Agent.abort()` directly, the run unwinds, and the UI
|
|
770
|
-
// sees `agent_end` -> `runEnd` with no `abortMark`, no `turn_end` and no
|
|
771
|
-
// error notice. A card left at `phase: 'waiting'` then pins
|
|
772
|
-
// `Transcript`'s monotonic boundary and re-renders the tail every frame
|
|
773
|
-
// for the rest of the session. By the time this runs the three other
|
|
774
|
-
// settle paths have already cleared `retryEntryId`, so this only fires on
|
|
775
|
-
// a genuinely unsettled card.
|
|
776
|
-
//
|
|
777
|
-
// `releaseLiveTails` rides the same argument one entry kind over (D-35):
|
|
778
|
-
// a tool card stranded at `running` by this very path would otherwise
|
|
779
|
-
// keep a tail and a frozen stall clock for the rest of the session.
|
|
780
|
-
...settleRetryCard(state, releaseLiveTails(entries), 'interrupted'),
|
|
781
|
-
};
|
|
782
|
-
}
|
|
783
|
-
case 'notice': {
|
|
784
|
-
const { id, seq } = nextId(state);
|
|
785
|
-
const entry = { id, kind: 'notice', level: action.level, text: action.text };
|
|
786
|
-
const appended = appendEntry(state, state.entries, entry);
|
|
787
|
-
// A terminal error notice is what an EXHAUSTED ladder looks like from here:
|
|
788
|
-
// the retry wrapper forwards the provider's own error once it stops
|
|
789
|
-
// retrying, and that error always precedes any `turn_end` because a failed
|
|
790
|
-
// turn has none. A non-error notice leaves the card alone.
|
|
791
|
-
const settled = action.level === 'error'
|
|
792
|
-
? settleRetryCard(state, appended.entries, 'exhausted')
|
|
793
|
-
: { entries: appended.entries };
|
|
794
|
-
return {
|
|
795
|
-
...state,
|
|
796
|
-
seq,
|
|
797
|
-
droppedEntries: appended.droppedEntries,
|
|
798
|
-
errorNoticed: action.level === 'error' ? true : state.errorNoticed,
|
|
799
|
-
...settled,
|
|
800
|
-
};
|
|
801
|
-
}
|
|
802
|
-
case 'abortMark': {
|
|
803
|
-
// Mark the run aborted even when no assistant entry is streaming (e.g. an
|
|
804
|
-
// abort during a tool call) so the runEnd failure guard stays suppressed.
|
|
805
|
-
const marked = state.streamingId
|
|
806
|
-
? mapEntry(state.entries, state.streamingId, (e) => e.kind === 'assistant' ? { ...e, streaming: false, aborted: true } : e)
|
|
807
|
-
: state.entries;
|
|
808
|
-
// Defect 5 / I-L1-1: prune AFTER the entry is settled, and write the
|
|
809
|
-
// returned id back below -- `abortMark` is the one settle path that does
|
|
810
|
-
// NOT clear `streamingId`, so a prune without that write-back would leave
|
|
811
|
-
// it naming an entry this branch has just removed.
|
|
812
|
-
const pruned = pruneEmptyAssistant(marked, state.streamingId);
|
|
813
|
-
return {
|
|
814
|
-
...state,
|
|
815
|
-
aborted: true,
|
|
816
|
-
streamingId: pruned.streamingId,
|
|
817
|
-
// An abort during a tool call is the COMMON way a tail is stranded --
|
|
818
|
-
// `Esc` while `npm test` runs -- so this is the clause manual row 9
|
|
819
|
-
// exercises (D-35 / AC-40).
|
|
820
|
-
...settleRetryCard(state, releaseLiveTails(pruned.entries), 'interrupted'),
|
|
821
|
-
};
|
|
822
|
-
}
|
|
823
|
-
// `teamEntryId` is dropped with the entries it pointed at. Keeping it would
|
|
824
|
-
// leave `teamUpdate` mapping an id that no longer exists — harmless today,
|
|
825
|
-
// and a dangling reference the next reader has to reason about.
|
|
826
|
-
case 'clearTranscript':
|
|
827
|
-
return {
|
|
828
|
-
...state,
|
|
829
|
-
entries: [],
|
|
830
|
-
// The count describes what the RETAIN RING removed from the visible
|
|
831
|
-
// transcript. `/clear screen` empties it by intent, so carrying the number
|
|
832
|
-
// over would make the exported transcript claim a loss that did not
|
|
833
|
-
// happen.
|
|
834
|
-
droppedEntries: 0,
|
|
835
|
-
streamingId: undefined,
|
|
836
|
-
expandedToolIds: {},
|
|
837
|
-
teamEntryId: undefined,
|
|
838
|
-
// The list goes with the screen it was on. `/clear screen` is an explicit
|
|
839
|
-
// user instruction, which is I-2's standing exception (`TodoClearReason`
|
|
840
|
-
// `'user'`, the same door `/todo clear` uses) - so the command clears the
|
|
841
|
-
// STORE via `controller.clearTodos()` and this clears the MIRROR, exactly
|
|
842
|
-
// the pairing `resetConversation` below has always had.
|
|
843
|
-
//
|
|
844
|
-
// The mirror write is same-valued with the store's own `todoCleared`
|
|
845
|
-
// dispatch rather than redundant with it: it is what makes this action
|
|
846
|
-
// stand on its own. `commands/clear-command.ts` is its only dispatcher
|
|
847
|
-
// TODAY - `/clear` itself now goes through `resetConversation`, because
|
|
848
|
-
// clearing the SCREEN and clearing the SESSION are different intentions
|
|
849
|
-
// (clear-command-context-reset D-3) - and a hypothetical second one would
|
|
850
|
-
// otherwise bring the bug back in identical form and in silence.
|
|
851
|
-
//
|
|
852
|
-
// KEPT RATHER THAN FOLDED INTO `resetConversation` for the same reason:
|
|
853
|
-
// a user who wants the screen back must not have to destroy their context
|
|
854
|
-
// to get it, and an action with no dispatcher is dead code by `CLAUDE.md`
|
|
855
|
-
// section 4 - which is what `/clear screen` prevents.
|
|
856
|
-
//
|
|
857
|
-
// The entry id is dropped because the entry it pointed at is gone.
|
|
858
|
-
todos: null,
|
|
859
|
-
todoEntryId: undefined,
|
|
860
|
-
// Same reasoning for the retry card: the entry is gone, so the id must go
|
|
861
|
-
// with it or `retryScheduled` would `mapEntry` an id that no longer
|
|
862
|
-
// exists. `retry` goes too — unlike `todos` it is not a belief the model
|
|
863
|
-
// holds, it is a projection of an entry that has just been erased.
|
|
864
|
-
retryEntryId: undefined,
|
|
865
|
-
retry: null,
|
|
866
|
-
// Same reasoning again for the compaction card: the entry is gone, so the
|
|
867
|
-
// id must go with it or `compactionEnd` would `mapEntry` an id that no
|
|
868
|
-
// longer exists. `compaction` STAYS — unlike `retry` it is not a
|
|
869
|
-
// projection of an entry, it is the live state of a subsystem that is
|
|
870
|
-
// still running. `/clear screen` clears the screen, not the session.
|
|
871
|
-
compactionEntryId: undefined,
|
|
872
|
-
};
|
|
873
|
-
case 'resetUsage':
|
|
874
|
-
// The one field, for the one reason: a new id bills from zero (D-10).
|
|
875
|
-
return { ...state, usageTotal: EMPTY_USAGE_TOTAL };
|
|
876
|
-
case 'resetConversation':
|
|
877
|
-
return {
|
|
878
|
-
...state,
|
|
879
|
-
pendingSteering: [],
|
|
880
|
-
entries: [],
|
|
881
|
-
droppedEntries: 0,
|
|
882
|
-
streamingId: undefined,
|
|
883
|
-
expandedToolIds: {},
|
|
884
|
-
// `context` IS DELIBERATELY UNTOUCHED (I-1). `clear-command.ts` calls
|
|
885
|
-
// `controller.resetConversation()` BEFORE dispatching this, and that marks
|
|
886
|
-
// the meter dirty and schedules a publication; zeroing here would make the
|
|
887
|
-
// final state `0` rather than the meter's real answer - which for an
|
|
888
|
-
// emptied conversation is the system prompt's own couple of percent, not
|
|
889
|
-
// nothing. `usageTotal` DOES reset: that is session spend, not occupancy.
|
|
890
|
-
usageTotal: EMPTY_USAGE_TOTAL,
|
|
891
|
-
teamEntryId: undefined,
|
|
892
|
-
// `/clear` clears `messages`, so the belief is gone and the list goes
|
|
893
|
-
// with it - the other half of I-2. `controller.clearMessages()` clears
|
|
894
|
-
// the STORE; this clears the mirror.
|
|
895
|
-
todos: null,
|
|
896
|
-
todoEntryId: undefined,
|
|
897
|
-
retryEntryId: undefined,
|
|
898
|
-
retry: null,
|
|
899
|
-
compactionEntryId: undefined,
|
|
900
|
-
// THE SIX RUN FACTS THIS BRANCH USED TO KEEP (clear-command-context-reset
|
|
901
|
-
// §3.6 / P0-3). `reduceRunFacts` above already zeroes `runPhase`,
|
|
902
|
-
// `activeTool`, `runOutcome` and `seenSteeringIds`, so nothing here
|
|
903
|
-
// repeats those four - these six are the actual gap, and each one
|
|
904
|
-
// otherwise describes a run that no longer exists.
|
|
905
|
-
//
|
|
906
|
-
// `status` is the status bar: a late `runEnd` would correct it a moment
|
|
907
|
-
// later, and until then the user is told a conversation that is over is
|
|
908
|
-
// still running. `team` is a dispatch panel with nothing dispatching.
|
|
909
|
-
// `fastEntryId` points at a `kind: 'fast'` card that `entries: []` just
|
|
910
|
-
// deleted, the same dangling-id rule `retryEntryId` and
|
|
911
|
-
// `compactionEntryId` above already follow.
|
|
912
|
-
//
|
|
913
|
-
// `turnProduced` / `errorNoticed` / `aborted` are ALSO cleared by
|
|
914
|
-
// `submit`, which is not a reason to leave them out: clearing is an
|
|
915
|
-
// independent entry point, so a user who clears and then only reads the
|
|
916
|
-
// panel would carry the previous turn's three facts into a conversation
|
|
917
|
-
// they never submitted into - and `runEnd`'s settle rule reads exactly
|
|
918
|
-
// those three.
|
|
919
|
-
status: 'idle',
|
|
920
|
-
team: null,
|
|
921
|
-
// The live run snapshot goes with the conversation it was shown in (the run itself
|
|
922
|
-
// survives on disk and announces itself again on its next event).
|
|
923
|
-
governance: null,
|
|
924
|
-
fastEntryId: undefined,
|
|
925
|
-
turnProduced: false,
|
|
926
|
-
errorNoticed: false,
|
|
927
|
-
aborted: false,
|
|
928
|
-
// THE TASK LABEL IS PART OF THE TASK (D-13). `/clear`, `/reset` and a
|
|
929
|
-
// session switch all land here, so the header loses its label without the
|
|
930
|
-
// App needing a second cleanup path. `/clear screen` does NOT come here -
|
|
931
|
-
// it keeps the conversation, so it keeps the label.
|
|
932
|
-
taskSummary: null,
|
|
933
|
-
taskSummaryPending: false,
|
|
934
|
-
};
|
|
935
|
-
case 'setOverlay':
|
|
936
|
-
return { ...state, overlay: action.overlay };
|
|
937
|
-
// --- Task summary (tui-shortcut-drawer-and-task-summary §7.1) ----------
|
|
938
|
-
//
|
|
939
|
-
// TWO FIELDS, ONE DIRECTION: `pending` is a latch the App raises before the
|
|
940
|
-
// call and releases on the result, and `taskSummary` is only ever replaced
|
|
941
|
-
// wholesale. Neither is persisted and neither is derived from `entries`, so a
|
|
942
|
-
// resumed conversation shows nothing until the App rebuilds the label.
|
|
943
|
-
case 'taskSummaryPending':
|
|
944
|
-
return { ...state, taskSummaryPending: true };
|
|
945
|
-
case 'taskSummaryReady':
|
|
946
|
-
return { ...state, taskSummary: action.view, taskSummaryPending: false };
|
|
947
|
-
// Pure mirror write. The App only ever dispatches what
|
|
948
|
-
// `controller.setAgentMode()` reported it ADOPTED, so this reducer has no
|
|
949
|
-
// deferral logic of its own to get out of step with (§3.1 / R-P7).
|
|
950
|
-
case 'setAgentMode':
|
|
951
|
-
return {
|
|
952
|
-
...state,
|
|
953
|
-
agentMode: action.mode,
|
|
954
|
-
pendingAgentMode: action.pending,
|
|
955
|
-
...adoptPosture(state.posture, action),
|
|
956
|
-
};
|
|
957
|
-
// --- Governance (governance-mode 14.4) -------------------------------------
|
|
958
|
-
case 'govSnapshot': {
|
|
959
|
-
const governance = foldGovernanceSnapshot(state.governance, action.snapshot);
|
|
960
|
-
return governance === state.governance ? state : { ...state, governance };
|
|
961
|
-
}
|
|
962
|
-
case 'govEnd':
|
|
963
|
-
return state.governance === null ? state : { ...state, governance: null };
|
|
964
|
-
case 'governanceCard': {
|
|
965
|
-
const { id, seq } = nextId(state);
|
|
966
|
-
const entry = { id, kind: 'governance', at: action.at ?? Date.now(), card: action.card };
|
|
967
|
-
return { ...state, seq, ...appendEntry(state, state.entries, entry) };
|
|
968
|
-
}
|
|
969
|
-
case 'toggleThinking':
|
|
970
|
-
return { ...state, thinkingVisible: !state.thinkingVisible };
|
|
971
|
-
case 'pushToast': {
|
|
972
|
-
const { seq } = nextId(state);
|
|
973
|
-
const toast = {
|
|
974
|
-
id: `t${seq}`,
|
|
975
|
-
level: action.level,
|
|
976
|
-
text: action.text,
|
|
977
|
-
ttlMs: action.ttlMs ?? DEFAULT_TOAST_TTL_MS,
|
|
978
|
-
};
|
|
979
|
-
return { ...state, seq, toasts: [...state.toasts, toast] };
|
|
980
|
-
}
|
|
981
|
-
case 'dismissToast':
|
|
982
|
-
return { ...state, toasts: state.toasts.filter((t) => t.id !== action.id) };
|
|
983
|
-
case 'toggleExpand': {
|
|
984
|
-
const next = { ...state.expandedToolIds };
|
|
985
|
-
if (next[action.id])
|
|
986
|
-
delete next[action.id];
|
|
987
|
-
else
|
|
988
|
-
next[action.id] = true;
|
|
989
|
-
return { ...state, expandedToolIds: next };
|
|
990
|
-
}
|
|
991
|
-
// --- Team mode (team-subagents §5.3) ---------------------------------
|
|
992
|
-
case 'teamStart': {
|
|
993
|
-
const { id, seq } = nextId(state);
|
|
994
|
-
const runs = action.specs.map((spec) => ({
|
|
995
|
-
label: spec.label,
|
|
996
|
-
description: spec.description,
|
|
997
|
-
phase: 'queued',
|
|
998
|
-
turns: 0,
|
|
999
|
-
toolCalls: 0,
|
|
1000
|
-
usage: { inputTokens: 0, outputTokens: 0 },
|
|
1001
|
-
filesTouched: [],
|
|
1002
|
-
messagesSent: 0,
|
|
1003
|
-
}));
|
|
1004
|
-
const entry = {
|
|
1005
|
-
id,
|
|
1006
|
-
kind: 'team',
|
|
1007
|
-
dispatchId: action.dispatchId,
|
|
1008
|
-
requested: action.requested,
|
|
1009
|
-
runs,
|
|
1010
|
-
aborted: false,
|
|
1011
|
-
active: true,
|
|
1012
|
-
};
|
|
1013
|
-
const team = {
|
|
1014
|
-
dispatchId: action.dispatchId,
|
|
1015
|
-
active: true,
|
|
1016
|
-
runs,
|
|
1017
|
-
requested: action.requested,
|
|
1018
|
-
startedAt: Date.now(),
|
|
1019
|
-
messageCount: 0,
|
|
1020
|
-
};
|
|
1021
|
-
// A GOVERNED DISPATCH LEAVES NO TRANSCRIPT CARD (RV-3): a run is 10-20 dispatches,
|
|
1022
|
-
// and a `TeamCard` for each would bury the conversation. The live snapshot is still
|
|
1023
|
-
// written - the legion panel and `/agents` read it - and with no `teamEntryId`,
|
|
1024
|
-
// `teamUpdate` / `teamEnd` already pass `entries` through untouched.
|
|
1025
|
-
if (isGovernedDispatch(action.specs))
|
|
1026
|
-
return { ...state, team };
|
|
1027
|
-
return {
|
|
1028
|
-
...state,
|
|
1029
|
-
seq,
|
|
1030
|
-
...appendEntry(state, state.entries, entry),
|
|
1031
|
-
teamEntryId: id,
|
|
1032
|
-
team,
|
|
1033
|
-
};
|
|
1034
|
-
}
|
|
1035
|
-
case 'teamUpdate': {
|
|
1036
|
-
// One `mapEntry` on the KNOWN id, never a scan: this action arrives at up
|
|
1037
|
-
// to `maxConcurrent` x 8/s and a reverse search over a long transcript on
|
|
1038
|
-
// every one of them is exactly the cost the coalescer exists to avoid.
|
|
1039
|
-
const entries = state.teamEntryId
|
|
1040
|
-
? mapEntry(state.entries, state.teamEntryId, (e) => e.kind === 'team' ? { ...e, runs: action.snapshot.runs } : e)
|
|
1041
|
-
: state.entries;
|
|
1042
|
-
return { ...state, entries, team: action.snapshot };
|
|
1043
|
-
}
|
|
1044
|
-
case 'teamUsage':
|
|
1045
|
-
// `usageTotal` AND NOTHING ELSE (§3.9 / AC-15). In particular NOT
|
|
1046
|
-
// `context`: that gauge shows the LEAD's context occupancy against
|
|
1047
|
-
// the model's window, and folding five children into it would read 180%
|
|
1048
|
-
// on a perfectly healthy session. Child spend is real money and has to
|
|
1049
|
-
// appear in the cost readout; it is not the lead's context.
|
|
1050
|
-
return { ...state, usageTotal: addUsage(state.usageTotal, action.usage, action.costDelta) };
|
|
1051
|
-
case 'teamEnd': {
|
|
1052
|
-
const { outcome } = action;
|
|
1053
|
-
const entries = state.teamEntryId
|
|
1054
|
-
? mapEntry(state.entries, state.teamEntryId, (e) => e.kind === 'team'
|
|
1055
|
-
? {
|
|
1056
|
-
...e,
|
|
1057
|
-
runs: outcome.runs,
|
|
1058
|
-
aborted: outcome.aborted,
|
|
1059
|
-
durationMs: Math.max(0, outcome.endedAt - outcome.startedAt),
|
|
1060
|
-
active: false,
|
|
1061
|
-
}
|
|
1062
|
-
: e)
|
|
1063
|
-
: state.entries;
|
|
1064
|
-
// `team: null` is what makes the panel disappear — the requirement's
|
|
1065
|
-
// "(dang you de shi hou)": it renders only while a dispatch is running.
|
|
1066
|
-
return { ...state, entries, team: null, teamEntryId: undefined };
|
|
1067
|
-
}
|
|
1068
|
-
// --- Todo planning (todo-plan-execution §5.2) -------------------------
|
|
1069
|
-
case 'todoUpdate': {
|
|
1070
|
-
// TYPED AS THE TODO MEMBER, NOT AS `Entry`. The rewrite below spreads this
|
|
1071
|
-
// and overrides `live: boolean`; while the return type was the whole union
|
|
1072
|
-
// that spread also produced a TOOL entry carrying `live: boolean`, which
|
|
1073
|
-
// stopped type-checking the moment the tool member gained its own `live`
|
|
1074
|
-
// (agent-activity-presentation-live). Narrowing here is what keeps the
|
|
1075
|
-
// override honest rather than accidentally legal.
|
|
1076
|
-
const entry = (id) => ({
|
|
1077
|
-
id,
|
|
1078
|
-
kind: 'todo',
|
|
1079
|
-
items: action.snapshot.items,
|
|
1080
|
-
doneCount: action.snapshot.doneCount,
|
|
1081
|
-
total: action.snapshot.total,
|
|
1082
|
-
acceptance: action.snapshot.acceptance,
|
|
1083
|
-
live: true,
|
|
1084
|
-
});
|
|
1085
|
-
// Rewrite the turn's card when there is one; otherwise append and record
|
|
1086
|
-
// its id. One `mapEntry` on the KNOWN id, never a scan.
|
|
1087
|
-
if (state.todoEntryId) {
|
|
1088
|
-
const known = state.todoEntryId;
|
|
1089
|
-
return {
|
|
1090
|
-
...state,
|
|
1091
|
-
todos: action.snapshot,
|
|
1092
|
-
entries: mapEntry(state.entries, known, (e) => e.kind === 'todo' ? { ...entry(known), live: e.live } : e),
|
|
1093
|
-
};
|
|
1094
|
-
}
|
|
1095
|
-
const { id, seq } = nextId(state);
|
|
1096
|
-
return {
|
|
1097
|
-
...state,
|
|
1098
|
-
seq,
|
|
1099
|
-
todos: action.snapshot,
|
|
1100
|
-
...appendEntry(state, state.entries, entry(id)),
|
|
1101
|
-
todoEntryId: id,
|
|
1102
|
-
};
|
|
1103
|
-
}
|
|
1104
|
-
// THE ENTRY STAYS. It is history, and history is not retracted — only the
|
|
1105
|
-
// live projection goes away, which is what unmounts the rail.
|
|
1106
|
-
case 'todoCleared':
|
|
1107
|
-
return { ...state, todos: null, todoEntryId: undefined };
|
|
1108
|
-
// --- Fast model tier (fast-model-tier §5.4) ---------------------------
|
|
1109
|
-
case 'fastStart': {
|
|
1110
|
-
const { id, seq } = nextId(state);
|
|
1111
|
-
const entry = {
|
|
1112
|
-
id,
|
|
1113
|
-
kind: 'fast',
|
|
1114
|
-
reviewIndex: action.index,
|
|
1115
|
-
model: action.model,
|
|
1116
|
-
status: 'running',
|
|
1117
|
-
turn: action.turn,
|
|
1118
|
-
live: true,
|
|
1119
|
-
};
|
|
1120
|
-
return {
|
|
1121
|
-
...state,
|
|
1122
|
-
seq,
|
|
1123
|
-
...appendEntry(state, state.entries, entry),
|
|
1124
|
-
fastEntryId: id,
|
|
1125
|
-
};
|
|
1126
|
-
}
|
|
1127
|
-
case 'fastEnd': {
|
|
1128
|
-
const { review } = action;
|
|
1129
|
-
const settle = (e) => e.kind === 'fast'
|
|
1130
|
-
? {
|
|
1131
|
-
...e,
|
|
1132
|
-
status: review.kind,
|
|
1133
|
-
...(review.text !== undefined ? { text: review.text } : {}),
|
|
1134
|
-
...(review.detail !== undefined ? { detail: review.detail } : {}),
|
|
1135
|
-
durationMs: review.durationMs,
|
|
1136
|
-
// SETTLED. `Transcript`'s boundary is monotonic, so a card that
|
|
1137
|
-
// never settles is re-rendered on every frame for the rest of the
|
|
1138
|
-
// session (C-5).
|
|
1139
|
-
live: false,
|
|
1140
|
-
}
|
|
1141
|
-
: e;
|
|
1142
|
-
// Rewrite the card this review opened when there is one; otherwise append
|
|
1143
|
-
// a settled card. THE SECOND BRANCH IS NOT DEFENSIVE PADDING: a review
|
|
1144
|
-
// that is dropped as `stranded` is emitted from `agent_end` and never had
|
|
1145
|
-
// a `review_start`, and a run restored from `/resume` has no live id at
|
|
1146
|
-
// all — "it did nothing" must never be the observable outcome (R-8).
|
|
1147
|
-
if (state.fastEntryId) {
|
|
1148
|
-
return {
|
|
1149
|
-
...state,
|
|
1150
|
-
entries: mapEntry(state.entries, state.fastEntryId, settle),
|
|
1151
|
-
fastEntryId: undefined,
|
|
1152
|
-
};
|
|
1153
|
-
}
|
|
1154
|
-
const { id, seq } = nextId(state);
|
|
1155
|
-
const entry = {
|
|
1156
|
-
id,
|
|
1157
|
-
kind: 'fast',
|
|
1158
|
-
reviewIndex: review.index,
|
|
1159
|
-
model: review.model,
|
|
1160
|
-
status: review.kind,
|
|
1161
|
-
...(review.text !== undefined ? { text: review.text } : {}),
|
|
1162
|
-
...(review.detail !== undefined ? { detail: review.detail } : {}),
|
|
1163
|
-
turn: review.turn,
|
|
1164
|
-
durationMs: review.durationMs,
|
|
1165
|
-
live: false,
|
|
1166
|
-
};
|
|
1167
|
-
return { ...state, seq, ...appendEntry(state, state.entries, entry) };
|
|
1168
|
-
}
|
|
1169
|
-
case 'fastUsage':
|
|
1170
|
-
// `usageTotal` AND NOTHING ELSE, exactly as `teamUsage` (§3.6 / AC-18). In
|
|
1171
|
-
// particular NOT `context`: that gauge shows the LEAD's context
|
|
1172
|
-
// occupancy against the lead model's window, and a review is a separate
|
|
1173
|
-
// conversation with a different model entirely.
|
|
1174
|
-
return { ...state, usageTotal: addUsage(state.usageTotal, action.usage, action.costDelta) };
|
|
1175
|
-
case 'fastTier':
|
|
1176
|
-
return { ...state, fast: action.snapshot };
|
|
1177
|
-
// --- Context compaction (context-auto-compaction §5.5) ----------------
|
|
1178
|
-
// THE TRANSCRIPT NOW CARRIES ONE CARD PER ATTEMPT (context-compaction-never-
|
|
1179
|
-
// fires C). Before this change a declined `pressure` checkpoint dispatched
|
|
1180
|
-
// NOTHING, which is what made "95 % and nothing happens" unreportable: the
|
|
1181
|
-
// feature was trying, failing and saying nothing. The flood the old rule
|
|
1182
|
-
// feared is bounded by `maxPerRun` / `stuckLimit` in `compaction/limits.ts`.
|
|
1183
|
-
case 'compactionStart': {
|
|
1184
|
-
const { id, seq } = nextId(state);
|
|
1185
|
-
let entries = state.entries;
|
|
1186
|
-
// RESPONSIBILITY 1 — SEAL ANY LIVE `streamingId` ENTRY, defensively.
|
|
1187
|
-
// C-8's failure mode, and reachable here: `provider.ts` records that
|
|
1188
|
-
// Anthropic can deliver an error IN-STREAM, after the connection is open
|
|
1189
|
-
// and therefore after text deltas have already been emitted. An entry that
|
|
1190
|
-
// never settles pins `Transcript`'s monotonic boundary and re-renders the
|
|
1191
|
-
// tail forever.
|
|
1192
|
-
if (state.streamingId) {
|
|
1193
|
-
entries = mapEntry(entries, state.streamingId, (e) => e.kind === 'assistant' ? { ...e, streaming: false, ...sealThinkingMs(e) } : e);
|
|
1194
|
-
}
|
|
1195
|
-
// RESPONSIBILITY 2 — REWRITE A TRAILING `context_overflow` ERROR NOTICE
|
|
1196
|
-
// (P1-4). `agent-loop.ts` emits `message_update` for EVERY stream event
|
|
1197
|
-
// before the `error` branch throws, and `reduceStreamEvent`'s `case 'error'`
|
|
1198
|
-
// turns that into a `level: 'error'` notice reading "Context length
|
|
1199
|
-
// exceeded". So the HAPPY PATH of the reactive recovery would otherwise end
|
|
1200
|
-
// with a red banner telling the user to throw away the session we are one
|
|
1201
|
-
// frame from saving.
|
|
1202
|
-
//
|
|
1203
|
-
// IT REWRITES RATHER THAN REMOVES, because `<Static>` cannot un-print an
|
|
1204
|
-
// entry it has already drawn — and it matches ONLY when that notice is the
|
|
1205
|
-
// TAIL entry, so it can never touch an older, genuine error. If the
|
|
1206
|
-
// recovery then fails, the ladder's own notice or the re-thrown 400 is what
|
|
1207
|
-
// the user is left with, so neither direction lies.
|
|
1208
|
-
if (action.trigger === 'overflow') {
|
|
1209
|
-
const tail = entries[entries.length - 1];
|
|
1210
|
-
if (tail &&
|
|
1211
|
-
tail.kind === 'notice' &&
|
|
1212
|
-
tail.level === 'error' &&
|
|
1213
|
-
tail.text.startsWith(CONTEXT_OVERFLOW_NOTICE_PREFIX)) {
|
|
1214
|
-
entries = mapEntry(entries, tail.id, (e) => e.kind === 'notice'
|
|
1215
|
-
? { ...e, level: 'info', text: 'Context window exceeded - compacting and retrying.' }
|
|
1216
|
-
: e);
|
|
1217
|
-
}
|
|
1218
|
-
}
|
|
1219
|
-
const entry = {
|
|
1220
|
-
id,
|
|
1221
|
-
kind: 'compaction',
|
|
1222
|
-
index: action.index,
|
|
1223
|
-
trigger: action.trigger,
|
|
1224
|
-
mode: 'none',
|
|
1225
|
-
applied: false,
|
|
1226
|
-
messagesBefore: 0,
|
|
1227
|
-
messagesAfter: 0,
|
|
1228
|
-
tokensBefore: 0,
|
|
1229
|
-
tokensAfter: 0,
|
|
1230
|
-
model: action.model,
|
|
1231
|
-
startedAt: Date.now(),
|
|
1232
|
-
phase: 'planning',
|
|
1233
|
-
live: true,
|
|
1234
|
-
};
|
|
1235
|
-
return {
|
|
1236
|
-
...state,
|
|
1237
|
-
seq,
|
|
1238
|
-
...appendEntry(state, entries, entry),
|
|
1239
|
-
compactionEntryId: id,
|
|
1240
|
-
streamingId: undefined,
|
|
1241
|
-
};
|
|
1242
|
-
}
|
|
1243
|
-
case 'compactionEnd': {
|
|
1244
|
-
const { record } = action;
|
|
1245
|
-
const settle = (e) => e.kind === 'compaction'
|
|
1246
|
-
? {
|
|
1247
|
-
...e,
|
|
1248
|
-
index: record.index,
|
|
1249
|
-
trigger: record.trigger,
|
|
1250
|
-
mode: record.mode,
|
|
1251
|
-
applied: record.applied,
|
|
1252
|
-
...(record.reason !== undefined ? { reason: record.reason } : {}),
|
|
1253
|
-
messagesBefore: record.messagesBefore,
|
|
1254
|
-
messagesAfter: record.messagesAfter,
|
|
1255
|
-
tokensBefore: record.tokensBefore,
|
|
1256
|
-
tokensAfter: record.tokensAfter,
|
|
1257
|
-
...(record.summary !== undefined ? { summary: record.summary } : {}),
|
|
1258
|
-
...(record.tailRelief ? { tailRelief: record.tailRelief } : {}),
|
|
1259
|
-
...(record.decision ? { decision: record.decision } : {}),
|
|
1260
|
-
...(record.memoryVersion ? { memoryVersion: record.memoryVersion } : {}),
|
|
1261
|
-
...(record.keepRelaxed ? { keepRelaxed: record.keepRelaxed } : {}),
|
|
1262
|
-
...(record.retainedTurns !== undefined
|
|
1263
|
-
? { retainedTurns: record.retainedTurns } : {}),
|
|
1264
|
-
...(record.retainedMessages !== undefined
|
|
1265
|
-
? { retainedMessages: record.retainedMessages } : {}),
|
|
1266
|
-
...(record.retainedMessages !== undefined
|
|
1267
|
-
? { retainedMessages: record.retainedMessages } : {}),
|
|
1268
|
-
...(record.keepRecentTurnsUsed !== undefined
|
|
1269
|
-
? { keepRecentTurnsUsed: record.keepRecentTurnsUsed } : {}),
|
|
1270
|
-
...(record.elidedMessages ? { elidedMessages: record.elidedMessages } : {}),
|
|
1271
|
-
model: record.model,
|
|
1272
|
-
durationMs: record.durationMs,
|
|
1273
|
-
// SETTLED. `Transcript`'s boundary is monotonic, so a card that
|
|
1274
|
-
// never settles is re-rendered on every frame for the rest of the
|
|
1275
|
-
// session (C-8).
|
|
1276
|
-
live: false,
|
|
1277
|
-
}
|
|
1278
|
-
: e;
|
|
1279
|
-
// THE SECOND BRANCH IS NOT DEFENSIVE PADDING, exactly as `fastEnd`'s is
|
|
1280
|
-
// not: the IDLE `/compact` path settles a record for a card this session
|
|
1281
|
-
// may never have opened, and "it did nothing" must never be the observable
|
|
1282
|
-
// outcome (R-8 / §6.4).
|
|
1283
|
-
if (state.compactionEntryId) {
|
|
1284
|
-
return {
|
|
1285
|
-
...state,
|
|
1286
|
-
entries: mapEntry(state.entries, state.compactionEntryId, settle),
|
|
1287
|
-
compactionEntryId: undefined,
|
|
1288
|
-
};
|
|
1289
|
-
}
|
|
1290
|
-
const { id, seq } = nextId(state);
|
|
1291
|
-
const entry = {
|
|
1292
|
-
id,
|
|
1293
|
-
kind: 'compaction',
|
|
1294
|
-
index: record.index,
|
|
1295
|
-
trigger: record.trigger,
|
|
1296
|
-
mode: record.mode,
|
|
1297
|
-
applied: record.applied,
|
|
1298
|
-
...(record.reason !== undefined ? { reason: record.reason } : {}),
|
|
1299
|
-
messagesBefore: record.messagesBefore,
|
|
1300
|
-
messagesAfter: record.messagesAfter,
|
|
1301
|
-
tokensBefore: record.tokensBefore,
|
|
1302
|
-
tokensAfter: record.tokensAfter,
|
|
1303
|
-
...(record.summary !== undefined ? { summary: record.summary } : {}),
|
|
1304
|
-
...(record.tailRelief ? { tailRelief: record.tailRelief } : {}),
|
|
1305
|
-
...(record.decision ? { decision: record.decision } : {}),
|
|
1306
|
-
...(record.memoryVersion ? { memoryVersion: record.memoryVersion } : {}),
|
|
1307
|
-
...(record.keepRelaxed ? { keepRelaxed: record.keepRelaxed } : {}),
|
|
1308
|
-
...(record.retainedTurns !== undefined ? { retainedTurns: record.retainedTurns } : {}),
|
|
1309
|
-
...(record.keepRecentTurnsUsed !== undefined
|
|
1310
|
-
? { keepRecentTurnsUsed: record.keepRecentTurnsUsed } : {}),
|
|
1311
|
-
...(record.elidedMessages ? { elidedMessages: record.elidedMessages } : {}),
|
|
1312
|
-
model: record.model,
|
|
1313
|
-
durationMs: record.durationMs,
|
|
1314
|
-
live: false,
|
|
1315
|
-
};
|
|
1316
|
-
return { ...state, seq, ...appendEntry(state, state.entries, entry) };
|
|
1317
|
-
}
|
|
1318
|
-
case 'compactionPhase': {
|
|
1319
|
-
// THE LIVE CARD'S STAGE, AND NOTHING ELSE. It must not open a card (a phase
|
|
1320
|
-
// can arrive for an entry that a fast settle already closed) and must not
|
|
1321
|
-
// resurrect a settled one: `live` is left untouched, so a late phase on a
|
|
1322
|
-
// settled card changes nothing the renderer shows.
|
|
1323
|
-
if (!state.compactionEntryId)
|
|
1324
|
-
return state;
|
|
1325
|
-
const phase = action.phase;
|
|
1326
|
-
return {
|
|
1327
|
-
...state,
|
|
1328
|
-
entries: mapEntry(state.entries, state.compactionEntryId, (e) => e.kind === 'compaction' && e.live ? { ...e, phase } : e),
|
|
1329
|
-
};
|
|
1330
|
-
}
|
|
1331
|
-
case 'compactionUsage':
|
|
1332
|
-
// `usageTotal` AND NOTHING ELSE, exactly as `teamUsage` and `fastUsage`. In
|
|
1333
|
-
// particular NOT `context`: that gauge shows the LEAD's context
|
|
1334
|
-
// occupancy against the lead model's window, and a summarization is a
|
|
1335
|
-
// separate conversation with a possibly different model entirely.
|
|
1336
|
-
return { ...state, usageTotal: addUsage(state.usageTotal, action.usage, action.costDelta) };
|
|
1337
|
-
case 'compactionSnapshot':
|
|
1338
|
-
return { ...state, compaction: action.snapshot };
|
|
1339
|
-
/**
|
|
1340
|
-
* THE ONE WRITER OF `state.context` (I-1).
|
|
1341
|
-
*
|
|
1342
|
-
* THE IDENTITY SHORT-CIRCUIT BELOW IS LOAD-BEARING, NOT A MICRO-OPTIMISATION
|
|
1343
|
-
* (§5.2 / R-3). The meter publishes on a 400 ms tick throughout a turn and
|
|
1344
|
-
* most of those ticks measure the same number - the history did not change
|
|
1345
|
-
* between two tool calls of the same size, or the percentage rounded to the
|
|
1346
|
-
* same integer. Returning the SAME state object lets React skip the whole
|
|
1347
|
-
* subtree; returning a fresh one would make a permanently visible row into a
|
|
1348
|
-
* 2.5 Hz re-render source and walk the render governor up its ladder for no
|
|
1349
|
-
* visible change at all.
|
|
1350
|
-
*
|
|
1351
|
-
* `ContextUsageSnapshot` carries no timestamp for exactly this reason: a
|
|
1352
|
-
* field that moves every tick would make this comparison never hold.
|
|
1353
|
-
*/
|
|
1354
|
-
case 'contextUsage': {
|
|
1355
|
-
const prev = state.context;
|
|
1356
|
-
const next = action.snapshot;
|
|
1357
|
-
if (prev.occupied === next.occupied &&
|
|
1358
|
-
prev.window === next.window &&
|
|
1359
|
-
prev.pct === next.pct &&
|
|
1360
|
-
prev.source === next.source &&
|
|
1361
|
-
prev.deltaTokens === next.deltaTokens &&
|
|
1362
|
-
prev.windowKnown === next.windowKnown &&
|
|
1363
|
-
prev.windowSource === next.windowSource &&
|
|
1364
|
-
prev.windowOverridden === next.windowOverridden) {
|
|
1365
|
-
return state;
|
|
1366
|
-
}
|
|
1367
|
-
return { ...state, context: next };
|
|
1368
|
-
}
|
|
1369
|
-
// --- API retry (llm-api-retry-backoff §6.4) ---------------------------
|
|
1370
|
-
case 'retryScheduled': {
|
|
1371
|
-
const snapshot = {
|
|
1372
|
-
attempt: action.attempt,
|
|
1373
|
-
maxRetries: action.maxRetries,
|
|
1374
|
-
resumeAt: action.resumeAt,
|
|
1375
|
-
phase: 'waiting',
|
|
1376
|
-
errorType: action.errorType,
|
|
1377
|
-
};
|
|
1378
|
-
// ONE CARD PER TURN, REWRITTEN IN PLACE. One `mapEntry` on the KNOWN id,
|
|
1379
|
-
// never a scan — the same discipline `todoUpdate` follows.
|
|
1380
|
-
if (state.retryEntryId) {
|
|
1381
|
-
const known = state.retryEntryId;
|
|
1382
|
-
return {
|
|
1383
|
-
...state,
|
|
1384
|
-
retry: snapshot,
|
|
1385
|
-
entries: mapEntry(state.entries, known, (e) => e.kind === 'retry'
|
|
1386
|
-
? {
|
|
1387
|
-
...e,
|
|
1388
|
-
attempt: action.attempt,
|
|
1389
|
-
maxRetries: action.maxRetries,
|
|
1390
|
-
errorType: action.errorType,
|
|
1391
|
-
message: action.message,
|
|
1392
|
-
delayMs: action.delayMs,
|
|
1393
|
-
resumeAt: action.resumeAt,
|
|
1394
|
-
phase: 'waiting',
|
|
1395
|
-
}
|
|
1396
|
-
: e),
|
|
1397
|
-
};
|
|
1398
|
-
}
|
|
1399
|
-
const { id, seq } = nextId(state);
|
|
1400
|
-
const entry = {
|
|
1401
|
-
id,
|
|
1402
|
-
kind: 'retry',
|
|
1403
|
-
attempt: action.attempt,
|
|
1404
|
-
maxRetries: action.maxRetries,
|
|
1405
|
-
errorType: action.errorType,
|
|
1406
|
-
message: action.message,
|
|
1407
|
-
delayMs: action.delayMs,
|
|
1408
|
-
resumeAt: action.resumeAt,
|
|
1409
|
-
phase: 'waiting',
|
|
1410
|
-
// The clock the settled card's `elapsedMs` is measured from: the FIRST
|
|
1411
|
-
// retry of the turn, not the last.
|
|
1412
|
-
startedAt: Date.now(),
|
|
1413
|
-
};
|
|
1414
|
-
return {
|
|
1415
|
-
...state,
|
|
1416
|
-
seq,
|
|
1417
|
-
retry: snapshot,
|
|
1418
|
-
...appendEntry(state, state.entries, entry),
|
|
1419
|
-
retryEntryId: id,
|
|
1420
|
-
};
|
|
1421
|
-
}
|
|
1422
|
-
case 'retryAttempt': {
|
|
1423
|
-
if (!state.retryEntryId)
|
|
1424
|
-
return state;
|
|
1425
|
-
const known = state.retryEntryId;
|
|
1426
|
-
return {
|
|
1427
|
-
...state,
|
|
1428
|
-
// `resumeAt` is CLEARED: the wait is over, so a countdown would tick
|
|
1429
|
-
// downward past zero while the request is already in flight.
|
|
1430
|
-
retry: state.retry ? { ...state.retry, phase: 'retrying', resumeAt: undefined } : null,
|
|
1431
|
-
entries: mapEntry(state.entries, known, (e) => e.kind === 'retry' ? { ...e, phase: 'retrying', resumeAt: undefined } : e),
|
|
1432
|
-
};
|
|
1433
|
-
}
|
|
1434
|
-
case 'streamRestart': {
|
|
1435
|
-
const discarded = new Set(action.discardedToolCallIds);
|
|
1436
|
-
const entries = state.entries.filter((e) => !(e.kind === 'tool' && discarded.has(e.toolCallId)));
|
|
1437
|
-
return {
|
|
1438
|
-
...state,
|
|
1439
|
-
entries: state.streamingId
|
|
1440
|
-
? mapEntry(entries, state.streamingId, (e) => e.kind === 'assistant'
|
|
1441
|
-
? {
|
|
1442
|
-
...e,
|
|
1443
|
-
text: '',
|
|
1444
|
-
thinking: undefined,
|
|
1445
|
-
thinkingOpen: false,
|
|
1446
|
-
// A restarted stream re-thinks from scratch. Carrying the old
|
|
1447
|
-
// clock forward would report a duration for reasoning that
|
|
1448
|
-
// was discarded.
|
|
1449
|
-
thinkingStartedAt: undefined,
|
|
1450
|
-
thinkingMs: undefined,
|
|
1451
|
-
}
|
|
1452
|
-
: e)
|
|
1453
|
-
: entries,
|
|
1454
|
-
};
|
|
1455
|
-
}
|
|
1456
|
-
/**
|
|
1457
|
-
* The 1 Hz countdown tick. NO VALUE CHANGES — the card is refreshed by
|
|
1458
|
-
* IDENTITY so that React re-renders exactly one entry.
|
|
1459
|
-
*
|
|
1460
|
-
* A FLAT NONCE ON `ViewState` WOULD NOT WORK, and this is the trap worth
|
|
1461
|
-
* naming: `EntryView` is `React.memo`'d with an explicit comparator whose
|
|
1462
|
-
* first clause is `a.entry === b.entry`, so a state field the card does not
|
|
1463
|
-
* receive as a prop changes nothing at all — the countdown would freeze at
|
|
1464
|
-
* whatever second it was first drawn on, and the transcript would keep
|
|
1465
|
-
* re-rendering around it for no benefit. Rewriting the entry is what breaks
|
|
1466
|
-
* that boundary, and `EntryView` reads the clock for this kind only.
|
|
1467
|
-
*
|
|
1468
|
-
* `entryRevision` is deliberately NOT extended: the card is one row in every
|
|
1469
|
-
* phase, so the cached height stays correct and no re-measure is wanted.
|
|
1470
|
-
*
|
|
1471
|
-
* Returning `state` unchanged when nothing is waiting is what keeps a stray
|
|
1472
|
-
* tick from costing a render at all.
|
|
1473
|
-
*/
|
|
1474
|
-
case 'retryTick': {
|
|
1475
|
-
if (state.retry?.phase !== 'waiting' || !state.retryEntryId)
|
|
1476
|
-
return state;
|
|
1477
|
-
const known = state.retryEntryId;
|
|
1478
|
-
return {
|
|
1479
|
-
...state,
|
|
1480
|
-
entries: mapEntry(state.entries, known, (e) => (e.kind === 'retry' ? { ...e } : e)),
|
|
1481
|
-
};
|
|
1482
|
-
}
|
|
1483
|
-
case 'restoreEntries': {
|
|
1484
|
-
// Adopt a seq beyond any restored id to keep future ids unique.
|
|
1485
|
-
const maxSeq = action.entries.reduce((acc, e) => {
|
|
1486
|
-
const n = Number.parseInt(e.id.replace(/^e/, ''), 10);
|
|
1487
|
-
return Number.isFinite(n) ? Math.max(acc, n) : acc;
|
|
1488
|
-
}, state.seq);
|
|
1489
|
-
// A session file can be longer than the retain ring; trimming it here is
|
|
1490
|
-
// the one append-free place the bound still has to hold, and the count
|
|
1491
|
-
// starts from this restore rather than inheriting the previous session's.
|
|
1492
|
-
const restored = trimEntries([...action.entries], entryRetain());
|
|
1493
|
-
return {
|
|
1494
|
-
...state,
|
|
1495
|
-
entries: restored.entries,
|
|
1496
|
-
pendingSteering: [],
|
|
1497
|
-
droppedEntries: restored.dropped,
|
|
1498
|
-
seq: maxSeq,
|
|
1499
|
-
teamEntryId: undefined,
|
|
1500
|
-
// As for `team`: the restored cards are history and no turn owns them.
|
|
1501
|
-
// The LIST itself arrives separately, through `controller.restoreTodos`
|
|
1502
|
-
// and the store's own event (§3.13).
|
|
1503
|
-
todoEntryId: undefined,
|
|
1504
|
-
// Same for the retry card. `normalizeLoadedEntries` has already forced any
|
|
1505
|
-
// in-flight phase to `interrupted`, so nothing restored is live.
|
|
1506
|
-
retryEntryId: undefined,
|
|
1507
|
-
retry: null,
|
|
1508
|
-
// Same again for the fast card: `normalizeLoadedEntries` has already
|
|
1509
|
-
// forced `live: false`, so nothing restored is live. `state.fast` is
|
|
1510
|
-
// NOT cleared — it is the session's own tier projection, which a
|
|
1511
|
-
// `/resume` does not change.
|
|
1512
|
-
fastEntryId: undefined,
|
|
1513
|
-
// `/resume` ADOPTS ANOTHER CONVERSATION, so the previous conversation's
|
|
1514
|
-
// label describes something that is no longer on screen. The reducer only
|
|
1515
|
-
// CLEARS it; the App's `makeCtx().dispatch` wrapper rebuilds one from
|
|
1516
|
-
// `action.entries` (D-13), because it is the only layer that sees the
|
|
1517
|
-
// restored entries BEFORE this branch runs.
|
|
1518
|
-
taskSummary: null,
|
|
1519
|
-
taskSummaryPending: false,
|
|
1520
|
-
// Same rule for a live governed run: `/resume` adopts another conversation, and
|
|
1521
|
-
// a snapshot of the old one's run must not be drawn over it. Settlement cards
|
|
1522
|
-
// arrive with the restored `entries`.
|
|
1523
|
-
governance: null,
|
|
1524
|
-
};
|
|
1525
|
-
}
|
|
1526
|
-
// --- Background services (§3.10 / D-11) -----------------------------
|
|
1527
|
-
case 'serviceStart': {
|
|
1528
|
-
const { id, seq } = nextId(state);
|
|
1529
|
-
const entry = { id, ...serviceFields(action.service) };
|
|
1530
|
-
const appended = appendEntry(state, state.entries, entry);
|
|
1531
|
-
return {
|
|
1532
|
-
...state,
|
|
1533
|
-
seq,
|
|
1534
|
-
entries: appended.entries,
|
|
1535
|
-
droppedEntries: appended.droppedEntries,
|
|
1536
|
-
};
|
|
1537
|
-
}
|
|
1538
|
-
case 'serviceUpdate': {
|
|
1539
|
-
const target = findServiceEntryId(state, action.service.id);
|
|
1540
|
-
if (!target)
|
|
1541
|
-
return state;
|
|
1542
|
-
return {
|
|
1543
|
-
...state,
|
|
1544
|
-
entries: mapEntry(state.entries, target, (e) => e.kind === 'service' ? { ...e, ...serviceFields(action.service) } : e),
|
|
1545
|
-
};
|
|
1546
|
-
}
|
|
1547
|
-
case 'serviceEnd': {
|
|
1548
|
-
const target = findServiceEntryId(state, action.service.id);
|
|
1549
|
-
const settled = target
|
|
1550
|
-
? mapEntry(state.entries, target, (e) => e.kind === 'service' ? { ...e, ...serviceFields(action.service) } : e)
|
|
1551
|
-
: state.entries;
|
|
1552
|
-
const { id, seq } = nextId(state);
|
|
1553
|
-
const appended = appendEntry(state, settled, {
|
|
1554
|
-
id,
|
|
1555
|
-
...serviceFields(action.service),
|
|
1556
|
-
terminal: true,
|
|
1557
|
-
});
|
|
1558
|
-
return {
|
|
1559
|
-
...state,
|
|
1560
|
-
seq,
|
|
1561
|
-
entries: appended.entries,
|
|
1562
|
-
droppedEntries: appended.droppedEntries,
|
|
1563
|
-
};
|
|
1564
|
-
}
|
|
1565
|
-
default:
|
|
1566
|
-
return state;
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
/**
|
|
1570
|
-
* Project a supervisor snapshot onto the entry's own fields.
|
|
1571
|
-
*
|
|
1572
|
-
* ONE PROJECTION FOR ALL THREE ACTIONS, so a field that reaches the card through
|
|
1573
|
-
* `serviceStart` can never fail to reach it through `serviceUpdate` — the kind of
|
|
1574
|
-
* asymmetry that produces a card frozen at its first values with nothing
|
|
1575
|
-
* reporting it.
|
|
1576
|
-
*/
|
|
1577
|
-
function serviceFields(service) {
|
|
1578
|
-
return {
|
|
1579
|
-
kind: 'service',
|
|
1580
|
-
serviceId: service.id,
|
|
1581
|
-
jobKind: service.kind,
|
|
1582
|
-
command: service.command,
|
|
1583
|
-
status: service.status,
|
|
1584
|
-
...(service.url !== undefined ? { url: service.url } : {}),
|
|
1585
|
-
...(service.port !== undefined ? { port: service.port } : {}),
|
|
1586
|
-
exitCode: service.exitCode,
|
|
1587
|
-
startedAt: service.startedAt,
|
|
1588
|
-
...(service.readyAt !== undefined ? { readyAt: service.readyAt } : {}),
|
|
1589
|
-
...(service.endedAt !== undefined ? { endedAt: service.endedAt } : {}),
|
|
1590
|
-
rows: service.rows,
|
|
1591
|
-
rowsSeen: service.rowsSeen,
|
|
1592
|
-
...(service.killIncomplete ? { killIncomplete: true } : {}),
|
|
1593
|
-
// The four fields long-task-jobs adds, in the SAME projection for all three
|
|
1594
|
-
// actions - which is the property this function exists to guarantee.
|
|
1595
|
-
lastOutputAt: service.lastOutputAt,
|
|
1596
|
-
...(service.progress ? { progress: service.progress.text } : {}),
|
|
1597
|
-
...(service.stalled ? { stalled: true } : {}),
|
|
1598
|
-
...(service.logPath !== undefined ? { logPath: service.logPath } : {}),
|
|
1599
|
-
};
|
|
1600
|
-
}
|
|
1601
|
-
/** The LIVE card for a service id, newest first; terminal records are skipped. */
|
|
1602
|
-
function findServiceEntryId(state, serviceId) {
|
|
1603
|
-
for (let i = state.entries.length - 1; i >= 0; i -= 1) {
|
|
1604
|
-
const e = state.entries[i];
|
|
1605
|
-
if (e && e.kind === 'service' && e.serviceId === serviceId && !e.terminal)
|
|
1606
|
-
return e.id;
|
|
1607
|
-
}
|
|
1608
|
-
return undefined;
|
|
1609
|
-
}
|
|
1610
|
-
function safeStringify(value) {
|
|
1611
|
-
try {
|
|
1612
|
-
return JSON.stringify(value);
|
|
1613
|
-
}
|
|
1614
|
-
catch {
|
|
1615
|
-
return String(value);
|
|
1616
|
-
}
|
|
1617
|
-
}
|