tumwater 0.4.2 → 0.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +6 -6
- package/dist/src/agent/{run-agent.js → agent-run.js} +27 -13
- package/dist/src/agent/{run-settle.js → agent-settle.js} +48 -24
- package/dist/src/agent/{run-spawn.js → agent-spawn.js} +21 -12
- package/dist/src/agent/{run-stream.js → agent-stream.js} +4 -4
- package/dist/src/agent/backend-traits.js +2 -2
- package/dist/src/agent/claude/claude-account.js +6 -27
- package/dist/src/agent/claude/claude-backend.js +3 -3
- package/dist/src/agent/claude/claude-env.js +27 -31
- package/dist/src/agent/claude/claude-stream.js +111 -54
- package/dist/src/agent/codex/codex-account.js +8 -0
- package/dist/src/agent/codex/codex-args.js +5 -5
- package/dist/src/agent/codex/codex-backend.js +2 -2
- package/dist/src/agent/codex/codex-env.js +28 -15
- package/dist/src/agent/codex/codex-hook-trust.js +39 -20
- package/dist/src/agent/codex/codex-rollout.js +93 -56
- package/dist/src/agent/codex/codex-stream.js +1 -1
- package/dist/src/agent/env-scrub.js +18 -0
- package/dist/src/agent/image-input.js +68 -11
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +26 -24
- package/dist/src/agent/opencode/opencode-catalog.js +85 -36
- package/dist/src/agent/opencode/opencode-config.js +32 -14
- package/dist/src/agent/opencode/opencode-env.js +12 -13
- package/dist/src/agent/opencode/opencode-stream.js +47 -25
- package/dist/src/agent/session-ledger.js +23 -27
- package/dist/src/agent/subscription-account.js +2 -83
- package/dist/src/backlog/backlog-archive.js +3 -4
- package/dist/src/backlog/backlog-cli-view.js +81 -41
- package/dist/src/backlog/backlog-cli.js +57 -26
- package/dist/src/backlog/backlog-eligibility.js +56 -122
- package/dist/src/backlog/backlog-md.js +5 -5
- package/dist/src/backlog/backlog-parts.js +65 -27
- package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +1 -1
- package/dist/src/backlog/backlog-release.js +12 -12
- package/dist/src/backlog/backlog-structure.js +114 -61
- package/dist/src/backlog/backlog.js +3 -2
- package/dist/src/baseline/main-baseline.js +148 -84
- package/dist/src/baseline/main-guardrails.js +2 -2
- package/dist/src/baseline/main-watch.js +84 -50
- package/dist/src/brief/brief.js +26 -13
- package/dist/src/budget/budget-fallback-view.js +100 -243
- package/dist/src/budget/budget-wiring.js +29 -65
- package/dist/src/budget/budget.js +4 -5
- package/dist/src/budget/fallback-breaker.js +2 -2
- package/dist/src/build/build-check-detect.js +43 -14
- package/dist/src/build/build-check-events.js +2 -2
- package/dist/src/build/build-check-report.js +10 -0
- package/dist/src/build/build-check-scoped.js +40 -18
- package/dist/src/build/build-check.js +13 -5
- package/dist/src/build/build-stage.js +6 -4
- package/dist/src/change/change-data.js +1 -4
- package/dist/src/cli/cli-command-args.js +89 -52
- package/dist/src/cli/cli-completion-command.js +75 -0
- package/dist/src/cli/{config-commands.js → cli-config-commands.js} +82 -71
- package/dist/src/cli/cli-doctor-command.js +103 -0
- package/dist/src/cli/cli-flag-specs.js +42 -22
- package/dist/src/cli/cli-init.js +36 -14
- package/dist/src/cli/{log-commands.js → cli-log-commands.js} +43 -23
- package/dist/src/cli/{notify-command.js → cli-notify-command.js} +2 -2
- package/dist/src/cli/cli-query-commands.js +2 -2
- package/dist/src/cli/{question-commands.js → cli-question-commands.js} +86 -60
- package/dist/src/cli/cli-repo-gate.js +2 -2
- package/dist/src/cli/cli-role-command.js +2 -41
- package/dist/src/cli/cli-start-summary.js +4 -3
- package/dist/src/cli/cli-start-supervisor.js +190 -0
- package/dist/src/cli/cli-start.js +94 -244
- package/dist/src/cli/completion.js +85 -49
- package/dist/src/cli/follow-view.js +20 -2
- package/dist/src/cli/help.js +6 -13
- package/dist/src/cli.js +70 -176
- package/dist/src/collections.js +39 -27
- package/dist/src/concurrency/check-slots.js +70 -0
- package/dist/src/concurrency/lock.js +23 -1
- package/dist/src/concurrency/run-permit.js +49 -0
- package/dist/src/concurrency/semaphore.js +46 -250
- package/dist/src/config/config-editable-keys.js +4 -7
- package/dist/src/config/config-fallbacks.js +81 -101
- package/dist/src/config/config-field-checks.js +66 -37
- package/dist/src/config/config-keys.js +66 -40
- package/dist/src/config/config-live.js +4 -28
- package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
- package/dist/src/config/config-request.js +163 -102
- package/dist/src/config/config-schema.js +10 -12
- package/dist/src/config/config-selector-checks.js +1 -1
- package/dist/src/config/config-validation-agents.js +2 -2
- package/dist/src/config/config-validation-roles.js +8 -72
- package/dist/src/config/config-validation.js +4 -39
- package/dist/src/config/config-views.js +26 -50
- package/dist/src/config/config-write.js +128 -102
- package/dist/src/config/config.js +5 -14
- package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
- package/dist/src/doctor/doctor-backlog-plans.js +53 -120
- package/dist/src/doctor/doctor-backlog.js +16 -14
- package/dist/src/doctor/doctor-check-names.js +3 -6
- package/dist/src/doctor/doctor-checks.js +8 -8
- package/dist/src/doctor/doctor-claude-checks.js +70 -36
- package/dist/src/doctor/doctor-codex-checks.js +6 -6
- package/dist/src/doctor/doctor-contention-checks.js +57 -117
- package/dist/src/doctor/doctor-fallback-checks.js +48 -26
- package/dist/src/doctor/doctor-holds.js +8 -47
- package/dist/src/doctor/doctor-install.js +1 -1
- package/dist/src/doctor/doctor-launch-services.js +1 -1
- package/dist/src/doctor/doctor-model-checks.js +178 -94
- package/dist/src/doctor/doctor-notify.js +2 -2
- package/dist/src/doctor/doctor-opencode-checks.js +5 -4
- package/dist/src/doctor/doctor-orphans.js +122 -21
- package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
- package/dist/src/doctor/doctor-pi-checks.js +2 -2
- package/dist/src/doctor/doctor-principles.js +3 -3
- package/dist/src/doctor/doctor-project-checks.js +24 -11
- package/dist/src/doctor/doctor-role-notes.js +1 -1
- package/dist/src/doctor/doctor.js +8 -11
- package/dist/src/events/event-format.js +44 -64
- package/dist/src/events/event-labels.js +1 -5
- package/dist/src/events/event-legacy.js +3 -5
- package/dist/src/events/{notify.js → event-notify.js} +6 -6
- package/dist/src/events/event-read.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +8 -0
- package/dist/src/failure/failure-cluster.js +9 -18
- package/dist/src/failure/failure-data.js +12 -8
- package/dist/src/failure/failure-render.js +46 -36
- package/dist/src/failure/failure-state-change.js +54 -120
- package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
- package/dist/src/files/files.js +30 -0
- package/dist/src/files/json-object.js +8 -8
- package/dist/src/files/stat-cache.js +12 -8
- package/dist/src/gates/budget-gates.js +85 -84
- package/dist/src/gates/gate-polls.js +32 -74
- package/dist/src/gates/pause-gates.js +16 -2
- package/dist/src/gates/readiness.js +1 -1
- package/dist/src/gates/role-cap-gates.js +44 -35
- package/dist/src/gates/{startup-gate.js → startup-gates.js} +9 -8
- package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
- package/dist/src/git/git-diff.js +6 -2
- package/dist/src/git/git.js +75 -45
- package/dist/src/git/worktree-pool.js +3 -6
- package/dist/src/gui/gui-admin-endpoints.js +3 -3
- package/dist/src/gui/gui-args.js +12 -0
- package/dist/src/gui/gui-backlog-commands.js +4 -4
- package/dist/src/gui/gui-backlog-endpoints.js +61 -41
- package/dist/src/gui/gui-command.js +43 -24
- package/dist/src/gui/gui-config-commands.js +2 -64
- package/dist/src/gui/gui-endpoint-commands.js +1 -1
- package/dist/src/gui/gui-endpoint-common.js +19 -1
- package/dist/src/gui/gui-history-endpoints.js +100 -52
- package/dist/src/gui/gui-operator-commands.js +43 -48
- package/dist/src/gui/gui-prompt-commands.js +25 -25
- package/dist/src/gui/gui-report-endpoints.js +1 -1
- package/dist/src/gui/gui-server.js +1 -2
- package/dist/src/gui/gui-team-commands.js +2 -1
- package/dist/src/history/history-data.js +71 -44
- package/dist/src/history/history-queued.js +49 -27
- package/dist/src/history/history.js +54 -36
- package/dist/src/inbox/prompt-commands.js +59 -42
- package/dist/src/init/init.js +88 -40
- package/dist/src/merge/merge-backlog-conflicts.js +1 -1
- package/dist/src/merge/merge-check-failures.js +12 -9
- package/dist/src/merge/merge-core.js +7 -32
- package/dist/src/merge/merge-drain.js +11 -13
- package/dist/src/merge/merge-pipeline.js +6 -7
- package/dist/src/merge/merge-post-review-drain.js +11 -14
- package/dist/src/merge/merge-queue-render.js +3 -3
- package/dist/src/merge/merge-requeue.js +1 -1
- package/dist/src/merge/merge-slot.js +0 -8
- package/dist/src/merge/merge-stack.js +17 -18
- package/dist/src/merge/merge-to-main.js +13 -58
- package/dist/src/merge/merge-verify.js +12 -21
- package/dist/src/notes-cli.js +52 -42
- package/dist/src/operator/operator-commands.js +80 -47
- package/dist/src/operator/operator-intent.js +3 -3
- package/dist/src/orchestrator/orchestrator-launch.js +31 -63
- package/dist/src/orchestrator/orchestrator-poll.js +44 -69
- package/dist/src/orchestrator/orchestrator-scheduling.js +5 -9
- package/dist/src/orchestrator/orchestrator.js +8 -21
- package/dist/src/orchestrator/scheduling-admit.js +101 -68
- package/dist/src/orchestrator/scheduling-scope.js +18 -27
- package/dist/src/orchestrator/scheduling-skip.js +10 -14
- package/dist/src/orchestrator/startup-migrations.js +1 -1
- package/dist/src/paths.js +10 -9
- package/dist/src/pi/pi-args.js +5 -2
- package/dist/src/pi/pi-backend.js +2 -2
- package/dist/src/pi/pi-bin.js +3 -3
- package/dist/src/pi/pi-event-line.js +28 -1
- package/dist/src/pi/pi-listed-models.js +13 -0
- package/dist/src/pi/pi-models.js +43 -29
- package/dist/src/pi/pi-settings.js +43 -20
- package/dist/src/pi/pi-stream.js +41 -8
- package/dist/src/pi/pi-watchdogs.js +43 -15
- package/dist/src/pi/pi.js +2 -2
- package/dist/src/pi-extension/context-shake.js +32 -20
- package/dist/src/pi-extension/kill-guard.js +69 -0
- package/dist/src/process/process-group.js +51 -26
- package/dist/src/process/process.js +20 -12
- package/dist/src/process/run-marker.js +33 -15
- package/dist/src/process/supervisor.js +40 -28
- package/dist/src/prompt/dialect.js +1 -1
- package/dist/src/prompt/gate-notes.js +2 -2
- package/dist/src/prompt/gate-prompts.js +1 -44
- package/dist/src/prompt/notes-backlog.js +66 -37
- package/dist/src/prompt/principles.js +6 -4
- package/dist/src/prompt/prompt-followup.js +12 -4
- package/dist/src/prompt/prompt.js +88 -38
- package/dist/src/redeploy/redeployer.js +21 -1
- package/dist/src/report/report-data.js +53 -37
- package/dist/src/report/report-fold.js +16 -9
- package/dist/src/report/report-render.js +21 -194
- package/dist/src/report/report-since-data.js +4 -3
- package/dist/src/report/report-since-render.js +173 -0
- package/dist/src/report/report-usage-fold.js +8 -8
- package/dist/src/report/report.js +46 -22
- package/dist/src/review/diff-rules.js +35 -21
- package/dist/src/review/post-merge-review-render.js +9 -3
- package/dist/src/review/post-merge-review.js +48 -41
- package/dist/src/review/review-precheck.js +14 -4
- package/dist/src/review/review.js +48 -170
- package/dist/src/review/secret-leaks.js +26 -16
- package/dist/src/roles/legacy-role-migration.js +6 -3
- package/dist/src/roles/role-catalog.js +11 -3
- package/dist/src/roles/role-guidance.js +16 -12
- package/dist/src/roles/role-render.js +7 -46
- package/dist/src/roles/role-view.js +10 -33
- package/dist/src/run/acceptance-greps.js +88 -45
- package/dist/src/run/ci-parity.js +19 -8
- package/dist/src/run/run-apply.js +12 -15
- package/dist/src/run/run-detail-data.js +121 -68
- package/dist/src/run/run-detail-render.js +92 -35
- package/dist/src/run/run-detail.js +72 -41
- package/dist/src/run/run-prompt.js +63 -50
- package/dist/src/run/run-resume.js +76 -29
- package/dist/src/run/run-stage.js +141 -90
- package/dist/src/run/run-timing.js +8 -2
- package/dist/src/run/run-usage.js +31 -1
- package/dist/src/run/run-verdict.js +128 -50
- package/dist/src/run/stage-check.js +60 -50
- package/dist/src/run/stage-checks-shared.js +28 -0
- package/dist/src/run/stage-name-checks.js +111 -92
- package/dist/src/run/stage-path-checks.js +69 -42
- package/dist/src/run/stage-prose-checks.js +55 -40
- package/dist/src/runner/{leftover.js → runner-leftover.js} +9 -26
- package/dist/src/runner/runner-outcome.js +7 -2
- package/dist/src/runner/runner-pi.js +9 -22
- package/dist/src/runner/runner-recovery.js +5 -13
- package/dist/src/runner/{revision.js → runner-revision.js} +1 -2
- package/dist/src/runner/runner-run.js +103 -50
- package/dist/src/runner/runner-state-load.js +18 -28
- package/dist/src/runner/runner-state.js +0 -14
- package/dist/src/runner/runner.js +41 -25
- package/dist/src/scheduling/backoff.js +8 -3
- package/dist/src/scheduling/claim-entries.js +2 -1
- package/dist/src/scheduling/claims.js +0 -4
- package/dist/src/scheduling/scheduling.js +53 -66
- package/dist/src/status/status-budget.js +4 -43
- package/dist/src/status/status-data.js +35 -39
- package/dist/src/status/status-role-notes.js +2 -2
- package/dist/src/status/status-roles.js +9 -18
- package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
- package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
- package/dist/src/team/team-hold.js +105 -70
- package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +55 -36
- package/dist/src/team/team-polls.js +53 -201
- package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
- package/dist/src/team/team-state.js +4 -13
- package/dist/src/text/datetime.js +16 -7
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +29 -15
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +6 -5
- package/dist/src/ui/alert-feeds.js +46 -111
- package/dist/src/ui/badges.js +17 -88
- package/dist/src/ui/gui/gui-client-boot.js +2 -8
- package/dist/src/ui/gui/gui-client-composer.js +1 -1
- package/dist/src/ui/gui/gui-client-drawer-config.js +8 -33
- package/dist/src/ui/gui/gui-client-drawer.js +1 -1
- package/dist/src/ui/gui/gui-client-feed.js +1 -1
- package/dist/src/ui/gui/gui-client-history.js +28 -14
- package/dist/src/ui/gui/gui-client-model.js +0 -12
- package/dist/src/ui/gui/gui-client-operator.js +7 -5
- package/dist/src/ui/gui/gui-client-pending.js +7 -6
- package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
- package/dist/src/ui/gui/gui-client-report.js +1 -1
- package/dist/src/ui/gui/gui-client-roles.js +0 -1
- package/dist/src/ui/gui/gui-client-settings.js +43 -84
- package/dist/src/ui/gui/gui-client-team.js +12 -6
- package/dist/src/ui/gui/gui-client.js +7 -1
- package/dist/src/ui/progress-data.js +107 -44
- package/dist/src/ui/run-progress-model.js +20 -4
- package/dist/src/ui/status-model.js +2 -24
- package/dist/src/ui/status-payload.js +11 -30
- package/dist/src/ui/status-render.js +13 -24
- package/dist/src/ui/team-alert-backlog.js +14 -204
- package/dist/src/ui/team-alert-shared.js +6 -29
- package/dist/src/ui/team-alert-status.js +9 -170
- package/dist/src/ui/team-alert-tail.js +19 -215
- package/dist/src/ui/team-alerts.js +8 -15
- package/dist/src/ui/transcript-run.js +27 -16
- package/dist/src/ui/tui/tui-backlog.js +2 -2
- package/dist/src/ui/tui/tui-editor-answer.js +12 -10
- package/dist/src/ui/tui/tui-editor-attach.js +4 -3
- package/dist/src/ui/tui/tui-editor-backends.js +4 -3
- package/dist/src/ui/tui/tui-editor-brief.js +6 -9
- package/dist/src/ui/tui/tui-editor-budget.js +4 -3
- package/dist/src/ui/tui/tui-editor-config.js +80 -68
- package/dist/src/ui/tui/tui-editor-entry.js +12 -20
- package/dist/src/ui/tui/tui-editor-pause.js +51 -57
- package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
- package/dist/src/ui/tui/tui-editor-queued.js +11 -14
- package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
- package/dist/src/ui/tui/tui-editor-release.js +4 -3
- package/dist/src/ui/tui/tui-editor-retire.js +7 -7
- package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
- package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
- package/dist/src/ui/tui/tui-editor-search.js +4 -3
- package/dist/src/ui/tui/tui-editor-session.js +17 -4
- package/dist/src/ui/tui/tui-editors.js +5 -10
- package/dist/src/ui/tui/tui-frame.js +182 -134
- package/dist/src/ui/tui/tui-guard.js +20 -0
- package/dist/src/ui/tui/tui-input.js +167 -98
- package/dist/src/ui/tui/tui-keymap.js +51 -29
- package/dist/src/ui/tui/tui-keys.js +1 -1
- package/dist/src/ui/tui/tui-pane-activity.js +5 -4
- package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +252 -230
- package/dist/src/ui/tui/tui-pane-history.js +67 -74
- package/dist/src/ui/tui/tui-pane-pending.js +288 -247
- package/dist/src/ui/tui/tui-pane-queued.js +25 -27
- package/dist/src/ui/tui/tui-pane-report.js +2 -1
- package/dist/src/ui/tui/tui-pane-session.js +15 -10
- package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
- package/dist/src/ui/tui/tui-pane.js +14 -11
- package/dist/src/ui/tui/tui-panes.js +38 -33
- package/dist/src/ui/tui/tui.js +4 -7
- package/dist/src/verdict/fix-claim.js +107 -56
- package/dist/src/verdict/refusal.js +1 -1
- package/dist/src/verdict/reply-contract.js +3 -4
- package/package.json +5 -5
- package/dist/src/concurrency/provider-lease.js +0 -105
- package/dist/src/review/review-followup.js +0 -87
- package/dist/src/review/review-run.js +0 -395
- package/dist/src/runner/model-fallback.js +0 -76
- package/dist/src/runner/runner-fallback.js +0 -66
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
- package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
- /package/dist/src/cli/{brief-command.js → cli-brief-command.js} +0 -0
- /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
- /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
- /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
- /package/dist/src/operator/{revert.js → operator-revert.js} +0 -0
package/dist/src/text/text.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
* depends on node built-ins alone, so any layer (harness or observer) can import it without
|
|
13
13
|
* reaching into another module's internals — and the collapse/truncation/compaction/
|
|
14
14
|
* abbreviation semantics live in exactly one place instead of drifting per consumer. */
|
|
15
|
+
import { distinctOrdered } from "../collections.js";
|
|
15
16
|
/** Collapse every run of whitespace to a single space and trim both ends — the shape every
|
|
16
17
|
* one-line label takes before display (multi-line pi text, command strings, error messages). */
|
|
17
18
|
export function collapseWhitespace(s) {
|
|
@@ -94,9 +95,9 @@ export function gotSuffix(v) {
|
|
|
94
95
|
* and its legacy provider, exempt paths, custom-role tasks), pi-event-line.ts's tool-result content
|
|
95
96
|
* test, quiet-hours.ts's per-role window lookup, and inbox-attachments.ts's image-name check, so
|
|
96
97
|
* the blank-means-unset rule cannot drift per call site. Callers where a bare empty string means
|
|
97
|
-
* unset rather than blank text (notify.ts, version.ts, inbox-cancel.ts) keep their own
|
|
98
|
-
* check; typed-string callers (checkStringField's two-branch shape,
|
|
99
|
-
* report) spell the trim test inline. */
|
|
98
|
+
* unset rather than blank text (event-notify.ts, version.ts, inbox-cancel.ts) keep their own
|
|
99
|
+
* `=== ""` check; typed-string callers (checkStringField's two-branch shape,
|
|
100
|
+
* checkStringArray's blank-index report) spell the trim test inline. */
|
|
100
101
|
export function isNonBlankString(v) {
|
|
101
102
|
return typeof v === "string" && v.trim() !== "";
|
|
102
103
|
}
|
|
@@ -112,7 +113,7 @@ export function splitCommaList(raw) {
|
|
|
112
113
|
* filter and the GUI's `role` and `model` query parameters, each of which treats a repeat as
|
|
113
114
|
* the same selector. */
|
|
114
115
|
export function splitCommaListUnique(raw) {
|
|
115
|
-
return
|
|
116
|
+
return distinctOrdered(splitCommaList(raw));
|
|
116
117
|
}
|
|
117
118
|
/** A value or list of `T` as a list: a bare `T` is one item, an array stays itself. The
|
|
118
119
|
* single home of the string-or-list normalization (a `--role` selector is either one id or a
|
|
@@ -272,5 +273,5 @@ export function tooLongMessage(subject, n, max, tail) {
|
|
|
272
273
|
* role-note missing-path scan (run/run-prompt.ts), so the two agree on what reads as a path. */
|
|
273
274
|
export function pathTokens(text) {
|
|
274
275
|
const tokenRe = /[A-Za-z0-9_.-]+(?:\/[A-Za-z0-9_.-]+)+\.[A-Za-z0-9]+/g;
|
|
275
|
-
return
|
|
276
|
+
return distinctOrdered([...text.matchAll(tokenRe)].map((m) => m[0]));
|
|
276
277
|
}
|
|
@@ -1,115 +1,50 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import { piModelsPath } from "../pi/pi-models.js";
|
|
13
|
-
import { piSettingsPath, readPiDefaultModel } from "../pi/pi-settings.js";
|
|
14
|
-
/** This module is the alert-feed half of src/ui/status-payload.ts: each always-on team alert's
|
|
15
|
-
* data-sourcing function (capacityContention, …) lives here, a
|
|
16
|
-
* sibling of the src/ui/team-alert-*.ts builders that turn them into banners. status-payload.ts
|
|
17
|
-
* stays the one status-document builder, importing these feeds and folding each feed's result
|
|
18
|
-
* into the `alerts` field; status-render.ts reads the feeds directly for the TUI. */
|
|
19
|
-
/** The open questions the doctor's 7-day rule (src/doctor/doctor-backlog-bugs.ts
|
|
20
|
-
* checkStaleQuestions, STALE_QUESTION_DAYS) calls stale: an open QUESTIONS.md entry whose
|
|
21
|
-
* `(asked by …)` stamp is at least 7 days old. This is the always-on feed for the stale-questions
|
|
22
|
-
* team alert — the shared rule's twin on every observer surface, computed from the same threshold
|
|
23
|
-
* and the same questionFilingDate parser the doctor's check uses, so the two cannot drift on which
|
|
24
|
-
* questions are stale. An undated heading is skipped (like the doctor's check); questions carry no
|
|
25
|
-
* refused note to filter. The returned objects carry the 1-based position (for
|
|
26
|
-
* `tumwater questions answer <n>`), the 0-based index (kept only as the file-order tiebreak), the
|
|
27
|
-
* stamp-stripped title, and the whole-day age. Returned oldest-first — most days waited, ties in
|
|
28
|
-
* file order — so a caller naming the first element names the most-stale question. */
|
|
29
|
-
export function staleOpenQuestions(root, now = Date.now()) {
|
|
30
|
-
return openQuestionEntries(root)
|
|
31
|
-
.flatMap((entry, index) => {
|
|
32
|
-
const stamp = questionFilingDate(entry.title);
|
|
33
|
-
if (stamp === null)
|
|
34
|
-
return [];
|
|
35
|
-
const days = ageInDays(stamp, now);
|
|
36
|
-
return days >= STALE_QUESTION_DAYS ? [{ index, position: index + 1, title: stripEntryStamp(entry.title), days }] : [];
|
|
37
|
-
})
|
|
38
|
-
.sort(byDaysThenIndex);
|
|
1
|
+
import { findOrphans, orphanFailDetail } from "../doctor/doctor-orphans.js";
|
|
2
|
+
import { systemProcessProbe } from "../process/process-table.js";
|
|
3
|
+
/** The orphan scan's alert shape, given a scan it is handed — the pure feed, so a test stages a
|
|
4
|
+
* fake probe's scan and gets exactly the row's message without a real host (the shared
|
|
5
|
+
* findOrphans/orphanFailDetail the doctor row reads, so the two cannot drift). `root` trims this
|
|
6
|
+
* repo's path from the commands, like the doctor row. */
|
|
7
|
+
export function orphanAlertFromScan(scan, root = "") {
|
|
8
|
+
if ("error" in scan || scan.orphans.length === 0)
|
|
9
|
+
return null;
|
|
10
|
+
const detail = orphanFailDetail(root, scan);
|
|
11
|
+
return detail === null ? null : { count: scan.orphans.length, detail };
|
|
39
12
|
}
|
|
40
|
-
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
* planned entries have no cycle, dangling ref, unparsed clause, or misworded dependency. */
|
|
46
|
-
export function planDependencyAlert(root) {
|
|
47
|
-
return planDependencyProblem(root);
|
|
13
|
+
/** Run the doctor's orphan scan and shape it into the standing alert. Async (it does the real
|
|
14
|
+
* process-table read); the always-on surfaces read it through getOrphanAlert's cache below, so
|
|
15
|
+
* a status frame never blocks on the host scan. */
|
|
16
|
+
export async function refreshOrphanAlert(root, probe = systemProcessProbe) {
|
|
17
|
+
return orphanAlertFromScan(await findOrphans(root, probe), root);
|
|
48
18
|
}
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
19
|
+
/** How stale a cached orphan scan may be before the next status read starts a fresh one:
|
|
20
|
+
* orphan leaks grow over days, so a sixty-second refresh is plenty and keeps the host scan
|
|
21
|
+
* (one `ps`, plus cwd reads only for the parentless candidates argv did not settle) off the
|
|
22
|
+
* hot render path. */
|
|
23
|
+
const ORPHAN_ALERT_REFRESH_MS = 60_000;
|
|
24
|
+
let orphanCache = null;
|
|
25
|
+
let orphanLoading = false;
|
|
26
|
+
/** The synchronous orphan feed the always-on surfaces (status-payload, status-render, the TUI)
|
|
27
|
+
* read each frame: the last scan's value while it is fresher than ORPHAN_ALERT_REFRESH_MS, else a
|
|
28
|
+
* background refresh is kicked (one at a time) and the previous value — null on the first ever
|
|
29
|
+
* read — is returned until that scan lands. The refresh runs off the render path, so a slow host
|
|
30
|
+
* scan never stalls a status frame. Per-process: each long-lived surface (the GUI server, the
|
|
31
|
+
* TUI) and the CLI each run their own throttled scan against the same doctor rule. */
|
|
32
|
+
export function getOrphanAlert(root, probe = systemProcessProbe, now = Date.now()) {
|
|
33
|
+
if (orphanCache === null || now - orphanCache.at >= ORPHAN_ALERT_REFRESH_MS) {
|
|
34
|
+
if (!orphanLoading) {
|
|
35
|
+
orphanLoading = true;
|
|
36
|
+
void (async () => {
|
|
37
|
+
try {
|
|
38
|
+
orphanCache = { value: await refreshOrphanAlert(root, probe), at: Date.now() };
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
orphanCache = { value: null, at: Date.now() };
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
orphanLoading = false;
|
|
45
|
+
}
|
|
46
|
+
})();
|
|
61
47
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
maybeWarn("workspace slots", SLOT_WAIT_CHECK_WINDOW_MS, (ev) => ev.type === "slot_wait", (waits) => slotWaitContention(waits, readSlotsState(root).slots.length));
|
|
67
|
-
maybeWarn("provider slots", PROVIDER_WAIT_CHECK_WINDOW_MS, (ev) => ev.type === "provider_wait", (waits) => providerWaitContention(waits, configForStatus(root)));
|
|
68
|
-
maybeWarn("check slots", CHECK_WAIT_CHECK_WINDOW_MS, (e) => e.type === "check_wait", (waits) => checkWaitContention(waits, configForStatus(root)));
|
|
69
|
-
return out;
|
|
70
|
-
}
|
|
71
|
-
/** A `maxConcurrentPerProvider` map that cannot do its job — the always-on twin of the doctor's
|
|
72
|
-
* `provider concurrency` row (src/doctor/doctor-contention-checks.ts checkProviderConcurrency):
|
|
73
|
-
* a cap key no seam resolves to (a typo, or a provider the team never runs on), a cap at or
|
|
74
|
-
* above `maxConcurrent` (it never binds), or a keyless seam in a capped team. The `contention`
|
|
75
|
-
* and `provider-hold` alerts fire only once a too-small cap has already parked runs or a storm
|
|
76
|
-
* has struck; this standing alert names the static misconfiguration itself, which the doctor
|
|
77
|
-
* row is the only other surface that otherwise exposes. Reads the same last-known-good config
|
|
78
|
-
* the snapshot uses (configForStatus) and audits it through the row's own
|
|
79
|
-
* providerConcurrencyProblems, so the alert and the row cannot drift on the trigger or the
|
|
80
|
-
* wording; `[]` when no cap is configured or the map is well-formed. */
|
|
81
|
-
export function providerCapProblems(root) {
|
|
82
|
-
return providerConcurrencyProblems(configForStatus(root), readPiDefaultModel(piSettingsPath(piModelsPath()), root));
|
|
83
|
-
}
|
|
84
|
-
/** The always-on twin of the doctor `parallel instances` row's pool-mismatch clause
|
|
85
|
-
* (doctor-contention-checks.ts smallPoolProblems): an enabled `feature`/`bugfix` running
|
|
86
|
-
* more than one instance while slotCount(config) sits below maxConcurrent — the shared pool
|
|
87
|
-
* is smaller than the run cap, so those instances contend for the same checkouts. Built from
|
|
88
|
-
* the same smallPoolProblems selector the doctor row uses, so the alert and the row cannot
|
|
89
|
-
* drift on the trigger, the role list, or the wording. configForStatus never returns
|
|
90
|
-
* undefined (an unreadable tumwater.json falls back to defaultConfig(), whose pool is the
|
|
91
|
-
* default maxConcurrent + 1 — never below maxConcurrent), so the [] is the only silent
|
|
92
|
-
* outcome. */
|
|
93
|
-
export function instancePoolProblems(root) {
|
|
94
|
-
return smallPoolProblems(configForStatus(root));
|
|
95
|
-
}
|
|
96
|
-
/** How far back the regressed-fixes alert looks — one day, so a recently Fixed test that failed
|
|
97
|
-
* again is a standing amber alert until a day passes (or the failure is filed). The doctor's
|
|
98
|
-
* own `regressed fixes` row keeps its since-the-fix read; this is the alert's daily trigger, the
|
|
99
|
-
* same trailing day the regressed-fixes alert reads. */
|
|
100
|
-
export const REGRESSED_FIX_WINDOW_MS = 24 * 60 * 60_000;
|
|
101
|
-
/** The always-on twin of the doctor's `regressed fixes` row (src/doctor/doctor-backlog-bugs.ts
|
|
102
|
-
* checkRegressedFixes): the same candidate selection (regressedFixCandidates) and the same
|
|
103
|
-
* failureMentions match (matchedRegressedFixes) and sentence (regressedFixesPhrase), read off the
|
|
104
|
-
* trailing REGRESSED_FIX_WINDOW_MS of events (the doctor, whose window is since each fix, still
|
|
105
|
-
* warns on an older recurrence). An unreadable event log yields none. */
|
|
106
|
-
export function regressedFixesAlert(root, now = Date.now()) {
|
|
107
|
-
const candidates = regressedFixCandidates(root);
|
|
108
|
-
const rows = matchedRegressedFixes(candidates, eventsSince(root, REGRESSED_FIX_WINDOW_MS, now));
|
|
109
|
-
const detail = regressedFixesPhrase(rows);
|
|
110
|
-
return detail === null ? null : { count: rows.length, detail };
|
|
48
|
+
}
|
|
49
|
+
return orphanCache === null ? null : orphanCache.value;
|
|
111
50
|
}
|
|
112
|
-
/** Re-export so the always-on alert's stale-pin selector stays in its one home
|
|
113
|
-
* (src/doctor/doctor-contention-checks.ts) while tui.tsx keeps importing every alert feed from
|
|
114
|
-
* this one module. */
|
|
115
|
-
export { staleWorkspacePins };
|
package/dist/src/ui/badges.js
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { sortEntriesByKey } from "../collections.js";
|
|
2
|
-
import { utilizationPct, windowLabel } from "../agent/subscription-account.js";
|
|
3
1
|
import { MODEL_TIERS } from "../config/config-schema.js";
|
|
4
2
|
import { budgetGate, budgetReached, fallbackTierEntries } from "../budget/budget.js";
|
|
5
3
|
import { fallbackDisplayPair } from "../config/config-views.js";
|
|
@@ -23,12 +21,21 @@ import { formatGB, shortSha, usd, usdCap } from "../text/format.js";
|
|
|
23
21
|
* team keeps running the stale build, so the badge reads "work continues" and keeps the
|
|
24
22
|
* deadline; `restart pending` (the drain that precedes the swap, which actually holds new runs
|
|
25
23
|
* and merges) resolves itself; `restart BLOCKED` never will until main moves. Telling them apart
|
|
26
|
-
* at a glance is the whole point (see BuildStatus.restartBlocked).
|
|
27
|
-
*
|
|
24
|
+
* at a glance is the whole point (see BuildStatus.restartBlocked). A build that is current against
|
|
25
|
+
* the newest commit that passed main's check, while main's tip has moved its code on, says the
|
|
26
|
+
* update waits for that check (BuildStatus.waitingForCheck) — otherwise nothing tells the
|
|
27
|
+
* operator why the team stays on an old build. Empty when the dist carries no stamp. Shared by
|
|
28
|
+
* the TUI/status header and the GUI's. */
|
|
28
29
|
export function buildBadge(build) {
|
|
29
30
|
if (!build)
|
|
30
31
|
return "";
|
|
31
|
-
const
|
|
32
|
+
const waiting = build.waitingForCheck;
|
|
33
|
+
const stale = build.stale
|
|
34
|
+
? ` — update available (${plural(build.aheadCommits ?? 0, "commit")} behind)`
|
|
35
|
+
: waiting
|
|
36
|
+
? ` — update waiting: main is ${plural(waiting.aheadCommits, "commit")} ahead at ` +
|
|
37
|
+
`${shortSha(waiting.head)}, and no newer commit has passed main's check yet`
|
|
38
|
+
: "";
|
|
32
39
|
const restart = build.restartBlocked
|
|
33
40
|
? build.restartBlocked.startsWith("cooldown until ")
|
|
34
41
|
? ` — restarts after cooldown; work continues (${build.restartBlocked})`
|
|
@@ -103,55 +110,6 @@ export function bootstrapBadge(bootstrap) {
|
|
|
103
110
|
return "";
|
|
104
111
|
return ` · bootstrap: ${bootstrap.plansDone}/${bootstrap.untilPlansDone} plans done`;
|
|
105
112
|
}
|
|
106
|
-
/** One window status as its badge word when no utilization was reported. */
|
|
107
|
-
function claudeWindowStatusWord(status) {
|
|
108
|
-
if (status === "allowed_warning")
|
|
109
|
-
return "near limit";
|
|
110
|
-
if (status === "rejected")
|
|
111
|
-
return "limited";
|
|
112
|
-
return status === "reset" ? "reset" : "ok";
|
|
113
|
-
}
|
|
114
|
-
/** How far off a window's reset is, two units wide: `4h 12m`, `5d 3h`, `37m`. A clock time
|
|
115
|
-
* alone cannot tell a 7d window resetting Monday from one resetting tonight. */
|
|
116
|
-
function untilReset(resetsAt, now) {
|
|
117
|
-
const minutes = Math.floor((resetsAt - now) / 60_000);
|
|
118
|
-
if (minutes < 60)
|
|
119
|
-
return `${minutes}m`;
|
|
120
|
-
const hours = Math.floor(minutes / 60);
|
|
121
|
-
if (hours < 24)
|
|
122
|
-
return `${hours}h ${minutes % 60}m`;
|
|
123
|
-
return `${Math.floor(hours / 24)}d ${hours % 24}h`;
|
|
124
|
-
}
|
|
125
|
-
/** The subscription usage-window fragment (plans/agent-backends.md §8, PLANS.md part 6d/7):
|
|
126
|
-
* `· Claude 5h · 64% · resets in 4h 12m` (`resets now` once the deadline has passed), with
|
|
127
|
-
* the status word when no `utilization` was reported and an `overage` marker while the
|
|
128
|
-
* account spends into overage. A rejected window reads
|
|
129
|
-
* `<backend> model jobs wait until 14:30` in place of `resets` while a future deadline holds
|
|
130
|
-
* the seams; a reset reading names neither. Reads the most-constrained block (`blocks[0]`,
|
|
131
|
-
* already most-constrained-first), rendering under that backend's own display label. The
|
|
132
|
-
* hold clause names the backend by its lowercase id (not the display label), so a claude-only
|
|
133
|
-
* team keeps the byte-identical `claude model jobs wait` the pre-3b badge rendered. The one
|
|
134
|
-
* home of the string, so the TUI header and the payload's preformatted badge cannot drift. */
|
|
135
|
-
function subscriptionWindowBadge(blocks, now, moved = false) {
|
|
136
|
-
const block = blocks[0];
|
|
137
|
-
const w = block.primary;
|
|
138
|
-
const parts = [`${block.label} ${windowLabel(w.type)}`];
|
|
139
|
-
parts.push(typeof w.utilization === "number" ? `${utilizationPct(w.utilization)}%` : claudeWindowStatusWord(w.status));
|
|
140
|
-
if (w.isUsingOverage === true)
|
|
141
|
-
parts.push("overage");
|
|
142
|
-
if (w.status === "rejected" && block.heldUntil !== null) {
|
|
143
|
-
parts.push(`${moved ? "on fallback" : `${block.backend} model jobs wait`} until ${clockHm(block.heldUntil)}`);
|
|
144
|
-
}
|
|
145
|
-
else if (w.status !== "reset" && typeof w.resetsAt === "number") {
|
|
146
|
-
parts.push(w.resetsAt - now < 60_000 ? "resets now" : `resets in ${untilReset(w.resetsAt, now)}`);
|
|
147
|
-
}
|
|
148
|
-
return ` · ${parts.join(" · ")}`;
|
|
149
|
-
}
|
|
150
|
-
/** Whether any tier of a chain block runs on a fallback because the Claude window (or its
|
|
151
|
-
* reserve) is used up. */
|
|
152
|
-
function chainMovedByWindow(chain) {
|
|
153
|
-
return Object.values(chain ?? {}).some((t) => t.reason === "window" || t.reason === "reserve");
|
|
154
|
-
}
|
|
155
113
|
/** The tiers' fallback steps as badge text, ` · `-joined without a leading separator:
|
|
156
114
|
* `default → DeepSeek-V4-Flash $3.10/$5 until 17:40`, one fragment per tier in MODEL_TIERS
|
|
157
115
|
* order. Empty when the block is absent or empty. */
|
|
@@ -172,18 +130,6 @@ export function chainBadge(chain) {
|
|
|
172
130
|
const text = chainFragments(chain);
|
|
173
131
|
return text ? ` · ${text}` : "";
|
|
174
132
|
}
|
|
175
|
-
/** The window fragment's tone rank, for the header badge and the GUI meter: a rejected window
|
|
176
|
-
* is red, an allowed_warning amber, anything else no tone. Reads the most-constrained block
|
|
177
|
-
* (`blocks[0]`), so a multi-backend team's tone follows its tightest window. */
|
|
178
|
-
export function subscriptionWindowTone(blocks) {
|
|
179
|
-
if (!blocks || blocks.length === 0)
|
|
180
|
-
return undefined;
|
|
181
|
-
if (blocks[0].primary.status === "rejected")
|
|
182
|
-
return "red";
|
|
183
|
-
if (blocks[0].primary.status === "allowed_warning")
|
|
184
|
-
return "yellow";
|
|
185
|
-
return undefined;
|
|
186
|
-
}
|
|
187
133
|
/** The header's daily-cost-budget fragment, standing in every cap state (the badge is also
|
|
188
134
|
* the affordance for editing the cap, so a disabled team needs it too): `· budget: n/a`
|
|
189
135
|
* for a team whose models are all free (spend can never accumulate against a cap that
|
|
@@ -204,16 +150,8 @@ export function subscriptionWindowTone(blocks) {
|
|
|
204
150
|
* needs no forecast slot: a disabled cap never carries a capHitAt (projectCapHit returns null
|
|
205
151
|
* for capUsd <= 0), and the fallback fragment only stands once the cap IS reached — the one
|
|
206
152
|
* state where the forecast is itself null — so the fragments can never compete for the line. */
|
|
207
|
-
export function budgetBadge(budget,
|
|
208
|
-
const
|
|
209
|
-
const window = (blocks ? subscriptionWindowBadge(blocks, now, chainMovedByWindow(chain)) : "") + chainBadge(chain);
|
|
210
|
-
// Subscription-only: the window stands IN PLACE of the daily spend — there is no priced
|
|
211
|
-
// spend for a dollar figure to report. This holds only while a window block actually
|
|
212
|
-
// exists: an all-free subscription-only team (only and free, no windows) reads ` · budget: n/a`
|
|
213
|
-
// like any other all-free team, so the free branch below stays reachable. Mixed and none
|
|
214
|
-
// fall through to the daily spend.
|
|
215
|
-
if (only && blocks)
|
|
216
|
-
return window;
|
|
153
|
+
export function budgetBadge(budget, chain) {
|
|
154
|
+
const window = chainBadge(chain);
|
|
217
155
|
if (budget.free)
|
|
218
156
|
return ` · budget: n/a${window}`;
|
|
219
157
|
// Only while the fallback is actually carrying the team (plans/fallback-model.md): the cap
|
|
@@ -383,21 +321,12 @@ export function diskBadge(disk) {
|
|
|
383
321
|
? ` · disk ${free}`
|
|
384
322
|
: "";
|
|
385
323
|
}
|
|
386
|
-
/** The permit pool's header fragment (plans/provider-concurrency.md §8): `slots 5/8
|
|
387
|
-
*
|
|
388
|
-
* in-use/cap. The WHOLE fragment appears only while at least one key carries a cap (the
|
|
389
|
-
* `maxConcurrentPerProvider` map is set), so a team without it renders the header exactly
|
|
390
|
-
* as before; uncapped keys that happen to be in the snapshot are dropped rather than shown
|
|
391
|
-
* with a bare in-use that would read like a cap. */
|
|
324
|
+
/** The permit pool's header fragment (plans/provider-concurrency.md §8): `slots 5/8` — the
|
|
325
|
+
* team total's in-use/cap. Empty when no snapshot was published. */
|
|
392
326
|
export function permitsBadge(permits) {
|
|
393
327
|
if (!permits)
|
|
394
328
|
return "";
|
|
395
|
-
|
|
396
|
-
.map(([k, e]) => `${k} ${e.inUse}/${e.limit}`)
|
|
397
|
-
.join(" · ");
|
|
398
|
-
if (providers.length === 0)
|
|
399
|
-
return "";
|
|
400
|
-
return ` · running ${permits.inUse} of ${permits.limit} · ${providers}`;
|
|
329
|
+
return ` · running ${permits.inUse} of ${permits.limit}`;
|
|
401
330
|
}
|
|
402
331
|
/** The check-permit pool's reading, ` · checks 1/2` plus `, 3 waiting` while checks queue. Empty
|
|
403
332
|
* when no check runs or waits, so an idle check pool leaves the header byte-identical. */
|
|
@@ -73,13 +73,13 @@ export const GUI_CLIENT_BOOT_JS = String.raw ` // One tail for the "…written
|
|
|
73
73
|
// submit listener saves the row's key (saveSetting, gui-client-settings). No per-row wiring,
|
|
74
74
|
// so rows can repaint freely.
|
|
75
75
|
document.addEventListener("submit", (ev) => {
|
|
76
|
-
const form = ev
|
|
76
|
+
const form = clickClosest(ev, "form.settings-row");
|
|
77
77
|
if (form) { ev.preventDefault(); saveSetting(form); }
|
|
78
78
|
});
|
|
79
79
|
// The Brief card's Save: its own form class, so the settings listener above never
|
|
80
80
|
// routes it to saveSetting.
|
|
81
81
|
document.addEventListener("submit", (ev) => {
|
|
82
|
-
const form = ev
|
|
82
|
+
const form = clickClosest(ev, "form.brief-form");
|
|
83
83
|
if (form) { ev.preventDefault(); saveBrief(form); }
|
|
84
84
|
});
|
|
85
85
|
// The Settings view's Clear buttons: one delegated click listener resolves the row's Clear
|
|
@@ -383,12 +383,6 @@ export const GUI_CLIENT_BOOT_JS = String.raw ` // One tail for the "…written
|
|
|
383
383
|
const thinking = thinkingBox ? thinkingBox.value.trim() : "";
|
|
384
384
|
const backend = backendBox ? backendBox.value.trim() : "";
|
|
385
385
|
postAction("/api/role-model", { role: arg, provider, model, thinking, backend }, settingDone(() => { modelEditing = null; }, "Model overrides updated"));
|
|
386
|
-
} else if (act === "review-toggle") {
|
|
387
|
-
const after = drawerRole?.view?.reviewedAfterMerge === true;
|
|
388
|
-
postAction("/api/role-review-after-merge", { role: arg, afterMerge: !after }, (d) => {
|
|
389
|
-
loadDrawerRole(arg);
|
|
390
|
-
return d && d.message ? d.message : "Review tier updated";
|
|
391
|
-
});
|
|
392
386
|
} else if (act === "mention") {
|
|
393
387
|
const { file, index } = entryRef(arg);
|
|
394
388
|
const title = entryTitleAt(file, index);
|
|
@@ -221,7 +221,7 @@ export const GUI_CLIENT_COMPOSER_JS = String.raw ` // ---- composer: one box fo
|
|
|
221
221
|
"<button type='button' data-idx='" + i + "' title='Remove' aria-label='Remove " + esc(img.name) + "'>×</button></span>").join("");
|
|
222
222
|
}
|
|
223
223
|
$("promptimages").addEventListener("click", (ev) => {
|
|
224
|
-
const b = ev
|
|
224
|
+
const b = clickClosest(ev, "button[data-idx]");
|
|
225
225
|
if (!b) return;
|
|
226
226
|
promptImages.splice(Number(b.dataset.idx), 1);
|
|
227
227
|
renderPromptImages();
|
|
@@ -7,22 +7,20 @@
|
|
|
7
7
|
export const GUI_CLIENT_DRAWER_CONFIG_JS = String.raw `
|
|
8
8
|
// A Configuration section built from the same /api/role payload the CLI's tumwater role
|
|
9
9
|
// <id> renders (rolePayload), so the drawer's and the terminal's rows read one document and
|
|
10
|
-
// cannot drift: what the role RUNS ON (model/tier,
|
|
10
|
+
// cannot drift: what the role RUNS ON (model/tier, image reading, the
|
|
11
11
|
// fallback pairs, billing, min run interval) and CARRIES (queued prompts, the instructions
|
|
12
12
|
// override, find text). Each row is guarded by a present/own check so an absent field
|
|
13
|
-
// renders no row —
|
|
14
|
-
//
|
|
13
|
+
// renders no row — no fallback shows no fallback rows, and a notes-only payload adds an
|
|
14
|
+
// empty (omitted) section. Labels and wording mirror
|
|
15
15
|
// role-render.ts's family; no backticks allowed here (this whole script is one big
|
|
16
16
|
// String.raw template).
|
|
17
17
|
function drawerConfigHtml(view, role) {
|
|
18
18
|
const pair = (p, m) => [p, m].filter(Boolean).join("/");
|
|
19
19
|
// A fallback pair's label, mirroring role-render.ts's fallbackPairLabel so the drawer's
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// byte-identical; only a model-fallback non-pi pair (modelFallbackView drops pi's provider
|
|
25
|
-
// for a non-pi backend) gains the namespace the CLI already shows.
|
|
20
|
+
// Budget fallback row names a non-pi backend's namespace the same way the CLI's fallback
|
|
21
|
+
// lines do: a non-pi backend with a model reads <backend>/<model>, else <provider>/<model>,
|
|
22
|
+
// else the model, else the provider, else the pair-less default. A budget pair's provider
|
|
23
|
+
// already holds the backend, so its row stays byte-identical.
|
|
26
24
|
// Local wall-clock HH:MM for the role-cap forecast's ~cap at stamp — the drawer is browser
|
|
27
25
|
// code and cannot import ../text/datetime.js (the quietWindowEnd precedent's own note), so
|
|
28
26
|
// it routes through the bundle's shared clockHm in FORMAT_JS, the same helper the operator
|
|
@@ -92,40 +90,17 @@ export const GUI_CLIENT_DRAWER_CONFIG_JS = String.raw `
|
|
|
92
90
|
if (view.contextWindow !== undefined) {
|
|
93
91
|
rows.push(kvRow("Context window", view.contextWindow === null ? "unknown" : fmtTokens(view.contextWindow) + " tokens"));
|
|
94
92
|
}
|
|
95
|
-
if (view.providerKey !== undefined && view.providerCap !== undefined) {
|
|
96
|
-
rows.push(kvRow("Provider", view.providerKey + " (cap " + view.providerCap + ")"));
|
|
97
|
-
}
|
|
98
93
|
if (view.imageInput !== undefined) {
|
|
99
94
|
const images = view.imageInput === true ? "yes"
|
|
100
95
|
: view.imageInput === false ? (view.imageReroute ? "no — reruns on " + view.imageReroute : "no")
|
|
101
96
|
: "unknown";
|
|
102
97
|
rows.push(kvRow("Images", images));
|
|
103
98
|
}
|
|
104
|
-
if (view.reviewEnabled === true && role !== "director") {
|
|
105
|
-
// The review tier, shown whenever the gate is on (rolePayload's reviewEnabled) so a
|
|
106
|
-
// before-merge-reviewed role still gets the control — the browser twin of the CLI's
|
|
107
|
-
// tumwater role <id> --review-after-merge. The director shows no row, like the cap and
|
|
108
|
-
// quiet-hours rows and the CLI render: its membership is refused by validateConfig's
|
|
109
|
-
// review.afterMerge rule, so a toggle it could never act on would only answer 400. The
|
|
110
|
-
// body names whether the role is reviewed after merge or before it, and the button posts
|
|
111
|
-
// the opposite membership through /api/role-review-after-merge; one click posts (no
|
|
112
|
-
// editor state), like the Settings view's Enable button.
|
|
113
|
-
const revAfter = view.reviewedAfterMerge === true;
|
|
114
|
-
const revBody = revAfter ? "after merge" : "before merge";
|
|
115
|
-
const revControl = "<button type='button' class='btn btn-sm' data-act='review-toggle' data-arg='" + esc(role) + "'>" + (revAfter ? "Review before merge" : "Review after merge") + "</button>";
|
|
116
|
-
rows.push("<div><dt>Review</dt><dd title='" + esc(revBody) + "'>" + esc(revBody) + revControl + "</dd></div>");
|
|
117
|
-
}
|
|
118
|
-
if (view.modelFallback && typeof view.modelFallback === "object" && view.modelFallback.model !== undefined) {
|
|
119
|
-
const fb = view.modelFallback;
|
|
120
|
-
const when = typeof fb.since === "number" ? " since " + fmtAgo(fb.since) : "";
|
|
121
|
-
rows.push(kvRow("Model fallback", fallbackLabel(fb, "") + " — on fallback" + when + " (primary failing: " + (fb.reason ?? "") + ")"));
|
|
122
|
-
}
|
|
123
99
|
if (view.fallback && typeof view.fallback === "object" && view.fallback.model !== undefined) {
|
|
124
100
|
rows.push(kvRow("Budget fallback", fallbackLabel(view.fallback, "") + " (" + (view.fallbackFree ? "free" : "priced") + ")"));
|
|
125
101
|
}
|
|
126
102
|
if (view.billing !== undefined) {
|
|
127
|
-
|
|
128
|
-
rows.push(kvRow("Billing", (view.billing === "api" ? "API" : view.billing) + window));
|
|
103
|
+
rows.push(kvRow("Billing", view.billing === "api" ? "API" : view.billing));
|
|
129
104
|
}
|
|
130
105
|
// The min run interval (rolePayload's minRunIntervalSeconds), worded like role-render.ts's
|
|
131
106
|
// Min run interval line, plus a control to set/clear the per-role override — the last
|
|
@@ -208,7 +208,7 @@ export const GUI_CLIENT_DRAWER_JS = String.raw ` let drawer = null; // { kind:
|
|
|
208
208
|
const commits = Array.isArray(view.commits) ? view.commits : [];
|
|
209
209
|
const dirty = Array.isArray(view.dirtyFiles) ? view.dirtyFiles : [];
|
|
210
210
|
if (view.state === "absent") return "<p class='muted'>No workspace yet — this role has not run.</p>";
|
|
211
|
-
if (view.state === "no-base") return "<p class='muted'>
|
|
211
|
+
if (view.state === "no-base") return "<p class='muted'>" + mainBranchMissingPhrase(view.mainBranch) + ".</p>";
|
|
212
212
|
const scoped = typeof view.path === "string";
|
|
213
213
|
if (scoped && view.state === "ready" && commits.length === 0 && dirty.length === 0) {
|
|
214
214
|
return "<p class='muted'>No pending change to " + esc(view.path) + ".</p>";
|
|
@@ -110,7 +110,7 @@ export const GUI_CLIENT_FEED_JS = String.raw `
|
|
|
110
110
|
const emptyFeedRow = (head, body) => "<li class='empty'><strong>" + head + "</strong>" + body +
|
|
111
111
|
"<button type='button' class='linkish' data-action='feedclear'>Clear filter</button></li>";
|
|
112
112
|
// A per-type tally for the Activity feed, mirroring the CLI's logs --count display rule
|
|
113
|
-
// (read-only from src/cli/log-commands.ts's sayTally): one N × <type> chip per present event
|
|
113
|
+
// (read-only from src/cli/cli-log-commands.ts's sayTally): one N × <type> chip per present event
|
|
114
114
|
// type, count-descending then type-name ascending. A pure function of the rows in hand, so the
|
|
115
115
|
// chips and the <li> rows below cannot drift on which events count; "" for an empty array so
|
|
116
116
|
// empty and empty-state feeds paint no stray chips. Each chip is a button: clicking the
|
|
@@ -71,13 +71,13 @@ export const GUI_CLIENT_HISTORY_JS = String.raw ` // The counts the "Runs to sh
|
|
|
71
71
|
fetchHistory();
|
|
72
72
|
});
|
|
73
73
|
$("histfilter").addEventListener("click", (ev) => {
|
|
74
|
-
const b = ev
|
|
74
|
+
const b = clickClosest(ev, "button[data-hist]");
|
|
75
75
|
if (!b) return;
|
|
76
76
|
histFilter = b.dataset.hist;
|
|
77
77
|
renderHistory();
|
|
78
78
|
});
|
|
79
79
|
$("histsum").addEventListener("click", (ev) => {
|
|
80
|
-
const b = ev
|
|
80
|
+
const b = clickClosest(ev, "button[data-histsum]");
|
|
81
81
|
if (!b) return;
|
|
82
82
|
histMode = b.dataset.histsum;
|
|
83
83
|
store("hist-mode", histMode);
|
|
@@ -98,7 +98,7 @@ export const GUI_CLIENT_HISTORY_JS = String.raw ` // The counts the "Runs to sh
|
|
|
98
98
|
// data-rundetail button).
|
|
99
99
|
// A By model row's label drills into that model's runs: the table, filtered to the selector.
|
|
100
100
|
$("histbody").addEventListener("click", (ev) => {
|
|
101
|
-
const b = ev
|
|
101
|
+
const b = clickClosest(ev, "button[data-histmodel]");
|
|
102
102
|
if (!b) return;
|
|
103
103
|
histModel = b.dataset.histmodel;
|
|
104
104
|
histMode = "table";
|
|
@@ -106,43 +106,43 @@ export const GUI_CLIENT_HISTORY_JS = String.raw ` // The counts the "Runs to sh
|
|
|
106
106
|
fetchHistory();
|
|
107
107
|
});
|
|
108
108
|
$("histbody").addEventListener("click", (ev) => {
|
|
109
|
-
const b = ev
|
|
109
|
+
const b = clickClosest(ev, "button[data-rundetail]");
|
|
110
110
|
if (!b) return;
|
|
111
111
|
toggleHistDetail(b.dataset.role, b.dataset.run);
|
|
112
112
|
});
|
|
113
113
|
// The drill-down's revert buttons: the first click previews, the preview's own button commits.
|
|
114
114
|
$("histbody").addEventListener("click", (ev) => {
|
|
115
|
-
const b = ev
|
|
115
|
+
const b = clickClosest(ev, "button[data-hist-revert], button[data-hist-revert-go]");
|
|
116
116
|
if (!b) return;
|
|
117
117
|
if (b.dataset.histRevertGo !== undefined) runHistRevert(b.dataset.histRevertGo);
|
|
118
118
|
else previewHistRevert(b.dataset.histRevert);
|
|
119
119
|
});
|
|
120
120
|
// The drill-down's "Show patch" / "Hide patch" button.
|
|
121
121
|
$("histbody").addEventListener("click", (ev) => {
|
|
122
|
-
const b = ev
|
|
122
|
+
const b = clickClosest(ev, "button[data-hist-patch]");
|
|
123
123
|
if (!b) return;
|
|
124
124
|
toggleHistPatch(b.dataset.histPatch);
|
|
125
125
|
});
|
|
126
126
|
// The drill-down's "Show transcript" / "Hide transcript" button.
|
|
127
127
|
$("histbody").addEventListener("click", (ev) => {
|
|
128
|
-
const b = ev
|
|
128
|
+
const b = clickClosest(ev, "button[data-hist-transcript]");
|
|
129
129
|
if (!b) return;
|
|
130
130
|
toggleHistTranscript(b.dataset.histTranscript);
|
|
131
131
|
});
|
|
132
132
|
// The open transcript's "Show prompts" / "Hide prompts" button.
|
|
133
133
|
$("histbody").addEventListener("click", (ev) => {
|
|
134
|
-
const b = ev
|
|
134
|
+
const b = clickClosest(ev, "button[data-hist-transcript-prompts]");
|
|
135
135
|
if (!b) return;
|
|
136
136
|
toggleHistTranscriptPrompts(b.dataset.histTranscriptPrompts);
|
|
137
137
|
});
|
|
138
138
|
// The open transcript's search box (applied on Enter or blur: the card re-renders each change).
|
|
139
139
|
$("histbody").addEventListener("change", (ev) => {
|
|
140
|
-
const el = ev
|
|
140
|
+
const el = clickClosest(ev, "input[data-hist-transcript-grep]");
|
|
141
141
|
if (el) searchHistTranscript(el.dataset.histTranscriptGrep, el.value);
|
|
142
142
|
});
|
|
143
143
|
// The pinned newest-failure card's × closes it.
|
|
144
144
|
$("histbody").addEventListener("click", (ev) => {
|
|
145
|
-
const b = ev
|
|
145
|
+
const b = clickClosest(ev, "button[data-hist-newest-close]");
|
|
146
146
|
if (!b) return;
|
|
147
147
|
closeHistNewest();
|
|
148
148
|
});
|
|
@@ -187,11 +187,16 @@ export const GUI_CLIENT_HISTORY_JS = String.raw ` // The counts the "Runs to sh
|
|
|
187
187
|
renderHistory();
|
|
188
188
|
try {
|
|
189
189
|
const d = await getJson("/api/run?last=1&result=problems");
|
|
190
|
-
// Name the endpoint
|
|
191
|
-
// server too old (or too new) for this dashboard's
|
|
190
|
+
// Name the endpoint, the run it answered for, and the actually missing field: a 200
|
|
191
|
+
// without either text or role is a server too old (or too new) for this dashboard's
|
|
192
|
+
// client, not a lost run. text is the usual miss (the card would render nothing) and
|
|
193
|
+
// role decides the card's cache key, so name role when text is present but the role
|
|
194
|
+
// is not rather than blaming text for a field the reply did carry.
|
|
192
195
|
if (!d || typeof d.text !== "string" || typeof d.role !== "string")
|
|
193
196
|
throw new Error(
|
|
194
|
-
"/api/run answered the newest failing run without a
|
|
197
|
+
"/api/run answered the newest failing run without a " +
|
|
198
|
+
(!d || typeof d.text !== "string" ? "text" : "role") +
|
|
199
|
+
" field — the running tumwater may not match this dashboard",
|
|
195
200
|
);
|
|
196
201
|
const key = histDetailKey(d.role, d.run);
|
|
197
202
|
histDetails.set(key, histDetailOk(d));
|
|
@@ -299,7 +304,16 @@ export const GUI_CLIENT_HISTORY_JS = String.raw ` // The counts the "Runs to sh
|
|
|
299
304
|
// drift apart: every one fetches runApiUrl, then insists the reply carries its field.
|
|
300
305
|
async function fetchRunField(key, query, field) {
|
|
301
306
|
const d = await getJson(runApiUrl(key, query));
|
|
302
|
-
if (!d || typeof d[field] !== "string")
|
|
307
|
+
if (!d || typeof d[field] !== "string") {
|
|
308
|
+
// Name the endpoint, the run it answered for, and the likely cause — the same
|
|
309
|
+
// ownership fetchHistDetail and openHistNewest give their /api/run errors: a 200
|
|
310
|
+
// without the promised field is a server too old (or too new) for this dashboard's
|
|
311
|
+
// client, not a silently dropped patch or transcript.
|
|
312
|
+
const at = key.lastIndexOf("#");
|
|
313
|
+
throw new Error(
|
|
314
|
+
"/api/run answered " + key.slice(0, at) + " run " + key.slice(at + 1) + " without a " + field + " field — the running tumwater may not match this dashboard",
|
|
315
|
+
);
|
|
316
|
+
}
|
|
303
317
|
return d;
|
|
304
318
|
}
|
|
305
319
|
// Fetch or toggle one card add-on field (the patch or the transcript) at key ("role#run").
|
|
@@ -29,18 +29,6 @@ export const GUI_CLIENT_MODEL_JS = String.raw ` // view-model:start
|
|
|
29
29
|
if (p.startsWith("merging")) return { key: "landing", label: "Merging", tone: "orange", live: true, detail: after(7) }; // vocabulary-ok: internal key the row classes and filters match
|
|
30
30
|
if (p.startsWith("approved")) return { key: "vetted", label: "Approved", tone: "indigo", live: false, detail: "waiting to merge" }; // vocabulary-ok: internal key the row classes and filters match
|
|
31
31
|
if (p.startsWith("waiting to start")) return { key: "awaiting", label: "Waiting to start", tone: "gray", live: false, detail: after(16) ? "for " + after(16) : "" };
|
|
32
|
-
// A waiter parked on a full provider key (provider-concurrency part 5/5): the phase reads
|
|
33
|
-
// waiting for <key> 2m, and this chip names the provider alongside the wait, so the
|
|
34
|
-
// operator sees which cap is full — the same chip and tone as a team-total waiter.
|
|
35
|
-
if (p.startsWith("waiting for ")) {
|
|
36
|
-
const m = after(12).match(/^(\S+)(?:\s+(.*))?$/);
|
|
37
|
-
const prov = m ? m[1] : "";
|
|
38
|
-
const dur = m && m[2] ? m[2].trim() : "";
|
|
39
|
-
return {
|
|
40
|
-
key: "awaiting", label: "Waiting to start", tone: "gray", live: false,
|
|
41
|
-
detail: (prov ? "on " + prov : "") + (dur ? " for " + dur : ""),
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
32
|
if (p === "failing") return { key: "failing", label: "Failing", tone: "red", live: false, detail: "failed runs in a row" };
|
|
45
33
|
if (p === "main's check is failing") return { key: "mainred", label: "Main's check failing", tone: "red", live: false, detail: "change pinned until main goes green" };
|
|
46
34
|
if (p.startsWith("paused")) return { key: "paused", label: "Paused", tone: "amber", live: false, detail: "starts no new runs" };
|