tumwater 0.4.0 → 0.4.2
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 +4 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +5 -2
- package/dist/src/agent/backend-traits.js +21 -0
- package/dist/src/agent/claude/claude-backend.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +181 -182
- package/dist/src/agent/claude/{write-guard-hook.js → claude-write-guard-hook.js} +1 -1
- package/dist/src/agent/claude/claude-write-guard.js +5 -4
- package/dist/src/agent/codex/codex-backend.js +1 -1
- package/dist/src/agent/codex/codex-rollout.js +48 -45
- package/dist/src/agent/codex/codex-stream.js +269 -215
- package/dist/src/agent/codex/{write-guard-hook.js → codex-write-guard-hook.js} +2 -1
- package/dist/src/agent/codex/codex-write-guard.js +3 -3
- package/dist/src/agent/image-input.js +18 -87
- 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 +2 -2
- package/dist/src/agent/opencode/opencode-config.js +2 -2
- package/dist/src/agent/opencode/opencode-stream.js +101 -92
- package/dist/src/agent/run-agent.js +76 -382
- package/dist/src/agent/run-settle.js +25 -15
- package/dist/src/agent/run-spawn.js +226 -0
- package/dist/src/agent/run-stream.js +185 -0
- package/dist/src/agent/session-ledger.js +16 -0
- package/dist/src/agent/write-guard.js +5 -3
- package/dist/src/backlog/backlog-archive.js +86 -170
- package/dist/src/backlog/backlog-cli-view.js +89 -70
- package/dist/src/backlog/backlog-eligibility.js +51 -59
- package/dist/src/backlog/backlog-file-bugs.js +133 -0
- package/dist/src/backlog/backlog-index-data.js +159 -0
- package/dist/src/backlog/backlog-index.js +7 -5
- package/dist/src/backlog/backlog-md.js +39 -28
- package/dist/src/backlog/backlog-release.js +21 -36
- package/dist/src/backlog/backlog-render.js +1 -1
- package/dist/src/backlog/backlog-structure.js +14 -145
- package/dist/src/backlog/backlog-write.js +31 -18
- package/dist/src/backlog/backlog.js +14 -4
- package/dist/src/backlog/question-answer.js +33 -15
- package/dist/src/baseline/main-baseline.js +71 -26
- package/dist/src/baseline/main-guardrails.js +76 -15
- package/dist/src/baseline/main-red.js +45 -94
- package/dist/src/baseline/main-watch.js +9 -3
- package/dist/src/brief/brief.js +40 -0
- package/dist/src/budget/budget-fallback-view.js +50 -8
- package/dist/src/budget/budget-wiring.js +15 -11
- package/dist/src/budget/budget.js +3 -2
- package/dist/src/build/build-check-events.js +4 -2
- package/dist/src/build/build-check-report.js +2 -1
- package/dist/src/build/build-check-scoped.js +70 -41
- package/dist/src/change/change-data.js +4 -2
- package/dist/src/cli/cli-args.js +9 -0
- package/dist/src/cli/cli-command-args.js +199 -98
- package/dist/src/cli/cli-flag-specs.js +6 -4
- package/dist/src/cli/cli-marker-commands.js +2 -2
- package/dist/src/cli/cli-query-commands.js +41 -6
- package/dist/src/cli/cli-review-commands.js +2 -1
- package/dist/src/cli/cli-role-command.js +46 -92
- package/dist/src/cli/cli-start.js +123 -47
- package/dist/src/cli/follow-view.js +3 -1
- package/dist/src/cli/help.js +19 -12
- package/dist/src/cli/log-commands.js +128 -107
- package/dist/src/cli/question-commands.js +82 -63
- package/dist/src/cli.js +396 -343
- package/dist/src/collections.js +46 -9
- package/dist/src/config/config-fallbacks.js +0 -4
- package/dist/src/config/config-live.js +179 -147
- package/dist/src/config/config-request.js +176 -51
- package/dist/src/config/config-validation-roles.js +271 -0
- package/dist/src/config/config-validation.js +10 -205
- package/dist/src/config/config-views.js +2 -7
- package/dist/src/config/config-write.js +205 -175
- package/dist/src/config/config.js +2 -1
- package/dist/src/doctor/doctor-backlog-bugs.js +24 -122
- package/dist/src/doctor/doctor-backlog-plans.js +24 -25
- package/dist/src/doctor/doctor-backlog.js +34 -42
- package/dist/src/doctor/doctor-check-names.js +4 -2
- package/dist/src/doctor/doctor-checks.js +16 -4
- package/dist/src/doctor/doctor-claude-checks.js +2 -2
- package/dist/src/doctor/doctor-codex-checks.js +9 -20
- package/dist/src/doctor/doctor-contention-checks.js +52 -19
- package/dist/src/doctor/doctor-holds.js +22 -20
- package/dist/src/doctor/doctor-model-checks.js +8 -184
- package/dist/src/doctor/doctor-notify.js +2 -5
- package/dist/src/doctor/doctor-opencode-checks.js +90 -41
- package/dist/src/doctor/doctor-principles.js +26 -0
- package/dist/src/doctor/doctor-project-checks.js +69 -19
- package/dist/src/doctor/doctor-role-notes.js +33 -0
- package/dist/src/doctor/doctor.js +13 -6
- package/dist/src/doctor/probe.js +1 -1
- package/dist/src/events/event-format.js +307 -162
- package/dist/src/events/event-labels.js +0 -1
- package/dist/src/events/event-legacy.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +15 -2
- package/dist/src/events/notify.js +1 -1
- package/dist/src/failure/failure-cluster.js +4 -3
- package/dist/src/failure/failure-data.js +126 -94
- package/dist/src/failure/failure-render.js +58 -17
- package/dist/src/failure/failure-state-change.js +308 -27
- package/dist/src/failure/time-spend.js +139 -106
- package/dist/src/files/files.js +7 -0
- package/dist/src/files/json-object.js +15 -2
- package/dist/src/gates/budget-gates.js +90 -68
- package/dist/src/gates/gate-polls.js +92 -61
- package/dist/src/gates/role-cap-gates.js +1 -1
- package/dist/src/gates/startup-gate.js +28 -28
- package/dist/src/gates/streak-gate.js +1 -1
- package/dist/src/git/git.js +1 -1
- package/dist/src/git/slot-salvage.js +9 -9
- package/dist/src/git/slots-state.js +12 -3
- package/dist/src/git/worktree-pool-startup.js +127 -0
- package/dist/src/git/worktree-pool.js +1 -120
- package/dist/src/gui/gui-admin-endpoints.js +3 -4
- package/dist/src/gui/gui-args.js +1 -1
- package/dist/src/gui/gui-backlog-commands.js +20 -11
- package/dist/src/gui/gui-backlog-endpoints.js +2 -8
- package/dist/src/gui/gui-config-commands.js +51 -76
- package/dist/src/gui/gui-endpoint-commands.js +4 -2
- package/dist/src/gui/gui-endpoint-common.js +4 -3
- package/dist/src/gui/gui-history-endpoints.js +36 -27
- package/dist/src/gui/gui-operator-commands.js +43 -17
- package/dist/src/gui/gui-prompt-commands.js +27 -21
- package/dist/src/gui/gui-quarantine-commands.js +30 -0
- package/dist/src/gui/gui-report-endpoints.js +13 -9
- package/dist/src/gui/gui-server.js +5 -3
- package/dist/src/gui/gui-team-commands.js +1 -1
- package/dist/src/history/history-data.js +7 -1
- package/dist/src/history/history-render.js +15 -10
- package/dist/src/history/history.js +103 -68
- package/dist/src/inbox/inbox-submit.js +1 -5
- package/dist/src/inbox/pending-prompt.js +3 -2
- package/dist/src/inbox/prompt-commands.js +194 -161
- package/dist/src/init/init.js +104 -25
- package/dist/src/merge/merge-core.js +1 -1
- package/dist/src/merge/merge-drain.js +1 -1
- package/dist/src/merge/merge-git.js +5 -5
- package/dist/src/merge/{merge-post-review.js → merge-post-review-drain.js} +1 -1
- package/dist/src/merge/merge-queue.js +2 -1
- package/dist/src/merge/merge-to-main.js +1 -1
- package/dist/src/merge/merge-verify.js +3 -2
- package/dist/src/operator/operator-commands.js +84 -52
- package/dist/src/operator/operator-intent.js +26 -3
- package/dist/src/operator/retire.js +2 -1
- package/dist/src/operator/revert.js +18 -12
- package/dist/src/orchestrator/orchestrator-poll.js +2 -2
- package/dist/src/orchestrator/orchestrator.js +1 -1
- package/dist/src/orchestrator/retention.js +6 -5
- package/dist/src/paths.js +10 -10
- package/dist/src/pi/pi-bin.js +2 -2
- package/dist/src/pi/pi-models.js +9 -0
- package/dist/src/pi/pi-stream.js +63 -41
- package/dist/src/pi/pi-watchdogs.js +84 -49
- package/dist/src/pi-extension/context-shake.js +55 -31
- package/dist/src/pi-extension/full-output.js +2 -3
- package/dist/src/process/process.js +24 -1
- package/dist/src/process/run-marker.js +2 -2
- package/dist/src/process/supervisor.js +24 -5
- package/dist/src/prompt/gate-notes.js +41 -16
- package/dist/src/prompt/notes-backlog.js +22 -29
- package/dist/src/prompt/principles.js +11 -0
- package/dist/src/prompt/prompt-followup.js +4 -12
- package/dist/src/prompt/prompt.js +15 -12
- package/dist/src/redeploy/redeploy-probes.js +11 -1
- package/dist/src/redeploy/redeployer.js +54 -7
- package/dist/src/redeploy/self-reload.js +7 -2
- package/dist/src/report/report-data.js +224 -320
- package/dist/src/report/report-fold.js +23 -231
- package/dist/src/report/report-main-red.js +70 -3
- package/dist/src/report/report-render.js +181 -48
- package/dist/src/report/report-since-data.js +218 -0
- package/dist/src/report/report-usage-fold.js +288 -0
- package/dist/src/report/report.js +2 -1
- package/dist/src/review/known-flakes.js +22 -3
- package/dist/src/review/post-merge-review-accept.js +3 -2
- package/dist/src/review/post-merge-review.js +16 -14
- package/dist/src/review/quarantine-render.js +29 -11
- package/dist/src/review/review-precheck.js +2 -1
- package/dist/src/roles/role-catalog.js +30 -29
- package/dist/src/roles/role-guidance.js +26 -43
- package/dist/src/roles/role-render.js +19 -0
- package/dist/src/roles/role-view.js +19 -5
- package/dist/src/roles/roles.js +2 -22
- package/dist/src/run/ci-parity.js +5 -6
- package/dist/src/run/feedback-filings.js +10 -1
- package/dist/src/run/qa-coverage.js +4 -4
- package/dist/src/run/run-apply.js +14 -2
- package/dist/src/run/run-detail-data.js +50 -10
- package/dist/src/run/run-detail-render.js +32 -1
- package/dist/src/run/run-detail.js +44 -22
- package/dist/src/run/run-dismiss.js +43 -0
- package/dist/src/run/run-finalize.js +75 -46
- package/dist/src/run/run-prompt.js +271 -187
- package/dist/src/run/run-resume.js +32 -31
- package/dist/src/run/run-stage.js +116 -64
- package/dist/src/run/run-timing.js +18 -4
- package/dist/src/run/run-verdict.js +119 -83
- package/dist/src/run/stage-backlog-checks.js +4 -31
- package/dist/src/run/stage-check.js +4 -12
- package/dist/src/run/stage-checks-shared.js +15 -0
- package/dist/src/run/stage-name-checks.js +3 -13
- package/dist/src/run/stage-path-checks.js +26 -29
- package/dist/src/runner/leftover.js +13 -4
- package/dist/src/runner/revision.js +2 -2
- package/dist/src/runner/runner-fallback.js +1 -1
- package/dist/src/runner/runner-outcome.js +0 -5
- package/dist/src/runner/runner-pi.js +72 -49
- package/dist/src/runner/runner-run.js +19 -102
- package/dist/src/runner/runner-state-load.js +0 -10
- package/dist/src/runner/runner.js +1 -32
- package/dist/src/scheduling/claims.js +4 -11
- package/dist/src/status/status-data.js +140 -91
- package/dist/src/status/status-polls.js +41 -5
- package/dist/src/status/status-role-notes.js +53 -0
- package/dist/src/status/status-roles.js +10 -37
- package/dist/src/text/datetime.js +8 -1
- package/dist/src/text/format.js +13 -1
- package/dist/src/text/phrases.js +16 -2
- package/dist/src/text/text.js +12 -0
- package/dist/src/ui/alert-feeds.js +115 -0
- package/dist/src/ui/badges.js +3 -4
- package/dist/src/ui/gui/gui-client-boot.js +78 -57
- package/dist/src/ui/gui/gui-client-drawer-config.js +8 -3
- package/dist/src/ui/gui/gui-client-drawer.js +18 -2
- package/dist/src/ui/gui/gui-client-feed.js +3 -8
- package/dist/src/ui/gui/gui-client-history.js +16 -16
- package/dist/src/ui/gui/gui-client-model.js +9 -9
- package/dist/src/ui/gui/gui-client-operator.js +6 -10
- package/dist/src/ui/gui/gui-client-pending.js +4 -4
- package/dist/src/ui/gui/gui-client-report-chart.js +111 -10
- package/dist/src/ui/gui/gui-client-report.js +70 -11
- package/dist/src/ui/gui/gui-client-roles.js +52 -12
- package/dist/src/ui/gui/gui-client-settings.js +31 -26
- package/dist/src/ui/gui/gui-client-team.js +28 -2
- package/dist/src/ui/gui/gui-client.js +33 -4
- package/dist/src/ui/gui/gui-icons.js +7 -1
- package/dist/src/ui/progress-data.js +2 -2
- package/dist/src/ui/run-progress-model.js +2 -2
- package/dist/src/ui/status-model.js +26 -16
- package/dist/src/ui/status-payload.js +149 -326
- package/dist/src/ui/status-render.js +131 -80
- package/dist/src/ui/team-alert-backlog.js +236 -0
- package/dist/src/ui/team-alert-shared.js +92 -0
- package/dist/src/ui/team-alert-status.js +326 -0
- package/dist/src/ui/team-alert-tail.js +238 -0
- package/dist/src/ui/team-alerts.js +16 -984
- package/dist/src/ui/transcript-tail.js +137 -94
- package/dist/src/ui/tui/tui-backlog.js +1 -1
- package/dist/src/ui/tui/tui-editor-answer.js +54 -44
- package/dist/src/ui/tui/tui-editor-attach.js +74 -63
- package/dist/src/ui/tui/tui-editor-backends.js +62 -56
- package/dist/src/ui/tui/tui-editor-brief.js +64 -54
- package/dist/src/ui/tui/tui-editor-budget.js +106 -89
- package/dist/src/ui/tui/tui-editor-config.js +2 -4
- package/dist/src/ui/tui/tui-editor-ctx.js +12 -1
- package/dist/src/ui/tui/tui-editor-entry.js +171 -153
- package/dist/src/ui/tui/tui-editor-instances.js +2 -1
- package/dist/src/ui/tui/tui-editor-instructions.js +50 -107
- package/dist/src/ui/tui/tui-editor-interval.js +39 -112
- package/dist/src/ui/tui/tui-editor-notes.js +28 -84
- package/dist/src/ui/tui/tui-editor-pause.js +210 -183
- package/dist/src/ui/tui/tui-editor-prompt.js +46 -35
- package/dist/src/ui/tui/tui-editor-queued.js +74 -64
- package/dist/src/ui/tui/tui-editor-quiet.js +122 -108
- package/dist/src/ui/tui/tui-editor-release.js +85 -0
- package/dist/src/ui/tui/tui-editor-retire.js +57 -46
- package/dist/src/ui/tui/tui-editor-role-enabled.js +90 -80
- package/dist/src/ui/tui/tui-editor-role-file.js +87 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +50 -42
- package/dist/src/ui/tui/tui-editor-search.js +70 -58
- package/dist/src/ui/tui/tui-editor-session.js +3 -0
- package/dist/src/ui/tui/tui-editors.js +4 -1
- package/dist/src/ui/tui/tui-frame.js +7 -3
- package/dist/src/ui/tui/tui-history.js +12 -10
- package/dist/src/ui/tui/tui-keys.js +6 -1
- package/dist/src/ui/tui/tui-pane-activity.js +62 -50
- package/dist/src/ui/tui/tui-pane-backlog.js +4 -4
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +45 -26
- package/dist/src/ui/tui/tui-pane-history.js +296 -264
- package/dist/src/ui/tui/tui-pane-report.js +74 -57
- package/dist/src/ui/tui/tui-pane-session.js +6 -2
- package/dist/src/ui/tui/tui-pane-transcript.js +158 -131
- package/dist/src/ui/tui/tui-pane.js +201 -174
- package/dist/src/ui/tui/tui-panes.js +211 -148
- package/dist/src/ui/tui/tui.js +4 -4
- package/dist/src/verdict/fix-claim.js +18 -6
- package/dist/src/verdict/refusal.js +32 -7
- package/package.json +1 -1
- package/dist/src/doctor/image-probe.js +0 -257
- /package/dist/src/gui/{http-body.js → gui-http-body.js} +0 -0
- /package/dist/src/merge/{backlog-conflicts.js → merge-backlog-conflicts.js} +0 -0
|
@@ -29,97 +29,107 @@ export function createRoleEnabledEditor(session) {
|
|
|
29
29
|
session.held.roleEnabled = false;
|
|
30
30
|
restoreDraft();
|
|
31
31
|
};
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
exitRoleEnabledMode();
|
|
43
|
-
return true;
|
|
44
|
-
}
|
|
45
|
-
if (ctx.view !== ctx.roleIds.length + 7)
|
|
46
|
-
return false; // off Health, keep the key's other meanings
|
|
47
|
-
if (session.editorEntryDenied("roleEnabled"))
|
|
48
|
-
return true;
|
|
49
|
-
restoreDraft = session.takeDraft();
|
|
50
|
-
roleEnabledMode = true;
|
|
51
|
-
session.held.roleEnabled = true;
|
|
52
|
-
session.input = "";
|
|
53
|
-
session.cursor = 0;
|
|
54
|
-
session.flashMessage("enable or disable a role: type the role id, Enter to toggle, Esc to cancel");
|
|
32
|
+
// Ctrl+V toggles enable/disable-edit mode on the Health view's prompt line (the same key
|
|
33
|
+
// the Activity attach, Pending top, History revert, Inbox top, and Transcript
|
|
34
|
+
// reset-counters arms use on their own views, guarded away here by the Health index). The
|
|
35
|
+
// line edits one role id; Enter toggles that role's `roles.<base>.enabled` through the
|
|
36
|
+
// same setConfigKey core the CLI, the Settings card and the drawer use. Entering saves
|
|
37
|
+
// the prompt draft and blanks the line; leaving restores it. While open, Ctrl+V again
|
|
38
|
+
// exits.
|
|
39
|
+
const roleEnabledKey = (ctx) => {
|
|
40
|
+
if (roleEnabledMode) {
|
|
41
|
+
exitRoleEnabledMode();
|
|
55
42
|
return true;
|
|
56
43
|
}
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
44
|
+
if (ctx.view !== ctx.roleIds.length + 7)
|
|
45
|
+
return false; // off Health, keep the key's other meanings
|
|
46
|
+
if (session.editorEntryDenied("roleEnabled"))
|
|
47
|
+
return true;
|
|
48
|
+
restoreDraft = session.takeDraft();
|
|
49
|
+
roleEnabledMode = true;
|
|
50
|
+
session.held.roleEnabled = true;
|
|
51
|
+
session.input = "";
|
|
52
|
+
session.cursor = 0;
|
|
53
|
+
session.flashMessage("enable or disable a role: type the role id, Enter to toggle, Esc to cancel");
|
|
54
|
+
return true;
|
|
55
|
+
};
|
|
56
|
+
// Esc cancels enable/disable-edit mode, restoring the previous prompt text and toggling
|
|
57
|
+
// nothing.
|
|
58
|
+
const cancelRoleEnabledEdit = () => {
|
|
59
|
+
exitRoleEnabledMode();
|
|
60
|
+
session.flashMessage("enable/disable cancelled");
|
|
61
|
+
return true;
|
|
62
|
+
};
|
|
63
|
+
// Enter toggles the typed role's base role's `roles.<base>.enabled` — the same core, and
|
|
64
|
+
// the same director/safety rules, the CLI's `tumwater role <id> --enable|--disable`, the
|
|
65
|
+
// dashboard Settings card's Enable and the role drawer's two-click Disable use. A
|
|
66
|
+
// config-disabled role is enabled at once; an enabled one is armed (this is the first of
|
|
67
|
+
// the drawer's two clicks) and a second Enter on the same base disables it. An empty id,
|
|
68
|
+
// an unknown id, a director disable, or a failed write flashes and STAYS in the editor so the
|
|
69
|
+
// operator can fix the id.
|
|
70
|
+
const commitRoleEnabledEntry = () => {
|
|
71
|
+
const role = session.input.trim();
|
|
72
|
+
if (role === "") {
|
|
73
|
+
session.flashMessage("a role id is required");
|
|
62
74
|
return true;
|
|
63
75
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
const enabled = cfg.roles[base]?.enabled !== false;
|
|
85
|
-
if (base === DIRECTOR_ROLE && enabled) {
|
|
86
|
-
// Only disabling is refused, matching the CLI (`--disable` fails for the director, while
|
|
87
|
-
// `--enable` stays allowed) and the Settings card. The director is always enabled (the
|
|
88
|
-
// CLI and this editor never write it disabled), so an enabled director is refused before
|
|
89
|
-
// the disable arm is offered; a config-disabled director would still enable at once below.
|
|
90
|
-
session.flashMessage("the director cannot be disabled");
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
if (!enabled) {
|
|
94
|
-
// A config-disabled role: enable it at once (the Settings card's Enable and the CLI's
|
|
95
|
-
// --enable both enable directly, with no confirm step).
|
|
96
|
-
const result = setConfigKey(session.root, `roles.${base}.enabled`, "true");
|
|
97
|
-
if (!result.ok) {
|
|
98
|
-
session.flashMessage(result.error);
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
exitRoleEnabledMode();
|
|
102
|
-
session.flashMessage(`${base}: enabled`);
|
|
103
|
-
return true;
|
|
104
|
-
}
|
|
105
|
-
// An enabled role: the drawer's two-click Disable — the first Enter arms, a second Enter
|
|
106
|
-
// on the same base writes `false`. The confirm is keyed to the base the line currently
|
|
107
|
-
// names, so an id changed — or a key that edits it — between the two Enters re-arms
|
|
108
|
-
// rather than disabling what the line no longer shows.
|
|
109
|
-
if (confirmBase !== base) {
|
|
110
|
-
confirmBase = base;
|
|
111
|
-
session.flashMessage(`press Enter again to disable ${base} — any other role id or Esc keeps it enabled`);
|
|
112
|
-
return true;
|
|
113
|
-
}
|
|
114
|
-
const result = setConfigKey(session.root, `roles.${base}.enabled`, "false");
|
|
76
|
+
const cfg = loadConfigSafe(session.root).config ?? defaultConfig();
|
|
77
|
+
const validIds = knownRoleIds(cfg);
|
|
78
|
+
if (!validIds.includes(role)) {
|
|
79
|
+
session.flashMessage(unknownRoleMessage(role, validIds));
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
const base = baseRoleOf(role);
|
|
83
|
+
const enabled = cfg.roles[base]?.enabled !== false;
|
|
84
|
+
if (base === DIRECTOR_ROLE && enabled) {
|
|
85
|
+
// Only disabling is refused, matching the CLI (`--disable` fails for the director, while
|
|
86
|
+
// `--enable` stays allowed) and the Settings card. The director is always enabled (the
|
|
87
|
+
// CLI and this editor never write it disabled), so an enabled director is refused before
|
|
88
|
+
// the disable arm is offered; a config-disabled director would still enable at once below.
|
|
89
|
+
session.flashMessage("the director cannot be disabled");
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
if (!enabled) {
|
|
93
|
+
// A config-disabled role: enable it at once (the Settings card's Enable and the CLI's
|
|
94
|
+
// --enable both enable directly, with no confirm step).
|
|
95
|
+
const result = setConfigKey(session.root, `roles.${base}.enabled`, "true");
|
|
115
96
|
if (!result.ok) {
|
|
116
97
|
session.flashMessage(result.error);
|
|
117
98
|
return true;
|
|
118
99
|
}
|
|
119
100
|
exitRoleEnabledMode();
|
|
120
|
-
|
|
101
|
+
flashRoleEnabled(base, true);
|
|
121
102
|
return true;
|
|
122
103
|
}
|
|
104
|
+
// An enabled role: the drawer's two-click Disable — the first Enter arms, a second Enter
|
|
105
|
+
// on the same base writes `false`. The confirm is keyed to the base the line currently
|
|
106
|
+
// names, so an id changed — or a key that edits it — between the two Enters re-arms
|
|
107
|
+
// rather than disabling what the line no longer shows.
|
|
108
|
+
if (confirmBase !== base) {
|
|
109
|
+
confirmBase = base;
|
|
110
|
+
session.flashMessage(`press Enter again to disable ${base} — any other role id or Esc keeps it enabled`);
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
const result = setConfigKey(session.root, `roles.${base}.enabled`, "false");
|
|
114
|
+
if (!result.ok) {
|
|
115
|
+
session.flashMessage(result.error);
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
exitRoleEnabledMode();
|
|
119
|
+
flashRoleEnabled(base, false);
|
|
120
|
+
return true;
|
|
121
|
+
};
|
|
122
|
+
// The Enter success flash: names the base and which way it was toggled.
|
|
123
|
+
const flashRoleEnabled = (base, enabled) => {
|
|
124
|
+
session.flashMessage(enabled ? `${base}: enabled` : `${base}: disabled`);
|
|
125
|
+
};
|
|
126
|
+
const handleKey = (_str, key, ctx) => {
|
|
127
|
+
if (key.ctrl && key.name === "v")
|
|
128
|
+
return roleEnabledKey(ctx);
|
|
129
|
+
if (key.name === "escape" && roleEnabledMode)
|
|
130
|
+
return cancelRoleEnabledEdit();
|
|
131
|
+
if (key.name === "return" && roleEnabledMode)
|
|
132
|
+
return commitRoleEnabledEntry();
|
|
123
133
|
return false;
|
|
124
134
|
};
|
|
125
135
|
return { handleKey, exit: exitRoleEnabledMode };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { viewedRole } from "./tui-editor-ctx.js";
|
|
2
|
+
import { readEditorPath } from "./tui-editor-session.js";
|
|
3
|
+
/** Build a role-located file-read editor. It takes the line only on a role's Transcript /
|
|
4
|
+
* notes view (the caller's mayOpen guard) and only while no other editor holds the line, reads
|
|
5
|
+
* the typed path whole, and writes through the caller's shared setter. A refused write flashes
|
|
6
|
+
* and STAYS in edit mode so the operator can fix the path; success repaints the notes and
|
|
7
|
+
* restores the saved prompt text. */
|
|
8
|
+
export function createRoleFileEditor(session, spec) {
|
|
9
|
+
// The role pinned when the editor opened, or null when closed.
|
|
10
|
+
let editFor = null;
|
|
11
|
+
let restoreDraft = () => { };
|
|
12
|
+
// Leave the editor (Esc, the key again, or a successful Enter): restore the saved prompt
|
|
13
|
+
// text with no write.
|
|
14
|
+
const exit = () => {
|
|
15
|
+
if (editFor === null)
|
|
16
|
+
return;
|
|
17
|
+
editFor = null;
|
|
18
|
+
session.held[spec.held] = null;
|
|
19
|
+
restoreDraft();
|
|
20
|
+
};
|
|
21
|
+
// The Ctrl+<openCtrl> open/toggle arm: toggles the editor out while open; otherwise opens on
|
|
22
|
+
// the guarded view and elsewhere keeps its own meaning and falls through to the pane arms.
|
|
23
|
+
const openRoleFileEdit = (ctx) => {
|
|
24
|
+
// A view/state guard miss falls through so the key keeps its own meaning on the pane arms.
|
|
25
|
+
if (!spec.mayOpen(ctx))
|
|
26
|
+
return false;
|
|
27
|
+
// The key toggles the editor out while open; otherwise it opens on the guarded view and
|
|
28
|
+
// elsewhere keeps its own meaning and falls through to the pane arms.
|
|
29
|
+
if (session.editorEntryDenied(spec.held))
|
|
30
|
+
return true;
|
|
31
|
+
if (editFor !== null) {
|
|
32
|
+
exit();
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
// Mid-recall, hand the arrows' saved draft back before the editor takes the line — the
|
|
36
|
+
// same settle the role editors do — so leaving restores the draft, not a history entry,
|
|
37
|
+
// with no stale recall state riding along.
|
|
38
|
+
const role = viewedRole(ctx); // defined: the open-arm range guard above pins the view into roleIds
|
|
39
|
+
restoreDraft = session.takeDraft();
|
|
40
|
+
editFor = role;
|
|
41
|
+
session.held[spec.held] = role;
|
|
42
|
+
session.input = "";
|
|
43
|
+
session.cursor = 0;
|
|
44
|
+
session.flashMessage(spec.openFlash(role));
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
48
|
+
// Esc cancels the editor, restoring the previous prompt text and writing nothing.
|
|
49
|
+
const cancelRoleFileEdit = () => {
|
|
50
|
+
exit();
|
|
51
|
+
session.flashMessage(spec.cancelFlash);
|
|
52
|
+
return true;
|
|
53
|
+
};
|
|
54
|
+
// Enter reads the typed path whole and commits it through the caller's shared setter — the
|
|
55
|
+
// same setConfigKey/saveRoleNote write the CLI's non-interactive replacements and the
|
|
56
|
+
// dashboard drawer make, so the caps and their wording cannot drift. A missing or unreadable
|
|
57
|
+
// path (readEditorPath) or a refused write flashes and STAYS in the editor so the operator
|
|
58
|
+
// can fix the path; success repaints the notes and exits with the shared success wording.
|
|
59
|
+
const submitRoleFileEdit = () => {
|
|
60
|
+
const role = editFor;
|
|
61
|
+
if (role === null)
|
|
62
|
+
return true;
|
|
63
|
+
const text = readEditorPath(session);
|
|
64
|
+
if (text === null)
|
|
65
|
+
return true;
|
|
66
|
+
const outcome = spec.commit(session, role, text);
|
|
67
|
+
if (outcome.ok) {
|
|
68
|
+
spec.refresh(session, role);
|
|
69
|
+
exit();
|
|
70
|
+
session.flashMessage(outcome.message);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
spec.flashFailure(session, outcome.reason);
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
};
|
|
77
|
+
const handleKey = (_str, key, ctx) => {
|
|
78
|
+
if (key.ctrl && key.name === spec.openCtrl)
|
|
79
|
+
return openRoleFileEdit(ctx);
|
|
80
|
+
if (key.name === "escape" && editFor !== null)
|
|
81
|
+
return cancelRoleFileEdit();
|
|
82
|
+
if (key.name === "return" && editFor !== null)
|
|
83
|
+
return submitRoleFileEdit();
|
|
84
|
+
return false;
|
|
85
|
+
};
|
|
86
|
+
return { handleKey, exit };
|
|
87
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { baseRoleOf } from "../../roles/runner-ids.js";
|
|
2
|
+
import { viewedRole } from "./tui-editor-ctx.js";
|
|
2
3
|
/** Build a role-pinned editor. It may take the line only on a role's Transcript view
|
|
3
4
|
* (view 1..roleIds.length) or when it already holds it (the toggle-out arm), and only while no
|
|
4
5
|
* other editor holds the line. An invalid value flashes and STAYS in edit mode so the operator
|
|
@@ -18,53 +19,60 @@ export function createRoleKeyEditor(session, spec) {
|
|
|
18
19
|
restoreDraft();
|
|
19
20
|
lockedRole = null;
|
|
20
21
|
};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
exit();
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
if (!(ctx.view >= 1 && ctx.view <= ctx.roleIds.length) || spec.yieldsOn?.(ctx))
|
|
30
|
-
return false;
|
|
31
|
-
const role = ctx.roleIds[ctx.view - 1];
|
|
32
|
-
if (session.editorEntryDenied(spec.mode))
|
|
33
|
-
return true;
|
|
34
|
-
// Pin the role first so the write below is safe against a mid-edit roster shift, then
|
|
35
|
-
// pre-fill from the file's own entry — one fresh read per open is the smallest honest
|
|
36
|
-
// source; a broken config degrades to no pre-fill and the write reports the error.
|
|
37
|
-
lockedRole = role;
|
|
38
|
-
const existing = spec.prefill(session, baseRoleOf(role));
|
|
39
|
-
restoreDraft = session.takeDraft();
|
|
40
|
-
mode = true;
|
|
41
|
-
session.held[spec.mode] = true;
|
|
42
|
-
session.input = existing;
|
|
43
|
-
session.cursor = session.input.length;
|
|
44
|
-
session.flashMessage(spec.openFlash(role));
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
if (key.name === "escape" && mode) {
|
|
48
|
-
// Esc cancels the editor, restoring the previous prompt text.
|
|
22
|
+
// The Ctrl+<key> arm: while open it toggles the editor out. Otherwise it opens only on a
|
|
23
|
+
// role's Transcript view; elsewhere it keeps its own meaning and falls through to the pane
|
|
24
|
+
// arms.
|
|
25
|
+
const openRoleKeyEdit = (ctx) => {
|
|
26
|
+
if (mode) {
|
|
49
27
|
exit();
|
|
50
28
|
return true;
|
|
51
29
|
}
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const role = lockedRole;
|
|
57
|
-
const text = session.input.trim();
|
|
58
|
-
const result = spec.commit(session, baseRoleOf(role), text);
|
|
59
|
-
if (result.ok) {
|
|
60
|
-
exit();
|
|
61
|
-
session.flashMessage(result.noop && spec.noopFlash ? spec.noopFlash(role, text) : spec.savedFlash(role, text));
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
session.flashMessage(result.error); // broken config or write failure — stay in edit mode
|
|
65
|
-
}
|
|
30
|
+
if (!(ctx.view >= 1 && ctx.view <= ctx.roleIds.length) || spec.yieldsOn?.(ctx))
|
|
31
|
+
return false;
|
|
32
|
+
const role = viewedRole(ctx);
|
|
33
|
+
if (session.editorEntryDenied(spec.mode))
|
|
66
34
|
return true;
|
|
35
|
+
// Pin the role first so the write below is safe against a mid-edit roster shift, then
|
|
36
|
+
// pre-fill from the file's own entry — one fresh read per open is the smallest honest
|
|
37
|
+
// source; a broken config degrades to no pre-fill and the write reports the error.
|
|
38
|
+
lockedRole = role;
|
|
39
|
+
const existing = spec.prefill(session, baseRoleOf(role));
|
|
40
|
+
restoreDraft = session.takeDraft();
|
|
41
|
+
mode = true;
|
|
42
|
+
session.held[spec.mode] = true;
|
|
43
|
+
session.input = existing;
|
|
44
|
+
session.cursor = session.input.length;
|
|
45
|
+
session.flashMessage(spec.openFlash(role));
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
48
|
+
// Esc cancels the editor, restoring the previous prompt text.
|
|
49
|
+
const cancelRoleKeyEdit = () => {
|
|
50
|
+
exit();
|
|
51
|
+
return true;
|
|
52
|
+
};
|
|
53
|
+
// Enter commits the whitespace-trimmed line through the spec's shared setter. A rejected
|
|
54
|
+
// value flashes and STAYS in edit mode so the operator can fix it; success restores the
|
|
55
|
+
// saved prompt text.
|
|
56
|
+
const submitRoleKeyEdit = () => {
|
|
57
|
+
const role = lockedRole;
|
|
58
|
+
const text = session.input.trim();
|
|
59
|
+
const result = spec.commit(session, baseRoleOf(role), text);
|
|
60
|
+
if (result.ok) {
|
|
61
|
+
exit();
|
|
62
|
+
session.flashMessage(result.noop && spec.noopFlash ? spec.noopFlash(role, text) : spec.savedFlash(role, text));
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
session.flashMessage(result.error); // broken config or write failure — stay in edit mode
|
|
67
66
|
}
|
|
67
|
+
return true;
|
|
68
|
+
};
|
|
69
|
+
const handleKey = (_str, key, ctx) => {
|
|
70
|
+
if (key.ctrl && key.name === spec.openCtrl)
|
|
71
|
+
return openRoleKeyEdit(ctx);
|
|
72
|
+
if (key.name === "escape" && mode)
|
|
73
|
+
return cancelRoleKeyEdit();
|
|
74
|
+
if (key.name === "return" && mode)
|
|
75
|
+
return submitRoleKeyEdit();
|
|
68
76
|
return false;
|
|
69
77
|
};
|
|
70
78
|
return { handleKey, exit };
|
|
@@ -20,65 +20,77 @@ export function createSearchEditor(session) {
|
|
|
20
20
|
session.held.search = false;
|
|
21
21
|
restoreDraft();
|
|
22
22
|
};
|
|
23
|
+
// `/` opens a free-text search on the prompt line (the pager's search key; this project
|
|
24
|
+
// already reserves `/` for a meta action): over the History pane's run rows on History,
|
|
25
|
+
// over the Activity feed's events on Activity, and over the Backlog pane's active entries
|
|
26
|
+
// on Backlog, over the Inbox pane's queued prompts, and over a role's live transcript.
|
|
27
|
+
// Guarded to those views and to an empty line, so elsewhere it types into the prompt as
|
|
28
|
+
// today and a director draft is never disturbed. Pre-fills the applied pattern so Enter
|
|
29
|
+
// re-submits it and the operator can edit it; Esc cancels without applying.
|
|
30
|
+
const openSearchEdit = (ctx) => {
|
|
31
|
+
if (searchMode || session.anyEditorHeld() || session.input !== "" ||
|
|
32
|
+
!(ctx.view === 0 || ctx.view === ctx.roleIds.length + 1 || ctx.view === ctx.roleIds.length + 4 || ctx.view === ctx.roleIds.length + 6 ||
|
|
33
|
+
(ctx.view >= 1 && ctx.view <= ctx.roleIds.length && !ctx.noteMode && !ctx.changeMode)))
|
|
34
|
+
return false;
|
|
35
|
+
restoreDraft = session.takeDraft();
|
|
36
|
+
if (ctx.view === 0)
|
|
37
|
+
searchFor = "activity";
|
|
38
|
+
else if (ctx.view === ctx.roleIds.length + 1)
|
|
39
|
+
searchFor = "backlog";
|
|
40
|
+
else if (ctx.view === ctx.roleIds.length + 4)
|
|
41
|
+
searchFor = "history";
|
|
42
|
+
else if (ctx.view === ctx.roleIds.length + 6)
|
|
43
|
+
searchFor = "queued";
|
|
44
|
+
else
|
|
45
|
+
searchFor = "transcript";
|
|
46
|
+
searchMode = true;
|
|
47
|
+
session.held.search = true;
|
|
48
|
+
const applied = { activity: ctx.activityGrep, history: ctx.trailSearch ? ctx.trailGrep : ctx.historyGrep, backlog: ctx.backlogGrep, queued: ctx.queuedGrep, transcript: ctx.transcriptGrep }[searchFor];
|
|
49
|
+
const noun = { activity: "events", history: ctx.trailSearch ? "transcript" : "runs", backlog: "backlog", queued: "inbox", transcript: "transcript" }[searchFor];
|
|
50
|
+
session.input = applied ?? "";
|
|
51
|
+
session.cursor = session.input.length;
|
|
52
|
+
session.flashMessage(`search ${noun}: Enter to filter, Esc to cancel`);
|
|
53
|
+
return true;
|
|
54
|
+
};
|
|
55
|
+
// Esc cancels the History search without applying it; the applied pattern is unchanged.
|
|
56
|
+
const cancelSearchEdit = () => {
|
|
57
|
+
if (!searchMode)
|
|
58
|
+
return false;
|
|
59
|
+
exitHistorySearch();
|
|
60
|
+
return true;
|
|
61
|
+
};
|
|
62
|
+
// Enter applies the trimmed line to the pane the editor opened over: Activity sets
|
|
63
|
+
// activityGrep (an empty line clears it) and resets the scroll to the live tail — the
|
|
64
|
+
// offset indexed the old filtered list; History recomputes its row cache now (the pane
|
|
65
|
+
// is showing) and re-enters at its head; Backlog sets backlogGrep and clears its entry
|
|
66
|
+
// selection so the next frame starts in list mode; Inbox sets queuedGrep, clears its row
|
|
67
|
+
// selection, and re-reads the filtered rows. A successful apply restores the saved
|
|
68
|
+
// prompt draft through exitHistorySearch, the shared editor's exit.
|
|
69
|
+
const submitSearchEdit = () => {
|
|
70
|
+
if (!searchMode)
|
|
71
|
+
return false;
|
|
72
|
+
const applied = session.input.trim() === "" ? null : session.input.trim();
|
|
73
|
+
if (searchFor === "activity")
|
|
74
|
+
session.applyActivitySearch(applied);
|
|
75
|
+
else if (searchFor === "history")
|
|
76
|
+
session.applyHistorySearch(applied);
|
|
77
|
+
else if (searchFor === "backlog")
|
|
78
|
+
session.applyBacklogSearch(applied);
|
|
79
|
+
else if (searchFor === "queued")
|
|
80
|
+
session.applyQueuedSearch(applied);
|
|
81
|
+
else
|
|
82
|
+
session.applyTranscriptSearch(applied);
|
|
83
|
+
exitHistorySearch();
|
|
84
|
+
session.flashMessage(applied === null ? "search cleared" : `search: "${applied}"`);
|
|
85
|
+
return true;
|
|
86
|
+
};
|
|
23
87
|
const handleKey = (str, key, ctx) => {
|
|
24
|
-
if (!key.ctrl && str === "/"
|
|
25
|
-
(ctx
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
// on Backlog, over the Inbox pane's queued prompts, and over a role's live transcript.
|
|
31
|
-
// Guarded to those views and to an empty line, so elsewhere it types into the prompt as
|
|
32
|
-
// today and a director draft is never disturbed. Pre-fills the applied pattern so Enter
|
|
33
|
-
// re-submits it and the operator can edit it; Esc cancels without applying.
|
|
34
|
-
restoreDraft = session.takeDraft();
|
|
35
|
-
if (ctx.view === 0)
|
|
36
|
-
searchFor = "activity";
|
|
37
|
-
else if (ctx.view === ctx.roleIds.length + 1)
|
|
38
|
-
searchFor = "backlog";
|
|
39
|
-
else if (ctx.view === ctx.roleIds.length + 4)
|
|
40
|
-
searchFor = "history";
|
|
41
|
-
else if (ctx.view === ctx.roleIds.length + 6)
|
|
42
|
-
searchFor = "queued";
|
|
43
|
-
else
|
|
44
|
-
searchFor = "transcript";
|
|
45
|
-
searchMode = true;
|
|
46
|
-
session.held.search = true;
|
|
47
|
-
const applied = { activity: ctx.activityGrep, history: ctx.trailSearch ? ctx.trailGrep : ctx.historyGrep, backlog: ctx.backlogGrep, queued: ctx.queuedGrep, transcript: ctx.transcriptGrep }[searchFor];
|
|
48
|
-
const noun = { activity: "events", history: ctx.trailSearch ? "transcript" : "runs", backlog: "backlog", queued: "inbox", transcript: "transcript" }[searchFor];
|
|
49
|
-
session.input = applied ?? "";
|
|
50
|
-
session.cursor = session.input.length;
|
|
51
|
-
session.flashMessage(`search ${noun}: Enter to filter, Esc to cancel`);
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
if (key.name === "escape" && searchMode) {
|
|
55
|
-
// Esc cancels the History search without applying it; the applied pattern is unchanged.
|
|
56
|
-
exitHistorySearch();
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
if (key.name === "return" && searchMode) {
|
|
60
|
-
// Enter applies the trimmed line to the pane the editor opened over: Activity sets
|
|
61
|
-
// activityGrep (an empty line clears it) and resets the scroll to the live tail — the
|
|
62
|
-
// offset indexed the old filtered list; History recomputes its row cache now (the pane
|
|
63
|
-
// is showing) and re-enters at its head; Backlog sets backlogGrep and clears its entry
|
|
64
|
-
// selection so the next frame starts in list mode; Inbox sets queuedGrep, clears its row
|
|
65
|
-
// selection, and re-reads the filtered rows. A successful apply restores the saved
|
|
66
|
-
// prompt draft through exitHistorySearch, the shared editor's exit.
|
|
67
|
-
const applied = session.input.trim() === "" ? null : session.input.trim();
|
|
68
|
-
if (searchFor === "activity")
|
|
69
|
-
session.applyActivitySearch(applied);
|
|
70
|
-
else if (searchFor === "history")
|
|
71
|
-
session.applyHistorySearch(applied);
|
|
72
|
-
else if (searchFor === "backlog")
|
|
73
|
-
session.applyBacklogSearch(applied);
|
|
74
|
-
else if (searchFor === "queued")
|
|
75
|
-
session.applyQueuedSearch(applied);
|
|
76
|
-
else
|
|
77
|
-
session.applyTranscriptSearch(applied);
|
|
78
|
-
exitHistorySearch();
|
|
79
|
-
session.flashMessage(applied === null ? "search cleared" : `search: "${applied}"`);
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
88
|
+
if (!key.ctrl && str === "/")
|
|
89
|
+
return openSearchEdit(ctx);
|
|
90
|
+
if (key.name === "escape" && searchMode)
|
|
91
|
+
return cancelSearchEdit();
|
|
92
|
+
if (key.name === "return" && searchMode)
|
|
93
|
+
return submitSearchEdit();
|
|
82
94
|
return false;
|
|
83
95
|
};
|
|
84
96
|
return { handleKey, exit: exitHistorySearch };
|
|
@@ -7,6 +7,7 @@ const HOLDER_SUBJECTS = [
|
|
|
7
7
|
["notes", "notes edit"],
|
|
8
8
|
["brief", "brief edit"],
|
|
9
9
|
["retire", "retire role"],
|
|
10
|
+
["release", "release test"],
|
|
10
11
|
["roleEnabled", "role state"],
|
|
11
12
|
["quiet", "quiet hours"],
|
|
12
13
|
["model", "model edit"],
|
|
@@ -48,6 +49,7 @@ export function createTuiEditorSession(deps) {
|
|
|
48
49
|
notes: null,
|
|
49
50
|
brief: false,
|
|
50
51
|
retire: false,
|
|
52
|
+
release: false,
|
|
51
53
|
roleEnabled: false,
|
|
52
54
|
quiet: false,
|
|
53
55
|
quietTeam: false,
|
|
@@ -72,6 +74,7 @@ export function createTuiEditorSession(deps) {
|
|
|
72
74
|
refreshQueued: deps.refreshQueued,
|
|
73
75
|
refreshNotes: deps.refreshNotes,
|
|
74
76
|
refreshBrief: deps.refreshBrief,
|
|
77
|
+
refreshReport: deps.refreshReport,
|
|
75
78
|
clearBacklogSelection: deps.clearBacklogSelection,
|
|
76
79
|
applyHistorySearch: deps.applyHistorySearch,
|
|
77
80
|
applyActivitySearch: deps.applyActivitySearch,
|
|
@@ -22,6 +22,7 @@ import { createAttachEditor } from "./tui-editor-attach.js";
|
|
|
22
22
|
import { createNotesEditor } from "./tui-editor-notes.js";
|
|
23
23
|
import { createBriefEditor } from "./tui-editor-brief.js";
|
|
24
24
|
import { createRetireEditor } from "./tui-editor-retire.js";
|
|
25
|
+
import { createQuarantineReleaseEditor } from "./tui-editor-release.js";
|
|
25
26
|
import { createRoleEnabledEditor } from "./tui-editor-role-enabled.js";
|
|
26
27
|
import { createPromptEditor } from "./tui-editor-prompt.js";
|
|
27
28
|
import { createPauseEditor } from "./tui-editor-pause.js";
|
|
@@ -70,6 +71,7 @@ export function createTuiEditors(deps) {
|
|
|
70
71
|
createSearchEditor(session),
|
|
71
72
|
createBriefEditor(session),
|
|
72
73
|
createRetireEditor(session),
|
|
74
|
+
createQuarantineReleaseEditor(session),
|
|
73
75
|
createRoleEnabledEditor(session),
|
|
74
76
|
];
|
|
75
77
|
const handleKey = (str, key, ctx) => {
|
|
@@ -120,7 +122,7 @@ export function createTuiEditors(deps) {
|
|
|
120
122
|
// above already returned), and budget, pause and search modes keep them out —
|
|
121
123
|
// recalling a prompt into the cap/field text would be a paste, not a recall. No
|
|
122
124
|
// history in this direction: fall through to applyKey, which ignores arrows as before.
|
|
123
|
-
if (!session.held.budget && !session.held.concurrency && !session.held.checks && !session.held.check && !session.held.slots && !session.held.model && !session.held.quiet && !session.held.interval && !session.held.roleModel && !session.held.instances && !session.held.instructions && !session.held.attach && !session.held.pause && !session.held.search && !session.held.notes && !session.held.brief && !session.held.retire && !session.held.roleEnabled && !session.held.providerCaps && !session.held.notify && !session.held.backends && !session.held.fallback && !session.held.claudeReserve) {
|
|
125
|
+
if (!session.held.budget && !session.held.concurrency && !session.held.checks && !session.held.check && !session.held.slots && !session.held.model && !session.held.quiet && !session.held.interval && !session.held.roleModel && !session.held.instances && !session.held.instructions && !session.held.attach && !session.held.pause && !session.held.search && !session.held.notes && !session.held.brief && !session.held.retire && !session.held.release && !session.held.roleEnabled && !session.held.providerCaps && !session.held.notify && !session.held.backends && !session.held.fallback && !session.held.claudeReserve) {
|
|
124
126
|
const recalled = recallPromptHistory(session.promptHistory, session.input, session.cursor, direction === "up" ? "older" : "newer");
|
|
125
127
|
if (recalled) {
|
|
126
128
|
session.promptHistory = recalled.history;
|
|
@@ -175,6 +177,7 @@ export function createTuiEditors(deps) {
|
|
|
175
177
|
notesEditFor: session.held.notes,
|
|
176
178
|
briefEditMode: session.held.brief,
|
|
177
179
|
retireMode: session.held.retire,
|
|
180
|
+
releaseMode: session.held.release,
|
|
178
181
|
roleEnabledMode: session.held.roleEnabled,
|
|
179
182
|
quietMode: session.held.quiet,
|
|
180
183
|
quietTeam: session.held.quietTeam,
|
|
@@ -65,6 +65,8 @@ export function hintLine(view, mode, width) {
|
|
|
65
65
|
keys = [["Enter", "replace notes from file"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
66
66
|
else if (mode.retireMode)
|
|
67
67
|
keys = [["Enter", "retire role"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
68
|
+
else if (mode.releaseMode)
|
|
69
|
+
keys = [["Enter", "release test"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
68
70
|
else if (mode.roleEnabledMode)
|
|
69
71
|
keys = [["Enter", "enable/disable role"], ["Esc", "cancel"], ["Ctrl+D", "quit"]];
|
|
70
72
|
else if (mode.budget)
|
|
@@ -111,19 +113,19 @@ export function hintLine(view, mode, width) {
|
|
|
111
113
|
else if (view.kind === "backlog")
|
|
112
114
|
keys = [["Enter", "send"], ["↑↓", "open entries"], ["/", "search entries"], ["Ctrl+O", "ready only"], ["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"]];
|
|
113
115
|
else if (view.kind === "usage" || view.kind === "failures")
|
|
114
|
-
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"], ...(view.window ? [["Ctrl+P", "earlier window"]] : []), ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
|
|
116
|
+
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"], ...(view.window ? [["Ctrl+P", "earlier window"]] : []), ...(view.kind === "failures" ? [["Ctrl+X", "release test"]] : []), ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
|
|
115
117
|
else if (view.kind === "history")
|
|
116
118
|
keys = [["Enter", "send"], ["↑↓", "history"], ["Ctrl+L", "newest failure"], ["PgUp/PgDn", "scroll"], ["Ctrl+G", view.role ? `scope: ${view.role}` : "scope: team"], ["Ctrl+S", view.since ? `window: ${view.since}` : "window: default"], ...(!view.trail && view.since ? [["Ctrl+P", "earlier window"]] : []), ["Ctrl+O", view.result ? `result: ${view.result}` : "result: all"], ["Ctrl+X", view.model ? `model: ${view.model}` : "model: all"], ["Ctrl+Y", view.summary === "model" ? "rows" : view.summary === "role" ? "by model" : "summary"], ["Ctrl+N", view.trail ? "history" : "run trail"], ...(view.trail ? [["Ctrl+P", "older run"], ["Ctrl+R", view.trailTranscript === "plain" ? "prompts" : view.trailTranscript === "prompts" ? "hide transcript" : "transcript"], ["Ctrl+W", view.trailPatch ? "hide patch" : "patch"], ["Ctrl+V", "revert run"]] : []), ["/", view.trail && view.trailTranscript !== undefined && view.trailTranscript !== "off" ? (view.trailGrep ? `transcript search: ${view.trailGrep}` : "search transcript") : (view.grep ? `search: ${view.grep}` : "search")], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
|
|
117
119
|
else if (view.kind === "pending")
|
|
118
120
|
keys = [["Enter", "send"], ["↑↓", "select merge/review"], ["PgUp/PgDn", "scroll"], ["Ctrl+R", "refresh"], ["Ctrl+N", "overlaps"], ["Ctrl+G", "overlap role"], ["Ctrl+O", "review state"], ["Ctrl+X", "cancel merge"], ["Ctrl+V", "move to top"], ["Ctrl+A", "accept review"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
|
|
119
121
|
else if (view.kind === "health")
|
|
120
|
-
keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+O", view.problems ? "problems only" : "all checks"], ["Ctrl+R", "re-run checks"], ["Ctrl+
|
|
122
|
+
keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+O", view.problems ? "problems only" : "all checks"], ["Ctrl+R", "re-run checks"], ["Ctrl+A", "commit backlog"], ["Ctrl+U", "re-sync install"], ["Ctrl+E", "reclaim disk"], ["Ctrl+N", "test notify"], ["Ctrl+K", "kill orphans"], ["Ctrl+S", "drop broken pins"], ["Ctrl+X", "retire role"], ["Ctrl+L", "retire leftovers"], ["Ctrl+B", "archive backlog"], ["Ctrl+V", "enable/disable role"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
|
|
121
123
|
else if (view.kind === "brief")
|
|
122
124
|
keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+E", "edit brief"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
|
|
123
125
|
else if (view.kind === "queued")
|
|
124
126
|
keys = [["Enter", "send"], ["↑↓", "history"], ["Ctrl+V", "top"], ["Ctrl+E", "edit"], ["Ctrl+X", "cancel"], ["/", view.grep ? `search: ${view.grep}` : "search"], ["PgUp/PgDn", "scroll"], ["Ctrl+T", "next view"], ["Ctrl+D", "quit"]];
|
|
125
127
|
else
|
|
126
|
-
keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+B", "daily cap"], ["Ctrl+E", "notify hook"], ["Ctrl+A", "model"], ["Ctrl+O", view.notable ? "notable only" : view.type ? `type: ${eventTypeLabel(view.type)}` : "type: all"], ["Ctrl+G", view.role ? `scope: ${view.role}` : "scope: team"], ["/", view.grep ? `search: ${view.grep}` : "search"], ["Ctrl+P", "provider caps"], ["Ctrl+X", "agent backends"], ["Ctrl+T", "next view"], ["Ctrl+L", "fallback"], ["Ctrl+Y", "claude reserve"], ["Ctrl+F", "pause team"], ["Ctrl+Q", "stop later"], ["Ctrl+C", "interrupt director"], ["Ctrl+V", "attach image"], ["Ctrl+R", "restart"], ["Ctrl+D", "quit"]];
|
|
128
|
+
keys = [["Enter", "send"], ["↑↓", "history"], ["PgUp/PgDn", "scroll"], ["Ctrl+B", "daily cap"], ["Ctrl+E", "notify hook"], ["Ctrl+A", "model"], ["Ctrl+N", "run cap"], ["Ctrl+K", "check cap"], ["Ctrl+U", "declared check"], ["Ctrl+S", "quiet hours"], ["Ctrl+W", "worktree slots"], ["Ctrl+O", view.notable ? "notable only" : view.type ? `type: ${eventTypeLabel(view.type)}` : "type: all"], ["Ctrl+G", view.role ? `scope: ${view.role}` : "scope: team"], ["/", view.grep ? `search: ${view.grep}` : "search"], ["Ctrl+P", "provider caps"], ["Ctrl+X", "agent backends"], ["Ctrl+T", "next view"], ["Ctrl+L", "fallback"], ["Ctrl+Y", "claude reserve"], ["Ctrl+F", "pause team"], ["Ctrl+Q", "stop later"], ["Ctrl+C", "interrupt director"], ["Ctrl+V", "attach image"], ["Ctrl+R", "restart"], ["Ctrl+D", "quit"]];
|
|
127
129
|
// The mode branches above name their own keys and keep the line, so every remaining
|
|
128
130
|
// view hint always shows the send affordance. This comment marks that ordering.
|
|
129
131
|
const spans = [];
|
|
@@ -172,6 +174,8 @@ export function promptPrefix(mode) {
|
|
|
172
174
|
return [{ text: "brief", tone: "brand" }, { text: " › " }];
|
|
173
175
|
if (mode.retireMode)
|
|
174
176
|
return [{ text: "retire", tone: "brand" }, { text: " › " }];
|
|
177
|
+
if (mode.releaseMode)
|
|
178
|
+
return [{ text: "release", tone: "brand" }, { text: " › " }];
|
|
175
179
|
if (mode.roleEnabledMode)
|
|
176
180
|
return [{ text: "role state", tone: "brand" }, { text: " › " }];
|
|
177
181
|
if (mode.notesEditFor)
|