tumwater 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -13
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +8 -9
- package/dist/src/agent/agent-backend.js +8 -6
- package/dist/src/agent/backend-traits.js +42 -25
- package/dist/src/agent/claude/claude-account.js +71 -204
- package/dist/src/agent/claude/claude-args.js +9 -6
- package/dist/src/agent/claude/claude-backend.js +10 -10
- package/dist/src/agent/claude/claude-env.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +4 -3
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +402 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +29 -24
- package/dist/src/agent/notes-shim.js +11 -12
- package/dist/src/agent/opencode/opencode-args.js +0 -1
- package/dist/src/agent/opencode/opencode-backend.js +12 -9
- package/dist/src/agent/opencode/opencode-catalog.js +55 -29
- package/dist/src/agent/opencode/opencode-config.js +19 -14
- package/dist/src/agent/opencode/opencode-env.js +0 -1
- package/dist/src/agent/opencode/opencode-session-id.js +1 -2
- package/dist/src/agent/opencode/opencode-stream.js +1 -1
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +5 -6
- package/dist/src/agent/run-agent.js +465 -448
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +86 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +246 -55
- package/dist/src/backlog/backlog-cli-view.js +280 -0
- package/dist/src/backlog/backlog-cli.js +85 -274
- package/dist/src/backlog/backlog-commit.js +4 -5
- package/dist/src/backlog/backlog-eligibility.js +175 -242
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +54 -6
- package/dist/src/backlog/backlog-parts.js +254 -0
- package/dist/src/backlog/backlog-release.js +117 -0
- package/dist/src/backlog/backlog-render.js +27 -0
- package/dist/src/backlog/backlog-structure.js +99 -156
- package/dist/src/backlog/backlog-write.js +40 -127
- package/dist/src/backlog/backlog.js +21 -6
- package/dist/src/backlog/question-answer.js +21 -15
- package/dist/src/baseline/main-baseline.js +89 -20
- package/dist/src/baseline/main-culprit.js +94 -0
- package/dist/src/baseline/main-guardrails.js +209 -0
- package/dist/src/baseline/main-red.js +139 -32
- package/dist/src/baseline/main-revert.js +133 -0
- package/dist/src/baseline/main-watch.js +184 -0
- package/dist/src/brief/brief-edit.js +9 -11
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget.js +30 -20
- package/dist/src/budget/fallback-breaker.js +26 -27
- package/dist/src/build/build-check-detect.js +23 -15
- package/dist/src/build/build-check-events.js +25 -6
- package/dist/src/build/build-check-report.js +49 -9
- package/dist/src/build/build-check-scoped.js +18 -11
- package/dist/src/build/build-check.js +2 -1
- package/dist/src/build/build-info.js +2 -3
- package/dist/src/build/build-stage.js +9 -14
- package/dist/src/build/dep-install.js +1 -2
- package/dist/src/change/change-data.js +123 -31
- package/dist/src/change/change-render.js +46 -5
- package/dist/src/cli/brief-command.js +3 -4
- package/dist/src/cli/cli-args.js +118 -33
- package/dist/src/cli/cli-command-args.js +46 -18
- package/dist/src/cli/cli-editor.js +2 -1
- package/dist/src/cli/cli-flag-specs.js +150 -45
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +4 -4
- package/dist/src/cli/cli-query-commands.js +308 -590
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-role-command.js +420 -0
- package/dist/src/cli/cli-start-summary.js +160 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +165 -237
- package/dist/src/cli/completion.js +149 -22
- package/dist/src/cli/config-commands.js +41 -18
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +189 -73
- package/dist/src/cli/log-commands.js +148 -82
- package/dist/src/cli/question-commands.js +205 -10
- package/dist/src/cli.js +106 -43
- package/dist/src/collections.js +26 -10
- package/dist/src/concurrency/check-permit.js +20 -3
- package/dist/src/concurrency/lock.js +17 -13
- package/dist/src/concurrency/provider-lease.js +44 -8
- package/dist/src/concurrency/semaphore.js +102 -26
- package/dist/src/config/config-aliases.js +0 -1
- package/dist/src/config/config-editable-keys.js +13 -1
- package/dist/src/config/config-fallbacks.js +93 -56
- package/dist/src/config/config-field-checks.js +40 -10
- package/dist/src/config/config-keys.js +30 -8
- package/dist/src/config/config-live.js +63 -24
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +31 -24
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +141 -199
- package/dist/src/config/config-views.js +127 -53
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +131 -264
- package/dist/src/config/config.js +48 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
- package/dist/src/doctor/doctor-backlog-plans.js +263 -0
- package/dist/src/doctor/doctor-backlog.js +100 -496
- package/dist/src/doctor/doctor-check-names.js +81 -0
- package/dist/src/doctor/doctor-checks.js +52 -579
- package/dist/src/doctor/doctor-claude-checks.js +145 -0
- package/dist/src/doctor/doctor-codex-checks.js +279 -0
- package/dist/src/doctor/doctor-contention-checks.js +380 -0
- package/dist/src/doctor/doctor-holds.js +82 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +236 -402
- package/dist/src/doctor/doctor-notify.js +3 -4
- package/dist/src/doctor/doctor-opencode-checks.js +162 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-project-checks.js +303 -0
- package/dist/src/doctor/doctor.js +121 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +7 -11
- package/dist/src/events/event-format.js +181 -86
- package/dist/src/events/event-labels.js +12 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +39 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +60 -24
- package/dist/src/events/events.js +30 -13
- package/dist/src/events/notify.js +7 -7
- package/dist/src/failure/failure-cluster.js +12 -12
- package/dist/src/failure/failure-data.js +56 -56
- package/dist/src/failure/failure-render.js +31 -23
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +99 -35
- package/dist/src/files/files.js +111 -35
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +28 -7
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +4 -5
- package/dist/src/gates/budget-gates.js +177 -84
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +108 -58
- package/dist/src/gates/maintenance-quota.js +10 -13
- package/dist/src/gates/pause-gates.js +28 -10
- package/dist/src/gates/readiness.js +7 -3
- package/dist/src/gates/role-cap-gates.js +8 -8
- package/dist/src/gates/startup-gate.js +94 -32
- package/dist/src/gates/streak-gate.js +54 -30
- package/dist/src/git/commit-message.js +77 -46
- package/dist/src/git/git-diff.js +16 -10
- package/dist/src/git/git-run.js +79 -38
- package/dist/src/git/git.js +36 -34
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +6 -6
- package/dist/src/git/worktree-pool.js +132 -38
- package/dist/src/git/worktree-use.js +4 -11
- package/dist/src/git/worktree.js +94 -37
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +79 -8
- package/dist/src/gui/gui-backlog-commands.js +42 -18
- package/dist/src/gui/gui-backlog-endpoints.js +155 -0
- package/dist/src/gui/gui-command.js +8 -13
- package/dist/src/gui/gui-config-commands.js +94 -72
- package/dist/src/gui/gui-endpoint-commands.js +4 -3
- package/dist/src/gui/gui-endpoint-common.js +4 -4
- package/dist/src/gui/gui-endpoints.js +11 -380
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +108 -13
- package/dist/src/gui/gui-prompt-commands.js +11 -14
- package/dist/src/gui/gui-report-endpoints.js +90 -0
- package/dist/src/gui/gui-server.js +11 -4
- package/dist/src/gui/gui-team-commands.js +7 -7
- package/dist/src/gui/http-body.js +1 -1
- package/dist/src/history/history-data.js +122 -234
- package/dist/src/history/history-queued.js +147 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +193 -49
- package/dist/src/inbox/inbox-attachments.js +29 -30
- package/dist/src/inbox/inbox-cancel.js +10 -11
- package/dist/src/inbox/inbox-edit.js +9 -9
- package/dist/src/inbox/inbox-submit.js +18 -18
- package/dist/src/inbox/inbox-top.js +25 -9
- package/dist/src/inbox/inbox.js +42 -43
- package/dist/src/inbox/pending-prompt.js +26 -26
- package/dist/src/inbox/prompt-commands.js +62 -49
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +1 -2
- package/dist/src/init/init.js +28 -23
- package/dist/src/merge/backlog-conflicts.js +215 -35
- package/dist/src/merge/merge-batch.js +14 -8
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +44 -22
- package/dist/src/merge/merge-core.js +81 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +150 -11
- package/dist/src/merge/merge-git.js +177 -38
- package/dist/src/merge/merge-pipeline.js +15 -3
- package/dist/src/merge/merge-post-review.js +90 -0
- package/dist/src/merge/merge-queue-render.js +49 -11
- package/dist/src/merge/merge-queue.js +31 -15
- package/dist/src/merge/merge-requeue.js +102 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +33 -11
- package/dist/src/merge/merge-to-main.js +86 -27
- package/dist/src/merge/merge-top.js +69 -0
- package/dist/src/merge/merge-verify.js +12 -10
- package/dist/src/notes-cli.js +30 -15
- package/dist/src/operator/operator-commands.js +192 -81
- package/dist/src/operator/operator-intent.js +69 -42
- package/dist/src/operator/operator-requests.js +35 -40
- package/dist/src/operator/retire.js +5 -6
- package/dist/src/operator/revert.js +142 -34
- package/dist/src/orchestrator/orchestrator-launch.js +60 -52
- package/dist/src/orchestrator/orchestrator-poll.js +320 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
- package/dist/src/orchestrator/orchestrator.js +128 -369
- package/dist/src/orchestrator/retention.js +42 -8
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +201 -0
- package/dist/src/orchestrator/scheduling-skip.js +185 -0
- package/dist/src/orchestrator/startup-migration-steps.js +8 -7
- package/dist/src/orchestrator/startup-migrations.js +11 -7
- package/dist/src/paths.js +116 -49
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +6 -4
- package/dist/src/pi/pi-backend.js +6 -6
- package/dist/src/pi/pi-bin.js +54 -63
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +63 -30
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +1 -1
- package/dist/src/pi/pi-stream.js +6 -6
- package/dist/src/pi/pi-watchdogs.js +18 -19
- package/dist/src/pi-extension/bounded-output.js +5 -3
- package/dist/src/pi-extension/context-budget.js +2 -2
- package/dist/src/pi-extension/context-shake.js +1 -1
- package/dist/src/pi-extension/context-window.js +0 -1
- package/dist/src/pi-extension/full-output.js +3 -10
- package/dist/src/pi-extension/strip-thinking.js +0 -1
- package/dist/src/pi-extension/trim-system-prompt.js +3 -4
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +1 -2
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process.js +41 -2
- package/dist/src/process/run-marker.js +18 -6
- package/dist/src/process/supervisor.js +22 -21
- package/dist/src/prompt/dialect.js +17 -2
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +121 -211
- package/dist/src/prompt/notes-backlog.js +181 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +129 -204
- package/dist/src/redeploy/redeploy-policy.js +24 -19
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +2 -3
- package/dist/src/redeploy/redeployer.js +20 -20
- package/dist/src/redeploy/self-reload.js +2 -1
- package/dist/src/report/report-data.js +161 -77
- package/dist/src/report/report-fold.js +129 -52
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +133 -64
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +26 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/known-flakes.js +100 -1
- package/dist/src/review/post-merge-review.js +106 -0
- package/dist/src/review/protected-edits.js +4 -5
- package/dist/src/review/review-followup.js +8 -8
- package/dist/src/review/review-precheck.js +66 -23
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +2 -2
- package/dist/src/review/review.js +155 -360
- package/dist/src/review/secret-leaks.js +36 -16
- package/dist/src/review/suite-rerun.js +0 -1
- package/dist/src/roles/legacy-role-migration.js +80 -7
- package/dist/src/roles/role-catalog.js +69 -12
- package/dist/src/roles/role-guidance.js +55 -20
- package/dist/src/roles/role-labels.js +1 -2
- package/dist/src/roles/role-render.js +67 -50
- package/dist/src/roles/role-view.js +59 -47
- package/dist/src/roles/roles.js +31 -31
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/{tick → run}/qa-coverage.js +9 -27
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +179 -0
- package/dist/src/run/run-detail-render.js +98 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +72 -60
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
- package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +108 -73
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
- package/dist/src/{tick/tick-stage.js → run/run-stage.js} +108 -50
- package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
- package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
- package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +91 -56
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +172 -0
- package/dist/src/run/stage-path-checks.js +730 -0
- package/dist/src/{loop → runner}/leftover.js +80 -25
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +202 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +107 -0
- package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +21 -15
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +102 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claims.js +35 -22
- package/dist/src/scheduling/once-round.js +22 -22
- package/dist/src/scheduling/quiet-hours.js +5 -6
- package/dist/src/scheduling/scheduling.js +74 -64
- package/dist/src/scheduling/work-merged-cache.js +2 -2
- package/dist/src/status/status-budget.js +55 -47
- package/dist/src/status/status-data.js +43 -16
- package/dist/src/status/status-inbox.js +2 -2
- package/dist/src/status/status-install.js +0 -1
- package/dist/src/status/status-polls.js +28 -28
- package/dist/src/status/status-roles.js +39 -13
- package/dist/src/team/error-storm.js +8 -8
- package/dist/src/team/failure-spread.js +2 -2
- package/dist/src/team/orchestrator-info.js +15 -9
- package/dist/src/team/reclaim.js +5 -6
- package/dist/src/team/team-hold.js +7 -7
- package/dist/src/team/team-polls.js +91 -50
- package/dist/src/team/team-state.js +8 -2
- package/dist/src/text/datetime.js +63 -17
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +57 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +67 -13
- package/dist/src/ui/badges.js +65 -37
- package/dist/src/ui/gui/gui-client-backlog.js +292 -0
- package/dist/src/ui/gui/gui-client-boot.js +32 -26
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
- package/dist/src/ui/gui/gui-client-drawer.js +126 -285
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +434 -67
- package/dist/src/ui/gui/gui-client-labels.js +1 -2
- package/dist/src/ui/gui/gui-client-markdown.js +1 -2
- package/dist/src/ui/gui/gui-client-model.js +27 -36
- package/dist/src/ui/gui/gui-client-operator.js +55 -51
- package/dist/src/ui/gui/gui-client-pending.js +227 -23
- package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
- package/dist/src/ui/gui/gui-client-report.js +87 -427
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +56 -4
- package/dist/src/ui/gui/gui-client-team.js +25 -465
- package/dist/src/ui/gui/gui-client.js +27 -25
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +9 -7
- package/dist/src/ui/gui/gui-styles.js +34 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +136 -49
- package/dist/src/ui/status-render.js +101 -82
- package/dist/src/ui/team-alerts.js +248 -57
- package/dist/src/ui/tone.js +5 -6
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -20
- package/dist/src/ui/transcript.js +63 -23
- package/dist/src/ui/tui/tui-app.js +0 -1
- package/dist/src/ui/tui/tui-backlog.js +51 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +3 -5
- package/dist/src/ui/tui/tui-editor-backends.js +132 -0
- package/dist/src/ui/tui/tui-editor-brief.js +10 -12
- package/dist/src/ui/tui/tui-editor-budget.js +35 -31
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
- package/dist/src/ui/tui/tui-editor-config.js +86 -0
- package/dist/src/ui/tui/tui-editor-entry.js +36 -47
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +37 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
- package/dist/src/ui/tui/tui-editor-interval.js +11 -9
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +3 -5
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +48 -59
- package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +5 -9
- package/dist/src/ui/tui/tui-editor-quiet.js +99 -35
- package/dist/src/ui/tui/tui-editor-retire.js +4 -6
- package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +45 -33
- package/dist/src/ui/tui/tui-editor-session.js +60 -34
- package/dist/src/ui/tui/tui-editor-slots.js +23 -0
- package/dist/src/ui/tui/tui-editors.js +35 -2
- package/dist/src/ui/tui/tui-frame.js +56 -11
- package/dist/src/ui/tui/tui-history.js +8 -9
- package/dist/src/ui/tui/tui-input.js +13 -11
- package/dist/src/ui/tui/tui-keymap.js +0 -1
- package/dist/src/ui/tui/tui-keys.js +24 -9
- package/dist/src/ui/tui/tui-pane-activity.js +14 -11
- package/dist/src/ui/tui/tui-pane-backlog.js +171 -126
- package/dist/src/ui/tui/tui-pane-brief.js +0 -1
- package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
- package/dist/src/ui/tui/tui-pane-health.js +58 -8
- package/dist/src/ui/tui/tui-pane-history.js +402 -82
- package/dist/src/ui/tui/tui-pane-pending.js +308 -13
- package/dist/src/ui/tui/tui-pane-queued.js +71 -55
- package/dist/src/ui/tui/tui-pane-report.js +46 -14
- package/dist/src/ui/tui/tui-pane-session.js +25 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +80 -23
- package/dist/src/ui/tui/tui-panes.js +100 -30
- package/dist/src/ui/tui/tui.js +14 -14
- package/dist/src/verdict/fix-claim.js +82 -32
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +42 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/stage-check.js +0 -419
- package/dist/src/tick/telemetry-digest.js +0 -25
- package/dist/src/tick/tick-detail-data.js +0 -108
- package/dist/src/tick/tick-detail.js +0 -163
- package/dist/src/ui/gui/gui-client-sound.js +0 -53
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dashboard's GET settings/admin handlers (src/gui/gui-server.ts routes to them):
|
|
3
|
+
* prompt-text, doctor, config, retire-list, and brief. Each answers its request and touches no
|
|
4
|
+
* socket beyond its own `res`; the domain work lives one layer down (doctor/doctor.js,
|
|
5
|
+
* config/config.js, operator/retire.js), so this module only adapts HTTP onto it. Barrelled
|
|
6
|
+
* into gui/gui-endpoints.ts beside the other per-family GET endpoint modules.
|
|
7
|
+
*/
|
|
8
|
+
import { runDoctor } from "../doctor/doctor.js";
|
|
9
|
+
import { knownRoleIds, loadConfigSafe } from "../config/config.js";
|
|
10
|
+
import { runnerEnabled } from "../roles/runner-ids.js";
|
|
11
|
+
import { artifactPhrase, previewRetire } from "../operator/retire.js";
|
|
12
|
+
import { EDITABLE_CONFIG_KEYS } from "../config/config-editable-keys.js";
|
|
13
|
+
import { readBrief } from "../brief/brief.js";
|
|
14
|
+
import { readQueuedPromptBody } from "../inbox/inbox-edit.js";
|
|
15
|
+
import { rejectBadQueueFileName, rejectBadRole } from "./gui-args.js";
|
|
16
|
+
import { DIRECTOR_ROLE } from "../roles/roles.js";
|
|
17
|
+
import { okGone, sendJson } from "./http-body.js";
|
|
18
|
+
/** Handle GET /api/prompt-text?file=<name>[&role=<id>]: one queued prompt's editable body —
|
|
19
|
+
* the text an edit replaces, for the dashboard's per-row edit affordance (PLANS.md
|
|
20
|
+
* 2026-10-11). The prompt is addressed by its queue-file basename (the /api/prompt-cancel
|
|
21
|
+
* address), so a stale poll's snapshot can never read the wrong entry; the not-before deferral
|
|
22
|
+
* marker and the `[image attached: …]` reference block are stripped, both plumbing the edit
|
|
23
|
+
* carries over unchanged. The role is optional (absent reads the director's queue) and
|
|
24
|
+
* validates exactly like /api/transcript; a file name failing the shared
|
|
25
|
+
* rejectBadQueueFileName guard is a user-input error answered 400 with nothing read. A
|
|
26
|
+
* vanished file answers 200 { ok: true, status: "gone" } — the role already dequeued
|
|
27
|
+
* it, which is data for the UI, not a server fault. Reads one file directly, so it works
|
|
28
|
+
* whether or not the team is running. */
|
|
29
|
+
export function handlePromptText(q, res, root) {
|
|
30
|
+
const roleParam = q.get("role");
|
|
31
|
+
if (roleParam !== null && rejectBadRole(root, res, roleParam))
|
|
32
|
+
return;
|
|
33
|
+
const role = roleParam ?? DIRECTOR_ROLE;
|
|
34
|
+
const file = q.get("file");
|
|
35
|
+
if (rejectBadQueueFileName(res, file))
|
|
36
|
+
return;
|
|
37
|
+
const text = readQueuedPromptBody(root, role, file);
|
|
38
|
+
if (text === null) {
|
|
39
|
+
okGone(res);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
sendJson(res, 200, { ok: true, text });
|
|
43
|
+
}
|
|
44
|
+
/** Handle GET /api/doctor: the same DoctorReport `tumwater doctor --json` prints, so the
|
|
45
|
+
* Settings view's Health check card and the CLI cannot disagree. Read-only — the checks write
|
|
46
|
+
* nothing (runDoctor's own contract) — and it works whether or not the team is running. `run`
|
|
47
|
+
* exists so tests can skip the host probes; it never probes images ("never") — the explicit
|
|
48
|
+
* re-probe is POST /api/doctor-reprobe; a throw reaches the server's outer 500. */
|
|
49
|
+
export async function handleDoctor(res, root, run = (r) => runDoctor(r, undefined, undefined, "never")) {
|
|
50
|
+
sendJson(res, 200, await run(root));
|
|
51
|
+
}
|
|
52
|
+
/** Handle GET /api/config: the Settings view's resolved values for exactly
|
|
53
|
+
* EDITABLE_CONFIG_KEYS — read through the same load path `tumwater config get` uses
|
|
54
|
+
* (loadConfigSafe, defaults merged in), so the page shows what the team would actually
|
|
55
|
+
* load. A broken or invalid tumwater.json answers 500 with validateConfig's message instead
|
|
56
|
+
* of serving a half-empty panel. */
|
|
57
|
+
export function handleConfig(res, root) {
|
|
58
|
+
const { config, error } = loadConfigSafe(root);
|
|
59
|
+
if (config === undefined) {
|
|
60
|
+
sendJson(res, 500, { error });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const record = config;
|
|
64
|
+
const out = {};
|
|
65
|
+
for (const key of EDITABLE_CONFIG_KEYS)
|
|
66
|
+
out[key] = record[key] ?? null;
|
|
67
|
+
sendJson(res, 200, out);
|
|
68
|
+
}
|
|
69
|
+
/** Handle GET /api/retire: the Settings view's "Disabled roles" card — every configured,
|
|
70
|
+
* disabled, non-director role, with what `tumwater retire` would remove (previewRetire, so the
|
|
71
|
+
* rails and wording are the CLI's) and the rails' objections. `wouldRemove` is empty when there
|
|
72
|
+
* is nothing to retire (a role disabled with a clean checkout), so the card can also re-enable
|
|
73
|
+
* a role via POST /api/role-enable. Read-only (previewRetire's contract) and works whether or
|
|
74
|
+
* not the team runs; a throw reaches the server's outer 500, an unreadable tumwater.json
|
|
75
|
+
* answers 500 like /api/config. */
|
|
76
|
+
export async function handleRetireList(res, root) {
|
|
77
|
+
const { config, error } = loadConfigSafe(root);
|
|
78
|
+
if (config === undefined) {
|
|
79
|
+
sendJson(res, 500, { error });
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const roles = [];
|
|
83
|
+
for (const role of knownRoleIds(config).filter((id) => id !== DIRECTOR_ROLE && !runnerEnabled(config, id))) {
|
|
84
|
+
const { wouldRemove, objections } = await previewRetire(root, role);
|
|
85
|
+
roles.push({ role, wouldRemove, phrases: wouldRemove.map((a) => artifactPhrase(a, role)), objections });
|
|
86
|
+
}
|
|
87
|
+
sendJson(res, 200, { roles });
|
|
88
|
+
}
|
|
89
|
+
/** Handle GET /api/brief: the brief file's name and the current brief text (what
|
|
90
|
+
* `tumwater brief --json` prints), or 404 when no brief carries a prompt block. */
|
|
91
|
+
export function handleBrief(res, root) {
|
|
92
|
+
const brief = readBrief(root);
|
|
93
|
+
if (brief === null)
|
|
94
|
+
return sendJson(res, 404, { error: "no project brief with a prompt block found — run `tumwater init` first" });
|
|
95
|
+
sendJson(res, 200, { file: brief.file, prompt: brief.prompt });
|
|
96
|
+
}
|
package/dist/src/gui/gui-args.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The request-argument validators behind the dashboard's /api endpoints (gui/gui-endpoints.ts's
|
|
3
|
-
* GET handlers and gui/gui-endpoint-commands.ts's POST handlers call them):
|
|
3
|
+
* GET handlers and gui/gui-endpoint-commands.ts's POST handlers call them): role-targeting role
|
|
4
4
|
* validation, integer query parsing, the report
|
|
5
5
|
* endpoints' days window, and the body-field checks the paired operator endpoints
|
|
6
6
|
* (/api/prompt and /api/prompt-role, /api/pause and /api/pause-role) must answer with
|
|
@@ -14,10 +14,10 @@ import { REPORT_DEFAULT_DAYS, REPORT_MAX_DAYS } from "../events/event-window.js"
|
|
|
14
14
|
import { promptLengthProblem } from "../inbox/inbox-submit.js";
|
|
15
15
|
import { queueFileNameProblem } from "../inbox/inbox-cancel.js";
|
|
16
16
|
import { DIRECTOR_ROLE } from "../roles/roles.js";
|
|
17
|
-
import { gotSuffix, parseNonNegativeInt, parsePositiveInt } from "../text/text.js";
|
|
17
|
+
import { gotSuffix, parseNonNegativeInt, parsePositiveInt, splitCommaListUnique } from "../text/text.js";
|
|
18
18
|
import { typoSuffix } from "../text/suggest.js";
|
|
19
19
|
import { badRequest, sendJson } from "./http-body.js";
|
|
20
|
-
/** The role ids a
|
|
20
|
+
/** The role ids a role-targeting endpoint accepts: when tumwater.json parses, catalog +
|
|
21
21
|
* customRoles (knownRoleIds); a transiently broken file falls back to the built-in catalog
|
|
22
22
|
* rather than refusing every id — the rule config.knownRoleIdsCached owns. Shared by
|
|
23
23
|
* /api/transcript and the two operator endpoints so their validation and 400 wording cannot
|
|
@@ -25,7 +25,7 @@ import { badRequest, sendJson } from "./http-body.js";
|
|
|
25
25
|
export function validRoleIds(root) {
|
|
26
26
|
return knownRoleIdsCached(root);
|
|
27
27
|
}
|
|
28
|
-
/** Validate one request's
|
|
28
|
+
/** Validate one request's role-targeting role against validRoleIds, sending the shared 400 on
|
|
29
29
|
* a miss and reporting whether the request should stop: an absent id reads "role required
|
|
30
30
|
* (valid ids: …)" — unless `allowMissing`, the wake endpoint's `{}` → all-roles default,
|
|
31
31
|
* which only a truly absent `undefined` may ride; an explicit null is always rejected — and a
|
|
@@ -50,6 +50,24 @@ export function rejectBadRole(root, res, role, allowMissing = false) {
|
|
|
50
50
|
}
|
|
51
51
|
return false;
|
|
52
52
|
}
|
|
53
|
+
/** Parse the GET endpoints' `?role=` comma-separated list (each id trimmed, deduplicated, and
|
|
54
|
+
* validated by rejectBadRole; an empty list or unknown id → 400). Returns undefined when
|
|
55
|
+
* `role` is absent and null once a 400 is sent. Shared by /api/diff and the report endpoints'
|
|
56
|
+
* reportScope so the role-list rule cannot drift between them. */
|
|
57
|
+
export function roleListQuery(q, res, root) {
|
|
58
|
+
const raw = q.get("role");
|
|
59
|
+
if (raw === null)
|
|
60
|
+
return undefined;
|
|
61
|
+
const roles = splitCommaListUnique(raw);
|
|
62
|
+
if (roles.length === 0) {
|
|
63
|
+
rejectBadRole(root, res, raw);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
for (const r of roles)
|
|
67
|
+
if (rejectBadRole(root, res, r))
|
|
68
|
+
return null;
|
|
69
|
+
return roles;
|
|
70
|
+
}
|
|
53
71
|
/** Validate one request's file-addressed queue name (a queued prompt's basename) through
|
|
54
72
|
* inbox-cancel.ts's queueFileNameProblem, sending its reason as the shared 400 and reporting
|
|
55
73
|
* whether the request should stop — the file twin of rejectBadRole above. The three
|
|
@@ -87,6 +105,11 @@ export function intQuery(q, res, name, kind, fallback) {
|
|
|
87
105
|
}
|
|
88
106
|
return parsed;
|
|
89
107
|
}
|
|
108
|
+
/** Clamp a parsed count into 1..max: the bound every GUI count query (`n`, `days`) applies after
|
|
109
|
+
* its parse, so a request never asks for zero or more than the endpoint's cap. */
|
|
110
|
+
export function clampCount(n, max) {
|
|
111
|
+
return Math.min(max, Math.max(1, n));
|
|
112
|
+
}
|
|
90
113
|
/** The window both report endpoints serve, from ?days=N on the request. One exact rule,
|
|
91
114
|
* shared so /api/report and /api/failures cannot drift: missing or non-decimal →
|
|
92
115
|
* REPORT_DEFAULT_DAYS, out-of-range clamped to 1..REPORT_MAX_DAYS (the same bounds the CLI's
|
|
@@ -97,7 +120,7 @@ export function intQuery(q, res, name, kind, fallback) {
|
|
|
97
120
|
* coerced value — raw Number.parseInt would read "1e3" as 1, "0x10" as 0, and "-5" as -5. */
|
|
98
121
|
export function windowDays(q) {
|
|
99
122
|
const n = parseNonNegativeInt(q.get("days") ?? "");
|
|
100
|
-
return n === null ? REPORT_DEFAULT_DAYS :
|
|
123
|
+
return n === null ? REPORT_DEFAULT_DAYS : clampCount(n, REPORT_MAX_DAYS);
|
|
101
124
|
}
|
|
102
125
|
/** Pull the prompt text out of a prompt endpoint's body — the shared validator for
|
|
103
126
|
* /api/prompt and /api/prompt-role, which must reject a non-string, blank, or over-long text
|
|
@@ -107,7 +130,7 @@ export function windowDays(q) {
|
|
|
107
130
|
export function requirePromptText(res, body, role = DIRECTOR_ROLE) {
|
|
108
131
|
const text = body.text;
|
|
109
132
|
if (typeof text !== "string") {
|
|
110
|
-
sendJson(res, 400, { error: `text must be a string${
|
|
133
|
+
sendJson(res, 400, { error: `text must be a string${gotSuffix(text)}` });
|
|
111
134
|
return null;
|
|
112
135
|
}
|
|
113
136
|
if (!text.trim()) {
|
|
@@ -140,6 +163,29 @@ export function sinceMsQuery(q, res, maxMs, overCap) {
|
|
|
140
163
|
}
|
|
141
164
|
return sinceMs;
|
|
142
165
|
}
|
|
166
|
+
/** Parse the report, failures, history and logs endpoints' optional `?untilMs=` window end — how
|
|
167
|
+
* long ago a `sinceMs` window stops, the twin of the CLI's `--until`. Absent → undefined (the
|
|
168
|
+
* window ends now). Present without a `sinceMs` → 400 (the CLI's "--until requires --since"),
|
|
169
|
+
* so the count and day shapes never ignore it silently; otherwise the value rides intQuery's
|
|
170
|
+
* positive-integer rule, and one not shorter than `sinceMs` is a 400 (the CLI's "must be
|
|
171
|
+
* shorter than --since"; sinceMs is already capped, so this bounds untilMs too). Returns the
|
|
172
|
+
* parsed length, undefined when absent, or null once the 400 is sent. */
|
|
173
|
+
export function untilMsQuery(q, res, sinceMs) {
|
|
174
|
+
if (q.get("untilMs") === null)
|
|
175
|
+
return undefined;
|
|
176
|
+
if (sinceMs === undefined) {
|
|
177
|
+
badRequest(res, "untilMs requires sinceMs (it ends a sinceMs window that long ago)");
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
const untilMs = intQuery(q, res, "untilMs", "positive");
|
|
181
|
+
if (untilMs === null)
|
|
182
|
+
return null;
|
|
183
|
+
if (untilMs >= sinceMs) {
|
|
184
|
+
badRequest(res, `untilMs (${untilMs}) must be shorter than sinceMs (${sinceMs})`);
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
return untilMs;
|
|
188
|
+
}
|
|
143
189
|
/** Pull the required curated config key out of a config endpoint's body — the shared validator
|
|
144
190
|
* for /api/config-set and /api/config-unset, which must reject a missing or uncurated key with
|
|
145
191
|
* the same 400 wording: the panel's reach stays exactly EDITABLE_CONFIG_KEYS, and the browser and
|
|
@@ -149,11 +195,36 @@ export function sinceMsQuery(q, res, maxMs, overCap) {
|
|
|
149
195
|
export function requireConfigKey(res, body) {
|
|
150
196
|
const key = body.key;
|
|
151
197
|
if (typeof key !== "string" || !EDITABLE_CONFIG_KEYS.includes(key)) {
|
|
152
|
-
badRequest(res, `key must be one of ${EDITABLE_CONFIG_KEYS.join(", ")}${
|
|
198
|
+
badRequest(res, `key must be one of ${EDITABLE_CONFIG_KEYS.join(", ")}${gotSuffix(key)}${typeof key === "string" ? typoSuffix(key, EDITABLE_CONFIG_KEYS) : ""}`);
|
|
153
199
|
return null;
|
|
154
200
|
}
|
|
155
201
|
return key;
|
|
156
202
|
}
|
|
203
|
+
/** Pull the required, non-blank string `role` out of a config POST body — the shared 400
|
|
204
|
+
* (`role required`) for the role-cap, min-run-interval, instances, model, instructions,
|
|
205
|
+
* role-enable/disable, and quiet-hours handlers in gui-config-commands.ts. Returns the role
|
|
206
|
+
* as sent (untrimmed, not yet validated against the role list), or null once the 400 is sent. */
|
|
207
|
+
export function requireRoleField(res, body) {
|
|
208
|
+
const role = body.role;
|
|
209
|
+
if (typeof role !== "string" || role.trim() === "") {
|
|
210
|
+
badRequest(res, "role required");
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
return role;
|
|
214
|
+
}
|
|
215
|
+
/** Pull a required string field out of a POST body — the shared 400 for the editors whose body
|
|
216
|
+
* carries a full replacement text that may be "" to clear it (role instructions, quiet-hours
|
|
217
|
+
* window, role notes, the brief, and the model handler's optional provider/model/thinking
|
|
218
|
+
* fields). `meaning` completes the message after the em dash. Returns the string as sent, or
|
|
219
|
+
* null once the 400 is sent. */
|
|
220
|
+
export function requireStringField(res, body, key, meaning) {
|
|
221
|
+
const value = body[key];
|
|
222
|
+
if (typeof value !== "string") {
|
|
223
|
+
badRequest(res, `"${key}" must be a string — ${meaning}${gotSuffix(value)}`);
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
return value;
|
|
227
|
+
}
|
|
157
228
|
/** Pull the required `paused` boolean out of a pause endpoint's body — the shared validator
|
|
158
229
|
* for /api/pause and /api/pause-role, which must reject a missing or non-boolean target state
|
|
159
230
|
* with the same 400 wording: the target state is explicit (`paused: true|false`), so a retried
|
|
@@ -162,7 +233,7 @@ export function requireConfigKey(res, body) {
|
|
|
162
233
|
export function requirePausedFlag(res, body) {
|
|
163
234
|
const value = body.paused;
|
|
164
235
|
if (typeof value !== "boolean") {
|
|
165
|
-
sendJson(res, 400, { error: `paused must be a boolean${
|
|
236
|
+
sendJson(res, 400, { error: `paused must be a boolean${gotSuffix(value)}` });
|
|
166
237
|
return null;
|
|
167
238
|
}
|
|
168
239
|
return value;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
// vocabulary: plain
|
|
2
1
|
/**
|
|
3
2
|
* The dashboard's POST backlog/QUESTIONS command handlers (barrelled into
|
|
4
3
|
* gui/gui-endpoint-commands.ts, which routes to them from src/gui/gui-server.ts):
|
|
@@ -8,9 +7,14 @@
|
|
|
8
7
|
* HTTP onto the domain work one layer down (backlog-write, question-answer, backlog-commit)
|
|
9
8
|
* and touches no socket beyond its own `res`.
|
|
10
9
|
*/
|
|
10
|
+
import { backlogHolds, cancelledClaimNote, stillHeldNote } from "../backlog/backlog-eligibility.js";
|
|
11
|
+
import { entryKey, plansReleasedBy, releasedPartsNote } from "../backlog/backlog-parts.js";
|
|
12
|
+
import { plannedPlanEntries } from "../backlog/backlog.js";
|
|
13
|
+
import { allLiveClaims } from "../runner/runner-state-load.js";
|
|
11
14
|
import { commitOperatorBacklogEdit } from "../backlog/backlog-commit.js";
|
|
12
15
|
import { stampedSubject } from "../git/commit-message.js";
|
|
13
|
-
import { fileEntryWithBody, normalizeEntryBody, OPERATOR_FILE_KINDS, tryCancelEntry, tryEditEntry,
|
|
16
|
+
import { fileEntryWithBody, normalizeEntryBody, OPERATOR_FILE_KINDS, tryCancelEntry, tryEditEntry, bodyStructureProblem } from "../backlog/backlog-write.js";
|
|
17
|
+
import { tryMoveEntryToTop, tryReleaseEntry } from "../backlog/backlog-release.js";
|
|
14
18
|
import { tryAnswerQuestion, UNCLOSED_FENCE_MESSAGE } from "../backlog/question-answer.js";
|
|
15
19
|
import { promptLengthProblem } from "../inbox/inbox-submit.js";
|
|
16
20
|
import { requestWake, submitRolePromptAndWake, wakeQuestionAsker } from "../operator/operator-intent.js";
|
|
@@ -23,7 +27,7 @@ import { readPostBody } from "./gui-endpoint-common.js";
|
|
|
23
27
|
function checkedBacklogKind(res, body) {
|
|
24
28
|
const kind = body.kind;
|
|
25
29
|
if (kind !== "bug" && kind !== "plan") {
|
|
26
|
-
badRequest(res, `kind must be "bug" or "plan"${
|
|
30
|
+
badRequest(res, `kind must be "bug" or "plan"${gotSuffix(kind)}`);
|
|
27
31
|
return null;
|
|
28
32
|
}
|
|
29
33
|
return kind;
|
|
@@ -36,11 +40,11 @@ function checkedBacklogKind(res, body) {
|
|
|
36
40
|
function checkedEntryPositionAndTitle(res, body) {
|
|
37
41
|
const { position, title } = body;
|
|
38
42
|
if (typeof position !== "number" || !Number.isInteger(position) || position < 1) {
|
|
39
|
-
badRequest(res, `position must be a positive integer${
|
|
43
|
+
badRequest(res, `position must be a positive integer${gotSuffix(position)}`);
|
|
40
44
|
return null;
|
|
41
45
|
}
|
|
42
46
|
if (typeof title !== "string" || title === "") {
|
|
43
|
-
badRequest(res, `title must be a non-empty string${
|
|
47
|
+
badRequest(res, `title must be a non-empty string${gotSuffix(title)}`);
|
|
44
48
|
return null;
|
|
45
49
|
}
|
|
46
50
|
return { position, title };
|
|
@@ -105,7 +109,7 @@ export async function handleQuestionAnswer(req, res, root) {
|
|
|
105
109
|
}
|
|
106
110
|
/** Handle POST /api/backlog-file: the Plans/Bugs tabs' New control — the same entry and wake
|
|
107
111
|
* `tumwater bug`/`tumwater plan` produce (fileEntryWithBody, then the shared wake text to the
|
|
108
|
-
* bugfix/feature
|
|
112
|
+
* bugfix/feature role). Body `{"kind": "bug"|"plan", "title": "...", "body": "optional
|
|
109
113
|
* markdown"}`. The empty-title and wake-length checks run here, before anything is written:
|
|
110
114
|
* the body is normalized and checked by bodyStructureProblem too, and an over-long wake
|
|
111
115
|
* would throw after the entry merged. */
|
|
@@ -147,7 +151,8 @@ export async function handleBacklogFile(req, res, root) {
|
|
|
147
151
|
* `tumwater plan|bug --cancel <n>` through the non-exiting tryCancelEntry. Body `{"kind":
|
|
148
152
|
* "bug"|"plan", "position": <1-based>, "title": "<heading>"}`; a missing position or a
|
|
149
153
|
* different heading there answers 200 { ok: true, status: "gone" } (the list moved since the
|
|
150
|
-
* poll).
|
|
154
|
+
* poll). A cancelled plan that planned parts required adds `releasedNote` naming them, as
|
|
155
|
+
* `plan --cancel` prints. No wake and no inbox prompt: removing work must not wake a role. */
|
|
151
156
|
export async function handleBacklogCancel(req, res, root) {
|
|
152
157
|
const body = await readPostBody(req, res, '{"kind": "bug", "position": 1, "title": "<heading>"}');
|
|
153
158
|
if (!body)
|
|
@@ -159,21 +164,30 @@ export async function handleBacklogCancel(req, res, root) {
|
|
|
159
164
|
if (!pair)
|
|
160
165
|
return;
|
|
161
166
|
const { position, title } = pair;
|
|
167
|
+
const claims = allLiveClaims(root);
|
|
162
168
|
const outcome = outcomeOrBadRequest(res, () => tryCancelEntry(root, kind, position, title));
|
|
163
169
|
if (outcome === null)
|
|
164
170
|
return;
|
|
165
171
|
if (outcome.status === "cancelled") {
|
|
172
|
+
const workingNote = cancelledClaimNote(claims.get(entryKey(outcome.title)) ?? null);
|
|
173
|
+
const releasedNote = kind === "plan" ? releasedPartsNote(plansReleasedBy(plannedPlanEntries(root), outcome.title)) : "";
|
|
166
174
|
const committed = await commitOperatorBacklogEdit(root, outcome.file, stampedSubject("operator", `Cancel ${kind}: ${outcome.title}`)); // vocabulary-ok: the commit prefix stays tumwater(operator):
|
|
167
|
-
sendJson(res, 200, { ok: true, status: "cancelled", file: outcome.file, title: outcome.title, committed });
|
|
175
|
+
sendJson(res, 200, { ok: true, status: "cancelled", file: outcome.file, title: outcome.title, committed, ...(workingNote === "" ? {} : { workingNote }), ...(releasedNote === "" ? {} : { releasedNote }) });
|
|
168
176
|
}
|
|
169
177
|
else {
|
|
170
178
|
okGone(res);
|
|
171
179
|
}
|
|
172
180
|
}
|
|
181
|
+
/** The hold on the kind's entry at a 1-based position (backlogHolds), `null` when eligible. */
|
|
182
|
+
function heldAt(root, kind, position) {
|
|
183
|
+
const holds = backlogHolds(root);
|
|
184
|
+
return (kind === "bug" ? holds.bugs : holds.plans)[position - 1] ?? null;
|
|
185
|
+
}
|
|
173
186
|
/** Handle POST /api/backlog-release: the entry drawer's release — the same lock and splice as
|
|
174
187
|
* `tumwater plan|bug --release <n>` through the non-exiting tryReleaseEntry. Body as
|
|
175
|
-
* backlog-cancel's. A released entry is newly eligible, so the kind's
|
|
176
|
-
*
|
|
188
|
+
* backlog-cancel's. A released entry that is not still held is newly eligible, so the kind's
|
|
189
|
+
* role is woken (a held one wakes no role and carries `heldNote`); `gone` and `moved` answer
|
|
190
|
+
* okGone, and an entry with neither a Refused nor a Waiting-for-the-next-
|
|
177
191
|
* failure note answers status "not_refused" with no write and no wake. */
|
|
178
192
|
export async function handleBacklogRelease(req, res, root) {
|
|
179
193
|
const body = await readPostBody(req, res, '{"kind": "bug", "position": 1, "title": "<heading>"}');
|
|
@@ -191,8 +205,10 @@ export async function handleBacklogRelease(req, res, root) {
|
|
|
191
205
|
return;
|
|
192
206
|
if (outcome.status === "released") {
|
|
193
207
|
const committed = await commitOperatorBacklogEdit(root, outcome.file, stampedSubject("operator", `Release ${kind}: ${outcome.title}`)); // vocabulary-ok: the commit prefix stays tumwater(operator):
|
|
194
|
-
|
|
195
|
-
|
|
208
|
+
const hold = heldAt(root, kind, position);
|
|
209
|
+
if (hold === null)
|
|
210
|
+
requestWake(root, [OPERATOR_FILE_KINDS[kind].role]);
|
|
211
|
+
sendJson(res, 200, { ok: true, status: "released", file: outcome.file, title: outcome.title, committed, ...(hold === null ? {} : { heldNote: stillHeldNote(hold) }) });
|
|
196
212
|
}
|
|
197
213
|
else if (outcome.status === "not_refused") {
|
|
198
214
|
sendJson(res, 200, { ok: true, status: "not_refused", title: outcome.title });
|
|
@@ -203,8 +219,9 @@ export async function handleBacklogRelease(req, res, root) {
|
|
|
203
219
|
}
|
|
204
220
|
/** Handle POST /api/backlog-top: the entry drawer's Move to top — the same splice as
|
|
205
221
|
* `tumwater plan|bug --top <n>` through the non-exiting tryMoveEntryToTop. Body as
|
|
206
|
-
* backlog-cancel's. Reordering makes no entry newly eligible, so no role is woken
|
|
207
|
-
* answers status "already_first" with no write, and `gone` and
|
|
222
|
+
* backlog-cancel's. Reordering makes no entry newly eligible, so no role is woken (a held entry
|
|
223
|
+
* carries `heldNote`); position 1 answers status "already_first" with no write, and `gone` and
|
|
224
|
+
* `moved` answer okGone. */
|
|
208
225
|
export async function handleBacklogTop(req, res, root) {
|
|
209
226
|
const body = await readPostBody(req, res, '{"kind": "bug", "position": 2, "title": "<heading>"}');
|
|
210
227
|
if (!body)
|
|
@@ -221,7 +238,8 @@ export async function handleBacklogTop(req, res, root) {
|
|
|
221
238
|
return;
|
|
222
239
|
if (outcome.status === "moved_to_top") {
|
|
223
240
|
const committed = await commitOperatorBacklogEdit(root, outcome.file, stampedSubject("operator", `Prioritize ${kind}: ${outcome.title}`)); // vocabulary-ok: the commit prefix stays tumwater(operator):
|
|
224
|
-
|
|
241
|
+
const hold = heldAt(root, kind, 1);
|
|
242
|
+
sendJson(res, 200, { ok: true, status: "moved_to_top", file: outcome.file, title: outcome.title, committed, ...(hold === null ? {} : { heldNote: stillHeldNote(hold) }) });
|
|
225
243
|
}
|
|
226
244
|
else if (outcome.status === "already_first") {
|
|
227
245
|
sendJson(res, 200, { ok: true, status: "already_first", title: outcome.title });
|
|
@@ -233,8 +251,10 @@ export async function handleBacklogTop(req, res, root) {
|
|
|
233
251
|
/** Handle POST /api/backlog-edit: the entry drawer's edit — tryEditEntry's title-checked,
|
|
234
252
|
* in-place rewrite under the state lock. Body `{"kind", "position", "title", "newTitle",
|
|
235
253
|
* "newBody"}`; a missing position or a different heading there answers 200 { ok: true, status:
|
|
236
|
-
* "gone" }
|
|
237
|
-
*
|
|
254
|
+
* "gone" }; text identical to the entry's own answers 200 { ok: true, status: "unchanged", file,
|
|
255
|
+
* title } with nothing written or committed. An edited entry that is still held adds `heldNote`
|
|
256
|
+
* (stillHeldNote). The body keeps its line structure. No wake and no inbox prompt: whichever run
|
|
257
|
+
* picks the entry up refines it. */
|
|
238
258
|
export async function handleBacklogEdit(req, res, root) {
|
|
239
259
|
const body = await readPostBody(req, res, '{"kind": "bug", "position": 1, "title": "<heading>", "newTitle": "...", "newBody": "..."}');
|
|
240
260
|
if (!body)
|
|
@@ -265,7 +285,11 @@ export async function handleBacklogEdit(req, res, root) {
|
|
|
265
285
|
return;
|
|
266
286
|
if (outcome.status === "edited") {
|
|
267
287
|
const committed = await commitOperatorBacklogEdit(root, outcome.file, stampedSubject("operator", `Edit ${kind}: ${outcome.title}`)); // vocabulary-ok: the commit prefix stays tumwater(operator):
|
|
268
|
-
|
|
288
|
+
const hold = heldAt(root, kind, position);
|
|
289
|
+
sendJson(res, 200, { ok: true, status: "edited", file: outcome.file, title: outcome.title, committed, ...(hold === null ? {} : { heldNote: stillHeldNote(hold) }) });
|
|
290
|
+
}
|
|
291
|
+
else if (outcome.status === "unchanged") {
|
|
292
|
+
sendJson(res, 200, { ok: true, status: "unchanged", file: outcome.file, title: outcome.title });
|
|
269
293
|
}
|
|
270
294
|
else {
|
|
271
295
|
okGone(res);
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dashboard's GET backlog and unmerged-change handlers (src/gui/gui-server.ts routes to
|
|
3
|
+
* them): backlog, backlog-match, and diff. Each answers its request and touches no socket
|
|
4
|
+
* beyond its own `res`; the domain work lives one layer down (backlog/backlog.ts,
|
|
5
|
+
* inbox/inbox.ts, change/change-data.ts), so this module only adapts HTTP onto it. Barrelled
|
|
6
|
+
* into gui/gui-endpoints.ts beside the other per-family GET endpoint modules.
|
|
7
|
+
*/
|
|
8
|
+
import { operatorHeldEntries, refusalNote, waitingNote } from "../backlog/backlog-eligibility.js";
|
|
9
|
+
import { answeredQuestionEntries, entryMatches, donePlanEntries, fixedBugEntries, openBugEntries, openQuestionEntries, plannedPlanEntries } from "../backlog/backlog.js";
|
|
10
|
+
import { collectChangeOverlaps, scopeChangeOverlaps, scopeChangeOverlapsToPath, collectTeamChanges, collectRoleChange, normalizeDiffPath } from "../change/change-data.js";
|
|
11
|
+
import { listWideScope, queuedListRows } from "../inbox/inbox.js";
|
|
12
|
+
import { intQuery, rejectBadRole, roleListQuery } from "./gui-args.js";
|
|
13
|
+
import { badRequest, sendJson } from "./http-body.js";
|
|
14
|
+
import { typoSuffix } from "../text/suggest.js";
|
|
15
|
+
/** The /api/backlog `file` values and the open-section reader each addresses — one table so
|
|
16
|
+
* the on-demand dispatch and the "valid values" list in both 400s cannot drift apart. A Map,
|
|
17
|
+
* not an object literal, so no `file` spelling (`constructor`, `toString`, `__proto__`) can
|
|
18
|
+
* reach an inherited member through the lookup. Insertion order is the list order. */
|
|
19
|
+
const BACKLOG_FILE_READERS = new Map([
|
|
20
|
+
["plans", plannedPlanEntries],
|
|
21
|
+
["bugs", openBugEntries],
|
|
22
|
+
["questions", openQuestionEntries],
|
|
23
|
+
["done", donePlanEntries],
|
|
24
|
+
["fixed", fixedBugEntries],
|
|
25
|
+
["answered", (root) => answeredQuestionEntries(root).reverse()],
|
|
26
|
+
]);
|
|
27
|
+
const BACKLOG_FILE_NAMES = [...BACKLOG_FILE_READERS.keys()];
|
|
28
|
+
const BACKLOG_FILE_VALUES = BACKLOG_FILE_NAMES.join(", ");
|
|
29
|
+
/** Handle GET /api/backlog?file=<plans|bugs|questions|done|fixed|answered>&index=N: one
|
|
30
|
+
* backlog entry's full text ({title, body}), fetched on demand so multi-KB bodies (long repros,
|
|
31
|
+
* whole plans) never ride the 1-second /api/status poll. index addresses the Nth entry of that
|
|
32
|
+
* file's section in the same order statusPayload lists its titles — plans: PLANS.md ## Planned,
|
|
33
|
+
* bugs: BUGS.md ## Open, questions: QUESTIONS.md ## Open, done: PLANS.md ## Done and fixed:
|
|
34
|
+
* BUGS.md ## Fixed (both newest first, the order of the dashboard's recent rows) and answered:
|
|
35
|
+
* QUESTIONS.md ## Answered, newest first (the file is appended to, so the reverse of file
|
|
36
|
+
* order, unlike done/fixed) — zero-based.
|
|
37
|
+
* Unknown/missing file, missing or bad index, and out-of-range index
|
|
38
|
+
* → 400 JSON error via sendJson.
|
|
39
|
+
* On a held-for-operator plan (file === "plans" and the entry in
|
|
40
|
+
* operatorHeldEntries' selection), the 200 payload adds `heldForOperator: true` so the drawer
|
|
41
|
+
* shows its Release button there too; every other entry's payload carries no such key.
|
|
42
|
+
* The query arrives pre-parsed (gui/gui-server.ts's parseRequestTarget), like every
|
|
43
|
+
* GET-data handler here. */
|
|
44
|
+
export function handleBacklog(q, res, root) {
|
|
45
|
+
const file = q.get("file");
|
|
46
|
+
if (file === null) {
|
|
47
|
+
badRequest(res, `file required (valid values: ${BACKLOG_FILE_VALUES})`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const reader = BACKLOG_FILE_READERS.get(file);
|
|
51
|
+
if (!reader) {
|
|
52
|
+
badRequest(res, `unknown file ${JSON.stringify(file)} (valid values: ${BACKLOG_FILE_VALUES})${typoSuffix(file, BACKLOG_FILE_NAMES)}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const entries = reader(root);
|
|
56
|
+
const index = intQuery(q, res, "index", "non-negative");
|
|
57
|
+
if (index === null)
|
|
58
|
+
return;
|
|
59
|
+
const entry = entries[index];
|
|
60
|
+
if (!entry) {
|
|
61
|
+
badRequest(res, `index ${index} out of range (${file} has ${entries.length} entries)`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
sendJson(res, 200, {
|
|
65
|
+
title: entry.title,
|
|
66
|
+
body: entry.body,
|
|
67
|
+
refused: refusalNote(entry.body) !== null,
|
|
68
|
+
waiting: waitingNote(entry.body) !== null,
|
|
69
|
+
...(file === "plans" && operatorHeldEntries(root).some((e) => e.index === index) ? { heldForOperator: true } : {}),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
/** Handle GET /api/backlog-match?grep=<text>: for each /api/backlog `file`, the ascending
|
|
73
|
+
* indexes (the ones /api/backlog?file=…&index=N addresses) whose entry heading or body contains
|
|
74
|
+
* the trimmed text, case-insensitively — the match `tumwater backlog --grep` and the TUI's `/`
|
|
75
|
+
* use — so the dashboard's Search can find text bodies hold without bodies riding the status
|
|
76
|
+
* poll. A `queued` key adds the queued messages whose full text (not only the 80-character
|
|
77
|
+
* preview) contains the text, as `{ role, file }` with the queue-file basename, director first —
|
|
78
|
+
* the match `tumwater prompt --list --grep` uses. Missing or blank grep → 400. */
|
|
79
|
+
export function handleBacklogMatch(q, res, root) {
|
|
80
|
+
const needle = (q.get("grep") ?? "").trim();
|
|
81
|
+
if (!needle) {
|
|
82
|
+
badRequest(res, "grep required");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const out = {};
|
|
86
|
+
for (const [file, reader] of BACKLOG_FILE_READERS) {
|
|
87
|
+
out[file] = reader(root).flatMap((entry, i) => (entryMatches(entry, needle) ? [i] : []));
|
|
88
|
+
}
|
|
89
|
+
const lower = needle.toLowerCase();
|
|
90
|
+
out.queued = queuedListRows(root, null, listWideScope(root))
|
|
91
|
+
.filter((row) => row.text.toLowerCase().includes(lower))
|
|
92
|
+
.map((row) => ({ role: row.role, file: row.file }));
|
|
93
|
+
sendJson(res, 200, out);
|
|
94
|
+
}
|
|
95
|
+
/** Handle GET /api/diff[?role=<id>]: the unmerged-change view the CLI prints from the same
|
|
96
|
+
* collectors — no `role` → collectTeamChanges (exactly `tumwater diff --json`'s document:
|
|
97
|
+
* `{ mainBranch, roles }`, no per-role patch halves); `?role=<id>` → collectRoleChange (the
|
|
98
|
+
* full per-role view with `diff`/`uncommittedDiff`, `tumwater diff --role <id> --json`). The
|
|
99
|
+
* role is a target, not a filter, so it validates through rejectBadRole exactly like
|
|
100
|
+
* /api/transcript and /api/run (unknown/missing → 400 naming the valid ids) — the same ids
|
|
101
|
+
* `parseRoleFlag` accepts on the CLI, including disabled and user-defined roles. The
|
|
102
|
+
* collectors degrade absent worktrees and missing base branches to their `state` instead of
|
|
103
|
+
* throwing, so a fresh or half-built repo still answers 200. Reads git plumbing directly, so
|
|
104
|
+
* it works whether or not the team is running. `?overlaps=1` serves collectChangeOverlaps
|
|
105
|
+
* (exactly `tumwater diff --overlaps --json`'s `{ mainBranch, state, overlaps }`); with `role` (a
|
|
106
|
+
* comma-separated list, each id validated) it keeps the files a listed role holds, exactly
|
|
107
|
+
* `tumwater diff --overlaps --role <ids> --json`; `&path=<path>` keeps the files at or under that
|
|
108
|
+
* path, exactly `tumwater diff --overlaps --path <path> --json`. `?role=<id>&stat=1` serves both
|
|
109
|
+
* halves as the `git diff --stat` summary, exactly `tumwater diff --role <id> --stat --json`. */
|
|
110
|
+
export async function handleDiff(q, res, root) {
|
|
111
|
+
const rawPath = q.get("path");
|
|
112
|
+
const stat = q.get("stat") !== null;
|
|
113
|
+
if (q.get("overlaps") !== null) {
|
|
114
|
+
if (stat) {
|
|
115
|
+
badRequest(res, "overlaps lists paths, not patches; drop stat");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const opath = rawPath === null ? undefined : normalizeDiffPath(rawPath);
|
|
119
|
+
if (opath === null) {
|
|
120
|
+
badRequest(res, "path takes a repo-relative path such as src/cli.ts (as overlaps lists it)");
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const roles = roleListQuery(q, res, root);
|
|
124
|
+
if (roles === null)
|
|
125
|
+
return;
|
|
126
|
+
let view = await collectChangeOverlaps(root);
|
|
127
|
+
if (roles !== undefined)
|
|
128
|
+
view = scopeChangeOverlaps(view, roles);
|
|
129
|
+
if (opath !== undefined)
|
|
130
|
+
view = scopeChangeOverlapsToPath(view, opath);
|
|
131
|
+
sendJson(res, 200, view);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const role = q.get("role");
|
|
135
|
+
if (role === null) {
|
|
136
|
+
if (stat) {
|
|
137
|
+
badRequest(res, "stat needs role (the team roster carries no patch)");
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
if (rawPath !== null) {
|
|
141
|
+
badRequest(res, "path needs role (the team roster carries no patch)");
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
sendJson(res, 200, await collectTeamChanges(root));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
if (rejectBadRole(root, res, role))
|
|
148
|
+
return;
|
|
149
|
+
const path = rawPath === null ? undefined : normalizeDiffPath(rawPath);
|
|
150
|
+
if (path === null) {
|
|
151
|
+
badRequest(res, "path takes a repo-relative path such as src/cli.ts (as overlaps lists it)");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
sendJson(res, 200, await collectRoleChange(root, role, { ...(path === undefined ? {} : { path }), ...(stat ? { stat: true } : {}) }));
|
|
155
|
+
}
|
|
@@ -6,8 +6,7 @@ import os from "node:os";
|
|
|
6
6
|
import { startGui } from "./gui-server.js";
|
|
7
7
|
import { errCode } from "../errno.js";
|
|
8
8
|
import { fail, say } from "../cli/cli-output.js";
|
|
9
|
-
import { flagValue, parsePortFlag } from "../cli/cli-args.js";
|
|
10
|
-
import { TOKEN_VALUE_ERROR } from "../cli/cli-flag-specs.js";
|
|
9
|
+
import { flagValue, parsePortFlag, tokenValueProblem } from "../cli/cli-args.js";
|
|
11
10
|
/** External IPv4 addresses of this machine's network interfaces, for printing the URLs a
|
|
12
11
|
* `gui --all-interfaces` server is reachable at. IPv6 and internal (loopback) addresses are
|
|
13
12
|
* skipped: the loopback URL is printed separately, and bracketed IPv6 URLs are rarely what
|
|
@@ -43,17 +42,13 @@ export async function cmdGui(root, args, seams = {}) {
|
|
|
43
42
|
const port = portRaw !== null ? parsePortFlag(portRaw) : 7180;
|
|
44
43
|
const allInterfaces = args.includes("--all-interfaces");
|
|
45
44
|
// A valueless or empty --token is a CLI error, not an open server: an operator who
|
|
46
|
-
// asked for protection must never silently get none.
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
const token =
|
|
53
|
-
if (tokenRaw !== null && !token)
|
|
54
|
-
fail(TOKEN_VALUE_ERROR);
|
|
55
|
-
if (tokenRaw !== null && token.startsWith("--"))
|
|
56
|
-
fail(`--token got the flag-looking value "${token}" instead of a secret — write the secret as its own argument (e.g. \`tumwater gui --token s3cret --all-interfaces\`)`);
|
|
45
|
+
// asked for protection must never silently get none. The validation and its wording
|
|
46
|
+
// live in tokenValueProblem, shared with `start --gui` so the two callers cannot
|
|
47
|
+
// drift; its flag-looking-value case names `tumwater gui` here.
|
|
48
|
+
const problem = tokenValueProblem(args, "tumwater gui");
|
|
49
|
+
if (problem)
|
|
50
|
+
fail(problem);
|
|
51
|
+
const token = flagValue(args, "--token") ?? "";
|
|
57
52
|
try {
|
|
58
53
|
await start(root, port, allInterfaces, token);
|
|
59
54
|
}
|