tumwater 0.4.2 → 0.4.3
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 +6 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +6 -6
- package/dist/src/agent/{run-agent.js → agent-run.js} +25 -12
- package/dist/src/agent/{run-settle.js → agent-settle.js} +48 -24
- package/dist/src/agent/{run-spawn.js → agent-spawn.js} +21 -12
- package/dist/src/agent/{run-stream.js → agent-stream.js} +4 -4
- package/dist/src/agent/backend-traits.js +2 -2
- package/dist/src/agent/claude/claude-account.js +1 -8
- package/dist/src/agent/claude/claude-backend.js +3 -3
- package/dist/src/agent/claude/claude-env.js +27 -31
- package/dist/src/agent/claude/claude-stream.js +111 -54
- package/dist/src/agent/codex/codex-account.js +9 -0
- package/dist/src/agent/codex/codex-backend.js +2 -2
- package/dist/src/agent/codex/codex-env.js +28 -15
- package/dist/src/agent/codex/codex-hook-trust.js +39 -20
- package/dist/src/agent/codex/codex-rollout.js +93 -56
- package/dist/src/agent/codex/codex-stream.js +1 -1
- package/dist/src/agent/env-scrub.js +18 -0
- package/dist/src/agent/image-input.js +68 -11
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +2 -2
- package/dist/src/agent/opencode/opencode-catalog.js +50 -27
- package/dist/src/agent/opencode/opencode-config.js +32 -14
- package/dist/src/agent/opencode/opencode-env.js +12 -13
- package/dist/src/agent/opencode/opencode-stream.js +47 -25
- package/dist/src/agent/session-ledger.js +1 -1
- package/dist/src/agent/subscription-account.js +3 -3
- package/dist/src/backlog/backlog-cli-view.js +81 -41
- package/dist/src/backlog/backlog-cli.js +57 -26
- package/dist/src/backlog/backlog-eligibility.js +58 -102
- package/dist/src/backlog/backlog-md.js +5 -5
- package/dist/src/backlog/backlog-parts.js +65 -27
- package/dist/src/backlog/backlog-release.js +12 -12
- package/dist/src/backlog/backlog-structure.js +114 -61
- package/dist/src/backlog/backlog.js +1 -1
- package/dist/src/baseline/main-baseline.js +148 -84
- package/dist/src/budget/budget-fallback-view.js +100 -243
- package/dist/src/budget/budget-wiring.js +29 -65
- package/dist/src/budget/fallback-breaker.js +2 -2
- package/dist/src/build/build-check-detect.js +43 -14
- package/dist/src/build/build-check-events.js +2 -2
- package/dist/src/build/build-check-scoped.js +40 -16
- package/dist/src/build/build-check.js +13 -5
- package/dist/src/cli/cli-command-args.js +89 -52
- package/dist/src/cli/cli-flag-specs.js +42 -22
- package/dist/src/cli/cli-init.js +34 -12
- package/dist/src/cli/cli-query-commands.js +1 -1
- package/dist/src/cli/cli-repo-gate.js +2 -2
- package/dist/src/cli/cli-start.js +91 -58
- package/dist/src/cli/completion.js +82 -46
- package/dist/src/cli/config-commands.js +82 -71
- package/dist/src/cli/help.js +4 -6
- package/dist/src/cli/log-commands.js +43 -23
- package/dist/src/cli/notify-command.js +2 -2
- package/dist/src/cli/question-commands.js +86 -60
- package/dist/src/cli.js +181 -135
- package/dist/src/collections.js +31 -16
- package/dist/src/concurrency/check-slots.js +70 -0
- package/dist/src/concurrency/lock.js +22 -0
- package/dist/src/concurrency/run-permit.js +49 -0
- package/dist/src/concurrency/semaphore.js +46 -250
- package/dist/src/config/config-editable-keys.js +4 -7
- package/dist/src/config/config-fallbacks.js +81 -101
- package/dist/src/config/config-field-checks.js +66 -37
- package/dist/src/config/config-keys.js +5 -20
- package/dist/src/config/config-live.js +4 -28
- package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
- package/dist/src/config/config-request.js +153 -102
- package/dist/src/config/config-schema.js +9 -11
- package/dist/src/config/config-selector-checks.js +1 -1
- package/dist/src/config/config-validation-agents.js +2 -2
- package/dist/src/config/config-validation-roles.js +4 -52
- package/dist/src/config/config-validation.js +4 -38
- package/dist/src/config/config-views.js +9 -34
- package/dist/src/config/config-write.js +6 -23
- package/dist/src/config/config.js +4 -5
- package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
- package/dist/src/doctor/doctor-backlog-plans.js +24 -24
- package/dist/src/doctor/doctor-backlog.js +7 -5
- package/dist/src/doctor/doctor-check-names.js +3 -6
- package/dist/src/doctor/doctor-checks.js +5 -5
- package/dist/src/doctor/doctor-claude-checks.js +44 -28
- package/dist/src/doctor/doctor-codex-checks.js +6 -6
- package/dist/src/doctor/doctor-contention-checks.js +44 -112
- package/dist/src/doctor/doctor-fallback-checks.js +2 -2
- package/dist/src/doctor/doctor-holds.js +6 -45
- package/dist/src/doctor/doctor-install.js +1 -1
- package/dist/src/doctor/doctor-launch-services.js +1 -1
- package/dist/src/doctor/doctor-model-checks.js +178 -94
- package/dist/src/doctor/doctor-notify.js +2 -2
- package/dist/src/doctor/doctor-opencode-checks.js +5 -4
- package/dist/src/doctor/doctor-orphans.js +120 -20
- package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
- package/dist/src/doctor/doctor-pi-checks.js +2 -2
- package/dist/src/doctor/doctor-principles.js +3 -3
- package/dist/src/doctor/doctor-project-checks.js +3 -3
- package/dist/src/doctor/doctor-role-notes.js +1 -1
- package/dist/src/doctor/doctor.js +8 -11
- package/dist/src/events/event-format.js +30 -54
- package/dist/src/events/event-labels.js +1 -5
- package/dist/src/events/event-legacy.js +1 -3
- package/dist/src/events/{notify.js → event-notify.js} +2 -2
- package/dist/src/events/event-read.js +1 -1
- package/dist/src/failure/failure-cluster.js +6 -6
- package/dist/src/failure/failure-data.js +8 -4
- package/dist/src/failure/failure-render.js +44 -34
- package/dist/src/failure/failure-state-change.js +4 -34
- package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
- package/dist/src/files/files.js +30 -0
- package/dist/src/files/json-object.js +8 -8
- package/dist/src/files/stat-cache.js +12 -8
- package/dist/src/gates/budget-gates.js +85 -84
- package/dist/src/gates/gate-polls.js +27 -68
- package/dist/src/gates/pause-gates.js +16 -2
- package/dist/src/gates/readiness.js +1 -1
- package/dist/src/gates/role-cap-gates.js +44 -35
- package/dist/src/gates/{startup-gate.js → startup-gates.js} +7 -7
- package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
- package/dist/src/git/git.js +75 -45
- package/dist/src/git/worktree-pool.js +3 -6
- package/dist/src/gui/gui-admin-endpoints.js +3 -3
- package/dist/src/gui/gui-backlog-commands.js +4 -4
- package/dist/src/gui/gui-backlog-endpoints.js +61 -41
- package/dist/src/gui/gui-command.js +43 -24
- package/dist/src/gui/gui-history-endpoints.js +100 -52
- package/dist/src/gui/gui-operator-commands.js +22 -23
- package/dist/src/gui/gui-prompt-commands.js +23 -23
- package/dist/src/gui/gui-report-endpoints.js +1 -1
- package/dist/src/history/history-data.js +71 -44
- package/dist/src/history/history-queued.js +49 -27
- package/dist/src/history/history.js +51 -33
- package/dist/src/inbox/prompt-commands.js +59 -42
- package/dist/src/init/init.js +88 -40
- package/dist/src/merge/merge-backlog-conflicts.js +1 -1
- package/dist/src/merge/merge-core.js +1 -1
- package/dist/src/merge/merge-drain.js +8 -11
- package/dist/src/merge/merge-pipeline.js +6 -7
- package/dist/src/merge/merge-post-review-drain.js +10 -13
- package/dist/src/merge/merge-requeue.js +1 -1
- package/dist/src/merge/merge-slot.js +0 -8
- package/dist/src/merge/merge-verify.js +8 -18
- package/dist/src/notes-cli.js +52 -42
- package/dist/src/operator/operator-commands.js +76 -44
- package/dist/src/operator/operator-intent.js +3 -3
- package/dist/src/orchestrator/orchestrator-launch.js +31 -63
- package/dist/src/orchestrator/orchestrator-poll.js +27 -32
- package/dist/src/orchestrator/orchestrator.js +8 -21
- package/dist/src/orchestrator/scheduling-admit.js +101 -68
- package/dist/src/orchestrator/scheduling-scope.js +1 -1
- package/dist/src/orchestrator/scheduling-skip.js +1 -1
- package/dist/src/orchestrator/startup-migrations.js +1 -1
- package/dist/src/paths.js +8 -7
- package/dist/src/pi/pi-args.js +5 -2
- package/dist/src/pi/pi-backend.js +2 -2
- package/dist/src/pi/pi-bin.js +3 -3
- package/dist/src/pi/pi-event-line.js +1 -1
- package/dist/src/pi/pi-models.js +43 -29
- package/dist/src/pi/pi-settings.js +43 -20
- package/dist/src/pi/pi-stream.js +32 -6
- package/dist/src/pi/pi-watchdogs.js +12 -5
- package/dist/src/pi/pi.js +2 -2
- package/dist/src/pi-extension/context-shake.js +32 -20
- package/dist/src/pi-extension/kill-guard.js +69 -0
- package/dist/src/process/process-group.js +49 -5
- package/dist/src/process/process.js +8 -8
- package/dist/src/process/run-marker.js +31 -13
- package/dist/src/process/supervisor.js +40 -28
- package/dist/src/prompt/dialect.js +1 -1
- package/dist/src/prompt/gate-notes.js +2 -2
- package/dist/src/prompt/notes-backlog.js +66 -37
- package/dist/src/prompt/principles.js +6 -4
- package/dist/src/prompt/prompt-followup.js +12 -4
- package/dist/src/prompt/prompt.js +88 -38
- package/dist/src/report/report-data.js +53 -37
- package/dist/src/report/report-fold.js +16 -9
- package/dist/src/report/report-render.js +21 -194
- package/dist/src/report/report-since-data.js +4 -3
- package/dist/src/report/report-since-render.js +173 -0
- package/dist/src/report/report-usage-fold.js +8 -8
- package/dist/src/report/report.js +46 -22
- package/dist/src/review/post-merge-review-render.js +9 -3
- package/dist/src/review/post-merge-review.js +1 -1
- package/dist/src/review/review-precheck.js +11 -1
- package/dist/src/review/review.js +40 -153
- package/dist/src/review/secret-leaks.js +26 -16
- package/dist/src/roles/role-catalog.js +11 -3
- package/dist/src/roles/role-guidance.js +16 -12
- package/dist/src/roles/role-render.js +1 -14
- package/dist/src/roles/role-view.js +9 -21
- package/dist/src/run/acceptance-greps.js +88 -45
- package/dist/src/run/run-apply.js +12 -6
- package/dist/src/run/run-detail-data.js +121 -68
- package/dist/src/run/run-detail-render.js +92 -35
- package/dist/src/run/run-detail.js +72 -41
- package/dist/src/run/run-prompt.js +63 -50
- package/dist/src/run/run-resume.js +76 -29
- package/dist/src/run/run-stage.js +135 -84
- package/dist/src/run/run-timing.js +8 -2
- package/dist/src/run/run-usage.js +31 -1
- package/dist/src/run/run-verdict.js +119 -50
- package/dist/src/run/stage-check.js +49 -32
- package/dist/src/run/stage-checks-shared.js +28 -0
- package/dist/src/run/stage-name-checks.js +111 -92
- package/dist/src/run/stage-path-checks.js +69 -42
- package/dist/src/run/stage-prose-checks.js +55 -40
- package/dist/src/runner/runner-outcome.js +6 -1
- package/dist/src/runner/runner-pi.js +3 -10
- package/dist/src/runner/runner-recovery.js +1 -1
- package/dist/src/runner/{revision.js → runner-revision.js} +1 -1
- package/dist/src/runner/runner-run.js +97 -35
- package/dist/src/runner/runner-state-load.js +12 -9
- package/dist/src/runner/runner.js +41 -25
- package/dist/src/scheduling/claims.js +0 -4
- package/dist/src/scheduling/scheduling.js +53 -66
- package/dist/src/status/status-data.js +2 -2
- package/dist/src/status/status-role-notes.js +2 -2
- package/dist/src/status/status-roles.js +9 -17
- package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
- package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
- package/dist/src/team/team-hold.js +98 -65
- package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +2 -15
- package/dist/src/team/team-polls.js +28 -126
- package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
- package/dist/src/team/team-state.js +4 -13
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +20 -15
- package/dist/src/text/text.js +3 -3
- package/dist/src/ui/alert-feeds.js +49 -73
- package/dist/src/ui/badges.js +3 -13
- package/dist/src/ui/gui/gui-client-boot.js +2 -2
- package/dist/src/ui/gui/gui-client-composer.js +1 -1
- package/dist/src/ui/gui/gui-client-drawer-config.js +7 -17
- package/dist/src/ui/gui/gui-client-drawer.js +1 -1
- package/dist/src/ui/gui/gui-client-history.js +28 -14
- package/dist/src/ui/gui/gui-client-model.js +0 -12
- package/dist/src/ui/gui/gui-client-pending.js +7 -6
- package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
- package/dist/src/ui/gui/gui-client-report.js +1 -1
- package/dist/src/ui/gui/gui-client-roles.js +0 -1
- package/dist/src/ui/gui/gui-client-settings.js +43 -84
- package/dist/src/ui/gui/gui-client-team.js +4 -4
- package/dist/src/ui/gui/gui-client.js +7 -1
- package/dist/src/ui/progress-data.js +53 -40
- package/dist/src/ui/run-progress-model.js +9 -2
- package/dist/src/ui/status-model.js +2 -24
- package/dist/src/ui/status-payload.js +3 -7
- package/dist/src/ui/status-render.js +5 -9
- package/dist/src/ui/team-alert-backlog.js +6 -6
- package/dist/src/ui/team-alert-status.js +9 -170
- package/dist/src/ui/team-alert-tail.js +19 -215
- package/dist/src/ui/team-alerts.js +7 -8
- package/dist/src/ui/transcript-run.js +27 -16
- package/dist/src/ui/tui/tui-editor-answer.js +12 -10
- package/dist/src/ui/tui/tui-editor-attach.js +4 -3
- package/dist/src/ui/tui/tui-editor-backends.js +4 -3
- package/dist/src/ui/tui/tui-editor-brief.js +6 -9
- package/dist/src/ui/tui/tui-editor-budget.js +4 -3
- package/dist/src/ui/tui/tui-editor-config.js +80 -68
- package/dist/src/ui/tui/tui-editor-entry.js +12 -20
- package/dist/src/ui/tui/tui-editor-pause.js +51 -57
- package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
- package/dist/src/ui/tui/tui-editor-queued.js +11 -14
- package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
- package/dist/src/ui/tui/tui-editor-release.js +4 -3
- package/dist/src/ui/tui/tui-editor-retire.js +7 -7
- package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
- package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
- package/dist/src/ui/tui/tui-editor-search.js +4 -3
- package/dist/src/ui/tui/tui-editor-session.js +17 -4
- package/dist/src/ui/tui/tui-editors.js +1 -7
- package/dist/src/ui/tui/tui-frame.js +182 -134
- package/dist/src/ui/tui/tui-guard.js +20 -0
- package/dist/src/ui/tui/tui-input.js +159 -92
- package/dist/src/ui/tui/tui-keymap.js +51 -29
- package/dist/src/ui/tui/tui-keys.js +1 -1
- package/dist/src/ui/tui/tui-pane-activity.js +3 -3
- package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +252 -228
- package/dist/src/ui/tui/tui-pane-history.js +61 -70
- package/dist/src/ui/tui/tui-pane-pending.js +284 -247
- package/dist/src/ui/tui/tui-pane-queued.js +25 -27
- package/dist/src/ui/tui/tui-pane-report.js +2 -1
- package/dist/src/ui/tui/tui-pane-session.js +11 -7
- package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
- package/dist/src/ui/tui/tui-pane.js +14 -11
- package/dist/src/ui/tui/tui-panes.js +29 -32
- package/dist/src/ui/tui/tui.js +3 -4
- package/dist/src/verdict/fix-claim.js +76 -54
- package/dist/src/verdict/refusal.js +1 -1
- package/package.json +5 -5
- package/dist/src/concurrency/provider-lease.js +0 -105
- package/dist/src/runner/model-fallback.js +0 -76
- package/dist/src/runner/runner-fallback.js +0 -66
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
- package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
- /package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +0 -0
- /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
- /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
- /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
- /package/dist/src/operator/{revert.js → operator-revert.js} +0 -0
- /package/dist/src/runner/{leftover.js → runner-leftover.js} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { setConfigKey, unsetConfigKey } from "../../config/config-write.js";
|
|
2
2
|
import { isExponentSpelling } from "../../text/text.js";
|
|
3
|
+
import { savedPromptDraft } from "./tui-editor-session.js";
|
|
3
4
|
/** Build a team-scope config-key editor. The editor owns its mode flag and saved-draft pair
|
|
4
5
|
* in a closure; it may take the line only on the Activity view (view 0, the team scope) or
|
|
5
6
|
* when it already holds it (the toggle-out arm), and only while no other editor holds the
|
|
@@ -8,86 +9,97 @@ import { isExponentSpelling } from "../../text/text.js";
|
|
|
8
9
|
* (the file is untouched), success restores the saved prompt text. */
|
|
9
10
|
export function createTeamConfigKeyEditor(session, spec) {
|
|
10
11
|
let mode = false;
|
|
11
|
-
|
|
12
|
+
const draft = savedPromptDraft(session);
|
|
12
13
|
// Leave the editor (Esc, the key again, or Ctrl+T): restore the saved prompt text.
|
|
13
14
|
const exit = () => {
|
|
14
15
|
if (!mode)
|
|
15
16
|
return;
|
|
16
17
|
mode = false;
|
|
17
18
|
session.held[spec.mode] = false;
|
|
18
|
-
|
|
19
|
+
draft.restore();
|
|
19
20
|
};
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
if (mode) {
|
|
27
|
-
exit();
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
if (ctx.view !== 0)
|
|
31
|
-
return false;
|
|
32
|
-
// A check-cap editor with text on the line must fall through to applyKey's kill before the
|
|
33
|
-
// hold-guard runs: while another editor holds the (non-empty) line, Ctrl+K should still
|
|
34
|
-
// kill from the cursor to the line end rather than be consumed by the mutual-exclusion
|
|
35
|
-
// flash, and a typed draft must never be swallowed. Only Ctrl+K sets `requireEmptyPrompt`,
|
|
36
|
-
// so only it takes this fall-through.
|
|
37
|
-
if (spec.requireEmptyPrompt && session.input !== "")
|
|
38
|
-
return false;
|
|
39
|
-
if (session.editorEntryDenied(spec.mode))
|
|
40
|
-
return true;
|
|
41
|
-
const existing = spec.prefill(session);
|
|
42
|
-
restoreDraft = session.takeDraft();
|
|
43
|
-
mode = true;
|
|
44
|
-
session.held[spec.mode] = true;
|
|
45
|
-
session.input = existing;
|
|
46
|
-
session.cursor = session.input.length;
|
|
47
|
-
session.flashMessage(spec.openFlash);
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
if (key.name === "escape" && mode) {
|
|
51
|
-
// Esc cancels the editor, restoring the previous prompt text.
|
|
21
|
+
// The key opens this team-scope editor only on the Activity view (view 0, the team
|
|
22
|
+
// scope): on the other views the key keeps its own meaning and this arm falls through
|
|
23
|
+
// so the later guards run. While open, the key toggles the editor out. Entering saves
|
|
24
|
+
// the prompt draft; leaving restores it.
|
|
25
|
+
const openOrToggle = (ctx) => {
|
|
26
|
+
if (mode) {
|
|
52
27
|
exit();
|
|
53
28
|
return true;
|
|
54
29
|
}
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
30
|
+
if (ctx.view !== 0)
|
|
31
|
+
return false;
|
|
32
|
+
// A check-cap editor with text on the line must fall through to applyKey's kill before the
|
|
33
|
+
// hold-guard runs: while another editor holds the (non-empty) line, Ctrl+K should still
|
|
34
|
+
// kill from the cursor to the line end rather than be consumed by the mutual-exclusion
|
|
35
|
+
// flash, and a typed draft must never be swallowed. Only Ctrl+K sets `requireEmptyPrompt`,
|
|
36
|
+
// so only it takes this fall-through.
|
|
37
|
+
if (spec.requireEmptyPrompt && session.input !== "")
|
|
38
|
+
return false;
|
|
39
|
+
if (session.editorEntryDenied(spec.mode))
|
|
40
|
+
return true;
|
|
41
|
+
const existing = spec.prefill(session);
|
|
42
|
+
draft.save();
|
|
43
|
+
mode = true;
|
|
44
|
+
session.held[spec.mode] = true;
|
|
45
|
+
session.input = existing;
|
|
46
|
+
session.cursor = session.input.length;
|
|
47
|
+
session.flashMessage(spec.openFlash);
|
|
48
|
+
return true;
|
|
49
|
+
};
|
|
50
|
+
// Esc cancels the editor, restoring the previous prompt text.
|
|
51
|
+
const cancel = () => {
|
|
52
|
+
exit();
|
|
53
|
+
return true;
|
|
54
|
+
};
|
|
55
|
+
// The four-way message a successful commit flashes: an empty-line no-op clear, a real
|
|
56
|
+
// clear, an unchanged set, and a real set.
|
|
57
|
+
const commitFlash = (result, text) => {
|
|
58
|
+
const noop = text === "" ? result.oldValue === undefined : "unchanged" in result;
|
|
59
|
+
return noop
|
|
60
|
+
? text === ""
|
|
61
|
+
? `${spec.configKey} is not set in tumwater.json, so nothing was cleared`
|
|
62
|
+
: `${spec.configKey} is already ${text}, so tumwater.json was not written`
|
|
63
|
+
: text === ""
|
|
64
|
+
? spec.clearedFlash
|
|
65
|
+
: spec.setFlash(text);
|
|
66
|
+
};
|
|
67
|
+
// Enter commits through the shared setter family the config CLI and the dashboard
|
|
68
|
+
// Settings use: a whitespace-trimmed empty line clears the key (`unsetConfigKey`),
|
|
69
|
+
// otherwise `setConfigKey` writes the trimmed line — it JSON-parses the text, so a
|
|
70
|
+
// bare command or typed value stays the literal while a typed number becomes a number
|
|
71
|
+
// — and validateConfig owns the value's truth (naming `configKey`) on the way out. An
|
|
72
|
+
// invalid value flashes and STAYS in edit mode so the operator can fix it (the file is
|
|
73
|
+
// untouched); success restores the saved prompt text.
|
|
74
|
+
const commit = () => {
|
|
75
|
+
const text = session.input.trim();
|
|
76
|
+
// Shape, before the write, for the plain-integer team keys: an exponent spelling is
|
|
77
|
+
// itself a JSON number that setConfigKey's JSON.parse would read past validateConfig
|
|
78
|
+
// and silently write, so refuse it here and stay in edit mode (the twin of the per-role
|
|
79
|
+
// instances/interval guards); a non-exponent invalid falls through to validateConfig.
|
|
80
|
+
if (spec.plainIntegerKey && text !== "" && isExponentSpelling(text)) {
|
|
81
|
+
session.flashMessage(`${spec.configKey} must be a plain integer — no exponent notation`);
|
|
89
82
|
return true;
|
|
90
83
|
}
|
|
84
|
+
const result = text === ""
|
|
85
|
+
? unsetConfigKey(session.root, spec.configKey)
|
|
86
|
+
: setConfigKey(session.root, spec.configKey, text);
|
|
87
|
+
if (result.ok) {
|
|
88
|
+
exit();
|
|
89
|
+
session.flashMessage(commitFlash(result, text));
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
session.flashMessage(result.error); // broken config or write failure — stay in edit mode
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
};
|
|
96
|
+
const handleKey = (_str, key, ctx) => {
|
|
97
|
+
if (key.ctrl && key.name === spec.openCtrl)
|
|
98
|
+
return openOrToggle(ctx);
|
|
99
|
+
if (key.name === "escape" && mode)
|
|
100
|
+
return cancel();
|
|
101
|
+
if (key.name === "return" && mode)
|
|
102
|
+
return commit();
|
|
91
103
|
return false;
|
|
92
104
|
};
|
|
93
105
|
return {
|
|
@@ -5,7 +5,8 @@ import { submitRolePromptAndWake } from "../../operator/operator-intent.js";
|
|
|
5
5
|
import { promptLengthProblem } from "../../inbox/inbox-submit.js";
|
|
6
6
|
import { collapseWhitespace } from "../../text/text.js";
|
|
7
7
|
import { selectedEditableEntry } from "./tui-pane-backlog.js";
|
|
8
|
-
import {
|
|
8
|
+
import { guardError } from "./tui-guard.js";
|
|
9
|
+
import { readEditorPath, savedPromptDraft } from "./tui-editor-session.js";
|
|
9
10
|
/** The backlog pane's title editor (Ctrl+N new entry, Ctrl+E on a selected plan or bug) on the
|
|
10
11
|
* shared prompt line. It owns its target (`entryEditFor`), its plan↔bug flip, its saved-draft
|
|
11
12
|
* pair, its exit helper, and the fileEntryWithBody / tryEditEntry / commit arms, plus the
|
|
@@ -13,7 +14,7 @@ import { readEditorPath } from "./tui-editor-session.js";
|
|
|
13
14
|
* the session's held registry. */
|
|
14
15
|
export function createEntryEditor(session) {
|
|
15
16
|
let entryEditFor = null;
|
|
16
|
-
|
|
17
|
+
const draft = savedPromptDraft(session);
|
|
17
18
|
// The triple state of the entry editor's prompt line: false (the title field), true (the
|
|
18
19
|
// file-path field whose contents replace the whole entry), and the draft the toggle parks
|
|
19
20
|
// when it hands the line to a different field.
|
|
@@ -29,7 +30,7 @@ export function createEntryEditor(session) {
|
|
|
29
30
|
entryEditFor = null;
|
|
30
31
|
entryBodyFile = false;
|
|
31
32
|
session.held.entry = null;
|
|
32
|
-
|
|
33
|
+
draft.restore();
|
|
33
34
|
};
|
|
34
35
|
// On the Backlog view Ctrl+N files a new entry: it opens the title editor with a null
|
|
35
36
|
// position (new plan), and pressed again inside that editor flips plan <-> bug.
|
|
@@ -44,7 +45,7 @@ export function createEntryEditor(session) {
|
|
|
44
45
|
// already holding the line) is the notes/change/trail cycle, a pane arm the outer
|
|
45
46
|
// dispatch handles — this factory returns false so it runs.
|
|
46
47
|
const openNewEntry = () => {
|
|
47
|
-
|
|
48
|
+
draft.save();
|
|
48
49
|
entryEditFor = { kind: "plan", position: null, title: "" };
|
|
49
50
|
session.held.entry = entryEditFor;
|
|
50
51
|
session.input = "";
|
|
@@ -76,22 +77,19 @@ export function createEntryEditor(session) {
|
|
|
76
77
|
// saves through tryEditEntry, the non-exiting core the dashboard drawer shares, keeping
|
|
77
78
|
// the body and stamp. The prompt line is single-line, so only the title is editable here.
|
|
78
79
|
const openSelectedEntry = (ctx) => {
|
|
79
|
-
|
|
80
|
+
guardError(session, () => {
|
|
80
81
|
const selected = selectedEditableEntry(session, ctx.selectedEntry, "edited");
|
|
81
82
|
if (selected !== null) {
|
|
82
83
|
const kind = selected.kind;
|
|
83
84
|
const position = selected.position;
|
|
84
|
-
|
|
85
|
+
draft.save();
|
|
85
86
|
entryEditFor = { kind, position, title: selected.picked.title };
|
|
86
87
|
session.held.entry = entryEditFor;
|
|
87
88
|
session.input = stripEntryStamp(selected.picked.title);
|
|
88
89
|
session.cursor = session.input.length;
|
|
89
90
|
session.flashMessage(`editing ${kind} #${position} — Enter to save, Esc to cancel`);
|
|
90
91
|
}
|
|
91
|
-
}
|
|
92
|
-
catch (err) {
|
|
93
|
-
session.flashError(err);
|
|
94
|
-
}
|
|
92
|
+
});
|
|
95
93
|
return true;
|
|
96
94
|
};
|
|
97
95
|
// Esc cancels the title editor, restoring the previous prompt text; nothing is written.
|
|
@@ -107,7 +105,7 @@ export function createEntryEditor(session) {
|
|
|
107
105
|
// the two arms pass differently is the (title, body) pair taking the entry's place;
|
|
108
106
|
// an omitted body preserves the matched entry's own body (the title edit arm).
|
|
109
107
|
const applyEntryEdit = (target, newTitle, newBody) => {
|
|
110
|
-
|
|
108
|
+
guardError(session, () => {
|
|
111
109
|
// Both call sites (the file arm's and the title arm's Entry-save paths) run only
|
|
112
110
|
// when `target.position` is non-null, so the position math never sees a null.
|
|
113
111
|
const position = target.position;
|
|
@@ -131,10 +129,7 @@ export function createEntryEditor(session) {
|
|
|
131
129
|
session.clearBacklogSelection();
|
|
132
130
|
session.flashMessage(`that ${target.kind} was already removed or moved — nothing edited`);
|
|
133
131
|
}
|
|
134
|
-
}
|
|
135
|
-
catch (err) {
|
|
136
|
-
session.flashError(err);
|
|
137
|
-
}
|
|
132
|
+
});
|
|
138
133
|
};
|
|
139
134
|
// File mode: the line holds a path whose contents replace title and body through the
|
|
140
135
|
// same title-checked tryEditEntry core the title edit uses, keeping the entry's
|
|
@@ -160,7 +155,7 @@ export function createEntryEditor(session) {
|
|
|
160
155
|
// New entry (Ctrl+N): file by title with an empty body and wake its role, the same
|
|
161
156
|
// steps handleBacklogFile takes; a length problem or a throw stays in the editor.
|
|
162
157
|
const fileNewEntry = (target) => {
|
|
163
|
-
|
|
158
|
+
guardError(session, () => {
|
|
164
159
|
const { role, wakeText } = OPERATOR_FILE_KINDS[target.kind];
|
|
165
160
|
const problem = promptLengthProblem(wakeText(collapseWhitespace(session.input)), role);
|
|
166
161
|
if (problem) {
|
|
@@ -172,10 +167,7 @@ export function createEntryEditor(session) {
|
|
|
172
167
|
submitRolePromptAndWake(session.root, role, wakeText(filed.title));
|
|
173
168
|
exitEntryEdit();
|
|
174
169
|
session.flashMessage(`filed ${target.kind}: ${filed.title} — woke ${role}`);
|
|
175
|
-
}
|
|
176
|
-
catch (err) {
|
|
177
|
-
session.flashError(err);
|
|
178
|
-
}
|
|
170
|
+
});
|
|
179
171
|
};
|
|
180
172
|
const commitEntryTitle = (target) => {
|
|
181
173
|
applyEntryEdit(target, session.input);
|
|
@@ -3,7 +3,9 @@ import { isTeamPaused, pausedRoles, pauseTeam, pauseRole, resumeTeam, resumeRole
|
|
|
3
3
|
import { durationLabel } from "../../cli/cli-args.js";
|
|
4
4
|
import { teamPausePhrase, teamResumePhrase } from "../../text/phrases.js";
|
|
5
5
|
import { parsePauseInput, parseRolePauseInput, parseRoleWakeInput, parseStopInInput, } from "./tui-input.js";
|
|
6
|
+
import { guardError } from "./tui-guard.js";
|
|
6
7
|
import { viewedRole } from "./tui-editor-ctx.js";
|
|
8
|
+
import { savedPromptDraft } from "./tui-editor-session.js";
|
|
7
9
|
/** The pause/stop/wake editor on the shared prompt line: the whole-team Ctrl+F pause and Ctrl+Q
|
|
8
10
|
* stop editors, the per-role Ctrl+P pause / Ctrl+A abort / Ctrl+W wake controls, and the
|
|
9
11
|
* parse/dispatch leaves those write. It owns its mode flags (pauseEditMode/`pauseEditRole`/
|
|
@@ -14,7 +16,7 @@ export function createPauseEditor(session) {
|
|
|
14
16
|
let pauseEditRole = null;
|
|
15
17
|
let pauseEditWake = false;
|
|
16
18
|
let pauseEditStop = false;
|
|
17
|
-
|
|
19
|
+
const draft = savedPromptDraft(session);
|
|
18
20
|
// Leave pause-edit mode (Esc, Ctrl+F again, a written pause, or Ctrl+T): restore the draft.
|
|
19
21
|
const exitPauseEditMode = () => {
|
|
20
22
|
if (!pauseEditMode)
|
|
@@ -27,10 +29,10 @@ export function createPauseEditor(session) {
|
|
|
27
29
|
session.held.pauseEditRole = null;
|
|
28
30
|
session.held.pauseEditWake = false;
|
|
29
31
|
session.held.pauseEditStop = false;
|
|
30
|
-
|
|
32
|
+
draft.restore();
|
|
31
33
|
};
|
|
32
34
|
const openPauseEditor = (role, wake, stop, hint) => {
|
|
33
|
-
|
|
35
|
+
draft.save();
|
|
34
36
|
pauseEditMode = true;
|
|
35
37
|
pauseEditRole = role;
|
|
36
38
|
pauseEditWake = wake;
|
|
@@ -57,7 +59,7 @@ export function createPauseEditor(session) {
|
|
|
57
59
|
exitPauseEditMode();
|
|
58
60
|
return true;
|
|
59
61
|
}
|
|
60
|
-
|
|
62
|
+
guardError(session, () => {
|
|
61
63
|
if (isTeamPaused(session.root)) {
|
|
62
64
|
session.flashMessage(resumeTeam(session.root) ? teamResumePhrase() : "team was not paused");
|
|
63
65
|
}
|
|
@@ -66,10 +68,7 @@ export function createPauseEditor(session) {
|
|
|
66
68
|
// until resumed. A paused team resumes at once.
|
|
67
69
|
openPauseEditor(null, false, false, 'pause the team: [duration] [reason], e.g. "2h deploy freeze" (empty pauses until resumed) — Enter to pause, Esc to cancel');
|
|
68
70
|
}
|
|
69
|
-
}
|
|
70
|
-
catch (err) {
|
|
71
|
-
session.flashError(err);
|
|
72
|
-
}
|
|
71
|
+
});
|
|
73
72
|
return true;
|
|
74
73
|
};
|
|
75
74
|
// Ctrl+Q (mnemonic: **q**uit later) schedules or cancels a graceful team stop through
|
|
@@ -81,7 +80,7 @@ export function createPauseEditor(session) {
|
|
|
81
80
|
exitPauseEditMode();
|
|
82
81
|
return true;
|
|
83
82
|
}
|
|
84
|
-
|
|
83
|
+
guardError(session, () => {
|
|
85
84
|
if (scheduledStopAt(session.root) !== undefined) {
|
|
86
85
|
session.flashMessage(cancelScheduledStopMessage(session.root));
|
|
87
86
|
}
|
|
@@ -90,10 +89,7 @@ export function createPauseEditor(session) {
|
|
|
90
89
|
// stop intent (no role pinned, stop mode on): nothing is written until Enter.
|
|
91
90
|
openPauseEditor(null, false, true, 'stop the team later: a duration, e.g. "2h" — current runs finish, then the team stops — Enter to schedule, Esc to cancel');
|
|
92
91
|
}
|
|
93
|
-
}
|
|
94
|
-
catch (err) {
|
|
95
|
-
session.flashError(err);
|
|
96
|
-
}
|
|
92
|
+
});
|
|
97
93
|
return true;
|
|
98
94
|
};
|
|
99
95
|
// Ctrl+R on the Activity view applies the pending self-redeploy now — the terminal twin
|
|
@@ -106,13 +102,10 @@ export function createPauseEditor(session) {
|
|
|
106
102
|
// out of pause-edit mode, whose prompt line the restart must not disturb. The write can
|
|
107
103
|
// fail like any marker write, so flash the reason instead of letting the throw escape.
|
|
108
104
|
const restartNow = (_ctx) => {
|
|
109
|
-
|
|
105
|
+
guardError(session, () => {
|
|
110
106
|
const result = requestRestart(session.root);
|
|
111
107
|
session.flashMessage(result.ok ? result.message : result.error);
|
|
112
|
-
}
|
|
113
|
-
catch (err) {
|
|
114
|
-
session.flashError(err);
|
|
115
|
-
}
|
|
108
|
+
});
|
|
116
109
|
return true;
|
|
117
110
|
};
|
|
118
111
|
// A second Ctrl+P or Ctrl+W closes the per-role pause/wake editor, mirroring Ctrl+F's
|
|
@@ -132,7 +125,7 @@ export function createPauseEditor(session) {
|
|
|
132
125
|
// throw would escape this keypress handler and kill the TUI — flash the reason instead.
|
|
133
126
|
const roleControlArm = (name, ctx) => {
|
|
134
127
|
const role = viewedRole(ctx); // defined: view is clamped inside the transcript range
|
|
135
|
-
|
|
128
|
+
guardError(session, () => {
|
|
136
129
|
if (name === "p") {
|
|
137
130
|
// Toggle by the marker's current state, read fresh: pauseRole/resumeRole's false
|
|
138
131
|
// return means another window raced us to the same state, and the wording
|
|
@@ -163,10 +156,7 @@ export function createPauseEditor(session) {
|
|
|
163
156
|
// Ctrl+W opens the one-line wake editor; nothing is written until Enter. Empty wakes now.
|
|
164
157
|
openPauseEditor(role, true, false, `wake ${role}: [duration], e.g. "2h", or "cancel" (empty wakes now) — Enter to apply, Esc to cancel`);
|
|
165
158
|
}
|
|
166
|
-
}
|
|
167
|
-
catch (err) {
|
|
168
|
-
session.flashError(err);
|
|
169
|
-
}
|
|
159
|
+
});
|
|
170
160
|
return true;
|
|
171
161
|
};
|
|
172
162
|
// Esc cancels the pause editor without writing, restoring the previous prompt text.
|
|
@@ -208,43 +198,34 @@ export function createPauseEditor(session) {
|
|
|
208
198
|
session.flashMessage(stopParsed.error);
|
|
209
199
|
return;
|
|
210
200
|
}
|
|
211
|
-
|
|
201
|
+
guardError(session, () => {
|
|
212
202
|
const result = requestScheduledStop(session.root, stopParsed.inMs);
|
|
213
203
|
exitPauseEditMode();
|
|
214
204
|
session.flashMessage(result.ok ? result.message : `error: ${result.error}`);
|
|
215
|
-
}
|
|
216
|
-
catch (err) {
|
|
217
|
-
session.flashError(err);
|
|
218
|
-
}
|
|
205
|
+
});
|
|
219
206
|
};
|
|
220
207
|
const applyWakeEntry = (wakeParsed, target) => {
|
|
221
208
|
if (!wakeParsed.ok) {
|
|
222
209
|
session.flashMessage(wakeParsed.error);
|
|
223
210
|
return;
|
|
224
211
|
}
|
|
225
|
-
|
|
212
|
+
guardError(session, () => {
|
|
226
213
|
const message = wakeParsed.cancel ? cancelWakes(session.root, [target]) : requestWake(session.root, [target], wakeParsed.inMs);
|
|
227
214
|
exitPauseEditMode();
|
|
228
215
|
session.flashMessage(message);
|
|
229
|
-
}
|
|
230
|
-
catch (err) {
|
|
231
|
-
session.flashError(err);
|
|
232
|
-
}
|
|
216
|
+
});
|
|
233
217
|
};
|
|
234
218
|
const applyRolePauseEntry = (parsed, target) => {
|
|
235
|
-
|
|
219
|
+
guardError(session, () => {
|
|
236
220
|
const at = session.now();
|
|
237
221
|
const until = parsed.forMs === undefined ? undefined : at + parsed.forMs;
|
|
238
222
|
const changed = pauseRole(session.root, target, until);
|
|
239
223
|
exitPauseEditMode();
|
|
240
224
|
session.flashMessage(rolePauseMessage(session.root, target, changed, until === undefined ? undefined : { ms: parsed.forMs, untilMs: until }, at));
|
|
241
|
-
}
|
|
242
|
-
catch (err) {
|
|
243
|
-
session.flashError(err);
|
|
244
|
-
}
|
|
225
|
+
});
|
|
245
226
|
};
|
|
246
227
|
const applyTeamPauseEntry = (parsed) => {
|
|
247
|
-
|
|
228
|
+
guardError(session, () => {
|
|
248
229
|
const until = parsed.forMs === undefined ? undefined : session.now() + parsed.forMs;
|
|
249
230
|
const changed = pauseTeam(session.root, until, parsed.reason);
|
|
250
231
|
exitPauseEditMode();
|
|
@@ -252,31 +233,44 @@ export function createPauseEditor(session) {
|
|
|
252
233
|
session.flashMessage(changed || until !== undefined
|
|
253
234
|
? `${teamPausePhrase()}${forSuffix}${changed && parsed.reason ? `: ${parsed.reason}` : ""}`
|
|
254
235
|
: "team is already paused");
|
|
236
|
+
});
|
|
237
|
+
};
|
|
238
|
+
const pauseLineFree = () => !session.anyEditorHeld("pause");
|
|
239
|
+
const restartAllowed = (ctx) => ctx.view === 0 && !pauseEditMode && pauseLineFree();
|
|
240
|
+
const roleViewEditable = (ctx) => !session.anyEditorHeld("rolePromptFor", "search") &&
|
|
241
|
+
ctx.roleIds.length > 0 &&
|
|
242
|
+
ctx.view >= 1 && ctx.view <= ctx.roleIds.length;
|
|
243
|
+
const ctrlRoutes = [
|
|
244
|
+
{ name: "f", should: pauseLineFree, run: teamPauseToggle },
|
|
245
|
+
{ name: "q", should: pauseLineFree, run: stopLaterToggle },
|
|
246
|
+
{ name: "r", should: restartAllowed, run: restartNow },
|
|
247
|
+
{ name: "p", should: () => pauseEditRole !== null, run: closeRolePauseEdit },
|
|
248
|
+
{ name: "w", should: () => pauseEditRole !== null, run: closeRolePauseEdit },
|
|
249
|
+
{ name: "p", should: roleViewEditable, run: (ctx) => roleControlArm("p", ctx) },
|
|
250
|
+
{ name: "a", should: roleViewEditable, run: (ctx) => roleControlArm("a", ctx) },
|
|
251
|
+
{ name: "w", should: roleViewEditable, run: (ctx) => roleControlArm("w", ctx) },
|
|
252
|
+
{ name: "v", should: roleViewEditable, run: (ctx) => roleControlArm("v", ctx) },
|
|
253
|
+
];
|
|
254
|
+
const routeCtrl = (key, ctx) => {
|
|
255
|
+
for (const route of ctrlRoutes) {
|
|
256
|
+
if (route.name === key.name && route.should(ctx))
|
|
257
|
+
return route.run(ctx);
|
|
255
258
|
}
|
|
256
|
-
|
|
257
|
-
session.flashError(err);
|
|
258
|
-
}
|
|
259
|
+
return null;
|
|
259
260
|
};
|
|
260
|
-
const
|
|
261
|
-
// Ordered dispatch: a guard miss falls through to the next arm, then to applyKey.
|
|
262
|
-
if (key.ctrl && key.name === "f" && !session.anyEditorHeld("pause"))
|
|
263
|
-
return teamPauseToggle(ctx);
|
|
264
|
-
if (key.ctrl && key.name === "q" && !session.anyEditorHeld("pause"))
|
|
265
|
-
return stopLaterToggle(ctx);
|
|
266
|
-
if (key.ctrl && key.name === "r" && ctx.view === 0 && !pauseEditMode && !session.anyEditorHeld("pause"))
|
|
267
|
-
return restartNow(ctx);
|
|
268
|
-
if (key.ctrl && (key.name === "p" || key.name === "w") && pauseEditRole !== null)
|
|
269
|
-
return closeRolePauseEdit();
|
|
270
|
-
if (!session.anyEditorHeld("rolePromptFor", "search") &&
|
|
271
|
-
ctx.roleIds.length > 0 &&
|
|
272
|
-
ctx.view >= 1 && ctx.view <= ctx.roleIds.length &&
|
|
273
|
-
key.ctrl && (key.name === "p" || key.name === "a" || key.name === "w" || key.name === "v"))
|
|
274
|
-
return roleControlArm(key.name, ctx);
|
|
261
|
+
const editEntryArm = (key) => {
|
|
275
262
|
if (key.name === "escape" && pauseEditMode)
|
|
276
263
|
return cancelPauseEdit();
|
|
277
264
|
if (key.name === "return" && pauseEditMode)
|
|
278
265
|
return commitPauseEntry();
|
|
279
266
|
return false;
|
|
280
267
|
};
|
|
268
|
+
const handleKey = (_str, key, ctx) => {
|
|
269
|
+
// Ordered dispatch: a guard miss falls through to the next route, then to editEntryArm.
|
|
270
|
+
const ctrl = key.ctrl ? routeCtrl(key, ctx) : null;
|
|
271
|
+
if (ctrl !== null)
|
|
272
|
+
return ctrl;
|
|
273
|
+
return editEntryArm(key);
|
|
274
|
+
};
|
|
281
275
|
return { handleKey, exit: exitPauseEditMode };
|
|
282
276
|
}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import { submitRolePromptAndWake } from "../../operator/operator-intent.js";
|
|
2
2
|
import { pushPromptHistory } from "./tui-prompt-history.js";
|
|
3
3
|
import { parseRolePromptInput } from "./tui-input.js";
|
|
4
|
+
import { guardError } from "./tui-guard.js";
|
|
4
5
|
import { viewedRole } from "./tui-editor-ctx.js";
|
|
6
|
+
import { savedPromptDraft } from "./tui-editor-session.js";
|
|
5
7
|
/** The per-role prompt Ctrl+R editor on the shared prompt line. It owns its mode flag (the
|
|
6
8
|
* viewed role's role id), its saved-draft pair, its exit helper, and the handleKey arms for its
|
|
7
9
|
* key: the per-role toggle (guarded out of the other editors' prompt line) and its Esc/Enter
|
|
8
10
|
* leaves. Mutually exclusive with the other editors via the session's held registry. */
|
|
9
11
|
export function createPromptEditor(session) {
|
|
10
12
|
let rolePromptFor = null;
|
|
11
|
-
|
|
13
|
+
const draft = savedPromptDraft(session);
|
|
12
14
|
// Leave role-prompt mode (Esc, Ctrl+R again, or Ctrl+T): restore the saved prompt text,
|
|
13
15
|
// and reset the recall state with it — the saved draft was saved settled (see the Ctrl+R
|
|
14
16
|
// handler), so resuming a browse entered in role mode would restore role-mode text onto
|
|
@@ -18,7 +20,7 @@ export function createPromptEditor(session) {
|
|
|
18
20
|
return;
|
|
19
21
|
rolePromptFor = null;
|
|
20
22
|
session.held.rolePromptFor = null;
|
|
21
|
-
|
|
23
|
+
draft.restore();
|
|
22
24
|
};
|
|
23
25
|
// Ctrl+R toggles role-prompt mode for the viewed role (mnemonic: p**R**ompt — Ctrl+P is
|
|
24
26
|
// taken by pause): entering saves the director draft in this mode's own pair and blanks
|
|
@@ -30,7 +32,7 @@ export function createPromptEditor(session) {
|
|
|
30
32
|
ctx.roleIds.length > 0 &&
|
|
31
33
|
ctx.view >= 1 && ctx.view <= ctx.roleIds.length) {
|
|
32
34
|
const role = viewedRole(ctx); // defined: view is clamped inside the transcript range
|
|
33
|
-
|
|
35
|
+
guardError(session, () => {
|
|
34
36
|
if (rolePromptFor) {
|
|
35
37
|
exitRolePromptMode();
|
|
36
38
|
session.flashMessage("role prompt cancelled");
|
|
@@ -40,17 +42,14 @@ export function createPromptEditor(session) {
|
|
|
40
42
|
// recall state: settle first, so the role editor saves (and later restores) the
|
|
41
43
|
// draft the operator was writing, and no stale index/draft crosses modes —
|
|
42
44
|
// otherwise Esc here would surface role-mode recalls on the director line.
|
|
43
|
-
|
|
45
|
+
draft.save();
|
|
44
46
|
rolePromptFor = role;
|
|
45
47
|
session.held.rolePromptFor = role;
|
|
46
48
|
session.input = "";
|
|
47
49
|
session.cursor = 0;
|
|
48
50
|
session.flashMessage(`prompt for ${role}: Enter to send, Esc to cancel`);
|
|
49
51
|
}
|
|
50
|
-
}
|
|
51
|
-
catch (err) {
|
|
52
|
-
session.flashError(err);
|
|
53
|
-
}
|
|
52
|
+
});
|
|
54
53
|
return true;
|
|
55
54
|
}
|
|
56
55
|
return false;
|
|
@@ -76,15 +75,12 @@ export function createPromptEditor(session) {
|
|
|
76
75
|
session.flashMessage(parsed.error);
|
|
77
76
|
}
|
|
78
77
|
else {
|
|
79
|
-
|
|
78
|
+
guardError(session, () => {
|
|
80
79
|
submitRolePromptAndWake(session.root, role, parsed.value, undefined);
|
|
81
80
|
session.promptHistory = pushPromptHistory(session.promptHistory, parsed.value);
|
|
82
81
|
exitRolePromptMode();
|
|
83
82
|
session.flashMessage(`added to ${role}'s inbox`);
|
|
84
|
-
}
|
|
85
|
-
catch (err) {
|
|
86
|
-
session.flashError(err);
|
|
87
|
-
}
|
|
83
|
+
});
|
|
88
84
|
}
|
|
89
85
|
return true;
|
|
90
86
|
};
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { editQueuedFile, readQueuedPromptBody } from "../../inbox/inbox-edit.js";
|
|
2
2
|
import { parseRolePromptInput } from "./tui-input.js";
|
|
3
|
+
import { guardError } from "./tui-guard.js";
|
|
4
|
+
import { savedPromptDraft } from "./tui-editor-session.js";
|
|
3
5
|
/** The Queued pane's in-place row editor (Ctrl+E) on the shared prompt line. It owns its target
|
|
4
6
|
* (`queuedEditFor`), its saved-draft pair, its exit helper, and the readQueuedPromptBody /
|
|
5
7
|
* editQueuedFile leaves that pre-fill the line and write it back. Mutually exclusive with the
|
|
6
8
|
* other editors via the session's held registry. */
|
|
7
9
|
export function createQueuedEditor(session) {
|
|
8
10
|
let queuedEditFor = null;
|
|
9
|
-
|
|
11
|
+
const draft = savedPromptDraft(session);
|
|
10
12
|
// Leave the Queued pane's in-place editor (Esc, a saved edit, or Ctrl+T): restore the saved
|
|
11
13
|
// prompt text, and reset the recall state with it — the same settle contract the other
|
|
12
14
|
// editors honor.
|
|
@@ -15,7 +17,7 @@ export function createQueuedEditor(session) {
|
|
|
15
17
|
return;
|
|
16
18
|
queuedEditFor = null;
|
|
17
19
|
session.held.queued = null;
|
|
18
|
-
|
|
20
|
+
draft.restore();
|
|
19
21
|
};
|
|
20
22
|
// Ctrl+E opens the Queued pane's in-place editor on the selected row (mnemonic:
|
|
21
23
|
// **E**dit) through the same readQueuedPromptBody/editQueuedFile pair the dashboard's
|
|
@@ -31,8 +33,9 @@ export function createQueuedEditor(session) {
|
|
|
31
33
|
ctx.selectedQueued === null) {
|
|
32
34
|
return false;
|
|
33
35
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
const selected = ctx.selectedQueued; // narrowed to number by the guard above
|
|
37
|
+
guardError(session, () => {
|
|
38
|
+
const position = Math.min(selected, ctx.queuedRows.length - 1);
|
|
36
39
|
const row = ctx.queuedRows[position];
|
|
37
40
|
if (row !== undefined) {
|
|
38
41
|
const body = readQueuedPromptBody(session.root, row.role, row.file);
|
|
@@ -44,7 +47,7 @@ export function createQueuedEditor(session) {
|
|
|
44
47
|
// Mid-recall, the line holds a history entry while the real draft sits in the
|
|
45
48
|
// recall state: settle first, so the queued editor saves (and later restores)
|
|
46
49
|
// the draft the operator was writing.
|
|
47
|
-
|
|
50
|
+
draft.save();
|
|
48
51
|
queuedEditFor = { role: row.role, file: row.file };
|
|
49
52
|
session.held.queued = queuedEditFor;
|
|
50
53
|
session.input = body;
|
|
@@ -52,10 +55,7 @@ export function createQueuedEditor(session) {
|
|
|
52
55
|
session.flashMessage(`editing inbox message #${row.position} for ${row.role} — Enter to save, Esc to cancel`);
|
|
53
56
|
}
|
|
54
57
|
}
|
|
55
|
-
}
|
|
56
|
-
catch (err) {
|
|
57
|
-
session.flashError(err);
|
|
58
|
-
}
|
|
58
|
+
});
|
|
59
59
|
return true;
|
|
60
60
|
};
|
|
61
61
|
// Esc cancels the Queued pane's in-place editor, restoring the previous prompt text.
|
|
@@ -77,17 +77,14 @@ export function createQueuedEditor(session) {
|
|
|
77
77
|
else {
|
|
78
78
|
// The dispatch guards on `queuedEditFor`, so it is never null when this arm runs.
|
|
79
79
|
const target = queuedEditFor;
|
|
80
|
-
|
|
80
|
+
guardError(session, () => {
|
|
81
81
|
const outcome = editQueuedFile(session.root, target.role, target.file, parsed.value);
|
|
82
82
|
exitQueuedEdit();
|
|
83
83
|
session.refreshQueued();
|
|
84
84
|
session.flashMessage(outcome.status === "edited"
|
|
85
85
|
? `edited inbox message for ${target.role}`
|
|
86
86
|
: "the inbox message was already gone");
|
|
87
|
-
}
|
|
88
|
-
catch (err) {
|
|
89
|
-
session.flashError(err);
|
|
90
|
-
}
|
|
87
|
+
});
|
|
91
88
|
}
|
|
92
89
|
return true;
|
|
93
90
|
};
|