tumwater 0.1.3 → 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 +63 -28
- 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-eligibility.js +332 -43
- package/dist/src/backlog/backlog-md.js +79 -17
- 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 +178 -441
- 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 +32 -30
- package/dist/src/{brief-edit.js → brief/brief-edit.js} +12 -11
- package/dist/src/{brief.js → brief/brief.js} +33 -8
- 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 +13 -11
- package/dist/src/change/change-render.js +11 -10
- package/dist/src/cli/brief-command.js +9 -7
- package/dist/src/cli/cli-args.js +25 -5
- package/dist/src/cli/cli-command-args.js +43 -10
- package/dist/src/cli/cli-editor.js +13 -1
- package/dist/src/cli/cli-flag-specs.js +31 -6
- package/dist/src/cli/cli-marker-commands.js +13 -10
- package/dist/src/cli/cli-query-commands.js +439 -56
- package/dist/src/cli/cli-repo-gate.js +1 -1
- package/dist/src/cli/cli-run.js +124 -68
- package/dist/src/cli/completion.js +70 -12
- package/dist/src/cli/config-commands.js +14 -11
- package/dist/src/cli/help.js +264 -124
- 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 +13 -224
- package/dist/src/cli.js +78 -36
- 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 +107 -325
- 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 +494 -68
- package/dist/src/doctor/doctor-checks.js +509 -58
- package/dist/src/doctor/doctor-holds.js +139 -78
- 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 +2 -3
- package/dist/src/doctor/doctor-orphans.js +10 -9
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +76 -25
- package/dist/src/doctor/image-probe.js +244 -0
- package/dist/src/doctor/probe.js +64 -0
- package/dist/src/events/event-format.js +219 -106
- 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 +80 -30
- 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 +6 -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 +79 -59
- 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 -716
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +105 -54
- 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 +25 -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 +179 -104
- package/dist/src/history/history-render.js +76 -0
- package/dist/src/history/history.js +29 -70
- package/dist/src/inbox/inbox-attachments.js +66 -10
- 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 +114 -73
- 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 +104 -50
- package/dist/src/loop/loop-recovery.js +15 -15
- package/dist/src/loop/loop-state.js +130 -13
- package/dist/src/loop/loop-tick.js +190 -88
- package/dist/src/loop/loop.js +68 -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 -20
- 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 +113 -45
- package/dist/src/operator/operator-intent.js +74 -53
- package/dist/src/operator/operator-requests.js +52 -52
- package/dist/src/operator/retire.js +47 -28
- package/dist/src/operator/revert.js +71 -0
- package/dist/src/orchestrator/orchestrator-launch.js +74 -14
- package/dist/src/orchestrator/orchestrator-scheduling.js +74 -43
- package/dist/src/orchestrator/orchestrator.js +149 -96
- 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 +124 -38
- 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 +4 -3
- 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 +36 -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 +29 -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 +259 -254
- package/dist/src/report/report-fold.js +353 -0
- package/dist/src/report/report-render.js +235 -44
- package/dist/src/request-timeouts.js +1 -1
- package/dist/src/review/known-flakes.js +34 -14
- package/dist/src/review/protected-edits.js +4 -3
- 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 +55 -34
- 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 +109 -131
- 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 +16 -14
- package/dist/src/scheduling/backoff.js +29 -19
- package/dist/src/scheduling/claims.js +15 -8
- package/dist/src/scheduling/once-round.js +11 -11
- package/dist/src/scheduling/quiet-hours.js +25 -32
- package/dist/src/scheduling/scheduling.js +28 -25
- 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 +72 -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 +50 -39
- 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 +84 -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} +39 -38
- 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 +142 -25
- 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 +23 -22
- package/dist/src/tick/tick-detail.js +37 -24
- 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 +67 -53
- 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 +75 -10
- package/dist/src/ui/badges.js +113 -69
- package/dist/src/ui/gui/gui-client-boot.js +202 -29
- 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 +94 -37
- package/dist/src/ui/gui/gui-client-labels.js +24 -0
- package/dist/src/ui/gui/gui-client-loops.js +90 -47
- package/dist/src/ui/gui/gui-client-markdown.js +1 -0
- package/dist/src/ui/gui/gui-client-model.js +60 -43
- package/dist/src/ui/gui/gui-client-operator.js +73 -43
- package/dist/src/ui/gui/gui-client-pending.js +34 -31
- package/dist/src/ui/gui/gui-client-report.js +330 -47
- package/dist/src/ui/gui/gui-client-settings.js +126 -18
- package/dist/src/ui/gui/gui-client-sound.js +2 -1
- package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +190 -74
- package/dist/src/ui/gui/gui-client.js +26 -20
- package/dist/src/ui/gui/gui-page.js +18 -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 +176 -102
- package/dist/src/ui/status-payload.js +114 -36
- package/dist/src/ui/status-render.js +121 -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 +9 -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 +61 -57
- package/dist/src/ui/tui/tui-history.js +12 -7
- package/dist/src/ui/tui/tui-input.js +44 -50
- package/dist/src/ui/tui/tui-keymap.js +1 -0
- package/dist/src/ui/tui/tui-keys.js +87 -1821
- 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 +52 -28
- package/dist/src/ui/tui/tui-panes.js +294 -0
- package/dist/src/ui/tui/tui.js +44 -26
- package/dist/src/verdict/fix-claim.js +56 -33
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +43 -11
- 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 -56
- package/dist/src/landing/landing-slot.js +0 -256
- 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,230 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
2
|
+
import { bodyStructureProblem, fileEntryWithBody, OPERATOR_FILE_KINDS, splitEntryFile, tryEditEntry } from "../../backlog/backlog-write.js";
|
|
3
|
+
import { stripEntryStamp } from "../../backlog/backlog-structure.js";
|
|
4
|
+
import { readTextIfExists } from "../../files/files.js";
|
|
5
|
+
import { submitRolePromptAndWake } from "../../operator/operator-intent.js";
|
|
6
|
+
import { promptLengthProblem } from "../../inbox/inbox-submit.js";
|
|
7
|
+
import { collapseWhitespace } from "../../text/text.js";
|
|
8
|
+
/** The backlog pane's title editor (Ctrl+N new entry, Ctrl+E on a selected plan or bug) on the
|
|
9
|
+
* shared prompt line. It owns its target (`entryEditFor`), its plan↔bug flip, its saved-draft
|
|
10
|
+
* pair, its exit helper, and the fileEntryWithBody / tryEditEntry / commit arms, plus the
|
|
11
|
+
* position math that counts `plan`/`bug` labels. Mutually exclusive with the other editors via
|
|
12
|
+
* the session's held registry. */
|
|
13
|
+
export function createEntryEditor(session) {
|
|
14
|
+
let entryEditFor = null;
|
|
15
|
+
let entryEditSavedInput = "";
|
|
16
|
+
let entryEditSavedCursor = 0;
|
|
17
|
+
// The triple state of the entry editor's prompt line: false (the title field), true (the
|
|
18
|
+
// file-path field whose contents replace the whole entry), and the draft the toggle parks
|
|
19
|
+
// when it hands the line to a different field.
|
|
20
|
+
let entryBodyFile = false;
|
|
21
|
+
let entryBodySavedInput = "";
|
|
22
|
+
let entryBodySavedCursor = 0;
|
|
23
|
+
// Leave the backlog pane's title editor (Esc, a saved edit, or Ctrl+T): the same settle
|
|
24
|
+
// contract as exitAnswerMode. Any open file mode is dropped too, so the editor always lands
|
|
25
|
+
// back in the title state on the next Ctrl+E.
|
|
26
|
+
const exitEntryEdit = () => {
|
|
27
|
+
if (!entryEditFor)
|
|
28
|
+
return;
|
|
29
|
+
entryEditFor = null;
|
|
30
|
+
entryBodyFile = false;
|
|
31
|
+
session.held.entry = null;
|
|
32
|
+
session.restorePromptFrom(entryEditSavedInput, entryEditSavedCursor);
|
|
33
|
+
};
|
|
34
|
+
const handleKey = (_str, key, ctx) => {
|
|
35
|
+
if (key.ctrl && key.name === "n") {
|
|
36
|
+
// On the Backlog view Ctrl+N files a new entry: it opens the title editor with a null
|
|
37
|
+
// position (new plan), and pressed again inside that editor flips plan <-> bug.
|
|
38
|
+
if (entryEditFor !== null && entryEditFor.position === null) {
|
|
39
|
+
const other = entryEditFor.kind === "plan" ? "bug" : "plan";
|
|
40
|
+
entryEditFor = { ...entryEditFor, kind: other };
|
|
41
|
+
session.held.entry = entryEditFor;
|
|
42
|
+
session.flashMessage(`new ${other} — Enter to file, Ctrl+N switches to ${other === "plan" ? "bug" : "plan"}, Esc cancels`);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
// A Ctrl+N on any other state (not the Backlog view's new-entry slot, or an editor
|
|
46
|
+
// already holding the line) is the notebook/change/trail cycle, a pane arm the outer
|
|
47
|
+
// dispatch handles — this factory returns false so it runs.
|
|
48
|
+
if (ctx.view === ctx.roleIds.length + 1 &&
|
|
49
|
+
!session.held.budget && !session.held.rolePromptFor && !session.held.search &&
|
|
50
|
+
!session.held.pause && !session.held.queued &&
|
|
51
|
+
!session.held.answer && !entryEditFor && !session.held.retire) {
|
|
52
|
+
session.settleDraft((t) => (entryEditSavedInput = t), (n) => (entryEditSavedCursor = n));
|
|
53
|
+
entryEditFor = { kind: "plan", position: null, title: "" };
|
|
54
|
+
session.held.entry = entryEditFor;
|
|
55
|
+
session.input = "";
|
|
56
|
+
session.cursor = 0;
|
|
57
|
+
session.flashMessage("new plan — Enter to file, Ctrl+N switches to bug, Esc cancels");
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
if (key.ctrl && key.name === "e" && entryEditFor !== null && entryEditFor.position !== null) {
|
|
63
|
+
// A second Ctrl+E inside the entry editor toggles the same prompt line into the file
|
|
64
|
+
// field, whose path's contents replace the whole entry (splitEntryFile's rule) through
|
|
65
|
+
// tryEditEntry — the terminal twin of `tumwater plan|bug --edit <n> --file <path>`. A
|
|
66
|
+
// third Ctrl+E returns to the title draft, parked in the saved pair; Esc cancels all.
|
|
67
|
+
entryBodyFile = !entryBodyFile;
|
|
68
|
+
if (entryBodyFile) {
|
|
69
|
+
entryBodySavedInput = session.input;
|
|
70
|
+
entryBodySavedCursor = session.cursor;
|
|
71
|
+
session.input = "";
|
|
72
|
+
session.cursor = 0;
|
|
73
|
+
session.flashMessage(`replace ${entryEditFor.kind} #${entryEditFor.position} from a file — Enter to save, Ctrl+E back to the title, Esc cancels`);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
session.input = entryBodySavedInput;
|
|
77
|
+
session.cursor = entryBodySavedCursor;
|
|
78
|
+
session.flashMessage(`editing ${entryEditFor.kind} #${entryEditFor.position} title — Enter to save, Ctrl+E to replace from a file, Esc cancels`);
|
|
79
|
+
}
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
if (key.ctrl && key.name === "e" &&
|
|
83
|
+
!session.held.budget && !session.held.rolePromptFor && !session.held.search &&
|
|
84
|
+
!session.held.pause && !session.held.queued &&
|
|
85
|
+
!session.held.answer && !entryEditFor && !session.held.retire &&
|
|
86
|
+
ctx.view === ctx.roleIds.length + 1 && ctx.selectedEntry !== null) {
|
|
87
|
+
// Ctrl+E opens the title editor on the selected plan or bug (mnemonic: **E**dit); Enter
|
|
88
|
+
// saves through tryEditEntry, the non-exiting core the dashboard drawer shares, keeping
|
|
89
|
+
// the body and stamp. The prompt line is single-line, so only the title is editable here.
|
|
90
|
+
try {
|
|
91
|
+
const entries = session.flat();
|
|
92
|
+
const sel = Math.min(ctx.selectedEntry, entries.length - 1);
|
|
93
|
+
const picked = entries[sel];
|
|
94
|
+
if (picked === undefined) {
|
|
95
|
+
// an emptied list has nothing to edit
|
|
96
|
+
}
|
|
97
|
+
else if (picked.label === "done" || picked.label === "fixed" || picked.label === "answered") {
|
|
98
|
+
session.flashMessage("a done plan, fixed bug or answered question is read-only");
|
|
99
|
+
}
|
|
100
|
+
else if (picked.label === "question") {
|
|
101
|
+
session.flashMessage("only a plan or open bug can be edited here");
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const kind = picked.label === "plan" ? "plan" : "bug";
|
|
105
|
+
const position = kind === "plan" ? sel + 1 : sel - entries.filter((e) => e.label === "plan").length + 1;
|
|
106
|
+
session.settleDraft((t) => (entryEditSavedInput = t), (n) => (entryEditSavedCursor = n));
|
|
107
|
+
entryEditFor = { kind, position, title: picked.title };
|
|
108
|
+
session.held.entry = entryEditFor;
|
|
109
|
+
session.input = stripEntryStamp(picked.title);
|
|
110
|
+
session.cursor = session.input.length;
|
|
111
|
+
session.flashMessage(`editing ${kind} #${position} — Enter to save, Esc to cancel`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
session.flashError(err);
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
if (key.name === "escape" && entryEditFor) {
|
|
120
|
+
// Esc cancels the title editor, restoring the previous prompt text; nothing is written.
|
|
121
|
+
const filing = entryEditFor.position === null;
|
|
122
|
+
exitEntryEdit();
|
|
123
|
+
session.flashMessage(filing ? "new entry cancelled" : "edit cancelled");
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
if (key.name === "return" && entryEditFor) {
|
|
127
|
+
// Enter saves the new title through tryEditEntry with the body re-read now, so a
|
|
128
|
+
// concurrent body edit is not clobbered by a stale copy. The entry is matched by flat
|
|
129
|
+
// index and title; a mismatch is treated as moved without writing. A blank title and a
|
|
130
|
+
// throw flash and STAY in the editor so the text survives.
|
|
131
|
+
const target = entryEditFor;
|
|
132
|
+
// Shared tail of the title and file edit arms: re-read the entry by flat index and
|
|
133
|
+
// title, guard against a concurrent move (so a mismatch is treated as moved, not
|
|
134
|
+
// written), then settle the editor and commit through tryEditEntry. The only thing
|
|
135
|
+
// the two arms pass differently is the (title, body) pair taking the entry's place;
|
|
136
|
+
// an omitted body preserves the matched entry's own body (the title edit arm).
|
|
137
|
+
const applyEntryEdit = (newTitle, newBody) => {
|
|
138
|
+
try {
|
|
139
|
+
// Both call sites (the file arm's and the title arm's Entry-save paths) run only
|
|
140
|
+
// when `target.position` is non-null, so the position math never sees a null.
|
|
141
|
+
const position = target.position;
|
|
142
|
+
const entries = session.flat();
|
|
143
|
+
const plans = entries.filter((e) => e.label === "plan").length;
|
|
144
|
+
const entry = entries[target.kind === "plan" ? position - 1 : plans + position - 1];
|
|
145
|
+
const outcome = entry?.label === target.kind && entry.title === target.title
|
|
146
|
+
? tryEditEntry(session.root, target.kind, position, target.title, newTitle, newBody ?? entry.body)
|
|
147
|
+
: { status: "moved", title: "" };
|
|
148
|
+
exitEntryEdit();
|
|
149
|
+
if (outcome.status === "edited") {
|
|
150
|
+
session.commitBacklogEdit(outcome.file, `Edit ${target.kind}: ${outcome.title}`);
|
|
151
|
+
session.flashMessage(`edited ${target.kind} #${target.position}: ${outcome.title}`);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
session.clearBacklogSelection();
|
|
155
|
+
session.flashMessage(`that ${target.kind} was already removed or moved — nothing edited`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
session.flashError(err);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
if (entryBodyFile && target.position !== null) {
|
|
163
|
+
// File mode: the line holds a path whose contents replace title and body through the
|
|
164
|
+
// same title-checked tryEditEntry core the title edit uses, keeping the entry's
|
|
165
|
+
// position and its (planned …)/(found by …) stamp. A missing file, an empty path, a
|
|
166
|
+
// file with no title line, and a structurally unsound body all flash and stay here.
|
|
167
|
+
const p = session.input.trim();
|
|
168
|
+
if (!p) {
|
|
169
|
+
session.flashMessage("a file path is required");
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
let text;
|
|
173
|
+
try {
|
|
174
|
+
text = readTextIfExists(p);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
session.flashError(err);
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
if (text === null) {
|
|
181
|
+
session.flashMessage(`no such file: ${p}`);
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
const { title: rawTitle, body } = splitEntryFile(text);
|
|
185
|
+
const newTitle = stripEntryStamp(rawTitle);
|
|
186
|
+
if (!newTitle) {
|
|
187
|
+
session.flashMessage("the file needs a title line");
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
const problem = bodyStructureProblem(body);
|
|
191
|
+
if (problem !== null) {
|
|
192
|
+
session.flashMessage(problem);
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
applyEntryEdit(newTitle, body);
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
if (!session.input.trim()) {
|
|
199
|
+
session.flashMessage("an entry needs a title");
|
|
200
|
+
}
|
|
201
|
+
else if (target.position === null) {
|
|
202
|
+
// New entry (Ctrl+N): file by title with an empty body and wake its loop, the same
|
|
203
|
+
// steps handleBacklogFile takes; a length problem or a throw stays in the editor.
|
|
204
|
+
try {
|
|
205
|
+
const { role, wakeText } = OPERATOR_FILE_KINDS[target.kind];
|
|
206
|
+
const problem = promptLengthProblem(wakeText(collapseWhitespace(session.input)), role);
|
|
207
|
+
if (problem) {
|
|
208
|
+
session.flashMessage(problem);
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
const filed = fileEntryWithBody(session.root, target.kind, session.input, "");
|
|
212
|
+
session.commitBacklogEdit(filed.file, `File ${target.kind}: ${filed.title}`);
|
|
213
|
+
submitRolePromptAndWake(session.root, role, wakeText(filed.title));
|
|
214
|
+
exitEntryEdit();
|
|
215
|
+
session.flashMessage(`filed ${target.kind}: ${filed.title} — woke ${role}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
catch (err) {
|
|
219
|
+
session.flashError(err);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
applyEntryEdit(session.input);
|
|
224
|
+
}
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
return false;
|
|
228
|
+
};
|
|
229
|
+
return { handleKey, exit: exitEntryEdit };
|
|
230
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
2
|
+
import { setConfigKey, unsetConfigKey } from "../../config/config-write.js";
|
|
3
|
+
import { checkMinRunInterval } from "../../config/config-keys.js";
|
|
4
|
+
import { baseRoleOf } from "../../roles/loop-ids.js";
|
|
5
|
+
import { rawRoleField } from "../../roles/role-view.js";
|
|
6
|
+
/** The Ctrl+S min-run-interval editor on the shared prompt line, the terminal twin of
|
|
7
|
+
* `tumwater role <id> --min-run-interval` and the dashboard role drawer's `Min run interval`
|
|
8
|
+
* row. It owns its mode flag, its saved-draft pair, and the role pinned when it opened, so a
|
|
9
|
+
* roster shift cannot redirect the write. Enter writes through the shared
|
|
10
|
+
* setConfigKey/unsetConfigKey family the CLI and the drawer use — so checkMinRunInterval
|
|
11
|
+
* (naming `roles.<base>.minRunIntervalSeconds`) and validateConfig own the value's truth — and
|
|
12
|
+
* Esc, Ctrl+S again, and Ctrl+T cancel by restoring the prompt draft. Unlike the quiet editor
|
|
13
|
+
* there is no director refusal: `tumwater role director --min-run-interval` is accepted, so the
|
|
14
|
+
* director edits too. Mutually exclusive with the other editors on the one prompt line via the
|
|
15
|
+
* session's held registry. */
|
|
16
|
+
export function createIntervalEditor(session) {
|
|
17
|
+
let intervalMode = false;
|
|
18
|
+
let savedInput = "";
|
|
19
|
+
let savedCursor = 0;
|
|
20
|
+
// The role pinned when the editor opened, so Enter writes the same role's override even if
|
|
21
|
+
// syncRoster shrinks or reorders the roster while the editor is open.
|
|
22
|
+
let lockedRole = null;
|
|
23
|
+
// Leave min-run-interval mode (Esc, Ctrl+S again, or Ctrl+T): restore the saved prompt text.
|
|
24
|
+
const exitIntervalMode = () => {
|
|
25
|
+
if (!intervalMode)
|
|
26
|
+
return;
|
|
27
|
+
intervalMode = false;
|
|
28
|
+
session.held.interval = false;
|
|
29
|
+
session.restorePromptFrom(savedInput, savedCursor);
|
|
30
|
+
lockedRole = null;
|
|
31
|
+
};
|
|
32
|
+
const handleKey = (_str, key, ctx) => {
|
|
33
|
+
if (key.ctrl && key.name === "s") {
|
|
34
|
+
// Ctrl+S edits the viewed role's own min-run-interval override on its Transcript view.
|
|
35
|
+
// Every non-transcript view keeps Ctrl+S — the guard below lets the key fall through to
|
|
36
|
+
// the pane arms (History's trailing-window cycle) unless the interval editor already
|
|
37
|
+
// holds the line, in which case Ctrl+S is the toggle-out arm. Entering pre-fills the
|
|
38
|
+
// override from the FILE's raw `roles.<base>.minRunIntervalSeconds` (empty when unset —
|
|
39
|
+
// empty means "clear it, fall back to the team-wide interval" on save) and saves the
|
|
40
|
+
// prompt text; leaving restores it. There is no director exemption: the director ticks
|
|
41
|
+
// on the same clock, so `--min-run-interval` is accepted for it too.
|
|
42
|
+
if (!intervalMode && !(ctx.view >= 1 && ctx.view <= ctx.roleIds.length))
|
|
43
|
+
return false;
|
|
44
|
+
const role = ctx.roleIds[ctx.view - 1];
|
|
45
|
+
const held = session.heldEditorFlash("interval");
|
|
46
|
+
if (held) {
|
|
47
|
+
// Mutually exclusive with the other editors: all share one prompt line, so a mode may
|
|
48
|
+
// not enter while another holds it. Flash the way out first.
|
|
49
|
+
session.flashMessage(held);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
if (intervalMode) {
|
|
53
|
+
exitIntervalMode();
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
// Pin the role first so the write below is safe against a mid-edit roster shift, then
|
|
57
|
+
// pre-fill from the file's own entry — one fresh read per open is the smallest honest
|
|
58
|
+
// source; a broken config degrades to no pre-fill and the write reports the error. The
|
|
59
|
+
// FILE (rawRoleField) is the source of truth, not the merged config: overlayDefaults
|
|
60
|
+
// seeds every built-in role's minRunIntervalSeconds from the catalog, so the merged
|
|
61
|
+
// value cannot tell an operator override from a seeded default, and clearing a role that
|
|
62
|
+
// only has the seed would otherwise be a no-op the operator could not see.
|
|
63
|
+
lockedRole = role;
|
|
64
|
+
const base = baseRoleOf(role);
|
|
65
|
+
const existing = rawRoleField(session.root, base, "minRunIntervalSeconds");
|
|
66
|
+
session.settleDraft((t) => (savedInput = t), (n) => (savedCursor = n));
|
|
67
|
+
intervalMode = true;
|
|
68
|
+
session.held.interval = true;
|
|
69
|
+
session.input = typeof existing === "number" ? String(existing) : "";
|
|
70
|
+
session.cursor = session.input.length;
|
|
71
|
+
session.flashMessage(`edit ${role}'s min run interval in seconds (0 allows back-to-back runs, empty clears): Enter to save, Esc to cancel`);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
if (key.name === "escape" && intervalMode) {
|
|
75
|
+
// Esc cancels min-run-interval mode, restoring the previous prompt text.
|
|
76
|
+
exitIntervalMode();
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
if (key.name === "return" && intervalMode) {
|
|
80
|
+
// Enter commits the override through the shared setter family the CLI's
|
|
81
|
+
// `--min-run-interval` and the dashboard drawer use: an empty or whitespace-only line
|
|
82
|
+
// clears the key (`unsetConfigKey`), otherwise checkMinRunInterval (naming the dotted
|
|
83
|
+
// key) screens the value and `setConfigKey` writes `roles.<base>.minRunIntervalSeconds`
|
|
84
|
+
// as a JSON number. An invalid or rejected value flashes and STAYS in edit mode so the
|
|
85
|
+
// operator can fix it; success restores the saved prompt text.
|
|
86
|
+
const role = lockedRole;
|
|
87
|
+
const cfgKey = `roles.${baseRoleOf(role)}.minRunIntervalSeconds`;
|
|
88
|
+
const text = session.input.trim();
|
|
89
|
+
if (text === "") {
|
|
90
|
+
const result = unsetConfigKey(session.root, cfgKey);
|
|
91
|
+
if (result.ok) {
|
|
92
|
+
exitIntervalMode();
|
|
93
|
+
session.flashMessage(`${role}: min run interval cleared`);
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
session.flashMessage(result.error); // broken config or write failure — stay in edit mode
|
|
97
|
+
}
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
const value = Number(text);
|
|
101
|
+
const invalid = checkMinRunInterval(value, cfgKey);
|
|
102
|
+
if (invalid) {
|
|
103
|
+
session.flashMessage(invalid); // name the rejected value and stay open
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
const result = setConfigKey(session.root, cfgKey, JSON.stringify(value));
|
|
107
|
+
if (result.ok) {
|
|
108
|
+
exitIntervalMode();
|
|
109
|
+
session.flashMessage(`${role}: min run interval set to ${text}s`);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
session.flashMessage(result.error); // broken config or write failure — stay in edit mode
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
};
|
|
118
|
+
return {
|
|
119
|
+
handleKey,
|
|
120
|
+
exit: exitIntervalMode,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
2
|
+
import { saveRoleNote } from "../../agent/role-notes-write.js";
|
|
3
|
+
import { readTextIfExists } from "../../files/files.js";
|
|
4
|
+
import { roleNotesPath } from "../../paths.js";
|
|
5
|
+
/** The notes view's Ctrl+E replace-notes-from-a-file editor on the shared prompt line, the
|
|
6
|
+
* terminal twin of `tumwater role <id> --notes-file <path>` and the dashboard drawer's
|
|
7
|
+
* textarea. It owns its `notesEditFor` flag, its saved-draft pair, and the handleKey arms for
|
|
8
|
+
* its key (the toggle with the `heldEditorFlash(entering)` guard clause — the shared guard
|
|
9
|
+
* priority lives in the session module — plus Esc, Ctrl+E again, and Enter). Guarded to the
|
|
10
|
+
* notes view (`ctx.noteMode`), so the Report window's Ctrl+E arm keeps the key on its own
|
|
11
|
+
* views. The prompt line is single-line and a note may be multiline up to ROLE_NOTES_MAX_BYTES,
|
|
12
|
+
* so the editor takes a file path and reads it verbatim — exactly the CLI's own
|
|
13
|
+
* non-interactive replacement, the way the Activity view's Ctrl+V attach editor also takes a
|
|
14
|
+
* path. The write goes through saveRoleNote (the same write the CLI and the dashboard drawer
|
|
15
|
+
* make), so the 4 KB cap and its wording cannot drift; the fresh read is repainted through the
|
|
16
|
+
* session's refreshNotes seam. */
|
|
17
|
+
export function createNotesEditor(session) {
|
|
18
|
+
let notesEditFor = null;
|
|
19
|
+
let notesSavedInput = "";
|
|
20
|
+
let notesSavedCursor = 0;
|
|
21
|
+
// Leave the notes editor (Esc, Ctrl+E again, or Ctrl+T): restore the saved prompt text with
|
|
22
|
+
// no note written.
|
|
23
|
+
const exitNotesMode = () => {
|
|
24
|
+
if (notesEditFor === null)
|
|
25
|
+
return;
|
|
26
|
+
notesEditFor = null;
|
|
27
|
+
session.held.notes = null;
|
|
28
|
+
session.restorePromptFrom(notesSavedInput, notesSavedCursor);
|
|
29
|
+
};
|
|
30
|
+
const handleKey = (_str, key, ctx) => {
|
|
31
|
+
if (key.ctrl && key.name === "e" && ctx.noteMode &&
|
|
32
|
+
// The pane's noteMode closure goes stale when syncRoster shrinks the roster past the
|
|
33
|
+
// viewed role (it never resets the pane's flags), so the same in-range guard every
|
|
34
|
+
// transcript editor arm carries must hold here too before its view indexes roleIds.
|
|
35
|
+
ctx.roleIds.length > 0 && ctx.view >= 1 && ctx.view <= ctx.roleIds.length) {
|
|
36
|
+
// Ctrl+E toggles the replace-notes editor on the notes view's prompt line (mnemonic:
|
|
37
|
+
// **e**dit notes — the same key the Report window's window-cycle arm and the Queued
|
|
38
|
+
// pane's row-edit arm use on their own views, guarded away here by ctx.noteMode). The
|
|
39
|
+
// line edits one file path that becomes the role's whole note through the same
|
|
40
|
+
// saveRoleNote write the CLI and the dashboard drawer use. Entering saves the prompt
|
|
41
|
+
// draft and blanks the line; leaving restores it.
|
|
42
|
+
const held = session.heldEditorFlash("notes");
|
|
43
|
+
if (held) {
|
|
44
|
+
// Mutually exclusive with the other editors: all share one prompt line, so a mode may
|
|
45
|
+
// not enter while another holds it. Flash the way out instead of silently dropping
|
|
46
|
+
// the held mode's saved draft.
|
|
47
|
+
session.flashMessage(held);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
if (notesEditFor !== null) {
|
|
51
|
+
exitNotesMode();
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// Mid-recall, hand the arrows' saved draft back before the notes editor takes the
|
|
55
|
+
// line — the same settle the role editors do — so leaving restores the
|
|
56
|
+
// draft, not a history entry, with no stale recall state riding along.
|
|
57
|
+
const role = ctx.roleIds[ctx.view - 1]; // defined: the open-arm range guard above pins the view into roleIds
|
|
58
|
+
session.settleDraft((t) => (notesSavedInput = t), (n) => (notesSavedCursor = n));
|
|
59
|
+
notesEditFor = role;
|
|
60
|
+
session.held.notes = role;
|
|
61
|
+
session.input = "";
|
|
62
|
+
session.cursor = 0;
|
|
63
|
+
session.flashMessage(`replace ${role}'s notes from a file — Enter to save, Esc to cancel`);
|
|
64
|
+
}
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
if (key.name === "escape" && notesEditFor !== null) {
|
|
68
|
+
// Esc cancels the notes editor, restoring the previous prompt text and writing nothing.
|
|
69
|
+
exitNotesMode();
|
|
70
|
+
session.flashMessage("notes edit cancelled");
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
if (key.name === "return" && notesEditFor !== null) {
|
|
74
|
+
// Enter reads the typed path whole and saves it through saveRoleNote — the shared write
|
|
75
|
+
// the CLI's `--notes-file` and the dashboard drawer make, so the 4 KB cap and its wording
|
|
76
|
+
// cannot drift. An empty path, a missing file, an unreadable file, and an over-cap note
|
|
77
|
+
// all flash and STAY in the editor so the operator can fix the path; an empty file
|
|
78
|
+
// clears the note. On success the notes view repaints the new note without leaving and
|
|
79
|
+
// re-entering, and the editor exits with the shared success wording.
|
|
80
|
+
const role = notesEditFor;
|
|
81
|
+
const p = session.input.trim();
|
|
82
|
+
if (p === "") {
|
|
83
|
+
session.flashMessage("a file path is required");
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
let text;
|
|
87
|
+
try {
|
|
88
|
+
text = readTextIfExists(p);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
session.flashError(err);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
if (text === null) {
|
|
95
|
+
session.flashMessage(`no such file: ${p}`);
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
const message = saveRoleNote(roleNotesPath(session.root, role), text);
|
|
100
|
+
session.refreshNotes(role);
|
|
101
|
+
exitNotesMode();
|
|
102
|
+
session.flashMessage(message);
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
// The write or the 4 KB cap refused the note: flash the reason and stay in the editor
|
|
106
|
+
// so the operator can fix the path.
|
|
107
|
+
session.flashError(err);
|
|
108
|
+
}
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
return false;
|
|
112
|
+
};
|
|
113
|
+
return { handleKey, exit: exitNotesMode };
|
|
114
|
+
}
|