tumwater 0.4.2 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +6 -6
- package/dist/src/agent/{run-agent.js → agent-run.js} +25 -12
- package/dist/src/agent/{run-settle.js → agent-settle.js} +48 -24
- package/dist/src/agent/{run-spawn.js → agent-spawn.js} +21 -12
- package/dist/src/agent/{run-stream.js → agent-stream.js} +4 -4
- package/dist/src/agent/backend-traits.js +2 -2
- package/dist/src/agent/claude/claude-account.js +1 -8
- package/dist/src/agent/claude/claude-backend.js +3 -3
- package/dist/src/agent/claude/claude-env.js +27 -31
- package/dist/src/agent/claude/claude-stream.js +111 -54
- package/dist/src/agent/codex/codex-account.js +9 -0
- package/dist/src/agent/codex/codex-backend.js +2 -2
- package/dist/src/agent/codex/codex-env.js +28 -15
- package/dist/src/agent/codex/codex-hook-trust.js +39 -20
- package/dist/src/agent/codex/codex-rollout.js +93 -56
- package/dist/src/agent/codex/codex-stream.js +1 -1
- package/dist/src/agent/env-scrub.js +18 -0
- package/dist/src/agent/image-input.js +68 -11
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +2 -2
- package/dist/src/agent/opencode/opencode-catalog.js +50 -27
- package/dist/src/agent/opencode/opencode-config.js +32 -14
- package/dist/src/agent/opencode/opencode-env.js +12 -13
- package/dist/src/agent/opencode/opencode-stream.js +47 -25
- package/dist/src/agent/session-ledger.js +1 -1
- package/dist/src/agent/subscription-account.js +3 -3
- package/dist/src/backlog/backlog-cli-view.js +81 -41
- package/dist/src/backlog/backlog-cli.js +57 -26
- package/dist/src/backlog/backlog-eligibility.js +58 -102
- package/dist/src/backlog/backlog-md.js +5 -5
- package/dist/src/backlog/backlog-parts.js +65 -27
- package/dist/src/backlog/backlog-release.js +12 -12
- package/dist/src/backlog/backlog-structure.js +114 -61
- package/dist/src/backlog/backlog.js +1 -1
- package/dist/src/baseline/main-baseline.js +148 -84
- package/dist/src/budget/budget-fallback-view.js +100 -243
- package/dist/src/budget/budget-wiring.js +29 -65
- package/dist/src/budget/fallback-breaker.js +2 -2
- package/dist/src/build/build-check-detect.js +43 -14
- package/dist/src/build/build-check-events.js +2 -2
- package/dist/src/build/build-check-scoped.js +40 -16
- package/dist/src/build/build-check.js +13 -5
- package/dist/src/cli/cli-command-args.js +89 -52
- package/dist/src/cli/cli-flag-specs.js +42 -22
- package/dist/src/cli/cli-init.js +34 -12
- package/dist/src/cli/cli-query-commands.js +1 -1
- package/dist/src/cli/cli-repo-gate.js +2 -2
- package/dist/src/cli/cli-start.js +91 -58
- package/dist/src/cli/completion.js +82 -46
- package/dist/src/cli/config-commands.js +82 -71
- package/dist/src/cli/help.js +4 -6
- package/dist/src/cli/log-commands.js +43 -23
- package/dist/src/cli/notify-command.js +2 -2
- package/dist/src/cli/question-commands.js +86 -60
- package/dist/src/cli.js +181 -135
- package/dist/src/collections.js +31 -16
- package/dist/src/concurrency/check-slots.js +70 -0
- package/dist/src/concurrency/lock.js +22 -0
- package/dist/src/concurrency/run-permit.js +49 -0
- package/dist/src/concurrency/semaphore.js +46 -250
- package/dist/src/config/config-editable-keys.js +4 -7
- package/dist/src/config/config-fallbacks.js +81 -101
- package/dist/src/config/config-field-checks.js +66 -37
- package/dist/src/config/config-keys.js +5 -20
- package/dist/src/config/config-live.js +4 -28
- package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
- package/dist/src/config/config-request.js +153 -102
- package/dist/src/config/config-schema.js +9 -11
- package/dist/src/config/config-selector-checks.js +1 -1
- package/dist/src/config/config-validation-agents.js +2 -2
- package/dist/src/config/config-validation-roles.js +4 -52
- package/dist/src/config/config-validation.js +4 -38
- package/dist/src/config/config-views.js +9 -34
- package/dist/src/config/config-write.js +6 -23
- package/dist/src/config/config.js +4 -5
- package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
- package/dist/src/doctor/doctor-backlog-plans.js +24 -24
- package/dist/src/doctor/doctor-backlog.js +7 -5
- package/dist/src/doctor/doctor-check-names.js +3 -6
- package/dist/src/doctor/doctor-checks.js +5 -5
- package/dist/src/doctor/doctor-claude-checks.js +44 -28
- package/dist/src/doctor/doctor-codex-checks.js +6 -6
- package/dist/src/doctor/doctor-contention-checks.js +44 -112
- package/dist/src/doctor/doctor-fallback-checks.js +2 -2
- package/dist/src/doctor/doctor-holds.js +6 -45
- package/dist/src/doctor/doctor-install.js +1 -1
- package/dist/src/doctor/doctor-launch-services.js +1 -1
- package/dist/src/doctor/doctor-model-checks.js +178 -94
- package/dist/src/doctor/doctor-notify.js +2 -2
- package/dist/src/doctor/doctor-opencode-checks.js +5 -4
- package/dist/src/doctor/doctor-orphans.js +120 -20
- package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
- package/dist/src/doctor/doctor-pi-checks.js +2 -2
- package/dist/src/doctor/doctor-principles.js +3 -3
- package/dist/src/doctor/doctor-project-checks.js +3 -3
- package/dist/src/doctor/doctor-role-notes.js +1 -1
- package/dist/src/doctor/doctor.js +8 -11
- package/dist/src/events/event-format.js +30 -54
- package/dist/src/events/event-labels.js +1 -5
- package/dist/src/events/event-legacy.js +1 -3
- package/dist/src/events/{notify.js → event-notify.js} +2 -2
- package/dist/src/events/event-read.js +1 -1
- package/dist/src/failure/failure-cluster.js +6 -6
- package/dist/src/failure/failure-data.js +8 -4
- package/dist/src/failure/failure-render.js +44 -34
- package/dist/src/failure/failure-state-change.js +4 -34
- package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
- package/dist/src/files/files.js +30 -0
- package/dist/src/files/json-object.js +8 -8
- package/dist/src/files/stat-cache.js +12 -8
- package/dist/src/gates/budget-gates.js +85 -84
- package/dist/src/gates/gate-polls.js +27 -68
- package/dist/src/gates/pause-gates.js +16 -2
- package/dist/src/gates/readiness.js +1 -1
- package/dist/src/gates/role-cap-gates.js +44 -35
- package/dist/src/gates/{startup-gate.js → startup-gates.js} +7 -7
- package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
- package/dist/src/git/git.js +75 -45
- package/dist/src/git/worktree-pool.js +3 -6
- package/dist/src/gui/gui-admin-endpoints.js +3 -3
- package/dist/src/gui/gui-backlog-commands.js +4 -4
- package/dist/src/gui/gui-backlog-endpoints.js +61 -41
- package/dist/src/gui/gui-command.js +43 -24
- package/dist/src/gui/gui-history-endpoints.js +100 -52
- package/dist/src/gui/gui-operator-commands.js +22 -23
- package/dist/src/gui/gui-prompt-commands.js +23 -23
- package/dist/src/gui/gui-report-endpoints.js +1 -1
- package/dist/src/history/history-data.js +71 -44
- package/dist/src/history/history-queued.js +49 -27
- package/dist/src/history/history.js +51 -33
- package/dist/src/inbox/prompt-commands.js +59 -42
- package/dist/src/init/init.js +88 -40
- package/dist/src/merge/merge-backlog-conflicts.js +1 -1
- package/dist/src/merge/merge-core.js +1 -1
- package/dist/src/merge/merge-drain.js +8 -11
- package/dist/src/merge/merge-pipeline.js +6 -7
- package/dist/src/merge/merge-post-review-drain.js +10 -13
- package/dist/src/merge/merge-requeue.js +1 -1
- package/dist/src/merge/merge-slot.js +0 -8
- package/dist/src/merge/merge-verify.js +8 -18
- package/dist/src/notes-cli.js +52 -42
- package/dist/src/operator/operator-commands.js +76 -44
- package/dist/src/operator/operator-intent.js +3 -3
- package/dist/src/orchestrator/orchestrator-launch.js +31 -63
- package/dist/src/orchestrator/orchestrator-poll.js +27 -32
- package/dist/src/orchestrator/orchestrator.js +8 -21
- package/dist/src/orchestrator/scheduling-admit.js +101 -68
- package/dist/src/orchestrator/scheduling-scope.js +1 -1
- package/dist/src/orchestrator/scheduling-skip.js +1 -1
- package/dist/src/orchestrator/startup-migrations.js +1 -1
- package/dist/src/paths.js +8 -7
- package/dist/src/pi/pi-args.js +5 -2
- package/dist/src/pi/pi-backend.js +2 -2
- package/dist/src/pi/pi-bin.js +3 -3
- package/dist/src/pi/pi-event-line.js +1 -1
- package/dist/src/pi/pi-models.js +43 -29
- package/dist/src/pi/pi-settings.js +43 -20
- package/dist/src/pi/pi-stream.js +32 -6
- package/dist/src/pi/pi-watchdogs.js +12 -5
- package/dist/src/pi/pi.js +2 -2
- package/dist/src/pi-extension/context-shake.js +32 -20
- package/dist/src/pi-extension/kill-guard.js +69 -0
- package/dist/src/process/process-group.js +49 -5
- package/dist/src/process/process.js +8 -8
- package/dist/src/process/run-marker.js +31 -13
- package/dist/src/process/supervisor.js +40 -28
- package/dist/src/prompt/dialect.js +1 -1
- package/dist/src/prompt/gate-notes.js +2 -2
- package/dist/src/prompt/notes-backlog.js +66 -37
- package/dist/src/prompt/principles.js +6 -4
- package/dist/src/prompt/prompt-followup.js +12 -4
- package/dist/src/prompt/prompt.js +88 -38
- package/dist/src/report/report-data.js +53 -37
- package/dist/src/report/report-fold.js +16 -9
- package/dist/src/report/report-render.js +21 -194
- package/dist/src/report/report-since-data.js +4 -3
- package/dist/src/report/report-since-render.js +173 -0
- package/dist/src/report/report-usage-fold.js +8 -8
- package/dist/src/report/report.js +46 -22
- package/dist/src/review/post-merge-review-render.js +9 -3
- package/dist/src/review/post-merge-review.js +1 -1
- package/dist/src/review/review-precheck.js +11 -1
- package/dist/src/review/review.js +40 -153
- package/dist/src/review/secret-leaks.js +26 -16
- package/dist/src/roles/role-catalog.js +11 -3
- package/dist/src/roles/role-guidance.js +16 -12
- package/dist/src/roles/role-render.js +1 -14
- package/dist/src/roles/role-view.js +9 -21
- package/dist/src/run/acceptance-greps.js +88 -45
- package/dist/src/run/run-apply.js +12 -6
- package/dist/src/run/run-detail-data.js +121 -68
- package/dist/src/run/run-detail-render.js +92 -35
- package/dist/src/run/run-detail.js +72 -41
- package/dist/src/run/run-prompt.js +63 -50
- package/dist/src/run/run-resume.js +76 -29
- package/dist/src/run/run-stage.js +135 -84
- package/dist/src/run/run-timing.js +8 -2
- package/dist/src/run/run-usage.js +31 -1
- package/dist/src/run/run-verdict.js +119 -50
- package/dist/src/run/stage-check.js +49 -32
- package/dist/src/run/stage-checks-shared.js +28 -0
- package/dist/src/run/stage-name-checks.js +111 -92
- package/dist/src/run/stage-path-checks.js +69 -42
- package/dist/src/run/stage-prose-checks.js +55 -40
- package/dist/src/runner/runner-outcome.js +6 -1
- package/dist/src/runner/runner-pi.js +3 -10
- package/dist/src/runner/runner-recovery.js +1 -1
- package/dist/src/runner/{revision.js → runner-revision.js} +1 -1
- package/dist/src/runner/runner-run.js +97 -35
- package/dist/src/runner/runner-state-load.js +12 -9
- package/dist/src/runner/runner.js +41 -25
- package/dist/src/scheduling/claims.js +0 -4
- package/dist/src/scheduling/scheduling.js +53 -66
- package/dist/src/status/status-data.js +2 -2
- package/dist/src/status/status-role-notes.js +2 -2
- package/dist/src/status/status-roles.js +9 -17
- package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
- package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
- package/dist/src/team/team-hold.js +98 -65
- package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +2 -15
- package/dist/src/team/team-polls.js +28 -126
- package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
- package/dist/src/team/team-state.js +4 -13
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +20 -15
- package/dist/src/text/text.js +3 -3
- package/dist/src/ui/alert-feeds.js +49 -73
- package/dist/src/ui/badges.js +3 -13
- package/dist/src/ui/gui/gui-client-boot.js +2 -2
- package/dist/src/ui/gui/gui-client-composer.js +1 -1
- package/dist/src/ui/gui/gui-client-drawer-config.js +7 -17
- package/dist/src/ui/gui/gui-client-drawer.js +1 -1
- package/dist/src/ui/gui/gui-client-history.js +28 -14
- package/dist/src/ui/gui/gui-client-model.js +0 -12
- package/dist/src/ui/gui/gui-client-pending.js +7 -6
- package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
- package/dist/src/ui/gui/gui-client-report.js +1 -1
- package/dist/src/ui/gui/gui-client-roles.js +0 -1
- package/dist/src/ui/gui/gui-client-settings.js +43 -84
- package/dist/src/ui/gui/gui-client-team.js +4 -4
- package/dist/src/ui/gui/gui-client.js +7 -1
- package/dist/src/ui/progress-data.js +53 -40
- package/dist/src/ui/run-progress-model.js +9 -2
- package/dist/src/ui/status-model.js +2 -24
- package/dist/src/ui/status-payload.js +3 -7
- package/dist/src/ui/status-render.js +5 -9
- package/dist/src/ui/team-alert-backlog.js +6 -6
- package/dist/src/ui/team-alert-status.js +9 -170
- package/dist/src/ui/team-alert-tail.js +19 -215
- package/dist/src/ui/team-alerts.js +7 -8
- package/dist/src/ui/transcript-run.js +27 -16
- package/dist/src/ui/tui/tui-editor-answer.js +12 -10
- package/dist/src/ui/tui/tui-editor-attach.js +4 -3
- package/dist/src/ui/tui/tui-editor-backends.js +4 -3
- package/dist/src/ui/tui/tui-editor-brief.js +6 -9
- package/dist/src/ui/tui/tui-editor-budget.js +4 -3
- package/dist/src/ui/tui/tui-editor-config.js +80 -68
- package/dist/src/ui/tui/tui-editor-entry.js +12 -20
- package/dist/src/ui/tui/tui-editor-pause.js +51 -57
- package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
- package/dist/src/ui/tui/tui-editor-queued.js +11 -14
- package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
- package/dist/src/ui/tui/tui-editor-release.js +4 -3
- package/dist/src/ui/tui/tui-editor-retire.js +7 -7
- package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
- package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
- package/dist/src/ui/tui/tui-editor-search.js +4 -3
- package/dist/src/ui/tui/tui-editor-session.js +17 -4
- package/dist/src/ui/tui/tui-editors.js +1 -7
- package/dist/src/ui/tui/tui-frame.js +182 -134
- package/dist/src/ui/tui/tui-guard.js +20 -0
- package/dist/src/ui/tui/tui-input.js +159 -92
- package/dist/src/ui/tui/tui-keymap.js +51 -29
- package/dist/src/ui/tui/tui-keys.js +1 -1
- package/dist/src/ui/tui/tui-pane-activity.js +3 -3
- package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +252 -228
- package/dist/src/ui/tui/tui-pane-history.js +61 -70
- package/dist/src/ui/tui/tui-pane-pending.js +284 -247
- package/dist/src/ui/tui/tui-pane-queued.js +25 -27
- package/dist/src/ui/tui/tui-pane-report.js +2 -1
- package/dist/src/ui/tui/tui-pane-session.js +11 -7
- package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
- package/dist/src/ui/tui/tui-pane.js +14 -11
- package/dist/src/ui/tui/tui-panes.js +29 -32
- package/dist/src/ui/tui/tui.js +3 -4
- package/dist/src/verdict/fix-claim.js +76 -54
- package/dist/src/verdict/refusal.js +1 -1
- package/package.json +5 -5
- package/dist/src/concurrency/provider-lease.js +0 -105
- package/dist/src/runner/model-fallback.js +0 -76
- package/dist/src/runner/runner-fallback.js +0 -66
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
- package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
- /package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +0 -0
- /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
- /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
- /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
- /package/dist/src/operator/{revert.js → operator-revert.js} +0 -0
- /package/dist/src/runner/{leftover.js → runner-leftover.js} +0 -0
package/dist/src/ui/badges.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { sortEntriesByKey } from "../collections.js";
|
|
2
1
|
import { utilizationPct, windowLabel } from "../agent/subscription-account.js";
|
|
3
2
|
import { MODEL_TIERS } from "../config/config-schema.js";
|
|
4
3
|
import { budgetGate, budgetReached, fallbackTierEntries } from "../budget/budget.js";
|
|
@@ -383,21 +382,12 @@ export function diskBadge(disk) {
|
|
|
383
382
|
? ` · disk ${free}`
|
|
384
383
|
: "";
|
|
385
384
|
}
|
|
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. */
|
|
385
|
+
/** The permit pool's header fragment (plans/provider-concurrency.md §8): `slots 5/8` — the
|
|
386
|
+
* team total's in-use/cap. Empty when no snapshot was published. */
|
|
392
387
|
export function permitsBadge(permits) {
|
|
393
388
|
if (!permits)
|
|
394
389
|
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}`;
|
|
390
|
+
return ` · running ${permits.inUse} of ${permits.limit}`;
|
|
401
391
|
}
|
|
402
392
|
/** The check-permit pool's reading, ` · checks 1/2` plus `, 3 waiting` while checks queue. Empty
|
|
403
393
|
* 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
|
|
@@ -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,9 +90,6 @@ 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")
|
|
@@ -115,11 +110,6 @@ export const GUI_CLIENT_DRAWER_CONFIG_JS = String.raw `
|
|
|
115
110
|
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
111
|
rows.push("<div><dt>Review</dt><dd title='" + esc(revBody) + "'>" + esc(revBody) + revControl + "</dd></div>");
|
|
117
112
|
}
|
|
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
113
|
if (view.fallback && typeof view.fallback === "object" && view.fallback.model !== undefined) {
|
|
124
114
|
rows.push(kvRow("Budget fallback", fallbackLabel(view.fallback, "") + " (" + (view.fallbackFree ? "free" : "priced") + ")"));
|
|
125
115
|
}
|
|
@@ -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>";
|
|
@@ -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" };
|
|
@@ -58,7 +58,7 @@ export const GUI_CLIENT_PENDING_JS = String.raw ` let pendingTeam = null;
|
|
|
58
58
|
if (!team || !Array.isArray(team.roles) || team.roles.length === 0)
|
|
59
59
|
return "<div class='empty'><strong>No roles yet</strong>Roles appear here once the team is configured.</div>";
|
|
60
60
|
if (team.roles.every((r) => r.state === "no-base"))
|
|
61
|
-
return "<div class='empty'><strong>
|
|
61
|
+
return "<div class='empty'><strong>" + mainBranchMissingPhrase(team.mainBranch) + "</strong>There is no base to compare unmerged work against.</div>";
|
|
62
62
|
const rows = team.roles.map((r) =>
|
|
63
63
|
"<tr class='clickable' data-open='" + esc(r.role) + "'>" +
|
|
64
64
|
"<td class='mono'>" + esc(roleLabel(r.role)) + "</td>" +
|
|
@@ -202,7 +202,7 @@ export const GUI_CLIENT_PENDING_JS = String.raw ` let pendingTeam = null;
|
|
|
202
202
|
let body;
|
|
203
203
|
let head = "<div class='card-head'><h2>Overlapping files</h2></div>";
|
|
204
204
|
if (ok && view.state === "no-base") {
|
|
205
|
-
body = "<div class='empty'>
|
|
205
|
+
body = "<div class='empty'>" + mainBranchMissingPhrase(view.mainBranch) + "</div>";
|
|
206
206
|
} else if (!ok || !Array.isArray(view.overlaps) || view.overlaps.length === 0) {
|
|
207
207
|
body = "<div class='empty'>No file is touched by two pending changes</div>";
|
|
208
208
|
} else {
|
|
@@ -267,6 +267,7 @@ export const GUI_CLIENT_PENDING_JS = String.raw ` let pendingTeam = null;
|
|
|
267
267
|
case "fixed":
|
|
268
268
|
return "[fixed — the repair " + shortSha(e.fixed) + " already merged on main]";
|
|
269
269
|
case "rejected":
|
|
270
|
+
if (!e.rejected || e.rejected.length === 0) return "[rejected]";
|
|
270
271
|
return "[rejected — " + e.rejected.map((r) => squashText(r, 60)).join("; ") + "]";
|
|
271
272
|
case "failing":
|
|
272
273
|
if (e && typeof e.lastFailure === "string" && e.lastFailure.length > 0) {
|
|
@@ -388,7 +389,7 @@ export const GUI_CLIENT_PENDING_JS = String.raw ` let pendingTeam = null;
|
|
|
388
389
|
// A Merge queue row's Move to top: a single click posts /api/queue-top by sha, moving
|
|
389
390
|
// that entry to the head of the queue and refetching (the move is reversible and
|
|
390
391
|
// non-destructive, so it needs no two-step arm).
|
|
391
|
-
const t = e
|
|
392
|
+
const t = clickClosest(e, "[data-top]");
|
|
392
393
|
if (t) {
|
|
393
394
|
const sha = t.getAttribute("data-top");
|
|
394
395
|
if (sha) {
|
|
@@ -397,7 +398,7 @@ export const GUI_CLIENT_PENDING_JS = String.raw ` let pendingTeam = null;
|
|
|
397
398
|
}
|
|
398
399
|
return;
|
|
399
400
|
}
|
|
400
|
-
const b = e
|
|
401
|
+
const b = clickClosest(e, "[data-cancel]");
|
|
401
402
|
if (!b) return;
|
|
402
403
|
const sha = b.getAttribute("data-cancel");
|
|
403
404
|
if (!sha) return;
|
|
@@ -415,7 +416,7 @@ export const GUI_CLIENT_PENDING_JS = String.raw ` let pendingTeam = null;
|
|
|
415
416
|
// (repainting that button as Confirm accept), a second on the armed commit within 5 s posts
|
|
416
417
|
// /api/post-review-accept then clears the arm and refetches so the removed row disappears.
|
|
417
418
|
$("pendingreviews").addEventListener("click", async (e) => {
|
|
418
|
-
const b = e
|
|
419
|
+
const b = clickClosest(e, "[data-accept]");
|
|
419
420
|
if (!b) return;
|
|
420
421
|
const commit = b.getAttribute("data-accept");
|
|
421
422
|
if (!commit) return;
|
|
@@ -445,7 +446,7 @@ export const GUI_CLIENT_PENDING_JS = String.raw ` let pendingTeam = null;
|
|
|
445
446
|
}
|
|
446
447
|
});
|
|
447
448
|
$("pendingoverlaps").addEventListener("click", (e) => {
|
|
448
|
-
const b = e
|
|
449
|
+
const b = clickClosest(e, "[data-overlap]");
|
|
449
450
|
if (b) togglePendingOverlap(Number(b.dataset.overlap));
|
|
450
451
|
});
|
|
451
452
|
$("pendingoverlaps").addEventListener("change", (e) => {
|
|
@@ -48,7 +48,7 @@ export const GUI_CLIENT_REPORT_CHART_JS = String.raw `
|
|
|
48
48
|
// Object.prototype key whose inherited value would otherwise seed the counter and satisfy
|
|
49
49
|
// the membership test (the browser twin of addTo's fix; BUGS.md 2026-10-10).
|
|
50
50
|
function reportRoleOrder(data) {
|
|
51
|
-
return tallyAcross(data.series, ["runsByRole"], ["costByRole", "notionalByRole", "rejectionsByRole", "revisionsByRole", "refusalsByRole", "
|
|
51
|
+
return tallyAcross(data.series, ["runsByRole"], ["costByRole", "notionalByRole", "rejectionsByRole", "revisionsByRole", "refusalsByRole", "imageReroutesByRole", "failedRunsByRole", "mergeFailedByRole", "commitsByRole", "mergedByRole"]);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
// X-axis labels: MM-DD, thinned to at most seven, counted back from the newest day so today
|
|
@@ -371,10 +371,10 @@ export const GUI_CLIENT_REPORT_CHART_JS = String.raw `
|
|
|
371
371
|
function chartRefusalsByRole(data) {
|
|
372
372
|
return roleStackChart(data, "refusalsByRole", (n) => plural(n, "refusal"), countAxis, true);
|
|
373
373
|
}
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
function
|
|
377
|
-
return roleStackChart(data, "
|
|
374
|
+
// Image reroutes per role, drawn like the other role stacks: one segment per (day, role)
|
|
375
|
+
// with a reroute.
|
|
376
|
+
function chartImageReroutesByRole(data) {
|
|
377
|
+
return roleStackChart(data, "imageReroutesByRole", (n) => plural(n, "image reroute"), countAxis, true);
|
|
378
378
|
}
|
|
379
379
|
// Runs that ended in an error, per role, drawn the same way.
|
|
380
380
|
function chartFailedRunsByRole(data) {
|
|
@@ -335,7 +335,7 @@ export const GUI_CLIENT_REPORT_JS = String.raw `// report-tip:start
|
|
|
335
335
|
(seriesHasAny(d, "postMergeRejectionsByRole") ? block("Post-merge rejections by role", chartPostMergeRejectionsByRole(d)) : "") +
|
|
336
336
|
(seriesHasAny(d, "revisionsByRole") ? block("Revisions by role", chartRevisionsByRole(d)) : "") +
|
|
337
337
|
(seriesHasAny(d, "refusalsByRole") ? block("Refusals by role", chartRefusalsByRole(d)) : "") +
|
|
338
|
-
(seriesHasAny(d, "
|
|
338
|
+
(seriesHasAny(d, "imageReroutesByRole") ? block("Image reroutes by role", chartImageReroutesByRole(d)) : "") +
|
|
339
339
|
(seriesHasAny(d, "failedRunsByRole") ? block("Failed runs by role", chartFailedRunsByRole(d)) : "") +
|
|
340
340
|
(seriesHasAny(d, "mergedByRole") ? block("Merged by role", chartMergedByRole(d)) : "") +
|
|
341
341
|
(!d.role && seriesHasAny(d, "runsByModel") ? block("Runs by model", chartRunsByModel(d)) : "") +
|
|
@@ -163,7 +163,6 @@ export const GUI_CLIENT_ROLES_JS = String.raw `
|
|
|
163
163
|
const name = "<div class='role-name'><button type='button' class='linkish role' data-open='" + esc(l.role) + "' title='Open " + esc(roleLabel(l.role)) + "'>" + esc(roleLabel(l.role)) + "</button>" +
|
|
164
164
|
(l.custom ? "<span class='tag' title='A custom role from tumwater.json'>custom</span>" : "") +
|
|
165
165
|
(l.modelTier ? "<span class='tag' title='The model tier this role resolves at'>" + esc(l.modelTier) + "</span>" : "") +
|
|
166
|
-
(l.fallback ? "<span class='tag t-amber' title='Running on the model fallback while its primary keeps failing provider-class errors'>fallback</span>" : "") +
|
|
167
166
|
(queued ? "<span class='tag t-indigo' title='" + esc(plural(queued, "message") + " in this role's inbox") + "'>" + icon("chat") + queued + "</span>" : "") +
|
|
168
167
|
"</div><div class='sub'>" + esc(plural(l.commits, "commit") + " · " + plural(l.runs, "run") + " · merged " + (l.lastMergedAt ? fmtAgo(l.lastMergedAt) : "never")) + (l.model ? " · " + esc(l.model) : "") +
|
|
169
168
|
(l.modelFromPi ? " <span class='tag' title='pi's own default model, from its settings.json: tumwater.json names no model'>(pi default)</span>" : "") + "</div>";
|
|
@@ -34,10 +34,8 @@ export const GUI_CLIENT_SETTINGS_JS = String.raw ` // settings-view:start
|
|
|
34
34
|
["fallback", "Fallback (free at the spend cap)"],
|
|
35
35
|
["maxDailyCostUsd", "Daily spend cap (USD)"],
|
|
36
36
|
["maxConcurrent", "Max concurrent runs"],
|
|
37
|
-
["maxConcurrentPerProvider", "Provider concurrency caps (JSON)"],
|
|
38
37
|
["maxConcurrentChecks", "Max concurrent checks"],
|
|
39
38
|
["worktreeSlots", "Worktree slots"],
|
|
40
|
-
["claudeWindowReserve", "Claude window reserve (backends.claude.windowReserve)"],
|
|
41
39
|
["quietHours", "Quiet hours"],
|
|
42
40
|
["notify", "Notify hook"],
|
|
43
41
|
["backends", "Agent backends (JSON)"],
|
|
@@ -162,13 +160,7 @@ export const GUI_CLIENT_SETTINGS_JS = String.raw ` // settings-view:start
|
|
|
162
160
|
// The row's Enable button: re-enable the role through POST /api/role-enable (writing
|
|
163
161
|
// roles.<base>.enabled), flash the server's message, and refetch so the card drops the row.
|
|
164
162
|
async function enableRole(role) {
|
|
165
|
-
|
|
166
|
-
const d = await postJson("/api/role-enable", { role: role });
|
|
167
|
-
showFlash(d.message);
|
|
168
|
-
} catch (err) {
|
|
169
|
-
showFlash("error: " + clientErrorMessage(err));
|
|
170
|
-
}
|
|
171
|
-
await fetchRetirable();
|
|
163
|
+
await postSetting("/api/role-enable", { role: role }, fetchRetirable, (d) => d.message);
|
|
172
164
|
}
|
|
173
165
|
// First click arms the role's button; a second within the confirm window posts the retire.
|
|
174
166
|
async function confirmRetire(role) {
|
|
@@ -177,13 +169,42 @@ export const GUI_CLIENT_SETTINGS_JS = String.raw ` // settings-view:start
|
|
|
177
169
|
return;
|
|
178
170
|
}
|
|
179
171
|
retireSlot.disarm();
|
|
172
|
+
await postSetting("/api/retire", { role: role }, fetchRetirable, (d) => d.message);
|
|
173
|
+
}
|
|
174
|
+
// POST one Settings action and flash its outcome, then refetch the card it changed — the
|
|
175
|
+
// try/catch tail every Enable/Retire/Health-button handler shares, so the thrown-error
|
|
176
|
+
// coercion and the success/error wording live in one place. news derives the success line
|
|
177
|
+
// from the endpoint's answer: the default is the team-wide "ok: / error: " prefix, Enable
|
|
178
|
+
// and Retire pass the server's message through verbatim, and Retire-leftovers overrides the
|
|
179
|
+
// none-left fallback. Deliberately not named postAction (gui-client.ts's core helper, which
|
|
180
|
+
// closes menus, refreshes the standing poll, and does not refetch on failure — this one
|
|
181
|
+
// refetches the card its action changed even when the post failed).
|
|
182
|
+
async function postSetting(path, body, refetch, news) {
|
|
180
183
|
try {
|
|
181
|
-
const d = await postJson(
|
|
182
|
-
showFlash(d.message);
|
|
184
|
+
const d = await postJson(path, body);
|
|
185
|
+
showFlash(news ? news(d) : (d.ok ? "" : "error: ") + d.message);
|
|
183
186
|
} catch (err) {
|
|
184
187
|
showFlash("error: " + clientErrorMessage(err));
|
|
185
188
|
}
|
|
186
|
-
await
|
|
189
|
+
await refetch();
|
|
190
|
+
}
|
|
191
|
+
// POST one two-click Health-card action after its slot was armed by a prior click: the first
|
|
192
|
+
// click only arms the slot (the handler's button repaints to "Confirm …") and returns; a
|
|
193
|
+
// second click within the confirm window disarms, repaints, flashes the action line, posts,
|
|
194
|
+
// and re-runs the card's checks. The Health card's four repair buttons (Kill orphans, Drop
|
|
195
|
+
// broken pins, Retire leftovers, Archive backlog) share this one confirm shape: only the slot
|
|
196
|
+
// key, the paint fn, the flash line, the endpoint, and an optional news override (passed
|
|
197
|
+
// through to postSetting, e.g. Retire-leftovers' outcome-line override) differ, so they fold
|
|
198
|
+
// into this one helper instead of four copies of the arm/disarm/paint/flash/postSetting tail.
|
|
199
|
+
async function postConfirmedHealthAction(slot, key, paint, flash, path, news) {
|
|
200
|
+
if (!slot.armed(key)) {
|
|
201
|
+
slot.arm(key);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
slot.disarm();
|
|
205
|
+
paint();
|
|
206
|
+
showFlash(flash);
|
|
207
|
+
await postSetting(path, {}, fetchDoctor, news);
|
|
187
208
|
}
|
|
188
209
|
// The Health card's Kill orphans button: a two-click confirm, then POST /api/kill-orphans runs
|
|
189
210
|
// the same kill as "tumwater doctor --kill-orphans" and the card re-runs its checks.
|
|
@@ -193,20 +214,7 @@ export const GUI_CLIENT_SETTINGS_JS = String.raw ` // settings-view:start
|
|
|
193
214
|
}
|
|
194
215
|
const killSlot = confirmSlot(paintKillButton);
|
|
195
216
|
async function killOrphansClick() {
|
|
196
|
-
|
|
197
|
-
killSlot.arm("orphans");
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
killSlot.disarm();
|
|
201
|
-
paintKillButton();
|
|
202
|
-
showFlash("Killing orphaned processes…");
|
|
203
|
-
try {
|
|
204
|
-
const d = await postJson("/api/kill-orphans", {});
|
|
205
|
-
showFlash((d.ok ? "" : "error: ") + d.message);
|
|
206
|
-
} catch (err) {
|
|
207
|
-
showFlash("error: " + clientErrorMessage(err));
|
|
208
|
-
}
|
|
209
|
-
await fetchDoctor();
|
|
217
|
+
await postConfirmedHealthAction(killSlot, "orphans", paintKillButton, "Killing orphaned processes…", "/api/kill-orphans");
|
|
210
218
|
}
|
|
211
219
|
// The Health card's Drop broken pins button: a two-click confirm, then POST
|
|
212
220
|
// /api/doctor-drop-broken-pins runs the same drop as "tumwater doctor --drop-broken-pins" and
|
|
@@ -217,20 +225,7 @@ export const GUI_CLIENT_SETTINGS_JS = String.raw ` // settings-view:start
|
|
|
217
225
|
}
|
|
218
226
|
const dropSlot = confirmSlot(paintDropButton);
|
|
219
227
|
async function dropBrokenPinsClick() {
|
|
220
|
-
|
|
221
|
-
dropSlot.arm("broken-pins");
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
dropSlot.disarm();
|
|
225
|
-
paintDropButton();
|
|
226
|
-
showFlash("Dropping broken pins…");
|
|
227
|
-
try {
|
|
228
|
-
const d = await postJson("/api/doctor-drop-broken-pins", {});
|
|
229
|
-
showFlash((d.ok ? "" : "error: ") + d.message);
|
|
230
|
-
} catch (err) {
|
|
231
|
-
showFlash("error: " + clientErrorMessage(err));
|
|
232
|
-
}
|
|
233
|
-
await fetchDoctor();
|
|
228
|
+
await postConfirmedHealthAction(dropSlot, "broken-pins", paintDropButton, "Dropping broken pins…", "/api/doctor-drop-broken-pins");
|
|
234
229
|
}
|
|
235
230
|
// The Health card's Retire leftovers button: a two-click confirm, then POST
|
|
236
231
|
// /api/doctor-retire-leftovers runs the same reap as "tumwater doctor --retire-leftovers" and
|
|
@@ -242,22 +237,11 @@ export const GUI_CLIENT_SETTINGS_JS = String.raw ` // settings-view:start
|
|
|
242
237
|
}
|
|
243
238
|
const retireLeftoversSlot = confirmSlot(paintRetireLeftoversButton);
|
|
244
239
|
async function retireLeftoversClick() {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
paintRetireLeftoversButton();
|
|
251
|
-
showFlash("Retiring every disabled role's leftovers…");
|
|
252
|
-
try {
|
|
253
|
-
const d = await postJson("/api/doctor-retire-leftovers", {});
|
|
254
|
-
// The shared leftoverRetireLines join is empty when nothing is retirable, so the
|
|
255
|
-
// button falls back to the same none-left sentence the TUI Health key flashes.
|
|
256
|
-
showFlash((d.ok ? "" : "error: ") + (d.message || "no leftover roles to retire"));
|
|
257
|
-
} catch (err) {
|
|
258
|
-
showFlash("error: " + clientErrorMessage(err));
|
|
259
|
-
}
|
|
260
|
-
await fetchDoctor();
|
|
240
|
+
// The news override's none-left fallback matches the none-left sentence the TUI Health key
|
|
241
|
+
// flashes when nothing is retirable: the shared leftoverRetireLines join is empty when
|
|
242
|
+
// nothing is retirable, so the button falls back to that same sentence.
|
|
243
|
+
await postConfirmedHealthAction(retireLeftoversSlot, "retire-leftovers", paintRetireLeftoversButton, "Retiring every disabled role's leftovers…", "/api/doctor-retire-leftovers",
|
|
244
|
+
(d) => (d.ok ? "" : "error: ") + (d.message || "no leftover roles to retire"));
|
|
261
245
|
}
|
|
262
246
|
// The Health card's Archive backlog button: a two-click confirm, then POST
|
|
263
247
|
// /api/doctor-archive-backlog runs the same archive as "tumwater archive" (holding the merge
|
|
@@ -270,20 +254,7 @@ export const GUI_CLIENT_SETTINGS_JS = String.raw ` // settings-view:start
|
|
|
270
254
|
}
|
|
271
255
|
const archiveBacklogSlot = confirmSlot(paintArchiveBacklogButton);
|
|
272
256
|
async function archiveBacklogClick() {
|
|
273
|
-
|
|
274
|
-
archiveBacklogSlot.arm("archive-backlog");
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
archiveBacklogSlot.disarm();
|
|
278
|
-
paintArchiveBacklogButton();
|
|
279
|
-
showFlash("Archiving the finished backlog entries…");
|
|
280
|
-
try {
|
|
281
|
-
const d = await postJson("/api/doctor-archive-backlog", {});
|
|
282
|
-
showFlash((d.ok ? "" : "error: ") + d.message);
|
|
283
|
-
} catch (err) {
|
|
284
|
-
showFlash("error: " + clientErrorMessage(err));
|
|
285
|
-
}
|
|
286
|
-
await fetchDoctor();
|
|
257
|
+
await postConfirmedHealthAction(archiveBacklogSlot, "archive-backlog", paintArchiveBacklogButton, "Archiving the finished backlog entries…", "/api/doctor-archive-backlog");
|
|
287
258
|
}
|
|
288
259
|
// The Health card's Commit backlog button: a single click, because the commit is the doctor's
|
|
289
260
|
// own recommended repair and is reversible (unlike the two-click Kill orphans arm), POSTing
|
|
@@ -292,13 +263,7 @@ export const GUI_CLIENT_SETTINGS_JS = String.raw ` // settings-view:start
|
|
|
292
263
|
// clean), then re-running the checks so the uncommitted-backlog row repaints.
|
|
293
264
|
async function commitBacklogClick() {
|
|
294
265
|
showFlash("Committing the uncommitted backlog/brief files…");
|
|
295
|
-
|
|
296
|
-
const d = await postJson("/api/doctor-commit-backlog", {});
|
|
297
|
-
showFlash((d.ok ? "" : "error: ") + d.message);
|
|
298
|
-
} catch (err) {
|
|
299
|
-
showFlash("error: " + clientErrorMessage(err));
|
|
300
|
-
}
|
|
301
|
-
await fetchDoctor();
|
|
266
|
+
await postSetting("/api/doctor-commit-backlog", {}, fetchDoctor);
|
|
302
267
|
}
|
|
303
268
|
// The Health card's Re-sync install button: a single click, because the re-sync is the doctor's
|
|
304
269
|
// own recommended repair and is a no-op when the install already matches the lockfile, POSTing
|
|
@@ -307,13 +272,7 @@ export const GUI_CLIENT_SETTINGS_JS = String.raw ` // settings-view:start
|
|
|
307
272
|
// the checks so the root-install row and the install amber alert clear once the drift is gone.
|
|
308
273
|
async function resyncInstallClick() {
|
|
309
274
|
showFlash("Re-syncing the root install…");
|
|
310
|
-
|
|
311
|
-
const d = await postJson("/api/doctor-resync-install", {});
|
|
312
|
-
showFlash((d.ok ? "" : "error: ") + d.message);
|
|
313
|
-
} catch (err) {
|
|
314
|
-
showFlash("error: " + clientErrorMessage(err));
|
|
315
|
-
}
|
|
316
|
-
await fetchDoctor();
|
|
275
|
+
await postSetting("/api/doctor-resync-install", {}, fetchDoctor);
|
|
317
276
|
}
|
|
318
277
|
async function fetchSettings() {
|
|
319
278
|
buildSettingsView();
|
|
@@ -95,13 +95,13 @@ export const GUI_CLIENT_TEAM_JS = String.raw ` // ---- sidebar: project, team s
|
|
|
95
95
|
: "");
|
|
96
96
|
}
|
|
97
97
|
// The permit pool (plans/provider-concurrency.md §8): the payload's preformatted
|
|
98
|
-
// permitsBadge — running 5 of 8
|
|
99
|
-
//
|
|
100
|
-
//
|
|
98
|
+
// permitsBadge — running 5 of 8 — upcased for the row's label style, the same reading
|
|
99
|
+
// the TUI/status header carries, so the two surfaces cannot drift. Present for any
|
|
100
|
+
// team with a published permit snapshot.
|
|
101
101
|
if (d.permitsBadge) {
|
|
102
102
|
const tok = String(d.permitsBadge).replace(/^ · /, "");
|
|
103
103
|
rows += row("blue", icon("gauge"), esc(tok),
|
|
104
|
-
"Roles running at once out of the team limit
|
|
104
|
+
"Roles running at once out of the team limit (maxConcurrent); a role waits to start when the team is at its limit.");
|
|
105
105
|
}
|
|
106
106
|
// The check-permit pool: the payload's checksBadge, present only while a check runs or waits.
|
|
107
107
|
if (d.checksBadge) {
|
|
@@ -151,6 +151,12 @@ const FORMAT_JS = String.raw ` // format:start
|
|
|
151
151
|
// section had the byte-identical map; a missing or non-array list paints nothing.
|
|
152
152
|
const commitRowsHtml = (commits) =>
|
|
153
153
|
(Array.isArray(commits) ? commits : []).map((c) => "<div class='mono clamp1'>" + esc(c.sha) + " " + esc(c.subject || "—") + "</div>").join("");
|
|
154
|
+
// The empty state for a missing main branch, shared by the Pending roster, its
|
|
155
|
+
// overlapping-files card, and the role drawer's Pending change section so the esced branch
|
|
156
|
+
// name and the wording cannot drift between them — change-render.ts's mainMissingPhrase,
|
|
157
|
+
// whose terminal spelling is lowercase/unescaped. The browser client cannot import that TS
|
|
158
|
+
// module, so this is its own copy, capitalized and esced for the HTML sentence it leads.
|
|
159
|
+
const mainBranchMissingPhrase = (mainBranch) => "Main branch " + esc(mainBranch) + " does not exist";
|
|
154
160
|
// Title-case a phrase's first character and keep the rest — backend-traits.ts's
|
|
155
161
|
// backendDisplayName rule, the one home of the upcasing gui-client-model.ts's result labels
|
|
156
162
|
// and gui-client-history.ts's window options each inlined, so a label cannot drift between
|
|
@@ -226,7 +232,7 @@ const FORMAT_JS = String.raw ` // format:start
|
|
|
226
232
|
// phase rather than throwing: the browser copy is untyped, and its sibling phaseInfo/
|
|
227
233
|
// roleRank already coerce the same way, so one bad row must not blank the whole table.
|
|
228
234
|
const phase = String(l.phase || "");
|
|
229
|
-
const active = isActivePhase(phase) || phase.startsWith("waiting to start") || phase.startsWith("
|
|
235
|
+
const active = isActivePhase(phase) || phase.startsWith("waiting to start") || phase.startsWith("director working");
|
|
230
236
|
if (!teamRunning || active) return "-";
|
|
231
237
|
const sec = Math.round((l.nextRunAt - Date.now()) / 1000);
|
|
232
238
|
const suffix = l.yieldMultiplier > 1 ? " ×" + l.yieldMultiplier : "";
|