tumwater 0.2.0 → 0.3.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 +27 -13
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +8 -9
- package/dist/src/agent/agent-backend.js +8 -6
- package/dist/src/agent/backend-traits.js +42 -25
- package/dist/src/agent/claude/claude-account.js +71 -204
- package/dist/src/agent/claude/claude-args.js +9 -6
- package/dist/src/agent/claude/claude-backend.js +10 -10
- package/dist/src/agent/claude/claude-env.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +4 -3
- package/dist/src/agent/claude/claude-write-guard.js +49 -0
- package/dist/src/agent/claude/write-guard-hook.js +13 -0
- package/dist/src/agent/codex/codex-account.js +119 -0
- package/dist/src/agent/codex/codex-args.js +181 -0
- package/dist/src/agent/codex/codex-backend.js +279 -0
- package/dist/src/agent/codex/codex-catalog.js +97 -0
- package/dist/src/agent/codex/codex-env.js +18 -0
- package/dist/src/agent/codex/codex-hook-trust.js +246 -0
- package/dist/src/agent/codex/codex-patch-paths.js +36 -0
- package/dist/src/agent/codex/codex-rollout.js +179 -0
- package/dist/src/agent/codex/codex-stream.js +402 -0
- package/dist/src/agent/codex/codex-write-guard.js +86 -0
- package/dist/src/agent/codex/write-guard-hook.js +12 -0
- package/dist/src/agent/image-input.js +29 -24
- package/dist/src/agent/notes-shim.js +11 -12
- package/dist/src/agent/opencode/opencode-args.js +0 -1
- package/dist/src/agent/opencode/opencode-backend.js +12 -9
- package/dist/src/agent/opencode/opencode-catalog.js +55 -29
- package/dist/src/agent/opencode/opencode-config.js +19 -14
- package/dist/src/agent/opencode/opencode-env.js +0 -1
- package/dist/src/agent/opencode/opencode-session-id.js +1 -2
- package/dist/src/agent/opencode/opencode-stream.js +1 -1
- package/dist/src/agent/opencode/write-guard-plugin/index.js +48 -0
- package/dist/src/agent/role-notes-write.js +5 -6
- package/dist/src/agent/run-agent.js +465 -448
- package/dist/src/agent/run-settle.js +214 -0
- package/dist/src/agent/session-ledger.js +86 -16
- package/dist/src/agent/subscription-account.js +220 -0
- package/dist/src/agent/write-guard.js +148 -0
- package/dist/src/backlog/backlog-archive.js +246 -55
- package/dist/src/backlog/backlog-cli-view.js +280 -0
- package/dist/src/backlog/backlog-cli.js +85 -274
- package/dist/src/backlog/backlog-commit.js +4 -5
- package/dist/src/backlog/backlog-eligibility.js +175 -242
- package/dist/src/backlog/backlog-index.js +149 -0
- package/dist/src/backlog/backlog-md.js +54 -6
- package/dist/src/backlog/backlog-parts.js +254 -0
- package/dist/src/backlog/backlog-release.js +117 -0
- package/dist/src/backlog/backlog-render.js +27 -0
- package/dist/src/backlog/backlog-structure.js +99 -156
- package/dist/src/backlog/backlog-write.js +40 -127
- package/dist/src/backlog/backlog.js +21 -6
- package/dist/src/backlog/question-answer.js +21 -15
- package/dist/src/baseline/main-baseline.js +89 -20
- package/dist/src/baseline/main-culprit.js +94 -0
- package/dist/src/baseline/main-guardrails.js +209 -0
- package/dist/src/baseline/main-red.js +139 -32
- package/dist/src/baseline/main-revert.js +133 -0
- package/dist/src/baseline/main-watch.js +184 -0
- package/dist/src/brief/brief-edit.js +9 -11
- package/dist/src/brief/brief.js +31 -51
- package/dist/src/budget/budget.js +30 -20
- package/dist/src/budget/fallback-breaker.js +26 -27
- package/dist/src/build/build-check-detect.js +23 -15
- package/dist/src/build/build-check-events.js +25 -6
- package/dist/src/build/build-check-report.js +49 -9
- package/dist/src/build/build-check-scoped.js +18 -11
- package/dist/src/build/build-check.js +2 -1
- package/dist/src/build/build-info.js +2 -3
- package/dist/src/build/build-stage.js +9 -14
- package/dist/src/build/dep-install.js +1 -2
- package/dist/src/change/change-data.js +123 -31
- package/dist/src/change/change-render.js +46 -5
- package/dist/src/cli/brief-command.js +3 -4
- package/dist/src/cli/cli-args.js +118 -33
- package/dist/src/cli/cli-command-args.js +46 -18
- package/dist/src/cli/cli-editor.js +2 -1
- package/dist/src/cli/cli-flag-specs.js +150 -45
- package/dist/src/cli/cli-init.js +51 -0
- package/dist/src/cli/cli-marker-commands.js +4 -4
- package/dist/src/cli/cli-query-commands.js +308 -590
- package/dist/src/cli/cli-repo-gate.js +11 -1
- package/dist/src/cli/cli-role-command.js +420 -0
- package/dist/src/cli/cli-start-summary.js +160 -0
- package/dist/src/cli/{cli-run.js → cli-start.js} +165 -237
- package/dist/src/cli/completion.js +149 -22
- package/dist/src/cli/config-commands.js +41 -18
- package/dist/src/cli/follow-view.js +75 -0
- package/dist/src/cli/help.js +189 -73
- package/dist/src/cli/log-commands.js +148 -82
- package/dist/src/cli/question-commands.js +205 -10
- package/dist/src/cli.js +106 -43
- package/dist/src/collections.js +26 -10
- package/dist/src/concurrency/check-permit.js +20 -3
- package/dist/src/concurrency/lock.js +17 -13
- package/dist/src/concurrency/provider-lease.js +44 -8
- package/dist/src/concurrency/semaphore.js +102 -26
- package/dist/src/config/config-aliases.js +0 -1
- package/dist/src/config/config-editable-keys.js +13 -1
- package/dist/src/config/config-fallbacks.js +93 -56
- package/dist/src/config/config-field-checks.js +40 -10
- package/dist/src/config/config-keys.js +30 -8
- package/dist/src/config/config-live.js +63 -24
- package/dist/src/config/config-request.js +137 -0
- package/dist/src/config/config-schema.js +31 -24
- package/dist/src/config/config-selector-checks.js +123 -0
- package/dist/src/config/config-validation-agents.js +80 -0
- package/dist/src/config/config-validation-model.js +116 -0
- package/dist/src/config/config-validation.js +141 -199
- package/dist/src/config/config-views.js +127 -53
- package/dist/src/config/config-write-core.js +135 -0
- package/dist/src/config/config-write.js +131 -264
- package/dist/src/config/config.js +48 -38
- package/dist/src/config/model-selector.js +34 -34
- package/dist/src/doctor/doctor-backlog-bugs.js +287 -0
- package/dist/src/doctor/doctor-backlog-plans.js +263 -0
- package/dist/src/doctor/doctor-backlog.js +100 -496
- package/dist/src/doctor/doctor-check-names.js +81 -0
- package/dist/src/doctor/doctor-checks.js +52 -579
- package/dist/src/doctor/doctor-claude-checks.js +145 -0
- package/dist/src/doctor/doctor-codex-checks.js +279 -0
- package/dist/src/doctor/doctor-contention-checks.js +380 -0
- package/dist/src/doctor/doctor-holds.js +82 -66
- package/dist/src/doctor/doctor-install.js +2 -1
- package/dist/src/doctor/doctor-model-checks.js +236 -402
- package/dist/src/doctor/doctor-notify.js +3 -4
- package/dist/src/doctor/doctor-opencode-checks.js +162 -0
- package/dist/src/doctor/doctor-orphans.js +182 -63
- package/dist/src/doctor/doctor-project-checks.js +303 -0
- package/dist/src/doctor/doctor.js +121 -77
- package/dist/src/doctor/image-probe.js +21 -8
- package/dist/src/doctor/outcome.js +3 -2
- package/dist/src/doctor/probe.js +7 -11
- package/dist/src/events/event-format.js +181 -86
- package/dist/src/events/event-labels.js +12 -5
- package/dist/src/events/event-legacy.js +39 -15
- package/dist/src/events/event-read.js +39 -39
- package/dist/src/events/event-tally.js +16 -0
- package/dist/src/events/event-window.js +60 -24
- package/dist/src/events/events.js +30 -13
- package/dist/src/events/notify.js +7 -7
- package/dist/src/failure/failure-cluster.js +12 -12
- package/dist/src/failure/failure-data.js +56 -56
- package/dist/src/failure/failure-render.js +31 -23
- package/dist/src/failure/failure-state-change.js +17 -12
- package/dist/src/failure/time-spend.js +99 -35
- package/dist/src/files/files.js +111 -35
- package/dist/src/files/json-files.js +3 -3
- package/dist/src/files/json-object.js +28 -7
- package/dist/src/files/stat-cache.js +2 -2
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +4 -5
- package/dist/src/gates/budget-gates.js +177 -84
- package/dist/src/gates/disk-gate.js +1 -1
- package/dist/src/gates/gate-polls.js +108 -58
- package/dist/src/gates/maintenance-quota.js +10 -13
- package/dist/src/gates/pause-gates.js +28 -10
- package/dist/src/gates/readiness.js +7 -3
- package/dist/src/gates/role-cap-gates.js +8 -8
- package/dist/src/gates/startup-gate.js +94 -32
- package/dist/src/gates/streak-gate.js +54 -30
- package/dist/src/git/commit-message.js +77 -46
- package/dist/src/git/git-diff.js +16 -10
- package/dist/src/git/git-run.js +79 -38
- package/dist/src/git/git.js +36 -34
- package/dist/src/git/slot-salvage.js +253 -0
- package/dist/src/git/slots-state.js +6 -6
- package/dist/src/git/worktree-pool.js +132 -38
- package/dist/src/git/worktree-use.js +4 -11
- package/dist/src/git/worktree.js +94 -37
- package/dist/src/gui/gui-admin-endpoints.js +96 -0
- package/dist/src/gui/gui-args.js +79 -8
- package/dist/src/gui/gui-backlog-commands.js +42 -18
- package/dist/src/gui/gui-backlog-endpoints.js +155 -0
- package/dist/src/gui/gui-command.js +8 -13
- package/dist/src/gui/gui-config-commands.js +94 -72
- package/dist/src/gui/gui-endpoint-commands.js +4 -3
- package/dist/src/gui/gui-endpoint-common.js +4 -4
- package/dist/src/gui/gui-endpoints.js +11 -380
- package/dist/src/gui/gui-history-endpoints.js +301 -0
- package/dist/src/gui/gui-operator-commands.js +108 -13
- package/dist/src/gui/gui-prompt-commands.js +11 -14
- package/dist/src/gui/gui-report-endpoints.js +90 -0
- package/dist/src/gui/gui-server.js +11 -4
- package/dist/src/gui/gui-team-commands.js +7 -7
- package/dist/src/gui/http-body.js +1 -1
- package/dist/src/history/history-data.js +122 -234
- package/dist/src/history/history-queued.js +147 -0
- package/dist/src/history/history-render.js +42 -22
- package/dist/src/history/history.js +193 -49
- package/dist/src/inbox/inbox-attachments.js +29 -30
- package/dist/src/inbox/inbox-cancel.js +10 -11
- package/dist/src/inbox/inbox-edit.js +9 -9
- package/dist/src/inbox/inbox-submit.js +18 -18
- package/dist/src/inbox/inbox-top.js +25 -9
- package/dist/src/inbox/inbox.js +42 -43
- package/dist/src/inbox/pending-prompt.js +26 -26
- package/dist/src/inbox/prompt-commands.js +62 -49
- package/dist/src/inbox/prompt-not-before.js +2 -2
- package/dist/src/init/init-templates.js +1 -2
- package/dist/src/init/init.js +28 -23
- package/dist/src/merge/backlog-conflicts.js +215 -35
- package/dist/src/merge/merge-batch.js +14 -8
- package/dist/src/merge/merge-cancel.js +53 -0
- package/dist/src/merge/merge-check-failures.js +44 -22
- package/dist/src/merge/merge-core.js +81 -32
- package/dist/src/merge/merge-diff.js +5 -4
- package/dist/src/merge/merge-drain.js +150 -11
- package/dist/src/merge/merge-git.js +177 -38
- package/dist/src/merge/merge-pipeline.js +15 -3
- package/dist/src/merge/merge-post-review.js +90 -0
- package/dist/src/merge/merge-queue-render.js +49 -11
- package/dist/src/merge/merge-queue.js +31 -15
- package/dist/src/merge/merge-requeue.js +102 -0
- package/dist/src/merge/merge-slot.js +61 -16
- package/dist/src/merge/merge-stack.js +33 -11
- package/dist/src/merge/merge-to-main.js +86 -27
- package/dist/src/merge/merge-top.js +69 -0
- package/dist/src/merge/merge-verify.js +12 -10
- package/dist/src/notes-cli.js +30 -15
- package/dist/src/operator/operator-commands.js +192 -81
- package/dist/src/operator/operator-intent.js +69 -42
- package/dist/src/operator/operator-requests.js +35 -40
- package/dist/src/operator/retire.js +5 -6
- package/dist/src/operator/revert.js +142 -34
- package/dist/src/orchestrator/orchestrator-launch.js +60 -52
- package/dist/src/orchestrator/orchestrator-poll.js +320 -0
- package/dist/src/orchestrator/orchestrator-scheduling.js +68 -369
- package/dist/src/orchestrator/orchestrator.js +128 -369
- package/dist/src/orchestrator/retention.js +42 -8
- package/dist/src/orchestrator/scheduling-admit.js +116 -0
- package/dist/src/orchestrator/scheduling-scope.js +201 -0
- package/dist/src/orchestrator/scheduling-skip.js +185 -0
- package/dist/src/orchestrator/startup-migration-steps.js +8 -7
- package/dist/src/orchestrator/startup-migrations.js +11 -7
- package/dist/src/paths.js +116 -49
- package/dist/src/pi/command-shape.js +12 -1
- package/dist/src/pi/pi-args.js +6 -4
- package/dist/src/pi/pi-backend.js +6 -6
- package/dist/src/pi/pi-bin.js +54 -63
- package/dist/src/pi/pi-listed-models.js +53 -0
- package/dist/src/pi/pi-models.js +63 -30
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +1 -1
- package/dist/src/pi/pi-stream.js +6 -6
- package/dist/src/pi/pi-watchdogs.js +18 -19
- package/dist/src/pi-extension/bounded-output.js +5 -3
- package/dist/src/pi-extension/context-budget.js +2 -2
- package/dist/src/pi-extension/context-shake.js +1 -1
- package/dist/src/pi-extension/context-window.js +0 -1
- package/dist/src/pi-extension/full-output.js +3 -10
- package/dist/src/pi-extension/strip-thinking.js +0 -1
- package/dist/src/pi-extension/trim-system-prompt.js +3 -4
- package/dist/src/pi-extension/write-guard.js +37 -0
- package/dist/src/process/launch-services.js +1 -2
- package/dist/src/process/node-bin.js +16 -0
- package/dist/src/process/process-group.js +8 -18
- package/dist/src/process/process.js +41 -2
- package/dist/src/process/run-marker.js +18 -6
- package/dist/src/process/supervisor.js +22 -21
- package/dist/src/prompt/dialect.js +17 -2
- package/dist/src/prompt/gate-notes.js +203 -0
- package/dist/src/prompt/gate-prompts.js +121 -211
- package/dist/src/prompt/notes-backlog.js +181 -0
- package/dist/src/prompt/principles.js +1 -2
- package/dist/src/prompt/prompt-followup.js +19 -14
- package/dist/src/prompt/prompt.js +129 -204
- package/dist/src/redeploy/redeploy-policy.js +24 -19
- package/dist/src/redeploy/redeploy-probes.js +1 -1
- package/dist/src/redeploy/redeploy.js +2 -3
- package/dist/src/redeploy/redeployer.js +20 -20
- package/dist/src/redeploy/self-reload.js +2 -1
- package/dist/src/report/report-data.js +161 -77
- package/dist/src/report/report-fold.js +129 -52
- package/dist/src/report/report-main-red.js +55 -0
- package/dist/src/report/report-render.js +133 -64
- package/dist/src/report/report-types.js +1 -0
- package/dist/src/report/report.js +26 -9
- package/dist/src/request-timeouts.js +2 -2
- package/dist/src/review/known-flakes.js +100 -1
- package/dist/src/review/post-merge-review.js +106 -0
- package/dist/src/review/protected-edits.js +4 -5
- package/dist/src/review/review-followup.js +8 -8
- package/dist/src/review/review-precheck.js +66 -23
- package/dist/src/review/review-run.js +395 -0
- package/dist/src/review/review-verdict.js +2 -2
- package/dist/src/review/review.js +155 -360
- package/dist/src/review/secret-leaks.js +36 -16
- package/dist/src/review/suite-rerun.js +0 -1
- package/dist/src/roles/legacy-role-migration.js +80 -7
- package/dist/src/roles/role-catalog.js +69 -12
- package/dist/src/roles/role-guidance.js +55 -20
- package/dist/src/roles/role-labels.js +1 -2
- package/dist/src/roles/role-render.js +67 -50
- package/dist/src/roles/role-view.js +59 -47
- package/dist/src/roles/roles.js +31 -31
- package/dist/src/roles/runner-ids.js +97 -0
- package/dist/src/run/acceptance-greps.js +119 -0
- package/dist/src/run/feedback-filings.js +51 -0
- package/dist/src/{tick → run}/qa-coverage.js +9 -27
- package/dist/src/{tick/tick-apply.js → run/run-apply.js} +95 -94
- package/dist/src/run/run-detail-data.js +179 -0
- package/dist/src/run/run-detail-render.js +98 -0
- package/dist/src/run/run-detail.js +168 -0
- package/dist/src/{tick/tick-finalize.js → run/run-finalize.js} +72 -60
- package/dist/src/{tick/tick-outcome.js → run/run-outcome.js} +6 -7
- package/dist/src/{tick/tick-prompt.js → run/run-prompt.js} +108 -73
- package/dist/src/{tick/tick-resume.js → run/run-resume.js} +55 -26
- package/dist/src/{tick/tick-stage.js → run/run-stage.js} +108 -50
- package/dist/src/{tick/tick-timing.js → run/run-timing.js} +43 -41
- package/dist/src/{tick/tick-usage.js → run/run-usage.js} +45 -37
- package/dist/src/{tick/tick-verdict.js → run/run-verdict.js} +91 -56
- package/dist/src/run/selfcheck-digest.js +40 -0
- package/dist/src/run/stage-backlog-checks.js +249 -0
- package/dist/src/run/stage-check.js +172 -0
- package/dist/src/run/stage-path-checks.js +730 -0
- package/dist/src/{loop → runner}/leftover.js +80 -25
- package/dist/src/{loop → runner}/model-fallback.js +12 -12
- package/dist/src/runner/revision.js +202 -0
- package/dist/src/{loop/loop-fallback.js → runner/runner-fallback.js} +8 -8
- package/dist/src/runner/runner-outcome.js +107 -0
- package/dist/src/{loop/loop-pi.js → runner/runner-pi.js} +140 -131
- package/dist/src/{loop/loop-recovery.js → runner/runner-recovery.js} +21 -15
- package/dist/src/{loop/loop-tick.js → runner/runner-run.js} +218 -240
- package/dist/src/{loop/loop-state.js → runner/runner-state-load.js} +61 -114
- package/dist/src/runner/runner-state.js +102 -0
- package/dist/src/{loop/loop.js → runner/runner.js} +123 -120
- package/dist/src/scheduling/backoff.js +38 -38
- package/dist/src/scheduling/claims.js +35 -22
- package/dist/src/scheduling/once-round.js +22 -22
- package/dist/src/scheduling/quiet-hours.js +5 -6
- package/dist/src/scheduling/scheduling.js +74 -64
- package/dist/src/scheduling/work-merged-cache.js +2 -2
- package/dist/src/status/status-budget.js +55 -47
- package/dist/src/status/status-data.js +43 -16
- package/dist/src/status/status-inbox.js +2 -2
- package/dist/src/status/status-install.js +0 -1
- package/dist/src/status/status-polls.js +28 -28
- package/dist/src/status/status-roles.js +39 -13
- package/dist/src/team/error-storm.js +8 -8
- package/dist/src/team/failure-spread.js +2 -2
- package/dist/src/team/orchestrator-info.js +15 -9
- package/dist/src/team/reclaim.js +5 -6
- package/dist/src/team/team-hold.js +7 -7
- package/dist/src/team/team-polls.js +91 -50
- package/dist/src/team/team-state.js +8 -2
- package/dist/src/text/datetime.js +63 -17
- package/dist/src/text/format.js +48 -11
- package/dist/src/text/markdown.js +8 -5
- package/dist/src/text/phrases.js +57 -23
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +67 -13
- package/dist/src/ui/badges.js +65 -37
- package/dist/src/ui/gui/gui-client-backlog.js +292 -0
- package/dist/src/ui/gui/gui-client-boot.js +32 -26
- package/dist/src/ui/gui/gui-client-composer.js +8 -9
- package/dist/src/ui/gui/gui-client-drawer-config.js +212 -0
- package/dist/src/ui/gui/gui-client-drawer.js +126 -285
- package/dist/src/ui/gui/gui-client-feed.js +271 -0
- package/dist/src/ui/gui/gui-client-history.js +434 -67
- package/dist/src/ui/gui/gui-client-labels.js +1 -2
- package/dist/src/ui/gui/gui-client-markdown.js +1 -2
- package/dist/src/ui/gui/gui-client-model.js +27 -36
- package/dist/src/ui/gui/gui-client-operator.js +55 -51
- package/dist/src/ui/gui/gui-client-pending.js +227 -23
- package/dist/src/ui/gui/gui-client-report-chart.js +464 -0
- package/dist/src/ui/gui/gui-client-report.js +87 -427
- package/dist/src/ui/gui/{gui-client-loops.js → gui-client-roles.js} +43 -44
- package/dist/src/ui/gui/gui-client-settings.js +56 -4
- package/dist/src/ui/gui/gui-client-team.js +25 -465
- package/dist/src/ui/gui/gui-client.js +27 -25
- package/dist/src/ui/gui/gui-icons.js +0 -2
- package/dist/src/ui/gui/gui-page.js +9 -7
- package/dist/src/ui/gui/gui-styles.js +34 -34
- package/dist/src/ui/progress-data.js +9 -11
- package/dist/src/ui/{tick-progress-model.js → run-progress-model.js} +31 -31
- package/dist/src/ui/status-model.js +133 -125
- package/dist/src/ui/status-payload.js +136 -49
- package/dist/src/ui/status-render.js +101 -82
- package/dist/src/ui/team-alerts.js +248 -57
- package/dist/src/ui/tone.js +5 -6
- package/dist/src/ui/transcript-run.js +58 -0
- package/dist/src/ui/transcript-tail.js +28 -20
- package/dist/src/ui/transcript.js +63 -23
- package/dist/src/ui/tui/tui-app.js +0 -1
- package/dist/src/ui/tui/tui-backlog.js +51 -14
- package/dist/src/ui/tui/tui-editor-answer.js +27 -31
- package/dist/src/ui/tui/tui-editor-attach.js +3 -5
- package/dist/src/ui/tui/tui-editor-backends.js +132 -0
- package/dist/src/ui/tui/tui-editor-brief.js +10 -12
- package/dist/src/ui/tui/tui-editor-budget.js +35 -31
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +24 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +24 -0
- package/dist/src/ui/tui/tui-editor-config.js +86 -0
- package/dist/src/ui/tui/tui-editor-entry.js +36 -47
- package/dist/src/ui/tui/tui-editor-fallback.js +31 -0
- package/dist/src/ui/tui/tui-editor-instances.js +37 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +136 -0
- package/dist/src/ui/tui/tui-editor-interval.js +11 -9
- package/dist/src/ui/tui/tui-editor-model.js +29 -0
- package/dist/src/ui/tui/tui-editor-notes.js +3 -5
- package/dist/src/ui/tui/tui-editor-notify.js +25 -0
- package/dist/src/ui/tui/tui-editor-pause.js +48 -59
- package/dist/src/ui/tui/tui-editor-prompt.js +11 -16
- package/dist/src/ui/tui/tui-editor-provider-caps.js +27 -0
- package/dist/src/ui/tui/tui-editor-queued.js +5 -9
- package/dist/src/ui/tui/tui-editor-quiet.js +99 -35
- package/dist/src/ui/tui/tui-editor-retire.js +4 -6
- package/dist/src/ui/tui/tui-editor-role-key.js +76 -0
- package/dist/src/ui/tui/tui-editor-role-model.js +28 -0
- package/dist/src/ui/tui/tui-editor-search.js +45 -33
- package/dist/src/ui/tui/tui-editor-session.js +60 -34
- package/dist/src/ui/tui/tui-editor-slots.js +23 -0
- package/dist/src/ui/tui/tui-editors.js +35 -2
- package/dist/src/ui/tui/tui-frame.js +56 -11
- package/dist/src/ui/tui/tui-history.js +8 -9
- package/dist/src/ui/tui/tui-input.js +13 -11
- package/dist/src/ui/tui/tui-keymap.js +0 -1
- package/dist/src/ui/tui/tui-keys.js +24 -9
- package/dist/src/ui/tui/tui-pane-activity.js +14 -11
- package/dist/src/ui/tui/tui-pane-backlog.js +171 -126
- package/dist/src/ui/tui/tui-pane-brief.js +0 -1
- package/dist/src/ui/tui/tui-pane-ctx.js +2 -2
- package/dist/src/ui/tui/tui-pane-health.js +58 -8
- package/dist/src/ui/tui/tui-pane-history.js +402 -82
- package/dist/src/ui/tui/tui-pane-pending.js +308 -13
- package/dist/src/ui/tui/tui-pane-queued.js +71 -55
- package/dist/src/ui/tui/tui-pane-report.js +46 -14
- package/dist/src/ui/tui/tui-pane-session.js +25 -6
- package/dist/src/ui/tui/tui-pane-transcript.js +111 -37
- package/dist/src/ui/tui/tui-pane.js +80 -23
- package/dist/src/ui/tui/tui-panes.js +100 -30
- package/dist/src/ui/tui/tui.js +14 -14
- package/dist/src/verdict/fix-claim.js +82 -32
- package/dist/src/verdict/no-change.js +4 -4
- package/dist/src/verdict/refusal.js +40 -16
- package/dist/src/verdict/reply-contract.js +42 -31
- package/dist/src/version.js +0 -1
- package/package.json +2 -1
- package/dist/src/loop/revision.js +0 -70
- package/dist/src/roles/loop-ids.js +0 -77
- package/dist/src/tick/stage-check.js +0 -419
- package/dist/src/tick/telemetry-digest.js +0 -25
- package/dist/src/tick/tick-detail-data.js +0 -108
- package/dist/src/tick/tick-detail.js +0 -163
- package/dist/src/ui/gui/gui-client-sound.js +0 -53
package/dist/src/ui/badges.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { utilizationPct, windowLabel } from "../agent/subscription-account.js";
|
|
2
2
|
import { MODEL_TIERS } from "../config/config-schema.js";
|
|
3
3
|
import { budgetGate, budgetReached, fallbackTierEntries } from "../budget/budget.js";
|
|
4
4
|
import { fallbackDisplayPair } from "../config/config-views.js";
|
|
@@ -9,24 +9,29 @@ import { formatGB, shortSha, usd, usdCap } from "../text/format.js";
|
|
|
9
9
|
/** The status header's BADGE fragments, phrased once for both observer surfaces: the terminal
|
|
10
10
|
* table (status-render.ts's renderStatus header) and the JSON/GUI payload
|
|
11
11
|
* (status-payload.ts's preformatted badge fields). Split from status-model.ts, which keeps the
|
|
12
|
-
* per-
|
|
13
|
-
* facts (build, main check, budget, merge queue, timed pause) rather than per-
|
|
12
|
+
* per-role display model (phase, rows, token metrics) — the badges are header-scale team
|
|
13
|
+
* facts (build, main check, budget, merge queue, timed pause) rather than per-role state, and
|
|
14
14
|
* their one-home rule ("the page cannot re-derive and drift from these strings") is easier to
|
|
15
15
|
* see when the module is exactly the badges. The duration phrasing they share (humanSeconds)
|
|
16
16
|
* lives in datetime.ts — core modules need it too, so it sits below the ui layer. */
|
|
17
17
|
/** The header's build fragment: which commit the running harness was compiled from and, when
|
|
18
18
|
* main's build inputs have moved past it, how far — the team is then executing code main no
|
|
19
19
|
* longer describes (auto-restart lands the new build; off, restart `tumwater run` by hand).
|
|
20
|
-
* A stale build also says what auto-restart is doing about it:
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* A stale build also says what auto-restart is doing about it: a cooldown-deferred restart
|
|
21
|
+
* (the reason literally opens with "cooldown until …") scopes the wait to the redeploy — the
|
|
22
|
+
* team keeps running the stale build, so the badge reads "work continues" and keeps the
|
|
23
|
+
* deadline; `restart pending` (the drain that precedes the swap, which actually holds new runs
|
|
24
|
+
* and merges) resolves itself; `restart BLOCKED` never will until main moves. Telling them apart
|
|
25
|
+
* at a glance is the whole point (see BuildStatus.restartBlocked). Empty when the dist carries
|
|
26
|
+
* no stamp. Shared by the TUI/status header and the GUI's. */
|
|
24
27
|
export function buildBadge(build) {
|
|
25
28
|
if (!build)
|
|
26
29
|
return "";
|
|
27
30
|
const stale = build.stale ? ` — update available (${plural(build.aheadCommits ?? 0, "commit")} behind)` : "";
|
|
28
31
|
const restart = build.restartBlocked
|
|
29
|
-
?
|
|
32
|
+
? build.restartBlocked.startsWith("cooldown until ")
|
|
33
|
+
? ` — restarts after cooldown; work continues (${build.restartBlocked})`
|
|
34
|
+
: `; restart blocked: ${build.restartBlocked}`
|
|
30
35
|
: build.restartPending
|
|
31
36
|
? " — restarts when idle"
|
|
32
37
|
: "";
|
|
@@ -84,7 +89,7 @@ export function mainCheckBadge(mainCheck) {
|
|
|
84
89
|
}
|
|
85
90
|
/** The team's current budget-gate state, derived from a snapshot's budget block — the header
|
|
86
91
|
* badge's default-tier story (part 5c/8): the badge names the team's bulk fallback, so it
|
|
87
|
-
* reads the `default` tier's verdict alone (the per-tier pause lives on the
|
|
92
|
+
* reads the `default` tier's verdict alone (the per-tier pause lives on the role rows, via
|
|
88
93
|
* status-data.ts's budgetPausedRoles). */
|
|
89
94
|
function teamBudgetGate(budget) {
|
|
90
95
|
return budgetGate(budgetReached(budget), { default: budget.fallback !== null, strong: budget.fallback !== null }, false);
|
|
@@ -120,17 +125,21 @@ function untilReset(resetsAt, now) {
|
|
|
120
125
|
* `· Claude 5h · 64% · resets in 4h 12m` (`resets now` once the deadline has passed), with
|
|
121
126
|
* the status word when no `utilization` was reported and an `overage` marker while the
|
|
122
127
|
* account spends into overage. A rejected window reads
|
|
123
|
-
*
|
|
124
|
-
* seams; a reset reading names neither.
|
|
125
|
-
*
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
* `<backend> model jobs wait until 14:30` in place of `resets` while a future deadline holds
|
|
129
|
+
* the seams; a reset reading names neither. Reads the most-constrained block (`blocks[0]`,
|
|
130
|
+
* already most-constrained-first), rendering under that backend's own display label. The
|
|
131
|
+
* hold clause names the backend by its lowercase id (not the display label), so a claude-only
|
|
132
|
+
* team keeps the byte-identical `claude model jobs wait` the pre-3b badge rendered. The one
|
|
133
|
+
* home of the string, so the TUI header and the payload's preformatted badge cannot drift. */
|
|
134
|
+
function subscriptionWindowBadge(blocks, now, moved = false) {
|
|
135
|
+
const block = blocks[0];
|
|
136
|
+
const w = block.primary;
|
|
137
|
+
const parts = [`${block.label} ${windowLabel(w.type)}`];
|
|
138
|
+
parts.push(typeof w.utilization === "number" ? `${utilizationPct(w.utilization)}%` : claudeWindowStatusWord(w.status));
|
|
130
139
|
if (w.isUsingOverage === true)
|
|
131
140
|
parts.push("overage");
|
|
132
|
-
if (w.status === "rejected" &&
|
|
133
|
-
parts.push(`${moved ? "on fallback" :
|
|
141
|
+
if (w.status === "rejected" && block.heldUntil !== null) {
|
|
142
|
+
parts.push(`${moved ? "on fallback" : `${block.backend} model jobs wait`} until ${clockHm(block.heldUntil)}`);
|
|
134
143
|
}
|
|
135
144
|
else if (w.status !== "reset" && typeof w.resetsAt === "number") {
|
|
136
145
|
parts.push(w.resetsAt - now < 60_000 ? "resets now" : `resets in ${untilReset(w.resetsAt, now)}`);
|
|
@@ -163,13 +172,14 @@ export function chainBadge(chain) {
|
|
|
163
172
|
return text ? ` · ${text}` : "";
|
|
164
173
|
}
|
|
165
174
|
/** The window fragment's tone rank, for the header badge and the GUI meter: a rejected window
|
|
166
|
-
* is red, an allowed_warning amber, anything else no tone.
|
|
167
|
-
|
|
168
|
-
|
|
175
|
+
* is red, an allowed_warning amber, anything else no tone. Reads the most-constrained block
|
|
176
|
+
* (`blocks[0]`), so a multi-backend team's tone follows its tightest window. */
|
|
177
|
+
export function subscriptionWindowTone(blocks) {
|
|
178
|
+
if (!blocks || blocks.length === 0)
|
|
169
179
|
return undefined;
|
|
170
|
-
if (
|
|
180
|
+
if (blocks[0].primary.status === "rejected")
|
|
171
181
|
return "red";
|
|
172
|
-
if (
|
|
182
|
+
if (blocks[0].primary.status === "allowed_warning")
|
|
173
183
|
return "yellow";
|
|
174
184
|
return undefined;
|
|
175
185
|
}
|
|
@@ -193,11 +203,15 @@ export function claudeWindowTone(claudeWindow) {
|
|
|
193
203
|
* needs no forecast slot: a disabled cap never carries a capHitAt (projectCapHit returns null
|
|
194
204
|
* for capUsd <= 0), and the fallback fragment only stands once the cap IS reached — the one
|
|
195
205
|
* state where the forecast is itself null — so the fragments can never compete for the line. */
|
|
196
|
-
export function budgetBadge(budget,
|
|
197
|
-
const
|
|
206
|
+
export function budgetBadge(budget, windows, only = false, now = Date.now(), chain) {
|
|
207
|
+
const blocks = windows && windows.length > 0 ? windows : undefined;
|
|
208
|
+
const window = (blocks ? subscriptionWindowBadge(blocks, now, chainMovedByWindow(chain)) : "") + chainBadge(chain);
|
|
198
209
|
// Subscription-only: the window stands IN PLACE of the daily spend — there is no priced
|
|
199
|
-
// spend for a dollar figure to report.
|
|
200
|
-
|
|
210
|
+
// spend for a dollar figure to report. This holds only while a window block actually
|
|
211
|
+
// exists: an all-free subscription-only team (only and free, no windows) reads ` · budget: n/a`
|
|
212
|
+
// like any other all-free team, so the free branch below stays reachable. Mixed and none
|
|
213
|
+
// fall through to the daily spend.
|
|
214
|
+
if (only && blocks)
|
|
201
215
|
return window;
|
|
202
216
|
if (budget.free)
|
|
203
217
|
return ` · budget: n/a${window}`;
|
|
@@ -211,13 +225,12 @@ export function budgetBadge(budget, claudeWindow, now = Date.now(), chain) {
|
|
|
211
225
|
// single-pair text byte-identical to the pre-tier badge. Off-gate the badge is byte-
|
|
212
226
|
// identical to before either way.
|
|
213
227
|
const tierEntries = fallbackTierEntries(budget.tiers);
|
|
214
|
-
// The
|
|
215
|
-
|
|
216
|
-
const agentDefault = `${budget.backend && budget.backend !== "pi" ? budget.backend : "pi"} default`;
|
|
228
|
+
// The label when the engaged pair names neither half: pi's own default.
|
|
229
|
+
const backendDefault = "pi default";
|
|
217
230
|
const fallback = teamBudgetGate(budget) === "fallback"
|
|
218
231
|
? tierEntries.length >= 2
|
|
219
232
|
? ` · over-cap model: ${tierEntries.join(", ")} (cost n/a)`
|
|
220
|
-
: ` · over-cap model: ${fallbackDisplayPair(budget.fallback,
|
|
233
|
+
: ` · over-cap model: ${fallbackDisplayPair(budget.fallback, backendDefault)} (cost n/a)`
|
|
221
234
|
: "";
|
|
222
235
|
const forecast = budget.capHitAt !== null
|
|
223
236
|
? ` · ~cap at ${clockHm(budget.capHitAt)}`
|
|
@@ -249,11 +262,11 @@ export function wakeBadge(wake, now) {
|
|
|
249
262
|
return "";
|
|
250
263
|
return ` · wake scheduled in ${humanUntil(wake.atMs, now)} (${wake.roles.join(", ")})`;
|
|
251
264
|
}
|
|
252
|
-
/** One
|
|
265
|
+
/** One role's own pending-wake marker for its state cell: ` · wake in <duration>` for the
|
|
253
266
|
* earliest future wake naming `role` (or `instanceOf`, the base role an extra instance runs),
|
|
254
|
-
* empty when none does. Same match rule as the GUI's `scheduledWakesFor` (gui-client-
|
|
267
|
+
* empty when none does. Same match rule as the GUI's `scheduledWakesFor` (gui-client-roles.ts
|
|
255
268
|
* keeps its own JS copy). `wakes` need not be sorted. */
|
|
256
|
-
export function
|
|
269
|
+
export function roleWakeMarker(wakes, role, instanceOf, now) {
|
|
257
270
|
let at;
|
|
258
271
|
for (const w of wakes ?? []) {
|
|
259
272
|
if (w.atMs <= now || !(w.roles.includes(role) || (instanceOf !== undefined && w.roles.includes(instanceOf))))
|
|
@@ -272,7 +285,7 @@ export function loopWakeMarker(wakes, role, instanceOf, now) {
|
|
|
272
285
|
* or already-expired deadline renders nothing — the read side treats an expired marker as
|
|
273
286
|
* unpaused, so the badge never claims a countdown that is over. The duration goes through
|
|
274
287
|
* pauseCountdown, the one home of that guard and rounding. renderStatus renders it in
|
|
275
|
-
* the TUI/status header after the budget badge; the GUI recomputes the
|
|
288
|
+
* the TUI/status header after the budget badge; the GUI recomputes the counting-down number
|
|
276
289
|
* client-side from the payload's raw `pausedUntil` (status-payload.ts) using its own
|
|
277
290
|
* humanSeconds copy, pinned against this one by test. `reason` (the operator's
|
|
278
291
|
* `pause --reason <text>` note) rides after the countdown — and makes the untimed pause
|
|
@@ -295,7 +308,7 @@ export function pauseBadge(pausedUntil, now, reason) {
|
|
|
295
308
|
* countdowns, shared by pauseBadge's header fragment and team-alerts.ts's paused alert
|
|
296
309
|
* title, so the two surfaces cannot disagree about how much time remains. The guard is this
|
|
297
310
|
* function's own; the ms→seconds rounding rides datetime.ts's secondsUntil. The GUI's pause
|
|
298
|
-
* control recomputes the
|
|
311
|
+
* control recomputes the counting-down number client-side from the payload's raw `pausedUntil`
|
|
299
312
|
* (gui-client-operator.ts) — a separate runtime that cannot import TypeScript. */
|
|
300
313
|
export function pauseCountdown(pausedUntil, now) {
|
|
301
314
|
if (pausedUntil === undefined || pausedUntil <= now)
|
|
@@ -304,7 +317,7 @@ export function pauseCountdown(pausedUntil, now) {
|
|
|
304
317
|
}
|
|
305
318
|
/** The header's quiet-hours fragment (plans: "Quiet hours … part 2/2, observability"): the
|
|
306
319
|
* schedule as standing information in every configured state — `· quiet until 07:00` while
|
|
307
|
-
* the local clock sits inside the window (the active reading answers "why are the
|
|
320
|
+
* the local clock sits inside the window (the active reading answers "why are the roles
|
|
308
321
|
* idle?" with when they start again) and `· quiet 23:00-07:00` otherwise — and empty when
|
|
309
322
|
* the snapshot carries no window, so a team without the schedule keeps its header
|
|
310
323
|
* byte-identical. The window end comes from quiet-hours.ts's quietWindowEnd — read back out of
|
|
@@ -354,3 +367,18 @@ export function permitsBadge(permits) {
|
|
|
354
367
|
return "";
|
|
355
368
|
return ` · running ${permits.inUse} of ${permits.limit} · ${providers}`;
|
|
356
369
|
}
|
|
370
|
+
/** The check-permit pool's reading, ` · checks 1/2` plus `, 3 waiting` while checks queue. Empty
|
|
371
|
+
* when no check runs or waits, so an idle check pool leaves the header byte-identical. */
|
|
372
|
+
export function checksBadge(checks) {
|
|
373
|
+
if (!checks || (checks.inUse === 0 && checks.waiting === 0))
|
|
374
|
+
return "";
|
|
375
|
+
return ` · checks ${checks.inUse}/${checks.limit}${checks.waiting > 0 ? `, ${checks.waiting} waiting` : ""}`;
|
|
376
|
+
}
|
|
377
|
+
/** The workspace pool's header fragment: ` · workspaces 8/8`, plus `, 2 waiting` when runs queue
|
|
378
|
+
* for a slot. Shown only while the pool is full or something waits; a pool with a free workspace
|
|
379
|
+
* leaves the header byte-identical. */
|
|
380
|
+
export function workspacesBadge(pool) {
|
|
381
|
+
if (!pool || (pool.waiting === 0 && pool.leased < pool.limit))
|
|
382
|
+
return "";
|
|
383
|
+
return ` · workspaces ${pool.leased}/${pool.limit}${pool.waiting > 0 ? `, ${pool.waiting} waiting` : ""}`;
|
|
384
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/** The dashboard's backlog card, browser-side: the questions/plans/bugs/queued tabs with
|
|
2
|
+
* their text Search box, the Queued tab's in-place prompt editor, and the New plan / File a
|
|
3
|
+
* bug forms. renderBacklog repaints it from each /api/status poll (renderTeam in
|
|
4
|
+
* gui-client-team.ts calls it). Split out of gui-client-team.ts; spliced into gui-client.ts's
|
|
5
|
+
* script right after it, reaching its helpers ($, esc, recall, store, getJson, postJson,
|
|
6
|
+
* paintPanel, showFlash, clickClosest) and lastStatus through that concatenation. */
|
|
7
|
+
export const GUI_CLIENT_BACKLOG_JS = String.raw `
|
|
8
|
+
// ---- backlog: questions, plans, bugs, and queued prompts ----
|
|
9
|
+
let backlogTab = recall("backlog") || "";
|
|
10
|
+
let backlogGrep = recall("backlog-grep") || "";
|
|
11
|
+
let backlogBodyHits = null;
|
|
12
|
+
// backlog:start
|
|
13
|
+
// The Queued tab's in-place editor state, beside the tab choice its rows paint from:
|
|
14
|
+
// backlogStatus is the last payload renderBacklog saw, queuedEdit is the open editor
|
|
15
|
+
// ({ role, file, text } or null), and queuedEditPainted marks that its textarea is on
|
|
16
|
+
// screen so the standing 1s poll cannot repaint over what the operator is typing.
|
|
17
|
+
let backlogStatus = null;
|
|
18
|
+
let backlogMatchAt = 0;
|
|
19
|
+
let queuedEdit = null;
|
|
20
|
+
let queuedEditPainted = false;
|
|
21
|
+
// The Plans/Bugs tabs' New form: { kind: "plan" | "bug" } or null, with the same painted
|
|
22
|
+
// guard so a poll does not repaint over the typed title.
|
|
23
|
+
let backlogNew = null;
|
|
24
|
+
let backlogNewPainted = false;
|
|
25
|
+
// Names of the blocked series whose parts the Plans tab currently shows.
|
|
26
|
+
let openSeries = new Set();
|
|
27
|
+
function setBacklogTab(tab) {
|
|
28
|
+
backlogTab = tab;
|
|
29
|
+
store("backlog", tab);
|
|
30
|
+
queuedEdit = null;
|
|
31
|
+
queuedEditPainted = false;
|
|
32
|
+
backlogNew = null;
|
|
33
|
+
backlogNewPainted = false;
|
|
34
|
+
if (lastStatus) renderBacklog(lastStatus);
|
|
35
|
+
}
|
|
36
|
+
// The Search box's text: trimmed, remembered, and the last payload repainted through it.
|
|
37
|
+
function setBacklogGrep(text) {
|
|
38
|
+
backlogGrep = text.trim();
|
|
39
|
+
store("backlog-grep", backlogGrep);
|
|
40
|
+
if (backlogStatus) renderBacklog(backlogStatus);
|
|
41
|
+
fetchBacklogMatch();
|
|
42
|
+
}
|
|
43
|
+
// The server's body matches for the current Search text: { needle, plans: [index…], … } from
|
|
44
|
+
// /api/backlog-match. A stale answer (the box changed meanwhile) is dropped, and a failed
|
|
45
|
+
// fetch leaves the shown-text matching in place.
|
|
46
|
+
async function fetchBacklogMatch() {
|
|
47
|
+
if (!backlogGrep) { backlogBodyHits = null; return; }
|
|
48
|
+
const needle = backlogGrep;
|
|
49
|
+
backlogMatchAt = Date.now();
|
|
50
|
+
try {
|
|
51
|
+
const d = await getJson("/api/backlog-match?grep=" + encodeURIComponent(needle));
|
|
52
|
+
if (backlogGrep !== needle) return;
|
|
53
|
+
backlogBodyHits = { needle, ...d };
|
|
54
|
+
if (backlogStatus) renderBacklog(backlogStatus);
|
|
55
|
+
} catch (e) { /* keep shown-text matching */ }
|
|
56
|
+
}
|
|
57
|
+
function beginBacklogNew(kind) {
|
|
58
|
+
backlogNew = { kind };
|
|
59
|
+
backlogNewPainted = false;
|
|
60
|
+
renderBacklog(backlogStatus);
|
|
61
|
+
}
|
|
62
|
+
function toggleBacklogSeries(name) {
|
|
63
|
+
if (openSeries.has(name)) openSeries.delete(name); else openSeries.add(name);
|
|
64
|
+
renderBacklog(backlogStatus);
|
|
65
|
+
}
|
|
66
|
+
function endBacklogNew() {
|
|
67
|
+
backlogNew = null;
|
|
68
|
+
backlogNewPainted = false;
|
|
69
|
+
renderBacklog(backlogStatus);
|
|
70
|
+
}
|
|
71
|
+
function beginQueuedEdit(role, file, text) {
|
|
72
|
+
backlogTab = "queued";
|
|
73
|
+
queuedEdit = { role, file, text };
|
|
74
|
+
queuedEditPainted = false;
|
|
75
|
+
renderBacklog(backlogStatus);
|
|
76
|
+
}
|
|
77
|
+
function endQueuedEdit() {
|
|
78
|
+
queuedEdit = null;
|
|
79
|
+
queuedEditPainted = false;
|
|
80
|
+
renderBacklog(backlogStatus);
|
|
81
|
+
}
|
|
82
|
+
const BACKLOG = [
|
|
83
|
+
["questions", "Questions", "question", "indigo", "No open questions", "Roles post here when they need a decision from you."],
|
|
84
|
+
["plans", "Plans", "plan", "blue", "Nothing planned", "The plan role files features in PLANS.md."],
|
|
85
|
+
["bugs", "Bugs", "bug", "red", "No open bugs", "Bugs filed in BUGS.md show up here."],
|
|
86
|
+
["queued", "Inbox", "chat", "gray", "Inbox empty", "Messages you send wait here until their role's next run."], // vocabulary-ok: internal tab key
|
|
87
|
+
];
|
|
88
|
+
function renderBacklog(d) {
|
|
89
|
+
backlogStatus = d;
|
|
90
|
+
if (backlogGrep && Date.now() - backlogMatchAt > 5000) fetchBacklogMatch();
|
|
91
|
+
if (queuedEdit && queuedEditPainted) return; // an open editor owns the panel until Save or Cancel
|
|
92
|
+
if (backlogNew && backlogNewPainted) return; // likewise the New form
|
|
93
|
+
const items = { questions: d.questions || [], plans: d.plans || [], bugs: d.bugs || [], queued: queuedPrompts(d) };
|
|
94
|
+
if (!items[backlogTab]) backlogTab = items.questions.length ? "questions" : "plans";
|
|
95
|
+
paintPanel("backlogtabs", BACKLOG.map(([k, label]) => "<button type='button' role='tab' data-tab='" + k + "' aria-selected='" + (k === backlogTab) + "'" +
|
|
96
|
+
(k === backlogTab ? " class='active'" : "") + ">" + label + "<span class='badge" + (k === "questions" && items.questions.length ? " t-indigo" : "") + "'>" +
|
|
97
|
+
items[k].length + "</span></button>").join(""));
|
|
98
|
+
const [key, , ic, tone, emptyTitle, emptyText] = BACKLOG.find((s) => s[0] === backlogTab);
|
|
99
|
+
const list = items[key];
|
|
100
|
+
// Search narrows rows by the text each one shows, or by a body match from /api/backlog-match;
|
|
101
|
+
// indexes stay the payload's own. Inbox rows also match their full text there (the queued key).
|
|
102
|
+
const needle = backlogGrep.toLowerCase();
|
|
103
|
+
const hit = (text, file, i) => !needle || String(text).toLowerCase().includes(needle) ||
|
|
104
|
+
(!!file && !!backlogBodyHits && backlogBodyHits.needle === backlogGrep && (backlogBodyHits[file] || []).includes(i));
|
|
105
|
+
const queuedHit = (q) => !!backlogBodyHits && backlogBodyHits.needle === backlogGrep &&
|
|
106
|
+
(backlogBodyHits.queued || []).some((m) => m.role === q.role && m.file === q.file);
|
|
107
|
+
let shown = 0;
|
|
108
|
+
let html;
|
|
109
|
+
if (!list.length) html = "<div class='empty'><strong>" + esc(emptyTitle) + "</strong>" + esc(emptyText) + "</div>";
|
|
110
|
+
else if (key === "queued") {
|
|
111
|
+
// Each role's queue head carries no Move to top (a no-op there); every later row of
|
|
112
|
+
// that role does. The rows arrive grouped by role — director first, then each role in
|
|
113
|
+
// id order — so a Set of roles already painted marks each group's first row.
|
|
114
|
+
const paintedFirst = new Set();
|
|
115
|
+
html = list.map((q) => {
|
|
116
|
+
const addr = "data-file='" + esc(q.file) + "' data-role='" + esc(q.role) + "'";
|
|
117
|
+
// Mark this role's group head before the edit-row early return, so an edited head row
|
|
118
|
+
// still leaves the Move to top button on that role's later rows.
|
|
119
|
+
const alreadyFirst = paintedFirst.has(q.role);
|
|
120
|
+
paintedFirst.add(q.role);
|
|
121
|
+
// The row being edited swaps its preview for a textarea and a Save/Cancel pair; the
|
|
122
|
+
// address stays the queue-file basename, the same one /api/prompt-cancel uses.
|
|
123
|
+
if (queuedEdit && q.file === queuedEdit.file && q.role === queuedEdit.role) {
|
|
124
|
+
queuedEditPainted = true;
|
|
125
|
+
shown++; // the open editor always shows, so a search never replaces it with "Nothing matches"
|
|
126
|
+
return "<div class='list-item'><span class='li-icon'>" + icon("chat") + "</span>" +
|
|
127
|
+
"<textarea id='queuedit' class='queuedit'>" + esc(queuedEdit.text) + "</textarea>" +
|
|
128
|
+
"<button type='button' class='btn btn-sm rowaction' data-action='prompteditsave' " + addr + ">Save</button>" +
|
|
129
|
+
"<button type='button' class='btn btn-sm rowaction' data-action='prompteditcancel' " + addr + ">Cancel</button></div>";
|
|
130
|
+
}
|
|
131
|
+
if (!hit(q.preview + " " + q.role) && !queuedHit(q)) return "";
|
|
132
|
+
shown++;
|
|
133
|
+
const topBtn = alreadyFirst
|
|
134
|
+
? "<button type='button' class='btn btn-sm rowaction' data-action='prompttop' " + addr + ">Move to top</button>"
|
|
135
|
+
: "";
|
|
136
|
+
return "<div class='list-item'><span class='li-icon'>" + icon("chat") + "</span><div class='li-main'><span class='li-title'>" + esc(q.preview) +
|
|
137
|
+
"</span><span class='li-meta'>" + esc((q.role === "director" ? "for the director" : "for the " + roleLabel(q.role) + " role") +
|
|
138
|
+
(q.notBeforeMs && q.notBeforeMs > Date.now() ? " · delivers in " + humanSeconds(Math.round((q.notBeforeMs - Date.now()) / 1000))
|
|
139
|
+
: (q.queuedAtMs ? " · queued " + fmtAgo(q.queuedAtMs) : ""))) + "</span></div>" +
|
|
140
|
+
topBtn +
|
|
141
|
+
"<button type='button' class='btn btn-sm rowaction' data-action='promptcancel' " + addr + ">Cancel</button>" +
|
|
142
|
+
"<button type='button' class='btn btn-sm rowaction' data-action='promptedit' " + addr + ">Edit</button></div>";
|
|
143
|
+
}).join("");
|
|
144
|
+
} else {
|
|
145
|
+
const open = openEntryKey();
|
|
146
|
+
// The eligibility holds beside the title, from the payload's parallel arrays (indexed by
|
|
147
|
+
// the same tab position the click sends): empty for eligible rows so their markup is
|
|
148
|
+
// unchanged, and the Questions tab carries no holds.
|
|
149
|
+
const holds = key === "plans" ? (d.planHolds || []) : key === "bugs" ? (d.bugHolds || []) : [];
|
|
150
|
+
// Blocked entries (a hold of any kind) paint after the pickable ones under one divider;
|
|
151
|
+
// each row keeps its original data-index so the drawer and Answer still address it.
|
|
152
|
+
const blocked = key === "plans" ? (d.planBlocked || []) : key === "bugs" ? (d.bugBlocked || []) : [];
|
|
153
|
+
const rows = list.map((title, i) => {
|
|
154
|
+
const t = splitTitle(title);
|
|
155
|
+
const mark = holds[i] || "";
|
|
156
|
+
if (!hit(t.title + " " + t.meta + " " + mark, key, i)) return "";
|
|
157
|
+
shown++;
|
|
158
|
+
return "<div class='list-item t-" + tone + (open === key + ":" + i ? " active" : "") + "'><span class='li-icon'>" + icon(ic) + "</span>" +
|
|
159
|
+
"<button type='button' class='li-open backloglink' data-file='" + key + "' data-index='" + i + "'><span class='li-title'>" + esc(t.title) + "</span>" +
|
|
160
|
+
(t.meta ? "<span class='li-meta'>" + esc(t.meta) + "</span>" : "") +
|
|
161
|
+
(mark ? "<span class='li-meta'>" + esc(mark.trim()) + "</span>" : "") + "</button>" +
|
|
162
|
+
(key === "questions" ? "<button type='button' class='btn btn-sm backloglink' data-file='questions' data-index='" + i + "'>Answer</button>" : "") + "</div>";
|
|
163
|
+
});
|
|
164
|
+
const ready = rows.filter((r, i) => r && !blocked[i]);
|
|
165
|
+
const series = key === "plans" ? (d.planSeries || []) : [];
|
|
166
|
+
let heldCount = 0;
|
|
167
|
+
const held = [];
|
|
168
|
+
const seen = new Set();
|
|
169
|
+
rows.forEach((r, i) => {
|
|
170
|
+
if (!blocked[i] || !r) return;
|
|
171
|
+
heldCount++;
|
|
172
|
+
const s = series[i];
|
|
173
|
+
if (!s || needle) { held.push(r); return; }
|
|
174
|
+
if (seen.has(s.name)) return;
|
|
175
|
+
seen.add(s.name);
|
|
176
|
+
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>");
|
|
177
|
+
if (openSeries.has(s.name)) rows.forEach((r2, j) => { if (blocked[j] && series[j] && series[j].name === s.name) held.push(r2); });
|
|
178
|
+
});
|
|
179
|
+
html = ready.join("") + (heldCount ? "<div class='list-item list-divider'><span class='li-meta'>Blocked (" + heldCount + ")</span></div>" + held.join("") : "");
|
|
180
|
+
}
|
|
181
|
+
const answered = (key === "questions" ? (d.answeredQuestions || []) : []).map((a, i) => ({ a, i })).filter((x) => hit(x.a.title + " " + (x.a.decision || ""), "answered", x.i));
|
|
182
|
+
shown += answered.length;
|
|
183
|
+
if (answered.length) {
|
|
184
|
+
html += "<div class='list-item list-divider'><span class='li-meta'>Answered recently</span></div>" + answered.map(({ a, i }) =>
|
|
185
|
+
"<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>" +
|
|
186
|
+
(a.decision ? "<span class='li-meta'>" + esc(a.decision) + "</span>" : "") + "</button></div>").join("");
|
|
187
|
+
}
|
|
188
|
+
const finished = (key === "plans" ? (d.donePlans || []) : key === "bugs" ? (d.fixedBugs || []) : []).map((f, i) => ({ f, i })).filter((x) => hit(x.f.title, key === "plans" ? "done" : "fixed", x.i));
|
|
189
|
+
shown += finished.length;
|
|
190
|
+
if (finished.length) {
|
|
191
|
+
html += "<div class='list-item list-divider'><span class='li-meta'>" + (key === "plans" ? "Done recently" : "Fixed recently") + "</span></div>" + finished.map(({ f, i }) => {
|
|
192
|
+
const doneFile = key === "plans" ? "done" : "fixed";
|
|
193
|
+
return "<div class='list-item t-gray" + (openEntryKey() === doneFile + ":" + i ? " active" : "") + "'><span class='li-icon'>" + icon("check") + "</span>" +
|
|
194
|
+
"<button type='button' class='li-open backloglink' data-file='" + doneFile + "' data-index='" + i + "'><span class='li-title'>" + esc(f.title) + "</span>" +
|
|
195
|
+
(f.completedOn ? "<span class='li-meta'>" + esc((key === "plans" ? "done " : "fixed ") + f.completedOn) + "</span>" : "") + "</button></div>";
|
|
196
|
+
}).join("");
|
|
197
|
+
}
|
|
198
|
+
if (needle && list.length && !shown) html = "<div class='empty'><strong>Nothing matches this search</strong>Clear the search box to see every entry.</div>";
|
|
199
|
+
if (key === "plans" || key === "bugs") {
|
|
200
|
+
const kind = key === "plans" ? "plan" : "bug";
|
|
201
|
+
if (backlogNew && backlogNew.kind === kind) {
|
|
202
|
+
backlogNewPainted = true;
|
|
203
|
+
html = "<div class='list-item'><input id='backlognew' type='text' placeholder='" + (kind === "plan" ? "Plan title" : "Bug symptom") + "'>" +
|
|
204
|
+
"<textarea id='backlognewbody' rows='6' placeholder='Details (optional, markdown)'></textarea>" +
|
|
205
|
+
"<button type='button' class='btn btn-sm rowaction' data-action='backlognewsave' data-kind='" + kind + "'>Save</button>" +
|
|
206
|
+
"<button type='button' class='btn btn-sm rowaction' data-action='backlognewcancel'>Cancel</button></div>" + html;
|
|
207
|
+
} else {
|
|
208
|
+
html = "<div class='list-item'><button type='button' class='btn btn-sm' data-action='backlognew' data-kind='" + kind + "'>" +
|
|
209
|
+
(kind === "plan" ? "New plan" : "File a bug") + "</button></div>" + html;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
paintPanel("backlog", html);
|
|
213
|
+
}
|
|
214
|
+
// backlog:end
|
|
215
|
+
$("backlogtabs").addEventListener("click", (ev) => {
|
|
216
|
+
const b = clickClosest(ev, "button[data-tab]");
|
|
217
|
+
if (b) setBacklogTab(b.dataset.tab);
|
|
218
|
+
});
|
|
219
|
+
// Like #feedgrep, the box is static markup set once at load: the 1s poll repaints only the rows,
|
|
220
|
+
// never the box, so it cannot overwrite what the operator is typing.
|
|
221
|
+
$("backloggrep").value = backlogGrep;
|
|
222
|
+
$("backloggrep").addEventListener("input", (ev) => setBacklogGrep(ev.target.value));
|
|
223
|
+
$("backlog").addEventListener("click", async (ev) => {
|
|
224
|
+
const ser = clickClosest(ev, "button[data-action='seriestoggle']");
|
|
225
|
+
if (ser) { toggleBacklogSeries(ser.dataset.series); return; }
|
|
226
|
+
const fresh = clickClosest(ev, "button[data-action='backlognew']");
|
|
227
|
+
if (fresh) { beginBacklogNew(fresh.dataset.kind); return; }
|
|
228
|
+
if (clickClosest(ev, "button[data-action='backlognewcancel']")) { endBacklogNew(); return; }
|
|
229
|
+
const freshSave = clickClosest(ev, "button[data-action='backlognewsave']");
|
|
230
|
+
if (freshSave) {
|
|
231
|
+
const input = $("backlognew");
|
|
232
|
+
const title = input ? input.value.trim() : "";
|
|
233
|
+
if (!title) return;
|
|
234
|
+
const bodyBox = $("backlognewbody");
|
|
235
|
+
await postAction("/api/backlog-file", { kind: freshSave.dataset.kind, title, body: bodyBox ? bodyBox.value : "" }, (d) => "Filed: " + d.title + uncommittedTail(d));
|
|
236
|
+
endBacklogNew();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const top = clickClosest(ev, "button.rowaction[data-action='prompttop']");
|
|
240
|
+
if (top) {
|
|
241
|
+
await postAction("/api/prompt-top", { role: top.dataset.role, file: top.dataset.file }, (d) =>
|
|
242
|
+
d && d.status === "topped"
|
|
243
|
+
? "Moved to the front: " + (d.preview || "")
|
|
244
|
+
: d && d.status === "already_first"
|
|
245
|
+
? "Already first"
|
|
246
|
+
: "No longer in the inbox — " + (top.dataset.role ? roleLabel(top.dataset.role) : "the director") + " already took it");
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
const cancel = clickClosest(ev, "button.rowaction[data-action='promptcancel']");
|
|
250
|
+
if (cancel) {
|
|
251
|
+
await postAction("/api/prompt-cancel", { role: cancel.dataset.role, file: cancel.dataset.file }, (d) =>
|
|
252
|
+
d && d.status === "cancelled"
|
|
253
|
+
? "Cancelled: " + (d.preview || "")
|
|
254
|
+
: "No longer in the inbox — " + (cancel.dataset.role ? roleLabel(cancel.dataset.role) : "the director") + " already took it");
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const edit = clickClosest(ev, "button.rowaction[data-action='promptedit']");
|
|
258
|
+
if (edit) {
|
|
259
|
+
try {
|
|
260
|
+
const d = await getJson("/api/prompt-text?role=" + encodeURIComponent(edit.dataset.role) + "&file=" + encodeURIComponent(edit.dataset.file));
|
|
261
|
+
if (d && d.status === "gone") {
|
|
262
|
+
showFlash("No longer in the inbox — " + (edit.dataset.role ? roleLabel(edit.dataset.role) : "the director") + " already took it");
|
|
263
|
+
refresh();
|
|
264
|
+
} else {
|
|
265
|
+
beginQueuedEdit(edit.dataset.role, edit.dataset.file, (d && d.text) || "");
|
|
266
|
+
}
|
|
267
|
+
} catch (err) {
|
|
268
|
+
showFlash("error: " + err.message);
|
|
269
|
+
}
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const save = clickClosest(ev, "button.rowaction[data-action='prompteditsave']");
|
|
273
|
+
if (save) {
|
|
274
|
+
const area = $("queuedit");
|
|
275
|
+
const text = area ? area.value.trim() : "";
|
|
276
|
+
if (!text) { showFlash("error: prompt text is empty"); return; }
|
|
277
|
+
await postAction("/api/prompt-edit", { role: save.dataset.role, file: save.dataset.file, text }, (d) =>
|
|
278
|
+
d && d.status === "edited"
|
|
279
|
+
? "Edited: " + (d.newText || "")
|
|
280
|
+
: "No longer in the inbox — " + (save.dataset.role ? roleLabel(save.dataset.role) : "the director") + " already took it");
|
|
281
|
+
endQueuedEdit();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const editCancel = clickClosest(ev, "button.rowaction[data-action='prompteditcancel']");
|
|
285
|
+
if (editCancel) {
|
|
286
|
+
endQueuedEdit();
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
const entry = clickClosest(ev, ".backloglink");
|
|
290
|
+
if (entry) toggleEntry(entry.dataset.file, Number(entry.dataset.index));
|
|
291
|
+
});
|
|
292
|
+
`;
|