tumwater 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -108
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +24 -0
- package/dist/src/agent/agent-backend.js +2 -2
- package/dist/src/agent/backend-traits.js +60 -0
- package/dist/src/agent/claude/claude-account.js +61 -8
- package/dist/src/agent/claude/claude-args.js +12 -15
- package/dist/src/agent/claude/claude-backend.js +51 -41
- package/dist/src/agent/claude/claude-env.js +7 -3
- package/dist/src/agent/claude/claude-stream.js +7 -16
- package/dist/src/agent/image-input.js +107 -0
- package/dist/src/agent/notes-shim.js +41 -0
- package/dist/src/agent/opencode/opencode-args.js +61 -0
- package/dist/src/agent/opencode/opencode-backend.js +255 -0
- package/dist/src/agent/opencode/opencode-catalog.js +184 -0
- package/dist/src/agent/opencode/opencode-config.js +188 -0
- package/dist/src/agent/opencode/opencode-env.js +36 -0
- package/dist/src/agent/opencode/opencode-session-id.js +37 -0
- package/dist/src/agent/opencode/opencode-stream.js +278 -0
- package/dist/src/agent/role-notes-write.js +16 -32
- package/dist/src/agent/run-agent.js +95 -18
- package/dist/src/agent/session-ledger.js +57 -1
- package/dist/src/backlog/backlog-archive.js +296 -0
- package/dist/src/backlog/backlog-cli.js +573 -0
- package/dist/src/backlog/backlog-commit.js +31 -0
- package/dist/src/backlog/backlog-eligibility.js +357 -33
- package/dist/src/backlog/backlog-md.js +98 -18
- package/dist/src/backlog/backlog-render.js +62 -11
- package/dist/src/backlog/backlog-structure.js +261 -58
- package/dist/src/backlog/backlog-write.js +197 -319
- package/dist/src/backlog/backlog.js +113 -16
- package/dist/src/backlog/question-answer.js +339 -0
- package/dist/src/baseline/main-baseline.js +75 -88
- package/dist/src/baseline/main-red.js +38 -30
- package/dist/src/brief/brief-edit.js +43 -0
- package/dist/src/{brief.js → brief/brief.js} +66 -10
- package/dist/src/budget/budget.js +32 -15
- package/dist/src/budget/fallback-breaker.js +8 -7
- package/dist/src/build/build-check-detect.js +8 -7
- package/dist/src/build/build-check-events.js +68 -22
- package/dist/src/build/build-check-report.js +22 -23
- package/dist/src/build/build-check-scoped.js +51 -62
- package/dist/src/build/build-check.js +15 -14
- package/dist/src/build/build-info.js +2 -1
- package/dist/src/build/build-stage.js +23 -22
- package/dist/src/build/dep-install.js +22 -21
- package/dist/src/build/host-sleep.js +1 -1
- package/dist/src/change/change-data.js +25 -14
- package/dist/src/change/change-render.js +11 -10
- package/dist/src/cli/brief-command.js +58 -0
- package/dist/src/cli/cli-args.js +25 -5
- package/dist/src/cli/cli-command-args.js +46 -14
- package/dist/src/cli/cli-editor.js +64 -0
- package/dist/src/cli/cli-flag-specs.js +54 -16
- package/dist/src/cli/cli-marker-commands.js +13 -10
- package/dist/src/cli/cli-output.js +9 -0
- package/dist/src/cli/cli-query-commands.js +458 -49
- package/dist/src/cli/cli-repo-gate.js +1 -1
- package/dist/src/cli/cli-run.js +208 -59
- package/dist/src/cli/completion.js +207 -0
- package/dist/src/cli/config-commands.js +14 -11
- package/dist/src/cli/help.js +305 -110
- package/dist/src/cli/log-commands.js +127 -21
- package/dist/src/cli/notify-command.js +33 -18
- package/dist/src/cli/question-commands.js +29 -224
- package/dist/src/cli.js +121 -40
- package/dist/src/collections.js +66 -16
- package/dist/src/concurrency/check-permit.js +16 -17
- package/dist/src/concurrency/lock.js +6 -5
- package/dist/src/concurrency/provider-lease.js +69 -0
- package/dist/src/concurrency/semaphore.js +258 -36
- package/dist/src/config/config-aliases.js +106 -0
- package/dist/src/config/config-editable-keys.js +3 -3
- package/dist/src/config/config-fallbacks.js +379 -0
- package/dist/src/config/config-field-checks.js +19 -10
- package/dist/src/config/config-keys.js +160 -0
- package/dist/src/config/config-live.js +64 -12
- package/dist/src/config/config-schema.js +34 -22
- package/dist/src/config/config-validation.js +132 -48
- package/dist/src/config/config-views.js +119 -337
- package/dist/src/config/config-write.js +138 -177
- package/dist/src/config/config.js +60 -49
- package/dist/src/config/model-selector.js +28 -2
- package/dist/src/doctor/doctor-backlog.js +546 -49
- package/dist/src/doctor/doctor-checks.js +534 -56
- package/dist/src/doctor/doctor-holds.js +193 -53
- package/dist/src/doctor/doctor-install.js +30 -0
- package/dist/src/doctor/doctor-launch-services.js +2 -2
- package/dist/src/doctor/doctor-model-checks.js +549 -157
- package/dist/src/doctor/doctor-notify.js +27 -0
- package/dist/src/doctor/doctor-orphans.js +10 -9
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +80 -22
- package/dist/src/doctor/image-probe.js +244 -0
- package/dist/src/doctor/probe.js +64 -0
- package/dist/src/events/event-format.js +226 -102
- package/dist/src/events/event-labels.js +116 -0
- package/dist/src/events/event-legacy.js +140 -0
- package/dist/src/events/event-read.js +58 -22
- package/dist/src/events/event-window.js +27 -7
- package/dist/src/events/events.js +18 -14
- package/dist/src/events/notify.js +137 -38
- package/dist/src/failure/failure-cluster.js +12 -9
- package/dist/src/failure/failure-data.js +96 -48
- package/dist/src/failure/failure-render.js +69 -58
- package/dist/src/failure/failure-state-change.js +33 -26
- package/dist/src/failure/time-spend.js +79 -59
- package/dist/src/files/file-queue.js +16 -4
- package/dist/src/files/files.js +18 -6
- package/dist/src/files/json-object.js +18 -3
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +5 -4
- package/dist/src/gates/budget-gates.js +250 -34
- package/dist/src/gates/disk-gate.js +18 -18
- package/dist/src/gates/gate-polls.js +117 -69
- package/dist/src/gates/maintenance-quota.js +29 -29
- package/dist/src/gates/pause-gates.js +11 -11
- package/dist/src/gates/readiness.js +2 -1
- package/dist/src/gates/role-cap-gates.js +16 -21
- package/dist/src/gates/startup-gate.js +75 -31
- package/dist/src/gates/streak-gate.js +18 -16
- package/dist/src/git/commit-message.js +12 -5
- package/dist/src/git/git-diff.js +16 -16
- package/dist/src/git/git-run.js +8 -8
- package/dist/src/git/git.js +86 -55
- package/dist/src/git/slots-state.js +14 -5
- package/dist/src/git/worktree-pool.js +25 -18
- package/dist/src/git/worktree-use.js +15 -14
- package/dist/src/git/worktree.js +18 -18
- package/dist/src/gui/gui-args.js +10 -14
- package/dist/src/gui/gui-backlog-commands.js +273 -0
- package/dist/src/gui/gui-command.js +1 -1
- package/dist/src/gui/gui-config-commands.js +440 -0
- package/dist/src/gui/gui-endpoint-commands.js +16 -669
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +116 -56
- package/dist/src/gui/gui-operator-commands.js +145 -0
- package/dist/src/gui/gui-prompt-commands.js +234 -0
- package/dist/src/gui/gui-server.js +28 -8
- package/dist/src/gui/gui-team-commands.js +164 -0
- package/dist/src/gui/http-body.js +17 -1
- package/dist/src/history/history-data.js +194 -98
- package/dist/src/history/history-render.js +76 -0
- package/dist/src/history/history.js +37 -58
- package/dist/src/inbox/inbox-attachments.js +75 -14
- package/dist/src/inbox/inbox-cancel.js +7 -5
- package/dist/src/inbox/inbox-edit.js +1 -1
- package/dist/src/inbox/inbox-submit.js +44 -4
- package/dist/src/inbox/inbox-top.js +83 -0
- package/dist/src/inbox/inbox.js +8 -2
- package/dist/src/inbox/pending-prompt.js +1 -1
- package/dist/src/inbox/prompt-commands.js +170 -80
- package/dist/src/init/init-templates.js +16 -14
- package/dist/src/init/init.js +12 -11
- package/dist/src/loop/leftover.js +28 -27
- package/dist/src/loop/loop-fallback.js +2 -2
- package/dist/src/loop/loop-pi.js +106 -52
- package/dist/src/loop/loop-recovery.js +15 -15
- package/dist/src/loop/loop-state.js +139 -13
- package/dist/src/loop/loop-tick.js +194 -86
- package/dist/src/loop/loop.js +72 -36
- package/dist/src/loop/model-fallback.js +3 -2
- package/dist/src/loop/revision.js +16 -7
- package/dist/src/merge/backlog-conflicts.js +155 -0
- package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
- package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
- package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
- package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
- package/dist/src/merge/merge-drain.js +212 -0
- package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
- package/dist/src/merge/merge-pipeline.js +99 -0
- package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
- package/dist/src/merge/merge-queue-render.js +75 -0
- package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
- package/dist/src/merge/merge-slot.js +340 -0
- package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
- package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
- package/dist/src/merge/merge-verify.js +212 -0
- package/dist/src/notes-cli.js +97 -0
- package/dist/src/operator/operator-commands.js +154 -47
- package/dist/src/operator/operator-intent.js +87 -49
- package/dist/src/operator/operator-requests.js +52 -52
- package/dist/src/operator/retire.js +62 -27
- package/dist/src/operator/revert.js +71 -0
- package/dist/src/orchestrator/orchestrator-launch.js +74 -14
- package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
- package/dist/src/orchestrator/orchestrator.js +158 -100
- package/dist/src/orchestrator/retention.js +29 -12
- package/dist/src/orchestrator/startup-migration-steps.js +51 -0
- package/dist/src/orchestrator/startup-migrations.js +40 -0
- package/dist/src/paths.js +128 -37
- package/dist/src/pi/pi-args.js +26 -3
- package/dist/src/pi/pi-backend.js +15 -23
- package/dist/src/pi/pi-bin.js +18 -12
- package/dist/src/pi/pi-event-line.js +17 -1
- package/dist/src/pi/pi-failure.js +87 -0
- package/dist/src/pi/pi-models.js +126 -38
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +86 -0
- package/dist/src/pi/pi-stream.js +131 -107
- package/dist/src/pi/pi-watchdogs.js +47 -43
- package/dist/src/pi-extension/bounded-output.js +20 -5
- package/dist/src/pi-extension/context-budget.js +3 -1
- package/dist/src/pi-extension/context-shake.js +54 -21
- package/dist/src/pi-extension/context-window.js +37 -0
- package/dist/src/pi-extension/full-output.js +2 -2
- package/dist/src/pi-extension/strip-thinking.js +80 -0
- package/dist/src/pi-extension/trim-system-prompt.js +38 -0
- package/dist/src/process/launch-services.js +11 -10
- package/dist/src/process/process-table.js +1 -1
- package/dist/src/process/process.js +7 -7
- package/dist/src/process/run-marker.js +29 -6
- package/dist/src/process/supervisor.js +47 -25
- package/dist/src/prompt/dialect.js +30 -10
- package/dist/src/prompt/gate-prompts.js +170 -67
- package/dist/src/prompt/principles.js +1 -0
- package/dist/src/prompt/prompt-followup.js +35 -20
- package/dist/src/prompt/prompt.js +249 -91
- package/dist/src/redeploy/redeploy-policy.js +10 -10
- package/dist/src/redeploy/redeploy.js +6 -4
- package/dist/src/redeploy/redeployer.js +63 -55
- package/dist/src/redeploy/self-reload.js +1 -1
- package/dist/src/report/report-data.js +264 -255
- package/dist/src/report/report-fold.js +353 -0
- package/dist/src/report/report-render.js +270 -33
- package/dist/src/request-timeouts.js +1 -1
- package/dist/src/review/known-flakes.js +34 -14
- package/dist/src/review/protected-edits.js +33 -0
- package/dist/src/review/review-followup.js +16 -4
- package/dist/src/review/review-precheck.js +14 -13
- package/dist/src/review/review-verdict.js +14 -0
- package/dist/src/review/review.js +81 -39
- package/dist/src/review/secret-leaks.js +127 -0
- package/dist/src/review/suite-rerun.js +4 -3
- package/dist/src/roles/legacy-role-ids.js +21 -0
- package/dist/src/roles/legacy-role-migration.js +62 -0
- package/dist/src/roles/loop-ids.js +19 -2
- package/dist/src/roles/role-catalog.js +125 -135
- package/dist/src/roles/role-guidance.js +64 -31
- package/dist/src/roles/role-labels.js +10 -0
- package/dist/src/roles/role-render.js +194 -18
- package/dist/src/roles/role-view.js +216 -17
- package/dist/src/roles/roles.js +18 -15
- package/dist/src/scheduling/backoff.js +29 -19
- package/dist/src/scheduling/claims.js +25 -9
- package/dist/src/scheduling/once-round.js +11 -11
- package/dist/src/scheduling/quiet-hours.js +25 -32
- package/dist/src/scheduling/scheduling.js +43 -24
- package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
- package/dist/src/status/status-budget.js +115 -0
- package/dist/src/status/status-data.js +77 -184
- package/dist/src/status/status-inbox.js +47 -0
- package/dist/src/status/status-install.js +27 -0
- package/dist/src/status/status-polls.js +68 -33
- package/dist/src/status/status-roles.js +105 -0
- package/dist/src/{fleet → team}/error-storm.js +10 -10
- package/dist/src/{fleet → team}/failure-spread.js +8 -8
- package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
- package/dist/src/{fleet → team}/reclaim.js +5 -4
- package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
- package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
- package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
- package/dist/src/text/datetime.js +42 -2
- package/dist/src/text/format.js +10 -1
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +35 -34
- package/dist/src/text/suggest.js +3 -2
- package/dist/src/text/text.js +2 -2
- package/dist/src/tick/qa-coverage.js +2 -1
- package/dist/src/tick/stage-check.js +147 -26
- package/dist/src/tick/telemetry-digest.js +10 -9
- package/dist/src/tick/tick-apply.js +101 -72
- package/dist/src/tick/tick-detail-data.js +32 -18
- package/dist/src/tick/tick-detail.js +62 -21
- package/dist/src/tick/tick-finalize.js +29 -22
- package/dist/src/tick/tick-outcome.js +2 -1
- package/dist/src/tick/tick-prompt.js +94 -26
- package/dist/src/tick/tick-resume.js +29 -13
- package/dist/src/tick/tick-stage.js +91 -47
- package/dist/src/tick/tick-timing.js +60 -59
- package/dist/src/tick/tick-usage.js +53 -23
- package/dist/src/tick/tick-verdict.js +76 -10
- package/dist/src/ui/badges.js +118 -67
- package/dist/src/ui/gui/gui-client-boot.js +209 -30
- package/dist/src/ui/gui/gui-client-composer.js +102 -23
- package/dist/src/ui/gui/gui-client-drawer.js +330 -75
- package/dist/src/ui/gui/gui-client-history.js +96 -38
- package/dist/src/ui/gui/gui-client-labels.js +24 -0
- package/dist/src/ui/gui/gui-client-loops.js +94 -52
- package/dist/src/ui/gui/gui-client-markdown.js +1 -0
- package/dist/src/ui/gui/gui-client-model.js +63 -43
- package/dist/src/ui/gui/gui-client-operator.js +129 -39
- package/dist/src/ui/gui/gui-client-pending.js +36 -32
- package/dist/src/ui/gui/gui-client-report.js +339 -46
- package/dist/src/ui/gui/gui-client-settings.js +148 -14
- package/dist/src/ui/gui/gui-client-sound.js +2 -1
- package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
- package/dist/src/ui/gui/gui-client.js +26 -20
- package/dist/src/ui/gui/gui-page.js +19 -19
- package/dist/src/ui/gui/gui-styles.js +12 -4
- package/dist/src/ui/progress-data.js +5 -3
- package/dist/src/ui/status-model.js +184 -122
- package/dist/src/ui/status-payload.js +119 -32
- package/dist/src/ui/status-render.js +122 -52
- package/dist/src/ui/team-alerts.js +341 -0
- package/dist/src/ui/tick-progress-model.js +14 -14
- package/dist/src/ui/tone.js +11 -8
- package/dist/src/ui/transcript-tail.js +1 -0
- package/dist/src/ui/transcript.js +23 -16
- package/dist/src/ui/tui/tui-app.js +1 -0
- package/dist/src/ui/tui/tui-backlog.js +85 -11
- package/dist/src/ui/tui/tui-editor-answer.js +97 -0
- package/dist/src/ui/tui/tui-editor-attach.js +107 -0
- package/dist/src/ui/tui/tui-editor-brief.js +107 -0
- package/dist/src/ui/tui/tui-editor-budget.js +155 -0
- package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
- package/dist/src/ui/tui/tui-editor-entry.js +230 -0
- package/dist/src/ui/tui/tui-editor-interval.js +122 -0
- package/dist/src/ui/tui/tui-editor-notes.js +114 -0
- package/dist/src/ui/tui/tui-editor-pause.js +266 -0
- package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
- package/dist/src/ui/tui/tui-editor-queued.js +98 -0
- package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
- package/dist/src/ui/tui/tui-editor-retire.js +83 -0
- package/dist/src/ui/tui/tui-editor-search.js +73 -0
- package/dist/src/ui/tui/tui-editor-session.js +97 -0
- package/dist/src/ui/tui/tui-editors.js +152 -0
- package/dist/src/ui/tui/tui-frame.js +67 -55
- package/dist/src/ui/tui/tui-history.js +12 -7
- package/dist/src/ui/tui/tui-input.js +49 -44
- package/dist/src/ui/tui/tui-keymap.js +1 -0
- package/dist/src/ui/tui/tui-keys.js +91 -1704
- package/dist/src/ui/tui/tui-pane-activity.js +99 -0
- package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
- package/dist/src/ui/tui/tui-pane-brief.js +30 -0
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +124 -0
- package/dist/src/ui/tui/tui-pane-history.js +316 -0
- package/dist/src/ui/tui/tui-pane-pending.js +74 -0
- package/dist/src/ui/tui/tui-pane-queued.js +143 -0
- package/dist/src/ui/tui/tui-pane-report.js +122 -0
- package/dist/src/ui/tui/tui-pane-session.js +101 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
- package/dist/src/ui/tui/tui-pane.js +66 -28
- package/dist/src/ui/tui/tui-panes.js +294 -0
- package/dist/src/ui/tui/tui.js +45 -25
- package/dist/src/verdict/fix-claim.js +141 -38
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +92 -8
- package/dist/src/verdict/reply-contract.js +17 -5
- package/dist/src/version.js +3 -2
- package/package.json +4 -2
- package/dist/src/agent/mcp/role-notes-server.js +0 -160
- package/dist/src/landing/backlog-conflicts.js +0 -116
- package/dist/src/landing/landing-drain.js +0 -204
- package/dist/src/landing/landing-pipeline.js +0 -96
- package/dist/src/landing/landing-queue-render.js +0 -31
- package/dist/src/landing/landing-slot.js +0 -246
- package/dist/src/landing/landing-vetting.js +0 -196
- package/dist/src/pi-extension/role-notes.js +0 -40
- package/dist/src/ui/fleet-alerts.js +0 -174
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** The shared `/` search editor on the prompt line: over the History pane's tick rows or the
|
|
2
|
+
* Activity feed's events. It owns its mode flag (`searchMode`), its target pane (`searchFor`),
|
|
3
|
+
* its saved-draft pair, and the applyHistorySearch / applyActivitySearch leaves. Mutually
|
|
4
|
+
* exclusive with the other editors via the session's held registry. */
|
|
5
|
+
export function createSearchEditor(session) {
|
|
6
|
+
let searchMode = false;
|
|
7
|
+
// Which pane the shared `/` editor feeds: History's tick search or Activity's event search.
|
|
8
|
+
// The target is captured when `/` opens so a roster change mid-edit cannot retarget Enter.
|
|
9
|
+
let searchFor = "history";
|
|
10
|
+
let searchSavedInput = "";
|
|
11
|
+
let searchSavedCursor = 0;
|
|
12
|
+
// Leave the shared search editor (Enter applies, Esc cancels, Ctrl+T cycles): restore the
|
|
13
|
+
// saved prompt text, and reset the recall state with it — the same settle contract the budget
|
|
14
|
+
// and role-prompt editors honor. The applied activityGrep/historyGrep itself persists across
|
|
15
|
+
// view cycling, like historyWindow/historyResult.
|
|
16
|
+
const exitHistorySearch = () => {
|
|
17
|
+
if (!searchMode)
|
|
18
|
+
return;
|
|
19
|
+
searchMode = false;
|
|
20
|
+
session.held.search = false;
|
|
21
|
+
session.restorePromptFrom(searchSavedInput, searchSavedCursor);
|
|
22
|
+
};
|
|
23
|
+
const handleKey = (str, key, ctx) => {
|
|
24
|
+
if (!key.ctrl && str === "/" && !searchMode && !session.held.budget &&
|
|
25
|
+
!session.held.rolePromptFor && !session.held.pause &&
|
|
26
|
+
!session.held.queued && !session.held.answer && !session.held.entry &&
|
|
27
|
+
!session.held.attach && !session.held.retire &&
|
|
28
|
+
(ctx.view === 0 || ctx.view === ctx.roleIds.length + 4) && session.input === "") {
|
|
29
|
+
// `/` opens a free-text search on the prompt line (the pager's search key; this project
|
|
30
|
+
// already reserves `/` for a meta action): over the History pane's tick rows on History,
|
|
31
|
+
// over the Activity feed's events on Activity. Guarded to those two views and to an empty
|
|
32
|
+
// line, so elsewhere it types into the prompt as today and a director draft is never
|
|
33
|
+
// disturbed. Pre-fills the applied pattern so Enter re-submits it and the operator can
|
|
34
|
+
// edit it; Esc cancels without applying.
|
|
35
|
+
session.settleDraft((t) => (searchSavedInput = t), (n) => (searchSavedCursor = n));
|
|
36
|
+
searchFor = ctx.view === 0 ? "activity" : "history";
|
|
37
|
+
searchMode = true;
|
|
38
|
+
session.held.search = true;
|
|
39
|
+
session.input = (searchFor === "activity" ? ctx.activityGrep : ctx.historyGrep) ?? "";
|
|
40
|
+
session.cursor = session.input.length;
|
|
41
|
+
session.flashMessage(searchFor === "activity" ? "search events: Enter to filter, Esc to cancel" : "search runs: Enter to filter, Esc to cancel");
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (key.name === "escape" && searchMode) {
|
|
45
|
+
// Esc cancels the History search without applying it; the applied pattern is unchanged.
|
|
46
|
+
exitHistorySearch();
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
if (key.name === "return" && searchMode) {
|
|
50
|
+
// Enter applies the trimmed line to the pane the editor opened over: Activity sets
|
|
51
|
+
// activityGrep (an empty line clears it) and resets the scroll to the live tail — the
|
|
52
|
+
// offset indexed the old filtered list; History recomputes its row cache now (the pane
|
|
53
|
+
// is showing) and re-enters at its head. A successful apply restores the saved prompt
|
|
54
|
+
// draft through exitHistorySearch, the shared editor's exit.
|
|
55
|
+
const pattern = session.input.trim();
|
|
56
|
+
if (searchFor === "activity") {
|
|
57
|
+
const applied = pattern === "" ? null : pattern;
|
|
58
|
+
session.applyActivitySearch(applied);
|
|
59
|
+
exitHistorySearch();
|
|
60
|
+
session.flashMessage(applied === null ? "search cleared" : `search: "${applied}"`);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const applied = pattern === "" ? null : pattern;
|
|
64
|
+
session.applyHistorySearch(applied);
|
|
65
|
+
exitHistorySearch();
|
|
66
|
+
session.flashMessage(applied === null ? "search cleared" : `search: "${applied}"`);
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
};
|
|
72
|
+
return { handleKey, exit: exitHistorySearch };
|
|
73
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
2
|
+
import { newPromptHistory, resetPromptRecall, settlePromptRecall, } from "./tui-prompt-history.js";
|
|
3
|
+
/** Build the shared prompt-line session from the disk-side deps the aggregator received. The
|
|
4
|
+
* editor modules then take this session plus each keypress's `TuiEditorCtx`. */
|
|
5
|
+
export function createTuiEditorSession(deps) {
|
|
6
|
+
const session = {
|
|
7
|
+
input: "",
|
|
8
|
+
cursor: 0,
|
|
9
|
+
promptHistory: newPromptHistory(),
|
|
10
|
+
images: [],
|
|
11
|
+
held: {
|
|
12
|
+
budget: false,
|
|
13
|
+
rolePromptFor: null,
|
|
14
|
+
search: false,
|
|
15
|
+
pause: false,
|
|
16
|
+
pauseEditRole: null,
|
|
17
|
+
pauseEditWake: false,
|
|
18
|
+
pauseEditStop: false,
|
|
19
|
+
queued: null,
|
|
20
|
+
answer: null,
|
|
21
|
+
entry: null,
|
|
22
|
+
attach: false,
|
|
23
|
+
notes: null,
|
|
24
|
+
brief: false,
|
|
25
|
+
retire: false,
|
|
26
|
+
quiet: false,
|
|
27
|
+
interval: false,
|
|
28
|
+
},
|
|
29
|
+
root: deps.root,
|
|
30
|
+
now: deps.now,
|
|
31
|
+
flat: deps.flat,
|
|
32
|
+
refreshQueued: deps.refreshQueued,
|
|
33
|
+
refreshNotes: deps.refreshNotes,
|
|
34
|
+
refreshBrief: deps.refreshBrief,
|
|
35
|
+
clearBacklogSelection: deps.clearBacklogSelection,
|
|
36
|
+
applyHistorySearch: deps.applyHistorySearch,
|
|
37
|
+
applyActivitySearch: deps.applyActivitySearch,
|
|
38
|
+
flashMessage: deps.flashMessage,
|
|
39
|
+
flashError: deps.flashError,
|
|
40
|
+
commitBacklogEdit: deps.commitBacklogEdit,
|
|
41
|
+
restorePromptFrom(text, pos) {
|
|
42
|
+
session.input = text;
|
|
43
|
+
session.cursor = pos;
|
|
44
|
+
session.promptHistory = resetPromptRecall(session.promptHistory);
|
|
45
|
+
},
|
|
46
|
+
settleDraft(saveInput, saveCursor) {
|
|
47
|
+
const settled = settlePromptRecall(session.promptHistory, session.input, session.cursor);
|
|
48
|
+
session.promptHistory = settled.history;
|
|
49
|
+
saveInput(settled.text);
|
|
50
|
+
saveCursor(settled.cursor);
|
|
51
|
+
},
|
|
52
|
+
busyFlash(subject) {
|
|
53
|
+
return `finish or cancel the ${subject} first (Esc cancels)`;
|
|
54
|
+
},
|
|
55
|
+
heldEditorFlash(entering) {
|
|
56
|
+
const h = session.held;
|
|
57
|
+
if (h.search)
|
|
58
|
+
return session.busyFlash("search");
|
|
59
|
+
if (h.pause)
|
|
60
|
+
return session.busyFlash("pause");
|
|
61
|
+
if (h.queued)
|
|
62
|
+
return session.busyFlash("inbox edit");
|
|
63
|
+
if (h.answer)
|
|
64
|
+
return session.busyFlash("answer");
|
|
65
|
+
if (h.entry)
|
|
66
|
+
return session.busyFlash("entry edit");
|
|
67
|
+
if (h.rolePromptFor)
|
|
68
|
+
return session.busyFlash(`prompt for ${h.rolePromptFor}`);
|
|
69
|
+
if (h.attach && entering !== "attach")
|
|
70
|
+
return session.busyFlash("image attach");
|
|
71
|
+
if (h.notes && entering !== "notes")
|
|
72
|
+
return session.busyFlash("notes edit");
|
|
73
|
+
if (h.brief && entering !== "brief")
|
|
74
|
+
return session.busyFlash("brief edit");
|
|
75
|
+
if (h.retire && entering !== "retire")
|
|
76
|
+
return session.busyFlash("retire role");
|
|
77
|
+
if (h.quiet && entering !== "quiet")
|
|
78
|
+
return session.busyFlash("quiet hours");
|
|
79
|
+
if (h.interval && entering !== "interval")
|
|
80
|
+
return session.busyFlash("min run interval");
|
|
81
|
+
if (entering === "attach" && h.budget)
|
|
82
|
+
return session.busyFlash("budget edit");
|
|
83
|
+
if (entering === "notes" && (h.attach || h.budget))
|
|
84
|
+
return session.busyFlash(h.attach ? "image attach" : "budget edit");
|
|
85
|
+
if (entering === "brief" && h.budget)
|
|
86
|
+
return session.busyFlash("budget edit");
|
|
87
|
+
if (entering === "retire" && (h.attach || h.notes || h.budget))
|
|
88
|
+
return session.busyFlash(h.attach ? "image attach" : h.notes ? "notes edit" : "budget edit");
|
|
89
|
+
if (entering === "quiet" && (h.attach || h.notes || h.budget || h.brief || h.retire))
|
|
90
|
+
return session.busyFlash(h.attach ? "image attach" : h.notes ? "notes edit" : h.brief ? "brief edit" : h.retire ? "retire role" : "budget edit");
|
|
91
|
+
if (entering === "interval" && (h.attach || h.notes || h.budget || h.brief || h.retire || h.quiet))
|
|
92
|
+
return session.busyFlash(h.attach ? "image attach" : h.notes ? "notes edit" : h.brief ? "brief edit" : h.retire ? "retire role" : h.quiet ? "quiet hours" : "budget edit");
|
|
93
|
+
return null;
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
return session;
|
|
97
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
2
|
+
import { submitPrompt } from "../../inbox/inbox-submit.js";
|
|
3
|
+
import { applyKey } from "./tui-input.js";
|
|
4
|
+
import { pushPromptHistory, recallPromptHistory } from "./tui-prompt-history.js";
|
|
5
|
+
import { createTuiEditorSession } from "./tui-editor-session.js";
|
|
6
|
+
import { createBudgetEditor } from "./tui-editor-budget.js";
|
|
7
|
+
import { createQuietEditor } from "./tui-editor-quiet.js";
|
|
8
|
+
import { createIntervalEditor } from "./tui-editor-interval.js";
|
|
9
|
+
import { createAttachEditor } from "./tui-editor-attach.js";
|
|
10
|
+
import { createNotesEditor } from "./tui-editor-notes.js";
|
|
11
|
+
import { createBriefEditor } from "./tui-editor-brief.js";
|
|
12
|
+
import { createRetireEditor } from "./tui-editor-retire.js";
|
|
13
|
+
import { createPromptEditor } from "./tui-editor-prompt.js";
|
|
14
|
+
import { createPauseEditor } from "./tui-editor-pause.js";
|
|
15
|
+
import { createQueuedEditor } from "./tui-editor-queued.js";
|
|
16
|
+
import { createAnswerEditor } from "./tui-editor-answer.js";
|
|
17
|
+
import { createEntryEditor } from "./tui-editor-entry.js";
|
|
18
|
+
import { createSearchEditor } from "./tui-editor-search.js";
|
|
19
|
+
/** The prompt-line editor half of the createTuiKeys closure: a thin aggregator that builds the
|
|
20
|
+
* shared prompt-line session (tui-editor-session.ts) and the set of mutually-exclusive modal
|
|
21
|
+
* editors on it — budget Ctrl+B, quiet-hours Ctrl+O, role-prompt Ctrl+R, pause
|
|
22
|
+
* Ctrl+F/Ctrl+Q, answer, entry-edit, queued-edit, and the `/` search — each with its own
|
|
23
|
+
* saved-draft pair and exit
|
|
24
|
+
* helper in its own module beside the session. `handleKey` routes each key to the editors in
|
|
25
|
+
* today's priority order (each module returns true when it consumed the key and false to fall
|
|
26
|
+
* through, so the mutually-exclusive/busy-guard ordering is preserved exactly); the session's
|
|
27
|
+
* `held` registry and `heldEditorFlash` hold the exclusive-line invariants once, at the module
|
|
28
|
+
* boundary, instead of being re-stated per editor. The disk/commit responsibilities and the pane
|
|
29
|
+
* state stay on the tui-keys.ts and tui-panes.ts side; this factory receives them as deps and
|
|
30
|
+
* the per-keypress pane reads as ctx. */
|
|
31
|
+
export function createTuiEditors(deps) {
|
|
32
|
+
const session = createTuiEditorSession(deps);
|
|
33
|
+
const editors = [
|
|
34
|
+
createBudgetEditor(session),
|
|
35
|
+
createQuietEditor(session),
|
|
36
|
+
createIntervalEditor(session),
|
|
37
|
+
createAttachEditor(session),
|
|
38
|
+
createNotesEditor(session),
|
|
39
|
+
createPauseEditor(session),
|
|
40
|
+
createPromptEditor(session),
|
|
41
|
+
createQueuedEditor(session),
|
|
42
|
+
createAnswerEditor(session),
|
|
43
|
+
createEntryEditor(session),
|
|
44
|
+
createSearchEditor(session),
|
|
45
|
+
createBriefEditor(session),
|
|
46
|
+
createRetireEditor(session),
|
|
47
|
+
];
|
|
48
|
+
const handleKey = (str, key, ctx) => {
|
|
49
|
+
// Route the key to each editor in today's priority order; each module returns true when it
|
|
50
|
+
// consumed the key (the mutually-exclusive/busy-guard ordering is preserved exactly) and
|
|
51
|
+
// false to fall through to the next.
|
|
52
|
+
for (const editor of editors) {
|
|
53
|
+
if (editor.handleKey(str, key, ctx))
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
// The Enter key submits the director prompt when no modal editor holds the line — the
|
|
57
|
+
// shared/none case of the Enter arm. Only reached when the key was "return" and no editor
|
|
58
|
+
// consumed it; any other unhandled key falls through to the pane arms.
|
|
59
|
+
if (key.name === "return") {
|
|
60
|
+
const prompt = session.input.trim();
|
|
61
|
+
if (!prompt) {
|
|
62
|
+
// Whitespace-only input queues nothing; drop it like an empty line.
|
|
63
|
+
session.input = "";
|
|
64
|
+
session.cursor = 0;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const images = session.images.length > 0 ? session.images : undefined;
|
|
68
|
+
try {
|
|
69
|
+
submitPrompt(session.root, prompt, images);
|
|
70
|
+
session.promptHistory = pushPromptHistory(session.promptHistory, prompt);
|
|
71
|
+
// A successful send consumes the pending image attachments; a thrown submit below
|
|
72
|
+
// keeps them so the operator can resubmit with the images still attached.
|
|
73
|
+
session.images = [];
|
|
74
|
+
session.input = "";
|
|
75
|
+
session.cursor = 0;
|
|
76
|
+
session.flashMessage("added to the director's inbox");
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
// The queue write failed (disk full, permissions): keep the operator's text so
|
|
80
|
+
// it can be resubmitted, and flash the reason — the same contract the GUI's
|
|
81
|
+
// prompt form honors. Clearing the line first would silently lose the prompt,
|
|
82
|
+
// and an unguarded throw would escape the keypress handler and kill the TUI.
|
|
83
|
+
session.flashError(err);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
};
|
|
90
|
+
const onArrow = (direction) => {
|
|
91
|
+
// Up/Down walk the submitted-prompt history the way readline does (tui-prompt-history.ts's
|
|
92
|
+
// recall rules). The backlog pane keeps the arrows for entry browsing (its branch
|
|
93
|
+
// above already returned), and budget, pause and search modes keep them out —
|
|
94
|
+
// recalling a prompt into the cap/field text would be a paste, not a recall. No
|
|
95
|
+
// history in this direction: fall through to applyKey, which ignores arrows as before.
|
|
96
|
+
if (!session.held.budget && !session.held.quiet && !session.held.interval && !session.held.attach && !session.held.pause && !session.held.search && !session.held.notes && !session.held.brief && !session.held.retire) {
|
|
97
|
+
const recalled = recallPromptHistory(session.promptHistory, session.input, session.cursor, direction === "up" ? "older" : "newer");
|
|
98
|
+
if (recalled) {
|
|
99
|
+
session.promptHistory = recalled.history;
|
|
100
|
+
session.input = recalled.text;
|
|
101
|
+
session.cursor = recalled.cursor;
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return false;
|
|
106
|
+
};
|
|
107
|
+
const apply = (str, key) => {
|
|
108
|
+
const next = applyKey(session.input, session.cursor, str, key);
|
|
109
|
+
session.input = next.text;
|
|
110
|
+
session.cursor = next.cursor;
|
|
111
|
+
};
|
|
112
|
+
// Leave every editor (Ctrl+T): view cycling is orthogonal to them, so a cycle never strands
|
|
113
|
+
// an editor's pre-filled text in the prompt line. Each restores its own saved draft.
|
|
114
|
+
const exitAll = () => {
|
|
115
|
+
for (const editor of editors)
|
|
116
|
+
editor.exit();
|
|
117
|
+
};
|
|
118
|
+
const sync = (capUsd, budgetFree) => {
|
|
119
|
+
// Only the budget editor reads the snapshot-fed cap/free flags.
|
|
120
|
+
editors.forEach((editor) => editor.sync?.(capUsd, budgetFree));
|
|
121
|
+
};
|
|
122
|
+
return {
|
|
123
|
+
handleKey,
|
|
124
|
+
onArrow,
|
|
125
|
+
apply,
|
|
126
|
+
exitAll,
|
|
127
|
+
sync,
|
|
128
|
+
state() {
|
|
129
|
+
return {
|
|
130
|
+
input: session.input,
|
|
131
|
+
cursor: session.cursor,
|
|
132
|
+
budgetMode: session.held.budget,
|
|
133
|
+
rolePromptFor: session.held.rolePromptFor,
|
|
134
|
+
pauseEditMode: session.held.pause,
|
|
135
|
+
pauseEditRole: session.held.pauseEditRole,
|
|
136
|
+
pauseEditWake: session.held.pauseEditWake,
|
|
137
|
+
pauseEditStop: session.held.pauseEditStop,
|
|
138
|
+
queuedEditFor: session.held.queued,
|
|
139
|
+
searchMode: session.held.search,
|
|
140
|
+
answerFor: session.held.answer,
|
|
141
|
+
entryEditFor: session.held.entry,
|
|
142
|
+
attachMode: session.held.attach,
|
|
143
|
+
images: session.images.length,
|
|
144
|
+
notesEditFor: session.held.notes,
|
|
145
|
+
briefEditMode: session.held.brief,
|
|
146
|
+
retireMode: session.held.retire,
|
|
147
|
+
quietMode: session.held.quiet,
|
|
148
|
+
intervalMode: session.held.interval,
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
}
|
|
@@ -1,54 +1,39 @@
|
|
|
1
|
-
import { clipSpans } from "../status-render.js";
|
|
1
|
+
import { alertLines, clipSpans, toneLine } from "../status-render.js";
|
|
2
|
+
// Re-export the attention-banner helpers so the TUI's consumers (tui.tsx, tui-pane.ts, and
|
|
3
|
+
// the tui-frame tests) keep importing them from this module; their definitions now live with
|
|
4
|
+
// the other shared status renderers and the `tumwater status` banner block.
|
|
5
|
+
export { alertLines, toneLine };
|
|
2
6
|
import { eventResult } from "../../events/event-format.js";
|
|
7
|
+
import { eventTypeLabel } from "../../events/event-labels.js";
|
|
3
8
|
import { eventKind, resultTone } from "../tone.js";
|
|
9
|
+
import { roleLabel } from "../../roles/role-labels.js";
|
|
4
10
|
import { displayWidth } from "../../text/text-width.js";
|
|
11
|
+
import { plural } from "../../text/phrases.js";
|
|
5
12
|
import { stripTerminalControls } from "../../text/text.js";
|
|
6
|
-
/** A plain line clipped to the pane, in one tone. */
|
|
7
|
-
export function toneLine(text, width, tone) {
|
|
8
|
-
return clipSpans([{ text, ...(tone ? { tone } : {}) }], width);
|
|
9
|
-
}
|
|
10
13
|
/** Strip terminal control characters from every span of a composed frame — the TUI's single
|
|
11
14
|
* terminal boundary, applied in tui.tsx to the whole line list (status table, attention
|
|
12
15
|
* lines, queued-prompt previews, the pane body, flash/hint/input) just before ink draws it.
|
|
13
16
|
* This is what closes the same gap say() closes for the CLI: model-written text (tick
|
|
14
17
|
* summaries, lastError, backlog/QUESTION titles, transcript turns) reaches the terminal
|
|
15
|
-
* through
|
|
18
|
+
* through teamAlerts, renderStatusSpans, and paneBody, not through say(), so sanitizing the
|
|
16
19
|
* final spans — rather than each producer — covers every current and future line builder. */
|
|
17
20
|
export function sanitizeFrameLines(lines) {
|
|
18
21
|
return lines.map((line) => line.map((span) => ({ ...span, text: stripTerminalControls(span.text) })));
|
|
19
22
|
}
|
|
20
|
-
const ALERT_MARKS = {
|
|
21
|
-
red: { mark: "!", tone: "red" },
|
|
22
|
-
amber: { mark: "!", tone: "yellow" },
|
|
23
|
-
indigo: { mark: "?", tone: "magenta" },
|
|
24
|
-
blue: { mark: "i", tone: "blue" },
|
|
25
|
-
gray: { mark: "i", tone: "dim" },
|
|
26
|
-
};
|
|
27
|
-
/** The attention lines under the header: one per alert — its mark and title in its tone, then
|
|
28
|
-
* the detail — at most `max` of them, the rest counted on a last line. */
|
|
29
|
-
export function alertLines(alerts, width, max = 3) {
|
|
30
|
-
const shown = alerts.length > max ? alerts.slice(0, max - 1) : alerts;
|
|
31
|
-
const lines = shown.map((a) => {
|
|
32
|
-
const { mark, tone } = ALERT_MARKS[a.tone];
|
|
33
|
-
const t = tone ? { tone } : {};
|
|
34
|
-
return clipSpans([{ text: `${mark} `, ...t }, { text: a.title, ...t }, { text: a.detail ? ` — ${a.detail}` : "" }], width);
|
|
35
|
-
});
|
|
36
|
-
if (shown.length < alerts.length)
|
|
37
|
-
lines.push(toneLine(` +${alerts.length - shown.length} more — the dashboard lists them all`, width, "dim"));
|
|
38
|
-
return lines;
|
|
39
|
-
}
|
|
40
23
|
/** The pane's tab strip: every view by name, the current one bracketed (and bright), so the
|
|
41
24
|
* Ctrl+T cycle can be seen rather than remembered. */
|
|
42
25
|
export function tabStrip(view, width) {
|
|
43
26
|
const tabs = [
|
|
44
|
-
["activity", view.kind === "activity" && view.role ? `Activity: ${view.role}` : "Activity"],
|
|
45
|
-
["transcript", view.kind === "transcript" ? `${view.note ? "
|
|
27
|
+
["activity", view.kind === "activity" && view.role ? `Activity: ${roleLabel(view.role)}` : "Activity"],
|
|
28
|
+
["transcript", view.kind === "transcript" ? `${view.note ? "Notes" : view.change ? "Change" : "Transcript"}: ${roleLabel(view.role)} (${view.index}/${view.count})` : "Transcript"],
|
|
46
29
|
["backlog", "Backlog"],
|
|
47
30
|
["usage", view.kind === "usage" && view.scope ? `Usage: ${view.scope}` : "Usage"],
|
|
48
31
|
["failures", view.kind === "failures" && view.scope ? `Failures: ${view.scope}` : "Failures"],
|
|
49
|
-
["history", view.kind === "history" ? `${view.trail ? "
|
|
32
|
+
["history", view.kind === "history" ? `${view.trail ? "Run" : "History"}${view.role ? ": " + roleLabel(view.role) : ""}` : "History"],
|
|
50
33
|
["pending", "Pending"],
|
|
51
|
-
["queued", "
|
|
34
|
+
["queued", "Inbox"],
|
|
35
|
+
["health", "Health"],
|
|
36
|
+
["brief", "Brief"],
|
|
52
37
|
];
|
|
53
38
|
const spans = [];
|
|
54
39
|
tabs.forEach(([kind, label], i) => {
|
|
@@ -65,41 +50,53 @@ export function hintLine(view, mode, width) {
|
|
|
65
50
|
if (mode.entryEditFor?.position === null)
|
|
66
51
|
keys = [["Enter", `file ${mode.entryEditFor.kind}`], ["Ctrl+N", `switch to ${mode.entryEditFor.kind === "plan" ? "bug" : "plan"}`], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
67
52
|
else if (mode.entryEditFor)
|
|
68
|
-
keys = [["Enter", "save title"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
53
|
+
keys = [["Enter", "save title"], ["Ctrl+E", "body file"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
69
54
|
else if (mode.answerFor)
|
|
70
55
|
keys = [["Enter", "record answer"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
71
56
|
else if (mode.queuedEditFor)
|
|
72
57
|
keys = [["Enter", "save the edit"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
58
|
+
else if (mode.pauseEditMode && mode.pauseEditStop)
|
|
59
|
+
keys = [["Enter", "schedule stop"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
73
60
|
else if (mode.pauseEditMode)
|
|
74
61
|
keys = [["Enter", mode.pauseEditRole ? `${mode.pauseEditWake ? "wake" : "pause"} ${mode.pauseEditRole}` : "pause"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
75
|
-
else if (mode.
|
|
76
|
-
keys = [["Enter", "
|
|
62
|
+
else if (mode.attachMode)
|
|
63
|
+
keys = [["Enter", "attach image path"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
64
|
+
else if (mode.notesEditFor)
|
|
65
|
+
keys = [["Enter", "replace notes from file"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
66
|
+
else if (mode.retireMode)
|
|
67
|
+
keys = [["Enter", "retire role"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
77
68
|
else if (mode.budget)
|
|
78
69
|
keys = [["Enter", "save the daily cap"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
70
|
+
else if (mode.quietMode)
|
|
71
|
+
keys = [["Enter", "save quiet hours"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
72
|
+
else if (mode.intervalMode)
|
|
73
|
+
keys = [["Enter", "save interval"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
79
74
|
else if (mode.rolePromptFor)
|
|
80
75
|
keys = [["Enter", `send to ${mode.rolePromptFor}`], ["↑↓", "history"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
76
|
+
else if (mode.briefEditMode)
|
|
77
|
+
keys = [["Enter", "replace brief from file"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
81
78
|
else if (view.kind === "transcript") {
|
|
82
|
-
keys = [["Enter", "send"], ["↑↓", "history"], ["Ctrl+R", `prompt ${view.role}`], ["Ctrl+N", view.note ? "change" : view.change ? "transcript" : "
|
|
79
|
+
keys = [["Enter", "send"], ["↑↓", "history"], ["Ctrl+R", `prompt ${view.role}`], ["Ctrl+N", view.note ? "change" : view.change ? "transcript" : "notes"], ...(view.note ? [["Ctrl+X", "clear notes"], ["Ctrl+E", "edit notes"]] : []), ["Ctrl+P", "pause/resume"], ["Ctrl+W", "wake"], ["Ctrl+A", "stop run"], ["Ctrl+V", "reset counters"], ["Ctrl+B", "daily cap"], ["Ctrl+O", "quiet hours"],
|
|
83
80
|
["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
|
|
84
81
|
}
|
|
85
82
|
else if (view.kind === "backlog")
|
|
86
|
-
keys = [["Enter", "send"], ["↑↓", "open entries"], ["Ctrl+X", "cancel entry"], ["Ctrl+E", "edit entry"], ["Ctrl+N", "new plan/bug"], ["Ctrl+A", "answer question"], ["PgUp/PgDn", "scroll"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
|
|
83
|
+
keys = [["Enter", "send"], ["↑↓", "open entries"], ["Ctrl+X", "cancel entry"], ["Ctrl+R", "release entry"], ["Ctrl+V", "top entry"], ["Ctrl+E", "edit entry"], ["Ctrl+N", "new plan/bug"], ["Ctrl+A", "answer question"], ["PgUp/PgDn", "scroll"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
|
|
87
84
|
else if (view.kind === "usage" || view.kind === "failures")
|
|
88
|
-
keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+G", view.scope ? `scope: ${view.scope}` : "scope:
|
|
85
|
+
keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+G", view.scope ? `scope: ${view.scope}` : "scope: team"], ["Ctrl+E", view.window ? `window: ${view.window}` : "window: default"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
|
|
89
86
|
else if (view.kind === "history")
|
|
90
|
-
keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+G", view.role ? `scope: ${view.role}` : "scope:
|
|
87
|
+
keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+G", view.role ? `scope: ${view.role}` : "scope: team"], ["Ctrl+S", view.since ? `window: ${view.since}` : "window: default"], ["Ctrl+O", view.result ? `result: ${view.result}` : "result: all"], ["Ctrl+Y", view.summary ? "rows" : "summary"], ["Ctrl+N", view.trail ? "history" : "run trail"], ["/", view.grep ? `search: ${view.grep}` : "search"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
|
|
91
88
|
else if (view.kind === "pending")
|
|
92
|
-
keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
|
|
89
|
+
keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+R", "refresh"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
|
|
90
|
+
else if (view.kind === "health")
|
|
91
|
+
keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+O", view.problems ? "problems only" : "all checks"], ["Ctrl+R", "re-run checks"], ["Ctrl+E", "reclaim disk"], ["Ctrl+N", "test notify"], ["Ctrl+X", "retire role"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
|
|
92
|
+
else if (view.kind === "brief")
|
|
93
|
+
keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+E", "edit brief"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
|
|
93
94
|
else if (view.kind === "queued")
|
|
94
|
-
keys = [["Enter", "send"], ["↑↓", "history"], ["Ctrl+E", "edit"], ["Ctrl+X", "cancel"], ["PgUp/PgDn", "scroll"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
|
|
95
|
+
keys = [["Enter", "send"], ["↑↓", "history"], ["Ctrl+V", "top"], ["Ctrl+E", "edit"], ["Ctrl+X", "cancel"], ["PgUp/PgDn", "scroll"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
|
|
95
96
|
else
|
|
96
|
-
keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+B", "daily cap"], ["Ctrl+G", view.role ? `scope: ${view.role}` : "scope:
|
|
97
|
-
// The
|
|
98
|
-
//
|
|
99
|
-
// when one is set, otherwise the way to open it. The mode branches above already name
|
|
100
|
-
// their own keys and keep the line, so this only rides the view hints.
|
|
101
|
-
if (!mode.deferMode && !mode.pauseEditMode && !mode.budget && !mode.rolePromptFor && !mode.queuedEditFor && !mode.answerFor && !mode.entryEditFor)
|
|
102
|
-
keys.push(["Ctrl+L", mode.defer ? `defer: ${mode.defer}` : "defer"]);
|
|
97
|
+
keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+B", "daily cap"], ["Ctrl+O", view.type ? `type: ${eventTypeLabel(view.type)}` : "type: all"], ["Ctrl+G", view.role ? `scope: ${view.role}` : "scope: team"], ["/", view.grep ? `search: ${view.grep}` : "search"], ["Ctrl+T", "next view"], ["Ctrl+F", "pause team"], ["Ctrl+Q", "stop later"], ["Ctrl+C", "interrupt director"], ["Ctrl+V", "attach image"], ["Ctrl+R", "restart"], ["Ctrl+D", "quit"]];
|
|
98
|
+
// The mode branches above name their own keys and keep the line, so every remaining
|
|
99
|
+
// view hint always shows the send affordance. This comment marks that ordering.
|
|
103
100
|
const spans = [];
|
|
104
101
|
keys.forEach(([key, what], i) => {
|
|
105
102
|
if (i > 0)
|
|
@@ -112,37 +109,52 @@ export function hintLine(view, mode, width) {
|
|
|
112
109
|
export function promptPrefix(mode) {
|
|
113
110
|
if (mode.budget)
|
|
114
111
|
return [{ text: "daily cap $", tone: "brand" }, { text: " " }];
|
|
112
|
+
if (mode.quietMode)
|
|
113
|
+
return [{ text: "quiet hours", tone: "brand" }, { text: " › " }];
|
|
114
|
+
if (mode.intervalMode)
|
|
115
|
+
return [{ text: "min run interval", tone: "brand" }, { text: " › " }];
|
|
116
|
+
if (mode.briefEditMode)
|
|
117
|
+
return [{ text: "brief", tone: "brand" }, { text: " › " }];
|
|
118
|
+
if (mode.retireMode)
|
|
119
|
+
return [{ text: "retire", tone: "brand" }, { text: " › " }];
|
|
120
|
+
if (mode.notesEditFor)
|
|
121
|
+
return [{ text: `notes for ${mode.notesEditFor}`, tone: "brand" }, { text: " › " }];
|
|
115
122
|
if (mode.entryEditFor)
|
|
116
123
|
return [{ text: mode.entryEditFor.position === null ? `new ${mode.entryEditFor.kind}` : `edit ${mode.entryEditFor.kind} #${mode.entryEditFor.position}`, tone: "brand" }, { text: " › " }];
|
|
117
124
|
if (mode.answerFor)
|
|
118
125
|
return [{ text: `answer #${mode.answerFor.position}`, tone: "brand" }, { text: " › " }];
|
|
119
126
|
if (mode.queuedEditFor)
|
|
120
127
|
return [{ text: `edit ${mode.queuedEditFor.role}`, tone: "brand" }, { text: " › " }];
|
|
128
|
+
if (mode.pauseEditMode && mode.pauseEditStop)
|
|
129
|
+
return [{ text: "stop in", tone: "brand" }, { text: " › " }];
|
|
121
130
|
if (mode.pauseEditMode)
|
|
122
131
|
return [{ text: mode.pauseEditRole ? `${mode.pauseEditWake ? "wake" : "pause"} ${mode.pauseEditRole}` : "pause", tone: "brand" }, { text: " › " }];
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
|
|
132
|
+
// The pending image count (e.g. `director (2 images) › `) is named beside the target, so
|
|
133
|
+
// the user sees what the next Enter will send before pressing it. The image count names the
|
|
134
|
+
// director prompt only: session.images ride into the next director submit (submitPrompt),
|
|
135
|
+
// while a role prompt (submitRolePromptAndWake) passes images undefined, so the count must
|
|
136
|
+
// not claim an attachment a role-targeted Enter won't send.
|
|
137
|
+
const target = mode.rolePromptFor ?? "director";
|
|
138
|
+
const withImages = mode.rolePromptFor === null && (mode.images ?? 0) > 0;
|
|
139
|
+
const named = withImages ? `${target} (${plural(mode.images ?? 0, "image")})` : target;
|
|
140
|
+
return [{ text: named, tone: "brand" }, { text: " › " }];
|
|
129
141
|
}
|
|
130
142
|
/** The prefix's width in terminal columns, for sizing the input beside it. */
|
|
131
143
|
export function prefixWidth(prefix) {
|
|
132
144
|
return displayWidth(prefix.map((sp) => sp.text).join(""));
|
|
133
145
|
}
|
|
134
146
|
/** An activity line's tone, by the event's kind (tone.ts's eventKind — the dashboard's
|
|
135
|
-
* activity card uses the same rule):
|
|
147
|
+
* activity card uses the same rule): merges green, problems in their outcome's color,
|
|
136
148
|
* questions magenta, routine bookkeeping dim. */
|
|
137
149
|
export function eventTone(e) {
|
|
138
150
|
const result = eventResult(e);
|
|
139
151
|
const kind = eventKind(e.type, result);
|
|
140
152
|
if (kind === "landing")
|
|
141
|
-
return "green";
|
|
153
|
+
return "green"; // vocabulary-ok: event kind code
|
|
142
154
|
if (kind === "attention")
|
|
143
155
|
return "magenta";
|
|
144
156
|
if (kind === "problem")
|
|
145
|
-
return (e.type === "
|
|
157
|
+
return (e.type === "run_end" ? resultTone(result) : undefined) ?? "red";
|
|
146
158
|
if (kind === "routine")
|
|
147
159
|
return "dim";
|
|
148
160
|
return undefined;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
/** The TUI's History pane body: the completed-tick rows `tumwater history` prints (and the
|
|
2
3
|
* dashboard's History view renders), one line each, newest first. history-data.ts already
|
|
3
4
|
* builds the rows (`readTickRows`); this only renders them for the pane, so the TUI, the CLI,
|
|
@@ -6,19 +7,23 @@
|
|
|
6
7
|
* rows and their fields, not the CLI table's padding. Pure: a function of the rows it is
|
|
7
8
|
* handed. */
|
|
8
9
|
import { tickRowCells } from "../../history/history-data.js";
|
|
9
|
-
/** The pane body for a set of tick rows: `time loop #tick result duration`, then `
|
|
10
|
-
* and `detail` when they carry anything,
|
|
11
|
-
* history-data.ts's tickRowCells, the one
|
|
12
|
-
*
|
|
13
|
-
* same way; an empty `
|
|
10
|
+
/** The pane body for a set of tick rows: `time loop #tick result duration`, then `model`
|
|
11
|
+
* (between the result and the duration), and `usage` and `detail` when they carry anything,
|
|
12
|
+
* joined newest-first by newlines. The cells come from history-data.ts's tickRowCells, the one
|
|
13
|
+
* home the CLI's table builds on too, so a null `durationMs` (the tick's start sits outside
|
|
14
|
+
* the scanned window) shows the em dash here the same way; an empty `model` (the start named
|
|
15
|
+
* none or sat outside the window), an empty `usage` (neither tokens nor cost), and an empty
|
|
14
16
|
* `detail` are omitted rather than leaving a gap. No rows renders `""`, which the pane's
|
|
15
17
|
* `emptyNote` replaces. `row.result` is the string history-data.ts resolved (including a
|
|
16
|
-
* joined
|
|
18
|
+
* joined merge verdict), never reclassified here. */
|
|
17
19
|
export function historyPaneText(rows) {
|
|
18
20
|
return rows
|
|
19
21
|
.map((row) => {
|
|
20
22
|
const { duration } = tickRowCells(row);
|
|
21
|
-
const parts = [row.time, `${row.
|
|
23
|
+
const parts = [row.time, `${row.role} #${row.run}`, row.result];
|
|
24
|
+
if (row.model)
|
|
25
|
+
parts.push(row.model);
|
|
26
|
+
parts.push(duration);
|
|
22
27
|
if (row.usage)
|
|
23
28
|
parts.push(row.usage);
|
|
24
29
|
if (row.detail)
|