tumwater 0.1.3 → 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 +63 -28
- 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-eligibility.js +332 -43
- package/dist/src/backlog/backlog-md.js +79 -17
- 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 +178 -441
- 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 +32 -30
- package/dist/src/{brief-edit.js → brief/brief-edit.js} +12 -11
- package/dist/src/{brief.js → brief/brief.js} +33 -8
- 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 +13 -11
- package/dist/src/change/change-render.js +11 -10
- package/dist/src/cli/brief-command.js +9 -7
- package/dist/src/cli/cli-args.js +25 -5
- package/dist/src/cli/cli-command-args.js +43 -10
- package/dist/src/cli/cli-editor.js +13 -1
- package/dist/src/cli/cli-flag-specs.js +31 -6
- package/dist/src/cli/cli-marker-commands.js +13 -10
- package/dist/src/cli/cli-query-commands.js +439 -56
- package/dist/src/cli/cli-repo-gate.js +1 -1
- package/dist/src/cli/cli-run.js +124 -68
- package/dist/src/cli/completion.js +70 -12
- package/dist/src/cli/config-commands.js +14 -11
- package/dist/src/cli/help.js +264 -124
- 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 +13 -224
- package/dist/src/cli.js +78 -36
- 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 +107 -325
- 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 +494 -68
- package/dist/src/doctor/doctor-checks.js +509 -58
- package/dist/src/doctor/doctor-holds.js +139 -78
- 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 +2 -3
- package/dist/src/doctor/doctor-orphans.js +10 -9
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +76 -25
- package/dist/src/doctor/image-probe.js +244 -0
- package/dist/src/doctor/probe.js +64 -0
- package/dist/src/events/event-format.js +219 -106
- 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 +80 -30
- 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 +6 -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 +79 -59
- 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 -716
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +105 -54
- 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 +25 -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 +179 -104
- package/dist/src/history/history-render.js +76 -0
- package/dist/src/history/history.js +29 -70
- package/dist/src/inbox/inbox-attachments.js +66 -10
- 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 +114 -73
- 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 +104 -50
- package/dist/src/loop/loop-recovery.js +15 -15
- package/dist/src/loop/loop-state.js +130 -13
- package/dist/src/loop/loop-tick.js +190 -88
- package/dist/src/loop/loop.js +68 -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 -20
- 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 +113 -45
- package/dist/src/operator/operator-intent.js +74 -53
- package/dist/src/operator/operator-requests.js +52 -52
- package/dist/src/operator/retire.js +47 -28
- package/dist/src/operator/revert.js +71 -0
- package/dist/src/orchestrator/orchestrator-launch.js +74 -14
- package/dist/src/orchestrator/orchestrator-scheduling.js +74 -43
- package/dist/src/orchestrator/orchestrator.js +149 -96
- 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 +124 -38
- 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 +4 -3
- 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 +36 -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 +29 -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 +259 -254
- package/dist/src/report/report-fold.js +353 -0
- package/dist/src/report/report-render.js +235 -44
- package/dist/src/request-timeouts.js +1 -1
- package/dist/src/review/known-flakes.js +34 -14
- package/dist/src/review/protected-edits.js +4 -3
- 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 +55 -34
- 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 +109 -131
- 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 +16 -14
- package/dist/src/scheduling/backoff.js +29 -19
- package/dist/src/scheduling/claims.js +15 -8
- package/dist/src/scheduling/once-round.js +11 -11
- package/dist/src/scheduling/quiet-hours.js +25 -32
- package/dist/src/scheduling/scheduling.js +28 -25
- 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 +72 -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 +50 -39
- 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 +84 -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} +39 -38
- 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 +142 -25
- 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 +23 -22
- package/dist/src/tick/tick-detail.js +37 -24
- 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 +67 -53
- 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 +75 -10
- package/dist/src/ui/badges.js +113 -69
- package/dist/src/ui/gui/gui-client-boot.js +202 -29
- 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 +94 -37
- package/dist/src/ui/gui/gui-client-labels.js +24 -0
- package/dist/src/ui/gui/gui-client-loops.js +90 -47
- package/dist/src/ui/gui/gui-client-markdown.js +1 -0
- package/dist/src/ui/gui/gui-client-model.js +60 -43
- package/dist/src/ui/gui/gui-client-operator.js +73 -43
- package/dist/src/ui/gui/gui-client-pending.js +34 -31
- package/dist/src/ui/gui/gui-client-report.js +330 -47
- package/dist/src/ui/gui/gui-client-settings.js +126 -18
- package/dist/src/ui/gui/gui-client-sound.js +2 -1
- package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +190 -74
- package/dist/src/ui/gui/gui-client.js +26 -20
- package/dist/src/ui/gui/gui-page.js +18 -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 +176 -102
- package/dist/src/ui/status-payload.js +114 -36
- package/dist/src/ui/status-render.js +121 -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 +9 -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 +61 -57
- package/dist/src/ui/tui/tui-history.js +12 -7
- package/dist/src/ui/tui/tui-input.js +44 -50
- package/dist/src/ui/tui/tui-keymap.js +1 -0
- package/dist/src/ui/tui/tui-keys.js +87 -1821
- 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 +52 -28
- package/dist/src/ui/tui/tui-panes.js +294 -0
- package/dist/src/ui/tui/tui.js +44 -26
- package/dist/src/verdict/fix-claim.js +56 -33
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +43 -11
- 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 -56
- package/dist/src/landing/landing-slot.js +0 -256
- 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,35 +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.");
|
|
84
95
|
}
|
|
85
96
|
// The run --for deadline: the payload's preformatted runWindowBadge, like the disk row.
|
|
86
97
|
if (d.runWindowBadge) {
|
|
87
98
|
rows += row("blue", icon("info"), esc(badgeLabel(d.runWindowBadge)),
|
|
88
99
|
d.scheduledStopAt !== undefined && d.scheduledStopAt === d.runStopsAt
|
|
89
|
-
? "A scheduled stop (tumwater stop --in) — the
|
|
90
|
-
: "
|
|
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.");
|
|
91
102
|
}
|
|
92
103
|
// The configured quiet-hours window ("Quiet hours … part 2/2"): the schedule as
|
|
93
104
|
// standing information, amber while the local clock is inside it — the same
|
|
94
105
|
// inside/outside wording the TUI/status header's quietBadge carries, derived from the
|
|
95
106
|
// payload's raw quietHours/inQuietHours fields (the active case also raises the
|
|
96
|
-
//
|
|
107
|
+
// team-alerts quiet alert above).
|
|
97
108
|
if (d.quietHours) {
|
|
98
109
|
const end = String(d.quietHours).split("-")[1] || "";
|
|
99
110
|
rows += row(d.inQuietHours ? "amber" : "", icon("pause"),
|
|
100
111
|
d.inQuietHours ? "Quiet until " + esc(end) : "Quiet " + esc(d.quietHours),
|
|
101
|
-
"Quiet hours —
|
|
112
|
+
"Quiet hours — roles start no new runs during this local-time window; the director keeps running your messages");
|
|
102
113
|
}
|
|
103
114
|
// New-project bootstrap (plans/work-ratio.md, part 2/2): the payload's preformatted
|
|
104
115
|
// bootstrapBadge, upcased for the row's label style — the same reading the TUI/status
|
|
@@ -106,7 +117,7 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
106
117
|
if (d.bootstrapBadge) {
|
|
107
118
|
rows += row("blue", icon("info"),
|
|
108
119
|
esc(badgeLabel(d.bootstrapBadge)),
|
|
109
|
-
"New-project bootstrap —
|
|
120
|
+
"New-project bootstrap — maintenance roles wait until enough plans are done; plan, feature and director keep running");
|
|
110
121
|
}
|
|
111
122
|
}
|
|
112
123
|
paintPanel("statuschips", rows);
|
|
@@ -127,7 +138,7 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
127
138
|
// i * 2000 spacing exists for the test path, which passes a stepped clock so each fresh
|
|
128
139
|
// alert clears the window and sounds its own cue. The keys seen now are kept for the next
|
|
129
140
|
// poll's diff; lastNeedsYouKeys starts null, so a page opened onto an already-alerting
|
|
130
|
-
//
|
|
141
|
+
// team cues once.
|
|
131
142
|
let lastNeedsYouKeys = null;
|
|
132
143
|
function cueNewNeedsYou(alerts, now) {
|
|
133
144
|
const fresh = newNeedsYouKeys(lastNeedsYouKeys, alerts);
|
|
@@ -144,7 +155,7 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
144
155
|
// from under a click in progress.
|
|
145
156
|
function alertParts(a, restartable) {
|
|
146
157
|
// A stale-build alert's refresh icon IS the restart button (PLANS.md 2026-09-30): pressing
|
|
147
|
-
// it forces the
|
|
158
|
+
// it forces the team's self-redeploy onto main's head now instead of waiting out the
|
|
148
159
|
// cooldown. Only the build alert carries it, and only while the build is actually stale —
|
|
149
160
|
// a fresh build's icon stays inert decoration.
|
|
150
161
|
const glyph = icon(ALERT_ICONS[a.key] || "info");
|
|
@@ -202,7 +213,7 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
202
213
|
todayAt = Date.now();
|
|
203
214
|
} catch { /* keep the last numbers */ }
|
|
204
215
|
todayLoading = false;
|
|
205
|
-
if (lastStatus && activeView === "
|
|
216
|
+
if (lastStatus && activeView === "team") renderStats(lastStatus);
|
|
206
217
|
}
|
|
207
218
|
// stats:start
|
|
208
219
|
function statTile(t) {
|
|
@@ -211,27 +222,28 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
211
222
|
"<span class='stat-sub'>" + t.sub + "</span>" + (t.meter || "") + (t.act ? "</button>" : "</div>");
|
|
212
223
|
}
|
|
213
224
|
function renderStats(d) {
|
|
214
|
-
const loops = d.
|
|
225
|
+
const loops = d.roles || [];
|
|
215
226
|
// The running set (phaseInfo.live), not the permit-holder inFlight: the exempt running
|
|
216
227
|
// director is genuinely in flight, so the tile must count it even though it holds no
|
|
217
228
|
// maxConcurrent permit (BUGS.md 2026-10-06).
|
|
218
229
|
const live = loops.filter((l) => phaseInfo(l.phase).live);
|
|
219
230
|
const kinds = {};
|
|
220
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
|
|
221
233
|
const liveSub = live.length
|
|
222
|
-
?
|
|
223
|
-
: 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";
|
|
224
236
|
const t = today && today.totals;
|
|
225
237
|
const plans = (d.plans || []).length;
|
|
226
238
|
const bugs = (d.bugs || []).length;
|
|
227
239
|
const qs = (d.questions || []).length;
|
|
228
240
|
paintPanel("stats", [
|
|
229
|
-
{ label: "In flight", icon: "bolt", value: live.length + " <small>of " + loops.length + "
|
|
230
|
-
{ 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",
|
|
231
243
|
value: t ? String(t.commits) + " <small>" + agree(t.commits, "commit", "commits") + "</small>" : "—",
|
|
232
244
|
sub: t ? esc(plural(t.featuresDone, "feature") + " done · " + plural(t.bugsFixed, "bug") + " fixed · " + workSplit(t)) : "counting…" },
|
|
233
|
-
{ label: "
|
|
234
|
-
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>" : "—",
|
|
235
247
|
sub: t ? esc(fmtTokens(t.tokensOut) + " output tokens" + (t.costUsd > 0 ? " · " + fmtUsd(t.costUsd) : "")) : "counting…" },
|
|
236
248
|
{ label: "Backlog", icon: "inbox", act: "backlog", hint: "Jump to the backlog", value: String(plans + bugs) + " <small>open</small>",
|
|
237
249
|
sub: esc(plural(plans, "plan") + " · " + plural(bugs, "bug") + (qs ? " · " + plural(qs, "question") : "")) },
|
|
@@ -253,6 +265,8 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
253
265
|
// guard so a poll does not repaint over the typed title.
|
|
254
266
|
let backlogNew = null;
|
|
255
267
|
let backlogNewPainted = false;
|
|
268
|
+
// Names of the blocked series whose parts the Plans tab currently shows.
|
|
269
|
+
let openSeries = new Set();
|
|
256
270
|
function setBacklogTab(tab) {
|
|
257
271
|
backlogTab = tab;
|
|
258
272
|
store("backlog", tab);
|
|
@@ -267,6 +281,10 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
267
281
|
backlogNewPainted = false;
|
|
268
282
|
renderBacklog(backlogStatus);
|
|
269
283
|
}
|
|
284
|
+
function toggleBacklogSeries(name) {
|
|
285
|
+
if (openSeries.has(name)) openSeries.delete(name); else openSeries.add(name);
|
|
286
|
+
renderBacklog(backlogStatus);
|
|
287
|
+
}
|
|
270
288
|
function endBacklogNew() {
|
|
271
289
|
backlogNew = null;
|
|
272
290
|
backlogNewPainted = false;
|
|
@@ -284,10 +302,10 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
284
302
|
renderBacklog(backlogStatus);
|
|
285
303
|
}
|
|
286
304
|
const BACKLOG = [
|
|
287
|
-
["questions", "Questions", "question", "indigo", "No open questions", "
|
|
288
|
-
["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."],
|
|
289
307
|
["bugs", "Bugs", "bug", "red", "No open bugs", "Bugs filed in BUGS.md show up here."],
|
|
290
|
-
["queued", "
|
|
308
|
+
["queued", "Inbox", "chat", "gray", "Inbox empty", "Messages you send wait here until their role's next run."], // vocabulary-ok: internal tab key
|
|
291
309
|
];
|
|
292
310
|
function renderBacklog(d) {
|
|
293
311
|
backlogStatus = d;
|
|
@@ -303,8 +321,16 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
303
321
|
let html;
|
|
304
322
|
if (!list.length) html = "<div class='empty'><strong>" + esc(emptyTitle) + "</strong>" + esc(emptyText) + "</div>";
|
|
305
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();
|
|
306
328
|
html = list.map((q) => {
|
|
307
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);
|
|
308
334
|
// The row being edited swaps its preview for a textarea and a Save/Cancel pair; the
|
|
309
335
|
// address stays the queue-file basename, the same one /api/prompt-cancel uses.
|
|
310
336
|
if (queuedEdit && q.file === queuedEdit.file && q.role === queuedEdit.role) {
|
|
@@ -314,10 +340,14 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
314
340
|
"<button type='button' class='btn btn-sm rowaction' data-action='prompteditsave' " + addr + ">Save</button>" +
|
|
315
341
|
"<button type='button' class='btn btn-sm rowaction' data-action='prompteditcancel' " + addr + ">Cancel</button></div>";
|
|
316
342
|
}
|
|
343
|
+
const topBtn = alreadyFirst
|
|
344
|
+
? "<button type='button' class='btn btn-sm rowaction' data-action='prompttop' " + addr + ">Move to top</button>"
|
|
345
|
+
: "";
|
|
317
346
|
return "<div class='list-item'><span class='li-icon'>" + icon("chat") + "</span><div class='li-main'><span class='li-title'>" + esc(q.preview) +
|
|
318
|
-
"</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") +
|
|
319
348
|
(q.notBeforeMs && q.notBeforeMs > Date.now() ? " · delivers in " + humanSeconds(Math.round((q.notBeforeMs - Date.now()) / 1000))
|
|
320
349
|
: (q.queuedAtMs ? " · queued " + fmtAgo(q.queuedAtMs) : ""))) + "</span></div>" +
|
|
350
|
+
topBtn +
|
|
321
351
|
"<button type='button' class='btn btn-sm rowaction' data-action='promptcancel' " + addr + ">Cancel</button>" +
|
|
322
352
|
"<button type='button' class='btn btn-sm rowaction' data-action='promptedit' " + addr + ">Edit</button></div>";
|
|
323
353
|
}).join("");
|
|
@@ -327,7 +357,10 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
327
357
|
// the same tab position the click sends): empty for eligible rows so their markup is
|
|
328
358
|
// unchanged, and the Questions tab carries no holds.
|
|
329
359
|
const holds = key === "plans" ? (d.planHolds || []) : key === "bugs" ? (d.bugHolds || []) : [];
|
|
330
|
-
|
|
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) => {
|
|
331
364
|
const t = splitTitle(title);
|
|
332
365
|
const mark = holds[i] || "";
|
|
333
366
|
return "<div class='list-item t-" + tone + (open === key + ":" + i ? " active" : "") + "'><span class='li-icon'>" + icon(ic) + "</span>" +
|
|
@@ -335,6 +368,37 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
335
368
|
(t.meta ? "<span class='li-meta'>" + esc(t.meta) + "</span>" : "") +
|
|
336
369
|
(mark ? "<span class='li-meta'>" + esc(mark.trim()) + "</span>" : "") + "</button>" +
|
|
337
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>";
|
|
338
402
|
}).join("");
|
|
339
403
|
}
|
|
340
404
|
if (key === "plans" || key === "bugs") {
|
|
@@ -358,6 +422,8 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
358
422
|
if (b) setBacklogTab(b.dataset.tab);
|
|
359
423
|
});
|
|
360
424
|
$("backlog").addEventListener("click", async (ev) => {
|
|
425
|
+
const ser = clickClosest(ev, "button[data-action='seriestoggle']");
|
|
426
|
+
if (ser) { toggleBacklogSeries(ser.dataset.series); return; }
|
|
361
427
|
const fresh = clickClosest(ev, "button[data-action='backlognew']");
|
|
362
428
|
if (fresh) { beginBacklogNew(fresh.dataset.kind); return; }
|
|
363
429
|
if (clickClosest(ev, "button[data-action='backlognewcancel']")) { endBacklogNew(); return; }
|
|
@@ -367,16 +433,26 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
367
433
|
const title = input ? input.value.trim() : "";
|
|
368
434
|
if (!title) return;
|
|
369
435
|
const bodyBox = $("backlognewbody");
|
|
370
|
-
await postAction("/api/backlog-file", { kind: freshSave.dataset.kind, title, body: bodyBox ? bodyBox.value : "" }, (d) => "Filed: " + d.title + (d
|
|
436
|
+
await postAction("/api/backlog-file", { kind: freshSave.dataset.kind, title, body: bodyBox ? bodyBox.value : "" }, (d) => "Filed: " + d.title + uncommittedTail(d));
|
|
371
437
|
endBacklogNew();
|
|
372
438
|
return;
|
|
373
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
|
+
}
|
|
374
450
|
const cancel = clickClosest(ev, "button.rowaction[data-action='promptcancel']");
|
|
375
451
|
if (cancel) {
|
|
376
452
|
await postAction("/api/prompt-cancel", { role: cancel.dataset.role, file: cancel.dataset.file }, (d) =>
|
|
377
453
|
d && d.status === "cancelled"
|
|
378
454
|
? "Cancelled: " + (d.preview || "")
|
|
379
|
-
: "No longer
|
|
455
|
+
: "No longer in the inbox — " + (cancel.dataset.role ? roleLabel(cancel.dataset.role) : "the director") + " already took it");
|
|
380
456
|
return;
|
|
381
457
|
}
|
|
382
458
|
const edit = clickClosest(ev, "button.rowaction[data-action='promptedit']");
|
|
@@ -384,7 +460,7 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
384
460
|
try {
|
|
385
461
|
const d = await getJson("/api/prompt-text?role=" + encodeURIComponent(edit.dataset.role) + "&file=" + encodeURIComponent(edit.dataset.file));
|
|
386
462
|
if (d && d.status === "gone") {
|
|
387
|
-
showFlash("No longer
|
|
463
|
+
showFlash("No longer in the inbox — " + (edit.dataset.role ? roleLabel(edit.dataset.role) : "the director") + " already took it");
|
|
388
464
|
refresh();
|
|
389
465
|
} else {
|
|
390
466
|
beginQueuedEdit(edit.dataset.role, edit.dataset.file, (d && d.text) || "");
|
|
@@ -402,7 +478,7 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
402
478
|
await postAction("/api/prompt-edit", { role: save.dataset.role, file: save.dataset.file, text }, (d) =>
|
|
403
479
|
d && d.status === "edited"
|
|
404
480
|
? "Edited: " + (d.newText || "")
|
|
405
|
-
: "No longer
|
|
481
|
+
: "No longer in the inbox — " + (save.dataset.role ? roleLabel(save.dataset.role) : "the director") + " already took it");
|
|
406
482
|
endQueuedEdit();
|
|
407
483
|
return;
|
|
408
484
|
}
|
|
@@ -419,12 +495,14 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
419
495
|
// The Activity card's own narrowing, beside the Notable/All segmented choice: a Loop picker
|
|
420
496
|
// and a text box filter the events the card carries, and — once a filter is set — a debounced
|
|
421
497
|
// /api/logs search reaches past that carried window into the log's recent slice (the same
|
|
422
|
-
// loop/match rule 'tumwater logs --role'/'--grep' apply to the CLI feed)
|
|
423
|
-
//
|
|
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.
|
|
424
501
|
let feedFilter = recall("feed") === "all" ? "all" : "notable";
|
|
425
502
|
let feedRole = recall("feed-role") || "";
|
|
503
|
+
let feedType = recall("feed-type") || "";
|
|
426
504
|
let feedText = recall("feed-grep") || "";
|
|
427
|
-
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
|
|
428
506
|
// Server-backed search state. feedResults is the /api/logs slice once it answers (newest
|
|
429
507
|
// first, the shape renderFeed consumes), null while the card paints its carried window; the
|
|
430
508
|
// debounce feedTimer keeps each keystroke from firing a request; feedSeq numbers requests
|
|
@@ -437,7 +515,9 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
437
515
|
let feedError = "";
|
|
438
516
|
let feedQueryKey = "";
|
|
439
517
|
// Only the non-empty filters ride the query: an absent grep/role means unfiltered server-side,
|
|
440
|
-
// 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.
|
|
441
521
|
function feedLogsQuery() {
|
|
442
522
|
const params = [];
|
|
443
523
|
if (feedText) params.push("grep=" + encodeURIComponent(feedText));
|
|
@@ -468,12 +548,36 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
468
548
|
}
|
|
469
549
|
if (lastStatus) renderFeed(lastStatus);
|
|
470
550
|
}
|
|
471
|
-
// The Loop picker's options follow the
|
|
472
|
-
// 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
|
|
473
553
|
// loops and persists, exactly as syncRolePicker does for the report and History pickers.
|
|
474
554
|
function syncFeedRoles(d) {
|
|
475
555
|
if (!d) return;
|
|
476
|
-
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>";
|
|
477
581
|
}
|
|
478
582
|
function renderFeed(d) {
|
|
479
583
|
syncFeedRoles(d);
|
|
@@ -488,48 +592,57 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
488
592
|
const notable = feedFilter === "all" ? items : items.filter((it) => eventKind(it) !== "routine");
|
|
489
593
|
const needle = feedText.toLowerCase();
|
|
490
594
|
const local = notable.filter((it) =>
|
|
491
|
-
(!feedRole || it.
|
|
492
|
-
(!needle || (it.
|
|
595
|
+
(!feedRole || it.source === feedRole) &&
|
|
596
|
+
(!needle || (it.source + " " + it.type + " " + it.message).toLowerCase().includes(needle)));
|
|
493
597
|
// The server's /api/logs slice replaces the carried window once it answers; the Notable/All
|
|
494
598
|
// choice still culls it. A failed fetch paints the search-empty state, never a misleading
|
|
495
599
|
// partial window.
|
|
496
600
|
const serverRows = feedResults !== null
|
|
497
601
|
? (feedFilter === "all" ? feedResults : feedResults.filter((it) => eventKind(it) !== "routine"))
|
|
498
602
|
: null;
|
|
499
|
-
|
|
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;
|
|
500
609
|
markActive($("feedfilter"), "filter", feedFilter);
|
|
501
|
-
const roles = new Set((d.
|
|
502
|
-
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
|
|
503
612
|
? shown.map((it) => {
|
|
504
613
|
const kind = eventKind(it);
|
|
505
|
-
const tone = it.type === "
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
|
|
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
|
|
509
619
|
return "<li class='feed-item k-" + kind + "'><span class='feed-icon t-" + tone + "'>" + icon(KINDS[kind][0]) + "</span>" +
|
|
510
620
|
"<div class='feed-msg clamp2' title='" + esc(it.message) + "'>" + who + esc(it.message) + "</div>" +
|
|
511
621
|
"<time class='feed-time' title='" + esc(fmtClock(it.ts)) + "'>" + esc(fmtAgo(it.ts)) + "</time></li>";
|
|
512
622
|
}).join("")
|
|
513
623
|
: feedResults !== null
|
|
514
|
-
? "
|
|
515
|
-
"Widen the Search or Loop picker to look further back. " +
|
|
516
|
-
"<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. ")
|
|
517
625
|
: feedError
|
|
518
|
-
? "
|
|
519
|
-
|
|
520
|
-
"
|
|
521
|
-
: notable.length
|
|
522
|
-
? "<li class='empty'><strong>No recent events match this filter</strong>" +
|
|
523
|
-
"Clear the Loop or Search box to see the recent feed again. " +
|
|
524
|
-
"<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. ")
|
|
525
629
|
: "<li class='empty'><strong>" + (items.length ? "Nothing notable lately" : "No events yet") + "</strong>" +
|
|
526
|
-
(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);
|
|
527
638
|
}
|
|
528
639
|
function clearFeedFilter() {
|
|
529
640
|
feedRole = "";
|
|
530
641
|
feedText = "";
|
|
642
|
+
feedType = "";
|
|
531
643
|
store("feed-role", "");
|
|
532
644
|
store("feed-grep", "");
|
|
645
|
+
store("feed-type", "");
|
|
533
646
|
if ($("feedrole")) $("feedrole").value = "";
|
|
534
647
|
if ($("feedgrep")) $("feedgrep").value = "";
|
|
535
648
|
if (lastStatus) renderFeed(lastStatus);
|
|
@@ -557,17 +670,20 @@ export const GUI_CLIENT_FLEET_JS = String.raw ` // ---- sidebar: project, fleet
|
|
|
557
670
|
if (lastStatus) renderFeed(lastStatus);
|
|
558
671
|
});
|
|
559
672
|
// The empty state's #feed is static markup, so its delegated listener registers at load like
|
|
560
|
-
// 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.
|
|
561
675
|
$("feed").addEventListener("click", (ev) => {
|
|
562
|
-
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);
|
|
563
679
|
});
|
|
564
680
|
// activity:end
|
|
565
681
|
|
|
566
|
-
function
|
|
567
|
-
const loops = d.
|
|
568
|
-
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") +
|
|
569
685
|
(d.paused ? " · paused" : "");
|
|
570
|
-
if ($("
|
|
686
|
+
if ($("teamsub").textContent !== sub) $("teamsub").textContent = sub;
|
|
571
687
|
renderStats(d);
|
|
572
688
|
renderLoops(d);
|
|
573
689
|
renderBacklog(d);
|