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
|
@@ -10,16 +10,16 @@
|
|
|
10
10
|
import { setConfigKey, setDailyBudgetUsd, setRoleModelFields, unsetConfigKey } from "../config/config-write.js";
|
|
11
11
|
import { checkDailyBudgetUsd, checkMinRunInterval } from "../config/config-keys.js";
|
|
12
12
|
import { testNotifyHook } from "../cli/notify-command.js";
|
|
13
|
-
import { rejectBadRole, requireConfigKey } from "./gui-args.js";
|
|
14
|
-
import { baseRoleOf } from "../roles/
|
|
13
|
+
import { rejectBadRole, requireConfigKey, requireRoleField, requireStringField } from "./gui-args.js";
|
|
14
|
+
import { baseRoleOf } from "../roles/runner-ids.js";
|
|
15
15
|
import { DIRECTOR_ROLE } from "../roles/roles.js";
|
|
16
16
|
import { badRequest, sendJson } from "./http-body.js";
|
|
17
|
-
import { gotSuffix } from "../text/text.js";
|
|
18
17
|
import { readPostBody } from "./gui-endpoint-common.js";
|
|
19
18
|
/** Handle POST /api/budget: the dashboard's budget-badge editor saves the daily cost cap —
|
|
20
19
|
* same body discipline as /api/prompt (readJsonObject), and the same shared validation rule +
|
|
21
20
|
* atomic setter the TUI's Ctrl+B uses, so both surfaces write tumwater.json identically and
|
|
22
|
-
* the running orchestrator picks the change up on its next ~2 s poll.
|
|
21
|
+
* the running orchestrator picks the change up on its next ~2 s poll. A cap already in place
|
|
22
|
+
* answers `unchanged: true` and writes nothing. */
|
|
23
23
|
export async function handleBudget(req, res, root) {
|
|
24
24
|
const body = await readPostBody(req, res, '{"maxDailyCostUsd": 25}');
|
|
25
25
|
if (!body)
|
|
@@ -43,7 +43,7 @@ export async function handleBudget(req, res, root) {
|
|
|
43
43
|
sendJson(res, 500, { error: result.error });
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
|
-
sendJson(res, 200, { ok: true, maxDailyCostUsd: value });
|
|
46
|
+
sendJson(res, 200, { ok: true, maxDailyCostUsd: value, ...unchangedFlag(result) });
|
|
47
47
|
}
|
|
48
48
|
/** Handle POST /api/role-cap: the dashboard role drawer's editor saves one role's daily cost
|
|
49
49
|
* cap — the control the amber role-cap alert remedies. Same body discipline as /api/budget
|
|
@@ -52,18 +52,16 @@ export async function handleBudget(req, res, root) {
|
|
|
52
52
|
* through the CLI's own setConfigKey dotted path so the per-key validator and whole-candidate
|
|
53
53
|
* validateConfig (which rejects an unknown role id) own the final truth. The director has no
|
|
54
54
|
* cap (role-view.ts's guard exempts it), so it is refused up front with wording that mirrors
|
|
55
|
-
* handleRoleNotes. A bare `feature-2`
|
|
55
|
+
* handleRoleNotes. A bare `feature-2` role id maps to its base role's cap (baseRoleOf), since
|
|
56
56
|
* the cap is about the role. A write failure is configKeyError's 400 exactly as handleConfigSet
|
|
57
|
-
* reports it. */
|
|
57
|
+
* reports it. A no-op write answers `unchanged: true`. */
|
|
58
58
|
export async function handleRoleCap(req, res, root) {
|
|
59
59
|
const body = await readPostBody(req, res, '{"role": "feature", "maxDailyCostUsd": 2}');
|
|
60
60
|
if (!body)
|
|
61
61
|
return;
|
|
62
|
-
const role = body
|
|
63
|
-
if (
|
|
64
|
-
badRequest(res, "role required");
|
|
62
|
+
const role = requireRoleField(res, body);
|
|
63
|
+
if (role === null)
|
|
65
64
|
return;
|
|
66
|
-
}
|
|
67
65
|
if (role === DIRECTOR_ROLE) {
|
|
68
66
|
badRequest(res, "the director has no daily cap");
|
|
69
67
|
return;
|
|
@@ -86,7 +84,7 @@ export async function handleRoleCap(req, res, root) {
|
|
|
86
84
|
const result = setConfigKey(root, "maxDailyCostUsdPerRole." + baseRole, JSON.stringify(value));
|
|
87
85
|
if (!result.ok)
|
|
88
86
|
return configKeyError(res, "maxDailyCostUsdPerRole." + baseRole, result);
|
|
89
|
-
sendJson(res, 200, { ok: true, role: baseRole, maxDailyCostUsd: value });
|
|
87
|
+
sendJson(res, 200, { ok: true, role: baseRole, maxDailyCostUsd: value, ...unchangedFlag(result) });
|
|
90
88
|
}
|
|
91
89
|
/** Handle POST /api/role-interval: the role drawer's min run interval editor sets or clears
|
|
92
90
|
* one role's override of the team-wide minRunIntervalSeconds — the knob that makes one role
|
|
@@ -95,19 +93,17 @@ export async function handleRoleCap(req, res, root) {
|
|
|
95
93
|
* value clears the key through unsetConfigKey (so the role falls back to the team-wide
|
|
96
94
|
* interval), otherwise the write rides the CLI's own setConfigKey dotted path so
|
|
97
95
|
* checkMinRunInterval and whole-candidate validateConfig (which rejects an unknown role id)
|
|
98
|
-
* own the final truth. The director is accepted here — it
|
|
99
|
-
* the cap's director exemption. A bare `feature-2`
|
|
96
|
+
* own the final truth. The director is accepted here — it runs on the same clock, unlike
|
|
97
|
+
* the cap's director exemption. A bare `feature-2` role id maps to its base role's override
|
|
100
98
|
* (baseRoleOf), since the interval is about the role. A write failure is configKeyError's 400
|
|
101
|
-
* exactly as handleRoleCap reports it. */
|
|
99
|
+
* exactly as handleRoleCap reports it. A no-op set or clear says so in its message. */
|
|
102
100
|
export async function handleRoleInterval(req, res, root) {
|
|
103
101
|
const body = await readPostBody(req, res, '{"role": "feature", "minRunIntervalSeconds": 300}');
|
|
104
102
|
if (!body)
|
|
105
103
|
return;
|
|
106
|
-
const role = body
|
|
107
|
-
if (
|
|
108
|
-
badRequest(res, "role required");
|
|
104
|
+
const role = requireRoleField(res, body);
|
|
105
|
+
if (role === null)
|
|
109
106
|
return;
|
|
110
|
-
}
|
|
111
107
|
const value = body.minRunIntervalSeconds;
|
|
112
108
|
if (value === undefined) {
|
|
113
109
|
badRequest(res, "minRunIntervalSeconds required");
|
|
@@ -126,7 +122,10 @@ export async function handleRoleInterval(req, res, root) {
|
|
|
126
122
|
const clearResult = unsetConfigKey(root, `roles.${base}.minRunIntervalSeconds`);
|
|
127
123
|
if (!clearResult.ok)
|
|
128
124
|
return configKeyError(res, `roles.${base}.minRunIntervalSeconds`, clearResult);
|
|
129
|
-
|
|
125
|
+
const message = clearResult.oldValue === undefined
|
|
126
|
+
? `${base}: min run interval is not set in tumwater.json, so nothing was cleared`
|
|
127
|
+
: `${base}: min run interval cleared`;
|
|
128
|
+
sendJson(res, 200, { ok: true, role: base, message });
|
|
130
129
|
return;
|
|
131
130
|
}
|
|
132
131
|
// The shared rule (finite 0..MAX_DURATION_SECONDS), naming the per-role dotted key so a
|
|
@@ -141,10 +140,13 @@ export async function handleRoleInterval(req, res, root) {
|
|
|
141
140
|
const result = setConfigKey(root, `roles.${base}.minRunIntervalSeconds`, JSON.stringify(value));
|
|
142
141
|
if (!result.ok)
|
|
143
142
|
return configKeyError(res, `roles.${base}.minRunIntervalSeconds`, result);
|
|
144
|
-
|
|
143
|
+
const message = result.unchanged
|
|
144
|
+
? `${base}: min run interval is already ${value}s, so tumwater.json was not written`
|
|
145
|
+
: `${base}: min run interval set to ${value}s`;
|
|
146
|
+
sendJson(res, 200, { ok: true, role: base, minRunIntervalSeconds: value, message });
|
|
145
147
|
}
|
|
146
148
|
/** Handle POST /api/role-instances: the role drawer's instance-count editor sets or clears one
|
|
147
|
-
* role's parallel-runner count — the count that lets `feature-2`…`feature-N`
|
|
149
|
+
* role's parallel-runner count — the count that lets `feature-2`…`feature-N` run. Same body
|
|
148
150
|
* discipline as /api/role-interval (readPostBody → 400 malformed/non-object, 413 oversized); a
|
|
149
151
|
* null, "" or whitespace-only value clears the key through unsetConfigKey (so the role falls
|
|
150
152
|
* back to the default 1), otherwise the write rides the CLI's own setConfigKey dotted path so
|
|
@@ -152,18 +154,16 @@ export async function handleRoleInterval(req, res, root) {
|
|
|
152
154
|
* (unlike handleRoleCap's director exemption): setConfigKey runs the candidate through
|
|
153
155
|
* validateConfig, whose INSTANCE_COUNT rejects 0, 9, 1.5 and `abc` and whose INSTANCE_ROLES
|
|
154
156
|
* branch rejects an instances key under any role but feature and bugfix (the director and
|
|
155
|
-
* custom roles included). A bare `feature-2`
|
|
157
|
+
* custom roles included). A bare `feature-2` role id maps to its base role's count (baseRoleOf),
|
|
156
158
|
* since the count is about the role. A write failure is configKeyError's 400 exactly as
|
|
157
|
-
* handleRoleInterval reports it. */
|
|
159
|
+
* handleRoleInterval reports it. A no-op set or clear says so in its message. */
|
|
158
160
|
export async function handleRoleInstances(req, res, root) {
|
|
159
161
|
const body = await readPostBody(req, res, '{"role": "feature", "instances": 3}');
|
|
160
162
|
if (!body)
|
|
161
163
|
return;
|
|
162
|
-
const role = body
|
|
163
|
-
if (
|
|
164
|
-
badRequest(res, "role required");
|
|
164
|
+
const role = requireRoleField(res, body);
|
|
165
|
+
if (role === null)
|
|
165
166
|
return;
|
|
166
|
-
}
|
|
167
167
|
const value = body.instances;
|
|
168
168
|
if (value === undefined) {
|
|
169
169
|
badRequest(res, "instances required");
|
|
@@ -182,7 +182,10 @@ export async function handleRoleInstances(req, res, root) {
|
|
|
182
182
|
const clearResult = unsetConfigKey(root, `roles.${base}.instances`);
|
|
183
183
|
if (!clearResult.ok)
|
|
184
184
|
return configKeyError(res, `roles.${base}.instances`, clearResult);
|
|
185
|
-
|
|
185
|
+
const message = clearResult.oldValue === undefined
|
|
186
|
+
? `${base}: instances is not set in tumwater.json, so nothing was cleared`
|
|
187
|
+
: `${base}: instances cleared`;
|
|
188
|
+
sendJson(res, 200, { ok: true, role: base, message });
|
|
186
189
|
return;
|
|
187
190
|
}
|
|
188
191
|
// setConfigKey takes the raw CLI text; a JSON round-trip of an already-parsed value is
|
|
@@ -191,7 +194,10 @@ export async function handleRoleInstances(req, res, root) {
|
|
|
191
194
|
const result = setConfigKey(root, `roles.${base}.instances`, JSON.stringify(value));
|
|
192
195
|
if (!result.ok)
|
|
193
196
|
return configKeyError(res, `roles.${base}.instances`, result);
|
|
194
|
-
|
|
197
|
+
const message = result.unchanged
|
|
198
|
+
? `${base}: instances is already ${value}, so tumwater.json was not written`
|
|
199
|
+
: `${base}: instances set to ${value}`;
|
|
200
|
+
sendJson(res, 200, { ok: true, role: base, instances: value, message });
|
|
195
201
|
}
|
|
196
202
|
/** Handle POST /api/role-model: the role drawer's Model overrides editor sets or clears one
|
|
197
203
|
* role's provider/model/thinking triple (roles.<base>.provider/.model/.thinking) in one
|
|
@@ -200,31 +206,27 @@ export async function handleRoleInstances(req, res, root) {
|
|
|
200
206
|
* already write it). Same body discipline as /api/role-interval (readPostBody → 400
|
|
201
207
|
* malformed/non-object, 413 oversized); a missing/blank role is a 400 `role required`, each
|
|
202
208
|
* of the three must be a string when present (400 naming the field otherwise), and at least
|
|
203
|
-
* one must be present (400 `provider, model or thinking required`). A bare `feature-2`
|
|
209
|
+
* one must be present (400 `provider, model or thinking required`). A bare `feature-2` role
|
|
204
210
|
* id maps to its base role's keys (baseRoleOf). The director is accepted — configForRole
|
|
205
211
|
* resolves roles.director.* like any role's, matching the CLI's accepted triple for it.
|
|
206
212
|
* Values are trimmed; `""` clears that field (never stored — checkStringField rejects an
|
|
207
213
|
* empty string). The write rides setRoleModelFields, whose validateConfig names
|
|
208
214
|
* roles.<base>.<field> on a bad value and refuses an unknown role, reported as a 400 exactly
|
|
209
|
-
* as handleRoleInterval reports a write failure. */
|
|
215
|
+
* as handleRoleInterval reports a write failure. A no-op write says so in its message. */
|
|
210
216
|
export async function handleRoleModel(req, res, root) {
|
|
211
|
-
const body = await readPostBody(req, res, '{"role": "feature", "provider": "
|
|
217
|
+
const body = await readPostBody(req, res, '{"role": "feature", "provider": "huggingface", "model": "claude-opus-5", "thinking": "high"}');
|
|
212
218
|
if (!body)
|
|
213
219
|
return;
|
|
214
|
-
const role = body
|
|
215
|
-
if (
|
|
216
|
-
badRequest(res, "role required");
|
|
220
|
+
const role = requireRoleField(res, body);
|
|
221
|
+
if (role === null)
|
|
217
222
|
return;
|
|
218
|
-
}
|
|
219
223
|
const fields = {};
|
|
220
224
|
for (const key of ["provider", "model", "thinking"]) {
|
|
221
|
-
|
|
222
|
-
if (value === undefined)
|
|
225
|
+
if (body[key] === undefined)
|
|
223
226
|
continue;
|
|
224
|
-
|
|
225
|
-
|
|
227
|
+
const value = requireStringField(res, body, key, 'a model override, or "" to clear it');
|
|
228
|
+
if (value === null)
|
|
226
229
|
return;
|
|
227
|
-
}
|
|
228
230
|
fields[key] = value.trim();
|
|
229
231
|
}
|
|
230
232
|
if (!("provider" in fields) && !("model" in fields) && !("thinking" in fields)) {
|
|
@@ -235,12 +237,15 @@ export async function handleRoleModel(req, res, root) {
|
|
|
235
237
|
const result = setRoleModelFields(root, base, fields);
|
|
236
238
|
if (!result.ok)
|
|
237
239
|
return badRequest(res, result.error);
|
|
238
|
-
|
|
240
|
+
const message = result.unchanged
|
|
241
|
+
? `${base}: model overrides unchanged, so tumwater.json was not written`
|
|
242
|
+
: `${base}: model updated`;
|
|
243
|
+
sendJson(res, 200, { ok: true, role: base, message });
|
|
239
244
|
}
|
|
240
245
|
/** Handle POST /api/role-instructions: the role drawer's standing-instructions editor — the
|
|
241
246
|
* `Instructions override` row's Edit/Save. Same body discipline as /api/role-cap (readPostBody
|
|
242
247
|
* → 400 malformed/non-object, 413 oversized); an empty role id is a 400, and a bare `feature-2`
|
|
243
|
-
*
|
|
248
|
+
* role id maps to its base role's override (baseRoleOf), the key run-prompt reads — so an
|
|
244
249
|
* instance edits the instructions its runs carry (readRoleBody's shared guard is not used
|
|
245
250
|
* because it rejects instance ids, and the unknown-role check is validateConfig's, below).
|
|
246
251
|
* The director is editable here (its override reaches its runs), unlike handleRoleNotes'
|
|
@@ -248,16 +253,17 @@ export async function handleRoleModel(req, res, root) {
|
|
|
248
253
|
* CLI's --clear-instructions); otherwise the write goes through the CLI's own setConfigKey
|
|
249
254
|
* with the same JSON.stringify wrapper --instructions-file uses, so validateConfig owns the
|
|
250
255
|
* 4096-char cap and the unknown-role check, and a write failure is configKeyError's 400
|
|
251
|
-
* naming the dotted key. */
|
|
256
|
+
* naming the dotted key. A no-op set or clear says so in its message. */
|
|
252
257
|
export async function handleRoleInstructions(req, res, root) {
|
|
253
258
|
const body = await readPostBody(req, res, '{"role": "feature", "text": "…"}');
|
|
254
259
|
if (!body)
|
|
255
260
|
return;
|
|
256
|
-
const role = body
|
|
257
|
-
if (
|
|
258
|
-
return
|
|
259
|
-
|
|
260
|
-
|
|
261
|
+
const role = requireRoleField(res, body);
|
|
262
|
+
if (role === null)
|
|
263
|
+
return;
|
|
264
|
+
const text = requireStringField(res, body, "text", `the full replacement instructions, or "" to clear it`);
|
|
265
|
+
if (text === null)
|
|
266
|
+
return;
|
|
261
267
|
const base = baseRoleOf(role);
|
|
262
268
|
// An empty or whitespace-only buffer clears the override, like the CLI's --clear-instructions;
|
|
263
269
|
// otherwise the same JSON.stringify wrapper --instructions-file uses (setConfigKey takes raw
|
|
@@ -265,7 +271,7 @@ export async function handleRoleInstructions(req, res, root) {
|
|
|
265
271
|
// 4096-char cap and the unknown-role check in writeConfigMutation. Unlike quiet hours the
|
|
266
272
|
// clear arm carries no role guard (the director may clear, and the unknown-role clear gap is
|
|
267
273
|
// pre-existing).
|
|
268
|
-
writeRoleJsonKey(res, root, base, `roles.${base}.instructions`, "instructions",
|
|
274
|
+
writeRoleJsonKey(res, root, base, `roles.${base}.instructions`, "instructions", text, (v) => JSON.stringify(v), (v) => `${base}: instructions saved (${v.length} chars)`);
|
|
269
275
|
}
|
|
270
276
|
/** The shared write behind POST /api/role-enable and POST /api/role-disable: the Settings
|
|
271
277
|
* card's Enable button and the role drawer's two-click Disable both write
|
|
@@ -273,18 +279,19 @@ export async function handleRoleInstructions(req, res, root) {
|
|
|
273
279
|
* yields the boolean checkBoolean expects), and validateConfig's unknown-role check refuses an
|
|
274
280
|
* unknown id as configKeyError's 400 before anything is written. Same body discipline as
|
|
275
281
|
* /api/role-cap (readPostBody → 400 malformed/non-object, 413 oversized); a missing,
|
|
276
|
-
* non-string, or blank role is a 400 `role required`, and a bare `feature-2`
|
|
282
|
+
* non-string, or blank role is a 400 `role required`, and a bare `feature-2` role id maps to
|
|
277
283
|
* its base role's key (baseRoleOf), so an instance disables/enables the role it belongs to.
|
|
278
|
-
* The director cannot be disabled (with it disabled
|
|
284
|
+
* The director cannot be disabled (with it disabled runnerIds omits it and no role dequeues the
|
|
279
285
|
* operator's inbox), so a disable is refused explicitly; enabling the director stays harmless
|
|
280
|
-
* and mirrors the CLI's --enable.
|
|
286
|
+
* and mirrors the CLI's --enable. A role already in the requested state answers `already
|
|
287
|
+
* enabled`/`already disabled` and writes nothing. */
|
|
281
288
|
async function setRoleEnabled(req, res, root, enabled) {
|
|
282
289
|
const body = await readPostBody(req, res, '{"role": "selfcheck"}');
|
|
283
290
|
if (!body)
|
|
284
291
|
return;
|
|
285
|
-
const role = body
|
|
286
|
-
if (
|
|
287
|
-
return
|
|
292
|
+
const role = requireRoleField(res, body);
|
|
293
|
+
if (role === null)
|
|
294
|
+
return;
|
|
288
295
|
const base = baseRoleOf(role);
|
|
289
296
|
if (!enabled && base === DIRECTOR_ROLE)
|
|
290
297
|
return badRequest(res, "the director cannot be disabled");
|
|
@@ -294,7 +301,8 @@ async function setRoleEnabled(req, res, root, enabled) {
|
|
|
294
301
|
const result = setConfigKey(root, key, enabled ? "true" : "false");
|
|
295
302
|
if (!result.ok)
|
|
296
303
|
return configKeyError(res, key, result);
|
|
297
|
-
|
|
304
|
+
const state = enabled ? "enabled" : "disabled";
|
|
305
|
+
sendJson(res, 200, { ok: true, role: base, message: result.unchanged ? `${base}: already ${state}` : `${base}: ${state}` });
|
|
298
306
|
}
|
|
299
307
|
/** Handle POST /api/role-enable: the Settings view's "Disabled roles" card Enable button —
|
|
300
308
|
* re-enables one disabled role (the GUI half of the CLI's `tumwater role <id> --enable`). Body
|
|
@@ -313,7 +321,7 @@ export async function handleRoleDisable(req, res, root) {
|
|
|
313
321
|
* role's daily quiet-hours window — the control the `Quiet hours` row it already shows leads
|
|
314
322
|
* to (the counterpart of the in-flight `tumwater role <id> --quiet-hours` CLI write). Same body
|
|
315
323
|
* discipline as /api/role-cap (readPostBody → 400 malformed/non-object, 413 oversized); a
|
|
316
|
-
* missing/blank role is a 400, and a bare `feature-2`
|
|
324
|
+
* missing/blank role is a 400, and a bare `feature-2` role id maps to its base role's key
|
|
317
325
|
* (baseRoleOf), so an instance edits the window its runs read. The director is refused up front:
|
|
318
326
|
* `quietHoursPerRole.director` is inert (roleQuietHold and doctor both exempt the director). An
|
|
319
327
|
* empty/whitespace-only window clears the key (unsetConfigKey, like the CLI's --clear-quiet-hours);
|
|
@@ -321,18 +329,20 @@ export async function handleRoleDisable(req, res, root) {
|
|
|
321
329
|
* map path skips validateConfig — the guard the set branch gets free from validateConfig's
|
|
322
330
|
* unknown-role refusal; otherwise the write goes through setConfigKey with the same
|
|
323
331
|
* JSON.stringify wrapper, so checkQuietHours (naming quietHoursPerRole.<base>) and validateConfig
|
|
324
|
-
* own the truth, and a write failure is configKeyError's 400 naming the dotted key.
|
|
332
|
+
* own the truth, and a write failure is configKeyError's 400 naming the dotted key. A no-op set
|
|
333
|
+
* or clear says so in its message. */
|
|
325
334
|
export async function handleRoleQuietHours(req, res, root) {
|
|
326
335
|
const body = await readPostBody(req, res, '{"role": "feature", "window": "23:00-07:00"}');
|
|
327
336
|
if (!body)
|
|
328
337
|
return;
|
|
329
|
-
const role = body
|
|
330
|
-
if (
|
|
331
|
-
return
|
|
338
|
+
const role = requireRoleField(res, body);
|
|
339
|
+
if (role === null)
|
|
340
|
+
return;
|
|
332
341
|
if (role === DIRECTOR_ROLE)
|
|
333
342
|
return badRequest(res, "the director has no quiet hours");
|
|
334
|
-
|
|
335
|
-
|
|
343
|
+
const window = requireStringField(res, body, "window", `a window like 23:00-07:00, or "" to clear it`);
|
|
344
|
+
if (window === null)
|
|
345
|
+
return;
|
|
336
346
|
const base = baseRoleOf(role);
|
|
337
347
|
// An empty or whitespace-only window clears the hold, like the CLI's --clear-quiet-hours;
|
|
338
348
|
// the clear arm carries rejectBadRole because unsetConfigKey's no-op map path (a role the
|
|
@@ -340,7 +350,7 @@ export async function handleRoleQuietHours(req, res, root) {
|
|
|
340
350
|
// branch's unknown-role refusal would never fire on a clear and a bogus id would answer 200
|
|
341
351
|
// "cleared" (mirrors the CLI's knownRoleIds guard). The set arm runs setConfigKey raw, its
|
|
342
352
|
// map branch checking the dotted key as the label and validateConfig owning the unknown role.
|
|
343
|
-
writeRoleJsonKey(res, root, base, `quietHoursPerRole.${base}`, "quiet hours",
|
|
353
|
+
writeRoleJsonKey(res, root, base, `quietHoursPerRole.${base}`, "quiet hours", window, (v) => JSON.stringify(v), (v) => `${base}: quiet hours set to ${v}`, () => rejectBadRole(root, res, role));
|
|
344
354
|
}
|
|
345
355
|
/** The shared 400 for a failed config mutation — the set and unset write paths both nest the
|
|
346
356
|
* write's error under the key it names, so the Settings view can tell which field failed and a
|
|
@@ -349,6 +359,12 @@ export async function handleRoleQuietHours(req, res, root) {
|
|
|
349
359
|
function configKeyError(res, key, result) {
|
|
350
360
|
badRequest(res, `${key}: ${result.error}`);
|
|
351
361
|
}
|
|
362
|
+
/** The `unchanged: true` field a no-op config write adds to its 200 reply, or nothing when
|
|
363
|
+
* the write changed the file — spread into the body by the budget, role-cap, and config-set
|
|
364
|
+
* handlers. */
|
|
365
|
+
function unchangedFlag(result) {
|
|
366
|
+
return result.unchanged ? { unchanged: true } : {};
|
|
367
|
+
}
|
|
352
368
|
/** The set-or-clear write the two per-role JSON-config-key handlers (quiet hours, standing
|
|
353
369
|
* instructions) share: an empty/whitespace `raw` unsets `key` (after `guardClear`, a per-call
|
|
354
370
|
* check the clear arm runs first — quiet hours' unknown-role guard; instructions has none),
|
|
@@ -363,15 +379,21 @@ function writeRoleJsonKey(res, root, base, key, noun, raw, serialize, setMessage
|
|
|
363
379
|
const result = unsetConfigKey(root, key);
|
|
364
380
|
if (!result.ok)
|
|
365
381
|
configKeyError(res, key, result);
|
|
366
|
-
else
|
|
367
|
-
|
|
382
|
+
else {
|
|
383
|
+
const message = result.oldValue === undefined
|
|
384
|
+
? `${base}: ${noun} is not set in tumwater.json, so nothing was cleared`
|
|
385
|
+
: `${base}: ${noun} cleared`;
|
|
386
|
+
sendJson(res, 200, { ok: true, role: base, message });
|
|
387
|
+
}
|
|
368
388
|
return;
|
|
369
389
|
}
|
|
370
390
|
const result = setConfigKey(root, key, serialize(raw));
|
|
371
391
|
if (!result.ok)
|
|
372
392
|
configKeyError(res, key, result);
|
|
373
|
-
else
|
|
374
|
-
|
|
393
|
+
else {
|
|
394
|
+
const message = result.unchanged ? `${base}: ${noun} unchanged, so tumwater.json was not written` : setMessage(raw);
|
|
395
|
+
sendJson(res, 200, { ok: true, role: base, message });
|
|
396
|
+
}
|
|
375
397
|
}
|
|
376
398
|
/** Handle POST /api/config-set: the Settings view's per-key Save — one curated key set to
|
|
377
399
|
* one JSON value, through setConfigKey so the browser cannot drift from the CLI's rules
|
|
@@ -382,7 +404,7 @@ function writeRoleJsonKey(res, root, base, key, noun, raw, serialize, setMessage
|
|
|
382
404
|
* JSON.stringify before setConfigKey parses it back, so `set model gpt-5`'s
|
|
383
405
|
* JSON-or-literal rule is bypassed harmlessly: the browser already sent parsed JSON.
|
|
384
406
|
* Same body discipline as /api/budget (readPostBody → 400 malformed/non-object, 413
|
|
385
|
-
* oversized). */
|
|
407
|
+
* oversized). A value already in place answers `unchanged: true`. */
|
|
386
408
|
export async function handleConfigSet(req, res, root) {
|
|
387
409
|
const body = await readPostBody(req, res, '{"key": "model", "value": "gpt-5"}');
|
|
388
410
|
if (!body)
|
|
@@ -399,7 +421,7 @@ export async function handleConfigSet(req, res, root) {
|
|
|
399
421
|
const result = setConfigKey(root, key, JSON.stringify(body.value));
|
|
400
422
|
if (!result.ok)
|
|
401
423
|
return configKeyError(res, key, result);
|
|
402
|
-
sendJson(res, 200, { ok: true, key, value: result.value, oldValue: result.oldValue });
|
|
424
|
+
sendJson(res, 200, { ok: true, key, value: result.value, oldValue: result.oldValue, ...unchangedFlag(result) });
|
|
403
425
|
}
|
|
404
426
|
/** Handle POST /api/config-unset: the Settings view's per-key Clear — one curated key removed,
|
|
405
427
|
* through unsetConfigKey so the browser cannot drift from `tumwater config unset`'s rules
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The dashboard's POST operator endpoint namespace (src/gui/gui-server.ts routes to these
|
|
3
3
|
* handlers): one file per domain, each adapting HTTP onto the domain work one layer down, all
|
|
4
|
-
* gathered here. The GET data endpoints
|
|
5
|
-
*
|
|
4
|
+
* gathered here. The GET data endpoints are barrelled into gui/gui-endpoints.ts from one module
|
|
5
|
+
* per family (gui-history-endpoints, gui-backlog-endpoints, gui-report-endpoints,
|
|
6
|
+
* gui-admin-endpoints); the shared request-body helpers
|
|
6
7
|
* (readPostBody, readRoleBody, sendRequestResult) live in gui/gui-endpoint-common.ts. The
|
|
7
8
|
* per-domain command modules, each a thin HTTP adapter touching no socket beyond its own `res`:
|
|
8
9
|
* prompt queueing/cancel/edit (gui/gui-prompt-commands), backlog entry file/cancel/edit +
|
|
@@ -15,4 +16,4 @@ export { handlePrompt, handlePromptRole, handlePromptCancel, handlePromptEdit, h
|
|
|
15
16
|
export { handleQuestionAnswer, handleBacklogFile, handleBacklogCancel, handleBacklogRelease, handleBacklogTop, handleBacklogEdit } from "./gui-backlog-commands.js";
|
|
16
17
|
export { handleConfigSet, handleConfigUnset, handleBudget, handleRoleCap, handleRoleEnable, handleRoleDisable, handleRoleInstances, handleRoleInterval, handleRoleInstructions, handleRoleModel, handleRoleQuietHours, handleNotifyTest } from "./gui-config-commands.js";
|
|
17
18
|
export { handlePause, handlePauseRole, handleWake, handleWakeCancel } from "./gui-team-commands.js";
|
|
18
|
-
export { handleResetCounters, handleRestart, handleReclaim, handleAbort, handleStopSchedule, handleRoleNotes, handleRetire, handleBriefEdit } from "./gui-operator-commands.js";
|
|
19
|
+
export { handleResetCounters, handleRestart, handleReclaim, handleKillOrphans, handleDoctorReprobe, handleAbort, handleStopSchedule, handleRoleNotes, handleRetire, handleRevert, handleQueueCancel, handleBriefEdit } from "./gui-operator-commands.js";
|
|
@@ -25,10 +25,10 @@ export async function readPostBody(req, res, example) {
|
|
|
25
25
|
* wake/abort/prompt-role/pause-role family states the body-discipline-and-role-check pairing
|
|
26
26
|
* once instead of two guard lines per handler. Its null return means the response is already
|
|
27
27
|
* written and the handler must stop; callers keep their own `if (!body) return` bail. Exactly
|
|
28
|
-
*
|
|
29
|
-
* (allowMissing, the {} → all-roles default), handleAbort, handlePauseRole,
|
|
30
|
-
* handleRoleNotes; handlePromptCancel deliberately does not ride it, since
|
|
31
|
-
* optional and validates only when present. */
|
|
28
|
+
* eight call sites — handlePromptRole, handleWake, handleWakeCancel and handleResetCounters
|
|
29
|
+
* (allowMissing, the {} → all-roles default), handleAbort, handlePauseRole,
|
|
30
|
+
* handleRoleNotes, and handleRetire; handlePromptCancel deliberately does not ride it, since
|
|
31
|
+
* its role is optional and validates only when present. */
|
|
32
32
|
export async function readRoleBody(req, res, example, root, allowMissing = false) {
|
|
33
33
|
const body = await readPostBody(req, res, example);
|
|
34
34
|
if (body === null)
|