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
|
@@ -65,7 +65,7 @@ export const GUI_CLIENT_OPERATOR_JS = String.raw ` // shared-operator:start
|
|
|
65
65
|
// The subscription usage window (plans/agent-backends.md §8, PLANS.md part 6d/7): the
|
|
66
66
|
// sidebar's spend card renders it beside the daily spend, or in its place for a
|
|
67
67
|
// subscription-only team. The label/status/clock rules mirror badges.ts's
|
|
68
|
-
//
|
|
68
|
+
// window badge — the preformatted header fragment the TUI uses cannot be reused here
|
|
69
69
|
// because the card lists EVERY window of EVERY block, not just the most constrained one.
|
|
70
70
|
function windowLabel(type) {
|
|
71
71
|
const m = /^(\w+)_(hour|day|week)$/.exec(String(type));
|
|
@@ -87,9 +87,9 @@ export const GUI_CLIENT_OPERATOR_JS = String.raw ` // shared-operator:start
|
|
|
87
87
|
if (hours < 24) return "resets in " + hours + "h " + (minutes % 60) + "m";
|
|
88
88
|
return "resets in " + Math.floor(hours / 24) + "d " + (hours % 24) + "h";
|
|
89
89
|
}
|
|
90
|
-
// A window's consumed fraction as a whole-number percent, the same shape as node-side
|
|
91
|
-
//
|
|
92
|
-
// the meter width share this one home instead of each rounding w.utilization by hand.
|
|
90
|
+
// A window's consumed fraction as a whole-number percent, the same shape as the node-side
|
|
91
|
+
// utilization percent — the browser script keeps its inline copy, so the text percentage
|
|
92
|
+
// and the meter width share this one home instead of each rounding w.utilization by hand.
|
|
93
93
|
function windowPct(w) {
|
|
94
94
|
return Math.round(w.utilization * 100);
|
|
95
95
|
}
|
|
@@ -171,7 +171,9 @@ export const GUI_CLIENT_OPERATOR_JS = String.raw ` // shared-operator:start
|
|
|
171
171
|
function renderBudgetBadge(d) {
|
|
172
172
|
if (budgetEditing) return; // the open editor owns the slot until save or cancel
|
|
173
173
|
const b = d && d.budget;
|
|
174
|
-
|
|
174
|
+
// The subscription-window block and its only flag no longer ride the status payload
|
|
175
|
+
// (Subtract 5x/6); the window card is dormant, so the budget card renders chain only.
|
|
176
|
+
paintPanel("budgetwrap", b ? budgetCardHtml(b, undefined, undefined, d && d.chain) : "");
|
|
175
177
|
}
|
|
176
178
|
function openBudgetEditor() {
|
|
177
179
|
const b = lastStatus && lastStatus.budget;
|
|
@@ -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();
|
|
@@ -48,8 +48,14 @@ export const GUI_CLIENT_TEAM_JS = String.raw ` // ---- sidebar: project, team s
|
|
|
48
48
|
// markup alertParts() emits for the build alert; a fresh build's icon stays inert
|
|
49
49
|
// decoration.
|
|
50
50
|
const lead = stale ? restartButton(icon("refresh")) : icon("refresh");
|
|
51
|
-
|
|
52
|
-
|
|
51
|
+
// Main's tip moved the code on but no newer commit has passed main's check, so redeploy has
|
|
52
|
+
// nothing to move to (BuildStatus.waitingForCheck): amber, and the Main row below says why.
|
|
53
|
+
// No restart button — a forced restart would land on this same build.
|
|
54
|
+
const waiting = !stale && d.build.waitingForCheck;
|
|
55
|
+
rows += row(stale ? (d.build.restartBlocked ? "amber" : "blue") : waiting ? "amber" : "", lead,
|
|
56
|
+
"Version <span class='mono'>" + esc(shortSha(d.build.sha)) + "</span>" +
|
|
57
|
+
(stale ? " · " + esc(plural(d.build.aheadCommits || 0, "commit")) + " behind"
|
|
58
|
+
: waiting ? " · update waits for main's check" : ""),
|
|
53
59
|
localizeInstants(("Running version" + (d.buildBadge || "").replace(/^, build/, "")).trim()));
|
|
54
60
|
}
|
|
55
61
|
if (d.mainCheck) {
|
|
@@ -95,13 +101,13 @@ export const GUI_CLIENT_TEAM_JS = String.raw ` // ---- sidebar: project, team s
|
|
|
95
101
|
: "");
|
|
96
102
|
}
|
|
97
103
|
// The permit pool (plans/provider-concurrency.md §8): the payload's preformatted
|
|
98
|
-
// permitsBadge — running 5 of 8
|
|
99
|
-
//
|
|
100
|
-
//
|
|
104
|
+
// permitsBadge — running 5 of 8 — upcased for the row's label style, the same reading
|
|
105
|
+
// the TUI/status header carries, so the two surfaces cannot drift. Present for any
|
|
106
|
+
// team with a published permit snapshot.
|
|
101
107
|
if (d.permitsBadge) {
|
|
102
108
|
const tok = String(d.permitsBadge).replace(/^ · /, "");
|
|
103
109
|
rows += row("blue", icon("gauge"), esc(tok),
|
|
104
|
-
"Roles running at once out of the team limit
|
|
110
|
+
"Roles running at once out of the team limit (maxConcurrent); a role waits to start when the team is at its limit.");
|
|
105
111
|
}
|
|
106
112
|
// The check-permit pool: the payload's checksBadge, present only while a check runs or waits.
|
|
107
113
|
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 : "";
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { applyToolExecutionEvent, parsePiEventLine, toolCallCommand, } from "../pi/pi-event-line.js";
|
|
2
|
+
import { applyToolExecutionEvent, foldPendingReply, messageUpdateHasContent, parsePiEventLine, piEventType, toolCallCommand, } from "../pi/pi-event-line.js";
|
|
3
3
|
import { bufferedCommandStallMs, callBuffersOutput } from "../pi/command-shape.js";
|
|
4
4
|
import { describeToolCall } from "../text/phrases.js";
|
|
5
5
|
import { squash } from "../text/text.js";
|
|
6
6
|
import { defaultConfig, liveConfig } from "../config/config.js";
|
|
7
|
-
import { nonNegativeNumber } from "../files/json-object.js";
|
|
7
|
+
import { nonNegativeNumber, parseJsonObject } from "../files/json-object.js";
|
|
8
8
|
import { worktreesDir } from "../paths.js";
|
|
9
9
|
import { statRoleLog, readCompleteLines, withTail } from "../files/tail.js";
|
|
10
10
|
/** How much log tail a first observation scans initially (or after rotation); findSeedOffset
|
|
@@ -78,6 +78,7 @@ function freshProgress(quietMs, agent) {
|
|
|
78
78
|
// stalledTool is deliberately NOT part of fresh: it is derived from wall-clock time on
|
|
79
79
|
// every read, never folded from lines.
|
|
80
80
|
openToolCalls: undefined,
|
|
81
|
+
pendingReply: undefined,
|
|
81
82
|
currentWork: undefined,
|
|
82
83
|
agent,
|
|
83
84
|
quietMs,
|
|
@@ -86,7 +87,7 @@ function freshProgress(quietMs, agent) {
|
|
|
86
87
|
/** The event types feedLine acts on — everything else (streaming deltas, turn/agent
|
|
87
88
|
* bookkeeping) is ignored. Also passed as parsePiEventLine's pre-filter to skip JSON.parse for
|
|
88
89
|
* pi lines whose type is verifiably not one of these; a new case in the switch must be added
|
|
89
|
-
* here too. `tumwater_run` is the harness's own marker (src/agent/
|
|
90
|
+
* here too. `tumwater_run` is the harness's own marker (src/agent/agent-spawn.ts), read for its
|
|
90
91
|
* run kind: a kind-bearing marker flips the demux and starts that accumulator fresh before
|
|
91
92
|
* its `session` event lands, and a legacy label-only marker ("review") still flips the demux
|
|
92
93
|
* to gate. */
|
|
@@ -96,59 +97,103 @@ const PROGRESS_TYPES = new Set([
|
|
|
96
97
|
"tool_execution_start",
|
|
97
98
|
"tool_execution_update",
|
|
98
99
|
"tool_execution_end",
|
|
100
|
+
"message_start",
|
|
99
101
|
"message_end",
|
|
100
102
|
]);
|
|
103
|
+
/** pi's compact serialization of a message_start, up to the role's value. */
|
|
104
|
+
const MESSAGE_START_ROLE = '{"type":"message_start","message":{"role":"';
|
|
105
|
+
/** A new run starts: everything before it was a previous run — restore the at-time-zero state. */
|
|
106
|
+
function applySessionEvent(progress) {
|
|
107
|
+
Object.assign(progress, freshProgress(progress.quietMs));
|
|
108
|
+
}
|
|
109
|
+
/** A tool execution starts: count the call, record its label as the last tool, and seed the
|
|
110
|
+
* open-call entry the stall flag reads. Track the open call for the stall flag — pi runs a
|
|
111
|
+
* message's tool calls concurrently by default, so several can be open at once and end in
|
|
112
|
+
* completion order. The state machine is shared with runPi's stall warning
|
|
113
|
+
* (src/pi/pi-watchdogs.ts); only the fallback label differs here. The full raw command rides
|
|
114
|
+
* along because the flag classifies the command's shape, and describeToolCall's label
|
|
115
|
+
* truncates at 32 chars — an operator past that point would be invisible there. */
|
|
116
|
+
function applyToolStart(progress, event) {
|
|
117
|
+
progress.toolCalls += 1;
|
|
118
|
+
const label = event.toolName ? describeToolCall(event.toolName, event.args) : undefined;
|
|
119
|
+
if (label)
|
|
120
|
+
progress.lastTool = label;
|
|
121
|
+
applyToolExecutionEvent(progress.openToolCalls ??= [], event.type, event.toolCallId, event.partialResult, label ?? "tool", toolCallCommand(event.args));
|
|
122
|
+
}
|
|
123
|
+
/** A tool update or end folds through the same call: `applyToolExecutionEvent` reads `event.type`
|
|
124
|
+
* to decide what to do (a content-bearing update moves the activity clock; end closes the
|
|
125
|
+
* call), and both no-op when no call is tracked. Only content-bearing updates prove the
|
|
126
|
+
* command is alive — bash emits one empty-content update right after start, and a
|
|
127
|
+
* content-free keepalive must not mask a hang — a rule applyToolExecutionEvent owns. */
|
|
128
|
+
function applyToolProgress(progress, event) {
|
|
129
|
+
if (progress.openToolCalls)
|
|
130
|
+
applyToolExecutionEvent(progress.openToolCalls, event.type, event.toolCallId, event.partialResult);
|
|
131
|
+
}
|
|
132
|
+
/** An assistant message ends: count the turn and fold usage/peak/context and the first work item.
|
|
133
|
+
* Guard each count like pi-stream's usageNumber does: a string-serialized or corrupt wire value
|
|
134
|
+
* would otherwise poison the live cell — `+=` would concatenate output into a string and
|
|
135
|
+
* Math.max would yield NaN. An unusable total keeps the previous context (this cell is "latest
|
|
136
|
+
* value"), while output and the peak floor treat it as absent. The first text the role speaks
|
|
137
|
+
* in this run is its work item ("I'll implement plan X"); later messages never replace it. */
|
|
138
|
+
function applyAssistantTurn(progress, event) {
|
|
139
|
+
if (event.message?.role !== "assistant")
|
|
140
|
+
return;
|
|
141
|
+
progress.turns += 1;
|
|
142
|
+
const usage = event.message.usage;
|
|
143
|
+
if (usage) {
|
|
144
|
+
progress.contextTokens = nonNegativeNumber(usage.totalTokens, progress.contextTokens);
|
|
145
|
+
progress.outputTokens += nonNegativeNumber(usage.output, 0);
|
|
146
|
+
progress.peakContextTokens = Math.max(progress.peakContextTokens, nonNegativeNumber(usage.totalTokens, 0));
|
|
147
|
+
}
|
|
148
|
+
if (!progress.currentWork)
|
|
149
|
+
progress.currentWork = workItemFromContent(event.message.content);
|
|
150
|
+
}
|
|
101
151
|
/** Apply one parsed progress event to a progress object (mutates it). */
|
|
102
152
|
function feedLine(progress, event) {
|
|
103
153
|
switch (event.type) {
|
|
104
154
|
case "session": // A new run starts: everything before it was a previous run — restore the at-time-zero state.
|
|
105
|
-
|
|
155
|
+
applySessionEvent(progress);
|
|
106
156
|
break;
|
|
107
|
-
case "tool_execution_start":
|
|
108
|
-
progress
|
|
109
|
-
const label = event.toolName ? describeToolCall(event.toolName, event.args) : undefined;
|
|
110
|
-
if (label)
|
|
111
|
-
progress.lastTool = label;
|
|
112
|
-
// Track the open call for the stall flag — pi runs a message's tool calls concurrently
|
|
113
|
-
// by default, so several can be open at once and end in completion order. The state
|
|
114
|
-
// machine is shared with runPi's stall warning (src/pi/pi-watchdogs.ts); only the
|
|
115
|
-
// fallback label differs here. The full raw command rides along because the flag
|
|
116
|
-
// classifies the command's shape, and describeToolCall's label truncates at 32 chars —
|
|
117
|
-
// an operator past that point would be invisible there.
|
|
118
|
-
applyToolExecutionEvent(progress.openToolCalls ??= [], event.type, event.toolCallId, event.partialResult, label ?? "tool", toolCallCommand(event.args));
|
|
157
|
+
case "tool_execution_start":
|
|
158
|
+
applyToolStart(progress, event);
|
|
119
159
|
break;
|
|
120
|
-
}
|
|
121
|
-
// Updates and ends fold through the same call: `applyToolExecutionEvent` reads `event.type`
|
|
122
|
-
// to decide what to do (a content-bearing update moves the activity clock; end closes the
|
|
123
|
-
// call), and both no-op when no call is tracked. Only content-bearing updates prove the
|
|
124
|
-
// command is alive — bash emits one empty-content update right after start, and a
|
|
125
|
-
// content-free keepalive must not mask a hang — a rule applyToolExecutionEvent owns.
|
|
126
160
|
case "tool_execution_update":
|
|
127
161
|
case "tool_execution_end":
|
|
128
|
-
|
|
129
|
-
|
|
162
|
+
applyToolProgress(progress, event);
|
|
163
|
+
break;
|
|
164
|
+
case "message_start":
|
|
165
|
+
applyReplyEvent(progress, event);
|
|
130
166
|
break;
|
|
131
167
|
case "message_end":
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const usage = event.message.usage;
|
|
135
|
-
if (usage) {
|
|
136
|
-
// Guard each count like pi-stream's usageNumber does: a string-serialized or corrupt
|
|
137
|
-
// wire value would otherwise poison the live cell — `+=` would concatenate output into
|
|
138
|
-
// a string and Math.max would yield NaN. An unusable total keeps the previous context
|
|
139
|
-
// (this cell is "latest value"), while output and the peak floor treat it as absent.
|
|
140
|
-
progress.contextTokens = nonNegativeNumber(usage.totalTokens, progress.contextTokens);
|
|
141
|
-
progress.outputTokens += nonNegativeNumber(usage.output, 0);
|
|
142
|
-
progress.peakContextTokens = Math.max(progress.peakContextTokens, nonNegativeNumber(usage.totalTokens, 0));
|
|
143
|
-
}
|
|
144
|
-
// The first text the role speaks in this run is its work item ("I'll implement plan X");
|
|
145
|
-
// later messages never replace it.
|
|
146
|
-
if (!progress.currentWork)
|
|
147
|
-
progress.currentWork = workItemFromContent(event.message.content);
|
|
148
|
-
}
|
|
168
|
+
applyAssistantTurn(progress, event);
|
|
169
|
+
applyReplyEvent(progress, event);
|
|
149
170
|
break;
|
|
150
171
|
}
|
|
151
172
|
}
|
|
173
|
+
/** A message starts or ends: fold it into the pending-reply state. A request opened before this
|
|
174
|
+
* observer attached is stamped with pi's own timestamp on the message, not with "now", so a
|
|
175
|
+
* dashboard started mid-wait still shows the whole wait. */
|
|
176
|
+
function applyReplyEvent(progress, event) {
|
|
177
|
+
const sent = event.message?.timestamp;
|
|
178
|
+
const now = typeof sent === "number" && Number.isFinite(sent) ? sent : Date.now();
|
|
179
|
+
progress.pendingReply = foldPendingReply(progress.pendingReply, event, { now });
|
|
180
|
+
}
|
|
181
|
+
/** The raw-line step both feeds take before parsing (`line` already trimmed), true when the line
|
|
182
|
+
* needs nothing more. A streaming delta (most of a pi log) is consumed without a parse unless a
|
|
183
|
+
* model request is still waiting on its answer: then the first content-bearing delta is that
|
|
184
|
+
* answer. A message_start whose compact prefix verifiably names a non-assistant role (a user or
|
|
185
|
+
* tool-result message, whose content can run to tens of KB) is consumed unparsed too: only an
|
|
186
|
+
* assistant message_start opens a request. */
|
|
187
|
+
function consumedUnparsed(progress, line) {
|
|
188
|
+
if (piEventType(line) === "message_update") {
|
|
189
|
+
if (progress.pendingReply) {
|
|
190
|
+
const hasContent = messageUpdateHasContent(parseJsonObject(line));
|
|
191
|
+
progress.pendingReply = foldPendingReply(progress.pendingReply, { type: "message_update" }, { hasContent });
|
|
192
|
+
}
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
return line.startsWith(MESSAGE_START_ROLE) && !line.startsWith('assistant"', MESSAGE_START_ROLE.length);
|
|
196
|
+
}
|
|
152
197
|
/** Parse pi event lines of ONE run (the `tumwater_run` marker is ignored; the run's
|
|
153
198
|
* `session` event resets the accumulator). Exported for tests; readLiveProgress routes
|
|
154
199
|
* through feedDemuxed instead, since a real role log can interleave two runs' lines. A
|
|
@@ -156,7 +201,10 @@ function feedLine(progress, event) {
|
|
|
156
201
|
export function parseProgress(lines, quietMs) {
|
|
157
202
|
const progress = freshProgress(quietMs);
|
|
158
203
|
for (const line of lines) {
|
|
159
|
-
const
|
|
204
|
+
const trimmed = line.trim();
|
|
205
|
+
if (consumedUnparsed(progress, trimmed))
|
|
206
|
+
continue;
|
|
207
|
+
const event = parsePiEventLine(trimmed, PROGRESS_TYPES);
|
|
160
208
|
if (event)
|
|
161
209
|
feedLine(progress, event);
|
|
162
210
|
}
|
|
@@ -164,6 +212,7 @@ export function parseProgress(lines, quietMs) {
|
|
|
164
212
|
// exists here so callers that pass a hand-built tail through workingDetail see the same
|
|
165
213
|
// shape readLiveProgress returns.
|
|
166
214
|
progress.stalledTool = stalledToolLabel(progress.openToolCalls);
|
|
215
|
+
progress.unansweredReply = unansweredReply(progress.pendingReply);
|
|
167
216
|
return progress;
|
|
168
217
|
}
|
|
169
218
|
/** The configured threshold for flagging an open tool call as stalled, in ms (0 when
|
|
@@ -200,6 +249,16 @@ export function stalledToolLabel(open, now = Date.now(), stallMs = defaultConfig
|
|
|
200
249
|
}
|
|
201
250
|
return undefined;
|
|
202
251
|
}
|
|
252
|
+
/** How long `pending` has waited and over how many attempts, once that wait reaches `stallMs` —
|
|
253
|
+
* the in-flight cell's "no model reply" flag, on the same threshold runPi's unanswered-request
|
|
254
|
+
* warning uses (src/pi/pi-watchdogs.ts), so the state cell and the event feed agree. `now` and
|
|
255
|
+
* `stallMs` are injectable for tests. */
|
|
256
|
+
export function unansweredReply(pending, now = Date.now(), stallMs = defaultConfig().toolCallStallSeconds * 1000) {
|
|
257
|
+
if (!pending || stallMs <= 0)
|
|
258
|
+
return undefined;
|
|
259
|
+
const waitedMs = now - pending.since;
|
|
260
|
+
return waitedMs >= stallMs ? { waitedMs, attempts: pending.attempts } : undefined;
|
|
261
|
+
}
|
|
203
262
|
/** Fold one raw log line into a role log's per-kind tail state (mutates it): the harness's
|
|
204
263
|
* `tumwater_run` marker or a `session` event switches which accumulator following lines fold
|
|
205
264
|
* into. A marker carries the run's kind, so a `session` preceded by one keeps that kind even
|
|
@@ -210,7 +269,10 @@ export function stalledToolLabel(open, now = Date.now(), stallMs = defaultConfig
|
|
|
210
269
|
* run's turns/context can never show in the new run's cell. Everything else folds into the
|
|
211
270
|
* current kind's accumulator. Non-JSON noise is skipped. */
|
|
212
271
|
function feedDemuxed(tail, line, gateCwd) {
|
|
213
|
-
const
|
|
272
|
+
const trimmed = line.trim();
|
|
273
|
+
if (consumedUnparsed(tail[tail.cur], trimmed))
|
|
274
|
+
return;
|
|
275
|
+
const event = parsePiEventLine(trimmed, PROGRESS_TYPES);
|
|
214
276
|
if (!event)
|
|
215
277
|
return; // Blank, unparseable, or a type this feed does not act on.
|
|
216
278
|
if (event.type === "session") {
|
|
@@ -251,7 +313,7 @@ function feedDemuxed(tail, line, gateCwd) {
|
|
|
251
313
|
* run in the role's own worktree, the default; `gate` — the review gate's runs in a leased pool
|
|
252
314
|
* slot, what the reviewing cell shows), or null when there is no log yet. The raw log is
|
|
253
315
|
* append-only while pi runs and every run starts with a `tumwater_run` marker
|
|
254
|
-
* (src/agent/
|
|
316
|
+
* (src/agent/agent-spawn.ts), so after seeding from the tail window once we only read and parse
|
|
255
317
|
* bytes appended since the last poll — observers that call this every second (TUI, GUI) stop
|
|
256
318
|
* rescanning up to TAIL_BYTES
|
|
257
319
|
* of JSON per role per poll. */
|
|
@@ -269,6 +331,7 @@ export function readLiveProgress(root, role, kind = "author") {
|
|
|
269
331
|
// observer first saw the log stamps "now" at seed, so it cannot be flagged retroactively;
|
|
270
332
|
// the mtime-based "no pi output" flag covers late observers.
|
|
271
333
|
progress.stalledTool = stalledToolLabel(progress.openToolCalls, Date.now(), toolCallStallMs(root), progress.agent);
|
|
334
|
+
progress.unansweredReply = unansweredReply(progress.pendingReply, Date.now(), toolCallStallMs(root));
|
|
272
335
|
// Copy the open-call entries: feedLine mutates the stored tail value in place — an
|
|
273
336
|
// un-copied collection would alias one mutable structure across every frame ever returned
|
|
274
337
|
// AND with the live tail state.
|