tumwater 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -108
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +24 -0
- package/dist/src/agent/agent-backend.js +2 -2
- package/dist/src/agent/backend-traits.js +60 -0
- package/dist/src/agent/claude/claude-account.js +61 -8
- package/dist/src/agent/claude/claude-args.js +12 -15
- package/dist/src/agent/claude/claude-backend.js +51 -41
- package/dist/src/agent/claude/claude-env.js +7 -3
- package/dist/src/agent/claude/claude-stream.js +7 -16
- package/dist/src/agent/image-input.js +107 -0
- package/dist/src/agent/notes-shim.js +41 -0
- package/dist/src/agent/opencode/opencode-args.js +61 -0
- package/dist/src/agent/opencode/opencode-backend.js +255 -0
- package/dist/src/agent/opencode/opencode-catalog.js +184 -0
- package/dist/src/agent/opencode/opencode-config.js +188 -0
- package/dist/src/agent/opencode/opencode-env.js +36 -0
- package/dist/src/agent/opencode/opencode-session-id.js +37 -0
- package/dist/src/agent/opencode/opencode-stream.js +278 -0
- package/dist/src/agent/role-notes-write.js +16 -32
- package/dist/src/agent/run-agent.js +95 -18
- package/dist/src/agent/session-ledger.js +57 -1
- package/dist/src/backlog/backlog-archive.js +296 -0
- package/dist/src/backlog/backlog-cli.js +573 -0
- package/dist/src/backlog/backlog-commit.js +31 -0
- package/dist/src/backlog/backlog-eligibility.js +357 -33
- package/dist/src/backlog/backlog-md.js +98 -18
- package/dist/src/backlog/backlog-render.js +62 -11
- package/dist/src/backlog/backlog-structure.js +261 -58
- package/dist/src/backlog/backlog-write.js +197 -319
- package/dist/src/backlog/backlog.js +113 -16
- package/dist/src/backlog/question-answer.js +339 -0
- package/dist/src/baseline/main-baseline.js +75 -88
- package/dist/src/baseline/main-red.js +38 -30
- package/dist/src/brief/brief-edit.js +43 -0
- package/dist/src/{brief.js → brief/brief.js} +66 -10
- package/dist/src/budget/budget.js +32 -15
- package/dist/src/budget/fallback-breaker.js +8 -7
- package/dist/src/build/build-check-detect.js +8 -7
- package/dist/src/build/build-check-events.js +68 -22
- package/dist/src/build/build-check-report.js +22 -23
- package/dist/src/build/build-check-scoped.js +51 -62
- package/dist/src/build/build-check.js +15 -14
- package/dist/src/build/build-info.js +2 -1
- package/dist/src/build/build-stage.js +23 -22
- package/dist/src/build/dep-install.js +22 -21
- package/dist/src/build/host-sleep.js +1 -1
- package/dist/src/change/change-data.js +25 -14
- package/dist/src/change/change-render.js +11 -10
- package/dist/src/cli/brief-command.js +58 -0
- package/dist/src/cli/cli-args.js +25 -5
- package/dist/src/cli/cli-command-args.js +46 -14
- package/dist/src/cli/cli-editor.js +64 -0
- package/dist/src/cli/cli-flag-specs.js +54 -16
- package/dist/src/cli/cli-marker-commands.js +13 -10
- package/dist/src/cli/cli-output.js +9 -0
- package/dist/src/cli/cli-query-commands.js +458 -49
- package/dist/src/cli/cli-repo-gate.js +1 -1
- package/dist/src/cli/cli-run.js +208 -59
- package/dist/src/cli/completion.js +207 -0
- package/dist/src/cli/config-commands.js +14 -11
- package/dist/src/cli/help.js +305 -110
- package/dist/src/cli/log-commands.js +127 -21
- package/dist/src/cli/notify-command.js +33 -18
- package/dist/src/cli/question-commands.js +29 -224
- package/dist/src/cli.js +121 -40
- package/dist/src/collections.js +66 -16
- package/dist/src/concurrency/check-permit.js +16 -17
- package/dist/src/concurrency/lock.js +6 -5
- package/dist/src/concurrency/provider-lease.js +69 -0
- package/dist/src/concurrency/semaphore.js +258 -36
- package/dist/src/config/config-aliases.js +106 -0
- package/dist/src/config/config-editable-keys.js +3 -3
- package/dist/src/config/config-fallbacks.js +379 -0
- package/dist/src/config/config-field-checks.js +19 -10
- package/dist/src/config/config-keys.js +160 -0
- package/dist/src/config/config-live.js +64 -12
- package/dist/src/config/config-schema.js +34 -22
- package/dist/src/config/config-validation.js +132 -48
- package/dist/src/config/config-views.js +119 -337
- package/dist/src/config/config-write.js +138 -177
- package/dist/src/config/config.js +60 -49
- package/dist/src/config/model-selector.js +28 -2
- package/dist/src/doctor/doctor-backlog.js +546 -49
- package/dist/src/doctor/doctor-checks.js +534 -56
- package/dist/src/doctor/doctor-holds.js +193 -53
- package/dist/src/doctor/doctor-install.js +30 -0
- package/dist/src/doctor/doctor-launch-services.js +2 -2
- package/dist/src/doctor/doctor-model-checks.js +549 -157
- package/dist/src/doctor/doctor-notify.js +27 -0
- package/dist/src/doctor/doctor-orphans.js +10 -9
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +80 -22
- package/dist/src/doctor/image-probe.js +244 -0
- package/dist/src/doctor/probe.js +64 -0
- package/dist/src/events/event-format.js +226 -102
- package/dist/src/events/event-labels.js +116 -0
- package/dist/src/events/event-legacy.js +140 -0
- package/dist/src/events/event-read.js +58 -22
- package/dist/src/events/event-window.js +27 -7
- package/dist/src/events/events.js +18 -14
- package/dist/src/events/notify.js +137 -38
- package/dist/src/failure/failure-cluster.js +12 -9
- package/dist/src/failure/failure-data.js +96 -48
- package/dist/src/failure/failure-render.js +69 -58
- package/dist/src/failure/failure-state-change.js +33 -26
- package/dist/src/failure/time-spend.js +79 -59
- package/dist/src/files/file-queue.js +16 -4
- package/dist/src/files/files.js +18 -6
- package/dist/src/files/json-object.js +18 -3
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +5 -4
- package/dist/src/gates/budget-gates.js +250 -34
- package/dist/src/gates/disk-gate.js +18 -18
- package/dist/src/gates/gate-polls.js +117 -69
- package/dist/src/gates/maintenance-quota.js +29 -29
- package/dist/src/gates/pause-gates.js +11 -11
- package/dist/src/gates/readiness.js +2 -1
- package/dist/src/gates/role-cap-gates.js +16 -21
- package/dist/src/gates/startup-gate.js +75 -31
- package/dist/src/gates/streak-gate.js +18 -16
- package/dist/src/git/commit-message.js +12 -5
- package/dist/src/git/git-diff.js +16 -16
- package/dist/src/git/git-run.js +8 -8
- package/dist/src/git/git.js +86 -55
- package/dist/src/git/slots-state.js +14 -5
- package/dist/src/git/worktree-pool.js +25 -18
- package/dist/src/git/worktree-use.js +15 -14
- package/dist/src/git/worktree.js +18 -18
- package/dist/src/gui/gui-args.js +10 -14
- package/dist/src/gui/gui-backlog-commands.js +273 -0
- package/dist/src/gui/gui-command.js +1 -1
- package/dist/src/gui/gui-config-commands.js +440 -0
- package/dist/src/gui/gui-endpoint-commands.js +16 -669
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +116 -56
- package/dist/src/gui/gui-operator-commands.js +145 -0
- package/dist/src/gui/gui-prompt-commands.js +234 -0
- package/dist/src/gui/gui-server.js +28 -8
- package/dist/src/gui/gui-team-commands.js +164 -0
- package/dist/src/gui/http-body.js +17 -1
- package/dist/src/history/history-data.js +194 -98
- package/dist/src/history/history-render.js +76 -0
- package/dist/src/history/history.js +37 -58
- package/dist/src/inbox/inbox-attachments.js +75 -14
- package/dist/src/inbox/inbox-cancel.js +7 -5
- package/dist/src/inbox/inbox-edit.js +1 -1
- package/dist/src/inbox/inbox-submit.js +44 -4
- package/dist/src/inbox/inbox-top.js +83 -0
- package/dist/src/inbox/inbox.js +8 -2
- package/dist/src/inbox/pending-prompt.js +1 -1
- package/dist/src/inbox/prompt-commands.js +170 -80
- package/dist/src/init/init-templates.js +16 -14
- package/dist/src/init/init.js +12 -11
- package/dist/src/loop/leftover.js +28 -27
- package/dist/src/loop/loop-fallback.js +2 -2
- package/dist/src/loop/loop-pi.js +106 -52
- package/dist/src/loop/loop-recovery.js +15 -15
- package/dist/src/loop/loop-state.js +139 -13
- package/dist/src/loop/loop-tick.js +194 -86
- package/dist/src/loop/loop.js +72 -36
- package/dist/src/loop/model-fallback.js +3 -2
- package/dist/src/loop/revision.js +16 -7
- package/dist/src/merge/backlog-conflicts.js +155 -0
- package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
- package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
- package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
- package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
- package/dist/src/merge/merge-drain.js +212 -0
- package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
- package/dist/src/merge/merge-pipeline.js +99 -0
- package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
- package/dist/src/merge/merge-queue-render.js +75 -0
- package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
- package/dist/src/merge/merge-slot.js +340 -0
- package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
- package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
- package/dist/src/merge/merge-verify.js +212 -0
- package/dist/src/notes-cli.js +97 -0
- package/dist/src/operator/operator-commands.js +154 -47
- package/dist/src/operator/operator-intent.js +87 -49
- package/dist/src/operator/operator-requests.js +52 -52
- package/dist/src/operator/retire.js +62 -27
- package/dist/src/operator/revert.js +71 -0
- package/dist/src/orchestrator/orchestrator-launch.js +74 -14
- package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
- package/dist/src/orchestrator/orchestrator.js +158 -100
- package/dist/src/orchestrator/retention.js +29 -12
- package/dist/src/orchestrator/startup-migration-steps.js +51 -0
- package/dist/src/orchestrator/startup-migrations.js +40 -0
- package/dist/src/paths.js +128 -37
- package/dist/src/pi/pi-args.js +26 -3
- package/dist/src/pi/pi-backend.js +15 -23
- package/dist/src/pi/pi-bin.js +18 -12
- package/dist/src/pi/pi-event-line.js +17 -1
- package/dist/src/pi/pi-failure.js +87 -0
- package/dist/src/pi/pi-models.js +126 -38
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +86 -0
- package/dist/src/pi/pi-stream.js +131 -107
- package/dist/src/pi/pi-watchdogs.js +47 -43
- package/dist/src/pi-extension/bounded-output.js +20 -5
- package/dist/src/pi-extension/context-budget.js +3 -1
- package/dist/src/pi-extension/context-shake.js +54 -21
- package/dist/src/pi-extension/context-window.js +37 -0
- package/dist/src/pi-extension/full-output.js +2 -2
- package/dist/src/pi-extension/strip-thinking.js +80 -0
- package/dist/src/pi-extension/trim-system-prompt.js +38 -0
- package/dist/src/process/launch-services.js +11 -10
- package/dist/src/process/process-table.js +1 -1
- package/dist/src/process/process.js +7 -7
- package/dist/src/process/run-marker.js +29 -6
- package/dist/src/process/supervisor.js +47 -25
- package/dist/src/prompt/dialect.js +30 -10
- package/dist/src/prompt/gate-prompts.js +170 -67
- package/dist/src/prompt/principles.js +1 -0
- package/dist/src/prompt/prompt-followup.js +35 -20
- package/dist/src/prompt/prompt.js +249 -91
- package/dist/src/redeploy/redeploy-policy.js +10 -10
- package/dist/src/redeploy/redeploy.js +6 -4
- package/dist/src/redeploy/redeployer.js +63 -55
- package/dist/src/redeploy/self-reload.js +1 -1
- package/dist/src/report/report-data.js +264 -255
- package/dist/src/report/report-fold.js +353 -0
- package/dist/src/report/report-render.js +270 -33
- package/dist/src/request-timeouts.js +1 -1
- package/dist/src/review/known-flakes.js +34 -14
- package/dist/src/review/protected-edits.js +33 -0
- package/dist/src/review/review-followup.js +16 -4
- package/dist/src/review/review-precheck.js +14 -13
- package/dist/src/review/review-verdict.js +14 -0
- package/dist/src/review/review.js +81 -39
- package/dist/src/review/secret-leaks.js +127 -0
- package/dist/src/review/suite-rerun.js +4 -3
- package/dist/src/roles/legacy-role-ids.js +21 -0
- package/dist/src/roles/legacy-role-migration.js +62 -0
- package/dist/src/roles/loop-ids.js +19 -2
- package/dist/src/roles/role-catalog.js +125 -135
- package/dist/src/roles/role-guidance.js +64 -31
- package/dist/src/roles/role-labels.js +10 -0
- package/dist/src/roles/role-render.js +194 -18
- package/dist/src/roles/role-view.js +216 -17
- package/dist/src/roles/roles.js +18 -15
- package/dist/src/scheduling/backoff.js +29 -19
- package/dist/src/scheduling/claims.js +25 -9
- package/dist/src/scheduling/once-round.js +11 -11
- package/dist/src/scheduling/quiet-hours.js +25 -32
- package/dist/src/scheduling/scheduling.js +43 -24
- package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
- package/dist/src/status/status-budget.js +115 -0
- package/dist/src/status/status-data.js +77 -184
- package/dist/src/status/status-inbox.js +47 -0
- package/dist/src/status/status-install.js +27 -0
- package/dist/src/status/status-polls.js +68 -33
- package/dist/src/status/status-roles.js +105 -0
- package/dist/src/{fleet → team}/error-storm.js +10 -10
- package/dist/src/{fleet → team}/failure-spread.js +8 -8
- package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
- package/dist/src/{fleet → team}/reclaim.js +5 -4
- package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
- package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
- package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
- package/dist/src/text/datetime.js +42 -2
- package/dist/src/text/format.js +10 -1
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +35 -34
- package/dist/src/text/suggest.js +3 -2
- package/dist/src/text/text.js +2 -2
- package/dist/src/tick/qa-coverage.js +2 -1
- package/dist/src/tick/stage-check.js +147 -26
- package/dist/src/tick/telemetry-digest.js +10 -9
- package/dist/src/tick/tick-apply.js +101 -72
- package/dist/src/tick/tick-detail-data.js +32 -18
- package/dist/src/tick/tick-detail.js +62 -21
- package/dist/src/tick/tick-finalize.js +29 -22
- package/dist/src/tick/tick-outcome.js +2 -1
- package/dist/src/tick/tick-prompt.js +94 -26
- package/dist/src/tick/tick-resume.js +29 -13
- package/dist/src/tick/tick-stage.js +91 -47
- package/dist/src/tick/tick-timing.js +60 -59
- package/dist/src/tick/tick-usage.js +53 -23
- package/dist/src/tick/tick-verdict.js +76 -10
- package/dist/src/ui/badges.js +118 -67
- package/dist/src/ui/gui/gui-client-boot.js +209 -30
- package/dist/src/ui/gui/gui-client-composer.js +102 -23
- package/dist/src/ui/gui/gui-client-drawer.js +330 -75
- package/dist/src/ui/gui/gui-client-history.js +96 -38
- package/dist/src/ui/gui/gui-client-labels.js +24 -0
- package/dist/src/ui/gui/gui-client-loops.js +94 -52
- package/dist/src/ui/gui/gui-client-markdown.js +1 -0
- package/dist/src/ui/gui/gui-client-model.js +63 -43
- package/dist/src/ui/gui/gui-client-operator.js +129 -39
- package/dist/src/ui/gui/gui-client-pending.js +36 -32
- package/dist/src/ui/gui/gui-client-report.js +339 -46
- package/dist/src/ui/gui/gui-client-settings.js +148 -14
- package/dist/src/ui/gui/gui-client-sound.js +2 -1
- package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
- package/dist/src/ui/gui/gui-client.js +26 -20
- package/dist/src/ui/gui/gui-page.js +19 -19
- package/dist/src/ui/gui/gui-styles.js +12 -4
- package/dist/src/ui/progress-data.js +5 -3
- package/dist/src/ui/status-model.js +184 -122
- package/dist/src/ui/status-payload.js +119 -32
- package/dist/src/ui/status-render.js +122 -52
- package/dist/src/ui/team-alerts.js +341 -0
- package/dist/src/ui/tick-progress-model.js +14 -14
- package/dist/src/ui/tone.js +11 -8
- package/dist/src/ui/transcript-tail.js +1 -0
- package/dist/src/ui/transcript.js +23 -16
- package/dist/src/ui/tui/tui-app.js +1 -0
- package/dist/src/ui/tui/tui-backlog.js +85 -11
- package/dist/src/ui/tui/tui-editor-answer.js +97 -0
- package/dist/src/ui/tui/tui-editor-attach.js +107 -0
- package/dist/src/ui/tui/tui-editor-brief.js +107 -0
- package/dist/src/ui/tui/tui-editor-budget.js +155 -0
- package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
- package/dist/src/ui/tui/tui-editor-entry.js +230 -0
- package/dist/src/ui/tui/tui-editor-interval.js +122 -0
- package/dist/src/ui/tui/tui-editor-notes.js +114 -0
- package/dist/src/ui/tui/tui-editor-pause.js +266 -0
- package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
- package/dist/src/ui/tui/tui-editor-queued.js +98 -0
- package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
- package/dist/src/ui/tui/tui-editor-retire.js +83 -0
- package/dist/src/ui/tui/tui-editor-search.js +73 -0
- package/dist/src/ui/tui/tui-editor-session.js +97 -0
- package/dist/src/ui/tui/tui-editors.js +152 -0
- package/dist/src/ui/tui/tui-frame.js +67 -55
- package/dist/src/ui/tui/tui-history.js +12 -7
- package/dist/src/ui/tui/tui-input.js +49 -44
- package/dist/src/ui/tui/tui-keymap.js +1 -0
- package/dist/src/ui/tui/tui-keys.js +91 -1704
- package/dist/src/ui/tui/tui-pane-activity.js +99 -0
- package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
- package/dist/src/ui/tui/tui-pane-brief.js +30 -0
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +124 -0
- package/dist/src/ui/tui/tui-pane-history.js +316 -0
- package/dist/src/ui/tui/tui-pane-pending.js +74 -0
- package/dist/src/ui/tui/tui-pane-queued.js +143 -0
- package/dist/src/ui/tui/tui-pane-report.js +122 -0
- package/dist/src/ui/tui/tui-pane-session.js +101 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
- package/dist/src/ui/tui/tui-pane.js +66 -28
- package/dist/src/ui/tui/tui-panes.js +294 -0
- package/dist/src/ui/tui/tui.js +45 -25
- package/dist/src/verdict/fix-claim.js +141 -38
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +92 -8
- package/dist/src/verdict/reply-contract.js +17 -5
- package/dist/src/version.js +3 -2
- package/package.json +4 -2
- package/dist/src/agent/mcp/role-notes-server.js +0 -160
- package/dist/src/landing/backlog-conflicts.js +0 -116
- package/dist/src/landing/landing-drain.js +0 -204
- package/dist/src/landing/landing-pipeline.js +0 -96
- package/dist/src/landing/landing-queue-render.js +0 -31
- package/dist/src/landing/landing-slot.js +0 -246
- package/dist/src/landing/landing-vetting.js +0 -196
- package/dist/src/pi-extension/role-notes.js +0 -40
- package/dist/src/ui/fleet-alerts.js +0 -174
|
@@ -1,25 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// vocabulary: plain
|
|
2
|
+
/** The dashboard's team view, browser-side — the main screen between polls: the sidebar's
|
|
3
|
+
* project name and team status (loops running, build state, main's check, quiet hours),
|
|
3
4
|
* the alerts band for whatever needs a human (a failing loop, a red main, a spent budget,
|
|
4
5
|
* an old build, open questions, a pause), today's progress tiles with their
|
|
5
6
|
* per-day bar chart, the backlog with its questions/plans/bugs tabs, and the notable activity
|
|
6
7
|
* feed (the loops table with its per-row actions lives in gui-client-loops.ts; the composer
|
|
7
|
-
* rendered onto this view lives in gui-client-composer.ts).
|
|
8
|
+
* rendered onto this view lives in gui-client-composer.ts). renderTeam repaints all of it
|
|
8
9
|
* from each /api/status poll. Spliced into gui-client.ts's script, reaching its helpers (esc,
|
|
9
10
|
* icon, getJson, postJson, postAction, plural, agree, recall, fmtAgo, fmtTokens, fmtUsd,
|
|
10
11
|
* paintPanel, showFlash) through that concatenation. */
|
|
11
|
-
export const
|
|
12
|
+
export const GUI_CLIENT_TEAM_JS = String.raw ` // ---- sidebar: project, team status, budget and pause controls ----
|
|
12
13
|
// restart-button:start
|
|
13
|
-
// The
|
|
14
|
-
//
|
|
15
|
-
// serve both
|
|
14
|
+
// The alert affordances' exact markup — one home shared by the sidebar's stale-build row and
|
|
15
|
+
// alertParts()'s build alert (a restart) and the held/reclaim disk row (a reclaim), so styling
|
|
16
|
+
// and the global [data-act='restart'] / [data-act='reclaim'] handlers serve both from one place.
|
|
17
|
+
function actionButton(glyph, act, title) {
|
|
18
|
+
return "<button type='button' class='alert-icon' data-act='" + act + "' title='" + title + "'>" + glyph + "</button>";
|
|
19
|
+
}
|
|
16
20
|
function restartButton(glyph) {
|
|
17
|
-
return "
|
|
21
|
+
return actionButton(glyph, "restart", "Restart onto the update now");
|
|
18
22
|
}
|
|
19
|
-
// The disk row's reclaim affordance: its icon IS the button when the row is held or in the
|
|
20
|
-
// reclaim band; the global [data-act='reclaim'] handler posts /api/reclaim.
|
|
21
23
|
function reclaimButton(glyph) {
|
|
22
|
-
return "
|
|
24
|
+
return actionButton(glyph, "reclaim", "Reclaim idle build outputs now");
|
|
23
25
|
}
|
|
24
26
|
// restart-button:end
|
|
25
27
|
// sidebar:start
|
|
@@ -40,7 +42,7 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
40
42
|
else {
|
|
41
43
|
rows = d.running
|
|
42
44
|
? row("green", "<span class='dot live'></span>", "Running", "Orchestrator running as pid " + d.pid)
|
|
43
|
-
: row("gray", "<span class='dot'></span>", "Stopped", "Start it with tumwater
|
|
45
|
+
: row("gray", "<span class='dot'></span>", "Stopped", "Start it with tumwater start");
|
|
44
46
|
if (d.build) {
|
|
45
47
|
const stale = d.build.stale;
|
|
46
48
|
// A stale build's refresh icon IS the restart button — restartButton(), the same
|
|
@@ -48,8 +50,8 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
48
50
|
// decoration.
|
|
49
51
|
const lead = stale ? restartButton(icon("refresh")) : icon("refresh");
|
|
50
52
|
rows += row(stale ? (d.build.restartBlocked ? "amber" : "blue") : "", lead,
|
|
51
|
-
"
|
|
52
|
-
localizeInstants(("Running
|
|
53
|
+
"Version <span class='mono'>" + esc(shortSha(d.build.sha)) + "</span>" + (stale ? " · " + esc(d.build.aheadCommits || 0) + " behind" : ""),
|
|
54
|
+
localizeInstants(("Running version" + (d.buildBadge || "").replace(/^, build/, "")).trim()));
|
|
53
55
|
}
|
|
54
56
|
if (d.mainCheck) {
|
|
55
57
|
// Main's newest merge-scope check (status-model's mainCheckBadge): green, red, or — a
|
|
@@ -70,28 +72,44 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
70
72
|
// The disk floor (plans/disk-floor.md, part 4/4): the sidebar row renders the payload's
|
|
71
73
|
// preformatted diskBadge (upcased for the row's label style) — the same facts the
|
|
72
74
|
// TUI/status header carries, so the two surfaces cannot drift — and names the free space,
|
|
73
|
-
//
|
|
75
|
+
// and the hold there.
|
|
74
76
|
if (d.diskBadge) {
|
|
75
77
|
const held = Boolean(d.disk && d.disk.held);
|
|
76
78
|
const low = Boolean(d.disk && !held && d.disk.reclaimGB > 0 && d.disk.freeGB < d.disk.reclaimGB);
|
|
77
79
|
rows += row(held ? "amber" : "blue", held || low ? reclaimButton(icon("info")) : icon("info"),
|
|
78
80
|
esc(badgeLabel(d.diskBadge)),
|
|
79
81
|
held
|
|
80
|
-
? "Free space is below the " + esc(String(d.disk.holdGB)) + " GB floor, so the
|
|
82
|
+
? "Free space is below the " + esc(String(d.disk.holdGB)) + " GB floor, so the team starts no new work until it recovers. Build outputs are reclaimed first."
|
|
81
83
|
: low
|
|
82
84
|
? "Free space is below the " + esc(String(d.disk.reclaimGB)) + " GB reclaim threshold; idle build outputs are being cleaned."
|
|
83
|
-
: "
|
|
85
|
+
: "");
|
|
86
|
+
}
|
|
87
|
+
// The permit pool (plans/provider-concurrency.md §8): the payload's preformatted
|
|
88
|
+
// permitsBadge — running 5 of 8 · omlx 3/3 · claude 2/6 — upcased for the row's label
|
|
89
|
+
// style, the same reading the TUI/status header carries, so the two surfaces cannot
|
|
90
|
+
// drift. Empty on a map-less team, so the sidebar stays unchanged.
|
|
91
|
+
if (d.permitsBadge) {
|
|
92
|
+
const tok = String(d.permitsBadge).replace(/^ · /, "");
|
|
93
|
+
rows += row("blue", icon("gauge"), esc(tok),
|
|
94
|
+
"Roles running at once out of the team limit, then each capped provider's own running/cap; a role waits to start when its provider is at its cap.");
|
|
95
|
+
}
|
|
96
|
+
// The run --for deadline: the payload's preformatted runWindowBadge, like the disk row.
|
|
97
|
+
if (d.runWindowBadge) {
|
|
98
|
+
rows += row("blue", icon("info"), esc(badgeLabel(d.runWindowBadge)),
|
|
99
|
+
d.scheduledStopAt !== undefined && d.scheduledStopAt === d.runStopsAt
|
|
100
|
+
? "A scheduled stop (tumwater stop --in) — the team finishes current work, then stops."
|
|
101
|
+
: "tumwater was started with start --for; at the deadline it finishes current work, prints a per-role summary, and stops.");
|
|
84
102
|
}
|
|
85
103
|
// The configured quiet-hours window ("Quiet hours … part 2/2"): the schedule as
|
|
86
104
|
// standing information, amber while the local clock is inside it — the same
|
|
87
105
|
// inside/outside wording the TUI/status header's quietBadge carries, derived from the
|
|
88
106
|
// payload's raw quietHours/inQuietHours fields (the active case also raises the
|
|
89
|
-
//
|
|
107
|
+
// team-alerts quiet alert above).
|
|
90
108
|
if (d.quietHours) {
|
|
91
109
|
const end = String(d.quietHours).split("-")[1] || "";
|
|
92
110
|
rows += row(d.inQuietHours ? "amber" : "", icon("pause"),
|
|
93
111
|
d.inQuietHours ? "Quiet until " + esc(end) : "Quiet " + esc(d.quietHours),
|
|
94
|
-
"Quiet hours —
|
|
112
|
+
"Quiet hours — roles start no new runs during this local-time window; the director keeps running your messages");
|
|
95
113
|
}
|
|
96
114
|
// New-project bootstrap (plans/work-ratio.md, part 2/2): the payload's preformatted
|
|
97
115
|
// bootstrapBadge, upcased for the row's label style — the same reading the TUI/status
|
|
@@ -99,12 +117,13 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
99
117
|
if (d.bootstrapBadge) {
|
|
100
118
|
rows += row("blue", icon("info"),
|
|
101
119
|
esc(badgeLabel(d.bootstrapBadge)),
|
|
102
|
-
"New-project bootstrap —
|
|
120
|
+
"New-project bootstrap — maintenance roles wait until enough plans are done; plan, feature and director keep running");
|
|
103
121
|
}
|
|
104
122
|
}
|
|
105
123
|
paintPanel("statuschips", rows);
|
|
106
124
|
renderBudgetBadge(d);
|
|
107
125
|
renderPauseBadge(d);
|
|
126
|
+
renderStopControl(d);
|
|
108
127
|
renderSoundBadge();
|
|
109
128
|
}
|
|
110
129
|
// sidebar:end
|
|
@@ -119,7 +138,7 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
119
138
|
// i * 2000 spacing exists for the test path, which passes a stepped clock so each fresh
|
|
120
139
|
// alert clears the window and sounds its own cue. The keys seen now are kept for the next
|
|
121
140
|
// poll's diff; lastNeedsYouKeys starts null, so a page opened onto an already-alerting
|
|
122
|
-
//
|
|
141
|
+
// team cues once.
|
|
123
142
|
let lastNeedsYouKeys = null;
|
|
124
143
|
function cueNewNeedsYou(alerts, now) {
|
|
125
144
|
const fresh = newNeedsYouKeys(lastNeedsYouKeys, alerts);
|
|
@@ -136,7 +155,7 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
136
155
|
// from under a click in progress.
|
|
137
156
|
function alertParts(a, restartable) {
|
|
138
157
|
// A stale-build alert's refresh icon IS the restart button (PLANS.md 2026-09-30): pressing
|
|
139
|
-
// it forces the
|
|
158
|
+
// it forces the team's self-redeploy onto main's head now instead of waiting out the
|
|
140
159
|
// cooldown. Only the build alert carries it, and only while the build is actually stale —
|
|
141
160
|
// a fresh build's icon stays inert decoration.
|
|
142
161
|
const glyph = icon(ALERT_ICONS[a.key] || "info");
|
|
@@ -194,7 +213,7 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
194
213
|
todayAt = Date.now();
|
|
195
214
|
} catch { /* keep the last numbers */ }
|
|
196
215
|
todayLoading = false;
|
|
197
|
-
if (lastStatus && activeView === "
|
|
216
|
+
if (lastStatus && activeView === "team") renderStats(lastStatus);
|
|
198
217
|
}
|
|
199
218
|
// stats:start
|
|
200
219
|
function statTile(t) {
|
|
@@ -203,27 +222,28 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
203
222
|
"<span class='stat-sub'>" + t.sub + "</span>" + (t.meter || "") + (t.act ? "</button>" : "</div>");
|
|
204
223
|
}
|
|
205
224
|
function renderStats(d) {
|
|
206
|
-
const loops = d.
|
|
225
|
+
const loops = d.roles || [];
|
|
207
226
|
// The running set (phaseInfo.live), not the permit-holder inFlight: the exempt running
|
|
208
227
|
// director is genuinely in flight, so the tile must count it even though it holds no
|
|
209
228
|
// maxConcurrent permit (BUGS.md 2026-10-06).
|
|
210
229
|
const live = loops.filter((l) => phaseInfo(l.phase).live);
|
|
211
230
|
const kinds = {};
|
|
212
231
|
for (const l of live) { const k = phaseInfo(l.phase).key; kinds[k] = (kinds[k] || 0) + 1; }
|
|
232
|
+
const KIND_WORDS = { working: "working", reviewing: "reviewing", landing: "merging" }; // vocabulary-ok: phase key, renamed with the status model // retired-name-ok: phase key, renamed with the status model
|
|
213
233
|
const liveSub = live.length
|
|
214
|
-
?
|
|
215
|
-
: d.running ? "every
|
|
234
|
+
? Object.keys(KIND_WORDS).filter((k) => kinds[k]).map((k) => kinds[k] + " " + KIND_WORDS[k]).join(" · ")
|
|
235
|
+
: d.running ? "every role is between runs" : "the team is stopped";
|
|
216
236
|
const t = today && today.totals;
|
|
217
237
|
const plans = (d.plans || []).length;
|
|
218
238
|
const bugs = (d.bugs || []).length;
|
|
219
239
|
const qs = (d.questions || []).length;
|
|
220
240
|
paintPanel("stats", [
|
|
221
|
-
{ label: "In flight", icon: "bolt", value: live.length + " <small>of " + loops.length + "
|
|
222
|
-
{ label: "
|
|
241
|
+
{ label: "In flight", icon: "bolt", value: live.length + " <small>of " + loops.length + " roles</small>", sub: esc(liveSub) },
|
|
242
|
+
{ label: "Merged today", icon: "merge", act: "view", arg: "history", hint: "See every run in History",
|
|
223
243
|
value: t ? String(t.commits) + " <small>" + agree(t.commits, "commit", "commits") + "</small>" : "—",
|
|
224
244
|
sub: t ? esc(plural(t.featuresDone, "feature") + " done · " + plural(t.bugsFixed, "bug") + " fixed · " + workSplit(t)) : "counting…" },
|
|
225
|
-
{ label: "
|
|
226
|
-
value: t ? String(t.
|
|
245
|
+
{ label: "Runs today", icon: "refresh", act: "view", arg: "usage", hint: "See usage per day and per role",
|
|
246
|
+
value: t ? String(t.runs) + " <small>" + agree(t.runs, "run", "runs") + "</small>" : "—",
|
|
227
247
|
sub: t ? esc(fmtTokens(t.tokensOut) + " output tokens" + (t.costUsd > 0 ? " · " + fmtUsd(t.costUsd) : "")) : "counting…" },
|
|
228
248
|
{ label: "Backlog", icon: "inbox", act: "backlog", hint: "Jump to the backlog", value: String(plans + bugs) + " <small>open</small>",
|
|
229
249
|
sub: esc(plural(plans, "plan") + " · " + plural(bugs, "bug") + (qs ? " · " + plural(qs, "question") : "")) },
|
|
@@ -245,6 +265,8 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
245
265
|
// guard so a poll does not repaint over the typed title.
|
|
246
266
|
let backlogNew = null;
|
|
247
267
|
let backlogNewPainted = false;
|
|
268
|
+
// Names of the blocked series whose parts the Plans tab currently shows.
|
|
269
|
+
let openSeries = new Set();
|
|
248
270
|
function setBacklogTab(tab) {
|
|
249
271
|
backlogTab = tab;
|
|
250
272
|
store("backlog", tab);
|
|
@@ -259,6 +281,10 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
259
281
|
backlogNewPainted = false;
|
|
260
282
|
renderBacklog(backlogStatus);
|
|
261
283
|
}
|
|
284
|
+
function toggleBacklogSeries(name) {
|
|
285
|
+
if (openSeries.has(name)) openSeries.delete(name); else openSeries.add(name);
|
|
286
|
+
renderBacklog(backlogStatus);
|
|
287
|
+
}
|
|
262
288
|
function endBacklogNew() {
|
|
263
289
|
backlogNew = null;
|
|
264
290
|
backlogNewPainted = false;
|
|
@@ -276,10 +302,10 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
276
302
|
renderBacklog(backlogStatus);
|
|
277
303
|
}
|
|
278
304
|
const BACKLOG = [
|
|
279
|
-
["questions", "Questions", "question", "indigo", "No open questions", "
|
|
280
|
-
["plans", "Plans", "plan", "blue", "Nothing planned", "The plan
|
|
305
|
+
["questions", "Questions", "question", "indigo", "No open questions", "Roles post here when they need a decision from you."],
|
|
306
|
+
["plans", "Plans", "plan", "blue", "Nothing planned", "The plan role files features in PLANS.md."],
|
|
281
307
|
["bugs", "Bugs", "bug", "red", "No open bugs", "Bugs filed in BUGS.md show up here."],
|
|
282
|
-
["queued", "
|
|
308
|
+
["queued", "Inbox", "chat", "gray", "Inbox empty", "Messages you send wait here until their role's next run."], // vocabulary-ok: internal tab key
|
|
283
309
|
];
|
|
284
310
|
function renderBacklog(d) {
|
|
285
311
|
backlogStatus = d;
|
|
@@ -295,8 +321,16 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
295
321
|
let html;
|
|
296
322
|
if (!list.length) html = "<div class='empty'><strong>" + esc(emptyTitle) + "</strong>" + esc(emptyText) + "</div>";
|
|
297
323
|
else if (key === "queued") {
|
|
324
|
+
// Each role's queue head carries no Move to top (a no-op there); every later row of
|
|
325
|
+
// that role does. The rows arrive grouped by role — director first, then each loop in
|
|
326
|
+
// id order — so a Set of roles already painted marks each group's first row.
|
|
327
|
+
const paintedFirst = new Set();
|
|
298
328
|
html = list.map((q) => {
|
|
299
329
|
const addr = "data-file='" + esc(q.file) + "' data-role='" + esc(q.role) + "'";
|
|
330
|
+
// Mark this role's group head before the edit-row early return, so an edited head row
|
|
331
|
+
// still leaves the Move to top button on that role's later rows.
|
|
332
|
+
const alreadyFirst = paintedFirst.has(q.role);
|
|
333
|
+
paintedFirst.add(q.role);
|
|
300
334
|
// The row being edited swaps its preview for a textarea and a Save/Cancel pair; the
|
|
301
335
|
// address stays the queue-file basename, the same one /api/prompt-cancel uses.
|
|
302
336
|
if (queuedEdit && q.file === queuedEdit.file && q.role === queuedEdit.role) {
|
|
@@ -306,10 +340,14 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
306
340
|
"<button type='button' class='btn btn-sm rowaction' data-action='prompteditsave' " + addr + ">Save</button>" +
|
|
307
341
|
"<button type='button' class='btn btn-sm rowaction' data-action='prompteditcancel' " + addr + ">Cancel</button></div>";
|
|
308
342
|
}
|
|
343
|
+
const topBtn = alreadyFirst
|
|
344
|
+
? "<button type='button' class='btn btn-sm rowaction' data-action='prompttop' " + addr + ">Move to top</button>"
|
|
345
|
+
: "";
|
|
309
346
|
return "<div class='list-item'><span class='li-icon'>" + icon("chat") + "</span><div class='li-main'><span class='li-title'>" + esc(q.preview) +
|
|
310
|
-
"</span><span class='li-meta'>" + esc((q.role === "director" ? "for the director" : "for the " + q.role + "
|
|
347
|
+
"</span><span class='li-meta'>" + esc((q.role === "director" ? "for the director" : "for the " + roleLabel(q.role) + " role") +
|
|
311
348
|
(q.notBeforeMs && q.notBeforeMs > Date.now() ? " · delivers in " + humanSeconds(Math.round((q.notBeforeMs - Date.now()) / 1000))
|
|
312
349
|
: (q.queuedAtMs ? " · queued " + fmtAgo(q.queuedAtMs) : ""))) + "</span></div>" +
|
|
350
|
+
topBtn +
|
|
313
351
|
"<button type='button' class='btn btn-sm rowaction' data-action='promptcancel' " + addr + ">Cancel</button>" +
|
|
314
352
|
"<button type='button' class='btn btn-sm rowaction' data-action='promptedit' " + addr + ">Edit</button></div>";
|
|
315
353
|
}).join("");
|
|
@@ -319,7 +357,10 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
319
357
|
// the same tab position the click sends): empty for eligible rows so their markup is
|
|
320
358
|
// unchanged, and the Questions tab carries no holds.
|
|
321
359
|
const holds = key === "plans" ? (d.planHolds || []) : key === "bugs" ? (d.bugHolds || []) : [];
|
|
322
|
-
|
|
360
|
+
// Blocked entries (a hold of any kind) paint after the pickable ones under one divider;
|
|
361
|
+
// each row keeps its original data-index so the drawer and Answer still address it.
|
|
362
|
+
const blocked = key === "plans" ? (d.planBlocked || []) : key === "bugs" ? (d.bugBlocked || []) : [];
|
|
363
|
+
const rows = list.map((title, i) => {
|
|
323
364
|
const t = splitTitle(title);
|
|
324
365
|
const mark = holds[i] || "";
|
|
325
366
|
return "<div class='list-item t-" + tone + (open === key + ":" + i ? " active" : "") + "'><span class='li-icon'>" + icon(ic) + "</span>" +
|
|
@@ -327,6 +368,37 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
327
368
|
(t.meta ? "<span class='li-meta'>" + esc(t.meta) + "</span>" : "") +
|
|
328
369
|
(mark ? "<span class='li-meta'>" + esc(mark.trim()) + "</span>" : "") + "</button>" +
|
|
329
370
|
(key === "questions" ? "<button type='button' class='btn btn-sm backloglink' data-file='questions' data-index='" + i + "'>Answer</button>" : "") + "</div>";
|
|
371
|
+
});
|
|
372
|
+
const ready = rows.filter((r, i) => !blocked[i]);
|
|
373
|
+
const series = key === "plans" ? (d.planSeries || []) : [];
|
|
374
|
+
let heldCount = 0;
|
|
375
|
+
const held = [];
|
|
376
|
+
const seen = new Set();
|
|
377
|
+
rows.forEach((r, i) => {
|
|
378
|
+
if (!blocked[i]) return;
|
|
379
|
+
heldCount++;
|
|
380
|
+
const s = series[i];
|
|
381
|
+
if (!s) { held.push(r); return; }
|
|
382
|
+
if (seen.has(s.name)) return;
|
|
383
|
+
seen.add(s.name);
|
|
384
|
+
held.push("<div class='list-item list-series'><button type='button' class='li-open' data-action='seriestoggle' data-series='" + esc(s.name) + "'><span class='li-title'>" + esc(s.label) + "</span><span class='li-meta'>blocked: each waits on an earlier part</span></button></div>");
|
|
385
|
+
if (openSeries.has(s.name)) rows.forEach((r2, j) => { if (blocked[j] && series[j] && series[j].name === s.name) held.push(r2); });
|
|
386
|
+
});
|
|
387
|
+
html = ready.join("") + (heldCount ? "<div class='list-item list-divider'><span class='li-meta'>Blocked (" + heldCount + ")</span></div>" + held.join("") : "");
|
|
388
|
+
}
|
|
389
|
+
const answered = key === "questions" ? (d.answeredQuestions || []) : [];
|
|
390
|
+
if (answered.length) {
|
|
391
|
+
html += "<div class='list-item list-divider'><span class='li-meta'>Answered recently</span></div>" + answered.map((a, i) =>
|
|
392
|
+
"<div class='list-item t-gray" + (openEntryKey() === "answered:" + i ? " active" : "") + "'><span class='li-icon'>" + icon("check") + "</span><button type='button' class='li-open backloglink' data-file='answered' data-index='" + i + "'><span class='li-title'>" + esc(a.title) + "</span>" +
|
|
393
|
+
(a.decision ? "<span class='li-meta'>" + esc(a.decision) + "</span>" : "") + "</button></div>").join("");
|
|
394
|
+
}
|
|
395
|
+
const finished = key === "plans" ? (d.donePlans || []) : key === "bugs" ? (d.fixedBugs || []) : [];
|
|
396
|
+
if (finished.length) {
|
|
397
|
+
html += "<div class='list-item list-divider'><span class='li-meta'>" + (key === "plans" ? "Done recently" : "Fixed recently") + "</span></div>" + finished.map((f, i) => {
|
|
398
|
+
const doneFile = key === "plans" ? "done" : "fixed";
|
|
399
|
+
return "<div class='list-item t-gray" + (openEntryKey() === doneFile + ":" + i ? " active" : "") + "'><span class='li-icon'>" + icon("check") + "</span>" +
|
|
400
|
+
"<button type='button' class='li-open backloglink' data-file='" + doneFile + "' data-index='" + i + "'><span class='li-title'>" + esc(f.title) + "</span>" +
|
|
401
|
+
(f.completedOn ? "<span class='li-meta'>" + esc((key === "plans" ? "done " : "fixed ") + f.completedOn) + "</span>" : "") + "</button></div>";
|
|
330
402
|
}).join("");
|
|
331
403
|
}
|
|
332
404
|
if (key === "plans" || key === "bugs") {
|
|
@@ -350,6 +422,8 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
350
422
|
if (b) setBacklogTab(b.dataset.tab);
|
|
351
423
|
});
|
|
352
424
|
$("backlog").addEventListener("click", async (ev) => {
|
|
425
|
+
const ser = clickClosest(ev, "button[data-action='seriestoggle']");
|
|
426
|
+
if (ser) { toggleBacklogSeries(ser.dataset.series); return; }
|
|
353
427
|
const fresh = clickClosest(ev, "button[data-action='backlognew']");
|
|
354
428
|
if (fresh) { beginBacklogNew(fresh.dataset.kind); return; }
|
|
355
429
|
if (clickClosest(ev, "button[data-action='backlognewcancel']")) { endBacklogNew(); return; }
|
|
@@ -359,16 +433,26 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
359
433
|
const title = input ? input.value.trim() : "";
|
|
360
434
|
if (!title) return;
|
|
361
435
|
const bodyBox = $("backlognewbody");
|
|
362
|
-
await postAction("/api/backlog-file", { kind: freshSave.dataset.kind, title, body: bodyBox ? bodyBox.value : "" }, (d) => "Filed: " + d.title);
|
|
436
|
+
await postAction("/api/backlog-file", { kind: freshSave.dataset.kind, title, body: bodyBox ? bodyBox.value : "" }, (d) => "Filed: " + d.title + uncommittedTail(d));
|
|
363
437
|
endBacklogNew();
|
|
364
438
|
return;
|
|
365
439
|
}
|
|
440
|
+
const top = clickClosest(ev, "button.rowaction[data-action='prompttop']");
|
|
441
|
+
if (top) {
|
|
442
|
+
await postAction("/api/prompt-top", { role: top.dataset.role, file: top.dataset.file }, (d) =>
|
|
443
|
+
d && d.status === "topped"
|
|
444
|
+
? "Moved to the front: " + (d.preview || "")
|
|
445
|
+
: d && d.status === "already_first"
|
|
446
|
+
? "Already first"
|
|
447
|
+
: "No longer in the inbox — " + (top.dataset.role ? roleLabel(top.dataset.role) : "the director") + " already took it");
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
366
450
|
const cancel = clickClosest(ev, "button.rowaction[data-action='promptcancel']");
|
|
367
451
|
if (cancel) {
|
|
368
452
|
await postAction("/api/prompt-cancel", { role: cancel.dataset.role, file: cancel.dataset.file }, (d) =>
|
|
369
453
|
d && d.status === "cancelled"
|
|
370
454
|
? "Cancelled: " + (d.preview || "")
|
|
371
|
-
: "No longer
|
|
455
|
+
: "No longer in the inbox — " + (cancel.dataset.role ? roleLabel(cancel.dataset.role) : "the director") + " already took it");
|
|
372
456
|
return;
|
|
373
457
|
}
|
|
374
458
|
const edit = clickClosest(ev, "button.rowaction[data-action='promptedit']");
|
|
@@ -376,7 +460,7 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
376
460
|
try {
|
|
377
461
|
const d = await getJson("/api/prompt-text?role=" + encodeURIComponent(edit.dataset.role) + "&file=" + encodeURIComponent(edit.dataset.file));
|
|
378
462
|
if (d && d.status === "gone") {
|
|
379
|
-
showFlash("No longer
|
|
463
|
+
showFlash("No longer in the inbox — " + (edit.dataset.role ? roleLabel(edit.dataset.role) : "the director") + " already took it");
|
|
380
464
|
refresh();
|
|
381
465
|
} else {
|
|
382
466
|
beginQueuedEdit(edit.dataset.role, edit.dataset.file, (d && d.text) || "");
|
|
@@ -394,7 +478,7 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
394
478
|
await postAction("/api/prompt-edit", { role: save.dataset.role, file: save.dataset.file, text }, (d) =>
|
|
395
479
|
d && d.status === "edited"
|
|
396
480
|
? "Edited: " + (d.newText || "")
|
|
397
|
-
: "No longer
|
|
481
|
+
: "No longer in the inbox — " + (save.dataset.role ? roleLabel(save.dataset.role) : "the director") + " already took it");
|
|
398
482
|
endQueuedEdit();
|
|
399
483
|
return;
|
|
400
484
|
}
|
|
@@ -411,12 +495,14 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
411
495
|
// The Activity card's own narrowing, beside the Notable/All segmented choice: a Loop picker
|
|
412
496
|
// and a text box filter the events the card carries, and — once a filter is set — a debounced
|
|
413
497
|
// /api/logs search reaches past that carried window into the log's recent slice (the same
|
|
414
|
-
// loop/match rule 'tumwater logs --role'/'--grep' apply to the CLI feed)
|
|
415
|
-
//
|
|
498
|
+
// loop/match rule 'tumwater logs --role'/'--grep' apply to the CLI feed), while the tally
|
|
499
|
+
// chips select one exact event type the way 'tumwater logs --type <type>' does. All persist
|
|
500
|
+
// like the Notable/All choice, so a reload keeps the operator's scope.
|
|
416
501
|
let feedFilter = recall("feed") === "all" ? "all" : "notable";
|
|
417
502
|
let feedRole = recall("feed-role") || "";
|
|
503
|
+
let feedType = recall("feed-type") || "";
|
|
418
504
|
let feedText = recall("feed-grep") || "";
|
|
419
|
-
const KINDS = { landing: ["merge", "green"], problem: ["fail", "red"], attention: ["question", "indigo"], info: ["info", "blue"], routine: ["dot", "gray"] };
|
|
505
|
+
const KINDS = { landing: ["merge", "green"], problem: ["fail", "red"], attention: ["question", "indigo"], info: ["info", "blue"], routine: ["dot", "gray"] }; // retired-name-ok: event kind key, renamed with the status model
|
|
420
506
|
// Server-backed search state. feedResults is the /api/logs slice once it answers (newest
|
|
421
507
|
// first, the shape renderFeed consumes), null while the card paints its carried window; the
|
|
422
508
|
// debounce feedTimer keeps each keystroke from firing a request; feedSeq numbers requests
|
|
@@ -429,7 +515,9 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
429
515
|
let feedError = "";
|
|
430
516
|
let feedQueryKey = "";
|
|
431
517
|
// Only the non-empty filters ride the query: an absent grep/role means unfiltered server-side,
|
|
432
|
-
// exactly as handleLogs reads them.
|
|
518
|
+
// exactly as handleLogs reads them. The type is deliberately NOT carried: the server slice must
|
|
519
|
+
// still deliver every type so the tally chips can be built from it — the type is a local
|
|
520
|
+
// narrowing on the rows in hand, like the Loop picker, not a query narrowing.
|
|
433
521
|
function feedLogsQuery() {
|
|
434
522
|
const params = [];
|
|
435
523
|
if (feedText) params.push("grep=" + encodeURIComponent(feedText));
|
|
@@ -460,12 +548,36 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
460
548
|
}
|
|
461
549
|
if (lastStatus) renderFeed(lastStatus);
|
|
462
550
|
}
|
|
463
|
-
// The Loop picker's options follow the
|
|
464
|
-
// poll with no roster change repaints nothing. A role the
|
|
551
|
+
// The Loop picker's options follow the team's role list (custom loops included), keyed so a
|
|
552
|
+
// poll with no roster change repaints nothing. A role the team dropped falls back to All
|
|
465
553
|
// loops and persists, exactly as syncRolePicker does for the report and History pickers.
|
|
466
554
|
function syncFeedRoles(d) {
|
|
467
555
|
if (!d) return;
|
|
468
|
-
feedRole = syncRolePicker("feedrole", (d.
|
|
556
|
+
feedRole = syncRolePicker("feedrole", (d.roles || []).map((l) => l.role), feedRole, "feed-role");
|
|
557
|
+
}
|
|
558
|
+
// An empty feed state: a li.empty with a bold head, a body, and the shared Clear-filter
|
|
559
|
+
// button that ends all three search/error/filter-empty rows — one home for the button's
|
|
560
|
+
// markup so the three cannot drift. The last empty state (nothing notable / no events yet)
|
|
561
|
+
// carries no Clear-filter action, so it stays literal.
|
|
562
|
+
const emptyFeedRow = (head, body) => "<li class='empty'><strong>" + head + "</strong>" + body +
|
|
563
|
+
"<button type='button' class='linkish' data-action='feedclear'>Clear filter</button></li>";
|
|
564
|
+
// A per-type tally for the Activity feed, mirroring the CLI's logs --count display rule
|
|
565
|
+
// (read-only from src/cli/log-commands.ts's sayTally): one N × <type> chip per present event
|
|
566
|
+
// type, count-descending then type-name ascending. A pure function of the rows in hand, so the
|
|
567
|
+
// chips and the <li> rows below cannot drift on which events count; "" for an empty array so
|
|
568
|
+
// empty and empty-state feeds paint no stray chips. Each chip is a button: clicking the
|
|
569
|
+
// currently-selected type clears it, otherwise it selects that type (the active one carries
|
|
570
|
+
// the active class and aria-pressed), the browser twin of 'logs --type <type>'.
|
|
571
|
+
function tallyChips(events, activeType) {
|
|
572
|
+
if (!events.length) return "";
|
|
573
|
+
const counts = new Map();
|
|
574
|
+
for (const e of events) counts.set(e.type, (counts.get(e.type) || 0) + 1);
|
|
575
|
+
const chip = (type, n) => "<button type='button' class='chip mono" + (type === activeType ? " active" : "") +
|
|
576
|
+
"' data-type='" + esc(type) + "' title='" + esc(type) + "'" + (type === activeType ? " aria-pressed='true'" : "") +
|
|
577
|
+
">" + n + " × " + esc(eventTypeLabel(type)) + "</button>";
|
|
578
|
+
return "<div class='feed-tally chips'>" + [...counts.entries()]
|
|
579
|
+
.sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0))
|
|
580
|
+
.map(([type, n]) => chip(type, n)).join("") + "</div>";
|
|
469
581
|
}
|
|
470
582
|
function renderFeed(d) {
|
|
471
583
|
syncFeedRoles(d);
|
|
@@ -480,48 +592,57 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
480
592
|
const notable = feedFilter === "all" ? items : items.filter((it) => eventKind(it) !== "routine");
|
|
481
593
|
const needle = feedText.toLowerCase();
|
|
482
594
|
const local = notable.filter((it) =>
|
|
483
|
-
(!feedRole || it.
|
|
484
|
-
(!needle || (it.
|
|
595
|
+
(!feedRole || it.source === feedRole) &&
|
|
596
|
+
(!needle || (it.source + " " + it.type + " " + it.message).toLowerCase().includes(needle)));
|
|
485
597
|
// The server's /api/logs slice replaces the carried window once it answers; the Notable/All
|
|
486
598
|
// choice still culls it. A failed fetch paints the search-empty state, never a misleading
|
|
487
599
|
// partial window.
|
|
488
600
|
const serverRows = feedResults !== null
|
|
489
601
|
? (feedFilter === "all" ? feedResults : feedResults.filter((it) => eventKind(it) !== "routine"))
|
|
490
602
|
: null;
|
|
491
|
-
|
|
603
|
+
// The type chip narrows whichever slice is in hand (carried window or server slice), so it
|
|
604
|
+
// composes after the Notable/All cull and the role/grep narrowing. The chips are built from
|
|
605
|
+
// the pre-type rows so every present type stays one click away even when the filter empties
|
|
606
|
+
// the feed.
|
|
607
|
+
const preType = feedError ? [] : serverRows !== null ? serverRows : local;
|
|
608
|
+
const shown = feedType ? preType.filter((it) => it.type === feedType) : preType;
|
|
492
609
|
markActive($("feedfilter"), "filter", feedFilter);
|
|
493
|
-
const roles = new Set((d.
|
|
494
|
-
paintPanel("feed", shown.length
|
|
610
|
+
const roles = new Set((d.roles || []).map((l) => l.role));
|
|
611
|
+
paintPanel("feed", (preType.length ? tallyChips(preType, feedType) : "") + (shown.length
|
|
495
612
|
? shown.map((it) => {
|
|
496
613
|
const kind = eventKind(it);
|
|
497
|
-
const tone = it.type === "
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
614
|
+
const tone = it.type === "run_end" && kind === "problem" ? resultInfo(it.result).tone : KINDS[kind][1];
|
|
615
|
+
const whoLabel = it.source === "harness" ? "tumwater" : roleLabel(it.source); // vocabulary-ok: the harness event source
|
|
616
|
+
const who = roles.has(it.source) // vocabulary-ok: event field name
|
|
617
|
+
? "<button type='button' class='linkish feed-loop' data-open='" + esc(it.source) + "'>" + esc(whoLabel) + "</button>" // vocabulary-ok: CSS class and event field
|
|
618
|
+
: "<span class='feed-loop'>" + esc(whoLabel) + "</span>"; // vocabulary-ok: CSS class
|
|
501
619
|
return "<li class='feed-item k-" + kind + "'><span class='feed-icon t-" + tone + "'>" + icon(KINDS[kind][0]) + "</span>" +
|
|
502
620
|
"<div class='feed-msg clamp2' title='" + esc(it.message) + "'>" + who + esc(it.message) + "</div>" +
|
|
503
621
|
"<time class='feed-time' title='" + esc(fmtClock(it.ts)) + "'>" + esc(fmtAgo(it.ts)) + "</time></li>";
|
|
504
622
|
}).join("")
|
|
505
623
|
: feedResults !== null
|
|
506
|
-
? "
|
|
507
|
-
"Widen the Search or Loop picker to look further back. " +
|
|
508
|
-
"<button type='button' class='linkish' data-action='feedclear'>Clear filter</button></li>"
|
|
624
|
+
? emptyFeedRow("No events match this filter in the log's recent slice", "Widen the Search or Role picker, or clear the type chip, to look further back. ")
|
|
509
625
|
: feedError
|
|
510
|
-
? "
|
|
511
|
-
|
|
512
|
-
"
|
|
513
|
-
: notable.length
|
|
514
|
-
? "<li class='empty'><strong>No recent events match this filter</strong>" +
|
|
515
|
-
"Clear the Loop or Search box to see the recent feed again. " +
|
|
516
|
-
"<button type='button' class='linkish' data-action='feedclear'>Clear filter</button></li>"
|
|
626
|
+
? emptyFeedRow("Search unavailable", esc(feedError) + " — the running server may predate the /api/logs endpoint. ")
|
|
627
|
+
: notable.length || preType.length
|
|
628
|
+
? emptyFeedRow("No recent events match this filter", "Clear the Role, Search, or type chip to see the recent feed again. ")
|
|
517
629
|
: "<li class='empty'><strong>" + (items.length ? "Nothing notable lately" : "No events yet") + "</strong>" +
|
|
518
|
-
(items.length ? "
|
|
630
|
+
(items.length ? "Merges, failures, and questions show up here. All events has every run start and check." : "Events appear here as the roles work.") + "</li>"));
|
|
631
|
+
}
|
|
632
|
+
// The single state change both the chip click and Clear filter use: select a type (stored so
|
|
633
|
+
// a reload keeps the scope), or clear it by passing "".
|
|
634
|
+
function setFeedType(type) {
|
|
635
|
+
feedType = type;
|
|
636
|
+
store("feed-type", type);
|
|
637
|
+
if (lastStatus) renderFeed(lastStatus);
|
|
519
638
|
}
|
|
520
639
|
function clearFeedFilter() {
|
|
521
640
|
feedRole = "";
|
|
522
641
|
feedText = "";
|
|
642
|
+
feedType = "";
|
|
523
643
|
store("feed-role", "");
|
|
524
644
|
store("feed-grep", "");
|
|
645
|
+
store("feed-type", "");
|
|
525
646
|
if ($("feedrole")) $("feedrole").value = "";
|
|
526
647
|
if ($("feedgrep")) $("feedgrep").value = "";
|
|
527
648
|
if (lastStatus) renderFeed(lastStatus);
|
|
@@ -549,17 +670,20 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
549
670
|
if (lastStatus) renderFeed(lastStatus);
|
|
550
671
|
});
|
|
551
672
|
// The empty state's #feed is static markup, so its delegated listener registers at load like
|
|
552
|
-
// the #feedfilter one above.
|
|
673
|
+
// the #feedfilter one above. A chip click whose type matches the current feedType clears it
|
|
674
|
+
// (toggle off), otherwise it selects that type — the same toggle the Notable/All control uses.
|
|
553
675
|
$("feed").addEventListener("click", (ev) => {
|
|
554
|
-
if (clickClosest(ev, "button[data-action='feedclear']")) clearFeedFilter();
|
|
676
|
+
if (clickClosest(ev, "button[data-action='feedclear']")) { clearFeedFilter(); return; }
|
|
677
|
+
const b = clickClosest(ev, "button[data-type]");
|
|
678
|
+
if (b) setFeedType(b.dataset.type === feedType ? "" : b.dataset.type);
|
|
555
679
|
});
|
|
556
680
|
// activity:end
|
|
557
681
|
|
|
558
|
-
function
|
|
559
|
-
const loops = d.
|
|
560
|
-
const sub = plural(loops.length, "
|
|
682
|
+
function renderTeam(d) {
|
|
683
|
+
const loops = d.roles || [];
|
|
684
|
+
const sub = plural(loops.length, "role") + " · " + (d.running ? loops.filter((l) => phaseInfo(l.phase).live).length + " in flight" : "the team is stopped") +
|
|
561
685
|
(d.paused ? " · paused" : "");
|
|
562
|
-
if ($("
|
|
686
|
+
if ($("teamsub").textContent !== sub) $("teamsub").textContent = sub;
|
|
563
687
|
renderStats(d);
|
|
564
688
|
renderLoops(d);
|
|
565
689
|
renderBacklog(d);
|